当下java社区分布式解决方案考察

概述

本片只要针对分布式系统的背景和所需要解决的问题做一个简要总结,以及介绍下当下Java开发社区的分布式系统解决方案。

分布式

引出

对于传统单体服务架构,服务的并发性和容错率严重受到服务所处的硬件条件所限制。而由于当下技术条件和成本限制,往往硬件的提升是非常有限和成本高昂的。此外,单体式服务还存在系统维护性差,代码耦合度高以及容错率低的问题。

分布式系统的提出就是为了解决上述问题。

但引入分布式也将带来更多的新问题急需解决,正所谓事物的发展过程就是新旧矛盾的不断更替的过程。

分布式系统需要解决的问题

  1. 网络问题

    布式要求将业务分发给不同的服务进行处理,而这些服务又分布在不同的计算机上。而计算机间通信必须通过计算机网络进行,网络环境的特点也就限制着分布式系统的特点。选择什么样的物理链路和什么样的传输协议,就意味着多大的吞吐,多久的延时以及是否稳定可靠的通信。

  2. 远程调用问题RPC
    RPC 全称 Remote Procedure Call——远程过程调用。对于传统架构而言,接口的和方法的调用都发生在一个基于冯诺依曼体系的计算机中完成,这些进程或线程间使用着同一块内存,CPU,磁盘和总线。而在分布式下,大量的接口方法分布在不同的计算机之上,所以需要解决诸如参数如何传递,所需的服务接口如何管理识别等等问题。

    Java程序的方法调用直接在方法栈中完成,其入参和出参都直接存在于内存之中,调用时也只需要参数对象的引用,返回的也只是对象的引用。但RPC不行,模块系统A无法直接使用模块B系统目标机的内存。因此,传递的参数必须序列化后通过计算机网络传递到服务端,经过反序列化和才可以使用。另外,本地方法调用实在一个进程内完成的,而RPC则是在不同物理机上的,所以RPC必然时C/S模式的。RPC还涉及其他诸多问题,这里不做过多赘言。

  3. 系统可用性

    一个模块系统提供一类请求的处理,分布式中,为了系统的可靠性,一个模块系统会存在多个实例共同处理这类问题,当由于不可控原因导致某个模块系统挂掉后,整个系统依旧能够处理后续请求。这里也就引出了如何维护一个可用服务列表的问题。例如在spring cloud netflix 中eruka就时为了解决这类问题,实例服务统一在启动时向eruka注册自身的服务并定时发送心跳检测,如果挂掉了,eruka则会根据处理策略将目标机从可用列表中剔除,并通知订阅这些服务的客户端。

    新的问题就是,这些实例都向一个注册中心注册。如果该注册中心也故障了呢?这里就需要将其也集群化,通过例如选举策略推举出新的注册中心。见到这里,我其实认为分布式问题绝不仅仅是个计算机问题,更像是一个组织学和系统论的问题。

  4. 服务的注册与发现

    服务集群的跨度很大、数量很多(数以百计甚至更多),为保障系统的正常运行,必然需要有一个中心化的组件完成对各个服务的整合,即将分散于各处的服务进行汇总,汇总的信息可以是服务器的名称、地址、数量等,并且这些服务器组件还拥有被监听功能等(服务发现)。

  5. 分布式事务

    本地事务主要限制在单个会话内,不涉及多个数据库资源。但是在基于SOA(Service-Oriented Architecture,面向服务架构)的分布式应用环境下,越来越多的应用要求对多个数据库资源,多个服务的访问都能纳入到同一个事务当中,分布式事务应运而生。

  6. 分布式日志,配置中心,处理分发,负载均衡

cap理论

它是由加州大学伯克利分校教授埃里克·布鲁尔(Eric Brewer)在 2000 年首次提出,主要是关于搜索引擎、分布式 Web 缓存权衡的一个猜想,直到 2002 年,麻省理工学院的赛斯·吉尔伯特(Seth Gilbert)和 南希·林奇(Nancy Lynch)才提出和发表对该猜想的正式证明,使之成为分布式计算领域公认的一个定理。

简单来说,一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

  1. 一致性:所有节点在同一时间的数据完全一致.
  2. 可用性:服务在正常时间内一直可用
  3. 分区容错性:系统在遇到某节点或者网络分区故障的时候,仍然能够对外满足可用性或一致性的服务.

————————————————

在分布式系统中分区容错性是一定要满足的,为什么?怎么满足?

当我们的数据项只在一个节点保存时,一旦出现分区,那么其他部分就访问不到这个数据了,这是不能容忍的,所以我们需要把数据分布在多个节点上,那么当出现分区后,这一数据项可能到各个区里,从而提高容错性。但是当我们把数据放在多个节点中后,又会带来数据一致性的问题,为了保证数据一致, 每次写操作就需要等待所有节点执行成功,而这等待又会带来可用性的问题。

因此,CAP理论指导我们在设计分布式系统时要根据实际情况进行权衡和取舍,并在一致性、可用性和分区容错性之间做出适当的平衡。

例如中满足AP的样例Eureka,在任何时间都可用,但是可能有数据不一致的情况。满足CP的样例:zookeeper, 满足数据的最终一致性,但是在leader选举过程中不可用。

BASE理论

BASE理论是一种针对分布式系统的设计原则,它是CAP理论的延伸,特别是在一致性和可用性之间的权衡。

BASE理论包含三个基本要素:

  • 基本可用性(Basically Available)

    指在出现故障或数据损坏的情况下,系统仍能保持核心功能的可用性,并尽可能提供其他功能的可用。

  • 软状态(Soft State)

    意味着系统中的数据可以暂时不一致,即在一定时间内可以存在数据副本之间的不同步状态。

  • 最终一致性(Eventually Consistent)

    确保所有节点上的数据在经过一定时间的同步后最终达到一致状态,这意味着系统不需要保证每个节点上的数据实时一致。

    它不像强一致性那样,需要分区数据保证实时一致,导致系统数据的同步代价过高。也不像弱一致性那样,数据更新后不保证数据一致,导致后续的请求只能访问到老数据。

    当前业界的分布式系统,甚至关系数据库系统的数据,大都是用最终一致性实现的。比如 MySQL 的主从备份,就是在一段时间内通过 binlog 日志和监听线程让从库和主库的数据保持最终一致。
    ————————————————

与CAP理论中的强一致性不同,BASE理论接受分区容忍性(P),但在一致性和可用性之间做出妥协,以适应分布式系统的特性,如网络分区和网络延迟等。BASE理论适用于那些可以容忍暂时数据不一致但追求高可用性的系统,例如搜索。

在大规模的系统中,强一致性往往会导致性能问题,因为在分布式环境下实现强一致性往往会牺牲系统的可用性和性能。相反,BASE理论提供了一种折衷的方案,允许系统在特定时段下保持数据不一致,但最终会达到一致性的状态。


当下java社区分布式系统解决方案

spring cloud

Spring Cloud 被称为构建分布式微服务系统的“全家桶”,它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过 Spring Boot 的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。

Spring Cloud 中包含了 spring-cloud-config、spring-cloud-bus 等近 20 个子项目,提供了服务治理、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列、配置管理等领域的解决方案。

Spring Cloud 本身并不是一个拿来即可用的框架,它是一套微服务规范,共有两代实现。

  • Spring Cloud Netflix 是 Spring Cloud 的第一代实现,主要由 Eureka、Ribbon、Feign、Hystrix 等组件组成。

  • Spring Cloud Alibaba 是 Spring Cloud 的第二代实现,主要由 Nacos、Sentinel、Seata 等组件组成。

    ————————————————

img img

简单说,Spring Cloud Alibaba是阿里开源的一套Sping Cloud规范的实现,配置比 NetFlix 更简单易用。

服务注册与发现中心

如前面所描述过的,分布式系统中存在着大量承担不同功能模块的子系统,如何管理这些子系统及相互关系就是服务注册与发现中心所需要解决的问题。

一个服务注册与发现中心大致包含三个功能

  • Register, 服务启动时候进行注册
  • Query, 查询已注册服务信息
  • Healthy Check,确认服务状态是否健康

服务注册

每个服务启动后,需要到统一的服务注册中心进行注册登记,服务正常终止后,也可以到注册中心移除自身的注册记录。在服务执行过程中,通过不断的发送心跳信息,来通知注册中心,本服务运行正常。注册中心只要超过一定的时间没有收到心跳消息,就可以将这个服务状态判断为异常,进而移除该服务的注册记录。

发现模式

服务发现的发现机制:

  • 服务提供者:服务启动时将服务信息注册到注册中心,服务退出时将注册中心的服务信息删除掉。

  • 服务消费者:从服务注册表获取服务提供者的最新网络位置等服务信息,维护与服务提供者之间的通信。

  • 注册中心:服务提供者和服务消费者之间的一个桥梁。

    ————————————————

客户端发现模式

在客户端模式下,如果要进行微服务调用,首先要进行的是到服务注册中心获取服务列表,然后再根据调用端本地的负载均衡策略,进行服务调用。

优点

  • 负载均衡作为client中一个功能,用自身的算法,从服务提供者列表中选择一个合适服务提供者进行访问,因此client端可以定制化负载均衡算法。优点是服务客户端可以灵活、智能地制定负载均衡策略,包括轮询、加权轮询、一致性哈希等策略。
  • 可以实现点对点的网状通讯,即去中心化的通讯。可以有效避开单点造成的性能瓶颈和可靠性下降等问题。

缺点

  • 当负载均衡算法需要更新时候,很难做到同一时间全部更新,所以就造成新旧算法同时运行
  • 与注册中心紧密耦合。

目前来说,大部分服务发现的实现都采取了客户端模式。

服务端发现模式

在服务端模式下,调用方直接向服务注册中心进行请求,服务注册中心再通过自身负载均衡策略,对微服务进行调用。这个模式下,调用方不需要在自身节点维护服务发现逻辑以及服务注册信息。

优点:

  • 服务消费者不需要关心服务提供者的列表,以及其采取何种负载均衡策略
  • 负载均衡策略的改变,只需要注册中心修改就行,不会出现新老算法同时存在的现象
  • 服务提供者上下线,对于服务消费者来说无感知

缺点:

  • rt增加,因为每次请求都要请求注册中心,尤其返回一个服务提供者
  • 注册中心成为瓶颈,所有的请求都要经过注册中心,如果注册服务过多,服务消费者流量过大,可能会导致注册中心不可用
  • 微服务的一个目标是故障隔离,将整个系统切割为多个服务共同运行,如果某服务无法正常运行,只会影响到整个系统的相关部分功能,其它功能能够正常运行,即去中心化。然而,服务端发现模式实际上是集中式的做法,如果路由器或者负载均衡器无法提供服务,那么将导致整个系统瘫痪。

实现及对比

远程调用及解决方案

总体架构

调用流程

img

一次完整调用流程可简单描述为:

  1. 客户端(Client)通过本地调用的方式调用服务(以接口方式调用);
  2. 客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息进行组装序列化成能够进行网络传输的消息体(将消息体对象序列化为二进制流);
  3. 客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端(通过sockets发送消息);
  4. 服务端存根(Server Stub)收到消息后进行反序列化操作,即解码(将二进制流反序列化为消息对象);
  5. 服务端存根(Server Stub)通过解码结果调用本地的服务进行相关处理;
  6. 服务端(Server)本地服务业务处理;
  7. 服务端(Server)将处理结果返回给服务端存根;
  8. 服务端存根(Server Stub)序列化处理结果(将结果消息对象序列化为二进制流);
  9. 服务端存根(Server Stub)将序列化结果通过网络发送至客户端(通过sockets发送消息);
  10. 客户端存根(Server Stub)接收到消息,进行反序列化解码(将结果二进制流反序列化为消息对象);
    客户端得到最终的结果。

————————————————

服务寻址功能

RPC中所有函数或方法都有自己的一个ID,在所有进程中都唯一。客户端在做远程过程调用时,必须附上这个ID,即客户端会查一下表,找出相应的Call ID,然后传给服务端,服务端也会查表,来确定客户端需要调用的函数,然后执行相应函数的代码。Call ID映射表一般是一个哈希表。

序列化

由于客户端和服务端不在同一个服务器上,涉及不同的进程,不能通过内存传递参数,此时就需要将客户端先将请求参数转成字节流(编码),传递给服务端,服务端再将字节流转为自己可读取格式(解码),这就是序列化和反序列化的过程。反之,服务端返回值也逆向经历序列化和反序列化到客户端。

内容一般包括:目标方法标识,调用参数

Feign

Feign是Spring Cloud提供的一个声明式的伪Http客户端,它使得调用远程服务就像调用本地服务一样。Nacos注册中心很好的兼容了Feign,Feign默认集成了Ribbon,所以在Nacos下使用Fegin默认就实现了负载均衡的效果。

Feign是通过REST API实现的远程调用,基于Http传输协议,服务提供者需要对外暴露Http接口供消费者调用,服务粒度是http接口级的。通过短连接的方式进行通信,不适合高并发的访问。Feign追求的是简洁,少侵入。

Dubbo

Dubbo是阿里巴巴开源的基于Java的高性能RPC分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。Spring-cloud-alibaba-dubbo是基于SpringCloudAlibaba技术栈对dubbo技术的一种封装,目的在于实现基于RPC的服务调用。

Dubbo方式更灵活。Dubbo是通过RPC调用实现的远程调用,支持多传输协议(Dubbo、Rmi、http、redis等等),可以根据业务场景选择最佳的方式,非常灵活。默认的Dubbo协议:利用Netty,TCP传输,单一、异步、长连接,适合数据量小、高并发和服务提供者远远少于消费者的场景。Dubbo通过TCP长连接的方式进行通信,服务粒度是方法级的。

从协议层选择看,Dubbo是配置化的,更加灵活。Dubbo协议更适合小数据高并发场景。

架构

调用关系

1、服务容器负责启动,加载,运行服务提供者。
2、服务提供者在启动时,向注册中心注册自己提供的服务。
3、服务消费者在启动时,向注册中心订阅自己所需的服务。
4、注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5、服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
6、服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

分布式事务及解决方案

分布式事务顾名思义就是要在分布式系统中实现事务,它其实是由多个本地事务组合而成。对于分布式事务而言几乎满足不了 ACID。

2PC(Two-phase commit protocol)

中文名两阶段提交,阶段提交是一种强一致性设计,2PC 引入一个事务协调者的角色来协调管理各参与者(也可称之为各本地资源)的提交和回滚,二阶段分别指的是准备(投票)和提交两个阶段。

假如在第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事务都提交成功之后,返回事务执行成功。

假如在第一阶段有一个参与者返回失败,那么协调者就会向所有参与者发送回滚事务的请求,即分布式事务执行失败。

另外,为解决两阶段提交的问题,又提出了三阶段提交,,TCC,消息事务等概念,这里不作深究。

seata框架

spring-cloud-starter-alibaba-seat

需要额外部署一个seata 服务,具体使用可参见https://blog.csdn.net/Zach1Lavine/article/details/125501231


参考: