Redis进阶使用

  • Pipelining
  • Pub/Sub
  • 事物
  • WATCH
  • MULTI
  • EXEC
  • DISCARD
  • module
  • 过期淘汰策略
  • redis作为数据库与缓存的区别


Redis的底层IO使用epoll模型并且是单线程,从而速度非常的快。

Pipelining

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。

这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。

在redis中,如果要执行大量的命令,可以通过管道技术,使用socket可以运行redis命令。

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。

这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多POP3协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

Redis很早就支持管道(pipelining)技术,因此无论你运行的是什么版本,你都可以使用管道(pipelining)操作Redis。

echo -e "set k2 99\nincr k2\n get k2" | nc 127.0.0.1 6379

运行结果:

[root@basic ~]# echo -e "flushdb\nset k2 99\nincr k2\n get k2" | nc 127.0.0.1 6379
+OK
+OK
:100
$3
100
[root@basic ~]# redis-cli 
127.0.0.1:6379> keys *
1) "k2"
127.0.0.1:6379> get k2
"100"
127.0.0.1:6379>

也可以将echo替换成符合规则的文件发送给redis。

cat redis.txt | nc 127.0.0.1 6379

这样节省了大量的时间并提高了效率。

Pub/Sub

发布订阅

消费端开启监听后,才能收到生产端推送的消息,接收到消息会实时存在。

需要注意的是,消费者先于生产者监听,消费者不在线的时候生产者发送的消息将收不到。

客户端一:使用 PUBLISH

127.0.0.1:6379> PUBLISH cha hallo
(integer) 0
127.0.0.1:6379> PUBLISH cha hallo
(integer) 1
127.0.0.1:6379>

客户端二:使用 SUBSCRIBE

[root@basic ~]# redis-cli 
127.0.0.1:6379> SUBSCRIBE cha
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "cha"
3) (integer) 1
1) "message"
2) "cha"
3) "hallo"

事物

提到redis 的事物,就会引出一个问题:为什么 Redis 不支持回滚(roll back)?

如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR

WATCH

WATCH

WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC

这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。

MULTI

MULTI

EXEC

MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC

因为是单线程的,哪个客户端先执行 exec

DISCARD

调用 DISCARD

127.0.0.1:6379> set k1 1
OK
127.0.0.1:6379> WATCH k1 
OK
127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379> INCRBY k1 3
QUEUED
127.0.0.1:6379> INCR k1 
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 4
2) (integer) 5
3) "5"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k1
"5"
127.0.0.1:6379>

module

redis作为高速的缓存服务器,也可以挂载一些模块,这个时候就的去redis的官网 了,官网中向我们展示了较为优秀的redis module。

以布隆过滤器为例,将bitmap交给redis维护,非常快,减少客户端的实现,实现计算向数据移动。

RedisBloom的github地址

下载项目并且解压,编译后会出现一个redisbloom.so 可执行文件。

wget https://github.com/RedisBloom/RedisBloom/archive/master.zip
tar -xf RedisBloom-master.zip
cd RedisBloom-master
make

使用命令挂载启动,路径替换成redisbloom.so的绝对路径

redis-server --loadmodule /path/to/redisbloom.so

连接redis使用布隆过滤器。

[root@basic ~]# redis-cli 
127.0.0.1:6379> bf.add name lilei
(integer) 1
127.0.0.1:6379> bf.add name hanmeimei
(integer) 1
127.0.0.1:6379> BF.EXISTS name lilei
(integer) 1
127.0.0.1:6379> BF.EXISTS name hanmeimei
(integer) 1
127.0.0.1:6379> BF.EXISTS name guodegang
(integer) 0
127.0.0.1:6379>

返回值为1,表示lilei可能存在集合中,因为布隆过滤器可能会产生误判。

如果返回值为0,标识guodegang绝对不在集合中。

过期淘汰策略

设置过期时间有三种方式

ex

expire

expireat

回收策略:

redis最大使用多少内存建议 1G-10G

当maxmemory限制达到的时候,Redis会使用由 Redis的maxmemory-policy配置指令来进行淘汰。

以下的策略是可用的:

  • noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  • allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  • volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  • allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  • volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  • volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

以下为5.0以后新增的策略

  • allkeys-lfu: 尝试回收最多使用的键(LFU),使得新添加的数据有空间存放。
  • volatile-lfu: 尝试回收最多使用的键(LFU),但仅限于在过期集合的键,使得新添加的数据有空间存放。

如果当做数据库使用要用 noeviction, 数据是不能丢的

如果当做缓存使用的话,random随机不可取,volatile-ttl时间复杂度高,不建议用。如果大量的key使用了Expire,要用 volatile-lru, 反之要用 allkey-lru, 因为大量使用了expire淘汰的数量要受于expire的限制

内部机制:

  • 被动访问淘汰
    访问一个key时,如果存在,先判断是否过期,如果过期了,直接淘汰掉,返回nil。
  • 周期判断,默认10秒一次
  1. 测试随机的20个keys进行相关过期检测。
  2. 删除所有已经过期的keys。
  3. 如果有多于25%的keys过期,重复步骤1.

稍微牺牲内存,提高效率,利用空间换时间的概念。

过期时间这里有两点需要特别注意:

  1. key不会随着访问的次数延长时间。
  2. 如果对设置过期时间的key做写操作,会剔除过期时间。

redis作为数据库与缓存的区别

  1. 缓存不是全量数据。
  2. 在业务运转的过程中,随着访问变化,缓存应该淘汰冷数据。
  3. 在业务运转的过程中,随着访问变化,缓存应该保留热点数据。