转载请注明出处 http://www.paraller.com
原文排版地址 点击跳转得到更好阅读体验前端
这是一篇《微服务设计》的学习笔记,主要是本身提炼的一些知识点,书比较薄,建议看原版书理解相关概念。算法
简介
相关概念
- 背景:随着代码库愈来愈大,代码修改困难 、 模块之间界限模糊 、 类似代码过多。
-
内聚性 - 单一职责原则
:相同缘由而变化的东西放在一块儿,因不一样缘由变化的东西分离开来;微服务将这个理念应用到独立的服务上,根据业务的边界来肯定服务的边界。
- 微服务是SOA的一种特定方法
特性:
- 一个微服务就是一个独立的实体,能够独立部署
- 服务之间经过网络进行通信
- 服务彼此间能够独立的进行修改,服务的部署不该该引发消费方的变更
- 服务暴露过多,会形成和消费方的紧耦合
优势:
- 技术异构性: 尝试新技术,下降风险
- 系统中组件不可用,不会形成级联故障
- 扩展:对服务进行针对性的扩展
- 简化部署:特定代码部署,不影响系统总体,快速回滚
- 组织结构匹配: 不一样的团队负责不一样的服务
- 可组合性: 对不一样的场景组合服务
分解技术
微服务
- 分布式系统的复杂性
- 部署、测试、监控的投入
- 类型分布式事务和CAP的考虑
共享库
对重复代码进行分包组织,工具类,重复业务代码类。缺点以下:spring
- 没法使用异构技术
- 每次更新,须要将相关的程序从新部署
- 公共任务而且不属于业务代码,能够这样作,但若是涉及服务间的通信,会成为耦合点
模块
Erlang的模块化能力惊人;难度比较大,很容易会和其余代码耦合在一块儿数据库
微服务演化
须要注意细则
- 架构师相似城市规划师,专一在大方向上,有限状况参与到具体的开发,不关注每一个区域内发生的事,更关注区域之间的事情(服务之间的交互)
- 将来的变化很难预见,对全部可能性进行预测,不如作一个容许变化的计划
- 系统设计方面的决定一般是取舍。
- 为了和更大的目标保持一致,制定一些具体的规则,称为
原则
- 原则做为指导,
约束
是很难被改变的。显示指出二者,并按期回顾是否要修正。
- 编写文档是有用的,配上真实的代码范例
- 架构师提供一些温和的指导,让团队自行决定什么时候偿还债务,维护一个债务列表,并按期回顾
- 偏离原则:针对某个场景记录下来,当例外不少次出现,考虑修改原则
- 架构师和团队小组存在分歧,大部分状况要认同小组的决定。
要求的标准
- 建议确保全部的服务使用
一样的方式
报告健康状态 及 监控相关的数据,标准化,隐藏具体技术实现, 日志服务和监控服务同样,要集中化
- 使用统一的接口协议
如何建模服务
概念 & 准则
松耦合
- 独立修改部署而不影响系统的其余部分
- 限制服务间的调用数量,除了性能问题,过分通信会形成紧耦合
高内聚
改变某个行为,只须要在一个地方进行修改,就能够尽快发布,快速修改,低风险发布编程
bounded Context(限界上下文)
共享的隐藏模型:
- 财务和仓库两个限界上下文,会对仓库的 库存模型存在交集,针对库存模型, 应该存在 内部和外部两种表示方式,不暴露全部属性
- 共享
特定模型
,不共享内部表示能够避免潜在的紧耦合,
- 一旦发现了领域内部的限界上下文,必定要使用模块对其进行建模,同时使用共享模型和隐藏模型
其余
- 对于一个新系统而言,可使用一段时间单系统,避免后期的修复代价。
- 将一个已有的代码库划分为微服务,比从头开始构建微服务要简单
集成
集成技术选型:
- 不该该选择那种对微服务具体实现技术有限制的集成方式
- 使服务易于消费方使用(提供客户端库能够简化使用,可是增长了耦合)
- 隐藏内部实现,避免服务方的任何修改均可能影响到消费方
共享数据库
- 外部系统可以查看内部实现细节,并与其彻底绑定在一块儿,全部服务均可以完成访问该数据库; 若是修改数据库会致使消费方没有办法工做。须要大量的回归测试
- 消费方和服务绑定在一块儿,没法轻易的替换技术
同步与异步
同步:及时的获得操做的响应 ;请求/响应
异步:适用长时间的操做;基于事件后端
编排与协同
场景:建立用户的操做, 须要发放优惠券、建立银行帐户、发送欢迎邮件浏览器
编排
- 使用客户服务做为中心,同步顺序的调用操做,能及时知道每一步是否成功
- 客户服务成为中心控制承担了太多职责,中心枢纽和不少逻辑的起点
协同
消除耦合,但没有明显的流程视图,没法保证每一步流程都正确执行,须要更多额外的工做,来构建一个与业务流程匹配的监控系统,缓存
折中方案
使用异步回调的方式。安全
请求/响应的技术:
RPC
- 核心特色,使用本地调用的方式和远程进行交互。
- 核心思想是隐藏远程调用的复杂性,可是不少框架
隐藏过头
了;使用本地调用不会形成性能问题,可是RPC花大量的时间来对负荷和解封装,以及网络通讯的时间,简单的把一个远程服务改形成跨服务的远程API每每会带来问题
- 更糟的状况是: 开发人员不知道调用时远程调用,并对其进行使用
- 网络的出错模式不止一种,很难
对问题进行定位
- 脆弱性:对象参数的修改,须要对客户端从新生成打桩,应用这些修改须要同时部署客户端和服务端
- 选用RPC,必定不要对远程调用过分抽象,确保能够独立的升级服务器,
不要隐藏
网络调用的事实
REST
- HTTP周边有很大的生态系统,包含不少支撑工具和技术,好比 Varnish HTTP缓存代理 / mod_proxy 负载均衡 / 大量的监控工具
- HTTP也能够用来实现 RPC,好比soap就是基于HTTP进行路由的,只是使用了少许的HTTP特性
- 对于有些接口来讲,HTML既能够作UI,也能够作API,
- 建议使用XML,在工具上有不少支持
- springboot过多的约定带来了紧耦合
- 使用客户端库会增长复杂度,由于人们不自觉地回到基于HTTP的RPC思路上去了,而后构造出一堆共享库,在
客户端和服务端之间共享代码是很危险
的,
- 在低延迟要求的服务中,HTTP的封装开销须要注意
- 低延迟通讯最好的选择是TCP编程
- REST获得序列化和反序列化须要本身实现,会成为消费者和服务端的耦合点
基于异步的实现
增长开发流程的复杂度,须要额外的系统才能开发及测试,须要额外的专业知识和机器保持基础设计正常运行
- 原则: 尽可能让中间件简单,将逻辑放在本身的服务中
- 设置最大重试次数,失败的消息统一发送到一个地方,进行查看和重试,
- 确保使用监控机制保证每一个流程,而后对流程进行ID关联 (zookeeper)
- 把关键领域的生命周期显示建模出来很是有用,不但能够在惟一的地方处理状态冲突,还能够在这些状态的基础上封装一些行为
灾难性故障转移: 队列中存放了任务,消费者A处理崩溃,消费者B处理也崩溃,一个异常元素致使一系列的消费者崩溃。
DRY:避免重复代码
若是有相同代码作一样的事情,代码规模就会变大,从而下降可维护性
建立一个随处可用的共享库?
- 在微服务中是危险的,会致使耦合,客户端和服务端须要同时更新部署
- 但在服务间使用日志库代码不是问题,由于对外是不可见的
- 服务间使用共享库比重复代码还要可怕
客户端库
若是要使用,要保证只包含处理底层传输协议的代码,好比服务发现和故障处理等等,千万不要把与目标服务相关的逻辑代码放到客户端库中
按引用访问
- 微服务应该包含核心领域实体的全生命周期的相关操做,服务应该是关于该领域的惟一可靠来源
- 对服务发起一个资源的请求,而后保存在本地副本中,可能一段时间会失效,因此请求返回的结果,要保存一个指向原始资源的引用(好比一个资源URL),确保须要最新数据的时候能够有办法获取
- 老是经过一个服务去获取某个领域的信息,会形成过多的负载,若是可以获得该领域的有效时间是最好的
版本管理
- 尽量不作破坏性修改,使用良好的架构设计
- 鼓励客户端正确的行为,例如json传输数据,一些强类型语言会使用绑定技术,会将全部的字段绑定,不管消费者是否须要,当修改接口数据结构的时候会影响到消费者,可使用XPath技术提取出想要的字段
- 鲁棒性原则,每一个模块都应该
宽进严出
,发送的东西要严格,接收的东西要宽容
- 使用语义化的版本管理,格式以下:
major.minor.patch
;major表明包含向后不兼容的修改; minor意味着新功能的增长 ; patch表明对缺陷的修改
- 不一样接口能够共存,发布一个破坏性修改的时候,能够部署一个包含新老接口的版本;但更建议在V1接口中转换后请求V2接口
- 同时使用多个版本的服务
BFF(Backed for frontends)为前端服务的后端
对于不一样的客户端,使用聚合接口,对后台调用的服务进行编排,相似于一个专门的后台服务,好比Node程序,对JAVA后台的接口进行组合,也称做
分解单块系统
- 首先识别出单块后台系统明显的几个上下文
- 为他们建立包结构来表示,把已有的代码进行移动
解决横跨不一样上下文的表
- 打破外键约束,将访问变成逻辑外键,经过暴露的API进行交互
- 共享的静态数据,经过配置文件和代码中进行配置,不要放在公有包中
共享数据
- 不一样的上下文会对同一张表进行读写操做:概念领域不是在代码中进行建模,相反是在
数据库中隐式的建模
,表明这个表是一个上下文,做为一个中间步骤,能够建立一个新的包最终变成一个服务
- 共享表:存在一个通用的行条目录表,不一样上下文都用到了部分数据:能够分红两张表
重构数据库
- 先分离数据库结构,不对服务进行分离
- 对数据库的访问次数会变多,之前一个查询得到全部数据,如今要内存中进行组装
事务边界
一个事务能够帮助咱们的系统从一个一致性状态 转移到另一个一致性; 分离数据库以后,没有了原生的事务处理,解决方案:
- 再试一次:把失败的操做,记录在日志或者失败队列中,后面对他们尝试触发,要保证从新触发可以成功,最终一致性
- 终止整个操做:对上一个成功的操做进行补偿事务来抵消以前的操做,可靠性不佳
- 分布式事务:外部的事务管理器统一编排执行,经常使用算法是两阶段提交,可靠性也不佳
总结:是否真的须要强一致性? 是否要跨业务进行操做? 是否能够经过业务逻辑的处理避免事务,好比新增处理中的订单
状态
报表:
- 为了防止对主系统的影响,报表的查询使用副本; 缺点:共享数据库结构会抑制修改表结构的积极性
- 使用MongoDB或基于列的数据库来 保存副本
数据库分布在不一样的系统中
- 经过服务调用来获取数据:少许的数据能够考虑在内存中进行组合
- 大数据读取:使用HTTP POST方法,携带一个位置信息,让服务器返回200,把获取的内容写入到文件中,而后保存在请求的位置上,客户端轮询请求,直到返回201,这样就减小了HTTP的开销
- 数据导出: 使用一个独立的服务,直接访问不一样的微服务使用的数据库,导出到单独的报表系统中;在报表数据库中包含了全部的服务数据结构,而后可使用视图之类的技术来建立一个聚合。
- 事件数据导出:在事件发生时就给报表系统发送数据,而不是周期性的导出,增量导入更高效。 缺点:数据量较大时不容易扩展
- 对数据导出的备份进行处理:可使用Hadoop对数据处理后,储存起来
部署
持续集成(CI)
- 当构建失败以后,把修复CI看成第一优先级要处理的事情
- 集成须要测试,这样才能保证集成代码的正确性,否则只是对语法错误进行检查
- 每一个微服务要有一个专有的CI,包含测试代码
构建流水线和持续交付(CD)
- CD可以检查每次提交是否到达了部署生产环境的要求,并持续的把这些消息反馈给咱们,把每次提交当成候选版本对待
- 在CD中,会把多阶段构建流水线的概念进行扩展,从而覆盖软件经过的全部阶段
-
编译及快速测试
-> 耗时测试
-> 用户验收测试
-> 性能测试
-> 生产环境
测试
单元测试
一般只测试一个函数或者方法,经过TDD写的测试就属于这一类,不启动服务,对外部网络和文件使用也颇有限;面向技术,对功能正常给出快速反馈
服务测试
- 对于包含多个服务的系统,一个服务测试只测试其中一个功能
- 为了达到隔离性,须要为其余服务打桩,MOCK
端到端测试
- 会覆盖整个系统,一般须要打开一个浏览器来操做图形界面。
- 测试类型的比例:应该是不一样数量级的
- 随着测试的范围扩大,遇到的可能状况也越多,发现脆弱测试时,应该不遗余力去解决,避免异常正常化(对事情出错变得习觉得常);当不能当即修复的时候,从测试套件中移除。
- 不要轻易删掉测试代码,除非你理解风险
- 测试场景,而不是故事:测试的重点放在核心的场景中,其余场景在服务测试中进行。
CDC
消费者驱动测试:定义消费者的指望,服务端没有达到预期将没法部署,有助于不一样团队一块儿来编写代码
部署后在测试:
- 部署以前的测试不能保证零缺陷,部署只是在正式环境启动,不表明引入正常流量。
- 蓝绿部署 -> 冒烟测试 -> 切换流量
- 金丝雀发布:少许流量引入新部署的服务中,而后不断的调节流量来验证咱们的功能性和非功能性。进行计分而后确认彻底切换,简单的作法Nginx分流,复杂的复制生产环境请求
- 性能测试:原来的单次调用可能会变成屡次调用,以及跨数据库,会影响到整个微服务调用链,因此比单块系统更加剧要
微服务规模化的挑战:
了解真正的需求:响应时间、延迟、可用性、数据持久性的 权衡
功能降级:当出现问题的其余处理方式
- 程序使用HTTP链接池来处理下游连接:若是某个下游请求故障,可是HTTP设置了超时时间,就会致使大量的请求堆积,全部的worker都在等待超时,阻止创建新的HTTP请求,致使系统大范围不可用
- 在分布式系统中,延迟是致命的
解决方案:
- 正确的设置超时时间
- 实现资源隔离,使用不一样的链接池
- 实现一个断路器,快速失败
断路器
对下游资源请求失败的次数到达必定数量,断路器打开,全部请求快速失败,一段时间发送请求成功,将会重置断路器
资源隔离
分配不一样的资源,当某个部分资源耗尽不影响其余的组件
幂等
确保部分操做幂等安全性,Nginx的重试不包括POST请求。
扩展
- 帮助处理失败,额外的程序保证正常运行
- 性能扩展,减小延迟增长负载
强大的主机
称之为垂直扩展,若是软件没有充分利用也是白搭
分散风险
不要把全部的微服务放在一个地方
负载均衡:SSL终止
经过HTTPS链接到负载均衡器后(Nginx),转到http server ,变成HTTP链接,提升性能。HTTP链接在局域网中,全部外部请求经过一个路由访问内部
扩展数据库
- 扩展读操做:经过多个副本扩展,通常有一致性问题,确保能够接受
- 扩展写操做:对数据进行哈希,基于哈希分配到一个数据库中,缺点:查询复杂(mongo map/reduce),扩展困难
- 每一个微服务一个单独的数据库实例,避免一个数据库实例分配多个数据库
缓存
代理服务器缓存,介于客户端和服务端之间; 客户端缓存以及服务端缓存,通常都是三者混用
HTTP缓存:
- 对客户端的响应使用 cache-control指令:告诉是否缓存以及时限
- 设置Expire头部,指定一个日期,该日期以后失效
- Etag用来标志资源是否匹配,有一种请求方式叫作条件GET
缓存失效
后台异步生成缓存,接受部分实时请求(服务端可能负载),其余请求快速失败,异步生成缓存
自动伸缩
不一样的流量对服务进行自动伸缩。
CAP: consistency / availability / partition tolerance
通常是AP: 分区可用,最终一致性; CP:一致性 ,可是拒绝新请求