系统架构设计之--异步日志记录篇
一. 背景
目前我们开发的绝大部分系统是都需要记录业务日志的(包括不限于操作日志),比如电子商务够物结帐的时候,相关的购买信息,支付信息你要记录下来吧,因为将来某一天你可能要和客户对帐的.再举个例子,平时大家都会去银行转帐,银行背地里一定把你转帐的相关信息都记录下来了,比如,转出帐户,转出时间,转出金额,转入帐户等等.银行也经常说要多久以后到帐(比如5分钟),如果不到的话你可以凭单去银行对帐的.
二. 现状
目前绝大部分系统设计之初都是把日志这个动作,作为同步记录的,下面以伪码描述一下.
using(transcation a){
book.pay();//购买一本书并付款
operate.add(); //增加一条事务日志.
}
三. 问题分析
1. 如果用的是一个connection,那么事务将保证是一个本地事务,保证最大的性能.但如果按上述做法设计,即N个对象一起保存数据(N>=2)那么将会引起一个分布式事务,性能就会降低10倍以上,响应时间也相应变慢.
2. 并发访问下的数据连接,在这个最简单的事务里打开两个连接,book一个,operate一个.考虑.net数据连接池默认100个情况,只考虑这个简单业务操作的话,强并发最多只能支持50个客户同时访问.
3.网络访问,数据库访问是一个非常耗时的操作,将一个正常事务执行时间延长1倍,客户体验效率不好.
4.每个业务操作都要调用,日志函数,导致日志不能批量写入,性能比较差
5.业务日志有可能和生产库放在同一个数据库,不利于维护和查询优化以及数据量巨大时,数据分区等操作.不利于提交生产数据库性能
四. 解决方案
1. 支持两种数据写入方法,一种同步日志写入,即上面以前做法,一种异步日志,异步日志是对同步日志的一种包装.
2. 异步日志主要是利用一日志文件来保证异步写入,比如日志文件为operate.log. 当系统调用需要写日志时,异步日志方法将日志写入到operate.log文件中,然后返回.
3. 后台有个独立线程每隔X秒,读取全部日志文件,调用同步日志类中批量写方法写入日志数据库.
4. 伪代码如下:
using(transcation a){
book.pay();//购买一本书并付款
operate.aysAdd(); //异步增加一条事务日志.
}
aysAdd(){
FileStream stream = File.Open(path);
stream.WriteLine(); //异步写日志文件
}
ThreadProc(){
while(true){
FileStream stream = File.Open(path);
stream.ReadAllLine(); //同步读取文件
operate.BatchSave(); //批量保存日志信息
Thread.Sleep(N); //睡眠N秒,继续批量保存数据
}
}
五. 方案分析以及保障制度
1. 不是同步保存,那我系统断电,或者IIS重起怎么办? 答:当实例化日志类时候,将首先读取operate.log,并保存入库,保证日志文件不会丢失.
2. 如果写文件过程中写入失败怎么办?答:这里说了,异步日志是对同步模型的一个包装,是一个典型的装饰器模式.如果写文件失败,则转成同步模式去保存数据,不会丢失任何业务数据.
3. 你那里是同步写还是异步写?如果是异步写文件那断电怎么办?如果是同步写日志,那性能和以前对比有什么提高吗?
答: 1. 同步写日志文件,即一定要保证日志文件能写入到文本文件,才能提交整个事务.
2. 只有异步写入数据的时候才会遇到这种问题,突然断电,日志文件丢失.
3. 做了一个性能测试,每秒可以写入1,000,000条业务日志到文件里,极高提升系统性能.批量提交日志文件到数据库,共享一个连接,而且Sql组成批语句,更可以节省数量级的性能损耗