分布式
2021-09-06 21:46:01 0 举报
AI智能生成
分布式
作者其他创作
大纲/内容
分布式锁
redis 实现
多个客户端在一个redis实例上加锁
加锁机制
客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器
两种方式
LUA脚本
Why Lua?
可以通过封装在lua脚本中发送给redis,保证这段复杂业务逻辑执行的**原子性**。
参数含义
KEYS[1]
这里KEYS[1]代表的是你加锁的那个key,
比如说:RLock lock = redisson.getLock("myLock");这里你自己设置了加锁的那个锁key就是“myLock”。
比如说:RLock lock = redisson.getLock("myLock");这里你自己设置了加锁的那个锁key就是“myLock”。
ARGV[1]
代表的就是锁key的默认生存时间,默认30秒
ARGV[2]
代表的是加锁的客户端的ID,类似于下面这样:8743c9c0-0795-4907-87fd-6c719a6b4586:1
逻辑含义
第一段if
exists myLock
判断一下,如果你要加锁的那个锁key不存在的话,你就进行加锁
hset myLock hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1
加锁
pexpire myLock 30000
设置myLock这个锁key的生存时间是30秒
第二段 if
hexists myLock
判断这个key已经存在
hincrby myLock UUID
对应持有锁的客户端UUID+1, 可重入, 计数器+1
pexpire myLock 30000
设置myLock这个锁key的生存时间是30秒
一个原子操作的命令
set key UUID NX PX TTL
NX:仅在不存在 key 的时候才能被执行成功;
PX:失效时间,传入 30000,就是 30s 后自动释放锁;
UUID:就是随机值,可以是线程号之类的。主要是为了更安全的释放锁,释放锁的时候使用脚本告诉 Redis: 只有 key 存在并且存储的值和我指定的值一样才能删除成功。
TTL: 过期时间
PX:失效时间,传入 30000,就是 30s 后自动释放锁;
UUID:就是随机值,可以是线程号之类的。主要是为了更安全的释放锁,释放锁的时候使用脚本告诉 Redis: 只有 key 存在并且存储的值和我指定的值一样才能删除成功。
TTL: 过期时间
锁互斥机制
如果想要加锁的key已经存在,
判断这个key对应加速的客户端UUID是否和自己相等, 相等可冲入, 并且计数器+1
不等返回这个key生存时间, 然后自旋获取锁
防死锁
假如一个客户端在持有锁的时候崩溃了,没有释放锁,那么别的客户端无法获得锁,则会造成死锁,所以要保证客户端一定会释放锁。
Redis中我们可以设置锁的过期时间来保证不会发生死锁, 即使客户端宕机, 过期时间到了锁自动释放
watch dog自动延期机制
如果加锁的客户端在锁过期后仍然想要持有这把锁, 怎么办?
watch dog
只要客户端1一旦加锁成功,就会启动一个watch dog看门狗
他是一个后台线程,会每隔10秒检查一下, 如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。
可重入加锁机制
LUA脚本的第二段代码, 锁存在, 判断持有锁的客户端UUID是否和自己想等, 相等就计数器+1
释放锁机制(持锁人解锁)
加锁和解锁必须是同一个客户端,客户端A的线程加的锁必须是客户端A的线程来解锁,客户端不能解开别的客户端的锁。
有了UUID的存在, 当锁的计数器=0, 必须设置这把所得客户端才能DEL myLock 命令,
否则存在客户端B发现客户端A过期后, 将A的锁释放
否则存在客户端B发现客户端A过期后, 将A的锁释放
缺点
主从复制状态下写入master, 异步复制给对应的master slave实例, 这个过程中, master宕机,
主从切换, slave变成master但是 master没有获取加锁信息导致其他客户端这个时候请求锁可以获得, 导致了多个客户端对一个分布式锁完成加锁
主从切换, slave变成master但是 master没有获取加锁信息导致其他客户端这个时候请求锁可以获得, 导致了多个客户端对一个分布式锁完成加锁
使用redlock
对集群的每个节点进行加锁,如果大多数(N/2+1)加锁成功了,则认为获取锁成功。
在Redis的分布式环境中,我们假设有N个完全互相独立的Redis节点,在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。
现在假设有5个Redis主节点(大于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程中,客户端会执行以下操作:
1. 获取当前Unix时间,以毫秒为单位
2. 依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁
- 当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间,这样可以避免客户端死等
3. 客户端使用当前时间减去开始获取锁时间就得到获取锁使用的时间。
- 当且仅当从半数以上的Redis节点取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功
4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间,这个很重要
5. 如果因为某些原因,获取锁失败(没有半数以上实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁,
- 无论Redis实例是否加锁成功,因为可能服务端响应消息丢失了但是实际成功了,毕竟多释放一次也不会有问题
在Redis的分布式环境中,我们假设有N个完全互相独立的Redis节点,在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。
现在假设有5个Redis主节点(大于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程中,客户端会执行以下操作:
1. 获取当前Unix时间,以毫秒为单位
2. 依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁
- 当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间,这样可以避免客户端死等
3. 客户端使用当前时间减去开始获取锁时间就得到获取锁使用的时间。
- 当且仅当从半数以上的Redis节点取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功
4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间,这个很重要
5. 如果因为某些原因,获取锁失败(没有半数以上实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁,
- 无论Redis实例是否加锁成功,因为可能服务端响应消息丢失了但是实际成功了,毕竟多释放一次也不会有问题
存在的问题思考
过度依赖时间,
如果某个redis实例上的时钟向前跳跃导致了某个客户端A的锁key提前过期,
就可能导致第二个客户端获得了这个key的锁,
但是第一个获取锁key的客户端A仍然觉得自己获得这个key的锁因为对于redis时钟的过期不知道,
所以这个时候就存在两个客户端A B 都认为自己获得了锁
无法解决客户端阻塞时间超过锁的持续时间
获取锁的客户端在持有锁时可能会暂停一段较长的时间,
尽管锁有一个超时时间,避免了崩溃的客户端可能永远持有锁并且永远不会释放它,但是如果客户端的暂停持续的时间长于锁的到期时间,
并且客户没有意识到它已经到期,那么它可能会继续进行一些不安全的更改,换言之由于客户端阻塞导致的持有的锁到期而不自知。
尽管锁有一个超时时间,避免了崩溃的客户端可能永远持有锁并且永远不会释放它,但是如果客户端的暂停持续的时间长于锁的到期时间,
并且客户没有意识到它已经到期,那么它可能会继续进行一些不安全的更改,换言之由于客户端阻塞导致的持有的锁到期而不自知。
zookeeper 实现
What is zookeeper?
他是个数据库,文件存储系统,并且有监听通知机制(观察者模式)
zk存储了什么?
节点
持久化节点(zk断开节点还在)
持久化顺序编号目录节点
临时目录节点(客户端断开后节点就删除了)
临时目录编号目录节点
持久化顺序编号目录节点
临时目录节点(客户端断开后节点就删除了)
临时目录编号目录节点
节点怎么创建?
create /test laogong // 创建永久节点
create -e /test laogong // 创建临时节点
create -s /test // 创建顺序节点
create -e -s /test // 创建临时顺序节点
zk实现分布式锁的原理
zookeeper是集群
客户端通过访问leader结点实现增删改操作, 读访问从节点也可以
leader是单机的, 通过两阶段提交的方式来产生分布式锁
- 请求过来, 写入leader log, 然后发消息给其他follower, 也把这个消息写到自己的log
- 半数以上通过以后, 再发消息, 提交这个log 半数通过以后, 就可以返回
- 半数以上通过以后, 再发消息, 提交这个log 半数通过以后, 就可以返回
分布式锁是通过, 在zookper 创建一个临时的path node 文件的当做第三方锁的参照物,
- 创建文件成功的客户端成功拿到锁
- 且访问zookeeper的客户端都会注册回调函数, 关注我想创建这个路径的结点的变化
- 如果发生了 Delete Create change都会通过回调函数通知, 就不需要去自旋询问是否上锁
- 顺序性因为增删改都是同过leader, 有的直接连到了leader 有的通过follower结点转发过来, 那么因为leader是单机的, 那么一定存在先后顺序, 就很好解决了
- 且访问zookeeper的客户端都会注册回调函数, 关注我想创建这个路径的结点的变化
- 如果发生了 Delete Create change都会通过回调函数通知, 就不需要去自旋询问是否上锁
- 顺序性因为增删改都是同过leader, 有的直接连到了leader 有的通过follower结点转发过来, 那么因为leader是单机的, 那么一定存在先后顺序, 就很好解决了
zk实现分布式锁的机制
如何避免死锁
创建临时节点
客户端连接一断开,对于的临时结点也就删除了, 所有监听了这个结点的客户端都可以重新获取锁。
所有客户端监听结点的弊端
羊群效应
监听,是所有服务都去监听一个节点的,节点的释放也会通知所有的服务器(服务器数量可能很大)
一个释放的消息,就好像一个牧羊犬进入了羊群,大家都四散而开,随时可能干掉机器,会占用服务资源,网络带宽等等。
解决方法
创建临时顺序结点
全部监听一个节点问题很大,那我们就监听我们的前一个节点,因为是顺序的,很容易找到自己的前后。
每个节点只监听了自己的前一个节点,释放当然也是一个个释放下去,就不会出现羊群效应了。
缺点
Zk性能上可能并没有缓存服务那么高。
因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。
ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。
zk的应用场景
服务注册与订阅(共用节点)
分布式通知(监听znode)
服务命名(znode特性)
数据订阅、发布(watcher)
分布式锁(临时节点)
分布式通知(监听znode)
服务命名(znode特性)
数据订阅、发布(watcher)
分布式锁(临时节点)
应用场景
多客户端访问一个缓存的key(key过期了)
通过分布式锁保证到达DB的客户端只有一个, 减轻了DB的压力, 同时达到DB的客户端会回写缓存, 这样其他客户端唤醒后直接从缓存拿到数据无需再去DB
秒杀,多请求减库存
分布式事务
什么是分布式事务
- 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
- 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,
- 分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
- 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,
- 分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
以订单请求为例
需要请求多个服务
订单中心、用户中心、库存中心, 并且这些服务都不在一个机器上
造成业务间隔离,
每个业务都维护着自己的数据库,数据的交换只能进行RPC调用。
需要对多个服务进行操作
创建订单和扣减库存,需要同时对订单DB和库存DB进行操作
两步操作必须同时成功,否则就会造成业务混乱
分布式事务控制数据一致性
此时我们只能保证自己服务的数据一致性,无法保证调用其他服务的操作是否成功
所以为了保证整个下单流程的数据一致性,就需要分布式事务介入。
分布式事务产生的原因
- service产生多个节点,
- resource产生多个节点。
- resource产生多个节点。
CAP
C (一致性) Consistency:
- 对某个指定的客户端来说,读操作能返回最新的写操作。
- 对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,
- 如果有某个节点没有读取到,那就是分布式不一致。
- 对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,
- 如果有某个节点没有读取到,那就是分布式不一致。
A (可用性) Availability:
- 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
- 可用性的两个关键一个是合理的时间,一个是合理的响应。
- 合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。
- 合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
- 可用性的两个关键一个是合理的时间,一个是合理的响应。
- 合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。
- 合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
P (分区容错性) Partition tolerance:
- 当出现网络分区后,系统能够继续工作。
- 打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
- 打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
BASE
Basically Available(基本可用)
分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
Soft state(软状态)
允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
Eventually consistent (最终一致性)
最终一致是指经过一段时间后,所有节点数据都将会达到一致。
BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态
2PC 3PC TCC
TCC与 2PC、3PC一样,都只是实现分布式事务的一种方案而已。
2PC 两阶段提交
2PC 原理
`两段提交`顾名思义就是要进行两个阶段的提交:第一阶段,准备阶段(投票阶段) ; 第二阶段,提交阶段(执行阶段)。
2PC 过程
Prepare
通知参与者执行事务操作
Commit
收到所有参与者的对于Prepare操作的Ready回复后, 通知参与者可以提交本地事务
下单扣库存案例
- 一个下单流程就会用到多个服务,各个服务都无法保证调用的其他服务的成功与否,
- 这个时候就需要一个全局的角色(`协调者`)对各个服务(`参与者`)进行协调。
- 这个时候就需要一个全局的角色(`协调者`)对各个服务(`参与者`)进行协调。
一个下单请求过来通过`协调者`,给每一个`参与者`发送`Prepare`消息,执行本地数据脚本但不提交事务。
- 如果`协调者`收到了`参与者`的失败消息或者超时,直接给每个`参与者`发送`回滚(Rollback)`消息
- 否则,发送`提交(Commit)`消息;
- 否则,发送`提交(Commit)`消息;
`参与者`根据`协调者`的指令执行提交或者回滚操作,释放所有事务处理过程中被占用的资源,
显然`2PC`做到了所有操作要么全部成功、要么全部失败。
两段提交(2PC)的缺点
网络抖动导致的数据不一致
- 第二阶段中`协调者`向`参与者`发送`commit`命令之后,一旦此时发生网络抖动,
- 导致一部分`参与者`接收到了`commit`请求并执行,可其他未接到`commit`请求的`参与者`无法执行事务提交。
- 进而导致整个分布式系统出现了数据不一致。
- 导致一部分`参与者`接收到了`commit`请求并执行,可其他未接到`commit`请求的`参与者`无法执行事务提交。
- 进而导致整个分布式系统出现了数据不一致。
超时导致的同步阻塞问题
`2PC`中的所有的参与者节点都为`事务阻塞型`,当某一个`参与者`节点出现通信超时,其余`参与者`都会被动阻塞占用资源不能释放。
单点故障的风险
- 由于严重的依赖`协调者`,一旦`协调者`发生故障,而此时`参与者`还都处于锁定资源的状态,无法完成事务`commit`操作。
- 虽然协调者出现故障后,会重新选举一个协调者,可无法解决因前一个`协调者`宕机导致的`参与者`处于阻塞状态的问题。
- 虽然协调者出现故障后,会重新选举一个协调者,可无法解决因前一个`协调者`宕机导致的`参与者`处于阻塞状态的问题。
3PC 三阶段提交
三段提交(3PC)是对两段提交(2PC)的一种升级优化,`3PC`在`2PC`的第一阶段和第二阶段中插入一个准备阶段canCommit。
解决了2PC的问题
超时
在协调者和参与者中都引入超时机制,当`参与者`各种原因未收到`协调者`的commit请求后,会对本地事务进行commit,不会一直阻塞等待
但`3PC` 还是没能从根本上解决数据一致性的问题。
PreCommit
保证了在最后提交阶段之前,各参与者节点的状态都一致。
3PC 过程
CanCommit
协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。
类似于2PC中的第二个阶段中的Ready阶段,是一种事务询问操作(被调用方检查自己是否有能力进行事务操作)
PreCommit
`协调者`向所有`参与者`发送`PreCommit`命令,询问是否可以进行事务的预提交操作
参与者收到后开始执行事务操作,并将Undo和Redo信息记录到事务日志中。
- 参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回`Yes`响应,进入最终commit阶段。
- 一旦参与者中有向协调者发送了`No`响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送`abort`请求,参与者接受abort命令执行事务的中断。
- 一旦参与者中有向协调者发送了`No`响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送`abort`请求,参与者接受abort命令执行事务的中断。
DoCommit
在前两个阶段中所有参与者的响应反馈均是`YES`后,协调者向参与者发送`DoCommit`命令正式提交事务,
如协调者没有接收到参与者对于DoCommit的ACK响应,会向所有参与者发送`abort`请求命令,执行事务的中断。
3PC对于2PC的优化
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制
避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
TCC 补偿事务
`TCC`(Try-Confirm-Cancel)又被称`补偿事务`,`TCC`与`2PC`的思想很相似,事务处理流程也很相似,
但`2PC` 是应用于在DB层面,TCC则可以理解为在应用层面的`2PC`,是需要我们编写业务逻辑来实现。
但`2PC` 是应用于在DB层面,TCC则可以理解为在应用层面的`2PC`,是需要我们编写业务逻辑来实现。
`TCC`它的核心思想是:"针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)"。
TCC 过程
Try
通过Try接口执行对应的事务操作
尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)
Confirm
确认执行业务操作,查看是否执行成功,获得对应的操作的执行情况回复
确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
Cancel
只要涉及到的相关业务中,有一个业务方未成功,则取消所有请求。
取消执行,释放Try阶段预留的业务资源 Cancel操作满足幂等性Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。
TCC的缺点
应用侵入性强
TCC由于基于在业务层面,至使每个操作都需要有 `try`、`confirm`、`cancel`三个接口。
开发难度大
代码开发量很大,要保证数据一致性 `confirm` 和 `cancel` 接口还必须实现幂等性
分布式ID
什么是分布式ID?
数据库中对于一条数据的全局唯一标识
随着数据日渐增长,主从同步也扛不住了,就需要对数据库进行分库分表,
但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;
特别一点的如订单、优惠券也都需要有`唯一ID`做标识。此时一个能够生成`全局唯一ID`的系统是非常必要的。
那么这个`全局唯一ID`就叫`分布式ID`。
但分库分表后需要有一个唯一ID来标识一条数据,数据库的自增ID显然不能满足需求;
特别一点的如订单、优惠券也都需要有`唯一ID`做标识。此时一个能够生成`全局唯一ID`的系统是非常必要的。
那么这个`全局唯一ID`就叫`分布式ID`。
分布式ID需要满足哪些条件
全局唯一
必须保证ID是全局性唯一的,基本要求
高性能
高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈
高可用
100%的可用性是骗人的,但是也要无限接近于100%的可用性
好接入
要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单
趋势递增
最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求
分布式ID都有哪些生成方式?
UUID
生成一个16 字节128位,36位长度的字符串
不推荐
优点
生成足够简单,本地生成无网络消耗,具有唯一性
缺点
无序的字符串,不具备趋势自增特性
没有具体的业务含义
长度过长,存储以及查询对MySQL的性能消耗较大, - 作为数据库主键 `UUID` 的无序性会导致数据位置频繁变动,严重影响性能。
数据库自增ID
基于数据库的`auto_increment`自增ID完全可以充当`分布式ID`,具体实现:需要一个单独的MySQL实例用来生成ID,
当我们需要一个ID的时候,向表中插入一条记录返回`主键ID`
优点
实现简单,ID单调自增,数值类型查询速度快
缺点
DB单点存在宕机风险,无法扛住高并发场景, 访问量激增时MySQL本身就是系统的瓶颈
数据库多主模式
双主模式集群,也就是两个Mysql实例都能单独的生产自增ID。
两个实例设置起始值和自增步长, 如通过奇偶性保证不会重复
扩容
需要人工修改一、二两台`MySQL实例`的起始值和步长,把`第三台机器的ID`起始生成位置设定在比现有`最大自增ID`的位置远一些,
但必须在一、二两台`MySQL实例`ID还没有增长到`第三台MySQL实例`的`起始ID`值的时候,
否则`自增ID`就要出现重复了,必要时可能还需要停机修改
但必须在一、二两台`MySQL实例`ID还没有增长到`第三台MySQL实例`的`起始ID`值的时候,
否则`自增ID`就要出现重复了,必要时可能还需要停机修改
优点
解决DB单点问题
缺点
不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发场景。
号段模式
号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围
等这批号段ID用完,再次向数据库申请新号段,对`max_id`字段做一次`update`操作,`update max_id= max_id + step`,
update成功则说明新号段获取成功,新的号段范围是`(max_id ,max_id +step]`。
update成功则说明新号段获取成功,新的号段范围是`(max_id ,max_id +step]`。
由于多业务端可能同时操作,所以采用版本号`version`乐观锁方式更新,这种`分布式ID`生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。
Redis
利用`redis`的 `incr`命令实现ID的原子性自增。
要考虑到redis持久化的问题。`redis`有两种持久化方式`RDB`和`AOF`
`RDB`会定时打一个快照进行持久化,假如连续自增但`redis`没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
`AOF`会对每条写命令进行持久化,即使`Redis`挂掉也不会出现ID重复,但由于incr命令的特殊性,会导致`Redis`重启恢复的数据时间过长。
雪花算法(SnowFlake)
`Snowflake`生成的是Long类型的ID
ID的组成
第一个bit位(1bit)
- Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
时间戳部分(41bit)
毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;
41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
工作机器id(10bit)
也被叫做`workId`,这个可以灵活配置,机房或者机器号组合都可以。
序列号部分(12bit)
自增值支持同一毫秒内同一个节点可以生成4096个ID
根据这个算法的逻辑,只需要将这个算法用Java语言实现出来,封装为一个工具方法,那么各个业务应用可以直接使用该工具方法来获取分布式ID,只需保证每个业务应用有自己的工作机器id即可,而不需要单独去搭建一个获取分布式ID的应用。
百度 (Uidgenerator)
`uid-generator`是基于`Snowflake`算法实现的
`uid-generator`支持自`定义时间戳`、`工作机器ID`和 `序列号` 等各部分的位数,
`uid-generator`中采用用户自定义`workId`的生成策略。
`uid-generator`需要与数据库配合使用,需要新增一个`WORKER_NODE`表。当应用启动时会向数据库表中去插入一条数据,插入成功后返回的自增ID就是该机器的`workId`数据由host,port组成。
对于`uid-generator` ID组成结构:
`workId`,占用了22个bit位,时间占用了28个bit位,序列化占用了13个bit位,
和原始的`snowflake`不太一样,时间的单位是秒,而不是毫秒,
`workId`也不一样,而且同一应用每次重启就会消费一个`workId`。
美团(Leaf)
`Leaf`同时支持号段模式和`snowflake`算法模式,可以切换使用。
号段模式
snowflake模式
`Leaf`的snowflake模式依赖于`ZooKeeper`
原始snowflake
算法也主要是在workId
的生成上,Leaf
中workId
是基于ZooKeeper
每个应用在使用`Leaf-snowflake`时,启动时都会都在`Zookeeper`中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个`workId
滴滴出品(TinyID)
`Tinyid`是基于号段模式原理实现的与`Leaf`如出一辙,每个服务获取一个号段(1000,2000]、(2000,3000]、(3000,4000]
共识算法(raft)
etcd
redis
sentinel 选举局部领头对监控主服务器进行故障转移
sharding 分片 出现某个主节点下线, 这个主节点的从节点就会向其他负责处理槽的结点发起请求,
当选的从结点获得超过一半结点的选票称为下线主节点负责槽的当前负责结点
当选的从结点获得超过一半结点的选票称为下线主节点负责槽的当前负责结点
zookeeper
一致性哈希
负载均衡算法
分布式限流
计数器法
漏桶法
令牌桶法
漏桶法
令牌桶法
降级
熔断策略
0 条评论
下一页