Spring Boot****緩存技術(shù)*
1.Ehcache 和 Redis 的對比
Ehcache
在java項目廣泛的使用浅蚪。它是一個開源的准验、設(shè)計于提高在數(shù)據(jù)從RDBMS中取出來的高花費芍躏、高延遲采取的一種緩存方案历等。正因為Ehcache具有健壯性(基于java開發(fā))偿凭、被認證(具有apache 2.0 license)婶熬、充滿特色,所以被用于大型復(fù)雜分布式web application的各個節(jié)點中量窘。夠簡單就是Ehcache的一大特色雇寇,自然用起來 just so easy!
夠快
Ehcache 的發(fā)行有一段時長了,經(jīng)過幾年的努力和不計其數(shù)的性能測試,Ehcache 終被設(shè)計于 large, high concurrency systems锨侯。
夠簡單
開發(fā)者提供的接口非常簡單明了嫩海,從 Ehcache 的搭建到運用運行僅僅需要的是你寶貴的幾分鐘。其實很多編程者都不知道自己在用Ehcache识腿,Ehcache被廣泛的運用于其他的開源項目出革,比如:Hibernate造壮。
夠袖珍
關(guān)于這點的特性渡讼,官方給了一個很可愛的名字 small foot print,一般 Ehcache 的發(fā)布版本不會到2M耳璧, V2.2.3版本才668KB成箫,目前最新版的V3.8.0版本也不過才1754KB。
夠輕量
核心程序僅僅依賴 slf4j 這一個包旨枯,沒有之一蹬昌!
好擴展
Ehcache 提供了對大數(shù)據(jù)的內(nèi)存和硬盤的存儲,最近版本允許多實例攀隔、保存對象高靈活性皂贩、提供LRU、LFU昆汹、FIFO淘汰算法明刷,基礎(chǔ)屬性支持熱配置、支持的插件多满粗。
監(jiān)聽器
緩存管理器監(jiān)聽器 (CacheManagerListener)和 緩存監(jiān)聽器(CacheEvenListener)
辈末,做一些統(tǒng)計或數(shù)據(jù)一致性廣播挺好用的。
Redis
支持持久化
Redis 的本地持久化支持兩種方式:RDB和AOF
映皆。RDB 在redis.conf配置文件里配置持久化觸發(fā)器挤聘,AOF指的是Redis每增加一條記錄都會保存到持久化文件中(保存的是這條記錄的生成命令)。
豐富的數(shù)據(jù)類型
Redis 支持 String 捅彻、List组去、Set、Sorted Set步淹、hash 多種數(shù)據(jù)類型从隆。
高性能
內(nèi)存操作的級別是毫秒級的比硬盤操作秒級操作自然高效不少,減少了磁頭尋道贤旷、數(shù)據(jù)讀取广料、頁面交換這些高開銷的操作!這也是NOSQL冒出來的原因吧幼驶,應(yīng)該是高性能是基于RDBMS的衍生產(chǎn)品艾杏,雖然RDBMS也具有緩存結(jié)構(gòu),但是始終在應(yīng)用層面達不到我們的需求盅藻。
Replication
Redis 提供主從復(fù)制方案购桑,跟 MySql 一樣增量復(fù)制而且復(fù)制的實現(xiàn)都很相似畅铭,這個復(fù)制跟AOF有點類似復(fù)制的是新增記錄命令,主庫新增記錄將新增腳本發(fā)送給從庫勃蜘,從庫根據(jù)腳本生成記錄硕噩,這個過程非常快缭贡,就看網(wǎng)絡(luò)了炉擅,一般主從都是在同一個局域網(wǎng),所以可以說 Redis 的主從近似及時同步阳惹,同時它還支持一主多從谍失,動態(tài)添加從庫,從庫數(shù)量沒有限制莹汤。
更新快
Redis 到目前為止已經(jīng)發(fā)了大版本 5 個快鱼,小版本沒算過。Redis 作者是個非常積極的人纲岭,無論是郵件提問還是論壇發(fā)帖抹竹,他都能及時耐心的為你解答,維護度很高止潮。有人維護的話窃判,讓我們用的也省心和放心。目前作者對Redis 的主導(dǎo)開發(fā)方向是 Redis 的集群方向沽翔。
總結(jié)
Ehcache 直接在 jvm 虛擬機中緩存兢孝,速度快,效率高仅偎;但是緩存共享麻煩跨蟹,集群分布式應(yīng)用不方便。如果是單個應(yīng)用或者對緩存訪問要求很高的應(yīng)用橘沥,用 Ehcache窗轩。
Redis 是通過 socket 訪問到緩存服務(wù),效率比 Ecache 低座咆,比數(shù)據(jù)庫要快很多痢艺,處理集群和分布式緩存方便,有成熟的方案
介陶。如果是大型系統(tǒng)堤舒,存在緩存共享、分布式部署哺呜、緩存內(nèi)容很大的舌缤,建議用 Redis。
2.Ehcache
使用 Ehcache 緩存的步驟是:
- 添加 Ehcache 組件和依賴
- 添加 Ehcache 配置文件
- 業(yè)務(wù)層使用 @Cacheable 處理緩存
- 啟動類使用 @EnableCaching 開啟緩存
創(chuàng)建項目
添加組件依賴
我們需要在 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 數(shù)據(jù)庫驅(qū)動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid 數(shù)據(jù)庫連接池 -->
<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標(biāo)簽 常用于添加插件及編譯配置 -->
<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 項目源碼中的配置文件模板国撵。
diskStore
diskStore 元素是可選的陵吸,非必須的。如果不使用磁盤存儲介牙,只需要將 diskStore 注釋掉即可壮虫;如果使用,需要在 ehcache.xml
文件中的 ehcahce 元素下的定義一個 diskStore 元素并指定其 path 屬性环础。
path 屬性可以配置的目錄有:
user.home(用戶的家目錄)
user.dir(用戶當(dāng)前的工作目錄)
java.io.tmpdir
(默認的臨時目錄)
ehcache.disk.store.dir(ehcache的配置目錄)
絕對路徑(如:D:\ehcache)
DiskStore 中驅(qū)除元素跟 MemoryStore 中驅(qū)除元素的規(guī)則是不一樣的囚似。當(dāng)往 DiskStore 中添加元素且此時DiskStore 中的容量已經(jīng)超出限制時將采用 LFU(最不常用)驅(qū)除規(guī)則將對應(yīng)的元素進行刪除,而且該驅(qū)除規(guī)則是不可配置的(通過 cache 中的 diskExpiryThreadIntervalSeconds
屬性完成)喳整。
緩存策略
name :緩存名稱
maxElementsInMemory :內(nèi)存緩存中最多可以存放的元素數(shù)量谆构,若放入 Cache 中的元素超過這個數(shù)值,則有以下兩種情況:
- 若 overflowToDisk=true框都,則會將 Cache 中多出的元素放入磁盤文件中
- 若 overflowToDisk=false,則根據(jù) memoryStoreEvictionPolicy 策略替換 Cache 中原有的元素
eternal :緩存中對象是否永久有效呵晨,是否永駐內(nèi)存魏保,true時將忽略timeToIdleSeconds和 timeToLiveSeconds
timeToIdleSeconds
:設(shè)置對象的空閑時間
timeToLiveSeconds
:設(shè)置對象的存活時間
overflowToDisk :
內(nèi)容溢出是否寫入磁盤
memoryStoreEvictionPolicy :內(nèi)存存儲與釋放策略,即達到 maxElementsInMemory
限制時摸屠,Ehcache 會根據(jù)指定策略清理內(nèi)存谓罗。共有三種策略:
LRU(最近最少使用)
LFU(最常用的)
FIFO(先進先出)
ehcache 中緩存的 3 種清空策略:
- FIFO: first in first out,這個是大家最熟的季二,先進先出檩咱,不多講了
- LFU: Less Frequently Used,直白一點就是講一直以來最少被使用的胯舷。緩存的元素有一個 hit 屬性刻蚯, hit 值最小的將會被清出緩存。
- LRU: Least Recently Used桑嘶,最近最少使用的膀估,緩存的元素有一個時間戳矾缓,當(dāng)緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那么現(xiàn)有緩存元素中時間戳離當(dāng)前時間最遠的元素將被清出緩存即纲。
對于 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 配置文件
# 配置數(shù)據(jù)庫驅(qū)動
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
# 配置數(shù)據(jù)庫連接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 配置MyBatis數(shù)據(jù)返回類型別名(默認別名是類名)
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();
// 根據(jù)主鍵查詢用戶
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>
<!-- 根據(jù)主鍵查詢用戶 -->
<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>
業(yè)務(wù)層
業(yè)務(wù)層使用 @Cacheable 處理緩存
UserServiceI.java
public interface UserServiceI {
int insertUser(User user);
// 查詢用戶
List<User> selectUserList();
// 根據(jù)主鍵查詢用戶
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;
/**
* 頁面跳轉(zhuǎn)
*/
@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";
}
/**
* 修改用戶跳轉(zhuǎn)頁面
*/
@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);
}
}
測試
數(shù)據(jù)庫
訪問:http://localhost:8080/user/selectUserList
刪除數(shù)據(jù)庫
再次查詢,控制臺無任何打印消息盈蛮,依然可以查詢到數(shù)據(jù)废菱,緩存配置成功。
Cache 常用注解詳解
@Cacheable
value 、 cacheNames :兩個等同的參數(shù)( cacheNames 為Spring 4新增昙啄,作為 value 的別名)穆役,用于 指定緩存存儲的集合名。由于Spring 4中新增了 @CacheConfig 梳凛,因此在Spring 3中原本必須有的 value 屬性耿币,也成為非必需項了。
key :緩存對象存儲在Map集合中的key值韧拒,非必需淹接,缺省按照函數(shù)的所有參數(shù)組合作為 key 值,若自己配置需使用 SpEL 表達式叛溢,比如: @Cacheable(key = "#p0")
:使用函數(shù)第一個參數(shù)作為緩存的 key 值塑悼,在查詢時如果key 存在,那么直接從緩存中將數(shù)據(jù)返回楷掉。更多關(guān)于SpEL表達式的詳細內(nèi)容可參考官方文檔厢蒜。
UserServiceImpl.java
// 對當(dāng)前查詢的結(jié)果做緩存處理,不配置 cacheNames 屬性時使用默認緩存策略
@Cacheable(cacheNames = "userCache")
@Override
public List<User> selectUserList() {
return userMapper.selectUserList();
} /
*
設(shè)置緩存的 key
#p0:使用第一個參數(shù)作為 key
#id:使用參數(shù) id 作為 key
#user.id:使用參數(shù) 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 即可。如果在方法上使用別的緩存名稱草雕,那么依然以方法的緩存名稱為準(zhǔn)巷屿。
@CacheEvict
配置于函數(shù)上,通常用在寫操作方法上墩虹,用來從緩存中移除相應(yīng)數(shù)據(jù)嘱巾。除了同 @Cacheable 一樣的參數(shù)之外,它還有下面兩個參數(shù):
allEntries
:非必需诫钓,默認為 false旬昭。當(dāng)為 true 時,會移除所有數(shù)據(jù)尖坤;
beforeInvocation
:非必需稳懒,默認為 false,會在調(diào)用方法之后移除數(shù)據(jù)慢味。當(dāng)為 true 時场梆,會在調(diào)用方法之前移除數(shù)據(jù)。
// 清除緩存中以 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);
}
當(dāng)我們執(zhí)行寫操作時,緩存的內(nèi)容會被清除驰唬,查詢時會重新查詢關(guān)系數(shù)據(jù)庫再次放入緩存顶岸。
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語言編寫、支持網(wǎng)絡(luò)辖佣、可基于內(nèi)存亦可持久化的日志型霹抛、Key-Value數(shù)據(jù)庫,并提供多種語言的API卷谈。no-sql 型的數(shù)據(jù)庫杯拐。
2008年,意大利一家創(chuàng)業(yè)公司Merzia的創(chuàng)始人Salvatore Sanfilippo為了避免MySQL的低性能世蔗,親自定做一個數(shù)據(jù)庫端逼,并于2009年開發(fā)完成,這個就是Redis污淋。
從2010年3月15日起顶滩,Redis的開發(fā)工作由VMware主持。
從2013年5月開始寸爆,Redis的開發(fā)由Pivotal贊助礁鲁。
安裝****Redis
下載地址:http://redis.io/;
將 redis.tar.gz 上傳至服務(wù)器而昨,解壓 tar zxvf redis.tar.gz 救氯;
安裝依賴: yum -y install gcc-c++ autoconf automake ;
創(chuàng)建安裝目錄 mkdir -p /usr/local/redis 歌憨;
切換至解壓目錄 cd redis ;預(yù)編譯 make 墩衙;
安裝 make PREFIX=/usr/local/redis install 务嫡;
安裝成功如下圖:
redis-cli:客戶端
redis-server:服務(wù)端
修改配置文件并啟動
復(fù)制解壓目錄下 redis.conf 至安裝目錄 /usr/local/redis/bin
;
修改 redis.conf漆改,將 daemonize 修改為 yes(后臺啟動)心铃;
注釋掉 bind127.0.0.1 使所有的 ip 訪問 redis,若是想指定多個 ip 訪問挫剑,并不是全部的 ip 訪問去扣,可以 bind設(shè)置;
添加訪問認證 requirepass root 樊破;
處理防火墻愉棱;
啟動時,指定配置文件路徑即可 bin/redis-server bin/redis.conf 哲戚;
安裝可視化客戶端訪問:
Spring Data Redis
Spring Data Redis 是 Spring 大家族的一部分奔滑,提供了在 Srping 應(yīng)用中通過簡單的配置訪問 Redis 服務(wù),對Reids 底層開發(fā)包(Jedis顺少,JRedis朋其,RJC)進行了高度封裝王浴,RedisTemplate 提供了 Redis 各種操作、異常處理及序列化梅猿,支持發(fā)布訂閱氓辣,對 Redis Sentinel 和 Redis Cluster 支持,并對 Spring 3.1 cache進行了實現(xiàn)袱蚓。
案例中我們分別講解 Lettuce 和 Jedis
兩種實現(xiàn)钞啸, Lettuce 和 Jedis 的都是連接 Redis Server 的客戶端程序。
因為 Spring Boot2.0 之后癞松,底層默認不再采用 Jedis 作為實現(xiàn)了爽撒。而是采用效率更高,線程更安全的 Lettuce客戶端响蓉。
Jedis 是一個優(yōu)秀的基于 Java 語言的 Redis 客戶端硕勿,但是,其不足也很明顯:Jedis 在實現(xiàn)上是直接連接 Redis-Server枫甲,在多個線程間共享一個 Jedis 實例時是線程不安全的
源武,如果想要在多線程場景下使用 Jedis,需要使用連接池想幻,每個線程都使用自己的 Jedis 實例粱栖,當(dāng)連接數(shù)量增多時,會消耗較多的物理資源脏毯。
Lettuce 則完全克服了其線程不安全的缺點:Lettuce 是基于 Netty 的連接實例(StatefulRedisConnection)
闹究,
Lettuce 是一個可伸縮的線程安全的 Redis 客戶端,支持同步食店、異步和響應(yīng)式模式渣淤。多個線程可以共享一個連接實例,而不必擔(dān)心多線程并發(fā)問題吉嫩。
它基于優(yōu)秀 Netty NIO 框架構(gòu)建价认,支持 Redis 的高級功能,如Sentinel自娩,集群用踩,流水線,自動重新連接和 Redis 數(shù)據(jù)模型忙迁。
3.1Lettuce
我們先來講 Spring Boot2.x 版本以后的默認方式 Lettuce脐彩。
創(chuàng)建項目
創(chuàng)建Spring Boot項目,選擇 Web 組件 Redis 組件动漾。需要手動添加 commons-pool2 對象池依賴丁屎。 !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
# 最大連接數(shù),默認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服務(wù)器地址
spring.redis.host=192.168.190.10
# Redis服務(wù)器端口
spring.redis.port=6379
# Redis服務(wù)器密碼
spring.redis.password=root
# 選擇哪個庫共虑,默認0庫
spring.redis.database=0
自定義模板
默認情況下的模板 RedisTemplate<Object, Object> 愧怜,默認序列化使用的是 JdkSerializationRedisSerializer
,存儲二進制字節(jié)碼妈拌。這時需要自定義模板拥坛,當(dāng)自定義模板后又想存儲String 字符串時,可以使用 StringRedisTemplate 的方式尘分,他們倆并不沖突猜惋。
序列化問題:
要把 domain object 做為 key-value 對保存在 redis 中,就必須要解決對象的序列化問題培愁。Spring Data Redis給我們提供了一些現(xiàn)成的方案:
JdkSerializationRedisSerializer
使用JDK提供的序列化功能著摔。 優(yōu)點是反序列化時不需要提供類型信息(class),但缺點是序列化后的結(jié)果非常龐大定续,是JSON格式的5倍左右谍咆,這樣就會消耗 Redis 服務(wù)器的大量內(nèi)存。
Jackson2JsonRedisSerializer
使用 Jackson 庫將對象序列化為JSON字符串私股。優(yōu)點是速度快摹察,序列化后的字符串短小精悍。但缺點也非常致命倡鲸,那就是此類的構(gòu)造函數(shù)中有一個類型參數(shù)供嚎,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼峭状,發(fā)現(xiàn)其只在反序列化過程中用到了類型信息查坪。
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 設(shè)置序列化器
template.setKeySerializer(new StringRedisSerializer());
// 為 String 類型 value 設(shè)置序列化器
template.setValueSerializer(new
GenericJackson2JsonRedisSerializer());
// 為 Hash 類型 key 設(shè)置序列化器
template.setHashKeySerializer(new StringRedisSerializer());
// 為 Hash 類型 value 設(shè)置序列化器
template.setHashValueSerializer(new
GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
debug 模式運行 RedisConnectionFactory 信息如下:
實體類
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);
}
}
結(jié)果
3.2Jedis
創(chuàng)建項目
創(chuàng)建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 的版本默認采用的連接池技術(shù)是 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
# 最大連接數(shù)幽钢,默認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服務(wù)器地址
spring.redis.host=192.168.190.10
# Redis服務(wù)器端口
spring.redis.port=6379
# Redis服務(wù)器密碼
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 設(shè)置序列化器
template.setKeySerializer(new StringRedisSerializer());
// 為 String 類型 value 設(shè)置序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 為 Hash 類型 key 設(shè)置序列化器
template.setHashKeySerializer(new StringRedisSerializer());
// 為 Hash 類型 value 設(shè)置序列化器
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
debug 模式運行 RedisConnectionFactory 信息如下:
實體類
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);
}
}
結(jié)果
3.3Sentinel
Redis 哨兵是為 Redis 提供一個高可靠解決方案帽驯,Redis 主節(jié)點掛掉會自動幫我們提升從為主龟再,對一定程序上的錯誤可以不需要人工干預(yù)自行解決。哨兵功能還有監(jiān)視尼变、事件通知利凑、配置功能等。以下是哨兵的功能列表:
監(jiān)控:不間斷的檢查主從服務(wù)是否如預(yù)期一樣正常工作嫌术;
事件通知:對被監(jiān)視的 Redis 實例的異常哀澈,能通知系統(tǒng)管理員,或者以API接口通知其他應(yīng)用程序度气;
智能援救:當(dāng)被監(jiān)視的主服務(wù)異常時割按,哨兵會智能的把某個從服務(wù)提升為主服務(wù),同時其他從服務(wù)與新的主服務(wù)之間的關(guān)系將得到重新的配置蚯嫌。應(yīng)用程序?qū)⑼ㄟ^redis服務(wù)端重新得到新的主服務(wù)的地址并重新建立連接哲虾;
配置服務(wù):客戶端可連接哨兵的接口,獲得主從服務(wù)的相關(guān)信息择示,如果發(fā)生改變束凑,哨兵新通知客戶端。
Spring Boot 也提供了對于哨兵連接的配置栅盲,關(guān)于哨兵主從服務(wù)汪诉,我們先來看看本案例使用的環(huán)境。
properties 配置文件
application.properties
# 最大連接數(shù)谈秫,默認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服務(wù)器地址
spring.redis.host=192.168.18.10
# Redis服務(wù)器端口,哨兵模式下不一定非要配置為主節(jié)點硕淑,只要是主從環(huán)境中任何一個節(jié)點即可
spring.redis.port=6379
# Redis服務(wù)器密碼
spring.redis.password=root
# 選擇哪個庫课竣,默認0庫
spring.redis.database=0
# 哨兵主從服務(wù)
# 主節(jié)點名稱
spring.redis.sentinel.master=mymaster
# 主從服務(wù)器地址
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")// 主節(jié)點名稱
.sentinel("192.168.10.10", 26379)
.sentinel("192.168.10.10", 26380)
.sentinel("192.168.10.10", 26381);
sentinelConfig.setPassword("root");// 設(shè)置密碼
return new LettuceConnectionFactory(sentinelConfig);
}
// 重寫 RedisTemplate 序列化
@Bean
public RedisTemplate<String, Object> redisTemplate(
LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 為 String 類型 key 設(shè)置序列化器
template.setKeySerializer(new StringRedisSerializer());
// 為 String 類型 value 設(shè)置序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 為 Hash 類型 key 設(shè)置序列化器
template.setHashKeySerializer(new StringRedisSerializer());
// 為 Hash 類型 value 設(shè)置序列化器
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")// 主節(jié)點名稱
.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 設(shè)置序列化器
template.setKeySerializer(new StringRedisSerializer());
// 為 String 類型 value 設(shè)置序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 為 Hash 類型 key 設(shè)置序列化器
template.setHashKeySerializer(new StringRedisSerializer());
// 為 Hash 類型 value 設(shè)置序列化器
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(redisConnectionFactory);
return template;
}
*/
}
測試類
測試同上
不需要配置主節(jié)點,哨兵可以可以自動查找