目录
6.2.1 在service⼦⼯程添加Spring data redis依赖
6.2.2 在application.yml配置redis数据源
6.3.3 在ProductServiceImpl中修改业务代码
11.2.2 在需要只⽤⽤户信息的位置,直接根据token从redis查询
⼀、Redis介绍
1.1 项⽬问题
- 数据库访问压⼒:为了降低对数据库的访问压⼒,当多个⽤户请求相同的数据时,我们 可以将第⼀次从数据库查询到数据进⾏缓存(存储在内存中),以减少对数据库的访问次数
- ⾸⻚数据的加载效率:将⼤量的且不经常改变的数据缓存在内容中,可以⼤幅度提⾼访 问速度
- 集群部署下的商品超卖:分布式事务
- ⽤户登录:分布式会话
1.2 Redis介绍
1.2.1 Redis的产⽣背景
2008年 萨尔瓦多
——
开发⼀个进⾏⽹站实时统计软件项⽬(
LLOOGG
)
,
项⽬的实时统计功能需要频繁的进⾏数据库的读写(
对数据库的读写要求很⾼
—
数千次
/s)
,
MySQL
满⾜不了项⽬的需求,萨尔瓦多就使⽤C
语⾔⾃定义了⼀个数据存储系统
—Redis
。后来萨尔瓦多不满⾜仅仅在LLOOGG
这个项⽬中使⽤
redis
,就对
redis
进⾏
产品化
并进⾏开源,以便 让更多的⼈能够使⽤。
1.2.2 Redis使⽤
Redis
就是⼀个⽤
C
语⾔开发的、基于内存结构进⾏
键值对
数据存储的、⾼性能的、⾮关系型
NoSQL
数据库
1.2.3 Redis⽀持的数据类型
redis
是基于键值对进⾏数据存储的,但是
value
可以是多种数据类型:
- string 字符串
- hash 映射
- list 列表(队列)
- set 集合
- zset ⽆序集合
1.2.4 Redis特点
- 基于内存存储,数据读写效率很⾼
- Redis本身⽀持持久化
- Reids虽然基于key-value存储,但是⽀持多种数据类型
- Redis⽀持集群、⽀持主从模式
1.3 Redis应⽤场景
- 缓存:在绝⼤多数的互联⽹项⽬中,为了提供数据的访问速度、降低数据库的访问压 ⼒,我们可以使⽤redis作为缓存来实现
- 点赞、排⾏榜、计数器等功能:对数据实时读写要求⽐较⾼,但是对数据库的⼀致性要求并不是太⾼的功能场景
- 分布式锁:基于redis的操作特性可以实现分布式锁功能
- 分布式会话:在分布式系统中可以使⽤redis实现 session (共享缓存)
- 消息中间件:可以使⽤redis实现应⽤之间的通信
1.4 Redis的优缺点
1.4.1 优点
- redis是基于内存结构,性能极⾼(读 110000次/秒,写 81000次/秒)
- redis基于键值对存储,但是⽀持多种数据类型
- redis的所有操作都是原⼦性,可以通过lua脚本将多个操作合并为⼀个院⼦操作(Redis 的事务)
- reids是基于单线程操作,但是其多路复⽤实现了⾼性能读写
1.4.2 缺点
- 缓存数据与数据库数据必须通过两次写操作才能保持数据的⼀致性
- 使⽤缓存会存在缓存穿透、缓存击穿及缓存雪崩等问题,需要处理
- redis可以作为数据库使⽤进⾏数据的持久存储,存在丢失数据的⻛险
⼆、Redis安装及配置
2.1 Redis安装
基于linux环境安装redis
2.1.1 下载Redis
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
2.1.2 安装redis
- 安装gcc
yum -y install gcc
- 解压redis安装包
tar -zxvf redis-5.0.5.tar.gz
- 解压之后进⼈到redis-5.0.5⽬录
cd redis-5.0.5
- 编译
make MALLOC=libc
- 安装
make install
- 启动redis
## 当我们完成 redis 安装之后,就可以执⾏ redis 相关的指令redis-server ## 启动 redis 服务redis-server &
- 打开客户端
redis-cli ## 启动redis操作客户端(命令⾏客户端)
2.2 Redis配置
- 使⽤ redis-server 指令启动redis服务的时候,可以在指令后添加redis配置⽂件的路径,以设置redis是以何种配置进⾏启动
redis-server redis-6380.conf & ## redis 以 redis-6380.conf ⽂件中的配置来启动
- 如果不指定配置⽂件的名字,则按照redis的默认配置启动(默认配置≠redis.conf)
- 我们可以通过创建redis根⽬录下 redis.conf 来创建多个配置⽂件,启动多个redis服务
redis-server redis-6380.conf &redis-server redis-6381.conf &
常⽤
redis
配置
## 设置 redis 实例(服务)为守护模式 , 默认值为 no ,可以设置为 yesdaemonize no## 设置当前 redis 实例启动之后保存进程 id 的⽂件路径pidfile /var/run/redis_6379.pid## 设置 redis 实例的启动端⼝(默认 6379 )port 6380## 设置当前 redis 实例是否开启保护模式protected-mode yes## 设置允许访问当前 redis 实例的 ip 地址列表bind 127.0.0.1## 设置连接密码requirepass 123456## 设置 redis 实例中数据库的个数(默认 16 个,编号 0-15 )databases 16## 设置最⼤并发数量maxclients## 设置客户端和 redis 建⽴连接的最⼤空闲时间,设置为 0 表示不限制timeout 0
三、Redis基本使⽤
3.1 Redis存储的数据结构
Redis
是以键值对形式进⾏数据存储的,但是
value
⽀持多种数据类型
![](https://i-blog.csdnimg.cn/blog_migrate/f99627f75a4195ebaf1c1b2c3a9749eb.png)
3.2 string常⽤指令
## 设置值 / 修改值 如果 key 存在则进⾏修改set key value## 取值get key## 批量添加mset k1 v1 [k2 v2 k3 v3 ...]## 批量取值mget k1 [k2 k3...]## ⾃增和⾃减incr key ## 在 key 对应的 value 上⾃增 +1decr key ## 在 key 对应的 value 上⾃减 -1incrby key v ## 在 key 对应的 value 上 +vdecrby key v ## 在 key 对应的 value 上 -v## 添加键值对,并设置过期时间 (TTL)setex key time(seconds) value## 设置值,如果 key 不存在则成功添加,如果 key 存在则添加失败(不做修改操作)setnx key value## 在指定的 key 对应 value 拼接字符串append key value## 获取 key 对应的字符串的⻓度strlen key
3.3 hash常⽤指令
## 向 key 对应的 hash 中添加键值对hset key field value## 从 key 对应的 hash 获取 field 对应的值hget key field## 向 key 对应的 hash 结构中批量添加键值对hmset key f1 v1 [f2 v2 ...]## 从 key 对应的 hash 中批量获取值hmget key f1 [f2 f3 ...]## 在 key 对应的 hash 中的 field 对应 value 上加 vhincrby key field v## 获取 key 对应的 hash 中所有的键值对hgetall key## 获取 key 对应的 hash 中所有的 fieldhkeys key## 获取 key 对应的 hash 中所有的 valuehvals key## 检查 key 对应的 hash 中是否有指定的 fieldhexists key field## 获取 key 对应的 hash 中键值对的个数hlen key## 向 key 对应的 hash 结构中添加 f-v, 如果 field 在 hash 中已经存在,则添加失败hsetnx key field value
3.4 list常⽤指令
## 存储数据lpush key value # 在 key 对应的列表的左侧添加数据 valuerpuhs key value # 在 key 对应的列表的右侧添加数据 value## 获取数据lpop key # 从 key 对应的列表的左侧取⼀个值rpop key # 从 key 对应的列表的右侧取⼀个值## 修改数据lset key index value # 修改 key 对应的列表的索引位置的数据(索引从左往右,从 0 开始)## 查看 key 对应的列表中索引从 start 开始到 stop 结束的所有值lrange key start stop## 查看 key 对应的列表中 index 索引对应的值lindex key index## 获取 key 对应的列表中的元素个数llen key## 从 key 对应的列表中截取 key 在 [start,stop] 范围的值,不在此范围的数据⼀律被清除掉ltrim key start stop## 从 k1 右侧取出⼀个数据存放到 k2 的左侧rpoplpush k1 k2
3.5 set常⽤指令
## 存储元素 :在 key 对应的集合中添加元素,可以添加 1 个,也可以同时添加多个元素sadd key v1 [v2 v3 v4...]## 遍历 key 对应的集合中的所有元素smembers key## 随机从 key 对于听的集合中获取⼀个值(出栈)spop key## 交集sinter key1 key2## 并集sunion key1 key2## 差集sdiff key1 key2## 从 key 对应的集合中移出指定的 valuesrem key value## 检查 key 对应的集合中是否有指定的 valuesismember key value
3.6 zset常⽤指令
zset
有序不可重复集合
z
## 存储数据 (score 存储位置必须是数值,可以是 float 类型的任意数字; member 元素不允许重复 )zadd key score member [score member...]## 查看 key 对应的有序集合中索引 [start,stop] 数据 —— 按照 score 值由⼩到⼤( start 和stop 指的不是 score ,⽽是元素在有序集合中的索引)zrange key start top## 查看 member 元素在 key 对应的有序集合中的索引zscore key member## 获取 key 对应的 zset 中的元素个数zcard key## 获取 key 对应的 zset 中, score 在 [min,max] 范围内的 member 个数zcount key min max## 从 key 对应的 zset 中移除指定的 memberzrem key6 member## 查看 key 对应的有序集合中索引 [start,stop] 数据 —— 按照 score 值由⼤到⼩zrevrange key start stop
3.7 key
相关指令
## 查看 redis 中满⾜ pattern 规则的所有的 key ( keys * )keys pattern## 查看指定的 key 谁否存在exists key## 删除指定的 key-value 对del key## 获取当前 key 的存活时间 ( 如果没有设置过期返回 -1 ,设置过期并且已经过期返回 -2)ttl key## 设置键值对过期时间expire key secondspexpire key milliseconds## 取消键值对过期时间persist key
3.8 db常⽤指令
redis
的键值对是存储在数据库中的
——db
redis
中默认有
16
个
db
,编号
0-15
## 切换数据库select index## 将键值对从当前 db 移动到⽬标 dbmove key index## 清空当前数据库数据flushdb## 清所有数据库的 k-vflushall## 查看当前 db 中 k-v 个数dbsize## 获取最后⼀次持久化操作时间lastsave
四、Redis的持久化 [重点]
Redis
是基于内存操作,但作为⼀个数据库也具备数据的持久化能⼒;但是为了实现⾼效
的读写操作,并不会即时进⾏数据的持久化,⽽是按照⼀定的规则进⾏持久化操作的
——
持久化策略
Redis
提供了
2
中持久化策略:
- RDB (Redis DataBase)
- AOF(Append Only File)
4.1 RDB
在满⾜特定的
redis
操作条件时,将内存中的数据以数据快照的形式存储到
rdb
⽂件中
![](https://i-blog.csdnimg.cn/blog_migrate/66ad31a7023acebca83e628807999e8b.png)
- 原理:
RDB是
redis
默认的持久化策略,当
redis
中的写操作达到指定的次数、同时距离上⼀次持
久化达到指定的时间就会将redis
内存中的数据⽣成数据快照,保存在指定的
rdb
⽂件中。
- 默认触发持久化条件:
- 900s 1次:当操作次数达到1次,900s就会进⾏持久化
- 300s 10次:当操作次数达到10次,300s就会进⾏持久化
- 60s 10000次:当操作次数达到10000次,60s就会就⾏持久化
- 我们可以通过修改redis.conf⽂件,来设置RDB策略的触发条件:
## rdb 持久化开关rdbcompression yes## 配置 redis 的持久化策略save 900 1save 300 10save 60 10000## 指定 rdb 数据存储的⽂件dbfilename dump.rdb
- RED持久化细节分析:
缺点
如果redis出现故障,存在数据丢失的⻛险,丢失上⼀次持久化之后的操作数据;
RDB采⽤的是数据快照形式进⾏持久化,不适合实时性持久化;
如果数据量巨⼤,在RDB
持久化过程中⽣成数据快照的⼦进程执⾏时间过⻓,会导致
redis卡顿,因此
save
时间周期设置不宜过短;
优点
但是在数据量较⼩的情况下,执⾏速度⽐较快;
由于RDB
是以数据快照的形式进⾏保存的,我们可以通过拷⻉
rdb
⽂件轻松实现
redis
数据移植
4.2 AOF
Apeend Only File
,当达到设定触发条件时,将
redis
执⾏的写操作指令存储在
aof
⽂件
中,
Redis
默认未开启
aof
持久化
![](https://i-blog.csdnimg.cn/blog_migrate/7d69b6df9f25b5d6adce98bb7e163fcb.png)
- 原理:
Redis将每⼀个成功的写操作写⼊到
aof
⽂件中,当
redis
重启的时候就执⾏
aof
⽂件中的指令以恢复数据
- 配置:
## 开启 AOFappendonly yes## 设置触发条件(三选⼀)appendfsync always ## 只要进⾏成功的写操作,就执⾏ aofappendfsync everysec ## 每秒进⾏⼀次 aofappendfsync no ## 让 redis 执⾏决定 aof## 设置 aof ⽂件路径appendfilename "appendonly.aof"
- AOF细节分析:
- 也可以通过拷⻉aof⽂件进⾏redis数据移植
- aof存储的指令,⽽且会对指令进⾏整理;⽽RDB直接⽣成数据快照,在数据量不⼤ 时RDB⽐较快
- aof是对指令⽂件进⾏增量更新,更适合实时性持久化
- redis官⽅建议同时开启2中持久化策略,如果同时存在aof⽂件和rdb⽂件的情况下 aof优先
五、Java应⽤连接Redis
5.1 设置redis允许远程连接
Java
应⽤连接
Redis
,⾸先要将我们的
Redis
设置允许远程连接
- 修改redis-6379.conf
## 开启 AOFappendonly yes## 设置触发条件(三选⼀)appendfsync always ## 只要进⾏成功的写操作,就执⾏ aofappendfsync everysec ## 每秒进⾏⼀次 aofappendfsync no ## 让 redis 执⾏决定 aof## 设置 aof ⽂件路径appendfilename "appendonly.aof"
- 重启redis
redis-server redis-6379.conf
- 阿⾥云安全组设置放⾏6379端⼝
5.1 在普通Maven⼯程连接Redis
使⽤
jedis
客户端连接
5.1.1 添加Jedis依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
5.1.2 使⽤案例
public static void main(String[] args) {
Product product = new Product("101", "娃哈哈AD钙奶", 2.5);
//1.连接redis
Jedis jedis = new Jedis("47.96.11.185", 6379);
//2.操作
String s = jedis.set(product.getProductId(), new
Gson().toJson(product));
System.out.println(s);
//3.关闭连接
jedis.close();
}
5.1.3 redis远程可视化客户端
Redis desktop manager
5.2 在SpringBoot⼯程连接Redis
Spring Data Redis , part of the larger Spring Data family, provides easyconfiguration and access to Redis from Spring applications. It offers both low-leveland high-level abstractions for interacting with the store, freeing the user frominfrastructural concerns.Spring Data Redis 依赖中,提供了⽤于连接 redis 的客户端:
- RedisTemplate
- StringRedisTemplate
5.2.1 创建springBoot应⽤
5.2.2 配置redis
application.yml
⽂件配置
redis
的连接信息
spring :redis :host : 47.96.11.185port : 6379database : 0password : 123456
5.2.3 使⽤redis客户端连接redis
直接在
service
中注⼊
RedisTemplate
或者
StringRedisTemplate
,就可以使⽤此对象完 成redis操作
5.3 Spring Data Redis
5.3.1 不同数据结构的添加操作
//1.string
//添加数据 set key value
stringRedisTemplate.boundValueOps(product.getProductId()).set(
jsonstr);
//2.hash
stringRedisTemplate.boundHashOps("products").put(product.getProductId()
,jsonstr);
//3.list
stringRedisTemplate.boundListOps("list").leftPush("ccc");
//4.set
stringRedisTemplate.boundSetOps("s1").add("v2");
//5.zset
stringRedisTemplate.boundZSetOps("z1").add("v1",1.2);
5.3.2 string类型的操作⽅法
//添加数据 set key value
stringRedisTemplate.boundValueOps(product.getProductId()).set(
jsonstr);
//添加数据时指定过期时间 setex key 300 value
stringRedisTemplate.boundValueOps("103").set(jsonstr,300);
//设置指定key的过期时间 expire key 20
stringRedisTemplate.boundValueOps("103").expire(20, TimeUnit.SECONDS);
//添加数据 setnx key value
Boolean absent =
stringRedisTemplate.boundValueOps("103").setIfAbsent(jsonstr);
5.3.3 不同数据类型的取值操作
//string
String o = stringRedisTemplate.boundValueOps("103").get();
//hash
Object v = stringRedisTemplate.boundHashOps("products").get("101");
//list
String s1 = stringRedisTemplate.boundListOps("list").leftPop();
String s2 = stringRedisTemplate.boundListOps("list").rightPop();
String s3 = stringRedisTemplate.boundListOps("list").index(1);
//set
Set<String> vs = stringRedisTemplate.boundSetOps("s1").members();
//zset
Set<String> vs2 = stringRedisTemplate.boundZSetOps("z1").range(0, 5);
六、使⽤redis缓存数据库数据
6.1 redis作为缓存的使⽤流程
6.2 在使⽤redis缓存商品详情
6.2.1 在service⼦⼯程添加Spring data redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
6.2.2 在application.yml配置redis数据源
spring :datasource :druid :driver-class-name : com.mysql.jdbc.Driver## 如果后端项⽬服务器和数据库服务器不在同⼀台主机,则需要修改 localhost 为数据库服务器 ip 地址url : jdbc : mysql : //localhost : 3306/fmmall2?characterEncoding=utf-8username : rootpassword : admin123redis :port : 6379host : 47.96.11.185database : 0password : 123456
6.3.3 在ProductServiceImpl中修改业务代码
七、使⽤Redis做缓存使⽤存在的问题 [重点]
使⽤
redis
做为缓存在⾼并发场景下有可能出现缓存击穿、缓存穿透、缓存雪崩等问题
7.1 缓存击穿
缓存击穿:⼤量的并发请求同时访问同⼀个在
redis
中不存在的数据,就会导致⼤量的请
求绕过
redis
同时并发访问数据库,对数据库造成了⾼并发访问压⼒。
- 使⽤ 双重检测锁 解决 缓存击穿 问题
@Service
public class IndexImgServiceImpl implements IndexImgService {
@Autowired
private IndexImgMapper indexImgMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private ObjectMapper objectMapper = new ObjectMapper();
public ResultVO listIndexImgs() {
List<IndexImg> indexImgs = null;
try {
//1000个并发请求,请求轮播图
String imgsStr =
stringRedisTemplate.boundValueOps("indexImgs").get();
//1000个请求查询到redis中的数据都是null
if (imgsStr != null) {
// 从redis中获取到了轮播图信息
JavaType javaType = objectMapper.getTypeFactory()
.constructParametricType(ArrayList.class,
IndexImg.class);
indexImgs = objectMapper.readValue(imgsStr, javaType);
} else {
// 1000个请求都会进⼊else
// (service类在spring容器中是单例的,
// 1000个并发会启动1000个线程来处理,但是公⽤⼀个service实 例)
synchronized (this){
// 第⼆次查询redis
String s =
stringRedisTemplate.boundValueOps("indexImgs").get();
if(s == null){
// 这1000个请求中,只有第⼀个请求再次查询redis时依然为
null
indexImgs = indexImgMapper.listIndexImgs();
System.out.println("----------------查询数据
库");
stringRedisTemplate.boundValueOps("indexImgs")
.set(objectMapper.writeValueAsString(indexImgs));
stringRedisTemplate.boundValueOps("indexImgs")
.expire(1, TimeUnit.DAYS);
}else{
JavaType javaType =
objectMapper.getTypeFactory()
.constructParametricType(ArrayList.class,
IndexImg.class);
indexImgs = objectMapper.readValue(s,
javaType);
}
}
}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//返回数据
if(indexImgs != null){
return new ResultVO(ResStatus.OK,"success",indexImgs);
}else{
return new ResultVO(ResStatus.NO,"fail",null);
}
}
}
7.2 缓存穿透
缓存穿透:⼤量的请求⼀个数据库中不存在的数据,⾸先在
redis
中⽆法命中,最终所有
的请求都会访问数据库,同样会导致数据库承受巨⼤的访问压⼒。
- 解决⽅案:当从数据库查询到⼀个null时,写⼀个⾮空的数据到redis,并设置过期时间
![](https://i-blog.csdnimg.cn/blog_migrate/352f910bf7390d8680c906516b7c9e16.png)
indexImgs = indexImgMapper.listIndexImgs();
if(indexImgs != null) {
String s = objectMapper.writeValueAsString(indexImgs);
stringRedisTemplate.boundValueOps("indexImgs").set(s);
stringRedisTemplate.boundValueOps("indexImgs").expire(1,
TimeUnit.DAYS);
}else{
//当从数据库查询数据为null时,保存⼀个⾮空数据到redis,并设置过期时间
stringRedisTemplate.boundValueOps("indexImgs").set("[]");
stringRedisTemplate.boundValueOps("indexImgs").expire(10,
TimeUnit.SECONDS);
}
7.3
缓存雪崩
缓存雪崩:缓存⼤量的数据集中过期,导致请求这些数据的⼤量的并发请求会同时访问
数据库
解决⽅案:
- 将缓存中的数据设置成不同的过期时间
- 在访问洪峰到达前缓存热点数据,过期时间设置到流量最低的时段
7.4 Jmeter测试
Jmeter
是基于
Java
开发的⼀个测试⼯具,因此需要先安装
JDK
7.4.1 创建测试计划
7.4.2 创建线程组
7.4.3 设置HTTP请求
⼋、Redis⾼级应⽤
使⽤
redis
作为缓存数据库使⽤⽬的是为了提升数据加载速度、降低对数据库的访问压
⼒,我们需要保证
redis
的可⽤性。
- 主从配置
- 哨兵模式
- 集群配置
8.1 主从配置
主从配置:在多个
redis
实例建⽴起主从关系,当
主
redis
中的数据发⽣变化,
从
redis
中的数据也会同步变化。
- 通过主从配置可以实现redis数据的备份( 从redis 就是对 主redis 的备份),保证 数据的安全性;
- 通过主从配置可以实现redis的读写分离
主从配置示例
- 启动三个redis实例
## 在 redis-5.0.5 ⽬录下创建 msconf ⽂件夹[root@theo redis-5.0.5] # mkdir msconf## 拷⻉ redis.conf ⽂件 到 msconf ⽂件夹 ---> redis-master.conf[root@theo redis-5.0.5] # cat redis.conf |grep -v "#" | grep -v "^$"> msconf/redis-master.conf## 修改 redis-master.conf 端⼝及远程访问设置[root@theo msconf] # vim redis-master.conf
![](https://i-blog.csdnimg.cn/blog_migrate/fc59009974383c6741ae8d7f3822b48c.png)
## 将 redis-master.conf 拷⻉两份分别为: redis-slave1.conf redisslave2.conf[root@theo msconf] # sed 's/6380/6381/g' redis-master.conf > redisslave1.conf[root@theo msconf] # sed 's/6380/6382/g' redis-master.conf > redisslave2.conf## 修改 redis-slave1.conf redis-slave2.conf ,设置 “ 跟从 ”---127.0.0.16380[root@theo msconf] # vim redis-slave1.conf[root@theo msconf] # vim redis-slave2.conf
![](https://i-blog.csdnimg.cn/blog_migrate/adb92312ca63e45085fd59826119d35a.png)
## 启动三个redis实例
[root@theo msconf] # redis-server redis-master.conf &[root@theo msconf] # redis-server redis-slave1.conf &[root@theo msconf] # redis-server redis-slave2.conf &
8.2 哨兵模式
哨兵模式:⽤于监听主库,当确认主库宕机之后,从备库
(
从库
)
中选举⼀个转备为主
![](https://i-blog.csdnimg.cn/blog_migrate/2fc0d7a7aca48e4e32479f5ada184bed.png)
哨兵模式配置
## ⾸先实现三个 redis 实例之间的主从配置(如上)## 创建并启动三个哨兵## 拷⻉ sentinel.conf ⽂件三份: sentinel-26380.conf sentinel-26382.confsentinel-26382.conf## 创建 sentinelconf ⽬录[root@theo redis-5.0.5] # mkdir sentinelconf## 拷⻉ sentinel.conf ⽂件到 sentinelconf ⽬录: sentinel-26380.conf[root@theo redis-5.0.5] # cat sentinel.conf | grep -v "#" | grep -v "^$"> sentinelconf/sentinel-26380.conf[root@theo redis-5.0.5] # cd sentinelconf/[root@theo sentinelconf] # lltotal 4-rw-r--r-- 1 root root 326 May 19 17 :09 sentinel-26380.conf## 编辑 sentinelconf/sentinel-26380.conf ⽂件[root@theo sentinelconf] # vim sentinel-26380.con
port 26380daemonize nopidfile "/var/run/redis-sentinel-26380.pid"logfile ""dir "/tmp"sentinel deny-scripts-reconfig yes# 此处配置默认的主库的 ip 和端⼝ 最后的数字是哨兵数量的⼀半多⼀个sentinel monitor mymaster 127 .0.0.1 6380 2sentinel config-epoch mymaster 1sentinel leader-epoch mymaster 1protected-mode no
[root@theo sentinelconf] # sed 's/26380/26381/g' sentinel-26380.conf >sentinel-26381.conf[root@theo sentinelconf] # sed 's/26380/26382/g' sentinel-26380.conf >sentinel-26382.conf
测试:
启动 主 redis启动 备 1redis启动 备 2redis再依次启动三个哨兵:[root@theo sentinelconf] # redis-sentinel sentinel-26380.conf
8.3 集群配置
⾼可⽤:保证
redis
⼀直处于可⽤状态,即时出现了故障也有备⽤⽅案保证可⽤性
⾼并发:⼀个
redis
实例已经可以⽀持多达
11w
并发读操作或者
8.1w
并发写操作;但是如
果对于有更⾼并发需求的应⽤来说,我们可以通过
读写分离
、
集群配置
来解决⾼并发问
题
![](https://i-blog.csdnimg.cn/blog_migrate/cc82e78069748e2453388fb2dddf2ba5.png)
Redis
集群
- Redis集群中每个节点是对等的,⽆中⼼结构
- 数据按照slots分布式存储在不同的redis节点上,节点中的数据可共享,可以动态调整数 据的分布
- 可扩展性强,可以动态增删节点,最多可扩展⾄1000+节点
- 集群每个节点通过主备(哨兵模式)可以保证其⾼可⽤性
8.3.1 集群搭建
[root@theo ~] # cd /usr/local/redis-5.0.5[root@theo redis-5.0.5] # mkdir cluster-conf[root@theo redis-5.0.5] # cat redis.conf | grep -v "#"|grep -v "^$" >cluster-conf/redis-7001.conf[root@theo redis-5.0.5] # cd cluster-conf/[root@theo cluster-conf] # lsredis-7001.conf[root@theo cluster-conf] # vim redis-7001.conf
![](https://i-blog.csdnimg.cn/blog_migrate/7eb2c3b232257762b0eeaef3c0a7071b.png)
- 拷⻉6个⽂件,端⼝分别为 7001-7006
[root@theo cluster-conf] # sed 's/7001/7002/g' redis-7001.conf > redis-7002.conf[root@theo cluster-conf] # sed 's/7001/7003/g' redis-7001.conf > redis-7003.conf[root@theo cluster-conf] # sed 's/7001/7004/g' redis-7001.conf > redis-7004.conf[root@theo cluster-conf] # sed 's/7001/7005/g' redis-7001.conf > redis-7005.conf[root@theo cluster-conf] # sed 's/7001/7006/g' redis-7001.conf > redis-7006.conf
- 启动6个redis实例
[root@theo cluster-conf] # redis-server redis-7001.conf &[root@theo cluster-conf] # redis-server redis-7002.conf &[root@theo cluster-conf] # redis-server redis-7003.conf &[root@theo cluster-conf] # redis-server redis-7004.conf &[root@theo cluster-conf] # redis-server redis-7005.conf &[root@theo cluster-conf] # redis-server redis-7006.conf &
- 查看6个实例是否启动
[root@theo cluster-conf] # ps -ef|grep redisroot 4789 1 0 10 :20 ? 00 :00:00 redis-server *:7001[cluster]root 4794 1 0 10 :20 ? 00 :00:00 redis-server *:7002[cluster]root 4799 1 0 10 :20 ? 00 :00:00 redis-server *:7003[cluster]root 4806 1 0 10 :21 ? 00 :00:00 redis-server *:7004[cluster]root 4811 1 0 10 :21 ? 00 :00:00 redis-server *:7005[cluster]root 4816 1 0 10 :21 ? 00 :00:00 redis-server *:7006[cluster]
- 启动集群
[root@theo cluster-conf] # redis-cli --cluster create 47.96.11.185:700147.96.11.185:7002 47.96.11.185:7003 47.96.11.185:7004 47.96.11.185:700547.96.11.185:7006 --cluster-replicas 1
- 连接集群:
[root@theo cluster-conf]# redis-cli -p 7001 -c
8.3.2 集群管理
- 如果集群启动失败:等待节点加⼊
1.
云服务器检查安全组是否放⾏
redis
实例端⼝,以及
+10000
的端⼝
2. Linux
防⽕墙是否放⾏
redis
服务(关闭防⽕墙)
3. Linux
状态(
top
)
----
更换云主机操作系统
4. redis
配置⽂件错误
- 创建集群:
[root@theo cluster-conf] # redis-cli --cluster create47.96.11.185:7001 47.96.11.185:7002 47.96.11.185:700347.96.11.185:7004 47.96.11.185:7005 47.96.11.185:7006 --clusterreplicas 1
- 查看集群状态
[root@theo cluster-conf] # redis-cli --cluster info 47.96.11.185:700147 .96.11.185:7001 (4678478a...) - > 2 keys | 5461 slots | 1 slaves.47 .96.11.185:7002 (e26eaf2a...) - > 0 keys | 5462 slots | 1 slaves.47 .96.11.185:7003 (5752eb20...) - > 1 keys | 5461 slots | 1 slaves.[OK] 3 keys in 3 masters.0 .00 keys per slot on average.
- 平衡节点的数据槽数
[root@theo cluster-conf] # redis-cli --cluster rebalance47.96.11.185:7001>>> Performing Cluster Check (using node 47 .96.11.185:7001)[OK] All nodes agree about slots configuration.>>> Check for open slots...>>> Check slots coverage...[OK] All 16384 slots covered.*** No rebalancing needed! All nodes are within the 2 .00% threshold.
- 迁移节点槽
- 删除节点
[root@theo cluster-conf] # redis-cli --cluster del-node47.96.11.185:7001 4678478aa66b6d37b23944cf7db0ac07298538a4>>> Removing node 4678478aa66b6d37b23944cf7db0ac07298538a4 fromcluster 47 .96.11.185:7001>>> Sending CLUSTER FORGET messages to the cluster...>>> SHUTDOWN the node .[root@theo cluster-conf] # redis-cli --cluster info 47.96.11.185:700247 .96.11.185:7002 (e26eaf2a...) - > 1 keys | 8192 slots | 2 slaves.47 .96.11.185:7003 (5752eb20...) - > 2 keys | 8192 slots | 1 slaves.[OK] 3 keys in 2 masters.0 .00 keys per slot on average.
- 添加节点
[root@theo cluster-conf] # redis-cli --cluster add-node47.96.11.185:7007 47.96.11.185:7002
8.3.3 SpringBoot应⽤连接集群
- 添加依赖
<dependency><groupId> org.springframework.boot </groupId><artifactId> spring-boot-starter-data-redis </artifactId></dependency>
- 配置集群节点
spring :redis :cluster :nodes : 47.96.11.185 : 7001,47.96.11.185 : 7002,47.96.11.185 : 7003max-redirects : 3
- 操作集群
@RunWith ( SpringRunner . class )@SpringBootTest ( classes = RedisDemo3Application . class )class RedisDemo3ApplicationTests {@Autowiredprivate StringRedisTemplate stringRedisTemplate ;@Testvoid contextLoads () {//stringRedisTemplate.boundValueOps("key1").set("value1");String s = stringRedisTemplate . boundValueOps ( "key1" ). get ();System . out . println ( s );}}
九、Redis淘汰策略
Redis
是基于内存结构进⾏数据缓存的,当内存资源消耗完毕,想要有新的数据缓存进
来,必然要从
Redis
的内存结构中释放⼀些数据。如何进⾏数据的释放呢?
----Redis
的淘
汰策略
Redis
提供的
8
中淘汰策略
# volatile-lru -> 从设置了过期时间的数据中淘汰最久未使⽤的数据 .# allkeys-lru -> 从所有数据中淘汰最久未使⽤的数据 .# volatile-lfu -> 从设置了过期时间的数据中淘汰使⽤最少的数据 .# allkeys-lfu -> 从所有数据中淘汰使⽤最少的数据 .# volatile-random -> 从设置了过期时间的数据中随机淘汰⼀批数据 .# allkeys-random -> 从所有数据中随机淘汰⼀批数据 .# volatile-ttl -> 淘汰过期时间最短的数据 .# noeviction -> 不淘汰任何数据,当内存不够时直接抛出异常 .
- 理解两个算法名词:
LRU 最久最近未使⽤
LFU 最近最少使⽤
⼗、Redis⾼频⾯试题
1.
在项⽬中
redis
的使⽤场景
⽤于缓存⾸⻚数据
...
2. Redis
的持久化策略
3. Redis
⽀持的数据类型
4.
如何保证
redis
的⾼可⽤
- Redis⽀持持久化,同时开启rdb 和 aof ,以保证数据的安全性(还是存在数据丢失⻛险的)
- Redis⽀持主从配置,我们可通配置哨兵,实现主备配置,保证可⽤性
- Redis也⽀持集群,通过集群配置可以保证redis的⾼并发
5.
你刚才提到了
redis
集群,请问如何解决
redis
集群的脑裂问题?
6. redis
中的数据可以设置过期时间,当数据过期之后有些
key
并没有及时清除,请问如何处
理?
⼗⼀、使⽤Redis实现分布式会话
11.1 流程分析
11.2 在商城中使⽤redis实现分布式会话
11.2.1 修改登录接⼝
当登录成功以
token
为
key
将⽤户信息保存到
redis
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private ObjectMapper objectMapper = new ObjectMapper();
@Transactional
public ResultVO userResgit(String name, String pwd) {
//...
}
@Override
public ResultVO checkLogin(String name, String pwd) {
Example example = new Example(Users.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("username", name);
List<Users> users = usersMapper.selectByExample(example);
if(users.size() == 0){
return new ResultVO(ResStatus.NO,"登录失败,⽤户名不存
在!",null);
}else{
String md5Pwd = MD5Utils.md5(pwd);
if(md5Pwd.equals(users.get(0).getPassword())){
//如果登录验证成功,则需要⽣成令牌token(token就是按照特定规则
⽣成的字符串)
//使⽤jwt规则⽣成token字符串
JwtBuilder builder = Jwts.builder();
HashMap<String,Object> map = new HashMap<>();
map.put("key1","value1");
map.put("key2","value2");
String token = builder.setSubject(name)
.setIssuedAt(new Date())
.setId(users.get(0).getUserId() + "")
.setClaims(map)
.setExpiration(new
Date(System.currentTimeMillis() + 24*60*60*1000))
.signWith(SignatureAlgorithm.HS256,
"QIANfeng6666")
.compact();
//当⽤户登录成功之后,以token为key 将⽤户信息保存到reids
try {
String userInfo =
objectMapper.writeValueAsString(users.get(0));
stringRedisTemplate.boundValueOps(token).set(userInfo,30,
TimeUnit.MINUTES);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return new ResultVO(ResStatus.OK,token,users.get(0));
}else{
return new ResultVO(ResStatus.NO,"登录失败,密码错
误!",null);
}
}
}
}
11.2.2 在需要只⽤⽤户信息的位置,直接根据token从redis查询
@PostMapping("/add")
public ResultVO addShoppingCart(@RequestBody ShoppingCart
cart,@RequestHeader("token")String token) throws JsonProcessingException
{
ResultVO resultVO = shoppingCartService.addShoppingCart(cart);
String s = stringRedisTemplate.boundValueOps(token).get();
Users users = objectMapper.readValue(s, Users.class);
System.out.println(users);
return resultVO; }
11.2.3 修改受限资源拦截器
@Component
public class CheckTokenInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
String method = request.getMethod();
if("OPTIONS".equalsIgnoreCase(method)){
return true;
}
String token = request.getHeader("token");
if(token == null){
ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
doResponse(response,resultVO);
}else{
//根据token从redis中获取⽤户信息
String s = stringRedisTemplate.boundValueOps(token).get();
if(s == null){
//如果⽤户信息为空,表示⽤户未登录或者距离上⼀次访问超过30分钟
ResultVO resultVO = new
ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
doResponse(response,resultVO);
}else{
// 如果不为空,表示⽤户登录成功,续命
stringRedisTemplate.boundValueOps(token).expire(30,
TimeUnit.MINUTES);
return true;
}
}
return false;
}
private void doResponse(HttpServletResponse response,ResultVO
resultVO) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String s = new ObjectMapper().writeValueAsString(resultVO);
out.print(s);
out.flush();
out.close();
}
}
11.2.4 创建⾮受限资源拦截器
即使访问的是⾮受限资源,但是如果已经登录,只要与服务器有交互也要续命
@Component
public class SetTimeInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) {
String token = request.getHeader("token");
if(token != null){
String s = stringRedisTemplate.boundValueOps(token).get();
if(s != null){
stringRedisTemplate.boundValueOps(token).expire(30,
TimeUnit.MINUTES);
}
}
return true;
}
}
配置拦截器:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private CheckTokenInterceptor checkTokenInterceptor;
@Autowired
private SetTimeInterceptor setTimeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkTokenInterceptor)
.addPathPatterns("/shopcart/**")
.addPathPatterns("/orders/**")
.addPathPatterns("/useraddr/**")
.addPathPatterns("/user/check");
registry.addInterceptor(setTimeInterceptor).addPathPatterns("/**");
}
}
前端访问注意事项:
只要前端有
token
,对接⼝的访问就要携带
token