美文网首页
读书笔记-微服务架构中的进程间通信-02

读书笔记-微服务架构中的进程间通信-02

作者: 愤怒的老照 | 来源:发表于2020-07-23 21:22 被阅读0次

    1 进程间通信

    和单体应用不同的是,单体应用部署在同一台机器,属于同一个进程,各个模块之间通过函数互相调用;但是微服务架构中的服务实例通常是部署在多个机器上的,所以必须使用进程间通信。
    有很多进程间通信技术可供参考。服务可以使用基于同步/异步的通信机制,同时服务通信的消息格式也是不相同的,服务可以使用具有可读性的格式(JSON/XML),也可以使用更高效的二进制格式。

    1.1 交互方式

    有多种客户端和服务的交互方式,可以分两个维度。
    1、一对一和一对多

    • 一对一:每个客户端请求由一个服务实例来处理
    • 一对多:每个客户端请求由多个服务实例来处理
      2、同步和异步
    • 同步:客户端调用服务端,必须等待服务端响应
    • 异步:客户端的请求不会阻塞进程
      各种交互方式可以用两个维度来表示:


      image.png

    一对一:

    • 请求/响应:客户端向服务端发起请求,并等待响应,等待的过程可能造成线程阻塞,并且会造成服务的紧耦合
    • 异步请求/响应:客户端发送请求到服务器,服务器异步响应请求,客户端不会阻塞
    • 单向通知:客户端的请求发送到服务端,不期望服务端作出任何响应

    一对多:
    发布/订阅:客户端发出消息,发送给多个订阅者
    发布/异步响应:客户端发布消息,并等待订阅者的响应。

    1.2 API演化

    API作为客户端和服务端的通信桥梁,不可避免的会随着功能的增加而发生变化。在单体应用中,变更API是相对简单并且不容易出错的事,因为更改之后编译器会对不兼容的调用提示编译错误;但是基于微服务的API更改起来就比较麻烦了,你并不能保证客户端和你的api保持一致,一般有两种处理方法

    • 努力的进行向后兼容的更改,对API附加更强能功能,包括添加可选属性、向响应添加属性等,如果只是进行这些类型的更改,那么老版本的客户端依然可以继续使用更新后的服务,但是客户端和服务端必须同时支持健壮性原则的请求响应内容
    • 另一种是无法对已有的api进行更改,因此服务必须在一段时间内同时支持新旧版本的API,如getV1和getV2,旧版本的API可以在下一个版本提出需求去掉

    1.3 消息格式

    如果使用了HTTP歇息,那么需要选择具体的消息格式(JSON/XML);有些进程间的通信机制已经指定了消息格式(gRPC/Thrift)。我们不应该使用类似Java序列化这类跟变成虚言强相关的消息格式,因为今后可能会扩展到其他的语言。

    2 使用断路器保护系统

    分布式系统中,当服务试图向另一个服务发送请求,随时都会面临着局部故障的风险,因为二者是独立的进程,如下图所示:


    image.png

    此时服务B和服务A将会无限期的阻塞,所以要通过合理的设计服务来防止整个应用程序的故障传导

    • 网络超时:请求链接一定不要做成无限阻塞,而是要设定超时时间,指定时间内没有完成调用变返回超时错误
    • 限流:服务接收请求是有能力上限的,当请求到达上线,让请求立即失败,可以保护服务端
    • 断路器模式:服务端监控请求的成功和失败的数量,如果失败率超过一定比例就启动断路器,让后续的调用都失效。在经过一定的时间客户端应该继续尝试,如果调用成功就解除断路器。
    • 弹性扩容/缩容:当访问量达到N时,增加一台机器,当访问量减少到M时,减少一台机器,可以提高系统处理请求的能力

    3 服务发现机制

    服务发现和服务注册是属于服务治理的概念(治理肯定是有问题了才需要的),你的客户端调用服务端,必须知道服务实例具体的ip地址和端口号,在单体应用中,这些都是固定的,但是在微服务中,因为一个服务可能通过X轴或者Z轴扩展为多个实例,并且服务实例的ip地址会变服务实例集群会动态更改因此微服务必须使用服务发现。

    3.1 服务发现概念

    其关键组件是服务注册表,它是包含服务实例网络位置信息的一个数据库。当服务实例启动或者停止时,服务发现机制会更新服务注册表。(采用服务发现需要给服务起一个为一个名字,以便于调用方可以调用)

    3.2 应用层服务发现模式

    3.2.1 概念

    客户端会通过服务注册表,获取可用的服务列表,然后通过路由策略选择要调用的实例发送请求。这个过程包括两部分:

    • 自注册:服务实例向服务注册表中注册自己的网络位置,并且提供运行状况检查URL以便于服务注册表定期检查服务是否正常(服务还可以通过“续命”的方式,定期调用心跳API防止注册过期)
    • 客户端发现:客户端获取服务实例列表,并通过路由策略在它们之间进行负载均衡。


      image.png

    3.2.2 优点

    可以处理多平台部署的问题

    3.2.3 缺点

    必须为每种编程语言提供API,并且必须由开发人员对服务发现进行开发。

    3.3 平台层服务发现模式(服务治理平台)

    3.3.1 概念

    部署平台包括一个服务注册表,用于跟踪可用服务的IP地址,当由请求时,平台会根据指定的路由策略(同机房优先,同城市优先)在实例之间进行负载均衡,包括两部分:

    • 第三方注册模式:注册统一由第三方负责(治理平台的一部分),而不是服务本身注册自己到服务注册表。
    • 服务端发现模式:客户端不需要查询服务注册表,然后自己选择实例,而是直接向治理平台发送请求(带上服务的标识),平台查询服务注册表后根据指定路由返回可用实例,客户端再访问这个实例。


      image.png

    3.3.2 优点

    客户端和服务端不需要包含服务发现相关的代码

    3.3.3 缺点

    对平台有限制

    4 基于异步消息模式的通信

    4.1 两种类型的消息通道

    • 点对点:服务使用点对点通道来实现一对一交互方式
    • 发布订阅:将一条消息发送给所有订阅的接收方,使用发布-订阅模式实现一对多的交互方式。

    4.2 无代理消息和有代理消息

    image.png

    如上图所示,无代理的架构中服务直接通信,有代理的架构中服务通过代理通信

    4.2.1 无代理消息

    服务可以直接通信,典型的是ZMQ,虽然操作起来很简单,但是客户端和服务端还是强耦合的方式,双方需要了解彼此的位置(也就是要使用服务发现机制),导致服务双方必须同时存在。

    4.2.2 基于代理的消息

    发送方将消息写入消息代理,消息代理将消息发送给接受方(发送方并不需要知道接收方的位置,并且可以使用消息队列进行削峰,例如Kafka,kafka使用topic和消费组的概念实现消息机制),可以理解发送方和接受方是松耦合,因为二者不需要知道彼此的位置,并且可以使用消息队列进行削峰;但是因为引入了消息代理,所以会引来消费队列造成的瓶颈问题。

    4.2.3 kafka

    4.2.3.1 名词解释
    • Broker:Kafka节点,一个节点就是一个Broker,多个节点组成kafka集群
    • topic:消息主题,供消费者或消费组订阅
    • Partition:分片,一个topic可以分为多个分片,从而提高性能
    • Producer:生产者,生产信息
    • Consumer:消费者,订阅topic
    • Consumer Group:消费组,可以包含多个消费者,一个消息只能被一个消费组消费一次。
    4.2.3.2 消费逻辑

    当启动一个消费组去消费一个topic的时候,无论有多少个consumer,这个consumer group一定回去把这个topic下所有的partition都消费了。当consumer group里面的consumer数量小于这个topic下的partition数量的时候,如下图groupA,groupB,就会出现一个conusmer thread消费多个partition的情况,总之是这个topic下的partition都会被消费。如果consumer group里面的consumer数量等于这个topic下的partition数量的时候,如下图groupC,此时效率是最高的,每个partition都有一个consumer thread去消费。当consumer group里面的consumer数量大于这个topic下的partition数量的时候,如下图GroupD,就会有一个consumer thread空闲。因此,我们在设定consumer group的时候,只需要指明里面有几个consumer数量即可,无需指定对应的消费partition序号,consumer会自动进行rebalance。


    image.png
    4.2.3.3 消息有序

    有序从两方面保证:kafka节点不仅要保存数据的时候有序,并且消费者消费也要有序。kafka是怎么保证单个分片有序的?

    • 生产者发送队列时,通过加锁保证发消息顺序(有两个问题,第一个是kafka节点因为网络原因没有及时ack,导致生产者重复发送;另一个是msg1发送失败,msg2发送成功;msg1重新发送导致乱序)。。。为实现Producer的幂等性,Kafka引入了Producer ID(即PID)和Sequence Number。对于每个PID,该Producer发送消息的每个<Topic, Partition>都对应一个单调递增的Sequence Number。同样,Broker端也会为每个<PID, Topic, Partition>维护一个序号,并且每Commit一条消息时将其对应序号递增。对于接收的每条消息,如果其序号比Broker维护的序号)大一,则Broker会接受它,否则将其丢弃:如果消息序号比Broker维护的序号差值比一大,说明中间有数据尚未写入,即乱序,此时Broker拒绝该消息,Producer抛出InvalidSequenceNumber如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息,Producer抛出DuplicateSequenceNumberSender发送失败后会重试,这样可以保证每个消息都被发送到broker。
    • Kafka Consumer端是需要维护这个partition当前消费到哪个message的offset信息的,这个offset信息,high level api是维护在Zookeeper上,low level api是自己的程序维护。(Kafka管理界面上只能显示high level api的consumer部分,因为low level api的partition offset信息是程序自己维护,kafka是不知道的,无法在管理界面上展示 )当使用high level api的时候,先拿message处理,再定时自动commit offset+1(也可以改成手动), 并且kakfa处理message是没有锁操作的。因此如果处理message失败,此时还没有commit offset+1,当consumer thread重启后会重复消费这个message。。。重复消费通常会使逻辑错误,需要在代码里进行幂等处理(通过加锁,然后检查数据库/redis的数据)

    参考:https://www.zhihu.com/question/266390197/answer/772404605

    相关文章

      网友评论

          本文标题:读书笔记-微服务架构中的进程间通信-02

          本文链接:https://www.haomeiwen.com/subject/wlqklktx.html