docker 部署redis的一些坑

lishihuan大约 7 分钟

docker 部署redis的一些坑

https://gitee.com/jkh95/redis-learning/tree/mainopen in new window

一、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是不行的,一直到主从复制都是open in new window 可以的,但是哨兵他获取的时候内网的ip,他找不到对应的主的ip就把192.168.79.128:6379变成了从。

获取内网ip的方式:docker inspect + 容器id。

docker获取内网ip
docker获取内网ip

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

docker哨兵配置IP问题
docker哨兵配置IP问题

五、命令模板:搭建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"