высокопроизводительное легковесное решение для шардинга таблиц и баз данных в efcore с поддержкой разделения чтения и записи.
китайская документация Github | английская документация Github
китайская документация Gitee | английская документация Gitee
shardingcore последняя версия.efcoreверсия.x.x
первая версия это версия shardingcore
вторая версия это версия efcore
остальные версии используют последнюю версию
efcore8 использует shardingcore7.8.x.x
efcore7 использует shardingcore7.7.x.x
efcore6 использует shardingcore7.6.x.x
efcore5 использует shardingcore7.5.x.x
efcore3 использует shardingcore7.3.x.x
efcore2 использует shardingcore7.2.x.x
Выпуск | EF Core | .NET | .NET (Core) |
---|---|---|---|
6.x.x.x | 6.0.0 | net 6.0 | 6.0+ |
5.x.x.x | 5.0.10 | Standard 2.1 | 5.0+ |
3.x.x.x | 3.1.18 | Standard 2.0 | 2.0+ |
2.x.x.x | 2.2.6 | Standard 2.0 | 2.0+ |
Используйте условие компиляции с NetFramework нет
Выпуск | EF Core | .NET (Core) |
---|---|---|
6.7.0.0+ | 6.x | net6 |
6.7.0.0+ | 5.x | net5 или netstandard2.1 |
6.7.0.0+ | 3.x | netcoreapp3 или netstandard2.0 |
6.7.0.0+ | 2.x | netcoreapp2 |
5 шагов для реализации шардинга по месяцам и поддержки автоматического создания таблиц по месяцам
Выберите драйвер базы данных для efcore
# основной пакет
PM> Install-Package ShardingCore
# драйвер SQL Server
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
# драйвер MySQL
#PM> Install-Package Pomelo.EntityFrameworkCore.MySql
# используйте другой драйвер базы данных, если он поддерживается efcore
Запросите сущность заказа
/// <summary>
/// таблица заказов
/// </summary>
public class Order
{
/// <summary>
/// идентификатор заказа
/// </summary>
public string Id { get; set; }
/// <summary>
/// идентификатор плательщика
/// </summary>
public string Payer { get; set; }
/// <summary>
/// сумма платежа центами
/// </summary>
public long Money { get; set; }
/// <summary>
/// регион
/// </summary>
public string Area { get; set; }
/// <summary>
/// статус заказа
/// </summary>
public OrderStatusEnum OrderStatus { get; set; }
/// <summary>
/// время создания
/// </summary>
public DateTime CreationTime { get; set; }
}
public enum OrderStatusEnum
{
NotPaid = 1,
Paying = 2,
Paid = 3,
PaymentFailed = 4
}
Создайте MyDbContext
, расширяющий AbstractShardingDbContext
,
тогда этот контекст поддерживает шардинг базы данных, если вы хотите поддержать
шардинг таблиц (например, order_202101, order_202102, order_202103...),
вам нужно реализовать IShardingTableDbContext
public class MyDbContext : AbstractShardingDbContext, IShardingTableDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Order>(entity =>
{
entity.HasKey(o => o.Id);
entity.Property(o => o.Id).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.Payer).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.Area).IsRequired().IsUnicode(false).HasMaxLength(50);
entity.Property(o => o.OrderStatus).HasConversion<int>();
// настоящее имя таблицы Order_202101, Order_202102, Order_202103...
entity.ToTable(nameof(Order));
});
}
/// <summary>
/// пустое выполнение, если используется шардинг таблиц
/// </summary>
public IRouteTail RouteTail { get; set; }
}
// Конструктор маршрута поддерживает внедрение зависимостей, то есть область жизни является `Singleton`
public class OrderVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
{
//private readonly IServiceProvider _serviceProvider;
//public OrderVirtualTableRoute(IServiceProvider serviceProvider)
//{
//_serviceProvider = serviceProvider;
//}
/// <summary>
/// фиксированное значение, не использовать DateTime.Now, так как при перезапуске приложения это значение может измениться
/// </summary>
/// <returns></returns>
public override DateTime GetBeginTime()
{
return new DateTime(2021, 1, 1);
}
/// <summary>
/// настройте свойство шардинга
/// </summary>
/// <param name="builder"></param>
public override void Configure(EntityMetadataTableBuilder<Order> builder)
{
builder.ShardingProperty(o => o.CreationTime);
}
/// <summary>
/// включите задачу автоматической генерации таблиц
/// </summary>
/// <returns></returns>
public override bool AutoCreateTableByTime()
{
return true;
}
}
Необходимо изменить второй параметр метода AddDefaultDataSource
(строка подключения), не изменяйте параметры делегата UseShardingQuery
и UseShardingTransaction
public void ConfigureServices(IServiceCollection services)
{
// настройки шардинга
services.AddShardingDbContext<MyDbContext>()
.UseRouteConfig(op =>
{
op.AddShardingTableRoute<OrderVirtualTableRoute>();
}).UseConfig(op =>
{
op.UseShardingQuery((connStr, builder) =>
{
// connStr — входящий параметр делегата
builder.UseSqlServer(connStr);
});
op.UseShardingTransaction((connection, builder) =>
{
// connection — входящий параметр делегата
builder.UseSqlServer(connection);
});
// используйте вашу строку подключения к базе данных
op.AddDefaultDataSource(Guid.NewGuid().ToString("n"),
"Data Source=localhost;Initial Catalog=EFCoreShardingTableDB;Integrated Security=True;");
}).AddShardingCore();
}
``` public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Необязательно, включение проверки отсутствующих таблиц и автоматической генерации
app.ApplicationServices.UseAutoTryCompensateTable();
// Другие настройки...
}
Если EFCore поддерживает шардинг таблиц, как Sharding-JDBC в Java, вы можете радостно программировать на EFCore
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly MyDbContext _myDbContext;
public ValuesController(MyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
[HttpGet]
public async Task<IActionResult> Get()
{
//_myDbContext.Add(order);
//_myDbContext.Update(order);
//_myDbContext.Remove(order);
//_myDbContext.SaveChanges();
var order = await _myDbContext.Set<Order>().FirstOrDefaultAsync(o => o.Id == "2");
return Ok(order);
}
}
Тестирование
Пример тестирования производительности
// * Summary *
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.18363.1500 (1909/November2019Update/19H2) AMD Ryzen 9 3900X, 1 CPU, 24 логических и 12 физических ядер .NET SDK=6.0.100 [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT
Метод | N | Среднее | Ошибка | Стандартное отклонение |
---|---|---|---|---|
NoShardingIndexFirstOrDefaultAsync | 10 | 1.512 мс | 0.0071 мс | 0.0063 мс |
ShardingIndexFirstOrDefaultAsync | 10 | 1.567 мс | 0.0127 мс | 0.0113 мс |
Все примеры ниже демонстрируются с использованием SQL Server. Код показывает примеры распределения таблиц, если требуется распределение баз данных, можно обратиться к Sample.SqlServerShardingDataSource.
Краткое описание библиотеки: все версии библиотеки основаны на номерах версий EF Core. Номер второй версии равный 2 указывает на поддержку только распределения баз данных, а номер более 3 указывает на поддержку как распределения баз данных, так и распределения таблиц. Библиотека разделена на две основные ветви: main и shardingTableOnly. Она поддерживает полностью настраиваемое распределение баз данных для 95% бизнес-потребностей, а также поддерживает распределение таблиц с использованием произвольной логики.
Несколько ключевых концепций библиотеки:
<PackageReference Include="ShardingCore" Version="5.LastVersion" />
или
<PackageReference Include="ShardingCore" Version="3.LastVersion" />
или
<PackageReference Include="ShardingCore" Version="2.LastVersion" />
Пример использования модульного распределения таблиц для пользователя:
public class SysUserMod
{
/// <summary>
/// ID пользователя для распределения таблиц
/// </summary>
public string Id { get; set; }
/// <summary>
/// Имя пользователя
/// </summary>
public string Name { get; set; }
/// <summary>
/// Возраст пользователя
/// </summary>
public int Age { get; set; }
}
Создание виртуального маршрута:
public class SysUserModVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualRoute<SysUserMod>
{
// Длина хвоста: 00, 01, 02......99
// Хэш-код % 3: [0, 1, 2]
public SysUserModVirtualTableRoute() : base(2, 3)
{
}
public override void Configure(EntityMetadataTableBuilder<SysUserMod> builder)
{
builder.ShardingProperty(o => o.Id);
}
}
Для использования распределения таблиц необходимо создать контекст баз данных, который наследует от IShardingTableDbContext
и реализует IShardingDbContext
. По умолчанию предоставляется AbstractShardingDbContext
.
``````markdown
//DefaultTableDbContext является фактическим контекстом выполнения
public class DefaultShardingDbContext : AbstractShardingDbContext, IShardingTableDbContext
{
public DefaultShardingDbContext(DbContextOptions<DefaultShardingDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new SysUserModMap());
}
public IRouteTail RouteTail { get; set; }
}
Startup.cs
в методе ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// если вы хотите использовать операцию без шардинга
// services.AddDbContext<DefaultTableDbContext>(o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDB;Integrated Security=True"));
```### Операции с базой данных
#### Добавление записей в контекст базы данных
```csharp
_defaultShardingDbContext.AddRange(userMods);
await _defaultShardingDbContext.SaveChangesAsync();
public async Task ToList_All()
{
var mods = await _defaultShardingDbContext.Set<SysUserMod>().ToListAsync();
Assert.Equal(1000, mods.Count);
}
``` var modOrders1 = await _defaultShardingDbContext.Set<SysUserMod>().OrderBy(o => o.Age).ToListAsync();
int ascAge = 1;
foreach (var sysUserMod in modOrders1)
{
Assert.Equal(ascAge, sysUserMod.Age);
ascAge++;
}
var modOrders2 = await _defaultShardingDbContext.Set<SysUserMod>().OrderByDescending(o => o.Age).ToListAsync();
int descAge = 1000;
foreach (var sysUserMod in modOrders2)
{
Assert.Equal(descAge, sysUserMod.Age);
descAge--;
}
}
Метод | Описание |
---|---|
ToListAsync | Возвращает все записи |
FirstOrDefaultAsync | Возвращает первую найденную запись |
MaxAsync | Возвращает максимальное значение поля |
MinAsync | Возвращает минимальное значение поля |
AnyAsync | Проверяет наличие записей |
CountAsync | Возвращает количество записей |
LongCountAsync | Возвращает количество записей как long |
SumAsync | Возвращает сумму значений поля |
AverageAsync | Возвращает среднее значение поля |
ContainsAsync | Проверяет наличие значения в поле |
GroupByAsync | Группирует записи |
Абстракция | Описание |
---|---|
AbstractSimpleShardingModKeyIntVirtualTableRoute | Маршрутизация по модулю целого числа |
AbstractSimpleShardingModKeyStringVirtualTableRoute | Маршрутизация по модулю строки |
AbstractSimpleShardingDayKeyDateTimeVirtualTableRoute | Маршрутизация по времени (дата и время) |
AbstractSimpleShardingDayKeyLongVirtualTableRoute | Маршрутизация по времени (timestamp) |
AbstractSimpleShardingWeekKeyDateTimeVirtualTableRoute | Маршрутизация по неделе (дата и время) |
AbstractSimpleShardingWeekKeyLongVirtualTableRoute | Маршрутизация по неделе (timestamp) |
AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute | Маршрутизация по месяцу (дата и время) |
AbstractSimpleShardingMonthKeyLongVirtualTableRoute | Маршрутизация по месяцу (timestamp) |
AbstractSimpleShardingYearKeyDateTimeVirtualTableRoute | Маршрутизация по году (дата и время) |
AbstractSimpleShardingYearKeyLongVirtualTableRoute | Маршрутизация по году (timestamp) |
public class SysUserModVirtualTableRoute : AbstractSimpleShardingModKeyStringVirtualTableRoute<SysUserMod>
{
protected override bool EnableHintRoute => true;
public SysUserModVirtualTableRoute() : base(2, 3)
{
}
}
using (_shardingRouteManager.CreateScope())
{
_shardingRouteManager.Current.TryCreateOrAddMustTail<SysUserMod>("00");
var mod00s = await _defaultTableDbContext.Set<SysUserMod>().Skip(10).Take(11).ToListAsync();
}
Пример автоматического создания таблиц
await _defaultShardingDbContext.SaveChangesAsync();
using (var tran = _defaultTableDbContext.Database.BeginTransaction())
{
...
_defaultTableDbContext.SaveChanges();
tran.Commit();
}
var list = new List<SysUserMod>();
var dbContexts = _defaultTableDbContext.BulkShardingEnumerable(list);
foreach (var dataSourceMap in dbContexts)
{
foreach (var tailMap in dataSourceMap.Value)
{
tailMap.Key.BulkInsert(tailMap.Value.ToList());
//tailMap.Key.BulkDelete(tailMap.Value.ToList());
//tailMap.Key.BulkUpdate(tailMap.Value.ToList());
}
}
_defaultTableDbContext.SaveChanges();
//или использовать транзакцию
using (var tran = _defaultTableDbContext.Database.BeginTransaction())
{
foreach (var dataSourceMap in dbContexts)
{
foreach (var tailMap in dataSourceMap.Value)
{
tailMap.Key.BulkInsert(tailMap.Value.ToList());
//tailMap.Key.BulkDelete(tailMap.Value.ToList());
//tailMap.Key.BulkUpdate(tailMap.Value.ToList());
}
}
_defaultTableDbContext.SaveChanges();
tran.Commit();
}
Поддерживается разделение чтения и записи между основным сервером и несколькими репликами.
services.AddShardingDbContext<DefaultShardingDbContext>((conStr, builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger)).Begin(o =>
{
o.CreateShardingTableOnStart = true;
o.EnsureCreatedWithOutShardingTable = true;
}).AddShardingTransaction((connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger)).AddDefaultDataSource("ds0", "Data Source=localhost;Initial Catalog=ShardingCoreDB1;Integrated Security=True;").AddShardingTableRoute(o => { o.AddShardingTableRoute<SysUserModVirtualTableRoute>(); }).AddReadWriteSeparation(o => { return new Dictionary<string, ISet<string>>() { { "ds0", new HashSet<string>() { "Data Source=localhost;Initial Catalog=ShardingCoreDBReadOnly1;Integrated Security=True;", "Data Source=localhost;Initial Catalog=ShardingCoreDBReadOnly2;Integrated Security=True;" } }; }; }, ReadStrategyEnum.Loop, defaultEnable: true).End();
# решает проблему задержки чтения/записи данных при отсутствии данных
# dbcontext использует соединение с базой данных для записи
_virtualDbContext.ReadWriteSeparationWriteOnly();
sharding-core сам использует потоковое обработание для получения данных, в обычной ситуации и для одного таблицы разница практически незаметна. Однако при использовании пагинации с пропуском X страниц производительность будет снижаться пропорционально увеличению X (O(n)). В настоящее время этот фреймворк реализовал высокопроизводительную пагинацию, которая может быть настроена по запросу пользователя для выполнения функции пагинации.
Поддерживаемые версии x.2.0.16+
1. Как включить конфигурацию пагинации? Например, если мы хотим настроить пагинацию для месячного журнала зарплат пользователей, нам следует реализовать интерфейс IPaginationConfiguration<>
.
public class SysUserSalaryPaginationConfiguration : IPaginationConfiguration<SysUserSalary>
{
public void Configure(PaginationBuilder<SysUserSalary> builder)
{
builder.PaginationSequence(o => o.Id)
.UseRouteCompare(Comparer<string>.Default)
.UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch);
builder.PaginationSequence(o => o.DateOfMonth)
.UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch).UseAppendIfOrderNone(10);
builder.PaginationSequence(o => o.Salary)
.UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch).UseAppendIfOrderNone();
builder.ConfigReverseShardingPage(0.5d, 10000L);
}
}
public override IPaginationConfiguration<SysUserSalary> CreatePaginationConfiguration()
{
return new SysUserSalaryPaginationConfiguration();
}
builder.PaginationSequence(o => o.Id)
— это конфигурация поля Id
как ключа для пагинации. Правила сравнения маршрута задаются через UseRouteCompare
. Это позволяет оптимизировать выборку данных, когда используется поле Id
для сортировки.UseQueryMatch
указывает правила соответствия запроса, такие как наличие поля Owner
, Named
или PrimaryMatch
.builder.ConfigReverseShardingPage
— это конфигурация обратного порядка пагинации, что помогает улучшить производительность при большом количестве пропущенных записей.var shardingPageResultAsync = await _defaultTableDbContext.Set<SysUserMod>().OrderBy(o => o.Age).ToShardingPageAsync(pageIndex, pageSize);
Id
для сортировки и добавить обратное сортирование для повышения производительности.Вы можете включить кэширование выражений через маршрут для кэширования выражений для каждого tail. Поддерживаются операторы =
, >
, >=
, <
, <=
, equal
.
public class OrderCreateTimeVirtualTableRoute : AbstractSimpleShardingMonthKeyDateTimeVirtualTableRoute<Order>
{
// Включение кэширования выражений
public override bool EnableRouteParseCompileCache => true;
}
Для кэширования выражений можно переопределить родительский метод или реализовать его самостоятельно.
public virtual Func<string, bool> CachingCompile(Expression<Func<string, bool>> parseWhere)
{
if (EnableRouteParseCompileCache)
{
var doCachingCompile = DoCachingCompile(parseWhere);
if (doCachingCompile != null)
return doCachingCompile;
doCachingCompile = CustomerCachingCompile(parseWhere);
if (doCachingCompile != null)
return doCachingCompile;
}
return parseWhere.Compile();
}
protected virtual Func<string, bool> DoCachingCompile(Expression<Func<string, bool>> parseWhere)
{
var shouldCache = ShouldCache(parseWhere);
if (shouldCache)
return _routeCompileCaches.GetOrAdd(parseWhere, key => parseWhere.Compile());
return null;
}
protected virtual Func<string, bool> CustomerCachingCompile(Expression<Func<string, bool>> parseWhere)
{
return null;
}
Кэширование выражений может повысить производительность компиляции маршрутов до 10 раз.
При использовании этого фреймворка важно отметить следующие моменты:
ShardingDbContext
переопределяет следующие службы, они могут не работать корректно. Чтобы восстановить работу, вам потребуется переопределить их самостоятельно.ShardingDbContext
return optionsBuilder.UseShardingWrapMark()
.ReplaceService<IDbSetSource, ShardingDbSetSource>()
.ReplaceService<IQueryCompiler, ShardingQueryCompiler>()
.ReplaceService<IDbContextTransactionManager, ShardingRelationalTransactionManager<TShardingDbContext>>()
.ReplaceService<IRelationalTransactionFactory, ShardingRelationalTransactionFactory<TShardingDbContext>>();
DefaultDbContext
return optionsBuilder.ReplaceService<IModelCacheKeyFactory, ShardingModelCacheKeyFactory>()
.ReplaceService<IModelCustomizer, ShardingModelCustomizer<TShardingDbContext>>();
Фреймворк использует AppDomain.CurrentDomain.GetAssemblies()
, что может вызвать проблемы с загрузкой сборок. Поэтому рекомендуется загружать необходимые DLL на уровне API.
При использовании фреймворка убедитесь, что:
IShardingTable
ShardingKey
IShardingDataSource
ShardingDataSourceKey
Startup
уже имеет добавленное виртуальное направлениеStartup
уже имеет добавленный Bootstrapper.Start()
// Поддерживает окончательное изменение
var sresult = _defaultTableDbContext.Set<SysUserMod>().ToList();
var sysUserMod98 = result.FirstOrDefault(o => o.Id == "98");
sysUserMod98.Name = "name_update" + new Random().Next(1, 99) + "_98";
await _defaultTableDbContext.SaveChangesAsync();
-- лог информации
Executed DbCommand (1ms) [Parameters=[@p1='?' (Size = 128), @p0='?' (Size = 128)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
UPDATE [SysUserMod_02] SET [Name] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
Этот фреймворк был создан, применив идеи многих существующих систем разделения таблиц. На данный момент все предоставляемые интерфейсы реализованы и поддерживают запросы между таблицами. Для пагинации также используются потоковые запросы, чтобы избежать взрывного использования памяти при пропуске больших объемов данных. Этот библиотека ещё находится в начальной стадии развития и требует доработки, поэтому прошу понимания. Если вам понравился проект, я буду рад получить звезду.
Этот документ был подготовлен ночью, надеюсь он поможет привлечь больше внимания к проекту. Также я очень ценю возможность общаться и делиться опытом.
Благодаря множеству открытых проектов и идей была создана эта система, и я надеюсь, что она сможет сделать свой вклад в развитие экосистемы .NET. Я буду продолжать поддерживать этот проект долгое время. Если вы являетесь экспертом в этой области, я буду рад сотрудничеству.
QQ группа: 771630778
Личный QQ: 326308290 (добро пожаловать для техподдержки)
Личная почта: 326308290@qq.com
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )