分库分表下极致的优化

题外话

这边说一句题外话,就是ShardingCore目前已经正式加入 NCC 开源组织了,也是希望框架和社区能发展的越来越好,希望为更多.netter提供解决方案和开源组件

介绍

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

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

你的star和点赞是我坚持下去的最大动力,一起为.net生态提供更好的解决方案

项目地址

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

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

本次优化点

直奔主题来讲下本次的极致优化具体是优化了什么,简单说就是CircuitBreakerFastFail.

断路器CircuitBreaker

我们假设这么一个场景,现在我们有一个订单order表,订单会按照月份进行分片,那么订单表会有如下几个order_202201order_202202order_202203order_202204order_202205,假设我们有5张表。
首先我们来看一条普通的语句

select * from order where id='xxx' limit 1

这是一条普通的不能在普通的sql了,查询第一条id是xxx的订单,
那么他在分表下面会如何运行

//开启5个线程并发查询
select * from order_202201 where id='xxx' limit 1
select * from order_202202 where id='xxx' limit 1
select * from order_202203 where id='xxx' limit 1
select * from order_202204 where id='xxx' limit 1
select * from order_202205 where id='xxx' limit 1
//查询出来的结果在内存中进行聚合成一个list集合
//然后在对这个list集合进行第一条的获取
list.Where(o=>o is not null).FirstOrDefault()

这个操作我相信很多同学都是可以了解的,稍微熟悉点分表分库的同学应该都知道这是基本操作了,但是这个操作看似高效(时间上)但是在连接数上而言并不是那么的高效,因为同一时间需要开打的连接数将由5个

那么在这个背景下ShardingCore参考ShardingSphere 提供了更加友好的连接控制和内存聚合模式ConnectionMode

e88548c2da949cbf701f7b2583538aed.png

这个张图上我们可以清晰的看到不同的数据库直接才用了一个并发限制,比如设置的是2,那么在相同库里面的查询将是每2个一组,进行查询,这样可以控制在同一个数据库下的连接数,进而解决了客户端连接模式下的连接数消耗猛烈的一个弊端。

//开启5个线程并发查询
{//并行select * from order_202201 where id='xxx' limit 1select * from order_202202 where id='xxx' limit 1
}//串行
{//并行select * from order_202203 where id='xxx' limit 1select * from order_202204 where id='xxx' limit 1
}//串行
{select * from order_202205 where id='xxx' limit 1
}
//查询出来的结果在内存中进行聚合成一个list集合
//然后在对这个list集合进行第一条的获取
list.Where(o=>o is not null).FirstOrDefault()

到目前为止这边已经对分片的查询优化到了一个新的高度。但是虽然我们优化了连接数的处理,但是就查询速度而言基本上是没有之前的那么快,可以说和你分组的组数成线性增加时间的消耗。
所以到此为止ShardingCore又再一次进化出了全新的翅膀CircuitBreaker断路器,我们继续往下看

我们现在的sql是

select * from order where id='xxx' limit 1

那么如果我们针对这个sql进行优化呢,譬如

select * from order where id='xxx' order by create_time desc limit 1

同样是查询第一条,添加了一个order排序那么情况就会大大的不一样,首先我们来观察我们的分片查询

//开启5个线程并发查询
--  select * from order_202201 where id='xxx' order by create_time desc  limit 1
--  select * from order_202202 where id='xxx' order by create_time desc  limit 1
--  select * from order_202203 where id='xxx' order by create_time desc  limit 1
--  select * from order_202204 where id='xxx' order by create_time desc  limit 1
--  select * from order_202205 where id='xxx' order by create_time desc  limit 1
-- 抛弃上述写法select * from order_202205 where id='xxx' order by create_time desc  limit 1select * from order_202204 where id='xxx' order by create_time desc  limit 1select * from order_202203 where id='xxx' order by create_time desc  limit 1select * from order_202202 where id='xxx' order by create_time desc  limit 1select * from order_202201 where id='xxx' order by create_time desc  limit 1

如果在连接模式下那么他们将会是2个一组,那么我们在查询第一组的结果后是否就可以直接抛弃掉下面的所有查询,也就是我们只需要查询

select * from order_202205 where id='xxx' order by create_time desc  limit 1select * from order_202204 where id='xxx' order by create_time desc  limit 1

只要他们是有返回一个以上的数据那么本次分片查询将会被终止,ShardingCore目前的大杀器,本来年前已经开发完成了,奈何太懒只是发布了版本并没有相关的说明和使用方法

CircuitBreaker

断路器,它具有类似拉闸中断操作的功能,这边简单说下linq操作下的部分方法的断路器点在哪里

方法名是否支持中断操作中断条件
First支持按顺序查询到第一个时就可以放弃其余查询
FirstOrDefault支持按顺序查询到第一个时就可以放弃其余查询
Last支持按顺序倒叙查询到第一个时就可以放弃其余查询
LastOrDefault支持按顺序倒叙查询到第一个时就可以放弃其余查询
Single支持查询到两个时就可以放弃,因为元素个数大于1个了需要抛错
SingleOrDefault支持查询到两个时就可以放弃,因为元素个数大于1个了需要抛错
Any支持查询一个结果true就可以放弃其余查询
All支持查询到一个结果fasle就可以放弃其余查询
Contains支持查询一个结果true就可以放弃其余查询
Count不支持--
LongCount不支持--
Max支持按顺序最后一条并且查询最大字段是分片顺序同字段是,max的属性只需要查询一条记录
Min支持按顺序第一条并且查询最小字段是分片顺序同字段,min的属性只需要查询一条记录
Average不支持--
Sum不支持--

这边其实只有三个操作是任何状态下都可以支持中断,其余操作需要在额外条件顺序查询的情况下才可以,并且我们本次查询分片涉及到过多的后缀表那么性能和资源的利用将会大大提升

查询配置

废话不多说我们开始以mysql作为本次案例(不要问我为什么不用SqlServer,因为写文章的时候我是mac电脑),这边我们创建一个项目新建一个订单按月分表

新建项目

安装依赖

fdcddeb214b76bbe9e0eb845e0887fab.png

添加订单表和订单表映射

public class Order{public string Id  { get; set; }public string Name  { get; set; }public DateTime Createtime  { get; set; }}public class OrderMap : IEntityTypeConfiguration<Order>{public void Configure(EntityTypeBuilder<Order> builder){builder.HasKey(o => o.Id);builder.Property(o => o.Id).HasMaxLength(32).IsUnicode(false);builder.Property(o => o.Name).HasMaxLength(255);builder.ToTable(nameof(Order));}}

添加DbContext

public class ShardingDbContext:AbstractShardingDbContext,IShardingTableDbContext{public ShardingDbContext(DbContextOptions<ShardingDbContext> options) : base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ApplyConfiguration(new OrderMap());}public IRouteTail RouteTail { get; set; }}

添加订单分片路由

从5月份开始按创建时间建表

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(2021, 5, 1);}}

启动配置

简单的配置启动创建表和库,并且添加种子数据

ILoggerFactory efLogger = LoggerFactory.Create(builder =>
{builder.AddFilter((category, level) => category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information).AddConsole();
});
builder.Services.AddControllers();
builder.Services.AddShardingDbContext<ShardingDbContext>().AddEntityConfig(op =>{op.CreateShardingTableOnStart = true;op.EnsureCreatedWithOutShardingTable = true;op.AddShardingTableRoute<OrderRoute>();op.UseShardingQuery((conStr, b) =>{b.UseMySql(conStr, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});op.UseShardingTransaction((conn, b) =>{b.UseMySql(conn, new MySqlServerVersion(new Version())).UseLoggerFactory(efLogger);});}).AddConfig(op =>{op.ConfigId = "c1";op.AddDefaultDataSource("ds0", "server=127.0.0.1;port=3306;database=db2;userid=root;password=root;");op.ReplaceTableEnsureManager(sp=>new MySqlTableEnsureManager<ShardingDbContext>());}).EnsureConfig();
var app = builder.Build();app.Services.GetRequiredService<IShardingBootstrapper>().Start();
using (var scope=app.Services.CreateScope())
{var shardingDbContext = scope.ServiceProvider.GetRequiredService<ShardingDbContext>();if (!shardingDbContext.Set<Order>().Any()){var begin = new DateTime(2021, 5, 2);List<Order> orders = new List<Order>(8);for (int i = 0; i < 8; i++){orders.Add(new Order(){Id = i.ToString(),Name = $"{begin:yyyy-MM-dd HH:mm:ss}",Createtime = begin});begin = begin.AddMonths(1);}shardingDbContext.AddRange(orders);shardingDbContext.SaveChanges();}
}
app.UseAuthorization();
app.MapControllers();
app.Run();

这边默认连接模式的分组是Environment.ProcessorCount

编写查询

d51e372a07cd3b081eb81079b170c304.png
没有配置的情况下那么这个查询将是十分糟糕

接下来我们将配置Order的查询

public class OrderQueryConfiguration:IEntityQueryConfiguration<Order>{public void Configure(EntityQueryBuilder<Order> builder){//202105,202106...是默认的顺序,false表示使用反向排序,就是如果存在分片那么分片的tail将进行反向排序202202,202201,202112,202111....builder.ShardingTailComparer(Comparer<string>.Default, false);//order by createTime asc的顺序和分片ShardingTailComparer一样那么就用true//但是目前ShardingTailComparer是倒序所以order by createTime asc需要和他一样必须要是倒序,倒序就是falsebuilder.AddOrder(o => o.CreateTime,false);//配置当不存在Order的时候如果我是FirstOrDefault那么将采用和ShardingTailComparer相反的排序执行因为是false//默认从最早的表开始查询builder.AddDefaultSequenceQueryTrip(false, CircuitBreakerMethodNameEnum.FirstOrDefault);默认从最近表开始查询//builder.AddDefaultSequenceQueryTrip(true, CircuitBreakerMethodNameEnum.FirstOrDefault);//内部配置单表查询的FirstOrDefault connections limit限制为1builder.AddConnectionsLimit(1, LimitMethodNameEnum.FirstOrDefault);}}public class OrderRoute:AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>{//......//配置路由才用这个对象查询public override IEntityQueryConfiguration<Order> CreateEntityQueryConfiguration(){return new OrderQueryConfiguration();}}

1fcb5dc28f98a4d7954a25c246b4af91.png
带配置的Order
a86259c0b92302e7b9e62f6f27b97d8b.png

现在我们将默认的配置修改回正确

//不合适因为一般而言我们肯定是查询最新的所以应该和ShardingComparer一样都是倒序查询
//builder.AddDefaultSequenceQueryTrip(false, CircuitBreakerMethodNameEnum.FirstOrDefault);
builder.AddDefaultSequenceQueryTrip(true, CircuitBreakerMethodNameEnum.FirstOrDefault);

7690b7e1c9387c2dfd4edef450d84a78.png
当然如果你希望本次查询不使用配置的连接数限制可以进行如下操作

_shardingDbContext.Set<Order>().UseConnectionMode(2).Where(o=>o.Id=="7").FirstOrDefaultAsync();

结论:当我们配置了默认分片表应该以何种顺序进行分片聚合时,如果相应的查询方法也进行了配置那么将这种查询视为顺序查询,
所有的顺序查询都符合上述表格模式,遇到对应的将直接进行熔断,不在进行后续的处理直接返回,保证高性能和防止无意义的查询。

快速失败FastFail

顾名思义就是快速失败,但是很多小伙伴可能不清楚这个快速失败的意思,失败就是失败了为什么有快速失败一说,因为ShardingCore内部的本质是将一个sql语句进行才分N条然后并行执行

-- 普通sqlselect * from order where id='1' or id='2'-- 分片sql
select * from order_1 where id='1' or id='2'
select * from order_2 where id='1' or id='2'
-- 分别对这两个sql进行并行执行

在正常情况下程序是没有什么问题的,但是由于程序是并行查询后迭代聚合所以会带来一个问题,就是假设执行order_1的线程挂掉了,那么Task.WhenAll会一致等待所有线程完成,然后抛出响应的错误,
那么这在很多情况下等于其余线程都在多无意义的操作,各自管各自。

static async Task Main(string[] args){try{await Task.WhenAll(DoSomething1(), DoSomething2());Console.WriteLine("execute success");}catch {Console.WriteLine("error");}Console.ReadLine();}static async Task<int> DoSomething1(){for (int i = 0; i < 10; i++){if (i == 2)throw new Exception("111");await Task.Delay(1000);Console.WriteLine("DoSomething1"+i);}return 1;}static async Task<int> DoSomething2(){for (int i = 0; i < 10; i++){await Task.Delay(1000);Console.WriteLine("DoSomething2"+i);}return 1;}

代码很简单就是Task.WhenAll的时候执行两个委托方法,然后让其中一个快速抛异常的情况下看看是否马上返回
894673558091e5031e9f0070e91a040a.png

结果是TaskWhenAll哪怕出现异常也需要等待所有的线程完成任务,这会在某些情况下浪费不必要的性能,所以这边ShardingCore参考资料采用了FastFail版本的

public static Task WhenAllFailFast(params Task[] tasks){if (tasks is null || tasks.Length == 0) return Task.CompletedTask;// defensive copy.var defensive = tasks.Clone() as Task[];var tcs = new TaskCompletionSource();var remaining = defensive.Length;Action<Task> check = t =>{switch (t.Status){case TaskStatus.Faulted:// we 'try' as some other task may beat us to the punch.tcs.TrySetException(t.Exception.InnerException);break;case TaskStatus.Canceled:// we 'try' as some other task may beat us to the punch.tcs.TrySetCanceled();break;default:// we can safely set here as no other task remains to run.if (Interlocked.Decrement(ref remaining) == 0){// get the results into an array.tcs.SetResult();}break;}};foreach (var task in defensive){task.ContinueWith(check, default, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);}return tcs.Task;}

7f1f6174b62fb4158f403e5f93846aa6.png
采用failfast后当前主线程会直接在错误时返回,其余线程还是继续执行,需要自行进行canceltoken.cancel或者通过共享变量来取消执行

总结

ShardngCore目前还在不断努力成长中,也希望各位多多包涵可以在使用中多多提出响应的意见和建议

  • demo https://github.com/xuejmnet/ShardingCircuitBreaker

参考资料

https://stackoverflow.com/questions/57313252/how-can-i-await-an-array-of-tasks-and-stop-waiting-on-first-exception

下期预告

下一篇我们将讲解如何让流式聚合支持更多的sql查询,如何将不支持的sql降级为union all

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

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

相关文章

关于photoshop

photoshop的常见快捷键&#xff1a;&#xff08;只写了一部分&#xff0c;还有的实用快捷键不知道&#xff09; 矩形、椭圆选框工具 M移动工具 V 套索、多边形套索、磁性套索 L 魔棒工具 W 裁剪工具 C 切片工具、切片选择工具 K 喷枪工具 J 画笔工具、铅笔工具 B 像皮图章、图案…

linux(centos) NET模式网络配置

2019独角兽企业重金招聘Python工程师标准>>> linux虚拟机一般使用桥接和net模式&#xff0c;但是由于桥接在不同的网络环境中&#xff0c;需要重新配置&#xff0c;所以建议使用net模式&#xff0c;net模式的配置步骤如下&#xff1a; 虚拟机网络连接使用NAT模式&am…

LeetCode之Island Perimeter

1、题目 You are given a map in form of a two-dimensional integer grid where 1 represents land and 0 represents water. Grid cells are connected horizontally/vertically (not diagonally). The grid is completely surrounded by water, and there is exactly one is…

java获取tomcat目录结构_tomcat目录结构简介_动力节点Java学院整理

tomcat目录结构简介如果我们有一个web应用&#xff0c;名称为“mail”(同时也是web应用所在目录的名称)&#xff0c;那么其目录内不同类型的文件应该服从如下放置的规则&#xff1a;一般来讲&#xff1a;对于html、jsp、css、js文件等&#xff0c;可以直接放置在web应用所在目录…

Linux和Windows下部署BeetleX服务网关

有朋友希望写一篇BeetleX服务网关部署到Linux和windows下并以服务的方式运行的介绍文章。接下详细介绍如何做并简单介绍一下网的使用。首先需要在官网(beetlex-io.com)下载对应版本的BeetleX服务网关&#xff08;现阶段只支持linux64和windows64&#xff09;&#xff0c;下载完…

HDU 1978 How many ways DP问题

How many ways Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2568 Accepted Submission(s): 1509 Problem Description这是一个简单的生存游戏&#xff0c;你控制一个机器人从一个棋盘的起始点(1,1)走到棋盘的…

课堂练习-找水王绪

题目&#xff1a;三人行设计了一个灌水论坛。信息学院的学生都喜欢在上面交流灌水&#xff0c;传说在论坛上有一个“水王”&#xff0c;他不但喜欢发帖&#xff0c;还会回复其他ID发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子数目的一半。 如果你有一张当前论坛的…

java添加事件监听器_Java事件监听器的四种实现方式

自身类作为事件监听器外部类作为事件监听器匿名内部类作为事件监听器内部类作为事件监听器自身类作为事件监听器:1 import javax.swing.*;2 import java.awt.*;3 import java.awt.event.*;45 /**6 *Java事件处理机制:自身类作为事件监听器7 *authorWinty(wintysgmail.com)8 *ve…

使用Brighter实现轻量型独立管道

前言上次&#xff0c;我们介绍了使用MediatR的Behaviors功能&#xff0c;在业务层实现管道模式。(《为什么应该在业务层实现管道模式&#xff0c;而不用ASP.NET Core Middleware实现 | 2点原因和实现方式》)但是&#xff0c;这种管道有个特点或者说缺点&#xff0c;不管你需不需…

CentOS6最小化安装默认启动的服务说明

centos6.2最小化安装后执行chkconfig --list,显示所有服务&#xff0c;如下图&#xff1a;下边分别进行说明&#xff1a;auditd&#xff1a;审核守护进程当 auditd 运行的时候&#xff0c;审核信息会被发送到一个用户配置日志文件中&#xff08;默认的文件是 /var/log/audit/au…

[有奖励]GeneralUpdate开源项目招募开发者

[有奖励]GeneralUpdate开源项目招募开发者希望看到这篇文章的小伙伴&#xff0c;能看完这篇文章顺便帮忙给项目点一下“star”、转发、“在看”。先在这里谢谢各位了。github仓库地址&#xff1a;https://github.com/WELL-E/AutoUpdatergitee仓库地址&#xff1a;https://gitee…

C/C++ 读取16进制文件

1.为什么有这种需求 因为有些情况需要避免出现乱码。不管什么编码都是二进制的&#xff0c;这样表示为16进制就可以啦。 2.如何读取16进制文件 最近编程用这一问题&#xff0c;网上查了一下&#xff0c;感觉还是自己写吧。 16进制数据一般是:text0x340xb5...&#xff0c;就是0x…

基于PaddleOCR实现AI发票识别的Asp.net Core应用

简要介绍用户批量上传需要识别的照片,上传成功后,系统会启动Hangfire后台Job开始调用PaddleOCR服务返回结果,这个过程有点类似微服务的架构模型。PaddleOCRPaddleOCR是百度AI团队开源的一个项目&#xff0c;应该是目前所有免费开源OCR项目中识别效果最好的,具体可以通过PaddleO…

常用的搜索引擎dork (不断更新)

为什么80%的码农都做不了架构师&#xff1f;>>> pan.baidu.com xiaomi.rar 转载于:https://my.oschina.net/ecnu/blog/265731

学习笔记之卸载远程目标进程中的DLL模块(转)

学习笔记之卸载远程目标进程中的DLL模块 (2007-07-23 23:51:02)转载▼学习笔记之卸载远程目标进程中的DLL模块2007/7/231.首先得把DLL模块中的线程结束使用CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD,0);创建系统线程的快照然后用Thread32First()和Thread32Next()遍历系统中所…

Wow,一个免费、不怕打的评论插件!

快速给网站添加评论功能大家好&#xff0c;我是鱼皮&#xff0c;前段时间我自己做的网站不是被 DDOS 攻击了么&#xff1f;然后我就即时地给大家分享了一下我是怎么临时 “化解” 这次 DDOS 攻击的。结果我今天一看&#xff0c;好家伙&#xff0c;这个视频竟然都已经 120 w 播放…

MSSQLSERVER启动不了,报SQL Server 无法生成 FRunCM 线程

为什么80%的码农都做不了架构师&#xff1f;>>> 在启动MSSQLSERVER服务时&#xff0c;提示启动不了&#xff0c;在事件查看器中发现报错&#xff1a;SQL Server 无法生成 FRunCM 线程 网上搜了一下说是&#xff1a;MSSQLSERVER的协议中VIA协议被启用了&#xff0c;…

hdu 2648 Shopping

原题链接&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid2648 纯暴力的方法T_T。。。 如下: 1 #include<cstdio>2 #include<cstdlib>3 #include<string>4 #include<iostream>5 #include<algorithm>6 typedef char State[35];7 char *ta…

Windows导出所有计划任务方法

windows计划任务的命令为&#xff1a;schtasksSCHTASKS /parameter [arguments]描述:允许管理员创建、删除、查询、更改、运行和中止本地或远程系统上的计划任务。参数列表:/Create 创建新计划任务。/Delete 删除计划任务。/Query 显示所有计划任务。…

C# 使用多个异步方法

在一个异步方法中&#xff0c;可以调用一个或多个异步方法。如何编写代码&#xff0c;取决于一个异步方法的结果是否依靠于另一个异步方法。01 按顺序调用异步方法使用 await 关键字可以调用每个异步方法。在有些情况下&#xff0c;如果一个异步方法依赖另一个异步方法的结果&a…