目录java
问题起源,微信小程序抽风 wx.request() 重复请求服务器提交数据。后端服务也很简单,伪代码以下:spring
class SignLogService { public void saveSignLog(SignLogDO log) { // 简单插入作记录 SignLogDAO.insert(log); } }
发现数据库会存在重复数据行,提交时间如出一辙。但业务需求是不能有多余的 log 出现,这明显是个问题。数据库
问题是,重复请求致使的数据重复插入。这问题形成的后果很明显:小程序
问题如图所示:后端
解决方式:如何将 同请求 A,不执行插入,而是读取前一个请求插入的数据并返回。解决后流程应该以下:微信小程序
上面说的那种业务场景:sign_log 表会有 user_id、sign_id、sign_time 等。那么每次签到,每一个人天天只有一条签到记录。springboot
数据库层采起惟一索引的形式,保证数据记录惟一性。即 UNIQUE 约束,UNIQUE 约束惟一标识数据库表中的每条记录。另外,user_id,sign_id,sign_time 三个组合适惟一字段。创表的伪代码以下:服务器
CREATE TABLE sign_log ( id int NOT NULL, user_id int NOT NULL, sign_id int, sign_time int, CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time) )
重点是 CONSTRAINT unique_sign_log UNIQUE (user_id,sign_id,sign_time)
。有个小问题,数据量大的时候,每条记录都会有对应的惟一索引,比较耗资源。那么这样就好了吗?微信
答案是不行,服务不够健壮。第一个请求插入成功,第二个请求直接报错,Java 服务会抛出 DuplicateKeyException
。并发
简单的幂等写法操做便可,伪代码以下:
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 幂等处理 SignLogDO insertLog = null; try { insertLog = signLogDAO.insert(log); } catch (DuplicateKeyException e) { insertLog = selectByUniqueKeys(userId,signId,signTime); } return insertLog; } }
的确,流量不是很大,也不算很高并发。重复写问题,这样处理便可。那大流量、高并发场景咋搞
流量大了后,单库单表会演变成分库分表。那么基于单表的惟一索引形式,在碰到分表就没法保证呢,插入的地方多是两个分表 A1 和 A2。
解决思路:将数据的惟一性条件放到其余存储,并进行锁控制
仍是上面的例子,天天,每次签到,每一个人只有一条签到记录。那么使用分布式锁 Redis 的解决方案。大体伪代码以下:
// 加锁 jedis.set(lockKey, requestId, "NX", "PX", expireTime);
// 解锁 jedis.eval(script, lockKey,requestId);
class SignLogService { public SingLogDO saveSignLog(SignLogDO log) { // 幂等校验 SignLogDO existLog = selectByUniqueKeys(userId,signId,signTime); if(Objects.nonNull(existLog)) { return existLog; } // 加锁 jedis.set SignLogDO insertLog = signLogDAO.insert(log); // 解锁 jedis.eval return insertLog; } }
这个方案仍是不是很成熟,你们参考下便可。
解决方案实战中,了解具体术。概括以下: