分享一个基于FileSystemWatcher的文件自动备份程序
我们公司asp.net的项目都是使用FTP进行上传下载来发布和更新的。尽管我们在发布修改之前都在本地进行进行测试,但本地的开发环境始终和外网上的实际环境还是有差别的(特别在一些数据上的差别),所以有时不可避免地出现在本地测试时没有Bug,但上传到外网就出现Bug的情况。
很多时候我们在ftp后如果出现bug,第一个反应就是马上还原回更新前的状态,也就是将旧问题替换回去。但这样也就需要在ftp上传前先将旧文件备份。但因为这样做太繁琐,有时候更新量大而且文件分散,备份旧文件就变成一件还是挺烦人的事情。虽然ftp工具都有简单的自动提示功能,例如filezilla就可以设定同名文件的处理方式,但功能薄弱。而我也找过一些文件自动备份软件,例如大名鼎鼎的alway sync ,但感觉它不是专门为这种情况而设计的,用起来没有那么方便。例如:我需要搜索某一段时间里对某个更新进行备份的文件,并将这些文件批量恢复回去。
(注:当然,我觉得:
1假如有这种需求,应该会有这类型成熟的软件,这是我不知道。有经验的朋友可以分享一下。
2: 或者我每次更新都是用ftp来手动上传下载是一种比较落后的做法,感觉是一种较为作坊式的项目管理方式,用收工ftp的整个过程根本不可控不可管理。公司的php项目组在发布是将文件提交至生产服务器上的svn,通过svn的钩子对文件进行更新的。也就是说,业界可能有很多更成熟的发布做法。希望有经验的朋友分享一下。)
基于希望能对FTP上传文件自动备份的问题,我前一段时间使用FileSystemWatcher类编写了一个小程序,自动的监视某个目录,当该目录的文件被修改后对文件作备份。注意这里是"修改后",并不是"修改前"。FileSystemWatcher里的事件都是在原文件被修改后才触发的,并不是我们希望的在操作系统对文件修改前暂停修改操作并触发事件,等我们备份了文件再继续修改操作。我开始也很困惑,甚至去找一些"修改前触发事件"的api,但发现就算c++也好像没有这类方案(使用钩子应该可以,但没试过)。后来觉得微软这样设计也是合理的,因为对文件进行修改是操作系统的工作,而备份是出于操作系统上层的应用程序的工作,为了保持操作系统的工作的连贯性,当然不希望上层应用中断操作系统的操作(这点纯碎是个人猜测)
所以,使用FileSystemWatcher来实现监视和备份,都是备份修改过的最新文件的。这也是可以接受的,因为除了第一次更改前的旧文件不能被备份外,后续每次更改的文件都会有一份对应的备份,第二次及以后的更改都可以找回前一份文件。


1.该工具可以监视某一个目录(可以设定是否监视子目录),当文件被更改后(文件内容被更改,或者重命名),该文件会被自动备份到一个指定的备份目录中。由于是”更改后触发事件“,因为并没有对删除操作进行备份,因为一个文件被删除了,在deleted事件中是找不到原来的文件的,而且通常来说,被删除的文件在删除前早就被备份过了。
2.文件每次被更新,其备份文件都会带有更改时间和更改类型作为标识,以便日后区分是什么时候和什么操作导致的备份,方便记录。
3.可以对备份文件进行按文件名,更改时间,更改类型的条件查找。并对文件进行恢复(批量将备份文件还原回去,恢复更改前的状态)
4.可以包含子目录监视并在备份时自动创建于被监视目录相同的目录结构。比如:被监视的目录是c:\a ,备份目录是c:\b,当c:\a\d\e\f\somefile.txt被修改后,将会在c:\b\d\e\f\somefile.txt
5.可以开机自动启动并回复关机前的监视状态。因为有时候软件所在服务器可能会关机,在重新启动后就可以自动启动软件并回复上次的监视状态,减少人工干预。
6.可以自己编写记录备份和搜索备份的模块(代码里面预定义了接口),原来是将备份文件名以更改时间和更改动作,搜索时使用File在文件系统上搜索文件。但同事给的意见是:最好可以提供记录在数据库的方式,利用数据库强大的检索功能。于是就定义了两个接口,允许自己编写保存和查找的代码。
这个程序核心就是FileSystemWatcher类了。我自定义了一个继承FileSystemWatcher的子类XWXFileSystemWatcher,封装父类,添加了一些方法和一些事件。
程序中维护着一个XWXFileSystemWatcher的列表:List<XWXFileSystemWatcher> watcherList,并与程序运行过程中的监视项的同步。
此外,程序中还定义了一个事件OnWatchSettingChanged。每个导致监视变化的操作都触发该事件,例如开始/暂停监视,改变文件更改类型以及选择是否包含子目录。在该事件的事件处理函数里面同时将监视的详细信息通过序列化的方式保持到文件中,以便下次启动可以加载并反序列化该文件,达到恢复上次监视状态。
程序中定义了一个可序列化的模型类WatchInfo,用来封装一个监视项的信息,包括:监视目录,备份目录,更改类型,运行状态和是否包括子目录。如上面说的,在每次对监视项进行设置,都会触发OnWatchSettingChanged事件去将监视项实时进行保存。
对于备份记录的保存,默认的是以 @yyyy-mm-dd.changeType的后缀来命名备份文件,而查找备份文件也同样的是对备份文件名的“@yyyy-mm-dd.changeType”进行分析以识别备份文件是什么时候对哪种操作进行备份的。但程序中也定义了两个接口:IRecord和ISearchBackupFiles。可以通过继承这两个接口来实现自己的保存备份记录和查找备份记录的模块。程序中使用了工厂模式+配置文件反射的方式来加载这些模块。
其他具体的请见源代码。由于个人精力与水平限制,程序中可能存在一些bug,欢迎斧正。
文本编辑器中文字断行及排版算法研究
袁永福
2013/4/15
文本编辑器是一种非常复杂的图形软件,涉及到的很多开发技巧和软件结构都是传统的数据库程序开发中所从未应用的,因此掌握相关技术的人是非常的少的。在其中文字断行及排版算法是编辑器开发中的核心算法之一。如果没有掌握这个算法,那只能在开源软件的基础上小打小闹了。
本文就讨论一下编辑器中文档断行及排版算法。
文字排版大致分为以下几个步骤:
- 测量各个字符的宽度和高度。[袁永福版权所有]
- 计算文档容器的客户区宽度。比如设置的纸张宽度减去左页边距和右页边距的宽度。这里的文档容器不仅仅指大的正文区域,还包括单元格、文本框之类的文档结构。
- 断行,也就是将各个字符从左到右,从上到下的依次放置在文档容器中。产生一行行文本,实现一种流式排版。
- 行内排版,也就是在文档行中进行字符排版,特别是为了完成文档内容两边对齐功能。
- 分页。
■■测量字符大小
排版的第一步就是计算文档中各个字符的宽度和高度。笔者是使用C#开发的,因此可以调用System.Drawing.Graphics.MeasureString方法来测量字符的宽度和高度。由于文档中字符个数很多,比如几万个,则一个个测量是非常消耗时间的,为此需要采用很多优化手段来加速测量。[袁永福版权所有]
说到测量字符,就涉及到等宽字体和比例字体的概念了。等宽字体就是使用该字体绘制字符,字符的宽度是一样的,比如“宋体”,它就是等宽字体,用它来测量和绘制字母“W”和“i”,其宽度是一样的。比例字体就是使用该字体测量和绘制字符,其宽度是不一样的,比如“Times new roman”,用它来测量字母“W”和“i”,其宽度是不一样的。
对于等宽字体,可以事先测量一个字符的宽度,比如“W”,则以后遇到其他字符就使用这个已经测量好的宽度;而对于比例字体,则需要进行实时的测量。
不过一般来说,对于等宽字体和比例字体,中文符号的宽度还是一致的。因此可以实现测量一个中文字符的宽度,以后遇到中文字符就采用这个事先测好的宽度。
这里带来一个问题,如何判断一个字符是否为中文字符,那就需要参照GB3212,GBK等计算机字符集的标准来判断了。一般来说Unicode编码范围从19968至40869的字符为中文字符,当然为了进一步的优化,可以知道一些全角符号,它们的宽度也等于中文字符。
不过仅仅依照UNICODE编码来判断是否是中文字符是不可靠的。因为一样的UNICODE字符在不同的字体中其意义可能是不一样的。[袁永福版权所有]
比如对于字体“Wingdings”,所有的字符在这个字体中完全变味了,就表示一个个特定形状的符号,判断是否是中文就毫无意义了;另外对于条码字体也有这种情况。
最为保险的做法就是直接解析字体二进制文件(扩展名为ttf或ttc),获得其中的字体轮廓信息,然后根据字符的UNICODE编码值来计算出字符的宽度,这样做是最为准确可靠的。笔者猜测Graphics.MeasureString方法内部也可能采用这种方法。不过编辑器自己解析字体二进制文件进行字符测量,绕过底层诸多的调用层次,其速度可以非常的快,可以在几十毫秒内完成几万个字符的测量。[袁永福版权所有]
不过解析字体二进制文件信息还是要花掉不少时间的,比如对于宋体,其字体文件名simsun.ttc,文件大小15MB,含28762个字符轮廓信息。但分析所得的结果信息量很小,只有1424 字节,为此需要将分析结果保存在一个临时文件中,下次就无需分析这个字体二进制文件了。
■■断行
测量完字符的大小后,编辑器程序开始在内存中构造排版对象模型,不断的将字符填充到最后一个文档行,若文档行的字符宽度和加上准备添加的字符的宽度大于文档容器客户区宽度时,就进行断行,另起一行开始填充字符。
不过也存在提前断行的情况。为了尽量保证连续的英文字母字符和阿拉伯数字之间不能出现断行,这样会导致同一个逻辑上密切相关的单词被拆散放在两行了。因此遇到这种情况需要提前断行。
为此程序在执行断行的时候需要进行判断,如果下一个字符和文档行中最后几个字符都是英文字母字符或阿拉伯数字字符时,需要从右到左遍历最后一个文档行,将相关字符抽取出来,准备放置在下一行中。[袁永福版权所有]
当然这样的操作也不是绝对的,比如遇到连续的超级长的“单词”时,比如100个连续字符“a”,虽然基本上没有实际意义,但这是一种必需考虑的边界条件,很容易导致程序运行错误。因此在提前断行时需要进行这样的判断,若真的出现这种情况,那就取消提前断行。
※前置标点和后置标点
不能出现在行尾的符号称为前置标点,例如“([{·‘“〈《「『【〔〖(.[{£¥”;不能出现在行首的符号称为后置标点,例如“!),.:;?]}¨·ˇˉ―‖’”…∶、。〃々〉》」』】〕〗!"'),.:;?]`|}~¢”。
比如一个文本行内容为“?张三李四王五【”,这就是一种不和规范的文本行,需要避免这种情况。
在进行文字断行时,若这个文档行的最后一个字符是前置标点时,需要进行提前断行;如果断行后第一个要排版的字符为后置标点时,也需要进行提前断行。
在进行断行的时候,对于段落符号要进行一些特殊处理。段落符号本身是有一定的宽度的,但当文档行要执行断行时,参与计算时的宽度就可以当做零了。
在排版的编程实践中,笔者采用堆栈的方式实现断行。首先将所有要排版的字符压入一个堆栈中,然后循环从堆栈中Peek获得一个字符元素,然后试图添加到当前文档行中,若文档行剩余空间足够容纳新字符,则将该新字符添加到文档行中,同时堆栈执行Pop操作。若文档行剩余空间不够,则不执行Pop操作,新建一个文档行,从而开始新的循环。如果出现提前断行,则需要将当前文档行中的若干个字符元素移出来,并压入堆栈中等着下一次循环中使用。
当堆栈内容为空时,就跳出循环,完成文档的断行操作。[袁永福版权所有]
※停止行
用户在编辑的时候会频繁的输入字符,这就使得程序频繁的进行文档排版操作。当文档内容比较多,比如上万个字符时,进行整个文档范围的字符排版及重新绘制用户界面可能要花上几百毫秒的,这样就导致用户输入字符时编辑器反应迟钝。
为此在用户编辑录入的时候,需要进行文档内容的部分区域的文字排版,而其他区域的排版就不要动了。为此在编程中采用了一种技巧来减轻排版的工作量,笔者称之为停止行技巧。
在排版前,首先备份文档容器的文档行信息。在每完成一个断行,形成一个新的文档行时。遍历备份的文档行信息,从最后一行开始和新的文档行内容进行比较,比较内容主要是文档行中的文档元素是否完全一致,当然还有一些其他判断。当新旧两个文档行内容一致时,这个旧的文档行称为停止行。此时文档内容断行提前结束。然后进行新文档行的行内排版,最后新文档行和一部分旧的文档行合并,形成新的文档排版。这样就能比较大的降低运行时排版工作量。[袁永福版权所有]
■■行内排版
文字断行完成后,需要进行行内排版。
文档行中各个字符的宽度之和不大可能正好等于文档容器的客户区宽度。两者会有空白差。
由于中文字符和英文字符宽度不一样,对于不等宽字体,各个英文字符、数字字符等宽度还不一样。使得各个文本行的字符宽度之和是不一样的,使得各个文档行右边缘是参差不齐的。这样比较严重的影响美观。
为此需要将文档行的宽度拉长成文档容器客户区宽度,由此会额外的制造出不少空白,此时需要将这些空白比较均匀的分摊到各个字符上。此处是比较均匀的分摊,但不是完全均匀,是有一定的分布算法的。
同一行中,字符不是相对孤立的,而且从逻辑上分为一组一组的,对于汉字和标点符号,它们是各自为政,自己组成一组。对于连续的英文字母字符和阿拉伯数字,它们逻辑上是同一组的,一起构成一个完整的单词,因此同一组之间的字符之间应该是紧密连接在一起,不得拆开。[袁永福版权所有]
为此要分摊由于文字两边对齐而造成的额外空间时,首先要对文档行的字符进行分组,然后将额外的空白平均分摊到字符组上。
例如对于文字“DCWriter电子病历文本编辑器。”,其分组为“[DCWriter][电][子][病][历][文][本][编][辑][器][。]”,其中一对方括号之间就是一组字符,这样就分成11组。如果额外的空白宽度为20个单位,则需要将空白平均分摊到这些字符组上面,最后一组不分摊,于是前面10组分配得到20÷(11-1)=2个单位的空白宽度。在排版时将这10个2单位的空白宽度插入到字符组之间,这样就能拉长文档行的宽度正好等于文档容器的客户区宽度。
■■分页
分页本质上说就是计算分页线的位置。其过程如下
- 首先计算出标准页的高度,也就是纸张高度减去上下页边距的值,还需要考虑到页眉页脚的修正量。
- 设置当前分页线的位置,也就是上一个分页线的位置加上标准页高。
- 遍历文档行,若分页线的位置在文档行中间,说明该行文字被分割到两页中,此时将分页线的位置向上移动,使得分页线在当前文档行的上边缘和上一个文档行下边缘的中间。
- 如此循环,使得所有的文档页的高度和大于等于文档的内容高度。[袁永福版权所有]
在进行分页时,也需要判断很多边界条件,比如当某个文档行非常高,比如中间放置了一个超高的图片,使得这个文档行的高度大于标准页高,此时就不能随便移动分页线的位置了。
另外当文档中有表格时,则需要深入到表格单元格内部进行修正分页线位置的操作,这是一种递归操作。
在电子病历业务中有着继续打印的功能,在笔者的实现中,续打位置实际上就算是一种特殊的分页线,这样就能避免在续打时文字被分割打印的情况。
文字断行和排版算法是非常复杂的,即使笔者经过长期的重构再重构,优化再优化,也还是花费了一万多行的C#代码来实现这个功能,而且还有不少地方仍然需要优化。
一些人认为C#无法开发高性能的程序,编辑器这样程序应该需要用C++开发。笔者经过实践认为,所谓C#性能不高的说法是不对的,关键还是算法。C#程序只是启动有些慢,运行起来后仍然可以达到很高的性能。[袁永福版权所有]
关于编辑器软件可以看网站www.dcwriter.cn 。
优化杭州某著名电子商务网站高并发千万级大型数据库经验之- 读写分离
2013-04-18 09:23 by 通用信息化建设平台, 13 阅读, 1 评论, 收藏, 编辑好久没写博客了,一方面是日常工作繁忙,另外一方面是想更多的时间陪陪家里人,享受春天的美好时光,同时还在写一本《程序员,你伤不起》的一本书要由人民邮电出版社出版;我的性格可能也跟大多数程序员类似吧,没什么兴趣爱好、不擅长与人交流,平时话也少,也不够幽默,就是一个实实在在的人。
下图命名为:孤独的程序员
由于公司的服务器责任重大,而且慢负荷跑了接近2年了,怕万一有闪失主服务器出现硬件故障订货到货周期会比较长,所以公司购买了一台更强大的服务器来当备用服务器,购买的初衷就是防止出现硬件故障。
春节前新服务器就采购到位了,但是由于手上事情比较多根本忙不过来配置新服务器,春节休息期间看了一些资料,准备把数据库的读写分离实现以下,主要思路就是下图的思想。
很多需要读取的都从另外一个服务器读取,这样主服务器的压力会小很多。
以前习惯了什么事情都自己亲自操刀,想也改变一下思想,把数据库镜像、读写分离的就安排给部门里的其他人员去实施了,我只是抛出了一些主导思想,结果折腾了很久没能那么顺利实现,主要问题有:
1: 读写分离,进行了复制订阅后,同步的周期非常长,在主服务器繁忙时,3分钟都无法进行同步。
2:主服务器无形中额外增加了更严重的同步负担,没能达到预期的效果。
3:服务器维护人员对数据库也不是很精通,主要精通硬件及网络配置维护,他们平时的各种维护管理工作也非常繁杂。
经过第一次读写分离的失败后,思考了1-2周时间,还是打算亲自操刀一下否则没有非常狠的力度。
1:分析主服务器最影响性能效率的SQL语句,几乎把所有常用的语句都整理出来,分析公司的核心业务、核心数据表。
2:把最核心的少数几个表同步到新服务器,这样同步的效率高一些,主服务器的压力也小一些,而不是选择同步所有的表。
3:把推送模式修改为从服务器拉的模式,由于新服务器的性能更好,承担的工作压力更小,所以新服务器主动去拉数据的模式。
4:把主服务器上的所有SQL语句、索引都进行优化,主服务器的性能提升了接近4倍。
5:把所有实时性要求不高的,数量庞大的,操作频繁的SQL语句都从新服务器读取。
6:新服务器的性能非常强大,所以一部分磁盘用来做镜像、另外一部分磁盘做数据同步,这样新服务器的磁盘也充分利用起来了。
7:新服务器装了2个SQL2008,一套用来数据同步,另外一套用来数据镜像,这样丢失数据的概率降低到极点、同时也不担心发生硬件故障数据丢失了。
其实说说复制订阅很简单一样,一晚上折腾了5-6次,耗时4-5个小时才能成功同步,很多东西不是亲自动手是不知道其中的痛苦,嘴巴说说也就几句话,这就是很多时候为什么需要有工作经验的人,而不是理论知识够就可以了。
第一次设置数据库的发布订阅,以前工作上也没这个需要,设置成功了,进行了几次测试都正常,新服务器正常开始运转,又检测了几天复制订阅的运行情况都良好,不管在业务高峰期还流量不大时,运行状态都比较稳定,就把网站上的一些需要大量读取数据的程序进行了调整,从新服务器读取了。
耗费了巨资购买的新服务器,经过2-3个月的接近闲置空闲状态进入了忙碌的工作状态了,心里也舒服了很多,数据库镜像也做好了,数据库同步读写分离也如愿了,主服务器的磁盘I/O压力接近降低了10倍以上,对主机的磁盘寿命、整个网站的稳定高效性运行打了牢固的基础。
在主服务器优化前经常有超过2秒运行的SQL语句非常多,有时还有耗时20-30秒的SQL语句,经过一周的优化后,主服务器上几乎看不到超过2秒的SQL语句了,绝大部分SQL语句都能在0.5秒以内能运行完毕了。
经过这次优化,有2个心得体会,一方面决策很重要、另一方面执行力也很重要;没有强劲的执行力决策就是空洞的无法落地。遇到任何困难不能妥协、坚决想办法克服各种困难,每次突破就是一次升华。
其实公司里有不少人想加薪、获得的待遇等等;也总是抱怨自己的水平无法施展什么的;其实公司里需要做的事情很多,需要去主动承担很多艰巨的任务、去做一些自己并不擅长的事情;例如我也不是专业DBA的职责,以前也从来没优化过这么大并发量的网站,但是需要去突破,克服困难,把一个个难题解决掉。
思想很关键、毛驴车很便宜也不需要加油、绿色环保、驴肉还可以吃、车还可以当柴火烧。当你最想要是什么?最想达到的目的是什么?
浙公网安备 33010602011771号