主要内容:
- Redisson简介
- Redisson的配置方式
- spring boot starter的封装和使用
一.Redisson简介
简单来说,Redisson其实就是一个Redis的客户端jar包,使用基于NIO的netty开发,更加注重分布式场景应用的封装,比如分布式锁、异步流式处理、分布式远程服务、分布式调度服务、队列等等,还提供了与spring框架的整合,并且还包含对spring cache、spring session的支持,方便开发者使用。具体的介绍这里就不过多的啰嗦,可以参考官方文档:Redisson官方Wiki文档(含中英文版)
不过我觉得比较值得提出的有以下几点:
- Redisson的封装是用Java对象与Redis中数据结构对应的方式。即在Redisson中是实现了Java里的Map、List、Set等接口,也就是按照Java这些对象的使用方法就能够操作Redis缓存数据。对应关系见官方文档:Redis命令和Redisson对象匹配列表
- Redis中的HASH数据结构的过期时间只能针对key设置,不能针对hashKey分别设置过期时间。而Redisson中实现了可以针对hashKey分别设置不同的过期时间(使用Lua脚本 + Java调度任务实现)
- HASH结构数据与Redisson中对应的是RMap接口的实现
- 普通HASH结构数据与RedissonMap对应
- 可以针对hashKey分别设置不同过期时间的对应类是RedissonMapCache
- 清理HASH结构中的过期hashKey的调度类是EvictionScheduler,清理任务的抽象类是EvictionTask,针对不同场景的清理有不同的实现
- 针对已过期的hashKey,Lua脚本中有判断是否过期,已过期的返回空,并且即时清理
- Redisson中封装了多种锁,如:分布式锁Lock、读写锁ReadWriteLock、RedLock、联锁MultiLock等等,并且这些锁都是实现Java并发包里的接口,遵循Java定义的规范
- Redisson实现了本地缓存,当本地缓存没有值时自动回源到Redis缓存,即实现了多级缓存的效果。可以使用LocalCachedMapOptions对象在创建本地缓存时设置一些参数。需要注意的是在Redis中缓存的数据没有过期时间
二.Redisson的配置方式
Redisson支持了多种配置方式,不管是哪种方式,主要是使用Config对象
- 程序化配置方式,这种方式不方便切换不同的环境,在实际项目中基本不采用,示例如下:
Config config = new Config();
config.setUseLinuxNativeEpoll(true);
config.useClusterServers()
//可以用"rediss://"来启用SSL连接
.addNodeAddress("redis://127.0.0.1:7181");
- 文件方式配置,主要包含3种:json格式文件、yaml文件和spring配置文件
- json和yaml文件需要单独创建一个对应后缀的配置文件,然后使用Config对象加载,示例如下:
# json文件
Config config = Config.fromJSON(new File("config-file.json"));
RedissonClient redisson = Redisson.create(config);
# yaml文件与json文件的方式相同
Config config = Config.fromYAML(new File("config-file.yaml"));
RedissonClient redisson = Redisson.create(config);
- spring配置文件的方式就是xml配置,主要配置节点如下:
说明:
- 具体配置的属性可以参考Config对象,spring配置文件的定义在redisson.jar包中的org.redisson.spring.support包中
- Redisson对象是RedissonClient接口的实现,所有对Redis的操作入口也都是Redisson对象的实例
三.spring boot starter的封装和使用
其实,在redisson.jar包中是有对spring boot的一些支持,但是并没有对于配置和初始化的支持。在jar包resources/META-INF/spring.factories文件中只有RedissonCacheStatisticsAutoConfiguration配置,可以看到这里只是有Redisson缓存的一些统计指标,主要是命中率和不命中率。
所以,就需要我们自行对Redisson的配置进行封装,知道了Redisson的配置方式,封装spring boot starter也就相对容易了。spring boot官方建议(spring boot创建自定义starter)自定义封装的starter命名以封装的对象开头,于是取名:redisson-spring-boot-starter。基于json或yaml文件的配置方式的封装步骤及源码:
- 定义properties
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author fuwei.deng
* @date 2018年1月3日 下午2:12:47
* @version 1.0.0
*/
@ConfigurationProperties(prefix = "spring.redisson")
public class RedissonProperties {
private ConfigFile configFile = new ConfigFile();
public class ConfigFile {
/** json格式配置文件*/
private String json;
/** yaml格式配置文件*/
private String yaml;
public String getJson() {
return json;
}
public void setJson(String json) {
this.json = json;
}
public String getYaml() {
return yaml;
}
public void setYaml(String yaml) {
this.yaml = yaml;
}
}
public ConfigFile getConfigFile() {
return configFile;
}
public void setConfigFile(ConfigFile configFile) {
this.configFile = configFile;
}
}
- 定义配置文件
import java.io.File;
import java.io.IOException;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* @author fuwei.deng
* @date 2018年1月3日 下午2:13:48
* @version 1.0.0
*/
@Configuration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
private final Logger logger = LoggerFactory.getLogger(RedissonAutoConfiguration.class);
@Autowired
private RedissonProperties redissonProperties;
public Config configJson() throws IOException {
File file = ResourceUtils.getFile(redissonProperties.getConfigFile().getJson());
return Config.fromJSON(file);
}
public Config configYaml() throws IOException {
File file = ResourceUtils.getFile(redissonProperties.getConfigFile().getYaml());
return Config.fromYAML(file);
}
@Bean
@ConditionalOnMissingBean
public Config config() throws IOException {
if (!StringUtils.isEmpty(redissonProperties.getConfigFile().getJson())) {
return configJson();
} else if (!StringUtils.isEmpty(redissonProperties.getConfigFile().getYaml())) {
return configYaml();
} else {
throw new RuntimeException("please offer the config file by json/yaml");
}
}
@Bean(destroyMethod="shutdown")
@ConditionalOnMissingBean
public RedissonClient redissonClient(Config config) throws IOException {
logger.info("create RedissonClient, config is : {}", config.toJSON());
return Redisson.create(config);
}
}
- 在resources/META-INF/spring.factories文件中增加配置
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itopener.redisson.spring.boot.autoconfigure.RedissonAutoConfiguration
- 这样封装之后,在项目中引入starter
com.itopener
redisson-spring-boot-starter
1.0.0-SNAPSHOT
- 在properties中增加配置
spring.redisson.config-file.json=classpath:redisson.json
- 在classpath下(具体路径与上一步的配置对应)增加redisson.json配置文件,示例如下(具体属性参考官方文档 Redisson配置方法 或 Config 源码,此段配置摘取自官方文档)
{
"clusterServersConfig":{
"idleConnectionTimeout":10000,
"pingTimeout":1000,
"connectTimeout":10000,
"timeout":3000,
"retryAttempts":3,
"retryInterval":1500,
"reconnectionTimeout":3000,
"failedAttempts":3,
"password":null,
"subscriptionsPerConnection":5,
"clientName":null,
"loadBalancer":{
"class":"org.redisson.connection.balancer.RoundRobinLoadBalancer"
},
"slaveSubscriptionConnectionMinimumIdleSize":1,
"slaveSubscriptionConnectionPoolSize":50,
"slaveConnectionMinimumIdleSize":10,
"slaveConnectionPoolSize":64,
"masterConnectionMinimumIdleSize":10,
"masterConnectionPoolSize":64,
"readMode":"SLAVE",
"nodeAddresses":[
"redis://127.0.0.1:7001",
"redis://127.0.0.1:7002",
"redis://127.0.0.1:7003",
"redis://127.0.0.1:7004",
"redis://127.0.0.1:7005",
"redis://127.0.0.1:7006"
],
"scanInterval":1000
},
"threads":0,
"nettyThreads": 0,
"codec":null,
"useLinuxNativeEpoll":false
}
- 注入Redisson对象并使用(以下只是缓存对象的示例,对应Redis里的key-value数据结构)
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.itopener.framework.ResultMap;
/**
* @author fuwei.deng
* @date 2018年1月4日 下午2:28:41
* @version 1.0.0
*/
@RestController
@RequestMapping("redisson/bucket")
public class RedissonBucketController {
@Resource
private RedissonClient redissonClient;
@GetMapping("{key}")
public ResultMap get(@PathVariable String key) {
RBucket bucket = redissonClient.getBucket(key);
String value = bucket.get();
long keyCount = redissonClient.getKeys().count();
return ResultMap.buildSuccess().put("keyCount", keyCount).put("value", value);
}
@PostMapping("{key}/{value}")
public ResultMap put(@PathVariable String key, @PathVariable String value) {
redissonClient.getBucket(key).set(value, 5, TimeUnit.MINUTES);
long keyCount = redissonClient.getKeys().count();
return ResultMap.buildSuccess().put("keyCount", keyCount);
}
}
这样封装之后就可以轻松的应用到spring boot项目当中。需要注意的是,yaml配置方式的配置内容不能直接放入到spring boot的yml配置当中,否则Redisson初始化配置的时候会出现转换的异常。
在结合spring cloud config使用的时候,不同环境也可以指定不同的Redisson配置文件,方便环境的切换。
至此,redisson spring boot starter的封装已经支持使用json或yaml文件的方式进行配置,以下是支持spring boot配置文件方式的封装
说明:
如果是想封装成单独定义属性的方式,有些属性不允许调用set方法,比如Config类中设置集群模式配置的方法作用域是默认的,在自定义包中调用不了:
void setClusterServersConfig(ClusterServersConfig clusterServersConfig) {
this.clusterServersConfig = clusterServersConfig;
}
并且配置中有一些属性是spring boot配置文件映射不了的,比如Config类中的编码属性Codec codec、集群模式配置的集群地址属性List
- RedissonProperties配置属性类,其中ConfigFile与之前的一致,只是单独到ConfigFile.java文件中去了;而此处的Config不是Redisson包中的Config,而是自己定义的与redisson.jar包中Config对应的类,方便spring boot将配置映射到此处的Config类上去
import org.springframework.boot.context.properties.ConfigurationProperties;
import com.itopener.redisson.spring.boot.autoconfigure.config.Config;
import com.itopener.redisson.spring.boot.autoconfigure.config.ConfigFile;
/**
* @author fuwei.deng
* @date 2018年1月3日 下午2:12:47
* @version 1.0.0
*/
@ConfigurationProperties(prefix = "spring.redisson")
public class RedissonProperties {
private Config config;
private ConfigFile configFile = new ConfigFile();
public Config getConfig() {
return config;
}
public void setConfig(Config config) {
this.config = config;
}
public ConfigFile getConfigFile() {
return configFile;
}
public void setConfigFile(ConfigFile configFile) {
this.configFile = configFile;
}
}
- 而配置类里相比前面来说,多了对spring boot属性配置的支持
import java.io.File;
import java.io.IOException;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author fuwei.deng
* @date 2018年1月3日 下午2:13:48
* @version 1.0.0
*/
@Configuration
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {
private final Logger logger = LoggerFactory.getLogger(RedissonAutoConfiguration.class);
@Autowired
private RedissonProperties redissonProperties;
public Config configJson() throws IOException {
File file = ResourceUtils.getFile(redissonProperties.getConfigFile().getJson());
return Config.fromJSON(file);
}
public Config configYaml() throws IOException {
File file = ResourceUtils.getFile(redissonProperties.getConfigFile().getYaml());
return Config.fromYAML(file);
}
@Bean
@ConditionalOnMissingBean
public Config config() throws IOException {
if (!StringUtils.isEmpty(redissonProperties.getConfigFile().getJson())) {
return configJson();
} else if (!StringUtils.isEmpty(redissonProperties.getConfigFile().getYaml())) {
return configYaml();
} else {
ObjectMapper mapper = new ObjectMapper();
String configJson = mapper.writeValueAsString(redissonProperties.getConfig());
logger.info("config is : {}", configJson);
return Config.fromJSON(configJson);
}
}
@Bean(destroyMethod="shutdown")
@ConditionalOnMissingBean
public RedissonClient redissonClient(Config config) throws IOException {
logger.info("create RedissonClient, config is : {}", config.toJSON());
return Redisson.create(config);
}
}
需要注意的是:由于json的配置内容中,对于属性是对象的情况,json多了一层class,比如BaseMasterSlaveServersConfig类中的负载策略属性LoadBalancer loadBalancer,对应的json配置格式是:
"loadBalancer":{
"class":"org.redisson.connection.balancer.RoundRobinLoadBalancer"
}
因此需要对对象属性做一下处理,比如使用如下方式包装一下:
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @author fuwei.deng
* @date 2018年1月5日 下午2:56:50
* @version 1.0.0
*/
public class ClassProperty {
@JsonProperty("class")
private String className;
public ClassProperty(String className) {
super();
this.className = className;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
// 对应属性改为这种方式:
private ClassProperty loadBalancer = new ClassProperty(RoundRobinLoadBalancer.class.getName());
- 这样就可以像其他spring boot属性配置一样去配置Redisson的相关参数了
# config
#spring.redisson.config.threads=5
#spring.redisson.config.nettyThreads=5
spring.redisson.config.codec.className=org.redisson.codec.KryoCodec
#spring.redisson.config.codecProvider.className=org.redisson.codec.DefaultCodecProvider
#spring.redisson.config.resolverProvider.className=org.redisson.liveobject.provider.DefaultResolverProvider
#spring.redisson.config.redissonReferenceEnabled=false
#spring.redisson.config.useLinuxNativeEpoll=false
#spring.redisson.config.lockWatchdogTimeout=40000
#spring.redisson.config.keepPubSubOrder=false
# cluster config
#spring.redisson.config.clusterServersConfig.idleConnectionTimeout=10000
#spring.redisson.config.clusterServersConfig.scanInterval=2000
spring.redisson.config.clusterServersConfig.nodeAddresses=redis://10.250.20.179:7001,redis://10.250.20.179:7002,redis://10.250.20.179:7003,redis://10.250.20.179:7004,redis://10.250.20.179:7005,redis://10.250.20.179:7006
至此,对于Redisson的spring boot starter封装就基本结束了。
参考资料:
- https://github.com/redisson/redisson/wiki
- https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/
源码
- https://gitee.com/itopener/springboot
- starter目录:springboot / itopener-parent / spring-boot-starters-parent / redisson-spring-boot-starter-parent
- 示例目录: springboot / itopener-parent / demo-parent / demo-redisson