摘要:接著上一篇實(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 = <<3、運(yùn)行一把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; } }
獲取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ù)的方法,原理相同,不再解釋
測(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
摘要:出于以上兩個(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...
摘要:為什么需要發(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)...
摘要:實(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。 ...
閱讀 2590·2021-11-18 10:02
閱讀 1720·2021-09-30 10:00
閱讀 5351·2021-09-22 15:27
閱讀 1224·2019-08-30 15:54
閱讀 3685·2019-08-29 11:13
閱讀 2959·2019-08-29 11:05
閱讀 3336·2019-08-29 11:01
閱讀 581·2019-08-26 13:52