• [Redis] Redis实战


    ✨✨个人主页:沫洺的主页

    📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏 

                               📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏

                               📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏     

    💖💖如果文章对你有所帮助请留下三连✨✨

    💌RedisTemplate使用Scan 

    1. @SpringBootTest
    2. public class AppTests_Scan {
    3. @Resource(name = "redisTemplate")
    4. private ValueOperations valueOperations;
    5. @Autowired
    6. private StringRedisTemplate stringRedisTemplate;
    7. @Test
    8. void test1() {
    9. for (int i =1 ;i<11;i++){
    10. User user = User.builder().name(i+"号张").age(18).build();
    11. valueOperations.set("user."+i,user);
    12. }
    13. }
    14. }

    原生的scan用法

    RedisTemplate使用Scan 

    1. @SpringBootTest
    2. public class AppTests_Scan {
    3. @Resource(name = "redisTemplate")
    4. private ValueOperations valueOperations;
    5. @Autowired
    6. private StringRedisTemplate stringRedisTemplate;
    7. @Test
    8. void test1() {
    9. for (int i =1 ;i<11;i++){
    10. User user = User.builder().name(i+"号张").age(18).build();
    11. valueOperations.set("user."+i,user);
    12. }
    13. }
    14. @Test
    15. void test2(){
    16. //迭代扫描,指定模糊匹配,预想数量
    17. ScanOptions scanOptions = ScanOptions.scanOptions().match("user.*").count(3).build();
    18. //获取游标
    19. Cursor cursor = stringRedisTemplate.scan(scanOptions);
    20. while (cursor.hasNext()){
    21. //获取游标id
    22. System.out.println(cursor.getCursorId());
    23. System.out.println(cursor.next());
    24. }
    25. cursor.close();
    26. }
    27. }

    💌模拟多线程并发使用Decrby 

    模拟高并发场景线上购物,多个用户(线程)同时选购某产品,提示库存信息

    先初始化产品库存

    1. @SpringBootTest
    2. class AppTests_DecrBy {
    3. @Resource(name = "redisTemplate")
    4. private ValueOperations valueOperations;
    5. @Autowired
    6. private StringRedisTemplate stringRedisTemplate;
    7. //定义一个产品
    8. private final static String productKey = "product01";
    9. @Test
    10. void test1() {
    11. //设置初始化库存5个
    12. valueOperations.set(productKey, 5);
    13. }
    14. }

    不考虑并发的情况下

    1. @Test
    2. void test2() throws InterruptedException {
    3. //获取线程池
    4. ExecutorService executorService = Executors.newCachedThreadPool();
    5. //模拟开启10个线程
    6. for (int i = 1; i <= 10; i++) {
    7. //开启线程执行
    8. executorService.execute(() -> {
    9. //获取库存
    10. Integer qty = valueOperations.get(productKey);
    11. //如果库存充足,产品数量就减一
    12. if (qty > 0) {
    13. qty = qty - 1;
    14. //重新设置库存
    15. valueOperations.set(productKey, qty);
    16. System.out.println(Thread.currentThread().getName() + " 库存充足");
    17. } else {
    18. System.out.println(Thread.currentThread().getName() + " 库存不足");
    19. }
    20. });
    21. }
    22. //防止主线程停止,导致模拟线程不执行
    23. Thread.sleep(60 * 1000);
    24. }

    出现问题,明明库存只有5个,10个用户却都提示库存充足

    考虑高并发的情况下 

    使用decrement来保证原子性,解决高并发问题

    1. @Test
    2. void test3() throws InterruptedException {
    3. //获取线程池
    4. ExecutorService executorService = Executors.newCachedThreadPool();
    5. //模拟开启10个线程
    6. for (int i = 1; i <= 10; i++) {
    7. //开启线程执行
    8. executorService.execute(() -> {
    9. //获取库存
    10. Long qty = valueOperations.decrement(productKey, 1);
    11. if (qty >= 0) {
    12. System.out.println(Thread.currentThread().getName() + " 库存充足");
    13. } else {
    14. System.out.println(Thread.currentThread().getName() + " 库存不足");
    15. }
    16. });
    17. }
    18. //防止主线程停止,导致模拟线程不执行
    19. Thread.sleep(60 * 1000);
    20. }

    解决了并发问题但是出现了新的问题,库存量为负数不合理(意思就是某个线程在执行删减库存时,其他线程也在同一时刻去执行,没有保障redis原子性问题)

    有一种解决办法就是通过加锁synchronized来解决,但是响应能力就会大幅降低,也就失去了redis高性能的意义

    所以最好的解决方式就是通过EVAL命令执行Lua脚本来解决这类问题

    💌模拟多线程并发获取分布式锁SetNX

    模拟某一个产品的出入库单据只能一个人(线程)操作的场景(分布式锁),当有人操作时,其他人只能等待该人操作结束之后进行操作

    1. @SpringBootTest
    2. @Slf4j
    3. class AppTests_SetNx {
    4. @Resource(name = "redisTemplate")
    5. private ValueOperations valueOperations;
    6. @Autowired
    7. private StringRedisTemplate stringRedisTemplate;
    8. //定义一个产品
    9. private final static String productKey = "product01";
    10. //定义锁
    11. private final static String lockKey = "lock.1";
    12. //业务
    13. private void doing(){
    14. while (true){
    15. //设置锁
    16. Boolean b = valueOperations.setIfAbsent(lockKey, 1);
    17. if(b){
    18. log.info(Thread.currentThread().getName() + " 获取到分布式锁");
    19. //业务代码开始
    20. ThreadUtil.sleep(3000);//模拟执行业务操作用时
    21. //业务代码结束
    22. stringRedisTemplate.delete(lockKey);
    23. log.info(Thread.currentThread().getName() + " 释放分布式锁");
    24. break;
    25. } else {
    26. //log.info(Thread.currentThread().getName() + " 没有获取到分布式锁,开始睡眠");
    27. ThreadUtil.sleep(2000);
    28. }
    29. }
    30. }
    31. @Test
    32. void test1() {
    33. //设置初始化库存5个
    34. valueOperations.set(productKey, 0);
    35. }
    36. @Test
    37. void test2() throws InterruptedException {
    38. //获取线程池
    39. ExecutorService executorService = Executors.newCachedThreadPool();
    40. //模拟开启10个线程
    41. for (int i = 1; i <= 10; i++) {
    42. //开启线程执行
    43. executorService.execute(this::doing);
    44. }
    45. //防止主线程停止,导致模拟线程不执行
    46. Thread.sleep(60 * 1000);
    47. }
    48. }

    使用分布式锁模拟用户下单操作,防止恶意并发(一般来说用户下单产品操作时间最少也需要几秒的时间,这里预设5秒),也就是说5秒内只允许下单成功一次,也就解决了恶意并发的问题

    1. @Test
    2. void test3() throws InterruptedException {
    3. String user = "用户1";
    4. //获取线程池
    5. ExecutorService executorService = Executors.newCachedThreadPool();
    6. //模拟开启10个线程
    7. for (int i = 1; i <= 10; i++) {
    8. //开启线程执行
    9. executorService.execute(() -> {
    10. Boolean b = valueOperations.setIfAbsent("lock." + user, 1, 5, TimeUnit.SECONDS);
    11. if (b) {
    12. //下单
    13. log.info("{} 下单成功", Thread.currentThread().getName());
    14. } else {
    15. log.info("{} 稍后再试", Thread.currentThread().getName());
    16. }
    17. });
    18. }
    19. //防止主线程停止,导致模拟线程不执行
    20. Thread.sleep(60 * 1000);
    21. }

    💌模拟多线程并发访问

    在网关设置防止恶意并发访问,将用户ip作为key,只允许用户ip在短时间内访问2次

    1. package com.moming;
    2. import cn.hutool.core.date.DateUtil;
    3. import cn.hutool.core.thread.ThreadUtil;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.junit.jupiter.api.Test;
    6. import org.springframework.beans.factory.annotation.Autowired;
    7. import org.springframework.boot.test.context.SpringBootTest;
    8. import org.springframework.data.redis.core.StringRedisTemplate;
    9. import org.springframework.data.redis.core.ValueOperations;
    10. import javax.annotation.Resource;
    11. import java.util.concurrent.ExecutorService;
    12. import java.util.concurrent.Executors;
    13. import java.util.concurrent.TimeUnit;
    14. @SpringBootTest
    15. @Slf4j
    16. class AppTests_Increment {
    17. @Resource(name = "redisTemplate")
    18. private ValueOperations valueOperations;
    19. @Autowired
    20. private StringRedisTemplate stringRedisTemplate;
    21. @Test
    22. void test4() throws InterruptedException {
    23. //模拟用户ip
    24. String userIp = "127.0.0.1";
    25. //获取线程池
    26. ExecutorService executorService = Executors.newCachedThreadPool();
    27. //模拟开启10个线程
    28. for (int i = 1; i <= 10; i++) {
    29. //开启线程执行
    30. executorService.execute(() -> {
    31. String seconds = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss");
    32. String key = seconds + userIp;
    33. Long count = valueOperations.increment(key, 1);
    34. if(count>2){
    35. //下单
    36. log.info("{} 超过请求次数",Thread.currentThread().getName());
    37. } else {
    38. log.info("{} 请求正常",Thread.currentThread().getName());
    39. }
    40. });
    41. }
    42. //防止主线程停止,导致模拟线程不执行
    43. Thread.sleep(60 * 1000);
    44. }
    45. }

    💌BitMap模拟在线统计 

    模拟某线上活动要举办3天,参与活动的人员可以线上签到,后台通过3天人员的签到情况,统计信息

    1. 3天满勤人数: 三天都签到才算一位
    2. 3天活跃人数: 任何一天签到都算一位
    1. @SpringBootTest
    2. class AppTests_BitMap {
    3. @Autowired
    4. private StringRedisTemplate stringRedisTemplate;
    5. @Test
    6. public void test2() {
    7. //2022111 20221112 20221113 三天活动
    8. //张三 1, 李四 2, 王五:3
    9. //满勤人数,1人
    10. //活跃人数,3人
    11. stringRedisTemplate.opsForValue().setBit("20221111",1,true);
    12. stringRedisTemplate.opsForValue().setBit("20221112",2,true);
    13. stringRedisTemplate.opsForValue().setBit("20221112",2,true);
    14. stringRedisTemplate.opsForValue().setBit("20221111",3,true);
    15. stringRedisTemplate.opsForValue().setBit("20221112",3,true);
    16. stringRedisTemplate.opsForValue().setBit("20221113",3,true);
    17. //RedisStringCommands.BitOperation.AND 满勤人数
    18. //RedisStringCommands.BitOperation.OR 活跃人数
    19. RedisCallback callback1 = connection -> {
    20. connection.bitOp(RedisStringCommands.BitOperation.OR,
    21. "人数统计".getBytes(StandardCharsets.UTF_8),
    22. "20221111".getBytes(StandardCharsets.UTF_8),
    23. "20221112".getBytes(StandardCharsets.UTF_8),
    24. "20221113".getBytes(StandardCharsets.UTF_8)
    25. );
    26. Long count = connection.bitCount("人数统计".getBytes(StandardCharsets.UTF_8));
    27. return count;
    28. };
    29. Long count1 = stringRedisTemplate.execute(callback1);
    30. System.out.println(count1);
    31. }
    32. }

    RedisStringCommands.BitOperation.AND 满勤人数

    RedisStringCommands.BitOperation.OR 活跃人数

  • 相关阅读:
    MyBatis与Spring的集成
    kindediter富文本内容导出html文件
    基于Restful的WebService
    oracle 同一张表同时insert多条数据 mysql 同一张表同时insert多条数据
    SpringBoot集成JWT(极简版):
    c++day3
    通过python股票接口把数据导入Mysql数据库
    力扣 739. 每日温度
    什么是冲突域,什么是广播域?区别又是什么
    core sound driver详解
  • 原文地址:https://blog.csdn.net/HeyVIrBbox/article/details/127794810