高并发系统设计40问
唐扬
美图公司技术专家
立即订阅
9202 人已学习
课程目录
已更新 38 讲 / 共 40 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么你要学习高并发系统设计?
免费
基础篇 (6讲)
01 | 高并发系统:它的通用设计方法是什么?
02 | 架构分层:我们为什么一定要这么做?
免费
03 | 系统设计目标(一):如何提升系统性能?
04 | 系统设计目标(二):系统怎样做到高可用?
05 | 系统设计目标(三):如何让系统易于扩展?
06 | 面试现场第一期:当问到组件实现原理时,面试官是在刁难你吗?
演进篇 · 数据库篇 (5讲)
07 | 池化技术:如何减少频繁创建数据库连接的性能损耗?
08 | 数据库优化方案(一):查询请求增加时,如何做主从分离?
09 | 数据库优化方案(二):写入数据量增加时,如何实现分库分表?
10 | 发号器:如何保证分库分表后ID的全局唯一性?
11 | NoSQL:在高并发场景下,数据库和NoSQL如何做到互补?
演进篇 · 缓存篇 (6讲)
12 | 缓存:数据库成为瓶颈后,动态数据的查询要如何加速?
13 | 缓存的使用姿势(一):如何选择缓存的读写策略?
14 | 缓存的使用姿势(二):缓存如何做到高可用?
15 | 缓存的使用姿势(三):缓存穿透了怎么办?
16 | CDN:静态资源如何加速?
加餐 | 数据的迁移应该如何做?
演进篇 · 消息队列篇 (6讲)
17 | 消息队列:秒杀时如何处理每秒上万次的下单请求?
18 | 消息投递:如何保证消息仅仅被消费一次?
19 | 消息队列:如何降低消息队列系统中消息的延迟?
20 | 面试现场第二期:当问到项目经历时,面试官究竟想要了解什么?
用户故事 | 从“心”出发,我还有无数个可能
期中测试 | 10道高并发系统设计题目自测
演进篇 · 分布式服务篇 (9讲)
21 | 系统架构:每秒1万次请求的系统要做服务化拆分吗?
22 | 微服务架构:微服务化后,系统架构要如何改造?
23 | RPC框架:10万QPS下如何实现毫秒级的服务调用?
24 | 注册中心:分布式系统如何寻址?
25 | 分布式Trace:横跨几十个分布式组件的慢请求要如何排查?
26 | 负载均衡:怎样提升系统的横向扩展能力?
27 | API网关:系统的门面要如何做呢?
28 | 多机房部署:跨地域的分布式系统如何做?
29 | Service Mesh:如何屏蔽服务化系统的服务治理细节?
演进篇 · 维护篇 (5讲)
30 | 给系统加上眼睛:服务端监控要怎么做?
31 | 应用性能管理:用户的使用体验应该如何监控?
32 | 压力测试:怎样设计全链路压力测试平台?
33 | 配置管理:成千上万的配置项要如何管理?
34 | 降级熔断:如何屏蔽非核心系统故障的影响?
高并发系统设计40问
登录|注册

10 | 发号器:如何保证分库分表后ID的全局唯一性?

唐扬 2019-10-09
你好,我是唐扬。
在前面两节课程中,我带你了解了分布式存储两个核心问题:数据冗余和数据分片,以及在传统关系型数据库中是如何解决的。当我们面临高并发的查询数据请求时,可以使用主从读写分离的方式,部署多个从库分摊读压力;当存储的数据量达到瓶颈时,我们可以将数据分片存储在多个节点上,降低单个存储节点的存储压力,此时我们的架构变成了下面这个样子:
你可以看到,我们通过分库分表和主从读写分离的方式解决了数据库的扩展性问题,但是在 09 讲我也提到过,数据库在分库分表之后,我们在使用数据库时存在的许多限制,比方说查询的时候必须带着分区键;一些聚合类的查询(像是 count())性能较差,需要考虑使用计数器等其它的解决方案,其实分库分表还有一个问题我在09 讲中没有提到,就是主键的全局唯一性的问题。本节课,我将带你一起来了解,在分库分表后如何生成全局唯一的数据库主键。
不过,在探究这个问题之前,你需要对“使用什么字段作为主键”这个问题有所了解,这样才能为我们后续探究如何生成全局唯一的主键做好铺垫。

数据库的主键要如何选择?

数据库中的每一条记录都需要有一个唯一的标识,依据数据库的第二范式,数据库中每一个表中都需要有一个唯一的主键,其他数据元素和主键一一对应。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《高并发系统设计40问》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(34)

  • aoe
    个人觉得“微信序列号生成器”的方法更简单,因为:
    Snowflake
    1. Snowflake算法是基于二进制的,对于像我这样基础不扎实的理解起来还是比较困难。
    2. Snowflake集群环境下需要保证时钟同步,对运维能力有一定要求;一旦时钟错乱,又刚好是高并发时,会导致大量异常序号。
    3. 如果公司运维能力有限,不适合用Snowflake。
    4. 百度开源的UidGenerator(仅支持单机部署)使用Snowflake算法,单机QPS可达600万。项目说明:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 。
    5. 美团Leaf(分布式ID生成系统),QPS近5万。项目地址:https://tech.meituan.com/2017/04/21/mt-leaf.html 。

    微信序列号生成器
    文档地址:https://www.infoq.cn/article/wechat-serial-number-generator-architecture
    1. 递增但不连续的数字序列解决方案。
    2. 设计目标QPS1000万以上。
    3. 通过在递增过程中使用“步长”将每秒磁盘写入由1000万级降至1万。
    4. 设计原理相对于Snowflake更通俗易懂。
    5. 可以使用hash的负载均衡策略组建集群。
    6. 缺点:需要自己实现集群中机器增减后更新负载均衡策略的逻辑。
    7. Java版最简单Demo():使用spring boot搭建一个web工程,使用Controller调用Service实现数字递增
    Service类
    import org.springframework.stereotype.Service;

    import javax.annotation.PostConstruct;
    import java.util.concurrent.atomic.AtomicLong;

    @Service
    public class GeneratorService {

        private AtomicLong id;

        @PostConstruct
        private void init(){
            id = new AtomicLong(0);
        }

        public long getId(){
            return id.incrementAndGet();
        }
    }
    单机测试QPS 3万(测试工程、测试脚本在同一机器运行。)
    硬件信息:CPU 2.7 GHz Intel Core i7 | 内存 16 GB 2133 MHz LPDDR3
    测试工具:JMeter
    2019-10-10
    3
    45
  • 程序水果宝
    老师说如果我们发现系统时钟不准,就可以让发号器暂时拒绝发号,直到时钟准确为止。我们的程序本身就是运行在系统中的,如何来判断系统中的时间是否准确呢?

    作者回复: 可以暂时记录上次发好的时间,然后和这次的时间比较

    2019-10-09
    1
    5
  • 小喵喵
    但是当数据库分库分表后,使用自增字段就无法保证 ID 的全局唯一性了?
    1.使用数据库的自增,设置起始值和步长不一样,不是一样可以实现吗?
    2.预估每天的数据量,预先生成ID存入缓存(比如Redis)里面,然后去取,这种方法也简单?

    作者回复: 其实很难预估数据量,某一天有活动咋办?不同的起始值也可,只是增加人工成本,增加了库表咋办?忘了设置咋办?

    2019-10-09
    3
  • stg609
    假设通过容器化来部署发号器,且同时会有多个发号器容器运行,那这个 worker Id 如何生成。容器自身的 id 是一串很长的16进制,无法转换为 worker id 吧?难道也需要引入 zookeeper 吗?有没有其他简单可行的方案?

    作者回复: 容器ID太长了。。。 其实引入zk也还好,对于zk是弱依赖,只是启动的时候拉一下机器ID

    2019-10-09
    2
  • jimmy
    snowflake方案中 现在一般公司都有容器虚拟化,所以每个实例都有自己的实例ID,以此作为唯一ID即可,另外保险起见在服务启动的时候可以向其他启动的服务发送check请求,确保ID全局唯一,这样可以不引入zk,让系统更简单些~

    作者回复: 容器ID太长了吧,比较占发号器的位数

    2019-10-09
    2
  • gogo
    标准的snowflake算法最多支持69年,如果项目真的支撑到69年之后,应该怎么处理呢

    作者回复: 在没到69年的时候增加时间的位数……

    2019-10-10
    2
    1
  • ET go home
    请问下同一时间位,同一机器,在生成序列号时,是要上锁的吧?

    作者回复: 是的 不过像redis那样单线程处理就好了

    2019-10-09
    1
  • 大鸡腿🍗
    感觉不引入zk也可以实现唯一,不同机器工作机器id不同,即使同一台机器同个时间后面12位序列号也不同呢
    2019-12-01
  • 疯狂咸鱼
    老师,序列号设置为10位每毫秒生成1024个序号,拿评论来说,也就是每秒支持1024*1000的并发评论么,另外,雪花算法生成的主键也会作为分库表的一列保存在数据库里么

    作者回复: 是的

    2019-11-22
  • yuan
    为什么snowflake的第一位一定是0?

    作者回复: 标准实现是的

    2019-11-13
    1
  • XD
    如果单纯为了保证分表之后自增主键唯一,在创建数据表的时候,配合auto_increment_offset和auto_increment_increment不就可以实现吗?(当然我不是说在微服务中不需要取号器)

    作者回复: 这样要对每一个库的offset都要维护,你要是分了1000张表,就要维护1000个offset

    2019-11-03
  • 吕宗霖
    老师好呀,发号器生成id当主键的话,由于位数较多,对数据库索引性能影响大么

    作者回复: 不会的

    2019-10-30
  • 长期规划
    老师,我理解发号器是单节点时,发出的号的单调递增的,但由于网络延时等,客户端收到的顺序并不一定严格单调递增,导致创建DB记录时,并不能保证按ID单调递增的顺序创建。不过,从秒级或更粗的粒度看,DB记录是按ID单调递增创建的
    2019-10-23
  • 长期规划
    老师,如果发号器部在一台机器上,使用多线程,那对于占12位的序号部分,在生成时,要用线程锁吧?

    作者回复: 可以单线程,比如类似redis的实现

    2019-10-22
  • 长期规划
    老师,序列号占12位,对应序列号最大值4096,如果一毫秒内请求生成唯一键的次数大于此值怎么办呢?我能想到的办法是当生成的序列号达到4096时,延时1毫秒,再生成。实际中,是这样处理吗?

    作者回复: 会发这么多号吗……

    2019-10-22
  • 长期规划
    学习了,之前面试时被问到如何设计ID生成器,没答好。
    2019-10-22
  • helloworld
    老师,有相关的示例代码不?我的理解是每一个毫秒将下41时间戳加1,10位的机器不变,12的序列号先随机生成一个数字,然后再在这个基础上生成这一毫秒所需要的全局id的数量。不知道我理解的对不对。打卡09.

    作者回复: 是的,没错

    2019-10-21
    1
  • Ricky Fung
    大部分公司使用的都是基于推特的snowflake算法吧,有见过基于Redis实现发号器的,对运维水平要求可能高一点。
    2019-10-17
  • 👽
    学习的时候用 int做主键,
    后来工作的初步也用int,
    后来公司为了考虑分布式,换了uuid作主键,
    再后来,换公司,这里用Long做主键。

    额外说两个问题:
    1,如果数据量小的情况下,就算分布式UUID其实也足够。不能用于排序,增查效率较差,这些再数据量没那么大的情况下,都不构成问题。但是带来的优点:易于生成(甚至直接引入个类就可以用),也无需独立部署,隐藏业务(其实生成器已经可以隐藏部分业务了,但是单调递增的特性,还是可以暴力尝试其他的id,但是uuid按理说更加不容易被破解)
    2,小公司,小系统,小业务。我觉得其实id int 已经足够。毕竟最大21亿的数据量,通常来讲也够了。而且简单,高性能。。。现在很多用long的公司,都是为了将来的扩展考虑的么?
    2019-10-17
    1
  • stg609
    老师,有个疑问,如果部署了这么一套发号器,在不考虑非功能性需求前提下,是不是意味着所有原先使用数据库自增或者guid来作为唯一id的场景都可以用发号器来代替?
    有没有什么场景不应该这么用的?

    作者回复: 是可以用发号器来代替了

    2019-10-16
收起评论
34
返回
顶部