CAP理论
分布式锁
分布式一致性
分布式事务
负载均衡
微服务
一 CAP和BASE理论
1.1 CAP理论
分布式领域中存在CAP理论,且该理论已被证明:任何分布式系统只可同时满足两点,无法三者兼顾。很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
C:Consistency,一致性,数据一致更新,所有数据变动都是同步的。读操作总是能读取到之前完成的写操作结果,满足这个条件的系统称为强一致系统,这里的“之前”一般对同一个客户端而言;
A:Availability,可用性,系统具有好的响应性能。系统提供的服务必须处于可用的一个状态,用户的每一个请求都能在有限的时间内返回。在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
P:Partition tolerance,分区容错性。 :以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。 出现网络故障、down机什么的还能用
满足 ca:做成一个节点
一般来讲,容错性是分布式系统必须要满足的,一般架构师根据业务特点在一致性和可用性之间平衡
elasticsesarch和CAP
AP 根据CAP理论在牺牲一致性(C)的,保证了数据的分布(P)和可达(A)。当然数据是有最终一直性的。
Zookeeper和CAP
ZooKeeper设计的本意是保持节点的数据一致,也就是CP。所以,这样 一来,你可能既得不到一个数据一致的(CP)也得不到一个高可用的(AP)的Service发现服务了 2台没法玩了
1.2 BASE理论
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果
BA:Basically Available,基本可用
S:Soft State,软状态,状态可以有一段时间不同步
E:Eventually Consistent,最终一致,最终数据是一致的就可以了,而不是时时保持强一致
二 分布式一致性
- 强一致性
这种一致性级别是最符合用户直觉的,他要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响比较大。
- 弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不具体承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。弱一致性还可以再进行细分:
- 会话一致性:该一致性级别只保证对于写入的值,在同一个客户端会话中可以读到一致的值,但其他会话不能保证。
- 用户一致性:该一致性级别只保证对于写入的值,在同一个用户会话中可以读到一致的值,但其他用户不能保证。
- 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。
三 分布式锁
分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息。在实现的时候要注意的几个关键点:
锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;
同一时刻只能有一个线程获取到锁。
3.1 基于数据库实现分布式锁(主键的唯一性)
利用第三方数据库 主键的唯一性做 锁
在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。 (数据库字段 方法名做唯一性约束)
3.2 基于缓存(Redis等)实现分布式锁
优缺点:可以使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。通过超时时间来控制锁的失效时间并不是十分的靠谱。
设置成功返回1,
3.3 基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:
- 创建一个目录mylock;
- 线程A想获取锁就在mylock目录下创建临时顺序节点;
- 获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
- 线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
- 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
- 优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。
- 缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。但是可靠性比redis高。
四 分布式事务
- 两阶段型: 强一致性。但是实现起来复杂、成本较高,不够灵活,有一个严重问题,不能满足高可用,性能要求不高
- 补偿型: 如果A成功B失败,那么回调A的回滚函数。
- 异步确保型: 放入消息队列。保证最终一致性
- 最大努力通知型: 保障,放入消息队列定时同步回滚
在分布式服务事务设计,按照不同业务设计这一个原则
- 如果一致性要求比较高的操作,下单-库存。补偿型+最大努力通知型
- 如果不是很高。可以异步确保型+最大努力通知型
- 最后还加上对账模式T+1。每天晚上定时比较,一致性
五 分布式思想
- 分片+副本+读写分离 redis mysql
- 分片+副本+去中心化 es
5.1 数据持久化
在分布式系统设计里面,一般恢复数据有两种方式:
- 快照备份(es快照是副本索引文件,redis是rdb二进制文件)
- translog事务日志
Elasticsearch是副本+translog(默认5s,内存到磁盘),redis是 rdb(定时快照)和aof(默认1s,内存到磁盘),es和redis如果不是实时fsync,那服务重启,可能丢死失段时间内的数据
六 微服务
6.1 什么是微服务
微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
- 应用按业务拆分成服务
- 各个服务均可独立部署
- 服务可被多个应用共享
- 服务之间可以通信
6.2 实现方案
6.3 微服务治理(Hystrix、dubbo)
服务限流、熔断、降级、异步RPC是基于SOA(面向服务的架构)的分布式系统中一些常见的基本策略,并且这些策略现在都有成熟的开源框架支持。用好这些策略,对整个系统的容错性、稳定性有很大帮助。
限流:
- 计数器限流:计数器限流只要一定时间内的总请求数超过设定的阀值则进行限流,是一种简单粗暴的总数量限流,而不是平均速率限流。
- 漏桶限流:漏桶可以看做是一个具有固定容量、固定流出速率的队列,漏桶限制的是请求的流出速率。漏桶中装的是请求。漏斗
- 令牌桶限流:令牌桶是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌,填满了就丢弃令牌,请求是否被处理要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求。(安全性高)
熔断
服务熔断一般是某个服务(下游服务)故障引起,处理:直接短路掉,不实际调用,而是直接返回一个mock的值。而不是一直等到此服务超时。
- Closed:熔断器关闭状态,调用失败次数积累,到了阈值(或一定比例)则启动熔断机制;
- Open:熔断器打开状态,此时对下游的调用都内部直接返回错误,不走网络,但设计了一个时钟选项,默认的时钟达到了一定时间(这个时间一般设置成平均故障处理时间,也就是MTTR),到了这个时间,进入半熔断状态;
- Half-Open:半熔断状态,允许定量的服务请求,如果调用都成功(或一定比例)则认为恢复了,关闭熔断器,否则认为还没好,又回到熔断器打开状态;
降级
有了熔断,就得有降级。所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值
6.4 优雅停机
Java的优雅停机通常通过注册JDK的ShutdownHook(钩子)来实现,当系统接收到退出指令后,首先标记系统处于退出状态,不再接收新的消息,然后将积压的消息处理完,最后调用资源回收接口将资源销毁,最后各线程退出执行。
核心思想:
引流 → 挡板 → 等待停机
超过一段时间还有停止完毕,强制KILL -9
- 通知注销服务
- 开启挡板
- 钩子调用销毁业务服务(比如把没有消费的先给消费了)
- 自旋检查请求是否完成
- 超时KILL -9
优雅停机可以解决以下场景:
- KILL PID
- 应用意外自动退出(System.exit(n))
- 使用脚本命令的方式停止应用
优雅停机解决不了以下场景:
- 突然断电
- 机器物理破坏
- KILL-9 PID 或 taskkill /f /pid
七 负载均衡
7.1 负载均衡实现方式
一般互联网设计:
7.1.1 DNS(Domain Name System,域名系统)域名解析
client->dns server-client->web server
缺点:集群调度不可控、应用服务器ip直接暴露、故障不能迅速转移
利用DNS处理域名解析请求的同时进行负载均衡是另一种常用的方案。在DNS服务器中配置多个A记录,如:www.mysite.com IN A 114.100.80.1、www.mysite.com IN A 114.100.80.2。
每次域名解析请求都会根据负载均衡算法计算一个不同的IP地址返回,这样A记录中配置的多个服务器就构成一个集群,并可以实现负载均衡。调度权交给了DNS服务器,DNS服务器也没办法了解每台服务器的负载情况,只不过把所有请求平均分配给后端服务器罢了。
由于DNS服务器会有缓存,该IP仍然会在DNS中保留一段时间,那么就会导致一部分用户无法正常访问网站。(解决方案:写个动态监听程序,动态dns)
7.1.2 反向代理(nginx)
反向代理服务器是一个位于实际服务器之前的服务器,所有向我们网站发来的请求都首先要经过反向代理服务器,服务器根据用户的请求要么直接将结果返回给用户,要么将请求交给后端服务器处理,再返回给用户。
优点:
- 隐藏后端服务器
与HTTP重定向相比,反向代理能够隐藏后端服务器,所有浏览器都不会与后端服务器直接交互,从而能够确保调度者的控制权,提升集群的整体性能。
- 故障转移
与DNS负载均衡相比,反向代理能够更快速地移除故障结点。当监控程序发现某一后端服务器出现故障时,能够及时通知反向代理服务器,并立即将其删除。
- 合理分配任务
我们可以根据服务器的配置设置不同的权重,权重的不同会导致被调度者选中的概率的不同。
- 静态页面和常用的动态页面的缓存
缺点:调度者压力过大
7.2 负载均衡算法
7.2.1 轮询算法(加权)
轮询很容易实现,将请求按顺序轮流分配到后台服务器上,均衡的对待每一台服务器,而不关心服务器实际的连接数和当前的系统负载。按公约后的权重设置轮循比率
7.2.2 随机算法(加权)
Random随机,按权重设置随机概率。在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
7.2.3 哈希算法(一致性hash)
- 一致性哈希
将object1、object2、object3、object4四个对象通过特定的Hash函数计算出对应的key值,然后散列到Hash环上,对象的hash值就能快速的定位到对应的机器中。
如果node3机器挂了或新增:过按顺时针迁移的规则,那么object2被迁移到了NODE4中。
- 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
- 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
- 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
- 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
- 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
- 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
保证平衡性:加入虚拟节点
- ip哈希:设置一个hash算法
.7.2.4 最少连接算法(least connection)