前言 1.Redis简介
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
相比Memcached
它支持存储的类型相对更多(字符、哈希、集合、有序集合、列表、GEO) ,同时Redis是线程安全的 。
除此之外,Redis 还提供一些类数据库的特性,比如事务,HA,主从库。可以说 Redis 兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。
2.Lettuce简介 Lettuce
和 Jedis
的都是连接Redis Server
的客户端程序,Spring Boot 1.x 默认使用的是 Jedis 客户端,2.x 替换成 Lettuce :
Jedis
在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接 。
Lettuce
基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例 。
来自 Lettuce官网 的一段描述:
Lettuce is a scalable Redis client for building non-blocking Reactive applications
(Lettuce 是构建非阻塞响应式应用的一个可伸缩的Redis客户端)
SpringBoot 整合 Redis 1.创建子模块 这里我们创建一个子模块
1 2 group = 'com.ray.study' artifact ='spring-boot-06-nosql-redis'
2.引入依赖 2.1 继承父工程依赖 在父工程spring-boot-seeds
的 settings.gradle
加入子工程
1 2 3 4 5 6 7 8 rootProject.name = 'spring-boot-seeds' include 'spring-boot-01-helloworld' include 'spring-boot-02-restful-test' include 'spring-boot-03-thymeleaf' include 'spring-boot-04-swagger2' include 'spring-boot-05-jpa' include 'spring-boot-05-mybatis' include 'spring-boot-06-nosql-redis'
这样,子工程spring-boot-05-redis
就会自动继承父工程中subprojects
`函数里声明的依赖,主要包含如下依赖:
1 2 3 4 5 implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
2.2 引入Redis
依赖 将子模块spring-boot-05-redis
的build.gradle
修改为如下内容:
1 2 3 4 5 6 7 8 dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.apache.commons:commons-pool2' }
可以看到spring-boot-starter-data-redis
中依赖了lettuce
4.修改配置 4.1 修改application.yml
这一步如果没有必要,可以省略,因为SpringBoot提供了默认的配置,默认配置如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 spring.redis.host =localhost spring.redis.port =6379 spring.redis.password =空 spring.redis.database =0 spring.redis.lettuce.pool.max-active =8 spring.redis.lettuce.pool.max-wait =-1 spring.redis.lettuce.pool.max-idle =8 spring.redis.lettuce.pool.min-idle =0
更多的可配置选项,请查看mybatis-spring-boot-autoconfigure
工程下的spring-configuration-metadata.json
文件,在这个json文件中,我们可以看到有哪些可选配置项,配置项的描述,以及配置项是对应那个Java类的哪个属性
4.2 注册Bean 这里我们注册了一个自定义RedisTemplate,可以方便我们存储实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.ray.study.springboot06nosqlredis.config;import org.springframework.boot.autoconfigure.AutoConfigureAfter;import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer;import java.io.Serializable;@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisCacheAutoConfiguration { @Bean public RedisTemplate<String, Serializable> redisCacheTemplate (LettuceConnectionFactory redisConnectionFactory) { RedisTemplate<String, Serializable> template = new RedisTemplate <>(); template.setKeySerializer(new StringRedisSerializer ()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer ()); template.setConnectionFactory(redisConnectionFactory); return template; } }
默认的RedisTemplate配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package org.springframework.boot.autoconfigure.data.redis;import java.net.UnknownHostException;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;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.context.annotation.Import;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;@Configuration @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate ( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate <>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate ( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate (); template.setConnectionFactory(redisConnectionFactory); return template; } }
可看到默认配置了两个RedisTemplate:
redisTemplate
:存储类型为 RedisTemplate<Object, Object>
stringRedisTemplate
:存储类型为 RedisTemplate<String, String>
5.entity 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.ray.study.springboot06nosqlredis.entity;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { private static final long serialVersionUID = 8655851615465363473L ; private Long id; private String name; private Integer age; }
6.单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.ray.study.springboot06nosqlredis;import com.ray.study.springboot06nosqlredis.entity.User;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.test.context.junit4.SpringRunner;import java.io.Serializable;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.stream.IntStream;import static org.hamcrest.CoreMatchers.equalTo;import static org.hamcrest.CoreMatchers.is;import static org.hamcrest.MatcherAssert.assertThat;@RunWith(SpringRunner.class) @SpringBootTest public class RedisTemplateTest { private static final Logger log = LoggerFactory.getLogger(RedisTemplateTest.class); @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisTemplate<String, Serializable> redisCacheTemplate; @Test public void get () throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(1000 ); IntStream.range(0 , 1000 ).forEach( i -> executorService.execute(() -> stringRedisTemplate.opsForValue().increment("k" , 1 )) ); Thread.sleep(6000 ); assertThat(stringRedisTemplate.opsForValue().get("k" ), is("1000" )); stringRedisTemplate.opsForValue().set("name" , "tom" ); assertThat(stringRedisTemplate.opsForValue().get("name" ), is("tom" )); String key = "battcn:user:1" ; User user = new User (1L , "u1" , 20 ); redisCacheTemplate.opsForValue().set(key, user); assertThat((User) redisCacheTemplate.opsForValue().get(key), equalTo(user)); } }