zoukankan      html  css  js  c++  java
  • memcached罢工引发的血案博客园评论超时问题处理过程

      起因

      话说2009年12月4日的下午,博客园团队的一位在数据库服务器中修改了一个设置:


    sql_server_govermor

      将"Use query governor to prevent long-running queries"设置为500,500代表的是查询成本,不是查询执行时间,如果SQL Server认为查询成本超过500,就会返回超时错误。

      之前我们经常使用这个设置,这样可以避免一些执行时间过长的查询影响整体数据库的性能。但这不是解决问题的办法,只是让局部问题不去影响整体,真正解决问题还是要优化执行时间长的查询。

      而就在修改过这个设置之后某个时候,担任缓冲重任的memcached服务突然偷偷地罢工了,血案就这样开始了...

      发现问题

      有些用户在博客中发评论时出现了超时错误,有人在闪存上进行反映,我们在闪存看到并进行测试,也遇到了超时问题,当时我们以为是同时连接过多造成服务器压力大造成的,从日志中发现有过于频繁访问的IP,屏蔽了该IP之后,可以正常发表评论了,我们以为问题已经解决了,从最新评论看,大家也能发表评论。

      实际上问题依然存在,会在特定的条件下发生,只不过我们的测试时没有遇到这样的条件。如果没有设置"Use query governor to prevent long-running queries",这个问题更难发现,因为这时发评论只会速度变慢,不会出现超时,大家不会去反馈这个速度慢的问题。后来,超时出现越来越频繁,有用户在闪存上进行反馈,我们才知道了这个问题。

      采取措施

      我们怎么也没想到memcached服务已经罢工了,之前memcached服务还从来没罢工过,于是把解决问题的焦点放在评论功能相关的数据库优化。在SQL Server 2005管理工具中执行添加评论的存储过程,执行速度也很慢,查看执行计划,发现成本在聚集索引的插入上,于是我们以为是评论表的聚集索引设置有问题(看来不能仅从执行计划去分析问题)。评论表的聚集索引本来是建立在自增ID上的(博客文章表的聚集索引建立在发表时间上),因为评论表的查询主要就是根据ParentID进行查询(查询结果根据ID进行排序),之外最多的操作就是插入记录。既然成本在聚集索引的插入上,我们就取消了聚集索引,这样可以避免在插入记录时的聚集索引插入。这样操作之后,发评论的速度提升了,但速度还是不理想,还是会出现超时...

      柳暗花明

      后来,无意间发现memcached服务停掉了,立即想到这才是罪魁祸首,不是聚集索引的问题,为什么会这么想呢,看一下添加评论的存储过程就知道了:

    BEGIN TRANSACTION
    UPDATE blog_Content
    SET FeedBackCount = FeedBackCount+1,LastCommentTime = GETDATE()
    WHERE [ID] = @ParentID
    INSERT INTO blog_Comment([Text],Author)VALUES(@Text,@Author)
    Select @ID = @@Identity
    COMMIT TRANSACTION

      之前发表评论的性能就不怎么理想,瓶颈就在对blog_Content表FeedBackCount字段的更新上,因为blog_Content是查询最频繁的表,我们几乎对所有查询都使用了WITH(NOLOCK),但是发表评论的性能还是存在问题。memcached服务罢工,直接的结果就是对blog_Content的查询大量增加,在发表评论时的Update操作成本就很高,从而造成超时。

      启动memcached服务,问题立即解决。

      解决问题

      但是问题并没有真正解决,真正的问题是在添加评论时对blog_Content表FeedBackCount字段的更新。

      针对这个问题,我采用一种解决方法:在插入评论时不进行Update操作,而是将评论所属的文章ID插入另外一张表中,表结构如下:

    CREATE TABLE [dbo].[blog_Comment_CountLog](
    [EntryID] [int] NOT NULL,
    [FeedbackCount] [int] NOT NULL
    )

      插入评论时,进行下面的操作:  

    INSERT INTO blog_Comment_CountLog(EntryID,FeedbackCount)VALUES(@ParentID,1)

      显然,这个插入操作速度很快。

      删除评论时,进行下面的操作:

    INSERT INTO blog_Comment_CountLog(EntryID,FeedbackCount)VALUES(@ParentID,-1)

      那么怎么更新文章的评论数呢?

      然后通过SQL Server的任务计划定时执行存储过程进行更新:

    SET XACT_ABORT ON
    BEGIN TRANSACTION
    UPDATE [blog_Content]
    SET [FeedBackCount] = [FeedBackCount]+CommentCount
    FROM [CNBlogs].[dbo].[blog_Content] A
    INNER JOIN
    (
    SELECT EntryID,SUM(FeedbackCount) AS CommentCount FROM blog_Comment_CountLog GROUP BY EntryID ) AS B
    ON A.ID=B.EntryID
    DELETE blog_Comment_CountLog
    COMMIT TRANSACTION

      这样还会带来批量更新的好处,在这个任务计划的间隔时间内添加评论,如果属于同一篇文章,只要执行一次更新操作。

      采用这个方法后,发表评论的性能有了明显提升,现在大家发评论可以看到评论提交的执行时间。

      小结

      在处理问题时,不要着急,要全面分析,把可能引起问题的因素尽量多地考虑到,在动手解决问题之前,多花些时间考虑从何处下手。

      在开发中,异步是一个提高性能的有效方法。

      在我们解决各种问题的过程中,从网上获得了很多启发,如果有人遇到过类似问题并分享了自己的处理经验,会节省我们很多时间。

      大家遇到问题时多数会先Google一下吧,如果大家都只想着Google一下,而不去分享,这个生态链就很难维系。别人的分享帮助了你,你的分享帮助了别人,在这种互相帮助中,大家都会进步。

      在刚解决一个问题的时候,是分享的最佳时期,如果此时不分享,也许就永远不会分享,分享的不仅是解决问题的方法,更是解决问题时的那种兴奋。这种兴奋是程序人生的乐趣所在!

  • 相关阅读:
    WPF 得一些问题汇总
    System.Rtti.TRttiObject.GetAttributes 简例
    ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้
    erlang局域网内节点通信——艰难四步曲 (转)
    delphi 单例模式实现
    NotePad++ delphi/Pascal函数过程列表插件
    用Visual C#实现MVC模式的简要方法
    Visual C#常用函数和方法集汇总
    需要Niagara邀请码的伙伴可以联系
    一个通过百度贴吧找到身份证失主的案例(供参考)
  • 原文地址:https://www.cnblogs.com/cmt/p/1618410.html
Copyright © 2011-2022 走看看