全局唯一ID
一般的订单会出什么问题呢?
第一点:数据量大
第二点:要具备唯一性(数据量过大时分表,可能出现重复id的问题)
因此要使用==全局唯一ID==在分布式系统下用来生成全局唯一ID的工具
需要满足:
- 唯一性
- 高可用
- 高性能
- 递增性(有利于索引和插入)Redis的incr
- 安全性(自增不能规律过于明显)
为了增加ID的安全性,我们可以不直接使用Redis自增的数值,而是拼接一些其它信息:
同一业务与日期命名空间中的应用实例,共用 Redis 计数器获取自增序列,再与约定的时间信息组成 ID。编码需要明确字段边界、日期切换和序列溢出的处理,不能随意拼接便认为绝不会重复。这是 Redis 自增组合方案,与通常包含机器标识的 Snowflake 应区别理解,也不代表 ID 已不可猜测。
全局唯一ID生成策略:.
- UUID
- Redis自增snowflake算法
- 数据库自增
- Redis自增ID策略:
- 每天一个key,方便统计订单量.
- ID构造是时间戳+计数器
下单时需要判断两点:
- 秒杀是否开始或结束,如果尚未开始或已经结束则无法下单
- 库存是否充足,不足则无法下单
活动时间不符合条件时直接拒绝下单;活动进行中也不能仅凭先前读到的库存继续扣减。应在数据库更新时同时约束库存大于零,并依据更新结果决定是否创建订单。库存扣减与订单写入需要事务边界,而同一用户同一优惠券只能购买一次,还需要额外的并发约束。
==高并发情况下会出现超卖==
超卖的风险来自检查与修改之间的并发窗口:多个请求可能先后读到同一个可售库存,再各自继续下单。若扣减没有同步约束,就可能卖出超过可用数量的订单,或者发生更新覆盖。把库存条件放进更新语句,并检查影响行数,才能让过时的读取结果不再直接决定是否售出。
悲观锁把冲突操作放进互斥区,让其他执行者等待;乐观方案则允许先读取,在提交更新时用版本号或条件比较拒绝过期修改。前者需要评估锁粒度与等待成本,后者需要处理冲突失败和重试,不能只用“更快”概括选择。进程内同步锁也不会自动约束另一台应用实例。
乐观锁的实现方式:
- 版本号法
- CAS
1.悲观锁:添加同步锁,让线程串行执行
- 优点:简单粗暴
- 缺点:性能一般
2.乐观锁:不加锁,在更新时判断是否有其它线程在修改
- 优点∶性能好
- 缺点∶存在成功率低的问题
修改秒杀业务,要求同一个优惠券,一个用户只能下一单
p54
集群下的并发安全问题
同一用户的请求分散到不同应用进程后,各进程自己的同步锁无法相互排斥。需要让相同业务资源竞争同一个共享锁键,只有获锁者进入受保护业务,未获锁者等待、重试或返回。锁键粒度应匹配优惠券与用户的业务约束,数据库唯一约束也可作为防止重复订单的最终保护。