Skip to content

Commit

Permalink
add UncertaintyIdGenerator (#117)
Browse files Browse the repository at this point in the history
* add `UncertaintyIdGenerator`.
  • Loading branch information
Ahoo-Wang authored Jun 14, 2022
1 parent 0363104 commit c1a73f1
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 2 deletions.
5 changes: 4 additions & 1 deletion config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="AvoidStarImport"/>
<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"/>
Expand Down
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;
}
}
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();
}

}
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();
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#

group=me.ahoo.cosid
version=1.12.0
version=1.12.1

description=Universal, flexible, high-performance distributed ID generator.
website=https://github.com/Ahoo-Wang/CosId
Expand Down

0 comments on commit c1a73f1

Please sign in to comment.