初始提交
Docker Image Deploy / Push Docker image (push) Waiting to run Details
Benchmark CosId Test / CosId Core Benchmark (push) Has been cancelled Details
Benchmark CosId Test / CosId Spring Redis Benchmark (push) Has been cancelled Details
Benchmark CosId Test / CosId Jdbc Benchmark (push) Has been cancelled Details
Benchmark VS Test / CosId VS Leaf (push) Has been cancelled Details
Codecov / Codecov (push) Has been cancelled Details
Documentation Build and Deploy / build-and-deploy (push) Has been cancelled Details
Example Test / Example Redis Test (push) Has been cancelled Details
Example Test / Example Zookeeper Test (push) Has been cancelled Details
Example Test / Example Proxy Test (push) Has been cancelled Details
Integration Test / CosId Core Test (push) Has been cancelled Details
Integration Test / CosId Jackson Test (push) Has been cancelled Details
Integration Test / CosId Axon Test (push) Has been cancelled Details
Integration Test / CosId Activiti Test (push) Has been cancelled Details
Integration Test / CosId Flowable Test (push) Has been cancelled Details
Integration Test / CosId Mybatis Test (push) Has been cancelled Details
Integration Test / CosId Spring Jdbc Test (push) Has been cancelled Details
Integration Test / CosId Spring Redis Test (push) Has been cancelled Details
Integration Test / CosId Mongo Test (push) Has been cancelled Details
Integration Test / CosId Proxy Test (push) Has been cancelled Details
Integration Test / CosId Zookeeper Test (push) Has been cancelled Details
Integration Test / CosId Jdbc Test (push) Has been cancelled Details
Integration Test / CosId Spring Boot Starter Test (push) Has been cancelled Details
CodeQL / Analyze (java) (push) Has been cancelled Details

This commit is contained in:
Your Name 2025-09-19 11:54:38 +08:00
commit 0a27835ec5
759 changed files with 48930 additions and 0 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,28 @@
---
name: "\U0001F41B Bug Report"
about: Something isn't working as expected
---
## Bug Report
Before report a bug, make sure you have:
- Searched open and closed [GitHub issues](https://github.com/Ahoo-Wang/CosId/issues).
- Read documentation: [CosId Doc](https://cosid.ahoo.me/).
Please pay attention on issues you submitted, because we maybe need more details.
If no response anymore and we cannot reproduce it on current information, we will **close it**.
Please answer these questions before submitting your issue. Thanks!
### Which version of CosId did you use?
### Expected behavior
### Actual behavior
### Reason analyze (If you can)
### Steps to reproduce the behavior
### Example codes for reproduce this issue (such as a github link).

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

6
.github/PULL_REQUEST_TEMPLATE vendored Normal file
View File

@ -0,0 +1,6 @@
Fixes #ISSUSE_ID.
Changes proposed in this pull request:
-
-
-

107
.github/workflows/benchmark-test.yml vendored Normal file
View File

@ -0,0 +1,107 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
#
## :::::: WARNING ::::::
## https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
## :::::::::::::::::::::
## 因受到 GitHub Runner 资源限制,运行在 GitHub Runner 中的基准测试与真实环境基准测试对比有非常大的差距近2倍
## 但是对于运行在同一环境配置资源情况下(都运行在 GitHub Runner进行 commit 前后的基准对比、以及第三方库的对比依然是有价值。
## :::::::::::::::::::::
## Due to the resource constraints of GitHub runner, the benchmark running in GitHub runner is very different from the benchmark in the real environment (nearly twice),
## However, it is still valuable to compare the benchmark before and after commit and the third-party library when running in the same environment and configuring resources (both running in GitHub runner).
## :::::::::::::::::::::
name: Benchmark CosId Test
on:
push:
paths:
- 'cosid-core/**'
- 'cosid-spring-redis/**'
- 'cosid-jdbc/**'
pull_request:
paths:
- 'cosid-core/**'
- 'cosid-spring-redis/**'
- 'cosid-jdbc/**'
workflow_dispatch:
jobs:
cosid-core-benchmark:
name: CosId Core Benchmark
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: CosId-Core
run: gradle cosid-core:clean cosid-core:jmh
cosid-spring-redis-benchmark:
name: CosId Spring Redis Benchmark
runs-on: ubuntu-latest
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: CosId-Spring-Redis
run: ./gradlew cosid-spring-redis:clean cosid-spring-redis:jmh
cosid-jdbc-benchmark:
name: CosId Jdbc Benchmark
runs-on: ubuntu-latest
steps:
- name: Start Mysql
run: sudo /etc/init.d/mysql start
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Init CosId-Db
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

75
.github/workflows/benchmark-vs-test.yml vendored Normal file
View File

@ -0,0 +1,75 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
#
## :::::: WARNING ::::::
## https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
## :::::::::::::::::::::
## 因受到 GitHub Runner 资源限制,运行在 GitHub Runner 中的基准测试与真实环境基准测试对比有非常大的差距近2倍
## 但是对于运行在同一环境配置资源情况下(都运行在 GitHub Runner进行 commit 前后的基准对比、以及第三方库的对比依然是有价值。
## :::::::::::::::::::::
## Due to the resource constraints of GitHub runner, the benchmark running in GitHub runner is very different from the benchmark in the real environment (nearly twice),
## However, it is still valuable to compare the benchmark before and after commit and the third-party library when running in the same environment and configuring resources (both running in GitHub runner).
## :::::::::::::::::::::
name: Benchmark VS Test
on:
push:
paths:
- 'cosid-benchmark/**'
workflow_dispatch:
jobs:
# WARNING中央仓库没有找到美团官方提供的Jar!!!
# git clone https://github.com/Meituan-Dianping/Leaf
# mvn install -Dmaven.test.skip=true
benchmark-vs-leaf:
name: CosId VS Leaf
runs-on: ubuntu-latest
steps:
- name: Checkout Leaf
uses: actions/checkout@master
with:
repository: Meituan-Dianping/Leaf
# - name: Cache local Maven repository
# id: cache-maven-repo
# uses: actions/cache@v2
# with:
# path: ~/.m2/repository
# key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
# restore-keys: |
# ${{ runner.os }}-maven-
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Install Leaf Package
run: mvn install -Dmaven.test.skip=true
- name: Start Mysql
run: sudo /etc/init.d/mysql start
- name: Checkout CosId
uses: actions/checkout@master
- name: Init Benchmark-Db
run: mysql -vvv -h localhost -uroot -proot < cosid-benchmark/src/jmh/init/init-script.sql
- name: Benchmark Test
working-directory: cosid-benchmark
run: ./gradlew clean jmh

70
.github/workflows/codecov.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: Codecov
on:
push:
paths-ignore:
- 'cosid-benchmark/**'
- 'docs/**'
- 'document/**'
- 'documentation/**'
- 'examples/**'
- 'wiki/**'
pull_request:
paths-ignore:
- 'cosid-benchmark/**'
- 'docs/**'
- 'document/**'
- 'documentation/**'
- 'examples/**'
- 'wiki/**'
env:
CI: true
jobs:
codecov:
name: Codecov
runs-on: ubuntu-latest
env:
MYSQL: 5.1
CODECOV: true
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Start Mysql
run: sudo /etc/init.d/mysql start
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Init CosId-Db
run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql
- name: Build Code Coverage Report
run: ./gradlew codeCoverageReport --stacktrace
- name: Upload Code Coverage Report to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unittests # optional
name: codecov-umbrella # optional
fail_ci_if_error: true # optional (default = false)
verbose: true # optional (default = false)
# directory: ./build/reports/jacoco/codeCoverageReport/
files: ./code-coverage-report/build/reports/jacoco/codeCoverageReport/codeCoverageReport.xml
path_to_write_report: ./coverage/codecov_report.txt

77
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,77 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
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'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4 # v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
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.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# 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@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

110
.github/workflows/docker-deploy.yml vendored Normal file
View File

@ -0,0 +1,110 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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/**'
- 'documentation/**'
- 'examples/**'
- 'wiki/**'
branches:
- '**'
tags:
- 'v*.*.*'
pull_request:
paths-ignore:
- 'cosid-benchmark/**'
- 'docs/**'
- 'document/**'
- 'documentation/**'
- 'examples/**'
- 'wiki/**'
branches:
- 'main'
workflow_dispatch:
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 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Build Dist
run: ./gradlew cosid-proxy-server:installDist
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to AliyunCR
uses: docker/login-action@v3
with:
registry: registry.cn-shanghai.aliyuncs.com
username: ${{ secrets.ALIYUN_CR_USERNAME }}
password: ${{ secrets.ALIYUN_CR_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ahoowang/cosid-proxy
ghcr.io/ahoo-wang/cosid-proxy
registry.cn-shanghai.aliyuncs.com/ahoo/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@v6
with:
platforms: linux/amd64,linux/arm64
context: cosid-proxy-server
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

87
.github/workflows/document-deploy.yml vendored Normal file
View File

@ -0,0 +1,87 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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: Documentation Build and Deploy
on:
push:
paths:
- 'documentation/**'
pull_request:
paths:
- 'documentation/**'
workflow_dispatch:
permissions:
contents: write
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
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
cache-dependency-path: documentation/yarn.lock
cache: yarn
- name: Build VitePress site
working-directory: documentation
run: yarn add -D vitepress && yarn 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: yarn add -D vitepress && yarn 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:
# 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"

98
.github/workflows/example-test.yml vendored Normal file
View File

@ -0,0 +1,98 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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: Example Test
on:
push:
paths:
- 'examples/**'
pull_request:
paths:
- 'examples/**'
workflow_dispatch:
jobs:
example-redis-test:
name: Example Redis Test
runs-on: ubuntu-latest
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test Example-Redis
run: ./gradlew cosid-example-redis:clean cosid-example-redis:check
example-zookeeper-test:
name: Example Zookeeper Test
runs-on: ubuntu-latest
services:
zookeeper:
image: zookeeper
ports:
- 2181:2181
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test Example-Zookeeper
run: ./gradlew cosid-example-zookeeper:clean cosid-example-zookeeper:check
example-proxy-test:
name: Example Proxy Test
runs-on: ubuntu-latest
services:
zookeeper:
image: zookeeper
ports:
- 2181:2181
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test Example-Proxy
run: ./gradlew cosid-example-proxy:clean cosid-example-proxy:check

21
.github/workflows/gitee-sync.yml vendored Normal file
View File

@ -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"

305
.github/workflows/integration-test.yml vendored Normal file
View File

@ -0,0 +1,305 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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: Integration Test
on:
push:
paths-ignore:
- 'cosid-benchmark/**'
- 'docs/**'
- 'document/**'
- 'documentation/**'
- 'examples/**'
- 'wiki/**'
pull_request:
paths-ignore:
- 'cosid-benchmark/**'
- 'docs/**'
- 'document/**'
- 'documentation/**'
- 'examples/**'
- 'wiki/**'
env:
CI: true
jobs:
cosid-core-test:
name: CosId Core Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test CosId-Core
run: ./gradlew cosid-core:clean cosid-core:check
cosid-jackson-test:
name: CosId Jackson Test
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test CosId-Jackson
run: ./gradlew cosid-jackson:clean cosid-jackson:check
cosid-axon-test:
name: CosId Axon Test
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test CosId-Axon
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 17
uses: actions/setup-java@v4
with:
java-version: '17'
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 17
uses: actions/setup-java@v4
with:
java-version: '17'
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 17
uses: actions/setup-java@v4
with:
java-version: '17'
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 17
uses: actions/setup-java@v4
with:
java-version: '17'
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
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
services:
redis:
image: redis
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test CosId-Spring-Redis
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 17
uses: actions/setup-java@v4
with:
java-version: '17'
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
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test CosId-Proxy
run: ./gradlew cosid-proxy:clean cosid-proxy:check
cosid-zookeeper-test:
name: CosId Zookeeper Test
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Test CosId-Zookeeper
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/
cosid-jdbc-test:
name: CosId Jdbc Test
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
env:
MYSQL: 5.1
steps:
- name: Start Mysql
run: sudo /etc/init.d/mysql start
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Init CosId-Db
run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql
- name: Test CosId-Jdbc
run: ./gradlew cosid-jdbc:clean cosid-jdbc:check
cosid-spring-boot-starter-test:
name: CosId Spring Boot Starter Test
needs: [ cosid-core-test ]
runs-on: ubuntu-latest
steps:
- name: Start Mysql
run: sudo /etc/init.d/mysql start
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Init CosId-Db
run: mysql -vvv -h localhost -uroot -proot < cosid-jdbc/src/main/init-script/init-cosid-mysql.sql
- name: Test CosId-Spring-Boot-Starter
run: ./gradlew cosid-spring-boot-starter:clean cosid-spring-boot-starter:check

65
.github/workflows/package-deploy.yml vendored Normal file
View File

@ -0,0 +1,65 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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: Packages Deploy
on:
release:
types: [created]
jobs:
github-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Publish package
run: ./gradlew publishAllPublicationsToGitHubPackagesRepository
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGNING_KEYID: ${{ secrets.SIGNING_KEYID }}
SIGNING_SECRETKEY: ${{ secrets.SIGNING_SECRETKEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
central-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
server-id: github
settings-path: ${{ github.workspace }}
- name: Publish package
run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
SIGNING_KEYID: ${{ secrets.SIGNING_KEYID }}
SIGNING_SECRETKEY: ${{ secrets.SIGNING_SECRETKEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
.gradle
/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
/cosid-benchmark/.idea/**

View File

@ -0,0 +1,6 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value="Copyright [2021-present] [ahoo wang &lt;ahoowang@qq.com&gt; (https://github.com/Ahoo-Wang)].&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10; http://www.apache.org/licenses/LICENSE-2.0&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
<option name="myName" value="ahoo-wang" />
</copyright>
</component>

View File

@ -0,0 +1,7 @@
<component name="CopyrightManager">
<settings default="ahoo-wang">
<module2copyright>
<element module="All" copyright="ahoo-wang" />
</module2copyright>
</settings>
</component>

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2024] [ahoo wang <ahoowang@qq.com>]
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.

725
README.md Normal file
View File

@ -0,0 +1,725 @@
<p align="center" style="text-align:center">
<img width="300" src="./document/docs/.vuepress/public/logo.png"/>
</p>
# [CosId](https://cosid.ahoo.me/) Universal, flexible, high-performance distributed ID generator
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![GitHub release](https://img.shields.io/github/release/Ahoo-Wang/CosId.svg)](https://github.com/Ahoo-Wang/CosId/releases)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.ahoo.cosid/cosid-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/me.ahoo.cosid/cosid-core)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/dfd1d6237a1644409548ebfbca300dc1)](https://app.codacy.com/gh/Ahoo-Wang/CosId?utm_source=github.com&utm_medium=referral&utm_content=Ahoo-Wang/CosId&utm_campaign=Badge_Grade_Settings)
[![codecov](https://codecov.io/gh/Ahoo-Wang/CosId/branch/main/graph/badge.svg?token=L0N51NB7ET)](https://codecov.io/gh/Ahoo-Wang/CosId)
![Integration Test Status](https://github.com/Ahoo-Wang/CosId/actions/workflows/integration-test.yml/badge.svg)
> [中文文档](https://cosid.ahoo.me/)
## Introduction
*[CosId](https://github.com/Ahoo-Wang/CosId)* aims to provide a universal, flexible and high-performance distributed ID
generator.
- `CosIdGenerator` : Stand-alone *TPS performance15,570,085 ops/s* , three times that of `UUID.randomUUID()`,global trend increasing based-time.
- `SnowflakeId` : Stand-alone *TPS performance4,096,000 ops/s* [JMH Benchmark](#jmh-benchmark) , It mainly solves two major
problems of `SnowflakeId`: machine number allocation problem and clock backwards problem and provide a more friendly
and flexible experience.
- `SegmentId`: Get a segment (`Step`) ID every time to reduce the network IO request frequency of the `IdSegment`
distributor and improve performance.
- `IdSegmentDistributor`:
- `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) .
- `PrefetchWorker` maintains a safe distance (`safeDistance`), and supports dynamic `safeDistance` expansion and
contraction based on hunger status.
## SnowflakeId
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/Snowflake-identifier.png" alt="Snowflake"/>
</p>
> *SnowflakeId* is a distributed ID algorithm that uses `Long` (64-bit) bit partition to generate ID.
> The general bit allocation scheme is : `timestamp` (41-bit) + `machineId` (10-bit) + `sequence` (12-bit) = 63-bit。
- 41-bit `timestamp` = (1L<<41)/(1000/3600/24/365) approximately 69 years of timestamp can be stored, that is, the usable
absolute time is `EPOCH` + 69 years. Generally, we need to customize `EPOCH` as the product development time. In
addition, we can increase the number of allocated bits by compressing other areas The number of timestamp bits to
extend the available time.
- 10-bit `machineId` = (1L<<10) = 1024 That is, 1024 copies of the same business can be deployed (there is no
master-slave copy in the Kubernetes concept, and the definition of Kubernetes is directly used here) instances.
Generally, there is no need to use so many, so it will be redefined according to the scale of deployment.
- 12-bit `sequence` = (1L<<12) * 1000 = 4096000 That is, a single machine can generate about 409W ID per second, and a
global same-service cluster can generate `4096000*1024=4194304000=4.19 billion (TPS)`.
It can be seen from the design of SnowflakeId:
- :thumbsup: The first 41-bit are a `timestamp`,So *SnowflakeId* is local monotonically increasing, and affected by
global clock synchronization *SnowflakeId* is global trend increasing.
- :thumbsup: `SnowflakeId` does not have a strong dependency on any third-party middleware, and its performance is also
very high.
- :thumbsup: The bit allocation scheme can be flexibly configured according to the needs of the business system to
achieve the optimal use effect.
- :thumbsdown: Strong reliance on the local clock, potential clock moved backwards problems will cause ID duplication.
- :thumbsdown: The `machineId` needs to be set manually. If the `machineId` is manually assigned during actual
deployment, it will be very inefficient.
---
*[CosId-SnowflakeId](https://github.com/Ahoo-Wang/CosId/tree/main/cosid-core/src/main/java/me/ahoo/cosid/snowflake)*
It mainly solves two major problems of `SnowflakeId`: machine number allocation problem and clock backwards problem and
provide a more friendly and flexible experience.
### MachineIdDistributor
> Currently [CosId](https://github.com/Ahoo-Wang/CosId) provides the following three `MachineId` distributors.
#### ManualMachineIdDistributor
```yaml
cosid:
snowflake:
machine:
distributor:
type: manual
manual:
machine-id: 0
```
> Manually distribute `MachineId`
#### StatefulSetMachineIdDistributor
```yaml
cosid:
snowflake:
machine:
distributor:
type: stateful_set
```
> Use the stable identification ID provided by the `StatefulSet` of `Kubernetes` as the machine number.
#### RedisMachineIdDistributor
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/RedisMachineIdDistributor.png" alt="Redis Machine Id Distributor"/>
</p>
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/Machine-Id-Safe-Guard.png" alt="Machine Id Safe Guard"/>
</p>
```yaml
cosid:
snowflake:
machine:
distributor:
type: redis
```
> Use *Redis* as the distribution store for the machine number.
### ClockBackwardsSynchronizer
```yaml
cosid:
snowflake:
clock-backwards:
spin-threshold: 10
broken-threshold: 2000
```
The default `DefaultClockBackwardsSynchronizer` clock moved backwards synchronizer uses active wait synchronization
strategy, `spinThreshold` (default value 10 milliseconds) is used to set the spin wait threshold, when it is greater
than `spinThreshold`, use thread sleep to wait for clock synchronization, if it exceeds` BrokenThreshold` (default value
2 seconds) will directly throw a `ClockTooManyBackwardsException` exception.
### 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/
```
The default `LocalMachineStateStorage` local machine state storage uses a local file to store the machine number and the
most recent timestamp, which is used as a `MachineState` cache.
### ClockSyncSnowflakeId
```yaml
cosid:
snowflake:
share:
clock-sync: true
```
The default `SnowflakeId` will directly throw a `ClockBackwardsException` when a clock moved backwards occurs, while
using the `ClockSyncSnowflakeId` will use the `ClockBackwardsSynchronizer` to actively wait for clock synchronization to
regenerate the ID, providing a more user-friendly experience.
### SafeJavaScriptSnowflakeId
```java
SnowflakeId snowflakeId=SafeJavaScriptSnowflakeId.ofMillisecond(1);
```
The `Number.MAX_SAFE_INTEGER` of `JavaScript` has only 53-bit. If the 63-bit `SnowflakeId` is directly returned to the
front end, the value will overflow. Usually we can convert `SnowflakeId` to String type or customize `SnowflakeId` Bit
allocation is used to shorten the number of bits of `SnowflakeId` so that `ID` does not overflow when it is provided to
the front end.
### SnowflakeFriendlyId (Can parse `SnowflakeId` into a more readable `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
```
## SegmentId
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/SegmentId.png" alt="Segment Id"/>
</p>
### RedisIdSegmentDistributor
```yaml
cosid:
segment:
enabled: true
distributor:
type: redis
```
### JdbcIdSegmentDistributor
> Initialize the `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
```
After enabling `enable-auto-init-id-segment:true`, the application will try to create the `idSegment` record when it
starts to avoid manual creation. Similar to the execution of the following initialization sql script, there is no need
to worry about misoperation, because `name` is the primary key.
```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
distributor:
type: redis
share:
offset: 0
step: 100
provider:
bizC:
offset: 10000
step: 100
bizD:
offset: 10000
step: 100
```
## IdGeneratorProvider
```yaml
cosid:
snowflake:
provider:
bizA:
# timestamp-bit:
sequence-bit: 12
bizB:
# timestamp-bit:
sequence-bit: 12
```
```java
IdGenerator idGenerator=idGeneratorProvider.get("bizA");
```
In actual use, we generally do not use the same `IdGenerator` for all business services, but different businesses use
different `IdGenerator`, then `IdGeneratorProvider` exists to solve this problem, and it is the container
of `IdGenerator` , You can get the corresponding `IdGenerator` by the business name.
### CosIdPlugin (MyBatis Plugin)
> 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({
"<script>",
"insert into t_friendly_table (id)",
"VALUES" +
"<foreach item='item' collection='list' open='' separator=',' close=''>" +
"(#{item.id})" +
"</foreach>",
"</script>"})
void insertList(List<FriendlyIdEntity> list);
}
```
```java
LongIdEntity entity=new LongIdEntity();
entityRepository.insert(entity);
/**
* {
* "id": 208796080181248
* }
*/
return entity;
```
### ShardingSphere Plugin
> [cosid-shardingsphere](https://github.com/apache/shardingsphere/tree/master/features/sharding/plugin/cosid)
#### CosIdKeyGenerateAlgorithm (Distributed-Id)
```yaml
spring:
shardingsphere:
rules:
sharding:
key-generators:
cosid:
type: COSID
props:
id-name: __share__
```
#### Interval-based time range sharding algorithm
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/CosIdIntervalShardingAlgorithm.png" alt="CosIdIntervalShardingAlgorithm"/>
</p>
- Ease of use: supports multiple data types (`Long`/`LocalDateTime`/`DATE`/ `String` / `SnowflakeId`),The official
implementation is to first convert to a string and then convert to `LocalDateTime`, the conversion success rate is
affected by the time formatting characters.
- Performance: Compared to `org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm`
,The performance is *1200~4000* times higher.
| **PreciseShardingValue** | **RangeShardingValue** |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![Throughput Of IntervalShardingAlgorithm - PreciseShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-PreciseShardingValue.png) | ![Throughput Of IntervalShardingAlgorithm - RangeShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-RangeShardingValue.png) |
- CosIdIntervalShardingAlgorithm
- type: COSID_INTERVAL
```yaml
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
alg-name:
type: COSID_INTERVAL
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
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/CosIdModShardingAlgorithm.png" alt="CosId Mod Sharding Algorithm"/>
</p>
- Performance: Compared to `org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm`
,The performance is *1200~4000* times higher.And it has higher stability and no serious performance degradation.
| **PreciseShardingValue** | **RangeShardingValue** |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![Throughput Of ModShardingAlgorithm - PreciseShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-PreciseShardingValue.png) | ![Throughput Of ModShardingAlgorithm - RangeShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-RangeShardingValue.png) |
```yaml
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
alg-name:
type: COSID_MOD
props:
mod: 4
logic-name-prefix: t_table_
```
## Examples
> 项目中根据使用的场景(`jdbc`/`proxy`/`redis-cosid`/`redis`/`shardingsphere`/`zookeeper`等)提供了对应的例子,实践过程中可以参照配置快速接入。
[点击查看Examples](https://github.com/Ahoo-Wang/CosId/tree/main/examples)
## Installation
### Gradle
> Kotlin DSL
``` kotlin
val cosidVersion = "1.14.5";
implementation("me.ahoo.cosid:cosid-spring-boot-starter:${cosidVersion}")
```
### Maven
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>demo</artifactId>
<properties>
<cosid.version>1.14.5</cosid.version>
</properties>
<dependencies>
<dependency>
<groupId>me.ahoo.cosid</groupId>
<artifactId>cosid-spring-boot-starter</artifactId>
<version>${cosid.version}</version>
</dependency>
</dependencies>
</project>
```
### application.yaml
```yaml
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/cosid_db_0
username: root
password: root
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/cosid_db_1
username: root
password: root
props:
sql-show: true
rules:
sharding:
binding-tables:
- t_order,t_order_item
tables:
cosid:
actual-data-nodes: ds0.cosid
t_table:
actual-data-nodes: ds0.t_table_$->{0..1}
table-strategy:
standard:
sharding-column: id
sharding-algorithm-name: table-inline
t_date_log:
actual-data-nodes: ds0.t_date_log_202112
key-generate-strategy:
column: id
key-generator-name: snowflake
table-strategy:
standard:
sharding-column: create_time
sharding-algorithm-name: data-log-interval
sharding-algorithms:
table-inline:
type: COSID_MOD
props:
mod: 2
logic-name-prefix: t_table_
data-log-interval:
type: COSID_INTERVAL
props:
logic-name-prefix: t_date_log_
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
key-generators:
snowflake:
type: COSID
props:
id-name: snowflake
cosid:
namespace: ${spring.application.name}
machine:
enabled: true
# stable: true
# machine-bit: 10
# instance-id: ${HOSTNAME}
distributor:
type: redis
# manual:
# machine-id: 0
snowflake:
enabled: true
# epoch: 1577203200000
clock-backwards:
spin-threshold: 10
broken-threshold: 2000
share:
clock-sync: true
friendly: true
provider:
order_item:
# timestamp-bit:
sequence-bit: 12
snowflake:
sequence-bit: 12
safeJs:
machine-bit: 3
sequence-bit: 9
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
provider:
order:
offset: 10000
step: 100
longId:
offset: 10000
step: 100
```
## JMH-Benchmark
- The development notebook : MacBook Pro (M1)
- All benchmark tests are carried out on the development notebook.
- Deploying Redis on the development notebook.
### SnowflakeId
``` shell
gradle cosid-core:jmh
# or
java -jar cosid-core/build/libs/cosid-core-1.14.5-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
```
### Throughput (ops/s) of SegmentChainId
<p align="center" >
<img src="./document/docs/.vuepress/public/assets/perf/Throughput-Of-SegmentChainId.png" alt="Throughput-Of-SegmentChainId"/>
</p>
### Percentile-Sample (*P9999=0.208 us/op*) of SegmentChainId
> In statistics, a [percentile](https://en.wikipedia.org/wiki/Percentile) (or a centile) is a score below which a given percentage of scores in its frequency distribution falls (exclusive definition) or a score at or below which a given percentage falls (inclusive definition). For example, the 50th percentile (the median) is the score below which (exclusive) or at or below which (inclusive) 50% of the scores in the distribution may be found.
<p align="center" >
<img src="./document/docs/.vuepress/public/assets/perf/Percentile-Sample-Of-SegmentChainId.png" alt="Percentile-Sample-Of-SegmentChainId"/>
</p>
### CosId VS MeiTuan Leaf
> CosId (`SegmentChainId`) is 5 times faster than Leaf(`segment`).
<p align="center" >
<img src="./document/docs/.vuepress/public/assets/perf/CosId-VS-Leaf.png" alt="CosId VS MeiTuan Leaf"/>
</p>
## Community Partners and Sponsors
<a href="https://www.jetbrains.com/?from=CosId" target="_blank">
<img src="./docs/jetbrains-logo.png" title="JetBrains" width=130 />
</a>

366
README.zh-CN.md Normal file
View File

@ -0,0 +1,366 @@
<p align="center" style="text-align:center">
<img width="300" src="./document/docs/.vuepress/public/logo.png" alt="CosId Logo"/>
</p>
# [CosId](https://cosid.ahoo.me/) 通用、灵活、高性能分布式 ID 生成器
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[![GitHub release](https://img.shields.io/github/release/Ahoo-Wang/CosId.svg)](https://github.com/Ahoo-Wang/CosId/releases)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.ahoo.cosid/cosid-core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/me.ahoo.cosid/cosid-core)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/dfd1d6237a1644409548ebfbca300dc1)](https://app.codacy.com/gh/Ahoo-Wang/CosId?utm_source=github.com&utm_medium=referral&utm_content=Ahoo-Wang/CosId&utm_campaign=Badge_Grade_Settings)
[![codecov](https://codecov.io/gh/Ahoo-Wang/CosId/branch/main/graph/badge.svg?token=L0N51NB7ET)](https://codecov.io/gh/Ahoo-Wang/CosId)
![Integration Test Status](https://github.com/Ahoo-Wang/CosId/actions/workflows/integration-test.yml/badge.svg)
> [English Document](https://github.com/Ahoo-Wang/CosId/blob/main/README.md)
## 简介
*[CosId](https://github.com/Ahoo-Wang/CosId)* 旨在提供通用、灵活、高性能的分布式 ID 生成器。
- `CosIdGenerator` : *单机 TPS 性能1557W/s*,三倍于 `UUID.randomUUID()`基于时钟的全局趋势递增ID。
- `SnowflakeId` : *单机 TPS 性能409W/s* [JMH 基准测试](https://cosid.ahoo.me/guide/perf-test.html) , 主要解决 *时钟回拨问题* 、*机器号分配问题*、*取模分片不均匀问题* 并且提供更加友好、灵活的使用体验。
- `SegmentId`: 每次获取一段 (`Step`) ID来降低号段分发器的网络IO请求频次提升性能。
- `IdSegmentDistributor`: 号段分发器(号段存储器)
- `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`扩容/收缩。
## [快速开始](https://cosid.ahoo.me/guide/getting-started.html)
## 背景(为什么需要*分布式ID*
在软件系统演进过程中,随着业务规模的增长 (TPS/存储容量),我们需要通过集群化部署来分摊计算、存储压力。
应用服务的无状态设计使其具备了伸缩性。在使用 **Kubernetes** 部署时我们只需要一行命令即可完成服务伸缩
(`kubectl scale --replicas=5 deployment/order-service`)。
但对于有状态的数据库就不那么容易了,此时数据库变成系统的性能瓶颈是显而易见的。
### 分库分表
> 从微服务的角度来理解垂直拆分其实就是微服务拆分。以限界上下文来定义服务边界将大服务/单体应用拆分成多个自治的粒度更小的服务,因为自治性规范要求,数据库也需要进行业务拆分。
> 但垂直拆分后的单个微服务依然会面临 TPS/存储容量 的挑战,所以这里我们重点讨论水平拆分的方式。
<p align="center" >
<img src="document/docs/.vuepress/public/assets/shardingsphere/sharding-db.png" alt="分库分表"/>
</p>
数据库分库分表方案是逻辑统一,物理分区自治的方案。其核心设计在于中间层映射方案的设计 (上图 **Mapping**),即分片算法的设计。
几乎所有编程语言都内置实现了散列表(java:`HashMap`/csharp:`Dictionary`/python:`dict`/go:`map` ...)。分片算法跟散列表高度相似(`hashCode`),都得通过 `key`/`shardingValue` 映射到对应的槽位(`slot`)。
那么 `shardingValue` 从哪里来呢?**CosId**
> 当然还有很多分布式场景需要*分布式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是单调递增的比如像表的自增主键那样。但是很遗憾因全局时钟、性能等分布式系统问题我们通常只能选择局部单调递增、全局趋势递增的组合就像我们在分布式系统中不得不的选择最终一致性那样以获得多方面的权衡。下面我们来看一下什么是单调递增与趋势递增。
#### 有序性之单调递增
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/monotonically-increasing.png" alt="单调递增"/>
</p>
单调递增T表示全局绝对时点假设有T<sub>n+1</sub>>T<sub>n</sub>绝对时间总是往前进的这里不考虑相对论、时间机器等那么必然有F(T<sub>n+1</sub>)>F(T<sub>n</sub>),数据库自增主键就属于这一类。
另外需要特别说明的是单调递增跟连续性递增是不同的概念。 连续性递增:`F(n+1)=(F(n)+step)`即下一次获取的ID一定等于当前`ID+Step`,当`Step=1`时类似于这样一个序列:`1->2->3->4->5`。
> 扩展小知识:数据库的自增主键也不是连续性递增的,相信你一定遇到过这种情况,请思考一下数据库为什么这样设计?
#### 有序性之趋势递增
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/trend-increasing.png" alt="趋势递增"/>
</p>
趋势递增T<sub>n</sub>>T<sub>n-s</sub>那么大概率有F(T<sub>n</sub>)>F(T<sub>n-s</sub>)。虽然在一段时间间隔内有乱序,但是整体趋势是递增。从上图上看,是有上升趋势的(趋势线)。
- 在**SnowflakeId**中<sub>n-s</sub>受到全局时钟同步影响。
- 在号段模式(**SegmentId**)中<sub>n-s</sub>受到号段可用区间(`Step`)影响。
## 分布式ID分配方案
### UUID/GUID
- :thumbsup:不依赖任何第三方中间件
- :thumbsup:性能高
- :thumbsdown:完全无序
- :thumbsdown:空间占用大需要占用128位存储空间。
UUID最大的缺陷是随机的、无序的当用于主键时会导致数据库的主键索引效率低下为了维护索引树频繁的索引中间位置插入数据而不是追加写。这也是UUID不适用于数据库主键的最为重要的原因。
### SnowflakeId
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/Snowflake-identifier.png" alt="Snowflake 雪花算法"/>
</p>
> *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之机器号分配问题
在**SnowflakeId**中根据业务设计的位分配方案确定了基本上就不再有变更了,也很少需要维护。但是`machineId`总是需要配置的而且集群中是不能重复的否则分区原则就会被破坏而导致ID唯一性原则破坏当集群规模较大时`machineId`的维护工作是非常繁琐,低效的。
> 有一点需要特别说明的,**SnowflakeId**的**MachineId**是逻辑上的概念,而不是物理概念。
> 想象一下假设**MachineId**是物理上的,那么意味着一台机器拥有只能拥有一个**MachineId**,那会产生什么问题呢?
> 目前 *[CosId](https://github.com/Ahoo-Wang/CosId)* 提供了以下五种 `MachineId` 分配器。
- ManualMachineIdDistributor: 手动配置`machineId`,一般只有在集群规模非常小的时候才有可能使用,不推荐。
- StatefulSetMachineIdDistributor: 使用`Kubernetes`的`StatefulSet`提供的稳定的标识IDHOSTNAME=service-01作为机器号。
- RedisMachineIdDistributor: 使用**Redis**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。
- JdbcMachineIdDistributor: 使用**关系型数据库**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。
- ZookeeperMachineIdDistributor: 使用**ZooKeeper**作为机器号的分发存储,同时还会存储`MachineId`的上一次时间戳,用于**启动时时钟回拨**的检查。
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/RedisMachineIdDistributor.png" alt="Redis MachineId Distributor"/>
</p>
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/Machine-Id-Safe-Guard.png" alt="Machine Id Safe Guard"/>
</p>
#### 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
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/SegmentId.png" alt="Segment Id"/>
</p>
从上面的设计图中,不难看出**号段模式**基本设计思路是通过每次获取一定长度Step的可用IDId段/号段来降低网络IO请求次数提升性能。
- :thumbsdown:强依赖第三方号段分发器,可用性受到第三方分发器影响。
- :thumbsdown:每次号段用完时获取`NextMaxId`需要进行网络IO请求此时的性能会比较低。
- 单实例ID单调递增全局趋势递增。
- 从设计图中不难看出**Instance 1**每次获取的`NextMaxId`,一定比上一次大,意味着下一次的号段一定比上一次大,所以从单实例上来看是单调递增的。
- 多实例各自持有的不同的号段意味着同一时刻不同实例生成的ID是乱序的但是整体趋势的递增的所以全局趋势递增。
- ID乱序程度受到Step长度以及集群规模影响从趋势递增图中不难看出
- 假设集群中只有一个实例时**号段模式**就是单调递增的。
- `Step`越小,乱序程度越小。当`Step=1`时,将无限接近单调递增。需要注意的是这里是无限接近而非等于单调递增,具体原因你可以思考一下这样一个场景:
- 号段分发器T<sub>1</sub>时刻给**Instance 1**分发了`ID=1`,T<sub>2</sub>时刻给**Instance 2**分发了`ID=2`。因为机器性能、网络等原因,`Instance 2`网络IO写请求先于`Instance 1`到达。那么这个时候对于数据库来说ID依然是乱序的。
## 号段链模式SegmentChainId
[分布式ID(CosId)之号段链模式性能(1.2亿/s)解析](https://cosid.ahoo.me/guide/segment-chain.html)
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/SegmentChainId.png" alt="Segment Chain Id"/>
</p>
**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 基准测试](https://github.com/Ahoo-Wang/CosId/blob/main/README.zh-CN.md#jmh-benchmark) 。
- 适应性:从**SegmentId**介绍中我们知道了影响**ID乱序**的因素有俩个:集群规模、`Step`大小。集群规模是我们不能控制的,但是`Step`是可以调节的。
- `Step`应该近可能小才能使得**ID单调递增**的可能性增大。
- `Step`太小会影响吞吐量,那么我们如何合理设置`Step`呢答案是我们无法准确预估所有时点的吞吐量需求那么最好的办法是吞吐量需求高时Step自动增大吞吐量低时Step自动收缩。
- **SegmentChainId**引入了**饥饿状态**的概念,**PrefetchWorker**会根据**饥饿状态**检测当前**安全距离**是否需要膨胀或者收缩,以便获得吞吐量与有序性之间的权衡,这便是**SegmentChainId**的自适应性。
## 集成
### CosIdPluginMyBatis 插件)
> Kotlin DSL
``` kotlin
implementation("me.ahoo.cosid:cosid-mybatis:${cosidVersion}")
```
```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;
}
}
```
### ShardingSphere 插件
> [cosid-shardingsphere](https://github.com/apache/shardingsphere/tree/master/features/sharding/plugin/cosid)
#### CosIdKeyGenerateAlgorithm (分布式主键)
```yaml
spring:
shardingsphere:
rules:
sharding:
key-generators:
cosid:
type: COSID
props:
id-name: __share__
```
#### 基于间隔的时间范围分片算法
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/CosIdIntervalShardingAlgorithm.png" alt="CosId Interval Sharding Algorithm"/>
</p>
- 易用性: 支持多种数据类型 (`Long`/`LocalDateTime`/`DATE`/ `String` / `SnowflakeId`),而官方实现是先转换成字符串再转换成`LocalDateTime`,转换成功率受时间格式化字符影响。
- 性能 : 相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.datetime.IntervalShardingAlgorithm` 性能高出 *1200~4000* 倍。
| **PreciseShardingValue** | **RangeShardingValue** |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![Throughput Of IntervalShardingAlgorithm - PreciseShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-PreciseShardingValue.png) | ![Throughput Of IntervalShardingAlgorithm - RangeShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-IntervalShardingAlgorithm-RangeShardingValue.png) |
- 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
```
#### 取模分片算法
<p align="center">
<img src="./document/docs/.vuepress/public/assets/design/CosIdModShardingAlgorithm.png" alt="CosIdModShardingAlgorithm"/>
</p>
- 性能 : 相比于 `org.apache.shardingsphere.sharding.algorithm.sharding.mod.ModShardingAlgorithm` 性能高出 *1200~4000* 倍。并且稳定性更高,不会出现严重的性能退化。
| **PreciseShardingValue** | **RangeShardingValue** |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![Throughput Of ModShardingAlgorithm - PreciseShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-PreciseShardingValue.png) | ![Throughput Of ModShardingAlgorithm - RangeShardingValue](./document/docs/.vuepress/public/assets/perf/sharding/Throughput-Of-ModShardingAlgorithm-RangeShardingValue.png) |
```yaml
spring:
shardingsphere:
rules:
sharding:
sharding-algorithms:
alg-name:
type: COSID_MOD
props:
mod: 4
logic-name-prefix: t_table_
```
## 性能测试报告
### SegmentChainId-吞吐量 (ops/s)
<p align="center" >
<img src="./document/docs/.vuepress/public/assets/perf/Throughput-Of-SegmentChainId.png" alt="Throughput-Of-SegmentChainId"/>
</p>
### SegmentChainId-每次操作耗时的百分位数(us/op)
> [百分位数](https://zh.wikipedia.org/wiki/%E7%99%BE%E5%88%86%E4%BD%8D%E6%95%B0) 统计学术语若将一组数据从小到大排序并计算相应的累计百分点则某百分点所对应数据的值就称为这百分点的百分位数以Pk表示第k百分位数。百分位数是用来比较个体在群体中的相对地位量数。
<p align="center" >
<img src="./document/docs/.vuepress/public/assets/perf/Percentile-Sample-Of-SegmentChainId.png" alt="Percentile-Sample-Of-SegmentChainId"/>
</p>
### CosId VS 美团 Leaf
> CosId (`SegmentChainId`) 性能是 Leaf(`segment`) 的 5 倍。
<p align="center" >
<img src="./document/docs/.vuepress/public/assets/perf/CosId-VS-Leaf.png" alt="CosId VS 美团 Leaf"/>
</p>

258
build.gradle.kts Normal file
View File

@ -0,0 +1,258 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.testretry.TestRetryPlugin
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
*/
plugins {
alias(libs.plugins.test.retry)
alias(libs.plugins.publish)
alias(libs.plugins.jmh)
alias(libs.plugins.spotbugs)
`java-library`
jacoco
}
val dependenciesProject = project(":cosid-dependencies")
val bomProjects = setOf(
project(":cosid-bom"),
dependenciesProject,
)
val coreProjects = setOf(
project(":cosid-core")
)
val serverProjects = setOf(
project(":cosid-example-proxy"),
project(":cosid-example-redis"),
project(":cosid-example-redis-cosid"),
project(":cosid-example-zookeeper"),
project(":cosid-proxy-server")
)
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
val isInCI = null != System.getenv("CI")
ext.set("libraryProjects", libraryProjects)
allprojects {
repositories {
mavenLocal()
maven { url = uri("https://repo.spring.io/milestone") }
mavenCentral()
}
}
configure(bomProjects) {
apply<JavaPlatformPlugin>()
configure<JavaPlatformExtension> {
allowDependencies()
}
}
configure(libraryProjects) {
apply<CheckstylePlugin>()
configure<CheckstyleExtension> {
toolVersion = "9.2.1"
}
apply<com.github.spotbugs.snom.SpotBugsPlugin>()
configure<com.github.spotbugs.snom.SpotBugsExtension> {
excludeFilter.set(file("${rootDir}/config/spotbugs/exclude.xml"))
}
apply<JacocoPlugin>()
apply<JavaLibraryPlugin>()
configure<JavaPluginExtension> {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
withJavadocJar()
withSourcesJar()
}
apply<me.champeau.jmh.JMHPlugin>()
configure<me.champeau.jmh.JmhParameters> {
val delimiter = ',';
val jmhIncludesKey = "jmhIncludes"
val jmhExcludesKey = "jmhExcludes"
val jmhThreadsKey = "jmhThreads"
val jmhModeKey = "jmhMode"
if (project.hasProperty(jmhIncludesKey)) {
val jmhIncludes = project.properties[jmhIncludesKey].toString().split(delimiter)
includes.set(jmhIncludes)
}
if (project.hasProperty(jmhExcludesKey)) {
val jmhExcludes = project.properties[jmhExcludesKey].toString().split(delimiter)
excludes.set(jmhExcludes)
}
warmupIterations.set(1)
iterations.set(1)
resultFormat.set("json")
var jmhMode = listOf(
"thrpt"
)
if (project.hasProperty(jmhModeKey)) {
jmhMode = project.properties[jmhModeKey].toString().split(delimiter)
}
benchmarkMode.set(jmhMode)
var jmhThreads = 1
if (project.hasProperty(jmhThreadsKey)) {
jmhThreads = Integer.valueOf(project.properties[jmhThreadsKey].toString())
}
threads.set(jmhThreads)
fork.set(1)
jvmArgs.set(listOf("-Dlogback.configurationFile=${rootProject.rootDir}/config/logback-jmh.xml"))
}
apply<TestRetryPlugin>()
tasks.withType<Test> {
useJUnitPlatform()
testLogging {
exceptionFormat = TestExceptionFormat.FULL
}
jvmArgs =
listOf(
// fix logging missing code for JacocoPlugin
"-Dlogback.configurationFile=${rootProject.rootDir}/config/logback.xml",
"--add-opens=java.base/java.util=ALL-UNNAMED",
"--add-opens=java.base/java.lang=ALL-UNNAMED"
)
retry {
if (isInCI) {
maxRetries = 2
maxFailures = 20
}
failOnPassedAfterRetry = true
}
}
// SpringBoot 3.2.0: fix Failed to extract parameter names for CosIdEndpoint
tasks.withType<JavaCompile> {
options.compilerArgs.addAll(listOf("-parameters"))
}
dependencies {
api(platform(dependenciesProject))
annotationProcessor(platform(dependenciesProject))
testAnnotationProcessor(platform(dependenciesProject))
jmh(platform(dependenciesProject))
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
testCompileOnly("org.projectlombok:lombok")
testAnnotationProcessor("org.projectlombok:lombok")
implementation("com.google.guava:guava")
implementation("org.slf4j:slf4j-api")
testImplementation("ch.qos.logback:logback-classic")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.junit-pioneer:junit-pioneer")
testImplementation("org.hamcrest:hamcrest")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
jmh("org.openjdk.jmh:jmh-core")
jmh("org.openjdk.jmh:jmh-generator-annprocess")
}
}
configure(publishProjects) {
val isBom = bomProjects.contains(this)
apply<MavenPublishPlugin>()
apply<SigningPlugin>()
configure<PublishingExtension> {
repositories {
maven {
name = "projectBuildRepo"
url = uri(layout.buildDirectory.dir("repos"))
}
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/Ahoo-Wang/CosId")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
maven {
name = "LinYiPackages"
url = uri(project.properties["linyiPackageReleaseUrl"].toString())
credentials {
username = project.properties["linyiPackageUsername"]?.toString()
password = project.properties["linyiPackagePwd"]?.toString()
}
}
}
publications {
val publishName = if (isBom) "mavenBom" else "mavenLibrary"
val publishComponentName = if (isBom) "javaPlatform" else "java"
create<MavenPublication>(publishName) {
from(components[publishComponentName])
pom {
name.set(rootProject.name)
description.set(getPropertyOf("description"))
url.set(getPropertyOf("website"))
issueManagement {
system.set("GitHub")
url.set(getPropertyOf("issues"))
}
scm {
url.set(getPropertyOf("website"))
connection.set(getPropertyOf("vcs"))
}
licenses {
license {
name.set(getPropertyOf("license_name"))
url.set(getPropertyOf("license_url"))
distribution.set("repo")
}
}
developers {
developer {
id.set("ahoo-wang")
name.set("ahoo wang")
organization {
url.set(getPropertyOf("website"))
}
}
}
}
}
}
}
configure<SigningExtension> {
if (isInCI) {
val signingKeyId = System.getenv("SIGNING_KEYID")
val signingKey = System.getenv("SIGNING_SECRETKEY")
val signingPassword = System.getenv("SIGNING_PASSWORD")
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
}
if (isBom) {
sign(extensions.getByType(PublishingExtension::class).publications.get("mavenBom"))
} else {
sign(extensions.getByType(PublishingExtension::class).publications.get("mavenLibrary"))
}
}
}
nexusPublishing {
this.repositories {
sonatype {
username.set(System.getenv("MAVEN_USERNAME"))
password.set(System.getenv("MAVEN_PASSWORD"))
}
}
}
fun getPropertyOf(name: String) = project.properties[name]?.toString()

View File

@ -0,0 +1,49 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
*/
plugins {
base
java
id("jacoco-report-aggregation")
}
@Suppress("UNCHECKED_CAST")
val libraryProjects = rootProject.ext.get("libraryProjects") as Iterable<Project>
dependencies {
libraryProjects.forEach {
jacocoAggregation(it)
}
}
reporting {
reports {
val codeCoverageReport by creating(JacocoCoverageReport::class) {
testSuiteName = "test"
}
}
}
tasks.check {
dependsOn(tasks.named<JacocoReport>("codeCoverageReport"))
}
tasks.register<Javadoc>("aggregateJavadoc") {
title = "CosId | 通用、灵活、高性能的分布式 ID 生成器"
options.header("<a href='${project.properties["website"]}' target='_blank'>GitHub</a>")
options.destinationDirectory = rootProject.layout.buildDirectory.dir("aggregatedJavadoc").get().asFile
libraryProjects.forEach {
source += it.sourceSets["main"].allJava
classpath += it.sourceSets["main"].compileClasspath
}
}

18
codecov.yml Normal file
View File

@ -0,0 +1,18 @@
coverage:
range: 60..100
round: down
precision: 2
status:
patch:
default:
target: 60%
threshold: 1%
project:
default:
target: 80%
threshold: 1%
ignore:
- "cosid-test/.*"
- "examples/.*"
- "cosid-benchmark/.*"
- "cosid-proxy-server/.*"

View File

@ -0,0 +1,345 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Based on Google Java Style. (https://google.github.io/styleguide/javaguide.html)
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="error"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${config_loc}/suppressions.xml"/>
<property name="optional" value="false"/>
</module>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.org/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="LineLength">
<property name="fileExtensions" value="java"/>
<property name="max" value="200"/>
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="TreeWalker">
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport">
<property name="excludes"
value="org.hamcrest.Matchers.*,org.hamcrest.CoreMatchers.*,org.mockito.Mockito.*,org.mockito.ArgumentMatchers.*"/>
</module>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap">
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
</module>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces">
<property name="tokens"
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
</module>
<module name="LeftCurly">
<property name="tokens"
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT,
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,
COMPACT_CTOR_DEF"/>
</module>
<module name="SuppressionXpathSingleFilter">
<!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
<property name="id" value="RightCurlyAlone"/>
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
or preceding-sibling::*[last()][self::LCURLY]]"/>
</module>
<module name="WhitespaceAfter">
<property name="tokens"
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE,
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<property name="ignoreEnhancedForColon" value="false"/>
<property name="tokens"
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="tokens"
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,
COMPACT_CTOR_DEF"/>
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
ANNOTATION_DEF, RECORD_DEF"/>
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="PatternVariableName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="RecordComponentName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Record component name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="RecordTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Record type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName"/>
<module name="InterfaceTypeParameterName"/>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="4"/>
<property name="braceAdjustment" value="4"/>
<property name="caseIndent" value="4"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="4"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="0"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
RECORD_COMPONENT_DEF"/>
</module>
<module name="NoWhitespaceBeforeCaseDefaultColon"/>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<!-- <property name="sortImportsInGroupAlphabetically" value="true"/>-->
<property name="separateLineBetweenGroups" value="true"/>
<property name="specialImportsRegExp" value="^me\.ahoo\."/>
<property name="customImportOrderRules"
value="STATIC###SPECIAL_IMPORTS###THIRD_PARTY_PACKAGE###STANDARD_JAVA_PACKAGE"/>
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
</module>
<module name="MethodParamPad">
<property name="tokens"
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/>
</module>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT,
LABELED_STAT, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad">
<property name="tokens"
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,
RECORD_DEF"/>
</module>
<module name="OperatorWrap"/>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,
RECORD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="InvalidJavadocPosition"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="SummaryJavadoc"/>
<module name="JavadocParagraph"/>
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="accessModifiers" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="MissingJavadocMethod">
<!--TODO severity:error-->
<property name="severity" value="warning"/>
<property name="scope" value="public"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
</module>
<module name="MissingJavadocType">
<!--TODO severity:error-->
<property name="severity" value="warning"/>
<property name="scope" value="protected"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
RECORD_DEF, ANNOTATION_DEF"/>
<property name="excludeScope" value="nothing"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc"/>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation">
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
<!-- <module name="SuppressionXpathFilter">-->
<!-- <property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"-->
<!-- default="checkstyle-xpath-suppressions.xml"/>-->
<!-- <property name="optional" value="true"/>-->
<!-- </module>-->
</module>
</module>

View File

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<!--
~ Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
-->
<!DOCTYPE suppressions PUBLIC
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<suppress checks=".*" files=".*/src/test/.*"/>
<suppress checks=".*" files=".*/src/jmh/.*"/>
<suppress checks="InvalidJavadocPosition"/>
<suppress checks="MissingJavadocMethod"/>
<suppress checks="MissingJavadocType"/>
</suppressions>

25
config/logback-jmh.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
-->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

25
config/logback.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
-->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter
xmlns="https://github.com/spotbugs/filter/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
<Match>
<Package name="~me\.ahoo\.cosid\..*\.*jmh_generated"/>
</Match>
<Match>
<Class name="~.*\.*Test.*"/>
</Match>
<Match>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
<Match>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Bug pattern="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE"/>
</Match>
<Match>
<!--https://github.com/spotbugs/spotbugs/issues/2627-->
<!--https://github.com/Ahoo-Wang/CosId/pull/450-->
<Bug pattern="PI_DO_NOT_REUSE_PUBLIC_IDENTIFIERS_CLASS_NAMES"/>
</Match>
<Match>
<Class name="me.ahoo.cosid.spring.redis.SpringRedisMachineIdDistributor"/>
<Method name="distribute0"/>
<Bug pattern="NP_NULL_ON_SOME_PATH"/>
</Match>
<Match>
<Class name="me.ahoo.cosid.accessor.IdDefinition$NotFoundIdDefinition"/>
<Field name="id"/>
<Bug pattern="SS_SHOULD_BE_STATIC"/>
</Match>
<Match>
<Class name="me.ahoo.cosid.snowflake.AbstractSnowflakeId"/>
<Bug pattern="CT_CONSTRUCTOR_THROW"/>
</Match>
<Match>
<Class name="me.ahoo.cosid.cosid.RadixCosIdGenerator"/>
<Bug pattern="CT_CONSTRUCTOR_THROW"/>
</Match>
<Match>
<Class name="me.ahoo.cosid.sharding.ExactCollection"/>
<Bug pattern="SING_SINGLETON_HAS_NONPRIVATE_CONSTRUCTOR"/>
</Match>
</FindBugsFilter>

View File

@ -0,0 +1,6 @@
dependencies {
implementation(libs.activiti.engine)
api(project(":cosid-core"))
testImplementation(project(":cosid-test"))
}

View File

@ -0,0 +1,38 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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();
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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_"));
}
}

View File

@ -0,0 +1,6 @@
dependencies {
api(platform(libs.axon.bom))
api(project(":cosid-core"))
testImplementation(project(":cosid-test"))
implementation("org.axonframework:axon-messaging")
}

View File

@ -0,0 +1,40 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.axon;
import me.ahoo.cosid.provider.IdGeneratorProvider;
import me.ahoo.cosid.provider.LazyIdGenerator;
import org.axonframework.common.IdentifierFactory;
/**
* CosId Identifier Factory .
*
* @author ahoo wang
*/
public class CosIdIdentifierFactory extends IdentifierFactory {
public static final String ID_KEY = "cosid.axon";
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);
}
@Override
public String generateIdentifier() {
return ID_GENERATOR.generateAsString();
}
}

View File

@ -0,0 +1 @@
me.ahoo.cosid.axon.CosIdIdentifierFactory

View File

@ -0,0 +1,50 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.axon;
import static me.ahoo.cosid.axon.CosIdIdentifierFactory.ID_KEY;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.startsWith;
import me.ahoo.cosid.provider.DefaultIdGeneratorProvider;
import me.ahoo.cosid.test.MockIdGenerator;
import org.axonframework.common.IdentifierFactory;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.SetSystemProperty;
/**
* CosIdIdentifierFactoryTest .
*
* @author ahoo wang
*/
class CosIdIdentifierFactoryTest {
@Disabled
@Test
void generateIdentifier() {
DefaultIdGeneratorProvider.INSTANCE.setShare(MockIdGenerator.INSTANCE);
String id = IdentifierFactory.getInstance().generateIdentifier();
assertThat(id, startsWith(MockIdGenerator.TEST_PREFIX));
}
@SetSystemProperty(key = ID_KEY, value = "axon")
@Test
void generateIdentifierWhenSetIdKey() {
DefaultIdGeneratorProvider.INSTANCE.set("axon", MockIdGenerator.usePrefix("axon_"));
String id = IdentifierFactory.getInstance().generateIdentifier();
assertThat(id, startsWith("axon_"));
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
*/
plugins {
`java-library`
id("me.champeau.jmh") version "0.7.3"
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenLocal()
maven { url = uri("https://repo.spring.io/milestone") }
mavenCentral()
}
dependencies {
api(platform(libs.cosidBom))
implementation("me.ahoo.cosid:cosid-jdbc")
implementation("me.ahoo.cosid:cosid-test")
testImplementation("com.zaxxer:HikariCP:6.2.1")
testImplementation("mysql:mysql-connector-java:8.0.33")
/**
* WARNING中央仓库没有找到美团官方提供的Jar!!!
* git clone https://github.com/Meituan-Dianping/Leaf
* mvn install -Dmaven.test.skip=true
*/
testImplementation("com.sankuai.inf.leaf:leaf-core:1.0.1")
/**
* WARNING中央仓库没有找到滴滴官方提供的Jar!!!
* git clone https://github.com/didi/tinyid
* mvn install -Dmaven.test.skip=true
*/
// testImplementation("com.xiaoju.uemc.tinyid:tinyid-client:0.1.0-SNAPSHOT")
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.12.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.12.1")
}
jmh {
val DELIMITER = ',';
val JMH_INCLUDES_KEY = "jmhIncludes"
val JMH_EXCLUDES_KEY = "jmhExcludes"
val JMH_THREADS_KEY = "jmhThreads"
val JMH_MODE_KEY = "jmhMode"
if (project.hasProperty(JMH_INCLUDES_KEY)) {
val jmhIncludes = project.properties[JMH_INCLUDES_KEY].toString().split(DELIMITER)
includes.set(jmhIncludes)
}
if (project.hasProperty(JMH_EXCLUDES_KEY)) {
val jmhExcludes = project.properties[JMH_EXCLUDES_KEY].toString().split(DELIMITER)
excludes.set(jmhExcludes)
}
jmhVersion.set("1.37")
warmupIterations.set(1)
iterations.set(1)
resultFormat.set("json")
var jmhMode = listOf(
"thrpt"
)
if (project.hasProperty(JMH_MODE_KEY)) {
jmhMode = project.properties[JMH_MODE_KEY].toString().split(DELIMITER)
}
benchmarkMode.set(jmhMode)
var jmhThreads = 1
if (project.hasProperty(JMH_THREADS_KEY)) {
jmhThreads = Integer.valueOf(project.properties[JMH_THREADS_KEY].toString())
}
threads.set(jmhThreads)
fork.set(1)
}
tasks.withType<Test> {
useJUnitPlatform()
}

View File

@ -0,0 +1,15 @@
#
# Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
#
group=me.ahoo.cosid.benchmark
version=2.2.6

View File

@ -0,0 +1,21 @@
[versions]
cosid = "2.12.1"
jmh = "1.37"
junit="5.12.1"
hikariCP = "6.2.1"
mysql="8.0.33"
jmhPlugin = "0.7.3"
[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" }

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
cosid-benchmark/gradlew vendored Executable file
View File

@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# 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/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
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.
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=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
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 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" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
cosid-benchmark/gradlew.bat vendored Normal file
View File

@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
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
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
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
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,14 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
*/
rootProject.name = "cosid-benchmark"

View File

@ -0,0 +1,48 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
*/
create database if not exists benchmark_db;
use benchmark_db;
# CosId
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;
# insert into cosid
# (name, last_max_id, last_fetch_time)
# value
# ('namespace.name', 0, unix_timestamp());
# Leaf
CREATE TABLE `leaf_alloc`
(
`biz_tag` varchar(128) NOT NULL DEFAULT '',
`max_id` bigint(20) NOT NULL DEFAULT '1',
`step` int(11) NOT NULL,
`description` varchar(256) DEFAULT NULL,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`biz_tag`)
) ENGINE = InnoDB;
# insert into leaf_alloc(biz_tag, max_id, step, description)
# values ('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')
#

View File

@ -0,0 +1,28 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
/**
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class AbstractBenchmark {
@Param({"1", "100", "1000"})
protected int step;
}

View File

@ -0,0 +1,39 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import me.ahoo.cosid.jvm.AtomicLongGenerator;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
/**
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class AtomicLongBenchmark {
AtomicLongGenerator atomicLongGenerator;
@Setup
public void setup() {
atomicLongGenerator = new AtomicLongGenerator();
}
@Benchmark
public long generate() {
return atomicLongGenerator.generate();
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import me.ahoo.cosid.jdbc.JdbcIdSegmentDistributor;
import me.ahoo.cosid.jdbc.JdbcIdSegmentInitializer;
import me.ahoo.cosid.segment.SegmentChainId;
import me.ahoo.cosid.test.MockIdGenerator;
import org.openjdk.jmh.annotations.*;
import javax.sql.DataSource;
import java.io.Closeable;
import java.io.IOException;
/**
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class CosIdBenchmark extends AbstractBenchmark{
DataSource dataSource;
SegmentChainId segmentChainId;
@Setup
public void setup() {
dataSource = DataSourceFactory.createDataSource();
JdbcIdSegmentInitializer jdbcIdSegmentInitializer = new JdbcIdSegmentInitializer(dataSource);
JdbcIdSegmentDistributor jdbcIdSegmentDistributor = new JdbcIdSegmentDistributor(MockIdGenerator.INSTANCE.generateAsString(), String.valueOf(step), step, dataSource);
jdbcIdSegmentInitializer.tryInitIdSegment(jdbcIdSegmentDistributor.getNamespacedName(), 0);
segmentChainId= new SegmentChainId(jdbcIdSegmentDistributor);
}
@Benchmark
public long generate() {
return segmentChainId.generate();
}
@TearDown
public void tearDown(){
if (dataSource instanceof Closeable) {
try {
((Closeable) dataSource).close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import com.sankuai.inf.leaf.IDGen;
import com.sankuai.inf.leaf.common.Result;
import com.sankuai.inf.leaf.common.Status;
import com.sankuai.inf.leaf.segment.SegmentIDGenImpl;
import com.sankuai.inf.leaf.segment.dao.IDAllocDao;
import com.sankuai.inf.leaf.segment.dao.impl.IDAllocDaoImpl;
import me.ahoo.cosid.test.MockIdGenerator;
import org.openjdk.jmh.annotations.*;
import javax.sql.DataSource;
import java.io.Closeable;
import java.io.IOException;
/**
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class LeafBenchmark extends AbstractBenchmark {
DataSource dataSource;
IDGen idGen;
String bizTag;
@Setup
public void setup() {
bizTag = MockIdGenerator.INSTANCE.generateAsString();
dataSource = DataSourceFactory.createDataSource();
LeafInitializer.initSegment(dataSource, bizTag, step);
IDAllocDao dao = new IDAllocDaoImpl(dataSource);
idGen = new SegmentIDGenImpl();
((SegmentIDGenImpl) idGen).setDao(dao);
idGen.init();
}
@Benchmark
public long generate() {
Result result = idGen.get(bizTag);
if (Status.SUCCESS.equals(result.getStatus())){
return result.getId();
}
return generate();
}
@TearDown
public void tearDown() {
if (dataSource instanceof Closeable) {
try {
((Closeable) dataSource).close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
/**
* TODO
*
* @author ahoo wang
*/
public class TinyIDBenchmark {
}

View File

@ -0,0 +1,22 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
/**
* TODO
*
* @author ahoo wang
*/
public class UidBenchmark {
}

View File

@ -0,0 +1,31 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
/**
* @author ahoo wang
*/
public final class DataSourceFactory {
public static DataSource createDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/benchmark_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author ahoo wang
*/
public final class LeafInitializer {
public static void initSegment(DataSource dataSource, String bizTag, long step) {
String initIdSegmentSql = "insert into leaf_alloc(biz_tag, max_id, step, description)\n" +
"values (?, 1, ?, 'LeafBenchmark')";
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement initStatement = connection.prepareStatement(initIdSegmentSql)) {
initStatement.setString(1, bizTag);
initStatement.setLong(2, step);
int affected = initStatement.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
import com.sankuai.inf.leaf.IDGen;
import com.sankuai.inf.leaf.common.Result;
import com.sankuai.inf.leaf.common.Status;
import com.sankuai.inf.leaf.segment.SegmentIDGenImpl;
import com.sankuai.inf.leaf.segment.dao.IDAllocDao;
import com.sankuai.inf.leaf.segment.dao.impl.IDAllocDaoImpl;
import me.ahoo.cosid.test.MockIdGenerator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.Closeable;
import java.io.IOException;
/**
* @author ahoo wang
*/
public class LeafTest {
DataSource dataSource;
IDGen idGen;
String bizTag;
int step = 100;
@BeforeEach
public void init() {
bizTag = MockIdGenerator.INSTANCE.generateAsString();
dataSource = DataSourceFactory.createDataSource();
LeafInitializer.initSegment(dataSource, bizTag, step);
IDAllocDao dao = new IDAllocDaoImpl(dataSource);
idGen = new SegmentIDGenImpl();
((SegmentIDGenImpl) idGen).setDao(dao);
idGen.init();
}
@Test
public void get() {
Result result = idGen.get(bizTag);
Assertions.assertEquals(Status.SUCCESS, result.getStatus());
}
@AfterEach
public void tearDown() {
if (dataSource instanceof Closeable) {
try {
((Closeable) dataSource).close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.benchmark;
/**
* TODO
* wow,TinyID 竟然是使用RPC方式消费ID难以置信
*
* @author ahoo wang
*/
public class TinyIDTest {
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright [2021-2021] [ahoo wang <ahoowang@qq.com> (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.
-->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="WARN">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@ -0,0 +1,23 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.
*/
@Suppress("UNCHECKED_CAST")
val libraryProjects = rootProject.ext.get("libraryProjects") as Iterable<Project>;
dependencies {
constraints {
libraryProjects.forEach {
api(it)
}
}
}

View File

@ -0,0 +1,17 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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(libs.jakarta.annotation.api)
testImplementation(project(":cosid-test"))
}

View File

@ -0,0 +1,41 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import me.ahoo.cosid.util.Clock;
import org.openjdk.jmh.annotations.Benchmark;
/**
* Clock Benchmark.
*
* @author ahoo wang
*/
public class ClockBenchmark {
@Benchmark
public long currentTimeMillis() {
return System.currentTimeMillis();
}
@Benchmark
public long nanoTime() {
return System.nanoTime();
}
@Benchmark
public long cacheSecondTime() {
return Clock.CACHE.secondTime();
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import static me.ahoo.cosid.cosid.Radix62CosIdGenerator.*;
import me.ahoo.cosid.converter.Radix62IdConverter;
import me.ahoo.cosid.cosid.ClockSyncCosIdGenerator;
import me.ahoo.cosid.cosid.CosIdGenerator;
import me.ahoo.cosid.cosid.Radix36CosIdGenerator;
import me.ahoo.cosid.jvm.AtomicLongGenerator;
import me.ahoo.cosid.cosid.Radix62CosIdGenerator;
import me.ahoo.cosid.cosid.CosIdState;
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.UUID;
/**
* SegmentId Benchmark.
*
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class CosIdGeneratorBenchmark {
AtomicLongGenerator atomicLongGenerator;
CosIdGenerator radix62CosIdGenerator;
CosIdGenerator radix36CosIdGenerator;
CosIdGenerator customizeRadix62CosIdGenerator;
/**
* Initialize IdGenerator.
*/
@Setup
public void setup() {
atomicLongGenerator = new AtomicLongGenerator();
radix62CosIdGenerator = new ClockSyncCosIdGenerator(new Radix62CosIdGenerator(1));
radix36CosIdGenerator = new ClockSyncCosIdGenerator(new Radix36CosIdGenerator(1));
final int customizeSequenceBit = 18;
final int customizeSequenceResetThreshold = ~(-1 << (customizeSequenceBit - 1));
customizeRadix62CosIdGenerator =
new ClockSyncCosIdGenerator(new Radix62CosIdGenerator(DEFAULT_TIMESTAMP_BIT, DEFAULT_MACHINE_BIT, customizeSequenceBit, 1, customizeSequenceResetThreshold));
}
@Benchmark
public UUID uuid_generate() {
return UUID.randomUUID();
}
@Benchmark
public long atomicLong_generate() {
return atomicLongGenerator.generate();
}
@Benchmark
public String atomicLong_generateAsString() {
return Radix62IdConverter.PAD_START.asString(atomicLongGenerator.generate());
}
@Benchmark
public String cosIdGenerator62_generateAsString() {
return radix62CosIdGenerator.generateAsString();
}
@Benchmark
public CosIdState cosIdGenerator62_generateAsState() {
return radix62CosIdGenerator.generateAsState();
}
@Benchmark
public String cosIdGenerator36_generateAsString() {
return radix36CosIdGenerator.generateAsString();
}
@Benchmark
public String cosIdGeneratorCustomize62_generateAsString() {
return customizeRadix62CosIdGenerator.generateAsString();
}
@Benchmark
public CosIdState cosIdGeneratorCustomize62_generateAsState() {
return customizeRadix62CosIdGenerator.generateAsState();
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import static me.ahoo.cosid.segment.IdSegment.TIME_TO_LIVE_FOREVER;
import me.ahoo.cosid.jvm.AtomicLongGenerator;
import me.ahoo.cosid.segment.DefaultSegmentId;
import me.ahoo.cosid.segment.IdSegmentDistributor;
import me.ahoo.cosid.segment.SegmentChainId;
import me.ahoo.cosid.segment.SegmentId;
import me.ahoo.cosid.segment.concurrent.PrefetchWorkerExecutorService;
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.UUID;
/**
* SegmentId Benchmark.
*
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class SegmentIdBenchmark {
SegmentId segmentId;
SegmentChainId segmentChainId;
AtomicLongGenerator atomicLongGenerator;
/**
* Initialize IdGenerator.
*/
@Setup
public void setup() {
atomicLongGenerator = new AtomicLongGenerator();
segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock());
segmentChainId = new SegmentChainId(TIME_TO_LIVE_FOREVER, 10, new IdSegmentDistributor.Mock(), PrefetchWorkerExecutorService.DEFAULT);
}
@Benchmark
public UUID uuid_generate() {
return UUID.randomUUID();
}
@Benchmark
public long atomicLong_generate() {
return atomicLongGenerator.generate();
}
@Benchmark
public long segmentId_generate() {
return segmentId.generate();
}
@Benchmark
public long segmentChainId_generate() {
return segmentChainId.generate();
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import me.ahoo.cosid.machine.ClockBackwardsSynchronizer;
import me.ahoo.cosid.snowflake.ClockSyncSnowflakeId;
import me.ahoo.cosid.snowflake.DefaultSnowflakeFriendlyId;
import me.ahoo.cosid.snowflake.MillisecondSnowflakeId;
import me.ahoo.cosid.snowflake.SafeJavaScriptSnowflakeId;
import me.ahoo.cosid.snowflake.SecondSnowflakeId;
import me.ahoo.cosid.snowflake.SnowflakeFriendlyId;
import me.ahoo.cosid.snowflake.SnowflakeId;
import me.ahoo.cosid.snowflake.SnowflakeIdState;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
/**
* SnowflakeId Benchmark.
*
* @author ahoo wang
*/
@State(Scope.Benchmark)
public class SnowflakeIdBenchmark {
SnowflakeId millisecondSnowflakeId;
SnowflakeId secondSnowflakeId;
SnowflakeId safeJsMillisecondSnowflakeId;
SnowflakeId safeJsSecondSnowflakeId;
SnowflakeFriendlyId snowflakeFriendlyId;
/**
* Initialize IdGenerator.
*/
@Setup
public void setup() {
millisecondSnowflakeId = new ClockSyncSnowflakeId(new MillisecondSnowflakeId(1));
secondSnowflakeId = new ClockSyncSnowflakeId(new SecondSnowflakeId(1));
safeJsSecondSnowflakeId = new ClockSyncSnowflakeId(SafeJavaScriptSnowflakeId.ofSecond(1));
safeJsMillisecondSnowflakeId = new ClockSyncSnowflakeId(SafeJavaScriptSnowflakeId.ofMillisecond(1));
snowflakeFriendlyId = new DefaultSnowflakeFriendlyId(new ClockSyncSnowflakeId(new MillisecondSnowflakeId(1), ClockBackwardsSynchronizer.DEFAULT));
}
@Benchmark
public long millisecondSnowflakeId_generate() {
return millisecondSnowflakeId.generate();
}
@Benchmark
public SnowflakeIdState millisecondSnowflakeId_friendlyId() {
return snowflakeFriendlyId.friendlyId();
}
@Benchmark
public long secondSnowflakeId_generate() {
return secondSnowflakeId.generate();
}
@Benchmark
public long safeJsMillisecondSnowflakeId_generate() {
return safeJsMillisecondSnowflakeId.generate();
}
@Benchmark
public long safeJsSecondSnowflakeId_generate() {
return safeJsSecondSnowflakeId.generate();
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import java.time.LocalDateTime;
/**
* CosId constant.
*
* @author ahoo wang
*/
public final class CosId {
public static final String COSID = "cosid";
public static final String COSID_PREFIX = COSID + ".";
/**
* UTC EPOCH DATE of CosId .
*/
public static final LocalDateTime COSID_EPOCH_DATE;
/**
* COSID_EPOCH:1577203200000 .
*/
public static final long COSID_EPOCH = 1577203200000L;
/**
* COSID_EPOCH_SECOND:15772032000 .
*/
public static final long COSID_EPOCH_SECOND = 1577203200L;
static {
COSID_EPOCH_DATE = LocalDateTime.of(2019, 12, 24, 16, 0);
}
private CosId() {
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* CosId root Exception.
*
* @author ahoo wang
*/
public class CosIdException extends RuntimeException {
public CosIdException() {
}
public CosIdException(String message) {
super(message);
}
public CosIdException(String message, Throwable cause) {
super(message, cause);
}
public CosIdException(Throwable cause) {
super(cause);
}
public CosIdException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,29 @@
package me.ahoo.cosid;
import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;
/**
* Decorator.
*
* @author ahoo wang
*/
@ThreadSafe
public interface Decorator<D> {
/**
* Get decorator actual id generator.
*
* @return actual id generator
*/
@Nonnull
D getActual();
@SuppressWarnings({"unchecked", "rawtypes"})
static <D> D getActual(D any) {
if (any instanceof Decorator decorator) {
return getActual((D) decorator.getActual());
}
return any;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import me.ahoo.cosid.stat.Stat;
import me.ahoo.cosid.stat.Statistical;
import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;
/**
* ID converter.
*
* @author ahoo wang
*/
@ThreadSafe
public interface IdConverter extends Statistical {
/**
* convert {@code long} type ID to {@link String}.
*
* @param id {@code long} type ID
* @return {@link String} type ID
*/
@Nonnull
String asString(long id);
/**
* convert {@link String} type ID to {@code long}.
*
* @param idString {@link String} type ID
* @return {@code long} type ID
*/
long asLong(@Nonnull String idString);
@Override
default Stat stat() {
return Stat.simple(getClass().getSimpleName());
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import me.ahoo.cosid.converter.Radix62IdConverter;
import me.ahoo.cosid.stat.Statistical;
import me.ahoo.cosid.stat.generator.IdGeneratorStat;
import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;
/**
* Id Generator.
*
* @author ahoo wang
*/
@ThreadSafe
public interface IdGenerator extends StringIdGenerator, Statistical {
/**
* ID converter, used to convert {@code long} type ID to {@link String}.
*
* @return ID converter
*/
@Nonnull
default IdConverter idConverter() {
return Radix62IdConverter.PAD_START;
}
/**
* Generate distributed ID.
*
* @return generated distributed ID
*/
long generate();
@Nonnull
@Override
default String generateAsString() {
return idConverter().asString(generate());
}
@Override
default IdGeneratorStat stat() {
return IdGeneratorStat.simple(getClass().getSimpleName(), idConverter().stat());
}
}

View File

@ -0,0 +1,36 @@
package me.ahoo.cosid;
import me.ahoo.cosid.stat.generator.IdGeneratorStat;
import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;
/**
* IdGenerator decorator.
*
* @author ahoo wang
*/
@ThreadSafe
public interface IdGeneratorDecorator extends IdGenerator, Decorator<IdGenerator> {
/**
* Get decorator actual id generator.
*
* @return actual id generator
*/
@Nonnull
IdGenerator getActual();
static <T extends IdGenerator> 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());
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;
/**
* Integer Id Generator.
*
* @author ahoo wang
*/
@ThreadSafe
public class IntegerIdGenerator implements StringIdGenerator {
protected final IdGenerator actual;
public IntegerIdGenerator(IdGenerator actual) {
this.actual = actual;
}
/**
* Generate distributed ID of type int.
*
* @return generated distributed ID of type int
* @throws IdOverflowException This exception is thrown when the ID overflows
*/
public int generate() throws IdOverflowException {
long id = actual.generate();
ensureInteger(id);
return (int) id;
}
/**
* Generate distributed ID of type string.
*
* @return generated distributed ID of type string
* @throws IdOverflowException This exception is thrown when the ID overflows
*/
@Nonnull
@Override
public String generateAsString() throws IdOverflowException {
long id = actual.generate();
ensureInteger(id);
return actual.idConverter().asString(id);
}
private void ensureInteger(long id) throws IdOverflowException {
if (id < Integer.MIN_VALUE || id > Integer.MAX_VALUE) {
throw new IdOverflowException(id);
}
}
/**
* ID Overflow Exception.
*/
public static class IdOverflowException extends CosIdException {
private final long id;
public IdOverflowException(long id) {
super("id [" + id + "] overflow.");
this.id = id;
}
public long getId() {
return id;
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import com.google.errorprone.annotations.ThreadSafe;
import jakarta.annotation.Nonnull;
/**
* String type ID generator.
*
* @author ahoo wang
*/
@ThreadSafe
public interface StringIdGenerator {
/**
* Generate string type distributed ID.
*
* @return string type distributed ID
*/
@Nonnull
String generateAsString();
}

View File

@ -0,0 +1,45 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import jakarta.annotation.Nonnull;
/**
* used to enhance the generated string ID.
*
* @author ahoo wang
*/
public class StringIdGeneratorDecorator implements IdGeneratorDecorator {
protected final IdGenerator actual;
protected final IdConverter idConverter;
public StringIdGeneratorDecorator(IdGenerator actual, IdConverter idConverter) {
this.actual = actual;
this.idConverter = idConverter;
}
@Nonnull
@Override
public IdConverter idConverter() {
return idConverter;
}
@Nonnull
@Override
public IdGenerator getActual() {
return actual;
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.IdGenerator;
import me.ahoo.cosid.provider.LazyIdGenerator;
/**
* Abstract {@link IdMetadata}.
*
* @author ahoo wang
*/
public abstract class AbstractIdMetadata implements IdMetadata {
private final IdDefinition idDefinition;
private final LazyIdGenerator idGenerator;
public AbstractIdMetadata(IdDefinition idDefinition) {
this.idDefinition = idDefinition;
this.idGenerator = new LazyIdGenerator(idDefinition.getGeneratorName());
}
@Override
public IdDefinition getIdDefinition() {
return idDefinition;
}
@Override
public IdGenerator getIdGenerator() {
return idGenerator;
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.IdGenerator;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
/**
* CosId Accessor.
*
* @author ahoo wang
*/
public interface CosIdAccessor extends CosIdGetter, CosIdSetter, IdMetadata, EnsureId {
NotFound NOT_FOUND = new NotFound();
static boolean availableType(Class<?> idType) {
return String.class.equals(idType)
|| Long.class.equals(idType)
|| long.class.equals(idType)
|| Integer.class.equals(idType)
|| int.class.equals(idType)
;
}
static void ensureAccessible(AccessibleObject accessibleObject) {
if (!accessibleObject.isAccessible()) {
accessibleObject.setAccessible(true);
}
}
class NotFound implements CosIdAccessor {
@Override
public IdDefinition getIdDefinition() {
return null;
}
@Override
public IdGenerator getIdGenerator() {
return null;
}
@Override
public Field getIdField() {
return null;
}
@Override
public Object getId(Object target) {
return null;
}
@Override
public void setId(Object target, Object id) {
}
@Override
public boolean ensureId(Object target) {
return false;
}
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* CosId Getter.
*
* @author ahoo wang
*/
public interface CosIdGetter {
Object getId(Object target);
}

View File

@ -0,0 +1,25 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* CosId Setter.
*
* @author ahoo wang
*/
public interface CosIdSetter {
void setId(Object target, Object id);
}

View File

@ -0,0 +1,120 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.IntegerIdGenerator;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
/**
* Default {@link CosIdAccessor} implementation.
*
* @author ahoo wang
*/
public class DefaultCosIdAccessor extends AbstractIdMetadata implements CosIdAccessor {
private final CosIdGetter getter;
private final CosIdSetter setter;
private final EnsureId ensureId;
public DefaultCosIdAccessor(IdDefinition idDefinition, CosIdGetter getter, CosIdSetter setter) {
super(idDefinition);
this.getter = getter;
this.setter = setter;
this.ensureId = getEnsureId();
}
private EnsureId getEnsureId() {
Class<?> idFieldType = getIdType();
if (Long.class.equals(idFieldType) || long.class.equals(idFieldType)) {
return new EnsureLongId();
}
if (Integer.class.equals(idFieldType) || int.class.equals(idFieldType)) {
return new EnsureIntegerId();
}
return new EnsureStringId();
}
@Override
public Object getId(Object target) {
return getter.getId(target);
}
@Override
public void setId(Object target, Object id) {
setter.setId(target, id);
}
public CosIdGetter getGetter() {
return getter;
}
public CosIdSetter getSetter() {
return setter;
}
@Override
public boolean ensureId(Object target) {
Preconditions.checkArgument(getIdDeclaringClass().isInstance(target), "Target:[%s] is not instance of IdDeclaringClass:[%s]", target, getIdDeclaringClass());
return ensureId.ensureId(target);
}
public class EnsureStringId implements EnsureId {
@Override
public boolean ensureId(Object target) {
Object previousId = getId(target);
if (null != previousId && !Strings.isNullOrEmpty((String) previousId)) {
return false;
}
setId(target, getIdGenerator().generateAsString());
return true;
}
}
public class EnsureLongId implements EnsureId {
private static final long MIN_ID = 0;
@Override
public boolean ensureId(Object target) {
Object previousId = getId(target);
if (null != previousId && (Long) previousId > MIN_ID) {
return false;
}
setId(target, getIdGenerator().generate());
return true;
}
}
public class EnsureIntegerId implements EnsureId {
private static final int MIN_ID = 0;
private final IntegerIdGenerator integerIdGenerator;
public EnsureIntegerId() {
this.integerIdGenerator = new IntegerIdGenerator(getIdGenerator());
}
@Override
public boolean ensureId(Object target) {
Object previousId = getId(target);
if (null != previousId && (Integer) previousId > MIN_ID) {
return false;
}
setId(target, integerIdGenerator.generate());
return true;
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* Ensure that the target object has an ID set.
*
* @author ahoo wang
*/
public interface EnsureId {
/**
* Ensure that the target object has an ID set.
*
* @param target target object
* @return Set successfully?
*/
boolean ensureId(Object target);
}

View File

@ -0,0 +1,73 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.provider.IdGeneratorProvider;
import java.lang.reflect.Field;
/**
* ID Definition.
*
* @author ahoo wang
*/
public class IdDefinition {
public static final IdDefinition NOT_FOUND = new IdDefinition(NotFoundIdDefinition.NAME, NotFoundIdDefinition.ID_FIELD, long.class);
private final String generatorName;
private final Field idField;
private final Class<?> idType;
public IdDefinition(Field idField) {
this(IdGeneratorProvider.SHARE, idField);
}
public IdDefinition(String generatorName, Field idField) {
this(generatorName, idField, idField.getType());
}
public IdDefinition(String generatorName, Field idField, Class<?> idType) {
this.generatorName = generatorName;
this.idField = idField;
this.idType = idType;
}
public String getGeneratorName() {
return generatorName;
}
public Field getIdField() {
return idField;
}
public Class<?> getIdType() {
return idType;
}
private static class NotFoundIdDefinition {
static final String NAME = "NOT_FOUND";
static final Field ID_FIELD;
static {
try {
ID_FIELD = NotFoundIdDefinition.class.getDeclaredField("id");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
private final long id = 0L;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.IdGenerator;
import com.google.errorprone.annotations.Immutable;
import java.lang.reflect.Field;
/**
* Id Metadata.
*
* @author ahoo wang
*/
@Immutable
public interface IdMetadata {
IdDefinition getIdDefinition();
default String getGeneratorName() {
return getIdDefinition().getGeneratorName();
}
IdGenerator getIdGenerator();
default Field getIdField() {
return getIdDefinition().getIdField();
}
default Class<?> getIdDeclaringClass() {
return getIdField().getDeclaringClass();
}
default Class<?> getIdType() {
return getIdDefinition().getIdType();
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.CosIdException;
import com.google.common.base.Strings;
import java.lang.reflect.Field;
/**
* ID Type Not Support Exception.
*
* @author ahoo wang
*/
public class IdTypeNotSupportException extends CosIdException {
private final Field idField;
public IdTypeNotSupportException(Field idField) {
super(Strings.lenientFormat("ID type only supports Long/long/Integer/int/String, idField:[%s]!", idField));
this.idField = idField;
}
public Field getIdField() {
return idField;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 me.ahoo.cosid.CosIdException;
import com.google.common.base.Strings;
/**
* Multiple Id Not Support Exception.
*
* @author ahoo wang
*/
public class MultipleIdNotSupportException extends CosIdException {
private final Class<?> declaringClass;
public MultipleIdNotSupportException(Class<?> declaringClass) {
super(Strings.lenientFormat("Not support defining multiple CosIds, declaringClass:[%s]!", declaringClass));
this.declaringClass = declaringClass;
}
public Class<?> getDeclaringClass() {
return declaringClass;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.field;
import me.ahoo.cosid.CosIdException;
import me.ahoo.cosid.accessor.CosIdAccessor;
import me.ahoo.cosid.accessor.CosIdGetter;
import java.lang.reflect.Field;
/**
* Field Getter.
*
* @author ahoo wang
*/
public class FieldGetter implements CosIdGetter {
private final Field idField;
public FieldGetter(Field idField) {
CosIdAccessor.ensureAccessible(idField);
this.idField = idField;
}
@Override
public Object getId(Object target) {
try {
return idField.get(target);
} catch (IllegalAccessException e) {
throw new CosIdException(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.field;
import me.ahoo.cosid.CosIdException;
import me.ahoo.cosid.accessor.CosIdAccessor;
import me.ahoo.cosid.accessor.CosIdSetter;
import java.lang.reflect.Field;
/**
* Field Setter.
*
* @author ahoo wang
*/
public class FieldSetter implements CosIdSetter {
private final Field idField;
public FieldSetter(Field idField) {
CosIdAccessor.ensureAccessible(idField);
this.idField = idField;
}
@Override
public void setId(Object target, Object id) {
try {
idField.set(target, id);
} catch (IllegalAccessException e) {
throw new CosIdException(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.method;
import me.ahoo.cosid.CosIdException;
import me.ahoo.cosid.accessor.CosIdAccessor;
import me.ahoo.cosid.accessor.CosIdGetter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Method Getter.
*
* @author ahoo wang
*/
public class MethodGetter implements CosIdGetter {
private final Method getter;
public MethodGetter(Method getter) {
CosIdAccessor.ensureAccessible(getter);
this.getter = getter;
}
public Method getGetter() {
return getter;
}
@Override
public Object getId(Object target) {
try {
return this.getter.invoke(target);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new CosIdException(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.method;
import me.ahoo.cosid.CosIdException;
import me.ahoo.cosid.accessor.CosIdAccessor;
import me.ahoo.cosid.accessor.CosIdSetter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Method Setter.
*
* @author ahoo wang
*/
public class MethodSetter implements CosIdSetter {
private final Method setter;
public MethodSetter(Method setter) {
CosIdAccessor.ensureAccessible(setter);
this.setter = setter;
}
public Method getSetter() {
return setter;
}
@Override
public void setId(Object target, Object id) {
try {
this.setter.invoke(target, id);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new CosIdException(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,14 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;

View File

@ -0,0 +1,44 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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<FieldDefinitionParser> fieldDefinitionParsers;
public CompositeFieldDefinitionParser(List<FieldDefinitionParser> fieldDefinitionParsers) {
this.fieldDefinitionParsers = fieldDefinitionParsers;
}
@Override
public IdDefinition parse(List<Class<?>> lookupClassList, Field field) {
for (FieldDefinitionParser fieldDefinitionParser : fieldDefinitionParsers) {
IdDefinition idDefinition = fieldDefinitionParser.parse(lookupClassList, field);
if (idDefinition != IdDefinition.NOT_FOUND) {
return idDefinition;
}
}
return IdDefinition.NOT_FOUND;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.CosIdAccessor;
/**
* CosIdAccessor Parser.
*
* @author ahoo wang
*/
public interface CosIdAccessorParser {
CosIdAccessor parse(Class<?> clazz);
}

View File

@ -0,0 +1,183 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 static java.util.Locale.ENGLISH;
import me.ahoo.cosid.accessor.CosIdAccessor;
import me.ahoo.cosid.accessor.IdDefinition;
import me.ahoo.cosid.accessor.CosIdGetter;
import me.ahoo.cosid.accessor.CosIdSetter;
import me.ahoo.cosid.accessor.DefaultCosIdAccessor;
import me.ahoo.cosid.accessor.IdTypeNotSupportException;
import me.ahoo.cosid.accessor.MultipleIdNotSupportException;
import me.ahoo.cosid.accessor.field.FieldGetter;
import me.ahoo.cosid.accessor.field.FieldSetter;
import me.ahoo.cosid.accessor.method.MethodGetter;
import me.ahoo.cosid.accessor.method.MethodSetter;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Default {@link CosIdAccessorParser} implementation.
*
* @author ahoo wang
*/
@Slf4j
public class DefaultAccessorParser implements CosIdAccessorParser {
public static final String GET_PREFIX = "get";
public static final String SET_PREFIX = "set";
private final ConcurrentHashMap<Class<?>, CosIdAccessor> classMapAccessor = new ConcurrentHashMap<>();
private final FieldDefinitionParser definitionParser;
public DefaultAccessorParser(FieldDefinitionParser definitionParser) {
this.definitionParser = definitionParser;
}
@Override
public CosIdAccessor parse(Class<?> clazz) {
return classMapAccessor.computeIfAbsent(clazz, (key) -> parseClass(clazz));
}
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
}
public static Method parseGetter(Field field) {
String getterName = GET_PREFIX + capitalize(field.getName());
try {
Method method = field.getDeclaringClass().getMethod(getterName);
if (!method.getReturnType().equals(field.getType())) {
return null;
}
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
return null;
}
}
public static Method parseSetter(Field field) {
String setterName = SET_PREFIX + capitalize(field.getName());
try {
Method method = field.getDeclaringClass().getMethod(setterName, field.getType());
if (!method.isAccessible()) {
method.setAccessible(true);
}
return method;
} catch (NoSuchMethodException e) {
return null;
}
}
protected CosIdAccessor parseClass(Class<?> clazz) {
CosIdAccessor firstAccessor = CosIdAccessor.NOT_FOUND;
Class<?> currentDeclaringClass = clazz;
List<Class<?>> lookupClassList = new ArrayList<>();
while (!Object.class.equals(currentDeclaringClass)) {
lookupClassList.add(currentDeclaringClass);
for (Field declaredField : currentDeclaringClass.getDeclaredFields()) {
IdDefinition idDefinition = definitionParser.parse(lookupClassList, declaredField);
if (idDefinition == null
|| IdDefinition.NOT_FOUND.equals(idDefinition)) {
continue;
}
if (!CosIdAccessor.NOT_FOUND.equals(firstAccessor)) {
throw new MultipleIdNotSupportException(clazz);
}
IdDefinition fixedIdDefinition = fixGenericFieldActualType(lookupClassList, idDefinition);
firstAccessor = definitionAsAccessor(fixedIdDefinition);
}
currentDeclaringClass = currentDeclaringClass.getSuperclass();
}
return firstAccessor;
}
private IdDefinition fixGenericFieldActualType(List<Class<?>> lookupClassList, IdDefinition idDefinition) {
Type fieldGenericType = idDefinition.getIdField().getGenericType();
if (!(fieldGenericType instanceof TypeVariable)) {
return idDefinition;
}
for (int i = lookupClassList.size() - 2; i >= 0; i--) {
Class<?> superClass = lookupClassList.get(i + 1);
Class<?> subClass = lookupClassList.get(i);
fieldGenericType = getActualFieldType(fieldGenericType, superClass, subClass);
if (fieldGenericType instanceof Class<?>) {
return new IdDefinition(idDefinition.getGeneratorName(), idDefinition.getIdField(), (Class<?>) fieldGenericType);
}
}
return idDefinition;
}
private Type getActualFieldType(Type typeVariable, Class<?> superClass, Class<?> subClass) {
int genericVarIdx = -1;
TypeVariable<?>[] typeVariables = superClass.getTypeParameters();
for (int i = 0; i < typeVariables.length; i++) {
if (typeVariable.equals(typeVariables[i])) {
genericVarIdx = i;
}
}
if (genericVarIdx == -1) {
throw new IllegalArgumentException(Strings.lenientFormat("Type Parameter:[%s] not found in Class:[%].", typeVariable, superClass));
}
ParameterizedType genericSuperclass = (ParameterizedType) subClass.getGenericSuperclass();
Type[] actualTypes = genericSuperclass.getActualTypeArguments();
return actualTypes[genericVarIdx];
}
protected CosIdAccessor definitionAsAccessor(IdDefinition idDefinition) {
Field idField = idDefinition.getIdField();
if (!CosIdAccessor.availableType(idDefinition.getIdType())) {
throw new IdTypeNotSupportException(idField);
}
if (Modifier.isFinal(idField.getModifiers())) {
if (log.isWarnEnabled()) {
log.warn("idField:[{}] is final.", idField);
}
}
Method getter = parseGetter(idField);
Method setter = parseSetter(idField);
CosIdGetter cosIdGetter = getter != null ? new MethodGetter(getter) : new FieldGetter(idField);
CosIdSetter cosIdSetter = setter != null ? new MethodSetter(setter) : new FieldSetter(idField);
return new DefaultCosIdAccessor(idDefinition, cosIdGetter, cosIdSetter);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* Field IdDefinition Parser.
*
* @author ahoo wang
*/
@FunctionalInterface
public interface FieldDefinitionParser {
IdDefinition parse(List<Class<?>> lookupClassList, Field field);
}

View File

@ -0,0 +1,41 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* Named {@link FieldDefinitionParser} implementation.
*
* @author ahoo wang
*/
public class NamedDefinitionParser implements FieldDefinitionParser {
private final String idFieldName;
public NamedDefinitionParser(String idFieldName) {
this.idFieldName = idFieldName;
}
@Override
public IdDefinition parse(List<Class<?>> lookupClassList, Field field) {
if (!idFieldName.equals(field.getName())) {
return IdDefinition.NOT_FOUND;
}
return new IdDefinition(field);
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.registry;
import me.ahoo.cosid.accessor.CosIdAccessor;
import com.google.errorprone.annotations.ThreadSafe;
/**
* CosIdAccessor Registry.
*
* @author ahoo wang
*/
@ThreadSafe
public interface CosIdAccessorRegistry {
void register(Class<?> clazz);
void register(Class<?> clazz, CosIdAccessor cosIdAccessor);
CosIdAccessor get(Class<?> clazz);
default boolean ensureId(Object target) {
CosIdAccessor cosIdAccessor = get(target.getClass());
if (CosIdAccessor.NOT_FOUND.equals(cosIdAccessor)) {
return false;
}
return cosIdAccessor.ensureId(target);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.registry;
import me.ahoo.cosid.accessor.CosIdAccessor;
import me.ahoo.cosid.accessor.parser.CosIdAccessorParser;
import java.util.concurrent.ConcurrentHashMap;
/**
* Default CosIdAccessorRegistry implementation.
*
* @author ahoo wang
*/
public class DefaultAccessorRegistry implements CosIdAccessorRegistry {
private final ConcurrentHashMap<Class<?>, CosIdAccessor> classMapAccessor = new ConcurrentHashMap<>();
private final CosIdAccessorParser accessorParser;
public DefaultAccessorRegistry(CosIdAccessorParser accessorParser) {
this.accessorParser = accessorParser;
}
@Override
public void register(Class<?> clazz) {
register(clazz, accessorParser.parse(clazz));
}
@Override
public void register(Class<?> clazz, CosIdAccessor cosIdAccessor) {
classMapAccessor.put(clazz, cosIdAccessor);
}
@Override
public CosIdAccessor get(Class<?> clazz) {
return classMapAccessor.computeIfAbsent(clazz, accessorParser::parse);
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.scanner;
/**
* Scan the packages and register the qualified classes to {@link me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry}.
*
* @author ahoo wang
*/
public interface CosIdScanner {
void scan();
}

View File

@ -0,0 +1,76 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.scanner;
import me.ahoo.cosid.CosIdException;
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.FieldDefinitionParser;
import me.ahoo.cosid.accessor.registry.CosIdAccessorRegistry;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Arrays;
/**
* Default {@link CosIdScanner} implementation.
*
* @author ahoo wang
*/
@Slf4j
public class DefaultCosIdScanner implements CosIdScanner {
private final String[] basePackages;
private final CosIdAccessorParser cosIdAccessorParser;
private final CosIdAccessorRegistry cosIdAccessorRegistry;
public DefaultCosIdScanner(String[] basePackages, FieldDefinitionParser fieldDefinitionParser, CosIdAccessorRegistry cosIdAccessorRegistry) {
this(basePackages, new DefaultAccessorParser(fieldDefinitionParser), cosIdAccessorRegistry);
}
public DefaultCosIdScanner(String[] basePackages, CosIdAccessorParser cosIdAccessorParser, CosIdAccessorRegistry cosIdAccessorRegistry) {
this.basePackages = basePackages;
this.cosIdAccessorRegistry = cosIdAccessorRegistry;
this.cosIdAccessorParser = cosIdAccessorParser;
}
@Override
public void scan() {
if (log.isInfoEnabled()) {
log.info("Scan basePackages:{}.", Arrays.toString(basePackages));
}
try {
ClassLoader classLoader = getClass().getClassLoader();
ClassPath classPath = ClassPath.from(classLoader);
for (String basePackage : basePackages) {
ImmutableSet<ClassPath.ClassInfo> classInfos = classPath.getTopLevelClassesRecursive(basePackage);
for (ClassPath.ClassInfo classInfo : classInfos) {
Class<?> clazz = classLoader.loadClass(classInfo.getName());
if (clazz.isInterface()) {
continue;
}
CosIdAccessor cosIdAccessor = cosIdAccessorParser.parse(clazz);
cosIdAccessorRegistry.register(clazz, cosIdAccessor);
}
}
} catch (IOException | ClassNotFoundException e) {
throw new CosIdException(e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import me.ahoo.cosid.accessor.IdDefinition;
import me.ahoo.cosid.accessor.parser.FieldDefinitionParser;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;
/**
* Annotation FieldDefinitionParser.
*
* @author ahoo wang
* @see CosId
*/
@Slf4j
public class AnnotationDefinitionParser implements FieldDefinitionParser {
public static final AnnotationDefinitionParser INSTANCE = new AnnotationDefinitionParser();
@Override
public IdDefinition parse(List<Class<?>> lookupClassList, Field field) {
CosId fieldCosId = field.getAnnotation(CosId.class);
if (null != fieldCosId) {
return new IdDefinition(fieldCosId.value(), field);
}
Optional<CosId> clazzCosIdOp = lookupClassList
.stream()
.filter(clazz -> clazz.isAnnotationPresent(CosId.class))
.map(clazz -> clazz.getAnnotation(CosId.class))
.findFirst();
if (clazzCosIdOp.isEmpty()) {
return IdDefinition.NOT_FOUND;
}
CosId clazzCosId = clazzCosIdOp.get();
if (!field.getName().equals(clazzCosId.field())) {
return IdDefinition.NOT_FOUND;
}
return new IdDefinition(clazzCosId.value(), field);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
import me.ahoo.cosid.provider.IdGeneratorProvider;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Define CosId.
*
* @author ahoo wang
*/
@Target({ElementType.FIELD, ElementType.TYPE})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CosId {
String DEFAULT_FIELD = "id";
/**
* id generator name.
* {@link IdGeneratorProvider#get(String)}
*
* @return id generator name
*/
String value() default IdGeneratorProvider.SHARE;
/**
* cosid field.
*
* @return field name of id.
*/
String field() default DEFAULT_FIELD;
}

View File

@ -0,0 +1,63 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 jakarta.annotation.Nonnull;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DatePrefixIdConverter implements IdConverter, Decorator<IdConverter> {
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());
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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 jakarta.annotation.Nonnull;
import com.google.common.base.Preconditions;
public class GroupedPrefixIdConverter implements IdConverter, Decorator<IdConverter> {
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());
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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.PrefixConverterStat;
import jakarta.annotation.Nonnull;
import com.google.common.base.Preconditions;
/**
* Converter for setting string ID prefix.
*
* @author ahoo wang
*/
public class PrefixIdConverter implements IdConverter, Decorator<IdConverter> {
private final String prefix;
private final IdConverter actual;
public PrefixIdConverter(String prefix, IdConverter actual) {
Preconditions.checkNotNull(prefix, "prefix can not be null!");
this.prefix = prefix;
this.actual = actual;
}
@Nonnull
@Override
public IdConverter getActual() {
return actual;
}
public String getPrefix() {
return prefix;
}
@Nonnull
@Override
public String asString(long id) {
String idStr = actual.asString(id);
if (prefix.isEmpty()) {
return idStr;
}
return prefix + idStr;
}
@Override
public long asLong(@Nonnull String idString) {
String idStr = idString.substring(prefix.length());
return actual.asLong(idStr);
}
@Override
public Stat stat() {
return new PrefixConverterStat(getClass().getSimpleName(), prefix, actual.stat());
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* 36 bit string ID converter like this [0-9][A-Z]{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.
*
* @param padStart padStart
* @param charSize Size
* @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;
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright [2021-present] [ahoo wang <ahoowang@qq.com> (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;
/**
* 62 bit string ID converter like this [0-9][A-Z][a-z]{11} .
* If you use the string ID generated by this ID converter as the database primary key, you must set the primary key case-sensitive.
*
* @author ahoo wang
*/
public final class Radix62IdConverter extends RadixIdConverter {
public static final int MAX_CHAR_SIZE = 11;
public static final int RADIX = 62;
public static final Radix62IdConverter INSTANCE = new Radix62IdConverter(false, MAX_CHAR_SIZE);
public static final Radix62IdConverter PAD_START = new Radix62IdConverter(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.
*
* @param padStart padStart
* @param charSize Size
* @return Radix62IdConverter
*/
public static Radix62IdConverter 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 Radix62IdConverter(padStart, charSize);
}
public Radix62IdConverter(boolean padStart, int charSize) {
super(padStart, charSize);
}
@Override
int getRadix() {
return RADIX;
}
@Override
int getMaxCharSize() {
return MAX_CHAR_SIZE;
}
}

Some files were not shown because too many files have changed in this diff Show More