Redis

Redis

SpringDataRedis

img

img

img

img

缓存穿透

主要针对恶意请求,数据库不存在的请求

img

缓存击穿

主要针对热点数据,redis过期时,在redis重建过程中,大量访问冲击数据库

img

缓存穿透与缓存击穿的三种解决方法

    //    解决内存穿透,将非法请求存储redis为空
    Shop shop = redisClient.getObjectOrEmpty(RedisConstants.CACHE_SHOP_KEY,
            id,
            Shop.class,
            id2->getById(id2),
            RedisConstants.CACHE_SHOP_TTL,
            TimeUnit.MINUTES);
    //   基于互斥锁解决缓存击穿
    Shop shop = redisClient.getObjectAndLock(
            RedisConstants.CACHE_SHOP_KEY,
            id,
            Shop.class,
            id2 -> getById(id2),
            RedisConstants.LOCK_SHOP_TTL,
            TimeUnit.MINUTES
    );
    //    基于逻辑过期,解决缓存击穿
    Shop shop = redisClient.getWithLogicalExpire(
            RedisConstants.CACHE_SHOP_KEY,
            id,
            Shop.class,
            id2 -> getById(id2),
            RedisConstants.LOCK_SHOP_TTL,
            TimeUnit.MINUTES
    );

redisClient工具类

@Component
@Slf4j
@RequiredArgsConstructor
public class RedisClient {

    private final StringRedisTemplate stringRedisTemplate;

    private final RedisLock redisLock;

    private final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    // 基于逻辑过期,默认命中(会提前在redis缓存热点内容)
    public <ID, R> R getWithLogicalExpire(String keyPre, ID id, Class<R> objectClass,Function<ID, R> selectObject,Long time, TimeUnit timeUnit){
        // 查询redis
        String key = keyPre+id;
        R object = null;

        String string = stringRedisTemplate.opsForValue().get(key);
        // 命中为空
        if (StrUtil.isBlank(string)){
            return null;
        }
        // 命中不为空,判断是否过期
        RedisData bean = JSONUtil.toBean(string, RedisData.class);
        JSONObject data = (JSONObject) bean.getData();
        object = JSONUtil.toBean(data, objectClass);
        LocalDateTime expireTime = bean.getExpireTime();
        // 未过期
        if (expireTime.isAfter(LocalDateTime.now())){
            return object;
        }
        // 过期,重建缓存
        // 获取互斥锁,开启新的线程重建
        boolean lock = redisLock.tryLock(RedisConstants.LOCK_SHOP_KEY+id, RedisConstants.LOCK_SHOP_TTL, TimeUnit.MINUTES);
        if (!lock){
            return object;
        }
        CACHE_REBUILD_EXECUTOR.submit(()->{
            // 获取到互斥锁,请求数据
            RedisData redisData = new RedisData();
            R newObject = selectObject.apply(id);
            redisData.setData(newObject);
            redisData.setExpireTime(LocalDateTime.now().plusSeconds(RedisConstants.LOCK_SHOP_TTL));
            // 查询到放入redis缓存
            this.set(key,redisData,time,timeUnit);
            // 释放锁
            redisLock.delLock(RedisConstants.LOCK_SHOP_KEY + id);
        });
        return object;
    }

    public <ID, R> R getObjectAndLock(String keyPre, ID id, Class<R> objectClass,Function<ID, R> selectObject,Long time, TimeUnit timeUnit){
        // 查询redis
        String key = keyPre+id;
        R object = null;
        try {
            String string = stringRedisTemplate.opsForValue().get(key);
            // 命中不为空
            if (StrUtil.isNotBlank(string)){
                return JSONUtil.toBean(string, objectClass);
            }
            // 命中为空
            if (string!=null){
                return null;
            }
            // 未命中,获取互斥锁
            boolean getLock = redisLock.tryLock(RedisConstants.LOCK_SHOP_KEY + id, RedisConstants.LOCK_SHOP_TTL, TimeUnit.SECONDS);
            if (!getLock){
                Thread.sleep(50);
                return getObjectAndLock(keyPre,id,objectClass,selectObject,time,timeUnit);
            }
            // 获取锁成功,再次检查redis是否存在,做DoubleCheck
            string = stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isNotBlank(string)){
                redisLock.delLock(RedisConstants.LOCK_SHOP_KEY + id);
                return JSONUtil.toBean(string, objectClass);
            }

            object = selectObject.apply(id);
            Thread.sleep(200);
            // 未查询到,非法访问
            if (object==null){
                this.set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            // 查询到放入redis缓存
            this.set(key,object,time,timeUnit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 释放锁
            redisLock.delLock(RedisConstants.LOCK_SHOP_KEY + id);
        }

        return object;
    }

    public <ID, R> R getObjectOrEmpty(String keyPre, ID id, Class<R> objectClass,Function<ID, R> selectObject,Long time, TimeUnit timeUnit){
        // 查询redis
        String key = keyPre+id;
        String string = stringRedisTemplate.opsForValue().get(key);
        // 命中不为空
        if (StrUtil.isNotBlank(string)){
            return JSONUtil.toBean(string, objectClass);
        }
        // 命中为空
        if (string!=null){
            return null;
        }
        // 未命中,查询数据库
        R object = selectObject.apply(id);
        // 查询到
        if (object!=null){
            this.set(key,object,time,timeUnit);
            return object;
        }
        // 非法访问
        this.set(key,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
        return null;
    }
    public void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(value),time,timeUnit);
    }
}

基于reids的setnx实现互斥锁

@Component
public class RedisLock {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public boolean tryLock(String key, Long time, TimeUnit timeUnit){
        Boolean f = stringRedisTemplate.opsForValue().setIfAbsent(key,"true",time,timeUnit);
        return BooleanUtil.isTrue(f);
    }

    public void delLock(String keyPrefix){
        stringRedisTemplate.delete(keyPrefix+"lock");
    }
}

秒杀问题

全局唯一id生成策略

img

@Component
@RequiredArgsConstructor
public class RedisWorker {
     private static final long BEGIN_TIMESTAMP = 1640995200;

    private static final long COUNT_BITS = 32;

     private final StringRedisTemplate stringRedisTemplate;
     public long nextId(String keyPrefix){
         // 生成时间戳
         LocalDateTime now = LocalDateTime.now();
         long temp = now.toEpochSecond(ZoneOffset.UTC) - BEGIN_TIMESTAMP;

         String format = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
         // redis 自增长
         long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + format);

         return temp << COUNT_BITS | count;
         
     }
}

测试

private ExecutorService es = Executors.newFixedThreadPool(500);

    @Test
    void testIdWorker() throws InterruptedException {
        //计数器
        CountDownLatch latch = new CountDownLatch(300);
        // 线程类,不能直接运行
        Runnable task = ()->{
            for (int i = 0; i < 100; i++) {
                long id = redisWorker.nextId("order");
                System.out.println("id= "+id);
            }
            //计数器减一
            latch.countDown();
        };
        long begin = System.currentTimeMillis();
        for (int i = 0; i < 300; i++) {
            //启动线程
            es.submit(task);
        }
        // 计数器不为0,阻塞
        latch.await();
        long end = System.currentTimeMillis();
        System.out.println("time: "+(end-begin));
    }

乐观锁与悲观锁

img

boolean success = killVocherService.update()
                    .setSql("Stock = stock-1")
                    .eq("voucher_id", voucherId)
                    // 库存大于0,而不是大于查询到的数量
                    // 防止在高并发下,线程1、2、3、4同时拿到同一查询数量,线程1执行结束,更新值,其他线程比较与查询数量不同全部失败
                    .gt("stock",0) 
                    .update();

Redison

分布式服务

img

img

posted @ 2024-09-09 14:33  9k  阅读(23)  评论(0)    收藏  举报