分布式技术

目录

一、微服务

1、概述

2、服务发现

3、负载均衡

4、服务调用容灾

5、微服务网关

6、服务调用跟踪

7、服务动态配置

二、分布式互斥

1、集中式算法

2、分布式算法

3、令牌环算法

4、分布式锁

三、分布式事务

1、BASE理论

2、消息事务+最终一致性

3、TCC

4、二阶段提交

5、三阶段提交

四、可用性

1、一致性哈希

2、分布式选举

3、负载均衡

4、限流

五、分布式存储

六、分布式计算

七、分布式调度


       分布式是个大命题,持续记录对它的理解和梳理其脉络。(持续更新中~)

一、微服务

1、概述

       针对单机服务性能瓶颈的问题,一般有两种解决方案,即对原服务进行水平拆解和垂直拆解。水平拆解指将同一服务copy多份,做并行化改造,各服务之间没有关系。垂直拆解指将服务拆成多个小服务,各自通过网络进行通信,与微内核有异曲同工之处。这两种解法优劣显而易见,水平拆解成本一般更低,并且耦合的业务之间都在单机中通信,效率更高,但依然会出现单一服务过于臃肿,维护成本高的问题。垂直拆解成本更高,但是维护成本相对来说更低,更加适合互联网快速迭代的今天。

       微服务便是垂直拆解的一套方案,不过它并没有一个标准,更多的是一种思想,Martin Fowler则在Microservices(https://www.martinfowler.com/articles/microservices.html)中对这套架构进行了一些总结,这里做简单摘要:

(1)Componentization via Services

       通过服务而不是库来让整个系统组件化,从而让组件的修改给系统的修改带来最小开销。

(2)Organized around Business Capabilities

       对团队的划分,不是根据技术类型来的,而是根据业务来的,很有意思,因为同一业务技术间的沟通是频繁的,不同业务间的沟通是稀疏的,所以根据技术类型来对团队划分会带来比较大的沟通成本。

(3)Products not Projects

       每个小团队都要对自己的产品的整个生命周期负责,而不是以交付为目标。

(4)Smart endpoints and dumb pipes

       微服务架构的其中一个重点是让服务更加内聚与解耦,所以一个服务暴露的接口往往是粗粒度的,适合使用HTTP的RESTful API或轻量级的消息总线做消息传递。

(5)Decentralized Governance

       去中心化管理。

(6)Decentralized Data Management

       去中心化管理数据,每个服务自行管理自己的数据库。

(7)Infrastructure Automation

       CI/CD。

(8)Design for failure

       分布式的一个重头戏:容错,高可用。

(9)Evolutionary Design

       不断迭代优化设计。

 

2、服务发现

       微服务架构中,多个组件需要交互,那么如何知道对方的IP端口呢,直接保存那是不实际的,因为ip和端口都是可能随着服务的升级变化的,所以需要有一种机制获取服务提供者的网络信息,所以有了服务发现组件consul,它需要有以下功能:

(1)服务注册表:记录各个微服务的信息,如名称(不变的),IP,端口号等,并提供查询服务API,注册服务API,注销服务API。

(2)服务注册与发现:在服务启动时,将自己的信息注册到服务发现组件上。在需要与某一服务交互时,从服务发现组件上查询服务API。

(3)服务检查:定时检查下线服务,从服务注册表中移除该实例。

 

3、负载均衡

       微服务架构中,某一个服务可能存在多个实例,所以需要有负载均衡器,将请求合理分摊到各个实例上去。一般的负载均衡算法有轮询,随机等,首先从服务发现组件获取某服务的实例列表,然后根据负载均衡算法请求相应实例。

 

4、服务调用容灾

       服务崩了或其它原因都会导致某个服务的不可用,所以需要一种方式发现某个服务的不可用,并用恰当的手段来处理。一般使用请求响应时间判断服务是否不可用,使用熔断器处理不可用的服务,它统计一段时间内调用失败的次数,如果失败次数过多,则直接返回错误,不浪费CPU时间。

       通常在分布式存储集群中,某个节点不可用了,会通过选举算法选出一个从节点代替主节点,当然这与微服务的场景不太一样,因为微服务的多实例是对等的,大多数分布式存储集群内是分主从节点的,它们并不对等。

 

5、微服务网关

       客户端可能需要调用多个接口才能完成一次业务需求,这就增加了客户端的复杂性,每个服务还都需要独立的认证,未来迭代难以重构。因此,需要有一个门面来封装应用程序的内部结构,客户端只与门面交互即可,这个门面就是微服务网关。

一般来说,微服务网关的核心是一系列的过滤器,用以完成:

(1)身份认证:过滤非法请求

(2)埋点:提供数据统计切面

(3)动态路由:动态地将请求路由到不同后端集群

(4)负载分配

(5)静态响应处理:部分静态资源直接响应,避免转发到内部集群

 

6、服务调用跟踪

       我们写程序时通常会假定网络很健硕,但这是不恰当的,文章The Eight Fallacies of Distributed Computing中也进行了阐述,网络往往很脆弱,网络资源也很有限,所以如果我们能够跟踪每个请求经过了哪些微服务,请求耗时时间,业务逻辑耗时等指标,那么就能更好地分析系统瓶颈,解决系统问题。

 

7、服务动态配置

       配置管理是很麻烦的,并且为了修改一个配置还需要重启服务,代价也是很大的,因此微服务架构中,配置往往统一管理,做到:

(1)集中管理配置

(2)不同环境,不同配置:如测试和生产环境,国内与国外环境,可能都有不同的配置。

(3)运行期间动态调整:不停止微服务,修改配置并自动更新

 

二、分布式互斥

       多服务需要访问同一临界资源的场景很多,需要有效的分布式同步与互斥手段。

1、集中式算法

       有一个*服务器,用于协调各个服务器对临界资源的访问。

       优点:简单,项目早期ROI较高。

       缺点:访问一次临界资源交互次数较多,请求过多后*服务器会成为性能瓶颈,并且要保证*服务器的可用性。

 

2、分布式算法

       每次要访问临界资源,要向所有服务器发送请求,其它所有服务同意时间戳最小的请求能够获得本次使用的机会。Hadoop修改一个文件便是使用的该算法。

       优点:公平,无需一个*服务器,适合p2p场景。

       缺点:沟通成本过高,可用性较差,适合请求频率低,集群规模小的场景。

 

3、令牌环算法

       多个服务进程围成一个环,循环传递一个令牌,持有令牌的服务有资格使用临界资源。

       优点:稳定,可用性高,沟通成本低。

       缺点:一个服务需要使用资源时,即使其它服务不需要使用资源,也必须等待令牌转到自己。

 

4、分布式锁

       先说锁是什么,在一篇文章中这样写到”锁是实现多线程同时访问同一共享资源,保证同一时刻只有一个线程可访问共享资源所做的一种标记。”这个解释妙在可以等同地搬到分布式场景。所以分布式锁一般有三种实现方式:

       (1)基于数据库实现:建立一张锁表,对锁的操作即对表中一条记录的操作。要锁住某个资源时,就在该表中增加一条记录,想要释放锁的时候就删除这条记录。数据库对共享资源做了唯一性约束,如果有多个请求被同时提交到数据库的话,数据库会保证只有一个操作可以成功。

       优点:简单。

       缺点:基于磁盘的数据库IO开销大,只适用于并发量低的场景,并且发生死锁的时候只能人工介入。

 

       (2)基于缓存实现:缓存的数据全部都在内存,因此速度比基于磁盘要快得多。一般可以使用Redis的setnx指令,key为锁id,val为过期时间。返回0表示其它服务已经获得锁,返回1表示当前服务获得锁。

       优点:性能高,可以跨集群部署,可设置超时时间解除死锁。

       缺点:超时时间难以确定,因为一个服务持锁的时间可能很长。

 

三、分布式事务

       关于分布式事务事务,在网上看到一个很好的总结:“分布式事务本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。”

1、BASE理论

       (1)基本可用(Basically Available)

       基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。典型的如服务降级。

       (2)软状态(Soft State)

       软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。

       (3)最终一致性(Eventual Consistency)

       最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

 

2、消息事务+最终一致性

       引入消息中间件做持久化操作,将与其它系统的交互推迟执行。如在淘宝系统中用户成功建立了一个订单,接下来需要仓库系统完成相应操作,如果整个过程都要用户等着那体验是很不好的,如果用户成功建立订单并支付就不需要等待了,体验会好很多,此时可以用消息中间件记录订单状态,用户侧立刻返回成功,然后由消息中间件根据业务与仓库系统交互。

 

3、TCC

       TCC提供了一个编程框架,将整个业务逻辑分为三块:Try、Confirm和Cancel三个操作。以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样。

       我个人理解TCC与二阶段的区别在于,TCC是部分控制,Try、Confirm和Cancel接口均由业务完成,而二阶段是完全控制,全部由事务服务本身保证。

 

4、二阶段提交

       以大多数据库都实现了的XA二阶段提交协议来说,一般包含一或多个RM(Resource Managers,一般为数据库),一个TM(Transaction Manager,协调全局事务的各个事务,mysql中为连接mysql的客户端)。

       第一阶段,TM会向所有用到的RM发送事务操作请求,RM们执行后并不提交,而是告诉TM自己准备好数据可以提交了。

       第二阶段,如果所有RM都返回数据准备ok,则TM会让所有RM提交事务;如果有大于等于一个RM数据准备失败,则TM会让所有RM都进行回滚。

       但二阶段提交是存在问题的:

       (1)第二阶段中,TM发生故障了,则所有RM的临界资源都会被锁定,阻塞其它事务。

       (2)第二阶段中,部分RM节点网络发生了波动,会导致其收不到提交指令,从而一直锁定临界资源。

 

5、三阶段提交

       三阶段提交相比二阶段增加了超时机制,当超过时间没有收到期望的指令,则中断事务,防止前面提到的一直锁定临界资源的问题。另外三阶段提交分为三个步骤:

       第一阶段,TM询问所有RM是否可以进行事务操作。

       第二阶段,若RM都回复可以,说明网络状况还行,TM就向RM们发送执行事务指令,否则发送中断事务指令。

       第三阶段,若RM都回复事务执行成功,则TM就像RM们发送提交事务指令,否则发送中断事务指令,回滚事务。

 

四、可用性

       解决可用性的问题,本质上只有冗余这一条路,如双机热备,多机热备,主从节点,但是除此之外还需要解决四个问题:

       (1)集群新增或移除节点如何可用:一致性哈希;

       (2)主节点不可用后如何挑选从节点:选举算法;

       (3)请求集中在某个节点导致不可用:负载均衡;

       (4)瞬时流量过高导致节点不可用:限流。

1、一致性哈希

       如果使用简单哈希算法,目前有三个节点,则通过key%3只会哈希出三个值:0,1,2。这时如果新加入一个节点,则需要修改为key%4哈希出四个值,这就导致了原来0,1,2节点中的数据都有可能哈希到其它节点中去,整个集群需要大洗牌,导致不可用。

       这个案例中,核心是%3到%4发生了变化,导致集群大洗牌,所以要解决这个问题很简单,就是%一个固定值,比如%2^32,这样不管集群节点数量的改变,key的哈希值是不变的,那如何通过哈希值找到相应节点呢?可以将哈希值从0到2^32看成一个环,同样也对每个节点进行哈希,分布在这个环上,然后每个节点负责自己到下个节点这个区域内的哈希值,如图:

分布式技术

 

       这样,当某个节点不可用了,只是将原本它负责的哈希值变为了另一节点的任务,并没有让整个集群大动干戈。而上面的这种做法,其实就是一致性哈希,不过上面的做法并不是完美的,它无法解决数据倾斜的问题:大部分数据集中在小块区域上,小部分节点处理集群大部分任务,所以一种方法是,每个节点不再哈希一次,而是哈希多次,这样哈希环上就有了更加密集的节点,以解决数据倾斜的问题。

       在Redis中,将每个哈希值认为是一个槽,槽的数量是固定的,每个节点负责一定数量的槽,所以其本质也就是一致性哈希的做法。

 

2、分布式选举

       主节点不可用后,需要从从节点中挑选出一个新的主节点,而挑选的算法一般有以下这些:

       (1)Bully算法

       前提是集群中每个节点均知道其他节点的 ID,然后每次选举都选节点ID最大的节点。

       这种算法优点是简单、快,缺点是每当有ID更大的节点加入集群时,都可能触发选举,选举的频率可能会比较高。目前MongoDB就是用的该算法,采用最后操作的时间戳作为节点ID。

 

       (2)Raft算法

       这个是典型的多数派选举算法,每次选举,拥有超过半数选票的节点成为主节点。一般投票的标准是投第一个收到消息的节点,它的科学性在于最先收到消息,说明和自己和它之间的网络状况最好,它成为主节点最可靠。

       这种算法优点是实现简单,快,稳定,缺点是通信量比较大,每个节点都需要和所有其它节点通信,但是它没有Bully算法的问题,当有新节点加入时,虽然会触发一轮新的选举,但是不一定会切主。

       Raft的一个动画:https://raft.github.io/

 

3、负载均衡

       这个在微服务也有提到,一般的方法就是:

       (1)轮询:顺序地将请求落到不同的节点中;

       (2)加权轮询(Nginx中的做法):按照权重顺序地将请求落到不同的节点中;

       (3)随机

       (4)一致性哈希

 

4、限流

       有时需要限制流量,以防止瞬时大流量导致的服务不可用,常见的限流算法有漏桶算法和令牌桶算法。

       (1)漏桶算法:请求先集中到一个流量固定的漏桶中,然后漏桶以固定流速将流量放出。

       (2)令牌桶算法:有一个装着令牌(token)的桶,它按照qps的速率补充令牌,满了则溢出。当一个请求到来时,它需要先从桶中取出一个令牌,才能继续进行后续操作。所以简单来说,令牌桶算法是基于令牌的补给速率限制qps,对于这个算法我自己也写了一个轻量的小切面,博客地址:https://blog.csdn.net/weixin_42333737/article/details/106130057

 

五、分布式存储

 

六、分布式计算

 

七、分布式调度

 

 

 

参考文章:

https://www.martinfowler.com/articles/microservices.html

《Spring Cloud与Docker微服务架构实战》

《The Eight Fallacies of Distributed Computing》

《分布式技术原理与算法解析》

https://www.cnblogs.com/cxxjohnson/p/9145548.html

https://blog.csdn.net/qq_35642036/article/details/88809234

https://www.jianshu.com/p/735a3d4789fc

https://raft.github.io/

https://www.cnblogs.com/xuwc/p/9123078.html

https://blog.csdn.net/weixin_42333737/article/details/106130057