-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add `UncertaintyIdGenerator`.
- Loading branch information
Showing
5 changed files
with
257 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
cosid-core/src/main/java/me/ahoo/cosid/uncertainty/OriginalIdOverflowException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright [2021-present] [ahoo wang <[email protected]> (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.uncertainty; | ||
|
||
import me.ahoo.cosid.CosIdException; | ||
|
||
import com.google.common.base.Strings; | ||
|
||
public class OriginalIdOverflowException extends CosIdException { | ||
private final long originalId; | ||
private final int originalIdBits; | ||
private final long maxOriginalId; | ||
|
||
public OriginalIdOverflowException(long originalId, int originalIdBits, long maxOriginalId) { | ||
super(Strings.lenientFormat("OriginalId[%s] overflow - originalIdBits[%s] - maxOriginalId[%s].", originalId, originalIdBits, maxOriginalId)); | ||
this.originalId = originalId; | ||
this.originalIdBits = originalIdBits; | ||
this.maxOriginalId = maxOriginalId; | ||
} | ||
|
||
public long originalId() { | ||
return originalId; | ||
} | ||
|
||
public int originalIdBits() { | ||
return originalIdBits; | ||
} | ||
|
||
public long maxOriginalId() { | ||
return maxOriginalId; | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
cosid-core/src/main/java/me/ahoo/cosid/uncertainty/UncertaintyIdGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
* Copyright [2021-present] [ahoo wang <[email protected]> (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.uncertainty; | ||
|
||
import me.ahoo.cosid.IdGenerator; | ||
import me.ahoo.cosid.IdGeneratorDecorator; | ||
import me.ahoo.cosid.snowflake.SnowflakeId; | ||
|
||
import com.google.common.annotations.Beta; | ||
import com.google.common.base.Preconditions; | ||
|
||
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
/** | ||
* Uncertainty ID Generator. | ||
* For the following usage scenarios: | ||
* <pre> | ||
* 1. The problem of uneven sharding of snowflake IDs. | ||
* 2. I don’t want the generated ID to be predictable, such as preventing crawler by ID number, predicting transaction volume. | ||
* </pre> | ||
*/ | ||
@Beta | ||
public class UncertaintyIdGenerator implements IdGeneratorDecorator { | ||
protected final IdGenerator actual; | ||
private final int uncertaintyBits; | ||
private final int originalIdBits; | ||
private final long uncertaintyBound; | ||
private final long maxOriginalId; | ||
|
||
public UncertaintyIdGenerator(IdGenerator actual, int uncertaintyBits) { | ||
Preconditions.checkArgument(uncertaintyBits > 0 && uncertaintyBits < SnowflakeId.TOTAL_BIT, "uncertaintyBits[%s] must be greater than 0 and less than 63."); | ||
this.actual = actual; | ||
this.uncertaintyBits = uncertaintyBits; | ||
this.originalIdBits = SnowflakeId.TOTAL_BIT - uncertaintyBits; | ||
this.maxOriginalId = ~(-1L << originalIdBits); | ||
this.uncertaintyBound = ~(-1L << uncertaintyBits) + 1; | ||
} | ||
|
||
public int uncertaintyBits() { | ||
return uncertaintyBits; | ||
} | ||
|
||
public int originalIdBits() { | ||
return originalIdBits; | ||
} | ||
|
||
public long uncertaintyBound() { | ||
return uncertaintyBound; | ||
} | ||
|
||
public long maxOriginalId() { | ||
return maxOriginalId; | ||
} | ||
|
||
private long uncertainty() { | ||
return ThreadLocalRandom.current().nextLong(0, uncertaintyBound); | ||
} | ||
|
||
@Override | ||
public IdGenerator getActual() { | ||
return actual; | ||
} | ||
|
||
@Override | ||
public long generate() { | ||
long originalId = getActual().generate(); | ||
if (originalId > maxOriginalId) { | ||
throw new OriginalIdOverflowException(originalId, originalIdBits, maxOriginalId); | ||
} | ||
return originalId << uncertaintyBits | uncertainty(); | ||
} | ||
|
||
} |
125 changes: 125 additions & 0 deletions
125
cosid-core/src/test/java/me/ahoo/cosid/uncertainty/UncertaintyIdGeneratorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/* | ||
* Copyright [2021-present] [ahoo wang <[email protected]> (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.uncertainty; | ||
|
||
import static me.ahoo.cosid.snowflake.MillisecondSnowflakeId.DEFAULT_MACHINE_BIT; | ||
import static me.ahoo.cosid.snowflake.MillisecondSnowflakeId.DEFAULT_SEQUENCE_BIT; | ||
import static me.ahoo.cosid.snowflake.MillisecondSnowflakeId.DEFAULT_TIMESTAMP_BIT; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.*; | ||
|
||
import me.ahoo.cosid.CosId; | ||
import me.ahoo.cosid.IdGenerator; | ||
import me.ahoo.cosid.segment.DefaultSegmentId; | ||
import me.ahoo.cosid.segment.IdSegmentDistributor; | ||
import me.ahoo.cosid.snowflake.MillisecondSnowflakeId; | ||
import me.ahoo.cosid.snowflake.SnowflakeId; | ||
import me.ahoo.cosid.test.ConcurrentGenerateSpec; | ||
|
||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
class UncertaintyIdGeneratorTest { | ||
private static final int UNCERTAINTY_BITS = 5; | ||
|
||
@Test | ||
void generateGivenSnowflakeId() { | ||
SnowflakeId snowflakeId = new MillisecondSnowflakeId(CosId.COSID_EPOCH, DEFAULT_TIMESTAMP_BIT, DEFAULT_MACHINE_BIT - UNCERTAINTY_BITS, DEFAULT_SEQUENCE_BIT, 0); | ||
UncertaintyIdGenerator idGenerator = new UncertaintyIdGenerator(snowflakeId, UNCERTAINTY_BITS); | ||
assertThat(idGenerator.uncertaintyBits(), equalTo(UNCERTAINTY_BITS)); | ||
assertThat(idGenerator.originalIdBits(), equalTo(SnowflakeId.TOTAL_BIT - UNCERTAINTY_BITS)); | ||
assertThat(idGenerator.uncertaintyBound(), equalTo(~(-1L << UNCERTAINTY_BITS) + 1)); | ||
assertThat(idGenerator.maxOriginalId(), equalTo(~(-1L << (SnowflakeId.TOTAL_BIT - UNCERTAINTY_BITS)))); | ||
long beforeId = idGenerator.generate(); | ||
long afterId = idGenerator.generate(); | ||
assertThat(beforeId, lessThan(afterId)); | ||
} | ||
|
||
@Test | ||
void generateGivenOverflow() { | ||
IdGenerator overflowIdGen = new IdGenerator() { | ||
private final AtomicLong actual = new AtomicLong(~(-1L << (SnowflakeId.TOTAL_BIT - UNCERTAINTY_BITS))); | ||
|
||
@Override | ||
public long generate() { | ||
return actual.getAndIncrement(); | ||
} | ||
}; | ||
UncertaintyIdGenerator idGenerator = new UncertaintyIdGenerator(overflowIdGen, UNCERTAINTY_BITS); | ||
idGenerator.generate(); | ||
boolean thrown = false; | ||
try { | ||
idGenerator.generate(); | ||
} catch (OriginalIdOverflowException overflowException) { | ||
thrown = true; | ||
assertThat(overflowException.maxOriginalId(), equalTo(idGenerator.maxOriginalId())); | ||
assertThat(overflowException.originalIdBits(), equalTo(idGenerator.originalIdBits())); | ||
assertThat(overflowException.originalId(), equalTo(idGenerator.maxOriginalId() + 1)); | ||
} | ||
|
||
assertThat(thrown, equalTo(true)); | ||
} | ||
|
||
@Test | ||
void generateGivenSegmentId() { | ||
DefaultSegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); | ||
UncertaintyIdGenerator idGenerator = new UncertaintyIdGenerator(segmentId, UNCERTAINTY_BITS); | ||
long beforeId = idGenerator.generate(); | ||
long afterId = idGenerator.generate(); | ||
assertThat(beforeId, lessThan(afterId)); | ||
} | ||
|
||
@Test | ||
void generateWhenConcurrentGivenSegmentId() { | ||
DefaultSegmentId segmentId = new DefaultSegmentId(new IdSegmentDistributor.Mock()); | ||
UncertaintyIdGenerator idGenerator = new UncertaintyIdGenerator(segmentId, UNCERTAINTY_BITS); | ||
new ConcurrentGenerateSpec(idGenerator) { | ||
@Override | ||
protected void assertGlobalFirst(long id) { | ||
} | ||
|
||
@Override | ||
protected void assertGlobalEach(long previousId, long id) { | ||
Assertions.assertTrue(id > previousId); | ||
} | ||
|
||
@Override | ||
protected void assertGlobalLast(long lastId) { | ||
} | ||
}.verify(); | ||
} | ||
|
||
@Test | ||
void generateWhenConcurrentGivenSnowflakeId() { | ||
SnowflakeId snowflakeId = new MillisecondSnowflakeId(CosId.COSID_EPOCH, DEFAULT_TIMESTAMP_BIT, DEFAULT_MACHINE_BIT - UNCERTAINTY_BITS, DEFAULT_SEQUENCE_BIT, 0); | ||
|
||
UncertaintyIdGenerator idGenerator = new UncertaintyIdGenerator(snowflakeId, UNCERTAINTY_BITS); | ||
new ConcurrentGenerateSpec(idGenerator) { | ||
@Override | ||
protected void assertGlobalFirst(long id) { | ||
} | ||
|
||
@Override | ||
protected void assertGlobalEach(long previousId, long id) { | ||
Assertions.assertTrue(id > previousId); | ||
} | ||
|
||
@Override | ||
protected void assertGlobalLast(long lastId) { | ||
} | ||
}.verify(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters