分布式数据库中的 CAP 原理
-
传统关系型数据库的 ACID
A (Atomicity) 原子性,事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。
C (Consistency) 一致性,数据库一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。
I (Isolation) 独立性,指并发的事务之间不会互相影响。如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。
D (Durability) 持久性,指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
-
分布式数据库 CAP
C (Consistency) 强一致性,数据的强一致性
A (Availability) 可用性
P (Partition tolerance) 分区容错性
CAP理论的核心是:
一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三大类:
- CA:单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP:满足一致性,分区容忍必的系统,通常性能不是特别高。
- AP:满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
对于分布式数据库来说,由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的
所以我们只能在一致性和可用性之间进行权衡,没有 NoSQL 系统能同时保证这三点。
CA -----> 传统关系型数据库
AP -----> 大多数网站架构的选择
CP -----> Redis、Mongodb
一致性与可用性的决择
- 对于大多数 Web 应用,其实并不需要强一致性(最终一致性即可),而是更关心高可用性。因此牺牲 C 换取 AP,这是目前分布式数据库产品的方向。
-
BASE
BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。
基本可用(Basically Available)
软状态(Soft state)
最终一致(Eventually consistent)- 主要思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。即牺牲 C 换取 AP
Redis 杂项基础知识
启动
在 /usr/local/bin 中
-
Redis-sever /myRedis/Redis.conf
/myRedis/Redis.conf 为 副本文件,源文件在 opt/Redis/Redis.conf 中 (自己的安装目录)
-
Redis-cli -p 6379
默认端口为 6379
Redis 是单进程模型,通过对 epoll 函数包装做到对读写等事件的响应。
Epoll — Linux内核 为处理大批量文件描述符而改进的 epoll,是 Linux 下多路复用 IO 接口 select / poll 的增强版本。
-
select 切换数据库 默认 0 - 15 16个库
-
Dbsize 查看当前数据库的 key 数量
-
keys * 查看当前库所有key
-
keys k 查看 k 是否存在
-
flushdb 清空当前库
-
flushall 清空所有库
Redis 五大数据类型
**获取 Redis 常见数据类型操作命令 Redis命令手册 **
- Redis 键 (key) :
命令 | 描述 |
---|---|
exists key | 检查给定 key 是否存在 |
expire key second | 为给定 key 设置过期时间,以秒计 |
type key | 返回 key 所储存的值的类型 |
move key db | 将当前数据库的 key 移动到给定的数据库 db 当中 db默认 0 - 15 |
ttl key | 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live) -1表示永不过期 -2表示已经过期,移除 |
*keys pattern (keys ) | 查找所有符合给定模式( pattern)的 key (查找所有) |
persist key | 移除 key 的过期时间,key 将持久保持 |
del key | key 存在时删除 key |
dump key | 序列化给定 key ,并返回被序列化的值 |
expireat key timestamp | expireat 的作用和 expire 类似,都用于为 key 设置过期时间。 不同在于 expireat 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
expire key milliseconds | 设置 key 的过期时间以毫秒计 |
pexpireat key milliseconds-timestamp | 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
pttl key | 以毫秒为单位返回 key 的剩余的过期时间 |
randomkey | 从当前数据库中随机返回一个 key |
rename key newkey | 修改 key 的名称 |
renamex key newkey | 仅当 newkey 不存在时,将 key 改名为 newkey |
scan cursor [MATCH pattern] [COUNT count] | 迭代数据库中的数据库键 |
- 1、String(字符串)
Redis 最基本的类型 单值单 value
String 是二进制安全的,指的是可以包含任何数据。如jpg或序列化对象等
命令 | 描述 |
---|---|
Set key value | 设置指定 key 的值 |
get key | 获取指定 key 的值 |
del key | 删除指定 key 的值 |
append key value | 如果 key 已经存在并且是一个字符串, 将指定的 value 追加到该 key 的 value 末尾 |
strlen key | 返回 key 所储存的字符串值的长度 |
incr key | 将 key 中储存的数字值增一 |
decr key | 将 key 中储存的数字值减一 |
incrby key increment | 将 key 所储存的值加上给定的增量值(increment) |
decrby key decrement | 将key 所储存的值减去给定的减量值(decrement) |
getrange key start end | 返回 key 中字符串值的子字符 |
setrange key offset value | 用 value 覆盖给定 key 所储存的字符串值,从偏移量 offset 开始,覆盖个数与value有关 |
setex key seconds value setex —>(Set with expire 键秒值) | **将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位) ** |
setnx key value setnx —>(Set if not exit) | 只有在 key 不存在时设置 key 的值 |
mget key1 [key2…] mget —>(more get) | 获取所有(一个或多个)给定 key 的值 |
mset key value [key value …] mget —>(more get) | 同时设置一个或多个 key-value 对 |
msetnx key value [key value …] | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 |
getset key value | **先返回 key 的旧值(old value),再将给定 key 的值设为 value ** |
getbit key offset | 对 key 所储存的字符串值,获取指定偏移量上的位 (bit) |
setbit key offset value | 对 key 所储存的字符串值,设置或清除指定偏移量上的位 (bit) |
psetex key milliseconds value | 和 setex 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 setex 命令那样,以秒为单位 |
incrbyfloat key increment | 将 key 所储存的值加上给定的浮点增量值(increment) |
- 2、List(列表)
单值多 value
Redis 的 List 是简单的字符串列表,按照插入顺序排序,可以在两边插入,底层是链表
命令 | 描述(把列表当做一个双端队列) |
---|---|
lpush key value1 [value2] | 将一个或多个值插入到列表头部(从左边) |
rpush key value1 [value2] | 将一个或多个值插入到列表尾部(从右边) |
lrange key start stop | 获取 key 指定范围内的元素 (start = 0 stop = -1 表示全部) |
lpop key | 移出 key 的第一个元素(从左边) |
rpop key | 移除 key 的最后一个元素(从右边) |
lindex key index | 通过索引获取key中的元素(从0开始) |
llen key | 获取 key 长度 |
lrem key count value | 移除 count 个 value |
ltrim key start stop | 获取指定范围元素,其余都删除 |
rpoplpush source destination | 移除 source 列表的尾元素,并将该元素添加到 destination 表头 |
lset key index value | 通过索引设置列表元素的值 |
linsert key before/after pivot value | 在key指定元素前或者后插入元素 |
blpop key1 [key2 ] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
brpop key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
brpoplpush source destination timeout | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
lpushx key value | 将一个值插入到已存在的列表头部 |
rpushx key value | 为已存在的列表添加值 |
性能总结:
- 它是一个字符串链表,left、right 都可以插入添加;
- 如果键不存在,创建新的链表;
- 如果键已存在,新增内容;
- 如果值全移除,对应的键也就消失了;
- 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率差。
- 3、Set(集合)
单值多 value
Redis 的 Set 是 String 类型的无序集合且值唯一。它是通过HashTable实现的。
命令 | 描述 |
---|---|
sadd key member1 [member2] | 向 key 添加一个或多个成员 |
smembers key | 返回 key 中的所有成员 |
sismember key member (sismember —> s is member) | 判断 member 元素是否为 key 的成员 |
scard key | 获取 key 的元素个数 |
srem key member1 [member2] | 移除 key 中一个或多个成员 |
srandmember key [count] | 从 key 中随机出 count 个数字 |
spop key [count] | 从 key 中随机删除 count 个元素 |
smove source destination member | 将 member 元素从 source 集合移动到 destination 集合(source中会删除) |
sunion key1 [key2] | 并 |
sinter key1 [key2] | 交 |
sdiff key1 [key2] | 差 (key1-key2) |
sdiffstore destination key1 [key2] | 返回给定所有集合的差集并存储在 destination 中 |
sinterstore destination key1 [key2] | 返回给定所有集合的交集并存储在 destination 中 |
sunionstore destination key1 [key2] | 所有给定集合的并集存储在 destination 集合中 |
sscan key cursor [MATCH pattern] [COUNT count] | 迭代集合中的元素 |
- 4、Hash(哈希)
<key, value>其中value是一个键值对,即<key, <field, value>>
其中 key 为 String 类型,Hash 适合存储对象
命令 | 描述 |
---|---|
hset key field value | 将 key 的字段 field 的值设为 value |
hget key field | 获取 key 中一个 field 的 value |
hmset key field1 value1 [field2 value2 ] | 设置多个 field-value 到 key 中 |
**hmget key field1 [field2] | 获取 key 中多个 field 的 value |
hgetall key | 获取 key 中的所有 field 和 value |
hdel key field1 [field2] | 删除一个或多个哈希表字段 |
hlen key | 获取 key 中字段的数量 |
hexists key field | 判断 key 中指定的字段是否存在 |
hkeys key | 获取 key 中的所有 field |
hvals key | 获取 key 中所有 value |
hincrby key field increment | 为 key 中的指定 field 的整数值加上增量 increment |
hincrbyfloat key field increment | 为 key 中的指定 field 的浮点数值加上增量 increment |
hsetnx key field value | 只有在 key 的 field 不存在时,设置 value |
hscan key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对 |
- 5、Zset(Sorted Set,有序集合)
Redis Zset 和 Set 一样也是 String 类型元素的集合,且不允许重复
不同的是,每个元素都会在其前面关联一个 double 类型的 score
Set 为:k1 v1 v2 v3 Zset 为:k1 score1 v1 score2 v2 (score + value 为一个整体)
Redis 正是通过分数来为集合中的成员进行从小到大的排序,Zset 的成员是唯一的,但 score 是可重复的
命令 | 描述 |
---|---|
zadd key score1 value1 [score2 value2] | 向 key 中添加一个或多个元素,或者更新已存在元素的分数 |
zrange key start stop [withscores] | 返回 key 指定索引区间内元素 withscores 表示显示 score,默认不显示 |
1、zrangebyscore key min max [withscores] | 通过 score 返回 key 指定 索引区间 内元素 |
2、zrangebyscore key (min (max | 通过 score 返回 key 指定 索引区间 内元素 ( ‘(’ 表示不包含,即 min < … < max) |
3、zrangebyscore key min max [limit start count] | 通过 score 返回 key 指定 索引区间 内元素 (从返回元素中 索引 start 开始截取 count 个) |
zrem key value1 [member …] | 移除 key 中的一个或多个元素 |
zcard key | 获取 key 的元素个数 |
zcount key minscore maxscore | 计算在 key 中指定区间分数的元素个数 |
zrank key value1 | 返回 key 中指定 value 的下标 |
zscore key value1 | 返回 key 中,value 的 score |
zrevrank key value1 | 返回 key 中指定 value 的逆序下标,原来按 score 增序排序 |
zrevrange key start stop [withscores] | 逆序返回 key 中指定索引区间内的成员,按 score 降序排序显示 |
zrevrangebyscore key maxscore minscore [withscores] | 逆序返回 key 中指定 score 区间内的成员,score从高到低排序 |
zincrby key increment member | 有序集合中对指定成员的分数加上增量 increment |
zinterstore destination numkeys key [key …] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
zlexcount key min max | 在有序集合中计算指定字典区间内成员数量 |
zrangebylex key min max [limit offset count] | 通过字典区间返回有序集合的成员 |
zremrangebylex key min max | 移除有序集合中给定的字典区间的所有成员 |
zremrangebyrank key start stop | 移除有序集合中给定的排名区间的所有成员 |
zremrangebyscore key min max | 移除有序集合中给定的分数区间的所有成员 |
zunionstore destination numkeys key [key …] | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
zscan key cursor [MATCH pattern] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
Redis的配置文件 Redis.conf (重中之重)
位置: 在Redis安装目录下的 redis.conf (具体按个人情况)
注意:首先一定要拷贝一份默认文件备用
-
单位
只支持bytes,不支持bit
对大小写不敏感
-
include 包含
可以包含其他配置文件
-
general 通用
-
daemonize no/yes
指定 Redis 是否要用守护线程的方式启动,默认 no
yes,Redis 会在后台运行,此时 Redis 将一直运行,除非手动kill该进程。同时将进程 pid 写入至 redis.conf 选项 pidfile 设置的 /var/run/Redis_pid.pid 文件中,也可以通过 pidfile 命令(pidfile 路径)来指定 pid 文件生成的位置。
no,当前界面将进入 Redis 的命令行界面,exit 强制退出或者关闭连接工具 (putty,xshell等) 都会导致 Redis 进程退出。
-
port 6379
指定 Redis 监听端口,默认端口为 6379
-
tcp-backlog
设置 tcp 的 backlog,默认 511,backlog 是一个连接队列
backlog 队列总和=未完成三次握手队列 + 已经完成三次握手队列
在高并发环境下,需要与一个高 backlog 值来避免慢客户端连接问题。注意 Linux 内核会将这个值减小到 /proc/sys/net/core/somaxconn 的值,所以需要确认增大 somaxconn 和 tcp_max_syn_backlog两个值来达到想要效果
-
network
绑定的主机地址
-
time out 300
当客户端闲置多长秒后关闭连接,如果指定为 0 ,表示关闭该功能
-
tcp-keepalive 60
类似于 心跳检测
单位为秒,设置为 0 表示不进行 keepalive 检测,建议设置为 60
-
loglevel
日志级别
-
logfile
日志记录方式,默认为标准输出
如果配置 Redis 为守护进程方式运行,并且配置为标准输出,则日志会发送给 /dev/null
-
syslog-enabled yes/no
是否把日志输出到 syslog 中 默认 no
-
syslog-ident redis
指定 syslog 里的日志标志 默认 redis
-
syslog-facility local0
指定 syslog 设备,值可以是 USER 或 LOCAL0 - LOCAL7
-
databases 16
数据库数量 默认16
-
-
snapshotting 快照 -------> 持久化之 RDB
-
security 安全
-
config get requirepass
在 Redis 中使用,查看是否设置了密码验证
通过 config Set requirepass “xxx” 来设置密码 (临时)
且设置密码后 每次操作必须通过验证才能进行其他操作 (故不建议设置)
1、AUTH xxx OK
2、config get requirepass (其他命令也需要先进行密码验证)
同理 config get dir 获取启动 Redis 的当前工作路径 即 本地数据库的存放目录(默认是当前路径)
-
-
limit 限制
-
maxclients 10000
最大连接用户 默认 0 表示无限制
-
maxmemory
最大内存限制
-
**maxmemory-policy **
内存过期和淘汰策略
- volatile-lru -> 使用 LRU 算法移除 key,只对设置了过期时间的 key 有效
- allkeys-lru -> 使用 LRU 算法移除 key
- volatile-random -> 在过期集合中移除随机的 key,只对设置了过期时间的 key 有效
- allkeys-random -> 移除随机的 key
- volatile-ttl -> 移除 TTL 值最小的 key,即那些最近要过期的 key
- noeviction -> 不进行移除;针对写操作,只返回错误信息
默认为:永不过期
-
maxmemory-samples 5
设置样本数量,LRU 算法和最小 TTL 算法都是非精确的算法,而是估计值,所以可以设计样本的大小,Redis 默认会检查值么多个 key 并根据算法选择其中的一个
-
-
append only mode 追加 -------> 持久化之 AOF
Redis 的持久化之 RDB
RDB —> Redis DataBase
-
在指定的时间间隔内将内存中的数据集快照写入磁盘,行话讲就是 Snapshot 快照,它恢复时是将快照磁盘文件直接读到内存里。
-
RDB 保存的就是 dump.rdb 文件
通过备份 dump.rdb 文件可以进行恢复
将备份文件 dump.rdb 移动到 Redis 安装路径并重新启动 即可
config get dir 获取路径
-
相关配置 就是 Snapshot 快照
-
Redis 自动保存dump.rdb 如果满足在规定时间内完成修改的次数
如:(多个的话,满足其中一个即可)
分别表示:1h 内至少 1 次修改、5min 内至少 100 次修改、60s 内至少10000次修改
空字符表示 禁用,停止所有 RDB 保存规则
1、Redis.conf 中 配置为控制符
2、命令:Redis-cli config Set save “”
-
save / bgsave —> 立即生成最新 dump.rdb 文件
save :阻塞当前进程,进行保存
bgsave:fork 一个后台进程异步执行,专门负责生成 rdb 文件,可以最大化 Redis 性能
执行 flushall 命令,也会产生 rdb 文件,但里面是空的,无意义
-
stop-writes-on-bgsave-error yes
表示当 RDB 持久化过程中出现错误时,是否停止写入操作。
yes,表示当 RDB 持久化出错时,Redis 会停止接受写入操作,默认值 yes
no,表示即使 RDB 持久化出错,Redis 仍然会继续接受写入操作。说明 不在乎数据不一致或者有其他方法发现和控制
-
rdbcompression yes
对于存储到磁盘中的快照,可以设置是否进行压缩存储。
yes,表示 Redis 会采用LZF算法进行压缩,默认值 yes
no,表示不采用
-
rdbchecksum yes
在存储快照后,还可以让 Redis 使用 CRC64 算法来进行数据校验,会增大约 10 % 性能消耗,默认 yes
-
dbfilename dump.rdb
RDB 保存的 rdb 文件名
-
dir ./
dump.rdb 路径
-
-
如果需要进行大规模数据的恢复(优),且对于数据恢复的完整性不是非常敏感(缺),那 RDB 方式要比 AOF 方式更加的高效。
-
在没有到达设置持久化时间间隔时宕机,最后一次持久化后的数据可能丢失(缺)。
回忆:查看 Redis 是否启动 ps -ef|grep Redis / lsof -i :6379
Redis 的持久化之 AOF
AOF —> Append Only File
以日志的形式来记录每个写操作,将 Redis 执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,Redis 启动之初会读取该文件重新构建数据。
换言之,Redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
-
相关配置 就是 append only mode 追加
-
appendonly no
是否启动 AOF 默认 no
-
appendfilename “appendonly.aof”
AOF 默认保存的就是 appendonly.aof 文件,一般不修改
通过备份 appendonly.aof 文件可以进行恢复,将备份文件 appendonly.aof 移动到 Redis 安装路径并重新启动 即可
config get dir 获取路径
-
appendfsync everysec / always / no
持久化策略:
-
always:同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性较好
-
everysec:异步持久化,最多每秒调用一次 fsync,如果一秒内宕机,就会有数据丢失,默认
-
no:不持久化
-
-
-
rewrite
是什么
-
Redis 中有缓存淘汰策略,因此会出现数据自动过期,另外也可能会被用户主动删除,导致 Redis 中数据变少了。
但是,虽然数据被删除了,其对应的写日志还在 AOF 日志文件中,并且因为 AOF 日志文件采用文件追加方式,因此这个文件会不断变大~~
-
为避免出现此种情况,Redis 新增了重写机制, 当 AOF 文件的大小超过所设定的阈值时,Redis 就会在自动后台启动 AOF 文件的 内容压缩, 只保留可以恢复数据的最小指令集。
-
可以使用命令 bgrewriteaof 手动触发重写操作
重写原理
- 从数据库中 读取 当前键值,然后 用一条命令 替代 键值对的多条命令,也可以恢复原数据状态,压缩了 AOF 文件
触发机制
-
Redis 会记录上次重写时的 AOF 大小
默认 是当 AOF 文件大小是上次 rewrite 后大小的一倍 且 文件大于 64M 时触发
-
no-appendfsync-on-rewrite no
重写时是否运用 appendfsync,用默认 no 即可,保证数据安全性
-
auto-aof-rewrite-min-size 64mb
设置重写的基准值,默认为 64 mb,指当文件大于 64mb 时触发
-
auto-aof-rewrite-percentage 100
设置重写的基准值,默认为 100,指当 aof 文件大小为上次 rewrite 后的一倍时触发
二者同时满足时 rewrite
-
AOF 与 RDB 总结
-
RDB 持久化 能够在指定时间间隔对数据进行快照存储
-
AOF 持久化 记录每次服务器的写操作,当服务器重启时会重新执行 AOF 文件中的写命令来恢复原始数据
-
AOF 命令 以 Redis 协议追加保存每次写操作到文件末尾
-
Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大
-
如果你希望你的数据在服务器运行的时候存在,也可以不使用任何持久化方式 — 只做缓存
-
同时开启两种持久化方式
- 此时,Redis 重启时会优先载入 AOF 文件来恢复原始数据,因为通常 AOF 文件保存的数据集 比 RDB 文件保存的数据集要完整(RDB可能会丢失最后一次持久化数据)
- RDB 数据不实时,同时使用两者时服务器重启也只会找 AO F文件。那要不要只使用 AOF 呢?作者建议不要,因为 RDB 更适合用于备份数据库(AOF在不断变化不好备份)快速重启(RDB 快于 AOF),而且不会有 AOF 可能潜在的bug,留着作为一个万一的手段。
若 AOF 文件数据异常,则无法启动 Redis,
只有 关闭AOF 或 AOF 文件不存在时,才会去使用 RDB 文件
需要修复 rof 文件,即可使用 (同理 修复 dump.rdb)
-
性能建议
-
因为 RDB 文件只用作后备用途,建议只在 Slave 上持久化 RDB 文件,而且只要 15分钟备份一次就够了,只保留 save 900 1 这条规则。
-
如果 Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单,只 load 自己的 AOF 文件就可以了。
代价一是体积较大的 AOF 文件带来了持续的 IO,二是 AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
因此,只要硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF 重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
-
如果不 Enable AOF,仅靠 Master-Slave Replication 实现高可用性也可以。能省掉
一大笔 IO,也减少了 rewrite 时带来的系统波动。代价是如果 Master/Slave 同时倒掉,会丢失 十几分钟 的数据,启动脚本也要比较两个 Master/Slave 中的 RDB 文件,载入较新的那个。新浪微博就选用了这种架构
-
Redis 的事务
可以一次执行多个命令,本质是一组命令的集合
单独的隔离操作:一个事物中的所有命令都会序列化,按顺序地串行执行而不会被其他命令插入加塞
没有隔离级别的概念:队列中的命令没有 exec 之前都不会实际的被执行,也就**不存在"事务内的查询要看到事务里的更新,在事务外查询不能看到"**这个让人万分头痛的问题
一个队列中、一次性、顺序性、排他性的执行一系列命令
命令 | 描述 |
---|---|
multi | 标记一个事务块的开始 |
exec | 执行所有事务块内的命令 |
discard | 取消事务,放弃执行事务块内的所有命令 |
watch key1 [key2 …] | 监视一个(或多个) key ,如果在事务执行之前这个(或这些)被监视的 key 被其他命令所改动,那么事务将被打断 |
unwatch | 取消 WATCH 命令对所有 key 的监视 |
-
multi / exec
-
discard
放弃事务,事务内的所有命令都没有执行
-
事务中,有一个命令格式错误,全部都不执行
命令格式错误 --> (相当于编译时就出错)
-
事务中**,命令格式不错,但命令执行出错**,该命令报错不执行,其余都执行
命令格式不错,但命令执行出错 -->(相当于可以正常编译但执行时逻辑错误)
即不保证原子性,Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
-
watch 监控
WATCH 使得 EXEC 命令需要有条件地执行:
事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足的话,事务就不会被执行
-
悲观锁 (Pessimistic Lock)
基本思想是,在获取数据之前,假设会有其他使用者对其进行修改操作,因此采取主动的措施将数据加锁,以阻止其他人的修改操作。这种悲观的态度认为数据冲突是不可避免的,因此在获取数据之前先锁定数据,以防止冲突的发生。
传统的关系型数据库里边就用到了很多这种锁机制,比如表锁,行锁等,读锁,写锁等,都是在做操作之前先上锁。
性能会有所下降
-
乐观锁(一般情况用的多)
基本思想是,假设多个用户对同一数据的修改操作是不会冲突的,因此不需要加锁限制,并且每次操作都会进行版本检查,以确保数据的一致性。
乐观锁的实现方式通常是在数据中添加一个版本号字段,每次修改操作都会更新该字段的值。**当用户要修改数据时,会先读取数据的当前版本号,然后在提交修改时再次检查版本号是否一致。**如果版本号一致,说明没有其他用户同时修改,可以正常提交修改;如果版本号不一致,则说明有其他用户修改了数据,当前用户的修改操作可能会覆盖其他用户的修改,此时需要回滚事务或重新尝试。
提交版本必须大于记录当前版本才能执行更新
适用于多读的应用类型,可以提高吞吐量
-
CAS(Check And Set)
案例:
先初始化信用卡可用余额和欠额
- 模拟正常无加塞篡改,先监控再开启 multi, 保证两笔金额变动在同一个事务内
-
模拟有加塞篡改,如果 key 被修改了,后面一个事务的执行会失效
-
-
unwatch
如果在监视后,发现已经被修改,需要 unwatch 取消监控,然后重新 watch 监控,再执行
-
一旦执行了 exec,之前加的监控锁都会被取消,不论是 执行成功还是失败
Redis 的发布订阅
进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
了解即可,实际企业使用的消息中间件不会使用 Redis的
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
命令 | 描述 |
---|---|
subscribe channel [channel …] | 订阅给定的一个或多个频道的信息 |
publish channel message | 将信息发送到指定的频道 |
psubscribe pattern [pattern …] | 订阅一个或多个符合给定模式的频道 |
pubsub subcommand [argument [argument …]] | 查看订阅与发布系统状态 |
punsubscribe [pattern [pattern …]] | 退订所有给定模式的频道 |
unsubscribe [channel [channel …]] | 指退订给定的频道 |
案例:
先订阅,发布后才能收到消息
可以一次性订阅多个,subscribe c1 c2 c3
消息发布,publish c2 hello-Redis
订阅所有,通配符*,pubscribe new* (任何频道消息都会受到)
收取消息,publish new1 Redis2024
Redis 的主从复制 (重中之重)
主机数据更新后根据配置和策略,自动同步到备机的 master/slaver 机制,Master 以写为主,Slave 为 Read Only
应用:读写分离,容灾恢复
-
复制原理
Slave 启动成功连接到 Master 后会发送一个 Sync 命令
Master 接到命令,启动后台的存盘进程,同时收集所有接收到用于修改数据集的命令, 在后台进程执行完毕之后,Master 传送整个数据文件到 Slave,完成一次完全同步
全量复制:Slave 在接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master 继续将新的所有收集到的修改命令依次传给 Slave,完成同步
但是只要是重新连接 Master,一次完全同步(全量复制)将被自动执行 -
配从(库)不配主(库)
从库配置:slaveof 主库IP 主库端口 (从库"认老大")
模拟:
-
开启多个终端模拟,拷贝多个配置文件,分别为进行配置
-
开启守护进程 daemonize yes
-
pidfilename 分别为 6379,6380,6381
-
port 6379 / 6380 / 6381
-
logfile 分别为 6379,6380,6381
-
dbfilename 分别为 6379,6380,6381
-
-
分别启动
- info replication 查看信息,都是 master
- 一主(写)二从(备份)
-
Master 负责写,Slave 为 Read Only
-
Slave 会备份 Master 的所有数据
-
Master 主动SHUTDOWN,所有 Slave 还是 Slave,原地待命
-
Master 再次登录,像什么都没发生一样,可以正常使用
-
-
Slave 主动 SHUTDOWN 与 Master 断开后,需要重新连接,除非配置进 Redis.conf 文件
Redis.conf 的 REPLICATION 模块
-
薪火相传
上一个 Slave 可以是下一个 Slave 的 Master,Slave 同样可以接收其他 Slaves 的连接和同步请求,那么该 Slave 作为了链条中下一个的 Master, 可以有效减轻 Master 的写压力
命令:slaveof 新主库IP 新主库端口
[81]中途变更转向:会清除之前的数据,重新建立拷贝最新的
-
反客为主
Master 主动 SHUTDOWN 后,其中一个 Slave 不再原地待命而是变成 Master
命令:slaveof no one
- 哨兵模式(sentinel)(重中之重)
“反客为主的自动版”,能够后台监控 Master 是否故障,如果故障了根据投票数自动将Slave转换为Master
模拟:
-
调整结构,6379 为 Master,80,81为 6379 的 Slave
-
在 /myRedis/ 下新建 sentinel.conf 文件,名字绝不能错 (根据自己的路径)
-
配置 sentinel.conf
sentinel monitor
-
master-group-name:被监控数据库名字(自己起)
-
ip:被监控数据库ip
-
port:被监控数据库端口
-
quorum:将 Master 判断为失效至少需要 quorum 个 Sentinel 同意,仅用于检测Master 连接失败。
如 Redis 集群中有5个 Sentinel 实例,若这里的票数是 2,表示有 2 个 Sentinel 认为 Master 挂掉才能被认为是真正的挂掉,此时会触发自动故障转移。
其中 Sentinel 集群中各个 Sentinel 也能通过 Gossip 协议互相通信。这意味着在故障期间,若大多数 Sentinel 进程间无法互相通信,自动故障转移将不会被触发(aka no failover in the minority partition)
-
-
启动哨兵
Redis-sentinel /myRedis/sentinel.conf(根据自己的路径)
-
原 Master 挂掉,自动选择新的 Master,并将其他 Slave 的 Master 更新
原 Master 再次登录后,会成为新 Master 的 Slave
-
复制的缺点
复制延时
由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master 同步到 Slave 机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。
ip:被监控数据库ip
port:被监控数据库端口
quorum:将 Master 判断为失效至少需要 quorum 个 Sentinel 同意,仅用于检测Master 连接失败。
如 Redis 集群中有5个 Sentinel 实例,若这里的票数是 2,表示有 2 个 Sentinel 认为 Master 挂掉才能被认为是真正的挂掉,此时会触发自动故障转移。
其中 Sentinel 集群中各个 Sentinel 也能通过 Gossip 协议互相通信。这意味着在故障期间,若大多数 Sentinel 进程间无法互相通信,自动故障转移将不会被触发(aka no failover in the minority partition)
-
启动哨兵
Redis-sentinel /myRedis/sentinel.conf(根据自己的路径)
[外链图片转存中…(img-Stn2crut-1705224165687)]
-
原 Master 挂掉,自动选择新的 Master,并将其他 Slave 的 Master 更新
[外链图片转存中…(img-npE76CJJ-1705224165687)]
原 Master 再次登录后,会成为新 Master 的 Slave
[外链图片转存中…(img-QTuv8E8J-1705224165687)]
-
复制的缺点
复制延时
由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master 同步到 Slave 机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。
最后:
欢迎指正不足或错误的地方。如果文章对你有所帮助,欢迎点赞支持。欢迎转载。