diff --git a/README.md b/README.md index e8039e77..6455f4cc 100644 --- a/README.md +++ b/README.md @@ -378,20 +378,31 @@ ctor inject IShardingRouteManager shardingRouteManager } ``` ## 读写分离 -该框架目前已经支持单node的读写分离,后续框架将支持多node的读 +该框架目前已经支持一主多从的读写分离`UseReadWriteConfiguration`第一个参数返回对应的读数据库链接,默认写数据库链接不会放入其中,并且支持轮询和随机两种读写分离策略,又因为读写分离多链接的时候会导致数据读写不一致,(如分页其实是2步第一步获取count,第二部获取list)会导致数据量在最后几页出现缺量的问题, +针对这个问题框架目前实现了自定义读链接获取策略`ReadConnStringGetStrategyEnum.LatestEveryTime`表示为每次都是新的(这个情况下会出现上述问题),`ReadConnStringGetStrategyEnum.LatestFirstTime`表示以dbcontext作为单位获取一次(同dbcontext不会出现问题), +又因为各节点读写分离网络等一系列问题会导致刚刚写入的数据没办法获取到所以系统默认在dbcontext上添加是否支持读写分离如果false默认选择写字符串去读取`DbContext.ReadWriteSupport` ```c# - services.AddShardingDbContext(o => o.UseSqlServer(hostBuilderContext.Configuration.GetSection("SqlServer")["ConnectionString"]) - ,op => - { - op.EnsureCreatedWithOutShardingTable = true; - op.CreateShardingTableOnStart = true; - op.UseShardingOptionsBuilder((connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger), - (conStr,builder)=> builder.UseSqlServer("read db connection string").UseLoggerFactory(efLogger)); - op.AddShardingTableRoute(); - op.AddShardingTableRoute(); - }); + services.AddShardingDbContext( + o => o.UseSqlServer("Data Source=localhost;Initial Catalog=ShardingCoreDB;Integrated Security=True;") + , op => + { + op.EnsureCreatedWithOutShardingTable = true; + op.CreateShardingTableOnStart = true; + op.UseShardingOptionsBuilder( + (connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger),//使用dbconnection创建dbcontext支持事务 + (conStr,builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + //.ReplaceService()//支持sqlserver2008r2 + );//使用链接字符串创建dbcontext + op.UseReadWriteConfiguration(sp => new List() + { + "Data Source=localhost;Initial Catalog=ShardingCoreDB1;Integrated Security=True;", + "Data Source=localhost;Initial Catalog=ShardingCoreDB2;Integrated Security=True;" + }, ReadStrategyEnum.Random); + op.AddShardingTableRoute(); + op.AddShardingTableRoute(); + }); ``` ## 高性能分页 diff --git a/samples/Sample.SqlServer/Shardings/SysUserSalaryPaginationConfiguration.cs b/samples/Sample.SqlServer/Shardings/SysUserSalaryPaginationConfiguration.cs index 75332005..aca03b68 100644 --- a/samples/Sample.SqlServer/Shardings/SysUserSalaryPaginationConfiguration.cs +++ b/samples/Sample.SqlServer/Shardings/SysUserSalaryPaginationConfiguration.cs @@ -12,7 +12,7 @@ namespace Sample.SqlServer.Shardings public void Configure(PaginationBuilder builder) { builder.PaginationSequence(o => o.Id) - .UseTailCompare(Comparer.Default) + .UseTailComparer(Comparer.Default) .UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch); builder.PaginationSequence(o => o.DateOfMonth) .UseQueryMatch(PaginationMatchEnum.Owner | PaginationMatchEnum.Named | PaginationMatchEnum.PrimaryMatch).UseAppendIfOrderNone(10); diff --git a/samples/Sample.SqlServer/Startup.cs b/samples/Sample.SqlServer/Startup.cs index 4b6102cf..75a2823b 100644 --- a/samples/Sample.SqlServer/Startup.cs +++ b/samples/Sample.SqlServer/Startup.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; @@ -11,6 +12,7 @@ using Sample.SqlServer.DbContexts; using Sample.SqlServer.Shardings; using ShardingCore; using ShardingCore.EFCores; +using ShardingCore.Sharding.ReadWriteConfigurations; namespace Sample.SqlServer { @@ -36,9 +38,14 @@ namespace Sample.SqlServer op.CreateShardingTableOnStart = true; op.UseShardingOptionsBuilder( (connection, builder) => builder.UseSqlServer(connection).UseLoggerFactory(efLogger),//使用dbconnection创建dbcontext支持事务 - (conStr,builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger) + (conStr,builder) => builder.UseSqlServer(conStr).UseLoggerFactory(efLogger).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) //.ReplaceService()//支持sqlserver2008r2 );//使用链接字符串创建dbcontext + op.UseReadWriteConfiguration(sp => new List() + { + "Data Source=localhost;Initial Catalog=ShardingCoreDB1;Integrated Security=True;", + "Data Source=localhost;Initial Catalog=ShardingCoreDB2;Integrated Security=True;" + }, ReadStrategyEnum.Random); op.AddShardingTableRoute(); op.AddShardingTableRoute(); }); diff --git a/samples/Samples.AbpSharding/AbstractShardingAbpDbContext.cs b/samples/Samples.AbpSharding/AbstractShardingAbpDbContext.cs index a1d30b32..2a1b5dd2 100644 --- a/samples/Samples.AbpSharding/AbstractShardingAbpDbContext.cs +++ b/samples/Samples.AbpSharding/AbstractShardingAbpDbContext.cs @@ -146,6 +146,11 @@ namespace Samples.AbpSharding } } + public string GetConnectionString() + { + throw new NotImplementedException(); + } + public void UseShardingTransaction(DbTransaction transaction) { throw new NotImplementedException(); diff --git a/src/ShardingCore/DIExtension.cs b/src/ShardingCore/DIExtension.cs index 9553c8f0..f2666738 100644 --- a/src/ShardingCore/DIExtension.cs +++ b/src/ShardingCore/DIExtension.cs @@ -19,6 +19,8 @@ using ShardingCore.Core.ShardingPage; using ShardingCore.Core.ShardingPage.Abstractions; using ShardingCore.Core.VirtualRoutes; using ShardingCore.Core.VirtualRoutes.RouteTails.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; using ShardingCore.Sharding.ShardingQueryExecutors; namespace ShardingCore @@ -32,7 +34,6 @@ namespace ShardingCore public static class DIExtension { - public static IServiceCollection AddShardingDbContext(this IServiceCollection services, Action optionsAction = null, Action> configure=null, @@ -49,14 +50,7 @@ namespace ShardingCore configure?.Invoke(shardingConfigOptions); services.AddSingleton>(sp=> shardingConfigOptions); - - //添加创建TActualDbContext 的 创建者 - var config = new ShardingDbContextOptionsBuilderConfig(shardingConfigOptions.SameConnectionConfigure,shardingConfigOptions.DefaultQueryConfigure); - services.AddSingleton>(sp=> config); - - //添加创建TActualDbContext创建者 - services.AddSingleton>(sp=> new DefaultShardingDbContextCreatorConfig(typeof(TActualDbContext))); - + services.AddShardingBaseOptions(shardingConfigOptions); Action shardingOptionAction = option => { @@ -93,12 +87,9 @@ namespace ShardingCore services.AddSingleton>(sp=> shardingConfigOptions); - //添加创建TActualDbContext 的 创建者 - var config = new ShardingDbContextOptionsBuilderConfig(shardingConfigOptions.SameConnectionConfigure,shardingConfigOptions.DefaultQueryConfigure); - services.AddSingleton>(sp=> config); - //添加创建TActualDbContext创建者 - services.AddSingleton>(sp=> new DefaultShardingDbContextCreatorConfig(typeof(TActualDbContext))); + services.AddShardingBaseOptions(shardingConfigOptions); + Action shardingOptionAction = (sp, option) => @@ -119,6 +110,49 @@ namespace ShardingCore return services; } + internal static void AddShardingBaseOptions(this IServiceCollection services, + ShardingConfigOption shardingConfigOptions) + where TActualDbContext : DbContext, IShardingTableDbContext + where TShardingDbContext : DbContext, IShardingDbContext + { + + //添加创建TActualDbContext 的DbContextOptionsBuilder创建者 + var config = new ShardingDbContextOptionsBuilderConfig(shardingConfigOptions.SameConnectionConfigure, shardingConfigOptions.DefaultQueryConfigure); + services.AddSingleton>(sp => config); + + //添加创建TActualDbContext创建者 + services.AddSingleton>(sp => new DefaultShardingDbContextCreatorConfig(typeof(TActualDbContext))); + + if (!shardingConfigOptions.UseReadWrite) + { + services.AddTransient>(); + } + else + { + services.AddTransient>(); + + services.AddSingleton>(sp=>new ReadWriteOptions(shardingConfigOptions.ReadWriteDefaultPriority, shardingConfigOptions.ReadWriteDefaultEnable, shardingConfigOptions.ReadConnStringGetStrategy)); + if (shardingConfigOptions.ReadStrategyEnum == ReadStrategyEnum.Loop) + { + services + .AddSingleton>(sp => + new LoopShardingConnectionStringResolver( + shardingConfigOptions.ReadConnStringConfigure(sp))); + }else if (shardingConfigOptions.ReadStrategyEnum == ReadStrategyEnum.Random) + { + services + .AddSingleton>(sp => + new RandomShardingConnectionStringResolver( + shardingConfigOptions.ReadConnStringConfigure(sp))); + } + + + services.AddSingleton(); + services.AddSingleton>(); + } + } internal static IServiceCollection AddInternalShardingCore(this IServiceCollection services) { diff --git a/src/ShardingCore/Extensions/ShardingExtension.cs b/src/ShardingCore/Extensions/ShardingExtension.cs index 0ff25a3a..90b8a5b8 100644 --- a/src/ShardingCore/Extensions/ShardingExtension.cs +++ b/src/ShardingCore/Extensions/ShardingExtension.cs @@ -61,6 +61,7 @@ namespace ShardingCore.Extensions #endif } + /// /// 根据对象集合解析 /// diff --git a/src/ShardingCore/Helpers/RandomHelper.cs b/src/ShardingCore/Helpers/RandomHelper.cs new file mode 100644 index 00000000..dbbb8db7 --- /dev/null +++ b/src/ShardingCore/Helpers/RandomHelper.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace ShardingCore.Helpers +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 14:33:52 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public static class RandomHelper + { + static int seed = Environment.TickCount; + + static readonly ThreadLocal random = + new ThreadLocal(() => new Random(Interlocked.Increment(ref seed))); + + public static int Next() + { + return random.Value.Next(); + } + public static int Next(int max) + { + return random.Value.Next(max); + } + public static int Next(int min,int max) + { + return random.Value.Next(min,max); + } + } +} diff --git a/src/ShardingCore/IShardingConfigOption.cs b/src/ShardingCore/IShardingConfigOption.cs index db722c0a..df9698f8 100644 --- a/src/ShardingCore/IShardingConfigOption.cs +++ b/src/ShardingCore/IShardingConfigOption.cs @@ -16,6 +16,7 @@ namespace ShardingCore { Type ShardingDbContextType { get;} Type ActualDbContextType { get;} + bool UseReadWrite { get; } void AddShardingTableRoute() where TRoute : IVirtualTableRoute; Type GetVirtualRouteType(Type entityType); diff --git a/src/ShardingCore/Sharding/AbstractShardingDbContext.cs b/src/ShardingCore/Sharding/AbstractShardingDbContext.cs index afe77b71..960b9c11 100644 --- a/src/ShardingCore/Sharding/AbstractShardingDbContext.cs +++ b/src/ShardingCore/Sharding/AbstractShardingDbContext.cs @@ -10,6 +10,8 @@ using ShardingCore.DbContexts.ShardingDbContexts; using ShardingCore.Exceptions; using ShardingCore.Extensions; using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -19,8 +21,6 @@ using System.Linq; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore.Infrastructure; -using ShardingCore.EFCores; namespace ShardingCore.Sharding { @@ -34,17 +34,19 @@ namespace ShardingCore.Sharding /// 分表分库的dbcontext /// /// - public abstract class AbstractShardingDbContext : DbContext, IShardingDbContext, IShardingTransaction where T : DbContext, IShardingTableDbContext + public abstract class AbstractShardingDbContext : DbContext, IShardingDbContext, IShardingTransaction,IShardingReadWriteSupport where T : DbContext, IShardingTableDbContext { private readonly ConcurrentDictionary _dbContextCaches = new ConcurrentDictionary(); + private readonly IShardingConfigOption shardingConfigOption; private readonly IVirtualTableManager _virtualTableManager; private readonly IRouteTailFactory _routeTailFactory; private readonly IShardingDbContextFactory _shardingDbContextFactory; private readonly IShardingDbContextOptionsBuilderConfig _shardingDbContextOptionsBuilderConfig; + private readonly IReadWriteOptions _readWriteOptions; + private readonly IConnectionStringManager _connectionStringManager; private DbContextOptions _dbContextOptions; private readonly object CREATELOCK = new object(); - private Guid idid = Guid.NewGuid(); public AbstractShardingDbContext(DbContextOptions options) : base(options) { @@ -53,7 +55,20 @@ namespace ShardingCore.Sharding _routeTailFactory = ShardingContainer.GetService(); _shardingDbContextOptionsBuilderConfig = ShardingContainer .GetService>() - .FirstOrDefault(o => o.ShardingDbContextType == ShardingDbContextType); + .FirstOrDefault(o => o.ShardingDbContextType == ShardingDbContextType)??throw new ArgumentNullException(nameof(IShardingDbContextOptionsBuilderConfig)); + + _connectionStringManager = ShardingContainer.GetService>() + .FirstOrDefault(o => o.ShardingDbContextType == ShardingDbContextType) ?? throw new ArgumentNullException(nameof(IConnectionStringManager)); + + shardingConfigOption =ShardingContainer.GetService>().FirstOrDefault(o=>o.ShardingDbContextType==ShardingDbContextType&&o.ActualDbContextType==typeof(T)) ?? throw new ArgumentNullException(nameof(IShardingConfigOption)); + if (shardingConfigOption.UseReadWrite) + { + _readWriteOptions = ShardingContainer + .GetService>() + .FirstOrDefault(o => o.ShardingDbContextType == ShardingDbContextType) ?? throw new ArgumentNullException(nameof(IReadWriteOptions)); + ReadWriteSupport = _readWriteOptions.ReadWriteSupport; + ReadWritePriority = _readWriteOptions.ReadWritePriority; + } } public abstract Type ShardingDbContextType { get; } @@ -69,6 +84,23 @@ namespace ShardingCore.Sharding //} + public int ReadWritePriority { get; set; } + public bool ReadWriteSupport { get; set; } + public ReadConnStringGetStrategyEnum GetReadConnStringGetStrategy() + { + return _readWriteOptions.ReadConnStringGetStrategy; + } + + public string GetWriteConnectionString() + { + return GetConnectionString(); + } + public string GetConnectionString() + { + return Database.GetDbConnection().ConnectionString; + } + + private DbContextOptionsBuilder CreateDbContextOptionBuilder() { Type type = typeof(DbContextOptionsBuilder<>); @@ -83,10 +115,10 @@ namespace ShardingCore.Sharding _shardingDbContextOptionsBuilderConfig.UseDbContextOptionsBuilder(dbConnection, dbContextOptionBuilder); return dbContextOptionBuilder.Options; } - private DbContextOptions CreateMonopolyDbContextOptions() + private DbContextOptions CreateParallelDbContextOptions() { var dbContextOptionBuilder = CreateDbContextOptionBuilder(); - var connectionString = Database.GetDbConnection().ConnectionString; + var connectionString = _connectionStringManager.GetConnectionString(this); _shardingDbContextOptionsBuilderConfig.UseDbContextOptionsBuilder(connectionString, dbContextOptionBuilder); return dbContextOptionBuilder.Options; } @@ -106,9 +138,9 @@ namespace ShardingCore.Sharding return new ShardingDbContextOptions(_dbContextOptions, routeTail); } - private ShardingDbContextOptions CetMonopolyShardingDbContextOptions(IRouteTail routeTail) + private ShardingDbContextOptions CetParallelShardingDbContextOptions(IRouteTail routeTail) { - return new ShardingDbContextOptions(CreateMonopolyDbContextOptions(), routeTail); + return new ShardingDbContextOptions(CreateParallelDbContextOptions(), routeTail); } @@ -133,7 +165,7 @@ namespace ShardingCore.Sharding } else { - return _shardingDbContextFactory.Create(ShardingDbContextType, CetMonopolyShardingDbContextOptions(routeTail)); + return _shardingDbContextFactory.Create(ShardingDbContextType, CetParallelShardingDbContextOptions(routeTail)); } } @@ -166,6 +198,7 @@ namespace ShardingCore.Sharding } } + public void UseShardingTransaction(DbTransaction transaction) { _dbContextCaches.Values.ForEach(o => o.Database.UseTransaction(transaction)); diff --git a/src/ShardingCore/Sharding/Abstractions/IConnectionStringManager.cs b/src/ShardingCore/Sharding/Abstractions/IConnectionStringManager.cs new file mode 100644 index 00000000..c605ab61 --- /dev/null +++ b/src/ShardingCore/Sharding/Abstractions/IConnectionStringManager.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.EntityFrameworkCore; + +namespace ShardingCore.Sharding.Abstractions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/7 10:29:38 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public interface IConnectionStringManager + { + Type ShardingDbContextType { get; } + string GetConnectionString(IShardingDbContext shardingDbContext); + } +} diff --git a/src/ShardingCore/Sharding/Abstractions/IShardingDbContext.cs b/src/ShardingCore/Sharding/Abstractions/IShardingDbContext.cs index 072aaf18..3f9ac9c7 100644 --- a/src/ShardingCore/Sharding/Abstractions/IShardingDbContext.cs +++ b/src/ShardingCore/Sharding/Abstractions/IShardingDbContext.cs @@ -47,7 +47,7 @@ namespace ShardingCore.Sharding.Abstractions IEnumerable CreateExpressionDbContext(Expression> where) where TEntity : class; - + string GetConnectionString(); } diff --git a/src/ShardingCore/Sharding/Abstractions/IShardingReadWriteSupport.cs b/src/ShardingCore/Sharding/Abstractions/IShardingReadWriteSupport.cs new file mode 100644 index 00000000..f45ffb40 --- /dev/null +++ b/src/ShardingCore/Sharding/Abstractions/IShardingReadWriteSupport.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ShardingCore.Sharding.ReadWriteConfigurations; + +namespace ShardingCore.Sharding.Abstractions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 20:27:17 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public interface IShardingReadWriteSupport + { + int ReadWritePriority { get; set; } + bool ReadWriteSupport { get; set; } + ReadConnStringGetStrategyEnum GetReadConnStringGetStrategy(); + string GetWriteConnectionString(); + } +} diff --git a/src/ShardingCore/Sharding/DefaultConnectionStringManager.cs b/src/ShardingCore/Sharding/DefaultConnectionStringManager.cs new file mode 100644 index 00000000..2e15c410 --- /dev/null +++ b/src/ShardingCore/Sharding/DefaultConnectionStringManager.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; + +namespace ShardingCore.Sharding +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/7 10:32:26 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class DefaultConnectionStringManager:IConnectionStringManager where TShardingDbContext:DbContext,IShardingDbContext + { + public Type ShardingDbContextType => typeof(TShardingDbContext); + + public string GetConnectionString(IShardingDbContext shardingDbContext) + { + return shardingDbContext.GetConnectionString(); + } + } +} diff --git a/src/ShardingCore/Sharding/PaginationConfigurations/PaginationOrderPropertyBuilder.cs b/src/ShardingCore/Sharding/PaginationConfigurations/PaginationOrderPropertyBuilder.cs index 71410702..26668fc8 100644 --- a/src/ShardingCore/Sharding/PaginationConfigurations/PaginationOrderPropertyBuilder.cs +++ b/src/ShardingCore/Sharding/PaginationConfigurations/PaginationOrderPropertyBuilder.cs @@ -25,7 +25,7 @@ namespace ShardingCore.Sharding.PaginationConfigurations /// /// /// - public PaginationOrderPropertyBuilder UseTailCompare(IComparer tailComparer) + public PaginationOrderPropertyBuilder UseTailComparer(IComparer tailComparer) { _paginationSequenceConfig.TailComparer= tailComparer ?? throw new ArgumentException(nameof(tailComparer)); diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IReadWriteOptions.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IReadWriteOptions.cs new file mode 100644 index 00000000..ccb7cf0d --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IReadWriteOptions.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ShardingCore.Sharding.ReadWriteConfigurations.Abstractions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/7 11:13:52 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public interface IReadWriteOptions + { + Type ShardingDbContextType { get; } + /// + /// 默认读写配置优先级 + /// + int ReadWritePriority { get; } + /// + /// 默认是否开启读写分离 + /// + bool ReadWriteSupport { get; } + ReadConnStringGetStrategyEnum ReadConnStringGetStrategy { get; } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingConnectionStringResolver.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingConnectionStringResolver.cs new file mode 100644 index 00000000..b878f925 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingConnectionStringResolver.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations.Abstractions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 13:01:59 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public interface IShardingConnectionStringResolver + { + Type ShardingDbContextType { get; } + string GetConnectionString(); + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingReadWriteAccessor.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingReadWriteAccessor.cs new file mode 100644 index 00000000..2b054377 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingReadWriteAccessor.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ShardingCore.Sharding.ReadWriteConfigurations.Abstractions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 16:30:44 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public interface IShardingReadWriteAccessor + { + Type ShardingDbContextType { get;} + ShardingReadWriteContext ShardingReadWriteContext { get; set; } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingReadWriteManager.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingReadWriteManager.cs new file mode 100644 index 00000000..67cc590d --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/Abstractions/IShardingReadWriteManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations.Abstractions +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 16:31:32 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public interface IShardingReadWriteManager + { + ShardingReadWriteContext GetCurrent() + where TShardingDbContext : DbContext, IShardingDbContext; + + ShardingReadWriteContext GetCurrent(Type shardingDbContextType); + + ShardingReadWriteScope CreateScope() + where TShardingDbContext : DbContext, IShardingDbContext; + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/LoopShardingConnectionStringResolver.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/LoopShardingConnectionStringResolver.cs new file mode 100644 index 00000000..ba0f76c6 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/LoopShardingConnectionStringResolver.cs @@ -0,0 +1,38 @@ +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 14:39:23 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class LoopShardingConnectionStringResolver : IShardingConnectionStringResolver + { + public Type ShardingDbContextType => typeof(TShardingDbContext); + + private readonly string[] _connectionStrings; + private readonly int _length; + private long _seed = 0; + public LoopShardingConnectionStringResolver(IEnumerable connectionStrings) + { + _connectionStrings = connectionStrings.ToArray(); + _length = _connectionStrings.Length; + } + public string GetConnectionString() + { + Interlocked.Increment(ref _seed); + var next = (int)(_seed % _length); + if (next < 0) + return _connectionStrings[Math.Abs(next)]; + return _connectionStrings[next]; + } + + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/RandomShardingConnectionStringResolver.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/RandomShardingConnectionStringResolver.cs new file mode 100644 index 00000000..ff586cd3 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/RandomShardingConnectionStringResolver.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Helpers; +using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 14:22:55 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class RandomShardingConnectionStringResolver :IShardingConnectionStringResolver + { + public Type ShardingDbContextType => typeof(TShardingDbContext); + + private readonly string[] _connectionStrings; + private readonly int _length; + public RandomShardingConnectionStringResolver(IEnumerable connectionStrings) + { + _connectionStrings = connectionStrings.ToArray(); + _length = _connectionStrings.Length; + } + public string GetConnectionString() + { + var next = RandomHelper.Next(0, _length); + return _connectionStrings[next]; + + } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadStrategyEnum.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadStrategyEnum.cs new file mode 100644 index 00000000..9f75f67c --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadStrategyEnum.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 13:08:31 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public enum ReadStrategyEnum + { + Random=1, + Loop=2, + } + + public enum ReadConnStringGetStrategyEnum + { + /// + /// 每次都是最新的 + /// + LatestEveryTime, + /// + /// 已dbcontext作为缓存条件每次都是第一次获取的 + /// + LatestFirstTime + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadWriteConnectionStringManager.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadWriteConnectionStringManager.cs new file mode 100644 index 00000000..1b45252e --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadWriteConnectionStringManager.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/7 10:37:28 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class ReadWriteConnectionStringManager : IConnectionStringManager where TShardingDbContext : DbContext, IShardingDbContext + { + private readonly IShardingReadWriteManager _shardingReadWriteManager; + public Type ShardingDbContextType => typeof(TShardingDbContext); + private IShardingConnectionStringResolver _shardingConnectionStringResolver; + private string _cacheConnectionString; + + + public ReadWriteConnectionStringManager(IShardingReadWriteManager shardingReadWriteManager, IEnumerable shardingConnectionStringResolvers) + { + _shardingReadWriteManager = shardingReadWriteManager; + _shardingConnectionStringResolver = shardingConnectionStringResolvers.FirstOrDefault(o => o.ShardingDbContextType == ShardingDbContextType) ?? throw new ArgumentNullException($"{ShardingDbContextType.FullName}:{nameof(shardingConnectionStringResolvers)}"); + } + public string GetConnectionString(IShardingDbContext shardingDbContext) + { + if (!(shardingDbContext is IShardingReadWriteSupport shardingReadWriteSupport)) + { + return shardingDbContext.GetConnectionString(); + } + var shardingReadWriteContext = _shardingReadWriteManager.GetCurrent(ShardingDbContextType); + var support = shardingReadWriteSupport.ReadWriteSupport; + if (shardingReadWriteContext != null) + { + support = (shardingReadWriteSupport.ReadWritePriority >= shardingReadWriteContext.DefaultPriority) + ? shardingReadWriteSupport.ReadWriteSupport + : shardingReadWriteContext.DefaultReadEnable; + } + + if (support) + { + return GetReadConnectionString0(shardingReadWriteSupport); + } + return shardingReadWriteSupport.GetWriteConnectionString(); + } + private string GetReadConnectionString0(IShardingReadWriteSupport shardingReadWriteSupport) + { + var readConnStringGetStrategy = shardingReadWriteSupport.GetReadConnStringGetStrategy(); + if (readConnStringGetStrategy == ReadConnStringGetStrategyEnum.LatestFirstTime) + { + if (_cacheConnectionString == null) + _cacheConnectionString = _shardingConnectionStringResolver.GetConnectionString(); + return _cacheConnectionString; + } + else if (readConnStringGetStrategy == ReadConnStringGetStrategyEnum.LatestEveryTime) + { + return _shardingConnectionStringResolver.GetConnectionString(); + } + else + { + throw new InvalidOperationException($"ReadWriteConnectionStringManager:{readConnStringGetStrategy}"); + } + } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadWriteOptions.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadWriteOptions.cs new file mode 100644 index 00000000..01686b43 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ReadWriteOptions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/7 11:06:40 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + + public class ReadWriteOptions : IReadWriteOptions + where TShardingDbContext : DbContext, IShardingDbContext + { + public ReadWriteOptions(int readWritePriority, bool readWriteSupport, ReadConnStringGetStrategyEnum readConnStringGetStrategy) + { + ReadWritePriority = readWritePriority; + ReadWriteSupport = readWriteSupport; + ReadConnStringGetStrategy = readConnStringGetStrategy; + } + public Type ShardingDbContextType => typeof(TShardingDbContext); + /// + /// 默认读写配置优先级 + /// + public int ReadWritePriority { get; } + /// + /// 默认是否开启读写分离 + /// + public bool ReadWriteSupport { get; } + public ReadConnStringGetStrategyEnum ReadConnStringGetStrategy { get; } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteAccessor.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteAccessor.cs new file mode 100644 index 00000000..997dc29b --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteAccessor.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 16:54:23 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class ShardingReadWriteAccessor:IShardingReadWriteAccessor where TShardingDbContext:DbContext,IShardingDbContext + { + private static AsyncLocal _shardingReadWriteContext = new AsyncLocal(); + + public Type ShardingDbContextType => typeof(TShardingDbContext); + + /// + /// + /// + public ShardingReadWriteContext ShardingReadWriteContext + { + get => _shardingReadWriteContext.Value; + set => _shardingReadWriteContext.Value = value; + } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteContext.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteContext.cs new file mode 100644 index 00000000..2b7b29f0 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteContext.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 16:52:29 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class ShardingReadWriteContext + { + public bool DefaultReadEnable { get; set; } + public int DefaultPriority { get; set; } + + private ShardingReadWriteContext() + { + DefaultReadEnable = false; + DefaultPriority = 0; + } + + public static ShardingReadWriteContext Create() + { + return new ShardingReadWriteContext(); + } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteManager.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteManager.cs new file mode 100644 index 00000000..c35a8b71 --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteManager.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Extensions; +using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 21:02:56 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class ShardingReadWriteManager:IShardingReadWriteManager + { + private readonly ConcurrentDictionary _shardingReadWriteAccessors; + + public ShardingReadWriteContext GetCurrent() where TShardingDbContext : DbContext, IShardingDbContext + { + return GetCurrent(typeof(TShardingDbContext)); + } + + public ShardingReadWriteContext GetCurrent(Type shardingDbContextType) + { + if (!shardingDbContextType.IsShardingDbContext()) + throw new InvalidOperationException(shardingDbContextType.FullName); + + if (_shardingReadWriteAccessors.TryGetValue(shardingDbContextType, out var accessor)) + return accessor.ShardingReadWriteContext; + throw new InvalidOperationException(shardingDbContextType.FullName); + } + + public ShardingReadWriteManager(IEnumerable shardingReadWriteAccessors) + { + + _shardingReadWriteAccessors = new ConcurrentDictionary(shardingReadWriteAccessors.ToDictionary(o => o.ShardingDbContextType, o => o)); + } + + public ShardingReadWriteScope CreateScope() where TShardingDbContext : DbContext, IShardingDbContext + { + var shardingPageScope = new ShardingReadWriteScope(_shardingReadWriteAccessors.Values); + shardingPageScope.ShardingReadWriteAccessor.ShardingReadWriteContext = ShardingReadWriteContext.Create(); + return shardingPageScope; + } + } +} diff --git a/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteScope.cs b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteScope.cs new file mode 100644 index 00000000..739469ba --- /dev/null +++ b/src/ShardingCore/Sharding/ReadWriteConfigurations/ShardingReadWriteScope.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.EntityFrameworkCore; +using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations.Abstractions; + +namespace ShardingCore.Sharding.ReadWriteConfigurations +{ + /* + * @Author: xjm + * @Description: + * @Date: 2021/9/6 20:58:57 + * @Ver: 1.0 + * @Email: 326308290@qq.com + */ + public class ShardingReadWriteScope:IDisposable + where TShardingDbContext : DbContext, IShardingDbContext + { + public IShardingReadWriteAccessor ShardingReadWriteAccessor { get; } + + + /// + /// 构造函数 + /// + /// + public ShardingReadWriteScope(IEnumerable shardingReadWriteAccessors) + { + ShardingReadWriteAccessor = shardingReadWriteAccessors.FirstOrDefault(o=>o.ShardingDbContextType==typeof(TShardingDbContext))??throw new ArgumentNullException(nameof(shardingReadWriteAccessors)); + } + + /// + /// 回收 + /// + public void Dispose() + { + ShardingReadWriteAccessor.ShardingReadWriteContext = null; + } + } +} diff --git a/src/ShardingCore/ShardingConfigOption.cs b/src/ShardingCore/ShardingConfigOption.cs index ea978e52..bd1a03b4 100644 --- a/src/ShardingCore/ShardingConfigOption.cs +++ b/src/ShardingCore/ShardingConfigOption.cs @@ -9,6 +9,7 @@ using ShardingCore.Core.VirtualRoutes.TableRoutes; using ShardingCore.EFCores; using ShardingCore.Sharding; using ShardingCore.Sharding.Abstractions; +using ShardingCore.Sharding.ReadWriteConfigurations; namespace ShardingCore { @@ -25,13 +26,13 @@ namespace ShardingCore { private readonly Dictionary _virtualRoutes = new Dictionary(); - public Action SameConnectionConfigure { get; set; } - public Action DefaultQueryConfigure { get; set; } + public Action SameConnectionConfigure { get;private set; } + public Action DefaultQueryConfigure { get; private set; } /// /// 配置数据库分表查询和保存时的DbContext创建方式 /// /// DbConnection下如何配置因为不同的DbContext支持事务需要使用同一个DbConnection - /// 默认查询DbContext创建的配置 + /// 默认查询DbContext创建的配置 public void UseShardingOptionsBuilder(Action sameConnectionConfigure, Action defaultQueryConfigure = null) { @@ -39,6 +40,30 @@ namespace ShardingCore DefaultQueryConfigure = defaultQueryConfigure ?? throw new ArgumentNullException(nameof(defaultQueryConfigure)); } + public bool UseReadWrite => ReadConnStringConfigure != null; + public Func> ReadConnStringConfigure { get; private set; } + public ReadStrategyEnum ReadStrategyEnum { get; private set; } + public bool ReadWriteDefaultEnable { get; private set; } + public int ReadWriteDefaultPriority { get; private set; } + public ReadConnStringGetStrategyEnum ReadConnStringGetStrategy { get; private set; } + + /// + /// 使用读写分离配置 + /// + /// + /// + /// 考虑到很多时候读写分离的延迟需要马上用到写入的数据所以默认关闭需要的话自己开启或者通过IShardingReadWriteManager,false表示默认不走读写分离除非你自己开启,true表示默认走读写分离除非你禁用, + /// IShardingReadWriteManager.CreateScope()会判断dbcontext的priority然后判断是否启用readwrite + /// 读写分离可能会造成每次查询不一样甚至分表后的分页会有错位问题,因为他不是一个原子操作,所以如果整个请求为一次读写切换大多数更加合适 + public void UseReadWriteConfiguration(Func> readConnStringConfigure, ReadStrategyEnum readStrategyEnum,bool defaultEnable=false,int defaultPriority=10,ReadConnStringGetStrategyEnum readConnStringGetStrategy= ReadConnStringGetStrategyEnum.LatestFirstTime) + { + ReadConnStringConfigure = readConnStringConfigure ?? throw new ArgumentNullException(nameof(readConnStringConfigure)); + ReadStrategyEnum = readStrategyEnum; + ReadWriteDefaultEnable = defaultEnable; + ReadWriteDefaultPriority = defaultPriority; + ReadConnStringGetStrategy = readConnStringGetStrategy; + } + public Type ShardingDbContextType => typeof(TShardingDbContext); public Type ActualDbContextType => typeof(TActualDbContext);