實戰 —— 生存唯一隨機數
前言
實際開發過程中,我們會遇到生成唯一標識的隨機數的需求,下面總結一下在網上看到的一些規則以及規則由來。
實戰
UUID 真的唯一嗎
並不是!UUID會隨機生成一個32位的字符,代碼如下:
// 通過uuid生成一個32位的隨機數,並刪除隨機數中的“-”
String str = UUID.randomUUID().toString().replaceAll("-", "");
但它並不唯一,在單機多線程環境下也不唯一,我們可以改成下面的方式,並對其生成是隨機數進行處理,使其佔用空間更小,一切源於網絡,不是我自己寫的,總結而已。
String str = UUID.randomUUID().toString().replaceAll("-", "") + new Random().nextLong();
// 產生的字符串太長,浪費存儲,再進行MD5
// 可以使用apache的org.apache.commons.codec.digest.DigestUtils
// 也可以是使用java.security.MessageDigest進行加密
// 注意返回的是長度爲16的byte數組,使用Hex轉換成32的char數組,在轉成字符串
String uuid = new String(Hex.encodeHex(DigestUtils.md5(str)));
通過zookeeper生成隨機數
通過zk的版本號生成分佈式唯一且有序id的例子
直接上代碼吧:
- 核心代碼
package cc11001100.zookeeper.uniqId;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
/**
* 使用zk生成分佈式唯一id,自增有序
*
* @author CC11001100
*/
public class ZkIdGenerator {
private ZooKeeper zk;
private String path;
public ZkIdGenerator(String serverAddress, String path) {
try {
this.path = path;
zk = new ZooKeeper(serverAddress, 3000, event -> {
System.out.println(event.toString());
});
if (zk.exists(path, false) == null) {
zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException | InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
public long next() {
try {
Stat stat = zk.setData(path, new byte[0], -1);
return stat.getVersion();
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
return -1;
}
}
- 測試類
package cc11001100.zookeeper.uniqId;
/**
* @author CC11001100
*/
public class ZkIdGeneratorTest {
public static void main(String[] args) {
ZkIdGenerator zkIdGenerator = new ZkIdGenerator("xx.xx.xx.xx:2181", "/id-gen");
for(int i=0; i<100; i++){
System.out.println(zkIdGenerator.next());
}
}
}
注意
因爲版本號是保存在zk服務器上的,所以客戶端重啓無所謂,只要這個節點不被刪除就不會出現生成的id回退的情況。
通過redis
採用reids的RedisAtomicLong來實現
- SequenceFactory
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Service
public class SequenceFactory {
@Autowired
RedisTemplate<String, String> redisTemplate;
/**
* @param key
* @param value
* @param expireTime
* @Title: set
* @Description: set cache.
*/
public void set(String key, int value, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.set(value);
counter.expireAt(expireTime);
}
/**
* @param key *
* @param value *
* @param timeout *
* @param unit *
* @Title: set *
* @Description: set cache.
*/
public void set(String key, int value, long timeout, TimeUnit unit) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.set(value);
counter.expire(timeout, unit);
}
/**
* @param key *
* @return *
* @Title: generate *
* @Description: Atomically increments by one the current value.
*/
public long generate(String key) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return counter.incrementAndGet();
}
/**
* @param key *
* @return *
* @Title: generate *
* @Description: Atomically increments by one the current value.
*/
public long generate(String key, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.expireAt(expireTime);
return counter.incrementAndGet();
}
/**
* @param key *
* @param increment *
* @return *
* @Title: generate *
* @Description: Atomically adds the given value to the current value.
*/
public long generate(String key, int increment) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return counter.addAndGet(increment);
}
/**
* @param key *
* @param increment *
* @param expireTime *
* @return *
* @Title: generate *
* @Description: Atomically adds the given value to the current value.
*/
public long generate(String key, int increment, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.expireAt(expireTime);
return counter.addAndGet(increment);
}
}
- SequenceFactory
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Service
public class SequenceFactory {
@Autowired
RedisTemplate<String, String> redisTemplate;
/**
* @param key *
* @param value *
* @param expireTime *
* @Title: set *
* @Description: set cache.
*/
public void set(String key, int value, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.set(value);
counter.expireAt(expireTime);
}
/**
* @param key *
* @param value *
* @param timeout *
* @param unit *
* @Title: set *
* @Description: set cache.
*/
public void set(String key, int value, long timeout, TimeUnit unit) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.set(value);
counter.expire(timeout, unit);
}
/**
* @param key *
* @return *
* @Title: generate *
* @Description: Atomically increments by one the current value.
*/
public long generate(String key) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return counter.incrementAndGet();
}
/**
* @param key *
* @return *
* @Title: generate *
* @Description: Atomically increments by one the current value.
*/
public long generate(String key, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.expireAt(expireTime);
return counter.incrementAndGet();
}
/**
* @param key *
* @param increment *
* @return *
* @Title: generate *
* @Description: Atomically adds the given value to the current value.
*/
public long generate(String key, int increment) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return counter.addAndGet(increment);
}
/**
* @param key *
* @param increment *
* @param expireTime *
* @return *
* @Title: generate *
* @Description: Atomically adds the given value to the current value.
*/
public long generate(String key, int increment, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
counter.expireAt(expireTime);
return counter.addAndGet(increment);
}
}
它還可以統計“訪問量”
參考:https://blog.csdn.net/qq_35264464/article/details/79490664
開源方案
Flikr
基於int/bigint自增
- 優點
開發成本低 - 缺點
如果需要高性能,需要專門一套MySQL集羣只用於生成自增ID。可用性也不強
淺見:這種方案不太適合延展性,假設項目比較小,是不會有集羣的用武之地。
Snowflake
twitter利用zookeeper實現了一個全局ID生成的服務snowflake,https://github.com/twitter/snowflake,可以生成全局唯一的64bit ID。
ID構成如下:
時間--用前面41 bit來表示時間,精確到毫秒,可以表示69年的數據
機器ID--用10 bit來表示,也就是說可以部署1024臺機器
序列數--用12 bit來表示,意味着每臺機器,每毫秒最多可以生成4096個ID
- 優點
可用性強,速度快,id保存信息多 - 缺點
需要引入zookeeper和獨立的snowflake專用服務器
淺見:優缺點說明的很明確,需要獨立引入snowflake,對這個不熟悉的團隊還是避免使用這種技術,又不是練手害公司
instagram參考了flickr的方案,再結合twitter的經驗,利用Postgres數據庫的特性,實現了一個更簡單可靠的ID生成服務。
使用41 bit來存放時間,精確到毫秒,可以使用41年。
使用13 bit來存放邏輯分片ID。
使用10 bit來存放自增長ID,意味着每臺機器,每毫秒最多可以生成1024個ID
- 優點
開發成本低 - 缺點
基於postgreSQL的存儲過程,通用性差
淺見:通用性問題,如果使用的數據庫恰巧是postgreSQL可以使用這種方案,但假設沒用用到或者規劃中不會使用,建議採用別的方式。
總結
因爲現在大部分互聯網會使用redis和zk,所以建議使用zk和redis方式來生成隨機id,作爲訂單id也好,個人認爲設計原則就是生成的“容器”是脫離分佈式環境的單線程實例,所以可以自己去生成,大道至簡。