缓存的基本思想

缓存的使用场景

1.DB缓存,减轻服务器的压力

在请求和数据库中的数据都较少且对数据访问的速度要求也不是很高的时候,我们可以让应用程序直接访问数据库来解决我们的需求。

但是,当应用程序的访问量越来越大,数据库的数据量越来越大时,由于数据库中的数据是存在硬盘上的,虽然有pool在内存中暂存了数据库中的数据,但是pool的大小是非常有限的,当访问请求变多,访问的数据量变多时,内存的pool和硬盘上的DB数据也会发生频繁的swap,时间损耗也是非常大的。

这时候我们就需要引入缓存,将已经访问的内容或者数据存储起来,当再次访问时先找缓存,缓存命中直接返回数据。不命中的话再找数据库,并且回填缓存。回填之后,当下一个请求访问同样内容的时候,就不需要再访问数据库,直接缓存就可以命中。

redis做缓存的简单实例 redis缓存设计思路_缓存


2.提高系统响应能力

数据库的数据是存在文件里,当请求来访问时需要跟内存做swap才能访问到

在大量瞬间访问时,MySQL单机会因为频繁IO而造成无法响应。将数据存在Redis中就是将数据存在内存中,内存可以很好的支持高并发,能够支持的qps更高。3.做session的分离

传统的session是由tomcat自己进行维护和管理

集群或分布式环境下,不同的tomcat管理各自的session。如果用户的请求被Nginx转发到不同的tomcat上,可以就会遇到一个建立了session,一个没建立session的情况,所以需要各个tomcat之间通过网络和IO进行session的复制,这样的操作会很大的影响系统的性能。

各个tomcat之间复制session会导致性能损耗,也不能保证每个tomcat的session都是数据同步的。

这时我们可以把Redis用作数据的临时存储,将登录成功的session信息,放在Redis中,这样多个tomcat服务器就可以共享session信息。

redis做缓存的简单实例 redis缓存设计思路_数据_02

4.做分布式锁

5.做乐观锁

什么是缓存?

缓存原指CPU上的一种高速存储器,它先于内存与CPU交换数据,速度非常快,现在泛指存储在计算机上的原始数据的复制集,便于快速访问。在互联网技术中心,缓存是系统快速响应的关键技术之一。是一种以空间换时间的技术

电脑分级存储图示

redis做缓存的简单实例 redis缓存设计思路_redis做缓存的简单实例_03

大型网站中缓存的使用:

最开始的网站架构都是单机架构Linux,apache,MySQL,PHP ssm等

当访问量变大时,响应力就会变差,用户体验也会变差

这时就需要引入缓存

redis做缓存的简单实例 redis缓存设计思路_redis做缓存的简单实例_04

常见缓存的分类:

客户端缓存

包括页面缓存和游览器缓存,还有app缓存

页面缓存

页面自身对某些元素或全部元素进行存储并保存为文件

游览器缓存

当客户端向服务器请求资源时,会先抵达游览器缓存,如果游览器有被请求资源的副本,就可以直接从游览器缓存中提取而不是从原始服务器中提取这个资源
游览器缓存可以分为强制缓存和协商缓存
条件:cache-control的max-age没有过期,expires的缓存时间没有过期

强制缓存指的是直接使用游览器的缓存数据

协商缓存指的是服务器资源未修改,则使用游览器的缓存(304),若服务器资源已经修改,那就使用服务器资源

app缓存

原生app中把数据存在内存文件或本地数据库中

网络端缓存

通过代理的方式响应客户端的请求,对重复的请求返回缓存中的数据资源。

web代理缓存

可以缓存原生服务器的静态资源,比如样式图片等

边缘缓存

边缘缓存中典型的商业化服务就是CDN了。
CDN的全程是Content Delivery Network,即内容分发网络
CDN通过部署在各地的边缘服务器,使得用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率
比如说阿里云一般就有内地服务器和香港服务器,会根据你登录时的ip地址显示简体和繁体

服务端缓存

服务器端缓存是整个缓存体系的核心。包括数据库级缓存、平台级缓存和应用级缓存

数据库级缓存

数据库是用来存储和管理数据的
MySQL在server层使用缓存机制。将查询后的数据缓存起来,下一次查询同样数据就可以直接通过访问内存得到结果

另外,还有InnoDB存储引擎中的buffer-pool用于缓存InnoDB索引以及数据块

平台级缓存

平台级缓存指的是带有缓存特性的应用框架
部署在应用服务器上,也叫作服务器本地缓存

应用级缓存

具有缓存功能的中间件,采用K-V形式存储,利用集群支持高可用高性能高并发高扩展

缓存的优势和代价

使用缓存的优势

1.提升用户体验
用户体验(User Experience):用户在使用产品过程中建立起来的一种纯主观感受。
缓存的使用可以提升系统的响应能力,大大提升了用户体验。
2.减轻服务器压力
客户端缓存、网络端缓存减轻应用服务器压力。
服务端缓存减轻数据库服务器的压力。
3.提升系统性能
系统性能指标:响应时间、延迟时间、吞吐量、并发用户数和资源利用率等。
缓存技术可以:
缩短系统的响应时间
减少网络传输时间和应用延迟时间
提高系统的吞吐量
增加系统的并发用户数
提高了数据库资源的利用率

使用缓存的代价

1.额外的硬件支出
缓存是一种软件系统中以空间换时间的技术
需要额外的磁盘空间和内存空间来存储数据
搭建缓存服务器集群需要额外的服务器
采用云服务器的缓存服务就不用额外的服务器了
阿里云,百度云,提供缓存服务
2.高并发缓存失效
在高并发场景下会出现缓存失效(缓存穿透、缓存雪崩、缓存击穿)
造成瞬间数据库访问量增大,甚至崩溃
3.缓存与数据库数据同步
缓存与数据库无法做到数据的时时同步
Redis无法做到主从时时数据同步
4.缓存并发竞争
多个redis的客户端同时对一个key进行set值得时候由于执行顺序引起的并发问题

缓存的读写模式

缓存有三种读写模式

Cache Aside Pattern

旁路缓存是最经典的缓存+数据库读写模式

读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据放入缓存,同时返回响应。

redis做缓存的简单实例 redis缓存设计思路_缓存_05


更新的时候,先更新数据库,然后再删除缓存。

之所以选择是删除缓存而不是更新缓存,是因为如果缓存的值是一个数据结构,比如说是hash或者list的话,你只更新了一条数据,要找到对应的该条数据,就得挨个去遍历这个list,这样时间成本比较大。另外就是懒加载,当我们使用的时候才更新缓存。

当然遇到高并发情况时,会出现脏读的情况
1.先更新数据库,再更新缓存
假设线程一已经更新了数据库,在线程一还没更新缓存之前,线程二读取了缓存之中的数据,这下线程二读到的就是脏数据。

2.先删除缓存,再更新数据库
假设线程一已经删除了缓存,在线程一还没更新数据库之前,线程二读缓存不命中,又去读了数据库中的数据,这时,线程二读到的也是脏数据

3.先更新数据库,再删除缓存
线程一先更新了数据库,在线程一还没删除缓存之前,线程来读数据,读缓存命中,读到了旧的数据,也是脏数据

Read/Write Through Pattern

这种模式应用程序只操作缓存,缓存操作数据库
Read-Through(穿透读模式/只读模式):应用程序直接读缓存,缓存没有的话,就读数据库,并由数据库回写内容到缓存(guavacache)

Write-Through(穿透写模式/直写模式):应用程序直接写缓存,缓存再写数据库

Write Behind Caching Pattern

应用程序只更新缓存,然后缓存通过异步的方式将数据批量更新到DB中,缺点是不能实时同步,甚至会丢失数据。

缓存架构的设计思路

1.多层次

redis做缓存的简单实例 redis缓存设计思路_数据库_06

分布式缓存如果宕机,本地缓存还可以使用

2.数据类型
如果value是字符串或整数或者二进制,value的值比较大,只进行setter和getter,我们可以采用memcached。memcached纯内存缓存,支持多线程。

如果value是复杂类型,比如hash,set,list,zset,需要存储关系,需要聚合计算,可采用redis

3.要做集群
分布式缓存集群方案(redis)哨兵加主从

4.缓存的数据结构设计

1)与数据库表一致
数据库表和缓存是一一对应的
缓存的字段会比数据库表少一些
缓存尽量要多存常常访问的数据,数据库中存冷数据

2)与数据库表不一致

当我们需要存储关系,聚合,计算等。

以用户评论为例,DB结构如下:

redis做缓存的简单实例 redis缓存设计思路_缓存_07


如果我们要找出UID为1000的用户某一天的评论,用原始表的数据结构就是不合适的。

合理的设计:
key可以取UID+时间戳(精确到天),评论一般以天为单位
value:redis的hash类型,field为id和content
expire:可以设置为一天

典型案例

以拉钩网(www.lagou.com)为例,看看首页缓存的设计

首页分析:

职位实时变化,不能使用静态html

数据在服务端拿出,不能为空

数据不一定实时,架构图如下:

redis做缓存的简单实例 redis缓存设计思路_缓存_08

1.静态文件的缓存
静态文件一般缓存在Nginx中,包括css,js,图片等

server {
listen 80 default_server;
server_name localhost;
root /mnt/blog/;
location / {
}
#要缓存文件的后缀,可以在以下设置。
location ~ .*\.(gif|jpg|png|css|js)(.*) {
proxy_pass http://ip地址:90;
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 24h;
proxy_cache_valid 301 30d;
proxy_cache_valid any 5m;
expires 90d;
add_header wall "hello lagou.";
}
}

2.职位列表的缓存

redis做缓存的简单实例 redis缓存设计思路_redis做缓存的简单实例_09


职位列表的数据特点是固定数据,一次性读取

缓存方案是:

在服务器开启时一次性初始化到服务器本地缓存,采用Guava Cache用于存储频繁使用的少量数据,支持高并发访问3.热门职位的缓存

redis做缓存的简单实例 redis缓存设计思路_数据_10


热门职位数据的特点是频繁变化,不必需要实时同步,但一定要有数据,不能为空

缓存方案是:数据从服务层读取(dubbo),然后放到本地缓存中(guava),如果出现超时或者读取为空,那么就返回本地缓存的数据

4.数据回填
从dubbo中读取数据时,先读取Redis集群的缓存,如果缓存命中就直接返回。
如果缓存不命中就返回本地缓存,不能直接读取数据库。定时采用异步的形式从数据库刷入到缓存中。

5.热点策略
对于热点数据,我们采用本地缓存策略,而不采用服务熔断策略,因为首页数据可以不准确,但是不能不响应