java redis 生成唯一id_Redis在集群环境中生成唯一ID

概述

设计目标:每秒最大生成10万个ID,ID单调递增且唯一。Reidis可以不需要持久化ID。

要求:集群时钟不能倒退。

总体思路:集群中每个节点预生成生成ID;然后与redis的已经存在的ID做比较。如果大于,则取节点生成的ID;小于的话,取Redis中最大ID自增。

Java代码

import org.apache.commons.lang3.RandomStringUtils;

import org.apache.commons.lang3.StringUtils;

import org.apache.commons.lang3.time.FastDateFormat;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import static com.google.common.base.Preconditions.checkArgument;

/**

* 生成递增的唯一序列号, 可以用来生成订单号,例如216081817202494579

*

* 生成规则:

* 业务类型 + redis中最大的序列号

*

* 约定:

* redis中最大的序列号长度为17,包括{6位日期 + 6位时间 + 3位毫秒数 + 2位随机}

*

* 建议:

* 为了容错和服务降级, SeqGenerator生成失败时最好采用UUID替换

*

* Created by juemingzi on 16/8/19.

*/

public class SeqGenerator {

private static final Logger logger = LoggerFactory.getLogger(SeqGenerator.class);

private static final Path filePath = Paths.get(Thread.currentThread().getContextClassLoader().getResource("lua/get_next_seq.lua").getPath());

//线程安全

private static final FastDateFormat seqDateFormat = FastDateFormat.getInstance("yyMMddHHmmssSSS");

private static final RedisExtraService redisExtraService = SpringContext.getBean(RedisExtraService.class);

private final byte[] keyName;

private final byte[] incrby;

private byte[] sha1;

public SeqGenerator(String keyName) throws IOException {

this(keyName, 1);

}

/**

* @param keyName

* @param incrby

*/

public SeqGenerator(String keyName, int incrby) throws IOException {

checkArgument(keyName != null && incrby > 0);

this.keyName = keyName.getBytes();

this.incrby = Integer.toString(incrby).getBytes();

init();

}

private void init() throws IOException {

byte[] script;

try {

script = Files.readAllBytes(filePath);

} catch (IOException e) {

logger.error("读取文件出错, path: {}", filePath);

throw e;

}

sha1 = redisExtraService.scriptLoad(script);

}

public String getNextSeq(String bizType) {

checkArgument(StringUtils.isNotBlank(bizType));

return bizType + getMaxSeq();

}

private String generateSeq() {

String seqDate = seqDateFormat.format(System.currentTimeMillis());

String candidateSeq = new StringBuilder(17).append(seqDate).append(RandomStringUtils.randomNumeric(2)).toString();

return candidateSeq;

}

/**

* 通过redis生成17位的序列号,lua脚本保证序列号的唯一性

*

* @return

*/

public String getMaxSeq() {

String maxSeq = new String((byte[]) redisExtraService.evalsha(sha1, 3, keyName, incrby, generateSeq().getBytes()));

return maxSeq;

}

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

113006530_1_20171010092720249.png

lua脚本

--

-- 获取最大的序列号,样例为16081817202494579

--

-- Created by IntelliJ IDEA.

-- User: juemingzi

-- Date: 16/8/18

-- Time: 17:22

local function get_max_seq()

local key = tostring(KEYS[1])

local incr_amoutt = tonumber(KEYS[2])

local seq = tostring(KEYS[3])

local month_in_seconds = 24 * 60 * 60 * 30

if (1 == redis.call(\'setnx\', key, seq))

then

redis.call(\'expire\', key, month_in_seconds)

return seq

else

local prev_seq = redis.call(\'get\', key)

if (prev_seq < seq)

then

redis.call(\'set\', key, seq)

return seq

else

--[[

不能直接返回redis.call(\'incr\', key),因为返回的是number浮点数类型,会出现不精确情况。

注意: 类似"16081817202494579"数字大小已经快超时lua和reids最大数值,请谨慎的增加seq的位数

--]]

redis.call(\'incrby\', key, incr_amoutt)

return redis.call(\'get\', key)

end

end

end

return get_max_seq()1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

测试代码

public class SeqGeneratorTest extends BaseTest {

@Test

public void testGetNextSeq() throws Exception {

final SeqGenerator seqGenerater = new SeqGenerator("orderId");

String orderId = seqGenerater.getNextSeq(Integer.toString(WaitingOrder.KIND_TAKE_OUT));

assertNotNull(orderId);

System.out.println("orderId is: " + orderId);

}

@Test

public void testGetNextSeqWithMultiThread() throws Exception {

int cpus = Runtime.getRuntime().availableProcessors();

CountDownLatch begin = new CountDownLatch(1);

CountDownLatch end = new CountDownLatch(cpus);

final Set seqSet = new ConcurrentSkipListSet<>();

ExecutorService executorService = Executors.newFixedThreadPool(cpus);

final SeqGenerator seqGenerater = new SeqGenerator("orderId");

for (int i = 0; i < cpus; i++) {

executorService.execute(new Worker(seqGenerater, seqSet, begin, end));

}

begin.countDown();

end.await();

assertEquals(seqSet.size(), cpus * 10000);

System.out.println("finish!");

}

private static class Worker implements Runnable {

private final CountDownLatch begin;

private final CountDownLatch end;

private final Set seqSet;

private final SeqGenerator seqGenerator;

public Worker(SeqGenerator seqGenerator, Set seqSet, CountDownLatch begin, CountDownLatch end) {

this.seqGenerator = seqGenerator;

this.seqSet = seqSet;

this.begin = begin;

this.end = end;

}

@Override

public void run() {

try {

begin.await();

for (int i = 0; i < 10000; i++) {

String seq = seqGenerator.getNextSeq("2");

if (!seqSet.add(seq)) {

System.out.println(seq);

fail();

}

}

System.out.println("end");

} catch (Exception e) {

e.printStackTrace();

} finally {

end.countDown();

}

}

}

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438608.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java await signal_【Java并发008】原理层面:ReentrantLock中 await()、signal()/signalAll()全解析...

一、前言上篇的文章中我们介绍了AQS源码中lock方法和unlock方法&#xff0c;这两个方法主要是用来解决并发中互斥的问题&#xff0c;这篇文章我们主要介绍AQS中用来解决线程同步问题的await方法、signal方法和signalAll方法&#xff0c;这几个方法主要对应的是synchronized中的…

第八节:Task的各类TaskTResult返回值以及通用线程的异常处理方案

一. Task的各种返回值-Task<TResult> PS&#xff1a; 在前面章节&#xff0c;我们介绍了Task类开启线程、线程等待、线程延续的方式&#xff0c;但我们并没有关注这些方式的返回值&#xff0c;其实他们都是有返回值的Task<TResult>&#xff0c;然后可以通过Task的…

mysql2005触发器修改成绩_创建、更改和删除触发器

创建、更改和删除触发器Creating, Altering, and Removing Triggers08/06/2017本文内容适用于&#xff1a;Applies to: SQL ServerSQL Server(所有支持的版本)SQL ServerSQL Server (all supported versions) Azure SQL 数据库Azure SQL DatabaseAzure SQL 数据库Azure SQL Dat…

第一节:从面向对象思想(oo)开发、接口、抽象类以及二者比较

一. 面向对象思想 1. 面向过程&#xff08;OP&#xff09;和面向对象&#xff08;OO&#xff09;的区别&#xff1a; (1)&#xff1a;面向过程就是排着用最简单的代码一步一步写下去&#xff0c;没有封装&#xff0c;当业务复杂的时候&#xff0c;改动就很麻烦了 (2)&#xff…

第二节:重写(new)、覆写(overwrite)、和重载(overload)

一. 重写 1. 关键字&#xff1a;new 2. 含义&#xff1a;子类继承父类中的普通方法&#xff0c;如果在子类中重写了一个和父类中完全相同的方法&#xff0c;子类中会报警告(问是否显式的隐藏父类的中的方法)&#xff0c;如果在子类中的方法前加上new关键字&#xff0c;则警告…

java 分页查询_JavaWeb之分页查询

时间&#xff1a;2016-12-11 01:411、分页的优点&#xff1a;只查询一页&#xff0c;不需要查询所有数据&#xff0c;能够提高效率。2、分页数据页面的数据都是由Servlet传递的* 当前页&#xff1a;pageCode> 如果页面没有向Servlet传递页码&#xff0c;那么Servlet默认…

第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字

一. 各类数据结构比较及其线程安全问题 1. Array(数组)&#xff1a; 分配在连续内存中,不能随意扩展&#xff0c;数组中数值类型必须是一致的。数组的声明有两种形式&#xff1a;直接定义长度&#xff0c;然后赋值&#xff1b;直接赋值。 缺点&#xff1a;插入数据慢。 优点&a…

第四节:IO、序列化和反序列化、加密解密技术

一. IO读写   这里主要包括文件的读、写、移动、复制、删除、文件夹的创建、文件夹的删除等常规操作。 注意&#xff1a;这里需要特别注意&#xff0c;对于普通的控制台程序和Web程序&#xff0c;将"相对路径"转换成"绝对路径"的方法不一致。 (1). 在w…

java mediator_java—mediator中介模式

中介者模式是由GoF提出的23种软件设计模式的一种。Mediator模式是行为模式之一&#xff0c;Mediator模式定义:用一个中介者对象来封装一系列的对象交互。中介者使各对象不需要显式的相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立的改变他们之间的交互。适用性…

第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)

一. 泛型诞生的背景 在介绍背景之前&#xff0c;先来看一个案例&#xff0c;要求&#xff1a;分别输出实体model1、model2、model3的id和name值,这三个实体有相同的属性名字id和name。 1 public class myUtils2 {3 //要求&#xff1a;分别输出实体model1、model2、…

第六节:反射(几种写法、好处和弊端、利用反射实现IOC)

一. 加载dll,读取相关信息 1. 加载程序集的三种方式 调用Assembly类下的三个方法&#xff1a;Load、LoadFile、LoadFrom。 1       //1.1 Load方法&#xff1a;动态默认加载当前路径下的(bin)下的dll文件,不需要后缀 2 Assembly assembly Assembly.Load(&…

第七节:语法总结(1)(自动属性、out参数、对象初始化器、var和dynamic等)

一. 语法糖简介 语法糖也译为糖衣语法&#xff0c;是由英国计算机科学家彼得约翰兰达&#xff08;Peter J. Landin&#xff09;发明的一个术语&#xff0c;指计算机语言中添加的某种语法&#xff0c;这种语法对语言的功能并没有影响&#xff0c;但是更方便程序员使用。通常来说…

java不用插件播放媒体文件_java servlet不用插件上传文件:

展开全部import java.net.*;import java.io.*;import java.util.*;import javax.servlet.*;import javax.servlet.http.*;public class SaveFileServlet extends HttpServlet{FileWriter savefile;String filename null;String value null;/*** Handles a POST request*/publ…

第八节:语法总结(2)(匿名类、匿名方法、扩展方法)

一. 匿名类 1. 传统的方式给类赋值&#xff0c;需要先建一个实体类→实例化→赋值&#xff0c;步骤很繁琐&#xff0c;在.Net 3.0时代&#xff0c;微软引入匿名类的概念&#xff0c;简化了代码编写&#xff0c;提高了开发效率。 匿名类的声明语法&#xff1a; var objnew {字段…

第九节:委托和事件(1)(委托的发展历史、插件式编程、多播委托)

一. 委托的发展历史和基本用法 说起委托&#xff0c;每个人可能都会对他有不同的理解&#xff0c;结合实战中委托的使用&#xff0c;我对其理解是&#xff1a;委托和类一样&#xff0c;是用户的一个自定义类型&#xff0c;委托可以有参数、有返回值&#xff0c;委托的关键字是d…

第十节:委托和事件(2)(泛型委托、Func和Action、事件及与委托的比较)

一. 泛型委托 所谓的泛型委托&#xff0c;即自定义委托的参数可以用泛型约束&#xff0c;同时内置委托Func和Action本身就是泛型委托。 将上一个章节中的Calculator类中的方法用自定义泛型委托重新实现一下。 1 public class Calculator22 {3 //传统解决方案一&am…

java+sm4+加密算法_SM4加密算法实现Java和C#相互加密解密

https://www.cnblogs.com/miaoziblog/p/9040473.html近期由于项目需要使用SM4对数据进行加密&#xff0c;然后传给Java后台&#xff0c;Java后台使用的也是SM4的加密算法但是就是解密不正确&#xff0c;经过一步步调试发现Java中好多数据类型与C#的相同的数据类型是存在不同的比…

DotNet进阶系列

一. 回顾历史 回顾个人发展历程&#xff0c;自2012年初次接触开发至今(2018年)已经有六个年头&#xff0c;这期间陆陆续续学习并掌握了不少技术&#xff0c;C#语言、ORM框架、多线程技术、设计模式、前端技术、MVC、MVVM框架思想等等&#xff0c;每种技术随着多次使用&#xff…

第十一节:特性(常见的特性标签、自定义特性、特性的使用案例)

一. 基本概念 1. 什么是特性? MSDN官方给出的定义时&#xff1a;公共语言运行时允许添加类似关键字的描述声明&#xff0c;叫做特性&#xff0c;它对程序中的元素进行标注&#xff0c;如类型、字段、方法和属性等。Attribute和Microsoft .Net Framework文件的元数据&#xff…

第十二节:Lambda、linq、SQL的相爱相杀(1)

一. 谈情怀 Lambda、Linq、SQL伴随着我的开发一年又一年&#xff0c;但它们三者并没有此消彼长&#xff0c;各自占有这一定的比重&#xff0c;起着不可替代的作用。 相信我们最先接触的应该就是SQL了&#xff0c;凡是科班出身的人&#xff0c;大学期间都会学习SQL Server数据库…