Hello Redis,我有7个问题想请教你( 三 )


 
②Cmpxchg
可以发现追查到最终 CAS,“比较并修改”,本来是两个语意,但是最终确实一条 CPU 指令 Cmpxchg 完成 。
Cmpxchg 是一条 CPU 指令的命令而不是多条 CPU 指令,所以它不会被多线程的调度所打断,所以能够保证 CAS 的操作是一个原子操作 。
当然 Cmpxchg 的机制其实存在 ABA 还有多次重试的问题,这个不在这里讨论 。
③Redis 的 Watch 机制
Redis 的 Watch 也是使用 Cmpxchg 吗,两者存在相似之处在用法上也有一些不同,Redis 的 Watch 不存在 ABA 问题,也没有多次重试机制,其中有一个重大的不同是:Redis 事务执行其实是串行的 。
简单追一下源码:摘录出来的源码可能有些凌乱,不过可以简单总结出来数据结构图和简单的流程图,之后再看源码就会清晰很多 。

Hello Redis,我有7个问题想请教你

文章插图
 

Hello Redis,我有7个问题想请教你

文章插图
 

Hello Redis,我有7个问题想请教你

文章插图
 

Hello Redis,我有7个问题想请教你

文章插图
 
存储如下图:
Hello Redis,我有7个问题想请教你

文章插图
 
RedisDb 存放了一个 watched_keys 的 Dcit 结构,每个被 Watch 的 Key 的值是一个链表结构,存放的是一组 Redis 客户端标志 。
流程如下图:
Hello Redis,我有7个问题想请教你

文章插图
 
每一次 Watch,Multi,Exec 时都会去查询这个 watched_keys 结构进行判断,每次 Touch 到被 Watch 的 Key 时都会标志为 CLIENT_DIRTY_CAS 。
因为在 Redis 中所有的事务都是串行的,假设有客户端 A 和客户端 B 都 Watch 同一个 Key 。
当客户端 A 进行 Touch 修改或者 A 率先执行完,会把客户端 A 从这个 watched_keys 的这个 Key 的列表删除,然后把这个列表所有的客户端都设置成 CLIENT_DIRTY_CAS 。
当后面的客户端 B 开始执行时,判断到自己的状态是 CLIENT_DIRTY_CAS,便 discardTransaction 终止事务 。
简单总结:Cmpxchg 的实现主要是利用了 CPU 指令,看似两个操作使用一条 CPU 指令完成,所以不会被多线程进行打断 。
而 Redis 的 Watch 机制,更多是利用了 Redis 本身单线程的机制,采用了 watched_keys 的数据结构和串行流程实现了乐观锁机制 。
Redis 是如何持久化的
Hello Redis,我有7个问题想请教你

文章插图
 
Redis 的持久化有两种机制,一个是 RDB,也就是快照,快照就是一次全量的备份,会把所有 Redis 的内存数据进行二进制的序列化存储到磁盘 。
另一种是 AOF 日志,AOF 日志记录的是数据操作修改的指令记录日志,可以类比 MySQL 的 Binlog,AOF 日期随着时间的推移只会无限增量 。
在对 Redis 进行恢复时,RDB 快照直接读取磁盘即可恢复,而 AOF 需要对所有的操作指令进行重放进行恢复,这个过程有可能非常漫长 。
Hello Redis,我有7个问题想请教你

文章插图
 
RDB
Redis 在进行 RDB 的快照生成有两种方法,一种是 Save,由于 Redis 是单进程单线程,直接使用 Save,Redis 会进行一个庞大的文件 IO 操作 。
由于单进程单线程势必会阻塞线上的业务,一般的话不会直接采用 Save,而是采用 Bgsave,之前一直说 Redis 是单进程单线程,其实不然 。
在使用 Bgsave 的时候,Redis 会 Fork 一个子进程,快照的持久化就交给子进程去处理,而父进程继续处理线上业务的请求 。
①Fork 机制
想要弄清楚 RDB 快照的生成原理就必须弄清楚 Fork 机制,Fork 机制是 linux 操作系统的一个进程机制 。
当父进程 Fork 出来一个子进程,子进程和父进程拥有共同的内存数据结构,子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段 。
Hello Redis,我有7个问题想请教你

文章插图
 
一开始两个进程都具备了相同的内存段,子进程在做数据持久化时,不会去修改现在的内存数据,而是会采用 COW(Copy On Write)的方式将数据段页面进行分离 。
当父进程修改了某一个数据段时,被共享的页面就会复制一份分离出来,然后父进程再在新的数据段进行修改 。
Hello Redis,我有7个问题想请教你

文章插图
 


推荐阅读