• 当我看源码的时候,我在想什么?


    引言

    在斯坦福大学, 乔布斯做了一场我认为他最精彩的演讲之一 (另一场可能是iphone的问世发布会)。他讲了第一个故事 "connecting the dots"

    你不可能充满预见地将生命的点滴串联起来;只有在你回头看的时候,你才发现这些点点滴滴之间的联系。所以,你要坚信,你现在所经历的将在你未来的生命中串联起来。你不得不相信某些东西,你的直觉、命运、生活、因缘际会……正是这种信仰让我不会失去希望,它让我的人生变得与众不同。

    什么是连接生命中的点滴呢?

    我的理解: 当一个人的能力还在上升期, 格局和视野还不够广阔的时候,他需要专注当前的事情,并且把每件事情做到他当时的能力所能做到的极限,也许当时没有那么大的成就感,但"蓦然回首,那人却在灯火阑珊处"。

    十年前刚进入IT这个行业的时候,我是一个很普通的工程师,脑袋也不灵光,工作老是得不到要领,我的同学智商很高,他看一次代码基本就会写了,我得花很长时间去消化吸收,我对自己能不能在这一行生存下去都产生了质疑。

    没有办法,只能勤能补拙,笨鸟先飞, 当遇到问题的时候, 我都抱着死咬不放的心态去寻找最佳解决方案, 洗澡的时候,睡觉的时候,吃饭的时候,甚至上厕所的时候都会去思考。很自然的,"追"源码也成为我程序生命中的一部分。

    阅读过很多源码,和大家分享几个对我的职业影响比较大的追源码的经历。

    Druid 连接池

    这是在2013年,我第一次重构一个彩票算奖服务,原有代码是C#版本,每次计算订单金额耗时2~3个小时,很多用户反馈体验很差(因为收到奖金很晚)。

    当时采用druid作为新项目的数据库连接池,上线后,重构效果明显,算将性能提升到原来的10倍。

    不过,有一个问题是:每天第一次请求,就报链接错误。

    当时也不怎么会看源码,就直接给druid作者温少(也是fastjson作者)发了一封邮件:

    温少给我回复了邮件,我马上翻开源码, 发现我配置数据库连接池的心跳有问题。

    核心点在于需要连接池每隔一段时间发送心跳包到oracle服务器,因为数据库为了节省资源, 每隔一段时间会关闭掉长期没有读写的连接。所以客户端必须每隔一段时间发送心跳包到服务端。

    这次简单的源码之旅给了我长久的激励,也让我更加关注技术背后的原理。

    • 精神层面,向别人请教问题是会上瘾的

    • 技能层面,理解连接池的实现原理:druid是基于数组实现,后来用到的jedis连接池基于commons-pool实现,netty连接池实现FixChannelPool

    • 架构层面,客户端和服务端请求需要考虑心跳:定时任务线程发送心跳包(类似druid连接池发送心跳机制),netty中的idleStateHandler

    Cobar分库中间件

    也还是在2013年,接触到cobar带给我的震撼简直无以复加。

    当时互联网大潮奔涌而来, 各大互联网公司的数据爆炸般的增长, 我曾在javaeye上看到淘宝订单技术人员分享分库分表的帖子,如获至宝,想从字里行间探寻分库分表的解决方案,可惜受限于篇幅,文字总归是文字,总感觉隔靴搔痒。

    没曾想到,cobar横空出世(开源了),用navicat配置cobar的信息,就可用像连单个mysql一样,而且数据会均匀的分布到多个数据库中。

    **这对于我当时孱弱的技术思维来讲,简直就像是三体里水滴遇到人类舰队般,降维打击。**

    因为对分库分表原理的渴求,我没有好的学习方法,大约花了3个月的时间,我把整个cobar的核心代码抄了一次。

    真的是智商不够,体力来凑。

    但光有体力是真的不够的,经常会陷入怀疑,怎么这也看不懂,那也看不懂。

    边抄代码边学习好像进步得没有那么明显。

    那好,总得找一个突破口吧: 网络通讯是非常重要的一环。

    当时的我做了一个决定,我要把cobar的网络通讯层剥离出来,去深刻理解使用原生nio实现通讯的模式。

    剥离的过程同样很痛苦,但我有目标了,不至于像没头的苍蝇,后来也就有了人生第一个github项目。

    在写nio工程的同时,我同时也学习到了maven的asemble打包的模式,这个现在听起来很简单,但在2013年tomcat部署webapp war包的模式流行情况下,让我眼前一亮。

    后来在2018年参与的一个直播答题的系统中,就采取和cobar一样的打包部署模式。

    同时第一次了解到内存池的概念,后来的netty里面ByteBuf也可用池化。这是一个非常实用的理念。

    追cobar的过程中, 好像我和阿里的大牛面对面交流,虽然我资质驽钝,但这位大牛谆谆教诲,对我耐心解答, 打通我的任督二脉,由是感激。

    说是我生命中最重要的开源项目也不为过。

    后来在艺龙网工作,和艺龙分布式数据库的架构师沟通的时候,因为有学cobar打的底子,理解起他的思路也会很快, 后来也找到数据中间件事务的一个bug,提交给他修改了。

    再后来,cobar并没有消亡,阿里云的drds使用了cobar的解析器,民间中间件mycat大部分也使用了cobar的源码, 当然mycat的质量有待商榷。

    metaq消息队列

    2015年加入神州专车,那个时候神州专车处于上升期。各个系统遇到了较大的瓶颈。

    metaq的故事很精彩,而且相关引申的知识点很多。

    当时我们使用的metaq是庄晓丹在github开源的版本。

    2016年初,我checkout了metaq的源码,边理解业务,边深入理解metaq的机制。

    集群消费

    首先冲击我的思维观念的概念是集群消费,我原来使用activemq,rabbitmq,使用点对点的模型, 每天消息接近百万级也能凑合的处理。但是假如不同的服务想要处理同样一条消息, 在点对点的模型下,只能重复发送,看起来很ugly。

    集群消费模式下每一条消息都只会被分发到一台机器上处理。不同的消费者有不同的组名,但是可以消费同样的一条消息。对当时的我来讲,是思想上的突破,metaq借鉴了kafka的模型

    我经常想:"工程上实现都还好,技术层面理念的创新更为重要",为什么先进的理念很多是由国外的公司提出的, 这都是值得我辈深思的。

    神州专车的场景下,订单系统发送一条订单状态转换消息,可以被订单子系统消费,也可以被大数据团队的服务消费,还可以被派单系统消费。不同的系统有不同消费者group,消费进度offset存储在zk里。多么精妙的设计呀。我不由得感慨!

    广播消息在专车推送系统中的使用(经典模型)

    有幸和架构部的同事沟通专车推送是如何设计的。

    最开始的时候,他们也是使用极光推送,后续因为定制的需求,所以决定自研。

    最开始,我的疑问是:"推送服务器如何推送信息到每一个连接的专车app呢?"。

    他们给我的答复很简单:"采用metaq的广播模式,就可以实现这个功能"。

    • 业务系统推送消息到metaq(rocketmq)

    • 消费metaq广播消息的tcp网关,获取到推送消息

    • tcp网关获取连接到该服务器的channel连接引用,推送数据给app

    后来,我仔细研读了京麦tcp网关的设计,关于推送方面的实现和我们当初的实现非常相似,文章里有一句话:

    在线通知是通过MQ广播机制到所有服务器,所有服务器收到消息后,获取当前服务器所持有的所有Session会话,进行数据广播下行通知

    所以,技术的实现很多是相通的。

    2018年我服务的一家电商公司研发直播答题系统,需要实现推送题目的功能,我花了大概3天时间,帮tcp网关的研发同学实现了相关的功能。

    一场zk奔溃引发的一连串的知识点

    我们也都知道metaq依赖zookeeper,metaq在zk里要保存。

    突然有一天,专车整个zk集群down掉了。架构负责人修改了zk的jvm参数,问题貌似解决了。

    但疑问随之产生了。

    metaq和服务治理共用一组zk集群合适吗?

    来公司后,阅读metaq源码后,我发现metaq消费者多的情况下,启动的时候,会频繁的争抢锁,另外消费的过程,也会对offset频繁的修改。在topic数量增多,partition数量增多的情况下,metaq对zk实际上是有写的压力的。

    后来,神州架构部确实也是将metaq的zk集群和服务治理的zk集群分开了。当然迁移也是有技巧的。当前不赘述。

    zk作为神州体系的注册中心是否有瓶颈?

    zk会存储各个服务的ip和端口,以及对外暴露的方法。

    随着专车系统的服务越来越多,zk真的可以承受得了吗?公司领导邀请了京东研发团队的同学来给大家答疑。

    京东的注册中心服务信息用什么实现的呢?

    京东的同学回答是: mysql。

    我在旁边听得目瞪口呆,什么?mysql!

    后来,淘宝中间件博客出了一篇文章: 阿里巴巴为什么不用 ZooKeeper 做服务发现?

    文章的结论是:

    当数据中心服务规模超过一定数量 (服务规模=F{服务pub数,服务sub数}),作为注册中心的 ZooKeeper 很快就会像下图的驴子一样不堪重负

    可以使用 ZooKeeper,但是大数据请向左,而交易则向右,分布式协调向左,服务发现向右。

    后来,我参看了张开涛写的一篇博客,以及在github上零落的jsf代码, 手撸了一个基于AP模型,客户端使用berkeyDb的注册中心。

    我们也知道2019年,阿里在spring cloud生态上发力,nacos也诞生了,nacos同时支持AP和CP两种模型。

    开源世界的选择更有多样性了。

    当我们使用zookeeper的时候,一定要注意集群规模和使用场景。

    神州架构团队:metaq重构之旅

    说实话,我挺佩服神州架构团队的。

    核心成员也没有那么多,但还是有进取精神的。一个中间件的重构需要领导者的前瞻性和勇气。

    虽然,我并不完全知道神州架构团队为什么要去重构metaq,但可以从下面几点来说一下:

    metaq的网络通讯框架

    metaq的通讯框架是庄晓周自研的。

    在神州专车在使用过程中,确实也发生过服务假死的情况,需要重启metaq服务。淘宝的一位架构师加入了神州架构团队开始从网络通讯层着手提升metaq的稳定性,将网络通讯层替换为netty。

    当然替换的过程并非一帆风顺,得益于庄晓丹的代码可读性,替换后,就在测试环境小规模使用了。

    突然,我惊奇的发现我的测试服务器连接数暴增,可能的原因是metaq重构网络处理连接这块可能还待优化,后续这位架构师很快解决了问题。

    我很佩服高水平的架构师。

    消息管理的自动化

    最开始metaq有一个简单的管理系统,申请主题后,还需要运维修改配置重启服务。

    后续的做法是:做一个消息管理平台,各个业务线申请主题,分区都是通过这一个平台,并且可以监控消息服务器的生产消费情况。

    我并不是metaq重构的参与者,但我内心触动的点在于: 有勇气去重构知名开源产品,做出适当创新。

    metaq vs rocketmq

    很多同学在用rocketmq, 有一些我认为很重要的点,供大家思考。

    • rocketmq为什么将所有的数据存储在一个commitlog里,metaq多partition模式有瓶颈么?

    • rocketmq为什么要使用推拉结合的模式?相比metaq的拉模式有什么优势吗?

    • 阿里的ons系统有任意时延的消息,为什么rocketmq只支持固定的延迟消息。可否在rocketmq的基础上实现任意时延?可以参考 如何在MQ中实现支持任意延迟的消息?

    • rocketmq的控制台,你认为好用吗?能否支持多集群的模式,你要是设计该如何实现。

    任务调度系统设计

    xxljob(许雪里) , crane(美团),  schedulex(阿里云),这些都是任务调度系统。

    时间已经到了2018年,技术部需要一个可靠的任务调度系统。最开始租赁团队使用的是xxljob,但不知道怎么搞的,老是使用有问题。

    第一阶段: 坦率的讲,我看了xxljob的源码后,第一直觉是:"简单"。因为作者已经最大限度的将这个系统做成了开箱即用,去掉了quartz的集群调度模式,自研基于数据库的调度器。

    但当前公司已经有自研的rpc服务,让其他团队配合xxljob添加jobhandler好像也不太容易。

    所以最开始, 我修改了调度器的代码,使用了公司的rpc来执行。只不过将xxljob的jobHandler替换为公司的rpc的serviceId。

    运行起来还行,能满足公司要求。

    第二阶段: 为什么我想再优化一波?

    因为当前的rpc调度是同步执行,xxljob的调度基于数据库锁,这两方面都有瓶颈。我想向大公司的同事取经。

    所以,我找到了美团的朋友,向他请教他们公司是如何设计任务调度系统。

    他给我演示了crane的执行过程,因为考虑到保密以及安全,他仅仅给我讲了其中的原理。我根据他的描述做了如下架构设计:

    第三阶段: 光有架构设计没用,工程上如何实现呢?

    我必须吃下定心丸,当前的设计模式确实可行。

    我想到了阿里云的schedulex。假如我是一名阿里云的开发者,我怎么设计一个好的任务调度系统,支持每天千亿级别的任务调度。

    我翻看schedulex的开放文档,以及schedulex client的源码。想不到真的是宝藏。

    schedulex client里包含如下几点:

    • rpc调用类似rocketmq remoting

    • 任务调度通过rpc触发,任务调度有统一的注册中心(nameserver模式)

    • 支持多端口启动(若当前端口启动失败)

    • 任务执行和任务调度/回调线程池隔离

    吃下了定心丸,无非是工程的实现了。

    重构的效果还不错,技术同事们对当前的系统还算比较满意。

    但我深知,当前的系统还有两个待解决的问题:

    • 任务调度重度依赖数据库, 当真正有10万,20万级别的任务的时候,任务的分配以及调度的触发肯定会有瓶颈。

    • 当前系统是容器的时候,是否可以正常使用。

    于是,我在今年年初在github上写下了自己相对完整的一个任务调度系统,将quartz替换成了时间轮,将任务触发改成服务端推送模式。

    在写任务调度的过程中, 实际上是不断超越自己的过程,我想把系统做成一个可以和行业对标的作品,就必须去向业界最先进的技术产品学习。

  • 相关阅读:
    Linux shell
    华为数字化转型之道 方法篇 第三章 数字化转型框架
    若依框架的使用
    ROSCAR试运行
    商城项目环境准备 — docker安装kinaba和配置ik中文分词器
    Chrome 同站策略(SameSite)问题
    HTML、CSS学习笔记小结
    系列九、Entry的key为什么要设计成弱引用
    SpringBoot启动失败报错,spring.profiles.active:@env@中环境变量@无法识别报错_active: @env@
    动图GIF图片怎么制作?教你一键搞定
  • 原文地址:https://blog.csdn.net/JavaMonsterr/article/details/126034447