.NET CORE 分布式事务(四) CAP实现最终一致性

目录

引言:

1.0 最终一致性介绍

2.0 CAP

2.0 架构预览

3.0 .NET CORE 结合CAP实现最终一致性分布式事务

3.1 准备工作(数据库,本文使用的是MySql)

3.1.1 数据模型

3.1.2 DbContext

3.1.3 数据库最终生成 

3.2 Nuget引入

3.3 appsettings.json 

3.4 docker启动一个RabbitMQ

3.5 Program.cs

3.6 用户1 API控制器

3.7 用户2 API控制器

4.0 测试运行

5.0 消息附加头信息的CAP

5.1 上游事务代码

5.2 下游事务代码

5.3 测试运行

6.0 使用消息队列中不同的交换机和队列 

7.0 重试失败回调

小结:


引言:

结合前三期 .NET CORE 分布式事务(一) DTM实现二阶段提交(.NET CORE 分布式事务(一) DTM实现二阶段提交_net 分布式事务-CSDN博客);.NET CORE 分布式事务(二) DTM实现TCC(.NET CORE 分布式事务(二) DTM实现TCC_.net core 分布式事物-CSDN博客);.NET CORE 分布式事务(三) DTM实现Saga及高并发下的解决方案(.NET CORE 分布式事务(三) DTM实现Saga及高并发下的解决方案-CSDN博客)。

1.0 最终一致性介绍

在分布式系统中,事务管理是确保数据一致性和系统稳定性的关键技术。传统的集中式事务处理模型(如两阶段提交协议)在分布式环境中面临诸多挑战,包括性能瓶颈、单点故障和扩展性问题。为了解决这些问题,最终一致性模型被提出并广泛应用于分布式事务处理中。

最终一致性(Eventual Consistency)是一种分布式系统的数据一致性模型,它不要求数据在每个时刻都保持一致,而是允许数据在短时间内不一致,但保证在一定时间后,所有节点上的数据会达到一致状态。这种模型降低了对即时一致性的要求,从而提高了系统的可用性和扩展性。

在介绍最终一致性之前,我们需要了解CAP定理,它是分布式计算领域的一个重要理论。CAP定理指出,在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容错性(Partition Tolerance)三者不可兼得。换句话说,一个分布式系统最多只能满足这三个属性中的两个。最终一致性模型通常在可用性和分区容错性之间做出权衡,以实现高可用和可扩展的分布式系统。

最终一致性的实现方式有很多,以下是一些常见的方法:

  1. 异步复制:在分布式系统中,数据更新操作首先在一个节点上完成,然后通过异步方式将更新复制到其他节点。这种方式可以降低响应延迟,提高系统性能,但可能导致短时间的数据不一致。

  2. 消息队列:通过消息队列来实现节点间的通信和数据同步。当一个节点完成数据更新后,它会将更新操作发送到消息队列,其他节点从队列中获取更新操作并执行。这种方式可以解耦节点间的依赖关系,提高系统的可扩展性。

  3. 版本向量:每个数据项都有一个版本向量,用于记录数据在不同节点上的更新顺序。当一个节点完成数据更新后,它会更新版本向量并将更新操作发送到其他节点。其他节点根据版本向量判断是否需要执行更新操作,从而避免冲突和数据不一致。

  4. 补偿事务:在某些场景下,无法立即完成数据一致更新。这时可以采用补偿事务的方式,即在后续操作中修复数据不一致的问题。例如,用户在购物车中添加商品后,系统可能会因为网络延迟导致库存数据不一致。这时可以在用户提交订单时进行库存校验,如果发现库存不足,则提示用户重新选择商品或等待库存恢复。

最终一致性模型在很多分布式系统中得到了广泛应用,如Amazon的Dynamo、Google的Bigtable和Megastore等。这些系统通过最终一致性实现了高可用、高性能和可扩展的分布式存储和计算服务。

然而,最终一致性也存在一定的局限性。首先,它不能保证实时数据一致性,可能导致短时间内的数据不一致和冲突。其次,最终一致性的实现通常需要复杂的逻辑和额外的资源消耗,如消息队列、版本向量等。此外,在某些关键业务场景下,如金融交易、实时监控等,最终一致性可能无法满足业务需求,需要采用更严格的一致性模型。

总之,最终一致性作为一种分布式事务管理模型,通过降低即时一致性要求,提高了分布式系统的可用性和扩展性。然而,它也存在一定局限性,需要根据具体业务场景和需求进行权衡和选择。在未来的分布式系统设计中,最终一致性仍将是一种重要的数据一致性模型,与其他一致性模型共同支撑着分布式事务处理的发展。

2.0 CAP

CAP 是一个基于 .NET Standard 的 C# 库,它是一种处理分布式事务的解决方案,同样具有 EventBus 的功能,它具有轻量级、易使用、高性能等特点。

在我们构建 SOA 或者 微服务系统的过程中,我们通常需要使用事件来对各个服务进行集成,在这过程中简单的使用消息队列并不能保证数据的最终一致性, CAP 采用的是和当前数据库集成的本地消息表的方案来解决在分布式系统互相调用的各个环节可能出现的异常,它能够保证任何情况下事件消息都是不会丢失的。

你同样可以把 CAP 当做 EventBus 来使用,CAP提供了一种更加简单的方式来实现事件消息的发布和订阅,在订阅以及发布的过程中,你不需要继承或实现任何接口。

2.0 架构预览

根据上图我们可以看到 Micro-serviceA 微服务A 通过Cap 把数据提交给 Message Queue 也就是消息队列,另一个微服务B Micro-serviceB 通过Cap 连接 Message Queue 获取信息,然后执行。

CAP 支持主流的消息队列作为传输器,你可以按需选择下面的包进行安装:

PM> Install-Package DotNetCore.CAP.Kafka
PM> Install-Package DotNetCore.CAP.RabbitMQ
PM> Install-Package DotNetCore.CAP.AzureServiceBus
PM> Install-Package DotNetCore.CAP.AmazonSQS
PM> Install-Package DotNetCore.CAP.NATS
PM> Install-Package DotNetCore.CAP.RedisStreams
PM> Install-Package DotNetCore.CAP.Pulsar

 CAP 提供了主流数据库作为存储,你可以按需选择下面的包进行安装:

PM> Install-Package DotNetCore.CAP.SqlServer
PM> Install-Package DotNetCore.CAP.MySql
PM> Install-Package DotNetCore.CAP.PostgreSql
PM> Install-Package DotNetCore.CAP.MongoDB

3.0 .NET CORE 结合CAP实现最终一致性分布式事务

案例就不换了,还是以跨行转账作为例子,给大家详解这种架构。

3.1 准备工作(数据库,本文使用的是MySql)

3.1.1 数据模型

//模型
public class UserMoney
{public int id { get; set; }public int money { get; set; }public int trading_balance { get; set; }public int balance { get; set; }public int trymoney { get; set; }public string guid { get; set; }
}

3.1.2 DbContext

 public class DtmDbContext : DbContext{public DtmDbContext() { }public DtmDbContext(DbContextOptions<DtmDbContext> options) : base(options) { }public virtual DbSet<UserMoney> UserMoney { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){optionsBuilder.UseMySql("server=localhost;port=3307;user id=root;password=123;database=DTM_Test", ServerVersion.Parse("8.0.23-mysql")).UseLoggerFactory(LoggerFactory.Create(option =>{option.AddConsole();}));}protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.UseCollation("utf8_general_ci").HasCharSet("utf8");modelBuilder.Entity<UserMoney>(entity =>{entity.ToTable("UserMoney");});}}

3.1.3 数据库最终生成 

数据库不仅会生成我们的数据模型,还会多生成两张表,一张是事务发布表,一张是事务接收表。

3.2 Nuget引入

  <ItemGroup><PackageReference Include="DotNetCore.CAP" Version="5.1.0" /><PackageReference Include="DotNetCore.CAP.MySql" Version="5.1.0" /><PackageReference Include="DotNetCore.CAP.Dashboard" Version="5.1.0" /><PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="5.1.0" /></ItemGroup>

3.3 appsettings.json 

{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"AllowedHosts": "*","RabbitMQ": {"HostName": "192.168.157.157","UserName": "123","Password": "123","Port": "5672"},"MysqlConn": "server=localhost;port=3307;user id=root;password=123;database=DTM_Test"
}

3.4 docker启动一个RabbitMQ

docker run -d --name rabbitmq -e RABBITMQ_DEFAULT_USER=123 -e RABBITMQ_DEFAULT_PASS=123 -p 15672:15672 -p 5672:5672 rabbitmq:3-management

账号密码都是 123。之前的博文详细介绍过RabbitMQ 。(.NET CORE消息队列RabbitMQ-CSDN博客)

3.5 Program.cs

namespace Ncc_Cap_Service
{public class Program{public static void Main(string[] args){var builder = WebApplication.CreateBuilder(args);builder.Services.AddControllers();builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();#region NCC_CAPvar mysqlConn = builder.Configuration.GetValue<string>("MysqlConn")!;var rabbitMQHostName = builder.Configuration.GetValue<string>("RabbitMQ:HostName")!;var rabbitMQUserName = builder.Configuration.GetValue<string>("RabbitMQ:UserName")!;var rabbitMQPassword = builder.Configuration.GetValue<string>("RabbitMQ:Password")!;var rabbitMQPort = builder.Configuration.GetValue<int>("RabbitMQ:Port")!;builder.Services.AddCap(x =>{x.UseMySql(mysqlConn);x.UseRabbitMQ(x =>{x.HostName = rabbitMQHostName;x.UserName = rabbitMQUserName;x.Password = rabbitMQPassword;x.Port = rabbitMQPort;});//重试次数x.FailedRetryCount = 10;//失败的重试间隔x.FailedRetryInterval = 60;});#region   注册DbContextbuilder.Services.AddDbContext<DtmDbContext>(options =>{options.UseMySql(mysqlConn, ServerVersion.Parse("8.0.23-mysql"));});#endregion#endregionvar app = builder.Build();if (app.Environment.IsDevelopment()){app.UseSwagger();app.UseSwaggerUI();}app.UseAuthorization();app.MapControllers();app.Run();}}
}

3.6 用户1 API控制器

using DotNetCore.CAP;
using DTM_EF;
using DTM_EF.Model;
using Microsoft.AspNetCore.Mvc;namespace Ncc_Cap_Service.Controllers
{[ApiController][Route("[controller]")]public class CapUserAController : ControllerBase{private readonly ILogger<CapUserAController> _logger;private readonly ICapPublisher _iCapPublisher;private readonly DtmDbContext _dtmDbContext;const string PublishName = "NCC_CAP.CapUserAController.CAP";public CapUserAController(ILogger<CapUserAController> logger,ICapPublisher iCapPublisher,DtmDbContext dtmDbContext){_logger = logger;_iCapPublisher = iCapPublisher;_dtmDbContext = dtmDbContext;}/// <summary>/// 普通的Cap/// </summary>/// <param name="trymoney"></param>/// <returns></returns>/// <exception cref="Exception"></exception>[Route("/Cap")][HttpPost]public async Task<IActionResult> CapTransaction(int trymoney){//获取用户账户信息var UserMoney = _dtmDbContext.Set<UserMoney>().Where(c => c.id == 1).FirstOrDefault();if (UserMoney is null) throw new Exception($"用户{1}--不存在");if (UserMoney.money + trymoney < 0) throw new Exception($"用户{1}--金额不足");//前序判断都通过,修改信息准备提交   UserMoney!.money += trymoney;_dtmDbContext.SaveChanges();await _iCapPublisher.PublishAsync(PublishName, -trymoney);return Ok();}}
}

3.7 用户2 API控制器

using DotNetCore.CAP;
using DTM_EF;
using Microsoft.AspNetCore.Mvc;
using DTM_EF.Model;
using Microsoft.EntityFrameworkCore;namespace Ncc_Cap_Service.Controllers
{public class CAPUserBController{private readonly ILogger<CAPUserBController> _logger;private readonly DtmDbContext _dtmDbContext;public CAPUserBController(ILogger<CAPUserBController> logger,DtmDbContext dtmDbContext){_logger = logger;_dtmDbContext = dtmDbContext;}[NonAction][CapSubscribe("NCC_CAP.CapUserAController.CAP")]public async Task CapTransaction(int trymoney){//获取用户账户信息var UserMoney = await _dtmDbContext.Set<UserMoney>().Where(c => c.id == 2).FirstOrDefaultAsync();if (UserMoney is null) throw new Exception($"用户{1}--不存在");if (UserMoney.money + trymoney < 0) throw new Exception($"用户{1}--金额不足");//前序判断都通过,修改信息准备提交   UserMoney!.money += trymoney;_dtmDbContext.SaveChanges();}}
}

4.0 测试运行

用户1和2都先初始化金额1000元。

转100元: 

 

5.0 消息附加头信息的CAP

CAP可以使用一个 Dictionary<string, string> 字典类型的作为下游事务的附加消息头。

5.1 上游事务代码

        /// <summary>/// Cap--消息附加的头信息/// </summary>/// <param name="trymoney"></param>/// <returns></returns>/// <exception cref="Exception"></exception>[Route("/Cap/Header")][HttpPost]public async Task<IActionResult> CapHeaderTransaction(int trymoney){//获取用户账户信息var UserMoney = _dtmDbContext.Set<UserMoney>().Where(c => c.id == 1).FirstOrDefault();if (UserMoney is null) throw new Exception($"用户{1}--不存在");if (UserMoney.money + trymoney < 0) throw new Exception($"用户{1}--金额不足");//前序判断都通过,修改信息准备提交   UserMoney!.money += trymoney;_dtmDbContext.SaveChanges();IDictionary<string, string> header = new Dictionary<string, string>();header.Add("转账发起人", "1");header.Add("转账接受人", "2");header.Add("转账金额", trymoney.ToString());await _iCapPublisher.PublishAsync(PublishHeaderName, -trymoney, header);return Ok();}

5.2 下游事务代码

  [NonAction][CapSubscribe("NCC_CAP.CapUserAController.CapHeader")]public async Task CapHeaderTransaction(int trymoney, [FromCap] CapHeader header){#region header信息_logger.LogInformation($"转账发起人:{header["转账发起人"]}");_logger.LogInformation($"转账接受人:{header["转账接受人"]}");_logger.LogInformation($"转账金额:{header["转账金额"]}");#endregion//获取用户账户信息var UserMoney = await _dtmDbContext.Set<UserMoney>().Where(c => c.id == 2).FirstOrDefaultAsync();if (UserMoney is null) throw new Exception($"用户{1}--不存在");if (UserMoney.money + trymoney < 0) throw new Exception($"用户{1}--金额不足");//前序判断都通过,修改信息准备提交   UserMoney!.money += trymoney;_dtmDbContext.SaveChanges();}

5.3 测试运行

我们在下游事务中,接受上游事务的头信息,并输出。

6.0 使用消息队列中不同的交换机和队列 

服务实例在启动时会自动向RabbitMQ注册一个名为 cap.default.router 的交换机。这是最普通的交换机,如果您想设置其他种类的交换机请参考(.NET CORE消息队列RabbitMQ-CSDN博客),可以先建立一个交换机或队列。然后代码指向这个交换机或队列。

  builder.Services.AddCap(x =>{//设置队列名称x.DefaultGroupName = "queue.name";x.UseMySql(mysqlConn);x.UseRabbitMQ(x =>{x.HostName = rabbitMQHostName;x.UserName = rabbitMQUserName;x.Password = rabbitMQPassword;x.Port = rabbitMQPort;//设置交换机名称--就是可以先设置交换机类型--扇形模式,发布订阅模式,主题模式,路由模式。x.ExchangeName = "Exchange";});//重试次数x.FailedRetryCount = 10;//失败的重试间隔x.FailedRetryInterval = 60;});

如果设置了队列名称,下游服务怎么区分是哪个队列的事务呢?我们可以在代码中指向对应的队列名字。

        [NonAction][CapSubscribe("NCC_CAP.CapUserAController.CapQueue", Group = "queue.name")]public async Task CapQueueTransaction(int trymoney){//获取用户账户信息var UserMoney = await _dtmDbContext.Set<UserMoney>().Where(c => c.id == 2).FirstOrDefaultAsync();if (UserMoney is null) throw new Exception($"用户{1}--不存在");if (UserMoney.money + trymoney < 0) throw new Exception($"用户{1}--金额不足");//前序判断都通过,修改信息准备提交   UserMoney!.money += trymoney;_dtmDbContext.SaveChanges();}

通过Group = "queue.name" 直接指向了队列名称。

7.0 重试失败回调

正常情况下,分布式事务正常执行完毕之后会在数据库中进行标识。

但是当出现网络延迟、服务宕机时。CAP就会根据设置时间,进行指定次数的重试。全部重试均失败之后,会执行一个回调。

  builder.Services.AddCap(x =>{x.UseMySql(mysqlConn);x.UseRabbitMQ(x =>{x.HostName = rabbitMQHostName;x.UserName = rabbitMQUserName;x.Password = rabbitMQPassword;x.Port = rabbitMQPort;});//重试次数x.FailedRetryCount = 10;//失败的重试间隔x.FailedRetryInterval = 60;x.FailedThresholdCallback = failed =>{//当一个cap重试失败之后执行的回调(这里可以执行邮件通知,错误短信发送给运维工程师,进行人工介入处理)var logger = failed.ServiceProvider.GetService<ILogger<Program>>()!;logger.LogError($@"MessageType {failed.MessageType} 失败了, 重试了 {x.FailedRetryCount} 次, 消息参数: {failed.Message.Value}");};});

小结:

本文给出了一个完整的 CAP 事务方案,是一个可以实际运行的 CAP ,并解决高并发的使用场景,您只需要在这个示例的基础上进行简单修改,就能够用于解决您的真实问题。

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

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

相关文章

【漏洞复现】极简云 download.php 接口处存在任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

OpenHarmony实战:小型系统平台驱动移植

在这一步&#xff0c;我们会在源码目录//device/vendor_name/soc_name/drivers目录下创建平台驱动。 建议的目录结构&#xff1a; device ├── vendor_name │ ├── drivers │ │ │ ├── common │ │ │ ├── Kconfig # 厂商驱动内核菜单入口 │ …

武汉星起航电子商务公司领航跨境电商新纪元,助力品牌走向全球

在全球经济一体化的时代背景下&#xff0c;跨境电商正成为推动国际贸易增长的重要力量。武汉星起航电子商务有限公司&#xff0c;作为一家专注于提供一站式解决方案的跨境电商服务商&#xff0c;凭借其丰富的实战经验和专业团队&#xff0c;在行业中取得了令人瞩目的成绩。 自…

前端学习<四>JavaScript基础——02-JavaScript入门:hello world

开始写第一行 JavaScript&#xff1a;hello world JS 代码的书写位置在哪里呢&#xff1f;这个问题&#xff0c;也可以理解成&#xff1a;引入 JS 代码&#xff0c;有哪几种方式&#xff1f;有三种方式&#xff1a;&#xff08;和 CSS 的引入方式类似&#xff09; 行内式&…

前端(动态雪景背景+动态蝴蝶)

1.CSS样式 <style>html, body, a, div, span, table, tr, td, strong, ul, ol, li, h1, h2, h3, p, input {font-weight: inherit;font-size: inherit;list-style: none;border-spacing: 0;border: 0;border-collapse: collapse;text-decoration: none;padding: 0;margi…

014——超声波模块驱动开发Plus(基于I.MX6uLL、SR04和poll机制)

目录 一、基础知识 二、分析为什么打印会影响中断 三、驱动程序 四、应用程序 五、验证及其它 一、基础知识 013——超声波模块驱动开发&#xff08;基于I.MX6uLL与SR04&#xff09;-CSDN博客 二、分析为什么打印会影响中断 asmlinkage __visible int printk(const ch…

戴尔电脑Dell SupportAssist占用内存高,卸载Dell SupportAssist

咨询戴尔客服了解到&#xff0c;SupportAssist是机器出厂自带的一款应用&#xff0c;主要的功能是可以检查驱动更新以及做一些硬件方面的健康检测&#xff0c;有时候后台运行可能会导致进程占用内存比较大&#xff0c;导致访问被的应用崩溃。 咨询卸载不影响之后&#xff0c;然…

【Python系列】 yaml中写入数据

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

使用pytorch构建一个初级的无监督的GAN网络模型

在这个系列中将系统的构建GAN及其相关的一些变种模型&#xff0c;来了解GAN的基本原理。本片为此系列的第一篇&#xff0c;实现起来很简单&#xff0c;所以不要期待有很好的效果出来。 第一篇我们搭建一个无监督的可以生成数字 (0-9) 手写图像的 GAN&#xff0c;使用MINIST数据…

BugKu:Simple SSTI

1.进入此题 2.查看源代码 可以知道要传入一个名为flag的参数&#xff0c;又说我们经常设置一个secret_key 3.flask模版注入 /?flag{{config.SECRET_KEY}} 4.学有所思 4.1 什么是flask&#xff1f; flask是用python编写的一个轻量web开发框架 4.2 SSTI成因&#xff08;SST…

RIP协议(路由信息协议)

一、RIP协议概述 RIP协议&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;是一种基于距离矢量的内部网关协议&#xff0c;即根据跳数来度量路由开销&#xff0c;进行路由选择。 相比于其它路由协议&#xff08;如OSPF、ISIS等&#xff09;&#…

Spring Cloud微服务入门(二)

微服务的技术栈 服务治理&#xff1a; 服务注册、发现、调用。 负载均衡&#xff1a; 高可用、集群部署。 容错&#xff1a; 避免雪崩、削峰、服务降级。 消息总线&#xff1a; 消息队列、异步通信&#xff0c;数据一致性。 网关&#xff1a; 校验路径、请求转发、服务集成…

使用Python获取红某书笔记详情并批量无水印下载

根据红某手最新版 请求接口必须要携带x-s x-s-c x-t,而调用官方接口又必须携带cookie,缺一不可,获取笔记详情可以通过爬取网页的形式获取&#xff0c;虽然也是无水印&#xff0c;但是一些详情信息只能获取大概&#xff0c;并不是详细的数值&#xff0c;因此既不想自己破解x-s x…

UE5 C++ LevelSequence

前言 最近在用UE C做一些功能&#xff0c;用到了Level Sequence功能&#xff0c;但是看了下UE官方论坛包括一些文章基本没有关于C 处理Level Sequence 这块内容&#xff0c;有的也是一些修改或者源码原理的一些内容分析&#xff0c;接下来我就把我新建Sequence包括一些库的调用…

# 达梦数据库知识点

达梦数据库知识点 测试数据 -- SYSDBA.TABLE_CLASS_TEST definitionCREATE TABLE SYSDBA.TABLE_CLASS_TEST (ID VARCHAR(100) NOT NULL,NAME VARCHAR(100) NULL,CODE VARCHAR(100) NULL,TITLE VARCHAR(100) NULL,CREATETIME TIMESTAMP NULL,COLUMN1 VARCHAR(100) NULL,COLUMN…

云容器引擎CCE弹性伸缩

CCE弹性伸缩介绍 CCE的弹性伸缩能力分为如下两个维度&#xff1a; 工作负载弹性伸缩&#xff1a;即调度层弹性&#xff0c;主要是负责修改负载的调度容量变化。例如&#xff0c;HPA是典型的调度层弹性组件&#xff0c;通过HPA可以调整应用的副本数&#xff0c;调整的副本数会…

ShardingJdbc+Mybatis实现多数据源

Mybatis多数据源 这个是对shardingjdbc应用的一个升级&#xff0c;如果对于shardingjdbc的整合还没看过之前的文章的&#xff0c;可以先看看文章https://blog.csdn.net/Think_and_work/article/details/137174049?spm1001.2014.3001.5501 整合步骤 1、依赖 和全新项目的单…

翻译: 硅谷软件工程师面试:准备所需的一切

没有人有时间去做成百上千道LeetCode题目&#xff0c;好消息是你实际上并不需要做那么多题目就能够在FAANG公司找到工作&#xff01; 我曾经在Grab工作&#xff0c;这是东南亚的一家共享出行公司&#xff0c;但我对工作感到沮丧&#xff0c;想要进入FAANG公司&#xff0c;但我…

Linux------一篇博客了解Linux最常用的指令

&#x1f388;个人主页&#xff1a;靓仔很忙i &#x1f4bb;B 站主页&#xff1a;&#x1f449;B站&#x1f448; &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;Linux &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#…

LeetCode-199. 二叉树的右视图【树 深度优先搜索 广度优先搜索 二叉树】

LeetCode-199. 二叉树的右视图【树 深度优先搜索 广度优先搜索 二叉树】 题目描述&#xff1a;解题思路一&#xff1a;广度优先搜索解题思路二&#xff1a;深度优先搜索解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它…