一,缓存原理
在高并发方案中首先考虑的第一个优化层是增加缓存。特别是Redis,将数据库中的原始数据复制到内存中,这可以减少数据库的读取操作,并减少数据库中的压力。它还将加速系统的响应能力,但它也会带来其他问题,例如需要考虑数据的一致性,还要防止缓存破裂,渗透和雪崩等可能出现的问题。
1.实施步骤
首先查询缓存中的数据,如果有,则直接返回缓存中的数据。如果缓存中没有数据,将查询数据库,数据将在缓存中更新并返回,如果数据库中没有数据,则可以返回空数据。
考虑到数据的一致性,高速缓冲存储器中的代码逻辑更加标准化。首先,使用Redis,粘贴并返回。如果丢失,它将通过数据库进行咨询和同步。
查询公共结果(字符串ID){
结果结果=null;
//1.从Redis缓存中获取数据
结果=(结果)redisTemplate.opsForValue()。得到(id);
是(null!=结果){
System.out.println('获取缓存中的数据');
返回结果
}
//2.通过数据库查询,有一个同步更新redis,否则返回空
System.out.println('获取数据库中的数据');
Result=Dao.query(id);
是(null!=结果){
redisTemplate.opsForValue()。 set(id,result);
redisTemplate.expire(id,20000,TimeUnit.MILLISECONDS);
}
返回结果
}
如果首先清除密钥中缓存的值,然后执行数据库操作,则可以进行其他添加,删除和更新操作。逻辑清晰简单,维护的复杂性降低,成本高于查询。
空公共更新(实体实体){
redisTemplate.delete(entity.getId());
Dao.update(实体);
退货实体;}
增加公共实体(实体实体){
redisTemplate.delete(entity.getId());
Dao.insert(实体);
退货实体;
}
2.缓存更新策略
适用于缓存的方案通常是:频繁访问,更多阅读场景,更少的写入方案和不太严格的数据一致性要求。如果不满足前面三个条件,那么保持一组数据存储在缓存中的重要性不是很好。实际上,通常需要为业务场景选择适当的缓存方案。提供了以下四种缓存策略。下一步是从强到弱的一致性顺序。
适用方案更新战略。
更新实时更新同步保证了良好的一致性,与业务的紧密联系,强大的财务转移业务等。
在实时(MQ /后订阅/观察者模式)弱异步更新,服务,弱一致性的脱钩,延迟不适合写频繁的场景
失效机制提供该高速缓存无效,存在延迟,有可能是雪崩,其适于读取和写入以下,并且可以接受一定的延迟。
任务调度通过计划任务完整地更新统计服务,并定期更新。
2.大量缓存和故障。
缓存雪崩概念
缓存雪崩意味着在配置缓存时使用相同的到期时间,这会导致缓存同时失败。请求被转发到DB,DB处于压力和雪崩状态。与高速缓存的细分不同,高速缓存划分并验证相同的数据。缓存雪崩是不同的数据已过期,您无法找到许多数据来验证数据库。
Solucion
分配缓存过期时间。例如,我们可以添加在原有基础上到期时间为1-5分钟的随机值,使缓存的每小时到期的重复率降低,也难以引起的集体失败。事件
所述高速缓存的单链的写入(处理)通过阻断或排队,从而防止大量的并发请求保证落入底层存储系统时,失败。
第一种方案相对容易实现,第二种方法主要通过添加独占阻塞锁来实现。在缓存查询不可用的情况下,只有一个线程可以查询DB,因此可以避免。数据库中包含大量并发ID请求。
查询公共结果(字符串ID){
//从缓存中获取数据
结果结果=null;
结果=(结果)redisTemplate.opsForValue()。得到(id);
是(结果!=空){
Logger.info('获取缓存中的数据');返回结果
}
//2.阻塞队列,阻塞块
doLock(id); //可以有多少个ID?有多少个块?
测试{
//一次只有一个线程
//仔细检查,第一次得到以下内容时,可以直接从缓存中获取
结果=(结果)redisTemplate.opsForValue()。得到(id);
是(结果!=空){
Logger.info('获取缓存中的数据');
返回结果; //第二个帖子,回到这里
}
Result=dao.query(id);
//3.数据库查询的结果不为空,然后将数据放在缓存中,方便下次查询
是(null!=结果){
redisTemplate.opsForValue()。 set(id,result);
redisTemplate.expire(id,20000,TimeUnit.MILLISECONDS);
}
各省回归;
} catch(例外e){
返回null
} finally {
//4.取消阻止
releaseLock(provinceid);
}
}
PrivateLic void releaseLock(String userCode){
ReentrantLock oldLock=(ReentrantLock)locks.get(userCode);
是(oldLock!=Null && oldLock.isHeldByCurrentThread()){
oldLock.unlock();
}
}
私有真空doLock(String Lockcode){
//id具有不同的值
//id是一样的,添加一个挂锁,不是同一个键,你不能使用相同的挂锁
ReentrantLock newLock=new ReentrantLock(); //创建一个块
//如果它已经存在,newLock会直接丢弃
锁oldLock=locks.putIfAbsent(lockcode,newLock);是(oldLock==null){
newLock.lock();
} else {
oldLock.lock();
}
}
??
注意:阻止队列的解决方案是处理分布式环境中的并发问题。也可以解决分布式块的问题;线程被阻止,用户体验不好。因此,在真正的高并发场景中,使用!
2.缓存故障概念
在缓存过期时,现有密钥同时具有大量请求,这些请求将被分解到数据库中,这将导致大量即时数据库请求,压力突然增加。
Solucion
在访问密钥之前,使用SETNX(如果不存在则配置)配置另一个短期密钥以阻止访问当前密钥,然后在访问完成后删除短期密钥。
3.缓存的渗透。
缓存渗透概念
高速缓存渗透是指在高速缓存和数据库中不可用的数据,并且用户连续发起请求,例如启动具有“-1”标识的数据或具有标识的数据。一个特别多的人。此时,用户可能是攻击者,攻击将导致数据库承受压力。
解决方案:Bloom过滤器
Bloom过滤器的使用类似于java的SET集合,用于确定元素是否在集合中。与通用散列集不同,此算法不需要存储密钥的值。对于每个密钥,仅需要k个比特,每个密钥存储一个标记以确定密钥是否在该集合中。