spring boot redisson starter的封装和使用

原创
2018/01/05 18:25
阅读数 4.7W

主要内容:

  • Redisson简介
  • Redisson的配置方式
  • spring boot starter的封装和使用

一.Redisson简介


简单来说,Redisson其实就是一个Redis的客户端jar包,使用基于NIO的netty开发,更加注重分布式场景应用的封装,比如分布式锁、异步流式处理、分布式远程服务、分布式调度服务、队列等等,还提供了与spring框架的整合,并且还包含对spring cache、spring session的支持,方便开发者使用。具体的介绍这里就不过多的啰嗦,可以参考官方文档:Redisson官方Wiki文档(含中英文版)

不过我觉得比较值得提出的有以下几点:

  1. Redisson的封装是用Java对象与Redis中数据结构对应的方式。即在Redisson中是实现了Java里的Map、List、Set等接口,也就是按照Java这些对象的使用方法就能够操作Redis缓存数据。对应关系见官方文档:Redis命令和Redisson对象匹配列表
  2. Redis中的HASH数据结构的过期时间只能针对key设置,不能针对hashKey分别设置过期时间。而Redisson中实现了可以针对hashKey分别设置不同的过期时间(使用Lua脚本 + Java调度任务实现)
  • HASH结构数据与Redisson中对应的是RMap接口的实现
  • 普通HASH结构数据与RedissonMap对应
  • 可以针对hashKey分别设置不同过期时间的对应类是RedissonMapCache
  • 清理HASH结构中的过期hashKey的调度类是EvictionScheduler,清理任务的抽象类是EvictionTask,针对不同场景的清理有不同的实现
  • 针对已过期的hashKey,Lua脚本中有判断是否过期,已过期的返回空,并且即时清理
  1. Redisson中封装了多种锁,如:分布式锁Lock、读写锁ReadWriteLock、RedLock、联锁MultiLock等等,并且这些锁都是实现Java并发包里的接口,遵循Java定义的规范
  2. Redisson实现了本地缓存,当本地缓存没有值时自动回源到Redis缓存,即实现了多级缓存的效果。可以使用LocalCachedMapOptions对象在创建本地缓存时设置一些参数。需要注意的是在Redis中缓存的数据没有过期时间

二.Redisson的配置方式


Redisson支持了多种配置方式,不管是哪种方式,主要是使用Config对象

  1. 程序化配置方式,这种方式不方便切换不同的环境,在实际项目中基本不采用,示例如下:
Config config = new Config();
config.setUseLinuxNativeEpoll(true);
config.useClusterServers()
    //可以用"rediss://"来启用SSL连接
    .addNodeAddress("redis://127.0.0.1:7181");
  1. 文件方式配置,主要包含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配置,主要配置节点如下:
<redisson:client>
    <redisson:single-server ... />
    <!-- 或者 -->
    <redisson:master-slave-servers ... />
    <!-- 或者 -->
    <redisson:sentinel-servers ... />
    <!-- 或者 -->
    <redisson:cluster-servers ... />
    <!-- 或者 -->
    <redisson:replicated-servers ... />
</redisson:client>

说明:

  • 具体配置的属性可以参考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
<dependency>
    <groupId>com.itopener</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>
  • 在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<String> 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<URI> nodeAddresses。所以需要定义与Config属性一致的Properties类(包含嵌套属性的层级结构),然后在AutoConfiguration中将properties转换成json字符串,利用Config.fromJSON(String content)方法,跟加载json文件一样去初始化Redisson配置,代码如下:

  • 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://gitee.com/itopener/springboot
    • starter目录:springboot / itopener-parent / spring-boot-starters-parent / redisson-spring-boot-starter-parent
    • 示例目录: springboot / itopener-parent / demo-parent / demo-redisson
展开阅读全文
打赏
0
17 收藏
分享
加载中
该评论暂时无法显示,详情咨询 QQ 群:912889742
更多评论
打赏
1 评论
17 收藏
0
分享
返回顶部
顶部