goim源码分析与二次开发-comet分析一
因为要完成一个聊天的项目,所以借鉴了goim,第一篇分析打算半原版,先摘抄http://www.jianshu.com/p/8bd96a9a473d他的一些理解,写这些还是为了让自己更好的理解这个项目,如果有什么不对的,请大家给我留言。
Comet 角色定位
![](http://upload-images.jianshu.io/upload_images/659877-95ebc9a3a63c7448.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
在整个架构中,系统被分成 Comet, Logic, Job, Router 四大模块,各个模块通过 RPC 通信,参考官方中文文档,Comet 程序是连接层,暴露给公网,所有的业务处理推给 Logic 模块,通过 RPC 通信。这样设计的好处在于,长连接逻辑很少变动,稳定的保持公网连接,而后端 Logic, Router 模块经常变动,重启不会影响连接层。
几个重要的结构体
做为典型代码即注释的开源项目,goim 基本无太多阅读障碍,几个逻辑点梳理下很快就会明白。
Bucket: 每个 Comet 程序拥有若干个 Bucket, 可以理解为 Session Management, 保存着当前 Comet 服务于哪些 Room 和 Channel. 长连接具体分布在哪个 Bucket 上呢?根据 SubKey 一致性 Hash 来选择。
Room: 可以理解为房间,群组或是一个 Group. 这个房间内维护 N 个 Channel, 即长连接用户。在该 Room 内广播消息,会发送给房间内的所有 Channel.
Channel: 维护一个长连接用户,只能对应一个 Room. 推送的消息可以在 Room 内广播,也可以推送到指定的 Channel.
Proto: 消息结构体,存放版本号,操作类型,消息序号和消息体。
多协议支持
Goim 支持 Tcp, Http, WebSocket, TLS WebSocket. 非常强大,底层原理一样,下面的分析都是基于 Tcp 协议。
Bucket
先来看看结构体的定义
定义很明了,维护当前消息通道和房间的信息,方法也很简单,加减 Channel 和 Room. 一个 Comet Server 默认开启 1024 Bucket, 这样做的好处是减少锁 ( Bucket.cLock ) 争用,在大并发业务上尤其明显。最新的版本增加了routineamout,routinesize这两个选项主要是用来做progress推送消息的作用,第一个是做消息推送协程个数,第二个是管道长度。比旧版buket取消了以前的roomoptions直接使用room的参数
Room
![](https://images2015.cnblogs.com/blog/358212/201704/358212-20170420163208149-1257205773.png)
Room 结构体稍显复杂一些,不但要维护所属的消息通道 Channel, 还要消息广播的合并写,即 Batch Write, 如果不合并写,每来一个小的消息都通过长连接写出去,系统 Syscall 调用的开销会非常大,Pprof 的时候会看到网络 Syscall 是大头。新版本room改动很大,以前这么同学说辞不知道是否管用,但是从结构体定义来看,room增加了下个channel和一个在线状态。
![](https://images2015.cnblogs.com/blog/358212/201704/358212-20170420163549806-1686401557.png)
Logic Server 通过 RPC 调用,将广播的消息发给 Room.Push, 数据会被暂存在proto这个结构体 里,每个 Room 在初始化时会开启一个 groutine 用来处理暂存的消息,达到 Batch Num 数量或是延迟一定时间后,将消息批量 Push 到 Channel 消息通道。
Channel
![](https://images2015.cnblogs.com/blog/358212/201704/358212-20170420163819024-452243535.png)
这是一个通道。Writer/Reader 就是对网络 Conn 的封装,cliProto 是一个 Ring Buffer,保存 Room 广播或是直接发送过来的消息体。
消息流转
这里只分析 Comet 代码,所以消息生成暂时不提
1. Client 连接到 Comet Server, 握手认证
2. 新建当前长连接的 Channel, 由于 Comet 服务不处理业务逻辑,需要 RPC 去 Logic Server 获取该 Channel 的订阅信息。同时 Channel 开启一个 dispatchTCP groutine, 阻塞等待 Ring Buffer 数据可用,发送到 Client。
3. Logic 服务通过 RPC, 将消息写到 Room (广播)或是直接写到指定 Channel (单播)。注意这里,广播是有写合并 BatchWrite, 而单播没有,消息生成后立刻发送。
4. Room 里的广播消息到达一定数量 Batch Num, 或是延迟等待一定时间后,将消息写到 Channel Ring Buffer。
小结
新版本跟以前那位同学的很不一样,第一版本由于时间关系,基本大多数是摘抄这位同学的,感觉新版本很多不对,但是阅读代码还没到那个程度,所以第一版就到这里,这位同学对goim大体架子分析的很清楚,细节在我后续章节会有,如果有不对的,请及时留言纠正
补充-来自毛剑
1. bucket按照key的cityhash求余命中的,没有用一致性hash,因为这里不涉及迁移
2. 私信发送其实也有合并的,和room合并不同的是,在ringbuffer取消息饥饿时候才会真正flush
3. 还有一个优化可以改进,因为room有个特点大家消息可能都一样,所以在room提前合并成字节buffer,然后广播所有人,避免每个人都序列化一次,然后利用gc来处理这个buffer的释放,这样可以节省大量cpu,目前这个优化还没做