作者: 杨高超

juejin.im/post/5a4984265188252b145b643e

获取全局唯一标识的方法介绍

在一个IT系统中,获取一个对象的唯一标识符是一个普遍的需求。在以前的单体应用中,如果数据库是一个单数据库的结构。通常可以利用数据库的自增字段来获取这个唯一标识。

例如,在 Mysql 数据库中,我们可以通过 sql 语句创建一个自增长的 int 字段类型的表。如下所示

CREATE TABLE student(    id INT NOT NULL AUTO_INCREMENT,    name VARCHAR(16),    PRIMARY KEY (id))

然后插入两条数据

INSERT INTO student(name) VALUE('yanggch');INSERT INTO student(name) VALUE('grace');

通过 SQL 语句查看表数据

 SELECT * FROM student;

得到如下的结果

可以看到,虽然我们在通过 SQL 插入数据的时候没有指定 id 字段的值,但是因为该字段的 AUTO_INCREMENT 自增长的特性,自动的给两条记录添加了1和2两个值。

这个方法有两个主要问题。一个是如果是一个分库分表的数据库结构,那么在分布在不同实例中的同一个表中的id是重复的。另一个问题是记录插入到数据库里后,我们在代码中并不能知道刚刚插入数据库的记录的主键的值到底是什么。如果我们的一个业务是要同时插入一条主表记录一节一系列以这条主表记录主键为外键的子表记录,我们在插入子表记录的时候,不知道对应的外键的值是多少。导致无法插入。例如如果我们有一个下单业务,要求在订单表中插入一条订单记录,同时在订单明细中插入多条在这个订单中购买的商品的详细信息的记录。订单数据插入成功后,我们不知道订单的主键的值,所以我们也就无法正确的插入商品详细信息记录了。

另外一个利用数据库自增字段属性获取唯一标识方式是在数据库中建立一个带一个自增字段的数据表。每次在表中插入一条记录,然后将这条记录的值取出来作为主键值。这个的问题是每次要另外在数据表中插入一条记录,同时在多用户使用的环境下,要严格保证你取到的记录就是你插入的记录。否则会导致主键重复。着会让获取唯一标识符的速度变得比较慢。同时,这个方式在分库分表的结构下,也不能让唯一标识在全局唯一。

还有一些其他的方式。例如用 uuid 算法可以保证全局唯一,也能保证高性能。但是他生成是一个字符串,不能保证顺序性,同时也太长了。

所以在分布式架构中,我们就需要一个满足如下条件的唯一标识符服务

  • 全局唯一

  • 高性能

  • 具备顺序性

  • 可以附加其他业务属性

这里我们可以用 redis 的 INCR 命令来作为生成全局的唯一标识符。INCR 命令的语法是

INCR key

根据 redis 的官网的 INCR 命令介绍,它是一个原子操作,效果是是将 redis 数据库中 key 的值加一并且返回这个结果。如果 key 不存在,将在执行加一操作前,将这个 key 的值设置为0,也就是说执行这个命令的结果是从 1 开始一直累加下去的。

同时我们可以看到这个命令的算法时间复杂度是 O(1),而 redis 的数据是存储在内存中的,这个命令的执行速度是非常快的。在 redis 服务器为双核 16g环境下,通过千兆局域网在另一台服务器上命令行执行压力测试

redis-benchmark -h 10.110.2.56 -p 52981 -a hhSbcpotThgWdnxJNhrzwstSP20DvYOldkjf

结果如下

可以看到每秒可以生成5万个标识。这个可以满足一般的高性能需求了

通过 Java 和 redis 实现一个全局唯一标识服务

接下来我们来用继续来在 Java 中利用 redis 来实现一个全局唯一标识的服务。这个服务要满足如下的需求

  • 全局唯一

  • 高性能

  • 具备顺序性

  • 可以将日期数字作为全局唯一标识的前缀

  • 可以每天从 1 开始重新计数

  • 不同的实体类型可以单独生成标识。例如订单标识,会员标识

  • 可以在新的一天中从 1 开始计数

定义唯一标识服务接口

package com.x9710.common.redis;

/** * 全局唯一标识服务接口 * * @author 杨高超 */public interface UUIDService {

/** * 每天从 1 开始生成唯一标识 * * @param key     要生成唯一标识的对象 * @param length  要生成为唯一标识后缀的长度。不包括需要附加的时间前缀 *                如果 haveDay = false 或者 length 长度小于标识后缀的长度则无效 * @param haveDay 是否要附加日期前缀 * @return 唯一标识 * @throws Exception 异常 */Long fetchDailyUUID(String key, Integer length, Boolean haveDay) throws Exception;

/** * 全局从 1 开始生成唯一标识 * * @param key     要生成唯一标识的对象 * @param length  要生成为唯一标识后缀的长度。不包括需要附加的时间前缀 *                如果 haveDay = false 或者 length 长度小于标识后缀的长度则无效 * @param haveDay 是否要附加日期前缀。 * @return 唯一标识 * @throws Exception 异常 */Long fetchUUID(String key, Integer length, Boolean haveDay) throws Exception;}

基于 redis 实现唯一标识服务

package com.x9710.common.redis.impl;

import com.x9710.common.redis.RedisConnection;import com.x9710.common.redis.UUIDService;import redis.clients.jedis.Jedis;

import java.text.DateFormat;import java.text.NumberFormat;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.GregorianCalendar;

/** * @author 杨高超 */public class UUIDServiceRedisImpl implements UUIDService {private RedisConnection redisConnection;private Integer dbIndex;

private DateFormat df = new SimpleDateFormat("yyyyMMdd");

public void setRedisConnection(RedisConnection redisConnection) {    this.redisConnection = redisConnection;}

public void setDbIndex(Integer dbIndex) {    this.dbIndex = dbIndex;}

public Long fetchDailyUUID(String key, Integer length, Boolean haveDay) {    Jedis jedis = null;    try {        jedis = redisConnection.getJedis();        jedis.select(dbIndex);        Calendar now = new GregorianCalendar();        String day = df.format(now.getTime());        //新的一天,通过新 key 获取值,每天都能从1开始获取        key = key + "_" + day;        Long num = jedis.incr(key);        //设置 key 过期时间        if (num == 1) {            jedis.expire(key, (24 - now.get(Calendar.HOUR_OF_DAY)) * 3600 + 1800);        }        if (haveDay) {            return createUUID(num, day, length);        } else {            return num;        }    } finally {        if (jedis != null) {            jedis.close();        }    }}

public Long fetchUUID(String key, Integer length, Boolean haveDay) {    Jedis jedis = null;    try {        jedis = redisConnection.getJedis();        jedis.select(dbIndex);        Calendar now = new GregorianCalendar();        Long num = jedis.incr(key);

        if (haveDay) {            String day = df.format(now.getTime());            return createUUID(num, day, length);        } else {            return num;        }    } finally {        if (jedis != null) {            jedis.close();        }    }}

private Long createUUID(Long num, String day, Integer length) {    String id = String.valueOf(num);    if (id.length()         NumberFormat nf = NumberFormat.getInstance();        nf.setGroupingUsed(false);        nf.setMaximumIntegerDigits(length);        nf.setMinimumIntegerDigits(length);        id = nf.format(num);    }    return Long.parseLong(day + id);}}

编写测试用例

在 Junit4 中不支持多线程测试,所以这里直接采用了 main 方法中运行测试用例。

package com.x9710.common.redis.test;

import com.x9710.common.redis.RedisConnection;import com.x9710.common.redis.impl.UUIDServiceRedisImpl;

import java.util.Date;

public class RedisUUIDTest {

public static void main(String[] args) {    for (int i = 0; i 20; i++) {        new Thread(new Runnable() {            public void run() {                RedisConnection redisConnection = RedisConnectionUtil.create();                UUIDServiceRedisImpl uuidServiceRedis = new UUIDServiceRedisImpl();                uuidServiceRedis.setRedisConnection(redisConnection);                uuidServiceRedis.setDbIndex(15);                try {                    for (int i = 0; i 100; i++) {                        System.out.println(new Date() + " get uuid = " +                               uuidServiceRedis.fetchUUID("MEMBER", 8, Boolean.TRUE) +                               " by globle in " + Thread.currentThread().getName());                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();

        new Thread(new Runnable() {            public void run() {                RedisConnection redisConnection = RedisConnectionUtil.create();                UUIDServiceRedisImpl uuidServiceRedis = new UUIDServiceRedisImpl();                uuidServiceRedis.setRedisConnection(redisConnection);                uuidServiceRedis.setDbIndex(15);                try {                    for (int i = 0; i 100; i++) {                        System.out.println(new Date() + " get uuid = " +                             uuidServiceRedis.fetchDailyUUID("ORDER", 8, Boolean.TRUE) +                             " by daily in " + Thread.currentThread().getName());                    }                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();    }}}

执行结果如下

Mon Dec 11 16:14:10 CST 2017 get uuid = 2017121100000003 by member in Thread-32Mon Dec 11 16:14:10 CST 2017 get uuid = 2017121100000001 by member in Thread-8Mon Dec 11 16:14:10 CST 2017 get uuid = 2017121100000007 by order in Thread-19......Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100002000 by member in Thread-14Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100001999 by member in Thread-16Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100001999 by order in Thread-39Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100002000 by order in Thread-39

这样,我们就实现了一个满足开始七个需求的一个基本的唯一标识服务。只要调用这个模块的程序连接的 redis 服务器的配置一样,就能实现在同一个对象高效生成唯一标识的基础服务。你还可以将这个包装成为一个 rest 服务,客户端不需要直接连接 redis 服务器,直接通过 rest 的http 服务远程获取唯一标识即可。

GitHub地址

https://github.com/gaochao2000/redis_util

Java面试题专栏

【81期】面试官:说说HashMap 中的容量与扩容实现

【82期】面试中被问到SQL优化,看这篇就对了!

【83期】面试被问到了Redis和MongoDB的区别?看这里就对了

【84期】面试中设计模式能问些什么?比如说一下三种单例模式实现

【85期】谈谈Java面向对象设计的六大原则,中高级面试常问!

【86期】五个刁钻的String面试问题及解答

【87期】面试官问:Java序列化和反序列化为什么要实现Serializable接口

【88期】面试官问:你能说说 Spring 中,接口的bean是如何注入的吗?

【89期】面试官 5 连问一个 TCP 连接可以发多少个 HTTP 请求?

【90期】面试官:说一下使用 Redis 实现大规模的帖子浏览计数的思路

欢迎长按下图关注公众号后端技术精选

c#获取对象的唯一标识_在 Java 中利用 redis 实现分布式全局唯一标识服务相关推荐

  1. java 结合redis队列_在 Java 中使用 redis 的消息队列服务

    前言 关于 redis 我们前面已经讨论过了缓存.分布式锁.分布式唯一标识.LBS服务的用法,这里我们来谈谈利用 redis 来实现一个消息服务. 典型的消息服务是一个生产者和消费者模式的服务.一般是 ...

  2. JAVA中所有对象的超类是_在Java中获取对象的超类

    可以使用java.lang.Class.getSuperclass()方法获得任何实体的直接超类,例如对象,类,原始类型,接口等.此方法不包含任何参数. 演示此的程序如下所示- 示例public cl ...

  3. java获取页面点击次数_在Java中怎样得出一个按钮点击的次数

    展开全部 java中得出一个按钮点击的次数,可以使用临时变量32313133353236313431303231363533e58685e5aeb931333337616566来保存点击的次数,在监听 ...

  4. java 唯一id生成算法_分布式全局唯一ID生成方案之snowflake算法

    已有的方案: 可大致分为: 完全依赖关系/非关系型数据库递增的方案 完全不依赖数据源作为生成因子的UUID 半依赖数据源作为生成因子的snowflake 为什么推荐snowflake? 这个问题,可以 ...

  5. 分布式全局唯一ID的实现

    分布式全局唯一ID的实现 前言 上周末考完试,这周正好把工作整理整理,然后也把之前的一些素材,整理一番,也当自己再学习一番. 一方面正好最近看到几篇这方面的文章,另一方面也是正好工作上有所涉及,所以决 ...

  6. java 正则表达式 判断 日期_怎么在java中利用正则表达式对时间日期进行判断

    怎么在java中利用正则表达式对时间日期进行判断 发布时间:2020-12-08 16:18:34 来源:亿速云 阅读:105 作者:Leah 这篇文章给大家介绍怎么在java中利用正则表达式对时间日 ...

  7. java请求参数_在Java中发送http的post请求,设置请求参数等等

    前几天做了一个定时导入数据的接口,需要发送http请求,第一次做这种的需求,特地记一下子, 导包 import java.text.SimpleDateFormat; import java.util ...

  8. [分布式] ------ 全局唯一id生成之雪花算法(Twitter_Snowflake)

    雪花算法(Twitter_Snowflake) 我们知道,分布式全局唯一id的生成,一般是以下几种: 基于雪花算法生成 基于数据库 基于redis 基于zookeeper 本文说下雪花算法,后面附源码 ...

  9. Redis:实现全局唯一ID

    Redis:实现全局唯一ID 一. 概述 二. 实现 (1)获取初始时间戳 (2)生成全局ID 三. 测试 为什么可以实现全局唯一? 其他唯一ID策略 补充:countDownLatch 一. 概述 ...

最新文章

  1. Java10来了,来看看它一同发布的全新JIT编译器
  2. manacher(马拉车)算法详解
  3. 字体在ppt中可以整体替换吗_如何给ppt整体改字体_教你给ppt整体改字体的方法-系统城...
  4. 利用Civil 3D API更改曲面的样式
  5. 算法(一):智能小车速度控制(PID模糊控制)
  6. Unity3D视图介绍
  7. PS图片删除需要计算机管理权限,电脑打开ps,显示没有管理员权限
  8. VS软件版本号定义、规则和相关的Visual Studio插件
  9. 洛谷 P1069 细胞分裂 质因数分解
  10. 关于c++中引用的基本用法
  11. 安迪·鲁宾支持的猫头鹰实验室刚推出了一款机器人视频会议摄像机
  12. 鸿蒙不用百度网盘,百度网盘限速有救了!官方新出2种方法,不用开会员
  13. 11 个非常有用的 HTML One-Liners
  14. C# WPF DataGrid控件的详细介绍和推荐一些样式设计
  15. 中国软件人没有创造力?
  16. Mysql EXPLAIN 详解 + SQL
  17. Jquery Select 插件 lyhucSelect 数据联动
  18. ES6-Promise简介、ES7 新特性及ES8新特性-async和await
  19. sqoop的安装与使用,sqoop安装使用
  20. php单元格,PHP中的单元格怎么利用PhpSpreadsheet进行设置

热门文章

  1. Comparison of Laser SLAM and Visual SLAM
  2. 全景分割:CVPR2019论文解析
  3. 计算机视觉多目标检测整合算法
  4. 2021年大数据HBase(十三):HBase读取和存储数据的流程
  5. 2021年大数据Flink(六):Flink On Yarn模式
  6. Python数据挖掘1:创建一位数组和二维数组,取最大最小值,切片
  7. python 字符串拼接
  8. Android 自定义View (入门 篇) 的使用
  9. 上一篇的js处理失真数据存在问题换了种方法
  10. 洛谷P4053 [JSOI2007]建筑抢修