缓存使用&加锁

目录

缓存使用

本地缓存

分布式缓存 

缓存穿透

缓存雪崩

缓存击穿

缓存数据一致性

双写模式

失效模式

canal订阅binlog的方式

总结

缓存加锁

锁-时序问题

本地锁:只能锁住当前进程

synchronized

juc(lock)

分布式锁

redis分布式锁

redison分布式锁

SpringBoot整合redis

docker安装redis

引入spring-boot-starter-data-redis

配置redis

SpringBoot整合redisson

引入redisson

配置redisson

Redisson-lock看门狗原理 

SpringBoot整合springcache

引入spring-boot-starter-cache

配置springcache

缓存注解

springcache原理


缓存使用

本地缓存

分布式缓存 

缓存穿透

缓存雪崩

缓存击穿

缓存数据一致性

双写模式

失效模式

canal订阅binlog的方式

总结

  • 能放入缓存的数据本就不应该是实时性、一致性要求超高的,缓存数据的时候加上过期时间,保证可以拿到最新数据即可。
  • 不应该过度设计,增加系统的复杂性,并发读写,写写的时候,使用读写锁。
  • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

缓存加锁

锁-时序问题

本地锁:只能锁住当前进程

synchronized

public Map<String, List<Category2VO>> getCategorySync() {
        Map<String, List<Category2VO>> map;
        synchronized (this) {
            // 从redis中获取数据
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if (StringUtils.isEmpty(catalogJSON)) {
                System.out.println("getCatelogSync-->缓存没命中,查询数据库。。。。。。");
                map = getCatelogFromDB();
                // 将数据放入redis
                redisTemplate.opsForValue().set("catalogJSON", JSON.toJSONString(map), 30, TimeUnit.DAYS);
            } else {
                System.out.println("getCatelogSync-->缓存命中。。。。。。");
                map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Category2VO>>>(){});
            }
        }
        return map;
}

juc(lock)

  • 可重入锁(ReentrantLock)
public String reentrantLock() {
    Lock lock = new ReentrantLock();
    lock.lock();
    System.out.println("加锁成功。。。。。。"+Thread.currentThread().getId());
    String s = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set("ww", s);
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
        System.out.println("释放锁成功。。。。。。"+Thread.currentThread().getId());
    }

    return s;
}
  • 读写锁(ReentrantReadWriteLock)
public String writeLock() {
    Lock lock = new ReentrantReadWriteLock().writeLock();
    lock.lock();
    System.out.println("写锁加锁成功。。。。。。"+Thread.currentThread().getId());
    String s = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set("ww", s);
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
        System.out.println("写锁释放锁成功。。。。。。"+Thread.currentThread().getId());
    }

    return s;
}

public String readLock() {
    Lock lock = new ReentrantReadWriteLock().readLock();
    lock.lock();
    System.out.println("读锁加锁成功。。。。。。"+Thread.currentThread().getId());
    String s = redisTemplate.opsForValue().get("ww");
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
        System.out.println("读锁释放锁成功。。。。。。"+Thread.currentThread().getId());
    }

    return s;
}
  • 闭锁(CountDownLatch)
public void countDownLatch() {
    CountDownLatch countDownLatch = new CountDownLatch(5);
    try {
        // 计数减一
        countDownLatch.countDown();
        // 等待闭锁完成
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
  • 信号量(Semaphore)
public void semaphore() {
    // 声明信号量,5个信号
    Semaphore s = new Semaphore(5); // 声明信号量,5个信号
    try {
        // 获取一个信号量,阻塞式等待
        s.acquire();
        // 释放一个信号
        s.release();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

分布式锁

redis分布式锁

  

  • 加锁命令:SET key value [EX seconds] [PX milliseconds] [NX|XX]
  1. EX seconds – 设置键key的过期时间,单位时秒
  2. PX milliseconds – 设置键key的过期时间,单位时毫秒
  3. NX – 只有键key不存在的时候才会设置key的值
  4. XX – 只有键key存在的时候才会设置key的值
  • luo脚本:
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
/**
 * redis分布式锁:加锁+设置过期时间+删锁=原子性
 */
public Map<String, List<Category2VO>> getCategoryByRedis() {
        Map<String, List<Category2VO>> map = null;
        // 1、加锁+设置过期时间,保证原子性
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        while (true) { // 自旋的方式
            if (lock) {
                try {
                    // 从redis中获取数据
                    String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
                    if (StringUtils.isEmpty(catalogJSON)) {
                        System.out.println("getCatelogByRedis-->缓存没命中,查询数据库。。。。。。");
                        map = getCatelogFromDB();
                        // 将数据放入redis
                        redisTemplate.opsForValue().set("catalogJSON", JSON.toJSONString(map), 30, TimeUnit.DAYS);
                    } else {
                        System.out.println("getCatelogByRedis-->缓存命中。。。。。。");
                        map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Category2VO>>>(){});
                    }
                } finally {
                    // 2、luo脚本删除锁,保证原子性
                    // KEYS[1]=lock;ARGV[1]=uuid
                    String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
                    redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
                }
                return  map;
            } else {
                // 加锁失败,重试
                lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
            }
        }
}

redison分布式锁

/**
  * redisson分布式锁
  */
public Map<String, List<Category2VO>> getCategoryByRedisson() {
        Map<String, List<Category2VO>> map;
        RLock lock = redisson.getLock("catalogJSON-lock");
        lock.lock();
        try {
            // 从redis中获取数据
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if (StringUtils.isEmpty(catalogJSON)) {
                System.out.println("getCatelogByRedisson-->缓存没命中,查询数据库。。。。。。");
                map = getCatelogFromDB();
                // 将数据放入redis
                redisTemplate.opsForValue().set("catalogJSON", JSON.toJSONString(map), 30, TimeUnit.DAYS);
            } else {
                System.out.println("getCatelogByRedisson-->缓存命中。。。。。。");
                map = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Category2VO>>>(){});
            }
        } finally {
            lock.unlock();
        }

        return map;
}

SpringBoot整合redis

docker安装redis

  • 由于docker redis容器内部没有redis.conf文件,我们现在外部创建出redis.conf文件,然后再和docker容器内部进行挂载

mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf

docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

vi redis.conf 

# 启用auf持久化
appendonly yes

引入spring-boot-starter-data-redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置redis

spring.redis.host=localhost
spring.redis.port=6379

SpringBoot整合redisson

引入redisson

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
</dependency>

配置redisson

@Configuration
public class RedissonConfig {

    @Value("${idea.redis.host}")
    private String redisHost;

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
        Config config = new Config();
        config.useSingleServer().setAddress(redisHost);
        return Redisson.create(config);
    }

}

Redisson-lock看门狗原理 

  • 如果我们设置了锁的过期时间,就发送redis执行脚本,进行占锁,超时时间就是我们指定的时间。
  • 如果我们没有设置锁的过期时间,就使用看门狗默认时【lockWatchdogTimeout=30000L】,只要占锁成功,就会启动一个定时任务【重新设置过期时间,新的过期时间也是看门狗默认时间】,每隔10s【this.internalLockLeaseTime / 3L】都会自动续期过期时间,续成默认看门狗时间。
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        // 占锁
        Long ttl = this.tryAcquire(leaseTime, unit, threadId);
        if (ttl != null) {
            RFuture<RedissonLockEntry> future = this.subscribe(threadId);
            if (interruptibly) {
                this.commandExecutor.syncSubscriptionInterrupted(future);
            } else {
                this.commandExecutor.syncSubscription(future);
            }

            try {
                while(true) {
                    ttl = this.tryAcquire(leaseTime, unit, threadId);
                    if (ttl == null) {
                        return;
                    }

                    if (ttl.longValue() >= 0L) {
                        try {
                            ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);
                        } catch (InterruptedException var13) {
                            if (interruptibly) {
                                throw var13;
                            }

                            ((RedissonLockEntry)future.getNow()).getLatch().tryAcquire(ttl.longValue(), TimeUnit.MILLISECONDS);
                        }
                    } else if (interruptibly) {
                        ((RedissonLockEntry)future.getNow()).getLatch().acquire();
                    } else {
                        ((RedissonLockEntry)future.getNow()).getLatch().acquireUninterruptibly();
                    }
                }
            } finally {
                this.unsubscribe(future, threadId);
            }
        }
}

// 占锁
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        return (Long)this.get(this.tryAcquireAsync(leaseTime, unit, threadId));
}

private <T> RFuture<Long> tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
        if (leaseTime != -1L) { // 设置了过期时间
            return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
        } else { // 没有设置过期时间
            RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining == null) {
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;
        }
}

// 设置了过期时间,发送redis执行脚本
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        this.internalLockLeaseTime = unit.toMillis(leaseTime);
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}

// 未设置过期时间,给redis发送命令设置锁的过期时间
RFuture<Long> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
            // 监听占锁结果
            ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
                if (e == null) {
                    if (ttlRemaining == null) {
                        // 
                        this.scheduleExpirationRenewal(threadId);
                    }

                }
            });
            return ttlRemainingFuture;


private void scheduleExpirationRenewal(long threadId) {
        RedissonLock.ExpirationEntry entry = new RedissonLock.ExpirationEntry();
        RedissonLock.ExpirationEntry oldEntry = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.putIfAbsent(this.getEntryName(), entry);
        if (oldEntry != null) {
            oldEntry.addThreadId(threadId);
        } else {
            entry.addThreadId(threadId);
            this.renewExpiration(); // 重新设置过期时间
        }
}

private void renewExpiration() {
        RedissonLock.ExpirationEntry ee = (RedissonLock.ExpirationEntry)EXPIRATION_RENEWAL_MAP.get(this.getEntryName());
        if (ee != null) {
            Timeout task = this.commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
                public void run(Timeout timeout) throws Exception {
                    RedissonLock.ExpirationEntry ent = (RedissonLock.ExpirationEntry)RedissonLock.EXPIRATION_RENEWAL_MAP.get(RedissonLock.this.getEntryName());
                    if (ent != null) {
                        Long threadId = ent.getFirstThreadId();
                        if (threadId != null) {
                            RFuture<Boolean> future = RedissonLock.this.renewExpirationAsync(threadId.longValue());
                            future.onComplete((res, e) -> {
                                if (e != null) {
                                    RedissonLock.log.error("Can't update lock " + RedissonLock.this.getName() + " expiration", e);
                                } else {
                                    if (res.booleanValue()) {
                                        RedissonLock.this.renewExpiration();
                                    }

                                }
                            });
                        }
                    }
                }
            }, this.internalLockLeaseTime / 3L, TimeUnit.MILLISECONDS);
            ee.setTimeout(task);
        }
}

// 异步重新设置过期时间
protected RFuture<Boolean> renewExpirationAsync(long threadId) {
        return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then redis.call('pexpire', KEYS[1], ARGV[1]); return 1; end; return 0;", Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
}

SpringBoot整合springcache

引入spring-boot-starter-cache

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

配置springcache

spring.cache.type=redis
# 毫秒为单位
spring.cache.redis.time-to-live=3600000
# 缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
@EnableCaching // 开启缓存功能
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
public class RedisCacheConfig {

    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        // 将配置文件中的所有配置都生效
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }

        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }

        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }

        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }

        return config;
    }

}

缓存注解

  • @EnbaleCaching:开启缓存功能 
  • @Cacheable:触发将数据保存到缓存的操作,如果缓存中有,方法不会调用,如果缓存中没有,会调用方法,最后将方法返回结果放入缓存
  • @CacheEvict:触发将数据从缓存中删除(失效模式)
  • @CachePut:不影响方法执行更新缓存(双写模式)
  • @Cacheing:组合以上多个操作
  • @CacheConfig:在类级别共享缓存相同配置

springcache原理

  • spring从3.1开始定义了Cache和Cachemanager接口来统一不同的缓存技术

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值