随笔 - 435  文章 - 0  评论 - 111  阅读 - 62万 

1.  只看主页介绍,你不知道ICapPublisher的实现在哪里,注入的实例是什么, 查看代码才知道 原来在AddCap的调用里有一个内部实现

Publish

Inject ICapPublisher in your Controller, then use the ICapPublisher to send messages

复制代码
 public static CapBuilder AddCap(this IServiceCollection services, Action<CapOptions> setupAction)
        {
            if (setupAction == null)
            {
                throw new ArgumentNullException(nameof(setupAction));
            }

            services.AddSingleton(_ => services);
            services.TryAddSingleton<CapMarkerService>();

            services.TryAddSingleton<ICapPublisher, CapPublisher>();

            services.TryAddSingleton<IConsumerServiceSelector, ConsumerServiceSelector>();
            services.TryAddSingleton<ISubscribeInvoker, SubscribeInvoker>();
            services.TryAddSingleton<MethodMatcherCache>();

            services.TryAddSingleton<IConsumerRegister, ConsumerRegister>();

            //Processors
            services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, IDispatcher>(sp => sp.GetRequiredService<IDispatcher>()));
            services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, IConsumerRegister>(sp => sp.GetRequiredService<IConsumerRegister>()));
            services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, CapProcessingServer>());

            //Queue's message processor
            services.TryAddSingleton<MessageNeedToRetryProcessor>();
            services.TryAddSingleton<TransportCheckProcessor>();
            services.TryAddSingleton<CollectorProcessor>();

            //Sender
            services.TryAddSingleton<IMessageSender, MessageSender>();

            services.TryAddSingleton<ISerializer, JsonUtf8Serializer>();

            // Warning: IPublishMessageSender need to inject at extension project. 
            services.TryAddSingleton<ISubscribeDispatcher, SubscribeDispatcher>();

            //Options and extension service
            var options = new CapOptions();
            setupAction(options);

            //Executors
            if (options.UseDispatchingPerGroup)
            {
                services.TryAddSingleton<IDispatcher, DispatcherPerGroup>();
            }
            else
            {
                services.TryAddSingleton<IDispatcher, Dispatcher>();
            }

            foreach (var serviceExtension in options.Extensions)
            {
                serviceExtension.AddServices(services);
            }
            services.Configure(setupAction);

            //Startup and Hosted 
            services.AddSingleton<Bootstrapper>();
            services.AddHostedService(sp => sp.GetRequiredService<Bootstrapper>());
            services.AddSingleton<IBootstrapper>(sp => sp.GetRequiredService<Bootstrapper>());

            return new CapBuilder(services);
        }
复制代码

 

Q:Unhandled exception. MySql.Data.MySqlClient.MySqlException (0x80004005): Only MySqlParameter objects may be stored

A: DotNetCore.CAP.MySql问题分析:only mysqlparameter objects may be stored 

Q:数据库的消息表是什么时候创建的呢? 出现表还没创建 MySqlConnector.MySqlException:“Table cap.published' doesn't exist”

A:   

IServiceProvider serviceProvider = services.BuildServiceProvider();
serviceProvider.GetService<IBootstrapper>().BootstrapAsync();  //手工调用Bootstrapper
复制代码
 public async Task BootstrapAsync()
        {
            _logger.LogDebug("### CAP background task is starting.");

            CheckRequirement();

            try
            {
                _processors = _serviceProvider.GetServices<IProcessingServer>();

                await _serviceProvider.GetRequiredService<IStorageInitializer>().InitializeAsync(_cts.Token);
            }
            catch (Exception e)
            {
                if (e is InvalidOperationException)
                {
                    throw;
                }
                _logger.LogError(e, "Initializing the storage structure failed!");
            }
复制代码

Q: 创建了本地数据库的消息表,用了publish的方法,为啥没发到MQ里去呢?

 

 

5270724538993467393    v1    IDG    {"Headers":{"cap-callback-name":null,"cap-msg-id":"5270724538993467393","cap-corr-id":"5270724538993467393","cap-corr-seq":"0","cap-msg-name":"IDG","cap-msg-type":"DateTime","cap-senttime":"2022/7/19 14:53:49 \u002B08:00"},"Value":"2022-07-19T14:53:49.3657308+08:00"}    0    2022-07-19 14:53:49        Scheduled

 

A: In order to ensure that the message will not be lost, we first store it into the Storage and set the status to Scheduled, and change it to Succeeded after push to broker successful.

publish方法出现Null Reference错误

提交事务的时候 会调用 EnqueueToPublish 方法,然后你发现_publishedChannel==null, 你打开RabbitMQ的管理界面,会发现connection和Channel都是空的

控制台程序需要启动serviceProvider.GetService<IBootstrapper>().BootstrapAsync(); 不然不会发送

 

 

 

Q: 显示Status=Succeeded, 是否已经成功了? 还有个ExpiresAt 默认是1天后过期, 但是我在RabbitMQ的管理界面查找queue的消息数量,却没有变化???

A: 因为我刚才只是用了Publish,没有订阅者,所以没有队列, 队列仅是针对接收方(consumer)的,由接收方根据需求创建的。只有队列创建了,交换机才会将新接受到的消息送到队列中,交换机是不会在队列创建之前的消息放进来的。即在建立队列之前,发出的所有消息都被丢弃了,CAP认为成功了

建立订阅者之后才会建立队列

队列的名字是自动生成的,例如这个 cap.queue.sample.consoleapp.v1, publish使用交换机 cap.default.router

 

Q: 假如我通过其他程序,发送通过默认交换机,发送消息到这个队列:cap.queue.sample.consoleapp.v1, 订阅者能收到吗?

A: 不能,必须用指定的交换机和RoutingKey,才可以。另外发送的消息,必须把自定义的Header放到RabbitMQ的消息头里,订阅者才能正确解析

1
2
3
4
5
6
7
8
9
10
11
12
13
channel = _connectionChannelPool.Rent();
 
 channel.ConfirmSelect();
 
 var props = channel.CreateBasicProperties();
 props.DeliveryMode = 2;
 props.Headers = message.Headers.ToDictionary(x => x.Key, x => (object?)x.Value);
 
 channel.ExchangeDeclare(_exchange, RabbitMQOptions.ExchangeType, true);
 
 channel.BasicPublish(_exchange, message.GetName(), props, message.Body);
 
 channel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(5));

  

订阅消息的处理 在DotNetCore.CAP.Internal.ConsumerRegister类里

 private void RegisterMessageProcessor(IConsumerClient client)

 Q:  这段代码为啥会出错呢?

复制代码
  using (var connection = new MySqlConnection(MySqlConn))
            {
                using (var transaction = connection.BeginTransaction(_capBus, autoCommit: true))
                {
                    //your business logic code
                    for (int i = 0; i < 3; i++)
                    {

                        //要先启动客户端订阅,才会生成队列,名字默认为cap.queue.【客户端程序命名空间】.v1
                        //第一个参值数为消息队列的topic, 交换机为cap.default.router,通过topic作为routingkey Bind to queue
                        //
                        _capBus.Publish(topic, DateTime.Now);
                    }
                    
                }
            }
复制代码

A:  循环多条消息,要把AutoComit=false, 手动提交

Q:  把mysql,rabbitmq停掉, 程序会怎样?

A: mysql停掉, 程序会出错; 订阅客户端要做好DBFail之后自动恢复重试的工作,CAP自带的例子没做这部分。 启动Mysql,停掉RabbitMQ,发送3次失败后在发送消息表里会记录 Status=fail, 在下次正常连接RabbitMQ时会再次发出去,变成Succeed

 

 Q: 发送接收速度如何?

A:  循环发送接收1w条记录, Mysql+RabbitMQ用时106秒,也就是最多100条/秒, 假如处理逻辑再复杂的话,估计每秒50条以下,这个速度估计只能用在数量较少的订单交易类的处理,不能用在频繁的日志处理。 相对我之前不用CAP,直接发送1W消息到MQ,用时在3~4秒,慢了挺多的。有什么优化的方法吗?

尝试改用 DotNetCore.CAP.InMemoryStorage,放弃本地消息表,测试一下,1w条记录也要60秒

 

Q: 发布端和订阅端的异常如何处理?项目都没有介绍

 A: 可以参考 第十六节:CAP框架异常处理、实现分布式事务(最终一致性) 及 其它用法    第一次有人把“分布式事务”讲的这么简单明了

接收消息成功,执行业务失败, Cap认为是失败,MQ的中的消息也被删除了,这里CAP框架就是这么设计的。

对应的处理方法1:人工干预

登录后台dashboard ,查看异常;此时把订阅者接口改成正常接收,在cap后台手动点击异常记录,进行重新消费,消费成功

 

CAP只是保证发布端的消息表和业务是同一事务,订阅端的消息表和业务是同一事务。

对于发布端来说,业务状态是这样:【未执行】-》【执行中】-》【OK/NG】

当消息成功发出去了。业务状态是中间状态【执行中】,至于订阅端是否消费成功,应该是订阅端消费完成后,再给发布端发一个Topic消息(OK或NG),

原来的发布端收到Topic消息后,更改业务状态为【OK或NG】

posted on   Gu  阅读(1084)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示