Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What are the reasons why single-threaded Redis supports 10w + QPS?

2025-04-10 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article focuses on "what are the reasons why single-threaded Redis supports 10w + QPS", interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Let the editor take you to learn "what are the reasons why single-threaded Redis supports 10w + QPS?"

Why can single thread support 10w + QPS?

We often hear that Redis is a single threaded program. To be exact, Redis is a multithreaded program, except that the part of the request processing is implemented by a single thread.

Aliyun's test results for Redis QPS are as follows

"how does Redis implement 10w + per second QPS with a single thread?"

Using IO Multiplexing

Non-CPU intensive task

Pure memory operation

Efficient data structure

"how can multiple client connections be handled with only one thread?"

Therefore, we have to mention IO multiplexing technology, that is, NIO in Java.

When we use blocking IO (BIO in Java), we call the read function, passing in the parameter n, which means that the thread will not return until n bytes have been read, otherwise it will keep blocking. The write method generally does not block, and write is not blocked unless the write buffer is full until space in the buffer is freed.

When we use IO multiplexing technology, when there is no data to read or write, the client thread will return directly and will not block. This allows Redis to use a single thread to listen to multiple Socket. When a Socket is readable or writable, the Redis reads the request, manipulates the data in memory, and then returns.

Multicore CPU cannot be used when using single threading, but most of the commands in Redis are not CPU-intensive tasks, so CPU is not the bottleneck of Redis.

The bottleneck of Redis with high concurrency and large amount of data is mainly reflected in memory and network bandwidth, so you can see that in order to save memory, Redis takes up less memory on the underlying data structure, and a type of data will use different data structures in different scenarios.

So Redis can already handle a large number of requests with single thread, so there is no need to use multithreading. In addition, "using single threading has the following benefits"

Without the performance overhead of thread switching

Various operations do not need to be locked (if multithreading is used, access to shared resources needs to be locked, which increases overhead)

Easy to debug and high maintainability

Finally, Redis is an in-memory database, and the read and write operations of various commands are based on memory. Everyone knows that there is a difference of several orders of magnitude between operating memory and operating disk efficiency. Although Redis is very efficient, there are still some slow operations to avoid.

What are the slow operations of Redis?

The various commands of Redis are executed in one thread in turn. If a command is executed in Redis for too long, it will affect the overall performance, because the subsequent requests will not be processed until the previous requests are processed. These time-consuming operations have the following parts

Redis can log those time-consuming commands using the following configuration

# it takes more than 5 milliseconds to execute the command, and record slow logs CONFIG SET slowlog-log-slower-than 5000 # keep only the last 500slow logs CONFIG SET slowlog-max-len 500s

Execute the following command to query the recently recorded slow log

127.0.1 SLOWLOG get 6379 > integer 5 1) 1) (integer) 32693 # slow log ID 2) (integer) 1593763337 # execution timestamp 3) (integer) 5299 # execution time (microseconds) 4) 1) "LRANGE" # commands and parameters executed specifically 2) "user_list:2000" 3) "0" 4) "- 1" 2) 1) (integer) 32692 2) (integer) 1593763337 3) (integer) 5044 4) 1) "GET" 2) "user_info:1000".

Use commands that are too complex

In the previous article, we have introduced the underlying data structures of Redis, and their time complexity is shown in the following table

Name time complexity dict (dictionary) O (1) ziplist (compressed list) O (n) zskiplist (jump table) O (logN) quicklist (quick list) O (n) intset (set of integers) O (n)

"single element operation": the operation of adding, deleting, changing and querying the elements in the collection is related to the underlying data structure. For example, the time complexity of adding, deleting, modifying and querying the dictionary is O (1), and the time of adding, deleting and querying the jump table is O (logN).

"scope operation": traverses the collection, such as Hash type HGETALL,Set type SMEMBERS,List type LRANGE,ZSet type ZRANGE, time complexity is O (n), avoid use, use SCAN series commands instead. (hash with hscan,set, with sscan,zset with zscan)

"aggregation operations": the time complexity of such operations is usually greater than O (n), such as SORT, SUNION, ZUNIONSTORE

"Statistical operation": when you want to get the number of elements in a collection, such as LLEN or SCARD, the time complexity is O (1), because their underlying data structures such as quicklist,dict,intset hold the number of elements.

"Boundary operation": the bottom layer of list is implemented with quicklist, and quicklist saves the head and tail nodes of the linked list, so the time complexity of operating the head and tail nodes of the linked list is O (1), such as LPOP, RPOP, LPUSH, RPUSH.

"when you want to get the key in Redis, avoid using keys *". The key-value pairs saved in Redis are stored in a dictionary (similar to HashMap in Java, it is also realized by array + linked list). The type of key is string,value, and the type can be string,set,list, etc.

For example, when we execute the following command, the dictionary structure of redis is as follows

Set bookName redis; rpush fruits banana apple

We can use the keys command to query a specific key in Redis, as shown below

# query all key keys * # query key keys book* prefixed with book

The complexity of the keys command is O (n), and it traverses all the key in the dict. If there is a lot of key in the Redis, all instructions to read and write Redis will be delayed, so don't use this command in the production environment (have fun if you're ready to leave).

"since you are not allowed to use keys, there must be a substitute, and that is scan."

Compared with keys, scan has the following characteristics

Although complexity is also O (n), it is executed through cursor distribution and does not block threads.

Like keys, it provides pattern matching.

From the beginning of the full traversal to the end of the full traversal, all elements that have existed in the dataset will be fully traversed, but the same element may be returned multiple times.

If an element is added to the dataset or removed from the dataset during iteration, the element may or may not be returned

If the return result is empty, it does not mean that the traversal is over, but depends on whether the cursor value returned is 0.

Interested partners can understand these features by analyzing the implementation of the scan source code.

"the principle of traversing zset,hscan with zscan traversing hash,sscan traversing set is similar to the scan command, because dict is in the data structure of the underlying implementation of hash,set,zset. "

Manipulate bigkey

"if a key corresponds to a very large value, then the key is called bigkey. Writing to bigkey takes longer to allocate memory. Similarly, it takes longer to delete bigkey to free memory. "

If you find a low-complexity command like SET/DEL in the slow log, you should check whether it is caused by writing to bigkey.

"how to locate bigkey?"

Redis provides commands to scan bigkey

$redis-cli-h 127.0.0.1-p 6379-- bigkeys-I 0.01...-summary-Sampled 829675 keys in the keyspace! Total key length in bytes is 10059825 (avg len 10059825) Biggest string found 'key:291880' has 10 bytes Biggest list found' mylist:004' has 40 items Biggest set found 'myset:2386' has 38 members Biggest hash found' myhash:3574' has 37 fields Biggest zset found 'myzset:2704' has 42 members 36313 strings with 363130 bytes (04.38% of keys, avg size 10.00) 787393 lists with 896540 items (94.90% of keys, avg size 1.14) 1994 sets with 40052 members (00.24% of keys Avg size 20.09) 1990 hashs with 39632 fields (00.24% of keys, avg size 19.92) 1985 zsets with 39750 members (00.24% of keys, avg size 20.03)

You can see that the input of the command has the following three parts

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Number of key in memory, total memory already occupied, average memory occupied per key

The maximum memory consumed by each type, already the name of the key

Percentage of each data type, as well as average size

The principle of this command is that redis executes the scan command internally, traverses all the key in the instance, and then executes the strlen,llen,hlen,scard,zcard command against the key type to get the length of the string type and the number of elements of the container type (list,hash,set,zset).

You need to pay attention to the following two problems when using this command

To avoid a sudden increase in ops (operation per second operations per second) when bigkey scanning online instances, you can add a hibernation parameter through-I, which means that every 100th scan instruction will hibernate for 0.01s.

For the container type (list,hash,set,zset), the key with the largest number of elements is scanned, but the large number of elements in a key does not necessarily mean that it takes up too much memory.

"how to solve the performance problems caused by bigkey?"

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Avoid writing bigkey as much as possible

If you are using a version of redis4.0 or above, you can use the unlink command instead of del. This command can release the key memory and execute it in the background thread.

If you are using a version of redis6.0 or above, you can enable the lazy-free mechanism (lazyfree-lazy-user-del yes). When you execute the del command, it will also be placed in the background thread to execute it.

A large number of key centrally expired

We can set the expiration time for the key in Redis, so when will the key be deleted when it expires?

"if we were to write the Redis expiration policy, we would think of the following three options."

Delete regularly and create a timer while setting the expiration time of the key. When the expiration time of the key comes, delete the key immediately

Lazy deletion, determine whether the key expires each time you get the key, delete the key if it expires, and return the key if it does not expire

Delete the key periodically and check the key at regular intervals. Deleting the expired key in it is not friendly to CPU. When there are more expired keys, the Redis thread is used to delete the expired key, which will affect the response of the normal request.

The scheduled deletion policy is not friendly to CPU. When there are many expired keys, Redis threads are used to delete expired keys, which will affect the response of normal requests.

Lazy deletion to read CPU is good, but it wastes a lot of memory. If an key setting expires in memory but is not accessed, it will remain in memory all the time

The periodic deletion policy is friendly to both CPU and memory.

The following two deletion strategies are selected for redis expired key

Lazy deletion

Delete periodically

When the "lazy delete" client accesses key, it verifies the expiration time of key, and deletes it immediately if it expires.

"periodically delete" Redis puts the key with the expiration time set in a separate dictionary, and periodically traverses the dictionary to delete the expired key. The traversal policy is as follows

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Perform 10 expiration scans per second and randomly select 20 key from expired dictionaries each time

Delete the expired key from 20 key

If the proportion of expired key is more than 1amp 4, proceed to step 1

The upper limit of each scan time does not exceed 25ms by default to avoid thread sticking.

Because the expired key in the Redis is deleted by the main thread, the expired key is deleted a small number of times in order not to block the user's request. The source code can refer to the activeExpireCycle method in expire.c.

To prevent the main thread from deleting the key all the time, we can use the following two scenarios

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Add a random number to the key that expires at the same time, break up the expiration time, and reduce the pressure to remove key.

If you are using redis with redis4.0 version or above, you can turn on the lazy-free mechanism (lazyfree-lazy-expire yes). When you delete an expired key, the operation of freeing memory will be executed in the background thread.

The memory reaches the upper limit, triggering the elimination strategy

Picture Redis is an in-memory database. When the memory used by Redis exceeds the limit of physical memory, memory data will be frequently exchanged with disk, which will lead to a sharp decline in Redis performance. So in a production environment, we limit the amount of memory used by configuring the parameter maxmemoey.

When the actual memory used exceeds maxmemoey, Redis provides the following optional strategies.

Noeviction: write request returned error

Volatile-lru: use lru algorithm to delete keys with expiration time set volatile-lfu: use lfu algorithm to delete keys with expiration time set volatile-random: delete randomly in key-value pairs with expiration time set volatile-ttl: delete according to the order of expiration time, the earlier the expiration, the first to be deleted

Allkeys-lru: delete using lru algorithm in all key-value pairs allkeys-lfu: delete using lfu algorithm in all key-value pairs allkeys-random: random deletion in all key-value pairs

"Redis's elimination strategy is also executed in the main thread. However, when the memory exceeds the Redis limit, some key needs to be eliminated for each write, resulting in a longer request time. "

Improvements can be made in the following ways

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Increase memory or put data into multiple instances

The elimination strategy is changed to random elimination, which is generally much faster than lru.

Avoid storing bigkey and reduce the time it takes to free memory

The way to write AOF log is always

The persistence mechanisms of Redis include RDB snapshots and AOF logs. After each command is written, Redis provides the following three flushing mechanisms

Always: write back synchronously, write commands are executed synchronously to disk everysec: write back every second, each write command is finished, but first write the log to the memory buffer of the aof file, and write the contents of the buffer to the disk every 1 second: the operating system controls the write back, and each write command is executed, just write the log to the memory buffer of the aof file first, and it is up to the operating system to decide when to write the contents of the buffer back to disk.

When the flushing mechanism of aof is that every time always,redis processes a write command, it brushes the write command to disk before returning. The whole process is carried out in the main thread of Redis, which is bound to slow down the performance of redis.

When aof's flushing mechanism is everysec,redis, it returns after writing memory, and the flushing operation is carried out in the background thread, which brushes the data in memory to disk every 1 second.

When the disk flushing mechanism of aof is no, some data may be lost after downtime, which is generally not used.

"in general, the aof flushing mechanism can be configured as everysec."

Fork takes too long

In the persistence section, we have mentioned that "Redis generates rdb files and aof log rewrites, both of which are executed by the child process by the main thread fork. The larger the memory of the main thread, the longer the blocking time. "

It can be optimized in the following ways

Control the memory size of Redis instances to within 10g as much as possible, because the larger the memory, the longer the blocking time.

Configure reasonable persistence policies, such as generating rdb snapshots on slave nodes

At this point, I believe you have a deeper understanding of "what are the reasons why single-threaded Redis supports 10w + QPS?" you might as well do it in practice. Here is the website, more related content can enter the relevant channels to inquire, follow us, continue to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report