1.使用场景引入
现在直播非常之火,2020直播带货更是成为了风口,抖音,快手,淘宝等等,每个人都可以直播,当进入直播间的时候,需要查询主播的信息,这个时候直接访问数据库是不可行的,肯定会对数据进行缓存.
2.缓存中存在的问题
使用缓存之后,能解决大部分的并发问题,但是也会存在一些问题,比如发生缓存失效问题,又或者说某个人的数据过期时间到了,所以缓存中的数据被删除了,然后这个时候访问又突增,直接访问数据库就会导致数据库崩溃,从而影响到整个系统,发生雪崩.
3.对于查询数据库的代码加锁
在查询缓存后找不到数据,会去数据中查,这个时候为了保证数据库的安全,我们可能会对其加锁.这里简单的写了一个伪代码.
public String selectById(String userId) {
//1.先去缓存中查询数据
System.out.println("缓存Miss...");
//2.缓存中没有去数据库中查询
synchronized (this) {
System.out.println("在mysql中查询数据...");
System.out.println("将数据添加到缓存中...");
return "查询的数据";
}
}
4.简单加锁存在的问题.
其实大家都知道加锁不好,会影响性能,但是其实也是相对来说的,还是拿直播行业来说,比如我们进入了一个大咖的直播间,人很多,那这个时候出现卡顿,观众会潜意识的认为是可以接受的,毕竟人多嘛,但是如果直播间人少,几个人还出现卡顿,那么观众就会认为是你这个系统垃圾,在上面代码中,任何人去请求任何一个主播的数据都会用到同一把锁,无疑会大大降低用户体验,那可以怎么进行改进呢?
5.降低锁的细粒度,对同一个主播进行加锁.
对同一个主播进行加锁,不同的主播用到不同的锁,就可以提升性能,提高用户体验,那要怎么实现呢?
这样就来到了今天的主角–ConcurrentHashMap,我们可以用该集合去实现降低锁的细粒度.
ConcurrentHashMap<String,Lock> map = new ConcurrentHashMap<String,Lock>();
public String selectById(String userId) {
//1.先去缓存中查询数据
System.out.println("缓存Miss...");
//2.缓存中没有去数据库中查询
ReentrantLock lock = new ReentrantLock();
if(!map.contains(userId)) {
map.put(userId, lock);
}else{
lock= map.get(userId);
}
try {
lock.lock();
System.out.println("在mysql中查询数据...");
System.out.println("将数据添加到缓存中...");
}catch(Exception e) {
e.printStackTracle();
}finally {
lock.unlock();
}
return "查询的数据";
}
6.继续优化
对同一个主播加锁之后,比如现在对于某个主播有一百个去查询数据库得请求,有一个获取锁对象,去查询数据库数据,然后将数据放在缓存中,其他99个请求在阻塞等待,那么其他99个还需要去查询数据库嘛? 不需要,第一个获取锁对象得已经将数据放在了缓存中,所以我们可以使用DCL(Double check lock)双重判断,继续去优化.
ConcurrentHashMap<String,Lock> map = new ConcurrentHashMap<String,Lock>();
public String selectById(String userId) {
//1.先去缓存中查询数据
System.out.println("缓存Miss...");
//2.缓存中没有去数据库中查询
ReentrantLock lock = new ReentrantLock();
if(!map.contains(userId)) {
map.put(userId, lock);
}else{
lock= map.get(userId);
}
try {
lock.lock();
System.out.println("在缓存中查询数据,如果查到,直接返回...");
System.out.println("在mysql中查询数据...");
System.out.println("将数据添加到缓存中...");
}catch(Exception e) {
e.printStackTracle();
}finally {
lock.unlock();
}
return "查询的数据";
}
7.补充.
看到网上说如果很多请求同时落到数据库上,就会导致数据库宕机,其实这个说法是不对得,因为我们必然会用数据库连接池,而连接池得作用就是限制并发得数量,所以连接池本身就可以限制高并发,那么为什么我们还要在代码中加锁去限制访问请求呢?
答案就是资源隔离,假如数据库连接池有200个连接对象,而这两百个连接对象是针对于所有得接口得,我们得系统不可能只有一个查询接口,假如某个接口请求增加,一下子来了300个,那连接池得所有连接对象都被同一个接口占用,那其他查询或者更新接口怎么办呢?就会导致请求得不到响应,造成整个系统问题.