每天千万级订单的生成背后痛点及技术突破

发布时间 2023-05-29 08:29:13作者: 十一vs十一
千万级订单的生成
了解随订单量的提升,数据库系统经历了哪些变化,这些变化带来哪些痛点
分库分表环境下,订单的id生成有哪些办法
雪花算法的原理及实现
支付环境下,对订单系统的架构设计带来哪些影响
1. 架构体系深入剖析
1.1 演进与背景
随着数据量的增长,一般db的架构,经历如下演进:
1)单库主从
业务请求并发量大到一定量级后,单一主库无法承受,将读写剥离,从库诞生。
挑战:开发层框架支持,多数据源,数据读从延迟问题。
2)单库双主多从架构
实战较少,多为灾备而生,双主单写,灾备切换
3)分区表
数据库层面做数据分区策略,对开发层透明。
适用场景:
适合订单场景,最后部分有热点数据,其他都是历史订单(不活跃)
分区表的数据更容易维护,可以直接针对分区做删除、优化、检查、修复、备份等操作
支持多硬件设备,不同分区分散到不同设备,如硬盘
优化查询,只使用必要的分区来提高查询效率,涉及sum()和count()聚合查询时,也可以实现分区
并发再汇总。
局限性:
数量上限,一个表最多只能有1024个分区(mysql5.6之后支持8192个分区)
分区表达式类型受限,多为整数或日期。
如果表中有主键或唯一索引,那么分区键必须是主键或唯一索引
分区表中无法使用外键约束
4)横向分表
mysql单表性能超过千万级别会导致性能严重下降,横向,切成多张表。
挑战:分表策略,量级估算,分多少表?查询问题,扩容问题
5)多库
 
 
 
公司
功能
Atlas
360
读写分离、静态分表
Meituan
Atlas
美团
读写分离、单库分表,目前已经在原厂逐步下架。
Cobar
阿里
(B2B)
Proxy 的形式位于前台应用和实际数据库之间,开放MySQL 通信
协议,开源版中只支持 MySQL,不支持读写分离。
MyCAT
阿里
基于 Cobar ,是一个实现了 MySQL 协议的服务器,可以把它看
作是一个数据库代理
Heisenberg
百度
热重启配置、可水平扩容、遵守 MySQL 原生协议、无语言限制。
Kingshard
Kingshard
由 Go 开发高性能 MySQL Proxy 项目,在满足基本的读写分离的
功能上,Kingshard 的性能是直连 MySQL 性能的80%以上。
Vitess
谷歌、
Youtube
Rpc方式,集群基于ZooKeeper管理
DRDS
阿里
专注于解决单机关系型数据库扩展性问题。
超大量级的单库,备份,主从同步臃肿不堪,
即使拆了表,单服务器依然扛不住,io成为瓶颈。扩充物理节点,就必须分库
挑战:多数据源写,开发框架支持。数据分发难度进一步上升。
1.2 痛点
主流架构一般分库分表都会涉及,追求性能的同时,带来各种痛点
分库分表并不是一门创新技术,它只是由于数据体系结构的限制而做的无奈之举
机器配置无法无限上升,成本飙升,迫不得已衍生的方案
1.2.1 连接
1)jdbc直连
开发层面维护,最原始的,sql拼接
简单粗暴,sql代码写死,扩容会变得极其糟糕。
2)中间件:一般来讲,两种手段
String year = getYear();
String sql = "select * from order_"+year+" where xxxx";
DBproxy,对DB层面,针对机器做代理,一般需要 LVS/F5 等手段来实现流量的负载均衡,跨机房
可能需要DNS分发,常见组件:
 
 
 
公司
功能
TDDL
阿里淘
动态数据源、读写分离、分库分表,很久没更新了
Zebra
美团点
动态数据源、读写分离、分库分表、CAT监控,接入复杂、限制多。
MTDDL
美团点
动态数据源、读写分离、分布式主键生成、分库分表、连接池、SQL监
JDBC Proxy,从jdbc连接层面下手,需要对不同的语言编写 Driver
3)sharding-jdbc:
ORM框架,JPA, Hibernate, Mybatis, Spring JDBC,甚至直接使用JDBC。
连接池,DBCP, C3P0, BoneCP, Druid, HikariCP。
数据库,MySQL,Oracle,SQLServer和PostgreSQL。
轻量级Java框架,在Java的JDBC层提供的额外服务。 以jar包形式使用客户端直连数据库,无需额
外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
 
 
全网课程 超低价格1.2.2 数据
1)分库分表课题:
分表维度矛盾(用户,时间)
查询复杂度上升(买家,卖家)
数据聚合运算难度增加(数据统计)
2)亿级数据扩容课题:
扩容变得复杂(影响数据分片)
3)本课题:
多库多表怎么保证生成的订单号唯一
2. 分布式订单生成策略
springboot下,基于sharding-jdbc的框架简介。
分表下的订单表案例介绍,userid维度(分库雷同)
启动与调试,按userid验证数据落库,再查询
重点:分布式id的生成策略
2.1 自增
2.1.1 问题背景
1)业务代码
2)运行结果
@GetMapping("/incadd")
public Incorder add(int userid){
Incorder incorder = new Incorder();
incorder.setUserid(userid);
mapper.insert(incorder);
return incorder;
}
 
 
全网课程 超低价格3)分析
单表下自增功能不会造成数据错乱,数据库自身特性保障了主键的安全
会泄露id规律,数据隔离做不好的话,不法分子可能会循环撞库窃取订单数据
自增是表维度,一旦拆表,多个自增,有序性被打破
2.1.2 起始点分段
1)方案
设置表2的起始点,再来跑试试……
2)优缺点
简单容易,数据库层面设置,代码是不需要动的
边界的切分人为维护,操作复杂,触发器自动维护可以实现但在高并发下不推荐
2.1.3 分段步长自增
1)方案
#用以下sql,或者客户端工具设置:
ALTER TABLE incorder_1 AUTO_INCREMENT=10;
 
 
全网课程 超低价格3)问题
影响范围不可控,要么session每次设置,忘记会出乱子。要么全局设置,影响全库所有表
结论:不可取!!!
2.1.4 Sequence特性
仅限于oracle和sqlserver,主流mysql不支持
2.2 业务规则
2.2.1 方案思想
不用自增,自定义id,加上业务属性,从业务细分角度对并发性降维。例如淘宝,在订单号中加入用户
id。
加上用户id后,并发性维度降低到单个用户,每个用户的下单速度变的可控。
时间戳+userid,业务角度,一个正常用户不可能1毫秒内下两个单子,即便有说明是刻意刷单,应该被
前端限流。
--查看
show session variables like 'auto_inc%';
show global variables like 'auto_inc%';
--设定自增步长
set session auto_increment_increment=2;
--设置起始值
set session auto_increment_offset=1;
--全局的
set global auto_increment_increment=2;
set globa
l auto_increment_offset=1;
-- 创建一个sequence:
create sequence sequence_name as int minvalue 1 maxvalue 1000000000 start with 1
increment by 1 no cache;
-- sequence的使用:
sequence_name.nextval
sequence_name.currval
-- 在表中使用sequence:
insert into incorder_0 values(sequence_name.nextval,userid);
 
 
全网课程 超低价格2.2.2.实现
2.3 集中式分配
2.3.1 MaxId表
1)通过一张max表集中分配
2)创建函数
3)StrorderMapper调整id策略,借助mybatis的SelectKey生成id,注意Before=true
@GetMapping("/busiadd")
public Strorder busiadd(int userid){
Strorder order = new Strorder();
order.setId(System.currentTimeMillis()+"-"+userid);
order.setUserid(userid);
strorderMapper.save(order);
retur
n order;
}
CREATE TABLE `maxid` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`nextid` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
);
insert into maxid(name,nextid) values ('orders',1000);
DROP FUNCTION getid;
-- 创建函数
CREATE FUNCTION getid(table_name VARCHAR(50))
RETURNS BIGINT(20)
BEGIN
-- 定义变量
DECLARE id BIGINT(20);
-- 给定义的变量赋值
update maxid set nextid=nextid+1 where name = table_name;
SELECT nextid INTO id FROM maxid WHERE name = table_name;
-- 返回函数处理结果
RETURN id;
END
 
 
全网课程 超低价格4)启动验证分表的id情况,maxid表的记录情况。
5)优缺点
不需要借助任何中间件,数据库内部解决
表性能问题感人,下单业务如果事务过长,会造成锁等待
2.3.2 分布式缓存
通过redis的inc原子属性来实现
1)配置redis服务器
2)使用redis主键
3)优缺点
@Insert({
"insert into strorder (id,userid)",
"values (#{id},#{userid,jdbcType=INTEGER})"
})
@SelectKey(statement="SELECT getid('orders') from dual",
keyProperty="id", before=true, resultType=String.class)
int getIdSave(Strorder record);
/**
* maxid表验证
*/
@GetMappi
ng("/maxId")
public S
t
ro
r
d
e
r
m
a
x
I
d
(
i
n
t
us
e
r
id
)
{
Stro
rd
er
o
r
d
e
r
=
n
e
w
St
ro
r
d
e
r
();
order.setUserid(userid);
strorderMapper.getIdSave(order);
return order;
}
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
@GetMapping("/redisId")
public Strorder redisId(int userid){
Strorder order = new Strorder();
order.setId(template.opsForValue().increment("next_order_id").toString());
order.setUserid(userid);
strorderMapper.save(order);
return order;
}
 
 
全网课程 超低价格需要额外的中间件redis
与db相比不够直观,不方便查看当前增长的id值,需要额外连接redis服务器读取
性能不是问题,redis得到业界验证和认可
对redis集群的可靠性要求很高,禁止出现故障,否则全部入库被阻断
数据一致性需要注意,尽管redis有持久策略,down机恢复时需要确认和当前库中最大id的一致性
2.4 uuid
2.4.1 代码生成
1)业务代码
2)启动,数据库验证save结果
2.4.2 优缺点
最简单的方案,数据迁移方便
缺点也是非常明显的,太过冗长,非常的不友好,可读性极差
需要使用字符串存储,占用大量存储空间
在建立索引和基于索引进行查询时性能不如数字
2.5 雪花算法
2.5.1 概论
@GetMapping("/uuid")
public Strorder uuid(int userid){
Strorder order = new Strorder();
order.setId(UUID.randomUUID().toString());
order.setUserid(userid);
strorderMapper.save(order);
return order;
}
 
 
全网课程 超低价格1 bit:固定为0
二进制里第一个
bit
如果是 1,表示负数,但是我们生成的 id都是正数,所以第一个 bit 统一都是 0。
41 bit:时间戳,单位毫秒
41 bit 可以表示的数字多达
2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值。
注意!这个时间不是绝对时间戳,而是相对值,所以需要定义一个系统开始上线的起始时间
10 bit:哪台机器产生的
代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。
官方定义,前5 个 bit 代表机房 id,后5 个 bit 代表机器 id。
这10位是机器维度,可以根据公司的实际情况自由定制。
12 bit:自增序列
同1毫秒内,同一机器,可以产生2 ^ 12 - 1 = 4096个不同的 id。
优缺点:
不依赖第三方介质例如 Redis、数据库,本地程序生成分布式自增 ID
只能保证在工作组中的机器生成的 ID 唯一,不同组下可能会重复
时间回拨后,生成的 ID 就会重复,所以需要保持时间是网络同步的。
2.5.2 实现
1)自己用java代码实现
工具类:
UUID 能保证时空唯一,但是过长且是字符,雪花算法由Twitter发明,是一串数字。
Snowflake是一种约定,它把时间戳、工作组 ID、工作机器 ID、自增序列号组合在一起,生成一个
64bits 的整数 ID,能够使用 (2^41)/(1000*60*60*24*365) = 69.7 年,每台机器每毫秒理论最
多生成 2^12 个 ID
package com.itheima.sharding.config;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Snowflake {
/** 序列的掩码,12个1,也就是(0B111111111111=0xFFF=4095) */
private static final long SEQUENCE_MASK = 0xFFF;
/**系统起始时间,这里取2020-01-01 **/
private long startTimeStamp = 1577836800000L;
/** 上次生成 ID 的时间截 */
private long lastTimestamp = -1L;
/** 工作机器 ID(0~31) */
 
 
全网课程 超低价格private long workerId;
/** 数据中心 ID(0~31) */
private long datacenterId;
/** 毫秒内序列(0~4095) */
private long sequence = 0L;
/**
* @param
datacenterId 数据中心 ID (0~31)
* @pa
ram workerId 工作机器 ID (0~31)
*/
publ
i
c
S
n
ow
f
la
ke
(@
V
a
lu
e
(
"$
{
s
no
w
f
l
a
ke
.d
a
t
a
c
en
terId}") long datacenterId,
@Value("
$
{
s
no
w
f
la
ke
.w
o
r
ke
r
Id
}
")
l
o
ng
w
o
rk
e
r
Id
)
{
if (workerId > 31 || workerId < 0) {
throw new IllegalArgumentException("workId必须在0-31之间,当前
="+workerId);
}
if (datacenterId > 31 || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId必须在0-31之间,当前
="+datacenterId);
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 加锁,线程安全
* @return long 类型的 ID
*/
public synchronized long nextId() {
long timestamp = currentTime();
// 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException("时钟回退!时间差="+(lastTimestamp -
timestamp));
}
// 同一毫秒内,序列增加
if (lastTimestamp == timestamp) {
//超出阈值。思考下为什么这么运算?
sequence = (sequence + 1) & SEQUENCE_MASK;
// 毫秒内序列溢出
if (sequence == 0) {
//自旋等待下一毫秒
while ((timestamp= currentTime()) <= lastTimestamp);
}
} else {
//已经进入下一毫秒,从0开始计数
sequence = 0L;
}
//赋值为新的时间戳
lastTimestamp = timestamp;
 
 
全网课程 超低价格springboot启动参数,指定机器编号:
//移位拼接
long id = ((timestamp - startTimeStamp) << 22)
| (datacenterId << 17)
| (workerId << 12)
| sequence;
System.out.println("new id = "+id);
System.out.println("bit id = "+toBit(id));
retur
n id;
}
/**
* 返
回当前时间,以毫秒为单位
*/
protected long currentTime() {
return System.currentTimeMillis();
}
/**
* 转成二进制展示
*/
public static String toBit(long id){
String bit = StringUtils.leftPad(Long.toBinaryString(id), 64, "0");
return bit.substring(0,1) +
" - " +
bit.substring(1,42) +
" - " +
bit.substring(42,52)+
" - " +
bit.substring(52,64);
}
public static void main(String[] args) {
Snowflake idWorker = new Snowflake(1, 1);
for (int i = 0; i < 10; i++) {
long id = idWorker.nextId();
System.out.println(id);
System.out.println(toBit(id));
}
}
}
snowflake.datacenterId=1
snowflake.workerId=1
 
 
全网课程 超低价格业务部分:
代码启动生成,分析位数
更改机器id,分析位数
2)借助sharding配置
配置信息,非常简单
Mapper代码
业务代码
/**
* 自定义雪花算法
*/
@GetMapping("/myflake")
public Strorder myflake(int userid){
Strorder order = new Strorder();
order.set
Id
(S
t
ri
ng
.v
a
l
ue
Of(snowflake.nextId()));
order.set
U
se
r
id
(u
se
r
id
)
;
strorderMapper.save(order);
retur
n order;
}
spring.shardingsphere.sharding.tables.strorder.key-generator.column=id
spring.shardingsphere.sharding.tables.strorder.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.strorder.key-generator.props.worker.id=3
@Insert({
"insert into strorder (userid)",
"values (#{userid,jdbcType=INTEGER})"
})
@SelectKey(statement="SELECT max(id) from strorder where userid=#
{userid,jdbcType=INTEGER}", keyProperty="id", before=false,
resultType=String.class)
int shardingIdSave(Strorder record);
/**
* sharding的雪花算法
*/
@GetMapping("/shardingFlake")
public Strorder shardingFlake(int userid){
Strorder order = new Strorder();
order.setUserid(userid);
strorderMapper.shardingIdSave(order);
System.out.println(Snowflake.toBit(Long.valueOf(order.getId())));
return order;
}
 
 
全网课程 超低价格结果分析
生成的id号由sharding-jdbc自动添加到maper的sql中
机器编号为3,所以打印的bit中机器为 00011,修改为其他机器,测试结果
sharding源码分析:
package org.apache.shardingsphere.core.strategy.keygen;
import com
.google.common.base.Preconditions;
import j
a
v
a
.
u
t
i
l
.
C
a
le
nd
a
r
;
import j
a
v
a
.
u
t
i
l
.
P
ro
pe
rt
i
e
s;
import lombok.Generated;
import org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator;
public final class SnowflakeShardingKeyGenerator implements ShardingKeyGenerator
{
public static final long EPOCH;
private static final long SEQUENCE_BITS = 12L;
private static final long WORKER_ID_BITS = 10L;
private static final long SEQUENCE_MASK = 4095L;
private static final long WORKER_ID_LEFT_SHIFT_BITS = 12L;
private static final long TIMESTAMP_LEFT_SHIFT_BITS = 22L;
private static final long WORKER_ID_MAX_VALUE = 1024L;
private static final long WORKER_ID = 0L;
private static final int DEFAULT_VIBRATION_VALUE = 1;
private static final int MAX_TOLERATE_TIME_DIFFERENCE_MILLISECONDS = 10;
private static TimeService timeService = new TimeService();
private Properties properties = new Properties();
private int sequenceOffset = -1;
private long sequence;
private long lastMilliseconds;
public SnowflakeShardingKeyGenerator() {
}
public String getType() {
return "SNOWFLAKE";
}
public synchronized Comparable<?> generateKey() {
long currentMilliseconds = timeService.getCurrentMillis();
if (this.waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {
currentMilliseconds = timeService.getCurrentMillis();
}
if (this.lastMilliseconds == currentMilliseconds) {
if (0L == (this.sequence = this.sequence + 1L & 4095L)) {
currentMilliseconds =
this.waitUntilNextTime(currentMilliseconds);
}
} else {
this.vibrateSequenceOffset();
this.sequence = (long)this.sequenceOffset;
 
 
全网课程 超低价格3)时钟回退问题
关于snowflflake算法的缺陷(时钟回拨问题),sharding-jdbc没有给出解决方案
2.5.3 第三方实现
}
this.lastMilliseconds = currentMilliseconds;
return currentMilliseconds - EPOCH << 22 | this.getWorkerId() << 12 |
this.sequence;
}
//...
//获取机器
private l
on
g getWorkerId() {
lo
ng
re
su
lt
=
Long.valueOf(this.properties.getProperty("worker.id",
String.va
lu
e
Of
(0
L)
)
)
;
P
r
ec
o
n
di
t
i
o
n
s.
checkArgument(result >= 0L && result < 1024L);
re
t
u
r
n
r
e
s
ul
t
;
}
//...
//序列上限,等候下一毫秒
private long waitUntilNextTime(long lastTime) {
long result;
for(result = timeService.getCurrentMillis();
result <= lastTime;
result = timeService.getCurrentMillis()) {
;
}
return result;
}
static {
Calendar calendar = Calendar.getInstance();
calendar.set(2016, 10, 1);
calendar.set(11, 0);
calendar.set(12, 0);
calendar.set(13, 0);
calendar.set(14, 0);
EPOCH = calendar.getTimeInMillis();
}
}
 
 
全网课程 超低价格1)
百度UidGenerator
https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
位数不太一样,1-28-22-13
需要mysql数据库建表,来自动配置工作节点
支持spring配置与集成
支持bit位自定义,及bit分配相关建议
2)
美团Leaf-snowflflak
e
https://tech.meituan.com/2017/04/21/mt-leaf.html
位数沿用
sno
wflflak
e方案的bit位设计
使用Zook
eeper
持久顺序节点的特性自动对
snowflflake节点配置wokerID
解决了时钟回退问题
线上可靠性验证,美团的金融、支付交易、餐饮、外卖、酒店旅游、猫眼电影等众多业务
3. 支付场景下的订单系统
3.1 支付政策
1)支付牌照是干啥的?
2)217号文的下达
3)
“一清”与“二清”是什么:
4)为什么这么做?二清的风险在哪里
理论上,没有支付牌照,电商只能做自营。
凡是涉及B端用户在平台开展业务,就会涉及资金流动问题。别人的钱通过平台支付转手,就需要经过
批准。这个批准所获得的资格就是支付牌照。
主要给出了无证经营支付业务的主要认定标准:采取平台对接或“大商户”模式,即客户资金先划转至
网络平台账户,再由网络平台结算给该平台二级商户,均属于无证经营支付业务。
一清公司的支付不需要支付牌照,但是也不做资金结算,而是交给银联来结算。
二清公司则是没有支付牌照却做着资金结算工作。
简单来说就是平台接不接触到钱的问题。
 
 
全网课程 超低价格5)那么对订单系统的影响在哪里?
(订单系统整改案
例)
如果你所在公司涉及二清不合规问题。那么订单系统要注意。一般来说,银行接口要求以下操作:
商户入驻,确立平台方,商户方的虚拟子账户。买方也就是支付方不需要入驻
支付下单时条目带分成(一般两种方式,比例和金额)
确认收货后,平台调银行订单结算接口完成交易
3.2 条目折扣
1)活动折扣比例折算到条目
回顾满减活动问题。满99减9,那么设计订单条目表时,要带有实际折扣价,而比例放在订单上
订单表记录:
条目表记录:
平账:
30x0.9090 + 40x0.9090 +(50*0.909+1.92)= 111
3.3 退货换货
1)退货设计:
退货要生成退货单,关联旧订单id,条目也关联旧条目id,而原始订单不做任何改动。
调取银行时,调接口,对应的条目退货即可,资金会由银行原路返回。
注意赠品返还和实际退款金额问题。
2)换货设计:
换货也要生成退货单,关联新旧两条订单id,条目关联旧条目id,用来记录要拿哪些条目换。
买家的钱应该给卖家才对,现在给到平台再由平台转交。
那么平台无授权无牌照的情况下,跑了怎么办?
 
 
全网课程 超低价格同时生成新的订单,表示要换成的新商品。订单类型标注为换货单
换货时的价格折算问题:多退少补。如果多好办,抵扣后,剩余条目走银行退货接口
补的时候比较麻烦。这就涉及到下面的支付单。
3.4 分期支付
支付单的设计:
常规情况下,一笔订单一笔支付单,支付单上挂订单号,金额=订单应付金额
如果是上面的补单,支付金额就需要作为差价记录实际支付金额,条目标记为换货差价
涉及分期支付,对应多笔支付单,形成虚拟条目标注支付内容。
3.5 订单状态与接口关系
下单 → 新建,不需要调银行接口
支付 → 支付成功,调银行支付接口,只是支付成功,没有分成
确认收货 → 结算,调银行结算接口,银行会进行清算操作
3.6 超时订单取消
根据库存设计对订单有不同的处理策略:
下单减库存的,要注意超时取消,大订单量及分库分表条件下,扫表方案不可取,应该设计为延迟消费
支付减库存的,不需要额外处理。
 
 
全网课程 超低价格3.7 对账单与结算单
在“二清”政策下,对账流程变成两步,类比旧的支付,对应单子也会变成两种
支付成功的:进入支付对账
确认收货的:进入结算对账
4. 系统级可用性保障
4.1 数据一致性
1)订单周边服务:
下订单过程,业务极其复杂,不只是订单号的生成插入,除了订单系统,还可能涉及库存系统,促销系
统,支付系统,结算系统,积分系统,同时可能有下游的订单统计中心。
2)双向接口:
调用需要返回值的交互,比如调促销系统,获取促销信息。
多为强依赖的关联,使用分布式框架,基于框架层面的重试机制,接口幂等设计,保障数据的最终一致
性。
3)单向接口:
属于通知类调用,不需要返回值。如下单后通知给下游的订单统计中心,或大屏展示
可以采用扔消息队列,基于消息队列层面做高可用与调优。对订单系统来说,只需要保障投放时的消息
确认即可。下游消费端是消费方需要关注的事情。
4)重复数据问题:
一般的重复性数据,只要做到幂等设计,不会发生。
多见于支付环节。即一条订单对应了多条支付单。
策略:人工确认,接口退单,对账保底。
4.2 数据库高可用
1)日常备份
冷备:文件级备份,快速且完整。读写操作均不可进行,需要停机。作为灾难时恢复到某个时间点
热备:读写操作均可执行,作为备用库待命,down机时及时顶上去。
2)机房灾备
同一服务下,日志文件与数据文件分盘放
跨机房,双主单写,事务日志校验,灾备切换。
 
 
全网课程 超低价格4.3 应用级保护
1)限流,防止刷单
nginx,lua+redis,sentinel
2)异步排队,秒杀消息队列排队,异步消费
秒杀请求到来后进入
mq,后端下单服务异步消费,前台轮询查询排队状态。
3)周边服务降级
如积分,评价,某些统计,爬虫,推荐服务降级,延迟处理。
 
 
全网课程 超低价格