spring boot学习(06):Redis 实现数据缓存和 Session 共享

前言

前面我们学习了redis的基本使用,我们知道redis最常用的应用场景,就是数据缓存和session共享,Spring Boot 针对这两个场景都做了一些优化,让我们在实际项目中使用非常的方便。

数据缓存

使用 Redis 做为数据缓存是最常用的场景了。我们知道绝大多数的网站/系统,最先遇到的一个性能瓶颈就是数据库,使用 Redis 做数据库的前置缓存,可以非常有效的降低数据库的压力,从而提升整个系统的响应效率和并发量。Spring Boot 也提供了非常简单的解决方案,这里给大家演示最核心的三个注解:@Cacheable、@CacheEvict、@CachePut 。

spring-boot-starter-cache

先了解一下组件:spring-boot-starter-cache

spring-boot-starter-cache 是 Spring Boot 提供缓存支持的 starter 包。spring-boot-starter-cache 会进行缓存的自动化配置和识别,Spring Boot 为 Redis 自动配置了 RedisCacheConfiguration 等信息。spring-boot-starter-cache 中的注解也主要是使用了 Spring Cache 提供的支持。

@Cacheable

@Cacheable 是用来声明方法是可缓存的,将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方法,直接从缓存中取值。@Cacheable 可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。

简单例子:

@RequestMapping("/hello")
@Cacheable(value="neoCache")
public String hello(String name) {
    System.out.println("没有走缓存!");
    return "hello "+name;
}

项目启动后,http://localhost:8080/hello?name=ljy访问,发现第一次打印”没有走缓存!”,继续访问,发现不打印数据,说明没有走controller方法,而是直接从缓存中获取的数据。

@Cacheable的参数

  • value:缓存的名称。
  • key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
  • condition:触发条件,只有满足条件的情况才会加入缓存,默认为空,既表示全部都加入缓存,支持 SpEL。

    数据库例子:

    扫描二维码关注公众号,回复: 3255095 查看本文章
    @RequestMapping("/getUsers")
    @Cacheable(value="usersCache",key="#nickname",condition="#nickname.length() <= 6")
    public List<User> getUsers(String nickname) {
        List<User> users=userRepository.findByNickname(nickname);
        System.out.println("执行了数据库操作");
        return users;
    }

    执行以下语句测试:
    http://localhost:8080/getUsers?nickname=1234
    http://localhost:8080/getUsers?nickname=1234567
    我们发现结果不一样,执行到:condition=”#nickname.length() <= 6” ,spring首先检查condition条件是否满足,满足,在缓存空间中查找使用 key 存储的对象,如果找到,将找到的结果返回,如果没有找到执行方法,将方法的返回值以 key-value 对象的方式存入缓存中,然后方法返回。

@CachePut

项目运行中会对数据库的信息进行更新,如果仍然使用 @Cacheable 就会导致数据库的信息和缓存的信息不一致。在以往的项目中,我们一般更新完数据库后,再手动删除掉 Redis 中对应的缓存,以保证数据的一致性。Spring 提供了另外的一种解决方案,可以优雅的去更新缓存。

与 @Cacheable 不同的是使用 @CachePut 标注的方法在执行前,不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
新增一个 getPutUsers 方法,value、key 设置和 getUsers 方法保持一致,使用 @CachePut。

@RequestMapping("/getPutUsers")
@CachePut(value="usersCache",key="#nickname")
public List<User> getPutUsers(String nickname) {
    List<User> users=userRepository.findByNickname(nickname);
    System.out.println("执行了数据库操作");
    return users;
}

先访问: http://localhost:8080/getUsers?nickname=ljy 我们可以得到一条数据,然后用另外的方法修改nickname=ljy的数据库的值,继续访问此地址,发现数据并没有实时更新,这样就会出现问题。然后访问: http://localhost:8080/getPutUsers?nickname=ljy 后再访问:先访问: http://localhost:8080/getUsers?nickname=ljy,发现数据更新了,并且是在缓存中直接读取的数据。

重点内容@CachePut 配置方法

  • value 缓存的名称。
  • key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
  • condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。

@CacheEvict

@CacheEvict 是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

@CacheEvict 可以指定的属性有 value、key、condition、allEntries 和 beforeInvocation。

其中 value、key 和 condition 的语义与 @Cacheable 对应的属性类似。即 value 表示清除操作是发生在哪些 Cache 上的(对应 Cache 的名称);key 表示需要清除的是哪个 key,如未指定则会使用默认策略生成的 key;condition 表示清除操作发生的条件。

allEntries 属性:

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

在上一个方法中我们使用注解:@CachePut(value=”usersCache”,key=”#nickname”) 来更新缓存,但如果不写 key=”#nickname”,Spring Boot 会以默认的 key 值去更新缓存,导致最上面的方法 getUsers() 方法并没有获取最新的数据。但是现在使用 @CacheEvict 就可以解决这个问题了,它会将所有以 usersCache 为名的缓存全部清除。

beforeInvocation 属性:

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

总结

@Cacheable
Spring 在执行 @Cacheable 标注的方法前先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,执行该方法并将方法返回值放进缓存。 一般用于读取数据。

@CachePut
和 @Cacheable 类似,但会把方法的返回值放入缓存中, 主要用于数据新增和修改方法

@CacheEvict
方法执行成功后会从缓存中移除相应数据。

session共享

什么是session?

由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户。Session 是另一种记录客户状态的机制,不同的是 Cookie 保存在客户端浏览器中,而 Session 保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找该客户的状态就可以了。

为什么需要 Session 共享

这里写图片描述
用户的请求首先会到达前置网关,前置网关根据路由策略将请求分发到后端的服务器,这就会出现第一次的请求会交给服务器 1 处理,下次的请求可能会是服务B处理,如果不做 Session 共享的话,就有可能出现用户在服务 2登录了,下次请求的时候到达服务 2 又要求用户重新登录。

Spring Session

Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案。Spring Session 提供了集群 Session(Clustered Sessions)功能,默认采用外置的 Redis 来存储 Session 数据(不用手动存储到redis中),以此来解决 Session 共享的问题。

Spring Session 为企业级 Java 应用的 session 管理带来了革新,使得以下的功能更加容易实现:

  • API 和用于管理用户会话的实现。
  • HttpSession - 允许以应用程序容器(即 Tomcat)中性的方式替换 HttpSession。
  • 将 session 所保存的状态卸载到特定的外部 session 存储中,如 Redis 或 Apache Geode
    中,它们能够以独立于应用服务器的方式提供高质量的集群。
  • 支持每个浏览器上使用多个 session,从而能够很容易地构建更加丰富的终端用户体验。
  • 控制 session id 如何在客户端和服务器之间进行交换,这样的话就能很容易地编写 Restful API,因为它可以从 HTTP头信息中获取 session id,而不必再依赖于 cookie。
  • 当用户使用 WebSocket 发送请求的时候,能够保持 HttpSession 处于活跃状态。

Spring 为 Spring Session 和 Redis 的集成提供了组件:spring-session-data-redis,下面演示如何使用。

快速集成

引入依赖包

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

Session 配置

@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class SessionConfig {
}
maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,原 Boot 的 server.session.timeout 属性不再生效。

实现模拟登陆

添加登陆方法和注册方法:

添加登录方法,登录成功后将用户信息存放到 Session 中。

@RequestMapping(value = "/login")
public String login (HttpServletRequest request,String userName,String password){
    String msg="登陆失败!";
    User user= userRepository.findByUserName(userName);
    if (user!=null && user.getPassword().equals(password)){
        request.getSession().setAttribute("user",user);
        msg="登陆成功!";
    }
    return msg;
}

定义 index 方法,只有用户登录之后才会看到:index content 这条信息否则提示请先登录。

@RequestMapping(value = "/index")
public String index (HttpServletRequest request){
    String msg="首页内容";
    Object user= request.getSession().getAttribute("user");
    if (user==null){
        msg="请先登录!";
    }
    return msg;
}

将项目复制,修改项目端口号:8899和8800

测试流程:
访问:http://localhost:8800/indexhttp://localhost:8899/index都会提示先登录。
用8800窗口登陆:localhost:8800/login?userName=xiaoli&password=123456。
登陆成功后访问首页:显示首页内容,8899端口直接访问首页,我们发现显示首页内容,则实现了session共享。

退出登录:

@RequestMapping(value = "/logout")
    public String logout (HttpServletRequest request){
        HttpSession session=request.getSession();
        session.removeAttribute("user");
        return "";
    }

猜你喜欢

转载自blog.csdn.net/weixin_41555736/article/details/80945073