在系列文章的第二篇文章《 BAT解密(二):聊聊业务如何驱动技术发展 》中我们深入分析了互联网业务发展的一个特点:复杂性越来越高。复杂性增加的典型现象就是系统越来越多,当系统的数量增加到一定的程度,就由复杂度量变带来了复杂度的质变,主要体现在系统间相互依赖程度加深。下面是BAT解密系列的前三篇文章:
比如说为了完成A业务系统,可能需要B、C、D、E等十几个其它系统进行合作。从数学的角度进行评估,可以发现系统间的依赖是指数级增长的,例如3个系统相互关联的路径为3条,6个系统相互关联的路径为15条。
服务层的主要目标,其实就是为了 降低系统间相互关联的复杂度 。
配置中心故名思议,就是集中管理各个系统的配置。
当系统数量不多的时候,我们一般采取各系统自己管理自己的配置,但系统数量多了以后,这样的处理方式有以下的问题:
-
某个功能上线的时候,要多个系统配合一起上线,分散配置的时候,配置检查、沟通协调需要耗费较多时间;
-
处理线上问题的时候,需要多个系统配合查询相关信息,分散配置的时候,操作效率很低,沟通协调也需要耗费较多时间;
-
各系统自己管理配置的时候,一般是通过文本编辑的方式修改,没有自动的校验机制,容易配置错误,而且很难发现。 例如:我们曾经遇到将IP地址的数字0误敲成了键盘的字母O,肉眼非常难发现,但程序检查其实就很容易;
实现配置中心主要就是为了解决以上这些问题,将配置中心做成通用的系统有如下的好处:
-
集中配置多个系统,操作效率高;
-
所有配置都在一个集中的地方,检查方便,协作效率高;
-
配置中心可以实现程序化的规则检查,避免常见的错误。 比如说检查最小值、最大值、是否IP地址、是否URL地址等等,都可以用正则表达式完成;
-
配置中心相当于备份了系统的配置,当某些情况下需要搭建新的环境时,能够快速搭建环境和恢复业务;
整机磁盘坏掉、机器主板坏掉。遇到这些不可恢复的故障时,基本上只能重新搭建新的环境,程序包肯定是已经有的,加上配置中心的配置,能够很快的搭建新的运行环境,恢复业务,否则几十个配置文件重新一个一个去vim修改,耗时很长,还很容易出错。
配置中心简单的设计如下,其中通过“系统标识 + host + port”来标识唯一一个系统运行实例,是常见的设计方法。
当我们的系统数量不多的时候,系统间的调用一般都是直接通过配置文件记录在各系统内部的,但当系统数量多了以后,这种方式就存在问题了。
比如说总共有10个系统依赖A系统的X接口,如果A系统实现了一个新接口Y,能够更好的提供原有X接口的功能,那么如果要让已有的10个系统都切换到Y接口,则这10个系统的几十上百台机器的配置都要修改然后重启,可想而知这个效率是很低的。
除此以外,如果A系统总共有20台机器,现在其中5台出故障了,其它系统如果是通过域名访问A系统,则域名缓存失效前,还是可能访问到这5台故障机器;如果其它系统通过IP访问A系统,那么A系统每次增加或者删除机器,其它所有10个系统的几十上百台机器都要同步修改,这样的协调工作量也是非常大的。
服务中心就是为了解决上面提到的跨系统依赖的“配置”和“调度”问题。
服务中心的实现一般来说有两种方式: 服务名字系统、服务总线系统 。
1)服务名字系统(Service Name System)
看到这个翻译,相信很多人都能立刻联想到DNS,即:Domain Name System。没错,两者的性质是基本类似的。
DNS是为了将域名解析为IP地址,主要原因是因为我们记不住太多的数字ip,域名就容易记住。
服务名字系统是为了将Service名称解析为“host + port + 接口名称”,但是和DNS一样,真正发起请求的还是请求方。
基本的设计如下:
2)服务总线系统(Service Bus System)
看到这个翻译,相信很多人也都能立刻联想到电脑的总线。没错,两者的本质也是基本类似的。
相比服务名字系统,服务总线系统更进一步了:由总线系统完成调用,服务请求方都不需要直接和服务提供方交互了。
基本的设计如下:
服务名字系统和服务总线系统简单对比如下:
服务总线系统 | 服务名字系统 | |
复杂度 | 设计更加复杂,要同时完成配置和调度功能,且本身高性能和高可用的设计也更加复杂 | 设计简单,基本类似一个服务配置中心,如果要做调度,需要提供独立的SDK包 |
可靠性 | 可靠性的关键,它故障后所有业务间的访问都故障,影响较大,但因为服务总线主要做调度,可以部署两套或者多套并行系统 | 仅仅保存配置,调用还是由服务请求方发起,可靠性要求没那么高,即使故障,各系统也可以使用本地缓存配置继续完成调用 |
灵活性 | 控制所有的调度和配置,可以做得非常灵活 | 仅仅有配置,即使提供独立的SDK支持调度,灵活性也要差一些,毕竟SDK只能拿到静态的配置信息 |
实时性 | 系统完成实际的调度,可以做到非常实时,例如某个服务及机器故障后立刻剔除故障节点 | 提供调度的SDK包,也需要定时更新配置,不能每次请求都去获取一下最新的配置,实时性一般,这个问题和DNS类似 |
可维护性 | 服务总线系统的修改和升级只需要自己完成即可 | 修改和升级大部分情况下要修改SDK包(例如调度算法变更),修改SDK包要求所有系统应用新SDK包才能生效 |
多语言支持 | 服务总线系统支持通用的HTTP和TCP协议,和语言无关 | 服务名字系统提供的SDK包需要适配多个语言,这个工作量也不小 |
消息队列 & 事件订阅
互联网业务的一个特点是“快”,这就要求很多业务处理采用异步的方式。例如大V发布一条微博后,系统需要发消息给关注的用户,我们不可能等到所有消息都发送给关注用户后再告诉大V说微博发布成功了,只能先让大V发布微博,然后再发消息给关注用户。
传统的异步通知方式是直接由消息生产者直接调用消息消费者提供的接口进行通知,但当业务变得庞大,子系统数量增多时,这样做会导致系统间交互非常复杂和难以管理,因为系统间互相依赖和调用, 整个系统的结构就像一张蜘蛛网 ,例如:
消息队列和事件订阅就是为了实现这种 跨系统异步通知 而提供的中间件系统。其中消息队列用于“一对一”通知,事件订阅用于“一对多”广播。如下图以微博为例,可以清晰的看到异步通知的实现和作用:
对比前面的蜘蛛网架构,我们可以清晰的看出引入消息队列或者事件订阅发布系统后的作用:
-
整体结构从网状结构变为线性结构, 清晰 ;
-
消息生产和消息消费解耦, 实现简单 ;
-
增加新的消息消费者,消息生产者完全不需要任何改动, 扩展方便 ;
-
事件订阅发布系统可以做高可用高性能,避免各业务子系统各自独立做一套, 节省工作量 ;
-
业务子系统只需要聚焦业务即可, 实现简单 ;
消息队列和事件订阅系统基本功能实现比较简单,但要做到 高性能、高可用、消息时序性、消息事务性 则比较难。
业界已经有很多成熟的开源实现,如果要求不高,基本拿来用即可。例如:淘宝的Notify、MetaQ、开源的Kafka、ActiveMQ等,但如果业务对消息的可靠性、时序、事务性要求较高时,要深入研究这些开源方案,否则很容易踩坑。
开源的用起来方便,但要改就很麻烦了。由于其相对比较简单,很多公司也会花费人力和时间重复造一个轮子,这样也有好处,因为可以根据自己的业务特点做快速的适配开发。