接上一篇php+redis+lua实现一个简单的信号器(一)——原理篇,本篇再说信号发生器的具体实现。一、基础知识数生成器的实现主要用到以下知识点:1、php中位运算的运算与求值2、计算机原码、补码、反码的基本概念3、redis中的lua脚本如果你已经熟悉这方面知识的编写和调试,直接看就好了,不懂就戳一下。2、我们先实现代码,再慢慢分析classSignGenerator{CONSTBITS_FULL=64;CONSTBITS_PRE=1;//FixedCONSTBITS_TIME=41;//毫秒时间戳最多支持69年CONSTBITS_SERVER=5;//服务器最多支持32台CONSTBITS_WORKER=5;//最多支持32种业务CONSTBITS_SEQUENCE=12;//一毫秒内支持4096次请求CONSTOFFSET_TIME="2019-05-0500:00:00";//时间戳开始时间/***serverid*/protected$serverId;/***企业ID*/protected$workerId;/***实例*/protectedstatic$instance;/***redis服务*/protectedstatic$redis;/***获取单个实例*/publicstaticfunctiongetInstance($redis){if(isset(self::$instance)){returnself::$instance;}else{returnself::$instance=newself($redis);}}/***构造初始化实例*/protectedfunction__construct($redis){if($redisinstanceof\Redis||$redisinstanceof\Predis\Client){self::$redis=$redis;}别的{thrownew\Exception("redis服务丢失");}}/***获取唯一值*/publicfunctiongetNumber(){if(!isset($this->serverId)){thrownew\Exception("serverIdislost");}if(!isset($this->workerId)){thrownew\Exception("workerIdislost");}do{$id=pow(2,self::BITS_FULL-self::BITS_PRE)<serverId<<$shift;$uuidItem['段']['serverId']=$this->serverId;//业务$shift=$shift-self::BITS_WORKER;$id|=$this->workerId<<$shift;$uuidItem['segment']['workerId']=$this->workerId;//自增值$sequenceNumber=$this->getSequence($id);$uuidItem['段']['sequenceNumber']=$sequenceNumber;如果($sequenceNumber>pow(2,self::BITS_SEQUENCE)-1){usleep(1000);}else{$id|=$sequenceNumber;$uuidItem['uuid']=strval($id);返回$uuidItem;}}而(真);}/***反获取业务数据*/publicfunctionreverseNumber($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-dH:i:s",$time);返回$uuidItem;}/***获取自增序列*/protectedfunctiongetSequence($id){$lua=<<eval($lua,[$id],1);$luaError=self::$redis->getLastError();如果(isset($luaError)){抛出新的\ErrorException($luaError);}else{返回$序列;}}/***@returnmixed*/publicfunctiongetServerId(){return$this->serverId;}/***@parammixed$serverId*/publicfunctionsetServerId($serverId){$this->serverId=$serverId;返回$这个;}/***@returnmixed*/publicfunctiongetWorkerId(){return$this->workerId;}/***@parammixed$workerId*/publicfunctionsetWorkerId($workerId){$this->workerId=$workerId;返回$这个;}}3.运行一个获取uuid$redis=newRedis;$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=newRedis;$redis->connect("127.0.0.1",6379);$instance=符号生成器::getInstance($redis);$ite??m=$instance->reverseNumber(1369734562062337);var_dump($item);die();打印结果如下,通过对比发现和前面的一致4.代码分析从上面的代码来看,在php中使用了大量的位操作。有些学生可能与他们接触不多。这里我们以getNumber为例简单说明一下上面的代码。如果你已经很清楚了,请忽略这段先了解一个基本概念。所有计算机数据都以二进制补码的形式存储。正数的原码=反码=补码解析。getNumber方法的实现过程:1.初始化数生成器$id=pow(2,self::BITS_FULL-self::BITS_PRE)<serverId<<$shift;移位前$id和$serverId的二进制形式|--------BITS_PRE+BITS_TIME+BITS_SERVER--------||------shift------|00000000000001001101110111000100001011100000000000000000000000001$serverId移位后的二进制形式|--------BITS_PRE+BITS_TIME+BITS_SERVER--------||-----shift------|0000000000000100110111011100010000101110000000000000000000000000100000000000000000然后用$id进行或操作得到以下结果|------BITS_PRE+--BITS-shift-TIME-+--BITS-TIME---|00000000000001001101110111000100001011100000001000000000000000004.将业务编号添加到编号生成器中//在新的$shift中计算位移偏移$shift=$shift-self::BITS_WORKER;//更改业务编号位uuid$id|=$this->workerId<<$shift;$id和$workerId在移位前的二进制形式,$workerId=2|---BITS_PRE+BITS_TIME+BITS_SERVER+BITS_WORKDER----||---shift---|000000000000010011011101110001000010111000000010000000000$workerIdisthebinaryformaftershifting|---BITS_PRE+BITS_TIME+BITS_SERVER+BITS_WORKDER----||---shift---|000000000000010011011101110001000010111000000010or00followedby0idfollowedby00$0000Operation,getthefollowingresult|---BITS_PRE+BITS_TIME+BITS_SERVER+BITS_WORKDER----||---shift---|000000000000010011011101110001000010111000000010001000000000000005,addsequenceforthenumbergeneratorhere=1$id|=$sequenceNumber;|--BITS_PRE+BITS_TIME+BITS_SERVER+BITS_WORKDER+BITS_SEQUENCE--|00000000000001001101110111000100001011100000001000100000000000001紧接着同$id进行或操作,得到如下结果|--BITS_PRE+BITS_TIME+BITS_SERVER+BITS_WORKDER+BITS_SEQUENCE--|0000000000000100110111011100010000101110000000100010000000000001最后我们得出二进制数据为:100110111011100010000101110000000100010000000000001,通通过base转换得到对应的数字为:1369734562062337,取业务数据的方法逆向,原理相同,不再赘述。connect('127.0.0.1',6379);$instance=SignGenerator::getInstance($redis);$instance->setServerId(1)->setWorkerId(2);//循环写入10万次for($count=1;$count<=100000;$count++){$uuidItem=$instance->getNumber();$segment=$uuidItem['segment'];$uuid=$uuidItem['uuid'];echoimplode("\t",$segment),"\t",$uuid,"\n";}执行php./SignTest.php>>/tmp/SignTest.log命令,所有运行结果会保存在/tmp/SignTest.log。只统计最后一列的总数和去重后的数量是否一致。6、发现的问题需要注意的是,由于网络情况不同,建议调整redis中key的过期时间,这里是100毫秒,否则可能会出现相同的uuid。具体原因如下,相同的key值(相同的diffTime+相同的workerId+相同的serverId会生成相同的key),获取序列,第一个请求者执行完后返回1,然后redis会回收过期后的密钥。第二次请求通过,key不存在,返回1。此时同一个uuid7,参考资料分布式ID生成器PHP+Swoole实现(下)-代码实现原码、反码、补码杂谈由于能力和水平有限,难免会有错误。希望读者及时付款!