2019 年 6 月告别南京,告别校园,我来到了上海。
与大多数人相同,初入社会,无比怀恋校园生活。
后悔大学四年,都没有去逛过总统府,没有去爬过紫金山,没有在操场多停留。
感谢那四年宿舍、实验室两点一线的我,让我在在工作中未曾感到过压力。
但是,南京,终究是回不去了。
2019 前半年,3 月份的时候,我就提前来了公司实习。一直到差不多五月初,我才回了学校。
这段时间的记忆并不深刻,几乎想不起来做了什么,算是体验了一下职场生活吧。
工作后一段时间,才开始真正使用 Github 。
2019 主要输出了三个半项目吧
protoc 编译生成的 pb.go 文件,默认情况下 tag 中会设置 json 忽略零值的返回属性 omitempty
。
type Message struct {
Header map[string]string `protobuf:"bytes,1,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
一个比较 hack 的方式,是在 pb.go 文件生成后,手动去删掉 omitempty
。每次手动去删除,比较麻烦且容易出错,下面提供一个 Makefile ,每次生成 pb.go 的时候就去删除 omitempty
。
二叉堆是一组能够用堆有序的完全二叉树排序的元素,一般用数组来存储。
大顶堆, 每个结点的值都大于或等于其左右孩子结点的值,其顶部为最大值。
小顶堆,每个结点的值都小于或等于其左右孩子结点的值,其顶部为最小值。
Github Actions 是 Github 内置的 CI/CD 工具,现在已经对所有的开源项目免费开放了。
本文主要记录使用 Github Actions 实践 CI/CD 的一些配置。
项目主要目录
go-micro 框架支持动态加载插件,无需修改代码。
启动服务前,设定 MICRO_PLUGIN
环境变量指定 .so 文件路径,支持多个插件,逗号分割。程序启动前会读取 MICRO_PLUGIN
环境变量,并完成插件设定。
下面是其内部实现:
go-micro/service.go
从上面的代码可以看出,service 初始化化的时候,读取 MICRO_PLUGIN
环境变量中指定的 .so 文件路径。并且调用 plugin
包,逐个 Init
。
下面我们看下 plugin
包的实现:
微服务架构愈演愈烈,但是社区一直缺少一个部署简便,高可用的配置中心。
XConf
是一个基于 go-micro 微服务框架构建的分布式配置中心,提供配置的管理与发布、实时推送。
配置中心底层存储采用 MySQL 数据库,主要分为三个服务:
配置中心本身就是一个“读多写少”的服务,所以在 agent-api 服务中增加缓存,从而有效增加并发性能。得益于 go-micro 框架,三个服务可以便捷的横向伸缩。例如,当获取配置的客户端较多时,可以增加 agent-api 实例。
配置获取采用 HTTP 方式实现,配置的实时推送采用 HTTP Long Polling(长轮询)方式实现。选择 HTTP 方式,更加便于各种语言接入配置中心。
Golang 语言读取配置和监听实时配置推送( ➡️ 源码地址 ):
https://github.com/Allenxuxu/gev
gev
是一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库,支持自定义协议,轻松快速搭建高性能服务器。
最近对 gev 进行了一次较大改动,主要是为了能够以插件的形式支持各种自定义的数据协议,让使用者可以便捷处理 TCP 粘包问题,专注于业务逻辑。
做法如下,定义一个接口 Protocol
// Protocol 自定义协议编解码接口
type Protocol interface {
UnPacket(c *Connection, buffer *ringbuffer.RingBuffer) (interface{}, []byte)
Packet(c *Connection, data []byte) []byte
}
用户只需实现这个接口,并注册到 server 中,当客户端数据到来时,gev 会首先调用 UnPacket 方法,如果缓冲区中的数据足够组成一帧,则将数据解包,并返回真正的用户数据,然后在回调 OnMessage 函数并将数据通过参数传递。
下面,我们实现一个简单的自定义协议插件,来启动一个 Server :
| 数据长度 n | payload |
| 4字节 | n 字节 |
gev 是一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库,底层并不使用 golang net 库,而是使用 epoll 和 kqueue,因此它并不支持 Windows。
Golang 的 goroutine 虽然非常轻量,但是每启动一个 goroutine 仍需要 4k 左右的内存。读了鸟窝大佬的文章【百万 Go TCP 连接的思考: epoll方式减少资源占用】后,便去研究了了下 evio。
evio 虽然非常快,但是仍然存在一些问题,便尝试去优化它,于是有了 eviop 项目。关于 evio 的问题可以看我的另一篇博文 【Golang 网络库evio一些问题/bug和思考】。在优化 evio 完成 eviop 的过程中,因为其网络模型的缘故,愈加感觉修改它非常麻烦,成本比重新搞一个还高。
最终决定自己重搞一个,更加轻量,不需要的全去掉。加上大学时学习过 muduo ,便参考 muduo 的使用的 Reactor 模型实现 gev 。
在 linux 环境下,gev 底层使用 epoll ,这是 gev 会专注优化的地方。在 mac 下底层使用 kqueue,可能不会过多关注这部分的优化,毕竟很少有用 mac 做服务器的(Windows 环境"暂"不支持)。
风格是指规范代码的共同约定。风格一词其实是有点用词不当的,因为共同约定的范畴远远不止 gofmt 所做的源代码格式化这些。
本指南旨在通过详尽描述 Uber 在编写 Go 代码中的注意事项(规定)来解释其中复杂之处。制定这些注意事项(规定)是为了提高代码可维护性同时也让工程师们高效的使用 Go 的特性。
这份指南最初由 Prashant Varanasi 和 Simon Newton 编写,目的是让一些同事快速上手 Go 。多年来,已经根据其他人的反馈不断修改。
这份文档记录了我们在 Uber 遵守的 Go 惯用准则。其中很多准则是 Go 的通用准则,其他方面依赖于外部资源:
所有的代码都应该通过 golint
和 go vet
检查。我们建议您设置编辑器:
goimports
golint
和 go vet
来检查错误您可以在这找到关于编辑器设定 Go tools 的相关信息:
Fast event-loop networking for Go
最近翻了 evio 的源码,发现一些问题,主要集中在 linux 平台 epoll 上和读写的处理。
阅读前提:了解 epoll
evio 是一个基于事件驱动的网络框架,它非常轻量而且相比 Go net 标准库更快。其底层使用epoll 和 kqueue 系统调度实现。
evio 是 Reactor 模式的简单实现。Reactor 本质就是“non-blocking IO + IO multiplexing”,通过非阻塞IO+ IO 多路复用来处理并发。程序运行一个或者多个事件循环,通过在事件循环中注册回调的方式实现业务逻辑。
evio 将所有文件描述符设为非阻塞,并注册到事件循环( epoll / kqueue )中。相较于传统的 per thread per connection 的处理方法,线程使用更少,线程资源利用率更高。
evio 需要在服务启动前,注册回调函数,当事件循环中有事件到来时,会调用回调函数处理。
先从一个简单的 echo server 的例子来了解 evio 。