分布式系统
...大约 6 分钟
分布式系统
特点
可扩展性
- 通过对服务、储存的扩展,来提高系统的处理能力
- 通过对多台服务器协同工作,来完成单台服务器无法处理的任务,尤其是高并发或者大数据量的任务
不出现单点故障
- 单点故障:在系统中某个组件一旦失效,会让整个系统无法工作
无状态:无状态的服务才能满足部分机器宕机不影响全部,可以随时进行扩展的需求
CAP 理论
- C(consistency)一致性:所有节点同时看到相同的数据
- A(availability)可用性:任何时候,读写都是成功的
- P(partition tolerance)分区容忍性:当部分节点出现消息丢失或分区故障的时候,分布式系统仍能够继续进行
Base 理论
- 基本可用(basically available):系统能够基本运行,一直提供服务
- 软状态(soft-state):允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延时
- 最终一致性(eventually consistent):在一个时间期限之后达到各个节点的一致性,达到数据的最终一致性
不同数据一致性模型
强一致性:当更新操作完成后,任何多个后续进程的访问都会返回最新的更新过的值
弱一致性:系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到,用户读到更新的数据需要一短时间,即“不一致性窗口”
最终一致性:所有数据的副本,在经过一段时间的同步之后,最终都能达到一个一致的状态
Paxos算法
ZooKeeper如何保证数据一致性
Zab 一致
分布式锁
- 基于关系型数据库实现的分布式锁,是依赖数据库的唯一性来实现资源锁定,比如主键和唯一索引等。以唯一索引为例,创建一张锁表,定义方法或资源名、失效时间等字段,同时针对加锁的信息添加唯一索引,比如方法名,当要锁住某个方法或资源时,就在该表中插入对应方法的一条记录,插入成功表示获取了锁,想要释放锁的时候就删除这条记录
- 优点:实现简单,好理解
- 缺点
- 存在单点故障风险:该方法强依赖数据库的可用性,一旦数据库挂掉,则会导致业务系统不可用,解决方案:配置数据库主从机器,防止单点故障
- 超时无法失效:一旦解锁操作失败,会导致锁记录一直在数据库中,其他线程无法再获取锁,解决方案:添加独立的定时任务,通过时间戳对比等方式,删除超时数据
- 不可重入:同一个线程在没有释放锁之前无法再次获得该锁,解决方法:需要改加锁方法,额外储存和判断线程信息,不再阻塞获得锁的县城再次请求加锁
- 对阻塞操作不友好:其他线程关在请求对应方法时,插入数据失败会直接返回,不会阻塞线程,解决方法:不断重试insert操作,直到数据插入成功
- 应用Redis缓存实现分布式锁,缓存性能更好,各种缓存组件提供了多种集群方案们可以解决单点问题,使用setnx和expire实现加锁,setnx:[set if not exists] 如果不存在,则SET
- 当一个线程执行setnx返回1,说明key不存在,该线程获得锁
- 当一个线程执行setnx返回0,说明key存在,该线程获得锁失败
分布式锁需要具有哪些特点
- 互斥性:同一时刻只能有一个线程持有锁,执行临界操作
- 超时释放:通过超时释放,防止不必要的县城等待和资源浪费
- 可重入性:同一个节点上的同一个线程吐过获取了锁之后,再次请求还是可以成功
- 高性能和高可用:加锁和解锁的开销要尽可能的小,同时也需要保证高可用,防止分布式锁失效
- 支持阻塞和非阻塞性:在获取锁时通过轮询和while(true)来实现阻塞操作
使用setnx实现分布式锁
以该锁为key,这是一个随机值
- 如果setnx返回1,说明key不存在,该进程获得锁
- 如果setnx返回0,说明其他进程已经获得了锁,进程不能进入临界区
使用setnx和expire实现
redis在设置一个key时,支持设置过期时间,可以在缓存中实现锁的超时释放,解决死锁问题。但是在redis中,setnx和expire这两条命令不具备原子性,如果一个进程执行完setnx之后崩溃了,导致锁没有设置过期时间,那么这个锁就会一直存在,这个锁就不会被其他进程获取
使用set扩展命令实现
SET key value expireTime nx
nx表示仅在键不存在时设置,可以在同意之间内完成设置值和设置过期时间这两个操作,防止设置过期时间异常导致的死锁
可能导致:在加锁和释放锁之间的业务逻辑执行的太长,以至于超出了锁的超时限制,缓存将对应key删除,其他线程可以获取锁,出现对加锁资源的并发操作
解决方法
- 首先,业务上超时的可能性较小
- 其次,在获取锁的时候,可以设置value为一个随机数,在释放锁时进行读取和对比,确保释放的是当前线程持有的锁,一般是通过redis结合lua脚本的方案实现
- 最后,需要添加完备的日志,记录上下游数据链路,当出现超时,则需要检查对应的问题数据,并且进行人工修复
分布式锁的高可用
redis集群,集群环境下,redis通过主从复制来实现数据同步
可能导致:在故障转移过程中丧失锁的安全性,锁没有被转移到从节点上
解决方案:redlock算法,假设起群友5个节点
- 客户端记录当前系统时间,以毫秒为单位
- 一次尝试从5个redis实例中,使用相同的key获取锁,当想redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,超时时间应该小于锁的失效时间,避免因为网络故障出现的问题
- 客户端使用当前时间减去开始获取锁时间就得到了获取锁使用的时间,当且仅当从半数以上的redis节点获取到锁,并且当使用的时间小于锁失效时间时,锁才算获取成功
- 如果获取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间,减少超时几率
- 如果获取锁失败,客户端应该在所有的redis实例上进行解锁,防止因为服务端响应消失丢失,但是实际数据添加成功导致的不一致