分库分表之历史表如何选择最佳分片路由规则

前言

先别急着关闭,我相信这篇文章应该是所有讲分表分库下的人都没有和你们讲过的一种分片模式,外面的文章基本上都是教你如何从零开始分片,现在我将讲解的是如何从1+开始分片

项目地址

  • github地址 https://github.com/dotnetcore/sharding-core

  • gitee地址 https://gitee.com/dotnetchina/sharding-core

背景

首先我相信很多人使用分表分库一定有这么一个情况,就是目前我们的系统有一张表可能会非常的庞大,然后希望通过分片技术将其进行水平拆分,但是如何拆分或者说如何拆分可以保证让目前的数据性能达到最优解,是一个很值得探讨的问题。

这边简单举一个例子,譬如我们的订单表,目前我们的订单表可能已经达到一定的数量级了比如百万或者千万级别了,可能光是简单的查询性能是很高的,但是新增订单可能就没这么乐观了,随着索引的增多新增的数目也会不断地变慢,不仅仅是查询一个维度迫使你选择分表。
基于这个简单的案例我们来延伸一下如何水平拆分成为目前最关键的一个问题。

按月份表

这边我们如果将订单表按月进行水平分表那么我们可以了解到哪怕是随着时间的推移,数据库的瓶颈也会慢慢的变成容量的瓶颈了而不仅仅是单表的上限了。

假设我们这边的订单是从2016年开始的,一直到2022年3月我们发现订单表可以分成近70张表,而且针对分片我们有个天然的优势就是按时间分片可以拥有顺序查询这一特性,所以说这么来分片将是一个比较完美的实现

但是随着系统的运行我们发现这种分片方式虽然看着比较完美,但是存在一个很严重的问题就是数据的分布不均匀,因为可能系统刚上线那段时间我们的系统使用量并不是那么多,导致了系统内部的订单数量不会那么的多,所以虽然我们把订单表按月来分了,但是之前的历史数据因为使用量的原因导致按月分表的每张表里面可能拥有的数据很少很少。

导致了分片在各个表中的数据分布极其不均匀。会造成很多不必要的跨表聚合问题,那么我们希望的方案是什么呢?

多维度分片

什么是多维度分片

  • 2018年及以前的数据我们将其归集到Order_History表中

  • 2019到2021年份的我们按年分表

  • 2022年开始的数据我们按月分表

通过上述纬度分片我们保证了各个分片表之间的数据都是区域平均,并且不会产生过多的跨分片聚合。

时间分片遇到的问题

随着系统的不断升级迭代,我们的系统也慢慢地拆分成了多个微服务,在各个微服务之间针对订单的调用我们将会传递一个订单id作为各个微服务之间交互的手段。

但是也是因为这种方式,让我们认识到分片如果按时间来分配那么微服务之间交互的id那么如果不是雪花id那么最好是带时间的或者说可以反解析出创建时间的。

但是因为订单历史原因导致2022年之前的订单全部采用的是guid那种无序的id,分表后我们将无法通过无序的guid来进行分片路由的指定,没办法用多字段分片辅助路由这个特性了。

针对这个问题我们该如何解决呢?

引入redis来辅助分片

虽然我们没办法通过历史订单id,guid来进行路由的辅助,但是我们可以借助第三方高速缓存来实现乱序id在分片环境下的辅助路由。

具体我们的实现原理是什么呢

  • 采用订单id进行辅助路由

  • 将历史数据全部导入到redis,redis只需要存储id和时间即可

  • 程序利用辅助路由来实现乱序guid进行实际分片辅助

直接进入实战

第一步安装依赖

# ShardingCore核心框架 版本6.4.2.4+
PM> Install-Package ShardingCore
# 数据库驱动这边选择的是mysql的社区驱动 efcore6最新版本即可
PM> Install-Package Pomelo.EntityFrameworkCore.MySql
# redis驱动
PM> Install-Package CSRedisCore

第二步添加订单表和数据库上下文

添加订单表

public class Order{public string Id { get; set; }public string Title { get; set; }public string Description { get; set; }public OrderStatusEnum OrderStatus { get; set; }public DateTime? PayTime { get; set; }public DateTime CreateTime { get; set; }}public enum OrderStatusEnum{NoPay=1,Paid=1<<1}

添加数据库上下文和Order对象的数据库映射

public class MyDbContext:AbstractShardingDbContext,IShardingTableDbContext{public MyDbContext(DbContextOptions<MyDbContext> options) : base(options){}public IRouteTail RouteTail { get; set; }protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.Entity<Order>(builder =>{builder.HasKey(o => o.Id);builder.Property(o => o.Id).HasMaxLength(50).IsRequired().IsUnicode(false);builder.Property(o => o.Title).HasMaxLength(50).IsRequired();builder.Property(o => o.Description).HasMaxLength(255).IsRequired();builder.Property(o => o.OrderStatus).HasConversion<int>();builder.ToTable(nameof(Order));});}}

第三步添加按创建时间按月路由

public class OrderRoute:AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>{public override void Configure(EntityMetadataTableBuilder<Order> builder){builder.ShardingProperty(o => o.CreateTime);}public override bool AutoCreateTableByTime(){return true;}public override DateTime GetBeginTime(){return new DateTime(2016, 1, 1);}}

第四步初始化配置和数据

var builder = WebApplication.CreateBuilder(args);// Add services to the container.
ILoggerFactory efLogger = LoggerFactory.Create(builder =>
{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();
});
builder.Services.AddControllers();
builder.Services.AddShardingDbContext<MyDbContext>().AddEntityConfig(o =>{o.CreateShardingTableOnStart = true;o.EnsureCreatedWithOutShardingTable = true;o.AddShardingTableRoute<OrderRoute>();}).AddConfig(o =>{o.ConfigId = "c1";o.UseShardingQuery((conStr, b) =>{b.UseMySql(conStr, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});o.UseShardingTransaction((conn, b) =>{b.UseMySql(conn, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});o.AddDefaultDataSource("ds0", "server=127.0.0.1;port=3306;database=ShardingHistoryDB;userid=root;password=root;");o.ReplaceTableEnsureManager(sp => new MySqlTableEnsureManager<MyDbContext>());}).EnsureConfig();var app = builder.Build();app.Services.GetRequiredService<IShardingBootstrapper>().Start();
using (var scope = app.Services.CreateScope())
{var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();if (!myDbContext.Set<Order>().Any()){List<Order> orders = new List<Order>();var order2016s = createOrders(2016,50);var order2017s = createOrders(2017,100);var order2018s = createOrders(2018,200);var order2019s = createOrders(2019,300);var order2020s = createOrders(2020,300);var order2021s = createOrders(2021,300);var order2022s = createOrders(2022,90);orders.AddRange(order2016s);orders.AddRange(order2017s);orders.AddRange(order2018s);orders.AddRange(order2019s);orders.AddRange(order2020s);orders.AddRange(order2021s);orders.AddRange(order2022s);myDbContext.AddRange(orders);myDbContext.SaveChanges();}
}
app.MapControllers();app.Run();List<Order> createOrders(int year,int count)
{var beginTime = new DateTime(year, 1, 1, 1, 1,1);var orders = Enumerable.Range(1,count).Select((o, i) =>{var createTime = beginTime.AddDays(i);return new Order(){Id = year<2022?Guid.NewGuid().ToString("n"):$"{createTime:yyyyMMddHHmmss}",CreateTime = createTime,Title = year+"年订单:" + i,Description = year+"年订单详细描述:" + i,OrderStatus = i % 7 == 0 ? OrderStatusEnum.NoPay : OrderStatusEnum.Paid,PayTime = i % 7 == 0 ? null : createTime.AddSeconds(new Random().Next(1, 300)),};}).ToList();return orders;
}

第五步开启程序

SELECT table_name,table_rows FROM information_schema.tables
WHERE TABLE_SCHEMA = 'ShardingHistoryDB' ORDER BY TABLE_SCHEMA DESC;

通过上述sql语句我们可以查询出对应表内有多少数据量
570ae40567580ffe8f9fb8e2d30965ad.png

通过截图我们可以看到数据分布相对恨不均匀导致很多表的数据过少(这边是做了一个测试)
所以当我们进行查询的时候,有很大的可能性会做落到无关表上,并且因为历史原因导致我们在2022年之前的数据订单id都是采用的是guid,这让我们无法通过guid来实现分表的辅助查询。

优化数据表分布

因为上述原因我们这边需要进行表数据的分布优化,具体我们采用的是现实将2018年包括2018年的数据全部存入一张叫做history的表,然后针对2019、2020、2021表进行按年分表,剩下的订单按月分表

目前市面上很少有框架支持这么复杂的订单路由所以我们接下来就需要进行实现

第一步改写路由

  • 改写2018年之前的

  • 改写近期按年分表

  • 剩下的按月分表

public class OrderRoute:AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>{public override void Configure(EntityMetadataTableBuilder<Order> builder){builder.ShardingProperty(o => o.CreateTime);}public override bool AutoCreateTableByTime(){return true;}public override DateTime GetBeginTime(){return new DateTime(2016, 1, 1);}//系统启动需要知道数据库应该有哪些表public override List<string> GetAllTails(){var tails=new List<string>();tails.Add("History");tails.Add("2019");tails.Add("2020");tails.Add("2021");var beginTime = ShardingCoreHelper.GetCurrentMonthFirstDay(new DateTime(2022,1,1));//提前创建表var nowTimeStamp =ShardingCoreHelper.GetCurrentMonthFirstDay(DateTime.Now);if (beginTime > nowTimeStamp)throw new ArgumentException("begin time error");var currentTimeStamp = beginTime;while (currentTimeStamp <= nowTimeStamp){var tail = ShardingKeyToTail(currentTimeStamp);tails.Add(tail);currentTimeStamp = ShardingCoreHelper.GetNextMonthFirstDay(currentTimeStamp);}return tails;}private static readonly DateTime historyTime = new DateTime(2019, 1, 1);private static readonly DateTime yearTime = new DateTime(2022, 1, 1);public override string ShardingKeyToTail(object shardingKey){var dateTime = (DateTime)shardingKey;if (dateTime < historyTime){return "History";}if (dateTime < yearTime){return $"{dateTime:yyyy}";}return base.ShardingKeyToTail(shardingKey);}private static readonly HistoryMinComparer _historyMinComparer = new HistoryMinComparer();public override Expression<Func<string, bool>> GetRouteToFilter(DateTime shardingKey, ShardingOperatorEnum shardingOperator){var t = TimeFormatToTail(shardingKey);switch (shardingOperator){case ShardingOperatorEnum.GreaterThan:case ShardingOperatorEnum.GreaterThanOrEqual:return tail => _historyMinComparer.Compare(tail, t) >= 0;case ShardingOperatorEnum.LessThan:{// var currentMonth = ShardingCoreHelper.GetCurrentMonthFirstDay(shardingKey);// //处于临界值 o=>o.time < [2021-01-01 00:00:00] 尾巴20210101不应该被返回// if (currentMonth == shardingKey)//     return tail => _historyMinComparer.Compare(tail, t) < 0;return tail => _historyMinComparer.Compare(tail, t) <= 0;}case ShardingOperatorEnum.LessThanOrEqual:return tail => _historyMinComparer.Compare(tail, t) <= 0;case ShardingOperatorEnum.Equal: return tail => tail == t;default:{
#if DEBUGConsole.WriteLine($"shardingOperator is not equal scan all table tail");
#endifreturn tail => true;}}}}public class HistoryMinComparer:IComparer<string>{private const string History = "History";public int Compare(string? x, string? y){if (!Object.Equals(x, y)){if (History.Equals(x))return -1;if (History.Equals(y))return 1;}return Comparer<string>.Default.Compare(x, y);}}

这边我们改写路由将原先的按月分表改成2019年之前存入历史,2022年之前按年之后按月来实现,并且针对表后缀实现了一个历史记录History最小的比较器

第二步从新跑一边数据

删除原先的数据库从新启动程序

SELECT table_name,table_rows FROM information_schema.tables
WHERE TABLE_SCHEMA = 'ShardingHistoryDB' ORDER BY TABLE_SCHEMA DESC;

d1ee3c49a284905d5679c01d661d24d1.png

针对这次优化我们发现我们大大的减少了数据库表的分片数量,可以有效的提高数据分布在分片环境下的存储。

第三步编写查询

编写查询控制器
0042dfb513b68e583745cd17869aadaf.png
首先两个按时间查询复核预期
05603516014967af2c79caf604e9abc6.png
因为id是guid历史原因并且框架没有对id配置辅助路由所以会进行全分片扫描
82b829f39168c221d2da25d1a98d858c.png
出现这种情况会导致程序系统稳定性不足,在分布式环境下查询会变得很复杂

历史GUID辅助分片

首先因为系统历史原因导致系统的订单id使用的是乱序guid,乱序guid在程序中很难对时间分片进行优化,所以这边采用引入三方框架redis,来实现,最新数据将采用雪花id(本次演示采用格式化时间)

第一步将历史数据存入到redis,分别对应到具体表后缀

//....
RedisHelper.Initialization(new CSRedis.CSRedisClient("127.0.0.1:6379,defaultDatabase=0,poolsize=10,ssl=false,writeBuffer=10240"));app.Services.GetRequiredService<IShardingBootstrapper>().Start();
using (var scope = app.Services.CreateScope())
{var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();if (!myDbContext.Set<Order>().Any()){List<Order> orders = new List<Order>();//.....myDbContext.AddRange(orders);myDbContext.SaveChanges();var virtualTableManager = app.Services.GetRequiredService<IVirtualTableManager<MyDbContext>>();var virtualTable = virtualTableManager.GetVirtualTable(typeof(Order));foreach (var order in orders.Where(o=>o.CreateTime<new DateTime(2022,1,1))){var physicTables = virtualTable.RouteTo(new ShardingTableRouteConfig(shardingKeyValue:order.CreateTime));var tail = physicTables[0].Tail;RedisHelper.Set(order.Id, tail);}}
}
app.MapControllers();app.Run();

第二步编写路由多字段分表

public class OrderRoute:AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>{public override void Configure(EntityMetadataTableBuilder<Order> builder){builder.ShardingProperty(o => o.CreateTime);builder.ShardingExtraProperty(o => o.Id);}//.....public override Expression<Func<string, bool>> GetExtraRouteFilter(object shardingKey, ShardingOperatorEnum shardingOperator, string shardingPropertyName){if (shardingPropertyName == nameof(Order.Id)){return GetOrderNoRouteFilter(shardingKey, shardingOperator);}return base.GetExtraRouteFilter(shardingKey, shardingOperator, shardingPropertyName);}/// <summary>/// 订单编号的路由/// </summary>/// <param name="shardingKey"></param>/// <param name="shardingOperator"></param>/// <returns></returns>private Expression<Func<string, bool>> GetOrderNoRouteFilter(object shardingKey,ShardingOperatorEnum shardingOperator){//将分表字段转成订单编号var orderNo = shardingKey?.ToString() ?? string.Empty;//判断订单编号是否是我们符合的格式if (!CheckOrderNo(orderNo, out var orderTime)){//如果格式不一样就查询redisvar t = RedisHelper.Get(shardingKey.ToString());if (string.IsNullOrWhiteSpace(t)){return tail => false;}return tail => tail==t;}//当前时间的tailvar currentTail = TimeFormatToTail(orderTime);//因为是按月分表所以获取下个月的时间判断id是否是在临界点创建的//var nextMonthFirstDay = ShardingCoreHelper.GetNextMonthFirstDay(DateTime.Now);//这个是错误的var nextMonthFirstDay = ShardingCoreHelper.GetNextMonthFirstDay(orderTime);if (orderTime.AddSeconds(10) > nextMonthFirstDay){var nextTail = TimeFormatToTail(nextMonthFirstDay);return DoOrderNoFilter(shardingOperator, orderTime, currentTail, nextTail);}//因为是按月分表所以获取这个月月初的时间判断id是否是在临界点创建的//if (orderTime.AddSeconds(-10) < ShardingCoreHelper.GetCurrentMonthFirstDay(DateTime.Now))//这个是错误的if (orderTime.AddSeconds(-10) < ShardingCoreHelper.GetCurrentMonthFirstDay(orderTime)){//上个月tailvar previewTail = TimeFormatToTail(orderTime.AddSeconds(-10));return DoOrderNoFilter(shardingOperator, orderTime, previewTail, currentTail);}return DoOrderNoFilter(shardingOperator, orderTime, currentTail, currentTail);}private Expression<Func<string, bool>> DoOrderNoFilter(ShardingOperatorEnum shardingOperator, DateTime shardingKey, string minTail, string maxTail){switch (shardingOperator){case ShardingOperatorEnum.Equal:{var isSame = minTail == maxTail;if (isSame){return tail => tail == minTail;}else{return tail => tail == minTail || tail == maxTail;}}default:{return tail => true;}}}private bool CheckOrderNo(string orderNo, out DateTime orderTime){//yyyyMMddHHmmssif (orderNo.Length == 14){if (DateTime.TryParseExact(orderNo, "yyyyMMddHHmmss", CultureInfo.InvariantCulture,DateTimeStyles.None, out var parseDateTime)){orderTime = parseDateTime;return true;}}orderTime = DateTime.MinValue;return false;}}//....

省略了相同部分代码,我们再次来尝试看看

第三步运行

6d053c31a70d28343211713c6d69a3c3.png
因为雪花id所以不需要经过redis就可以直接解析出订单信息对应的所属分片,非合法id通过redis来判断是否是数据库中存在的

demo

DEMO

总结

目前ShardingCore在分片领域基本上给出了非常多的解决方案可以使用,针对.net在分表分库领域的缺失我相信会随着开源项目和更多使用的人群,来帮助.Net在未来走的更远。

最后的最后

感谢博客园-饭勺oO 提供的实践方案

身位一个dotnet程序员我相信在之前我们的分片选择方案除了mycatshardingsphere-proxy外没有一个很好的分片选择,但是我相信通过ShardingCore 的原理解析,你不但可以了解到大数据下分片的知识点,更加可以参与到其中或者自行实现一个,我相信只有了解了分片的原理dotnet才会有更好的人才和未来,我们不但需要优雅的封装,更需要原理的是对原理了解。

我相信未来dotnet的生态会慢慢起来配上这近乎完美的语法

您的支持是开源作者能坚持下去的最大动力

  • Github ShardingCore

  • Gitee ShardingCore


博客

QQ群:771630778

个人QQ:326308290(欢迎技术支持提供您宝贵的意见)

个人邮箱:326308290@qq.com

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

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

相关文章

COMA(二):Counterfactual Multi-Agent Policy Gradients 论文讲解

Counterfactual Multi-Agent Policy Gradients 论文链接&#xff1a;https://arxiv.org/pdf/1705.08926.pdf 1. 问题提出&#xff08;解决了什么问题&#xff1f;&#xff09; 在现实世界中&#xff0c;有非常多的问题需要多个单位之间的“合作”才能完成任务&#xff0c;这就…

lecture6-mini批量梯度训练及三个加速的方法

Hinton的第6课&#xff0c;这一课中最后的那个rmsprop&#xff0c;关于它的资料&#xff0c;相对较少&#xff0c;差不多除了Hinton提出&#xff0c;没论文的样子&#xff0c;各位大大可以在这上面研究研究啊。 一、mini-批量梯度下降概述 这部分将介绍使用随机梯度下降学习来训…

Dapr集成之GRPC 接口

Dapr 为本地调用实现 HTTP 和 gRPC API 。通常大家第一时间想到的是通过 gRPC 调用 Dapr&#xff0c;更重要的一点是Dapr 也可以通过 gRPC 与应用程序通信。要做到这一点&#xff0c;原理很简单&#xff0c;应用程序需要托管一个gRPC服务器&#xff0c;并实现 Dapr 的GRPC 规范…

jQuery Validate验证框架详解

jQuery Validate验证框架详解 jQuery校验官网地址&#xff1a;http://bassistance.de/jquery-plugins/jquery-plugin-validation 一、导入js库 <script type"text/javascript" src"<%path %>/validate/jquery-1.6.2.min.js"></script> &…

RNN入门笔记

本笔记来源自Youtube李宏毅老师的RNN视频归纳&#xff0c;主要分为以下几个知识点: RNN 的特点RNN 的几种实现方法 (Simple RNN, LSTM)RNN 的训练不稳定性RNN 的keras实现 (定长和变长输入案例) Recurrent Neural Network Feature of RNN Differ from normal Neural Networ…

WPF 基础控件之 DatePicker 样式

此群已满340500857 &#xff0c;请加新群458041663由于微信群人数太多入群请添加小编微信号yanjinhuawechat 或 W_Feng_aiQ 邀请入群需备注WPF开发者 PS&#xff1a;有更好的方式欢迎推荐。支持NugetInstall-Package WPFDevelopers.Minimal -Version 3.2.001—代码如下一、创建…

stagefright框架(四)-Video Buffer传输流程

這篇文章將介紹Stagefright中是如何和OMX video decoder传送buffer。 (1) OMXCodec會在一開始的時候透過read函式來傳送未解碼的data給decoder&#xff0c;並且要求decoder將解碼後的data傳回來 status_t OMXCodec::read(...){ if (mInitialBufferSubmit) { mInitialBuffe…

微信支付四大支付模式分别有哪些区别?

微信支付是集成在微信客户端的支付功能&#xff0c;用户可以通过手机完成快速的支付流程。微信支付已为百货、餐厅、便利店、酒店、快递、景区、医院、售货机等提供了支付与营销的全方位支持。 目前微信支付已实现刷卡支付、扫码支付、公众号支付、APP支付&#xff0c;并提供企…

利用Deep Reinforcement Learning训练王者荣耀超强AI

Mastering Complex Control in MOBA Games with Deep Reinforcement Learning&#xff08;一&#xff09;知识背景&#xff08;二&#xff09;系统架构&#xff08;三&#xff09;算法结构3.1 Target Attention3.2 利用LSTM学习技能连招释放3.3 Decoupling of Control Dependen…

C和指针之编译出现warning: implicit declaration of function ‘matrix_multiply‘ is invalid in C99问题

1、问题 在我的mac上编译一个c文件&#xff0c;出现下面错误2、原因和解决办法 是因为我用vim的时候&#xff0c;把函数名少写了一个字符导致&#xff0c;把这个函数名改正就行了。

5. 堪比JMeter的.Net压测工具 - Crank 实战篇 - 接口以及场景压测

1. 前言通过之前的学习&#xff0c;我们已经掌握了crank的配置以及对应http基准工具bombardier、wrk、wrk2的用法&#xff0c;本篇文章介绍一下如何将其用于实战&#xff0c;在实际的项目中我们如何使用crank来完成压测任务。2. 项目背景目前有一个项目&#xff0c;我们希望通过…

Pytorch快速入门笔记

Pytorch 入门笔记1. Pytorch下载与安装2. Pytorch的使用教程2.1 Pytorch设计理念及其基本操作2.2 使用torch.nn搭建神经网络2.3 创建属于自己的Dataset和DataLoader2.3.1 编写Dataset类2.3.2 编写Transform类2.3.3 将Transform融合到Dataset中去2.3.4 编写DataLoader类2.4 使用…

详解用65行javascript代码做Flappy Bird

点击查看特效JavaScript做Flappy Bird游戏&#xff0c;代码仅仅65行资源包括&#xff1a;javascript源码&#xff1a;phaser.min.js&#xff1b;main.js&#xff1b;index.html素材&#xff1a;两张图片&#xff01;素材PS&#xff1a;素材源码下载来我的前端群570946165&#…

C和指针之数组编程练习5 (矩阵相乘)

1、问题 5.如果A是个x行y列的矩阵,B是个y行z列的矩阵,把A和B相乘,其结果将是另一个x行z列的矩阵C。这个矩阵的每个元素是由下面的公式决定的: 例如: 结果矩阵中14这个值是通过2-2加上-6-3得到的,编写一个函数,用于执行两个矩阵的乘法。函数的原型如下: void matrix_mul…

我的技术回顾因ABP框架触发DevOps云原生之路-2020年

我的技术回顾&#xff1a;2015年&#xff1a;我的技术回顾那些与ABP框架有关的故事-2015年2016年&#xff1a;从ABP框架国内社区发展回顾.NET技术变迁-2016年2017年&#xff1a;我的技术回顾那些与ABP框架有关的故事-2017年2018年&#xff1a;我的技术回顾那些与ABP框架有关的故…

半身头像

画的好丑。。。继续加油 转载于:https://www.cnblogs.com/manlurensheng/p/4102631.html

Swift - 操作SQLite数据库(引用SQLite3库)

SQLite轻量级数据库在移动应用中使用非常普遍&#xff0c;但是目前的库是C编写的&#xff0c;为了方便使用&#xff0c;对SQLite相关的操作用Swift进行了封装。这个封装代码使用了一个开源项目SQLiteDB&#xff0c;地址是&#xff1a;https://github.com/fahimf/sqlitedb 重要事…

如何在Clion中使用C++调用Python代码

在很多时候&#xff0c;我们需要在一个c工程项目中调用部分Python代码&#xff0c;这就需要我们实现Python和C之间的交互。交互方式有两种&#xff1a;1. 依靠 TCP 建立的网络通信交互&#xff1b;2. 嵌入式混合语言编程&#xff08;Embedding Code&#xff09;。这里主要介绍后…

.NET6之MiniAPI(二十四):用Polly重试

为了保障系统的稳定和安全&#xff0c;在调用三方服务时&#xff0c;可以增加重试和熔断。重试是调用一次失败后再试几试&#xff0c;避免下游服务一次闪断&#xff0c;就把整个链路终止&#xff1b;熔断是为了防止太多的次数的无效访问&#xff0c;导致系统不可知异常。Polly是…

CLion 中使用 C++ 版本的 OpenCV

配置环境&#xff1a; Windows 10CLion 2020OpenCV 3.4.1MinGW-w64 1. 下载 CLion 并配置好 MinGW CLion 下载地址&#xff1a;https://www.jetbrains.com/clion MinGW 安装包下载地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1c00uHbcf_jGeDDrVg99jtA 提取码&…