zoukankan      html  css  js  c++  java
  • 分布式环境利用数据库生成连续唯一序列

    生成连续唯一的序列号在很多业务场景都会需要,本文分享一个在分布式环境利用数据库生成连续唯一序列的例子(按天生成),在并发不是特别高的大型场景还是值得一干。文中采用版本(version)机制,结合自旋锁 + 乐观锁生成连续的唯一的数字。

    直接上代码

    1. 创建表test_no

    CREATE TABLE `test_no` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `date` varchar(10) NOT NULL COMMENT '格式yyyyMMdd',
      `number` bigint(20) unsigned DEFAULT NULL COMMENT '序列号',
      `version` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '版本',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uniq_date` (`date`)
    ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
    

    date字段建有唯一索引。

    2. MyBaties持久层代码TestNoMapper

    package com.mingo.exp.generate_number.single_db_cas;
    
    import com.mingo.exp.generate_number.single_db_cas.dto.TestNoDO;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.Update;
    
    /**
     * @author Doflamingo
     */
    public interface TestNoMapper {
    
        /**
         * 插入一条数据。主要用于初始值插入
         *
         * @param noDO
         */
        @Insert("INSERT INTO test_no(`date`,number,version) VALUES(#{date}, #{number}, #{version})")
        void insert(TestNoDO noDO);
    
        /**
         * 查询数据。date 具有唯一键
         *
         * @param date
         * @return
         */
        @Select("SELECT date,number,version FROM test_no WHERE `date` = #{date}")
        TestNoDO select(@Param("date") String date);
    
        /**
         * 只有与当前版本号一样才能更新
         *
         * @param date
         * @param version
         * @return
         */
        @Update("UPDATE test_no SET number = number + 1, version = version + 1 WHERE `date` = #{date} AND version = #{version}")
        int update(@Param("date") String date, @Param("version") Integer version);
    }
    

    3. 生成连续唯一序列代码DistributeSerialNumber

    package com.mingo.exp.generate_number.single_db_cas;
    
    import com.mingo.exp.generate_number.single_db_cas.dto.TestNoDO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * 分布式环境利用数据库生成连续唯一序列
     *
     * @author Doflamingo
     */
    @Component
    public class DistributeSerialNumber {
    
        @Autowired
        private TestNoMapper testNoMapper;
    
        /**
         * 查询唯一号码
         *
         * @param date 格式 yyyyMMdd
         * @return
         */
        public int get(String date) {
            TestNoDO testNoDO = this.selectOrInsert(date);
            boolean flag = this.update(testNoDO);
            if (flag) {
                return testNoDO.getNumber();
            }
            // 自旋锁 + 乐观锁
            while (!flag) {
                testNoDO = testNoMapper.select(date);
                // 更新number
                flag = this.update(testNoDO);
            }
            return testNoDO.getNumber();
        }
    
        /**
         * 这里主要用于当前首次查询和插入数据,保证直插入一条数据,date建有唯一索引;
         * 并发度不大也可以不用双重校验锁,直接插入,异常再查询即可
         *
         * @Param date yyyyMMdd
         */
        private TestNoDO selectOrInsert(String date) {
            TestNoDO testNoDO = testNoMapper.select(date);
            if (null == testNoDO) {
                try {
                    testNoDO = new TestNoDO(date, 1, 0);
                    synchronized (this) {
                        TestNoDO testNoDO2 = testNoMapper.select(date);
                        if (null != testNoDO2) {
                            return testNoDO2;
                        }
                        testNoMapper.insert(testNoDO);
                    }
                } catch (Exception e) {
                    // 插入失败,其他机器已经插入了
                    testNoDO = testNoMapper.select(date);
                }
            }
            return testNoDO;
        }
    
        /**
         * 按版本号更新数据
         *
         * @param testNoDO
         * @return true 即 成功
         */
        private boolean update(TestNoDO testNoDO) {
            return 1 == testNoMapper.update(testNoDO.getDate(), testNoDO.getVersion());
        }
    }
    

    4. 测试类DistributeSerialNumberTest

    为了测试效果,我用20线程生成序列号

    package com.mingo.exp.generate_number.single_db_cas;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class DistributeSerialNumberTest {
    
        @Autowired
        private DistributeSerialNumber serialNumber;
    
        @Test
        public void test() throws Exception {
            System.out.println("
    
    ==============================
    ");
    
            // 20个线程
            ExecutorService executorService =
                    new ThreadPoolExecutor(
                            20,
                            20,
                            0,
                            TimeUnit.MILLISECONDS,
                            new LinkedBlockingDeque<>()
                    );
    
            for (int i = 30; i > 0; i--) {
                // 启动
                executorService.execute(() -> {
                    int number = serialNumber.get("20200608");
                    System.out.println("获得的数:" + number);
                });
            }
    
            executorService.shutdown();
            executorService.awaitTermination(10, TimeUnit.SECONDS);
        }
    }
    

    5. 测试结果

    获得的数:2
    获得的数:1
    获得的数:3
    获得的数:4
    获得的数:6
    获得的数:5
    获得的数:13
    获得的数:8
    获得的数:10
    获得的数:14
    获得的数:9
    获得的数:11
    获得的数:7
    获得的数:12
    获得的数:15
    获得的数:18
    获得的数:17
    获得的数:16
    获得的数:20
    获得的数:26
    获得的数:24
    获得的数:21
    获得的数:25
    获得的数:27
    获得的数:28
    获得的数:23
    获得的数:19
    获得的数:22
    获得的数:30
    获得的数:29
    

    原创 Doflamingo https://www.cnblogs.com/doflamingo
  • 相关阅读:
    jquery弹窗居中-类似alert()
    php explode时间分割
    php+mysql+jquery日历签到
    php查找字符串中第一个非0的位置截取
    SQL Server Data Tool 嘹解(了解)一下 SSDT -摘自网络
    Headless MSBuild Support for SSDT (*.sqlproj) Projects [利用msbuild自动化部署 .sqlproj]- 摘自网络
    SSDT – Error SQL70001 This statement is not recognized in this context-摘自网络
    Web Services and C# Enums -摘自网络
    Excel中VBA 连接 数据库 方法- 摘自网络
    解决 Provider 'System.Data.SqlServerCe.3.5' not installed. -摘自网络
  • 原文地址:https://www.cnblogs.com/doflamingo/p/13543525.html
Copyright © 2011-2022 走看看