zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

SpringBoot 1.5.22 Release 版通过 yml 文件配置redis 切换 database 实现

2023-04-18 14:26:54 时间

1. 项目背景

问题:最近在做一个项目,通过jackson 的方式将数据 缓存到 redis 后,再根据key 取出数据为 null。

原因:SpringBoot 版本采用 SpringBoot 1.5.22 Release, 采用yml 配置 redis ,database 采用 1。但实际上系统一直使用的数据库0,所以导致取出的数据是null。

 redis:
        database: 1
        host: 192.168.1.xxx
        password: xxx
        pool:
            max-active: 8
            max-idle: 8
            max-wait: -1
            min-idle: 0
        port: 6380
        timeout: 60000

原来的 redisConfig 配置:

	@Bean(name="redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String,Object>();
		redisTemplate.setConnectionFactory(factory);

		RedisSerializer<String> redisSerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(redisSerializer);

		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jackson2JsonRedisSerializer.setObjectMapper(om);

		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

		RedisSerializer stringSerializer = new StringRedisSerializer();
		redisTemplate.setHashKeySerializer(stringSerializer);
		redisTemplate.setHashValueSerializer(stringSerializer);
		return redisTemplate;
	}

2. 知识准备

Jedis和Lettuce的区别
jedis和Lettuce都是Redis的客户端,它们都可以连接Redis服务器,但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器。因为当使用Jedis客户端连接Redis服务器的时候,每个线程都要拿自己创建的Jedis实例去连接Redis客户端,当有很多个线程的时候,不仅开销大需要反复的创建关闭一个Jedis连接,而且也是线程不安全的,一个线程通过Jedis实例更改Redis服务器中的数据之后会影响另一个线程;

但是如果使用Lettuce这个客户端连接Redis服务器的时候,就不会出现上面的情况,Lettuce底层使用的是Netty,当有多个线程都需要连接Redis服务器的时候,可以保证只创建一个Lettuce连接,使所有的线程共享这一个Lettuce连接,这样可以减少创建关闭一个Lettuce连接时候的开销;而且这种方式也是线程安全的,不会出现一个线程通过Lettuce更改Redis服务器中的数据之后而影响另一个线程的情况;

spring-boot-starter-data-redis有两种实现:lettuce 和 jedis 。然而默认是使用lettuce.

spring boot 2的spring-boot-starter-data-redis中,默认使用的是lettuce作为redis客户端,它与jedis的主要区别如下:

1.Jedis:
Jedis是同步的,不支持异步,Jedis客户端实例不是线程安全的,需要每个线程一个Jedis实例,所以一般通过连接池来使用Jedis.

优点:

提供了比较全面的 Redis 操作特性的 API
API 基本与 Redis 的指令一一对应,使用简单易理解
缺点:

同步阻塞 IO
不支持异步
线程不安全
如果不使用默认的lettuce,使用jedis的话,可以排除lettuce的依赖,手动加入jedis依赖,配置如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>


2.Lettuce:
Lettuce是基于Netty框架的事件驱动的Redis客户端,其方法调用是异步的,Lettuce的API也是线程安全的,所以多个线程可以操作单个Lettuce连接来完成各种操作,同时Lettuce也支持连接池.

优点:

线程安全
基于 Netty 框架的事件驱动的通信,可异步调用
适用于分布式缓存
缺点:

API 更抽象,学习使用成本高
pom:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.X集成redis所需common-pool2-->
<!-- 如果使用Lettuce作为连接池,需要引入commons-pool2包,
否则会报错bean注入失败 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

3. 正确配置

SpringBoot 2.0 以下 RedisConnectionFactory 通过properties 文件配置database 没有任何问题,但是通过yml 文件时,默认为0,修改为其他database 不生效,需要通过JedisConnectionFactory 重新设置一下。

@Bean(name="redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String,Object>();

		//因为配置文件改为 yml以后,配置文件中的spring.redis.database 非0时无效,此处手动设置成配置文件中内容。
		int database = Integer.valueOf(environment.getProperty("spring.redis.database"));
		JedisConnectionFactory jedisConnectionFactoryFactory = (JedisConnectionFactory)factory;
		jedisConnectionFactoryFactory.setDatabase(database);

		redisTemplate.setConnectionFactory(jedisConnectionFactoryFactory);

		RedisSerializer<String> redisSerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(redisSerializer);

		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
		jackson2JsonRedisSerializer.setObjectMapper(om);

		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

		RedisSerializer stringSerializer = new StringRedisSerializer();
		redisTemplate.setHashKeySerializer(stringSerializer);
		redisTemplate.setHashValueSerializer(stringSerializer);
		return redisTemplate;
	}

完整代码:
RedisCacheConfig

package com.wzw.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching
public class RedisCacheConfig {

	@Resource
	private Environment environment;

	@Bean
	public CacheManager cacheManager(RedisTemplate<?, ?> redisTemplate){
		RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
		cacheManager.setDefaultExpiration(3600);//默认缓存1小时

		Map<String, Long> expiresMap=new HashMap<String, Long>();
		expiresMap.put("ibsLongTimeCache", 7*24*3600L);//长时间缓存区域   7天   缓存基本不会改变的数据
		expiresMap.put("ibsTempCache", 600L);//临时缓存区域  10分钟  改动基本稍多实时性要求不高的数据
		cacheManager.setExpires(expiresMap);
		return cacheManager;
	}

	@Bean(name="redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String,Object>();

		//因为配置文件改为 yml以后,配置文件中的spring.redis.database 非0时无效,此处手动设置成配置文件中内容。
		int database = Integer.valueOf(environment.getProperty("spring.redis.database"));
		JedisConnectionFactory jedisConnectionFactoryFactory = (JedisConnectionFactory)factory;
		jedisConnectionFactoryFactory.setDatabase(database);

		redisTemplate.setConnectionFactory(jedisConnectionFactoryFactory);

		RedisSerializer<String> redisSerializer = new StringRedisSerializer();
		redisTemplate.setKeySerializer(redisSerializer);

		Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
		jackson2JsonRedisSerializer.setObjectMapper(om);

		redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

		RedisSerializer stringSerializer = new StringRedisSerializer();
		redisTemplate.setHashKeySerializer(stringSerializer);
		redisTemplate.setHashValueSerializer(stringSerializer);
		return redisTemplate;
	}

	@Bean
	public KeyGenerator keyGenerator() {
		return new KeyGenerator() {
			@Override
			public Object generate(Object target, Method method, Object... params) {
				StringBuilder sb = new StringBuilder();
				sb.append(target.getClass().getName());
				sb.append(method.getName());
				for (Object obj : params) {
					sb.append(obj.toString());
				}
				return sb.toString();
			}
		};
	}

}

RedisService

package com.wzw.service;

import java.util.List;
import java.util.Map;

public interface RedisService {
	/**
	 * 批量删除对应的value
	 *
	 * @param keys
	 */
	public void remove(final String... keys);

	/**
	 * 批量删除key
	 *
	 * @param pattern
	 */
	public void removePattern(final String pattern);

	/**
	 * 删除对应的value
	 *
	 * @param key
	 */
	public void remove(final String key);

	/**
	 * 判断缓存中是否有对应的value
	 *
	 * @param key
	 * @return
	 */
	public boolean exists(final String key);

	/**
	 * 读取缓存
	 *
	 * @param key
	 * @return
	 */
	public Object get(final String key);

	/**
	 * 读取缓存
	 *
	 * @param key
	 * @return
	 */
	public Map<Object,Object> getHashKey(final String key);


	public List<Object> getHashKey(final String key, List<Object> hashkeys);

	/**
	 * 写入缓存
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	public boolean set(final String key, Object value);

	/**
	 * 写入缓存
	 * @param key
	 * @param value
	 * @return
	 */
	public boolean set(final String key, Object value, Integer expireTime);

	/**
	 * 设置失效时间
	 * @param key
	 * @param expireTime
	 * @return
	 */
	public boolean expire(final String key, Integer expireTime);
}

RedisServiceImpl

package com.wzw.service.impl;

import com.wzw.service.RedisService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;


@Service("redisService")
public class RedisServiceImpl implements RedisService{

	@Autowired
	@Qualifier("redisTemplate")
	private RedisTemplate<String, Object> redisTemplate;
	/**
	 * 批量删除对应的value
	 *
	 * @param keys
	 */
	@Override
	public void remove(final String... keys) {
		for (String key : keys) {
			remove(key);
		}
	}

	/**
	 * 批量删除key
	 *
	 * @param pattern
	 */
	@Override
	public void removePattern(final String pattern) {
		Set<String> keys = redisTemplate.keys(pattern);
		if (keys.size() > 0)
			redisTemplate.delete(keys);
	}

	/**
	 * 删除对应的value
	 *
	 * @param key
	 */
	@Override
	public void remove(final String key) {
		if (exists(key)) {
			redisTemplate.delete(key);
		}
	}

	/**
	 * 判断缓存中是否有对应的value
	 *
	 * @param key
	 * @return
	 */
	@Override
	public boolean exists(final String key) {
		return redisTemplate.hasKey(key);
	}

	/**
	 * 读取缓存
	 *
	 * @param key
	 * @return
	 */
	@Override
	public Object get(final String key) {
		Object result = null;
		ValueOperations<String, Object> operations = redisTemplate.opsForValue();
		result = operations.get(key);
		return result;
	}

	/**
	 * 读取缓存
	 *
	 * @param key
	 * @return
	 */
	@Override
	public Map<Object,Object> getHashKey(final String key) {
		Map<Object,Object> objectMap = redisTemplate.opsForHash().entries(key);;
		return objectMap;
	}

	/**
	 * 读取缓存
	 *
	 * @param key
	 * @return
	 */
	@Override
	public List<Object> getHashKey(final String key, List<Object> hashkeys) {
		List<Object> objectMap = redisTemplate.opsForHash().multiGet(key,hashkeys);
		return objectMap;
	}

	/**
	 * 写入缓存
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	@Override
	public boolean set(final String key, Object value) {
		boolean result = false;
		try {
			ValueOperations<String, Object> operations = redisTemplate.opsForValue();
			operations.set(key, value);
			result = true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * 写入缓存
	 * @param key
	 * @param value
	 * @return
	 */
	@Override
	public boolean set(final String key, Object value, Integer expireTime) {
		boolean result = false;
		try {
			ValueOperations<String, Object> operations = redisTemplate.opsForValue();
			operations.set(key, value);
			redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
			result = true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * 设置失效时间
	 * @param key
	 * @param expireTime
	 * @return
	 * @author Jone
	 * create date:2018年2月27日
	 */
	@Override
	public boolean expire(final String key, Integer expireTime) {
		boolean result = false;
		try {
			redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
			result = true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}
}

yml

spring:
	redis:
        database: 1
        host: 192.168.1.xxx
        password: asdffdsa
        pool:
            max-active: 8
            max-idle: 8
            max-wait: -1
            min-idle: 0
        port: 6380
        timeout: 60000

4.小结

  1. spirngboot 2.0 以下 redis 采用jedis,jedis 同步、不支持异步、线程不安全; 2.0 以后采用 Lettuce,底层采用redis 支持异步、线程安全 。
  2. springboot 2.0 一下采用properties 配置 redis 的database 没有任何问题,当采用yml 文件配置时,默认时0 ,通过配置文件修改也不生效,需要在redisConfig 里通过JedisConnectionFactory 重新指定一下。
int database = Integer.valueOf(environment.getProperty("spring.redis.database"));
		JedisConnectionFactory jedisConnectionFactoryFactory = (JedisConnectionFactory)factory;
		jedisConnectionFactoryFactory.setDatabase(database);

		redisTemplate.setConnectionFactory(jedisConnectionFactoryFactory);