.NET分库分表:高性能分页(mycat之外的选择)

🏆作者:科技、互联网行业优质创作者
🏆专注领域:.Net技术、软件架构、人工智能、数字化转型、DeveloperSharp、微服务、工业互联网、智能制造
🏆欢迎关注我(Net数字智慧化基地),里面有很多高价值技术文章,是你刻苦努力也积累不到的经验,能助你快速成长。升职+涨薪!!

框架介绍

依照惯例首先介绍本期主角:ShardingCore 一款ef-core下高性能、轻量级针对分表分库读写分离的解决方案,具有零依赖、零学习成本、零业务代码入侵

dotnet下唯一一款全自动分表,多字段分表框架,拥有高性能,零依赖、零学习成本、零业务代码入侵,并且支持读写分离动态分表分库,同一种路由可以完全自定义的新星组件框架。

项目地址

github:https://github.com/dotnetcore/sharding-core

gitee:https://gitee.com/dotnetchina/sharding-core

背景

在大数据量下针对app端的瀑布流页面分页的优化实战,有大量的数据,前端需要以瀑布流的形式展示出来,我们最简单的就是以用户发布的文章为例,假设我们有大量的文章帖子被,需求需要按帖子的发布时间倒序展示给用户看,那么在手机端我们一般都是以下拉刷新,上拉加载的形式去展示,那么我们一般会有以下集中写法。

常规分页操作

select count(*) from article
select * from article order by publish_time desc limit 0,20

这个操作是一般我们的常规分页操作,先进行total然后进行分页获取,这种做法的好处是支持任意规则的分页,缺点就是需要查询两次,一次count一次limit当然后期数据量实在太大可以只需要第一次count,但是也有一个问题就是如果数据量一直在变化会出现下一次分页中还会有上一次的部分数据,因为数据在不断地新增,你的分页没跟上发布的速度那么就会有这个情况发送.

瀑布流分页

除了上述常规分页操作外,我们针对特定顺序的分页也可以进行特定的分页方式来实现高性能,因为基于大前提我们是大数量下的瀑布流,我们的文章假设是以雪花id作为主键,那么我们的分页可以这么写

select * from article where id<last_id order by publish_time desc limit 0,20

首先我们来分析一下,这个语句是利用了插入的数据分布是顺序和你需要查询的排序一直来实现的,又因为id不会重复并且雪花id的顺序和时间是一致的都是同向的所以可以利用这种方式来进行排序,limit每次不需要跳过任何数目,直接获取需要的数目即可,只需要传递上一次的查询结果的id即可,这个方式弥补了上述常规分页带来的问题,并且拥有非常高的性能,但是缺点也显而易见,不支持跳页,不支持任意排序,所以这个方式目前来说非常适合前端app的瀑布流排序。

分片下的实现

首先分片下需要实现这个功能我们需要有id支持分片,并且publish_time按时间分表,两者缺一不可。

原理

假设文章表article我们是以publish_time作为分片字段,假设按天分表,那么我们会拥有如下的表

article_20220101、article_20220102、article_20220103、article_20220104、article_20220105、article_20220106......

雪花id辅助分片

因为雪花id可以反解析出时间,所以我们对雪花id的=,>=,>,<=,<,contains的操作都是可以进行辅助分片进行缩小分片范围 假设我们的雪花id解析出来是2021-01-05 11:11:11,那么针对这个雪花id<小于操作我们可以等价于x < 2021-01-05 11:11:11,那么如果我问你这下我们需要查询的表有哪些,很明显 [article_20220101、article_20220102、article_20220103、article_20220104、article_20220105],除了20220106外我们都需要查询。

union all分片模式

如果你使用union all的分片模式那么通常会将20220101-20220105的所有的表进行union all然后机械能过滤,那么优点可想而知:简单,连接数消耗仅1个,sql语句支持的多,缺点也显而易见,优化起来后期是个很大的问题,并且跨库下的使用有问题

select * from 
(select * from article_20220101 union all select * from article_20220102 union all select * from article_20220103....) twhere id<last_id order by publish_time desc limit 0,20

流式分片,顺序查询

如果你是流式分片模式进行聚合通常我们会将20220101-20220105的所有的表进行并行的分别查询,然后针对每个查询的结果集进行优先级队列的排序后获取,优点:语句简单便于优化,性能可控,支持分库,缺点:实现复杂,连接数消耗多

select * from article_20220101 where id<last_id order by publish_time desc limit 0,20
select * from article_20220102where id<last_id order by publish_time desc limit 0,20
select * from article_20220103 where id<last_id order by publish_time desc limit 0,20
......

流式分片下的优化

目前 ShardingCore采用的是流式聚合+union all,当且仅当用户手动3调用UseUnionAllMerge时会将分片sql转成union all 聚合。

针对上述瀑布流的分页ShardingCore是这么操作的

  • 确定分片表的顺序,也就是因为分片字段是publish_time,又因为排序字段是publish_time所以分片表其实是有顺序的,也就是[article_20220105、article_20220104、article_20220103、article_20220102、article_20220101], 因为我们是开启n个并发线程所以这个排序可能没有意义,但是如果我们是仅开启设置单个连接并发的时候,程序将现在通过id<last_id进行表筛选,之后依次从大到小进行获取直到满足skip+take也就是0+20=20条数据后,进行直接抛弃剩余查询返回结果,那么本次查询基本上就是和单表查询一样,因为基本上最多跨两张表基本可以满足要求(具体场景不一定)

  • 说明:假设last_id反解析出来的结果是2022-01-04 05:05:05那么可以基本上排除article_20220105,判断并发连接数如果是1那么直接查询article_20220104,如果不满足继续查询article_20220103,直到查询结果为20条如果并发连接数是2那么查询[article_20220104、article_20220103]如果不满足继续下面两张表直到获取到结果为20条数据,所以我们可以很清晰的了解其工作原理并且来优化

说明

  • 通过上述优化可以保证流式聚合查询在顺序查询下的高性能O(1)

  • 通过上述优化可以保证客户端分片拥有最小化连接数控制

  • 设置合理的主键可以有效的解决我们在大数据分片下的性能优化

实践

ShardingCore目前针对分片查询进行了不断地优化和尽可能的无业务代码入侵来实现高性能分片查询聚合。

接下来我将为大家展示一款dotnet下唯一一款全自动路由、多字段分片、无代码入侵、高性能顺序查询的框架在传统数据库领域下的分片功能,如果你使用过我相信你一定会爱上他。

第一步 安装依赖

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

第二步 添加对象和上下文

有很多朋友问我一定需要使用fluentapi来使用ShardingCore吗,只是个人喜好,这边我才用dbset+attribute来实现

//文章表
[Table(nameof(Article))]
public class Article
{[MaxLength(128)][Key]public string Id { get; set; }[MaxLength(128)][Required]public string Title { get; set; }[MaxLength(256)][Required]public string Content { get; set; }public DateTime PublishTime { get; set; }
}
context
public class MyDbContext:AbstractShardingDbContext,IShardingTableDbContext
{public MyDbContext(DbContextOptions<MyDbContext> options) : base(options){
添加会导致efcore 的model提前加载的方法如Database.xxxx}public IRouteTail RouteTail { get; set; }public DbSet<Article> Articles { get; set; }
}

第三步 添加文章路由

public class ArticleRoute:AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute<Article>
{public override void Configure(EntityMetadataTableBuilder<Article> builder){builder.ShardingProperty(o => o.PublishTime);}public override bool AutoCreateTableByTime(){return true;}public override DateTime GetBeginTime(){return new DateTime(2022, 3, 1);}
}

到目前为止基本上Article已经支持了按天分表

第四步 添加查询配置,让框架知道我们是顺序分表且定义分表的顺序

public class TailDayReverseComparer : IComparer<string>
{public int Compare(string? x, string? y){//程序默认使用的是正序也就是按时间正序排序我们需要使用倒序所以直接调用原生的比较器然后乘以负一即可return Comparer<string>.Default.Compare(x, y) * -1;}
}
//当前查询满足的复核条件必须是单个分片对象的查询,可以join普通非分片表
public class ArticleEntityQueryConfiguration:IEntityQueryConfiguration<Article>
{public void Configure(EntityQueryBuilder<Article> builder){//设置默认的框架针对Article的排序顺序,这边设置的是倒序builder.ShardingTailComparer(new TailDayReverseComparer());如下设置和上述是一样的效果让框架真对Article的后缀排序使用倒序//builder.ShardingTailComparer(Comparer<string>.Default, false);//简单解释一下下面这个配置的意思//第一个参数表名Article的哪个属性是顺序排序和Tail按天排序是一样的这边使用了PublishTime//第二个参数表示对属性PublishTime asc时是否和上述配置的ShardingTailComparer一致,true表示一致,很明显这边是相反的因为默认已经设置了tail排序是倒序//第三个参数表示是否是Article属性才可以,这边设置的是名称一样也可以,因为考虑到匿名对象的selectbuilder.AddOrder(o => o.PublishTime, false,SeqOrderMatchEnum.Owner|SeqOrderMatchEnum.Named);//这边为了演示使用的id是简单的时间格式化所以和时间的配置一样builder.AddOrder(o => o.Id, false,SeqOrderMatchEnum.Owner|SeqOrderMatchEnum.Named);//这边设置如果本次查询默认没有带上述配置的order的时候才用何种排序手段//第一个参数表示是否和ShardingTailComparer配置的一样,目前配置的是倒序,也就是从最近时间开始查询,如果是false就是从最早的时间开始查询//后面配置的是熔断器,也就是复核熔断条件的比如FirstOrDefault只需要满足一个就可以熔断builder.AddDefaultSequenceQueryTrip(true, CircuitBreakerMethodNameEnum.Enumerator, CircuitBreakerMethodNameEnum.FirstOrDefault);//这边配置的是当使用顺序查询配置的时候默认开启的连接数限制是多少,startup一开始可以设置一个默认是当前cpu的线程数,这边优化到只需要一个线程即可,当然如果跨表那么就是串行执行builder.AddConnectionsLimit(1, LimitMethodNameEnum.Enumerator, LimitMethodNameEnum.FirstOrDefault);}
}

第五步 添加配置到路由

public class ArticleRoute:AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute<Article>
{//省略.....public override IEntityQueryConfiguration<Article> CreateEntityQueryConfiguration(){return new ArticleEntityQueryConfiguration();}
}

第六步 startup配置

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<ArticleRoute>();}).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=ShardingWaterfallDB;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.Articles.Any()){List<Article> articles = new List<Article>();var beginTime = new DateTime(2022, 3, 1, 1, 1,1);for (int i = 0; i < 70; i++){var article = new Article();article.Id = beginTime.ToString("yyyyMMddHHmmss");article.Title = "标题" + i;article.Content = "内容" + i;article.PublishTime = beginTime;articles.Add(article);beginTime= beginTime.AddHours(2).AddMinutes(3).AddSeconds(4);}myDbContext.AddRange(articles);myDbContext.SaveChanges();}
}
app.MapControllers();app.Run();

第七步 编写查询表达式

public async Task<IActionResult> Waterfall([FromQuery] string lastId,[FromQuery]int take)
{Console.WriteLine($"-----------开始查询,lastId:[{lastId}],take:[{take}]-----------");var list = await _myDbContext.Articles.WhereIf(o => String.Compare(o.Id, lastId) < 0,!string.IsNullOrWhiteSpace(lastId)).Take(take)..OrderByDescending(o => o.PublishTime)ToListAsync();return Ok(list);
}

运行程序

图片

因为07表是没有的所以这次查询会查询07和06表,之后我们进行下一次分页传入上次id

图片

因为没有对Article.Id进行分片路由的规则编写所以没办法进行对id的过滤,那么接下来我们配置Id的分片规则

首先针对ArticleRoute进行代码编写

public class ArticleRoute:AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute<Article>
{public override void Configure(EntityMetadataTableBuilder<Article> builder){builder.ShardingProperty(o => o.PublishTime);builder.ShardingExtraProperty(o => o.Id);}public override bool AutoCreateTableByTime(){return true;}public override DateTime GetBeginTime(){return new DateTime(2022, 3, 1);}public override IEntityQueryConfiguration<Article> CreateEntityQueryConfiguration(){return new ArticleEntityQueryConfiguration();}public override Expression<Func<string, bool>> GetExtraRouteFilter(object shardingKey, ShardingOperatorEnum shardingOperator, string shardingPropertyName){switch (shardingPropertyName){case nameof(Article.Id): return GetArticleIdRouteFilter(shardingKey, shardingOperator);}return base.GetExtraRouteFilter(shardingKey, shardingOperator, shardingPropertyName);}/// <summary>/// 文章id的路由/// </summary>/// <param name="shardingKey"></param>/// <param name="shardingOperator"></param>/// <returns></returns>private Expression<Func<string, bool>> GetArticleIdRouteFilter(object shardingKey,ShardingOperatorEnum shardingOperator){//将分表字段转成订单编号var id = shardingKey?.ToString() ?? string.Empty;//判断订单编号是否是我们符合的格式if (!CheckArticleId(id, out var orderTime)){//如果格式不一样就直接返回false那么本次查询因为是and链接的所以本次查询不会经过任何路由,可以有效的防止恶意攻击return tail => false;}//当前时间的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 DoArticleIdFilter(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 DoArticleIdFilter(shardingOperator, orderTime, previewTail, currentTail);}return DoArticleIdFilter(shardingOperator, orderTime, currentTail, currentTail);}private Expression<Func<string, bool>> DoArticleIdFilter(ShardingOperatorEnum shardingOperator, DateTime shardingKey, string minTail, string maxTail){switch (shardingOperator){case ShardingOperatorEnum.GreaterThan:case ShardingOperatorEnum.GreaterThanOrEqual:{return tail => String.Compare(tail, minTail, StringComparison.Ordinal) >= 0;}case ShardingOperatorEnum.LessThan:{var currentMonth = ShardingCoreHelper.GetCurrentMonthFirstDay(shardingKey);//处于临界值 o=>o.time < [2021-01-01 00:00:00] 尾巴20210101不应该被返回if (currentMonth == shardingKey)return tail => String.Compare(tail, maxTail, StringComparison.Ordinal) < 0;return tail => String.Compare(tail, maxTail, StringComparison.Ordinal) <= 0;}case ShardingOperatorEnum.LessThanOrEqual:return tail => String.Compare(tail, maxTail, StringComparison.Ordinal) <= 0;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 CheckArticleId(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;}
}

完整路由:针对Id进行多字段分片并且支持大于小于排序

以上是多字段分片的优化,详情博客可以点击这边 .Net下你不得不看的分表分库解决方案-多字段分片

然后我们继续查询看看结果

图片

第三页也是如此

图片

DEMO:https://github.com/xuejmnet/ShardingWaterfallApp

总结

当前框架虽然是一个很年轻的框架,但是我相信我对其在分片领域的性能优化应该在.net现有的所有框架下找不出第二个,并且框架整个也支持union all聚合,可以满足列入group+first的特殊语句的查询,又有很高的性能,一个不但是全自动分片而且还是高性能框架拥有非常多的特性性能,目标是榨干客户端分片的最后一点性能。

最后

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

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

🏆欢迎关注我(Net数字智慧化基地),里面有很多高价值技术文章,是你刻苦努力也积累不到的经验,能助你快速成长。升职+涨薪!!
🏆点击下方卡片关注公众号

回复'面试',获取C#/.NET/.NET Core面试宝典

回复'C#',领取零基础学习C#编程

回复'NET',领取.NET零基础入门到实战

回复'Linux',领取Linux从入门到精通

回复'wpf',领取高薪热门【WPF上位机+工业互联网】从零手写实战

回复'Modbus',领取初识C#+上位机Modbus通信

回复'PLC',领取C#语言与西门子PLC的通信实操

回复'blazor',领取blazor从入门到实战

回复'TypeScript',领取前端热门TypeScript系统教程

回复'vue',领取vue前端从入门到精通

回复'23P',领取C#实现23种常见设计模式

回复'MongoDB',领取MongoDB实战

回复'Trans',领取分布式事务

回复'Lock',领取分布式锁实践

回复'Docker',领取微服务+Docker综合实战

回复'K8s',领取K8s部署微服务

回复'加群',进.NET技术社区交流群

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

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

相关文章

【USTC】verilog 习题练习 21-25

21 基于端口名称的实例化 题目描述 创建一 verilog 电路&#xff0c;实现对模块 mod_a 基于端口名称的实例化&#xff0c;如下图所示&#xff1a; 其中mod_a模块的代码为&#xff1a; module mod_a (output out1,output out2,input in1,input in2,input in3,in…

边缘计算AI智能分析网关V4客流统计算法的概述

客流量统计AI算法是一种基于人工智能技术的数据分析方法&#xff0c;通过机器学习、深度学习等算法&#xff0c;实现对客流量的实时监测和统计。该算法主要基于机器学习和计算机视觉技术&#xff0c;其基本流程包括图像采集、图像预处理、目标检测、目标跟踪和客流量统计等步骤…

【架构】docker实现3主3从架构配置【案例1/4】

一&#xff0c;集群规划及准备工作 架构实现&#xff1a;Redis3主3从 二&#xff0c;搭建命令 第一步&#xff0c;创建6台服务&#xff1a; docker run -d --name redis-node-1 --net host --privilegedtrue -v /data/redis/share/redis-node-1:/data redis:6.0.8 --clust…

基于Springboot+vue图书管理系统(前后端分离)

该项目完全免费 项目技术栈前后端分离&#xff1a; 后端&#xff1a;Springboot Mybatis-plus 前端&#xff1a;Vue ElementUI 数据库&#xff1a; MySQL 项目功能描述 管理员&#xff1a; 登录、个人信息、修改密码、管理后台管理系统所有数据 首页统计&#xff1a;…

Python(37):使用logging的配置文件配置日志

Python(37):使用logging的配置文件配置日志 输出日志到控制台和日志文件方法&#xff1a; 创建一个日志配置文件&#xff0c;然后使用fileConfig()函数来读取该文件的内容。 方法1&#xff1a;输出日志到文件&#xff0c;文件是固定的 方法2&#xff1a;输出日志到文件&…

【机器学习】机器学习四大类第01课

一、机器学习四大类 有监督学习 (Supervised Learning) 有监督学习是通过已知的输入-输出对&#xff08;即标记过的训练数据&#xff09;来学习函数关系的过程。在训练阶段&#xff0c;模型会根据这些示例调整参数以尽可能准确地预测新的、未见过的数据点的输出。 实例&#x…

docker安装 unexpected wsl error

docker unexpected wsl error 问题描述&#xff1a; 很诡异的一个问题 大概现象和这个帖子很像 https://developer.aliyun.com/article/1395485 docker版本4.26.1 系统&#xff1a; windows 10 winR 输入winver可以看见自己的版本号 华为matebook 16s 重装的Win10 解决流程…

代码随想录算法训练营29期|day 23 任务以及具体安排

669. 修剪二叉搜索树 class Solution {public TreeNode trimBST(TreeNode root, int low, int high) {if (root null) {return null;}if (root.val < low) {return trimBST(root.right, low, high);}if (root.val > high) {return trimBST(root.left, low, high);}// ro…

农用拖拉机市场调研:预计2029年将达到171亿美元

由于近些年来新兴市场的迅速崛起和技术创新的不断涌现&#xff0c;全球农用拖拉机市场的竞争也日趋激烈。生产商不仅需要提供质量可靠的产品&#xff0c;还需要提供良好的售后服务以赢得客户的信赖。 农业是支撑国民经济建设与发展的基础产业&#xff0c;而农业机械化是建设现代…

2024杭州国际智慧城市,人工智能,安防展览会(杭州智博会)

在智能化浪潮的冲击下&#xff0c;我们的生活与环境正在经历一场深刻的变革。这是一场前所未有的技术革命&#xff0c;它以前所未有的速度和广度&#xff0c;改变着我们的生活方式、工作方式、思维方式和社会结构。在这场变革中&#xff0c;有的人选择激流勇进&#xff0c;拥抱…

ACL实验

一&#xff1a;实验要求 二&#xff1a;实验分析 PC1可以telnet R1但不能ping通R1 PC1可以ping通R2但不能telnet R2 PC2可以ping通R1但不能telnet R1 PCR可以telnet R2但不能ping通R2 三&#xff1a;实验过程 配置IP 配置静态路由 检查是否全网可通 配置Telnet r1创建…

Xshell无法ssh连接虚拟机问题或主机无法ping通虚拟机。

常见报错如下&#xff1a; 1&#xff0c;Could not connect to ‘&#xff1f;&#xff1f;&#xff1f;’ (port 22): Connection failed. 2&#xff0c;卡在To escape to local shell, press ‘CtrlAlt]’. 3&#xff0c;Connection closing…Socket close. Connection clos…

一款轻量级、基于Java语言开发的低代码开发框架,开箱即用!

数字化时代&#xff0c;企业对于灵活、高效和安全的软件开发需求日益旺盛。为了满足这些需求&#xff0c;许多组织转向低代码技术&#xff0c;以寻求更具成本效益和创新性的解决方案。JNPF基础框架正是在这一背景下应运而生&#xff0c;凭借其私有化部署和100%源码交付的特性&a…

Unity之铰链关节和弹簧组件

《今天闪电侠他回来了&#xff0c;这一次他要拿回属于他的一切》 目录 &#x1f4d5;一、铰链关节组件HingeJoint 1. 实例 2. 铰链关节的坐标属性 ​3.铰链关节的马达属性Motor &#x1f4d5;二、弹簧组件 &#x1f4d5;三、杂谈 一、铰链关节组件HingeJoint 1. 实例 说…

【STM32调试】寄存器调试不良问题记录持续版

STM32寄存器调试不良问题记录 NVIC&#xff08;内嵌的中断向量控制器&#xff09;EXTI&#xff08;外部中断/事件&#xff09; 记录一些stm32调试过程中&#xff1a;不易被理解、存在使用误区、不清不楚、是坑、使用常识等方面的一些记录。本记录只包含stm32的内核以及外设等寄…

从uptime看linux平均负载

从前遇到系统卡顿只会top。。top看不出来怎么搞呢&#xff1f; Linux系统提供了丰富的命令行工具&#xff0c;以帮助用户和系统管理员监控和分析系统性能。在这些工具中&#xff0c;uptime、mpstat和pidstat是非常有用的命令&#xff0c;它们可以帮助你理解系统的平均负载以及资…

短信系统搭建主要因素|网页短信平台开发源码

短信系统搭建主要因素|网页短信平台开发源码 随着移动互联网的快速发展&#xff0c;短信系统已成为企业和个人进行信息传递的重要工具。建立一个高效可靠的短信系统对于企业来说非常重要。下面我们将介绍一些影响短信系统搭建的主要因素。 1. 平台选择&#xff1a;在搭建短信系…

2018年认证杯SPSSPRO杯数学建模B题(第一阶段)动态模糊图像全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 B题 动态模糊图像 原题再现&#xff1a; 人眼由于存在视觉暂留效应&#xff0c;所以看运动的物体时&#xff0c;看到的每一帧画面都包含了一段时间内 (大约 1/24 秒) 的运动过程&#xff0c;所以这帧画面事实上是模糊的。对电影的截图来说&…

PyQt5零基础入门(五)——QLabel显示图像

QLabel显示图像 前言显示静态图片显示动态图 前言 QLabel是Qt库中的一个部件&#xff0c;通常用于显示文本或富文本文档。然而&#xff0c;QLabel也可以用来显示图像。 一种常见的使用QLabel显示图像的方法是使用QPixmap类。QPixmap可以从文件中接收图片&#xff0c;然后可以…

2024年WebGIS开发三维面试题及答案解析

​前言 简介 面试之前 面试流程 自我介绍 1 面试简介 1 、首先做一个简单的自我介绍 2 、在公司/学习中做了什么样的项目&#xff1f; 在项目中参与哪些模块&#xff1f;重点介绍做了哪些功能? 3 、用户名/密码传输给后台的时候是加密传输还是明文传输 4 、token如何校…