Redis使用

2020/10/01 Redis 共 8072 字,约 24 分钟

Redis使用

安装

  • 下载源码
  • make编译

使用

Redis命令

  • info : 监控Redis命中次数和未命中次数

管道

Redis的发布订阅

Redis LUA脚本

http://www.redis.cn/commands/eval.html

相关命令

  • eval
  • evalsha
  • script debug
  • script exist
  • script flush
  • script kill
  • script load

使用示例

  • set 'foo' 'bar' ==> eval "return redis.call('set',KEYS[1],'bar')" 1 foo

  • redis.call 和 redis.pcall : 基本相同,不同是的是pcall会将错误转换为lua形式返回

LUA脚本调试

内存优化

小的聚合类型数据的特殊编码处理

参见Redis配置

使用32位的redis

  • 对于每一个key,将使用更少的内存,因为32位程序,指针占用的字节数更少
  • 32的redis整个实例使用的内存将被限制在4G以下
  • 使用make 32bit命令编译生成32位的redis
  • RDB和AOF文件是不区分32位和64位的(包括字节顺序),所以你可以使用64位的reidis恢复32位的RDB备份文件,相反亦然

位级别和字级别的操作

尽可能使用散列表(hashes)

  • 小散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面
  • 比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面

内存分配

  • 当某些缓存被删除后Redis并不是总是立即将内存归还给操作系统。这并不是redis所特有的,而是函数malloc()的特性。例如你缓存了5G的数据,然后删除了2G数据,从操作系统看,redis可能仍然占用了5G的内存(这个内存叫RSS,后面会用到这个概念),即使redis已经明确声明只使用了3G的空间。这是因为redis使用的底层内存分配器不会这么简单的就把内存归还给操作系统,可能是因为已经删除的key和没有删除的key在同一个页面(page),这样就不能把完整的一页归还给操作系统.
  • 上面的一点意味着,你应该基于你可能会用到的 最大内存 来指定redis的最大内存。如果你的程序时不时的需要10G内存,即便在大多数情况是使用5G内存,你也需要指定最大内存为10G.
  • 内存分配器是智能的,可以复用用户已经释放的内存。所以当使用的内存从5G降低到3G时,你可以重新添加更多的key,而不需要再向操作系统申请内存。分配器将复用之前已经释放的2G内存.
  • 因为这些,当redis的peak内存非常高于平时的内存使用时,碎片所占可用内存的比例就会波动很大。当前使用的内存除以实际使用的物理内存(RSS)就是fragmentation;因为RSS就是peak memory,所以当大部分key被释放的时候,此时内存的mem_used / RSS就比较高.
  • 如果 maxmemory 没有设置,redis就会一直向OS申请内存,直到OS的所有内存都被使用完。所以通常建议设置上redis的内存限制
  • 设置了maxmemory后,当redis的内存达到内存限制后,再向redis发送写指令,会返回一个内存耗尽的错误。错误通常会触发一个应用程序错误,但是不会导致整台机器宕掉.

过期

基本概念

  • 设置key的过期时间,超过时间后,将会自动删除该key
  • 超时后只有对key执行DEL命令或者SET命令或[GETSET等对key进行操作时key才会清除
  • 使用PERSIST命令可以清除超时,使其变成一个永久的key
  • 如果keyRENAME命令修改,相关的超时时间会转移到新key上面

Keys的过期时间

  • 通常Redis keys创建时没有设置相关过期时间。他们会一直存在,除非使用显示的命令移除,例如,使用DEL命令。
  • EXPIRE一类命令能关联到一个有额外内存开销的key。当key执行过期操作时,Redis会确保按照规定时间删除他们。
  • key的过期时间和永久有效性可以通过EXPIREPERSIST命令(或者其他相关命令)来进行更新或者删除过期时间。

过期精度

  • 在 Redis 2.4 及以前版本,过期期时间可能不是十分准确,有0-1秒的误差。
  • 从 Redis 2.6 起,过期时间误差缩小到0-1毫秒。

过期和持久

  • Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的。
  • 要想过期的工作处理好,计算机必须采用稳定的时间。 如果你将RDB文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的 keys装载时就会过期)。
  • 即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。

Redis如何淘汰过期的keys

  • Redis keys过期有两种方式:被动和主动方式。
  • 当一些客户端尝试访问它时,key会被发现并主动的过期。
  • 当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。
  • 具体就是Redis每秒10次做的事情:
    • 测试随机的20个keys进行相关过期检测。
    • 删除所有已经过期的keys。
    • 如果有多于25%的keys过期,重复步奏1.

在复制AOF文件时如何处理过期

  • 为了获得正确的行为而不牺牲一致性,当一个key过期,DEL将会随着AOF文字一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。
  • 当slaves连接到master时,不会独立过期keys(会等到master执行DEL命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master。

把Redis当做LRU算法来使用

Maxmemory配置指令

  • maxmemory 100mb

回收策略

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

回收进程如何工作

  • 一个客户端运行了新的命令,添加了新的数据。
  • Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
  • 一个新的命令被执行,等等。
  • 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。

近似LRU算法

  • Redis的LRU算法并非完整的实现。这意味着Redis并没办法选择最佳候选来进行回收,也就是最久未被访问的键。相反它会尝试运行一个近似LRU的算法,通过对少量keys进行取样,然后回收其中一个最好的key(被访问时间较早的)。
  • Redis 3.0算法已经改进为回收键的候选池子。这改善了算法的性能,使得更加近似真是的LRU算法的行为。
  • Redis LRU有个很重要的点,你通过调整每次回收时检查的采样数量,以实现调整算法的精度。这个参数可以通过以下的配置指令调整: maxmemory-samples 5

Redis事务

事务相关命令

  • MULTI:开始事务
  • EXEC:执行命令
  • DISCARD:抛弃事务
  • WATCH:监控key是否被修改
  • UNWATCH:撤销监控

事务中的错误

错误场景
  • 由于语法错误导致的入队错误
  • 由于错误的操作了key导致的执行时的错误
处理方案
  • 2.6.5之前:
    • 客户端将检查入队命令返回不是QUEUED,不是的化客户端将放弃该事务
  • 2.6.5之后:
    • Redis记录入队失败的命令,并在客户端调用exec时自动抛弃
其他实现事务的方式
  • LUA脚本

大量插入数据

netcat

  • 数据

    SET Key0 Value0
    SET Key1 Value1
    ...
    SET KeyN ValueN
    
  • 命令:(cat data.txt; sleep 10) | nc localhost 6379 > /dev/null

pipe mode

  • 数据:同上
  • 命令:cat data.txt | redis-cli --pipe

生成Redis协议

从文件中批量插入数据

步骤

  • 创建文本文件,如:
    set myk12 v1
    zadd zset12 0 a 1 b 3 c
    sadd sset12 e f g hh
    set myk22 v2
    hset myset12 k1 v1
    hmset myset22 k2 v2 k3 v3 k4 v4
    set myk32 v3
    
  • 转码:换行符转为 \r\n,Redis只支持这种形式,
    • Linux执行命令:unix2dos d1.txt
    • yum install unix2dos 在CentOS 7下安装unix2dos
  • 执行导入:
    • cat d1.txt | redis-cli
    • cat d1.txt | redis-cli --pipe
    • cat d1.txt | redis-cli -p 6380 -h 192.168.1.166 --pipe

分区

分区的目的

  • 让Redis管理更大的内存,Redis将可以使用所有机器的内存。如果没有分区,你最多只能使用一台机器的内存。
  • 使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。

分区基本概念

  • 范围分区
  • 哈希分区

不同的分区实现方案

  • 客户端分区:就是在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取。大多数客户端已经实现了客户端分区。
  • 代理分区:意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些Redis实例,然后根据Redis的响应结果返回给客户端。redis和memcached的一种代理实现就是Twemproxy
  • 查询路由:客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求从一个redis节点转发到另一个redis节点,而是在客户端的帮助下直接redirected到正确的redis节点。

分区的缺点

  • 涉及多个key的操作通常不会被支持。例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用交集指令)。
  • 同时操作多个key,则不能使用Redis事务.
  • 分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set).
  • 当使用分区的时候,数据处理会非常复杂,例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件。
  • 分区时动态扩容或缩容可能非常复杂。Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题。

持久化数据还是缓存

  • 如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容
  • 如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样 - Redis 集群已经可用 2015.4.1.

预分片

  • 概念:提前部署多个Redis实例,待以后需要新增实例时直接迁移过去,以避免重新K-V重新分片
  • 复制Redis实例流程:
    • 在你新服务器启动一个空Redis实例。
    • 把新Redis实例配置为原实例的slave节点
    • 停止你的客户端
    • 更新你客户端配置,以便启用新的redis实例(更新IP)。
    • 在新Redis实例中执行SLAVEOF NO ONE命令
    • (更新配置后)重启你的客户端
    • 停止你原服务器的Redis实例

Redis分区实现

  • Redis 集群:查询路由和客户端分区的混合实现
  • Twemproxy:
  • 支持一致性哈希的客户端:

一致性哈希算法

概念
  • 设置若干个哈希槽
  • 使用服务的ip的哈希值对哈希槽的数量取模,得到节点负责的哈希槽范围
  • key将根据其在环上的哈希值和哈希槽数量的取模映射到对应的节点上
扩展容错
  • 新增节点时,仅新增节点右边最近节点的数据需要重新映射

分布式锁

单实例(物理或逻辑上)下的分布式锁

示例

SET resource_name my_random_value NX PX 30000

思路
  • NX:不存在时set,解决互斥问题
  • PX:设置过期时间,防止死锁
  • my_random_value:value为随机且唯一的值,防止被其他客户端错误解锁

Redlock算法(多个独立的单实例下分布式锁)

流程
  1. 获取当前Unix时间,以毫秒为单位。
  2. 依次尝试从N个实例,使用相同的key和随机值获取锁。当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
  3. 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
  4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  5. 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。
思路
  • 遍历N个实例加锁,大于N/2+1个加锁成功则获取锁成功,若失败要遍历解锁并重试
  • 单节点获取锁成功需要满足,锁的有效时间大于0(过期时间 - 获取锁时间 > 0)
  • 客户端设置超时时间,避免客户端在Redis挂掉等极端情况仍死等响应

key事件通知

功能概述

允许客户端订阅发布/订阅频道,以便以某种方式接收影响Redis数据集的事件。可能接收的事件示例如下:

  • 所有影响给定键的命令。
  • 所有接收LPUSH操作的键。
  • 所有在数据库0中到期的键。

事件类型

  • 键空间通知
  • 键事件通知
示例

在数据库0中名为mykey的键上执行DEL操作,将触发两条消息的传递,完全等同于下面两个PUBLISH命令:

  • PUBLISH __keyspace@0__:mykey del键空间通知收到的消息是操作的名称
  • PUBLISH __keyevent@0__:del mykey键事件通知收到的消息是键的名称

配置

  • 默认关闭
  • notify-keyspace-events ""

不同的命令生成的事件

  • DEL命令为每一个删除的key生成一个del事件。
  • RENAME生成两个事件,一个是为源key生成的rename_from事件,一个是为目标key生成的rename_to事件。
  • EXPIRE在给一个键设置有效期时,会生成一个expire事件,或者每当设置有效期导致键被删除时,生成expired事件(请查阅EXPIRE文档以获取更多信息)。
  • SORT会在使用STORE选项将结果存储到新键时,生成一个sortstore事件。如果结果列表为空,且使用了STORE选项,并且已经存在具有该名称的键时,那个键将被删除,因此在这种场景下会生成一个del事件。
  • SET以及所有其变种(SETEXSETNXGETSET)生成set事件。但是SETEX还会生成一个expire事件。
  • MSET为每一个key生成一个set事件。
  • SETRANGE生成一个setrange事件。
  • INCRDECRINCRBYDECRBY命令都生成incrby事件。
  • INCRBYFLOAT生成一个incrbyfloat事件。
  • APPEND生成一个append事件。
  • LPUSHLPUSHX生成一个lpush事件,即使在可变参数情况下也是如此。
  • RPUSHRPUSHX生成一个rpush事件,即使在可变参数情况下也是如此。
  • RPOP生成rpop事件。此外,如果键由于列表中的最后一个元素弹出而被删除,则会生成一个del事件。
  • LPOP生成lpop事件。此外,如果键由于列表中的最后一个元素弹出而被删除,则会生成一个del事件。
  • LINSERT生成一个linsert事件。
  • LSET生成一个lset事件。
  • LTRIM生成ltrim事件,此外,如果结果列表为空或者键被移除,将会生成一个del事件。
  • RPOPLPUSHBRPOPLPUSH生成rpop事件和lpush事件。这两种情况下,顺序都将得到保证(lpush事件将总是在rpop事件之后传递)。此外,如果结果列表长度为零且键被删除,则会生成一个del事件。
  • HSETHSETNX以及HMSET都生成一个hset事件。
  • HINCRBY生成一个hincrby事件。
  • HINCRBYFLOAT生成一个hincrbyfloat事件。
  • HDEL生成一个hdel事件,此外,如果结果哈希集为空或者键被移除,将生成一个del事件。
  • SADD生成一个sadd事件,即使在可变参数情况下也是如此。
  • SREM生成一个srem事件,此外,如果结果集合为空或者键被移除,将生成一个del事件。
  • SMOVE为每一个源key生成一个srem事件,以及为每一个目标key生成一个sadd事件。
  • SPOP生成一个spop事件,此外,如果结果集合为空或者键被移除,将生成一个del事件。
  • SINTERSTORESUNIONSTORESDIFFSTORE分别生成sinterstoresunionostoresdiffstore事件。在特殊情况下,结果集是空的,并且存储结果的键已经存在,因为删除了键,所以会生成del事件。
  • ZINCR生成一个zincr事件。
  • ZADD生成一个zadd事件,即使添加了多个元素。
  • ZREM生成一个zrem事件,即使删除了多个元素。当结果有序集合为空且生成了键,则会生成额外的del事件。
  • ZREMBYSCORE生成一个zrembyscore事件。当结果有序集合为空且生成了键,则会生成额外的del事件。
  • ZREMBYRANK生成一个zrembyrank事件。当结果有序集合为空且生成了键,则会生成额外的del事件。
  • ZINTERSTOREZUNIONSTORE分别生成zinterstorezunionstore事件。在特殊情况下,结果有序集合是空的,并且存储结果的键已经存在,因为删除了键,所以会生成del事件。
  • 每次一个拥有过期时间的键由于过期而从数据集中移除时,将生成一个expired事件。
  • 每次一个键由于maxmemory策略而被从数据集中驱逐,以便释放内存时,将生成一个evicted事件。
注意

重要 所有命令仅在真正修改目标键时才生成事件。例如,使用SREM命令从集合中删除一个不存在的元素将不会改变键的值,因此不会生成任何事件。

过期事件的时间安排

expired事件是在Redis服务器删除键的时候生成的,而不是在理论上生存时间达到零值时生成的

创建二级索引

文档信息

Search

    Table of Contents