【问题标题】:Redis as a distributed cache not performing?Redis 作为分布式缓存不执行?
【发布时间】:2016-01-11 17:02:12
【问题描述】:

我有一个相当复杂的 asp.net 站点,它可以即时构建页面(我想像 WordPress)。它在很大程度上依赖于缓存来保持美观和活泼(0.5 秒的响应时间)。

我最近自己承担了对应用程序进行负载平衡的任务,这样做需要将缓存放入分布式环境中(我已经在使用 SQL Server 会话状态)。所以我做了研究,选择了 redis,并选择了 StackExchange.Redis 客户端。

我使我的对象非常简单,使用“type_dbIndex”键命名约定的小对象,然后使用集合链接所有具有关系的对象。我委托了一个专门用于 redis 的 Ubuntu 盒子,并且非常兴奋地打开了开关。不幸的是,我被严重压垮了。事实证明,我的 redis 缓存平均比内存缓存慢 3 倍(1.5 秒)。

现在我要第一个承认我不是 redis 专家,只是基本的安装和设置,但我也没有时间成为 redis 调优大师来尝试从中挤出一秒。我可以想象真正的罪魁祸首是网络延迟。每个请求平均从缓存中加载大约 100-200 个对象,所有这些对象都需要序列化/反序列化并通过网络推送(服务器位于使用本地网络的同一 DMZ 中)。

所以对于我的问题,我得到的结果是否异常糟糕,还是这是常态?我需要为我的所有对象编写自定义序列化程序吗?我应该使用 ProtoBuf 而不是标准序列化程序。我是否需要在两台机器之间创建一个内部网络(专用网卡,就像我们过去在 SQL 和 Web Server 之间所做的那样)?基本上,有没有希望达到黄金半秒标记,因为管理负载平衡服务器之间的进程内内存缓存是一场噩梦,以至于完全不切实际。我是否错误地使用了缓存?

感谢任何建议/帮助/战争故事。

编辑:我想我会在此处粘贴一个 miniprofiler 输出,以便您进一步了解我的难题...

【问题讨论】:

  • 应用和 Redis 服务器之间的平均网络延迟是多少?缓存对象的平均大小是多少?
  • @ItamarHaber - 不幸的是我不知道实际的网络延迟。 (告诉我如何测量,我会测量),但缓存中的对象相对较小,可能 20 个属性都是简单类型(没有列表等)
  • 如您所见,我对一个简单的按键调用 Get 的响应时间平均为 100 毫秒...我希望低于 10 毫秒
  • 尝试 ping 你的 Redis 服务器,或者更好地使用 redis-cli --latency

标签: c# asp.net caching redis stackexchange.redis


【解决方案1】:

您的问题不是 redis,而是通过网络访问 redis 100-200 次。 试试这些内置的 redis 解决方案:

  1. 流水线:这可以批量发送 redis 请求,并在一个(或更多)网络请求中获取所有数据。

http://redis.io/topics/pipelining

  1. Lua 脚本:服务器端 Lua 脚本让您可以将 Lua 代码发送到 redis 服务器,在那里进行评估,然后在单个网络请求中返回数据。

在您的特定情况下,管道将是最有效的解决方案,它适用于所有 redis 驱动程序,如 python、php 等。

【讨论】:

  • 不幸的是,我不能真正批量处理呼叫,因为它们是“按需”制作的,并且从请求到请求的变化很大。然而,我确实删除了“Exists”检查并将其替换为单个 get 并检查 null,这几乎将调用减少了一半,但它仍然太慢了。
【解决方案2】:

您可以尝试使用混合缓存模型,同时使用内存缓存和 redis。所有缓存方法(get、exists 等)也应该使用内存缓存,如果内存缓存中不存在该项目,则从 redis 查询项目并将其添加到内存缓存以供进一步请求。

这可能会导致客户端节点之间的不一致,但会提高缓存操作的性能。

示例代码取自nopCommerce

    /// <summary>
    /// Gets or sets the value associated with the specified key.
    /// </summary>
    /// <typeparam name="T">Type</typeparam>
    /// <param name="key">The key of the value to get.</param>
    /// <returns>The value associated with the specified key.</returns>
    public virtual T Get<T>(string key)
    {
        //little performance workaround here:
        //we use "MemoryCacheManager" to cache a loaded object in memory for the current HTTP request.
        //this way we won't connect to Redis server 500 times per HTTP request (e.g. each time to load a locale or setting)
        if (_memoryCacheManager.IsSet(key))
            return _memoryCacheManager.Get<T>(key);

        var rValue = _db.StringGet(key);
        if (!rValue.HasValue)
            return default(T);
        var result = Deserialize<T>(rValue);

        _memoryCacheManager.Set(key, result, CacheProfiles.ShortLivedDuration);
        return result;
    }

    /// <summary>
    /// Adds the specified key and object to the cache.
    /// </summary>
    /// <param name="key">key</param>
    /// <param name="data">Data</param>
    /// <param name="cacheTime">Cache time</param>
    public virtual void Set(string key, object data, int cacheTime)
    {
        if (data == null || string.IsNullOrEmpty(key))
            return;

        var entryBytes = Serialize(data);
        var expiresIn = TimeSpan.FromMinutes(cacheTime);

         _db.StringSetAsync(key, entryBytes, expiresIn);
        _memoryCacheManager.Set(key, data, cacheTime);
    }

【讨论】:

    猜你喜欢
    • 2018-11-23
    • 2017-09-28
    • 2018-08-20
    • 2014-02-12
    • 1970-01-01
    • 2021-07-01
    • 1970-01-01
    • 2021-10-03
    • 1970-01-01
    相关资源
    最近更新 更多