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.pngdiskStore
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 中的元素超过这个数值,则有以下两种情况:
- 若 overflowToDisk=true,则会将 Cache 中多出的元素放入磁盘文件中
- 若 overflowToDisk=false,则根据 memoryStoreEvictionPolicy 策略替换 Cache 中原有的元素
eternal :缓存中对象是否永久有效,是否永驻内存,true时将忽略timeToIdleSeconds和 timeToLiveSeconds
timeToIdleSeconds
:设置对象的空闲时间
timeToLiveSeconds
:设置对象的存活时间
overflowToDisk :
内容溢出是否写入磁盘
memoryStoreEvictionPolicy :内存存储与释放策略,即达到 maxElementsInMemory
限制时,Ehcache 会根据指定策略清理内存。共有三种策略:
LRU(最近最少使用)
LFU(最常用的)
FIFO(先进先出)
ehcache 中缓存的 3 种清空策略:
- FIFO: first in first out,这个是大家最熟的,先进先出,不多讲了
- LFU: Less Frequently Used,直白一点就是讲一直以来最少被使用的。缓存的元素有一个 hit 属性, hit 值最小的将会被清出缓存。
- 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.pngSpring 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.pngpom.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.png3.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.png3.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;
}
*/
}
测试类
测试同上
不需要配置主节点,哨兵可以可以自动查找
网友评论