Redis的值有5种数据结构,不同数据结构的使用场景是什么?

字符串

缓存

(1)使用原生字符类型缓存
优点:简单直观,每个属性都支持更新操作
缺点:占用过多的键、内存占用量较大,同时用户内聚性比较差。
(2)使用序列化字符串类型缓存
优点:简化编程,如果合理地使用序列化可以提高内存的利用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出来,反序列化,更新后在序列化存到Redis中。

计数

许多应用都会使用Redis作为计数的基础工具。它可以实现快速计数、查询缓存等功能,同时数据可以异步落到其它数据源。比如视频播放系统使用Redis作为视频播放计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1。

long incrVideoCounter(long id){
	key = "video:playCount:" + id;
	return redis.incr(key);
}

图解mysql专栏:count( * )这么慢,我该怎么办?

Redis计数和数据库同步的问题:

以InnoDB为存储引擎的数据库,由于MVCC机制,count(*)计数时需要一行一行的读取,判断是否对当前事务可见, 然后计数加1或者不加。所以当数据量很大时,执行count(*)的效率很慢。

假设某个网站有这样一个页面,要显示数据库中某张操作记录表的总数,同时还要显示最新的100条记录。由于count(*)的效率很慢,我们可以用Redis来计数,如果这张操作记录表插入了一行,Redis计数就加1,删除了一行,Redis计数减1。

我们用一张表模拟两个事务的操作过程,事务B负责这个页面的业务查询,即获取Redis计数和操作表中最新的100条记录,事务A向表中插入一条记录,Redis计数加1

由于多线程中,线程的执行顺序是不确定的,如果事务B的代码在t2时刻执行,则查询到底100条记录里面有最新的插入记录,但redis计数却没变。

时刻事务A事务B
t0
t1向操作表中插入一条记录
t2读取Redis计数
获取表中最新的100条记录
t3Redis记录加1

如果事务A变化一下添加记录的执行过程,先去Redis计数加1,再向操作表中执行插入操作。事务B在t2时刻执行,此时该页面显示的情况是,Redis的计数变了,但是查询到的100行记录里面没有最新的改变

时刻事务A事务B
t0
t1Redis记录加1
t2读取Redis计数
获取表中最新的100条记录
t3向操作表中插入一条记录

以上两种情况,对于事物B来说,查计数值和“最近的100条记录”看到的结构,逻辑上是不一致的。

共享Session

查看我写的另一篇博客
什么是Redis共享Session?

限速

很多应用出了安全考虑,会在每次进行登录的时候,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信验证接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。

此功能可以通过Redis来实现:

phoneNum = "173xxxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// 将这个key的生命周期设置为60s,也就是1分钟
// key初始时的次数是1
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <= 5){
	// 第一次创建这个键
	// 或者
    // 不是第一次创建键,但 key 对应的次数小于等于5

    // 通过
}else{
	// 限速
}

哈希

缓存

优点:简单直观,如果合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。

列表

消息队列

如图所示,Redis的lpush+brpop命令组合即可实现阻塞队列,生产者使用lpush命令从列表左侧插入元素,多个消费者使用brpop命令阻塞式地抢列表尾部的元素。
在这里插入图片描述

文章列表

每个用户有属于自己文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表是索引有序的,可以支持按照索引范围获取元素。
(1)每篇文章用哈希类型存储,例如每篇文章有一些属性:title、time、author、content、isbn、money等

hmset acticle:1 title xx time 1476536196 content xxxx ...
...
hmset acticle:k title xx time 1476536196 content xxxx ...

使用类似这样的命令构造k篇文章的数据
在这里插入图片描述

(2)向用户添加文字列表

lpush user:1:acticles article:1 article:3 ...
...
lpush user:n:acticles article:2 article:4

每个用户有自己的一个文章列表
在这里插入图片描述
(3)分页获取用户文章列表,例如通过下面的伪代码获取用户id=1的前10篇文章:

articles = lrange user:1:articles 0 9
for (article : articles){
	hgetall {article}
}

在这里插入图片描述
使用列表类型保存或获取文章列表会存在两个问题:

  1. 如果每次分页获取的文章数目较多,需要执行多次hgetall操作,此时可以考虑使用Pipeline批量获取。或者考虑将文章数据序列化为字符串类型,使用mget来获取。
  2. 分页获取文章列表时,lrange命令在列表的两端性能比较好,但如果列表较大,lrange获取列表中间元素性能会变差,此时可以考虑将列表做二级拆分。或者使用Redis quciklist内部编码实现。

lpush + lpop = Stack

队列

lpush + rpop = Queue

有限集合

lpush + ltrim = Capped Collection

集合

标签

一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了标签之后我们可以得到:

  • 喜欢同一个标签的人(站在标签角度)
  • 两个或多个用户共同喜欢的标签(站在用户角度)

这些数据对于用户体验,以及增强用户黏度比较重要。

例如,一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品感兴趣的人,在各个页面或通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的收益。

下面使用集合类型实现标签的若干功能
(1)给用户添加标签

SADD user:1:tags tag1 tag2 tag3
SADD user:2:tags tag2 tag4 tag5
SADD user:3:tags tag1 tag3 tag5
SADD user:4:tags tag2 tag3 tag4
SADD user:5:tags tag1 tag4 tag5
SADD user:6:tags tag3 tag4 tag5

在redis种可以使用lua脚本一次性执行这命令:

EVAL "
redis.call('SADD', 'user:1:tags', 'tag1', 'tag2', 'tag3')
redis.call('SADD', 'user:2:tags', 'tag2', 'tag4', 'tag5')
redis.call('SADD', 'user:3:tags', 'tag1', 'tag3', 'tag5')
redis.call('SADD', 'user:4:tags', 'tag2', 'tag3', 'tag4')
redis.call('SADD', 'user:5:tags', 'tag1', 'tag4', 'tag5')
redis.call('SADD', 'user:6:tags', 'tag3', 'tag4', 'tag5')
" 0

(2) 给标签添加用户

SADD tag1:users user:1 user:3 user:5
SADD tag2:users user:1 user:2 user:4
SADD tag3:users user:1 user:3 user:4
SADD tag4:users user:2 user:4 user:5 user:6
SADD tag5:users user:2 user:3 user:5 user:6

在redis种可以使用lua脚本一次性执行这命令:

EVAL "
redis.call('SADD', 'tag1:users', 'user:1', 'user:3', 'user:5')
redis.call('SADD', 'tag2:users', 'user:1', 'user:2', 'user:4')
redis.call('SADD', 'tag3:users', 'user:1', 'user:3', 'user:4')
redis.call('SADD', 'tag4:users', 'user:2', 'user:4', 'user:5', 'user:6')
redis.call('SADD', 'tag5:users', 'user:2', 'user:3', 'user:5', 'user:6')
" 0

(3)删除用户下标签

srem user:1:tags tag1 tag2

(4)删除标签下的用户

srem tag1:users user:1
srem tag2:users user:1

(1)和(2)尽量放在一个事务中执行。
(3)和(4)尽量放在一个事务中执行。

(5)获取喜欢同一个标签的人

 smembers tag1:users

在这里插入图片描述
(6)获取user:1与user:2共同喜欢的标签

sinter user:1:tags user:2:tags

在这里插入图片描述
在这里插入图片描述

抽奖

spop/srandmember = Random item

社交需求

sadd + sinter = Social Graph

有序集合

排行榜系统

比如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多方面的,可能按照以下几个维度排名:

  • 时间
  • 播放数量
  • 获得的赞数

以获得的赞数为例,记录每天用户上传的视频的排行榜。

(1)视频添加用户赞数
例如用户mike上传了一个视频,并获得了3个赞,可以使用有序集合的zadd和zincrby功能:

zadd user:ranking:2024_03_12 3 mike

如果之后再获得一个赞,就可以使用zincrby:

zincrby user:ranking:2024_03_12 1 mike

(2)将用户移除榜单
由于各种原因(如用户注销、用户作弊)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用zrem。例如删除榜单中的tom用户

zrem user:ranking:2024_03_12 tom

(3)显示榜单Top10
为了模拟这个功能,我用lua脚本创建了20条模拟数据:

EVAL "
redis.call('ZADD', 'user:ranking:2024_03_12', 3, 'mike')
redis.call('ZADD', 'user:ranking:2024_03_12', 5, 'jack')
redis.call('ZADD', 'user:ranking:2024_03_12', 7, 'tom')
redis.call('ZADD', 'user:ranking:2024_03_12', 9, 'curt')
redis.call('ZADD', 'user:ranking:2024_03_12', 11, 'dexter')
redis.call('ZADD', 'user:ranking:2024_03_12', 13, 'bert')
redis.call('ZADD', 'user:ranking:2024_03_12', 15, 'christian')
redis.call('ZADD', 'user:ranking:2024_03_12', 17, 'cecil')
redis.call('ZADD', 'user:ranking:2024_03_12', 19, 'charles')
redis.call('ZADD', 'user:ranking:2024_03_12', 21, 'bill')
redis.call('ZADD', 'user:ranking:2024_03_12', 23, 'cathy')
redis.call('ZADD', 'user:ranking:2024_03_12', 25, 'crystal')
redis.call('ZADD', 'user:ranking:2024_03_12', 27, 'elaine')
redis.call('ZADD', 'user:ranking:2024_03_12', 29, 'ellie')
redis.call('ZADD', 'user:ranking:2024_03_12', 31, 'hortensia')
redis.call('ZADD', 'user:ranking:2024_03_12', 33, 'kit')
redis.call('ZADD', 'user:ranking:2024_03_12', 35, 'lori')
redis.call('ZADD', 'user:ranking:2024_03_12', 37, 'marian')
redis.call('ZADD', 'user:ranking:2024_03_12', 39, 'lesley')
redis.call('ZADD', 'user:ranking:2024_03_12', 41, 'thirza')
" 0

根据或赞数排序,取前或赞数量前10个用户,可以使用zrevrange命令,从高到地返回成员:

 zrevrange user:ranking:2024_03_12 0 9

在这里插入图片描述
(4)展示用户信息、用户分数以及用户排名

假设用户信息保存再哈希类型中,用户分数和用户排名可以用zscore和zrank两个命令:

hgetall user:info:tom
zscore user:ranking:2024_03_12 mike
zrank user:ranking:2024_03_12 mike
  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis 是一个基于内存的键存储系统,支持多数据结构。常见的 Redis 数据结构包括: 1. 字符串(String):最基本的数据结构,可以存储字符串、整数和浮点数等类型的。字符串类型支持多操作,如设置、获取、自增、自减等。适用于存储简单的键对数据,如用户信息等。 2. 列表(List):链表结构,可以存储多个字符串类型的,支持在头部和尾部添加、删除元素,以及获取指定范围内的元素等操作。适用于存储一些有序的数据,如消息队列等。 3. 集合(Set):无序的字符串类型的集合,支持添加、删除、获取集合中的元素,以及集合间的交、并、差等操作。适用于去重、统计和关系运算等场景,如推荐系统中的用户兴趣标签。 4. 哈希(Hash):键对的集合,可以存储多个键对,每个键对可以存储字符串类型的键和。适用于存储一些结构化的数据,如用户信息、商品信息等。 5. 有序集合(Sorted Set):有序的字符串类型的集合,每个元素都有一个对应的分,可以根据分排序。支持添加、删除、获取元素,以及根据分范围获取元素等操作。适用于排行榜、热门商品等场景。 6. 地理位置(Geo):支持地理位置信息的存储和查询,如获取两个位置之间的距离、查询某个位置周围的其他位置等。适用于 LBS 场景。 7. 布隆过滤器(Bloom Filter):一空间效率非常高的数据结构,用于判断一个元素是否存在于一个集合中。布隆过滤器可以快速地判断一个元素可能不存在于集合中,但无法确定一个元素一定存在于集合中。适用于大规模的数据去重场景,如爬虫去重、邮件地址去重等。 以上是 Redis 常见的数据结构每种数据结构都有其特定的用途和优缺点,根据实际的业务需求选择合适的数据结构可以提高系统的性能和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值