📕 Redis 相关的业务场景实现
我打算创建一个专栏,主要用于结合八股文和各种场景题来进行代码实践,包括但不限制于集成各种中间件去实现对应的场景和工具封装,该文章为SpringBoot专栏的一个系列,希望大家观看以后帮我多多点赞评论。
大家的点赞和关注是我创作的动力,实属不易,谢谢大家~
本文主要实现了Redis的 多方案实现延迟队列、排行榜功能的实现、查找附近的人功能、 分布式限流、分布式Session 等 业务场景。
如果你们需要该markdown所有文档,请私信我,该专栏所有文章都是个人学习笔记,有一个20W字的所有文章汇总。
⚪ 多方案实现延迟队列
-
**keyExpirationEventMessageListener 监听过期key **
-
redis.conf 开启监听配置
notify-keyspace-events Ex
-
监听key的配置信息
/** * 监听过期key的配置信息 * @param redisMessageListenerContainer * @return */ @Bean public KeyExpirationEventMessageListener redisKeyExpirationListener(RedisMessageListenerContainer redisMessageListenerContainer) { return new KeyExpirationEventMessageListener(redisMessageListenerContainer); }
-
监听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