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: 8080spring:redis:database: 0timeout: 3000password: azhi2021888cluster: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:8002lettuce:pool:max-idle: 50min-idle: 10max-active: 100max-wait: 1000
单机配置
server:port: 8080spring:redis:host: login.linux.zymapp.compassword: xxxxport: 6379database: 1
3.访问代码:
@RestControllerpublic class IndexController {@Autowiredprivate 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();//操作hashredisTemplate.opsForList();//操作listredisTemplate.opsForSet();//操作setredisTemplate.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”,…) |
