Redis是单线程加多路IO复原的形式,MemCache是多线程加锁的显示
Redis本身是单线程的形式,所以Redis的操作是本身就具有原子性的,每执行的命令都是有原子性的
原子性:指不会被线程调度机制打断的操作
操作一旦执行,就一直运行到结束,中间不会有任何线程将操作切换进来
- 在单线程中,能够再单条指令中完成的操作都可以被认为是"原子性操作",因为中断只能发生再指令之间
- 在多线程中,不能被其他进程(线程)打断的操作也叫原则性操作
Redis的原则性主要由它本身的单线程所决定的
Redis也提供了事务的操作
一,Redis事务的概念
Redis事务是一个单独的隔离操作:事务中的所有命令都会被序列化,按顺序执行执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
Redis事务的主要作用:串联多个命令防止别的命令插队
Redis事务的三大命令:
- Multi(组队阶段):开启一个事务,所有操作依次进入队列(不执行)
- Exec(执行阶段):执行事务中的命令,依次执行
- Discard:组队阶段中放弃组队
- 组队阶段命令出错误,执行exec时这个事务的所有操作都会被取消
- 执行阶段命令出错误,那个错误命令会报错不会执行,其他命令不受影响,这个时候它不像MySql是原子性的
一,Redis事务的冲突问题(悲观锁和乐观锁)
事务冲突主要发生在并发情况下,当多个用户同时修改一条数据时就会出现事务冲突问
例:现在有redis客户端,设置money为1000,有三个连接客户端分别请求服务器并修改money数据
解决这种事务冲突的方案主要有两种:
- 悲观锁
- 乐观锁
二,悲观锁
悲观锁,顾名思义就是对未来的操作很悲观,每一次拿数据的时候都认为别人会修改,所以在每一次在拿数据的时候都会上锁,这样别人想拿这个数据就需要先block锁,直到拿到锁,一次只有一个线程能拿到锁
悲观锁的实现本质上就是加锁,传统的关系型数据库就是使用悲观锁,比如MySql的行锁,表锁,读锁,写锁等都是悲观锁的实现
悲观锁本质上操作是串行的,没有获取到锁的对象需要进行等待,依次获取锁执行,效率很低
注:Redis并没有自带悲观锁的操作,要实现悲观锁需要使用外部编程的方式的实现
三,乐观锁
乐观锁,顾名思义就是很乐观,每一次拿数据的时候都认为其他用户不会做修改,所以不会上锁,但是在更新的时候会判断一下在此期间有没有用户去更新这个数据,它的实现主要提供版本号,可以版本号判断其他用户有没有做修改
乐观锁:用于多读的应用类型,这样可以提高吞吐量,Redis本身自带了乐观锁机制(Watch)
四,Redis乐观锁操作
Redis默认自带了乐观锁,通过命令 WATCH key [key……]
在执行Multi之前,先执行watch key1 [key2],可以监视一个或多个key,如果事务在exec执行之前监听的key被其他命令改动,那么事务就会被打断执行失败
命令:
- watch key:监听key(本质上就是在key上面加一个版本号)
- unwatch:取消监听
通过乐观锁和基本使用分析Redis的事务特性:
- 单独的隔离操作:事务中的所有命令都会被序列化,按照顺序执行,事务在执行过程中不会被其他客户端发来的命令请求打断
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性:事务中如果有一条命令执行失败,其后的命令依然会被执行,没有回滚
二,通过Redis实现简单秒杀案例
一,无事务秒杀
一,无事务秒杀代码
① controll层
@Controller
public class sckillcontrol {
//秒杀的service
@Autowired
Seckillservice seckillservice;
//虚拟用户id,没有前端不能从前端获取
Random random = new Random();
@RequestMapping("/seckill")
@ResponseBody
public String seckill(){
//没有前端,固定商品id
String commodity_id="1001";
//每一次秒杀随机用户id
int userid = random.nextInt(1000);
//传入service
boolean b = seckillservice.seckill_service(commodity_id, userid);
if(!b){
return "秒杀失败!";
}
return "秒杀成功!";
}
② service层
@Service
public class Seckillservice {
@Autowired
RedisTemplate redisTemplate;
public boolean seckill_service(String commodityid,int userid){
//1,判断商品id和用户id是否为空
if(commodityid.equals("") || userid==0 ){
return false;
}
//2,对commodityid和userid做为key进行拼接
String commodity = "commodit:"+commodityid;
String user = "user:size";
//3,判断redis中commodityid是否存在,不存在则秒杀没有开始
Object o = redisTemplate.opsForValue().get(commodity);
if(o==null){
System.out.println("秒杀未开始!");
return false;
}
//4,判断commodity是否还要库存
Integer quantity = (Integer)redisTemplate.opsForValue().get(commodity);
if(quantity>1){
System.out.println("库存不够!");
return false;
}
//5,判断用户是否重复操作
if(redisTemplate.opsForSet().isMember(user,userid)){
System.out.println("用户重复操作!");
return false;
}
//6,秒杀库存
//库存减一
redisTemplate.opsForValue().decrement(commodity, 1);
//秒杀成功的用户添加到用户set集合中
redisTemplate.opsForSet().add(user,userid);
return true;
}
}
















Comments | NOTHING