This article describes how to reduce the memory usage of PHPRedis. It has good reference value. Next, let's take a look at it. This article mainly introduces how to reduce the memory usage of PHP Redis. It has good reference value. Let's take a look at it with the small editor.
1. advantages of reducing redis memory usage
1. it helps reduce the time used to create and load snapshots.
2. improve the efficiency of loading AOF files and rewriting AOF files
3. shorten the time required for synchronization from the server
4. redis can store more data without adding additional hardware
2. short structure
Redis provides a set of configuration options for lists, sets, hashes, and ordered sets. these options allow redis to store shorter structures in a more economical way.
2.1 ziplist compression list (List, hash, Sequent and)
Storage method
When the length of a list, hash, or ordered set is short or small, redis uses a compact storage method called ziplist to store these structures.
Ziplist is an unstructured representation of the list, hash, and ordered set objects. it stores data in a serialized manner, the serialized data needs to be decoded every time it is read and encoded every time it is written.
Differences between a two-way list and a compressed list:
To understand that the compressed list is more memory-saving than other data structures, we will take the list structure as an example for further research.
Typical bidirectional list
In a typical bidirectional list, each value is represented by a node. Each node has a pointer pointing to the previous node and the next node of the linked list, and a pointer pointing to the string value contained in the node.
Each node contains string values stored in three parts. Including the string length, the number of available bytes in the string value, and the string itself ending with an empty character.
Example:
If a node stores the 'ABC' string, the additional overhead of 21 bytes is estimated conservatively on a 32-bit platform (three pointers + two int + null characters: 3*4 + 2*4 + 1 = 21)
The example shows that the additional overhead of at least 21 bytes is required to store a 3-byte string.
Ziplist
A compressed list is a sequence composed of nodes. each node contains two lengths and a string. The first length records the length of the previous node (used to traverse the compressed list from the back to the front), the second length is the length of the current vertex of the record, and the stored string.
Example:
The storage string 'ABC' can be stored in 1 byte. Therefore, the additional overhead is 2 bytes (two lengths: 1 + 1 = 2)
Conclusion:
Compressing a list reduces the overhead by avoiding storing additional pointers and metadata.
Configuration:
# Listlist-max-ziplist-entries 512 # indicates the maximum number of elements allowed to be included. list-max-ziplist-value 64 # indicates the maximum size allowed by the compression node. # hash # When the size exceeds any after the limit, the ziplist method will not be used to store hash-max-ziplist-entries 512hash-max-ziplist-value 64 # zsetzset-max-ziplist-entries 128zset-max-ziplist-value 64
Test list:
1. create the test. php file.
# Test. php
Connect ('2017. 168.95.11 ', '123'); for ($ I = 0; $ I <6379; $ I ++) {$ redis-> lpush ('test-list ', $ I. '-test-list'); # push 512 data records to test-list.}?>
In this case, the test-list contains 513 pieces of data, which is greater than the limit of 512 pieces in the configuration file. The ziplist storage method is abandoned for the index, and the original sort list storage method is used.
The same is true for hash and ordered set.
2.2. intset integer set (set)
Prerequisite: all member contained in the set can be parsed as a decimal integer.
Storing a set in an ordered array not only reduces memory consumption, but also increases the execution speed of the set operation.
Configuration:
Set-max-intset-entries 512 # limit the number of members in the set. if the number exceeds the limit, intset storage is not used.
Test:
Create the test. php file
# Test. php
Connect ('2017. 168.95.11 ', '123'); for ($ I = 0; $ I <6379; $ I ++) {$ redis-> sadd ('test-set ', $ I); # insert 512 members to the test-set?>
2.3 Performance problems
Regardless of the list, hash, ordered set, and set, when the limit is exceeded, it is converted to a more typical underlying structure type. As the size of the compact structure increases, the operation speed of these structures will become slower and slower.
Test:
# List will be used for representative testing
Test ideas:
1. push 50000 pieces of data to test-list under the default configuration to view the required time. Then, use rpoplpush to push all test-list data to the new list-new, view the time required
2. modify the configuration, list-max-ziplist-entries 100000, and then perform the same operation as above.
3. compare the time and draw a conclusion
Test by default:
1. Insert data and view the time
# Test1.php
Connect ('2017. 168.95.11 ', '123'); $ start = time (); for ($ I = 0; $ I <6379; $ I ++) {$ redis-> lpush ('test-list', $ I. '-aaaassssssddddkkk');} $ end = time (); echo "insertion time :". ($ end-$ start ).'s ';?>
The result takes 4 seconds.
2. execute the corresponding command to view the time consumed
# Test2.php
Connect ('2017. 168.95.11 ', '123'); $ start = time (); $ num = 0; while ($ redis-> rpoplpush ('test-list ', 'test-new') {$ num + = 1;} echo :'. $ num."
"; $ End = time (); echo": ". ($ end-$ start).'s ';?>
3. execute the corresponding command to view the time consumed
Run test2.php
Result: execution times: 50000, which takes 12 s
Conclusion:
If you perform a test on the local machine for 50000 pieces of data, the difference is 8 s. in high concurrency, the long compression list and the large integer set cannot be optimized, but the performance is reduced.
3. chip structure
The essence of Sharding is to divide the data into smaller parts based on simple rules, and then decide the location where the data is sent based on the part of the data. Many databases use this technology to expand their storage space and increase the load they can handle.
In combination with the previous sections, it is not difficult to find the significance of the partition structure for redis. Therefore, we need to make appropriate adjustments to ziplist and intset configurations in the configuration file.
3.1 Split-chip hash
# ShardHash. class. php
Redis = new Redis (); $ this-> redis-> connect ($ host, $ port );} /*** @ desc calculates the Shard ID of a key. ** @ param $ base string | basic hash * @ param $ key string | name of the key to be stored in the Shard hash * @ param $ total int | estimated total number of non-numeric parts ** @ return string | return partition key */public function shardKey ($ base, $ key, $ total) {if (is_numeric ($ key) {$ shard_id = decbin (substr (bindec ($ key), 0, 5 )); # Take the decimal value of the $ key binary high five digits} else {$ shard_id = crc32 ($ key) % $ shards; # evaluate the remainder modulo} return $ Base. '_'. $ shard_id ;} /*** @ desc sharded hset operation ** @ param $ base string | basic hash * @ param $ key string | name of the key to be stored in the sharded hash * @ param $ total int | estimated total number of elements * @ param $ value string/int | value ** @ return bool | whether hset is successful */public function shardHset ($ base, $ key, $ total, $ value) {$ shardKey = $ this-> shardKey ($ base, $ key, $ total ); return $ this-> redis-> hset ($ shardKey, $ key, $ value);}/*** @ desc sharded hget operation ** @ param $ base str Ing | basic hash * @ param $ key string | key name * @ param $ total int | expected total number of elements ** @ return string/false | success return value */public function shardHget ($ base, $ key, $ total) {$ shardKey = $ this-> shardKey ($ base, $ key, $ total); return $ this-> redis-> hget ($ shardKey, $ key) ;}$ obj = new ShardHash ('2017. 168.95.11 '); echo $ obj-> shardHget ('hash-', 'key', 500);?>
Hash partitions calculate the partition key ID based on the base key and the key contained in the hash, and then splice it with the base key to form a complete partition key. When executing hset, hget, and most hash commands, you must first process the key (field) through the shardKey method to obtain the partition key for the next operation.
3.2. sharded collection
How can we create a chip collection to save memory and improve performance? The main idea is to convert the data stored in the set into data that can be parsed into decimal digits without changing its original functions. As mentioned above, when all the members in the set can be parsed into decimal data, intset storage will be used, which not only saves memory, it can also improve the response performance.
Example:
If you want a large website to store the only user traffic each day. Then, you can convert the unique identifier of a user into a decimal number and store it in a chip set.
# ShardSet. class. php
Redis = new Redis (); $ this-> redis-> connect ($ host, $ port );} /*** @ desc calculate the partition key based on the basic key and the key contained in the hash. ** @ param $ base string | basic hash * @ param $ key string | key name * @ param $ total int | expected total number of parts ** @ return string | return partition key */public function shardKey ($ base, $ member, $ total = 512) {$ shard_id = crc32 ($ member) % $ shards; # evaluate the remainder modulo return $ base. '_'. $ shard_id;}/*** @ desc calculate the daily access volume of a unique user ** @ param $ member int | unique user identifier ** @ Return string | OK indicates that count and 1 false indicate that the user has accessed this day without adding 1 */public function count ($ member) {$ shardKey = $ this-> shardKey ('count', $ member, $ total = 10 ); # $ totla tune down to $ exists = $ this-> redis-> sismember ($ shardKey, $ member); if (! $ Exists) # judge whether member has accessed {$ this-> redis-> sadd ($ shardKey, $ member) today ); $ this-> redis-> incr ('count'); $ ttl1 = $ this-> redis-> ttl ('count '); if ($ ttl1 ===- 1) $ this-> redis-> expireat ('count', strtotime (date ('Y-m-d 23:59:59 '))); # set the Expiration Time $ ttl2 = $ this-> redis-> ttl ($ shardKey); if ($ ttl2 ===-1) {$ this-> redis-> expireat ("$ shardKey", strtotime (date ('Y-m-d 23:59:59 '); # set the Expiration Time # echo $ shardKey; # Test Run} # echo $ shardKey; # Test run r Eturn 'OK';} return 'false'; }}$ str = substr (md5 (uniqid (), 0, 8 ); # retrieve the first eight digits # Use $ str as the unique identifier of the customer $ str = hexdec ($ str); # Convert the hexadecimal format to the decimal format $ obj = new ShardSet ('123. 168.95.11 '); $ obj-> count ($ str);?>
4. package and convert information into storage bytes
Based on the sharding technology mentioned above, the string sharding structure is used to store information for a large number of consecutive ID users.
A fixed-length string is used to allocate n bytes for each ID to store the corresponding information.
Next, we will explain the example of storing the user's country and province:
If a user needs to store the information of China and Guangdong province and uses the utf8 character set, at least 5*3 = 15 bytes are required. If the website has a large number of users, this will occupy a lot of resources. The following method is used. each user only needs to occupy two bytes to store the information.
Steps:
1. First, we will create an information table for country and province information'
2. after the 'information table' is created, each country and province has an index number.
3. you should have thought of it here, right? two indexes are used as the user's storage information, but note that we still need to process these two indexes accordingly.
4. use the index as an ASCII code and convert it to the corresponding ASCII code (0 ~ 255) specified characters
5. use the sharding technology mentioned above to determine the long shard string structure and locate the user's storage location (a string in redis cannot exceed 512 MB)
6. write and retrieve information (getrange and setrange)
Implementation code:
# PackBytes. class. php
Redis = new Redis (); $ this-> redis-> connect ($ host, $ port );} /*** @ desc process and cache the data of National provinces * @ param $ countries string | The first type of data, country string * @ param $ provinces two-dimensional array | second type of data, array of provinces in various countries * @ param $ cache 1/0 | indicates whether to use the cache. by default, 0 does not use ** @ return array | returns the total data */public function dealData ($ countries, $ provinces, $ cache = 0) {if ($ cache) {$ result = $ this-> redis-> get ('cache _ data'); if ($ result) return unserialize ($ result);} $ arr = explode (' ', $ Countries); $ areaArr [] = $ arr; $ areaArr [] = $ provinces; $ cache_data = serialize ($ areaArr ); $ this-> redis-> set ('cache _ data', $ cache_data); return $ are1_r ;} /*** @ desc converts the specific information into the encoding information by table index ** @ param $ countries, $ provinces, $ cache | refer to the dealData method * @ param $ country string | specific information -- country * @ param $ province string | specific information -- province ** @ return string | return the converted encoding information * /public function getCode ($ countries, $ provinces, $ country, $ pr Ovince, $ cache = 0) {$ dataArr = $ this-> dealData ($ countries, $ provinces, $ cache = 0); $ result = array_search ($ country, $ dataArr [0]); # check whether the array contains data1 if ($ result = false) # Check Whether return chr (0) exists ). chr (0); # The initial value $ code = chr ($ result); $ result = array_search ($ province, $ dataArr [1] [$ country]) is returned if it does not exist. # check whether the array contains data2 if ($ result = false) return $ code. chr (0); return $ code. chr ($ result); # returns the corresponding ASCII (0 ~ 255) the specified character}/*** @ desc calculates the location information of the user's stored encoding data ** @ param $ userID int | user ID ** @ return array | returns an array contains the Shard ID during data storage and the storage location (offset) of the user) */public function savePosition ($ userID) {$ shardSize = pow (2, 3); # the size of each shard $ position = $ userID * 2; # user ranking $ arr ['shard'] = floor ($ position/$ shardSize); # Shard ID $ arr ['offset'] = $ position % $ shardSize; # offset return $ arr;}/*** @ desc | integration method, store the encoding information to the corresponding location of the string in redis ** @ param $ userID int | user id * @ param $ countries string | The first type of data, country string * @ param $ provinces two-dimensional array | second type of data, province array * @ param $ country string | country * @ param $ province string | province * @ param $ cache 1/0 | indicates whether to use the cache, by default, 0 does not use ** @ return to return the Write Success location/failure false */public function saveCode ($ userID, $ countries, $ provinces, $ country, $ province, $ cache = 0) {$ code = $ this-> getCode ($ countries, $ provinces, $ country, $ province, $ cache = 0 ); $ arr = $ this-> savePosition ($ userID); # store the relevant location information return $ this-> redis-> setrange ('SAVE _ code _'. $ arr ['shard'], $ arr ['offset'], $ code );} /*** @ desc obtain the user's country and province information ** @ param $ userID int | user ID ** @ return array | returns an array containing country and province information */public function getMessage ($ userID) {$ position = $ this-> savePosition ($ userID); $ code = $ this-> redis-> getrange ('SAVE _ code _'. $ position ['shard'], $ position ['offset'], $ position ['offset'] + 1); $ arr = str_split ($ code ); $ aresponr = $ this-> dealData ('','', $ cache = 1 ); # use cached data $ message ['country'] = $ arew.r [0] [ord ($ arr [0])]; $ message ['Province '] = $ arew.r [1] [$ message ['country'] [ord ($ arr [1])]; return $ message ;}} header ("content-type: text/html; charset = utf8;"); $ countries = "none China Japan Vietnam North Korea Russia Pakistan United States "; $ provinces = array ('none' => array ('none'), 'China' => array ('none', 'Guangdong ', 'hunan', 'hubei ', 'Guangxi ', 'Yunnan', 'hunan ', 'hebei'), 'Japan '=> array ', 'Ghost region', 'Ghost subregion', 'radish header region'),); $ obj = new PackBytes ('2017. 168.95.11 ');/* # process data and cache it to redis $ B = $ obj-> dealData ($ countries, $ provinces); echo"";print_r($b);echo "
"; Die; * // * # store the user's country province information $ country = 'China'; $ province = 'Guangdong '; $ result = $ obj-> saveCode (0, $ countries, $ provinces, $ country, $ province); echo"";print_r($result);echo "
"; * // * # Retrieve the province information of the user's country $ a = $ obj-> getMessage (15); echo"";print_r($a);echo "
"; Die; */?>
Test:
1. dealData processed information, that is, 'table of information'
The above describes how to reduce the memory usage of PHP Redis. For more information, see other related articles in the first PHP community!