数据准备
订单表: order_detail
CREATE TABLE `order_detail` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_code` varchar(10) NOT NULL COMMENT '用户id',
`name` varchar(40) NOT NULL COMMENT '订单名',
`price` decimal(10,0) NOT NULL COMMENT '金钱',
`create_time` datetime DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12000308 DEFAULT CHARSET=utf8;
存储过程: batch_orders
CREATE PROCEDURE `batch_orders`(in max int)
begin
declare start int default 0;
declare i int default 0;
set autocommit = 0;
while i < max do
set i = i + 1;
insert into order_detail(userId,name,price)
values (i%8,concat('订单title-',i%8),i%50);
end while;
commit;
end
# 创建 100W 数据
call batch_orders(1000000);
jmeter 工具
模拟高并发请求
Druid 监控
druid 是数据库连接池,提供强大的监控和扩展功能
缓存雪崩
场景
概念
当缓存服务器重启或者大量缓存集中在某一个时间段失效,而这这时遇到高并发,数据库操作时间比较长,会给后端系统(比如DB)带来很大压力,甚至DB宕机
解决
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量
模拟雪崩代码
/**
* 模拟缓存雪崩
*/
public BigDecimal noSyn(String userCode) {
String json = redisService.get(userCode);
if(StringUtils.isNotEmpty(json)){
System.err.println("*****************************从redis中读取*****************************" + json);
return new BigDecimal(json);
}
BigDecimal countPrice = orderDetailDao.countPrice(userCode);
System.err.println("----------------从DB中读取----------------" + json);
if(countPrice != null){
redisService.set(userCode, countPrice.toString());
//设置失效时间,这里是模拟 缓存雪崩,有效时间1秒
redisService.expire(userCode, 1, TimeUnit.SECONDS);
}
return countPrice;
}
修改:
/**
* 解决缓存雪崩
*/
public BigDecimal syn(String userCode) {
String json = redisService.get(userCode);
if(StringUtils.isNotEmpty(json)){
System.err.println("*****************************从redis中读取*****************************" + json);
return new BigDecimal(json);
}
synchronized (userCode) {
json = redisService.get(userCode);
if(StringUtils.isNotEmpty(json)){
System.err.println("*****************************从redis中读取*****************************" + json);
return new BigDecimal(json);
}
BigDecimal countPrice = orderDetailDao.countPrice(userCode);
System.err.println("----------------从DB中读取----------------" + json);
if(countPrice != null){
redisService.set(userCode, countPrice.toString());
//设置失效时间,这里是模拟 缓存雪崩,有效时间1秒
redisService.expire(userCode, 1, TimeUnit.SECONDS);
}
return countPrice;
}
}
思考: 为何不同步方法?
为何同步代码里,还需缓存中读取?
druid 监控分析
缓存穿透
场景
概念
高并发查询缓存中不存在的数据,每次都会到数据库中查询。大并发时,会导致用户体验差,数据库宕机,系统瘫痪,难以恢复
解决
1. 如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。
·2根据缓存数据Key的规则顾虑不符合规则的key;这种办法只能缓解一部分的压力,过滤和系统无关的查询,但是无法根治。
·3,采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的BitSet中,不存在的数据将会被拦截掉,从而避免了对底层存储系统的查询压力。关于布隆过滤器,详情查看:基于BitSet的布隆过滤器(Bloom Filter)
模拟缓存穿透代码:
/**
* 模拟缓存穿透,缓存雪崩
*/
public BigDecimal noBloomFilter(String userCode) {
String json = redisService.get(userCode);
if(StringUtils.isNotEmpty(json)){
System.err.println("*****************************从redis中读取*****************************" + json);
return new BigDecimal(json);
}
BigDecimal countPrice = orderDetailDao.countPrice(userCode);
System.err.println("----------------从DB中读取----------------" + json);
if(countPrice != null){
redisService.set(userCode, countPrice.toString());
}
return countPrice;
}
修改:
private BloomFilter<String> bf; //布隆过滤器
@PostConstruct //将用户userCod 放到 BloomFilter 中,可将usercode放入缓存,然后从缓存中读取
private void init(){
List<String> userCodes = orderDetailDao.getDistinctUserCode();
bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), userCodes.size());
for(String s : userCodes){
bf.put(s);
}
}
/**
* 解决缓存穿透
*/
public BigDecimal bloomFilter(String userCode) {
if( !bf.mightContain(userCode)){
System.err.println("#########################被bloomfilter 过滤#########################");
return null;
}
String json = redisService.get(userCode);
if(StringUtils.isNotEmpty(json)){
System.err.println("*****************************从redis中读取*****************************" + json);
return new BigDecimal(json);
}
BigDecimal countPrice = orderDetailDao.countPrice(userCode);
System.err.println("----------------从DB中读取----------------" + json);
if(countPrice != null){
redisService.set(userCode, countPrice.toString());
}
return countPrice;
}
druid 监控分析
区别
缓存雪崩 | 缓存穿透 | |
产生原因 | 高并发下缓存数据失效 | 高并发查询缓存中不存在的key |
发生频率 | 缓存时效期间 | 随时发生 |
产生场景 | 业务需求导致的高并发 | 黑客攻击 |
破化效果 | DB宕机,系统瘫痪 | 用户体验差,DB宕机,系统瘫痪,难以恢复 |
解决之道 | JVM锁或分布式锁 | bloomfilter... |