Redis高可用集群搭建

    • redis集群搭建

    redis集群需要至少三个master节点,我们这里搭建三个master节点,并且给每个master再搭建一个slave节点,总共6个redis节点,这里用三台机器部署6个redis实例,每台机器一主一从。注意,最好是关闭防火墙,如果非要开启的话那要注意redis集群之间是通过gossip进行通讯的,会用到18001、18002(默认是在你redis.conf文件中设置的port端口加10000)端口进行通讯,这些端口要开放好,搭建集群的步骤如下:

    1. 第一步:在第一台机器的cd /usr/local/redis/bin/下
    2. 1mkdir cluster #创建文件夹cluster
    3. 2mkdir 8001 8002 #在redis-cluster下面分别创建2个文件夾
    4. 3cd /usr/local/redis/bin/ 下: cp redis.conf ./cluster/8001/
    5. 第二步:把之前的redis.conf配置文件copy8001下,修改如下内容:
    6. 1daemonize yes
    7. 2port 8001(分别对每个机器的端口号进行设置)
    8. 3pidfile /var/run/redis_8001.pid # 把pid进程号写入pidfile配置的文件
    9. 4dir /usr/local/redis/bin/cluster/8001/(指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据)
    10. 5cluster-enabled yes(启动集群模式)
    11. 6cluster-config-file nodes-8001.conf(集群节点信息文件,这里800x最好和port对应上)
    12. 7cluster-node-timeout 10000
    13. (8)#bind 127.0.0.1bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
    14. (9)protected-mode no (关闭保护模式)
    15. (10)appendonly yes #这里也可以配置为混合持久存储模式
    16. (11)requirepass azhi2021888 (设置redis访问密码)
    17. (12)masterauth azhi2021888 (设置集群节点间访问密码,跟上面一致)
    18. 第三步:把修改后的配置文件,copy8002中,修改第2346项里的端口号,
    19. 可以用批量替换vim redis.cinf,输入:%s/8001/8002/g回车即可完成替换。
    20. 第四步:另外两台机器也需要做上面三步操作,
    21. 可以直接把第一台机器上配置好的两个redis.conf文件分别复制到其它机器上的80018002文件夹里,不需要做其它变更。
    22. 第五步:开始分别启动6redis实例,然后检查是否启动成功
    23. 1)/usr/local/redis/bin/redis-server /usr/local/redis/bin/cluster/8001/redis.conf
    24. 2)/usr/local/redis/bin/redis-server /usr/local/redis/bin/cluster/8002/redis.conf
    25. 3ps -ef | grep redis 查看是否启动成功
    26. 第六步:用redis-cli创建整个redis集群(redis5以前的版本集群是依靠ruby脚本redis-trib.rb实现)
    27. # 下面命令里的1代表为每个创建的主服务器节点创建一个从服务器节点
    28. # 执行这条命令需要确认三台机器之间的redis实例要能相互访问,可以先简单把所有机器防火墙关掉,如果不关闭防火墙则需要打开redis服务端口和集群节点gossip通信端口16379(默认是在redis端口号上加1W)
    29. # 关闭防火墙
    30. # systemctl stop firewalld # 临时关闭防火墙
    31. # systemctl disable firewalld # 禁止开机启动
    32. # 在随意一台主机上执行以下命令,注意:下面这条创建集群的命令尽量不要直接复制,里面的空格编码可能有问题导致创建集群不成功
    33. 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
    34. 第七步:验证集群:
    35. 1)连接任意一个客户端即可:./redis-cli -c -h -p (-a访问服务端密码,-c表示集群模式,指定ip地址和端口号)
    36. 如:./redis-cli -a azhi2021888 -c -h 192.168.0.200 -p 800*
    37. 2)进行验证: cluster info(查看集群信息)、cluster nodes(查看节点列表)
    38. 3)进行数据操作验证
    39. 4)关闭集群则需要逐个进行关闭,使用命令:
    40. ./redis-cli -a azhi2021888 -c -h 192.168.0.200 -p 800* shutdown
    41. 第八步:至此完成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
    image.png
    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的从节点进行备份了
    image.png
    5.操作测试:azhi这个key分配到200这个机器上,而zhi这个key被分配到166机器上,这也是redis用hash算法实现的分片存储功能。image.png
    jedis操作redis集群
    借助redis的java客户端jedis可以操作以上集群,引用jedis版本的maven坐标如下:

    1. <dependency>
    2. <groupId>redis.clients</groupId>
    3. <artifactId>jedis</artifactId>
    4. <version>2.9.0</version>
    5. </dependency>

    jedis编写访问redis集群的代码非常简单,如下所示:

    1. class JedisClusterTest {
    2. public static void main(String[] args) throws IOException {
    3. JedisPoolConfig config = new JedisPoolConfig();
    4. config.setMaxTotal(20);
    5. config.setMaxIdle(10);
    6. config.setMinIdle(5);
    7. Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
    8. jedisClusterNode.add(new HostAndPort("192.168.0.200", 8001));
    9. jedisClusterNode.add(new HostAndPort("192.168.0.200", 8002));
    10. jedisClusterNode.add(new HostAndPort("192.168.0.166", 8001));
    11. jedisClusterNode.add(new HostAndPort("192.168.0.166", 8002));
    12. jedisClusterNode.add(new HostAndPort("192.168.0.72", 8001));
    13. jedisClusterNode.add(new HostAndPort("192.168.0.72", 8002));
    14. System.out.println("starting...");
    15. JedisCluster jedisCluster = null;
    16. try {
    17. //connectionTimeout:指的是连接一个url的连接等待时间
    18. //soTimeout:指的是连接上一个url,获取response的返回等待时间
    19. jedisCluster = new JedisCluster(jedisClusterNode, 6000, 5000, 10, "azhi2021888", config);
    20. System.out.println(jedisCluster.set("dlz", "888"));
    21. System.out.println(jedisCluster.get("dlz"));
    22. } catch (Exception e) {
    23. e.printStackTrace();
    24. } finally {
    25. if (jedisCluster != null)
    26. jedisCluster.close();
    27. }
    28. System.out.println("end...");
    29. }
    30. }

    在服务器上的运行效果如下:
    image.png
    ./redis-cli -a azhi2021888 -c -h 192.168.0.200 -p 8001查看一下分配到那里去了。
    image.png

    下面试一下spring-boot-starter-data-redis访问redis集群的DEMO代码:
    1、引入相关依赖和配置打包jar:

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-web</artifactId>
    5. <version>2.3.9.RELEASE</version>
    6. </dependency>
    7. <!-- redis -->
    8. <dependency>
    9. <groupId>org.springframework.boot</groupId>
    10. <artifactId>spring-boot-starter-data-redis</artifactId>
    11. <version>2.3.9.RELEASE</version>
    12. </dependency>
    13. </dependencies>
    14. <!--打包jar-->
    15. <build>
    16. <finalName>test2</finalName>
    17. <plugins>
    18. <!--spring-boot-maven-plugin-->
    19. <plugin>
    20. <groupId>org.springframework.boot</groupId>
    21. <artifactId>spring-boot-maven-plugin</artifactId>
    22. <version>2.4.2</version>
    23. <!--解决打包出来的jar文件中没有主清单属性问题-->
    24. <executions>
    25. <execution>
    26. <goals>
    27. <goal>repackage</goal>
    28. </goals>
    29. </execution>
    30. </executions>
    31. </plugin>
    32. <plugin>
    33. <groupId>org.apache.maven.plugins</groupId>
    34. <artifactId>maven-compiler-plugin</artifactId>
    35. <configuration>
    36. <source>8</source>
    37. <target>8</target>
    38. </configuration>
    39. </plugin>
    40. </plugins>
    41. </build>

    2.springboot项目核心配置:
    集群配置

    1. server:
    2. port: 8080
    3. spring:
    4. redis:
    5. database: 0
    6. timeout: 3000
    7. password: azhi2021888
    8. cluster:
    9. 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
    10. lettuce:
    11. pool:
    12. max-idle: 50
    13. min-idle: 10
    14. max-active: 100
    15. max-wait: 1000

    单机配置

    1. server:
    2. port: 8080
    3. spring:
    4. redis:
    5. host: login.linux.zymapp.com
    6. password: xxxx
    7. port: 6379
    8. database: 1

    3.访问代码:

    1. @RestController
    2. public class IndexController {
    3. @Autowired
    4. private StringRedisTemplate stringRedisTemplate;
    5. @GetMapping("/set")
    6. public String set(@RequestParam("key") String key,@RequestParam("value") String value) throws InterruptedException {
    7. try{
    8. stringRedisTemplate.opsForValue().set(key, value);
    9. }
    10. catch (Exception ex){
    11. return "出错了:" + ex.getMessage();
    12. }
    13. return "写入成功:" + stringRedisTemplate.opsForValue().get(key);
    14. }
    15. @GetMapping("/get")
    16. public String get(@RequestParam("key") String key) throws InterruptedException {
    17. try{
    18. return "读取成功:" + stringRedisTemplate.opsForValue().get(key);
    19. }
    20. catch (Exception ex){
    21. return "出错了:" + ex.getMessage();
    22. }
    23. }
    24. }

    jar启动:
    image.png
    写入测试:
    image.png
    读取测试:
    image.png
    至此,cluster集群与springboot操作集群已全部测试完毕。

    StringRedisTemplate与RedisTemplate详解
    spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。在RedisTemplate中提供了几个常用的接口方法的使用,分别是:

    1. private ValueOperations<K, V> valueOps;
    2. private HashOperations<K, V> hashOps;
    3. private ListOperations<K, V> listOps;
    4. private SetOperations<K, V> setOps;
    5. private ZSetOperations<K, V> zSetOps;

    RedisTemplate中定义了对5种数据结构操作

    1. redisTemplate.opsForValue();//操作字符串
    2. redisTemplate.opsForHash();//操作hash
    3. redisTemplate.opsForList();//操作list
    4. redisTemplate.opsForSet();//操作set
    5. 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”,…)