node.js 在服务器端避免重复提交有没有什么好办法吗?
发布于 7天前 作者 zxl777 573 次浏览 来自 问答

由于客户端app偶尔会快速提交两个重复数据,导致数据库出现两个重复数据。

我在数据库里做了检查重复数据的设置也没用,因为有时两个重复提交时间相隔很近, 第一个数据还没写入数据库,第二个重复数据的提交就到了,导致数据库里还是出现了两个重复的数据。

在客户端当然有办法,比如app限制提交数据的频率。

我很想知道,在node.js 服务器端,对付这种间隔很短的重复数据的提交有没有什么好办法呢?

20 回复

第一个数据还没写入数据库,第二个重复数据的提交就到了,导致数据库里还是出现了两个重复的数据

这是有多快…还是说服务器处理慢?

试试在客户端限制吧.

  • 方案1 提交事件触发后, 禁用控件,不让客户提交
  • 方案2 设置一变量作为标识,如果是0就接受提交,如果是1就不接受. 每次提交的时候设为1,服务端响应之后设为0

比如这两个一样数据,相差0.0001秒 。 服务器端的解决办法,我现在搜索的结果,只有用“队列”。

给数据标加个唯一标识ID?查询唯一ID不存在就写入数据库?

@esc228 第一个数据还没来得及写入数据库,所以查不到。

(1)前端可以限制,前端限制 (2)如果数据库支持事务,则在事务里面进行判断数据是否发生变更 (3)如果不支持事务,或者不使用事务,那肯定要使用一个标志如时间戳,根据最后时间戳相等才能修改。怎么样保持标志的同步,就看你的实际需要进行设计了

自己看了redis的文档,redis的集合天生适合用在这里,集合的数据一定是唯一的。 我开一个小内存的redis,客户端频繁提交的token都先push到redis集合,然后另外一个守护程序再慢慢把tokens都pop到mysql里。

你这是标准的异步IO问题。 这种IO的问题 最好使用内存队列解决 写起来也不麻烦啊,你这类操作入队他的回调,处理队列的时候,判断是否执行过,标志位记下,就能解决了。 node本身IO全部异步,所以这种问题都是用内存来做同步。 引用一句话:nodejs除了js是同步执行,其他全是异步。所以你要处理异步问题,就用内存来同步管理。

@zxl777 楼主我也遇到了类似的问题,由于基础比较差,所以有些东西想请教一下,可以给我你的联系方式么

@sunwenteng 应该和redis是一回事,其实就是内存队列。数据先写到内存的队列里,没问题了再写入mysql数据库。

@zxl777 redis我感觉有点杀鸡牛刀了,何必如此费劲

@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));
    }
} 

@sunwenteng 谢谢你的代码。 但很抱歉没看懂: 1、sessionId 在哪里赋值的? 2、next是做什么用的?为什么有这句 sessionInProgresssessionId;

谢谢大家的答复,甚至还提供了详细的源码。 也许我找到了最省事的答案,也分享给大家。 其实这个问题本质就是“限定一个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 把上次逻辑执行的结果直接返还个前端

@zxl777 关键点就是保证你的消息是按序执行的,执行完一个才执行一下个,这样的话,你后端的逻辑就可以检查数据库里有没有这个值了,有的话,就不用添加了

distributed lock?

@fantasticsoul 谢谢,但不用那么麻烦。我用上面的中间件,限制API访问频度,用现成轮子就可以了。

你说的如果是事务操作,redis也有现成的解决办法,就可以让一个事务不可分割和插入,具体redis文档有。

回到顶部