当前位置:   article > 正文

Redis基本使用_redis7.2.3

redis7.2.3

目录

⼀、Redis介绍

1.1 项⽬问题

1.2 Redis介绍

1.2.1 Redis的产⽣背景

1.2.2 Redis使⽤

1.2.3 Redis⽀持的数据类型

1.2.4 Redis特点

1.3 Redis应⽤场景

1.4 Redis的优缺点

1.4.1 优点

1.4.2 缺点

⼆、Redis安装及配置

2.1 Redis安装

2.1.1 下载Redis

2.1.2 安装redis

2.2 Redis配置

三、Redis基本使⽤

3.1 Redis存储的数据结构

3.2 string常⽤指令

3.3 hash常⽤指令

3.4 list常⽤指令

3.5 set常⽤指令

3.6 zset常⽤指令

3.8 db常⽤指令

四、Redis的持久化 [重点]

4.1 RDB

4.2 AOF

五、Java应⽤连接Redis

5.1 设置redis允许远程连接

5.1 在普通Maven⼯程连接Redis

5.1.1 添加Jedis依赖

5.1.2 使⽤案例

5.1.3 redis远程可视化客户端

5.2 在SpringBoot⼯程连接Redis

5.2.1 创建springBoot应⽤

5.2.2 配置redis

5.2.3 使⽤redis客户端连接redis

5.3 Spring Data Redis

5.3.1 不同数据结构的添加操作

5.3.2 string类型的操作⽅法

5.3.3 不同数据类型的取值操作

六、使⽤redis缓存数据库数据

6.1 redis作为缓存的使⽤流程

6.2 在使⽤redis缓存商品详情

6.2.1 在service⼦⼯程添加Spring data redis依赖

6.2.2 在application.yml配置redis数据源

6.3.3 在ProductServiceImpl中修改业务代码

​编辑

七、使⽤Redis做缓存使⽤存在的问题 [重点]

7.1 缓存击穿

7.2 缓存穿透

7.4 Jmeter测试

7.4.1 创建测试计划

7.4.2 创建线程组

 7.4.3 设置HTTP请求

⼋、Redis⾼级应⽤

8.1 主从配置

8.2 哨兵模式

8.3 集群配置

8.3.1 集群搭建

8.3.2 集群管理

8.3.3 SpringBoot应⽤连接集群

九、Redis淘汰策略

⼗、Redis⾼频⾯试题

⼗⼀、使⽤Redis实现分布式会话

11.1 流程分析

11.2 在商城中使⽤redis实现分布式会话

11.2.1 修改登录接⼝

11.2.2 在需要只⽤⽤户信息的位置,直接根据token从redis查询

11.2.3 修改受限资源拦截器

11.2.4 创建⾮受限资源拦截器


⼀、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 ,可以设置为 yes
daemonize 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 ⽀持多种数据类型

 

3.2 string常⽤指令

## 设置值 / 修改值 如果 key 存在则进⾏修改
set key value
## 取值
get key
## 批量添加
mset k1 v1 [k2 v2 k3 v3 ...]
## 批量取值
mget k1 [k2 k3...]
## ⾃增和⾃减
incr key ## key 对应的 value 上⾃增 +1
decr key ## key 对应的 value 上⾃减 -1
incrby key v ## key 对应的 value +v
decrby 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 上加 v
hincrby key field v
## 获取 key 对应的 hash 中所有的键值对
hgetall key
## 获取 key 对应的 hash 中所有的 field
hkeys key
## 获取 key 对应的 hash 中所有的 value
hvals key
## 检查 key 对应的 hash 中是否有指定的 field
hexists key field
## 获取 key 对应的 hash 中键值对的个数
hlen key
## key 对应的 hash 结构中添加 f-v, 如果 field hash 中已经存在,则添加失败
hsetnx key field value

3.4 list常⽤指令

 

## 存储数据
lpush key value # key 对应的列表的左侧添加数据 value
rpuhs 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 对应的集合中移出指定的 value
srem key value
## 检查 key 对应的集合中是否有指定的 value
sismember 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 中移除指定的 member
zrem 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 seconds
pexpire key milliseconds
## 取消键值对过期时间
persist key

3.8 db常⽤指令

redis 的键值对是存储在数据库中的 ——db
redis 中默认有 16 db ,编号 0-15
## 切换数据库
select index
## 将键值对从当前 db 移动到⽬标 db
move key index
## 清空当前数据库数据
flushdb
## 清所有数据库的 k-v
flushall
## 查看当前 db k-v 个数
dbsize
## 获取最后⼀次持久化操作时间
lastsave

四、Redis的持久化 [重点]

Redis 是基于内存操作,但作为⼀个数据库也具备数据的持久化能⼒;但是为了实现⾼效
的读写操作,并不会即时进⾏数据的持久化,⽽是按照⼀定的规则进⾏持久化操作的
—— 持久化策略
Redis 提供了 2 中持久化策略:
  • RDB (Redis DataBase)
  • AOF(Append Only File)

4.1 RDB

在满⾜特定的 redis 操作条件时,将内存中的数据以数据快照的形式存储到 rdb ⽂件中
 
  • 原理:
        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 1
save 300 10
save 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 持久化

 

  • 原理:
        Redis将每⼀个成功的写操作写⼊到 aof ⽂件中,当 redis 重启的时候就执⾏ aof ⽂件中的指令以恢复数据
  • 配置:

## 开启 AOF
appendonly yes
## 设置触发条件(三选⼀)
appendfsync always ## 只要进⾏成功的写操作,就执⾏ aof
appendfsync everysec ## 每秒进⾏⼀次 aof
appendfsync 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

## 开启 AOF
appendonly yes
## 设置触发条件(三选⼀)
appendfsync always ## 只要进⾏成功的写操作,就执⾏ aof
appendfsync everysec ## 每秒进⾏⼀次 aof
appendfsync no ## redis 执⾏决定 aof
## 设置 aof ⽂件路径
appendfilename "appendonly.aof"
  • 重启redis

redis-server redis-6379.conf

  • 阿⾥云安全组设置放⾏6379端⼝

 

5.1 在普通Maven⼯程连接Redis

使⽤ jedis 客户端连接

5.1.1 添加Jedis依赖

  1. <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
  2. <dependency>
  3. <groupId>redis.clients</groupId>
  4. <artifactId>jedis</artifactId>
  5. <version>2.9.0</version>
  6. </dependency>

5.1.2 使⽤案例

  1. public static void main(String[] args) {
  2. Product product = new Product("101", "娃哈哈AD钙奶", 2.5);
  3. //1.连接redis
  4. Jedis jedis = new Jedis("47.96.11.185", 6379);
  5. //2.操作
  6. String s = jedis.set(product.getProductId(), new
  7. Gson().toJson(product));
  8. System.out.println(s);
  9. //3.关闭连接
  10. jedis.close();
  11. }

5.1.3 redis远程可视化客户端

        Redis desktop manager

5.2 SpringBoot⼯程连接Redis

Spring Data Redis , part of the larger Spring Data family, provides easy
configuration and access to Redis from Spring applications. It offers both low-level
and high-level abstractions for interacting with the store, freeing the user from
infrastructural concerns.
Spring Data Redis 依赖中,提供了⽤于连接 redis 的客户端:
  • RedisTemplate
  • StringRedisTemplate

5.2.1 创建springBoot应⽤

 

5.2.2 配置redis

application.yml ⽂件配置 redis 的连接信息
spring :
redis :
host : 47.96.11.185
port : 6379
database : 0
password : 123456

5.2.3 使⽤redis客户端连接redis

直接在 service 中注⼊ RedisTemplate 或者 StringRedisTemplate ,就可以使⽤此对象完 成redis操作

5.3 Spring Data Redis

5.3.1 不同数据结构的添加操作

  1. //1.string
  2. //添加数据 set key value
  3. stringRedisTemplate.boundValueOps(product.getProductId()).set(
  4. jsonstr);
  5. //2.hash
  6. stringRedisTemplate.boundHashOps("products").put(product.getProductId()
  7. ,jsonstr);
  8. //3.list
  9. stringRedisTemplate.boundListOps("list").leftPush("ccc");
  10. //4.set
  11. stringRedisTemplate.boundSetOps("s1").add("v2");
  12. //5.zset
  13. stringRedisTemplate.boundZSetOps("z1").add("v1",1.2);

5.3.2 string类型的操作⽅法

  1. //添加数据 set key value
  2. stringRedisTemplate.boundValueOps(product.getProductId()).set(
  3. jsonstr);
  4. //添加数据时指定过期时间 setex key 300 value
  5. stringRedisTemplate.boundValueOps("103").set(jsonstr,300);
  6. //设置指定key的过期时间 expire key 20
  7. stringRedisTemplate.boundValueOps("103").expire(20, TimeUnit.SECONDS);
  8. //添加数据 setnx key value
  9. Boolean absent =
  10. stringRedisTemplate.boundValueOps("103").setIfAbsent(jsonstr);

5.3.3 不同数据类型的取值操作

  1. //string
  2. String o = stringRedisTemplate.boundValueOps("103").get();
  3. //hash
  4. Object v = stringRedisTemplate.boundHashOps("products").get("101");
  5. //list
  6. String s1 = stringRedisTemplate.boundListOps("list").leftPop();
  7. String s2 = stringRedisTemplate.boundListOps("list").rightPop();
  8. String s3 = stringRedisTemplate.boundListOps("list").index(1);
  9. //set
  10. Set<String> vs = stringRedisTemplate.boundSetOps("s1").members();
  11. //zset
  12. Set<String> vs2 = stringRedisTemplate.boundZSetOps("z1").range(0, 5);

六、使⽤redis缓存数据库数据

6.1 redis作为缓存的使⽤流程

6.2 在使⽤redis缓存商品详情

6.2.1 service⼦⼯程添加Spring data redis依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </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-8
username : root
password : admin123
redis :
port : 6379
host : 47.96.11.185
database : 0
password : 123456

6.3.3 ProductServiceImpl中修改业务代码

 

 

 

 

 

七、使⽤Redis做缓存使⽤存在的问题 [重点]

使⽤ redis 做为缓存在⾼并发场景下有可能出现缓存击穿、缓存穿透、缓存雪崩等问题

7.1 缓存击穿

缓存击穿:⼤量的并发请求同时访问同⼀个在 redis 中不存在的数据,就会导致⼤量的请
求绕过 redis 同时并发访问数据库,对数据库造成了⾼并发访问压⼒。
  • 使⽤ 双重检测锁 解决 缓存击穿 问题
  1. @Service
  2. public class IndexImgServiceImpl implements IndexImgService {
  3. @Autowired
  4. private IndexImgMapper indexImgMapper;
  5. @Autowired
  6. private StringRedisTemplate stringRedisTemplate;
  7. private ObjectMapper objectMapper = new ObjectMapper();
  8. public ResultVO listIndexImgs() {
  9. List<IndexImg> indexImgs = null;
  10. try {
  11. //1000个并发请求,请求轮播图
  12. String imgsStr =
  13. stringRedisTemplate.boundValueOps("indexImgs").get();
  14. //1000个请求查询到redis中的数据都是null
  15. if (imgsStr != null) {
  16. // 从redis中获取到了轮播图信息
  17. JavaType javaType = objectMapper.getTypeFactory()
  18. .constructParametricType(ArrayList.class,
  19. IndexImg.class);
  20. indexImgs = objectMapper.readValue(imgsStr, javaType);
  21. } else {
  22. // 1000个请求都会进⼊else
  23. // (service类在spring容器中是单例的,
  24. // 1000个并发会启动1000个线程来处理,但是公⽤⼀个service实 例)
  25. synchronized (this){
  26. // 第⼆次查询redis
  27. String s =
  28. stringRedisTemplate.boundValueOps("indexImgs").get();
  29. if(s == null){
  30. // 这1000个请求中,只有第⼀个请求再次查询redis时依然为
  31. null
  32. indexImgs = indexImgMapper.listIndexImgs();
  33. System.out.println("----------------查询数据
  34. 库");
  35. stringRedisTemplate.boundValueOps("indexImgs")
  36. .set(objectMapper.writeValueAsString(indexImgs));
  37. stringRedisTemplate.boundValueOps("indexImgs")
  38. .expire(1, TimeUnit.DAYS);
  39. }else{
  40. JavaType javaType =
  41. objectMapper.getTypeFactory()
  42. .constructParametricType(ArrayList.class,
  43. IndexImg.class);
  44. indexImgs = objectMapper.readValue(s,
  45. javaType);
  46. }
  47. }
  48. }
  49. } catch (JsonProcessingException e) {
  50. e.printStackTrace();
  51. }
  52. //返回数据
  53. if(indexImgs != null){
  54. return new ResultVO(ResStatus.OK,"success",indexImgs);
  55. }else{
  56. return new ResultVO(ResStatus.NO,"fail",null);
  57. }
  58. }
  59. }

7.2 缓存穿透

缓存穿透:⼤量的请求⼀个数据库中不存在的数据,⾸先在 redis 中⽆法命中,最终所有
的请求都会访问数据库,同样会导致数据库承受巨⼤的访问压⼒。
  • 解决⽅案:当从数据库查询到⼀个null时,写⼀个⾮空的数据到redis,并设置过期时间

 

  1. indexImgs = indexImgMapper.listIndexImgs();
  2. if(indexImgs != null) {
  3. String s = objectMapper.writeValueAsString(indexImgs);
  4. stringRedisTemplate.boundValueOps("indexImgs").set(s);
  5. stringRedisTemplate.boundValueOps("indexImgs").expire(1,
  6. TimeUnit.DAYS);
  7. }else{
  8. //当从数据库查询数据为null时,保存⼀个⾮空数据到redis,并设置过期时间
  9. stringRedisTemplate.boundValueOps("indexImgs").set("[]");
  10. stringRedisTemplate.boundValueOps("indexImgs").expire(10,
  11. TimeUnit.SECONDS);
  12. }

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
## redis-master.conf 拷⻉两份分别为: redis-slave1.conf redis
slave2.conf
[root@theo msconf] # sed 's/6380/6381/g' redis-master.conf > redis
slave1.conf
[root@theo msconf] # sed 's/6380/6382/g' redis-master.conf > redis
slave2.conf
## 修改 redis-slave1.conf redis-slave2.conf ,设置 跟从 ”---127.0.0.1
6380
[root@theo msconf] # vim redis-slave1.conf
[root@theo msconf] # vim redis-slave2.conf

 ## 启动三个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 哨兵模式

哨兵模式:⽤于监听主库,当确认主库宕机之后,从备库 ( 从库 ) 中选举⼀个转备为主

 

哨兵模式配置
## ⾸先实现三个 redis 实例之间的主从配置(如上)
## 创建并启动三个哨兵
## 拷⻉ sentinel.conf ⽂件三份: sentinel-26380.conf sentinel-26382.conf
sentinel-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] # ll
total 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 26380
daemonize no
pidfile "/var/run/redis-sentinel-26380.pid"
logfile ""
dir "/tmp"
sentinel deny-scripts-reconfig yes
# 此处配置默认的主库的 ip 和端⼝ 最后的数字是哨兵数量的⼀半多⼀个
sentinel monitor mymaster 127 .0.0.1 6380 2
sentinel config-epoch mymaster 1
sentinel leader-epoch mymaster 1
protected-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 并发写操作;但是如
果对于有更⾼并发需求的应⽤来说,我们可以通过 读写分离 集群配置 来解决⾼并发问

 

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] # ls
redis-7001.conf
[root@theo cluster-conf] # vim redis-7001.conf

 

  • 拷⻉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
  • 启动6redis实例

[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 redis
root 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:7001
47.96.11.185:7002 47.96.11.185:7003 47.96.11.185:7004 47.96.11.185:7005
47.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 create
47.96.11.185:7001 47.96.11.185:7002 47.96.11.185:7003
47.96.11.185:7004 47.96.11.185:7005 47.96.11.185:7006 --cluster
replicas 1
  • 查看集群状态

[root@theo cluster-conf] # redis-cli --cluster info 47.96.11.185:7001
47 .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 rebalance
47.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-node
47.96.11.185:7001 4678478aa66b6d37b23944cf7db0ac07298538a4
>>> Removing node 4678478aa66b6d37b23944cf7db0ac07298538a4 from
cluster 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:7002
47 .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-node
47.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 : 7003
max-redirects : 3
  • 操作集群

@RunWith ( SpringRunner . class )
@SpringBootTest ( classes = RedisDemo3Application . class )
class RedisDemo3ApplicationTests {
@Autowired
private StringRedisTemplate stringRedisTemplate ;
@Test
void 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
  1. @Service
  2. public class UserServiceImpl implements UserService {
  3. @Autowired
  4. private UsersMapper usersMapper;
  5. @Autowired
  6. private StringRedisTemplate stringRedisTemplate;
  7. private ObjectMapper objectMapper = new ObjectMapper();
  8. @Transactional
  9. public ResultVO userResgit(String name, String pwd) {
  10. //...
  11. }
  12. @Override
  13. public ResultVO checkLogin(String name, String pwd) {
  14. Example example = new Example(Users.class);
  15. Example.Criteria criteria = example.createCriteria();
  16. criteria.andEqualTo("username", name);
  17. List<Users> users = usersMapper.selectByExample(example);
  18. if(users.size() == 0){
  19. return new ResultVO(ResStatus.NO,"登录失败,⽤户名不存
  20. 在!",null);
  21. }else{
  22. String md5Pwd = MD5Utils.md5(pwd);
  23. if(md5Pwd.equals(users.get(0).getPassword())){
  24. //如果登录验证成功,则需要⽣成令牌token(token就是按照特定规则
  25. ⽣成的字符串)
  26. //使⽤jwt规则⽣成token字符串
  27. JwtBuilder builder = Jwts.builder();
  28. HashMap<String,Object> map = new HashMap<>();
  29. map.put("key1","value1");
  30. map.put("key2","value2");
  31. String token = builder.setSubject(name)
  32. .setIssuedAt(new Date())
  33. .setId(users.get(0).getUserId() + "")
  34. .setClaims(map)
  35. .setExpiration(new
  36. Date(System.currentTimeMillis() + 24*60*60*1000))
  37. .signWith(SignatureAlgorithm.HS256,
  38. "QIANfeng6666")
  39. .compact();
  40. //当⽤户登录成功之后,以token为key 将⽤户信息保存到reids
  41. try {
  42. String userInfo =
  43. objectMapper.writeValueAsString(users.get(0));
  44. stringRedisTemplate.boundValueOps(token).set(userInfo,30,
  45. TimeUnit.MINUTES);
  46. } catch (JsonProcessingException e) {
  47. e.printStackTrace();
  48. }
  49. return new ResultVO(ResStatus.OK,token,users.get(0));
  50. }else{
  51. return new ResultVO(ResStatus.NO,"登录失败,密码错
  52. 误!",null);
  53. }
  54. }
  55. }
  56. }

11.2.2 在需要只⽤⽤户信息的位置,直接根据tokenredis查询

  1. @PostMapping("/add")
  2. public ResultVO addShoppingCart(@RequestBody ShoppingCart
  3. cart,@RequestHeader("token")String token) throws JsonProcessingException
  4. {
  5. ResultVO resultVO = shoppingCartService.addShoppingCart(cart);
  6. String s = stringRedisTemplate.boundValueOps(token).get();
  7. Users users = objectMapper.readValue(s, Users.class);
  8. System.out.println(users);
  9. return resultVO; }

11.2.3 修改受限资源拦截器

  1. @Component
  2. public class CheckTokenInterceptor implements HandlerInterceptor {
  3. @Autowired
  4. private StringRedisTemplate stringRedisTemplate;
  5. @Override
  6. public boolean preHandle(HttpServletRequest request,
  7. HttpServletResponse response, Object handler) throws Exception {
  8. String method = request.getMethod();
  9. if("OPTIONS".equalsIgnoreCase(method)){
  10. return true;
  11. }
  12. String token = request.getHeader("token");
  13. if(token == null){
  14. ResultVO resultVO = new ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
  15. doResponse(response,resultVO);
  16. }else{
  17. //根据token从redis中获取⽤户信息
  18. String s = stringRedisTemplate.boundValueOps(token).get();
  19. if(s == null){
  20. //如果⽤户信息为空,表示⽤户未登录或者距离上⼀次访问超过30分钟
  21. ResultVO resultVO = new
  22. ResultVO(ResStatus.LOGIN_FAIL_NOT, "请先登录!", null);
  23. doResponse(response,resultVO);
  24. }else{
  25. // 如果不为空,表示⽤户登录成功,续命
  26. stringRedisTemplate.boundValueOps(token).expire(30,
  27. TimeUnit.MINUTES);
  28. return true;
  29. }
  30. }
  31. return false;
  32. }
  33. private void doResponse(HttpServletResponse response,ResultVO
  34. resultVO) throws IOException {
  35. response.setContentType("application/json");
  36. response.setCharacterEncoding("utf-8");
  37. PrintWriter out = response.getWriter();
  38. String s = new ObjectMapper().writeValueAsString(resultVO);
  39. out.print(s);
  40. out.flush();
  41. out.close();
  42. }
  43. }

11.2.4 创建⾮受限资源拦截器

即使访问的是⾮受限资源,但是如果已经登录,只要与服务器有交互也要续命
  1. @Component
  2. public class SetTimeInterceptor implements HandlerInterceptor {
  3. @Autowired
  4. private StringRedisTemplate stringRedisTemplate;
  5. @Override
  6. public boolean preHandle(HttpServletRequest request,
  7. HttpServletResponse response, Object handler) {
  8. String token = request.getHeader("token");
  9. if(token != null){
  10. String s = stringRedisTemplate.boundValueOps(token).get();
  11. if(s != null){
  12. stringRedisTemplate.boundValueOps(token).expire(30,
  13. TimeUnit.MINUTES);
  14. }
  15. }
  16. return true;
  17. }
  18. }
配置拦截器:
  1. @Configuration
  2. public class InterceptorConfig implements WebMvcConfigurer {
  3. @Autowired
  4. private CheckTokenInterceptor checkTokenInterceptor;
  5. @Autowired
  6. private SetTimeInterceptor setTimeInterceptor;
  7. @Override
  8. public void addInterceptors(InterceptorRegistry registry) {
  9. registry.addInterceptor(checkTokenInterceptor)
  10. .addPathPatterns("/shopcart/**")
  11. .addPathPatterns("/orders/**")
  12. .addPathPatterns("/useraddr/**")
  13. .addPathPatterns("/user/check");
  14. registry.addInterceptor(setTimeInterceptor).addPathPatterns("/**");
  15. }
  16. }

前端访问注意事项: 只要前端有 token ,对接⼝的访问就要携带 token
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/699045
推荐阅读
相关标签
  

闽ICP备14008679号