四 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"获取
学无止境,关注我,我们一起进步。如果觉得文章还可以,点个赞,点个在看呗,谢谢~我们下期见。