一)Redis持久化之RDB=redisDataBase:
什么是持久化:
1)持久性:和持久化说的是同一回事,衡量持久性的前提是重启进程或者是重启主机以后数据是否还存在
持久:把数据存储在硬盘上,那么就是持久性
不持久:把数据存储在内存中
2)redis是一个内存级别的数据库,是把数据存储在内存中的,内存中的数据是不持久的,要想能够做到持久,就必须让redis把数据存储在硬盘上面,但是redis相比于MYSQL这样的数据库,最明显的优势就是快,效率高,就是数据保存在内存中,为了保证数据快,数据肯定还是在内存中,但是为了持久,数据肯定还得想办法存储在硬盘上,redis最终得出策略是,内存中也存数据,硬盘上也存数据,实际上这两份数据,理论上是完全相同的,但是实际上可能有小小的概率是不相同的,取决于如何进行持久化的操作
3)当要进行插入一个新的数据的时候,就需要把这个数据同时写入到内存和磁盘,当查询某一个数据的时候,同时写入到内存和磁盘,当读取某一条数据的时候,直接从内存中读取,硬盘的数据主要是在redis重启的时候,内存数据没了,用来恢复内存中的数据的,硬盘的数据平时都不用,相当于是一个备份,代价就是消耗了更多的空间,同一份数据,存储了两遍,毕竟磁盘比较便宜,这样的开销不会带来更多的成本
4)虽然说具体在向redis中写入数据的时候,同时写入到内存和硬盘,但是实际上怎么写硬盘还有不同的策略,可以保证整体的效率还是足够高
RDB:定期备份,每半年,把我电脑上面的硬盘资料整体的备份到这个备份盘中
AOF:实时备份,只要我下载了一个学习资料,就立即把这个学习资料往备份盘中进行拷贝
RDB:RDB定期的把redis内存中的所有数据都给写入到磁盘中,生成一个快照,就是redis给内存中存储的数据,赶紧拍个照片,生成一个文件存储在硬盘中,后续redis一旦重启了,内存中的数据就没了,后续redis一旦重启了,就可以根据刚才的快照,就能把内存中的数据回复回来了,这里的定期有两种方式:
1)手动触发:程序员通过redis客户端执行特定的命令,来触发快照生成:
save:执行save的时候,redis就会全力以赴的生成快照文件这样的操作,将内存中的所有数据写到磁盘里面,此时就会阻塞redis其他客户端的操作,因为redis本身是一个单线程的系统,一次只能处理一个命令,类似于keys *的后果,如果此时使用redis作为缓存,可能导致MYSQL和redis都挂了,一般不建议save;
bgsave:back ground,在后面,save是在前台进行持久化,bgsave不会影响redis服务器处理其他的请求和命令,这样既可以保证redis能够进行持久化,还能够处理其他客户端的请求,此时没有采用多线程,此处redis使用的是多进程的方式完成并发编程实现bgsave
bgsave的执行流程:
1)当前可能需要bgsave的进程有很多,系统会进行判断当前是否已经存在有着其他工作的子进程比如说现在已经有一个子进程正在执行bgsave,那么就直接把当前执行的bgsave返回
2)如果没有其它的工作子进程,就通过fork这样的系统调用创建出一个子进程来,在这个fork的过程中,父进程会阻塞redis的其他命令,在fork过程中主进程是会阻塞的,但是通常情况下该指令执行的速度比较快,对性能影响不大;
2.1)进程和线程JAVA进行并发编程,主要是通过多线程的方式,fork是linux系统提供的一个创建子进程的API,也就是系统调用,fork创建子进程简单粗暴,就直接把当前的父进程复制一份作为子进程,一旦复制完成了,父子进程就是两个完全相同的进程,就各自执行各自的了,这个fork的过程中,会复制PCB,虚拟地址空间(内存中的数据),文件描述符表,也就是说本来在redis-server中,有若干变量,保存了一些键值对的数据,随着这样fork的进行,子进程中的内存里面也会存在着和父进程一模一样的变量
2.2)因此复制出来的克隆体(子进程)的内存数据就是和本体父进程的是一样的,接下来就去安排子进程去进行持久化操作,相当于是把父进程这里面的内存数据给持久化了,父进程打开了一个文件,fork了以后,子进程也是可以直接进行使用这个文件的,这样也就导致了子进程持久化写入的那一个文件和父进程是一模一样的;
如果当前redis服务器中,存储的数据特别多,内存消耗特别大,比如说100GB,此时再去执行上面的复制操作,是否存着很大的性能开销呢?
2.3)此时的性能开销其实挺小的,fork在进行内存拷贝的时候,不是简单无脑的直接把所有的数据都直接拷贝一遍,而是通过写时拷贝这样的机制来完成的,如果子进程的内存数据和父进程的内存数据是相同的,此时就不会触发真正的拷贝操作,其实爷俩使用同一份数据,但是其实这俩的内存空间应该是相互独立的,一旦某一方针对于这个内存数据进行了修改,就会立即触发真正的物理内存上的数据拷贝
接下来父进程在这里面产生修改,将10变成20
2.4)在进行bgsave的场景中,绝大部分的内存数据是不需要进行改变的,整体上来说RDB的执行速度还是很快的,在这个短时间内,父进程不会有大量的数据的变化,因此子进程的写实拷贝不会触发很多次,也就保证了整体的拷贝时间是可控的,高效的,所以当前基于fork的方式创建子进程并由子进程完成持久化的思路是完全行得通的,一方面子进程完全继承于父进程的内存数据,同时也继承于父进程的文件描述符表,另一方面还有写时拷贝的支撑,让这样的继承过程并不是很低效,redis子进程完成持久化的过程是完全可行的
3)子进程创建RDB文件,对原有的rdb文件进行替换find / -name dump.rdb
RDB的触发时机:
dir /var/lib/redis默认是redis的工作目录,dofilename dump.rdb表示的是rdb文件的名字
rdb的触发时机:
1)手动触发:save bgsave,注意redis配置文件修改以后,必须重启redis服务器以后才会生效
2)自动触发:在配置文件中进行配置,save ""关闭自动生成快照
2)自动触发:
2.1)在redis配置文件中,设置一下,让redis每个多隔长时间,每产生多少次修改,就进行触发save seconds changes,一个是秒数,一个是次数是在具体的修改的秒数范围内修改的次数虽然上面的这些数值,都是可以自行修改配置的,但是基于修改上述数据的时候,要有一个最基本的原则,生成一次rdb快照,这个成本是一个比较高的成本,不能让这个操作执行的太频繁,因为这个操作要把内存中的所有键值对都写入到一个文件里面,所以本身dump数据的操作消耗是很大的,要限制操作频率,正是因为rdb快照不能生成的太频繁,这就很有可能导致快照中的数据和当前实施的数据情况可能会存在偏差,save 60 10000,这个参数代表的意思就是60秒内数据修改了10000次就要生成rdb文件
2.2)问题:12:00:00生成了rdb文件,此时硬盘上面的快照文件和内存中的数据是一致的,但是从12:00:01开始,服务器收到了redis的大量的key的请求,但是redis在12:00:02开始才会生成快照文件,在这段区间也就是12:00:00到12:00:01这段区间如果redis服务器宕机了,就会导致12:00:01后面的数据都丢失了,但是AOF就是解决这些问题的方案
1)当手动执行bgsave触发一次生成内存快照的操作:
从redis服务器进行测试以后发现,我们向redis服务器中插入几个键值对,然后等待一段时间之后打开rdb文件发现文件内容并没有发生变化,此时就可以手动执行save或者是bgsave,由于这里的数据量比较小,执行bgsave瞬间就完成了,立即查看应该就是有结果的,但是以后接触到的数据多了,执行bgsave就很有可能消耗一定的时间,立即查看不一定就生成完毕了,redis再进行重新启动的时候,加载了rdb的文件内容,恢复了之前内存中的状态了
2)插入新的key不手动执行bgsave:
重新启动redis服务器发现新插入的键值对竟然在重启后仍然存在,如果是通过正常流程重新启动redis服务器,此时当redis服务器会在退出的时候,自动触发生成rdb操作,但是如果是异常重启,kill -9或者是服务器掉电,此时redis服务器来不及生成rdb,内存中没有保存到快照中的数据,就会随着服务器重启而消失;
总结:rdb生成快照,不仅仅是通过手动执行命令才触发,也是可以自动触发的:
1)通过刚才配置文件中的save执行M时间内,修改N次
2)通过shutDown命令,也就是redis中的一个命令关闭redis服务器,也会触发
3)当redis进行主从复制的时候,主节点也会生成rdb快照,然后把rdb快照文件内容传输给从节点,实际开发中更害怕的是出现异常情况,比如说通过kill -9命令杀死,
手动观察RDB文件替换:
1)bgsave的执行流程是先创建子进程,子进程完成持久化操作以后,持久化就会把数据写入到新的文件中,然后使用新的文件来替换旧的文件,但是持久化本身速度太快了,因为数据量比较少,所以很难观察到子进程但是使用新的文件替换旧的文件,这个是很容易观察到的
2)stat dump.rdb命令可以显示文件的inode编号,inode相当于是rdb文件的唯一身份标识,有的时候文件不是同一个文件了,不过内容是一样的
3)如果这里面直接使用save命令,此时是不会触发子进程和文件替换逻辑的,如果是save就直接在当前进程中,往刚才的同一个文件里面写入数据了,flushall会清空rdb文件;
手动破坏rdb文件:
1)手动的把rdb文件内容改坏,然后一定是通过kill进程的方式重启redis服务器,如果通过service redis-server start的方式重启,就会在redis服务器退出的时候,重新生成rdb快照,就会把刚才咱们修改坏了的文件给替换掉了
2)手动将rdb文件内容破坏,也就是在rdb文件内容的结尾写上一大堆的乱七八糟的字符串,然后杀死redis进程,然后重启redis文件发现仍能启动成功并且能够正确地获取redis中的数据,但是这里取决于redis怎么样,取决于rdb的文件坏在哪里,如果像刚才这样修改坏的位置在文件末尾,那么就对前面的内容没啥影响,但是如果是中间的位置坏了,可就不一定了
3)当redis服务器挂了可以查看redis日志:
rdb文件是二进制的,直接就把坏的rdb文件交给redis服务器来使用,得到的结果是不可预期的,可能redis服务器能启动,但是也有可能得到的数据是错误的,也有可能redis服务器直接启动失败,redis本身提供了rdb文件的检查工具,可以先来通过检查工具,检查一下rdb文件是否符合要求,就是redis-check-rdb *,redis-check-rdb dump.rdb
redis-server /etc/redis.conf&
RDB的特点:
1)rdb是一个紧凑压缩的二进制文件,代表redis在某一个时间节点的二进制快照,非常适用于备份,全量复制等场景,比如说每隔6h做一次bgsave备份,并把rdb文件复制到远程机器或者是文件系统中用于灾备,Redis加载rdb文件格式的数据远远快于AOF的方式,因为rdb这里面使用二进制方式来组织数据,直接把数据读取到内存里面,按照字节的方式放到结构体和对象中即可,AOF是使用文本的方式来组织数据的,则需要手动的进行字符串切分操作
2)rdb最大的问题就是不能实时的持久化保存数据,在两次生成快照之间,实时的数据可能会随着重启而丢失
二)Redis持久化操作之AOF
1)AOF,顾名思义就是append only file,类似于MYSQL的binlog日志,因为MYSQL把用户的每一个操作都记录到了文件中,当开启AOF的时候,rdb就不生效了,启动的时候也就不在读取rdb文件内容了,当redis重新启动的时候,就会读取这个AOF文件中的内容用来恢复数据
AOF默认一般是关闭状态,修改配置文件,来开启AOF功能
2)可以使用appendonly yes来手动开启AOF,appendfilename表示aof生成的文件名字,这个aof所在的文件的位置也是和rdb所在的目录一样,/var/lib/redis同样也是可配置的
3)AOF文件本身记录了用户执行操作以后的命令,AOF本身是一个文本文件,每一次进行的操作,都会被记录到文本文件中,通过一些特殊符号来作为分隔符,来针对于命令的细节来做区分
4)redis虽然本身是一个单线程的服务器,但是速度非常快,为啥速度快,一个非常重要的原因就是只是操作内存,引入AOF以后,又要写内存,又要写磁盘,还能和之前一样快了吗?
实际上是没有影响的,她并没有影响到redis处理请求的速度
5)AOF机制是否影响了redis处理请求的速度呢?
5.1)AOF机制并非是直接让工作线程把数据写入到磁盘,而是先写入到一个内存的缓冲区,积累一波之后再来进行写入磁盘的操作,这样就大大降低了写硬盘的次数,假设现在有100个请求,100个请求的数据,一次性写入到硬盘比分100次,每一次写一个请求要快很多,但是实际上写硬盘的时候,写入磁盘数据的多少,对性能的影响不是很大,但是写入磁盘的次数就影响很大了
5.2)在硬盘上面读写数据,顺序读写的速度是比较快的,虽然还是要比内存慢很多,但是随机访问速度还是要慢很多的AOF操作是每一次把新的操作写入到原有文件的末尾是随机写入
AOF缓冲区的刷新策略:
但是此时如果把数据写入到缓冲区里面,本质上还是在内存中,万一这个时候突然进程挂了,或者是主机掉电了,是不是缓冲区的数据就丢了,对的,但是redis提供了缓冲区的刷新策略,刷新频率越高,性能影响就越大,同时数据的可靠性就越高,刷新频率越低,性能影响就越小,同时数据的可靠性就越低,通过在配置文件中使用everyfsync来配置
1)always:当命令写入到AOF缓冲区中调用fsyc,立即刷新到AOF文件中,这种方式频率最高,数据可靠性最高,但是性能最低
2)everyesc:命令写入到AOF缓冲区中每一秒由同步线程刷新到缓冲区中,本身频率要低一些,数据可靠性也会降低,但是性能会提升
3)no:命令写入到AOF中redis就不管了,由OS控制fsyc刷新到缓冲区中,频率最低,数据可靠性也是最低了,性能是最高的
AOF重写机制:
因为随着AOF记录的内容越来越多,体积越来越大,会影响到redis下一次的启动时间,因为redis本身在进行启动的时候要去读取AOF文件的内容,但是AOF文件本身只是记录了中间的过程,但是实际上redis在重新启动的时候,他是只是会记录中间的结果,因为本身AOF的文件有很多的信息是冗余的,比如说假设有一个客户端,对于redis做了下面的操作
因此redis本身就存在着这样一个机制,能够针对于AOF文件做整理操作,这个整理就是能够剔除这里面的荣誉操作,并且能够合并一些操作,从而达到给AOF文件本身瘦身的这样一个效果
AOF的重写机制的触发时机:
1)手动触发:调用bgrewriteof命令
2)自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentge参数来确定自动触发时机
auto-aof-rewrite-min-size:表示触发重写的时候的aof的最小文件大小,默认是64MB
auto-aof-rewrite-percentge:代表AOF占用大小相比较于上次重写的时候增加的比例
AOF重写的流程:
1)父进程仍然负责接受请求,子进程仍然负责针对于AOF文件进行重写,重写的时候不关心AOF文件中原来都有啥,只是关心内存中最终的数据状态,子进程只是需要把内存中的数据获取出来以AOF的格式写入到一个新的AOF文件中,但是实际上AOF在进行重写的过程中,并不需要遍历所有的命令进行压缩,因为内存中的数据已经是最终的结果了,也就是说AOF文件中的数据的状态,就已经是相当于把AOF文件中的数据整理以后的结果了
2)此处这个子进程写数据的过程,非常类似于RDB生成一个快照文件的过程,只不过是RDB是按照二进制的方式来生成的,AOF重写,是按照AOF这里面生成的文本格式来生成的,都是为了把当前内存中的所有的数据状态记录到文件中,只不过是写入的数据的方式不一样,一个是文本,一个是二进制
3)在子进程写AOF文件的同时,父进程仍然在不断的不停的接受客户端的新的请求,但是父进程仍然会将这些新的请求写入到原来的缓冲区中,然后再刷新到旧的AOF文件中
4)但是在创建子进程的那一时刻,子进程就继承了父亲进程的当前的内存状态,因此此时的子进程的内存数据就是父亲进程fork之前的状态,那么在fork以后的时候,新来的请求对内存进行的修改,对子进程没有影响,解决的方案就是在父亲进程里面又准备了一个aof_rewrite-buf缓冲区,这个缓冲区专门用来存放父进程fork出来的子进程以后新增的命令,所以此时父进程就很忙了,父进程既要写原来的aof缓冲区,又要写aof_rewrite_buf缓冲区,两个缓冲区都要写,一个是往旧的缓冲区里面写,一个是往新的缓冲区里面写
5)当子进程这边把新的AOF的文件的数据写完以后,会通过一个信号机制通知一下父进程,然后父进程再把aof_rewrite_buf缓冲区中的数据也写入到新的AOF缓冲区里面
新的AOF文件来源于两方面:
1)父进程fork子进程那一时刻的内存的数据
2)子进程在重写期间父进程向aof_rewrite_buf中写的数据
6)新的AOF文件替换原来的AOF文件
总结:整个重写过程分成两个阶段,一份是fork之前的父进程内存中的数据子进程直接写入到新的AOF文件中,一份是fork之后父进程收到命令修改后的数据,由父进程现存放到AOF重写缓冲区以后,等待子进程发送重写完成的信号,再由父进程将重写缓冲区里面的数据写入到新的AOF文件中;
AOF重写的问题:
1)如果在执行bgrewriteaof的时候,当前redis已经正在进行AOF重写了,会咋样呢此时就不会再执行AOF重写了,就会直接返回了,只有等待一段时间以后AOF重写才会起到立竿见影的成果
2)如果在执行bgrewriteaof的时候,发现当前redis在生成rdb文件快照的时候,会咋样呢?
此时aof重写操作就会等待,等待rdb生成快照完毕以后再来进行aof重写操作
3)rdb本身对于fork之后的新数据就置之不理了,但是AOF对于fork之后的新数据,采取了AOF_rewrite_buf的缓冲区来进行处理,因为本身rdb的设计理念就是用来做定期备份数据的,只要是定期备份,就很难和最新的数据保持一份,aof的设计理念就是实时备份,实时备份比定期备份的系统开销比较大,但是现在的系统中,系统的资源一般来说都是比较充裕的,上一些更好的服务器,更好的配置,更好的服务器,AOF的开销也就不算事,但是AOF的加载速度比较慢;
4)当前父进程fork完毕以后,就已经让子进程写新的AOF文件了,并且随着时间的推移,子进程已经写完了新的文件,并且要让新的AOF代替旧的,但是父亲进程还是在继续写这个即将消亡的旧的AOF文件,是否还有意义?
不能不写旧的文件,假设在重写过程中重写了一半了,突然间服务器挂了,这样的重写显然是无法继续了,如果主机挂了,aof_rewrite_buf中的数据就没了,服务器就不完整,子进程内存中的数据就会丢失,新的AOF文件的内容还不完整,如果父进程不坚持写旧的AOF文件,重启以后就无法保证数据的完整性了;
三)混和持久化:
混合持久化既可以保证aof文件没有那么大,还可以保证服务器启动以后加载的效率
1)混合持久化的定义:AOF本来是按照文本的方式来写入文件的,但是以文本的方式写文件,后续加载的成本是比较高的,于是redis就引入了混和持久化的方式,本身结合了rdb和aof的特点,按照aof的方式每一个请求/操作,都记录到文件里面,但是在触发AOF重写以后,就会把当前内存的状态按照rdb二进制的形式写入到新的AOF文件中,后续再进行操作redis,仍然是按照aof文本的方式追加到文件的后面;
2)aof-use-rdb-preamble yes表示开启混和持久化,修改配置以后要记得重启服务器才可以生效,如果不开启混和持久化,那么重写AOF文件就是按照AOF文本格式来记录的,本质上还是命令;
四)关于信号的理解:
当redis上同时存在rdb和aof快照的时候以AOF为主,rdb就直接被忽略了,因为AOF包含的数据比RDB更全
1)信号表达的信息有限,并非能够像socket这样的方式来进行传输数据,因此像上述父子进程场景中,子进程表示我干完了这种简单的信息传输使用信号也是OK,当然其他进程中通信方式就是OK,和JS的事件是类似的
2)事件=事件源+事件类型+事件处理函数,虽然不知道事件什么时候来,但是当用户点击这个按钮,就弹出一个对话框或者是进行提交,信号就可以理解成linux内核版本的事件机制:
信号源+信号类型+信号的处理函数,在内核中更关注的是信号发给谁,kill -9表示给指定进程发送9号信号,信号处理函数等同于事件处理函数;
3)function(data,status){把重写缓冲区中的数据写到新的AOF文件中}
五)Redis事务:
1)原子性:redis中的事务到底存不存在原子性?存在争议,原子就是不可以拆分的最小单元
原子性最初的含义是:是将多个操作打包到一起,要么全都执行成功,要么全都不执行,redis做到了上述的含义,但是MYSQL这里面的原子性走得更远,也是把多个操作打包到一起,要么全部执行正确,要么全都不执行,但是redis这里面的事务不保证成功,redis是将这些操作打包了,是全部执行了,但是它只是保证执行,但是成功与否,与我无瓜,redis中的事务由若干个操作,存在有失败的,那就失败把,不会执行回滚操作,但是MYSQL中的事务如果有操作执行失败,就会进行回滚,把中间已经执行的操作全部回退,redis这里面的事务只是打包,MYSQL是打包在一起正确执行;
2)不具备一致性:redis没有约束,也没有回滚机制,事务执行过程中一旦出现了某一个修改操作出现失败,就可能会引起数据不一致的情况;
3)持久性:不具备持久性,redis本身就是一个内存数据库,数据是存储在内存中,虽然redis也有持久化机制,但是这里的持久化机制和事务没啥关系,MYSQL保证只要执行事务,最终修改的数据就一定保存在硬盘上,但是redis事务和持久性没啥关系
4)隔离性:不涉及隔离性,redis是一个单线程模型的服务器程序,所有的请求和事务都是串行执行的,MYSQL中的隔离性只有说并发执行事务才会出现执行但是现在redis完全串行执行
1)redis的事务主要的意义就是为了打包一起,避免其他客户端的命令插队插到中间,Redis本身实现事务,是引入了一个队列,这是每一个客户端都存在一个,开启事务的时候,此时客户端输入的命令,就会发送给服务器并且进入到这个队列里面,而不是立即执行,当遇到了执行事务这个命令以后,此时就会把队列中的这些任务都按照顺序依次执行,这些任务的执行,都是在主线程中完成的,主线程会先把事务中的操作都执行完成,再来执行处理别的客户端,redis中的事务只是保证执行,但是最终执行的结果对不对,redis说的不算
2)redis中的事务为什么就搞得这个简单,为什么不像MYSQL那样子弄得那么强大呢?
因为MYSQL中的事务,在背后付出了很大的代价,要花费更多的空间来存储更多的数据,时间上也有很大的执行开销,正是因为MYSQL有了上述的问题,才有了redis上场的机会
啥时候需要使用到Redis中的事务呢,如果我们将多个操作打包进行使用事务是比较合适的
超卖是放货5000台,实际上如果让5001个人下单成功,实际上就属于超卖问题
如果不加上任何限制,就很有可能存在线程安全问题,因为在以前多线程的时候,是通过加锁的方式来避免插队的,但是在redis中直接使用事务就可以
1)当第二个客户端的执行事务命令发送过来以后,服务器才真正的执行第二个事务里面的内容,此时第一个事务执行完成事务命令已经运行过了,此时第二个事务get到的count就已经是第一个事务自减以后的结果了,实际上在这个过程没加锁,也可以解决问题
2)从上面的角度来看redis的原生命令里面是不支持条件判定的,但是redis中支持lua脚本,通过lua脚本就可以实现上述的条件判定,并且也是和事务一起是打包一起批量执行的,lua脚本的实现方式是事务的进阶版本,redis如果是按照集群部署,不支持事务
事务的操作:
开启事务:multi,向里面添加命令只是向服务器的事务队列中添加了并且保存了上述请求,此时如果另外在开启一个客户端,再尝试查询这几个key对应的数据是不存在结果的
执行事务:exec
回滚事务:discrad丢弃这个事务中添加任务队列中的任务
1)当开启事务并且给服务器发送若干个命令以后,服务器的事务相当于是discard,当执行multi以后是把命令发送给服务器的任务队列里面,但是并没有执行,但是任务队列是内存的数据
2)通过watch命令来监控某一个key在任务执行前是否发生了改变,下面结果是222
1)由于在客户端1,得是exec执行了,才会真正的执行set key 222,这个操作实际上变成了更晚的操作,所以最终值就是222;
2)在刚才这个场景中,我们就可以使用watch命令来监控这个key,看看这个key在事务的multi和exec之间,set key以后是否存在着外部被其他客户端修改了,如果加上了这个监控指标,此时当exec执行命令的时候,发现外部的key存在着修改,于是真正执行set key 222的时候就会报错,就没有真正执行,虽然watch命令没有真正的实现加锁,但是可以得知外部的客户端针对于当前的key造成的修改和变化,从而不会导致其他客户端进行修改从而造成结果上的一些歧义和影响;
watch的实现:底层是基于乐观锁实现的
1)乐观锁和悲观锁指的不是某一把具体的锁,而是指的是某一类锁的特性,锁冲突的意思就是两个线程针对于同一个锁加锁,一个线程加锁成功,另一个就得阻塞等待
乐观锁:在加锁之前就有一个心理预期,预计锁冲突的概率比较低
悲观锁:在加锁之前也有一个心理预期,预计锁冲突的概率比较高
redis本身就是基于版本号这样的机制,来实现了乐观锁
2)当执行watch key的时候,就会给key安排一个版本号,版本号可以理解成一个整数,每一次进行修改key的时候,key的版本号就会变大,注意:版本号会变大,但是不会自增
1)watch必须搭配事务来进行使用,况且这个命令执行必须在multi之前
2)当执行watch命令的时候,当前的redis客户端就会给这个key分配一个版本号,并且当前事务会记录这个版本号,别的客户端想要针对于当前这个key作出修改,就会引起版本号变大,比如说客户端让key的版本号变成了2
3)当客户端1执行了命令的时候,此时就会做出判定,当前这个key的版本号和最初watch时候的版本号是否一致,如果不一致,说明key在其它客户端已经被修改了就会执行失败直接丢弃事务中的操作,exec直接返回nil,如果一致,那么就说明当前key在事务开启的过程中到最终执行的过程中,没有别的客户端修改这个key,才能真正进行设置,watch本质上是给exec家伙是那个了一个版本号的判断条件,是基于乐观锁实现的,底层就是基于CAS实现;
总结:redis中的事务,要比MYSQL做的事务要简单得多
1)原子性:Redis中的事务,并不支持回滚,只是可以保证这些操作可以在一起执行
2)一致性:Redis本身并不会保证事务执行前和执行后,内容统一,数据一致
3)持久性:redis的数据是保存在内存里面的,持久化机制和事务的持久性没有任何关联关系
4)隔离性:redis本身是单线程执行的服务器模型,上面处理的请求本质上是串行执行的,官方网站上说类似于事务的操作,都是可以通过lua脚本来实现的