关系型数据库的数据量比较小,以mysql为例,单表的量尽量控制在千万级别。
关系型数据库在TPS上的瓶颈往往会比其他瓶颈更容易暴露出来,尤其对于大型web系统,由于每天大量的并发访问,对数据库的读写性能要求非常高;而传统的关系型数据库的处理能力确实捉襟见肘;以我们常用的MySQL数据库为例,常规情况下的TPS大概只有1500左右(各种极端场景下另当别论)。
下面是MySQL官方所给出的一份测试数据:
系统配置:
Sun V40z / 4x 2390MHZ / Solaris 10 / 8GB RAM
1m rows,Read Only,4 CPU
Connections | Trans/sec |
---|---|
1 | 382 |
2 | 677 |
4 | 1130 |
8 | 1479 |
32 | 1418 |
256 | 947 |
1024 | 224 |
https://www.percona.com/blog/files/presentations/UC2005-Advanced-Innodb-Optimization.pdf
对于一个PV上亿的网站,每一次请求涉及多次数据库交互,每天的读写请求量远远超过关系型数据库的处理能力,所以必须通过高效的缓存抵挡大部分的数据请求。
本地缓存
本地缓存会减少网络层的交互,无论是本地内存还是磁盘,速度比较快。但对分布式系统来讲有一个缺点,当数据库更新时,没有一个简单有效的方法去更新本地缓存。
本地缓存适用两种场景:
注意问题:
分布式缓存
本地缓存的使用很容易让你的应用服务器带上“状态”,而且容易受内存大小的限制。
分布式缓存借助分布式的概念,集群化部署,独立运维,容量无上限,虽然会有网络传输的损耗,但这1~2ms的延迟相比其更多优势完成可以忽略。
优秀的分布式缓存系统有大家所熟知的Memcached、Redis。对比关系型数据库和缓存存储,其在读和写性能上的差距可谓天壤之别,redis单节点已经可以做到8W+ QPS。设计方案时尽量把读写压力从数据库转移到缓存上,有效保护脆弱的关系型数据库。
客户端缓存
大部分的web应用、微服务应用都会尽量做到无状态,方便于线性扩容。有状态的后端存储:DB、NoSQL、分布式文件系统、CDN等。
另一个很重要的就是客户端缓存了,对客户端存储的合理使用,原本每天几千万甚至上亿的接口调用,一下就可能降到了几百万甚至更少,而且即便是用户更换浏览器,或者缓存丢失需要重新访问服务器,由于随机性比较强,请求分散,给服务器的压力也很小。另外再加上合理的缓存过期时间,就可以在数据准确和性能上做一个很好的折衷。
更多缓存框架:http://www.oschina.net/project/tag/109/cacheserver
被动失效
缓存数据主要是服务读请求的,通常会设置一个过期时间,或者当数据库状态改变时,通过一个简单的delete操作,使数据失效掉;当下次再去读取时,如果发现数据过期了或者不存在了,那么就重新去数据库读取,然后更新到缓存中,这即是所谓的被动失效策略。
被动策略有一个很大的风险,从缓存失效到数据再次被预热到cache这段时间,所有的读请求会直接打到DB上,对于一个高访问量的系统,很容易被击垮。
主动更新
主动更新,很容易理解,就是数据库存储发生变化时,会直接同步更新到Cache,主要是为了解决cache空窗期引发的问题。比如电商的卖家修改商品详情,具有读多写少特点。
但如果是读多写多,同样会带来另一个问题,就是并发更新。多台应用服务器同时访问一份数据是很正常的,这样就会存在一台服务器读取并修改了缓存数据,但是还没来得及写入的情况下,另一台服务器也读取并修改旧的数据,这时候,后写入的将会覆盖前面的,从而导致数据丢失。解决的方式主要有三种:
1、锁控制。这种方式一般在客户端实现(在服务端加锁是另外一种情况),其基本原理就是使用读写锁,即任何线程要调用写方法时,先要获取一个排他锁,阻塞住所有的其他访问,等自己完全修改完后才能释放。如果遇到其他线程也在修改或读取数据,那么则需要等待。锁控制虽然是一种方案,但是很少有真的这样去做的,其缺点显而易见,其并发性只存在于读操作之间,只要有写操作存在,就只能串行。
2、单版本机制(乐观锁)。为每份数据保存一个版本号,当缓存数据写入时,需要回传这个版本号,然后服务端将传入的版本号和数据当前的版本号进行比对,如果等于当前版本号,则成功写入,否则失败。这样解决方式比较简单;但是增加了高并发下客户端的写失败概率;
3、多版本机制。即存储系统为每个数据保存多份,每份都有自己的版本号,互不冲突,然后通过一定的策略来定期合并,再或者就是交由客户端自己去选择读取哪个版本的数据。
分布式缓存的本质就是将所有的业务数据对象序列化为字节数组,然后保存到自己的内存中。所使用的序列化方案也自然会成为影响系统性能的关键点之一
常见的序列化框架:
方案一:
uid---> 发过的贴子内容列表
方案二:
uid--->发过的贴子tid列表
tid--->贴子内容
我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。那这种问题有什么好办法解决呢?
有一个比较巧妙的做法是,可以将这个不存在的key预先设定一个值。比如,"NULL" ,在返回这个NULL值的时候,我们的应用就可以认为这是不存在的key。
缓存穿透如果被恶意攻击,造成的影响面很容易放大。比如文章详情页,查询一个不存在的tid,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成影响。
对于一些活动期间的数据通常会提前预热到缓存中,并设置一个过期时间,如果系统的并发量很高,恰巧缓存又失效了,此时会将压力转嫁给后面的DB,很容易击垮系统。
那如何解决这些问题呢?
其中的一个简单方案就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。还有一种方式,就是计算好缓存的过期时间。
当修改了数据库后,没有及时修改缓存,或者缓存服务器挂了。如果是因为网络问题引起的没有及时更新,可以通过重试机制来解决。而缓存服务器挂了,请求首先自然也就无法到达,从而直接访问到数据库。那么我们在修改数据库后,无法修改缓存,这时候可以将这条数据放到数据库中,同时启动一个异步任务定时去检测缓存服务器是否连接成功,一旦连接成功则从数据库中按顺序取出修改数据,依次进行缓存最新值的修改。
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )