快捷搜索:  汽车  科技

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)结构类型结构存储的值结构的读写能力String字符串可以是字符串、整数或浮点数对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;List列表一个链表,链表上的每个节点都包含一个字符串对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;但是,基本的数据结构,在这里再列举一下,方便后续方案的理解:本文稍微有点点长,如果时间不够,建议看一下目录,收藏起来,用的时候再翻出来看看。测试源码:https://github.com/vehang/ehang-spring-boot/tree/main/spring-boot-011-redis本文假定你已经了解过Redis,并知晓Redis最基础的一些使用,如果你对Redis的基础API还不了解,可以先看一下菜鸟教程:https://www.runoob.com/redis,那么缓存部分及基础API的演示

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)(1)

后端程序员,不管是出去面试,还是当面试官,Redis几乎是100%会问到的技术点;究其原因,主要是因为他实在过于强大、使用率太高了;导致项目中几乎无处不在。

那Redis部分,不出意外,第一个问题就是:你做的项目,用Redis干了些啥?大部分人的回答都会是:缓存;当问到是否还有其他场景中使用时,部分用得少的朋友就会微微摇头;

其实也没错,redis绝不部分使用场景就是用来做缓存;但是,由于Redis 支持比较丰富的数据结构,因此他能实现的功能并不仅限于缓存,而是可以运用到各种业务场景中,开发出既简洁、又高效的系统;

下面整理了20种 Redis 的妙用场景,每个方案都用一个实际的业务需求并结合数据结构的API来讲解,希望大家能够理解其底层的实现方式,学会举一反三,并运用到项目的方方面面:

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)(2)

本文稍微有点点长,如果时间不够,建议看一下目录,收藏起来,用的时候再翻出来看看。

测试源码:https://github.com/vehang/ehang-spring-boot/tree/main/spring-boot-011-redis

1缓存

本文假定你已经了解过Redis,并知晓Redis最基础的一些使用,如果你对Redis的基础API还不了解,可以先看一下菜鸟教程:https://www.runoob.com/redis,那么缓存部分及基础API的演示,就不过多来讲解了;

但是,基本的数据结构,在这里再列举一下,方便后续方案的理解:

结构类型结构存储的值结构的读写能力String字符串可以是字符串、整数或浮点数对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;List列表一个链表,链表上的每个节点都包含一个字符串对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;

Set集合包含字符串的无序集合字符串的集合,包含基础的方法有:看是否存在、添加、获取、删除;还包含计算交集、并集、差集等Hash散列包含键值对的无序散列表包含方法有:添加、获取、删除单个元素Zset有序集合和散列一样,用于存储键值对字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有:添加、获取、删除单个元素以及根据分值范围或成员来获取元素

  • 依赖以下所有通过SpringBoot测试的用例,都需要引入 Redis 的依赖<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
2抽奖

曾几何时,抽奖是互联网APP热衷的一种推广、拉新的方式,节假日没有好的策划,那就抽个奖吧!一堆用户参与进来,然后随机抽取几个幸运用户给予实物/虚拟的奖品;此时,开发人员就需要写上一个抽奖的算法,来实现幸运用户的抽取;其实我们完全可以利用Redis的集合(Set),就能轻松实现抽奖的功能;

功能实现需要的API

  • SADD key member1 [member2]:添加一个或者多个参与用户;
  • SRANDMEMBER KEY [count]:随机返回一个或者多个用户;
  • SPOP key:随机返回一个或者多个用户,并删除返回的用户;

SRANDMEMBER 和 SPOP 主要用于两种不同的抽奖模式,SRANDMEMBER 适用于一个用户可中奖多次的场景(就是中奖之后,不从用户池中移除,继续参与其他奖项的抽取);而 SPOP 就适用于仅能中一次的场景(一旦中奖,就将用户从用户池中移除,后续的抽奖,就不可能再抽到该用户); 通常 SPOP 会用的会比较多。

Redis-cli 操作

127.0.0.1:6379> SADD raffle user1 (integer) 1 127.0.0.1:6379> SADD raffle user2 user3 user4 user5 user6 user7 user8 user9 user10 (integer) 9 127.0.0.1:6379> SRANDMEMBER raffle 2 1) "user5" 2) "user2" 127.0.0.1:6379> SPOP raffle 2 1) "user3" 2) "user4" 127.0.0.1:6379> SPOP raffle 2 1) "user10" 2) "user9" SpringBoot 实现

import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import Java.util.List; /** * @author 一行Java * @title: RaffleMain * @projectName ehang-spring-boot * @description: TODO * @date 2022/7/18 15:17 */ @Slf4j @SpringBootTest public class RaffleMain { private final String KEY_RAFFLE_PROFIX = "raffle:"; @Autowired RedisTemplate redisTemplate; @Test void test() { Integer raffleId = 1; join(raffleId 1000 1001 2233 7890 44556 74512); List lucky = lucky(raffleId 2); log.info("活动:{} 的幸运中奖用户是:{}" raffleId lucky); } public void join(Integer raffleId Integer... userIds) { String key = KEY_RAFFLE_PROFIX raffleId; redisTemplate.opsForSet().add(key userIds); } public List lucky(Integer raffleId long num) { String key = KEY_RAFFLE_PROFIX raffleId; // 随机抽取 抽完之后将用户移除奖池 List list = redisTemplate.opsForSet().pop(key num); // 随机抽取 抽完之后用户保留在池子里 //List list = redisTemplate.opsForSet().randomMembers(key num); return list; } } 3Set实现点赞/收藏功能

有互动属性APP一般都会有点赞/收藏/喜欢等功能,来提升用户之间的互动。

传统的实现:用户点赞之后,在数据库中记录一条数据,同时一般都会在主题库中记录一个点赞/收藏汇总数,来方便显示;

Redis方案:基于Redis的集合(Set),记录每个帖子/文章对应的收藏、点赞的用户数据,同时set还提供了检查集合中是否存在指定用户,用户快速判断用户是否已经点赞过

功能实现需要的API

  • SADD key member1 [member2]:添加一个或者多个成员(点赞)
  • SCARD key:获取所有成员的数量(点赞数量)
  • SISMEMBER key member:判断成员是否存在(是否点赞)
  • SREM key member1 [member2] :移除一个或者多个成员(点赞数量)
Redis-cli API操作

127.0.0.1:6379> sadd like:article:1 user1 (integer) 1 127.0.0.1:6379> sadd like:article:1 user2 (integer) 1 # 获取成员数量(点赞数量) 127.0.0.1:6379> SCARD like:article:1 (integer) 2 # 判断成员是否存在(是否点在) 127.0.0.1:6379> SISMEMBER like:article:1 user1 (integer) 1 127.0.0.1:6379> SISMEMBER like:article:1 user3 (integer) 0 # 移除一个或者多个成员(取消点赞) 127.0.0.1:6379> SREM like:article:1 user1 (integer) 1 127.0.0.1:6379> SCARD like:article:1 (integer) 1 SpringBoot 操作

import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; /** * @author 一行Java * @title: LikeMain * @projectName ehang-spring-boot * @description: TODO * @date 2022/7/18 15:38 */ @Slf4j @SpringBootTest public class LikeMain { private final String KEY_LIKE_ARTICLE_PROFIX = "like:article:"; @Autowired RedisTemplate redisTemplate; @Test void test() { Long articleId = 100; Long likeNum = like(articleId 1001 1002 2001 3005 4003); unLike(articleId 2001); likeNum = likeNum(articleId); boolean b2001 = isLike(articleId 2001); boolean b3005 = isLike(articleId 3005); log.info("文章:{} 点赞数量:{} 用户2001的点赞状态:{} 用户3005的点赞状态:{}" articleId likeNum b2001 b3005); } /** * 点赞 * * @param articleId 文章ID * @return 点赞数量 */ public Long like(Long articleId Integer... userIds) { String key = KEY_LIKE_ARTICLE_PROFIX articleId; Long add = redisTemplate.opsForSet().add(key userIds); return add; } public Long unLike(Long articleId Integer... userIds) { String key = KEY_LIKE_ARTICLE_PROFIX articleId; Long remove = redisTemplate.opsForSet().remove(key userIds); return remove; } public Long likeNum(Long articleId) { String key = KEY_LIKE_ARTICLE_PROFIX articleId; Long size = redisTemplate.opsForSet().size(key); return size; } public Boolean isLike(Long articleId Integer userId) { String key = KEY_LIKE_ARTICLE_PROFIX articleId; return redisTemplate.opsForSet().isMember(key userId); } } 4排行榜

排名、排行榜、热搜榜是很多APP、游戏都有的功能,常用于用户活动推广、竞技排名、热门信息展示等功能;

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)(3)

比如上面的热搜榜,热度数据来源于全网用户的贡献,但用户只关心热度最高的前50条。

常规的做法:就是将用户的名次、分数等用于排名的数据更新到数据库,然后查询的时候通过Order by limit 取出前50名显示,如果是参与用户不多,更新不频繁的数据,采用数据库的方式也没有啥问题,但是一旦出现爆炸性热点资讯(比如:大陆收复湾湾,xxx某些绿了等等),短时间会出现爆炸式的流量,瞬间的压力可能让数据库扛不住;

Redis方案:将热点资讯全页缓存,采用Redis的有序队列(Sorted Set)来缓存热度(SCORES),即可瞬间缓解数据库的压力,同时轻松筛选出热度最高的50条;

功能实现需要的命令

  • ZADD key score1 member1 [score2 member2]:添加并设置SCORES,支持一次性添加多个;
  • ZREVRANGE key start stop [WITHSCORES] :根据SCORES降序排列;
  • ZRANGE key start stop [WITHSCORES] :根据SCORES降序排列;
Redis-cli操作

# 单个插入 127.0.0.1:6379> ZADD ranking 1 user1 (integer) 1 # 批量插入 127.0.0.1:6379> ZADD ranking 10 user2 50 user3 3 user4 25 user5 (integer) 4 # 降序排列 不带SCORES 127.0.0.1:6379> ZREVRANGE ranking 0 -1 1) "user3" 2) "user5" 3) "user2" 4) "user4" 5) "user1" # 降序排列 带SCORES 127.0.0.1:6379> ZREVRANGE ranking 0 -1 WITHSCORES 1) "user3" 2) "50" 3) "user5" 4) "25" 5) "user2" 6) "10" 7) "user4" 8) "3" 9) "user1" 10) "1" # 升序 127.0.0.1:6379> ZRANGE ranking 0 -1 WITHSCORES 1) "user1" 2) "1" 3) "user4" 4) "3" 5) "user2" 6) "10" 7) "user5" 8) "25" 9) "user3" 10) "50" SpringBoot操作

import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.DefaultTypedTuple; import org.springframework.data.redis.core.RedisTemplate; import java.util.Set; /** * @author 一行Java * @title: RankingTest * @projectName ehang-spring-boot * @description: TODO * @date 2022/7/18 15:54 */ @SpringBootTest @Slf4j public class RankingTest { private final String KEY_RANKING = "ranking"; @Autowired RedisTemplate redisTemplate; @Test void test() { add(1001 (double) 60); add(1002 (double) 80); add(1003 (double) 100); add(1004 (double) 90); add(1005 (double) 70); // 取所有 Set<DefaultTypedTuple> range = range(0 -1); log.info("所有用户排序:{}" range); // 前三名 range = range(0 2); log.info("前三名排序:{}" range); } public Boolean add(Integer userId Double score) { Boolean add = redisTemplate.opsForZSet().add(KEY_RANKING userId score); return add; } public Set<DefaultTypedTuple> range(long min long max) { // 降序 Set<DefaultTypedTuple> set = redisTemplate.opsForZSet().reverseRangeWithScores(KEY_RANKING min max); // 升序 //Set<DefaultTypedTuple> set = redisTemplate.opsForZSet().rangeWithScores(KEY_RANKING min max); return set; } }

输出

所有用户排序:[DefaultTypedTuple [score=100.0 value=1003] DefaultTypedTuple [score=90.0 value=1004] DefaultTypedTuple [score=80.0 value=1002] DefaultTypedTuple [score=70.0 value=1005] DefaultTypedTuple [score=60.0 value=1001]] 前三名排序:[DefaultTypedTuple [score=100.0 value=1003] DefaultTypedTuple [score=90.0 value=1004] DefaultTypedTuple [score=80.0 value=1002]] 5PV统计(incr自增计数)

Page View(PV)指的是页面浏览量,是用来衡量流量的一个重要标准,也是数据分析很重要的一个依据;通常统计规则是页面被展示一次,就加一

功能所需命令

  • INCR:将 key 中储存的数字值增一
Redis-cli 操作

127.0.0.1:6379> INCR pv:article:1 (integer) 1 127.0.0.1:6379> INCR pv:article:1 (integer) 2 6UV统计(HeyperLogLog)

前面,介绍了通过(INCR)方式来实现页面的PV;除了PV之外,UV(独立访客)也是一个很重要的统计数据;

但是如果要想通过计数(INCR)的方式来实现UV计数,就非常的麻烦,增加之前,需要判断这个用户是否访问过;那判断依据就需要额外的方式再进行记录。

你可能会说,不是还有Set嘛!一个页面弄个集合,来一个用户塞(SADD)一个用户进去,要统计UV的时候,再通过SCARD汇总一下数量,就能轻松搞定了;此方案确实能实现UV的统计效果,但是忽略了成本;如果是普通页面,几百、几千的访问,可能造成的影响微乎其微,如果一旦遇到爆款页面,动辄上千万、上亿用户访问时,就一个页面UV将会带来非常大的内存开销,对于如此珍贵的内存来说,这显然是不划算的。

此时,HeyperLogLog数据结构,就能完美的解决这一问题,它提供了一种不精准的去重计数方案,注意!这里强调一下,是不精准的,会存在误差,不过误差也不会很大,**标准的误差率是0.81%**,这个误差率对于统计UV计数,是能够容忍的;所以,不要将这个数据结构拿去做精准的去重计数。

另外,HeyperLogLog 是会占用12KB的存储空间,虽然说,Redis 对 HeyperLogLog 进行了优化,在存储数据比较少的时候,采用了稀疏矩阵存储,只有在数据量变大,稀疏矩阵空间占用超过阈值时,才会转为空间为12KB的稠密矩阵;相比于成千、上亿的数据量,这小小的12KB,简直是太划算了;但是还是建议,不要将其用于数据量少,且频繁创建 HeyperLogLog 的场景,避免使用不当,造成资源消耗没减反增的不良效果。

功能所需命令:

  • PFADD key element [element ...]:增加计数(统计UV)
  • PFCOUNT key [key ...]:获取计数(货物UV)
  • PFMERGE destkey sourcekey [sourcekey ...]:将多个 HyperLogLog 合并为一个 HyperLogLog(多个合起来统计)
Redis-cli 操作

# 添加三个用户的访问 127.0.0.1:6379> PFADD uv:page:1 user1 user2 user3 (integer) 1 # 获取UV数量 127.0.0.1:6379> PFCOUNT uv:page:1 (integer) 3 # 再添加三个用户的访问 user3是重复用户 127.0.0.1:6379> PFADD uv:page:1 user3 user4 user5 (integer) 1 # 获取UV数量 user3是重复用户 所以这里返回的是5 127.0.0.1:6379> PFCOUNT uv:page:1 (integer) 5SpringBoot操作HeyperLogLog

模拟测试10000个用户访问id为2的页面

import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; /** * @author 一行Java * @title: HeyperLogLog 统计UV * @projectName ehang-spring-boot * @description: TODO * @date 2022/7/19 16:13 */ @SpringBootTest @Slf4j public class UVTest { private final String KEY_UV_PAGE_PROFIX = "uv:page:"; @Autowired RedisTemplate redisTemplate; @Test public void uvTest() { Integer pageId = 2; for (int i = 0; i < 10000; i ) { uv(pageId i); } for (int i = 0; i < 10000; i ) { uv(pageId i); } Long uv = getUv(pageId); log.info("pageId:{} uv:{}" pageId uv); } /** * 用户访问页面 * @param pageId * @param userId * @return */ private Long uv(Integer pageId Integer userId) { String key = KEY_UV_PAGE_PROFIX pageId; return redisTemplate.opsForHyperLogLog().add(key userId); } /** * 统计页面的UV * @param pageId * @return */ private Long getUv(Integer pageId) { String key = KEY_UV_PAGE_PROFIX pageId; return redisTemplate.opsForHyperLogLog().size(key); } }

日志输出

pageId:2 uv:10023

由于存在误差,这里访问的实际访问的数量是1万,统计出来的多了23个,在标准的误差(0.81%)范围内,加上UV数据不是必须要求准确,因此这个误差是可以接受的。

7去重(BloomFiler)

通过上面HeyperLogLog的学习,我们掌握了一种不精准的去重计数方案,但是有没有发现,他没办法获取某个用户是否访问过;理想中,我们是希望有一个PFEXISTS的命令,来判断某个key是否存在,然而HeyperLogLog并没有;要想实现这一需求,就得 BloomFiler 上场了。

  • 什么是Bloom Filter?Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。 通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。 基于一种概率数据结构来实现,是一个有趣且强大的算法。

举个例子:假如你写了一个爬虫,用于爬取网络中的所有页面,当你拿到一个新的页面时,如何判断这个页面是否爬取过?

普通做法:每爬取一个页面,往数据库插入一行数据,记录一下URL,每次拿到一个新的页面,就去数据库里面查询一下,存在就说明爬取过;

普通做法的缺点:少量数据,用传统方案没啥问题,如果是海量数据,每次爬取前的检索,将会越来越慢;如果你的爬虫只关心内容,对来源数据不太关心的话,这部分数据的存储,也将消耗你很大的物理资源;

此时通过 BloomFiler 就能以很小的内存空间作为代价,即可轻松判断某个值是否存在。

同样,BloomFiler 也不那么精准,在默认参数情况下,是存在1%左右的误差;但是 BloomFiler 是允许通过error_rate(误差率)以及initial_size(预计大小)来设置他的误差比例

  • error_rate:误差率,越低,需要的空间就越大;
  • initial_size:预计放入值的数量,当实际放入的数量大于设置的值时,误差率就会逐渐升高;所以为了避免误差率,可以提前做好估值,避免再次大的误差;
BloomFiler 安装

为了方便测试,这里使用 Docker 快速安装

docker run -d -p 6379:6379 redislabs/rebloom

功能所需的命令

  • bf.add 添加单个元素
  • bf.madd 批量田间
  • bf.exists 检测元素是否存在
  • bf.mexists 批量检测
Redis-cli操作

127.0.0.1:6379> bf.add web:crawler baidu (integer) 1 127.0.0.1:6379> bf.madd web:crawler tencent bing 1) (integer) 1 2) (integer) 1 127.0.0.1:6379> bf.exists web:crawler baidu (integer) 1 127.0.0.1:6379> bf.mexists web:crawler baidu 163 1) (integer) 1 2) (integer) 0 SpringBoot整合

  • 工具类 RedisBloomimport org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.data.redis.core.script.RedisScript;
    import org.springframework.stereotype.Component;
    import java.util.Arrays;
    import java.util.List;
    /**
    * redis布隆过滤器
    *
    * @author 一行Java
    * @title: RedisBloom
    * @projectName ehang-spring-boot
    * @description: TODO
    * @date 2022/7/19 17:03
    */
    @Component
    public class RedisBloom {
    private static RedisScript<Boolean> bfreserveScript = new DefaultRedisScript<>("return redis.call('bf.reserve' KEYS[1] ARGV[1] ARGV[2])" Boolean.class);
    private static RedisScript<Boolean> bfaddScript = new DefaultRedisScript<>("return redis.call('bf.add' KEYS[1] ARGV[1])" Boolean.class);
    private static RedisScript<Boolean> bfexistsScript = new DefaultRedisScript<>("return redis.call('bf.exists' KEYS[1] ARGV[1])" Boolean.class);
    private static String bfmaddScript = "return redis.call('bf.madd' KEYS[1] %s)";
    private static String bfmexistsScript = "return redis.call('bf.mexists' KEYS[1] %s)";
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
    * 设置错误率和大小(需要在添加元素前调用,若已存在元素,则会报错)
    * 错误率越低,需要的空间越大
    *
    * @param key
    * @param errorRate 错误率,默认0.01
    * @param initialSize 默认100,预计放入的元素数量,当实际数量超出这个数值时,误判率会上升,尽量估计一个准确数值再加上一定的冗余空间
    * @return
    */
    public Boolean bfreserve(String key double errorRate int initialSize) {
    return redisTemplate.execute(bfreserveScript Arrays.asList(key) String.valueOf(errorRate) String.valueOf(initialSize));
    }
    /**
    * 添加元素
    *
    * @param key
    * @param value
    * @return true表示添加成功,false表示添加失败(存在时会返回false)
    */
    public Boolean bfadd(String key String value) {
    return redisTemplate.execute(bfaddScript Arrays.asList(key) value);
    }
    /**
    * 查看元素是否存在(判断为存在时有可能是误判,不存在是一定不存在)
    *
    * @param key
    * @param value
    * @return true表示存在,false表示不存在
    */
    public Boolean bfexists(String key String value) {
    return redisTemplate.execute(bfexistsScript Arrays.asList(key) value);
    }
    /**
    * 批量添加元素
    *
    * @param key
    * @param values
    * @return 按序 1表示添加成功,0表示添加失败
    */
    public List<Integer> bfmadd(String key String... values) {
    return (List<Integer>) redisTemplate.execute(this.generateScript(bfmaddScript values) Arrays.asList(key) values);
    }
    /**
    * 批量检查元素是否存在(判断为存在时有可能是误判,不存在是一定不存在)
    *
    * @param key
    * @param values
    * @return 按序 1表示存在,0表示不存在
    */
    public List<Integer> bfmexists(String key String... values) {
    return (List<Integer>) redisTemplate.execute(this.generateScript(bfmexistsScript values) Arrays.asList(key) values);
    }
    private RedisScript<List> generateScript(String script String[] values) {
    StringBuilder sb = new StringBuilder();
    for (int i = 1; i <= values.length; i ) {
    if (i != 1) {
    sb.append(" ");
    }
    sb.append("ARGV[").append(i).append("]");
    }
    return new DefaultRedisScript<>(String.format(script sb.toString()) List.class);
    }
    }
  • 测试import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    import java.util.List;
    /**
    * @author 一行Java
    * @title: BFTest
    * @projectName ehang-spring-boot
    * @description: TODO
    * @date 2022/7/19 17:04
    */
    @SpringBootTest
    @Slf4j
    public class BFTest {
    private final String KEY_WEB_CRAWLER = "web:crawler1";
    @Autowired
    RedisBloom bloom;
    @Autowired
    RedisTemplate redisTemplate;
    @Test
    public void test() {
    Boolean hasKey = redisTemplate.hasKey(KEY_WEB_CRAWLER);
    log.info("bloom hasKey:{}" hasKey);
    if (!hasKey) {
    // 不存在的时候 再去初始化
    Boolean bfreserve = bloom.bfreserve(KEY_WEB_CRAWLER 0.0001 10000);
    log.info("bloom bfreserve:{}" bfreserve);
    }
    List<Integer> madd = bloom.bfmadd(KEY_WEB_CRAWLER "baidu" "google");
    log.info("bloom bfmadd:{}" madd);
    Boolean baidu = bloom.bfexists(KEY_WEB_CRAWLER "baidu");
    log.info("bloom bfexists baidu:{}" baidu);
    Boolean bing = bloom.bfexists(KEY_WEB_CRAWLER "bing");
    log.info("bloom bfexists bing:{}" bing);
    }
    }
    日志输出com.ehang.redis.bloom_filter.BFTest : bloom hasKey:false
    com.ehang.redis.bloom_filter.BFTest : bloom bfreserve:true
    com.ehang.redis.bloom_filter.BFTest : bloom bfmadd:[1 1]
    com.ehang.redis.bloom_filter.BFTest : bloom bfexists baidu:true
    com.ehang.redis.bloom_filter.BFTest : bloom bfexists bing:false
8用户签到(BitMap)

很多APP为了拉动用户活跃度,往往都会做一些活动,比如连续签到领积分/礼包等等

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)(4)

传统做法:用户每次签到时,往是数据库插入一条签到数据,展示的时候,把本月(或者指定周期)的签到数据获取出来,用于判断用户是否签到、以及连续签到情况;此方式,简单,理解容易;

Redis做法:由于签到数据的关注点就2个:是否签到(0/1)、连续性,因此就完全可以利用BitMap(位图)来实现;

redis 缓存什么数据(Redis只会用缓存20种妙用让同事直呼惊艳)(5)

猜您喜欢: