Go语言及其在大并发环境下的应用张景埕(@diogin)diogin@gmail.com2012.12.08关于我•程序员@360•Go语言忠实追随者与实践者•2010.08开始接触Go语言并喜欢上了它hello,worldFAQ:什么是Go语言?•Google主导开发的一门通用编程语言,BSDL•Unix之父KenThompson、Unix先驱RobPike•C+Python+Limbo(并发)•编译+汇编+链接(主要是静态链接)•简洁、朴素、直观的语法•语法级的并发与自动化垃圾回收支持•卓越的国际化与跨平台支持FAQ:为什么要发明Go语言?•计算机越来越快,软件开发速度却跟不上•C/C++大型项目依赖管理复杂,编译链接等操作太耗时•如何充分利用多核、如何高效地进行并发编程越来越让程序员头疼•没有一门现有语言同时具备快速编译、高效执行、快乐编程三个目标•传统的面向对象语言太注重类型继承,过于臃肿、啰嗦•内存管理、DLL地狱困扰着系统程序员FAQ:Go语言能做什么?•除了编写操作系统内核及其模块之外,Go可以做各种应用级别的开发•当前最适合编写各种服务器程序(Web服务器,应用服务器,游戏服务器,数据存储服务器等等)•GUI桌面应用程序开发(还不成熟)•手机App开发(还不成熟)•教育、科学计算……FAQ:Go语言的目标是什么?•同时具备静态语言的运行效率、动态语言的开发效率•类型安全、内存安全•优秀的并发与通信能力•高效、无延迟的自动化垃圾收集•高速编译与静态链接•丰富、高质量的包FAQ:Go在编程语言谱系中的位置?FAQ:Go在编程语言谱系中的位置?FAQ:Go在编程语言谱系中的位置?一、Go程序运行原理、相关工具、语言规范及标准库(1)Go程序运行原理•静态链接的二进制可执行程序(gc编译器)•只依赖内核,完全不依赖libc/glibc/crt(意味着Go程序自己封装了系统调用)•由多个包(package)联编而成,每个包在加载时进行初始化(通过init()函数),程序运行入口为main包里的main函数•每个程序都带runtime包,进行运行时管理(调度、内存管理、反射、高级数据类型实现、profiling、panic/race管理及检测等)(1)Go程序运行原理:从零开始•以linux/amd64为例(大体步骤):•汇编例程_rt0_amd64_linux()-汇编例程_rt0_amd64()-准备g和m寄存器-命令行参数初始化-OS级别的初始化-调度器初始化(内存管理系统初始化-m初始化-并行能力探测)-runtime·main()-垃圾收集器启动-main·init()-main·main()(1)Go程序运行原理:什么是g/m?•g=goroutine,m=machine(内核线程)•externregisterG*g;•externregisterM*m;•一个m上可以跑多个g,如果一个g进行阻塞式系统调用,其它g可以调度到别的m上继续运行•调度器的任务就是为g选择合适的m来运行(1)Go程序运行原理:包初始化•main包依赖其它包,其它包又依赖其它包,所有的依赖形成一个有向无环图。每个包都可以有多init()函数,在main·init()阶段按深度优先的方式遍历调用以初始化所有的包,直到最后初始化main包。•进入main·main()时,所有的包都完成了初始化。(1)Go程序运行原理:程序视图•一个进程,内部多个goroutine并发运行,多个goroutine共享一个内核线程(machine),goroutine间通过channel进行同步或异步通信。所有goroutine共享进程虚拟内存空间•程序由多个包以无环依赖的形式联接而成,进程开始运行之前全部包已完成初始化•每个包有四种元素:const、var、type、func•自动化的内存管理让进程无需手动管理内存释放,静态链接让进程无需担心动态链接库是否存在(2)相关工具•go:编译汇编链接一体化,模拟脚本解释器等在go开发过程中需要进行的各种操作•godoc:一个在本地运行的官方网站镜像,方便随时查阅•gofmt:鸡血工具,代码格式化,放进scm挂勾,可以让整个团队代码格式完全一致•gotoolfix:在不同Go版本间自动升级代码•文本编辑器/IDE:按自己的爱好自由选择(2)相关工具:Go标准目录结构•通常每个项目有多个程序•一个工程师可能同时开发多个项目•Go使用一个GOPATH模拟一个项目•每个GOPATH有标准的目录结构:–bin/–pkg/–src/(3)Go语言规范概述1•语法溯源:大体上属于C系,有指针(new),有引用(make),“传值”,简洁,容易上手•类型后置式声明,例如:variint=123•“面向对象”:用户定义的类型可以附加方法,功能复用采用的是组合而非继承•“鸭子类型”:只需提供接口声明的方法,该类型就满足接口,无需显式实现该接口•“多返回值”:函数可以返回多个值,错误处理不使用异常机制,而是用多返回值(3)Go语言规范概述2•内存安全:数组边界检查、堆与栈不区分,不存在C里面返回栈空间的地址等陷阱•++、--操作不再是表达式,而成了语句•内置高级数据结构:map、closure•严格的类型大小要求,保证跨平台性•panic/recover/defer:对致命错误及其恢复机制的支持,对延迟调用的支持•goroutine/channel/select:对并发的支持•大小写区分包内元素对外部的可见性(3)Go语言词法元素•源代码采用UTF-8编码(Unicode6.2标准)•注释(//、/*…*/)、标识符(a,int)、关键字(if,go)、操作符(+,-)、整数(123,0xff,0337)、浮点数(3.14,1e9)、虚数(3.14i,1e9i)、字符(’a’,‘中’)、字符串(”go”,“\n”)(3)Go语言规范:关键字(3)Go语言例子:if语句(3)Go语言例子:for语句(3)Go语言例子:switch语句(3)Go语言例子:混合(4)标准库(部分)•runtime:Go程序运行时环境•syscall:系统调用的封装•net:网络协议栈封装•net/http:HTTP协议封装•os:跨平台操作系统API封装•encoding/gob:Go程序进程间通信编码•reflect:反射•sync:同步原语•strings:字符串API•bytes:字节API•……其它•官方网站:•项目托管:•邮件列表:二、Go在大并发环境下的应用并发与并行•并发是一种程序设计模式•Go语言在语法层面支持并发设计•并行是一种可选的程序运行优化方法•并发!=并行并发模型:多进程、多线程、事件?•Nginx:多进程,每个进程单线程(异步非阻塞I/O)•Apache(prefork):多进程,每个进程单线程(同步阻塞I/O)•PHP-FPM:同Apache(prefork)•MySQL:单进程+多线程(同步阻塞I/O)•Memcached:单进程+多线程+异步非阻塞I/O•Redis:单进程+单线程+异步非阻塞I/OGo的并发模型?•单进程,每个进程多个goroutine(同步阻塞I/O。可以有上百万个goroutine,只要你内存足够大)•goroutine(运行单元)•channel(通信通道,分缓冲式、非缓冲式)•select语句(查看并选择可运行的通道操作)•mutex,rwmutex,condition等等低级原语•“Don’tcommunicatebysharedmemory.Instead,sharememorybycommunicating.——RobPike”goroutinegoroutine•Go的基本并发单元•单个goroutine占用5KB左右的栈空间•一个普通的函数(或方法),通过go关键字进行调用,就成了个goroutine•没有父子之说,全部goroutine地位平等•无法在应用层获取goroutineidchannelchannel•Firstclassvalue•Unbuffered(同步)、buffered(异步)•有向:仅接收、仅发送;或双向•通道内可以发送任何firstclassvalue,包括通道自身,甚至一个函数selectselect•某goroutine需要同时侦测多个channel上的事件•如果没有一个channel上有事件发生,同时提供了default分支,则执行default分支实战:实时推送服务•长连接(HTTP,TCP),实时传输•全双工,实现上行发送、下行接收(TCP)•每个长连接三个goroutine,一个负责读(阻塞在read),一个负责写(阻塞在write),一个负责管理与协调(select),三者通过channel进行内部通信系统组件•Room:负责对接客户端•Register:负责记录哪些用户当前连接在哪个room•Saver:存储离线消息•IdGenerator:消息id生成器系统组件——room•需要一个map用来记录用户与服务它的Server实例的对应关系•抽象Server接口,并用HttpServer、TcpServer实现接入协议,每个Server有一个channel用于缓冲消息•抽象Service层可复用API•用户连接时注册在线信息到register,离线时清除•定期心跳方式检测在线系统组件——register•Hash(user)方式存储用户在线信息•存储方式为一个HashTable(采用Gomap)•提供RPC接口(传输协议为gob)•性质类似redis,但方便添加逻辑系统组件——saver•存储抽象层,封装后端存储•全对称方式设计•RPC(gob)系统组件——idGenerator•全局int64数字生成器•100个实例,每个实例负责一部分数字生成•后台goroutine定时存储当前数字•启动时跳过一部分数字,防止重启时串数•RPC(gob)数据•16台服务器,单服务器24核CPU、64GB内存,支撑70万同时在线用户时系统负载0.8,CPU使用率10%•总在线1000(70*16)万用户,日发送心跳包约10亿条•毫无压力Q&ATHANKS张景埕2012.12.08