成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

php + redis + lua 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的發(fā)號(hào)器(2)-- 實(shí)現(xiàn)篇

iOS122 / 520人閱讀

摘要:接著上一篇實(shí)現(xiàn)一個(gè)簡(jiǎn)單的發(fā)號(hào)器原理篇,本篇講一下發(fā)號(hào)器的具體實(shí)現(xiàn)。統(tǒng)計(jì)最后一列的總數(shù)量和去重后的數(shù)量是否一致即可。

接著上一篇 php + redis + lua 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的發(fā)號(hào)器(1)-- 原理篇,本篇講一下發(fā)號(hào)器的具體實(shí)現(xiàn)。

1、基礎(chǔ)知識(shí)

發(fā)號(hào)器的實(shí)現(xiàn)主要用到了下面的一些知識(shí)點(diǎn):

1. php中的位運(yùn)算的操作和求值

2. 計(jì)算機(jī)原碼、補(bǔ)碼、反碼的基本概念

3. redis中l(wèi)ua腳本的編寫和調(diào)試

如果你對(duì)這些知識(shí)已經(jīng)熟悉,直接往下看即可, 不了解的話就猛戳。

2、具體實(shí)現(xiàn)

先上代碼吧,然后再慢慢分析

class SignGenerator
{
    CONST BITS_FULL = 64;
    CONST BITS_PRE = 1;//固定
    CONST BITS_TIME = 41;//毫秒時(shí)間戳 可以最多支持69年
    CONST BITS_SERVER = 5; //服務(wù)器最多支持32臺(tái)
    CONST BITS_WORKER = 5; //最多支持32種業(yè)務(wù)
    CONST BITS_SEQUENCE = 12; //一毫秒內(nèi)支持4096個(gè)請(qǐng)求

    CONST OFFSET_TIME = "2019-05-05 00:00:00";//時(shí)間戳起點(diǎn)時(shí)間

    /**
     * 服務(wù)器id
     */
    protected $serverId;

    /**
     * 業(yè)務(wù)id
     */
    protected $workerId;

    /**
     * 實(shí)例
     */
    protected static $instance;

    /**
     * redis 服務(wù)
     */
    protected static $redis;

    /**
     * 獲取單個(gè)實(shí)例
     */
    public static function getInstance($redis)
    {
        if (isset(self::$instance)) {
            return self::$instance;
        } else {
            return self::$instance = new self($redis);
        }
    }

    /**
     * 構(gòu)造初始化實(shí)例
     */
    protected function __construct($redis)
    {
        if ($redis instanceof Redis || $redis instanceof PredisClient) {
            self::$redis = $redis;
        } else {
            throw new Exception("redis service is lost");
        }
    }

    /**
     * 獲取唯一值
     */
    public function getNumber()
    {
        if (!isset($this->serverId)) {
            throw new Exception("serverId is lost");
        }
        if (!isset($this->workerId)) {
            throw new Exception("workerId is lost");
        }

        do {
            $id = pow(2, self::BITS_FULL - self::BITS_PRE) << self::BITS_PRE;

            //時(shí)間戳 41位
            $nowTime = (int)(microtime(true) * 1000);
            $startTime = (int)(strtotime(self::OFFSET_TIME) * 1000);
            $diffTime = $nowTime - $startTime;
            $shift = self::BITS_FULL - self::BITS_PRE - self::BITS_TIME;
            $id |= $diffTime << $shift;
            $uuidItem["segment"]["diffTime"] = $diffTime;

            //服務(wù)器
            $shift = $shift - self::BITS_SERVER;
            $id |= $this->serverId << $shift;
            $uuidItem["segment"]["serverId"] = $this->serverId;

            //業(yè)務(wù)
            $shift = $shift - self::BITS_WORKER;
            $id |= $this->workerId << $shift;
            $uuidItem["segment"]["workerId"] = $this->workerId;

            //自增值
            $sequenceNumber = $this->getSequence($id);
            $uuidItem["segment"]["sequenceNumber"] = $sequenceNumber;
            if ($sequenceNumber > pow(2, self::BITS_SEQUENCE) - 1) {
                usleep(1000);
            } else {
                $id |= $sequenceNumber;
                $uuidItem["uuid"] = $id;
                return $uuidItem;
            }
        } while (true);
    }

    /**
     * 反解獲取業(yè)務(wù)數(shù)據(jù)
     */
    public function reverseNumber($number)
    {
        $uuidItem = [];
        $shift = self::BITS_FULL - self::BITS_PRE - self::BITS_TIME;
        $uuidItem["diffTime"] = ($number >> $shift) & (pow(2, self::BITS_TIME) - 1);

        $shift -= self::BITS_SERVER;
        $uuidItem["serverId"] = ($number >> $shift) & (pow(2, self::BITS_SERVER) - 1);

        $shift -= self::BITS_WORKER;
        $uuidItem["workerId"] = ($number >> $shift) & (pow(2, self::BITS_WORKER) - 1);

        $shift -= self::BITS_SEQUENCE;
        $uuidItem["sequenceNumber"] = ($number >> $shift) & (pow(2, self::BITS_SEQUENCE) - 1);

        $time = (int)($uuidItem["diffTime"] / 1000) + strtotime(self::OFFSET_TIME);
        $uuidItem["generateTime"] = date("Y-m-d H:i:s", $time);

        return $uuidItem;
    }

    /**
     * 獲取自增序列
     */
    protected function getSequence($id)
    {
        $lua = <<eval($lua, [$id], 1);
        $luaError = self::$redis->getLastError();
        if (isset($luaError)) {
            throw new ErrorException($luaError);
        } else {
            return $sequence;
        }
    }

    /**
     * @return mixed
     */
    public function getServerId()
    {
        return $this->serverId;
    }

    /**
     * @param mixed $serverId
     */
    public function setServerId($serverId)
    {
        $this->serverId = $serverId;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getWorkerId()
    {
        return $this->workerId;
    }

    /**
     * @param mixed $workerId
     */
    public function setWorkerId($workerId)
    {
        $this->workerId = $workerId;
        return $this;
    }
}

3、運(yùn)行一把

獲取uuid

$redis = new Redis;

$redis->connect("127.0.0.1", 6379);

$instance = SignGenerator::getInstance($redis);

$instance->setWorkerId(2)->setServerId(1);

$number = $instance->getNumber();

//于此同時(shí),為了方便同可反解操作做對(duì)別,分別記錄下來 diffTime,serverId,workerId,sequenceNumber, 運(yùn)行結(jié)果如下圖

反解uuid

$redis = new Redis;

$redis->connect("127.0.0.1", 6379);

$instance = SignGenerator::getInstance($redis);

$item = $instance->reverseNumber(1369734562062337);

var_dump($item);die();

打印結(jié)果如下, 通過對(duì)比發(fā)現(xiàn)和之前的一致

4、代碼解析

從上面的代碼上看,里面大量的使用了php的位運(yùn)算操作,可能有些同學(xué)接觸的不多,這里以getNumber為例,簡(jiǎn)單解釋一下上面的代碼,如果你已經(jīng)很清楚了,那就請(qǐng)直接忽略本段。

首先明白一個(gè)基礎(chǔ)的概念,計(jì)算機(jī)所有的數(shù)據(jù)都是以二進(jìn)制補(bǔ)碼的形式進(jìn)行存儲(chǔ)的,正數(shù)的原碼 = 反碼 = 補(bǔ)碼

分析getNumber方法的實(shí)現(xiàn)過程:

1、初始化發(fā)號(hào)器

$id = pow(2,self::BITS_FULL - self::BITS_PRE) << self::BITS_PRE;

我們可以認(rèn)為:pow(2,self::BITS_FULL - self::BITS_PRE)我們向計(jì)算機(jī)申請(qǐng)了一塊內(nèi)存,它大概長(zhǎng)下面這個(gè)樣子:

高位  <----------------------------------------------------------   低位
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

執(zhí)行位運(yùn)算,由低位向高位移動(dòng),空位使用0補(bǔ)齊,變成了現(xiàn)在的這個(gè)樣子
高位  <----------------------------------------------------------   低位
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

這不就是0么,對(duì)的,經(jīng)過實(shí)驗(yàn)測(cè)試,直接將$id = 0,效果是一樣的

所以$id 的初始化有下面三種
// $id = pow(2, self::BITS_FULL);
// $id = pow(2,self::BITS_FULL - self::BITS_PRE) << self::BITS_PRE;
// $id = 0;

2、為發(fā)號(hào)器添加時(shí)間屬性

//時(shí)間戳 41位
$nowTime = (int)(microtime(true) * 1000);
$startTime = (int)(strtotime(self::OFFSET_TIME) * 1000);

//計(jì)算毫秒差,基于上圖,這里 diffTime=326570168
$diffTime = $nowTime - $startTime;

//計(jì)算出位移 的偏移量
$shift = self::BITS_FULL - self::BITS_PRE - self::BITS_TIME;

//改變uuid的時(shí)間bit位
$id |= $diffTime << $shift;

$id 與 $diffTime 執(zhí)行位移前的二進(jìn)制形式

|-------------BITS_PRE + BITS_TIME------------||--------shift---------|
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
                                       10011 01110111 00010000 10111000

$diffTime 執(zhí)行位移后的二進(jìn)制形式

|-------------BITS_PRE + BITS_TIME------------||--------shift---------|
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
              100 11011101 11000100 00101110 00|--------shift---------|

緊接著同$id進(jìn)行或操作,得到如下結(jié)果
|-------------BITS_PRE + BITS_TIME------------||--------shift---------|
00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000

3、為發(fā)號(hào)器添加服務(wù)器編號(hào)

//在新的$shift 計(jì)算出位移 的偏移量
$shift = $shift - self::BITS_SERVER;

//改變uuid的服務(wù)器bit位
$id |= $this->serverId << $shift;

$id 與 $serverId 執(zhí)行位移前的二進(jìn)制形式
|-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------|
00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000
                                                                      1

$serverId 執(zhí)行位移后的二進(jìn)制形式
|-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------|
00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000
                                                   10 00000000 00000000
緊接著同$id進(jìn)行或操作,得到如下結(jié)果
|-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------|
00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
                 

4、為發(fā)號(hào)器添加業(yè)務(wù)編號(hào)

//在新的$shift 計(jì)算出位移 的偏移量
$shift = $shift - self::BITS_WORKER;

//改變uuid的業(yè)務(wù)編號(hào)bit位
$id |= $this->workerId << $shift;

$id 與 $workerId 執(zhí)行位移前的二進(jìn)制形式, $workerId = 2
|---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---|
00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
                                                                     10
                                                                     
$workerId 執(zhí)行位移后的二進(jìn)制形式
|---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---|
00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
                                                        100000 00000000

緊接著同$id進(jìn)行或操作,得到如下結(jié)果
|---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---|
00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000000

5、為發(fā)號(hào)器添加sequence

//這里$sequenceNumber = 1
$id |= $sequenceNumber;

|--BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER + BITS_SEQUENCE--|
00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000000
                                                                      1  
緊接著同$id進(jìn)行或操作,得到如下結(jié)果
|--BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER + BITS_SEQUENCE--|
00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000001
                                                       

最后我們得出二進(jìn)制數(shù)據(jù)為:100 11011101 11000100 00101110 00000010 00100000 00000001,通過進(jìn)制轉(zhuǎn)換得到對(duì)應(yīng)的數(shù)字就是:1369734562062337。
反解獲取業(yè)務(wù)數(shù)據(jù)的方法,原理相同,不再解釋

5、測(cè)試

測(cè)試方法很簡(jiǎn)單,循環(huán)寫入5萬次,看看是否有重復(fù)的uuid出現(xiàn)?

connect("127.0.0.1", 6379);
$instance = SignGenerator::getInstance($redis);
$instance->setServerId(1)->setWorkerId(2);

//循環(huán)寫入10萬次
for($count = 1; $count <= 100000; $count++) {
    $uuidItem = $instance->getNumber();
    $segment = $uuidItem["segment"];
    $uuid = $uuidItem["uuid"];
    echo implode("	", $segment), "	", $uuid, "
";
}

執(zhí)行 php ./SignTest.php >> /tmp/SignTest.log命令,所有的運(yùn)行結(jié)果講會(huì)被保存在/tmp/SignTest.log中。統(tǒng)計(jì)最后一列的總數(shù)量和去重后的數(shù)量是否一致即可。

6、發(fā)現(xiàn)的問題

需要注意的是,由于網(wǎng)絡(luò)情況的不同,建議將redis中key的過期時(shí)間進(jìn)行調(diào)整,這里是100毫秒,否則可能會(huì)出現(xiàn)相同的uuid

具體原因如下,相同的key值(相同的diffTime + 相同的workerId + 相同的serverId 會(huì)產(chǎn)生相同的key),去獲取sequence, 第一個(gè)請(qǐng)求者執(zhí)行完畢后,返回得到1后返回,此時(shí)redis 將key過期回收。第二個(gè)請(qǐng)求過去,key不存在,返回也得到1,此時(shí)會(huì)造成相同的uuid

7、參考資料

分布式ID生成器PHP+Swoole實(shí)現(xiàn)(下) - 代碼實(shí)現(xiàn)

原碼,反碼,補(bǔ)碼雜談

由于能力和水平的有限,難免會(huì)有錯(cuò)誤,希望讀者及時(shí)支出!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/31427.html

相關(guān)文章

  • php + redis + lua 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的發(fā)號(hào)器(1)-- 原理

    摘要:出于以上兩個(gè)原因,我們需要自己的發(fā)號(hào)器來產(chǎn)生。與此同時(shí),為了保證執(zhí)行,具有原子性,我們使用來進(jìn)行實(shí)現(xiàn)。由于能力和水平有限,難免會(huì)有紕漏,希望及時(shí)指出。參考文章分布式生成器實(shí)現(xiàn)上實(shí)現(xiàn)原理 1、為什么要實(shí)現(xiàn)發(fā)號(hào)器 很多地方我們都需要一個(gè)全局唯一的編號(hào),也就是uuid。舉一個(gè)常見的場(chǎng)景,電商系統(tǒng)產(chǎn)生訂單的時(shí)候,需要有一個(gè)對(duì)應(yīng)的訂單編號(hào)。在composer上我們也可以看到有很多可以產(chǎn)生uuid...

    rottengeek 評(píng)論0 收藏0
  • 分布式系統(tǒng)全局發(fā)號(hào)器的幾點(diǎn)思考

    摘要:為什么需要發(fā)號(hào)器在分布式系統(tǒng)中,經(jīng)常需要對(duì)大量的數(shù)據(jù)消息請(qǐng)求等進(jìn)行唯一標(biāo)識(shí),例如對(duì)于分布式系統(tǒng),服務(wù)間相互調(diào)用需要唯一標(biāo)識(shí),調(diào)用鏈路分析,日志追蹤的時(shí)候需要使用這個(gè)唯一標(biāo)識(shí)。 原文鏈接:何曉東 博客 文章起源于 康神交流群的 panda大佬和boss li關(guān)于發(fā)號(hào)器的一些交流,特此感謝讓我們學(xué)到了新知識(shí)。 為什么需要發(fā)號(hào)器 在分布式系統(tǒng)中,經(jīng)常需要對(duì)大量的數(shù)據(jù)、消息、http 請(qǐng)求等進(jìn)...

    dayday_up 評(píng)論0 收藏0
  • 基于Redis作為發(fā)號(hào)器生成短網(wǎng)址Python實(shí)踐

    摘要:實(shí)現(xiàn)發(fā)號(hào)器使用的函數(shù)個(gè)字符作為進(jìn)制符號(hào)轉(zhuǎn)成進(jìn)制為代碼作為發(fā)號(hào)器生成短網(wǎng)址,假如域名為通過解碼到原本文為原創(chuàng)首發(fā)于繼續(xù)閱讀全文 showImg(https://segmentfault.com/img/bV9f8g?w=1120&h=126); 描述 如何將長(zhǎng)地址URL轉(zhuǎn)換為短地址URL,一個(gè)比較理想的解決方案就是使用發(fā)號(hào)器生成一個(gè)唯一的整數(shù)ID,然后轉(zhuǎn)換為62進(jìn)制,作為短地址URL。 ...

    trilever 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<