ABP - 缓存模块(1)
ABP - 缓存模块(1)
- 1. 与 .NET Core 缓存的关系和差异
- 2. Abp 缓存的使用
- 2.1 常规使用
- 2.2 非字符串类型的 Key
- 2.3 批量操作
- 3. 额外功能
1. 与 .NET Core 缓存的关系和差异
ABP 框架中的缓存系统核心包是 Volo.Abp.Caching ,而对于分布式缓存的支持,abp 官方提供了基于 Redis 的方案,需要安装 Volo.Abp.Caching.StackExchangeRedis 集成包。默认的情况下,在我们使用 ABP CLI 创建 ABP 框架模板项目的时候已经集成了这个包,我们不需要手动进行安装。
ABP 框架中的缓存系统在 ASP.NET Core的分布式缓存系统上进行扩展,从而使分布式缓存使用起来更加方便。理所当然的,ABP 框架的缓存系统兼容 ASP.NET Core 原生的分布式缓存使用方式,关于 ASP.NET Core 分布式缓存的使用,可以参考我之前的文章 ASP.NET Core - 缓存之分布式缓存,也可以看下官方文档 ASP.NET Core 中的分布式缓存 。
那么 ABP 框架中的缓存系统扩展了什么呢?ASP.NET Core 的分布式缓存又有哪些不便之处呢 ?ASP.NET Core 通过 IDistributedCache 抽象了分布式缓存的存取操作,但是有这样两个问题:
-
它对缓存值的存取是基于 byte 数组的,而不是对象。
这使得我们在使用分布式缓存的时候需要先将实例对象进行序列化/反序列化,之后再转码为 byte 数组。如果每个使用分布式缓存的地方都这么做将会有很多的重复冗余的代码,这是需要抽取封装的。
-
为了能实现多个应用公用缓存,达成分布式缓存的作用,它将所有的缓存项存放在同一个 Key 池之中。
这就可能会有问题,我们要特别注意缓存键的设置,如果开发人员不注意,很可能将一些缓存相互覆盖了,特别是共用缓存的应用比较多,或者多租户的情况下,这造成的问题可能很严重,而且很难排查。
ABP 框架的缓存系统扩展了通用的泛型接口 IDistributedCache<TCacheItem>
,泛型类型就是缓存值的类型,用于解决上面提到的问题:
-
该接口内部实现了对缓存对象 序列化/反序列化 的逻辑。 默认使用 JSON 序列化, 我们如果有需要可以替换 依赖注入 系统中 IDistributedCacheSerializer 服务的实现来覆盖默认的方式。
-
该接口会根据缓存中对象类型自动向缓存key添加 缓存名称 前缀。 默认缓存名是缓存对象的全类名(如果类名以CacheItem 结尾, 那么CacheItem 会被忽略,不应用到缓存名称上),开发人员也可以在缓存类上使用 CacheName 特性 设置缓存的名称.
-
如果是多租户应用的话,它会自动将当前的租户id添加到缓存键中, 以区分不同租户的缓存项 。 如果租户之间需要共享缓存对象, 我们可以在缓存类打上 IgnoreMultiTenancy 特性,声明当前缓存不区分租户。
-
允许为每个应用程序定义 全局缓存键前缀 , 不同的应用程序可以在共享的分布式缓存中拥有自己的隔离池.
-
它提供了 错误容忍 机制,对分布式缓存存取过程中的异常进行了处理,例如分布式缓存服务连接失败等,避免因缓存问题导致应用出错。
因为缓存本身就只是一种用于提升应用性能,提升用户体验的策略,如果因为分布式缓存的问题导致应用出错,业务逻辑无法继续执行,那就得不偿失了。缓存系统应该是,就算将其从应用中摘除,也不影响应用业务逻辑正常执行的东西,只是性能可能差了点。 -
它额外提供了 GetManyAsync 和 SetManyAsync 等方法, 支持对缓存的批量操作,可以显著提高批处理的性能。
2. Abp 缓存的使用
这里还是以控制台应用作为演示,Web 应用中使用比控制台更简单,因为 Web 应用中已经集成了缓存模块,不需要再自行引入。首先,通过以下命令生成一个 ABP 的控制台启动模板:
abp new AbpCacheSample -t console
之后,如果是使用基于内存的分布式缓存的话,只需要安装 Volo.Abp.Caching 包即可。
Install-package Volo.Abp.Caching
再之后,在项目的模块文件中添加模块依赖:
这里不需要再进行依赖关系的配置,因为在 AbpCacheModule 模块的初始化之中已经配置好了相关的内容,通过源码可以看到:
当然,你也可以更高效地在项目所在的文件夹使用以下命令,省掉对模块依赖的配置。
abp add-package Volo.Abp.Caching
如果是使用 Redis 分布式缓存的话,需要安装 Volo.Abp.Caching.StackExchangeRedis 集成包,这个包对 Microsoft.Extensions.Caching.StackExchangeRedis 进行了扩展,它简化了 Redis 缓存的配置,也是前面提到的 GetManyAsync 和SetManyAsync 等更加性能的方法的实现所在。
install-package Volo.Abp.Caching.StackExchangeRedis
同时,模块依赖改成以下这样:
同样的,模块初始化的时候也已经配置好 Redis 缓存使用的内容:
通过源码我们也可以看到,Redis 相应的配置是从配置文件中的 “Redis” 节点读取的,我们可以在appsettings.json 中添加以下内容启用 Redis 缓存:
{"Redis": {"IsEnabed": true, // 控制 Redis 分布式缓存是否启用"Configuration": "xxx.xxx.xxx.xxx:6379,password=123456" // Redis 连接字符串}
}
这里其实就是通过配置信息中的 “Redis:Configuration” 节点配置 RedisCacheOpions 选项,这个选项是微软标准的 Redis 缓存支持包中的,如果有需要,我们可以在代码中通过以下方式对该选项进行配置:
[DependsOn(typeof(AbpAutofacModule),typeof(AbpCachingStackExchangeRedisModule)
)]
public class AbpCacheSampleModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){Configure<RedisCacheOptions>(option =>{// ...});}
}
2.1 常规使用
首先我们定义缓存类:
/// <summary>
/// 缓存中,会以缓存类的全类名作为建,如果类名以 CacheItem 结尾,CacheItem 会被忽略
/// </summary>
// [CacheName("DateTime")] 也可以通过 CacheName 特性设置缓存键
public class DateTimeCacheItem
{public DateTime Now { get; set; }public string Name { get; set; }
}
之后,只需要在要用到的服务中注入 IDistributedCache<CacheItem>
泛型接口即可:
public class HelloWorldService : ITransientDependency
{public const string CacheKey = nameof(HelloWorldService);private readonly IDistributedCache<DateTimeCacheItem> _distributedCache;public HelloWorldService(IDistributedCache<DateTimeCacheItem> distributedCache){_distributedCache = distributedCache;}public async Task SayHelloAsync(){// 常规的 IDisctributedCache 的同名方法,不过 ABP 框架中进行了扩展// 使其支持泛型,可以直接存取对象//var cacheValue = await _distributedCache.GetAsync(CacheKey);//if (cacheValue == null)//{// cacheValue = new DateTimeCacheItem// {// Name = CacheKey,// Now = DateTime.Now,// };// await _distributedCache.SetAsync(// CacheKey, // 缓存键,最终的键会是 缓存名称(缓存类全类名去除CacheItem,或CacheName特性设置的名称) + 这里的键// cacheValue, // 直接存取对象,而不用自己序列化/反序列化 以及转码 // new DistributedCacheEntryOptions // 一样可以通过选项设置缓存策略// {// SlidingExpiration = TimeSpan.FromMinutes(1)// });//}// ABP 框架新增的方法var cacheValue = await _distributedCache.GetOrAddAsync(CacheKey,async () =>{return await Task.FromResult(new DateTimeCacheItem(){Name = CacheKey,Now = DateTime.Now});},() => new DistributedCacheEntryOptions{SlidingExpiration= TimeSpan.FromMinutes(1)});Console.WriteLine(JsonSerializer.Serialize(cacheValue));Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));}
}
2.2 非字符串类型的 Key
除了可以使用 string 类型作为缓存键之外,我们还可以通过 IDistributedCache<CacheKey, CacheItem> 接口使用其他类型,甚至复杂类型作为缓存键,使用复杂类型作为缓存键的时候需要重写 ToString() 方法。
public class MyCacheKey{public int Id { get; set; }public string Name { get; set; }public override string ToString(){return Id + Name;}}public class HelloWorldService : ITransientDependency
{private readonly IDistributedCache<DateTimeCacheItem, int> _distributedCacheKeyInt;private readonly IDistributedCache<DateTimeCacheItem, MyCacheKey> _distiributedCacheKey;public HelloWorldService(IDistributedCache<DateTimeCacheItem, int> distributedCacheKeyInt,IDistributedCache<DateTimeCacheItem, MyCacheKey> distiributedCacheKey){_distributedCache = distributedCache;}public async Task SayHelloAsync(){_ = await _distributedCacheKeyInt.GetOrAddAsync(1,async () =>{return await Task.FromResult(new DateTimeCacheItem{Name = "1",Now = DateTime.Now});},() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) });_ = await _distiributedCacheKey.GetOrAddAsync(new MyCacheKey { Id = 1, Name = "MyKey" },async () =>{return await Task.FromResult(new DateTimeCacheItem{Name = "1",Now = DateTime.Now});},() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) });}
}
2.3 批量操作
批量进行缓存存取操作的方式如下:
_ = await _distributedCache.GetOrAddManyAsync(new List<string> { "Key1", "Key2" }, async keys =>
{return await Task.FromResult(keys.Select(k => new KeyValuePair<string, DateTimeCacheItem>(k, new DateTimeCacheItem { Name = k, Now = DateTime.Now })).ToList());
},
() => new DistributedCacheEntryOptions { SlidingExpiration = TimeSpan.FromSeconds(10) });
ABP的分布式缓存接口定义了以下批量操作方法,当你需要在一个方法中调用多次缓存操作时,这些方法可以提高性能
- SetManyAsync 和 SetMany 方法可以用来向缓存中设置多个值.
- GetManyAsync 和 GetMany 方法可以用来从缓存中获取多个值.
- GetOrAddManyAsync 和 GetOrAddMany 方法可以用来从缓存中获取并添加缺少的值.
- RefreshManyAsync 和 RefreshMany 方法可以来用重置多个值的滚动过期时间.
- RemoveManyAsync 和 RemoveMany 方法可以用来从缓存中删除多个值.
这些不是标准的ASP.NET Core缓存方法, 所以其他的分布式缓存方案可能不支持。而 ABP 通过 Volo.Abp.Caching.StackExchangeRedis 实现了 Redis 分布式缓存下的批量操作。如果采用了其他方案实现分布式缓存,而提供程序不支持的情况下,会回退到循环调用 SetAsync 和 GetAsync 方法。
以上就是 ABP 框架中缓存系统的基本使用。除此之外,ABP 框架提供了 AbpDistributedCacheOptions 选项用于配置一些缓存策略,可用属性:
- HideErrors (bool, 默认: true): 启用/禁用隐藏从缓存服务器写入/读取值时的错误.
- KeyPrefix (string, 默认: null): 如果你的缓存服务器由多个应用程序共同使用, 则可以为应用程序的缓存键设置一个前缀. 在这种情况下, 不同的应用程序不能覆盖彼此的缓存内容.
- GlobalCacheEntryOptions (DistributedCacheEntryOptions): 用于设置保存缓内容却没有指定选项时, 默认的分布式缓存选项 (例如 AbsoluteExpiration 和 SlidingExpiration). SlidingExpiration的默认值设置为20分钟.
3. 额外功能
另外,还有 ABP 框架下的缓存系统还有以下一些功能:
-
错误处理
当为你的对象设计缓存时, 通常会首先尝试从缓存中获取值. 如果在缓存中找不到该值, 则从来源查询对象. 它可能在数据库中, 或者可能需要通过HTTP调用远程服务器.
在大多数情况下, 你希望容忍缓存错误; 如果缓存服务器出现错误, 也不希望取消该操作. 相反, 你可以默默地隐藏(并记录)错误并从来源查询. 这就是ABP框架默认的功能.
ABP的分布式缓存异常处理, 默认记录并隐藏错误,有一个全局修改该功能的选项.所有的·IDistributedCache· (和 ·IDistributedCache<TCacheItem, TCacheKey>·)方法都有一个可选的参数 hideErrors, 默认值为null. 如果此参数设置为null, 则全局生效, 否则你可以选择单个方法调用时隐藏或者抛出异常.
-
工作单元级别的缓存
分布式缓存服务提供了一个有趣的功能. 假设你已经更新了数据库中某本书的价格, 然后将新价格设置到缓存中, 以便以后使用缓存的值. 如果设置缓存后出现异常, 并且更新图书价格的事务被回滚了, 该怎么办?在这种情况下, 缓存值是错误的.
IDistributedCache<…>方法提供一个可选参数, considerUow, 默认为false. 如果将其设置为true, 则你对缓存所做的更改不会应用于真正的缓存存储, 而是与当前的工作单元关联. 你将获得在同一工作单元中设置的缓存值, 但仅当前工作单元成功时更改才会生效.
-
可替换的键、值处理方式
-
IDistributedCacheSerializer
IDistributedCacheSerializer 服务用于序列化和反序列化缓存内容. 默认实现是 Utf8JsonDistributedCacheSerializer 类, 它使用 IJsonSerializer 服务将对象转换为 JSON, 反之亦然. 然后, 它使用 UTC8 编码将 JSON 字符串转换为分布式缓存接受的字节数组.
如果你想实现自己的序列化逻辑, 可以自己实现并替换此服务.
-
IDistributedCacheKeyNormalizer
默认情况下, IDistributedCacheKeyNormalizer 是由 DistributedCacheKeyNormalizer 类实现的. 它将缓存名称、应用程序缓存前缀和当前租户id添加到缓存键中. 如果需要更高级的键规范化, 可以自己实现并替换此服务。
-
参考文章:
ABP 官方文档 - 缓存
ABP 系列总结:
目录:ABP 系列总结
上一篇:ABP - 依赖注入(2)
下一篇:ABP - 缓存模块(2)
相关文章:

ABP - 缓存模块(1)
ABP - 缓存模块(1) 1. 与 .NET Core 缓存的关系和差异2. Abp 缓存的使用2.1 常规使用2.2 非字符串类型的 Key2.3 批量操作 3. 额外功能 1. 与 .NET Core 缓存的关系和差异 ABP 框架中的缓存系统核心包是 Volo.Abp.Caching ,而对于分布式缓存…...

二、点灯基础实验
嵌入式基础实验第一个就是点灯,地位相当于编程界的hello world。 如下为LED原理图,要让相应LED发光,需要给I/O口设置输出引脚,低电平,二极管才会导通 2.1 打开初始工程,编写代码 以下会实现BLINKY常亮&…...

双端队列实战 实现滑动窗口 用LinkedList的基类双端队列Deque实现 洛谷[P1886]
集合 关系 介绍 Deque 是一个接口 LinkedList 是这个接口的实现类 题目 输入输出 滑动窗口 基于双端队列实现 Deque<Integer> deque new LinkedList<>(); 滑动窗口代码 public static List<Integer> maxSlidingWindow(int[] nums, int k) {List<Int…...

HTML<img>标签
例子 如何插入图片: <img src"img_girl.jpg" alt"Girl in a jacket" width"500" height"600"> 下面有更多“自己尝试”的示例。 定义和用法 该<img>标签用于在 HTML 页面中嵌入图像。 从技术上讲&#x…...

【网络 MAC 学习专栏 -- 如何理解 PHY 的 Link Up】
请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 OverviewClause 22/Clause 45Clause 22Clause 45 PHY Link 状态的软件实现 转自: 开心果 Need Car 2022年10月20日 09:50 上海 Overview PHY…...

Linux虚拟机安装与FinalShell使用:探索Linux世界的便捷之旅
文章目录 软件准备安装 VMware 虚拟机下载CentOS 光盘镜像文件选择适合的 CentOS 版本选择合适的镜像文件 本教程工具版本 第一部分:安装 Linux 虚拟机1. 启动 VMware 并创建新虚拟机2. 默认硬件兼容性设置3. 安装操作系统的设置4. 选择操作系统类型与版本5. 为虚拟…...
Mixly米思齐1.0 2.0 3.0 软件windows版本MAC苹果电脑系统安装使用常见问题与解决
Mixly软件应用常见问题 Mixly米思齐编译或上传报错? 1、软件安装与驱动(Mixly1-2) 1-1 Windows版本 软件及驱动可以在Mixly群(QQ群号621937623)的群文件夹中找到,或到Mixly在线软件下载链接中重新下安装…...
vben5 admin ant design vue如何使用时间范围组件RangePicker
本文参考:https://pusdn-dev.feishu.cn/wiki/VF4hwBAUliTE6TkUPKrcBNcZn9f?fromfrom_copylink 由PUSDN整理发行,收录时请保留PUSDN。 前端组件专题 年月日时间范围表单回显RangePicker 推荐使用多个字段存储,不推荐用英文逗号拼接时间&am…...

Kafka 日志存储 — 文件目录及日志格式
日志存储机制是Kafka实现高吞吐量和持久化能力的关键。 1 文件目录布局 图 主题与日志文件的关系 Kafka中的消息持久化为日志文件。一个副本对应一个日志。日志文件在broker上是命名形式为<topic>-<partition>的文件夹。例如,主题par3第3分区在某个副…...

故障诊断 | BWO白鲸算法优化KELM故障诊断(Matlab)
目录 效果一览文章概述BWO白鲸算法优化KELM故障诊断一、引言1.1、研究背景及意义1.2、故障诊断技术的现状1.3、研究目的与内容二、KELM基本理论2.1、KELM模型简介2.2、核函数的选择2.3、KELM在故障诊断中的应用三、BWO白鲸优化算法3.1、BWO算法基本原理3.2、BWO算法的特点3.3、…...

一文读懂AI Agent 智能体
一、什么是智能体Agent? 在计算机科学和人工智能领域,智能体(Agent) 是一个抽象的概念,用于描述能够感知环境、执行行动并以此对环境产生影响的实体。智能体通常被设计成具有自主性和适应性,能够在不确定、…...
《 C++ 点滴漫谈: 二十二 》操作符炼金术:用C++ operator重塑代码美学
摘要 C 的 operator 关键字和操作符重载是语言的核心特性之一,使开发者能够扩展内置操作符以适应自定义类型,从而实现更高效、直观的代码表达。本文全面解析了 operator 关键字的基本概念、支持重载的操作符范围及其使用场景,详细介绍了操作…...

通信协议之多摩川编码器协议
前言 学习永无止境!本篇是通信协议之多摩川编码器协议,主要介绍RS485硬件层以及软件层帧格式。 注:本文章为学习笔记,部分图片与文字来源于网络/应用手册,如侵权请联系!谢谢! 一、多摩川协议概述…...

新星杯-ESP32智能硬件开发--ESP32的I/O组成-系统中断矩阵
本博文内容导读📕🎉🔥 ESP32开发板的中断矩阵、功能描述与实现、相关API和示例程序进行介绍 ESP32中断矩阵将任一外部中断源单独分配到每个CPU的任一外部中断上,提供了强大的灵活性,能适应不同的应用需求。 ESP32中断主…...
4329 树的连边II
通过链式前向星来求树的直径 主要包括:链式前向星的初始化,遍历,使用 #include<bits/stdc.h> using namespace std; using lllong long; const int N1e59; int n,head[N],to[N<<1],nx[N<<1],cnt0; int ans0; int dp[N][2…...

Spring的Bean详解=Bean别名+作用范围+使用场景
目录 Bean的别名:id和name的地位等同 Bean的作用范围:scope单例与非单例 Bean的使用场景:什么时候交给容器?什么时候不交? Bean的别名实践(含代码) 如果看不懂下面的,例如不知道i…...
聊一聊如何适应AI时代
我的工作行业就不提了,处于AI的前沿阵地之一,AI的进步非常惊艳,虽然我对AI持有开放态度,但也恐惧,因为我的进步跟不上它迭代的速度。 AI能涉及的行业:辅助驾驶、医疗诊断、数据分析、文稿生成、工业控制...…...

dl学习笔记:(4)简单神经网络
(1)单层正向回归网络 bx1x2z100-0.2110-0.05101-0.051110.1 接下来我们用代码实现这组线性回归数据 import torch x torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype torch.float32) z torch.tensor([-0.2, -0.05, -0.05, 0.1]) w torch.…...

电商项目高级篇08-springCache
电商项目高级篇08-springCache 1、整合springCache2、Cacheable细节设置 1、整合springCache 1、引入依赖 <!--引入springCache--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifa…...
4.1 AI 大模型应用最佳实践:如何提升 GPT 模型使用效率与质量
AI 大模型应用最佳实践:如何提升 GPT 模型使用效率与质量 随着人工智能技术的不断进步,GPT系列大模型已经成为了自然语言处理领域的核心工具。无论是在文本生成、对话系统,还是内容创作等领域,GPT模型都展现出了强大的能力。然而,要高效、精确地使用这些模型,仍然需要一…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...