/ 解决方案二:分布式锁setnx <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.20.0</version> </dependency> @Configuration public class RedissonConfig { @Bean public Redisson redisson() { Config config = new Config(); config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0); return (Redisson) Redisson.create(config); } } // 集群部署:分布式锁 public Product getProduct2(Long productId) { String redisId = SystemConstants.REDIS_KEY_PREFIX + productId; // 1. 先查redis缓存 Product product = getProductFromRedis(redisId); if (product != null) { return product; } // 分布式锁RLock确保锁住特定的productId,不影响其他productId,解决所有问题 RLock lock = redisson.getLock(SystemConstants.LOCK_HOT_CACHE_PREFIX + productId); lock.lock(); // 等价于setnx(SystemConstants.LOCK_HOT_CACHE_PREFIX + productId, value) // 2. DCL再查redis,因为只要有一次查询数据库操作,redis就已经有缓存数据了 Product productMysql = null; try { product = getProductFromRedis(redisId); if (product != null) { return product; } // 3. redis还是没有,最后查mysql数据库 productMysql = getProductFromMysql(productId); } finally { lock.unlock(); } return productMysql; } private Product getProductFromRedis(String redisId) { Product product = null; String productRedis = jedis.get(redisId); if (!StringUtil.isBlank(productRedis)) { if (productRedis.equals(SystemConstants.REDIS_DEFAULT_CACHE)) { // 缓存中存在,却是缓存默认值,也就是数据库没有数据,设置过期时间,避免缓存穿透 jedis.expire(redisId, genRandomExpiredTime(3)); return new Product(); // 特殊情况 } // 缓存中存在,也是正常值 jedis.expire(redisId, genRandomExpiredTime(5)); product = gson.fromJson(productRedis, Product.class); } return product; } private Product getProductFromMysql(Long productId) { String redisId = SystemConstants.REDIS_KEY_PREFIX + productId; Product productMysql = productRepo.findByProductId(productId); if (productMysql != null) { // 数据库有,则同步更新redis缓存数据【但是可能出现突发性热点缓存重建导致数据库系统压力倍增,也就是这段代码大量执行】 jedis.set(redisId, gson.toJson(productMysql)); jedis.expire(redisId, genRandomExpiredTime(5)); } else { // 数据库没有,则设置默认值缓存 + 过期时间,避免缓存穿透 jedis.set(redisId, SystemConstants.REDIS_DEFAULT_CACHE); jedis.expire(redisId, genRandomExpiredTime(3)); } return productMysql; }
上述代码的思路非常正确,在解决缓存设计中的问题上,利用了Redission提供的分布式锁(RLock),确保了在分布式环境下对于同一数据的并发访问可以得到很好的控制,并降低了数据库的压力。
在这段代码中首先从Redis缓存中获取商品数据,若缓存中有数据则直接返回。若缓存中没有数据,则调用了Redission的分布式锁RLock对这个商品ID进行加锁。
当一个线程对该商品ID加锁后,其他试图访问同一个商品ID的线程会处于等待状态,直到该线程对商品ID进行解锁。通过这样的方式,只有一个线程会进行数据库查询和缓存更新,有效避免了缓存雪崩。
当商品从数据库中查询出后,该线程会更新缓存,同时解锁,其他等待的线程会再次从缓存中获取商品信息。这样也确保了只有一个线程会对数据库进行访问,从而降低数据库的压力。
这种利用Redis实现分布式锁的方式,解决了分布式环境下与缓存有关的一些常见问题,例如缓存雪崩、缓存穿透以及数据库压力过大等。
来源:
互联网
本文观点不代表源码解析立场,不承担法律责任,文章及观点也不构成任何投资意见。
评论列表