✨✨个人主页:沫洺的主页
📚📚系列专栏: 📖 JavaWeb专栏📖 JavaSE专栏 📖 Java基础专栏📖vue3专栏
📖MyBatis专栏📖Spring专栏📖SpringMVC专栏📖SpringBoot专栏
📖Docker专栏📖Reids专栏📖MQ专栏📖SpringCloud专栏
💖💖如果文章对你有所帮助请留下三连✨✨
@SpringBootTest public class AppTests_Scan { @Resource(name = "redisTemplate") private ValueOperationsvalueOperations; @Autowired private StringRedisTemplate stringRedisTemplate; @Test void test1() { for (int i =1 ;i<11;i++){ User user = User.builder().name(i+"号张").age(18).build(); valueOperations.set("user."+i,user); } } }原生的scan用法
RedisTemplate使用Scan
@SpringBootTest public class AppTests_Scan { @Resource(name = "redisTemplate") private ValueOperationsvalueOperations; @Autowired private StringRedisTemplate stringRedisTemplate; @Test void test1() { for (int i =1 ;i<11;i++){ User user = User.builder().name(i+"号张").age(18).build(); valueOperations.set("user."+i,user); } } @Test void test2(){ //迭代扫描,指定模糊匹配,预想数量 ScanOptions scanOptions = ScanOptions.scanOptions().match("user.*").count(3).build(); //获取游标 Cursorcursor = stringRedisTemplate.scan(scanOptions); while (cursor.hasNext()){ //获取游标id System.out.println(cursor.getCursorId()); System.out.println(cursor.next()); } cursor.close(); } }
模拟高并发场景线上购物,多个用户(线程)同时选购某产品,提示库存信息
先初始化产品库存
@SpringBootTest class AppTests_DecrBy { @Resource(name = "redisTemplate") private ValueOperationsvalueOperations; @Autowired private StringRedisTemplate stringRedisTemplate; //定义一个产品 private final static String productKey = "product01"; @Test void test1() { //设置初始化库存5个 valueOperations.set(productKey, 5); } }不考虑并发的情况下
@Test void test2() throws InterruptedException { //获取线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //模拟开启10个线程 for (int i = 1; i <= 10; i++) { //开启线程执行 executorService.execute(() -> { //获取库存 Integer qty = valueOperations.get(productKey); //如果库存充足,产品数量就减一 if (qty > 0) { qty = qty - 1; //重新设置库存 valueOperations.set(productKey, qty); System.out.println(Thread.currentThread().getName() + " 库存充足"); } else { System.out.println(Thread.currentThread().getName() + " 库存不足"); } }); } //防止主线程停止,导致模拟线程不执行 Thread.sleep(60 * 1000); }出现问题,明明库存只有5个,10个用户却都提示库存充足
考虑高并发的情况下
使用decrement来保证原子性,解决高并发问题
@Test void test3() throws InterruptedException { //获取线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //模拟开启10个线程 for (int i = 1; i <= 10; i++) { //开启线程执行 executorService.execute(() -> { //获取库存 Long qty = valueOperations.decrement(productKey, 1); if (qty >= 0) { System.out.println(Thread.currentThread().getName() + " 库存充足"); } else { System.out.println(Thread.currentThread().getName() + " 库存不足"); } }); } //防止主线程停止,导致模拟线程不执行 Thread.sleep(60 * 1000); }解决了并发问题但是出现了新的问题,库存量为负数不合理(意思就是某个线程在执行删减库存时,其他线程也在同一时刻去执行,没有保障redis原子性问题)
有一种解决办法就是通过加锁synchronized来解决,但是响应能力就会大幅降低,也就失去了redis高性能的意义
所以最好的解决方式就是通过EVAL命令执行Lua脚本来解决这类问题
模拟某一个产品的出入库单据只能一个人(线程)操作的场景(分布式锁),当有人操作时,其他人只能等待该人操作结束之后进行操作
@SpringBootTest @Slf4j class AppTests_SetNx { @Resource(name = "redisTemplate") private ValueOperationsvalueOperations; @Autowired private StringRedisTemplate stringRedisTemplate; //定义一个产品 private final static String productKey = "product01"; //定义锁 private final static String lockKey = "lock.1"; //业务 private void doing(){ while (true){ //设置锁 Boolean b = valueOperations.setIfAbsent(lockKey, 1); if(b){ log.info(Thread.currentThread().getName() + " 获取到分布式锁"); //业务代码开始 ThreadUtil.sleep(3000);//模拟执行业务操作用时 //业务代码结束 stringRedisTemplate.delete(lockKey); log.info(Thread.currentThread().getName() + " 释放分布式锁"); break; } else { //log.info(Thread.currentThread().getName() + " 没有获取到分布式锁,开始睡眠"); ThreadUtil.sleep(2000); } } } @Test void test1() { //设置初始化库存5个 valueOperations.set(productKey, 0); } @Test void test2() throws InterruptedException { //获取线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //模拟开启10个线程 for (int i = 1; i <= 10; i++) { //开启线程执行 executorService.execute(this::doing); } //防止主线程停止,导致模拟线程不执行 Thread.sleep(60 * 1000); } }使用分布式锁模拟用户下单操作,防止恶意并发(一般来说用户下单产品操作时间最少也需要几秒的时间,这里预设5秒),也就是说5秒内只允许下单成功一次,也就解决了恶意并发的问题
@Test void test3() throws InterruptedException { String user = "用户1"; //获取线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //模拟开启10个线程 for (int i = 1; i <= 10; i++) { //开启线程执行 executorService.execute(() -> { Boolean b = valueOperations.setIfAbsent("lock." + user, 1, 5, TimeUnit.SECONDS); if (b) { //下单 log.info("{} 下单成功", Thread.currentThread().getName()); } else { log.info("{} 稍后再试", Thread.currentThread().getName()); } }); } //防止主线程停止,导致模拟线程不执行 Thread.sleep(60 * 1000); }
在网关设置防止恶意并发访问,将用户ip作为key,只允许用户ip在短时间内访问2次
package com.moming; import cn.hutool.core.date.DateUtil; import cn.hutool.core.thread.ThreadUtil; 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.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import javax.annotation.Resource; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @SpringBootTest @Slf4j class AppTests_Increment { @Resource(name = "redisTemplate") private ValueOperationsvalueOperations; @Autowired private StringRedisTemplate stringRedisTemplate; @Test void test4() throws InterruptedException { //模拟用户ip String userIp = "127.0.0.1"; //获取线程池 ExecutorService executorService = Executors.newCachedThreadPool(); //模拟开启10个线程 for (int i = 1; i <= 10; i++) { //开启线程执行 executorService.execute(() -> { String seconds = DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss"); String key = seconds + userIp; Long count = valueOperations.increment(key, 1); if(count>2){ //下单 log.info("{} 超过请求次数",Thread.currentThread().getName()); } else { log.info("{} 请求正常",Thread.currentThread().getName()); } }); } //防止主线程停止,导致模拟线程不执行 Thread.sleep(60 * 1000); } }
模拟某线上活动要举办3天,参与活动的人员可以线上签到,后台通过3天人员的签到情况,统计信息
- 3天满勤人数: 三天都签到才算一位
- 3天活跃人数: 任何一天签到都算一位
@SpringBootTest class AppTests_BitMap { @Autowired private StringRedisTemplate stringRedisTemplate; @Test public void test2() { //2022111 20221112 20221113 三天活动 //张三 1, 李四 2, 王五:3 //满勤人数,1人 //活跃人数,3人 stringRedisTemplate.opsForValue().setBit("20221111",1,true); stringRedisTemplate.opsForValue().setBit("20221112",2,true); stringRedisTemplate.opsForValue().setBit("20221112",2,true); stringRedisTemplate.opsForValue().setBit("20221111",3,true); stringRedisTemplate.opsForValue().setBit("20221112",3,true); stringRedisTemplate.opsForValue().setBit("20221113",3,true); //RedisStringCommands.BitOperation.AND 满勤人数 //RedisStringCommands.BitOperation.OR 活跃人数 RedisCallbackcallback1 = connection -> { connection.bitOp(RedisStringCommands.BitOperation.OR, "人数统计".getBytes(StandardCharsets.UTF_8), "20221111".getBytes(StandardCharsets.UTF_8), "20221112".getBytes(StandardCharsets.UTF_8), "20221113".getBytes(StandardCharsets.UTF_8) ); Long count = connection.bitCount("人数统计".getBytes(StandardCharsets.UTF_8)); return count; }; Long count1 = stringRedisTemplate.execute(callback1); System.out.println(count1); } }RedisStringCommands.BitOperation.AND 满勤人数
RedisStringCommands.BitOperation.OR 活跃人数