Redis Core: The Secret of Only Fast and Unbreakable

The world of martial arts, all invincible, only quick and unbreakable!

Learning a technology usually only comes into contact with scattered technical points, without establishing a complete knowledge framework and architecture system in mind, and without a system view. This will be very laborious, and it will appear as if you will, and then forget it later, with a dumbfounded look.

Learn Redis with "Code Gebyte", and master Redis core principles and practical skills in depth. Build a complete knowledge framework together, learn the overall view to organize the entire knowledge system.

The system view is actually very important. To a certain extent, when solving problems, having a system view means that you can locate and solve problems with a basis and structure.

Redis panorama

The panorama can be expanded around two latitudes, which are:

Application latitude: cache use, cluster use, clever use of data structure

System latitude: can be classified as three high

  1. High performance: thread model, network IO model, data structure, persistence mechanism;
  2. High availability: master-slave replication, sentinel cluster, Cluster sharded cluster;
  3. High expansion: load balancing

The Redis series of chapters are developed around the following mind map. This time , we will explore the core knowledge of Redis from "The Secret of Redis Only Fast".

Eat Redis

The only secret

65 Brother went to interview 996 Dachang some time ago and was asked "Why is Redis fast?"

65 Brother: Uh, because it is based on memory implementation and single-threaded model

Interviewer: Anything else?

65 Brother: Nothing.

Many people just know that based on the memory implementation, other core reasons are ambiguous. Today, we will explore the reasons for being really fast with "Megabytes", and be a real man who can only be fast and not broken!

Redis has been optimized from all aspects for high performance. The interviewer asked Redis why the performance is so high next time when the friends interviewed. You can't just talk about single thread and memory storage.

The only secret

According to official data, Redis's QPS can reach about 100,000 (requests per second). Those who are interested can refer to the official benchmark test "How fast is Redis?" ", address: https://redis.io/topics/benchmarks

Benchmarks

The horizontal axis is the number of connections, and the vertical axis is QPS. At this time, this picture reflects an order of magnitude. I hope everyone can describe it correctly during the interview. When you don't ask you, the order of magnitude of your answers is very different!

Completely based on memory

65 Brother: I know that Redis is a memory-based database. Compared with a disk database, the speed of completely hitting the disk is like Duan Yu's Lingbo microstep. For a disk database, the data must first be read into memory through IO operations.

That's right, regardless of the read and write operations are done on the memory, let's compare the differences between memory operations and disk operations.

Disk call stack diagram

Redis Core: The Secret of Only Fast and Unbreakable

Memory operation

The memory is directly controlled by the CPU, that is, the memory controller integrated inside the CPU, so the memory is directly connected to the CPU to enjoy the optimal bandwidth for communication with the CPU.

Redis stores data in memory, and read and write operations are not limited by the IO speed of the disk, so the speed is so fast!

Finally, a picture is used to quantify the various delay times of the system (part of the data quoted Brendan Gregg)

Redis Core: The Secret of Only Fast and Unbreakable

Efficient data structure

65 Brother: When I was learning MySQL, I knew that the B+ Tree data structure was used in order to improve the retrieval speed, so the speed of Redis should also be related to the data structure.

The answer is correct, the data structure mentioned here is not the five data types provided to us by Redis: String, List, Hash, Set, SortedSet.

In Redis, the five commonly used data types and application scenarios are as follows:

  • String: Cache, counter, distributed lock, etc.
  • List: linked list, queue, Weibo follower timeline list, etc.
  • Hash: User information, Hash table, etc.
  • Set: De-duplicate, like, dislike, mutual friends, etc.
  • Zset: Ranking of visits, ranking of clicks, etc.

The above should be called the data type supported by Redis, that is, the data storage format. What "Code Ge byte" wants to talk about is for these 5 data types, which efficient data structures are used at the bottom to support them.

65 Brother: Why do so many data structures?

Of course, in pursuit of speed, different data types use different data structures to speed up. Each data type has one or more data structures to support, and there are 6 underlying data structures.

Redis Core: The Secret of Only Fast and Unbreakable

Redis hash dictionary

Redis as a whole is a hash table to store all key-value pairs, no matter the data type is any of the five. A hash table is essentially an array, and each element is called a hash bucket. No matter what data type, the entry in each bucket holds a pointer to the actual value.

Redis global hash table

The entire database is a global hash table , and the time complexity of the hash table is O(1). You only need to calculate the hash value of each key to know the location of the corresponding hash bucket, and locate the entry in the bucket to find the corresponding Data, this is also one of the reasons Redis is fast.

What about Hash conflicts?

When more and more data is written to Redis, hash collisions are inevitable, and different keys will calculate the same hash value.

Redis resolves conflicts through chained hashing : that is, the elements in the same bucket are stored in a linked list . But when the linked list is too long, the search performance may deteriorate, so Redis uses two global hash tables in order to pursue speed. Used for rehash operations to increase the number of existing hash buckets and reduce hash conflicts.

Initially, hash table 1 is used to store key-value pair data by default, and hash table 2 has no space allocated at the moment. When more and more data triggers the rehash operation, perform the following operations:

  1. Allocate more space to hash table 2;
  2. Remap and copy the data of hash table 1 to hash table 2;
  3. Free up the space of hash table 1.

It is worth noting that the process of remapping hash table 1 data to hash table 2 is not a one-off process, which will cause Redis to block and fail to provide services.

Instead, a progressive rehash is used . Each time a client request is processed, start with the first index in hash table 1 and copy all data at this position to hash table 2, so that the rehash is dispersed multiple times Avoid time-consuming blocking during the request process.

SDS simple dynamic character

65 Brother: Redis is implemented in C language, why do you re-create a SDS dynamic string?

The string structure is the most widely used, usually we use to cache the user information after login, key = userId, value = user information JSON serialized into a string.

To obtain the length of the "MageByte" of the string in C language, it must be traversed from the beginning until "\0". Redis, as a man who can only be fast and unbreakable, cannot bear it.

The comparison chart of C language string structure and SDS string structure is as follows:

C language string and SDS

The difference between SDS and C string

O(1) time complexity to get the string length

C language string Bougainvillea length information, the time complexity of traversing the entire string is O(n), and the C string traversal ends when it encounters'\0'.

In SDS, len saves the length of this string, O(1) time complexity.

Space preallocation

After the SDS is modified, the program will not only allocate the necessary space for the SDS, but also allocate additional unused space.

The allocation rules are as follows: if the length of len is less than 1M after SDS is modified, the program will allocate unused space of the same length as len. For example, if len=10, after reallocation, the actual length of buf will become 10 (used space) + 10 (extra space) + 1 (empty character) = 21. If the length of len is greater than 1M after SDS is modified, the program will allocate 1M of unused space.

Inert space release

When the SDS is shortened, the program does not reclaim the excess memory space, but uses the free field to record the number of bytes without releasing it. If an append operation is required later, the unused space in free is used directly to reduce The allocation of memory.

Binary security

In Redis, you can store not only String type data, but also some binary data.

Binary data is not a regular string format. It will contain some special characters such as'\0'. Encountered'\0' in C indicates the end of the string, but in SDS, the end of the string is marked len attribute.

zipList compressed list

The compressed list is one of the underlying implementations of the three data types List, hash, and sorted Set.

When a list has only a small amount of data, and each list item is either a small integer value or a string with a relatively short length, then Redis will use a compressed list as the underlying implementation of the list key.

The ziplist is a sequential data structure composed of a series of specially coded contiguous memory blocks. The ziplist can contain multiple entry nodes, and each node can store integers or strings.

The ziplist header has three fields zlbytes, zltail and zllen, which respectively indicate the number of bytes occupied by the list, the offset at the end of the list, and the number of entries in the list; the compressed list also has a zlend at the end of the list, which indicates the end of the list.

struct ziplist<T> {
    int32 zlbytes; // 整个压缩列表占用字节数
    int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
    int16 zllength; // 元素个数
    T[] entries; // 元素内容列表,挨个挨个紧凑存储
    int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}

ziplist

If we want to find and locate the first element and the last element, we can directly locate it by the length of the three fields in the header, and the complexity is O(1). When searching for other elements, it is not so efficient. You can only search one by one. The complexity at this time is O(N)

Double-ended list

The Redis List data type is usually used in scenarios such as queues and Weibo followers timeline lists. Whether it is a first-in first-out queue or a first-in-last-out stack, the double-ended list supports these features very well.

Redis Core: The Secret of Only Fast and Unbreakable

The characteristics of Redis's linked list implementation can be summarized as follows:

  • Double-ended: The linked list node has prev and next pointers, and the complexity of obtaining the pre-node and post-node of a node is O(1).
  • Acyclic: The prev pointer of the head node and the next pointer of the tail node both point to NULL, and the access to the linked list ends with NULL.
  • With head pointer and tail pointer: through the head pointer and tail pointer of the list structure, the complexity of the program to obtain the head node and the tail node of the linked list is O(1).
  • Linked list length counter: The program uses the len attribute of the list structure to count the list nodes held by the list. The complexity of the program to obtain the number of nodes in the list is O(1).
  • Polymorphism: Linked list nodes use void* pointers to store node values, and type-specific functions can be set for node values ​​through the three attributes of dup, free, and match in the list structure, so linked lists can be used to store various types of values.

Subsequent versions modified the data structure of the list, using quicklist instead of ziplist and linkedlist.

Quicklist is a mixture of ziplist and linkedlist. It divides linkedlist into segments. Each segment uses ziplist for compact storage, and multiple ziplists are connected in series using two-way pointers.

Redis Core: The Secret of Only Fast and Unbreakable

This is also the reason why Redis is fast, not letting go of any details that can improve performance.

skipList skip list

The sorting function of the sorted set type is realized through the "jump list" data structure.

Skiplist is an ordered data structure, which achieves the purpose of quickly accessing nodes by maintaining multiple pointers to other nodes in each node.

The hop table supports node lookup with average O(logN) and worst O(N) complexity, and can also process nodes in batches through sequential operations.

On the basis of the linked list, the jump list adds a multi-level index. Through several jumps of the index position, rapid data positioning is realized, as shown in the following figure:

Skip list

When you need to find 40, this element needs to go through three searches.

Integer array (intset)

When a collection contains only integer-valued elements and the number of elements in this collection is small, Redis will use the integer collection as the underlying implementation of the collection key. The structure is as follows:

typedef struct intset{
     //编码方式
     uint32_t encoding;
     //集合包含的元素数量
     uint32_t length;
     //保存元素的数组
     int8_t contents[];
}intset;

The contents array is the underlying implementation of the integer collection: each element of the integer collection is an array item (item) of the contents array, and each item is arranged in an orderly manner in the array according to the size of the value, and the array does not contain any Duplicate items. The length property records the number of elements contained in the integer set, which is the length of the contents array.

Reasonable data encoding

Redis uses objects (redisObject) to represent the key values ​​in the database. When we create a key-value pair in Redis, at least two objects are created. One object is a key object used as a key-value pair, and the other is a key-value pair. The value object.

For example, when we execute SET MSG XXX, the key of the key-value pair is an object containing the string "MSG", and the value object of the key-value pair is an object containing the string "XXX".

redisObject

typedef struct redisObject{
    //类型
   unsigned type:4;
   //编码
   unsigned encoding:4;
   //指向底层数据结构的指针
   void *ptr;
    //...
 }robj;

The type field records the type of the object, including string objects, list objects, hash objects, collection objects, and ordered collection objects.

For each data type, the underlying support may be a variety of data structures, when to use which data structure, which involves the problem of encoding conversion.

Then let's take a look at how different data types are encoded and transformed:

String : if storing numbers, use int type encoding, if it is not a number, use raw encoding;

List : The encoding of the List object can be ziplist or linkedlist, string length <64 bytes and number of elements <512 use ziplist encoding, otherwise it will be converted to linkedlist encoding;

Note: These two conditions can be modified, in redis.conf:

list-max-ziplist-entries 512
list-max-ziplist-value 64

Hash : The encoding of the Hash object can be ziplist or hashtable.

When the Hash object meets the following two conditions at the same time, the Hash object uses ziplist encoding:

  • The key and value strings of all key-value pairs stored in the Hash object are less than 64 bytes in length.
  • The number of key-value pairs stored in the Hash object is less than 512.

Otherwise, it is hashtable encoding.

Set : The encoding of the Set object can be intset or hashtable. The intset encoding object uses an integer set as the underlying implementation, and all elements are stored in an integer set.

The saved elements are integers and the number of elements is less than a certain range using intset encoding, if any condition is not met, hashtable encoding is used;

Zset : The encoding of the Zset object can be ziplist or zkiplist. When the ziplist encoding is used for storage, each set element is stored using two compressed lists next to each other.

Ziplist compresses the first node of the list to store the members of the element, and the second node stores the value of the element, and it is arranged in order from small to large.

Redis Core: The Secret of Only Fast and Unbreakable

When the Zset object meets the following two conditions at the same time, the ziplist encoding is used:

  • The number of elements saved by Zset is less than 128.
  • The member length of the Zset element is less than 64 bytes.

If any of the above conditions are not met, ziplist will be converted to zkiplist encoding. Note: These two conditions can be modified, in redis.conf:

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

Single thread model

65 Brother: Why is Redis single-threaded instead of multi-threaded parallel execution to make full use of the CPU?

What we want to be clear is: Redis single thread refers to Redis network IO and key-value pair instruction read and write is executed by one thread. Redis persistence, cluster data synchronization, asynchronous deletion, etc. are all executed by other threads.

As for why single threading is used, let us first understand the disadvantages of multithreading.

Disadvantages of multithreading

Using multithreading can generally increase system throughput and make full use of CPU resources.

However, after using multithreading, there is no good system design, and the scenario shown in the figure below may appear. When the number of threads is increased, the throughput will increase in the early stage. When further threads are added, the system throughput will hardly increase. And will even drop!

Number of threads and throughput

Before running each task, the CPU needs to know where the task is loaded and start running. In other words, the system needs to help it set up CPU registers and program counters in advance, which is called CPU context.

These saved contexts are stored in the system kernel and loaded again when the task is rescheduled. In this way, the original state of the task will not be affected, and the task will appear to be running continuously.

When switching context, we need to complete a series of work, which is a very resource-consuming operation.

In addition, when multiple threads modify shared data in parallel, in order to ensure that the data is correct, the need for a locking mechanism will bring additional performance overhead and face concurrent access control problems for shared resources.

The introduction of multi-threaded development requires the use of synchronization primitives to protect concurrent reading and writing of shared resources, increasing code complexity and difficulty in debugging.

What are the benefits of single thread?

  1. No performance consumption caused by thread creation;
  2. Avoid CPU consumption caused by context switching, without the overhead of multi-thread switching;
  3. Avoid competition issues between threads, such as adding locks, releasing locks, deadlocks, etc., without considering various lock issues.
  4. The code is clearer and the processing logic is simple.

Does single thread not make full use of CPU resources?

Official answer: Because Redis is a memory-based operation, CPU is not the bottleneck of Redis. The bottleneck of Redis is most likely the size of machine memory or network bandwidth . Since single-threaded is easy to implement and the CPU will not become a bottleneck, it is logical to adopt a single-threaded solution. Original address: https://redis.io/topics/faq .

I/O multiplexing model

Redis uses I/O multiplexing technology to process connections concurrently. Using epoll + simple event framework implemented by itself. Reading, writing, closing, and connection in epoll are all converted into events, and then using the multiplexing feature of epoll, never waste any time on IO.

65 Brother: What is I/O multiplexing?

Before explaining IO multiplexing multiplexing, let's first understand what the basic IO operation will experience.

Basic IO model

A basic network IO model, when processing a get request, it will go through the following process:

  1. Establish and establish with the client accept;
  2. Read request from socket recv;
  3. Parse the request sent by the client parse;
  4. Execute getinstructions;
  5. Respond to client data, that is, write data back to the socket.

Among them, bind/listen, accept, recv, parse, and send belong to network IO processing, and get belongs to key-value data operations. Since Redis is single-threaded, the most basic implementation is to execute these operations in sequence in one thread.

The key point is that accept and recv will be blocked . When Redis listens to a client's connection request, but has not successfully established a connection, it will block in the accept() function, causing other clients to fail to establish a connection with Redis.

Similarly, when Redis reads data from a client through recv(), if the data has not arrived, Redis will always block in recv().

Redis Core: The Secret of Only Fast and Unbreakable

The reason for blocking is due to the use of traditional blocking IO, which means that network operations such as read, accept, recv, etc. will always be blocked and waiting. As shown below:

Blocking IO

IO multiplexing

Multiplexing refers to multiple socket connections, and multiplexing refers to multiplexing a thread. There are three main technologies for multiplexing: select, poll, and epoll. Epoll is the latest and best multiplexing technology.

Its basic principle is that the kernel does not monitor the connection of the application itself, but monitors the file descriptor of the application.

When the client runs, it will generate sockets with different event types. On the server side, the I/O multiplexing program (I/O multiplexing module) will put the message into the queue (that is, the socket queue of the I/O multiplexing program in the figure below), and then pass the file The event dispatcher forwards it to different event handlers.

To put it simply: In the case of Redis single thread, the kernel will always monitor connection requests or data requests on the socket, and once a request arrives, it will be handed over to the Redis thread for processing, which achieves the effect of one Redis thread processing multiple IO streams.

select/epoll provides an event-based callback mechanism, that is, in response to the occurrence of different events, the corresponding event handler is called. So Redis has been processing events to improve Redis's response performance.

High-performance IO multiplexing

The Redis thread will not be blocked on a specific listening or connected socket, that is, it will not be blocked on a specific client request processing. Because of this, Redis can connect to multiple clients at the same time and process requests, thereby improving concurrency.

Summary of the principle of not breaking fast

65 Brother: After learning, I finally know the essential reason why Redis is fast. "Brother Code", don't talk, let me summarize! I will like and share this article in a while to let more people know the core principles of Redis fast.

  1. Pure memory operations are generally simple access operations, threads occupy a lot of time, and the time spent is mainly concentrated on IO, so the read speed is fast.
  2. The entire Redis is a global hash table, its time complexity is O(1), and in order to prevent the hash conflict from causing the linked list to be too long, Redis will perform a rehash operation to expand the number of hash buckets and reduce hash conflicts. And to prevent thread blocking caused by the one-time remapping data is too large, using progressive rehash. Cleverly distribute the one-time copy to the multiple request process to avoid blocking.
  3. Redis uses non-blocking IO: IO multiplexing, using a single thread to poll the descriptors, converting database opening, closing, reading, and writing into events. Redis uses its own event separator, which is more efficient. high.
  4. The single-threaded model guarantees the atomicity of each operation and reduces thread context switching and competition.
  5. Redis uses a hash structure throughout the entire process, which is fast to read. There are also some special data structures. Data storage is optimized, such as compressed tables, short data is compressed and stored, and another example is to jump tables and use ordered data structures to speed up The speed of reading.
  6. Choose different codes according to the type of data actually stored

The next article "Code Gebyte" will bring "Redis Log: A Killer for Fast Recovery Without Fear of Downtime" , follow me and get real hardcore knowledge points.

In addition, the technical reader group has also been opened, and the background will reply to "Add Group" to obtain the author's WeChat of "Code Ge Byte", and grow and communicate together.

The above is the detailed explanation of Redis's secrets that are not broken. If you think it is good, please like and share it. "Code Gebyte" is very grateful.

Code Byte

Guess you like

Origin blog.51cto.com/14745561/2607383