乐趣区

关于java:每天千万级订单的生成数据库系统如何设计订单系统架构如何设计

  • 理解随订单量的晋升,数据库系统经验了哪些变动,这些变动带来哪些痛点
  • 分库分表环境下,订单的 id 生成有哪些方法
  • 雪花算法的原理及实现
  • 领取环境下,对订单零碎的架构设计带来哪些影响

1. 架构体系深刻分析

1.1 演进与背景

随着数据量的增长,个别 db 的架构,经验如下演进:

1)单库主从

  • 业务申请并发量大到一定量级后,繁多主库无奈接受,将读写剥离,从库诞生。
  • 挑战:开发层框架反对,多数据源,数据读从提早问题。

2)单库双主多从架构

  • 实战较少,多为灾备而生,双主单写,灾备切换

3)分区表

数据库层面做数据分区策略,对开发层通明。

实用场景:

  • 适宜订单场景,最初局部有热点数据,其余都是历史订单(不沉闷)
  • 分区表的数据更容易保护,能够间接针对分区做删除、优化、查看、修复、备份等操作
  • 反对多硬件设施,不同分区扩散到不同设施,如硬盘
  • 优化查问,只应用必要的分区来进步查问效率,波及 sum()和 count()聚合查问时,也能够实现分区并发再汇总。

局限性:

  • 数量下限,一个表最多只能有 1024 个分区(mysql5.6 之后反对 8192 个分区)
  • 分区表达式类型受限,多为整数或日期。
  • 如果表中有主键或惟一索引,那么分区键必须是主键或惟一索引
  • 分区表中无奈应用外键束缚

4)横向分表

  • mysql 单表性能超过千万级别会导致性能重大降落,横向,切成多张表。
  • 挑战:分表策略,量级估算,分多少表?查问问题,扩容问题

5)多库

  • 超大量级的单库,备份,主从同步臃肿不堪,
  • 即便拆了表,单服务器仍然扛不住,io 成为瓶颈。裁减物理节点,就必须分库
  • 挑战:多数据源写,开发框架反对。数据散发难度进一步回升。

1.2 痛点

  • 支流架构个别分库分表都会波及,谋求性能的同时,带来各种痛点
  • 分库分表并不是一门翻新技术,它只是因为数据体系结构的限度而做的无奈之举
  • 机器配置无奈有限回升,老本飙升,无可奈何衍生的计划

1.2.1 连贯

1)jdbc 直连

开发层面保护,最原始的,sql 拼接

简略粗犷,sql 代码写死,扩容会变得极其蹩脚。

String year = getYear();

String sql = "select * from order_"+year+"where xxxx";

2)中间件:一般来讲,两种伎俩

​ DBproxy,对 DB 层面,针对机器做代理,个别须要 LVS/F5 等伎俩来实现流量的负载平衡,跨机房可能须要 DNS 散发,常见组件:

组件 公司 性能
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 阿里 专一于解决单机关系型数据库扩展性问题。

JDBC Proxy,从 jdbc 连贯层面下手,须要对不同的语言编写 Driver

组件 公司 性能
TDDL 阿里淘宝 动静数据源、读写拆散、分库分表,很久没更新了
Zebra 美团点评 动静数据源、读写拆散、分库分表、CAT 监控,接入简单、限度多。
MTDDL 美团点评 动静数据源、读写拆散、分布式主键生成、分库分表、连接池、SQL 监控

3)sharding-jdbc:


​ 轻量级 Java 框架,在 Java 的 JDBC 层提供的额定服务。以 jar 包模式应用客户端直连数据库,无需额定部署和依赖,可了解为增强版的 JDBC 驱动,齐全兼容 JDBC 和各种 ORM 框架。

  • ORM 框架,JPA, Hibernate, Mybatis, Spring JDBC,甚至间接应用 JDBC。
  • 连接池,DBCP, C3P0, BoneCP, Druid, HikariCP。
  • 数据库,MySQL,Oracle,SQLServer 和 PostgreSQL。

1.2.2 数据

1)分库分表课题:

  • 分表维度矛盾(用户,工夫)
  • 查问复杂度回升(买家,卖家)
  • 数据聚合运算难度减少(数据统计)

2)亿级数据扩容课题:

  • 扩容变得复杂(影响数据分片)

3)本课题:

  • 多库多表怎么保障生成的订单号惟一

2. 分布式订单生成策略

  • springboot 下,基于 sharding-jdbc 的框架简介。
  • 分表下的订单表案例介绍,userid 维度(分库雷同)
  • 启动与调试,按 userid 验证数据落库,再查问
  • 重点:分布式 id 的生成策略

2.1 自增

2.1.1 问题背景

1)业务代码

    @GetMapping("/incadd")
    public Incorder add(int userid){Incorder incorder = new Incorder();
        incorder.setUserid(userid);
        mapper.insert(incorder);
        return incorder;
    }

2)运行后果

3)剖析

  • 单表下自增性能不会造成数据错乱,数据库本身个性保障了主键的平安
  • 会泄露 id 法则,数据隔离做不好的话,不法分子可能会循环撞库窃取订单数据
  • 自增是表维度,一旦拆表,多个自增,有序性被突破

2.1.2 起始点分段

1)计划

设置表 2 的起始点,再来跑试试……

# 用以下 sql,或者客户端工具设置:ALTER TABLE incorder_1 AUTO_INCREMENT=10;

2)优缺点

  • 简略容易,数据库层面设置,代码是不须要动的
  • 边界的切分人为保护,操作简单,触发器主动保护能够实现但在高并发下不举荐

2.1.3 分段步长自增

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 global auto_increment_offset=1;

3)问题

  • 影响范畴不可控,要么 session 每次设置,遗记会出乱子。要么全局设置,影响全库所有表
  • 论断:不可取!!!

2.1.4 Sequence 个性

仅限于 oracle 和 sqlserver,支流 mysql 不反对

-- 创立一个 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.1 计划思维

不必自增,自定义 id,加上业务属性,从业务细分角度对并发性降维。例如淘宝,在订单号中退出用户 id。

加上用户 id 后,并发性维度升高到单个用户,每个用户的下单速度变的可控。

工夫戳 +userid,业务角度,一个失常用户不可能 1 毫秒内下两个单子,即使有阐明是刻意刷单,应该被前端限流。

2.2.2. 实现

    @GetMapping("/busiadd")
    public Strorder busiadd(int userid){Strorder order = new Strorder();
        order.setId(System.currentTimeMillis()+"-"+userid);
        order.setUserid(userid);
        strorderMapper.save(order);
        return order;
    }

2.3 集中式调配

2.3.1 MaxId 表

1)通过一张 max 表集中调配

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

2)创立函数

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 

3)StrorderMapper 调整 id 策略,借助 mybatis 的 SelectKey 生成 id,留神 Before=true

    @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 表验证
 */
@GetMapping("/maxId")
public Strorder maxId(int userid){Strorder order = new Strorder();
    order.setUserid(userid);
    strorderMapper.getIdSave(order);
    return order;
}

4)启动验证分表的 id 状况,maxid 表的记录状况。

5)优缺点

  • 不须要借助任何中间件,数据库外部解决
  • 表性能问题感人,下单业务如果事务过长,会造成锁期待

2.3.2 分布式缓存

通过 redis 的 inc 原子属性来实现

1)配置 redis 服务器

# Redis 服务器地址
spring.redis.host=127.0.0.1
# Redis 服务器连贯端口
spring.redis.port=6379

2)应用 redis 主键

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

3)优缺点

  • 须要额定的中间件 redis
  • 与 db 相比不够直观,不不便查看以后增长的 id 值,须要额定连贯 redis 服务器读取
  • 性能不是问题,redis 失去业界验证和认可
  • 对 redis 集群的可靠性要求很高,禁止呈现故障,否则全副入库被阻断
  • 数据一致性须要留神,只管 redis 有长久策略,down 机复原时须要确认和以后库中最大 id 的一致性

2.4 uuid

2.4.1 代码生成

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

2)启动,数据库验证 save 后果

2.4.2 优缺点

  • 最简略的计划,数据迁徙不便
  • 毛病也是非常明显的,太过简短,十分的不敌对,可读性极差
  • 须要应用字符串存储,占用大量存储空间
  • 在建设索引和基于索引进行查问时性能不如数字

2.5 雪花算法

2.5.1 概论

​ UUID 能保障保障时空惟一,然而过长且是字符,雪花算法由 Twitter 创造,是一串数字。

​ Snowflake 是一种约定,它把工夫戳、工作组 ID、工作机器 ID、自增序列号组合在一起,生成一个 64bits 的整数 ID,可能应用 (2^41)/(1000606024365) = 69.7 年,每台机器每毫秒实践最多生成 2^12 个 ID

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 代码实现

工具类:

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)
     * @param workerId     工作机器 ID (0~31)
     */
    public Snowflake(@Value("${snowflake.datacenterId}") long datacenterId, @Value("${snowflake.workerId}") long workerId) {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;

        // 移位拼接
        long id = ((timestamp - startTimeStamp) << 22)
                | (datacenterId << 17)
                | (workerId << 12)
                | sequence;

        System.out.println("new id ="+id);
        System.out.println("bit id ="+toBit(id));

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

    }


}

springboot 启动参数,指定机器编号:

snowflake.datacenterId=1
snowflake.workerId=1

业务局部:

/**
 * 自定义雪花算法
 */
@GetMapping("/myflake")
public Strorder myflake(int userid){Strorder order = new Strorder();
    order.setId(String.valueOf(snowflake.nextId()));
    order.setUserid(userid);
    strorderMapper.save(order);
    return order;
}
  • 代码启动生成,剖析位数
  • 更改机器 id,剖析位数

2)借助 sharding 配置

配置信息,非常简单

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

Mapper 代码

@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 java.util.Calendar;
import java.util.Properties;
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;
        }

        this.lastMilliseconds = currentMilliseconds;
        return currentMilliseconds - EPOCH << 22 | this.getWorkerId() << 12 | this.sequence;}

    //...

    // 获取机器编号
    private long getWorkerId() {long result = Long.valueOf(this.properties.getProperty("worker.id", String.valueOf(0L)));
        Preconditions.checkArgument(result >= 0L && result < 1024L);
        return result;
    }



    //...
    

    // 序列下限,等待下一毫秒
    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();}
}

3)时钟回退问题

对于 snowflake 算法的缺点(时钟回拨问题),sharding-jdbc 没有给出解决方案

2.5.3 第三方实现

1)百度 UidGenerator

https://github.com/baidu/uid-…

  • 位数不太一样,1-28-22-13
  • 须要 mysql 数据库建表,来主动配置工作节点
  • 反对 spring 配置与集成
  • 反对 bit 位自定义,及 bit 调配相干倡议

2)美团 Leaf-snowflake

https://tech.meituan.com/2017…

  • 位数沿用 snowflake 计划的 bit 位设计
  • 应用 Zookeeper 长久程序节点的个性主动对 snowflake 节点配置 wokerID
  • 解决了时钟回退问题
  • 线上可靠性验证,美团的金融、领取交易、餐饮、外卖、酒店游览、猫眼电影等泛滥业务

3. 领取场景下的订单(拓展)

3.1 领取政策

1)领取牌照是干啥的?

​ 实践上,没有领取牌照,电商只能做自营。

​ 但凡波及 B 端用户在平台开展业务,就会波及资金流动问题。他人的钱通过平台领取转手,就须要通过批准。这个批准所取得的资格就是领取牌照。

2)217 号文的下达

​ 次要给出了无证经营领取业务的次要认定规范:采取平台对接或“大商户”模式,即客户资金先划转至网络平台账户,再由网络平台结算给该平台二级商户,均属于无证经营领取业务。

3)“一清”与“二清”是什么:

​ 一清公司的领取不须要领取牌照,然而也不做资金结算,而是交给银联来结算。

​ 二清公司则是没有领取牌照却做着资金结算工作。

​ 简略来说就是平台接不接触到钱的问题。

4)为什么这么做?二清的危险在哪里

​ 买家的钱应该给卖家才对,当初给到平台再由平台转交。

​ 那么平台无受权无牌照的状况下,跑了怎么办?

5)那么对订单零碎的影响在哪里?

(订单零碎整改案例)

如果你所在公司波及二清不合规问题。那么订单零碎要留神。一般来说,银行接口要求以下操作:

  • 商户入驻,确立平台方,商户方的虚构子账户。买方也就是支付方不须要入驻
  • 领取下单时条目带分成(个别两种形式,比例和金额)
  • 确认收货后,平台调银行订单结算接口实现交易

3.2 条目折扣

1)流动折扣比例折算到条目

回顾满减流动问题。满 99 减 9,那么设计订单条目表时,要带有理论折扣价,而比例放在订单上

订单表记录:


条目表记录:


平账:

30×0.9090 + 40×0.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)周边服务降级

如积分,评估,某些统计,爬虫,举荐服务降级,提早解决。

本文由传智教育博学谷 – 狂野架构师教研团队公布
如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源
转载请注明出处!

退出移动版