前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET性能优化-使用内存+磁盘混合缓存

.NET性能优化-使用内存+磁盘混合缓存

作者头像
InCerry
发布2023-03-08 16:00:25
3670
发布2023-03-08 16:00:25
举报
文章被收录于专栏:InCerryInCerry

我们回顾一下上一篇文章中的内容,有一个朋友问我这样一个问题:

我的业务依赖一些数据,因为数据库访问慢,我把它放在 Redis 里面,不过还是太慢了,有什么其它的方案吗?

其实这个问题比较简单的是吧?Redis 其实属于网络存储,我对照下面的这个表格,可以很容易的得出结论,既然网络存储的速度慢,那我们就可以使用内存 RAM 存储,把放 Redis 里面的数据给放内存里面就好了。

操作

速度

执行指令

1/1,000,000,000 秒 = 1 纳秒

从一级缓存读取数据

0.5 纳秒

分支预测失败

5 纳秒

从二级缓存读取数据

7 纳秒

使用 Mutex 加锁和解锁

25 纳秒

从主存(RAM 内存)中读取数据

100 纳秒

在 1Gbps 速率的网络上发送 2Kbyte 的数据

20,000 纳秒

从内存中读取 1MB 的数据

250,000 纳秒

磁头移动到新的位置(代指机械硬盘)

8,000,000 纳秒

从磁盘中读取 1MB 的数据

20,000,000 纳秒

发送一个数据包从美国到欧洲然后回来

150 毫秒 = 150,000,000 纳秒

提出这个方案以后,接下来就遇到了另外一个问题:

但是数据比我应用的内存大,这怎么办呢?

在上篇文章中,我们提到了使用 FASTER 作为内存+磁盘混合缓存的方案,但是由于 FASTER 的 API 比较难使用,另外在纯内存场景中表现不如ConcurrentDictionary,所以最后得出的结论也是仅供参考。

经过一段时间的研究,笔者实现了一个基于微软 FasterKv 封装的进程内混合缓存库(内存+磁盘),它有着更加易用的 API,接下来就和大家讨论讨论它。

FasterKvCache 架构

这里需要简单的说一说 FasterKvCache 的架构,它核心使用的 FasterKv,所以架构实际上和 FasterKv 一致,其原理比较复杂,所以笔者简化了原理图,大概就如下所示:

FasterKv 的热数据会在内存中,而全量的数据会持久化在磁盘中。这中间有一些缓存淘汰算法,所以大家看到这张图就能明白 FasterKvCache 适用和不适用哪些场景了。

如何使用它

笔者之前给 EasyCaching 提交了 FasterKv 的实现,但是由于有一些 EasyCaching 的高级功能在 FasterKv 上目前无法高性能的实现,所以单独创建了这个库,提供高性能和最基本的 API 实现;如果大家已经使用了 EasyCaching,那么可以直接使用 EasyCaching.FasterKv 这个 NuGet 包。

如果使用需要 FasterKvCache 的话,只需要安装 Nuget 包,Nuget 包不同的功能如下所示,其中序列化包可以只安装自己需要的即可。

软件包名

版本

备注

FasterKv.Cache.Core[1]

1.0.0-rc1

缓存核心包,包含 FasterKvCache 主要的 API

FasterKv.Cache.MessagePack[2]

1.0.0-rc1

基于 MessagePack 的磁盘序列化包,它具有着非常好的性能,但是需要注意它稍微有一点使用门槛,大家可以看它的文档。

FasterKv.Cache.SystemTextJson[3]

1.0.0-rc1

基于 System.Text.Json 的磁盘序列化包,它是.NET 平台上性能最好 JSON 序列化封装,但是比 MessagePack 差。不过它易用性非常好,无需对缓存实体进行单独配置。

使用

直接使用

我们可以直接通过new FasterKvCache(...)的方式使用它,目前它只支持基本的三种操作GetSetDelete。为了方便使用和性能的考虑,我们将 FasterKvCache 分为两种 API 风格,一种是通用对象风格,一种是泛型风格。

  • 通用对象:直接使用new FasterKvCache(...)创建,可以存放任意类型的 Value。它底层使用object类型存储,所以内存缓冲内访问值类型对象会有装箱和拆箱的开销。
  • 泛型:需要使用new FasterKvCache<T>(...)创建,只能存放T类型的 Value。它底层使用T类型存储,所以内存缓冲内不会有任何开销。

当然如果内存缓冲不够,对应的 Value 被淘汰到磁盘上,那么同样都会有读写磁盘、序列化和反序列化开销。

通用对象版本

代码如下所示,同一个 cache 实例可以添加任意类型:

代码语言:javascript
复制
using FasterKv.Cache.Core;
using FasterKv.Cache.Core.Configurations;
using FasterKv.Cache.MessagePack;

// create a FasterKvCache
var cache = new FasterKv.Cache.Core.FasterKvCache("MyCache",
    new DefaultSystemClock(),
    new FasterKvCacheOptions(),
    new IFasterKvCacheSerializer[]
    {
        new MessagePackFasterKvCacheSerializer
        {
            Name = "MyCache"
        }
    },
    null);

var key = Guid.NewGuid().ToString("N");

// sync
// set key and value with expiry time
cache.Set(key, "my cache sync", TimeSpan.FromMinutes(5));

// get
var result = cache.Get<string>(key);
Console.WriteLine(result);

// delete
cache.Delete(key);

// async
// set
await cache.SetAsync(key, "my cache async");

// get
result = await cache.GetAsync<string>(key);
Console.WriteLine(result);

// delete
await cache.DeleteAsync(key);

// set other type object
cache.Set(key, new DateTime(2022,2,22));
Console.WriteLine(cache.Get<DateTime>(key));

输出结果如下所示:

代码语言:javascript
复制
my cache sync
my cache async
2022/2/22 0:00:00
泛型版本

泛型版本的话性能最好,但是它只允许添加一个类型,否则代码将编译不通过:

代码语言:javascript
复制
// create a FasterKvCache<T>
// only set T type value
var cache = new FasterKvCache<string>("MyTCache",
    new DefaultSystemClock(),
    new FasterKvCacheOptions(),
    new IFasterKvCacheSerializer[]
    {
        new MessagePackFasterKvCacheSerializer
        {
            Name = "MyTCache"
        }
    },
    null);
Microsoft.Extensions.DependencyInjection

当然,我们也可以直接使用依赖注入的方式使用它,用起来也非常简单。按照通用和泛型版本的区别,我们使用不同的扩展方法即可:

代码语言:javascript
复制
var services = new ServiceCollection();
// use AddFasterKvCache
services.AddFasterKvCache(options =>
{
    // use MessagePack serializer
    options.UseMessagePackSerializer();
}, "MyKvCache");

var provider = services.BuildServiceProvider();

// get instance do something
var cache = provider.GetService<FasterKvCache>();

泛型版本需要调用相应的AddFasterKvCache<T>方法:

代码语言:javascript
复制
var services = new ServiceCollection();
// use AddFasterKvCache<string>
services.AddFasterKvCache<string>(options =>
{
    // use MessagePack serializer
    options.UseMessagePackSerializer();
}, "MyKvCache");

var provider = services.BuildServiceProvider();

// get instance do something
var cache = provider.GetService<FasterKvCache<string>>();

配置

FasterKvCache 构造函数
代码语言:javascript
复制
public FasterKvCache(
    string name, // 如果存在多个Cache实例,定义一个名称可以隔离序列化等配置和磁盘文件
    ISystemClock systemClock, // 当前系统时钟,new DefaultSystemClock()即可
    FasterKvCacheOptions? options, // FasterKvCache的详细配置,详情见下文
    IEnumerable<IFasterKvCacheSerializer>? serializers, // 序列化器,可以直接使用MessagePack或SystemTextJson序列化器
    ILoggerFactory? loggerFactory) // 日志工厂 用于记录FasterKv内部的一些日志信息
FasterKvCacheOptions 配置项

对于 FasterKvCache,有着和 FasterKv 差不多的配置项,更详细的信息大家可以看FasterKv-Settings[4],下方是 FasterKvCache 的配置:

  • IndexCount:FasterKv 会维护一个 hash 索引池,IndexCount 就是这个索引池的 hash 槽数量,一个槽为 64bit。需要配置为 2 的次方。如 1024(2 的 10 次方)、 2048(2 的 11 次方)、65536(2 的 16 次方) 、131072(2 的 17 次方)。默认槽数量为 131072,占用 1024kb 的内存。
  • MemorySizeBit: FasterKv 用来保存 Log 的内存字节数,配置为 2 的次方数。默认为 24,也就是 2 的 24 次方,使用 16MB 内存。
  • PageSizeBit:FasterKv 内存页的大小,配置为 2 的次方数。默认为 20,也就是 2 的 20 次方,每页大小为 1MB 内存。
  • ReadCacheMemorySizeBit:FasterKv 读缓存内存字节数,配置为 2 的次方数,缓存内的都是热点数据,最好设置为热点数据所占用的内存数量。默认为 20,也就是 2 的 20 次方,使用 16MB 内存。
  • ReadCachePageSizeBit:FasterKv 读缓存内存页的大小,配置为 2 的次方数。默认为 20,也就是 2 的 20 次方,每页大小为 1MB 内存。
  • LogPath:FasterKv 日志文件的目录,默认会创建两个日志文件,一个以.log结尾,一个以obj.log结尾,分别存放日志信息和 Value 序列化信息,注意,不要让不同的 FasterKvCache 使用相同的日志文件,会出现不可预料异常默认为{当前目录}/FasterKvCache/{进程 Id}-HLog/{实例名称}.log
  • SerializerName:Value 序列化器名称,需要安装序列化 Nuget 包,如果没有单独指定Name的情况下,可以使用MessagePackSystemTextJson默认无需指定
  • ExpiryKeyScanInterval:由于 FasterKv 不支持过期删除功能,所以目前的实现是会定期扫描所有的 key,将过期的 key 删除。这里配置的就是扫描间隔。默认为 5 分钟
  • CustomStore:如果您不想使用自动生成的实例,那么可以自定义的 FasterKv 实例。默认为 null

所以 FasterKvCache 所占用的内存数量基本就是(IndexCount*64)+(MemorySize)+ReadCacheMemorySize,当然如果 Key 的数量过多,那么还有加上OverflowBucketCount * 64

容量规划

从上面提到的内容大家可以知道,FasterKvCache 所占用的内存字节基本就是(IndexCount * 64)+(MemorySize) + ReadCacheMemorySize + (OverflowBucketCount * 64)。磁盘的话就是保存了所有的数据+对象序列化的数据,由于不同的序列化协议有不同的大小,大家可以先进行测试。

内存数据存储到 FasterKv 存储引擎,每个 key 都会额外元数据信息,存储空间占用会有一定的放大,建议在磁盘空间选择上,留有适当余量,按实际存储需求的 1.2 - 1.5 倍预估。

如果使用内存存储 100GB 的数据,总的访问 QPS 不到 2W,其中 80%的数据都很少访问到。那么可以使用 【32GB 内存 + 128GB 磁盘】 存储,节省了近 70GB 的内存存储,内存成本可以下降 50%+。

性能

目前作者还没有时间将 FasterKvCache 和其它主流的缓存库进行比对,现在只对 FasterKvCache、EasyCaching.FasterKv 和 EasyCaching.Sqlite 做的比较。下面是 FasterKVCache 的配置,总占用约为 2MB。

代码语言:javascript
复制
services.AddFasterKvCache<string>(options =>
{
    options.IndexCount = 1024;
    options.MemorySizeBit = 20;
    options.PageSizeBit = 20;
    options.ReadCacheMemorySizeBit = 20;
    options.ReadCachePageSizeBit = 20;
    // use MessagePack serializer
    options.UseMessagePackSerializer();
}, "MyKvCache");

由于作者笔记本性能不够,使用 Sqlite 无法在短期内完成 100W、1W 个 Key 的性能测试,所以我们在默认设置下将数据集大小设置为 1000 个 Key,设置 50%的热点 Key。进行 100%读、100%写和 50%读写随机比较。

可以看到无论是读、写还是混合操作 FasterKvCache 都有着不俗的性能,在 8 个线程情况下,TPS 达到了惊人的 1600w/s

缓存

类型

线程数

Mean(us)

Error(us)

StdDev(us)

Gen0

Gen1

Allocated

fasterKvCache

Read

8

59.95

3.854

2.549

1.5259

7.02

NULL

fasterKvCache

Write

8

63.67

1.032

0.683

0.7935

3.63

NULL

fasterKvCache

Random

4

64.42

1.392

0.921

1.709

8.38

NULL

fasterKvCache

Read

4

64.67

0.628

0.374

2.5635

11.77

NULL

fasterKvCache

Random

8

64.80

3.639

2.166

1.0986

5.33

NULL

fasterKvCache

Write

4

65.57

3.45

2.053

0.9766

4.93

NULL

fasterKv

Read

8

92.15

10.678

7.063

5.7373

-

26.42 KB

fasterKv

Write

4

99.49

2

1.046

10.7422

-

49.84 KB

fasterKv

Write

8

108.50

5.228

3.111

5.6152

-

25.93 KB

fasterKv

Read

4

109.37

1.476

0.772

10.9863

-

50.82 KB

fasterKv

Random

8

119.94

14.175

9.376

5.7373

-

26.18 KB

fasterKv

Random

4

124.31

6.191

4.095

10.7422

-

50.34 KB

fasterKvCache

Read

1

207.77

3.307

1.73

9.2773

43.48

NULL

fasterKvCache

Random

1

208.71

1.832

0.958

6.3477

29.8

NULL

fasterKvCache

Write

1

211.26

1.557

1.03

3.418

16.13

NULL

fasterKv

Write

1

378.60

17.755

11.744

42.4805

-

195.8 KB

fasterKv

Read

1

404.57

17.477

11.56

43.457

-

199.7 KB

fasterKv

Random

1

441.22

14.107

9.331

42.9688

-

197.75 KB

sqlite

Read

8

7450.11

260.279

172.158

54.6875

7.8125

357.78 KB

sqlite

Read

4

14309.94

289.113

172.047

109.375

15.625

718.9 KB

sqlite

Read

1

56973.53

1,774.35

1,173.62

400

100

2872.18 KB

sqlite

Random

8

475535.01

214,015.71

141,558.14

-

-

395.15 KB

sqlite

Random

4

1023524.87

97,993.19

64,816.43

-

-

762.46 KB

sqlite

Write

8

1153950.84

48,271.47

28,725.58

-

-

433.7 KB

sqlite

Write

4

2250382.93

110,262.72

72,931.96

-

-

867.7 KB

sqlite

Write

1

4200783.08

43,941.69

29,064.71

-

-

3462.89 KB

sqlite

Random

1

5383716.10

195,085.96

129,037.28

-

-

2692.09 KB

总结

可以看到 FasterKvCache 有着不俗的性能,目前也在笔者朋友的项目使用上了,反馈不错,解决了他的缓存问题。由于现在还只是 1.0.0-rc1 版本,还有很多特性没有实现。可能有一些 BUG 还存在,欢迎大家试用和反馈问题。

Github 开源地址: https://github.com/InCerryGit/FasterKvCache

参考链接

https://developer.aliyun.com/article/740811

参考资料

[1]

FasterKv.Cache.Core: https://www.nuget.org/packages/FasterKv.Cache.Core

[2]

FasterKv.Cache.MessagePack: https://www.nuget.org/packages/FasterKv.Cache.MessagePack

[3]

FasterKv.Cache.SystemTextJson: https://www.nuget.org/packages/FasterKv.Cache.SystemTextJson

[4]

FasterKv-Settings: https://microsoft.github.io/FASTER/docs/fasterkv-basics/#fasterkvsettings

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 InCerry 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • FasterKvCache 架构
  • 如何使用它
    • 使用
      • 直接使用
      • Microsoft.Extensions.DependencyInjection
    • 配置
      • FasterKvCache 构造函数
      • FasterKvCacheOptions 配置项
  • 容量规划
  • 性能
  • 总结
  • 参考链接
    • 参考资料
    相关产品与服务
    文件存储
    文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档