SpringBoot中使用Redis实现缓存和两台或者多台中共享 Session方案

41 篇文章 10 订阅
23 篇文章 2 订阅

Redis介绍

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

如何使用

1、引入依赖包

SpringBoot从1.4版本开始,spring-boot-starter-redis依赖改名了。

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

Spring Boot 提供了对 Redis 集成的组件包: spring-boot-starter-data-redisspring-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 框架来高效地管理多个连接。

2、添加配置文件

  redis:
    database: 0 # Redis数据库索引(默认为0)
    host: localhost  # Redis服务器地址
    port: 6379  # Redis服务器连接端口
    password:    # Redis服务器连接密码(默认为空)
    timeout: 6000ms  # 连接超时时长(毫秒)

#    Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce
#    jedis:
#      pool:
#        max-active: 10000  # 连接池最大连接数(使用负值表示没有限制)
#        max-wait: -1      # 连接池最大阻塞等待时间(使用负值表示没有限制)
#        max-idle: 10      # 连接池中的最大空闲连接
#        min-idle: 5       # 连接池中的最小空闲连接
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-wait: -1  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-idle: 8   # 连接池中的最大空闲连接 默认 8
        min-idle: 0   # 连接池中的最小空闲连接 默认 0

这样,SpringBoot将会自动配置redis,注入相关bean,我们就可以使用@CacheConfig@Cacheable@CachePut@CacheEvict了。

3、添加cache的配置类

缓存对象集合中,缓存是以key-value形式保存的。当不指定缓存的key时,SpringBoot会使用SimpleKeyGenerator生成key。

看源码:

public class SimpleKeyGenerator implements KeyGenerator {

	@Override
	public Object generate(Object target, Method method, Object... params) {
		return generateKey(params);
	}

	/**
	 * Generate a key based on the specified parameters.
	 */
	public static Object generateKey(Object... params) {
		if (params.length == 0) {
			return SimpleKey.EMPTY;
		}
		if (params.length == 1) {
			Object param = params[0];
			if (param != null && !param.getClass().isArray()) {
				return param;
			}
		}
		return new SimpleKey(params);
	}

}
	public SimpleKey(Object... elements) {
		Assert.notNull(elements, "Elements must not be null");
		this.params = new Object[elements.length];
		System.arraycopy(elements, 0, this.params, 0, elements.length);
		this.hashCode = Arrays.deepHashCode(this.params);
	}

查看源码可以发现,它是使用方法参数组合生成的一个key。

此时有一个问题:
如果2个方法,参数是一样的,但执行逻辑不同,那么将会导致执行第二个方法时命中第一个方法的缓存。
解决办法是在@Cacheable注解参数中指定key,或者自己实现一个KeyGenerator,在注解中指定KeyGenerator。
但是如果这样的情况很多,每一个都要指定key、KeyGenerator很麻烦。

Spring同样提供了方案:继承CachingConfigurerSupport并重写keyGenerator()

下面贴出代码:

package com.zoo.lion.modules.test.test.config;

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Configuration;

/**
 * @Author: xf
 * @Date: 2019/7/16 14:11
 * @Version 1.0
 */
@Configuration
@EnableCaching
public class TestRedisConfig extends CachingConfigurerSupport {

    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append("::").append(method.getName()).append(":");
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }
}

此时,缓存的key是包名+方法名+参数列表,这样就很难会冲突了。

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

3、好了,接下来就可以直接使用了(自动根据方法生成缓存)

    /**
     * 测试redis
     *
     * @return
     */
    @ApiOperation(value = "测试redis", notes = "测试redis")
    @GetMapping("/test")
    @Cacheable(value = "user-key")
    public Resp test() {
        System.out.println("无缓存的时候调用的。。。。。。。。。。。。。。");
        redisUtil.set("test", "i am here");
        String s = redisUtil.get("test");
        return Resp.success("success", s);
    }

这里使用了@Cacheable(value = "user-key")

  1. cacheNames:该缓存的方法名称,cacheName也可以使用value 
  2. key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合,方法的参数 
  3. unless:当方法返回空值时,就不会被缓存起来,决定是否要否定方法缓存,可以用来做条件判断 
  4. scf_company:结合redis,配置在redis的配置文件里

当调用这个方法的时候,会从一个名叫 scf_company的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。

效果:

redis

 注解详解

 Spring为我们提供了几个注解来支持Spring Cache。其核心主要是@Cacheable和@CacheEvict。使用@Cacheable标记的方法在执行后Spring Cache将缓存其返回结果,而使用@CacheEvict标记的方法会在方法执行前或者执行后移除Spring Cache中的某些元素。下面我们将来详细介绍一下Spring基于注解对Cache的支持所提供的几个注解。

@Cacheable

 @Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果

@CachePut

在支持Spring Cache的环境下,对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的。

@CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即value表示清除操作是发生在哪些Cache上的(对应Cache的名称);key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;condition表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation。

allEntries属性:allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。

 beforeInvocation属性:清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

@Caching

 @Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

 使用自定义注解

Spring允许我们在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如我们有如下这么一个使用@Cacheable进行标注的自定义注解。

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Cacheable(value=”users”)

public @interface MyCacheable {

 

}

共享 Session

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

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 共享的问题。

如何使用

1、引入依赖

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

2、Session 配置:

package com.zoo.lion.modules.test.test.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * @Author: xf
 * @Date: 2019/7/16 15:13
 * @Version 1.0
 */
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400 * 30)
public class TestSessionConfig {
}

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

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

3、测试

添加测试方法获取 sessionid

    @RequestMapping("/uid")
    public Resp testSession(HttpSession session) {
        UUID uid = (UUID) session.getAttribute("uid");
        if (uid == null) {
            uid = UUID.randomUUID();
        }
        session.setAttribute("uid", uid);
        return Resp.success("ok",session.getId());
    }

 结果:

 redis

其中1565853840000为失效时间,意思是这个时间后 Session 失效, db031986-8ecc-48d6-b471-b137a3ed6bc4 为 sessionId,登录 http://localhost:8080/uid 发现会一致,就说明 Session 已经在 Redis 里面进行有效的管理了。

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

其实就是按照上面的步骤在另一个项目中再次配置一次,启动后自动就进行了 Session 共享。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值