php + redis + lua implements a simple sender--Implementation Chapter

Keywords: PHP Redis

Next to the last article php + redis + lua to implement a simple emitter (1) -- Principle Chapter This article talks about the implementation of the emitter.

1. Basic knowledge

The implementation of the emitter mainly uses the following knowledge points:

1. Operations and evaluation of bit operations in PHP

2. Basic Concepts of Computer Primitive Code, Complement Code and Inverse Code

3. Writing and debugging of lua script in redis

If you are familiar with these knowledge, just look down and poke if you don't.

2. Realization

Let's start with the code, and then slowly analyze it.

    class SignGenerator
    {
        CONST BITS_FULL = 64;
        CONST BITS_PRE = 1;//fixed
        CONST BITS_TIME = 41;//Millisecond timestamps can support up to 69 years
        CONST BITS_SERVER = 5; //Up to 32 servers are supported
        CONST BITS_WORKER = 5; //Support up to 32 services
        CONST BITS_SEQUENCE = 12; //Supporting 4096 requests in a millisecond

        CONST OFFSET_TIME = "2019-05-05 00:00:00";//Time stamp start time

        /**
         * Server id
         */
        protected $serverId;

        /**
         * Business id
         */
        protected $workerId;

        /**
         * Example
         */
        protected static $instance;

        /**
         * redis service
         */
        protected static $redis;

        /**
         * Get a single instance
         */
        public static function getInstance($redis)
        {
            if(isset(self::$instance)) {
                return self::$instance;
            } else {
                return self::$instance = new self($redis);
            }
        }

        /**
         * Construct an initialization instance
         */
        protected function __construct($redis)
        {
            if($redis instanceof \Redis || $redis instanceof \Predis\Client) {
                self::$redis = $redis;
            } else {
                throw new \Exception("redis service is lost");
            }
        }

        /**
         * Get unique values
         */
        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;

                //Time stamp 41 bits
                $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";

                //The server
                $shift = $shift - self::BITS_SERVER;
                $id |= $this->serverId << $shift;
                echo "serverId=",$this->serverId,"\t";

                //business
                $shift = $shift - self::BITS_WORKER;
                $id |= $this->workerId << $shift;
                echo "workerId=",$this->workerId,"\t";

                //Self increment
                $sequenceNumber = $this->getSequence($id);
                echo "sequenceNumber=",$sequenceNumber,"\t";
                if($sequenceNumber > pow(2, self::BITS_SEQUENCE)) {
                    usleep(1000);
                } else {
                    $id |= $sequenceNumber;
                    return $id;
                }
            } while(true);
        }

        /**
         * Reverse Solution to Obtain Business Data
         */
        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;
        }

        /**
         * Obtaining self-increasing sequences
         */
        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. Run one

Get uuid

$redis = new Redis;

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

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

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

$number = $instance->getNumber();

//At the same time, in order to compare with the reversible operation, diffTime, serverId, workerId and sequenceNumber are recorded respectively. The results are as follows.

Inverse solution uuid

$redis = new Redis;

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

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

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

var_dump($item);die();

The print results are as follows, which are consistent with the previous ones by comparison.

4. Code parsing

Seen from the above code, there are a lot of bitwise operations using php. Some students may not have much contact with it. Here, take getNumber as an example, simply explain the above code. If you are already clear, please ignore this paragraph directly.

First of all, we understand a basic concept. All the data of computer are stored in the form of binary complement. The positive source code = inverse code = complement code

The implementation process of getNumber method is analyzed.

1. Initialize the sender

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

We can think of it as: pow (2, self:: BITS_FULL-self:: BITS_PRE) We applied for a piece of memory from the computer. It looks like this:

High <----------------------------------------------------------------------------------------------------- Low
10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

Execution bit operation, from low to high displacement, vacancies using 0 to fill, has become what it is now.
High <----------------------------------------------------------------------------------------------------- Low
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

Isn't that zero? Right. After experimental testing, we directly put $id = 0. The effect is the same.

So there are three ways to initialize $id
// $id = pow(2, self::BITS_FULL);
// $id = pow(2,self::BITS_FULL - self::BITS_PRE) << self::BITS_PRE;
// $id = 0;

2. Adding time attributes to the emitter

// Time stamp 41 bits
$nowTime = (int)(microtime(true) * 1000);
$startTime = (int)(strtotime(self::OFFSET_TIME) * 1000);

// Calculate the millisecond difference, based on the figure above, where diffTime=326570168
$diffTime = $nowTime - $startTime;

// Calculate displacement offset
$shift = self::BITS_FULL - self::BITS_PRE - self::BITS_TIME;

// Change the time bit bit bit of uuid
$id |= $diffTime << $shift;

The binary form of $id and $diffTime before the displacement is executed

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

$diffTime executes the displaced binary form

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

Next, do or operate with $id, and you get the following results
|-------------BITS_PRE + BITS_TIME------------||--------shift---------|
00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000

3. Adding Server Number to the Numberer

// Calculate the displacement offset with the new $shift
$shift = $shift - self::BITS_SERVER;

// Change the bit bit bit of uuid server
$id |= $this->serverId << $shift;

The binary form of $id and $serverId before the displacement is executed
|-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------|
00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000
                                                                      1

$serverId executes the displaced binary form
|-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------|
00000000 00000100 11011101 11000100 00101110 00000000 00000000 00000000
                                                   10 00000000 00000000
 Next, do or operate with $id, and you get the following results
|-------BITS_PRE + BITS_TIME + BITS_SERVER---------||------shift------|
00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
                 

4. Adding Business Numbers to the Numberer

// Calculate the displacement offset with the new $shift
$shift = $shift - self::BITS_WORKER;

// Change uuid's Business Number bit
$id |= $this->workerId << $shift;

$id and $workerId execute the binary form before the displacement, $workerId = 2
|---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---|
00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
                                                                     10
                                                                     
WorerId executes the displaced binary form
|---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---|
00000000 00000100 11011101 11000100 00101110 00000010 00000000 00000000
                                                        100000 00000000

Next, do or operate with $id, and you get the following results
|---BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER----||---shift---|
00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000000

5. Adding sequence to the emitter

// Here $sequenceNumber = 1
$id |= $sequenceNumber;

|--BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER + BITS_SEQUENCE--|
00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000000
                                                                      1  
Next, do or operate with $id, and you get the following results
|--BITS_PRE + BITS_TIME + BITS_SERVER + BITS_WORKDER + BITS_SEQUENCE--|
00000000 00000100 11011101 11000100 00101110 00000010 00100000 00000001
                                                       

Finally, we get the binary data as follows: 100 11011101 11000 10010110 00010 00010 00000 00000000001. The corresponding number is 1369734562062337 through the binary conversion.

5. References

Implementation of Distributed ID Generator PHP+Swoole (Part 2) - Code Implementation

Source Code, Inverse Code and Complement Code

Due to the limited ability and level, there will inevitably be mistakes. I hope readers can spend in time!

Posted by sudhakararaog on Sat, 18 May 2019 07:55:37 -0700