分库分表下极致的优化

题外话

这边说一句题外话,就是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,一经查实,立即删除!

相关文章

网页小要求

1. 制作出的网页要求图文并茂&#xff0c;有自己设计的网站Logo图标&#xff1b;文字要有字体格式和颜色上的变化&#xff0c;图形要与网页的内容相关。2. 页面要求使用DIVCSS进行页面设计布局&#xff0c;至少 4个页面&#xff08;图像文件不能太大&#xff09;&…

关于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发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子数目的一半。 如果你有一张当前论坛的…

LeetCode之Nim Game

1、题目 You are playing the following Nim Game with your friend: There is a heap of stones on the table, each time one of you take turns to remove 1 to 3 stones. The one who removes the last stone will be the winner. You will take the first turn to remove …

适配器模式和装饰模式

1 什么是适配器模式 当我要使用一个类时&#xff0c;但是我发现它的接口不是我想要的模样&#xff0c;这个时候&#xff0c;我可以使用适配器模式&#xff0c;新设计一个类&#xff0c;然后这个类提供我想要的接口&#xff0c;在它里面引用原来的类。 2 什么是装饰模式 当我想要…

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;不管你需不需…

Adobe Air 写文件如何换行

在用Air打log的时候发现,在字符串后面加"\n"并不能实现换行.百度一下才知道windows的换行是"\r\n".Mac OS 和 Linux换行符是"\n". 不同操作系统的换行符可能不一样.File有个静态属性File.lineEnding.用这个就可以了 下面是这个属性的官方帮助文档…

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

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

网页中插入javascript的几种方法

网页中插入javascript的方法常见的有两种&#xff1a; 一、直接使用html标记 JavaScript 可以出现在 html的任意地方。使用标记<script>…</script>&#xff0c;你可以在 HTML 文档的任意地方插入 JavaScript&#xff0c;甚至在<HTML>之前插入也不成问题。不…

LeetCode之Max Consecutive Ones

1、题目 Given a binary array, find the maximum number of consecutive 1s in this array. Example 1: Input: [1,1,0,1,1,1] Output: 3 Explanation: The first two digits or the last three digits are consecutive 1s.The maximum number of consecutive 1s is 3.Note: T…

java exception 行号_java日志记录错误的文件_方法_行号_报错信息

1、java日志记录错误的文件、方法、行号、报错信息StackTraceElement s e.getStackTrace()[0];1.1、记录保存的文件s.getFileName()1.2、记录保存的方法s.getMethodName()1.3、记录报错的行号 s.getLineNumber()1.4、记录报错的信息(不全面) e.getMessage()1.5、互利报错的类名…

[有奖励]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…

【转】shell pipe与输入输出重定向的区别

http://www.cnblogs.com/chengmo/archive/2010/10/21/1856577.html转载于:https://www.cnblogs.com/qrlozte/p/4465120.html