<small id='tyGrH'></small> <noframes id='CMOPHlD'>

  • <tfoot id='CNWDsUeoT'></tfoot>

      <legend id='QA6nOGj'><style id='9uFHIb'><dir id='u1glFpaHcP'><q id='Hoq46'></q></dir></style></legend>
      <i id='w0HiGglN8'><tr id='PzUNRyAa'><dt id='uQqkfndWwe'><q id='IbKp1Xk'><span id='ryfxzTo5H'><b id='ez9ky'><form id='zPhUV2N'><ins id='9QfaHlWKm'></ins><ul id='K6Tuvw1xU'></ul><sub id='UWGzMk'></sub></form><legend id='flkhKjVzC'></legend><bdo id='zvAaTM'><pre id='TxYcLjWo'><center id='38dlf'></center></pre></bdo></b><th id='wgkDTZ5'></th></span></q></dt></tr></i><div id='iSmByv'><tfoot id='PCRA'></tfoot><dl id='PEeG5Dx'><fieldset id='X6VeUE0CT'></fieldset></dl></div>

          <bdo id='fml6'></bdo><ul id='BeOHDpcYZ0'></ul>

          1. <li id='vCKEL6ed'></li>
            登陆

            支撑百万并发的数据库架构怎么规划?

            admin 2019-05-14 275人围观 ,发现0个评论



            前语

            作为一个全球人数最多的国家,一个再怎样惨痛的职业,都能找出许多的人为之支付。而在这个互联网的年代,IT公司绝比照牛毛还多许多。可是大多数都是创业公司,长时刻存活的真的不多。大多数的IT项目在注册量从0-100万,日活泼1-5万,说实话就这种体系随意找一个有几年作业经验的高级工程师,然后带几个年青工程师,随意干干都能够做出来。

            由于这样的体系,实践上首要便是在前期快速的进职事务功用的开发,搞一个单块体系布置在一台服务器上,然后衔接一个数据库就能够了。接着咱们便是不断的在一个工程里填充进去各种事务代码,尽快把公司的事务支撑起来。

            可是假如真的开展的还能够,或许就会遇到如下问题:

            在运转的进程中体系拜访数据库的功用越来越差,单表数据量越来越大,一些杂乱查询 SQL直接拖垮!

            这种时分就不得不考虑的解决计划:缓存,负载均衡,项目分块(微服务);数据库:读写别离,分库分表等技能

            假如说此刻你仍是一台数据库服务器在支撑每秒上万的恳求,负责任的通知你,每次顶峰期会呈现下述问题:

            • 数据库服务器的磁盘 IO、网络带宽、CPU 负载、内存耗费,都会到达十分高的状况,数据库地点服务器的全体负载会十分重,甚至都快不堪重负了。
            • 顶峰期时,原本你单表数据量就很大,SQL 功用就不太好,这时加上你的数据库服务器负载太高导致功用下降,就会发现你的 SQL 功用更差了。
            • 最显着的一个感觉,便是你的体系在顶峰期各个功用都运转的很慢,用户体会很差,点一个按钮或许要几十秒才出来成果。
            • 假如你命运不太好,数据库服务器的装备不是特其他高的话,弄欠好你还会阅历数据库宕机的状况,由于负载太高对数据库压力太大了。

            其实大多数公司的瓶颈都在数据库,其实假如把上面的解决计划,都完成了,基本上就没的什么问题了,举例:

            假如订单一年有 1 亿条数据,能够把订单表总共拆分为 1024 张表,涣散在5个库中,这样 1 亿数据量的话,涣散到每个表里也就才 10 万量级的数据量,然后这上千张表涣散在 5 台数据库里就能够了。

            在写入数据的时分,需要做两次路由,先对订单 id 支撑百万并发的数据库架构怎么规划?hash 后对数据库的数量取模,能够路由到一台数据库上,然后再对那台数据库上的表数量取模,就能够路由到数据库上的一个表里了。

            经过这个进程,就能够让每个表里的数据量十分小,每年 1 亿数据增加,可是到每个表里才 10 万条数据增加,这个体系运转 10 年,每个表里或许才百万级的数据量。

            大局仅有ID

            在分库分表之后你必定要面临的一个问题,便是 id 咋生成?由于要是一个表分红多个表之后,每个表的 id 都是从 1 开端累加自增加,那必定不对啊。

            举个比方,你的订单表拆分为了 1024 张订单表,每个表的 id 都从 1 开端累加,这个必定有问题了!

            你的体系就没办法依据表主键来查询订单了,比方 id = 50 这个订单,在每个表里都有!

            所以此刻就需要分布式架构下的大局仅有 id 生成的计划了,在分库分表之后,关于刺进数据库中的中心 id,不能直接简略运用表自增 id,要大局生成仅有 id,然后刺进各个表中,确保每个表内的某个 id,大局仅有。

            比方说订单表尽管拆分为了 1024 张表,可是 id = 50 这个订单,只会存在于一个表里。

            那么怎么完成大局仅有 id 呢?有以下几种计划:

            计划一:独立数据库自增 id

            这个计划便是说你的体系每非必须生成一个 id,都是往一个独立库的一个独立表里刺进一条没什么事务意义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。

            比方说你有一个 au支撑百万并发的数据库架构怎么规划?to_id 库,里边就一个表,叫做 auto_id 表,有一个 id 是自增加的。

            那么你每非必须获取一个大局仅有 id,直接往这个表里刺进一条记载,获取一个大局仅有 id 即可,然后这个大局仅有 id 就能够刺进订单的分库分表中。

            这个计划的优点便是便利简略,谁都会用。缺陷便是单库生成自增 id,要是高并发的话,就会有瓶颈的,由于 auto_id 库要是承载个每秒几万并发,必定是不现实的了。

            计划二:UUID

            这个每个人都应该知道吧,便是用 UUID 生成一个大局仅有的 id。

            优点便是每个体系本地生成,不要依据数据库来了。欠好之处便是,UUID 太长了,作为主键功用太差了,不适合用于主键。

            假如你是要随机生成个什么文件名了,编号之类的,你能够用 UUID,可是作为主键是不能用 UUID 的。

            计划三:获取体系当时时刻

            这个计划的意思便是获取当时时刻作为大局仅有的 id。可是问题是,并发很高的时分,比方一秒并发几千,会有重复的状况,这个必定是不合适的。

            一般假如用这个计划,是将当时时刻跟许多其他的事务字段拼接起来,作为一个 id,假如事务上你觉得能够承受,那么也是能够的。

            你能够将其他事务字段值跟当时时刻拼接起来,组成一个大局仅有的编号,比方说订单编号:时刻戳 + 用户 id + 事务意义编码。

            计划四:SnowFlake 算法的思维剖析

            SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其间心思维便是:运用一个 64 bit 的 long 型的数字作为大局仅有 id。这 64 个 bit 中,其间 1 个 bit 是不必的,然后用其间的 41 bit 作为毫秒数,用 10 bit 作为作业机器 id,12 bit 作为序列号。

            给咱们举个比方吧,比方下面那个 64 bit 的 long 型数字:

            • 第一个部分,是 1 个 bit:0,这个是无意义的。
            • 第二个部分是 41 个 bit:表明的是时刻戳。
            • 第三个部分是 5 个 bit:表明的是机房 id,10001。
            • 第四个部分是 5 个 bit:表明的是机器 id,1 1001。
            • 第五个部分是 12 个 bit:表明的序号,便是某个机房某台机器上这一毫秒内一起生成的 id 的序号,0000 00000000。

            ①1 bit:是不必的,为啥呢?

            由于二进制里第一个 bit 为假如是 1,那么都是负数,可是咱们生成的 id 都是正数,所以第一个 bit 一致都是 0。

            ②41 bit:表明的是时刻戳,单位是毫秒。

            41 bit 能够表明的数字多达 2^41 - 1,也便是能够标识 2 ^ 41 - 1 个毫秒值,换算成年便是表明 69 年的时刻。

            ③10 bit:记载作业机器 id,代表的是这个服务最多能够布置在 2^10 台机器上,也便是 1024 台机器。

            可是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思便是最多代表 2 ^ 5 个机房(32 个机房),每个机房里能够代表 2 ^ 5 个机器(32 台机器)。

            ④12 bit:这个是用来记载同一个毫秒内发生的不同 id。

            12 bit 能够代表的最大正整数是 2 ^ 12 - 1 = 4096,也便是说能够用这个 12 bit 代表的数字来区别同一个毫秒内的 4096 个不同的 id。简略来说,你的某个服务假定要生成一个大局仅有 id,那么就能够发送一个恳求给布置了 SnowFlake 算法的体系,由这个 SnowFlake 算法体系来生成仅有 id。

            这个 SnowFlake 算法体系首要必定是知道自己地点的机房和机器的,比方机房 id = 17,机器 id = 12。

            接着 SnowFlake 算法体系接收到这个恳求之后,首要就会用二进制位运算的办法生成一个 64 bit 的 long 型 id,64 个 bit 中的第一个 bit 是无意义的。

            接着 41 个 bit,就能够用当时时刻戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置上机器 id。

            终究再判别一下,当时这台机房的这台机器上这一毫秒内,这是第几个恳求,给这次生成 id 的恳求累加一个序号,作为终究的 12 个 bit。

            终究一个 64 个 bit 的 id 就出来了,类似于:

            这个算法能够确保说,一个机房的一台机器上,在同一毫秒内,生成了一个仅有的 id。或许一个毫秒内会生成多个 id,可是有终究 12 个 bit 的序号来区别开来。

            下面咱们简略看看这个 SnowFlake 算法的一个代码完成,这便是个示例,咱们假如理解了这个意思之后,今后能够自己测验改造这个算法。

            总归便是用一个 64 bit 的数字中各个 bit 位来设置不同的标志位,区别每一个 id。

            SnowFlake 算法JAVA版(含测验办法):

            import java.util.ArrayList;
            import java.util.HashMap;
            import java.util.HashSet;
            import java.util.List;
            import java.util.Map;
            import java.util.Set;
            import java.util.concurrent.CountDownLatch;
            import org.slf4j.Logger;
            import org.slf4j.LoggerFactory;
            import lombok.ToString;
            /**
            * Copyright: Copyright (c) 2019
            *
            * @ClassName: IdWorker.java
            * @Description:

            SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。
            * 其间心思维便是:运用一个 64 bit 的 long 型的数字作为大局仅有 id。
            * 这 64 个 bit 中,其间 1 个 bit 是不必的,然后用其间的 41 bit 作为毫秒数,
            * 用 10 bit 作为作业机器 id,12 bit 作为序列号
            *


            * @version: v1.0.0
            * @author: BianPeng
            * @date: 2019年4月11日 下午3:13:41
            *
            * Modification History:
            * Date Author Version Description
            *---------------------------------------------------------------*
            * 2019年4月11日 BianPeng v1.0.0 initialize
            */
            @ToString
            public class SnowflakeIdFactory {

            static Logger log = LoggerFactory.getLogger(SnowflakeIdFactory.class);

            private final long twepoch = 1288834974657L;
            private final long workerIdBits = 5L;
            private final long datacenterIdBits = 5L;
            private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
            private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
            private final long sequenceBits = 12L;
            private final long workerIdShift = sequenceBits;
            private final long datacenterIdShift = sequenceBits + workerIdBits;
            private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
            private final long sequenceMask = -1L ^ (-1L << sequenceBits);

            private long workerId;
            private long datacenterId;
            private long sequence = 0L;
            private long lastTimestamp = -1L;



            public SnowflakeIdFactory(long workerId, long datacenterId) {
            if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater th忘却你的欢喜城an %d or less than 0", maxWorkerId));
            }
            if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
            }
            this.workerId = workerId;
            this.datacenterId = datacenterId;
            }

            public synchronized long nextId() {
            long timestamp = timeGen();
            if (timestamp < lastTimestamp) {
            //服务器时钟被调整了,ID生成器中止服务.
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            }
            if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
            timestamp = tilNextMillis(lastTimestamp);
            }
            } else {
            sequence = 0L;
            }

            lastTimestamp = timestamp;
            return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
            }

            protected long tilNextMillis(long lastTimestamp) {
            long timestamp = timeGen();
            while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
            }
            return timestamp;
            }

            protected long timeGen() {
            return System.currentTimeMillis();
            }

            public static void testProductIdByMoreThread(int dataCenterId, int workerId, int n) throws InterruptedException {
            List tlist = new ArrayList<>();
            Set setAll = new HashSet<>();
            CountDownLatch cdLatch = new CountDownLatch(10);
            long start = System.currentTimeMillis();
            int threadNo = dataCenterId;
            Map idFactories = new HashMap<>();
            for(int i=0;i<10;i++){
            //用线程名称做map key.
            idFactories.put("snowflake"+i,new SnowflakeIdFactory(workerId, threadNo++));
            }
            for(int i=0;i<10;i++){
            Thread temp =new Thread(new Runnable() {
            @Override
            public void run() {
            Set setId = new HashSet<>();
            SnowflakeIdFactory idWorker = idFactories.get(Thread.currentThread().getName());
            for(int j=0;j
            setId.add(idWorker.nextId());
            }
            synchronized (setAll){
            setAll.addAll(setId);
            log.info("{}出产了{}个id,并成功参加到setAll中.",Thread.currentThread().getName(),n);
            }
            cdLatch.countDown();
            }
            },"snowflake"+i);
            tlist.add(temp);
            }
            for(int j=0;j<10;j++){
            tlist.get(j).start();
            }
            cdLatch.await();

            long end1 = System.currentTimeMillis() - start;

            log.info("共耗时:{}毫秒,预期应该出产{}个id, 实践兼并总计生成ID个数:{}",end1,10*n,setAll.size());

            }

            public static void testProductId(int dataCenterId, int workerId, int n){
            SnowflakeIdFacto支撑百万并发的数据库架构怎么规划?ry idWorker = new SnowflakeIdFactory(workerId, dataCenterId);
            SnowflakeIdFactory idWorker2 = new SnowflakeIdFactory(workerId+1, dataCenterId);
            Set setOne = new HashSet<>();
            Set setTow = new HashSet<>();
            long start = System.currentTimeMillis();
            for (int i = 0; i < n; i++) {
            setOne.add(idWorker.nextId());//参加set
            }
            long end1 = System.currentTimeMillis() - start;
            log.info("第一批ID估计生成{}个,实践生成{}个<<<<*>>>>共耗时:{}",n,setOne.size(),end1);

            for (int i = 0; i < n; i++) {
            setTow.add(idWorker2.nextId());//参加set
            }
            long end2 = System.currentTimeMillis() - start;
            log.info("第二批ID估计生成{}个,实践生成{}个<<<<*>>>>共耗时:{}",n,setTow.size(),end2);

            setOne.addAll(setTow);
            log.info("兼并总计生成ID个数:{}",setOne.size());

            }

            public static void testPerSecondProductIdNums(){
            SnowflakeId支撑百万并发的数据库架构怎么规划?Factory idWorker = new SnowflakeIdFactory(1, 2);
            long start = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; System.currentTimeMillis()-start<1000; i++,count=i) {
            /** 测验办法一: 此用法朴实的出产ID,每秒出产ID个数为400w+ */
            //idWorker.nextId();
            /** 测验办法二: 在log中打印,一起获取ID,此用法出产ID的才能受限于log.error()的吞吐才能.
            * 每秒徜徉在10万左右. */
            log.info(""+idWorker.nextId());
            }
            long end = System.currentTimeMillis()-start;
            System.out.println(end);
            System.out.println(count);
            }

            public static void main(String[] args) {
            /** case1: 测验每秒出产id个数?
            * 定论: 每秒出产id个数400w+
            */
            //testPerSecondProductIdNums();

            /** case2: 单线程-测验多个出产者一起出产N个id,验证id是否有重复?
            * 定论: 验证经过,没有重复.
            */
            //testProductId(1,2,10000);//验证经过!
            //testProductId(1,2,20000);//验证经过!

            /** case3: 多线程-测验多个出产者一起出产N个id, 悉数id在大局范围内是否会重复?
            * 定论: 验证经过,没有重复.
            */
            try {
            testProductIdByMoreThread(1,2,100000);//单机测验此场景,功用丢失至少减半!
            } catch (InterruptedException e) {
            e.printStackTrace();
            }

            }
            }

            这个算法也叫雪花算法我运用的类源码:https://gitee.com/flying-cattle/earn_knife/blob/master/item-common/src/main/java/com/item/util/SnowflakeIdWorker.java

            项目是一个递进的进程,优先考虑缓存,其次读写别离,再分表分库。当然这仅仅个人主意,各位同伴仍是依据自己的项目和事务来归纳考虑实施计划。

            请关注微信公众号
            微信二维码
            不容错过
            Powered By Z-BlogPHP