需要缓存。这是结论。
如果你的网站用户量小的可怜,倒是不必,但加了还是比不加好。
这里要理解缓存和数据库的作用是同等重要的,也是需要强调的。
首先需要理解缓存,缓存你可以简单的理解成一个哈希表,通过key就可以取得值。而这个key-value对在内存中,内存的访问极快。如果数据库有索引,那也是相当的快,但索引需要生成文件,如果对大量的key进行索引会导致索引文件数据膨胀,甚至有可能会超过数据文件本身。查询虽然性能消耗的小,但大量的查询性能还是会消耗,相比之下,能节约数据库性能就节约,因为我们程序员是干嘛的?就是为了不必重复劳动。
既然lz对缓存作用有怀疑,觉得sql的查询性能已经很高了,那么我们可以仔细分析,有这么一种情况:
1个用户被100个用户关注。
OK,这样我们如果不使用缓存,加载一个用户的粉丝页面。
1. 首先通过数据库查询到用户,例如from user where id=xxx。
2. 然后通过数据库查到粉丝列表,例如from fans where follow_id=xxx。
3. 显示用户的页面,这里有101个用户。循环查询了from user where id=xxx 101次。
这个时候你会发现,即便是同一个用户,那么访问一次页面需要101个查询,虽然粉丝已经很少了。即便性能很快,还是需要消耗sql资源。如果一个页面每天被访问1000万次,你就自己能够得到结论。
如果使用缓存,会有下面的情况:
1. 首先通过数据库查询到用户,from user where id=xxx,放入缓存。
2. 通过数据库查询到粉丝列表,from fans where follow_id=xxx列表可以放入缓存。
3. 显示用户的页面,假设没有任何缓存的情况下,依旧循环查询了from user where id=xxx 100次。
这个时候再访问同一个页面的时候,假设访问1000万次。
1. 首先通过缓存看用户是否在缓存里,存在,命中返回。
2. 看用户的粉丝列表是否在缓存里,存在,命中返回。
3. 显示用户的页面,100个粉丝的用户都在缓存里,存在,命中返回。
无需数据库查询。
至于当用户的粉丝变动了,如何更新缓存呢,一个简单的方法就是删除缓存。假设用户的粉丝取消关注了这个用户。
1. 删除用户的粉丝列表的缓存。
那么刷新页面的时候就会下面的情况:
1. 首先通过缓存看用户是否在缓存里,存在,命中返回。
2. 看用户的粉丝列表是否在缓存里,不存在,查询数据库from fans where follow_id=xxx,并放入缓存。
3. 显示用户页面,99个粉丝都在缓存里,存在,命中返回。
即便访问依旧是1000万次,只有取消关注的那第一次返回查询了一个数据库,并且是lz说的高性能查询。那么数据库服务器依旧没有什么压力。
至于缓存性能?相当的快,在内存中,可以忽略不计(当然这里也是根据量级来判断,但即便千万用户也可以忽略不计)。
上面的情况只是一个业务里相当小的情况,有比如你当前打开的v2ex这个页面,加载了多少用户、话题和评论?你可以计算一下缓存的优势。
----
当然,继续深入的话,我们推荐缓存什么最好?(正确的缓存不会造成不一致)首先提出不好的:
1. 不要针对页面缓存,这才会造成什么不一致的情况。
2. 不要针对大块数据进行缓存,下面再说。
(其实有时候是可以针对一些业务页面进行缓存的,例如上个月到几年前的每个月的财务结算报表)
要缓存的,最好是最原始的数据。根据上面的情况,例如:
1. 一个用户的数据库数据的缓存。可以直接映射成一个用户的实例。
2. 一个用户的粉丝列表的id缓存,必须强调,这里不是粉丝列表的实例的缓存。
假设通过mc.get(user_key),那么得到的最好是用户数据库里的原数据。如果mc.get(user_fans_key),那么得到的就是用户粉丝的**id**缓存。这里详细说明一下,不要针对大块数据进行缓存的原因。
假设你缓存了大块数据,即拿到用户粉丝的列表不是缓存id,而是缓存对象,那么如果一个用户更新了自己的信息,这个时候并不会删除掉用户的关注列表缓存,对吧,因为粉丝并没有取消关注用户,那么关注列表拿到的用户数就错误了。
另外一个问题就是如果缓存id,即便有100万个粉丝,内存占用也不大,如果100万个实例呢。。。
所以,只针对粉丝的id进行缓存是很重要的,举例说明:
1. 用户有100个粉丝,并且缓存了。
2. 一个粉丝改了自己的名字,这个时候把粉丝的用户缓存失效。
3. 用户查询自己的粉丝时,发现99个用户缓存命中,1个用户缓存失效,查询并放入缓存。
因为在2里粉丝并没有取消关注用户,不会刷新粉丝缓存,如果这个时候缓存的是用户对象,那就出现了不一致。
----
我们可以再发散,为什么现在redis比memcached更热门,因为redis可以直接在内存里排序。假设我有一个需求是最新关注的粉丝在最上面。那么redis可以直接在缓存列表的最前面插入最新粉丝的id,但memcached不行,按上面的方法就是先删除,然后数据库里查询保持顺序,在插入到缓存中。
redis这样的情况可以让类似twitter、新浪微博这样的业务顺利进行,memcached就用起来别扭了。
----
再回到这个页面,我们看看v2ex当前页面可以怎么缓存。
1. 用户信息。
2. lz发的内容的原数据,文字可以放入key-value持久化数据库。
3. lz发的内容的评论id列表。
4. 每个评论的原数据缓存,内容可以放入key-value持久化数据库。
5. 如果有人添加评论,可以过期lz发的内容的评论id列表。
6. 如果有人更新评论,如果是内容,不需要更新缓存,直接更新持久化数据库。
7. 如果有人更新评论如时间之类的,或者删除评论,过期lz内容评论的id列表。
可以看到数据库查询大大的减少,v2ex这个用户量级和访问,至少毫秒内就能展示。
----
我不太同意『缓存』这是一个过度优化的设计,『缓存』这个概念无论是计算机硬件、软件还是web设计都是很重要的。不仅能提升app的使用体验(载入更快了),还减少了sql服务器的压力。
大部分互联网公司都及其依赖缓存,如果缓存服务器挂了,网站必然挂。但之前我也说了,缓存和数据库都是非常重要的,因为在内存中的数据断电就会消失,还是需要持久化的。另外要知道,内存的速度是硬盘访问的数百倍甚至千倍。
正如 @
pubby 说的,网站运行了几年,那积累的缓存要是没了,查询流量瞬间就会击垮服务器。
几年前大家做开发的时候,喜欢把数据写入数据库,然后放入memcached,现在更多的开发更喜欢先放入redis,然后写入数据库持久化,当成是log和恢复时使用。当然我觉得可以根据业务来选择不同的方案,不一定redis是最好的(因为它太消耗内存)。
本来想解答lz的没想到说了这么多,只是觉得能反应架构和思维上的区别,当然我是一个垃圾,如果说的有问题还希望大家轻拍。