【任雨杰真帅啊】 8. SpringBoot 集成Redis集群及业务场景 (八)

本文介绍了如何在SpringBoot项目中结合Redis实现延迟队列、排行榜、查找附近的人功能、分布式限流和分布式Session,通过ZSet和Redisson等工具,详细展示了代码示例和应用场景。
摘要由CSDN通过智能技术生成

📕 Redis 相关的业务场景实现

我打算创建一个专栏,主要用于结合八股文和各种场景题来进行代码实践,包括但不限制于集成各种中间件去实现对应的场景和工具封装,该文章为SpringBoot专栏的一个系列,希望大家观看以后帮我多多点赞评论。

大家的点赞和关注是我创作的动力,实属不易,谢谢大家~


本文主要实现了Redis的 多方案实现延迟队列排行榜功能的实现查找附近的人功能分布式限流分布式Session 等 业务场景。


如果你们需要该markdown所有文档,请私信我,该专栏所有文章都是个人学习笔记,有一个20W字的所有文章汇总。

多方案实现延迟队列
  • **keyExpirationEventMessageListener 监听过期key **

    1. redis.conf 开启监听配置

      notify-keyspace-events Ex
      
    2. 监听key的配置信息

          /**
           *  监听过期key的配置信息
           * @param redisMessageListenerContainer
           * @return
           */
          @Bean
          public KeyExpirationEventMessageListener redisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) {
              return new KeyExpirationEventMessageListener(redisMessageListenerContainer);
          }
      
    3. 监听rediskey的事件

      @Component
      public class MyRedisKeyExpiredEventListener implements ApplicationListener<RedisKeyExpiredEvent> {
      
          @Override
          public void onApplicationEvent(RedisKeyExpiredEvent event) {
              byte[] body = event.getSource();
              System.out.println("获取到延迟消息:" + new String(body));
              System.out.println("任雨杰真他娘的帅啊");
          }
      
      }
      
  • 利用zset 实现延迟队列

       private ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
    
    
        /**
         *  利用zset 的source 进行延迟消息删除,
         * @return
         */
        @PostMapping("/cacheBreak6")
        @ResponseBody
        public String cacheBreak6(){
    		/**
    		* 存储当前的时间戳
    		*/
            redisTemplate.opsForZSet().add("yjren:china:shanxi","yjren",System.currentTimeMillis() + 60*1000);
            scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
    
                    Set<String> result = redisTemplate.opsForZSet().rangeByScore("yjren:china:shanxi", System.currentTimeMillis() - 10 * 1000, System.currentTimeMillis());
                    if (null != result) {
                        result.forEach(e->{
                            System.out.println("过期的元素为:【" + e + "】");
                        });
                    }
                }
            },1,10,TimeUnit.SECONDS);
    
            return "任雨杰真帅啊";
        }
    
    

    案例二

    @Component
    public class DelayQueueUtils {
    
        @Resource
        private RedisTemplate<String,String> redisTemplate;
    
    
        public void delay(String key,String msg) {
            redisTemplate.opsForZSet().add(key, msg,System.currentTimeMillis() + 5000); // 塞入延时队列 ,5s 后再试
        }
    
        public void loop(String key){
            while(!Thread.interrupted()){
                Set<String> values = redisTemplate.opsForZSet().rangeByScore(key, 0, System.currentTimeMillis(), 0, 1);
                if (values.isEmpty()) {
                    try {
                        Thread.sleep(500); // 歇会继续
                    } catch (InterruptedException e) {
                        break;
                    }
                    continue;
                }
                String value = values.iterator().next();
                if (0 < redisTemplate.opsForZSet().remove(key, value)){
                    System.out.println("该线程抢到了这个程序:" + value);
                }
            }
        }
    }
    
    
        @Resource
        private DelayQueueUtils delayQueueUtils;
    
        @Test
        public void delayQueue(){
    
            String key = "DELAY_QUEUE";
    
            Thread producer = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        delayQueueUtils.delay(key,"codehole" + i);
                    }
                }
            };
    
    
            Thread consumer = new Thread() {
                @Override
                public void run() {
                    delayQueueUtils.loop(key);
                }
            };
            producer.start();
            consumer.start();
    
            try {
                producer.join();
                Thread.sleep(6000);
                consumer.interrupt();
                consumer.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    
  • Redission实现延迟队列

        @Resource
        private RDelayedQueue rDelayedQueue;
    
        @Resource
        private RedissonClient redissonClient;
    
        @Test
        public void delayQueueRedission() throws InterruptedException {
            // 添加延迟任务
            rDelayedQueue.offer("Task1", 5000,TimeUnit.SECONDS); // 5秒延迟
            rDelayedQueue.offer("Task2", 10000,TimeUnit.SECONDS); // 10秒延迟
    
    
            RBlockingQueue<String> queue = redissonClient.getBlockingQueue("redisson_delay_Queue");
    
    
    
            int number = 2;
    
            String take = queue.take();
            System.out.println("processing task" + take);
            number--;
            System.out.println("任务正在进行时,任雨杰还是有点小帅的");
            Thread.sleep(2*1000);
    
    //        do {
    //
    //        } while (number != 0);
            System.out.println("任务截止了,任雨杰真帅啊");
        }
    }
    
⚪ 排行榜功能的实现
  • Zset功能的使用

    	/**
    	* 排行榜功能主要是使用zset 进行有序排列
    	*/
    	@Test
        public void billBoard(){
    
            //增加数据
            Set<ZSetOperations.TypedTuple<String>> values = new HashSet<>();
            values.add(ZSetOperations.TypedTuple.of("shanxi",5.0));
            values.add(ZSetOperations.TypedTuple.of("china",9.0));
            values.add(ZSetOperations.TypedTuple.of("beijing",7.0));
            values.add(ZSetOperations.TypedTuple.of("daning",1.0));
    
            //添加到zset中,
            redisTemplate.opsForZSet().add("Yjren",values);
    
    
            System.out.println(redisTemplate.opsForZSet().incrementScore("Yjren","shanxi2222",3.0));
    
            //增量添加,有则增量,没有则新增
            redisTemplate.opsForZSet().incrementScore("Yjren", "shanxi", 3.0);
            redisTemplate.opsForZSet().incrementScore("Yjren", "fenghuangcheng", 4.0);
    
            System.out.println(redisTemplate.opsForZSet().rank("Yjren", "fenghuangcheng"));
    
            System.out.println(redisTemplate.opsForZSet().rank("Yjren", "yuncheng"));
    
            System.out.println(redisTemplate.opsForZSet().reverseRank("Yjren", "shanxi"));
    
    
            System.out.println(redisTemplate.opsForZSet().score("Yjren", "beijing"));
    
            //获取元素在某区间的值
            System.out.println(redisTemplate.opsForZSet().rangeByScore("Yjren", 0, 100));
    
            System.out.println(redisTemplate.opsForZSet().count("Yjren", 0, 5));
    
        }
    
  • 相关功能的实现

        /**
         *  利用zset 的source 进行延迟消息删除,
         * @return
         */
        @PostMapping("/cacheBreak7")
        @ResponseBody
        public String cacheBreak7(){
    
            int number = (int)(1+Math.random()*10);
            String key = "yujie" + number;
    
            //增量添加,不存在则直接添加,存在则增量添加
            redisTemplate.opsForZSet().incrementScore("RenYuJie",key,1.0);
    
            /**
             *  后续可以设置定时任务,将排行榜更新到数据库,主要看该排行榜是多久更新一次,时效性是否足够
             *
             */
    
            return "任雨杰真帅啊" + key;
        }
    
    
  • 测试

        @Test
        public void billBoard100(){
            Set<String> renYuJie = redisTemplate.opsForZSet().reverseRange("RenYuJie", 0, 100);
    
            renYuJie.forEach(e->{
                System.out.println(e);
            });
        }
    
⚪ 查找附近的人功能
  •     @Test
        public void geoHashTest(){
    
            Point point = new Point(13.361389,38.115556);
            Point point2 = new Point(13.361389,38.115558);
    
            redisTemplate.opsForGeo().add("GeoTest",point,"yjren");
            redisTemplate.opsForGeo().add("GeoTest",point2,"xiaoru");
    
    
            System.out.println("该元素的地理位置为:" + redisTemplate.opsForGeo().position("GeoTest", "yjren"));
    
            //计算两个成员之间的距离
            System.out.println(redisTemplate.opsForGeo().distance("GeoTest", "yjren", "xiaoru"));
    
            //根据中心点返回指定返回的成员
            GeoResults<RedisGeoCommands.GeoLocation<String>> geoTest = redisTemplate.opsForGeo().radius("GeoTest", new Circle(new Point(13.361389, 38.115558), new Distance(200, Metrics.KILOMETERS)));
    
    
        }
    
    
分布式限流
  • 固定窗口限流

      /**
         * 固定窗口限流
         * 每分钟限定时间请求数为五次,超过五次后限流
         */
        @PostMapping("/cacheBreak8")
        @ResponseBody
        public ResponseEntity<String> cacheBreak8(){
    
            String result = "任雨杰真帅啊" + formatter.format(LocalDateTime.now());
    
            if (5 > redisTemplate.opsForValue().increment(formatter.format(LocalDateTime.now()) + "fixed")){
                return new ResponseEntity<>(result,HttpStatus.OK);
            }
            return new ResponseEntity<>("对不起超过请求限制了",HttpStatus.BAD_REQUEST);
        }
    
    
  • 滑动窗口限

        /**
         * 滑动窗口限流
         * 根据每个请求当前时间戳-五秒,来进行判断预计的十秒之内的请求数量
         */
        @PostMapping("/cacheBreak9")
        @ResponseBody
        public ResponseEntity<String> cacheBreak9(){
            long time = System.currentTimeMillis() - 5 * 1000;
    
            String result = "任雨杰真帅啊" + formatter.format(LocalDateTime.now());
            redisTemplate.setEnableTransactionSupport(true);
            try {
                redisTemplate.multi();
                //这里应该用lua脚本将判断和添加的操作合二为一的奈何我不会,就先这样吧
                Long number = redisTemplate.opsForZSet().count("Yjren", time, time + 10 * 1000);
                if (10 > number) {
                    redisTemplate.opsForZSet().add("Yjren", time + "renYuJie", (double) time);
                    return new ResponseEntity<>(result + "number的值为:" + number, HttpStatus.OK);
                }
            }catch (Exception e){
                redisTemplate.discard();
            }
            return new ResponseEntity<>("对不起超过请求限制了",HttpStatus.BAD_REQUEST);
    
        }
    
    
分布式Session
  • 在单机Tomcat中,我们通常会使用session会话来存储服务器和浏览器之间的数据信息,sessionId存储到浏览器之中,sessionValue存储到内存中,方便我们去获取信息。但是当多个Tomcat的时候,单机可能存在问题。这个时候我们可以考虑以下解决方案:① Nginx 绑定IP,同一个IP下发到同一个Tomcat上,② redis 分布式session解决方案。 我们下面演示redis的解决方案:

    该实例图片采用 凯哥Java个人博客

    例子为:redis缓存登录用户的个人信息,存储用户信息返回token

    e44e9661d6e2f3d8f501dba50a448535.png

  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值