redis梳理
remote dictionary server远程字典服务器
C语言开发的
TCP协议读写
数据都存储在内存中,可达到10万个键值读写每秒。
支持持久化。
还可以限定数据占用的最大内存空间,数据量达到空间限制后会按一定的规则自动淘汰不需要的键。
除了缓存,还可以利用列表类型键实现队列,并支持阻塞读取。还有“发布/订阅”的消息模式,做分布式锁。
https://gist.github.com/348262 这是一个用redis实现的聊天室
单线程,对手Memcached支持多线程,但redis性能已经足够优异。
redis的优势是支持字符串以外的其他数据类型,还支持持久化。
redis快的原因
纯内存操作
单线程操作,避免了频繁的上下文切换
采用了非阻塞I/O多路复用机制
redis线程模型:
redis-client在操作的时候,会产生具有不同事件类型的socket。在服务端,有一段I/0多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
服务环境
轻量级,一个空redis实例内存只有1MB左右。
第一个小数点后的数字是偶数的版本是稳定版
redis-benchmark 是redis性能测试工具
redis-check-aof 是AOF文件修复工具
redis-check-dump 是RDB文件检查工具
生产环境推荐使用脚本启动redis,使得redis能随系统自动运行。
将redis源代码目录utils里的redis_init_script脚本,拷贝到/etc/init.d目录中,重命名文件为redis_6379。
新建/etc/redis目录存放配置文件(6379.conf),新建/var/redis/6379文件夹存放redis持久化文件。
shutdown停止服务时,redis收到SHUTDOWN命令后会先断开所有客户端连接,然后根据配置执行持久化,最后完成退出。
使用kill
也可以正常停止redis服务。
数据库
0-15一个16个库,默认使用第0号库。
redis不支持自定义数据库名,也不支持为每个库设置不同的密码。
数据库之间也不是完全隔离,FLUSHALL
命令可以清空一个redis实例中所有库的数据。
所以这些数据库更像是一种命名空间,不适合存储不同应用程序的数据,可以存储不同环境如测试、生产环境的数据。
键值支持的数据类型:
String
value可以是String也可以是数字。一般做一些复杂的计数功能的缓存,还可以存储json化的对象,甚至是图片(二进制数据),最大容量是512MB(看具体版本)。
hash
注意不能嵌套其他的数据类型(其他的也是),字段值只能是字符串。
一般存放的是结构化的对象,使用对象类型:对象id
构成键名,使用字段表示对象属性,字段值存放属性值。比较方便的操作其中的某个字段。不然使用多个string类型存储一个对象的不同属性会不好管理。
也可以存映射关系,如搭建博客网站时用散列类型存储文章缩略名slug和id之间的映射关系。
在做单点登录的时候,用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。
list
列表类型可以存储一个有序(指的是插入顺序)的字符串列表。
内部是双向链表。获取头部或尾部的topn会比较快。插入数据的速度也不会受已有数据数量的影响。
可以做简单的消息队列的功能。不同方向插入、弹出即可。
另外可以用列表类型存储id列表,利用分片获取命令,做基于redis的分页功能。
set
因为set堆放的是一堆不重复值的集合。内部是使用值为空的散列表实现的。最方便的是可以进行交集、并集、差集等运算。
可以存储标签。可以利用集合类型优势的场景。
sorted set
sorted set是使用散列表和跳跃表实现的。与普通集合相比,多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。
redis-cli操作
大小写不敏感 $ redis-cli PING
如果连接正常会受到PONG的回复,用来测试客户端与redis的连接是否正常(也可以直接> PING
)。 $ redis-cli -h 127.0.0.1 -p 6379
进入交互模式 > flushdb
清空当前数据库。 > flushall
清空所有数据库。> select [index]
选择索引数据库,index为索引值名,如:select 1
。> quit
退出客户端连接。
键值获取
查看当前库有哪些键KEYS (pattern)
:
支持*
、?
、[]
、\x
(表示匹配字符x,如匹配问好,就 KEYS \?
)
需要遍历所有键,当数据量多时,不建议使用。
EXISTS (键名)
判断一个键是否存在,存在则返回1,否则0。 DEL (键名,多个键空格隔开)
删除键,返回值是删除的个数。
可以通过管道实现通配删除:redis-cli KEYS "user:*" | xargs redis-cli DEL
删除所有以"user:"开头的键。或者redis-cli DEL 'redis-cli KEYS "user:*"'
TYPE (键名)
获得键值的数据类型。
STRLEN (键名)
获得键值的长度(中文的话,计算的是utf-8编码后的长度)。不存在,返回0。
键值设置
========string类型值SET (键名) (值)
赋字符串类型值。项目中键的命名有个建议是对象类型:对象id:对象属性
GET (键名)
当键不存在时会返回空。
MSET (键名) (值)
可以设置多个键值。MGET (键名)
当键不存在时会返回空。
虽然没有整数类型,但也提供了用于整数操作的命令,如INCR
:
常用于统计文章访问量等。
INCRBY (键名) (步长)
递增非1的情况使用。如果要递增浮点数,使用INCRBYFLOAT
。
同样,INCR
都可以换为DECR
实现递减。
APPEND (键名) (带双引号的字符串)
实现向键值末尾追加。若键不存在,则相当于在null里追加。
========hash类型值
HSET 键名 字段名 字段值
用来给hash类型数据赋值即插入(返回1),也可能是更新(返回0)。HSETNX 键名 字段名 字段值
与上面的区别是如果字段已经存在,不会执行返回0。HGET 键名 字段名
HMSET 键名 (字段名1 字段值1) (字段名2 字段值2) (字段名3 字段值3)...
HMGET 键名 (字段名1) (字段名2) (字段名3)
同时获取多个字段值HGETALL 键名
获取所有键值HEXISTS 键名 字段名
判断一个字段是否存在,存在则返回1,否则(以及键不存在时)返回0。
HINCRBY 键名 字段名 步长
使指定字段的字段值增加指定的整数。不存在的键会自动被建立。
HDEL 键名 字段名1 2 3
可以删除一个或多个字段,返回删除字段的个数。
HKEYS 键名
或 HVALS 键名
分别获取该键的所有字段名和所有字段值。 HLEN 键名
获得该键名的字段数。
========list类型值LPUSH 键名 值1 值2...
从左侧插入数据,RPUSH
表示从右侧插入。返回该键的元素数量。 LPOP 键名
从列表左侧弹出一个元素。RPOP
表示从右侧弹出。LLEN 键名
返回列表中元素的个数。不存在返回0。LRANGE 键名 起始索引 结尾索引
从左往右,获取列表分片,起始从0开始,而且包含两端的元素。索引为负值,如-1表示右边的第一个元素。 LRANGE 键名 0 -1
可以获取列表中所有元素,另外若”起始索引“位置比”结尾索引“位置靠后,会返回空列表;”结尾索引“大于实际索引范围则会返回到列表最右边的元素为止。 LTRIM 键名 起始索引 结尾索引
删除指定索引范围外的所有元素。
LREM 键名 个数 值
当”个数“>0时,会删除从左边数前"个数"个值为"值"的元素; 当”个数“=0时,会删除所有值为"值"的元素;当”个数“<0时,会删除从右边数前"个数"的绝对值个值为"值"的元素。返回实际删除的元素个数。
LINDEX 键名 索引
获取指定索引(从0开始,负数表示从右边开始计算最右是-1)的元素值。LSET 键名 索引 值
设置指定索引的元素值。LINSERT 键名 BEFORE|AFTER pivot value
从左到右找到值为pivot的元素,然后根据第二个参数BEFORE|AFTER将value插入到该值的前或后面。返回最后列表的元素的个数。
RPOPLPUSH source destination
从源列表RPOP一个元素,然后LPUSH到目标列表。返回这个元素的值。相当于一个队列操作。源和目标列表可以是同一个列表。
========set类型值SADD 键名 元素1 元素2...
增添一个或多个元素,键不存在自动创建。返回成功加入元素的个数。SREM 键名 元素1 元素2...
删除一个或多个元素,返回删除成功的个数。 SPOP 键名
从集合中随机弹出一个元素。
SMEMBERS 键名
返回集合中所有元素。SRANDMEMBER 键名
随机从集合中获取一个元素。后面也可以加数字表示返回该数字个元素,分正、负、以及大于集合数的情况。
ps:这种“随机”对每一个元素并不是平等的。首先是随机选一个桶,然后在桶里随机选元素。没有哈希冲突的元素被选中的几率要大于有哈希冲突的。
SCARD 键名
获取集合中元素个数。SISMEMBER 键名 元素
判断元素是否在集合中。存在返回1,不存在或键不存在时返回0。
SDIFF 键1 键2 键3...
多集合的差集运算。使用SDIFFSTORE 键0 键1 键2 ...
将结果集存储在键0中。SINTER 键1 键2 键3...
多集合的交集运算。SINTERSTORE、SUNIONSTORE
类似。SUNION 键1 键2 键3...
多集合的并集运算。
========sorted set类型值ZADD 键名 score1 元素1 score2 元素2 ...
增加元素,返回新增元素个数(不包括重复覆盖的)。 ZSCORE 键名 元素
获取元素的score。ZRANGE 键名 start stop
返回按score从小到大排序后在指定索引区间里(包含两端) 的元素。加上WITHSCORES
也会返回元素的score。另外从大到小,使用ZREVRANGE
。
ZRANGEBYSCORE 键名 minScore maxScore
返回指定score范围里的元素。也可带WITHSCORES
,以及LIMIT offset count
,还可以指定不包含端点值,还可以使用-inf、+inf
表示负无穷、正无穷。同样也有ZREVRANGEBYSCORE
,先maxScore再minScore。
ZINCRBY 键名 increment 元素
增加某个元素的score,返回最后该元素的分数。元素不存在时,会初始化为0再加。
ZCARD、ZCOUNT、ZREM、ZREMRANGEBYRANK、ZREMRANGEBYSCORE、ZRANK
以及一些集合运算命令
排序
redis提供了对列表、集合、有序集合(忽略score)类型键的排序命令SORT
。
可以对数字排序,还有指定ALPHA
参数实现字典排序。 DESC
实现倒序。 LIMIT offset count
实现返回值的指定范围(跳过前offset个元素,获取count个元素)。
BY
参数后面加参考键(字符串类型或hash类型键的某个字段键名->字段名
),命令将对“每个元素的值替换参考键中第一个(可以是键名带,可以是字段名带*)并获取其值后的值”排序。
若得到的值相同,则会再比较元素本身的值再排序。
当参考键不带*时(常量键名,与元素值无关),或改参考键不存在时,命令不会执行排序操作。
配置
CONFIG SET ...
命令可以在不重启服务的情况下动态修改redis配置,如日志级别loglevel,是否开启持久化等,但也不支持所有的配置项。CONFIG GET ...
命令查看当前配置情况。
事务
redis先将属于一个事务的命令发送给服务器,然后再依次执行这些命令。 MULTI
命令告知服务器接下来的命令都属于同一个事物,后面的命令都会被加入等待执行的事务队列中。EXEC
命令告知服务器将执行事务队列中的所有命令,并保证不会被其他命令插入,当事务里所有命令都执行完后,返回每个命令的返回值。
但是1
redis事务没有回滚功能。若出现执行出错的情况只有自己手动恢复数据。
但是2
若出现某条命令需要先获取上一条命令返回值才能继续执行的情况时,由于redis事务中每个命令的返回值都是最后一起返回的,这种情况不能“将上一条命令”与该命令都加入到事务中,而是获取“上一条命令”返回值后再使用事务,并采用类似“锁”的监控机制,在执行事务前防止获取的返回值被改变造成脏读。 WATCH
命令可以监控一个或多个键,一旦其中有一个被改动,之后的事务便不会执行。监控一直持续到EXEC
命令。 也可以用UNWATCH
取消监控
若exec执行失败,再重新执行整个函数。
生存时间
EXPIRE
设置键的生存时间(Time To Live,TTL),单位秒,必须是整数。PEXPIRE
单位是毫秒。TTL
获取该键还有多久时间被删除,返回-1表示该键已不存在,或没有为该键设置生存时间。PTTL
以毫秒为单位返回剩余时间。 PERSIST
取消生存时间设置。使用SET、GETSET命令为键赋值时也会清除生存时间设置。其他如HSET、ZREM、LPUSH只对键值操作的命令不影响键的生存时间。
若WATCH
监控一个设置了生存时间的键,该键到期自然死亡后,WATCH并不会被认为该键被改变。
问题1
到期后的键都会自动被删除吗?
不一定,redis采用的是定期删除+惰性删除策略
。定期删除
:redis默认每隔100ms检查是否有过期的key,有过期key则删除。但redis不是每个100ms将所有的key检查一次,而是随机抽取
进行检查(如果每隔100ms,全部key进行检查,redis可能会卡死),因此,如果只采用定期删除策略,会导致很多key到时间没有删除。还需要惰性删除机制
,即在你获取某个key的时候,redis同时也会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除该键。
问题2
每个键设置多长时间的生存时间?
设置得太长,在内存有限的情况下可能会导致内存占满。
设置太短,可能导致缓存命中率低导致大量内存闲置。
实际开发过程中会比较难为缓存设置合理的生存时间,一般是通过限制redis最大内存使用量,并让redis按照一定的规则淘汰不需要的缓存键,这样来利用redis缓存系统。
配置文件redis.conf中maxmemory
参数来限制redis最大可用内存(单位字节)。 maxmemory-policy
指定超出内存后的删除规则,直到redis占用内存小于限制内存。有以下几种删除策略:
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory # is reached. You can select among five behaviors: # # volatile-lru -> remove the key with an expire set using an LRU algorithm # allkeys-lru -> remove any key according to the LRU algorithm # volatile-random -> remove a random key with an expire set # allkeys-random -> remove a random key, any key # volatile-ttl -> remove the key with the nearest expire time (minor TTL) # noeviction -> don't expire at all, just return an error on write operations
1)volatile-lru:即使用LRU算法删除键。当内存不足以容纳新写入数据时,在设置了过期时间的键空间中
,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐。
2)allkeys-lru:也是使用LRU算法删除键。当内存不足以容纳新写入数据时,在键空间中,直接移除最近最少使用的key。推荐使用。
3)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中
,随机移除某个key。依然不推荐。
4)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。
5)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐。
6)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。(默认的)
ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。