接着上一篇 php + redis + lua 实现一个简单的发号器(1)-- 原理篇,本篇讲一下发号器的具体实现。

1、基础知识

发号器的实现主要用到了下面的一些知识点:

1. php中的位运算的操作和求值

2. 计算机原码、补码、反码的基本概念

3. redis中lua脚本的编写和调试

如果你对这些知识已经熟悉,直接往下看即可, 不了解的话就猛戳。

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 sequenceNumberLUA;            $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实现(下) - 代码实现

原码,反码,补码杂谈

由于能力和水平的有限,难免会有错误,希望读者及时支出!