由于客户端app偶尔会快速提交两个重复数据,导致数据库出现两个重复数据。
我在数据库里做了检查重复数据的设置也没用,因为有时两个重复提交时间相隔很近, 第一个数据还没写入数据库,第二个重复数据的提交就到了,导致数据库里还是出现了两个重复的数据。
在客户端当然有办法,比如app限制提交数据的频率。
我很想知道,在node.js 服务器端,对付这种间隔很短的重复数据的提交有没有什么好办法呢?
第一个数据还没写入数据库,第二个重复数据的提交就到了,导致数据库里还是出现了两个重复的数据
这是有多快…还是说服务器处理慢?
试试在客户端限制吧.
- 方案1 提交事件触发后, 禁用控件,不让客户提交
- 方案2 设置一变量作为标识,如果是0就接受提交,如果是1就不接受. 每次提交的时候设为1,服务端响应之后设为0
(1)前端可以限制,前端限制 (2)如果数据库支持事务,则在事务里面进行判断数据是否发生变更 (3)如果不支持事务,或者不使用事务,那肯定要使用一个标志如时间戳,根据最后时间戳相等才能修改。怎么样保持标志的同步,就看你的实际需要进行设计了
自己看了redis的文档,redis的集合天生适合用在这里,集合的数据一定是唯一的。 我开一个小内存的redis,客户端频繁提交的token都先push到redis集合,然后另外一个守护程序再慢慢把tokens都pop到mysql里。
你这是标准的异步IO问题。 这种IO的问题 最好使用内存队列解决 写起来也不麻烦啊,你这类操作入队他的回调,处理队列的时候,判断是否执行过,标志位记下,就能解决了。 node本身IO全部异步,所以这种问题都是用内存来做同步。 引用一句话:nodejs除了js是同步执行,其他全是异步。所以你要处理异步问题,就用内存来同步管理。
@sunwenteng 如果不用redis,用原生node.js怎么实现呢? var tokens = new Array(); 比如这个tokens列表设定200个。
新提交的token,先遍历tokens。 如果已存在,就忽略。 如果不存在,就添加到列表,并写入mysql数据库。
我写一个简单http响应的,当然其他的,网络层写法是一样的,这里是用标志位的也就是后续过来的请求我是直接else处理的,如果你有你的需求 可以在else分支里写
// 以下代码仅为范例,模拟http的一个请求是做插入操作的,并且立刻写入数据库
// 避免同一个会话多次重复提交同一个请求
var sessionInProgress = {};
var mysqlConn;
function httpPostInsertData(req, res, next) {
var sessionId; // 每一个链接都有一个独立的会话id
var param = req.data; // 你的参数
if(!sessionInProgress[sessionId]) {
sessionInProgress[sessionId] = next;
mysqlConn.query('insert into table set ?', param, function(err, result) {
sessionInProgress[sessionId](err);
delete sessionInProgress[sessionId];
});
}
else {
var errMsg = 'session is in progress, id=' + sessionId;
console.error(errMsg);
next(new Error(errMsg));
}
}
谢谢大家的答复,甚至还提供了详细的源码。 也许我找到了最省事的答案,也分享给大家。 其实这个问题本质就是“限定一个API接口的请求次数”。有这样一个现成的中间件express-rate-limit , 我设置1分钟只能请求一次,就能实现我的要求。 源码见 https://github.com/nfriedly/express-rate-limit/blob/master/lib/express-rate-limit.js
@zxl777 前端请求队列化处理,即每一个操作生成的请求对push到队列里,然后有个全局的httpmanager之类的类管理这个队列,不停的按序发送请求,1号请求发送成功并收到响应,再发2号请求,编号规则由后端定义,每次用户登录时获得一个编号,然后前端在此编号上加1,发送给后端,后端利用redis缓存,为每一个用户的请求设置如此标记:set yourReqKey -1 NX EX 12 得到ok响应响应值,表示首次执行该编号的请求,得到null值表示,该编号请求正在执行中, 当然,在此逻辑前先检查该用户的请求编号和缓存的编号,相差值是否为1,否则就可以就标记为非法请求了,也许这样说太抽象了,我写个示意吧: 这个是基于redis架构来做的: 前端:用户user登录----------->从后端获得编号seq值为52; 前端:操作a,生成请求,push到队列里,消息编号seq为53; 前端:操作b,生成请求,push到队列里,消息编号seq为54; 前端:操作c,生成请求,push到队列里,消息编号seq为55; 前端:httpManager发送编号为53的请求a给后端--------> 后端收到该用户请求,后端检查前端的消息里的seq值(此时后端用户里的curSeq为52), 如果seq为53,set user_53 -1 NX EX 12,响应ok,表示首次执行用户user的53号请求,执行完后将结果序列成字符串set user_53 yourResponseStr,把用户的curSeq值修改为53,并返回结果给前端; 如果seq为53,set user_53 -1 NX EX 12,响应null,表示用户user的53号请求正在执行中,get user_53得到-1,表示还是在执行中,发给前端前端做相应处理; 如果seq为52,表示52号请求后端回给前端回丢了,前端没收到,get user_52,把缓存的结果返回给前端 如果seq>53,非法请求! 不知题主能理解否O(∩_∩)O~
@zxl777 对了还漏说了一种情况: 53号消息后端回给前端,在网络上丢了,前端httpmanager有一种轮询机制,对每一条消息设一个超时时间1s,1s内没收到响应,就重发该编号的消息,最多重发5次(这个值可以自己设),5次都没收到,那么表示网络太差了, 而后端呢收到53号消息时,如果用户的curSeq值是53,那么不用执行逻辑了,直接get user_53 把上次逻辑执行的结果直接返还个前端
@fantasticsoul 谢谢,但不用那么麻烦。我用上面的中间件,限制API访问频度,用现成轮子就可以了。
你说的如果是事务操作,redis也有现成的解决办法,就可以让一个事务不可分割和插入,具体redis文档有。