├── .DS_Store
├── README.md
├── gopher_china_talk.pdf
├── gorpc_design.md
├── img
├── .DS_Store
├── bus
│ ├── .DS_Store
│ ├── bus_architecture.png
│ ├── bus_dev_test.jpeg
│ ├── bus_queue_list.jpeg
│ ├── dashboard_overview.jpeg
│ ├── error_detail.jpeg
│ ├── pepper_bus_overview.png
│ ├── queue_detail.jpeg
│ └── storage_monitor.jpeg
├── cron
│ ├── .DS_Store
│ ├── google_distributed_cron.jpg
│ ├── illustration of progress of a cron launch.jpg
│ ├── monitor_cron.jpeg
│ ├── pepper_cron.svg
│ ├── pepper_cron_1.svg
│ ├── pepper_cron_arch.png
│ ├── pepper_cron_create_job.png
│ ├── pepper_cron_list.png
│ ├── pepper_cron_log.jpeg
│ └── pepper_cron_node_monitor.png
├── gorpc
│ ├── gorpc_edition_1.jpg
│ ├── gorpc_edition_2.jpg
│ └── gorpc_edition_3.jpg
└── pepper
│ ├── design.png
│ ├── design1.png
│ ├── style.jpg
│ └── style1.jpg
├── pepper_golang_train_4beginner.md
├── pepperbus.md
├── peppercron.md
├── rpc_evolution.md
├── zhouyang_GopherChina2019.pdf
└── zhouyang_archsummit.pdf
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | * [创业中台设想与实践 Gopher China 2019 演讲](zhouyang_GopherChina2019.pdf)
2 | * [百万在线直播系统设计 ArchSummit2018 演讲](zhouyang_archsummit.pdf)
3 | * [Go官方博客发表早期Golang实时通信领域的优化案例](https://blog.golang.org/qihoo)
4 | * [花椒直播总线队列系统](pepperbus.md)
5 | * [花椒分布式cron管理平台](peppercron.md)
6 | * [360消息系统Gopher china 2015 演讲](gopher_china_talk.pdf)
7 | * [360消息系统rpc库迭代](rpc_evolution.md)
8 | * [自研发rpc框架分享](gorpc_design.md)
9 | * [花椒内部培训基础篇](pepper_golang_train_4beginner.md)
10 |
11 |
--------------------------------------------------------------------------------
/gopher_china_talk.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/gopher_china_talk.pdf
--------------------------------------------------------------------------------
/gorpc_design.md:
--------------------------------------------------------------------------------
1 | # 亿级在线系统二三事-网络编程/RPC框架
2 |
3 | #####由于 火丁笔记 攻略组投诉我文章太晦涩,所以后续我们可能把之前经历的工作,拆分成多个系列进行讨论。大的背景是拾忆多年前在360构建的亿级在线的Push/IM系统,把一些经验性和知识性的内容分享给大家。回忆梳理了下,构建一个稳定亿级在线系统,涉及下面四种基本能力,我们后续会分系列进行详细追述~ 今天讨论网络编程中的RPC框架的设计~
4 |
5 | 1. 网络编程
6 | * RPC框架设计
7 | 2. 分布式系统设计
8 | 3. 网络和应用层协议栈的理解(TroubleShooting)
9 | 4. 客户端实现与策略
10 |
11 | ## 网络编程/RPC框架设计
12 |
13 | ##### 随着开源技术的活跃,grpc库基本统一了golang分布式系统的rpc框架。近2年很少有团队重新构建自己的rpc库。但你看到的是结果,如果想深入问题本质,真正能够稳定维持和构建一个亿级的实时在线长连接系统,其核心技术 rpc框架的构建和实现,还是有很大价值的。并且裸写一套高性能的rpc框架,也并没有想象那么难。
14 |
15 | #####文中提供的360Push/IM系统使用的rpc框架,也是一个你可以选择的迭代的雏形。在我的laptop上,也可以实现接近20w QPS的吞吐。如果说grpc是内饰高端性能稳定的6缸自然吸气豪华型SUV,我这个原型绝对是改装的8缸涡轮增压小钢炮。
16 |
17 | #####很遗憾我们构建长连服务时候,go语言本身还没有成型的开源方案(标准库里面rpc也还没成型),对于rpc库先后开发3个版本,才完善成型。名字当时定的很大,就叫了gorpc。
18 |
19 | ##### 库设计确实比较早了,现在golang的内存管理和GC优化了很多,所以里面涉及的开销数据会有些失准。但我觉得优化和迭代的思路还是可以借鉴的。
20 |
21 | #### 我个人的版本和公司目前使用还是有一定差别的,但作为一个原型版本是足够用的~ 欢迎大家参考:
22 | `https://github.com/johntech-o/gorpc`
23 |
24 |
25 | 浅谈rpc
26 | ----------------------------------------------
27 |
28 | ####一个基本的RPC库的设计(基于TCP),涉及到几个核心的概念:
29 | * **用户调用接口形式**
30 | * **传输编解码**
31 | * **连接信道的利用**
32 |
33 | ##### **用户调用形式**
34 | 按过程分为:调用者发起请求(call),等待远端完成工作,获取对端响应,三个过程。具体设计上可分为:
35 |
36 | 1. 同步调用:发送请求,等待结果,结果返回调用方。Golang基本上是这种形式。
37 | 2. 异步调用:发送请求,通信和调用交给底层框架处理,用户可以处理其他逻辑,再通过之前返回handler来直接查询和获取处理结果。我们的rpc库里面不涉这种形式的应用层接口,但在golang阻塞编程的习惯下,这种设计略反直觉。
38 | 3. 同步通知:发送请求,但调用方只关心数据是否送达,并不关心结果,无需等待服务端逻辑处理的结果,服务端发现是这种类型调用,直接返回空response到调用方,释放请求方资源。
39 | 4. 细节上通常提供不同级别的超时控制,rpc的接口级别(controller/action),单次调用的超时(同一个接口,每次超时等待不固定),或者针对某一个rpc server的超时控制(remote address),便于用户控制接口的响应时间。
40 | 5. 提供context上下文,比如允许用户显式终止对某些接口的请求,特别是一些流式的数据传输,业务层由于某些原因不关心了,通信层能及时检测到进行终止。
41 |
42 | ##### **传输编解码**
43 |
44 | 1. 我们要在client和Server端传输需要操作的数据对象,比如一个struct,map,或者嵌套的struct,在网络通信的时候,需要编码进行描述,在解码的时候,再根据描述,重新构建内存中的struct对象,即encode生成request,服务端decode。具体编码方式,比如常见的protobuf,msgPack,bson,json,xml,gob等。
45 | 2. 如果要对编解码分类,可能分为文本型和二进制型,比如json,xml这些属于文本型,protobuf,gob属于二进制型。长连接系统主要使用了go语言原生的gob编码,好处是简单,使用的数据结构可以直接gob传输,相比较protobuf减少了写描述环节。缺点就是不能跨语言了~ 当然如果你要将我们的gorpc库改写成pb编码的,也是很快的。
46 | 3. 具体编码的实现上,对于一个高性能rpc框架来说,性能是一个瓶颈点。可以仔细查看源码,是否有编解码过程中的内存复用,与对象复用。否则编解码开销很大,另外看好复用的上下文是什么,是全局的,还是针对连接的。全局的是否有竞争,针对连接的,是否要使用长连接,什么时候释放资源。
47 |
48 | #### **连接信道利用**
49 | 1. 用户调用的协成与具体物理连接的对应关系,通常在golang环境下,同一个remote address的所有rpc调用,全局共享一个连接池。
50 | 2. 连接池内连接管理,数量的控制,包括连接的动态扩增,连接状态监测,连接回收。
51 | 3. 高效的读写超时的控制(read/write deadline),及其优化,据说现在应用层不需要做太多的策略,底层做了timer管理的sharding了。
52 | 4. 类似多路复用的支持与设计,所有协成仍旧是阻塞等待输出,但底层会汇总所有协成的请求数据,复用连接批量发送给目的地址,目的服务器返回的数据,在rpc底层分发给调用方。由于不是1对1的映射物理连接,一个连接上的所有出错操作,也必须能告知所有复用连接的调用协程。所谓的小包打满万兆网卡,其实就是要充分利用好连接池连接的通信效率,汇聚数据,统一传输。减少系统调用次数。
53 |
54 |
55 | 早期RPC框架
56 | -----------------------------------------------------
57 | #### 早期的长连接系统,使用的策略是:同步调用+短连接+动态创建所有buffer+动态创建所有对象这种方式,在初期很快的完成了原型,并且在项目初期,通信比较少情况下,稳定跑了近半年时间。通信时序图如下:
58 |
59 | 
60 |
61 | * 随着业务放量,推送使用频率的增长,业务逻辑的复杂。瓶颈出现,100w连,稳定服务后,virt 50G,res 40G,左右。gc时间一度达到3~6s,整个系统负载也比较高。(12年,1.0.3版本)
62 | * 其实早期在实现的时候,选择动态创建buffer和object,也不奇怪,主要是考虑到go在runtime已经实现了tcmalloc,并且我们相信它效率很高,无需在应用层实现缓存和对象池。(当年没有sync.pool)
63 | * 但实际上高并发下,使用短连接,pprof时候可以看到,应用层编解码过程建立大量对象和buffer外,go的tcp底层创建tcpConnection也会动态创建大量对象,具体瓶颈在newfd操作。整体给GC造成很大压力。
64 |
65 |
66 | RPC通信框架第一次迭代
67 | --------------------------------------------------
68 | #### 针对这种情况,我对通信库做了第一次迭代改造。
69 | 1. 使用长连接代替短连接,对每一个远端server的address提供一个连接池,用户调用,从连接池中获取连接,对应下图中get conn环节。用户的一次request和response请求获取后,将连接放入连接池,供其他用户调用使用。因此系统中能并行处理请求的数量,在调度器不繁忙的情况下,取决于连接池内连接数量。假设用户请求一次往返加服务端处理时间,需要消耗10ms,连接池内有100个连接,那每秒钟针对一个server的qps为1w qps。这是一个理解想情况。实际上受server端处理能力影响,响应时间不一定是平均的,网络状况也可能发生抖动。这个数据为后面讨论pipeline做准备。
70 | 2. 对连接绑定buffer,这里需要两个buffer,一个用于解码(decode),从socket读缓冲获取的数据放入decode buffer,用户对读到的数据进行解码,即反序列化成应用层数据结构。一个用户编码(encode),即对用户调用传入的所有参数进行编码操作,通过这个缓冲区,缓存编码后的一个完整序列化数据包,再将数据包写入socket 写缓冲。
71 | 3. 使用object池,对编解码期间产生的中间数据结构进行重复利用,注意这里并不是对用户传递的参数进行复用,因为这个是由调用用户进行维护的,底层通信框架无法清楚知道,该数据在传输后是否能够释放。尤其在使用pipeline情况下,中间层数据结构也占了通信传输动态创建对象的一大部分。我们当时开发时候,还么有sync.Pool~,看过实现,方式类似,我觉得性能应该类似。
72 | 4. 改动后,通信图如下,上图中红线所带来的开销已经去除,换成各种粒度的连接池和部分数据结构的对象池。具体细节,后面说明
73 |
74 | ####rpc框架第二版使用策略:同步调用+连接池+单连接复用编解码buffer +复用部分对象
75 |
76 | 
77 |
78 | * 这种方式,无疑大大提高了传输能力,另外解决了在重启等极限情况下,内部通信端口瞬时会有耗尽问题。内存从最高res 40G下降到20G左右。gc时间也减少3倍左右。这个版本在线上稳定服务了接近一年。(13年左右数据)
79 |
80 | RPC通信框架二次迭代
81 | ---------------------------------------
82 |
83 | 但这种方式对连接的利用率并不高,举例说明,用户调用到达后,从连接池获取连接,调用完成后,将连接放回,这期间,这个连接是无法复用的。设想在连接数量有限情况下,由于个别请求的服务端处理延时较大,连接必须等待用户调用的响应后,才能回放到连接池中给其他请求复用。用户调用从连接池中获取连接,发送request,服务端处理10ms,服务端发送response,假设一共耗时14ms,那这14ms中,连接上传输数据只有4ms,同一方向上传输数据只有2m,大部分时间链路上都是没有数据传输的。但这种方式也是大多目前开源软件使用的长连接复用方案,并没有充分利用tcp的全双工特性,通信的两端同时只有一方在做读写。这样设计好处是client逻辑很简单,传输的数据很纯粹,没有附加的标记。在连接池开足够大的情况下,网络状况良好,用户请求处理开销时长平均,这几个条件都满足情况下,也可以将server端的qps发挥到极限(吃满cpu)。
84 |
85 | 另一种方案,是使整个框架支持pipeline操作,做法是对用户请求进行编号,这里我们称做sequence id,从一个连接上发送的所有request,都是有不同id的,并且client需要维护一个请求id与用户调用handler做对应关系。服务端在处理数据后,将request所带的请求的sequence id写入对应请求的response,并通过同一条连接写回。client端拿到带序号的response后,从这个连接上找到之前该序号对应的用户调用handler,解除用户的阻塞请求,将response返回给request的调用方。
86 |
87 | 对比上面说的两个方案,第二个方案明显麻烦许多。当你集群处于中小规模时候,开足够的连接池使用第一种方案是没问题的。问题是当系统中有几百个上千个实例进行通信的时候,对于一个tcp通信框架,会对几百个甚至上千个需要通信的实例建立连接,每个目标开50个到100个连接,相乘后,整个连接池的开销都是巨大的。而rpc请求的耗时对于通信框架是透明的,肯定会有耗时的请求,阻塞连接池中的连接,针对这种情况调用者可以针对业务逻辑做策略,不同耗时接口的业务开不同的rpc实例。但在尽量少加策略的情况下,使用pipline更能发挥连接的通信效率。
88 | pipeline版本的rpc库,还加入了其他设计和考虑,这里只在最基础的设计功能,进行了讨论,下图是,第三版本rpc库,对比版本二的不同。
89 |
90 | 
91 |
92 | 1. 如上图所示,两次rpc调用可以充分利用tcp全双工特性,在14ms内,完成2次tcp请求。在server端处理能力非饱和环境下,用户调用在连接池的利用上提高一个量级,充分利用tcp全双工特性,让连接保持持续活跃。其目的是可以用最少的连接实现最大程度并发,在集群组件tcp互联通信的情况下,减少因为请求阻塞造成的连接信道浪费。
93 | 2. 以上对消息系统rpc通信框架的迭代和演进进行了说明。只是对通信过程中基础环节和模型做了粗线条介绍。第三版本的rpc通信框架,其实为了适应分布式系统下的需求,需要辅助其他功能设计。每个细节都决定这个库能否在中大规模分布式环境中下是否试用,或者说是否可控,我们将在下一章里面详细介绍。
94 |
95 | ### 小结
96 | 以上就是设计环节~,我们后续可能讨论下一个rpc库的具体实现~ 看代码确实会通俗易懂很多,欢迎关注公众号,了解后续内容。
97 |
98 |
99 |
--------------------------------------------------------------------------------
/img/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/.DS_Store
--------------------------------------------------------------------------------
/img/bus/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/.DS_Store
--------------------------------------------------------------------------------
/img/bus/bus_architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/bus_architecture.png
--------------------------------------------------------------------------------
/img/bus/bus_dev_test.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/bus_dev_test.jpeg
--------------------------------------------------------------------------------
/img/bus/bus_queue_list.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/bus_queue_list.jpeg
--------------------------------------------------------------------------------
/img/bus/dashboard_overview.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/dashboard_overview.jpeg
--------------------------------------------------------------------------------
/img/bus/error_detail.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/error_detail.jpeg
--------------------------------------------------------------------------------
/img/bus/pepper_bus_overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/pepper_bus_overview.png
--------------------------------------------------------------------------------
/img/bus/queue_detail.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/queue_detail.jpeg
--------------------------------------------------------------------------------
/img/bus/storage_monitor.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/bus/storage_monitor.jpeg
--------------------------------------------------------------------------------
/img/cron/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/.DS_Store
--------------------------------------------------------------------------------
/img/cron/google_distributed_cron.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/google_distributed_cron.jpg
--------------------------------------------------------------------------------
/img/cron/illustration of progress of a cron launch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/illustration of progress of a cron launch.jpg
--------------------------------------------------------------------------------
/img/cron/monitor_cron.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/monitor_cron.jpeg
--------------------------------------------------------------------------------
/img/cron/pepper_cron.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
475 |
--------------------------------------------------------------------------------
/img/cron/pepper_cron_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
245 |
--------------------------------------------------------------------------------
/img/cron/pepper_cron_arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/pepper_cron_arch.png
--------------------------------------------------------------------------------
/img/cron/pepper_cron_create_job.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/pepper_cron_create_job.png
--------------------------------------------------------------------------------
/img/cron/pepper_cron_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/pepper_cron_list.png
--------------------------------------------------------------------------------
/img/cron/pepper_cron_log.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/pepper_cron_log.jpeg
--------------------------------------------------------------------------------
/img/cron/pepper_cron_node_monitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/cron/pepper_cron_node_monitor.png
--------------------------------------------------------------------------------
/img/gorpc/gorpc_edition_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/gorpc/gorpc_edition_1.jpg
--------------------------------------------------------------------------------
/img/gorpc/gorpc_edition_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/gorpc/gorpc_edition_2.jpg
--------------------------------------------------------------------------------
/img/gorpc/gorpc_edition_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/gorpc/gorpc_edition_3.jpg
--------------------------------------------------------------------------------
/img/pepper/design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/pepper/design.png
--------------------------------------------------------------------------------
/img/pepper/design1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/pepper/design1.png
--------------------------------------------------------------------------------
/img/pepper/style.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/pepper/style.jpg
--------------------------------------------------------------------------------
/img/pepper/style1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/img/pepper/style1.jpg
--------------------------------------------------------------------------------
/pepper_golang_train_4beginner.md:
--------------------------------------------------------------------------------
1 | # 花椒golang培训基础篇(持续更新中)
2 |
3 | ## 1. 背景与起源
4 |
5 | 
6 |
7 | #### 1.1 作者都是资深的c和c++程序员,都就职于google.
8 | * Thompson是图灵奖得主,unix之父,C语言的前身,B语言的创造者,plan9操作系统创始人之一.
9 | * Rob Pike也是Unix开发组成员,另外和KenThompson一起开发了UTF-8编码.go语言的前身,Limbo语言的作者
10 | * Robert Griesemer 是v8 JavaScript引擎作者 google GFS开发者之一(15年有幸见到作者,并与go team产品和技术邮件协作,在官博发表了golang在实时通信领域的案例: https://blog.golang.org/qihoo )
11 |
12 | #### 1.2 关于plan9操作系统:
13 | * all system interfaces, including those required for networking and the user interface, are represented through the file system rather than specialized interfaces
14 | * 一个理想化的操作系统,与常用操作系统API和设计理念差距有点大,没能得到广泛推广。但Plan9操作系统的建立,衍生了plan9 C和plan9 汇编,两个可以被复用的强大体系,对日后go语言的发展有很大影响。plan9和go语言的关系,从plan9的logo,这个大家耳熟能详的兔子,就能感受出来
15 |
16 | #### 1.3 golang与plan9
17 |
18 | * 在经过plan9之后,rob pike发明了现代go语言的前身,limbo语言,这个语言是建立在虚拟机之上的。跨平台方案固然不错,但效率上可能与c和c++比,还有差距。加入google之后,三位专家,根据google使用c和c++现状,发明了其一个替代方案,即go语言。09年发布,并对内部推广。
19 |
20 | #### 1.4 golang早期问题
21 |
22 | * 360消息系统大概建立时间是2012年6月份,当时是1.0版本左右。还有很多bug,比如,想sleep 1s,本来可以调用语言原生api(sleep)的。但由于有内存泄漏,需要自己封装。Map在较大的时候,内存有泄漏,等等坑。不过这些目前已经修复了... go的版本迭代还是比较稳健的,1.1版本有大的改动,就是支持并行gc了,1.2版本都是小的修改。1.3版本又有很多新的改进,增加了新特性,可以看下官方ppt。
23 |
24 |
25 | ## 2. Why GO ?
26 | 
27 |
28 | ### 2.1诞生背景
29 | ##### 编译速度
30 | * Google的中服务器最主要是C++编写的,除此之外还有很多Java、Python代码。另外,Google还有数千名工程师、无数行代码、庞大的分布式构建系统以及数不清的机器(我们认为相对于一个中等规模的集群)。Google的开发可能很慢,甚至笨拙,但它总是很有效。即使在Google的分布式构建系统的的帮助下,大型构建工程依然会花费不少时间(以其中一个二进制文件为例,在2007年花了45分钟,现在是27分钟)。生活质量还是太低。
31 | * 而Go语言则是为了解决这些问题而设计的,它在语言设计的时候,对编译速度都作为一个重要的指标来考核。目前360长连服务代码,几百个文件,全部编译一次,不超过10s,单独编译某一个组件,时间在秒级别。基本上可以保证开发调试,dump数据,马上编译通过,运行,来看结果。非常方便.
32 |
33 | ##### 依赖性控制
34 | * C语言的依赖一直是个大问题,包括依赖叠加、编译时引入依赖的情况都很难处理,同时你也没办法查清哪些依赖是可以删除的,那些不可以。在C++中,这一点变得更加明.
35 |
36 | ##### 设计理念
37 | * 适合大型程序,大型团队以及拥有大量依赖的应用。所谓的大型程序,大量依赖的应用,缺点往往是运维成本很高。go原因不需要安装各种让人头疼的依赖,不需要搭建各种运行环境,部署非常方便。
38 | > 从C++和Java转向Go的程序员很怀念使用类型编程的方式,特别是继承和子类这样的概念。当你用类型编程思想,设计对外接口的时候,你需要决定什么东西封装在什么里面,父类型是什么?两个相关的类,到底是A继承B还是B继承A。一个好的设计,是一个好的抽象过程。go语言设计者认为,把精力集中于这些有点荒谬,真正重要的是这些类能为你做什么而不是它们之间是什么关系!如果说C++和Java是关于类的层次和分类,那么Go的核心思想就是组合(composition)
39 |
40 | * 易于接近。事实上go语言的设计是这样评价的:以C语言为原型,以c++为目标设计。修补部分明显的缺陷,去掉垃圾功能,添加一些缺失的功能。既然go语言源自c语言,大量的c语言开发程序员,可以很快上手。
41 |
42 | > Rob提到:事实上在google内部golang吸引了更多的ruby,python程序员。c++程序员的不安全感我感觉占了很大因素。既然大家选择了c/c++总是希望,一切可控,当go的好处是希望让程序员的工作更简单。
43 |
44 | * 适合多核架构,适合网络开发,适合并发场景的开发,io密集型
45 | > 对于是否适合web开发,我保持沉默,PHP是世界上最好的语言。
46 | * 清晰的依赖,清晰语法,清晰语义(鸭子类型,静态编译,阻塞编程)
47 | > 告别依赖的静态编译,丢弃类型继承和多态,使用鸭子类型的接口隐式实现。内建string,slice,map常规数据结构,没有模板,没有异常。
48 | * 简单的模型
49 | > 垃圾回收,并发,跨平台对开发者提供了友好的屏蔽。C++程序员不愿意转向Go,是因为他们竭尽全力为了对自己的程序的完全掌控。对于他们,软件并不仅是完成工作,而是更极致的做好工作。但golang带来的生产力上的解放,避免各种复杂体系开源库的拼凑(boost?),一定程度带了了另一种层次的对程序的完全掌控。更像哲学问题,不讨论。
50 |
51 | * 便捷的工具(go tool gofmt godoc gofix)
52 |
53 | > Go在设计上做了一个方向性的定位,并没有提及UI,数据分析等方向。虽然现在很多热心的项目在把go往这些方向发展。
54 |
55 | ## 3. golang开发必备条件反射
56 | ### 循环中的协程使用变量问题
57 | CASE 1 打印10个数字,基础并发与乱序,当替换成复杂场景是否能review出来问题所在
58 |
59 | ```
60 | func main() {
61 | for i := 0; i < 10; i++ {
62 | go func() {
63 | fmt.Println(i)
64 | }()
65 |
66 | ```
67 |
68 | ```
69 | func main() {
70 | for i := 0; i < 10; i++ {
71 | go func(i int) {
72 | fmt.Println(i)
73 | }(i)
74 | }
75 | ```
76 |
77 | ### Slice and Array
78 | * 理解make的两个参数,理解底层reslice实现,注意初始化时的优化
79 |
80 | ```
81 | // https://play.golang.org/p/YQEYuQCF12
82 | package main
83 |
84 | import (
85 | "fmt"
86 | )
87 |
88 | func main() {
89 | s := make([]int, 3, 3)
90 | s = []int{1,2,3}
91 |
92 | // another := []int{4,5,6}
93 | another := []int{4,5}
94 |
95 | s2 := append(s[:1], another...)
96 |
97 | fmt.Println(s,s2)
98 |
99 | }
100 |
101 | ```
102 |
103 | ### defer的使用
104 | * 强迫症同学要注意 请对比运行代码段1与代码段2是否有区别,请对比运行代码段3是否有区别。线上现象如偶发崩溃时候,注意review这个细节。
105 |
106 | ```
107 | // 代码段1
108 | // https://play.golang.org/p/I6j5oIiDZ0
109 | package main
110 | import (
111 | "fmt"
112 | "os"
113 | )
114 | func main() {
115 | file, err := os.Open("foo.txt")
116 | defer file.Close()
117 | if err != nil {
118 | fmt.Println("file name is: ", file)
119 | return
120 | }
121 | }
122 | ```
123 |
124 | ```
125 | // 代码段2
126 | package main
127 | import (
128 | "fmt"
129 | "os"
130 | )
131 |
132 | func main() {
133 | file, err := os.Open("foo.txt")
134 | if err != nil {
135 | fmt.Println("file name is: ", file)
136 | return
137 | }
138 | defer file.Close()
139 | }
140 | ```
141 |
142 |
143 | ```
144 | // 代码段3
145 | package main
146 |
147 | import (
148 | "errors"
149 | "fmt"
150 | )
151 |
152 | func main() {
153 |
154 | b, err := open("")
155 | defer b.close()
156 | if err != nil {
157 | return
158 | }
159 | // defer b.close()
160 | }
161 |
162 | type myint int
163 |
164 | func (mt *myint) string() {
165 | fmt.Println("result is:", *mt * *mt)
166 | }
167 |
168 | func open(fileName string) (*myint, error) {
169 | if fileName == "" {
170 | return nil, errors.New("there is a error")
171 | }
172 | var b myint = 5
173 | return &b, nil
174 | }
175 |
176 | func (mt *myint) close() {
177 | fmt.Println("close: ", *mt)
178 | mt.string()
179 | }
180 |
181 | ```
182 | 上面两个例子想强调问题:反复阅读官方文档,其实官方文档都有说明
183 | > the arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes. Besides avoiding worries about variables changing values as the function executes, this means that a single deferred call site can defer multiple function executions. Here's a silly example.
184 |
185 | * 容易忽略的问题,闭包对变量的“延迟”读取
186 |
187 | ```
188 | // tell me the difference
189 | func (this *object) test(){
190 | defer this.redis.close()
191 | this.redis = getNewRedis(redis)
192 | }
193 | func (this *object) test(){
194 | defer func(){
195 | this.redis.close()
196 | }()
197 | this.redis = getNewRedis(redis)
198 | }
199 | ```
200 |
201 |
202 | ### 方法集
203 | 读官方文档比较绕的环境,建议大家寻找和补充例子:
204 | > A type may have a method set associated with it.
205 | >
206 | > The method set of any other type T consists of all methods declared with receiver type T.
207 | >
208 | > The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).
209 | >
210 | > The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.
211 |
212 | ```
213 | package main
214 |
215 | import "fmt"
216 |
217 | type t1 int
218 | type t2 int
219 |
220 | func (t *t1) String() string { return "ptr" }
221 |
222 | func (t t2) String() string { return "val" }
223 |
224 | func main() {
225 |
226 | // var a t1 // type T consist all method declared with receiver type t1
227 | // var ai fmt.Stringer = a // Error: cannot use a (type t1) as type fmt.Stringer in assignment
228 |
229 |
230 | var b *t1 // type *T is the set of all methods declared with receiver *t1 or t1
231 | var c t2 // type T consist all method declared with receiver type t2
232 | var d *t2 // type *T is the set of all methods declared with receiver *t2 or t2 当调用string方法时候,仍旧是receiver value copy
233 |
234 | var bi, ci, di fmt.Stringer = b, c, d
235 |
236 | fmt.Println(bi, ci, di)
237 | fmt.Println(b, c, d)
238 |
239 | }
240 |
241 |
242 | ```
243 |
244 | ### timer相关
245 | * 请描述下面程序问题,什么时候打印 "10s timer"
246 |
247 | ```
248 | func demo(input chan interface{}) {
249 | for {
250 | select {
251 | case msg <- input:
252 | println(msg)
253 |
254 | case <-time.After(time.Second * 5):
255 | println("5s timer")
256 |
257 | case <-time.After(time.Second * 10):
258 | println("10s timer")
259 | }
260 | }
261 | }
262 | ```
263 |
264 | * CASE 6 这段代码,每秒钟调用个位数,运行一段时间后,会导致server间歇性cpu超载(1.5版本)
265 |
266 | ```
267 | func eventNone(evtReq *model.Event) (*model.Event, error) {
268 | // 此处省去50行
269 | timer := time.NewTicker(time.Duration(EventInterval) * time.Second)* time.Second)
270 |
271 | for {
272 | select {
273 | case <-timer.C:
274 | return nil, nil
275 | case evt := <-node.Event:
276 | return &evt, nil
277 | }
278 | }
279 | }
280 | ```
281 | > 谨慎使用全局维护状态的函数,对于phper来说,除了考虑业务逻辑和性能之外,转型golang要注意理解库函数使用的系统资源或者对象资源,思考资源如何被释放
282 |
283 |
284 | * 加深下资源管理的印象,求下面代码问题
285 |
286 | ```
287 | var wait sync.WaitGroup
288 | for _, address := range logic.Setting.RoomAdmins {
289 | wait.Add(1)
290 | go func(address string) {
291 | client := network.NewHttpClient()
292 | if err := client.ConnectTo(address, logic.Setting.InternalConnectTimeout); err != nil {
293 | return
294 | }
295 | defer client.Close()
296 | defer wait.Done()
297 | response, err := client.Get("/book/len?product="+string(product), logic.Setting.InternalReadTimeout, logic.Setting.InternalWriteTimeout)
298 | Logger.Debug(address + response.BodyString())
299 | if err != nil {
300 | return
301 | }
302 | if count, err := strconv.Atoi(response.BodyString()); err == nil {
303 | amountLock.Lock()
304 | amount += count
305 | amountLock.Unlock()
306 | }
307 | }(address)
308 | }
309 | wait.Wait()
310 |
311 | ```
312 |
313 | ### Interface的使用
314 |
315 | ```
316 | package main
317 |
318 | import "fmt"
319 |
320 | func main() {
321 | var data *byte
322 | var in interface{}
323 |
324 | fmt.Println(data,data == nil)
325 | fmt.Println(in,in == nil)
326 | in = data
327 | fmt.Println(in,in == nil)
328 | }
329 | ```
330 |
331 | ```
332 | package main
333 |
334 | import "fmt"
335 |
336 | func main() {
337 | doit := func(arg int) interface{} {
338 | var result *struct{} = nil
339 |
340 | if(arg > 0) {
341 | result = &struct{}{}
342 | }
343 |
344 | return result
345 | }
346 |
347 | if res := doit(-1); res != nil {
348 | fmt.Println("good result:",res)
349 | }
350 | }
351 | ```
352 | > 还有更隐晦的,gorpc库的error坑
353 |
354 |
355 | ```
356 | package main
357 |
358 | import (
359 | "fmt"
360 | "time"
361 | )
362 |
363 | // MyError is an error implementation that includes a time and message.
364 | type MyError struct {
365 | When time.Time
366 | What string
367 | }
368 |
369 | func (e MyError) Error() string {
370 | return fmt.Sprintf("%v: %v", e.When, e.What)
371 | }
372 |
373 | func oops() error {
374 | // return MyError{
375 | // time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
376 | // "the file system has gone away",
377 | // }
378 | var e *MyError
379 | fmt.Println(e == nil)
380 | return e
381 | }
382 |
383 | func main() {
384 | if err := oops(); err != nil {
385 | fmt.Println("err != nil",err)
386 | }
387 | }
388 | ```
389 |
390 | ## 4. 风格讨论
391 | ### Avoid nesting by handling errors first
392 |
393 | ```
394 | type Gopher struct {
395 | Name string
396 | AgeYears int
397 | }
398 | func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
399 | err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
400 | if err == nil {
401 | size += 4
402 | var n int
403 | n, err = w.Write([]byte(g.Name))
404 | size += int64(n)
405 | if err == nil {
406 | err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
407 | if err == nil {
408 | size += 4
409 | }
410 | return
411 | }
412 | return
413 | }
414 | return
415 | }
416 | ```
417 | ```
418 | func (g *Gopher) WriteTo(w io.Writer) (size int64, err error) {
419 | err = binary.Write(w, binary.LittleEndian, int32(len(g.Name)))
420 | if err != nil {
421 | return
422 | }
423 | size += 4
424 | n, err := w.Write([]byte(g.Name))
425 | size += int64(n)
426 | if err != nil {
427 | return
428 | }
429 | err = binary.Write(w, binary.LittleEndian, int64(g.AgeYears))
430 | if err == nil {
431 | size += 4
432 | }
433 | return
434 | }
435 |
436 | ```
437 |
438 |
439 | ### 加锁解锁写法的折中,可控or可读
440 |
441 | 1.风格A
442 | 
443 |
444 | 2.风格B
445 |
446 | 
447 |
448 |
449 |
450 |
451 |
--------------------------------------------------------------------------------
/pepperbus.md:
--------------------------------------------------------------------------------
1 | # Golang实践之花椒直播总线系统
2 |
3 |
4 | 总线系统大家并不陌生,各个业务团队都有自己的数据流的处理策略,一般都是基于开源的,能提供队列数据结构的产品,进行包装构建。相比较传统业务,直播场景大型活动中洪峰流量较大,因此业务形态上有20%左右的处理,都采取了异步化。早期内部也有多套解决方案,队列存储部分有基于redis,nsq,kafka的。异步任务消费过程有cron消费,deamon消费,自研发的processWorker(使用php,基于父子进程方式,进行进程管理的工具)。子团队根据需要和技术栈选择和自行维护。如何针对各个业务线有一个统一的异步处理系统,是花椒系统研发团队需要面临的问题.
5 |
6 | 我们不是要造一个队里服务,而是要基于花椒现有的应用,提供一个统一的队列资源管理,运维,监控系统。并且针对不同技术栈的团队进行定制。提供统一友好的可视化服务。
7 |
8 | 目前服务上线以来,已经迁移和承接了花椒直播大部分的异步任务。由于整套服务由于依赖了一些自研发的根据内部场景定制的服务治理等工具,暂时没有对外开源。考虑在Gopher China之后进行开源。
9 |
10 | ## 需求分析
11 | 1. 如何做到让公司的所有子系统,微服务中涉及的异步任务能够可衡量,可控制,使用成本低,科学扩容,并且扩容对业务透明。
12 | 2. 如何让异步任务更方便调试,从加入,消费,处理,都能够显示的给出,出现的问题报错也能显示的说明。
13 | 3. 能够让公司所有业务统一接入,统一管理,而不是分别构建很多子队列系统,分开运维,监控。
14 | 4. 对PHP业务要友好,公司的主要线上队列服务使用php,用法是基于php的配置管理+进程管理框架(processWorker)+redis,能快速替换该方案,否则推广是一个问题。(当然现在java,golang也占一部分)
15 |
16 | ## Pepperbus设计
17 | ###基于以上需求,我们展开对总线队列的设计。
18 | 1. 首先存储选型,大趋势来说,肯定是插件化的,无论提供什么样底层存储,应用层提供统一接口。这一点设计上上很容易做到。
19 | 2. 第二,入口的通信协议,一定要对调用端友好,因为团队并不想花很多时间,去实现各个语言的总线sdk,所以入口协议我们选择了redis协议,无论长连接还是短连接,连接池,异常处理等细节在各种语言上已经很完备了,只需在逻辑层简单封装即可。既数据生产(add job),采用redis协议。
20 | 3. 最后,最重要的就是任务管理,一种模式是开进程,消费总线使用的存储中的数据,过程计时打点,同时总线系统提供必要的进程管理功能(SDK).因为我并不想让业务接入各种数据打点的sdk,想从总线上直接获取第一手的数据采集,针对php的场景,很自然想到了,能否直接与fastcgi进行通信,通信的响应时间,其实就是对于任务的处理时间,同时基于这种方式,可以直接享受到php-fpm可以动态的更新代码的红利。我们异步任务的脚本,也能平滑动态进行更新。当然后期针对fpm的数据转发和通信,包括php-fpm和脚本各种执行时间的配置,我们有过不少的挫折,但后来都迎刃而解。团队之前本身也是负责360的长连接相关技术,对于网络的处理还是有一定的经验的~
21 | 5. 最后就是针对其他语言,直接提供http接口的任务转发。这样一来,方便debug这一条需求也能覆盖住,其实后续所有的异步任务,对于订阅者来说,都是从post请求等获取,理论上本地可以通过curl命令调试异步任务。当然我们提供了一个丰富的dashboard可以完成这个工作,并且测试环境可以获取历史数据,进行重放,减少大家构建请求的时间。
22 | 6. **业务驱动的Dashboard,队列增删查改,trouble shooting,重放,报警,监控。重新封装一套总线服务的目的,就是要解决业务痛点,最大程度的减少使用复杂度,提供更多有意义的信息。项目成功这一非硬核技术环节很重要。要好用,要好用,要好用。**
23 |
24 | ## PepperBus Features
25 | ### 基于上面的设计,最终总线特征如下:
26 | - 支持redis协议作为总线数据输入,充分利用业务端成熟的redis扩展,对业务端友好,迁移和改造成本低
27 | - 通过fastcgi协议与后端通信,与实际的业务逻辑解耦,方便的支持php和golang等多种语言接入
28 | - 数据的输入和输出对于长连接友好,解决LNMP解决方案,长连接情况下,由于集群连接过多给后端服务造成压力,通过代理方式与最终的后端服务做连接复用,对输入不限制长连,对输出维持有限长连接
29 | - 总线队列支持常规的生产者和消费者模式,消息可以按订阅关系重复消费
30 | - 全链路监控策略,可以获取流水线作业的操作序列,完整自描述其关系
31 | - 队列拥塞状态监控,通过与消费者协议交互,反馈处理异常的队列,计算队列消费者处理能力,动态扩容消费进程
32 | - 总线支持定时任务的设置和管理,动态控制和迁移定时任务,解决常规的crontab的管理问题
33 | - 总线队列支持平滑重启,平稳恢复所有任务
34 |
35 | ## 系统架构图
36 |
37 |
38 | ### 架构描述
39 | 1. 业务服务器可以与bus总线实例混合部署也可以独立部署,混合部署时可以优先访问本地总线服务器,独立分离部署可以与总线服务用域名进行通信
40 | 2. 总线服务器与php-fpm交互可以通过本地也可以通过网络
41 |
42 | ### 业务处理流程图说明
43 | 1. 业务服务器将任务加入到总线服务
44 | 2. 总线服务存储任务到存储
45 | 3. 将总线任务传输给cgi进程
46 | 4. cgi进程返回处理结果
47 | 5. 总线移除处理任务
48 | - 业务服务器可以与bus总线实例混合部署也可以独立部署,混合部署时可以优先访问本地总线服务器,独立分离部署可以与总线服务用域名进行通信
49 | - 总线服务器与php-fpm交互可以通过本地也可以通过网络
50 | - 总体架构图:
51 |
52 | 
53 |
54 | ###整体架构图
55 | ### 调用部署
56 | 
57 |
58 | ## 项目依赖的其他组件
59 |
60 | - dashboard: 提供用户管理的web ui。
61 | - gokeeper: 提供配置管理服务。
62 |
63 |
64 | ## 用户使用演示
65 | 用户使用演示
66 |
67 | 1. 后台整体情况,可以按系统进行区分
68 | 
69 | 2. 队列列表管理
70 | 
71 | 3. 队列实时监控,可以进行重放清理等设定,新版已经支持灰度重放等。同时会实时显示请求延时,如图:第一列90%的请求在2146ms内完成。
72 | 
73 | 4. 显示错误日志查看,可以定位到具体消息id,如果php报错,也会正常展示。
74 | 
75 | 5. 方便的测试dashboard,可以将测试历史数据和本地保存的线上历史数据,直接加入bus,拿到异步处理结果
76 | 
77 | 6. 对集群使用的存储进行监控(目前支持pika和redis),扩容对业务透明
78 | 
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/peppercron.md:
--------------------------------------------------------------------------------
1 | 大家好~ 我是 花椒直播 周洋,上次火丁笔记投稿了[《花椒总线系统》](http://mp.weixin.qq.com/s?__biz=Mzg5NTEzODk4Mg==&mid=2247483688&idx=1&sn=7285e38c876c068bc6cd6dbe55ec5b6f&chksm=c015ace1f76225f78e6d199f8fd2d8c829e36df624ef81e8ff4a548aec3686539805b8ffa9b2#rd)后,发现阅读量较大,但点赞(看一看)数很少。受到了公众号攻略组的调侃。**要求 改善或解释 点赞量过低的原因。**
2 |
3 | 针对这个需求,我决定在技术文章后增加一个互联网圈的时事讨论环节。所以大家对我的内容不感兴趣的,可以直接跳转到话题讨论环节。**并且如果大家 不认可 我的话题观点,可以通过 点赞 的方式辅助我做运营统计。对,是不认可才点赞**
4 |
5 | #### [今天的话题是《创业公司是否需要中台?应该是一个什么样的中台?》](#jump_zhongtai)
6 |
7 | # Google大规模分布式Cron系统的最简实现
8 |
9 | ### 首先给出行业内的高配版本实现的出处,来自于google的两篇设计cron系统的论文。
10 | 1. 《Distributed Periodic Scheduling with Cron》
11 | 2. 《Reliable Cron across the Planet》
12 |
13 | ### 这两篇文章,是一个很好的分布式cron产品需求文档,描述的需求大概如下:
14 |
15 | 1. 定时任务的高可用策略,包括对任务本身容错性等级,是否满足幂等性,执行状态的监控,以及任务所在服务器down机情况下的处理
16 | 2. 大规模cron管理的部署问题,包括可靠的调度系统保障cron的执行和失败处理能够跨主机的迁移,对于失败任务进行补偿执行等。
17 | 3. cron job迁移过程中,如果保障中间结果数据和状态数据的迁移,如何管理或保障迁移后,新服务器也具备原有cron进程具备所有依赖的环境.
18 | 4. cron job的隔离性,以及cron job的按硬件资源的分配和调度问题。运行间隔短暂的job,在调度过程中保障实时性和按时执行的准确性问题。
19 |
20 | ### 论文中概述的实现方式如下:
21 |
22 | 1. cron的状态存储,并没有选用gfs和hdfs,而是作为这套cron分布式系统子服务的一部分,原因是这套系统的高可用级别需要尽可能减少外部服务的依赖,另外高频与小文件的读写需求,使用gfs和hdfs并不合适。
23 | 2. 使用Fast Paxos算法,来设计保障高可用系统,去中心化,保障大部分节点正常情况下,整个系统可以稳定运行。
24 | 3. 主要单元结构如下,选用了Fast Paxos,有主从节点的概念,所有节点都保存了任务的分配和调度信息,但只有主节点作为cron服务的任务执行进程,主节点当机,重新组内选举,新的主节点由于保存了和同步了之前主节点所有的状态信息,可以接管之前这个单元的任务执行。整个执行单元通过rpc与任务调度中心进行通信。
25 | 
26 | 4. 主节点视角的任务执行过程如下,执行的起始和结束都通过paxos协议同步通知同组的其他节点,并且只有leader可以与调度中心通信。
27 | 
28 | 5. 主节点down机,从节点接管任务的执行,对于执行一半的任务,会有复杂的配置策略,通过与数据调度中心进行联动进行决策。临界情况的处理复杂,对外依赖的任务,尽量要满足幂等性设计,否则需要在risking a double launch and risking skipping a launch中间进行权衡。另外对于任务的执行,整个系统还设计了很多策略,比如防止群振效应。(业务线每天凌晨开始执行任务瞬时全部启动,影响集群可用性)
29 |
30 | ### 花椒直播的最简实现 (感谢团队花椒系统团队贡献 github: @youlu-cn @specode @qudexin1986)
31 | * google的论文实现侧重于全球数据中心的高可用,但对于我们一般性的创业公司来讲,高可用是重要的点,但场景和规模并没有那么复杂,所以是可以用简化方案实现的。这一部分,花椒直播的系统团队也做了多次权衡和考量。其中一种方案也考虑google的实现,但只是将fast paxos换成开源库中更成熟的raft方案,其他类似。但即使是使用raft,整个系统的完备性测试也是一个非常复杂的问题,这个项目本身也是与pepperbus并行的,开发资源有限。经过多次考量,我们决定,再降低一次难度,直接使用etcd进行状态的存储和主从节点的选举。这样整体又降低了一个难度。系统的调试和测试也可以通过访问etcd的日志和etcd本身提供的版本数据,进行追溯和排查。
32 | * 如果使用了etcd来保存状态,实际上也并不需要原论文中的主从概念了,所有节点都能拿到etcd中的数据和执行的状态,只是根据节点的角色和预先的配置进行sharding,执行不同的job即可。etcd选举的leader节点就是原论文中的 Datacenter scheduler的角色,只有这个leader节点进行job的存储,分配,管理,迁移工作。其他节点只是按照拿到的配置进行周期性的cron任务。当任何一个子节点down机,leader节点拿到信息后,将这个节点的数据再转移给其他节点即可。当leader节点down机,其他节点进行选举选出新的leader节点,然后获取原有leader存储的信息,重新进行任务分配工作。
33 | * 另外需求上,根据推荐系统团队的一些常见需求进行了重新梳理,丰富了一些新的功能。
34 | 1. Cron任务的规范与管理
35 | 2. Cron任务结果可视化与执行状态监控,报警
36 | 3. 父子任务,互斥任务,任务并发度控制,简单的任务编排
37 | 4. 支持秒级别Cron任务,支持永久循环执行任务
38 | 5. 未来会对容器化场景的任务集成与监控,提供同样友好的支持
39 |
40 | * 整个系统的结构图如下:
41 | 
42 |
43 | * 根据需求定制的可视化后台,实时展示任务列表和执行情况
44 | 
45 |
46 | * 展示每个cron执行节点的任务与执行状态
47 | 
48 |
49 | * 任务创建过程,有三种类型 其中cron类型就是常规的cron语法配置
50 | 
51 |
52 | * 任务执行状况的在线日志查看
53 | 
54 |
55 |
56 |
57 |
58 | ## 《创业公司是否需要中台?应该是一个什么样的中台?》
59 | * 看了一些文章,基本上大厂的中台,主要是解决技术复用,基础资源复用,技术经验复制,技术认知同步和技术迭代的同步。操作上又分成业务中台,服务总台,架构中台等其他概念,为了不同的目的,做技术团队集中化或者虚拟的集中化策略。这是大厂节省资源的一个刚需,你可以理解一个又有事业群,又有事业部,又有项目部等网状,树状的大厂,技术上百花齐放,从高层看起来一定是让人很焦虑的,从底层看,可能更像是神仙打架。
60 | * 对于早期初创可能不需要考虑这个问题,当具备一定规模后,还是要有中台这个概念。可能是EP,系统开发团队来承担的横向串联的角色。其目的是提升整个团队效率为基础,以技术能力输出,统一服务输出,技术创新与培训,等不同维度的工作进行推进,打破小团队的技术封闭,转化和扩大小团队内部受影响力限制的最佳实践。
61 | * 创业公司的中台相比较大公司,还是有很多政策优势的。大公司虽然有智力优势,但技术的标准化意味着自由度被控制在一定范围内,比如语言技术栈就是一个限制。因此敢于大范围的尝试对接和转化新技术能力,并且能快速推进整体集团的改变和认知同步,我是持怀疑的。当然类似RN,weex,flutter这种级别创新往往孕育在大厂中,但如何选择,我觉得一定是没有具备中台技术积累的创业公司选择的自由度大。
62 | * 对于创业公司来说,甚至可以有集成客户端,服务端一起的中台,提供统一的可以复制的能力,这在大厂项目中可以实现,但不可能像创业公司那样,一个集中技术中台可以迅速影响整个公司。尤其创业公司在缺少技术人才情况下,如果集中力量办大事,确实是一个值得思考的问题。
63 | * 当然一个没有中台概念或者类似效能组织的创业公司也可能是运行良好的。但从逻辑推理上来看,他一定需要更多的人,买更多的技术,然后团队不停的分裂下去。如果业务蒸蒸日上,也并不需要感知这些问题。但如果业务发展不顺利崩盘那一天,他是什么也剩不下的,即使二次创业,技术上也是从零开始。
64 | * 对于每个开发者来讲,创业结束后你还剩下什么,我觉得如果不是财务自由,那中台的代码库希望能让你略感欣慰一些~
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/rpc_evolution.md:
--------------------------------------------------------------------------------
1 | 360消息系统RPC基础库迭代
2 | ---
3 | 引文:
4 | =============================
5 |
6 | 消息系统内部各服务的通信,是依赖于RPC调用串联起来的。RPC框架的易用性,性能,开销决定了整个系统的开发的灵活性,整体性能
7 | 和负载上限。因此对于这样一个基础库,硬性要求一定是稳定高效易用的,一旦整个系统使用,除非必要不做影响应用层的修改。
8 | 但由于项目在启动时,go语言本身还没有成型的开源方案,先后开发3个版本,才完善成型。
9 |
10 | 消息系统的rpc框架的迭代,可以说是系统通信模型设计的一个缩影,走完了能走的弯路。。。(填坑)他的起点或者说设计思路,就是
11 | 简单易用,迅速迭代完成系统原型。它在上线初期很好的完成了使命,但后期随之而来的并发通信压力,业务级别的策略已经无法改善
12 | 通信瓶颈,必须对底层通信库做彻底改造,并升级系统服务所有接口到新通信框架。下面来谈谈具体迭代过程。
13 |
14 | 浅谈rpc
15 | ----------------------------------------------
16 |
17 | 一个RPC库的设计(基于TCP),牵扯到几个基本的概念:用户调用,传输编码,信道利用。
18 | 用户调用:
19 | 按过程分为:调用者发起请求(call),等待远端完成工作,获取对端响应,三个过程。
20 | 根据调用方式还可分为:
21 | 同步调用:发送请求,等待结果,结果返回调用方。
22 | 异步调用:发送请求,用户立刻拿到请求handler,通信和调用交给底层框架处理,用户可以处理其他逻辑,再通过之前返回
23 | handler来直接获取处理结果。同步通知:发送请求,数据送达到对方,无需等待结果,返回ack response到调用方,释放同步
24 | 请求。
25 | 传输编码:
26 | 即对用户的请求request与服务端的响应response做encode与decode。即将请求数据结构,与响应数据结构序列化后在网络中总
27 | 传输。比如常见的protobuf,msgPack,bson,json,xml,gob等。如果要对编解码分类,可能分为文本型和二进制型,比如
28 | json,xml这些属于文本型,其他几种属于二进制型。在编码方式实现上,同一种编码在实现效率上可能有区别,我们消息系统主
29 | 要使用了go语言原生的gob编码(相比较网络io,编码效率与业务层易用性权衡下的选择)。具体编码的选择上,因项目而异,对
30 | 于rpc通信框架来说,只关心编解码实现过程中牵扯到与rpc框架交互的接口。牵扯到两个相关于性能的细节:内存复用,与对象
31 | 复用。
32 | 信道利用:
33 | 对于一个基于tcp的框架,主要牵扯两个问题,一个是长短连接,连接复用策略,与连接管理。
34 |
35 | 了解了以上概念,剩下问题,就是如何根据用户的调用方式,编解码库实现方式,和信道利用上,最大限度提高效率。
36 |
37 |
38 | 早期RPC框架
39 | -----------------------------------------------------
40 | 早期的消息系统,使用的策略是:
41 | 同步调用+短连接+动态创建所有buffer+动态创建所有对象
42 |
43 | 这种方式,在初期很快的完成了原型,并且在项目初期,通信比较少情况下,稳定跑了近半年时间。
44 |
45 | 通信时序图如下:
46 |
47 | 
48 |
49 | 随着业务放量,推送使用频率的增长,业务逻辑的复杂。瓶颈出现,100w连,稳定服务后,virt 50G,res 40G,左右。gc时间一
50 | 度达到3~6s,整个系统负载也比较高。(12年,1.0.3版本)
51 |
52 | 其实早期在实现的时候,选择动态创建buffer和object,也不奇怪,主要是考虑到go在runtime已经实现了tcmalloc,并且我们相
53 | 信它效率很高,无需在应用层实现缓存和对象池,但实际上由于在通信繁忙时候,这种实现方式,给通信端口,语言的造成很大压力
54 | ,并且使用短连接,每次对内服务,都重新创建连接开销也很大,pprof时候可以看到,除了应用层建立大量对象和buffer外,go
55 | 的tcp底层创建tcpConnection也会动态创建大量对象,具体瓶颈在newfd操作,非常明显。
56 |
57 |
58 | RPC通信框架第一次迭代
59 | --------------------------------------------------
60 |
61 | 针对这种情况,我对通信库做了第一次迭代改造。
62 |
63 | 1.使用长连接代替短连接,对每一个远端server的address提供一个连接池,用户调用,从连接池中获取连接,对应下图中get conn
64 | 环节。用户的一次request和response请求获取后,将连接放入连接池,供其他用户调用使用。因此系统中能并行处理请求的数量,
65 | 在调度器不繁忙的情况下,取决于连接池内连接数量。假设用户请求一次往返加服务端处理时间,需要消耗10ms,连接池内有100个
66 | 连接,那每秒钟针对一个server的qps为1w qps。这是一个理解想情况。实际上受server端处理能力影响,响应时间不一定是平均的,
67 | 网络状况也可能发生抖动。这个数据为后面讨论pipeline做准备。
68 |
69 | 2.对连接绑定buffer,这里需要两个buffer,一个用于解码(decode),从socket读缓冲获取的数据放入decode buffer,用户对读
70 | 到的数据进行解码,即反序列化成应用层数据结构。一个用户编码(encode),即对用户调用传入的所有参数进行编码操作,通过这
71 | 个缓冲区,缓存编码后的一个完整序列化数据包,再将数据包写入socket 写缓冲。
72 |
73 | 3.使用object池,对编解码期间产生的中间数据结构进行重复利用,注意这里并不是对用户传递的参数进行复用,因为这个是由调用
74 | 用户进行维护的,底层通信框架无法清楚知道,该数据在传输后是否能够释放。尤其在使用pipeline情况下,中间层数据结构也占了
75 | 通信传输动态创建对象的一大部分。
76 |
77 | 改动后,通信图如下,上图中红线所带来的开销已经去除,换成各种粒度的连接池和部分数据结构的对象池。具体细节,后面说明
78 | 手机助手gorpc框架时候,说明。
79 |
80 | rpc框架第二版使用策略:
81 | 同步调用+连接池+单连接复用编解码buffer +复用部分对象
82 |
83 | 
84 |
85 | 这种方式,无疑大大提高了传输能力,另外解决了在重启等极限情况下,内部通信端口瞬时会有耗尽问题。内存从最高res 40G下降
86 | 到20G左右。gc时间也减少3倍左右。这个版本在线上稳定服务了接近一年。
87 |
88 |
89 | RPC通信框架二次迭代
90 | ---------------------------------------
91 |
92 | 但这种方式对连接的利用率并不高,举例说明,用户调用到达后,从连接池获取连接,调用完成后,将连接放回,这期间,这个连接
93 | 是无法复用的。
94 |
95 | 设想在连接数量有限情况下,由于个别请求的服务端处理延时较大,连接必须等待用户调用的响应后,才能回放到连接池中给其他请
96 | 求复用。用户调用从连接池中获取连接,发送request,服务端处理10ms,服务端发送response,假设一共耗时14ms,那这14ms中,
97 | 连接上传输数据只有4ms,同一方向上传输数据只有2m,大部分时间链路上都是没有数据传输的。
98 |
99 | 但这种方式也是大多目前开源软件使用的长连接复用方案,并没有充分利用tcp的全双工特性,通信的两端同时只有一方在做读写。
100 | 这样设计好处是client逻辑很简单,传输的数据很纯粹,没有附加的标记。在连接池开足够大的情况下,网络状况良好,用户请求
101 | 处理开销时长平均,这几个条件都满足情况下,也可以将server端的qps发挥到极限(吃满cpu)。
102 |
103 | 另一种方案,是使整个框架支持pipeline操作,做法是对用户请求进行编号,这里我们称做sequence id,从一个连接上发送的所有
104 | request,都是有不同id的,并且client需要维护一个请求id与用户调用handler做对应关系。服务端在处理数据后,将request所带
105 | 的请求的sequence id写入对应请求的response,并通过同一条连接写回。client端拿到带序号的response后,从这个连接上找到之
106 | 前该序号对应的用户调用handler,解除用户的阻塞请求,将response返回给request的调用方。
107 |
108 | 对比上面说的两个方案,第二个方案明显麻烦许多。当你集群处于中小规模时候,开足够的连接池使用第一种方案是没问题的。问题
109 | 是当你系统中有几百个上千个实例进行通信的时候,对于一个tcp通信框架,会对几百个甚至上千个需要通信的实例建立连接,每个
110 | 目标开50个到100个连接,相乘后,整个连接池的开销都是巨大的。而rpc请求的耗时对于通信框架是透明的,肯定会有耗时的请求,
111 | 阻塞连接池中的连接,针对这种情况调用者可以针对业务逻辑做策略,不同耗时接口的业务开不同的rpc实例。但在尽量少加策略的情况
112 | 下,使用pipline更能发挥连接的通信效率。
113 |
114 | pipeline版本的rpc库,还加入了其他设计和考虑,这里只在最基础的设计功能,进行了讨论,下图是,第三版本rpc库,对比版本二的
115 | 不同。
116 |
117 | 
118 |
119 | 如上图所示,两次rpc调用可以充分利用tcp全双工特性,在14ms内,完成2次tcp请求。在server端处理能力非饱和环境下,用户调用
120 | 在连接池的利用上提高一个量级,充分利用tcp全双工特性,让连接保持持续活跃。其目的是可以用最少的连接实现最大程度并发,
121 | 在集群组件tcp互联通信的情况下,减少因为请求阻塞造成的连接信道浪费。
122 |
123 |
124 | 以上对消息系统rpc通信框架的迭代和演进进行了说明。只是对通信过程中基础环节和模型做了粗线条介绍。第三版本的rpc通信框架,其实为
125 | 了适应分布式系统下的需求,需要辅助其他功能设计。每个细节都决定这个库能否在中大规模分布式环境中下是否试用,或者说是否可控,我们
126 | 将在下一章里面详细介绍。
127 |
128 |
--------------------------------------------------------------------------------
/zhouyang_GopherChina2019.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/zhouyang_GopherChina2019.pdf
--------------------------------------------------------------------------------
/zhouyang_archsummit.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johntech-o/blog/5c933856ce3c6500b32ed46e3d99393fa9ca339c/zhouyang_archsummit.pdf
--------------------------------------------------------------------------------