【问题标题】:Caching individual keys vs caching dictionary of keys in ASP.NET web API在 ASP.NET Web API 中缓存单个键与缓存键字典
【发布时间】:2019-01-29 23:10:42
【问题描述】:

我需要在 ASP.NET Web API 中缓存有关用户角色的信息。我决定使用System.Web.Helpers.WebCache 类。角色是纯字符串,大约 40 个字符长。每个用户可能有 1-10 个角色。

我正在考虑两种方法:

  1. 使用WebCache.Set(UserID, List<String>). 使用用户ID 作为键并存储角色列表(字符串)作为值。它很容易检索。
  2. 使用字典,我将使用 userId 作为键,使用角色列表作为值,然后缓存字典。这样我只用一个键进行缓存。当我检索此信息时,我首先检索字典,然后使用用户 ID 获取角色信息。

问题:

  1. 哪种方法更好?我喜欢方法一,因为它易于使用。它有什么缺点吗?
  2. 我计算用于将这些键保存到缓存中的内存使用的方法是将相同数量的数据(存储 10 个字符串类型的角色)添加到记事本中,然后计算记事本的大小(使用 UTF-8 编码)。大小约为500 bytes,磁盘大小为4 KB。然后如果我有 200 个用户,我将乘以 200 * 500 字节来计算内存使用量。这是正确的(如果近似关闭,我可以)计算方式吗?

【问题讨论】:

  • 我会使用静态的ConcurrentDictionary<userId, List<string>>
  • 这似乎是XY problem。重新考虑您的设计选择。研究 JWT(JSON Web Token)。在进行 API 调用时,将角色作为令牌的一部分。 Web API 应该是无状态的。

标签: c# .net caching asp.net-web-api memory-consumption


【解决方案1】:

我更喜欢保存单个键的方法,而不是将所有用户的角色保存为单个缓存对象。

原因如下:

1) 创建很简单,当用户登录或在适当的时间,检查缓存并为该用户创建“如果为空”,无需遍历字典对象(或 LINQ)来获取到那个关键项目。

2) 当用户注销或在适当的时候,缓存对象将被完全销毁,而不是仅从缓存中删除该特定键。

3) 当多个用户试图同时访问对象时,也不需要锁定对象,这种情况就会发生。由于对象是按用户创建的,因此不存在锁定该对象或需要使用同步或互斥锁的风险。

谢谢,普拉文

【讨论】:

    【解决方案2】:

    1. 方案一是可取的。它很简单,似乎只提供优势。

    2。您的计算对选项 1 有意义,但对选项 2 没有意义。使用散列的 C# 字典占用更多内存,对于此类原始数据和短数据,散列占用的数据可能会显着增加。

    与可维护性和功能相比,此类应用程序以单个字节为单位的内存存储通常是次要问题,这是因为用户角色通常是具有相当大安全问题的核心功能,并且随着项目的发展,它将变得非常代码的可维护性和安全性很重要。

    缓存应该专门用作一种优化,因为这与相对较小的用户群(约 200 人)的少量数据有关,最好将这些角色的缓存细化并易于重新获取。 根据这个库的官方文档

    Microsoft system.web.helpers.webcache

    一般来说,您永远不应该指望已缓存的项目在缓存中

    因为我假设用户角色定义了一些相当重要的功能,所以最好将这些角色的查询添加到您的 Web API 请求中,而不是将它们存储在本地。

    但是,如果您一心想要使用此缓存并在它消失时重新获取,那么根据您的问题,选项一将是一个更可取的选择。

    这是因为列表占用的内存更少,在这种情况下看起来更直接,我认为使用字典没有任何好处。

    当您拥有大型数据集并需要速度时,字典会大放异彩,但对于所有数据都已存储在内存中且数据集相对较小的场景,字典会带来复杂性和更高的内存要求,而其他方面则不多。尽管在大多数现代设备和服务器上的任何一种情况下,内存使用量听起来都可以忽略不计。

    鉴于您需要按用户查找角色,虽然字典可能听起来很有吸引力,但 WebCache 类似乎已经提供了这种功能,因此额外的字典失去了吸引力

    【讨论】:

      【解决方案3】:

      Q1:不知道Cache项的实际使用情况,很难下定论。尽管如此,我认为这一切都归结为这些项目的生活垃圾邮件的设计。如果您想在一段时间内一次性将它们全部淘汰,然后查询一组新数据,则将包含用户和角色的 ConcurrentDictionary 存储到 WebCache 是一种更易于管理的解决方案。

      否则,如果您想根据某个事件单独停用每个条目,那么接近一个似乎是一个相当直截了当的答案。请注意,如果您选择方法二,请使用 ConcurrentDictionary 而不是 Dictionary,因为后者不是线程安全的。

      Q2:WebCache 本质上是一个 IEnumerable>,因此除了对象的元数据之外,它还存储键字符串和每个值的内存位置。另一方面,ConcurrentDictionary/Dictionary 存储键字符串的哈希码和每个值的内存位置。虽然每个键的 byte[] 长度非常小,但其哈希码可能略大于字符串的大小。否则,HashCode 的大小是非常可预测的并且相当苗条(在我的测试中大约 10 个字节)。每次添加条目时,整个集合的大小都会增加大约 30 个字节。当然,这个数字不包括价值的实际大小,因为它与收藏无关。

      您可以使用以下方法计算字符串的大小:

      System.Text.Encoding.UTF8.GetByteCount(key);
      

      您可能还会发现编写代码来实现对象的大小很有用:

      static long GetSizeOfObject(object obj) 
      {
          using (var stream = new MemoryStream())
          {
              BinaryFormatter formatter = new BinaryFormatter();
              formatter.Serialize(stream, obj);
              return stream.Length;
          }
      }
      

      【讨论】:

        【解决方案4】:

        首先确保会有抽象层,如果将来你可以很容易地改变实现。 我看不出这两种方法之间有任何显着差异,它们都使用哈希表进行搜索。 但是我想第二次使用搜索两次,当它在缓存中搜索字典和在字典中搜索用户时。 另外,我完全推荐

        1. 如果用户数量很大,不要将角色存储为字符串,而是存储角色 标识。如果有 1000-10000 没有意义去做它

        2. 列表项

          别忘了 更新用户角色时清除缓存记录

        【讨论】:

          【解决方案5】:

          您不需要选项 2,选项 1 就足够了,因为您只需要 key,list<string>

          在使用缓存之前一般要考虑几点:-

          • 缓存的数据量是多少。
          • 您使用的是内存/分布式缓存模式。
          • 您将如何管理缓存。
          • 如果正在缓存的数据增长超过阈值,那么什么是跌倒机制。

          缓存有其优点和缺点,在您的场景中,您已经完成了有效负载分析,因此我认为选项 1 没有任何问题。

          【讨论】:

            猜你喜欢
            • 2018-06-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-06
            • 1970-01-01
            相关资源
            最近更新 更多