.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (下)

Posted 言00FFCC

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (下)相关的知识,希望对你有一定的参考价值。

前言:

延续上一篇博客写的 数据库主从分离配置,本篇简单介绍一下代码层面的负载均衡策略。

在上篇文章中,已经实现了两个数据库间的实时同步,那么对于代码层面,就需要根据T-SQL语句的操作是读还是写,来链接不同的数据库。

1、创建一个枚举 LibraDbBehaviorEnum

/// <summary>
/// 数据库操作的行为
/// </summary>
public enum LibraDbBehaviorEnum

    Read,
    Write

2、创建模型 LibraConnectionStringModel

用来存储每个链接对象配置

/// <summary>
/// 每个数据库链接模型
/// </summary>
public class LibraConnectionStringModel

    /// <summary>
    /// 当前连接字符串
    /// </summary>
    public string ConnectionString  get; set; 

    /// <summary>
    /// 权重数值 Weighing模式可用
    /// </summary>
    public int WeighingNumber  get; set; 

    /// <summary>
    /// 请求次数
    /// </summary>
    internal int AskCount  get; set;  = 0;

    /// <summary>
    /// 最近10次耗时 供压力策略使用的
    /// </summary>
    internal double[] TimeConsume  get; set;  = new double[10];

3、创建枚举 LibraStrategyReaderEnum

用来标识负载均衡的读策略,提供四种策略供参考。

public enum LibraStrategyReaderEnum

    /*
        读取负载均衡实现方式:
        1、轮询策略
        2、随机策略
        3、权重策略
        4、压力策略
            a) 可根据近百次响应时间的平均来分配
            b) 根据服务器硬件实时监控来分配
            c) 根据当前每台服务器的数据库链接数来分配
        5 ...
    */
    /// <summary>
    /// 轮询平均策略
    /// </summary>
    [Description("轮询策略")]
    RoundRobin,
    /// <summary>
    /// 随机平均策略
    /// </summary>
    [Description("随机策略")]
    Random,
    /// <summary>
    /// 服务器权重策略
    /// </summary>
    [Description("权重策略")]
    Weighing,
    /// <summary>
    /// 数据库压力策略
    /// 使用响应平均分配策略
    /// </summary>
    [Description("压力策略")]
    Pressure

4、创建数据库链接的管理类LibraConnectionStringPool

该类供操作读写数据库时,返回数据库的链接

internal class LibraConnectionStringPool

    internal static LibraConnectionStringModel WriteConnection;
    internal static List<LibraConnectionStringModel> ReadConnections;
    internal static LibraStrategyReaderEnum Strategy;

    /// <summary>
    /// 初始化连接字符串
    /// </summary>
    /// <param name="writeConnection">写链接的实体</param>
    /// <param name="strategy">读取策略</param>
    /// <param name="readConnections">读链接的实体集</param>
    internal static void PoolInitialization(LibraConnectionStringModel writeConnection, LibraStrategyReaderEnum strategy, LibraConnectionStringModel[] readConnections)
    
        WriteConnection = writeConnection ?? throw new ArgumentNullException(nameof(writeConnection));
        ReadConnections = new List<LibraConnectionStringModel>();
        // 如果有配置读链接
        if (readConnections != null && readConnections.Length > 0)
        
            // 设置负载均衡策略
            Strategy = strategy;
            // 如果是权重策略,需要根据权重创建对应权重值的链接。
            if (Strategy == LibraStrategyReaderEnum.Weighing)
            
                foreach (var item in readConnections)
                
                    for (int i = 0; i < item.WeighingNumber; i++)
                    
                        ReadConnections.Add(item);
                    
                
            
            else
            
                ReadConnections = readConnections.ToList();
            
        
    

    /// <summary>
    /// 根据操作数据库的目的,返回链接字符串
    /// </summary>
    /// <param name="behavior"></param>
    /// <param name="realtime">是否实时查询</param>
    /// <returns></returns>
    internal static LibraConnectionStringModel GetConnection(LibraDbBehaviorEnum behavior, bool realtime = false)
    
        return behavior switch
        
            LibraDbBehaviorEnum.Read => Dispatcher(realtime),
            LibraDbBehaviorEnum.Write => WriteConnection,
            _ => throw new Exception("错误的数据操作目的."),
        ;
    

    private static int _nIndex = 0;

    internal static LibraConnectionStringModel Dispatcher(bool realtime)
    
        // 未配置读写分离,则自动返回主库.
        // 或者需要实时查询,则返回主库
        if (ReadConnections == null || ReadConnections.Count == 0 || realtime) return WriteConnection;
        switch (Strategy)
        
            case LibraStrategyReaderEnum.RoundRobin:
                
                    var readModel = ReadConnections[_nIndex++ % ReadConnections.Count];
                    if (_nIndex == ReadConnections.Count) _nIndex = 0;
                    return readModel;
                
            case LibraStrategyReaderEnum.Random:
            case LibraStrategyReaderEnum.Weighing:
                
                    var readModel = ReadConnections[new Random(_nIndex++).Next(0, ReadConnections.Count)];
                    if (_nIndex == ReadConnections.Count) _nIndex = 0;
                    return readModel;
                
            case LibraStrategyReaderEnum.Pressure:
                
                    // 压力策略下,自动获取最小耗时的连接
                    var sumMaxTimeConsume = ReadConnections.Select(r => r.TimeConsume.Sum()).Min();
                    return ReadConnections.Where(r => r.TimeConsume.Sum() == sumMaxTimeConsume).First();
                
            // 未配置策略则自动返回主库
            default: return WriteConnection;
        
    

即使已经配置读写分离,但是也会出现需要查询实时状态下的数据,这时候提供了realtime用来返回写链接。

在上方代码中,权重策略与随机策略的逻辑是相同的。不同的是,在初始化配置中,权重策略下,会根据权重值新增了对应多个相同链接。目的在于随机Random下出现的比值加大。

压力策略,需要根据每次使用链接操作查询的前后进行耗时记录。这里提供简单的实现:

/// <summary>
/// 执行SQL语句的操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="func"></param>
/// <param name="parameter"></param>
/// <returns></returns>
public static T Execute<T>(string sql, LibraDbBehaviorEnum behavior, Func<SqlCommand, T> func, bool realtime, params SqlParameter[] parameter)

	// 去获取对应的链接字符串对象
    var connectionModel = LibraConnectionStringPool.GetConnection(behavior, realtime);
    using SqlConnection connection = new SqlConnection(connectionModel.ConnectionString);
    SqlCommand command = new SqlCommand(sql, connection);
    if (parameter != null && parameter.Length > 0)
        command.Parameters.AddRange(parameter);
    connection.Open();
    // 执行前开启计时器
    _watch.Restart();
    var result = func.Invoke(command);
    // 执行完结束计时器
    _watch.Stop();
    // 将计时器耗时结果记录到对应链接字符串对象中
    connectionModel.TimeConsume[connectionModel.AskCount++ % connectionModel.TimeConsume.Length] = _watch.Elapsed.TotalMilliseconds;
    connection.Close();
    return result;

至此,数据库的读写分离负载均衡已完成。测试结果就不放上来了。

另外,根据所学知识以及网上教程,写了一个小小的ORM实现框架。 github地址:Libra.orm ,欢迎各位大佬多多指教。

最后,我尽可能详细的去解释介绍如何实现,如果在本文中有错漏之处,请各位大佬们多多指教。如果我的文章能帮到你,也请各位不吝点个赞点个收藏,如果对文中代码有疑问,也请下方评论。谢谢各位看官。

以上是关于.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (下)的主要内容,如果未能解决你的问题,请参考以下文章

.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (下)

.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (下)

.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (上)

.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (上)

.Net 下的数据库主从分离以及简单的几种负载均衡策略代码实现 (上)

Nginx七层负载均衡的几种调度算法