實戰 —— 生存唯一隨機數

實戰 —— 生存唯一隨機數

前言

實際開發過程中,我們會遇到生成唯一標識的隨機數的需求,下面總結一下在網上看到的一些規則以及規則由來。


實戰

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

instagram參考了flickr的方案,再結合twitter的經驗,利用Postgres數據庫的特性,實現了一個更簡單可靠的ID生成服務。

使用41 bit來存放時間,精確到毫秒,可以使用41年。
使用13 bit來存放邏輯分片ID。
使用10 bit來存放自增長ID,意味着每臺機器,每毫秒最多可以生成1024個ID
  • 優點
    開發成本低
  • 缺點
    基於postgreSQL的存儲過程,通用性差

淺見:通用性問題,如果使用的數據庫恰巧是postgreSQL可以使用這種方案,但假設沒用用到或者規劃中不會使用,建議採用別的方式。

總結

因爲現在大部分互聯網會使用redis和zk,所以建議使用zk和redis方式來生成隨機id,作爲訂單id也好,個人認爲設計原則就是生成的“容器”是脫離分佈式環境的單線程實例,所以可以自己去生成,大道至簡。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章