[项目更新] 集成RabbitMQ队列与EventBus总线

(Blog.Core框架开发情况,着色部分为本次新增)

终于项目继续迭代更新了,在开源这两年多,也是感谢每一个支持Blog.Core项目的同学,同时也感谢每一个在生产环境中使用,并提出意见和建议的小伙伴,2,606个Star,是我们相互之间共同的努力和肯定,上边的这些都是我和各位使用者提出的需求,刚开始很快,越是到后边,开发起来越难,这里先说明几点问题:

1、增加的东西太多,有一部分使用者表示使用不到,太笨重;

2、目前基本比较全面,后期新增需求难度系数较高;

3、功能太多了,不好抄代码;????

不过该更新的还是需要更新的,我已经很贴心的把各部分的代码隔开了,就差每个功能建立一个类库了,这个我也考虑过,不过那要是建立起来,就是二三十个,果断放弃了。因为代码已经隔开了,如果自己不需要,可以删除掉,当然这样也方便其他不使用我框架的粘贴复制到自己项目。

今年终于在年末的时候,增加上了RabbitMQ消息队列和EventBus事件总线,之前新增过Redis的消息队列,基于Redis很方便且很简单的一个InitQ组件,具体请看《【BCVP】实现基于 Redis 的消息队列》,然后,大家应该都知道,最近我一直在录制一个系列视频教程——《eShopOnContainer微服务系列讲解》,里边最重要的就是事件总线,基于的也正好是RabbitMQ的分布式消息队列组件,当然其中的订单微服务也用到了MediatR作为进程内的订阅发布模式,这个MediatR我在DDD系列中已经讲过,就不说了,这次就重点说说RabbitMQ和EventBus吧,也正好属于俩个系列的串烧了。这里说一下,我是从eshop代码里拷贝出来做讲解的,当然做了适当修改,还是要多关注官方,支持原作者:

https://github.com/dotnet-architecture/eShopOnContainers。

此外,热烈欢迎支付组件的合作者,如果你正在开发支付相关组件,可以联系我,一起推广开发,一起造福社区,也可以入驻BCVP开发者社区。

OK,今天就先简单的给大家先说下思路,以下每一个小节其实都可以写一篇或多篇文章的,本文就当个系列文章导读吧,详细讲解以后会有,主要就是关于RabbitMQ消息队列和EventBus事件总线的。

01

消息队列

Message Queue

Publish/Subscribe

基本概念:

消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。

消息队列提供了异步的通信协议,每一个队列中的记录包含详细说明的数据,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。

最终可以实现解耦的目的。

下面通过一个简单的架构模型来解释:

  • Producer:消息生产者,负责产生和发送消息到Broker。

  • Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个Queue。

  • Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理。

消息队列的好处:

从上边的定义中,我们可以看出来,优点主要是三块:异步、流量削峰与流控、解耦。这三个优点在高并发等三高场景还是很有必要的,甚至说是十分必要的。

系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据,从而实现了解耦的目的:

(图片来源于知乎/question/54152397)

接下来,为了提高用户体验和吞吐量,其实可以异步地调用系统B、C、D的接口。所以,我们可以弄成是这样的:

(图片来源于知乎/question/54152397)

最后,系统B和系统C根据自己的能够处理的请求数去消息队列中拿数据,这样即便有每秒有8000个请求,那只是把请求放在消息队列中,去拿消息队列的消息由系统自己去控制,这样就不会把整个系统给搞崩:

(图片来源于知乎/question/54152397)

当然消息队列,也有些坏处,这里就先随便列几个,其他的大家自行搜索即可:

1、高可用:如果使用消息队列,基本要配合集群的,因为如果MQ服务器崩了,那就整个服务灾难了。

2、数据安全:必须保证数据不能丢失,也就是要考虑好最终一致性,做好补偿机制。

3、合理的消费。

好啦,基本概念先说到这里,下边就简单说下代码吧,因为篇幅的问题,我们只统一讲解接口的设计,毕竟实现类是比较复杂的,当然,我会抽一个实现类的核心方法说一下。

02

RabbitMQ持久连接

IRabbitMQ

PersistentConnection

首先说下关于RabbitMQ的连接,这个是很简单的,和平时我们使用SqlServer/Redis这种第三方组件是类似的,通过连接字符串(或者说是服务器),然后配置用户名/密码,就能连接上了,相关的接口是这样的:

 /// <summary>/// RabbitMQ持久连接/// 接口/// </summary>public interface IRabbitMQPersistentConnection: IDisposable{bool IsConnected { get; }bool TryConnect();IModel CreateModel();}

接口一共提供了三个方法,分别是是否连接、尝试连接、创建模型。

使用的时候,首先需要连接nuget包:

<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.2.1" />

其实只需要第三个RabbitMQ.Client就行了,前边两个是辅助作用,分别是提供序列化和重试机制的,如果你有一个需求是需要重试的,比如连接数据库或者执行某个进程,如果遇到异常,重试几次,可以使用组件Polly,它还有其他的功能,自己可以多尝试下。

那说到了重试,我就说一下,TryConnect(); 这个核心的方法:

/// <summary>
/// 连接
/// </summary>
/// <returns></returns>
public bool TryConnect()
{_logger.LogInformation("RabbitMQ Client is trying to connect");// 加锁lock (sync_root){// 重试策略var policy = RetryPolicy.Handle<SocketException>().Or<BrokerUnreachableException>().WaitAndRetry(_retryCount,retryAttempt =>TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>{_logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);});// 执行策略policy.Execute(() =>{// 开始连接RabbitMQ_connection = _connectionFactory.CreateConnection();});// 连接成功if (IsConnected){// 追加事件处理器,目的是为了异常重试,共3种情况_connection.ConnectionShutdown += OnConnectionShutdown;_connection.CallbackException += OnCallbackException;_connection.ConnectionBlocked += OnConnectionBlocked;_logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName);return true;}else{_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");return false;}}
}

相应的逻辑我已经在代码中,增加了注释,过程肯定能看得懂,至于真实的底层原理,这里先不说了。

可以看到上边就用到了重试机制,可以配置策略。这样就可以连接上RabbitMQ服务器了,那如何基于这个连接做事件总线呢,别着急,咱们先说下什么是事件和事件处理器


03

事件与处理器

IntegrationEvent

IIntegrationEventHandler<T>

关于事件

如果你看过我DDD领域驱动设计,应该会有些印象和了解,我这里再简单的说明一下吧。关于事件,其实我们每天都在用,而且很久之前就用过,就比如说asp的时候的按钮事件:

void btnRegister_Click(object sender, EventArgs e)

其中object sender指代发出事件的对象,这里也就是button对象;EventArgs e事件参数,可以理解为对事件的描述 ,它们可以统称为事件源。其中的代码逻辑,就是对事件的处理。我们可以统称为事件处理程序

所以:事件有两部分=事件源对象+事件处理器程序。

关于总线

那我们平时肯定会遇到很多很多的事件:

注册的时候,校验成功后持久化到数据库,然后发注册成功的邮件。

支付的时候,判断成功后,修改数据库订单,库存,物流,邮件,短信,等等等等,这都是一个个的事件。

那如何对这些事件进行统一的管理呢,单体下很简单,就是按照过程走就行了,分布式或者微服务中,多个服务已经隔离开,无法按照过程一步步走,那这个时候就需要一个策略,常用的就是——订阅发布模式,事件总线是对发布-订阅模式的一种实现。它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。

代码举例

我们用代码来简单看看如何设计事件和事件处理器:

 /// <summary>/// 事件模型/// 基类/// </summary>public class IntegrationEvent{public IntegrationEvent(){Id = Guid.NewGuid();CreationDate = DateTime.UtcNow;}[JsonConstructor]public IntegrationEvent(Guid id, DateTime createDate){Id = id;CreationDate = createDate;}[JsonProperty]public Guid Id { get; private set; }[JsonProperty]public DateTime CreationDate { get; private set; }}

事件是一个对象,是一个模型,那很重要的标识,就是Id和Date这两个属性了,当然也可以适当做其他的一些处理,请注意private set; 的写法。

  /// <summary>/// 集成事件处理程序/// 泛型接口/// </summary>/// <typeparam name="TIntegrationEvent"></typeparam>public interface IIntegrationEventHandler<in TIntegrationEvent> : IIntegrationEventHandlerwhere TIntegrationEvent : IntegrationEvent{Task Handle(TIntegrationEvent @event);}/// <summary>/// 集成事件处理程序/// 基 接口/// </summary>public interface IIntegrationEventHandler{}

对事件的处理比较简单的,我们定义接口,只需要一个Handle方法即可,剩下的就是我们定义一个一个的具体的事件处理器,通过继承这个接口,来实现具体的业务逻辑。

比如我这里定义了一个例子,关于博客删除的,当然可能不太贴切,我只是想举个例子:

 /// <summary>/// 博客删除事件处理器/// 删除博客后触发/// </summary>public class BlogDeletedIntegrationEventHandler : IIntegrationEventHandler<BlogDeletedIntegrationEvent>{private readonly IBlogArticleServices _blogArticleServices;private readonly ILogger<BlogDeletedIntegrationEventHandler> _logger;public BlogDeletedIntegrationEventHandler(IBlogArticleServices blogArticleServices,ILogger<BlogDeletedIntegrationEventHandler> logger){_blogArticleServices = blogArticleServices;_logger = logger ?? throw new ArgumentNullException(nameof(logger));}public async Task Handle(BlogDeletedIntegrationEvent @event){_logger.LogInformation("----- Handling integration event: {IntegrationEventId} at {AppName} - ({@IntegrationEvent})", @event.Id, "Blog.Core", @event);ConsoleHelper.WriteSuccessLine($"----- Handling integration event: {@event.Id} at Blog.Core - ({@event})");await _blogArticleServices.DeleteById(@event.BlogId.ToString());}}

当我执行删除的时候,不去执行,而是放到队列里,通过订阅发布的模式,每一个订阅者来消费信息,从而实现解耦的目的。

现在明白了事件和处理器,那如何对这是事件操作,怎么发布,又是如何订阅呢?事件总线就这么出现了,请往下看。

04

基于RabbitMQ事件总线

IEventBus

EventBusRabbitMQ

上边我们已经连接好了RabbitMQ服务器,也明白了什么是事件和处理器,现在就是需要发布和订阅了,总线是一个很好的方案,那设计下接口,就是这样的:

/// <summary>
/// 事件总线
/// 接口
/// </summary>
public interface IEventBus
{/// <summary>/// 发布/// </summary>/// <param name="event">事件模型</param>void Publish(IntegrationEvent @event);/// <summary>/// 订阅/// </summary>/// <typeparam name="T">约束:事件模型</typeparam>/// <typeparam name="TH">约束:事件处理器<事件模型></typeparam>void Subscribe<T, TH>()where T : IntegrationEventwhere TH : IIntegrationEventHandler<T>;/// <summary>/// 取消订阅/// </summary>/// <typeparam name="T"></typeparam>/// <typeparam name="TH"></typeparam>void Unsubscribe<T, TH>()where TH : IIntegrationEventHandler<T>where T : IntegrationEvent;/// <summary>/// 动态订阅/// </summary>/// <typeparam name="TH">约束:事件处理器</typeparam>/// <param name="eventName"></param>void SubscribeDynamic<TH>(string eventName)where TH : IDynamicIntegrationEventHandler;/// <summary>/// 动态取消订阅/// </summary>/// <typeparam name="TH"></typeparam>/// <param name="eventName"></param>void UnsubscribeDynamic<TH>(string eventName)where TH : IDynamicIntegrationEventHandler;
}

这里定义了基本的常见操作,如何实现这个接口,可以针对不同的方案,既然我们使用了RabbitMQ,就说说它,当然你也可以使用其他的,比如AzureService之类的。

基于RabbitMQ的事件总线实现类比较复杂,我就不多说明了,感兴趣的可以直接看我的代码,我这里就说一下构造函数,从构造函数中,可以知道,当前类的依赖项,毕竟现在都是使用依赖注入了:

 /// <summary>/// RabbitMQ事件总线/// </summary>/// <param name="persistentConnection">RabbitMQ持久连接</param>/// <param name="logger">日志</param>/// <param name="autofac">autofac容器</param>/// <param name="subsManager">事件总线订阅管理器</param>/// <param name="queueName">队列名称</param>/// <param name="retryCount">重试次数</param>public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,ILifetimeScope autofac, IEventBusSubscriptionsManager subsManager, string queueName = null, int retryCount = 5){_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));_logger = logger ?? throw new ArgumentNullException(nameof(logger));_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();_queueName = queueName;_consumerChannel = CreateConsumerChannel();_autofac = autofac;_retryCount = retryCount;_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;}

除了比较常规的日志、RabbitMQ连接、Autofac容器、Polly重试这几个比较基础和必要的,还有一个参数是很重要的——IEventBusSubscriptionsManager subsManager 。

这个是干什么的呢,我们知道,单体应用很简单,按照过程一一步骤即可,单一的订阅发布模式也比较简单,就是一对一,但是还是有很多复杂的,那如何对这些订阅统一管理呢,就是需要一个事件总线订阅管理器。

05

事件总线订阅管理器

InMemory

EventBusSubscriptionsManager

是对每一个订阅事件需要做管理,比如该发布的事件不想被消费了,比如需要动态的添加一个订阅者,比如全部清除等,可以这么设计接口:

 /// <summary>/// 事件总线订阅管理器/// 接口/// </summary>public interface IEventBusSubscriptionsManager{bool IsEmpty { get; }event EventHandler<string> OnEventRemoved;void AddDynamicSubscription<TH>(string eventName)where TH : IDynamicIntegrationEventHandler;void AddSubscription<T, TH>()where T : IntegrationEventwhere TH : IIntegrationEventHandler<T>;void RemoveSubscription<T, TH>()where TH : IIntegrationEventHandler<T>where T : IntegrationEvent;void RemoveDynamicSubscription<TH>(string eventName)where TH : IDynamicIntegrationEventHandler;bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;bool HasSubscriptionsForEvent(string eventName);Type GetEventTypeByName(string eventName);void Clear();IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);string GetEventKey<T>();}

这里应该明白它的作用了吧,就是控制当前项目中的总线中的各个订阅情况,可以直接在内存里操作,注意这个内存是管理总线中的订阅的,和RabbitMQ的分布式不一样,需要搞清楚二者的区别,如果不是很懂,可以联系我,或者留言。

所以可以设计这么一个实现类InMemoryEventBusSubscriptionsManager:

基本到这里就没啥问题了,核心的几个知识点也讲完了,当然,仅仅是讲完了,其中的知识点量,要远比这个多的多,剩下的可以看看效果。

06

服务注册和使用

Service 

registration and usage

上边的设计完,接下来注册一下服务就行了,首先就是注册RabbitMQ:

public static void AddRabbitMQSetup(this IServiceCollection services)
{if (services == null) throw new ArgumentNullException(nameof(services));if (Appsettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool()){services.AddSingleton<IRabbitMQPersistentConnection>(sp =>{var logger = sp.GetRequiredService<ILogger<RabbitMQPersistentConnection>>();var factory = new ConnectionFactory(){HostName = Appsettings.app(new string[] { "RabbitMQ", "Connection" }),DispatchConsumersAsync = true};if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "UserName" }))){factory.UserName = Appsettings.app(new string[] { "RabbitMQ", "UserName" });}if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "Password" }))){factory.Password = Appsettings.app(new string[] { "RabbitMQ", "Password" });}var retryCount = 5;if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" }))){retryCount = int.Parse(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" }));}return new RabbitMQPersistentConnection(factory, logger, retryCount);});}
}

可以在配置文章中配置下参数。

然后注册事件总线EventBus:

  public static void AddEventBusSetup(this IServiceCollection services){if (services == null) throw new ArgumentNullException(nameof(services));if (Appsettings.app(new string[] { "RabbitMQ", "Enabled" }).ObjToBool() && Appsettings.app(new string[] { "EventBus", "Enabled" }).ObjToBool()){var subscriptionClientName = Appsettings.app(new string[] { "EventBus", "SubscriptionClientName" });services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();services.AddTransient<BlogDeletedIntegrationEventHandler>();services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>{var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();var retryCount = 5;if (!string.IsNullOrEmpty(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" }))){retryCount = int.Parse(Appsettings.app(new string[] { "RabbitMQ", "RetryCount" }));}return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);});}}

最后,当项目启动的时候,直接订阅我们的事件处理程序:

 var eventBus = app.ApplicationServices.GetRequiredService<IEventBus>();eventBus.Subscribe<BlogDeletedIntegrationEvent, BlogDeletedIntegrationEventHandler>(); 

我们尝试一下,发送一个事件到总线里:

 [HttpGet][AllowAnonymous]public void EventBusTry([FromServices] IEventBus _eventBus, string blogId = "1"){var blogDeletedEvent = new BlogDeletedIntegrationEvent(blogId);_eventBus.Publish(blogDeletedEvent);}

动图效果如下:

是不是很简单,好啦,暂时就先到这里,打完手工。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306728.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cv2.imread读取图像结果none_python cv2.imread 读取中文路径的图片返回为None的问题

此篇文章首发于我的csdn博客&#xff0c;见原文链接。使用cv2读取图片是常见的事情&#xff0c;但如果&#xff0c;输出图片形状大小时出现报错“ NoneType object has no attribute shape”&#xff0c;后来排查发现读取图片的返回值image为None&#xff0c; 这就说明图片根本…

利用模板化应对ERP业务模型的快速变化

源宝导读&#xff1a;ERP这类复杂系统中&#xff0c;业务模型是系统功能的核心抽象&#xff0c;但业务模型对于不同的客户会有差异&#xff0c;也会随着业务发展而变化。虽然可以对业务组件进行复用&#xff0c;但客户定制的成本依然较高&#xff0c;本文将讨论如何利用模板化应…

mockmvc get请求 tm的 一直404_大家快来看看404的兄弟姐妹

码个蛋(codeegg)第 624 次推文作者&#xff1a;xiaoxiunique博客&#xff1a;https://juejin.im/post/5cd2ea425188254459335583做开发的我们肯定少不了跟网络数据打交道&#xff0c;我们都知道&#xff0c;我们进行网络请求&#xff0c;无论成功还是失败&#xff0c;后台都会给…

网传不要升级.NET5的诸多原因,你赞同几个?

.NET5正式发布有十多天&#xff0c;博客园、知乎、技术群都讨论的非常热烈。关于项目是否迁移.NET5的话题讨论的尤为热烈&#xff0c;作为.NET十年老司机要告诉你&#xff0c;.NET5的迁移势在必行&#xff0c;当下就是最好的时机&#xff01;犹豫项目是否升级到.NET5的&#xf…

WPF开发的实用小工具 - 快捷悬浮菜单

WPF开发的实用小工具 - 快捷悬浮菜单❝本文由网友投稿&#xff0c;Dotnet9站长整理。站长觉得这小工具很实用&#xff0c;站长家里、公司也在尝试使用了。行文目录&#xff1a;这工具有什么用&#xff1f;正文源码获取及应用下载体验站长的建议1. 这工具有什么用&#xff1f;问…

accdb原有的数据怎么清除_VBA中利用数组对数据批量处理的方法

大家好&#xff0c;今日继续和大家分享VBA编程中常用的常用"积木"过程代码。这些内容大多是我的经验和记录&#xff0c;来源于我多年的经验。今日分享的是NO.225-NO.226&#xff0c;内容是&#xff1a;NO. 225&#xff1a;数组的赋值和回填工作表NO. 226&#xff1a;…

云南河道 kml文件_处理能力提升 4 倍 ,大疆智图 4 天完成 5 公里河道建模

马陵河是江苏省宿迁市老城区一条重要排涝河道&#xff0c;1974 年人工开挖而成&#xff0c;全长 5.2 km&#xff0c;汇水面积 11.6 km&#xff0c;居住人口 13.85 万人。河道水质长期处于黑臭状态&#xff0c;严重影响周边居民日常生活&#xff0c;被称为宿迁的“龙须沟”。宿迁…

linq 查询的结果会开辟新的内存吗?

一&#xff1a;背景 1. 讲故事昨天群里有位朋友问&#xff1a;linq 查询的结果会开辟新的内存吗&#xff1f;如果开了&#xff0c;那是对原序列集里面元素的深拷贝还是仅仅拷贝其引用&#xff1f;其实这个问题我觉得问的挺好&#xff0c;很多初学 C# 的朋友或多或少都有这样的疑…

问题 B: 十进制到二进制的转换

这个问题我们来用栈来实现 首先&#xff0c;我们先定义一个栈的结构体&#xff08;栈的结构体与链表的结构体不可同&#xff0c;栈的结构体第二项是用int定义栈的顶端; 而链表的第二项&#xff0c;是用struct定义一个指针&#xff09; struct stack{int data[10005];int top;…

javascript内存泄漏调试工具mac_node.js 内存泄漏的秘密

一直以来&#xff0c;跟踪 Node.js 的内存泄漏是一个反复出现的话题&#xff0c;人们始终希望对其复杂性和原因了解更多。并非所有的内存泄漏都显而易见。但是&#xff0c;一旦我们确定了其模式&#xff0c;就必须在内存使用率&#xff0c;内存中保存的对象和响应时间之间寻找关…

Leansoft再发招贤令:面试官徐磊有话讲 | IDCF

&#xff08;图片来源于网络&#xff09;2020是Leansoft成立的第五年&#xff0c;凭借专业的服务及实施能力&#xff0c;逐渐成长为国内唯一的端到端专业DevOps实施服务公司。Leansoft是一家怎样的公司呢&#xff1f;准确地说&#xff0c;我们其实是国内唯一一家提供端到端的De…

问题 B: 数塔问题

题目描述 有如下所示的数塔&#xff0c;要求从顶层走到底层&#xff0c;若每一步只能走到相邻的结点&#xff0c;则经过的结点的数字之和最大是多少&#xff1f; 输入 第一行是一个整数N(1 < N < 20)&#xff0c;表示数塔的高度&#xff0c;接下来用N个数字表示数塔&a…

e盾服务端源码_gRPC服务注册发现及负载均衡的实现方案与源码解析

今天聊一下gRPC的服务发现和负载均衡原理相关的话题&#xff0c;不同于Nginx、Lvs或者F5这些服务端的负载均衡策略&#xff0c;gRPC采用的是客户端实现的负载均衡。什么意思呢&#xff0c;对于使用服务端负载均衡的系统&#xff0c;客户端会首先访问负载均衡的域名/IP&#xff…

堆问题(最小堆变最大堆,堆删除,中序遍历)

2-6 设最小堆&#xff08;小根堆&#xff09;的层序遍历结果为 {8, 38, 25, 58, 52, 82, 70, 60}。用线性时间复杂度的算法将该堆调整为最大堆&#xff08;大根堆&#xff09;&#xff0c;然后连续执行两次删除最大元素操作&#xff08;DeleteMax&#xff09;。则该树的中序遍历…

推荐一款.NET Core开源爬虫神器:DotnetSpider

没有爬虫就没有互联网&#xff01;爬虫的意义在于采集大批量数据&#xff0c;然后基于此进行加工/分析&#xff0c;做更有意义的事情。谷歌&#xff0c;百度&#xff0c;今日头条&#xff0c;天眼查都离不开爬虫。去开源中国和Github查询C#的爬虫项目&#xff0c;仅有几个非常简…

Excel学习使用教程

1.Excel的保存与加密 加密&#xff1a; 我设置的密码&#xff1a;517485

问题 D: 二叉树求高度

题目描述 已知一棵二叉树用邻接表结构存储&#xff0c;求这棵树的高度。例&#xff1a;如图二叉树的数据文件的数据格式如下: 输入 第一行n为二叉树的结点个树&#xff0c;n≤100&#xff1b;以下第一列数据是各结点的值&#xff0c;第二列数据是左儿子结点编号&#xff0c;第…

.Net Core in Docker - 使用阿里云Codepipeline及阿里云容器镜像服务实现持续集成(CI)...

前面已经介绍过了 .Net Core In Docker 在容器内编译并发布的内容。但是每次通过 SSH 链接到服务器敲命令&#xff0c;运行脚本也是挺麻烦的一件事。程序员是最懒的&#xff0c;能让电脑解决的问题绝不手动解决&#xff0c;如果当我们push一次代码后自动build代码&#xff0c;自…

mysql 序列_MySql中序列的应用和总结

Mysql中的序列主要用于主键&#xff0c;主键是递增的字段&#xff0c;不可重复。Mysql与Oracle不同的是&#xff0c;它不支持原生态的sequence&#xff0c;需要用表和函数的组合来实现类似序列的功能。1.首先创建序列的主表/*2.其次创建如下三个函数&#xff0c;它们的功能分别…

汉诺塔问题详细解析zufeoj

汉诺塔&#xff08;Tower of Hanoi&#xff09;&#xff0c;又称河内塔&#xff0c;是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子&#xff0c;在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重…