一、Redis事务的定义
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化,按顺序的执行。事务在执行的过程中,不会被其他客户端发送来的请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
二、Redis事务的指令
Multi:开启事务
Exec:执行事务
discard:取消事务
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行。直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中,可以通过discard来放弃组队。
三、Redis事务的错误处理
1.组队中某个命令出现了报告错误,即语法错误,执行时整个队列中的命令都会被取消。
报告错误就相当于Java中的编译时异常,连启动都无法启动。
2.如果在执行执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他命令都会执行,且不会回滚。
执行阶段错误就相当于Java中的运行时异常一样。
四、redis为什么需要事务
Redis是单线程处理,也就是命令会顺序执行。那么为什么会存在并发问题呢?虽然redis是单线程,但是可以同时有多个客户端访问,每个客户端会有一个线程。客户端访问之间存在竞争,redis里面只有单个命令是执行的。
比如set,get。每执行一个命令都需要客户端来竞争,所以可能出现并发问题,但如果你的命令希望把一组命令执行的结果作为整体,要么全部成功,要么失败,就必须用锁,或者事务。
redis的事务是为了解决事务冲突的问题,举个场景的例子,在进行购票的时候,数据库中仅剩一张票了,张三和李四登录网站,同时看到了这张票,同时对这张票发起了订票请求,如果不加入事务,那么这张票会被卖给两个人,这在现实生活中是不允许的,所以就会加入事务进行控制。而redis的事务控制是属于乐观锁形式的。
悲观锁:
顾名思义就是看待事务是以悲观的态度来看待的,使用悲观锁,它会认为我在进行数据的操作过程中,会有其他人也会对这条数据进行操作,那么就会造成有一个人操作的是错误的数据,所以它就会对这条数据从一开始就锁住,即我在操作该条数据时,这条数据就不再接受其他人的操作请求,知道我操作完毕之后。这就好比上厕所,我进去上厕所了就会把厕所门关上并锁住,别人再急也得在外边等着,直到我上完了出来为止。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。
乐观锁:
Redis的事务控制就属于乐观锁,乐观锁顾名思义就是保持乐观,它认为我在操作某条数据时是不会有其他人同时来对该条数据进行操作的。这就好比你去上厕所不关门,心里觉得反正不会有人那么变态来看你上厕所的。虽然乐观锁觉得在操作数据时不会有别人同时对数据进行操作,但是在更新时也会判断一下在此期间别人有没有去更新这条数据,可以通过版本号等机制进行判断。当你在操作数据时,你会得到这条数据的当前版本号,当你操作成功之后就会将这条数据的版本号进行更改,下一次操作时就会得到新的版本号,所以在最后对数据进行更新时,若版本号不能匹配则不会被允许更新数据,会被重新去请求操作该条数据。
乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
五、监视命令:WATCH key1 key2 ...
在执行Multi命令之前,先执行 watch key 命令,可以监视一个或者多个key,如果在事务执行之前这个或这些key被其他命令所改动,那么事务将被打断,即这个事务中的所有指令将全部取消。
六、取消监视:UNWATCH
unwatch即取消watch命令对所有key的监视;如果在执行watch命令之后,exec命令或者discard命令先被执行了的话,那么就不需要执行unwatch命令了,因为此时已经没有意义了。
七、Redis事务的三大特性
1.单独的隔离操作
事务中的所有命令都会序列化,按顺序的执行。事务在执行的过程中,不会被其他客户端发来的命令请求所打断。
2.没有隔离级别的概念
队列中的命令在没有提交(exce)之前都不会被实际的执行。因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个万分让人头疼的问题。
3.不保证原子性
Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,不能回滚。在传统的关系型数据库中则必须保证原子性,要么同时成功,要么同时失败,一条失败,一个事务中已经成功了的命令就会被回滚。
八、redis实现秒杀的过程
1.将产品数量设置在string类型里面:
set prodouctNum 100
2.将秒杀成功的用户的userid标识符设置在set类型中,set类型无序不可重复。
3.逻辑:用户点击秒杀按钮进行秒杀任务,进入程序中,首先判断库存量是否为0,为0则返回秒杀结束提示语;接着判断set集合中是否存在该用户--
//判断set集合中是否存在某个值
sismember keyname value
若已存在该用户,则返回已秒杀成功,不允许重复秒杀的提示语;以上均不满足,则可以获取到该产品,进行产品数量减1,并将该用户添加进set集合中。
4.以上步骤可能会出现三种问题,一是超时问题;二是超卖问题,即原本库存100件产品,最后确卖了一百多件出去;三是少卖,既是原本打算卖100件,结果却还遗留了几件没卖完。
5.解决超时问题,设置连接池可解决。
6.解决超卖问题,超卖问题是由于并发带来的,此时应使用redis事务,先使用watch监视产品数量的变化,使用Multi开启事务,使用Exec执行事务。产品数量一旦发生变化,则同一批的并发请求全部失败,此时只会有一个请求秒杀成功,其余全部失败。那么就可能会造成少卖的问题。
7.解决少卖的问题,因为使用事务之后会造成一批请求中只会有一个秒杀成功,则可能会造成少卖。此时建议使用Lua脚本解决,用以保证只要产品数量不为0,那么先到的请求就可以秒杀成功,而不会导致一个成功,其余失败的问题。