主要写下redis得一些命令,耗时,使用场景,注意点等等。
keys *
127.0.0.1:6379> keys *
1) "python"
2) "java"
3) "hello"
dbsize
127.0.0.1:6379> dbsize
(integer) 4
exists key
如果键存在则返回1,不存在则返回0。
del key [key ...]
返回结果为成功删除键的个数,假设删除一个不存在的键,就会返回
0。
expire key seconds
Redis支持对键添加过期时间,当超过过期时间后,会自动删除键。
ttl命令会返回键的剩余过期时间,它有3种返回值:
#还剩7秒
127.0.0.1:6379> ttl hello
(integer) 7 ...
#还剩1秒
127.0.0.1:6379> ttl hello
(integer) 1
#返回结果为-2,说明键hello已经被删除
127.0.0.1:6379> ttl hello
(integer) -2
127.0.0.1:6379> get hello
(nil)
type key
如果键不存在,则返回none。
对外,type命令会返回5种数据结构,它们分别是: string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集 合)。
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现, 这样Redis会在合适的场景选择合适的内部编码。
种内部编码实现可以在不同场景下发挥各自的优 势
例子:例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有 所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为 linkedlist。
缓存功能
例如:中Redis作为缓存层,MySQL作 为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
计数
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、
查询缓存的功能,同时数据可以异步落地到其他数据源。
共享Session
使用Redis将用户的Session进行集中管理,每次用户 更新或者查询登录信息都直接从Redis中集中获取。
限速
在进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用 户每分钟获取验证码的频率,例如一分钟不能超过5次。
此功能可以使用Redis来实现,下面的伪代码给出了基本实现思路:phoneNum = "138xxxxxxxx"; key = "shortMsg:limit:" + phoneNum; // SET key value EX 60 NX isExists = redis.set(key,1,"EX 60","NX"); if(isExists != null || redis.incr(key) <=5){ // 通过 }else{ // 限速 }
当作数据库表格使用理解,就是去模拟关系型复杂查询开发困难,维护成本高;一行一个哈希。
Redis3.2版本提供了quicklist内部编码,简单地说它是以一个ziplist为节 点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一 种更为优秀的内部编码实现,它的设计原理可以参考Redis的另一个作者 Matt Stancliff的博客:
https://matt.sh/redis-quicklist
。
消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产 者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令 阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用
性。
文章列表
1)每篇文章使用哈希结构存储,例如每篇文章有3个属性title、 timestamp、content。
2)向用户文章列表添加文章,user:{id}:articles作为用户文章列表的键。
3)分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章。
tip:使用列表类型保存和获取文章列表会存在两个问题。第一,如果每次分 页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑使用 Pipeline(第3章会介绍)批量获取,或者考虑将文章数据序列化为字符串类 型,使用mget批量获取。第二,分页获取文章列表时,lrange命令在列表两
端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此 时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现, 它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完
成。
开发提示
实际上列表的使用场景很多,在选择时可以参考以下口诀:
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
)当元素个数较少且都为整数时,内部编码为intset;当元素个数超过512个,内部编码变为hashtable;当某个元素不为整数时,内部编码也会变为hashtable。
集合类型比较典型的使用场景是标签(tag)。
它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。
但是它和列表使用索引下标作为 排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依 据。
有序集合中的元素不能重复,但是score可以重复,就和一个班里的同学学号不能重复,但是考试成绩可以相同。
当元素个数较少且每个元素较小时,内部编码为skiplist;当元素个数超过128个,内部编码变为ziplist;当某个元素大于64字节时,内部编码也会变为hashtable
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用
户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播
放数量、按照获得的赞数。
使用赞数这个维度作为例子,主要实现下列功能
(1)添加用户赞数
例如用户mike上传了一个视频,并获得了3个赞,可以使用有序集合的 zadd和zincrby功能
zadd user:ranking:2016_03_15 mike 3
如果之后再获得一个赞,可以使用zincrby:
zincrby user:ranking:2016_03_15 mike 1
(2)取消用户赞数
由于各种原因(例如用户注销、用户作弊)需要将用户删除,此时需要 将用户从榜单中删除掉,可以使用zrem。
zrem user:ranking:2016_03_15 mike
(3)展示获取赞数最多的十个用户
zrevrangebyrank user:ranking:2016_03_15 0 9
(4)展示用户信息以及用户分数
此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户 的分数和排名可以使用zscore和zrank两个功能.
hgetall user:info:tom zscore user:ranking:2016_03_15 mike zrank user:ranking:2016_03_15 mike
keys pattern
考虑到Redis的单线程架构就不那么美妙了,如果Redis包含了 大量的键,执行keys命令很可能会造成Redis阻塞,所以一般建议不要在生 产环境下使用keys命令。
可以在以下三种情况使用:
- 在一个不对外提供服务的Redis从节点上执行,这样不会阻塞到客户端 的请求,但是会影响到主从复制,有关主从复制我们将在第6章进行详细介绍。
- 如果确认键值总数确实比较少,可以执行该命令。
- 使用下面要介绍的scan命令渐进式的遍历所有键,可以有效防止阻塞。
scan cursor [match pattern] [count number]
使用时参考如下伪代码
String key = "myset";
// 定义pattern
String pattern = "old:user*";
// 游标每次从0开始
String cursor = "0";
while (true) {
// 获取扫描结果
ScanResult scanResult = redis.sscan(key, cursor, pattern);
List elements = scanResult.getResult();
if (elements != null && elements.size() > 0) {
// 批量删除
redis.srem(key, elements);
}
// 获取新的游标
cursor = scanResult.getStringCursor();
// 如果游标为0表示遍历结束
if ("0".equals(cursor)) {
break;
}
}
除了scan以外,Redis提供了面向哈希类型、集合类型、有序集合的扫 描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对 应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似。
渐进式遍历可以有效的解决keys命令可能产生的阻塞问题,但是scan并 非完美无瑕,如果在scan的过程中如果有键的变化(增加、删除、修改),那么遍历效果可能会碰到如下问题:新增的键可能没有遍历到,遍历出了重 复的键等情况,也就是说scan并不能保证完整的遍历出来所有的键,这些是
我们在开发时需要考虑的。