后端如何处理接口幂等性?

2018-10-25 12:41:15 +08:00
 HarryQu

最近在搞 Spring Boot ,遇到了这样的一个问题 : 菜鸡如我,想问问大家是如何处理的。

场景 :

有个移动端的用户,手特别快, 注册的时候瞬间连点 100 下 ,发出了 100 个请求 :


问题 :

@Service
@Transactional
public class UserService {

    @Autowired
    UserDao userDao;
    
    public boolean register(User user) {
        Optional<User> user = userDao.findByTelephone(user.telephone);
        if (user.isPresent()) {
            throw new IllegalStateException("账号已存在");
        }
        userDao.save(user);
        return true;
    }
}

register 是非线程安全的 ,这样就导致该用户重复注册,数据库中有多条数据。


我的解决方案 : 加锁... 但是我觉得效率上个问题

大家是如何处理的呢 ?

8503 次点击
所在节点    程序员
38 条回复
q397064399
2018-10-25 15:49:40 +08:00
@p2pCoder #19 应用中加锁? 那不是要把用户注册信息加载到 Redis 里面 做分布式锁?
ysweics
2018-10-25 15:57:08 +08:00
就像前面说的,手机号码这种字段,数据库里面加 unique 索引,然后代码处理的时候加锁,我觉得单机或者分布式用 redis 锁来解决
metrxqin
2018-10-25 16:01:44 +08:00
这后端太不称职,数据表设计有严重缺陷。
p2pCoder
2018-10-25 16:39:16 +08:00
@q397064399 如果单机就是 普通的字段加锁就行
上了多台服务,肯定要用 redis 或者 zk 之类对相应字段加锁
我目前做的的都是 一个服务部署多台,用的是 redis 加锁
总之,别把压力抛给 db
Raymon111111
2018-10-25 16:41:35 +08:00
你这叫防并发不是幂等

幂等是用户慢慢的点 100 下, 处理正确
fkdog
2018-10-25 16:48:32 +08:00
首先 lz 一堆人把幂等和滤重混淆在一起了。什么是幂等?举个例子,就是一堆下订单的重复请求到达服务器,对所有的订单接口都应该返回一摸一样的订单号数据。

所以对于幂等要求,正确的做法应该是,接到请求以后去数据库里查询,是否已经存在相关订单数据,如果是,则直接返回已有的订单数据详情。

当然这个需要考虑一个情况,对于同一个商品,可能用户的确是手动下了两个一样的订单,所以需要判断是否是一定时间间隔范围内的。可以使用时间戳、唯一 token 等方式滤重。

另外,确保订单入库的过程在同一个事物中。
tabris17
2018-10-25 16:49:13 +08:00
@p2pCoder 程序怎么加锁?还要用分布式锁,这性能可靠性还不如数据库 unique 索引呢
foolish1024
2018-10-25 16:49:18 +08:00
乐观锁+唯一性索引
richieboy
2018-10-25 16:51:55 +08:00
防并发就是要锁吧,数据库为什么不能重复唯一值,应该也是数据库自己加了锁才实现的吧?我们做的就是根据实际情况尽可能选择轻量级的锁
p2pCoder
2018-10-25 17:13:05 +08:00
@tabris17 单机的话的直接对电话号码字段加锁就行
如果部署多台分布式的话,肯定就代表业务的并发正在或者以后潜在面临挑战,在这种背景下,把压力扔给 db 不合适,数据库的唯一索引校验,也是给数据库压力,当然如果 db 能接受,数据库唯一索引也行
这个唯一索引做幂等的本身的扩展性也是问题,当前业务下,我们是对 电话号码添加 unique 校验,但是后来 需要的是限制 姓名+电话号码,来防止重复注册,再到线上的已经膨胀的表中更改索引是不可取的,大表加个索引就可能影响整个 db

如果是 单机的话,synchronized (user.telephone.intern())就行了,也不用考虑分布式锁带来的问题

这为老兄的 user.telephone 写法也是很不讲究
HarryQu
2018-10-25 18:16:34 +08:00
@fkdog 不好意思,又查了下,确实我把概念给搞混淆了。
mmdsun
2018-10-25 18:48:37 +08:00
数据库加唯一索引就 OK 了
ppyybb
2018-10-25 20:15:46 +08:00
@fkdog 对于下单,不可能幂等,因为第一次一定会带来状态的变化。准确说这个接口是安全非幂等的。只是给建议的人知道楼主的实际需求所以没有纠结在这个概念上而已。去重显然能直接实现安全性
519718366
2018-10-25 20:40:38 +08:00
@Raymon111111 正解,感觉前 24 楼,全跑偏了
519718366
2018-10-25 20:44:58 +08:00
这是 leader 之前分享过的一个幂等性文章 https://blog.csdn.net/jks456/article/details/71453053
做的 b2b,没什么并发压力,在这块比较弱,不能回答你的问题,只能贴上这个文章了
luozic
2018-10-25 21:34:02 +08:00
单个 Ip 地址限制请求频率… 这不是网关默认有的功能?
passerbytiny
2018-10-26 09:24:32 +08:00
我看了你的代码,在已启动事务,并且查找到账户已存在的时候抛出"账号已存在",那么已经够了,用户点击 100 下,只有第一下回成功,后面的全部是“账号已存在”、“事务检测到脏数据”,或者“事务等待超时”。

你这情况,应该找数据库的问题,代码没有任何问题。
yc8332
2018-10-26 11:31:04 +08:00
数据库唯一必须的。然后就是接口是加锁的,不知道你说的效率什么意思。就是 mc/redis add 就能搞定了。。。还有就是前端要限制,尽量避免垃圾请求

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/501000

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX