目录
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);
}
}