3、高性能-数据库读写分离(NET篇)
数据库读写分离对于编码来说最首要的是什么?答案就是:如何获取数据库的读库和写库的连接字符串。
程序有了它,就有了连接数据库的目标了。至于数据库是如何实现读写分离还能保持数据同步一致的,那是DBA的知识了,我这里只介绍程序员应该咋做。DBA咋做的我需要在开一个模块写了。暂时这里不会写,想学这个的请百度自己弄一下吧。
先介绍读库连接字符串的获取代码:
1、ILoadBalancing.cs
public interface ILoadBalancing { string NextConnectionString(IReadOnlyList<SlaveConfiguration> slaves); }
2、WeightedPolling.cs(轮训方式获取读库字符串)
//[System.Diagnostics.DebuggerStepThrough] public class WeightedPolling : ILoadBalancing { private static readonly object LockObject = new object(); public string NextConnectionString(IReadOnlyList<SlaveConfiguration> slaves) { if (slaves == null || !slaves.Any()) { throw new ArgumentNullException(nameof(slaves)); } if (slaves.Count == 1) { return slaves.First().ConnectionString; } lock (LockObject) { var index = -1; var total = 0; for (var i = 0; i < slaves.Count; i++) { slaves[i].Attach += slaves[i].Weight; total += slaves[i].Weight; if (index == -1 || slaves[index].Attach < slaves[i].Attach) { index = i; } } slaves[index].Attach -= total; return slaves[index].ConnectionString; } } }
3、SlaveConfiguration.cs
public class SlaveConfiguration { public string ConnectionString { get; set; } public int Weight { get; set; } public int Attach { get; set; } }
4、配置文件里写库和读库的字符串设置格式,根据任意类型的数据库名称可以设置多个不同名称的数据库和不同类型的数据库。
"ConnectionStrings": { "DefaultConnection": "", //增加一个默认的数据库连接字符串,方便程序里增加功能的时候可以灵活处理。 "任意类型的数据库名称": { "Master": "", "Slaves": [ { "ConnectionString": "", "Weight": 1 }, { "ConnectionString": "", "Weight": 2 } ] }, "另一个任意类型的数据库名称": { "Master": "", "Slaves": [ { "ConnectionString": "", "Weight": 1 }, { "ConnectionString": "", "Weight": 2 } ] } }
5、ConnectionConfiguration.cs(读取配置文件里,定义的写库和读库的属性字段名称和类型)
public class ConnectionConfiguration { public string Master { get; set; } public List<SlaveConfiguration> Slaves { get; set; } }
6、ConnectionConfigureManager.cs(放出最后一个大招,配上Ioc就可以实现了)
public class ConnectionConfigureManager { private IConfiguration Configuration { get; } private ILoadBalancing LoadBalancing { get; } private ILogger<ConnectionConfigureManager> Logger { get; } private readonly ConcurrentDictionary<string, ConnectionConfiguration> _connections = new ConcurrentDictionary<string, ConnectionConfiguration>(); public ConnectionConfigureManager(IConfiguration configuration, ILoadBalancing loadBalancing, IServiceProvider service) { this.Configuration = configuration; this.LoadBalancing = loadBalancing; this.Logger = service.GetService<ILogger<ConnectionConfigureManager>>(); } public string GetConnectionString(string connectionName, bool readOnly = false) { var connection = this._connections.GetOrAdd(connectionName, name => { this.Configure(name); return this.Bind(name); }); return readOnly ? this.LoadBalancing.NextConnectionString(connection.Slaves) : connection.Master; } private ConnectionConfiguration Bind(string connectionName) { var section = this.Configuration.GetSection($"ConnectionStrings:{connectionName}"); if (!section.Exists()) { this.Logger?.LogError($"配置节点 'ConnectionStrings:{connectionName}' 没有找到."); throw new Exception($"配置节点 'ConnectionStrings:{connectionName}' 没有找到."); } var configure = section.Get<ConnectionConfiguration>(); if (configure == null || string.IsNullOrWhiteSpace(configure.Master)) { this.Logger?.LogError($"连接名称 '{connectionName}' master 不能为空字符串."); throw new Exception($"连接名称 '{connectionName}' master 不能为空字符串."); } if (configure.Slaves == null || !configure.Slaves.Any()) { this.Logger?.LogError($"连接名称 '{connectionName}' slaves 不能为空,至少需要一个节点."); throw new Exception($"连接名称 '{connectionName}' slaves 不能为空,至少需要一个节点."); } return configure; } private void Configure(string connectionName) { var section = this.Configuration.GetSection($"ConnectionStrings:{connectionName}"); ChangeToken.OnChange(() => section.GetReloadToken(), name => { this._connections[name] = this.Bind(name); }, connectionName); } }
以上只是获取数据库连接字符串的方式,如果你可以了,就不用往后看了。
先休息一下
未完待续
3、高性能-数据库读写分离(JAVA篇)
未完待续