谷粒商城高级随记
复杂json转对象
注://将拿到json串转成指定的对象TypeReference,categoryjson是json串
Map<String, List<Catelog2Vo>> result=JSON.parseObject(categoryjson,new TypeReference<Map<String, List<Catelog2Vo>>>(){});
Redis锁相关问题:
- 缓存穿透:恶意攻击,用工具一直去查一个没有东西,由于redis也没存null值,所以一直请求数据库,
解决方案:放一个标致味,null或者0,并且假如短暂的过期时间,不然该商品突然有了也一直是null或者0;如果再次没查到也放标志味跟短暂过期时间 - 缓存雪崩:指所有的健都设置了同一时间过期,然后百万并发一起砸向了数据库.
解决方案:再设置失效期的的时候再加上个随机值,比如1-5分钟,最大程度的避免同时过期 - 缓存击穿:比如iphone的预售活动,正好缓存过期,几百万人又同时砸向数据库
**解决方案:**大量的并发,只能让一个人去查,其他人等待,查到以后去释放锁,其他人拿到锁就先去查缓存,就会有数据不用去db查 - 单体锁
注:锁的时序问题:假如1号线程进来拿到锁查询->查询缓存没有->查询数据库->释放锁->放入缓存时需要网络交互,但此时2号就已经又拿到锁又去缓存中查,结果1号的还没放进去,于是就又查询数据库, - 所以得调整为:
注:查到数据后先去放到缓存,最后再释放锁,测试单体应用没问题(但是多启动几个商品服务就有每个都查数据库的情况)如下:
注:本地锁:只能锁住当前的进程,假如商品服务有8个机器,那就8把锁,只能各自锁自己的,在分布式情况下想要锁住所有的只能使用分布式锁 - 分布式锁演进一:
redis的占坑命令:模拟4个redis客户端给redis发占坑命令:
结果:1号:nil 2号ok 3号nil 4号nil - 分布式锁的演进2:
问题:业务处出现异常导致没有执行删锁,就会进入死锁状态?
解决:获取锁的时候设置过期时间
出现问题:如果拿到锁设置时间前服务器断电等又成死锁? - 分布式锁的演进3:
问题:假如我们锁设置10s过期,但是业务代码执行的时间比较长,这时候有可能别人又已经把坑站了.我们采取删锁有可能删的是别人的锁; - 分布式演进4:
- 分布式最终形态:
- 最优方案:redisson:
1.引入坐标
2.配置:
@Configuration
public class MyRedisConfig {
/**
* 所有对Redisson的使用都是通过RedissonClient
* @return
* @throws
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
//1、创建配置
Config config = new Config();
//rediss为启用ssl连接
config.useSingleServer().setAddress("redis://192.168.2.5:6379");
//2、根据Config创建出RedissonClient实例
//Redis url should start with redis:// or rediss://
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
测试
//测试redisson
@GetMapping
@ResponseBody
public String hello(){
//获取一把锁,只要锁的名字一样就是同一把锁
RLock mylock = redissonClient.getLock("mylock");
//加锁
//mylock.lock(); //阻塞等待,一直等待拿锁
mylock.lock(10, TimeUnit.SECONDS); //设置锁的过期时间为10s,下面的业务要执行30s,会不会锁不住???
//手动指定解锁时间情况,一旦业务大于解锁时间就会锁不住!!!所以一定要解锁时间大于业务时间才行
try{
//模拟业务超长执行,睡眠30s,默认加锁都是30s,不够自动续
Thread.sleep(30000);
//如果业务超长,redisson会给锁自动续新的30s
//如果当前业务执行完成,就不会再续时间,即使不手动解锁,也会30s到了自动解锁
System.out.println("加锁成功,执行业务代码...线程id:"+Thread.currentThread().getId());
} catch (Exception e) {
e.printStackTrace();
} finally {
//执行完业务解锁
mylock.unlock(); //问题:假如执行业务时系统断电,会不会出现死锁情况?
System.out.println("业务执行完毕,释放锁...线程id:"+Thread.currentThread().getId());
}
return "hello";
}
redis中的存储情况
假如业务执行时断电会不会出现死锁?不会
看门狗机制:手动设定解锁时间是不会自动续时间的
注:手动指定解锁时间情况,一旦业务大于解锁时间就会锁不住!!!所以一定要解锁时间大于业务时间才行
推荐:自己手动设定时间,省掉了定时任务的消耗时间
读写锁:
/**
*读写锁
保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共锁
写锁没释放读就必须等待
读+读:相当于无锁,并发读,只会在 redis中记录好,所有当前的读锁。他们都会同时加锁成功
写+读:等待写锁释放
写+写:阻塞方式
读+写:有读锁。写也需要等待。
只要有写的存在,都必须等待
*/
@GetMapping("/write")
@ResponseBody
public String writeValue(){
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
String s="";
//拿到写锁
RLock writeLock = readWriteLock.writeLock();
try{
writeLock.lock();
s= UUID.randomUUID().toString();
Thread.sleep(30000);
redisTemplate.opsForValue().set("writeValue",s);
}catch (Exception e){
e.printStackTrace();
}finally {
writeLock.unlock();
}
return s;
}
@GetMapping("/read")
@ResponseBody
public String readValue(){
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
String s="";
//拿到读锁
RLock readLock = readWriteLock.readLock();
try{
readLock.lock();
s= UUID.randomUUID().toString();
Thread.sleep(30000);
redisTemplate.opsForValue().get("writeValue");
}catch (Exception e){
e.printStackTrace();
}finally {
readLock.unlock();
}
return s;
}
总结: *读写锁
保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁
写锁没释放读就必须等待
读+读:相当于无锁,并发读,只会在 redis中记录好,所有当前的读锁。他们都会同时加锁成功
写+读:等待写锁释放
写+写:阻塞方式
读+写:有读锁。写也需要等待。
只要有写的存在,都必须等待
信号量:也可以做限流
/**
* 提前在redis中存入park值为3
* 信号量
* 模拟车库停车,3个车位
*/
@GetMapping("/park")
@ResponseBody
public String park() throws Exception {
RSemaphore park = redissonClient.getSemaphore("park");
//park.acquire(); //获取一个信号量,获取一个值占一个车位,阻塞方法,获取成功就ok,获取不成功就一直等待获取
boolean b = park.tryAcquire(); //能停就直接返回true,不能停就直接返回false,不像上面阻塞等待
if (b){
System.out.println("执行业务...");
}else{
return "erro,当前活动太火爆,请稍候再试";
}
return "ok=>"+b;
}
@GetMapping("/go")
@ResponseBody
public String go(){
RSemaphore park = redissonClient.getSemaphore("park");
park.release(); //释放一个信号,释放一个车位
return "ok";
}
说明:首先往redis中存入park值为3,调用一次park方法值就减1,到0的时候调park.acquire()就会阻塞等待,直到掉一次go才会阻塞结果返回ok,而park.tryAcquire();则直接返回true跟fasle不用阻塞
闭锁
场景:比如放假了门卫要锁门,有5个班级,必须等到5个班的人全部走完了才锁门
/**
* 闭锁
* 比如放假了门卫要锁门,有5个班级,必须等到5个班的人全部走完了才锁门**
*/
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws Exception {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
door.trySetCount(5); //设置五个班
door.await();//等待闭锁都完成,掉完以后处于阻塞状态
return "放假了";
}
@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id){
RCountDownLatch door = redissonClient.getCountDownLatch("door"); //拿到的是同一把锁
door.countDown();//计数器减1
return id+"的人都走了!";
}
优化分类的代码
//基于redission加锁
public Map<String, List<Catelog2Vo>> getCategorylogjsonFromDbWithRedissionLock() {
//获取redis分布式锁,去redis占坑,锁的名字=锁的力度,
//锁的粒度:具体缓存的是某个数据,11号商品:product-11-lock, 12号商品:product-12-lock
RLock lock = redissonClient.getLock("catelogJson-lock");
lock.lock();//加锁
Map<String, List<Catelog2Vo>> dataFromDb=null;
try{
dataFromDb = getDataFromDb();
//无论业务成功与否都给解锁
}finally {
lock.unlock();//解锁
}
return dataFromDb;
}
缓存数据一致性问题:
1)双写模式(会有脏数据假如1先改但是速度还没有后面的改的快)
2)失效模式
我们的系统采用读写锁