服务建模
设计方法
- 根据业务能力划分
- 根据用例划分
- 根据技术能力划分
好服务的标准
松耦合
- 尽可能少地知道外部服务的信息
高内聚
- 相关的行为聚集咋一起
建模原则
bounded context
- 这里的product就是**共享模型**
模块与边界
模块的边界是绝佳的微服务候选者,但是新系统最好是先使用单体系统,过于早的边界划分,如果错了,代价会很大
业务功能
进行建模时,如果只考虑模型而不考虑具体业务功能,则就导致大量贫血的基于CRUD操作的服务
但是需要大量的业务知识,根据业务能力进行拆分的话,适合在初期,到了后期,随着系统体量增长,需要进一步拆分
逐步划分
一开始划分的是一些粗粒度的边界,接下来再对这些粗粒度边界继续划分成较细粒度的边界
业务逻辑设计与组织
组织模式:
- 入站适配器处理客户端请求并调用业务逻辑
- 出站适配器被业务逻辑调用,进而调用其他服务
对于简单的业务逻辑,使用简单的解决方式:事务脚本模式
事务脚本模式只适用于简单的业务逻辑,是一种过程式的代码。当业务逻辑逐渐复杂,则就准备使用领域模型,这种模式是将业务逻辑组织为具有状态和行为的对象模型。
领域驱动设计是构建复杂业务逻辑最后利器,它提供了诸如entity valueobject factory repository service等概念都已经被现在的开发人员广泛采用,但对微服务最重要的概念,还是DDD中的聚合。
聚合模式
业务逻辑必须仔细设计,以保障全局的一致性,为了维护这种一致性,将各个领域模型组织成聚合,每组聚合就是一个边界,聚合外的模型只能通过聚合根来进行交流。
一些规则:
- 只引用聚合根:就是只跟根打交道,这样可以通过仔细设计根的方式保证单一出入口
- 聚合间的引用通过主键:通过主键让持久化与扩展变得简单
- 一个事务中,只能更新或创建一个聚合:这是为了满足微服务场景下分布式事务的问题,此时可以使用Saga
聚合的粒度越细会越有扩展性,但粗一点的聚合也有降低编程工作量的好处,更粗的聚合,一次可以操作更多的模型。
领域事件
领域事件指的是一般是过去的时候模型发生了某些状态变化或者执行了某些操作等。
领域事件由聚合负责发布。作为事件,同样也得满足事务的特征同时被可靠地发布。
事件发布之后,则可以通过消息代理被各方消费。
事件溯源
服务拆分
单体地狱
早期单体架构的好处:
- 应用开发简单
- 易于大规模更改
- 测试部署扩展简单
随着应用的不断丰富,单体暴露出了下列问题:
- 复杂性
- 影响开发效率
- 扩展难
- 错误无法隔离,软件变得不那么可靠
拆分单体
根据改变速度,团队结构,安全需求以及实现技术等对其进行分离
拆分维度
- 压力模型:隔离高频低频并发流量
- 主线支线链路模型:隔离主链路业务与直线业务链路
- [DDD](/软件工程/领域驱动设计.html)
- 用户群体模型:隔离不同类型的用户
- 基于安全边界
- 基于技术异构
停止挖掘
当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务
前后端分离
将单体应用进行前后端分离,有两个好处:
- 使得可以接入微服务
- 界面与业务逻辑可以独立部署
抽出服务
- 对抽取成服务的模块进行优先级排序
- 经常变化的业务逻辑
- 资源消耗大户
- 粗粒度边界
- 抽取模块
- 定义粗粒度接口
- 将调用变为远程调用
与单体协作
单体重构的过程中,微服务肯定需要与单体进行协作,需要定义好一个它们之间的协作方式。
- 集成胶水API
- 进程间通信接口
- 反腐层:建立一个中间层,避免不同领域的概念相互污染
- 维护好数据一致性
- 身份验证与授权机制
拆分服务
那么如何将对应的系统操作拆分为独立的服务?
根据业务能力
组织的业务是做什么。
从业务能力到服务的映射是一个非常主观的判断。围绕业务能力建模的好处在于最终的架构会趋于稳定。
- 根据子域
利用DDD子域的概念来避免不同子领域复用相同术语所带来的混乱。
基于扩展性
将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务
基于可靠性
将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用
基于性能需求
将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务
服务API定义
- 将系统操作分配给服务
- 确定服务所暴露的API
依赖处理
数据库
- 分析数据库表的依赖关系,把不同的表或者不同的数据分到不同的限界上下文里
外键
放弃,改用api调用来实现数据查询
共享数据
- 静态数据
如果要求不苛刻,可以使用配置文件,否则使用一个专门的服务器来管理静态数据
- 动态数据
独立出一个服务,专门来处理
- 共享表
需要重新审视设计,进行分表操作
数据库重构
先分离数据库再分离服务,虽然这样会破坏事务完整性,但是可以保证随时可以回退
事务
分离数据库之后,如何保证事务的安全性?
如果一个事务中的部分操作成功,部分操作失败,该如何?
- 再试一次
- 也就是最终一致性,如果失败了,将其放入队列,稍后重试
- 终止操作
- 发起一个补偿事务,来撤销成功的操作
- 但是如果补偿事务再失败的话,可以引入重试或者人工操作
- 分布式事务
- 也就是两阶段提交,每个事务参与者需要向事务管理器投票,如果所有参与者都同意,则事务管理器告诉所有参与者提交,否则只要有一个不同意,则所有事务参与者都有放弃此次事务
引入这些都会增加系统的复杂性,最好的方式是避免这种跨服务的事务
报表问题
如果分离了数据库,那么如何解决需要所有数据的后台报表应用?
- 服务调用
- SQL接口
- 提供一个批量API
- 指导系统将数据写入到一个共享位置来解决传输问题
- 数据导出
- 由服务主动推送数据到报表服务器
- 事件数据导出
- 当服务发生事件时,服务主动推送这些事件到一个中间件上
拆分单体到服务的难点
- 网络延迟
- 同步通信导致的可用性降低
- 数据一致性问题
- 不同子域对同一业务实体复用造成的上帝类