springboot-stater|springboot-stater + redis + lua 实现一个简单的发号器(3)-- 实现篇
接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。
1、基础知识
发号器的实现主要用到了下面的一些知识点:
1. php中的位运算的操作和求值
2. 计算机原码、补码、反码的基本概念
3. redis中lua脚本的编写和调试
【springboot-stater|springboot-stater + redis + lua 实现一个简单的发号器(3)-- 实现篇】4. 如何自己定一个spring-boot-starter
2、具体实现
├── pom.xml
├── src
│├── main
││├── java
│││└── com
│││└── srorders
│││└── starter
│││├── SignGenerator.java
│││├── UuidConfiguration.java
│││└── UuidProperties.java
││└── resources
││├── META-INF
│││└── spring.factories
││└── application.yml
│└── test
│└── java
│└── com
│└── srorders
│└── starter
│└── SignGeneratorTest.java
pom的相关依赖:
4.0.0
com.srorders.starter
uuid
1.0.0
uuid
uuid 1.8
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-data-redis
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-autoconfigure
org.springframework.boot
spring-boot-dependencies
2.6.1
pom
import
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
定义一个spring-boot-starter主要分为4个部分:
1、定义一个法号器服务
package com.srorders.starter;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class SignGenerator {/**
* 申请64位内存
*/
public static final int BITS_FULL = 64;
/**
* uuid
*/
public static final String BITS_FULL_NAME = "id";
/**
* 1位符号位
*/
public static final int BITS_PREFIX = 1;
/**
* 41时间位
*/
public static final int BITS_TIME = 41;
/**
* 时间位名称
*/
public static final String BITS_TIME_NAME = "diffTime";
/**
* 产生的时间
*/
public static final String BITS_GENERATE_TIME_NAME = "generateTime";
/**
* 5个服务器位
*/
public static final int BITS_SERVER = 5;
/**
* 服务位名称
*/
public static final String BITS_SERVER_NAME = "serverId";
/**
* 5个worker位
*/
public static final int BITS_WORKER = 5;
/**
* worker位名称
*/
public static final String BITS_WORKER_NAME = "workerId";
/**
* 12个自增位
*/
public static final int BITS_SEQUENCE = 12;
/**
* 自增位名称
*/
public static final String BITS_SEQUENCE_NAME = "sequenceNumber";
/**
* uuid配置
*/
private UuidProperties uuidProperties;
/**
* redis client
*/
private StringRedisTemplate redisTemplate;
/**
* 构造
*
* @param uuidProperties
*/
public SignGenerator(UuidProperties uuidProperties, StringRedisTemplate redisTemplate) {
this.uuidProperties = uuidProperties;
this.redisTemplate = redisTemplate;
}private long getStaterOffsetTime() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return LocalDateTime.parse(uuidProperties.getOffsetTime(), dateTimeFormatter).toInstant(OffsetDateTime.now().getOffset())
.toEpochMilli();
}/**
* 获取uuid
*
* @return
*/
public Map getNumber() throws InterruptedException {
HashMap result = new HashMap<>();
do {
long id = 0L;
long diffTime = Instant.now().toEpochMilli() - this.getStaterOffsetTime();
long maxDiffTime = (long) (Math.pow(2, BITS_TIME) - 1);
if (diffTime > maxDiffTime) {
throw new RuntimeException(String.format("the offsetTime: %s is too small", uuidProperties.getOffsetTime()));
}// 对时间位进行计算
int shift = BITS_FULL - BITS_PREFIX - BITS_TIME;
id |= diffTime << shift;
result.put(BITS_TIME_NAME, diffTime);
// 对server进行计算
shift = shift - BITS_SERVER;
id |= uuidProperties.getServerId() << shift;
result.put(BITS_SERVER_NAME, uuidProperties.getServerId());
// 对worker进行计算
shift = shift - BITS_WORKER;
id |= uuidProperties.getWorkerId() << shift;
result.put(BITS_WORKER_NAME, uuidProperties.getWorkerId());
// 对sequence进行计算
Long sequence = this.getSequence("uuid_" + diffTime);
long maxSequence = (long) (Math.pow(2, BITS_SEQUENCE) - 1);
if (sequence > maxSequence) {
Thread.sleep(1);
} else {
id |= sequence;
result.put(BITS_SEQUENCE_NAME, sequence);
result.put(BITS_FULL_NAME, id);
return result;
}
} while (true);
}/**
* 获取自增id
*
* @param id
* @return
*/
private Long getSequence(String id) {
String lua = " local sequenceKey = KEYS[1];
" +
"local sequenceNumber = redis.call(\"incr\", sequenceKey);
" +
"redis.call(\"pexpire\", sequenceKey, 100);
" +
"return sequenceNumber";
RedisScript redisScript = RedisScript.of(lua, Long.class);
return redisTemplate.execute(redisScript, Collections.singletonList(id));
}/**
* 反解id
*
* @param id
* @return
*/
public Map reverseNumber(Long id) {
HashMap result = new HashMap<>();
//time
int shift = BITS_FULL - BITS_PREFIX - BITS_TIME;
Long diffTime = (id >> shift) & (long) (Math.pow(2, BITS_TIME) - 1);
result.put(BITS_TIME_NAME, diffTime);
//generateTime
Long generateTime = diffTime + this.getStaterOffsetTime();
result.put(BITS_GENERATE_TIME_NAME, generateTime);
//server
shift = shift - BITS_SERVER;
Long server = (id >> shift) & (long) (Math.pow(2, BITS_SERVER) - 1);
result.put(BITS_SERVER_NAME, server);
//worker
shift = shift - BITS_WORKER;
Long worker = (id >> shift) & (long) (Math.pow(2, BITS_WORKER) - 1);
result.put(BITS_WORKER_NAME, worker);
//sequence
Long sequence = id & (long) (Math.pow(2, BITS_SEQUENCE) - 1);
result.put(BITS_SEQUENCE_NAME, sequence);
return result;
}
}
2、定义一个产生bean的自动配置类
package com.srorders.starter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* @author zero
*/
@Configuration
@EnableConfigurationProperties(UuidProperties.class)
@ConditionalOnClass({StringRedisTemplate.class, UuidProperties.class})
public class UuidConfiguration {
@Bean
@ConditionalOnMissingBean(SignGenerator.class)
public SignGenerator signGenerator(UuidPropertiesuuidProperties, StringRedisTemplate stringRedisTemplate) {
return new SignGenerator(uuidProperties, stringRedisTemplate);
}
}
3、定义一个映射application.properties(application.yml)配置的对象
package com.srorders.starter;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author zero
*/
@ConfigurationProperties("spring.uuid")
@Data
public class UuidProperties {private Long serverId = 0L;
private Long workerId = 0L;
private String offsetTime = "2021-12-07 00:00:00";
}
4、在resources目录下创建 META-INF 目录,然后在该目录下创建 spring.factories 文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.srorders.starter.UuidConfiguration
3、运行一把 1、建立一个简单的spring-boot-web项目, pom.xml 文件内容如下:
4.0.0 org.springframework.boot
spring-boot-starter-parent
2.6.1
com.srorders.spring.boot
starter-demo
0.0.1-SNAPSHOT
starter-demo
Demo project for Spring Boot 1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-autoconfigure
com.srorders.starter
uuid
1.0.0
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2、新建一个控制器内容如下
package com.srorders.spring.boot.controller;
import com.srorders.starter.SignGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class UuidController {@Autowired
SignGenerator signedService;
@GetMapping("/getUuid")
public String getUuid() throws InterruptedException {
return this.signedService.getNumber().get(SignGenerator.BITS_FULL_NAME).toString();
}@GetMapping("/reverse")
public Map reverse(@RequestParam(value = "https://www.it610.com/article/id") Long id) throws InterruptedException {
return this.signedService.reverseNumber(id);
}
}
推荐阅读
- springboot使用redis缓存
- (1)redis集群原理及搭建与使用(1)
- springboot结合redis实现搜索栏热搜功能及文字过滤
- Redis——发布订阅/消息队列
- redis|redis 常见问题一
- 实操Redission|实操Redission 分布式服务
- 二、Redis的五种常用数据类型
- 深入理解redis——布隆过滤器BloomFilter
- redis哨兵模式
- 关于分布式锁|关于分布式锁 Redis 与 Zookeeper 的原理,它们如何实现分布式锁()