美文网首页分布式,微服务
Spring Boot缓存技术-----F04

Spring Boot缓存技术-----F04

作者: 小山居 | 来源:发表于2019-08-26 16:05 被阅读0次

    Spring Boot****缓存技术*

    1.Ehcache Redis 的对比

    Ehcache

    在java项目广泛的使用。它是一个开源的、设计于提高在数据从RDBMS中取出来的高花费、高延迟采取的一种缓存方案。正因为Ehcache具有健壮性(基于java开发)、被认证(具有apache 2.0 license)、充满特色,所以被用于大型复杂分布式web application的各个节点中。够简单就是Ehcache的一大特色,自然用起来 just so easy!

    够快

    Ehcache 的发行有一段时长了,经过几年的努力和不计其数的性能测试,Ehcache 终被设计于 large, high concurrency systems。

    够简单

    开发者提供的接口非常简单明了,从 Ehcache 的搭建到运用运行仅仅需要的是你宝贵的几分钟。其实很多编程者都不知道自己在用Ehcache,Ehcache被广泛的运用于其他的开源项目,比如:Hibernate。

    够袖珍

    关于这点的特性,官方给了一个很可爱的名字 small foot print,一般 Ehcache 的发布版本不会到2M, V2.2.3版本才668KB,目前最新版的V3.8.0版本也不过才1754KB。

    够轻量

    核心程序仅仅依赖 slf4j 这一个包,没有之一!

    好扩展

    Ehcache 提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多。

    监听器

    缓存管理器监听器 (CacheManagerListener)和 缓存监听器(CacheEvenListener),做一些统计或数据一致性广播挺好用的。

    Redis

    支持持久化

    Redis 的本地持久化支持两种方式:RDB和AOF。RDB 在redis.conf配置文件里配置持久化触发器,AOF指的是Redis每增加一条记录都会保存到持久化文件中(保存的是这条记录的生成命令)。

    丰富的数据类型

    Redis 支持 String 、List、Set、Sorted Set、hash 多种数据类型。

    高性能

    内存操作的级别是毫秒级的比硬盘操作秒级操作自然高效不少,减少了磁头寻道、数据读取、页面交换这些高开销的操作!这也是NOSQL冒出来的原因吧,应该是高性能是基于RDBMS的衍生产品,虽然RDBMS也具有缓存结构,但是始终在应用层面达不到我们的需求。

    Replication

    Redis 提供主从复制方案,跟 MySql 一样增量复制而且复制的实现都很相似,这个复制跟AOF有点类似复制的是新增记录命令,主库新增记录将新增脚本发送给从库,从库根据脚本生成记录,这个过程非常快,就看网络了,一般主从都是在同一个局域网,所以可以说 Redis 的主从近似及时同步,同时它还支持一主多从,动态添加从库,从库数量没有限制。

    更新快

    Redis 到目前为止已经发了大版本 5 个,小版本没算过。Redis 作者是个非常积极的人,无论是邮件提问还是论坛发帖,他都能及时耐心的为你解答,维护度很高。有人维护的话,让我们用的也省心和放心。目前作者对Redis 的主导开发方向是 Redis 的集群方向。

    总结

    Ehcache 直接在 jvm 虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。如果是单个应用或者对缓存访问要求很高的应用,用 Ehcache。

    Redis 是通过 socket 访问到缓存服务,效率比 Ecache 低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用 Redis。

    2.Ehcache

    使用 Ehcache 缓存的步骤是:

    • 添加 Ehcache 组件和依赖
    • 添加 Ehcache 配置文件
    • 业务层使用 @Cacheable 处理缓存
    • 启动类使用 @EnableCaching 开启缓存

    创建项目

    image.png

    添加组件依赖

    我们需要在 pom.xml 中添加 cache 组件 和 ehcache 依赖

    (ehcache 依赖可以不用添加,本案例添加是为了获取 ehcache 配置文件模板

    pom.xml

     <dependencies>
            <!-- cache 组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
            <!-- ehcache 依赖 -->
            <dependency>
                <groupId>net.sf.ehcache</groupId>
                <artifactId>ehcache</artifactId>
            </dependency>
            <!-- thymeleaf 组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <!-- web 组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- mybatis 组件 -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.0</version>
            </dependency>
            <!-- mysql 数据库驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!-- druid 数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.19</version>
            </dependency>
    
            <!-- test 组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <!-- build标签 常用于添加插件及编译配置 -->
        <build>
            <!-- 读取配置文件 -->
            <resources>
                <resource>
                    <directory>src/main/resources</directory>
                </resource>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                        <include>**/*.properties</include>
                        <include>**/*.tld</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    
    

    ehcache 配置文件

    我们可以参考 ehcache 项目源码中的配置文件模板。

    image.png

    diskStore

    diskStore 元素是可选的,非必须的。如果不使用磁盘存储,只需要将 diskStore 注释掉即可;如果使用,需要在 ehcache.xml 文件中的 ehcahce 元素下的定义一个 diskStore 元素并指定其 path 属性。

    path 属性可以配置的目录有:

    user.home(用户的家目录)

    user.dir(用户当前的工作目录)

    java.io.tmpdir(默认的临时目录)

    ehcache.disk.store.dir(ehcache的配置目录)

    绝对路径(如:D:\ehcache)

    DiskStore 中驱除元素跟 MemoryStore 中驱除元素的规则是不一样的。当往 DiskStore 中添加元素且此时DiskStore 中的容量已经超出限制时将采用 LFU(最不常用)驱除规则将对应的元素进行删除,而且该驱除规则是不可配置的(通过 cache 中的 diskExpiryThreadIntervalSeconds 属性完成)。

    缓存策略

    name :缓存名称

    maxElementsInMemory :内存缓存中最多可以存放的元素数量,若放入 Cache 中的元素超过这个数值,则有以下两种情况:

    1. 若 overflowToDisk=true,则会将 Cache 中多出的元素放入磁盘文件中
    1. 若 overflowToDisk=false,则根据 memoryStoreEvictionPolicy 策略替换 Cache 中原有的元素

    eternal :缓存中对象是否永久有效,是否永驻内存,true时将忽略timeToIdleSeconds和 timeToLiveSeconds

    timeToIdleSeconds :设置对象的空闲时间

    timeToLiveSeconds :设置对象的存活时间

    overflowToDisk :内容溢出是否写入磁盘

    memoryStoreEvictionPolicy :内存存储与释放策略,即达到 maxElementsInMemory 限制时,Ehcache 会根据指定策略清理内存。共有三种策略:

    LRU(最近最少使用)

    LFU(最常用的)

    FIFO(先进先出)

    ehcache 中缓存的 3 种清空策略:

    1. FIFO: first in first out,这个是大家最熟的,先进先出,不多讲了
    2. LFU: Less Frequently Used,直白一点就是讲一直以来最少被使用的。缓存的元素有一个 hit 属性, hit 值最小的将会被清出缓存。
    3. LRU: Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

    对于 EhCache 的配置文件也可以通过 application.properties 文件中使用 spring.cache.ehcache.config 属性来指定,比如: spring.cache.ehcache.config=classpath:config/ehcache.xml

    resources/ehcache.xml

    <?xml version="1.0" encoding="utf-8" ?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    
        <diskStore path="java.io.tmpdir"/>
    
        <!-- 默认缓存策略 -->
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxElementsOnDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </defaultCache>
    
        <!-- 自定义缓存策略 -->
        <cache name="userCache"
               maxElementsInMemory="10000"
               eternal="false"
               timeToIdleSeconds="120"
               timeToLiveSeconds="120"
               maxElementsOnDisk="10000000"
               diskExpiryThreadIntervalSeconds="120"
               memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </cache>
    
    </ehcache>
    

    properties 配置文件

    # 配置数据库驱动
    spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    spring.datasource.username=root
    spring.datasource.password=shsxt
    # 配置数据库连接池
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    # 配置MyBatis数据返回类型别名(默认别名是类名)
    mybatis.type-aliases-package=com.springboot.pojo
    # 配置MyBatis Mapper映射文件
    mybatis.mapper-locations=classpath:com/springboot/mapper/*.xml
    # 配置SQL打印
    # 指定某个包下的SQL打印
    logging.level.com.springboot.mapper=debug
    # 所有包下的SQL打印
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    

    添加 application.properties 全局配置文件,配置SQL打印。

    SQL****文件

    CREATE TABLE `user` (
    `id` INT (11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR (255) DEFAULT NULL,
    `age` INT (11) DEFAULT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
    

    实体类

    User.java

    package com.springboot.pojo;
    
    import java.io.Serializable;
    
    public class User implements Serializable {
    
        private Integer id;
        private String name;
        private Integer age;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    

    Mapper 接口

    UserMapper.java

    public interface UserMapper {
    
        // 添加用户
        int insertUser(User user);
    
        // 查询用户
        List<User> selectUserList();
    
        // 根据主键查询用户
        User selectUserById(Integer id);
    
        // 修改用户
        int updateUser(User user);
    
        // 删除用户
        int deleteUser(Integer id);
    
    }
    
    

    映射配置文件

    UserMapper.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace 必须是接口的完全限定名 -->
    <mapper namespace="com.springboot.mapper.UserMapper">
    
        <!-- id 必须和接口中的方法名一致 -->
        <insert id="insertUser" parameterType="user">
            insert into user (name, age) values (#{name}, #{age})
        </insert>
    
        <!-- 查询所有用户 -->
        <select id="selectUserList" resultType="user">
            select id, name, age from user;
        </select>
    
        <!-- 根据主键查询用户 -->
        <select id="selectUserById" resultType="user">
            select id, name, age from user where id = #{id};
        </select>
    
        <!-- 修改用户 -->
        <update id="updateUser" parameterType="user">
            update user set name = #{name}, age = #{age} where id = #{id};
        </update>
    
        <!-- 删除用户 -->
        <delete id="deleteUser">
            delete from user where id = #{id}
        </delete>
    
    </mapper>
    

    业务层

    业务层使用 @Cacheable 处理缓存

    UserServiceI.java

    public interface UserServiceI {
    
        int insertUser(User user);
    
        // 查询用户
        List<User> selectUserList();
    
        // 根据主键查询用户
        User selectUserById(Integer id);
    
        // 修改用户
        int updateUser(User user);
    
        // 删除用户
        int deleteUser(Integer id);
    
    }
    

    UserServiceImpl.java

    @Service
    @Transactional
    @CacheConfig(cacheNames = "userCache")
    public class UserServiceImpl implements UserServiceI {
    
        @Autowired
        private UserMapper userMapper;
    
        @Autowired
        private CacheManager cacheManager;
    
        // 清除缓存中以 userCache 缓存策略缓存的对象
        @CacheEvict(allEntries = true)
        @Override
        public int insertUser(User user) {
            return userMapper.insertUser(user);
        }
    
        @Cacheable
        @Override
        public List<User> selectUserList() {
            return userMapper.selectUserList();
        }
    
        @Cacheable(key = "#id")
        @Override
        public User selectUserById(Integer id) {
            return userMapper.selectUserById(id);
        }
    
        // 清除缓存中以 userCache 缓存策略缓存的对象
        @CacheEvict(allEntries = true)
        @Override
        public int updateUser(User user) {
            return userMapper.updateUser(user);
        }
    
        // 清除缓存中以 userCache 缓存策略缓存的对象
        //@CacheEvict(allEntries = true)
        @Override
        public int deleteUser(Integer id) {
            Cache cache = cacheManager.getCache("userCache");
            cache.clear();
            return userMapper.deleteUser(id);
        }
    
    }
    
    
    

    控制层

    UserController.java

    @Controller
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private UserServiceI userService;
    
        /**
         * 页面跳转
         */
        @RequestMapping("/{page}")
        public String page(@PathVariable String page) {
            return page;
        }
    
        /**
         * 添加用户
         */
        @PostMapping("/insertUser")
        public String insertUser(User user) {
            int result = userService.insertUser(user);
            return "success";
        }
    
        /**
         * 查询用户列表
         */
        @GetMapping("/selectUserList")
        public String selectUserList(Model model) {
            model.addAttribute("userList", userService.selectUserList());
            return "user-list";
        }
    
        /**
         * 修改用户跳转页面
         */
        @GetMapping("/edit/{id}")
        public String edit(@PathVariable Integer id, Model model) {
            model.addAttribute("user", userService.selectUserById(id));
            return "updateUser";
        }
    
        /**
         * 修改用户保存
         */
        @PostMapping("/updateUser")
        public String updateUser(User user) {
            userService.updateUser(user);
            return "success";
        }
    
        /**
         * 删除用户
         */
        @GetMapping("/deleteUser/{id}")
        public String deleteUser(@PathVariable Integer id) {
            userService.deleteUser(id);
            return "success";
        }
    
    }
    
    
    

    视图层

    templates/register.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>注册用户</title>
    </head>
    <body>
        <form th:action="@{/user/insertUser}" method="post">
            姓名:<input name="name"/><br/>
            年龄:<input name="age"/><br/>
            <input type="submit" value="注册"/>
        </form>
    </body>
    </html>
    
    

    templates/success.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>提示</title>
    </head>
    <body>
        成功
    </body>
    </html>
    
    

    templates/updateUser.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>修改用户</title>
    </head>
    <body>
        <form th:action="@{/user/updateUser}" method="post">
            <input type="hidden" name="id" th:value="${user.id}"/>
            姓名:<input name="name" th:value="${user.name}"/><br/>
            年龄:<input name="age" th:value="${user.age}"/><br/>
            <input type="submit" value="修改"/>
        </form>
    </body>
    </html>
    
    

    templates/user-list.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>用户列表</title>
    </head>
    <body>
        <table border="1" width="300px" cellspacing="0">
            <tr>
                <th>ID</th>
                <th>NAME</th>
                <th>AGE</th>
                <th>操作</th>
            </tr>
            <tr th:each="user : ${userList}">
                <td th:text="${user.id}"></td>
                <td th:text="${user.name}"></td>
                <td th:text="${user.age}"></td>
                <td>
                    <a th:href="@{/user/edit/{id}(id=${user.id})}">修改</a>
                    <a th:href="@{/user/deleteUser/{id}(id=${user.id})}">删除</a>
                </td>
            </tr>
        </table>
    </body>
    </html>
    
    

    启动类

    启动类使用 @EnableCaching 开启缓存

    App.java

    @SpringBootApplication
    // 扫描 mapper 接口和映射配置文件
    @MapperScan("com.springboot.mapper")
    // 开启缓存
    @EnableCaching
    public class App {
    
        public static void main(String[] args) {
            SpringApplication.run(App.class, args);
        }
    
    }
    
    
    

    测试

    数据库

    image.png

    访问:http://localhost:8080/user/selectUserList

    image.png

    删除数据库


    image.png

    再次查询,控制台无任何打印消息,依然可以查询到数据,缓存配置成功。

    Cache 常用注解详解

    @Cacheable

    value 、 cacheNames :两个等同的参数( cacheNames 为Spring 4新增,作为 value 的别名),用于 指定缓存存储的集合名。由于Spring 4中新增了 @CacheConfig ,因此在Spring 3中原本必须有的 value 属性,也成为非必需项了。

    key :缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为 key 值,若自己配置需使用 SpEL 表达式,比如: @Cacheable(key = "#p0") :使用函数第一个参数作为缓存的 key 值,在查询时如果key 存在,那么直接从缓存中将数据返回。更多关于SpEL表达式的详细内容可参考官方文档

    UserServiceImpl.java

    // 对当前查询的结果做缓存处理,不配置 cacheNames 属性时使用默认缓存策略
    @Cacheable(cacheNames = "userCache")
    @Override
    public List<User> selectUserList() {
    return userMapper.selectUserList();
    } /
    *
    设置缓存的 key
    #p0:使用第一个参数作为 key
    #id:使用参数 id 作为 key
    #user.id:使用参数 user 的 id 作为 key (user 是对象 id 是 user 的属性)
    */
    @Cacheable(cacheNames = "userCache", key = "#id")
    @Override
    public User selectUserById(Integer id) {
    return userMapper.selectUserById(id);
    }
    
    

    @CacheConfig

    @CacheConfig is a class-level annotation that allows to share the cache names.

    主要用于配置某些类中会用到的一些共用的缓存配置,比如本案例中我们多次使用到

    @Cacheable(cacheNames= "userCache") 我们便可以在该类上添加 @CacheConfig(cacheNames = "userCache") 注解,方法上只需要使用 @Cacheable 即可。如果在方法上使用别的缓存名称,那么依然以方法的缓存名称为准。

    @CacheEvict

    配置于函数上,通常用在写操作方法上,用来从缓存中移除相应数据。除了同 @Cacheable 一样的参数之外,它还有下面两个参数:

    allEntries :非必需,默认为 false。当为 true 时,会移除所有数据;

    beforeInvocation :非必需,默认为 false,会在调用方法之后移除数据。当为 true 时,会在调用方法之前移除数据。

    // 清除缓存中以 userCache 缓存策略缓存的对象
    @CacheEvict(cacheNames = "userCache", allEntries = true)
    @Override
    public int insertUser(User user) {
    return userMapper.insertUser(user);
    } 
    // 如果该类配置了@CacheConfig(cacheNames = "userCache"),可以简写
    @CacheEvict(allEntries = true)
    @Override
    public int updateUser(User user) {
    return userMapper.updateUser(user);
    } 
    // 如果该类配置了@CacheConfig(cacheNames = "userCache"),可以简写
    @CacheEvict(allEntries = true)
    @Override
    public int deleteUserById(Integer id) {
    return userMapper.deleteUserById(id);
    }
    
    

    当我们执行写操作时,缓存的内容会被清除,查询时会重新查询关系数据库再次放入缓存。

    CacheManager

    我们还可以通过 CacheManager 对象来管理缓存。

    UserServiceImpl.java

    @Autowired
    private CacheManager cacheManager;
    @Override
    public int deleteUserById(Integer id) {
    // 清除缓存中以 userCache 缓存策略缓存的对象
    cacheManager.getCache("userCache").clear();
    return userMapper.deleteUserById(id);
    }j
    
    
    3.Redis

    Redis 是一个开源的使用 ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。no-sql 型的数据库。

    2008年,意大利一家创业公司Merzia的创始人Salvatore Sanfilippo为了避免MySQL的低性能,亲自定做一个数据库,并于2009年开发完成,这个就是Redis。

    从2010年3月15日起,Redis的开发工作由VMware主持。

    从2013年5月开始,Redis的开发由Pivotal赞助。

    安装****Redis

    下载地址:http://redis.io/

    将 redis.tar.gz 上传至服务器,解压 tar zxvf redis.tar.gz ;

    安装依赖: yum -y install gcc-c++ autoconf automake ;

    创建安装目录 mkdir -p /usr/local/redis ;

    切换至解压目录 cd redis ;预编译 make ;

    安装 make PREFIX=/usr/local/redis install ;

    安装成功如下图:


    image.png

    redis-cli:客户端

    redis-server:服务端

    修改配置文件并启动

    复制解压目录下 redis.conf 至安装目录 /usr/local/redis/bin

    修改 redis.conf,将 daemonize 修改为 yes(后台启动);

    注释掉 bind127.0.0.1 使所有的 ip 访问 redis,若是想指定多个 ip 访问,并不是全部的 ip 访问,可以 bind设置;

    添加访问认证 requirepass root ;

    处理防火墙;

    启动时,指定配置文件路径即可 bin/redis-server bin/redis.conf ;

    安装可视化客户端访问:

    image.png

    Spring Data Redis

    Spring Data Redis 是 Spring 大家族的一部分,提供了在 Srping 应用中通过简单的配置访问 Redis 服务,对Reids 底层开发包(Jedis,JRedis,RJC)进行了高度封装,RedisTemplate 提供了 Redis 各种操作、异常处理及序列化,支持发布订阅,对 Redis Sentinel 和 Redis Cluster 支持,并对 Spring 3.1 cache进行了实现。

    案例中我们分别讲解 Lettuce 和 Jedis 两种实现, Lettuce 和 Jedis 的都是连接 Redis Server 的客户端程序。

    因为 Spring Boot2.0 之后,底层默认不再采用 Jedis 作为实现了。而是采用效率更高,线程更安全的 Lettuce客户端。

    Jedis 是一个优秀的基于 Java 语言的 Redis 客户端,但是,其不足也很明显:Jedis 在实现上是直接连接 Redis-Server,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程场景下使用 Jedis,需要使用连接池,每个线程都使用自己的 Jedis 实例,当连接数量增多时,会消耗较多的物理资源。

    Lettuce 则完全克服了其线程不安全的缺点:Lettuce 是基于 Netty 的连接实例(StatefulRedisConnection)

    Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功能,如Sentinel,集群,流水线,自动重新连接和 Redis 数据模型。

    3.1Lettuce

    我们先来讲 Spring Boot2.x 版本以后的默认方式 Lettuce。

    创建项目

    创建Spring Boot项目,选择 Web 组件 Redis 组件。需要手动添加 commons-pool2 对象池依赖。 ! image.png

    pom.xml

    <!-- spring data redis 组件 -->
    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!-- commons-pool2 对象池依赖 -->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <!-- web 组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
                <!-- test 组件 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    
    
    

    properties 配置文件

    application.properties

    # 最大连接数,默认8
    spring.redis.lettuce.pool.max-active=1024
    # 最大连接阻塞等待时间,单位毫秒,默认-1
    spring.redis.lettuce.pool.max-wait=10000
    # 最大空闲连接,默认8
    spring.redis.lettuce.pool.max-idle=200
    # 最小空闲连接,默认0
    spring.redis.lettuce.pool.min-idle=5
    # 连接超时时间
    spring.redis.timeout=10000
    # Redis服务器地址
    spring.redis.host=192.168.190.10
    # Redis服务器端口
    spring.redis.port=6379
    # Redis服务器密码
    spring.redis.password=root
    # 选择哪个库,默认0库
    spring.redis.database=0
    
    
    

    自定义模板

    默认情况下的模板 RedisTemplate<Object, Object> ,默认序列化使用的是 JdkSerializationRedisSerializer ,存储二进制字节码。这时需要自定义模板,当自定义模板后又想存储String 字符串时,可以使用 StringRedisTemplate 的方式,他们俩并不冲突。

    序列化问题:

    要把 domain object 做为 key-value 对保存在 redis 中,就必须要解决对象的序列化问题。Spring Data Redis给我们提供了一些现成的方案:

    JdkSerializationRedisSerializer 使用JDK提供的序列化功能。 优点是反序列化时不需要提供类型信息(class),但缺点是序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗 Redis 服务器的大量内存。

    Jackson2JsonRedisSerializer 使用 Jackson 库将对象序列化为JSON字符串。优点是速度快,序列化后的字符串短小精悍。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。

    GenericJackson2JsonRedisSerializer 通用型序列化,这种序列化方式不用自己手动指定对象的Class。

    RedisConfigForLettuce.java

    @Configuration
    public class RedisConfigForLettuce {
    // 重写 RedisTemplate 序列化
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
    LettuceConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    // 为 String 类型 key 设置序列化器
    template.setKeySerializer(new StringRedisSerializer());
    // 为 String 类型 value 设置序列化器
    template.setValueSerializer(new
    GenericJackson2JsonRedisSerializer());
    // 为 Hash 类型 key 设置序列化器
    template.setHashKeySerializer(new StringRedisSerializer());
    // 为 Hash 类型 value 设置序列化器
    template.setHashValueSerializer(new
    GenericJackson2JsonRedisSerializer());
    template.setConnectionFactory(redisConnectionFactory);
    return template;
    }
    }
    
    

    debug 模式运行 RedisConnectionFactory 信息如下:

    image.png

    实体类

    user.java

    package com.springboot.pojo;
    
    import java.io.Serializable;
    
    public class User implements Serializable {
    
        private Integer id;
        private String username;
        private Integer age;
    
        public User() {
        }
    
        public User(Integer id, String username, Integer age) {
            this.id = id;
            this.username = username;
            this.age = age;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", age=" + age +
                    '}';
        }
    
    }
    
    

    测试类

    SpringbootRedisApplicationTests.java

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = {App.class})
    public class SpringbootRedisApplicationTests {
        @Autowired
        private RedisTemplate<String,Object> redisTemplate;
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void testSet() {
            User user = new User();
            user.setId(1);
            user.setUsername("张三");
            user.setAge(18);
            redisTemplate.opsForValue().set("user:" + user.getId(), user);
    
            User u = (User) redisTemplate.opsForValue().get("user:1");
            System.out.println(u);
        }
    
        @Test
        public void testGet() {
            User user = (User) redisTemplate.opsForValue().get("user:1");
            System.out.println(user);
        }
    
    }
    
    

    结果

    image.png
    3.2Jedis

    创建项目

    创建Spring Boot项目,选择 Web 组件 Redis 组件。需要手动添加 commons-pool2 对象池依赖,排除 Lettuce依赖,添加 Jedis 依赖。

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.7.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.springboot</groupId>
        <artifactId>springboot-redis</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>springboot-redis</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <!--
                    1.x 的版本默认采用的连接池技术是 Jedis,
                    2.0 以上版本默认连接池是 Lettuce,
                    如果采用 Jedis,需要排除 Lettuce 的依赖。
                 -->
             <exclusions>
                 <exclusion>
                          <groupId>io.lettuce</groupId>
                             <artifactId>lettuce-core</artifactId>
                   </exclusion>
              </exclusions>
            </dependency>
            <!-- jedis 依赖 -->
          <dependency>
         <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
         </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    
    
    

    properties 配置文件

    application.properties

    # 最大连接数,默认8
    spring.redis.lettuce.pool.max-active=1024
    # 最大连接阻塞等待时间,单位毫秒,默认-1
    spring.redis.lettuce.pool.max-wait=10000
    # 最大空闲连接,默认8
    spring.redis.lettuce.pool.max-idle=200
    # 最小空闲连接,默认0
    spring.redis.lettuce.pool.min-idle=5
    # 连接超时时间
    spring.redis.timeout=10000
    # Redis服务器地址
    spring.redis.host=192.168.190.10
    # Redis服务器端口
    spring.redis.port=6379
    # Redis服务器密码
    spring.redis.password=root
    # 选择哪个库,默认0库
    spring.redis.database=0
    
    
    

    自定义模板

    RedisConfigForJedis.java

    //@Configuration
    public class RedisConfigForJedis {
    
        // 重写 RedisTemplate 序列化
        //@Bean
    
        public RedisTemplate<String, Object> redisTemplate(
                JedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            // 为 String 类型 key 设置序列化器
            template.setKeySerializer(new StringRedisSerializer());
            // 为 String 类型 value 设置序列化器
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            // 为 Hash 类型 key 设置序列化器
            template.setHashKeySerializer(new StringRedisSerializer());
            // 为 Hash 类型 value 设置序列化器
            template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    
    }
    
    
    

    debug 模式运行 RedisConnectionFactory 信息如下:

    image.png

    实体类

    User.java

    测试类

    SpringbootRedisApplicationTests.java

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = {App.class})
    public class SpringbootRedisApplicationTests {
        @Autowired
        private RedisTemplate<String,Object> redisTemplate;
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void testSet() {
            User user = new User();
            user.setId(1);
            user.setUsername("张三");
            user.setAge(18);
            redisTemplate.opsForValue().set("user:" + user.getId(), user);
    
            User u = (User) redisTemplate.opsForValue().get("user:1");
            System.out.println(u);
        }
    
        @Test
        public void testGet() {
            User user = (User) redisTemplate.opsForValue().get("user:1");
            System.out.println(user);
        }
    
    }
    
    
    

    结果

    image.png
    3.3Sentinel

    Redis 哨兵是为 Redis 提供一个高可靠解决方案,Redis 主节点挂掉会自动帮我们提升从为主,对一定程序上的错误可以不需要人工干预自行解决。哨兵功能还有监视、事件通知、配置功能等。以下是哨兵的功能列表:

    监控:不间断的检查主从服务是否如预期一样正常工作;

    事件通知:对被监视的 Redis 实例的异常,能通知系统管理员,或者以API接口通知其他应用程序;

    智能援救:当被监视的主服务异常时,哨兵会智能的把某个从服务提升为主服务,同时其他从服务与新的主服务之间的关系将得到重新的配置。应用程序将通过redis服务端重新得到新的主服务的地址并重新建立连接;

    配置服务:客户端可连接哨兵的接口,获得主从服务的相关信息,如果发生改变,哨兵新通知客户端。

    Spring Boot 也提供了对于哨兵连接的配置,关于哨兵主从服务,我们先来看看本案例使用的环境。


    image.png

    properties 配置文件

    application.properties

    # 最大连接数,默认8
    spring.redis.lettuce.pool.max-active=1024
    # 最大连接阻塞等待时间,单位毫秒,默认-1
    spring.redis.lettuce.pool.max-wait=10000
    # 最大空闲连接,默认8
    spring.redis.lettuce.pool.max-idle=200
    # 最小空闲连接,默认0
    spring.redis.lettuce.pool.min-idle=5
    # 连接超时时间
    spring.redis.timeout=10000
    # Redis服务器地址
    spring.redis.host=192.168.18.10
    # Redis服务器端口,哨兵模式下不一定非要配置为主节点,只要是主从环境中任何一个节点即可
    spring.redis.port=6379
    # Redis服务器密码
    spring.redis.password=root
    # 选择哪个库,默认0库
    spring.redis.database=0
    # 哨兵主从服务
    # 主节点名称
    spring.redis.sentinel.master=mymaster
    # 主从服务器地址
    spring.redis.sentinel.nodes=192.168.18.10:26379,192.168.18.10:26380,192.168.18.10:26381
    
    

    @Bean

    除了使用配置文件或者使用 @Bean 配置 Sentinel

    Lettuce 配置 Sentinel

    @Configuration
    public class RedisConfigForLettuce {
    
        /**
         * Lettuce
         */
        //@Bean
        public RedisConnectionFactory lettuceConnectionFactory() {
            RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                    .master("mymaster")// 主节点名称
                    .sentinel("192.168.10.10", 26379)
                    .sentinel("192.168.10.10", 26380)
                    .sentinel("192.168.10.10", 26381);
            sentinelConfig.setPassword("root");// 设置密码
            return new LettuceConnectionFactory(sentinelConfig);
        }
    
        // 重写 RedisTemplate 序列化
        @Bean
        public RedisTemplate<String, Object> redisTemplate(
                LettuceConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            // 为 String 类型 key 设置序列化器
            template.setKeySerializer(new StringRedisSerializer());
            // 为 String 类型 value 设置序列化器
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            // 为 Hash 类型 key 设置序列化器
            template.setHashKeySerializer(new StringRedisSerializer());
            // 为 Hash 类型 value 设置序列化器
            template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    }
    
    
    

    Jedis 配置 Sentinel

    package com.springboot.config;
    
    //@Configuration
    public class RedisConfigForJedis {
    
        /**
         * Jedis
         */
        //@Bean
        /*
        public RedisConnectionFactory jedisConnectionFactory() {
            RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
                    .master("mymaster")// 主节点名称
                    .sentinel("192.168.10.10", 26379)
                    .sentinel("192.168.10.10", 26380)
                    .sentinel("192.168.10.10", 26381);
            sentinelConfig.setPassword("root");
            return new JedisConnectionFactory(sentinelConfig);
        }
         */
    
        // 重写 RedisTemplate 序列化
        //@Bean
        /*
        public RedisTemplate<String, Object> redisTemplate(
                JedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            // 为 String 类型 key 设置序列化器
            template.setKeySerializer(new StringRedisSerializer());
            // 为 String 类型 value 设置序列化器
            template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
            // 为 Hash 类型 key 设置序列化器
            template.setHashKeySerializer(new StringRedisSerializer());
            // 为 Hash 类型 value 设置序列化器
            template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
        */
    
    }
    
    
    

    测试类

    测试同上

    不需要配置主节点,哨兵可以可以自动查找

    相关文章

      网友评论

        本文标题:Spring Boot缓存技术-----F04

        本文链接:https://www.haomeiwen.com/subject/olcdectx.html