前言:曾经就由于一个小小的疏忽,从而致使了服务器崩溃了,后来才发现:原来就是由于一个循环而致使的,因此,对“注意细节“这一说法是深有感触。javascript
本篇的议题以下:java
问题的描述数据库
细节的重要性缓存
问题的描述服务器
首先,描述一下故事的背景:(但愿你们耐心的故事读完)ide
在网站中,网页中的分页控件每次显示10条数据,每次点击下一页,就再次去取下一个10条数据。至于分页的方法怎样作,方法有不少,相信这点你们都知道。网站
过程是这样的:在用户请求数据的时候(考虑到了用户的操做和网站的访问量)我会第一次取出500条数据,而后把数据放在缓存中,也就是说,我取出了50页的数据,放在缓存中,这样若是,之后用户请求第一页到第49页的时候,就直接从缓存中拿数据。this
以下图:spa
第一个数据块:xml
采用键值对的形式:字典保存
若是用户请求到了49页之后,那么就再次从数据库中取出下一个数据块(包含501到1000数据),而后,如今内存中就有了1000条数据。
至于缓存多久,数据什么失效,失效后怎么作,这里暂不谈论。(网站在这种缓存策略下运行的很好)。
代码以下:
- List<Product> products=GetDataFromCacheOrDatabase(condition,pageIndex,count….);
代码的意思很清楚,从缓存中拿数据,若是缓存中没有对应的数据,那么就先从数据库中拿500条数据,而后放在缓存中,最后返回10条数据。
后来,由于某些功能的须要,须要返回当前页的前6页数据和后6页的数据,例如:若是当前页是第12页,那么就要返回12页以前6页Product(也就是第6,7,8,9,10,11页的数据),和第12页后的页的Product(第13,14,15,16,17,18页的数据)。
以下:
固然,若是当前页是第5页,那么就把以前全部5页的数据都返回,另外再加上第5页以后的6页数据。
这里就可能涉及到跨块获取数据,如:
若是当前页是第48页的时候,那么返回前6页数据是没有什么问题的,那么后6页的数据就不足了,由于49,40也得数据能够从缓存的数据块中取到,至于51,52,53,54页的数据,就须要再次从数据库中读取,而后再次缓存(若是事先没有被缓存)。
最后在缓存中的数据以下:
而后调用方法:(伪码)
List<Product> products=GetDataFromCacheOrDatabase(condition,42, 126….);
上面传入的是从第42页开始的数据,也就是第48页的前6页和后6页的数据。
这个方法的内部实现是这样的:
1. 首先从第一个数据块中取出42页到50页的数据
取出数据后保存在一个List<Product> firstProductList;
2. 从第二个数据块中取出从51页到54页(若是第二个数据块在缓存不存在,就去数据库中取501-1000条,而后再放在缓存的第二个数据块中)。
保存在第二个List<Product> secondProductList
3. 而后把两个list合并,返回结果。例如
secondProductList.Foreach(u=>firstProductList.Add(u));
基本的实现就是这样,看起来还行,也比较的合理,可是就是由于这个操做,从而致使服务器内存溢出。
你们想一想看是什么缘由。
细节的重要性
其实缓存的数据不是不少,是不足以让服务器内存溢出的,可是服务器仍是出现了out of memory的异常。以前一直跑的很好,就是改了代码以后才出现问题的。
其实这就是因为一个最基本的错误产生的:引用类型。
下面就来分析下:
首先是从第一个数据块中取出数据,而后用
List<Product> firstProductList 引用指向取出的数据
而后从第二个数据块中取出数据,用
List<Product> secondProductList指向数据的引用
以下图
在第三步中采用
- secondProductList.Foreach(u=>firstProductList.Add(u));
把secondProductList中的数据加入到firstProductList中,就由于是引用类型,其实实际操做的结果是:不断的在改变第一个数据块中的数据,使得第一个数据块中的数据逐渐的变多。
如今当前页是48页,采用上面的操做,导致第一个数据块中的数据增长了60条,
若是用户再次翻页,到了49页,那么第一个数据块中的数据又增多了60条
依此类推,最后致使了服务器内存的不足,导致服务器崩溃了。本来的“功臣”----缓存却成为了罪魁祸首。
其实这个问题的解决,只要改变一点点的代码就好了:
List<Product> firstProductList;
List<Product> secondProductList;
而后
List<Product> resultProductList=new List<Product>();而后分别把firstProductList,secondProductList遍历,加入到resultProductList就好了。
就这么简单。
一个小的细节,致使了大的问题。