01 | 创建和更新订单时,如何保证数据准确无误?
01 | 创建和更新订单时,如何保证数据准确无误?
讲述:李玥
时长15:32大小12.44M
订单系统的核心功能和数据
如何避免重复下单?
如何解决 ABA 问题?
小结
思考题
赞 50
提建议
精选留言(87)
- 李玥置顶2020-02-26hello,我是李玥。之后我都会在留言板上同步上节课思考题的答案,欢迎你跟我一起学习讨论。 在课前加餐这节课里,我给你留了道思考题,让你作为公司的CTO,想一想上节课我们提到的电商系统,它的技术选型应该是什么样的? 关于这个问题,我是这么理解的。 技术选型本身没有好与坏,更多的是选择“合适”的技术。对于编程语言和技术栈的选择,我认为需要从两方面考虑,一方面就是团队的人员配置,尽量选择大家熟悉的技术,第二个方面就是要考察选择的技术它的生态是不是够完善。这两个原则在选择编程语言、技术栈、云服务和存储的时候都是适用的。 如何根据业务来选择合适的存储系统,这是个很大的话题,我会在后面的课程中陆续穿插的来和你讲,什么场景下应该选择什么样的存储,敬请期待。展开共 6 条评论64
- Panmax2020-03-05课程中给的那个 SQL 语句很危险啊😂,where 条件丢掉了最重要的订单ID。应该改为: UPDATE orders set tracking_number = 666, version = version + 1 WHERE id = 12345 and version = 8;共 15 条评论107
- 川杰2020-02-26请问,生成订单号 服务的一般逻辑会是怎样的?思来想去,如果要想这个ID全局唯一,只能带上时间,可是如果带上时间,像那种,不小心点了两次按钮的情况,必然是两个不同的订单号;请问这个问题怎么解决?
作者回复: 如果单纯是生成GUID(全局唯一ID)方法有很多,比如小规模系统完全可以用MySQL的Sequence或者Redis来生成。大规模系统也可以采用类似雪花算法之类的方式分布式生成GUID。 但是订单号这个东西又有点儿特殊要求,比如在订单号中最好包含一些品类、时间等信息,便于业务处理,再比如,订单号它不能是一个单纯自增的ID,否则别人很容易根据订单号计算出你大致的销量,所以订单号的生产算法在保证不重复的前提下,一般都会加入很多业务规则在里面,这个每家都不一样,算是商业秘密吧。
共 19 条评论61 - 业余爱好者2020-02-26每次请求之前必须先生成一个唯一的请求id,服务端将该id暂时放入redis。客户端请求时必须携带上这个id,借口会首先到redis中查询,如何有的话就继续后续的处理逻辑,同时删除该id,灭有的话就退出,返回不能重复请求的错误到客户端。 一句话总结:每次处理必须对应一个一次性的token。
作者回复: 这个思路非常好,并且可以适用很多的场景。 如果是超大规模的系统,可能用一个Redis实例来生成和验证这个GUID就忙不过来了,大家也可以想一下有没有什么性能上更好的方式?
共 26 条评论52 - 家庆2020-03-06李老师好,请问生成一个唯一订单号放在前端,如何保证客户绕过客户端直接发送请求恶意乱填订单号的问题,符合规则的订单号,而这个订单号有可能是后续系统生成的。
作者回复: 有的同学已经在留言区给出了答案,那就是,在Redis里面缓存一下给出去的订单号,收到客户端请求的时候,先验证一下这个订单号在缓存中是否存在,就可以解决这个问题了。
共 5 条评论31 - 鸠摩智2020-02-28老师,生成全局唯一订单号如果不是自增的,插入mysql innodb表的时候,底层的B+树索引是不是会发生页分裂等问题,影响插入性能?如果遇到大促,短时间生成大量订单,写入会成为瓶颈不?
作者回复: 这确实是一个需要考虑的性能问题。 所以很多业务在设计订单号规则的时候都不是完全随机的,一般都是递增的。这种情况下,页分裂就不会特别严重。
共 13 条评论29 - Sunday2020-03-07老师,用户手机号注册会出现多次注册现象,但因为有注销功能,我又不能设置成唯一主键,所以想问下有没有什么好的解决办法?目前是用redis锁控制并发的。还有ABA问题,一般业务中这种更新都是牵扯到更新多条,使用事务,但事务的话因为隔离级别问题,比如可重复读,感觉并发情况还是有问题
作者回复: 手机号注册重复这种情况,可以简单的用insert if not exist来解决,比如: insert into user (name, phone) select * from (select '张三', 13988888888) as tmp where not exists ( select phone from user where phone = 13988888888 ) limit 1;
共 13 条评论20 - 约书亚2020-02-27请教您一个实际问题: 无论采用哪种生成订单id的方式,短时间内都可能出现靠前的id后提交(生成订单的问题),这样一定程度上数据库接收到的id不是严格按时间递增的。而页面浏览的场景,尤甚。 请问在您做过的系统里,这会带来一定程度上的性能下降嘛?
作者回复: 你是指在数据库中插入数据的时候,写入索引的性能下降吗?
共 23 条评论17 - fgdgtz2020-03-21老师你好,我觉的订单号设为唯一索引约束即可,仍然有个自增主键,订单号对外用,主键不暴露,这样表空间还是连续紧密的,其他关联订单表仍然以订单号作外键
作者回复: 这样也是没问题的,很多情况下架构不是只有一种解决方案的。这种设计的好处就是主键连续,使用订单号做主键的好处是,最常用的按订单号查询会少走一次索引,各有优劣,都可以选择。
共 8 条评论16 - 王超2020-03-24老师,如果是一个预约中台的项目,只对外提供API能力,前端的预约入口应用不由自己负责,无法要求别人的前端应用在进入下单界面的时候就调用统一生成单号的服务,这种情况下有什么推荐方案么,感谢
作者回复: 这种下单接口,订单内容中一般都会有用户ID,可以通过“1秒钟内每个用户只允许最多下1单”,这种方式来判重。因为一般来说,由于重试导致的重复请求之间的时间间隔都是比较短的。 INSERT INTO my_orders (...) SELECT * FROM (SELECT 'aaa', 'bbb', 'ccc') AS tmp -- 实际要insert的数据 WHERE NOT EXISTS ( SELECT userid FROM my_orders WHERE userid = 'bbb' and last_update_time > now - 1秒 ) LIMIT 1;
15 - leo2020-03-22关于数据报表类数据,现在主流解决方案能否推荐一下。本来想推es,但运营人员觉得操作复杂。大厂的实践是什么呢
作者回复: 后面我会专门讲,海量数据应该怎么查才会快一些。 但是,有一个原则上,底层用什么存储,应该尽量对最终用户透明,不要因为换了存储系统,就改变用户的操作习惯。
共 2 条评论10 - 撒旦的堕落2020-03-03老师 我曾经看到文章说用disruptor大规模生成预订单 可以动态改变生成速率 并且可以防止瞬间流量 不过没有说细节 老师在这方面有思路么
作者回复: disruptor是一个高性能队列,我没有听过类似的用法,我猜测可能是利用disruptor这个队列,预先生成大量的订单号,高并发的情况下,可以瞬间从队列中取出大量预先生成好的订单号,避免订单号生成的速度跟不上瞬时的请求量。
共 3 条评论10 - Javatar2020-11-24老师一言不和就讲了两个基本的面试题: 1. 如何防止重复提交? 2. 谈谈乐观锁? db唯一约束搭配乐观锁,日常防止并发场景应该是足够用了。 还是题目中的场景,实现幂等应该也可以使用redis: 订单号还是事先生成,然后用每次创建之前,先在redis中setnx一把,key要包含订单号。 如果setnx成功,证明没有创建;如果setnx失败,说明已经创建了,此时应该返回幂等成功。 当然这会带来一个问题,就是redis中的数据会不断增多,可以考虑增加一个定时清理的任务,把已经“结束”的订单,对应的key清理掉展开共 1 条评论8
- L2020-03-01最喜欢看评论了,总能找到解决我疑问的答案。7
- Ling2021-05-251. 作者讲了什么? 针对**必须保证数据可靠性的写需求**(例如订单操作)的操作,如何保证数据库的可靠性(准确) 引入了**幂等**的概念,表明重复请求在实际生产环境中不可避免; 要避免重复请求,就需要使请求幂等,并通过列子说明,如何实现请求的幂等 2. 作者是怎么把事情说明白的? 通过举了电商系统订单的实际的几个例子: 1. 订单创建,如何保证保证不重复下单 2. 订单更新,如何保证准确和幂等 3. 为了讲明白,作者讲了哪些要点?哪些是亮点? **要点** 1. 实际生产环境,重复请求不可避免(客户端重试、服务间重试) 2. 增: 1. 生成唯一订单ID,订单ID全局唯一,并递增(为了MySQL建立索引时,如果不递增引起页分裂等问题,导致写入性能下降) 2. 将生成的ID放入缓存 3. order表的订单ID字段设置唯一索引约束 4. 客户端请求带上订单ID,如果重复插入会引起唯一索引重复,导致事务失败 5. 由于订单已经创建成功,对于由于唯一索引冲突导致的创建失败,需要给前端返回“创建成功”,防止对客户产生困扰 3. 改:更改请求接口,带上当前version;SQL条件加上`WHERE version=x `实现天然幂等 ```sql UPDATE order SET tarcing_num=666 AND version=version+1 WHERE id=100 AND version=6 ``` 4. 对于作者所讲,我有哪些发散性思考? 1. 对于任何“写”操作的接口,在设计的时候,都需要考虑**幂等**的概念。对于需要强保证数据可靠性的服务,需要设计**幂等** 2. 对于下订单、发表文章、发表评论这些写入服务的接口,除了幂等,还有防止重复提交、快速提交的问题 - 一方面 :客户端端需要做锁,用户点击“发布”按钮后,在网络请求没有返回,或者超时之前,用户都不可以继续点击“发布按钮”,界面可以将按钮置灰或者转菊花 - 另一方面:服务端需要进行加锁 1. 请求接口时,获取一个锁 锁的粒度 :同一用户的同一操作逻辑 锁名称规则:业务名称+用户ID (例如:post_msg:100101 ) 2. 给锁设置过期时间2-3秒,防止业务逻辑执行错误,用户一直被锁住 3. 如果被锁了,返回“正在处理,请勿重复提交” 4. 没有被锁,执行正常逻辑,在逻辑结束后,删掉锁 5. 在未来哪些场景,我可以使用它? 1. 在做任何更新接口时,时刻考虑是否需要实现**幂等**展开7
- 京京beaver2020-03-03提供一种思路,电商网站一般都是从购物车进入结算的,意味着购物车已经有了待结算商品列表。在结算成单接口,做下单和清除购物车商品两个动作。对于幂等来讲,购物车系统对本次购物车数据有个购物车id,成单接口的入参是购物车id。购物车id本身是全局唯一的,所以订单系统只要检查购物车id是否被处理过就可以了。 而结算服务需要做两个动作:使用购物车id去生成订单id,清除购物车,这两个动作调用购物车服务和订单服务,构成了分布式事务。一般为了高并发,是不做事务的,采用后台修数来应对错误。展开共 8 条评论6
- L2020-03-01我有个疑问,在提交订单的时候,一种是先拿着订单号去查库,让业务代码校验是否存在,另一种是直接利用库表主键唯一约束抛异常,这两种处理方式哪种性能更好?共 4 条评论5
- aoe2020-02-27老师,您在”幂等创建订单的流程“的时序图中:1. 下单;1.1. 生成订单号 ……,按这个逻辑是不能防止重复提交。 因为:下单后会走一个完整的生成订单流程 留言中”业余爱好者“的思路是正确的
作者回复: “业余爱好者”同学相比课中的方案,多了一步在Redis中验证订单号,我理解,这种设计一般是出于安全的考虑,确保提交的订单号确实是我们生成的,而不是来自外部的攻击。 真正的幂等或者说是防重,还是要依靠数据库的唯一约束。 也欢迎你说一下你的理解。
共 6 条评论5 - 划过天空阿忠2020-03-19下单接口的参数做签名,放redis。且每个签名做setnx判断,并且设定时长,规定时间内有重复的就是重复下单3
- 陈迪2020-02-29第一个防重复创建订单的解决方案中有个疑点,唯一的订单号是在进入“创建订单页面”时创建,用户如果对这个页面不小心同时开了多个tab页,每次打开一个tab页都会生成个新订单号,那就可以提交多个“一模一样”的订单了,而且用户本意应该是一个订单。 老师怎么看待这个问题?
作者回复: 这种情况我们确实没提到。如果用户打开了多个tab页,只会产生多个订单号,这个时候还没有生成新订单呢。 只有他在每个tab也都点“下单”按钮,才会产生多个订单。这种情况不存在“误操作”的可能了。
共 7 条评论3