接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。
1、基础知识
发号器的实现主要用到了下面的一些知识点:
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(UuidProperties uuidProperties, 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 = "id") Long id) throws InterruptedException {
return this.signedService.reverseNumber(id);
}
}