- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
我们回顾一下上一篇文章中的内容,有一个朋友问我这样一个问题:
我的业务依赖一些数据,因为数据库访问慢,我把它放在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的架构,它核心使用的FasterKv,所以架构实际上和FasterKv一致,其原理比较复杂,所以笔者简化了原理图,大概就如下所示
FasterKv的热数据会在内存中,而全量的数据会持久化在磁盘中。这中间有一些缓存淘汰算法,所以大家看到这张图就能明白FasterKvCache适用和不适用哪些场景了.
笔者之前给EasyCaching提交了FasterKv的实现,但是由于有一些EasyCaching的高级功能在FasterKv上目前无法高性能的实现,所以单独创建了这个库,提供高性能和最基本的API实现;如果大家已经使用了EasyCaching,那么可以直接使用EasyCaching.FasterKv这个NuGet包.
如果使用需要FasterKvCache的话,只需要安装Nuget包,Nuget包不同的功能如下所示,其中序列化包可以只安装自己需要的即可.
软件包名 | 版本 | 备注 |
---|---|---|
FasterKv.Cache.Core | 1.0.0-rc1 | 缓存核心包,包含FasterKvCache主要的API |
FasterKv.Cache.MessagePack | 1.0.0-rc1 | 基于MessagePack的磁盘序列化包,它具有着非常好的性能,但是需要注意它稍微有一点使用门槛,大家可以看它的文档。 |
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实例可以添加任意类型:
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));
输出结果如下所示:
my cache sync
my cache async
2022/2/22 0:00:00
泛型版本的话性能最好,但是它只允许添加一个类型,否则代码将编译不通过:
// 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);
当然,我们也可以直接使用依赖注入的方式使用它,用起来也非常简单。按照通用和泛型版本的区别,我们使用不同的扩展方法即可:
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> 方法:
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>>();
public FasterKvCache(
string name, // 如果存在多个Cache实例,定义一个名称可以隔离序列化等配置和磁盘文件
ISystemClock systemClock, // 当前系统时钟,new DefaultSystemClock()即可
FasterKvCacheOptions? options, // FasterKvCache的详细配置,详情见下文
IEnumerable<IFasterKvCacheSerializer>? serializers, // 序列化器,可以直接使用MessagePack或SystemTextJson序列化器
ILoggerFactory? loggerFactory) // 日志工厂 用于记录FasterKv内部的一些日志信息
对于FasterKvCache,有着和FasterKv差不多的配置项,更详细的信息大家可以看 FasterKv-Settings ,下方是FasterKvCache的配置:
.log
结尾,一个以 obj.log
结尾,分别存放日志信息和Value序列化信息, 注意,不要让不同的FasterKvCache使用相同的日志文件,会出现不可预料异常 。 默认为{当前目录}/FasterKvCache/{进程Id}-HLog/{实例名称}.log 。 Name
的情况下,可以使用 MessagePack
和 SystemTextJson
。 默认无需指定 。 所以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.
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 。
最后此篇关于.NET性能优化-使用内存+磁盘混合缓存的文章就讲到这里了,如果你想了解更多关于.NET性能优化-使用内存+磁盘混合缓存的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在这段令人惊叹的视频 ( https://www.youtube.com/watch?v=udix3GZouik ) 中,Alex Blom 谈到了 Ember 在移动世界中的“黑客攻击”。 在 22
我们希望通过我们的应用收集使用情况统计信息。因此,我们希望在服务器端的某个地方跟踪用户操作。 就性能而言,哪个选项更合适: 在 App Engine 请求日志中跟踪用户操作。即为每个用户操作写入一个日
在针对对象集合的 LINQ 查询的幕后究竟发生了什么?它只是语法糖还是发生了其他事情使其更有效的查询? 最佳答案 您是指查询表达式,还是查询在幕后的作用? 查询表达式首先扩展为“普通”C#。例如: v
我正在构建一个简单的照片库应用程序,它在列表框中显示图像。 xaml 是:
对于基于 Web 的企业应用程序,使用“静态 Hashmap 存储对象” 和 apache java 缓存系统有何优缺点?哪一个最有利于性能并减少堆内存问题 例如: Map store=Applica
我想知道在性能方面存储类变量的最佳方式是什么。我的意思是,由于 Children() 函数,存储一个 div id 比查找所有其他类名更好。还是把类名写在变量里比较好? 例如这样: var $inne
我已经阅读了所有这些关于 cassandra 有多快的文章,例如单行读取可能需要大约 5 毫秒。 到目前为止,我不太关心我的网站速度,但是随着网站变得越来越大,一些页面开始需要相当多的查询,例如一个页
最近,我在缓存到内存缓存之前的查询一直需要很长时间才能处理!在这个例子中,它花费了 10 秒。在这种情况下,我要做的就是获得 10 个最近的点击。 我感觉它加载了所有 125,592 行然后只返回 1
我找了几篇文章(包括SA中的一些问题),试图找到基本操作的成本。 但是,我尝试制作自己的小程序,以便自己进行测试。在尝试测试加法和减法时,我遇到了一些问题,我用简单的代码向您展示了这一点
这个问题在这里已经有了答案: Will Java app slow down by presence of -Xdebug or only when stepping through code? (
我记得很久以前读过 with() 对 JavaScript 有一些严重的性能影响,因为它可能对范围堆栈进行非确定性更改。我很难找到最近对此的讨论。这仍然是真的吗? 最佳答案 与其说 with 对性能有
我们有一个数据仓库,其中包含非规范化表,行数从 50 万行到 6 多万行不等。我正在开发一个报告解决方案,因此出于性能原因我们正在使用数据库分页。我们的报告有搜索条件,并且我们已经创建了必要的索引,但
我有一条有效的 SQL 语句,但需要很长时间才能处理 我有一个 a_log 表和一个 people 表。我需要在 people 表中找到给定人员的每个 ID 的最后一个事件和关联的用户。 SELECT
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
通常当我建立一个站点时,我将所有的 CSS 放在一个文件中,并且一次性定义与一组元素相关的所有属性。像这样: #myElement { color: #fff; background-
两者之间是否存在任何性能差异: p { margin:0px; padding:0px; } 并省略最后的分号: p { margin:0px; padding:0px } 提前致谢!
我的应用程序 (PHP) 需要执行大量高精度数学运算(甚至可能出现一共100个数字) 通过这个论坛的最后几篇帖子,我发现我必须使用任何高精度库,如 BC Math 或 GMP,因为 float 类型不
我一直在使用 javamail 从 IMAP 服务器(目前是 GMail)检索邮件。 Javamail 非常快速地从服务器检索特定文件夹中的消息列表(仅 id),但是当我实际获取消息(仅包含甚至不包含
我非常渴望开发我的第一个 Ruby 应用程序,因为我的公司终于在内部批准了它的使用。 在我读到的关于 Ruby v1.8 之前的所有内容中,从来没有任何关于性能的正面评价,但我没有发现关于 1.9 版
我是 Redis 的新手,我有一个包含数百万个成员(member) ID、电子邮件和用户名的数据集,并且正在考虑将它们存储在例如列表结构中。我认为 list 和 sorted set 可能最适合我的情
我是一名优秀的程序员,十分优秀!