Redis实战总结

bitmap

使用场景

用户签到

准备数据

SETBIT key offset value 对指定的key的value的指定偏移(offset)的位置1或0, 其中key我们可以设置为当天的年月日,offset是用户uid(这里暂时只考虑uid是纯数字的情况),value的话1表示已签到。

SETBIT 20190601 12500 1
SETBIT 20190602 12500 1
SETBIT 20190603 12500 1
SETBIT 20190601 12501 1
SETBIT 20190602 12502 1
SETBIT 20190601 12503 1
SETBIT 20190602 12503 1
SETBIT 20190603 12504 1
SETBIT 20190603 12503 1

#查询用户某天是否签到

GETBIT 20190501 12500,返回1表示已签到,0未签到。

#如果需要查询某天有多少人签到

BITCOUNT 2019050

#查询连续签到的有多少人

bitop AND 3_201906_sign 20190601 20190602 20190603

BITCOUNT 3_201906_sign

判断登录状态

#第一步,执行以下指令,表示用户已登录。
SETBIT login_status 10086 1
#第二步,检查该用户是否登陆,返回值 1 表示已登录。
GETBIT login_status 10086
#第三步,登出,将 offset 对应的 value 设置成 0。
SETBIT login_status 10086 0

小结

思路才是最重要,当我们遇到的统计场景只需要统计数据的二值状态,比如用户是否存在、 ip 是否是黑名单、以及签到打卡统计等场景就可以考虑使用 Bitmap。

Set

使用场景

网站UV

#用户编号 89757 访问 「Redis 为什么这么快 」时,我们将这个信息放到 Set 中。
SADD Redis为什么这么快:uv 89757

#通过 SCARD 命令,统计「Redis 为什么这么快」页面 UV。
SCARD Redis为什么这么快:uv

交集-共同好友

比如 QQ 中的共同好友正是聚合统计中的交集。我们将账号作为 Key,该账号的好友作为 Set 集合的 value。
模拟两个用户的好友集合:

SADD user:码哥字节 R大 Linux大神 PHP之父
SADD user:大佬 Linux大神 Python大神 C++菜鸡

交集

SINTERSTORE user:共同好友 user:码哥字节 user:大佬

差集-每日新增好友数

比如,统计某个 App 每日新增注册用户量,只需要对近两天的总注册用户量集合取差集即可。

比如,2021-06-01 的总注册用户量存放在 key = user:20210601 set 集合中,2021-06-02 的总用户量存放在 key = user:20210602 的集合中。

SDIFFSTORE  user:new  user:20210602 user:20210601

并集-总共新增好友

还是差集的例子,统计 2021/06/01 和 2021/06/02 两天总共新增的用户量,只需要对两个集合执行并集。

SUNIONSTORE  userid:new user:20210602 user:20210601

Hash

使用场景

网站UV

#输入数据

HSET uv 89757 1
HSET uv 89758 1
#统计 UV
HLEN uv

HyperLogLog

使用场景

网站UV

Set 虽好,如果文章非常火爆达到千万级别,一个 Set 就保存了千万个用户的 ID,页面多了消耗的内存也太大了。同理,Hash数据类型也是如此。咋办呢?HyperLogLog是王者方案

PFADD

将访问页面的每个用户 ID 添加到 HyperLogLog 中。

PFADD Redis主从同步原理:uv userID1 userID 2 useID3

PFCOUNT

利用 PFCOUNT 获取 「Redis主从同步原理」页面的 UV值。

PFCOUNT Redis主从同步原理:uv

UV数据合并

将多个 HyperLogLog  合并在一起形成一个新的 HyperLogLog 值

PFMERGE destkey sourcekey [sourcekey ...]

比如在网站中我们有两个内容差不多的页面,运营说需要这两个页面的数据进行合并。
其中页面的 UV 访问量也需要合并,那这个时候 PFMERGE 就可以派上用场了,也就是同样的用户访问这两个页面则只算做一次。
如下所示:Redis、MySQL 两个 Bitmap 集合分别保存了两个页面用户访问数据。

PFADD Redis数据 user1 user2 user3
PFADD MySQL数据 user1 user2 user4
PFMERGE 数据库 Redis数据 MySQL数据
PFCOUNT 数据库 // 返回值 = 4

List

使用场景

最新评论

只有不需要分页(比如每次都只取列表的前 5 个元素)或者更新频率低(比如每天凌晨统计更新一次)的列表才适合用 List 类型实现。
对于需要分页并且会频繁更新的列表,需用使用有序集合 Sorted Set 类型实现。

每当一个用户评论,则利用 LPUSH key value [value ...] 插入到 List 队头。

LPUSH 码哥字节 1 2 3 4 5 6

接着再用 LRANGE key star stop 获取列表指定区间内的元素。

LRANGE 码哥字节 0 4

结果 

1) "6"
2) "5"
3) "4"
4) "3"
5) "2"


 

Sorted Set

需求场景

排行榜

我们将《青花瓷》和《花田错》《菊花台》播放量添加到 musicTop 集合中:

ZADD musicTop 100000000 青花瓷 8999999 花田错 200000000 菊花台

歌曲每播放一次就通过 ZINCRBY指令将 score + 1

ZINCRBY musicTop 1 青花瓷
ZINCRBY musicTop 1 花田错 

获取榜单

#获取第一名

ZREVRANGE musicTop 0 0 WITHSCORES

#获取前两名

ZREVRANGE musicTop 0 1 WITHSCORES 

#显示有序列表
ZRANGEBYSCORE musicTop -inf +inf
# 显示整个有序集及成员的 score 值
ZRANGEBYSCORE musicTop -inf +inf WITHSCORES    
#显示点播量 <=100000000 的所有歌曲
ZRANGEBYSCORE salary -inf 100000000 WITHSCORES  
#显示点播量在8999999和100000000的所有歌曲
ZRANGEBYSCORE salary (8999999 100000000 

ZREVRANGE

可通过 ZREVRANGE key start stop [WITHSCORES]指令。
其中元素的排序按 score 值递减(从大到小)来排列。
具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。

小结

在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议优先考虑使用 Sorted Set。

string

分布式锁

方案一:SETNX + EXPIRE
方案二:SETNX + value值是(系统时间+过期时间)
方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令)
方案四:SET的扩展命令(SET EX PX NX)
方案五:SET EX PX NX  + 校验唯一随机值,再释放锁
方案六: 开源框架~Redisson
方案七:多机实现的分布式锁Redlock

 分布式锁特征

实现

redis分布式锁和简单秒杀限流的实现_csdncjh的博客-CSDN博客分布式锁看门狗防止死锁redission实现依赖 <!--整合redission框架start--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> &...https://blog.csdn.net/csdncjh/article/details/124924397?spm=1001.2014.3001.5501

消息队列

非阻塞队列

#入队列

LPUSH queue msg2

#出队列

RPOP queue

阻塞队列

#入队列

LPUSH bqueue msg2

#出队列

如果设置为 0,则表示不设置超时,直到有新消息才返回,否则会在指定的超时时间后返回 NULL

brpop bqueue 0

消息订阅

多消费者同时消费同一批数据

#发布

PUBLISH queueReading msg3

#订阅

SUBSCRIBE queueReading

Pub/Sub 的优缺点:
支持发布 / 订阅,支持多组生产者、消费者处理消息
消费者下线,数据会丢失
不支持数据持久化,Redis 宕机,数据也会丢失
消息堆积,缓冲区溢出,消费者会被强制踢下线,数据也会丢失
有没有发现,除了第一个是优点之外,剩下的都是缺点
所以,很多人看到 Pub/Sub 的特点后,觉得这个功能很「鸡肋」。
也正是以上原因,Pub/Sub 在实际的应用场景中用得并不多。

Stream

在 Redis 5.0 版本,作者把 disque 功能移植到了 Redis 中,并给它定义了一个新的数据类型:Stream。

XADD:发布消息

XADD key ID field value [field value ...]

 XADD 命令发布消息,其中的「*」表示让 Redis 自动生成唯一的消息 ID

 XADD queue * name zhangsan


XREAD:读取消息

XREAD COUNT 1 BLOCK 1000 mystream $
XREAD COUNT 1 BLOCK 1000 mystream 0-0
XREAD COUNT 1 mystream $
XREAD COUNT 1 mystream 0-0
#$,从当前时间开始接收,0-0,从第一条开始接收,COUNT接收一条,BLOCK等待1000ms
#注意block这里等待是等待服务器回复才会继续,等待是服务器等待

#队列长度

XLEN mystream

stream特性

Stream支持阻塞式拉取消息
可以的,在读取消息时,只需要增加 BLOCK 参数即可。

stream支持发布/订阅模式
XGROUP:创建消费者组
XREADGROUP:在指定消费组下,开启消费者拉取消息

Stream支持确认机制,重新消费
当一组消费者处理完消息后,需要执行 XACK 命令告知 Redis,这时 Redis 就会把这条消息标记为「处理完成」

stream支持发布消息时,你可以指定队列的最大长度,防止队列积压导致内存爆炸
XADD queue MAXLEN 10000 * name

Stream 还支持查看消息长度(XLEN)
查看消费者状态(XINFO)等命令

小结

Redis 本身可能会丢数据
面对消息积压,Redis 内存资源紧张
到这里,Redis 是否可以用作队列,我想这个答案你应该会比较清晰了。

如果你的业务场景足够简单,对于数据丢失不敏感,而且消息积压概率比较小的情况下,把 Redis 当作队列是完全可以的。

而且,Redis 相比于 Kafka、RabbitMQ,部署和运维也更加轻量。

如果你的业务场景对于数据丢失非常敏感,而且写入量非常大,消息积压时会占用很多的机器资源,那么我建议你使用专业的消息队列中间件。

延时队列

原理解析

实际上,redisson 使用了 两个list + 一个 sorted-set + pub/sub 来实现延时队列,而不是单一的sort-set。
sorted-set:存放未到期的消息&到期时间,提供消息延时排序功能
list-0:存放未到期消息,作为消息的原始顺序视图,提供如查询、删除指定第几条消息的功能(分析源码得出的,查看哪些地方有使用这个list)
list-q:消费队列,存放到期后的消息,提供消费

使用样例

public class DelayTest {
    public static void main(String[] args) throws InterruptedException, UnsupportedEncodingException {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);
        RBlockingQueue<String> blockingQueue = redisson.getBlockingQueue("dest_queue3");
        RDelayedQueue<String> delayedQueue = redisson.getDelayedQueue(blockingQueue);
        new Thread() {
            public void run() {
                while(true) {
                    try {
                        //阻塞队列有数据就返回,否则wait
                        System.err.println( blockingQueue.take()+" time="+System.currentTimeMillis());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();

        for(int i=1;i<=5;i++) {
            // 向阻塞队列放入数据
            delayedQueue.offer("fffffffff"+i, 10*i, TimeUnit.SECONDS);
        }
        System.out.println("over!!!");
    }
}

来源

Bitmap的原理和应用 - 知乎

巧用Bitmap 实现亿级海量数据统计 - 知乎

Redis位图实现7天连续签到_weixin_45970536的博客-CSDN博客_redis连续签到功能

实战!Redis 巧用数据类型实现亿级数据统计!_不才陈某的博客-CSDN博客

Redis的数据结构之List_笑我归无处的博客-CSDN博客_redis的list数据结构

redis | 六、redis之Set_雨中散步撒哈拉的博客-CSDN博客_redis set

redis计数操作_小周同学666的博客-CSDN博客_redis 计数

Redis实现分布式锁的7种方案_fyygree的博客-CSDN博客_redis分布式锁

Redisson 延迟队列实现原理探究_不如敲代码的博客-CSDN博客_redisson延迟队列实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值