字符串(K-V)
字符串(K-V)是我们在Redis中使用最多的一个类型,其中V的值不能超过512M,甚至很多人用Redis只用这个类型。如果只是单纯的使用K-V其实是并没有把Redis的特性发挥出来,在这种使用情况下,使用Redis和使用Memcache并没有过多的区别。
命令
-
设置值
set key value
直接设置值。
setex key seconds value
设置值并且带有过期时间。
setnx key value
设置值,并且判断是否存在。如果存在则设置不成功,如果不存在则设置成功。主要用于分布式锁。
getset key value
设置并返回原值。 -
获取值
get key
根据key获取值,如果不存在则返回nil。 -
批量设置值
mset key value [key value ...]
批量设置K-V,例如:mset a 1 b 2 c 3 d 4
。 -
批量获取值
mget key [key ...]
下面操作批量获取了键 a、b、c、d 的值:127.0.0.1:6379> mget a b c d 1) "1" 2) "2" 3) "3" 4) "4"
如果有些键不存在,那么它的值为 nil(空),结果是按照传入键的顺序返回:
127.0.0.1:6379> mget a b c f 1) "1" 2) "2" 3) "3" 4) (nil)
批量调用命令与单个命令相比的好处就是节约了网络请求时间。get命令执行5次,相当于5次请求&响应,但是如果执行mget5个key,只有1次请求&响应。
-
计数
incr key
根据key自增,并且返回执行后的结果。
值不是整数,返回错误。
值是整数,返回自增后的结果。
键不存在,按照值为0自增,返回结果为1。
decr key
自减。
incrby key increment
自增指定数字。
decrby key decrement
自减指定数字。
incrbyfloat key increment
自增浮点数。 -
删除
del key
删除
内部编码
字符串类型的内部编码有3种:
- int:8个字节的长整型。
- embstr:小于等于39个字节的字符串。
- raw:大于39个字节的字符串。
Redis 会根据当前值的类型和长度决定使用哪种内部编码实现。
使用场景
字符串的使用场景比较常见。我们日常用的缓存,session共享等,都是通过字符串的方式去实现。
哈希(hash)
在 Redis 中,哈希类型是指键值本身又是一个键值对结构。相当于 key-{field1-value1},{field2-value2},{field3-value3}......{fieldN-valueN}
命令
-
设置值
hset key field value
设置key-field-value。 -
获取值
hget key field
如果key或者field不存在,则返回nil。 -
计算 field 个数
hlen key
-
计算 value 的字符串长度
hstrlen key field
-
批量设置值&获取值
hmget key field [field ...]
批量获取值,同一个key,不同的fieled
hmset key field value [field value ...]
批量设置值,同一个key,不同的field-value。 -
删除field
hdel key field [field ...]
hdel 会删除一个或多个 field,返回结果为成功删除 field 的个数。 -
判断 field 是否存在
hexists key field
包含时返回结果为1,不包含返回0。 -
获取所有field
hkeys key
根据key,返回所有的field。 -
获取所有value
hvals key
根据key,返回所有的value -
获取所有的 field-value
hgetall key
根据kye,返回所有的filed及value。如果这个key里面的field太多,而每个field的value也太多,不建议使用,很有可能会引起Redis阻塞。 -
计数(与k-v差不多)
hincrby key field
hincrbyfloat key field
内部编码
哈希类型的内部编码有两种:
- ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于 hash-max-ziplist-value 配置(默认64字节)时,Redis 会使用 ziplist 作为哈希的内部实现,ziplist 使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比 hashtable 更加优秀。
- hashtable(哈希表):当哈希类型无法满足 ziplist 的条件时,Redis 会使用 hashtable 作为哈希的内部实现,因为此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度为O(1)。
使用场景
- 一般情况我们存储一个对象,可以用K-V,key为一个对象的主键,value这个对象(或者对象的json)。这样的话,我们每次get key获取到的就是整个对象,如果这个对象里面我们可能只需要用到一两个字段值,这样的操作其实是非常浪费性能的。而且存储对象的时候,我们需要用到序列化与反序列化,这些也是会消耗一定的性能的。我们可以把整个对象用hash的方式存到Redis中,需要哪个对象的哪些值就获取哪个。
列表(list)
Redis中的列表可以存储多个有序字符串,最多可以存储2^32-1个元素。而且它支持双向操作,可以从列表两端插入(push)和弹出(pop)。因此,它可以用在很多种数据结构上。
命令
列表主要是四种操作类型
操作类型 | 操作 |
---|---|
增 | rpush lpush linsert |
删 | lpop rpop lrem ltrim |
改 | lset |
查 | lrange lindex llen |
阻塞 | blpop brpop |
-
增(插入)
rpush key value [value ...]
从右边插入value。
lpush key value [value ...]
从左边插入value。
linsert key before|after VAL value
linsert 命令会从列表中找到等于 VAL 的元素,在其前(before)或者后(after)插入一个新的元素 value。 -
删除
lpop key
从列表左侧弹出元素。
rpop key
从列表右侧弹出元素。
ltrim key start end
按照索引范围修剪列表。
lrem key count value
删除指定元素。
rem 命令会从列表中找到等于 value 的元素进行删除,根据 count 的不同分为三种情况:count>0,从左到右,删除最多 count 个元素;count<0,从右到左,删除最多 count 绝对值个元素;
count=0,删除所有。 -
改
lset key index newValue
根据index修改列表中的元素。 -
查
lrange key start end
lrange 操作会获取列表指定索引范围所有的元素。索引下标有两个特点:第一,索引下标从左到右分别是0到 N-1,但是从右到左分别是-1到-N,全部分别是0到-1。第二,lrange 中的 end 选项包含了自身,这个和很多编程语言不包含 end 不太相同。
lindex key index
获取列表指定索引下标的元素。索引下标从左到右分别是0到 N-1,但是从右到左分别是-1到-N。
llen key
获取列表长度。 -
阻塞
blpop key \[key ...\] timeout
brpop key \[key ...\] timeout
blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,它们除了弹出方向不同,使用方法基本相同,所以下面以 brpop 命令进行说明,brpop 命令包含两个参数:
key[key...]:多个列表的键。
timeout:阻塞时间(单位:秒)。
列表为空:如果 timeout=3,那么客户端要等到3秒后返回,如果 timeout=0,那么客户端一直阻塞等下去。
列表不为空:客户端会立即返回。
在使用 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最会获取到元素,因为客户端1最先执行 brpop,而客户端2和客户端3继续阻塞:
client> brpop list:test 0
1) "list:test"
2) "element"
内部编码
列表类型的内部编码有两种。
- ziplist(压缩列表):当列表的元素个数小于 list-max-ziplist-entries 配置(默认512个),同时列表中每个元素的值都小于 list-max-ziplist-value 配置时(默认64字节),Redis 会选用 ziplist 来作为列表的内部实现来减少内存的使用。
- linkedlist(链表):当列表类型无法满足 ziplist 的条件时,Redis 会使用 linkedlist 作为列表的内部实现。
使用场景
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+brpop=Message Queue(消息队列)
集合(set)
集合(set)类型也是用来保存多个的字符串元素,它和Java中的Set是差不多的东西,不允许重复。
命令
-
添加元素
sadd key element [element ...]
返回结果为添加成功的元素个数(如果已有这个元素,则不计入成功元素)。 -
删除元素
srem key element [element ...]
返回结果为成功删除元素个数。 -
计算元素个数
scard key
-
判断元素是否在集合中
sismember key element
如果给定元素在集合内返回1,反之返回0。 -
随机从集合返回指定个数元素
srandmember key [count]
[count]是可选参数,如果不写默认为1。 -
从集合随机弹出元素
spop key [count]
-
获取所有元素
smembers key
-
求多个集合的交集
sinter key [key ...]
集合1与集合2的共有部分。 -
求多个集合的并集
suinon key [key ...]
集合1+集合2。 -
求多个集合的差集
sdiff key [key ...]
集合1-集合2。 -
将交集、并集、差集的结果保存
sinterstore destination newKey [key1 key2...]
suionstore destination newKey [key1 key2...]
sdiffstore destination newKey [key1 key2...]
将结果保存到newkey中。
内部编码
集合类型的内部编码有两种:
- intset(整数集合):当集合中的元素都是整数且元素个数小于 set-max-intset-entries 配置(默认512个)时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用。
- hashtable(哈希表):当集合类型无法满足 intset 的条件时,Redis 会使用 hashtable 作为集合的内部实现。
使用场景
其实set和Java中的set非常像,一般情况如果不需要与其他服务共享数据,Java中的set足够使用了,在某些情况需要共享数据或者存储的情况才需要用到Redis中的set。
比如,使用抽奖的情况下,就可以先将所有人放到Redis中的set,然后利用spop/srandmember可以实现抽奖。
有序集合(zset)
有序集合和集合很相似,set是key中包含多个不重复的字符串,zset也是包含了多个不重复的字符串,但是每个字符串带了一个“分数”,分数允许重复。可以根据分数进行排序操作。
命令
-
添加成员
zadd key score member [score member ...]
-
计算元素个数
zcard key
-
计算某个成员的分数
zscore key member
-
计算成员的排名
zrank key member
zrevrank key member
zrank是从分数从低到高返回排名,zrevrank反之(排名从0开始计算)。 -
删除成员
zrem key member [member ...]
返回结果为成功删除元素个数。 -
增加成员的分数
zincrby key increment member
-
返回指定排名范围的成员
zrange key start end [withscores]
zrevrange key start end [withscores]
有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。如果加上withscores选项,同时会返回成员的分数。 -
返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。[limit offset count]选项可以限制输出的起始位置和个数。同时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"
-
返回指定分数范围成员个数
zcount key min max
-
删除指定排名内的升序元素
zremrangebyrank key start end
-
删除指定分数范围的成员
zremrangebyscore key min max
内部编码
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
- skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每天用户上传视频的排行榜。
位图(Bitmaps)
Bitmaps 本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作,它是一个最大长度为512MB(2^32)的位数组。一般情况Bitmaps在普通的业务上是用不了太多的,这里之所以拿出来讲解,是因为它能够实现一个非常强大的功能:布隆过滤器。
Bitmaps 单独提供了一套命令,所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。可以把 Bitmaps 想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。但是它的每个存储单元都是bit,8bit=1Byt,所以它的存在主要就是占用内存非常少。
命令
-
设置值
setbit key offset value
设置键的第 offset 个位的值(从0算起,value只能是0或者是1)。 -
获取值
getbit key offset
获取键的第 offset 位的值(从0开始算)。 -
获取 Bitmaps 指定范围值为1的个数
bitcount key [start] [end]
-
Bitmaps 间的运算
bitop 运算符 destkey key [key....]
bitop 是一个复合操作,它可以做多个 Bitmaps 的 and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在 destkey 中 -
计算 Bitmaps 中第一个值为 targetBit 的偏移量
bitpos key targetBit [start] [end]
使用场景
以下这个业务场景有点牵强,但是还是简单的描述一下吧。如果统计一个网站某一天的用户访问量,我们通常的做法是添加一个用户登录记录流水表,然后根据日期去count(distinct userId)这张表。但是这样的做法会导致这张表里的数据非常多,而且占用大量的空间。
如果用户的ID是自动增长的,那么就可以使用位图。比如ID为125的用户在2019-01-01这一天登录的时候,我们可以使用setbit user:login:2019-01-01 125 1
在Bitmaps的偏移量为125的位置设置值为1;如果还有其他的用户登录,同样根据用户ID将对应偏移量的值设置为1。然后调用bitcount user:login:2019-01-01
计算出2019-01-01这一天的访问量。
使用位图最方便的是占用资源比较小,而且执行速度会比较快。但是如果活跃用户比较少,而且用户ID又非常大的情况,用位图就有点得不偿失了。比如:2019-01-02这一天只有一个用户访问这个网站,而且这个用户的ID为100000,那么就需要占用1bit*100000的空间。
布隆过滤器 (Bloom Filter)
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k,以下图为例,具体的操作流程:首先将位数组进行初始化,将里面每个位都设置为0。这时候我们有一个集合{x, y, z},对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
综合来说:如果这个元素存在,那么可能会存在误判,其实它有可能是不存在的。如果这个元素不存在,那么它一定不存在!

语法
其实Redis4.0已经帮我们实现了对应的布隆过滤器,我们简单的看下它的语法。
-
创建布隆过滤器
bf.reserve key error_rate size
key为redis存储键值,error_rate 为错误率(大于0,小于1),size为预计存储的数量(size是比较关键的,需要根据自己的需求情况合理估计,设置太小的话会增大错误率,设置太大会占用过多不必要的空间) -
添加元素
bf.add key value
添加值到布隆过滤器中(当过滤器不存在的时候会,会以默认值自动创建一个,建议最好提前创建好)
bf.madd key value [value ...]
批量插入元素到布隆过滤器 -
判断是否存在
bf.exists key value
判断值是否存在过滤器中 1(表示很可能存在) 0 (表示绝对不存在)
bf.mexists key value [value ...]
批量判断判断值是否存在过滤器中
应用场景
如果我们结合BitMaps,就可以实现一个布隆过滤器。布隆过滤器的应用场景也非常广泛,举一个比较典型的例子:
通常,我们对缓存的操作是:
Object obj = 查询缓存(key);
if (obj == null) {
obj = 查询数据库(key);
if (obj != null) {
设置缓存数据(key, obj);
}
}
return obj;
但是,这里产生了一个问题:如果这个key本身在我们的数据库就是一个不存在的值,那么这里会直接造成缓存穿透。如果我们的数据库这个表中的数据特别多,而且有人知道这个漏洞,一直调用这个接口,查询这个根本不存在的key,会对数据库造成非常大的压力,甚至压垮数据库。
解决方案:
- 我们把数据库中,这个表全部的key同步到redis中。这个方案应该是一般人第一想法,但是如果这个表中的数据特别多,那么直接会造成Redis内存爆了。
- 利用布隆过滤器,对每个key进行多次hash,将每次hash出来的值当作offset修改Bitmaps的值为1。如果需要查询这个key,先查缓存,再查布隆过滤器对应的Bitmaps,最后确定有这个值就再查数据库。
Object obj = 查询缓存(key);
if (obj == null) {
boolean exists = 查询Bitmaps(key);
if (exists)
{
obj = 查询数据库(key);
if (obj != null) {
设置缓存数据并修改Bitmaps(key, obj);
}
}
}
return obj;
网友评论