JAVA技术文章java高级开发群互联网架构

Redis的几种数据模型及应用场景

2019-03-08  本文已影响4人  迦若莹

字符串(K-V)


字符串(K-V)是我们在Redis中使用最多的一个类型,其中V的值不能超过512M,甚至很多人用Redis只用这个类型。如果只是单纯的使用K-V其实是并没有把Redis的特性发挥出来,在这种使用情况下,使用Redis和使用Memcache并没有过多的区别。

命令

批量调用命令与单个命令相比的好处就是节约了网络请求时间。get命令执行5次,相当于5次请求&响应,但是如果执行mget5个key,只有1次请求&响应。

内部编码

字符串类型的内部编码有3种:

使用场景

字符串的使用场景比较常见。我们日常用的缓存,session共享等,都是通过字符串的方式去实现。

哈希(hash)


在 Redis 中,哈希类型是指键值本身又是一个键值对结构。相当于 key-{field1-value1},{field2-value2},{field3-value3}......{fieldN-valueN}

命令

内部编码

哈希类型的内部编码有两种:

使用场景

列表(list)


Redis中的列表可以存储多个有序字符串,最多可以存储2^32-1个元素。而且它支持双向操作,可以从列表两端插入(push)和弹出(pop)。因此,它可以用在很多种数据结构上。

命令

列表主要是四种操作类型

操作类型 操作
rpush lpush linsert
lpop rpop lrem ltrim
lset
lrange lindex llen
阻塞 blpop brpop
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"

内部编码

列表类型的内部编码有两种。

使用场景

集合(set)


集合(set)类型也是用来保存多个的字符串元素,它和Java中的Set是差不多的东西,不允许重复。

命令

内部编码

集合类型的内部编码有两种:

使用场景

其实set和Java中的set非常像,一般情况如果不需要与其他服务共享数据,Java中的set足够使用了,在某些情况需要共享数据或者存储的情况才需要用到Redis中的set。
比如,使用抽奖的情况下,就可以先将所有人放到Redis中的set,然后利用spop/srandmember可以实现抽奖。

有序集合(zset)


有序集合和集合很相似,set是key中包含多个不重复的字符串,zset也是包含了多个不重复的字符串,但是每个字符串带了一个“分数”,分数允许重复。可以根据分数进行排序操作。

命令

127.0.0.1:6379> zrangebyscore user:ranking (200 +inf withscores
1) "tim"
2) "220"
3) "martin"
4) "250"
5) "tom"
6) "260"

内部编码

有序集合类型的内部编码有两种:

使用场景

有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数。本节使用赞数这个维度,记录每天用户上传视频的排行榜。

位图(Bitmaps)


Bitmaps 本身不是一种数据结构,实际上它就是字符串,但是它可以对字符串的位进行操作,它是一个最大长度为512MB(2^32)的位数组。一般情况Bitmaps在普通的业务上是用不了太多的,这里之所以拿出来讲解,是因为它能够实现一个非常强大的功能:布隆过滤器。
Bitmaps 单独提供了一套命令,所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同。可以把 Bitmaps 想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。但是它的每个存储单元都是bit,8bit=1Byt,所以它的存在主要就是占用内存非常少。

命令

使用场景

以下这个业务场景有点牵强,但是还是简单的描述一下吧。如果统计一个网站某一天的用户访问量,我们通常的做法是添加一个用户登录记录流水表,然后根据日期去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,这是误判率存在的原因。
综合来说:如果这个元素存在,那么可能会存在误判,其实它有可能是不存在的。如果这个元素不存在,那么它一定不存在!

image

语法

其实Redis4.0已经帮我们实现了对应的布隆过滤器,我们简单的看下它的语法。

应用场景

如果我们结合BitMaps,就可以实现一个布隆过滤器。布隆过滤器的应用场景也非常广泛,举一个比较典型的例子:
通常,我们对缓存的操作是:

Object obj = 查询缓存(key);
if (obj == null) {
    obj = 查询数据库(key);
    
    if (obj != null) {
        设置缓存数据(key, obj);
    }
}
return obj;

但是,这里产生了一个问题:如果这个key本身在我们的数据库就是一个不存在的值,那么这里会直接造成缓存穿透。如果我们的数据库这个表中的数据特别多,而且有人知道这个漏洞,一直调用这个接口,查询这个根本不存在的key,会对数据库造成非常大的压力,甚至压垮数据库。
解决方案:

Object obj = 查询缓存(key);
if (obj == null) {
    boolean exists = 查询Bitmaps(key);
    
    if (exists)
    {
        obj = 查询数据库(key);
        
        if (obj != null) {
            设置缓存数据并修改Bitmaps(key, obj);
        }
    }
}
return obj;
上一篇 下一篇

猜你喜欢

热点阅读