首页 » 网站建设 » php分库分页技巧_NET分库分表高机能分页mycat之外的选择

php分库分页技巧_NET分库分表高机能分页mycat之外的选择

访客 2024-12-09 0

扫一扫用手机浏览

文章目录 [+]

【专注领域】.Net技能、软件架构、人工智能、数字化转型、DeveloperSharp、微做事、工业互联网、智能制造

点击右上方“关注”,里面有很多高代价技能文章,是你刻苦努力也积累不到的履历,能助你快速发展。
升职+涨薪!

php分库分页技巧_NET分库分表高机能分页mycat之外的选择

框架先容

php分库分页技巧_NET分库分表高机能分页mycat之外的选择
(图片来自网络侵删)

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

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

项目地址

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

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

背景

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

常规分页操作

selectcount()fromarticleselectfromarticleorderbypublish_timedesclimit0,20

这个操作是一样平常我们的常规分页操作,前辈行total然后进行分页获取,这种做法的好处是支持任意规则的分页,缺陷便是须要查询两次,一次count一次limit当然后期数据量实在太大可以只须要第一次count,但是也有一个问题便是如果数据量一贯在变革会涌现下一次分页中还会有上一次的部分数据,由于数据在不断地新增,你的分页没跟上发布的速率那么就会有这个情形发送.

瀑布流分页

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

selectfromarticlewhereid<last_idorderbypublish_timedesclimit0,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语句支持的多,缺陷也显而易见,优化起来后期是个很大的问题,并且跨库下的利用有问题

selectfrom(selectfromarticle_20220101unionallselectfromarticle_20220102unionallselectfromarticle_20220103....)twhereid<last_idorderbypublish_timedesclimit0,20

流式分片,顺序查询

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

selectfromarticle_20220101whereid<last_idorderbypublish_timedesclimit0,20selectfromarticle_20220102whereid<last_idorderbypublish_timedesclimit0,20selectfromarticle_20220103whereid<last_idorderbypublish_timedesclimit0,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-PackageShardingCore#数据库驱动这边选择的是mysql的社区驱动efcore6最新版本即可PM>Install-PackagePomelo.EntityFrameworkCore.MySql

第二步 添加工具和高下文

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

//文章表[Table(nameof(Article))]publicclassArticle{[MaxLength(128)][Key]publicstringId{get;set;}[MaxLength(128)][Required]publicstringTitle{get;set;}[MaxLength(256)][Required]publicstringContent{get;set;}publicDateTimePublishTime{get;set;}}contextpublicclassMyDbContext:AbstractShardingDbContext,IShardingTableDbContext{publicMyDbContext(DbContextOptions<MyDbContext>options):base(options){添加会导致efcore的model提前加载的方法如Database.xxxx}publicIRouteTailRouteTail{get;set;}publicDbSet<Article>Articles{get;set;}}

第三步 添加文章路由

publicclassArticleRoute:AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute<Article>{publicoverridevoidConfigure(EntityMetadataTableBuilder<Article>builder){builder.ShardingProperty(o=>o.PublishTime);}publicoverrideboolAutoCreateTableByTime(){returntrue;}publicoverrideDateTimeGetBeginTime(){returnnewDateTime(2022,3,1);}}

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

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

publicclassTailDayReverseComparer:IComparer<string>{publicintCompare(string?x,string?y){//程序默认利用的是正序也便是按韶光正序排序我们须要利用倒序以是直接调用原生的比较器然后乘以负一即可returnComparer<string>.Default.Compare(x,y)-1;}}//当前查询知足的复核条件必须是单个分片工具的查询,可以join普通非分片表publicclassArticleEntityQueryConfiguration:IEntityQueryConfiguration<Article>{publicvoidConfigure(EntityQueryBuilder<Article>builder){//设置默认的框架针对Article的排序顺序,这边设置的是倒序builder.ShardingTailComparer(newTailDayReverseComparer());////如下设置和上述是一样的效果让框架真对Article的后缀排序利用倒序//builder.ShardingTailComparer(Comparer<string>.Default,false);//大略阐明一下下面这个配置的意思//第一个参数表名Article的哪个属性是顺序排序和Tail按天排序是一样的这边利用了PublishTime//第二个参数表示对属性PublishTimeasc时是否和上述配置的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);}}

第五步 添加配置到路由

publicclassArticleRoute:AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute<Article>{//省略.....publicoverrideIEntityQueryConfiguration<Article>CreateEntityQueryConfiguration(){returnnewArticleEntityQueryConfiguration();}}

第六步 startup配置

varbuilder=WebApplication.CreateBuilder(args);//Addservicestothecontainer.ILoggerFactoryefLogger=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,newMySqlServerVersion(newVersion())).UseLoggerFactory(efLogger);});o.UseShardingTransaction((conn,b)=>{b.UseMySql(conn,newMySqlServerVersion(newVersion())).UseLoggerFactory(efLogger);});o.AddDefaultDataSource("ds0","server=127.0.0.1;port=3306;database=ShardingWaterfallDB;userid=root;password=root;");o.ReplaceTableEnsureManager(sp=>newMySqlTableEnsureManager<MyDbContext>());}).EnsureConfig();varapp=builder.Build();app.Services.GetRequiredService<IShardingBootstrapper>().Start();using(varscope=app.Services.CreateScope()){varmyDbContext=scope.ServiceProvider.GetRequiredService<MyDbContext>();if(!myDbContext.Articles.Any()){List<Article>articles=newList<Article>();varbeginTime=newDateTime(2022,3,1,1,1,1);for(inti=0;i<70;i++){vararticle=newArticle();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();

第七步 编写查询表达式

publicasyncTask<IActionResult>Waterfall([FromQuery]stringlastId,[FromQuery]inttake){Console.WriteLine($"-----------开始查询,lastId:[{lastId}],take:[{take}]-----------");varlist=await_myDbContext.Articles.WhereIf(o=>String.Compare(o.Id,lastId)<0,!string.IsNullOrWhiteSpace(lastId)).Take(take)..OrderByDescending(o=>o.PublishTime)ToListAsync();returnOk(list);}

运行程序

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

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

首先针对ArticleRoute进行代码编写

publicclassArticleRoute:AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute<Article>{publicoverridevoidConfigure(EntityMetadataTableBuilder<Article>builder){builder.ShardingProperty(o=>o.PublishTime);builder.ShardingExtraProperty(o=>o.Id);}publicoverrideboolAutoCreateTableByTime(){returntrue;}publicoverrideDateTimeGetBeginTime(){returnnewDateTime(2022,3,1);}publicoverrideIEntityQueryConfiguration<Article>CreateEntityQueryConfiguration(){returnnewArticleEntityQueryConfiguration();}publicoverrideExpression<Func<string,bool>>GetExtraRouteFilter(objectshardingKey,ShardingOperatorEnumshardingOperator,stringshardingPropertyName){switch(shardingPropertyName){casenameof(Article.Id):returnGetArticleIdRouteFilter(shardingKey,shardingOperator);}returnbase.GetExtraRouteFilter(shardingKey,shardingOperator,shardingPropertyName);}///<summary>///文章id的路由///</summary>///<paramname="shardingKey"></param>///<paramname="shardingOperator"></param>///<returns></returns>privateExpression<Func<string,bool>>GetArticleIdRouteFilter(objectshardingKey,ShardingOperatorEnumshardingOperator){//将分表字段转成订单编号varid=shardingKey?.ToString()??string.Empty;//判断订单编号是否是我们符合的格式if(!CheckArticleId(id,outvarorderTime)){//如果格式不一样就直接返回false那么本次查询由于是and链接的以是本次查询不会经由任何路由,可以有效的防止恶意攻击returntail=>false;}//当前韶光的tailvarcurrentTail=TimeFormatToTail(orderTime);//由于是按月分表以是获取下个月的韶光判断id是否是在临界点创建的//varnextMonthFirstDay=ShardingCoreHelper.GetNextMonthFirstDay(DateTime.Now);//这个是缺点的varnextMonthFirstDay=ShardingCoreHelper.GetNextMonthFirstDay(orderTime);if(orderTime.AddSeconds(10)>nextMonthFirstDay){varnextTail=TimeFormatToTail(nextMonthFirstDay);returnDoArticleIdFilter(shardingOperator,orderTime,currentTail,nextTail);}//由于是按月分表以是获取这个月月初的韶光判断id是否是在临界点创建的//if(orderTime.AddSeconds(-10)<ShardingCoreHelper.GetCurrentMonthFirstDay(DateTime.Now))//这个是缺点的if(orderTime.AddSeconds(-10)<ShardingCoreHelper.GetCurrentMonthFirstDay(orderTime)){//上个月tailvarpreviewTail=TimeFormatToTail(orderTime.AddSeconds(-10));returnDoArticleIdFilter(shardingOperator,orderTime,previewTail,currentTail);}returnDoArticleIdFilter(shardingOperator,orderTime,currentTail,currentTail);}privateExpression<Func<string,bool>>DoArticleIdFilter(ShardingOperatorEnumshardingOperator,DateTimeshardingKey,stringminTail,stringmaxTail){switch(shardingOperator){caseShardingOperatorEnum.GreaterThan:caseShardingOperatorEnum.GreaterThanOrEqual:{returntail=>String.Compare(tail,minTail,StringComparison.Ordinal)>=0;}caseShardingOperatorEnum.LessThan:{varcurrentMonth=ShardingCoreHelper.GetCurrentMonthFirstDay(shardingKey);//处于临界值o=>o.time<[2021-01-0100:00:00]尾巴20210101不应该被返回if(currentMonth==shardingKey)returntail=>String.Compare(tail,maxTail,StringComparison.Ordinal)<0;returntail=>String.Compare(tail,maxTail,StringComparison.Ordinal)<=0;}caseShardingOperatorEnum.LessThanOrEqual:returntail=>String.Compare(tail,maxTail,StringComparison.Ordinal)<=0;caseShardingOperatorEnum.Equal:{varisSame=minTail==maxTail;if(isSame){returntail=>tail==minTail;}else{returntail=>tail==minTail||tail==maxTail;}}default:{returntail=>true;}}}privateboolCheckArticleId(stringorderNo,outDateTimeorderTime){//yyyyMMddHHmmssif(orderNo.Length==14){if(DateTime.TryParseExact(orderNo,"yyyyMMddHHmmss",CultureInfo.InvariantCulture,DateTimeStyles.None,outvarparseDateTime)){orderTime=parseDateTime;returntrue;}}orderTime=DateTime.MinValue;returnfalse;}}

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

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

然后我们连续查询看当作果

第三页也是如此

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

总结

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

末了

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

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

写在末了关注【数字聪慧化基地】(微信‬扫描‬下方‬二维码‬),免费领取如下‬15个‬视频‬教程‬!

‬回答'口试',获取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技能社区互换群

标签:

相关文章

php为无色透明技巧_水货钻石其实也还行

从各种钻石中,可以看到大大小小的“包裹体” 图片来源:参考文献包裹体的种类多样。比钻石形成更早的包裹体,叫“原生包裹体”;与钻石同...

网站建设 2024-12-19 阅读1 评论0

phpstudy发送gbk技巧_php的文件上传

这里首先声明一下这一章的内容比较多,比较难,你要抱着和自己去世磕的态度。细微之处不放过,多敲多练是王道。 学习就像爬山,得一步一步...

网站建设 2024-12-19 阅读1 评论0