17

技术分享之golang构建分布式任务系统 – 峰云就她了

 4 years ago
source link: http://xiaorui.cc/archives/6955?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

技术分享之golang构建分布式任务系统 – 峰云就她了

专注于Golang、Python、DB、cluster

技术分享之golang构建分布式任务系统

如何使用golang构建一个分布式的任务系统 ? 该任务系统搞了几个月,有一些经验心得分享给大家。

在公司内部已做过一次分享,评价还不错。已经把ppt推到github了,有兴趣可看下完整的ppt。求在github上点个star。😁

https://github.com/rfyiamcool/share_ppt/blob/master/shark.pdf

shark

shark?对的,shark是我们项目名,为什么叫鲨鱼? 同事们的项目好多都是海产品,比如starfish ? 那么我的任务系统理所当然也是一个海产品了。

shark可以做很多东西,可以把他理解为一个简单版的ansible和saltstack,架构设计上更趋于salstack。起初单纯要做kubernetes集群部署工具,但随着后面的需求越来越多,所以抽象为面向于服务和主机的任务系统。相比ansible和satlstack来说,更好的分布式设计,对多机房友好、grpc接口更友好、扩展功能更适配我们自己。

golang 分布式任务系统
golang 分布式任务系统
golang 分布式任务系统
golang 分布式任务系统

从下往上说,minion是客户端的意思,每个主机都要部署该服务,master为控制端,管理下发所有的请求。gateway是代理层,由于有多机房,所以需要gateway来转发请求。

golang grpc

协议是使用了grpc,这么没的说,grpc简单高效且适合stream流传输。

golang gateway

gatway的逻辑前面有阐述,主要是做机房的请求转发。对于客户端来说,只需连接gateway就可以了。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-9-812020.jpeg

主备的选举方案很简单,直接采用redis的分布式锁,谁拿到锁谁就是主,锁操作使用redis lua封装避免误操作。由于redis本身无强一致的同步协议,而是用异步传输日志,所以当redis采用主从模式,redis主挂时,锁可能不太可靠。

这里没有采用etcd、zk这类强一致性db做分布式锁,也没有扩展redis的redlock方案。我们评估了业务需求,对于短时间内引起的锁冲突是可以容忍的。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-10-812020.jpeg
shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-11-812020.jpeg

master的主备之间会做同步的,采用的方案是参考了redis的fullsync和psync。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-12-812020.jpeg

实现了服务的自升级,尽量避免运维去操作升级。

首先minion拿着本地的version询问master是否有更新。如有更新在通过grpc通道下载本地,然后对比md5及运行版本,通过校验后则创建一个使用新文件运行的子进程,并传递listen fd。主进程会监听winch信号,当子进程运行30s后,无问题则向主进程发送winch信号,主进程收到winch后执行优雅退出。

如果子进程在规定的时间内没有给主进程发送winch信号,主进程会强制把子进程干掉,并且做好状态位的回滚。我们在升级时参照了nginx在各阶段改变进程名的思路,这样有利于排查问题。子进程启动时调用syscall procname改名为 shark-minion (new),主进程则会变更为 shark-minion (old)。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-14-812020.jpeg

shark里完成了对k8s的管理,基于rancher rke二次开发的,原rke是一个基于ssh的工具,我们将其改造成基于minion的k8s管理模块。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-15-812020.jpeg
shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-16-812020.jpeg

任务异步化,通过各个state来标记任务当前的状态。异步化带来的好处在于任务进度可控,并发可控,协程数也可控,可取消任务。任务系统里的任务绝大数较耗时,如果使用同步请求来实现任务系统,不太好实现进度的上报,而异步就简单的多。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-17-812020.jpeg

这里的watch跟etcd watch道理差不多,由于任务都是异步出路,如不实现watch监听则需要不断轮询结果。原理的实现很简单,在每条数据上加入revision版本,当客户端发起订阅时需要携带revison,每当minion产生数据都会用grpc stream推送给客户端。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-18-812020.jpeg

使用golang plugin动态库来扩展自定义逻辑,目的在于方便的在线热升级,还有一个在于减少代码污染,因为shark作为任务系统总线,会有其他人对他需求的定制开发,但代码残次不齐,索性直接搞到plugin动态库里,依赖shark的load机制来加载其他人的逻辑。😅

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-19-812020.jpeg

在shark里也可自定义功能模块,为了避免模块出问题影响到minion,使用子进程方式去加载运行,子进程绑定unix domain socket,主进程和子进程之间通过grpc stream over unix domain socket通信。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-20-812020.jpeg

可注册自定义方法,且每个方法需要接收eventHandler接口,该方法可实现数据的收集,进度发送及其他资源收敛。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-21-812020.jpeg

高性能定时器的实现,这里没有再使用时间轮,而是采用类似rocketmq定时器的实现。根据不同时间间隔的定时器放到不同的队列里。由于定时器的需求在于任务超时控制及数据清理,所以定时器的精度可粗化。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-22-812020.jpeg

minion需要本地存储,这里采用go badger来实现类似redis的数据结构。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-23-812020.jpeg

安全控制,所有的链路都需要证书来保证安全,minion不能直接对外服务,只能master才能来访问,minion使用master的公钥加密token上报给master,master使用私钥解密token才可访问minion。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-24-812020.jpeg

兜底主要是依赖systemd

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-25-812020.jpeg

防护处理,backoff退避算法,避免频繁的重试某操作。failfast 快速失败,避免无意义请求,failvoer 失效转义,当master主挂时会重置连接到master备。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-26-812020.jpeg
shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-27-812020.jpeg

logrus日志库本就性能不咋地,但考虑更好排查问题,所以在日志里加入了行号、函数名、文件名的解析。这类操作加大了开销,所以改用zap来消减日志开销。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-28-812020.jpeg

cpu off的开销也显示主要在打日志上。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-29-812020.jpeg
shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-30-812020.jpeg
shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-31-812020.jpeg

遇到的问题

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-32-812020.jpeg
shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-33-812020.jpeg

多个协程并发读写string时会出现数据混乱的情况,string不是线程安全的,因为string的底层数据结构为stringHeader,一个数据指针,一个length。解决方法可用atomic,也可采用创建新结构复制。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-34-812020.jpeg

golang os exec里没有提供stop方法,如要实现关闭已启动的shell,可获取pid,然后进行kill。但问题来了,你获取的pid是bash的,kill的也是bash的,bash启动的命令不会被kill。如何处理该问题? 干掉进程组。

shark_v1%E7%9A%84%E5%89%AF%E6%9C%AC-35-812020.jpeg

go-shell对shell命令做了封装,不仅解决了上面的stop问题,加入更易用的api。

https://github.com/rfyiamcool/go-shell

grpcx是对grpc的封装,实现了拦截器、balancer、grpc error的处理。

https://github.com/rfyiamcool/grpcx

wmanager对golang协程的封装,实现了start、stop、join、pname、status的接口。

https://github.com/rfyiamcool/wmanager


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc
weixin_new.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK