In-depth understanding of Redis cache penetration, breakdown, avalanche and solutions

1. Introduction

Introduction to Redis

Redis is a memory-based data structure storage system and a high-performance key-value storage database that supports key-value pairs, publish/subscribe, and store news information.

Caching and optimization

Caching technology is an important part of web development and is often used to enhance the performance and fault tolerance of web applications. Caches place calculated data or read-ahead data in the cache, and respond directly from the cache when the same data is requested. Therefore, caching plays a very significant role in accelerating application response time and saving processor resources.

In order to make better use of cache, it is necessary to classify and solve cache problems.

2. Classification of cache problems

cache penetration

Cache penetration refers to querying for non-existing data. Since there is no data in the cache, the request is transparently transmitted to the database. At this time, if malicious users continue to initiate queries for non-existing data, the cache will not be effective, and the request will eventually overwhelm the database. In this case, non-existing data needs to be processed, such as using Nginx cache; and exception mechanism is used for processing.

cache breakdown

Cache breakdown means that for an existing key, due to the large amount of concurrency, it fails at the same time, causing multiple threads to query the database, resulting in cache breakdown. In order to avoid such situations, you can make all threads wait for the first query before proceeding; or use mechanisms such as mutual exclusion locks to limit concurrent access.

cache avalanche

Cache avalanche means that a large number of keys in the cache fail at the same time, resulting in a large number of requests directly accessing the database in an instant, which seriously affects the performance of the database and the stability of the application. In order to solve this problem, measures such as cache warming and setting different expiration times can be introduced.

3. The solution of cache penetration

bloom filter

Bloom filters can quickly determine whether an element exists in a collection, so it can be used to verify whether the requested parameters or IDs exist in the database, thereby effectively preventing cache penetration caused by malicious attacks.

import redis
from bitarray import bitarray

class BloomFilter:
  def __init__(self, capacity, error_rate):
    self.capacity = capacity
    self.error_rate = error_rate
    self.redis_client = redis.Redis()
    self.hash_count = int(-1 * (capacity * math.log(error_rate) / (math.log(2) ** 2)))
    self.bit_array_length = int(math.ceil((capacity * math.log(error_rate)) / math.log(1.0 / (2 ** math.log(2)))))
    self.redis_client.setbit('bloom_filter', self.bit_array_length, 0)

  def exists(self, key):
    for i in range(self.hash_count):
      hashed_index = hash(key + str(i)) % self.bit_array_length
      if not self.redis_client.getbit('bloom_filter', hashed_index):
        return False
    return True

  def add(self, key):
    for i in range(self.hash_count):
      hashed_index = hash(key + str(i)) % self.bit_array_length
      self.redis_client.setbit('bloom_filter', hashed_index, 1)

Cache empty objects

When the query result is empty, we can also cache it in Redis and give it a short lifetime. In this way, next time if the same query request arrives again, an empty result can be returned directly from the cache without penetrating to the database.

def get_user_info(user_id):
  user_key = f'user:{user_id}'
  user_info = redis.get(user_key)
  if not user_info:
    # 从数据库中查询用户信息,如果查不到标记为空并将结果缓存到Redis中
    user_info = db.query_user_info(user_id)
    if not user_info:
      redis.set(user_key, '', ex=60)
    else:
      redis.set(user_key, user_info, ex=3600)
  return user_info

Interface layer verification

Add a verification mechanism at the application layer or interface layer to intercept illegal requests. It can be identified according to the request parameter characteristics, request frequency and other information, so as to avoid requests such as SQL injection attacks from penetrating the cache.

4. Solution to cache breakdown

mutex

In scenarios where a large number of cache updates are required, we usually need to use mutexes to avoid cache breakdown. For example, you can use the SETNX command of Redis to set the flag. When the cache is found to be expired, first acquire the lock, then load the data and update the cache, and release the lock at the same time.

def get_user_info(user_id):
  user_key = f'user:{user_id}'
  user_info = redis.get(user_key)
  if not user_info:
    lock_key = f'{user_id}_lock'
    # 使用SETNX命令尝试获取锁,如果获取成功
    if redis.setnx(lock_key, 1):
      # 设置锁的超时时间避免死锁
      redis.expire(lock_key, 60)
      user_info = db.query_user_info(user_id)
      redis.set(user_key, user_info, ex=3600)
      # 解锁,删除锁标记
      redis.delete(lock_key)
  return user_info

Hotspot data loaded in advance

Load data in advance before the cache expires to avoid cache breakdown caused by concurrent requests penetrating and directly accessing the database. You can set the cache expiration time to be slightly longer than the preload time to ensure that data can be preloaded into the cache.

def preload_hot_data():
  hot_data_key = 'hot_data'
  hot_data = db.query_hot_data()
  redis.set(hot_data_key, hot_data, ex=600)

def get_hot_data():
  hot_data_key = 'hot_data'
  hot_data = redis.get(hot_data_key)
  if not hot_data:
    # 预加载热点数据到缓存
    preload_hot_data()
    hot_data = redis.get(hot_data_key)
  return hot_data

5. The solution to cache avalanche

Increase cache fault tolerance

When a large number of caches fail at the same time, you can prevent cache avalanche by applying multi-level caches and adding fault-tolerant mechanisms. The specific implementation can be selected and adjusted according to the business scenario.

data preheating

Try to load all cached data into the cache system before the off-peak period of business, so as to avoid cache avalanche caused by cache penetration and cache breakdown during peak business period. You can use scheduled tasks or asynchronous loading to load slow queries or hot data into the cache in advance.

def preload_cache():
  user_keys = db.query_all_user_keys()
  for user_key in user_keys:
    user_info = db.query_user_info(user_key)
    redis.set(user_key, user_info, ex=3600)

# 定时任务,每天凌晨1点执行一次数据预热
scheduler.add_job(preload_cache, 'cron', hour='1')

6. Redis's solution to the above problems

Redis is a high-performance memory database. In order to solve the above problems, it proposes the following two solutions:

multi-level caching strategy

As a type of caching system, cache middleware is mainly used to increase website concurrency, reduce website response time, and reduce the pressure on the source database. Multi-level caching refers to the use of two or more caching technologies to speed up response.

"""
多级缓存策略示例:
1.使用 Redis 作为一级缓存,存储频繁访问的热数据;
2.使用 Memcached 作为二级缓存,存储冷数据或者业务数据;
3.使用本地缓存作为三级缓存,存储session,减少请求量。
"""
import redis
import memcache

class Cache:
    def __init__(self):
        self.redis = redis.Redis(host='localhost', port=6379)
        self.memcache = memcache.Client(['127.0.0.1:11211'])

    def get(self, key):
        # 尝试从 Redis 中获取数据
        data = self.redis.get(key)

        if not data:
            # Redis 中没有该数据,尝试从 Memcached 中获取
            data = self.memcache.get(key)

            if not data:
                # Memcached 中也没有,则从本地缓存中获取
                data = self.local_cache.get(key)

        return data

    def set(self, key, data):
        # 三个缓存都存储数据
        self.redis.set(key, data)
        self.memcache.set(key, data)
        self.local_cache.set(key, data)

Master-slave replication and persistence

In order to improve the stability and reliability of Redis, Redis provides a master-slave replication and persistence mechanism. Master-slave replication refers to data backup, copying the data of the Redis database to one or more Redis instances to achieve read-write separation and disaster recovery; The data is saved to the disk to ensure that the data still exists after Redis restarts.

"""
主从复制示例:
1.创建一个 Redis 实例,将其设置为主服务器;
2.创建两个 Redis 实例,将其分别设置为从服务器;
3.从主服务器同步数据到两个从服务器。
"""

import redis

# 创建 Redis 实例,作为主服务器
redis_master = redis.Redis(host='192.168.1.100', port=6379)

# 创建两个 Redis 实例,作为从服务器
redis_slave1 = redis.Redis(host='192.168.1.101', port=6379)
redis_slave2 = redis.Redis(host='192.168.1.102', port=6379)

# 将从服务器连接到主服务器上,实现主从复制
redis_slave1.slaveof(redis_master.host, redis_master.port)
redis_slave2.slaveof(redis_master.host, redis_master.port)

# 可以通过以下命令查看主从状态
# redis-cli info replication

Guess you like

Origin blog.csdn.net/u010349629/article/details/130895473