读写分离


  Fireasy 支持读写分离,详见 数据库实例配置 -- 集群配置

  配置了集群后,你的代码里面不需要做任何改动,Database 类会根据你的操作适当地切换到主库或从库。

方法 主从选择
ExecuteNonQuery 主库
ExecuteScalar 带回自增量时使用主库,否则使用从库
Update 主库
FillDataSet 从库
ExecuteDataTable 从库
ExecuteEnumerable 从库
ExecuteReader 从库

  配置文件里从库组的 weight 为权重,它表示两个以上的从库的命中机率。其调度算法可以通过实现 IDistributedConnectionManager 并通过 IOC 配置来进行切换。


同步延迟的问题

  使用读写分离后,面临的一个问题是主从库间同步延迟,Fireasy 提供的解决方案是:主从库增加一张 ClusterSync(TableName varchar, RowVersion: datetime) 表,并为每个数据表增加一个触发器,当数据表有新增、修改、删除时,均更新 ClusterSync 的 RowVersion 字段为当前时间。在查询数据时,先读取主从库两边的 ClusterSync 表的 RowVersion,判断是否一致,如果不一致,则强制使用主库查询。

  ClusterSync 表的结构如下:

名称 类型 主键
TableName varchar(50) Yes
RowVersion datetime No

  对应此种方案,Fireasy 提供了 IDistributedSynchronizer 接口,称之为分布式同步器。它有以下两个方法:

  • CatchExecuting 方法

  该方法在 Databaes 类的 ExecuteNonQuery、Update、ExecuteScalar(带回自增量) 方法执行时均会调用,它的目的是为数据表添加 ClusterSync 记录以及创建触发器。

  • AdjustMode 方法

  该则是在 Database 类的 FillDataSet、ExecuteDataTable、ExecuteEnumerable、ExecuteReader、ExecuteScalar(未带回自增量) 查询方法执行时调用,目的是比对主从库 ClusterSync 表的数据是否一致,以便调整 DistributedMode。Fireasy 未提供最终的实现类,你可以继承 DistributedSynchronizerBase 类重写创建触发器的 GetTriggerCommands 方法创建触发器来实现。另外重写 ClusterSyncTableName 属性可以改变 ClusterSync 表的名称。

  以下分别是 SqlServer 、MySql 和 Oracle 数据库的分布式同步器的实现:

// SqlServer
public class MsSqlDistributedSynchronizer : DistributedSynchronizerBase
{
    protected override IEnumerable<IQueryCommand> GetTriggerCommands(string clusterSyncTableName, string tableName)
    {
        yield return (SpecialCommand)string.Format(@"CREATE TRIGGER TIG_{0}
ON {0}
FOR INSERT, UPDATE, DELETE
AS
BEGIN
  UPDATE {1} set RowVersion = GETDATE() WHERE TableName = '{0}'
END", tableName, clusterSyncTableName);
    }
}

// MySql
public class MySqlDistributedSynchronizer : DistributedSynchronizerBase
{
    protected override IEnumerable<IQueryCommand> GetTriggerCommands(string clusterSyncTableName, string tableName)
    {
        yield return (SpecialCommand)string.Format(@"CREATE TRIGGER TRI_{0}_Insert
AFTER INSERT ON {0} FOR EACH ROW
UPDATE {1} SET RowVersion = now() WHERE TableName = '{0}'", tableName, clusterSyncTableName);
        yield return (SpecialCommand)string.Format(@"CREATE TRIGGER TRI_{0}_Update
AFTER UPDATE ON {0} FOR EACH ROW
UPDATE {1} SET RowVersion = now() WHERE TableName = '{0}'", tableName, clusterSyncTableName);
    }
}

// Oracle
public class OracleDistributedSynchronizer : DistributedSynchronizerBase
{
    protected override IEnumerable<IQueryCommand> GetTriggerCommands(string clusterSyncTableName, string tableName)
    {
        yield return (SpecialCommand)string.Format(@"CREATE OR REPLACE TRIGGER TRI_{0}_Insert
AFTER INSERT ON {0} FOR EACH ROW
BEGIN
  UPDATE {1} SET RowVersion = SYSDATE WHERE TableName = '{0}';
END TRI_{0}_Insert;", tableName, clusterSyncTableName);
        yield return (SpecialCommand)string.Format(@"CREATE OR REPLACE TRIGGER TRI_{0}_Update
AFTER UPDATE ON {0} FOR EACH ROW
BEGIN
  UPDATE {1} SET RowVersion = SYSDATE WHERE TableName = '{0}';
END TRI_{0}_Update;", tableName, clusterSyncTableName);
    }
}

  将你的实现类添加到 IOC 里即可使用分布式同步器。


💡 小提示

  开启事务后,所有的操作将统一使用主库。