Redis author Salvatore Sanfilippo has compared these two memory-based data storage systems:

  1. Redis supports server-side data manipulation: Redis has more data structures and supports richer data manipulation than Memcached. In Memcached, you usually need to take the data to the client to make similar changes and set it back. This greatly increases the number of network IO and data volume. In Redis, these complex operations are usually just as efficient as regular GET/SET. So if you need a cache that can support more complex structures and operations, Redis is a good choice.

  2. Memory usage comparison: Memcached is more efficient using simple key-value storage. Redis uses hash hash for key-value storage, which is more efficient than Memcached due to its combined compression.

  3. Performance comparison: Because Redis uses only one core and Memcached can use multiple cores, Redis performs better than Memcached on average for storing small data on each core. For data over 100K, Memcached outperforms Redis, which has recently been optimized for storing large data, but still lags behind Memcached.

The specific reasons for the above conclusions are as follows:

1. Different data types are supported

Unlike Memcached, which supports a simple key-value structure for data logging, Redis supports a much richer set of data types. The most common data types are String, Hash, List, Set, and Sorted Set. Redis internally uses a redisObject to represent all keys and values. The main information of a redisObject is as follows:

Type specifies the data type of a value object. Encoding specifies how different data types are stored in Redis. For example: Type =string Specifies that value is a normal string, and the encoding can be raw or int. If it is int, it means that redis stores and represents the string as a numeric class. Of course, if the string itself can be represented by a number, such as “123” or “456”. Vm fields only allocate memory if Redis virtual memory is enabled, which is turned off by default.

1) String

  • Common commands: set/get/decr/ INCr /mget, etc.

  • Application scenario: String is the most commonly used data type. Common key/value storage can be classified into this type.

  • String is stored in Redis as a String by default, and is referenced by redisObject. When incR, DECr, etc., is converted to a numeric value for calculation, the encoding field of redisObject is int.

2) the Hash

  • Common commands: hget, hset, hgetall, etc

  • Application scenario: We want to store a user information object data, including the user ID, user name, age and birthday, through the user ID we want to obtain the user’s name or age or birthday;

  • Implementation: Redis Hash is actually a HashMap that stores values internally and provides an interface to access the Map members directly. As shown in the figure, Key is the user ID and value is a Map. The Map’s key is the attribute name of the member and its value is the attribute value. In this way, data can be modified and accessed directly through the Key of the internal Map (Redis calls the Key of the internal Map field), that is, the corresponding attribute data can be manipulated by Key(user ID) + field(attribute tag). HashMap is currently implemented in two ways: When the HashMap has a small number of members, Redis will compact store it in a way similar to the one-dimensional array to save memory, instead of adopting the real HashMap structure. In this case, the encoding of the redisObject of the corresponding value is zipmap. When the number of members increases, it automatically converts to a real HashMap, in which case the encoding is HT.

3) List

  • Commonly used commands: lpush rpush/lpop rpop/lrange, etc.;

  • Application scenario: There are many application scenarios of Redis List, which is also one of the most important data structures of Redis. For example, The list structure of Twitter followers and fans can be implemented.

  • Implementation: Redis list is a two-way linked list, that can support reverse lookup and traversal, more convenient operation, but brought part of the extra memory overhead, Redis internal many implementations, including sending buffer queues are also using this data structure.

4) Set

  • Commonly used commands: sadd/spop/smembers/sunion, etc.;

  • Application Scenarios: Redis set provides a list function similar to that of list. The special feature is that set can be automatically sorted. When you need to store a list of data and do not want duplicate data, set is a good choice. And sets provide an important interface for determining whether a member is in a set, which lists do not.

  • Implementation: The internal implementation of a set is a HashMap with an always-null value, which actually computs the hash to quickly sort out weights, which is why a set provides the ability to determine if a member is in the set.

5) Sorted Set

  • Common commands: zadd/zrange/zrem/zcard, etc.

  • Application scenario: The usage scenario of Redis sorted set is similar to that of SET, except that set is not automatically ordered. However, sorted set can be sorted by adding an additional priority parameter (score), that is, automatically sorted. When you need a sorted and non-repetitive list of collections, choose the sorted set data structure. For example, Twitter’s public Timeline stores the published time as score, so that it is automatically sorted by time.

  • Implementation method: In Redis sorted set, HashMap and SkipList are used internally to ensure data storage and order. HashMap stores members’ mapping to Score, while SkipList stores all members. Sorting is based on the score stored in HashMap. The structure of skip list can obtain relatively high search efficiency and is relatively simple to implement.

2. Different memory management mechanisms

In Redis, not all data is stored in memory at all times. This is one of the biggest differences from Memcached. When physical memory runs out, Redis can swap some long-unused values to disk. Redis only caches information about all keys. If Redis finds that the memory usage exceeds a certain threshold, the swap operation will be triggered. Redis calculates which key values need to be swapped according to swappability = age*log(size_in_memory). The values corresponding to these keys are persisted to disk and erased from memory. This feature allows Redis to hold data that exceeds the size of its machine’s own memory. Of course, the machine’s own memory must be able to hold all the keys, after all, these data will not swap. At the same time, when Redis swaps the data in the memory to the disk, the main thread that provides the service and the sub-thread that performs the swap operation will share the memory. Therefore, if the data needs to be updated, Redis will block the operation until the sub-thread completes the swap operation. When reading data from Redis, if the value of the read key is not in memory, Redis must load the corresponding data from swap and return it to the requester. There is the issue of an I/O thread pool. By default, Redis blocks until all swap files are loaded. This policy is suitable for batch operations when the number of clients is small. However, if you apply Redis to a large web application, this is obviously not sufficient for large concurrency. So Redis runs we set the size of the I/O thread pool to do concurrent operations on read requests that need to load the corresponding data from the swap file, reducing the blocking time.

For memory-based database systems such as Redis and Memcached, the efficiency of memory management is a key factor affecting system performance. The malloc/free function in traditional C language is the most commonly used method to allocate and free memory. However, this method has major drawbacks. First, for developers, malloc and free mismatch can easily cause memory leaks. Secondly, frequent invocation will cause a large number of memory fragments cannot be reclaimed and reused, reducing the memory utilization. Finally, as a system call, its overhead is much higher than that of a normal function call. Therefore, in order to improve the efficiency of memory management, efficient memory management solutions do not directly use malloc/free calls. Redis and Memcached both use their own memory management mechanisms, but they are implemented in very different ways. Here’s how they are managed.

By default, Memcached uses Slab Allocation to manage memory. The main idea of Memcached is to divide the allocated memory into blocks of a specified length to store key-value data records of the corresponding length according to the preset size. This completely solves the memory fragmentation problem. Slab Allocation is only designed to store external data. That is, all key-value data will be stored in Slab Allocation. Other Memcached memory requests will be requested using the normal malloc/free method. Because the number and frequency of these requests will not affect the overall system performance, the principle of Slab Allocation is quite simple. As shown in the figure, it first applies for a large Chunk of memory from the operating system, divides it into chunks of various sizes, and divides the chunks of the same size into Slab classes. Chunk is the smallest unit used to store key-value data. The size of each Slab Class can be controlled by a Growth Factor at Memcached startup time. Assuming the Growth Factor is 1.25, if the first Chunk is 88 bytes, the second Chunk is 112 bytes, and so on.

When Memcached receives the data sent from the client, it first selects the most appropriate Slab Class based on the size of the received data. You can then query Memcached for the list of free chunks in that Slab Class to find a Chunk that can be used to store data. When a database expires or is discarded, the Chunk occupied by the record can be reclaimed and added back to the free list.

As you can see from the above process, Memcached’s memory management is efficient and does not cause memory fragmentation, but its biggest drawback is that it wastes space. Because each Chunk is allocated a specific length of memory space, variable-length data cannot fully utilize this space. As shown in the figure, when 100 bytes of data are cached in a 128-byte Chunk, the remaining 28 bytes are wasted.

Redis memory management mainly through the source code zmalloc.h and zmalloc.c two files to achieve. To facilitate memory management, Redis stores the size of a block of memory in the head of the block after allocating it. As shown in the figure, real_ptr is the pointer redis returns after calling malloc. Redis stores the size of the memory block in the header. Size occupies a known size of memory, which is of type size_t, and returns ret_ptr. When memory needs to be freed, reT_ptr is passed to the memory manager. With ret_ptr, the program can easily calculate the value of real_ptr and pass real_pTR to free memory.

Redis records all memory allocations by defining an array of length ZMALLOC_MAX_ALLOC_STAT. Each element of the array represents the number of memory blocks currently allocated by the program, and the size of the memory block is the subscript of that element. In the source code, this array is zmalloc_allocations. Zmalloc_allocations [16] represents the number of memory blocks of 16bytes that were allocated. Zmalloc. c has a static variable used_memory that records the total amount of memory currently allocated. So, in general, Redis uses mallc/ Free wrapped, which is much simpler than Memcached’s memory management approach.

3. Data persistence support

Although Redis is a memory-based storage system, it itself supports persistence of in-memory data and provides two main persistence strategies: RDB snapshots and AOF logging. Memcached does not support data persistence.

1) RDB snapshot

Redis supports a persistence mechanism that stores a snapshot of the current data as a data file, called AN RDB snapshot. But how does a database that keeps writing generate snapshots? Redis makes use of the copy on write mechanism of fork commands. During snapshot generation, fork the current process out of a child process, then loop all data in the child process and write the data to an RDB file. The save command of Redis can be used to configure the timing of RDB snapshot generation. For example, a snapshot can be generated after 10 minutes, a snapshot can be generated after 1000 writes, or multiple rules can be implemented together. These rules are defined in the Redis configuration file. You can also SET the rules while Redis is running by using the Redis CONFIG SET command without rebooting Redis.

The Redis RDB file does not break because its writes are performed in a new process. When a new RDB file is created, the Redis child writes the data to a temporary file and then uses the atomic RENAME system call to rename the temporary file to the RDB file. This way, Redis RDB files are always available in case of a failure. Redis RDB files are also part of the internal Redis master/slave synchronization implementation. RDB has its disadvantages, that is, once there is a problem with the database, the data saved in our RDB file is not new, and the data from the last RDB file generation to Redis shutdown period is lost. In some businesses, this is tolerable.

2) AOF logs

The full name of an AOF log is Append Only File, which is an appending log file. Unlike the binlog of a database, an AOF file is plain text, and its contents are simply Redis standard commands. Only commands that cause data to be modified are appended to the AOF file. With every command to modify data generating a log, the AOF file would get bigger and bigger, so Redis provided another feature called AOF rewrite. The function is to recreate an AOF file, where a record is operated on only once, unlike an old file, which may record multiple operations on the same value. Similar to RDB, it forks a process that iterates through the data and writes to a new AOF temporary file. During the process of writing a new file, all write operation logs are still written to the old AOF file and also recorded in the memory buffer. When the redo operation is complete, all the logs in the buffer are written to a temporary file at once. Then call the atomic rename command to replace the old AOF file with the new AOF file.

AOF is a write file operation whose purpose is to write operation logs to disk, so it also encounters the flow of write operations we described above. After writing to AOF in Redis, the appendfsync option controls how long fsync is written to disk. The following appendfsync Settings become more secure.

  • Appendfsync No When appendfsync is set to no, Redis does not actively call fsync to synchronize the AOF log content to disk, so it is entirely dependent on OS debugging. On most Linux operating systems, fsync is performed every 30 seconds to write the data in the buffer to disk.

  • Appendfsync everySec When appendfsync is set to everysec, Redis defaults to making fsync calls every second to write data from the buffer to disk. But when the fsync call takes longer than 1 second. Redis will adopt a policy of delaying fsync for another second. That is, fsync will be performed two seconds later, and this time the fsync will be performed no matter how long it takes. Since the file descriptor is blocked during fsync, the current write operation is blocked. So the conclusion is that, in most cases, Redis will do fsync every second. In the worst case, a fsync operation is performed every two seconds. This operation, known as a group commit on most database systems, is to combine the data of multiple writes and write the log to disk at once.

  • Appednfsync always When appendfsync is set to always, fsync is called once for every write operation and the data is most secure. Of course, performance is affected because fsync is executed every time.

For general business requirements, it is recommended to use RDB for persistence because the overhead of RDB is much lower than that of AOF logs. AOF logs are recommended for applications that cannot afford to lose data.

4. Different cluster management

Memcached is a full-memory data caching system. Redis supports data persistence, but full memory is the essence of its high performance. As a memory-based storage system, the size of the physical memory of the machine is the maximum amount of data that the system can hold. If the amount of data that needs to be processed exceeds the physical memory size of a single machine, you need to build distributed clusters to expand the storage capacity.

Memcached does not support distribution per se, so distributed storage of Memcached can only be implemented on the client side using distributed algorithms such as consistent hashing. The following diagram shows Memcached’s distributed storage implementation architecture. Before sending data to the Memcached cluster, the client uses a built-in distributed algorithm to calculate the destination node for the data. The data is then sent directly to the node for storage. However, when the client queries data, it also calculates the node where the data is queried and sends a query request to the node to obtain data.

Redis prefers to build distributed storage on the server side rather than on the client side. Distributed storage is already supported in the latest version of Redis. Redis Cluster is an advanced version of Redis that implements distributed and allows single point of failure. It has no central node and linear scalability. The following figure shows the distributed storage architecture of Redis Cluster, in which nodes communicate with each other through binary protocol and nodes communicate with clients through ASCII protocol. In terms of data placement strategy, Redis Cluster divides the value field of the whole key into 4096 hash slots. Each node can store one or more hash slots, that is to say, the maximum number of nodes supported by the current Redis Cluster is 4096. Redis Cluster also uses a simple distributed algorithm: crc16(key) % HASH_SLOTS_NUMBER.

 

To ensure data availability in a single point of failure, the Redis Cluster introduces Master nodes and Slave nodes. In a Redis Cluster, each Master node has two Slave nodes for redundancy. In this way, the downtime of any two nodes in the entire cluster will not result in data unavailability. After the Master node exits, the cluster automatically selects a Slave node to become the new Master node.