分析几个有名中间件源码的核心类的设计

分析几个有名中间件源码的核心类的设计

分析过不少中间件产品后,自己也设计过产品后,也需要在更高的高度上总结一下。本文先总结了一下核心类的定义与主要的设计特点,再进一步用druid,rocketmq,spring-jms等源码的核心类进行说明(包括启动/停止),之后还介绍了一下dubbo的核心类在哪?(它更像一个产业链结构,核心类控制采购与销售,中间可能层层外包出去)

在讲核心类之前,其实在软件系统有之前,我们的社会组织,或者企业组织,都是这么个结构,核心类就是中央,就是总经理。当然也有与dobbo对应的组织形态,控制头尾,中间层层分包出去。

理解了核心类,也就很容易理解整个产品的的结构设计了,另外对于应用系统,对于分布式架构,对于微服务架构设计与组织,也有思想上的启发。类比一下,核心类类似DDD(领域驱动设计)中的聚合根,类似于组合多个resposity的service,类似于微服务中的聚合服务。

一、什么是核心类

我们说,擒贼先擒王,研究一个东西要抓核心,那一个软件产品的核心在哪?研究过一些产品后,发现产品都有核心类,自己也应用这个思想把控组件设计。

核心类是产品功能的最主要的实现类,也是类中的老大哥,它下面的兄弟众多,它可能只负责协调调用,也可能功能需要兄弟们协助,也可能根据不同的情况选择不同的兄弟,也可能安排助手去监督兄弟们的工作...

核心类不一定是最花时间的,但一定是稳定系统结构的,显示出整体的思想与高度。有助于在万事开头难的情况下跨出第一步。现在常说API接口优先,那是实现角度,复杂系统的功能是如何进行组织的,可能是更优先的设计。

核心类的基本结构与考量如下:

  • 自身可能是工厂产生,或者是单例对象,一般复杂的都是工厂产生的。
  • 有自身状态,有生命周期。构造,inti,优雅的启动与关闭,在构造中组建好自己的团队成员
  • 它持有其它功能类成员,这些功能类一般不相互引用,但它把this传给功能类,可相互引用
  • 特殊的成员比如,通讯成员,负责对外接收发送数据,也可能对内对外协议不同有不同的通讯员。如果外对支持多协议,还要插入一个通用API层。
  • 成员基于接口引用,所以核心类持有的成员兄弟很多可替换,如果是策略模式,加载根据运行情况,可通过spi,或者aware spring容器去取,或者配置参数用工厂来产生,或者如dubbo统一建一个extension中心。
  • 交给一些成员重要功能时,可能会给它设置一个引用自己的回调类,让核心类感知变化,协调资源
  • 可能包含多个线程或者线程池,可能分工作线程与自身守护线程定时运行,可能需要锁进行同步,Synchronized优化偏向锁,自旋锁,锁再升级,性能可以的
  • 包含共享变量,或者共享数据容器,注意volitile/concurrent/atom使用
  • 核心类自身产生多个对象各自工作,一般用享元模式存放,即static map,当然也可能有个上层进行简单管理,比如多个jms消费容器,上头有注册器统一管理。

二、核心类例子

1. rocketmq中的BrokerController

rocketmq是阿里的消息中间件,它有一个重要模块是broker,功能是接收消息并存储,索引,分队列的,也是推送消息的客户端的。这个模块的核心类是org.apache.rocketmq.broker.BrokerController,它的特点有:

  • 成员众多
    1. 有几个config成员,管不同组件的配置
    2. 有很多Manager成员,比如管消费偏移量,管客户端
    3. 有Service,比如扫描客户端通道状态与变化
    4. 有些Listener,比如监听消息到来
    5. 有负责内部通讯的romotingServer
    6. 有处理消息的Processor,有存储负责DefaultMessageStore等,各司其职。DefaultMessageStore也是存储方面的子核心类,结构关系很相似。
    7. 有很多BlockingQueue与ExecutorService,它们组合起来,再与Processor交给romotingServer用于处理接收不同类型的消息。
  • 构造函数与initialize初始化方法
    1. 构造函数中有很多类似this.topicConfigManager = new TopicConfigManager(this);的语句,即产生了成员对象,也实现了核心类与功能类对象的相互引用。如果设计不好,走“捷径”,成员之间相互引用,就乱成毛线团了。而成员由于引用了核心类对象,找谁也找的到,也就无所不能了。
    2. initialize中,先由一些成员load加载数据,满足条件后,再进一步产生一些成员,比如存储管理,通讯管理等成员。
  • init中安排成员工作
    1. 比如安排通讯成员工作。在初始化中,通讯成员启动后把Processor与ExecutorService交给它,通讯成员就开始工作了,监听tcp端口,处理请求了。那请求时需要核心类帮忙怎么办?通过SendMessageProcessor sendProcessor = new SendMessageProcessor(this);,处理器可以找到核心类,那工作都好做了。
    2. 比如安排存储成员工作。先产生了DLedgerRoleChangeHandler传给存储成员,存储发生变化时会通知这个handler,hander由于持有核心类对象,所以也无所不能。
  • 生命周期管理
    1. 比如start()方法,让很多成员也start(),也让自己的定时守线线程启动起来。
    2. shutdown()让很多成员也shutdown(),线程池也shutdown了。
  • 如何启动/停止核心类
    1. BrokerStartup用main方法开始,调用构造函数new 一个BrokerController。
    2. 并在Runtime停止时,用hook关闭核心类对象。
    3. 另外提一下,客户端client的核心类是懒加载的,有producer了才启动。没人用,不空转。

通过上面的总结,一个简单的消息处理过程是这样的:通讯成员接收客户端消息,它在线程池中用处理器处理时,处理器可以找到核心类,核心类会让存储存好数据,再返回结果,通讯成员就可以回复客户端消息接收成功了。

2. druid中的DruidDataSource

druid是阿里的数据库连接池产品,代理了几个常用的连接,并在所有操作中切入filter得到监控数据。这的最外层核心类是:com.alibaba.druid.pool.DruidDataSource,包括它父类。它的特点有:

  • 成员众多
    1. 它不是上面那样很多功能类,而是很多基本数据,如:long弄的Count或者AtomicLong。正好符合统计监控的需要
    2. 重要的数据,即一堆真实的数据库连接。DruidConnectionHolder[]
    3. 有两个重要线程Thread,分别用于增加与减少真实的连接数。还有logStatsThread统计线程。
    4. 有个CountDownLatch,用于保证init操作在两个额外线程完成后完成。
    5. List用于存放将插入监控的过滤器,Init中也反持有核心类。
    6. 有ReentrantLock用于控制共享数据的操作,其它略。
  • 构造函数与初始化
    1. 由于它实现了jdbc的DataSource接口,可以就当一个DB数据源使用。在getConnection获取数据库连接时,进行Init()操作。
    2. Init中初始化一些值,包括连接的容器外,还启动了控制pool中连接数量的两个线程。是否可以用一个?
    3. 过滤器filter是插入sql操作中,真正进行监控的工具,核心类用List保存它们,而在核心类的Init中有:filter.init(this);说明每个过滤器都要持有核心类来工作,至少会把数据写在核心类的统计属性中。
  • 关闭数据库连接
    1. close()中,interrupt这些线程,connHolder中的真正连接都close(),清空容器,清空filter的list。

通过上面的总结,一个简单的连接过程是这样的:调用DruidDataSource获取连接方法,如果DruidDataSource没初始化就进行初始化,从DruidDataSource中再拿到filter来处理统计,再用filter反持有的核心类DruidDataSource真正做事。实际中用了过滤链模式。这个druid原理很简单,但所有操作,所有对象要包装起来,琐碎工作量还是很大的。

3. spring-jms中的消费容器DefaultMessageListenerContainer

与前面的核心类相比,这个org.springframework.jms.listener.DefaultMessageListenerContainer并不是很突出,不过获取消息进行消费都是在这个容器中进行的。而且每一个注解@jmsListen都产生一个,它有多层父类,其特点有:

  • 它有也不少属性成员
    1. 包括很多参数,重要成员有:Executor,transactionManager,messageListener,messageConverter等
    2. 还有放置runnable对象的容器Set,可放Executor中执行,线程之间有Monitor对象,用synchronized进行同步处理
    3. 它持有的线程启动后,很聪明的进行自我管理,可减少,可增加,可休息
  • 它受spring生命周期管理,有明确的initialize()/start()/stop()方法。
  • 它受上级管理
    1. 虽然它做核心功能,但由于它有很多相同的对象一起工作,所以有上层的管理者JmsListenerEndpointRegistry。
    2. JmsListenerEndpointRegistry用工厂JmsListenerContainerFactory构造出它后,用Map<String, MessageListenerContainer> listenerContainers保存它。算享元模式。
    3. 上级也试图对它们进行start()/stop()控制。

由于这个工具是在spring中,启动过程就是扫描注解,按@jmsListen产生这样的真正工作的容器,并接受spring的控制。

4.dubbo中的核心类

与前面几个产品中的核心类不同,回忆dubbo还真不如上面的这么明显存在一个核心类。

先整理下服务端接收调用过程例子:一个包含invocation的TCP请求过来,netty从早就建好的了channel中decode出来特定应用协议msg,看消息头,如果是业务处理msg,就找相应的handler处理消息,handler会从一个map表中找到invocation对应的invoker,invoker是由service统一包装出来的,再反射调用得到结果,再封装成msg,再由netty从channel中发回去。

服务端本来只有一个个service,通过dubbo产生上面的所有相关的东西。dubbo操作的过程是:

  • 先把一个个service变成invoker
  • 一个个invoker以invocation为key,存放在map中
  • 启动一个netty通讯服务端,这个过程中要产生Netty用的decoder/encoder/msgHandler等类
  • 要给msgHandler设置一个专门处理业务的内部handler
  • 内部handler要能从msg中拿到invocation,再从map中拿到invoker
  • 最后调用到service,得到结果,后面不提了。

这里,被加工的对象就是service,结果是外部的各种请求可以调的到它。那么是否有设计一个核心类来做上面这一系列工作呢?没有。

或者说,由于dubbo的微核心化,大多数功能的实现类都是不确定的,在运行时由参数来确定。实现类统一放在extension中存放,甚至extension本身也有两种实现。在spring中用的话,那么上面的功能实际上由两个主要的类来实现。

核心类一个是ServiceBean,一个是协议如DubboProtocol。spring启动前者开始工作,前者再根据配置启动DubboProtocol工作,当然最主要的工作还是后者来做了。而且两个核心类的角度不同,ServiceBean对应一个service,但可以用多个Protocol暴露,而一个Protocol持有多个service的invoker,就是多对多的关系。

DubboProtocol持有的成员,与工作有:

  • 持有一个New构造的真正处理RPC请求的requestHandler
  • 持有exporterMap,让requestHandler可以找到对应的Invoker
  • 提供参数启动具体的传输层,并把requestHandler给它,由于它是内部类,不用象上面用this引用核心类了。
  • 传输层具体怎么做就不管了,反正参数都给了,算是分包出去了。最后处理还是找我的内部类处理。
  • 传输层会根据参数产生具体的netty,产生具体的encode/decode,再产生具体的serilize。

虽然dubbo的核心类不好设计,但顺着业务用例的流动,可以梳理出一个核心类选择另一个核心类工作的设计。如果不是要设计的这么灵活,特定的协议下,也可以写出一个清晰的核心类来做上面的工作。

5. 图

看着也蛮简单的,主要还是很多细节工作要做,而且细节要考虑的很仔细。比如rocketmq主要是存储,索引,队列和高可用工作。

在这里插入图片描述

三、总结

其实还有些小例子就不一一举例了,我在设计组件时,也基本上采取这样的设计,感觉比较清楚,再复杂都可以可以把握全局,如同现实生活中,做好业务就有一个好的公司架构,也有强大的核心。

自己设计中,就可以把相似的功能复制过来用,比如rocketmq的内部通讯可以拿来用;或者把局部好的设计抄过来用,比如线程的自我控制,通讯处理器与带有阻塞队列线程池的组合,细粒度控制;设置监听了解成员的工作;另外成员其实也是局部功能中心,它也可以是层次结构;成员可以在运行中替换,比如dubbo中动态生成适配器成员,按参数持有真正成员;成员可以统一存放,用多种方式加载,比如工厂,比如SPI…

话说现在最喜欢的源码还是rocketmq,1主多从小集合比kafka全对等好,这与看到的某支3城5中心中的10个组,以及某信抢红包的一个set,都是相同的理念;另外对文件的处理值得参考;还有通讯设计可以直接用…

了解了结构也可以更好的使用,给使用者提供简单明了的操作入口。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值