读写分离
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
里即可使用分布式同步器。
💡 小提示
开启事务后,所有的操作将统一使用主库。