第十周学习总结

用户头像
熊桂平
关注
发布于: 2020 年 11 月 29 日

1.微服务

1.1微服务框架需求

对于大型互联网系统,除了 Web Service 所规范的服务注册与发现,服务调用等标准功能,还需要微服务框架能够支持:

  • 失效转移(Fail Over): 对于大型网站的微服务而言,即使是很少访问的简单服务,也需要集群部署,同时微服务框架还需要支持服务提供者的失效转移机制,以实现服务高可用。

  • 负载均衡:对于集群部署的服务提供者,服务请求者可以使用加权轮询等手段访问,使服务提供者集群实现负载均衡。

  • 高效的远程通信: 对于大型网站,核心服务每天的调用次数会达到数以亿计,如果没有高效的远程通信手段,服务调用可能会成为整个系统性能的瓶颈。

  • 对应用最少侵入: 网站技术是为业务服务服务的,是否使用微服务需要根据业务发展规划,微服务也需要渐进式的演化,甚至会出现反复,即使用了微服务后又退回到集中式部署,微服务框架需要支持这种渐进式演化和反复。当然服务模块本身需要支持可集中式部署,也可分布式部署。

  • 版本管理:为了应对快速变化的需求,服务版本升级不可避免,如果仅仅是服务实现升级,那么这种升级对服务请求者而言是透明的,无需关注。但是如果服务的访问接口发生变化,就需要服务请求者和服务提供者同时升级才不会导致服务器调用失败。企业应用系统可以申请停机维护,同时升级接口,而网站服务不可能中断,需要服务提供者先升级接口,并同时提供历史版本的服务供请求者调用,当请求者访问接口升级后才可以关闭历史版本服务。(阿里的对升级以后服务维护半年,半年以后就下线。)

1.2Dubbo微服务框架

客户端请求服务端的服务,服务端获取到服务程序以后,逐层按照请求顺序返回;客户端内部组件也是按照顺序逐层返回到服务框架客户端,服务框架客户端最后执行获取到的服务代码。

Dubbo



1.3微服务架构实践

1.3.1微服务架构落地

  • 业务先行,先理顺业务边界和依赖,技术是手段而不是目的。

  • 先有独立的模块,后有分布式的服务。

  • 业务耦合严重,逻辑复杂多变的系统进行微服务重构要谨慎。

  • 要搞清楚实施微服务的目的是什么,业务复用?开发边界清晰?分布式集群提升性能?

1.3.2命令与查询职责隔离(CQRS)

在服务接口层面将查询(读操作)与命令(写操作)隔离,实现服务层的读写分离。

  • 更清晰的领域模型

  • 针对读写分别优化,实现更好的性能

  • 查询服务不会修改数据,更好地保护数据

命令与查询职责隔离

1.3.3事件溯源

将用户请求处理过程中的每次状态变化都记录到事件日志中,并按时间序列进行持久化

存储。

  • 利用事件溯源,可以精确复现任何用户状态,进行复核审计。

  • 利用事件溯源,可以有效监控用户状态变化,并在此基础上实现分布式事务。

1.3.4断路器

当某个服务出现故障,响应延迟或者失败率增加,继续调用这个服务会导致调用者请求阻塞,资源消耗增加,进而出现服务级联失效,这种情况下使用断路器阻断对故障服务的调用。

断路器三种状态:关闭,打开,半开

断路器

1.3.5服务重试及调用超时

上游调用者超时时间要大于下游调用者超时时间之和。

服务重试及调用

1.3.6落地实践指导思想

  • 先有需求,要达到的目标Needs

  • 可以带来的价值Values

  • 要遵循的原则Principles

  • 有哪些最佳实践Practices

  • 实现实践的工具框架Tools

落地实践指导思想



2.领域驱动

  • 系统重构是不可避免的

软件开发是一个过程,这个过程中相关方对软件系统的认知会不断改变,当系统现状和大家的认知有严重冲突的时候,不重构系统难以继续开发下去。

在持续的需求迭代过程中,代码本身会逐渐腐坏,变得僵硬、脆弱、难以维护,需求开发周期越来越长,bug却越来越多。

  • 当微服务架构面临重构的挑战

如何随着业务不断发展,保持微服务的高内聚、低耦合?

2.1领域驱动设计关键术语

  • 领域:一个组织所做的事情以及其包含的一切,通俗地说,就是组织的业务范围和做事方式,也是软件开发的目标范围。

  • 子域:领域是一个组织所做的事情以及其包含的一切。这个范围就太大了,不知道该如何下手。所以通常的做法是把整个领域拆分成多个子域,比如用户、商品、订单、库存、物流、发票等。

  • 限界上下文:在一个子域中,会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文通常和子域具有一对一的关系,用来控制子域的边界,保证子域内的概念统一性。

  • 上下文映射图:不同的限界上下文,也就是不同子域之间会有各种的交互合作。DDD 使用上下文映射图来设计这种关联和交互。

  • 实体:领域模型对象也被称为实体,每个实体都是唯一的,具有一个唯一标识,一个订单对象是一个实体,一个产品对象也是一个实体,订单ID 或者产品ID 是它们的唯一标识。实体可能会发生变化,比如订单的状态会变化,但是它们的唯一标识不会变化。

  • 值对象:并不是领域内的对象都应该被设计为实体,DDD 推荐尽可能将对象设计为值对象。比如像住址这样的对象就是典型的值对象。

  • 聚合:聚合是一个关联领域对象的集合,我们将其作为一个单元来处理数据更改。每个集合都有一个根和一个边界。边界定义了聚合内部的内容。根是聚合中包含的单个特定实体。

2.2用限界上下文识别微服务的功能、边界和依赖关系

2.3DDD典型开发过程与关键产出



3.软件组件设计原则

3.1软件的复杂度和它的规模成指数关系

一个复杂度为100 的软件系统,如果能拆分成两个互不相关、同等规模的子系统,那么每个子系统的复杂度应该是25,而不是50。软件开发这个行业很久之前就形成了一个共识,应该将复杂的软件系统进行拆分,拆成多个更低复杂度的子系统,子系统还可以继续拆分成更小粒度的组件。也就是说,软件需要进行模块化、组件化设计。

3.2组件内聚原则

组件内聚原则主要讨论哪些类应该聚合在同一个组件中,以便组件既能提供相对完整的功能,又不至于太过庞大。

  • 复用发布等同原则

  • 共同封闭原则

  • 共同复用原则

3.2.1复用发布等同原则

复用发布等同原则是说,软件复用的最小粒度应该等同于其发布的最小粒度。也就是说,如果你希望别人以怎样的粒度复用你的软件,你就应该以怎样的粒度发布你的软件。这其实就是组件的定义了,组件是软件复用和发布的最小粒度软件单元。这个粒度既是复用的粒度,也是发布的粒度。

版本号约定建议:

  • 版本号格式:主版本号. 次版本号. 修订号。比如1.3.12,在这个版本号中,主版本号是1,次版本号是3,修订号是12。

  • 主版本号升级,表示组件发生了不向前兼容的重大修订;

  • 次版本号升级,表示组件进行了重要的功能修订或者bug 修复,但是组件是向前兼容的;

  • 修订号升级,表示组件进行了不重要的功能修订或者bug 修复。

3.2.2共同封闭原则

共同封闭原则是说,我们应该将那些会同时修改,并且为了相同目的而修改的类放到同一个组件中。而将不会同时修改,并且不会为了相同目的而修改的类放到不同的组件中。

组件的目的虽然是为了复用,然而开发中常常引发问题的,恰恰在于组件本身的可维护性。如果组件在自己的生命周期中必须经历各种变更,那么最好不要涉及其他组件,相关的变更都在同一个组件中。这样,当变更发生的时候,只需要重新发布这个组件就可以了,而不是一大堆组件都受到牵连。

3.2.3共同复用原则

共同复用原则是说,不要强迫一个组件的用户依赖他们不需要的东西。

这个原则一方面是说,我们应该将互相依赖,共同复用的类放在一个组件中。比如说,一个数据结构容器组件,提供数组、Hash 表等各种数据结构容器,那么对数据结构遍历的类、排序的类也应该放在这个组件中,以使这个组件中的类共同对外提供服务。

另一方面,这个原则也说明,如果不是被共同依赖的类,就不应该放在同一个组件中。如果不被依赖的类发生变更,就会引起组件变更,进而引起使用组件的程序发生变更。这样就会导致组件的使用者产生不必要的困扰,甚至讨厌使用这样的组件,也造成了组件复用的困难。

3.3组件耦合原则

组件内聚原则讨论的是组件应该包含哪些功能和类,而组件耦合原则讨论组件之间的耦合关系应该如何设计。

  • 无循环依赖原则

  • 稳定依赖原则

  • 稳定抽象原则

3.3.1无循环依赖原则

无循环依赖原则说,组件依赖关系中不应该出现环。如果组件A 依赖组件B,组件B 依赖组件C,组件C 又依赖组件A,就形成了循环依赖。

很多时候,循环依赖是在组件的变更过程中逐渐形成的,组件A 版本1.0 依赖组件B 版本1.0,后来组件B 升级到1.1,升级的某个功能依赖组件A 的1.0 版本,于是形成了循环依赖。如果组件设计的边界不清晰,组件开发设计缺乏评审,开发者只关注自己开发的组件,整个项目对组件依赖管理没有统一的规则,很有可能出现循环依赖。

3.3.2稳定依赖原则

稳定依赖原则说,组件依赖关系必须指向更稳定的方向。较少变更的组件是稳定的,也就是说,经常变更的组件是不稳定的。根据稳定依赖原则,不稳定的组件应该依赖稳定的组件,而不是反过来。

反过来说,如果一个组件被更多组件依赖,那么它需要相对是稳定的,因为想要变更一个被很多组件依赖的组件,本身就是一件困难的事。相对应的,如果一个组件依赖了很多的组件,那么它相对也是不稳定的,因为它依赖的任何组件变更,都可能导致自己的变更。

稳定依赖原则通俗地说就是,组件不应该依赖一个比自己还不稳定的组件。

3.3.3稳定抽象原则

稳定抽象原则说,一个组件的抽象化程度应该与其稳定性程度一致。也就是说,一个稳定的组件应该是抽象的,而不稳定的组件应该是具体的。

这个原则对具体开发的指导意义就是:如果你设计的组件是具体的、不稳定的,那么可以为这个组件对外提供服务的类设计一组接口,并把这组接口封装在一个专门的组件中,那么这个组件相对就比较抽象、稳定。

Java 中的JDBC 就是这样一个例子,我们开发应用程序的时候只需要使用JDBC 的接口编程就可以了。而发布应用的时候,我们指定具体的实现组件,可以是MySQL 实现的JDBC 组件,也可以是Oracle 实现的JDBC 组件。

3.4组件的边界与依赖关系,不仅仅是技术问题

组件的边界与依赖关系划分,不仅需要考虑技术问题,也要考虑业务场景问题。易变与稳定,依赖与被依赖,都需要放在业务场景中去考察。有的时候,甚至不只是技术和业务的问题,还需要考虑人的问题,在一个复杂的组织中,组件的依赖与设计需要考虑人的因素,如果组件的功能划分涉及到部门的职责边界,甚至会和公司内的政治关联起来。



用户头像

熊桂平

关注

还未添加个人签名 2020.09.14 加入

还未添加个人简介

评论

发布
暂无评论
第十周学习总结