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

news2024/10/10 4:19:48

目录

引言:

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_CAP

            var 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   注册DbContext

            builder.Services.AddDbContext<DtmDbContext>(options =>
            {
                options.UseMySql(mysqlConn, ServerVersion.Parse("8.0.23-mysql"));
            });

            #endregion

            #endregion

            var 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.coloradmin.cn/o/1568164.html

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

相关文章

FressRTOS_day4:2024/4/4

1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 二进制信号量的数值只有0和1。&#xff08;用于共享资源的访问&#xff09;&#xff1b;而计数型信号量的值一般是大于或者等于2&#xff08;用于生产者和消费者模型&#xff09; 2.使用计数型信号量…

安装Schedule库的方法最终解答!_Python第三方库

安装Python第三方库Schedule 我的环境&#xff1a;Window10&#xff0c;Python3.7&#xff0c;Anaconda3&#xff0c;Pycharm2023.1.3 Schedule库 Schedule 是一个轻量级、功能强大而灵活的任务调度工具库&#xff0c;用于在指定的时间间隔内执行任务。为用户提供了简单易用的…

树(Tree) - 概念与基础

树的基本概念 树(Tree)是一种重要的数据结构&#xff0c;它在计算机科学中被广泛应用于各种算法和程序中。树是由节点(node)组成的层次结构&#xff0c;其中每个节点都有一个父节点&#xff0c;除了根节点外&#xff0c;每个节点都有零个或多个子节点。树的一个关键特点是没有…

Java云联his系统,支持电子病历四级,医院信息管理系统源码

SaaS模式Java语言开发的云HIS系统源码&#xff0c;支持电子病历四级&#xff0c;系统充分考虑了模板化、配置化、智能化、扩展化等设计方法&#xff0c;覆盖了基层医疗机构的主要工作流程&#xff0c;能够与监管系统有序对接&#xff0c;并能满足未来系统扩展的需要。 云HIS系统…

matlab使用教程(33)—求解时滞微分方程(1)

1.时滞微分方程(DDE)的分类 时滞微分方程 (DDE) 是当前时间的解与过去时间的解相关的常微分方程。该时滞可以固定不变、与时间相关、与状态相关或与导数相关。要开始积分&#xff0c;通常必须提供历史解&#xff0c;以便求解器可以获取初始积分点之前的时间的解。 1.1常时滞 D…

二维动画制作软件 Animate 2024 for mac激活版

Animate 2024 for Mac是一款功能强大的二维动画制作软件&#xff0c;专为Mac用户打造。它提供了丰富的动画编辑功能&#xff0c;使用户能够轻松创建出生动逼真的动画作品。无论是短片、广告还是游戏等应用领域&#xff0c;Animate 2024都能发挥出出色的表现。 软件下载&#xf…

ArcGIS Pro导出布局时去除在线地图水印

目录 一、背景 二、解决方法 一、背景 在ArcGIS Pro中经常会用到软件自带的在线地图&#xff0c;但是在导出布局时&#xff0c;图片右下方会自带地图的水印 二、解决方法 解决方法&#xff1a;添加动态文本--服务图层制作者名单&#xff0c;然后在布局中选定位置添加 在状…

基于 Docker 的 python grpc quickstart

工作之后一直使用的 RPC 框架是 Apache 的 thrift&#xff0c;现在发现 grpc 更流行&#xff0c;所以也要学习一下&#xff0c;先来简单的跑一下 demo。在本地安装运行也很方便&#xff0c;不过因为有了 docker&#xff0c;所以在 docker 里面安装运行隔离性更好&#xff0c;顺…

多线程--深入探究多线程的重点,难点以及常考点线程安全问题

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

【机器学习】K-近邻算法(KNN)介绍、应用及文本分类实现

一、引言 1.1 K-近邻算法&#xff08;KNN&#xff09;的基本概念 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基于实例的学习算法&#xff0c;它利用训练数据集中与待分类样本最相似的K个样本的类别来判断待分类样本所属的类别。KNN算法…

STM32工程 如何设置堆栈大小(Heap和Stack)

方法1&#xff1a;通过CubeMX、CubeIDE 配置 方法2&#xff1a;直接在启动文件中修改 &#xff08;适合所有Keil工程&#xff09; Heap、Stack的值大小&#xff0c;不管使用哪种开发环境&#xff0c;它俩都肯定在启动文件中。 可以通过CtrlF&#xff0c;搜索: Heap&#xff0…

iOS开发之Swift标识符

iOS开发之Swift标识符 在iOS开发中&#xff0c;使用Swift语言时&#xff0c;标识符是用来命名变量、常量、函数、类、结构体、枚举等程序实体的&#xff1b; 这些标识符使得Swift代码更加清晰、易于理解和维护。 一、变量与常量&#xff1a;var、let var代表variable&#…

算法练习—day1

title: 算法练习—day1 date: 2024-04-03 21:49:55 tags: 算法 categories:LeetCode typora-root-url: 算法练习—day1 网址&#xff1a;https://red568.github.io 704. 二分查找 题目&#xff1a; 题目分析&#xff1a; 左右指针分别为[left,right]&#xff0c;每次都取中…

X86平台下Linux系统安装部署KVM,以及KVM一些配置

环境&#xff1a;银河麒麟SP1-V2303系统&#xff0c;X86架构&#xff0c;目标虚拟机为win10 一、安装 1.APT源安装 sudo apt install qemu qemu-kvm bridge-utils virt-manager libguestfs-tools qemu-system qemu-efi qemu-utils libvirt-clients libvirt-daemon-system …

Centos7安装Docker与Docker-compose【图文教程】

个人记录 查看一下系统是否已经安装了Docker yum list installed | grep docker如下图代表没有安装Docker 卸载已有Docker yum remove docker docker-common docker-selinux docker-engine切换目录 cd /etc/yum.repos.d/查看当前目录所有的镜像源 ll安装yum-util与devi…

去班味的尽头是风险管理

运维工程师的“班味”是从风险管理就加重的。 什么是班味呢&#xff1f;指的是打工人身上特有的疲惫气质&#xff0c;面色憔悴、双目无神和腰酸背痛都是“班味”的显著表现。习惯性回复“收到&#xff0c;马上来”、不自觉唉声叹气、下班也提不起精神等症状&#xff0c;则说明…

GIS水文分析计算流向学习

1 初步操作 流向&#xff0c;即水文表面水的流向&#xff1b; 水文分析的很多功能需要基于流向栅格&#xff1b; 在 SuperMap 中&#xff0c;对中心栅格的8个邻域栅格进行编码&#xff1b; 每一个中心栅格的水流方向都由这八个值中的某一个值来确定&#xff1b; 我还没弄懂水…

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

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

2012年认证杯SPSSPRO杯数学建模C题(第一阶段)碎片化趋势下的奥运会商业模式全过程文档及程序

2012年认证杯SPSSPRO杯数学建模 C题 碎片化趋势下的奥运会商业模式 原题再现&#xff1a; 从 1984 年的美国洛杉矶奥运会开始&#xff0c;奥运会就不在成为一个“非卖品”&#xff0c;它在向观众诠释更高更快更强的体育精神的同时&#xff0c;也在攫取着巨大的商业价值&#…

《C++程序设计》阅读笔记【1-函数】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;《C程序设计》阅读笔记 本文对应的PDF源文件请关注微信公众号程序员刘同学&#xff0c;回复C程序设计获取下载链接。 1 函数1.1 概述1.2 函数定义、声明、原型1.3 变量1.3.1 全局变量1.3.…