成神之路之Redis从头开始学习(二)
四 Java连接Redis
Jedis连接Redis,Lettuce连接Redis
4.1 Jedis连接Redis
1、创建maven项目
2、导入需要的依赖包
<dependencies>
<!--1、Jedis依赖包-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--2、Junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!--3、Lombok依赖包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
3、测试
public class Demo1 {
@Test
public void set(){
//1、连接Redis
Jedis jedis = new Jedis("127.0.0.1",6379);
//2、操作Redis - redis的命令是什么jedis对应的方法就是什么
jedis.set("name","zhangsan");
//3、释放资源
jedis.close();
}
@Test
public void get(){
//1、连接Redis
Jedis jedis = new Jedis("127.0.0.1",6379);
//2、操作Redis - redis的命令是什么jedis对应的方法就是什么
String value = jedis.get("name");
System.out.println(value);
//3、释放资源
jedis.close();
}
}
4.2 Jedis如何存储一个对象到Redis
准备一个User实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private Long id;
private String name;
private Date birthday;
}
导入spring-context依赖
<!--4、导入spring-context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
创建Demo测试类,编写内容
public class Demo2 {
//存储对象 -- 以byte[]形式存储在redis中
@Test
public void setByteArray(){
//1、连接redis服务
Jedis jedis = new Jedis("127.0.0.1",6379);
//2.1 准备key(String) - value(User)
String key = "user";
User user = new User(1L,"张三",new Date());
//2.2 将key和value转换为byte[]
byte[] byteKey = SerializationUtils.serialize(key);
//user对象序列化和反序列化,需要在User类实现Serializable接口
byte[] byteValue = SerializationUtils.serialize(user);
//2.3 将key和value存储到redis
jedis.set(byteKey,byteValue);
//3、释放资源
jedis.close();
}
@Test
public void getByteArray(){
//1、连接redis服务
Jedis jedis = new Jedis("127.0.0.1",6379);
//2.1 准备key(String)
String key = "user";
//2.2 将key转换为byte[]
byte[] byteKey = SerializationUtils.serialize(key);
//2.3 获取value
byte[] byteValue = jedis.get(byteKey);
//2.4 将value反序列化为user对象
User user2 = (User)SerializationUtils.deserialize(byteValue);
System.out.println(user2);
//3、释放资源
jedis.close();
}
}
4.3 Jedis如何存储一个对象到Redis,以String的形式存储
导入一个fastjson依赖
<!--5、导入fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.71</version>
</dependency>
编写测试类
public class Demo3 {
//存储的对象,以String形式
@Test
public void setString(){
//1、连接redis
Jedis jedis = new Jedis("127.0.0.1",6379);
//2.1 准备key(String) - value(User)
String stringKey = "stringUser";
User value = new User(2L,"李四",new Date());
//2.2 使用fastjson将value格式化为json字符串
String stringVlue = JSON.toJSONString(value);
//2.3 存储到redis中
jedis.set(stringKey,stringVlue);
//3关闭连接
jedis.close();
}
@Test
public void getString(){
//1、连接redis
Jedis jedis = new Jedis("127.0.0.1",6379);
//2.1 准备key
String stringKey = "stringUser";
//2.2 去redis中查询value
String stringValue =jedis.get(stringKey);
//2.3 将value反序列化为User
User user = JSON.parseObject(stringValue,User.class);
System.out.println(user);
//3关闭连接
jedis.close();
}
}
4.4 Jedis连接池的操作
@Test
public void pool2(){
//1、创建连接池的配置信息
GenericObjectPoolConfig config = new GenericObjectPoolConfig();
//连接池中最大的活跃数
config.setMaxTotal(100);
//最大空闲数
config.setMaxIdle(10);
//最大空闲数
config.setMinIdle(5);
//当连接池空了之后,多久没获取到jedis对象就超时,单位毫秒
config.setMaxWaitMillis(3000);
//2、创建连接池
JedisPool pool = new JedisPool(config,"127.0.0.1",6379);
//3、获取jedis
Jedis jedis = pool.getResource();
//4、操作
String value = jedis.get("stringUser");
System.out.println(value);
//6、释放连接
jedis.close();
}
4.5 Redis的管道操作
因为在操作Redis的时候,执行一个命令需要先发送请求到Redis服务器,这个过程需要经历网络延迟,Redis还需要给客户端一个响应。
如果我需要一次性执行很多个命令,上述的方式效率很低,可以通过Redis的管道,先将命令放到客户端的一个pipeline中,之后一次性的将全部命令发送到Redis服务器,Redis服务一次性的将全部的返回结果响应给客户端。
//Redis的管道操作
@Test
public void pipeline(){
//1、创建连接
JedisPool pool = new JedisPool("127.0.0.1",6379);
long start = System.currentTimeMillis();
//2、获取一个连接对象
Jedis jedis = pool.getResource();
// //3、执行incr - 10000次
// for (int i = 0; i < 50000; i++) {
// jedis.incr("pp");
// }
// //4、释放资源
// jedis.close();
//------------------
//3、创建管道
Pipeline pipeline = jedis.pipelined();
//4、执行incr - 10000次放到管道中
for (int i = 0; i < 50000; i++) {
pipeline.incr("qq");
}
pipeline.syncAndReturnAll();
//5、释放资源
jedis.close();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
五 Redis其他配置及集群
修改yaml文件,以方便后期修改Redis配置信息
#指定本 yml 依从的 compose 哪个版本制定的
version: '3.1'
#定义服务
services:
#定义一个服务
redis:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 6379:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis.conf:/usr/local/redis.conf
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
执行docker命令:
#停止和删除容器、网络、卷、镜像。先停止和删除之前的redis容器
docker-compose down
#重新创建容器并后台启动
docker-compose up -d
5.1 Redis的AUTH
方式一:通过修改Redis的配置文件,实现Redis的密码校验
#在./conf/redis.conf里面添加如下配置
requirepass 密码
然后重启redis容器:docker-compose restart
三种客户端的连接方式
redis-cli:在输入正常命令之前,先输入auth密码即可
图形化界面:在连接Redis的信息中添加上验证的密码
Jedis客户端:
第一种:jedis.auth(password);(不推荐)
第二种:使用JedisPool的方式
public JedisPool(GenericObjectPoolConfig poolConfig, String host, int port, int timeout, String password)
方式二:在不修改redis.conf文件的前提下,在第一次连接redis时,输入命令:Config set requirepass 密码,后续再次操作redis时,需要先AUTH做一次校验。(不推荐这种方式)重启之后密码就失效了。
5.2 Redis的事务
Redis的事务:一次事务,该成功的成功,该失败的失败。
先开启事务,执行一系列的命令,但是密码不会立即执行,会被放在一个队列中,如果你执行事务,那么这个队列中的命令全部执行,如果取消事务,一个队列中的命令全部作废。
开启事务:multi
输入要执行的命令,命令被放入到一个队列中
执行事务:exec
取消事务:discard
Redis的事务想发挥功能,需要配合watch监听机制
在开启事务之前,先通过watch命令监听一个或者多个key,在开启事务之后,如果有其他客户端修改了我监听的key,事务会自动取消。
如果执行了事务或者取消了事务,watch监听自动消除,一般不需要手动执行unwatch释放监听。
5.3 Redis持久化机制
RDB方式-默认
RDB是Redis默认的持久化机制
RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。
RDB持久化的时机:
save 900 1 #在900秒内,有1个key改变,就执行RDB持久化 save 300 10 #在300秒内,有10个key改变,就执行RDB持久化 save 60 10000 #在60秒内,有10000个key改变,就执行RDB持久化
RDB无法保证数据的绝对安全
#RDB主要配置项
#持久化时机:在900秒内,有1个key改变,就执行RDB持久化
save 900 1
#持久化时机:在300秒内,有10个key改变,就执行RDB持久化
save 300 10
#持久化时机:在60秒内,有10000个key改变,就执行RDB持久化
save 60 10000
#开启RDB持久化的压缩
rdbcompression yes
#RDB持久化文件的名称
dbfilename dump.rdb
AOF方式
AOF持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全,避免数据丢失。在aof无法使用的时候,再用rdb的备份文件做替补恢复
AOF持久化的速度相对RDB较慢,存储的是一个文本文件,时间久了文件会比较大,传输困难
AOF持久化机制:
#每执行一个写操作,立即持久化到AOF文件中,性能比较低
appendfsync always
#每秒执行一次持久化 appendfsync everysec #会根据你的操作系统不同,环境的不同,在一定时间执行一次持久化
appendfsync no
AOF相对RDB更安全,推荐同时开启AOF和RDB。
#AOF主要配置项
#代表开启AOF持久化
appendonly yes
#AOF文件的名称
appendfilename "redis.aof"
#AOF持久化执行的时机
#每执行一个写操作,立即持久化到AOF文件中,性能比较低
appendfsync always
#每秒执行一次持久化
appendfsync everysec
#会根据你的操作系统不同,环境的不同,在一定时间执行一次持久化
appendfsync no
同时开启RDB和AOF的注意事项:
如果同时开启了AOF和RDB持久化,那么Redis宕机重启之后,需要加载一个持久化文件,优先选择AOF文件。
如果先开启了RDB,然后之后开启AOF,如果RDB执行了持久化,那么RDB文件中的内容会被AOF覆盖掉。
5.4 Redis主从架构
单机版Redis存在读写瓶颈的问题
docker-compose.yml文件:
#指定本 yml 依从的 compose 哪个版本制定的
version: '3.1'
#定义服务
services:
#定义一个服务
redis1:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis1
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 7001:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis1.conf:/usr/local/redis.conf
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
redis2:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis2
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 7002:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis2.conf:/usr/local/redis.conf
#配置链接,redis1容器别名master
links:
- redis1:master
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
redis3:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis3
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 7003:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis3.conf:/usr/local/redis.conf
links:
- redis1:master
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
#redis2和redis3从节点配置 replicaof <masterip> <masterport>
replicaof master 6379
具体操作步骤
-
在/opt目录下面创建工作目录
mkdir docker_redis_master_salve
-
vi docker-compose.yml
复制上面配置信息到yml
-
在docker_redis_master_salve下创建conf目录
-
touch redis1.conf
-
touch redis2.conf
-
touch redis3.conf
-
向redis2.conf和redis3.conf中添加配置:replicaof master 6379
5.5 哨兵
哨兵可以帮助我们解决主从架构中的单点故障问题
修改docker-compose.yml,为了可以在容器内部使用哨兵的配置
#指定本 yml 依从的 compose 哪个版本制定的
version: '3.1'
#定义服务
services:
#定义一个服务
redis1:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis1
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 7001:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis1.conf:/usr/local/redis.conf
- ./conf/sentinel1.conf:/data/sentinel.conf #添加的内容
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
redis2:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis2
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 7002:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis2.conf:/usr/local/redis.conf
- ./conf/sentinel2.conf:/data/sentinel.conf #添加的内容
#配置链接,redis1容器别名master
links:
- redis1:master
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
redis3:
#指定镜像
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
#容器名称
container_name: redis3
#添加环境变量。指定时区
environment:
- TZ=Asia/Shanghai
#添加端口映射
ports:
- 7003:6379
#将主机当前目录的conf目录下的redis.conf挂载到容器里的/usr/local/redis.conf。
volumes:
- ./conf/redis3.conf:/usr/local/redis.conf
- ./conf/sentinel3.conf:/data/sentinel.conf #添加的内容
links:
- redis1:master
#覆盖容器启动的默认命令。
command: ["redis-server","/usr/local/redis.conf"]
准备哨兵的配置文件,并且在容器内部手动启动哨兵即可
哨兵基本配置:
#哨兵需要后台启动
daemonize yes
#指定Master节点的ip和端口(主) 哨兵 监视 主节节点 主节点IP/名称 端口 2个从节点
sentinel monitor master localhost 6379 2
#指定Master节点的ip和端口(从) sentinel monitor mymaster 127.0.0.1 6379 2
sentinel monitor master master 6379 2
#哨兵每隔多久监听一次redis架构,默认为3秒,这里设置1秒好看效果
sentinel down-after-milliseconds master 10000
./conf/sentinel1.conf
#哨兵需要后台启动
daemonize yes
#指定Master节点的ip和端口(主) 哨兵 监视 主节节点 主节点IP/名称 端口 2个从节点
sentinel monitor master localhost 6379 2
#哨兵每隔多久监听一次redis架构,默认为3秒,这里设置1秒好看效果
sentinel down-after-milliseconds master 10000
./conf/sentinel2.conf
#哨兵需要后台启动
daemonize yes
#指定Master节点的ip和端口(从) sentinel monitor mymaster 127.0.0.1 6379 2
sentinel monitor master master 6379 2
#哨兵每隔多久监听一次redis架构,默认为3秒,这里设置1秒好看效果
sentinel down-after-milliseconds master 10000
./conf/sentinel3.conf
#哨兵需要后台启动
daemonize yes
#指定Master节点的ip和端口(从) sentinel monitor mymaster 127.0.0.1 6379 2
sentinel monitor master master 6379 2
#哨兵每隔多久监听一次redis架构,默认为3秒,这里设置1秒好看效果
sentinel down-after-milliseconds master 10000
修改docker-compose.yml和增加sentinel的配置文件之后重新构建容器。
docker-compose down
docker-compose up -d
在Redis容器内部启动sentinel即可,三个容器分别进入启动。
redis-sentinel sentinel.conf
启动成功之后,如果我们down掉redis1这个容器,集群会自动选举redis2或者redis3为Master.
5.6 Redis集群
Redis集群在保证主从加哨兵的基本功能之外,还能提升Redis存储数据的能力。
搭建集群
#docker-compose.yml
version: "3.1"
services:
redis1:
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
container_name: redis1
environment:
- TZ=Asia/Shanghai
ports:
- 7001:7001
- 17001:17001
volumes:
- ./conf/redis1.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis2:
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
container_name: redis2
environment:
- TZ=Asia/Shanghai
ports:
- 7002:7002
- 17002:17002
volumes:
- ./conf/redis2.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis3:
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
container_name: redis3
environment:
- TZ=Asia/Shanghai
ports:
- 7003:7003
- 17003:17003
volumes:
- ./conf/redis3.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis4:
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
container_name: redis4
environment:
- TZ=Asia/Shanghai
ports:
- 7004:7004
- 17004:17004
volumes:
- ./conf/redis4.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis5:
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
container_name: redis5
environment:
- TZ=Asia/Shanghai
ports:
- 7005:7005
- 17005:17005
volumes:
- ./conf/redis5.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
redis6:
image: daocloud.io/library/redis:5.0.7
#容器总是从新启动
restart: always
container_name: redis6
environment:
- TZ=Asia/Shanghai
ports:
- 7006:7006
- 17006:17006
volumes:
- ./conf/redis6.conf:/usr/local/redis/redis.conf
command: ["redis-server","/usr/local/redis/redis.conf"]
#redis.conf
# 指定redis的端口号
port 7001
#开启redis集群
cluster-enabled yes
# 集群信息的文件
cluster-config-file nodes-7001.conf
# 集群的对外ip地址
cluster-announce-ip 192.168.102.11
# 集群的对外端口号
cluster-announce-port 7001
#集群的总线端口号
cluster-announce-bus-port 17001
启动6个Redis的节点。
随便跳转到一个容器内部,使用redis-cli管理集群,他会自动分配好主从节点以及hash槽
redis-cli --cluster create 192.168.102.11:7001 192.168.102.11:7002 192.168.102.11:7003 192.168.102.11:7004 192.168.102.11:7005 192.168.102.11:7006 --cluster-replicas 1
测试
使用redis-cli -h 192.168.102.11 -p 7001 连接指定一个redis节点,此时set key可能设置不进去,因为通过计算key应该在另外的节点。如果需要在客户端连接,但是set数据能跳转到其他节点set,连接命令需要加-c,如下
redis-cli -h 192.168.102.11 -p 7001 -c
5.7 Java连接Redis集群
使用JedisCluster对象连接Redis集群
public class Demo5 {
public void clusterTest(){
//创建Set<HostAndPort>
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("192.168.102.11",7001));
nodes.add(new HostAndPort("192.168.102.11",7002));
nodes.add(new HostAndPort("192.168.102.11",7003));
nodes.add(new HostAndPort("192.168.102.11",7004));
nodes.add(new HostAndPort("192.168.102.11",7005));
nodes.add(new HostAndPort("192.168.102.11",7006));
//创建jedisCluster集群对象
JedisCluster jedisCluster = new JedisCluster(nodes);
String value = jedisCluster.get("a");
System.out.println(value);
}
}
六 Redis常见问题
6.1 key的生存时间到了,Redis会立即删除吗?
不会立即删除
定期删除:
Redis每隔一段时间就会去查看Redis设置了过期时间的key,会在大概100ms的间隔中默认查看3个key.
惰性删除
当去查询一个已经过了生存时间的key时,Redis会先查看当前key的生存时间是否已经到了,直接删除当前key,并且给用户返回一个空值。
6.2 Redis的淘汰机制
在Redis内存已经满的时候,添加一个新的数据,就会执行淘汰策略。
volatile-lru:在内存不足时,Redis会在设置了过期时间的key中淘汰掉一个最近最少使用的key
allkeys-lru:在内存不足时,Redis会在全部的key中淘汰掉一个最近最少使用的key
volatile-lfu:在内存不足时,Redis会在设置了过期时间的key中淘汰掉一个最近最少频次使用的key
allkeys-lfu:在内存不足时,Redis会在全部的key中淘汰掉一个最近最少频次使用的key
volatile-random:在内存不足时,Redis会在设置了过期时间的key中随机淘汰掉一个key
allkeys-random:在内存不足时,Redis会在全部的key中随机淘汰掉一个key
volatile-ttl:在内存不足时,Redis会在设置了过期时间的key中随机淘汰掉一个剩余生存时间最少的key
noeviction:(默认):在内存不足时,直接报错
指定淘汰机制的方式:maxmemory-policy noeviction(具体策略)
设置Redis最大内存:maxmemory <bytes>
6.3 缓存的常见问题
缓存穿透
问题出现的原因:查询的数据,Redis中没有,数据库中也没有。如何解决?
-
根据Id查询时,如果id是自增的,将id的最大值放到Redis中,在查询数据库之前,直接比较一下id.
-
如果id不是整形的,可以将全部id放到set中,在用户查询之前,去set中查看一些是否有这个id.
-
获取客户端的ip地址,可以将ip的访问添加限制。
-
将访问的key直接在Redis中缓存一个空值,下次访问的时候可直接查redis放回空值
-
根据缓存数据Key的设计规则,将不符合规则的key采用布隆过滤器进行过滤
缓存击穿
问题出现的原因:缓存中的热点数据,突然到期了,造成大量的请求都去访问数据库,造成数据库宕机
-
在访问缓存中没有的时候,添加一个锁,让几个请求去访问数据库,避免数据库宕机
-
去掉热点数据的生存时间
缓存雪崩
问题出现的原因:当大量缓存同时到期时,最终大量的同时去访问数据库,导致数据库宕机
-
将缓存中的数据设置不同的生存时间,例如设置为30~60分钟的要给随机时间
缓存倾斜
问题出现的原因:热点数据放在一个Reids节点上,导致Redis节点无法承受住大量的请求,最终导致Redis宕机。
-
扩展主从架构,搭建多个从节点,缓解Redis的压力
-
可以在Tomcat中做JVM缓存,在查询Redis之前,先去查询Tomcat中的缓存。
好了,本次Redis学习就到这里了。相关的示例代码已上传码云,学习文档也已放百度云,仓库地址和文档地址可关注"良辰"公众号,回复"学无止境"或者"redis"获取
学无止境,关注我,我们一起进步。如果觉得文章还可以,点个赞,点个在看呗,谢谢~我们下期见。
上一篇: 图的深度优先搜索和广度优先搜索C语言实现
下一篇: SpringCloud总结
推荐阅读