支撑1000万pv的数据库缓存解决方案

舍得网支撑1000万pv/数据库缓存系统

  系统主要是构建在hibernate之上的高效数据库缓存系统,其中包含了分布式解决方案,该系统已经应用在舍得网上了,没有发现大问题,本人也相信该系统已经足够强大,应付数百万IP/天的应用都不是问题。

  代码看上去很简单,其实却是两年经验的总结,整过过程也遇到了很多难点,最后一一解决了。本系统非常简洁易用,主程序BaseManager.java不到1000行代码,用“精悍”来形容绝对不为过,1000行代码却包含了数据库对象的缓存、列表和长度的缓存、按字段散列缓存、update延时更新、自动清除列表缓存等功能,用它来实现像论坛、博客、校友录、交友社区等绝大部分应用网站都足够了。

  现在进入正题。。。。。

  为什么要用缓存?如果问这个问题说明你还是新手,数据库吞吐量毕竟有限,每秒读写5000次了不起了,如果不用缓存,假设一个页面有100个数据库操作,50个用户并发数据库就歇菜,这样最多能支撑的pv也就50*3600*15=270万,而且数据库服务器累得半死,搞不好什么时候就累死了。我的这套缓存系统比单独用memcached做缓存还要强大,相当于在memcached上再做了两级缓存,大家都知道memcached很强了,但是吞吐量还是有限,每秒20000次get和put当遇到超大规模的应用时还是会歇菜,本地HashMap每秒可执行上百万次put和get,在这上面损耗的性能几乎可以忽略不记了。温馨提示:能不用分布式的时候就不要用分布式,非用分布式的时候再考虑用memcached,我的缓存系统在这方面都已经实现了,改个配置就可以了,有兴趣的可以仔细测试测试!

  一般数据库缓存在我看来包含四种。第一种:单个对象的缓存(一个对象就是数据库一行记录),对于单个对象的缓存,用HashMap就可以了,稍微复杂一点用LRU算法包装一个HashMap,再复杂一点的分布式用memcached即可,没什么太难的;第二种:列表缓存,就像论坛里帖子的列表;第三种:长度的缓存,比如一个论坛板块里有多少个帖子,这样才方便实现分页。第四种:复杂一点的group,sum,count查询,比如一个论坛里按点击数排名的最HOT的帖子列表。第一种比较好实现,后面三种比较困难,似乎没有通用的解决办法,我暂时以列表缓存(第二种)为例分析。

  mysql和hibernate的底层在做通用的列表缓存时都是根据查询条件把列表结果缓存起来,但是只要该表的记录有任何变化(增加/删除/修改),列表缓存要全部清除,这样只要一个表的记录经常变化(通常情况都会这样),列表缓存几乎失效,命中率太低了。

  本人想了一个办法改善了列表缓存,当表的记录有改变时,遍历所有列表缓存,只有那些被影响到的列表缓存才会被删除,而不是直接清除所有列表缓存,比如在一个论坛版(id=1)里增加了一个帖子,那么只要清除id=1这个版对应的列表缓存就可以了,版id=2就不用清除了。这样处理有个好处,可以缓存各种查询条件(如等于、大于、不等于、小于)的列表缓存,但也有个潜在的性能问题,由于需要遍历,CPU符合比较大,如果列表缓存最大长度设置成10000,两个4核的CPU每秒也只能遍历完300多次,这样如果每秒有超过300个insert/update/delete,系统就吃不消了,此路不通。

  在前面两种解决办法都不完美的情况下,本人和同事经过几个星期的思索,总算得出了根据表的某几个字段做散列的缓存办法,这种办法无需大规模遍历,所以CPU符合非常小,由于这种列表缓存按照字段做了散列,所以命中率极高。思路如下:每个表有3个缓存Map(key=value键值对),第一个Map是对象缓存A,在A中,key是数据库的id,Value是数据库对象(也就是一行数据);第二个Map是通用列表缓存B,B的最大长度一般1000左右,在B中,key是查询条件拼出来的String(如start=0,length=15#active=0#state=0),Value是该条件查询下的所有id组成的List;第三个Map是散列缓存C,在C中,key是散列的字段(如根据userId散列的话,其中某个key就是userId=109这样的String)组成的String,value是一个和B类似的HashMap。其中只有B这个Map是需要遍历的,不知道说明白了没有,看完小面这个例子应该就明白了,就用论坛的回复表作说明,假设回复表T中假设有字段id,topicId,postUserId等字段(topicId就是帖子的id,postUserId是发布者id)。

  第一种情况,也是最常用的情况,就是获取一个帖子对应的回复,sql语句应该是象

  select id from T where topicId=2008 order by createTime desc limit 0,5

  select id from T where topicId=2008 order by createTime desc limit 5,5

  select id from T where topicId=2008 order by createTime desc limit 10,5

  的样子,那么这种列表很显然用topicId做散列是最好的,把上面三个列表缓存(可以是N个)都散列到key是topicId=2008这一个Map中,当id是2008的帖子有新的回复时,系统自动把key是topicId=2008的散列Map清除即可。由于这种散列不需要遍历,因此可以设置成很大,例如100000,这样10万个帖子对应的所有回复列表都可以缓存起来,当有一个帖子有新的回复时,其余99999个帖子对应的回复列表都不会动,缓存的命中率极高。

  第二种情况,就是后台需要显示最新的回复,sql语句应该是象

  select id from T order by createTime desc limit 0,50

  的样子,这种情况不需要散列,因为后台不可能有太多人访问,常用列表也不会太多,所以直接放到通用列表缓存B中即可。

  第三种情况,获取一个用户的回复,sql语句象

  select id from T where userId=2046 order by createTime desc limit 0,15

  select id from T where userId=2046 order by createTime desc limit 15,15

  select id from T where userId=2046 order by createTime desc limit 30,15

  的样子,那么这种列表和第一种情况类似,用userId做散列即可。

  第四种情况,获取一个用户对某个帖子的回复,sql语句象

  select id from T where topicId=2008 and userId=2046 order by createTime desc limit 0,15

  select id from T where topicId=2008 and userId=2046 order by createTime desc limit 15,15

  的样子,这种情况比较少见,一般以topicId=2008为准,也放到key是topicId=2008这个散列Map里即可。

  那么最后的缓存结构应该是下面这个样子:

  缓存A是:

  Key键(long型)Value值(类型T)

  11Id=11的T对象

  22Id=22的T对象

  133Id=133的T对象

  ……

  列表缓存B是:

  Key键(String型)Value值(ArrayList型)

  from T order by createTime desc limit 0,50ArrayList,对应取出来的所有id

  from T order by createTime desc limit 50,50ArrayList,对应取出来的所有id

  from T order by createTime desc limit 100,50ArrayList,对应取出来的所有id

  ……

  散列缓存C是:

  Key键(String型)Value值(HashMap)

  userId=2046Key键(String型)Value值(ArrayList)

  userId=2046#0,5id组成的List

  userId=2046#5,5id组成的List

  userId=2046#15,5id组成的List

  ……

  userId=2047Key键(String型)Value值(ArrayList)

  userId=2047#0,5id组成的List

  userId=2047#5,5id组成的List

  userId=2047#15,5id组成的List

  ……

  userId=2048Key键(String型)Value值(ArrayList)

  userId=2048#topicId=2008#0,5id组成的List

  userId=2048#5,5id组成的List

  userId=2048#15,5id组成的List

  ……

  ……

  总结:这种缓存思路可以存储大规模的列表,缓存命中率极高,因此可以承受超大规模的应用,但是需要技术人员根据自身业务逻辑来配置需要做散列的字段,一般用一个表的索引键做散列(注意顺序,最散的字段放前面),假设以userId为例,可以存储N个用户的M种列表,如果某个用户的相关数据发生变化,其余N-1个用户的列表缓存纹丝不动。以上说明的都是如何缓存列表,缓存长度和缓存列表思路完全一样,如缓存象select count(*) from T where topicId=2008这样的长度,也是放到topicId=2008这个散列Map中。如果再配合好使用mysql的拆表和memcached,加上F5设备做分布式负载均衡,该系统对付像1000万IP/天这种规模级的应用都足够了。

  如果觉得不错,请把该文贴到自己的博客中或者收藏本文,谢谢

时间: 2024-10-10 14:20:07

支撑1000万pv的数据库缓存解决方案的相关文章

如何快速的将已知的1000万条数据插入到数据库中

首先,可以定义1个1000万次的循环,每次循环插入1条数据,当1000万次循环全部执行结束,则插入完成! 也可以使用1条INSERT语句插入多条数据,例如: INSERT INTO t_user (username, password, age, phone, email) VALUES ('user01', 'password01', 11, '13800138001', '[email protected]'), ('user02', 'password02', 12, '138001380

再送一波干货,测试2000线程并发下同时查询1000万条数据库表及索引优化

继上篇文章<绝对干货,教你4分钟插入1000万条数据到mysql数据库表,快快进来>发布后在博客园首页展示得到了挺多的阅读量,我这篇文章就是对上篇文章的千万级数据库表在高并发访问下如何进行测试访问 这篇文章的知识点如下: 1.如何自写几十行代码就能模拟测试高并发下访问千万级数据库表 2.比较高并发下(200次/秒,2000次/秒,10000次/秒)数据库的性能 3.比较千万级数据库在查询时加索引与不加索引的巨大差异(说实话,这个测试结果让我自己本人也很惊讶) 针对上篇文章插入的1000万条数据

1.1000万用户可能造成的并发数量是多少? 解决方案(理论篇)

今天开始对之前所能够想到的一些问题进行一些理论解决方案的研究. 首先,1000万的用户可以造成多么大的并发数量,应该是可以被计算出来的.我通过百度进行了一些搜索,关于用户数量与并发数的关系. 得到了一些资料,主要参考了一篇名为<并?发?用?户?数?.?吞?吐?量?.?思?考?时?间?的?计?算?公?式>的文档. 其中提到了关于性能需要考虑的几个方面,这些内容稍后再讨论.主要先说说几个公式 1.平均并发用户数的计算公式 C=nL / T 其中C是平均的并发用户数,n是平均每天访问用户数,L是一天

300万PV的ASP.NET网站使用阿里云的配置建议

300万PV的ASP.NET网站使用阿里云的配置建议 @老牛吃肉在博文“今天的访问高峰,扛过去了”的评论中询问了这样一个问题: 你好,站长,本公司正在考虑用阿里云.用途:互联网网站,主要站点:asp.net开发目前的考虑情况:访问ip 15-20万,pv300万.我想请问一下,当前的博客园的日均ip和pv,阿里云服务器有几台,每台的配置如何(包含带宽),还有稳定性如何(这是我们考虑的一个很大因素),价格如何. 另外,相比较托管服务器,阿里云的优势和劣势如何.谢谢,继续资料,望尽早回复,再次感谢

建设一个能承受500万PV/每天的网站如果计算?

PV是什么: PV是page view的简写.PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv. 计算模型: 每台服务器每秒处理请求的数量=((80%*总PV量)/(24小时*60分*60秒*40%)) / 服务器数量 .其中关键的参数是80%.40%.表示一天中有80%的请求发生在一天的40%的时间内.24小时的40%是9.6小时,有80%的请求发生一天的9.6个小时当中(很适合互联网的应用,白天请求多,晚上请求少). 简单计算的结果:((80%*500万)/(24小时*60分*6

建设一个能承受500万PV/每天的网站

你想建设一个能承受500万PV/每天的网站吗? 500万PV是什么概念?服务器每秒要处理多少个请求才能应对?如果计算呢? PV是什么: PV是page view的简写.PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv. 计算模型: 每台服务器每秒处理请求的数量=((80%*总PV量)/(24小时*60分*60秒*40%)) / 服务器数量 . 其中关键的参数是80%.40%.表示一天中有80%的请求发生在一天的40%的时间内.24小时的40%是9.6小时,有80%的请求发生一天的9.

你想建设一个能承受500万PV/每天的网站吗?服务器每秒要处理多少个请求才能应对?

你想建设一个能承受500万PV/每天的网站吗?服务器每秒要处理多少个请求才能应对? 你想建设一个能承受500万PV/每天的网站吗? 500万PV是什么概念?服务器每秒要处理多少个请求才能应对?如果计算呢? PV是什么: PV是page view的简写.PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv. 计算模型:  每台服务器每秒处理请求的数量=((80%*总PV量)/(24小时*60分*60秒*40%)) / 服务器数量 .其中关键的参数是80%.40%.表示一天中有80%的请求发

你想建设一个能承受500万PV/每天的网站吗?如果计算呢?(转)

作者:赵磊 博客:http://elf8848.iteye.com 你想建设一个能承受500万PV/每天的网站吗? 500万PV是什么概念?服务器每秒要处理多少个请求才能应对?如果计算呢? PV是什么: PV是page view的简写.PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv. 计算模型: 每台服务器每秒处理请求的数量=((80%*总PV量)/(24小时*60分*60秒*40%)) / 服务器数量 .其中关键的参数是80%.40%.表示一天中有80%的请求发生在一天的40%的

缓存穿透、缓存并发、热点缓存解决方案

一.前言 在之前的一篇缓存穿透.缓存并发.缓存失效之思路变迁文章中介绍了关于缓存穿透.并发的一些常用思路,但是个人感觉文章中没有明确一些思路的使用场景,本文继续将继续深化与大家共同探讨,同时也非常感谢这段时间给我提宝贵建议的朋友们. 说明:本文中提到的缓存可以理解为Redis. 二.缓存穿透与并发方案 相信不少朋友之前看过很多类似的文章,但是归根结底就是二个问题: 如何解决穿透 如何解决并发 当并发较高的时候,其实我是不建议使用缓存过期这个策略的,我更希望缓存一直存在,通过后台系统来更新缓存系统