功能实现介绍:
先判断优惠卷的信息保证不过期等,为了防止一个用户多次进行插入,要结合悲观锁。故
根据userId在redis生成分布式锁,使得之后的用户请求无法实现。
生成订单。是对优惠卷的数目进行更新,可以使用乐观锁,所以在插入前,判断某个值是否满足即库存是否大于0,如果大于则继续执行,
可能出现的问题(线程安全):
1.单用户多购买:
在线程a执行插入操作时,线程b绕过了当前无订单的判断也准备执行插入操作。
2.在多用户准备进行消费卷抢购时,密集执行库存减操作导致库存出现负数。
全局id生成
生成原因:使用自增id会被恶意猜测到数据的变化,所以重要id要用一个序列串
实现:保存某个时间的时间错,减去当前的。即可获得时间序列。
获得自增id,为了保证不重复性,根据日期以天的形式对自增id进行刷新。然后左翼32位通过或操作返回序列号
返回的值是一个64位,因为long的长度是64,首位会补0
优惠卷
使用优惠卷
引发线程安全问题
锁
乐观锁适合更新数据,而不是插入数据
乐观锁解决--CAS:
在写操作时才进行判断是否写
优惠劵--一人一单
使用synchronized实现悲观锁。
出现问题:多台服务器下,服务器间线程无法干预
使用分布式锁解决
出现问题2:当运行的时间操过过期时间,再执行删除时会将其他线程的锁删除,故在删除锁前判断锁是否属于自己。
实现:根据线程id为标识执行setnx的操作,观察锁是否被使用
解析:redis的键为用户,保证setnx单个用户只能有一把锁。值为线程id(uuid),保证了不会出现误删,防止因为时间缓慢等操作导致执行删除操作时属于自己的线程锁过期,而删除了其他的线程锁。
出现问题3:确定是自己准备进行删除时发生了延迟,导致过期又没能删除正确的。理由:非原子性,需要保证命令是一次性全部执行完,才会进行其他操作。
解决:实用lua脚本
有key和argv2个数组,第一元素是在1的位置
java实现:
设置脚本参数
在执行redis命令时携带,则会将lua脚本的命令执行,实现redis,同时支持传参数
结合redisson
可重入锁
问题产生:一个线程多次对一个锁判断,如果使用上面的setnx无法实现。
实现思路:
将redis的储存转为hash多一个value的值。在进行锁判断时要进行一个锁是否为自己的判断,所以setnx失效,同时使用value的+-实现锁的多次读取。
lua脚本--获取锁
使用了hexists先判断锁是否是自己。(用于判断一个hash值是否存在)
lua脚本--释放锁
settNx的不好
异步的操作--优化秒杀
前提:商品卷的数据都要永久保存在redis中
1.在执行写操作等耗时久的操作分离出去。
实现:
通过lua判断后确认可以下单,生成要向数据库添加的信息后加入到阻塞队列中
在redis中确认可以实现操作后,将要保存传入异步操作,进行异步处理。
在init保证资源加载成功后启动这个线程,通过阻塞队列的方式,当队列有数据时开始对数据库进行操作
redis消息队列实现秒杀
将队列存入jvm中会带来错误,比如用户以为已经成功,但是jvm崩溃导致异步队列的数据无法完成等操作。故借助redis队列实现
基于list的消息队列
lpop和blpop的区别
好处:存入到redis中是数据的持久化,就算宕机后再次启动数据仍然存在。
坏处:取出后处理过程中失败,会导致数据失效。队列的数据只允许被拿走一次。
基于pubsub的消息队列
优缺点
基于stream的消息队列
基于消息组
多个消费者在一个组内去操作一个消息队列。
将队列和组建立关联。
消费者从组中读数据,实际上是从队列s1中读取数据。末尾是 >
表示读取那些还没有被消费的队列数据
通过ack从pending-list中确认数据
查看pending-list中剩余数据
当读取的方式是读取第一个 末尾是0,会读取处于消息队列的第一个元素。这种形式就算被读取了,下次读取仍然是k6 v6,因为在pendlist中没有被处理。当ack处理后将从消息队列中消失。
实现:XGROUP CREATE stream.orders g1 9 MKSTREAM