SpringBoot集成Redis

文章主要讨论了:

        1)SpringBoot集成Redis;

                对Lettuce连接池的讨论;

        2)Redis配置

                特别优化了Java对象以hashValue的格式写入Redis时,去掉默认的@type属性

        3)Redis工具类

                Redis管道读写

 1、SpringBoot集成Redis

spingboot提供了一个专门操作Redis的项目sping-boot-starter-data-redis,里面封装了jedis和lettuce两个客户端。

导入依赖:

<!-- Redis缓存操作 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

若是联网环境下开发,pom文件导入依赖后,直接更新即可自动下载(自己设置maven镜像源,可快速下载);由于我是在断网环境下开发,所以需要下载离线依赖jar包并手动安装,命令如下:

mvn install:install-file -Dfile=spring-boot-starter-data-redis-3.2.0.jar -DgroupId=org.springframework.boot -DartifactId=spring-boot-starter-data-redis -Dversion=3.2.0 -Dpackaging=jar

在application.yml文件中配置redis

spring:
    # 地址
    # host: xx.xx.xx.xx
    # 端口
    # port: xx
    # 数据库索引
    database: 1
    # 密码
    password:xxxxxx
    # 连接超时时间
    # timeout: 60s
    
    # 因为这里我的项目用的是哨兵模式的Redis集群
    sentiel:
        # 主节点名称
        master: mymaster
        # 主节点Redis的地址端口
        ndoes:
            - xx.xx.xx.xx:xx    
        # 密码
        # password:xxxxx

    # 这里用的是lettuce客户端默认
    lettuce:
        pool:
            # 连接池中的最小空闲连接
            min-idle: 0
            # 连接池中的最大空闲连接
            max-idle: 8
            # 连接池的最大数据库连接数
            max-active: 8
            # 连接池的最大阻塞等待时间(负值表示没有限制)
            max-wait: 60s
            time-between-eviction-runs: 60s
        shutdown-timeout:1s

request:
    myConfig:
        # 主备状态
        mainFlag = true
        # 服务器组名
        serverName:server1
        # reids负载均衡模式配置:1所有节点负载均衡   2从从节点负载均衡   3从延迟最低节点获取数据
        redisCategory: 2
            
            

 可以从配置文件中看到我使用了连接池,但单纯配置连接池不行,需要导入连接池依赖,

<!-- pool 对象池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

项目运行后,redistemplate对象中如果看到poolConfig有值,说明连接池使用成功。

lettuce连接池真的有用吗?

前提:我的项目是一个数据资源池项目,对数据响应时效有一定要求,我使用redis数据库作为缓存,又增加了连接池,以期望提高数据处理效率。

假设一个命令的处理时间是100ms,加上数据传输和系统调度假设是20ms,那么处理5个命令的时间就是(100+20)*5 = 600ms;加上连接池后,处理5个命令的时间理论上应该是100*5+20 = 520ms。
但在反复测试调整redis连接池个数后,发现接口响应时间并没有大幅度提高,为什么???lettuce连接池真的有必要吗???有用吗???大家可以去其他博客调研一下lettuce。

Lettuce的官方文档中有这样的话:

Lettuce is thread-safe by design which is sufficient for most cases. 
All Redis user operations are executed single-threaded. Using multiple 
connections does not impact the performance of an application in a 
positive way. The use of blocking operations usually goes hand in hand 
with worker threads that get their dedicated connection. The use of Redis 
Transactions is the typical use case for dynamic connection pooling as the 
number of threads requiring a dedicated connection tends to be dynamic. 
That said, the requirement for dynamic connection pooling is limited. 
Connection pooling always comes with a cost of complexity and maintenance.

 大概意思就是:lettuce是一个线程安全的,大多数场景下,单连接即可满足业务需求。多连接并不能显著提高应用的效率。但比如事务操作是一个典型的动态连接池操作。

2、Redis配置类

自定义序列化器

/**
自定义序列化器,特别处理启用WriteMapNullValue 并禁用@type信息
*/
public class CustomFastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
	private final Class<T> clazz;
	
	public CustomFastJson2JsonRedisSerializer(Class<T> clazz) {
		super();
		this.clazz = clazz;
	}

	@Override
	public byte[] serialize(T t) throws SerializationException {
		if (t == null) {
			return new byte[0];
		}
		// 启用WriteMapNullValue 并禁用@type信息
		return JSON.toJSONString(t, JSONWriter.Feature.WriteMapNullValue).getBytes(StandardCharsets.UTF_8);
	}

	@Override
	public T deserialize(byte[] bytes) throws SerializationException {
		if(bytes == null || bytes.length <= 0) {
			reutrn null;
		}
		String str = new String(bytes, StandarCharsets.UTF_8);
		return JSON.parseObject(str, clazz);
	}
	
}



 3、redis配置类

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigureSupport{
	@Value("${request.myConfig.redisCategory}")
	private Integer redisCategory;

	@Bean
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(connectionFactory);

		FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
		// 用StringRedisSerializer 序列化和反序列化Redis的key
		template.setKeySerializer(new StringRedisSerializer());
		template.setValueSerializer(serializer);

		// Hash对象的key也有StringRedisSerializer序列化和反序列化
		template.setHashKeySerializer(new StringRedisSerializer());
		// Hash对象的value用自定义的序列化器,避免@type被写入
		template.setHashValueSerializer(new CustomFastJson2JsonRedisSerializer<>(Object.class));
		
		template.afterPropertiesSet();
		return template;
	}

	/**
	lettuce读取客户端策略	
	*/
	@Bean
	public LettuceClientConfigurationBuilderCustomizer builderCustomizer() {
		return new LettuceClientConfigurationBuilderCustomizer() {
			@Override
			public void customize(LettuceClientConfiguration.LettuceClientConfiguraionBuilder builder){
				if(redisCategory.equals(1)) {
					// 从任意节点读取数据
					builder.readFrom(ReadFrom.ANY);
				}else if(redisCategory.equals(2)) {
					// 客户端仅从从节点读取数据。将读取负载均衡到多个从节点上,减轻主节点的压力。但从节点的数据可能存在一定延迟,不能保证数据是最新的
					builder.readFrom(ReadFrom.ANY_REPLICA);
				}else if(redisCategory.equals(3)) {
					// 客户端从网络延迟最低的节点读取数据。在分布式环境中,可以让客户端从距离最近或者网络延迟最低的节点读取数据
					builder.readFrom(ReadFrom.LOWEST_LATENCY);
				}else{
					// 从任意节点读取数据
					builder.readFrom(ReadFrom.ANY);
				}
			}
		}
	}
	
	@Bean
	public DefaultRedisScript<Long> limitScript() {
		DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
		redisScript.setScriptText(limitScriptText());
		redisScript.setResultType(Long.class);
		return redisScript;
	}

	/**
	限流脚本
	*/
	private String limitScriptText() {
		return "local key = KEYS[1]\n" +
				"local count = tonumber(ARGV[1])\n" +
				"local time = tonumber(ARGV[2])\n" +
				"local current = redis.call('get', key);\n" +
				"if current and tonumber(current) > count then\n" +
				"	return tonumber(current);\n" +
				"end\n" +
				"current = redis.call('incr', key)\n" +
				"if tonumber(current) == 1 then\n" +
				"	redis.call('expire', key, time)\n" +
				"end\n" +
				"return tonumber(current);";
	}
}

3、Redis工具类

@Component
public class RedisCache{
	@AutoWired
	private RedisTemplate redisTemplate;

	// redis命令操作很多,这里不一一展示,只展示管道化操作
	

	/**
	以管道的方式批量查询String数据
	*/
	public Map<String, Object> getStringByPipeline(final Collection<String> keys) {
		Map<String, Object> ansResult = new HashMap<>();
		Map<Integer, String> keyIndexMap = new HashMap<>();
		List<Map<String, Object>> tmpResult = redisTemplate.executePipelined(new SessionCallBack<Object>(){
			@Override
			public Object execute(RedisOperations operations) throws DataAcessException {
				// redis查询返回结果是有序的,想key和结果对应,就需要记录key的位置
				int idx = 0;
				for (String key : keys) {
					operations.opsForValue().get(key);
					keyIndexMap.put(idx, key);
					idx++;
				}
				return null;
			}
		});
		// 查询结果和key绑定
		for (int i = 0; i < tmpResult.size(); i++) {
			Map<String, Object> hash = tmpResult.get(i);
			ansResult.put(keyindexMap.get(i), hash);
		}
		return ansResult;
	}

	

	/**
	以管道的方式批量写String数据
	*/
	public void setStringByPipeline(final Map<String, String> dataList) {
		redisTemplate.executePipelined(new SessionCallBack<Object>() {
			@Override
			public Object execute(RedisOperations operations) throws DataAcessException {
				for (String key : dataList.keySet()) {
					operations.opsForValue().set(key, datalist.get(key);
				}
				return null;
			}
		});
	}


	/**
	以管道的方式批量查询hash数据
	管道只有在批量查询结束后才会返回结果,如果你在execute里输出查询对象,是不会有输出的
	*/
	public Map<String, Object> getHashByPipeline(final Collection<String> keys) {
		Map<String, Object> ansResult = new HashMap<>();
		Map<Integer, String> keyIndexMap = new HashMap<>();
		List<Map<String, Object>> tmpResult = redisTemplate.executePipelined(new SessionCallBack<Object>(){
			@Override
			public Object execute(RedisOperations operations) throws DataAcessException {
				// redis查询返回结果是有序的,想key和结果对应,就需要记录key的位置
				int idx = 0;
				for (String key : keys) {
					operations.opsForHash().entries(key);
					keyIndexMap.put(idx, key);
					idx++;
				}
				return null;
			}
		});
		// 查询结果和key绑定
		for (int i = 0; i < tmpResult.size(); i++) {
			Map<String, Object> hash = tmpResult.get(i);
			ansResult.put(keyindexMap.get(i), hash);
		}
		return ansResult;
	}

	
	/**
	写入hash 
	这里写的时候不会再写入@type
	*/
	public <T> void setHash(final String key, final String hKey, final T value) {
		redisTemplate.opsForHash().put(key, hKey, value);
	}

}

使用redis

// 使用Redis工具类进行读写操作,
public class Test{
    @AutoWired
    private  RedisCache redisCache;

    public void test() {
        Map<String, Object> map = new HashMap<>();
        map.put("userName", "Tomy");
        map.put("passWord", "youaremylove");
        map.put("time", "2026-12--31 00:00:00");
        redisCache.setHash("config", map.get("userName"), map);
    }
}

Spring Boot集成Redis可以通过以下步骤实现: 1. 引入spring-boot-starter-data-redis依赖。在项目的pom.xml文件中,添加以下依赖项: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 这将自动引入与Redis集成所需的依赖项。 2. 在Spring Boot的核心配置文件application.properties中配置Redis连接信息。在该文件中添加以下配置项: ``` spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password=123456 ``` 根据你的实际情况,将host、port和password替换为相应的值。这些配置将用于建立与Redis服务器的连接。 通过以上步骤,你就成功地在Spring Boot应用程序中集成Redis。现在,你可以使用Spring Data Redis的API来访问和操作Redis数据库。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SpringBoot集成redis](https://blog.youkuaiyun.com/qq_43512320/article/details/122684865)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [springboot集成Redis](https://blog.youkuaiyun.com/m0_54853420/article/details/126515971)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值