A few days ago, my colleague encountered a problem about the failure of updating Redis, which I found very interesting. The technical details behind it can also be further explored, so I made a record in this paper. I also have a lot of questions about this question, and I hope the readers of this article can answer them.
The code functions and implementation are also very simple:
$ip = "localhost"; $port = 24362; $socket=fsockopen($ip,$port,$errno, $errstr); $t1 = microtime(true); $line=""; $arr = range (1100); foreach($arr as $K=>$t){ $line="lpush send_mass_mailx {$row}\r\n"; $bool = fwrite($socket,$line); if ($bool === false) { echo $t . "_error\n" ; exit; } } fclose($socket); $t2 = microtime(true); Echo round($t2-$t1,4).
Copy the code
As you can see from the above code, the function is to loop data into the REIDS queue. The problem is that the llen send_mass_mailx command always fails to insert data between 990 and 1000.
I didn’t know the reason at the time, but from the point of view of code rigor (the response of redis server is not judged, so the code cannot know whether the data is successfully inserted into the Redis queue), I modified the code:
$ip = "localhost"; $port = 24362; $socket=fsockopen($ip,$port,$errno, $errstr); $t1 = microtime(true); $line=""; $arr = range (1100); foreach($arr as $K=>$t){ $line="lpush send_mass_mailx {$row}\r\n"; $bool = fwrite($socket,$line); if ($bool === false) { echo $t . "_error\n" ; exit; } $m = fgets($socket,200); echo $m "\n" } fclose($socket); $t2 = microtime(true); Echo round($t2-$t1,4).
Copy the code
The difference between the two snippets is small (the second code adds the fgets operation), but the result is quite different: the second code not only runs successfully, but successfully inserts 1000 values into the Redis queue.
What’s the reason behind it?
Many people may ask why not use some PHP Redis wrapper libraries. From that point of view, using the wrapper library is the right decision, but this article aims to analyze the problem and understand the principle behind it from another point of view.
I first captured the second code fragment using tcpdump and then analyzed it using Wireshark.
As can be seen, each loop, the code will timely read the response of REDis, that is, to complete a REDIS update, the client (the machine running the code) to the server (Redis) through an RTT round trip (the client and the server send and request).
Take a look at the details of each request sent by the client, as shown in the figure below:
Then look at the server’s response, as shown below:
The above two figures show what the client and server do to further validate the interaction between a request and an update.
I then captured the first code fragment in tcpdump and found the following information, as shown in figure 1:
As you can see from the sample figure, the client is constantly sending data to the server without waiting for a response from the server. Unlike the first code snippet, 1000 RTTS are not required to complete the 1000 value update.
So what is sent for each update operation? Look at the last client operation, as shown in figure:
As you can see, the value of the last lpush operation on the client is the 991st value, which means that the problem is caused by the client, which did not send all the data update operations.
Conclusions and assumptions:
(1) The behavior behind fwrite
There is a difference between PHP’s fwrite and socket (such as socket_connect). Write is further abstracted and can send data to local files and network handles.
The value returned by fwrite does not represent a data update operation, but rather a successful sending to the local cache (which may not be sending to a local file or network handle), such as running the following code:
$line="lpushx send_mass_mailx {$row}\r\n";
$bool = fwrite($socket,$line);
Copy the code
Even if lPUSHx is intentionally miswritten, the return is still successful.
(2) Network request behind
In the case of the first code snippet, the PHP parser receives a fwrite command, not immediately, but puts the request into the cache, merges multiple updates, and then sends the request to Redis.
In the second code fragment, the PHP parser issues a network request as soon as it receives a fwrite command, so that the client code knows in detail whether the data was successfully updated.
(3) Non-blocking operations and blocking operations
In the case of the first snippet, this is a non-blocking operation where the client code does not wait for a response from the Redis server every time it sends a request, so it performs a very large block, basically taking less than 0.003 seconds.
The second fragment is a blocking operation, and the client code waits for a response from the Redis server every time it sends a request, so execution is relatively slow, taking about 0.5 seconds to complete.
I’m not entirely sure why the client is sending fewer reids updates. It could be that the local cache is full (it clears every fgets), but I can’t prove it. Since Fwrite is highly encapsulated, this mechanism is not controlled by PHP. It’s the operating system and TCP/IP control behind it.
If the reader can understand why, please let us know.
I’m Jane books (https://www.jianshu.com/u/4fd01692f6d9) also wrote many articles, you can scan qr code to access.