redis知识复习
- redis基础知识
- 一. redis的认识
- 1. 非关系型数据库 与 传统数据库 的区别
- 2. 安装redis并设置自启动
- 3. 熟悉命令行客户端
- 4. 熟悉图形化工具RDM
- 二. redis的命令与数据结构
- 1. 数据结构介绍
- 2. redis通用命令(熟练掌握)
- 三. redis的Java客户端
- 1. Jedis
- 2. SpringDataRedis
- 3. ==StringRedisTemplate(推荐)==
- redis应用(未完结...)
- 四. 处理登录验证
- 1. 设计登录拦截
- 五. 处理热点数据的查询工作
- 1. 处理缓存穿透
- 2. 处理缓存雪崩
- 3. 处理缓存击穿
- 六. 处理秒杀任务(优惠券)
- 1. 处理优惠券全局ID的生成唯一性
- 2. 优惠券秒杀流程(抢优惠券)
- 2.1 单体模式下的优惠券秒杀流程
- 2.2 集群环境下的优惠券秒杀流程(setnx分布式锁)
- 七. ==redis分布式锁——Redisson(重要掌握)==
- 1. 快速入门
- 2. Redisson的重要原理
- 2.1 可重入锁原理
- 2.2 可重试锁,
redis基础知识
一. redis的认识
1. 非关系型数据库 与 传统数据库 的区别
背会这张表就行了
2. 安装redis并设置自启动
-
在Linux环境下 安装redis依赖
yum install -y gcc tcl
-
(/usr/local/src目录下) 下载对应的redis安装包(本次为v6.2.6,如果有之前下载过的redis,记得提前删除干净,以防配置环境等因素造成安装的异常)
wget https://download.redis.io/releases/redis-6.2.6.tar.gz
-
解压压缩包获得 redis程序安装包
tar -xvf redis-6.2.6.tar.gz
-
在该程序包目录下执行 编译安装命令(默认该步骤会将redis软件安装到/usr/local/bin目录下)
make && make install
-
执行redis服务命令 即可启动redis,该方式为前台启动方式(不友好,不推荐使用)
redis-server
-
修改配置文件,完成指定配置下的启动准备(记得对redis.conf做备份,以防修改失误)
cp redis.conf redis.conf.bck vi redis.conf
# 文本内部的修改(供复制粘贴) # 任意ip可访问 bind 0.0.0.0 # 守护进程打开,可后台运行 daemonize yes # 密码设置123321 requirepass 123321 # 打开日志记录,并命名 logfile "redis.log"
-
根据指令,完成指定配置文件下的启动
redis-server redis.conf
-
查看redis进程命令,以及杀死进程命令
ps -ef | grep redis kill -9 PID(PID为对应的进程序列号)
-
开机自启动(在 system系统文件夹中 新建一个配置类文件)
vi /etc/systemd/system/redis.service
配置类文件内容如下:
[Unit] Description=redis-server After=network.target [Service] Type=forking # 这行配置内容要根据redis的安装目录自定义路径 ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf PrivateTmp=true [Install] WantedBy=multi-user.target
重载系统服务,以便配置文件生效
systemctl daemon-reload
此时可以使用系统命令实现redis的启动、查看状态或关闭
systemctl start redis systemctl status redis systemctl stop redis
执行下面的命令,实现开机自启:
systemctl enable redis
查看此时,redis 服务的状态:
systemctl status redis
3. 熟悉命令行客户端
-
在/usr/local/bin/目录下,使用redis-cli实现连通redis
redis-cli -h 192.168.2.190 -p 6379 -a 123321 >ping
-
存取数据set/get,换库select [index]
4. 熟悉图形化工具RDM
二. redis的命令与数据结构
1. 数据结构介绍
2. redis通用命令(熟练掌握)
# keys:查看所有key
keys *
# set:设置添加k-v mset:批量添加
set k1 v1
mset k1 v1 k2 v2 k3 v3
# del:删除
del k1
# exist 查看是否存在
exist k1
# expire:设置有效期时间,单位s,没有特殊设置则为-1表示永久有效
expire k1 20
# ttl:查看有效期剩余时间(-1表示永久,-2表示过期,正数表示剩余秒数)
ttl k1
String类型(可存string,int,float)
redis的key的格式:
层级存储:[项目名]:[业务名]:[类型]:[id],这种存储的方式,有一个好处,那就是在使用gui图形界面能看到层级结构
Hash类型
List类型(对比Java的双向链表)
list的总结:可以广泛模拟 栈(同一个方向先进后出) 队列(不同方向进出) 阻塞队列(一头取,一头放,需要设置等待时间)
Set类型(对比Java的hashset,相当于底层使用hashmap实现)
SortedSet类型(功能上类似TreeSet,底层数据结构不同)
三. redis的Java客户端
1. Jedis
单例使用流程
Jedis连接池用法
创建连接池对象,设置参数,完成连接池的创建,在使用过程中,与上述直接创建连接不同的是直接从连接池中获取一个连接,其他基本一致
// jedis = new Jedis("192.168.2.190",6379);
jedis = JedisConnectionFactory.getJedis(); //直接从连接池中获取一个
2. SpringDataRedis
使用流程(写pom,写yml,写测试用例,完成测试)
-
创建项目,引入依赖
2. 完成配置文件的设置 3. 注入装配,实施测试
redisTemplate的序列化操作存在的问题
基于可读性差的因素,可以自定义序列化方式,规避序列化造成的可读性问题 -
加依赖
<!--jackson序列化工具-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
- 自定义序列化方式(最好能理解!实际上由于该方式进行反序列化的必要操作时,会必定携带@class信息,造成占用内存产生大量冗余,并不推荐使用,后续会使用StringRedisTemplate操作key,value则手动进行序列化与反序列化操作)
/**
* redis反序列化自定义操作工具类
*/
@Configuration
public class RedisConfig {
/**
* @param redisConnectionFactory 引入工厂
* @return 返回经过处理的redisTemplate模板
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(redisConnectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashKeySerializer(jsonRedisSerializer);
// 返回RedisTemplate对象
return template;
}
}
3. StringRedisTemplate(推荐)
(基于内存占用问题,使用StringRedisTemplate来改善内存问题,StringRedisTemplate操作key,value则手动进行序列化与反序列化操作)
RedisTemplate与StringRedisTemplate处理后两者存取的数据对比:
redis应用(未完结…)
四. 处理登录验证
1. 设计登录拦截
五. 处理热点数据的查询工作
1. 处理缓存穿透
缓存穿透:浏览器不断发送未命中的请求,redis一直未命中,一直查询数据库,给数据库造成很大压力
实例:用户查询一个热点商铺/商品/文章信息,信息不存在,持续访问造成数据库压力
解决方案:
2. 处理缓存雪崩
实例:用户分时段查询多个热点商铺/商品/文章信息,结果在某个时间节点该信息全部失效,导致该时间节点需要大量访问数据库造成数据库压力
解决方案:给redis中的缓存数据设置不同的TTL
3. 处理缓存击穿
实例:多名用户在一个定时活动的时间节点访问某个热点商铺/商品/文章信息,结果造成缓存失效,结果造成访问数据库造成数据库的压力过大
- 使用互斥锁处理缓存击穿
- 使用【逻辑过期时间】处理缓存击穿
六. 处理秒杀任务(优惠券)
1. 处理优惠券全局ID的生成唯一性
ID生成类
@Component
public class RedisIdWorker {
//开始时间戳
private static final long BEGIN_TIMESTAMP = 1674086400L;
//序列号位数
private static final int COUNT_BITS = 32;
private StringRedisTemplate stringRedisTemplate;
public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public long nextId(String keyPrefix){
//1.生成时间戳
LocalDateTime time = LocalDateTime.now();
long nowSecond = time.toEpochSecond(ZoneOffset.UTC);
long timestamp = nowSecond - BEGIN_TIMESTAMP;
//2.生成序列号,redis自增长,redis单个key自增长有上限,2的64次方
//2.1获取当前日期,精确到天
String date = time.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
//3.拼接并返回,不能使用字符串方式拼接
return timestamp << COUNT_BITS | count;//先向左移32位,那么低32位全为0,跟序列号进行或操作
}
/**
* 生成开始时间戳
* @param args
*/
public static void main(String[] args) {
LocalDateTime time = LocalDateTime.of(2023, 1, 19, 0, 0, 0);
long second = time.toEpochSecond(ZoneOffset.UTC);
System.out.println(second);
}
}
其他方案:
2. 优惠券秒杀流程(抢优惠券)
2.1 单体模式下的优惠券秒杀流程
2.2 集群环境下的优惠券秒杀流程(setnx分布式锁)
(在集群模式下,加锁只是该台jvm给当前这台服务器处理的请求加锁,而集群是多台服务器轮询处理请求,会造成每台服务器都有一个加锁的线程,每台服务器都会有一个新订单创建处理)
解决原子性问题,造成的锁无法及时释放的Lua脚本代码
-- 这里的 KEYS[1] 就是锁的 key,这里的 ARGV[1] 就是当前线程标识
-- 获取锁中的线程标识 get key
local id = redis.call('get', KEYS[1]);
-- 比较线程标识与锁中的标识是否一致
if (id == ARGV[1]) then
-- 释放锁 del key
return redis.call('del', KEYS[1])
end
return 0
七. redis分布式锁——Redisson(重要掌握)
上述集群的基于 setnx 实现的分布式锁存在下面的问题
1.不可重入:同一个线程无法多次获取同一把锁
2.不可重试:获取锁只尝试一次就返回 false,没有重试机制
3.超时释放:锁超时释放虽然可以避免死锁,但如果是业务执行耗时较长,也会导致锁释放,存在安全隐患
4.主从一致性:如果 Redis 提供了主从集群,主从延同步在延迟,当主机宕机时,如果从机同步主机中的数据,则会出现锁失效
Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格
它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
1. 快速入门
- 导依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
- 建Redisson类
@Configuration
public class RedisConfig {
@Bean
public RedissonClient redissionClient() {
// 配置类
Config config = new Config();
// 添加 Redis 地址,此处添加了单点的地址,也可以使用 config.useClusterServers() 添加集群地址
config.useSingleServer().setAddress("redis://192.168.2.12:6379").setPassword("123321");
// 创建客户端
return Redisson.create(config);
}
}
- 测试基础使用
@Resource
private RedissonClient redissonClient;
@Test
void testRedisson() throws InterruptedException {
// 获取锁(可重入),指定锁的名称
RLock lock = redissonClient.getLock("anyLock");
// 尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试过),锁自动释放时间,时间单位
boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
// 判断锁是否获取成功
if (isLock) {
try {
System.out.println("执行业务");
} finally {
//释放锁
lock.unlock();
}
}
}
2. Redisson的重要原理
2.1 可重入锁原理
2.2 可重试锁,
@Resource
private RedissonClient redissonClient;
@Test
void testRedisson() throws InterruptedException {
// 获取锁(可重入),指定锁的名称
RLock lock = redissonClient.getLock("anyLock");
// 尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试过),锁自动释放时间,时间单位
boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
// 判断锁是否获取成功
if (isLock) {
try {
System.out.println("执行业务");
} finally {
//释放锁
lock.unlock();
}
}
}