3.事务处理
redis 对事务的支持目前还比较简单。 redis 只能保证一个 client 发起的事务中的命令可以连续的执行,而中间不会插入其他 client 的命令。 由于 redis 是单线
程来处理所有 client 的请求的所以做到这点是很容易的。一般情况下redis 在接受到一个 client 发来的命令后会立即处理并 返回处理结果,但是当一个client 在
一个连接中发出 multi 命令时,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到 exec 命令后
,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给 client.然后此连接就 结束事务上下文。
【例】
127.0.0.1:6379> get name "emperor"
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> multi OK #打开事务上下文 127.0.0.1:6379> set name dee QUEUED #不立即执行,把命令放入队列 127.0.0.1:6379> set age 27 QUEUED 127.0.0.1:6379> set name king QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) OK #按队列中命令的顺序开始执行并执行成功 127.0.0.1:6379> get name "king" 127.0.0.1:6379> get age "27"
取消一个事务
discard 命令用于取消事务时队列里面的所有命令。
【例】
127.0.0.1:6379> get name "king" 127.0.0.1:6379> multi OK 127.0.0.1:6379> set name emperor QUEUED 127.0.0.1:6379> set name dee QUEUED 127.0.0.1:6379> discard OK 127.0.0.1:6379> get name "king"
这次 2 个 set name 命令都没有被执行,discard 命令其实就是清空事务的命令队列并退出事务上下文,也就是常说的事务回滚。
Redis 事务的不成熟
在关系型数据库例如 MySQL 中,事务中只要有一条语句执行失败则整个事务都将回滚,而 Redis 的事务则是当事务队列中有执行失败的命令时,执行成功的命令依然会执行成功,整个事务不会回滚。
例如 incr name,name 的值是一个字符串类型的值,不能使用增加命令:
127.0.0.1:6379> incr name (error) ERR value is not an integer or out of range
在事务中:
127.0.0.1:6379> get name "king" 127.0.0.1:6379> get age "27" 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr age QUEUED 127.0.0.1:6379> incr name QUEUED 127.0.0.1:6379> exec 1) (integer) 28 2) (error) ERR value is not an integer or out of range 127.0.0.1:6379> get age "28" 127.0.0.1:6379> get name "king"
当 incr name 执行失败后,incr age 依然可以执行成功。
乐观锁(和版本控制器类似)
大多数是基于数据版本( version)的记录机制实现的。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据
库表添加一个“ version ” 字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加 1。此时,将提交数据的版本号与数据库表对应记录的
当前版本号进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
【Redis 乐观锁实例】
假设有一个 age 的 key,我们开 2 个 session 来对 age 进行赋值操作:
第 1 步 session 1
127.0.0.1:6379> get age "29" 127.0.0.1:6379> watch age OK 127.0.0.1:6379> multi OK
第 2 步 session 2
127.0.0.1:6379> set age 30 OK
127.0.0.1:6379> get age "30"
第 3 步 session 1
127.0.0.1:6379> set age 20 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get age "30"
watch 命令会监视给定的 key,当 exec 时候如果监视的 key 从调用 watch 后发生过变化,则整个事务会失败。也可以调用 watch 多次监视多个
key。这样就可以对指定的 key 加乐观锁了。注意 watch 的 key 是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了
exec,discard,unwatch 命令都会清除连接中的所有监视。
4.持久化机制
redis 是一个支持持久化的内存数据库,也就是说 redis 需要经常将内存中的数据同步到磁盘来保证持久化。 redis 支持两种持久化方式,一种是
Snapshotting(快照)也是默认方式(将数据存入文件),另一种是 Append-only file(缩写 aof)的方式(将操作存入文件)。
① Snapshotting 方式
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。可以通过配置设置自动做快照持久
化的方式。我们可以配置 redis在 n 秒内如果超过 m 个 key 被修改就自动做快照。
save 60 10000 #60 秒内超过 10000 个 key 被修改,则发起快照保存
快照路径:/usr/local/redis/bin/dump.rdb
【操作】
a. 打开配置文件:
[root@localhost bin]# vim /usr/local/redis/etc/redis.conf
b. 搜索 save:
默认开启了 3 种方式:
save 900 1
save 300 10
save 60 10000
② aof 方式
由于快照方式是在一定间隔时间做一次的,所以如果 redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,
可以采用 aof 持久化方式。下面介绍 Append-only file:
aof 比快照方式有更好的持久化性,是由于在使用 aof 持久化方式时,redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是
appendonly.aof)。当 redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于 os 会在内核中缓存 write 做的修改,
所以可能不是立即写到磁盘上。这样 aof 方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉 redis 我们想要通过 fsync 函数强制
os 写入到磁盘的时机。有三种方式如下(默认是:每秒 fsync 一次)
appendonly yes //启用 aof 持久化方式 # appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化 appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中 # appendfsync no //完全依赖 os,性能最好,持久化没保证
【操作】
a.打开配置文件:
[root@localhost bin]# vim /usr/local/redis/etc/redis.conf
b. 搜索 aof
c.默认 appendonly 是 no,改成 yes;
同步方式,默认的配置:
d. 保存退出;
e. 结束 redis 服务;
[root@localhost bin]# pkill redis-server
f. 开启 redis 服务,进入客户端;
[root@localhost bin]# /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
[root@localhost bin]# redis-cli -a phpdee
g. 测试:
127.0.0.1:6379> set name dee OK 127.0.0.1:6379> exit [root@localhost bin]# ll 总用量 13688 -rw-r--r--. 1 root root 55 6月 28 04:44 appendonly.aof -rw-r--r--. 1 root root 18 3月 18 08:09 dump.rdb -rwxrwxr-x. 1 root root 566 12月 16 2014 mkreleasehdr.sh -rwxr-xr-x. 1 root root 4169079 3月 17 06:40 redis-benchmark -rwxr-xr-x. 1 root root 16415 3月 17 06:40 redis-check-aof -rwxr-xr-x. 1 root root 37647 3月 17 06:40 redis-check-dump -rwxr-xr-x. 1 root root 4249851 3月 17 06:40 redis-cli -rwxr-xr-x. 1 root root 5519070 3月 17 06:40 redis-server
浏览该文件:
[root@localhost bin]# cat appendonly.aof *2 $6 SELECT $1 0 *3 $3 set $4 name $3 dee [root@localhost bin]#
5.发布与订阅信息
发布订阅(pub/sub)是一种消息通信模式,主要的目的是解耦消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似。 pub/sub 不
仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合。 redis 作为一个 pub/sub 的 server,在订阅者和发布者之间起到了消息路由的
功能。订阅者可以通过 subscribe 和 psubscribe 命令向 redis server 订阅自己感兴趣的消息类型, redis 将消息类型称为通道(channel)。当发布者通
过 publish 命令向 redis server 发送特定类型的消息时。订阅该消息类型的全部 client 都会收到此消息。这里消息的传递是多对多的。一个 client 可以订
阅多个 channel,也可以向多个 channel发送消息。
【例】(打开3个 session :打开3个客户端连接)
在第一个客户端(第 1 个 session)开启 redis 服务并进入 redis 客户端,开始监听 tv1 这个频道:
127.0.0.1:6379> subscribe tv1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "tv1" 3) (integer) 1
打开第二个客户端(第 2 个 session),开启 redis 服务并进入 redis 客户端,同时监听 tv1 和 tv2 两个频道:
127.0.0.1:6379> subscribe tv1 tv2 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "tv1" 3) (integer) 1 1) "subscribe" 2) "tv2" 3) (integer) 2
打开第三个客户端,开启 redis 服务并进入 redis 客户端,发布消息:
127.0.0.1:6379> publish tv1 deephper (integer) 2
返回 2 代表有 两个人正在监听 tv1
此时第 1 个 session 和 第 2 个 session 会收到 message:
1) "message" 2) "tv1" 3) "deephper"
此时第 3 个 session 继续发布 tv2:
返回 1 ,代表有 1 个人正在监听 tv2;同时 session 2 弹出信息:
注:可以实现消息系统和 web 聊天系统。
6. 虚拟内存的使用
redis 的虚拟内存与操作系统的虚拟内存不是一码事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存
空间用于其他需要访问的数据。尤其是对于 redis 这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个 redis server 外。另外的能够提高数据
库容量的办法就是使用虚拟内存把那些不经常访问的数据交换的磁盘上。
【配置】
#vm 相关配置 vm-enabled yes #开启 vm 功能 vm-swap-file /tmp/redis.swap #交换出来的 value 保存的文件路径 vm-max-memory 1000000 #redis 使用的最大内存上限 vm-page-size 32 #每个页面的大小 32 个字节(4k) vm-pages 134217728 #最多使用多少页面 vm-max-threads 4 #用于执行 value 对象换入换出的工作线程数量
代开配置文件:
[root@localhost ~]# vim /usr/local/redis/etc/redis.conf
搜索 vm(我的 redis 2.8.19 没有在 配置文件中看到 vm 配置);
no 改成 yes;
vm-max-memory 指定为 100000;
保存退出。
重启 redis 服务:
把 really-use-vm yes 添加进配置文件;
保存退出;
重启 redis 服务。
开始使用虚拟内存。