ABP - 事件总线之分布式事件总线
ABP - 事件总线之分布式事件总线
- 1. 分布式事件总线的集成
- 1.2 基于 RabbitMQ 的分布式事件总线
- 2. 分布式事件总线的使用
- 2.1 发布
- 2.2 订阅
- 2.3 事务和异常处理
- 3. 自己扩展的分布式事件总线实现
事件总线可以实现代码逻辑的解耦,使代码模块之间功能职责更清晰。而分布式事件总线的功能不止这些,它允许我们通过消息队列进行中转,发布和订阅跨应用/服务边界传输的事件,经常被用作微服务或不同应用程序之间异步发送和接收消息的手段,属于分布式应用通讯的方式之一。
分布式事件总线依赖于 消息队列 中间件,ABP 框架中提供了4种开箱即用的提供程序,我们也可以基于抽象的接口自行实现分布式事件总线提供程序,已有的四种分别适用于 RabbitMQ、Kafka、Rebus 等消息队列,还有一种是默认实现:进程内的分布式事件总线,它允许我们在没有接入消息队列时,也能够编写与分布式体系结构兼容的代码,方便日后可能的微服务拆分,这时它的工作方式与本地事件总线一样,整体的设计思想就和微软的分布式缓存一样。
1. 分布式事件总线的集成
以下的演示还是基于控制台程序,分布式事件总线不会默认集成在 ABP 启动模板之中,需要我们自行集成,Web 应用的集成方式也是一样的。
通过以下命令创建一个控制台程序启动模板:
abp new AbpDistributeEventBusSample -t console
之后再打开解决方案,由于分布式事件总线是在不同应用程序之间进行通讯的,所以还需要再创建一个控制台项目进行演示,将解决方案中的项目复制一份即可。
本地分布式事件总线
首先讲一下进程内的事件总线的集成,这个还是有些必要的,如果有考虑后续进行微服务拆分的情况下,前期对于事件总线的使用可以基于这个进行开发。当然并不是说本地事件总线就不推荐使用,从我自己的日常工作经验中,很多时候还是分布式事件总线和本地事件总线搭配使用的。
首先,分布式事件总线的核心依赖包为 Volo.Abp.EventBus,和本地事件总线一样。我们在需要集成的项目的根目录下,通过以下命令进行集成:
abp add-package Volo.Abp.EventBus
由于是本地分布式事件总线,所以这种方式下是没办法跨进程通讯的,使用方式和上一篇讲的本地事件总线类似,不过使用的时候不再通过 ILocalEventBus接口,而是通过 IDistributedEventBus 接口,主要是用于业务逻辑的解耦,同时也为后续可能的分布式拆分做好准备。具体的使用方式下面细讲。
1.2 基于 RabbitMQ 的分布式事件总线
ABP 框架提供了三种开箱即用的分布式事件总线提供程序,分别对应 RabbitMQ、Kafka、Rebus,通过结合第三方消息队列实现真正基于消息跨进程通讯的分布式事件总线,这里主要讲一下基于 RabbitMQ 的方式,其他方式用法类似。
首先,基于 RabbitMQ 的分布式事件总线需要安装 Volo.Abp.EventBus.RabbitMQ 驱动程序包。可通过一下方式安装:
abp add-package Volo.Abp.EventBus.RabbitMQ
上面创建的两个工程都要安装,因为我们要演示两个进程间的通讯。
之后,需要部署 RabbitMQ 消息队列,这里我通过 docker 快速启动一个带有管理平台的 RabbitMQ,命令如下:
docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq rabbitmq:management
RabbitMQ 默认用户密码为:guest / guest,这里只是用于测试就直接使用了。生产环境中大家最好将默认用户禁用,另行创建自己的用户。RabbitMQ 相关的更详细的使用和配置这里就不细讲了,详细内容可见 [[2.1 RabbitMQ基本概念]] 系列文章。
然后,添加分布式事件总线相应的配置,我们可以在 appsettings.json 文件中添加以下配置节点:
"RabbitMQ": {"Connections": {// 这里的配置支持 RabbitMQ 官方 sdk 中的 ConnectionFactory 的任意属性的配置"Default": {"HostName": "localhost", // rabbitmq 地址,集群环境下多个ip用逗号分隔"Port": "5672", // rabbbitmq 端口,默认5672"UserName": "guest","Password": "guest"}// 允许配置多个 rabbitmq 连接,但只能有一个用于事件总线//"Second": {// "HostName": "xxx.xxx.xxx.xxx", // rabbitmq 地址,集群环境下多个ip用逗号分隔// "Port": "5672" // rabbbitmq 端口,默认5672//}},"EventBus": {"ClientName": "MyClientName", // 用于事件总线的队列名"ExchangeName": "MyExchangeName", // 用于事件总线的交换机名称// "ConnectionName": "Default" // 配置多个连接时,指定用于事件总线的 RabbitMQ 连接,默认是 Default}}
以上配置,最后都会被转换为 AbpRabbitMqOptions 和 AbpRabbitMqEventBusOptions,所以我们也可以直接在代码中对这两个选项进行配置:
Configure<AbpRabbitMqOptions>(options =>
{options.Connections.Default.UserName = "guest";options.Connections.Default.Password = "guest";options.Connections.Default.HostName = "localhost";options.Connections.Default.Port = 5672;
});Configure<AbpRabbitMqEventBusOptions>(options =>
{options.ClientName = "TestApp1";options.ExchangeName = "TestMessages";
});
两种方式选择一种即可,如果两种方式同时使用,代码配置优先于配置文件。解决方案的两个项目都需要进行配置,进行通讯的两个项目需要连接到同一个队列。
完成上面的配置之后,启动应用,即可看到 RabbitMQ 中创建了我们配置的交换机和队列:
2. 分布式事件总线的使用
2.1 发布
事件发布需要一个事件对象,官方将之称为 Eto(事件传输对象),这是一个普通类,用于保存和事件相关的数据,一般以 Eto 作为后缀。就算一个事件不需要传输任何数据,也必须创建一个空类,这和上一章的本地事件总线是一样的,由于在分布式事件触发之后,事件对象会被序列化传输到消息队列中,所以事件对象应避免循环引用、多态、私有setter,并提供默认(空)构造函数,如果你有其他的构造函数(虽然某些序列化器可能会正常工作)。 下面是一个用于测试的 Eto 对象的定义。
[EventName("helloEvent")]
public class HelloEto
{public string Who { get; set; }public DateTime When { get; set; }public string ToWho { get; set; }
}
默认情况下,事件名将事件名称将是事件类的全名,我们可以通过 EventNameattribute 特性指定事件名称。
分布式事件的发布通过 IDistributedEventBus 接口,只需将其注入到相应的类中使用即可,使用方式和本地事件总线一样。
public class HelloWorldService : ITransientDependency
{private readonly IDistributedEventBus _distributedEventBus;public HelloWorldService(IDistributedEventBus distributedEventBus){_distributedEventBus = distributedEventBus;}public async Task SayHelloAsync(){await _distributedEventBus.PublishAsync(new HelloEto{Who = "Jerry",When = DateTime.Now,ToWho = "Jack"});}
}
以上代码写在 AbpDistributeSample 项目中,这里是事件发布的进程。
2.2 订阅
事件的订阅也和本地事件总线类似,这里通过实现了 IDistributedEventHandler<TEvent>
接口的处理器来处理事件,当前像上一章本地事件总线中讲到的,我们也可以通过 IDistributedEventBus 来自己订阅事件。
public class HelloDistribuedEventHandler : IDistributedEventHandler<HelloEto>, ITransientDependency
{public Task HandleEventAsync(HelloEto eventData){Console.WriteLine($"{eventData.Who} Say Hello To {eventData.ToWho} at { eventData.When.ToString("yyyy-MM-dd HH:mm:ss") }");return Task.CompletedTask;}
}
一个事件处理程序可以同时处理多种事件,只需要实现多个针对不同 ETO 的 IDistributedEventHandler 泛型接口即可。
之后通过 vs 设置多项目启动:
应用启动之后,可以看到控制台的输出如下,一个简单的基于消息队列的跨进程通讯已经完成:
同时在 RabbitMQ 上可以看到已经连接上来了2个消费者,每个进程既作为生产者,也作为消费者:
通过源码可以看到,分布式事件总线中会进行初始化,其实就是根据配置连接了 RabbitMQ 队列,并创建了一个消费者自动订阅消息队列上的事件,当接受到消息之后会从消息中获取消息的类型和具体的数据,触发消息执行处理程序,如果事件处理程序成功执行(没有抛出任何异常),它将向消息代理发送确认(ACK)。
这里的 EventTypes 其实就是维护消息类型和它的类名的对应关系的一个字典,在 SubscribeHandlers 中初始化消息类型和执行器对应关系的时候维护的,这个就和前一篇的本地事件总线大同小异了。
而消息的发布过程就更简单了,也就是将消息序列化,发送到 RabbitMQ 队列中。
ABP 分布式事件总线从 5.0 版本开始,还加入了 Inbox 、Outbox 机制,我们可以通过 AbpDistributedEventBusOptions 选项进行配置,例如:
Configure<AbpDistributedEventBusOptions>(option =>
{option.Inboxes.Configure(config => config.UseDbContext<TDbContext>());option.Outboxes.Configure(config => config.UseDbContext<TDbContext>());
});
从配置方式也可以看出,这里是借助了数据库的,实际上 Inbox 就是从消息队列接收到消息之后,将消息先存入数据库中,没有直接执行。
而在应用启动的时候,消息队列模块会注册一个 InboxProcessManager,实际上这就是一个后台工作者,这里面又用到 IInboxProcessor
在 InboxProcessor 中再通过定时器,每个一段时间从数据库中读取消息真正地去执行,并且将数据库中的记录删除。
OutBox 的机制也是一样的,这样做大概就是起到缓冲的作用,避免短时间内大量的消息对消息队列或者我们的消费者造成冲击导致应用崩溃,而是将这些消息的发送、执行均匀地处理。
2.3 事务和异常处理
分布式事件总线默认实现是 LocalDistributedEventBus
,在没有集成 RabbitMQ 等第三方消息队列中间件,并且没有使用发件箱/收件箱模式的情况,事件发布和事件订阅是在同一个进程的。如果当前事件发布的模块使用了工作单元,事件总线是在和发布事件的同一工作单元范围内执行事件处理程序,如果事件处理程序抛出异常,那么相关的工作单元(数据库事务)将被回滚。这样,我们的应用程序逻辑和事件处理逻辑就具有事务性(原子)。如果想忽略事件处理程序中的错误,则必须在处理程序中使用try-catch
块,并且不应该重新抛出异常。
当我们切换到真正的分布式事件总线提供程序时,事件处理程序将在不同的进程/应用程序中执行。在这种情况下,实现事务性事件发布的唯一方法是使用发件箱/收件箱模式。这篇文章上面的内容简单地提了一下发件箱/收件箱,之后还会有文章详细梳理它的工作模式。
如果在未真正使用分布式事件总线的情况下,想在工作单元中立即发布事件,而不是等到工作单元中的逻辑执行完成之后再发布,可以在使用IDistributedEventBus.PublishAsync
方法时将onUnitOfWorkComplete
设置为false
。如果接入 RabbitMQ 等第三方消息队列实现了分布式事件总线,想要立即发布事件,则还得将 useOutbox
设置为 false。
3. 自己扩展的分布式事件总线实现
要注意的一点是,ABP 框架的分布式事件总线只能配置使用一个队列,这就意味着如果一个进程需要和多个进程进行通讯时,是无法通过不同的队列进行区分的。当多个进程都连接到同一个队列中时,我们无法控制一个进程发送的消息会被哪个进程所消费,无法单独通知某一个消费者,这是需要注意的。
有一种折中的方式,那就是如果一个事件只想发送给某一个进程,那就只在这个进程中实现其处理程序,其他没有实现处理程序的进程接收到消息之后,因为没有处理会抛出异常,消息重新返回消息队列中。这种方式依旧是存在一些问题,只是将工作中的一点经验供大家参考一下。
public interface IWantDistributedEventBus
{Task PublishAsync(Type eventType, object eventData, string queueName);
}public class RabbitMqWantDistributedEventBus : IWantDistributedEventBus, ISingletonDependency
{protected ILocalEventBus LocalEventBus { get; }protected IConnectionPool ConnectionPool { get; }protected WantRabbitMqEventBusOptions WantRabbitMqEventBusOptions { get; }protected AbpLocalEventBusOptions AbpLocalEventBusOptions { get; }protected IRabbitMqSerializer Serializer { get; }protected IRabbitMqMessageConsumer Consumer { get; private set; }protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; }protected ConcurrentDictionary<string, Type> EventTypes { get; }public RabbitMqWantDistributedEventBus(IConnectionPool connectionPool,IOptions<WantRabbitMqEventBusOptions> options,IOptions<AbpLocalEventBusOptions> localEventBusOptions,IRabbitMqSerializer serializer,IRabbitMqMessageConsumerFactory rabbitMqMessageConsumerFactory,ILocalEventBus localEventBus){ConnectionPool = connectionPool;WantRabbitMqEventBusOptions = options.Value;Serializer = serializer;MessageConsumerFactory = rabbitMqMessageConsumerFactory;LocalEventBus = localEventBus;AbpLocalEventBusOptions = localEventBusOptions.Value;EventTypes = new ConcurrentDictionary<string, Type>();}/// <summary>/// 队列消费者初始化/// </summary>public void Initialize(){foreach (var queueName in WantRabbitMqEventBusOptions.ConsumeQueueNames){Consumer = MessageConsumerFactory.Create(new ExchangeDeclareConfiguration(WantRabbitMqEventBusOptions.ExchangeName,type: "direct",durable: true),new QueueDeclareConfiguration(queueName,durable: true,exclusive: false,autoDelete: false),WantRabbitMqEventBusOptions.ConnectionName);Consumer.BindAsync(queueName);Consumer.OnMessageReceived(ProcessEventAsync);}LoadEventTypes(AbpLocalEventBusOptions.Handlers);}/// <summary>/// 消息消费逻辑/// </summary>/// <param name="message">从队列接收到的消息</param>/// <returns></returns>private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea){var msgData = (MessageData)Serializer.Deserialize(ea.Body.ToArray(), typeof(MessageData));var eventName = msgData.Type;var eventType = EventTypes.GetOrDefault(eventName);// eventType为空, 即不存在类型对应的Handler,消息不应该被消费if (eventType == null){// return;throw new NotFoundHandlerException($"不存在{eventName}消息Handler!");}// 通过消息类型转发LocalHandler进行具体逻辑处理var eventData = Serializer.Deserialize(Serializer.Serialize(msgData.Data), eventType);await LocalEventBus.PublishAsync(eventType, eventData);}/// <summary>/// 根据现有注册的Handler构建消息类型集合/// </summary>/// <param name="handlers">当前应用中Handler类型集合</param>/// <returns></returns>public Task LoadEventTypes(ITypeList<IEventHandler> handlers){foreach (var handler in handlers){var interfaces = handler.GetInterfaces();foreach (var @interface in interfaces){if (!typeof(IEventHandler).GetTypeInfo().IsAssignableFrom(@interface)){continue;}var genericArgs = @interface.GetGenericArguments();if (genericArgs.Length == 1){var eventTypeName = genericArgs[0].FullName;if (!EventTypes.ContainsKey(eventTypeName)){EventTypes[eventTypeName] = genericArgs[0];}}}}return Task.CompletedTask;}/// <summary>/// 发布消息/// </summary>/// <param name="eventType">消息类型</param>/// <param name="eventData">消息内容</param>/// <param name="queueName">队列名称</param>/// <returns></returns>public Task PublishAsync(Type eventType, object eventData, string queueName){var msg = new MessageData { Type = eventType.FullName, Data = eventData };var body = Serializer.Serialize(msg);using (var channel = ConnectionPool.Get(WantRabbitMqEventBusOptions.ConnectionName).CreateModel()){channel.ExchangeDeclare(WantRabbitMqEventBusOptions.ExchangeName,"direct",durable: true);var properties = channel.CreateBasicProperties();properties.DeliveryMode = RabbitMqConsts.DeliveryModes.Persistent;var queue = channel.QueueDeclare(queueName, durable: true, exclusive: false, autoDelete: false);channel.QueueBind(queueName, WantRabbitMqEventBusOptions.ExchangeName, queueName);channel.BasicPublish(exchange: WantRabbitMqEventBusOptions.ExchangeName,routingKey: queueName,mandatory: true,basicProperties: properties,body: body);}return Task.CompletedTask;}
}public class WantRabbitMqEventBusOptions
{public string ConnectionName { get; set; }public string ClientName { get; set; }public string ExchangeName { get; set; }public IList<string> ConsumeQueueNames { get; }public WantRabbitMqEventBusOptions(){ConsumeQueueNames = new List<string>();}
}public class MessageData
{public string Type { get; set; }public object Data { get; set; }
}[DependsOn(typeof(AbpRabbitMqModule))]
[DependsOn(typeof(AbpEventBusModule))]
public class WantAbpEventBusRabbitMqModule : AbpModule
{public override void ConfigureServices(ServiceConfigurationContext context){var configuration = context.Services.GetConfiguration();Configure<WantRabbitMqEventBusOptions(configuration.GetSection("RabbitMQ:EventBus"));}public override void OnApplicationInitialization(ApplicationInitializationContext context){context.ServiceProvider.GetRequiredService<RabbitMqWantDistributedEventBus>().Initialize();}
}
以上就是 ABP 框架下分布式事件总线的基本知识点,其中也提到了 发件箱/收件箱 等新特性,ABP 框架经过长时间的发展,基本与 .NET 版本同步更新,到现在 9.0 版本,各种组件的特性也在逐步迭代更新。分布式事件总线除了这篇文章中提到的基本内容之外,还有一些新特性,后续会有一篇文章专门讲一下这些特性。
参考文档:
ABP 官方文档 - 分布式事件总线
相关文章:
ABP - 事件总线之分布式事件总线
ABP - 事件总线之分布式事件总线 1. 分布式事件总线的集成1.2 基于 RabbitMQ 的分布式事件总线 2. 分布式事件总线的使用2.1 发布2.2 订阅2.3 事务和异常处理 3. 自己扩展的分布式事件总线实现 事件总线可以实现代码逻辑的解耦,使代码模块之间功能职责更清晰。而分布…...
ComfyUI流程图生图原理详解
一、引言 ComfyUI 是一款功能强大的工具,在图像生成等领域有着广泛应用。本文补充一点ComfyUI 的安装与配置过程遇到的问题,并深入剖析图生图过程及相关参数,帮助读者快速入门并深入理解其原理。 二、ComfyUI 的安装与配置中遇到的问题 &a…...
洛谷 P3660 USACO17FEB Why Did the Cow Cross the Road III 题解
题意 有一个圆,圆周上按顺时针方向给出 2 n 2n 2n个点。第 i i i个点的颜色是 c o l o r i color_i colori,其中数据保证 1 ≤ c o l o r i ≤ n 1\le color_i\le n 1≤colori≤n,而且每种不同的颜色有且只有两个点。不存在位置重叠的点…...
kubekey一键部署k8s高可用与kubesphere
kubekey一键安装k8s与kubesphere还是蛮方便的,kubesphere官网上面也提到了高可用安装的一些事宜,但是没有涉及到kubesphere资深的redis的系统的部署问题,本文简单给出对应配置,其实这个配置在kubephere的cluster-configuration.ya…...
SwiftUI 5.0 中宝藏视图修改器 containerRelativeFrame 趣谈(下)
概览 小伙伴们都知道,为了将 SwiftUI 中多如牛毛的视图井然有序、有条不紊的组织起来,我们必须借助容器(Container)伏虎降龙般地威力。而如何最大限度的让容器中的子视图能根据容器尺寸安排自己的空间,则需要一些技术手段来洞幽察微。 在过去,我们往往使用 GeometryRead…...
ElasticSearch基础和使用
ElasticSearch基础 1 初识ES相关组件 (1)Elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。Elasticsearch结合kibana、Logstash、Beats组件 也就是elastic stack(ELK) 广泛应…...
我用 Cursor 开发了一款个人小记系统
https://note.iiter.cn 项目背景 在日常工作和学习中,我们经常需要快速记录一些想法、收藏一些有用的链接或者保存一些重要的文本、图片内容。虽然市面上已经有很多笔记软件,但我想要一个更轻量、更简单的工具,专注于快速记录和智能检索。于是我开发了这款个人小记系统。 系统…...
如何使用Three.js制作3D月球与星空效果
目录 1. 基本设置2. 创建星空效果3. 创建月球模型4. 添加中文3D文字5. 光照与相机配置6. 动画与控制7. 响应式布局8. 结语 在本文中,我们将一起学习如何利用Three.js实现一个3D月球与星空的效果,并添加一些有趣的元素,比如中文3D文字和互动功…...
DeepSeek接入网络安全领域,AI高效驱动,重新定义网络防御边界!
DeepSeek新一代模型的发布,标志着AI大模型的应用将逐步走向普及,并加速AI技术在各行业的赋能与全面落地。在科技日新月异的今天,AI技术凭借其强大的数据处理与分析能力,已成为推动社会进步的核心动力。 在网络安全领域࿰…...
【动态规划】斐波那契数列模型
目录 动态规划 动态规划的基本步骤 1137. 第 N 个泰波那契数 - 力扣(LeetCode) 算法分析 算法代码 算法代码 面试题 08.01. 三步问题 - 力扣(LeetCode) 算法分析 算法代码 优化 746. 使用最小花费爬楼梯 - 力扣&#x…...
Spring中的IOC详解
文章目录 IOC IOC容器的工作原理Bean的生命周期Bean的自动装配 AutowiredResourceInject 使用Spring底层组件 IOC Spring的核心之一是IOC,IOC全称为Inversion of Control,中文译为控制反转,是面向对象编程中的一种设计原则,可…...
深挖vue3基本原理之七 —— 功能模块的深度技术解析
Vue 3 四个核心功能模块的深度技术解析 一、Effect 调度系统:同步/异步任务队列 实现原理 // runtime-core/src/scheduler.ts const queue: (EffectJob | null)[] [] let isFlushing false const resolvedPromise Promise.resolve()function queueJob(job: Ef…...
数据结构 day 07
数据结构 day07 7. 树7.3. 层次遍历代码实现 8. 查询算法8.1. 顺序查找 seqSearch代码实现 8.2. 二分法查找 binarySearch代码实现 8.2. 分块查找 blockSearch代码实现 8.3. 哈希表 hash 9. 排序算法9.1. 冒泡排序 bubSort代码实现 9.2. 选择排序 selSort代码实现 9.3. 插入排序…...
《代码随想录》刷题笔记——回溯篇【java实现】
文章目录 组合组合总和 III电话号码的字母组合组合总和组合总和II思路代码实现 分割回文串※思路字符串分割回文串判断效率优化※ 复原 IP 地址优化版本 子集子集 II使用usedArr辅助去重不使用usedArr辅助去重 递增子序列※全排列全排列 II重新安排行程题意代码 N 皇后解数独直…...
React:初识React
React是什么? React是由Meta公司研发,也就是Facebook的公司(马克扎克伯格这个见人)研发的构建Web和原生交互界面的库 不仅可以写网页,还可以写苹果和安卓上面的app React的优势: React也是前端里最流行的…...
全面理解-c++中的内存布局
在 C 中,程序的内存布局指的是程序运行时,代码和数据在内存中的组织和分布方式。一般来说,C 程序的内存可以划分为以下几个主要区域: 1. 代码段(Text Segment,也称为 .text 段) 存储内容&…...
百度沈抖:传统云计算不再是主角,智能计算呼唤新一代“操作系统”
Create 2024 百度AI开发者大会 4月16日,Create 2024 百度AI开发者大会在深圳召开。期间,百度集团执行副总裁、百度智能云事业群总裁沈抖正式发布新一代智能计算操作系统——万源,通过对AI原生时代的智能计算平台进行抽象与封装设计ÿ…...
【银河麒麟高级服务器操作系统】服务器卡死后恢复系统日志丢失-分析及处理全过程
了解更多银河麒麟操作系统全新产品,请点击访问 麒麟软件产品专区:https://product.kylinos.cn 开发者专区:https://developer.kylinos.cn 文档中心:https://document.kylinos.cn 服务器环境以及配置 【机型】 处理器ÿ…...
VSCode Error Lens插件介绍(代码静态检查与提示工具)(vscode插件)
文章目录 VSCode Error Lens 插件介绍**功能概述****开发背景****使用方法****适用场景** VSCode Error Lens 插件介绍 功能概述 Error Lens 是一款增强 VS Code 错误提示的扩展工具,通过 内联显示错误和警告信息,直接定位代码问题,提升开发…...
ffmpeg configure 研究1-命令行参数的分析
author: hjjdebug date: 2025年 02月 14日 星期五 17:16:12 CST description: ffmpeg configure 研究1 ./configure 命令行参数的分析 文章目录 1 configure 对命令行参数的分析,在4019行1.1 函数名称: is_in1.2. 函数名称: enable1.3. 函数名称: set_all 2 执行退出判断的关键…...
如何调整 Nginx工作进程数以提升性能
🏡作者主页:点击! Nginx-从零开始的服务器之旅专栏:点击! 🐧Linux高级管理防护和群集专栏:点击! ⏰️创作时间:2025年2月15日14点20分 Nginx 的工作进程数࿰…...
分布式 NewSQL 数据库(TiDB)
TiDB 是一个分布式 NewSQL 数据库。它支持水平弹性扩展、ACID 事务、标准 SQL、MySQL 语法和 MySQL 协议,具有数据强一致的高可用特性,是一个不仅适合 OLTP 场景还适合 OLAP 场景的混合数据库。 TiDB是 PingCAP公司自主设计、研发的开源分布式关系型数据…...
try learning-git-branching
文章目录 mergerebase分离 HEAD相对引用利用父节点branch -f 撤销变更cherry-pick交互式 rebase只取一个提交记录提交的技巧rebase 在上一次提交上amendcherry-pick 在上一次提交上 amend tag多分支 rebase两个parent节点纠缠不清的分支偏离的提交历史锁定的Main推送主分支合并…...
【kafka系列】Kafka事务的实现原理
目录 1. 事务核心组件 1.1 幂等性生产者(Idempotent Producer) 1.2 事务协调器(TransactionCoordinator) 1.3 事务日志(Transaction Log) 2. 事务执行流程 2.1 事务初始化 2.2 发送消息 2.3 事务提…...
数据结构6
一、哈希散列--通讯录查找 #include "hash.h" #include <stdio.h> #include <stdlib.h> #include <string.h>//int *a[10];int hash_function(char key) {if (key > a && key < z){return key - a;}else if (key > A && …...
Flutter 的 Widget Key 提议大调整?深入聊一聊 Key 的作用
Flutter 的 Widget Key 提议大调整?深入聊一聊 Key 的作用 在 Flutter 里,Key 对象存在的目的主要是区分和维持 Widget 的状态,它是控件在渲染树里的「复用」标识之一,这一点在之前的《深入 Flutter 和 Compose 在 UI 渲染刷新时…...
src和href区别
src和href区别 (1)请求资源类型不同(2)作用结果不同(3)解析方式不同 (1)请求资源类型不同 href 用来建立文档和元素之间的链接(是引用),常用的有a、linksrc 在请求src资源时候会将指向的资源下载并且应用到文档中(引入),常用的有script、iframe、image。 (2)作用结果不同 hr…...
STM32之SG90舵机控制
目录 前言: 一、硬件准备与接线 1.1 硬件清单 1.2 接线 二、 SG90舵机简介 1.1 外观 1.2 基本参数 1.3 引脚说明 1.4 控制原理 1.5 特点 1.6 常见问题 三、 单片机简介 四、 程序设计 4.1 定时器配置 4.2 角度控制函数 4.3 主函数调用 五、 总结 …...
尚硅谷课程【笔记】——大数据之Hadoop【一】
课程视频链接:尚硅谷Hadoop3.x教程 一、大数据概论 1)大数据概念 大数据(Big Data):指无法再一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发…...
QEMU 搭建 Ubuntu x86 虚拟机
1. 安装 QEMU 在 Ubuntu 系统中,可以通过以下命令安装 QEMU: sudo apt-get update sudo apt-get install qemu-system-x86_64 qemu-kvm libvirt-daemon libvirt-clients bridge-utils virt-manager2. 创建虚拟硬盘镜像 qemu-img create -f raw ubuntu…...
mac 意外退出移动硬盘后再次插入移动硬盘不显示怎么办
第一步:sudo ps aux | grep fsck 打开mac控制台输入如下指令,我们看到会出现两个进程,看进程是root的这个 sudo ps aux|grep fsck 第二步:杀死进程 在第一步基础上我们知道不显示u盘的进程是:62319,我们…...
Acwing-基础算法课笔记之基础算法(双指针)
Acwing-基础算法课笔记之基础算法(双指针) 一、双指针算法概念二、关于双指针的一个问题三、模板 一、双指针算法概念 双指针(又称尺取法)是一个常用的优化技巧,用来解决序列的区间问题。 两个指针i,j&am…...
PCIE基础学习
PCIE PIO模式: 一个CPU传输一个32bit给PCIE(IP)。CPU直接与PCIE做数据传输。 DMA模式: CPU通过PCIE bridge 与多个PCIE设备连接,CPU发送命令给桥,桥控制PCIE与memory直接数据连接。 tlp报文 读报文 …...
架构——Nginx功能、职责、原理、配置示例、应用场景
以下是关于 Nginx 的功能、职责、原理、配置示例、应用场景及其高性能原因的详细说明: 一、Nginx 的核心功能 1. 静态资源服务 功能:直接返回静态文件(如 HTML、CSS、JS、图片、视频等)。配置示例:server {listen 80…...
【教程】比亚迪车机接入AI大模型语音助手
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 更新说明: v1.1.0.2 1、新增长按音量键触发,不再需要迪加 (需设置modelisten)。 2、新增kimi、豆包、ChatGPT等多个GPT接口。 3…...
ios中常见的设计原则和设计模式
七大设计原则 1:开闭原则 对扩展开放,对修改关闭,在设计模块的时候,使模块在不被修改的前提下可以扩展功能 2:依赖倒置原则 实现尽量依赖抽象,不依赖具体实现 (1)高层模块不应该依赖底层模…...
WSL Ubuntu 安装 CUDA 教程
WSL Ubuntu 安装 CUDA 教程 1. 概述2. 准备工作3. 删除旧的 GPG 密钥4. 安装 CUDA Toolkit4.1 使用 WSL-Ubuntu 包安装(推荐) 5. 设置环境变量6. 注意事项7. 参考链接8. 总结 1. 概述 随着 WSL 2 的推出,Windows 用户现在可以在 Windows 子系…...
案例-02.部门管理-查询
一.查询部门-需求 二.查询部门-思路 API接口文档 三.代码实现 1.controller层:负责与前端进行交互,接收前端所发来的请求 注:Slf4j用于记录日志使用,可以省略private static Logger log LoggerFactory.getLogger(DeptControlle…...
【ARM】解决ArmDS Fast Models 中部分内核无法上电的问题
1、 文档目标 解决ArmDS Fast Models 中部分内核无法上电的问题。 2、 问题场景 在调用ArmDS的Fast Models中的Cortex-A55的模型,只有Core 0是上电状态,而Core 1处于掉电状态,如图2-1所示: 图2-1 3、软硬件环境 1)…...
docker 基础命令使用(ubuntu)
docker 状态查询 docker ps docker ps -adocker --version docker info docker --help docker run --help docker ps --help ...docker 操作镜像命令 docker imagesdocker rmi 镜像id/镜像名docker 操作容器命令 docker ps docker ps -adocker run 命令 # 端口映射 -p 参数…...
WEB安全--SQL注入--二次注入
一、原理: 二次注入的关键在于攻击者的输入并不立即执行,而是经过某些存储或处理后,在后续某个步骤中再触发注入攻击 二、示例: 2.1、sqli-labs-master/less-24: admin# 第一次在网页注册账号和密码时没有漏洞&#x…...
c++中什么时候应该使用final关键字?
在C中,final关键字是自C11标准引入的重要特性,主要用于类继承和虚函数重写机制的约束。下面从技术原理、使用场景和最佳实践三个维度进行系统分析,并给出工业级代码示例。 目录 一、技术原理深度解析 二、关键使用场景分析 1. 类级别的fi…...
DeepSeek学术秘籍:如何让DeepSeek辅助论证?
随着人工智能技术的飞速发展,AIGC技术在学术领域的应用逐渐引起了广泛关注。其中最近大火的DeepSeek作为一款基于大语言模型的应用,其出现标志着学术论文写作中研究方法的一次重大变革。 辅助论证 在学术论文写作中,借助DeepSeek优化辅助论证…...
Atlassian工具集:Jira与Confluence集成优势、使用技巧、更新功能等
本文由Atlassian全球白金合作伙伴-龙智翻译整理,深入探讨了Jira和Confluence最受欢迎的集成功能与技巧,期待为您新一年的团队协作开个好头。 此前,来自K15t 的Customer Advocate Matt Reiner 和Atlassian副产品经理David Olive在一场学习会议…...
传输层协议TCP ( 下 )
文章目录 前言序号与确认序号超时重传RTOJacobson算法内核中超时时间的计算 滑动窗口滑动窗口延迟应答流量控制 拥塞控制慢启动拥塞避免快重传快速恢复 保活机制参考资料 前言 TCP(Transmission Control Protocol,传输控制协议)是互联网最重要…...
【Deepseek 零门槛指南】DeepSeek 教程和常见问题解答 | 大白技术控
粉丝朋友们大家好,我是极客学长。最近一直在玩 DeepSeek,积累了一点经验,用它提高写作的效率挺好用的。 在使用DeepSeek的过程中,也遇到了如下几个问题(相信很多小伙伴也遇到了): DeepSeek 官网卡顿,突然出…...
ELK组成及实现原理
ELK是由三个主要组件组成的日志处理和搜索平台,分别是: Elasticsearch:Elasticsearch 是一个基于Lucene构建的开源搜索引擎,提供强大的搜索、分析功能。它负责存储和索引所有数据,并提供实时搜索能力。数据可以通过HTT…...
迅为RK3568开发板篇OpenHarmony实操HDF驱动配置LED-LED测试
将编译好的镜像全部进行烧写,镜像在源码根目录 out/rk3568/packages/phone/images/目录下。 烧写完成之后,在调试串口查看打印日志,如下图所示: 然后打开 hdc 工具,运行测试程序,输入“led_test 1”&…...
【C++】IO流
目录 一、C语言的输入与输出二、流是什么三、CIO流3.1 C标准IO流3.2 C文件IO流3.2.1 二进制读写3.2.2 文本读写 四、stringstream的简单介绍结尾 一、C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键盘)读取数据…...
前端知识速记--css篇:CSS3中的常见动画及实现方式
前端知识速记–css篇:CSS3中的常见动画及实现方式 常见的CSS3动画 1. 过渡 (Transitions) 过渡是一种非常简单的动画效果,允许你在元素的状态变更时平滑过渡到新状态。 语法格式: transition: property duration timing-function delay;…...