Redis高可用集群搭建
- redis集群搭建
redis集群需要至少三个master节点,我们这里搭建三个master节点,并且给每个master再搭建一个slave节点,总共6个redis节点,这里用三台机器部署6个redis实例,每台机器一主一从。注意,最好是关闭防火墙,如果非要开启的话那要注意redis集群之间是通过gossip进行通讯的,会用到18001、18002(默认是在你redis.conf文件中设置的port端口加10000)端口进行通讯,这些端口要开放好,搭建集群的步骤如下:
第一步:在第一台机器的cd /usr/local/redis/bin/下
(1)mkdir cluster #创建文件夹cluster
(2)mkdir 8001 8002 #在redis-cluster下面分别创建2个文件夾
(3)cd /usr/local/redis/bin/ 下: cp redis.conf ./cluster/8001/
第二步:把之前的redis.conf配置文件copy到8001下,修改如下内容:
(1)daemonize yes
(2)port 8001(分别对每个机器的端口号进行设置)
(3)pidfile /var/run/redis_8001.pid # 把pid进程号写入pidfile配置的文件
(4)dir /usr/local/redis/bin/cluster/8001/(指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据)
(5)cluster-enabled yes(启动集群模式)
(6)cluster-config-file nodes-8001.conf(集群节点信息文件,这里800x最好和port对应上)
(7)cluster-node-timeout 10000
(8)#bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
(9)protected-mode no (关闭保护模式)
(10)appendonly yes #这里也可以配置为混合持久存储模式
(11)requirepass azhi2021888 (设置redis访问密码)
(12)masterauth azhi2021888 (设置集群节点间访问密码,跟上面一致)
第三步:把修改后的配置文件,copy到8002中,修改第2、3、4、6项里的端口号,
可以用批量替换vim redis.cinf,输入:%s/8001/8002/g回车即可完成替换。
第四步:另外两台机器也需要做上面三步操作,
可以直接把第一台机器上配置好的两个redis.conf文件分别复制到其它机器上的8001和8002文件夹里,不需要做其它变更。
第五步:开始分别启动6个redis实例,然后检查是否启动成功
(1)/usr/local/redis/bin/redis-server /usr/local/redis/bin/cluster/8001/redis.conf
(2)/usr/local/redis/bin/redis-server /usr/local/redis/bin/cluster/8002/redis.conf
(3)ps -ef | grep redis 查看是否启动成功
第六步:用redis-cli创建整个redis集群(redis5以前的版本集群是依靠ruby脚本redis-trib.rb实现)
# 下面命令里的1代表为每个创建的主服务器节点创建一个从服务器节点
# 执行这条命令需要确认三台机器之间的redis实例要能相互访问,可以先简单把所有机器防火墙关掉,如果不关闭防火墙则需要打开redis服务端口和集群节点gossip通信端口16379(默认是在redis端口号上加1W)
# 关闭防火墙
# systemctl stop firewalld # 临时关闭防火墙
# systemctl disable firewalld # 禁止开机启动
# 在随意一台主机上执行以下命令,注意:下面这条创建集群的命令尽量不要直接复制,里面的空格编码可能有问题导致创建集群不成功
(1)./redis-cli -a azhi2021888 --cluster create --cluster-replicas 1 192.168.0.200:8001 192.168.0.166:8001 192.168.0.72:8001 192.168.0.200:8002 192.168.0.166:8002 192.168.0.72:8002
第七步:验证集群:
(1)连接任意一个客户端即可:./redis-cli -c -h -p (-a访问服务端密码,-c表示集群模式,指定ip地址和端口号)
如:./redis-cli -a azhi2021888 -c -h 192.168.0.200 -p 800*
(2)进行验证: cluster info(查看集群信息)、cluster nodes(查看节点列表)
(3)进行数据操作验证
(4)关闭集群则需要逐个进行关闭,使用命令:
./redis-cli -a azhi2021888 -c -h 192.168.0.200 -p 800* shutdown
第八步:至此完成redis集群。
./redis-cli -a azhi2021888 —cluster create —cluster-replicas 1 192.168.0.200:8001 192.168.0.166:8001 192.168.0.72:8001 192.168.0.200:8002 192.168.0.166:8002 192.168.0.72:8002
执行结果图如下:集群完美运行,也可以看出:
1.上面命令语句中配置从数量为1,那么cluster会每一个主节点配置1个从节点,如果写的是2那么会为每个主节点配置两个从节点。
2.可以看出如果设置1个从节点的话cluster会将前面三个当做主节点,后面三个当作从节点进行配置。
3.cluster默认会帮我们分配好slots到各个主节点上,从0-16383
4.cluster会自动进行从节点错位备份。
如192.168.0.200的主节点由192.168.0.72的从节点进行备份了
192.168.0.166的主节点由192.168.0.200的从节点进行备份了
192.168.0.72的主节点由192.168.0.166的从节点进行备份了
5.操作测试:azhi这个key分配到200这个机器上,而zhi这个key被分配到166机器上,这也是redis用hash算法实现的分片存储功能。
jedis操作redis集群
借助redis的java客户端jedis可以操作以上集群,引用jedis版本的maven坐标如下:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
jedis编写访问redis集群的代码非常简单,如下所示:
class JedisClusterTest {
public static void main(String[] args) throws IOException {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(20);
config.setMaxIdle(10);
config.setMinIdle(5);
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.0.200", 8001));
jedisClusterNode.add(new HostAndPort("192.168.0.200", 8002));
jedisClusterNode.add(new HostAndPort("192.168.0.166", 8001));
jedisClusterNode.add(new HostAndPort("192.168.0.166", 8002));
jedisClusterNode.add(new HostAndPort("192.168.0.72", 8001));
jedisClusterNode.add(new HostAndPort("192.168.0.72", 8002));
System.out.println("starting...");
JedisCluster jedisCluster = null;
try {
//connectionTimeout:指的是连接一个url的连接等待时间
//soTimeout:指的是连接上一个url,获取response的返回等待时间
jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "azhi2021888", config);
System.out.println(jedisCluster.set("dlz", "888"));
System.out.println(jedisCluster.get("dlz"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedisCluster != null)
jedisCluster.close();
}
System.out.println("end...");
}
}
在服务器上的运行效果如下:
./redis-cli -a azhi2021888 -c -h 192.168.0.200 -p 8001查看一下分配到那里去了。
下面试一下spring-boot-starter-data-redis访问redis集群的DEMO代码:
1、引入相关依赖和配置打包jar:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
</dependencies>
<!--打包jar-->
<build>
<finalName>test2</finalName>
<plugins>
<!--spring-boot-maven-plugin-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.2</version>
<!--解决打包出来的jar文件中没有主清单属性问题-->
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
2.springboot项目核心配置:
集群配置
server:
port: 8080
spring:
redis:
database: 0
timeout: 3000
password: azhi2021888
cluster:
nodes: 192.168.0.200:8001,192.168.0.200:8002,192.168.0.166:8001,192.168.0.166:8002,192.168.0.72:8001,192.168.0.72:8002
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
单机配置
server:
port: 8080
spring:
redis:
host: login.linux.zymapp.com
password: xxxx
port: 6379
database: 1
3.访问代码:
@RestController
public class IndexController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping("/set")
public String set(@RequestParam("key") String key,@RequestParam("value") String value) throws InterruptedException {
try{
stringRedisTemplate.opsForValue().set(key, value);
}
catch (Exception ex){
return "出错了:" + ex.getMessage();
}
return "写入成功:" + stringRedisTemplate.opsForValue().get(key);
}
@GetMapping("/get")
public String get(@RequestParam("key") String key) throws InterruptedException {
try{
return "读取成功:" + stringRedisTemplate.opsForValue().get(key);
}
catch (Exception ex){
return "出错了:" + ex.getMessage();
}
}
}
jar启动:
写入测试:
读取测试:
至此,cluster集群与springboot操作集群已全部测试完毕。
StringRedisTemplate与RedisTemplate详解
spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。在RedisTemplate中提供了几个常用的接口方法的使用,分别是:
private ValueOperations<K, V> valueOps;
private HashOperations<K, V> hashOps;
private ListOperations<K, V> listOps;
private SetOperations<K, V> setOps;
private ZSetOperations<K, V> zSetOps;
RedisTemplate中定义了对5种数据结构操作
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
StringRedisTemplate继承自RedisTemplate,也一样拥有上面这些操作。
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
Redis客户端命令对应的RedisTemplate中的方法列表:
String类型结构 | |
---|---|
Redis | RedisTemplate rt |
set key value | rt.opsForValue().set(“key”,”value”) |
get key | rt.opsForValue().get(“key”) |
del key | rt.delete(“key”) |
strlen key | rt.opsForValue().size(“key”) |
getset key value | rt.opsForValue().getAndSet(“key”,”value”) |
getrange key start end | rt.opsForValue().get(“key”,start,end) |
append key value | rt.opsForValue().append(“key”,”value”) |
Hash结构 | |
hmset key field1 value1 field2 value2… | rt.opsForHash().putAll(“key”,map) //map是一个集合对象 |
hset key field value | rt.opsForHash().put(“key”,”field”,”value”) |
hexists key field | rt.opsForHash().hasKey(“key”,”field”) |
hgetall key | rt.opsForHash().entries(“key”) //返回Map对象 |
hvals key | rt.opsForHash().values(“key”) //返回List对象 |
hkeys key | rt.opsForHash().keys(“key”) //返回List对象 |
hmget key field1 field2… | rt.opsForHash().multiGet(“key”,keyList) |
hsetnx key field value | rt.opsForHash().putIfAbsent(“key”,”field”,”value” |
hdel key field1 field2 | rt.opsForHash().delete(“key”,”field1”,”field2”) |
hget key field | rt.opsForHash().get(“key”,”field”) |
List结构 | |
lpush list node1 node2 node3… | rt.opsForList().leftPush(“list”,”node”) |
rt.opsForList().leftPushAll(“list”,list) //list是集合对象 | |
rpush list node1 node2 node3… | rt.opsForList().rightPush(“list”,”node”) |
rt.opsForList().rightPushAll(“list”,list) //list是集合对象 | |
lindex key index | rt.opsForList().index(“list”, index) |
llen key | rt.opsForList().size(“key”) |
lpop key | rt.opsForList().leftPop(“key”) |
rpop key | rt.opsForList().rightPop(“key”) |
lpushx list node | rt.opsForList().leftPushIfPresent(“list”,”node”) |
rpushx list node | rt.opsForList().rightPushIfPresent(“list”,”node”) |
lrange list start end | rt.opsForList().range(“list”,start,end) |
lrem list count value | rt.opsForList().remove(“list”,count,”value”) |
lset key index value | rt.opsForList().set(“list”,index,”value”) |
Set结构 | |
sadd key member1 member2… | rt.boundSetOps(“key”).add(“member1”,”member2”,…) |
rt.opsForSet().add(“key”, set) //set是一个集合对象 | |
scard key | rt.opsForSet().size(“key”) |
sidff key1 key2 | rt.opsForSet().difference(“key1”,”key2”) //返回一个集合对象 |
sinter key1 key2 | rt.opsForSet().intersect(“key1”,”key2”)//同上 |
sunion key1 key2 | rt.opsForSet().union(“key1”,”key2”)//同上 |
sdiffstore des key1 key2 | rt.opsForSet().differenceAndStore(“key1”,”key2”,”des”) |
sinter des key1 key2 | rt.opsForSet().intersectAndStore(“key1”,”key2”,”des”) |
sunionstore des key1 key2 | rt.opsForSet().unionAndStore(“key1”,”key2”,”des”) |
sismember key member | rt.opsForSet().isMember(“key”,”member”) |
smembers key | rt.opsForSet().members(“key”) |
spop key | rt.opsForSet().pop(“key”) |
srandmember key count | rt.opsForSet().randomMember(“key”,count) |
srem key member1 member2… | rt.opsForSet().remove(“key”,”member1”,”member2”,…) |