接着上一篇 php + redis + lua 实现一个简略的发号器(1)-- 原理篇,本篇讲一下spring-boot-starter 版本的发号器的具体实现。
1、基础知识
发号器的实现次要用到了上面的一些知识点:
1. php中的位运算的操作和求值
2. 计算机原码、补码、反码的基本概念
3. redis中lua脚本的编写和调试
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的相干依赖:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.srorders.starter</groupId> <artifactId>uuid</artifactId> <version>1.0.0</version> <name>uuid</name> <description>uuid</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--Spring Boot Starter: START--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 引入redis的starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <!-- Import dependency management from Spring Boot --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.6.1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build></project>
定义一个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<String, Long> getNumber() throws InterruptedException { HashMap<String, Long> 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<Long> redisScript = RedisScript.of(lua, Long.class); return redisTemplate.execute(redisScript, Collections.singletonList(id)); } /** * 反解id * * @param id * @return */ public Map<String, Long> reverseNumber(Long id) { HashMap<String, Long> 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")@Datapublic 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 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.srorders.spring.boot</groupId> <artifactId>starter-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>starter-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <!-- 这里咱们引入咱们自定义的 starter --> <dependency> <groupId>com.srorders.starter</groupId> <artifactId>uuid</artifactId> <version>1.0.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build></project>
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;@RestControllerpublic 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<String,Long> reverse(@RequestParam(value = "id") Long id) throws InterruptedException { return this.signedService.reverseNumber(id); }}