【redis】06Redis的高级应用之事务处理、持久化操作、pub_sub、虚拟内存

上节课详细讲解了redis数据库的常用命令,以及redis数据库高级应用当中的,
安全性,跟咱们的主从复制,
这节课呢,咱们继续来讲咱们的高级应用,
首先来看一下咱们的事务处理,
事务处理
我前面说过redis可以做事务处理,但是是非常简单的,
他对事务处理的支持还是比较简单的一个阶段啊,
Redis只能保证一个client发起的事务中的命令可以连续的执行,
而中间不会插入其他的client客户端的命令。
当一个客户端在一个连接中发出mutil命令时,
当我输入这个命令的时候,
好这个时候咱们就进入了一个事务处理,进入了一个事务的上下文,
该连接后续的命令不会立即执行,
而是先发到一个队列中,当咱们执行exec这条命令的时候,
redis会按照,咱们刚才输入的那些顺序,一个一个的执行队列中的所有命令,
 
咱们首先获取下,咱们当前的age键,获取完以后我输入一个命令叫做multi
输入multi以后,咱们就打开了一个事务的上下文,打开了一个事务,
然后我再setage这里没有返回OK吧,
也没有返回他的值吧,这里返回一个QUEUED
代表什么意思放入队列,setage 10放入队列,
setage 20好再放入队列,
此时咱们的setage 10 和setage 20这两条命令还没有执行。
那我这个时候的的这个exec代表这两条命令按顺序执行,
首先让他执行setage 10再执行setage 20 好,
然后exec返回的是两条命令的结果,分别返回两天命令的结果,
第一条返回成功,第二条返回成功,我最终age编程了多少,age变成了20
咱们理解这个意思吧,首先multi是打开一个上下文,
当打开上下文以后set age 10和set age 20都会放到一个队列里面,
但是他们不会立即执行,当你输入exec的时候,才会按照队列里面的每一条命令,
依次来执行,最终执行完毕后,age里面是20
这是咱们的一个事务处理,比较简单,
 
那么咱们如何取消一个事务呢,
比如说我get age 等于20
然后multi打开一个事务,
set age 30 
set age 40
好我这个时候反悔了,我不想让他执行怎么办,咱们用一个命令叫做
discard,取消前面所有的事务,取消前面所有队列里面的命令,
然后在get age是不是20,对吧,没有执行吧,
可以发现这次2个set age命令都没有被执行,
而discard命令其实就是清空事务的名利队列并退出事务的上下文,
也就是我们常说的事务回滚,
对吧,这个时候咱们可以达到一个事务回滚吧,
达到了一个事务回滚的效果,
 
但是为什么我们说他对事务的支持还是比较简单的呢,
我给大家举一个例子
keys *
1) "age"
是不是只有一个age
set name lijie
OK
首先我用一个incr方法,是让咱们的一个键自增对不对,
incr name
(error) ERR value is not an integer or out of range
本身咱们name里面存的是字符串吧,
你incr name返回的是不是一个错误,不允许咱们字符串自增吧,
multi
OK
multi打开咱们的事务处理
打开以后我们让age incr一下
incr age
QUEUED
进入队列,然后再让咱们的name进行自增,
incr name
QUEUED
然后咱们再
exec
1) (integer) 101
2)(error) ERR value is not an integer or out of range
大家说这样子会有什么后果,
咱们在讲mysql事务处理的时候,是不是里面任何一个操作不成功,
整个事务都会回滚,也就是说当我incr age它可以是吧,     
那我incr name是不是不成功啊,但我他执行不成功的时候,
咱们的incr age是不是也应该不成功啊,
最终是不是整个事务会回滚啊,但是咱们的redis事务不行,
看见了吗,咱们的age变成了101但是咱们的name呢,是不是刚才报了一个错,
 
 也就是说事务里面的某一个队列,里面的某一个命令,执行错误,
好这个队列里面的某一个名利执行错误,整个事务不会回滚,
这是他事务需要改进的一个地方,
get age
"101"
是不是变成101啦,没有回滚吧,
 
说道事务处理,咱们引出一个乐观锁的概念
 
乐观锁:大多数是基于数据版本(version)的记录机制实现的。
即为数据增加一个版本标识,在基于数据库表的版本解决方案中,
一般是通过为数据库添加一个"version"字段来实现读取出数据时,
这个字段来记录咱们的版本号,
将此版本好一同读出,之后iou更新时,对此版本号加1。此时,
将提交数据的版本号与数据库表对应记录的当前版本号,进行比对,
如果提交的数据版本号大于数据库当前版本好,则予以更新,
否者认为是过期数据。
说这么多大家是不是能够想到咱们用的一个软件,版本控制器吧,
其实乐观锁和咱们的版本控制器完全相识的,
还记得版本控制器吧,
我再来给大家说一下版本控制器吧,
比如说这里有个版本控制器,
里面我有一个文件叫a.txt
我第一次下载,下载到第一个人的本地,
 a.txt假设我当前的版本号为1,
版本库当中a文件的版本号为1
然后第二个人也在下载,
下载过后是一样的吧,a.txt,版本号也为1,
比如说第一个人对a.txt进行,编辑的时候
vi a.txt这个文件的时候,编辑完这个文件以后我保存退出,
保存完了以后我向上提交,当我提交到版本库的时候,
我这个文件的版本号就会增加1,
也就是说我当前的a.txt文件,版本号升级为2,版本号变为了2,
因为我进行了更新吧,进行了更改,
第一个人修改了文件,提交到版本库,版本号会加1,
这是咱们svn的操作吧,
当我第二个人,我刚才是不是下载好的,下载好的a.txt吧,
我没有更新的情况下这个文件的版本号还为1,对吧,
好我这个时候 vi a.txt,编辑,
我这个时候编辑完,保存退出,我再给他提交,你们说能提交成功吗,
答案是不能,因为咱们的a.txt本地的版本号是1,但是咱们服务器的版本号
是不是2,1是不是小于2啊,也就是说,也就是说第二个人当前的这个版本
已经过期了,在第二个人提交的时候就会给你报错,报一个此文件已经过期,
这是咱们的一个乐观锁的一个概念,
 
那么咱们来看一下redis是怎么做乐观锁的啊,  
看redis的乐观锁事例,假设,有一个age的key,
我们开2两个session来对age进行赋值操作,
我们来看一下结果如何,
 
watch age代表对age进行监控,如果age有修改,查看是否age有修改,啊
好我打开一个事务上下文,
然后我们打开第二个session,直接设置这个age为30,我这里没有开事务啊,
我是不是能直接执行成功吧,直接执行成功,这里我再get age,发现这个age
是不是已经变成30啦,
 
好我这个时候我session1我又想起来了,我还开着事务,
这个时候我在设置set age 20
设置set age以后我exec
你们说能提交吗,是能提交但是提交不成功,
因为咱们是不是watch age对吧,
watch age我监控age键是否被修改过,如果修改过我当前的事务我是
不允许他执行成功的,返回一个nil空吧,
我当前的事务是不允许他执行成功的,因为我另一个session那边是不是
把他设置成30啦,如果我这边再set age为20 的话,是不是会造成一个
冲突吧,这样就不合适了吧,所以这边会返回一个空,
所以咱们的事务会执行不成功,
也就是说我监控age如果age进行过修改,
我队列里面有对age进行更改的,在执行的时候就不允许执行成功,
好这是咱们的乐观锁,redis进行一个乐观锁的事例,
 
watch 命令会监视给定的key,当exec的时候,如果监视的key从
调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视
多个key。这样就可以对指定的key加乐观锁了。
也就是说咱们打开watch的时候,就对咱们的age进行了乐观锁,
就对他加了一个乐观锁,注意watch的key是对整个连接有效的,
事务也一样,如果连接断开,监视和事务都会被自动清楚,
当然咱们的exec,discard,unwatch命令都会清楚连接中的所有监视,
这是咱们乐观锁的概念,
 
 
这是redis的事务处理,接着来讲下一块的高级应用,
叫做持久化机制,
我前面是不是说过redis的所有的键,是不是都在内存里面存的吧,
但是咱们的内存是不是有限的啊,
我不可能把几G的数据都存在内存里面吧,
如果说内存,内存是不是跟着计算机的关机而释放啊,如果说我计算机
突然宕机,咱们的数据是不是会丢失啊,这样是不是不合适,
所以说redis做成了一个支持持久化的内存数据库,
它支持咱们的持久化啊,
也就是说redis经常需要将内存当中的数据,同步到咱们的应聘来保证持久化,
Redis支持的两种持久化方式,
一种是,snapshorring(快照)也是默认的方式,
也就是说把数据做一个备份,也是默认方式啊,
第二种方式呢是,Append-only file(缩写aof)的方式,
这个意思是将咱们的写跟更改还有删除操作,将这些操作给他存到一个文件里面,
而第一种方式呢,是将咱们的数据,存到文件里面,
对不对这是两种方式吧,来分别来看一下,
 
Snapshotting方式,
快照方式是默认的持久化方式,这种方式是将数据中数据以快照的方式写入到,
二进制文件中,这个二进制文件咱们是无法查看的,因为他是二进制啊,
默认的文件名呢为dump.rdb。
可以通过配置设置自动做快照持久化的方式。
我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,
 
这是咱们的配置文件来配置啊,非常简单,这是咱们的快照方式,
 
是不是有一个文件叫dump.rdb因为他是默认的,
他会把当前咱们数据当中的所有数据,快照到咱们的dump.rdb文件里面,
来咱们来看一下,cat dump.rcb是不是无法查看,因为他是二进制的。
 
第二种方式呢叫aof方式
由于快照方式是在一定间隔 时间内做一次,所以如果redis意外down掉的
话,就会丢失最后一次快照后的所有修改,
如果这样子设置的话,咱们是不是有一个时间的间隙啊,
比如说我第一次做外快照了,我下一次做快照是不是需要900秒以后啦,
那怎么办,如果这900秒当中,有数据修改,
但是 还没有到900,还没有第二次备份的时候,数据就已经宕机了,
怎么办啊,你之前的数据是不是同样会丢失啊,
所以咱们会有第二种方式叫做aof方式,
 
那么aof比快照方式有更好的持久化性,是由于在使用aof时呢,
redis会将每一个收到的写命令都通过write函数追加到文件中,
当redis重启时候会通过重新执行文件中保存的命令来在内存中重建,
整个数据库的内容,
我每次都将咱们操作的一个记录写入到文件当中, 比如说修改啊,删除啊
以及往里面写的命令,全部写入到咱们的文件里面,
让redis重启的时候会执行咱们的aof文件,
aof文件里面存的都是咱们对数据增啊删啊修改啊,的操作,
这是咱们的aof方式 ,这样子会更安全一些啊,
 
看他具体来怎么设置啊,
由于os会在内存中缓存write做的修改,所以可能不是立即写到磁盘上的。
这样aof方式的持久化也还是有可能会丢失部分修改。
可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的
时机,
 
首先要打开咱们的appendonly ye打开aof方式,
启用之后呢,有三种方式,
第一种方式呢,是appendfsync是收到谢明亮立即写入磁盘,但是这种
效率最慢,但是是保证安全的持久化吧,我是不是没写一次就往文件里面
添加一次,
 
咱们来设置一下啊,
首先打开咱们的配置文件
vi /usr/local/redis/etc/redis.conf
找一个aof和save
 
改成yes以后来看一下咱们同步的方式
看一下这里面是不是有三种方式啊,
保存退出,打开咱们的aof方式了啊,
pkill redis-server
删除完以后来启动
/user/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
通过这个配置文件来启动,启动了以后我进入到咱们的客户端,
/usr/local/redis/bin/redis-cli -a lamplijie
set name lampbrother
OK
这个时候我看一下是不是有一个文件叫
appendonly.aof
对不对这个文件存的是什么,咱们看一下
cat appendonly.aof
发现存的是一些相关的命令吧,这是他存的一些所有的命令,
跟咱们的dump.rdb文件是不一样的,
dump.rdb里面存的就是数据,他不会存操作啊,
咱们的aof文件存的是操作,这是咱们的两种持久化操作吧,
 
发布及订阅信息
首先发布订阅(pub/sub)是一种消息通信模式,
主要的目的是解除,消息发布者与消息订阅者之间的耦合,
解除他们之间的关系吧,
redis呢作为一个pub/sub的server,
在订阅者和发布者之间,起到了一个消息路由的功能,
如果我发布者,发布了以后,所有的订阅者都可以看到,
订阅者可以通过咱们的subscribe和psubscribe命令向
redis server订阅自己感兴趣的消息类型,
redis呢将详细类型成为是通道(channel)。
叫做通道,
当发布者通过publish命令向redis server发送特定类型的信息时,
订阅该信息类型的全部client 都会收到此消息。
 
来咱们来做一个实验,
 
首先我打开第一个客户端,
/usr/local/redis/bin/redis-cli -a lamplijie
 
好我再打开一个session
进来以后我再打开一个客户端,
/usr/local/redis/bin/redis-cli -a lamplijie
 
我在打开一个session,进而再打开一个客户端,
这个时候我打开了三个客户端,
咱们用一个命令叫做subscribe
subscribe什么意思啊,订阅者监听,自己喜欢的频道啊,
 
subscribe tv1
Reading messages.. (press Ctrl-C to quit)
1) "subscribe"
2) "tv1"
3) (interger) 1
在此跳动阻塞在这儿啦,
 
监听tv1,监听电视台1
这个时候用第一个客户端,我们同样用
我监听tv1跟tv2,同时监听两个频道,
subscribe tv1 tv2
Reading messages.. (press Ctrl-C to quit)
1) "subscribe"
2) "tv1"
3) (interger) 1
1) "subscribe"
2) "tv2"
3) (interger) 2
在此跳动阻塞在这儿啦,
 
现在订阅两个频道,好,
这个时候这个客户端阻塞到这里了,他会占用一个session
来咱们用第三个session
进入到咱们的客户端,
进入来以后咱们给他发布一个命令,用咱们的publish
publish代表咱们的广播命令,广播咱们的tv1
 
publish tv1 lijielamp
(integer) 2
 
通过咱们的tv1广播一个命令,比如说叫lijielamp
广播了以后大家看到
 
第一个session收到了一个消息后还是阻塞在那里了,
1) "message"
2) "tv1"
3) "lijielamp"
在此跳动,
 
 
第二个session收到了一个消息后还是阻塞在那里了,
1) "message"
2) "tv1"
3) "lijielamp"
在此跳动,
 
首先我第二个客户端是不是弹出了一个message信息啊,
从哪儿来的是不是从tv1来的,消息是什么是不是lijielamp
 
再来看咱们的第一个客户端,他是不是同样会弹出一个啊,
也就是说,我从发布者那里,
发布的一些东西,都会干嘛,都会在咱们的客户端立即弹出来,
他是时刻监听着啊,好
我再publish 一下
publish tv2 lampbrother
(integer)1
 
刚才的咱们的tv1返回2吧,代表有两个人在监听,tv1
那么tv2呢,返回一个1代表只有一个人监听tv2吧,
咱们是不是第二个客户端,在坚挺着tv1跟tv2吧,
但是第一个客户端呢,是不是只监听着tv1
我刚才是不是对tv2发布了一个消息,但是在这儿是不是没有看到啊,
因为在第一个客户端这儿我只坚挺了tv1吧,
那么在第二个客户端这里
 
是因为tv1和tv2第二个客户端他同时监视着呢,
 
这是咱们的pub_sub消息订阅与发布啊,
 
这个做起来还是比较简单的啊,
这个可以做咱们的消息系统啊,
也可以做网页上面的web聊天系统吧,比较简单啊,
 
最后一块呢,咱们来看咱们虚拟内存的使用,
Redis的虚拟内存和咱们操作系统的虚拟内存不是一回事儿,
但是思路跟咱们的目的都是相同的,
就是暂时把不经常访问的数据从内存交换到磁盘当中,
是不是也是做交换的啊, 
咱们的虚拟内存本身就是与内存交换数据的吧,
从而腾出咱们宝贵的内存空间用于其他需要访问的数据。
尤其是对redis这样的内存数据库,内存终是不够用的,
因为你不停的把数据往内存里面放吧,
所以说内存总是不够用的,
除了可以将数据分割到多个redis server外,
另外能够提高数据容量的办法就是使用虚拟内存把那些
不经常访问的数据交换到磁盘上,
来看怎么配置啊,
通过一个配置,也是打开咱们的配置文件,
下面是vm相关的配置: