前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >dotNET Core实现分布式环境下的流水号唯一

dotNET Core实现分布式环境下的流水号唯一

作者头像
oec2003
发布于 2019-09-17 07:39:37
发布于 2019-09-17 07:39:37
1.2K00
代码可运行
举报
文章被收录于专栏:不止dotNET不止dotNET
运行总次数:0
代码可运行

业务背景

在管理系统中,很多功能模块都会涉及到各种类型的编号,例如:流程编号、订单号、合同编号等等。编号各有各自的规则,但通常有一个流水号来确定编号的唯一性,保证流水号的唯一,在不同的环境中实现方式有所不同。本文将介绍在单机和分布式环境中保证流水号唯一的方式。

实现思路

1、在数据库中创建 seqno 表,每个业务一条数据,存储业务 code 和流水号的最大值 2、获取某业务的流水号时,根据业务 code 查询 seqno 表,获取流水号返回,并将最大值加一 3、使用 Monitor.Enter 解决单机重复性问题 4、使用 Redis 分布式锁解决分布式部署的重复性问题

环境

  • dotNET Core:2.1
  • VS For Mac:2019
  • Docker:18.09.2
  • MySql:8.0.17,基于Docker构建
  • Redis:3.2,基于Docker构建
  • CSRedisCore:3.1.5

准备工作

1、执行下面命令构建 Redis 容器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker run -p 6379:6379  -d --name s2redis_test   --restart=always redis:3.2   redis-server --appendonly yes

2、执行下面命令构建 MySql 容器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker run -d -p 3306:3306 -e MYSQL_USER="oec2003" -e MYSQL_PASSWORD="123456" -e MYSQL_ROOT_PASSWORD="123456" --name s2mysql mysql/mysql-server --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci --default-authentication-plugin=mysql_native_password

3、在 MySql 中创建数据库seqno_test,执行下面 SQL 创建表和测试数据

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- ----------------------------
-- Table structure for seqno
-- ----------------------------
DROP TABLE IF EXISTS `seqno`;
CREATE TABLE `seqno` (
  `code` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `num` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

-- ----------------------------
-- Records of seqno
-- ----------------------------
BEGIN;
INSERT INTO `seqno` VALUES ('order', 1);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

4、在 VS2019 中创建两个控制台项目和一个类库项目,如下图:

单机测试

1、在 SeqNo 类中添加 GetSeqByNoLock 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static string GetSeqNoByNoLock()
{
    string connectionStr = "server = localhost; user id = oec2003; password = 123456; database = seqno_test";
    string getSeqNosql = "select num from seqno where code='order'";
    string updateSeqNoSql = "update seqno set num=num+1 where code='order'";

    var seqNo = MySQLHelper.ExecuteScalar(connectionStr, System.Data.CommandType.Text, getSeqNosql);
    MySQLHelper.ExecuteNonQuery(connectionStr, System.Data.CommandType.Text, updateSeqNoSql);

    return seqNo.ToString();
}

2、在 RedisLockConsoleApp1 控制台程序中用多线程来模拟测试

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Program
{
    static void Main(string[] args)
    {
        Task.Run(() =>
        {
            for (int i = 0; i < 50; i++)
            {
                Console.WriteLine($"Thread1:SeqNo:{SeqNo.GetSeqNoByNoLock()}");
            }
        });

        Task.Run(() =>
        {
            for (int i = 0; i < 50; i++)
            {
                Console.WriteLine($"Thread2:SeqNo:{SeqNo.GetSeqNoByNoLock()}");
            }
        });
        Console.ReadLine();
    }
}

3、测试结果如下,可以看出在多线程情况下会出现重复的编号

单机环境加锁测试

在 SeqNo 类中添加 GetSeqNoByLock 方法,通过 Monitor.Enter 来解决单机多线程流水号重复问题

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static string GetSeqNoByLock()
{
    string connectionStr = "server = localhost; user id = oec2003; password = 123456; database = seqno_test";
    string getSeqNosql = "select num from seqno where code='order'";
    string updateSeqNoSql = "update seqno set num=num+1 where code='order'";
    var seqNo = string.Empty;
    try
    {
        Monitor.Enter(_myLock);
        seqNo = MySQLHelper.ExecuteScalar(connectionStr, System.Data.CommandType.Text, getSeqNosql).ToString();

        MySQLHelper.ExecuteNonQuery(connectionStr, System.Data.CommandType.Text, updateSeqNoSql);

        Monitor.Exit(_myLock);
    }
    catch
    {
        Monitor.Exit(_myLock);
    }

    return seqNo.ToString();
}

运行结果如下,可以看出已经没有出现重复的流水号了

多机环境测试

Monitor 只能解决进程内的重复性问题,现在用两个控制台程序来模拟分布式下的多机器运行,在 RedisLockConsoleApp2 控制台程序添加如下代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void Main(string[] args)
{
    Task.Run(() =>
    {
        for (int i = 0; i < 50; i++)
        {
            Console.WriteLine($"Thread1:SeqNo:{SeqNo.GetSeqNoByLock()}");
        }
    });

    Task.Run(() =>
    {
        for (int i = 0; i < 50; i++)
        {
            Console.WriteLine($"Thread2:SeqNo:{SeqNo.GetSeqNoByLock()}");
        }
    });

    Console.ReadLine();
}

同时运行两个控制台程序,测试结果如下:

可以看出在每一个控制台程序内没有重复流水号,但两个控制台还是会间歇性地出现重复流水号。

要解决这个问题就必须使用分布式锁。

多机环境分布式锁测试

分布式锁又很多实现方式,本例中采用 Redis 来实现,Redis 客户端使用的是 CSRedisCore ,在 CSRedisCore 最新的版本 3.1.5 中实现了分布式锁,这让使用变得非常的方便。

1、在 RedisLockLib 项目中添加 CSRedisCore 包的引用

2、在 SeqNo 类中添加 GetSeqNoByRedisLock 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static string GetSeqNoByRedisLock()
{
    string connectionStr = "server = localhost; user id = oec2003; password = 123456; database = seqno_test";
    string getSeqNosql = "select num from seqno where code='order'";
    string updateSeqNoSql = "update seqno set num=num+1 where code='order'";

    var seqNo=string.Empty;
    using (_redisClient.Lock("test", 5000))
    {
        seqNo = MySQLHelper.ExecuteScalar(connectionStr, System.Data.CommandType.Text, getSeqNosql).ToString();

        MySQLHelper.ExecuteNonQuery(connectionStr, System.Data.CommandType.Text, updateSeqNoSql);
    }
    return seqNo;
}

3、测试结果如下:

总结

例子非常简单,提供一种解决问题的思路,如您有更好的方式欢迎讨论。本文的示例代码已上传 Github ,地址如下:

https://github.com/oec2003/StudySamples/tree/master/RedisLockDemo

祝大家假期快乐!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 不止dotNET 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
手撕redis分布式锁,隔壁张小帅都看懂了!
看到标题,有人可能会满心疑惑?张小帅是谁?咳咳,老猫在此先卖个关子,关于张小帅,老猫后续会向大家正式介绍的,标题算是彩蛋了。
程序员老猫
2021/01/12
4840
手撕redis分布式锁,隔壁张小帅都看懂了!
怎样生成全局唯一流水号?UUID、自增主键,你已经Out啦,快来学习定制化雪花算法。
流水号是每个系统永远都绕不开的一个话题,如订单系统中的订单号,物流系统的运单号、银行系统的业务单号等等,不难发现这些单号虽然叫法不一样,但都有着一些相同的共性,那就是全局唯一性。除此之外,一个设计良好的流水号生成规则还应该包含如下特性:
敲得码黛
2021/07/02
10.1K0
Curator分布式锁之生成流水号
程序新视界
2018/01/08
1.4K0
拒绝躺平,如何使用AOP的环绕通知实现分布式锁
如何在分布式环境下,像用synchronized关键字那样使用分布式锁。比如开发一个注解,叫@DistributionLock,作用于一个方法函数上,每次调方法前加锁,调完之后自动释放锁。
IT大咖说
2021/07/19
6980
大厂-分布式专栏 23 分布式系统下分布式锁的实现
锁是开发过程中十分常见的工具,你一定不陌生,悲观锁,乐观锁,排它锁,公平锁,非公平锁等等,很多概念,如果你对java里的锁还不了解,可以参考这一篇:不可不说的Java“锁”事,这一篇写的很全面了,但是对于初学者,知道这些锁的概念,由于缺乏实际工作经验,可能并不了解锁的实际使用场景,Java中可以通过Volatile、Synchronized、ReentrantLock 三个关键字来实现线程的安全,这部分知识在第一轮基础面试里一定会问(要熟练掌握哦)。
botkenni
2022/08/25
4220
【分布式详解】一致性算法、全局唯一ID、分布式锁、分布式事务、 分布式缓存、分布式任务、分布式会话
分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同,称之为副本一致性(consistency)。副本一致性是针对分布式系统而言的,不是针对某一个副本而言。
奥耶可乐冰
2024/01/11
1K0
【分布式详解】一致性算法、全局唯一ID、分布式锁、分布式事务、 分布式缓存、分布式任务、分布式会话
基于分布式系统的7种唯一ID实现方案,值得收藏
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。
lyb-geek
2019/11/20
1.6K0
基于分布式系统的7种唯一ID实现方案,值得收藏
分布式锁:数据库、Redis、ZK
分布式锁的实现方式有以下三种方式:「数据库分布式锁、Redis实现分布式锁、ZooKeeper实现分布式锁」。
用户6884826
2021/07/08
9100
分布式锁全网最详解!!
在单进程的系统中,当存在多个线程可以同时改变某个变量(可变共享变量)时,就需要对变量或代码块做同步,使其在修改这种变量时能够线性执行消除并发修改变量。
用户5927304
2019/07/31
5090
面试官问分布式技术面试题,一脸懵逼怎么办?
2、当 redis 服务器初始化时,会预先分配 16 个数据库(该数量可以通过配置文件配置),所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。
苏先生
2019/04/26
1.2K0
面试官问分布式技术面试题,一脸懵逼怎么办?
分布式锁(数据库、Redis、ZK)拍了拍你
标题使用最近异常火热的微信拍一拍的方式命名,最近拍一拍的玩法被各位网友玩坏了,出现了各种版本的拍一拍。
用户4172423
2020/09/22
3000
分布式锁(数据库、Redis、ZK)拍了拍你
关于分布式锁的面试题都在这里了
最简单的理由就是作为一个社招程序员,面试的时候一定被面啦,你看怎么多公众号都翻来覆去的发分布式锁的主题,可见它很重要啦,在高考里这就是送分题,不要怪可惜的。
王炸
2020/04/26
3.2K0
高性能高可用的分布式唯一ID服务——mooon-uniq-id
源码位置:https://github.com/eyjian/mooon/tree/master/application/uniq_id。
一见
2018/08/02
5600
Java面试——架构设计与分布式
LRU(Least Recently Used:最近最少使用):简单的说,就是保证基本的 Cache容量,如果超过容量则必须丢掉最不常用的缓存数据,再添加最新的缓存。每次读取缓存都会改变缓存的使用时间,将缓存的存在时间重新刷新。其实,就是清理缓冲的一种策略。 我们可以通过双向链表的数据结构实现 LRU Cache,链表头(head)保存最新获取和存储的数据值,链表尾(tail)既为最不常使用的值,当需要清理时,清理链表的 tail 即可,并将前一个元素设置为tail。结构图如下:
Java架构师必看
2021/04/25
7060
Java面试——架构设计与分布式
分布式系统唯一ID生成方案汇总
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。下面就介绍一些常见的ID生成策略。 1. 数据库自增长序列或字段 最常见的方式。利用数据库,全数据库唯一。 优点: 1)简单,代码方便,性能可以接受。 2)数字ID天然排序,对分页或者需要排序的结果很有帮助。 缺点: 1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。 2)在单个数据库或读写分离或一
庞小明
2018/03/08
1.5K0
分布式系统唯一ID生成方案汇总
最常用的分布式 ID 解决方案,都在这里了!
说起ID,特性就是唯一,在人的世界里,ID就是身份证,是每个人的唯一的身份标识。在复杂的分布式系统中,往往也需要对大量的数据和消息进行唯一标识。举个例子,数据库的ID字段在单体的情况下可以使用自增来作为ID,但是对数据分库分表后一定需要一个唯一的ID来标识一条数据,这个ID就是分布式ID。对于分布式ID而言,也需要具备分布式系统的特点:高并发,高可用,高性能等特点。
好好学java
2021/04/19
1.6K0
最常用的分布式 ID 解决方案,都在这里了!
干货 | 分布式架构系统生成全局唯一序列号的一个思路
作者简介 丁宜人,10年java开发经验。携程技术中心基础业务研发部用户中心资深java工程师,负责携程账号的基础服务和相关框架组件研发。之前在惠普公司供职6年,负责消息中间件产品研发。 一、相关背景 分布式架构下,唯一序列号生成是我们在设计一个系统,尤其是数据库使用分库分表的时候常常会遇见的问题。当分成若干个sharding表后,如何能够快速拿到一个唯一序列号,是经常遇到的问题。 在携程账号数据库迁移MySql过程中,我们对用户ID的生成方案进行了新的设计,要求能够支撑携程现有的新用户注册体量。 本文通过
携程技术
2018/03/16
2.1K0
干货 | 分布式架构系统生成全局唯一序列号的一个思路
Java分布式锁(6种实现方法)
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/144795.html原文链接:https://javaforall.cn
全栈程序员站长
2022/08/30
3.1K0
分布式唯一ID生成方案总结
在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。
Bug开发工程师
2019/11/18
2K0
分布式系统唯一ID生成方案汇总
系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的策略。下面就介绍一些常见的ID生成策略。
lyb-geek
2018/07/26
8700
分布式系统唯一ID生成方案汇总
推荐阅读
相关推荐
手撕redis分布式锁,隔壁张小帅都看懂了!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验