分布式缓存架构
分布式缓存 知识点
jvm内置缓存ehcache
ehcache 的五种集群模式
ehcache + Redis 实现一级缓存、二级缓存
《springboot整合ehcache+redis实现双缓存》
分布式Redis缓存持久化
分布式Redis缓存水平拆分
分布式Redis缓存主从复制
分布式Redis缓存集群模式
分布式Redis缓存穿透、雪崩、效应
基于Redis实现分布式锁
为什么使用缓存、缓存用在什么地方?
使用缓存技术为了减轻服务器压力
怎么解决缓存 与 DB 的一致性
在 更新 和 删除 之后清除缓存、创建定时任务 定时健康检查。
单个 jvm 缓存简单实现
// 单个jvm 缓存简单实现
@Component
public class MapCache<K,V> {
// 缓存容器使用 ConcurrentHashMap不需要考虑线程安全问题
public final Map<K,V> concruent = new ConcurrentHashMap<>();
public V get(K k){
return concruent.get(k);
}
public void put(K k,V v){
concruent.put(k,v);
}
public void remove(K k){
concruent.remove(k);
}
}
springboot 开启 ehcache 缓存
开启缓存 @EnableCaching
使用类注解 @CacheConfig 配置缓存基本信息 缓存名字等
使用方法注解 @Cacheable 方法执行完之后使用缓存
编写 xml 配置文件
使用 EHCache + Redis 实现分布式缓存
分布式缓存实现流程图
注:问题 过期时间如何控制(如何同步呢?)
一级缓存过期时间一定要比二级缓存要短。
定时任务,mq通知。
远程调用 RMI 与 RPC
RMI 与 RPC 理念相同
RMI 输入jvm与 jvm 之间的远程调用不能够 跨平台
RPC 也属于远程调用技术,底层基于 socket 实现,能够跨平台调用
EHCache 集群方式就是 使用的 RMI 的方式实现缓存 同步。
Redis 学习知识点
Redis 默认有16个库 默认连接 第 0 个库
什么是Redis ,Redis是一个非关系行的内存数据库,以key value 的方式进行储存
Redis 以单线程方式存储,既然是单线程那就能保证线程安全。
使用持久化机制可以保证数据高可用。
Redis的应用场景
1.令牌的生成
2.短信验证码
3.热点数据(减轻服务器查询数据库)
4.分布式锁(使用ZK或者Redis实现分布式锁)
5.网站的计数器(因为Redis是单线程,在高并发情况下,能保证全局count的一致性。)
6.使用Redis实现消息中间件(不推荐)发布订阅功能
Redis的五种常用数据类型
string 类型
hash 类型
list 类型
set 类型
zset 类型
springboot2.0 整合 Redis
在 pom.xml 文件中加入 Redis jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application.yml 中编写 Redis 配置
spring:
redis:
database: 0 #表示 连接 第几个库
host: 127.0.0.1 # ip 地址
port: 6739 # 端口号 默认是 6379
password: 123456 # 连接密码
timeout: 10000 # 超时时间
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
Redis 工具分装
@Component
public class RedisUtils {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void setString(String key, String value,Long time){
stringRedisTemplate.opsForValue().set(key,value);
// 设置 key 的过期时间
stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
}
}
Redis 主从复制
1.为什么主从复制
将服务器分为主服务器 和 从服务器,主服务器可以允许做读写的操作,从服务器只允许做读的操作。
2.主从复制应用场景
集群(多台服务器)、读写分离、日志备份、高可用
3.为什么读写分离
读和写分库连接,读一个库,写一个库。互不影响,增加整体吞吐量。
主服务器可以做写,从服务器可以做读。
读写分离产生的 数据同步问题 ,Redis 已经自带 解决。
注:在 Redis 中只能有一个主,可以有多个 从。
《主从服务器 安装及配置》
Redis 高可用哨兵机制
1.哨兵机制:心跳检测、监控、故障转移(选举策略)
2.
Redis 持久化机制 AOF 与 RDB
RDB: 以二进制文件形式,以某个时间点进行存储。
非实时,突然断电,数据可能有一部分丢失。
默认开启 RDB 方式。
AOF:实时日志记录的形式只会记录写的操作,效率低,会影响到整体性能。
AOF比较安全,在突然断电的情况下。
Redis 事务处理机制
Redis集群不支持事务 单节点的Redis 支持事务
事务操作 示例代码
public void setString(String key, String value,Long time){
//开启事务权限
stringRedisTemplate.setEnableTransactionSupport(true);
try {
// 开启事务 相当于 begin
stringRedisTemplate.multi();
stringRedisTemplate.opsForValue().set(key,value);
stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
// 提交事务
stringRedisTemplate.exec();
}catch (Exception e){
// 回滚事务
stringRedisTemplate.discard();
}
}
Redis集群方案
Redis 3.0之前不支持 群集 3.0之后支持集群 ,两个版本差别很大
1.客户端分片 (mycat) 没有故障转移功能
2.主从复制 缺点:数据非常冗余,浪费内存
3.使用代理工具
4. Redis-cluster 集群 Redis 3.0 之后开始实现(Redis 强烈推荐的方式)
Redis 雪崩效应、穿透效应
Redis雪崩效应
雪崩效应的产生,在Redis 突然宕机后 ,所有的访问量直接访问数据库,导致数据库CPU和内存负载过高,甚至宕机。
解决方案:
1.分布式锁(本地锁) (不推荐使用)
2.使用消息中间件(靠谱)消息中间件方式可以解决高并发
3.做 一级和二级缓存(Redis + ehcache)
4.均摊分配 Redis Key 的失效时间 (重点)
Redis 穿透效应
产生原因:客户端随机生成不同的 key,在Redis中没有的 key ,数据库中也没有,导致一直发送jdbc连接,导致雪崩的出现。
解决方案:
1.网关判断客户端传入的key的规则,如果不符合数据库查询规则,直接返回为空。
2.如果没有的 key 数据库也查不到就直接向 Redis 中存放一份为 null 缓存,
基于 Redis 实现分布式锁
什么是分布式锁?
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
解决分布式锁的核心思路:在多台服务器集群中,只保证一个 jvm 进行操作。
分布式锁解决方案
1.采用数据库
2.基于 Redis 实现分布式
3.基于 Zookeeper 实现分布式锁
实现原理:
多个客户端(jvm),使用setnx命令的方式,同时在Redis中创建相同的key,因为临时节点路径是保证唯一,只要谁能够创建节点成功,谁就能获取锁。没有创建成功的,就会进行等待,当释放锁的时候,采用事件通知给客户端(jvm)重新获取锁。
setnx命令也可以存入key,如果存入成功返回 1,如果存入的key已存在则返回 0。
Redis 和 Zookeeper 实现分布式锁的原理是相同的,不同点是 释放锁 Zookeeper 有事件通知
如何释放锁:
在执行完操作之后,删除对应的key ,每个对应的key 都有自己的过期时间,防止删除失败出现死锁现象。
Redis 实现分布式锁 有两个超时时间的问题
1.在获取锁之前的超时时间----在尝试获取锁的时候,如果在规定的时间内没有获取到,就直接放弃
2.在获取锁之后的超时时间----当获取锁成功之后,对应的key 有对应的失效时间,在规定时间内失效
分布式锁 实例代码
public class LockRedis {
// redis线程池
private JedisPool jedisPool;
// 同时在redis上创建相同的一个key 相同key 名称
private String redislockKey = "redis_lock";
public LockRedis(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// redis 以key (redislockKey) 和value(随机不能够重复数字 锁的id)方式进行存储
// redis实现分布式锁 有两个超时 时间问题
/**
* 两个超时时间含义:<br>
* 1.在获取锁之前的超时时间----在尝试获取锁的时候,如果在规定的时间内还没有获取锁,直接放弃。<br>
* 2.在获取锁之后的超时时间---当获取锁成功之后,对应的key 有对应有效期,对应的key 在规定时间内进行失效
*/
// 基于redis实现分布式锁代码思路 核心方法 获取锁 、释放锁
public String getRedisLock(Long acquireTimeout, Long timeOut) {
Jedis conn = null;
try {
// 1.建立redis连接
conn = jedisPool.getResource();
// 2.定义 redis 对应key 的value值( uuid) 作用 释放锁 随机生成value
String identifierValue = UUID.randomUUID().toString();
// 3.定义在获取锁之后的超时时间
int expireLock = (int) (timeOut / 1000);// 以秒为单位
// 4.定义在获取锁之前的超时时间
// 5.使用循环机制 如果没有获取到锁,要在规定acquireTimeout时间 保证重复进行尝试获取锁(乐观锁)
// 使用循环方式重试的获取锁
Long endTime = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < endTime) {
// 获取锁
// 6.使用setnx命令插入对应的redislockKey ,如果返回为1 成功获取锁
if (conn.setnx(redislockKey, identifierValue) == 1) {
// 设置对应key的有效期
conn.expire(redislockKey, expireLock);
return identifierValue;
}
// 为什么获取锁之后,还要设置锁的超时时间 目的是为了防止死锁
// zookeeper实现分布式锁通过什么方式 防止死锁 设置session 有效期
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return null;
}
// 如果直接使用 conn.del(redislockKey); 保证对应是自己的创建redislockKey 删除对应自己的。
// 释放redis锁
public void unRedisLock(String identifierValue) {
Jedis conn = null;
// 1.建立redis连接
conn = jedisPool.getResource();
try {
// 如果该锁的id 等于identifierValue 是同一把锁情况才可以删除
if (conn.get(redislockKey).equals(identifierValue)) {
System.out.println("释放锁..." + Thread.currentThread().getName() + ",identifierValue:" + identifierValue);
conn.del(redislockKey);
}
} catch (Exception e){
} finally {
if (conn != null) {
conn.close();
}
}
// 释放锁有两种 key自动有有效期
// 整个程序执行完毕情况下,删除对应key
}
}