diff --git a/.github/workflows/benchmark-test.yml b/.github/workflows/benchmark-test.yml index 85a13b0118..76493f3abb 100644 --- a/.github/workflows/benchmark-test.yml +++ b/.github/workflows/benchmark-test.yml @@ -24,19 +24,16 @@ name: Benchmark CosId Test on: push: - paths-ignore: - - 'cosid-benchmark/**' - - 'docs/**' - - 'document/**' - - 'examples/**' - - 'wiki/**' + paths: + - 'cosid-core/**' + - 'cosid-spring-redis/**' + - 'cosid-jdbc/**' pull_request: - paths-ignore: - - 'cosid-benchmark/**' - - 'docs/**' - - 'document/**' - - 'examples/**' - - 'wiki/**' + paths: + - 'cosid-core/**' + - 'cosid-spring-redis/**' + - 'cosid-jdbc/**' + workflow_dispatch: jobs: cosid-core-benchmark: @@ -46,11 +43,11 @@ jobs: - name: Checkout uses: actions/checkout@master - - name: Set up JDK 8 - uses: actions/setup-java@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: '8' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} @@ -74,16 +71,16 @@ jobs: - name: Checkout uses: actions/checkout@master - - name: Set up JDK 8 - uses: actions/setup-java@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: '8' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: CosId-Spring-Redis - run: gradle cosid-spring-redis:clean cosid-spring-redis:jmh + run: ./gradlew cosid-spring-redis:clean cosid-spring-redis:jmh cosid-jdbc-benchmark: name: CosId Jdbc Benchmark @@ -95,11 +92,11 @@ jobs: - name: Checkout uses: actions/checkout@master - - name: Set up JDK 8 - uses: actions/setup-java@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: '8' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} @@ -107,4 +104,4 @@ jobs: run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql - name: CosId-Jdbc - run: gradle cosid-jdbc:clean cosid-jdbc:jmh \ No newline at end of file + run: gradle cosid-jdbc:clean cosid-jdbc:jmh diff --git a/.github/workflows/benchmark-vs-test.yml b/.github/workflows/benchmark-vs-test.yml index 72bd599b23..f816a307b4 100644 --- a/.github/workflows/benchmark-vs-test.yml +++ b/.github/workflows/benchmark-vs-test.yml @@ -26,6 +26,7 @@ on: push: paths: - 'cosid-benchmark/**' + workflow_dispatch: jobs: # WARNING:中央仓库没有找到美团官方提供的Jar!!! @@ -49,11 +50,11 @@ jobs: # restore-keys: | # ${{ runner.os }}-maven- - - name: Set up JDK 11 - uses: actions/setup-java@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - java-version: '11' - distribution: 'adopt' + java-version: '17' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} @@ -71,4 +72,4 @@ jobs: - name: Benchmark Test working-directory: cosid-benchmark - run: gradle clean jmh + run: ./gradlew clean jmh diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 6ffbe79d20..20b7bb3259 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -5,6 +5,7 @@ on: - 'cosid-benchmark/**' - 'docs/**' - 'document/**' + - 'documentation/**' - 'examples/**' - 'wiki/**' pull_request: @@ -12,8 +13,11 @@ on: - 'cosid-benchmark/**' - 'docs/**' - 'document/**' + - 'documentation/**' - 'examples/**' - 'wiki/**' +env: + CI: true jobs: codecov: name: Codecov @@ -40,10 +44,10 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} @@ -51,10 +55,10 @@ jobs: run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql - name: Build Code Coverage Report - run: gradle codeCoverageReport --stacktrace + run: ./gradlew codeCoverageReport --stacktrace - name: Upload Code Coverage Report to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} flags: unittests # optional diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7721d8bfb8..a6015dbe7a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -17,6 +17,13 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [ main ] + paths-ignore: + - 'cosid-benchmark/**' + - 'docs/**' + - 'document/**' + - 'documentation/**' + - 'examples/**' + - 'wiki/**' schedule: - cron: '17 9 * * 5' @@ -38,11 +45,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,7 +60,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -67,4 +74,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/docker-deploy.yml b/.github/workflows/docker-deploy.yml deleted file mode 100644 index a00638d0e5..0000000000 --- a/.github/workflows/docker-deploy.yml +++ /dev/null @@ -1,100 +0,0 @@ -# -# Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Docker Image Deploy -on: - schedule: - - cron: '0 10 * * *' - push: - paths-ignore: - - 'cosid-benchmark/**' - - 'docs/**' - - 'document/**' - - 'examples/**' - - 'wiki/**' - branches: - - '**' - tags: - - 'v*.*.*' - pull_request: - paths-ignore: - - 'cosid-benchmark/**' - - 'docs/**' - - 'document/**' - - 'examples/**' - - 'wiki/**' - branches: - - 'main' - -jobs: - docker-deploy: - name: Push Docker image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout - uses: actions/checkout@master - - - name: Set up JDK 8 - uses: actions/setup-java@v3 - with: - java-version: '8' - distribution: 'adopt' - server-id: github - settings-path: ${{ github.workspace }} - - - name: Build Dist - run: gradle cosid-proxy-server:installDist - - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to DockerHub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GHCR - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - ahoowang/cosid-proxy - ghcr.io/ahoo-wang/cosid-proxy - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push - uses: docker/build-push-action@v4 - with: - platforms: linux/amd64,linux/arm64 - context: cosid-proxy-server - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/document-deploy.yml b/.github/workflows/document-deploy.yml index 603b35fbb3..ee27785b3d 100644 --- a/.github/workflows/document-deploy.yml +++ b/.github/workflows/document-deploy.yml @@ -11,11 +11,18 @@ # limitations under the License. # -name: Document Build and Deploy +name: Documentation Build and Deploy on: push: paths: - - 'document/**' + - 'documentation/**' + pull_request: + paths: + - 'documentation/**' + workflow_dispatch: +permissions: + contents: write + jobs: build-and-deploy: runs-on: ubuntu-latest @@ -23,12 +30,56 @@ jobs: - name: Checkout uses: actions/checkout@master - - name: Build and Deploy - uses: jenkey2011/vuepress-deploy@master + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + - name: Generate JavaDoc + run: ./gradlew aggregateJavadoc + + - name: Copy To Documentation + run: cp -R build/aggregatedJavadoc documentation/docs/public/javadoc + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Build VitePress site + working-directory: documentation + run: npm add -D vitepress && npm run docs:build + + - name: Deploy to gh-pages + uses: crazy-max/ghaction-github-pages@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + target_branch: gh-pages + build_dir: documentation/docs/.vitepress/dist/ + fqdn: cosid.ahoo.me + + - name: Build VitePress site with SITE_BASE + working-directory: documentation + env: + SITE_BASE: /cosid/ + run: npm add -D vitepress && npm run docs:build + + - name: Deploy to gh-pages-with-site-base + uses: crazy-max/ghaction-github-pages@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + target_branch: gh-pages-with-site-base + build_dir: documentation/docs/.vitepress/dist/ + + - name: Sync to Gitee + uses: wearerequired/git-mirror-action@master env: - ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} - TARGET_REPO: Ahoo-Wang/CosId - TARGET_BRANCH: gh-pages - BUILD_SCRIPT: cd document && yarn && yarn docs:build - BUILD_DIR: docs/.vuepress/dist/ - CNAME: cosid.ahoo.me + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + with: + source-repo: "git@github.com:Ahoo-Wang/CosId.git" + destination-repo: "git@gitee.com:AhooWang/CosId.git" \ No newline at end of file diff --git a/.github/workflows/example-test.yml b/.github/workflows/example-test.yml index b2094d6b13..da6f7539c3 100644 --- a/.github/workflows/example-test.yml +++ b/.github/workflows/example-test.yml @@ -14,19 +14,12 @@ name: Example Test on: push: - paths-ignore: - - 'cosid-benchmark/**' - - 'docs/**' - - 'document/**' + paths: - 'examples/**' - - 'wiki/**' pull_request: - paths-ignore: - - 'cosid-benchmark/**' - - 'docs/**' - - 'document/**' + paths: - 'examples/**' - - 'wiki/**' + workflow_dispatch: jobs: example-redis-test: @@ -47,15 +40,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test Example-Redis - run: gradle cosid-example-redis:clean cosid-example-redis:check + run: ./gradlew cosid-example-redis:clean cosid-example-redis:check example-zookeeper-test: name: Example Zookeeper Test @@ -70,15 +63,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test Example-Zookeeper - run: gradle cosid-example-zookeeper:clean cosid-example-zookeeper:check + run: ./gradlew cosid-example-zookeeper:clean cosid-example-zookeeper:check example-proxy-test: name: Example Proxy Test @@ -93,12 +86,13 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test Example-Proxy - run: gradle cosid-example-proxy:clean cosid-example-proxy:check \ No newline at end of file + run: ./gradlew cosid-example-proxy:clean cosid-example-proxy:check + diff --git a/.github/workflows/gitee-sync.yml b/.github/workflows/gitee-sync.yml new file mode 100644 index 0000000000..7807a1ae80 --- /dev/null +++ b/.github/workflows/gitee-sync.yml @@ -0,0 +1,21 @@ +name: Sync to Gitee +on: + schedule: + - cron: '0 0 * * *' + push: + branches: + - main + - gh-pages + - gh-pages-with-site-base + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Sync to Gitee + uses: wearerequired/git-mirror-action@master + env: + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + with: + source-repo: "git@github.com:Ahoo-Wang/CosId.git" + destination-repo: "git@gitee.com:AhooWang/CosId.git" \ No newline at end of file diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 40820ca81d..b1c01ed68d 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -18,6 +18,7 @@ on: - 'cosid-benchmark/**' - 'docs/**' - 'document/**' + - 'documentation/**' - 'examples/**' - 'wiki/**' pull_request: @@ -25,9 +26,11 @@ on: - 'cosid-benchmark/**' - 'docs/**' - 'document/**' + - 'documentation/**' - 'examples/**' - 'wiki/**' - +env: + CI: true jobs: cosid-core-test: name: CosId Core Test @@ -37,15 +40,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test CosId-Core - run: gradle cosid-core:clean cosid-core:check + run: ./gradlew cosid-core:clean cosid-core:check cosid-jackson-test: name: CosId Jackson Test @@ -56,15 +59,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test CosId-Jackson - run: gradle cosid-jackson:clean cosid-jackson:check + run: ./gradlew cosid-jackson:clean cosid-jackson:check cosid-axon-test: name: CosId Axon Test @@ -75,15 +78,91 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test CosId-Axon - run: gradle cosid-axon:clean cosid-axon:check + run: ./gradlew cosid-axon:clean cosid-axon:check + + cosid-activiti-test: + name: CosId Activiti Test + needs: [ cosid-core-test ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + - name: Test CosId-Activiti + run: ./gradlew cosid-activiti:clean cosid-activiti:check + + cosid-flowable-test: + name: CosId Flowable Test + needs: [ cosid-core-test ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + - name: Test CosId-Flowable + run: ./gradlew cosid-flowable:clean cosid-flowable:check + + cosid-mybatis-test: + name: CosId Mybatis Test + needs: [ cosid-core-test ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + - name: Test CosId-Mybatis + run: ./gradlew cosid-mybatis:clean cosid-mybatis:check + + cosid-spring-jdbc-test: + name: CosId Spring Jdbc Test + needs: [ cosid-core-test ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + - name: Test CosId-Spring-Jdbc + run: ./gradlew cosid-spring-data-jdbc:clean cosid-spring-data-jdbc:check cosid-spring-redis-test: name: CosId Spring Redis Test @@ -104,15 +183,34 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test CosId-Spring-Redis - run: gradle cosid-spring-redis:clean cosid-spring-redis:check + run: ./gradlew cosid-spring-redis:clean cosid-spring-redis:check + + cosid-mongo-test: + name: CosId Mongo Test + needs: [ cosid-core-test ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + server-id: github + settings-path: ${{ github.workspace }} + + - name: Test CosId-Mongo + run: ./gradlew cosid-mongo:clean cosid-mongo:check cosid-proxy-test: name: CosId Proxy Test @@ -123,15 +221,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test CosId-Proxy - run: gradle cosid-proxy:clean cosid-proxy:check + run: ./gradlew cosid-proxy:clean cosid-proxy:check cosid-zookeeper-test: name: CosId Zookeeper Test @@ -142,15 +240,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Test CosId-Zookeeper - run: gradle cosid-zookeeper:clean cosid-zookeeper:check + run: ./gradlew cosid-zookeeper:clean cosid-zookeeper:check # https://github.com/actions/virtual-environments/issues/375 # https://github.blog/changelog/2020-02-21-github-actions-breaking-change-ubuntu-virtual-environments-will-no-longer-start-the-mysql-service-automatically/ @@ -168,10 +266,10 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} @@ -179,28 +277,7 @@ jobs: run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql - name: Test CosId-Jdbc - run: gradle cosid-jdbc:clean cosid-jdbc:check - - cosid-mongo-test: - name: CosId Mongo Test - needs: [ cosid-core-test ] - runs-on: ubuntu-latest - env: - CI: GITHUB_ACTIONS - steps: - - name: Checkout - uses: actions/checkout@master - - - name: Set up JDK 8 - uses: actions/setup-java@v3 - with: - java-version: '8' - distribution: 'adopt' - server-id: github - settings-path: ${{ github.workspace }} - - - name: Test CosId-Mongo - run: gradle cosid-mongo:clean cosid-mongo:check + run: ./gradlew cosid-jdbc:clean cosid-jdbc:check cosid-spring-boot-starter-test: name: CosId Spring Boot Starter Test @@ -214,10 +291,10 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} @@ -225,4 +302,4 @@ jobs: run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql - name: Test CosId-Spring-Boot-Starter - run: gradle cosid-spring-boot-starter:clean cosid-spring-boot-starter:check + run: ./gradlew cosid-spring-boot-starter:clean cosid-spring-boot-starter:check diff --git a/.github/workflows/package-deploy.yml b/.github/workflows/package-deploy.yml index f6e9fa4d03..f03a0dcca6 100644 --- a/.github/workflows/package-deploy.yml +++ b/.github/workflows/package-deploy.yml @@ -27,15 +27,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Publish package - run: gradle publishAllPublicationsToGitHubPackagesRepository + run: ./gradlew publishAllPublicationsToGitHubPackagesRepository env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SIGNING_KEYID: ${{ secrets.SIGNING_KEYID }} @@ -48,15 +48,15 @@ jobs: uses: actions/checkout@master - name: Set up JDK 8 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: '8' - distribution: 'adopt' + distribution: 'temurin' server-id: github settings-path: ${{ github.workspace }} - name: Publish package - run: gradle publishToSonatype closeAndReleaseSonatypeStagingRepository + run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} diff --git a/.idea/copyright/ahoo_wang.xml b/.idea/copyright/ahoo_wang.xml new file mode 100644 index 0000000000..7edf9a0583 --- /dev/null +++ b/.idea/copyright/ahoo_wang.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000000..121e225a80 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8e448330ae..3169048910 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2021] [ahoo wang ] + Copyright [2023] [ahoo wang ] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index d1b4e4130c..9f0ed97272 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ generator. - `RedisIdSegmentDistributor`: `IdSegment` distributor based on *Redis*. - `JdbcIdSegmentDistributor`: The *Jdbc-based* `IdSegment` distributor supports various relational databases. - `ZookeeperIdSegmentDistributor`: `IdSegment` distributor based on *Zookeeper*. + - `MongoIdSegmentDistributor`: `IdSegment` distributor based on *MongoDB*. - `SegmentChainId`(**recommend**):`SegmentChainId` (*lock-free*) is an enhancement of `SegmentId`, the design diagram is as follows. `PrefetchWorker` maintains a `safe distance`, so that `SegmentChainId` achieves approximately `AtomicLong` *TPS performance: 127,439,148+ ops/s* [JMH Benchmark](#jmh-benchmark) . @@ -709,6 +710,14 @@ SnowflakeIdBenchmark.secondSnowflakeId_generate thrpt 4206843. Percentile-Sample-Of-SegmentChainId

+### CosId VS MeiTuan Leaf + +> CosId (`SegmentChainId`) is 5 times faster than Leaf(`segment`). + +

+ CosId VS MeiTuan Leaf +

+ ## Community Partners and Sponsors diff --git a/README.zh-CN.md b/README.zh-CN.md index 2279a14b38..7e8088a8d6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -24,6 +24,7 @@ - `RedisIdSegmentDistributor`: 基于 *Redis* 的号段分发器。 - `JdbcIdSegmentDistributor`: 基于 *Jdbc* 的号段分发器,支持各种关系型数据库。 - `ZookeeperIdSegmentDistributor`: 基于 *Zookeeper* 的号段分发器。 + - `MongoIdSegmentDistributor`: 基于 *MongoDB* 的号段分发器。 - `SegmentChainId`(**推荐**):`SegmentChainId` (*lock-free*) 是对 `SegmentId` 的增强。性能可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s* [JMH 基准测试](https://cosid.ahoo.me/guide/perf-test.html) 。 - `PrefetchWorker` 维护安全距离(`safeDistance`), 并且支持基于饥饿状态的动态`safeDistance`扩容/收缩。 @@ -355,3 +356,11 @@ spring:

Percentile-Sample-Of-SegmentChainId

+ +### CosId VS 美团 Leaf + +> CosId (`SegmentChainId`) 性能是 Leaf(`segment`) 的 5 倍。 + +

+ CosId VS 美团 Leaf +

diff --git a/build.gradle.kts b/build.gradle.kts index 92d346aac2..57e6839355 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.testretry.TestRetryPlugin + /* * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,8 +15,10 @@ */ plugins { - id("io.github.gradle-nexus.publish-plugin") - id("me.champeau.jmh") + alias(libs.plugins.testRetry) + alias(libs.plugins.publishPlugin) + alias(libs.plugins.jmhPlugin) + alias(libs.plugins.spotbugsPlugin) `java-library` jacoco } @@ -35,18 +40,17 @@ val serverProjects = setOf( project(":cosid-proxy-server") ) -val testProject = project(":cosid-test") +val testProjects = setOf(project(":cosid-test"), project(":cosid-mod-test")) val codeCoverageReportProject = project(":code-coverage-report") val publishProjects = subprojects - serverProjects - codeCoverageReportProject val libraryProjects = publishProjects - bomProjects - -ext { - set("libraryProjects", libraryProjects) -} +val isInCI = null != System.getenv("CI") +ext.set("libraryProjects", libraryProjects) allprojects { repositories { mavenLocal() + maven { url = uri("https://repo.spring.io/milestone") } mavenCentral() } } @@ -112,11 +116,24 @@ configure(libraryProjects) { fork.set(1) jvmArgs.set(listOf("-Dlogback.configurationFile=${rootProject.rootDir}/config/logback-jmh.xml")) } - + apply() tasks.withType { useJUnitPlatform() - // fix logging missing code for JacocoPlugin - jvmArgs = listOf("-Dlogback.configurationFile=${rootProject.rootDir}/config/logback.xml") + testLogging { + exceptionFormat = TestExceptionFormat.FULL + } + jvmArgs = + listOf( + // fix logging missing code for JacocoPlugin + "-Dlogback.configurationFile=${rootProject.rootDir}/config/logback.xml" + ) + retry { + if (isInCI) { + maxRetries = 2 + maxFailures = 20 + } + failOnPassedAfterRetry = true + } } dependencies { @@ -199,7 +216,6 @@ configure(publishProjects) { } configure { - val isInCI = null != System.getenv("CI"); if (isInCI) { val signingKeyId = System.getenv("SIGNING_KEYID") val signingKey = System.getenv("SIGNING_SECRETKEY") @@ -216,7 +232,7 @@ configure(publishProjects) { } nexusPublishing { - repositories { + this.repositories { sonatype { username.set(System.getenv("MAVEN_USERNAME")) password.set(System.getenv("MAVEN_PASSWORD")) diff --git a/code-coverage-report/build.gradle.kts b/code-coverage-report/build.gradle.kts index ad31a89d99..b850e24893 100644 --- a/code-coverage-report/build.gradle.kts +++ b/code-coverage-report/build.gradle.kts @@ -13,6 +13,7 @@ plugins { base + java id("jacoco-report-aggregation") } @@ -35,4 +36,14 @@ reporting { tasks.check { dependsOn(tasks.named("codeCoverageReport")) +} + +tasks.register("aggregateJavadoc") { + title = "CosId | 通用、灵活、高性能的分布式 ID 生成器" + options.header("
GitHub") + options.destinationDirectory = rootProject.layout.buildDirectory.dir("aggregatedJavadoc").get().asFile + libraryProjects.forEach { + source += it.sourceSets["main"].allJava + classpath += it.sourceSets["main"].compileClasspath + } } \ No newline at end of file diff --git a/config/spotbugs/exclude.xml b/config/spotbugs/exclude.xml index c662ab19c4..e6be9e5cac 100644 --- a/config/spotbugs/exclude.xml +++ b/config/spotbugs/exclude.xml @@ -9,20 +9,42 @@ - + - + - + - + + + + + + - + + + + + + + + + + + + + + + + + + diff --git a/cosid-activiti/build.gradle.kts b/cosid-activiti/build.gradle.kts new file mode 100644 index 0000000000..001c07cfcc --- /dev/null +++ b/cosid-activiti/build.gradle.kts @@ -0,0 +1,6 @@ +dependencies { + implementation(libs.activitiEngine) + api(project(":cosid-core")) + testImplementation(project(":cosid-test")) + +} diff --git a/cosid-activiti/src/main/java/me/ahoo/cosid/activiti/ActivitiIdGenerator.java b/cosid-activiti/src/main/java/me/ahoo/cosid/activiti/ActivitiIdGenerator.java new file mode 100644 index 0000000000..9de732ca04 --- /dev/null +++ b/cosid-activiti/src/main/java/me/ahoo/cosid/activiti/ActivitiIdGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.activiti; + +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.provider.LazyIdGenerator; + +/** + * Activiti IdGenerator Based on CosId. + */ +public class ActivitiIdGenerator implements org.activiti.engine.impl.cfg.IdGenerator { + /** + * The key of the system property that can be used to set the id generator name. + */ + public static final String ID_KEY = "cosid.activiti"; + private static final String ID_NAME; + private static final LazyIdGenerator ID_GENERATOR; + + static { + ID_NAME = System.getProperty(ID_KEY, IdGeneratorProvider.SHARE); + ID_GENERATOR = new LazyIdGenerator(ID_NAME); + } + + public String getNextId() { + return ID_GENERATOR.generateAsString(); + } +} diff --git a/cosid-activiti/src/test/java/me/ahoo/cosid/activiti/ActivitiIdGeneratorTest.java b/cosid-activiti/src/test/java/me/ahoo/cosid/activiti/ActivitiIdGeneratorTest.java new file mode 100644 index 0000000000..491186e5c6 --- /dev/null +++ b/cosid-activiti/src/test/java/me/ahoo/cosid/activiti/ActivitiIdGeneratorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.activiti; + +import static me.ahoo.cosid.activiti.ActivitiIdGenerator.ID_KEY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +class ActivitiIdGeneratorTest { + + @SetSystemProperty(key = ID_KEY, value = "activiti") + @Test + void getNextId() { + DefaultIdGeneratorProvider.INSTANCE.set("activiti", MockIdGenerator.usePrefix("activiti_")); + String id = new ActivitiIdGenerator().getNextId(); + assertThat(id, startsWith("activiti_")); + } +} \ No newline at end of file diff --git a/cosid-axon/build.gradle.kts b/cosid-axon/build.gradle.kts index e402daaf9a..0ccf99a612 100644 --- a/cosid-axon/build.gradle.kts +++ b/cosid-axon/build.gradle.kts @@ -1,5 +1,5 @@ dependencies { - api(platform("org.axonframework:axon-bom:4.7.4")) + api(platform(libs.axonBom)) api(project(":cosid-core")) testImplementation(project(":cosid-test")) implementation("org.axonframework:axon-messaging") diff --git a/cosid-benchmark/build.gradle.kts b/cosid-benchmark/build.gradle.kts index 9610c75118..7124b876f8 100644 --- a/cosid-benchmark/build.gradle.kts +++ b/cosid-benchmark/build.gradle.kts @@ -13,24 +13,26 @@ plugins { `java-library` - id("me.champeau.jmh") version "0.7.1" + id("me.champeau.jmh") version "0.7.2" } java { toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) + languageVersion.set(JavaLanguageVersion.of(17)) } } repositories { mavenLocal() + maven { url = uri("https://repo.spring.io/milestone") } mavenCentral() } dependencies { - implementation("me.ahoo.cosid:cosid-jdbc:1.19.2") - implementation("me.ahoo.cosid:cosid-test:1.19.2") - testImplementation("com.zaxxer:HikariCP:5.0.1") + api(platform(libs.cosidBom)) + implementation("me.ahoo.cosid:cosid-jdbc") + implementation("me.ahoo.cosid:cosid-test") + testImplementation("com.zaxxer:HikariCP:5.1.0") testImplementation("mysql:mysql-connector-java:8.0.33") /** * WARNING:中央仓库没有找到美团官方提供的Jar!!! @@ -45,12 +47,12 @@ dependencies { */ // testImplementation("com.xiaoju.uemc.tinyid:tinyid-client:0.1.0-SNAPSHOT") - jmh("org.openjdk.jmh:jmh-core:1.36") - jmh("org.openjdk.jmh:jmh-generator-annprocess:1.36") + jmh("org.openjdk.jmh:jmh-core:1.37") + jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.3") } jmh { @@ -69,7 +71,7 @@ jmh { excludes.set(jmhExcludes) } - jmhVersion.set("1.36") + jmhVersion.set("1.37") warmupIterations.set(1) iterations.set(1) resultFormat.set("json") diff --git a/cosid-benchmark/gradle.properties b/cosid-benchmark/gradle.properties index ed7d04f756..f013327215 100644 --- a/cosid-benchmark/gradle.properties +++ b/cosid-benchmark/gradle.properties @@ -12,4 +12,4 @@ # group=me.ahoo.cosid.benchmark -version=1.12.0 +version=2.2.6 diff --git a/cosid-benchmark/gradle/libs.versions.toml b/cosid-benchmark/gradle/libs.versions.toml new file mode 100644 index 0000000000..52c0333990 --- /dev/null +++ b/cosid-benchmark/gradle/libs.versions.toml @@ -0,0 +1,21 @@ +[versions] +cosid = "2.9.0" +jmh = "1.37" +junit="5.10.3" +hikariCP = "5.1.0" +mysql="8.0.33" + +jmhPlugin = "0.7.2" + +[libraries] +cosidBom = { module = "me.ahoo.cosid:cosid-bom", version.ref = "cosid" } +hikariCP = { module = "com.zaxxer:HikariCP", version.ref = "hikariCP" } +mysql = { module = "mysql:mysql-connector-java", version.ref = "mysql" } +jmhCore = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } +jmhGeneratorAnnprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } +junitJupiterApi = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } +junitJupiterParams = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } +junitJupiterEngine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } + +[plugins] +jmhPlugin = { id = "me.champeau.jmh", version.ref = "jmhPlugin" } \ No newline at end of file diff --git a/cosid-benchmark/gradle/wrapper/gradle-wrapper.jar b/cosid-benchmark/gradle/wrapper/gradle-wrapper.jar index c1962a79e2..e6441136f3 100644 Binary files a/cosid-benchmark/gradle/wrapper/gradle-wrapper.jar and b/cosid-benchmark/gradle/wrapper/gradle-wrapper.jar differ diff --git a/cosid-benchmark/gradle/wrapper/gradle-wrapper.properties b/cosid-benchmark/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0..a4413138c9 100644 --- a/cosid-benchmark/gradle/wrapper/gradle-wrapper.properties +++ b/cosid-benchmark/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/cosid-benchmark/gradlew b/cosid-benchmark/gradlew index aeb74cbb43..b740cf1339 100755 --- a/cosid-benchmark/gradlew +++ b/cosid-benchmark/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/cosid-benchmark/gradlew.bat b/cosid-benchmark/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/cosid-benchmark/gradlew.bat +++ b/cosid-benchmark/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/cosid-core/src/main/java/me/ahoo/cosid/Decorator.java b/cosid-core/src/main/java/me/ahoo/cosid/Decorator.java new file mode 100644 index 0000000000..326b1daeee --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/Decorator.java @@ -0,0 +1,30 @@ +package me.ahoo.cosid; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Decorator. + * + * @author ahoo wang + */ +@ThreadSafe +public interface Decorator { + /** + * Get decorator actual id generator. + * + * @return actual id generator + */ + @Nonnull + D getActual(); + + @SuppressWarnings({"unchecked", "rawtypes"}) + static D getActual(D any) { + if (any instanceof Decorator) { + Decorator decorator = (Decorator) any; + return getActual((D) decorator.getActual()); + } + return any; + } + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/IdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/IdConverter.java index 52caf3010f..5ceaf91761 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/IdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/IdConverter.java @@ -13,6 +13,9 @@ package me.ahoo.cosid; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.Statistical; + import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; @@ -22,7 +25,7 @@ * @author ahoo wang */ @ThreadSafe -public interface IdConverter { +public interface IdConverter extends Statistical { /** * convert {@code long} type ID to {@link String}. @@ -40,4 +43,9 @@ public interface IdConverter { * @return {@code long} type ID */ long asLong(@Nonnull String idString); + + @Override + default Stat stat() { + return Stat.simple(getClass().getSimpleName()); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/IdGenerator.java b/cosid-core/src/main/java/me/ahoo/cosid/IdGenerator.java index b69ea68c16..7f329b3251 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/IdGenerator.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/IdGenerator.java @@ -14,6 +14,9 @@ package me.ahoo.cosid; import me.ahoo.cosid.converter.Radix62IdConverter; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.Statistical; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; @@ -24,7 +27,7 @@ * @author ahoo wang */ @ThreadSafe -public interface IdGenerator extends StringIdGenerator { +public interface IdGenerator extends StringIdGenerator, Statistical { /** * ID converter, used to convert {@code long} type ID to {@link String}. @@ -48,4 +51,9 @@ default IdConverter idConverter() { default String generateAsString() { return idConverter().asString(generate()); } + + @Override + default IdGeneratorStat stat() { + return IdGeneratorStat.simple(getClass().getSimpleName(), idConverter().stat()); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/IdGeneratorDecorator.java b/cosid-core/src/main/java/me/ahoo/cosid/IdGeneratorDecorator.java index 8b8a3b9167..0fed3e3026 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/IdGeneratorDecorator.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/IdGeneratorDecorator.java @@ -1,5 +1,7 @@ package me.ahoo.cosid; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; + import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; @@ -9,7 +11,7 @@ * @author ahoo wang */ @ThreadSafe -public interface IdGeneratorDecorator extends IdGenerator { +public interface IdGeneratorDecorator extends IdGenerator, Decorator { /** * Get decorator actual id generator. * @@ -17,18 +19,18 @@ public interface IdGeneratorDecorator extends IdGenerator { */ @Nonnull IdGenerator getActual(); - - @SuppressWarnings("unchecked") - static T getActual(IdGenerator idGenerator) { - if (idGenerator instanceof IdGeneratorDecorator) { - return getActual(((IdGeneratorDecorator) idGenerator).getActual()); - } - return (T) idGenerator; + + static T getActual(T idGenerator) { + return Decorator.getActual(idGenerator); } - + @Override default long generate() { return getActual().generate(); } - + + @Override + default IdGeneratorStat stat() { + return IdGeneratorStat.simple(getClass().getSimpleName(), getActual().stat(), idConverter().stat()); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/accessor/parser/CompositeFieldDefinitionParser.java b/cosid-core/src/main/java/me/ahoo/cosid/accessor/parser/CompositeFieldDefinitionParser.java new file mode 100644 index 0000000000..9e617c8217 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/accessor/parser/CompositeFieldDefinitionParser.java @@ -0,0 +1,44 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.accessor.parser; + +import me.ahoo.cosid.accessor.IdDefinition; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Composite {@link FieldDefinitionParser}. + * + * @see FieldDefinitionParser + * @see NamedDefinitionParser + */ +public class CompositeFieldDefinitionParser implements FieldDefinitionParser { + private final List fieldDefinitionParsers; + + public CompositeFieldDefinitionParser(List fieldDefinitionParsers) { + this.fieldDefinitionParsers = fieldDefinitionParsers; + } + + @Override + public IdDefinition parse(List> lookupClassList, Field field) { + for (FieldDefinitionParser fieldDefinitionParser : fieldDefinitionParsers) { + IdDefinition idDefinition = fieldDefinitionParser.parse(lookupClassList, field); + if (idDefinition != IdDefinition.NOT_FOUND) { + return idDefinition; + } + } + return IdDefinition.NOT_FOUND; + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/DatePrefixIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/DatePrefixIdConverter.java new file mode 100644 index 0000000000..24460fc315 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/DatePrefixIdConverter.java @@ -0,0 +1,62 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.converter; + +import me.ahoo.cosid.Decorator; +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.converter.DatePrefixConverterStat; + +import javax.annotation.Nonnull; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class DatePrefixIdConverter implements IdConverter, Decorator { + private final String pattern; + private final DateTimeFormatter formatter; + private final String delimiter; + private final IdConverter actual; + + public DatePrefixIdConverter(String pattern, String delimiter, IdConverter actual) { + this.pattern = pattern; + this.formatter = DateTimeFormatter.ofPattern(pattern); + this.delimiter = delimiter; + this.actual = actual; + } + + + @Nonnull + @Override + public String asString(long id) { + return LocalDateTime.now().format(formatter) + delimiter + actual.asString(id); + } + + @Override + public long asLong(@Nonnull String idString) { + int appendedLength = pattern.length() + delimiter.length(); + String idStr = idString.substring(appendedLength); + return actual.asLong(idStr); + } + + @Nonnull + @Override + public IdConverter getActual() { + return actual; + } + + @Override + public Stat stat() { + return new DatePrefixConverterStat(getClass().getSimpleName(), pattern, actual.stat()); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/GroupedPrefixIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/GroupedPrefixIdConverter.java new file mode 100644 index 0000000000..c8f93ea00e --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/GroupedPrefixIdConverter.java @@ -0,0 +1,67 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.converter; + +import me.ahoo.cosid.Decorator; +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.segment.grouped.GroupedAccessor; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.converter.GroupedPrefixConverterStat; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nonnull; + +public class GroupedPrefixIdConverter implements IdConverter, Decorator { + public static final String DEFAULT_DELIMITER = "-"; + private final String delimiter; + private final IdConverter actual; + + public GroupedPrefixIdConverter(String delimiter, IdConverter actual) { + Preconditions.checkNotNull(delimiter, "prefix can not be null!"); + this.delimiter = delimiter; + this.actual = actual; + } + + @Nonnull + @Override + public IdConverter getActual() { + return actual; + } + + public String getDelimiter() { + return delimiter; + } + + @Nonnull + @Override + public String asString(long id) { + String idStr = actual.asString(id); + String groupKey = GroupedAccessor.requiredGet().getKey(); + if (delimiter.isEmpty()) { + return groupKey + idStr; + } + return groupKey + delimiter + idStr; + } + + @Override + public long asLong(@Nonnull String idString) { + throw new UnsupportedOperationException("GroupedPrefixIdConverter does not support converting String to Long!"); + } + + @Override + public Stat stat() { + return new GroupedPrefixConverterStat(getClass().getSimpleName(), delimiter, actual.stat()); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/PrefixIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/PrefixIdConverter.java index 1eae38c704..8ade11f6f0 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/converter/PrefixIdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/PrefixIdConverter.java @@ -13,7 +13,10 @@ package me.ahoo.cosid.converter; +import me.ahoo.cosid.Decorator; import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.converter.PrefixConverterStat; import com.google.common.base.Preconditions; @@ -24,15 +27,21 @@ * * @author ahoo wang */ -public class PrefixIdConverter implements IdConverter { +public class PrefixIdConverter implements IdConverter, Decorator { private final String prefix; - private final IdConverter idConverter; + private final IdConverter actual; - public PrefixIdConverter(String prefix, IdConverter idConverter) { + public PrefixIdConverter(String prefix, IdConverter actual) { Preconditions.checkNotNull(prefix, "prefix can not be null!"); this.prefix = prefix; - this.idConverter = idConverter; + this.actual = actual; + } + + @Nonnull + @Override + public IdConverter getActual() { + return actual; } public String getPrefix() { @@ -42,7 +51,7 @@ public String getPrefix() { @Nonnull @Override public String asString(long id) { - String idStr = idConverter.asString(id); + String idStr = actual.asString(id); if (prefix.isEmpty()) { return idStr; } @@ -52,6 +61,11 @@ public String asString(long id) { @Override public long asLong(@Nonnull String idString) { String idStr = idString.substring(prefix.length()); - return idConverter.asLong(idStr); + return actual.asLong(idStr); + } + + @Override + public Stat stat() { + return new PrefixConverterStat(getClass().getSimpleName(), prefix, actual.stat()); } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/Radix36IdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/Radix36IdConverter.java index 2653f27642..64e556fb82 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/converter/Radix36IdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/Radix36IdConverter.java @@ -19,13 +19,13 @@ * @author ahoo wang */ public final class Radix36IdConverter extends RadixIdConverter { - + public static final int MAX_CHAR_SIZE = 13; public static final int RADIX = 36; - + public static final Radix36IdConverter INSTANCE = new Radix36IdConverter(false, MAX_CHAR_SIZE); public static final Radix36IdConverter PAD_START = new Radix36IdConverter(true, MAX_CHAR_SIZE); - + /** * Return an instance representing the specified parameter. * If new instances are not required, static cached instances are used to provide space and time efficiency. @@ -35,27 +35,27 @@ public final class Radix36IdConverter extends RadixIdConverter { * @return Radix62IdConverter */ public static Radix36IdConverter of(boolean padStart, int charSize) { - + if (INSTANCE.isPadStart() == padStart && INSTANCE.getCharSize() == charSize) { return INSTANCE; } - + if (PAD_START.isPadStart() == padStart && PAD_START.getCharSize() == charSize) { return PAD_START; } - + return new Radix36IdConverter(padStart, charSize); } - + public Radix36IdConverter(boolean padStart, int charSize) { super(padStart, charSize); } - + @Override int getRadix() { return RADIX; } - + @Override int getMaxCharSize() { return MAX_CHAR_SIZE; diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/RadixIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/RadixIdConverter.java index c92f6cf7fb..3a4072373a 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/converter/RadixIdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/RadixIdConverter.java @@ -14,6 +14,8 @@ package me.ahoo.cosid.converter; import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.converter.RadixConverterStat; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -116,6 +118,10 @@ public int getCharSize() { return charSize; } + public long getMaxId() { + return maxId; + } + abstract int getRadix(); abstract int getMaxCharSize(); @@ -173,4 +179,9 @@ public long asLong(@Nonnull String idString) { } return result; } + + @Override + public Stat stat() { + return new RadixConverterStat(getClass().getSimpleName(), getRadix(), getCharSize(), isPadStart(), getMaxId()); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverter.java index dd092c76af..7db016974f 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverter.java @@ -48,4 +48,5 @@ public String asString(long id) { public long asLong(@Nonnull String idString) { return snowflakeIdStateParser.parse(idString).getId(); } + } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/SuffixIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/SuffixIdConverter.java index 67bd7f1dde..1c7dc1b6d6 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/converter/SuffixIdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/SuffixIdConverter.java @@ -13,8 +13,10 @@ package me.ahoo.cosid.converter; - +import me.ahoo.cosid.Decorator; import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.converter.SuffixConverterStat; import com.google.common.base.Preconditions; @@ -25,33 +27,44 @@ * * @author ahoo wang */ -public class SuffixIdConverter implements IdConverter { +public class SuffixIdConverter implements IdConverter, Decorator { private final String suffix; - private final IdConverter idConverter; - - public SuffixIdConverter(String suffix, IdConverter idConverter) { + private final IdConverter actual; + + public SuffixIdConverter(String suffix, IdConverter actual) { Preconditions.checkNotNull(suffix, "suffix can not be null!"); this.suffix = suffix; - this.idConverter = idConverter; + this.actual = actual; + } + + @Nonnull + @Override + public IdConverter getActual() { + return actual; } - + public String getSuffix() { return suffix; } - + @Nonnull @Override public String asString(long id) { - String idStr = idConverter.asString(id); + String idStr = actual.asString(id); if (suffix.isEmpty()) { return idStr; } return idStr + suffix; } - + @Override public long asLong(@Nonnull String idString) { String idStr = idString.substring(0, idString.length() - suffix.length()); - return idConverter.asLong(idStr); + return actual.asLong(idStr); + } + + @Override + public Stat stat() { + return new SuffixConverterStat(getClass().getSimpleName(), suffix, actual.stat()); } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/converter/ToStringIdConverter.java b/cosid-core/src/main/java/me/ahoo/cosid/converter/ToStringIdConverter.java index e7bb7330b7..0ea56ac17a 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/converter/ToStringIdConverter.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/converter/ToStringIdConverter.java @@ -16,6 +16,8 @@ import static me.ahoo.cosid.converter.RadixIdConverter.PAD_CHAR; import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.converter.ToStringConverterStat; import com.google.common.base.Strings; @@ -27,16 +29,16 @@ * @author ahoo wang */ public class ToStringIdConverter implements IdConverter { - + public static final ToStringIdConverter INSTANCE = new ToStringIdConverter(false, 0); private final boolean padStart; private final int charSize; - + public ToStringIdConverter(boolean padStart, int charSize) { this.padStart = padStart; this.charSize = charSize; } - + @Nonnull @Override public String asString(long id) { @@ -46,9 +48,14 @@ public String asString(long id) { } return Strings.padStart(idStr, charSize, PAD_CHAR); } - + @Override public long asLong(@Nonnull String idString) { return Long.parseLong(idString); } + + @Override + public Stat stat() { + return new ToStringConverterStat(getClass().getSimpleName(), padStart, charSize); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/cosid/ClockSyncCosIdGenerator.java b/cosid-core/src/main/java/me/ahoo/cosid/cosid/ClockSyncCosIdGenerator.java index 0f50eea166..208bb80ed7 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/cosid/ClockSyncCosIdGenerator.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/cosid/ClockSyncCosIdGenerator.java @@ -13,15 +13,34 @@ package me.ahoo.cosid.cosid; +import me.ahoo.cosid.Decorator; import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; import me.ahoo.cosid.snowflake.exception.ClockBackwardsException; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; +/** + * ClockSync {@link CosIdGenerator}. + *

+ * If the clock is backwards, synchronize the clock and retry generating the Id. + *

+ * + * @see ClockBackwardsSynchronizer + * @see CosIdGenerator + * @see CosIdIdStateParser + * @see CosIdState + * @see Decorator + * @see IdGeneratorStat + * @see Stat + * @see ClockBackwardsException + * @see ClockBackwardsSynchronizer + */ @Slf4j -public class ClockSyncCosIdGenerator implements CosIdGenerator { +public class ClockSyncCosIdGenerator implements CosIdGenerator, Decorator { private final CosIdGenerator actual; private final ClockBackwardsSynchronizer clockBackwardsSynchronizer; @@ -34,6 +53,12 @@ public ClockSyncCosIdGenerator(CosIdGenerator actual, ClockBackwardsSynchronizer this.clockBackwardsSynchronizer = clockBackwardsSynchronizer; } + @Nonnull + @Override + public CosIdGenerator getActual() { + return actual; + } + @Override public int getMachineId() { return actual.getMachineId(); @@ -64,4 +89,8 @@ public CosIdState generateAsState() { } } + @Override + public IdGeneratorStat stat() { + return IdGeneratorStat.simple(getClass().getSimpleName(), actual.stat(), Stat.simple(getStateParser().getClass().getSimpleName())); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdGenerator.java b/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdGenerator.java index 4e56d7d34d..4332aa2552 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdGenerator.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdGenerator.java @@ -15,6 +15,9 @@ import me.ahoo.cosid.IdConverter; import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.stat.Stat; +import me.ahoo.cosid.stat.generator.CosIdGeneratorStat; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; import javax.annotation.Nonnull; @@ -52,4 +55,9 @@ default long generate() { default String generateAsString() { return getStateParser().asString(generateAsState()); } + + @Override + default IdGeneratorStat stat() { + return new CosIdGeneratorStat(getClass().getSimpleName(), getMachineId(), getLastTimestamp(), Stat.simple(getStateParser().getClass().getSimpleName())); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdIdStateParser.java b/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdIdStateParser.java index d123e820eb..1e57ba2048 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdIdStateParser.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdIdStateParser.java @@ -13,6 +13,12 @@ package me.ahoo.cosid.cosid; +/** + * Parser for converting {@link CosIdState} to String and vice versa. + *

+ * The {@link CosIdState} is a composite of timestamp, machineId, and sequence. + *

+ */ public interface CosIdIdStateParser { CosIdState asState(String id); diff --git a/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdState.java b/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdState.java index 9da54d7174..152c6f83fc 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdState.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/cosid/CosIdState.java @@ -17,6 +17,12 @@ import java.util.Objects; +/** + * CosId State. + *

+ * The {@link CosIdState} is a composite of timestamp, machineId, and sequence. + *

+ */ public final class CosIdState implements Comparable { private final long timestamp; private final int machineId; diff --git a/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdGenerator.java b/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdGenerator.java index 7910bbd46f..f7cfc1cc36 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdGenerator.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdGenerator.java @@ -16,12 +16,22 @@ import me.ahoo.cosid.snowflake.exception.ClockBackwardsException; import me.ahoo.cosid.snowflake.exception.TimestampOverflowException; -import com.google.common.annotations.Beta; import com.google.common.base.Strings; import javax.annotation.Nonnull; -@Beta +/** + * Radix CosIdGenerator. + *

+ * It's a simple implementation of {@link CosIdGenerator}. + *

+ * + * @see CosIdGenerator + * @see CosIdIdStateParser + * @see CosIdState + * @see ClockBackwardsException + * @see TimestampOverflowException + */ public class RadixCosIdGenerator implements CosIdGenerator { public static final int DEFAULT_TIMESTAMP_BIT = 44; public static final int DEFAULT_MACHINE_BIT = 20; diff --git a/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdStateParser.java b/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdStateParser.java index b37212c1df..d87839cc48 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdStateParser.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/cosid/RadixCosIdStateParser.java @@ -19,7 +19,13 @@ import com.google.common.base.Preconditions; +/** + * Parser for converting {@link CosIdState} to String and vice versa. + *

+ * The {@link CosIdState} is a composite of timestamp, machineId, and sequence. + *

+ */ public class RadixCosIdStateParser implements CosIdIdStateParser { public static final RadixCosIdStateParser DEFAULT = ofRadix62(Radix62CosIdGenerator.DEFAULT_TIMESTAMP_BIT, Radix62CosIdGenerator.DEFAULT_MACHINE_BIT, Radix62CosIdGenerator.DEFAULT_SEQUENCE_BIT); diff --git a/cosid-core/src/main/java/me/ahoo/cosid/machine/HostAddressSupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/machine/HostAddressSupplier.java new file mode 100644 index 0000000000..4448b56c96 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/machine/HostAddressSupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.machine; + +@FunctionalInterface +public interface HostAddressSupplier { + String getHostAddress(); +} + diff --git a/cosid-core/src/main/java/me/ahoo/cosid/machine/LocalHostAddressSupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/machine/LocalHostAddressSupplier.java new file mode 100644 index 0000000000..301b8e0059 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/machine/LocalHostAddressSupplier.java @@ -0,0 +1,55 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.machine; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +@Slf4j +public final class LocalHostAddressSupplier implements HostAddressSupplier { + public static final LocalHostAddressSupplier INSTANCE = new LocalHostAddressSupplier(); + + @SneakyThrows + @Override + public String getHostAddress() { + String hostAddress = InetAddress.getLocalHost().getHostAddress(); + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface eachNetworkInterface = networkInterfaces.nextElement(); + if (!eachNetworkInterface.isUp() || eachNetworkInterface.isLoopback()) { + continue; + } + Enumeration inetAddresses = eachNetworkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress instanceof Inet4Address + && !inetAddress.isLoopbackAddress() + && !inetAddress.isAnyLocalAddress() + ) { + hostAddress = inetAddress.getHostAddress(); + } + } + } + } catch (Exception ex) { + log.error("Cannot get first non-loopback address", ex); + } + return hostAddress; + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/provider/DefaultIdGeneratorProvider.java b/cosid-core/src/main/java/me/ahoo/cosid/provider/DefaultIdGeneratorProvider.java index d960cee5ef..9eeea7fe3b 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/provider/DefaultIdGeneratorProvider.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/provider/DefaultIdGeneratorProvider.java @@ -17,7 +17,9 @@ import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** @@ -83,6 +85,11 @@ public void clear() { nameMapIdGen.clear(); } + @Override + public Set> entries() { + return nameMapIdGen.entrySet(); + } + @Override public Collection getAll() { return nameMapIdGen.values(); diff --git a/cosid-core/src/main/java/me/ahoo/cosid/provider/IdGeneratorProvider.java b/cosid-core/src/main/java/me/ahoo/cosid/provider/IdGeneratorProvider.java index 7aec4eb1f9..88e35e4e91 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/provider/IdGeneratorProvider.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/provider/IdGeneratorProvider.java @@ -19,7 +19,9 @@ import javax.annotation.concurrent.ThreadSafe; import java.util.Collection; +import java.util.Map; import java.util.Optional; +import java.util.Set; /** * {@link IdGenerator} container. @@ -28,6 +30,7 @@ */ @ThreadSafe public interface IdGeneratorProvider { + /** * the key of shared ID generator. */ @@ -88,6 +91,8 @@ default IdGenerator getRequired(String name) { */ void clear(); + Set> entries(); + /** * get all ID generator. * diff --git a/cosid-core/src/main/java/me/ahoo/cosid/provider/LazyIdGenerator.java b/cosid-core/src/main/java/me/ahoo/cosid/provider/LazyIdGenerator.java index 16940fd071..b5dd85414d 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/provider/LazyIdGenerator.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/provider/LazyIdGenerator.java @@ -35,7 +35,7 @@ public final class LazyIdGenerator implements IdGeneratorDecorator { private final String generatorName; - private IdGenerator lazyIdGen; + private volatile IdGenerator lazyIdGen; private final IdGeneratorProvider idGeneratorProvider; diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultIdSegment.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultIdSegment.java index 6a6bd1d075..3e4cdf5f3a 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultIdSegment.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultIdSegment.java @@ -13,6 +13,8 @@ package me.ahoo.cosid.segment; +import me.ahoo.cosid.segment.grouped.GroupedAccessor; +import me.ahoo.cosid.segment.grouped.GroupedKey; import me.ahoo.cosid.util.Clock; import com.google.common.base.Preconditions; @@ -25,9 +27,9 @@ * @author ahoo wang */ public class DefaultIdSegment implements IdSegment { - - public static final DefaultIdSegment OVERFLOW = new DefaultIdSegment(IdSegment.SEQUENCE_OVERFLOW, 0, Clock.CACHE.secondTime(), TIME_TO_LIVE_FOREVER); - + + public static final DefaultIdSegment OVERFLOW = new DefaultIdSegment(IdSegment.SEQUENCE_OVERFLOW, 0, Clock.SYSTEM.secondTime(), TIME_TO_LIVE_FOREVER, GroupedKey.NEVER); + /** * include. */ @@ -37,14 +39,14 @@ public class DefaultIdSegment implements IdSegment { private volatile long sequence; private final long fetchTime; private final long ttl; - + private final GroupedKey group; private static final AtomicLongFieldUpdater S = AtomicLongFieldUpdater.newUpdater(DefaultIdSegment.class, "sequence"); - + public DefaultIdSegment(long maxId, long step) { - this(maxId, step, Clock.CACHE.secondTime(), TIME_TO_LIVE_FOREVER); + this(maxId, step, Clock.SYSTEM.secondTime(), TIME_TO_LIVE_FOREVER, GroupedKey.NEVER); } - - public DefaultIdSegment(long maxId, long step, long fetchTime, long ttl) { + + public DefaultIdSegment(long maxId, long step, long fetchTime, long ttl, GroupedKey group) { Preconditions.checkArgument(ttl > 0, "ttl:[%s] must be greater than 0.", ttl); this.maxId = maxId; this.step = step; @@ -52,52 +54,59 @@ public DefaultIdSegment(long maxId, long step, long fetchTime, long ttl) { this.sequence = offset; this.fetchTime = fetchTime; this.ttl = ttl; + this.group = group; } - + + @Override + public GroupedKey group() { + return group; + } + @Override public long getFetchTime() { return fetchTime; } - + @Override public long getTtl() { return ttl; } - + @Override public long getMaxId() { return maxId; } - + @Override public long getOffset() { return offset; } - + @Override public long getSequence() { return sequence; } - + @Override public long getStep() { return step; } - + @Override public long incrementAndGet() { if (isOverflow()) { return SEQUENCE_OVERFLOW; } - + final long nextSeq = S.incrementAndGet(this); - + if (isOverflow(nextSeq)) { return SEQUENCE_OVERFLOW; } + GroupedAccessor.setIfNotNever(group()); return nextSeq; } - + @Override public String toString() { return "DefaultIdSegment{" diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultSegmentId.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultSegmentId.java index 867bbd82f3..fd4821b428 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultSegmentId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/DefaultSegmentId.java @@ -15,6 +15,8 @@ import static me.ahoo.cosid.segment.IdSegment.TIME_TO_LIVE_FOREVER; +import me.ahoo.cosid.segment.grouped.GroupedAccessor; + import com.google.common.base.Preconditions; import lombok.extern.slf4j.Slf4j; @@ -28,28 +30,34 @@ */ @Slf4j public class DefaultSegmentId implements SegmentId { - + private final long idSegmentTtl; private final IdSegmentDistributor maxIdDistributor; - + @GuardedBy("this") private volatile IdSegment segment = DefaultIdSegment.OVERFLOW; - + public DefaultSegmentId(IdSegmentDistributor maxIdDistributor) { this(TIME_TO_LIVE_FOREVER, maxIdDistributor); } - + public DefaultSegmentId(long idSegmentTtl, IdSegmentDistributor maxIdDistributor) { Preconditions.checkArgument(idSegmentTtl > 0, "idSegmentTtl:[%s] must be greater than 0.", idSegmentTtl); - + this.idSegmentTtl = idSegmentTtl; this.maxIdDistributor = maxIdDistributor; } - + + @Override + public IdSegment current() { + return segment; + } + @Override public long generate() { - + if (maxIdDistributor.getStep() == ONE_STEP) { + GroupedAccessor.setIfNotNever(maxIdDistributor.group()); return maxIdDistributor.nextMaxId(); } long nextSeq; @@ -59,7 +67,7 @@ public long generate() { return nextSeq; } } - + synchronized (this) { while (true) { if (segment.isAvailable()) { @@ -69,10 +77,12 @@ public long generate() { } } IdSegment nextIdSegment = maxIdDistributor.nextIdSegment(idSegmentTtl); - segment.ensureNextIdSegment(nextIdSegment); + if (!maxIdDistributor.allowReset()) { + segment.ensureNextIdSegment(nextIdSegment); + } segment = nextIdSegment; } } } - + } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegment.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegment.java index 9daf23833d..a467935955 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegment.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegment.java @@ -13,6 +13,7 @@ package me.ahoo.cosid.segment; +import me.ahoo.cosid.segment.grouped.Grouped; import me.ahoo.cosid.util.Clock; import javax.annotation.concurrent.ThreadSafe; @@ -25,7 +26,7 @@ * @author ahoo wang */ @ThreadSafe -public interface IdSegment extends Comparable { +public interface IdSegment extends Comparable, Grouped { long SEQUENCE_OVERFLOW = -1; @@ -70,7 +71,8 @@ default boolean isExpired() { */ return false; } - return Clock.CACHE.secondTime() - getFetchTime() > getTtl(); + + return Clock.SYSTEM.secondTime() - getFetchTime() > getTtl(); } default boolean isOverflow() { diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentChain.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentChain.java index 27c94ecbf1..c29106e73b 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentChain.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentChain.java @@ -13,6 +13,8 @@ package me.ahoo.cosid.segment; +import me.ahoo.cosid.segment.grouped.GroupedKey; + import java.util.function.Function; import javax.annotation.concurrent.GuardedBy; @@ -24,21 +26,23 @@ public class IdSegmentChain implements IdSegment { public static final int ROOT_VERSION = -1; public static final IdSegmentChain NOT_SET = null; - + private final long version; private final IdSegment idSegment; @GuardedBy("this") private volatile IdSegmentChain next; - - public IdSegmentChain(IdSegmentChain previousChain, IdSegment idSegment) { - this(previousChain.getVersion() + 1, idSegment); + private final boolean allowReset; + + public IdSegmentChain(IdSegmentChain previousChain, IdSegment idSegment, boolean allowReset) { + this(previousChain.getVersion() + 1, idSegment, allowReset); } - - public IdSegmentChain(long version, IdSegment idSegment) { + + public IdSegmentChain(long version, IdSegment idSegment, boolean allowReset) { this.version = version; this.idSegment = idSegment; + this.allowReset = allowReset; } - + /** * try set next Chained ID segment. * @@ -51,7 +55,7 @@ public boolean trySetNext(Function idSegmentChai if (NOT_SET != next) { return false; } - + synchronized (this) { if (NOT_SET != next) { return false; @@ -61,12 +65,15 @@ public boolean trySetNext(Function idSegmentChai return true; } } - + public void setNext(IdSegmentChain nextIdSegmentChain) { - ensureNextIdSegment(nextIdSegmentChain); + if (!allowReset) { + ensureNextIdSegment(nextIdSegmentChain); + } + next = nextIdSegmentChain; } - + public IdSegmentChain ensureSetNext(Function idSegmentChainSupplier) throws NextIdSegmentExpiredException { IdSegmentChain currentChain = this; while (!currentChain.trySetNext(idSegmentChainSupplier)) { @@ -74,57 +81,67 @@ public IdSegmentChain ensureSetNext(Function idS } return currentChain; } - + public IdSegmentChain getNext() { return next; } - + public IdSegment getIdSegment() { return idSegment; } - + + @Override + public GroupedKey group() { + return idSegment.group(); + } + public long getVersion() { return version; } - + public int gap(IdSegmentChain end, long step) { return (int) ((end.getMaxId() - getSequence()) / step); } - - public static IdSegmentChain newRoot() { - return new IdSegmentChain(IdSegmentChain.ROOT_VERSION, DefaultIdSegment.OVERFLOW); + + public static IdSegmentChain newRoot(boolean allowReset) { + return new IdSegmentChain(IdSegmentChain.ROOT_VERSION, DefaultIdSegment.OVERFLOW, allowReset); } - + @Override public long getFetchTime() { return idSegment.getFetchTime(); } - + + @Override + public long getTtl() { + return idSegment.getTtl(); + } + @Override public long getMaxId() { return idSegment.getMaxId(); } - + @Override public long getOffset() { return idSegment.getOffset(); } - + @Override public long getSequence() { return idSegment.getSequence(); } - + @Override public long getStep() { return idSegment.getStep(); } - + @Override public long incrementAndGet() { return idSegment.incrementAndGet(); } - + @Override public String toString() { return "IdSegmentChain{" diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributor.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributor.java index 17f561d86a..b6f3be8e18 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributor.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributor.java @@ -15,6 +15,8 @@ import static me.ahoo.cosid.segment.IdSegment.TIME_TO_LIVE_FOREVER; +import me.ahoo.cosid.segment.grouped.Grouped; +import me.ahoo.cosid.segment.grouped.GroupedKey; import me.ahoo.cosid.util.Clock; import com.google.common.annotations.VisibleForTesting; @@ -32,7 +34,7 @@ * * @author ahoo wang */ -public interface IdSegmentDistributor { +public interface IdSegmentDistributor extends Grouped { int DEFAULT_SEGMENTS = 1; long DEFAULT_OFFSET = 0; long DEFAULT_STEP = 10; @@ -57,6 +59,10 @@ default long getStep(int segments) { return Math.multiplyExact(getStep(), segments); } + default boolean allowReset() { + return GroupedKey.NEVER.equals(group()); + } + long nextMaxId(long step); default long nextMaxId() { @@ -73,7 +79,7 @@ default IdSegment nextIdSegment(long ttl) { Preconditions.checkArgument(ttl > 0, "ttl:[%s] must be greater than 0.", ttl); final long maxId = nextMaxId(); - return new DefaultIdSegment(maxId, getStep(), Clock.CACHE.secondTime(), ttl); + return new DefaultIdSegment(maxId, getStep(), Clock.SYSTEM.secondTime(), ttl, group()); } @Nonnull @@ -83,7 +89,7 @@ default IdSegment nextIdSegment(int segments, long ttl) { final long totalStep = getStep(segments); final long maxId = nextMaxId(totalStep); - final IdSegment nextIdSegment = new DefaultIdSegment(maxId, totalStep, Clock.CACHE.secondTime(), ttl); + final IdSegment nextIdSegment = new DefaultIdSegment(maxId, totalStep, Clock.SYSTEM.secondTime(), ttl, group()); return new MergedIdSegment(segments, nextIdSegment); } @@ -96,11 +102,11 @@ default IdSegmentChain nextIdSegmentChain(IdSegmentChain previousChain) { default IdSegmentChain nextIdSegmentChain(IdSegmentChain previousChain, int segments, long ttl) { if (DEFAULT_SEGMENTS == segments) { IdSegment nextIdSegment = nextIdSegment(ttl); - return new IdSegmentChain(previousChain, nextIdSegment); + return new IdSegmentChain(previousChain, nextIdSegment, allowReset()); } IdSegment nextIdSegment = nextIdSegment(segments, ttl); - return new IdSegmentChain(previousChain, nextIdSegment); + return new IdSegmentChain(previousChain, nextIdSegment, allowReset()); } static void ensureStep(long step) { diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributorFactory.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributorFactory.java index f7656bd646..4d2ae6a858 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributorFactory.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/IdSegmentDistributorFactory.java @@ -20,6 +20,7 @@ * * @author ahoo wang */ +@FunctionalInterface public interface IdSegmentDistributorFactory { @Nonnull IdSegmentDistributor create(IdSegmentDistributorDefinition definition); diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/MergedIdSegment.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/MergedIdSegment.java index 161187b21b..974d511765 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/MergedIdSegment.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/MergedIdSegment.java @@ -13,6 +13,8 @@ package me.ahoo.cosid.segment; +import me.ahoo.cosid.segment.grouped.GroupedKey; + import java.util.concurrent.TimeUnit; /** @@ -32,6 +34,11 @@ public MergedIdSegment(int segments, IdSegment idSegment) { this.singleStep = idSegment.getStep() / segments; } + @Override + public GroupedKey group() { + return idSegment.group(); + } + public int getSegments() { return segments; } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentChainId.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentChainId.java index 68b4fc6619..9ffbd4dc68 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentChainId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentChainId.java @@ -32,13 +32,13 @@ */ @Slf4j public class SegmentChainId implements SegmentId { - public static final int DEFAULT_SAFE_DISTANCE = 1; + public static final int DEFAULT_SAFE_DISTANCE = 2; private final long idSegmentTtl; private final int safeDistance; private final IdSegmentDistributor maxIdDistributor; private final PrefetchJob prefetchJob; - private volatile IdSegmentChain headChain = IdSegmentChain.newRoot(); + private volatile IdSegmentChain headChain; public SegmentChainId(IdSegmentDistributor maxIdDistributor) { this(TIME_TO_LIVE_FOREVER, DEFAULT_SAFE_DISTANCE, maxIdDistributor, PrefetchWorkerExecutorService.DEFAULT); @@ -47,6 +47,7 @@ public SegmentChainId(IdSegmentDistributor maxIdDistributor) { public SegmentChainId(long idSegmentTtl, int safeDistance, IdSegmentDistributor maxIdDistributor, PrefetchWorkerExecutorService prefetchWorkerExecutorService) { Preconditions.checkArgument(idSegmentTtl > 0, Strings.lenientFormat("Illegal idSegmentTtl parameter:[%s].", idSegmentTtl)); Preconditions.checkArgument(safeDistance > 0, "The safety distance must be greater than 0."); + this.headChain = IdSegmentChain.newRoot(maxIdDistributor.allowReset()); this.idSegmentTtl = idSegmentTtl; this.safeDistance = safeDistance; this.maxIdDistributor = maxIdDistributor; @@ -54,6 +55,11 @@ public SegmentChainId(long idSegmentTtl, int safeDistance, IdSegmentDistributor prefetchWorkerExecutorService.submit(prefetchJob); } + @Override + public IdSegment current() { + return headChain; + } + public IdSegmentChain getHead() { return headChain; } @@ -63,8 +69,8 @@ public IdSegmentChain getHead() { * ----- *
      * synchronized (this) {
-     *   if (currentChain.getVersion() > headChain.getVersion()) {
-     *      headChain = currentChain;
+     *   if (forwardChain.getVersion() > headChain.getVersion()) {
+     *      headChain = forwardChain;
      *  }
      * }
      * 
@@ -72,12 +78,18 @@ public IdSegmentChain getHead() { * @param forwardChain forward IdSegmentChain */ private void forward(IdSegmentChain forwardChain) { - if (forwardChain.compareTo(headChain) > 0) { - if (log.isDebugEnabled()) { - log.debug("Forward [{}] - [{}] -> [{}].", maxIdDistributor.getNamespacedName(), headChain, forwardChain); - } + if (headChain.getVersion() >= forwardChain.getVersion()) { + return; + } + if (log.isDebugEnabled()) { + log.debug("Forward [{}] - [{}] -> [{}].", maxIdDistributor.getNamespacedName(), headChain, forwardChain); + } + if (maxIdDistributor.allowReset()) { + headChain = forwardChain; + } else if (forwardChain.compareTo(headChain) > 0) { headChain = forwardChain; } + } private IdSegmentChain generateNext(IdSegmentChain previousChain, int segments) { @@ -168,7 +180,7 @@ public void run() { public void prefetch() { - long wakeupTimeGap = Clock.CACHE.secondTime() - lastHungerTime; + long wakeupTimeGap = Clock.SYSTEM.secondTime() - lastHungerTime; final boolean hunger = wakeupTimeGap < hungerThreshold; final int prePrefetchDistance = this.prefetchDistance; diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentId.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentId.java index 0944f8b0c5..67bb93e67e 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/SegmentId.java @@ -14,6 +14,8 @@ package me.ahoo.cosid.segment; import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; +import me.ahoo.cosid.stat.generator.SegmentIdStat; /** * Segment algorithm ID generator. @@ -24,4 +26,22 @@ */ public interface SegmentId extends IdGenerator { int ONE_STEP = 1; + + IdSegment current(); + + @Override + default IdGeneratorStat stat() { + return new SegmentIdStat(getClass().getSimpleName(), + current().getFetchTime(), + current().getMaxId(), + current().getOffset(), + current().getSequence(), + current().getStep(), + current().isExpired(), + current().isOverflow(), + current().isAvailable(), + current().group(), + idConverter().stat() + ); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/StringSegmentId.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/StringSegmentId.java index 9f9ca1230e..175a7cef29 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/segment/StringSegmentId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/StringSegmentId.java @@ -13,8 +13,9 @@ package me.ahoo.cosid.segment; -import me.ahoo.cosid.StringIdGeneratorDecorator; import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.StringIdGeneratorDecorator; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; /** * String SegmentId. @@ -22,8 +23,20 @@ * @author ahoo wang */ public class StringSegmentId extends StringIdGeneratorDecorator implements SegmentId { - + private final SegmentId actualSegmentId; + public StringSegmentId(SegmentId actual, IdConverter idConverter) { super(actual, idConverter); + this.actualSegmentId = actual; + } + + @Override + public IdSegment current() { + return actualSegmentId.current(); + } + + @Override + public IdGeneratorStat stat() { + return super.stat(); } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/DefaultGroupedIdSegmentDistributor.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/DefaultGroupedIdSegmentDistributor.java new file mode 100644 index 0000000000..f0cf7d7625 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/DefaultGroupedIdSegmentDistributor.java @@ -0,0 +1,186 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import me.ahoo.cosid.segment.IdSegment; +import me.ahoo.cosid.segment.IdSegmentChain; +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; + +import javax.annotation.Nonnull; + +public class DefaultGroupedIdSegmentDistributor implements GroupedIdSegmentDistributor { + private final GroupBySupplier groupBySupplier; + private final IdSegmentDistributorDefinition idSegmentDistributorDefinition; + private final IdSegmentDistributorFactory idSegmentDistributorFactory; + private volatile GroupedBinding currentGroup; + + public DefaultGroupedIdSegmentDistributor(GroupBySupplier groupBySupplier, IdSegmentDistributorDefinition idSegmentDistributorDefinition, IdSegmentDistributorFactory idSegmentDistributorFactory) { + this.groupBySupplier = groupBySupplier; + this.idSegmentDistributorDefinition = idSegmentDistributorDefinition; + this.idSegmentDistributorFactory = idSegmentDistributorFactory; + this.ensureGroupedBinding(); + } + + private GroupedBinding ensureGroupedBinding() { + GroupedKey groupedKey = groupBySupplier.get(); + if (currentGroup != null && currentGroup.group().equals(groupedKey)) { + return currentGroup; + } + synchronized (this) { + if (currentGroup != null && currentGroup.group().equals(groupedKey)) { + return currentGroup; + } + String groupedName = idSegmentDistributorDefinition.getName() + "@" + groupedKey.getKey(); + IdSegmentDistributorDefinition groupedDef = new IdSegmentDistributorDefinition(idSegmentDistributorDefinition.getNamespace(), + groupedName, + idSegmentDistributorDefinition.getOffset(), + idSegmentDistributorDefinition.getStep()); + this.currentGroup = new GroupedBinding(groupedKey, idSegmentDistributorFactory.create(groupedDef)); + } + + return currentGroup; + } + + public GroupBySupplier groupBySupplier() { + return groupBySupplier; + } + + @Nonnull + @Override + public String getNamespace() { + return this.idSegmentDistributorDefinition.getNamespace(); + } + + @Nonnull + @Override + public String getName() { + return this.idSegmentDistributorDefinition.getName(); + } + + @Override + public long getStep() { + return this.idSegmentDistributorDefinition.getStep(); + } + + @Override + public GroupedKey group() { + return this.ensureGroupedBinding().group(); + } + + @Override + public long nextMaxId() { + return this.ensureGroupedBinding().nextMaxId(); + } + + @Override + public long nextMaxId(long step) { + return this.ensureGroupedBinding().nextMaxId(step); + } + + @Nonnull + @Override + public IdSegment nextIdSegment() { + return this.ensureGroupedBinding().nextIdSegment(); + } + + @Nonnull + @Override + public IdSegment nextIdSegment(long ttl) { + return this.ensureGroupedBinding().nextIdSegment(ttl); + } + + @Nonnull + @Override + public IdSegment nextIdSegment(int segments, long ttl) { + return this.ensureGroupedBinding().nextIdSegment(segments, ttl); + } + + @Nonnull + @Override + public IdSegmentChain nextIdSegmentChain(IdSegmentChain previousChain, int segments, long ttl) { + return this.ensureGroupedBinding().nextIdSegmentChain(previousChain, segments, ttl); + } + + @Nonnull + @Override + public IdSegmentChain nextIdSegmentChain(IdSegmentChain previousChain) { + return this.ensureGroupedBinding().nextIdSegmentChain(previousChain); + } + + public static class GroupedBinding implements GroupedIdSegmentDistributor { + + private final GroupedKey group; + private final IdSegmentDistributor idSegmentDistributor; + + public GroupedBinding(GroupedKey group, IdSegmentDistributor idSegmentDistributor) { + this.group = group; + this.idSegmentDistributor = idSegmentDistributor; + } + + @Override + public GroupedKey group() { + return group; + } + + @Nonnull + @Override + public String getNamespace() { + return idSegmentDistributor.getNamespace(); + } + + @Nonnull + @Override + public String getName() { + return idSegmentDistributor.getName(); + } + + @Override + public long getStep() { + return idSegmentDistributor.getStep(); + } + + @Override + public long nextMaxId(long step) { + return idSegmentDistributor.nextMaxId(step); + } + + private long getMinTtl(long ttl) { + long groupedTtl = group.ttl(); + return Math.min(groupedTtl, ttl); + } + + @Nonnull + @Override + public IdSegment nextIdSegment(long ttl) { + long minTtl = getMinTtl(ttl); + return GroupedIdSegmentDistributor.super.nextIdSegment(minTtl); + } + + @Nonnull + @Override + public IdSegment nextIdSegment(int segments, long ttl) { + long minTtl = getMinTtl(ttl); + return GroupedIdSegmentDistributor.super.nextIdSegment(segments, minTtl); + } + + @Nonnull + @Override + public IdSegmentChain nextIdSegmentChain(IdSegmentChain previousChain, int segments, long ttl) { + long minTtl = getMinTtl(ttl); + return GroupedIdSegmentDistributor.super.nextIdSegmentChain(previousChain, segments, minTtl); + } + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupBySupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupBySupplier.java new file mode 100644 index 0000000000..38e3fa7d2b --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupBySupplier.java @@ -0,0 +1,20 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import java.util.function.Supplier; + +public interface GroupBySupplier extends Supplier { +} + diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/Grouped.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/Grouped.java new file mode 100644 index 0000000000..0ea853a283 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/Grouped.java @@ -0,0 +1,20 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +public interface Grouped { + default GroupedKey group() { + return GroupedKey.NEVER; + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedAccessor.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedAccessor.java new file mode 100644 index 0000000000..482ec62292 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedAccessor.java @@ -0,0 +1,47 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; +import java.util.Objects; + +@ThreadSafe +public final class GroupedAccessor { + private static final ThreadLocal CURRENT = new ThreadLocal<>(); + + public static void set(GroupedKey groupedKey) { + CURRENT.set(groupedKey); + } + + public static void setIfNotNever(GroupedKey groupedKey) { + if (GroupedKey.NEVER.equals(groupedKey)) { + return; + } + set(groupedKey); + } + + @Nullable + public static GroupedKey get() { + return CURRENT.get(); + } + + public static GroupedKey requiredGet() { + return Objects.requireNonNull(get(), "The current thread has not set the GroupedKey."); + } + + public static void clear() { + CURRENT.remove(); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedIdSegmentDistributor.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedIdSegmentDistributor.java new file mode 100644 index 0000000000..6826dd86d1 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedIdSegmentDistributor.java @@ -0,0 +1,24 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import me.ahoo.cosid.segment.IdSegmentDistributor; + +public interface GroupedIdSegmentDistributor extends IdSegmentDistributor { + + @Override + default boolean allowReset() { + return true; + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedIdSegmentDistributorFactory.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedIdSegmentDistributorFactory.java new file mode 100644 index 0000000000..3985af61f8 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedIdSegmentDistributorFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; + +import javax.annotation.Nonnull; + +public class GroupedIdSegmentDistributorFactory implements IdSegmentDistributorFactory { + private final GroupBySupplier groupBySupplier; + private final IdSegmentDistributorFactory actual; + + public GroupedIdSegmentDistributorFactory(GroupBySupplier groupBySupplier, IdSegmentDistributorFactory actual) { + this.groupBySupplier = groupBySupplier; + this.actual = actual; + } + + @Nonnull + @Override + public IdSegmentDistributor create(IdSegmentDistributorDefinition definition) { + return new DefaultGroupedIdSegmentDistributor(groupBySupplier, definition, actual); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedKey.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedKey.java new file mode 100644 index 0000000000..269f7abc23 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/GroupedKey.java @@ -0,0 +1,80 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import me.ahoo.cosid.segment.IdSegment; +import me.ahoo.cosid.util.Clock; + +import com.google.common.base.MoreObjects; + +import java.util.Objects; + +public final class GroupedKey { + public static final GroupedKey NEVER = new GroupedKey("", IdSegment.TIME_TO_LIVE_FOREVER); + private final String key; + private final long ttlAt; + + public GroupedKey(String key, long ttlAt) { + this.key = key; + this.ttlAt = ttlAt; + } + + public String getKey() { + return key; + } + + /** + * get ttlAt of group. + * + * @return ttlAt + * @see IdSegment#getTtl() + */ + public long getTtlAt() { + return ttlAt; + } + + public long ttl() { + return ttlAt - Clock.CACHE.secondTime(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GroupedKey that = (GroupedKey) o; + return ttlAt == that.ttlAt && Objects.equals(key, that.key); + } + + @Override + public int hashCode() { + return Objects.hash(key, ttlAt); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("key", key) + .add("ttlAt", ttlAt) + .toString(); + } + + public static GroupedKey forever(String key) { + return new GroupedKey(key, IdSegment.TIME_TO_LIVE_FOREVER); + } +} + diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/AbstractDateGroupBySupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/AbstractDateGroupBySupplier.java new file mode 100644 index 0000000000..fa196f6a9a --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/AbstractDateGroupBySupplier.java @@ -0,0 +1,45 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + +import me.ahoo.cosid.segment.grouped.GroupBySupplier; +import me.ahoo.cosid.segment.grouped.GroupedKey; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; + +public abstract class AbstractDateGroupBySupplier implements GroupBySupplier { + protected final DateTimeFormatter formatter; + + public AbstractDateGroupBySupplier(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + abstract D now(); + + abstract LocalDateTime lastTimestamp(D date); + + @Override + public GroupedKey get() { + D nowDate = now(); + String key = formatter.format(nowDate); + + LocalDateTime lastTs = lastTimestamp(nowDate); + ZoneId currentZone = ZoneId.systemDefault(); + long ttlAt = lastTs.atZone(currentZone).toInstant().toEpochMilli() / 1000; + return new GroupedKey(key, ttlAt); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearGroupBySupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearGroupBySupplier.java new file mode 100644 index 0000000000..6bf2110091 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearGroupBySupplier.java @@ -0,0 +1,42 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Year; +import java.time.format.DateTimeFormatter; + +public class YearGroupBySupplier extends AbstractDateGroupBySupplier { + + public YearGroupBySupplier(DateTimeFormatter formatter) { + super(formatter); + } + + public YearGroupBySupplier(String pattern) { + this(DateTimeFormatter.ofPattern(pattern)); + } + + @Override + Year now() { + return Year.now(); + } + + @Override + LocalDateTime lastTimestamp(Year date) { + return LocalDateTime.of(LocalDate.MAX.withYear(date.getValue()), LocalTime.MAX); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearMonthDayGroupBySupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearMonthDayGroupBySupplier.java new file mode 100644 index 0000000000..7f5307f180 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearMonthDayGroupBySupplier.java @@ -0,0 +1,40 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; + +public class YearMonthDayGroupBySupplier extends AbstractDateGroupBySupplier { + + public YearMonthDayGroupBySupplier(DateTimeFormatter formatter) { + super(formatter); + } + + public YearMonthDayGroupBySupplier(String pattern) { + this(DateTimeFormatter.ofPattern(pattern)); + } + + @Override + LocalDate now() { + return LocalDate.now(); + } + + @Override + LocalDateTime lastTimestamp(LocalDate date) { + return LocalDateTime.of(date, LocalTime.MAX); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearMonthGroupBySupplier.java b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearMonthGroupBySupplier.java new file mode 100644 index 0000000000..261f1c07e8 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/segment/grouped/date/YearMonthGroupBySupplier.java @@ -0,0 +1,42 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; + + +public class YearMonthGroupBySupplier extends AbstractDateGroupBySupplier { + public YearMonthGroupBySupplier(DateTimeFormatter formatter) { + super(formatter); + } + + public YearMonthGroupBySupplier(String pattern) { + this(DateTimeFormatter.ofPattern(pattern)); + } + + @Override + YearMonth now() { + return YearMonth.now(); + } + + @Override + LocalDateTime lastTimestamp(YearMonth date) { + LocalDate lastDate = LocalDate.MAX.withYear(date.getYear()).withMonth(date.getMonthValue()); + return LocalDateTime.of(lastDate, LocalTime.MAX); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/snowflake/ClockSyncSnowflakeId.java b/cosid-core/src/main/java/me/ahoo/cosid/snowflake/ClockSyncSnowflakeId.java index 43951df7b6..b028615bf7 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/snowflake/ClockSyncSnowflakeId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/snowflake/ClockSyncSnowflakeId.java @@ -16,6 +16,7 @@ import me.ahoo.cosid.IdGeneratorDecorator; import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; import me.ahoo.cosid.snowflake.exception.ClockBackwardsException; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; import lombok.extern.slf4j.Slf4j; @@ -27,26 +28,26 @@ * @author ahoo wang */ @Slf4j -public class ClockSyncSnowflakeId implements SnowflakeId, IdGeneratorDecorator { - +public class ClockSyncSnowflakeId implements IdGeneratorDecorator, SnowflakeId { + private final SnowflakeId actual; private final ClockBackwardsSynchronizer clockBackwardsSynchronizer; - + public ClockSyncSnowflakeId(SnowflakeId actual) { this(actual, ClockBackwardsSynchronizer.DEFAULT); } - + public ClockSyncSnowflakeId(SnowflakeId actual, ClockBackwardsSynchronizer clockBackwardsSynchronizer) { this.actual = actual; this.clockBackwardsSynchronizer = clockBackwardsSynchronizer; } - + @Nonnull @Override public SnowflakeId getActual() { return actual; } - + @Override public long generate() { try { @@ -59,57 +60,60 @@ public long generate() { return actual.generate(); } } - - + + @Override + public IdGeneratorStat stat() { + return IdGeneratorDecorator.super.stat(); + } + @Override public long getEpoch() { return actual.getEpoch(); } - + @Override public int getTimestampBit() { return actual.getTimestampBit(); } - + @Override public int getMachineBit() { return actual.getMachineBit(); } - + @Override public int getSequenceBit() { return actual.getSequenceBit(); } - + @Override public boolean isSafeJavascript() { return actual.isSafeJavascript(); } - + @Override public long getMaxTimestamp() { return actual.getMaxTimestamp(); } - + @Override public int getMaxMachine() { return actual.getMaxMachine(); } - + @Override public long getMaxSequence() { return actual.getMaxSequence(); } - + @Override public long getLastTimestamp() { return actual.getLastTimestamp(); } - + @Override public int getMachineId() { return actual.getMachineId(); } - - + } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/snowflake/SnowflakeId.java b/cosid-core/src/main/java/me/ahoo/cosid/snowflake/SnowflakeId.java index 5b65ff67e0..fa1e482c3a 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/snowflake/SnowflakeId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/snowflake/SnowflakeId.java @@ -14,6 +14,8 @@ package me.ahoo.cosid.snowflake; import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; +import me.ahoo.cosid.stat.generator.SnowflakeIdStat; /** * Snowflake algorithm ID generator. @@ -24,15 +26,15 @@ */ public interface SnowflakeId extends IdGenerator { int TOTAL_BIT = 63; - + long getEpoch(); - + int getTimestampBit(); - + int getMachineBit(); - + int getSequenceBit(); - + /** * 是否是 Javascript 安全的 SnowflakeId. * {@link SafeJavaScriptSnowflakeId#JAVA_SCRIPT_MAX_SAFE_NUMBER_BIT}. @@ -42,19 +44,33 @@ public interface SnowflakeId extends IdGenerator { default boolean isSafeJavascript() { return (getTimestampBit() + getMachineBit() + getSequenceBit()) <= SafeJavaScriptSnowflakeId.JAVA_SCRIPT_MAX_SAFE_NUMBER_BIT; } - + long getMaxTimestamp(); - + int getMaxMachine(); - + long getMaxSequence(); - + long getLastTimestamp(); - + int getMachineId(); - - + static long defaultSequenceResetThreshold(int sequenceBit) { return ~(-1L << (sequenceBit - 1)); } + + @Override + default IdGeneratorStat stat() { + return new SnowflakeIdStat( + getClass().getSimpleName(), + getEpoch(), + getTimestampBit(), + getMachineBit(), + getSequenceBit(), + isSafeJavascript(), + getMachineId(), + getLastTimestamp(), + idConverter().stat() + ); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/snowflake/StringSnowflakeId.java b/cosid-core/src/main/java/me/ahoo/cosid/snowflake/StringSnowflakeId.java index c45128d0cf..78c276d56f 100644 --- a/cosid-core/src/main/java/me/ahoo/cosid/snowflake/StringSnowflakeId.java +++ b/cosid-core/src/main/java/me/ahoo/cosid/snowflake/StringSnowflakeId.java @@ -15,6 +15,7 @@ import me.ahoo.cosid.IdConverter; import me.ahoo.cosid.StringIdGeneratorDecorator; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; /** * String SnowflakeId. @@ -73,4 +74,10 @@ public long getLastTimestamp() { public int getMachineId() { return snowflakeId.getMachineId(); } + + + @Override + public IdGeneratorStat stat() { + return super.stat(); + } } diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/SimpleStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/SimpleStat.java new file mode 100644 index 0000000000..8982c12aec --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/SimpleStat.java @@ -0,0 +1,24 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class SimpleStat implements Stat { + private final String kind; + private final Stat actual; +} \ No newline at end of file diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/Stat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/Stat.java new file mode 100644 index 0000000000..14c72b3b4d --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/Stat.java @@ -0,0 +1,34 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat; + +import javax.annotation.Nullable; + +public interface Stat { + + String getKind(); + + @Nullable + default Stat getActual() { + return null; + } + + static Stat simple(String kind, @Nullable Stat actual) { + return new SimpleStat(kind, actual); + } + + static Stat simple(String kind) { + return simple(kind, null); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/Statistical.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/Statistical.java new file mode 100644 index 0000000000..abc45f0ac0 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/Statistical.java @@ -0,0 +1,19 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat; + +@FunctionalInterface +public interface Statistical { + Stat stat(); +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/DatePrefixConverterStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/DatePrefixConverterStat.java new file mode 100644 index 0000000000..6a42b66191 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/DatePrefixConverterStat.java @@ -0,0 +1,27 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.converter; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class DatePrefixConverterStat implements Stat { + private final String kind; + private final String pattern; + private final Stat actual; +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/GroupedPrefixConverterStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/GroupedPrefixConverterStat.java new file mode 100644 index 0000000000..a48d307ee1 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/GroupedPrefixConverterStat.java @@ -0,0 +1,27 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.converter; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class GroupedPrefixConverterStat implements Stat { + private final String kind; + private final String delimiter; + private final Stat actual; +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/PrefixConverterStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/PrefixConverterStat.java new file mode 100644 index 0000000000..28089cf4f5 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/PrefixConverterStat.java @@ -0,0 +1,28 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.converter; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class PrefixConverterStat implements Stat { + private final String kind; + private final String prefix; + private final Stat actual; + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/RadixConverterStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/RadixConverterStat.java new file mode 100644 index 0000000000..dc3f8e9e71 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/RadixConverterStat.java @@ -0,0 +1,29 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.converter; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class RadixConverterStat implements Stat { + private final String kind; + private final int radix; + private final int charSize; + private final boolean padStart; + private final long maxId; +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/SuffixConverterStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/SuffixConverterStat.java new file mode 100644 index 0000000000..59e7f08461 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/SuffixConverterStat.java @@ -0,0 +1,28 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.converter; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class SuffixConverterStat implements Stat { + private final String kind; + private final String suffix; + private final Stat actual; + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/ToStringConverterStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/ToStringConverterStat.java new file mode 100644 index 0000000000..ed9bdf22ff --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/converter/ToStringConverterStat.java @@ -0,0 +1,28 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.converter; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class ToStringConverterStat implements Stat { + private final String kind; + private final boolean padStart; + private final int charSize; + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/CosIdGeneratorStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/CosIdGeneratorStat.java new file mode 100644 index 0000000000..14a18519a9 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/CosIdGeneratorStat.java @@ -0,0 +1,29 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.generator; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class CosIdGeneratorStat implements IdGeneratorStat { + private final String kind; + private final int machineId; + private final long lastTimestamp; + private final Stat converter; + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/IdGeneratorStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/IdGeneratorStat.java new file mode 100644 index 0000000000..021380c8de --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/IdGeneratorStat.java @@ -0,0 +1,37 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.generator; + +import me.ahoo.cosid.stat.Stat; + +import javax.annotation.Nullable; + +public interface IdGeneratorStat extends Stat { + @Nullable + @Override + default IdGeneratorStat getActual() { + return null; + } + + @Nullable + Stat getConverter(); + + static IdGeneratorStat simple(String kind, @Nullable IdGeneratorStat actual, Stat converter) { + return new SimpleIdGeneratorStat(kind, actual, converter); + } + + static IdGeneratorStat simple(String kind, Stat converter) { + return simple(kind, null, converter); + } +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SegmentIdStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SegmentIdStat.java new file mode 100644 index 0000000000..8d78fb92ac --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SegmentIdStat.java @@ -0,0 +1,37 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.generator; + +import me.ahoo.cosid.segment.grouped.GroupedKey; +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class SegmentIdStat implements IdGeneratorStat { + private final String kind; + private final long fetchTime; + private final long maxId; + private final long offset; + private final long sequence; + private final long step; + private final boolean isExpired; + private final boolean isOverflow; + private final boolean isAvailable; + private final GroupedKey groupedKey; + private final Stat converter; + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SimpleIdGeneratorStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SimpleIdGeneratorStat.java new file mode 100644 index 0000000000..517b765617 --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SimpleIdGeneratorStat.java @@ -0,0 +1,28 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.generator; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class SimpleIdGeneratorStat implements IdGeneratorStat { + private final String kind; + private final IdGeneratorStat actual; + private final Stat converter; + +} diff --git a/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SnowflakeIdStat.java b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SnowflakeIdStat.java new file mode 100644 index 0000000000..6a92bfdd3f --- /dev/null +++ b/cosid-core/src/main/java/me/ahoo/cosid/stat/generator/SnowflakeIdStat.java @@ -0,0 +1,34 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat.generator; + +import me.ahoo.cosid.stat.Stat; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public final class SnowflakeIdStat implements IdGeneratorStat { + private final String kind; + private final long epoch; + private final int timestampBit; + private final int machineBit; + private final int sequenceBit; + private final boolean isSafeJavascript; + private final int machineId; + private final long lastTimestamp; + private final Stat converter; + +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/accessor/NotFoundCosIdAccessorTest.java b/cosid-core/src/test/java/me/ahoo/cosid/accessor/NotFoundCosIdAccessorTest.java new file mode 100644 index 0000000000..729dc22084 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/accessor/NotFoundCosIdAccessorTest.java @@ -0,0 +1,51 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.accessor; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import org.junit.jupiter.api.Test; + +class NotFoundCosIdAccessorTest { + @Test + public void getIdDefinition() { + assertThat(CosIdAccessor.NOT_FOUND.getIdDefinition(), nullValue()); + } + + @Test + public void getIdGenerator() { + assertThat(CosIdAccessor.NOT_FOUND.getIdGenerator(), nullValue()); + } + + @Test + public void getIdField() { + assertThat(CosIdAccessor.NOT_FOUND.getIdField(), nullValue()); + } + + @Test + public void getId() { + assertThat(CosIdAccessor.NOT_FOUND.getId(new Object()), nullValue()); + } + + @Test + public void setId() { + CosIdAccessor.NOT_FOUND.setId(new Object(), new Object()); + } + + @Test + public void ensureId() { + assertThat(CosIdAccessor.NOT_FOUND.ensureId(new Object()), equalTo(false)); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/accessor/parser/CompositeFieldDefinitionParserTest.java b/cosid-core/src/test/java/me/ahoo/cosid/accessor/parser/CompositeFieldDefinitionParserTest.java new file mode 100644 index 0000000000..1ff3bfee10 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/accessor/parser/CompositeFieldDefinitionParserTest.java @@ -0,0 +1,52 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.accessor.parser; + +import com.google.common.collect.Lists; +import me.ahoo.cosid.accessor.IdDefinition; +import me.ahoo.cosid.annotation.AnnotationDefinitionParser; +import me.ahoo.cosid.annotation.entity.IdNotFoundEntity; +import me.ahoo.cosid.annotation.entity.LongIdEntity; +import me.ahoo.cosid.provider.IdGeneratorProvider; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.List; + +class CompositeFieldDefinitionParserTest { + + @SneakyThrows + @Test + void parse() { + + CompositeFieldDefinitionParser compositeFieldDefinitionParser = new CompositeFieldDefinitionParser(Lists.newArrayList(AnnotationDefinitionParser.INSTANCE)); + Field idField = LongIdEntity.class.getDeclaredField("id"); + IdDefinition idDefinition = compositeFieldDefinitionParser.parse(Lists.newArrayList(LongIdEntity.class), idField); + + Assertions.assertNotEquals(IdDefinition.NOT_FOUND, idDefinition); + Assertions.assertEquals(IdGeneratorProvider.SHARE, idDefinition.getGeneratorName()); + } + + @SneakyThrows + @Test + void parseIfNotFound() { + CompositeFieldDefinitionParser compositeFieldDefinitionParser = new CompositeFieldDefinitionParser(Lists.newArrayList(AnnotationDefinitionParser.INSTANCE)); + Field nameField = IdNotFoundEntity.class.getDeclaredField("name"); + IdDefinition idDefinition = compositeFieldDefinitionParser.parse(Lists.newArrayList(IdNotFoundEntity.class), nameField); + Assertions.assertEquals(IdDefinition.NOT_FOUND, idDefinition); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/accessor/registry/DefaultAccessorRegistryTest.java b/cosid-core/src/test/java/me/ahoo/cosid/accessor/registry/DefaultAccessorRegistryTest.java index 5cd8fae2d0..6d82b798fc 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/accessor/registry/DefaultAccessorRegistryTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/accessor/registry/DefaultAccessorRegistryTest.java @@ -13,8 +13,12 @@ package me.ahoo.cosid.accessor.registry; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + import me.ahoo.cosid.accessor.parser.DefaultAccessorParser; import me.ahoo.cosid.annotation.AnnotationDefinitionParser; +import me.ahoo.cosid.annotation.entity.IntIdEntity; import me.ahoo.cosid.annotation.entity.LongIdEntity; import me.ahoo.cosid.annotation.entity.MissingIdGenEntity; import me.ahoo.cosid.annotation.entity.StringIdEntity; @@ -30,9 +34,9 @@ * @author ahoo wang */ class DefaultAccessorRegistryTest { - + CosIdAccessorRegistry accessorRegistry = new DefaultAccessorRegistry(new DefaultAccessorParser(AnnotationDefinitionParser.INSTANCE)); - + @Test void ensureIdExistsByAnnotation() { DefaultIdGeneratorProvider.INSTANCE.setShare(AtomicLongGenerator.INSTANCE); @@ -41,7 +45,7 @@ void ensureIdExistsByAnnotation() { accessorRegistry.ensureId(entity); Assertions.assertEquals(888, entity.getId()); } - + @Test void ensureIdNotFindByAnnotation() { MissingIdGenEntity entity = new MissingIdGenEntity(); @@ -49,7 +53,7 @@ void ensureIdNotFindByAnnotation() { accessorRegistry.ensureId(entity); }); } - + @Test void ensureStringId() { DefaultIdGeneratorProvider.INSTANCE.setShare(AtomicLongGenerator.INSTANCE); @@ -57,5 +61,12 @@ void ensureStringId() { accessorRegistry.ensureId(entity); Assertions.assertFalse(Strings.isNullOrEmpty(entity.getId())); } - + + @Test + void ensureIntId() { + DefaultIdGeneratorProvider.INSTANCE.setShare(AtomicLongGenerator.INSTANCE); + IntIdEntity entity = new IntIdEntity(); + accessorRegistry.ensureId(entity); + assertThat(entity.getId(), not(0)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/accessor/scanner/DefaultCosIdScannerTest.java b/cosid-core/src/test/java/me/ahoo/cosid/accessor/scanner/DefaultCosIdScannerTest.java index 23dcadd929..0b5ea4e36e 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/accessor/scanner/DefaultCosIdScannerTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/accessor/scanner/DefaultCosIdScannerTest.java @@ -14,7 +14,6 @@ package me.ahoo.cosid.accessor.scanner; import me.ahoo.cosid.accessor.CosIdAccessor; -import me.ahoo.cosid.accessor.parser.CosIdAccessorParser; import me.ahoo.cosid.accessor.parser.DefaultAccessorParser; import me.ahoo.cosid.accessor.parser.NamedDefinitionParser; import me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry; @@ -23,6 +22,7 @@ import me.ahoo.cosid.accessor.scanner.entity.OrderItemEntity; import me.ahoo.cosid.annotation.AnnotationDefinitionParser; import me.ahoo.cosid.annotation.entity.ChildEntity; +import me.ahoo.cosid.annotation.entity.IntIdEntity; import me.ahoo.cosid.annotation.entity.LongIdEntity; import me.ahoo.cosid.annotation.entity.MissingIdGenEntity; import me.ahoo.cosid.annotation.entity.PrimitiveLongIdEntity; @@ -35,56 +35,62 @@ * @author ahoo wang */ class DefaultCosIdScannerTest { - + @Test void scanAnnotationDefinitionParser() { CosIdAccessorRegistry registry = new DefaultAccessorRegistry(new DefaultAccessorParser(AnnotationDefinitionParser.INSTANCE)); DefaultCosIdScanner scanner = new DefaultCosIdScanner(new String[] {"me.ahoo.cosid.accessor.annotation.entity"}, AnnotationDefinitionParser.INSTANCE, registry); scanner.scan(); - + CosIdAccessor cosIdAccessor = registry.get(LongIdEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(LongIdEntity.class, cosIdAccessor.getIdDeclaringClass()); - + cosIdAccessor = registry.get(MissingIdGenEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(MissingIdGenEntity.class, cosIdAccessor.getIdDeclaringClass()); - + cosIdAccessor = registry.get(PrimitiveLongIdEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(PrimitiveLongIdEntity.class, cosIdAccessor.getIdDeclaringClass()); - + + cosIdAccessor = registry.get(IntIdEntity.class); + Assertions.assertNotNull(cosIdAccessor); + Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); + Assertions.assertEquals(IntIdEntity.class, cosIdAccessor.getIdDeclaringClass()); + cosIdAccessor = registry.get(StringIdEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(StringIdEntity.class, cosIdAccessor.getIdDeclaringClass()); - + cosIdAccessor = registry.get(ChildEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(LongIdEntity.class, cosIdAccessor.getIdDeclaringClass()); } - + @Test void scanNamedDefinitionParser() { CosIdAccessorRegistry registry = new DefaultAccessorRegistry(new DefaultAccessorParser(AnnotationDefinitionParser.INSTANCE)); DefaultCosIdScanner scanner = new DefaultCosIdScanner(new String[] {"me.ahoo.cosid.accessor.scanner.entity"}, new NamedDefinitionParser("id"), registry); scanner.scan(); - + CosIdAccessor cosIdAccessor = registry.get(OrderEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(OrderEntity.class, cosIdAccessor.getIdDeclaringClass()); - + cosIdAccessor = registry.get(OrderItemEntity.class); Assertions.assertNotNull(cosIdAccessor); Assertions.assertNotEquals(CosIdAccessor.NOT_FOUND, cosIdAccessor); Assertions.assertEquals(OrderItemEntity.class, cosIdAccessor.getIdDeclaringClass()); + } - + } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/annotation/entity/IdNotFoundEntity.java b/cosid-core/src/test/java/me/ahoo/cosid/annotation/entity/IdNotFoundEntity.java new file mode 100644 index 0000000000..1529b0d327 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/annotation/entity/IdNotFoundEntity.java @@ -0,0 +1,22 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.annotation.entity; + +/** + * @author ahoo wang + */ +public class IdNotFoundEntity { + private String name; + +} diff --git a/cosid-core/src/test/java/me/ahoo/cosid/annotation/entity/IntIdEntity.java b/cosid-core/src/test/java/me/ahoo/cosid/annotation/entity/IntIdEntity.java new file mode 100644 index 0000000000..ccb276182a --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/annotation/entity/IntIdEntity.java @@ -0,0 +1,32 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.annotation.entity; + +import me.ahoo.cosid.annotation.CosId; + +/** + * @author ahoo wang + */ +public class IntIdEntity { + @CosId + private int id; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/DatePrefixIdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/DatePrefixIdConverterTest.java new file mode 100644 index 0000000000..ed9c02b048 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/DatePrefixIdConverterTest.java @@ -0,0 +1,51 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.converter; + +import me.ahoo.cosid.stat.converter.DatePrefixConverterStat; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static me.ahoo.cosid.converter.GroupedPrefixIdConverter.DEFAULT_DELIMITER; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +class DatePrefixIdConverterTest { + private final DatePrefixIdConverter idConverter = new DatePrefixIdConverter("yyMMdd", DEFAULT_DELIMITER, ToStringIdConverter.INSTANCE); + + @Test + void asString() { + String idString = idConverter.asString(1); + String expected = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMdd")) + DEFAULT_DELIMITER + "1"; + assertThat(idString, equalTo(expected)); + } + + @Test + void asLong() { + assertThat(idConverter.asLong("240618-1"), equalTo(1L)); + } + + @Test + void getActual() { + assertThat(idConverter.getActual(), equalTo(ToStringIdConverter.INSTANCE)); + } + + @Test + void stat() { + assertThat(idConverter.stat(), instanceOf(DatePrefixConverterStat.class)); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/GroupedPrefixIdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/GroupedPrefixIdConverterTest.java new file mode 100644 index 0000000000..4c4ba34248 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/GroupedPrefixIdConverterTest.java @@ -0,0 +1,56 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.converter; + +import static me.ahoo.cosid.converter.GroupedPrefixIdConverter.DEFAULT_DELIMITER; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.segment.grouped.GroupedAccessor; +import me.ahoo.cosid.segment.grouped.GroupedKey; +import me.ahoo.cosid.stat.converter.GroupedPrefixConverterStat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class GroupedPrefixIdConverterTest { + GroupedPrefixIdConverter converter = new GroupedPrefixIdConverter(DEFAULT_DELIMITER, ToStringIdConverter.INSTANCE); + + @Test + void asString() { + GroupedAccessor.set(GroupedKey.forever("2023")); + assertThat(converter.getDelimiter(), equalTo("-")); + assertThat(converter.asString(1), equalTo("2023-1")); + assertThat(converter.getActual(), equalTo(ToStringIdConverter.INSTANCE)); + GroupedAccessor.clear(); + } + + @Test + void asStringIfEmptyDelimiter() { + GroupedPrefixIdConverter converter = new GroupedPrefixIdConverter("", ToStringIdConverter.INSTANCE); + GroupedAccessor.set(GroupedKey.forever("2023")); + assertThat(converter.asString(1), equalTo("20231")); + GroupedAccessor.clear(); + } + + @Test + void asLong() { + Assertions.assertThrows(UnsupportedOperationException.class, () -> converter.asLong("2023-1")); + } + + @Test + void stat() { + assertThat(converter.stat(), instanceOf(GroupedPrefixConverterStat.class)); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/PrefixIdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/PrefixIdConverterTest.java index e19100f9e7..af98e0d7d4 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/converter/PrefixIdConverterTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/PrefixIdConverterTest.java @@ -14,12 +14,12 @@ package me.ahoo.cosid.converter; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.stat.converter.PrefixConverterStat; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import java.util.concurrent.ThreadLocalRandom; @@ -35,6 +35,11 @@ void getSuffix() { assertThat(idConverter.getPrefix(), equalTo(PREFIX)); } + @Test + void getActual() { + assertThat(idConverter.getActual(), equalTo(ToStringIdConverter.INSTANCE)); + } + @Test void asString() { long randomId = ThreadLocalRandom.current().nextLong(); @@ -65,4 +70,8 @@ void asLongWhenNumberFormat() { }); } + @Test + void stat() { + assertThat(idConverter.stat(), instanceOf(PrefixConverterStat.class)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix36IdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix36IdConverterTest.java index 56d4cae0e4..33199d0f89 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix36IdConverterTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix36IdConverterTest.java @@ -13,8 +13,12 @@ package me.ahoo.cosid.converter; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + import me.ahoo.cosid.IdGenerator; import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.stat.converter.RadixConverterStat; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -25,7 +29,7 @@ * @author ahoo wang */ class Radix36IdConverterTest { - + @ParameterizedTest @ValueSource(longs = {1, 5, 62, 63, 124, Integer.MAX_VALUE, Long.MAX_VALUE}) void asString(long argId) { @@ -40,7 +44,7 @@ void asStringWhenIdNegative() { Radix36IdConverter.INSTANCE.asString(-1L); }); } - + @ParameterizedTest @ValueSource(longs = {1, 5, 62, 63, 124, Integer.MAX_VALUE, Long.MAX_VALUE}) void asLong(long argId) { @@ -48,12 +52,12 @@ void asLong(long argId) { long actual = Radix36IdConverter.INSTANCE.asLong(idStr); Assertions.assertEquals(argId, actual); } - + @Test void asLongWhenNumberFormat() { int charSize = 2; Radix36IdConverter idConvert = Radix36IdConverter.of(false, charSize); - + Assertions.assertThrows(NumberFormatException.class, () -> { idConvert.asLong("-1"); }); @@ -64,7 +68,7 @@ void asLongWhenNumberFormat() { idConvert.asLong("1_"); }); } - + @ParameterizedTest @ValueSource(longs = {1, 5, 62, 63, 124, Integer.MAX_VALUE, Long.MAX_VALUE}) void asStringPad(long argId) { @@ -72,7 +76,7 @@ void asStringPad(long argId) { Assertions.assertNotNull(idStr); Assertions.assertEquals(Radix36IdConverter.MAX_CHAR_SIZE, idStr.length()); } - + @ParameterizedTest @ValueSource(longs = {1, 5, 62, 63, 124, Integer.MAX_VALUE, Long.MAX_VALUE}) void asLongPad(long argId) { @@ -80,7 +84,7 @@ void asLongPad(long argId) { long actual = Radix36IdConverter.PAD_START.asLong(idStr); Assertions.assertEquals(argId, actual); } - + @Test void asStringSnowflakeId() { IdGenerator idGenerator = new MillisecondSnowflakeId(1); @@ -89,7 +93,7 @@ void asStringSnowflakeId() { Assertions.assertNotNull(idStr); Assertions.assertEquals(Radix36IdConverter.MAX_CHAR_SIZE, idStr.length()); } - + @Test void asStringCharSize12() { int charSize = 12; @@ -101,11 +105,11 @@ void asStringCharSize12() { Assertions.assertEquals(charSize, actualIdStr.length()); long actualId = idConvert.asLong(actualIdStr); Assertions.assertEquals(id, actualId); - + actualIdStr = idConvert.asString(1L); Assertions.assertEquals(1, actualIdStr.length()); } - + @Test void asStringPadCharSize12() { int charSize = 12; @@ -113,5 +117,9 @@ void asStringPadCharSize12() { String actualIdStr = idConvert.asString(1L); Assertions.assertEquals(charSize, actualIdStr.length()); } - + + @Test + void stat() { + assertThat(Radix36IdConverter.INSTANCE.stat(), instanceOf(RadixConverterStat.class)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix62IdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix62IdConverterTest.java index 9c63347c66..8bfbc09969 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix62IdConverterTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/Radix62IdConverterTest.java @@ -13,8 +13,12 @@ package me.ahoo.cosid.converter; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + import me.ahoo.cosid.IdGenerator; import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.stat.converter.RadixConverterStat; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -115,4 +119,8 @@ void asStringPadCharSize10() { Assertions.assertEquals(charSize, actualIdStr.length()); } + @Test + void stat() { + assertThat(Radix62IdConverter.INSTANCE.stat(), instanceOf(RadixConverterStat.class)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverterTest.java index 0d2f13d9bc..e9bb912ca9 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverterTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/SnowflakeFriendlyIdConverterTest.java @@ -13,9 +13,13 @@ package me.ahoo.cosid.converter; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + import me.ahoo.cosid.snowflake.SecondSnowflakeId; import me.ahoo.cosid.snowflake.SecondSnowflakeIdStateParser; import me.ahoo.cosid.snowflake.SnowflakeId; +import me.ahoo.cosid.stat.SimpleStat; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -60,8 +64,10 @@ void getParser() { SecondSnowflakeIdStateParser snowflakeIdStateParser = SecondSnowflakeIdStateParser.of(idGen); SnowflakeFriendlyIdConverter snowflakeFriendlyIdConverter = new SnowflakeFriendlyIdConverter(snowflakeIdStateParser); Assertions.assertNotNull(snowflakeFriendlyIdConverter.getParser()); - } - + @Test + void stat() { + assertThat(SnowflakeFriendlyIdConverter.INSTANCE.stat(), instanceOf(SimpleStat.class)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/SuffixIdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/SuffixIdConverterTest.java index f7ed2eb7e7..356c327e6e 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/converter/SuffixIdConverterTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/SuffixIdConverterTest.java @@ -14,7 +14,9 @@ package me.ahoo.cosid.converter; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.stat.converter.SuffixConverterStat; import org.junit.jupiter.api.Test; @@ -34,6 +36,11 @@ void getSuffix() { assertThat(idConverter.getSuffix(), equalTo(SUFFIX)); } + @Test + void getActual() { + assertThat(idConverter.getActual(), equalTo(ToStringIdConverter.INSTANCE)); + } + @Test void asString() { long randomId = ThreadLocalRandom.current().nextLong(); @@ -47,4 +54,9 @@ void asLong() { long actual = idConverter.asLong(randomId + SUFFIX); assertThat(actual, equalTo(randomId)); } + + @Test + void stat() { + assertThat(idConverter.stat(), instanceOf(SuffixConverterStat.class)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/converter/ToStringIdConverterTest.java b/cosid-core/src/test/java/me/ahoo/cosid/converter/ToStringIdConverterTest.java index 7be26eaa4c..102912b270 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/converter/ToStringIdConverterTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/converter/ToStringIdConverterTest.java @@ -13,6 +13,11 @@ package me.ahoo.cosid.converter; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.stat.converter.ToStringConverterStat; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -59,4 +64,9 @@ void asStringWithPadStart() { Assertions.assertEquals("00001", idConvert.asString(1)); Assertions.assertEquals(1, idConvert.asLong("00001")); } + + @Test + void stat() { + assertThat(ToStringIdConverter.INSTANCE.stat(), instanceOf(ToStringConverterStat.class)); + } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/cosid/ClockSyncCosIdGeneratorTest.java b/cosid-core/src/test/java/me/ahoo/cosid/cosid/ClockSyncCosIdGeneratorTest.java index b7753f3cd7..02dccc3060 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/cosid/ClockSyncCosIdGeneratorTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/cosid/ClockSyncCosIdGeneratorTest.java @@ -22,6 +22,16 @@ class ClockSyncCosIdGeneratorTest { private final Radix62CosIdGenerator radix62CosIdGenerator = new Radix62CosIdGenerator(1); private final ClockSyncCosIdGenerator clockSyncCosIdGenerator = new ClockSyncCosIdGenerator(radix62CosIdGenerator); + @Test + void getActual() { + assertThat(clockSyncCosIdGenerator.getActual(), sameInstance(radix62CosIdGenerator)); + } + + @Test + void getMachineId() { + assertThat(clockSyncCosIdGenerator.getMachineId(), equalTo(1)); + } + @Test void getLastTimestamp() { assertThat(clockSyncCosIdGenerator.getLastTimestamp(), equalTo(radix62CosIdGenerator.getLastTimestamp())); diff --git a/cosid-core/src/test/java/me/ahoo/cosid/jvm/AtomicLongGeneratorTest.java b/cosid-core/src/test/java/me/ahoo/cosid/jvm/AtomicLongGeneratorTest.java index dc96f5fa15..5ed4e826e4 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/jvm/AtomicLongGeneratorTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/jvm/AtomicLongGeneratorTest.java @@ -20,11 +20,12 @@ * @author ahoo wang */ class AtomicLongGeneratorTest { - + @Test void generate() { long idFirst = AtomicLongGenerator.INSTANCE.generate(); long idSecond = AtomicLongGenerator.INSTANCE.generate(); Assertions.assertTrue(idSecond > idFirst); } + } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/machine/LocalHostAddressSupplierTest.java b/cosid-core/src/test/java/me/ahoo/cosid/machine/LocalHostAddressSupplierTest.java new file mode 100644 index 0000000000..7d5ee1cdf7 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/machine/LocalHostAddressSupplierTest.java @@ -0,0 +1,27 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.machine; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import org.junit.jupiter.api.Test; + +class LocalHostAddressSupplierTest { + + @Test + void getHostName() { + assertThat(LocalHostAddressSupplier.INSTANCE.getHostAddress(), notNullValue()); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/DefaultSegmentIdTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/DefaultSegmentIdTest.java index 1f217bb925..debf2cfa93 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/segment/DefaultSegmentIdTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/DefaultSegmentIdTest.java @@ -13,6 +13,9 @@ package me.ahoo.cosid.segment; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + import me.ahoo.cosid.test.ConcurrentGenerateSpec; import me.ahoo.cosid.test.ConcurrentGenerateStingSpec; @@ -30,6 +33,12 @@ void generate() { Assertions.assertTrue(defaultSegmentId.generate() > 0); } + @Test + void current() { + DefaultSegmentId defaultSegmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + assertThat(defaultSegmentId.current(), equalTo(DefaultIdSegment.OVERFLOW)); + } + @Test void generateWhenConcurrent() { DefaultSegmentId defaultSegmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/SegmentChainIdTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/SegmentChainIdTest.java index 36f079ccef..7a31da1750 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/segment/SegmentChainIdTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/SegmentChainIdTest.java @@ -14,10 +14,13 @@ package me.ahoo.cosid.segment; import static me.ahoo.cosid.segment.IdSegment.TIME_TO_LIVE_FOREVER; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; import me.ahoo.cosid.segment.concurrent.PrefetchWorkerExecutorService; import me.ahoo.cosid.test.ConcurrentGenerateSpec; import me.ahoo.cosid.test.ConcurrentGenerateStingSpec; +import me.ahoo.cosid.test.ModSpec; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -34,9 +37,9 @@ class SegmentChainIdTest { @Test void sort() { IdSegmentDistributor idSegmentDistributor = new IdSegmentDistributor.Atomic(); - IdSegmentChain idSegmentChain1 = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot()); - IdSegmentChain idSegmentChain2 = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot()); - IdSegmentChain idSegmentChain3 = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot()); + IdSegmentChain idSegmentChain1 = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot(false)); + IdSegmentChain idSegmentChain2 = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot(false)); + IdSegmentChain idSegmentChain3 = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot(false)); List chainList = Arrays.asList(idSegmentChain2, idSegmentChain1, idSegmentChain3); chainList.sort(null); Assertions.assertEquals(idSegmentChain1, chainList.get(0)); @@ -44,10 +47,16 @@ void sort() { Assertions.assertEquals(idSegmentChain3, chainList.get(2)); } + @Test + void current() { + SegmentChainId segmentChainId = new SegmentChainId(TIME_TO_LIVE_FOREVER, 10, new IdSegmentDistributor.Atomic(2), PrefetchWorkerExecutorService.DEFAULT); + assertThat(segmentChainId.current().isAvailable(), equalTo(false)); + } + @Test void nextIdSegmentsChain() { IdSegmentDistributor idSegmentDistributor = new IdSegmentDistributor.Atomic(); - IdSegmentChain rootChain = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot(), 3, TIME_TO_LIVE_FOREVER); + IdSegmentChain rootChain = idSegmentDistributor.nextIdSegmentChain(IdSegmentChain.newRoot(true), 3, TIME_TO_LIVE_FOREVER); Assertions.assertEquals(0, rootChain.getVersion()); Assertions.assertEquals(0, rootChain.getIdSegment().getOffset()); Assertions.assertEquals(30, rootChain.getStep()); @@ -69,6 +78,12 @@ public void concurrent_generate() { new ConcurrentGenerateSpec(segmentChainId).verify(); } + @Test + public void sequenceModUniformity() { + SegmentChainId segmentChainId = new SegmentChainId(new IdSegmentDistributor.Mock()); + new ModSpec(99999, 4, 100, segmentChainId::generate, ModSpec.DEFAULT_WAIT).verify(); + } + @Test public void generateWhenConcurrentString() { IdSegmentDistributor testMaxIdDistributor = new IdSegmentDistributor.Mock(); diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/StringSegmentIdTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/StringSegmentIdTest.java index 3dc5f454e4..0c0d384bd4 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/segment/StringSegmentIdTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/StringSegmentIdTest.java @@ -17,5 +17,6 @@ void ctor() { DefaultSegmentId delegate = new DefaultSegmentId(new IdSegmentDistributor.Mock()); StringSegmentId stringSegmentId = new StringSegmentId(delegate, Radix62IdConverter.PAD_START); Assertions.assertNotNull(stringSegmentId); + Assertions.assertNotNull(stringSegmentId.current()); } } diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/DefaultGroupedIdSegmentDistributorTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/DefaultGroupedIdSegmentDistributorTest.java new file mode 100644 index 0000000000..163d10df96 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/DefaultGroupedIdSegmentDistributorTest.java @@ -0,0 +1,68 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; + +import org.junit.jupiter.api.Test; + +class DefaultGroupedIdSegmentDistributorTest { + + @Test + void ensureGrouped() { + MockGroupBySupplier groupedSupplier = new MockGroupBySupplier(GroupedKey.forever("group-1")); + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition("ns", "n", 0, 1); + IdSegmentDistributorFactory actual = definition1 -> new IdSegmentDistributor.Mock(); + DefaultGroupedIdSegmentDistributor distributor = new DefaultGroupedIdSegmentDistributor(groupedSupplier, definition, actual); + long maxId1 = distributor.nextMaxId(1); + assertThat(maxId1, equalTo(1L)); + long maxId2 = distributor.nextMaxId(1); + assertThat(maxId2, equalTo(2L)); + assertThat(distributor.groupBySupplier().get().getKey(), equalTo("group-1")); + + groupedSupplier.setGroup(GroupedKey.forever("group-2")); + maxId1 = distributor.nextMaxId(1); + assertThat(maxId1, equalTo(1L)); + maxId2 = distributor.nextMaxId(1); + assertThat(maxId2, equalTo(2L)); + assertThat(distributor.groupBySupplier().get().getKey(), equalTo("group-2")); + } + + public static class MockGroupBySupplier implements GroupBySupplier { + private GroupedKey group; + + public MockGroupBySupplier(GroupedKey group) { + this.group = group; + } + + public GroupedKey getGroup() { + return group; + } + + public MockGroupBySupplier setGroup(GroupedKey group) { + this.group = group; + return this; + } + + @Override + public GroupedKey get() { + return group; + } + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearGroupBySupplierTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearGroupBySupplierTest.java new file mode 100644 index 0000000000..0a7d72d886 --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearGroupBySupplierTest.java @@ -0,0 +1,42 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.segment.grouped.GroupedKey; +import org.junit.jupiter.api.Test; + +import java.time.Year; +import java.time.format.DateTimeFormatter; + +class YearGroupBySupplierTest { + + @Test + void year() { + String pattern = "yyyy"; + YearGroupBySupplier supplier = new YearGroupBySupplier(pattern); + GroupedKey groupedKey = supplier.get(); + assertThat(groupedKey.getKey(), equalTo(Year.now().format(DateTimeFormatter.ofPattern(pattern)))); + } + + @Test + void yearTwoPattern() { + String pattern = "yy"; + YearGroupBySupplier supplier = new YearGroupBySupplier(pattern); + GroupedKey groupedKey = supplier.get(); + assertThat(groupedKey.getKey(), equalTo(Year.now().format(DateTimeFormatter.ofPattern(pattern)))); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearMonthDayGroupBySupplierTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearMonthDayGroupBySupplierTest.java new file mode 100644 index 0000000000..1b00277d4c --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearMonthDayGroupBySupplierTest.java @@ -0,0 +1,41 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + +import me.ahoo.cosid.segment.grouped.GroupedKey; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.*; + +class YearMonthDayGroupBySupplierTest { + + @Test + void get() { + String pattern = "yyyyMMdd"; + YearMonthDayGroupBySupplier supplier = new YearMonthDayGroupBySupplier(pattern); + GroupedKey groupedKey = supplier.get(); + assertEquals(groupedKey.getKey(), LocalDate.now().format(DateTimeFormatter.ofPattern(pattern))); + } + + @Test + void getYyMm() { + String pattern = "yyMMdd"; + YearMonthDayGroupBySupplier supplier = new YearMonthDayGroupBySupplier(pattern); + GroupedKey groupedKey = supplier.get(); + assertEquals(groupedKey.getKey(), LocalDate.now().format(DateTimeFormatter.ofPattern(pattern))); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearMonthGroupBySupplierTest.java b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearMonthGroupBySupplierTest.java new file mode 100644 index 0000000000..e1fe51262a --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/segment/grouped/date/YearMonthGroupBySupplierTest.java @@ -0,0 +1,41 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.segment.grouped.date; + +import me.ahoo.cosid.segment.grouped.GroupedKey; +import org.junit.jupiter.api.Test; + +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.*; + +class YearMonthGroupBySupplierTest { + + @Test + void get() { + String pattern = "yyyy-MM"; + YearMonthGroupBySupplier supplier = new YearMonthGroupBySupplier(pattern); + GroupedKey groupedKey = supplier.get(); + assertEquals(groupedKey.getKey(), YearMonth.now().format(DateTimeFormatter.ofPattern(pattern))); + } + + @Test + void getYyMm() { + String pattern = "yyMM"; + YearMonthGroupBySupplier supplier = new YearMonthGroupBySupplier(pattern); + GroupedKey groupedKey = supplier.get(); + assertEquals(groupedKey.getKey(), YearMonth.now().format(DateTimeFormatter.ofPattern(pattern))); + } +} \ No newline at end of file diff --git a/cosid-core/src/test/java/me/ahoo/cosid/snowflake/MillisecondSnowflakeIdTest.java b/cosid-core/src/test/java/me/ahoo/cosid/snowflake/MillisecondSnowflakeIdTest.java index dd24010c84..c6f15234b6 100644 --- a/cosid-core/src/test/java/me/ahoo/cosid/snowflake/MillisecondSnowflakeIdTest.java +++ b/cosid-core/src/test/java/me/ahoo/cosid/snowflake/MillisecondSnowflakeIdTest.java @@ -7,14 +7,13 @@ import me.ahoo.cosid.converter.Radix62IdConverter; import me.ahoo.cosid.test.ConcurrentGenerateSpec; import me.ahoo.cosid.test.ConcurrentGenerateStingSpec; +import me.ahoo.cosid.test.ModSpec; -import com.google.common.collect.Range; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.time.Duration; -import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.LockSupport; /** @@ -63,50 +62,20 @@ public void sequenceIncrement() { long id2 = snowflakeId.generate(); SnowflakeIdState snowflakeIdState2 = snowflakeId.friendlyId(id2); - assertThat(snowflakeIdState2.getTimestamp(), greaterThan(snowflakeIdState.getTimestamp())); - assertThat(snowflakeIdState2.getSequence(), greaterThan(snowflakeIdState.getSequence())); + assertThat( + snowflakeIdState2.getTimestamp() + ">" + snowflakeIdState.getTimestamp(), + snowflakeIdState2.getTimestamp(), greaterThan(snowflakeIdState.getTimestamp()) + ); + assertThat( + snowflakeIdState2.getSequence() + ">" + snowflakeIdState.getSequence(), + snowflakeIdState2.getSequence(), greaterThan(snowflakeIdState.getSequence()) + ); } @Test public void sequenceModUniformity() { - int divisor = 4; - int total = 99999; - int avg = total / divisor; - double diff = (avg * .001); - - int mod0Counter = 0; - int mod1Counter = 0; - int mod2Counter = 0; - int mod3Counter = 0; - for (int i = 0; i < total; i++) { - long id = snowflakeId.generate(); - int mod = (int) (id % divisor); - switch (mod) { - case 0: { - mod0Counter++; - break; - } - case 1: { - mod1Counter++; - break; - } - case 2: { - mod2Counter++; - break; - } - case 3: { - mod3Counter++; - break; - } - } - int wait = ThreadLocalRandom.current().nextInt(0, 1000); - LockSupport.parkNanos(wait); - } - assertThat((double) mod0Counter, closeTo(avg, diff)); - assertThat((double) mod1Counter, closeTo(avg, diff)); - assertThat((double) mod2Counter, closeTo(avg, diff)); - assertThat((double) mod3Counter, closeTo(avg, diff)); + new ModSpec(99999, 4, 100, snowflakeId::generate, ModSpec.DEFAULT_WAIT).verify(); } @Test diff --git a/cosid-core/src/test/java/me/ahoo/cosid/stat/StatisticalTest.java b/cosid-core/src/test/java/me/ahoo/cosid/stat/StatisticalTest.java new file mode 100644 index 0000000000..3e09dfb2fa --- /dev/null +++ b/cosid-core/src/test/java/me/ahoo/cosid/stat/StatisticalTest.java @@ -0,0 +1,89 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.stat; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.converter.Radix62IdConverter; +import me.ahoo.cosid.cosid.Radix36CosIdGenerator; +import me.ahoo.cosid.jvm.UuidGenerator; +import me.ahoo.cosid.segment.DefaultSegmentId; +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.StringSegmentId; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.StringSnowflakeId; +import me.ahoo.cosid.stat.generator.CosIdGeneratorStat; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; +import me.ahoo.cosid.stat.generator.SegmentIdStat; + +import me.ahoo.cosid.stat.generator.SimpleIdGeneratorStat; +import me.ahoo.cosid.stat.generator.SnowflakeIdStat; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class StatisticalTest { + + @Test + void statUuidGenerator() { + IdGeneratorStat stat = UuidGenerator.INSTANCE.stat(); + Assertions.assertNotNull(stat); + } + + @Test + void statSnowflakeId() { + IdGenerator snowflakeId = new MillisecondSnowflakeId(0); + IdGeneratorStat stat = snowflakeId.stat(); + Assertions.assertNotNull(stat); + assertThat(stat, Matchers.instanceOf(SnowflakeIdStat.class)); + SnowflakeIdStat snowflakeIdStat = (SnowflakeIdStat) stat; + assertThat(snowflakeIdStat.getMachineId(), equalTo(0)); + } + + @Test + void statStringSnowflakeId() { + IdGenerator snowflakeId = new StringSnowflakeId(new MillisecondSnowflakeId(0), Radix62IdConverter.INSTANCE); + IdGeneratorStat stat = snowflakeId.stat(); + Assertions.assertNotNull(stat); + assertThat(stat, Matchers.instanceOf(SimpleIdGeneratorStat.class)); + } + + @Test + void statSegmentId() { + IdGenerator segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + IdGeneratorStat stat = segmentId.stat(); + Assertions.assertNotNull(stat); + assertThat(stat, Matchers.instanceOf(SegmentIdStat.class)); + } + + @Test + void statStringSegmentId() { + IdGenerator segmentId = new StringSegmentId(new DefaultSegmentId(new IdSegmentDistributor.Mock()), Radix62IdConverter.INSTANCE); + IdGeneratorStat stat = segmentId.stat(); + Assertions.assertNotNull(stat); + assertThat(stat, Matchers.instanceOf(SimpleIdGeneratorStat.class)); + } + + @Test + void statCosIdGenerator() { + IdGenerator cosIdGenerator = new Radix36CosIdGenerator(0); + IdGeneratorStat stat = cosIdGenerator.stat(); + Assertions.assertNotNull(stat); + assertThat(stat, Matchers.instanceOf(CosIdGeneratorStat.class)); + CosIdGeneratorStat cosIdGeneratorStat = (CosIdGeneratorStat) stat; + assertThat(cosIdGeneratorStat.getMachineId(), equalTo(0)); + } +} \ No newline at end of file diff --git a/cosid-dependencies/build.gradle.kts b/cosid-dependencies/build.gradle.kts index cc61f438c6..4c40e188b4 100644 --- a/cosid-dependencies/build.gradle.kts +++ b/cosid-dependencies/build.gradle.kts @@ -1,4 +1,3 @@ - /* * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,17 +12,18 @@ */ dependencies { - api(platform("org.springframework.boot:spring-boot-dependencies:2.7.12")) - api(platform("org.springframework.cloud:spring-cloud-dependencies:2021.0.7")) - api(platform("com.squareup.okhttp3:okhttp-bom:4.11.0")) - api(platform("org.testcontainers:testcontainers-bom:1.18.3")) + api(platform(libs.springBootDependencies)) + api(platform(libs.springCloudDependencies)) + api(platform(libs.okhttpBom)) + api(platform(libs.testcontainersBom)) constraints { - api("org.projectlombok:lombok:1.18.28") - api("org.mybatis:mybatis:3.5.13") - api("com.google.guava:guava:30.0-jre") - api("org.junit-pioneer:junit-pioneer:1.9.1") - api("org.hamcrest:hamcrest:2.2") - api("org.openjdk.jmh:jmh-core:1.36") - api("org.openjdk.jmh:jmh-generator-annprocess:1.36") + api(libs.guava) + api(libs.mybatis) + api(libs.mybatisSpringBoot) + api(libs.springDocStarterWebfluxUi) + api(libs.junitPioneer) + api(libs.hamcrest) + api(libs.jmhCore) + api(libs.jmhGeneratorAnnprocess) } } diff --git a/cosid-flowable/build.gradle.kts b/cosid-flowable/build.gradle.kts new file mode 100644 index 0000000000..305065e4eb --- /dev/null +++ b/cosid-flowable/build.gradle.kts @@ -0,0 +1,6 @@ +dependencies { + implementation(libs.flowableEngineCommon) + api(project(":cosid-core")) + testImplementation(project(":cosid-test")) + +} diff --git a/cosid-flowable/src/main/java/me/ahoo/cosid/flowable/FlowableIdGenerator.java b/cosid-flowable/src/main/java/me/ahoo/cosid/flowable/FlowableIdGenerator.java new file mode 100644 index 0000000000..a846215225 --- /dev/null +++ b/cosid-flowable/src/main/java/me/ahoo/cosid/flowable/FlowableIdGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.flowable; + +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.provider.LazyIdGenerator; + +/** + * Flowable IdGenerator Based on CosId. + */ +public class FlowableIdGenerator implements org.flowable.common.engine.impl.cfg.IdGenerator { + /** + * The key of the system property that can be used to set the id generator name. + */ + public static final String ID_KEY = "cosid.flowable"; + private static final String ID_NAME; + private static final LazyIdGenerator ID_GENERATOR; + + static { + ID_NAME = System.getProperty(ID_KEY, IdGeneratorProvider.SHARE); + ID_GENERATOR = new LazyIdGenerator(ID_NAME); + } + + public String getNextId() { + return ID_GENERATOR.generateAsString(); + } +} diff --git a/cosid-flowable/src/test/java/me/ahoo/cosid/flowable/FlowableIdGeneratorTest.java b/cosid-flowable/src/test/java/me/ahoo/cosid/flowable/FlowableIdGeneratorTest.java new file mode 100644 index 0000000000..4a6f430bc8 --- /dev/null +++ b/cosid-flowable/src/test/java/me/ahoo/cosid/flowable/FlowableIdGeneratorTest.java @@ -0,0 +1,35 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.flowable; + +import static me.ahoo.cosid.flowable.FlowableIdGenerator.ID_KEY; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +class FlowableIdGeneratorTest { + + @SetSystemProperty(key = ID_KEY, value = "flowable") + @Test + void getNextId() { + DefaultIdGeneratorProvider.INSTANCE.set("flowable", MockIdGenerator.usePrefix("flowable_")); + String id = new FlowableIdGenerator().getNextId(); + assertThat(id, startsWith("flowable_")); + } +} \ No newline at end of file diff --git a/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/GroupedJdbcIdSegmentDistributorTest.java b/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/GroupedJdbcIdSegmentDistributorTest.java new file mode 100644 index 0000000000..6a69285786 --- /dev/null +++ b/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/GroupedJdbcIdSegmentDistributorTest.java @@ -0,0 +1,43 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.jdbc; + +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; +import me.ahoo.cosid.test.segment.distributor.GroupedIdSegmentDistributorSpec; + +import org.junit.jupiter.api.BeforeEach; + +import javax.sql.DataSource; + +/** + * @author ahoo wang + */ +class GroupedJdbcIdSegmentDistributorTest extends GroupedIdSegmentDistributorSpec { + DataSource dataSource; + JdbcIdSegmentDistributorFactory distributorFactory; + JdbcIdSegmentInitializer mySqlIdSegmentInitializer; + + @BeforeEach + void setup() { + dataSource = DataSourceFactory.INSTANCE.createDataSource(); + mySqlIdSegmentInitializer = new JdbcIdSegmentInitializer(dataSource); + distributorFactory = + new JdbcIdSegmentDistributorFactory(dataSource, true, mySqlIdSegmentInitializer, JdbcIdSegmentDistributor.INCREMENT_MAX_ID_SQL, JdbcIdSegmentDistributor.FETCH_MAX_ID_SQL); + } + + @Override + protected IdSegmentDistributorFactory getFactory() { + return distributorFactory; + } +} diff --git a/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/JdbcIdSegmentDistributorTest.java b/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/JdbcIdSegmentDistributorTest.java index 74f931bace..92bf6a4375 100644 --- a/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/JdbcIdSegmentDistributorTest.java +++ b/cosid-jdbc/src/test/java/me/ahoo/cosid/jdbc/JdbcIdSegmentDistributorTest.java @@ -55,7 +55,7 @@ protected void setMaxIdBack(T distributor, long @Override public void nextMaxIdWhenBack() { - //TODO + } @Test diff --git a/cosid-mod-test/build.gradle.kts b/cosid-mod-test/build.gradle.kts new file mode 100644 index 0000000000..f7bfe5a095 --- /dev/null +++ b/cosid-mod-test/build.gradle.kts @@ -0,0 +1,18 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +dependencies { + implementation(project(":cosid-core")) + testImplementation(project(":cosid-test")) + testImplementation("com.netease.nim:camellia-id-gen-core:1.2.20") + testImplementation("org.apache.shardingsphere:shardingsphere-sharding-core:5.4.1") +} diff --git a/cosid-mod-test/src/jmh/java/me/ahoo/cosid/test/mod/SnowflakeIdBenchmark.java b/cosid-mod-test/src/jmh/java/me/ahoo/cosid/test/mod/SnowflakeIdBenchmark.java new file mode 100644 index 0000000000..489d4f438a --- /dev/null +++ b/cosid-mod-test/src/jmh/java/me/ahoo/cosid/test/mod/SnowflakeIdBenchmark.java @@ -0,0 +1,85 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.test.mod; + +import me.ahoo.cosid.snowflake.ClockSyncSnowflakeId; +import me.ahoo.cosid.snowflake.DefaultSnowflakeFriendlyId; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.SnowflakeFriendlyId; + +import com.netease.nim.camellia.id.gen.snowflake.CamelliaSnowflakeConfig; +import com.netease.nim.camellia.id.gen.snowflake.CamelliaSnowflakeIdGen; +import org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Properties; + +/** + * SnowflakeId Benchmark. + * + * @author ahoo wang + */ +@State(Scope.Benchmark) +public class SnowflakeIdBenchmark { + public static final int TEST_MACHINE_ID = 1; + SnowflakeFriendlyId cosidSnowflakeId; + CamelliaSnowflakeIdGen camelliaSnowflakeId; + SnowflakeKeyGenerateAlgorithm shardingsphereSnowflakeId; + SnowflakeKeyGenerateAlgorithm shardingsphereSnowflakeIdVibration127; + + /** + * Initialize IdGenerator. + */ + @Setup + public void setup() { + MillisecondSnowflakeId idGen = new MillisecondSnowflakeId(TEST_MACHINE_ID); + cosidSnowflakeId = new DefaultSnowflakeFriendlyId(new ClockSyncSnowflakeId(idGen)); + CamelliaSnowflakeConfig camelliaSnowflakeCfg = new CamelliaSnowflakeConfig(); + camelliaSnowflakeCfg.setWorkerIdGen(maxWorkerId -> TEST_MACHINE_ID); + camelliaSnowflakeId = new CamelliaSnowflakeIdGen(camelliaSnowflakeCfg); + + Properties defaultProperties = new Properties(); + defaultProperties.setProperty("max-tolerate-time-difference-milliseconds", String.valueOf(200)); + shardingsphereSnowflakeId = new SnowflakeKeyGenerateAlgorithm(); + shardingsphereSnowflakeId.init(defaultProperties); + + Properties vibration127Properties = new Properties(defaultProperties); + shardingsphereSnowflakeIdVibration127 = new SnowflakeKeyGenerateAlgorithm(); + vibration127Properties.setProperty("max-vibration-offset", String.valueOf(127)); + shardingsphereSnowflakeIdVibration127.init(vibration127Properties); + } + + @Benchmark + public long cosid() { + return cosidSnowflakeId.generate(); + } + + @Benchmark + public long netease() { + return camelliaSnowflakeId.genId(); + } + + @Benchmark + public long shardingsphere() { + return shardingsphereSnowflakeId.generateKey(); + } + + @Benchmark + public long shardingsphereVibration127() { + return shardingsphereSnowflakeIdVibration127.generateKey(); + } +} diff --git a/cosid-mod-test/src/test/java/me/ahoo/cosid/test/mod/ModTest.java b/cosid-mod-test/src/test/java/me/ahoo/cosid/test/mod/ModTest.java new file mode 100644 index 0000000000..8a0ed03bff --- /dev/null +++ b/cosid-mod-test/src/test/java/me/ahoo/cosid/test/mod/ModTest.java @@ -0,0 +1,98 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.test.mod; + +import me.ahoo.cosid.snowflake.ClockSyncSnowflakeId; +import me.ahoo.cosid.snowflake.DefaultSnowflakeFriendlyId; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.SnowflakeFriendlyId; +import me.ahoo.cosid.test.ModSpec; + +import com.netease.nim.camellia.id.gen.snowflake.CamelliaSnowflakeConfig; +import com.netease.nim.camellia.id.gen.snowflake.CamelliaSnowflakeIdGen; +import org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.Properties; +import java.util.concurrent.locks.LockSupport; + +public class ModTest { + public static final int TEST_MACHINE_ID = 1; + private static final int ITERATIONS = 1000; + private static final double ALLOWABLE_POP_STD = 10; + SnowflakeFriendlyId cosidSnowflakeId; + CamelliaSnowflakeIdGen camelliaSnowflakeId; + SnowflakeKeyGenerateAlgorithm shardingsphereSnowflakeId = new SnowflakeKeyGenerateAlgorithm(); + + @BeforeEach + void setup() { + MillisecondSnowflakeId idGen = new MillisecondSnowflakeId(TEST_MACHINE_ID); + cosidSnowflakeId = new DefaultSnowflakeFriendlyId(new ClockSyncSnowflakeId(idGen)); + CamelliaSnowflakeConfig camelliaSnowflakeCfg = new CamelliaSnowflakeConfig(); + camelliaSnowflakeCfg.setWorkerIdGen(maxWorkerId -> TEST_MACHINE_ID); + camelliaSnowflakeId = new CamelliaSnowflakeIdGen(camelliaSnowflakeCfg); + } + + @Test + public void cosidMod4() { + ModSpec spec = new ModSpec(ITERATIONS, 4, ALLOWABLE_POP_STD, cosidSnowflakeId::generate, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } + + @Test + public void shardingsphereMod4Default() { + ModSpec spec = new ModSpec(ITERATIONS, 4, ALLOWABLE_POP_STD, shardingsphereSnowflakeId::generateKey, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } + + @Test + public void shardingsphereMod4Vibration3() { + SnowflakeKeyGenerateAlgorithm idGen = new SnowflakeKeyGenerateAlgorithm(); + Properties properties = new Properties(); + properties.setProperty("max-vibration-offset", String.valueOf(3)); + idGen.init(properties); + ModSpec spec = new ModSpec(ITERATIONS, 4, ALLOWABLE_POP_STD, idGen::generateKey, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } + + @Test + public void neteaseMod4() { + ModSpec spec = new ModSpec(ITERATIONS, 4, ALLOWABLE_POP_STD, camelliaSnowflakeId::genId, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } + + @Test + public void cosidMod128() { + ModSpec spec = new ModSpec(ITERATIONS, 128, ALLOWABLE_POP_STD, cosidSnowflakeId::generate, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } + + @Test + public void neteaseMod128() { + ModSpec spec = new ModSpec(ITERATIONS, 128, ALLOWABLE_POP_STD, camelliaSnowflakeId::genId, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } + + @Test + public void shardingsphereMod128Vibration127() { + SnowflakeKeyGenerateAlgorithm idGen = new SnowflakeKeyGenerateAlgorithm(); + Properties properties = new Properties(); + properties.setProperty("max-vibration-offset", String.valueOf(127)); + idGen.init(properties); + ModSpec spec = new ModSpec(ITERATIONS, 128, ALLOWABLE_POP_STD, idGen::generateKey, () -> LockSupport.parkNanos(Duration.ofMillis(1).toNanos())); + spec.run(); + } +} diff --git a/cosid-mongo/build.gradle.kts b/cosid-mongo/build.gradle.kts index f4caf435fa..b5fe999e73 100644 --- a/cosid-mongo/build.gradle.kts +++ b/cosid-mongo/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { compileOnly("org.mongodb:mongodb-driver-reactivestreams") compileOnly("io.projectreactor:reactor-core") testImplementation(project(":cosid-test")) + testImplementation("io.projectreactor:reactor-test") testImplementation("org.mongodb:mongodb-driver-sync") testImplementation("io.projectreactor:reactor-core") testImplementation("org.mongodb:mongodb-driver-reactivestreams") diff --git a/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/Documents.java b/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/Documents.java index 26ea67128a..03fe608200 100644 --- a/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/Documents.java +++ b/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/Documents.java @@ -13,6 +13,8 @@ package me.ahoo.cosid.mongo; +import me.ahoo.cosid.mongo.reactive.BlockingAdapter; + import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.client.model.ReturnDocument; @@ -20,5 +22,6 @@ public interface Documents { String ID_FIELD = "_id"; FindOneAndUpdateOptions UPDATE_AFTER_OPTIONS = new FindOneAndUpdateOptions() - .returnDocument(ReturnDocument.AFTER); + .returnDocument(ReturnDocument.AFTER) + .maxTime(BlockingAdapter.DEFAULT_TIME_OUT.toMillis(), java.util.concurrent.TimeUnit.MILLISECONDS); } diff --git a/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/reactive/BlockingAdapter.java b/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/reactive/BlockingAdapter.java index 6c273a6643..d31dff77df 100644 --- a/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/reactive/BlockingAdapter.java +++ b/cosid-mongo/src/main/java/me/ahoo/cosid/mongo/reactive/BlockingAdapter.java @@ -22,7 +22,7 @@ import java.util.concurrent.TimeoutException; public final class BlockingAdapter { - private static final Duration DEFAULT_TIME_OUT = Duration.ofSeconds(10); + public static final Duration DEFAULT_TIME_OUT = Duration.ofSeconds(10); private BlockingAdapter() { } diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/GroupedMongoReactiveIdSegmentDistributorTest.java b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/GroupedMongoReactiveIdSegmentDistributorTest.java new file mode 100644 index 0000000000..2df95e9095 --- /dev/null +++ b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/GroupedMongoReactiveIdSegmentDistributorTest.java @@ -0,0 +1,44 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.mongo; + +import me.ahoo.cosid.mongo.reactive.MongoReactiveIdSegmentDistributorFactory; +import me.ahoo.cosid.mongo.reactive.MongoReactiveIdSegmentInitializer; +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; +import me.ahoo.cosid.test.container.MongoLauncher; +import me.ahoo.cosid.test.segment.distributor.GroupedIdSegmentDistributorSpec; + +import com.mongodb.reactivestreams.client.MongoClients; +import com.mongodb.reactivestreams.client.MongoDatabase; +import org.junit.jupiter.api.BeforeEach; + +class GroupedMongoReactiveIdSegmentDistributorTest extends GroupedIdSegmentDistributorSpec { + MongoDatabase mongoDatabase; + IdSegmentDistributorFactory distributorFactory; + MongoReactiveIdSegmentInitializer idSegmentInitializer; + + @BeforeEach + void setup() { + mongoDatabase = MongoClients.create(MongoLauncher.getConnectionString()).getDatabase("cosid_db"); + idSegmentInitializer = new MongoReactiveIdSegmentInitializer(mongoDatabase); + idSegmentInitializer.ensureCosIdCollection(); + distributorFactory = + new MongoReactiveIdSegmentDistributorFactory(mongoDatabase, true); + } + + @Override + protected IdSegmentDistributorFactory getFactory() { + return distributorFactory; + } +} \ No newline at end of file diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentDistributorTest.java b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentDistributorTest.java index b3bd53c9e9..e28a6ac977 100644 --- a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentDistributorTest.java +++ b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentDistributorTest.java @@ -15,6 +15,7 @@ import me.ahoo.cosid.segment.IdSegmentDistributor; import me.ahoo.cosid.segment.IdSegmentDistributorFactory; +import me.ahoo.cosid.test.container.MongoLauncher; import me.ahoo.cosid.test.segment.distributor.IdSegmentDistributorSpec; import com.mongodb.client.MongoClients; diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentInitializerTest.java b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentInitializerTest.java index 2c584dc5d7..bfdd6df322 100644 --- a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentInitializerTest.java +++ b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoIdSegmentInitializerTest.java @@ -16,6 +16,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import me.ahoo.cosid.test.MockIdGenerator; +import me.ahoo.cosid.test.container.MongoLauncher; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoDatabase; diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoMachineIdDistributorTest.java b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoMachineIdDistributorTest.java index 79b8b18a4a..ebc4709f0d 100644 --- a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoMachineIdDistributorTest.java +++ b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoMachineIdDistributorTest.java @@ -16,14 +16,13 @@ import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; import me.ahoo.cosid.machine.MachineIdDistributor; import me.ahoo.cosid.machine.MachineStateStorage; +import me.ahoo.cosid.test.container.MongoLauncher; import me.ahoo.cosid.test.machine.distributor.MachineIdDistributorSpec; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoDatabase; import org.junit.jupiter.api.BeforeEach; -import java.time.Duration; - class MongoMachineIdDistributorTest extends MachineIdDistributorSpec { MongoDatabase mongoDatabase; MachineIdDistributor machineIdDistributor; @@ -44,12 +43,5 @@ void setup() { protected MachineIdDistributor getDistributor() { return machineIdDistributor; } - - @Override - protected Duration getSafeGuardDuration() { - if (System.getenv().containsKey("CI")) { - return Duration.ofSeconds(10); - } - return super.getSafeGuardDuration(); - } + } \ No newline at end of file diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveIdSegmentDistributorTest.java b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveIdSegmentDistributorTest.java index 730f71c578..55275b0611 100644 --- a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveIdSegmentDistributorTest.java +++ b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveIdSegmentDistributorTest.java @@ -13,15 +13,26 @@ package me.ahoo.cosid.mongo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + import me.ahoo.cosid.mongo.reactive.MongoReactiveIdSegmentDistributorFactory; import me.ahoo.cosid.mongo.reactive.MongoReactiveIdSegmentInitializer; import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; import me.ahoo.cosid.segment.IdSegmentDistributorFactory; +import me.ahoo.cosid.segment.SegmentChainId; +import me.ahoo.cosid.test.MockIdGenerator; +import me.ahoo.cosid.test.container.MongoLauncher; import me.ahoo.cosid.test.segment.distributor.IdSegmentDistributorSpec; import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoDatabase; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.test.StepVerifier; class MongoReactiveIdSegmentDistributorTest extends IdSegmentDistributorSpec { MongoDatabase mongoDatabase; @@ -32,6 +43,7 @@ class MongoReactiveIdSegmentDistributorTest extends IdSegmentDistributorSpec { void setup() { mongoDatabase = MongoClients.create(MongoLauncher.getConnectionString()).getDatabase("cosid_db"); idSegmentInitializer = new MongoReactiveIdSegmentInitializer(mongoDatabase); + idSegmentInitializer.ensureCosIdCollection(); distributorFactory = new MongoReactiveIdSegmentDistributorFactory(mongoDatabase, true); @@ -51,4 +63,36 @@ protected void setMaxIdBack(T distributor, long public void nextMaxIdWhenBack() { } + + @Test + public void nextMaxIdInParallel() { + Mono mono = Mono.fromRunnable(() -> { + String namespace = MockIdGenerator.INSTANCE.generateAsString(); + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextMaxIdIParallel", TEST_OFFSET, TEST_STEP); + IdSegmentDistributor distributor = factory().create(definition); + long expected = TEST_OFFSET + TEST_STEP; + long actual = distributor.nextMaxId(); + assertThat(actual, equalTo(expected)); + long actual2 = distributor.nextMaxId(); + assertThat(actual2, greaterThan(actual)); + }).subscribeOn(Schedulers.parallel()); + StepVerifier.create(mono).verifyComplete(); + } + + @Test + public void batchNextMaxId() { + String namespace = MockIdGenerator.INSTANCE.generateAsString(); + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "batchNextMaxId", 1, 1); + IdSegmentDistributor distributor = factory().create(definition); + SegmentChainId segmentChainId = new SegmentChainId(distributor); + for (int i = 0; i < 1000; i++) { + segmentChainId.generateAsString(); + } + Mono mono = Mono.fromRunnable(() -> { + for (int i = 0; i < 1000; i++) { + segmentChainId.generateAsString(); + } + }).subscribeOn(Schedulers.single()); + StepVerifier.create(mono).verifyComplete(); + } } \ No newline at end of file diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveMachineIdDistributorTest.java b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveMachineIdDistributorTest.java index f869b37b4d..96dc807ff7 100644 --- a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveMachineIdDistributorTest.java +++ b/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoReactiveMachineIdDistributorTest.java @@ -18,14 +18,13 @@ import me.ahoo.cosid.machine.MachineStateStorage; import me.ahoo.cosid.mongo.reactive.MongoReactiveMachineCollection; import me.ahoo.cosid.mongo.reactive.MongoReactiveMachineInitializer; +import me.ahoo.cosid.test.container.MongoLauncher; import me.ahoo.cosid.test.machine.distributor.MachineIdDistributorSpec; import com.mongodb.reactivestreams.client.MongoClients; import com.mongodb.reactivestreams.client.MongoDatabase; import org.junit.jupiter.api.BeforeEach; -import java.time.Duration; - class MongoReactiveMachineIdDistributorTest extends MachineIdDistributorSpec { MongoDatabase mongoDatabase; MachineIdDistributor machineIdDistributor; @@ -33,7 +32,7 @@ class MongoReactiveMachineIdDistributorTest extends MachineIdDistributorSpec { @BeforeEach void setup() { - mongoDatabase = MongoClients.create(MongoLauncher.getConnectionString()).getDatabase("cosid_db"); + mongoDatabase = MongoClients.create(MongoLauncher.getConnectionString()).getDatabase("cosid_db_reactive"); machineInitializer = new MongoReactiveMachineInitializer(mongoDatabase); machineInitializer.ensureMachineCollection(); machineIdDistributor = new MongoMachineIdDistributor( @@ -46,12 +45,5 @@ void setup() { protected MachineIdDistributor getDistributor() { return machineIdDistributor; } - - @Override - protected Duration getSafeGuardDuration() { - if (System.getenv().containsKey("CI")) { - return Duration.ofSeconds(10); - } - return super.getSafeGuardDuration(); - } + } \ No newline at end of file diff --git a/cosid-mybatis/src/main/java/me/ahoo/cosid/mybatis/CosIdPlugin.java b/cosid-mybatis/src/main/java/me/ahoo/cosid/mybatis/CosIdPlugin.java index 2822c0f479..6f2e2d4b2e 100644 --- a/cosid-mybatis/src/main/java/me/ahoo/cosid/mybatis/CosIdPlugin.java +++ b/cosid-mybatis/src/main/java/me/ahoo/cosid/mybatis/CosIdPlugin.java @@ -15,7 +15,6 @@ import me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry; -import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; @@ -35,35 +34,35 @@ */ @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})}) public class CosIdPlugin implements Interceptor { - + public static final String DEFAULT_LIST_KEY = "list"; private final CosIdAccessorRegistry accessorRegistry; private final String listKey; - + public CosIdPlugin(CosIdAccessorRegistry accessorRegistry) { this(accessorRegistry, DEFAULT_LIST_KEY); } - + public CosIdPlugin(CosIdAccessorRegistry accessorRegistry, String listKey) { this.accessorRegistry = accessorRegistry; this.listKey = listKey; } - + @SuppressWarnings("rawtypes") @Override public Object intercept(Invocation invocation) throws Throwable { - + Object[] args = invocation.getArgs(); MappedStatement statement = (MappedStatement) args[0]; if (!SqlCommandType.INSERT.equals(statement.getSqlCommandType())) { return invocation.proceed(); } - + Object parameter = args[1]; if (Objects.isNull(parameter)) { return invocation.proceed(); } - + if (!(parameter instanceof Map)) { accessorRegistry.ensureId(parameter); return invocation.proceed(); diff --git a/cosid-mybatis/src/test/java/me/ahoo/cosid/mybatis/CosIdPluginTest.java b/cosid-mybatis/src/test/java/me/ahoo/cosid/mybatis/CosIdPluginTest.java index 83196cea4c..9f5bdd5d6f 100644 --- a/cosid-mybatis/src/test/java/me/ahoo/cosid/mybatis/CosIdPluginTest.java +++ b/cosid-mybatis/src/test/java/me/ahoo/cosid/mybatis/CosIdPluginTest.java @@ -1,5 +1,6 @@ package me.ahoo.cosid.mybatis; +import com.google.common.collect.Lists; import me.ahoo.cosid.accessor.parser.DefaultAccessorParser; import me.ahoo.cosid.accessor.registry.DefaultAccessorRegistry; import me.ahoo.cosid.annotation.AnnotationDefinitionParser; @@ -10,15 +11,27 @@ import lombok.SneakyThrows; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.cursor.Cursor; +import org.apache.ibatis.executor.BatchResult; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.transaction.Transaction; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -149,20 +162,92 @@ public void setId(long id) { } } - public static class InvocationTarget { + public static class InvocationTarget implements Executor { public static final Method INVOKE_METHOD; static { try { - INVOKE_METHOD = InvocationTarget.class.getMethod("invoke", MappedStatement.class, Object.class); + INVOKE_METHOD = Executor.class.getMethod("update", MappedStatement.class, Object.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } - - public void invoke(MappedStatement statement, Object entity) { + + + @Override + public int update(MappedStatement ms, Object parameter) throws SQLException { + return 0; + } + + @Override + public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException { + return Lists.newArrayList(); + } + + @Override + public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { + return Lists.newArrayList(); + } + + @Override + public Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException { + return null; + } + + @Override + public List flushStatements() throws SQLException { + return Lists.newArrayList(); + } + + @Override + public void commit(boolean required) throws SQLException { + + } + + @Override + public void rollback(boolean required) throws SQLException { + + } + + @Override + public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { + return null; + } + + @Override + public boolean isCached(MappedStatement ms, CacheKey key) { + return false; + } + + @Override + public void clearLocalCache() { + + } + + @Override + public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class targetType) { + + } + + @Override + public Transaction getTransaction() { + return null; + } + + @Override + public void close(boolean forceRollback) { + + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public void setExecutorWrapper(Executor executor) { + } - } } diff --git a/cosid-proxy-server/Dockerfile b/cosid-proxy-server/Dockerfile index 42a5870b5f..244f48a4f8 100644 --- a/cosid-proxy-server/Dockerfile +++ b/cosid-proxy-server/Dockerfile @@ -5,7 +5,7 @@ ARG APP_NAME=cosid-proxy-server ARG WORK_HOME=/opt/${APP_NAME} -FROM openjdk:17.0.2-jdk-slim AS base +FROM openjdk:21-jdk-slim AS base FROM base as build ARG WORK_HOME diff --git a/cosid-proxy-server/build.gradle.kts b/cosid-proxy-server/build.gradle.kts index 6c1bc113f9..a45e3dc681 100644 --- a/cosid-proxy-server/build.gradle.kts +++ b/cosid-proxy-server/build.gradle.kts @@ -17,7 +17,7 @@ plugins { java { toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) + languageVersion.set(JavaLanguageVersion.of(17)) } } @@ -35,13 +35,13 @@ application { mainClass.set("me.ahoo.cosid.proxy.server.ProxyServer") applicationDefaultJvmArgs = listOf( - "-Xms512M", - "-Xmx512M", - "-XX:MaxMetaspaceSize=128M", - "-XX:MaxDirectMemorySize=256M", + "-Xms1280M", + "-Xmx1280M", + "-XX:MaxMetaspaceSize=256M", + "-XX:MaxDirectMemorySize=512M", "-Xss1m", "-server", - "-XX:+UseG1GC", + "-XX:+UseZGC", "-Xlog:gc*:file=logs/${applicationName}-gc.log:time,tags:filecount=10,filesize=32M", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=data", @@ -79,6 +79,7 @@ dependencies { implementation("com.google.guava:guava") implementation("io.netty:netty-all") implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("org.springdoc:springdoc-openapi-starter-webflux-ui") compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/cosid-proxy-server/src/dist/config/application.yaml b/cosid-proxy-server/src/dist/config/application.yaml index a08fa7eda6..0d363ed7cb 100644 --- a/cosid-proxy-server/src/dist/config/application.yaml +++ b/cosid-proxy-server/src/dist/config/application.yaml @@ -1,14 +1,23 @@ +management: + endpoints: + web: + exposure: + include: + - health + - cosid + - cosidGenerator + - cosidStringGenerator +springdoc: + show-actuator: true server: port: 8688 spring: application: name: ${service.name:cosid-proxy} - # redis: - # url: redis://localhost:6379 autoconfigure: exclude: - # - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration - - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration +# - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration + - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration # datasource: # url: jdbc:mysql://localhost:3306/cosid_db # username: root @@ -19,15 +28,13 @@ cosid: enabled: true distributor: type: redis - guarder: - enabled: true snowflake: enabled: true segment: enabled: true - mode: chain distributor: type: redis logging: level: me.ahoo.cosid: debug + diff --git a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/configuration/SwaggerConfiguration.java b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/configuration/SwaggerConfiguration.java new file mode 100644 index 0000000000..4f34297677 --- /dev/null +++ b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/configuration/SwaggerConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.proxy.server.configuration; + +import com.google.common.base.MoreObjects; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfiguration { + + @Bean + public OpenApiCustomizer openApiCustomizer() { + var version = MoreObjects.firstNonNull(getClass().getPackage().getImplementationVersion(), "2.4.0"); + return openApi -> { + var info = new Info() + .title("CosId Proxy Server") + .description("Universal, flexible, high-performance distributed ID generator.") + .contact(new Contact().name("Ahoo Wang").url("https://github.com/Ahoo-Wang/CosId")) + .license(new License().url("https://github.com/Ahoo-Wang/CosId/blob/main/LICENSE").name("Apache 2.0")) + .version(version); + openApi.info(info); + }; + } +} diff --git a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/IdController.java b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/IdController.java index 7ba42ebcde..0a85521d1f 100644 --- a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/IdController.java +++ b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/IdController.java @@ -15,6 +15,7 @@ import me.ahoo.cosid.provider.IdGeneratorProvider; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,6 +39,7 @@ public IdController(IdGeneratorProvider provider) { this.provider = provider; } + @Operation(summary = "Generate a ID by share.") @GetMapping public long generate() { return provider @@ -45,6 +47,7 @@ public long generate() { .generate(); } + @Operation(summary = "Generate a ID by id name.") @GetMapping("{name}") public long generate(@PathVariable String name) { return provider @@ -52,6 +55,7 @@ public long generate(@PathVariable String name) { .generate(); } + @Operation(summary = "Generate a ID as String by share.") @GetMapping("/as-string") public String generateAsString() { return provider @@ -59,6 +63,7 @@ public String generateAsString() { .generateAsString(); } + @Operation(summary = "Generate a ID as String by id name.") @GetMapping("/as-string/{name}") public String generateAsString(@PathVariable String name) { return provider diff --git a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/MachineController.java b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/MachineController.java index 092cc758ee..a3b3c67d26 100644 --- a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/MachineController.java +++ b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/MachineController.java @@ -19,6 +19,7 @@ import me.ahoo.cosid.machine.MachineIdOverflowException; import me.ahoo.cosid.machine.MachineState; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -46,6 +47,7 @@ public MachineController(MachineIdDistributor distributor) { /** * Distribute a machine ID, the operation is idempotent. */ + @Operation(summary = "Distribute a machine ID, the operation is idempotent.") @PostMapping("/{namespace}") public MachineState distribute(@PathVariable String namespace, int machineBit, InstanceId instanceId, String safeGuardDuration) throws MachineIdOverflowException { return distributor.distribute(namespace, machineBit, instanceId, Duration.parse(safeGuardDuration)); @@ -54,6 +56,7 @@ public MachineState distribute(@PathVariable String namespace, int machineBit, I /** * Revert a machine ID, the operation is idempotent. */ + @Operation(summary = "Revert a machine ID, the operation is idempotent.") @DeleteMapping("/{namespace}") public void revert(@PathVariable String namespace, InstanceId instanceId) { distributor.revert(namespace, instanceId); @@ -63,6 +66,7 @@ public void revert(@PathVariable String namespace, InstanceId instanceId) { * Guard a machine ID. */ @PatchMapping("/{namespace}") + @Operation(summary = "Guard a machine ID.") public void guard(@PathVariable String namespace, InstanceId instanceId, String safeGuardDuration) throws MachineIdLostException { distributor.guard(namespace, instanceId, Duration.parse(safeGuardDuration)); } diff --git a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/SegmentController.java b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/SegmentController.java index 4817eaa362..bb84969cfc 100644 --- a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/SegmentController.java +++ b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/controller/SegmentController.java @@ -18,6 +18,7 @@ import me.ahoo.cosid.segment.IdSegmentDistributorFactory; import com.google.common.base.Preconditions; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -45,6 +46,7 @@ public SegmentController(IdSegmentDistributorFactory distributorFactory) { /** * Create an ID segment dispatcher, the operation is idempotent. */ + @Operation(summary = "Create an ID segment dispatcher, the operation is idempotent.") @PostMapping("/distributor/{namespace}/{name}") public void createDistributor(@PathVariable String namespace, @PathVariable String name, long offset, long step) { String namespacedName = IdSegmentDistributor.getNamespacedName(namespace, name); @@ -52,6 +54,7 @@ public void createDistributor(@PathVariable String namespace, @PathVariable Stri key -> distributorFactory.create(new IdSegmentDistributorDefinition(namespace, name, offset, step))); } + @Operation(summary = "Get next max id.") @PatchMapping("/{namespace}/{name}") public long nextMaxId(@PathVariable String namespace, @PathVariable String name, long step) { String namespacedName = IdSegmentDistributor.getNamespacedName(namespace, name); diff --git a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/ErrorResponse.java b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/ErrorResponse.java index 8217f91b90..150bea5900 100644 --- a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/ErrorResponse.java +++ b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/ErrorResponse.java @@ -25,10 +25,13 @@ public class ErrorResponse { public static final String BAD_REQUEST = "400"; + public static final String MACHINE_ID_OVERFLOW = "M-01"; + public static final String NOT_FOUND_MACHINE_STATE = "M-02"; + public static final String MACHINE_ID_LOST = "M-03"; private final String code; private final String msg; - @Nullable + private final List errors; public ErrorResponse(String code, String msg, List errors) { @@ -45,6 +48,7 @@ public String getMsg() { return msg; } + @Nullable public List getErrors() { return errors; } diff --git a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/GlobalRestExceptionHandler.java b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/GlobalRestExceptionHandler.java index 7566466a37..cd8a328c8a 100644 --- a/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/GlobalRestExceptionHandler.java +++ b/cosid-proxy-server/src/main/java/me/ahoo/cosid/proxy/server/error/GlobalRestExceptionHandler.java @@ -13,6 +13,10 @@ package me.ahoo.cosid.proxy.server.error; +import static me.ahoo.cosid.proxy.server.error.ErrorResponse.MACHINE_ID_LOST; +import static me.ahoo.cosid.proxy.server.error.ErrorResponse.MACHINE_ID_OVERFLOW; +import static me.ahoo.cosid.proxy.server.error.ErrorResponse.NOT_FOUND_MACHINE_STATE; + import me.ahoo.cosid.machine.MachineIdLostException; import me.ahoo.cosid.machine.MachineIdOverflowException; import me.ahoo.cosid.machine.NotFoundMachineStateException; @@ -56,21 +60,21 @@ public ResponseEntity handleClientException(IllegalArgumentExcept public ResponseEntity handleCosIdException(MachineIdOverflowException ex) { return ResponseEntity .badRequest() - .body(ErrorResponse.of("M-01", ex.getMessage())); + .body(ErrorResponse.of(MACHINE_ID_OVERFLOW, ex.getMessage())); } @ExceptionHandler(NotFoundMachineStateException.class) public ResponseEntity handleCosIdException(NotFoundMachineStateException ex) { return ResponseEntity .badRequest() - .body(ErrorResponse.of("M-02", ex.getMessage())); + .body(ErrorResponse.of(NOT_FOUND_MACHINE_STATE, ex.getMessage())); } @ExceptionHandler(MachineIdLostException.class) public ResponseEntity handleCosIdException(MachineIdLostException ex) { return ResponseEntity .badRequest() - .body(ErrorResponse.of("M-03", ex.getMessage())); + .body(ErrorResponse.of(MACHINE_ID_LOST, ex.getMessage())); } @ResponseStatus(HttpStatus.BAD_REQUEST) diff --git a/cosid-proxy-server/src/main/resources/application.yaml b/cosid-proxy-server/src/main/resources/application.yaml index abe91e1457..0d363ed7cb 100644 --- a/cosid-proxy-server/src/main/resources/application.yaml +++ b/cosid-proxy-server/src/main/resources/application.yaml @@ -1,10 +1,19 @@ +management: + endpoints: + web: + exposure: + include: + - health + - cosid + - cosidGenerator + - cosidStringGenerator +springdoc: + show-actuator: true server: port: 8688 spring: application: name: ${service.name:cosid-proxy} -# redis: -# url: redis://localhost:6379 autoconfigure: exclude: # - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration @@ -19,15 +28,13 @@ cosid: enabled: true distributor: type: redis - guarder: - enabled: true snowflake: enabled: true segment: enabled: true - mode: chain distributor: type: redis logging: level: me.ahoo.cosid: debug + diff --git a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ErrorResponse.java b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ErrorResponse.java index f67a2d7dcc..496a990b66 100644 --- a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ErrorResponse.java +++ b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ErrorResponse.java @@ -29,6 +29,9 @@ public class ErrorResponse { public static final String BAD_REQUEST = "400"; + public static final String MACHINE_ID_OVERFLOW = "M-01"; + public static final String NOT_FOUND_MACHINE_STATE = "M-02"; + public static final String MACHINE_ID_LOST = "M-03"; private final String code; private final String msg; @@ -54,23 +57,6 @@ public List getErrors() { return errors; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ErrorResponse)) { - return false; - } - ErrorResponse that = (ErrorResponse) o; - return Objects.equals(getCode(), that.getCode()) && Objects.equals(getMsg(), that.getMsg()) && Objects.equals(getErrors(), that.getErrors()); - } - - @Override - public int hashCode() { - return Objects.hash(getCode(), getMsg(), getErrors()); - } - public static ErrorResponse of(String code, String msg) { return new ErrorResponse(code, msg, Collections.emptyList()); } diff --git a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributor.java b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributor.java index 799ce853ec..6abdfadda5 100644 --- a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributor.java +++ b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributor.java @@ -13,8 +13,6 @@ package me.ahoo.cosid.proxy; -import static me.ahoo.cosid.proxy.ProxyMachineIdDistributor.JSON; - import me.ahoo.cosid.segment.IdSegmentDistributor; import com.google.common.base.Preconditions; @@ -23,9 +21,9 @@ import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.internal.Util; import javax.annotation.Nonnull; @@ -77,7 +75,7 @@ public long nextMaxId(long step) { Request request = new Request.Builder() .url(apiUrl) - .patch(RequestBody.create(JSON, "")) + .patch(Util.EMPTY_REQUEST) .build(); try (Response response = client.newCall(request).execute()) { ResponseBody responseBody = response.body(); diff --git a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributorFactory.java b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributorFactory.java index 2c322760d9..d187050e2d 100644 --- a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributorFactory.java +++ b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyIdSegmentDistributorFactory.java @@ -13,8 +13,6 @@ package me.ahoo.cosid.proxy; -import static me.ahoo.cosid.proxy.ProxyMachineIdDistributor.JSON; - import me.ahoo.cosid.segment.IdSegmentDistributor; import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; import me.ahoo.cosid.segment.IdSegmentDistributorFactory; @@ -24,9 +22,9 @@ import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.internal.Util; import javax.annotation.Nonnull; @@ -60,7 +58,7 @@ public IdSegmentDistributor create(IdSegmentDistributorDefinition definition) { Request request = new Request.Builder() .url(apiUrl) - .post(RequestBody.Companion.create("", JSON)) + .post(Util.EMPTY_REQUEST) .build(); try (Response response = client.newCall(request).execute()) { ResponseBody responseBody = response.body(); diff --git a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributor.java b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributor.java index 35b61b24e3..9cf5630e7a 100644 --- a/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributor.java +++ b/cosid-proxy/src/main/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributor.java @@ -13,8 +13,12 @@ package me.ahoo.cosid.proxy; -import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; +import static me.ahoo.cosid.proxy.ErrorResponse.MACHINE_ID_LOST; +import static me.ahoo.cosid.proxy.ErrorResponse.MACHINE_ID_OVERFLOW; +import static me.ahoo.cosid.proxy.ErrorResponse.NOT_FOUND_MACHINE_STATE; + import me.ahoo.cosid.machine.AbstractMachineIdDistributor; +import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; import me.ahoo.cosid.machine.InstanceId; import me.ahoo.cosid.machine.MachineIdLostException; import me.ahoo.cosid.machine.MachineIdOverflowException; @@ -25,12 +29,11 @@ import com.google.common.base.Strings; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; -import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.internal.Util; import java.time.Duration; @@ -42,33 +45,31 @@ */ @Slf4j public class ProxyMachineIdDistributor extends AbstractMachineIdDistributor { - - public static final MediaType JSON - = MediaType.get("application/json; charset=utf-8"); + private final OkHttpClient client; - + private final String proxyHost; - + public ProxyMachineIdDistributor(OkHttpClient client, String proxyHost, MachineStateStorage machineStateStorage, ClockBackwardsSynchronizer clockBackwardsSynchronizer) { super(machineStateStorage, clockBackwardsSynchronizer); this.client = client; this.proxyHost = proxyHost; } - + @SneakyThrows @Override protected MachineState distributeRemote(String namespace, int machineBit, InstanceId instanceId, Duration safeGuardDuration) { String apiUrl = - Strings.lenientFormat("%s/machines/%s?instanceId=%s&stable=%s&machineBit=%s&safeGuardDuration=%s", proxyHost, namespace, instanceId.getInstanceId(), instanceId.isStable(), machineBit, - safeGuardDuration); + Strings.lenientFormat("%s/machines/%s?instanceId=%s&stable=%s&machineBit=%s&safeGuardDuration=%s", proxyHost, namespace, instanceId.getInstanceId(), instanceId.isStable(), machineBit, + safeGuardDuration); if (log.isInfoEnabled()) { log.info("Distribute Remote instanceId:[{}] - machineBit:[{}] @ namespace:[{}] - apiUrl:[{}].", instanceId, machineBit, namespace, apiUrl); } - + Request request = new Request.Builder() - .url(apiUrl) - .post(RequestBody.create(JSON, "")) - .build(); + .url(apiUrl) + .post(Util.EMPTY_REQUEST) + .build(); try (Response response = client.newCall(request).execute()) { ResponseBody responseBody = response.body(); assert responseBody != null; @@ -76,20 +77,18 @@ protected MachineState distributeRemote(String namespace, int machineBit, Instan if (log.isInfoEnabled()) { log.info("Distribute Remote instanceId:[{}] - machineBit:[{}] @ namespace:[{}] - response:[{}].", instanceId, machineBit, namespace, bodyStr); } - + if (response.isSuccessful()) { return Jsons.OBJECT_MAPPER.readValue(bodyStr, MachineStateDto.class); } ErrorResponse errorResponse = Jsons.OBJECT_MAPPER.readValue(bodyStr, ErrorResponse.class); - switch (errorResponse.getCode()) { - case "M-01": - throw new MachineIdOverflowException(machineBit, instanceId); - default: - throw new IllegalStateException(Strings.lenientFormat("Unexpected code:[%s] - message:[%s].", errorResponse.getCode(), errorResponse.getMsg())); + if (errorResponse.getCode().equals(MACHINE_ID_OVERFLOW)) { + throw new MachineIdOverflowException(machineBit, instanceId); } + throw new IllegalStateException(Strings.lenientFormat("Unexpected code:[%s] - message:[%s].", errorResponse.getCode(), errorResponse.getMsg())); } } - + @SneakyThrows @Override protected void revertRemote(String namespace, InstanceId instanceId, MachineState machineState) { @@ -97,11 +96,11 @@ protected void revertRemote(String namespace, InstanceId instanceId, MachineStat if (log.isInfoEnabled()) { log.info("Revert Remote [{}] instanceId:[{}] @ namespace:[{}] - apiUrl:[{}].", machineState, instanceId, namespace, apiUrl); } - + Request request = new Request.Builder() - .url(apiUrl) - .delete() - .build(); + .url(apiUrl) + .delete() + .build(); try (Response response = client.newCall(request).execute()) { if (log.isInfoEnabled()) { ResponseBody responseBody = response.body(); @@ -111,21 +110,21 @@ protected void revertRemote(String namespace, InstanceId instanceId, MachineStat } } } - + @SneakyThrows @Override protected void guardRemote(String namespace, InstanceId instanceId, MachineState machineState, Duration safeGuardDuration) { String apiUrl = - Strings.lenientFormat("%s/machines/%s?instanceId=%s&stable=%s&safeGuardDuration=%s", proxyHost, namespace, instanceId.getInstanceId(), instanceId.isStable(), safeGuardDuration); - + Strings.lenientFormat("%s/machines/%s?instanceId=%s&stable=%s&safeGuardDuration=%s", proxyHost, namespace, instanceId.getInstanceId(), instanceId.isStable(), safeGuardDuration); + if (log.isInfoEnabled()) { log.info("Guard Remote [{}] instanceId:[{}] @ namespace:[{}] - apiUrl:[{}].", machineState, instanceId, namespace, apiUrl); } - + Request request = new Request.Builder() - .url(apiUrl) - .patch(RequestBody.create(JSON, "")) - .build(); + .url(apiUrl) + .patch(Util.EMPTY_REQUEST) + .build(); try (Response response = client.newCall(request).execute()) { ResponseBody responseBody = response.body(); assert responseBody != null; @@ -136,17 +135,17 @@ protected void guardRemote(String namespace, InstanceId instanceId, MachineState if (response.isSuccessful()) { return; } - + ErrorResponse errorResponse = Jsons.OBJECT_MAPPER.readValue(bodyStr, ErrorResponse.class); switch (errorResponse.getCode()) { - case "M-02": + case NOT_FOUND_MACHINE_STATE: throw new NotFoundMachineStateException(namespace, instanceId); - case "M-03": + case MACHINE_ID_LOST: throw new MachineIdLostException(namespace, instanceId, machineState); default: throw new IllegalStateException("Unexpected value: " + errorResponse.getCode()); } } } - + } diff --git a/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/JsonsTest.java b/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/JsonsTest.java index e8f169daab..0e1046b712 100644 --- a/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/JsonsTest.java +++ b/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/JsonsTest.java @@ -25,7 +25,9 @@ void serialize() { ErrorResponse errorResponse = ErrorResponse.badRequest("badRequest"); String jsonStr = Jsons.serialize(errorResponse); ErrorResponse actual = Jsons.deserialize(jsonStr, ErrorResponse.class); - assertThat(actual, equalTo(errorResponse)); + assertThat(actual.getCode(), equalTo(errorResponse.getCode())); + assertThat(actual.getMsg(), equalTo(errorResponse.getMsg())); + assertThat(actual.getErrors(), equalTo(errorResponse.getErrors())); } } diff --git a/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributorTest.java b/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributorTest.java index 86691f3592..16895c6829 100644 --- a/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributorTest.java +++ b/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyMachineIdDistributorTest.java @@ -14,11 +14,16 @@ package me.ahoo.cosid.proxy; import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; +import me.ahoo.cosid.machine.InstanceId; import me.ahoo.cosid.machine.MachineIdDistributor; import me.ahoo.cosid.machine.MachineStateStorage; +import me.ahoo.cosid.machine.NotFoundMachineStateException; +import me.ahoo.cosid.test.Assert; +import me.ahoo.cosid.test.MockIdGenerator; import me.ahoo.cosid.test.machine.distributor.MachineIdDistributorSpec; import okhttp3.OkHttpClient; +import org.junit.jupiter.api.Test; class ProxyMachineIdDistributorTest extends MachineIdDistributorSpec { @@ -29,7 +34,19 @@ protected MachineIdDistributor getDistributor() { @Override public void guardLost() { - //TODO - //super.guardLost(); + //ignore + } + + @Test + public void guardIfNotFoundMachineState() { + MachineIdDistributor distributor = getDistributor(); + String namespace = MockIdGenerator.usePrefix("guardIfNotFoundMachineState").generateAsString(); + InstanceId instanceId = mockInstance(0, false); + MachineStateStorage.IN_MEMORY.set(namespace, 10, instanceId); + + Assert.assertThrows(NotFoundMachineStateException.class, () -> { + distributor.guard(namespace, instanceId, MachineIdDistributor.FOREVER_SAFE_GUARD_DURATION); + }); + } } diff --git a/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyServerLauncher.java b/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyServerLauncher.java index 4f584f14e6..14893bff6c 100644 --- a/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyServerLauncher.java +++ b/cosid-proxy/src/test/java/me/ahoo/cosid/proxy/ProxyServerLauncher.java @@ -34,11 +34,11 @@ public class ProxyServerLauncher { REDIS_CONTAINER.start(); int cosidProxyExposedPort = 8688; - COSID_PROXY_CONTAINER = new GenericContainer(DockerImageName.parse("ahoowang/cosid-proxy:1.18.3")) + COSID_PROXY_CONTAINER = new GenericContainer(DockerImageName.parse("ahoowang/cosid-proxy:2.5.0")) .withNetwork(NETWORK_CONTAINER) .withExposedPorts(cosidProxyExposedPort) .withReuse(true) - .withEnv("SPRING_REDIS_URL", "redis://redis:6379") + .withEnv("SPRING_DATA_REDIS_URL", "redis://redis:6379") .waitingFor(Wait.forHttp("/actuator/health").forStatusCode(200)); COSID_PROXY_CONTAINER.start(); diff --git a/cosid-spring-boot-starter/build.gradle.kts b/cosid-spring-boot-starter/build.gradle.kts index 4bc72fe251..392b05ecff 100644 --- a/cosid-spring-boot-starter/build.gradle.kts +++ b/cosid-spring-boot-starter/build.gradle.kts @@ -36,6 +36,26 @@ java { usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) capability(group.toString(), "mybatis-support", version.toString()) } + registerFeature("dataJdbcSupport") { + usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) + capability(group.toString(), "data-jdbc-support", version.toString()) + } + registerFeature("activitiSupport") { + usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) + capability(group.toString(), "activiti-support", version.toString()) + } + registerFeature("flowableSupport") { + usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) + capability(group.toString(), "flowable-support", version.toString()) + } + registerFeature("cloudSupport") { + usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) + capability(group.toString(), "cloud-support", version.toString()) + } + registerFeature("actuatorSupport") { + usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) + capability(group.toString(), "actuator-support", version.toString()) + } } dependencies { @@ -51,10 +71,16 @@ dependencies { "proxySupportImplementation"(project(":cosid-proxy")) "mongoSupportImplementation"(project(":cosid-mongo")) - + "activitiSupportImplementation"(project(":cosid-activiti")) + "activitiSupportImplementation"(libs.activitiSpringBootStarter) + "flowableSupportImplementation"(project(":cosid-flowable")) + "flowableSupportImplementation"(libs.flowableSpring) + "flowableSupportImplementation"(libs.flowableSpringBootAutoconfigure) "mybatisSupportImplementation"(project(":cosid-mybatis")) + "dataJdbcSupportImplementation"(project(":cosid-spring-data-jdbc")) api("org.springframework.boot:spring-boot-starter") - api("org.springframework.cloud:spring-cloud-commons") + "cloudSupportImplementation"("org.springframework.cloud:spring-cloud-commons") + "actuatorSupportImplementation"("org.springframework.boot:spring-boot-starter-actuator") compileOnly("org.mongodb:mongodb-driver-sync") compileOnly("org.mongodb:mongodb-driver-reactivestreams") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") @@ -70,6 +96,3 @@ dependencies { testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.testcontainers:mongodb") } - - - diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfiguration.java index 6662d36d59..41e608dfa8 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfiguration.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfiguration.java @@ -13,6 +13,7 @@ package me.ahoo.cosid.spring.boot.starter; +import me.ahoo.cosid.accessor.parser.CompositeFieldDefinitionParser; import me.ahoo.cosid.accessor.parser.CosIdAccessorParser; import me.ahoo.cosid.accessor.parser.DefaultAccessorParser; import me.ahoo.cosid.accessor.parser.FieldDefinitionParser; @@ -26,6 +27,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +import java.util.List; /** * CosId Auto Configuration. @@ -36,31 +42,37 @@ @ConditionalOnCosIdEnabled @EnableConfigurationProperties(CosIdProperties.class) public class CosIdAutoConfiguration { - + private final CosIdProperties cosIdProperties; - + public CosIdAutoConfiguration(CosIdProperties cosIdProperties) { this.cosIdProperties = cosIdProperties; } - + @Bean @ConditionalOnMissingBean public IdGeneratorProvider idGeneratorProvider() { return DefaultIdGeneratorProvider.INSTANCE; } - + + @Order(Ordered.HIGHEST_PRECEDENCE) @Bean - @ConditionalOnMissingBean - public FieldDefinitionParser fieldDefinitionParser() { + public FieldDefinitionParser annotationDefinitionParser() { return AnnotationDefinitionParser.INSTANCE; } - + + @Primary + @Bean + public FieldDefinitionParser compositeFieldDefinitionParser(List fieldDefinitionParsers) { + return new CompositeFieldDefinitionParser(fieldDefinitionParsers); + } + @Bean @ConditionalOnMissingBean public CosIdAccessorParser cosIdAccessorParser(FieldDefinitionParser definitionParser) { return new DefaultAccessorParser(definitionParser); } - + @Bean @ConditionalOnMissingBean public CosIdAccessorRegistry cosIdAccessorRegistry(CosIdAccessorParser cosIdAccessorParser) { diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CustomizeIdProperties.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CustomizeIdProperties.java new file mode 100644 index 0000000000..4a4d35ad2d --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/CustomizeIdProperties.java @@ -0,0 +1,19 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter; + +@FunctionalInterface +public interface CustomizeIdProperties

{ + void customize(P idProperties); +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDecorator.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDecorator.java new file mode 100644 index 0000000000..d2ee958a60 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDecorator.java @@ -0,0 +1,124 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter; + +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.converter.DatePrefixIdConverter; +import me.ahoo.cosid.converter.PrefixIdConverter; +import me.ahoo.cosid.converter.Radix36IdConverter; +import me.ahoo.cosid.converter.Radix62IdConverter; +import me.ahoo.cosid.converter.SuffixIdConverter; +import me.ahoo.cosid.converter.ToStringIdConverter; +import me.ahoo.cosid.converter.GroupedPrefixIdConverter; + +import com.google.common.base.Strings; + +import java.lang.reflect.InvocationTargetException; +import java.util.Date; + +public abstract class IdConverterDecorator { + protected final T idGenerator; + protected final IdConverterDefinition converterDefinition; + + protected IdConverterDecorator(T idGenerator, IdConverterDefinition converterDefinition) { + this.idGenerator = idGenerator; + this.converterDefinition = converterDefinition; + } + + public T decorate() { + IdConverter idConverter = ToStringIdConverter.INSTANCE; + switch (converterDefinition.getType()) { + case TO_STRING: + idConverter = newToString(idConverter); + break; + case RADIX: + idConverter = newRadix(); + break; + case RADIX36: + idConverter = newRadix36(); + break; + case SNOWFLAKE_FRIENDLY: + idConverter = newSnowflakeFriendly(); + break; + case CUSTOM: + idConverter = newCustom(); + break; + default: + throw new IllegalStateException("Unexpected value: " + converterDefinition.getType()); + } + + IdConverterDefinition.GroupPrefix groupPrefix = converterDefinition.getGroupPrefix(); + + if (groupPrefix.isEnabled() && groupPrefix.isBeforePrefix()) { + idConverter = new GroupedPrefixIdConverter(groupPrefix.getDelimiter(), idConverter); + } + + IdConverterDefinition.DatePrefix datePrefix = converterDefinition.getDatePrefix(); + if (datePrefix.isEnabled() && datePrefix.isBeforePrefix()) { + idConverter = new DatePrefixIdConverter(datePrefix.getPattern(), datePrefix.getDelimiter(), idConverter); + } + + if (!Strings.isNullOrEmpty(converterDefinition.getPrefix())) { + idConverter = new PrefixIdConverter(converterDefinition.getPrefix(), idConverter); + } + if (groupPrefix.isEnabled() && !groupPrefix.isBeforePrefix()) { + idConverter = new GroupedPrefixIdConverter(groupPrefix.getDelimiter(), idConverter); + } + + if (datePrefix.isEnabled() && !datePrefix.isBeforePrefix()) { + idConverter = new DatePrefixIdConverter(datePrefix.getPattern(), datePrefix.getDelimiter(), idConverter); + } + + if (!Strings.isNullOrEmpty(converterDefinition.getSuffix())) { + idConverter = new SuffixIdConverter(converterDefinition.getSuffix(), idConverter); + } + + return newIdGenerator(idConverter); + } + + protected IdConverter newRadix() { + IdConverterDefinition.Radix radix = converterDefinition.getRadix(); + return Radix62IdConverter.of(radix.isPadStart(), radix.getCharSize()); + } + + protected IdConverter newRadix36() { + IdConverterDefinition.Radix36 radix36 = converterDefinition.getRadix36(); + return Radix36IdConverter.of(radix36.isPadStart(), radix36.getCharSize()); + } + + protected IdConverter newToString(IdConverter defaultIdConverter) { + IdConverterDefinition.ToString toString = converterDefinition.getToString(); + if (toString != null) { + return new ToStringIdConverter(toString.isPadStart(), toString.getCharSize()); + } + return defaultIdConverter; + } + + protected IdConverter newSnowflakeFriendly() { + throw new UnsupportedOperationException("newSnowflakeFriendly"); + } + + protected IdConverter newCustom() { + IdConverterDefinition.Custom custom = converterDefinition.getCustom(); + try { + return custom.getType().getConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException + | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + protected abstract T newIdGenerator(IdConverter idConverter); +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinition.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinition.java index b47bb9d436..39120522c0 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinition.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinition.java @@ -13,6 +13,9 @@ package me.ahoo.cosid.spring.boot.starter; +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.converter.GroupedPrefixIdConverter; +import me.ahoo.cosid.converter.Radix36IdConverter; import me.ahoo.cosid.converter.Radix62IdConverter; /** @@ -21,116 +24,268 @@ * @author ahoo wang */ public class IdConverterDefinition { - + private Type type = Type.RADIX; private String prefix; + private GroupPrefix groupPrefix = new GroupPrefix(); private String suffix; private Radix radix = new Radix(); + private Radix36 radix36 = new Radix36(); private ToString toString; - + private Custom custom; + private DatePrefix datePrefix = new DatePrefix(); + public Type getType() { return type; } - + public void setType(Type type) { this.type = type; } - + public String getPrefix() { return prefix; } - + public void setPrefix(String prefix) { this.prefix = prefix; } - + + public GroupPrefix getGroupPrefix() { + return groupPrefix; + } + + public void setGroupPrefix(GroupPrefix groupPrefix) { + this.groupPrefix = groupPrefix; + } + public String getSuffix() { return suffix; } - + public void setSuffix(String suffix) { this.suffix = suffix; } - + public Radix getRadix() { return radix; } - + public void setRadix(Radix radix) { this.radix = radix; } - + + public Radix36 getRadix36() { + return radix36; + } + + public void setRadix36(Radix36 radix36) { + this.radix36 = radix36; + } + public ToString getToString() { return toString; } - + public void setToString(ToString toString) { this.toString = toString; } - + + public Custom getCustom() { + return custom; + } + + public void setCustom(Custom custom) { + this.custom = custom; + } + + public DatePrefix getDatePrefix() { + return datePrefix; + } + + public IdConverterDefinition setDatePrefix(DatePrefix datePrefix) { + this.datePrefix = datePrefix; + return this; + } + /** * Radix62IdConverter Config. */ public static class Radix implements PadStartIdConverter { - + private boolean padStart = true; private int charSize = Radix62IdConverter.MAX_CHAR_SIZE; - + + @Override + public boolean isPadStart() { + return padStart; + } + + public void setPadStart(boolean padStart) { + this.padStart = padStart; + } + + @Override + public int getCharSize() { + return charSize; + } + + public void setCharSize(int charSize) { + this.charSize = charSize; + } + } + + public static class Radix36 implements PadStartIdConverter { + + private boolean padStart = true; + private int charSize = Radix36IdConverter.MAX_CHAR_SIZE; + @Override public boolean isPadStart() { return padStart; } - + public void setPadStart(boolean padStart) { this.padStart = padStart; } - + @Override public int getCharSize() { return charSize; } - + public void setCharSize(int charSize) { this.charSize = charSize; } } - + public static class ToString implements PadStartIdConverter { - + private boolean padStart = false; private int charSize = Radix62IdConverter.MAX_CHAR_SIZE; - + @Override public boolean isPadStart() { return padStart; } - + public void setPadStart(boolean padStart) { this.padStart = padStart; } - + @Override public int getCharSize() { return charSize; } - + public void setCharSize(int charSize) { this.charSize = charSize; } } - + + public static class GroupPrefix { + private boolean enabled = false; + private String delimiter = GroupedPrefixIdConverter.DEFAULT_DELIMITER; + private boolean beforePrefix = true; + + public boolean isEnabled() { + return enabled; + } + + public GroupPrefix setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public String getDelimiter() { + return delimiter; + } + + public GroupPrefix setDelimiter(String delimiter) { + this.delimiter = delimiter; + return this; + } + + public boolean isBeforePrefix() { + return beforePrefix; + } + + public GroupPrefix setBeforePrefix(boolean beforePrefix) { + this.beforePrefix = beforePrefix; + return this; + } + } + + public static class DatePrefix { + private boolean enabled = false; + private String delimiter = GroupedPrefixIdConverter.DEFAULT_DELIMITER; + private String pattern = "yyMMdd"; + private boolean beforePrefix = true; + + public boolean isEnabled() { + return enabled; + } + + public DatePrefix setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public String getDelimiter() { + return delimiter; + } + + public DatePrefix setDelimiter(String delimiter) { + this.delimiter = delimiter; + return this; + } + + public String getPattern() { + return pattern; + } + + public DatePrefix setPattern(String pattern) { + this.pattern = pattern; + return this; + } + + public boolean isBeforePrefix() { + return beforePrefix; + } + + public DatePrefix setBeforePrefix(boolean beforePrefix) { + this.beforePrefix = beforePrefix; + return this; + } + } + + public static class Custom { + private Class type; + + public Class getType() { + return type; + } + + public Custom setType(Class type) { + this.type = type; + return this; + } + } + /** * IdConverter Type. */ public enum Type { TO_STRING, SNOWFLAKE_FRIENDLY, - RADIX + RADIX, + RADIX36, + CUSTOM } - + interface PadStartIdConverter { boolean isPadStart(); - + int getCharSize(); } } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/activiti/ActivitiIdGeneratorAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/activiti/ActivitiIdGeneratorAutoConfiguration.java new file mode 100644 index 0000000000..55a2c588af --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/activiti/ActivitiIdGeneratorAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.activiti; + +import me.ahoo.cosid.activiti.ActivitiIdGenerator; +import me.ahoo.cosid.flowable.FlowableIdGenerator; +import me.ahoo.cosid.spring.boot.starter.ConditionalOnCosIdEnabled; + +import org.activiti.spring.SpringProcessEngineConfiguration; +import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; + +/** + * Activiti IdGenerator Auto Configuration. + * + * @author ahoo wang + */ +@AutoConfiguration +@ConditionalOnCosIdEnabled +@ConditionalOnClass(FlowableIdGenerator.class) +public class ActivitiIdGeneratorAutoConfiguration { + + @Bean + public ProcessEngineConfigurationConfigurer engineConfigurationConfigurer() { + return new ActivitiIdGeneratorAutoConfiguration.CosIdProcessEngineConfigurationConfigurer(); + } + + static class CosIdProcessEngineConfigurationConfigurer implements ProcessEngineConfigurationConfigurer { + + @Override + public void configure(SpringProcessEngineConfiguration engineConfiguration) { + engineConfiguration.setIdGenerator(new ActivitiIdGenerator()); + } + } +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpoint.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpoint.java new file mode 100644 index 0000000000..8af59eb720 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpoint.java @@ -0,0 +1,59 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import me.ahoo.cosid.CosId; +import me.ahoo.cosid.IdGenerator; +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; + +import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; + +import java.util.HashMap; +import java.util.Map; + +@Endpoint(id = CosId.COSID) +public class CosIdEndpoint { + private final IdGeneratorProvider idGeneratorProvider; + + public CosIdEndpoint(IdGeneratorProvider idGeneratorProvider) { + this.idGeneratorProvider = idGeneratorProvider; + } + + @ReadOperation + public Map stat() { + Map statMap = new HashMap<>(); + for (Map.Entry entry : idGeneratorProvider.entries()) { + IdGeneratorStat stat = entry.getValue().stat(); + statMap.put(entry.getKey(), stat); + } + return statMap; + } + + @ReadOperation + public IdGeneratorStat getStat(@Selector String name) { + IdGenerator idGenerator = idGeneratorProvider.getRequired(name); + return idGenerator.stat(); + } + + @DeleteOperation + public IdGeneratorStat remove(@Selector String name) { + IdGenerator idGenerator = idGeneratorProvider.remove(name); + return idGenerator.stat(); + } + +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointAutoConfiguration.java new file mode 100644 index 0000000000..5204240b9e --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointAutoConfiguration.java @@ -0,0 +1,45 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import me.ahoo.cosid.provider.IdGeneratorProvider; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +@ConditionalOnClass(Endpoint.class) +public class CosIdEndpointAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public CosIdEndpoint cosIdEndpoint(IdGeneratorProvider idGeneratorProvider) { + return new CosIdEndpoint(idGeneratorProvider); + } + + @Bean + @ConditionalOnMissingBean + public CosIdGeneratorEndpoint cosIdGeneratorEndpoint(IdGeneratorProvider idGeneratorProvider) { + return new CosIdGeneratorEndpoint(idGeneratorProvider); + } + + @Bean + @ConditionalOnMissingBean + public CosIdStringGeneratorEndpoint cosIdStringGeneratorEndpoint(IdGeneratorProvider idGeneratorProvider) { + return new CosIdStringGeneratorEndpoint(idGeneratorProvider); + } +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdGeneratorEndpoint.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdGeneratorEndpoint.java new file mode 100644 index 0000000000..77d09bed27 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdGeneratorEndpoint.java @@ -0,0 +1,41 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import me.ahoo.cosid.CosId; +import me.ahoo.cosid.provider.IdGeneratorProvider; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; + +@Endpoint(id = CosId.COSID + "Generator") +public class CosIdGeneratorEndpoint { + private final IdGeneratorProvider idGeneratorProvider; + + public CosIdGeneratorEndpoint(IdGeneratorProvider idGeneratorProvider) { + this.idGeneratorProvider = idGeneratorProvider; + } + + @ReadOperation + public long shareGenerate() { + return idGeneratorProvider.getShare().generate(); + } + + @ReadOperation + public long generate(@Selector String name) { + return idGeneratorProvider.getRequired(name).generate(); + } + +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdStringGeneratorEndpoint.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdStringGeneratorEndpoint.java new file mode 100644 index 0000000000..e4262a68f8 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdStringGeneratorEndpoint.java @@ -0,0 +1,41 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import me.ahoo.cosid.CosId; +import me.ahoo.cosid.provider.IdGeneratorProvider; + +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.annotation.Selector; + +@Endpoint(id = CosId.COSID + "StringGenerator") +public class CosIdStringGeneratorEndpoint { + private final IdGeneratorProvider idGeneratorProvider; + + public CosIdStringGeneratorEndpoint(IdGeneratorProvider idGeneratorProvider) { + this.idGeneratorProvider = idGeneratorProvider; + } + + @ReadOperation + public String shareGenerateAsString() { + return idGeneratorProvider.getShare().generateAsString(); + } + + @ReadOperation + public String generateAsString(@Selector String name) { + return idGeneratorProvider.getRequired(name).generateAsString(); + } + +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfiguration.java index 376d8b296e..358c3cb401 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfiguration.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfiguration.java @@ -34,6 +34,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; +import javax.annotation.Nonnull; + /** * CosId Auto Configuration. * @@ -47,13 +49,13 @@ public class CosIdGeneratorAutoConfiguration { private final CosIdProperties cosIdProperties; private final MachineProperties machineProperties; private final CosIdGeneratorProperties cosIdGeneratorProperties; - + public CosIdGeneratorAutoConfiguration(CosIdProperties cosIdProperties, MachineProperties machineProperties, CosIdGeneratorProperties cosIdGeneratorProperties) { this.cosIdProperties = cosIdProperties; this.machineProperties = machineProperties; this.cosIdGeneratorProperties = cosIdGeneratorProperties; } - + @Bean @Primary @ConditionalOnMissingBean @@ -61,30 +63,28 @@ public CosIdGenerator cosIdGenerator(MachineIdDistributor machineIdDistributor, ClockBackwardsSynchronizer clockBackwardsSynchronizer) { String namespace = Namespaces.firstNotBlank(cosIdGeneratorProperties.getNamespace(), cosIdProperties.getNamespace()); int machineId = - machineIdDistributor.distribute(namespace, cosIdGeneratorProperties.getMachineBit(), instanceId, machineProperties.getSafeGuardDuration()).getMachineId(); + machineIdDistributor.distribute(namespace, cosIdGeneratorProperties.getMachineBit(), instanceId, machineProperties.getSafeGuardDuration()).getMachineId(); machineIdGuarder.register(namespace, instanceId); - CosIdGenerator cosIdGenerator; + CosIdGenerator cosIdGenerator = createCosIdGenerator(machineId); + + CosIdGenerator clockSyncCosIdGenerator = new ClockSyncCosIdGenerator(cosIdGenerator, clockBackwardsSynchronizer); + idGeneratorProvider.set(CosId.COSID, clockSyncCosIdGenerator); + return clockSyncCosIdGenerator; + } + + @Nonnull + private CosIdGenerator createCosIdGenerator(int machineId) { switch (cosIdGeneratorProperties.getType()) { - case RADIX62: { - cosIdGenerator = - new Radix62CosIdGenerator(cosIdGeneratorProperties.getTimestampBit(), + case RADIX62: + return new Radix62CosIdGenerator(cosIdGeneratorProperties.getTimestampBit(), cosIdGeneratorProperties.getMachineBit(), cosIdGeneratorProperties.getSequenceBit(), machineId, cosIdGeneratorProperties.getSequenceResetThreshold()); - break; - } - case RADIX36: { - cosIdGenerator = - new Radix36CosIdGenerator(cosIdGeneratorProperties.getTimestampBit(), + case RADIX36: + return new Radix36CosIdGenerator(cosIdGeneratorProperties.getTimestampBit(), cosIdGeneratorProperties.getMachineBit(), cosIdGeneratorProperties.getSequenceBit(), machineId, cosIdGeneratorProperties.getSequenceResetThreshold()); - break; - } default: throw new IllegalStateException("Unexpected value: " + cosIdGeneratorProperties.getType()); } - - CosIdGenerator clockSyncCosIdGenerator = new ClockSyncCosIdGenerator(cosIdGenerator, clockBackwardsSynchronizer); - idGeneratorProvider.set(CosId.COSID, clockSyncCosIdGenerator); - return clockSyncCosIdGenerator; } } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/flowable/FlowableIdGeneratorAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/flowable/FlowableIdGeneratorAutoConfiguration.java new file mode 100644 index 0000000000..e2c123d445 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/flowable/FlowableIdGeneratorAutoConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.flowable; + +import me.ahoo.cosid.flowable.FlowableIdGenerator; +import me.ahoo.cosid.spring.boot.starter.ConditionalOnCosIdEnabled; + +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; + +/** + * Flowable IdGenerator Auto Configuration. + * + * @author ahoo wang + */ +@AutoConfiguration +@ConditionalOnCosIdEnabled +@ConditionalOnClass(FlowableIdGenerator.class) +public class FlowableIdGeneratorAutoConfiguration { + + @Bean + public EngineConfigurationConfigurer engineConfigurationConfigurer() { + return new CosIdEngineConfigurationConfigurer(); + } + + static class CosIdEngineConfigurationConfigurer implements EngineConfigurationConfigurer { + + @Override + public void configure(SpringProcessEngineConfiguration engineConfiguration) { + engineConfiguration.setIdGenerator(new FlowableIdGenerator()); + } + } +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/jdbc/CosIdJdbcAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/jdbc/CosIdJdbcAutoConfiguration.java new file mode 100644 index 0000000000..546e72c3fa --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/jdbc/CosIdJdbcAutoConfiguration.java @@ -0,0 +1,47 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.jdbc; + +import me.ahoo.cosid.accessor.parser.FieldDefinitionParser; +import me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry; +import me.ahoo.cosid.spring.boot.starter.ConditionalOnCosIdEnabled; +import me.ahoo.cosid.spring.data.jdbc.CosIdBeforeConvertCallback; +import me.ahoo.cosid.spring.data.jdbc.IdAnnotationDefinitionParser; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; + +/** + * CosId Mybatis AutoConfiguration. + * + * @author ahoo wang + */ +@AutoConfiguration +@ConditionalOnCosIdEnabled +@ConditionalOnClass(CosIdBeforeConvertCallback.class) +public class CosIdJdbcAutoConfiguration { + @Bean + public FieldDefinitionParser idAnnotationDefinitionParser() { + return IdAnnotationDefinitionParser.INSTANCE; + } + + @Bean + @ConditionalOnMissingBean + public CosIdBeforeConvertCallback cosIdBeforeConvertCallback(CosIdAccessorRegistry accessorRegistry) { + return new CosIdBeforeConvertCallback(accessorRegistry); + } + +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdHostNameAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdHostNameAutoConfiguration.java new file mode 100644 index 0000000000..b78bcfcd52 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdHostNameAutoConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.machine; + +import me.ahoo.cosid.machine.HostAddressSupplier; +import me.ahoo.cosid.machine.LocalHostAddressSupplier; +import me.ahoo.cosid.spring.boot.starter.ConditionalOnCosIdEnabled; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.cloud.commons.util.InetUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@AutoConfiguration(afterName = "org.springframework.cloud.commons.util.UtilAutoConfiguration") +@ConditionalOnCosIdEnabled +public class CosIdHostNameAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public HostAddressSupplier hostNameSupplier() { + return LocalHostAddressSupplier.INSTANCE; + } + + @Configuration + @ConditionalOnClass(InetUtils.class) + static class CloudUtilInstanceIdConfiguration { + @Bean + @ConditionalOnBean(InetUtils.class) + public HostAddressSupplier hostNameSupplier(InetUtils inetUtils) { + return new CloudUtilHostAddressSupplier(inetUtils); + } + } + + static class CloudUtilHostAddressSupplier implements HostAddressSupplier { + private final InetUtils inetUtils; + + CloudUtilHostAddressSupplier(InetUtils inetUtils) { + this.inetUtils = inetUtils; + } + + @Override + public String getHostAddress() { + return inetUtils.findFirstNonLoopbackHostInfo().getIpAddress(); + } + } +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfiguration.java index 5c2208044c..0f984190a8 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfiguration.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfiguration.java @@ -16,6 +16,7 @@ import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; import me.ahoo.cosid.machine.DefaultClockBackwardsSynchronizer; import me.ahoo.cosid.machine.DefaultMachineIdGuarder; +import me.ahoo.cosid.machine.HostAddressSupplier; import me.ahoo.cosid.machine.InstanceId; import me.ahoo.cosid.machine.LocalMachineStateStorage; import me.ahoo.cosid.machine.MachineId; @@ -34,7 +35,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.commons.util.InetUtils; import org.springframework.context.annotation.Bean; import java.util.Objects; @@ -54,7 +54,7 @@ public CosIdMachineAutoConfiguration(CosIdProperties cosIdProperties, MachinePro @Bean @ConditionalOnMissingBean - public InstanceId instanceId(InetUtils inetUtils) { + public InstanceId instanceId(HostAddressSupplier hostAddressSupplier) { boolean stable = Boolean.TRUE.equals(machineProperties.getStable()); @@ -62,14 +62,12 @@ public InstanceId instanceId(InetUtils inetUtils) { return InstanceId.of(machineProperties.getInstanceId(), stable); } - InetUtils.HostInfo hostInfo = inetUtils.findFirstNonLoopbackHostInfo(); - int port = ProcessId.CURRENT.getProcessId(); if (Objects.nonNull(machineProperties.getPort()) && machineProperties.getPort() > 0) { port = machineProperties.getPort(); } - return InstanceId.of(hostInfo.getIpAddress(), port, stable); + return InstanceId.of(hostAddressSupplier.getHostAddress(), port, stable); } @Bean @@ -136,4 +134,5 @@ public MachineIdGuarder machineIdGuarder(MachineIdDistributor machineIdDistribut public CosIdLifecycleMachineIdGuarder cosIdLifecycleMachineIdGuarder(InstanceId instanceId, MachineIdGuarder machineIdGuarder) { return new CosIdLifecycleMachineIdGuarder(cosIdProperties, instanceId, machineIdGuarder); } + } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfiguration.java index 7f9da2039b..10fcd66ee9 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfiguration.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfiguration.java @@ -33,6 +33,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.core.annotation.Order; /** * CosId Redis Segment AutoConfiguration. @@ -54,6 +55,7 @@ public CosIdMongoSegmentAutoConfiguration(SegmentIdProperties segmentIdPropertie this.segmentIdProperties = segmentIdProperties; } + @Order(0) @Configuration @ConditionalOnClass(MongoClient.class) class Sync { diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfiguration.java index ac01bb99bb..70615613ef 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfiguration.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.lang.Nullable; /** * CosId Segment AutoConfiguration. @@ -61,15 +62,18 @@ public CosIdLifecyclePrefetchWorkerExecutorService lifecycleSegmentChainId(Prefe @Bean public SegmentIdBeanRegistrar segmentIdBeanRegistrar(IdSegmentDistributorFactory distributorFactory, - IdGeneratorProvider idGeneratorProvider, - PrefetchWorkerExecutorService prefetchWorkerExecutorService, - ConfigurableApplicationContext applicationContext) { + IdGeneratorProvider idGeneratorProvider, + PrefetchWorkerExecutorService prefetchWorkerExecutorService, + ConfigurableApplicationContext applicationContext, + @Nullable + CustomizeSegmentIdProperties customizeSegmentIdProperties + ) { return new SegmentIdBeanRegistrar(cosIdProperties, segmentIdProperties, distributorFactory, idGeneratorProvider, prefetchWorkerExecutorService, - applicationContext); + applicationContext, customizeSegmentIdProperties); } } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CustomizeSegmentIdProperties.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CustomizeSegmentIdProperties.java new file mode 100644 index 0000000000..0c16000878 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/CustomizeSegmentIdProperties.java @@ -0,0 +1,19 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.segment; + +import me.ahoo.cosid.spring.boot.starter.CustomizeIdProperties; + +public interface CustomizeSegmentIdProperties extends CustomizeIdProperties { +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdBeanRegistrar.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdBeanRegistrar.java index 8e917d5f53..123b6c8286 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdBeanRegistrar.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdBeanRegistrar.java @@ -13,11 +13,6 @@ package me.ahoo.cosid.spring.boot.starter.segment; -import me.ahoo.cosid.IdConverter; -import me.ahoo.cosid.converter.PrefixIdConverter; -import me.ahoo.cosid.converter.Radix62IdConverter; -import me.ahoo.cosid.converter.SuffixIdConverter; -import me.ahoo.cosid.converter.ToStringIdConverter; import me.ahoo.cosid.provider.IdGeneratorProvider; import me.ahoo.cosid.segment.DefaultSegmentId; import me.ahoo.cosid.segment.IdSegmentDistributor; @@ -25,17 +20,21 @@ import me.ahoo.cosid.segment.IdSegmentDistributorFactory; import me.ahoo.cosid.segment.SegmentChainId; import me.ahoo.cosid.segment.SegmentId; -import me.ahoo.cosid.segment.StringSegmentId; import me.ahoo.cosid.segment.concurrent.PrefetchWorkerExecutorService; +import me.ahoo.cosid.segment.grouped.GroupBySupplier; +import me.ahoo.cosid.segment.grouped.GroupedIdSegmentDistributorFactory; +import me.ahoo.cosid.segment.grouped.date.YearGroupBySupplier; +import me.ahoo.cosid.segment.grouped.date.YearMonthDayGroupBySupplier; +import me.ahoo.cosid.segment.grouped.date.YearMonthGroupBySupplier; import me.ahoo.cosid.spring.boot.starter.CosIdProperties; import me.ahoo.cosid.spring.boot.starter.IdConverterDefinition; import me.ahoo.cosid.spring.boot.starter.Namespaces; import com.google.common.base.MoreObjects; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.lang.Nullable; @Slf4j public class SegmentIdBeanRegistrar implements InitializingBean { @@ -45,60 +44,90 @@ public class SegmentIdBeanRegistrar implements InitializingBean { private final IdGeneratorProvider idGeneratorProvider; private final PrefetchWorkerExecutorService prefetchWorkerExecutorService; private final ConfigurableApplicationContext applicationContext; - + @Nullable + private final CustomizeSegmentIdProperties customizeSegmentIdProperties; + public SegmentIdBeanRegistrar(CosIdProperties cosIdProperties, SegmentIdProperties segmentIdProperties, IdSegmentDistributorFactory distributorFactory, IdGeneratorProvider idGeneratorProvider, PrefetchWorkerExecutorService prefetchWorkerExecutorService, - ConfigurableApplicationContext applicationContext) { + ConfigurableApplicationContext applicationContext, + @Nullable CustomizeSegmentIdProperties customizeSegmentIdProperties) { this.cosIdProperties = cosIdProperties; this.segmentIdProperties = segmentIdProperties; this.distributorFactory = distributorFactory; this.idGeneratorProvider = idGeneratorProvider; this.prefetchWorkerExecutorService = prefetchWorkerExecutorService; this.applicationContext = applicationContext; + this.customizeSegmentIdProperties = customizeSegmentIdProperties; } - + @Override public void afterPropertiesSet() { register(); } - + public void register() { + if (customizeSegmentIdProperties != null) { + customizeSegmentIdProperties.customize(segmentIdProperties); + } SegmentIdProperties.ShardIdDefinition shareIdDefinition = segmentIdProperties.getShare(); if (shareIdDefinition.isEnabled()) { registerIdDefinition(IdGeneratorProvider.SHARE, shareIdDefinition); } segmentIdProperties.getProvider().forEach(this::registerIdDefinition); } - + private void registerIdDefinition(String name, SegmentIdProperties.IdDefinition idDefinition) { IdSegmentDistributorDefinition distributorDefinition = asDistributorDefinition(name, idDefinition); - IdSegmentDistributor idSegmentDistributor = distributorFactory.create(distributorDefinition); + IdSegmentDistributor idSegmentDistributor; + SegmentIdProperties.IdDefinition.Group group = idDefinition.getGroup(); + GroupBySupplier groupBySupplier; + switch (group.getBy()) { + case YEAR: + groupBySupplier = new YearGroupBySupplier(group.getPattern()); + break; + case YEAR_MONTH: + groupBySupplier = new YearMonthGroupBySupplier(group.getPattern()); + break; + case YEAR_MONTH_DAY: + groupBySupplier = new YearMonthDayGroupBySupplier(group.getPattern()); + break; + default: + groupBySupplier = null; + break; + } + + if (groupBySupplier != null) { + idSegmentDistributor = new GroupedIdSegmentDistributorFactory(groupBySupplier, distributorFactory).create(distributorDefinition); + } else { + idSegmentDistributor = distributorFactory.create(distributorDefinition); + } + SegmentId idGenerator = createSegment(segmentIdProperties, idDefinition, idSegmentDistributor, prefetchWorkerExecutorService); registerSegmentId(name, idGenerator); } - + private void registerSegmentId(String name, SegmentId segmentId) { if (!idGeneratorProvider.get(name).isPresent()) { idGeneratorProvider.set(name, segmentId); } - + String beanName = name + "SegmentId"; applicationContext.getBeanFactory().registerSingleton(beanName, segmentId); } - + private IdSegmentDistributorDefinition asDistributorDefinition(String name, SegmentIdProperties.IdDefinition idDefinition) { String namespace = Namespaces.firstNotBlank(idDefinition.getNamespace(), cosIdProperties.getNamespace()); return new IdSegmentDistributorDefinition(namespace, name, idDefinition.getOffset(), idDefinition.getStep()); } - + private static SegmentId createSegment(SegmentIdProperties segmentIdProperties, SegmentIdProperties.IdDefinition idDefinition, IdSegmentDistributor idSegmentDistributor, PrefetchWorkerExecutorService prefetchWorkerExecutorService) { long ttl = MoreObjects.firstNonNull(idDefinition.getTtl(), segmentIdProperties.getTtl()); SegmentIdProperties.Mode mode = MoreObjects.firstNonNull(idDefinition.getMode(), segmentIdProperties.getMode()); - + SegmentId segmentId; if (SegmentIdProperties.Mode.SEGMENT.equals(mode)) { segmentId = new DefaultSegmentId(ttl, idSegmentDistributor); @@ -106,36 +135,9 @@ private static SegmentId createSegment(SegmentIdProperties segmentIdProperties, SegmentIdProperties.Chain chain = MoreObjects.firstNonNull(idDefinition.getChain(), segmentIdProperties.getChain()); segmentId = new SegmentChainId(ttl, chain.getSafeDistance(), idSegmentDistributor, prefetchWorkerExecutorService); } - + IdConverterDefinition converterDefinition = idDefinition.getConverter(); - - IdConverter idConverter = ToStringIdConverter.INSTANCE; - switch (converterDefinition.getType()) { - case TO_STRING: { - IdConverterDefinition.ToString toString = converterDefinition.getToString(); - if (toString != null) { - idConverter = new ToStringIdConverter(toString.isPadStart(), toString.getCharSize()); - } - break; - } - case RADIX: { - IdConverterDefinition.Radix radix = converterDefinition.getRadix(); - idConverter = Radix62IdConverter.of(radix.isPadStart(), radix.getCharSize()); - break; - } - default: - throw new IllegalStateException("Unexpected value: " + converterDefinition.getType()); - } - - if (!Strings.isNullOrEmpty(converterDefinition.getPrefix())) { - idConverter = new PrefixIdConverter(converterDefinition.getPrefix(), idConverter); - } - if (!Strings.isNullOrEmpty(converterDefinition.getSuffix())) { - idConverter = new SuffixIdConverter(converterDefinition.getSuffix(), idConverter); - } - return new StringSegmentId(segmentId, idConverter); - + return new SegmentIdConverterDecorator(segmentId, converterDefinition).decorate(); } - - + } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdConverterDecorator.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdConverterDecorator.java new file mode 100644 index 0000000000..1b7cc9f630 --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdConverterDecorator.java @@ -0,0 +1,31 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.segment; + +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.segment.SegmentId; +import me.ahoo.cosid.segment.StringSegmentId; +import me.ahoo.cosid.spring.boot.starter.IdConverterDecorator; +import me.ahoo.cosid.spring.boot.starter.IdConverterDefinition; + +public class SegmentIdConverterDecorator extends IdConverterDecorator { + protected SegmentIdConverterDecorator(SegmentId idGenerator, IdConverterDefinition converterDefinition) { + super(idGenerator, converterDefinition); + } + + @Override + protected SegmentId newIdGenerator(IdConverter idConverter) { + return new StringSegmentId(idGenerator, idConverter); + } +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdProperties.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdProperties.java index 54d72710bd..1e643d5ebe 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdProperties.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdProperties.java @@ -38,9 +38,9 @@ */ @ConfigurationProperties(prefix = SegmentIdProperties.PREFIX) public class SegmentIdProperties { - + public static final String PREFIX = CosId.COSID_PREFIX + "segment"; - + private boolean enabled = false; private Mode mode = Mode.CHAIN; /** @@ -52,261 +52,261 @@ public class SegmentIdProperties { private Chain chain; private ShardIdDefinition share; private Map provider; - + public SegmentIdProperties() { share = new ShardIdDefinition(); distributor = new Distributor(); chain = new Chain(); provider = new HashMap<>(); } - + public boolean isEnabled() { return enabled; } - + public void setEnabled(boolean enabled) { this.enabled = enabled; } - + public Mode getMode() { return mode; } - + public void setMode(Mode mode) { this.mode = mode; } - + public long getTtl() { return ttl; } - + public void setTtl(long ttl) { this.ttl = ttl; } - + public Distributor getDistributor() { return distributor; } - + public void setDistributor(Distributor distributor) { this.distributor = distributor; } - + public Chain getChain() { return chain; } - + public void setChain(Chain chain) { this.chain = chain; } - + public ShardIdDefinition getShare() { return share; } - + public void setShare(ShardIdDefinition share) { this.share = share; } - + @Nonnull public Map getProvider() { return provider; } - + public void setProvider(Map provider) { this.provider = provider; } - + public enum Mode { SEGMENT, CHAIN } - + public static class Chain { private int safeDistance = SegmentChainId.DEFAULT_SAFE_DISTANCE; private PrefetchWorker prefetchWorker; - + public Chain() { prefetchWorker = new PrefetchWorker(); } - + public int getSafeDistance() { return safeDistance; } - + public void setSafeDistance(int safeDistance) { this.safeDistance = safeDistance; } - + public PrefetchWorker getPrefetchWorker() { return prefetchWorker; } - + public void setPrefetchWorker(PrefetchWorker prefetchWorker) { this.prefetchWorker = prefetchWorker; } - + public static class PrefetchWorker { - + private Duration prefetchPeriod = PrefetchWorkerExecutorService.DEFAULT_PREFETCH_PERIOD; private int corePoolSize = Runtime.getRuntime().availableProcessors(); private boolean shutdownHook = true; - + public Duration getPrefetchPeriod() { return prefetchPeriod; } - + public void setPrefetchPeriod(Duration prefetchPeriod) { this.prefetchPeriod = prefetchPeriod; } - + public int getCorePoolSize() { return corePoolSize; } - + public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; } - + public boolean isShutdownHook() { return shutdownHook; } - + public void setShutdownHook(boolean shutdownHook) { this.shutdownHook = shutdownHook; } } } - + public static class Distributor { public static final String TYPE = PREFIX + ".distributor.type"; private Type type = Type.REDIS; private Redis redis; private Jdbc jdbc; private Mongo mongo; - + public Distributor() { this.redis = new Redis(); this.jdbc = new Jdbc(); this.mongo = new Mongo(); } - + public Type getType() { return type; } - + public void setType(Type type) { this.type = type; } - + public Redis getRedis() { return redis; } - + public void setRedis(Redis redis) { this.redis = redis; } - + public Jdbc getJdbc() { return jdbc; } - + public void setJdbc(Jdbc jdbc) { this.jdbc = jdbc; } - + public Mongo getMongo() { return mongo; } - + public void setMongo(Mongo mongo) { this.mongo = mongo; } - + public static class Redis { - + private Duration timeout = Duration.ofSeconds(1); - + public Duration getTimeout() { return timeout; } - + public void setTimeout(Duration timeout) { this.timeout = timeout; } } - + public static class Jdbc { - + private String incrementMaxIdSql = JdbcIdSegmentDistributor.INCREMENT_MAX_ID_SQL; private String fetchMaxIdSql = JdbcIdSegmentDistributor.FETCH_MAX_ID_SQL; private boolean enableAutoInitCosidTable = false; private String initCosidTableSql = JdbcIdSegmentInitializer.INIT_COSID_TABLE_SQL; private boolean enableAutoInitIdSegment = true; private String initIdSegmentSql = JdbcIdSegmentInitializer.INIT_ID_SEGMENT_SQL; - + public String getIncrementMaxIdSql() { return incrementMaxIdSql; } - + public void setIncrementMaxIdSql(String incrementMaxIdSql) { this.incrementMaxIdSql = incrementMaxIdSql; } - + public String getFetchMaxIdSql() { return fetchMaxIdSql; } - + public void setFetchMaxIdSql(String fetchMaxIdSql) { this.fetchMaxIdSql = fetchMaxIdSql; } - + public boolean isEnableAutoInitCosidTable() { return enableAutoInitCosidTable; } - + public void setEnableAutoInitCosidTable(boolean enableAutoInitCosidTable) { this.enableAutoInitCosidTable = enableAutoInitCosidTable; } - + public String getInitCosidTableSql() { return initCosidTableSql; } - + public void setInitCosidTableSql(String initCosidTableSql) { this.initCosidTableSql = initCosidTableSql; } - + public boolean isEnableAutoInitIdSegment() { return enableAutoInitIdSegment; } - + public void setEnableAutoInitIdSegment(boolean enableAutoInitIdSegment) { this.enableAutoInitIdSegment = enableAutoInitIdSegment; } - + public String getInitIdSegmentSql() { return initIdSegmentSql; } - + public void setInitIdSegmentSql(String initIdSegmentSql) { this.initIdSegmentSql = initIdSegmentSql; } - + } - + public static class Mongo { private String database = "cosid_db"; - + public String getDatabase() { return database; } - + public void setDatabase(String database) { this.database = database; } } - + public enum Type { REDIS, JDBC, @@ -315,7 +315,7 @@ public enum Type { PROXY } } - + public static class IdDefinition { private String namespace; private Mode mode; @@ -329,72 +329,109 @@ public static class IdDefinition { private Chain chain; @NestedConfigurationProperty private IdConverterDefinition converter = new IdConverterDefinition(); - + private Group group = new Group(); + public String getNamespace() { return namespace; } - + public void setNamespace(String namespace) { this.namespace = namespace; } - + public Mode getMode() { return mode; } - + public void setMode(Mode mode) { this.mode = mode; } - + public long getOffset() { return offset; } - + public void setOffset(long offset) { this.offset = offset; } - + public long getStep() { return step; } - + public void setStep(long step) { this.step = step; } - + public Long getTtl() { return ttl; } - + public void setTtl(Long ttl) { this.ttl = ttl; } - + public Chain getChain() { return chain; } - + public void setChain(Chain chain) { this.chain = chain; } - + public IdConverterDefinition getConverter() { return converter; } - + public void setConverter(IdConverterDefinition converter) { this.converter = converter; } - + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public static class Group { + private GroupBy by = GroupBy.NEVER; + private String pattern; + + public GroupBy getBy() { + return by; + } + + public Group setBy(GroupBy by) { + this.by = by; + return this; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + } + + public enum GroupBy { + YEAR, + YEAR_MONTH, + YEAR_MONTH_DAY, + NEVER + } } - + public static class ShardIdDefinition extends IdDefinition { private boolean enabled = true; - + public boolean isEnabled() { return enabled; } - + public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfiguration.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfiguration.java index 4d0712c2b7..e61fc73d1a 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfiguration.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; +import org.springframework.lang.Nullable; /** * CosId Snowflake AutoConfiguration. @@ -53,7 +54,10 @@ public SnowflakeIdBeanRegistrar snowflakeIdBeanRegistrar(final InstanceId instan IdGeneratorProvider idGeneratorProvider, MachineIdDistributor machineIdDistributor, ClockBackwardsSynchronizer clockBackwardsSynchronizer, - ConfigurableApplicationContext applicationContext) { + ConfigurableApplicationContext applicationContext, + @Nullable + CustomizeSnowflakeIdProperties customizeSnowflakeIdProperties + ) { return new SnowflakeIdBeanRegistrar(cosIdProperties, machineProperties, snowflakeIdProperties, @@ -61,7 +65,8 @@ public SnowflakeIdBeanRegistrar snowflakeIdBeanRegistrar(final InstanceId instan idGeneratorProvider, machineIdDistributor, clockBackwardsSynchronizer, - applicationContext); + applicationContext, + customizeSnowflakeIdProperties); } } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CustomizeSnowflakeIdProperties.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CustomizeSnowflakeIdProperties.java new file mode 100644 index 0000000000..0f7f399add --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/CustomizeSnowflakeIdProperties.java @@ -0,0 +1,19 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.snowflake; + +import me.ahoo.cosid.spring.boot.starter.CustomizeIdProperties; + +public interface CustomizeSnowflakeIdProperties extends CustomizeIdProperties { +} diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdBeanRegistrar.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdBeanRegistrar.java index 4e15108612..0e5baa9a0d 100644 --- a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdBeanRegistrar.java +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdBeanRegistrar.java @@ -13,32 +13,23 @@ package me.ahoo.cosid.spring.boot.starter.snowflake; -import me.ahoo.cosid.IdConverter; -import me.ahoo.cosid.converter.PrefixIdConverter; -import me.ahoo.cosid.converter.Radix62IdConverter; -import me.ahoo.cosid.converter.SnowflakeFriendlyIdConverter; -import me.ahoo.cosid.converter.SuffixIdConverter; -import me.ahoo.cosid.converter.ToStringIdConverter; import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; import me.ahoo.cosid.machine.InstanceId; import me.ahoo.cosid.machine.MachineIdDistributor; import me.ahoo.cosid.provider.IdGeneratorProvider; import me.ahoo.cosid.snowflake.ClockSyncSnowflakeId; -import me.ahoo.cosid.snowflake.DefaultSnowflakeFriendlyId; import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; import me.ahoo.cosid.snowflake.SecondSnowflakeId; import me.ahoo.cosid.snowflake.SnowflakeId; -import me.ahoo.cosid.snowflake.SnowflakeIdStateParser; -import me.ahoo.cosid.snowflake.StringSnowflakeId; import me.ahoo.cosid.spring.boot.starter.CosIdProperties; import me.ahoo.cosid.spring.boot.starter.IdConverterDefinition; import me.ahoo.cosid.spring.boot.starter.Namespaces; import me.ahoo.cosid.spring.boot.starter.machine.MachineProperties; import com.google.common.base.MoreObjects; -import com.google.common.base.Strings; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.lang.Nullable; import java.time.ZoneId; @@ -51,7 +42,9 @@ public class SnowflakeIdBeanRegistrar implements InitializingBean { private final MachineIdDistributor machineIdDistributor; private final ClockBackwardsSynchronizer clockBackwardsSynchronizer; private final ConfigurableApplicationContext applicationContext; - + @Nullable + private final CustomizeSnowflakeIdProperties customizeSnowflakeIdProperties; + public SnowflakeIdBeanRegistrar(CosIdProperties cosIdProperties, MachineProperties machineProperties, SnowflakeIdProperties snowflakeIdProperties, @@ -59,7 +52,8 @@ public SnowflakeIdBeanRegistrar(CosIdProperties cosIdProperties, IdGeneratorProvider idGeneratorProvider, MachineIdDistributor machineIdDistributor, ClockBackwardsSynchronizer clockBackwardsSynchronizer, - ConfigurableApplicationContext applicationContext) { + ConfigurableApplicationContext applicationContext, + @Nullable CustomizeSnowflakeIdProperties customizeSnowflakeIdProperties) { this.cosIdProperties = cosIdProperties; this.machineProperties = machineProperties; this.snowflakeIdProperties = snowflakeIdProperties; @@ -68,93 +62,66 @@ public SnowflakeIdBeanRegistrar(CosIdProperties cosIdProperties, this.machineIdDistributor = machineIdDistributor; this.clockBackwardsSynchronizer = clockBackwardsSynchronizer; this.applicationContext = applicationContext; + this.customizeSnowflakeIdProperties = customizeSnowflakeIdProperties; } - + @Override - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { register(); } - + public void register() { + if (customizeSnowflakeIdProperties != null) { + customizeSnowflakeIdProperties.customize(snowflakeIdProperties); + } SnowflakeIdProperties.ShardIdDefinition shareIdDefinition = snowflakeIdProperties.getShare(); if (shareIdDefinition.isEnabled()) { registerIdDefinition(IdGeneratorProvider.SHARE, shareIdDefinition); } snowflakeIdProperties.getProvider().forEach(this::registerIdDefinition); } - + private void registerIdDefinition(String name, SnowflakeIdProperties.IdDefinition idDefinition) { SnowflakeId idGenerator = createIdGen(idDefinition, clockBackwardsSynchronizer); registerSnowflakeId(name, idGenerator); } - + private void registerSnowflakeId(String name, SnowflakeId snowflakeId) { if (!idGeneratorProvider.get(name).isPresent()) { idGeneratorProvider.set(name, snowflakeId); } - + String beanName = name + "SnowflakeId"; applicationContext.getBeanFactory().registerSingleton(beanName, snowflakeId); } - + private SnowflakeId createIdGen(SnowflakeIdProperties.IdDefinition idDefinition, ClockBackwardsSynchronizer clockBackwardsSynchronizer) { long epoch = getEpoch(idDefinition); int machineBit = MoreObjects.firstNonNull(idDefinition.getMachineBit(), machineProperties.getMachineBit()); String namespace = Namespaces.firstNotBlank(idDefinition.getNamespace(), cosIdProperties.getNamespace()); int machineId = machineIdDistributor.distribute(namespace, machineBit, instanceId, machineProperties.getSafeGuardDuration()).getMachineId(); - + SnowflakeId snowflakeId; if (SnowflakeIdProperties.IdDefinition.TimestampUnit.SECOND.equals(idDefinition.getTimestampUnit())) { snowflakeId = new SecondSnowflakeId(epoch, idDefinition.getTimestampBit(), machineBit, idDefinition.getSequenceBit(), machineId, idDefinition.getSequenceResetThreshold()); } else { snowflakeId = - new MillisecondSnowflakeId(epoch, idDefinition.getTimestampBit(), machineBit, idDefinition.getSequenceBit(), machineId, idDefinition.getSequenceResetThreshold()); + new MillisecondSnowflakeId(epoch, idDefinition.getTimestampBit(), machineBit, idDefinition.getSequenceBit(), machineId, idDefinition.getSequenceResetThreshold()); } if (idDefinition.isClockSync()) { snowflakeId = new ClockSyncSnowflakeId(snowflakeId, clockBackwardsSynchronizer); } IdConverterDefinition converterDefinition = idDefinition.getConverter(); final ZoneId zoneId = ZoneId.of(snowflakeIdProperties.getZoneId()); - IdConverter idConverter = ToStringIdConverter.INSTANCE; - switch (converterDefinition.getType()) { - case TO_STRING: { - IdConverterDefinition.ToString toString = converterDefinition.getToString(); - if (toString != null) { - idConverter = new ToStringIdConverter(toString.isPadStart(), toString.getCharSize()); - } - break; - } - case SNOWFLAKE_FRIENDLY: { - idConverter = new SnowflakeFriendlyIdConverter(SnowflakeIdStateParser.of(snowflakeId, zoneId)); - break; - } - case RADIX: { - IdConverterDefinition.Radix radix = converterDefinition.getRadix(); - idConverter = Radix62IdConverter.of(radix.isPadStart(), radix.getCharSize()); - break; - } - default: - throw new IllegalStateException("Unexpected value: " + converterDefinition.getType()); - } - if (!Strings.isNullOrEmpty(converterDefinition.getPrefix())) { - idConverter = new PrefixIdConverter(converterDefinition.getPrefix(), idConverter); - } - if (!Strings.isNullOrEmpty(converterDefinition.getSuffix())) { - idConverter = new SuffixIdConverter(converterDefinition.getSuffix(), idConverter); - } - if (idDefinition.isFriendly()) { - SnowflakeIdStateParser snowflakeIdStateParser = SnowflakeIdStateParser.of(snowflakeId, zoneId); - return new DefaultSnowflakeFriendlyId(snowflakeId, idConverter, snowflakeIdStateParser); - } - return new StringSnowflakeId(snowflakeId, idConverter); + return new SnowflakeIdConverterDecorator(snowflakeId, converterDefinition, zoneId, idDefinition.isFriendly()).decorate(); } - + private long getEpoch(SnowflakeIdProperties.IdDefinition idDefinition) { if (idDefinition.getEpoch() > 0) { return idDefinition.getEpoch(); } return snowflakeIdProperties.getEpoch(); } - + } diff --git a/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdConverterDecorator.java b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdConverterDecorator.java new file mode 100644 index 0000000000..18ba0b8bbc --- /dev/null +++ b/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdConverterDecorator.java @@ -0,0 +1,50 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.snowflake; + +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.converter.SnowflakeFriendlyIdConverter; +import me.ahoo.cosid.snowflake.DefaultSnowflakeFriendlyId; +import me.ahoo.cosid.snowflake.SnowflakeId; +import me.ahoo.cosid.snowflake.SnowflakeIdStateParser; +import me.ahoo.cosid.snowflake.StringSnowflakeId; +import me.ahoo.cosid.spring.boot.starter.IdConverterDecorator; +import me.ahoo.cosid.spring.boot.starter.IdConverterDefinition; + +import java.time.ZoneId; + +public class SnowflakeIdConverterDecorator extends IdConverterDecorator { + private final ZoneId zoneId; + private final boolean isFriendly; + + protected SnowflakeIdConverterDecorator(SnowflakeId idGenerator, IdConverterDefinition converterDefinition, ZoneId zoneId, boolean isFriendly) { + super(idGenerator, converterDefinition); + this.zoneId = zoneId; + this.isFriendly = isFriendly; + } + + @Override + protected IdConverter newSnowflakeFriendly() { + return new SnowflakeFriendlyIdConverter(SnowflakeIdStateParser.of(idGenerator, zoneId)); + } + + @Override + protected SnowflakeId newIdGenerator(IdConverter idConverter) { + if (isFriendly) { + SnowflakeIdStateParser snowflakeIdStateParser = SnowflakeIdStateParser.of(idGenerator, zoneId); + return new DefaultSnowflakeFriendlyId(idGenerator, idConverter, snowflakeIdStateParser); + } + return new StringSnowflakeId(idGenerator, idConverter); + } +} diff --git a/cosid-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/cosid-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 3c218342fc..a04f4e6100 100644 --- a/cosid-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/cosid-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,5 +1,6 @@ me.ahoo.cosid.spring.boot.starter.CosIdAutoConfiguration me.ahoo.cosid.spring.boot.starter.zookeeper.CosIdZookeeperAutoConfiguration +me.ahoo.cosid.spring.boot.starter.machine.CosIdHostNameAutoConfiguration me.ahoo.cosid.spring.boot.starter.machine.CosIdMachineAutoConfiguration me.ahoo.cosid.spring.boot.starter.cosid.CosIdGeneratorAutoConfiguration me.ahoo.cosid.spring.boot.starter.machine.CosIdJdbcMachineIdDistributorAutoConfiguration @@ -15,3 +16,7 @@ me.ahoo.cosid.spring.boot.starter.segment.CosIdZookeeperSegmentAutoConfiguration me.ahoo.cosid.spring.boot.starter.segment.CosIdSegmentAutoConfiguration me.ahoo.cosid.spring.boot.starter.segment.CosIdProxySegmentAutoConfiguration me.ahoo.cosid.spring.boot.starter.mybatis.CosIdMybatisAutoConfiguration +me.ahoo.cosid.spring.boot.starter.actuate.CosIdEndpointAutoConfiguration +me.ahoo.cosid.spring.boot.starter.jdbc.CosIdJdbcAutoConfiguration +me.ahoo.cosid.spring.boot.starter.flowable.FlowableIdGeneratorAutoConfiguration +me.ahoo.cosid.spring.boot.starter.activiti.ActivitiIdGeneratorAutoConfiguration \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfigurationTest.java index a3155144b7..77809b7adc 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/CosIdAutoConfigurationTest.java @@ -2,8 +2,10 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import me.ahoo.cosid.accessor.parser.CompositeFieldDefinitionParser; import me.ahoo.cosid.accessor.parser.CosIdAccessorParser; import me.ahoo.cosid.accessor.parser.FieldDefinitionParser; +import me.ahoo.cosid.annotation.AnnotationDefinitionParser; import me.ahoo.cosid.provider.IdGeneratorProvider; import org.junit.jupiter.api.Test; @@ -21,13 +23,12 @@ public class CosIdAutoConfigurationTest { void contextLoads() { this.contextRunner .withUserConfiguration(CosIdAutoConfiguration.class) - .run(context -> { - assertThat(context) - .hasSingleBean(CosIdAutoConfiguration.class) - .hasSingleBean(CosIdProperties.class) - .hasSingleBean(IdGeneratorProvider.class) - .hasSingleBean(FieldDefinitionParser.class) - .hasSingleBean(CosIdAccessorParser.class); - }); + .run(context -> assertThat(context) + .hasSingleBean(CosIdAutoConfiguration.class) + .hasSingleBean(CosIdProperties.class) + .hasSingleBean(IdGeneratorProvider.class) + .hasSingleBean(AnnotationDefinitionParser.class) + .hasSingleBean(CompositeFieldDefinitionParser.class) + .hasSingleBean(CosIdAccessorParser.class)); } } diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinitionTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinitionTest.java index 834ceb150a..7b434664a2 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinitionTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/IdConverterDefinitionTest.java @@ -1,5 +1,6 @@ package me.ahoo.cosid.spring.boot.starter; +import me.ahoo.cosid.converter.Radix36IdConverter; import me.ahoo.cosid.converter.Radix62IdConverter; import org.junit.jupiter.api.Assertions; @@ -74,6 +75,26 @@ void setRadix() { Assertions.assertEquals(10, definition.getRadix().getCharSize()); } + @Test + void getRadix36() { + IdConverterDefinition definition = new IdConverterDefinition(); + Assertions.assertNotNull(definition.getRadix36()); + Assertions.assertTrue(definition.getRadix36().isPadStart()); + Assertions.assertEquals(Radix36IdConverter.MAX_CHAR_SIZE, definition.getRadix36().getCharSize()); + } + + @Test + void setRadix36() { + IdConverterDefinition definition = new IdConverterDefinition(); + IdConverterDefinition.Radix36 radix = new IdConverterDefinition.Radix36(); + radix.setPadStart(false); + radix.setCharSize(10); + definition.setRadix36(radix); + Assertions.assertNotNull(definition.getRadix36()); + Assertions.assertFalse(definition.getRadix36().isPadStart()); + Assertions.assertEquals(10, definition.getRadix36().getCharSize()); + } + @Test void getToString() { IdConverterDefinition definition = new IdConverterDefinition(); diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/activiti/ActivitiIdGeneratorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/activiti/ActivitiIdGeneratorAutoConfigurationTest.java new file mode 100644 index 0000000000..b4c55b0b58 --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/activiti/ActivitiIdGeneratorAutoConfigurationTest.java @@ -0,0 +1,38 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.activiti; + +import me.ahoo.cosid.spring.boot.starter.machine.ConditionalOnCosIdMachineEnabled; + +import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer; +import org.assertj.core.api.AssertionsForInterfaceTypes; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class ActivitiIdGeneratorAutoConfigurationTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void contextLoads() { + this.contextRunner + .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") + .withUserConfiguration(ActivitiIdGeneratorAutoConfiguration.class) + .run(context -> { + AssertionsForInterfaceTypes.assertThat(context) + .hasSingleBean(ActivitiIdGeneratorAutoConfiguration.class) + .hasSingleBean(ProcessEngineConfigurationConfigurer.class) + ; + }); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointAutoConfigurationTest.java new file mode 100644 index 0000000000..5a683a0a4e --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointAutoConfigurationTest.java @@ -0,0 +1,37 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.provider.IdGeneratorProvider; + +import org.assertj.core.api.AssertionsForInterfaceTypes; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class CosIdEndpointAutoConfigurationTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void contextLoads() { + this.contextRunner + .withBean(IdGeneratorProvider.class, () -> DefaultIdGeneratorProvider.INSTANCE) + .withUserConfiguration(CosIdEndpointAutoConfiguration.class) + .run(context -> AssertionsForInterfaceTypes.assertThat(context) + .hasSingleBean(CosIdEndpointAutoConfiguration.class) + .hasSingleBean(CosIdEndpoint.class) + .hasSingleBean(CosIdGeneratorEndpoint.class) + .hasSingleBean(CosIdStringGeneratorEndpoint.class)); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointTest.java new file mode 100644 index 0000000000..8f930af94e --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdEndpointTest.java @@ -0,0 +1,57 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.stat.generator.IdGeneratorStat; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +class CosIdEndpointTest { + private final IdGeneratorProvider idGeneratorProvider = new DefaultIdGeneratorProvider(); + private final CosIdEndpoint cosIdEndpoint = new CosIdEndpoint(idGeneratorProvider); + + public CosIdEndpointTest() { + idGeneratorProvider.setShare(MockIdGenerator.INSTANCE); + } + + @Test + void stat() { + Map stat = cosIdEndpoint.stat(); + assertThat(stat, notNullValue()); + assertThat(stat.size(), equalTo(1)); + } + + @Test + void getStat() { + IdGeneratorStat stat = cosIdEndpoint.getStat(IdGeneratorProvider.SHARE); + assertThat(stat, notNullValue()); + } + + @Test + void remove() { + DefaultIdGeneratorProvider idGeneratorProvider = new DefaultIdGeneratorProvider(); + CosIdEndpoint cosIdEndpoint = new CosIdEndpoint(idGeneratorProvider); + idGeneratorProvider.setShare(MockIdGenerator.INSTANCE); + cosIdEndpoint.remove(IdGeneratorProvider.SHARE); + assertThat(idGeneratorProvider.getShare(), nullValue()); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdGeneratorEndpointTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdGeneratorEndpointTest.java new file mode 100644 index 0000000000..c7da669297 --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdGeneratorEndpointTest.java @@ -0,0 +1,44 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; + +class CosIdGeneratorEndpointTest { + private final IdGeneratorProvider idGeneratorProvider = new DefaultIdGeneratorProvider(); + private final CosIdGeneratorEndpoint cosIdGeneratorEndpoint = new CosIdGeneratorEndpoint(idGeneratorProvider); + + public CosIdGeneratorEndpointTest() { + idGeneratorProvider.setShare(MockIdGenerator.INSTANCE); + } + + @Test + void shareGenerate() { + long id = cosIdGeneratorEndpoint.shareGenerate(); + assertThat(id, greaterThan(0L)); + } + + @Test + void generate() { + long id = cosIdGeneratorEndpoint.generate(IdGeneratorProvider.SHARE); + assertThat(id, greaterThan(0L)); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdStringGeneratorEndpointTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdStringGeneratorEndpointTest.java new file mode 100644 index 0000000000..06e80d8984 --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/actuate/CosIdStringGeneratorEndpointTest.java @@ -0,0 +1,44 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.actuate; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.provider.IdGeneratorProvider; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; + +class CosIdStringGeneratorEndpointTest { + private final IdGeneratorProvider idGeneratorProvider = new DefaultIdGeneratorProvider(); + private final CosIdStringGeneratorEndpoint cosIdGeneratorEndpoint = new CosIdStringGeneratorEndpoint(idGeneratorProvider); + + public CosIdStringGeneratorEndpointTest() { + idGeneratorProvider.setShare(MockIdGenerator.INSTANCE); + } + + @Test + void shareGenerate() { + String id = cosIdGeneratorEndpoint.shareGenerateAsString(); + assertThat(id, notNullValue()); + } + + @Test + void generate() { + String id = cosIdGeneratorEndpoint.generateAsString(IdGeneratorProvider.SHARE); + assertThat(id, notNullValue()); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfigurationTest.java index e592bc6322..1ea65a2d45 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/cosid/CosIdGeneratorAutoConfigurationTest.java @@ -27,6 +27,7 @@ import me.ahoo.cosid.snowflake.SnowflakeId; import me.ahoo.cosid.spring.boot.starter.CosIdAutoConfiguration; import me.ahoo.cosid.spring.boot.starter.machine.ConditionalOnCosIdMachineEnabled; +import me.ahoo.cosid.spring.boot.starter.machine.CosIdHostNameAutoConfiguration; import me.ahoo.cosid.spring.boot.starter.machine.CosIdLifecycleMachineIdDistributor; import me.ahoo.cosid.spring.boot.starter.machine.CosIdMachineAutoConfiguration; import me.ahoo.cosid.spring.boot.starter.machine.MachineProperties; @@ -49,6 +50,7 @@ void contextLoads() { .withPropertyValues(MachineProperties.PREFIX + ".distributor.manual.machineId=1") .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, CosIdMachineAutoConfiguration.class, CosIdGeneratorAutoConfiguration.class) .run(context -> { @@ -74,6 +76,7 @@ void contextLoadsWithType() { .withPropertyValues(CosIdGeneratorProperties.PREFIX + ".type=RADIX36") .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, CosIdMachineAutoConfiguration.class, CosIdGeneratorAutoConfiguration.class) .run(context -> { diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/flowable/FlowableIdGeneratorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/flowable/FlowableIdGeneratorAutoConfigurationTest.java new file mode 100644 index 0000000000..00608e0364 --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/flowable/FlowableIdGeneratorAutoConfigurationTest.java @@ -0,0 +1,38 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.flowable; + +import me.ahoo.cosid.spring.boot.starter.machine.ConditionalOnCosIdMachineEnabled; + +import org.assertj.core.api.AssertionsForInterfaceTypes; +import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class FlowableIdGeneratorAutoConfigurationTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void contextLoads() { + this.contextRunner + .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") + .withUserConfiguration(FlowableIdGeneratorAutoConfiguration.class) + .run(context -> { + AssertionsForInterfaceTypes.assertThat(context) + .hasSingleBean(FlowableIdGeneratorAutoConfiguration.class) + .hasSingleBean(EngineConfigurationConfigurer.class) + ; + }); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/jdbc/CosIdJdbcAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/jdbc/CosIdJdbcAutoConfigurationTest.java new file mode 100644 index 0000000000..5a2d8969bc --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/jdbc/CosIdJdbcAutoConfigurationTest.java @@ -0,0 +1,35 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.jdbc; + +import me.ahoo.cosid.spring.boot.starter.CosIdAutoConfiguration; +import me.ahoo.cosid.spring.data.jdbc.CosIdBeforeConvertCallback; +import me.ahoo.cosid.spring.data.jdbc.IdAnnotationDefinitionParser; + +import org.assertj.core.api.AssertionsForInterfaceTypes; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +class CosIdJdbcAutoConfigurationTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void contextLoads() { + this.contextRunner + .withUserConfiguration(CosIdAutoConfiguration.class, CosIdJdbcAutoConfiguration.class) + .run(context -> AssertionsForInterfaceTypes.assertThat(context) + .hasSingleBean(IdAnnotationDefinitionParser.class) + .hasSingleBean(CosIdBeforeConvertCallback.class)); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdHostNameAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdHostNameAutoConfigurationTest.java new file mode 100644 index 0000000000..8fe7016305 --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdHostNameAutoConfigurationTest.java @@ -0,0 +1,55 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.machine; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.machine.HostAddressSupplier; +import me.ahoo.cosid.machine.LocalHostAddressSupplier; + +import org.assertj.core.api.AssertionsForInterfaceTypes; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.commons.util.UtilAutoConfiguration; + +class CosIdHostNameAutoConfigurationTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + void contextLoads() { + this.contextRunner + .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") + .withUserConfiguration(CosIdHostNameAutoConfiguration.class) + .run(context -> { + AssertionsForInterfaceTypes.assertThat(context) + .hasSingleBean(LocalHostAddressSupplier.class) + ; + assertThat(context.getBean(LocalHostAddressSupplier.class).getHostAddress(), notNullValue()); + }); + } + + @Test + void contextLoadsIfCloud() { + this.contextRunner + .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") + .withUserConfiguration(UtilAutoConfiguration.class, CosIdHostNameAutoConfiguration.class) + .run(context -> { + AssertionsForInterfaceTypes.assertThat(context) + .hasSingleBean(CosIdHostNameAutoConfiguration.CloudUtilHostAddressSupplier.class) + ; + assertThat(context.getBean(HostAddressSupplier.class).getHostAddress(), notNullValue()); + }); + } +} \ No newline at end of file diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdJdbcMachineIdDistributorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdJdbcMachineIdDistributorAutoConfigurationTest.java index e99c500bee..42f5336480 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdJdbcMachineIdDistributorAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdJdbcMachineIdDistributorAutoConfigurationTest.java @@ -35,7 +35,7 @@ class CosIdJdbcMachineIdDistributorAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - + @Test void contextLoads() { this.contextRunner @@ -44,7 +44,11 @@ void contextLoads() { .withPropertyValues("spring.datasource.url=jdbc:mysql://localhost:3306/cosid_db") .withPropertyValues("spring.datasource.username=root") .withPropertyValues("spring.datasource.password=root") - .withUserConfiguration(UtilAutoConfiguration.class, DataSourceAutoConfiguration.class, CosIdAutoConfiguration.class, CosIdMachineAutoConfiguration.class) + .withUserConfiguration(UtilAutoConfiguration.class, + DataSourceAutoConfiguration.class, + CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, + CosIdMachineAutoConfiguration.class) .withUserConfiguration(CosIdJdbcMachineIdDistributorAutoConfiguration.class) .run(context -> { assertThat(context) diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfigurationTest.java index 45398e4979..203e2f1cae 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMachineAutoConfigurationTest.java @@ -42,7 +42,10 @@ void contextLoadsWhenMachineIdDistributorIsStatefulSet() { this.contextRunner .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") .withPropertyValues(MachineProperties.Distributor.TYPE + "=stateful_set") - .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, CosIdMachineAutoConfiguration.class) + .withUserConfiguration(UtilAutoConfiguration.class, + CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, + CosIdMachineAutoConfiguration.class) .run(context -> { assertThat(context) .hasSingleBean(CosIdMachineAutoConfiguration.class) diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMongoMachineIdDistributorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMongoMachineIdDistributorAutoConfigurationTest.java index 4dbc8b48aa..c0d82baa7a 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMongoMachineIdDistributorAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdMongoMachineIdDistributorAutoConfigurationTest.java @@ -19,8 +19,8 @@ import me.ahoo.cosid.mongo.reactive.MongoReactiveMachineCollection; import me.ahoo.cosid.mongo.reactive.MongoReactiveMachineInitializer; import me.ahoo.cosid.spring.boot.starter.CosIdAutoConfiguration; -import me.ahoo.cosid.spring.boot.starter.mongo.MongoLauncher; import me.ahoo.cosid.test.MockIdGenerator; +import me.ahoo.cosid.test.container.MongoLauncher; import org.assertj.core.api.AssertionsForInterfaceTypes; import org.junit.jupiter.api.Test; @@ -43,6 +43,7 @@ void contextLoads() { MongoAutoConfiguration.class, MongoReactiveAutoConfiguration.class, CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, CosIdMachineAutoConfiguration.class, CosIdMongoMachineIdDistributorAutoConfiguration.class) .run(context -> { diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdSpringRedisMachineIdDistributorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdSpringRedisMachineIdDistributorAutoConfigurationTest.java index ef8551df69..c3e3df360e 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdSpringRedisMachineIdDistributorAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdSpringRedisMachineIdDistributorAutoConfigurationTest.java @@ -40,7 +40,10 @@ void contextLoads() { this.contextRunner .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") .withPropertyValues(MachineProperties.Distributor.TYPE + "=redis") - .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, CosIdMachineAutoConfiguration.class) + .withUserConfiguration(UtilAutoConfiguration.class, + CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, + CosIdMachineAutoConfiguration.class) .withUserConfiguration(RedisAutoConfiguration.class, CosIdSpringRedisMachineIdDistributorAutoConfiguration.class) .run(context -> { assertThat(context) diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdZookeeperMachineIdDistributorAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdZookeeperMachineIdDistributorAutoConfigurationTest.java index fcba2562ba..a5fdb68368 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdZookeeperMachineIdDistributorAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/machine/CosIdZookeeperMachineIdDistributorAutoConfigurationTest.java @@ -47,7 +47,11 @@ void contextLoads() { .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") .withPropertyValues(MachineProperties.Distributor.TYPE + "=zookeeper") .withPropertyValues(CosIdZookeeperProperties.PREFIX + ".connect-string=" + testingServer.getConnectString()) - .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, CosIdZookeeperAutoConfiguration.class, CosIdMachineAutoConfiguration.class) + .withUserConfiguration(UtilAutoConfiguration.class, + CosIdAutoConfiguration.class, + CosIdZookeeperAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, + CosIdMachineAutoConfiguration.class) .withUserConfiguration(CosIdZookeeperMachineIdDistributorAutoConfiguration.class) .run(context -> { assertThat(context) diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/mongo/MongoLauncher.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/mongo/MongoLauncher.java deleted file mode 100644 index efc63162bb..0000000000 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/mongo/MongoLauncher.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package me.ahoo.cosid.spring.boot.starter.mongo; - - -import org.testcontainers.containers.MongoDBContainer; -import org.testcontainers.shaded.com.google.common.base.Strings; -import org.testcontainers.utility.DockerImageName; - -public class MongoLauncher { - private static final String DEV_CONNECTION_STRING = "mongodb://root:root@localhost"; - private static final MongoDBContainer MONGO_CONTAINER = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10")) - .withNetworkAliases("mongo") - .withReuse(true); - - public static String getConnectionString() { - if (Strings.isNullOrEmpty(System.getenv("CI"))) { - return DEV_CONNECTION_STRING; - } - MONGO_CONTAINER.start(); - return MONGO_CONTAINER.getConnectionString(); - } -} diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfigurationTest.java index 572f2a983b..681b1410f3 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdMongoSegmentAutoConfigurationTest.java @@ -17,7 +17,7 @@ import me.ahoo.cosid.mongo.MongoIdSegmentInitializer; import me.ahoo.cosid.mongo.reactive.MongoReactiveIdSegmentDistributorFactory; import me.ahoo.cosid.mongo.reactive.MongoReactiveIdSegmentInitializer; -import me.ahoo.cosid.spring.boot.starter.mongo.MongoLauncher; +import me.ahoo.cosid.test.container.MongoLauncher; import org.assertj.core.api.AssertionsForInterfaceTypes; import org.junit.jupiter.api.Test; diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfigurationTest.java index 41fccc807c..4071264f8f 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/CosIdSegmentAutoConfigurationTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; -import me.ahoo.cosid.segment.SegmentId; import me.ahoo.cosid.segment.concurrent.PrefetchWorkerExecutorService; import me.ahoo.cosid.spring.boot.starter.CosIdAutoConfiguration; @@ -17,7 +16,7 @@ */ class CosIdSegmentAutoConfigurationTest { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - + @Test void contextLoads() { this.contextRunner @@ -25,17 +24,19 @@ void contextLoads() { .withPropertyValues("spring.datasource.url=jdbc:mysql://localhost:3306/cosid_db") .withPropertyValues("spring.datasource.username=root") .withPropertyValues("spring.datasource.password=root") + .withBean(CustomizeSegmentIdProperties.class, () -> idProperties -> idProperties.getProvider().put("test", new SegmentIdProperties.IdDefinition())) .withUserConfiguration(CosIdAutoConfiguration.class, DataSourceAutoConfiguration.class, CosIdJdbcSegmentAutoConfiguration.class, CosIdSegmentAutoConfiguration.class) .run(context -> { assertThat(context) .hasSingleBean(CosIdSegmentAutoConfiguration.class) .hasSingleBean(PrefetchWorkerExecutorService.class) .hasSingleBean(CosIdLifecyclePrefetchWorkerExecutorService.class) - .hasSingleBean(SegmentId.class) + .hasBean("__share__SegmentId") + .hasBean("testSegmentId") ; }); } - + @Test void contextLoadsWithConfig() { this.contextRunner @@ -44,7 +45,10 @@ void contextLoadsWithConfig() { .withPropertyValues("spring.datasource.username=root") .withPropertyValues("spring.datasource.password=root") .withPropertyValues(SegmentIdProperties.PREFIX + ".share.enabled=false") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.group.by=year") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.group.pattern=yyyy") .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.type=to_string") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.group-prefix.enabled=true") .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.to_string.pad-start=true") .withUserConfiguration(CosIdAutoConfiguration.class, DataSourceAutoConfiguration.class, CosIdJdbcSegmentAutoConfiguration.class, CosIdSegmentAutoConfiguration.class) .run(context -> { @@ -57,4 +61,54 @@ void contextLoadsWithConfig() { ; }); } + + @Test + void contextLoadsWithConfigGroupYearMonth() { + this.contextRunner + .withPropertyValues(ConditionalOnCosIdSegmentEnabled.ENABLED_KEY + "=true") + .withPropertyValues("spring.datasource.url=jdbc:mysql://localhost:3306/cosid_db") + .withPropertyValues("spring.datasource.username=root") + .withPropertyValues("spring.datasource.password=root") + .withPropertyValues(SegmentIdProperties.PREFIX + ".share.enabled=false") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.group.by=year_month") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.group.pattern=yyyyMM") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.type=to_string") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.group-prefix.enabled=true") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.to_string.pad-start=true") + .withUserConfiguration(CosIdAutoConfiguration.class, DataSourceAutoConfiguration.class, CosIdJdbcSegmentAutoConfiguration.class, CosIdSegmentAutoConfiguration.class) + .run(context -> { + assertThat(context) + .hasSingleBean(CosIdSegmentAutoConfiguration.class) + .hasSingleBean(PrefetchWorkerExecutorService.class) + .hasSingleBean(CosIdLifecyclePrefetchWorkerExecutorService.class) + .doesNotHaveBean("__share__SegmentId") + .hasBean("testSegmentId") + ; + }); + } + + @Test + void contextLoadsWithConfigGroupYearMonthDay() { + this.contextRunner + .withPropertyValues(ConditionalOnCosIdSegmentEnabled.ENABLED_KEY + "=true") + .withPropertyValues("spring.datasource.url=jdbc:mysql://localhost:3306/cosid_db") + .withPropertyValues("spring.datasource.username=root") + .withPropertyValues("spring.datasource.password=root") + .withPropertyValues(SegmentIdProperties.PREFIX + ".share.enabled=false") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.group.by=year_month_day") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.group.pattern=yyyyMMdd") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.type=to_string") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.group-prefix.enabled=true") + .withPropertyValues(SegmentIdProperties.PREFIX + ".provider.test.converter.to_string.pad-start=true") + .withUserConfiguration(CosIdAutoConfiguration.class, DataSourceAutoConfiguration.class, CosIdJdbcSegmentAutoConfiguration.class, CosIdSegmentAutoConfiguration.class) + .run(context -> { + assertThat(context) + .hasSingleBean(CosIdSegmentAutoConfiguration.class) + .hasSingleBean(PrefetchWorkerExecutorService.class) + .hasSingleBean(CosIdLifecyclePrefetchWorkerExecutorService.class) + .doesNotHaveBean("__share__SegmentId") + .hasBean("testSegmentId") + ; + }); + } } diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdConverterDecoratorTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdConverterDecoratorTest.java new file mode 100644 index 0000000000..7dfc6aacaf --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/segment/SegmentIdConverterDecoratorTest.java @@ -0,0 +1,115 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.segment; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.IdConverter; +import me.ahoo.cosid.converter.DatePrefixIdConverter; +import me.ahoo.cosid.converter.SuffixIdConverter; +import me.ahoo.cosid.converter.GroupedPrefixIdConverter; +import me.ahoo.cosid.segment.DefaultSegmentId; +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.SegmentId; +import me.ahoo.cosid.spring.boot.starter.IdConverterDefinition; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.annotation.Nonnull; + +public class SegmentIdConverterDecoratorTest { + @Test + void newSnowflakeFriendly() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setType(IdConverterDefinition.Type.SNOWFLAKE_FRIENDLY); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate() + ); + } + + @Test + void newCustom() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setType(IdConverterDefinition.Type.CUSTOM); + idConverterDefinition.setCustom(new IdConverterDefinition.Custom().setType(SegmentIdConverterDecoratorTest.CustomIdConverter.class)); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + SegmentId newIdGen = new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(SegmentIdConverterDecoratorTest.CustomIdConverter.class)); + } + + + @Test + void withPrefixAndSuffix() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setPrefix("prefix-"); + idConverterDefinition.setSuffix("suffix-"); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + SegmentId newIdGen = new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(SuffixIdConverter.class)); + } + + @Test + void withGroupPrefix() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setGroupPrefix(new IdConverterDefinition.GroupPrefix().setEnabled(true)); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + SegmentId newIdGen = new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(GroupedPrefixIdConverter.class)); + } + + @Test + void withYearPrefixAfterPrefix() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setGroupPrefix(new IdConverterDefinition.GroupPrefix().setEnabled(true).setBeforePrefix(false)); + idConverterDefinition.setPrefix("prefix-"); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + SegmentId newIdGen = new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(GroupedPrefixIdConverter.class)); + } + + @Test + void withDatePrefix() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setDatePrefix(new IdConverterDefinition.DatePrefix().setEnabled(true)); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + SegmentId newIdGen = new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(DatePrefixIdConverter.class)); + } + + @Test + void withDateNotBeforePrefix() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setDatePrefix(new IdConverterDefinition.DatePrefix().setEnabled(true).setBeforePrefix(false)); + SegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); + SegmentId newIdGen = new SegmentIdConverterDecorator(segmentId, idConverterDefinition).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(DatePrefixIdConverter.class)); + } + + public static class CustomIdConverter implements IdConverter { + @Nonnull + @Override + public String asString(long id) { + return String.valueOf(id); + } + + @Override + public long asLong(@Nonnull String idString) { + return Long.getLong(idString); + } + } +} + diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfigurationTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfigurationTest.java index 5326f281ca..a3d0d81ef7 100644 --- a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfigurationTest.java +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/CosIdSnowflakeAutoConfigurationTest.java @@ -16,20 +16,17 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import me.ahoo.cosid.machine.ClockBackwardsSynchronizer; -import me.ahoo.cosid.snowflake.SnowflakeId; import me.ahoo.cosid.machine.InstanceId; import me.ahoo.cosid.machine.MachineId; import me.ahoo.cosid.machine.MachineStateStorage; -import me.ahoo.cosid.machine.ManualMachineIdDistributor; -import me.ahoo.cosid.machine.k8s.StatefulSetMachineIdDistributor; import me.ahoo.cosid.spring.boot.starter.CosIdAutoConfiguration; import me.ahoo.cosid.spring.boot.starter.machine.ConditionalOnCosIdMachineEnabled; +import me.ahoo.cosid.spring.boot.starter.machine.CosIdHostNameAutoConfiguration; import me.ahoo.cosid.spring.boot.starter.machine.CosIdLifecycleMachineIdDistributor; import me.ahoo.cosid.spring.boot.starter.machine.CosIdMachineAutoConfiguration; import me.ahoo.cosid.spring.boot.starter.machine.MachineProperties; import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.cloud.commons.util.UtilAutoConfiguration; @@ -47,8 +44,10 @@ void contextLoads() { .withPropertyValues(ConditionalOnCosIdMachineEnabled.ENABLED_KEY + "=true") .withPropertyValues(ConditionalOnCosIdSnowflakeEnabled.ENABLED_KEY + "=true") .withPropertyValues(MachineProperties.PREFIX + ".distributor.manual.machineId=1") + .withBean(CustomizeSnowflakeIdProperties.class, () -> idProperties -> idProperties.getProvider().put("test", new SnowflakeIdProperties.IdDefinition())) .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, CosIdMachineAutoConfiguration.class, CosIdSnowflakeAutoConfiguration.class) .run(context -> { @@ -60,7 +59,8 @@ void contextLoads() { .hasSingleBean(ClockBackwardsSynchronizer.class) .hasSingleBean(MachineId.class) .hasSingleBean(CosIdLifecycleMachineIdDistributor.class) - .hasSingleBean(SnowflakeId.class) + .hasBean("__share__SnowflakeId") + .hasBean("testSnowflakeId") ; }); } @@ -76,6 +76,7 @@ void contextLoadsWithConfig() { .withPropertyValues(SnowflakeIdProperties.PREFIX + ".provider.test.converter.to_string.pad-start=true") .withUserConfiguration(UtilAutoConfiguration.class, CosIdAutoConfiguration.class, + CosIdHostNameAutoConfiguration.class, CosIdMachineAutoConfiguration.class, CosIdSnowflakeAutoConfiguration.class) .run(context -> { diff --git a/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdConverterDecoratorTest.java b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdConverterDecoratorTest.java new file mode 100644 index 0000000000..035981dea3 --- /dev/null +++ b/cosid-spring-boot-starter/src/test/java/me/ahoo/cosid/spring/boot/starter/snowflake/SnowflakeIdConverterDecoratorTest.java @@ -0,0 +1,38 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.boot.starter.snowflake; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.converter.SnowflakeFriendlyIdConverter; +import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; +import me.ahoo.cosid.snowflake.SnowflakeId; +import me.ahoo.cosid.spring.boot.starter.IdConverterDefinition; + +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; + +class SnowflakeIdConverterDecoratorTest { + + @Test + void newSnowflakeFriendly() { + IdConverterDefinition idConverterDefinition = new IdConverterDefinition(); + idConverterDefinition.setType(IdConverterDefinition.Type.SNOWFLAKE_FRIENDLY); + SnowflakeId snowflakeId = new MillisecondSnowflakeId(1); + SnowflakeId newIdGen = new SnowflakeIdConverterDecorator(snowflakeId, idConverterDefinition, ZoneId.systemDefault(), false).decorate(); + assertThat(newIdGen.idConverter(), instanceOf(SnowflakeFriendlyIdConverter.class)); + } +} \ No newline at end of file diff --git a/cosid-spring-data-jdbc/build.gradle.kts b/cosid-spring-data-jdbc/build.gradle.kts new file mode 100644 index 0000000000..fc00ded304 --- /dev/null +++ b/cosid-spring-data-jdbc/build.gradle.kts @@ -0,0 +1,19 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +dependencies { + api(project(":cosid-core")) + api("org.springframework.data:spring-data-jdbc") + testImplementation(project(":cosid-test")) +} diff --git a/cosid-spring-data-jdbc/src/main/java/me/ahoo/cosid/spring/data/jdbc/CosIdBeforeConvertCallback.java b/cosid-spring-data-jdbc/src/main/java/me/ahoo/cosid/spring/data/jdbc/CosIdBeforeConvertCallback.java new file mode 100644 index 0000000000..16099fbce7 --- /dev/null +++ b/cosid-spring-data-jdbc/src/main/java/me/ahoo/cosid/spring/data/jdbc/CosIdBeforeConvertCallback.java @@ -0,0 +1,35 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.data.jdbc; + +import me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry; + +import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; + +import javax.annotation.Nonnull; + +public class CosIdBeforeConvertCallback implements BeforeConvertCallback { + private final CosIdAccessorRegistry accessorRegistry; + + public CosIdBeforeConvertCallback(CosIdAccessorRegistry accessorRegistry) { + this.accessorRegistry = accessorRegistry; + } + + @Nonnull + @Override + public Object onBeforeConvert(@Nonnull Object aggregate) { + accessorRegistry.ensureId(aggregate); + return aggregate; + } +} diff --git a/cosid-spring-data-jdbc/src/main/java/me/ahoo/cosid/spring/data/jdbc/IdAnnotationDefinitionParser.java b/cosid-spring-data-jdbc/src/main/java/me/ahoo/cosid/spring/data/jdbc/IdAnnotationDefinitionParser.java new file mode 100644 index 0000000000..34818a33fd --- /dev/null +++ b/cosid-spring-data-jdbc/src/main/java/me/ahoo/cosid/spring/data/jdbc/IdAnnotationDefinitionParser.java @@ -0,0 +1,45 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.data.jdbc; + +import me.ahoo.cosid.accessor.IdDefinition; +import me.ahoo.cosid.accessor.parser.FieldDefinitionParser; +import me.ahoo.cosid.provider.IdGeneratorProvider; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.annotation.Id; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Id Annotation FieldDefinitionParser. + * + * @author ahoo wang + * @see org.springframework.data.annotation.Id + */ +@Slf4j +public class IdAnnotationDefinitionParser implements FieldDefinitionParser { + + public static final IdAnnotationDefinitionParser INSTANCE = new IdAnnotationDefinitionParser(); + + @Override + public IdDefinition parse(List> lookupClassList, Field field) { + Id fieldId = field.getAnnotation(Id.class); + if (null == fieldId) { + return IdDefinition.NOT_FOUND; + } + return new IdDefinition(IdGeneratorProvider.SHARE, field); + } +} diff --git a/cosid-spring-data-jdbc/src/test/java/me/ahoo/cosid/spring/data/jdbc/CosIdBeforeConvertCallbackTest.java b/cosid-spring-data-jdbc/src/test/java/me/ahoo/cosid/spring/data/jdbc/CosIdBeforeConvertCallbackTest.java new file mode 100644 index 0000000000..26d1c4c3ae --- /dev/null +++ b/cosid-spring-data-jdbc/src/test/java/me/ahoo/cosid/spring/data/jdbc/CosIdBeforeConvertCallbackTest.java @@ -0,0 +1,108 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.data.jdbc; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.accessor.parser.CompositeFieldDefinitionParser; +import me.ahoo.cosid.accessor.parser.DefaultAccessorParser; +import me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry; +import me.ahoo.cosid.accessor.registry.DefaultAccessorRegistry; +import me.ahoo.cosid.annotation.AnnotationDefinitionParser; +import me.ahoo.cosid.annotation.CosId; +import me.ahoo.cosid.provider.DefaultIdGeneratorProvider; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; +import org.springframework.data.annotation.Id; + +import java.util.Arrays; + +class CosIdBeforeConvertCallbackTest { + private final CosIdBeforeConvertCallback cosIdBeforeConvertCallback; + + public CosIdBeforeConvertCallbackTest() { + CompositeFieldDefinitionParser fieldDefinitionParser = new CompositeFieldDefinitionParser(Arrays.asList(AnnotationDefinitionParser.INSTANCE, IdAnnotationDefinitionParser.INSTANCE)); + DefaultAccessorParser accessorParser = new DefaultAccessorParser(fieldDefinitionParser); + CosIdAccessorRegistry accessorRegistry = new DefaultAccessorRegistry(accessorParser); + cosIdBeforeConvertCallback = new CosIdBeforeConvertCallback(accessorRegistry); + } + + @Test + void onBeforeConvertIfId() { + IdEntity entity = new IdEntity(); + DefaultIdGeneratorProvider.INSTANCE.setShare(MockIdGenerator.INSTANCE); + cosIdBeforeConvertCallback.onBeforeConvert(entity); + assertThat(entity.getId(), not(0)); + } + + @Test + void onBeforeConvertIfCosId() { + CosIdEntity entity = new CosIdEntity(); + DefaultIdGeneratorProvider.INSTANCE.setShare(MockIdGenerator.INSTANCE); + cosIdBeforeConvertCallback.onBeforeConvert(entity); + assertThat(entity.getId(), not(0)); + } + + @Test + void onBeforeConvertIfNotFound() { + NotFoundEntity entity = new NotFoundEntity(); + DefaultIdGeneratorProvider.INSTANCE.setShare(MockIdGenerator.INSTANCE); + cosIdBeforeConvertCallback.onBeforeConvert(entity); + assertThat(entity.getId(), equalTo(0L)); + } + + static class IdEntity { + @Id + private long id; + + public long getId() { + return id; + } + + public IdEntity setId(int id) { + this.id = id; + return this; + } + } + + static class CosIdEntity { + @CosId + private long id; + + public long getId() { + return id; + } + + public CosIdEntity setId(int id) { + this.id = id; + return this; + } + } + + static class NotFoundEntity { + + private long id; + + public long getId() { + return id; + } + + public NotFoundEntity setId(int id) { + this.id = id; + return this; + } + } +} \ No newline at end of file diff --git a/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/GroupedSpringRedisIdSegmentDistributorTest.java b/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/GroupedSpringRedisIdSegmentDistributorTest.java new file mode 100644 index 0000000000..4adfce92e8 --- /dev/null +++ b/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/GroupedSpringRedisIdSegmentDistributorTest.java @@ -0,0 +1,71 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.spring.redis; + +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; +import me.ahoo.cosid.test.MockIdGenerator; +import me.ahoo.cosid.test.segment.distributor.GroupedIdSegmentDistributorSpec; +import me.ahoo.cosid.test.segment.distributor.IdSegmentDistributorSpec; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; + +/** + * @author ahoo wang + */ +class GroupedSpringRedisIdSegmentDistributorTest extends GroupedIdSegmentDistributorSpec { + StringRedisTemplate stringRedisTemplate; + SpringRedisIdSegmentDistributorFactory distributorFactory; + protected IdSegmentDistributorDefinition idSegmentDistributorDefinition; + + @BeforeEach + void setup() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration); + lettuceConnectionFactory.afterPropertiesSet(); + stringRedisTemplate = new StringRedisTemplate(lettuceConnectionFactory); + distributorFactory = new SpringRedisIdSegmentDistributorFactory(stringRedisTemplate); + idSegmentDistributorDefinition = new IdSegmentDistributorDefinition("GroupedSpringRedisIdSegmentDistributorTest", MockIdGenerator.INSTANCE.generateAsString(), 0, 100); + } + + @Override + protected IdSegmentDistributorFactory getFactory() { + return distributorFactory; + } + + @Test + protected void distributorFactoryTest() { + IdSegmentDistributor idSegmentDistributor = distributorFactory.create(idSegmentDistributorDefinition); + Assertions.assertNotNull(idSegmentDistributor); + } + + @Test + void getAdderKey() { + IdSegmentDistributor idSegmentDistributor = distributorFactory.create(idSegmentDistributorDefinition); + Assertions.assertTrue(idSegmentDistributor instanceof SpringRedisIdSegmentDistributor); + SpringRedisIdSegmentDistributor springRedisIdSegmentDistributor = (SpringRedisIdSegmentDistributor) idSegmentDistributor; + Assertions.assertNotNull(springRedisIdSegmentDistributor.getAdderKey()); + Assertions.assertNotNull(springRedisIdSegmentDistributor.getName()); + Assertions.assertTrue(springRedisIdSegmentDistributor.getOffset() == 0); + Assertions.assertTrue(springRedisIdSegmentDistributor.getStep() == 100); + Assertions.assertNotNull(springRedisIdSegmentDistributor.getNamespace()); + Assertions.assertNotNull(springRedisIdSegmentDistributor.nextMaxId(100)); + } +} diff --git a/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/SpringRedisIdSegmentDistributorTest.java b/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/SpringRedisIdSegmentDistributorTest.java index 7ae11cd9c6..b21162a8eb 100644 --- a/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/SpringRedisIdSegmentDistributorTest.java +++ b/cosid-spring-redis/src/test/java/me/ahoo/cosid/spring/redis/SpringRedisIdSegmentDistributorTest.java @@ -34,7 +34,7 @@ class SpringRedisIdSegmentDistributorTest extends IdSegmentDistributorSpec { StringRedisTemplate stringRedisTemplate; SpringRedisIdSegmentDistributorFactory distributorFactory; protected IdSegmentDistributorDefinition idSegmentDistributorDefinition; - + @BeforeEach void setup() { RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); @@ -42,37 +42,37 @@ void setup() { lettuceConnectionFactory.afterPropertiesSet(); stringRedisTemplate = new StringRedisTemplate(lettuceConnectionFactory); distributorFactory = new SpringRedisIdSegmentDistributorFactory(stringRedisTemplate); - idSegmentDistributorDefinition = new IdSegmentDistributorDefinition("RedisIdSegmentDistributorFactoryTest", MockIdGenerator.INSTANCE.generateAsString(), 0, 100); + idSegmentDistributorDefinition = new IdSegmentDistributorDefinition("SpringRedisIdSegmentDistributorTest", MockIdGenerator.INSTANCE.generateAsString(), 0, 100); } - - + + @Override protected IdSegmentDistributorFactory getFactory() { return distributorFactory; } - + @Override protected void setMaxIdBack(T distributor, long maxId) { String adderKey = ((SpringRedisIdSegmentDistributor) distributor).getAdderKey(); stringRedisTemplate.opsForValue().set(adderKey, String.valueOf(maxId - 1)); } - - + + @Test protected void distributorFactoryTest() { IdSegmentDistributor idSegmentDistributor = distributorFactory.create(idSegmentDistributorDefinition); Assertions.assertNotNull(idSegmentDistributor); } - + @Test void getAdderKey() { IdSegmentDistributor idSegmentDistributor = distributorFactory.create(idSegmentDistributorDefinition); Assertions.assertTrue(idSegmentDistributor instanceof SpringRedisIdSegmentDistributor); - SpringRedisIdSegmentDistributor springRedisIdSegmentDistributor=(SpringRedisIdSegmentDistributor)idSegmentDistributor; + SpringRedisIdSegmentDistributor springRedisIdSegmentDistributor = (SpringRedisIdSegmentDistributor) idSegmentDistributor; Assertions.assertNotNull(springRedisIdSegmentDistributor.getAdderKey()); Assertions.assertNotNull(springRedisIdSegmentDistributor.getName()); - Assertions.assertTrue(springRedisIdSegmentDistributor.getOffset()==0); - Assertions.assertTrue(springRedisIdSegmentDistributor.getStep()==100); + Assertions.assertTrue(springRedisIdSegmentDistributor.getOffset() == 0); + Assertions.assertTrue(springRedisIdSegmentDistributor.getStep() == 100); Assertions.assertNotNull(springRedisIdSegmentDistributor.getNamespace()); Assertions.assertNotNull(springRedisIdSegmentDistributor.nextMaxId(100)); } diff --git a/cosid-test/build.gradle.kts b/cosid-test/build.gradle.kts index 51ecd9de56..f86e6398eb 100644 --- a/cosid-test/build.gradle.kts +++ b/cosid-test/build.gradle.kts @@ -13,8 +13,18 @@ description = "CosId test specification module" +java { + registerFeature("mongoSupport") { + usingSourceSet(sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]) + capability(group.toString(), "mongo-support", version.toString()) + } +} + dependencies { implementation(project(":cosid-core")) implementation("org.junit.jupiter:junit-jupiter-api") implementation("org.hamcrest:hamcrest") + "mongoSupportImplementation"("org.testcontainers:testcontainers") + "mongoSupportImplementation"("org.testcontainers:junit-jupiter") + "mongoSupportImplementation"("org.testcontainers:mongodb") } diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/ConcurrentGenerateStingSpec.java b/cosid-test/src/main/java/me/ahoo/cosid/test/ConcurrentGenerateStingSpec.java index 6bdd26a93b..3f36c85862 100644 --- a/cosid-test/src/main/java/me/ahoo/cosid/test/ConcurrentGenerateStingSpec.java +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/ConcurrentGenerateStingSpec.java @@ -30,11 +30,11 @@ public class ConcurrentGenerateStingSpec implements TestSpec { private final int concurrentThreads; private final long idSize; private final int singleGenerates; - + public ConcurrentGenerateStingSpec(IdGenerator... idGenerators) { this(10, 800000, idGenerators); } - + public ConcurrentGenerateStingSpec(int concurrentThreads, long idSize, IdGenerator... idGenerators) { Preconditions.checkState(idGenerators.length > 0, "idGenerators can not be empty."); this.idGenerators = idGenerators; @@ -42,72 +42,72 @@ public ConcurrentGenerateStingSpec(int concurrentThreads, long idSize, IdGenerat this.idSize = idSize; this.singleGenerates = (int) (idSize / concurrentThreads); } - + public int getConcurrentThreads() { return concurrentThreads; } - + public long getIdSize() { return idSize; } - + private IdGenerator getIdGenerator(int threadIdx) { return idGenerators[threadIdx % (idGenerators.length)]; } - + protected void assertSingleEach(String previousId, String id) { Preconditions.checkState(id.compareTo(previousId) > 0, "id:[%s] must greater then previousId:[%s]", id, previousId); } - + protected void assertGlobalEach(String previousId, String id) { Preconditions.checkState(id.compareTo(previousId) > 0, "id:[%s] must equals previousId:[%s]+1.", id, previousId); } - + @Override @SuppressWarnings("unchecked") public void verify() { - + CompletableFuture[] completableFutures = new CompletableFuture[concurrentThreads]; - + for (int i = 0; i < completableFutures.length; i++) { final IdGenerator idGenerator = getIdGenerator(i); completableFutures[i] = CompletableFuture - .supplyAsync(() -> { - String[] ids = new String[singleGenerates]; - String previousId = "0"; - for (int j = 0; j < ids.length; j++) { - String nextId = idGenerator.generateAsString(); - ids[j] = nextId; - assertSingleEach(previousId, nextId); - previousId = nextId; - } - return ids; - }); + .supplyAsync(() -> { + String[] ids = new String[singleGenerates]; + String previousId = "0"; + for (int j = 0; j < ids.length; j++) { + String nextId = idGenerator.generateAsString(); + ids[j] = nextId; + assertSingleEach(previousId, nextId); + previousId = nextId; + } + return ids; + }); } - + CompletableFuture - .allOf(completableFutures) - .thenAccept(nil -> { - final String[] totalIds = new String[(int) idSize]; - int totalIdx = 0; - for (CompletableFuture completableFuture : completableFutures) { - String[] ids = completableFuture.join(); - for (String id : ids) { - totalIds[totalIdx++] = id; + .allOf(completableFutures) + .thenAccept(nil -> { + final String[] totalIds = new String[(int) idSize]; + int totalIdx = 0; + for (CompletableFuture completableFuture : completableFutures) { + String[] ids = completableFuture.join(); + for (String id : ids) { + totalIds[totalIdx++] = id; + } } - } - - Arrays.sort(totalIds); - - String previousId = "-1"; - for (String id : totalIds) { - if ("-1".equals(previousId)) { + + Arrays.sort(totalIds); + + String previousId = "-1"; + for (String id : totalIds) { + if ("-1".equals(previousId)) { + previousId = id; + continue; + } + assertGlobalEach(previousId, id); previousId = id; - continue; } - assertGlobalEach(previousId, id); - previousId = id; - } - }).join(); + }).join(); } } diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/ModSpec.java b/cosid-test/src/main/java/me/ahoo/cosid/test/ModSpec.java new file mode 100644 index 0000000000..284dba7cbf --- /dev/null +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/ModSpec.java @@ -0,0 +1,102 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.test; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.locks.LockSupport; +import java.util.function.LongSupplier; + +@Slf4j +public class ModSpec implements Runnable, TestSpec { + public static final Runnable DEFAULT_WAIT = () -> { + int wait = ThreadLocalRandom.current().nextInt(0, 1000); + LockSupport.parkNanos(wait); + }; + /** + * 测试迭代次数. + */ + private final int iterations; + /** + * 取模除数. + */ + private final int divisor; + /** + * 允许的标准差. + */ + private final double allowablePopStd; + + private final LongSupplier idGenerator; + /** + * 预期平均命中数. + */ + private final int expectedAvgHits; + private final Runnable wait; + private final int[] hits; + private double popVariance; + private double popStd; + private double popStdError; + + public ModSpec(int iterations, int divisor, double allowablePopStd, LongSupplier idGenerator, Runnable wait) { + this.iterations = iterations; + this.divisor = divisor; + this.allowablePopStd = allowablePopStd; + this.idGenerator = idGenerator; + this.wait = wait; + expectedAvgHits = iterations / divisor; + hits = new int[divisor]; + + } + + @Override + public void run() { + if (hits[0] > 0) { + return; + } + for (int i = 0; i < iterations; i++) { + long id = idGenerator.getAsLong(); + int mod = (int) (id % divisor); + hits[mod]++; + wait.run(); + } + + popVariance = Arrays.stream(hits) + .map(hit -> hit - expectedAvgHits) + .mapToDouble(diff -> Math.pow(diff, 2)) + .sum() / hits.length; + /** + * 标准方差 + */ + popStd = Math.sqrt(popVariance); + /** + * 标准误差 + */ + popStdError = popStd / Math.sqrt(hits.length); + if (log.isInfoEnabled()) { + log.info("Report - iterations:{},divisor:{},allowablePopStd:{},expectedAvgHits:{},popStd:{},popStdError:{} - hits:{}", + iterations, divisor, allowablePopStd, expectedAvgHits, popStd, popStdError, hits); + } + } + + @Override + public void verify() { + run(); + if (popStd > allowablePopStd) { + throw new AssertionError("popStd:" + popStd + ",allowablePopStd:" + allowablePopStd); + } + } + +} diff --git a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoLauncher.java b/cosid-test/src/main/java/me/ahoo/cosid/test/container/MongoLauncher.java similarity index 79% rename from cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoLauncher.java rename to cosid-test/src/main/java/me/ahoo/cosid/test/container/MongoLauncher.java index 8a7dbb2e52..5d0d607386 100644 --- a/cosid-mongo/src/test/java/me/ahoo/cosid/mongo/MongoLauncher.java +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/container/MongoLauncher.java @@ -11,7 +11,7 @@ * limitations under the License. */ -package me.ahoo.cosid.mongo; +package me.ahoo.cosid.test.container; import org.testcontainers.containers.MongoDBContainer; @@ -19,8 +19,9 @@ import org.testcontainers.utility.DockerImageName; public class MongoLauncher { - private static final String DEV_CONNECTION_STRING = "mongodb://root:root@localhost"; - private static final MongoDBContainer MONGO_CONTAINER = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10")) + private static final String CONNECTION_OPTIONS = "/?connectTimeoutMS=300000&maxIdleTimeMS=300000"; + private static final String DEV_CONNECTION_STRING = "mongodb://root:root@localhost" + CONNECTION_OPTIONS; + private static final MongoDBContainer MONGO_CONTAINER = new MongoDBContainer(DockerImageName.parse("mongo:6.0.12")) .withNetworkAliases("mongo") .withReuse(true); @@ -29,6 +30,7 @@ public static String getConnectionString() { return DEV_CONNECTION_STRING; } MONGO_CONTAINER.start(); - return MONGO_CONTAINER.getConnectionString(); + return MONGO_CONTAINER.getConnectionString() + CONNECTION_OPTIONS; + } } diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/DistributeSafeGuard.java b/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/DistributeSafeGuard.java index 8ad3cc5002..10c764754c 100644 --- a/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/DistributeSafeGuard.java +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/DistributeSafeGuard.java @@ -72,7 +72,7 @@ public void verify() { /* * 等待所有实例到达安全守护点(SafeGuardAt),即变成可回收状态. */ - LockSupport.parkNanos(this, safeGuardDuration.plusMillis(10).toNanos()); + LockSupport.parkNanos(this, safeGuardDuration.plusMillis(300).toNanos()); availableInstances = allInstances.subList(endIdx, MachineIdDistributor.totalMachineIds(moreMachineBit)); Integer[] machineIds = availableInstances diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/Guard.java b/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/Guard.java index 842f2e0a8f..626a0a7e3d 100644 --- a/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/Guard.java +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/Guard.java @@ -22,6 +22,7 @@ import me.ahoo.cosid.test.MockIdGenerator; import me.ahoo.cosid.test.TestSpec; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.Supplier; /** @@ -42,7 +43,8 @@ public Guard(Supplier implFactory, int machineBit) { public void verify() { MachineIdDistributor distributor = implFactory.get(); String namespace = MockIdGenerator.usePrefix("Guard").generateAsString(); - InstanceId instanceId = mockInstance(0, false); + int port = ThreadLocalRandom.current().nextInt(0, 65535); + InstanceId instanceId = mockInstance(port, false); int machineId = distributor.distribute(namespace, machineBit, instanceId, MachineIdDistributor.FOREVER_SAFE_GUARD_DURATION).getMachineId(); assertThat(machineId, equalTo(0)); distributor.guard(namespace, instanceId, MachineIdDistributor.FOREVER_SAFE_GUARD_DURATION); diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/MachineIdDistributorSpec.java b/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/MachineIdDistributorSpec.java index f13d64c096..9cc526d7c5 100644 --- a/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/MachineIdDistributorSpec.java +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/machine/distributor/MachineIdDistributorSpec.java @@ -30,9 +30,9 @@ public abstract class MachineIdDistributorSpec { public static final String TEST_HOST = "127.0.0.1"; private static final int TEST_MACHINE_BIT = 5; - private static final Duration TEST_SAFE_GUARD_DURATION = Duration.ofSeconds(5); + private static final Duration TEST_SAFE_GUARD_DURATION = Duration.ofSeconds(2); - static InstanceId mockInstance(int port, boolean stable) { + protected static InstanceId mockInstance(int port, boolean stable) { return InstanceId.of(TEST_HOST, port, stable); } diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/GroupedIdSegmentDistributorSpec.java b/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/GroupedIdSegmentDistributorSpec.java new file mode 100644 index 0000000000..eea88cc2cd --- /dev/null +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/GroupedIdSegmentDistributorSpec.java @@ -0,0 +1,91 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.test.segment.distributor; + +import static me.ahoo.cosid.segment.IdSegmentDistributor.DEFAULT_SEGMENTS; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +import me.ahoo.cosid.segment.IdSegment; +import me.ahoo.cosid.segment.IdSegmentChain; +import me.ahoo.cosid.segment.IdSegmentDistributor; +import me.ahoo.cosid.segment.IdSegmentDistributorDefinition; +import me.ahoo.cosid.segment.IdSegmentDistributorFactory; +import me.ahoo.cosid.segment.grouped.GroupBySupplier; +import me.ahoo.cosid.segment.grouped.GroupedIdSegmentDistributorFactory; +import me.ahoo.cosid.segment.grouped.date.YearGroupBySupplier; +import me.ahoo.cosid.test.MockIdGenerator; + +import org.junit.jupiter.api.Test; + +public abstract class GroupedIdSegmentDistributorSpec extends IdSegmentDistributorSpec { + + protected GroupBySupplier groupedSupplier() { + return new YearGroupBySupplier("yyyy"); + } + + @Override + public void nextMaxIdWhenBack() { + + } + + @Override + protected void setMaxIdBack(T distributor, long maxId) { + + } + + @Override + protected IdSegmentDistributorFactory factory() { + return new GroupedIdSegmentDistributorFactory(groupedSupplier(), getFactory()); + } + + @Test + @Override + public void getGroup() { + String namespace = MockIdGenerator.INSTANCE.generateAsString(); + String name = "getGroup"; + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, name, TEST_OFFSET, TEST_STEP); + IdSegmentDistributor distributor = factory().create(definition); + assertThat(distributor.group(), equalTo(groupedSupplier().get())); + } + + @Test + @Override + public void nextIdSegment() { + String namespace = MockIdGenerator.INSTANCE.generateAsString(); + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextIdSegment", TEST_OFFSET, TEST_STEP); + IdSegmentDistributor distributor = factory().create(definition); + long expectedMaxId = TEST_OFFSET + TEST_STEP; + IdSegment actual = distributor.nextIdSegment(); + assertThat(actual.getMaxId(), equalTo(expectedMaxId)); + assertThat(actual.getStep(), equalTo(TEST_STEP)); + assertThat(actual.getSequence(), equalTo(0L)); + assertThat(actual.getTtl(), equalTo(groupedSupplier().get().ttl())); + } + + @Test + @Override + public void nextIdSegmentChain() { + IdSegmentChain root = IdSegmentChain.newRoot(false); + String namespace = MockIdGenerator.INSTANCE.generateAsString(); + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextIdSegmentChain", TEST_OFFSET, TEST_STEP); + IdSegmentDistributor distributor = factory().create(definition); + long expectedMaxId = TEST_OFFSET + Math.multiplyExact(TEST_STEP, DEFAULT_SEGMENTS); + IdSegment actual = distributor.nextIdSegmentChain(root); + assertThat(actual.getMaxId(), equalTo(expectedMaxId)); + assertThat(actual.getStep(), equalTo(TEST_STEP)); + assertThat(actual.getSequence(), equalTo(0L)); + assertThat(actual.getTtl(), equalTo(groupedSupplier().get().ttl())); + } +} diff --git a/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/IdSegmentDistributorSpec.java b/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/IdSegmentDistributorSpec.java index a5ad043fc8..8c03344daa 100644 --- a/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/IdSegmentDistributorSpec.java +++ b/cosid-test/src/main/java/me/ahoo/cosid/test/segment/distributor/IdSegmentDistributorSpec.java @@ -27,6 +27,7 @@ import me.ahoo.cosid.segment.IdSegmentDistributorFactory; import me.ahoo.cosid.segment.SegmentChainId; import me.ahoo.cosid.segment.SegmentId; +import me.ahoo.cosid.segment.grouped.GroupedKey; import me.ahoo.cosid.test.Assert; import me.ahoo.cosid.test.ConcurrentGenerateSpec; import me.ahoo.cosid.test.MockIdGenerator; @@ -44,17 +45,30 @@ */ public abstract class IdSegmentDistributorSpec { - static final long TEST_OFFSET = 0; - static final long TEST_STEP = 100; + protected static final long TEST_OFFSET = 0; + protected static final long TEST_STEP = 100; protected abstract IdSegmentDistributorFactory getFactory(); + protected IdSegmentDistributorFactory factory() { + return getFactory(); + } + + @Test + public void getGroup() { + String namespace = MockIdGenerator.INSTANCE.generateAsString(); + String name = "getGroup"; + IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, name, TEST_OFFSET, TEST_STEP); + IdSegmentDistributor distributor = factory().create(definition); + assertThat(distributor.group(), equalTo(GroupedKey.NEVER)); + } + @Test public void getNamespace() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); String name = "getNamespace"; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, name, TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); assertThat(distributor.getNamespace(), equalTo(namespace)); } @@ -63,7 +77,7 @@ public void getName() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); String name = "getName"; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, name, TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); assertThat(distributor.getName(), equalTo(name)); } @@ -72,7 +86,7 @@ public void getNamespacedName() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); String name = "getNamespacedName"; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, name, TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); String expected = IdSegmentDistributor.getNamespacedName(namespace, name); assertThat(distributor.getNamespacedName(), equalTo(expected)); } @@ -81,7 +95,7 @@ public void getNamespacedName() { public void getStep() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "getStep", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); assertThat(distributor.getStep(), equalTo(TEST_STEP)); } @@ -90,7 +104,7 @@ public void getStepWithSegments() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); int segments = ThreadLocalRandom.current().nextInt(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "getStepWithSegments", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expected = Math.multiplyExact(TEST_STEP, segments); long actual = distributor.getStep(segments); assertThat(actual, equalTo(expected)); @@ -100,11 +114,10 @@ public void getStepWithSegments() { public void nextMaxId() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextMaxId", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expected = TEST_OFFSET + TEST_STEP; long actual = distributor.nextMaxId(); assertThat(actual, equalTo(expected)); - long actual2 = distributor.nextMaxId(); assertThat(actual2, greaterThan(actual)); } @@ -114,7 +127,7 @@ public void nextMaxIdWithStep() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); long step = 50; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextMaxIdWithStep", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expected = TEST_OFFSET + step; long actual = distributor.nextMaxId(step); assertThat(actual, equalTo(expected)); @@ -126,7 +139,7 @@ public void nextMaxIdWithStep() { public void nextMaxIdWhenBack() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextMaxIdWhenBack", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expected = TEST_OFFSET + TEST_STEP; long actual = distributor.nextMaxId(); assertThat(actual, equalTo(expected)); @@ -138,7 +151,7 @@ public void nextMaxIdWhenBack() { public void nextIdSegment() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextIdSegment", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expectedMaxId = TEST_OFFSET + TEST_STEP; IdSegment actual = distributor.nextIdSegment(); assertThat(actual.getMaxId(), equalTo(expectedMaxId)); @@ -150,9 +163,9 @@ public void nextIdSegment() { @Test public void nextIdSegmentWithTtl() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); - long ttl = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE); + long ttl = 10; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextIdSegmentWithTtl", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expectedMaxId = TEST_OFFSET + TEST_STEP; IdSegment actual = distributor.nextIdSegment(ttl); assertThat(actual.getMaxId(), equalTo(expectedMaxId)); @@ -165,9 +178,9 @@ public void nextIdSegmentWithTtl() { public void nextIdSegmentWithSegmentsAndTtl() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); int segments = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE); - long ttl = ThreadLocalRandom.current().nextLong(1, Long.MAX_VALUE); + long ttl = 10; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextIdSegmentWithSegmentsAndTtl", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expectedMaxId = TEST_OFFSET + Math.multiplyExact(TEST_STEP, segments); long expectedStep = Math.multiplyExact(TEST_STEP, segments); IdSegment actual = distributor.nextIdSegment(segments, ttl); @@ -179,10 +192,10 @@ public void nextIdSegmentWithSegmentsAndTtl() { @Test public void nextIdSegmentChain() { - IdSegmentChain root = IdSegmentChain.newRoot(); + IdSegmentChain root = IdSegmentChain.newRoot(false); String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextIdSegmentChain", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); long expectedMaxId = TEST_OFFSET + Math.multiplyExact(TEST_STEP, DEFAULT_SEGMENTS); IdSegment actual = distributor.nextIdSegmentChain(root); assertThat(actual.getMaxId(), equalTo(expectedMaxId)); @@ -198,7 +211,7 @@ public void nextMaxIdConcurrent() { int concurrency = 20; CompletableFuture[] results = new CompletableFuture[concurrency]; IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "nextMaxIdConcurrent", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); for (int i = 0; i < concurrency; i++) { results[i] = CompletableFuture.supplyAsync(() -> distributor.nextMaxId(1)); } @@ -213,7 +226,7 @@ public void nextMaxIdConcurrent() { public void generateConcurrent() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "generateConcurrent", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); SegmentId segmentId = new DefaultSegmentId(distributor); new ConcurrentGenerateSpec(segmentId).verify(); } @@ -222,7 +235,7 @@ public void generateConcurrent() { public void generateConcurrentOfChain() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "generateConcurrentOfChain", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); SegmentChainId segmentId = new SegmentChainId(distributor); new ConcurrentGenerateSpec(segmentId).verify(); } @@ -231,9 +244,9 @@ public void generateConcurrentOfChain() { public void generateMultiInstanceConcurrent() { String namespace = MockIdGenerator.INSTANCE.generateAsString(); IdSegmentDistributorDefinition definition = new IdSegmentDistributorDefinition(namespace, "generateMultiInstanceConcurrent", TEST_OFFSET, TEST_STEP); - IdSegmentDistributor distributor = getFactory().create(definition); + IdSegmentDistributor distributor = factory().create(definition); SegmentId segmentId = new DefaultSegmentId(distributor); - IdSegmentDistributor distributor2 = getFactory().create(definition); + IdSegmentDistributor distributor2 = factory().create(definition); SegmentId segmentId2 = new DefaultSegmentId(distributor2); new ConcurrentGenerateSpec(segmentId, segmentId2).verify(); } diff --git a/document/docs/.vuepress/config.ts b/document/docs/.vuepress/config.ts index 59dc4da71c..57c2c527d0 100644 --- a/document/docs/.vuepress/config.ts +++ b/document/docs/.vuepress/config.ts @@ -15,6 +15,10 @@ import { NavItemsZH, SidebarZH } from './config/index' +const siteBase: `/${string}/` = process.env.SITE_BASE || "/" + +console.log('Configured SiteBase is:' + siteBase) + const GM_ID = 'G-SP6EEGK56L' export default defineConfig(ctx => ({ @@ -83,5 +87,6 @@ export default defineConfig(ctx => ({ ['vuepress-plugin-flowchart'] ], extraWatchFiles: ['.vuepress/config/**'], - evergreen: !ctx.isProd + evergreen: !ctx.isProd, + base: siteBase })); diff --git a/document/docs/.vuepress/config/sidebar/shared.ts b/document/docs/.vuepress/config/sidebar/shared.ts index dc480d8920..e52ddbafc7 100644 --- a/document/docs/.vuepress/config/sidebar/shared.ts +++ b/document/docs/.vuepress/config/sidebar/shared.ts @@ -13,8 +13,8 @@ import {SidebarConfigArray} from 'vuepress/config'; -export function getGuideSidebar(groupA, groupB): SidebarConfigArray { - const sidebar: SidebarConfigArray = [ +export function getGuideSidebar(groupA: string, groupB: string): SidebarConfigArray { + return [ { title: groupA, collapsable: false, @@ -33,7 +33,9 @@ export function getGuideSidebar(groupA, groupB): SidebarConfigArray { 'cosid-zookeeper', 'cosid-jackson', 'cosid-mybatis', - 'cosid-shardingsphere' + 'cosid-shardingsphere', + 'cosid-axon', + 'cosid-flowable' ] }, { @@ -61,16 +63,15 @@ export function getGuideSidebar(groupA, groupB): SidebarConfigArray { title: '性能评测', collapsable: false, children: [ - "perf-test" + "perf-test", + "Performance-CosId-Leaf" ] } ] - - return sidebar } -export function getConfigSidebar(groupA): SidebarConfigArray { - const sidebar: SidebarConfigArray = [ +export function getConfigSidebar(groupA: string): SidebarConfigArray { + return [ { title: groupA, collapsable: false, @@ -84,5 +85,4 @@ export function getConfigSidebar(groupA): SidebarConfigArray { ] } ] - return sidebar } diff --git a/document/docs/.vuepress/public/assets/perf/CosId-VS-Leaf.png b/document/docs/.vuepress/public/assets/perf/CosId-VS-Leaf.png new file mode 100644 index 0000000000..cccbe473f5 Binary files /dev/null and b/document/docs/.vuepress/public/assets/perf/CosId-VS-Leaf.png differ diff --git a/document/docs/README.md b/document/docs/README.md index ee137b5daa..5cc3a180d1 100644 --- a/document/docs/README.md +++ b/document/docs/README.md @@ -4,7 +4,7 @@ heroImage: /logo.png heroText: CosId tagline: 通用、灵活、高性能分布式ID生成器 actionText: 快速上手 → -actionLink: /guide/ +actionLink: /guide/getting-started features: - title: 通用 details: 支持多种类型的分布式ID算法:SnowflakeId、SegmentId、SegmentChainId。 并且支持多种号段分发器、机器号分发器。 diff --git a/document/docs/guide/Performance-CosId-Leaf.md b/document/docs/guide/Performance-CosId-Leaf.md new file mode 100644 index 0000000000..50c5989728 --- /dev/null +++ b/document/docs/guide/Performance-CosId-Leaf.md @@ -0,0 +1,58 @@ +# 分布式ID性能评测:CosId VS 美团 Leaf + +## 环境 + +- MacBook Pro (M1) +- JDK 17 +- JMH 1.36 +- 运行在本机 Docker 内的 mariadb:10.6.4 + +## 运行 + +> 基准测试代码: https://github.com/Ahoo-Wang/CosId/tree/main/cosid-benchmark + +``` shell +git clone git@github.com:Ahoo-Wang/CosId.git +cd cosid-benchmark +./gradlew jmh +``` +or +```shell +gradle jmhJar +java -jar build/libs/cosid-benchmark-2.2.6-jmh.jar -wi 1 -rf json -f 1 +``` + +## 报告 + +``` +# JMH version: 1.36 +# VM version: JDK 17.0.7, OpenJDK 64-Bit Server VM, 17.0.7+7-LTS +# Warmup: 1 iterations, 10 s each +# Measurement: 1 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations + +Benchmark (step) Mode Cnt Score Error Units +AtomicLongBenchmark.generate N/A thrpt 142725210.565 ops/s +CosIdBenchmark.generate 1 thrpt 131920684.604 ops/s +CosIdBenchmark.generate 100 thrpt 132113994.232 ops/s +CosIdBenchmark.generate 1000 thrpt 130281016.155 ops/s +LeafBenchmark.generate 1 thrpt 25787669.815 ops/s +LeafBenchmark.generate 100 thrpt 23897328.183 ops/s +LeafBenchmark.generate 1000 thrpt 23550106.538 ops/s +``` + +

+ CosId VS 美团 Leaf +

+ +> GitHub Action 环境测试报告: [Performance: CosId vs Leaf](https://github.com/Ahoo-Wang/CosId/issues/22) +> +> 因受到 GitHub Runner 资源限制,运行在 GitHub Runner 中的基准测试与真实环境基准测试对比有非常大的差距(近2倍), +但是对于运行在同一环境配置资源情况下(都运行在 GitHub Runner),进行 commit 前后的基准对比、以及第三方库的对比依然是有价值的。 + +## 结论 + +1. CosId (`SegmentChainId`) 性能是 Leaf (`segment`) 的 5 倍。 +2. CosId 、Leaf 的性能与号段步长(Step) 无关。 +3. CosId TPS 基本接近 `AtomicLong` 。 \ No newline at end of file diff --git a/document/docs/guide/README.md b/document/docs/guide/README.md index b3f73cdea0..3f0464e00b 100644 --- a/document/docs/guide/README.md +++ b/document/docs/guide/README.md @@ -19,6 +19,7 @@ - `RedisIdSegmentDistributor`: 基于 *Redis* 的号段分发器。 - `JdbcIdSegmentDistributor`: 基于 *Jdbc* 的号段分发器,支持各种关系型数据库。 - `ZookeeperIdSegmentDistributor`: 基于 *Zookeeper* 的号段分发器。 + - `MongoIdSegmentDistributor`: 基于 *MongoDB* 的号段分发器。 - `SegmentChainId`(**推荐**):`SegmentChainId` (*lock-free*) 是对 `SegmentId` 的增强。性能可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s* [JMH 基准测试](perf-test.md) 。 - `PrefetchWorker` 维护安全距离(`safeDistance`), 并且支持基于饥饿状态的动态`safeDistance`扩容/收缩。 @@ -153,7 +154,7 @@ UUID最大的缺陷是随机的、无序的,当用于主键时会导致数据 - ZookeeperMachineIdDistributor: 使用**ZooKeeper**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。

- RedisMachineIdDistributor + RedisMachineIdDistributor

diff --git a/document/docs/guide/cosid-axon.md b/document/docs/guide/cosid-axon.md new file mode 100644 index 0000000000..3416cdf193 --- /dev/null +++ b/document/docs/guide/cosid-axon.md @@ -0,0 +1,3 @@ +# CosId-Axon 模块 + +TODO \ No newline at end of file diff --git a/document/docs/guide/cosid-flowable.md b/document/docs/guide/cosid-flowable.md new file mode 100644 index 0000000000..2eabe4a66c --- /dev/null +++ b/document/docs/guide/cosid-flowable.md @@ -0,0 +1,3 @@ +# CosId-Flowable 模块 + +TODO \ No newline at end of file diff --git a/document/docs/guide/cosid-redis.md b/document/docs/guide/cosid-redis.md index bf91052f05..0b140a9133 100644 --- a/document/docs/guide/cosid-redis.md +++ b/document/docs/guide/cosid-redis.md @@ -21,7 +21,7 @@ ## SpringRedisMachineIdDistributor

- SegmentId + SegmentId

diff --git a/document/docs/guide/getting-started.md b/document/docs/guide/getting-started.md index ebf9c48911..7eb0828cab 100644 --- a/document/docs/guide/getting-started.md +++ b/document/docs/guide/getting-started.md @@ -6,16 +6,22 @@ ## 安装 -### Gradle +> 开发者可以任选一种的分发器(Redis/JDBC/Mongodb/Zookeeper),并引入对应的依赖。 -> Kotlin DSL +### Redis 分发器 + +[CosId-Example-Redis](https://github.com/Ahoo-Wang/CosId/tree/main/examples/cosid-example-redis) + +#### Gradle Kotlin DSL ``` kotlin - val cosidVersion = "1.14.5"; + val cosidVersion = "latestVersion"; + implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("me.ahoo.cosid:cosid-spring-redis:${cosidVersion}") implementation("me.ahoo.cosid:cosid-spring-boot-starter:${cosidVersion}") ``` -### Maven +#### Maven ```xml @@ -27,10 +33,20 @@ 4.0.0 demo - 1.14.5 + latestVersion + + org.springframework.boot + spring-boot-starter-data-redis + ${springboot.version} + + + me.ahoo.cosid + cosid-spring-redis + ${cosid.version} + me.ahoo.cosid cosid-spring-boot-starter @@ -41,616 +57,56 @@ ``` -### application.yaml +#### 应用配置 (`application.yaml`) ```yaml +spring: + data: + redis: + host: localhost # Redis 分发器直接依赖 spring-data-redis,这样可以省去额外的配置。 cosid: namespace: ${spring.application.name} + machine: + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + distributor: + type: redis snowflake: - enabled: true - machine: - distributor: - type: redis + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + segment: + enabled: true # 可选,当需要使用号段算法时,需要设置为 true + distributor: + type: redis ``` -[//]: # () -[//]: # (*[CosId-SnowflakeId](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-core/src/main/java/me/ahoo/cosid/snowflake)*) - -[//]: # (主要解决 *SnowflakeId* 俩大问题:机器号分配问题、时钟回拨问题。 并且提供更加友好、灵活的使用体验。) - -[//]: # () -[//]: # (### MachineIdDistributor (MachineId 分配器)) - -[//]: # () -[//]: # (> 目前 *[CosId](https://github.com/Ahoo-Wang/CosId)* 提供了以下三种 `MachineId` 分配器。) - -[//]: # () -[//]: # (#### ManualMachineIdDistributor) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( machine:) - -[//]: # ( distributor:) - -[//]: # ( type: manual) - -[//]: # ( manual:) - -[//]: # ( machine-id: 0) - -[//]: # (```) - -[//]: # () -[//]: # (> 手动分配 `MachineId`。) - -[//]: # () -[//]: # (#### StatefulSetMachineIdDistributor) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( machine:) - -[//]: # ( distributor:) - -[//]: # ( type: stateful_set) - -[//]: # (```) - -[//]: # () -[//]: # (> 使用 `Kubernetes` 的 `StatefulSet` 提供的稳定的标识 ID 作为机器号。) - -[//]: # () -[//]: # (#### RedisMachineIdDistributor) - -[//]: # () -[//]: # (![RedisMachineIdDistributor](../docs/RedisMachineIdDistributor.png)) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( machine:) - -[//]: # ( distributor:) - -[//]: # ( type: redis) - -[//]: # (```) - -[//]: # () -[//]: # (> 使用 `Redis` 作为机器号的分发存储。) - -[//]: # () -[//]: # (### ClockBackwardsSynchronizer (时钟回拨同步器)) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( clock-backwards:) - -[//]: # ( spin-threshold: 10) - -[//]: # ( broken-threshold: 2000) - -[//]: # (```) - -[//]: # () -[//]: # (默认提供的 `DefaultClockBackwardsSynchronizer` 时钟回拨同步器使用主动等待同步策略,`spinThreshold`(默认值 10 毫秒) 用于设置自旋等待阈值, 当大于`spinThreshold`) - -[//]: # (时使用线程休眠等待时钟同步,如果超过`brokenThreshold`(默认值 2 秒)时会直接抛出`ClockTooManyBackwardsException`异常。) - -[//]: # () -[//]: # (### MachineStateStorage (机器状态存储)) - -[//]: # () -[//]: # (```java) - -[//]: # (public class MachineState {) - -[//]: # ( public static final MachineState NOT_FOUND = of(-1, -1);) - -[//]: # ( private final int machineId;) - -[//]: # ( private final long lastTimeStamp;) - -[//]: # () -[//]: # ( public MachineState(int machineId, long lastTimeStamp) {) - -[//]: # ( this.machineId = machineId;) - -[//]: # ( this.lastTimeStamp = lastTimeStamp;) - -[//]: # ( }) - -[//]: # () -[//]: # ( public int getMachineId() {) - -[//]: # ( return machineId;) - -[//]: # ( }) - -[//]: # () -[//]: # ( public long getLastTimeStamp() {) - -[//]: # ( return lastTimeStamp;) - -[//]: # ( }) - -[//]: # () -[//]: # ( public static MachineState of(int machineId, long lastStamp) {) - -[//]: # ( return new MachineState(machineId, lastStamp);) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( machine:) - -[//]: # ( state-storage:) - -[//]: # ( local:) - -[//]: # ( state-location: ./cosid-machine-state/) - -[//]: # (```) - -[//]: # () -[//]: # (默认提供的 `LocalMachineStateStorage` 本地机器状态存储,使用本地文件存储机器号、最近一次时间戳,用作 `MachineState` 缓存。) - -[//]: # () -[//]: # (### ClockSyncSnowflakeId (主动时钟同步 `SnowflakeId`)) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( share:) - -[//]: # ( clock-sync: true) - -[//]: # (```) - -[//]: # () -[//]: # (默认 `SnowflakeId` 当发生时钟回拨时会直接抛出 `ClockBackwardsException` 异常,而使用 `ClockSyncSnowflakeId` 会使用 `ClockBackwardsSynchronizer`) - -[//]: # (主动等待时钟同步来重新生成 ID,提供更加友好的使用体验。) - -[//]: # () -[//]: # (### SafeJavaScriptSnowflakeId (`JavaScript` 安全的 `SnowflakeId`)) - -[//]: # () -[//]: # (```java) - -[//]: # (SnowflakeId snowflakeId=SafeJavaScriptSnowflakeId.ofMillisecond(1);) - -[//]: # (```) - -[//]: # () -[//]: # (`JavaScript` 的 `Number.MAX_SAFE_INTEGER` 只有 53 位,如果直接将 63 位的 `SnowflakeId` 返回给前端,那么会值溢出的情况,通常我们可以将`SnowflakeId`) - -[//]: # (转换为 `String` 类型或者自定义 `SnowflakeId` 位分配来缩短 `SnowflakeId` 的位数 使 `ID` 提供给前端时不溢出。) - -[//]: # () -[//]: # (### SnowflakeFriendlyId (可以将 `SnowflakeId` 解析成可读性更好的 `SnowflakeIdState` )) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( share:) - -[//]: # ( friendly: true) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # (public class SnowflakeIdState {) - -[//]: # () -[//]: # ( private final long id;) - -[//]: # () -[//]: # ( private final int machineId;) - -[//]: # () -[//]: # ( private final long sequence;) - -[//]: # () -[//]: # ( private final LocalDateTime timestamp;) - -[//]: # ( /**) - -[//]: # ( * {@link #timestamp}-{@link #machineId}-{@link #sequence}) - -[//]: # ( */) - -[//]: # ( private final String friendlyId;) - -[//]: # (}) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # (public interface SnowflakeFriendlyId extends SnowflakeId {) - -[//]: # () -[//]: # ( SnowflakeIdState friendlyId(long id);) - -[//]: # () -[//]: # ( SnowflakeIdState ofFriendlyId(String friendlyId);) - -[//]: # () -[//]: # ( default SnowflakeIdState friendlyId() {) - -[//]: # ( long id = generate();) - -[//]: # ( return friendlyId(id);) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # ( SnowflakeFriendlyId snowflakeFriendlyId=new DefaultSnowflakeFriendlyId(snowflakeId);) - -[//]: # ( SnowflakeIdState idState=snowflakeFriendlyId.friendlyId();) - -[//]: # ( idState.getFriendlyId(); //20210623131730192-1-0) - -[//]: # (```) +> TIPS: 默认情况下,开启 `snowflake`/`segment` 会生成共享的(`__share__`) `IdGenerator` 注册到 `Spring` 容器 以及 `DefaultIdGeneratorProvider.INSTANCE`。 +> +> WARN: 当同时开启 `snowflake`/`segment` 时,只有其中一个共享的(`__share__`) `IdGenerator` 会注入到 `Spring` 容器(名称冲突),另一个会被忽略。 -[//]: # () -[//]: # (## SegmentId (号段模式)) +`IdGenerator` `Bean Name` 规则: +- SegmentId: `[name]SegmentId` , 比如 : `__share__SegmentId` +- SnowflakeId: `[name]SnowflakeId`, 比如 : `__share__SnowflakeId` -[//]: # () -[//]: # ([//]: # (![SegmentId](../docs/SegmentId.png))) -[//]: # () -[//]: # (### RedisIdSegmentDistributor (使用`Redis`作为号段分发后端存储)) +## 使用 -[//]: # () -[//]: # (```yaml) +> 通过 `@Autowired` 注入 `IdGenerator` 。 -[//]: # (cosid:) +```java + @Qualifier("__share__SegmentId") + @Lazy + @Autowired + private SegmentId segmentId; -[//]: # ( segment:) + @Qualifier("__share__SnowflakeId") + @Lazy + @Autowired + private SnowflakeId snowflakeId; +``` -[//]: # ( enabled: true) +> 通过 `DefaultIdGeneratorProvider.INSTANCE` 获取共享 `IdGenerator` 。 -[//]: # ( distributor:) - -[//]: # ( type: redis) - -[//]: # (```) - -[//]: # () -[//]: # (### JdbcIdSegmentDistributor (使用关系型数据库`Db`作为号段分发后端存储)) - -[//]: # () -[//]: # (> 初始化 `cosid` table) - -[//]: # () -[//]: # (```mysql) - -[//]: # (create table if not exists cosid) - -[//]: # (() - -[//]: # ( name varchar(100) not null comment '{namespace}.{name}',) - -[//]: # ( last_max_id bigint not null default 0,) - -[//]: # ( last_fetch_time bigint not null,) - -[//]: # ( constraint cosid_pk) - -[//]: # ( primary key (name)) - -[//]: # () engine = InnoDB;) - -[//]: # () -[//]: # (```) - -[//]: # () -[//]: # (```yaml) - -[//]: # (spring:) - -[//]: # ( datasource:) - -[//]: # ( url: jdbc:mysql://localhost:3306/test_db) - -[//]: # ( username: root) - -[//]: # ( password: root) - -[//]: # (cosid:) - -[//]: # ( segment:) - -[//]: # ( enabled: true) - -[//]: # ( distributor:) - -[//]: # ( type: jdbc) - -[//]: # ( jdbc:) - -[//]: # ( enable-auto-init-cosid-table: false) - -[//]: # ( enable-auto-init-id-segment: true) - -[//]: # (```) - -[//]: # () -[//]: # (开启 `enable-auto-init-id-segment:true` 之后,应用启动时会尝试创建 `idSegment` 记录,避免手动创建。类似执行了以下初始化sql脚本,不用担心误操作,因为 `name` 是主键。) - -[//]: # () -[//]: # (```mysql) - -[//]: # (insert into cosid) - -[//]: # ( (name, last_max_id, last_fetch_time)) - -[//]: # ( value) - -[//]: # ( ('namespace.name', 0, unix_timestamp());) - -[//]: # (```) - -[//]: # () -[//]: # (### SegmentChainId (号段链模式)) - -[//]: # () -[//]: # (![SegmentChainId](../docs/SegmentChainId.png)) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( segment:) - -[//]: # ( enabled: true) - -[//]: # ( mode: chain) - -[//]: # ( chain:) - -[//]: # ( safe-distance: 5) - -[//]: # ( prefetch-worker:) - -[//]: # ( core-pool-size: 2) - -[//]: # ( prefetch-period: 1s) - -[//]: # (```) - -[//]: # () -[//]: # (## IdGeneratorProvider) - -[//]: # () -[//]: # (```yaml) - -[//]: # (cosid:) - -[//]: # ( snowflake:) - -[//]: # ( provider:) - -[//]: # ( bizA:) - -[//]: # ( # epoch:) - -[//]: # ( # timestamp-bit:) - -[//]: # ( sequence-bit: 12) - -[//]: # ( bizB:) - -[//]: # ( # epoch:) - -[//]: # ( # timestamp-bit:) - -[//]: # ( sequence-bit: 12) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # (IdGenerator idGenerator=idGeneratorProvider.get("bizA");) - -[//]: # (```) - -[//]: # () -[//]: # (在实际使用中我们一般不会所有业务服务使用同一个`IdGenerator`而是不同的业务使用不同的`IdGenerator`,那么`IdGeneratorProvider`就是为了解决这个问题而存在的,他是 `IdGenerator`) - -[//]: # (的容器,可以通过业务名来获取相应的`IdGenerator`。) - -[//]: # () -[//]: # (### CosIdPlugin(MyBatis 插件)) - -[//]: # () -[//]: # (> Kotlin DSL) - -[//]: # () -[//]: # (``` kotlin) - -[//]: # ( implementation("me.ahoo.cosid:cosid-mybatis:${cosidVersion}")) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # () -[//]: # (@Target({ElementType.FIELD})) - -[//]: # (@Documented) - -[//]: # (@Retention(RetentionPolicy.RUNTIME)) - -[//]: # (public @interface CosId {) - -[//]: # ( String value() default IdGeneratorProvider.SHARE;) - -[//]: # () -[//]: # ( boolean friendlyId() default false;) - -[//]: # (}) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # (public class LongIdEntity {) - -[//]: # () -[//]: # ( @CosId(value = "safeJs")) - -[//]: # ( private Long id;) - -[//]: # () -[//]: # ( public Long getId() {) - -[//]: # ( return id;) - -[//]: # ( }) - -[//]: # () -[//]: # ( public void setId(Long id) {) - -[//]: # ( this.id = id;) - -[//]: # ( }) - -[//]: # (}) - -[//]: # () -[//]: # (public class FriendlyIdEntity {) - -[//]: # () -[//]: # ( @CosId(friendlyId = true)) - -[//]: # ( private String id;) - -[//]: # () -[//]: # ( public String getId() {) - -[//]: # ( return id;) - -[//]: # ( }) - -[//]: # () -[//]: # ( public void setId(String id) {) - -[//]: # ( this.id = id;) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # () -[//]: # (@Mapper) - -[//]: # (public interface OrderRepository {) - -[//]: # ( @Insert("insert into t_table (id) value (#{id});")) - -[//]: # ( void insert(LongIdEntity order);) - -[//]: # () -[//]: # ( @Insert({) - -[//]: # ( ""})) - -[//]: # ( void insertList(List list);) - -[//]: # (}) - -[//]: # (```) - -[//]: # () -[//]: # (```java) - -[//]: # ( LongIdEntity entity=new LongIdEntity();) - -[//]: # ( entityRepository.insert(entity);) - -[//]: # ( /**) - -[//]: # ( * {) - -[//]: # ( * "id": 208796080181248) - -[//]: # ( * }) - -[//]: # ( */) +```java + DefaultIdGeneratorProvider.INSTANCE.getShare() +``` -[//]: # ( return entity;) -[//]: # (```) -[//]: # () -[//]: # () diff --git a/document/package.json b/document/package.json index 0990d2da3c..5b455eca1f 100644 --- a/document/package.json +++ b/document/package.json @@ -11,13 +11,13 @@ "docs:build": "vuepress build docs" }, "devDependencies": { - "@vuepress/plugin-back-to-top": "1.9.9", - "@vuepress/plugin-google-analytics": "1.9.9", - "@vuepress/plugin-medium-zoom": "1.9.9", - "@vuepress/plugin-pwa": "1.9.9", - "@vuepress/theme-vue": "1.9.9", + "@vuepress/plugin-back-to-top": "1.9.10", + "@vuepress/plugin-google-analytics": "1.9.10", + "@vuepress/plugin-medium-zoom": "1.9.10", + "@vuepress/plugin-pwa": "1.9.10", + "@vuepress/theme-vue": "1.9.10", "vue-toasted": "1.1.28", - "vuepress": "1.9.9", + "vuepress": "1.9.10", "vuepress-plugin-flowchart": "1.5.0" } } diff --git a/documentation/.gitignore b/documentation/.gitignore new file mode 100644 index 0000000000..80bb256624 --- /dev/null +++ b/documentation/.gitignore @@ -0,0 +1,5 @@ +node_modules +.temp +cache +docs/.vitepress/dist +docs/.vitepress/public/dokka diff --git a/documentation/docs/.vitepress/config.mts b/documentation/docs/.vitepress/config.mts new file mode 100644 index 0000000000..727cf66a65 --- /dev/null +++ b/documentation/docs/.vitepress/config.mts @@ -0,0 +1,67 @@ +import {defineConfig} from 'vitepress' +import {SITE_BASE} from "./configs/SITE_BASE"; +import {head} from "./configs/head"; +import {navbar} from "./configs/navbar"; +import {sidebar} from "./configs/sidebar"; +import {withMermaid} from "vitepress-plugin-mermaid"; + +let hostname = 'https://cosid.ahoo.me/'; +if (SITE_BASE == '/wow/') { + hostname = 'https://ahoowang.gitee.io/cosid/' +} + +// https://vitepress.dev/reference/site-config +let userConfig = defineConfig({ + lang: 'zh-CN', + title: "CosId", + description: "通用、灵活、高性能的分布式ID生成器", + ignoreDeadLinks: 'localhostLinks', + head: head, + base: SITE_BASE, + sitemap: { + hostname: hostname, + transformItems: (items) => { + items.push({ + url: `${hostname}javadoc/index.html`, + changefreq: 'weekly', + priority: 0.8 + }) + return items + } + }, + appearance: 'dark', + themeConfig: { + logo: '/logo.png', + siteTitle: '分布式ID生成器 | CosId', + editLink: { + pattern: 'https://github.com/Ahoo-Wang/CosId/edit/main/documentation/docs/:path' + }, + lastUpdated: { + text: '上次更新' + }, + outline: { + label: '本页目录', + level: [2, 3] + }, + aside: true, + search: {provider: 'local',}, + // https://vitepress.dev/reference/default-theme-config + nav: navbar, + sidebar: sidebar, + socialLinks: [ + {icon: 'github', link: 'https://github.com/Ahoo-Wang/CosId'} + ], + externalLinkIcon: true, + footer: { + message: 'Released under the Apache 2.0 License.', + copyright: 'Copyright © 2022-present Ahoo Wang' + }, + notFound: { + title: '页面未找到', + quote: '你访问的页面不存在。', + linkText: '返回首页' + } + } +}) + +export default withMermaid(userConfig) \ No newline at end of file diff --git a/documentation/docs/.vitepress/configs/SITE_BASE.ts b/documentation/docs/.vitepress/configs/SITE_BASE.ts new file mode 100644 index 0000000000..861c6d6ea1 --- /dev/null +++ b/documentation/docs/.vitepress/configs/SITE_BASE.ts @@ -0,0 +1,14 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const SITE_BASE: '/' | `/${string}/` = process.env.SITE_BASE || "/" diff --git a/documentation/docs/.vitepress/configs/head.ts b/documentation/docs/.vitepress/configs/head.ts new file mode 100644 index 0000000000..c1a1245c93 --- /dev/null +++ b/documentation/docs/.vitepress/configs/head.ts @@ -0,0 +1,40 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {SITE_BASE} from "./SITE_BASE"; +import {HeadConfig} from "vitepress"; + +export const head: HeadConfig[] = [ + ['link', {rel: 'icon', href: `${SITE_BASE}favicon.ico`}], + ['meta', { + name: 'keywords', + content: '通用, 灵活, 高性能的分布式ID生成器' + }], + ['meta', {'http-equiv': 'cache-control', content: 'no-cache, no-store, must-revalidate'}], + ['meta', {'http-equiv': 'pragma', content: 'no-cache'}], + ['meta', {'http-equiv': 'expires', content: '0'}], + ['meta', {name: 'application-name', content: 'CosId'}], + ['meta', {name: 'theme-color', content: '#5f67ee'}], + [ + 'script', + {async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-SP6EEGK56L'} + ], + [ + 'script', + {}, + `window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'G-SP6EEGK56L');` + ] +] \ No newline at end of file diff --git a/documentation/docs/.vitepress/configs/navbar.ts b/documentation/docs/.vitepress/configs/navbar.ts new file mode 100644 index 0000000000..960cce37c5 --- /dev/null +++ b/documentation/docs/.vitepress/configs/navbar.ts @@ -0,0 +1,64 @@ +import {DefaultTheme} from "vitepress/types/default-theme"; + +export const navbar: DefaultTheme.NavItem[] = [ + { + text: '指南', + link: '/guide/getting-started', + activeMatch: '^/guide/' + }, + { + text: '参考', + activeMatch: '^/reference/', + items: [ + { + text: '配置', + link: '/reference/config/basic', + }, + {text: '谁在使用 CosId', link: '/reference/showcase/who-is-using'}, + {text: '博客', link: '/reference/blog/ShardingSphere-Integration-CosId'}, + ] + }, + { + text: 'JavaDoc', + link: `/javadoc/index.html`, + target: '_blank' + }, + { + text: "资源", + items: [ + { + text: '开源项目 - 微服务治理', + items: [ + { + text: 'Wow - 基于 DDD & EventSourcing 的现代响应式 CQRS 架构微服务开发框架', + link: 'https://github.com/Ahoo-Wang/Wow' + }, + { + text: 'CoSky - 高性能、低成本微服务治理平台', + link: 'https://github.com/Ahoo-Wang/CoSky' + }, + { + text: 'CoSec - 基于 RBAC 和策略的多租户响应式安全框架', + link: 'https://github.com/Ahoo-Wang/CoSec' + }, + { + text: 'CoCache - 分布式一致性二级缓存框架', + link: 'https://github.com/Ahoo-Wang/CoCache' + }, + { + text: 'Simba - 易用、灵活的分布式锁服务', + link: 'https://github.com/Ahoo-Wang/Simba' + } + ] + } + ] + }, + { + text: `更新日志`, + link: `https://github.com/Ahoo-Wang/CosId/releases` + }, + { + text: `Gitee`, + link: `https://gitee.com/AhooWang/CosId` + } +] \ No newline at end of file diff --git a/documentation/docs/.vitepress/configs/sidebar.ts b/documentation/docs/.vitepress/configs/sidebar.ts new file mode 100644 index 0000000000..8fe81ac52c --- /dev/null +++ b/documentation/docs/.vitepress/configs/sidebar.ts @@ -0,0 +1,116 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {DefaultTheme} from "vitepress/types/default-theme"; + +export const sidebar: DefaultTheme.Sidebar = { + '/guide/': [ + { + base: '/guide/', + text: '指南', + collapsed: false, + items: [ + {text: '简介', link: 'introduction'}, + {text: '快速上手', link: 'getting-started'}, + {text: 'SnowflakeId', link: 'snowflake'}, + {text: 'SegmentId', link: 'segment'}, + {text: 'SegmentChainId', link: 'segment-chain'}, + {text: 'CosIdGenerator', link: 'cosid-generator'}, + {text: 'IdConverter', link: 'id-converter'}, + {text: 'Id生成器容器', link: 'provider'}, + {text: '特定场景ID配置', link: 'specific-id'}, + {text: 'CosIdProxy', link: 'cosid-proxy'}, + ], + }, { + base: '/guide/extensions/', + text: '扩展', + collapsed: false, + items: [ + {text: 'Redis', link: 'cosid-redis'}, + {text: 'Jdbc', link: 'cosid-jdbc'}, + {text: 'MongoDB', link: 'cosid-mongo'}, + {text: 'Zookeeper', link: 'cosid-zookeeper'}, + {text: 'MyBatis', link: 'cosid-mybatis'}, + {text: 'Jackson', link: 'cosid-jackson'}, + {text: 'Spring-Data-Jdbc', link: 'cosid-spring-data-jdbc'}, + {text: 'Spring-Boot-Starter', link: 'cosid-spring-boot-starter'}, + {text: 'Activiti', link: 'cosid-activiti'}, + {text: 'Flowable', link: 'cosid-flowable'}, + {text: 'Axon', link: 'cosid-axon'}, + {text: 'ShardingSphere', link: 'cosid-shardingsphere'}, + {text: '兼容性测试套件', link: 'cosid-test'}, + ], + }, { + base: '/guide/faq/', + text: 'FAQ', + collapsed: false, + items: [ + {text: '常见问题', link: 'faq'}, + {text: '性能评测', link: 'perf-test'}, + {text: 'CosId VS 美团 Leaf', link: 'Performance-CosId-Leaf'}, + ], + }, { + base: '/guide/sharding/', + text: '分片算法', + collapsed: true, + items: [ + {text: '取模分片算法', link: 'mod-cycle'}, + {text: '时间范围分片算法', link: 'interval-timeline'} + ], + }, + { + text: '参考', + collapsed: false, + items: [ + {text: '配置', link: '/reference/config/basic'}, + {text: '谁在使用 CosId', link: '/reference/showcase/who-is-using'} + ] + } + ], + '/reference/': + [ + { + text: '参考', + items: [ + { + text: '配置', + base: '/reference/config/', + collapsed: false, + items: [ + {text: '基础配置', link: 'basic'}, + {text: '工作进程号', link: 'machine'}, + {text: 'Snowflake', link: 'snowflake'}, + {text: 'Segment', link: 'segment'}, + {text: 'CosIdGenerator', link: 'cosid-generator'}, + {text: 'Zookeeper', link: 'zookeeper'}, + {text: 'ShardingSphere', link: 'shardingsphere'}, + ], + }, { + text: '展示', + base: '/reference/showcase/', + collapsed: false, + items: [ + {text: '谁在使用 CosId', link: 'who-is-using'} + ] + }, { + text: '博客', + base: '/reference/blog/', + collapsed: false, + items: [ + {text: 'ShardingSphere 集成 CosId 实战', link: 'ShardingSphere-Integration-CosId'} + ], + } + ] + } + ] +} \ No newline at end of file diff --git a/documentation/docs/guide/advanced/cosid-annotation.md b/documentation/docs/guide/advanced/cosid-annotation.md new file mode 100644 index 0000000000..5d6275ceed --- /dev/null +++ b/documentation/docs/guide/advanced/cosid-annotation.md @@ -0,0 +1,3 @@ +# CosIdAnnotationSupport + +TODO diff --git a/documentation/docs/guide/advanced/id-generator.md b/documentation/docs/guide/advanced/id-generator.md new file mode 100644 index 0000000000..8f8f0b16c8 --- /dev/null +++ b/documentation/docs/guide/advanced/id-generator.md @@ -0,0 +1,41 @@ +# IdGenerator + +> **分布式ID**生成器 + +```java + +@ThreadSafe +public interface IdGenerator { + + /** + * ID converter, used to convert {@link long} type ID to {@link String} + * + * @return ID converter + */ + default IdConverter idConverter() { + return ToStringIdConverter.INSTANCE; + } + + /** + * Generate distributed ID + * + * @return distributed ID + */ + long generate(); + + /** + * Generate distributed ID as String + * + * @return distributed ID as String + */ + default String generateAsString() { + return idConverter().asString(generate()); + } +} +``` + +## IdGenerator implementation class diagram + +

+ IdGenerator implementation class diagram +

diff --git a/documentation/docs/guide/best-practices.md b/documentation/docs/guide/best-practices.md new file mode 100644 index 0000000000..8bdf2398e2 --- /dev/null +++ b/documentation/docs/guide/best-practices.md @@ -0,0 +1 @@ +# 最佳实践 diff --git a/documentation/docs/guide/cosid-generator.md b/documentation/docs/guide/cosid-generator.md new file mode 100644 index 0000000000..ceb782a948 --- /dev/null +++ b/documentation/docs/guide/cosid-generator.md @@ -0,0 +1,43 @@ +# CosIdGenerator + +_CosIdGenerator_ *单机 TPS 性能:1557W/s*,三倍于 `UUID.randomUUID()`,基于时钟的全局趋势递增ID,可以同时支持一百万个实例。 + +

+ IdGenerator design diagram +

+ +## 特性介绍 + +- 全局趋势递增 +- 局部单调递增 +- 高性能:15,570,085 ops/s(generateAsString),3倍于 `UUID.randomUUID()` +- 反向解析ID状态(时间戳,机器号,序列号) +- 易于扩展 +- 更小的存储空间:15个字符 +- 同时支持一百万个实例 + +基于以上特性,_CosIdGenerator_ 特别适用于大规模集群场景下的全局唯一性ID生成。 + +## Radix36CosIdGenerator + +使用36进制格式化的*CosId生成器* + +`[timestamp(44)]-[machineId(20)]-[sequence(16)] = 80 BITS = 17 CHARS=[timestamp(8)]-[machineId(4)]-[sequence(3)]` + +- 时间戳由原来的44位缩减为8位 +- 机器号由原来20位缩减为4位 +- 序列号由原来的16位缩减为3位 + +## Radix62CosIdGenerator + +使用62进制格式化的*CosId生成器* + +`[timestamp(44)]-[machineId-(20)]-[sequence-(16)] = 80 BITS = 15 CHARS=[timestamp(9)]-[machineId(4)]-[sequence(4)]` + +- 时间戳由原来的44位缩减为9位 +- 机器号由原来20位缩减为4位 +- 序列号由原来的16位缩减为3位 + +## 配置 + +[CosIdGenerator 配置](../reference/config/cosid-generator.md) diff --git a/documentation/docs/guide/cosid-proxy.md b/documentation/docs/guide/cosid-proxy.md new file mode 100644 index 0000000000..4b6c3d9112 --- /dev/null +++ b/documentation/docs/guide/cosid-proxy.md @@ -0,0 +1,6 @@ +# CosId Proxy 模块 + +
+ +![CosId Proxy](../public/assets/design/CosId-Proxy.png) +
\ No newline at end of file diff --git a/documentation/docs/guide/extensions/cosid-activiti.md b/documentation/docs/guide/extensions/cosid-activiti.md new file mode 100644 index 0000000000..3d883f4586 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-activiti.md @@ -0,0 +1,28 @@ +# CosId-Activiti 模块 + +_Activiti 模块_ 为 `Activiti` 提供了 `CosId` 的支持,实现了 _Activiti_ 的 `org.activiti.engine.impl.cfg.IdGenerator` 接口。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-activiti:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-activiti + ${cosid.version} + + +``` +::: + +## 配置 + +默认情况下 `ActivitiIdGenerator` 将从*ID生成器容器*(`IdGeneratorProvider`)中获取以 `__share__` 为名称的ID生成器。 + +开发者也可以通过配置系统属性 `cosid.activiti` 自定义ID生成器的名称。 + diff --git a/documentation/docs/guide/extensions/cosid-axon.md b/documentation/docs/guide/extensions/cosid-axon.md new file mode 100644 index 0000000000..464f629d4f --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-axon.md @@ -0,0 +1,28 @@ +# CosId-Axon 模块 + +_Axon 模块_ 为 `Axon-Framework` 提供了 `CosId` 的支持,实现了 _Axon-Framework_ 的 `org.axonframework.common.IdentifierFactory` 接口。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-axon:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-axon + ${cosid.version} + + +``` +::: + +## 配置 + +默认情况下 `CosIdIdentifierFactory` 将从*ID生成器容器*(`IdGeneratorProvider`)中获取以 `__share__` 为名称的ID生成器。 + +开发者也可以通过配置系统属性 `cosid.axon` 自定义ID生成器的名称。 + diff --git a/documentation/docs/guide/extensions/cosid-flowable.md b/documentation/docs/guide/extensions/cosid-flowable.md new file mode 100644 index 0000000000..bf701a6d7d --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-flowable.md @@ -0,0 +1,29 @@ +# CosId-Flowable 模块 + +_Flowable 模块_ 为 `Flowable` 提供了 `CosId` 的支持,实现了 _Flowable_ 的 `org.flowable.common.engine.impl.cfg.IdGenerator` 接口。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-flowable:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-flowable + ${cosid.version} + + +``` +::: + +## 配置 + +默认情况下 `FlowableIdGenerator` 将从*ID生成器容器*(`IdGeneratorProvider`)中获取以 `__share__` 为名称的ID生成器。 + +开发者也可以通过配置系统属性 `cosid.flowable` 自定义ID生成器的名称。 + + diff --git a/documentation/docs/guide/extensions/cosid-jackson.md b/documentation/docs/guide/extensions/cosid-jackson.md new file mode 100644 index 0000000000..92b8dc8e94 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-jackson.md @@ -0,0 +1,71 @@ +# CosId-Jackson 模块 + +**Jackson** 序列化/反序列化注解插件,相当于隔离了应用API边界内外的 *ID* 使用方式,应用内部使用 `long`、外部使用 `String`,做到了应用无侵入,无感知。 + +::: danger JavaScript Number 溢出问题 + +`JavaScript` 的 `Number.MAX_SAFE_INTEGER` 只有**53-bit**,如果直接将63位的 `SnowflakeId` 返回给前端,那么会产生值溢出的情况(所以这里我们应该知道后端传给前端的 `long` 值溢出问题,迟早会出现,只不过`SnowflakeId`出现得更快而已)。 +很显然溢出是不能被接受的,一般可以使用以下处理方案: + +- 直接将 `long` 转换成 `String` (`@AsString(AsString.Type.TO_STRING)`) +- 使用 `SnowflakeFriendlyId` 将 `SnowflakeId` 转换成比较友好的字符串表示:`{timestamp}-{machineId}-{sequence} -> 20210623131730192-1-0` (`@AsString(AsString.Type.FRIENDLY_ID)`) +- 自定义 `SnowflakeId` 位分配来缩短 `SnowflakeId` 的位数(**53-bit**)使 **ID** 提供给前端时不溢出(`SafeJavaScriptSnowflakeId`) +- 使用 `Radix62IdConverter` 转换 `long` 类型的 **ID**,并且压缩字符串。(`@AsString(AsString.Type.RADIX)`) + +[cosid-jackson](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-jackson) 模块为提供上述方案提供了最小的侵入性。 +::: + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-jackson:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-jackson + ${cosid.version} + + +``` +::: + +## 使用 + +```java +public class AsStringDto { + + @AsString + private Long id; + + @AsString(AsString.Type.RADIX) + private Long radixId; + + @AsString(value = AsString.Type.RADIX, radixPadStart = true) + private Long radixPadStartId; + + @AsString(value = AsString.Type.RADIX, radixPadStart = true, radixCharSize = 10) + private Long radixPadStartCharSize10Id; + + @AsString(AsString.Type.FRIENDLY_ID) + private long friendlyId; + + // getter / setter +} +``` + +**序列化结果** + +```json +{ + "id": "266300479548424192", + "radixId": "JferHIEYZk", + "radixPadStartId": "0JferHIEYZk", + "radixPadStartCharSize10Id": "JferHIEYZk", + "friendlyId": "20211228202301948-0-0" +} +``` + diff --git a/documentation/docs/guide/extensions/cosid-jdbc.md b/documentation/docs/guide/extensions/cosid-jdbc.md new file mode 100644 index 0000000000..3745943fb3 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-jdbc.md @@ -0,0 +1,83 @@ +# CosId-Jdbc 模块 + +[cosid-jdbc](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-jdbc) 提供 **关系型数据库** 的支持。实现了: + +- `MachineIdDistributor`:作为**雪花算法**(`SnowflakeId`)的机器号分配器 (`MachineIdDistributor`)。 +- `IdSegmentDistributor`:作为**号段算法**(`SegmentId`)的号段分发器 (`IdSegmentDistributor`)。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("org.springframework.boot:spring-boot-starter-data-jdbc") + implementation("me.ahoo.cosid:cosid-jdbc:${cosidVersion}") +``` +```xml [Maven] + + + org.springframework.boot + spring-boot-starter-data-jdbc + ${springboot.version} + + + me.ahoo.cosid + cosid-jdbc + ${cosid.version} + + +``` +::: + +### 创建 `cosid` 表 + +`cosid` 表作为号段分发器的号段分发记录表。 + +```sql +create table if not exists cosid +( + name varchar(100) not null comment '{namespace}.{name}', + last_max_id bigint not null default 0, + last_fetch_time bigint not null, + constraint cosid_pk + primary key (name) +) engine = InnoDB; +``` + +### 创建 `cosid_machine` 表 + +```sql +create table if not exists cosid_machine +( + name varchar(100) not null comment '{namespace}.{machine_id}', + namespace varchar(100) not null, + machine_id integer not null default 0, + last_timestamp bigint not null default 0, + instance_id varchar(100) not null default '', + distribute_time bigint not null default 0, + revert_time bigint not null default 0, + constraint cosid_machine_pk + primary key (name) +) engine = InnoDB; + +create index if not exists idx_namespace on cosid_machine (namespace); +create index if not exists idx_instance_id on cosid_machine (instance_id); +``` + +## 配置案例 + +```yaml {4,10,14} +spring: + datasource: + url: # Jdbc 分发器直接依赖 DataSource +cosid: + namespace: ${spring.application.name} + machine: + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + distributor: + type: jdbc + segment: + enabled: true # 可选,当需要使用号段算法时,需要设置为 true + distributor: + type: jdbc +``` \ No newline at end of file diff --git a/documentation/docs/guide/extensions/cosid-mongo.md b/documentation/docs/guide/extensions/cosid-mongo.md new file mode 100644 index 0000000000..859a610644 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-mongo.md @@ -0,0 +1,49 @@ +# CosId-Mongo 模块 + +[cosid-mongo](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-mongo) 提供 **MongoDB** 的支持。实现了: + +- `MachineIdDistributor`:作为**雪花算法**(`SnowflakeId`)的机器号分配器 (`MachineIdDistributor`)。 +- `IdSegmentDistributor`:作为**号段算法**(`SegmentId`)的号段分发器 (`IdSegmentDistributor`)。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("org.springframework.boot:spring-boot-starter-data-mongodb") + implementation("me.ahoo.cosid:cosid-mongo:${cosidVersion}") +``` +```xml [Maven] + + + org.springframework.boot + spring-boot-starter-data-mongodb + ${springboot.version} + + + me.ahoo.cosid + cosid-mongo + ${cosid.version} + + +``` +::: + +## 配置案例 + +```yaml {4,10,14} +spring: + data: + mongodb: + uri: # Mongo 分发器直接依赖 spring-data-mongodb,这样可以省去额外的配置。 +cosid: + namespace: ${spring.application.name} + machine: + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + distributor: + type: mongo + segment: + enabled: true # 可选,当需要使用号段算法时,需要设置为 true + distributor: + type: mongo +``` \ No newline at end of file diff --git a/documentation/docs/guide/extensions/cosid-mybatis.md b/documentation/docs/guide/extensions/cosid-mybatis.md new file mode 100644 index 0000000000..d2daf05691 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-mybatis.md @@ -0,0 +1,77 @@ +# CosId-MyBatis 模块 + +[cosid-mybatis](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-mybatis) 拦截**MyBatis**插入(`Insert`)请求,并解析 `@CosId` 注入**分布式ID**。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-mybatis:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-mybatis + ${cosid.version} + + +``` +::: + +## 使用 + +```java +public class Order { + + @CosId(value = "order") + private Long orderId; + private Long userId; + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } +} +``` + +```java +@Mapper +public interface OrderRepository { + @Insert("insert into t_table (id) value (#{id});") + void insert(LongIdEntity order); + + @Insert({ + ""}) + void insertList(List list); +} +``` + +```java + LongIdEntity entity=new LongIdEntity(); + entityRepository.insert(entity); + /** + * { + * "id": 208796080181248 + * } + */ + return entity; +``` diff --git a/documentation/docs/guide/extensions/cosid-redis.md b/documentation/docs/guide/extensions/cosid-redis.md new file mode 100644 index 0000000000..113c0dfd92 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-redis.md @@ -0,0 +1,53 @@ +# CosId-Redis 模块 + +[cosid-spring-redis](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-spring-redis) 模块提供 *Redis* 的支持。实现了: + +- `MachineIdDistributor`:作为**雪花算法**(`SnowflakeId`)的机器号分配器 (`MachineIdDistributor`)。 +- `IdSegmentDistributor`:作为**号段算法**(`SegmentId`)的号段分发器 (`IdSegmentDistributor`)。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("me.ahoo.cosid:cosid-spring-redis:${cosidVersion}") +``` +```xml [Maven] + + + org.springframework.boot + spring-boot-starter-data-redis + ${springboot.version} + + + me.ahoo.cosid + cosid-spring-redis + ${cosid.version} + + +``` +::: + +## 配置案例 + +[CosId-Example-Redis](https://github.com/Ahoo-Wang/CosId/tree/main/examples/cosid-example-redis) + +```yaml {4,10,14} +spring: + data: + redis: + host: localhost # Redis 分发器直接依赖 spring-data-redis,这样可以省去额外的配置。 +cosid: + namespace: ${spring.application.name} + machine: + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + distributor: + type: redis + segment: + enabled: true # 可选,当需要使用号段算法时,需要设置为 true + distributor: + type: redis +``` + + diff --git a/documentation/docs/guide/extensions/cosid-shardingsphere.md b/documentation/docs/guide/extensions/cosid-shardingsphere.md new file mode 100644 index 0000000000..1d7473a8a5 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-shardingsphere.md @@ -0,0 +1,98 @@ +# CosId-ShardingSphere 模块 + +::: tip 维护说明 +`CosIdKeyGenerateAlgorithm`、`CosIdModShardingAlgorithm`、`CosIdIntervalShardingAlgorithm` 已合并至 [ShardingSphere](https://github.com/apache/shardingsphere/pull/14132) 官方,当前该模块的维护可能会以官方为主。 +::: + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-shardingsphere:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-shardingsphere + ${cosid.version} + + +``` +::: + +## 分布式主键 + +```yaml +spring: + shardingsphere: + rules: + sharding: + key-generators: + cosid: + type: COSID + props: + id-name: __share__ +``` + +## 基于间隔的时间范围分片算法 + +

+ CosIdIntervalShardingAlgorithm +

+ +- 易用性: 支持多种数据类型 (`Long`/`LocalDateTime`/`DATE`/ `String` / `SnowflakeId`),而官方实现是先转换成字符串再转换成`LocalDateTime`,转换成功率受时间格式化字符影响。 +- 性能 : 相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm` 性能高出 *1200~4000* 倍。 + +| **PreciseShardingValue** | **RangeShardingValue** | +|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| | | + +- CosIdIntervalShardingAlgorithm + - type: COSID_INTERVAL +- SnowflakeIntervalShardingAlgorithm + - type: COSID_INTERVAL_SNOWFLAKE + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_INTERVAL_{type_suffix} + props: + logic-name-prefix: logic-name-prefix + id-name: cosid-name + datetime-lower: 2021-12-08 22:00:00 + datetime-upper: 2022-12-01 00:00:00 + sharding-suffix-pattern: yyyyMM + datetime-interval-unit: MONTHS + datetime-interval-amount: 1 +``` + +## 取模分片算法 + +

+ CosIdModShardingAlgorithm +

+ +- 性能 : 相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.mod.ModShardingAlgorithm` 性能高出 *1200~4000* 倍。并且稳定性更高,不会出现严重的性能退化。 + +| **PreciseShardingValue** | **RangeShardingValue** | +|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| | | + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_MOD + props: + mod: 4 + logic-name-prefix: t_table_ +``` diff --git a/documentation/docs/guide/extensions/cosid-spring-boot-starter.md b/documentation/docs/guide/extensions/cosid-spring-boot-starter.md new file mode 100644 index 0000000000..edfa200b34 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-spring-boot-starter.md @@ -0,0 +1,110 @@ +# CosId-Spring-Boot-Starter 模块 + +_Spring-Boot-Starter_ 模块 集成了所有 _CosId_ 扩展,提供了自动装配的能力,使 _CosId_ 框架在 _Spring Boot_ 项目中更加便捷地使用。 + +::: tip +该模块的配置文档请参考 [配置](../../reference/config/basic)。 +::: + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-spring-boot-starter:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-spring-boot-starter + ${cosid.version} + + +``` +::: + +## Actuate + +CosId-Spring-Boot-Starter 模块提供了 actuator 支持,可以通过 actuator 端点查看 CosId 的状态。 + +![CosId actuator](../../public/assets/spring-boot-starter/swagger-ui.png) + +### CosIdEndpoint + +用于查看所有定义的 Id 生成器状态信息。 + +:::code-group +```shell [curl] +curl -X GET "http://localhost:8080/actuator/cosid" -H "accept: */*" +``` +```json [响应内容] +{ + "cosid": { + "kind": "ClockSyncCosIdGenerator", + "actual": { + "kind": "Radix62CosIdGenerator", + "machineId": 252, + "lastTimestamp": 1704183358593, + "converter": { + "kind": "RadixCosIdStateParser", + "actual": null + } + }, + "converter": { + "kind": "RadixCosIdStateParser", + "actual": null + } + }, + "user": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1703401907, + "maxId": 91658, + "offset": 91638, + "sequence": 91638, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854776000 + } + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 6, + "padStart": true, + "maxId": 56800235584 + } + } +} +``` +::: + +### CosIdGeneratorEndpoint + +提供了生成 `long` 类型 ID 的 API 接口。 + +### CosIdStringGeneratorEndpoint + +提供了生成 `string` 类型 ID 的 API 接口。 + +### 配置 + +```yaml {6-8} +management: + endpoints: + web: + exposure: + include: + - cosid + - cosidGenerator + - cosidStringGenerator +``` \ No newline at end of file diff --git a/documentation/docs/guide/extensions/cosid-spring-data-jdbc.md b/documentation/docs/guide/extensions/cosid-spring-data-jdbc.md new file mode 100644 index 0000000000..071ddcbcc0 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-spring-data-jdbc.md @@ -0,0 +1,71 @@ +# CosId-Spring-Data-Jdbc 模块 + +[cosid-spring-data-jdbc](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-spring-data-jdbc) 模块提供了对 `org.springframework.data.annotation.Id` 注解的支持,支持自动注入**分布式ID**。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-spring-data-jdbc:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-spring-data-jdbc + ${cosid.version} + + +``` +::: + +## 使用 + +::: code-group +```java [@Id] + static class IdEntity { + @Id + private long id; + + public long getId() { + return id; + } + + public IdEntity setId(int id) { + this.id = id; + return this; + } + } +``` +```java [@CosId] + static class IdEntity { + @CosId + private long id; + + public long getId() { + return id; + } + + public IdEntity setId(int id) { + this.id = id; + return this; + } + } +``` +```java [named 'id'] + static class NamedIdEntity { + + private long id; + + public long getId() { + return id; + } + + public NotFoundEntity setId(int id) { + this.id = id; + return this; + } + } +``` +::: diff --git a/documentation/docs/guide/extensions/cosid-test.md b/documentation/docs/guide/extensions/cosid-test.md new file mode 100644 index 0000000000..d5f3d0fc4d --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-test.md @@ -0,0 +1,82 @@ +# 兼容性测试套件 + +兼容性测试套件是一组用于验证特定接口实现是否符合规范的测试用例。 + +通过 _cosid-test 模块_,为自定义扩展提供了便捷和正确性保障。 +这种标准化验证方式不仅简化了扩展开发,降低了潜在错误风险,还确保了整个生态系统的一致性和稳定性。 + + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + testImplementation("me.ahoo.cosid:cosid-test:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-test + ${cosid.version} + test + + +``` +::: + +## Redis 扩展案例 + +### MachineIdDistributor + +```java +class SpringRedisMachineIdDistributorTest extends MachineIdDistributorSpec { + StringRedisTemplate stringRedisTemplate; + + @BeforeEach + void setup() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration); + lettuceConnectionFactory.afterPropertiesSet(); + stringRedisTemplate = new StringRedisTemplate(lettuceConnectionFactory); + } + + @Override + protected MachineIdDistributor getDistributor() { + return new SpringRedisMachineIdDistributor(stringRedisTemplate, MachineStateStorage.IN_MEMORY, ClockBackwardsSynchronizer.DEFAULT); + } + +} +``` + +### IdSegmentDistributor + +```java +class SpringRedisIdSegmentDistributorTest extends IdSegmentDistributorSpec { + StringRedisTemplate stringRedisTemplate; + SpringRedisIdSegmentDistributorFactory distributorFactory; + protected IdSegmentDistributorDefinition idSegmentDistributorDefinition; + + @BeforeEach + void setup() { + RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); + LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration); + lettuceConnectionFactory.afterPropertiesSet(); + stringRedisTemplate = new StringRedisTemplate(lettuceConnectionFactory); + distributorFactory = new SpringRedisIdSegmentDistributorFactory(stringRedisTemplate); + idSegmentDistributorDefinition = new IdSegmentDistributorDefinition("SpringRedisIdSegmentDistributorTest", MockIdGenerator.INSTANCE.generateAsString(), 0, 100); + } + + + @Override + protected IdSegmentDistributorFactory getFactory() { + return distributorFactory; + } + + @Override + protected void setMaxIdBack(T distributor, long maxId) { + String adderKey = ((SpringRedisIdSegmentDistributor) distributor).getAdderKey(); + stringRedisTemplate.opsForValue().set(adderKey, String.valueOf(maxId - 1)); + } +} +``` \ No newline at end of file diff --git a/documentation/docs/guide/extensions/cosid-zookeeper.md b/documentation/docs/guide/extensions/cosid-zookeeper.md new file mode 100644 index 0000000000..7f31ee0285 --- /dev/null +++ b/documentation/docs/guide/extensions/cosid-zookeeper.md @@ -0,0 +1,67 @@ +# CosId-ZooKeeper 模块 + +[cosid-zookeeper](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-zookeeper) 模块提供 *ZooKeeper* 的支持。实现了: + +- `MachineIdDistributor`:作为**雪花算法**(`SnowflakeId`)的机器号分配器 (`MachineIdDistributor`)。 +- `IdSegmentDistributor`:作为**号段算法**(`SegmentId`)的号段分发器 (`IdSegmentDistributor`)。 + +## 安装 + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("me.ahoo.cosid:cosid-zookeeper:${cosidVersion}") +``` +```xml [Maven] + + + me.ahoo.cosid + cosid-zookeeper + ${cosid.version} + + +``` +::: + +## 配置 + +- 配置类:[CosIdZookeeperProperties](https://github.com/Ahoo-Wang/CosId/blob/main/cosid-spring-boot-starter/src/main/java/me/ahoo/cosid/spring/boot/starter/zookeeper/CosIdZookeeperProperties.java) +- 前缀:`cosid.zookeeper.` + +| 名称 | 数据类型 | 说明 | 默认值 | +|----------------------------|------------|-----------------|--------------------------| +| enabled | `boolean` | 是否开启*ZooKeeper* | true | +| connect-string | `String` | 链接字符串 | `localhost:2181` | +| block-until-connected-wait | `Duration` | 阻塞直到客户端已连接等待时间 | `Duration.ofSeconds(10)` | +| session-timeout | `Duration` | 会话超时时间 | `Duration.ofSeconds(60` | +| connection-timeout | `Duration` | 连接超时时间 | `Duration.ofSeconds(15)` | +| retry | `Retry` | 重试策略配置 | | + +### Retry (`ExponentialBackoffRetry`) 配置 + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------------|-------|-------------------|-------| +| baseSleepTimeMs | `int` | 重试之间等待的初始时间量 (毫秒) | `100` | +| maxRetries | `int` | 最大重试次数 | `5` | +| maxSleepMs | `int` | 每次重试时的最大睡眠时间(毫秒) | `500` | + +### 配置案例 + +[CosId-Example-Zookeeper](https://github.com/Ahoo-Wang/CosId/tree/main/examples/cosid-example-zookeeper) + +```yaml {2-8,11,14} +cosid: + zookeeper: + connect-string: localhost:2181 + retry: + base-sleep-time-ms: 100 + max-retries: 5 + max-sleep-ms: 500 + block-until-connected-wait: 10s + segment: + distributor: + type: zookeeper + machine: + distributor: + type: zookeeper +``` diff --git a/documentation/docs/guide/faq/Performance-CosId-Leaf.md b/documentation/docs/guide/faq/Performance-CosId-Leaf.md new file mode 100644 index 0000000000..8c4b88d8b3 --- /dev/null +++ b/documentation/docs/guide/faq/Performance-CosId-Leaf.md @@ -0,0 +1,63 @@ +# 分布式ID性能评测:CosId VS 美团 Leaf + +## 环境 + +- MacBook Pro (M1) +- JDK 17 +- JMH 1.36 +- 运行在本机 Docker 内的 mariadb:10.6.4 + +## 运行 + +> 基准测试代码: [cosid-benchmark](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-benchmark) + +``` shell +git clone git@github.com:Ahoo-Wang/CosId.git +cd cosid-benchmark +``` + +::: code-group +```shell [Gradle] +./gradlew jmh +``` +```shell [Java] +gradle jmhJar +java -jar build/libs/cosid-benchmark-2.2.6-jmh.jar -wi 1 -rf json -f 1 +``` +::: + + +## 报告 + +``` +# JMH version: 1.36 +# VM version: JDK 17.0.7, OpenJDK 64-Bit Server VM, 17.0.7+7-LTS +# Warmup: 1 iterations, 10 s each +# Measurement: 1 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations + +Benchmark (step) Mode Cnt Score Error Units +AtomicLongBenchmark.generate N/A thrpt 142725210.565 ops/s +CosIdBenchmark.generate 1 thrpt 131920684.604 ops/s +CosIdBenchmark.generate 100 thrpt 132113994.232 ops/s +CosIdBenchmark.generate 1000 thrpt 130281016.155 ops/s +LeafBenchmark.generate 1 thrpt 25787669.815 ops/s +LeafBenchmark.generate 100 thrpt 23897328.183 ops/s +LeafBenchmark.generate 1000 thrpt 23550106.538 ops/s +``` + +

+ CosId VS 美团 Leaf +

+ +> GitHub Action 环境测试报告: [Performance: CosId vs Leaf](https://github.com/Ahoo-Wang/CosId/issues/22) +> +> 因受到 GitHub Runner 资源限制,运行在 GitHub Runner 中的基准测试与真实环境基准测试对比有非常大的差距(近2倍), +但是对于运行在同一环境配置资源情况下(都运行在 GitHub Runner),进行 commit 前后的基准对比、以及第三方库的对比依然是有价值的。 + +## 结论 + +1. CosId (`SegmentChainId`) 性能是 Leaf (`segment`) 的 5 倍。 +2. CosId 、Leaf 的性能与号段步长(Step) 无关。 +3. CosId TPS 基本接近 `AtomicLong` 。 \ No newline at end of file diff --git a/documentation/docs/guide/faq/faq.md b/documentation/docs/guide/faq/faq.md new file mode 100644 index 0000000000..53f6c14fe5 --- /dev/null +++ b/documentation/docs/guide/faq/faq.md @@ -0,0 +1,22 @@ +# 常见问题 + +## CosId 需要部署服务端吗? + +虽然并没有规定 [CosId](https://github.com/Ahoo-Wang/CosId) 的使用方式,但是强烈推荐以本地 SDK 的方式使用,用户只需要安装一下 **CosId** 的依赖包做一些简单配置( [DEMO](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-example) ) 即可。 + +:::tip +分布式ID是不适合使用服务端部署模式的(C/S)。使用服务端部署模式,必然会产生网络IO(*Client*通过远程过程调用*Server*,获取ID),你想想我们费了那么大劲消除网络IO是为了什么? +::: + +## PrefetchWorker 是如何维护安全距离的? + +- 定时维护:每隔一段时间**PrefetchWorker**会主动检测安全距离是否满足配置要求,如果不满足则执行`NextMaxId`预取,保证安全距离。 +- 被动饥饿唤醒:当获取ID的线程获取ID时没有可用号段,会尝试获取新的号段,并主动唤醒**PrefetchWorker**并告诉他你太慢了,被唤醒的**PrefetchWorker**会检测安全距离是否需要膨胀,然后进行安全距离的维护。 + +## 本机单调、全局趋势递增-为什么还要尽可能保证单调递增? + +从上文的论述中我们不难理解本机单调递增,全局趋势递增是权衡后的设计结果。 +但是全局趋势递增的背面是周期内ID乱序,所以尽可能向单调递增优化(降低ID乱序程度)是优化目标,这俩点并不冲突。 + +如果各位同学还有其他问题请至 [Issues](https://github.com/Ahoo-Wang/CosId/issues) 提交你的疑问。 + diff --git a/documentation/docs/guide/faq/perf-test.md b/documentation/docs/guide/faq/perf-test.md new file mode 100644 index 0000000000..2503e95b58 --- /dev/null +++ b/documentation/docs/guide/faq/perf-test.md @@ -0,0 +1,201 @@ +# JMH-Benchmark + +## 运行环境说明 + +- 基准测试运行环境:笔记本开发机 ( MacBook Pro (M1) ) +- 所有基准测试都在开发笔记本上执行。 +- **Redis**、**MySql** 部署环境也在该笔记本开发机上。 + +## SegmentChainId + +### 吞吐量 (ops/s) + +

+ Throughput-Of-SegmentChainId +

+ +::: code-group +```shell [Gradle] +gradle cosid-redis:jmh +``` +```shell [Java] +java -jar cosid-redis/build/libs/cosid-redis-1.8.6-jmh.jar -bm thrpt -wi 1 -rf json -f 1 RedisChainIdBenchmark +``` +::: + +``` +Benchmark (step) Mode Cnt Score Error Units +RedisChainIdBenchmark.generate 1 thrpt 5 106188349.580 ± 26035022.285 ops/s +RedisChainIdBenchmark.generate 100 thrpt 5 112276460.950 ± 4091990.852 ops/s +RedisChainIdBenchmark.generate 1000 thrpt 5 110181522.770 ± 15531341.449 ops/s +``` + +::: code-group +```shell [Gradle] +gradle cosid-jdbc:jmh +``` +```shell [Java] +java -jar cosid-jdbc/build/libs/cosid-jdbc-1.8.6-jmh.jar -bm thrpt -wi 1 -rf json -f 1 MySqlChainIdBenchmark +``` +::: + + +``` +Benchmark (step) Mode Cnt Score Error Units +MySqlChainIdBenchmark.generate 1 thrpt 5 110020245.619 ± 4514432.472 ops/s +MySqlChainIdBenchmark.generate 100 thrpt 5 111589201.024 ± 1565714.192 ops/s +MySqlChainIdBenchmark.generate 1000 thrpt 5 115287146.614 ± 4471990.880 ops/s +``` + +### 每次操作耗时的百分位数(us/op) + +> [百分位数](https://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E4%BD%8D%E6%95%B0) ,统计学术语,若将一组数据从小到大排序,并计算相应的累计百分点,则某百分点所对应数据的值,就称为这百分点的百分位数,以Pk表示第k百分位数。百分位数是用来比较个体在群体中的相对地位量数。 + +

+ Percentile-Sample-Of-SegmentChainId +

+ +```shell +java -jar cosid-redis/build/libs/cosid-redis-1.8.6-jmh.jar -bm sample -wi 1 -rf json -f 1 -tu us step_1000 +``` + +``` +Benchmark Mode Cnt Score Error Units +RedisChainIdBenchmark.step_1000 sample 1336271 0.024 ± 0.001 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.00 sample ≈ 0 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.50 sample 0.041 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.90 sample 0.042 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.95 sample 0.042 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.99 sample 0.042 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.999 sample 0.042 us/op +RedisChainIdBenchmark.step_1000:step_1000·p0.9999 sample 0.208 us/op +RedisChainIdBenchmark.step_1000:step_1000·p1.00 sample 37.440 us/op +``` + +```shell +java -jar cosid-jdbc/build/libs/cosid-jdbc-1.8.6-jmh.jar -bm sample -wi 1 -rf json -f 1 -tu us step_1000 +``` + +``` +Benchmark Mode Cnt Score Error Units +MySqlChainIdBenchmark.step_1000 sample 1286774 0.024 ± 0.001 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.00 sample ≈ 0 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.50 sample 0.041 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.90 sample 0.042 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.95 sample 0.042 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.99 sample 0.042 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.999 sample 0.083 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p0.9999 sample 0.208 us/op +MySqlChainIdBenchmark.step_1000:step_1000·p1.00 sample 342.528 us/op +``` + +## SnowflakeId + +::: code-group +```shell [Gradle] +gradle cosid-core:jmh +``` +```shell [Java] +java -jar cosid-core/build/libs/cosid-core-1.8.6-jmh.jar -bm thrpt -wi 1 -rf json -f 1 +``` +::: + +``` +Benchmark Mode Cnt Score Error Units +SnowflakeIdBenchmark.millisecondSnowflakeId_friendlyId thrpt 4020311.665 ops/s +SnowflakeIdBenchmark.millisecondSnowflakeId_generate thrpt 4095403.859 ops/s +SnowflakeIdBenchmark.safeJsMillisecondSnowflakeId_generate thrpt 511654.048 ops/s +SnowflakeIdBenchmark.safeJsSecondSnowflakeId_generate thrpt 539818.563 ops/s +SnowflakeIdBenchmark.secondSnowflakeId_generate thrpt 4206843.941 ops/s +``` + +## CosIdIntervalShardingAlgorithm + +| **PreciseShardingValue** | **RangeShardingValue** | +|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| | | + + +``` shell +gradle cosid-shardingsphere:jmh +``` + +``` +# JMH version: 1.29 +# VM version: JDK 11.0.13, OpenJDK 64-Bit Server VM, 11.0.13+8-LTS +# VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/work/CosId/cosid-shardingsphere/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant +# Blackhole mode: full + dont-inline hint +# Warmup: 1 iterations, 10 s each +# Measurement: 1 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +Benchmark (days) Mode Cnt Score Error Units +IntervalShardingAlgorithmBenchmark.cosid_precise_local_date_time 10 thrpt 53279788.772 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_local_date_time 100 thrpt 38114729.365 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_local_date_time 1000 thrpt 32714318.129 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_local_date_time 10000 thrpt 22317905.643 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_timestamp 10 thrpt 20028091.211 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_timestamp 100 thrpt 19272744.794 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_timestamp 1000 thrpt 17814417.856 ops/s +IntervalShardingAlgorithmBenchmark.cosid_precise_timestamp 10000 thrpt 12384788.025 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_local_date_time 10 thrpt 18716732.080 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_local_date_time 100 thrpt 8436553.492 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_local_date_time 1000 thrpt 1655952.254 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_local_date_time 10000 thrpt 185348.831 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_timestamp 10 thrpt 9410931.643 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_timestamp 100 thrpt 5792861.181 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_timestamp 1000 thrpt 1585344.761 ops/s +IntervalShardingAlgorithmBenchmark.cosid_range_timestamp 10000 thrpt 196663.812 ops/s +IntervalShardingAlgorithmBenchmark.office_precise_timestamp 10 thrpt 72189.800 ops/s +IntervalShardingAlgorithmBenchmark.office_precise_timestamp 100 thrpt 11245.324 ops/s +IntervalShardingAlgorithmBenchmark.office_precise_timestamp 1000 thrpt 1339.128 ops/s +IntervalShardingAlgorithmBenchmark.office_precise_timestamp 10000 thrpt 113.396 ops/s +IntervalShardingAlgorithmBenchmark.office_range_timestamp 10 thrpt 64679.422 ops/s +IntervalShardingAlgorithmBenchmark.office_range_timestamp 100 thrpt 4267.860 ops/s +IntervalShardingAlgorithmBenchmark.office_range_timestamp 1000 thrpt 227.817 ops/s +IntervalShardingAlgorithmBenchmark.office_range_timestamp 10000 thrpt 7.579 ops/s +``` + +## CosIdModShardingAlgorithm + +| **PreciseShardingValue** | **RangeShardingValue** | +|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| | | + +``` shell +gradle cosid-shardingsphere:jmh +``` + +``` +# JMH version: 1.29 +# VM version: JDK 11.0.13, OpenJDK 64-Bit Server VM, 11.0.13+8-LTS +# VM options: -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/work/CosId/cosid-shardingsphere/build/tmp/jmh -Duser.country=CN -Duser.language=zh -Duser.variant +# Blackhole mode: full + dont-inline hint +# Warmup: 1 iterations, 10 s each +# Measurement: 1 iterations, 10 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +Benchmark (divisor) Mode Cnt Score Error Units +ModShardingAlgorithmBenchmark.cosid_precise 10 thrpt 121431137.111 ops/s +ModShardingAlgorithmBenchmark.cosid_precise 100 thrpt 119947284.141 ops/s +ModShardingAlgorithmBenchmark.cosid_precise 1000 thrpt 113095657.321 ops/s +ModShardingAlgorithmBenchmark.cosid_precise 10000 thrpt 108435323.537 ops/s +ModShardingAlgorithmBenchmark.cosid_precise 100000 thrpt 84657505.579 ops/s +ModShardingAlgorithmBenchmark.cosid_range 10 thrpt 37397323.508 ops/s +ModShardingAlgorithmBenchmark.cosid_range 100 thrpt 16905691.783 ops/s +ModShardingAlgorithmBenchmark.cosid_range 1000 thrpt 2969820.981 ops/s +ModShardingAlgorithmBenchmark.cosid_range 10000 thrpt 312881.488 ops/s +ModShardingAlgorithmBenchmark.cosid_range 100000 thrpt 31581.396 ops/s +ModShardingAlgorithmBenchmark.office_precise 10 thrpt 9135460.160 ops/s +ModShardingAlgorithmBenchmark.office_precise 100 thrpt 1356582.418 ops/s +ModShardingAlgorithmBenchmark.office_precise 1000 thrpt 104500.125 ops/s +ModShardingAlgorithmBenchmark.office_precise 10000 thrpt 8619.933 ops/s +ModShardingAlgorithmBenchmark.office_precise 100000 thrpt 629.353 ops/s +ModShardingAlgorithmBenchmark.office_range 10 thrpt 5535645.737 ops/s +ModShardingAlgorithmBenchmark.office_range 100 thrpt 83271.925 ops/s +ModShardingAlgorithmBenchmark.office_range 1000 thrpt 911.534 ops/s +ModShardingAlgorithmBenchmark.office_range 10000 thrpt 9.133 ops/s +ModShardingAlgorithmBenchmark.office_range 100000 thrpt 0.208 ops/s +``` diff --git a/documentation/docs/guide/faq/perf-vs.md b/documentation/docs/guide/faq/perf-vs.md new file mode 100644 index 0000000000..7ffaee26cd --- /dev/null +++ b/documentation/docs/guide/faq/perf-vs.md @@ -0,0 +1,51 @@ +# 性能对比 + +TODO + +## 分布式ID方案的核心指标 + +- **全局(相同业务)唯一性**:唯一性保证是**ID**的必要条件,假设ID不唯一就会产生主键冲突,这点很容易可以理解。 + - 通常所说的全局唯一性并不是指所有业务服务都要唯一,而是相同业务服务不同部署副本唯一。 + 比如 Order 服务的多个部署副本在生成`t_order`这张表的`Id`时是要求全局唯一的。至于`t_order_item`生成的`ID`与`t_order`是否唯一,并不影响唯一性约束,也不会产生什么副作用。 + 不同业务模块间也是同理。即唯一性主要解决的是ID冲突问题。 +- **有序性**:有序性保证是面向查询的数据结构算法(除了Hash算法)所必须的,是**二分查找法**(分而治之)的前提。 + - MySq-InnoDB B+树是使用最为广泛的,假设 Id 是无序的,B+ 树 为了维护 ID 的有序性,就会频繁的在索引的中间位置插入而挪动后面节点的位置,甚至导致频繁的页分裂,这对于性能的影响是极大的。那么如果我们能够保证ID的有序性这种情况就完全不同了,只需要进行追加写操作。所以 ID 的有序性是非常重要的,也是ID设计不可避免的特性。 +- **吞吐量/性能(ops/time)**:即单位时间(每秒)能产生的ID数量。生成ID是非常高频的操作,也是最为基本的。假设ID生成的性能缓慢,那么不管怎么进行系统优化也无法获得更好的性能。 + - 一般我们会首先生成ID,然后再执行写入操作,假设ID生成缓慢,那么整体性能上限就会受到限制,这一点应该不难理解。 +- **稳定性(time/op)**:稳定性指标一般可以采用**每个操作的时间进行百分位采样**来分析,比如 *[CosId](https://github.com/Ahoo-Wang/CosId)* 百分位采样 **P9999=0.208 us/op**,即 **0% ~ 99.99%** 的单位操作时间小于等于 **0.208 us/op**。 + - [百分位数 WIKI](https://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E4%BD%8D%E6%95%B0) :统计学术语,若将一组数据从小到大排序,并计算相应的累计百分点,则某百分点所对应数据的值,就称为这百分点的百分位数,以Pk表示第k百分位数。百分位数是用来比较个体在群体中的相对地位量数。 + - 为什么不用平均*每个操作的时间*:马老师的身价跟你的身价能平均么?平均后的值有意义不? + - 可以使用最小*每个操作的时间*、最大*每个操作的时间*作为参考吗?因为最小、最大值只说明了零界点的情况,虽说可以作为稳定性的参考,但依然不够全面。而且*百分位数*已经覆盖了这俩个指标。 +- **自治性(依赖)**:主要是指对外部环境有无依赖,比如**号段模式**会强依赖第三方存储中间件来获取`NexMaxId`。自治性还会对可用性造成影响。 +- **可用性**:分布式ID的可用性主要会受到自治性影响,比如**SnowflakeId**会受到时钟回拨影响,导致处于短暂时间的不可用状态。而**号段模式**会受到第三方发号器(`NexMaxId`)的可用性影响。 + - [可用性 WIKI](https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%94%A8%E6%80%A7) :在一个给定的时间间隔内,对于一个功能个体来讲,总的可用时间所占的比例。 + - MTBF:平均故障间隔 + - MDT:平均修复/恢复时间 + - Availability=MTBF/(MTBF+MDT) + - 假设MTBF为1年,MDT为1小时,即`Availability=(365*24)/(365*24+1)=0.999885857778792≈99.99%`,也就是我们通常所说对可用性4个9。 +- **适应性**:是指在面对外部环境变化的自适应能力,这里我们主要说的是面对流量突发时动态伸缩分布式ID的性能, + - **SegmentChainId**可以基于**饥饿状态**进行**安全距离**的动态伸缩。 + - **SnowflakeId**常规位分配方案性能恒定409.6W,虽然可以通过调整位分配方案来获得不同的TPS性能,但是位分配方法的变更是破坏性的,一般根据业务场景确定位分配方案后不再变更。 +- **存储空间**:还是用MySq-InnoDB B+树来举例,普通索引(二级索引)会存储主键值,主键越大占用的内存缓存、磁盘空间也会越大。Page页存储的数据越少,磁盘IO访问的次数会增加。总之在满足业务需求的情况下,尽可能小的存储空间占用在绝大多数场景下都是好的设计原则。 + +## 分布式ID的核心算法 + +## 按位分区算法 (`SnowflakeId`) + +| | 性能(吞吐量) | 稳定性(百分位数) | 自治性(依赖) | 机器号分配器 | 机器号回收 | 使用方式 | +|---------------------------------------------------------|-----------------:|-------------------:|-------------------|----------------------------------|-------|:--------------------| +| [CosId](https://github.com/Ahoo-Wang/CosId) | 4,096,000(ops/s) | P9999=0.244(us/op) | 首次启动,依赖**机器号分配器** | 手动分配器、K8S、关系型数据库、Redis、ZooKeeper | 支持 | SDK(推荐)/RPC/RESTful | +| [Leaf](https://github.com/Meituan-Dianping/Leaf) | | | | ZooKeeper | | | +| [uid-generator](https://github.com/baidu/uid-generator) | | | | 关系型数据库 | | | +| [TinyID](https://github.com/didi/tinyid) | 不支持**按位分区算法** | | | | + +## 号段算法 (`SegmentId`) + +| | 性能(吞吐量) | 稳定性(百分位数) | 自治性(依赖) | 号段分发器 | 适应性 | 存储空间 | 使用方式 | +|---------------------------------------------------------|-------------------:|-------------------:|-------------|------------------------|---------------|--------|---------------------| +| [CosId](https://github.com/Ahoo-Wang/CosId) | 127,439,148(ops/s) | P9999=0.208(us/op) | 依赖**号段分发器** | 关系型数据库、Redis、ZooKeeper | 支持`Step`自动扩缩容 | 64-bit | SDK(推荐)/RPC/RESTful | +| [Leaf](https://github.com/Meituan-Dianping/Leaf) | | | | MySql | | | | +| [uid-generator](https://github.com/baidu/uid-generator) | 不支持**号段算法** | | | | | | | +| [TinyID](https://github.com/didi/tinyid) | | | | 数据库 | | | | + + diff --git a/documentation/docs/guide/getting-started.md b/documentation/docs/guide/getting-started.md new file mode 100644 index 0000000000..5dc45b30e7 --- /dev/null +++ b/documentation/docs/guide/getting-started.md @@ -0,0 +1,96 @@ +# 快速上手 + +## 安装 + +:::tip +开发者可以任选一种的分发器(`Redis`/`JDBC`/`Mongodb`/`Zookeeper`),并引入对应的依赖。 +::: + +接下来以 `Redis` 扩展为例: [CosId-Example-Redis](https://github.com/Ahoo-Wang/CosId/tree/main/examples/cosid-example-redis) + +::: code-group +```kotlin [Gradle(Kotlin)] + val cosidVersion = "latestVersion" + implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("me.ahoo.cosid:cosid-spring-redis:${cosidVersion}") + implementation("me.ahoo.cosid:cosid-spring-boot-starter:${cosidVersion}") +``` +```xml [Maven] + + + org.springframework.boot + spring-boot-starter-data-redis + ${springboot.version} + + + me.ahoo.cosid + cosid-spring-redis + ${cosid.version} + + + me.ahoo.cosid + cosid-spring-boot-starter + ${cosid.version} + + +``` +::: + +## 应用配置 + +```yaml +spring: + data: + redis: + host: localhost # Redis 分发器直接依赖 spring-data-redis,这样可以省去额外的配置。 +cosid: + namespace: ${spring.application.name} + machine: + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + distributor: + type: redis + snowflake: + enabled: true # 可选,当需要使用雪花算法时,需要设置为 true + segment: + enabled: true # 可选,当需要使用号段算法时,需要设置为 true + distributor: + type: redis +``` + +:::tip +默认情况下,开启 `snowflake`/`segment` 会生成共享的(`__share__`) `IdGenerator` 注册到 `Spring` 容器 以及 `DefaultIdGeneratorProvider.INSTANCE`。 +::: + +:::warning +当同时开启 `snowflake`/`segment` 时,只有其中一个共享的(`__share__`) `IdGenerator` 会注入到 `Spring` 容器(名称冲突),另一个会被忽略。 +::: + +`IdGenerator` `Bean Name` 规则: +- SegmentId: `[name]SegmentId` , 比如 : `__share__SegmentId` +- SnowflakeId: `[name]SnowflakeId`, 比如 : `__share__SnowflakeId` + +## 使用 + +> 通过 `@Autowired` 注入 `IdGenerator` 。 + +```java {1,6} + @Qualifier("__share__SegmentId") + @Lazy + @Autowired + private SegmentId segmentId; + + @Qualifier("__share__SnowflakeId") + @Lazy + @Autowired + private SnowflakeId snowflakeId; +``` + +> 通过 `DefaultIdGeneratorProvider.INSTANCE` 获取共享 `IdGenerator` 。 + +```java + DefaultIdGeneratorProvider.INSTANCE.getShare(); +``` + +## Examples + +开发者可以通过 [CosId-Examples](https://github.com/Ahoo-Wang/CosId/tree/main/examples) 的学习快速开启 `CosId` 之旅。 \ No newline at end of file diff --git a/documentation/docs/guide/id-converter.md b/documentation/docs/guide/id-converter.md new file mode 100644 index 0000000000..2c56d622a5 --- /dev/null +++ b/documentation/docs/guide/id-converter.md @@ -0,0 +1,56 @@ +# IdConverter + +> **ID转换器**,用于将 `long` 类型ID转换为 `String`,反之亦然。 + +```java +@ThreadSafe +public interface IdConverter { + + /** + * convert {@link long} type ID to {@link String} + * + * @param id {@link long} type ID + * @return {@link String} type ID + */ + String asString(long id); + + /** + * convert {@link String} type ID to {@link long} + * + * @param idString {@link String} type ID + * @return {@link long} type ID + */ + long asLong(String idString); +} +``` + +## IdConverter implementation class diagram + +

+ IdGenerator implementation class diagram +

+ +## ToStringIdConverter + +> String 转换器,用于将 `long` 转换成String 或者将 String 转换成 long 类型 + +- 规则 + - long 转 String:String.valueOf + - String 转 long: Long.parseLong + +## Radix62IdConverter + +> 62进制转换器,用于将 `long` 类型转换成 `62进制字符串`,或者将 `62进制字符串` 转换成`long` 类型 + +- 规则:`[0-9][A-Z][a-z]{11}` + +## SnowflakeFriendlyIdConverter + +> 雪花Id转换器,将符合雪花规则的字符串,转换成 long ,或者long 转换成雪花规则字符串 + +## PrefixIdConverter + +> 将带有前缀的字符串转换成long,或者将long转换成带前缀字符串 + +- 规则 + - 例如:前缀为:`no_`, 转换器选用`ToStringIdConverter`,数字`1` 经过转换得到`no_1`,反之亦然。 diff --git a/documentation/docs/guide/introduction.md b/documentation/docs/guide/introduction.md new file mode 100644 index 0000000000..08a58c7202 --- /dev/null +++ b/documentation/docs/guide/introduction.md @@ -0,0 +1,216 @@ +# 简介 + +*[CosId](https://github.com/Ahoo-Wang/CosId)* 旨在提供通用、灵活、高性能的分布式 ID 生成器。 + +- `CosIdGenerator` : *单机 TPS 性能:1557W/s*,三倍于 `UUID.randomUUID()`,基于时钟的全局趋势递增ID,可以同时支持一百万个实例。 +- `SnowflakeId` : *单机 TPS 性能:409W/s* [JMH 基准测试](faq/perf-test.md) , 主要解决 *时钟回拨* 、*机器号分配*、*取模分片不均匀* 等问题并提供更加友好、灵活的使用体验。 +- `SegmentId`: 每次获取一段 (`Step`) ID,来降低号段分发器的网络IO请求频次提升性能。并提供多种号段分发器实现。 +- `SegmentChainId`:`SegmentChainId` (*lock-free*) 是对 `SegmentId` 的增强。性能可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s* [JMH 基准测试](faq/perf-test.md) 。 + - `PrefetchWorker` 维护安全距离(`safeDistance`), 并且支持基于饥饿状态的动态`safeDistance`扩容/收缩。 + - 适应性:相比于 `SegmentId`,`SegmentChainId` 可以根据业务场景动态调整 `Step` 来提升性能。 + +## 背景(为什么需要*分布式ID*) + +在软件系统演进过程中,随着业务规模的增长 (TPS/存储容量),我们需要通过集群化部署来分摊计算、存储压力。 +应用服务的无状态设计使其具备了伸缩性。在使用 **Kubernetes** 部署时我们只需要一行命令即可完成服务伸缩 +(`kubectl scale --replicas=5 deployment/order-service`)。 + +但对于有状态的数据库就不那么容易了,此时数据库变成系统的性能瓶颈是显而易见的。 + +### 分库分表 + +:::info +从微服务的角度来理解垂直拆分其实就是微服务拆分。以限界上下文来定义服务边界将大服务/单体应用拆分成多个自治的粒度更小的服务,因为自治性规范要求,数据库也需要进行业务拆分。 + +但垂直拆分后的单个微服务依然会面临 TPS/存储容量 的挑战,所以这里我们重点讨论水平拆分的方式。 +::: + +

+ 分库分表 +

+ +数据库分库分表方案是逻辑统一,物理分区自治的方案。其核心设计在于中间层映射方案的设计 (上图 **Mapping**),即分片算法的设计。 +几乎所有编程语言都内置实现了散列表(java:`HashMap`/csharp:`Dictionary`/python:`dict`/go:`map` ...)。分片算法跟散列表高度相似(`hashCode`),都得通过 `key`/`shardingValue` 映射到对应的槽位(`slot`)。 + +那么 `shardingValue` 从哪里来呢?**CosId**!!! + +:::tip +当然还有很多分布式场景需要*分布式ID*,这里不再一一列举。 +::: + +## 分布式ID方案的核心指标 + +- **全局(相同业务)唯一性**:唯一性保证是**ID**的必要条件,假设ID不唯一就会产生主键冲突,这点很容易可以理解。 + - 通常所说的全局唯一性并不是指所有业务服务都要唯一,而是相同业务服务不同部署副本唯一。 + 比如 Order 服务的多个部署副本在生成`t_order`这张表的`Id`时是要求全局唯一的。至于`t_order_item`生成的`ID`与`t_order`是否唯一,并不影响唯一性约束,也不会产生什么副作用。 + 不同业务模块间也是同理。即唯一性主要解决的是ID冲突问题。 +- **有序性**:有序性保证是面向查询的数据结构算法(除了Hash算法)所必须的,是**二分查找法**(分而治之)的前提。 + - MySq-InnoDB B+树是使用最为广泛的,假设 Id 是无序的,B+ 树 为了维护 ID 的有序性,就会频繁的在索引的中间位置插入而挪动后面节点的位置,甚至导致频繁的页分裂,这对于性能的影响是极大的。那么如果我们能够保证ID的有序性这种情况就完全不同了,只需要进行追加写操作。所以 ID 的有序性是非常重要的,也是ID设计不可避免的特性。 +- **吞吐量/性能(ops/time)**:即单位时间(每秒)能产生的ID数量。生成ID是非常高频的操作,也是最为基本的。假设ID生成的性能缓慢,那么不管怎么进行系统优化也无法获得更好的性能。 + - 一般我们会首先生成ID,然后再执行写入操作,假设ID生成缓慢,那么整体性能上限就会受到限制,这一点应该不难理解。 +- **稳定性(time/op)**:稳定性指标一般可以采用**每个操作的时间进行百分位采样**来分析,比如 *[CosId](https://github.com/Ahoo-Wang/CosId)* 百分位采样 **P9999=0.208 us/op**,即 **0% ~ 99.99%** 的单位操作时间小于等于 **0.208 us/op**。 + - [百分位数 WIKI](https://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E4%BD%8D%E6%95%B0) :统计学术语,若将一组数据从小到大排序,并计算相应的累计百分点,则某百分点所对应数据的值,就称为这百分点的百分位数,以Pk表示第k百分位数。百分位数是用来比较个体在群体中的相对地位量数。 + - 为什么不用平均*每个操作的时间*:马老师的身价跟你的身价能平均么?平均后的值有意义不? + - 可以使用最小*每个操作的时间*、最大*每个操作的时间*作为参考吗?因为最小、最大值只说明了零界点的情况,虽说可以作为稳定性的参考,但依然不够全面。而且*百分位数*已经覆盖了这俩个指标。 +- **自治性(依赖)**:主要是指对外部环境有无依赖,比如**号段模式**会强依赖第三方存储中间件来获取`NexMaxId`。自治性还会对可用性造成影响。 +- **可用性**:分布式ID的可用性主要会受到自治性影响,比如**SnowflakeId**会受到时钟回拨影响,导致处于短暂时间的不可用状态。而**号段模式**会受到第三方发号器(`NexMaxId`)的可用性影响。 + - [可用性 WIKI](https://zh.wikipedia.org/wiki/%E5%8F%AF%E7%94%A8%E6%80%A7) :在一个给定的时间间隔内,对于一个功能个体来讲,总的可用时间所占的比例。 + - _MTBF_:平均故障间隔 + - _MDT_:平均修复/恢复时间 + - `Availability=MTBF/(MTBF+MDT)` + - 假设*MTBF*为1年,*MDT*为1小时,即`Availability=(365*24)/(365*24+1)=0.999885857778792≈99.99%`,也就是我们通常所说对可用性4个9。 +- **适应性**:是指在面对外部环境变化的自适应能力,这里我们主要说的是面对流量突发时动态伸缩分布式ID的性能, + - **SegmentChainId**可以基于**饥饿状态**进行**安全距离**的动态伸缩。 + - **SnowflakeId**常规位分配方案性能恒定409.6W,虽然可以通过调整位分配方案来获得不同的TPS性能,但是位分配方法的变更是破坏性的,一般根据业务场景确定位分配方案后不再变更。 +- **存储空间**:还是用MySq-InnoDB B+树来举例,普通索引(二级索引)会存储主键值,主键越大占用的内存缓存、磁盘空间也会越大。Page页存储的数据越少,磁盘IO访问的次数会增加。总之在满足业务需求的情况下,尽可能小的存储空间占用在绝大多数场景下都是好的设计原则。 + +### 不同分布式ID方案核心指标对比 + +| 分布式ID | 全局唯一性 | 有序性 | 吞吐量 | 稳定性(1s=1000,000us) | 自治性 | 可用性 | 适应性 | 存储空间 | +|:--------------:|:-----:|:---------------------------:|-----------------:|:--------------------|:----------:|:----------------------------------------:|:---:|:-------:| +| UUID/GUID | 是 | 完全无序 | 3078638(ops/s) | P9999=0.325(us/op) | 完全自治 | 100% | 否 | 128-bit | +| SnowflakeId | 是 | 本地单调递增,全局趋势递增(受全局时钟影响) | 4096000(ops/s) | P9999=0.244(us/op) | 依赖时钟 | 时钟回拨会导致短暂不可用 | 否 | 64-bit | +| SegmentId | 是 | 本地单调递增,全局趋势递增(受Step影响) | 29506073(ops/s) | P9999=46.624(us/op) | 依赖第三方号段分发器 | 受号段分发器可用性影响 | 否 | 64-bit | +| SegmentChainId | 是 | 本地单调递增,全局趋势递增(受Step、安全距离影响) | 127439148(ops/s) | P9999=0.208(us/op) | 依赖第三方号段分发器 | 受号段分发器可用性影响,但因安全距离存在,预留ID段,所以高于SegmentId | 是 | 64-bit | + +### 有序性(要想分而治之·二分查找法,必须要维护我) + +刚刚我们已经讨论了ID有序性的重要性,所以我们设计ID算法时应该尽可能地让ID是单调递增的,比如像表的自增主键那样。但是很遗憾,因全局时钟、性能等分布式系统问题,我们通常只能选择局部单调递增、全局趋势递增的组合(就像我们在分布式系统中不得不的选择最终一致性那样)以获得多方面的权衡。下面我们来看一下什么是单调递增与趋势递增。 + +#### 有序性之单调递增 + +

+ 单调递增 +

+ +单调递增:T表示全局绝对时点,假设有Tn+1>Tn(绝对时间总是往前进的,这里不考虑相对论、时间机器等),那么必然有F(Tn+1)>F(Tn),数据库自增主键就属于这一类。 +另外需要特别说明的是单调递增跟连续性递增是不同的概念。 连续性递增:`F(n+1)=(F(n)+step)`即下一次获取的ID一定等于当前`ID+Step`,当`Step=1`时类似于这样一个序列:`1->2->3->4->5`。 + +:::tip +扩展小知识:数据库的自增主键也不是连续性递增的,相信你一定遇到过这种情况,请思考一下数据库为什么这样设计? +::: + +#### 有序性之趋势递增 + +

+ 趋势递增 +

+ +趋势递增:Tn>Tn-s,那么大概率有F(Tn)>F(Tn-s)。虽然在一段时间间隔内有乱序,但是整体趋势是递增。从上图上看,是有上升趋势的(趋势线)。 +- 在**SnowflakeId**中n-s受到全局时钟同步影响。 +- 在号段模式(**SegmentId**)中n-s受到号段可用区间(`Step`)影响。 + +## 分布式ID分配方案 + +### UUID/GUID + +- :thumbsup:不依赖任何第三方中间件 +- :thumbsup:性能高 +- :thumbsdown:完全无序 +- :thumbsdown:空间占用大,需要占用128位存储空间。 + +UUID最大的缺陷是随机的、无序的,当用于主键时会导致数据库的主键索引效率低下(为了维护索引树,频繁的索引中间位置插入数据,而不是追加写)。这也是UUID不适用于数据库主键的最为重要的原因。 + +### SnowflakeId + +*SnowflakeId* 使用`Long`(64-bit)位分区来生成ID的一种分布式ID算法。 +通用的位分配方案为:`timestamp`(41-bit)+`machineId`(10-bit)+`sequence`(12-bit)=63-bit。 + +

+ SnowflakeId +

+ +- 41-bit`timestamp`=(1L<<41)/(1000/3600/24/365),约可以存储69年的时间戳,即可以使用的绝对时间为`EPOCH`+69年,一般我们需要自定义`EPOCH`为产品开发时间,另外还可以通过压缩其他区域的分配位数,来增加时间戳位数来延长可用时间。 +- 10-bit`machineId`=(1L<<10)=1024,即相同业务可以部署1024个副本(在Kubernetes概念里没有主从副本之分,这里直接沿用Kubernetes的定义)。一般情况下没有必要使用这么多位,所以会根据部署规模需要重新定义。 +- 12-bit`sequence`=(1L<<12)*1000=4096000,即单机每秒可生成约409W的ID,全局同业务集群可产生`4096000*1024=419430W=41.9亿(TPS)`。 + +从 *SnowflakeId* 设计上可以看出: + +- :thumbsup: `timestamp`在高位,单实例*SnowflakeId*是会保证时钟总是向前的(校验本机时钟回拨),所以是本机单调递增的。受全局时钟同步/时钟回拨影响*SnowflakeId*是全局趋势递增的。 +- :thumbsup: *SnowflakeId*不对任何第三方中间件有强依赖关系,并且性能也非常高。 +- :thumbsup: 位分配方案可以按照业务系统需要灵活配置,来达到最优使用效果。 +- :thumbsdown: 强依赖本机时钟,潜在的时钟回拨问题会导致ID重复、处于短暂的不可用状态。 +- :thumbsdown: `machineId`需要手动设置,实际部署时如果采用手动分配`machineId`,会非常低效。 + +#### SnowflakeId之机器号分配问题 + +在**SnowflakeId**中根据业务设计的位分配方案确定了基本上就不再有变更了,也很少需要维护。但是`machineId`总是需要配置的,而且集群中是不能重复的,否则分区原则就会被破坏而导致ID唯一性原则破坏,当集群规模较大时`machineId`的维护工作是非常繁琐,低效的。 + +:::tip +有一点需要特别说明的,**SnowflakeId** 的 **MachineId** 是逻辑上的概念,而不是物理概念,所以称之为 `WorkerId` 更为准确。 + +想象一下假设 **MachineId** 是物理上的,那么意味着一台机器拥有只能拥有一个 **MachineId**,那会产生什么问题呢? +::: + +目前 *[CosId](https://github.com/Ahoo-Wang/CosId)* 提供了以下五种 `MachineId` 分配器。 + +- ManualMachineIdDistributor: 手动配置`machineId`,一般只有在集群规模非常小的时候才有可能使用,不推荐。 +- StatefulSetMachineIdDistributor: 使用`Kubernetes`的`StatefulSet`提供的稳定的标识ID(HOSTNAME=service-01)作为机器号。 +- RedisMachineIdDistributor: 使用**Redis**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 +- JdbcMachineIdDistributor: 使用**关系型数据库**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 +- ZookeeperMachineIdDistributor: 使用**ZooKeeper**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 + +

+ MachineIdDistributor +

+ +

+ Machine Id Safe Guard +

+ +#### SnowflakeId之时钟回拨问题 + +时钟回拨的致命问题是会导致ID重复、冲突(这一点不难理解),ID重复显然是不能被容忍的。 +在**SnowflakeId**算法中,按照**MachineId**分区ID,我们不难理解的是不同**MachineId**是不可能产生相同ID的。所以我们解决的时钟回拨问题是指当前**MachineId**的时钟回拨问题,而不是所有集群节点的时钟回拨问题。 + +**MachineId**时钟回拨问题大体可以分为俩种情况: + +- 运行时时钟回拨:即在运行时获取的当前时间戳比上一次获取的时间戳小。这个场景的时钟回拨是很容易处理的,一般**SnowflakeId**代码实现时都会存储`lastTimestamp`用于运行时时钟回拨的检查,并抛出时钟回拨异常。 + - 时钟回拨时直接抛出异常是不太好地实践,因为下游使用方几乎没有其他处理方案(噢,我还能怎么办呢,等吧),时钟同步是唯一的选择,当只有一种选择时就不要再让用户选择了。 + - `ClockSyncSnowflakeId`是`SnowflakeId`的包装器,当发生时钟回拨时会使用`ClockBackwardsSynchronizer`主动等待时钟同步来重新生成ID,提供更加友好的使用体验。 +- 启动时时钟回拨:即在启动服务实例时获取的当前时钟比上次关闭服务时小。此时的`lastTimestamp`是无法存储在进程内存中的。当获取的外部存储的**机器状态**大于当前时钟时钟时,会使用`ClockBackwardsSynchronizer`主动同步时钟。 + - LocalMachineStateStorage:使用本地文件存储`MachineState`(机器号、最近一次时间戳)。因为使用的是本地文件所以只有当实例的部署环境是稳定的,`LocalMachineStateStorage`才适用。 + - RedisMachineIdDistributor:将`MachineState`存储在**Redis**分布式缓存中,这样可以保证总是可以获取到上次服务实例停机时**机器状态**。 + +#### SnowflakeId之JavaScript数值溢出问题 + +`JavaScript`的`Number.MAX_SAFE_INTEGER`只有53-bit,如果直接将63位的`SnowflakeId`返回给前端,那么会产生值溢出的情况(所以这里我们应该知道后端传给前端的`long`值溢出问题,**迟早**会出现,只不过SnowflakeId出现得更快而已)。 +很显然溢出是不能被接受的,一般可以使用以下俩种处理方案: +- 将生成的63-bit`SnowflakeId`转换为`String`类型。 + - 直接将`long`转换成`String`。 + - 使用`SnowflakeFriendlyId`将`SnowflakeId`转换成比较友好的字符串表示:`{timestamp}-{machineId}-{sequence} -> 20210623131730192-1-0` +- 自定义`SnowflakeId`位分配来缩短`SnowflakeId`的位数(53-bit)使 `ID` 提供给前端时不溢出 + - 使用`SafeJavaScriptSnowflakeId`(`JavaScript` 安全的 `SnowflakeId`) + +### 号段模式(SegmentId) + +

+ SegmentId +

+ +从上面的设计图中,不难看出**号段模式**基本设计思路是通过每次获取一定长度(Step)的可用ID(Id段/号段),来降低网络IO请求次数,提升性能。 + +- :thumbsdown:强依赖第三方号段分发器,可用性受到第三方分发器影响。 +- :thumbsdown:每次号段用完时获取`NextMaxId`需要进行网络IO请求,此时的性能会比较低。 +- 单实例ID单调递增,全局趋势递增。 + - 从设计图中不难看出**Instance 1**每次获取的`NextMaxId`,一定比上一次大,意味着下一次的号段一定比上一次大,所以从单实例上来看是单调递增的。 + - 多实例各自持有的不同的号段,意味着同一时刻不同实例生成的ID是乱序的,但是整体趋势的递增的,所以全局趋势递增。 +- ID乱序程度受到Step长度以及集群规模影响(从趋势递增图中不难看出)。 + - 假设集群中只有一个实例时**号段模式**就是单调递增的。 + - `Step`越小,乱序程度越小。当`Step=1`时,将无限接近单调递增。需要注意的是这里是无限接近而非等于单调递增,具体原因你可以思考一下这样一个场景: + - 号段分发器T1时刻给**Instance 1**分发了`ID=1`,T2时刻给**Instance 2**分发了`ID=2`。因为机器性能、网络等原因,`Instance 2`网络IO写请求先于`Instance 1`到达。那么这个时候对于数据库来说,ID依然是乱序的。 + +### 号段链模式(SegmentChainId) + +

+ SegmentChainId +

+ +**SegmentChainId**是**SegmentId**增强版,相比于**SegmentId**有以下优势: + +- 稳定性:**SegmentId**的稳定性问题(P9999=46.624(us/op))主要是因为号段用完之后同步进行`NextMaxId`的获取导致的(会产生网络IO)。 + - **SegmentChainId** (P9999=0.208(us/op))引入了新的角色**PrefetchWorker**用以维护和保证**安全距离**,理想情况下使得获取ID的线程几乎完全不需要进行同步的等待`NextMaxId`获取,性能可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s* [JMH 基准测试](faq/perf-test.md) 。 +- 适应性:从**SegmentId**介绍中我们知道了影响**ID乱序**的因素有俩个:集群规模、`Step`大小。集群规模是我们不能控制的,但是`Step`是可以调节的。 + - `Step`应该近可能小才能使得**ID单调递增**的可能性增大。 + - `Step`太小会影响吞吐量,那么我们如何合理设置`Step`呢?答案是我们无法准确预估所有时点的吞吐量需求,那么最好的办法是吞吐量需求高时,Step自动增大,吞吐量低时Step自动收缩。 + - **SegmentChainId**引入了**饥饿状态**的概念,**PrefetchWorker**会根据**饥饿状态**检测当前**安全距离**是否需要膨胀或者收缩,以便获得吞吐量与有序性之间的权衡,这便是**SegmentChainId**的自适应性。 diff --git a/documentation/docs/guide/provider.md b/documentation/docs/guide/provider.md new file mode 100644 index 0000000000..c53d3a2fa4 --- /dev/null +++ b/documentation/docs/guide/provider.md @@ -0,0 +1,16 @@ +# IdGeneratorProvider + +> `IdGenerator` 容器 + +

+ IdGeneratorProvider implementation class diagram +

+ +## DefaultIdGeneratorProvider + +> 默认的 `IdGenerator` 容器,所有`IdGenerator`都将注入到 `DefaultIdGeneratorProvider.INSTANCE`中。 + +## LazyIdGenerator + +> 懒加载 `IdGenerator` + diff --git a/documentation/docs/guide/segment-chain.md b/documentation/docs/guide/segment-chain.md new file mode 100644 index 0000000000..92ab01915c --- /dev/null +++ b/documentation/docs/guide/segment-chain.md @@ -0,0 +1,59 @@ +# 号段链模式 + +

+ SegmentChainId +

+ +**SegmentChainId**是**SegmentId**增强版,相比于**SegmentId**有以下优势: + +- 稳定性:**SegmentId**的稳定性问题(P9999=46.624(us/op))主要是因为号段用完之后同步进行`NextMaxId`的获取导致的(会产生网络IO)。 + - **SegmentChainId** (P9999=0.208(us/op))引入了新的角色**PrefetchWorker**用以维护和保证**安全距离**,理想情况下使得获取ID的线程几乎完全不需要进行同步的等待`NextMaxId`获取,性能可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s* [JMH 基准测试](faq/perf-test.md) 。 +- 适应性:从**SegmentId**介绍中我们知道了影响**ID乱序**的因素有俩个:集群规模、`Step`大小。集群规模是我们不能控制的,但是`Step`是可以调节的。 + - `Step`应该近可能小才能使得**ID单调递增**的可能性增大。 + - `Step`太小会影响吞吐量,那么我们如何合理设置`Step`呢?答案是我们无法准确预估所有时点的吞吐量需求,那么最好的办法是吞吐量需求高时,Step自动增大,吞吐量低时Step自动收缩。 + - **SegmentChainId**引入了**饥饿状态**的概念,**PrefetchWorker**会根据**饥饿状态**检测当前**安全距离**是否需要膨胀或者收缩,以便获得吞吐量与有序性之间的权衡,这便是**SegmentChainId**的自适应性。 + +## 为什么需要*SegmentChainId* + +

+ SegmentChainId +

+ +通过**SegmentChainId**设计图中我们可以看到,号段链模式新增了一个角色**PrefetchWorker**。 +**PrefetchWorker**主要的职责是维护和保证号段链头部到尾部的**安全距离**,也可以近似理解为缓冲距离。 +有了安全距离的保障不难得出的结论是所有获取ID的线程只要从进程内存的号段里边获取下次ID即可,理想情况下不需要再进行`NextMaxId`(向号段分发器请求`NextMaxId`,网络IO)的,所以性能可以达到近似`AtomicLong` 的 *TPS 性能:12743W+/s*的级别。 + +**SegmentChainId**是**SegmentId**的增强版,相比于**SegmentId**有以下优势: + +- TPS性能:可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s* [JMH 基准测试](faq/perf-test)。通过引入了新的角色**PrefetchWorker**用以维护和保证**安全距离**,理想情况下使得获取ID的线程几乎完全不需要进行同步的等待`NextMaxId`获取。 +- 稳定性:P9999=0.208(us/op),通过上面的TPS性能描述中我们可以看到,**SegmentChainId**消除了同步等待的问题,所以稳定性问题也因此迎刃而解。 +- 适应性:从**SegmentId**介绍中我们知道了影响**ID乱序**的因素有俩个:集群规模、`Step`大小。集群规模是我们不能控制的,但是`Step`是可以调节的。 + - `Step`应该尽可能小才能使得**ID单调递增**的可能性增大。 + - `Step`太小会影响吞吐量,那么我们如何合理设置`Step`呢?答案是我们无法准确预估所有时点的吞吐量需求,那么最好的办法是吞吐量需求高时,Step自动增大,吞吐量低时Step自动收缩。 + - **SegmentChainId**引入了**饥饿状态**的概念,**PrefetchWorker**会根据**饥饿状态**检测当前**安全距离**是否需要膨胀或者收缩,以便获得吞吐量与有序性之间的权衡,这便是**SegmentChainId**的自适应性。 + - 所以在使用**SegmentChainId**时我们可以配置一个比较小的`Step`步长,然后由**PrefetchWorker**根据吞吐量需求自动调节**安全距离**,来自动伸缩步长。 + +## RedisIdSegmentDistributor、JdbcIdSegmentDistributor 均能够达到TPS=1.2亿/s? + +

+ Throughput-Of-SegmentChainId +

+ +上面的两张图给许多同学带来了困扰,为什么在`Step=1000`的时候*RedisIdSegmentDistributor*、*JdbcIdSegmentDistributor*TPS性能几乎一致(TPS=1.2亿/s)。 +*RedisIdSegmentDistributor*应该要比*JdbcIdSegmentDistributor*性能更高才对啊,为什么都能达到*AtomicLong*性能上限呢? +如果我说当`Step=1`时,只要基准测试的时间够长,那么他们依然能够达到*AtomicLong*性能级别(TPS=1.2亿/s),你会不会更加困惑。 +其实这里的*障眼法*是**PrefetchWorker**的**饥饿膨胀**导致的,*SegmentChainId*的极限性能跟分发器的TPS性能没有直接关系,因为最终都可以因饥饿膨胀到性能上限,只要给足够的时间膨胀。 +而为什么在上图的`Step=1`时TPS差异还是很明显的,这是因为*RedisIdSegmentDistributor*膨胀得更快,而基准测试又没有给足测试时间而已。 + +**SegmentChainId**基准测试*TPS极限性能*可以近似使用以下的公式的表示: + +`TPS(SegmentChainId)极限值=(Step*Expansion)*TPS(IdSegmentDistributor)*T/s<=TPS(AtomicLong)` + +1. `<=TPS(AtomicLong)`:因为*SegmentChainId*的内部号段就是使用的`AtomicLong`,所以这是性能上限。 +2. `Step*Expansion`:*Expansion*可以理解为饥饿膨胀系数,默认的饥饿膨胀系数是2。在*MySqlChainIdBenchmark*、*MySqlChainIdBenchmark*基准测试中这个值是一样的。 +3. `TPS(IdSegmentDistributor)`: 这是公式中唯一的不同。指的是请求号段分发器`NextMaxId`的TPS。 +4. `T`: 可以理解为基准测试运行时常。 + +从上面的公式中不难看出*RedisChainIdBenchmark*、*MySqlChainIdBenchmark*主要差异是分发器的TPS性能。 +分发器的`TPS(IdSegmentDistributor)`越大,达到`TPS(AtomicLong)`所需的`T`就越少。但只要`T`足够长,那么任何分发器都可以达到近似`TPS(AtomicLong)`。 +这也就解释了为什么不同TPS性能级别的号段分发器(**IdSegmentDistributor**)都可以达到TPS=1.2亿/s。 diff --git a/documentation/docs/guide/segment.md b/documentation/docs/guide/segment.md new file mode 100644 index 0000000000..983ebef99f --- /dev/null +++ b/documentation/docs/guide/segment.md @@ -0,0 +1,48 @@ +# SegmentId + +

+ SegmentId +

+ +从上面的设计图中,不难看出**号段模式**基本设计思路是通过每次获取一定长度(Step)的可用ID(Id段/号段),来降低网络IO请求次数,提升性能。 + +- :thumbsdown:强依赖第三方号段分发器,可用性受到第三方分发器影响。 +- :thumbsdown:每次号段用完时获取`NextMaxId`需要进行网络IO请求,此时的性能会比较低。 +- 单实例ID单调递增,全局趋势递增。 + - 从设计图中不难看出**Instance 1**每次获取的`NextMaxId`,一定比上一次大,意味着下一次的号段一定比上一次大,所以从单实例上来看是单调递增的。 + - 多实例各自持有的不同的号段,意味着同一时刻不同实例生成的ID是乱序的,但是整体趋势的递增的,所以全局趋势递增。 +- ID乱序程度受到Step长度以及集群规模影响(从趋势递增图中不难看出)。 + - 假设集群中只有一个实例时**号段模式**就是单调递增的。 + - `Step`越小,乱序程度越小。当`Step=1`时,将无限接近单调递增。需要注意的是这里是无限接近而非等于单调递增,具体原因你可以思考一下这样一个场景: + - 号段分发器T1时刻给**Instance 1**分发了`ID=1`,T2时刻给**Instance 2**分发了`ID=2`。因为机器性能、网络等原因,`Instance 2`网络IO写请求先于`Instance 1`到达。那么这个时候对于数据库来说,ID依然是乱序的。 + +## 具体实现 + +```mermaid +classDiagram +direction BT +class DefaultSegmentId +class IdGenerator { +<> + +} +class SegmentChainId +class SegmentId { +<> + +} +class StringSegmentId + +DefaultSegmentId ..> SegmentId +SegmentChainId ..> SegmentId +SegmentId --> IdGenerator +StringSegmentId ..> IdGenerator +StringSegmentId ..> SegmentId +``` + +## IdSegmentDistributor + +## GroupedIdSegmentDistributor + + +## 配置 diff --git a/documentation/docs/guide/sharding/interval-timeline.md b/documentation/docs/guide/sharding/interval-timeline.md new file mode 100644 index 0000000000..28086d58d9 --- /dev/null +++ b/documentation/docs/guide/sharding/interval-timeline.md @@ -0,0 +1,14 @@ +# 基于间隔的时间范围分片算法 + +

+ CosIdIntervalShardingAlgorithm +

+ +- 算法复杂度:O(1) +- 易用性: 支持多种数据类型 (`Long`/`LocalDateTime`/`DATE`/ `String` / `SnowflakeId`),而官方实现是先转换成字符串再转换成`LocalDateTime`,转换成功率受时间格式化字符影响。 +- 性能 : 相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm` 性能高出 *1200~4000* 倍。 + +| **PreciseShardingValue** | **RangeShardingValue** | +|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| | | + diff --git a/documentation/docs/guide/sharding/mod-cycle.md b/documentation/docs/guide/sharding/mod-cycle.md new file mode 100644 index 0000000000..d133d698e1 --- /dev/null +++ b/documentation/docs/guide/sharding/mod-cycle.md @@ -0,0 +1,13 @@ +# 取模分片算法 + +

+ CosIdModShardingAlgorithm +

+ +- 算法复杂度:O(1) +- 性能 : 相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.mod.ModShardingAlgorithm` 性能高出 *1200~4000* 倍。并且稳定性更高,不会出现严重的性能退化。 + +| **PreciseShardingValue** | **RangeShardingValue** | +|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| | | + diff --git a/documentation/docs/guide/snowflake.md b/documentation/docs/guide/snowflake.md new file mode 100644 index 0000000000..e311ea49e7 --- /dev/null +++ b/documentation/docs/guide/snowflake.md @@ -0,0 +1,179 @@ +# SnowflakeId + +_SnowflakeId_ 是*Twitter*开发的一种分布式唯一ID生成算法,被广泛应用于分布式系统中。它的设计目标是生成趋势递增、全局唯一的ID,以应对大规模系统的需求。 + +

+ SnowflakeId +

+ +## 简介 + +:::info +*SnowflakeId* 使用`Long`(64-bit)位分区来生成ID的一种分布式ID算法。 + +通用的位分配方案为:`timestamp`(41-bit)+`machineId`(10-bit)+`sequence`(12-bit)=63-bit。 +::: + +- 41-bit`timestamp`=(1L<<41)/(1000/3600/24/365),约可以存储69年的时间戳,即可以使用的绝对时间为`EPOCH`+69年,一般我们需要自定义`EPOCH`为产品开发时间,另外还可以通过压缩其他区域的分配位数,来增加时间戳位数来延长可用时间。 +- 10-bit`machineId`=(1L<<10)=1024,即相同业务可以部署1024个副本(在Kubernetes概念里没有主从副本之分,这里直接沿用Kubernetes的定义)。一般情况下没有必要使用这么多位,所以会根据部署规模需要重新定义。 +- 12-bit`sequence`=(1L<<12)*1000=4096000,即单机每秒可生成约409W的ID,全局同业务集群可产生`4096000*1024=419430W=41.9亿(TPS)`。 + +从 *SnowflakeId* 设计上可以看出: + +- :thumbsup: `timestamp`在高位,单实例*SnowflakeId*是会保证时钟总是向前的(校验本机时钟回拨),所以是本机单调递增的。受全局时钟同步/时钟回拨影响*SnowflakeId*是全局趋势递增的。 +- :thumbsup: *SnowflakeId*不对任何第三方中间件有强依赖关系,并且性能也非常高。 +- :thumbsup: 位分配方案可以按照业务系统需要灵活配置,来达到最优使用效果。 +- :thumbsdown: 强依赖本机时钟,潜在的时钟回拨问题会导致ID重复、处于短暂的不可用状态。 +- :thumbsdown: `machineId`需要手动设置,实际部署时如果采用手动分配`machineId`,会非常低效。 + +## 挑战 + +### 机器号分配 + +在**SnowflakeId**中根据业务设计的位分配方案确定了基本上就不再有变更了,也很少需要维护。但是`machineId`总是需要配置的,而且集群中是不能重复的,否则分区原则就会被破坏而导致ID唯一性原则破坏,当集群规模较大时`machineId`的维护工作是非常繁琐,低效的。 + +:::tip +有一点需要特别说明的,**SnowflakeId** 的 **MachineId** 是逻辑上的概念,而不是物理概念,所以称之为 `WorkerId` 更为准确。 + +想象一下假设 **MachineId** 是物理上的,那么意味着一台机器拥有只能拥有一个 **MachineId**,那会产生什么问题呢? +::: + +### 时钟回拨 + +时钟回拨的致命问题是会导致ID重复、冲突(这一点不难理解),ID重复显然是不能被容忍的。 +在**SnowflakeId**算法中,按照**MachineId**分区ID,我们不难理解的是不同**MachineId**是不可能产生相同ID的。所以我们解决的时钟回拨问题是指当前**MachineId**的时钟回拨问题,而不是所有集群节点的时钟回拨问题。 + +**MachineId**时钟回拨问题大体可以分为俩种情况: + +- 运行时时钟回拨:即在运行时获取的当前时间戳比上一次获取的时间戳小。这个场景的时钟回拨是很容易处理的,一般**SnowflakeId**代码实现时都会存储`lastTimestamp`用于运行时时钟回拨的检查,并抛出时钟回拨异常。 + - 时钟回拨时直接抛出异常是不太好地实践,因为下游使用方几乎没有其他处理方案(噢,我还能怎么办呢,等吧),时钟同步是唯一的选择,当只有一种选择时就不要再让用户选择了。 + - `ClockSyncSnowflakeId`是`SnowflakeId`的包装器,当发生时钟回拨时会使用`ClockBackwardsSynchronizer`主动等待时钟同步来重新生成ID,提供更加友好的使用体验。 +- 启动时时钟回拨:即在启动服务实例时获取的当前时钟比上次关闭服务时小。此时的`lastTimestamp`是无法存储在进程内存中的。当获取的外部存储的**机器状态**大于当前时钟时钟时,会使用`ClockBackwardsSynchronizer`主动同步时钟。 + - LocalMachineStateStorage:使用本地文件存储`MachineState`(机器号、最近一次时间戳)。因为使用的是本地文件所以只有当实例的部署环境是稳定的,`LocalMachineStateStorage`才适用。 + - RedisMachineIdDistributor:将`MachineState`存储在**Redis**分布式缓存中,这样可以保证总是可以获取到上次服务实例停机时**机器状态**。 + +### 取模分片不均匀 + +_CosId_ 通过引入 `sequenceResetThreshold` 属性,巧妙地解决了取模分片不均匀的问题,这一设计在无需牺牲性能的同时,为用户提供了更加出色的使用体验。 + +### JavaScript数值溢出 + +`JavaScript`的`Number.MAX_SAFE_INTEGER`只有53-bit,如果直接将63位的`SnowflakeId`返回给前端,那么会产生值溢出的情况(所以这里我们应该知道后端传给前端的`long`值溢出问题,**迟早**会出现,只不过SnowflakeId出现得更快而已)。 +很显然溢出是不能被接受的,一般可以使用以下俩种处理方案: +- 将生成的63-bit`SnowflakeId`转换为`String`类型。 + - 直接将`long`转换成`String`。 + - 使用`SnowflakeFriendlyId`将`SnowflakeId`转换成比较友好的字符串表示:`{timestamp}-{machineId}-{sequence} -> 20210623131730192-1-0` +- 自定义`SnowflakeId`位分配来缩短`SnowflakeId`的位数(53-bit)使 `ID` 提供给前端时不溢出 + - 使用`SafeJavaScriptSnowflakeId`(`JavaScript` 安全的 `SnowflakeId`) + +## 具体实现 + +```mermaid +classDiagram +direction BT +class AbstractSnowflakeId +class ClockSyncSnowflakeId +class DefaultSnowflakeFriendlyId +class IdGenerator { +<> + +} +class MillisecondSnowflakeId +class SecondSnowflakeId +class SnowflakeFriendlyId { +<> + +} +class SnowflakeId { +<> + +} +class StringSnowflakeId + +AbstractSnowflakeId ..> SnowflakeId +ClockSyncSnowflakeId ..> IdGenerator +ClockSyncSnowflakeId ..> SnowflakeId +DefaultSnowflakeFriendlyId ..> SnowflakeFriendlyId +DefaultSnowflakeFriendlyId --> StringSnowflakeId +MillisecondSnowflakeId --> AbstractSnowflakeId +SecondSnowflakeId --> AbstractSnowflakeId +SnowflakeFriendlyId --> SnowflakeId +SnowflakeId --> IdGenerator +StringSnowflakeId ..> IdGenerator +StringSnowflakeId ..> SnowflakeId +``` + +### MillisecondSnowflakeId + +`MillisecondSnowflakeId` 是 `SnowflakeId` 的默认实现,它使用 `System.currentTimeMillis()` 作为时间戳,精确到毫秒级别。 + +### SecondSnowflakeId + +`SecondSnowflakeId` 是 `SnowflakeId` 的另一种实现,它使用 `System.currentTimeMillis() / 1000` 作为时间戳,精确到秒级别。 + +### DefaultSnowflakeFriendlyId + +`DefaultSnowflakeFriendlyId` 是 `SnowflakeId` 的包装器,它将`SnowflakeId`转换成比较友好的字符串表示:`{timestamp}-{machineId}-{sequence} -> 20210623131730192-1-0` + +### ClockSyncSnowflakeId + +`ClockSyncSnowflakeId` 是 `SnowflakeId` 的包装器,当发生时钟回拨时会使用`ClockBackwardsSynchronizer`主动等待时钟同步来重新生成ID,提供更加友好的使用体验。 + +## MachineIdDistributor + +`MachineIdDistributor` 是 `SnowflakeId` 的机器号分配器,它负责分配机器号,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 + +

+ MachineIdDistributor +

+ +目前 *CosId* 提供了以下六种 `MachineId` 分配器。 + +- `ManualMachineIdDistributor`: 手动配置`machineId`,一般只有在集群规模非常小的时候才有可能使用,不推荐。 +- `StatefulSetMachineIdDistributor`: 使用`Kubernetes`的`StatefulSet`提供的稳定的标识ID(HOSTNAME=service-01)作为机器号。 +- `RedisMachineIdDistributor`: 使用**Redis**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 +- `JdbcMachineIdDistributor`: 使用**关系型数据库**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 +- `ZookeeperMachineIdDistributor`: 使用**ZooKeeper**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 +- `MongoMachineIdDistributor`: 使用**MongoDB**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。 + +## MachineIdGuarder + +

+ Machine Id Safe Guard +

+ +## 配置 + +[SnowflakeId 配置](../reference/config/snowflake) + +### 配置案例 + +```yaml +cosid: + namespace: ${spring.application.name} + machine: + enabled: true + distributor: + type: jdbc # 机器号分配器 + guarder: + enabled: true # 开启机器号守护 + snowflake: + enabled: true + zone-id: Asia/Shanghai + epoch: 1577203200000 + share: + clock-sync: true # 开启始终回拨同步 + friendly: true + provider: + short_id: + converter: + prefix: cosid_ + type: radix + radix: + char-size: 11 + pad-start: false + safe-js: + machine-bit: 3 + sequence-bit: 9 +``` + diff --git a/documentation/docs/guide/specific-id.md b/documentation/docs/guide/specific-id.md new file mode 100644 index 0000000000..6dda3bd22d --- /dev/null +++ b/documentation/docs/guide/specific-id.md @@ -0,0 +1,768 @@ +# 特定场景ID配置 + +## snowflake_friendly + +使用 _SnowflakeId_ 算法,要求输出的ID字符串: + +- 格式:`yyyyMMddHHmmssSSS--` +- 例如:`20240103152415876-5-16` + +::: code-group +```yaml {9-11} [配置] +cosid: + machine: + enabled: true + distributor: + type: redis + snowflake: + enabled: true + provider: + snowflake_friendly: + converter: + type: snowflake_friendly +``` +```json [配置信息] +{ + "snowflake_friendly": { + "kind": "DefaultSnowflakeFriendlyId", + "actual": { + "kind": "ClockSyncSnowflakeId", + "actual": { + "kind": "MillisecondSnowflakeId", + "epoch": 1577203200000, + "timestampBit": 41, + "machineBit": 10, + "sequenceBit": 12, + "isSafeJavascript": false, + "machineId": 5, + "lastTimestamp": -1, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "SnowflakeFriendlyIdConverter", // [!code focus] + "actual": null + } + } +} +``` +::: + +## snowflake_short_id + +使用 _SnowflakeId_ 算法,要求输出的ID字符串: + +- 格式:尽可能短 +- 例如:`0dMszf3Ht1l` + +::: code-group +```yaml {9-14} [配置] +cosid: + machine: + enabled: true + distributor: + type: redis + snowflake: + enabled: true + provider: + snowflake_short_id: + converter: + type: radix + radix: + char-size: 11 + pad-start: true +``` +```json [配置信息] +{ + "snowflake_short_id": { + "kind": "DefaultSnowflakeFriendlyId", + "actual": { + "kind": "ClockSyncSnowflakeId", + "actual": { + "kind": "MillisecondSnowflakeId", + "epoch": 1577203200000, + "timestampBit": 41, + "machineBit": 10, + "sequenceBit": 12, + "isSafeJavascript": false, + "machineId": 5, + "lastTimestamp": -1, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + } +} +``` +::: + +## snowflake_friendly_second + +使用 _SnowflakeId_ 算法,要求输出的ID字符串: + +- 格式:`yyyyMMddHHmmss--` +- 例如:`20240103153900-5-4` + +::: code-group +```yaml {9-16} [配置] +cosid: + machine: + enabled: true + distributor: + type: redis + snowflake: + enabled: true + provider: + snowflake_friendly_second: + timestamp-unit: second + epoch: 1577203200 + timestamp-bit: 31 + machine-bit: 10 + sequence-bit: 22 + converter: + type: snowflake_friendly +``` +```json [配置信息] +{ + "snowflake_friendly_second": { + "kind": "DefaultSnowflakeFriendlyId", + "actual": { + "kind": "ClockSyncSnowflakeId", + "actual": { + "kind": "SecondSnowflakeId", + "epoch": 1577203200, + "timestampBit": 31, + "machineBit": 10, + "sequenceBit": 22, + "isSafeJavascript": false, + "machineId": 5, + "lastTimestamp": 1704265875, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "SnowflakeFriendlyIdConverter", + "actual": null + } + } +} +``` +::: + +## biz_prefix_no + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`2000000000` +- 格式:`` +- 序号位:10位数值,不足10位前补0 +- 例如:`BIZ2000000219` + +::: code-group +```yaml {7-14} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + biz_prefix_no: + offset: 2000000000 + converter: + type: to_string + prefix: BIZ + to-string: + char-size: 10 + pad-start: true +``` +```json [配置信息] +{ + "biz_prefix_no": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1704265844, + "maxId": 2000000220, + "offset": 2000000200, + "sequence": 2000000218, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ", + "actual": { + "kind": "ToStringIdConverter", + "padStart": true, + "charSize": 10 + } + } + } +} +``` +::: + +## date_prefix_no + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 格式:`` +- 日期位:6位日期字符串,格式`yyMMdd` +- 例如:`BIZ-240618-25` + +::: code-group +```yaml {7-14} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + date_prefix_no: + converter: + type: to_string + prefix: BIZ- + date-prefix: + enabled: true + pattern: yyMMdd +``` +```json [配置信息] +{ + "date_prefix_no": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1718704101, + "maxId": 20, + "offset": 0, + "sequence": 0, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "groupedKey": { + "key": "", + "ttlAt": 9223372036854776000 + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854776000 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ-", + "actual": { + "kind": "DatePrefixIdConverter", + "pattern": "yyMMdd", + "actual": { + "kind": "ToStringIdConverter", + "padStart": false, + "charSize": 10 + } + } + } + } +} +``` +::: + +## no_suffix_biz + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`2000000000` +- 格式:`` +- 序号位:10位数值,不足10位前补0 +- 例如:`2000000201BIZ` + +::: code-group +```yaml {7-14} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + no_suffix_biz: + offset: 2000000000 + converter: + type: to_string + suffix: BIZ + to-string: + char-size: 10 + pad-start: true +``` +```json [配置信息] +{ + "no_suffix_biz": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1704265915, + "maxId": 2000000210, + "offset": 2000000200, + "sequence": 2000000200, + "step": 10, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "SuffixIdConverter", + "suffix": "BIZ", + "actual": { + "kind": "ToStringIdConverter", + "padStart": true, + "charSize": 10 + } + } + } +} +``` +::: + +## biz_prefix_radix + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`2000000000` +- 格式:`` +- 序号位:6位62进制字符串,不足6位前补0 +- 例如:`BIZ2BLnPb` + +::: code-group +```yaml {7-14} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + biz_prefix_radix: + offset: 2000000000 + converter: + type: radix + prefix: BIZ + radix: + char-size: 6 + pad-start: true +``` +```json [配置信息] +{ + "biz_prefix_radix": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1704265844, + "maxId": 2000000200, + "offset": 2000000180, + "sequence": 2000000190, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ", + "actual": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 6, + "padStart": true, + "maxId": 56800235584 + } + } + } +} +``` +::: + +## biz_prefix_radix36 + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`2000000000` +- 格式:`` +- 序号位:8位36进制字符串,不足8位前补0 +- 例如:`BIZ00000044` + +::: code-group +```yaml {7-14} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + biz_prefix_radix: + offset: 2000000000 + converter: + type: radix + prefix: BIZ + radix: + char-size: 6 + pad-start: true +``` +```json [配置信息] +{ + "biz_prefix_radix36": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1704265844, + "maxId": 150, + "offset": 130, + "sequence": 147, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854775807 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ", + "actual": { + "kind": "Radix36IdConverter", + "radix": 36, + "charSize": 8, + "padStart": true, + "maxId": 2821109907456 + } + } + } +} +``` +::: + +## group_year_biz + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`0` +- 格式:`` +- 分组:按年分组,每年序号从0开始。即每年序号需要重置为0. +- 序号位:8位数值,不足8位前补0 +- 例如:`BIZ-2024-00000231` + +::: code-group +```yaml {7-18} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + group_year_biz: + group: + by: year + pattern: yyyy + converter: + type: to_string + to-string: + pad-start: true + char-size: 8 + prefix: BIZ- + group-prefix: + enabled: true +``` +```json {14-17} [配置信息] +{ + "group_year_biz": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1715911764, + "maxId": 570, + "offset": 550, + "sequence": 550, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "groupedKey": { + "key": "2024", + "ttlAt": 1735660799 + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854776000 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ-", + "actual": { + "kind": "GroupedPrefixIdConverter", + "delimiter": "-", + "actual": { + "kind": "ToStringIdConverter", + "padStart": true, + "charSize": 8 + } + } + } + } +} +``` +::: + +## group_year_month_biz + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`0` +- 格式:`` +- 分组:按年月分组,序号从0开始。即跨月序号需要重置为0. +- 序号位:8位数值,不足8位前补0 +- 例如:`BIZ-240516-00000061` + +::: code-group +```yaml {7-18} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + group_year_month_biz: + group: + by: year_month + pattern: yyyyMM + converter: + type: to_string + to-string: + pad-start: true + char-size: 8 + prefix: BIZ- + group-prefix: + enabled: true +``` +```json {14-17} [配置信息] +{ + "group_year_month_biz": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1715911765, + "maxId": 310, + "offset": 290, + "sequence": 290, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "groupedKey": { + "key": "202405", + "ttlAt": 1717171199 + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854776000 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ-", + "actual": { + "kind": "GroupedPrefixIdConverter", + "delimiter": "-", + "actual": { + "kind": "ToStringIdConverter", + "padStart": true, + "charSize": 8 + } + } + } + } +} +``` +::: + +## group_year_month_day_biz + +使用 _SegmentId_ 算法,要求输出的ID字符串: +- 起始序号:`0` +- 格式:`` +- 分组:按日期分组,序号从0开始。即明天序号需要重置为0. +- 序号位:8位数值,不足8位前补0 +- 例如:`BIZ-240516-00000001` + +::: code-group +```yaml {7-18} [配置] +cosid: + segment: + enabled: true + distributor: + type: redis + provider: + group_year_month_day_biz: + group: + by: year_month_day + pattern: yyMMdd + converter: + type: to_string + to-string: + pad-start: true + char-size: 8 + prefix: BIZ- + group-prefix: + enabled: true +``` +```json {14-17} [配置信息] +{ + "group_year_month_day_biz": { + "kind": "StringSegmentId", + "actual": { + "kind": "SegmentChainId", + "fetchTime": 1715911765, + "maxId": 280, + "offset": 260, + "sequence": 260, + "step": 20, + "isExpired": false, + "isOverflow": false, + "isAvailable": true, + "groupedKey": { + "key": "240517", + "ttlAt": 1715961599 + }, + "converter": { + "kind": "Radix62IdConverter", + "radix": 62, + "charSize": 11, + "padStart": true, + "maxId": 9223372036854776000 + } + }, + "converter": { + "kind": "PrefixIdConverter", + "prefix": "BIZ-", + "actual": { + "kind": "GroupedPrefixIdConverter", + "delimiter": "-", + "actual": { + "kind": "ToStringIdConverter", + "padStart": true, + "charSize": 8 + } + } + } + } +} +``` +::: + +## 百万级规模集群实例的全局ID + +使用 _CosIdGenerator_ 算法,要求支持百万级规模集群实例的全局ID生成器。 + +::: code-group +```yaml {6-7} [配置] +cosid: + machine: + enabled: true + distributor: + type: redis + generator: + enabled: true +``` +```json [配置信息] +{ + "cosid": { + "kind": "ClockSyncCosIdGenerator", + "actual": { + "kind": "Radix62CosIdGenerator", + "machineId": 5, + "lastTimestamp": 1704265904677, + "converter": { + "kind": "RadixCosIdStateParser", + "actual": null + } + }, + "converter": { + "kind": "RadixCosIdStateParser", + "actual": null + } + } +} +``` +::: \ No newline at end of file diff --git a/documentation/docs/index.md b/documentation/docs/index.md new file mode 100644 index 0000000000..072204804c --- /dev/null +++ b/documentation/docs/index.md @@ -0,0 +1,34 @@ +--- +layout: home +title: 通用、灵活、高性能的分布式ID生成器 + +hero: + name: "CosId" + text: "通用、灵活、高性能的分布式ID生成器" +# tagline: "通用、灵活、高性能的分布式ID生成器" + image: + src: /logo.png + alt: CosId + actions: + - theme: brand + text: 快速上手 + link: /guide/getting-started + - theme: alt + text: 简介 + link: /guide/introduction + - theme: alt + text: GitHub + link: https://github.com/Ahoo-Wang/CosId + - theme: alt + text: Gitee + link: https://gitee.com/AhooWang/CosId + +features: +- title: 通用 + details: 支持多种类型的分布式ID算法:SnowflakeId、SegmentId、SegmentChainId。 并且支持多种号段分发器、机器号分发器。 +- title: 灵活 + details: 通过简单配置即可自定义切换多种算法实现,定制以满足场景需要。 +- title: 高性能 + details: 设计极致优化,SegmentChainId 性能可达到近似 AtomicLong 的 TPS 性能:12743W+/s。 +--- + diff --git a/documentation/docs/public/assets/design/CosId-Proxy.png b/documentation/docs/public/assets/design/CosId-Proxy.png new file mode 100644 index 0000000000..83fce2d39a Binary files /dev/null and b/documentation/docs/public/assets/design/CosId-Proxy.png differ diff --git a/documentation/docs/public/assets/design/CosIdGenerator.png b/documentation/docs/public/assets/design/CosIdGenerator.png new file mode 100644 index 0000000000..0e18dc52cb Binary files /dev/null and b/documentation/docs/public/assets/design/CosIdGenerator.png differ diff --git a/documentation/docs/public/assets/design/CosIdIntervalShardingAlgorithm.png b/documentation/docs/public/assets/design/CosIdIntervalShardingAlgorithm.png new file mode 100644 index 0000000000..21aa2e6eaa Binary files /dev/null and b/documentation/docs/public/assets/design/CosIdIntervalShardingAlgorithm.png differ diff --git a/documentation/docs/public/assets/design/CosIdModShardingAlgorithm.png b/documentation/docs/public/assets/design/CosIdModShardingAlgorithm.png new file mode 100644 index 0000000000..f7de33fef2 Binary files /dev/null and b/documentation/docs/public/assets/design/CosIdModShardingAlgorithm.png differ diff --git a/documentation/docs/public/assets/design/IdConverter-impl-class.png b/documentation/docs/public/assets/design/IdConverter-impl-class.png new file mode 100644 index 0000000000..17f3a47099 Binary files /dev/null and b/documentation/docs/public/assets/design/IdConverter-impl-class.png differ diff --git a/documentation/docs/public/assets/design/IdGenerator-impl-class.png b/documentation/docs/public/assets/design/IdGenerator-impl-class.png new file mode 100644 index 0000000000..3119a4618c Binary files /dev/null and b/documentation/docs/public/assets/design/IdGenerator-impl-class.png differ diff --git a/documentation/docs/public/assets/design/IdGeneratorProvider-impl-class.png b/documentation/docs/public/assets/design/IdGeneratorProvider-impl-class.png new file mode 100644 index 0000000000..62a8e1ec29 Binary files /dev/null and b/documentation/docs/public/assets/design/IdGeneratorProvider-impl-class.png differ diff --git a/documentation/docs/public/assets/design/Machine-Id-Safe-Guard.png b/documentation/docs/public/assets/design/Machine-Id-Safe-Guard.png new file mode 100644 index 0000000000..5d348420c5 Binary files /dev/null and b/documentation/docs/public/assets/design/Machine-Id-Safe-Guard.png differ diff --git a/documentation/docs/public/assets/design/MachineIdDistributor.png b/documentation/docs/public/assets/design/MachineIdDistributor.png new file mode 100644 index 0000000000..f9025e4f6a Binary files /dev/null and b/documentation/docs/public/assets/design/MachineIdDistributor.png differ diff --git a/documentation/docs/public/assets/design/SegmentChainId.png b/documentation/docs/public/assets/design/SegmentChainId.png new file mode 100644 index 0000000000..6e262fbb71 Binary files /dev/null and b/documentation/docs/public/assets/design/SegmentChainId.png differ diff --git a/documentation/docs/public/assets/design/SegmentId.png b/documentation/docs/public/assets/design/SegmentId.png new file mode 100644 index 0000000000..2a4a613993 Binary files /dev/null and b/documentation/docs/public/assets/design/SegmentId.png differ diff --git a/documentation/docs/public/assets/design/Sharding-impl-class.png b/documentation/docs/public/assets/design/Sharding-impl-class.png new file mode 100644 index 0000000000..83347efae8 Binary files /dev/null and b/documentation/docs/public/assets/design/Sharding-impl-class.png differ diff --git a/documentation/docs/public/assets/design/Snowflake-identifier.png b/documentation/docs/public/assets/design/Snowflake-identifier.png new file mode 100644 index 0000000000..51d3adbdb0 Binary files /dev/null and b/documentation/docs/public/assets/design/Snowflake-identifier.png differ diff --git a/documentation/docs/public/assets/design/monotonically-increasing.png b/documentation/docs/public/assets/design/monotonically-increasing.png new file mode 100644 index 0000000000..8ab13673ed Binary files /dev/null and b/documentation/docs/public/assets/design/monotonically-increasing.png differ diff --git a/documentation/docs/public/assets/design/trend-increasing.png b/documentation/docs/public/assets/design/trend-increasing.png new file mode 100644 index 0000000000..d6ed9e0f60 Binary files /dev/null and b/documentation/docs/public/assets/design/trend-increasing.png differ diff --git a/documentation/docs/public/assets/perf/CosId-VS-Leaf.png b/documentation/docs/public/assets/perf/CosId-VS-Leaf.png new file mode 100644 index 0000000000..cccbe473f5 Binary files /dev/null and b/documentation/docs/public/assets/perf/CosId-VS-Leaf.png differ diff --git a/documentation/docs/public/assets/perf/MySqlChainIdBenchmark-Sample.png b/documentation/docs/public/assets/perf/MySqlChainIdBenchmark-Sample.png new file mode 100644 index 0000000000..de234ae0c6 Binary files /dev/null and b/documentation/docs/public/assets/perf/MySqlChainIdBenchmark-Sample.png differ diff --git a/documentation/docs/public/assets/perf/MySqlChainIdBenchmark-Throughput.png b/documentation/docs/public/assets/perf/MySqlChainIdBenchmark-Throughput.png new file mode 100644 index 0000000000..d9aee1f7f7 Binary files /dev/null and b/documentation/docs/public/assets/perf/MySqlChainIdBenchmark-Throughput.png differ diff --git a/documentation/docs/public/assets/perf/Percentile-Sample-Of-SegmentChainId.png b/documentation/docs/public/assets/perf/Percentile-Sample-Of-SegmentChainId.png new file mode 100644 index 0000000000..14ec72116d Binary files /dev/null and b/documentation/docs/public/assets/perf/Percentile-Sample-Of-SegmentChainId.png differ diff --git a/documentation/docs/public/assets/perf/RedisChainIdBenchmark-Sample.png b/documentation/docs/public/assets/perf/RedisChainIdBenchmark-Sample.png new file mode 100644 index 0000000000..c9fe114b99 Binary files /dev/null and b/documentation/docs/public/assets/perf/RedisChainIdBenchmark-Sample.png differ diff --git a/documentation/docs/public/assets/perf/RedisChainIdBenchmark-Throughput.png b/documentation/docs/public/assets/perf/RedisChainIdBenchmark-Throughput.png new file mode 100644 index 0000000000..e46194362c Binary files /dev/null and b/documentation/docs/public/assets/perf/RedisChainIdBenchmark-Throughput.png differ diff --git a/documentation/docs/public/assets/perf/Throughput-Of-SegmentChainId-Previous.png b/documentation/docs/public/assets/perf/Throughput-Of-SegmentChainId-Previous.png new file mode 100644 index 0000000000..1f3aa7eae1 Binary files /dev/null and b/documentation/docs/public/assets/perf/Throughput-Of-SegmentChainId-Previous.png differ diff --git a/documentation/docs/public/assets/perf/Throughput-Of-SegmentChainId.png b/documentation/docs/public/assets/perf/Throughput-Of-SegmentChainId.png new file mode 100644 index 0000000000..70370e457c Binary files /dev/null and b/documentation/docs/public/assets/perf/Throughput-Of-SegmentChainId.png differ diff --git a/documentation/docs/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-PreciseShardingValue.png b/documentation/docs/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-PreciseShardingValue.png new file mode 100644 index 0000000000..145dce306c Binary files /dev/null and b/documentation/docs/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-PreciseShardingValue.png differ diff --git a/documentation/docs/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-RangeShardingValue.png b/documentation/docs/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-RangeShardingValue.png new file mode 100644 index 0000000000..88293091d5 Binary files /dev/null and b/documentation/docs/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-RangeShardingValue.png differ diff --git a/documentation/docs/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-PreciseShardingValue.png b/documentation/docs/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-PreciseShardingValue.png new file mode 100644 index 0000000000..fe8be422c9 Binary files /dev/null and b/documentation/docs/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-PreciseShardingValue.png differ diff --git a/documentation/docs/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-RangeShardingValue.png b/documentation/docs/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-RangeShardingValue.png new file mode 100644 index 0000000000..7771c3cb17 Binary files /dev/null and b/documentation/docs/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-RangeShardingValue.png differ diff --git a/documentation/docs/public/assets/shardingsphere/CosId-Integration-ShardingSphere-750x375.png b/documentation/docs/public/assets/shardingsphere/CosId-Integration-ShardingSphere-750x375.png new file mode 100644 index 0000000000..085e1a9901 Binary files /dev/null and b/documentation/docs/public/assets/shardingsphere/CosId-Integration-ShardingSphere-750x375.png differ diff --git a/documentation/docs/public/assets/shardingsphere/KeyGenerateAlgorithm-class-diagram.png b/documentation/docs/public/assets/shardingsphere/KeyGenerateAlgorithm-class-diagram.png new file mode 100644 index 0000000000..bee53abe88 Binary files /dev/null and b/documentation/docs/public/assets/shardingsphere/KeyGenerateAlgorithm-class-diagram.png differ diff --git a/documentation/docs/public/assets/shardingsphere/ShardingAlgorithm-class-diagram.png b/documentation/docs/public/assets/shardingsphere/ShardingAlgorithm-class-diagram.png new file mode 100644 index 0000000000..9055ebc584 Binary files /dev/null and b/documentation/docs/public/assets/shardingsphere/ShardingAlgorithm-class-diagram.png differ diff --git a/documentation/docs/public/assets/shardingsphere/sharding-db.png b/documentation/docs/public/assets/shardingsphere/sharding-db.png new file mode 100644 index 0000000000..5b4dece102 Binary files /dev/null and b/documentation/docs/public/assets/shardingsphere/sharding-db.png differ diff --git a/documentation/docs/public/assets/spring-boot-starter/swagger-ui.png b/documentation/docs/public/assets/spring-boot-starter/swagger-ui.png new file mode 100644 index 0000000000..9354cc6ecb Binary files /dev/null and b/documentation/docs/public/assets/spring-boot-starter/swagger-ui.png differ diff --git a/documentation/docs/public/favicon.ico b/documentation/docs/public/favicon.ico new file mode 100644 index 0000000000..e3964b7d40 Binary files /dev/null and b/documentation/docs/public/favicon.ico differ diff --git a/documentation/docs/public/icons/logo-180x180.png b/documentation/docs/public/icons/logo-180x180.png new file mode 100644 index 0000000000..f903660aee Binary files /dev/null and b/documentation/docs/public/icons/logo-180x180.png differ diff --git a/documentation/docs/public/icons/logo-512x512.png b/documentation/docs/public/icons/logo-512x512.png new file mode 100644 index 0000000000..b1ff1acb7a Binary files /dev/null and b/documentation/docs/public/icons/logo-512x512.png differ diff --git a/documentation/docs/public/icons/logo-transparent.png b/documentation/docs/public/icons/logo-transparent.png new file mode 100644 index 0000000000..61740484fc Binary files /dev/null and b/documentation/docs/public/icons/logo-transparent.png differ diff --git a/documentation/docs/public/logo.png b/documentation/docs/public/logo.png new file mode 100644 index 0000000000..45615f3f67 Binary files /dev/null and b/documentation/docs/public/logo.png differ diff --git a/documentation/docs/reference/blog/ShardingSphere-Integration-CosId.md b/documentation/docs/reference/blog/ShardingSphere-Integration-CosId.md new file mode 100644 index 0000000000..7ee60d0b0b --- /dev/null +++ b/documentation/docs/reference/blog/ShardingSphere-Integration-CosId.md @@ -0,0 +1,275 @@ +

+ ShardingSphere 集成 CosId +

+ +# ShardingSphere 集成 CosId 实战 + +## 背景 + +在软件系统演进过程中,随着业务规模的增长 (TPS/存储容量),我们需要通过集群化部署来分摊计算、存储压力。 +应用服务的无状态设计使其具备了伸缩性。在使用 **Kubernetes** 部署时我们只需要一行命令即可完成服务伸缩 +(`kubectl scale --replicas=5 deployment/order-service`)。 + +但对于有状态的数据库就不那么容易了,此时数据库变成系统的性能瓶颈是显而易见的。 + +### 分库分表 + +> 从微服务的角度来理解垂直拆分其实就是微服务拆分。以限界上下文来定义服务边界将大服务/单体应用拆分成多个自治的粒度更小的服务,因为自治性规范要求,数据库也需要进行业务拆分。 +> 但垂直拆分后的单个微服务依然会面临 TPS/存储容量 的挑战,所以这里我们重点讨论水平拆分的方式。 + +

+ 分库分表 +

+ +数据库分库分表方案是逻辑统一,物理分区自治的方案。其核心设计在于中间层映射方案的设计 (上图 **Mapping**),即分片算法的设计。 +几乎所有编程语言都内置实现了散列表(java:`HashMap`/csharp:`Dictionary`/python:`dict`/go:`map` ...)。分片算法跟散列表高度相似(`hashCode`),都得通过 `key`/`shardingValue` 映射到对应的槽位(`slot`)。 + +那么 `shardingValue` 从哪里来呢?**CosId**!!! + +### CosId:分布式 ID 生成器 + +*[CosId](https://github.com/Ahoo-Wang/CosId)* 旨在提供通用、灵活、高性能的分布式 ID 生成器。**CosId** 目前提供了以下三种算法: + +- `SnowflakeId` : *单机 TPS 性能:409W/s* , 主要解决 *时钟回拨问题* 、*机器号分配问题* 并且提供更加友好、灵活的使用体验。 +- `SegmentId`: 每次获取一段 (`Step`) ID,来降低号段分发器的网络IO请求频次提升性能,提供多种存储后端:关系型数据库、**Redis**、**Zookeeper** 供用户选择。 +- `SegmentChainId`(**推荐**):`SegmentChainId` (*lock-free*) 是对 `SegmentId` 的增强。性能可达到近似 `AtomicLong` 的 *TPS 性能:12743W+/s*。 + +`shardingValue` 问题解决了,但这就够了吗?**ShardingSphere**!!! + +> 摘自 **CosId** 官网: + +### ShardingSphere + +Apache ShardingSphere 是一款开源分布式数据库生态项目,由 JDBC、Proxy 和 Sidecar(规划中) 3 款产品组成。其核心采用可插拔架构,通过组件扩展功能。对上以数据库协议及 SQL 方式提供诸多增强功能,包括数据分片、访问路由、数据安全等;对下原生支持 MySQL、PostgreSQL、SQL Server、Oracle 等多种数据存储引擎。Apache ShardingSphere 项目理念,是提供数据库增强计算服务平台,进而围绕其上构建生态。充分利用现有数据库的计算与存储能力,通过插件化方式增强其核心能力,为企业解决在数字化转型中面临的诸多使用难点,为加速数字化应用赋能。 + +> 摘自 **Apache ShardingSphere** 官网: + +接下来进入本文的主要内容:如何基于 **ShardingSphere** 可插拔架构(SPI)来集成 **CosId**,以及应用配置指南。 + +## 安装 + +> 以 **Spring-Boot 应用** 为例 + +- ShardingSphere v5.1.0+ + +> 因为 `ShardingSphere v5.1.0` [PR](https://github.com/apache/shardingsphere/pull/14132),已经合并了 [cosid-shardingsphere](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-shardingsphere) 模块,所以只需要引用 `ShardingSphere` 依赖即可。 + +``` xml + + org.apache.shardingsphere + shardingsphere-jdbc-core-spring-boot-starter + 5.1.1 + +``` + +- ShardingSphere v5.0.0 + +``` xml + + org.apache.shardingsphere + shardingsphere-jdbc-core-spring-boot-starter + 5.0.0 + + + me.ahoo.cosid + cosid-shardingsphere + 1.8.15 + +``` + +## 分布式 ID + +> `KeyGenerateAlgorithm` + +### UML Class Diagram + +

+ KeyGenerateAlgorithm +

+ +> 上图展示了目前所有 `ShardingSphere` 内置的 `KeyGenerateAlgorithm` 实现,这里我们只讲 `CosIdKeyGenerateAlgorithm` ,其他实现请阅读。 + +### CosIdKeyGenerateAlgorithm + +#### 配置 + +> type: COSID + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------|----------|-------------------------------------------------|-------------| +| id-name | `String` | `IdGenerator` 的名称(在 `IdGeneratorProvider` 中已注册) | `__share__` | +| as-string | `String` | 是否生成字符串类型的ID | `fasle` | + +```yaml +spring: + shardingsphere: + rules: + sharding: + key-generators: + cosid: + type: COSID + props: + id-name: __share__ +``` + +## 分片算法 + +> `ShardingAlgorithm` + +### UML Class Diagram + +

+ ShardingAlgorithm +

+ +### CosIdModShardingAlgorithm + +CosId取模分片算法 + +#### 算法说明 + +

+ CosIdModShardingAlgorithm +

+ +> 单值分片键(`PreciseShardingValue`)算法复杂度:`O(1)`。 +> +> 范围值分片键(`RangeShardingValue`)算法复杂度:`O(N)`,其中`N`为范围值个数。 + +#### 性能基准测试 + +| 精确值/单值(**PreciseShardingValue**) | 范围值/多值(**RangeShardingValue**) | +|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| | | + +#### 配置 + +> type: COSID_MOD + +| 名称 | 数据类型 | 说明 | 默认值 | +|-------------------|----------|------------|-----| +| logic-name-prefix | `String` | 逻辑表/数据源名前缀 | | +| mod | `int` | 除数 | | + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_MOD + props: + mod: 4 + logic-name-prefix: t_table_ +``` + +### CosIdIntervalShardingAlgorithm + +基于间隔的时间范围分片算法。 + +#### 算法说明 + +

+ CosIdIntervalShardingAlgorithm +

+ +> 精确值/单值分片键(`PreciseShardingValue`)算法复杂度:`O(1)`。 +> +> 范围值分片键(`RangeShardingValue`)算法复杂度:`O(N)`,其中`N`为范围值单位时间个数。 + +#### 性能基准测试 + +| 精确值/单值(**PreciseShardingValue**) | 范围值/多值(**RangeShardingValue**) | +|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| | | + +#### 配置 + +> type: COSID_INTERVAL + +| 名称 | 数据类型 | 说明 | 默认值 | +|--------------------------|--------------|-------------------------------------|----------------------------------| +| logic-name-prefix | `String` | 逻辑表/数据源名前缀 | | +| datetime-lower | `String` | 时间分片下界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| datetime-upper | `String` | 时间分片上界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| sharding-suffix-pattern | `String` | 分片真实表/数据源后缀格式 | | +| datetime-interval-unit | `ChronoUnit` | 分片键时间间隔单位 | | +| datetime-interval-amount | `int` | 分片键时间间隔 | | +| ts-unit | `String` | 时间戳单位:`SECOND`/`MILLISECOND` | `MILLISECOND` | +| zone-id | `String` | 分片键时区 | `ZoneId.systemDefault().getId()` | + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_INTERVAL + props: + logic-name-prefix: logic-name-prefix + datetime-lower: 2021-12-08 22:00:00 + datetime-upper: 2022-12-01 00:00:00 + sharding-suffix-pattern: yyyyMM + datetime-interval-unit: MONTHS + datetime-interval-amount: 1 +``` + +### CosIdSnowflakeIntervalShardingAlgorithm + +#### 算法说明 + +我们知道 *SnowflakeId* 的位分区方式,*SnowflakeId* 可以解析出时间戳,即 *SnowflakeId* 可以作为时间,所以 *SnowflakeId* 可以作为 *INTERVAL* 的分片算法的分片值。 +(当没有`CreateTime`可用作分片时[这是一个非常极端的情况],或者对性能有非常极端的要求时, *分布式ID主键* 作为查询范围可能是持久层性能更好的选择。 ) + +#### 配置 + +> type: COSID_INTERVAL_SNOWFLAKE + +| 名称 | 数据类型 | 说明 | 默认值 | +|--------------------------|--------------|-------------------------------------------------|-------------| +| logic-name-prefix | `String` | 逻辑表/数据源名前缀 | | +| datetime-lower | `String` | 时间分片下界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| datetime-upper | `String` | 时间分片上界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| sharding-suffix-pattern | `String` | 分片真实表/数据源后缀格式 | | +| datetime-interval-unit | `ChronoUnit` | 分片键时间间隔单位 | | +| datetime-interval-amount | `int` | 分片键时间间隔 | | +| id-name | `String` | `IdGenerator` 的名称(在 `IdGeneratorProvider` 中已注册) | `__share__` | + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_INTERVAL_SNOWFLAKE + props: + logic-name-prefix: logic-name-prefix + datetime-lower: 2021-12-08 22:00:00 + datetime-upper: 2022-12-01 00:00:00 + sharding-suffix-pattern: yyyyMM + datetime-interval-unit: MONTHS + datetime-interval-amount: 1 + id-name: cosid-name +``` + +## 总结 + +本文主要讨论了分库分表产生的背景以及如何基于 **ShardingSphere** 可插拔架构集成 **CosId** 的应用实战。 +**ShardingSphere** 采用可插拔架构,使得开发者非常方便的自定义满足自身应用场景的功能扩展,如果你也对参与 **ShardingSphere** 社区贡献感兴趣请参考 。 + +## 阅读源码的小技巧之类图 + +相信很多小伙伴在阅读源码过程中总是难以自拔的遍历式以方法为单位一行行查看源码的实现细节,以至于迷失在细节中(如果你还能坚持下来,那真是佩服你的毅力之坚韧!)。这样的阅读方式是非常糟糕的、低效的。 +阅读源码跟阅读书籍一样有非常多的相似之处:先建立一个概览图(索引),然后再逐层往下精进。(自上而下的方式更有利于阅读过程中不迷失在具体细节中) +推荐大家使用IDEA的插件 *Diagrams* 用于生成源码级别的概览图:UML类图。 + +> - IntelliJ IDEA: + +## 引用说明 + +- ShardingSphere 官方文档: +- IntelliJ IDEA: +- CosId-ShardingSphere: diff --git a/documentation/docs/reference/config/basic.md b/documentation/docs/reference/config/basic.md new file mode 100644 index 0000000000..a8c065288e --- /dev/null +++ b/documentation/docs/reference/config/basic.md @@ -0,0 +1,52 @@ +# 基础配置 + +> `me.ahoo.cosid.spring.boot.starter.CosIdProperties` + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------|-----------|----------------------|---------| +| enabled | `boolean` | 是否启用 CosId | `true` | +| namespace | `String` | 命名空间,用于隔离不同应用间的分布式ID | `cosid` | + +**YAML 配置样例** + +```yaml +cosid: + namespace: ${spring.application.name} +``` + +## IdConverterDefinition + +> `me.ahoo.cosid.spring.boot.starter.IdConverterDefinition` + +| 名称 | 数据类型 | 说明 | 默认值 | +|--------|-------------------------------|------------------------------------------------|-----------------------------| +| type | `IdConverterDefinition.Type` | 转换器类型:`TO_STRING`、`SNOWFLAKE_FRIENDLY`、`RADIX` | `Type.RADIX` | +| prefix | `String` | 前缀 | `""` | +| radix | `IdConverterDefinition.Radix` | `Radix62IdConverter` 转换器配置 | `TimestampUnit.MILLISECOND` | + +### Radix + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------|-----------|-------------------------------------------------------|---------| +| char-size | `String` | 字符串ID长度 | `11` | +| pad-start | `boolean` | 当字符串不满足 `charSize` 时,是否填充字符(`'0'`)。如果需要保证字符串有序,需开启该功能 | `false` | + +**YAML 配置样例** + +```yaml +cosid: + snowflake: + share: + converter: + prefix: cosid_ + radix: + pad-start: false + char-size: 11 + segment: + share: + converter: + prefix: cosid_ + radix: + pad-start: false + char-size: 8 +``` diff --git a/documentation/docs/reference/config/cosid-generator.md b/documentation/docs/reference/config/cosid-generator.md new file mode 100644 index 0000000000..dc30a050ad --- /dev/null +++ b/documentation/docs/reference/config/cosid-generator.md @@ -0,0 +1,26 @@ +# Machine 配置 + +> `me.ahoo.cosid.spring.boot.starter.cosid.CosIdGeneratorProperties` + +| 名称 | 数据类型 | 说明 | 默认值 | +|---------------|-----------|-----------------|-------------------| +| enabled | `boolean` | 是否启用 | `false` | +| type | `enum` | 格式化类型:`RADIX62` | `RADIX36` | `RADIX62` | +| namespace | `String` | 命令空间 | `cosid.namespace` | +| timestamp-bit | `int` | 时间戳位数 | `44` | +| machine-bit | `int` | 机器位数 | `20` | +| sequence-bit | `int` | 序列位数 | `16` | + + +## 配置案例 + +```yaml {7-8} +cosid: + namespace: ${spring.application.name} + machine: + enabled: true + distributor: + type: jdbc + generator: + enabled: true +``` diff --git a/documentation/docs/reference/config/machine.md b/documentation/docs/reference/config/machine.md new file mode 100644 index 0000000000..2ee8dcf748 --- /dev/null +++ b/documentation/docs/reference/config/machine.md @@ -0,0 +1,65 @@ +# Machine 配置 + +> `me.ahoo.cosid.spring.boot.starter.machine.MachineProperties` + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------------|------------------|----------------------|-----------| +| stable | `boolean` | 是否为稳定的实例,稳定实例将不回收机器号 | `false` | +| port | `Integer` | 端口号 | 进程ID(PID) | +| instanceId | `String` | 应用实例编号(全局唯一) | 应用IP:PID | +| machineBit | `int` | 机器位数 | `10` | +| stateStorage | `StateStorage` | 机器状态存储 | | +| distributor | `Distributor` | 机器号分发器 | | +| guarder | `Guarder` | 机器号(心跳)守护 | | +| clock-backwards | `ClockBackwards` | 时钟回拨配置 | | + +### StateStorage + +> 状态存储配置 + +| 名称 | 数据类型 | 说明 | 默认值 | +|---------|----------------------|------------|--------| +| local | `StateStorage.Local` | 本地机器状态存储配置 | | + +#### StateStorage.Local + +| 名称 | 数据类型 | 说明 | 默认值 | +|----------------|----------|--------|--------------------------| +| state-location | `String` | 状态存储位置 | `./cosid-machine-state/` | + +### Distributor + +> 机器号分配器配置 + +| 名称 | 数据类型 | 说明 | 默认值 | +|--------|----------------------|-------------------------------------------------------------|----------| +| type | `Distributor.Type` | 机器号分配器类型:`MANUAL`/`STATEFUL_SET`/`JDBC`/`REDIS`/`ZOOKEEPER` | `MANUAL` | +| manual | `Distributor.Manual` | 手动分配器配置 | | + +#### Distributor.Manual + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------|-----------|-----|--------| +| machineId | `Integer` | 机器号 | `null` | + +## ClockBackwards + +> `me.ahoo.cosid.spring.boot.starter.machine.MachineProperties.ClockBackwards` + +| 名称 | 数据类型 | 说明 | 默认值 | +|------------------|-------|----------------------------------------------|--------| +| spin-threshold | `int` | 自旋同步阈值(ms) | `10` | +| broken-threshold | `int` | 抛出异常(`ClockTooManyBackwardsException`)阈值(ms) | `2000` | + +**YAML 配置样例** + +```yaml +cosid: + namespace: ${spring.application.name} + machine: + enabled: true + distributor: + type: jdbc + guarder: + enabled: true +``` diff --git a/documentation/docs/reference/config/segment.md b/documentation/docs/reference/config/segment.md new file mode 100644 index 0000000000..6b36edae84 --- /dev/null +++ b/documentation/docs/reference/config/segment.md @@ -0,0 +1,88 @@ +# SegmentId 配置 + +> `me.ahoo.cosid.spring.boot.starter.segment.SegmentIdProperties` + +| 名称 | 数据类型 | 说明 | 默认值 | +|-------------|-----------------------------|----------------------------|------------------------| +| enabled | `boolean` | 是否启用 | `false` | +| mode | `Mode` | 号段生成器模式:`DEFAULT`/ `CHAIN` | `CHAIN` | +| ttl | `long` | 号段的生存期(秒) | `TIME_TO_LIVE_FOREVER` | +| distributor | `Distributor` | 号段分发器 | | +| chain | `Chain` | 号段链模式配置 | | +| share | `IdDefinition` | 共享ID生成器配置 | | +| provider | `Map` | 多ID生成器配置 | | + +## Distributor + +> `me.ahoo.cosid.spring.boot.starter.segment.SegmentIdProperties.Distributor` + +| 名称 | 数据类型 | 说明 | 默认值 | +|------|--------------------|-------------------------------------|--------------| +| type | `Distributor.Type` | 号段分发器类型: `REDIS`/`JDBC`/`ZOOKEEPER` | `Type.REDIS` | +| jdbc | `Distributor.Jdbc` | Jdbc号段生成器配置 | | + +### Distributor.Jdbc + +| 名称 | 数据类型 | 说明 | 默认值 | +|------------------------------|-----------|----------------|---------| +| enable-auto-init-cosid-table | `boolean` | 自动创建号段`cosid`表 | `false` | +| enable-auto-init-id-segment | `boolean` | 自动创建号段行 | `true` | + +## Chain + +> `me.ahoo.cosid.spring.boot.starter.segment.SegmentIdProperties.Chain` + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------------|------------------------|------------|--------| +| safe-distance | `int` | 安全距离 | `10` | +| prefetch-worker | `Chain.PrefetchWorker` | 号段预取工作者线程池 | `true` | + +### Chain.PrefetchWorker + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------------|------------|-------|----------------------------------------------| +| prefetch-period | `Duration` | 预取周期 | `Duration.ofSeconds(1)` | +| core-pool-size | `int` | 线程池大小 | `Runtime.getRuntime().availableProcessors()` | + +## IdDefinition + +> `me.ahoo.cosid.spring.boot.starter.segment.SegmentIdProperties.IdDefinition` + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------|-------------------------|----------------------------|-----------------------| +| mode | `Mode` | 号段生成器模式:`DEFAULT`/ `CHAIN` | `cosid.segment.mode` | +| offset | `int` | 号段初始偏移量 | `0` | +| step | `long` | 步长 | 100 | +| ttl | `long` | 号段的生存期(秒) | `cosid.segment.ttl` | +| chain | `Chain` | 号段链模式配置 | `cosid.segment.chain` | +| converter | `IdConverterDefinition` | Id转换器配置 | | + +**YAML 配置样例** + +```yaml +cosid: + namespace: ${spring.application.name} + segment: + enabled: true + mode: chain + chain: + safe-distance: 5 + prefetch-worker: + core-pool-size: 2 + prefetch-period: 1s + distributor: + type: redis + share: + offset: 0 + step: 100 + converter: + prefix: cosid_ + type: radix + radix: + char-size: 6 + pad-start: false + provider: + order: + offset: 10000 + step: 100 +``` diff --git a/documentation/docs/reference/config/shardingsphere.md b/documentation/docs/reference/config/shardingsphere.md new file mode 100644 index 0000000000..33afda3565 --- /dev/null +++ b/documentation/docs/reference/config/shardingsphere.md @@ -0,0 +1,125 @@ +# ShardingSphere 配置 + +::: tip 维护说明 +`CosIdKeyGenerateAlgorithm`、`CosIdModShardingAlgorithm`、`CosIdIntervalShardingAlgorithm` 已合并至 [ShardingSphere](https://github.com/apache/shardingsphere/pull/14132) 官方,未来 *[cosid-shardingsphere](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-shardingsphere)* 模块的维护可能会以官方为主。 +::: + +## CosIdKeyGenerateAlgorithm + +> type: COSID + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------|----------|-------------------------------------------------|-------------| +| id-name | `String` | `IdGenerator` 的名称(在 `IdGeneratorProvider` 中已注册) | `__share__` | +| as-string | `String` | 是否生成字符串类型的ID | `fasle` | + +**YAML 配置样例** + +```yaml +spring: + shardingsphere: + rules: + sharding: + key-generators: + cosid: + type: COSID + props: + id-name: __share__ +``` + +## CosIdIntervalShardingAlgorithm + +> type: COSID_INTERVAL + +| 名称 | 数据类型 | 说明 | 默认值 | +|--------------------------|--------------|-------------------------------------|----------------------------------| +| logic-name-prefix | `String` | 逻辑表/数据源名前缀 | | +| datetime-lower | `String` | 时间分片下界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| datetime-upper | `String` | 时间分片上界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| sharding-suffix-pattern | `String` | 分片真实表/数据源后缀格式 | | +| datetime-interval-unit | `ChronoUnit` | 分片键时间间隔单位 | | +| datetime-interval-amount | `int` | 分片键时间间隔 | | +| ts-unit | `String` | 时间戳单位:`SECOND`/`MILLISECOND` | `MILLISECOND` | +| zone-id | `String` | 分片键时区 | `ZoneId.systemDefault().getId()` | + +**YAML 配置样例** + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_INTERVAL + props: + logic-name-prefix: logic-name-prefix + datetime-lower: 2021-12-08 22:00:00 + datetime-upper: 2022-12-01 00:00:00 + sharding-suffix-pattern: yyyyMM + datetime-interval-unit: MONTHS + datetime-interval-amount: 1 +``` + +## SnowflakeIntervalShardingAlgorithm + +::: tip 算法说明 +我们知道*SnowflakeId*的位分区方式,*SnowflakeId*可以解析出时间戳,即*SnowflakeId*可以作为时间,所以*SnowflakeId*可以作为*INTERVAL*的分配算法。 +(当没有`CreateTime`可用作分片时[这是一个非常极端的情况],或者对性能有非常极端的要求时,*分布式ID主键*作为查询范围可能是持久层性能更好的选择。 ) +::: + +> type: COSID_INTERVAL_SNOWFLAKE + +| 名称 | 数据类型 | 说明 | 默认值 | +|--------------------------|--------------|-------------------------------------------------|-------------| +| logic-name-prefix | `String` | 逻辑表/数据源名前缀 | | +| datetime-lower | `String` | 时间分片下界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| datetime-upper | `String` | 时间分片上界值,时间戳格式:`yyyy-MM-dd HH:mm:ss` | | +| sharding-suffix-pattern | `String` | 分片真实表/数据源后缀格式 | | +| datetime-interval-unit | `ChronoUnit` | 分片键时间间隔单位 | | +| datetime-interval-amount | `int` | 分片键时间间隔 | | +| id-name | `String` | `IdGenerator` 的名称(在 `IdGeneratorProvider` 中已注册) | `__share__` | + +**YAML 配置样例** + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_INTERVAL + props: + logic-name-prefix: logic-name-prefix + datetime-lower: 2021-12-08 22:00:00 + datetime-upper: 2022-12-01 00:00:00 + sharding-suffix-pattern: yyyyMM + datetime-interval-unit: MONTHS + datetime-interval-amount: 1 + id-name: cosid-name +``` + +## CosIdModShardingAlgorithm + +> type: COSID_MOD + +| 名称 | 数据类型 | 说明 | 默认值 | +|-------------------|----------|------------|-----| +| logic-name-prefix | `String` | 逻辑表/数据源名前缀 | | +| mod | `int` | 除数 | | + +**YAML 配置样例** + +```yaml +spring: + shardingsphere: + rules: + sharding: + sharding-algorithms: + alg-name: + type: COSID_MOD + props: + mod: 4 + logic-name-prefix: t_table_ +``` diff --git a/documentation/docs/reference/config/snowflake.md b/documentation/docs/reference/config/snowflake.md new file mode 100644 index 0000000000..b50cdbfdf3 --- /dev/null +++ b/documentation/docs/reference/config/snowflake.md @@ -0,0 +1,52 @@ +# SnowflakeId 配置 + +> `me.ahoo.cosid.spring.boot.starter.snowflake.SnowflakeIdProperties` + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------------|-----------------------------|-----------|-------------------------------------------------| +| enabled | `boolean` | 是否启用 | `false` | +| zone-id | `String` | 时区 | `ZoneId.systemDefault().getId()` | +| epoch | `long` | EPOCH | `CosId.COSID_EPOCH`
(UTC 2019-12-24 16:00) | +| machine | `Machine` | 机器号分配器配置 | | +| share | `IdDefinition` | 共享ID生成器配置 | 有 | +| provider | `Map` | 多ID生成器配置 | `null` | + +## IdDefinition + +> `me.ahoo.cosid.spring.boot.starter.snowflake.SnowflakeIdProperties.IdDefinition` + +| 名称 | 数据类型 | 说明 | 默认值 | +|----------------|------------------------------|----------------------------------|---------------------------------------| +| clock-sync | `boolean` | 是否开启时钟同步 | `true` | +| friendly | `boolean` | 是否启用`SnowflakeFriendlyId` | `true` | +| timestamp-unit | `IdDefinition.TimestampUnit` | 时间戳位的单位:`SECOND` / `MILLISECOND` | `TimestampUnit.MILLISECOND` | +| epoch | `int` | EPOCH | `cosid.snowflake.epoch` | +| timestamp-bit | `int` | 时间戳位数 | 41 | +| machine-bit | `int` | 机器位数 | `cosid.snowflake.machine.machine-bit` | +| sequence-bit | `int` | 序列位数 | 12 | +| converter | `IdConverterDefinition` | Id转换器配置 | | + +**YAML 配置样例** + +```yaml +cosid: + namespace: ${spring.application.name} + snowflake: + enabled: true + zone-id: Asia/Shanghai + epoch: 1577203200000 + share: + clock-sync: true + friendly: true + provider: + short_id: + converter: + prefix: cosid_ + type: radix + radix: + char-size: 11 + pad-start: false + safe-js: + machine-bit: 3 + sequence-bit: 9 +``` diff --git a/documentation/docs/reference/config/zookeeper.md b/documentation/docs/reference/config/zookeeper.md new file mode 100644 index 0000000000..a65be40e52 --- /dev/null +++ b/documentation/docs/reference/config/zookeeper.md @@ -0,0 +1,40 @@ +# ZooKeeper 配置 + +> `me.ahoo.cosid.spring.boot.starter.zookeeper.CosIdZookeeperProperties` + +| 名称 | 数据类型 | 说明 | 默认值 | +|----------------------------|------------|-----------------|--------------------------| +| enabled | `boolean` | 是否开启*ZooKeeper* | true | +| connect-string | `String` | 链接字符串 | `localhost:2181` | +| block-until-connected-wait | `Duration` | 阻塞直到客户端已连接等待时间 | `Duration.ofSeconds(10)` | +| session-timeout | `Duration` | 会话超时时间 | `Duration.ofSeconds(60` | +| connection-timeout | `Duration` | 连接超时时间 | `Duration.ofSeconds(15)` | +| retry | `Retry` | 重试策略配置 | | + +## Retry (`ExponentialBackoffRetry`) 配置 + +| 名称 | 数据类型 | 说明 | 默认值 | +|-----------------|-------|-------------------|-------| +| baseSleepTimeMs | `int` | 重试之间等待的初始时间量 (毫秒) | `100` | +| maxRetries | `int` | 最大重试次数 | `5` | +| maxSleepMs | `int` | 每次重试时的最大睡眠时间(毫秒) | `500` | + +**YAML 配置样例** + +```yaml +cosid: + zookeeper: + enabled: true + connect-string: localhost:2181 + retry: + base-sleep-time-ms: 100 + max-retries: 5 + max-sleep-ms: 500 + block-until-connected-wait: 10s + segment: + distributor: + type: zookeeper + machine: + distributor: + type: zookeeper +``` diff --git a/documentation/docs/reference/showcase/who-is-using.md b/documentation/docs/reference/showcase/who-is-using.md new file mode 100644 index 0000000000..f11d6b94b9 --- /dev/null +++ b/documentation/docs/reference/showcase/who-is-using.md @@ -0,0 +1,14 @@ +# 谁在使用 CosId + +## 开源项目 + +- [ShardingSphere](https://github.com/apache/shardingsphere): 分布式SQL事务和查询引擎,用于在任何数据库上进行数据分片、扩展、加密等 +- [Wow](https://github.com/Ahoo-Wang/Wow): 基于 DDD & EventSourcing 的现代响应式 CQRS 架构微服务开发框架 +- [CoSky](https://github.com/Ahoo-Wang/CoSky): 高性能、低成本微服务治理平台 +- [CoSec](https://github.com/Ahoo-Wang/CoSec): 基于 RBAC 和策略的多租户响应式安全框架 +- [CoCache](https://github.com/Ahoo-Wang/CoCache): 分布式一致性二级缓存框架 +- [Simba](https://github.com/Ahoo-Wang/Simba): 易用、灵活的分布式锁服务 + +## 公司 + +- [买道传感网](https://www.51mydao.com) \ No newline at end of file diff --git a/documentation/package.json b/documentation/package.json new file mode 100644 index 0000000000..0cbba5cc53 --- /dev/null +++ b/documentation/package.json @@ -0,0 +1,19 @@ +{ + "name": "CosId", + "version": "1.0.0", + "description": "通用、灵活、高性能的分布式ID生成器", + "main": "index.js", + "repository": "https://github.com/Ahoo-Wang/CosId", + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "author": "ahoo wang", + "license": "Apache 2.0", + "devDependencies": { + "mermaid": "^10.6.1", + "vitepress": "^1.0.0-rc.33", + "vitepress-plugin-mermaid": "^2.0.16" + } +} diff --git a/examples/cosid-example-proxy/build.gradle.kts b/examples/cosid-example-proxy/build.gradle.kts index ce54653879..c12885e01d 100644 --- a/examples/cosid-example-proxy/build.gradle.kts +++ b/examples/cosid-example-proxy/build.gradle.kts @@ -30,7 +30,7 @@ application { "-XX:MaxDirectMemorySize=256M", "-Xss1m", "-server", - "-XX:+UseG1GC", + "-XX:+UseZGC", "-Xlog:gc*:file=logs/${applicationName}-gc.log:time,tags:filecount=10,filesize=32M", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=data", diff --git a/examples/cosid-example-redis-cosid/build.gradle.kts b/examples/cosid-example-redis-cosid/build.gradle.kts index 672aca62b0..93494fe65f 100644 --- a/examples/cosid-example-redis-cosid/build.gradle.kts +++ b/examples/cosid-example-redis-cosid/build.gradle.kts @@ -30,7 +30,7 @@ application { "-XX:MaxDirectMemorySize=256M", "-Xss1m", "-server", - "-XX:+UseG1GC", + "-XX:+UseZGC", "-Xlog:gc*:file=logs/${applicationName}-gc.log:time,tags:filecount=10,filesize=32M", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=data", diff --git a/examples/cosid-example-redis-cosid/src/main/resources/application.yaml b/examples/cosid-example-redis-cosid/src/main/resources/application.yaml index 2646633e9f..286d594b19 100644 --- a/examples/cosid-example-redis-cosid/src/main/resources/application.yaml +++ b/examples/cosid-example-redis-cosid/src/main/resources/application.yaml @@ -1,10 +1,20 @@ +management: + endpoints: + web: + exposure: + include: + - cosid + - cosidGenerator + - cosidStringGenerator + server: port: 8610 spring: application: name: ${service.name:cosid-example-redis} - redis: - host: localhost + data: + redis: + host: localhost cosid: namespace: ${spring.application.name} machine: diff --git a/examples/cosid-example-redis/build.gradle.kts b/examples/cosid-example-redis/build.gradle.kts index f1f887204b..99e05c97e7 100644 --- a/examples/cosid-example-redis/build.gradle.kts +++ b/examples/cosid-example-redis/build.gradle.kts @@ -30,7 +30,7 @@ application { "-XX:MaxDirectMemorySize=256M", "-Xss1m", "-server", - "-XX:+UseG1GC", + "-XX:+UseZGC", "-Xlog:gc*:file=logs/${applicationName}-gc.log:time,tags:filecount=10,filesize=32M", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=data", @@ -49,12 +49,11 @@ dependencies { annotationProcessor(platform(project(":cosid-dependencies"))) implementation(project(":cosid-spring-boot-starter")) implementation(project(":cosid-jackson")) - implementation(project(":cosid-spring-redis")) implementation("org.springframework.boot:spring-boot-starter-data-redis") implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("com.google.guava:guava") - implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-webflux") compileOnly("org.projectlombok:lombok") annotationProcessor("org.projectlombok:lombok") annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") diff --git a/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/CustomIdConverter.java b/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/CustomIdConverter.java new file mode 100644 index 0000000000..1f28f2d403 --- /dev/null +++ b/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/CustomIdConverter.java @@ -0,0 +1,31 @@ +/* + * Copyright [2021-present] [ahoo wang (https://github.com/Ahoo-Wang)]. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package me.ahoo.cosid.example.redis.controller; + +import me.ahoo.cosid.IdConverter; + +import javax.annotation.Nonnull; + +public class CustomIdConverter implements IdConverter { + @Nonnull + @Override + public String asString(long id) { + return String.valueOf(id); + } + + @Override + public long asLong(@Nonnull String idString) { + return Long.getLong(idString); + } +} diff --git a/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/IdController.java b/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/IdController.java index a59a03062b..84ddcb55f1 100644 --- a/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/IdController.java +++ b/examples/cosid-example-redis/src/main/java/me/ahoo/cosid/example/redis/controller/IdController.java @@ -16,6 +16,7 @@ import me.ahoo.cosid.provider.IdGeneratorProvider; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -33,17 +34,17 @@ public IdController(IdGeneratorProvider provider) { this.provider = provider; } - @GetMapping - public long generate() { + @GetMapping("{idName}") + public long generate(@PathVariable String idName) { return provider - .getShare() + .getRequired(idName) .generate(); } - @GetMapping("/as-string") - public String generateAsString() { + @GetMapping("/{idName}/as-string") + public String generateAsString(@PathVariable String idName) { return provider - .getShare() + .getRequired(idName) .generateAsString(); } diff --git a/examples/cosid-example-redis/src/main/resources/application.yaml b/examples/cosid-example-redis/src/main/resources/application.yaml index ff74f2d7a7..d27fdbdf78 100644 --- a/examples/cosid-example-redis/src/main/resources/application.yaml +++ b/examples/cosid-example-redis/src/main/resources/application.yaml @@ -1,31 +1,134 @@ +management: + endpoints: + web: + exposure: + include: + - cosid + - cosidGenerator + - cosidStringGenerator +springdoc: + show-actuator: true server: port: 8601 spring: application: name: ${service.name:cosid-example-redis} - redis: - host: localhost + data: + redis: + host: localhost cosid: namespace: ${spring.application.name} machine: enabled: true distributor: type: redis - guarder: - enabled: true + generator: + enabled: true snowflake: - enabled: false + enabled: true + share: + enabled: false + provider: + snowflake_friendly: + converter: + type: snowflake_friendly + snowflake_short_id: + converter: + type: radix + radix: + char-size: 11 + pad-start: true + snowflake_friendly_second: + timestamp-unit: second + epoch: 1577203200 + timestamp-bit: 31 + machine-bit: 10 + sequence-bit: 22 + converter: + type: snowflake_friendly segment: enabled: true mode: chain distributor: type: redis share: - converter: - type: to_string - to-string: - pad-start: true - + enabled: false + provider: + biz_prefix_no: + offset: 2000000000 + converter: + type: to_string + prefix: BIZ + to-string: + char-size: 10 + pad-start: true + date_prefix_no: + converter: + type: to_string + prefix: BIZ- + date-prefix: + enabled: true + pattern: yyMMdd + no_suffix_biz: + offset: 2000000000 + converter: + type: to_string + suffix: BIZ + to-string: + char-size: 10 + pad-start: true + biz_prefix_radix: + offset: 2000000000 + converter: + type: radix + prefix: BIZ + radix: + char-size: 6 + pad-start: true + biz_prefix_radix36: + converter: + type: radix36 + prefix: BIZ + radix36: + char-size: 8 + pad-start: true + group_year_biz: + group: + by: year + pattern: yyyy + converter: + type: to_string + to-string: + pad-start: true + char-size: 8 + prefix: BIZ- + group-prefix: + enabled: true + group_year_month_biz: + group: + by: year_month + pattern: yyyyMM + converter: + type: to_string + to-string: + pad-start: true + char-size: 8 + prefix: BIZ- + group-prefix: + enabled: true + group_year_month_day_biz: + group: + by: year_month_day + pattern: yyMMdd + converter: + type: to_string + to-string: + pad-start: true + char-size: 8 + prefix: BIZ- + group-prefix: + enabled: true logging: level: me.ahoo.cosid: debug + diff --git a/examples/cosid-example-zookeeper/build.gradle.kts b/examples/cosid-example-zookeeper/build.gradle.kts index 99f4d3c804..b2b3463b31 100644 --- a/examples/cosid-example-zookeeper/build.gradle.kts +++ b/examples/cosid-example-zookeeper/build.gradle.kts @@ -30,7 +30,7 @@ application { "-XX:MaxDirectMemorySize=256M", "-Xss1m", "-server", - "-XX:+UseG1GC", + "-XX:+UseZGC", "-Xlog:gc*:file=logs/${applicationName}-gc.log:time,tags:filecount=10,filesize=32M", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:HeapDumpPath=data", diff --git a/gradle.properties b/gradle.properties index a746437d64..73d3045fa2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,14 +10,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # - group=me.ahoo.cosid -version=1.19.3 - +version=2.9.0 description=Universal, flexible, high-performance distributed ID generator. website=https://github.com/Ahoo-Wang/CosId issues=https://github.com/Ahoo-Wang/CosId/issues vcs=https://github.com/Ahoo-Wang/CosId.git - license_name=The Apache Software License, Version 2.0 license_url=https://www.apache.org/licenses/LICENSE-2.0.txt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..420ff482df --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,47 @@ +[versions] +# libraries +springBoot = "2.7.12" +springCloud = "2021.0.7" +okhttp = "4.12.0" +testcontainers = "1.19.8" +guava = "33.2.1-jre" +mybatis = "3.5.16" +mybatisSpringBoot = "3.0.3" +junitPioneer = "1.9.1" +axon = "4.9.4" +flowable = "6.8.1" +activiti = "7.0.0.SR1" +springDoc = "1.8.0" +hamcrest = "2.2" +jmh = "1.37" +# plugins +testRetry= "1.5.9" +publishPlugin = "2.0.0" +jmhPlugin = "0.7.2" +spotbugs = "6.0.18" + +[libraries] +springBootDependencies = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springBoot" } +springCloudDependencies = { module = "org.springframework.cloud:spring-cloud-dependencies", version.ref = "springCloud" } +okhttpBom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" } +axonBom = { module = "org.axonframework:axon-bom", version.ref = "axon" } +testcontainersBom = { module = "org.testcontainers:testcontainers-bom", version.ref = "testcontainers" } +guava = { module = "com.google.guava:guava", version.ref = "guava" } +mybatis = { module = "org.mybatis:mybatis", version.ref = "mybatis" } +mybatisSpringBoot = { module = "org.mybatis.spring.boot:mybatis-spring-boot-starter", version.ref = "mybatisSpringBoot" } +springDocStarterWebfluxUi = { module = "org.springdoc:springdoc-openapi-starter-webflux-ui", version.ref = "springDoc" } +activitiEngine = { module = "org.activiti:activiti-engine", version.ref = "activiti" } +activitiSpringBootStarter = { module = "org.activiti:activiti-spring-boot-starter", version.ref = "activiti" } +flowableEngineCommon = { module = "org.flowable:flowable-engine-common", version.ref = "flowable" } +flowableSpring = { module = "org.flowable:flowable-spring", version.ref = "flowable" } +flowableSpringBootAutoconfigure = { module = "org.flowable:flowable-spring-boot-autoconfigure", version.ref = "flowable" } +junitPioneer = { module = "org.junit-pioneer:junit-pioneer", version.ref = "junitPioneer" } +hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } +jmhCore = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } +jmhGeneratorAnnprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } +[plugins] +testRetry = { id = "org.gradle.test-retry", version.ref = "testRetry" } +publishPlugin = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "publishPlugin" } +jmhPlugin = { id = "me.champeau.jmh", version.ref = "jmhPlugin" } +spotbugsPlugin = { id = "com.github.spotbugs", version.ref = "spotbugs" } + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e2..e6441136f3 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3f0..a4413138c9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb43..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -130,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85bee..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle.kts b/settings.gradle.kts index ade6c4f75e..f2765c6403 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,8 +26,11 @@ include(":cosid-test") include(":cosid-proxy") include(":cosid-proxy-server") include(":cosid-axon") +include(":cosid-flowable") +include(":cosid-activiti") include(":cosid-mongo") - +include(":cosid-spring-data-jdbc") +include(":cosid-mod-test") include(":code-coverage-report") include("cosid-example-proxy") @@ -42,14 +45,3 @@ project(":cosid-example-redis-cosid").projectDir = file("examples/cosid-example- include("cosid-example-zookeeper") project(":cosid-example-zookeeper").projectDir = file("examples/cosid-example-zookeeper") -buildscript { - repositories { - gradlePluginPortal() - } - dependencies { - classpath("me.champeau.jmh:jmh-gradle-plugin:0.7.1") - classpath("io.github.gradle-nexus:publish-plugin:1.3.0") - classpath("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14") - } -} - diff --git a/wiki/getting-started.md b/wiki/getting-started.md index 73a54b56dc..0dbb80d8c2 100644 --- a/wiki/getting-started.md +++ b/wiki/getting-started.md @@ -21,7 +21,7 @@ ![Snowflake](../document/docs/.vuepress/public/assets/design/Snowflake-identifier.png) > *SnowflakeId*使用`Long`(64-bit)位分区来生成ID的一种分布式ID算法。 -> 通用的位分配方案为:`timestamp`(64-bit)+`machineId`(10-bit)+`sequence`(12-bit)=63bit。 +> 通用的位分配方案为:`timestamp`(41-bit)+`machineId`(10-bit)+`sequence`(12-bit)=63bit。 - 41-bit `timestamp` = (1L<<41)/(1000/3600/24/365) 约可以存储 69 年的时间戳,即可以使用的绝对时间为 `EPOCH` + 69 年,一般我们需要自定义 `EPOCH` 为产品开发时间,另外还可以通过压缩其他区域的分配位数,来增加时间戳位数来延长可用时间。