目录
思考一个场景,当我们的一个数据正在修改的时候,正好有用户来访问,我们对这个数据的相关统计监控数据就无法正确进行。
什么是读写锁:
读写锁是一种用于管理对共享资源的访问的同步机制,允许多个线程同时读取共享资源,但在写入时保证独占访问,以确保数据的一致性和完整性。
读写锁分两种:
1.读锁(共享锁):
多个线程可以同时获取读锁,用于并发读取共享资源。读锁在没有写锁的情况下可以被多个线程持有。
2.写锁(排它锁):
写锁是独占的,一旦一个线程获取了写锁,其他线程无法同时获取读锁或者写锁。写锁用于修改共享资源,确保在写入时没有其他线程能有访问。
优点:
读写锁的优势在于它允许多个线程同时读取共享资源,提高了读取的并发性,从而提升了性能。但是,在写入时必须独占资源,以确保数据的一致性。这种锁的使用场景适用于读操作频繁,写操作较少的情况。
仍有问题:
当我们在修改数据的时候拿到了写锁,读锁获取不到,难道这段时间内用户一直不允许访问么?
如果获取不到就一直等着,会有很多线程堆在这里,应用会垮掉。
如果获取不到就返回,那我们后续的数据统计监控,记录信息就不精准
改进:
我们用到了延迟队列:
当我们获取读锁失败的时候,把东西扔给延迟队列,可以设置一个等待时间,时间到了再去统计,如果还没获取到锁,那就再扔进去,等待一定时间后统计。
读写锁使用:
修改时:
java
//拿到写锁
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, requestParam.getFullShortUrl()));
RLock rLock = readWriteLock.writeLock();
if (!rLock.tryLock()) {
throw new ServiceException("短链接正在被访问,请稍后再试...");
}
try {
}
finally {
rLock.unlock();
}
读取信息时:
java
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, fullShortUrl));
//先拿读锁
RLock rLock = readWriteLock.readLock();
if (!rLock.tryLock()) {
delayShortLinkStatsProducer.send(statsRecord);
return;
}
try {
}
catch (Throwable ex) {
throw new ServiceException("监控器异常");
}
finally {
rLock.unlock();
}
生产者:
java
public class DelayShortLinkStatsProducer {
private final RedissonClient redissonClient;
/**
* 发送延迟消费短链接统计
*
* @param statsRecord 短链接统计实体参数
*/
public void send(ShortLinkStatsRecordDTO statsRecord) {
RBlockingDeque<ShortLinkStatsRecordDTO> blockingDeque = redissonClient.getBlockingDeque(DELAY_QUEUE_STATS_KEY);
RDelayedQueue<ShortLinkStatsRecordDTO> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
delayedQueue.offer(statsRecord, 5, TimeUnit.SECONDS);
}
}
消费者:
java
@Component
@RequiredArgsConstructor
public class DelayShortLinkStatsConsumer implements InitializingBean {
private final RedissonClient redissonClient;
private final ShortLinkService shortLinkService;
public void onMessage() {
Executors.newSingleThreadExecutor(
runnable -> {
Thread thread = new Thread(runnable);
thread.setName("delay_short-link_stats_consumer");
thread.setDaemon(Boolean.TRUE);
return thread;
})
.execute(() -> {
RBlockingDeque<ShortLinkStatsRecordDTO> blockingDeque = redissonClient.getBlockingDeque(DELAY_QUEUE_STATS_KEY);
RDelayedQueue<ShortLinkStatsRecordDTO> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
for (; ; ) {
try {
ShortLinkStatsRecordDTO statsRecord = delayedQueue.poll();
if (statsRecord != null) {
shortLinkService.shortLinkStats(null, null, statsRecord);
continue;
}
LockSupport.parkUntil(500);
} catch (Throwable ignored) {
}
}
});
}
@Override
public void afterPropertiesSet() throws Exception {
onMessage();
}
}
后续我们可以使用Rabbit或者别的MQ的延迟队列来进行更好的替换优化