软件架构阅读笔记15

3.每一个服务对接的底层数据表是独立的没有交叉关联的,也就是数据结构是不直接对外的,需要使用其他服务的数据一定通过访问接口进行。好处也就是面向对象设计中封装的好处:

  • 可以很方便地重构底层的数据结构甚至是数据源,只要接口不变,外部不会感知到。

  • 性能有问题的情况下需要加缓存、分表、拆库、归档是比较方便的事情,毕竟数据源没有外部依赖。

说白了就是我的数据我做主,我想怎么搞外面管不着,在重构或是做一些高层次技术架构(比如异地多活)的时候,没有底层数据被依赖,这太重要了。当然,坏处或是麻烦的地方就是跨服务的调用使得数据操作无法在一个数据库事务中完成,这并不是什么大问题,一是因为我们这种拆分方式并不会让粒度太细,大部分的业务逻辑是在一个业务服务里完成的,二是后面会提到跨服务的调用不管是通过MQ进行的还是直接调用进行的,都会有补偿来实现最终一致性。

4.考虑到跨机器跨进程调用服务稳定性方面的显著差异。在方法内部进行方法调用,我们需要考虑调用出现异常的情况,但是几乎不需要考虑超时的情况,几乎不需要考虑请求丢失的情况,几乎不需要考虑重复调用的情况,对于远程服务调用,这些点都需要去重点考虑,否则系统整体就是基本可用,测试环境不出问题,但是到了线上问题百出的状态。这就要求对于每一个服务的提供和调用多问几个上面的问题,细细考虑到因为网络问题方法没有执行多次执行或部分执行的情况:

  • 我们在对外提供服务的时候,不但要告知用户服务提供的业务能力,还要告知用户服务的特性,比如是否是幂等的(对于订单类型的操作服务,相同的订单相同的操作强烈建议是幂等的,这样调用方可以放心进行重试或补偿);是否需要外部进行补偿(在这里你可能说为什么需要外部进行补偿,服务就不能自己补偿吗,对于内部的子逻辑服务当然可以自己补偿,但是有的时候因为网络原因请求就没有到服务端,服务端一无所知这个调用当然无从去补偿);是否有频控的限制;是否有权限的限制;降级后的处理方式等等。

  • 反过来,我们调用其它服务也需要多问几句目标服务的特性,针对性进行设计相应的补偿逻辑、一致性处理逻辑和降级逻辑。我们必须考虑到有些时候并不是服务端的问题,而是请求根本没有到达服务端。

  • 服务本身往往也会有复杂的逻辑,作为客户端的身份调用大量外部的服务,所以服务端和客户端的角色不是固定不变的,当我们的服务内部有许多客户端来调用服务端的时候,对于每一个子逻辑我们都需要仔细考虑每一个环节。否正会出现的情况就是,这个服务是部分逻辑幂等的或是部分逻辑是具备最终一致性的。

如果你说,这么多服务,我在实现的时候很难考虑到这些点,我完全不去考虑分布式事务、幂等性、补偿(毫不夸张地说,有的时候我们花了20%的时间实现了业务逻辑,然后花80%的时间在实现这些可靠性方面的外围逻辑),行不行?也不是不可以,那么业务在线上跑的时候一定会是千疮百孔的,如果整个业务的处理对可靠性方面的要求不高或是业务不面向用户不会受到投诉的话,这部分业务的是可以暂时不考虑这些点,但是诸如订单业务这种核心的不允许有不一致性的业务还是需要全面考虑这些点的。

5. 考虑到跨机器跨进程调用服务数据传输方面的显著差异。对于本地的方法调用,如果参数和返回值传的是对象,那么对于大部分的语言来说,传的是指针(或指针的拷贝),指针指向的是堆中分配的对象,对象在数据传输上的成本几乎忽略不计,也没有序列化和反序列化的开销。对于跨进程的服务调用,这个成本往往不能忽略不计。如果我们需要返回很多数据,往往接口的定义需要进行特殊的改造:

  • 通过使用分页的形式,一次返回固定的少量数据,客户端按需拉取更多数据。

  • 可以在参数中传类似于EnumSet的数据结构,让客户端告知服务端我需要什么层次的数据,比如GetUserInfo接口可以提供给客户端BasicInfo、VIPInfo、InvestData、RechargeData、WithdrawData,客户端可以按需从服务端拿BasicInfo|VipInfo。

6. 这里还引申出方法粒度的问题,比如我们可以定义GetUserInfo通过传入不用的参数来返回不同的数据组合,也可以分别定义GetUserBasicInfo、GetUserVIPInfo、GetUserInvestData等等细粒度的接口,接口的粒度定义取决于使用者会怎么来使用数据,更趋向于一次使用单种类型数据还是复合类型的数据等等。

7. 然后我们需要考虑接口升级的问题,接口的改动最好是兼容之前的接口,如果接口需要淘汰下线,需要先确保调用方改造到了新接口,确保调用方流量为0观察一段时间后方能从代码下线老接口。一旦服务公开出去,要进行接口定义调整甚至下线往往就没有这么容易了,不是自己说了算了。所以对外API的设计需要慎重点。

8. 最后不得不说,在整个公司都搞起了微服务后,跨部门的一些服务调用在商定API的时候难免会有一些扯皮的现象发生,到底是我传给你呢还是你自己来拉,这个数据对我没用为什么要在我这里留一下呢?抛开非技术层面的事情不说,这些扯皮也是有一些技术手段来化解的:

  • 明确服务职责,也就明确了服务应该感知到什么不应该感知到什么。

  • 跨部门的服务交互的接口定义可以定的很轻,采用只有一个订单号的接口或MQ通知+数据回拉的策略(谁数据多谁提供数据接口,不用把数据一次性推给下游)。

  • 数据提供方可以构建一套通用数据接口,这样可以满足多个部门的需求,无需做定制化的处理。甚至在接口上可以提供落地和不落地两种性质的透传。

你可能看到这里觉得很头晕,为什么微服务需要额外考虑这么多东西,实现的复杂度一下子上升了。我想说的是我们需要换一个角度来考虑这个事情:

1. 我们不需要在一开始的时候对所有逻辑都进行严密的考虑,先覆盖核心流程核心逻辑。因为跨服务成为了服务的提供方和使用方,相当于除了我自己,还有很多其它人会来关系我的服务能力,大家会提出各种问题,这对设计一个可靠的方法是有好处的。

2. 即使在不跨服务调用的时候我们把所有逻辑堆积在一起,也不意味着这些逻辑一定是事务性的,实现严密的,跨服务调用往往是一定程度放大了问题产生的可能性。

3. 我们还有服务框架呢,服务框架往往会在监控跟踪层次和运维系统结合在一起提供很多一体化的功能,这将封闭在内部的方法逻辑打散暴露出来,对于有一个完善的监控平台的微服务系统,在排查问题的时候你往往会感叹这是一个远程服务调用就好了。

4. 最大的红利还是之前说的,当我们以清晰的业务逻辑形成了一个立体化的服务体系之后,任何需求可以解剖为很少量的代码修改和一些组合的服务调用,而且你知道我这么做是不会有任何问题的,因为底层的服务ABCDEFG都是经过历史考验的,这种爽快感体验过一次就会大呼过瘾。

但是,如果服务粒度划分的不合理,层次划分的不合理,底层数据源有交叉,没考虑到网络调用失败,没考虑到数据量,接口定义不合理,版本升级过于鲁莽,整个系统会出各种各样的扩展问题性能问题和Bug,这是很头痛的,这也就需要我们有一个完善的服务框架来帮助我们定位各种不合理,在之后说到中间件的文章中会再具体着重介绍服务治理这块。

3.每一个服务对接的底层数据表是独立的没有交叉关联的,也就是数据结构是不直接对外的,需要使用其他服务的数据一定通过访问接口进行。好处也就是面向对象设计中封装的好处:

  • 可以很方便地重构底层的数据结构甚至是数据源,只要接口不变,外部不会感知到。

  • 性能有问题的情况下需要加缓存、分表、拆库、归档是比较方便的事情,毕竟数据源没有外部依赖。

说白了就是我的数据我做主,我想怎么搞外面管不着,在重构或是做一些高层次技术架构(比如异地多活)的时候,没有底层数据被依赖,这太重要了。当然,坏处或是麻烦的地方就是跨服务的调用使得数据操作无法在一个数据库事务中完成,这并不是什么大问题,一是因为我们这种拆分方式并不会让粒度太细,大部分的业务逻辑是在一个业务服务里完成的,二是后面会提到跨服务的调用不管是通过MQ进行的还是直接调用进行的,都会有补偿来实现最终一致性。

4.考虑到跨机器跨进程调用服务稳定性方面的显著差异。在方法内部进行方法调用,我们需要考虑调用出现异常的情况,但是几乎不需要考虑超时的情况,几乎不需要考虑请求丢失的情况,几乎不需要考虑重复调用的情况,对于远程服务调用,这些点都需要去重点考虑,否则系统整体就是基本可用,测试环境不出问题,但是到了线上问题百出的状态。这就要求对于每一个服务的提供和调用多问几个上面的问题,细细考虑到因为网络问题方法没有执行多次执行或部分执行的情况:

  • 我们在对外提供服务的时候,不但要告知用户服务提供的业务能力,还要告知用户服务的特性,比如是否是幂等的(对于订单类型的操作服务,相同的订单相同的操作强烈建议是幂等的,这样调用方可以放心进行重试或补偿);是否需要外部进行补偿(在这里你可能说为什么需要外部进行补偿,服务就不能自己补偿吗,对于内部的子逻辑服务当然可以自己补偿,但是有的时候因为网络原因请求就没有到服务端,服务端一无所知这个调用当然无从去补偿);是否有频控的限制;是否有权限的限制;降级后的处理方式等等。

  • 反过来,我们调用其它服务也需要多问几句目标服务的特性,针对性进行设计相应的补偿逻辑、一致性处理逻辑和降级逻辑。我们必须考虑到有些时候并不是服务端的问题,而是请求根本没有到达服务端。

  • 服务本身往往也会有复杂的逻辑,作为客户端的身份调用大量外部的服务,所以服务端和客户端的角色不是固定不变的,当我们的服务内部有许多客户端来调用服务端的时候,对于每一个子逻辑我们都需要仔细考虑每一个环节。否正会出现的情况就是,这个服务是部分逻辑幂等的或是部分逻辑是具备最终一致性的。

如果你说,这么多服务,我在实现的时候很难考虑到这些点,我完全不去考虑分布式事务、幂等性、补偿(毫不夸张地说,有的时候我们花了20%的时间实现了业务逻辑,然后花80%的时间在实现这些可靠性方面的外围逻辑),行不行?也不是不可以,那么业务在线上跑的时候一定会是千疮百孔的,如果整个业务的处理对可靠性方面的要求不高或是业务不面向用户不会受到投诉的话,这部分业务的是可以暂时不考虑这些点,但是诸如订单业务这种核心的不允许有不一致性的业务还是需要全面考虑这些点的。

5. 考虑到跨机器跨进程调用服务数据传输方面的显著差异。对于本地的方法调用,如果参数和返回值传的是对象,那么对于大部分的语言来说,传的是指针(或指针的拷贝),指针指向的是堆中分配的对象,对象在数据传输上的成本几乎忽略不计,也没有序列化和反序列化的开销。对于跨进程的服务调用,这个成本往往不能忽略不计。如果我们需要返回很多数据,往往接口的定义需要进行特殊的改造:

  • 通过使用分页的形式,一次返回固定的少量数据,客户端按需拉取更多数据。

  • 可以在参数中传类似于EnumSet的数据结构,让客户端告知服务端我需要什么层次的数据,比如GetUserInfo接口可以提供给客户端BasicInfo、VIPInfo、InvestData、RechargeData、WithdrawData,客户端可以按需从服务端拿BasicInfo|VipInfo。

6. 这里还引申出方法粒度的问题,比如我们可以定义GetUserInfo通过传入不用的参数来返回不同的数据组合,也可以分别定义GetUserBasicInfo、GetUserVIPInfo、GetUserInvestData等等细粒度的接口,接口的粒度定义取决于使用者会怎么来使用数据,更趋向于一次使用单种类型数据还是复合类型的数据等等。

7. 然后我们需要考虑接口升级的问题,接口的改动最好是兼容之前的接口,如果接口需要淘汰下线,需要先确保调用方改造到了新接口,确保调用方流量为0观察一段时间后方能从代码下线老接口。一旦服务公开出去,要进行接口定义调整甚至下线往往就没有这么容易了,不是自己说了算了。所以对外API的设计需要慎重点。

8. 最后不得不说,在整个公司都搞起了微服务后,跨部门的一些服务调用在商定API的时候难免会有一些扯皮的现象发生,到底是我传给你呢还是你自己来拉,这个数据对我没用为什么要在我这里留一下呢?抛开非技术层面的事情不说,这些扯皮也是有一些技术手段来化解的:

  • 明确服务职责,也就明确了服务应该感知到什么不应该感知到什么。

  • 跨部门的服务交互的接口定义可以定的很轻,采用只有一个订单号的接口或MQ通知+数据回拉的策略(谁数据多谁提供数据接口,不用把数据一次性推给下游)。

  • 数据提供方可以构建一套通用数据接口,这样可以满足多个部门的需求,无需做定制化的处理。甚至在接口上可以提供落地和不落地两种性质的透传。

你可能看到这里觉得很头晕,为什么微服务需要额外考虑这么多东西,实现的复杂度一下子上升了。我想说的是我们需要换一个角度来考虑这个事情:

1. 我们不需要在一开始的时候对所有逻辑都进行严密的考虑,先覆盖核心流程核心逻辑。因为跨服务成为了服务的提供方和使用方,相当于除了我自己,还有很多其它人会来关系我的服务能力,大家会提出各种问题,这对设计一个可靠的方法是有好处的。

2. 即使在不跨服务调用的时候我们把所有逻辑堆积在一起,也不意味着这些逻辑一定是事务性的,实现严密的,跨服务调用往往是一定程度放大了问题产生的可能性。

3. 我们还有服务框架呢,服务框架往往会在监控跟踪层次和运维系统结合在一起提供很多一体化的功能,这将封闭在内部的方法逻辑打散暴露出来,对于有一个完善的监控平台的微服务系统,在排查问题的时候你往往会感叹这是一个远程服务调用就好了。

4. 最大的红利还是之前说的,当我们以清晰的业务逻辑形成了一个立体化的服务体系之后,任何需求可以解剖为很少量的代码修改和一些组合的服务调用,而且你知道我这么做是不会有任何问题的,因为底层的服务ABCDEFG都是经过历史考验的,这种爽快感体验过一次就会大呼过瘾。

但是,如果服务粒度划分的不合理,层次划分的不合理,底层数据源有交叉,没考虑到网络调用失败,没考虑到数据量,接口定义不合理,版本升级过于鲁莽,整个系统会出各种各样的扩展问题性能问题和Bug,这是很头痛的,这也就需要我们有一个完善的服务框架来帮助我们定位各种不合理,在之后说到中间件的文章中会再具体着重介绍服务治理这块。

猜你喜欢

转载自www.cnblogs.com/z245894546/p/11051824.html