表结构设计:
发红包时记录红包相关信息表、发红包时生成的对应随机金额信息表,以及抢红包时用户抢到的红包金额记录表。

“发红包”模块的核心处理逻辑在于接受前端发红包者设定的红包总金额M和总个数N,后端接口根据这两个参数,采用二倍均值法生成N个随机金额的红包,最后将红包个数N与随机金额列表List存至缓存中,同时将相关数据异步记录到数据库中。

后端接口在接收到前端用户发红包的请求时,将采用当前的时间戳(纳秒级别)作为红包全局唯一标识串,并将这一标识串返回给前端,后续用户发起“抢红包”的请求时,将会带上这一参数,目的是为了给发出的红包打标记,并根据这一标记去缓存中查询红包个数和随机金额列表等数据

当群里的成员看到有人发红包时,正常情况下都会点该红包图样,并由此开启后端处理抢红包请求逻辑的节奏。首先后端接口将会接收红包全局唯一标识串和用户账号id,从缓存系统中取出红包的个数,判断个数是否大于0。如果大于0则表示缓存中仍然有红包,然后从缓存系统的随机金额列表中弹出一个随机金额,判断是否为Null。如果不为Null,则表示当前用户抢到红包了,此时需要更新缓存系统的红包个数并异步记录相关信息到数据库中。

高并发问题:
通过前面的分析基本上可以得知问题产生的原因在于:同一时刻多个并发的线程对共享资源进行了访问操作,导致最终出现数据不一致或者结果并非自己所预料的现象,而这其实就是多线程高并发时出现的并发安全问题。在传统的单体 Java应用中,为了解决多线程高并发的安全问题,最常见的做法是在核心的业务逻辑代码中加锁操作(同步控制操作),即加Synchronized关键字。然而在微服务、分布式系统架构时代,这种做法是行不通的。因为Synchronized关键字是跟单一服务节点所在的JVM相关联,而分布式系统架构下的服务一般是部署在不同的节点(服务器)下,从而当出现高并发请求时,Synchronized同步操作将显得“力不从心”!因而我们需要寻找一种更为高效的解决方案。这种方案既要保证单一节点核心业务代码的同步控制,也要保证当扩展到多个节点部署时同样能实现核心业务逻辑代码的同步控制,这就是“分布式锁”出现的初衷。

由于Redis底层架构是采用单线程进行设计的,因而它提供的这些操作也是单线程的,即其操作具备原子性。而所谓的原子性,指的是同一时刻只能有一个线程处理核心业务逻辑,当有其他线程对应的请求过来时,如果前面的线程没有处理完毕,那么当前线程将进入等待状态(堵塞),直到前面的线程处理完毕。

Redis作为一款具有高性能存储的缓存中间件,给开发者提供了丰富的数据结构和相应的操作。由于Redis底层架构是采用单线程进行设计的,因而它提供的这些操作也是单线程的,即其操作具备原子性。而所谓的原子性,指的是同一时刻只能有一个线程处理核心业务逻辑,当有其他线程对应的请求过来时,如果前面的线程没有处理完毕,那么当前线程将进入等待状态(堵塞),直到前面的线程处理完毕。对于抢红包系统“抢红包”的业务模块,其核心处理逻辑在于“拆红包”的操作。因而可以通过 Redis的原子操作setIfAbsent()方法对该业务逻辑加分布式锁,表示“如果当前的Key不存在于缓存中,则设置其对应的Value,该方法的操作结果返回True;如果当前的Key已经存在于缓存中,则设置其对应的Value失败,即该方法的操作结果将返回False。由于该方法具备原子性(单线程)操作的特性,因而当多个并发的线程同一时刻调用setIfAbsent()时,Redis的底层是会将线程加入“队列”排队处理的。