1、前言

Redis 是目前业界使用最广泛的内存数据存储。相比 Memcached,Redis 支持更丰富的数据结构,例如 hash, list, set等,同时支持数据持久化。除此之外,Redis 还提供一些类数据库的特性,比如事务,HA,主从库。可以说 Redis 兼具了缓存系统和数据库的一些特性,因此有着丰富的应用场景。本文介绍 Redis 在 Spring Boot 中两个典型的应用场景。

2、快速入门

2.1 导入依赖

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

Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis,spring-boot-starter-data-redis依赖于spring-data-redis 和 lettuce 。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis 为我们隔离了其中的差异性。

Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。 线程模型:bio(socket) nio(netty) 多路复用的线程模型(redis) .

2.2 添加配置文件

添加application.yml 文件

# Redis数据库索引(默认为0)
spring:
  redis:
    database: 0
# Redis服务器地址
    host: localhost
# Redis服务器连接端口
    port: 6379
# Redis服务器连接密码(默认为空)
    password:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
    lettuce:
        pool:
            max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
            max-wait: -1
# 连接池中的最大空闲连接 默认 8
            max-idle: 8
# 连接池中的最小空闲连接 默认 0
            min-idle: 0

2.3 添加 cache 的配置类

import org.springframework.cache.annotation.CachingConfigurerSupport;
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 java.lang.reflect.Method;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    @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();
            }
        };
    }
}

注意我们使用了注解:@EnableCaching来开启缓存。

2.4 使用

启动我们windows或者linux的redis客户端

spring版本和redis版本关系 redis与springboot_缓存

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
    private int id;
    private String name;
    private String sex;
}
import org.junit.jupiter.api.Test;
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.data.redis.core.ValueOperations;

import java.util.concurrent.TimeUnit;

@SpringBootTest
class RedisApplicationTests {
	@Autowired
	private StringRedisTemplate stringRedisTemplate;
	@Autowired
	private RedisTemplate redisTemplate;

	@Test
	void contextLoads(){
		String aaa = stringRedisTemplate.opsForValue().get("aaa");
		System.out.println(aaa);
	}
	@Test
	public void testObj() throws Exception {
		User user=new User(1, "zd", "aa123456");
		ValueOperations<String, User> operations=redisTemplate.opsForValue();
		operations.set("com.neox", user);
		operations.set("com.neo.f", user,5, TimeUnit.SECONDS);
		Thread.sleep(1000);
		//redisTemplate.delete("com.neo.f");
		boolean exists=redisTemplate.hasKey("com.neo.f");
		if(exists){
			System.out.println("exists is true");
		}else{
			System.out.println("exists is false");
		}
	}

}

运行我们的第一个Test

spring版本和redis版本关系 redis与springboot_java_02


可以看到输出结果为空随后我们启动客户端,输入 set aaa Nical

spring版本和redis版本关系 redis与springboot_spring版本和redis版本关系_03

再次运行

spring版本和redis版本关系 redis与springboot_java_04


运行第二个测试项

spring版本和redis版本关系 redis与springboot_缓存_05


修改延时时间为5秒

spring版本和redis版本关系 redis与springboot_java_06


再次运行

spring版本和redis版本关系 redis与springboot_redis_07

2.5 RedisTemplate和StringRedisTemplate的区别

在上面我们看到了有两个Template,肯定是有区别的:

  1. 两者的关系是StringRedisTemplate继承RedisTemplate。
  2. 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
  3. SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。
    StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
    RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

RedisTemplate使用的序列类在在操作数据的时候,比如说存入数据会将数据先序列化成字节数组然后在存入Redis数据库,这个时候打开Redis查看的时候,你会看到你的数据不是以可读的形式展现的,而是以字节数组显示,类似下面

spring版本和redis版本关系 redis与springboot_spring版本和redis版本关系_08


当然从Redis获取数据的时候也会默认将数据当做字节数组转化,这样就会导致一个问题,当需要获取的数据不是以字节数组存在redis当中而是正常的可读的字符串的时候,比如说下面这种形式的数据

spring版本和redis版本关系 redis与springboot_缓存_09


RedisTemplate就无法获取导数据,这个时候获取到的值就是NULL。这个时候StringRedisTempate就派上了用场。

当Redis当中的数据值是以可读的形式显示出来的时候,只能使用StringRedisTemplate才能获取到里面的数据。

所以当你使用RedisTemplate获取不到数据的时候请检查一下是不是Redis里面的数据是可读形式而非字节数组

另外我在测试的时候即使把StringRedisTemplate的序列化类修改成RedisTemplate的JdkSerializationRedisSerializer

最后还是无法获取被序列化的对象数据,即使是没有转化为对象的字节数组,代码如下

@Test 
public void testRedisSerializer(){ 

User u = new User(); 

u.setName("java"); 

u.setSex("male"); 

redisTemplate.opsForHash().put("user:","1",u); 

/*查看redisTemplate 的Serializer*/ 
System.out.println(redisTemplate.getKeySerializer()); 
System.out.println(redisTemplate.getValueSerializer()); 
/*查看StringRedisTemplate 的Serializer*/ 

System.out.println(stringRedisTemplate.getValueSerializer()); 
System.out.println(stringRedisTemplate.getValueSerializer()); 
/*将stringRedisTemplate序列化类设置成RedisTemplate的序列化类*/ 

stringRedisTemplate.setKeySerializer(new JdkSerializationRedisSerializer()); 

stringRedisTemplate.setValueSerializer(new JdkSerializationRedisSerializer()); 

/*即使在更换stringRedisTemplate的的Serializer和redisTemplate一致的 
* JdkSerializationRedisSerializer 
* 最后还是无法从redis中获取序列化的数据 
* */ 

System.out.println(stringRedisTemplate.getValueSerializer()); 
System.out.println(stringRedisTemplate.getValueSerializer()); 
User user = (User) redisTemplate.opsForHash().get("user:","1"); 
User user2 = (User) stringRedisTemplate.opsForHash().get("user:","1"); 
System.out.println("dsd");

Debug结果

spring版本和redis版本关系 redis与springboot_缓存_10

总结:

当你的redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可,但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。
以上都是手动使用的方式,如何在查找数据库的时候自动使用缓存呢,看下面;
自动根据方法生成缓存

@RestController
public class UserController {
    @RequestMapping("/getUser")
@Cacheable(value="user-key",key="'pet:singon:'+#user.username",
unless="#result==null")
    public User getUser() {
        User user=new User("aa@126.com", "aa", "aa123456", "aa","123");
        System.out.println("若下面没出现“无缓存的时候调用”字样且能打印出数据表示测试成功");
        return user;
}}

@CacheEvict(value="signonCache", key="'petstore:signon:'+#user.username")
public void update(User user) {
    dao.update(user);
}

@Cacheable: 插入缓存

value: 缓存名称
key: 缓存键,一般包含被缓存对象的主键,支持Spring EL表达式
unless: 只有当查询结果不为空时,才放入缓存
@CacheEvict: 失效缓存

3、 共享 Session

分布式系统中,Session 共享有很多的解决方案,其中托管到缓存中应该是最常用的方案之一,session共享:

  1. 数据库 (性能问题)
  2. tomcat session复制(性能问题)
  3. tomcat session粘带(将session粘带到redis)
  4. nginx ip_hash(热点问题)
  5. spring-session-data-redis 解决(常用)

3.1 Spring Session 官方说明

Spring Session provides an API and implementations for managing a user’s session information.
Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案。Spring Session 提供了集群 Session(Clustered Sessions)功能,默认采用外置的 Redis 来存储 Session 数据,以此来解决 Session 共享的问题。

3.2 如何使用

3.2.1:导入依赖

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

3.2.2 Session配置

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {}

maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Spring Boot 的 server.session.timeout 属性不再生效。

好了,这样就配置好了,我们来测试一下

3.2.3 测试

添加测试方法获取 sessionid

@RequestMapping("/uid")
String uid(HttpSession session) {
    UUID uid = (UUID) session.getAttribute("uid");
    if (uid == null) {
        uid = UUID.randomUUID();
    }
    session.setAttribute("uid", uid);
return session.getId();
}

运行RedisApplication主类

打开浏览器输入http://localhost:8080/uid

spring版本和redis版本关系 redis与springboot_java_11


登录 Redis 输入 keys ‘*sessions*’

spring版本和redis版本关系 redis与springboot_redis_12

3.2.4 如何在两台或者多台中共享 Session

其实就是按照上面的步骤在另一个项目中再次配置一次,启动后自动就进行了 Session 共享。
参考
http://emacoo.cn/backend/spring-redis/http://www.ityouknow.com/springboot/2016/03/06/spring-boot-redis.html