docker 部署redis的一些坑
docker 部署redis的一些坑
https://gitee.com/jkh95/redis-learning/tree/main
一、docker的redis默认没有redis.conf
先确认自己docker的redis的版本号,然后去官网去下载linux版本对应的tar包,然后解压获取其中的redis.conf
然后使用数据卷的方式,启动docker的redis即可
启动命令如下:
docker run -d -p 6379:6379 -v /opt/redis/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis/data:/data --name redis-test redis redis-server /usr/local/etc/redis/redis.conf
二、使用上述的方式外界链接不了
如果你使用自定义的redis.conf去启动docker的redis,但是使用redis界面管理工具链接不上,需要把redis.conf中的bind去除即可。
三、主从复制/读写分离
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4、高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
四、哨兵模式下ip的问题
1.问题
原来我本地一直是192.168.79.128,所以的1主2从,的地址分辨是192.168.79.128:6379、192.168.79.128:6380、192.168.79.128:6381,哨兵的ip:192.168.79.128:26379。然后把哨兵开启后,主直接变成从,结果有3个从。
2.问题定位
然后搜了各种博客https://blog.csdn.net/yhk724555508/article/details/85338706,终于发现,docker下redis的ip使用192.168.79.128是不行的,一直到主从复制都是 可以的,但是哨兵他获取的时候内网的ip,他找不到对应的主的ip就把192.168.79.128:6379变成了从。
获取内网ip的方式:docker inspect + 容器id。

3.解决
把自己所有的redis的内网ip都记下来,然后配置主从关系的时候 使用这个ip,而不是一般的192.168XXXXX。 但是在sentinel.conf的时候 还是要写192.168xxxxx,因为172.17.0.x 是docker内部使用 可以,外面的还是不行
redis-6379:172.17.0.3:6379 redis-6380:172.17.0.4:6379 redis-6381:172.17.0.5:6379

五、命令模板:搭建redis的1主2从的部署
1.创建docker容器
#!/bin/bash
echo "开始创建redis主从复制体系~~"
docker run -d -p 6379:6379 -v /opt/redis/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis/data:/data --name redis-test redis redis-server /usr/local/etc/redis/redis.conf
docker run -d -p 6380:6379 -v /opt/redis6380/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6380/data:/data --name redis-test6380 redis redis-server /usr/local/etc/redis/redis.conf
docker run -d -p 6381:6379 -v /opt/redis6381/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6381/data:/data --name redis-test6381 redis redis-server /usr/local/etc/redis/redis.conf
echo "创建完毕~~"
2.删除
#!/bin/bash
docker rm -f $(docker ps -aq)
echo "删除完毕~~"
3.哨兵
echo "开始创建~~"
docker run -d -p 26379:26379 --name sentinel6379 redis
docker run -d -p 26380:26379 --name sentinel6380 redis
docker run -d -p 26381:26379 --name sentinel6381 redis
docker cp /opt/redis/sentinel.conf sentinel6379:/
docker cp /opt/redis/sentinel.conf sentinel6380:/
docker cp /opt/redis/sentinel.conf sentinel6381:/
echo "创建完毕~~"
六、集群
1.内网问题
部署集群之前,需要注意是,因为集群节点只要交流以及外网项目要访问docker容器内的redis,所有会出现ping不通的情况,如下图

一种解决方案是让Docker使用 host模式 的网络连接类型,Docker在使用host模式下创建的容器是没有自己独立的网络命名空间的,是跟物理机共享一个网络空间,进而可以共享物理机的所有端口与IP,这样就可以让公共网络直接访问容器了,尽管这种方式有安全隐患,但目前来说还没找到其他可行性模式。
解决办法:
就存在的问题我们重新采用 host模式,重新创建一下容器,使用如下命令。
跟之前创建命令不同,一是指定了 --net 网络类型为 host,二是这种情况下就不需要端口映射了,比如 -p 6379:6379,因为此时需要对外共享容器端口服务,所以只需要指定对外暴露的端口 -p 6379、-p 6380 等。
rm -rf /opt/redis/data
mkdir /opt/redis/data
rm -rf /opt/redis6380/data
mkdir /opt/redis6380/data
rm -rf /opt/redis6381/data
mkdir /opt/redis6381/data
rm -rf /opt/redis6382/data
mkdir /opt/redis6382/data
rm -rf /opt/redis6383/data
mkdir /opt/redis6383/data
rm -rf /opt/redis6384/data
mkdir /opt/redis6384/data
# 172.17.0.2 ---> 172.17.0.6
docker run -d --net host -v /opt/redis/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis/data:/data --name redis-test redis redis-server /usr/local/etc/redis/redis.conf --port 6379
# 172.17.0.3 ---> 172.17.0.7
docker run -d --net host -v /opt/redis6380/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6380/data:/data --name redis-test6380 redis redis-server /usr/local/etc/redis/redis.conf --port 6380
# 172.17.0.4 ---> 172.17.0.5
docker run -d --net host -v /opt/redis6381/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6381/data:/data --name redis-test6381 redis redis-server /usr/local/etc/redis/redis.conf --port 6381
# 172.17.0.5
docker run -d --net host -v /opt/redis6382/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6382/data:/data --name redis-test6382 redis redis-server /usr/local/etc/redis/redis.conf --port 6382
# 172.17.0.6
docker run -d --net host -v /opt/redis6383/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6383/data:/data --name redis-test6383 redis redis-server /usr/local/etc/redis/redis.conf --port 6383
# 172.17.0.7
docker run -d --net host -v /opt/redis6384/redis.conf:/usr/local/etc/redis/redis.conf -v /opt/redis6384/data:/data --name redis-test6384 redis redis-server /usr/local/etc/redis/redis.conf --port 6384
# 绑定集群关系
redis-cli --cluster create 192.168.79.128:6379 192.168.79.128:6380 192.168.79.128:6381 192.168.79.128:6382 192.168.79.128:6383 192.168.79.128:6384 --cluster-replicas 1
2.springboot整合redis集群
主要思想是让springboot管理JedisCluster。redis集群就是这个这个类操作的
spring:
redis:
host: 192.168.79.128
port: 6379
password:
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 0ms
# 哨兵
# sentinel:
# master: myredis
# # 哨兵的IP:Port列表
# nodes: 192.168.79.128:26379,192.168.79.128:26380,192.168.79.128:26381
# 集群
cluster:
nodes: 192.168.79.128:6379,192.168.79.128:6380,192.168.79.128:6381,192.168.79.128:6382,192.168.79.128:6383,192.168.79.128:6384
#设置key的生存时间,当key过期时,它会被自动删除;
expire-seconds: 120
#设置命令的执行时间,如果超过这个时间,则报错;
command-timeout: 5000
maxAttempts: 2 #连接失败重试次数
package com.kuang.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import java.util.HashSet;
import java.util.Set;
@Configuration
public class RedisConfig {
// @Value("${spring.redis.sentinel.nodes}")
// private String sentinelNodes;
//
// @Value("${spring.redis.sentinel.master}")
// private String masterName;
/**
* JedisPool相关配置
*/
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class ClusterPoolConfigProp {
String nodes;
int expireSeconds;
int commandTimeout;
int maxAttempts;
public String getNodes() {
return nodes;
}
public void setNodes(String nodes) {
this.nodes = nodes;
}
public int getExpireSeconds() {
return expireSeconds;
}
public void setExpireSeconds(int expireSeconds) {
this.expireSeconds = expireSeconds;
}
public int getCommandTimeout() {
return commandTimeout;
}
public void setCommandTimeout(int commandTimeout) {
this.commandTimeout = commandTimeout;
}
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
}
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
/**
*
* @author Fire Monkey
* @date 2018/3/12 下午6:53
* @return redis.clients.jedis.JedisPoolConfig
* 初始化连接池配置对象
*
*/
@Bean(value = "jedisPoolConfig")
public JedisPoolConfig initJedisPoolConfig(){
JedisPoolConfig config = new JedisPoolConfig();
//常规配置
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
return config;
}
/**
*
* @author Fire Monkey
* @date 下午7:20
* @return redis.clients.jedis.JedisSentinelPool
* 生成JedisSentinelPool并且放入Spring容器
*
*/
// @Bean
// public JedisSentinelPool jedisSentinelPool(JedisPoolConfig jedisPoolConfig){
//
// Set<String> nodeSet = new HashSet<>();
// //判断字符串是否为空
// if(sentinelNodes == null || "".equals(sentinelNodes)){
// throw new RuntimeException("RedisSentinelConfiguration initialize error nodeString is null");
// }
// String[] nodeArray = sentinelNodes.split(",");
// //判断是否为空
// if(nodeArray == null || nodeArray.length == 0){
// throw new RuntimeException("RedisSentinelConfiguration initialize error nodeArray is null");
// }
// //循环注入至Set中
// for(String node : nodeArray){
// nodeSet.add(node);
// }
// //创建连接池对象
// JedisSentinelPool jedisPool = new JedisSentinelPool(masterName ,nodeSet ,jedisPoolConfig);
// return jedisPool;
// }
@Bean
public JedisCluster jedisCluster(ClusterPoolConfigProp clusterPoolConfigProp){
//获取redis集群的ip及端口号等相关信息;
String[] serverArray = clusterPoolConfigProp.getNodes().split(",");
Set<HostAndPort> nodes = new HashSet<>();
//遍历add到HostAndPort中;
for (String ipPort : serverArray) {
String[] ipPortPair = ipPort.split(":");
nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
}
//构建对象并返回;
return new JedisCluster(nodes, clusterPoolConfigProp.getCommandTimeout());
}
}
测试:把多个redis关闭后 照样可以插入redis数据。
3.集群分片
Redis集群的目的是实现数据的横向伸缩,把一块数据分片保存到多个机器,可以横向扩展数据库大小,扩展带宽,计算能力等
也就是把redis的数据分别存在不同的节点上,比如 10W个数据,现在有3主3从,其实也就是3块。那么每一块分的3W3的数据。
实验:插入多个数据,然后在不同的redis中 执行keys * 命令,观察
127.0.0.1:6379> set test_key_06 1
OK
127.0.0.1:6379> set test_key_05 1
-> Redirected to slot [15517] located at 192.168.79.128:6384
OK
192.168.79.128:6384> set test_key_04 1
OK
192.168.79.128:6384> set test_key_03 1
-> Redirected to slot [7259] located at 192.168.79.128:6383
OK
192.168.79.128:6383> set test_key_02 1
-> Redirected to slot [3194] located at 192.168.79.128:6379
OK
192.168.79.128:6379> set test_key_01 1
-> Redirected to slot [15385] located at 192.168.79.128:6384
OK
192.168.79.128:6384> set test_key_07 1
-> Redirected to slot [7391] located at 192.168.79.128:6383
OK
192.168.79.128:6383> set test_key_08 1
-> Redirected to slot [11568] located at 192.168.79.128:6384
OK
192.168.79.128:6384> set test_key_09 1
OK
192.168.79.128:6384> set test_key_10 1
-> Redirected to slot [7945] located at 192.168.79.128:6383
OK
登录6383的节点上 keys * 查看
192.168.79.128:6383> keys *
1) "test_key_03"
2) "epoint"
3) "jkh"
4) "test_key_07"
5) "test_key_10"
登录6379的节点上 keys * 查看
127.0.0.1:6379> keys *
1) "test_key_06"
2) "test_key_02"
3) "wyx"