微服务是如今比较热门的话题,但是到底什么是微服务,微服务带来了哪些好处,又引入了什么问题,该如何设计微服务等等。
很多第一次了解微服务的同学可能会觉得这些内容很多,会对微服务产生距离感与畏惧感,当然我在最开始也是这样。
所以一开始我直接找了一些网上所谓微服务实战的教学视频,不管是什么语言实现的基本上都是那一套内容:从服务注册与发现,到服务治理中的熔断与限流,再到最后的服务链路监控。
这些视频很少有告诉你为什么会这样做,只会告诉你这样做的好处,无法明白这些步骤在整个微服务生命周期中到底有什么作用,我就像个僵尸一样,别人说应该这样做,那就这样做吧,缺乏一些自己的思考。
向大佬学习
读书永远是学习门槛最低的,花费最少的,同时也是最有效的学习方法。怀着对微服务懵懵懂懂的感觉去找了在业界质量比较高的动物书系列中的一本比较经典的微服务书籍学习了下:微服务设计
这本书并没有如八股文似的明确告诉你什么是微服务,像包工头似的指挥你做什么,更多的是分享大佬自己的经验,用实际经验与读者交流探讨微服务到底做了什么,该如何设计微服务
学习总结
接下来分享下自己从书中学习到的东西,以及一些自己的想法
内聚性
根据业务边界来确定服务的边界,保证每个服务具有单一职责原则,做到单个服务代码的内聚性
自治性
技术异构性
微服务群上的每个服务并不一定要统一使用一种技术实现,对于不同服务选择最合适的才是最好的
风险最小的服务
上尝试新技术弹性
扩展
简化部署
由于单个服务的代码量不大,以及与其他服务的耦合性低,当修改一个服务的代码,可以很简单的快速部署
这意味着微服务可以快速的进行功能迭代
与组织结构匹配
可组合性与可替换性
面向服务的架构SOA
可以认为微服务是SOA 服务的一种实现
难点:
实施SOA 最大的难点就是对服务的拆分粒度,通信协议的选择,第三方中间件的选择等:
到底拆分多小才算小,多大算大,目前没有所谓的标准, 如果一开始无法很清晰界定上下文的拆分,可以不用拆分的太细,=
并不是说一开始拆分为多少个服务,后续就一成不变的
但是一个好的微服务的架构并不是一开始就设计出来的,而是不断演进的
计算机行业发展不到百年,相比于其他传统行业,还是一个很年轻的行业。
架构师的职责:
共同的愿景
,以帮助我们向客户交付想要的系统软件工程师:
计算机行业的发展不到百年,相比于其他行业,还处于很年轻的状态
我们通常将自己称为软件工程师
,但是业界却没有权威的认证,不像传统的建筑业有建筑师的认证,因此市面上的很多IT证书是没有意义的
类比建筑师
建筑师需要做好详细的计划,然后让别人去理解和执行
但是在软件行业,如果以建筑师的视角去当架构师,将会导致非常糟糕的实践
软件系统并不是一个设计好就一成不变的系统,他是演进式
的,因此一个好的架构师的职责就是推进一个系统不断往好的方向演进
软件是不断演化的,架构师应该尽量预期可能发生的变化,但是这个预测并不能做到面面俱到
大方向
开心
架构师更多需要关注的是服务的边界
, 考虑服务之间如何交互,保证能够对整个系统的健康状态进行监控
技术架构师
集中治理和领导
架构师的部分职责是治理
治理通过评估需求,保证企业目标的达成,通过排优先级和做决策来设定方向。并且对已经达成一致的方向和目标进行监督
最好的做法是架构师和开发小组一起交流讨论出治理方案,团队的力量永远大于个人
架构师对于团队的领导必须学会放手,给团队一定的自由
就好比教小孩子骑单车,必须学会放手,当驶向马路的时候,我们就要及时制止,也就是说要把握大方向
建设团队
对于技术领导人来说,更重要的是帮助你的队友成长
,帮助他们理解技术愿景
,并且积极地参与到愿景的实现和调整中来
规则对于智者来说是指导,对于愚者来说是遵从
战略目标
原则
实践
原则与实践相结合
要求的标准
每个公司都希望所有技术人员做到代码规范,很多公司都有自己的规范标准
服务代码模板
技术愿景是为业务愿景所服务的,有时候当业务需要紧急特性上线时,技术不得不妥协,违背定下的一些原则与约束
对于创业公司来说技术债务会尤为显著,毕竟为了公司业务的快速发展,架构师与开发人员不得不妥协
但是要正视这些技术债务,它是一个公司发展所必经的旅途
架构师要做的就是要评估这些债务对系统的影响,然后提供一些温和的指导,让团队自行决定如何去偿还这些债务
和现实世界一样,欠的债必须还,即使还款速度很慢。
如果不偿还这些技术债务,随着这些技术债务的累积,系统必将处于奔溃的边缘
寻找理想的集成技术
避免破坏性修改
尽量避免对一个服务的修改导致下游消费方也随之改变
保证API的技术无关性
不应该限制服务间通信的方式,因此保证服务间通信API与技术无关
服务易于消费方使用
隐藏内部实现细节
对消费方暴露的内部实现细节越多,服务间耦合性就越高
集成方式
共享数据库集成
相当于拆分了服务,但是使用同一个数据库来进行数据的通行,这种集成方式实现起来简单,但是耦合性也是最高的
同步与异步
该集成方式目的是隐藏服务的内部细节,使用通信来协作
同步通信比较简单,很容易清楚整个业务流程,知道通信结果成功与否
异步通信对于运行时间长的任务来说比较有用
两者并没有好坏之分,合适的场景才是最好的
同步一般是请求/响应的方式,异步一般是基于事件的方式,基于事件的方式就可以很好的解耦服务与消费方
编排与协同
编排协作必须有一个大脑服务负责告知其他服务需要做什么,但是不需要知道怎么做的细节
缺点:
编排服务会导致以大脑服务为中心的网状服务,导致其他服务出现基于CRUD的贫血服务
协同一般是基于异步事件的方式,服务方不需要关心有哪些消费方,只需要发布事件至事件循环中,对应的消费者只需要订阅该事件即可
该协作方式可以很好的降低系统的耦合度
远程过程调用
优点:
缺点:
RPC的优点恰恰也导致了其缺点,快速生成桩代码的方式,导致了客户端与服务端的过渡耦合
服务端修改一个字段,客户端也得跟着修改一个字段,导致lock_step发布方式
因为使用方式太过简单,就像调用普通方法一样,很容易导致开发人员不知道是RPC调用还是调用的本地方法
因为RPC即使 隐藏了远程调用的复杂性,但其还是网络通信,网络通信是不可靠的,需要做一系列的处理
REST
REST可以作为RPC的替代方案,该风格强调的是资源的概念
基于事件的异步协作方式
该方式需要考虑两点:服务如何发布事件机制,消费者如何接收事件机制
可以通过已有的消息代理中间件实现这种发布订阅模式
异步架构可以非常好的解耦服务间的耦合,同时也带来了挑战与复杂性
当在排查异步架构的问题时,需要使用异步模式的思维去思考
同时我们要确保异步的每个流程都有很好的监控,因此必须要有链路追踪监控的功能
响应式扩展
可以把多个调用的结果组装起来并在此基础上执行操作
该集成方式在分布式系统中使用的比较多
版本管理
尽可能推迟
减少破坏性修改影响的最好方法就是不修改
有时候产品与研发会因为一些特殊的功能
导致需要修改服务,如果说这个功能是没必要的后者可以换种交互方式就可以做到不用发布新版本,作为开发人员其实是可以和产品磋商的,实在不行延迟发版也行
及早发现破坏性修改
一旦发现不可避免的破坏性修改,就要及时和收到影响的消费者服务的维护人员沟通同步,测试到位
使用语义化的版本管理
使用三个数字表示:例如1.3.0
不同版本接口共存
有的客户端用户希望使用旧版本,有的有希望使用新版本的功能,并且这两个版本用的是同一份数据
这个时候就遇到了破坏性的修改
多个版本共存, 平滑迁移版本,最终在合适的时机弃用旧版本
如果使用的HTTP系统,可以在URL中加上版本号来区分
迭代速度
相比于多个团队维护一个巨大的单块系统,我想一个小团队维护一个拆分后的小系统更加轻松
在一个自治单元上的迭代将更加快速
团队结构
安全
由于安全审计的机制,很多敏感信息需要做严密的保护
将这部分业务的代码作为单独的服务分离出去,可以做到更好的监控、传输数据的保护和静态数据的保护
技术
拆分为多个服务后,在单个服务的技术迭代上将更加优雅简单
比如,如果一个系统的核心功能是推荐算法,并且该算法是需要不断迭代更新的,我们就应该将该算法的代码抽离为单独的服务,交给负责算法的团队维护
关键
寻找服务的边界,然后以增量的形式进行
秉承增量的分解方式,要先找到一个拆分点,然后不断地去削减单体服务
自下而上分解
从数据库开始分解
外键约束
:
再分解数据库时会遇到外键的问题,我们应该放弃外键的约束,使用代码来实现这个约束
这种方式可能会导致服务间多次通信达到约束的效果,降低系统的响应速度
如果系统对响应速度要求不是太高,这种拆分数据库取消外键约束的方式是一种很好的方式
共享数据
:
有时候一张表设计不合理包含了多个领域的字段
比如说为了方便动态修改客户的订单信息,客户表包含了仓库和财务的的字段信息
此时就需要用到领域的抽象方式,识别出客户的界限上下文,将该表抽象为财务,仓库和客户三个服务
自上而下分解的好处就是,消费者基本上是无感知的,并且可以随时选择回退或者继续修改而不影响外部服务的调用
自上而下分解
从服务开始分解
兼容旧数据
:
从服务开始拆分,会出现为了兼容旧数据而写很多无意义的代码
影响消费者
:
服务变更大概率会导致接口变更,直接影响到消费者
好处
:
但是从服务开始拆分相比于从数据库开始拆分更加简单直观
在未拆分前可以通过外键的约束或者在同一张表内来达成事务的一致性,在拆分为多张表后就被分离为了多个事务
最终一致性:
当出现了多个事务的时候,可以使用队列来记录事务先后顺序,当其中一个事务出现异常时,可以通过捕获异常,重试的方式来保证整个系统的一致性
终止整个事务
当事务的异常没办法导致重试时,需要发起补偿事务
也就是反向操作来重置整个系统的初始状态
简单来说比如执行了add异常,那就执行delete 抵消
分布式事务
可以利用分布式事务特性来保证整个系统的一致性
分布式事务使用两阶段提交
算法保证系统事务一致性,该算法有两个阶段:投票阶段与执行阶段
缺点:
这种方式会导致参与者在投票完后暂停执行,一旦事务管理器宕机,整个系统将会暂停
并且协调进程会使用所,执行中的事务可能会对某些资源持有一个锁,导致后期系统难以扩展
取舍
如果说系统对于实时性要求不高,建议使用最终一致性加事务补偿的方式来保证整个系统的一致性
报表是对数据库中的数据的一种可视化操作,一般不包含太多的逻辑,使用SQL查询
但是报表的操作很依赖于数据库表结构,一旦表结构变更,可能会导致报表系统无法使用
调用服务获取数据
系统拆分后,不同数据保存在不同服务的表中,可以通过调用不同服务接口的方式来获取数据
但是一旦数据量过大或者访问频率高,这种方式的效率是很慢的
可以通过HTTP缓存或者批量请求来加速获取大文件
数据导出
继续集成
好处
CI需要做什么
每个微服务对应一个CI构建
流水线和持续交付
流水线
构建流水线将构建拆分为多个阶段,在每个阶段做不同的事情
比如说可以将测试拆分为快速测试阶段与耗时测试阶段,先运行快速测试阶段,如果说快速测试阶段失败了,就没必要继续运行了
持续交付
测试四象限
测试范围
服务测试实现起来比单元测试复杂,需要利用打桩或者mock
打桩:对于被测试服务的一个获取数据的透明服务,测试时不关心该打桩服务被访问了多少次
mock: 被测试服务也可以从mock 获取需要的数据,与打桩相比mock 会进一步验证请求本身是否被正确调用
端到端测试会包含多个服务的测试,测试范围更大,同时测试复杂度也最大,也最不利用定位失败的位置
不管是单元测试还是服务测试,或者是端到端测试,最好的做法都是使用流水线构建做到自动化测试
一种坏的流水线构建方式
比较好的做法
让多个流水线的服务扇入到一个独立的端到端测试的阶段
任意一个服务的构建都会触发一次端到端的测试
服务测试和端到端测试本身就是复杂的,容易失败的
包含在测试用的服务数量越多,测试就会越脆弱,不确定性也就越强
当脆弱性测试成为常态,测试人员可能就会对测试套件失去信心,认为这些失败是异常的正常化
服务测试套件应该也是迭代演化的,当一个服务测试不断失败的时候,我们就需要考虑该测试套件是否合理,然后暂时
将该失败服务测试从整个服务测试套件中移除
谁来写测试
服务的测试是由维护服务的团队成员写,还是由专业的测试团队写
一个好的做法是共享端到端测试套件的代码权,但同时对测试套件联合负责
测试团队可以随意提交套件,但是实现服务的团队必须维护套件的健康
消费者驱动测试
替换端到端测试的一种方案
消费者编写测试用例,当服务发版不满足消费者测试用例时,认定服务有问题
链路监控是微服务实践中最重要的环节之一
以前的单体服务,我们很直接的从单个系统日志中定位问题,但是拆分为微服务后,就会出现分布式部署的情况,有时候接口间的调用还是异步的,如果没有日志链路监控,排查问题将非常痛苦
目前市面上日志统一搜集与聚合可视化的工具已经比较成熟,logstash + kibana 就是一套标准的日志收集聚合显示的方案
但是日志链路监控的工具不是很多,相比于日志可视化没那么好用
日志链路监控的核心也是关联ID,需要有一个关联ID可以关联整个调用链路
不管是单体服务还是微服务,服务安全都很重要
身份验证和授权
一些单体服务中使用的安全策略在微服务中同样是适用的,比如说秘钥验证,JWT token 验证等
静态数据的安全
可以通过加解密来保证静态数据的安全,但是一定不要自己造轮子,使用业界比较成熟的加密方式就可以了
深度防御
康威定律
当协调变化的成本增加后,有一件事会发生:人们要么想方设法降低协调沟通成本,要么停止更改
而后者正是导致最终产生庞大的、难以维护的代码块的原因
内部开源
如果实在没办法让一个团队拥有单个服务的所有权,最好的做法就是使用内部开源的方式:
将开发团队分为核心提交者和代码守护者
代码守护者做的最多的就是审核MR的代码质量
反脆弱组织
使用故障机器人猴子军队可以使你的服务更加健壮
超时
如果等待太长时间来决定调用失败,整个系统会被拖慢
如果超时太短,会将一个可能还在正常工作的调用错认为是失败的
如果完全没有超时,一个宕掉的下游系统可能会让整个系统挂起
断路器
当请求下游失败达到一定阈值后,会触发断路器,认定下游服务出现故障,此时会断开与下游的连接,接下来的请求会快速的失败
客户端会发送偶尔健康检查,请求查看下游是否已经回复
如果已经恢复会重置断路器,恢复正常连接
舱壁
扩展
扩展原因
加机器
拆分负载
分散风险
负载均衡
基于worker 的系统
拆分为基于worker的系统也可以很好的实现实例的扩展
但是需要一个可靠的消息队列,例如zookeeper 和MQ
自动伸缩
在未负载伸缩时,一定谨慎不要太仓促缩容
在大多数情况下,手头有多余的计算能力,比没有足够的计算能力要好得多
CAP定理
一致性(consistency)
可用性(availability)
分区容忍性(partition tolerance)
在分布式系统中,最多满足CAP中的两个
AP系统
CP系统
AC系统?
使用CP还是AP?
服务注册与发现
比较成熟的服务注册与发现的工具有:
Zookeeper, Consule, Eureka
文档服务
swagger
HAL和HAL浏览器
对本书的总结
这本书并没有讲解微服务具体的实现细节,而是站在更高的角度去探讨微服务到底应该做什么?
也没有明确的说明什么叫微服务,而是明确的指出微服务架构应该是一个演进式的架构,我们要做的就是拥抱变化持续演进。
这本书花了很多的篇章去讲解服务拆分相关方面的知识,其中涉及到了很多领域驱动涉及方面的知识,作者并没有细讲DDD方面的内容,而是推荐读者去读DDD相关方面的书籍,但是却一直强调界限上下文的重要性,这是拆分服务的第一步,也是最难和最重要的一步。
这本书的后半部分则着重强调CI/CD工具对微服务的重要性,这个确实很重要,不然微服务一旦达到一定规模就是灾难。
作者也一直强调演变的重要性,保持不变的最好方式就是改变。
个人对于微服务未来的展望
作者是在2016年出版的这本书,如今已经是2022年,如今的云原生已经成为了不可阻挡的趋势,个人认为接下来的微服务更多的将会是以云原生为载体去实现,换句话说就是结合k8s 以及相关生态去实现微服务。
如果系统没有分区容忍性,就不能跨网络运行,所以CA系统在分布式系统中根本不存在
使用CP还是AP?
服务注册与发现
比较成熟的服务注册与发现的工具有:
Zookeeper, Consule, Eureka
文档服务
swagger
HAL和HAL浏览器
对本书的总结
这本书并没有讲解微服务具体的实现细节,而是站在更高的角度去探讨微服务到底应该做什么?
也没有明确的说明什么叫微服务,而是明确的指出微服务架构应该是一个演进式的架构,我们要做的就是拥抱变化持续演进。
这本书花了很多的篇章去讲解服务拆分相关方面的知识,其中涉及到了很多领域驱动涉及方面的知识,作者并没有细讲DDD方面的内容,而是推荐读者去读DDD相关方面的书籍,但是却一直强调界限上下文的重要性,这是拆分服务的第一步,也是最难和最重要的一步。
这本书的后半部分则着重强调CI/CD工具对微服务的重要性,这个确实很重要,不然微服务一旦达到一定规模就是灾难。
作者也一直强调演变的重要性,保持不变的最好方式就是改变。
个人对于微服务未来的展望
作者是在2016年出版的这本书,如今已经是2022年,如今的云原生已经成为了不可阻挡的趋势,个人认为接下来的微服务更多的将会是以云原生为载体去实现,换句话说就是结合k8s 以及相关生态去实现微服务。
接下来我将系统的学习k8s 相关方面的知识,去探索微服务在云原生舞台上的表现。