高性能 key-value 内存数据库 —— Redis
一、安装
下载安装包,解压
tar -zxvf redis-3.0.5.tar.gz
进入解压目录,编译,安装
cd redis-3.0.5
# 编译
make
# 安装(指定安装目录)
make PREFIX=/root/training/redis install
安装目录下的命令脚本如下
其作用如下:
指令 | 作用 |
---|---|
redis-benchmark | 压力测试工具 |
redis-check-aof | 检查AOF文件 |
redis-check-dump | 检查RDB文件 |
redis-cli | 客户端 |
redis-sentinel -> redis-server | 哨兵,实现了HA的功能 |
redis-server | 启动Redis服务器 |
修改配置文件(默认不存在,从编译目录中拷贝)
mkdir -p /root/training/redis/conf
cp redis-3.0.5/redis.conf /root/training/redis
进入 redis.conf,编辑修改
...
# redis是否在后台启动,默认为no
daemonize yes
...
启动 redis
bin/redis-server
看到 redis logo 启动成功,默认端口为 6379,如下图所示:
二、数据类型
三、事务与消息机制
1、基本概念
Redis 对事务的支持目前还比较简单。
Redis 只能保证一个 client 发起的事务中的命令可以连续地执行,而中间不会插入其他 client 的命令。由于 redis 是单线程来处理 client 的请求的所以做到这点是很容易的。一般情况下 redis 在接收到一个 client 发来的命令后会立即处理并返回处理结果,但是当一个 client 在一个连接中发出 multi 命令时,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当此连接接收到 exec 命令后,redis 会顺序地执行队列中的所有命令,并将所有命令的运行结果打包到一起返回给 client,然后连接就结束事务上下文。
2、特性和对比
redis 事务和数据库事务的区别
操作 | redis | mysql/oracle |
---|---|---|
开启事务 | multi | 自动开启事务(事务中的第一条DML) |
执行操作 | 指令 | DML语句 |
提交事务 | exec | commit |
回滚事务 | discard | rollback |
事务本质 | 将操作放入队列,执行批处理 | 将操作写入日志 |
Redis 不满足数据库的 ACID 特性:
- 原子性:原子性要求事务中的多条指令或操作要么全部执行成功,要么全部不执行(回滚),而 redis 事务中某条指令的执行失败不会影响其他指令的执行(PS.语法错误除外),因此可能出现部分指令成功部分指令失败的情况,不满足原子性要求。
- 隔离性:redis 处理指令和事务时是单线程处理的,事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,没有数据库隔离级别的概念,因此也不需要考虑 “事务内的查询要看到事务里的更新,在事务外查询不能看到” 这种问题。
- 一致性:原子性和一致性往往是孪生的,多个指令不能原子地被执行,自然不能满足一致性。
- 持久性:Redis 是一个基于内存的键值对 NOSQL 数据库,虽然有数据持久化的特性(AOF、RDB),但是跟事务的执行没有关系,是分离割裂开的,事务执行成功时,数据可能依然还在内存中,有丢失的风险。
演示:
tom 转账给 mike
四、锁机制
redis 事务不具备完善的 ACID 特性,除了说可能部分成功部分失败导致不满足原子性和一致性外,还因为没有完善的隔离性可能会导致一些问题。
现在有一个买票的事务场景,买票事务中,有两个指令,一个是扣钱,一个是票数减一,用 redis 事务来表达如下:
# 初始化数据
set ticket 1
set tom 100
# 开启事务
multi
decr tocket
decrby tom 100
exec
事务执行成功后,应该是票数减1,钱减100,但是如果在事务执行之前,有另一个指令线程提前执行了买票的操作,则当事务执行时,票数已为 0,这时候再执行事务买票票数会变成 -1,很明显的非法和异常数据。
在数据库中也存在这种并发时序的问题,它是通过隔离级别来实现不同事务之间不互相干扰的,如果涉及到对数据的修改,则会对修改的数据行加上一个行锁,这是个悲观锁,在加锁的事务释放锁之前,其他抢锁的事务会被阻塞。
而在 redis 中,通过 watch 命令实现的加锁机制是乐观锁,即不会加锁事务并不会锁死数据,但是如果其他事务在本事务执行之前提前修改了被加锁的数据,则加锁事务会回滚执行失败,这样就保证了数据的一致性。
改进一下上面的事务
五、消息机制
消息系统的类型
同步消息:需要等待对方的回答,eg:ATM
异步消息:不需要等待对方的聊天,eg:聊天室
消息传播的类型
Queue:队列,点对点
Topic:主题,广播
常见的消息系统
Redis:只支持 Topic
Kafka:只支持 Topic
JMS:Java Message Service 是 J2EE 中的一个标准(Queue、Topic 都支持),产品:weblogic
命令
publish:发布消息(指定频道)
subscribe:订阅消息(指定频道)
psubscribe:订阅消息(使用通配符指定频道)
常规消息发布订阅
使用通配符订阅
Redis 的消息机制存在一个严重的缺陷,假如消息发布者发布时没有订阅者或者订阅者发生故障,那么这条消息就永远丢失了,即使订阅者后面上线或者恢复正常。
六、持久化
redis 跟 memcache 相比的优点之一就是具有持久化特性,这使得它更有资格被称为一个 NOSQL 数据库,而 memcache 更多只能叫一个键值对缓存。
redis 提供了两种数据持久化机制:RDB、AOF。
1、RDB 持久化
RDB 持久化可以在指定 的时间间隔内生成数据集的时间快照点(point-in-time-snapshot)。
工作原理
每隔一段时间给内存照一个快照,将内存中的数据写入文件(rdb文件)。
配置(默认)
# save <seconds> <changes>
# 在多少秒内发生了多少次写操作,则执行一次RDB
save 900 1
save 300 10
save 60 10000
# 后台保存RDB报错时是否停止处理客户端的写请求
stop-writes-on-bgsave-error yes
# 保存数据到RDB文件时是否压缩数据
# 不建议开启压缩,会影响性能特别是数据恢复时
rdbcompression yes
# RDB文件名
dbfilename dump.rdb
# RDB文件所在目录
dir ./
优缺点
RDB 的优点是数据恢复快,缺点是在两次生成两次 RDB 快照之间,假如发生断电等故障,可能造成数据的丢失,所以要根据读写并发度和能够容忍的丢失数据对配置进行调整。
2、AOF 持久化
AOF(Append Only File)持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。
工作原理
记录操作的命令
相关配置(redis.conf)
# 是否启用 AOF
appendonly no
# AOF文件名
appendfilename "appendonly.aof"
# AOF同步频率
# (1)每条操作都写入日志
# appendfsync always
# (2)每秒写入一次
appendfsync everysec
# (3)由操作系统决定什么时候写入日志
# appendfsync no
# AOF重写时,是否不写入AOF
no-appendfsync-on-rewrite no
# 当AOF文件超过上次重写大小多大比例时,自动触发AOF重写
auto-aof-rewrite-percentage 100
# 当AOF文件超过多大时,触发AOF重写
auto-aof-rewrite-min-size 64mb
默认情况下,AOF是不开启的,将 appendonly 参数设置为 yes,重启 redis 服务即可,启动后就会在 redis 目录下生成 appendonly.aof 文件。
如果启用了 AOF,则 redis 启动时会优先读取 aof 文件。对于 redis 4.0 之前的版本来说,aof 必须是全量版本,因为启动时不会读取 rdb 文件的内容;对于 redis 4.0+ 版本,会结合两者的优势,利用 rdb 加载快的特点并周期性地进行更新数据快照,并且在两次 rdb 之间用 aof 来保存操作命令,这样最大程度地减少数据丢失的可能性。
aof 文件刚生成时大小为0,执行写操作指令后大小发生变化。
对于同步的频率,如果想获得最强的安全性,可以设置 appendfsync 参数为 always,表示每次发生写操作时都马上同步到 aof 文件中,但性能最低,一般不会设置为该值;设置为 no,表示写操作命令会放入缓冲区中,并由操作系统决定什么时候持久化到磁盘,这样可以让操作系统在相对空闲时才做该操作,但是不可预估,一旦服务器发生故障,很难预估丢失的数据规模和影响范围;默认且一般情况下,参数设置为 second,表示每秒同步一次,这样即使服务器发生故障,最多丢失一秒的数据。
重写
由于 AOF 会将所有的写操作命令写入到文件中,因此文件大小会膨胀得很快,重写(rewrite)可以有效压缩 AOF 文件大小,其原理就是对同个 key 的多次写操作,可以在 AOF 文件中被压缩为一条指令,比如:
set key v1
set key v2
...
set key v100
可以被压缩为 set key v100,这样就减小了 AOF 文件大小。
使用 redis 自带的压力测试工具来模拟演示 AOF 重写导致的文件大小的变化
# 表示执行10w个操作
bin/redis-benchmark -n 100000
文件大小膨胀到50多M,如果上述的 auto-aof-rewrite-min-size 参数是有效的,则再做10w笔压力测试将看到文件大小达到64M之后会被压缩。
优缺点
保证数据安全,但是恢复慢。
七、集群
1、主从复制
Redis 提供了复制功能来自动实现多台 redis 服务器的数据同步。
可以通过部署多台 redis,并在配置文件中指定这几台 redis 之间的主从关系,主负责写入数据,同时把写入的数据实时同步到从机器,这种模式叫主从复制,即 master/slave,并且 redis 默认 master 用于写,slave 用于读,向 slave 写数据会导致错误。
Redis 主从复制的架构有星型、线型两种。
两种架构各有优缺点。对于星型来说,由于每个 salve 到 master 的距离是相等的,所以数据同步的效率较高,但是当 master 故障时,选择哪个从节点称为新的主节点比较麻烦,因为不知道哪个 salve 中的数据滞后更小;对于线型来说,越往后的 salve 数据同步越滞后,数据同步效率低,但是 master 故障时只需要选最近的 slave 称为新的主节点即可。在实际应用中,星型架构更加普遍。
下面使用 192.168.190.111 机器,在上面启动三个 redis 服务端进程组成伪分布集群,模拟星型主从复制架构。使用不同端口号区分,master 使用 6379,两个 salve 使用 6380、6381。
master 负责写入数据和同步数据到 salve,并且不负责数据的持久化,因此持久化比较消耗性能;salve 负责从 master 同步数据,处理客户端的读请求,并且进行数据的持久化。
master 的配置 redis6379.conf 如下:
...
# 端口
port 6379
# 关闭RDB功能
#save 900 1
#save 300 10
#save 60 10000
# 关闭AOF功能
appendonly no
...
salve 1 的配置 redis6380.conf 如下:
...
# 端口
port 6380
dbfilename dump6380.rdb
appendfilename "appendonly6380.aof"
slaveof 192.168.190.111 6379
...
salve 2 的配置 redis6381.conf 如下:
...
# 端口
port 6381
dbfilename dump6381.rdb
appendfilename "appendonly6381.aof"
slaveof 192.168.190.111 6379
...
分别启动三个 redis 节点
[root@bigdata111 redis]# bin/redis-server conf/redis6379.conf
[root@bigdata111 redis]# bin/redis-server conf/redis6380.conf
[root@bigdata111 redis]# bin/redis-server conf/redis6381.conf
测试 master 写入的数据是否同步到 salve
尝试往从节点中写入数据会报错
在集群内部,主从节点通信的过程如下
2、分片
对 Redis 集群,客户端读请求会主要交给从节点处理,从节点有多个,客户端发起读请求时,直接与从节点通信不方便,所以中间还应该有个代理服务器来转发请求。
TwemProxy 就是一种 Redis 代理分片机制,由 Twitter 开源。Twemproxy 作为一种代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个 Redis 服务器,再原路返回,该方案很好地解决了单个 Redis 实例承载能力的问题。
下面演示 Twemproxy 的使用。
解压安装包
tar -zxvf nutcracker-0.3.0.tar.gz
编译安装
cd nutcracker-0.3.0
./configure --prefix=/root/training/proxy
make
make install
配置,在安装目录下创建配置目录,并从解压目录下拷贝配置文件
cd /root/training/proxy
mkdir conf
cp /root/tools/nutcracker-0.3.0/conf/nutcracker.yml conf/nutcracker.yml
修改配置文件
alpha:
listen: 127.0.0.1:22121
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- 192.168.190.111:6380:1
- 192.168.190.111:6381:1
beta:
listen: 192.168.190.111:22121
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- 192.168.190.111:6380:1
- 192.168.190.111:6381:1
检测配置文件格式是否有误
sbin/nutcracker -t conf/nutcracker.yml
启动 Twemproxy 服务
sbin/nutcracker -d -c conf/nutcracker.yml
启动客户端向22121端口发起请求
3、HA哨兵
Redis 从 2.4+ 版本开始就自带了一个实现 HA 的 sentinel,可以将 sentinel 看成是 redis 专用的 zookeeper。
哨兵的职责主要有:
- 监听所有 redis 节点的健康状况,所有 redis 节点会定时发送心跳给哨兵
- 监听 Master 的心跳,如果 Master 挂掉了,则负责切换,从所有从节点中选举出一个最合适的 Slave 称为新的 Master,并对集群发布广播,让剩下的 Slave 都成为新的 Master 的从节点
下面演示如何配置和使用哨兵:
将安装包解压目录下的 sentinel.conf 复制到安装配置目录下
cp sentinel.conf ~/training/redis/conf/
修改配置文件
...
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
#
# 最后一个参数表示,只要有指定数量的哨兵没有收到心跳,就认为该redis节点已经死掉了
#
# sentinel monitor mymaster 127.0.0.1 6379 2
sentinel monitor mymaster 127.0.0.1 6379 1
...
启动哨兵
bin/redis-sentinel conf/sentinel.conf
现在通过杀死主节点进程的方式模拟主节点故障,触发哨兵选举新主节点的过程。默认情况下,需要 30s 的超时时间哨兵才会正式认定主节点故障,
可以看到哨兵认定主节点故障后,触发了选举,最终选举 6381 的从节点为新的节点,并且让 6280 从节点认定其为新的主节点。
【相关参数】
# 哨兵监听器:主节点IP、端口、节点失效法定哨兵数
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 192.168.190.111 6379 1
# 设置连接 master 和 slave 时的密码
# sentinel auth-pass <master-name> <password>
# 主节点失效时间
# sentinel down-after-milliseconds <master-name> <milliseconds>
# 指定在发生 failover 主备切换时最多可以有多少个 salve 同时对 master 进行
# sentinel parallel-syncs <master-name> <numslaves>
4、RedisCluster
Redis Cluster 是 redis 的分布式解决方案,在 Redis 3.0 版本时推出,有效解决了 Redis 分布式方面的需求,当遇到单机内存、并发、流量等瓶颈时,可以使用 Cluster 架构达到负载均衡的目的。
为什么需要 Redis Cluster?
- 高可用
- 性能容量
- 并发性能问题:redis 单实例10w并发有上限
Redis Cluster 优势
- 官方推荐
- 去中心化,集群最大可增加1000个节点,性能随节点增加而线性扩展
- 管理方便,后续可自行增加或摘除节点,移动分槽等等
- 简单易上手
Redis Cluster 架构
分布式数据库首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责数据的一个子集。常用的分区规则有哈希分区和顺序分区。
Redis Cluster 采用哈希分区规则,采用虚拟槽分区,所有的键根据哈希函数映射到0~16383,计算公式:slot = CRC16(key) & 16383。每个节点负责维护一部分槽以及槽所映射的键值数据。
部署 Redis Cluster
这里使用6个节点来构建集群,由于使用同一台服务器,所以使用不同端口,架构图如下:
(1)手动创建
① 安装Ruby环境和Ruby Redis接口**
由于创建和管理需要使用到redis-trib 工具,该工具位于 Redis 源码的 src 文件夹中, 它是一个 Ruby 程序, 这个程序通过向实例发送特殊命令来完成创建新集群,检查集群,或者对集群进行重新分片(reshared)等工作,所以需要安装Ruby环境和相应的Redis接口�。
安装 ruby 环境
yum -y install ruby
安装 Redis ruby 接口
gem install redis-3.0.5.gem
② 节点配置启动
创建6个配置文件,以 redis-cluster-6379.conf 为例,将相关配置项修改如下:
...
# 端口
port 6379
# 启用集群
cluster-enabled ye
# 在redis安装根目录下创建nodes目录
cluster-config-file nodes/nodes-6379.conf
# RDB
dbfilename dump6379.rdb
# AOF
appendfilename "appendonly6379.aof"
启动 Redis 实例
bin/redis-server conf/redis-cluster-6379.conf
bin/redis-server conf/redis-cluster-6380.conf
bin/redis-server conf/redis-cluster-6381.conf
bin/redis-server conf/redis-cluster-6382.conf
bin/redis-server conf/redis-cluster-6383.conf
bin/redis-server conf/redis-cluster-6384.conf
③ 创建集群主从节点
将 redis-trib 工具复制到安装目录中
cp redis-3.0.5/src/redis-trib.rb ~/training/redis/bin
设置主节点从节点数为1,为主节点设置从节点
bin/redis-trib.rb create --replicas 1 192.168.190.111:6379 192.168.190.111:6380 192.168.190.111:6381 192.168.190.111:6382 192.168.190.111:6383 192.168.190.111:6384
④ 测试集群
由于 key "Tom" 计算出来的槽为8919,属于6380的主节点处理,所以请求被重定向到6380
(2)自动创建
使用的脚本位于安装包解压目录中
cp redis-3.0.5/utils/create-cluster/create-cluster ~/training/redis/bin
修改 create-cluster 脚本的路径
配置文件最前面的设置如下
# Settings
# 节点端口从30000开始
PORT=30000
TIMEOUT=2000
# 集群节点数为6个
NODES=6
# 6个节点中,3个是主节点,3个是从节点
REPLICAS=1
启动和创建redis集群
bin/create-cluster start
bin/create-cluster create