Provider some useful features for distributed cluster system.
You can add this library like this:
<dependency>
<groupId>com.github.sisyphsu</groupId>
<artifactId>common-cluster</artifactId>
<version>1.0.4</version>
</dependency>
This library use zookeeper
and redis
as datasource:
spring:
zookeeper:
addr: localhost:2181
redis:
host: localhost
port: 6379
- ClusterID: allocate and lock one unrepeated global nodeID for the current node, which based on ZooKeeper.
- DistributedLock: provide multi-key's distributed lock implementation, which based on Redis.
- SnowFlakeID: provide an simple and efficient SnowFlakeID implementation, which based on ClusterID.
- TickID: provide another distributed incremental ID generator, which based on ZooKeeper or Redis.
Compete and lock an unique global ID
for every cluster's member, which based on ZooKeeper.
You can specify the bitNum
of ClusterID
based on your cluster size, The final ID
will be distributed between 1<<0
and 1<<bitNum
. Remember to leave room for expansion and backup, because ClusterID
will loop through to find the coldest
ID.
The default bitNum
was 8
, which means the ClusterID
could be [0, 256)
.
The ClusterID
has three status:
- NONE: Didn't lock any ID, maybe starting or lost lock after reconnection.
- LOCK: Success,
ClusterID#get()
will return the unique id. - UNLOCK: Has an unique id, but lose connection to
ZooKeeper
, the lock may be token by other node.
Example:
public class ClusterIDTest extends SpringBaseTest {
@Autowired
private ClusterID clusterID;
@Test
public void testID() {
log.info("clusterID: {}", clusterID.get());
}
}
Notice: ClusterID#get()
which will block if not ready.
This is a multi-key's distributed lock implementation, based on Redis
's eval
and pubsub
commands.
The biggest feature is support for multiple keys, which is useful for batch operation or some complex logic.
For example, during some trade operation, one service need to lock userId=1001
and orderId=100000001
to prevent others modify the same data, it can do like this:
public class DistributedLockTest extends SpringBaseTest {
@Autowired
private DistributedLock dlock;
@Test
public void runInTradeLock() throws Exception {
dlock.runInLock(Arrays.asList("user:1001", "order:100000001"), () -> {
System.out.println("do business");
});
}
@Test
public void runInTradeLock2() throws Exception {
List<String> keys = Arrays.asList("user:1001", "order:100000001");
if (!dlock.lock(keys, 3000)) {
throw new TimeoutException("lock failed");
}
try {
System.out.println("do business");
} finally {
dlock.unlock(keys);
}
}
}
The DistributedLock
will try lock all keys, if not success, it will wait other's unlock
notify and then retry lock until success or timeout.
What is SnowFlake
? check this.
Use this SnowFlakeID
implementation, you can specified the bitNum of timestamp
, workId
, sequenceId
by your business.
This SnowFlakeId
dependent on ClusterID
, because the ClusterID
is an interface
, so you can custom it easily.
And the timestamp
is based on 2017-01-01 00:00:00 GMT+0800
to save 1 or 2 bit.
By default, the bitNum was configured like this:
- timestamp: 39 bits, support 2017 ~ 2051
- workId: 8 bits, support 256 nodes
- sequence: 6 bits, can generate 64 ids every millisecond and every node
Why so small?
- Don't need too many ids,
64000
qps was enough for one single node. - Don't want id too big,
53-bits
was shorter (1187276660375616
), which is compatible withJavaScript
.
You can also specified different bitNum
like this:
@Slf4j
public class SnowFlakeIDTest {
@Test
public void testOne() {
int timestampBitNum = 40;
int nodeBitNum = 10;
int sequenceBitNum = 8;
SnowFlakeID flakeID = new SnowFlakeID(new ClusterID() {
@Override
public int getBitNum() {
return nodeBitNum;
}
@Override
public int get() {
return 1;
}
@Override
public ClusterIDStatus getStatus() {
return ClusterIDStatus.LOCK;
}
}, timestampBitNum, sequenceBitNum);
System.out.println(flakeID.generate());
}
}
This is an another distributed incremental id solution.
Sometimes, we want some smaller id like 11872766
or 1187276660
, but not bigger id like 1187276660375616
.
In this case, SnowFlakeID
is not suitable, because it based on timestamp
and very large.
TickID
was designed to solve this problem, it can generate smaller incremental id in distributed system.
It use ZooKeeper
or Redis
to persist global counter, and maintain 2 TokenPool, one for allocating id, another one for preparing.
You can use it like this:
@Slf4j
public class TickIDTest {
@Autowired
private TickTemplate template;
@Test
public void testNormal() {
TickID tickID = template.createTickID("user_id", 100);
System.out.println(tickID.generate());
tickID.close();
}
}
Curator
version must match yourZooKeeper
version, click for detail.
MIT