文章目录
- 分布式缓存的原理及应用
- 缓存(进程级缓存与分布式缓存)
- 分布式缓存
- Ehcache的原理及应用
- Ehcache的原理
- Ehcache的特点
- Ehcache的架构
- Ehcache的存储方式
- Ehcache的扩展模块
- Encache的应用
- Redis原理及应用
- Redis的原理
- Redis的数据类型
- Redis管道
- Redis批量操作实操
- Redis的事务
- Redis的事务实操
- Redis的发布和订阅
- Redis集群数据复制的原理
- Redis的持久化
- Redis的集群模式及工作原理
- Redis实操
- 分布式缓存设计的核心问题
- 缓存预热
- 缓存更新
- 缓存淘汰策略
- 缓存雪崩
- 缓存击穿
- 缓存降级
分布式缓存的原理及应用
缓存(进程级缓存与分布式缓存)
缓存指将需要频繁访问的数据存放在内存中以加快用户访问速度的一种技术。缓存分进程级缓存和分布式缓存,进程级缓存指将数据缓存在服务内部,通过Map、List等结构实现缓存;分布式缓存指将数据单独存放在分布式系统中,以便于缓存的统一管理和存取。常用的分布式缓存系统有Ehcache、Redis和Memcached。
分布式缓存
对于传统单点的web系统一般使用进程式缓存即可,而在微服务架构下往往需要一个分布式缓存来实现跨服务的缓存系统。
如图所示,用户访问的数据库是被部署在多个服务器节点的集群数据库,缓存是被部署在多个服务器节点的分布式缓存,同时缓存之间有数据备份,在一个节点出故障后,分布式缓存会将用户的请求转发到其他备份节点以保障业务的正常运行。
Ehcache的原理及应用
Ehcache的原理
Ehcache是基于Java实现的一套简单、高效、线程安全的缓存管理类库,是一个高效缓存框架。其内部采用多线程实现,采用LinkedHashMap存储元素,同时支持将数据持久化到物理磁盘上。Ehcache提供了内存、磁盘文件及分布式缓存方式等多种灵活的Cache管理方案,特点是快速、轻量、可伸缩、操作灵活、支持持久化等。
Ehcache的特点
- 快速:Ehcache内部采用多线程机制实现,数据存取性能高;
- 轻量:Ehcache的安装包大小只有1.6MB左右,可以被快速、方便地继承到系统中;
- 可伸缩性:Ehcache缓存在内存和硬盘的存储可以伸缩到数几十GB,可以轻松应对大数据场景;
- 操作灵活:Ehcache提供了丰富的API接口,可实现基于主键、条件进行数据读取等。同时,Ehcache支持在运行时修改缓存配置(存活时间、空闲时间、内存的最大数据、磁盘的最大数量),提高了系统维护的灵活性;
- 支持多种淘汰算法:Ehcache支持最近最少被使用、最少被使用和先进先出缓存淘汰策略;
- 支持持久化:Ehcache支持将缓存数据持久化到磁盘上,在机器重启后从磁盘上重新加载缓存数据。
Ehcache的架构
Ehcache在架构上由Cache Replication 、 In-Process API和Core组成。其中,Cache Replication存储缓存副本;In-Process API封装操作缓存数据的API,包括Hibernate API、JMX API、Servlet Cacheing Filter API等;Core是Ehcache的核心部分,包括用于管理缓存的CacheManger、用于存储缓存的Store和用于操作缓存的Cache API等;NetWork APIs提供RESTful API、SOAP API等Web PI接口,如图:
Ehcache的存储方式
Ehcache的存储方式包括堆存储、堆外存储和磁盘存储。
- 堆存储:将缓存数据存储在Java堆内存中,其特点是存取速度快,但容量有限;
- 堆外存储:基于NIO的DirectByteBuffers实现,将缓存数据存储在堆外内存上。其特点是比磁盘存取速度快,而且不受GC的影响,可以保证响应时间的稳定性,在内存分配上开销比堆内存大,而且要求必须以字节数据的方式存储,因此对象必须在存储过程中进行序列化,对读取操作则进行反序列化,数据存取速度比堆内存慢一个数量级。
- 磁盘存储:将数据存储在磁盘上,保障服务重启后内存数据内购重新在磁盘上加载,其读取效率最低,是内存数据持久化的一种方式。
Ehcache的扩展模块
Ehcache是开放的缓存系统,除自身的实现外还有其他扩展模型,这些扩展模型是相互独立的库,每个都是为Ehcache添加新的功能。
模块名称 | 说明 |
encache-core | Api标准缓存引擎,RMI复制和Hibernate支持 |
encache | 分布式encache,包括encache的核心和Terracotta的库 |
encache-monitor | 企业级监控和管理 |
encache-web | 为Java Servlet Container提供缓存、gzip压缩支持的filters |
encache-jcache | JSR108 JCACHE的实现 |
encache-jgroupsreplication | 使用JGroup的复制 |
encache-jmsreplication | 使用JMS的复制 |
encache-openjpa | OpenJpa插件 |
encache-server | 在war内部署或者单独部署的RESTful cache server |
encache-unlockedreadsview | 允许Terracotta cache的无锁读 |
encache-debugger | 记录RMI分布式调用事件 |
Encache for Ruby | Jruby和Rails支持 |
Encache的应用
步骤:
- 导依赖
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
2.配置xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache>
<cache name="user" eternal="true" overflowToDisk="true" maxElementsInMemory="1000"/>
</ehcache>
声明了一个名称为user的缓存,其中eternal=true表示缓存对象永不过期,maxElementsInMemory表示内存中该Cache可存储最大的数据量,overflowToDisk=true表示在内存缓存的对象数量达到了maxElementsInMemory界限后,会把溢出的对象写到磁盘中。注意:如果需要将缓存的对象写入磁盘中,则该对象必须
实现Serializable接口。
- 使用Ehcache缓存
@Service
public class UserService {
private static final Logger logger= LoggerFactory.getLogger(UserService.class);
@Autowired
private UserRepository userRepository;
@CachePut(value="user",key="#user.id")
public User save(User user){
User add=userRepository.save(user);
logger.info("user info add db and ehcache , key:"+ add.getId());
return add;
}
@CachePut(value="user",key="#user.id")
public User findOne(Long id){
User user = userRepository.getOne(id);
return user;
}
}
通过@Cacheable(value=“user”,key="#user.id")开启Ehcache缓存.
在用户调用save()保存数据时会在Ehcache内存中也保存一份User对象,其key为User对象的id属性。在用户调用findOne()查询该数据时,首先会去Ehcache缓存中查找数据,如果存在则返回,不存在则去数据库中查找并返回结果。
- Ehcache Demo
GitHub:
晚点上传
Redis原理及应用
Redis是一个开源(BSD许可)的内存中的数据结构存储系统,可以用作内存数据库、缓存和消息中间件,支持多种类型的数据结构,例如String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Z
Set(有序集合)、Bitmap(位图)、HyperLogLog(超级日志)
和Geospatial(地理空间)。Redis内置了复制、Lua脚本、LRU驱动事件、事务和不同级别的磁盘持久化,并通过Redis哨兵模式(Sentinel)和集群模式(Cluster)提供高可用性(High Availability)。
Redis的原理
Redis不但支持丰富的数据类型,还支持分布式事务、数据分片、数据持久化等功能,是分布式系统中不可或缺的内存数据库服务。
Redis的数据类型
Redis支持的数据类型有String、List、Hash、Set、ZSet、Bitmap、HyperLogLog和Geospatial八种。
Redis管道
Redis是基于请求/响应协议的TCP服务。在客户端向服务器发送一个查询请求后,需要监听Socket的返回,该监听过程一直阻塞,直到服务器有结果返回。由于Redis集群是部署在多个服务器上的,所以Redis的请求/响应模型在每次请求时都要跨网络在不同的服务器之间传输数据,这样每次查询都存在一定的网络延迟(服务器之间的网络延迟一般在20ms左右)。由于服务器一般采用多线程处理业务,并且内存操作效率很高,所以如果一次请求延迟20ms,则多次请求的网络延迟会不断累加。也就是说,在分布式环境下,Redis的性能瓶颈主要体现在网络延迟上。
Redis的管道技术指在服务端末响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。管道技术能减少客户端和服务器交互的次数,将客户端的请求批量发送给服务器,服务器针对批量请求分别查询并统一回复,能显著提高Redis的性能。
Redis批量操作实操
Redis的事务
Redis支持分布式环境下的是事务操作,其事务可以一次执行多个命令,事务中的所有命令都会序列化地顺序执行。事务在执行过程中,不会被其他客户端发送来的命令请求打断。服务器在执行完事务中的所有命令后,才会继续处理其他客户端的其他命令。Redis的事务操作分为开启事务、命令入队、执行事务三个阶段。
Redis的事务执行流程:
- 开启事务:客户端执行multi命令开启事务;
- 提交请求:客户端提交命令到事务;
- 任务入队:Redis将客户端的请求放入事务队列中等待执行;
- 入队状态反馈:服务器返回QURUD,表示命令已被放入事务队列;
- 执行命令:客户端通过Exec执行事务;
- 事务执行错误:在Redis事务中如果某条命令执行错误,则其他命令会继续执行,不会回滚。可以通过watch监控事务执行的状态并处理命令执行错误的异常情况。
- 执行结果反馈:服务器向客户端返回事务执行结果。
Redis事务的相关命令有Multi、Exec、Discard、Watch和Unwatch。
命令 | 说明 |
Multi | 标记一个事务块的开始 |
Exec | 执行所有事务块内的命令 |
Discard | 取消事务,放弃执行事务块内的所有命令 |
Watch | 监视一个或多个key,如果在事务执行之前key被修改,那么事务将被打断 |
Unwatch | 取消watch命令对所有key的监视 |
Redis的事务实操
代码块
Redis的发布和订阅
Redis发布、订阅是一种消息通信模式:发送者(Pub)向频道(Channel)发送消息,订阅者(Sub)接收频道上的消息。Redis客户端可以订阅任意数量的频道,发送者也可以向任意频道发送数据。
Redis常用的消息订阅与发布命令:
命令 | 说明 |
PSUBSCRIBE | 订阅一个或多个符合给定模式的频道 |
PUBSUB | 查看订阅 与发布系统的状态 |
PUBLISH | 将信息发送到指定的频道 |
SUBSCRIBE | 订阅给定的一个或多个频道的信息 |
UNSUBSCRIBE | 退订指定的频道 |
Redis集群数据复制的原理
Redis主从复制,即实现在主数据库(Master)中的数据更新后,自动将更新的数据同步到从数据库(Slave)。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
Redis的主从数据复制原理如下:
- 一个从数据库启动后,会向主数据库发送SYNC命令。
- 主数据库在接收到SYNC命令后会开始在后台保存快照(RDB持久化的过程),并将保存快照期间接收到的命令缓存起来。在持久化过程中会生成一个.rdb快照文件。
- 在主数据库快照执行完成后,Redis会将快照文件和所有缓存的命令以.rdb快照文件的形式发送给从数据库。
- 从数据库收到主数据库的.rdb快照文件后,载入该快照文件到本地
- 从数据库执行载入后的.rdb快照文件,将数据写入内存中。该过程称为复制初始化。
- 在复制初始化结束后,主数据库在每次收到写命令时都会将命令同步给从数据库,从而保证主从数据库的一致。
在Redis中开启复制功能时需要在从数据库配置文件中加入以下配置,对主数据库无需进行任何配置:
#slaveof master_address master_port
slaveof 127.0.0.1 9000
#如果master有密码时,则需要配置masterauth
masterauth = 123456
# 需要重启Reids
Redis的持久化
Redis支持RDB和AOF两种持久化方式:
- RDB( Redis DataBase) : RDB在指定的时间间隔内对数据进行快照存储。RDB的特点在于:文件格式紧凑,方便进行数据传输和数据恢复;在保存.rdb快照文件时父进程会fork出一个子进程,由子进程完成具体的持久化工作,所以可以最大化Redis的性能;同时,与AOF相比,在恢复大的数据集时会更快一些。
- AOF(Append Of File):AOF记录对服务器的每次写操作,在Redis重启时会重放这些命令来恢复原数据。AOF命令以Redis协议追加和保存每次写操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。AOF的特点有:可以使用不同的fsync策略(无fsync、每秒fsync、每次写的时候fsync)将操作追加命令到文件中,操作效率高;同时,AOF文件是日志格式,更容易被理解和操作。
Redis的集群模式及工作原理
Redis有三种集群模式:主从模式,哨兵模式和集群模式
- 主从模式:所有的写请求都被发送到主数据库上,再由主数据库将数据同步到从数据库上。主数据库主要用于执行写操作和数据同步,从数据库主要用于执行读操作缓解系统的读压力。
- 哨兵模式:在主从模式上添加一个哨兵角色来监控集群的运行状态。哨兵通过发送命令让Redis服务器返回其运行状态。哨兵是一个独立运行的进程,在检测到Master宕机时会自动将Slave切换成Master,然后通过发布与订阅模式通知其他从服务器修改配置文件,完成主备热切。
- 集群模式:Redis集群实现了在多个Redis节点之间进行数据分片和数据复制。基于Redis集群的数据自动分片能力,我们能够方便的对Redis集群进行横向扩展,以提高Redis集群的吞吐量。基于Redis集群的数据复制能力,在集群中的一部分节点失效或者无法进行通信时,Redis仍然可以基于副本数据对外提供服务,这样提高了集群的可用性。
Redis集群遵循原则:
- 所有Redis节点彼此都通过PING-PONG机制互联,内部使用二进制协议优化传输速度和带宽。
- 在集群中超过半数的节点检测到某个节点Fail后将该节点设置为Fail状态。
- 客户端与Redis节点直连,客户端连接集群中任何一个可用节点即可对集群进行操作
- Redis-Cluster把所有的物理节点都映射到0~~16383的slot(槽)上,Cluster负责维护每个节点上的数据槽的分配。Redis的具体数据分配策略为:在Redis集群中内置了16384个散列槽;在需要在Redis集群中放置key-vlaue时,Redis会先对key使用CRC16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号为0~16383的散列操;Redis会根据节点的数据大致均等地将散列槽映射到不同的节点。
Redis实操
分布式缓存设计的核心问题
缓存预热
缓存预热指在用户请求数据前先将数据加载到缓存系统中,用户查询 事先被预热的缓存数据,以提高系统查询效率。缓存预热一般有系统启动加载、定时加载等方式。
缓存更新
缓存更新指在数据发生变化后及时将变化后的数据更新到缓存中。
常见的缓存更新策略有四种:
- 定时更新:定时将底层数据库的数据更新到缓存中,适用于数据量不大的场景。
- 过期更新:定时将缓存中过期的数据更新为最新数据并更新缓存的过期时间。
- 写请求更新:在用户有写请求时先写数据库同步更新缓存,这适用于用户对缓存和数据的数据有实时强一致性要求的情况。
- 读请求更新:在用户有读请求时,先判断该请求数据的缓存是否存在或过期,如果不存在或已过期,则进行底层数据库查询并将查询结果更新到缓存中,同时将查询结果返回给用户。
缓存淘汰策略
常见的淘汰算法有:
- FIFO先进先出:判断被存储的时间,离目前最远的数据优先被淘汰。
- LRU(Least Recently Used,最近最少使用):判断缓存最近被使用的时间,最旧的数据优先淘汰
- LFU(Least Frequently Used,最不经常使用):在一段时间内,被使用次数最少的缓存优先被淘汰
缓存雪崩
缓存雪崩指在同一时刻由于大量的缓存失效,导致大量的请求都跑去数据库查询,而对数据库的CPU和内存造成巨大压力,严重的话会导致数据库宕机,从而形成一系列连锁反应,使整个系统崩溃。
处理方法:
- 请求加锁:对于并发量不是很多的应用,使用请求加锁排队的方案防止过多请求数据库。
- 失效更新:为每一个缓存数据都增加过期标记来记录缓存数据是否失效,如果缓存标记失效,则更新数据缓存。
- 设置不同的失效时间,防止在同一时刻有大量的数据失效。
缓存击穿
缓存击穿是指由于缓存系统故障或者用户频繁查询系统中不存在(在系统中不存在,在自然数据库和缓存中都不存在)的数据,而这是请求穿过缓存不断被发送到数据库,导致数据库过载,进而引发一系列并发问题。
例如:用户查询username为zhangsan的请求,在系统中不存在这个用户,这样导致请求穿过缓存直接去数据库中查找,由于本身就不存在这个用户,数据库返回空,如果用户频繁查询这个用户,大量的请求会导致数据库过载,进而引发问题。
解决方案:
- 布隆过滤器:指将所有可能存在的数据都映射到一个足够大的bitmap中,在用户发起请求时首先经过布隆过滤器的拦截,不存在的数据首先会被其拦截,从而避免对底层存储系统带来查询上的压力。
- cache null 策略:在缓存中记录一个短时间过期的数据缓存来判断其是否存在的状态,如果不存在,就直接返回null,不再查询数据库,从而避免缓存穿透到数据库上。
缓存降级
缓存降级指由于访问量剧增导致服务出现问题,例如响应时间慢或不想要时,优先保障核心业务的进行,减少和关闭非核心业务对资源的使用。
常见的服务降级策略:
- 写降级:在写请求增大时,可以只进行cache的更新,然后将数据异步更新到数据库中,保证最终一致性,即将写请求从数据库降级为cache。
- 读降级:在数据库服务负载过高或者数据库系统故障时,可以只对Cache进行读取并将结果返回给用户,在数据库服务正常后再去查询数据库,即将读请求从数据库降级为cache。这种方式适用于对数据库实时性要求不高的场景,保障了在系统发生故障的情况下用户依旧能访问到数据,只是访问的数据有相对延迟。