Skip to content

Commit

Permalink
add sample,benchmark and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
InCerryGit committed Nov 21, 2022
1 parent 669bfb3 commit da7683a
Show file tree
Hide file tree
Showing 11 changed files with 548 additions and 23 deletions.
23 changes: 23 additions & 0 deletions FasterKv.Cache.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FasterKv.Cache.MessagePack"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FasterKv.Cache.SystemTextJson", "src\FasterKv.Cache.SystemTextJson\FasterKv.Cache.SystemTextJson.csproj", "{F5940765-9AE9-44E9-8847-AE1693E6C92B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Items", "Items", "{E26D3646-0BC7-43AA-97D0-FC51693BFBFD}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{A63977CD-B7C0-4963-857D-2DFCA8A28110}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FasterKvCache.Sample.ConsoleApp", "sample\FasterKvCache.Sample.ConsoleApp\FasterKvCache.Sample.ConsoleApp.csproj", "{5D6CFE59-B3FB-418B-A833-29861CDCFA1D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmark", "benchmark", "{814EFFB2-4634-47EC-82AF-0BC0D03C193E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FasterKvCache.Benchmark", "benchmark\FasterKvCache.Benchmark\FasterKvCache.Benchmark.csproj", "{488C3EBF-043B-4F49-8295-727A89607ABD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -34,11 +47,21 @@ Global
{F5940765-9AE9-44E9-8847-AE1693E6C92B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5940765-9AE9-44E9-8847-AE1693E6C92B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5940765-9AE9-44E9-8847-AE1693E6C92B}.Release|Any CPU.Build.0 = Release|Any CPU
{5D6CFE59-B3FB-418B-A833-29861CDCFA1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D6CFE59-B3FB-418B-A833-29861CDCFA1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D6CFE59-B3FB-418B-A833-29861CDCFA1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D6CFE59-B3FB-418B-A833-29861CDCFA1D}.Release|Any CPU.Build.0 = Release|Any CPU
{488C3EBF-043B-4F49-8295-727A89607ABD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{488C3EBF-043B-4F49-8295-727A89607ABD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{488C3EBF-043B-4F49-8295-727A89607ABD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{488C3EBF-043B-4F49-8295-727A89607ABD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FFDB364D-A31F-44EB-AAED-2823F39E4D48} = {390A098B-4FD2-419A-A2F6-77B6D1B19BFB}
{72445807-EB50-41D1-BEFE-1805B4AD0408} = {FFC77C65-4B97-4712-9FA6-65055A0E3CE2}
{B04D6116-8FE3-439B-B95A-8617A7777558} = {390A098B-4FD2-419A-A2F6-77B6D1B19BFB}
{F5940765-9AE9-44E9-8847-AE1693E6C92B} = {390A098B-4FD2-419A-A2F6-77B6D1B19BFB}
{5D6CFE59-B3FB-418B-A833-29861CDCFA1D} = {A63977CD-B7C0-4963-857D-2DFCA8A28110}
{488C3EBF-043B-4F49-8295-727A89607ABD} = {814EFFB2-4634-47EC-82AF-0BC0D03C193E}
EndGlobalSection
EndGlobal
240 changes: 240 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# FasterKv.Cache

FasterKv.Cache是一个基于微软FasterKv封装的进程内混合缓存库(内存+磁盘)。FasterKv它可以承载大于机器内存的Key-Value数据库,并且有着远超其它内存+磁盘数据库的性能。不过使用起来比较繁琐,对新人不友好,于是FasterKv.Cache在它的基础上进行了一层封装,让我们能更简单的处理缓存。

![arch](docs/assets/arch.png)

| 适用场景 | 不适用场景 | 原因 |
| ------------------------------------------------------------ | -------------------------------------------- | ------------------------------------------------------------ |
| 缓存数据量大,并且有降低内存使用的需求 | 数据量小,或者有钞能力 | 如果数据量小和有钞能力,直接使用内存缓存才是性能最好的。 |
| 有明显的冷、热数据,数据能存储在内存中 | 没有冷热数据,完全随机访问 | 如果完全随机访问,意味着内存缓存将无效,每次读盘损耗会比较大 |
| 对于缓存没有非常严格的延时要求,几百us无所谓 | 对于缓存有高要求,不能接受波动 | 如果对于缓存有非常高的要求,几百微秒延时都不能忍受,那解决方案还是内存缓存 |
| 1.没有缓存非常大的数据。 2.有非常大的数据,但是经常访问,可以利用缓存。2.非常大的数据很少访问,对延时不敏感 | 有非常大的数据,且随机访问,并且延时非常敏感 | 如果有非常大的数据缓存,比如超过内存和ReadCache大小的,那么性能会变得比较差,想要解决它,只有钞能力。 |

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

## NuGet 软件包

| 软件包名 | 版本 | 备注 |
|-----------------------------------------------------------------------------------------------|-----------|-------------------------------------------------------------------------------------------|
| [FasterKv.Cache.Core](https://www.nuget.org/packages/FasterKv.Cache.Core) | 1.0.0-rc1 | 缓存核心包,包含FasterKvCache主要的API |
| [FasterKv.Cache.MessagePack](https://www.nuget.org/packages/FasterKv.Cache.MessagePack) | 1.0.0-rc1 | 基于MessagePack的磁盘序列化包,它具有着非常好的性能,但是需要注意它稍微有一点使用门槛,大家可以看它的文档。 |
| [FasterKv.Cache.SystemTextJson](https://www.nuget.org/packages/FasterKv.Cache.SystemTextJson) | 1.0.0-rc1 | 基于System.Text.Json的磁盘序列化包,它是.NET平台上性能最好JSON序列化封装,但是比MessagePack差。不过它易用性非常好,无需对缓存实体进行单独配置。 |

## 使用

### 直接使用

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

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

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

### 通用对象版本

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

```cs
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));
```

输出结果如下所示:

```sh
my cache sync
my cache async
2022/2/22 0:00:00
```

### 泛型版本

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

```cs
// 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

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

```cs
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>`方法:

```cs
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构造函数

```cs
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](https://microsoft.github.io/FASTER/docs/fasterkv-basics/#fasterkvsettings),下方是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`的情况下,可以使用`MessagePack``SystemTextJson`**默认无需指定**
* 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**

```cs
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**

| Provider | Type | ThreadNum | 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 |

## 其它

由于现在还只是1.0.0-rc1版本,还有很多特性没有实现。可能有一些BUG还存在,欢迎大家试用和反馈问题。
20 changes: 20 additions & 0 deletions benchmark/FasterKvCache.Benchmark/FasterKvCache.Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\FasterKv.Cache.Core\FasterKv.Cache.Core.csproj" />
<ProjectReference Include="..\..\src\FasterKv.Cache.MessagePack\FasterKv.Cache.MessagePack.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit da7683a

Please sign in to comment.