西西河

主题:谈谈大型网站架构的一些关键技术 -- 季侯

共:💬43 🌺225
分页树展主题 · 全看首页 上页
/ 3
下页 末页
    • 家园 很有兴趣,请尽量多写一些。不要和某人学只挖坑不填.
    • 家园 1 先说说cache

      作为一个老军医,总有人问我,“xxx,帮我看看,为啥我这系统性能这么差?” 我的第一个反应就是,瓶颈在哪里,cache怎么做的。

      常有人说,木桶的容量是最短的那块木板决定的;那么如果把一个网站当作一个木桶,网站开发者这个桶匠绝对是个杯具。因为他拿到的木板太参差不齐了,有的长有上百米,有的才几公分。

      这块最短的木板往往就是数据库了,以12306为例,常见的使用场景是:

      用户登录上来,根据出发地和目的地查询车次,然后选择 一个有票的车次买票,下订单,付款。

      不管是登录/查询车次/查询是否有票/下订单/付款都涉及数据库操作,是一个典型的读多写少的场景,数据库将会是瓶颈。那么为什么数据库会是瓶颈哪?

      1 数据库操作相对web层是一个非常耗时的操作,单次操作往往需要几毫秒,并发链接数也只有3000/4000;而nginx之类的静态服务器,每秒处理10万个请求,支持数万个并发链接无压力;所以只要稍微大一点的网站,数据库很容易成为瓶颈;

      2. 数据库链接是一个非常昂贵的资源,一般来说单台mysql服务器能够只能提供3000/4000的并发链接;一旦大量web请求到来,那么很有可能申请不到数据库链接,不得不排队;当队列中的请求累计到一定数量时,新的请求很容易超时,从而失败。

      3. 查询操作太多,基本上每买一张票都会查询很多次,而每次查询都会产生数据库查询操作。虽然可以通过建立合适的索引加快查询,通过读写分离/水平分库等手段降低单个数据库的负荷,但是只要数据库操作数量大到一定程度,那么唯一的办法就是减少数据库操作。

      将一些很少变化,但是频繁查询的数据缓存到memcached/redis等缓存服务器中是一种成熟有效的技术。以根据出发地/目的地查询为例,以往的查询需要从数据库的车次站点表中查询,现在可以把{出发地/目的地}-> [车次列表]的映射关系存到redis中,这样每次查询的时候直接从redis中就可以得到。

      这么做的好处是,从redis中查询比从数据库中查询要快的多,相差不止一个数量级,redis能支持的并发链接数也远远超过mysql;所以能够降低了数据库负荷,也避免了数据库链接资源的申请。

      所以采用合理的cache技术,降低数据库负荷是大型网站架构的一个关键技术点。

      通宝推:楚庄王,史文恭,

      本帖一共被 1 帖 引用 (帖内工具实现)
      • 家园 拜君侯大作
      • 家园 看不太懂,推一推还是可以的
      • 家园 你终于写点东西了,这个你有发言权
      • 家园 内行啊!
      • 家园 【原创】Cache使用过程的一些陷阱

        使用Cache有一些前提,这是做Cache规划时必须记住的。第一个前提是:缓存下来的东西需要频繁读取但不需要频繁变化,第二个前提是:读取缓存的代价应远小于读取源数据的代价。通常第二个前提很容易满足,一般不需要考虑太多。而第一个前提则经常隐含一些陷阱,当我们使用一些Cache技术的时候,这些陷阱通常会带来一些新问题。

        我们把老季的例子推广到余票查询,这个余票数量就不是完全不变了。不过呢,按目前的情况我们可以合理假定余票数量变化期间还是有大量的重复查询动作。比较一下每天14亿的点击和100万的售票数量我们就知道有多少个查询是返回相同的结果了。那么我们是否可以把余票数据也放入缓存中呢?

        我的直觉是......有陷阱。以memcached为例,这只是一个缓存服务器,它并不了解你缓存的是什么东西——实际上大多数的通用缓存技术都是这样。那么自然无法了解余票数量会因为有人订票而发生变化。这就要求你必须在做订票功能的时候,更新缓存了相关车次数据的所有缓存项。如果通过订票资料我们可以反推出影响了哪些查询结果,那很好办。我们只需要在订票功能上增加查询,然后用新的查询结果更新对应的缓存项就好了。第一个陷阱就是我们必须能通过修改操作的参数反推出需要修改哪些缓存结果。好在据说12306网站不能自己生成车票,所以这个应该是陷阱不存在的。

        我们也可以在订票操作中直接删除受影响的缓存项,直到有人再次查询的时候顺便更新这个缓存。但这样很容易就踩进另一个小陷阱。尤其是查询量很大的时候,当我们突然删除一个缓存项,而同时又有大量的用户同时进行查询,那么极有可能在数据库出现一次查询爆发。因为在第一个查询尚未返回并更新缓存数据前,所有的相同查询都要访问数据库。在用户流量极大的时候这将会是一个问题。我经手的一个用户在使用memcached的时候就曾经碰到过这种情况,表现为网站随机地反应很慢。抑制这种突发的查询爆发,一般建议是在变更时直接更新缓存。如果做不到通过一种机制只让第一个查询访问数据库并更新缓存,其他的缓存访问则暂时等待一下。memcache在去年的一个更新里增加了一个新的API解决这个问题。不过我好像没在最新的发布版本上看到。

        另外,就是像memcached这类成熟的通用缓存,为了在多线程环境下保护缓存数据(读的时候不能改,改的时候不能读)使用了锁(memcached使用互斥量)来做线程同步。这个锁对并发性是有影响的。memcached使用一个全局的互斥量,虽然简单,但在高并发访问下这是一个潜在的瓶颈。

        以上的问题在大多数场合下并不严重,如果你的网站每秒不过一千的数据库访问可以基本忽视。

        • 家园 你对memcached的全局锁理解有误

          memcached虽然有多线程模式,但是他开启的线程数不多,一般是cpu核数加1,对数据的处理直接在io线程中完成。

          就算是高并发的情况下,请求也是在libevent epoll的队列中排队,所以不会出现热锁现象,更不会影响性能。当然如果现有的每秒数万次的性能你觉得还不够,还想优化到10万以上,那是需要对这些全局锁进行优化,貌似这也是m团队todolist中的。对于12306来说完全没必要了。

          可以参考我之前写的博文,[URL=http://uniseraph.iteye.com/blog/251488][/URL],分析源码,对memcached通讯层怎么支持高并发大连接量。

          我们使用memcached有不少年头了,也修改过不少memcached的代码,还是比较了解的,呵呵。

          • 家园 那我这样的理解是否有问题呢?

            目前memcached的全局锁将缓存操作部分变成非并发模式,能并发的部分主要是网络I/O和外围操作。同时线程数正好和CPU相配,所以冲突问题只发生在有数的几个线程中,并且缓存操作极快,所以冲突在绝大多数情况下不是问题。

            所以,更高的访问次数推荐多服务器横向扩展,而不是增加内存和CPU的纵向扩展。

            • 家园 基本正确

              后面的结论稍微有点出入。

              一般memcached集群不是为了解决访问速度问题,而是要缓存的内容太多了。

              如果要提高访问速度,还是得和你说得这样,把全局锁变成局部锁,优化算法。facebook对memcached代码进行优化,就做了类似的工作。

        • 家园 在牺牲一点查询效率的前提下,这样的问题应该可以解决。

          “我们也可以在订票操作中直接删除受影响的缓存项,直到有人再次查询的时候顺便更新这个缓存

          这操作的原则是拉,其实可以改成推。也就是说,数据库每次余票信息变化时,主动更新所有的memcached。而memcached本身只是被查询,它不能主动向数据库申请最新结果。这样就避免了数据库被频繁查询的问题。

          牺牲的这点查询效率,对于大多数订票人影响应该可以忽略不计。其实原来的铁路订票系统也经常发生查询时有票,但实际定不上的现象。如果以前的订票系统就已经考虑了类似的问题,那么现在的数据库查询似乎不应该有这样的问题。可能问题出在他处。

          关键词(Tags): #订票#IT
          • 家园 一般情况下我也会用推的方式

            应该说是牺牲一点更新的效率,换取查询的效率。我帖子里提到的就是这个

            我们必须能通过修改操作的参数反推出需要修改哪些缓存结果

            事实上,作为一线程序员我们的感觉这是以模块的耦合度换取查询效率,这样做的一个后果是更新功能和查询功能耦合在一起了。这就是架构师和程序员的矛盾之一。

            我们换一个例子,就以西西河为例。页面上有好几个帖子列表,如果这些列表有些需要缓存的话,那么发帖和回帖的功能就必须相应地更新这些缓存项。有时候不一定能准确推算出应该更新什么,比如热门贴之类的东西(你必须重新查一次才知道热门的变更)。

            再比如:复合条件查询结果,车票这个相对简单我们只使用了起止站点,如果把那个实际页面的查询条件加进来。缓存方法就需要另外设计,比如我们只用起止站点做查询,然后在结果中自己根据其他条件做筛选。否则在主动更新的时候,条件组合会很多。并且更改查询需求就会同时变更修改的代码。

            还有一个类似的例子就是分页显示,缓存什么、怎么更新就是一个很值得讨论的问题。

            综上所述,并不是任何时候都能用推的方式。

            我经手的一个客户就是这种情况,他们使用的是定期失效的方式被动更新缓存,结果就遇到那种随机的查询爆发。

            使用另外的机制主动更新缓存也是一个解决方法,不过这些都说明使用好缓存都需要付出一些代价。我一般会倾向于先发掘现有的代码(包括数据库)的潜能,因为模块耦合度在项目后期是所有人都要头疼的事情。

            所以Cache可以用,但要清楚付出的是什么,这可不是一手交钱一手交货的买卖,有时候它更像一个驴打滚的账单

          • 家园 你这个方法和我们做法差不多。

            对于一些不经常变化,但是又要尽量保证正确性的缓存,用一个rabbitmq;当缓存变化时,发现者发送一个广播消息,然后有个监听者受到消息后,更新所有缓存(可能不止一处)。

            就余票问题,其实不用每次变化都通知,因为关心的是有无,而不是准确数目。

分页树展主题 · 全看首页 上页
/ 3
下页 末页


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河