├── oss-release.cmd ├── docs └── assets │ └── img │ ├── bench.png │ ├── get10.png │ ├── wei-chat.png │ ├── seq-redis.png │ ├── seq-table.png │ └── profiler │ ├── cpu-profiler-mongo.png │ ├── cpu-profiler-mysql.png │ ├── cpu-profiler-lettuce.png │ └── cpu-profiler-postgre.png ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── oss-release.sh ├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── oss-deploy.yml │ └── maven.yml ├── examples ├── actuator-example │ ├── README.md │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── power4j │ │ │ └── sequence │ │ │ └── example │ │ │ └── SequenceActuatorExampleApplication.java │ └── pom.xml ├── redis-example │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── power4j │ │ │ └── sequence │ │ │ └── example │ │ │ └── SequenceRedisExampleApplication.java │ └── pom.xml ├── mongo-example │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── power4j │ │ │ └── sequence │ │ │ └── example │ │ │ └── SequenceMongoExampleApplication.java │ └── pom.xml ├── jdbc-example │ ├── src │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── power4j │ │ │ │ └── sequence │ │ │ │ └── example │ │ │ │ └── SequenceJdbcExampleApplicationTest.java │ │ └── main │ │ │ ├── resources │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── power4j │ │ │ └── sequence │ │ │ └── example │ │ │ ├── SeqService.java │ │ │ ├── SeqConfig.java │ │ │ └── SequenceJdbcExampleApplication.java │ └── pom.xml └── pom.xml ├── .editorconfig ├── sequence-spring-boot-starter ├── src │ └── main │ │ ├── resources │ │ └── META-INF │ │ │ └── spring │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── java │ │ └── com │ │ └── power4j │ │ └── kit │ │ └── seq │ │ └── spring │ │ └── boot │ │ └── autoconfigure │ │ ├── actuator │ │ ├── EndpointAutoConfiguration.java │ │ ├── SequenceEndpoint.java │ │ └── SeqSynchronizerEndpoint.java │ │ ├── MongoSynchronizerConfigure.java │ │ ├── SequenceProperties.java │ │ ├── JdbcSynchronizerConfigure.java │ │ ├── SequenceAutoConfigure.java │ │ └── RedisSynchronizerConfigure.java └── pom.xml ├── run-test.sh ├── bench-test ├── run-bench.sh ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── power4j │ │ │ └── kit │ │ │ └── seq │ │ │ ├── BenchParam.java │ │ │ ├── TestUtil.java │ │ │ ├── LongSeqPoolBench.java │ │ │ ├── LettuceSeqHolderBench.java │ │ │ ├── MongoSeqHolderBench.java │ │ │ ├── H2SeqHolderBench.java │ │ │ ├── PostgreSqlSeqHolderBench.java │ │ │ └── MySqlSeqHolderBench.java │ │ └── resources │ │ └── logback.xml └── pom.xml ├── scm-release.sh ├── develop.md ├── CHANGELOG.md ├── sequence-core ├── src │ ├── test │ │ ├── resources │ │ │ └── logback-test.xml │ │ └── java │ │ │ └── com │ │ │ └── power4j │ │ │ └── kit │ │ │ └── seq │ │ │ ├── persistent │ │ │ ├── provider │ │ │ │ ├── PostgreSqlSynchronizerTest.java │ │ │ │ ├── SimpleLettuceSynchronizerTest.java │ │ │ │ ├── SimpleMongoSynchronizerTest.java │ │ │ │ ├── H2SynchronizerTest.java │ │ │ │ ├── MySqlSynchronizerTest.java │ │ │ │ └── TestServices.java │ │ │ ├── MySqlSeqHolderTest.java │ │ │ ├── LettuceSeqHolderTest.java │ │ │ ├── PostgreSqlSeqHolderTest.java │ │ │ ├── H2SqlSeqHolderTest.java │ │ │ ├── MongoSeqHolderTest.java │ │ │ └── SeqHolderTestCase.java │ │ │ ├── TestUtil.java │ │ │ └── ext │ │ │ └── InMemorySequenceRegistryTest.java │ └── main │ │ └── java │ │ └── com │ │ └── power4j │ │ └── kit │ │ └── seq │ │ ├── core │ │ ├── exceptions │ │ │ └── SeqException.java │ │ ├── SeqPool.java │ │ ├── SeqFormatter.java │ │ ├── Sequence.java │ │ └── LongSeqPool.java │ │ ├── ext │ │ ├── SequenceRegistry.java │ │ └── InMemorySequenceRegistry.java │ │ ├── persistent │ │ ├── provider │ │ │ ├── RedisConstants.java │ │ │ ├── H2Synchronizer.java │ │ │ ├── MySqlSynchronizer.java │ │ │ ├── SimpleLettuceSynchronizer.java │ │ │ ├── LettuceClusterSynchronizer.java │ │ │ ├── InMemorySeqSynchronizer.java │ │ │ ├── AbstractSqlStatementProvider.java │ │ │ ├── PostgreSqlSynchronizer.java │ │ │ ├── AbstractLettuceSynchronizer.java │ │ │ └── SimpleMongoSynchronizer.java │ │ ├── Partitions.java │ │ ├── AddState.java │ │ └── SeqSynchronizer.java │ │ └── utils │ │ └── EnvUtil.java └── pom.xml ├── .travis.yml ├── .gitignore ├── ossrh-settings.xml └── README.md /oss-release.cmd: -------------------------------------------------------------------------------- 1 | mvn clean deploy -DskipTests=true -P oss-release -------------------------------------------------------------------------------- /docs/assets/img/bench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/bench.png -------------------------------------------------------------------------------- /docs/assets/img/get10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/get10.png -------------------------------------------------------------------------------- /docs/assets/img/wei-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/wei-chat.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /docs/assets/img/seq-redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/seq-redis.png -------------------------------------------------------------------------------- /docs/assets/img/seq-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/seq-table.png -------------------------------------------------------------------------------- /oss-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./mvnw clean deploy -Dgpg.passphrase=${GPG_PWD} -DskipTests=true -P 'oss-release' -------------------------------------------------------------------------------- /docs/assets/img/profiler/cpu-profiler-mongo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/profiler/cpu-profiler-mongo.png -------------------------------------------------------------------------------- /docs/assets/img/profiler/cpu-profiler-mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/profiler/cpu-profiler-mysql.png -------------------------------------------------------------------------------- /docs/assets/img/profiler/cpu-profiler-lettuce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/profiler/cpu-profiler-lettuce.png -------------------------------------------------------------------------------- /docs/assets/img/profiler/cpu-profiler-postgre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/power4j/sequence/HEAD/docs/assets/img/profiler/cpu-profiler-postgre.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | target-branch: "dev" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /examples/actuator-example/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 端点地址 3 | 4 | http://localhost:8080/actuator/sequence 5 | 6 | http://localhost:8080/actuator/sequence-synchronizer 7 | 8 | 9 | > 访问 http://localhost:8080/?size=123 ,模拟消费一定数量的序号 -------------------------------------------------------------------------------- /examples/redis-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | power4j: 5 | sequence: 6 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 7 | backend: redis 8 | lettuce-uri: "redis://127.0.0.1" 9 | -------------------------------------------------------------------------------- /examples/mongo-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | power4j: 5 | sequence: 6 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 7 | backend: mongo 8 | mongo-uri: "mongodb://root:root@127.0.0.1" 9 | 10 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{adoc, bat, groovy, html, java, js, jsp, kt, kts, md, properties, yml, py, rb, sh, sql, svg, txt, xml, xsd}] 4 | charset = utf-8 5 | 6 | [*.{groovy, java, kt, kts}] 7 | indent_style = tab 8 | indent_size = 4 9 | continuation_indent_size = 8 10 | end_of_line = lf 11 | 12 | [*.{js, html, xml, xsd, yml}] 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | com.power4j.kit.seq.spring.boot.autoconfigure.JdbcSynchronizerConfigure 2 | com.power4j.kit.seq.spring.boot.autoconfigure.RedisSynchronizerConfigure 3 | com.power4j.kit.seq.spring.boot.autoconfigure.MongoSynchronizerConfigure 4 | com.power4j.kit.seq.spring.boot.autoconfigure.SequenceAutoConfigure 5 | com.power4j.kit.seq.spring.boot.autoconfigure.actuator.EndpointAutoConfiguration -------------------------------------------------------------------------------- /run-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export TEST_MYSQL_URL="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" 4 | export TEST_MYSQL_USER=root 5 | export TEST_MYSQL_PWD=root 6 | export TEST_POSTGRESQL_URL="jdbc:postgresql://127.0.0.1/test?ssl=false" 7 | export TEST_POSTGRESQL_USER=root 8 | export TEST_POSTGRESQL_PWD=root 9 | export TEST_REDIS_URI="redis://127.0.0.1:6379" 10 | export TEST_MONGO_URI="mongodb://root:root@127.0.0.1:27017" 11 | 12 | 13 | mvn test -DskipTests=false -B -P '!oss-release,!travis-ci' -------------------------------------------------------------------------------- /bench-test/run-bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # usage: ./run-bench.sh -rf json 3 | 4 | export TEST_MYSQL_URL="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" 5 | export TEST_MYSQL_USER=root 6 | export TEST_MYSQL_PWD=root 7 | export TEST_POSTGRESQL_URL="jdbc:postgresql://127.0.0.1/test?ssl=false" 8 | export TEST_POSTGRESQL_USER=root 9 | export TEST_POSTGRESQL_PWD=root 10 | export TEST_REDIS_URI="redis://127.0.0.1:6379" 11 | export TEST_MONGO_URI="mongodb://root:root@127.0.0.1:27017" 12 | 13 | 14 | java -jar target/benchmarks.jar "$@" -------------------------------------------------------------------------------- /scm-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export TEST_MYSQL_URL="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" 4 | export TEST_MYSQL_USER=root 5 | export TEST_MYSQL_PWD=root 6 | export TEST_POSTGRESQL_URL="jdbc:postgresql://127.0.0.1/test?ssl=false" 7 | export TEST_POSTGRESQL_USER=root 8 | export TEST_POSTGRESQL_PWD=root 9 | export TEST_REDIS_URI="redis://127.0.0.1:6379" 10 | export TEST_MONGO_URI="mongodb://root:root@127.0.0.1:27017" 11 | 12 | mvn release:clean -P 'oss-release' && \ 13 | mvn release:prepare -P 'oss-release' && \ 14 | mvn release:perform -P 'oss-release' -------------------------------------------------------------------------------- /develop.md: -------------------------------------------------------------------------------- 1 | # Relase new branch 2 | ``` 3 | mvn release:branch -DbranchName=my-branch 4 | mvn clean deploy 5 | ``` 6 | 7 | By default, the POM in the new branch keeps the same version as the local working copy, and the local POM is incremented to the next revision. If you want to update versions in the new branch and not in the working copy, run: 8 | ``` 9 | mvn release:branch -DbranchName=my-branch -DupdateBranchVersions=true -DupdateWorkingCopyVersions=false 10 | git checkout my-branch 11 | mvn clean deploy 12 | git checkout master 13 | ``` 14 | 15 | # Updating POM Versions 16 | 17 | ``` 18 | mvn release:update-versions 19 | ``` 20 | You will be prompted for the version number for each module of the project. If you prefer that each module version be the same as the parent POM, you can use the option autoVersionSubmodules. 21 | ``` 22 | mvn release:update-versions -DautoVersionSubmodules=true 23 | ``` 24 | 25 | # Update version 26 | ``` 27 | mvn versions:set -DnewVersion= 28 | ``` -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/BenchParam.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | /** 20 | * 公共参数 21 | * 22 | * @author CJ (power4j@outlook.com) 23 | * @date 2020/7/3 24 | * @since 1.0 25 | */ 26 | public interface BenchParam { 27 | 28 | long SEQ_INIT_VAL = 1L; 29 | 30 | int SEQ_POOL_SIZE = 1000; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /examples/actuator-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | profiles: 6 | # mysql, postgresql 7 | active: mysql 8 | 9 | power4j: 10 | sequence: 11 | fetch-size: 10 12 | 13 | management: 14 | endpoints: 15 | web: 16 | exposure: 17 | include: 18 | - sequence 19 | - sequence-synchronizer 20 | --- 21 | spring: 22 | profiles: mysql 23 | datasource: 24 | url: "jdbc:mysql://${MYSQL_HOST:localhost}:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" 25 | username: root 26 | password: root 27 | 28 | power4j: 29 | sequence: 30 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 31 | backend: mysql 32 | --- 33 | spring: 34 | profiles: postgresql 35 | datasource: 36 | url: "jdbc:postgresql://${POSTGRESQL_HOST:localhost}:5432/test?ssl=false" 37 | username: root 38 | password: root 39 | 40 | power4j: 41 | sequence: 42 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 43 | backend: postgresql 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.1 2 | `2023-05-04` 3 | - 🔥 升级Spring Boot `3.x` 4 | - 🔥 使用Java 17 5 | 6 | ## 1.6.0 7 | `2022-06-01` 8 | - 🔥 升级Spring Boot `2.7.0`,使用Spring Boot 2.7新的配置文件加载方式(GH PR #79 from lishangbu/master) 9 | 10 | mvn versions:set -DnewVersion= 11 | ## 1.5.1 12 | `2022-06-01` 13 | - 🔥 升级到Java 11 14 | - 🔥 升级Spring Boot `2.6.8` 15 | - 🔥 插件升级到最新版本 16 | 17 | ## 1.5.0 18 | `2021-09-03` 19 | - 🔥 支持`H2`(GH PR #48 from lishangbu/master) 20 | - 🔥 新增SequenceRegistry,用于支持动态创建发号器的场景 21 | - 🔥 新增InMemorySeqSynchronizer,用于测试 22 | 23 | ## 1.4.0 24 | `2020-10-08` 25 | - Fix duplicate key error #5 26 | - 一些依赖升级 27 | 28 | 29 | ## 1.3.0 30 | 31 | `2020-08-03` 32 | 33 | - 🔥 支持`PostgreSQL` 34 | 35 | 36 | ## 1.2.0 37 | 38 | `2020-07-21` 39 | 40 | - 🔥 支持`MongoDB` 41 | 42 | ### 注意事项 43 | 44 | - 使用方需要引入MongoDB驱动包 45 | 46 | 47 | ## 1.1.1 48 | 49 | `2020-07-13` 50 | 51 | - 🔥 支持`Redis`,基于`Lettuce` 52 | 53 | 54 | ### 注意事项 55 | 56 | - 使用方需要引入Lettuce驱动包 57 | - 如果需要支持连接池,还需引入`commons-pool2` 58 | 59 | 60 | ## 1.0.1 61 | 62 | `2020-07-07` 63 | 64 | - 🔥 支持`MySQL` 65 | - 🔥Spring Boot 集成 66 | -------------------------------------------------------------------------------- /bench-test/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /sequence-core/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "57 17 * * 2" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ java ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Set up JDK 17 30 | uses: actions/setup-java@v3 31 | with: 32 | distribution: 'adopt' 33 | java-version: '17' 34 | 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v2 37 | with: 38 | languages: ${{ matrix.language }} 39 | queries: +security-and-quality 40 | 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v2 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@v2 46 | with: 47 | category: "/language:${{ matrix.language }}" 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis CI Java documentation can be found in: 2 | # https://docs.travis-ci.com/user/languages/java/ 3 | 4 | # https://docs.travis-ci.com/user/reference/overview/#linux 5 | dist: xenial 6 | 7 | # This enables the 'defaults' to test java applications: 8 | language: java 9 | 10 | # https://docs.travis-ci.com/user/reference/xenial/#jvm-clojure-groovy-java-scala-support 11 | jdk: 12 | - openjdk11 13 | 14 | cache: 15 | directories: 16 | - $HOME/.m2 17 | 18 | # https://docs.travis-ci.com/user/job-lifecycle/ 19 | script: 20 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -P '!oss-release,travis-ci' 21 | - mvn test -DskipTests=false -B -P '!oss-release,travis-ci' 22 | 23 | # tip: export CODECOV_TOKEN="xxx" 24 | after_success: 25 | - bash <(curl -s https://codecov.io/bash) 26 | - bash <(curl -Ls https://coverage.codacy.com/get.sh) 27 | 28 | before_script: 29 | - git config --global core.autocrlf input 30 | 31 | before_install: 32 | - mysql -e 'CREATE DATABASE IF NOT EXISTS test;' 33 | - psql -c 'create database test;' -U postgres 34 | 35 | services: 36 | - mysql 37 | - mongodb 38 | - redis-server 39 | - postgresql 40 | 41 | addons: 42 | postgresql: "9.6" 43 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/core/exceptions/SeqException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.core.exceptions; 18 | 19 | /** 20 | * @author CJ (power4j@outlook.com) 21 | * @date 2020/6/30 22 | * @since 1.0 23 | */ 24 | public class SeqException extends RuntimeException { 25 | 26 | public SeqException(String message) { 27 | super(message); 28 | } 29 | 30 | public SeqException(String message, Throwable cause) { 31 | super(message, cause); 32 | } 33 | 34 | public SeqException(Throwable cause) { 35 | super(cause); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/TestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import lombok.experimental.UtilityClass; 20 | 21 | import java.time.LocalDateTime; 22 | import java.time.format.DateTimeFormatter; 23 | 24 | /** 25 | * @author CJ (power4j@outlook.com) 26 | * @date 2020/7/14 27 | * @since 1.0 28 | */ 29 | @UtilityClass 30 | public class TestUtil { 31 | 32 | public String getPartitionName() { 33 | return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss.SSS")) + "." 34 | + Thread.currentThread().getId(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /examples/jdbc-example/src/test/java/com/power4j/sequence/example/SequenceJdbcExampleApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.power4j.sequence.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.web.client.TestRestTemplate; 7 | import org.springframework.boot.test.web.server.LocalServerPort; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | 14 | /** 15 | * @author CJ (power4j@outlook.com) 16 | * @date 2022/6/1 17 | * @since 1.0 18 | */ 19 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 20 | class SequenceJdbcExampleApplicationTest { 21 | 22 | @LocalServerPort 23 | private int port; 24 | 25 | @Autowired 26 | private TestRestTemplate restTemplate; 27 | 28 | @Test 29 | public void shouldReturnSeq() { 30 | List contents = Arrays.asList("seq", "query_count", "update_count"); 31 | String rsp = restTemplate.getForObject("http://localhost:" + port + "/", String.class); 32 | System.out.printf("payload: %s\n", rsp); 33 | assertThat(rsp).contains(contents); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /examples/jdbc-example/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | profiles: 6 | # mysql, postgresql, h2 7 | active: h2 8 | 9 | power4j: 10 | sequence: 11 | fetch-size: 10 12 | --- 13 | spring: 14 | config: 15 | activate: 16 | on-profile: mysql 17 | datasource: 18 | url: "jdbc:mysql://${MYSQL_HOST:localhost}:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false" 19 | username: root 20 | password: root 21 | 22 | power4j: 23 | sequence: 24 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 25 | backend: mysql 26 | --- 27 | spring: 28 | config: 29 | activate: 30 | on-profile: postgresql 31 | datasource: 32 | url: "jdbc:postgresql://${POSTGRESQL_HOST:localhost}:5432/test?ssl=false" 33 | username: root 34 | password: root 35 | 36 | power4j: 37 | sequence: 38 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 39 | backend: postgresql 40 | --- 41 | spring: 42 | config: 43 | activate: 44 | on-profile: h2 45 | datasource: 46 | url: jdbc:h2:mem:test-db;MODE=MYSQL 47 | username: sa 48 | password: password 49 | driverClassName: org.h2.Driver 50 | 51 | power4j: 52 | sequence: 53 | # 数据同步使用的后端支持(如: mysql,oracle,redis) 54 | backend: mysql 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### gradle ### 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | 6 | ### STS ### 7 | .settings/ 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | bin/ 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | *.lock 22 | rebel.xml 23 | 24 | ### NetBeans ### 25 | nbproject/private/ 26 | build/ 27 | nbbuild/ 28 | nbdist/ 29 | .nb-gradle/ 30 | 31 | ### maven ### 32 | target/ 33 | *.war 34 | *.ear 35 | *.zip 36 | *.tar 37 | *.tar.gz 38 | !**/src/main/** 39 | !**/src/test/** 40 | !/.mvn/wrapper/maven-wrapper.jar 41 | 42 | ### logs #### 43 | /logs/ 44 | *.log 45 | 46 | ### temp ignore ### 47 | *.cache 48 | *.diff 49 | *.patch 50 | *.tmp 51 | *.java~ 52 | *.properties~ 53 | *.xml~ 54 | 55 | ### system ignore ### 56 | .DS_Store 57 | Thumbs.db 58 | Servers 59 | .metadata 60 | upload 61 | gen_code 62 | 63 | # BlueJ files 64 | *.ctxt 65 | 66 | ### node ### 67 | node_modules 68 | !yarn.lock 69 | !package.lock 70 | 71 | ###################################################################### 72 | # Others 73 | *.log 74 | rebel.xml 75 | *.xml.versionsBackup 76 | *.xml.releaseBackup 77 | pom.xml.next 78 | release.properties 79 | .metadata/ 80 | settings-ossrh.xml 81 | 82 | # Mobile Tools for Java (J2ME) 83 | .mtj.tmp/ 84 | 85 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 86 | hs_err_pid* 87 | -------------------------------------------------------------------------------- /examples/jdbc-example/src/main/java/com/power4j/sequence/example/SeqService.java: -------------------------------------------------------------------------------- 1 | package com.power4j.sequence.example; 2 | 3 | import com.power4j.kit.seq.core.SeqFormatter; 4 | import com.power4j.kit.seq.core.Sequence; 5 | import com.power4j.kit.seq.ext.SequenceRegistry; 6 | import com.power4j.kit.seq.persistent.Partitions; 7 | import com.power4j.kit.seq.persistent.SeqHolder; 8 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.function.Supplier; 13 | 14 | /** 15 | * @author CJ (power4j@outlook.com) 16 | * @date 2021/9/2 17 | * @since 1.0 18 | */ 19 | @Service 20 | @RequiredArgsConstructor 21 | public class SeqService { 22 | 23 | private final Supplier partitionFunc = Partitions.DAILY; 24 | 25 | private final SeqSynchronizer seqSynchronizer; 26 | 27 | private final SequenceRegistry> sequenceRegistry; 28 | 29 | private final static SeqFormatter MY_FORMATTER = (seqName, partition, value) -> String.format("%s-%s-%04d", seqName, 30 | partition, value); 31 | 32 | public String getForName(String name) { 33 | return sequenceRegistry.getOrRegister(name, this::createSequence).nextStr(); 34 | } 35 | 36 | private Sequence createSequence(String name) { 37 | return new SeqHolder(seqSynchronizer, name, partitionFunc, 1L, 100, MY_FORMATTER); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/PostgreSqlSynchronizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | 21 | public class PostgreSqlSynchronizerTest extends SynchronizerTestCase { 22 | 23 | public final static String SEQ_TABLE = "tb_seq"; 24 | 25 | private PostgreSqlSynchronizer createSeqSynchronizer() { 26 | 27 | PostgreSqlSynchronizer postgreSqlSynchronizer = new PostgreSqlSynchronizer(SEQ_TABLE, 28 | TestServices.getPostgreSqlDataSource()); 29 | postgreSqlSynchronizer.init(); 30 | return postgreSqlSynchronizer; 31 | } 32 | 33 | @Override 34 | protected SeqSynchronizer getSeqSynchronizer() { 35 | return createSeqSynchronizer(); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/ext/SequenceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.power4j.kit.seq.ext; 2 | 3 | import com.power4j.kit.seq.core.Sequence; 4 | 5 | import java.util.Optional; 6 | import java.util.function.Function; 7 | 8 | /** 9 | * Sequence 注册表 10 | * 11 | * @author CJ (power4j@outlook.com) 12 | * @date 2021/9/2 13 | * @since 1.0 14 | * @param 自增类型,比如 {@code Long} 15 | * @param Sequence 接口 16 | */ 17 | public interface SequenceRegistry> { 18 | 19 | /** 20 | * 注册 {@code Sequence} 对象,如果名称相同,则之前的对象被替换 21 | * @param name 名称 22 | * @param seq 当{@code Sequence} 23 | * @return 返回之前的{@code Sequence} 对象 24 | */ 25 | Optional register(String name, S seq); 26 | 27 | /** 28 | * 返回一个已经注册的 {@code Sequence} 对象 29 | * @param name 名称 30 | * @return 返回Sequence 对象,如果不存在则返回 {@code Optional.empty()} 31 | */ 32 | Optional get(String name); 33 | 34 | /** 35 | * 从注册表中移除已经注册的 {@code Sequence} 对象 36 | * @param name 名称 37 | * @return 返回被删除的对象,如果不存在则返回 {@code Optional.empty()} 38 | */ 39 | Optional remove(String name); 40 | 41 | /** 42 | * 返回一个已经注册的 {@code Sequence} 对象,如果不存在则将新的对象注册 43 | * @param name 名称 44 | * @param func 当不存在指定名称的{@code Sequence} 对象时,由该函数创建对象实例,该函数的入参为name 45 | * @return 返回{@code Sequence} 对象 46 | */ 47 | S getOrRegister(String name, Function func); 48 | 49 | /** 50 | * 统计数量 51 | * @return 返回数量 52 | */ 53 | int size(); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/RedisConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | /** 20 | * @author CJ (power4j@outlook.com) 21 | * @date 2020/7/13 22 | * @since 1.1 23 | */ 24 | public interface RedisConstants { 25 | 26 | // @formatter:off 27 | 28 | String KEY_DELIMITER = ":"; 29 | 30 | String UPDATE_SCRIPT = "" + 31 | "local key = KEYS[1]\n" + 32 | "local old_val = ARGV[1]\n" + 33 | "local new_val = ARGV[2]\n" + 34 | "\n" + 35 | "local val = redis.call(\"GET\", key)\n" + 36 | "if val == old_val then\n" + 37 | " redis.call(\"SET\", key, new_val)\n" + 38 | " return true\n" + 39 | "else\n" + 40 | " return false\n" + 41 | "end"; 42 | 43 | // @formatter:on 44 | 45 | } 46 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/TestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import lombok.experimental.UtilityClass; 20 | 21 | import java.time.LocalDateTime; 22 | import java.time.format.DateTimeFormatter; 23 | import java.util.concurrent.CountDownLatch; 24 | 25 | /** 26 | * @author CJ (power4j@outlook.com) 27 | * @date 2020/7/14 28 | * @since 1.0 29 | */ 30 | @UtilityClass 31 | public class TestUtil { 32 | 33 | public String strNow() { 34 | return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); 35 | } 36 | 37 | public void wait(CountDownLatch countDownLatch) { 38 | try { 39 | countDownLatch.await(); 40 | } 41 | catch (InterruptedException e) { 42 | Thread.currentThread().interrupt(); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/utils/EnvUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.utils; 18 | 19 | import lombok.experimental.UtilityClass; 20 | 21 | /** 22 | * @author CJ (power4j@outlook.com) 23 | * @date 2020/7/20 24 | * @since 1.0 25 | */ 26 | @UtilityClass 27 | public class EnvUtil { 28 | 29 | public static String getStr(String envKey, String defValue) { 30 | String val = System.getenv(envKey); 31 | return val == null ? defValue : val; 32 | } 33 | 34 | public static Integer getInt(String envKey, Integer defValue) { 35 | String val = getStr(envKey, null); 36 | if (val == null) { 37 | return defValue; 38 | } 39 | try { 40 | return Integer.parseInt(val); 41 | } 42 | catch (NumberFormatException e) { 43 | return defValue; 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /ossrh-settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | ossrh 8 | ${env.OSSRH_USERNAME} 9 | ${env.OSSRH_PASSWORD} 10 | 11 | 12 | 13 | 14 | 15 | aliyun-repo 16 | 17 | 18 | aliyun-plugin 19 | https://maven.aliyun.com/repository/public 20 | 21 | true 22 | 23 | 24 | true 25 | 26 | 27 | 28 | 29 | 30 | aliyun 31 | aliyun 32 | https://maven.aliyun.com/repository/public 33 | 34 | true 35 | 36 | 37 | true 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/oss-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Publish to the Maven Central 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - snapshot 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | oss-deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v3 18 | with: 19 | distribution: 'adopt' 20 | java-version: '17' 21 | cache: maven 22 | 23 | - name: Cache Maven packages 24 | uses: actions/cache@v1 25 | with: 26 | path: ~/.m2 27 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 28 | restore-keys: ${{ runner.os }}-m2 29 | 30 | - name: Import GPG 31 | uses: crazy-max/ghaction-import-gpg@v5 32 | with: 33 | gpg_private_key: ${{ secrets.MAVEN_GPG_KEY }} 34 | passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 35 | 36 | - name: List GPG Keys 37 | run: gpg -K 38 | 39 | - name: Publish to Maven Central 40 | run: | 41 | ./mvnw -v 42 | ./mvnw --settings ./ossrh-settings.xml clean deploy -Dgpg.passphrase=${MAVEN_GPG_PASSPHRASE} -DskipTests=true -P 'oss-release' 43 | env: 44 | MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} 45 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 46 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 47 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/Partitions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import java.time.LocalDate; 20 | import java.time.LocalDateTime; 21 | import java.time.format.DateTimeFormatter; 22 | import java.util.function.Supplier; 23 | 24 | /** 25 | * 预置动态分区方法 26 | * 27 | * @author CJ (power4j@outlook.com) 28 | * @date 2020/7/5 29 | * @since 1.0 30 | */ 31 | public interface Partitions { 32 | 33 | /** 34 | * 按年份分区 35 | */ 36 | Supplier ANNUALLY = () -> LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy")); 37 | 38 | /** 39 | * 按月份分区 40 | */ 41 | Supplier MONTHLY = () -> LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM")); 42 | 43 | /** 44 | * 按日期分区 45 | */ 46 | Supplier DAILY = () -> LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/AddState.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import lombok.AllArgsConstructor; 20 | import lombok.Data; 21 | 22 | /** 23 | * 加法操作执行结果 24 | * 25 | * @author CJ (power4j@outlook.com) 26 | * @date 2020/7/2 27 | * @since 1.0 28 | */ 29 | @Data 30 | @AllArgsConstructor 31 | public class AddState { 32 | 33 | /** 34 | * 执行结果 35 | */ 36 | private boolean success; 37 | 38 | /** 39 | * 前一个值 40 | */ 41 | private Long previous; 42 | 43 | /** 44 | * 当前值 45 | */ 46 | private Long current; 47 | 48 | /** 49 | * 操作次数 50 | */ 51 | private int totalOps; 52 | 53 | public static AddState fail(int totalOps) { 54 | return new AddState(false, null, null, totalOps); 55 | } 56 | 57 | public static AddState success(long previous, long current, int totalOps) { 58 | return new AddState(true, previous, current, totalOps); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/core/SeqPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.core; 18 | 19 | /** 20 | * 序号池 21 | * 22 | * @author CJ (power4j@outlook.com) 23 | * @date 2020/6/30 24 | * @since 1.0 25 | */ 26 | public interface SeqPool> extends Sequence { 27 | 28 | /** 29 | * 查看当前计数器的值,注意此方法不能保证取值的有效性 30 | * @return 31 | */ 32 | T peek(); 33 | 34 | /** 35 | * 是否还能取值 36 | * @return 37 | */ 38 | boolean hasMore(); 39 | 40 | /** 41 | * 分离,得到一个新的{@code SeqPool},其起始值就是本实例的当前值 42 | * @param name 新实例的名称 43 | * @return 44 | */ 45 | S fork(String name); 46 | 47 | /** 48 | * 号池中的剩余序号数量 49 | * @return 50 | */ 51 | long remaining(); 52 | 53 | /** 54 | * 号池的容量(容量与用量无关) 55 | * @return 56 | */ 57 | long capacity(); 58 | 59 | /** 60 | * 最小值 61 | * @return 62 | */ 63 | T minValue(); 64 | 65 | /** 66 | * 最大值 67 | * @return 68 | */ 69 | T maxValue(); 70 | 71 | } 72 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence 23 | ${revision} 24 | 25 | 26 | sequence-examples 27 | ${parent.artifactId} 28 | pom 29 | 30 | 31 | true 32 | true 33 | 34 | 35 | 36 | jdbc-example 37 | redis-example 38 | mongo-example 39 | actuator-example 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/jdbc-example/src/main/java/com/power4j/sequence/example/SeqConfig.java: -------------------------------------------------------------------------------- 1 | package com.power4j.sequence.example; 2 | 3 | import com.power4j.kit.seq.core.SeqFormatter; 4 | import com.power4j.kit.seq.core.Sequence; 5 | import com.power4j.kit.seq.persistent.Partitions; 6 | import com.power4j.kit.seq.persistent.SeqHolder; 7 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 8 | import com.power4j.kit.seq.spring.boot.autoconfigure.SequenceProperties; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | /** 14 | * @author CJ (power4j@outlook.com) 15 | * @date 2021/7/19 16 | * @since 1.0 17 | */ 18 | @Slf4j 19 | @Configuration 20 | public class SeqConfig { 21 | 22 | private final static SeqFormatter MY_FORMATTER = (seqName, partition, value) -> partition + "批次,第" + value + "号"; 23 | 24 | @Bean 25 | public Sequence sequence(SequenceProperties sequenceProperties, SeqSynchronizer seqSynchronizer) { 26 | 27 | // @formatter:off 28 | 29 | log.info("开始自定义配置,底层实现为:{}", seqSynchronizer.getClass().getSimpleName()); 30 | // Partitions.MONTHLY 是分区函数,它返回分区的名称,在同一个分区中,序号自增(但不保证连续,比如服务重启,因为应用层是批量取号) 31 | // MY_FORMATTER 是格式化函数,应用层可以根据 seqName, partition, value 自定义输出 32 | 33 | return SeqHolder.builder() 34 | .name(sequenceProperties.getName()) 35 | .synchronizer(seqSynchronizer) 36 | .partitionFunc(Partitions.MONTHLY) 37 | .initValue(sequenceProperties.getStartValue()) 38 | .poolSize(sequenceProperties.getFetchSize()) 39 | .seqFormatter(MY_FORMATTER) 40 | .build(); 41 | 42 | // @formatter:on 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/SimpleLettuceSynchronizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | import io.lettuce.core.RedisClient; 21 | import org.junit.After; 22 | 23 | public class SimpleLettuceSynchronizerTest extends SynchronizerTestCase { 24 | 25 | public final static String SEQ_CACHE_NAME = "power4j:seq-test"; 26 | 27 | private RedisClient redisClient; 28 | 29 | private SimpleLettuceSynchronizer createSeqSynchronizer() { 30 | 31 | redisClient = TestServices.getRedisClient(); 32 | SimpleLettuceSynchronizer simpleLettuceSynchronizer = new SimpleLettuceSynchronizer(SEQ_CACHE_NAME, 33 | redisClient); 34 | simpleLettuceSynchronizer.init(); 35 | return simpleLettuceSynchronizer; 36 | } 37 | 38 | @After 39 | public void tearDown() { 40 | if (redisClient != null) { 41 | redisClient.shutdown(); 42 | } 43 | } 44 | 45 | @Override 46 | protected SeqSynchronizer getSeqSynchronizer() { 47 | return createSeqSynchronizer(); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/core/SeqFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.core; 18 | 19 | /** 20 | * 格式化函数 21 | * 22 | * @author CJ (power4j@outlook.com) 23 | * @date 2020/7/3 24 | * @since 1.0 25 | */ 26 | @FunctionalInterface 27 | public interface SeqFormatter { 28 | 29 | /** 30 | * 默认格式: 分区+序号值 31 | */ 32 | SeqFormatter DEFAULT_FORMAT = (seqName, partition, value) -> String.format("%s%08d", partition, value); 33 | 34 | /** 35 | * 适用于按年分区 36 | */ 37 | SeqFormatter ANNUALLY_FORMAT = (seqName, partition, value) -> String.format("%s%10d", partition, value); 38 | 39 | /** 40 | * 适用于按月分区 41 | */ 42 | SeqFormatter MONTHLY_FORMAT = (seqName, partition, value) -> String.format("%s%08d", partition, value); 43 | 44 | /** 45 | * 适用于按日分区 46 | */ 47 | SeqFormatter DAILY_FORMAT = (seqName, partition, value) -> String.format("%s%06d", partition, value); 48 | 49 | /** 50 | * 格式化 51 | * @param seqName 52 | * @param partition 53 | * @param value 54 | * @return 55 | */ 56 | String format(String seqName, String partition, long value); 57 | 58 | } 59 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/SimpleMongoSynchronizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.mongodb.client.MongoClient; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import org.junit.After; 22 | 23 | public class SimpleMongoSynchronizerTest extends SynchronizerTestCase { 24 | 25 | public final static String DB_NAME = "test"; 26 | 27 | public final static String COLL_NAME = "col_seq"; 28 | 29 | private MongoClient mongoClient; 30 | 31 | private SimpleMongoSynchronizer createSeqSynchronizer() { 32 | 33 | mongoClient = TestServices.getMongoClient(); 34 | SimpleMongoSynchronizer simpleMongoSynchronizer = new SimpleMongoSynchronizer(DB_NAME, COLL_NAME, mongoClient); 35 | simpleMongoSynchronizer.init(); 36 | return simpleMongoSynchronizer; 37 | } 38 | 39 | @After 40 | public void tearDown() { 41 | if (mongoClient != null) { 42 | mongoClient.close(); 43 | } 44 | } 45 | 46 | @Override 47 | protected SeqSynchronizer getSeqSynchronizer() { 48 | return createSeqSynchronizer(); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/core/Sequence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.core; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | 21 | import java.util.Optional; 22 | 23 | /** 24 | * 序号生成器 25 | * 26 | * @author CJ (power4j@outlook.com) 27 | * @date 2020/7/6 28 | * @since 1.0 29 | */ 30 | public interface Sequence { 31 | 32 | /** 33 | * 名称 34 | * @return 35 | */ 36 | String getName(); 37 | 38 | /** 39 | * 取值 40 | * @return 41 | * @throws SeqException 无法获得序号抛出异常 42 | */ 43 | T next() throws SeqException; 44 | 45 | /** 46 | * 取值 47 | * @return 无法获得序号返回 null 48 | */ 49 | Optional nextOpt(); 50 | 51 | /** 52 | * 返回经过格式化后字符串 53 | * @return 54 | * @throws SeqException 无法获得序号返回 null 55 | */ 56 | default Optional nextStrOpt() { 57 | return nextOpt().map(Object::toString); 58 | } 59 | 60 | /** 61 | * 返回经过格式化后字符串 62 | * @return 63 | * @throws SeqException 无法获得序号抛出异常 64 | */ 65 | default String nextStr() throws SeqException { 66 | return nextStrOpt().orElseThrow(() -> new SeqException("Nothing to offer")); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/H2SynchronizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | import org.junit.After; 21 | import org.junit.Before; 22 | 23 | /** 24 | * H2 测试 25 | *

26 | * 27 | * @author lishangbu 28 | * @date 2021/8/28 29 | * @since 1.5.0 30 | */ 31 | public class H2SynchronizerTest extends SynchronizerTestCase { 32 | 33 | public final static String SEQ_TABLE = "tb_seq"; 34 | 35 | private H2Synchronizer h2Synchronizer; 36 | 37 | private H2Synchronizer createSeqSynchronizer() { 38 | H2Synchronizer sqlSynchronizer = new H2Synchronizer(SEQ_TABLE, TestServices.getH2DataSource()); 39 | sqlSynchronizer.init(); 40 | return sqlSynchronizer; 41 | } 42 | 43 | @Before 44 | public void setUp() { 45 | h2Synchronizer = createSeqSynchronizer(); 46 | } 47 | 48 | @After 49 | public void tearDown() { 50 | if (h2Synchronizer != null) { 51 | h2Synchronizer.dropTable(); 52 | } 53 | } 54 | 55 | @Override 56 | protected SeqSynchronizer getSeqSynchronizer() { 57 | return h2Synchronizer; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | import org.junit.After; 21 | import org.junit.Before; 22 | 23 | /** 24 | * Mysql 测试 25 | *

26 | * 27 | * @author CJ (power4j@outlook.com) 28 | * @date 2020/7/3 29 | * @since 1.0 30 | */ 31 | public class MySqlSynchronizerTest extends SynchronizerTestCase { 32 | 33 | public final static String SEQ_TABLE = "tb_seq"; 34 | 35 | private MySqlSynchronizer mySqlSynchronizer; 36 | 37 | private MySqlSynchronizer createSeqSynchronizer() { 38 | MySqlSynchronizer sqlSynchronizer = new MySqlSynchronizer(SEQ_TABLE, TestServices.getMySqlDataSource()); 39 | sqlSynchronizer.init(); 40 | return sqlSynchronizer; 41 | } 42 | 43 | @Before 44 | public void setUp() { 45 | mySqlSynchronizer = createSeqSynchronizer(); 46 | } 47 | 48 | @After 49 | public void tearDown() { 50 | if (mySqlSynchronizer != null) { 51 | mySqlSynchronizer.dropTable(); 52 | } 53 | } 54 | 55 | @Override 56 | protected SeqSynchronizer getSeqSynchronizer() { 57 | return mySqlSynchronizer; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/MySqlSeqHolderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import com.power4j.kit.seq.TestUtil; 20 | import com.power4j.kit.seq.core.SeqFormatter; 21 | import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer; 22 | import com.power4j.kit.seq.persistent.provider.TestServices; 23 | import org.junit.After; 24 | 25 | public class MySqlSeqHolderTest extends SeqHolderTestCase { 26 | 27 | public final static String SEQ_TABLE = "tb_seq"; 28 | 29 | public final String seqName = "power4j_" + MySqlSeqHolderTest.class.getSimpleName(); 30 | 31 | public final String partition = TestUtil.strNow(); 32 | 33 | private MySqlSynchronizer seqSynchronizer; 34 | 35 | public SeqHolder createSeqHolder() { 36 | seqSynchronizer = new MySqlSynchronizer(SEQ_TABLE, TestServices.getMySqlDataSource()); 37 | seqSynchronizer.init(); 38 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT); 39 | holder.prepare(); 40 | return holder; 41 | } 42 | 43 | @After 44 | public void tearDown() { 45 | if (seqSynchronizer != null) { 46 | seqSynchronizer.dropTable(); 47 | } 48 | } 49 | 50 | @Override 51 | protected SeqHolder getSeqHolder() { 52 | return createSeqHolder(); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/LettuceSeqHolderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import com.power4j.kit.seq.TestUtil; 20 | import com.power4j.kit.seq.core.SeqFormatter; 21 | import com.power4j.kit.seq.persistent.provider.SimpleLettuceSynchronizer; 22 | import com.power4j.kit.seq.persistent.provider.TestServices; 23 | import io.lettuce.core.RedisClient; 24 | import org.junit.After; 25 | 26 | public class LettuceSeqHolderTest extends SeqHolderTestCase { 27 | 28 | public final static String SEQ_CACHE_NAME = "power4j:" + LettuceSeqHolderTest.class.getSimpleName(); 29 | 30 | public final String seqName = "seq_holder_test"; 31 | 32 | public final String partition = TestUtil.strNow(); 33 | 34 | private RedisClient redisClient; 35 | 36 | public SeqHolder createSeqHolder() { 37 | redisClient = TestServices.getRedisClient(); 38 | SimpleLettuceSynchronizer seqSynchronizer = new SimpleLettuceSynchronizer(SEQ_CACHE_NAME, redisClient); 39 | seqSynchronizer.init(); 40 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT); 41 | holder.prepare(); 42 | return holder; 43 | } 44 | 45 | @After 46 | public void tearDown() { 47 | if (redisClient != null) { 48 | redisClient.shutdown(); 49 | } 50 | } 51 | 52 | @Override 53 | protected SeqHolder getSeqHolder() { 54 | return createSeqHolder(); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /examples/actuator-example/src/main/java/com/power4j/sequence/example/SequenceActuatorExampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.sequence.example; 18 | 19 | import com.power4j.kit.seq.core.Sequence; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.SpringApplication; 22 | import org.springframework.boot.autoconfigure.SpringBootApplication; 23 | import org.springframework.web.bind.annotation.GetMapping; 24 | import org.springframework.web.bind.annotation.RequestParam; 25 | import org.springframework.web.bind.annotation.RestController; 26 | 27 | import java.util.ArrayList; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | @RestController 33 | @SpringBootApplication 34 | public class SequenceActuatorExampleApplication { 35 | 36 | @Autowired 37 | private Sequence sequence; 38 | 39 | public static void main(String[] args) { 40 | SpringApplication.run(SequenceActuatorExampleApplication.class, args); 41 | } 42 | 43 | @GetMapping("/") 44 | public Map getSequence(@RequestParam(required = false) Integer size) { 45 | size = (size == null || size <= 0) ? 10 : size; 46 | List list = new ArrayList<>(size); 47 | for (int i = 0; i < size; ++i) { 48 | list.add(sequence.nextStr()); 49 | } 50 | Map data = new HashMap<>(); 51 | data.put("seq", list); 52 | return data; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/PostgreSqlSeqHolderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import com.power4j.kit.seq.TestUtil; 20 | import com.power4j.kit.seq.core.SeqFormatter; 21 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer; 22 | import com.power4j.kit.seq.persistent.provider.TestServices; 23 | import org.junit.After; 24 | 25 | /** 26 | * @author CJ (power4j@outlook.com) 27 | * @date 2020/7/29 28 | * @since 1.3 29 | */ 30 | public class PostgreSqlSeqHolderTest extends SeqHolderTestCase { 31 | 32 | public final static String SEQ_TABLE = "tb_seq"; 33 | 34 | public final String seqName = "power4j_" + PostgreSqlSeqHolderTest.class.getSimpleName(); 35 | 36 | public final String partition = TestUtil.strNow(); 37 | 38 | private PostgreSqlSynchronizer seqSynchronizer; 39 | 40 | public SeqHolder createSeqHolder() { 41 | seqSynchronizer = new PostgreSqlSynchronizer(SEQ_TABLE, TestServices.getPostgreSqlDataSource()); 42 | seqSynchronizer.init(); 43 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT); 44 | holder.prepare(); 45 | return holder; 46 | } 47 | 48 | @After 49 | public void tearDown() { 50 | if (seqSynchronizer != null) { 51 | seqSynchronizer.dropTable(); 52 | } 53 | } 54 | 55 | @Override 56 | protected SeqHolder getSeqHolder() { 57 | return createSeqHolder(); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/H2SqlSeqHolderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import com.power4j.kit.seq.TestUtil; 20 | import com.power4j.kit.seq.core.SeqFormatter; 21 | import com.power4j.kit.seq.persistent.provider.H2Synchronizer; 22 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer; 23 | import com.power4j.kit.seq.persistent.provider.TestServices; 24 | import org.junit.After; 25 | 26 | /** 27 | * @author lishangbu 28 | * @date 2021/8/28 29 | * @since 1.5.0 30 | */ 31 | public class H2SqlSeqHolderTest extends SeqHolderTestCase { 32 | 33 | public final static String SEQ_TABLE = "tb_seq"; 34 | 35 | public final String seqName = "power4j_" + H2SqlSeqHolderTest.class.getSimpleName(); 36 | 37 | public final String partition = TestUtil.strNow(); 38 | 39 | private H2Synchronizer seqSynchronizer; 40 | 41 | public SeqHolder createSeqHolder() { 42 | seqSynchronizer = new H2Synchronizer(SEQ_TABLE, TestServices.getH2DataSource()); 43 | seqSynchronizer.init(); 44 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT); 45 | holder.prepare(); 46 | return holder; 47 | } 48 | 49 | @After 50 | public void tearDown() { 51 | if (seqSynchronizer != null) { 52 | seqSynchronizer.dropTable(); 53 | } 54 | } 55 | 56 | @Override 57 | protected SeqHolder getSeqHolder() { 58 | return createSeqHolder(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/actuator/EndpointAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure.actuator; 18 | 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; 21 | import org.springframework.boot.autoconfigure.AutoConfiguration; 22 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 23 | import org.springframework.context.ApplicationContext; 24 | import org.springframework.context.annotation.Bean; 25 | 26 | /** 27 | * 监控端点自动配置 28 | * 29 | * @author CJ (power4j@outlook.com) 30 | * @date 2020/9/26 31 | * @since 1.4 32 | */ 33 | @AutoConfiguration 34 | public class EndpointAutoConfiguration { 35 | 36 | @Autowired 37 | private ApplicationContext applicationContext; 38 | 39 | @Bean 40 | @ConditionalOnMissingBean(SequenceEndpoint.class) 41 | @ConditionalOnAvailableEndpoint(endpoint = SequenceEndpoint.class) 42 | public SequenceEndpoint sequenceEndpoint() { 43 | return new SequenceEndpoint(applicationContext); 44 | } 45 | 46 | @Bean 47 | @ConditionalOnMissingBean(SeqSynchronizerEndpoint.class) 48 | @ConditionalOnAvailableEndpoint(endpoint = SeqSynchronizerEndpoint.class) 49 | public SeqSynchronizerEndpoint seqSynchronizerEndpoint() { 50 | return new SeqSynchronizerEndpoint(applicationContext); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/LongSeqPoolBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import com.power4j.kit.seq.core.LongSeqPool; 20 | import org.openjdk.jmh.annotations.*; 21 | import org.openjdk.jmh.infra.Blackhole; 22 | import org.openjdk.jmh.runner.Runner; 23 | import org.openjdk.jmh.runner.options.Options; 24 | import org.openjdk.jmh.runner.options.OptionsBuilder; 25 | 26 | import java.util.concurrent.TimeUnit; 27 | 28 | /** 29 | * 单机序号池测试 30 | * 31 | * @author CJ (power4j@outlook.com) 32 | * @date 2020/7/3 33 | * @since 1.0 34 | */ 35 | @Fork(1) 36 | @State(Scope.Benchmark) 37 | @BenchmarkMode(Mode.Throughput) 38 | @Warmup(iterations = 1, time = 3) 39 | @Measurement(iterations = 3, time = 10) 40 | @OutputTimeUnit(TimeUnit.SECONDS) 41 | public class LongSeqPoolBench { 42 | 43 | private LongSeqPool longSeqPool; 44 | 45 | @Setup 46 | public void setup() { 47 | longSeqPool = LongSeqPool.forRange("longSeqPool", BenchParam.SEQ_INIT_VAL, Long.MAX_VALUE, false); 48 | } 49 | 50 | @Benchmark 51 | @Threads(1) 52 | public void testSingleThread(Blackhole bh) { 53 | bh.consume(longSeqPool.next()); 54 | } 55 | 56 | @Benchmark 57 | @Threads(4) 58 | public void test4Threads(Blackhole bh) { 59 | bh.consume(longSeqPool.next()); 60 | } 61 | 62 | public static void main(String[] args) throws Exception { 63 | Options opt = new OptionsBuilder().include(LongSeqPoolBench.class.getSimpleName()).build(); 64 | new Runner(opt).run(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/MongoSeqHolderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import com.mongodb.client.MongoClient; 20 | import com.power4j.kit.seq.TestUtil; 21 | import com.power4j.kit.seq.core.SeqFormatter; 22 | import com.power4j.kit.seq.persistent.provider.SimpleMongoSynchronizer; 23 | import com.power4j.kit.seq.persistent.provider.TestServices; 24 | import org.junit.After; 25 | 26 | /** 27 | * @author CJ (power4j@outlook.com) 28 | * @date 2020/7/20 29 | * @since 1.0 30 | */ 31 | public class MongoSeqHolderTest extends SeqHolderTestCase { 32 | 33 | public final static String DB_NAME = "seq_test"; 34 | 35 | public final static String COL_NAME = "power4j_" + MongoSeqHolderTest.class.getSimpleName(); 36 | 37 | public final String seqName = "holder_test"; 38 | 39 | public final String partition = TestUtil.strNow(); 40 | 41 | private MongoClient mongoClient; 42 | 43 | public SeqHolder createSeqHolder() { 44 | mongoClient = TestServices.getMongoClient(); 45 | SimpleMongoSynchronizer seqSynchronizer = new SimpleMongoSynchronizer(DB_NAME, COL_NAME, mongoClient); 46 | seqSynchronizer.init(); 47 | SeqHolder holder = new SeqHolder(seqSynchronizer, seqName, partition, 1L, 1000, SeqFormatter.DEFAULT_FORMAT); 48 | holder.prepare(); 49 | return holder; 50 | } 51 | 52 | @After 53 | public void tearDown() { 54 | if (mongoClient != null) { 55 | mongoClient.close(); 56 | } 57 | } 58 | 59 | @Override 60 | protected SeqHolder getSeqHolder() { 61 | return createSeqHolder(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/ext/InMemorySequenceRegistry.java: -------------------------------------------------------------------------------- 1 | package com.power4j.kit.seq.ext; 2 | 3 | import com.power4j.kit.seq.core.Sequence; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.concurrent.locks.Lock; 10 | import java.util.concurrent.locks.ReentrantReadWriteLock; 11 | import java.util.function.Function; 12 | 13 | /** 14 | * @author CJ (power4j@outlook.com) 15 | * @date 2021/9/2 16 | * @since 1.0 17 | */ 18 | public class InMemorySequenceRegistry> implements SequenceRegistry { 19 | 20 | private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); 21 | 22 | private final Lock rLock = rwLock.readLock(); 23 | 24 | private final Lock wLock = rwLock.writeLock(); 25 | 26 | /** 27 | * Guard with rwLock,So we don't need ConcurrentHashMap. TODO: GC 28 | */ 29 | private final Map map = new HashMap<>(8); 30 | 31 | @Override 32 | public Optional register(String name, S seq) { 33 | assert Objects.nonNull(seq); 34 | wLock.lock(); 35 | try { 36 | return Optional.ofNullable(map.put(name, seq)); 37 | } 38 | finally { 39 | wLock.unlock(); 40 | } 41 | } 42 | 43 | @Override 44 | public Optional get(String name) { 45 | rLock.lock(); 46 | try { 47 | return Optional.ofNullable(map.get(name)); 48 | } 49 | finally { 50 | rLock.unlock(); 51 | } 52 | } 53 | 54 | @Override 55 | public Optional remove(String name) { 56 | wLock.lock(); 57 | try { 58 | return Optional.ofNullable(map.remove(name)); 59 | } 60 | finally { 61 | wLock.unlock(); 62 | } 63 | } 64 | 65 | @Override 66 | public S getOrRegister(String name, Function func) { 67 | S seq = get(name).orElse(null); 68 | if (Objects.isNull(seq)) { 69 | wLock.lock(); 70 | try { 71 | seq = map.get(name); 72 | if (Objects.isNull(seq)) { 73 | seq = func.apply(name); 74 | assert Objects.nonNull(seq); 75 | map.put(name, seq); 76 | } 77 | } 78 | finally { 79 | wLock.unlock(); 80 | } 81 | } 82 | return seq; 83 | } 84 | 85 | @Override 86 | public int size() { 87 | rLock.lock(); 88 | try { 89 | return map.size(); 90 | } 91 | finally { 92 | rLock.unlock(); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /examples/mongo-example/src/main/java/com/power4j/sequence/example/SequenceMongoExampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.sequence.example; 18 | 19 | import com.power4j.kit.seq.core.Sequence; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.SpringApplication; 23 | import org.springframework.boot.autoconfigure.SpringBootApplication; 24 | import org.springframework.web.bind.annotation.GetMapping; 25 | import org.springframework.web.bind.annotation.RequestParam; 26 | import org.springframework.web.bind.annotation.RestController; 27 | 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | @RestController 34 | @SpringBootApplication 35 | public class SequenceMongoExampleApplication { 36 | 37 | @Autowired 38 | private Sequence sequence; 39 | 40 | @Autowired 41 | private SeqSynchronizer seqSynchronizer; 42 | 43 | public static void main(String[] args) { 44 | SpringApplication.run(SequenceMongoExampleApplication.class, args); 45 | } 46 | 47 | @GetMapping("/") 48 | public Map getSequence(@RequestParam(required = false) Integer size) { 49 | size = (size == null || size <= 0) ? 10 : size; 50 | List list = new ArrayList<>(size); 51 | for (int i = 0; i < size; ++i) { 52 | list.add(sequence.nextStr()); 53 | } 54 | Map data = new HashMap<>(); 55 | data.put("query_count", Long.toString(seqSynchronizer.getQueryCounter())); 56 | data.put("update_count", Long.toString(seqSynchronizer.getUpdateCounter())); 57 | data.put("seq", list); 58 | return data; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /examples/redis-example/src/main/java/com/power4j/sequence/example/SequenceRedisExampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.sequence.example; 18 | 19 | import com.power4j.kit.seq.core.Sequence; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import org.springframework.beans.factory.annotation.Autowired; 22 | import org.springframework.boot.SpringApplication; 23 | import org.springframework.boot.autoconfigure.SpringBootApplication; 24 | import org.springframework.web.bind.annotation.GetMapping; 25 | import org.springframework.web.bind.annotation.RequestParam; 26 | import org.springframework.web.bind.annotation.RestController; 27 | 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.List; 31 | import java.util.Map; 32 | 33 | @RestController 34 | @SpringBootApplication 35 | public class SequenceRedisExampleApplication { 36 | 37 | @Autowired 38 | private Sequence sequence; 39 | 40 | @Autowired 41 | private SeqSynchronizer seqSynchronizer; 42 | 43 | public static void main(String[] args) { 44 | SpringApplication.run(SequenceRedisExampleApplication.class, args); 45 | } 46 | 47 | @GetMapping("/") 48 | public Map getSequence(@RequestParam(required = false) Integer size) { 49 | size = (size == null || size <= 0) ? 10 : size; 50 | List list = new ArrayList<>(size); 51 | for (int i = 0; i < size; ++i) { 52 | list.add(sequence.nextStr()); 53 | } 54 | Map data = new HashMap<>(); 55 | data.put("query_count", Long.toString(seqSynchronizer.getQueryCounter())); 56 | data.put("update_count", Long.toString(seqSynchronizer.getUpdateCounter())); 57 | data.put("seq", list); 58 | return data; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/actuator/SequenceEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure.actuator; 18 | 19 | import com.power4j.kit.seq.core.Sequence; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Getter; 22 | import org.springframework.beans.factory.SmartInitializingSingleton; 23 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 24 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 25 | import org.springframework.context.ApplicationContext; 26 | 27 | import java.util.List; 28 | import java.util.stream.Collectors; 29 | 30 | /** 31 | * Endpoint for {@link Sequence} 32 | * 33 | * @author CJ (power4j@outlook.com) 34 | * @date 2020/9/26 35 | * @since 1.4 36 | */ 37 | @Endpoint(id = "sequence") 38 | public class SequenceEndpoint implements SmartInitializingSingleton { 39 | 40 | private final ApplicationContext applicationContext; 41 | 42 | private List sequenceInfoList; 43 | 44 | public SequenceEndpoint(ApplicationContext applicationContext) { 45 | this.applicationContext = applicationContext; 46 | } 47 | 48 | @Override 49 | public void afterSingletonsInstantiated() { 50 | sequenceInfoList = applicationContext.getBeansOfType(Sequence.class) 51 | .entrySet() 52 | .stream() 53 | .map(kv -> new SequenceInfo(kv.getKey(), kv.getValue().getClass().getName(), kv.getValue().getName())) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | @ReadOperation 58 | public List sequenceBeans() { 59 | return sequenceInfoList; 60 | } 61 | 62 | @Getter 63 | @AllArgsConstructor 64 | public static class SequenceInfo { 65 | 66 | private final String beanName; 67 | 68 | private final String className; 69 | 70 | private final String seqName; 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/MongoSynchronizerConfigure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure; 18 | 19 | import com.mongodb.client.MongoClient; 20 | import com.mongodb.client.MongoClients; 21 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 22 | import com.power4j.kit.seq.persistent.provider.SimpleMongoSynchronizer; 23 | import org.springframework.boot.autoconfigure.AutoConfiguration; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 28 | import org.springframework.context.annotation.Bean; 29 | 30 | /** 31 | * @author CJ (power4j@outlook.com) 32 | * @date 2020/7/20 33 | * @since 1.0 34 | */ 35 | @AutoConfiguration 36 | @ConditionalOnClass(MongoClient.class) 37 | public class MongoSynchronizerConfigure { 38 | 39 | @Bean(destroyMethod = "close") 40 | @ConditionalOnMissingBean 41 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "mongo") 42 | public MongoClient mongoClient(SequenceProperties sequenceProperties) { 43 | return MongoClients.create(sequenceProperties.getMongoUri()); 44 | } 45 | 46 | @Bean 47 | @ConditionalOnMissingBean 48 | @ConditionalOnBean(MongoClient.class) 49 | public SeqSynchronizer mongoSynchronizer(SequenceProperties sequenceProperties, MongoClient mongoClient) { 50 | SimpleMongoSynchronizer synchronizer = new SimpleMongoSynchronizer("sequence", 51 | sequenceProperties.getTableName(), mongoClient); 52 | if (!sequenceProperties.isLazyInit()) { 53 | synchronizer.init(); 54 | } 55 | return synchronizer; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/SequenceProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure; 18 | 19 | import lombok.Data; 20 | import org.springframework.boot.context.properties.ConfigurationProperties; 21 | 22 | /** 23 | * @author CJ (power4j@outlook.com) 24 | * @date 2020/7/6 25 | * @since 1.0 26 | */ 27 | @Data 28 | @ConfigurationProperties(prefix = SequenceProperties.PREFIX) 29 | public class SequenceProperties { 30 | 31 | public final static String PREFIX = "power4j.sequence"; 32 | 33 | /** 34 | * 开关 35 | */ 36 | private boolean enabled = true; 37 | 38 | /** 39 | * 后端类型 40 | */ 41 | private BackendTypeEnum backend; 42 | 43 | /** 44 | * 懒加载 45 | */ 46 | private boolean lazyInit = false; 47 | 48 | /** 49 | * 表名称(对于Redis则表示缓存名称,对于MongoDB则是集合名称,以此类推) 50 | */ 51 | private String tableName = "seq_registry"; 52 | 53 | /** 54 | * 每次从后端取值的步进,这个值需要权衡性能和序号丢失 55 | */ 56 | private int fetchSize = 100; 57 | 58 | /** 59 | * 序号名称 60 | */ 61 | private String name = "seq"; 62 | 63 | /** 64 | * 起始值 65 | */ 66 | private long startValue = 1L; 67 | 68 | /** 69 | * Lettuce URI 70 | * https://lettuce.io/core/release/reference/index.html#redisuri.uri-syntax 71 | */ 72 | private String lettuceUri = "redis://localhost"; 73 | 74 | /** 75 | * MongoDB URI https://docs.mongodb.com/manual/reference/connection-string/ 76 | */ 77 | private String mongoUri = "mongodb://localhost"; 78 | 79 | public enum BackendTypeEnum { 80 | 81 | /** 82 | * MySQL 83 | */ 84 | mysql, 85 | /** 86 | * Redis 87 | */ 88 | redis, 89 | /** 90 | * Redis Cluster 91 | */ 92 | redisCluster, 93 | /** 94 | * MongoDB 95 | */ 96 | mongo, 97 | /** 98 | * PostgreSQL 99 | */ 100 | postgresql 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /examples/mongo-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence-examples 23 | ${revision} 24 | 25 | mongo-example 26 | mongo-example 27 | 28 | Sequence example with mongodb backend 29 | 30 | 31 | true 32 | true 33 | 34 | 35 | 36 | 37 | com.power4j.kit 38 | sequence-spring-boot-starter 39 | ${project.parent.version} 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | org.mongodb 47 | mongodb-driver-sync 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | org.junit.vintage 56 | junit-vintage-engine 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-maven-plugin 67 | ${spring-boot.version} 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/actuator/SeqSynchronizerEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure.actuator; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | import lombok.AllArgsConstructor; 21 | import lombok.Getter; 22 | import org.springframework.beans.factory.SmartInitializingSingleton; 23 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 24 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 25 | import org.springframework.context.ApplicationContext; 26 | 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.stream.Collectors; 30 | 31 | /** 32 | * Endpoint for {@link SeqSynchronizer} 33 | * 34 | * @author CJ (power4j@outlook.com) 35 | * @date 2020/9/26 36 | * @since 1.4 37 | */ 38 | @Endpoint(id = "sequence-synchronizer") 39 | public class SeqSynchronizerEndpoint implements SmartInitializingSingleton { 40 | 41 | private final ApplicationContext applicationContext; 42 | 43 | private Map synchronizerBeanMap; 44 | 45 | public SeqSynchronizerEndpoint(ApplicationContext applicationContext) { 46 | this.applicationContext = applicationContext; 47 | } 48 | 49 | @Override 50 | public void afterSingletonsInstantiated() { 51 | synchronizerBeanMap = applicationContext.getBeansOfType(SeqSynchronizer.class); 52 | } 53 | 54 | @ReadOperation 55 | public List synchronizerInfo() { 56 | 57 | // @formatter:off 58 | 59 | return synchronizerBeanMap.entrySet() 60 | .stream() 61 | .map(kv -> new SynchronizerInfo( 62 | kv.getKey(), 63 | kv.getValue().getClass().getName(), 64 | kv.getValue().getQueryCounter(), 65 | kv.getValue().getUpdateCounter())) 66 | .collect(Collectors.toList()); 67 | 68 | // @formatter:on 69 | } 70 | 71 | @Getter 72 | @AllArgsConstructor 73 | public static class SynchronizerInfo { 74 | 75 | private final String beanName; 76 | 77 | private final String className; 78 | 79 | private final Long queryCount; 80 | 81 | private final Long updateCount; 82 | 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /examples/redis-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence-examples 23 | ${revision} 24 | 25 | redis-example 26 | redis-example 27 | 28 | Sequence example with redis backend 29 | 30 | 31 | true 32 | true 33 | 34 | 35 | 36 | 37 | com.power4j.kit 38 | sequence-spring-boot-starter 39 | ${project.parent.version} 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | io.lettuce 47 | lettuce-core 48 | 49 | 50 | org.apache.commons 51 | commons-pool2 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | org.junit.vintage 60 | junit-vintage-engine 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-maven-plugin 71 | ${spring-boot.version} 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/JdbcSynchronizerConfigure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer; 21 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer; 22 | import org.springframework.boot.autoconfigure.AutoConfiguration; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 26 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 27 | import org.springframework.context.annotation.Bean; 28 | 29 | import javax.sql.DataSource; 30 | 31 | /** 32 | * JDBC 序号同步器配置 33 | * 34 | * @author CJ (power4j@outlook.com) 35 | * @date 2020/7/7 36 | * @since 1.0 37 | */ 38 | @AutoConfiguration(after = DataSourceAutoConfiguration.class) 39 | public class JdbcSynchronizerConfigure { 40 | 41 | @Bean 42 | @ConditionalOnMissingBean 43 | @ConditionalOnBean(DataSource.class) 44 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "mysql") 45 | public SeqSynchronizer mysqlSynchronizer(SequenceProperties sequenceProperties, DataSource dataSource) { 46 | MySqlSynchronizer synchronizer = new MySqlSynchronizer(sequenceProperties.getTableName(), dataSource); 47 | if (!sequenceProperties.isLazyInit()) { 48 | synchronizer.init(); 49 | } 50 | return synchronizer; 51 | } 52 | 53 | @Bean 54 | @ConditionalOnMissingBean 55 | @ConditionalOnBean(DataSource.class) 56 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "postgresql") 57 | public SeqSynchronizer postgreSqlSynchronizer(SequenceProperties sequenceProperties, DataSource dataSource) { 58 | PostgreSqlSynchronizer synchronizer = new PostgreSqlSynchronizer(sequenceProperties.getTableName(), dataSource); 59 | if (!sequenceProperties.isLazyInit()) { 60 | synchronizer.init(); 61 | } 62 | return synchronizer; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/SeqSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import java.util.Optional; 20 | 21 | /** 22 | * Seq记录同步接口
23 | *

    24 | *
  • Seq记录唯一性 = 名称 + 分区
  • 25 | *
  • 此接口的所有方法除非特别说明,默认必须提供线程安全保障
  • 26 | *
27 | * 28 | * @author CJ (power4j@outlook.com) 29 | * @date 2020/7/1 30 | * @since 1.0 31 | */ 32 | public interface SeqSynchronizer { 33 | 34 | /** 35 | * 创建序号记录,如已经存在则忽略 36 | * @param name 名称 37 | * @param partition 分区 38 | * @param nextValue 初始值 39 | * @return true 表示创建成功,false 表示记录已经存在 40 | */ 41 | boolean tryCreate(String name, String partition, long nextValue); 42 | 43 | /** 44 | * 尝试更新记录 45 | *

46 | * 此接口为可选实现接口 47 | *

48 | * @param name 49 | * @param partition 50 | * @param nextValueOld 51 | * @param nextValueNew 52 | * @return true 表示更新成功 53 | * @throws UnsupportedOperationException 不支持此方法 54 | */ 55 | boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew); 56 | 57 | /** 58 | * 尝试加法操作 59 | * @param name 60 | * @param partition 61 | * @param delta 加数 62 | * @param maxReTry 最大重试次数,小于0表示无限制. 0 表示重试零次(总共执行1次) 1 表示重试一次(总共执行2次)。此参数跟具体的实现层有关。 63 | * @return 返回执行加法操作执行结果 64 | */ 65 | AddState tryAddAndGet(String name, String partition, int delta, int maxReTry); 66 | 67 | /** 68 | * 查询当前值 69 | * @param name 70 | * @param partition 71 | * @return 返回null表示记录不存在 72 | */ 73 | Optional getNextValue(String name, String partition); 74 | 75 | /** 76 | * 执行初始化. 77 | *

78 | * 无线程线程安全保障,但是可以多次执行 79 | *

80 | * 。 81 | */ 82 | void init(); 83 | 84 | /** 85 | * 关闭,执行资源清理. 86 | *

87 | * 无线程线程安全保障,但是可以多次执行 88 | *

89 | * 。 90 | */ 91 | default void shutdown() { 92 | // do nothing 93 | } 94 | 95 | /** 96 | * 查询语句总共执行的次数 97 | * @return 98 | */ 99 | default long getQueryCounter() { 100 | return 0L; 101 | } 102 | 103 | /** 104 | * 更新语句总共执行的次数 105 | * @return 106 | */ 107 | default long getUpdateCounter() { 108 | return 0L; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/LettuceSeqHolderBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import com.power4j.kit.seq.persistent.SeqHolder; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import com.power4j.kit.seq.persistent.provider.SimpleLettuceSynchronizer; 22 | import com.power4j.kit.seq.utils.EnvUtil; 23 | import io.lettuce.core.RedisClient; 24 | import org.openjdk.jmh.annotations.*; 25 | import org.openjdk.jmh.infra.Blackhole; 26 | import org.openjdk.jmh.runner.Runner; 27 | import org.openjdk.jmh.runner.options.Options; 28 | import org.openjdk.jmh.runner.options.OptionsBuilder; 29 | 30 | import java.util.concurrent.TimeUnit; 31 | 32 | /** 33 | * Lettuce 后端性能测试 34 | * 35 | * @author CJ (power4j@outlook.com) 36 | * @date 2020/7/12 37 | * @since 1.1 38 | */ 39 | @Fork(1) 40 | @State(Scope.Benchmark) 41 | @BenchmarkMode(Mode.Throughput) 42 | @Warmup(iterations = 1, time = 3) 43 | @Measurement(iterations = 3, time = 10) 44 | @OutputTimeUnit(TimeUnit.SECONDS) 45 | public class LettuceSeqHolderBench { 46 | 47 | /** 48 | * redis://[password@]host [: port][/database] 49 | */ 50 | private final static String REDIS_URI = EnvUtil.getStr("TEST_REDIS_URI", "redis://127.0.0.1:6379"); 51 | 52 | private static SeqSynchronizer synchronizer; 53 | 54 | private static SeqHolder seqHolder; 55 | 56 | @Setup 57 | public void setup() { 58 | final String partition = TestUtil.getPartitionName(); 59 | RedisClient redisClient = RedisClient.create(REDIS_URI); 60 | 61 | synchronizer = new SimpleLettuceSynchronizer("lettuce_ben_test", redisClient); 62 | synchronizer.init(); 63 | seqHolder = new SeqHolder(synchronizer, "lettuce_ben_test", partition, BenchParam.SEQ_INIT_VAL, 64 | BenchParam.SEQ_POOL_SIZE, null); 65 | seqHolder.prepare(); 66 | } 67 | 68 | @Benchmark 69 | @Threads(1) 70 | public void testSingleThread(Blackhole bh) { 71 | bh.consume(seqHolder.next()); 72 | } 73 | 74 | @Benchmark 75 | @Threads(4) 76 | public void test4Threads(Blackhole bh) { 77 | bh.consume(seqHolder.next()); 78 | } 79 | 80 | public static void main(String[] args) throws Exception { 81 | Options opt = new OptionsBuilder().include(LettuceSeqHolderBench.class.getSimpleName()).build(); 82 | new Runner(opt).run(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/MongoSeqHolderBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import com.mongodb.client.MongoClient; 20 | import com.mongodb.client.MongoClients; 21 | import com.power4j.kit.seq.persistent.SeqHolder; 22 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 23 | import com.power4j.kit.seq.persistent.provider.SimpleMongoSynchronizer; 24 | import com.power4j.kit.seq.utils.EnvUtil; 25 | import org.openjdk.jmh.annotations.*; 26 | import org.openjdk.jmh.infra.Blackhole; 27 | import org.openjdk.jmh.runner.Runner; 28 | import org.openjdk.jmh.runner.options.Options; 29 | import org.openjdk.jmh.runner.options.OptionsBuilder; 30 | 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * @author CJ (power4j@outlook.com) 35 | * @date 2020/7/20 36 | * @since 1.0 37 | */ 38 | @Fork(1) 39 | @State(Scope.Benchmark) 40 | @BenchmarkMode(Mode.Throughput) 41 | @Warmup(iterations = 1, time = 3) 42 | @Measurement(iterations = 3, time = 10) 43 | @OutputTimeUnit(TimeUnit.SECONDS) 44 | public class MongoSeqHolderBench { 45 | 46 | /** 47 | * mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]] 48 | */ 49 | private final static String MONGO_URI = EnvUtil.getStr("TEST_MONGO_URI", "mongodb://127.0.0.1:27017"); 50 | 51 | private static SeqSynchronizer synchronizer; 52 | 53 | private static SeqHolder seqHolder; 54 | 55 | @Setup 56 | public void setup() { 57 | MongoClient mongoClient = MongoClients.create(MONGO_URI); 58 | synchronizer = new SimpleMongoSynchronizer("seq_bench", "seq_col", mongoClient); 59 | synchronizer.init(); 60 | seqHolder = new SeqHolder(synchronizer, "mongo-bench-test", TestUtil.getPartitionName(), 61 | BenchParam.SEQ_INIT_VAL, BenchParam.SEQ_POOL_SIZE, null); 62 | seqHolder.prepare(); 63 | } 64 | 65 | @Benchmark 66 | @Threads(1) 67 | public void testSingleThread(Blackhole bh) { 68 | bh.consume(seqHolder.next()); 69 | } 70 | 71 | @Benchmark 72 | @Threads(4) 73 | public void test4Threads(Blackhole bh) { 74 | bh.consume(seqHolder.next()); 75 | } 76 | 77 | public static void main(String[] args) throws Exception { 78 | Options opt = new OptionsBuilder().include(MongoSeqHolderBench.class.getSimpleName()).build(); 79 | new Runner(opt).run(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /examples/jdbc-example/src/main/java/com/power4j/sequence/example/SequenceJdbcExampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.sequence.example; 18 | 19 | import com.power4j.kit.seq.core.Sequence; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import lombok.RequiredArgsConstructor; 22 | import org.springframework.boot.SpringApplication; 23 | import org.springframework.boot.autoconfigure.SpringBootApplication; 24 | import org.springframework.web.bind.annotation.GetMapping; 25 | import org.springframework.web.bind.annotation.PathVariable; 26 | import org.springframework.web.bind.annotation.RequestParam; 27 | import org.springframework.web.bind.annotation.RestController; 28 | 29 | import java.util.ArrayList; 30 | import java.util.HashMap; 31 | import java.util.List; 32 | import java.util.Map; 33 | 34 | @RequiredArgsConstructor 35 | @RestController 36 | @SpringBootApplication 37 | public class SequenceJdbcExampleApplication { 38 | 39 | private final static int MAX_SEQ_NAME = 40; 40 | 41 | private final Sequence sequence; 42 | 43 | private final SeqSynchronizer seqSynchronizer; 44 | 45 | private final SeqService seqService; 46 | 47 | public static void main(String[] args) { 48 | SpringApplication.run(SequenceJdbcExampleApplication.class, args); 49 | } 50 | 51 | @GetMapping("/") 52 | public Map getSequence(@RequestParam(required = false) Integer size) { 53 | size = (size == null || size <= 0) ? 10 : size; 54 | List list = new ArrayList<>(size); 55 | for (int i = 0; i < size; ++i) { 56 | list.add(sequence.nextStr()); 57 | } 58 | Map data = new HashMap<>(); 59 | data.put("query_count", Long.toString(seqSynchronizer.getQueryCounter())); 60 | data.put("update_count", Long.toString(seqSynchronizer.getUpdateCounter())); 61 | data.put("seq", list); 62 | return data; 63 | } 64 | 65 | @GetMapping("/name/{name}") 66 | public List getSequence(@PathVariable String name, @RequestParam(required = false) Integer size) { 67 | size = (size == null || size <= 0) ? 10 : size; 68 | if (name.length() > MAX_SEQ_NAME) { 69 | name = name.substring(0, 40); 70 | } 71 | List data = new ArrayList<>(10); 72 | for (int i = 0; i < size; ++i) { 73 | data.add(seqService.getForName(name)); 74 | } 75 | return data; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/H2SeqHolderBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import com.power4j.kit.seq.persistent.SeqHolder; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import com.power4j.kit.seq.persistent.provider.H2Synchronizer; 22 | import com.power4j.kit.seq.utils.EnvUtil; 23 | import com.zaxxer.hikari.HikariConfig; 24 | import com.zaxxer.hikari.HikariDataSource; 25 | import org.openjdk.jmh.annotations.*; 26 | import org.openjdk.jmh.infra.Blackhole; 27 | import org.openjdk.jmh.runner.Runner; 28 | import org.openjdk.jmh.runner.options.Options; 29 | import org.openjdk.jmh.runner.options.OptionsBuilder; 30 | 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * 取号器,使用H2作为后端 35 | * 36 | * @author lishangbu 37 | * @date 2021/8/28 38 | * @since 1.5.0 39 | */ 40 | @Fork(1) 41 | @State(Scope.Benchmark) 42 | @BenchmarkMode(Mode.Throughput) 43 | @Warmup(iterations = 1, time = 3) 44 | @Measurement(iterations = 3, time = 10) 45 | @OutputTimeUnit(TimeUnit.SECONDS) 46 | public class H2SeqHolderBench { 47 | 48 | private final static String DEFAULT_H2_JDBC_URL = "jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1"; 49 | 50 | private final static String JDBC_URL = EnvUtil.getStr("TEST_H2_URL", DEFAULT_H2_JDBC_URL); 51 | 52 | private static SeqSynchronizer synchronizer; 53 | 54 | private static SeqHolder seqHolder; 55 | 56 | @Setup 57 | public void setup() { 58 | HikariConfig config = new HikariConfig(); 59 | config.setJdbcUrl(JDBC_URL); 60 | config.setUsername(EnvUtil.getStr("TEST_H2_USER", "sa")); 61 | config.setPassword(EnvUtil.getStr("TEST_H2_PWD", "")); 62 | synchronizer = new H2Synchronizer("seq_bench", new HikariDataSource(config)); 63 | synchronizer.init(); 64 | seqHolder = new SeqHolder(synchronizer, "h2-bench-test", TestUtil.getPartitionName(), BenchParam.SEQ_INIT_VAL, 65 | BenchParam.SEQ_POOL_SIZE, null); 66 | seqHolder.prepare(); 67 | } 68 | 69 | @Benchmark 70 | @Threads(1) 71 | public void testSingleThread(Blackhole bh) { 72 | bh.consume(seqHolder.next()); 73 | } 74 | 75 | @Benchmark 76 | @Threads(4) 77 | public void test4Threads(Blackhole bh) { 78 | bh.consume(seqHolder.next()); 79 | } 80 | 81 | public static void main(String[] args) throws Exception { 82 | Options opt = new OptionsBuilder().include(H2SeqHolderBench.class.getSimpleName()).build(); 83 | new Runner(opt).run(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/PostgreSqlSeqHolderBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import com.power4j.kit.seq.persistent.SeqHolder; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import com.power4j.kit.seq.persistent.provider.PostgreSqlSynchronizer; 22 | import com.power4j.kit.seq.utils.EnvUtil; 23 | import com.zaxxer.hikari.HikariConfig; 24 | import com.zaxxer.hikari.HikariDataSource; 25 | import org.openjdk.jmh.annotations.*; 26 | import org.openjdk.jmh.infra.Blackhole; 27 | import org.openjdk.jmh.runner.Runner; 28 | import org.openjdk.jmh.runner.options.Options; 29 | import org.openjdk.jmh.runner.options.OptionsBuilder; 30 | 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * @author CJ (power4j@outlook.com) 35 | * @date 2020/7/29 36 | * @since 1.3 37 | */ 38 | @Fork(1) 39 | @State(Scope.Benchmark) 40 | @BenchmarkMode(Mode.Throughput) 41 | @Warmup(iterations = 1, time = 3) 42 | @Measurement(iterations = 3, time = 10) 43 | @OutputTimeUnit(TimeUnit.SECONDS) 44 | public class PostgreSqlSeqHolderBench { 45 | 46 | private final static String DEFAULT_POSTGRESQL_JDBC_URL = "jdbc:postgresql://127.0.0.1:5432/test?ssl=false"; 47 | 48 | private final static String JDBC_URL = EnvUtil.getStr("TEST_POSTGRESQL_URL", DEFAULT_POSTGRESQL_JDBC_URL); 49 | 50 | private static SeqSynchronizer synchronizer; 51 | 52 | private static SeqHolder seqHolder; 53 | 54 | @Setup 55 | public void setup() { 56 | HikariConfig config = new HikariConfig(); 57 | config.setJdbcUrl(JDBC_URL); 58 | config.setUsername(EnvUtil.getStr("TEST_POSTGRESQL_USER", "root")); 59 | config.setPassword(EnvUtil.getStr("TEST_POSTGRESQL_PWD", "root")); 60 | synchronizer = new PostgreSqlSynchronizer("seq_bench", new HikariDataSource(config)); 61 | synchronizer.init(); 62 | seqHolder = new SeqHolder(synchronizer, "postgresql-bench-test", TestUtil.getPartitionName(), 63 | BenchParam.SEQ_INIT_VAL, BenchParam.SEQ_POOL_SIZE, null); 64 | seqHolder.prepare(); 65 | } 66 | 67 | @Benchmark 68 | @Threads(1) 69 | public void testSingleThread(Blackhole bh) { 70 | bh.consume(seqHolder.next()); 71 | } 72 | 73 | @Benchmark 74 | @Threads(4) 75 | public void test4Threads(Blackhole bh) { 76 | bh.consume(seqHolder.next()); 77 | } 78 | 79 | public static void main(String[] args) throws Exception { 80 | Options opt = new OptionsBuilder().include(PostgreSqlSeqHolderBench.class.getSimpleName()).build(); 81 | new Runner(opt).run(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /examples/actuator-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence-examples 23 | ${revision} 24 | 25 | actuator-example 26 | actuator-example 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | 34 | 35 | com.power4j.kit 36 | sequence-spring-boot-starter 37 | ${project.parent.version} 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-jdbc 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-actuator 50 | 51 | 52 | com.mysql 53 | mysql-connector-j 54 | runtime 55 | 56 | 57 | org.postgresql 58 | postgresql 59 | runtime 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | org.junit.vintage 68 | junit-vintage-engine 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-maven-plugin 79 | ${spring-boot.version} 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /bench-test/src/main/java/com/power4j/kit/seq/MySqlSeqHolderBench.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq; 18 | 19 | import com.power4j.kit.seq.persistent.SeqHolder; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import com.power4j.kit.seq.persistent.provider.MySqlSynchronizer; 22 | import com.power4j.kit.seq.utils.EnvUtil; 23 | import com.zaxxer.hikari.HikariConfig; 24 | import com.zaxxer.hikari.HikariDataSource; 25 | import org.openjdk.jmh.annotations.*; 26 | import org.openjdk.jmh.infra.Blackhole; 27 | import org.openjdk.jmh.runner.Runner; 28 | import org.openjdk.jmh.runner.options.Options; 29 | import org.openjdk.jmh.runner.options.OptionsBuilder; 30 | 31 | import java.util.concurrent.TimeUnit; 32 | 33 | /** 34 | * 取号器,使用MysSql作为后端 35 | * 36 | * @author CJ (power4j@outlook.com) 37 | * @date 2020/7/4 38 | * @since 1.0 39 | */ 40 | @Fork(1) 41 | @State(Scope.Benchmark) 42 | @BenchmarkMode(Mode.Throughput) 43 | @Warmup(iterations = 1, time = 3) 44 | @Measurement(iterations = 3, time = 10) 45 | @OutputTimeUnit(TimeUnit.SECONDS) 46 | public class MySqlSeqHolderBench { 47 | 48 | private final static String DEFAULT_MYSQL_JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"; 49 | 50 | private final static String JDBC_URL = EnvUtil.getStr("TEST_MYSQL_URL", DEFAULT_MYSQL_JDBC_URL); 51 | 52 | private static SeqSynchronizer synchronizer; 53 | 54 | private static SeqHolder seqHolder; 55 | 56 | @Setup 57 | public void setup() { 58 | HikariConfig config = new HikariConfig(); 59 | config.setJdbcUrl(JDBC_URL); 60 | config.setUsername(EnvUtil.getStr("TEST_MYSQL_USER", "root")); 61 | config.setPassword(EnvUtil.getStr("TEST_MYSQL_PWD", "root")); 62 | synchronizer = new MySqlSynchronizer("seq_bench", new HikariDataSource(config)); 63 | synchronizer.init(); 64 | seqHolder = new SeqHolder(synchronizer, "mysql-bench-test", TestUtil.getPartitionName(), 65 | BenchParam.SEQ_INIT_VAL, BenchParam.SEQ_POOL_SIZE, null); 66 | seqHolder.prepare(); 67 | } 68 | 69 | @Benchmark 70 | @Threads(1) 71 | public void testSingleThread(Blackhole bh) { 72 | bh.consume(seqHolder.next()); 73 | } 74 | 75 | @Benchmark 76 | @Threads(4) 77 | public void test4Threads(Blackhole bh) { 78 | bh.consume(seqHolder.next()); 79 | } 80 | 81 | public static void main(String[] args) throws Exception { 82 | Options opt = new OptionsBuilder().include(MySqlSeqHolderBench.class.getSimpleName()).build(); 83 | new Runner(opt).run(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /examples/jdbc-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence-examples 23 | ${revision} 24 | 25 | jdbc-example 26 | jdbc-example 27 | 28 | Sequence example with JDBC backend 29 | 30 | 31 | true 32 | true 33 | 34 | 35 | 36 | 37 | com.power4j.kit 38 | sequence-spring-boot-starter 39 | ${project.parent.version} 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-jdbc 48 | 49 | 50 | com.mysql 51 | mysql-connector-j 52 | runtime 53 | 54 | 55 | org.postgresql 56 | postgresql 57 | runtime 58 | 59 | 60 | com.h2database 61 | h2 62 | runtime 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | org.junit.vintage 71 | junit-vintage-engine 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-maven-plugin 82 | ${spring-boot.version} 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/SequenceAutoConfigure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure; 18 | 19 | import com.power4j.kit.seq.core.SeqFormatter; 20 | import com.power4j.kit.seq.core.Sequence; 21 | import com.power4j.kit.seq.ext.InMemorySequenceRegistry; 22 | import com.power4j.kit.seq.ext.SequenceRegistry; 23 | import com.power4j.kit.seq.persistent.Partitions; 24 | import com.power4j.kit.seq.persistent.SeqHolder; 25 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 26 | import lombok.extern.slf4j.Slf4j; 27 | import org.springframework.boot.autoconfigure.AutoConfiguration; 28 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 30 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 31 | import org.springframework.context.annotation.Bean; 32 | import org.springframework.context.annotation.Import; 33 | 34 | /** 35 | * 自动配置 36 | * 37 | * @author CJ (power4j@outlook.com) 38 | * @date 2020/7/6 39 | * @since 1.0 40 | */ 41 | @Slf4j 42 | @AutoConfiguration 43 | @EnableConfigurationProperties(SequenceProperties.class) 44 | @Import({ JdbcSynchronizerConfigure.class, RedisSynchronizerConfigure.class }) 45 | public class SequenceAutoConfigure { 46 | 47 | @Bean 48 | @ConditionalOnMissingBean(value = Long.class, parameterizedContainer = Sequence.class) 49 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "enabled", havingValue = "true", 50 | matchIfMissing = true) 51 | public Sequence sequence(SequenceProperties sequenceProperties, SeqSynchronizer seqSynchronizer) { 52 | log.info("Sequence create,Using {}", seqSynchronizer.getClass().getSimpleName()); 53 | // 按月分区:即每个月有 Long.MAX 个序号可用 54 | 55 | // @formatter:off 56 | 57 | return SeqHolder.builder() 58 | .name(sequenceProperties.getName()) 59 | .synchronizer(seqSynchronizer) 60 | .partitionFunc(Partitions.MONTHLY) 61 | .initValue(sequenceProperties.getStartValue()) 62 | .poolSize(sequenceProperties.getFetchSize()) 63 | .seqFormatter(SeqFormatter.DEFAULT_FORMAT) 64 | .build(); 65 | 66 | // @formatter:on 67 | 68 | } 69 | 70 | @Bean 71 | @ConditionalOnMissingBean(SequenceRegistry.class) 72 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "enabled", havingValue = "true", 73 | matchIfMissing = true) 74 | public SequenceRegistry> sequenceRegistry() { 75 | return new InMemorySequenceRegistry<>(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/SeqHolderTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent; 18 | 19 | import com.power4j.kit.seq.TestUtil; 20 | import lombok.extern.slf4j.Slf4j; 21 | import org.junit.Assert; 22 | import org.junit.Test; 23 | 24 | import java.time.Duration; 25 | import java.time.Instant; 26 | import java.util.HashSet; 27 | import java.util.Set; 28 | import java.util.concurrent.CompletableFuture; 29 | import java.util.concurrent.CountDownLatch; 30 | import java.util.concurrent.ExecutorService; 31 | import java.util.concurrent.Executors; 32 | import java.util.concurrent.atomic.AtomicLong; 33 | 34 | /** 35 | * @author CJ (power4j@outlook.com) 36 | * @date 2020/7/14 37 | * @since 1.0 38 | */ 39 | @Slf4j 40 | public abstract class SeqHolderTestCase { 41 | 42 | protected abstract SeqHolder getSeqHolder(); 43 | 44 | @Test 45 | public void getValueTest() { 46 | final SeqHolder holder = getSeqHolder(); 47 | final int threads = 4; 48 | final int loops = 20000; 49 | CountDownLatch threadReady = new CountDownLatch(threads); 50 | CountDownLatch threadDone = new CountDownLatch(threads); 51 | AtomicLong opCount = new AtomicLong(); 52 | ExecutorService executorService = Executors.newFixedThreadPool(threads); 53 | log.info(String.format("start test loops = %d threads = %d", loops, threads)); 54 | Instant startTime = Instant.now(); 55 | for (int t = 0; t < threads; ++t) { 56 | CompletableFuture.runAsync(() -> { 57 | threadReady.countDown(); 58 | Set dataSet = new HashSet<>(loops); 59 | TestUtil.wait(threadReady); 60 | 61 | for (int i = 0; i < loops; ++i) { 62 | if (i % 5000 == 0) { 63 | log.info(String.format("[thread %s] test running [%d / %d]", Thread.currentThread().getName(), 64 | i, loops)); 65 | } 66 | long val = holder.next(); 67 | dataSet.add(val); 68 | } 69 | int size = dataSet.size(); 70 | dataSet.clear(); 71 | opCount.addAndGet(size); 72 | threadDone.countDown(); 73 | log.info(String.format("[thread %s] [done] dataset size = %08d", Thread.currentThread().getName(), 74 | size)); 75 | Assert.assertEquals(size, loops); 76 | }, executorService).exceptionally(e -> { 77 | threadDone.countDown(); 78 | e.printStackTrace(); 79 | return null; 80 | }); 81 | } 82 | 83 | TestUtil.wait(threadDone); 84 | long timeCost = Duration.between(startTime, Instant.now()).toMillis(); 85 | log.info(String.format("test end,opCount = %d,time cost = %d ms", opCount.get(), timeCost)); 86 | Assert.assertEquals(opCount.get(), loops * threads); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/src/main/java/com/power4j/kit/seq/spring/boot/autoconfigure/RedisSynchronizerConfigure.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.spring.boot.autoconfigure; 18 | 19 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 20 | import com.power4j.kit.seq.persistent.provider.LettuceClusterSynchronizer; 21 | import com.power4j.kit.seq.persistent.provider.SimpleLettuceSynchronizer; 22 | import io.lettuce.core.RedisClient; 23 | import io.lettuce.core.RedisURI; 24 | import io.lettuce.core.cluster.RedisClusterClient; 25 | import org.springframework.boot.autoconfigure.AutoConfiguration; 26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 27 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 28 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 29 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 30 | import org.springframework.context.annotation.Bean; 31 | 32 | /** 33 | * @author CJ (power4j@outlook.com) 34 | * @date 2020/7/13 35 | * @since 1.0 36 | */ 37 | @AutoConfiguration 38 | @ConditionalOnClass({ RedisClient.class, RedisClusterClient.class }) 39 | public class RedisSynchronizerConfigure { 40 | 41 | @Bean(destroyMethod = "shutdown") 42 | @ConditionalOnMissingBean 43 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "redis") 44 | public RedisClient redisClient(SequenceProperties sequenceProperties) { 45 | RedisURI redisUri = RedisURI.create(sequenceProperties.getLettuceUri()); 46 | return RedisClient.create(redisUri); 47 | } 48 | 49 | @Bean(destroyMethod = "shutdown") 50 | @ConditionalOnMissingBean 51 | @ConditionalOnProperty(prefix = SequenceProperties.PREFIX, name = "backend", havingValue = "redis-cluster") 52 | public RedisClusterClient redisClusterClient(SequenceProperties sequenceProperties) { 53 | RedisURI redisUri = RedisURI.create(sequenceProperties.getLettuceUri()); 54 | return RedisClusterClient.create(redisUri); 55 | } 56 | 57 | @Bean 58 | @ConditionalOnMissingBean 59 | @ConditionalOnBean(RedisClient.class) 60 | public SeqSynchronizer redisSynchronizer(SequenceProperties sequenceProperties, RedisClient redisClient) { 61 | SimpleLettuceSynchronizer synchronizer = new SimpleLettuceSynchronizer(sequenceProperties.getTableName(), 62 | redisClient); 63 | if (!sequenceProperties.isLazyInit()) { 64 | synchronizer.init(); 65 | } 66 | return synchronizer; 67 | } 68 | 69 | @Bean 70 | @ConditionalOnMissingBean 71 | @ConditionalOnBean(RedisClusterClient.class) 72 | public SeqSynchronizer redisClusterSynchronizer(SequenceProperties sequenceProperties, 73 | RedisClusterClient redisClusterClient) { 74 | LettuceClusterSynchronizer synchronizer = new LettuceClusterSynchronizer(sequenceProperties.getTableName(), 75 | redisClusterClient); 76 | if (!sequenceProperties.isLazyInit()) { 77 | synchronizer.init(); 78 | } 79 | return synchronizer; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/H2Synchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import lombok.AllArgsConstructor; 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | import javax.sql.DataSource; 25 | import java.sql.Connection; 26 | 27 | /** 28 | * H2数据源支持 29 | * 30 | * @author lishangbu 31 | * @date 2021/8/28 32 | * @since 1.5.0 33 | */ 34 | @Slf4j 35 | @AllArgsConstructor 36 | public class H2Synchronizer extends AbstractSqlStatementProvider implements SeqSynchronizer { 37 | 38 | // @formatter:off 39 | 40 | private final static String H2_CREATE_TABLE = 41 | "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" + 42 | "seq_name VARCHAR ( 255 ) NOT NULL," + 43 | "seq_partition VARCHAR ( 255 ) NOT NULL," + 44 | "seq_next_value BIGINT NOT NULL," + 45 | "seq_create_time TIMESTAMP NOT NULL," + 46 | "seq_update_time TIMESTAMP NULL," + 47 | "PRIMARY KEY ( `seq_name`, `seq_partition` ) " + 48 | ")"; 49 | 50 | private final static String H2_DROP_TABLE = "DROP TABLE IF EXISTS $TABLE_NAME"; 51 | 52 | private final static String H2_INSERT_IGNORE = 53 | "INSERT IGNORE INTO $TABLE_NAME" + 54 | "(seq_name,seq_partition,seq_next_value,seq_create_time)" + 55 | " VALUES (?,?,?,?)"; 56 | 57 | private final static String H2_UPDATE_VALUE = 58 | "UPDATE $TABLE_NAME SET seq_next_value=?,seq_update_time=? " + 59 | "WHERE seq_name=? AND seq_partition=? AND seq_next_value=?"; 60 | 61 | private final static String H2_SELECT_VALUE = 62 | "SELECT seq_next_value FROM $TABLE_NAME WHERE seq_name=? AND seq_partition=?"; 63 | 64 | // @formatter:on 65 | 66 | private final String tableName; 67 | 68 | private final DataSource dataSource; 69 | 70 | @Override 71 | protected Connection getConnection() { 72 | try { 73 | return dataSource.getConnection(); 74 | } 75 | catch (Exception e) { 76 | log.warn(e.getMessage(), e); 77 | throw new SeqException(e.getMessage(), e); 78 | } 79 | } 80 | 81 | @Override 82 | protected String getCreateTableSql() { 83 | return H2_CREATE_TABLE.replace("$TABLE_NAME", tableName); 84 | } 85 | 86 | @Override 87 | protected String getDropTableSql() { 88 | return H2_DROP_TABLE.replace("$TABLE_NAME", tableName); 89 | } 90 | 91 | @Override 92 | protected String getCreateSeqSql() { 93 | return H2_INSERT_IGNORE.replace("$TABLE_NAME", tableName); 94 | } 95 | 96 | @Override 97 | protected String getSelectSeqSql() { 98 | return H2_SELECT_VALUE.replace("$TABLE_NAME", tableName); 99 | } 100 | 101 | @Override 102 | protected String getUpdateSeqSql() { 103 | return H2_UPDATE_VALUE.replace("$TABLE_NAME", tableName); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import lombok.AllArgsConstructor; 22 | import lombok.extern.slf4j.Slf4j; 23 | 24 | import javax.sql.DataSource; 25 | import java.sql.Connection; 26 | 27 | /** 28 | * MySql支持 29 | * 30 | * @author CJ (power4j@outlook.com) 31 | * @date 2020/7/1 32 | * @since 1.0 33 | */ 34 | @Slf4j 35 | @AllArgsConstructor 36 | public class MySqlSynchronizer extends AbstractSqlStatementProvider implements SeqSynchronizer { 37 | 38 | // @formatter:off 39 | 40 | private final static String MYSQL_CREATE_TABLE = 41 | "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" + 42 | "seq_name VARCHAR ( 255 ) NOT NULL," + 43 | "seq_partition VARCHAR ( 255 ) NOT NULL," + 44 | "seq_next_value BIGINT NOT NULL," + 45 | "seq_create_time TIMESTAMP NOT NULL," + 46 | "seq_update_time TIMESTAMP NULL," + 47 | "PRIMARY KEY ( `seq_name`, `seq_partition` ) " + 48 | ")"; 49 | 50 | private final static String MYSQL_DROP_TABLE = "DROP TABLE IF EXISTS $TABLE_NAME"; 51 | 52 | private final static String MYSQL_INSERT_IGNORE = 53 | "INSERT IGNORE INTO $TABLE_NAME" + 54 | "(seq_name,seq_partition,seq_next_value,seq_create_time)" + 55 | " VALUES (?,?,?,?)"; 56 | 57 | private final static String MYSQL_UPDATE_VALUE = 58 | "UPDATE $TABLE_NAME SET seq_next_value=?,seq_update_time=? " + 59 | "WHERE seq_name=? AND seq_partition=? AND seq_next_value=?"; 60 | 61 | private final static String MYSQL_SELECT_VALUE = 62 | "SELECT seq_next_value FROM $TABLE_NAME WHERE seq_name=? AND seq_partition=?"; 63 | 64 | // @formatter:on 65 | 66 | private final String tableName; 67 | 68 | private final DataSource dataSource; 69 | 70 | @Override 71 | protected Connection getConnection() { 72 | try { 73 | return dataSource.getConnection(); 74 | } 75 | catch (Exception e) { 76 | log.warn(e.getMessage(), e); 77 | throw new SeqException(e.getMessage(), e); 78 | } 79 | } 80 | 81 | @Override 82 | protected String getCreateTableSql() { 83 | return MYSQL_CREATE_TABLE.replace("$TABLE_NAME", tableName); 84 | } 85 | 86 | @Override 87 | protected String getDropTableSql() { 88 | return MYSQL_DROP_TABLE.replace("$TABLE_NAME", tableName); 89 | } 90 | 91 | @Override 92 | protected String getCreateSeqSql() { 93 | return MYSQL_INSERT_IGNORE.replace("$TABLE_NAME", tableName); 94 | } 95 | 96 | @Override 97 | protected String getSelectSeqSql() { 98 | return MYSQL_SELECT_VALUE.replace("$TABLE_NAME", tableName); 99 | } 100 | 101 | @Override 102 | protected String getUpdateSeqSql() { 103 | return MYSQL_UPDATE_VALUE.replace("$TABLE_NAME", tableName); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/SimpleLettuceSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import io.lettuce.core.RedisClient; 22 | import io.lettuce.core.api.StatefulRedisConnection; 23 | import io.lettuce.core.api.sync.RedisCommands; 24 | import io.lettuce.core.api.sync.RedisKeyCommands; 25 | import io.lettuce.core.api.sync.RedisScriptingCommands; 26 | import io.lettuce.core.api.sync.RedisStringCommands; 27 | import io.lettuce.core.support.ConnectionPoolSupport; 28 | import org.apache.commons.pool2.impl.GenericObjectPool; 29 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 30 | 31 | import java.util.function.Function; 32 | 33 | /** 34 | * 普通Lettuce连接池同步 35 | * 36 | * @author CJ (power4j@outlook.com) 37 | * @date 2020/7/10 38 | * @since 1.1 39 | */ 40 | public class SimpleLettuceSynchronizer extends AbstractLettuceSynchronizer implements SeqSynchronizer { 41 | 42 | private final GenericObjectPool> pool; 43 | 44 | public SimpleLettuceSynchronizer(String cacheName, RedisClient redisClient) { 45 | this(cacheName, redisClient, new GenericObjectPoolConfig()); 46 | } 47 | 48 | public SimpleLettuceSynchronizer(String cacheName, RedisClient redisClient, 49 | GenericObjectPoolConfig> poolConfig) { 50 | super(cacheName); 51 | this.pool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(), poolConfig); 52 | } 53 | 54 | public SimpleLettuceSynchronizer(String cacheName, 55 | GenericObjectPool> pool) { 56 | super(cacheName); 57 | this.pool = pool; 58 | } 59 | 60 | public R execCommand(Function, R> func) { 61 | try (StatefulRedisConnection connection = getRedisConnection()) { 62 | return func.apply(connection.sync()); 63 | } 64 | } 65 | 66 | protected StatefulRedisConnection getRedisConnection() { 67 | try { 68 | return pool.borrowObject(); 69 | } 70 | catch (Exception e) { 71 | throw new SeqException(e.getMessage(), e); 72 | } 73 | } 74 | 75 | @Override 76 | public void shutdown() { 77 | pool.close(); 78 | } 79 | 80 | @Override 81 | protected R execStringCommand(Function, R> func) { 82 | try (StatefulRedisConnection connection = getRedisConnection()) { 83 | return func.apply(connection.sync()); 84 | } 85 | } 86 | 87 | @Override 88 | protected R execScriptingCommand(Function, R> func) { 89 | try (StatefulRedisConnection connection = getRedisConnection()) { 90 | return func.apply(connection.sync()); 91 | } 92 | } 93 | 94 | @Override 95 | protected R execKeyCommand(Function, R> func) { 96 | try (StatefulRedisConnection connection = getRedisConnection()) { 97 | return func.apply(connection.sync()); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/TestServices.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.mongodb.client.MongoClient; 20 | import com.mongodb.client.MongoClients; 21 | import com.power4j.kit.seq.utils.EnvUtil; 22 | import com.zaxxer.hikari.HikariConfig; 23 | import com.zaxxer.hikari.HikariDataSource; 24 | import io.lettuce.core.RedisClient; 25 | 26 | import javax.sql.DataSource; 27 | 28 | /** 29 | * @author CJ (power4j@outlook.com) 30 | * @date 2020/7/3 31 | * @since 1.0 32 | */ 33 | public class TestServices { 34 | 35 | /** 36 | * protocol//[hosts][/database][?properties] 37 | */ 38 | private final static String DEFAULT_MYSQL_JDBC_URL = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"; 39 | 40 | /** 41 | * jdbc:postgresql://host:port/database 42 | */ 43 | private final static String DEFAULT_POSTGRESQL_JDBC_URL = "jdbc:postgresql://127.0.0.1:5432/test?ssl=false"; 44 | 45 | /** 46 | * jdbc:h2://host:port:database;[properties]; 47 | */ 48 | private final static String DEFAULT_H2_JDBC_URL = "jdbc:h2:mem:test;MODE=MYSQL;DB_CLOSE_DELAY=-1"; 49 | 50 | /** 51 | * redis://[password@]host [: port][/database] 52 | */ 53 | public final static String DEFAULT_REDIS_URI = "redis://127.0.0.1:6379"; 54 | 55 | /** 56 | * mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]] 57 | */ 58 | public final static String DEFAULT_MONGO_URI = "mongodb://127.0.0.1:27017"; 59 | 60 | public static DataSource getMySqlDataSource() { 61 | String jdbcUrl = EnvUtil.getStr("TEST_MYSQL_URL", DEFAULT_MYSQL_JDBC_URL); 62 | HikariConfig config = new HikariConfig(); 63 | config.setJdbcUrl(jdbcUrl); 64 | config.setUsername(EnvUtil.getStr("TEST_MYSQL_USER", "root")); 65 | config.setPassword(EnvUtil.getStr("TEST_MYSQL_PWD", "")); 66 | return new HikariDataSource(config); 67 | } 68 | 69 | public static DataSource getPostgreSqlDataSource() { 70 | String jdbcUrl = EnvUtil.getStr("TEST_POSTGRESQL_URL", DEFAULT_POSTGRESQL_JDBC_URL); 71 | HikariConfig config = new HikariConfig(); 72 | config.setJdbcUrl(jdbcUrl); 73 | config.setUsername(EnvUtil.getStr("TEST_POSTGRESQL_USER", "postgres")); 74 | config.setPassword(EnvUtil.getStr("TEST_POSTGRESQL_PWD", "")); 75 | return new HikariDataSource(config); 76 | } 77 | 78 | public static DataSource getH2DataSource() { 79 | String jdbcUrl = EnvUtil.getStr("TEST_H2_URL", DEFAULT_H2_JDBC_URL); 80 | HikariConfig config = new HikariConfig(); 81 | config.setJdbcUrl(jdbcUrl); 82 | config.setUsername(EnvUtil.getStr("TEST_H2_USER", "sa")); 83 | config.setPassword(EnvUtil.getStr("TEST_H2_PWD", "")); 84 | return new HikariDataSource(config); 85 | } 86 | 87 | public static RedisClient getRedisClient() { 88 | String redisUri = EnvUtil.getStr("TEST_REDIS_URI", DEFAULT_REDIS_URI); 89 | RedisClient redisClient = RedisClient.create(redisUri); 90 | 91 | return redisClient; 92 | } 93 | 94 | public static MongoClient getMongoClient() { 95 | String mongoUri = EnvUtil.getStr("TEST_MONGO_URI", DEFAULT_MONGO_URI); 96 | MongoClient mongoClient = MongoClients.create(mongoUri); 97 | 98 | return mongoClient; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/LettuceClusterSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 21 | import io.lettuce.core.api.sync.RedisKeyCommands; 22 | import io.lettuce.core.api.sync.RedisScriptingCommands; 23 | import io.lettuce.core.api.sync.RedisStringCommands; 24 | import io.lettuce.core.cluster.RedisClusterClient; 25 | import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; 26 | import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; 27 | import io.lettuce.core.support.ConnectionPoolSupport; 28 | import org.apache.commons.pool2.impl.GenericObjectPool; 29 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig; 30 | 31 | import java.util.function.Function; 32 | 33 | /** 34 | * Lettuce 集群支持 35 | * 36 | * @author CJ (power4j@outlook.com) 37 | * @date 2020/7/12 38 | * @since 1.1 39 | */ 40 | public class LettuceClusterSynchronizer extends AbstractLettuceSynchronizer implements SeqSynchronizer { 41 | 42 | private final GenericObjectPool> pool; 43 | 44 | public LettuceClusterSynchronizer(String cacheName, RedisClusterClient redisClient) { 45 | this(cacheName, redisClient, new GenericObjectPoolConfig()); 46 | } 47 | 48 | public LettuceClusterSynchronizer(String cacheName, RedisClusterClient redisClient, 49 | GenericObjectPoolConfig> poolConfig) { 50 | super(cacheName); 51 | this.pool = ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(), poolConfig); 52 | } 53 | 54 | public LettuceClusterSynchronizer(String cacheName, 55 | GenericObjectPool> pool) { 56 | super(cacheName); 57 | this.pool = pool; 58 | } 59 | 60 | public R execCommand(Function, R> func) { 61 | try (StatefulRedisClusterConnection connection = getRedisConnection()) { 62 | return func.apply(connection.sync()); 63 | } 64 | } 65 | 66 | protected StatefulRedisClusterConnection getRedisConnection() { 67 | try { 68 | return pool.borrowObject(); 69 | } 70 | catch (Exception e) { 71 | throw new SeqException(e.getMessage(), e); 72 | } 73 | } 74 | 75 | @Override 76 | public void shutdown() { 77 | pool.close(); 78 | } 79 | 80 | @Override 81 | protected R execStringCommand(Function, R> func) { 82 | try (StatefulRedisClusterConnection connection = getRedisConnection()) { 83 | return func.apply(connection.sync()); 84 | } 85 | } 86 | 87 | @Override 88 | protected R execScriptingCommand(Function, R> func) { 89 | try (StatefulRedisClusterConnection connection = getRedisConnection()) { 90 | return func.apply(connection.sync()); 91 | } 92 | } 93 | 94 | @Override 95 | protected R execKeyCommand(Function, R> func) { 96 | try (StatefulRedisClusterConnection connection = getRedisConnection()) { 97 | return func.apply(connection.sync()); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/InMemorySeqSynchronizer.java: -------------------------------------------------------------------------------- 1 | package com.power4j.kit.seq.persistent.provider; 2 | 3 | import com.power4j.kit.seq.core.exceptions.SeqException; 4 | import com.power4j.kit.seq.persistent.AddState; 5 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | import java.util.concurrent.atomic.AtomicLong; 14 | import java.util.concurrent.locks.Lock; 15 | import java.util.concurrent.locks.ReentrantReadWriteLock; 16 | 17 | /** 18 | * 这个类用于测试 19 | * 20 | * @author CJ (power4j@outlook.com) 21 | * @date 2021/9/2 22 | * @since 1.0 23 | */ 24 | public class InMemorySeqSynchronizer implements SeqSynchronizer { 25 | 26 | protected final AtomicLong queryCount = new AtomicLong(); 27 | 28 | protected final AtomicLong updateCount = new AtomicLong(); 29 | 30 | private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); 31 | 32 | private final Lock rLock = rwLock.readLock(); 33 | 34 | private final Lock wLock = rwLock.writeLock(); 35 | 36 | private final Map map = new HashMap<>(8); 37 | 38 | @Override 39 | public boolean tryCreate(String name, String partition, long nextValue) { 40 | final String key = makeKey(name, partition); 41 | Row pre; 42 | wLock.lock(); 43 | try { 44 | pre = map.putIfAbsent(key, Row.of(key, nextValue)); 45 | } 46 | finally { 47 | wLock.unlock(); 48 | } 49 | return Objects.isNull(pre); 50 | } 51 | 52 | @Override 53 | public boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew) { 54 | final String key = makeKey(name, partition); 55 | wLock.lock(); 56 | try { 57 | Row pre = map.get(key); 58 | if (Objects.nonNull(pre)) { 59 | updateCount.incrementAndGet(); 60 | if (nextValueOld == pre.getNextValue()) { 61 | map.put(key, Row.of(key, nextValueNew)); 62 | return true; 63 | } 64 | } 65 | } 66 | finally { 67 | wLock.unlock(); 68 | } 69 | return false; 70 | } 71 | 72 | @Override 73 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) { 74 | final String key = makeKey(name, partition); 75 | Row pre; 76 | wLock.lock(); 77 | try { 78 | pre = map.get(key); 79 | if (Objects.nonNull(pre)) { 80 | queryCount.incrementAndGet(); 81 | Row row = Row.of(key, pre.getNextValue() + delta); 82 | map.put(key, row); 83 | updateCount.incrementAndGet(); 84 | return AddState.success(pre.getNextValue(), row.getNextValue(), 1); 85 | } 86 | throw new SeqException(key + " not exists"); 87 | } 88 | finally { 89 | wLock.unlock(); 90 | } 91 | } 92 | 93 | @Override 94 | public Optional getNextValue(String name, String partition) { 95 | final String key = makeKey(name, partition); 96 | Row row; 97 | rLock.lock(); 98 | try { 99 | row = map.get(key); 100 | } 101 | finally { 102 | rLock.unlock(); 103 | } 104 | if (Objects.nonNull(row)) { 105 | queryCount.incrementAndGet(); 106 | } 107 | return Optional.ofNullable(row).map(Row::getNextValue); 108 | } 109 | 110 | @Override 111 | public void init() { 112 | // Nothing 113 | } 114 | 115 | @Override 116 | public long getQueryCounter() { 117 | return SeqSynchronizer.super.getQueryCounter(); 118 | } 119 | 120 | @Override 121 | public long getUpdateCounter() { 122 | return SeqSynchronizer.super.getUpdateCounter(); 123 | } 124 | 125 | protected final String makeKey(String name, String partition) { 126 | return name + "/" + partition; 127 | } 128 | 129 | @Getter 130 | @Setter 131 | static class Row { 132 | 133 | private final String id; 134 | 135 | private Long nextValue; 136 | 137 | public static Row of(String id, Long nextValue) { 138 | return new Row(id, nextValue); 139 | } 140 | 141 | Row(String id, Long nextValue) { 142 | this.id = id; 143 | this.nextValue = nextValue; 144 | } 145 | 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /sequence-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence 23 | ${revision} 24 | 25 | 26 | sequence-core 27 | 28 | 29 | 30 | org.mongodb 31 | mongodb-driver-sync 32 | true 33 | 34 | 35 | io.lettuce 36 | lettuce-core 37 | true 38 | 39 | 40 | org.apache.commons 41 | commons-pool2 42 | true 43 | 44 | 45 | ch.qos.logback 46 | logback-classic 47 | true 48 | 49 | 50 | com.zaxxer 51 | HikariCP 52 | true 53 | 54 | 55 | 56 | com.mysql 57 | mysql-connector-j 58 | test 59 | 60 | 61 | 62 | org.postgresql 63 | postgresql 64 | test 65 | 66 | 67 | 68 | com.h2database 69 | h2 70 | test 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-deploy-plugin 79 | 80 | false 81 | 82 | 83 | 84 | org.codehaus.mojo 85 | flatten-maven-plugin 86 | true 87 | 88 | 89 | 90 | flatten 91 | process-resources 92 | 93 | flatten 94 | 95 | 96 | true 97 | oss 98 | true 99 | 100 | expand 101 | remove 102 | remove 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /sequence-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence 23 | ${revision} 24 | 25 | 26 | sequence-spring-boot-starter 27 | 28 | https://github.com/power4j/sequence 29 | Sequence Spring Boot support 30 | 31 | 32 | 33 | com.power4j.kit 34 | sequence-core 35 | ${project.parent.version} 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-configuration-processor 40 | true 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-web 45 | true 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-actuator-autoconfigure 50 | true 51 | 52 | 53 | io.lettuce 54 | lettuce-core 55 | true 56 | 57 | 58 | org.apache.commons 59 | commons-pool2 60 | true 61 | 62 | 63 | org.mongodb 64 | mongodb-driver-sync 65 | true 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-deploy-plugin 74 | 75 | false 76 | 77 | 78 | 79 | org.codehaus.mojo 80 | flatten-maven-plugin 81 | true 82 | 83 | 84 | 85 | flatten 86 | process-resources 87 | 88 | flatten 89 | 90 | 91 | true 92 | oss 93 | true 94 | 95 | expand 96 | remove 97 | remove 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | on: 6 | push: 7 | branches: 8 | - master 9 | - dev 10 | pull_request: 11 | types: [opened, synchronize, reopened] 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: ubuntu-latest 16 | 17 | services: 18 | redis: 19 | image: redis 20 | ports: 21 | - 6379:6379 22 | options: >- 23 | --health-cmd "redis-cli ping" 24 | --health-interval 10s 25 | --health-timeout 5s 26 | --health-retries 5 27 | mongo: 28 | image: mongo 29 | env: 30 | MONGO_INITDB_ROOT_USERNAME: test 31 | MONGO_INITDB_ROOT_PASSWORD: test 32 | MONGO_INITDB_DATABASE: test 33 | ports: 34 | - 27017:27017 35 | options: >- 36 | --health-cmd "echo 'db.runCommand("ping").ok' | mongosh --quiet" 37 | --health-interval 10s 38 | --health-timeout 5s 39 | --health-retries 5 40 | mysql: 41 | image: mysql:8.0 42 | env: 43 | MYSQL_ROOT_PASSWORD: 'root' 44 | MYSQL_DATABASE: 'test' 45 | MYSQL_USER: 'test' 46 | MYSQL_PASSWORD: 'test' 47 | ports: 48 | - 3306:3306 49 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 50 | postgres: 51 | image: postgres:9.6 52 | env: 53 | POSTGRES_USER: test 54 | POSTGRES_PASSWORD: test 55 | POSTGRES_DB: test 56 | ports: 57 | - 5432:5432 58 | options: >- 59 | --health-cmd pg_isready 60 | --health-interval 10s 61 | --health-timeout 5s 62 | --health-retries 5 63 | steps: 64 | - uses: actions/checkout@v3 65 | with: 66 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 67 | 68 | - name: Set up JDK 17 69 | uses: actions/setup-java@v3 70 | with: 71 | distribution: 'adopt' 72 | java-version: '17' 73 | 74 | - name: Cache Maven packages 75 | uses: actions/cache@v3 76 | with: 77 | path: ~/.m2 78 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 79 | restore-keys: ${{ runner.os }}-m2 80 | 81 | - name: Build 82 | env: 83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 84 | MAVEN_OPTS: "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true" 85 | TEST_MYSQL_URL: "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true" 86 | TEST_MYSQL_USER: test 87 | TEST_MYSQL_PWD: test 88 | TEST_POSTGRESQL_URL: "jdbc:postgresql://localhost:5432/test?ssl=false" 89 | TEST_POSTGRESQL_USER: test 90 | TEST_POSTGRESQL_PWD: test 91 | TEST_REDIS_URI: "redis://localhost:6379" 92 | TEST_MONGO_URI: "mongodb://test:test@localhost:27017" 93 | run: mvn -B package -DskipTests=false --file pom.xml -U 94 | # todo setup test service https://stackoverflow.com/questions/65914287/github-action-with-mysql-test-database 95 | # - name: Test with Maven 96 | # run: mvn -B test -DskipTests=false 97 | # - name: Upload to Codecov 98 | # uses: codecov/codecov-action@v1 99 | # with: 100 | # #token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos 101 | # #files: ./coverage1.xml,./coverage2.xml # optional 102 | # flags: unittests # optional 103 | # name: codecov-umbrella # optional 104 | # fail_ci_if_error: true # optional (default = false) 105 | # verbose: true # optional (default = false) 106 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/AbstractSqlStatementProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | import java.sql.Connection; 22 | import java.sql.PreparedStatement; 23 | import java.sql.SQLException; 24 | import java.sql.Timestamp; 25 | import java.time.LocalDateTime; 26 | 27 | /** 28 | * 描述信息 29 | * 30 | * @author CJ (power4j@outlook.com) 31 | * @date 2020/7/29 32 | * @since 1.0 33 | */ 34 | @Slf4j 35 | public abstract class AbstractSqlStatementProvider extends AbstractJdbcSynchronizer { 36 | 37 | /** 38 | * 建表SQL 39 | * @return 40 | */ 41 | protected abstract String getCreateTableSql(); 42 | 43 | /** 44 | * 删表SQL 45 | * @return 46 | */ 47 | protected abstract String getDropTableSql(); 48 | 49 | /** 50 | * 创建记录SQL 51 | * @return 52 | */ 53 | protected abstract String getCreateSeqSql(); 54 | 55 | /** 56 | * 查询SQL 57 | * @return 58 | */ 59 | protected abstract String getSelectSeqSql(); 60 | 61 | /** 62 | * 更新SQL 63 | * @return 64 | */ 65 | protected abstract String getUpdateSeqSql(); 66 | 67 | @Override 68 | protected PreparedStatement getCreateTableStatement(Connection connection) throws SQLException { 69 | final String sql = getCreateTableSql(); 70 | if (log.isDebugEnabled()) { 71 | log.debug("Create Table Sql:[{}]", sql); 72 | } 73 | return connection.prepareStatement(sql); 74 | } 75 | 76 | @Override 77 | protected PreparedStatement getDropTableStatement(Connection connection) throws SQLException { 78 | final String sql = getDropTableSql(); 79 | if (log.isDebugEnabled()) { 80 | log.debug("Drop Table Sql:[{}]", sql); 81 | } 82 | return connection.prepareStatement(sql); 83 | } 84 | 85 | @Override 86 | protected PreparedStatement getCreateSeqStatement(Connection connection, String name, String partition, 87 | long nextValue) throws SQLException { 88 | final Timestamp now = Timestamp.valueOf(LocalDateTime.now()); 89 | final String sql = getCreateSeqSql(); 90 | if (log.isDebugEnabled()) { 91 | log.debug("Create Seq Sql:[{}]", sql); 92 | } 93 | PreparedStatement statement = connection.prepareStatement(sql); 94 | statement.setString(1, name); 95 | statement.setString(2, partition); 96 | statement.setLong(3, nextValue); 97 | statement.setTimestamp(4, now); 98 | log.debug(String.format("param: [%s] [%s] [%d] [%s]", name, partition, nextValue, now)); 99 | return statement; 100 | } 101 | 102 | @Override 103 | protected PreparedStatement getSelectSeqStatement(Connection connection, String name, String partition) 104 | throws SQLException { 105 | final String sql = getSelectSeqSql(); 106 | if (log.isDebugEnabled()) { 107 | log.debug("Select Seq Sql:[{}]", sql); 108 | } 109 | PreparedStatement statement = connection.prepareStatement(sql); 110 | statement.setString(1, name); 111 | statement.setString(2, partition); 112 | log.debug(String.format("param: [%s] [%s]", name, partition)); 113 | return statement; 114 | } 115 | 116 | @Override 117 | protected PreparedStatement getUpdateSeqStatement(Connection connection, String name, String partition, 118 | long nextValueOld, long nextValueNew) throws SQLException { 119 | final Timestamp now = Timestamp.valueOf(LocalDateTime.now()); 120 | final String sql = getUpdateSeqSql(); 121 | if (log.isDebugEnabled()) { 122 | log.debug("Update Seq Sql:[{}]", sql); 123 | } 124 | PreparedStatement statement = connection.prepareStatement(sql); 125 | statement.setLong(1, nextValueNew); 126 | statement.setTimestamp(2, now); 127 | statement.setString(3, name); 128 | statement.setString(4, partition); 129 | statement.setLong(5, nextValueOld); 130 | log.debug(String.format("param: [%d] [%s] [%s] [%s] [%d]", nextValueNew, now.toString(), name, partition, 131 | nextValueOld)); 132 | return statement; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JAVA 序号工具包 Sequence 2 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/bad24df7d1364e1bbbfea77f3378ad1a)](https://app.codacy.com/gh/power4j/sequence?utm_source=github.com&utm_medium=referral&utm_content=power4j/sequence&utm_campaign=Badge_Grade_Dashboard) 3 | [![codecov](https://codecov.io/gh/power4j/sequence/branch/master/graph/badge.svg)](https://codecov.io/gh/power4j/sequence) 4 | [![travis-ci](https://travis-ci.org/power4j/sequence.svg)](https://travis-ci.org/github/power4j/sequence) 5 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.power4j.kit/sequence/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.power4j.kit/sequence) 6 | 7 | 使用场景 8 | 9 | - 业务需要根据一定的规则生成序号,如: 10 | - 连续性:序号连续自增地进行分配 11 | - 相关性:多租户隔离、按年、月、日分区 12 | - 格式化: 序号输出的格式灵活可控 13 | - 技术上需要满足 14 | - 高性能 15 | - 线程安全 16 | - 适用性好 17 | - 配置简单、容易集成 18 | 19 | 20 | ## 项目说明 21 | 22 | - ***JDK 版本要求: `JDK 11+`*** 23 | 24 | - 支持的后端 25 | - MySQL 26 | - PostgreSQL (`9.6 +`) 27 | - Redis 28 | - MongoDB 29 | - H2(MySQL兼容模式) 30 | 31 | 32 | ## 核心概念 33 | 34 | - 号池([`SeqPool`](sequence-core/src/main/java/com/power4j/kit/seq/core/SeqPool.java)): 一种设施,可以提供有限或者无限的序号。 35 | - 同步器([`SeqSynchronizer`](sequence-core/src/main/java/com/power4j/kit/seq/persistent/SeqSynchronizer.java)): 负责与某种后端(如数据库)交互,更新序号的当前值。 36 | - 取号器([`SeqHolder`](sequence-core/src/main/java/com/power4j/kit/seq/persistent/SeqHolder.java)): 负责缓存从后端批量取出的序号,然后交给本地的号池来管理。 37 | - 分区(`Partition`): 后端存储在保存序号信息时,序号的名称+分区表示一条唯一的记录,分区可以是静态的(一个字符串常量),也可以是动态分区(一个返回分区名称的函数)。分区并不是将序号的取值进行划分,主要是用于多租户等需要二次分类的场景以及避免号池耗尽。 38 | - 格式化接口([`SeqFormatter`](sequence-core\src\main\java\com\power4j\kit\seq\core\SeqFormatter.java)): 自定义格式输出的扩展接口. 39 | 40 | 注意事项: 41 | - 如果使用动态分区,不宜变化过于频繁,比如用系统时间作为分区。比较合适的是按年份、月份进行分区。 42 | - 每一个分区的序号取值独立的,比如按年份进行分区,那么每年都有`Long.MAX`个序号可用。 43 | 44 | ## 使用方法 45 | 46 | 47 | 引入依赖 48 | ```xml 49 | 50 | com.power4j.kit 51 | sequence-spring-boot-starter 52 | 最新版本 53 | 54 | ``` 55 | 56 | 开启配置(JDBC) 57 | ```yaml 58 | power4j: 59 | sequence: 60 | # 数据同步使用的后端支持(如: mysql,oracle,redis),跟你实际使用的数据源类型对应 61 | backend: mysql 62 | ``` 63 | 64 | 也可以使用Redis,则配置方式为 65 | 66 | ```yaml 67 | power4j: 68 | sequence: 69 | backend: redis 70 | lettuce-uri: "redis://127.0.0.1" 71 | ``` 72 | 73 | > `maven`依赖等详细配置请查看`examples`目录下的演示项目 74 | 75 | 使用 76 | 77 | ```java 78 | @RestController 79 | @SpringBootApplication 80 | public class SequenceExampleApplication { 81 | @Autowired 82 | private Sequence sequence; 83 | public static void main(String[] args) { 84 | SpringApplication.run(SequenceExampleApplication.class, args); 85 | } 86 | 87 | @GetMapping("/seq") 88 | public List getSequence(@RequestParam(required = false) Integer size) { 89 | size = (size == null || size <= 0) ? 10 : size; 90 | List list = new ArrayList<>(size); 91 | for (int i = 0; i < size; ++i) { 92 | list.add(sequence.nextStr()); 93 | } 94 | return list; 95 | } 96 | } 97 | ``` 98 | 99 | ## 自定义配置 100 | 101 | 例子: [SeqConfig.java](examples/jdbc-example/src/main/java/com/power4j/sequence/example/SeqConfig.java) 102 | 103 | ## 性能测试 104 | 105 | 106 | 107 | > 测试环境用的Redis,MySQL等外部服务都是默认安装,没有调整参数。 108 | > 109 | > 如果需要自己测试,已经为你写好测试脚本,见[run-bench.sh](bench-test/run-bench.sh) 110 | 111 | 112 | ## 效果演示 113 | 114 | ### 分配10个序号 115 | ![seq10](docs/assets/img/get10.png) 116 | 117 | ### 数据库中的记录 118 | ![seq-table](docs/assets/img/seq-table.png) 119 | 120 | ### Redis中的记录 121 | ![seq-redis](docs/assets/img/seq-redis.png) 122 | 123 | ## 开发计划 124 | 125 | - [X] 支持MySQL(`1.0`) 126 | - [X] 支持PostgreSQL(`1.3`) 127 | - [X] 支持H2(`1.5`MySQL兼容模式) 128 | - [ ] 支持Oracle(不一定会支持,这货没有开源版本) 129 | - [x] 支持Redis(`1.1`) 130 | - [x] 支持MongoDB(`1.2`) 131 | - [X] Spring Boot 集成(`1.0`) 132 | 133 | ## 贡献指南 134 | 135 | 代码要求: 136 | - 统一风格,包含注释、代码缩进等与本项目保持一致 137 | - 保持代码整洁,比如注释掉的代码块等垃圾代码应该删除 138 | - 严格控制外部依赖,如果没有必要,请不要引入外部依赖 139 | - 请在类注释中保留你的作者信息,请不要害羞 140 | 141 | ### 数据库支持实现 142 | 143 | 1. 参考[`MySqlSynchronizer`](sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizer.java)的实现方式,实现某个特定数据库后端的支持 144 | 2. 参考[`MySqlSynchronizerTest`](sequence-core/src/test/java/com/power4j/kit/seq/persistent/provider/MySqlSynchronizerTest.java) 编写单元测试,完成自测。如果你的代码能跑通测试,基本上应该没有严重bug 145 | 146 | 147 | 148 | ## Special Thanks 149 | 150 | - [JetBrains Developer Toolbox](https://www.jetbrains.com/?from=sequence) 151 | 152 | 153 | ## 联系方式 154 | 155 | 156 | ![weichat](docs/assets/img/wei-chat.png) -------------------------------------------------------------------------------- /sequence-core/src/test/java/com/power4j/kit/seq/ext/InMemorySequenceRegistryTest.java: -------------------------------------------------------------------------------- 1 | package com.power4j.kit.seq.ext; 2 | 3 | import com.power4j.kit.seq.core.Sequence; 4 | import com.power4j.kit.seq.persistent.Partitions; 5 | import com.power4j.kit.seq.persistent.SeqHolder; 6 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 7 | import com.power4j.kit.seq.persistent.provider.InMemorySeqSynchronizer; 8 | import org.junit.Assert; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.concurrent.atomic.AtomicInteger; 15 | import java.util.function.Supplier; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * @author CJ (power4j@outlook.com) 20 | * @date 2021/9/2 21 | * @since 1.0 22 | */ 23 | public class InMemorySequenceRegistryTest { 24 | 25 | private final long initVal = 1L; 26 | 27 | private final int poolSize = 100; 28 | 29 | private final Supplier partitionFunc = Partitions.DAILY; 30 | 31 | private SeqSynchronizer seqSynchronizer; 32 | 33 | protected Sequence createSeq(String name) { 34 | return createSeq(name, partitionFunc); 35 | } 36 | 37 | protected Sequence createSeq(String name, Supplier partitionFunc) { 38 | return SeqHolder.builder() 39 | .name(name) 40 | .synchronizer(seqSynchronizer) 41 | .partitionFunc(partitionFunc) 42 | .initValue(initVal) 43 | .poolSize(poolSize) 44 | .seqFormatter((seqName, partition, value) -> String.format("%s.%s.%04d", seqName, partition, value)) 45 | .build(); 46 | } 47 | 48 | @Before 49 | public void setup() { 50 | seqSynchronizer = new InMemorySeqSynchronizer(); 51 | } 52 | 53 | @Test 54 | public void simpleTest() { 55 | final String nameA100 = "A100"; 56 | final String nameA200 = "A200"; 57 | 58 | SequenceRegistry> registry = new InMemorySequenceRegistry<>(); 59 | Sequence a100 = registry.get(nameA100).orElse(null); 60 | Assert.assertNull(a100); 61 | 62 | a100 = registry.getOrRegister(nameA100, this::createSeq); 63 | Assert.assertEquals(initVal, a100.next().longValue()); 64 | 65 | registry.register(nameA200, createSeq(nameA200)); 66 | long val = registry.get(nameA200).map(Sequence::next).orElse(-1L); 67 | Assert.assertEquals(initVal, val); 68 | 69 | Assert.assertEquals(2, registry.size()); 70 | 71 | String v1 = registry.get(nameA100).map(Sequence::nextStr).orElse(""); 72 | System.out.println(v1); 73 | Assert.assertTrue(v1.startsWith(nameA100)); 74 | 75 | String v2 = registry.get(nameA200).map(Sequence::nextStr).orElse(""); 76 | System.out.println(v2); 77 | Assert.assertTrue(v2.startsWith(nameA200)); 78 | 79 | Assert.assertTrue(registry.remove(nameA100).isPresent()); 80 | } 81 | 82 | @Test 83 | public void getOrRegisterTest() { 84 | SequenceRegistry> registry = new InMemorySequenceRegistry<>(); 85 | String nameTemplate = "Biz_%03d"; 86 | 87 | // 每个Sequence 使用一次 88 | for (int i = 0; i < 10; i++) { 89 | String name = String.format(nameTemplate, i); 90 | String val = registry.getOrRegister(name, this::createSeq).nextStr(); 91 | System.out.println(val); 92 | String[] parts = val.split("\\."); 93 | Assert.assertEquals(1, Integer.parseInt(parts[2])); 94 | } 95 | 96 | // 每个Sequence 再使用一次 97 | for (int i = 0; i < 10; i++) { 98 | String name = String.format(nameTemplate, i); 99 | String val = registry.getOrRegister(name, this::createSeq).nextStr(); 100 | System.out.println(val); 101 | String[] parts = val.split("\\."); 102 | Assert.assertEquals(2, Integer.parseInt(parts[2])); 103 | } 104 | } 105 | 106 | /** 107 | * 分区计算函数按3取模,因此 :
108 | * 取号器每一次取号,区段都和上一次不同
109 | * 每3次又回到之前的号段,但因为重新批量取号的缘故,会出现跳号现象 110 | */ 111 | @Test 112 | public void partitionRoundRobinTest() { 113 | final int mod = 3; 114 | AtomicInteger count = new AtomicInteger(); 115 | Supplier rolling = () -> Integer.toString(count.getAndIncrement() % mod); 116 | SequenceRegistry> registry = new InMemorySequenceRegistry<>(); 117 | String nameTemplate = "R%02d"; 118 | 119 | List results = new ArrayList<>(32); 120 | for (int i = 0; i < 3; i++) { 121 | String name = String.format(nameTemplate, i); 122 | for (int round = 0; round < 20; round++) { 123 | String val = registry.getOrRegister(name, o -> createSeq(o, rolling)).nextStr(); 124 | results.add(val); 125 | String[] parts = val.split("\\."); 126 | long except = (round / mod) * poolSize + initVal; 127 | Assert.assertEquals(except, Integer.parseInt(parts[2])); 128 | } 129 | } 130 | // @formatter:off 131 | results.stream() 132 | .collect(Collectors.groupingBy(o -> o.substring(0, o.lastIndexOf('.')))) 133 | .entrySet() 134 | .stream() 135 | .sorted(java.util.Map.Entry.comparingByKey()) 136 | .forEach(kv -> System.out.printf("%s : %s%n", kv.getKey(), String.join(" -> ", kv.getValue()))); 137 | // @formatter:on 138 | 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /bench-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 4.0.0 20 | 21 | com.power4j.kit 22 | sequence 23 | ${revision} 24 | 25 | 26 | bench-test 27 | ${parent.artifactId} 28 | 29 | https://github.com/power4j/sequence 30 | performance test 31 | 32 | 33 | true 34 | true 35 | 1.37 36 | 37 | benchmarks 38 | 39 | 40 | 41 | 42 | com.power4j.kit 43 | sequence-core 44 | ${project.parent.version} 45 | 46 | 47 | ch.qos.logback 48 | logback-classic 49 | true 50 | 51 | 52 | org.openjdk.jmh 53 | jmh-core 54 | ${jmh.version} 55 | 56 | 57 | org.openjdk.jmh 58 | jmh-generator-annprocess 59 | ${jmh.version} 60 | provided 61 | 62 | 63 | com.zaxxer 64 | HikariCP 65 | 66 | 67 | com.mysql 68 | mysql-connector-j 69 | 70 | 71 | io.lettuce 72 | lettuce-core 73 | 74 | 75 | org.apache.commons 76 | commons-pool2 77 | 78 | 79 | org.mongodb 80 | mongodb-driver-sync 81 | 82 | 83 | org.postgresql 84 | postgresql 85 | 86 | 87 | com.h2database 88 | h2 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-shade-plugin 97 | 3.5.0 98 | 99 | 100 | package 101 | 102 | shade 103 | 104 | 105 | ${uberjar.name} 106 | 107 | 108 | org.openjdk.jmh.Main 109 | 110 | 111 | 112 | 113 | 114 | 118 | *:* 119 | 120 | META-INF/*.SF 121 | META-INF/*.DSA 122 | META-INF/*.RSA 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/PostgreSqlSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import com.power4j.kit.seq.persistent.AddState; 21 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 22 | import lombok.AllArgsConstructor; 23 | import lombok.extern.slf4j.Slf4j; 24 | 25 | import javax.sql.DataSource; 26 | import java.sql.Connection; 27 | import java.sql.PreparedStatement; 28 | import java.sql.ResultSet; 29 | import java.sql.SQLException; 30 | import java.sql.Timestamp; 31 | import java.time.LocalDateTime; 32 | import java.util.Optional; 33 | 34 | /** 35 | * PostgreSQL 支持 36 | * 37 | * @author CJ (power4j@outlook.com) 38 | * @date 2020/7/29 39 | * @since 1.3 40 | */ 41 | @Slf4j 42 | @AllArgsConstructor 43 | public class PostgreSqlSynchronizer extends AbstractSqlStatementProvider implements SeqSynchronizer { 44 | 45 | // @formatter:off 46 | 47 | private final static String POSTGRESQL_CREATE_TABLE = 48 | "CREATE TABLE IF NOT EXISTS $TABLE_NAME (" + 49 | "seq_name VARCHAR ( 255 ) NOT NULL," + 50 | "seq_partition VARCHAR ( 255 ) NOT NULL," + 51 | "seq_next_value BIGINT NOT NULL," + 52 | "seq_create_time TIMESTAMP NOT NULL," + 53 | "seq_update_time TIMESTAMP NULL," + 54 | "PRIMARY KEY ( seq_name, seq_partition ) " + 55 | ")"; 56 | 57 | private final static String POSTGRESQL_DROP_TABLE = "DROP TABLE IF EXISTS $TABLE_NAME"; 58 | 59 | private final static String POSTGRESQL_INSERT_IGNORE = 60 | "INSERT INTO $TABLE_NAME" + 61 | "(seq_name,seq_partition,seq_next_value,seq_create_time)" + 62 | " VALUES (?,?,?,?) ON CONFLICT(seq_name, seq_partition) DO NOTHING"; 63 | 64 | private final static String POSTGRESQL_UPDATE_VALUE = 65 | "UPDATE $TABLE_NAME SET seq_next_value=?,seq_update_time=? " + 66 | "WHERE seq_name=? AND seq_partition=? AND seq_next_value=?"; 67 | 68 | private final static String POSTGRESQL_ADD_VALUE = 69 | "UPDATE $TABLE_NAME SET seq_next_value=seq_next_value + ?,seq_update_time=? " + 70 | "WHERE seq_name=? AND seq_partition=? RETURNING seq_next_value"; 71 | 72 | private final static String POSTGRESQL_SELECT_VALUE = 73 | "SELECT seq_next_value FROM $TABLE_NAME WHERE seq_name=? AND seq_partition=?"; 74 | 75 | // @formatter:on 76 | 77 | private final String tableName; 78 | 79 | private final DataSource dataSource; 80 | 81 | @Override 82 | protected Connection getConnection() { 83 | try { 84 | return dataSource.getConnection(); 85 | } 86 | catch (Exception e) { 87 | log.warn(e.getMessage(), e); 88 | throw new SeqException(e.getMessage(), e); 89 | } 90 | } 91 | 92 | @Override 93 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) { 94 | try (Connection connection = getConnection()) { 95 | return addAndGet(connection, name, partition, delta).map(val -> AddState.success(val - delta, val, 1)) 96 | .orElseThrow(() -> new SeqException(String.format("Not exist: %s %s", name, partition))); 97 | } 98 | catch (SQLException e) { 99 | log.warn(e.getMessage(), e); 100 | throw new SeqException(e.getMessage(), e); 101 | } 102 | } 103 | 104 | /** 105 | * 加法操作 106 | * @param connection 107 | * @param name 108 | * @param partition 109 | * @param delta 110 | * @return 返回加法操作后的结果,如果记录不存在返回null 111 | * @throws SQLException 112 | */ 113 | private Optional addAndGet(Connection connection, String name, String partition, int delta) 114 | throws SQLException { 115 | final Timestamp now = Timestamp.valueOf(LocalDateTime.now()); 116 | final String sql = POSTGRESQL_ADD_VALUE.replace("$TABLE_NAME", tableName); 117 | if (log.isDebugEnabled()) { 118 | log.debug("Add Value Sql:[{}]", sql); 119 | } 120 | try (PreparedStatement statement = connection.prepareStatement(sql)) { 121 | statement.setInt(1, delta); 122 | statement.setTimestamp(2, now); 123 | statement.setString(3, name); 124 | statement.setString(4, partition); 125 | log.debug(String.format("param: [%d] [%s] [%s] [%s]", delta, now.toString(), name, partition)); 126 | updateCount.incrementAndGet(); 127 | try (ResultSet resultSet = statement.executeQuery()) { 128 | if (resultSet.next() && resultSet.getObject(1) != null) { 129 | return Optional.of(resultSet.getLong(1)); 130 | } 131 | } 132 | } 133 | return Optional.empty(); 134 | } 135 | 136 | @Override 137 | protected String getCreateTableSql() { 138 | return POSTGRESQL_CREATE_TABLE.replace("$TABLE_NAME", tableName); 139 | } 140 | 141 | @Override 142 | protected String getDropTableSql() { 143 | return POSTGRESQL_DROP_TABLE.replace("$TABLE_NAME", tableName); 144 | } 145 | 146 | @Override 147 | protected String getCreateSeqSql() { 148 | return POSTGRESQL_INSERT_IGNORE.replace("$TABLE_NAME", tableName); 149 | } 150 | 151 | @Override 152 | protected String getSelectSeqSql() { 153 | return POSTGRESQL_SELECT_VALUE.replace("$TABLE_NAME", tableName); 154 | } 155 | 156 | @Override 157 | protected String getUpdateSeqSql() { 158 | return POSTGRESQL_UPDATE_VALUE.replace("$TABLE_NAME", tableName); 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/core/LongSeqPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.core; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import lombok.Getter; 21 | 22 | import java.util.Optional; 23 | import java.util.concurrent.atomic.AtomicLong; 24 | 25 | /** 26 | * 序号池(Long型) 27 | *
    28 | *
  • 取值范围{@code [0,Long.MAX_VALUE]}
  • 29 | *
  • 支持一次性取号和循环取号
  • 30 | *
  • 线程安全,lock free
  • 31 | *
32 | * 33 | * @author CJ (power4j@outlook.com) 34 | * @date 2020/6/30 35 | * @since 1.0 36 | */ 37 | public class LongSeqPool implements SeqPool { 38 | 39 | private final static long ZERO = 0L; 40 | 41 | private final static long ONE = 1L; 42 | 43 | public final static long MAX_VALUE = Long.MAX_VALUE; 44 | 45 | public final static long MIN_VALUE = ZERO; 46 | 47 | /** 48 | * 表示一个号池外的值 49 | */ 50 | public final static long OUT_OF_POOL = Long.MIN_VALUE; 51 | 52 | private final String name; 53 | 54 | @Getter 55 | private final long start; 56 | 57 | @Getter 58 | private final long end; 59 | 60 | @Getter 61 | private final boolean reRoll; 62 | 63 | private final AtomicLong current; 64 | 65 | /** 66 | * 根据数量创建 67 | * @param name 名称 68 | * @param start 起始值,包含 69 | * @param size 数量 70 | * @param reRoll 是否允许滚动 71 | * @return LongSeqPool 72 | */ 73 | public static LongSeqPool forSize(String name, long start, int size, boolean reRoll) { 74 | return new LongSeqPool(name, start, start + size - ONE, reRoll); 75 | } 76 | 77 | /** 78 | * 根据区间创建 79 | * @param name 名称 80 | * @param min 起始值,包含 81 | * @param max end 结束值,包含 82 | * @param reRoll 是否允许滚动 83 | * @return LongSeqPool 84 | */ 85 | public static LongSeqPool forRange(String name, long min, long max, boolean reRoll) { 86 | return new LongSeqPool(name, min, max, reRoll); 87 | } 88 | 89 | /** 90 | * 根据起始值创建,最大值为 Long.MAX_VALUE 91 | * @param name 名称 92 | * @param start 起始值,包含 93 | * @param reRoll 是否允许滚动 94 | * @return LongSeqPool 95 | */ 96 | public static LongSeqPool startFrom(String name, long start, boolean reRoll) { 97 | return new LongSeqPool(name, start, Long.MAX_VALUE, reRoll); 98 | } 99 | 100 | /** 101 | * constructor 102 | * @param name 名称 103 | * @param start 起始值,包含 104 | * @param end 结束值,包含 105 | * @param reRoll 是否允许滚动,可以滚动的号池永远不会耗尽 106 | */ 107 | private LongSeqPool(String name, long start, long end, boolean reRoll) { 108 | assertMinValue(start, "Invalid start value: " + start); 109 | this.name = name; 110 | this.start = start; 111 | this.end = end; 112 | this.reRoll = reRoll; 113 | this.current = new AtomicLong(start); 114 | if (end < start) { 115 | throw new IllegalArgumentException("Nothing to offer"); 116 | } 117 | } 118 | 119 | @Override 120 | public String getName() { 121 | return name; 122 | } 123 | 124 | @Override 125 | public Long next() throws SeqException { 126 | return take(); 127 | } 128 | 129 | @Override 130 | public Optional nextOpt() { 131 | final long val = take(OUT_OF_POOL); 132 | return Optional.ofNullable(OUT_OF_POOL == val ? null : val); 133 | } 134 | 135 | @Override 136 | public Long peek() { 137 | return current.get(); 138 | } 139 | 140 | /** 141 | * 取出序号 142 | * @return 返回下一个序号 143 | * @throws SeqException 号池耗尽抛出异常,可以通过 {@code hasMore} 方法提前检查 144 | * @see SeqPool#hasMore 145 | */ 146 | public long take() { 147 | final long val = take(OUT_OF_POOL); 148 | if (val == OUT_OF_POOL) { 149 | long current = peek(); 150 | throw new SeqException(String.format("No more value,current = %08d(%d/%d)", current, start, end)); 151 | } 152 | return val; 153 | } 154 | 155 | /** 156 | * 取出序号 157 | * @param defVal 号池耗尽时返回的默认值,必须是号池范围外的值,推荐使用 {@code OUT_OF_POOL} 158 | * @return 返回下一个序号,号池耗尽返回默认值 159 | * @throws IllegalArgumentException defVal 无效 160 | * @see LongSeqPool#OUT_OF_POOL 161 | */ 162 | public long take(long defVal) { 163 | if (defVal >= start && defVal <= end) { 164 | throw new IllegalArgumentException("Bad defVal"); 165 | } 166 | long val = current.getAndUpdate(this::updateFunc); 167 | return (val > end || val < start) ? defVal : val; 168 | } 169 | 170 | private long updateFunc(final long pre) { 171 | if (reRoll && pre >= end) { 172 | return minValue(); 173 | } 174 | return pre + ONE; 175 | } 176 | 177 | @Override 178 | public boolean hasMore() { 179 | return remaining() > ZERO; 180 | } 181 | 182 | @Override 183 | public LongSeqPool fork(String name) { 184 | LongSeqPool seqPool = new LongSeqPool(name, start, end, reRoll); 185 | seqPool.setCurrent(peek()); 186 | return seqPool; 187 | } 188 | 189 | @Override 190 | public long remaining() { 191 | return reRoll ? capacity() : end - peek() + ONE; 192 | } 193 | 194 | @Override 195 | public long capacity() { 196 | return end - start + ONE; 197 | } 198 | 199 | @Override 200 | public Long minValue() { 201 | return start; 202 | } 203 | 204 | @Override 205 | public Long maxValue() { 206 | return end; 207 | } 208 | 209 | private void setCurrent(long val) { 210 | current.set(val); 211 | } 212 | 213 | @Override 214 | public String toString() { 215 | return peek() + " -> [" + start + "," + end + "],reRoll = " + reRoll; 216 | } 217 | 218 | protected void assertMinValue(long val, String msg) { 219 | if (val < MIN_VALUE) { 220 | throw new SeqException(msg); 221 | } 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/AbstractLettuceSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.power4j.kit.seq.core.exceptions.SeqException; 20 | import com.power4j.kit.seq.persistent.AddState; 21 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 22 | import io.lettuce.core.KeyScanCursor; 23 | import io.lettuce.core.ScanArgs; 24 | import io.lettuce.core.ScanCursor; 25 | import io.lettuce.core.ScriptOutputType; 26 | import io.lettuce.core.api.sync.RedisKeyCommands; 27 | import io.lettuce.core.api.sync.RedisScriptingCommands; 28 | import io.lettuce.core.api.sync.RedisStringCommands; 29 | import lombok.extern.slf4j.Slf4j; 30 | 31 | import java.util.Optional; 32 | import java.util.concurrent.atomic.AtomicBoolean; 33 | import java.util.concurrent.atomic.AtomicLong; 34 | import java.util.concurrent.atomic.AtomicReference; 35 | import java.util.function.Function; 36 | 37 | /** 38 | * @author CJ (power4j@outlook.com) 39 | * @date 2020/7/10 40 | * @since 1.1 41 | */ 42 | @Slf4j 43 | public abstract class AbstractLettuceSynchronizer implements SeqSynchronizer { 44 | 45 | private final AtomicBoolean checkKey = new AtomicBoolean(true); 46 | 47 | private final AtomicLong queryCounter = new AtomicLong(); 48 | 49 | private final AtomicLong updateCounter = new AtomicLong(); 50 | 51 | private final AtomicReference serverScript = new AtomicReference<>(); 52 | 53 | private final String cacheName; 54 | 55 | public AbstractLettuceSynchronizer(String cacheName) { 56 | this.cacheName = cacheName; 57 | } 58 | 59 | protected String makeKey(String seqName, String partition) { 60 | return cacheName + RedisConstants.KEY_DELIMITER + seqName + RedisConstants.KEY_DELIMITER + partition; 61 | } 62 | 63 | protected void validateKeyExists(RedisStringCommands redisCommands, String key, String msg) { 64 | if (null == redisCommands.get(key)) { 65 | throw new SeqException(msg); 66 | } 67 | } 68 | 69 | protected String loadScript(RedisScriptingCommands redisCommands, String script) { 70 | String id = redisCommands.scriptLoad(script); 71 | log.info("Script loaded,id = {}", id); 72 | return id; 73 | } 74 | 75 | protected boolean doUpdate(RedisScriptingCommands redisCommands, String name, String partition, 76 | long nextValueOld, long nextValueNew) { 77 | String scriptId = serverScript 78 | .updateAndGet((s -> s != null ? s : loadScript(redisCommands, RedisConstants.UPDATE_SCRIPT))); 79 | String[] keys = { makeKey(name, partition) }; 80 | boolean ret = redisCommands.evalsha(scriptId, ScriptOutputType.BOOLEAN, keys, Long.toString(nextValueOld), 81 | Long.toString(nextValueNew)); 82 | updateCounter.incrementAndGet(); 83 | return ret; 84 | } 85 | 86 | protected Optional doGet(RedisStringCommands redisCommands, String name, String partition) { 87 | String val = redisCommands.get(makeKey(name, partition)); 88 | queryCounter.incrementAndGet(); 89 | return Optional.ofNullable(Long.parseLong(val)); 90 | } 91 | 92 | protected AddState doInc(RedisStringCommands redisCommands, String name, String partition, 93 | int delta) { 94 | final String key = makeKey(name, partition); 95 | if (checkKey.get()) { 96 | validateKeyExists(redisCommands, key, "Key not exists:" + key); 97 | } 98 | long current = redisCommands.incrby(key, delta); 99 | updateCounter.incrementAndGet(); 100 | return AddState.success(current - delta, current, 1); 101 | } 102 | 103 | public int removeCache() { 104 | return execKeyCommand((cmd -> { 105 | int keys = 0; 106 | ScanCursor scanCursor = ScanCursor.INITIAL; 107 | ScanArgs scanArgs = ScanArgs.Builder.limit(10).match(cacheName + RedisConstants.KEY_DELIMITER + "*"); 108 | while (true) { 109 | KeyScanCursor keyScanCursor = cmd.scan(scanCursor, scanArgs); 110 | if (keyScanCursor.isFinished() || keyScanCursor.getKeys().size() <= 0) { 111 | break; 112 | } 113 | keys += keyScanCursor.getKeys().size(); 114 | cmd.del(keyScanCursor.getKeys().toArray(new String[keyScanCursor.getKeys().size()])); 115 | scanCursor = ScanCursor.of(keyScanCursor.getCursor()); 116 | } 117 | return keys; 118 | })); 119 | } 120 | 121 | public boolean setKeyValidate(boolean check) { 122 | return checkKey.getAndSet(check); 123 | } 124 | 125 | /** 126 | * 执行命令 127 | * @param func 128 | * @param 129 | * @return 130 | */ 131 | protected abstract R execStringCommand(Function, R> func); 132 | 133 | /** 134 | * 执行命令 135 | * @param func 136 | * @param 137 | * @return 138 | */ 139 | protected abstract R execScriptingCommand(Function, R> func); 140 | 141 | /** 142 | * 执行命令 143 | * @param func 144 | * @param 145 | * @return 146 | */ 147 | protected abstract R execKeyCommand(Function, R> func); 148 | 149 | @Override 150 | public boolean tryCreate(String name, String partition, long nextValue) { 151 | return execStringCommand((cmd) -> cmd.setnx(makeKey(name, partition), Long.toString(nextValue))); 152 | } 153 | 154 | @Override 155 | public boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew) { 156 | return execScriptingCommand((cmd) -> doUpdate(cmd, name, partition, nextValueOld, nextValueNew)); 157 | } 158 | 159 | @Override 160 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) { 161 | return execStringCommand((cmd) -> doInc(cmd, name, partition, delta)); 162 | } 163 | 164 | @Override 165 | public Optional getNextValue(String name, String partition) { 166 | return execStringCommand((cmd) -> doGet(cmd, name, partition)); 167 | } 168 | 169 | @Override 170 | public void init() { 171 | execScriptingCommand((cmd) -> loadScript(cmd, RedisConstants.UPDATE_SCRIPT)); 172 | } 173 | 174 | @Override 175 | public long getQueryCounter() { 176 | return queryCounter.get(); 177 | } 178 | 179 | @Override 180 | public long getUpdateCounter() { 181 | return updateCounter.get(); 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /sequence-core/src/main/java/com/power4j/kit/seq/persistent/provider/SimpleMongoSynchronizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 ChenJun (power4j@outlook.com) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.power4j.kit.seq.persistent.provider; 18 | 19 | import com.mongodb.MongoWriteException; 20 | import com.mongodb.client.FindIterable; 21 | import com.mongodb.client.MongoClient; 22 | import com.mongodb.client.MongoCollection; 23 | import com.mongodb.client.model.Filters; 24 | import com.mongodb.client.model.FindOneAndUpdateOptions; 25 | import com.mongodb.client.model.IndexOptions; 26 | import com.mongodb.client.model.Indexes; 27 | import com.mongodb.client.model.ReturnDocument; 28 | import com.mongodb.client.model.Updates; 29 | import com.mongodb.client.result.UpdateResult; 30 | import com.power4j.kit.seq.persistent.AddState; 31 | import com.power4j.kit.seq.persistent.SeqSynchronizer; 32 | import lombok.AllArgsConstructor; 33 | import lombok.extern.slf4j.Slf4j; 34 | import org.bson.Document; 35 | import org.bson.conversions.Bson; 36 | 37 | import java.time.LocalDateTime; 38 | import java.util.Optional; 39 | import java.util.concurrent.atomic.AtomicLong; 40 | import java.util.concurrent.atomic.AtomicReference; 41 | 42 | /** 43 | * @author CJ (power4j@outlook.com) 44 | * @date 2020/7/17 45 | * @since 1.2 46 | */ 47 | @Slf4j 48 | @AllArgsConstructor 49 | public class SimpleMongoSynchronizer implements SeqSynchronizer { 50 | 51 | private final AtomicLong queryCount = new AtomicLong(); 52 | 53 | private final AtomicLong updateCount = new AtomicLong(); 54 | 55 | private final AtomicReference> collectionRef = new AtomicReference<>(); 56 | 57 | private final String dataBaseName; 58 | 59 | private final String collectionName; 60 | 61 | private final MongoClient mongoClient; 62 | 63 | /** 64 | * MongoDB creates new collections when you first store data for the collections. 65 | */ 66 | public void createCollection() { 67 | collectionRef.compareAndSet(null, doCreateCollection()); 68 | } 69 | 70 | public void dropCollection() { 71 | Optional.ofNullable(collectionRef.get()).ifPresent(col -> col.drop()); 72 | } 73 | 74 | private MongoCollection doCreateCollection() { 75 | MongoCollection col = mongoClient.getDatabase(dataBaseName).getCollection(collectionName); 76 | String idxName = col.createIndex( 77 | Indexes.compoundIndex(Indexes.ascending(DocKeys.KEY_SEQ_NAME, DocKeys.KEY_SEQ_PARTITION)), 78 | new IndexOptions().unique(true)); 79 | log.info("Index created :{}", idxName); 80 | return col; 81 | } 82 | 83 | private MongoCollection ensureCollection() { 84 | return collectionRef.updateAndGet(col -> (col != null ? col : doCreateCollection())); 85 | } 86 | 87 | protected Bson getSeqSelector(String name, String partition) { 88 | return Filters.and(Filters.eq(DocKeys.KEY_SEQ_NAME, name), Filters.eq(DocKeys.KEY_SEQ_PARTITION, partition)); 89 | } 90 | 91 | protected Bson getValueSelector(String name, String partition, Long value) { 92 | return Filters.and(Filters.eq(DocKeys.KEY_SEQ_NAME, name), Filters.eq(DocKeys.KEY_SEQ_PARTITION, partition), 93 | Filters.eq(DocKeys.KEY_SEQ_VALUE, value)); 94 | } 95 | 96 | @Override 97 | public boolean tryCreate(String name, String partition, long nextValue) { 98 | MongoCollection col = ensureCollection(); 99 | Document document = new Document(); 100 | document.put(DocKeys.KEY_SEQ_NAME, name); 101 | document.put(DocKeys.KEY_SEQ_PARTITION, partition); 102 | document.put(DocKeys.KEY_SEQ_VALUE, nextValue); 103 | document.put(DocKeys.KEY_SEQ_CREATE_AT, LocalDateTime.now()); 104 | document.put(DocKeys.KEY_SEQ_UPDATE_AT, null); 105 | try { 106 | col.insertOne(document); 107 | return true; 108 | } 109 | catch (MongoWriteException writeException) { 110 | log.error("Ignore Insert error,{}", writeException.getMessage()); 111 | return false; 112 | } 113 | } 114 | 115 | @Override 116 | public boolean tryUpdate(String name, String partition, long nextValueOld, long nextValueNew) { 117 | updateCount.incrementAndGet(); 118 | MongoCollection col = ensureCollection(); 119 | Bson query = getValueSelector(name, partition, nextValueOld); 120 | Bson op = Updates.combine(Updates.set(DocKeys.KEY_SEQ_VALUE, nextValueNew), 121 | Updates.set(DocKeys.KEY_SEQ_UPDATE_AT, LocalDateTime.now())); 122 | UpdateResult result = col.updateOne(query, op); 123 | return result.getModifiedCount() == 1; 124 | } 125 | 126 | @Override 127 | public AddState tryAddAndGet(String name, String partition, int delta, int maxReTry) { 128 | updateCount.incrementAndGet(); 129 | MongoCollection col = ensureCollection(); 130 | Bson query = getSeqSelector(name, partition); 131 | Bson op = Updates.combine(Updates.inc(DocKeys.KEY_SEQ_VALUE, delta), 132 | Updates.set(DocKeys.KEY_SEQ_UPDATE_AT, LocalDateTime.now())); 133 | Document doc = col.findOneAndUpdate(query, op, 134 | new FindOneAndUpdateOptions().returnDocument(ReturnDocument.BEFORE)); 135 | if (doc == null) { 136 | return AddState.fail(1); 137 | } 138 | return AddState.success(doc.getLong(DocKeys.KEY_SEQ_VALUE), doc.getLong(DocKeys.KEY_SEQ_VALUE) + delta, 1); 139 | } 140 | 141 | @Override 142 | public Optional getNextValue(String name, String partition) { 143 | queryCount.incrementAndGet(); 144 | MongoCollection col = ensureCollection(); 145 | Bson query = getSeqSelector(name, partition); 146 | FindIterable itr = col.find(query); 147 | Document doc = itr.first(); 148 | return Optional.ofNullable(doc == null ? null : doc.getLong(DocKeys.KEY_SEQ_VALUE)); 149 | } 150 | 151 | @Override 152 | public void init() { 153 | createCollection(); 154 | } 155 | 156 | @Override 157 | public void shutdown() { 158 | // nothing to do 159 | } 160 | 161 | @Override 162 | public long getQueryCounter() { 163 | return queryCount.get(); 164 | } 165 | 166 | @Override 167 | public long getUpdateCounter() { 168 | return updateCount.get(); 169 | } 170 | 171 | interface DocKeys { 172 | 173 | // @formatter:off 174 | 175 | String KEY_SEQ_NAME = "seqName"; 176 | String KEY_SEQ_PARTITION = "seqPartition"; 177 | String KEY_SEQ_VALUE = "seqNextValue"; 178 | String KEY_SEQ_CREATE_AT = "seqCreateTime"; 179 | String KEY_SEQ_UPDATE_AT = "seqUpdateTime"; 180 | 181 | // @formatter:on 182 | 183 | } 184 | 185 | } 186 | --------------------------------------------------------------------------------