java架构之路-(Redis专题)SpringBoot连接Redis超简单
上次我们搭建了 Redis 的主从架构,哨兵架构以及我们的集群架构,但是我们一直还未投入到实战中去,这次我们用 jedis 和 springboot 两种方式来操作一下我们的 redis
主从架构
如何配置我上次已经讲过了,https://www.cnblogs.com/cxiaocai/p/11711377.html。我们这次主要看如何用 java 来操作 redis,先来复习一下上次的配置,准备三台服务器,安装 redis,保证互通,两台改为 slave,配置replicaof IP 端口,主从复制是通过 rdb 文件来复制的。大概就这么多,配置不再多说了,我们直接上代码。
jedis
创建一个 Maven 项目,引入我们的 jedis 依赖包
<!-- 加入 jedis 依赖 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
这里我就不封装连接工具类啦,我们直接看下测试代码吧
public static void main(String[] args) { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(20); jedisPoolConfig.setMaxIdle(10); jedisPoolConfig.setMinIdle(5); // timeout,这里既是连接超时又是读写超时,从 Jedis 2.8 开始有区分 connectionTimeout 和 soTimeout 的构造函数 JedisPool jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 3000, null);Jedis jedis </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">从redis连接池里拿出一个连接执行命令</span> jedis =<span style="color: rgba(0, 0, 0, 1)"> jedisPool.getResource(); System.out.println(jedis.set(</span>"xiaocai", "666"<span style="color: rgba(0, 0, 0, 1)">)); System.out.println(jedis.get(</span>"xiaocai"<span style="color: rgba(0, 0, 0, 1)">)); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { e.printStackTrace(); } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (jedis != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) jedis.close(); }
}
主从的连接很简单的,设置连接参数,直接连接就可以操作我们的 redis 了,主从的连接,就是基本的链接。
springboot
我们再来看一下 springboot 怎么来连接吧,建立一个 springboot 项目,什么也不用设置,建立一个最简单的就可以的,首先还是加入我们的 Maven 驱动包
<!-- 加入 redis 连接池--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
加入我们的 redis 的配置信息,放置在 application.yml 文件下即可。
spring: redis: database: 0 # Redis 数据库索引(默认为 0) host: 127.0.0.1 # Redis 服务器地址 port: 6379 # Redis 服务器连接端口 password: # Redis 服务器连接密码(默认为空) timeout: 5000 # 连接超时时间(毫秒) jedis: pool: max-active: 8 # 连接池最大连接数(使用负值表示没有限制) max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-idle: 8 # 连接池中的最大空闲连接 min-idle: 0 # 连接池中的最小空闲连接
这个也就不存在什么工具类了,内部都已经封装好了,我们直接来上测试类吧
package com.redisclient.master_slave;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.data.redis.core.StringRedisTemplate;@RestController
public class RedisMasterSlave {@Autowired </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> StringRedisTemplate stringRedisTemplate; @GetMapping(value </span>= "/"<span style="color: rgba(0, 0, 0, 1)">) </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getIndex(){ stringRedisTemplate.opsForValue().set(</span>"xiaocai", "888"<span style="color: rgba(0, 0, 0, 1)">); System.out.println(stringRedisTemplate.opsForValue().get(</span>"xiaocai"<span style="color: rgba(0, 0, 0, 1)">)); </span><span style="color: rgba(0, 0, 255, 1)">return</span> "小菜技术"<span style="color: rgba(0, 0, 0, 1)">; }
}
相比我们上面的 jedis 还要简单,直接自动装配一下我们的 StringRedisTemplate 即可,剩下的就是我们的 redis 操作了,五种数据的基本操作如下。
redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作 hash redisTemplate.opsForList();//操作 list redisTemplate.opsForSet();//操作 set redisTemplate.opsForZSet();//操作有序 set
还有什么更多的操作不知道的,欢迎来我的公众号内问答,我会依依给你解释清楚的。
哨兵架构
哨兵架构,是主从的升级版,master 节点宕机后,可以主动选取我们的 master 节点。
我们还是分为 jedis 和 springboot 两种方式分别来尝试连接一下我们的哨兵架构,搭建我就不说啦,上次博文已经说过了。https://www.cnblogs.com/cxiaocai/p/11711377.html
jedis
还是老规矩,引入依赖,剩下的我们直接来写测试类吧。
package com.redisclient.sentinel;import redis.clients.jedis.*;
import java.util.HashSet;
import java.util.Set;public class JedisClientSentinel {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> main(String[] args) { JedisPoolConfig jedisPoolConfig </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JedisPoolConfig(); jedisPoolConfig.setMaxTotal(</span>20<span style="color: rgba(0, 0, 0, 1)">); jedisPoolConfig.setMaxIdle(</span>10<span style="color: rgba(0, 0, 0, 1)">); jedisPoolConfig.setMinIdle(</span>5<span style="color: rgba(0, 0, 0, 1)">); String masterName </span>= "mymaster"<span style="color: rgba(0, 0, 0, 1)">; Set</span><String> sentinels = <span style="color: rgba(0, 0, 255, 1)">new</span> HashSet<String><span style="color: rgba(0, 0, 0, 1)">(); sentinels.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("47.105.97.79", 6380<span style="color: rgba(0, 0, 0, 1)">).toString()); sentinels.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("47.105.97.142", 6380<span style="color: rgba(0, 0, 0, 1)">).toString()); sentinels.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("118.190.147.181", 6380<span style="color: rgba(0, 0, 0, 1)">).toString()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">JedisSentinelPool其实本质跟JedisPool类似,都是与redis主节点建立的连接池 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">JedisSentinelPool并不是说与sentinel建立的连接池,而是通过sentinel发现redis主节点并与其建立连接</span> JedisSentinelPool jedisPool = <span style="color: rgba(0, 0, 255, 1)">new</span> JedisSentinelPool(masterName, sentinels, jedisPoolConfig, 5000, <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); Jedis jedis </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">从redis连接池里拿出一个连接执行命令</span> jedis =<span style="color: rgba(0, 0, 0, 1)"> jedisPool.getResource(); System.out.println(jedis.set(</span>"xiaocai888", "666888"<span style="color: rgba(0, 0, 0, 1)">)); System.out.println(jedis.get(</span>"xiaocai888"<span style="color: rgba(0, 0, 0, 1)">)); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { e.printStackTrace(); } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (jedis != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) jedis.close(); } }
}
最简单的理解就是我们建立了一个 set 连接池。当哨兵节点宕机一个的时候,会尝试连接其它节点,当 master 节点宕机时,会报错连接错误,稍后会自动恢复的。 ✨
springboot
我们先来修改一下我们 yml 配置文件
#哨兵模式
spring:
redis:
database: 0 # Redis 数据库索引(默认为 0)
host: 120.27.27.4 # Redis 服务器地址
port: 6379 # Redis 服务器连接端口
password: # Redis 服务器连接密码(默认为空)
timeout: 5000 # 连接超时时间(毫秒)
sentinel:
master: mymaster #主服务器所在集群名称
nodes: 115.28.208.105:26379,47.105.92.89:26379,118.190.151.92:26379
而我们的测试类和主从是一样的配置就可以了,springboot 主要改一下配置就可以了。
集群架构
上次我们说过了我们的集群架构,就是很多个小主从集合在一起,内部有半数机制,建议设置大于 3 的奇数个主从服务。
这从我们来搭建三组一主一从服务,我们先来看一下 jedis 的代码,我们内部 redis 是将 16384 分为了三个片区,为了确保每个片区都存入数据我们采用了随机生成的 key。
jedis
package com.redisclient.cluster;import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.*;
import java.io.IOException;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;public class JedisClientCluster {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> main(String[] args) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> IOException { GenericObjectPoolConfig config </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> GenericObjectPoolConfig(); config.setMaxTotal(</span>20<span style="color: rgba(0, 0, 0, 1)">); config.setMaxIdle(</span>10<span style="color: rgba(0, 0, 0, 1)">); config.setMinIdle(</span>5<span style="color: rgba(0, 0, 0, 1)">); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">这里将所有主从节点全部放入</span> Set<HostAndPort> jedisClusterNode = <span style="color: rgba(0, 0, 255, 1)">new</span> HashSet<HostAndPort><span style="color: rgba(0, 0, 0, 1)">(); jedisClusterNode.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("120.27.27.4", 6379<span style="color: rgba(0, 0, 0, 1)">)); jedisClusterNode.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("47.105.86.150", 6379<span style="color: rgba(0, 0, 0, 1)">)); jedisClusterNode.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("47.105.84.186", 6379<span style="color: rgba(0, 0, 0, 1)">)); jedisClusterNode.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("115.28.208.105", 6379<span style="color: rgba(0, 0, 0, 1)">)); jedisClusterNode.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("47.105.92.89", 6379<span style="color: rgba(0, 0, 0, 1)">)); jedisClusterNode.add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> HostAndPort("118.190.151.92", 6379<span style="color: rgba(0, 0, 0, 1)">)); JedisCluster jedisCluster </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">connectionTimeout:指的是连接一个url的连接等待时间 </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">soTimeout:指的是连接上一个url,获取response的返回等待时间</span> jedisCluster = <span style="color: rgba(0, 0, 255, 1)">new</span> JedisCluster(jedisClusterNode, 6000, 5000, 10, "xiaocai"<span style="color: rgba(0, 0, 0, 1)">, config); </span><span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)"> { jedisCluster.set(getRandomString(</span>4), "value" + getRandomString(4<span style="color: rgba(0, 0, 0, 1)">)); Thread.sleep(</span>5000<span style="color: rgba(0, 0, 0, 1)">); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { e.printStackTrace(); } } } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { e.printStackTrace(); } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (jedisCluster != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) jedisCluster.close(); } } </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">length用户要求产生字符串的长度</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> String getRandomString(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> length) { String str </span>= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"<span style="color: rgba(0, 0, 0, 1)">; Random random </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Random(); StringBuffer sb </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StringBuffer(); </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">int</span> i = 0; i < length; i++<span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">int</span> number = random.nextInt(62<span style="color: rgba(0, 0, 0, 1)">); sb.append(str.charAt(number)); } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sb.toString(); }
}
springboot
还是老规矩改一下配置类
# 集群模式
spring:
redis:
database: 0 # Redis 数据库索引(默认为 0)
host: 120.27.27.4 # Redis 服务器地址
port: 6379 # Redis 服务器连接端口
password: xiaocai # Redis 服务器连接密码(默认为空)
timeout: 5000 # 连接超时时间(毫秒)
cluster:
nodes: 115.28.208.105:6379,47.105.92.89:6379,118.190.151.92:6379
说到这里我们的三种模式的连接就全部写完了,这些使用还是一个比一个简单的,我们来看下内部的通讯和选举机制吧。
选举机制
当 slave 发现自己的 master 变为 FAIL 状态时,便尝试进行 Failover,以期成为新的 master。由于挂掉的 master 可能会有 多个 slave,从而存在多个 slave 竞争成为 master 节点的过程, 其过程如下:
1.slave 发现自己的 master 变为 FAIL
2. 将自己记录的集群 currentEpoch 加 1,并广播 FAILOVER_AUTH_REQUEST 信息
3. 其他节点收到该信息,只有 master 响应,判断请求者的合法性,并发送 FAILOVER_AUTH_ACK,对每一个 epoch 只发 送一次 ack
4. 尝试 failover 的 slave 收集 master 返回的 FAILOVER_AUTH_ACK
5.slave 收到超过半数 master 的 ack 后变成新 Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其 中一个挂了,只剩一个主节点是不能选举成功的)
6. 广播 Pong 消息通知其他集群节点。从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待 FAIL 状态在 集群中传播,slave 如果立即尝试选举,其它 masters 或许尚未意识到 FAIL 状态,可能会拒绝投票
最近公司有一些事情,就先说到这里吧,有时间给大家说一下 redis 来实现分布式锁的机制,还有一个炒鸡好用的 redis 分布式锁。
最进弄了一个公众号,小菜技术,欢迎大家的加入