Springboot 2.0.x 集成基于Centos7的Redis集群安装及配置
Redis简介
Redis是一个基于C语言开发的开源(BSD许可),开源高性能的高级内存数据结构存储,用作数据库、缓存和消息代理。它支持数据结构,如 字符串、散列、列表、集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。Redis具有内置复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区。
可以对这些数据类型进行原子性操作,例如 附加到字符串;递增哈希值;将元素推送到列表中;计算集合交集,并集和差异;或则在排序中获得排名最高的成员。为了实现其出色的性能,Redis使用内存数据集。根据您的配置情况,可以通过 每隔一段时间将数据集转存储到磁盘或通过每个命令附加到日志文件来保存它。如果您只需要功能丰富的网络内存缓存,则可以选择禁用持久性。
Redis还支持简单到设置的主从异步复制,具有非常快速的非阻塞第一次同步,自动重新连接以及在网络分割上的部分重新同步。
核心对象
RedisObject,图片来源:一文读懂redis
Redis五种数据结构对应的编码,图片来源:对象处理机制
五种数据结构类型,图片来源:初识Redis
1. Redis中的字符串
Redis的STRING和其他编程语言或则其他键值存储提供的字符串非常的相似。本文在使用图片表示键值的时候,通常会将键名(key name)和值的类型放在方框的顶部,将值放在方框的里面。
STRING拥有一些和其他键值存储相似的命令,比如GET(获得值)、SET(设置值)和DEL(删除值)。
2. Redis中的列表
Redis对链表(linked-list)结构的支持使得它在键值存储的世界中独树一帜。一个列表结构可以有序的存储多个字符串,和表示字符串时使用的方法一样。
list-key是一个包含三个元素的列表键,注意列表中的元素时可以重复的。
Redis列表可执行的操作和很多编程语言李米娜的列表操作非常的相似:LPUSH命令和RPUSH命令分别用户将元素推送至列表的左端(left end)和右端(right end);LPOP命令和RPOP命令分别用于从列表的左端和右端弹出元素;LINDEX命令用于获取列表在给定位置上的一个元素;LRANGE命令用于获取列表在给定范围上的所有元素。
3. Redis中的集合
Redis的集合和列表都可以存储多个字符串,它们之间的不同在于,列表可以存储多个相同的字符串,而集合则通过使用散列表来保证自己存储的每个字符都是各个相同的(这些散列表只有键,但没有与键相关联的值)。
因为Redis的集合使用无序(unordered)方式存储元素,所以用户不能像使用列表那样,将元素推入到集合的某一端,或者从结合的某一端弹出元素。不过用户可以使用SADD命令将元素添加到集合,或者使用SREM命令从集合里面移除元素。另外,还使用SISMEMBER命令快速地检查一个元素是否已经存在于集合中,或者使用SMEMBERS命令获取集合包含的所有元素(如果集合包含的元素非常多,那么SMEMBERS命令的执行速度可能会很慢,所以请谨慎地使用这个命令)。
4. Redis中的散列
Redis的散列可以存储多个键值对之间的映射。和字符串一样,散列存储的值既可以时字符串又可以时数字值,并且用户同样可以对散列存储的数字值执行自增操作或自减操作。
hash-key是一个包含两个键值对的散列键。
散列在很多方面就像是一个微缩版的Redis,好几个字符串命令都有相应的散列版本。其操作命令:HSET 在散列里面关联起给定的键值对;HGET 获取指定散列键的值;HGETALL 获取散列包含的所有键值对;HDEL 如果给定键存在于散列里面,那么移除这个键。
5. Redis中的有序集合
Redis有序集合和散列一样,都用于存储键值对,其中有序集合的每个键称为成员(member),都是独一无二的,而有序集合的每个值称为分值(score),都必须是浮点数。有序集合是Redis里面唯一既可以根据成员访问元素(这一点和散列一样),又可以根据分值以及分值的排列顺序来访问元素的结构。
zset-key是一个包含两个元素的有序集合键。
和Redis的其他结构一样,用户可以对有序集合执行添加、移除和获取等操作。
Redis集群安装及配置
服务器及节点:
192.168.56.101 7000、1001、7002;
192.168.56.102 7003、7004、7005
1. 安装GCC编译工具,不然可能会编译的过程中出现编译失败情况
yum install gcc g++ gcc-c++ make
2. 下载并安装Redis 官网下载
cd /opt 如果未找到wget命令:yum -y install wget wget http://download.redis.io/releases/redis-4.0.10.tar.gz tar xzf redis-4.0.10.tar.gz cd redis-4.0.10 make
3. 如果因为编译失败,又残留的文件
make distclean
4. 创建节点
步骤 1:
首先在 192.168.56.101机器上 /opt/redis-4.0.10目录下建 redis-cluster 目录
mkdir /opt/redis-4.0.10/redis-cluster
步骤 2:
在 redis-cluster 目录下,创建名为7000、7001、7002的目录
mkdir 7000 7001 7002
步骤 3:
分别修改这三个配置文件,把如下redis.conf 配置内容粘贴进去
vi 7000/redis.conf vi 7001/redis.conf vi 7002/redis.conf
redis.conf 示例:
port 7000 bind 192.168.56.101 daemonize yes pidfile /var/run/redis_7000.pid logfile /var/log/redis/redis_7000.log cluster-enabled yes cluster-config-file nodes_7000.conf cluster-node-timeout 10100 dbfilename dump_7000.rdb appendonly yes appendfilename "appendonly_7000.aof"
说明:
#端口7000,7001,7002 port 7000 #默认ip为127.0.0.1,需要改为其他节点机器可访问的ip,否则创建集群时无法访问对应的端口,无法创建集群 bind 192.168.56.101 #redis后台运行 daemonize yes #pidfile文件对应7000,7001,7002 pidfile /var/run/redis_7000.pid #开启集群,把注释#去掉 cluster-enabled yes #集群的配置,配置文件首次启动自动生成 7000,7001,7002 cluster-config-file nodes_7000.conf #请求超时,默认15秒,可自行设置 cluster-node-timeout 10100 #aof日志开启,有需要就开启,它会每次写操作都记录一条日志 appendonly yes
步骤 4:
接着在另外一台机器上(192.168.56.102)重复以上三步,只是把目录改为7003、7004、7005对应的配置文件也按照这个规则修改即可
5. 启动节点
# 第一台机器上执行 3个节点 for((i=0;i<=2;i++)); do /opt/redis-4.0.10/src/redis-server /opt/redis-4.0.10/redis-cluster/700$i/redis.conf; done # 第二台机器上执行 3个节点 for((i=3;i<=5;i++)); do /opt/redis-4.0.10/src/redis-server /opt/redis-4.0.10/redis-cluster/700$i/redis.conf; done # 启动单个节点示例 /opt/redis-4.0.10/src/redis-server /opt/redis-4.0.10/redis-cluster/7000/redis.conf
6. 检查服务
检查各 Redis 各个节点启动情况
ps -ef | grep redis //redis是否启动成功 netstat -tnlp | grep redis //监听redis端口
安装 netstat :https://www.cnblogs.com/cocoajin/p/4064547.html
7. 安装Ruby
yum -y install ruby ruby-devel rubygems rpm-build
gem install redis
安装过程中可能会出现异常情况发生,比如:
解决办法:https://blog.csdn.net/FengYe_YuLu/article/details/77628094
8. 创建集群
注意:
在任意一台上运行 不要在每台机器上都运行,一台就够了。
Redis 官方提供了 redis-trib.rb 这个工具,就在解压目录的 src 目录中
/opt/redis-4.0.10/src/redis-trib.rb create --replicas 1 192.168.56.101:7000 192.168.56.101:7001 192.168.56.101:7002 192.168.56.102:7003 192.168.56.102:7004 192.168.56.102:7005
出现以下内容,则表示集群创建成功了:
[root@master1 /]# /opt/redis-4.0.10/src/redis-trib.rb create --replicas 1 192.168.56.101:7000 192.168.56.101:7001 192.168.56.101:7002 192.168.56.102:7003 192.168.56.102:7004 192.168.56.102:7005 >>> Creating cluster >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 192.168.56.101:7000 192.168.56.102:7003 192.168.56.101:7001 Adding replica 192.168.56.102:7005 to 192.168.56.101:7000 Adding replica 192.168.56.101:7002 to 192.168.56.102:7003 Adding replica 192.168.56.102:7004 to 192.168.56.101:7001 M: 6af67c2741b3001e6d328621ac8a2e539b65d683 192.168.56.101:7000 slots:0-5460 (5461 slots) master M: e2f298953141f46b255b0f35372af917afc16205 192.168.56.101:7001 slots:10923-16383 (5461 slots) master S: 3516ed59324a7421878b2c17aba44d91ec7e9439 192.168.56.101:7002 replicates ab5b4535f3382c13d7afb91d005e8a87d830eb46 M: ab5b4535f3382c13d7afb91d005e8a87d830eb46 192.168.56.102:7003 slots:5461-10922 (5462 slots) master S: 16822c6d58461f9edaf965aa53efdac59d1adce5 192.168.56.102:7004 replicates e2f298953141f46b255b0f35372af917afc16205 S: c00dea4a44e01e1f17b29f7b3b95e0c57b06a653 192.168.56.102:7005 replicates 6af67c2741b3001e6d328621ac8a2e539b65d683 Can I set the above configuration? (type 'yes' to accept): yes
输入 yes
>>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join.... >>> Performing Cluster Check (using node 192.168.56.101:7000) M: 6af67c2741b3001e6d328621ac8a2e539b65d683 192.168.56.101:7000 slots:0-5460 (5461 slots) master 1 additional replica(s) S: c00dea4a44e01e1f17b29f7b3b95e0c57b06a653 192.168.56.102:7005 slots: (0 slots) slave replicates 6af67c2741b3001e6d328621ac8a2e539b65d683 S: 3516ed59324a7421878b2c17aba44d91ec7e9439 192.168.56.101:7002 slots: (0 slots) slave replicates ab5b4535f3382c13d7afb91d005e8a87d830eb46 M: ab5b4535f3382c13d7afb91d005e8a87d830eb46 192.168.56.102:7003 slots:5461-10922 (5462 slots) master 1 additional replica(s) S: 16822c6d58461f9edaf965aa53efdac59d1adce5 192.168.56.102:7004 slots: (0 slots) slave replicates e2f298953141f46b255b0f35372af917afc16205 M: e2f298953141f46b255b0f35372af917afc16205 192.168.56.101:7001 slots:10923-16383 (5461 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
重启集群过程中,可能出现问题:
[ERR] Node 192.168.56.101:7000 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
解决:
需要将redis-cluster数据文件清空,执行脚本如下:
rm -rf /opt/redis-4.0.10/redis-cluster/dump.rdb /opt/redis-4.0.10/redis-cluster/nodes_*.conf /opt/redis-4.0.10/redis-cluster/appendonly.aof
9. 关闭集群
推荐:
pkill redis
也可以通过循环节点方式,逐个关闭:
for((i=0;i<=2;i++)); do /opt/redis-4.0.10/src/redis-cli -c -h 192.168.56.101 -p 700$i shutdown; done for((i=3;i<=5;i++)); do /opt/redis-4.0.10/src/redis-cli -c -h 192.168.56.102 -p 700$i shutdown; done
关闭单个节点:
/opt/redis-4.0.10/src/redis-cli -c -h 192.168.56.101 -p 7000 shutdown
10. 集群验证
步骤1 . 连接集群测试
参数 -C 可连接到集群,因为 redis.conf 将 bind 改为了ip地址,所以 -h 参数不可以省略,-p 参数为端口号
我们在192.168.256.101机器redis 7000 的节点set 一个key
/opt/redis-4.0.10/src/redis-cli -h 192.168.56.101 -c -p 7000
执行如下:
set name xushuyi
get name
我们在192.168.56.102机器redis 7000 的节点get一个key
/opt/redis-4.0.10/src/redis-cli -h 192.168.56.102 -c -p 7000
执行如下:
get name
步骤2 . 检查集群状态
/opt/redis-4.0.10/src/redis-trib.rb check 192.168.56.101:7000
步骤3 . 列出集群节点
/opt/redis-4.0.10/src/redis-cli -h 192.168.56.101 -c -p 7000
更详细的,请参考:https://segmentfault.com/a/1190000010682551
11. spring boot 2.0.x 服务集成Redis集群
1. 引入依赖
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2
2. 配置文件
# redis 集群配置 spring: redis: cluster: nodes: 192.168.56.101:7000,192.168.56.101:7001,192.168.56.101:7002,192.168.56.102:7003,192.168.56.102:7004,192.168.56.102:7005 timeout: 6000ms # 连接池超时时间(毫秒) # 密码没有可以不填 password: database: 0 # 数据库索引 lettuce: pool: max-active: 8 # 连接池最大活跃连接数(使用负值表示没有限制) max-idle: 8 # 连接池最大空闲连接数 max-wait: -1ms # 连接池最大阻塞等待时间 毫秒(使用负值表示没有限制) min-idle: 0 # 最小空闲连接数
3. 创建RedisConfig配置文件
package com.sinosoft.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @ClassName: RedisConfig * @Description: Redis配置 * @author: Created by xushuyi Contact author * @date: 2019/2/28 16:01 * @Version: V1.0 */ @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisConfig { /** * 注入自定义的RedisTemplate * * @param redisConnectFactory LettuceConnectionFactory * @return RedisTemplate */ @Bean public RedisTemplateredisTemplate(LettuceConnectionFactory redisConnectFactory) { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectFactory); // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // 使用StringRedisSerializer来序列化和反序列化redis的key值 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
4. 启动入口增加 @EnableCaching
5. 单元测试
package com.sinosoft.redis; import com.sinosoft.AccountApplication; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Import; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; /** * @ClassName: RedisTest * @Description: Redis测试 * @author: Created by xushuyi Contact author * @date: 2019/2/28 16:18 * @Version: V1.0 */ @Slf4j @RunWith(SpringJUnit4ClassRunner.class) // SpringJUnit支持,由此引入Spring-Test框架支持! @SpringBootTest @Import(AccountApplication.class) // 指定我们SpringBoot工程的Application启动类 @ActiveProfiles(profiles = "dev") // 指定Application启动需要引用的配置文件 @WebAppConfiguration // 由于是Web项目,Junit需要模拟ServletContext,因此我们需要给我们的测试类加上@WebAppConfiguration public class RedisTest { /** * 获取RedisTemplate */ @Autowired private RedisTemplate redisTemplate; /** * 测试前 */ @Before public void before() { log.info("测试前..."); } /** * 开始测试 */ @Test public void redisTest() { // 测试通过RedisTemplate操作 redisTemplate.opsForValue().set("name", "huachunjie"); Object name = redisTemplate.opsForValue().get("name"); log.info("获取Redis集群中key为name对应的值:{}", name); } @After public void after() { log.info("测试后..."); } }
6. 基于注解的方式实现
/** * 测试Redis缓存 * * @param requestInfo 入参 * @return */ @Override @Cacheable(value = "cacheInfo", key = "#requestInfo.token", condition = "#requestInfo != null") public ResponseInfo testRedis(RequestInforequestInfo) { log.info("如果没有进来,则说明启用了Redis缓存..."); return new ResponseInfo<>(true, "success", 200, null); }