zoukankan      html  css  js  c++  java
  • 一次寻找Bug的“痛苦”旅程

    最近在做一个数据量很大的程序,这个程序的功能就是采集互联网上的链接,供用户查询,专业俗语叫“反链查询”或“外链查询”。

    比如http://www.cnblogs.com页面内有友情链接这么多

    我要做的就是把这些链接保存到数据库里,其对应的域名就是http://www.cnblogs.com
    当用户查询的时候,输入chinaz.com,就会列出www.cnblogs.com
    Demo地址:http://outlink.chinaz.com

    中国互联网顶级域名的数量可能是200多万,加上常用二级、三级域名,数量可能在千万,如果平均每个域名上有10个链接的话,差不多会有上亿的数据,并且还要定期更新。数据库设计为两个数据库,OutUrls和OutLinks,OutUrls用来保存域名,及其上面对应的链接,链接的保存采用LinkId+表后缀,表后缀是按照域名的第一个字母。考虑到每个表的数据量不能太大,采用了水平分表,根据域名的第一个字母,相同字母的归到同一个数据表。

    但当数据库文件达到10G,数据的select,insert,update就比较慢,性能监视器中显示Avg.Disk Queue Length的平均值达到20以上,程序日志记录里很多
    Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
    select一条记录都要1分钟。

    再说用户查询的性能,查询是通过查询视图,来获取数据,考虑到查询的性能,表的设计,字段出现了冗余,结果是每个域名第一次查询的时候很慢,像查询qq.com差不多要4s,第二次就比较快1s。这时数据量并不大,我想如果再大点的话,会更慢。
    以前就听说过lucene.net,全文的搜索引擎,其实一开始并不想把程序做得很复杂,能简单点,就简单的。现在这种情况下,还是试一下lucene.net吧。所以,就开始使用lucene.net了,效果果然很棒,几乎每个查询都能在1s以内。
    由于有些数据要先在数据库里更新,然后再更新到Lucene索引,所以,现在的做法是数据库保留,用户搜索是查询LuceneIndex里的数据,只要每天定时更新LuceneIndex就行了。

    再回到Avg.Disk Queue Length持续很大这个问题。
    由于服务器硬盘使用的是RAID,其实只有两个硬盘,同事建议说,可以把OutLinks数据库放到另外一个盘,我就按照他的建议做了,感觉并没有快多少。程序跑了一段时间,文件日志里记录,很多错误,形如:不能在具有唯一索引 'IX_Link1Q_Domain' 的对象 'dbo.Link1Q' 中插入重复键的行。

    Link1Q表里有一个unique 索引,在insert之前,我已经判断是否存在,但是还是会报这个错误。
    然后 DBCC CHECKDB (OutLinks) 爆出了一大堆的错误:

     1 消息 8978,级别 16,状态 1,第 1 行
     2 表错误: 对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID 72057594138525696 (类型为In-row data)。页 (1:54760) 缺少上一页 (1:563545) 对它的引用。可能是因为链链接有问题。
     3 消息 8935,级别 16,状态 1,第 1 行
     4 表错误: 对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID 72057594138525696 (类型为In-row data)。页 (1:60960) 上的上一页链接 (1:433433) 与父代 (1:50171) 槽 29 所预期的此页的上一页(1:655512) 不匹配。
     5 消息 8936,级别 16,状态 1,第 1 行
     6 表错误: 对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID 72057594138525696 (类型为In-row data)。B 树链链接不匹配。(1:655512)->next = (1:60960),但 (1:60960)->Prev = (1:433433)。
     7 消息 2533,级别 16,状态 1,第 1 行
     8 表错误: 看不到分配给对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID72057594138525696 (类型为 In-row data)的页 (1:563544)。该页可能无效,或者页头中可能包含错误的分配单元 ID。
     9 消息 2533,级别 16,状态 1,第 1 行
    10 表错误: 看不到分配给对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID72057594138525696 (类型为 In-row data)的页 (1:563545)。该页可能无效,或者页头中可能包含错误的分配单元 ID。
    11 消息 8976,级别 16,状态 1,第 1 行
    12 表错误: 对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID 72057594138525696 (类型为In-row data)。在扫描过程中未发现页 (1:563545),但该页的父级 (1:489984) 和上一页 (1:113381) 都引用了它。请检查以前的错误消息。
    13 消息 8978,级别 16,状态 1,第 1 行
    14 表错误: 对象 ID 1749581271,索引 ID 1,分区 ID 72057594148814848,分配单元 ID 72057594138525696 (类型为In-row data)。页 (1:564660) 缺少上一页 (1:563544) 对它的引用。可能是因为链链接有问题。

    CHECKDB 在表 'Link1P' (CHECKDB 在表 'Link1P' (对象 ID 1749581271)中发现 0 个分配错误和 8 个一致性错误。

    为了这个问题,确实是寝食难安啊,本来做这个东西已经花费了很多时间,内心已有些焦急,上面的领导时不时的又来问你进度,面对这个以前从没碰到过的问题,就像一个人第一次孤零零地行走于沙漠,无助与凄凉。

    不断的修复数据库:

    use OutLinks
    declare @dbname varchar(255)
    set @dbname='OutLinks'
    exec sp_dboption @dbname,'single user','true'
    dbcc checkdb(dbname,REPAIR_ALLOW_DATA_LOSS)
    dbcc checkdb(dbname,REPAIR_REBUILD)
    exec sp_dboption @dbname,'single user','false'

    只要程序运行了一会,还是会出现错误。
    也怀疑是硬盘的问题,用硬盘检测工具,快速检测,没有发现问题,如果不那么急躁的话,舍得那一点时间的话,可能就找到问题了。

    以前的经验告诉我,遇到事情总是先找自身的原因,并且经常也是自己的原因,如果你怀疑其他外界条件的话,好像你不能够搞定它,而把它归为外界因素。所以,有时候,走自己的路,让别人去说吧,不失为一种正确的选择,如果你坚信自己的怀疑是正确的话,就去实践吧。 

    由于程序同时也在优化中,以为是程序的问题,程序是分为 服务器端和客服端,都是Console Application,采用WCF进行通信,工作过程是这样的。
     一开始考虑到并发问题,以为是多个线程同时更新一张表造成的,为了解决这个问题,服务器端改用采用队列的方式,来更新数据。服务器端先从队列里取出一批待处理的Url,然后把相同表里的数据放到同一个集合,然后多线程的处理这批集合,一个线程负责处理一个集合,这样就可以控制一张表,同时只会有一个线程在操作。这样更改之后,程序运行一段时间之后,DBCC 命令还是会出现一堆的错误,希望又一次破灭,所剩的只是绝望。

    此时,硬盘有问题的想法,再次闪过我的脑海,我决定把数据库放回到原来的位置。程序跑了2天之后,dbcc checkdb没有发现错误,欣喜若狂啊,原来不是程序的问题。

    虽然这次的错误,花费了很多时间,寻找其中的bug,痛苦,纠结,失望,绝望,无奈。最终还是解决了,也很兴奋,更加自信啦。在解决问题的过程中,程序也不断的得到了优化与改进,也尝试了很多新的方法。因此,还是要感谢bug。

  • 相关阅读:
    覆盖一个DIV,让radio、checkbox和select只读
    DNN 配置 数据库篇
    Javascript实用语句收录
    绑定Hashtable到DataList
    WebDeploymentSetup 合并程序集时出错(ILMerge.Merge: ERROR)
    只能向页面中添加 ScriptManager 的一个实例
    让DNN添加的别名起作用
    360极速模式(Chrome内核)下由ashx输出的JavaScript代码不起作用
    C#中常用到的时间函数(天数差、星期几等)
    C#实用语句
  • 原文地址:https://www.cnblogs.com/lhking/p/2668547.html
Copyright © 2011-2022 走看看