性能测试必备知识,本章主要介绍Redis数据类型的常用API以及使用场景以及相关的经验技巧,是性能测试的必备知识,有不懂的地方可以咨询在线老师,一对一在线解答。
一、全局命令
Redis常见全局命令有以下几种:
1、查看所有键
1 keys *
2、键总数
1 dbsize
3、检查键是否存在
如果键存在则返回1,不存在则返回0
1 exists key
4、删除键
1 del key [key ...]
5、键过期
1 expire key seconds
TTL命令会返回键的剩余过期时间,它有3种返回值:
大于等于0的整数:键剩余的过期时间。
-1:键没设置过期时间。
-2:键不存在
二、字符串
1、设置值
set key value [ex seconds] [px milliseconds] [nx|xx]
set命令有几个选项:
·ex seconds:为键设置秒级过期时间。
·px milliseconds:为键设置毫秒级过期时间。
·nx:键必须不存在,才可以设置成功,用于添加。
·xx:与nx相反,键必须存在,才可以设置成功,用于更新。
除了set选项,Redis还提供了setex和setnx两个命令:
它们的作用和ex和nx选项是一样的。下面的例子说明了set、setnx、setxx的区别。
setnx和setxx在实际使用中有什么应用场景吗?以setnx命令为例子,由于Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value,根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案。
典型场景:
如果没有从Redis获取到用户信息,需要从MySQL中进行获取,并将结果回写到Redis,添加1小时(3600秒)过期时间。
// 从MySQL获取用户信息
userInfo = mysql.get(id);
// 将userInfo序列化,并存入Redis
redis.setex(userRedisKey, 3600, serialize(userInfo));
// 返回结果
return userInfo
2、获取值
get key
3、批量设置值
mset key value [key value ...]
4、批量获取值
mget key [key ...]
5、计数
incr key
incr命令用于对值做自增操作,返回结果分为三种情况:
·值不是整数,返回错误。
·值是整数,返回自增后的结果。
·键不存在,按照值为0自增,返回结果为1。
除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数)典型场景:
例如笔者所在团队的视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1。
long incrVideoCounter(long id) {
key = "video:playCount:" + id;
return redis.incr(key);
}
很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。
但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。
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{
// 限速
}
上面是一种常用命令,下面是一些不常用命令:
1、追加值
append key value
2、字符串长度
strlen key
3、设置并返回原值
getset key value
4、设置指定位置的字符
setrange key offeset value
127.0.0.1:6379> set redis pest
OK
127.0.0.1:6379> setrange redis 0 b
(integer) 4
127.0.0.1:6379> get redis
"best"
5、获取部分字符串
getrange key start end
127.0.0.1:6379> getrange redis 0 1
"be"
三、哈希
1、设置值
hset key field value
127.0.0.1:6379> hset user:1 name tom
(integer) 1
2、获取值
hget key field
127.0.0.1:6379> hget user:1 name
"tom"
3、删除field
hdel key field [field ...]
127.0.0.1:6379> hdel user:1 name
(integer) 1
127.0.0.1:6379> hdel user:1 age
(integer) 0
4、计算field个数
hlen key
127.0.0.1:6379> hlen user:1
(integer) 3
5、批量设置或获取field-value
hmget key field [field ...]
hmset key field value [field value ...]
127.0.0.1:6379> hmset user:1 name mike age 12 city tianjin
OK
127.0.0.1:6379> hmget user:1 name city
1) "mike"
2) "tianjin"
6、判断field是否存在
hexists key field
127.0.0.1:6379> hexists user:1 name
(integer) 1
7、获取所有field
hkeys key
127.0.0.1:6379> hkeys user:1
1) "name"
2) "age"
3) "city"
8、获取所有value
hvals key
127.0.0.1:6379> hvals user:1
1) "mike"
2) "12"
3) "tianjin"
9、获取所有field-value
hgetall key
127.0.0.1:6379> hgetall user:1
1) "name"
2) "mike"
3) "age"
4) "12"
5) "city"
6) "tianjin"
10、hincrby hincrbyfloat
hincrby key field
hincrbyfloat key field
hincrby和hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是filed。
11、计算value的字符串长度
hstrlen key field
127.0.0.1:6379> hstrlen user:1 name
(integer) 3
哈希数据类型一般用于存储用户信息。
哈希存储结构
1、原生字符串类型:每个属性一个键
set user:1:name tom
set user:1:age 23
set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。 缺点:占用过多的键,内存占用量较大,同时用户信息内
聚性比较差,所以此种方案一般不会在生产环境使用。
2、序列化字符串类型:将用户信息序列化后用一个键保存
set user:1 1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新
后再序列化到Redis中。
3、哈希类型:每个用户属性使用一对field-value,但是只用一个键保存
hmset user:1 name tomage 1 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。 缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
四、列表
列表(list)类型是用来存储多个有序的字符串,如图2-18所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表,列表中的每个字符串称为元素(element),一个列表最多可以存储23的2次方减1个元素。在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。
1、添加操作
rpush key value [value ...]
127.0.0. 1:6379> rpush listkey c b a
(integer) 3
lrange0-1命令可以从左到右获取列表的所有元素:
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "b"
3) "a"
2、从左边插入元素
lpush key 1 value [value ...]
3、向某个元素前或者后插入元素
linsert key before|after pivot value
linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后(after)插入一个新的元素
value,例如下面操作会在列表的元素b前插入java:
127.0.0.1:6379> linsert listkey before b java
(integer) 4
4、获取指定范围内的元素列表
lrange key start end
lrange操作会获取列表指定索引范围所有的元素。索引下标有两个特点:
第一,索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
第二,lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同,例如想获取列表的第2到第4个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey 1 3
1) "java"
2) "b"
3) "a"
5、获取列表指定索引下标的元素
lindex key index
127.0.0.1:6379> lindex listkey -1
"a"
6、获取列表长度
llen key
127.0.0.1:6379> llen listkey
(integer) 4
7、从列表左侧弹出元素
lpop key
127.0.0.1:6379>t lpop listkey
"c"
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"
8、从列表右侧弹出
rpop key
9、删除指定元素
lrem key count value
lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:
1. count>0,从左到右,删除最多count个元素。
2. count<0,从右到左,删除最多count绝对值个元素。
3. count=0,删除所有。
例如向列表从左向右插入5个a,那么当前列表变为“a a a a a java b a”,下面操作将从列表左边开始删除4个为a的元素:
127.0.0.1:6379> lrem listkey 4 a
(integer) 4
127.0.0.1:6379> lrange listkey 0 -1
1) "a"
2) "java"
3) "b"
4) "a"
10、按照索引范围修剪列表
ltrim key start end
例如,下面操作会只保留列表listkey第2个到第4个元素:
127.0.0.1:6379> ltrim listkey 1 3
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "a"
11、修改指定索引下标的元素
lset key index newValue
下面操作会将列表listkey中的第3个元素设置为python:
127.0.0.1:6379> lset listkey 2 python
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "java"
2) "b"
3) "python"
12、阻塞操作
blpop key [key ...] timeout
brpop key [key ...] timeout
blpop和brpop是lpop和rpop的阻塞版本,它们除了弹出方向不同,使用方法基本相同,所以下面以
brpop命令进行说明,brpop命令包含两个参数:
1、key[key...]:多个列表的键。
2、timeout:阻塞时间(单位:秒)。
列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果timeout=0,那么客户端一直阻塞等下去:
127.0.0.1:6379> brpop list:test 3
(nil)
(3.10s)
127.0.0.1:6379> brpop list:test 0
...阻塞...
如果此期间添加了数据element1,客户端立即返回:
127.0.0.1:6379> brpop list:test 3
1) "list:test"
2) "element1"
(2.06s)
列表不为空:客户端会立即返回。
127.0.0.1:6379> brpop list:test 0
1) "list:test"
2) "element1"
在使用brpop时,有两点需要注意。 第一点,如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回:
127.0.0.1:6379> brpop list:1 list:2 list:3 0
..阻塞..
此时另一个客户端分别向list:2和list:3插入元素:
client-lpush> lpush list:2 element2
(integer) 1
client-lpush> lpush list:3 element3
(integer) 1
客户端会立即返回list:2中的element2,因为list:2最先有可以弹出的元素:
127.0.0.1:6379> brpop list:1 list:2 list:3 0
1) "list:2"
2) "element2_1"
第二点,如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。
客户端1:
client-1> brpop list:test 0
...阻塞...
客户端2:
client-2> brpop list:test 0
...阻塞...
客户端3:
client-3> brpop list:test 0
...阻塞...
此时另一个客户端lpush一个元素到list:test列表中:
client-lpush> lpush list:test element
(integer)
那么客户端1最会获取到元素,因为客户端1最先执行brpop,而客户端2
和客户端3继续阻塞:
client> brpop list:test 0
1) "list:test"
2) "element"
以下场景一般会用到列表:
1、消息队列 Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
2、文章列表 每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
五、集合
集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
1、添加元素
sadd key element [element ...]
127.0.0.1:6379> exists myset
(integer) 0
127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> sadd myset a b
(integer) 0
2、删除元素
srem key element [element ...]
127.0.0.1:6379> srem myset a b
(integer) 2
127.0.0.1:6379> srem myset hello
(integer) 0
3、计算元素个数
scard key
127.0.0.1:6379> scard myset
(integer) 1
4、判断元素是否在集合中
sismember key element
127.0.0.1:6379> sismember myset c
(integer) 1
5、随机从集合返回指定个数元素
srandmember key [count]
[count]是可选参数,如果不写默认为1,例如:
127.0.0.1:6379> srandmember myset 2
1) "a"
2) "c"
127.0.0.1:6379> srandmember myset
"d"
6、从集合随机弹出元素
spop key
spop操作可以从集合中随机弹出一个元素,例如下面代码是一次spop
后,集合元素变为"d b a":
123
127.0.0.1:6379> spop myset
"c"
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
7、获取所有元素
smembers key
下面代码获取集合myset所有元素,并且返回结果是无序的:
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
8、求多个集合的交集
sinter key [key ...]
例如下面代码是求user:1:follow和user:2:follow两个集合的交集,
返回结果是sports、it:
127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "sports"
2) "it"
9、求多个集合的并集
suinon key [key ...]
例如下面代码是求user:1:follow和user:2:follow两个集合的并集,
返回结果是sports、it、his、news、music、ent:
127.0.0.1:6379> sunion user:1:follow user:2:follow
1) "sports"
2) "it"
3) "his"
4) "news"
5) "music"
6) "ent"
10、求多个集合的差集
sdiff key [key ...]
例如下面代码是求user:1:follow和user:2:follow两个集合的差集,
返回结果是music和his:
127.0.0.1:6379> sdiff user:1:follow user:2:follow
125
1) "music"
2) "his"
11、将交集、并集、差集的结果保存
sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...]
集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了上面三个命令(原命令+store)将集合间交集、并集、差集的结果保存在destination key中,例如下面操作将user:1:follow和user:2:follow两个集合的交集结果保存在user:1_2:inter中,user:1_2:inter本身也是集合类型:
127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> type user:1_2:inter
set
127.0.0.1:6379> smembers user:1_2:inter
1) "it"
2) "sports"
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品 比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益。
1、给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
...
2、给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:2
...
用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成的数据不一致,有关如何将两个
命令放在一个事务,第3章会介绍事务以及Lua的使用方法。
3、删除用户下的标签
srem user:1:tags tag1 tag5
...
4、删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
...
六、有序集合
有序集合相对于哈希、列表、集合来说会有一点点陌生,但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。
1、添加成员
zadd key score member [score member ...]
下面操作向有序集合user:ranking添加用户tom和他的分数251:
127.0.0.1:6379> zadd user:ranking 251 tom
(integer) 1
返回结果代表成功添加成员的个数:
127.0.0.1:6379> zadd user:ranking 1 kris 91 mike 200 frank 220 tim 250
martin
(integer) 5
2、计算成员个数
zcard key
例如下面操作返回有序集合user:ranking的成员数为5,和集合类型的scard命令一样,zcard的时间复杂度为O(1)。
127.0.0.1:6379> zcard user:ranking
(integer) 5
3、计算某个成员的分数
zscore key member
tom的分数为251,如果成员不存在则返回nil:
127.0.0.1:6379> zscore user:ranking tom
"251"
127.0.0.1:6379> zscore user:ranking test
(nil)
4、计算成员排名
zrank key member
zrevrank key member
zrank是从分数从低到高返回排名,zrevrank反之。例如下面操作中,tom在zrank和zrevrank分别
排名第5和第0(排名从0开始计算)。
127.0.0.1:6379> zrank user:ranking tom
(integer) 5
127.0.0.1:6379> zrevrank user:ranking tom
(integer) 0
5、删除成员
zrem key member [member ...]
下面操作将成员mike从有序集合user:ranking中删除。
127.0.0.1:6379> zrem user:ranking mike
(integer) 1
返回结果为成功删除的个数。
6、增加成员分数
zincrby key increment member
下面操作给tom增加了9分,分数变为了260分:
127.0.0.1:6379> zincrby user:ranking 9 tom
"260"
7、返回指定排名范围的成员
zrange key start end [withscores]
zrevrange key start end [withscores]
有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。下面代码返回排名最低的是三个成员,如果加上withscores选项,同时会返回成员的分数:
127.0.0.1:6379> zrange user:ranking 0 2 withscores
1) "kris"
2) "1"
3) "frank"
4) "200"
5) "tim"
6) "220"
127.0.0.1:6379> zrevrange user:ranking 0 2 withscores
1) "tom"
2) "260"
3) "martin"
4) "250"
5) "tim"
6) "220"
8、返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。例如下面操作从低到高返回200到221分的成员,withscores选项会同时返回每个成员的分数。[limit offset count]选项可以限制输出的起始位置和个数:
127.0.0.1:6379> zrangebyscore user:ranking 200 tinf withscores
1) "frank"
2) "200"
3) "tim"
4) "220"
127.0.0.1:6379> zrevrangebyscore user:ranking 221 200 withscores
1) "tim"
2) "220"
3) "frank"
4) "200"
同时min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大:
127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "260"
9、返回指定分数范围成员个数
zcount key min max
下面操作返回200到221分的成员的个数:
127.0.0.1:6379> zcount user:ranking 200 221
(integer) 2
10、删除指定排名内的升序元素
zremrangebyrank key start end
下面操作删除第start到第end名的成员:
127.0.0.1:6379> zremrangebyrank user:ranking 0 2
(integer) 3
11、删除指定分数范围的成员
zremrangebyscore key min max
下面操作将250分以上的成员全部删除,返回结果为成功删除的个数:
127.0.0.1:6379> zremrangebyscore user:ranking (250 +inf
(integer) 2
12、交集
zinterstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max]
这个命令参数较多,下面分别进行说明:
1、destination:交集计算结果保存到这个键。
2、numkeys:需要做交集计算键的个数。
3、key[key...]:需要做交集计算的键。
4、weights weight[weight...]:每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1。
5、aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum。
下面操作对user:ranking:1和user:ranking:2做交集,weights和aggregate使用了默认配置,可以看到目标键user:ranking:1_inter_2对分值做了sum操作:
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1
user:ranking:2
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "168"
3) "martin"
4) "875"
5) "tom"
6) "1139"
如果想让user:ranking:2的权重变为0.5,并且聚合效果使用max,可以执行如下操作:
127.0.0.1:6379> zinterstore user:ranking:1_inter_2 2 user:ranking:1
user:ranking:2 weights 1 0.5 aggregate max
(integer) 3
127.0.0.1:6379> zrange user:ranking:1_inter_2 0 -1 withscores
1) "mike"
2) "91"
3) "martin"
4) "312.5"
5) "tom"
6) "444"
13、并集
zunionstore destination numkeys key [key ...] [weights weight [weight ...]]
[aggregate sum|min|max]
该命令的所有参数和zinterstore是一致的,只不过是做并集计算,例如下面操作是计算user:
ranking:1和user:ranking:2的并集,weights和aggregate使用了默认配置,可以看到目标键user:
ranking:1_union_2对分值做了sum操作:
127.0.0.1:6379> zunionstore user:ranking:1_union_2 2 user:ranking:1
user:ranking:2
(integer) 7
127.0.0.1:6379> zrange user:ranking:1_union_2 0 -1 withscores
1) "kris"
2) "1"
3) "james"
4) "8"
5) "mike"
6) "168"
7) "frank"
8) "200"
9) "tim"
10) "220"
11) "martin"
12) "875"
13) "tom"
14) "1139"
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每天用户上传视频 的排行榜。主要需要实现以下4个功能。
1、添加用户赞数 例如用户mike上传了一个视频,并获得了3个赞,可以使用有序集合的zadd和zincrby
功能:
zadd user:ranking:1 2016_03_15 mike 3
如果之后再获得一个赞,可以使用zincrby:
zincrby user:ranking:2016_03_15 mike 1
2、取消用户赞数
由于各种原因(例如用户注销、用户作弊)需要将用户删除,此时需要将用户从榜单中删除掉,可以使
用zrem。例如删除成员tom:
zrem user:ranking:2016_03_15 mike
3、展示获取赞数最多的十个用户
此功能使用zrevrange命令实现:
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
本章介绍的“性能测试之Redis API和使用”内容就到这里,大家喜欢的话记得每天来这里和小编一起学习涨薪技能哦。(笔芯)
附:川石信息全国校区最新开班时间,课程资料获取13691729932(微信同号)。