mirror of https://github.com/dunwu/db-tutorial.git
update docs
parent
e56ae6a37b
commit
f87c7ebedf
|
@ -22,11 +22,8 @@
|
|||
- [Mysql 原理](docs/sql/mysql/mysql-theory.md)
|
||||
- :four: [Redis](docs/nosql/redis/README.md)
|
||||
- [Redis 快速入门](docs/nosql/redis/redis.md)
|
||||
- [Redis 数据类型](docs/nosql/redis/redis-data-type.md)
|
||||
- [Redis 持久化](docs/nosql/redis/redis-persistence.md)
|
||||
- [Redis 复制](docs/nosql/redis/redis-replication.md)
|
||||
- [Redis 哨兵](docs/nosql/redis/redis-sentinel.md)
|
||||
- [Redis 集群](docs/nosql/redis/redis-cluster.md)
|
||||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
- [Redis 运维](docs/nosql/redis/redis-ops.md)
|
||||
|
|
Binary file not shown.
|
@ -22,7 +22,7 @@
|
|||
- [Mysql 原理](sql/mysql/mysql-theory.md)
|
||||
- :four: Redis
|
||||
- [Redis 入门指南](nosql/redis/redis.md)
|
||||
- [Redis 数据类型](nosql/redis/redis-data-type.md)
|
||||
- [Redis 持久化](nosql/redis/redis-persistence.md)
|
||||
- [Redis 复制](nosql/redis/redis-replication.md)
|
||||
- [Redis 哨兵](nosql/redis/redis-sentinel.md)
|
||||
- [Redis 集群](nosql/redis/redis-cluster.md)
|
||||
|
@ -30,4 +30,4 @@
|
|||
|
||||
## 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -51,4 +51,4 @@ Cassandra 的主要特点就是它不是一个数据库,而是由一堆数据
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -12,4 +12,4 @@
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -1,34 +1,8 @@
|
|||
# Redis
|
||||
# Redis 教程
|
||||
|
||||
- [Redis 快速入门](redis.md)
|
||||
- [Redis 持久化](redis-persistence.md)
|
||||
- [Redis 复制](redis-replication.md)
|
||||
|
||||
## 命令行
|
||||
|
||||
[Redis 官方命令行字典](https://redis.io/commands)
|
||||
|
||||
## 客户端
|
||||
|
||||
它提供了多种语言的客户端,如:Python,Ruby,PHP,Java,使用方便。
|
||||
|
||||
更多内容参考:[Redis 官方列出的 Redis 客户端列表](https://redis.io/clients)。
|
||||
|
||||
## 资源
|
||||
|
||||
[redis 官网](https://redis.io/)
|
||||
[redis github](https://github.com/antirez/redis)
|
||||
|
||||
### Sentinel
|
||||
|
||||
- [官方文档](https://redis.io/topics/sentinel) 最全
|
||||
- [官方文档翻译](http://ifeve.com/redis-sentinel/) 翻译,排版一般,新
|
||||
- [官方文档翻译](http://redisdoc.com/topic/sentinel.html) 翻译有段时间了,但主要部分都包含,排版好
|
||||
- [redis sentinel实战](https://blog.csdn.net/yanggd1987/article/details/78364667) 简要实战,能快速看出来是怎么回事
|
||||
|
||||
### redis client
|
||||
|
||||
- [spring-data-redis 官方文档 ](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/)
|
||||
- [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)
|
||||
- [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content)
|
||||
- [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002)
|
||||
- [redis分布锁Redisson性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505)
|
||||
|
||||
- [Redis 哨兵](redis-sentinel.md)
|
||||
- [Redis 集群](redis-cluster.md)
|
||||
- [Redis 运维](redis-ops.md)
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
---
|
||||
title: Redis 事件
|
||||
date: 2018-06-11
|
||||
categories:
|
||||
- database
|
||||
tags:
|
||||
- database
|
||||
- nosql
|
||||
---
|
||||
|
||||
# Redis 事件
|
||||
|
||||
Redis 服务器是一个事件驱动程序。
|
||||
|
||||
## 文件事件
|
||||
|
||||
服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。
|
||||
|
||||
Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用响应的事件处理器。
|
||||
|
||||
## 时间事件
|
||||
|
||||
服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。
|
||||
|
||||
时间事件又分为:
|
||||
|
||||
- 定时事件:是让一段程序在指定的时间之内执行一次;
|
||||
- 周期性事件:是让一段程序每隔指定时间就执行一次。
|
||||
|
||||
Redis 将所有时间事件都放在一个无序链表中,通过遍历整个链表查找出已到达的时间事件,并调用响应的事件处理器。
|
||||
|
||||
## 事件的调度与执行
|
||||
|
||||
服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。
|
||||
|
||||
事件调度与执行由 aeProcessEvents 函数负责,伪代码如下:
|
||||
|
||||
```py
|
||||
def aeProcessEvents():
|
||||
|
||||
# 获取到达时间离当前时间最接近的时间事件
|
||||
time_event = aeSearchNearestTimer()
|
||||
|
||||
# 计算最接近的时间事件距离到达还有多少毫秒
|
||||
remaind_ms = time_event.when - unix_ts_now()
|
||||
|
||||
# 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0
|
||||
if remaind_ms < 0:
|
||||
remaind_ms = 0
|
||||
|
||||
# 根据 remaind_ms 的值,创建 timeval
|
||||
timeval = create_timeval_with_ms(remaind_ms)
|
||||
|
||||
# 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定
|
||||
aeApiPoll(timeval)
|
||||
|
||||
# 处理所有已产生的文件事件
|
||||
procesFileEvents()
|
||||
|
||||
# 处理所有已到达的时间事件
|
||||
processTimeEvents()
|
||||
```
|
||||
|
||||
将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下:
|
||||
|
||||
```py
|
||||
def main():
|
||||
|
||||
# 初始化服务器
|
||||
init_server()
|
||||
|
||||
# 一直处理事件,直到服务器关闭为止
|
||||
while server_is_not_shutdown():
|
||||
aeProcessEvents()
|
||||
|
||||
# 服务器关闭,执行清理操作
|
||||
clean_server()
|
||||
```
|
||||
|
||||
从事件处理的角度来看,服务器运行流程如下:
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/Redis事件的调度与执行.png!zp" />
|
||||
</div>
|
|
@ -1,150 +0,0 @@
|
|||
---
|
||||
title: Redis 事务
|
||||
date: 2018-06-11
|
||||
categories:
|
||||
- database
|
||||
tags:
|
||||
- database
|
||||
- nosql
|
||||
- key-value
|
||||
- transaction
|
||||
---
|
||||
|
||||
# Redis 事务
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [事务简介](#事务简介)
|
||||
- [EXEC](#exec)
|
||||
- [MULTI](#multi)
|
||||
- [DISCARD](#discard)
|
||||
- [WATCH](#watch)
|
||||
- [取消 WATCH 的场景](#取消-watch-的场景)
|
||||
- [使用 WATCH 创建原子操作](#使用-watch-创建原子操作)
|
||||
- [Redis 不支持回滚](#redis-不支持回滚)
|
||||
- [Redis 脚本和事务](#redis-脚本和事务)
|
||||
- [资料](#资料)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
## 事务简介
|
||||
|
||||
事务可以一次执行多个命令,并且有以下两个重要的保证:
|
||||
|
||||
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
|
||||
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
|
||||
|
||||
## EXEC
|
||||
|
||||
**EXEC 命令负责触发并执行事务中的所有命令。**
|
||||
|
||||
如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
|
||||
另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。
|
||||
|
||||
## MULTI
|
||||
|
||||
**MULTI 命令用于开启一个事务,它总是返回 OK。**
|
||||
|
||||
MULTI 执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。
|
||||
|
||||
以下是一个事务例子, 它原子地增加了 foo 和 bar 两个键的值:
|
||||
|
||||
```py
|
||||
> MULTI
|
||||
OK
|
||||
> INCR foo
|
||||
QUEUED
|
||||
> INCR bar
|
||||
QUEUED
|
||||
> EXEC
|
||||
1) (integer) 1
|
||||
2) (integer) 1
|
||||
```
|
||||
|
||||
## DISCARD
|
||||
|
||||
**当执行 DISCARD 命令时,事务会被放弃,事务队列会被清空,并且客户端会从事务状态中退出。**
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
> SET foo 1
|
||||
OK
|
||||
> MULTI
|
||||
OK
|
||||
> INCR foo
|
||||
QUEUED
|
||||
> DISCARD
|
||||
OK
|
||||
> GET foo
|
||||
"1"
|
||||
```
|
||||
|
||||
## WATCH
|
||||
|
||||
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
|
||||
|
||||
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回 null 来表示事务已经失败。
|
||||
|
||||
```
|
||||
WATCH mykey
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
MULTI
|
||||
SET mykey $val
|
||||
EXEC
|
||||
```
|
||||
|
||||
使用上面的代码,如果在 WATCH 执行之后, EXEC 执行之前,有其他客户端修改了 mykey 的值,那么当前客户端的事务就会失败。程序需要做的,就是不断重试这个操作,直到没有发生碰撞为止。
|
||||
|
||||
这种形式的锁被称作乐观锁,它是一种非常强大的锁机制。并且因为大多数情况下,不同的客户端会访问不同的键,碰撞的情况一般都很少,所以通常并不需要进行重试。
|
||||
|
||||
**WATCH 使得 EXEC 命令需要有条件地执行:事务只能在所有被监视键都没有被修改的前提下执行,如果这个前提不能满足的话,事务就不会被执行。**
|
||||
|
||||
WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。
|
||||
|
||||
用户还可以在单个 WATCH 命令中监视任意多个键,例如:
|
||||
|
||||
```py
|
||||
redis> WATCH key1 key2 key3
|
||||
OK
|
||||
```
|
||||
|
||||
### 取消 WATCH 的场景
|
||||
|
||||
当 EXEC 被调用时,不管事务是否成功执行,对所有键的监视都会被取消。
|
||||
|
||||
另外,当客户端断开连接时,该客户端对键的监视也会被取消。
|
||||
|
||||
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。对于一些需要改动多个键的事务,有时候程序需要同时对多个键进行加锁,然后检查这些键的当前值是否符合程序的要求。当值达不到要求时,就可以使用 UNWATCH 命令来取消目前对键的监视,中途放弃这个事务,并等待事务的下次尝试。
|
||||
|
||||
### 使用 WATCH 创建原子操作
|
||||
|
||||
WATCH 可以用于创建 Redis 没有内置的原子操作。
|
||||
|
||||
举个例子,以下代码实现了原创的 ZPOP 命令,它可以原子地弹出有序集合中分值(score)最小的元素:
|
||||
|
||||
```
|
||||
WATCH zset
|
||||
element = ZRANGE zset 0 0
|
||||
MULTI
|
||||
ZREM zset element
|
||||
EXEC
|
||||
```
|
||||
|
||||
## Redis 不支持回滚
|
||||
|
||||
Redis 不支持回滚的理由:
|
||||
|
||||
- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面。
|
||||
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
|
||||
|
||||
## Redis 脚本和事务
|
||||
|
||||
从定义上来说,Redis 中的脚本本身就是一种事务,所以任何在事务里可以完成的事,在脚本里面也能完成。并且一般来说,使用脚本要来得更简单,并且速度更快。
|
||||
|
||||
## 资料
|
||||
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [事务](http://redis.cn/topics/transactions.html)
|
||||
- [Redis 实战](https://item.jd.com/11791607.html)
|
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
title: Redis 发布订阅
|
||||
date: 2018-06-11
|
||||
categories:
|
||||
- database
|
||||
tags:
|
||||
- database
|
||||
- nosql
|
||||
- key-value
|
||||
---
|
||||
|
||||
# Redis 发布订阅
|
||||
|
||||
Redis 通过 PUBLISH 、SUBSCRIBE 等命令实现了订阅与发布模式,这个功能提供两种信息机制,分别是订阅/发布到频道和订阅/发布到模式。
|
||||
|
||||
| 命令 | 描述 |
|
||||
| ------------ | ------------------------------------------------------------------------ |
|
||||
| SUBSCRIBE | 订阅给定的一个或多个频道。 |
|
||||
| UNSUBSCRIBE | 退订给定的一个或多个频道,如果执行时灭有给定任何频道,那么退订所有频道。 |
|
||||
| PUBLISH | 向给定频道发送消息。 |
|
||||
| PSUBSCRIBE | 订阅与给定模式相匹配的所有频道。 |
|
||||
| PUNSUBSCRIBE | 退订给定的模式,如果执行时没有给定任何模式,那么退订所有模式。 |
|
||||
|
||||
## 频道的订阅与信息发送
|
||||
|
||||
Redis 的 SUBSCRIBE 命令可以让客户端订阅任意数量的频道,每当有新信息发送到被订阅的频道时,信息就会被发送给所有订阅指定频道的客户端。
|
||||
|
||||
### 订阅频道
|
||||
|
||||
### 发送信息到频道
|
||||
|
||||
## 模式的订阅与信息发送
|
||||
|
||||
## 资料
|
||||
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [Redis 实战](https://item.jd.com/11791607.html)
|
||||
- [Redis 设计与实现](https://item.jd.com/11486101.html)
|
|
@ -1,257 +0,0 @@
|
|||
---
|
||||
title: Redis 持久化
|
||||
date: 2018-06-11
|
||||
categories:
|
||||
- database
|
||||
tags:
|
||||
- database
|
||||
- nosql
|
||||
- key-value
|
||||
---
|
||||
|
||||
# Redis 持久化
|
||||
|
||||
> Redis 支持持久化,即把数据存储到硬盘中。
|
||||
>
|
||||
> Redis 提供了两种持久化方式:
|
||||
>
|
||||
> **RDB 快照(snapshot)** - 将存在于某一时刻的所有数据都写入到硬盘中。
|
||||
>
|
||||
> **只追加文件(append-only file,AOF)** - 它会在执行写命令时,将被执行的写命令复制到硬盘中。
|
||||
>
|
||||
> 这两种持久化方式既可以同时使用,也可以单独使用。
|
||||
>
|
||||
> 将内存中的数据存储到硬盘的一个主要原因是为了在之后重用数据,或者是为了防止系统故障而将数据备份到一个远程位置。另外,存储在 Redis 里面的数据有可能是经过长时间计算得出的,或者有程序正在使用 Redis 存储的数据进行计算,所以用户会希望自己可以将这些数据存储起来以便之后使用,这样就不必重新计算了。
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [快照](#快照)
|
||||
- [快照的原理](#快照的原理)
|
||||
- [快照的配置](#快照的配置)
|
||||
- [快照的优点](#快照的优点)
|
||||
- [快照的缺点](#快照的缺点)
|
||||
- [AOF](#aof)
|
||||
- [AOF 的原理](#aof-的原理)
|
||||
- [AOF 的配置](#aof-的配置)
|
||||
- [重写/压缩 AOF](#重写压缩-aof)
|
||||
- [AOF 的优点](#aof-的优点)
|
||||
- [AOF 的缺点](#aof-的缺点)
|
||||
- [选择持久化方式](#选择持久化方式)
|
||||
- [怎样从快照方式切换为 AOF 方式](#怎样从快照方式切换为-aof-方式)
|
||||
- [AOF 和快照之间的相互作用](#aof-和快照之间的相互作用)
|
||||
- [备份](#备份)
|
||||
- [容灾备份](#容灾备份)
|
||||
- [Redis 复制的启动过程](#redis-复制的启动过程)
|
||||
- [资料](#资料)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
Redis 提供了两种持久方式:RDB 和 AOF。你可以同时开启两种持久化方式。在这种情况下, 当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
|
||||
|
||||
## 快照
|
||||
|
||||
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。在创建快照之后,用户可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本,还可以将快好留在原地以便重启服务器时使用。
|
||||
|
||||
根据配置,快照将被写入 `dbfilename` 选项指定的文件里面,并存储在 `dir` 选项指定的路径上面。
|
||||
|
||||
创建快照的方法:
|
||||
|
||||
- 客户端可以通过向 Redis 发送 BGSAVE 命令来创建一个快照。对于支持 BGSAVE 命令的平台来说,Redis 会创建一个子进程,然后子进程负责将快照写入到硬盘,而父进程则继续处理命令请求。
|
||||
- 客户端还可以通过向 Redis 发送 SAVE 命令来创建一个快照。接到 SAVE 命令的 Redis 服务器在快照创建完毕之前将不再响应任何其他命令。
|
||||
- 如果用户设置了 save 配置选项,比如 save 60 10000,当这个条件被满足时,Redis 就会自动触发 BGSAVE 命令。如果用户设置了多个 save 配置选项所设置的条件被满足时,Redis 就会触发一次 BGSAVE 命令。
|
||||
- 当 Redis 通过 SHUTDOWN 命令接受到关闭服务器的请求时,或者接收到标准 TERM 信号时,会执行一个 SAVE 命令,阻塞所有客户端,不再执行客户端发送的任何命令,并在 SAVE 命令执行完毕之后关闭服务器。
|
||||
- 当一个 Redis 服务器连接另一个 Redis 服务器,并向对方发送 SYNC 命令来开始一次复制操作的时候,如果主服务器目前没有在执行 BGSAVE 操作,或者主服务器并非刚刚执行完 BGSAVE 操作,那么主服务器就会执行 BGSAVE 命令。
|
||||
|
||||
快照持久化方式能够在指定的时间间隔能对整个数据进行快照存储。
|
||||
|
||||
使用快照持久化来保存数据是,需要记住:**如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。**
|
||||
|
||||
快照配置:
|
||||
|
||||
```
|
||||
save 60 1000
|
||||
stop-writes-on-bgsave-error no
|
||||
rdbcompression yes
|
||||
dbfilename dump.rdb
|
||||
```
|
||||
|
||||
### 快照的原理
|
||||
|
||||
在默认情况下,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。你也可以通过调用 SAVE 或者 BGSAVE,手动让 Redis 进行数据集保存操作。这种持久化方式被称为快照。
|
||||
|
||||
当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
|
||||
|
||||
- Redis 创建一个子进程。
|
||||
- 子进程将数据集写入到一个临时快照文件中。
|
||||
- 当子进程完成对新 快照文件的写入时,Redis 用新快照文件替换原来的快照文件,并删除旧的快照文件。
|
||||
|
||||
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。
|
||||
|
||||
### 快照的配置
|
||||
|
||||
比如说, 在 redis.conf 中添加如下配置,表示让 Redis 在满足“ 60 秒内有至少有 1000 个键被改动”这一条件时, 自动保存一次数据集:
|
||||
|
||||
```
|
||||
save 60 10000
|
||||
```
|
||||
|
||||
### 快照的优点
|
||||
|
||||
- RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份。比如你可以在每个小时报保存一下过去 24 小时内的数据,同时每天保存过去 30 天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
|
||||
- RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的 S3(可能加密),非常适用于灾难恢复。
|
||||
- 快照在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以快照持久化方式可以最大化 redis 的性能。
|
||||
- 与 AOF 相比,在恢复大的数据集的时候,DB 方式会更快一些。
|
||||
|
||||
### 快照的缺点
|
||||
|
||||
- 如果你希望在 redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么 快照不适合你。虽然你可以配置不同的 save 时间点(例如每隔 5 分钟并且对数据集有 100 个写的操作),是 Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔 5 分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。
|
||||
- 快照需要经常 fork 子进程来保存数据集到硬盘上。当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且 CPU 性能不是很好的情况下,这种情况会持续 1 秒。AOF 也需要 fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。
|
||||
|
||||
## AOF
|
||||
|
||||
AOF 持久化方式记录每次对服务器执行的写操作。当服务器重启的时候会重新执行这些命令来恢复原始的数据。
|
||||
|
||||
AOF 命令以 redis 协议追加保存每次写的操作到文件末尾。Redis 还能对 AOF 文件进行后台重写。使得 AOF 文件的体积不至于过大。
|
||||
|
||||
```
|
||||
appendonly no
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
```
|
||||
|
||||
### AOF 的原理
|
||||
|
||||
- Redis 创建一个子进程。
|
||||
- 子进程开始将新 AOF 文件的内容写入到临时文件。
|
||||
- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
|
||||
- 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
|
||||
- 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。
|
||||
|
||||
### AOF 的配置
|
||||
|
||||
AOF 持久化通过在 `redis.conf` 中的 `appendonly yes` 配置选项来开启。
|
||||
|
||||
可以通过 `appendfsync` 配置选项来设置同步频率:
|
||||
|
||||
- **always** - 每个 Redis 写命令都要同步写入硬盘。这样做会严重降低 Redis 的速度。
|
||||
- **everysec** - 每秒执行一次同步,显示地将多个写命令同步到硬盘。
|
||||
- **no** - 让操作系统来决定应该何时进行同步。
|
||||
|
||||
为了兼顾数据安全和写入性能,推荐使用 `appendfsync everysec` 选项。Redis 每秒同步一次 AOF 文件时的性能和不使用任何持久化特性时的性能相差无几。
|
||||
|
||||
### 重写/压缩 AOF
|
||||
|
||||
随着 Redis 不断运行,AOF 的体积也会不断增长,这将导致两个问题:
|
||||
|
||||
1. AOF 耗尽磁盘可用空间。
|
||||
2. Redis 重启后需要执行 AOF 文件记录的所有写命令来还原数据集,如果 AOF 过大,则还原操作执行的时间就会非常长。
|
||||
|
||||
这个问题的解决方法:
|
||||
|
||||
执行 `BGREWRITEAOF` 命令,这个命令会通过移除 AOF 中的冗余命令来重写 AOF 文件,使 AOF 文件的体积尽可能地小。
|
||||
|
||||
`BGREWRITEAOF` 命令与 `BGSAVE` 原理类似:通过创建一个子进程,然后由子进程负责对 AOF 文件进行重写。
|
||||
|
||||
可以通过设置 `auto-aof-rewrite-percentage` 和 `auto-aof-rewrite-min-size`,使得 Redis 在满足条件时,自动执行 `BGREWRITEAOF`。
|
||||
|
||||
假设配置如下:
|
||||
|
||||
```
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
```
|
||||
|
||||
表明,当 AOF 大于 64MB,且 AOF 体积比上一次重写后的体积大了至少 100% 时,执行 `BGREWRITEAOF`。
|
||||
|
||||
### AOF 的优点
|
||||
|
||||
- 使用 AOF 会让你的 Redis 更加耐久: 你可以使用不同的 fsync 策略:无 fsync;每秒 fsync;每次写的时候 fsync。使用默认的每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失 1 秒的数据。
|
||||
- AOF 文件是一个只进行追加的日志文件,所以不需要写入 seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用 redis-check-aof 工具修复这些问题。
|
||||
- Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
|
||||
- AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存。因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单。举个例子,如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis ,就可以将数据集恢复到 FLUSHALL 执行之前的状态。
|
||||
|
||||
### AOF 的缺点
|
||||
|
||||
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
|
||||
- 根据所使用的 fsync 策略,AOF 的速度可能会慢于快照。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和快照一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,快照可以提供更有保证的最大延迟时间(latency)。
|
||||
|
||||
## 选择持久化方式
|
||||
|
||||
如果你只希望你的数据在服务器运行的时候存在,你可以不使用任何持久化方式。
|
||||
|
||||
如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 快照持久化。
|
||||
|
||||
如果你不能承受数分钟以内的数据丢失,那么你可以同时使用快照持久化和 AOF 持久化。
|
||||
|
||||
有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且快照恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用快照还可以避免之前提到的 AOF 程序的 bug 。
|
||||
|
||||
### 怎样从快照方式切换为 AOF 方式
|
||||
|
||||
在 Redis 2.2 或以上版本,可以在不重启的情况下,从快照切换到 AOF :
|
||||
|
||||
- 为最新的 dump.rdb 文件创建一个备份。
|
||||
- 将备份放到一个安全的地方。
|
||||
- 执行以下两条命令:
|
||||
- redis-cli config set appendonly yes
|
||||
- redis-cli config set save “”
|
||||
- 确保写命令会被正确地追加到 AOF 文件的末尾。
|
||||
- 执行的第一条命令开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件创建完成为止, 之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。
|
||||
|
||||
执行的第二条命令用于关闭快照功能。 这一步是可选的, 如果你愿意的话, 也可以同时使用快照和 AOF 这两种持久化功能。
|
||||
|
||||
重要:别忘了在 redis.conf 中打开 AOF 功能! 否则的话, 服务器重启之后, 之前通过 CONFIG SET 设置的配置就会被遗忘, 程序会按原来的配置来启动服务器。
|
||||
|
||||
### AOF 和快照之间的相互作用
|
||||
|
||||
在版本号大于等于 2.4 的 Redis 中, BGSAVE 执行的过程中, 不可以执行 BGREWRITEAOF 。 反过来说, 在 BGREWRITEAOF 执行的过程中, 也不可以执行 BGSAVE。这可以防止两个 Redis 后台进程同时对磁盘进行大量的 I/O 操作。
|
||||
|
||||
如果 BGSAVE 正在执行, 并且用户显示地调用 BGREWRITEAOF 命令, 那么服务器将向用户回复一个 OK 状态, 并告知用户, BGREWRITEAOF 已经被预定执行: 一旦 BGSAVE 执行完毕, BGREWRITEAOF 就会正式开始。 当 Redis 启动时, 如果快照持久化和 AOF 持久化都被打开了, 那么程序会优先使用 AOF 文件来恢复数据集, 因为 AOF 文件所保存的数据通常是最完整的。
|
||||
|
||||
## 备份
|
||||
|
||||
**务必确保你的数据有完整的备份。**
|
||||
|
||||
磁盘故障、节点失效,诸如此类的问题都可能让你的数据消失不见,不进行备份是非常危险的。
|
||||
|
||||
备份 Redis 数据建议采用如下策略:
|
||||
|
||||
备份 Redis 数据建议采用快照方式。RDB 文件一旦创建,就不会进行任何修改,所以十分安全。
|
||||
|
||||
Redis 快照备份过程:
|
||||
|
||||
- 创建一个定期任务(cron job),每小时将一个 RDB 文件备份到一个文件夹,并且每天将一个 RDB 文件备份到另一个文件夹。
|
||||
- 确保快照的备份都带有相应的日期和时间信息,每次执行定期任务脚本时,使用 find 命令来删除过期的快照:比如说,你可以保留最近 48 小时内的每小时快照,还可以保留最近一两个月的每日快照。
|
||||
- 至少每天一次,将 RDB 备份到你的数据中心之外,或者至少是备份到你运行 Redis 服务器的物理机器之外。
|
||||
|
||||
### 容灾备份
|
||||
|
||||
Redis 的容灾备份基本上就是对数据进行备份,并将这些备份传送到多个不同的外部数据中心。
|
||||
|
||||
容灾备份可以在 Redis 运行并产生快照的主数据中心发生严重的问题时,仍然让数据处于安全状态。
|
||||
|
||||
以下是一些实用的容灾备份方法:
|
||||
|
||||
- Amazon S3 ,以及其他类似 S3 的服务,是一个构建灾难备份系统的好地方。 最简单的方法就是将你的每小时或者每日 RDB 备份加密并传送到 S3 。对数据的加密可以通过 gpg -c 命令来完成(对称加密模式)。记得把你的密码放到几个不同的、安全的地方去(比如你可以把密码复制给你组织里最重要的人物)。同时使用多个储存服务来保存数据文件,可以提升数据的安全性。
|
||||
- 传送快照可以使用 SCP 来完成(SSH 的组件)。 以下是简单并且安全的传送方法: 买一个离你的数据中心非常远的 VPS ,装上 SSH ,创建一个无口令的 SSH 客户端 key ,并将这个 key 添加到 VPS 的 authorized_keys 文件中,这样就可以向这个 VPS 传送快照备份文件了。为了达到最好的数据安全性,至少要从两个不同的提供商那里各购买一个 VPS 来进行数据容灾备份。
|
||||
- 需要注意的是,这类容灾系统如果没有小心地进行处理的话,是很容易失效的。最低限度下,你应该在文件传送完毕之后,检查所传送备份文件的体积和原始快照文件的体积是否相同。如果你使用的是 VPS ,那么还可以通过比对文件的 SHA1 校验和来确认文件是否传送完整。
|
||||
|
||||
另外, 你还需要一个独立的警报系统, 让它在负责传送备份文件的传送器(transfer)失灵时通知你。
|
||||
|
||||
### Redis 复制的启动过程
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/Redis复制启动过程.png!zp" width="400"/>
|
||||
</div>
|
||||
|
||||
当多个从服务器尝试连接同一个主服务器时:
|
||||
|
||||
- 上图步骤 3 尚未执行:所有从服务器都会接收到相同的快照文件和相同的缓冲区写命令。
|
||||
- 上图步骤 3 正在执行或已经执行完毕:当主服务器与较早进行连接的从服务器执行完复制所需的 5 个步骤之后,主服务器会与新连接的从服务器执行一次新的步骤 1 至步骤 5。
|
||||
|
||||
## 资料
|
||||
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [Redis 实战](https://item.jd.com/11791607.html)
|
||||
- [Redis Persistence](https://redis.io/topics/persistence)
|
|
@ -1,52 +0,0 @@
|
|||
# Redis 缓存淘汰策略
|
||||
|
||||
## 概述
|
||||
|
||||
- 最大缓存
|
||||
|
||||
Redis 允许通过 maxmemory 参数来设置内存最大值。
|
||||
|
||||
- 主键失效
|
||||
|
||||
作为一种定期清理无效数据的重要机制,在 Redis 提供的诸多命令中,EXPIRE、EXPIREAT、PEXPIRE、PEXPIREAT 以及 SETEX 和 PSETEX 均可以用来设置一条 Key-Value 对的失效时间,而一条 Key-Value 对一旦被关联了失效时间就会在到期后自动删除(或者说变得无法访问更为准确)。
|
||||
|
||||
- 淘汰机制
|
||||
|
||||
随着不断的向 redis 中保存数据,当内存剩余空间无法满足添加的数据时,redis 内就会施行数据淘汰策略,清除一部分内容然后保证新的数据可以保存到内存中。
|
||||
|
||||
内存淘汰机制是为了更好的使用内存,用一定得 miss 来换取内存的利用率,保证 redis 缓存中保存的都是热点数据。
|
||||
|
||||
|
||||
- 非精准的 LRU
|
||||
|
||||
实际上 Redis 实现的 LRU 并不是可靠的 LRU,也就是名义上我们使用 LRU 算法淘汰键,但是实际上被淘汰的键并不一定是真正的最久没用的。
|
||||
|
||||
## 淘汰策略
|
||||
|
||||
内存淘汰只是 Redis 提供的一个功能,为了更好地实现这个功能,必须为不同的应用场景提供不同的策略,内存淘汰策略讲的是为实现内存淘汰我们具体怎么做,要解决的问题包括淘汰键空间如何选择?在键空间中淘汰键如何选择?
|
||||
|
||||
Redis 提供了下面几种淘汰策略供用户选择,其中默认的策略为 noeviction 策略:
|
||||
|
||||
- **noeviction** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
|
||||
- **allkeys-lru** - 在主键空间中,优先移除最近未使用的 key。
|
||||
- **allkeys-random** - 在主键空间中,随机移除某个 key。
|
||||
- **volatile-lru** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。
|
||||
- **volatile-random** - 在设置了过期时间的键空间中,随机移除某个 key。
|
||||
- **volatile-ttl** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。
|
||||
|
||||
这里补充一下主键空间和设置了过期时间的键空间,举个例子,假设我们有一批键存储在 Redis 中,则有那么一个哈希表用于存储这批键及其值,如果这批键中有一部分设置了过期时间,那么这批键还会被存储到另外一个哈希表中,这个哈希表中的值对应的是键被设置的过期时间。设置了过期时间的键空间为主键空间的子集。
|
||||
|
||||
## 如何选择淘汰策略
|
||||
|
||||
- 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru。
|
||||
- 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random。
|
||||
- volatile-lru 策略和 volatile-random 策略适合我们将一个 Redis 实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个 Redis 实例来达到相同的效果。
|
||||
- 将 key 设置过期时间实际上会消耗更多的内存,因此我们建议使用 allkeys-lru 策略从而更有效率的使用内存。
|
||||
|
||||
## 内部实现
|
||||
|
||||
Redis 删除失效主键的方法主要有两种:
|
||||
|
||||
- 消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它。
|
||||
- 主动方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除。
|
||||
- 主动删除:当前已用内存超过 maxmemory 限定时,触发主动清理策略,该策略由启动参数的配置决定主键具体的失效时间全部都维护在 expires 这个字典表中。
|
|
@ -1,47 +0,0 @@
|
|||
# Redis 配置
|
||||
|
||||
## 通过配置文件配置
|
||||
|
||||
Redis 的配置文件名一般叫做:`redis.conf`。
|
||||
|
||||
redis.conf 文件中的配置指令参数格式为:
|
||||
|
||||
```
|
||||
keyword argument1 argument2 ... argumentN
|
||||
关键字 参数1 参数2 ... 参数N
|
||||
```
|
||||
|
||||
## 通过命令行配置
|
||||
|
||||
自 Redis2.6 起就可以直接通过命令行传递 Redis 配置参数。这种方法可以用于测试。
|
||||
|
||||
例:这个例子配置一个新运行并以 6380 为端口的 Redis 实例,使配置它为 127.0.0.1:6379 Redis 实例的 slave。
|
||||
|
||||
```
|
||||
./redis-server --port 6380 --slaveof 127.0.0.1 6379
|
||||
```
|
||||
|
||||
## 动态修改配置
|
||||
|
||||
Redis 允许在运行的过程中,在不重启服务器的情况下更改服务器配置,同时也支持 使用特殊的 [CONFIG SET](https://redis.io/commands/config-set) 和 [CONFIG GET](https://redis.io/commands/config-get) 命令用编程方式查询并设置配置。
|
||||
|
||||
并非所有的配置指令都支持这种使用方式,但是大部分是支持的。
|
||||
|
||||
## 配置 Redis 成为一个缓存
|
||||
|
||||
如果你想把 Redis 当做一个缓存来用,所有的 key 都有过期时间,那么你可以考虑 使用以下设置(假设最大内存使用量为 2M):
|
||||
|
||||
```
|
||||
maxmemory 2mb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
以上设置并不需要我们的应用使用 EXPIRE(或相似的命令)命令去设置每个 key 的过期时间,因为 只要内存使用量到达 2M,Redis 就会使用类 LRU 算法自动删除某些 key。
|
||||
|
||||
相比使用额外内存空间存储多个键的过期时间,使用缓存设置是一种更加有效利用内存的方式。而且相比每个键固定的 过期时间,使用 LRU 也是一种更加推荐的方式,因为这样能使应用的热数据(更频繁使用的键) 在内存中停留时间更久。
|
||||
|
||||
当我们把 Redis 当成缓存来使用的时候,如果应用程序同时也需要把 Redis 当成存储系统来使用,那么强烈建议 使用两个 Redis 实例。一个是缓存,使用上述方法进行配置,另一个是存储,根据应用的持久化需求进行配置,并且 只存储那些不需要被缓存的数据。
|
||||
|
||||
## 资料
|
||||
|
||||
- https://redis.io/topics/config
|
|
@ -260,6 +260,7 @@ Redis 集群选举新的主节点流程基于[共识算法:Raft](https://www.j
|
|||
|
||||
## 参考资料
|
||||
|
||||
- 《Redis 实战》
|
||||
- 《Redis 设计与实现》
|
||||
- [Redis 集群教程](http://ifeve.com/redis-cluster-tutorial/)
|
||||
- [深入剖析 Redis 系列(三) - Redis 集群模式搭建与原理详解](https://juejin.im/post/5b8fc5536fb9a05d2d01fb11)
|
||||
|
|
|
@ -1,237 +0,0 @@
|
|||
# Redis 数据类型
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [STRING](#string)
|
||||
- [LIST](#list)
|
||||
- [SET](#set)
|
||||
- [HASH](#hash)
|
||||
- [ZSET](#zset)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
| 数据类型 | 可以存储的值 | 操作 |
|
||||
| :------: | :--------------------: | :--------------------------------------------------------------------------------------------------------------: |
|
||||
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 |
|
||||
| LIST | 列表 | 从两端压入或者弹出元素</br> 读取单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 |
|
||||
| SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 |
|
||||
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在 |
|
||||
| ZSET | 有序集合 | 添加、获取、删除元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名 |
|
||||
|
||||
## STRING
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-datatype-string.png!zp" width="400"/>
|
||||
</div>
|
||||
|
||||
命令:
|
||||
|
||||
| 命令 | 行为 |
|
||||
| ---- | -------------------------------------------------- |
|
||||
| GET | 获取存储在给定键中的值 |
|
||||
| SET | 设置存储在给定键中的值 |
|
||||
| DEL | 删除存储在给定键中的值(这个命令可以用于所有类型) |
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
127.0.0.1:6379> set name jack
|
||||
OK
|
||||
127.0.0.1:6379> get name
|
||||
"jack"
|
||||
127.0.0.1:6379> del name
|
||||
(integer) 1
|
||||
127.0.0.1:6379> get name
|
||||
(nil)
|
||||
```
|
||||
|
||||
## LIST
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-datatype-list.png!zp" width="400"/>
|
||||
</div>
|
||||
|
||||
命令:
|
||||
|
||||
| 命令 | 行为 |
|
||||
| ------ | ---------------------------------------- |
|
||||
| RPUSH | 将给定值推入列表的右端 |
|
||||
| LRANGE | 获取列表在给定范围上的所有值 |
|
||||
| LINDEX | 获取列表在给定位置上的单个元素 |
|
||||
| LPOP | 从列表的左端弹出一个值,并返回被弹出的值 |
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
127.0.0.1:6379> rpush list item1
|
||||
(integer) 1
|
||||
127.0.0.1:6379> rpush list item2
|
||||
(integer) 2
|
||||
127.0.0.1:6379> rpush list item3
|
||||
(integer) 3
|
||||
127.0.0.1:6379> lrange list 0 -1
|
||||
1) "item1"
|
||||
2) "item2"
|
||||
3) "item3"
|
||||
127.0.0.1:6379> lindex list 1
|
||||
"item2"
|
||||
127.0.0.1:6379> lpop list
|
||||
"item1"
|
||||
127.0.0.1:6379> lrange list 0 -1
|
||||
1) "item2"
|
||||
2) "item3"
|
||||
```
|
||||
|
||||
## SET
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-datatype-set.png!zp" width="400"/>
|
||||
</div>
|
||||
|
||||
命令:
|
||||
|
||||
| 命令 | 行为 |
|
||||
| --------- | -------------------------------------------- |
|
||||
| SADD | 将给定元素添加到集合 |
|
||||
| SMEMBERS | 返回集合包含的所有元素 |
|
||||
| SISMEMBER | 检查给定元素是否存在于集合中 |
|
||||
| SREM | 如果给定的元素存在于集合中,那么移除这个元素 |
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
127.0.0.1:6379> sadd set item1
|
||||
(integer) 1
|
||||
127.0.0.1:6379> sadd set item2
|
||||
(integer) 1
|
||||
127.0.0.1:6379> sadd set item3
|
||||
(integer) 1
|
||||
127.0.0.1:6379> sadd set item3
|
||||
(integer) 0
|
||||
|
||||
127.0.0.1:6379> smembers set
|
||||
1) "item3"
|
||||
2) "item2"
|
||||
3) "item1"
|
||||
|
||||
127.0.0.1:6379> sismember set item2
|
||||
(integer) 1
|
||||
127.0.0.1:6379> sismember set item6
|
||||
(integer) 0
|
||||
|
||||
127.0.0.1:6379> srem set item2
|
||||
(integer) 1
|
||||
127.0.0.1:6379> srem set item2
|
||||
(integer) 0
|
||||
|
||||
127.0.0.1:6379> smembers set
|
||||
1) "item3"
|
||||
2) "item1"
|
||||
```
|
||||
|
||||
## HASH
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-datatype-hash.png!zp" width="400"/>
|
||||
</div>
|
||||
|
||||
命令:
|
||||
|
||||
| 命令 | 行为 |
|
||||
| ------- | ---------------------------------------- |
|
||||
| HSET | 在散列里面关联起给定的键值对 |
|
||||
| HGET | 获取指定散列键的值 |
|
||||
| HGETALL | 获取散列包含的所有键值对 |
|
||||
| HDEL | 如果给定键存在于散列里面,那么移除这个键 |
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
127.0.0.1:6379> hset myhash key1 value1
|
||||
(integer) 1
|
||||
127.0.0.1:6379> hset myhash key2 value2
|
||||
(integer) 1
|
||||
127.0.0.1:6379> hset myhash key3 value3
|
||||
(integer) 1
|
||||
127.0.0.1:6379> hset myhash key3 value2
|
||||
(integer) 0
|
||||
|
||||
127.0.0.1:6379> hgetall myhash
|
||||
1) "key1"
|
||||
2) "value1"
|
||||
3) "key2"
|
||||
4) "value2"
|
||||
5) "key3"
|
||||
6) "value2"
|
||||
|
||||
127.0.0.1:6379> hdel myhash key2
|
||||
(integer) 1
|
||||
127.0.0.1:6379> hdel myhash key2
|
||||
(integer) 0
|
||||
|
||||
127.0.0.1:6379> hget myhash key2
|
||||
(nil)
|
||||
|
||||
127.0.0.1:6379> hgetall myhash
|
||||
1) "key1"
|
||||
2) "value1"
|
||||
3) "key3"
|
||||
4) "value2"
|
||||
127.0.0.1:6379>
|
||||
```
|
||||
|
||||
## ZSET
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-datatype-zset.png!zp" width="400"/>
|
||||
</div>
|
||||
|
||||
命令:
|
||||
|
||||
| 命令 | 行为 |
|
||||
| ------------- | ---------------------------------------------------------- |
|
||||
| ZADD | 将一个带有给定分值得成员添加到有序集合里面 |
|
||||
| ZRANGE | 根据元素在有序排列中所处的位置,从有序集合里面获取多个元素 |
|
||||
| ZRANGEBYSCORE | 获取有序集合在给定分值范围内的所有元素 |
|
||||
| ZREM | 如果给定成员存在于有序集合,那么移除这个成员 |
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
127.0.0.1:6379> zadd zset 1 redis
|
||||
(integer) 1
|
||||
127.0.0.1:6379> zadd zset 2 mongodb
|
||||
(integer) 1
|
||||
127.0.0.1:6379> zadd zset 3 mysql
|
||||
(integer) 1
|
||||
127.0.0.1:6379> zadd zset 3 mysql
|
||||
(integer) 0
|
||||
127.0.0.1:6379> zadd zset 4 mysql
|
||||
(integer) 0
|
||||
|
||||
127.0.0.1:6379> zrange zset 0 -1 withscores
|
||||
1) "redis"
|
||||
2) "1"
|
||||
3) "mongodb"
|
||||
4) "2"
|
||||
5) "mysql"
|
||||
6) "4"
|
||||
|
||||
127.0.0.1:6379> zrangebyscore zset 0 2 withscores
|
||||
1) "redis"
|
||||
2) "1"
|
||||
3) "mongodb"
|
||||
4) "2"
|
||||
|
||||
127.0.0.1:6379> zrem zset mysql
|
||||
(integer) 1
|
||||
127.0.0.1:6379> zrange zset 0 -1 withscores
|
||||
1) "redis"
|
||||
2) "1"
|
||||
3) "mongodb"
|
||||
4) "2"
|
||||
```
|
||||
|
||||
## 参考资料
|
||||
|
||||
《Redis 设计与实现》
|
|
@ -0,0 +1,324 @@
|
|||
# Redis 持久化
|
||||
|
||||
> Redis 支持持久化,即把数据存储到硬盘中。
|
||||
>
|
||||
> Redis 提供了两种持久化方式:
|
||||
>
|
||||
> - **`RDB 快照(snapshot)`** - 将存在于某一时刻的所有数据都写入到硬盘中。
|
||||
> - **`只追加文件(append-only file,AOF)`** - 它会在执行写命令时,将被执行的写命令复制到硬盘中。
|
||||
>
|
||||
> 这两种持久化方式既可以同时使用,也可以单独使用。
|
||||
>
|
||||
> 将内存中的数据存储到硬盘的一个主要原因是为了在之后重用数据,或者是为了防止系统故障而将数据备份到一个远程位置。另外,存储在 Redis 里面的数据有可能是经过长时间计算得出的,或者有程序正在使用 Redis 存储的数据进行计算,所以用户会希望自己可以将这些数据存储起来以便之后使用,这样就不必重新计算了。
|
||||
|
||||
<!-- TOC depthFrom:2 depthTo:3 -->
|
||||
|
||||
- [RDB](#rdb)
|
||||
- [RDB 的创建和载入](#rdb-的创建和载入)
|
||||
- [RDB 的配置](#rdb-的配置)
|
||||
- [RDB 的文件结构](#rdb-的文件结构)
|
||||
- [RDB 的原理](#rdb-的原理)
|
||||
- [RDB 的优缺点](#rdb-的优缺点)
|
||||
- [AOF](#aof)
|
||||
- [AOF 的载入](#aof-的载入)
|
||||
- [AOF 的重写/压缩](#aof-的重写压缩)
|
||||
- [AOF 的配置](#aof-的配置)
|
||||
- [AOF 的原理](#aof-的原理)
|
||||
- [AOF 的优缺点](#aof-的优缺点)
|
||||
- [选择持久化方式](#选择持久化方式)
|
||||
- [怎样从 RDB 切换为 AOF 方式](#怎样从-rdb-切换为-aof-方式)
|
||||
- [AOF 和 RDB 之间的相互作用](#aof-和-rdb-之间的相互作用)
|
||||
- [备份](#备份)
|
||||
- [容灾备份](#容灾备份)
|
||||
- [Redis 复制的启动过程](#redis-复制的启动过程)
|
||||
- [要点](#要点)
|
||||
- [参考资料](#参考资料)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
Redis 提供了两种持久方式:RDB 和 AOF。你可以同时开启两种持久化方式。在这种情况下, 当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整。
|
||||
|
||||
## RDB
|
||||
|
||||
> **RDB 文件用于保存和还原 Redis 服务器所有数据库中的所有键值对数据。**
|
||||
>
|
||||
> RDB 持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点的数据库状态保存到一个 RDB 文件中。
|
||||
>
|
||||
> 创建 RDB 后,用户可以对 RDB 进行备份,可以将 RDB 复制到其他服务器从而创建具有相同数据的服务器副本,还可以将快好留在原地以便重启服务器时使用。
|
||||
>
|
||||
> **如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。**
|
||||
>
|
||||
> **RDB 文件是一个经过压缩的二进制文件**。
|
||||
|
||||
### RDB 的创建和载入
|
||||
|
||||
#### 创建 RDB
|
||||
|
||||
有两个 Redis 命令可以用于生成 RDB 文件:`SAVE` 和 `BGSAVE`。
|
||||
|
||||
- `SAVE` - [SAVE](https://redis.io/commands/save) 命令会阻塞 Redis 服务器进程,直到 RDB 创建完成为止,在阻塞期间,服务器不能响应任何命令请求。
|
||||
- `BGSAVE` - [BGSAVE](https://redis.io/commands/bgsave) 命令会派生出一个子进程,然后由子进程负责创建 RDB 文件,服务器进程(父进程)继续处理命令请求。
|
||||
- 需要说明的是:BGSAVE 命令执行期间,SAVE、BGSAVE、BGREWRITEAOF 三个命令会被拒绝,以免与当前的 BGSAVE 操作产生冲突或降低性能。
|
||||
|
||||
#### 载入 RDB
|
||||
|
||||
**载入 RDB 文件是在服务器启动时自动执行的**,Redis 并没有专门用于载入 RDB 文件的命令。
|
||||
|
||||
### RDB 的配置
|
||||
|
||||
Redis RDB 默认配置如下:
|
||||
|
||||
```
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
dir ./
|
||||
```
|
||||
|
||||
Redis 的配置文件 `redis.conf` 中以下参数是与 RDB 有关的:
|
||||
|
||||
- `save` - Redis 会根据 `save` 选项,让服务器每隔一段时间自动执行一次 `BGSAVE` 命令。
|
||||
|
||||
如果在 `redis.conf` 中添加以下配置,则只要满足任意一条,`BGSAVE` 命令就会被执行。
|
||||
|
||||
```bash
|
||||
save 900 1 -- 900 秒内,至少对数据库进行了 1 次修改
|
||||
save 300 10 -- 300 秒内,至少对数据库进行了 10 次修改
|
||||
save 60 10000 -- 60 秒内,至少对数据库进行了 10000 次修改
|
||||
```
|
||||
|
||||
`BGSAVE` 命令执行的操作是:将当前时间点的数据库状态写入 `dir/dbfilename` 路径下。
|
||||
|
||||
- `stop-writes-on-bgsave-error` - 当 BGSAVE 命令出现错误时停止写 RDB 文件
|
||||
- `rdbcompression` - RDB 文件开启压缩功能。
|
||||
- `rdbchecksum` - 对 RDB 文件进行校验。
|
||||
- `dbfilename` - RDB 文件名。
|
||||
- `dir` - RDB 文件存储路径。
|
||||
|
||||
### RDB 的文件结构
|
||||
|
||||
RDB 文件是一个经过压缩的二进制文件,由多个部分组成。
|
||||
|
||||
对于不同类型的键值对,RDB 文件会使用不同的方式来保存它们。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-rdb-structure.png!zp" />
|
||||
</div>
|
||||
|
||||
### RDB 的原理
|
||||
|
||||
在默认情况下,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中。你可以对 Redis 进行设置, 让它在“N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次数据集。你也可以通过调用 SAVE 或者 BGSAVE,手动让 Redis 进行数据集保存操作。这种持久化方式被称为快照。
|
||||
|
||||
当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
|
||||
|
||||
- Redis 创建一个子进程。
|
||||
- 子进程将数据集写入到一个临时快照文件中。
|
||||
- 当子进程完成对新 快照文件的写入时,Redis 用新快照文件替换原来的快照文件,并删除旧的快照文件。
|
||||
|
||||
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。
|
||||
|
||||
### RDB 的优缺点
|
||||
|
||||
- **优点**
|
||||
- RDB 是一个非常紧凑的文件,它保存了某个时间点的数据集,非常适用于数据集的备份。比如你可以在每个小时报保存一下过去 24 小时内的数据,同时每天保存过去 30 天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集。
|
||||
- RDB 是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的 S3(可能加密),非常适用于灾难恢复。
|
||||
- 快照在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以快照持久化方式可以最大化 redis 的性能。
|
||||
- 与 AOF 相比,在恢复大的数据集的时候,DB 方式会更快一些。
|
||||
- **缺点**
|
||||
- 如果你希望在 redis 意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么 快照不适合你。虽然你可以配置不同的 save 时间点(例如每隔 5 分钟并且对数据集有 100 个写的操作),是 Redis 要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔 5 分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。
|
||||
- 快照需要经常 fork 子进程来保存数据集到硬盘上。当数据集比较大的时候,fork 的过程是非常耗时的,可能会导致 Redis 在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且 CPU 性能不是很好的情况下,这种情况会持续 1 秒。AOF 也需要 fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度。
|
||||
|
||||
## AOF
|
||||
|
||||
> `AOF(Append Only File)` 持久化方式记录每次对服务器执行的写操作。当服务器重启的时候会重新载入和执行这些命令来恢复原始的数据。
|
||||
>
|
||||
> AOF 命令以 redis 协议追加保存每次写的操作到文件末尾。Redis 还能对 AOF 文件进行后台重写。使得 AOF 文件的体积不至于过大。
|
||||
|
||||
```
|
||||
appendonly no
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
```
|
||||
|
||||
### AOF 的载入
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-aof-flow.png!zp" />
|
||||
</div>
|
||||
|
||||
### AOF 的重写/压缩
|
||||
|
||||
随着 Redis 不断运行,AOF 的体积也会不断增长,这将导致两个问题:
|
||||
|
||||
1. AOF 耗尽磁盘可用空间。
|
||||
2. Redis 重启后需要执行 AOF 文件记录的所有写命令来还原数据集,如果 AOF 过大,则还原操作执行的时间就会非常长。
|
||||
|
||||
解决方法:
|
||||
|
||||
执行 `BGREWRITEAOF` 命令,这个命令会通过移除 AOF 中的冗余命令来重写 AOF 文件,使 AOF 文件的体积尽可能地小。
|
||||
|
||||
`BGREWRITEAOF` 命令与 `BGSAVE` 原理类似:通过创建一个子进程,然后由子进程负责对 AOF 文件进行重写。
|
||||
|
||||
可以通过设置 `auto-aof-rewrite-percentage` 和 `auto-aof-rewrite-min-size`,使得 Redis 在满足条件时,自动执行 `BGREWRITEAOF`。
|
||||
|
||||
假设配置如下:
|
||||
|
||||
```
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
```
|
||||
|
||||
表明,当 AOF 大于 64MB,且 AOF 体积比上一次重写后的体积大了至少 100% 时,执行 `BGREWRITEAOF`。
|
||||
|
||||
### AOF 的配置
|
||||
|
||||
AOF 持久化通过在 `redis.conf` 中的 `appendonly yes` 配置选项来开启。
|
||||
|
||||
- **`appendonly`** - 开启 AOF 功能。
|
||||
- **`appendfilename`** - AOF 文件名。
|
||||
- 可以通过 `appendfsync` 配置选项来设置同步频率:
|
||||
- **`always`** - 每个 Redis 写命令都要同步写入硬盘。这样做会严重降低 Redis 的速度。
|
||||
- **`everysec`** - 每秒执行一次同步,显示地将多个写命令同步到硬盘。
|
||||
- **`no`** - 让操作系统来决定应该何时进行同步。
|
||||
- `no-appendfsync-on-rewrite` - AOF 重写时不支持追加命令
|
||||
- `auto-aof-rewrite-percentage` - AOF 重写百分比
|
||||
- `auto-aof-rewrite-min-size` - AOF 重写文件的最小大小
|
||||
|
||||
为了兼顾数据安全和写入性能,推荐使用 `appendfsync everysec` 选项。Redis 每秒同步一次 AOF 文件时的性能和不使用任何持久化特性时的性能相差无几。
|
||||
|
||||
### AOF 的原理
|
||||
|
||||
AOF 的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
|
||||
|
||||
当 Redis 服务器开启 AOF 功能时,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。
|
||||
|
||||
#### AOF 的实现
|
||||
|
||||
- Redis 创建一个子进程。
|
||||
- 子进程开始将新 AOF 文件的内容写入到临时文件。
|
||||
- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
|
||||
- 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
|
||||
- 搞定!现在 Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。
|
||||
|
||||
#### AOF 重写的实现
|
||||
|
||||
AOF 重写并非读取和分析现有 AOF 文件的内容,而是直接从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录记录这个键值对的多条命令。
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-aof-rewrite.png!zp" />
|
||||
</div>
|
||||
在执行 BGREWRITEAOF 命令是,Redis服务器会维护一个AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换就的AOF文件,以此来完成AOF重写操作。
|
||||
|
||||
### AOF 的优缺点
|
||||
|
||||
- **优点**
|
||||
- 使用 AOF 会让你的 Redis 更加耐久: 你可以使用不同的 fsync 策略:无 fsync;每秒 fsync;每次写的时候 fsync。使用默认的每秒 fsync 策略,Redis 的性能依然很好(fsync 是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失 1 秒的数据。
|
||||
- AOF 文件是一个只进行追加的日志文件,所以不需要写入 seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用 redis-check-aof 工具修复这些问题。
|
||||
- Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写:重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
|
||||
- AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存。因此 AOF 文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单。举个例子,如果你不小心执行了 FLUSHALL 命令,但只要 AOF 文件未被重写,那么只要停止服务器,移除 AOF 文件末尾的 FLUSHALL 命令,并重启 Redis ,就可以将数据集恢复到 FLUSHALL 执行之前的状态。
|
||||
- **缺点**
|
||||
- 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
|
||||
- 根据所使用的 fsync 策略,AOF 的速度可能会慢于快照。在一般情况下,每秒 fsync 的性能依然非常高,而关闭 fsync 可以让 AOF 的速度和快照一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,快照可以提供更有保证的最大延迟时间(latency)。
|
||||
|
||||
## 选择持久化方式
|
||||
|
||||
- 如果你只希望你的数据在服务器运行的时候存在,你可以不使用任何持久化方式。
|
||||
- 如果你非常关心你的数据,但仍然可以承受数分钟以内的数据丢失,那么你可以只使用 快照持久化。
|
||||
- 如果你不能承受数分钟以内的数据丢失,那么你可以同时使用快照持久化和 AOF 持久化。
|
||||
|
||||
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份,并且快照恢复数据集的速度也要比 AOF 恢复的速度要快,除此之外,使用快照还可以避免之前提到的 AOF 程序的 bug 。
|
||||
|
||||
### 怎样从 RDB 切换为 AOF 方式
|
||||
|
||||
在 Redis 2.2 或以上版本,可以在不重启的情况下,从快照切换到 AOF :
|
||||
|
||||
- 为最新的 dump.rdb 文件创建一个备份。
|
||||
- 将备份放到一个安全的地方。
|
||||
- 执行以下两条命令:
|
||||
- redis-cli config set appendonly yes
|
||||
- redis-cli config set save
|
||||
- 确保写命令会被正确地追加到 AOF 文件的末尾。
|
||||
- 执行的第一条命令开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件创建完成为止, 之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。
|
||||
|
||||
执行的第二条命令用于关闭快照功能。 这一步是可选的, 如果你愿意的话, 也可以同时使用快照和 AOF 这两种持久化功能。
|
||||
|
||||
重要:别忘了在 redis.conf 中打开 AOF 功能! 否则的话, 服务器重启之后, 之前通过 CONFIG SET 设置的配置就会被遗忘, 程序会按原来的配置来启动服务器。
|
||||
|
||||
### AOF 和 RDB 之间的相互作用
|
||||
|
||||
在版本号大于等于 2.4 的 Redis 中, BGSAVE 执行的过程中, 不可以执行 BGREWRITEAOF 。 反过来说, 在 BGREWRITEAOF 执行的过程中, 也不可以执行 BGSAVE。这可以防止两个 Redis 后台进程同时对磁盘进行大量的 I/O 操作。
|
||||
|
||||
如果 BGSAVE 正在执行, 并且用户显示地调用 BGREWRITEAOF 命令, 那么服务器将向用户回复一个 OK 状态, 并告知用户, BGREWRITEAOF 已经被预定执行: 一旦 BGSAVE 执行完毕, BGREWRITEAOF 就会正式开始。 当 Redis 启动时, 如果快照持久化和 AOF 持久化都被打开了, 那么程序会优先使用 AOF 文件来恢复数据集, 因为 AOF 文件所保存的数据通常是最完整的。
|
||||
|
||||
## 备份
|
||||
|
||||
**务必确保你的数据有完整的备份。**
|
||||
|
||||
磁盘故障、节点失效,诸如此类的问题都可能让你的数据消失不见,不进行备份是非常危险的。
|
||||
|
||||
备份 Redis 数据建议采用如下策略:
|
||||
|
||||
备份 Redis 数据建议采用快照方式。RDB 文件一旦创建,就不会进行任何修改,所以十分安全。
|
||||
|
||||
Redis 快照备份过程:
|
||||
|
||||
- 创建一个定期任务(cron job),每小时将一个 RDB 文件备份到一个文件夹,并且每天将一个 RDB 文件备份到另一个文件夹。
|
||||
- 确保快照的备份都带有相应的日期和时间信息,每次执行定期任务脚本时,使用 find 命令来删除过期的快照:比如说,你可以保留最近 48 小时内的每小时快照,还可以保留最近一两个月的每日快照。
|
||||
- 至少每天一次,将 RDB 备份到你的数据中心之外,或者至少是备份到你运行 Redis 服务器的物理机器之外。
|
||||
|
||||
### 容灾备份
|
||||
|
||||
Redis 的容灾备份基本上就是对数据进行备份,并将这些备份传送到多个不同的外部数据中心。
|
||||
|
||||
容灾备份可以在 Redis 运行并产生快照的主数据中心发生严重的问题时,仍然让数据处于安全状态。
|
||||
|
||||
以下是一些实用的容灾备份方法:
|
||||
|
||||
- Amazon S3 ,以及其他类似 S3 的服务,是一个构建灾难备份系统的好地方。 最简单的方法就是将你的每小时或者每日 RDB 备份加密并传送到 S3 。对数据的加密可以通过 gpg -c 命令来完成(对称加密模式)。记得把你的密码放到几个不同的、安全的地方去(比如你可以把密码复制给你组织里最重要的人物)。同时使用多个储存服务来保存数据文件,可以提升数据的安全性。
|
||||
- 传送快照可以使用 SCP 来完成(SSH 的组件)。 以下是简单并且安全的传送方法: 买一个离你的数据中心非常远的 VPS ,装上 SSH ,创建一个无口令的 SSH 客户端 key ,并将这个 key 添加到 VPS 的 authorized_keys 文件中,这样就可以向这个 VPS 传送快照备份文件了。为了达到最好的数据安全性,至少要从两个不同的提供商那里各购买一个 VPS 来进行数据容灾备份。
|
||||
- 需要注意的是,这类容灾系统如果没有小心地进行处理的话,是很容易失效的。最低限度下,你应该在文件传送完毕之后,检查所传送备份文件的体积和原始快照文件的体积是否相同。如果你使用的是 VPS ,那么还可以通过比对文件的 SHA1 校验和来确认文件是否传送完整。
|
||||
|
||||
另外, 你还需要一个独立的警报系统, 让它在负责传送备份文件的传送器(transfer)失灵时通知你。
|
||||
|
||||
### Redis 复制的启动过程
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/Redis复制启动过程.png!zp" />
|
||||
</div>
|
||||
|
||||
当多个从服务器尝试连接同一个主服务器时:
|
||||
|
||||
- 上图步骤 3 尚未执行:所有从服务器都会接收到相同的快照文件和相同的缓冲区写命令。
|
||||
- 上图步骤 3 正在执行或已经执行完毕:当主服务器与较早进行连接的从服务器执行完复制所需的 5 个步骤之后,主服务器会与新连接的从服务器执行一次新的步骤 1 至步骤 5。
|
||||
|
||||
## 要点
|
||||
|
||||
- RDB
|
||||
- RDB 文件用于保存和还原 Redis 服务器所有数据库中的所有键值对数据。
|
||||
- [`SAVE`](https://redis.io/commands/save) 命令由服务器进程直接执行,所以该命令会阻塞服务器。
|
||||
- [`BGSAVE`](https://redis.io/commands/bgsave) 命令由子进程执行,所以该命令不会阻塞服务器。
|
||||
- 服务器状态中会保存所有用 save 选项设置的保存条件,当满足任意一个条件时,服务器会自动执行 `BGSAVE` 命令。
|
||||
- RDB 文件是一个经过压缩的二进制文件,由多个部分组成。
|
||||
- 对于不同类型的键值对,RDB 文件会使用不同的方式来保存它们。
|
||||
- AOF
|
||||
- AOF 文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态。
|
||||
- AOF 文件中的所有命令都以 Redis 命令请求协议的格式保存。
|
||||
- 命令请求会先保存到 AOF 缓冲区中,之后再定期写入并同步到 AOF 文件。
|
||||
- `appendfsync` 选项的不同值对 AOF 持久化功能的安全性以及 Redis 服务器的性能有很大的影响。
|
||||
- 服务器只要载入并重新执行保存在 AOF 文件中的命令,就可以还原数据库本来的状态。
|
||||
- AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
|
||||
- AOF 重写是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
|
||||
- 在执行 `BGREWRITEAOF` 命令是,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换就的 AOF 文件,以此来完成 AOF 重写操作。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- 《Redis 实战》
|
||||
- 《Redis 设计与实现》
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [Redis Persistence](https://redis.io/topics/persistence)
|
|
@ -242,5 +242,6 @@ REPLCONF ACK <replication_coffset>
|
|||
|
||||
## 参考资料
|
||||
|
||||
- 《Redis 实战》
|
||||
- 《Redis 设计与实现》
|
||||
- http://redisdoc.com/topic/replication.html
|
||||
|
|
|
@ -873,3 +873,6 @@ Sentinel 所做的就是登记之前的中断调用时间,并和当前的调
|
|||
注意某些情况下,使用许多内核提供的单调时钟 API 代替 TILT 模式。可是它仍然是不清晰的如果这是一个很好的解决方案,因为在进程只是仅仅挂起或调度很长时间没有执行的情况下,当前的系统会避免这个问题。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- 《Redis 实战》
|
||||
- 《Redis 设计与实现》
|
||||
|
|
|
@ -12,10 +12,14 @@
|
|||
- [2.3. SET](#23-set)
|
||||
- [2.4. HASH](#24-hash)
|
||||
- [2.5. ZSET](#25-zset)
|
||||
- [3. 使用场景](#3-使用场景)
|
||||
- [3. Redis 使用场景](#3-redis-使用场景)
|
||||
- [4. Redis 管道](#4-redis-管道)
|
||||
- [5. 键的过期时间](#5-键的过期时间)
|
||||
- [6. 数据淘汰策略](#6-数据淘汰策略)
|
||||
- [6. 内存淘汰](#6-内存淘汰)
|
||||
- [6.1. 内存淘汰要点](#61-内存淘汰要点)
|
||||
- [6.2. 淘汰策略](#62-淘汰策略)
|
||||
- [6.3. 如何选择淘汰策略](#63-如何选择淘汰策略)
|
||||
- [6.4. 内部实现](#64-内部实现)
|
||||
- [7. 持久化](#7-持久化)
|
||||
- [7.1. 快照持久化](#71-快照持久化)
|
||||
- [7.2. AOF 持久化](#72-aof-持久化)
|
||||
|
@ -25,6 +29,7 @@
|
|||
- [9.2. MULTI](#92-multi)
|
||||
- [9.3. DISCARD](#93-discard)
|
||||
- [9.4. WATCH](#94-watch)
|
||||
- [9.5. Redis 不支持回滚](#95-redis-不支持回滚)
|
||||
- [10. 事件](#10-事件)
|
||||
- [10.1. 文件事件](#101-文件事件)
|
||||
- [10.2. 时间事件](#102-时间事件)
|
||||
|
@ -307,7 +312,7 @@ OK
|
|||
4) "2"
|
||||
```
|
||||
|
||||
## 3. 使用场景
|
||||
## 3. Redis 使用场景
|
||||
|
||||
- **缓存** - 将热点数据放到内存中,设置内存的最大使用量以及过期淘汰策略来保证缓存的命中率。
|
||||
- **计数器** - Redis 这种内存数据库能支持计数器频繁的读写操作。
|
||||
|
@ -353,22 +358,45 @@ redis> TTL mykey
|
|||
redis>
|
||||
```
|
||||
|
||||
## 6. 数据淘汰策略
|
||||
## 6. 内存淘汰
|
||||
|
||||
可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。
|
||||
### 6.1. 内存淘汰要点
|
||||
|
||||
| 策略 | 描述 |
|
||||
| --------------- | ---------------------------------------------------- |
|
||||
| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 |
|
||||
| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 |
|
||||
| volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 |
|
||||
| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 |
|
||||
| allkeys-random | 从所有数据集中任意选择数据进行淘汰 |
|
||||
| noeviction | 禁止驱逐数据 |
|
||||
- 最大缓存 - Redis 允许通过 `maxmemory` 参数来设置内存最大值。
|
||||
|
||||
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
|
||||
- 主键失效 - 作为一种定期清理无效数据的重要机制,在 Redis 提供的诸多命令中,`EXPIRE`、`EXPIREAT`、`PEXPIRE`、`PEXPIREAT` 以及 `SETEX` 和 `PSETEX` 均可以用来设置一条键值对的失效时间。而一条键值对一旦被关联了失效时间就会在到期后自动删除(或者说变得无法访问更为准确)。
|
||||
|
||||
作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法(LRU、TTL)实际实现上并非针对所有 key,而是抽样一小部分 key 从中选出被淘汰 key,抽样数量可通过 maxmemory-samples 配置。
|
||||
- 淘汰策略 - 随着不断的向 redis 中保存数据,当内存剩余空间无法满足添加的数据时,redis 内就会施行数据淘汰策略,清除一部分内容然后保证新的数据可以保存到内存中。内存淘汰机制是为了更好的使用内存,用一定得 miss 来换取内存的利用率,保证 redis 缓存中保存的都是热点数据。
|
||||
|
||||
- 非精准的 LRU - 实际上 Redis 实现的 LRU 并不是可靠的 LRU,也就是名义上我们使用 LRU 算法淘汰键,但是实际上被淘汰的键并不一定是真正的最久没用的。
|
||||
|
||||
### 6.2. 淘汰策略
|
||||
|
||||
内存淘汰只是 Redis 提供的一个功能,为了更好地实现这个功能,必须为不同的应用场景提供不同的策略,内存淘汰策略讲的是为实现内存淘汰我们具体怎么做,要解决的问题包括淘汰键空间如何选择?在键空间中淘汰键如何选择?
|
||||
|
||||
Redis 提供了下面几种淘汰策略供用户选择,其中默认的策略为 **`noeviction`** 策略:
|
||||
|
||||
- **`noeviction`** - 当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
|
||||
- **`allkeys-lru`** - 在主键空间中,优先移除最近未使用的 key。
|
||||
- **`allkeys-random`** - 在主键空间中,随机移除某个 key。
|
||||
- **`volatile-lru`** - 在设置了过期时间的键空间中,优先移除最近未使用的 key。
|
||||
- **`volatile-random`** - 在设置了过期时间的键空间中,随机移除某个 key。
|
||||
- **`volatile-ttl`** - 在设置了过期时间的键空间中,具有更早过期时间的 key 优先移除。
|
||||
|
||||
### 6.3. 如何选择淘汰策略
|
||||
|
||||
- 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru。
|
||||
- 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用 allkeys-random。
|
||||
- volatile-lru 策略和 volatile-random 策略适合我们将一个 Redis 实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个 Redis 实例来达到相同的效果。
|
||||
- 将 key 设置过期时间实际上会消耗更多的内存,因此我们建议使用 allkeys-lru 策略从而更有效率的使用内存。
|
||||
|
||||
### 6.4. 内部实现
|
||||
|
||||
Redis 删除失效主键的方法主要有两种:
|
||||
|
||||
- 消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它。
|
||||
- 主动方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除。
|
||||
- 主动删除:当前已用内存超过 maxmemory 限定时,触发主动清理策略,该策略由启动参数的配置决定主键具体的失效时间全部都维护在 expires 这个字典表中。
|
||||
|
||||
## 7. 持久化
|
||||
|
||||
|
@ -426,22 +454,38 @@ MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。
|
|||
|
||||
### 9.1. EXEC
|
||||
|
||||
EXEC 命令负责触发并执行事务中的所有命令:
|
||||
**[`EXEC`](https://redis.io/commands/exec) 命令负责触发并执行事务中的所有命令。**
|
||||
|
||||
- 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
|
||||
- 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。
|
||||
- 如果客户端在使用 `MULTI` 开启了一个事务之后,却因为断线而没有成功执行 `EXEC` ,那么事务中的所有命令都不会被执行。
|
||||
- 另一方面,如果客户端成功在开启事务之后执行 `EXEC` ,那么事务中的所有命令都会被执行。
|
||||
|
||||
### 9.2. MULTI
|
||||
|
||||
MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行。
|
||||
**[`MULTI`](https://redis.io/commands/multi) 命令用于开启一个事务,它总是返回 OK 。**
|
||||
|
||||
`MULTI` 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC 命令被调用时, 所有队列中的命令才会被执行。
|
||||
|
||||
以下是一个事务例子, 它原子地增加了 foo 和 bar 两个键的值:
|
||||
|
||||
```python
|
||||
> MULTI
|
||||
OK
|
||||
> INCR foo
|
||||
QUEUED
|
||||
> INCR bar
|
||||
QUEUED
|
||||
> EXEC
|
||||
1) (integer) 1
|
||||
2) (integer) 1
|
||||
```
|
||||
|
||||
### 9.3. DISCARD
|
||||
|
||||
当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。
|
||||
**当执行 [`DISCARD`](https://redis.io/commands/discard) 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出。**
|
||||
|
||||
示例:
|
||||
|
||||
```py
|
||||
```python
|
||||
> SET foo 1
|
||||
OK
|
||||
> MULTI
|
||||
|
@ -456,11 +500,11 @@ OK
|
|||
|
||||
### 9.4. WATCH
|
||||
|
||||
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
|
||||
**[`WATCH`](https://redis.io/commands/watch) 命令可以为 Redis 事务提供 check-and-set (CAS)行为。**
|
||||
|
||||
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回 nil-reply 来表示事务已经失败。
|
||||
|
||||
```
|
||||
```python
|
||||
WATCH mykey
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
|
@ -479,21 +523,49 @@ WATCH 命令可以被调用多次。对键的监视从 WATCH 执行之后开始
|
|||
|
||||
用户还可以在单个 WATCH 命令中监视任意多个键,例如:
|
||||
|
||||
```py
|
||||
```python
|
||||
redis> WATCH key1 key2 key3
|
||||
OK
|
||||
```
|
||||
|
||||
#### 取消 WATCH 的场景
|
||||
|
||||
当 EXEC 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。
|
||||
|
||||
另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。
|
||||
|
||||
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。
|
||||
|
||||
#### 使用 WATCH 创建原子操作
|
||||
|
||||
WATCH 可以用于创建 Redis 没有内置的原子操作。
|
||||
|
||||
举个例子,以下代码实现了原创的 ZPOP 命令,它可以原子地弹出有序集合中分值(score)最小的元素:
|
||||
|
||||
```
|
||||
WATCH zset
|
||||
element = ZRANGE zset 0 0
|
||||
MULTI
|
||||
ZREM zset element
|
||||
EXEC
|
||||
```
|
||||
|
||||
### 9.5. Redis 不支持回滚
|
||||
|
||||
Redis 不支持回滚的理由:
|
||||
|
||||
- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面。
|
||||
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
|
||||
|
||||
## 10. 事件
|
||||
|
||||
Redis 服务器是一个事件驱动程序。
|
||||
|
||||
Redis 服务器需要处理两类事件:
|
||||
|
||||
- 文件事件
|
||||
- 时间事件
|
||||
|
||||
### 10.1. 文件事件
|
||||
|
||||
服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。
|
||||
|
@ -513,7 +585,7 @@ Redis 将所有时间事件都放在一个无序链表中,通过遍历整个
|
|||
|
||||
### 10.3. 事件的调度与执行
|
||||
|
||||
服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能监听太久,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。
|
||||
服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。
|
||||
|
||||
事件调度与执行由 aeProcessEvents 函数负责,伪代码如下:
|
||||
|
||||
|
@ -561,6 +633,10 @@ def main():
|
|||
|
||||
从事件处理的角度来看,服务器运行流程如下:
|
||||
|
||||
<div align="center">
|
||||
<img src="http://dunwu.test.upcdn.net/cs/database/redis/redis-event.png!zp" />
|
||||
</div>
|
||||
|
||||
## 11. 集群
|
||||
|
||||
### 11.1. 复制
|
||||
|
@ -609,6 +685,19 @@ redis 官方推荐的 Java Redis Client:
|
|||
|
||||
## 13. 资料
|
||||
|
||||
- [Redis 官网](https://redis.io/)
|
||||
- [awesome-redis](https://github.com/JamzyWang/awesome-redis)
|
||||
- [Redis 实战](https://item.jd.com/11791607.html)
|
||||
- 官网
|
||||
- [redis 官网](https://redis.io/)
|
||||
- [redis github](https://github.com/antirez/redis)
|
||||
- [官方文档翻译版本一](http://ifeve.com/redis-sentinel/) 翻译,排版一般,新
|
||||
- [官方文档翻译版本二](http://redisdoc.com/topic/sentinel.html) 翻译有段时间了,但主要部分都包含,排版好
|
||||
- 书
|
||||
- 《Redis 实战》
|
||||
- 《Redis 设计与实现》
|
||||
- 资源汇总
|
||||
- [awesome-redis](https://github.com/JamzyWang/awesome-redis)
|
||||
- Redis Client
|
||||
- [spring-data-redis 官方文档 ](https://docs.spring.io/spring-data/redis/docs/1.8.13.RELEASE/reference/html/)
|
||||
- [redisson 官方文档(中文,略有滞后)](https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95)
|
||||
- [redisson 官方文档(英文)](https://github.com/redisson/redisson/wiki/Table-of-Content)
|
||||
- [CRUG | Redisson PRO vs. Jedis: Which Is Faster? 翻译](https://www.jianshu.com/p/82f0d5abb002)
|
||||
- [redis分布锁Redisson性能测试](https://blog.csdn.net/everlasting_188/article/details/51073505)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "linux-tutorial",
|
||||
"name": "db-tutorial",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "docsify serve ./ --port 4000"
|
||||
|
|
|
@ -470,4 +470,4 @@ H2 可以通过 CreateCluster 工具创建集群,示例步骤如下(在在
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -510,4 +510,4 @@ Flyway 的功能主要围绕着 7 个基本命令:[Migrate](https://flywaydb.o
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -29,4 +29,4 @@
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -89,4 +89,4 @@ mysql -u root -p'yourpassword' mysql < /home/zp/sql/all.sql
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -508,4 +508,4 @@ Query OK, 0 rows affected (0.00 sec)
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -560,4 +560,4 @@ MySQL 读写分离能提高性能的原因在于:
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -194,4 +194,4 @@ $ psql -h 127.0.0.1 -U user_name db_name < dump.sql
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -1092,4 +1092,4 @@ DROP TRIGGER IF EXISTS trigger_insert_user;
|
|||
|
||||
## 17. 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -579,4 +579,4 @@ MySQL 读写分离能提高性能的原因在于:
|
|||
|
||||
## 9. :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
|
|
@ -405,5 +405,5 @@ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:");
|
|||
|
||||
## :door: 传送门
|
||||
|
||||
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
|
||||
| [我的 Github 博客](https://github.com/dunwu/blog) | [db-tutorial 首页](https://github.com/dunwu/db-tutorial) |
|
||||
|
||||
|
|
Loading…
Reference in New Issue