说说唯一ID与CAS|得物技术
目录
一、从数据的唯一标识开讲
1. 数据区分与标识表现
2. 标识的生成与组织
3. 标识唯一性保证与核验
二、唯一索引到分布式锁
1. 唯一索引的业务契合度
2. 前置校验的方式选择
3. 分布式锁的必要性
三、锁的共性问题
1. 分布式锁的正确性保障
2. 数据库主键唯一性保障
3. 进程内协同之一:互斥
4. 分布式锁与进程内锁的共性
5. 唯一标识与CAS的联系
四、同类场景延伸
1. 分布式ID生成原理
2. 接口幂等与MQ消费幂等
3. 操作系统进程间通信与互斥
4. CAS原理的其他应用场景
五、总结
1. 唯一标识与CAS与多对一模型
2. 学会抽象归纳
一
从数据的唯一标识开讲
数据区分与标识表现
在编程语言中,它表现为变量名称、常量名称等; 在文件系统中,它表现为目录以及目录下的文件名等; 在数据库表中,它表现为库名、表名、主键或唯一索引; 在网络通信中,它表现为IP地址、MAC地址等; 在计算机内存中,它表现为物理内存地址等。
标识的生成与组织
全随机(可读性差,文字组合其实也可以当成一种全随机的特殊情况) 例如UUID(结构示例如下:6B29FC40-CA47-1067-B31D-00DD010662DA) 该方式经常用于小范围数据区分 该方式通常不会单独使用,一般会结合树形结构来实现一些目录区分 顺序递增的数值结构 该方式形式简单,索引方便 例如:数据库的自增主键、计算机内存的物理地址、Excel表的序号 树形结构区分 组织方式方便直观,便于索引 例如:文件目录结构、关系型数据库数据组织方式、编程语言的多层结构体 分布式ID生成方式 例如雪花算法,该算法的分段标记其实有点树形结构的结合,但其增长方式又有数值的使用便利 其他:以上方式的结合 很多其他的唯一性区分通常都表现成以上方式的结合 例如:URL URL的域名以及Path,本身就存在树形结构的引子(虽然其本身指向的资源存储不一定是该方式); Path中的每一小段,都是区分性命名,世界范围内看,都是随机和不确定的。
全部随机形式以及递增数值
树形结构区分的目录
标识唯一性保证与核验
文件系统命名冲突
数据库唯一索引冲突
编程语言变量重复命名
同一个文件夹下如果来了重名文件,是要选择丢弃操作还是进行文件覆盖? 唯一ID重复导致数据写入失败,是要丢弃数据还是通过其他方法来补偿?
切换离散性更高的的唯一字符串生成方式 这个可以通过UUID算法来实现 增加唯一性校验 理论上在UUID算法下,几乎不会出现重复,但在防御性编程的考量下,我们依然引入校验做双重保障。
二
唯一索引到分布式锁
唯一索引的业务契合度
数据字符较长,同时又不作为索引字段。 应用数据软删除的要求,系统中可能存在某个唯一字段的多条数据。
前置校验的方式选择
这一步一定要查询数据库才能确认是否存在重复吗? 你确定自己要查询的这个字段是索引字段吗?如果不是,查询性能太差,要怎么办? 你查询数据库就一定能保证不重复吗?高并发下检测存在并发与时差怎么处理?
是否必须查询数据库验证
这样的标识自带时间区分度,我们只要在这个前缀的时间粒度上保证唯一,那么就可以确认整个数据在整个系统的唯一性。 随机数据的生成,通常习惯于用系统时间作为种子值,所以高并发下的冲突不能依赖前缀来解决。 时间的颗粒度按照数据的量来确认,通常需要自行平衡其同级以及下级的数据量。
非索引字段怎么验证处理
高并发下如何保证正确性
分布式锁的必要性
三
锁的共性问题
分布式锁的正确性保障
加锁成功之后,后续对该锁的所有操作,能否确认该锁是当前线程持有 当前线程释放锁是否有可能释放了不是本线程持有的锁? 我加了锁,锁的时间不够我业务执行,后面我再操作锁,这个锁还是本线程的锁吗? 加锁的时间问题 当前线程持有的锁时间内,任务还没完成锁就过期了该怎么办? 当前线程持有的锁还有很长时间才自动释放,但是任务已经结束,并发上不来阻塞了怎么办? 锁的释放问题 你确定当前线程加的锁在程序正确执行或者异常之后都释放了吗? 你如何知道Redis或者Zookeeper给你保证的锁是唯一的呢? Redis的单线程模型保证锁操作的正确性,如果你的Redis是主从版或者集群版又正好节点异常切换了呢? Zookeeper的强CP原则,牺牲了A(可用性),但是也可能存在宕机等问题,如何处理?
获取锁的时候给锁value设置随机唯一标识,该标识可以用来判断接下来的锁持有方; 给锁一个合适的初始有效时长,并在锁即将到期的时候续期; 在程序执行的正常区间和异常区间都要释放锁; 如果有自旋场景,那么一定要给自旋场景一个最大过期时间防止死锁; 对于每一个操作,最好使用lua脚本实现操作的原子性; 针对集群版或者主从版本的Redis,需要根据业务权衡是否需要采用redlock或者单节点的Redis来作为锁载体。 redlock算法较重而且极端情况下也可能存在问题; 单节点显然存在单点问题,但是肯定可以保证强一致性。
Zookeeper基于其临时有序节点的“申请-未获取-监听-申请-获取-操作-释放”的循环来实现分布式锁; 因为临时节点的释放是基于会话级别的,所以在会话关闭或者异常中断的情况下,也都会自动释放,所以避免了死锁的问题,心跳的存在我们也不用手动续期。
数据库主键唯一性保障
内部计数器:MySQL内部会维护一个计数器,用来记录下一个可用的自增值。这个计数器通常保存在系统表中,跟踪每个表,以及每个表的AUTO_INCREMENT列的当前值。 锁机制:在插入新记录时,MySQL会使用锁机制来确保自增值的唯一性。在插入操作之前,会对计数器或相关数据进行锁定,以避免多个客户端同时尝试获取相同的自增值。 事务支持:对于使用事务的情况,MySQL也会确保在事务失败时(例如回滚),已分配的自增值不会被使用,以维护自增值的连续性和唯一性。
进程内协同之一:互斥
原子操作 原子操作是CPU提供的功能,由CPU保证执行的原子性 Go语言互斥锁所依赖的主要原子操作是CAS( Compare and Swap ) 自旋模式与阻塞模式 自旋+CAS 、阻塞/唤醒双模式 协程与锁的多对一状态,锁的自旋等待显得尤为必要 但对于长CPU时间片的操作,自旋等待过程所消耗的资源其实也不低 Go语言通过自旋模式到阻塞模式的切换,来缓解锁竞争激烈场景下的CPU消耗 均衡调度 普通模式:普通模式下,老协程相比新的协程,在正常锁竞争下缺乏优势,可能导致长时间无法获得执行权限 饥饿模式:饥饿模式会使未得到执行的协程获得更高的执行优先级,以摆脱长时间获取不到锁无法执行的困境
分布式锁与进程内锁的共性
使用唯一标识来识别锁的当前归属; 通常使用CAS方式来实现变更操作的原子性; 要考虑获得锁的失败自旋以及时间问题; 根据需要来确定是自旋等待还是失败结束。
唯一标识与CAS的联系
数据唯一性检验可以使用锁来解决 而锁的获取,又依赖一个小范围的唯一数据
四
同类场景延伸
分布式ID生成原理
雪花算法:雪花算法是Twitter开源的分布式ID生成算法,它可以在分布式系统中生成全局唯一的、递增有序的ID。Snowflake算法的ID由时间戳、机器ID和序列号组成。 数据库自增ID:在分布式系统中,可以使用单独的数据库服务器生成自增ID。不同的服务器会有不同的起始值和步长,从而避免冲突。 使用Redis的INCR命令:Redis的INCR命令可以用来原子递增一个key的值,因此可以利用这个特性来生成全局唯一ID。
接口幂等与MQ消费幂等
基于数据库中唯一ID以及某个状态的值做前置判断,符合条件才执行; 使用消息ID作为Redis分布式锁的key来判断当前消息是否消费成功; 使用消息ID入库以及成功后状态变更来判断消息消费是否已经执行过。
操作系统进程间通信与互斥
CAS原理的其他应用场景
无锁数据结构:CAS原理通常与无锁数据结构结合使用,以实现高效的并发数据访问。由于CAS原子操作的特性,可以在不使用锁的情况下对共享数据进行操作,从而提高并发性能。 分布式系统:CAS原理的思想可以应用于分布式系统中的数据同步和一致性问题。在分布式环境中,类似CAS的原子操作除了可以用于实现分布式锁,还可以用来实现分布式事务以及一致性算法,确保全局状态的一致性和可靠性。 数据库事务:在数据库系统中,CAS原理的思想可以用于乐观锁和并发控制。通过比较数据版本或标记位,并进行更新的原子操作,实现数据库事务的并发控制和一致性。 业务流程控制:CAS原理的思想也可以应用于业务系统中的流程控制,例如利用乐观锁的机制来处理并发访问下的业务逻辑一致性问题。(上面提到的幂等实现其实也是该大类下的)
五
总结
唯一标识与CAS与多对一模型
学会抽象归纳
往期回顾
文 / 预子
关注得物技术,每周一、三、五更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
“
扫码添加小助手微信
如有任何疑问,或想要了解更多技术资讯,请添加小助手微信: