本文基于SpringBoot2.2.8
在SpringBooot中引入一个组件,往往只需要引入一个starter,他会帮你将所有相关依赖自动引入,大大简化了使用流程。

一、使用步骤

通过starter引入所有依赖,只需一个依赖即可完成所有相关依赖的引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

starter引入了其他的jar:

 其中spring-data-redis包是Spring提供的对于Redis访问的统一标准接口,主要通过:
org.springframework.data.redis.core.RedisTemplate
RedisTemplate是Spring提供的应用层接口,向下屏蔽Redis客户端,连接池等信息,也就是说用户可以无缝替换Redis客户端,而不用修改应用使用RedisTemplate的相关代码。但是RedisTemplate只封装了Redis对数据的存取能力,没有分布式功能。所以如果要使用Redis上扩展的分布式能力,需要直接使用客户端提供的接口。

二、配置原理

引入jar包后,需要进行配置文件的处理,Redis服务的架构一定程度决定了配置文件的配置,不同架构需要的配置是不一样的。
redis架构分为单机和集群,当然,为了高可用,无论是单机还是集群,都会要求做主从配置。
如果是单机版,最简单配置如下,这里配置的是域名,会比IP方便。

spring:
  redis:
    host: d0nameredis.x.com.cn
    port: 4567
    password: 123456

如果是集群版,就会需要配置多个节点,注意,所有节点的密码是一样的。

spring:
  redis:
    cluster:
      nodes:
        - d0name.x.com.cn:6781
        - d0name2.x.com.cn:6782
        - d0name3.x.com.cn:6783
    password: 123456

三、Redis客户端

Redis服务端进行数据存储时是有自己的命令的,应用在使用Redis是希望的是一个友好的接口,而不是直接使用Redis提供的各种基础命令,所以这个时候就出现了Redis客户端,他们向上给应用提供友好的接口,向下将应用接口对应的功能翻译成Redis服务支持的操作命令。客户端在整个架构中地位如下:

 当前主流的客户端为:
1、jedis(已经慢慢过时)
2、lettuce(SpringBoot官方默认,高性能)
3、redisson
它提供的功能远远超出了一个Redis客户端的范畴,它基于Redis实现了各种分布式环境下的常用功能。使用它来替换spring-boot-data-redis 默认使用的 Lettuce。在可以使用基本Redis功能的同时,也能使用它提供的一些服务。redisson-spring-boot-starter 实现了 spring-boot-data-redis。所以跟平时没有区别。直接使用 springboot提供的,RedisTemplate 即可。

五、连接池

以上配置中都没有谈到连接池,对于有网络连接的2个服务,通常都是需要连接池来提高响应速度的。
这里是应用服务和Redis服务之间的连接,他们之间是可以有连接池的,当然,连接池不是必须要使用过的。

六、SpringBoot自动配置Redis源码

org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
可以看到SpringBoot是会自动注入lettuce和jedis的,当然必须是存在相关的jar。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)// 必须引入spring-data-redis包
@EnableConfigurationProperties(RedisProperties.class)// 读取spring.redis.xxx配置
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })//引入lettuce和jedis客户端
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")// 没有redisTemplate就创建
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean// 没有stringRedisTemplate就创建
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

}

org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration
根据是否引入了lettuce-core来进行自动配置,如果配置文件中配置了pool相关配置,就会自动启用带连接池的连接工厂,同时需要手动引入commons-pool2包。所以我们可以看到,如果你需要使用jedis,办法就是排除lettuce包,并引入jedis包。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)// 必须引入lettuce-core包
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

	LettuceConnectionConfiguration(RedisProperties properties,
			ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
			ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
		super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
	}

	@Bean(destroyMethod = "shutdown")
	@ConditionalOnMissingBean(ClientResources.class)
	DefaultClientResources lettuceClientResources() {
		return DefaultClientResources.create();
	}

	// 没有RedisConnnectionFactory就创建,读取配置文件中配置,根据配置文件中是否配置了pool来决定是否创建带连接池的连接工厂
	@Bean
	@ConditionalOnMissingBean(RedisConnectionFactory.class)
	LettuceConnectionFactory redisConnectionFactory(
			ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
			ClientResources clientResources) throws UnknownHostException {
		LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
				getProperties().getLettuce().getPool());// 根据pool配置判断
		return createLettuceConnectionFactory(clientConfig);
	}

	private LettuceConnectionFactory createLettuceConnectionFactory(LettuceClientConfiguration clientConfiguration) {
		if (getSentinelConfig() != null) {
			return new LettuceConnectionFactory(getSentinelConfig(), clientConfiguration);
		}
		if (getClusterConfiguration() != null) {
			return new LettuceConnectionFactory(getClusterConfiguration(), clientConfiguration);
		}
		return new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
	}

	private LettuceClientConfiguration getLettuceClientConfiguration(
			ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
			ClientResources clientResources, Pool pool) {
		LettuceClientConfigurationBuilder builder = createBuilder(pool);
		applyProperties(builder);
		if (StringUtils.hasText(getProperties().getUrl())) {
			customizeConfigurationFromUrl(builder);
		}
		builder.clientResources(clientResources);
		builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
		return builder.build();
	}

	private LettuceClientConfigurationBuilder createBuilder(Pool pool) {
		if (pool == null) { // 根据pool配置判断
			return LettuceClientConfiguration.builder();
		}
		return new PoolBuilderFactory().createBuilder(pool);
	}

	private LettuceClientConfigurationBuilder applyProperties(
			LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
		if (getProperties().isSsl()) {
			builder.useSsl();
		}
		if (getProperties().getTimeout() != null) {
			builder.commandTimeout(getProperties().getTimeout());
		}
		if (getProperties().getLettuce() != null) {
			RedisProperties.Lettuce lettuce = getProperties().getLettuce();
			if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
				builder.shutdownTimeout(getProperties().getLettuce().getShutdownTimeout());
			}
		}
		if (StringUtils.hasText(getProperties().getClientName())) {
			builder.clientName(getProperties().getClientName());
		}
		return builder;
	}

	private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
		ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
		if (connectionInfo.isUseSsl()) {
			builder.useSsl();
		}
	}

	/**
	 * Inner class to allow optional commons-pool2 dependency.使用pool一定要手动引入commons-pool2包
	 */
	private static class PoolBuilderFactory {

		LettuceClientConfigurationBuilder createBuilder(Pool properties) {
			return LettucePoolingClientConfiguration.builder().poolConfig(getPoolConfig(properties));
		}

		private GenericObjectPoolConfig<?> getPoolConfig(Pool properties) {
			GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
			config.setMaxTotal(properties.getMaxActive());
			config.setMaxIdle(properties.getMaxIdle());
			config.setMinIdle(properties.getMinIdle());
			if (properties.getTimeBetweenEvictionRuns() != null) {
				config.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRuns().toMillis());
			}
			if (properties.getMaxWait() != null) {
				config.setMaxWaitMillis(properties.getMaxWait().toMillis());
			}
			return config;
		}

	}

}

七、想要使用Redisson的分布式能力

org.redisson.spring.starter.RedissonAutoConfiguration
可以看出,使用redisson必须引入spring-data-redis包,且redisson的自动配置会在spring-data-redis自动配置之前,也就是说Redisson会提前注入redisTemplate,这样其他客户端(如lettuce)就不会创建,同时在Redisson配置中,版本升级时,spring.redis.redisson.config设置更名为spring.redis.redisson.file,所以使用是需要注意,你可以不配置Redisson的特有配置,他也能正常工作。逻辑为,首先读取Redisson独有的配置,根据独有的配置创建,如果没有Redisson独有配置,就读spring.redis配置,创建哨兵模式,或者集群模式,或者单机模式。

@Configuration
@ConditionalOnClass({Redisson.class, RedisOperations.class})// 必须引入redisson包和spring-data-redis包
@AutoConfigureBefore(RedisAutoConfiguration.class) // 在Spring的RedisAutoConfiguration自动配置之前配置
@EnableConfigurationProperties({RedissonProperties.class, RedisProperties.class}) // 读取spring.redis.redisson.x和spring.redis.x配置
public class RedissonAutoConfiguration {

    private static final String REDIS_PROTOCOL_PREFIX = "redis://";
    private static final String REDISS_PROTOCOL_PREFIX = "rediss://";

    @Autowired(required = false)
    private List<RedissonAutoConfigurationCustomizer> redissonAutoConfigurationCustomizers;

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private RedisProperties redisProperties;

    @Autowired
    private ApplicationContext ctx;

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")// 没有redisTemplate就创建,依赖RedisConnectionFactory
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class) // 没有StringRedisTemplate就创建,依赖RedisConnectionFactory
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class) // 没有RedisConnectionFactory就创建,依赖 RedissonClient
    public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
        return new RedissonConnectionFactory(redisson);
    }

    @Bean
    @Lazy
    @ConditionalOnMissingBean(RedissonReactiveClient.class) // 没有RedissonReactiveClient就创建,依赖 RedissonClient
    public RedissonReactiveClient redissonReactive(RedissonClient redisson) {
        return redisson.reactive();
    }

    @Bean
    @Lazy
    @ConditionalOnMissingBean(RedissonRxClient.class) // 没有RedissonRxClient就创建,依赖 RedissonClient
    public RedissonRxClient redissonRxJava(RedissonClient redisson) {
        return redisson.rxJava();
    }

    @Bean(destroyMethod = "shutdown")
    @ConditionalOnMissingBean(RedissonClient.class) // 没有RedissonClient就创建
    public RedissonClient redisson() throws IOException {
        Config config = null;
        Method clusterMethod = ReflectionUtils.findMethod(RedisProperties.class, "getCluster");// 获取get spring.redis.cluster配置方法
        Method timeoutMethod = ReflectionUtils.findMethod(RedisProperties.class, "getTimeout"); // 获取get spring.redis.timeout配置方法
        Object timeoutValue = ReflectionUtils.invokeMethod(timeoutMethod, redisProperties); // 获取get spring.redis.timeout 值
        int timeout;
        if(null == timeoutValue){
            timeout = 10000;
        }else if (!(timeoutValue instanceof Integer)) {
            Method millisMethod = ReflectionUtils.findMethod(timeoutValue.getClass(), "toMillis");
            timeout = ((Long) ReflectionUtils.invokeMethod(millisMethod, timeoutValue)).intValue();
        } else {
            timeout = (Integer)timeoutValue;
        }

        if (redissonProperties.getConfig() != null) {// 有配置spring.redis.redisson.config=classpath:redisson.yaml
            try {
                config = Config.fromYAML(redissonProperties.getConfig()); // 用yaml格式读取
            } catch (IOException e) {
                try {
                    config = Config.fromJSON(redissonProperties.getConfig()); // 用json格式读取
                } catch (IOException e1) {
                    throw new IllegalArgumentException("Can't parse config", e1);
                }
            }
        } else if (redissonProperties.getFile() != null) { // 有配置spring.redis.redisson.file=classpath:redisson.yaml,新版file配置取代config
            try {
                InputStream is = getConfigStream();
                config = Config.fromYAML(is);
            } catch (IOException e) {
                // trying next format
                try {
                    InputStream is = getConfigStream();
                    config = Config.fromJSON(is);
                } catch (IOException e1) {
                    throw new IllegalArgumentException("Can't parse config", e1);
                }
            }
        } else if (redisProperties.getSentinel() != null) { // 读取 spring.redis.sentinel 配置并使用
            Method nodesMethod = ReflectionUtils.findMethod(Sentinel.class, "getNodes");
            Object nodesValue = ReflectionUtils.invokeMethod(nodesMethod, redisProperties.getSentinel());

            String[] nodes;
            if (nodesValue instanceof String) {
                nodes = convert(Arrays.asList(((String)nodesValue).split(",")));
            } else {
                nodes = convert((List<String>)nodesValue);
            }

            config = new Config();
            config.useSentinelServers()
                .setMasterName(redisProperties.getSentinel().getMaster())
                .addSentinelAddress(nodes)
                .setDatabase(redisProperties.getDatabase())
                .setConnectTimeout(timeout)
                .setPassword(redisProperties.getPassword());
        } else if (clusterMethod != null && ReflectionUtils.invokeMethod(clusterMethod, redisProperties) != null) { // 读取 spring.redis.cluster 配置并使用
            Object clusterObject = ReflectionUtils.invokeMethod(clusterMethod, redisProperties);
            Method nodesMethod = ReflectionUtils.findMethod(clusterObject.getClass(), "getNodes");
            List<String> nodesObject = (List) ReflectionUtils.invokeMethod(nodesMethod, clusterObject);

            String[] nodes = convert(nodesObject);

            config = new Config();
            config.useClusterServers()
                .addNodeAddress(nodes)
                .setConnectTimeout(timeout)
                .setPassword(redisProperties.getPassword());
        } else {
            config = new Config();
            String prefix = REDIS_PROTOCOL_PREFIX;
            Method method = ReflectionUtils.findMethod(RedisProperties.class, "isSsl");
            if (method != null && (Boolean)ReflectionUtils.invokeMethod(method, redisProperties)) {
                prefix = REDISS_PROTOCOL_PREFIX;
            }

            config.useSingleServer()
                .setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
                .setConnectTimeout(timeout)
                .setDatabase(redisProperties.getDatabase())
                .setPassword(redisProperties.getPassword());
        }
        if (redissonAutoConfigurationCustomizers != null) {
            for (RedissonAutoConfigurationCustomizer customizer : redissonAutoConfigurationCustomizers) {
                customizer.customize(config);
            }
        }
        return Redisson.create(config);
    }

    private String[] convert(List<String> nodesObject) {
        List<String> nodes = new ArrayList<String>(nodesObject.size());
        for (String node : nodesObject) {
            if (!node.startsWith(REDIS_PROTOCOL_PREFIX) && !node.startsWith(REDISS_PROTOCOL_PREFIX)) {
                nodes.add(REDIS_PROTOCOL_PREFIX + node);
            } else {
                nodes.add(node);
            }
        }
        return nodes.toArray(new String[nodes.size()]);
    }

    private InputStream getConfigStream() throws IOException {
        Resource resource = ctx.getResource(redissonProperties.getFile());
        InputStream is = resource.getInputStream();
        return is;
    }


}

配置Redisson参数的方式有3种;
1、使用单独的配置文件,spring.redis.redisson.file=classpath:redisson.yaml
2、直接写在主配置文件中:

spring:
  redis:
    redisson:
      config: |
        singleServerConfig:
          password: 123456
          address: "redis://127.0.0.1:6379"
          database: 1
        threads: 0
        nettyThreads: 0
        codec: !<org.redisson.codec.FstCodec> {}
        transportMode: "NIO"

3、写死在代码中,可以自定义配置

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        config.setTransportMode(TransportMode.NIO);
        SingleServerConfig singleServerConfig = config.useSingleServer();
        //可以用"rediss://"来启用SSL连接
        singleServerConfig.setAddress("redis://127.0.0.1:6379");
        singleServerConfig.setPassword("123456");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐