一、什么是分布式锁
在单体架构中,系统只有一个,系统所用的内存和进程也只有一个,多个线程可以共享同一份数据。这样只要使用java提供的锁机制就可以解决并发访问带来的问题,但是分布式系统中,系统是多个,并且所使用的内存也是不同的,每个系统也都有独立的进程,这样Java提供的锁就没办法解决分布式系统中的并发访问问题。因此就需要引入分布式锁来解决分布式系统中共享资源访问的问题。
1.2 分布式锁的特性
1.要保证同一时刻内,只有一个服务获取到这个锁
2.这把锁要能重入,在某些业务下会出现同一把锁的重入
3.具备锁续命,保证高并发下不会因锁的时效问题引起数据错误
4.具有非阻塞式获取锁,在获取锁失败后立刻返回
1.3 基于Redisson实现的分布式锁
Redisson中分布式锁的架构:关于Redisson分布式锁的使用:
private Integer setInfo(Long key){
// 获取分布式锁
RLock lock = redisson.getLock("Ext_Info:" + key);
// 加锁
lock.lock();
try{
// 业务处理
Product product = productDao.get(key);
return 1;
}catch (Exception e){
e.printStackTrace();
}finally {
// 解锁
lock.unlock();
}
return -1;
}
1.4 分布式锁失效问题
当主从或集群机构的主节点挂掉后,该节点存储的分布式锁信息可能会丢失。而当从节点选举成新节点后,其它线程又可以对新的主节点进行添加分布式锁信息,这就会引起并发安全问题。
为了解决这个问题,就引入了redlock。
redlock的逻辑就是向多个节点写入加锁的信息,如果写入成功,加代表加锁成功。但是如果写入的节点丢失了锁的信息,仍然是会出现分布式锁失效问题。
还有一点就是性能问题,我们使用redis就是为了它的高性能,但是使用redlock后每次加锁都要写入多个节点,这就会降低redis性能,这样还不如使用zookeeper。
二、Redisson实现分布式锁源码剖析
加锁的核心逻辑:三、分布式锁的优化
对于读多写少的业务,可以使用readwirte锁:
public Product update(Product product) {
Product productResult = null;
// 获取分布式锁
RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
// 写锁
RLock writeLock = readWriteLock.writeLock();
// 加锁
writeLock.lock();
try {
productResult = productDao.update(product);
// 设置过期时间
redisUtil.set(productResult.getId(), JSON.toJSONString(productResult),
genProductCacheTimeout(), TimeUnit.SECONDS);
} finally {
// 释放锁
writeLock.unlock();
}
return productResult;
}
redisson实现的读写锁,大体上和上面的分布式锁逻辑相同,只是加了一个mode,用于区分是读还是写。对于读读而言,就等于是锁的重入,不会阻塞;对于读写、写写操作,就会阻塞保证并发的安全。
四、缓存问题
1、什么是缓存击穿?
当同一时刻有大量的缓存失效,就会导致大量的请求打到数据库,会造成数据库压力过大甚至宕机。
解决办法:在给缓存数据设置过期时间时,增加一个随机的扰动因子,避免让大量的缓存数据都同一时刻失效。
2、什么是缓存穿透?
缓存层和数据库都没有数据,每次请求都会落到数据库,如果是高并发场景下,就会引起数据的压力剧增,甚至宕机。
解决办法:
1、对于不存在的数据,可以在缓存层面设置对应key的空值
2、布隆过滤器,向布隆过滤器中添加key时,会先使用多个hash函数进行运算,然后定位到布隆过滤器的数组中某个下标。当某个key查询布隆过滤器数据时,会同样使用多个hash函数进行运算,然后得到数组中的下标位置。
3、什么是缓存雪崩?
缓存层的作用就是分担数据库层面的压力,如果缓存层宕机,大量的请求就直接打到数据库,高并发下数据库有可能宕机。
解决办法:
1、确保缓存层的高可用,比如搭建集群、主从架构
2、在后端应用上设置限流或服务降级