springboot 接入 cachecloud redis 实践
最近项目中需要接入 Redis CacheCloud, CacheCloud是一个开源的 Redis 运维监控云平台,功能十分强大,支持Redis 实例自动部署、扩容、碎片管理、统计、监控等功能, 特别是支持单机、sentinel 、cluster三种模式的自动部署,搭建redis集群一步到位轻松搞定。
java项目中 接入 CacheCloud redis的方式主要有两种。
第一种就是在 CacheCloud 上创建好redis实例后将对应的IP,端口直接配置以配置形式应用到项目中,优点是通用性好,原有项目改造成本低,不过万一后期CacheCloud上对redis进行管理扩容,那只能手动把每个项目的redis配置都改一遍了。
第二种CacheCloud 上创建好实例后有一个对应的appId,程序调用CacheCloud 平台的rest接口通过 appId获取redis相关配置,将程序中的redis配置 统一交给CacheCloud平台去管理维护,后期管理和扩容及其方便,不过程序改造成本比较高。
现在采用第二种方式接入,工程采用springboot,redis采用哨兵模式,redis客户端主要用spring-data-redis和redisson, 接入流程如下:
添加配置到pom.xml文件
<!--cachecloud 相关jar包--><dependency><groupId>com.sohu.tv</groupId><artifactId>cachecloud-open-client-redis</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.sohu.tv</groupId><artifactId>cachecloud-open-client-basic</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.sohu.tv</groupId><artifactId>cachecloud-open-common</artifactId><version>1.0-SNAPSHOT</version></dependency><!--spring redis 和 redisson--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><artifactId>jedis</artifactId><groupId>redis.clients</groupId></exclusion></exclusions></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.9.0</version></dependency>
准备配置文件 cacheCloudClient.properties,启动项目时 VM参数追加 -Dcachecloud.config= 配置文件路径
http_conn_timeout = 3000
http_socket_timeout = 5000
client_version = 1.0-SNAPSHOT
domain_url = http://192.168.33.221:8585 #cachecloud实际路径
redis_cluster_suffix = /cache/client/redis/cluster/%s.json?clientVersion=
redis_sentinel_suffix = /cache/client/redis/sentinel/%s.json?clientVersion=
redis_standalone_suffix = /cache/client/redis/standalone/%s.json?clientVersion=
cachecloud_report_url = /cachecloud/client/reportData.json
基本思路是先通过cachecloud的restapi接口获取并解析redis节点的配置信息,然后就可以按照传统的访问redis的方式进行初始化,获取RedisTemplate对象。
java代码如下:
import com.alibaba.fastjson.JSONObject;
import com.sohu.tv.cachecloud.client.basic.heartbeat.ClientStatusEnum;
import com.sohu.tv.cachecloud.client.basic.util.ConstUtils;
import com.sohu.tv.cachecloud.client.basic.util.HttpUtils;
import com.sohu.tv.cachecloud.client.jedis.stat.ClientDataCollectReportExecutor;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Component
public class RedisProperties {public static Logger logger = LoggerFactory.getLogger(RedisProperties.class);/*** 构建锁*/private static final Lock LOCK = new ReentrantLock();@Value("${cacheCloud.appId}") //cahcecloud 开通redis实例 应用idprivate Integer appId;@Getter@Setterprivate String masterName;@Getter@Setterprivate Set<Pair<String, String>> sentinelSet = new HashSet<>();private Boolean clientStatIsOpen=true;@Getter@Setterprivate String password;private Boolean getConfigSuccess = false;@PostConstructpublic void init() {while (true) {try {LOCK.tryLock(10, TimeUnit.MILLISECONDS);if (!getConfigSuccess) {/*** http请求返回的结果是空的;*/String response = HttpUtils.doGet(String.format(ConstUtils.REDIS_SENTINEL_URL, appId));if (response == null || response.isEmpty()) {logger.warn("get response from remote server error, appId: {}, continue...", appId);continue;}/*** http请求返回的结果是无效的;*/JSONObject jsonObject = null;try {jsonObject = JSONObject.parseObject(response);} catch (Exception e) {logger.error("heartbeat error, appId: {}. continue...", appId, e);}if (jsonObject == null) {logger.error("get sentinel info for appId: {} error. continue...", appId);continue;}int status = jsonObject.getIntValue("status");String message = jsonObject.getString("message");/** 检查客户端版本 **/if (status == ClientStatusEnum.ERROR.getStatus()) {throw new IllegalStateException(message);} else if (status == ClientStatusEnum.WARN.getStatus()) {logger.warn(message);} else {logger.info(message);}/*** 有效的请求:取出masterName和sentinels;*/masterName = jsonObject.getString("masterName");String sentinels = jsonObject.getString("sentinels");for (String sentinelStr : sentinels.split(" ")) {String[] sentinelArr = sentinelStr.split(":");if (sentinelArr.length == 2) {sentinelSet.add(Pair.of(sentinelArr[0], sentinelArr[1]));}}//收集上报数据if (clientStatIsOpen) {ClientDataCollectReportExecutor.getInstance();}password = jsonObject.getString("password");getConfigSuccess = true;return;}} catch (Throwable e) {//容错logger.error("error in build, appId: {}", appId, e);} finally {LOCK.unlock();}try {TimeUnit.MILLISECONDS.sleep(200 + new Random().nextInt(1000));//活锁} catch (InterruptedException e) {logger.error(e.getMessage(), e);}}}
}
import com.shunwang.buss.dispatchPay.provider.config.PropertiesUtil;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.ReadMode;
import org.redisson.config.SentinelServersConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;import java.net.UnknownHostException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;import static java.util.stream.Collectors.toList;@Configuration
public class RedisConfig {/*** JedisPoolConfig 连接池*/@Beanpublic JedisPoolConfig jedisPoolConfig(RedisProperties properties) {JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 最大空闲数jedisPoolConfig.setMaxIdle(20);// 连接池的最大数据库连接数jedisPoolConfig.setMaxTotal(20);// 最大建立连接等待时间jedisPoolConfig.setMaxWaitMillis(3000);return jedisPoolConfig;}/*** 配置redis的哨兵*/@Beanpublic RedisSentinelConfiguration sentinelConfiguration(RedisProperties properties) {RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();// 配置redis的哨兵sentinelSet<RedisNode> redisNodeSet = properties.getSentinelSet().stream().map(pair -> new RedisNode(pair.getLeft(), Integer.parseInt(pair.getRight()))).collect(Collectors.toSet());redisSentinelConfiguration.setSentinels(redisNodeSet);redisSentinelConfiguration.setMaster(properties.getMasterName());return redisSentinelConfiguration;}/*** 配置工厂*/@Beanpublic RedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig, RedisSentinelConfiguration sentinelConfig) {JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(sentinelConfig, jedisPoolConfig);return jedisConnectionFactory;}@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException {RedisTemplate template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);// 设置值(value)的序列化采用FastJsonRedisSerializer。template.setValueSerializer(fastJsonRedisSerializer);template.setHashValueSerializer(fastJsonRedisSerializer);// 设置键(key)的序列化采用StringRedisSerializer。template.setKeySerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.afterPropertiesSet();return template;}/*** Redisson 配置*/@Beanpublic RedissonClient redissonClient(RedisProperties properties) {Config config = new Config();List<String> newNodes = properties.getSentinelSet().stream().map(pa -> "redis://" + pa.getLeft() + ":" + pa.getRight()).collect(toList());SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(newNodes.toArray(new String[newNodes.size()])).setMasterName(properties.getMasterName()).setReadMode(ReadMode.SLAVE);if (StringUtils.isNotBlank(properties.getPassword())){serverConfig.setPassword(properties.getPassword());}return Redisson.create(config);}
}
到这里我们已经在Spring中 生成了RedisTemplate 和 RedissonClient 对象,无论是基本数据结构操作 还是分布式锁 都已经轻松支持了,具体使用就不展开了。
第一次写博客,写的不周到的地方 还请多多见谅。
springboot 接入 cachecloud redis 实践相关推荐
- Docker 部署 SpringBoot 项目整合 Redis 镜像做访问计数Demo
Docker 部署SpringBoot项目整合 Redis 镜像做访问计数Demo 最终效果如下 大概就几个步骤 1.安装 Docker CE 2.运行 Redis 镜像 3.Java 环境准备 4. ...
- springboot中使用redis详解
一.redis简介 redis是一款高性能key-value(键值对)内存型数据库,是非关系型数据库的一种,它采用单线程的架构方式,避免了多线程存在的锁处理造成的资源耗费,读取速度非常快,非常适合变化 ...
- 节约内存:Instagram的Redis实践(转)
一.问题: 数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求. 二.解决方案: 1.通过高速服务器Cache缓存数据库数据 2.内存数据库 三.主流解Cache和数据库对比: 从以上 ...
- (转) SpringBoot接入两套kafka集群
转自: SpringBoot接入两套kafka集群 - 风小雅 - 博客园引入依赖 compile 'org.springframework.kafka:spring-kafka' 第一套kafka配 ...
- SpringBoot (14)---使用Redis缓存
SpringBoot 中使用Redis缓存 在项目中对数据的访问往往都是直接访问数据库的方式,但如果对数据的访问量很大或者访问很频繁的话,将会对数据库来很大的压力,甚至造成数据库崩溃.为了解决这类问题 ...
- springboot Cacheable(redis),解决key乱码问题
springboot Cacheable(redis),解决key乱码问题 参考文章: (1)springboot Cacheable(redis),解决key乱码问题 (2)https://www. ...
- 基于springboot+bootstrap+mysql+redis搭建一套完整的权限架构【八】【完善整个项目】
上一章我们已经完成了菜单模块的开发工作,那么到了本章我们将完成我们角色管理模块的开发工作,在本章开始一个全新的模块进行开发的时候我们需要遵守一定的命名和开发规范如下: 1.我们的Controller的 ...
- springboot项目中redis客户端(Jedis、Lettuce、Redisson)
一.redis客户端的对比 1).Jedis Jedis作为Redis官方推荐的一款客户端,也算是简单好用,基础功能齐全,在中小型项目中还是很好用的,但是Jedis是直连模式,在多个线程间共享一个Je ...
- 微信开发:springboot接入微信公众号
微信公众号现今已经成为我们日常生活中获取信息的一个重要途径,今天这篇博客主要是介绍如何用springboot接入微信的公众号.微信的公众号分三种:订阅号,一般个人使用,很多功能受限:服务号,个人与企业 ...
- Redis(五)整合:SpringBoot如何整合Redis?
前言 SpringBoot应该不用过多介绍了吧!是Spring当前最火的一个框架,既然学习了Redis,我们肯定是要在实际项目中使用,那么肯定首选整合SpringBoot啦! 简单介绍下SpringB ...
最新文章
- 刷新ImageNet纪录,GAN不只会造假!DeepMind用它做图像分类,秒杀职业分类AI
- Web socket广播
- windows 2003负载均衡故障切换
- 阿里P8架构师谈:分布式架构设计12精讲
- jax-ws使用教程_JAX-WS教程
- 【个人笔记】OpenCV4 C++ 快速入门 20课
- Teradata SQL tips
- 如何按页进行PDF文档拆分
- php令牌桶,令牌桶限频(TokenBucket)
- python 开源cms内容管理系统_八大CMS内容管理系统推荐
- 各种浏览器UserAgent一览表(桌面+移动)
- 台式计算机硬件的拆装,电脑拆解实例:苹果台式机拆机换盘详细步骤!
- 转载:手机银行技术讨论3
- 微信小程序--图片相关问题合辑
- 利用Linux自带的logrotate管理日志
- input 测试工具 --evtest
- iOS tableView 右侧索引视图状态获取
- 【Python爬虫】之 抓取“微医”上的医生信息
- Android AlarmManager
- closeEvent