redis进阶

redis高级用法包括:
排序
事务
管道
发布/订阅
持久化
主从复制
虚拟内存

排序
可以对list和set和zset进行排序,以list为例子来讲:
既可以用list本身的按数字或者字母的升序、降序来排序,例如:

127.0.0.1:6379> rpush l 3 4 1 5 2
(integer) 5
127.0.0.1:6379> lrange l 0 5
1) "3"
2) "4"
3) "1"
4) "5"
5) "2"
127.0.0.1:6379> sort l
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379>
---------------------------------------------------------------------------
127.0.0.1:6379> lpush l xiaoh huo xingming beijing changping
(integer) 5
127.0.0.1:6379> lrange l 0 5
1) "changping"
2) "beijing"
3) "xingming"
4) "huo"
5) "xiaoh"
127.0.0.1:6379> sort l alpha
1) "beijing"
2) "changping"
3) "huo"
4) "xiaoh"
5) "xingming"
127.0.0.1:6379> sort l alpha desc
1) "xingming"
2) "xiaoh"
3) "huo"
4) "changping"
5) "beijing"
127.0.0.1:6379>

又可以自己自定义规则,根据自定义规则进行排序。但是redis里面的这个自定义规则看起来很奇怪,这个规则和list里面的元素是怎么建立联系的,好像并不是那么直观:

127.0.0.1:6379> lpush l e s g f a
(integer) 5
127.0.0.1:6379> lrange l 0 9
1) "a"
2) "f"
3) "g"
4) "s"
5) "e"
127.0.0.1:6379> sort l alpha
1) "a"
2) "e"
3) "f"
4) "g"
5) "s"
127.0.0.1:6379> set namea 4
OK
127.0.0.1:6379> set namee 5
OK
127.0.0.1:6379> set namef 3
OK
127.0.0.1:6379> set nameg 1
OK
127.0.0.1:6379> set names 2
OK
127.0.0.1:6379> sort l by name*
1) "g"
2) "s"
3) "f"
4) "a"
5) "e"
-----------------------------------------------------------
127.0.0.1:6379> set namea xiaoh
OK
127.0.0.1:6379> set namee me
OK
127.0.0.1:6379> set namef huo
OK
127.0.0.1:6379> set nameg blog
OK
127.0.0.1:6379> set names xingming
OK
127.0.0.1:6379> sort l by name* alpha
1) "g"
2) "f"
3) "e"
4) "a"
5) "s"

上面的方法返回的是原始的数据内容,如果想获得新的KEY对应的值(也就是进行排序的KEY)可以使用 GET PATTERN

127.0.0.1:6379> sort l by name* alpha get name*
1) "blog"
2) "huo"
3) "me"
4) "xiaoh"
5) "xingming"
127.0.0.1:6379> sort l by name* alpha get name* get #
 1) "blog"
 2) "g"
 3) "huo"
 4) "f"
 5) "me"
 6) "e"
 7) "xiaoh"
 8) "a"
 9) "xingming"
10) "s"
127.0.0.1:6379>

对redis里面排序的思考:如果我们有多个redis server的话,不同的key可能存在于不同的server中,比如name1,name2,name3可能就分别存储在不同的server中。这种情况对排序性能就会造成很大的影响。

redis作者在他的blog上提到了这个问题的解决办法,就是通过key tag将需要排序的key都放到同一个server上 。

再回来,具体决定哪个key存储在哪个server上,是由客户端的hash算法决定的。我们可以通过对key的一部分内容进行hash来确保要排序的key在同一台server上,比如name1,name2,name3这三个key,如果只对相同的部分"name"进行hash,则name1,name2和name3这三个key肯定就hash到同一台server上了,这样就可以在一台server上排序了。

举个例子假如我们的client如果发现key中包含[]。那么只对key中[]包含的内容进行hash。我们将四个name相关的key,都这样命名[name]1 [name]2 [name]3 [name]4 [name]5,于是client程序就会把他们都放到同一server上。

还有一个问题,如果要排序的set或者list很大,排序就需要消耗很长时间。由于redis是单线程的,所以长时间的排序操作会阻塞其他client的请求。解决办法是通过主从复制,把数据从主server复制到slave server上,只在slave server上做排序,主server就可以无阻的接收其他client的请求了。slave server上做的排序操作还可以缓存起来。另一个办法就是用sorted set来对需要按某个顺序排序的集合建立索引。

事务
redis对事务的支持比较简单,redis只能保证一个client发起的事务执行过程中,不会插入其他client的命令。
因为是单线程的,所以实现事务是很容易的。但是redis里面的事务有缺点。
事务是从multi开始,以exec结束。一般client提交请求,server会马上执行并返回结果给client。但是当client在一个连接中发出multi命令,这个连接会进入一个事务上下文环境,该连接后续的命令并不是立刻执行,而是放到一个队列中,当此连接接收到一个exec命令后,redis会顺序的执行队列中的那些命令,并将执行结果一起返回给client,然后此连接结束事务上下文。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr b
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 2
3) (integer) 1
127.0.0.1:6379>

redis事务的缺点:
第一个问题是redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。

127.0.0.1:6379> set b 1
OK
127.0.0.1:6379> set a e
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr a
QUEUED
127.0.0.1:6379> incr b
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) (integer) 2
127.0.0.1:6379>

可以发现,虽然执行中间有问题,但并没有回滚,其他命令还是执行了,而且第一条命令执行失败,并不影响后面命令的执行。
最后一个十分罕见的问题是,当事务的执行过程中,如果redis意外的挂了。很遗憾只有部分命令执行了,后面的也就被丢弃了。当然如果我们使用的append-only file方式持久化,redis会用单个write操作写入整个事务内容。即是是这种方式还是有可能只部分写入了事务到磁盘。发生部分写入事务的情况下,redis重启时会检测到这种情况,然后失败退出。可以使用redis-check-aof工具进行修复,修复会删除部分写入的事务内容。修复完后就能够重新启动了。

管道pipeline
redis是一个cs模式的tcp server,使用和http类似的请求响应协议。

一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。基本的通信过程如下

Client: INCR X
Server: 1
Client: INCR X
Server: 2
Client: INCR X
Server: 3
Client: INCR X
Server: 4

基本上四个命令需要8个tcp报文才能完成。由于通信会有网络延迟,假如从client和server之间的包传输时间需要0.125秒。那么上面的四个命令8个报文至少会需要1秒才能完成。这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令。这显示没有充分利用redis的处理能力。除了可以利用mget,mset之类的单条命令处理多个key的命令外我们还可以利用pipeline的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端。通信过程如下

Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4

假设不会因为tcp报文过长而被拆分。可能两个tcp报文就能完成四条命令,client可以将四个incr命令放到一个tcp报文一起发送,server则可以将四条命令的处理结果放到一个tcp报文返回。

通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试。

下面是我测试是否使用pipeline的代码:

from redis import Redis
import time

r = Redis()

def without_pipeline(times=100000):
    r.set('a', 0)
    for i in range(times):
        r.incr('a')

def use_pipeline(times=100000):
    r.set('a', 0)
    r.set('a', 0)
    pip = r.pipeline()
    for i in range(times):
        pip.incr('a')
    pip.execute()

start = time.time()
without_pipeline()
end = time.time()
print 'without pipeline spendtime:%f' % (end-start)

start = time.time()
use_pipeline()
end = time.time()
print 'use pipeline spendtime:%f' % (end-start)

结果为:

without pipeline spendtime:4.221240
use pipeline spendtime:1.572646

看起来对效果提升还是很明显的。

持久化
有快照和appendof两种方式,
快照是将数据全部写入到rdb文件中,appendof是将修改命令写入到aof文件中,所以aof文件会越来越大,redis提供了bgrewriteaof命令来整理aof文件。

主从复制(自己没用过主从复制,都是别人的经验)
Redis主从复制配置和使用都非常简单。通过主从复制可以允许多个slave server拥有和master server相同的数据库副本。

关于redis主从复制的一些特点

master可以有多个slave
除了多个slave连到相同的master外,slave也可以连接其他slave形成图状结构
主从复制不会阻塞master。也就是说当一个或多个slave与master进行初次同步数据时,master可以继续处理client发来的请求。相反slave在初次同步数据时则会阻塞不能处理client的请求。
主从复制可以用来提高系统的可伸缩性,我们可以用多个slave专门用于client的读请求,比如sort操作可以使用slave来处理。也可以用来做简单的数据冗余
可以在master禁用数据持久化,只需要注释掉master配置文件中的所有save配置,然后只在slave上配置数据持久化。

下面介绍下主从复制的过程

当设置好slave服务器后,slave会建立和master的连接,然后发送sync命令。

无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来。后台进程完成写文件后,master就发送文件给slave,slave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上。

接着master就会把缓存的命令转发给slave。而且后续master收到的写命令都会通过开始建立的连接发送给slave。

从master到slave的同步数据的命令和从client发送的命令使用相同的协议格式。当master和slave的连接断开时slave可以自动重新建立连接。

如果master同时收到多个slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave。

配置slave服务器很简单,只需要在配置文件中加入如下配置

slaveof 192.168.1.1 6379  #指定master的ip和端口

虚拟内存
首先说明下redis
的虚拟内存与os
的虚拟内存不是一码事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于redis
这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个redis server
外。另外的能够提高数据库容量的办法就是使用vm
把那些不经常访问的数据交换的磁盘上。

如果我们的存储的数据总是有少部分数据被经常访问,大部分数据很少被访问,对于网站来说确实总是只有少量用户经常活跃。当少量数据被经常访问时,使用vm不但能提高单台redis server
数据库的容量,而且也不会对性能造成太多影响。

redis
没有使用os
提供的虚拟内存机制而是自己在用户态实现了自己的虚拟内存机制,作者在自己的blog
专门解释了其中原因。http://antirez.com/post/redis-virtual-memory-story.html

主要的理由有两点

os
的虚拟内存是已4k页面为最小单位进行交换的。而redis
的大多数对象都远小于4k,所以一个os页面上可能有多个redis
对象。另外redis
的集合对象类型如list
,set
可能存在与多个os页面上。最终可能造成只有10%key被经常访问,但是所有os
页面都会被os
认为是活跃的,这样只有内存真正耗尽时os才会交换页面。

相比于os的交换方式。redis
可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息。一般压缩后的对象会比内存中的对象小10倍。这样redis
的vm会比os vm能少做很多io操作。

下面是vm相关配置

vm-enabled yes #开启vm功能

vm-swap-file /tmp/redis.swap #交换出来的value保存的文件路径/tmp/redis.swap

vm-max-memory 1000000 #redis使用的最大内存上限,超过上限后redis开始交换value到磁盘文件中。

vm-page-size 32 #每个页面的大小32个字节

vm-pages 134217728 #最多使用在文件中使用多少页面,交换文件的大小 = vm-page-size * vm-pages

vm-max-threads 4 #用于执行value对象换入换出的工作线程数量。0表示不使用工作线程(后面介绍)

redis
的vm
在设计上为了保证key
的查找速度,只会将value
交换到swap
文件中。所以如果是内存问题是由于太多value
很小的key
造成的,那么vm
并不能解决。

和os一样redis
也是按页面来交换对象的。redis
规定同一个页面只能保存一个对象。但是一个对象可以保存在多个页面中。

在redis
使用的内存没超过vm-max-memory
之前是不会交换任何value
的。当超过最大内存限制后,redis
会选择较老的对象。如果两个对象一样老会优先交换比较大的对象,精确的公式swappability = age*log(size_in_memory)

对于vm-page-size
的设置应该根据自己的应用将页面的大小设置为可以容纳大多数对象的大小。太大了会浪费磁盘空间,太小了会造成交换文件出现碎片。

对于交换文件中的每个页面,redis
会在内存中对应一个1bit
值来记录页面的空闲状态。所以像上面配置中页面数量(vm-pages 134217728
)会占用16M内存用来记录页面空闲状态。`

vm-max-threads表示用做交换任务的线程数量。如果大于0推荐设为服务器的
cpu core`的数量。如果是0则交换过程在主线程进行。

总结一下Redis的高级用法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1.1 资料 ,最好的入门小册子,可以先于一切文档之前看,免费。 作者Antirez的博客,Antirez维护的R...
    JefferyLcm阅读 17,200评论 1 51
  • NOSQL类型简介键值对:会使用到一个哈希表,表中有一个特定的键和一个指针指向特定的数据,如redis,volde...
    MicoCube阅读 9,491评论 2 27
  • 摘自http://xiaoh.me/2016/06/30/redis-advanced/ 排序 redis支持对l...
    鸵鸟要抬头阅读 66,705评论 1 3
  • 安全性 设置客户端连接后进行任何其他指令前需要使用的密码。 警告:因为redis 速度相当快,所以在一台比较好的服...
    OzanShareing阅读 5,835评论 1 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,797评论 19 139