业务简述:
为了提高站点性能,部署了一台Redis,把资源从SqlServer数据库中同步到Redis,站点由原来的读取数据库,变更为读取Redis,以利用Redis的高并发提升站点性能目的。
环境说明:
1、假设表名为softs, 记录的更新时间字段名为 updateTime;
2、不考虑数据库的DELETE操作,只考虑INSERT和UPDATE操作;
3、流程中所有时间,都以数据库时间为准,以避免不同服务器之间的时间误差,导致数据遗漏。
4、重要的前提条件:数据库执行INSERT和UPDATE操作时,把记录的updateTime字段更新为SqlServer的当前时间GETDATE();
旧的业务流程:
1、数据同步程序第一次启动时:
a、读取数据库当前时间GETDATE(),作为下一次增量更新的起始时间变量:LastUpdateTime;
b、全量填充:读取SqlServer的全部数据,填充到Redis中。
2、全量填充完成后,通过DataReader获取增量数据,填充到Redis中:
3、保存这一批增量数据中,最大的那个updateTime,作为下一次增量数据的最小时间:LastUpdateTime;
4、休眠一分钟,重复执行步骤2
流程代码如下:
DateTime lastUpdateTime = DateTime.MinValue; while(true) { string sql = "SELECT * FROM softs with(nolock) WHERE updateTime >= @LastUpdateTime ORDER BY updateTime"; var sqlPara = new SqlParaMeter("@LastUpdateTime", SqlDataType.DateTime){Value = lastUpdateTime}; using(var reader = SqlHelper.ExecuteReader(sql, connectionString, sqlPara) { while(reader.Read()) { DateTime dt = Convert.ToDateTime(reader["updateTime"]); if(dt > lastUpdateTime) lastUpdateTime = dt; // 获取数据,new数据实体,并Protobuf序列化后,填充到Redis } } Thread.Sleep(60000); }
存在的问题:
DataReader始终读取到数据库的实时数据,即 sql执行前,时间为1;sql执行后,数据读取前,把时间更新为2;数据读取后,读取到的时间是2,而不会是1;
假设这种情况:
1、LastUpdateTime为:2014-7-7 13:54:12,SQL读取到10条记录,这10条记录中,updateTime最大值为:2014-7-7 13:54:14
2、程序循环读取并处理这10条记录,当读取到第3条记录,并处理时;
3、数据库更新了第1条记录和第10条记录,并把这2条记录的updateTime更新为:2014-7-7 13:54:16
4、程序继续循环处理完成,此时得到的下一次待处理的LastUpdateTime为:2014-7-7 13:54:16
问题出现了:第3步更新了2条记录,第10条记录成功处理了,第1条记录的修改,没有映射到Redis,导致脏数据的产生
新的业务流程:
1、等同于旧流程;
2、全量填充完成后,读取数据库当前时间,作为下一次增量数据的最小时间:LastUpdateTime;
3、通过DataReader获取增量数据,填充到Redis中;
4、休眠一分钟,重复执行步骤2
注:重点是把执行SQL前的数据库时间,当成下一次的增量起始时间,缺点是会有一定的数据重复填充,现在Redis压力不大,可以接受,如果希望忽略重复填充,可以在代码里做去重处理。
代码如下:
DateTime lastUpdateTime; while(true) { DateTime nextUpdateTime = Convert.ToDateTime(SqlHelper.ExecuteScalar("SELECT GETDATE()", connectionString)); string sql = "SELECT * FROM softs with(nolock) WHERE updateTime >= @LastUpdateTime ORDER BY updateTime"; var sqlPara = new SqlParaMeter("@LastUpdateTime", SqlDataType.DateTime){Value = lastUpdateTime}; using(var reader = SqlHelper.ExecuteReader(sql, connectionString, sqlPara) { while(reader.Read()) { // 获取数据,new数据实体,并Protobuf序列化后,填充到Redis } } lastUpdateTime = nextUpdateTime; Thread.Sleep(60000); }
读取数据库数据填充到缓存的问题,及修复方案一则