1.优惠卷秒杀
(1)全局唯一ID
订单如果用自增,容易被猜到交易量,且数据量大的话分成多个表,都是自增,容易出现重复。所以用全局ID生成器,Redis独立自增不会重复,但安全性不保证,所以用符号位+时间戳+序列号来代替:
时间戳:当前时间-初始时间
序列号:设置每天一个KEY,在一天内KEY都相同,不同天的KEY不相同。
时间戳左移32位,留出32位进行拼接,然后让时间戳和序列号完成|运算,即可完成拼接。
还可以使用雪花算法来生成全局唯一ID
(2)添加优惠卷
优惠卷分为普通优惠卷和秒杀优惠卷,秒杀优惠卷优惠力度大,有库存、开始时间、结束时间,这是比普通多的信息。
(3)实现秒杀下单
判断1、是否在时间内
2、库存是否足够
(4)超卖问题
两个线程并发执行,线程1先查询,线程2随后查询,线程1库存-1,线程2库存-1,但线程1去-1后库存变为0了,线程2再减就变为-1了这就是超卖。在修改之前,都查询完。
解决方法:悲观锁或乐观锁
乐观锁:很乐观认为不会修改数据,每次就在更新数据时判断数据有没有修改,若修改发生异常。
给设置一个版本号,线程1、2先查询版本号为1,线程1去-1则版本号+1为2,线程2修改数据判断发现版本号变了就不改了。若用库存stock去代替版本号,那么就是CAS法。要使stock>0,因为若只等于的话,会造成一个线程成功修改,其他线程失败就不尝试了。
(5)实现一人一单功能
秒杀优惠卷需要限量,一个用户只能用它下一单。
所以扣库存之前需要先根据userid和优惠卷id判断数据库内是否已经有了订单,若有了就错误。可以用悲观锁,把功能封装成类,用的时候调用它,同步锁就在调用它的地方加,这样先加锁,然后到下单一直都有锁,其他线程不会趁虚而入。(仅限单机情况下)
集群情况下上边的方法就不行了,例如有两个JVM,里边有两个锁监视器,两个线程都可以各获得一把锁互不干扰,都可以去减库存。
(6)分布式锁
在多个JVM外边创建一个JVM都可以看见的并且互斥的锁监视器,这样一旦一个线程获取锁,其他JVM就会获取失败。可以利用Redis来实现,因为Redis独立与JVM,并且利用setnx来实现互斥,向Redis中set数据时,只有里边不存在数据时才能set成功,否则失败,这样来保证互斥。安全性利用锁超时释放。
(7)Redis分布式锁误删问题
线程1获取锁,然后业务阻塞,一直到锁超时释放,这时候线程2可以获取锁执行业务,过一段时间,线程1业务执行完毕,去执行释放锁操作,线程2还没完成业务,线程3也获得了锁,这时候多个线程同时执行,出现安全问题。
解决方法:先获取锁的标识(用UUID),释放锁的时候,判断一下锁的标识看是否是同一把锁,若是就释放失败,不是可以释放。
(8)分布式锁的原子性问题
上边的解决后,若在判断操作和释放锁操作之间,业务阻塞了(JVM的Full GC垃圾回收机制造成所有都阻塞了),然后锁超时释放,这是线程2获取锁,线程1阻塞结束释放锁,线程2被释放锁,线程3获取锁。
解决方法:将判断操作和释放锁操作变成原子性。
用lua脚本执行命令时,会一块执行保证原子性,具体lua命令查菜鸟教程。
(9)Redisson实现分布式锁
因为setnx会有
重试获取锁:
防止锁提前超时释放:
解决主从一致性问题:可以试用Redisson的联锁来解决,核心是开启多个Redis的主节点,设置锁时,必须所有主节点都写入成功,才算设置成功。
实现联锁的关键点:
1、所有节点都必须成功才行,否则就要从头来过
2、在所有锁设置成功后,要设置统一释放时间。
3、锁释放时间必须要大于抢锁等待时间,不然还没抢到锁就先释放了
(10)异步秒杀
为了优化秒杀业务,首先校验功能执行快,创建订单涉及到数据库操作执行慢,原本他俩在一个线程拖慢速度,现在把他俩分离开来,这样提高了速度。所以校验完后直接返回订单id给前端,创建订单操作数据库可以和校验异步执行。校验这部分就放到Redis里去执行。并且用set集合去储存数据。校验这部分放到lua脚本里去做,java代码只需调用id即可。注意下单业务是放到阻塞队列中的跟之前的校验异步下单
(11)Redis消息队列实现异步秒杀
消息队列中分为生产者、消息队列、消费者。相当于快递员、快递柜、我。把下单那一部分放到消息队列,我什么时候用就什么时候取。且它独立与JVM,不会造成内存溢出。
stream的单消费者模式:消息可以回溯、一个消息可被多个消费者读取、可以阻塞读取就是什么时候新消息发过来再读取,但是若在读取之前一下发了多条消息只能读最新的一条会漏读。
stream消费者组模式:
消费者组:同组中多个消费者相互竞争,只有一个消费者能抢到,增加消费能力。
消息标识:比作书签,从上次没读完的地方接着读
消息确认机制:默认消费的信息为pending状态,会放到每个消费者的pending-list中去,只有确认(ACK)以消费才移除,这样Redis宕机也可以从新处理。
点赞
首先判断是否已经点过赞了,防止同一个用户重复点赞,就给Blog表内设置一个islike字段来判断是否点过赞。用Redis的set集合,若已存在点赞则失败,不存在就成功然后把点赞信息放进去。
点赞排行榜
去显示最新点赞的五个人,用Redis的Soretedset来排序,里面会自动按score来排序,把score设为时间就行。
关注与取关
写关注、取关、判断关注接口。关注要先获得用户id,再封装到接口、保存,取关就直接remove()删除就行。
共同关注
本质是求两个用户关注集合的交集,用set集合。因为set有可能过期,过期后开启异步任务,构建缓存。
Feed流
Feed流指像抖音那样不断下拉刷视频那种,而现在要做的是Feed流关注推送给粉丝。
Feed流分两种:
1、Timeline:按时间顺序排的,比如朋友圈。
2、智能排序模式:抖音推荐算法
我们用的是Timeline,分为推、拉、推拉结合模式。
拉模式:up主一个发件箱,粉丝一个收件箱先到发件箱,再到收件箱。适合粉丝数多的。
推模式:只有粉丝有个收件箱,直接发到收件箱,适合粉丝数少的。
推拉结合:给活跃粉丝进行推模式,普通粉丝拉模式。
GEO地理坐标计算
GEO是Redis实现储存多个地理坐标、计算坐标间的距离、坐标半径范围内的其他点和距离(附近的人)GEO底层是Sortedset类型
签到用Bitmap
一共31bit,一个bit上0或1代表是否签到(二进制101010等),一个月正好31天,这样用的数据也少,底层是string类型
统计连续签到天数
从后往前统计,直到遇到0就断签,至于如何从后向前遍历,就是拿数据和1做与运算,都是1结果为1,否则为0,先从最后一位比较,然后数据右移比较倒数第二位,以此类推。