目錄
Redis持久化概述
跳轉到目錄
Redis的高性能是由於其將所有數據都存儲在了內存中,爲了使Redis在重啓之後仍能保證數據不丟失,需要將數據從內存中同步到硬盤中,這一過程就是持久化。Redis支持兩種方式的持久化,一種是RDB方式,一種是AOF方式。可以單獨使用其中一種或將二者結合使用。
- RDB持久化(默認支持,無需配置)
該機制是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。 - AOF持久化
該機制將以日誌的形式記錄服務器所處理的每一個寫操作,在Redis服務器啓動之初會讀取該文件來重新構建數據庫,以保證啓動後數據庫中的數據是完整的。 - 無持久化
我們可以通過配置的方式禁用Redis服務器的持久化功能,這樣我們就可以將Redis視爲一個功能加強版的memcached了。 - redis可以同時使用RDB和AOF
RDB持久化機制
RDB持久化機制優點
- 一旦採用該方式,那麼你的整個Redis數據庫將只包含一個文件,這對於文件備份而言是非常完美的。比如,你可能打算每個小時歸檔一次最近24小時的數據,同時還要每天歸檔一次最近30天的數據。通過這樣的備份策略,一旦系統出現災難性故障,我們可以非常容易的進行恢復。
- 對於災難恢復而言,RDB是非常不錯的選擇。因爲我們可以非常輕鬆的將一個單獨的文件壓縮後再轉移到其它存儲介質上
- 性能最大化。對於Redis的服務進程而言,在開始持久化時,它唯一需要做的只是fork(分叉)出子進程,之後再由子進程完成這些持久化的工作,這樣就可以極大的避免服務進程執行IO操作了。
RDB持久化機制缺點
- 如果你想保證數據的高可用性,即最大限度的避免數據丟失,那麼RDB將不是一個很好的選擇。因爲系統一旦在定時持久化之前出現宕機現象,此前沒有來得及寫入磁盤的數據都將丟失。
- 由於RDB是通過fork子進程來協助完成數據持久化工作的,因此,如果當數據集較大時,可能會導致整個服務器停止服務幾百毫秒,甚至是1秒鐘
RDB持久化機制的配置
在redis.windows.conf配置文件中有如下配置:
################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
save 900 1
save 300 10
save 60 10000
其中,上面配置的是RDB方式數據持久化時機:
關鍵字 | 時間(秒) | key修改數量 | 解釋 |
---|---|---|---|
save | 900 | 1 | 每900秒(15分鐘)至少有1個key發生變化,則dump內存快照 |
save | 300 | 10 | 每300秒(5分鐘)至少有10個key發生變化,則dump內存快照 |
save | 60 | 10000 | 每60秒(1分鐘)至少有10000個key發生變化,則dump內存快照 |
AOF持久化機制
AOF持久化機制優點
- 該機制可以帶來更高的數據安全性,即數據持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事實上,每秒同步也是異步完成的,其效率也是非常高的,所差的是一旦系統出現宕機現象,那麼這一秒鐘之內修改的數據將會丟失。而每修改同步,我們可以將其視爲同步持久化,即每次發生的數據變化都會被立即記錄到磁盤中。可以預見,這種方式在效率上是最低的。至於無同步,無需多言,我想大家都能正確的理解它。
- 由於該機制對日誌文件的寫入操作採用的是append模式,因此在寫入過程中即使出現宕機現象,也不會破壞日誌文件中已經存在的內容。然而如果我們本次操作只是寫入了一半數據就出現了系統崩潰問題,不用擔心,在Redis下一次啓動之前,我們可以通過redis-check-aof工具來幫助我們解決數據一致性的問題。
- 如果日誌過大,Redis可以自動啓用rewrite機制。即Redis以append模式不斷的將修改數據寫入到老的磁盤文件中,同時Redis還會創建一個新的文件用於記錄此期間有哪些修改命令被執行。因此在進行rewrite切換時可以更好的保證數據安全性。
- AOF包含一個格式清晰、易於理解的日誌文件用於記錄所有的修改操作。事實上,我們也可以通過該文件完成數據的重建
AOF持久化機制缺點
- 對於相同數量的數據集而言,AOF文件通常要大於RDB文件
- 根據同步策略的不同,AOF在運行效率上往往會慢於RDB。總之,每秒同步策略的效率是比較高的,同步禁用策略的效率和RDB一樣高效。
AOF持久化機制配置
############################## APPEND ONLY MODE ###############################
# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.
appendonly no
將appendonly修改爲yes,開啓aof持久化機制,默認會在目錄下產生一個appendonly.aof文件
AOF持久化時機
# appendfsync always
appendfsync everysec
# appendfsync no
上述配置爲aof持久化的時機,解釋如下:
關鍵字 | 持久化時機 | 解釋 |
---|---|---|
appendfsync | always | 每執行一次更新命令,持久化一次 |
appendfsync | everysec | 每秒鐘持久化一次 |
appendfsync | no | 不持久化 |
Jedis的基本使用
跳轉到目錄
Redis不僅是使用命令來操作,現在基本上主流的語言都有客戶端支持,比如java、C、C#、C++、php、Node.js、Go等。 在官方網站裏列一些Java的客戶端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推薦使用Jedis和Redisson。 在企業中用的最多的就是Jedis,Jedis同樣也是託管在github上,地址:https://github.co
m/xetorthio/jedis。
使用Jedis操作redis需要導入jar包如下:
Jedis常用API
方法 | 解釋 |
---|---|
new Jedis(host, port) | 創建jedis對象,參數host是redis服務器地址,參數port是redis服務端口 |
set(key,value) | 設置字符串類型的數據 |
get(key) | 獲得字符串類型的數據 |
hset(key,field,value) | 設置哈希類型的數據 |
hget(key,field) | 獲得哈希類型的數據 |
lpush(key,values) | 設置列表類型的數據 |
lpop(key) | 列表左面彈棧 |
rpop(key) | 列表右面彈棧 |
del(key) | 刪除指定的key |
Jedis的基本操作
/**
* 第一個Demo版
*/
public class RedisUtilSnapshot {
public static void main(String[] args) {
//1. 導入jar包
//2. 建立redis的連接
Jedis connection = new Jedis("localhost", 6379);
//3. 使用該連接, 執行命令
connection.set("test", "桂朝陽");
System.out.println(connection.get("test"));
//4. 關閉連接
connection.close();
}
}
Jedis連接池的基本概念
跳轉到目錄
jedis連接資源的創建與銷燬是很消耗程序性能,所以jedis爲我們提供了jedis的池化技術,jedisPool在創建時初始化一些連接資源存儲到連接池中,使用jedis連接資源時不需要創建,而是從連接池中獲取一個資源進行redis的操作,使用完畢後,不需要銷燬該jedis連接資源,而是將該資源歸還給連接池,供其他請求使用。
JedisPool的基本使用
/**
* 候選版本: release candidate穩定候選版
* @author ZYGui
*/
public class RedisUtilRC {
private static JedisPool pool = null;
static {
//1. 配置連接池
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 初始化
poolConfig.setMinIdle(10);
// 最大空閒
poolConfig.setMaxIdle(5);
// 最多有多少個
poolConfig.setMaxWaitMillis(100);
//2. 獲取連接(使用連接池)
pool = new JedisPool(poolConfig, "localhost", 6379);
}
public static Jedis getConnecttion(){
// 從連接池中獲取
Jedis connection = pool.getResource();
return connection;
}
public static void closePool(){
pool.close();
}
public static void main(String[] args) {
/* //1. 配置連接池
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 初始化
poolConfig.setMinIdle(10);
// 最大空閒
poolConfig.setMaxIdle(5);
// 最多有多少個
poolConfig.setMaxWaitMillis(100);
//2. 獲取連接(使用連接池)
JedisPool pool = new JedisPool(poolConfig, "localhost", 6379);
// 從連接池中獲取
Jedis connection = pool.getResource();
//3. 操作方法
String str = connection.get("test");
System.out.println(str);
//4. 歸還給連接池
connection.close();*/
}
}
Jedis連接池工具類
/**
* 候選版本: release candidate穩定候選版
*
* @author ZYGui
*/
public class RedisUtil2 {
private static JedisPool pool = null;
static {
//1. 配置連接池
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
/*
除了 properties來讀取配置文件往外
在Java.util包 resourceBundle就是用來解析properties
*/
// 讀取配置文件
ResourceBundle rb = ResourceBundle.getBundle("redis");
int minIdle = Integer.parseInt(rb.getString("redis.minIdle"));
int maxIdle = Integer.parseInt(rb.getString("redis.maxIdle"));
int maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));
int maxWaitMillis = Integer.parseInt(rb.getString("redis.maxWaitMillis"));
String host = rb.getString("redis.host");
int port = Integer.parseInt(rb.getString("redis.port"));
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxWaitMillis(maxWaitMillis);
poolConfig.setMaxTotal(maxTotal);
pool = new JedisPool(poolConfig, host, port);
/*
Properties properties = new Properties();
try {
properties.load(RedisUtil.class.getClassLoader().getResourceAsStream("redis.properties"));
int minIdle = Integer.parseInt(properties.getProperty("redis.minIdle"));
int maxIdle = Integer.parseInt(properties.getProperty("redis.maxIdle"));
int maxTotal = Integer.parseInt(properties.getProperty("redis.maxTotal"));
int maxWaitMillis = Integer.parseInt(properties.getProperty("redis.maxWaitMillis"));
String host = properties.getProperty("redis.host");
int port = Integer.parseInt(properties.getProperty("redis.port"));
poolConfig.setMinIdle(minIdle);
poolConfig.setMaxIdle(maxIdle);
poolConfig.setMaxWaitMillis(maxWaitMillis);
poolConfig.setMaxTotal(maxTotal);
pool = new JedisPool(poolConfig, host, port);
} catch (Exception e){
}
*/
}
public static Jedis getConnecttion() {
// 從連接池中獲取
Jedis connection = pool.getResource();
return connection;
}
public static void closePool() {
pool.close();
}
public static void main(String[] args) {
Jedis connection = getConnecttion();
String str = connection.get("test");
System.out.println(str);
connection.close();
}
}
Demo
跳轉到目錄
案例需求:
1. 提供index.html頁面,頁面中有一個省份 下拉列表
2. 當 頁面加載完成後 發送ajax請求,加載所有省份
* 注意:使用redis緩存一些不經常發生變化的數據。
* 數據庫的數據一旦發生改變,則需要更新緩存。
* 數據庫的表執行 增刪改的相關操作,需要將redis緩存數據清除,再次存入
* 在service對應的增刪改方法中,將redis數據刪除。(因爲不刪除,數據仍然是redis緩存中的)
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/jquery-3.3.1.js"></script>
<script>
$(function () {
/*
* 注意: 當在Servlet中 設置 response.setContentType("application/json;charset=utf-8");
* 不 需要將 JSON字符串 轉爲 JSON對象
* var JsonObj = JSON.parse(data);
* */
//發送ajax請求,加載所有省份數據
$.get("http://localhost:8080/redis/provinceServlet",{},function (data) {
//[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"廣州"},{"id":4,"name":"陝西"}]
//1.獲取select
var province = $("#province");
//2.遍歷json數組
$(data).each(function () {
//3.創建<option>
var option = "<option name='"+this.id+"'>"+this.name+"</option>";
//4.調用select的append追加option
province.append(option);
});
});
/*
* 注意: 當在Servlet中 設置 response.setContentType("text/html;charset=utf-8");
* 下面兩種方式都需要將 JSON字符串 轉爲 JSON對象
* var JsonObj = JSON.parse(data);
* */
//發送ajax請求,加載所有省份數據
// $.get("http://localhost:8080/redis/provinceServlet",{},function (data) {
// //[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"廣州"},{"id":4,"name":"陝西"}]
//
// //1.獲取select
// var province = $("#province");
// var JsonObj = JSON.parse(data);
// //2.遍歷json數組
// $(JsonObj).each(function () {
// //3.創建<option>
// var option = "<option name='"+this.id+"'>"+this.name+"</option>";
//
// //4.調用select的append追加option
// province.append(option);
// });
//
// });
// $.ajax({
// url: "http://localhost:8080/redis/provinceServlet",
// type: "get",
// success:function (data) {
// var jsonObj = JSON.parse(data);
// for (pro of jsonObj) {
// alert(pro.id);
// //3.創建<option>
// var option = "<option name='"+pro.id+"'>"+pro.name+"</option>";
// //4.調用select的append追加option
// $("#province").append(option);
// }
// }
// })
});
</script>
</head>
<body>
<select id="province">
<option>--請選擇省份--</option>
</select>
</body>
</html>
- domain
package com.sunny.domain;
public class Province {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- Dao
public class ProvinceDaoImpl implements ProvinceDao {
//1. 聲明成員變量
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
@Override
public List<Province> findAll() {
// 定義sql
String sql = "SELECT * FROM province";
// 執行
return template.query(sql, new BeanPropertyRowMapper<>(Province.class));
}
}
- Service
public class ProvinceServiceImpl implements ProvinceService {
private ProvinceDao dao = new ProvinceDaoImpl();
@Override
public List<Province> findAll() {
return dao.findAll();
}
/**
* 使用redis緩存
* @return
*/
@Override
public String findAllJson() {
//1. 先從redis中查詢數據
//1.1 獲取redis客戶端連接
Jedis jedis = RedisUtil.getConnecttion();
String province_json = jedis.get("province");
//2. 判斷province_json是否爲null
if (province_json == null || province_json.length() == 0){
// redis中沒有數據
System.out.println("redis中沒有數據, 查詢數據庫...");
//2.1 從數據庫中查詢
List<Province> ps = dao.findAll();
//2.2 將list序列化爲json
ObjectMapper mapper = new ObjectMapper();
try {
province_json = mapper.writeValueAsString(ps);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
//2.3 將json數據存入redis
jedis.set("province", province_json);
//歸還連接
jedis.close();
} else {
System.out.println("redis中有數據, 查詢緩存...");
}
return province_json;
}
}
- Servlet
@WebServlet("/provinceServlet")
public class ProvinceServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json;charset=utf-8");
request.setCharacterEncoding("utf-8");
//1. 調用Service查詢
// ProvinceService service = new ProvinceServiceImpl();
// List<Province> list = service.findAll();
//2. 序列化List爲JSON
// ObjectMapper mapper = new ObjectMapper();
// String json = mapper.writeValueAsString(list);
// System.out.println(json); //[{"id":1,"name":"北京"},{"id":2,"name":"上海"},{"id":3,"name":"廣州"},{"id":4,
// "name":"陝西"}]
// 添加緩存的調用
ProvinceService service = new ProvinceServiceImpl();
String json = service.findAllJson();
System.out.println(json);
//3. 響應結果
response.getWriter().write(json);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}