服务器雪崩的应对策略之----缓存(纯干货)

avatar
作者
猴君
阅读量:0

在解决服务器雪崩问题时,缓存是一个非常有效的方法。缓存可以减少对后端资源的直接请求,降低负载,提升系统的整体性能和响应速度。以下是一些使用缓存来缓解服务器雪崩的方法和示例代码:

常见的缓存方法及示例

一、缓存解决服务器雪崩的方法

  1. 本地缓存:在应用服务器内存中存储热点数据,减少对后端数据库的请求。
  2. 分布式缓存:使用分布式缓存系统(如Redis、Memcached)来存储和共享数据,适用于多个服务器节点。
  3. 缓存预热:在系统启动时预先加载常用数据到缓存中,避免高并发时缓存未命中。
  4. 缓存降级:在缓存不可用时,提供降级数据,保证系统基本功能可用。
  5. 缓存失效策略:合理设置缓存失效时间和更新机制,避免缓存数据过期导致的请求洪峰。
  6. 多级缓存:结合本地缓存和分布式缓存,进一步提升缓存命中率和性能。

二、示例代码

下面展示了如何在C++中实现简单的本地缓存和使用Redis作为分布式缓存。

1、本地缓存示例

本地缓存可以使用std::unordered_mapstd::mutex来实现线程安全的缓存。

#include <iostream> #include <unordered_map> #include <mutex> #include <optional> #include <chrono> #include <thread>  class LocalCache  { public:     void put(const std::string& key, const std::string& value)      {         std::lock_guard<std::mutex> lock(mutex_);         cache_[key] = value;     }      std::optional<std::string> get(const std::string& key)      {         std::lock_guard<std::mutex> lock(mutex_);         auto it = cache_.find(key);         if (it != cache_.end())          {             return it->second;         }         return std::nullopt;     }  private:     std::unordered_map<std::string, std::string> cache_;     std::mutex mutex_; };  void simulate_server(LocalCache& cache)  {     std::string key = "example_key";     auto value = cache.get(key);     if (value)      {         std::cout << "Cache hit: " << value.value() << std::endl;     }      else      {         std::cout << "Cache miss, fetching from database..." << std::endl;         // 模拟数据库查询         std::this_thread::sleep_for(std::chrono::seconds(1));         std::string db_value = "example_value";         cache.put(key, db_value);         std::cout << "Fetched value: " << db_value << std::endl;     } }  int main()  {     LocalCache cache;     // 模拟多次请求     for (int i = 0; i < 5; ++i)      {         simulate_server(cache);     }     return 0; } 

2、分布式缓存使用 Redis 的示例

在现代分布式系统中,分布式缓存是一种常见的解决方案,用于提高数据访问速度、减少数据库负载和增加系统的可扩展性。Redis 是一个流行的分布式缓存系统,它提供了高性能、持久性和丰富的数据结构支持。(示例同 3、缓存预热示例

3、缓存预热示例

缓存预热(Cache Warming)是指在系统启动或高负载前,预先将热点数据加载到缓存中,以避免高并发访问时缓存未命中导致的性能问题。缓存预热可以显著提高系统的响应速度,减少数据库负载。

以下是一个使用 Redis 作为分布式缓存的 C++ 示例,展示了如何在应用启动时进行缓存预热。

安装 hiredis

首先,需要安装 hiredis 库:

sudo apt-get update sudo apt-get install libhiredis-dev 

示例代码

#include <iostream> #include <hiredis/hiredis.h> #include <optional> #include <thread> #include <chrono> #include <vector> #include <unordered_map>  // Redis 缓存类 class RedisCache  { public:     RedisCache(const std::string& host, int port)      {         context_ = redisConnect(host.c_str(), port);         if (context_ == nullptr || context_->err)          {             if (context_)              {                 std::cerr << "Redis connection error: " << context_->errstr << std::endl;                 redisFree(context_);             }              else              {                 std::cerr << "Redis connection error: can't allocate redis context" << std::endl;             }             exit(1);         }     }      ~RedisCache()      {         if (context_)          {             redisFree(context_);         }     }      void put(const std::string& key, const std::string& value)      {         redisReply* reply = (redisReply*)redisCommand(context_, "SET %s %s", key.c_str(), value.c_str());         if (reply)          {             freeReplyObject(reply);         }     }      std::optional<std::string> get(const std::string& key)      {         redisReply* reply = (redisReply*)redisCommand(context_, "GET %s", key.c_str());         if (reply->type == REDIS_REPLY_STRING)          {             std::string value = reply->str;             freeReplyObject(reply);             return value;         }         freeReplyObject(reply);         return std::nullopt;     }  private:     redisContext* context_; };  // 模拟数据库查询 std::unordered_map<std::string, std::string> database =  {     {"key1", "value1"},     {"key2", "value2"},     {"key3", "value3"},     {"key4", "value4"},     {"key5", "value5"} };  // 预热缓存 void warm_up_cache(RedisCache& cache)  {     for (const auto& [key, value] : database)      {         cache.put(key, value);         std::cout << "Preloaded " << key << " : " << value << " into cache." << std::endl;     } }  void simulate_server(RedisCache& cache)  {     std::vector<std::string> keys = {"key1", "key2", "key3", "key4", "key5"};     for (const auto& key : keys)      {         auto value = cache.get(key);         if (value)          {             std::cout << "Cache hit: " << key << " : " << value.value() << std::endl;         }          else          {             std::cout << "Cache miss, fetching from database..." << std::endl;             std::string db_value = database[key];             cache.put(key, db_value);             std::cout << "Fetched value: " << db_value << std::endl;         }     } }  int main()  {     RedisCache cache("127.0.0.1", 6379);      // 预热缓存     warm_up_cache(cache);      // 模拟多次请求     simulate_server(cache);      return 0; } 

解释

  1. RedisCache 类

    • RedisCache 构造函数:初始化 Redis 连接。
    • put 方法:将数据存储到 Redis 中。
    • get 方法:从 Redis 中读取数据。
  2. 模拟数据库查询:用一个std::unordered_map来模拟数据库。

  3. warm_up_cache 函数:遍历模拟数据库,将所有数据预先加载到缓存中。

  4. simulate_server 函数:模拟服务器处理请求,首先尝试从缓存中获取数据,如果缓存未命中,则从模拟数据库查询,并将结果放入缓存。

  5. main 函数:首先预热缓存,然后模拟多个请求,验证缓存预热的效果。

注意事项

  • 确保 Redis 服务器在本地运行并监听默认端口 6379。可以使用以下命令启动 Redis 服务器:

    redis-server 
  • 在实际应用中,缓存预热的数据应根据业务需求和数据访问模式进行选择,预热的数据量也应控制在合理范围内,避免因预热数据过多而占用过多内存。

4、缓存降级示例

缓存降级是一种在缓存不可用或失效时提供备选数据的方法,以保证系统的基本功能和稳定性。在高并发和高可用性要求的系统中,缓存降级可以有效防止系统崩溃,保障服务的持续可用。

以下是一个使用 Redis 作为分布式缓存的 C++ 示例,展示了如何在缓存失效或不可用时进行缓存降级,提供备选数据。

安装 hiredis

首先,需要安装 hiredis 库:

sudo apt-get update sudo apt-get install libhiredis-dev 

示例代码

#include <iostream> #include <hiredis/hiredis.h> #include <optional> #include <thread> #include <chrono> #include <unordered_map>  // Redis 缓存类 class RedisCache  { public:     RedisCache(const std::string& host, int port)      {         context_ = redisConnect(host.c_str(), port);         if (context_ == nullptr || context_->err)          {             if (context_)              {                 std::cerr << "Redis connection error: " << context_->errstr << std::endl;                 redisFree(context_);             }              else              {                 std::cerr << "Redis connection error: can't allocate redis context" << std::endl;             }             exit(1);         }     }      ~RedisCache()      {         if (context_)          {             redisFree(context_);         }     }      void put(const std::string& key, const std::string& value)      {         redisReply* reply = (redisReply*)redisCommand(context_, "SET %s %s", key.c_str(), value.c_str());         if (reply)          {             freeReplyObject(reply);         }     }      std::optional<std::string> get(const std::string& key)      {         redisReply* reply = (redisReply*)redisCommand(context_, "GET %s", key.c_str());         if (reply && reply->type == REDIS_REPLY_STRING)          {             std::string value = reply->str;             freeReplyObject(reply);             return value;         }         if (reply)          {             freeReplyObject(reply);         }         return std::nullopt;     }      bool is_connected() const      {         return context_ != nullptr && context_->err == 0;     }  private:     redisContext* context_; };  // 模拟数据库查询 std::unordered_map<std::string, std::string> database =  {     {"key1", "value1"},     {"key2", "value2"},     {"key3", "value3"},     {"key4", "value4"},     {"key5", "value5"} };  // 模拟降级数据 std::unordered_map<std::string, std::string> degraded_data =  {     {"key1", "degraded_value1"},     {"key2", "degraded_value2"},     {"key3", "degraded_value3"},     {"key4", "degraded_value4"},     {"key5", "degraded_value5"} };  void simulate_server(RedisCache& cache)  {     std::vector<std::string> keys = {"key1", "key2", "key3", "key4", "key5"};     for (const auto& key : keys)      {         auto value = cache.get(key);         if (value)          {             std::cout << "Cache hit: " << key << " : " << value.value() << std::endl;         }          else          {             if (cache.is_connected())              {                 std::cout << "Cache miss, fetching from database..." << std::endl;                 std::string db_value = database[key];                 cache.put(key, db_value);                 std::cout << "Fetched value: " << db_value << std::endl;             }              else              {                 std::cout << "Cache unavailable, using degraded data for " << key << std::endl;                 std::string degraded_value = degraded_data[key];                 std::cout << "Degraded value: " << degraded_value << std::endl;             }         }     } }  int main()  {     RedisCache cache("127.0.0.1", 6379);      // 模拟多次请求     simulate_server(cache);      return 0; } 

解释

  1. RedisCache 类

    • RedisCache 构造函数:初始化 Redis 连接。
    • put 方法:将数据存储到 Redis 中。
    • get 方法:从 Redis 中读取数据。
    • is_connected 方法:检查 Redis 连接是否正常。
  2. 模拟数据库查询:用一个 std::unordered_map 来模拟数据库。

  3. 模拟降级数据:用一个 std::unordered_map 来提供降级数据,当缓存和数据库都不可用时使用。

  4. simulate_server 函数

    • 尝试从缓存中获取数据。
    • 如果缓存未命中且 Redis 连接正常,则从数据库查询数据,并将结果放入缓存。
    • 如果 Redis 连接不可用,则使用降级数据提供服务。
  5. main 函数:模拟多次请求,验证缓存降级的效果。

注意事项

  • 确保 Redis 服务器在本地运行并监听默认端口 6379。可以使用以下命令启动 Redis 服务器:

    redis-server 
  • 在实际应用中,降级数据应根据业务需求和数据访问模式进行选择,以保证在缓存和数据库都不可用时仍能提供基本的服务。

5、缓存失效策略示例

缓存失效策略是缓存系统中至关重要的一部分,旨在确保缓存数据的新鲜度和有效性。常见的缓存失效策略包括固定时间失效(TTL)、LRU(Least Recently Used)以及LFU(Least Frequently Used)。下面是一个使用Redis作为分布式缓存的C++示例,展示如何实现固定时间失效策略(TTL)。

安装 hiredis

首先,需要安装 hiredis 库:

sudo apt-get update sudo apt-get install libhiredis-dev 

示例代码

以下是一个使用 Redis 实现固定时间失效策略的示例:

#include <iostream> #include <hiredis/hiredis.h> #include <optional> #include <thread> #include <chrono> #include <unordered_map>  // Redis 缓存类 class RedisCache  { public:     RedisCache(const std::string& host, int port)      {         context_ = redisConnect(host.c_str(), port);         if (context_ == nullptr || context_->err)          {             if (context_)              {                 std::cerr << "Redis connection error: " << context_->errstr << std::endl;                 redisFree(context_);             }              else              {                 std::cerr << "Redis connection error: can't allocate redis context" << std::endl;             }             exit(1);         }     }      ~RedisCache()      {         if (context_)          {             redisFree(context_);         }     }      void put(const std::string& key, const std::string& value, int ttl)      {         redisReply* reply = (redisReply*)redisCommand(context_, "SET %s %s EX %d", key.c_str(), value.c_str(), ttl);         if (reply)          {             freeReplyObject(reply);         }     }      std::optional<std::string> get(const std::string& key)      {         redisReply* reply = (redisReply*)redisCommand(context_, "GET %s", key.c_str());         if (reply && reply->type == REDIS_REPLY_STRING)          {             std::string value = reply->str;             freeReplyObject(reply);             return value;         }         if (reply)          {             freeReplyObject(reply);         }         return std::nullopt;     }  private:     redisContext* context_; };  // 模拟数据库查询 std::unordered_map<std::string, std::string> database =  {     {"key1", "value1"},     {"key2", "value2"},     {"key3", "value3"},     {"key4", "value4"},     {"key5", "value5"} };  void simulate_server(RedisCache& cache)  {     std::vector<std::string> keys = {"key1", "key2", "key3", "key4", "key5"};     int ttl = 10; // 缓存数据的存活时间(秒)      for (const auto& key : keys)      {         auto value = cache.get(key);         if (value)          {             std::cout << "Cache hit: " << key << " : " << value.value() << std::endl;         }          else          {             std::cout << "Cache miss, fetching from database..." << std::endl;             std::string db_value = database[key];             cache.put(key, db_value, ttl);             std::cout << "Fetched value: " << db_value << std::endl;         }     } }  int main()  {     RedisCache cache("127.0.0.1", 6379);      // 模拟多次请求     for (int i = 0; i < 3; ++i)      {         std::cout << "Simulation round " << i + 1 << std::endl;         simulate_server(cache);         std::this_thread::sleep_for(std::chrono::seconds(5)); // 等待一段时间再进行下一轮模拟     }      return 0; } 

解释

  1. RedisCache 类

    • RedisCache 构造函数:初始化 Redis 连接。
    • put 方法:将数据存储到 Redis 中,并设置数据的生存时间(TTL)。
    • get 方法:从 Redis 中读取数据。
  2. 模拟数据库查询:用一个 std::unordered_map 来模拟数据库。

  3. simulate_server 函数

    • 尝试从缓存中获取数据。
    • 如果缓存未命中,则从模拟数据库查询数据,并将结果放入缓存,设置缓存数据的生存时间(TTL)。
  4. main 函数:模拟多次请求,验证缓存失效策略的效果。每轮模拟之间等待一段时间,以测试缓存数据的生存时间。

注意事项

  • 确保 Redis 服务器在本地运行并监听默认端口 6379。可以使用以下命令启动 Redis 服务器:

    redis-server 
  • 在实际应用中,TTL 应根据业务需求和数据更新频率进行设置,以平衡数据新鲜度和缓存命中率。

6、多级缓存示例

多级缓存是一种缓存策略,旨在利用不同层次的缓存来提高数据访问的效率和可靠性。常见的多级缓存体系包括本地缓存(如内存缓存)和分布式缓存(如 Redis)两级。当数据请求发出时,首先查询本地缓存,如果本地缓存未命中,再查询分布式缓存,最后如果仍未命中,则查询数据库并将结果更新到缓存中。

以下是一个使用 C++ 实现的多级缓存示例,其中包含内存缓存和 Redis 分布式缓存。

安装 hiredis

首先,需要安装 hiredis 库:

sudo apt-get update sudo apt-get install libhiredis-dev 

示例代码

#include <iostream> #include <hiredis/hiredis.h> #include <unordered_map> #include <optional> #include <chrono> #include <thread>  // 内存缓存类 class MemoryCache  { public:     void put(const std::string& key, const std::string& value, int ttl)      {         auto expiration_time = std::chrono::steady_clock::now() + std::chrono::seconds(ttl);         cache_[key] = {value, expiration_time};     }      std::optional<std::string> get(const std::string& key)      {         auto it = cache_.find(key);         if (it != cache_.end())          {             if (std::chrono::steady_clock::now() < it->second.expiration_time)              {                 return it->second.value;             }              else              {                 cache_.erase(it);             }         }         return std::nullopt;     }  private:     struct CacheItem      {         std::string value;         std::chrono::steady_clock::time_point expiration_time;     };     std::unordered_map<std::string, CacheItem> cache_; };  // Redis 缓存类 class RedisCache  { public:     RedisCache(const std::string& host, int port)      {         context_ = redisConnect(host.c_str(), port);         if (context_ == nullptr || context_->err)          {             if (context_)              {                 std::cerr << "Redis connection error: " << context_->errstr << std::endl;                 redisFree(context_);             }              else              {                 std::cerr << "Redis connection error: can't allocate redis context" << std::endl;             }             exit(1);         }     }      ~RedisCache()      {         if (context_)          {             redisFree(context_);         }     }      void put(const std::string& key, const std::string& value, int ttl)      {         redisReply* reply = (redisReply*)redisCommand(context_, "SET %s %s EX %d", key.c_str(), value.c_str(), ttl);         if (reply)          {             freeReplyObject(reply);         }     }      std::optional<std::string> get(const std::string& key)      {         redisReply* reply = (redisReply*)redisCommand(context_, "GET %s", key.c_str());         if (reply && reply->type == REDIS_REPLY_STRING)          {             std::string value = reply->str;             freeReplyObject(reply);             return value;         }         if (reply)          {             freeReplyObject(reply);         }         return std::nullopt;     }  private:     redisContext* context_; };  // 模拟数据库查询 std::unordered_map<std::string, std::string> database =  {     {"key1", "value1"},     {"key2", "value2"},     {"key3", "value3"},     {"key4", "value4"},     {"key5", "value5"} };  void simulate_server(MemoryCache& mem_cache, RedisCache& redis_cache)  {     std::vector<std::string> keys = {"key1", "key2", "key3", "key4", "key5"};     int ttl = 10; // 缓存数据的存活时间(秒)      for (const auto& key : keys)      {         // 先从内存缓存查询         auto value = mem_cache.get(key);         if (value)          {             std::cout << "Memory cache hit: " << key << " : " << value.value() << std::endl;             continue;         }          // 再从Redis缓存查询         value = redis_cache.get(key);         if (value)          {             std::cout << "Redis cache hit: " << key << " : " << value.value() << std::endl;             mem_cache.put(key, value.value(), ttl); // 更新内存缓存             continue;         }          // 最后从数据库查询         std::cout << "Cache miss, fetching from database..." << std::endl;         std::string db_value = database[key];         mem_cache.put(key, db_value, ttl);         redis_cache.put(key, db_value, ttl);         std::cout << "Fetched value: " << db_value << std::endl;     } }  int main()  {     MemoryCache mem_cache;     RedisCache redis_cache("127.0.0.1", 6379);      // 模拟多次请求     for (int i = 0; i < 3; ++i)      {         std::cout << "Simulation round " << i + 1 << std::endl;         simulate_server(mem_cache, redis_cache);         std::this_thread::sleep_for(std::chrono::seconds(5)); // 等待一段时间再进行下一轮模拟     }      return 0; } 

解释

  1. MemoryCache 类

    • 使用 std::unordered_map 来存储缓存项。
    • put 方法:将数据存储到内存缓存中,并设置数据的生存时间(TTL)。
    • get 方法:从内存缓存中读取数据,并检查数据是否过期。
  2. RedisCache 类

    • RedisCache 构造函数:初始化 Redis 连接。
    • put 方法:将数据存储到 Redis 中,并设置数据的生存时间(TTL)。
    • get 方法:从 Redis 中读取数据。
  3. simulate_server 函数

    • 先从内存缓存查询数据。
    • 如果内存缓存未命中,再从 Redis 缓存查询数据。
    • 如果 Redis 缓存也未命中,则从模拟数据库查询数据,并更新到内存缓存和 Redis 缓存。
  4. main 函数:模拟多次请求,验证多级缓存策略的效果。每轮模拟之间等待一段时间,以测试缓存数据的生存时间。

注意事项

  • 确保 Redis 服务器在本地运行并监听默认端口 6379。可以使用以下命令启动 Redis 服务器:

    redis-server 
  • 在实际应用中,内存缓存的大小应根据系统内存容量进行合理配置,以避免占用过多内存。

三、结论

使用缓存可以有效地缓解服务器雪崩问题,通过减少对后端资源的直接请求,提高系统的性能和响应速度。通过结合以上各种缓存方法,可以建立一个高效、稳定、可靠的缓存系统,从而显著提升系统的性能和用户体验。开发人员应根据具体业务需求,选择合适的缓存策略,并合理配置缓存参数,以达到最佳的缓存效果。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!