• Redis 各种用法总结,你知道几种?


    前言

    Redis绝不部分使用场景就是用来做缓存;但是,由于Redis 支持比较丰富的数据结构,因此他能实现的功能并不仅限于缓存,而是可以运用到各种业务场景中,开发出既简洁、又高效的系统;
    下面整理了几种 Redis 的妙用场景,每个方案都用一个实际的业务需求并结合数据结构的API来讲解,希望大家能够理解其底层的实现方式,学会举一反三,并运用到项目的方方面面:

    抽奖

    曾几何时,抽奖是互联网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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 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;
    
    /**
     */
    @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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    Set实现点赞/收藏功能

    有互动属性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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 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;
    
    @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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    排行榜

    排名、排行榜、热搜榜是很多APP、游戏都有的功能,常用于用户活动推广、竞技排名、热门信息展示等功能;
    在这里插入图片描述
    比如上面的热搜榜,热度数据来源于全网用户的贡献,但用户只关心热度最高的前50条。
    **常规的做法:**就是将用户的名次、分数等用于排名的数据更新到数据库,然后查询的时候通过Order by + limit 取出前50名显示,如果是参与用户不多,更新不频繁的数据,采用数据库的方式也没有啥问题,但是一旦出现爆炸性热点资讯,短时间会出现爆炸式的流量,瞬间的压力可能让数据库扛不住;
    **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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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;
    
    /**
     */
    @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 set = redisTemplate.opsForZSet().rangeWithScores(KEY_RANKING, min, max);
            return set;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    PV统计(incr自增计数)

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

    127.0.0.1:6379> INCR pv:article:1
    (integer) 1
    127.0.0.1:6379> INCR pv:article:1
    (integer) 2
    
    • 1
    • 2
    • 3
    • 4

    UV统计(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) 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    SpringBoot操作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;
    
    /**
     */
    @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    日志输出

    pageId:2 uv:10023
    
    • 1

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

    简单限流

    为了保证项目的安全稳定运行,防止被恶意的用户或者异常的流量打垮整个系统,一般都会加上限流,比如常见的sential、hystrix,都是实现限流控制;如果项目用到了Redis,也可以利用Redis,来实现一个简单的限流功能;
    功能所需命令
    INCR:将 key 中储存的数字值增一
    Expire:设置key的有效期
    Redis-cli操作

    127.0.0.1:6379> INCR r:f:user1
    (integer) 1
    # 第一次 设置一个过期时间
    127.0.0.1:6379> EXPIRE r:f:user1 5
    (integer) 1
    127.0.0.1:6379> INCR r:f:user1
    (integer) 2
    # 等待5s 再次增加 发现已经重置了
    127.0.0.1:6379> INCR r:f:user1
    (integer) 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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.concurrent.TimeUnit;
    
    
    @SpringBootTest
    @Slf4j
    public class FreqTest {
        // 单位时间(秒)
        private static final Integer TIME = 5;
        // 允许访问上限次数
        private static final Integer MAX = 100;
    
        @Autowired
        RedisTemplate redisTemplate;
    
        @Test
        public void test() throws Exception {
            String userName = "user1";
    
            int tag = 1;
    
            boolean frequency = frequency(userName);
            log.info("第{}次是否放行:{}", tag, frequency);
            for (int i = 0; i < 100; i++) {
                tag += 1;
                frequency(userName);
            }
            frequency = frequency(userName);
            log.info("第{}次是否放行:{}", tag, frequency);
    
            Thread.sleep(5000);
            frequency = frequency(userName);
            log.info("模拟等待5s后,第{}次是否放行:{}", tag, frequency);
        }
    
        /**
         * 校验访问频率
         *
         * @param uniqueId 用于限流的唯一ID 可以是用户ID、或者客户端IP等
         * @return true:放行  false:拦截
         */
        private boolean frequency(String uniqueId) {
            String key = "r:q:" + uniqueId;
            Long increment = redisTemplate.opsForValue().increment(key);
            if (increment == 1) {
                redisTemplate.expire(key, TIME, TimeUnit.SECONDS);
            }
    
            if (increment <= MAX) {
                return true;
            }
    
            return false;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    布隆过滤(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:**预计放入值的数量,当实际放入的数量大于设置的值时,误差率就会逐渐升高;所以为了避免误差率,可以提前做好估值,避免再次大的误差;
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    SpringBoot整合

    import 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布隆过滤器
     *
     */
    @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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    测试

    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;
    
    /**
     */
    @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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    总结

    到这里就列出来几种Redis 的用法,只也是几种基本用法,有的来源于网上。

  • 相关阅读:
    电子器件系列38:mos管散热片
    【代码随想录】算法训练计划15
    Python Django框架中文教程:学习简单、灵活、高效的Web应用程序框架
    call apply bind的作用及区别? 应用场景?
    【嵌入式开发 Linux 常用命令系列 7.1 -- awk 过滤列中含有特定字符的行】
    2款Notepad++平替工具(实用、跨平台的文本编辑器)
    中国微信生态行业投资价值分析及发展趋势预测报告
    JavaSE成神之路 - 我创建一个引用后赋值对象(必看)
    RabbitMQ安装
    MQ - 37 云原生:MQ的分层存储架构的实现方案
  • 原文地址:https://blog.csdn.net/houxian1103/article/details/128029148