内容简介:接着上一篇发号器的实现主要用到了下面的一些知识点:
接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇 ,本篇讲一下发号器的具体实现。
1、基础知识
发号器的实现主要用到了下面的一些知识点:
如果你对这些知识已经熟悉,直接往下看即可, 不了解的话就猛戳。
2、具体实现
先上代码吧,然后再慢慢分析
class SignGenerator { CONST BITS_FULL = 64; CONST BITS_PRE = 1;//固定 CONST BITS_TIME = 41;//毫秒时间戳 可以最多支持69年 CONST BITS_SERVER = 5; //服务器最多支持32台 CONST BITS_WORKER = 5; //最多支持32种业务 CONST BITS_SEQUENCE = 12; //一毫秒内支持4096个请求 CONST OFFSET_TIME = "2019-05-05 00:00:00";//时间戳起点时间 /** * 服务器id */ protected $serverId; /** * 业务id */ protected $workerId; /** * 实例 */ protected static $instance; /** * redis 服务 */ protected static $redis; /** * 获取单个实例 */ public static function getInstance($redis) { if(isset(self::$instance)) { return self::$instance; } else { return self::$instance = new self($redis); } } /** * 构造初始化实例 */ protected function __construct($redis) { if($redis instanceof \Redis || $redis instanceof \Predis\Client) { 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; //时间戳 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; echo "diffTime=",$diffTime,"\t"; //服务器 $shift = $shift - self::BITS_SERVER; $id |= $this->serverId << $shift; echo "serverId=",$this->serverId,"\t"; //业务 $shift = $shift - self::BITS_WORKER; $id |= $this->workerId << $shift; echo "workerId=",$this->workerId,"\t"; //自增值 $sequenceNumber = $this->getSequence($id); echo "sequenceNumber=",$sequenceNumber,"\t"; if($sequenceNumber > pow(2, self::BITS_SEQUENCE)) { usleep(1000); } else { $id |= $sequenceNumber; return $id; } } while(true); } /** * 反解获取业务数据 */ 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 = <<<LUA local sequenceKey = KEYS[1] local sequenceNumber = redis.call("incr", sequenceKey); redis.call("pexpire", sequenceKey, 1); return sequenceNumber LUA; $sequence = self::$redis->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、运行一把
获取uuid
$redis = new Redis; $redis->connect("127.0.0.1", 6379); $instance = SignGenerator::getInstance($redis); $instance->setWorkerId(2)->setServerId(1); $number = $instance->getNumber(); //于此同时,为了方便同可反解操作做对别,分别记录下来 diffTime,serverId,workerId,sequenceNumber, 运行结果如下图
反解uuid
$redis = new Redis; $redis->connect("127.0.0.1", 6379); $instance = SignGenerator::getInstance($redis); $item = $instance->reverseNumber(1369734562062337); var_dump($item);die(); 打印结果如下, 通过对比发现和之前的一致
4、代码解析
从上面的代码上看,里面大量的使用了 php 的位运算操作,可能有些同学接触的不多,这里以getNumber为例,简单解释一下上面的代码,如果你已经很清楚了,那就请直接忽略本段。
首先明白一个基础的概念,计算机所有的数据都是以二进制补码的形式进行存储的,正数的原码 = 反码 = 补码
分析getNumber方法的实现过程:
1、初始化发号器
$id = pow(2,self::BITS_FULL - self::BITS_PRE) << self::BITS_PRE; 我们可以认为:pow(2,self::BITS_FULL - self::BITS_PRE)我们向计算机申请了一块内存,它大概长下面这个样子: 高位 <---------------------------------------------------------- 低位 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 执行位运算,由低位向高位移动,空位使用0补齐,变成了现在的这个样子 高位 <---------------------------------------------------------- 低位 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 这不就是0么,对的,经过实验测试,直接将$id = 0,效果是一样的 所以$id 的初始化有下面三种 // $id = pow(2, self::BITS_FULL); // $id = pow(2,self::BITS_FULL - self::BITS_PRE) << self::BITS_PRE; // $id = 0;
2、为发号器添加时间属性
//时间戳 41位 $nowTime = (int)(microtime(true) * 1000); $startTime = (int)(strtotime(self::OFFSET_TIME) * 1000); //计算毫秒差,基于上图,这里 diffTime=326570168 $diffTime = $nowTime - $startTime; //计算出位移 的偏移量 $shift = self::BITS_FULL - self::BITS_PRE - self::BITS_TIME; //改变uuid的时间bit位 $id |= $diffTime << $shift; $id 与 $diffTime 执行位移前的二进制形式 |-------------BITS_PRE + BITS_TIME------------||--------shift---------| 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 10011 01110111 00010000 10111000 $diffTime 执行位移后的二进制形式 |-------------BITS_PRE + BITS_TIME------------||--------shift---------| 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 100 11011101 11000100 00101110 00|--------shift---------| 紧接着同$id进行或操作,得到如下结果 |-------------BITS_PRE + BITS_TIME------------||--------shift---------| 00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000
3、为发号器添加服务器编号
//在新的$shift 计算出位移 的偏移量 $shift = $shift - self::BITS_SERVER; //改变uuid的服务器bit位 $id |= $this->serverId << $shift; $id 与 $serverId 执行位移前的二进制形式 |-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------| 00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000 1 $serverId 执行位移后的二进制形式 |-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------| 00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000 10 00000000 00000000 紧接着同$id进行或操作,得到如下结果 |-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------| 00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
4、为发号器添加业务编号
//在新的$shift 计算出位移 的偏移量 $shift = $shift - self::BITS_WORKER; //改变uuid的业务编号bit位 $id |= $this->workerId << $shift; $id 与 $workerId 执行位移前的二进制形式, $workerId = 2 |---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---| 00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000 10 $workerId 执行位移后的二进制形式 |---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---| 00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000 100000 00000000 紧接着同$id进行或操作,得到如下结果 |---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---| 00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000000
5、为发号器添加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进行或操作,得到如下结果 |--BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER + BITS_SEQUENCE--| 00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000001
最后我们得出二进制数据为:100 11011101 11000100 00101110 00000010 00100000 00000001,通过进制转换得到对应的数字就是:1369734562062337
5、参考资料
分布式ID生成器PHP+Swoole实现(下) - 代码实现
由于能力和水平的有限,难免会有错误,希望读者及时支出!
以上所述就是小编给大家介绍的《php + redis + lua 实现一个简单的发号器(2)-- 实现篇》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
算法竞赛入门经典
刘汝佳 / 清华大学出版社 / 2009-11 / 24.00元
《算法竞赛入门经典》是一本算法竞赛的入门教材,把C/C++语言、算法和解题有机地结合在了一起,淡化理论,注重学习方法和实践技巧。全书内容分为11章,包括程序设计入门、循环结构程序设计、数组和字符串、函数和递归、基础题目选解、数据结构基础、暴力求解法、高效算法设计、动态规划初步、数学概念与方法、图论模型与算法,覆盖了算法竞赛入门所需的主要知识点,并附有大量习题。书中的代码规范、简洁、易懂,不仅能帮助......一起来看看 《算法竞赛入门经典》 这本书的介绍吧!