NoSQL Redis


Redis简介

NoSQL产品

  • RDBMS :MySQL,Oracle ,MSSQL,PG
  • NoSQL :Redis, MongoDB,列存储存储相关
  • NewSQL :分布式数据库架构(学习了MongoDB)

缓存产品介绍

  • Memcached
    优点:高性能读写、单一数据类型、支持客户端式分布式集群、一致性hash、多核结构、多线程读写性能高
    缺点:无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、架构扩容复杂度高

  • Redis
    优点:高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、支持分布式分片集群、单线程读写性能极高
    缺点:多线程读写较之Memcached慢

  • memcached与redis在读写性能的对比
    memcached:适合多用户访问,每个用户少量的rw(多核)
    redis:适合少用户访问,每个用户大量rw (单核,结合多实例)

  • Tair
    优点:高性能读写、支持三种存储引擎(ddb、rdb、ldb)、支持高可用、支持分布式分片集群、支撑了几乎所有淘宝业务的缓存
    缺点:单机情况下,读写性能较其他两种产品较慢

Redis功能介绍

  • 数据类型丰富
  • 支持持久化
  • 多种内存分配及回收策略
  • 支持事务
  • 消息队列、消息订阅
  • 支持高可用
  • 支持分布式分片集群
  • 缓存穿透\雪崩
  • Redis API

Redis使用场景

  • Memcached:多核的缓存服务,更加适合于多用户并发访问次数较少的应用场景
  • Redis:单核的缓存服务,单节点情况下,更加适合于少量用户,多次访问的应用场景。
  • Redis一般是单机多实例架构,配合redis集群出现。

一些概念

  • 缓存穿透:访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。
    解决方案:采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。
  • 缓存雪崩:大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
    解决方案:可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
  • 缓存击穿:一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
    解决方案:在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

Redis安装部署

  1. 下载解压
    [root@db01 ~]# wget http://download.redis.io/releases/redis-3.2.12.tar.gz
    [root@db01 ~]# tar xf redis-3.2.12.tar.gz 
    [root@db01 ~]# mv redis-3.2.12 /data/redis
    
  2. 编译安装
    [root@db01 redis]# cd /data/redis/
    [root@db01 redis]# make
    
  3. 添加环境变量
    [root@db01 redis]# vim /etc/profile
    export PATH=/data/redis/src:$PATH
    [root@db01 redis]# source /etc/profile
    
  4. 启动Redis
    [root@db01 redis]# redis-server &
    
  5. 连接测试
    127.0.0.1:6379> set name letty
    OK
    127.0.0.1:6379> get name
    "letty"
    

Redis基本管理

基础配置文件

  1. 编辑Redis配置文件
    [root@db01 redis]# mkdir /data/6379
    [root@db01 redis]# cat > /data/6379/redis.conf << EOF
    > daemonize yes                 # 是否后台运行
    > port 6379                     # 默认端口
    > logfile /data/6379/redis.log  # 日志文件
    > dir /data/6379                # 持久化文件存储位置
    > dbfilename dump.rdb           # RDB持久化数据文件
    > EOF
    
  2. 重启Redis
    # 方法一:连接关闭
    [root@db01 redis]# redis-cli
    127.0.0.1:6379> shutdown
    5089:M 09 Oct 23:42:55.331 # User requested shutdown...
    5089:M 09 Oct 23:42:55.331 * Saving the final RDB snapshot before exiting.
    5089:M 09 Oct 23:42:55.678 * DB saved on disk
    5089:M 09 Oct 23:42:55.678 # Redis is now ready to exit, bye bye...
    
    # 方法二:命令行关闭
    [root@db01 redis]# redis-cli shutdown
    
    # 启动redis
    [root@db01 redis]# redis-server /data/6379/redis.conf 
    [root@db01 redis]# netstat -lntup | grep 6379
    tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      5231/redis-server * 
    tcp6       0      0 :::6379                 :::*                    LISTEN      5231/redis-server * 
    
  3. 测试Redis
    [root@db01 redis]# redis-cli set num 10
    OK
    [root@db01 redis]# redis-cli get num
    "10"
    

redis安全配置

redis远程连接测试,报错

[root@db01 redis]# redis-cli -h 192.168.1.5 -p 6379
192.168.1.5:6379> set num 10
(error) DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients.In this mode connections are only accepted from the loopback interface...

redis默认开启了保护模式,只允许本地回环地址登录并访问数据库。

  • protected-mode yes/no (保护模式,是否只允许本地访问)

远程连接方案:Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

  1. bind:指定IP进行监听

    [root@db01 redis]# vim /data/6379/redis.conf 
    bind 192.168.1.5 127.0.0.1
    
  2. requirepass:增加密码

    [root@db01 redis]# vim /data/6379/redis.conf 
    requirepass 123456
    

验证bind & requirepass:

# 验证密码方法一
[root@db01 redis]# redis-cli -a 123456
127.0.0.1:6379> set num 10
OK

# 验证密码方法二
[root@db01 redis]# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set num01 11
OK

# 远程连接
[root@db01 redis]# redis-cli -a 123456 -h 192.168.1.5 -p 6379
192.168.1.5:6379> set num02 12
OK

在线查看和修改配置

127.0.0.1:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
  4) "123"
...

127.0.0.1:6379> CONFIG GET requirepass
1) "requirepass"
2) "123456"

127.0.0.1:6379> CONFIG GET r*
 1) "requirepass"
 2) "123456"
 3) "repl-ping-slave-period"
 4) "10"
 5) "repl-timeout"
 6) "60"
...

127.0.0.1:6379> CONFIG SET requirepass 123
OK

redis持久化(内存数据保存到磁盘)

RDB持久化

RDB(point-in-time snapshot)持久化:可以在指定的时间间隔内生成数据集的时间点快照

  • 优点:速度快,适合于用做备份,主从复制也是基于RDB持久化功能实现的
  • 缺点:会有数据丢失

RDB持久化核心配置参数:

vim /data/6379/redis.conf
dir /data/6379                 # 持久化文件存储位置
dbfilename dump.rdb            # RDB快照文件
save 900 1                     # 900秒(15分钟)内有1个更改,触发持久化
save 300 10                    # 300秒(5分钟)内有10个更改,触发持久化
save 60 10000                  # 60秒内有10000个更改,触发持久化

AOF持久化

AOF(append-only log file) 持久化:记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。

  • 优点:可以最大程度保证数据不丢
  • 缺点:日志记录量级比较大

AOF持久化核心配置参数

appendonly yes          # 是否打开aof日志功能
appendfsync always      # 每个命令都立即同步到aof  
appendfsync everysec    # 每秒同步一次
appendfsync no          # 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof

例如:

vim /data/6379/redis.conf
appendonly yes
appendfsync everysec 

RDB和AOF的区别?

  • RDB:基于快照的持久化,速度更快,一般用作备份,主从复制也是依赖于RDB持久化功能.
  • AOF:以追加的方式记录redis操作日志的文件。可以最大程度的保证redis数据安全,类似于mysql的binlog.

Redis数据类型

  • String:字符类型
  • Hash:字典类型
  • List:列表
  • Set:集合
  • Sorted set:有序集合

KEY的通用操作

  • 查看所有KEY名(生产环境避免使用)

    127.0.0.1:6379> set a 1
    OK
    127.0.0.1:6379> set b 1
    OK
    127.0.0.1:6379> set c 3 
    OK
    127.0.0.1:6379> keys * 
    1) "c"
    2) "b"
    3) "a"
    
  • 返回KEY所存储值的类型

    127.0.0.1:6379> TYPE a
    string
    127.0.0.1:6379> hset letty id 1
    (integer) 1
    127.0.0.1:6379> TYPE letty
    hash
    
  • 以秒\毫秒设定生存时间(默认不设定生存时间)
    为了防止Redis内存爆炸,可以对临时存在的键值对设置生存时间。另外生存时间要错开设置,不同的键值对设置不同的失效时间,以免同时失效引起服务器架构雪崩。

    # 以秒\毫秒设定生存时间
    127.0.0.1:6379> EXPIRE a 120
    (integer) 1
    
    # 以秒\毫秒为单位返回生存时间
    127.0.0.1:6379> TTL a
    (integer) 114
    
    # 取消生存时间设置
    127.0.0.1:6379> PERSIST a
    (integer) 1
    127.0.0.1:6379> TTL a
    (integer) -1                # -1表示永不过期
    
  • 删除一个key

    127.0.0.1:6379> DEL a 
    (integer) 1
    
  • 检查是否存在

    127.0.0.1:6379> EXISTS a
    (integer) 0
    
  • 变更KEY名

    127.0.0.1:6379> RENAME b aa
    OK
    

Strings

应用场景

  • session共享
  • 常规计数(如粉丝数、订阅数等)
  • key : value

计数器功能
例:每点一个关注,都执行incr num命令一次实时增加粉丝数量

127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
...

显示粉丝数量可以直接使用get获取

127.0.0.1:6379> get num 
"4"

每取消一个关注,都执行decr num命令一次实时减少粉丝数量

127.0.0.1:6379> decr num
(integer) 3
127.0.0.1:6379> decr num
(integer) 2

暗箱操作
一次直接增加一万个粉丝或减少一万个粉丝

127.0.0.1:6379> INCRBY num 10000
(integer) 10002
127.0.0.1:6379> get num
"10002"
127.0.0.1:6379> DECRBY num 10000
(integer) 2
127.0.0.1:6379> get num
"2"

Hash

应用场景

  • 存储部分变更的数据,如用户信息等
  • 最接近mysql表结构的一种类型
  • 主要可以用来做数据库缓存(了解MySQL 与 Redis 实时同步)

存数据

127.0.0.1:6379> hmset stu01 id 1 name letty age 22 gender f
OK
127.0.0.1:6379> hmset stu02 id 2 name harry age 20 gender m
OK

取数据

127.0.0.1:6379> HMGET stu01 id name age gender
1) "1"
2) "letty"
3) "22"
4) "f"
127.0.0.1:6379> HMGET stu02 id name age gender
1) "2"
2) "harry"
3) "20"
4) "m"

例:将MySQL数据库pressure中的前100行数据写入Redis中

mysql> show tables;
+--------------------+
| Tables_in_pressure |
+--------------------+
| t1000w             |
+--------------------+
1 row in set (0.00 sec)

mysql> desc t1000w;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type      | Null | Key | Default           | Extra                       |
+-------+-----------+------+-----+-------------------+-----------------------------+
| id    | int(11)   | YES  |     | NULL              |                             |
| num   | int(11)   | YES  |     | NULL              |                             |
| k1    | char(2)   | YES  |     | NULL              |                             |
| k2    | char(4)   | YES  |     | NULL              |                             |
| dt    | timestamp | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
5 rows in set (0.01 sec)

# 拼接hmset语句
mysql> select concat("hmset t_",id," id ",id," num ",num," k1 ",k1," k2 ",k2) from t1000w limit 100 into outfile '/tmp/hmset.txt';
Query OK, 100 rows affected (0.01 sec)

# 导入Redis
[root@db01 3307]# cat /tmp/hmset.txt | redis-cli -a 123456

List

应用场景

  • 消息队列系统

例如:SINA微博。在Redis中,最新微博ID使用了常驻缓存,这是一直更新的,但是做了限制不能超过5000个ID,因此获取ID的函数会一直询问Redis,只有在start/count参数超出了这个范围的时候,才需要去访问数据库。

模拟微信朋友圈

127.0.0.1:6379> LPUSH wechat "today is Monday"
(integer) 1
127.0.0.1:6379> LPUSH wechat "today is Tuesday"
(integer) 2
127.0.0.1:6379> LPUSH wechat "today is Wednesday"
(integer) 3
127.0.0.1:6379> LPUSH wechat "today is Thursday"
(integer) 4
127.0.0.1:6379> LPUSH wechat "today is Friday"
(integer) 5

[…monday ,…tuesday,…, …friday]
          0                   …                     -1

查询朋友圈信息

# 查询所有信息
127.0.0.1:6379> LRANGE wechat 0 -1
1) "today is Friday"
2) "today is Thursday"
3) "today is Wednesday"
4) "today is Tuesday"
5) "today is Monday"

# 查询最新信息
127.0.0.1:6379> LRANGE wechat 0 0
1) "today is Friday"

# 查询第二条信息
127.0.0.1:6379> LRANGE wechat 1 1
1) "today is Thursday"

Set

应用场景
案例:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

127.0.0.1:6379> sadd num0 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> sadd num1 5 6 7 8 9 10
(integer) 6

# 取并集
127.0.0.1:6379> SUNION num0 num1
 1) "1"
 2) "2"
 3) "3"
 4) "4"
 5) "5"
 6) "6"
 7) "7"
 8) "8"
 9) "9"
10) "10"

# 取交集
127.0.0.1:6379> SINTER num0 num1
1) "5"
2) "6"

# 取差集
127.0.0.1:6379> SDIFF num0 num1
1) "1"
2) "2"
3) "3"
4) "4"

Sorted Set

应用场景

  • 排行榜应用,取TOP N操作

该需求与上面需求的不同之处在于:上述操作以时间为权重,而这个是以某个条件为权重,比如按顶的次数排序,这时候就需要sorted set出马,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。

127.0.0.1:6379> ZADD topn 0 test01 0 test02 0 test03 0 test04 0 test05 0 test06
(integer) 6

# 增加次数
127.0.0.1:6379> ZINCRBY topn 100 test01
"100"
127.0.0.1:6379> ZINCRBY topn 10 test02
"10"
127.0.0.1:6379> ZINCRBY topn 1000 test03
"1000"
127.0.0.1:6379> ZINCRBY topn 1000 test04
"1000"
127.0.0.1:6379> ZINCRBY topn 10000 test05
"10000"
127.0.0.1:6379> ZINCRBY topn 100000 test06
"100000"

# 显示排行
127.0.0.1:6379> ZREVRANGE topn 0 -1 
1) "test06"
2) "test05"
3) "test04"
4) "test03"
5) "test01"
6) "test02"

Redis发布订阅

消息队列
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合、应用透明等问题,Redis就可以作为消息队列中间件。

Redis发布订阅:以Pub/Sub机制来看,更像是一个广播系统,多个Subscriber可以订阅多个Channel,多个Publisher可以往多个Channel中发布消息。可以简单的理解为:

  • Subscriber:收音机,可以收到多个频道,并以队列方式显示
  • Publisher:电台,可以往不同的FM频道中发消息
  • Channel:不同频里的FM频道

PUBLISH channel msg 将信息 message 发送到指定的频道 channel

SUBSCRIBE channel [channel …] 订阅频道,可以同时订阅多个频道

UNSUBSCRIBE [channel …] 取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道

PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所 有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有 以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类

PUNSUBSCRIBE [pattern [pattern …]] 退订指定的规则, 如果没有参数则会退订所有规则

PUBSUB subcommand [argument [argument …]] 查看订阅与发布系统状态

注意:使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,之前发送的不会缓存,必须Provider和Consumer同时在线。

# session1订阅频道
127.0.0.1:6379> SUBSCRIBE FM1024
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "FM1024"
3) (integer) 1

# session2发布消息
127.0.0.1:6379> PUBLISH FM1024 hello

测试图

Redis事务

Redis事务

  • Redis的事务是基于队列实现的,MySQL的事务是基于事务日志和锁机制实现的;
  • Redis是乐观锁机制。

开启Redis事务功能multi

multi           
# multi开始,command并没有真正执行,而是被放入同一队列中
command1      
command2
command3
command4
exec / discard
# 当执行exec时,对队列中所有的操作,要么全成功要么全失败
# 当执行discard时,直接丢弃队列中所有的命令,而不是做回滚

例如

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a 1 
QUEUED
127.0.0.1:6379> set b 2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK

Redis(Master-Replicaset)

Master-Replicaset原理

当一个redis服务器初次向主服务器发送salveof命令时,redis从服务器会进行一次全量同步:
图1

  1. 从库通过slaveof 192.168.1.5 6379命令,连接主库,并发送SYNC给主库;
  2. 主库收到SYNC,会立即触发BGSAVE,生成RDB快照,发送给从库;
  3. 从库接收RDB后会应用RDB快照;
  4. 主库会发送保存在缓冲区里的所有写命令,告诉从库可以进行同步;
  5. 从库执行这些写命令。

再此以后,主库只要发生新的操作,都会以命令传播的形式主动发送给从库(master执行过当前命令后,将当前命令发送给slave执行一遍,达成数据一致性)。

当主从同步断开重连之后会进行重新同步,重新同步分完全同步和部分同步,部分同步如下(断点重连):
部分同步

  1. 当从库断开重连后,会发送psync 命令给主库;
  2. 主库收到psync后会返回+continue,表示从库可以执行部分同步;
  3. 主库发送断开连接后的写命令给从库;
  4. 从库执行写命令。

主从数据一致性保证

min-slaves-to-write 1 保证至少一台从库与主库数据一致
min-slaves-max-lag 3 若从库与主库的网络延时超过了3s,则任务失败

问:主从中主库是否要开启持久化?

答:主库如果不开持久化,如果主库宕机数据丢失,再次开机会自动与从库同步,如此从库数据也会丢失。

Master-Replicaset实现

  1. 准备两个或两个以上的Redis实例

    [root@db01 ~]# mkdir /data/638{0..2}
    
    [root@db01 ~]# cat >> /data/6380/redis.conf <<EOF
    > port 6380
    > daemonize yes
    > pidfile /data/6380/redis.pid
    > loglevel notice
    > logfile "/data/6380/redis.log"
    > dbfilename dump.rdb
    > dir /data/6380
    > requirepass 123
    > masterauth 123
    > EOF
    
    [root@db01 ~]# cat >> /data/6381/redis.conf <<EOF
    > port 6381
    > daemonize yes
    > pidfile /data/6381/redis.pid
    > loglevel notice
    > logfile "/data/6381/redis.log"
    > dbfilename dump.rdb
    > dir /data/6381
    > requirepass 123
    > masterauth 123
    > EOF
    
    [root@db01 ~]# cat >> /data/6382/redis.conf <<EOF
    > port 6382
    > daemonize yes
    > pidfile /data/6382/redis.pid
    > loglevel notice
    > logfile "/data/6382/redis.log"
    > dbfilename dump.rdb
    > dir /data/6382
    > requirepass 123
    > masterauth 123
    > EOF
    
  2. 启动Redis

    [root@db01 ~]# redis-server /data/6380/redis.conf
    [root@db01 ~]# redis-server /data/6381/redis.conf
    [root@db01 ~]# redis-server /data/6382/redis.conf
    
  3. 开启Redis主从
    主节点:6380                从节点:6381、6382

    # 6381/6382
    [root@db01 ~]# redis-cli -p 6381 -a 123 SLAVEOF 127.0.0.1 6380
    OK
    [root@db01 ~]# redis-cli -p 6382 -a 123 SLAVEOF 127.0.0.1 6380
    OK
    
  4. 查询主从状态

    [root@db01 ~]# redis-cli -p 6380 -a 123 info replication
    # Replication
    role:master
    connected_slaves:2
    slave0:ip=127.0.0.1,port=6381,state=online,offset=57,lag=1
    slave1:ip=127.0.0.1,port=6382,state=online,offset=57,lag=0
    master_repl_offset:57
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:2
    repl_backlog_histlen:56
    
    [root@db01 ~]# redis-cli -p 6381 -a 123 info replication
    # Replication
    role:slave
    master_host:127.0.0.1
    master_port:6380
    master_link_status:up
    master_last_io_seconds_ago:2
    master_sync_in_progress:0
    slave_repl_offset:71
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_repl_offset:0
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
    

Redis Sentinel(哨兵)

Sentinel功能

主从切换技术在主服务器宕机后,需要手动把一台从服务器切换为主服务器,需要人工干预,还有一段时间的服务不可用。因此推荐使用哨兵模式。

Redis提供了哨兵的命令,哨兵是一个独立运行的进程,其功能如下:

  1. 通过发送命令监控Redis(包括主服务器和从服务器);
  2. 当哨兵监测到master宕机,自动选主并将slave切换到master,然后通过发布订阅模式通知其他从服务器,修改配置文件,让它们切换主机;
  3. 应用透明。

Sentinel搭建

  1. 编辑sentinel.conf配置文件

    [root@db01 ~]# mkdir /data/26380
    [root@db01 ~]# cd /data/26380/
    [root@db01 26380]# vim sentinel.conf
    port 26380
    dir "/data/26380"
    # mymaster为集群名称
    sentinel monitor mymaster 127.0.0.1 6380 1
    sentinel down-after-milliseconds mymaster 5000
    sentinel auth-pass mymaster 123 
    
  2. 启动哨兵

    [root@db01 26380]# redis-sentinel /data/26380/sentinel.conf  &>/tmp/sentinel.log &[1] 4755
    
  3. 停主库测试

    [root@db01 26380]# redis-cli -a 123 -p 6380 shutdown
    

    查看日志可以发现已经切换6381为master

    [root@db01 26380]# tail -5 /tmp/sentinel.log 
    4755:X 10 Oct 15:49:45.702 # +failover-end master mymaster 127.0.0.1 6380
    4755:X 10 Oct 15:49:45.702 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381
    4755:X 10 Oct 15:49:45.703 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381
    4755:X 10 Oct 15:49:45.703 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
    4755:X 10 Oct 15:49:50.764 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
    
  4. 恢复主库测试

    [root@db01 26380]# redis-server /data/6380/redis.conf
    

    查看日志,发现将6380作为从库加入到现主库中

    [root@db01 26380]# tail -5 /tmp/sentinel.log 
    4755:X 10 Oct 15:49:45.703 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381
    4755:X 10 Oct 15:49:45.703 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
    4755:X 10 Oct 15:49:50.764 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
    4755:X 10 Oct 15:55:45.010 # -sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
    4755:X 10 Oct 15:55:54.950 * +convert-to-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
    

Redis Cluster

Cluster介绍

Cluster
高性能

  1. 在多分片节点中,将16384个槽位,均匀分布到多个分片节点中;
  2. 存数据时,将key做crc16(key)运算,然后将运算结果和16384进行取模,得出槽位值(0-16383之间);
  3. 根据计算得出的槽位值,找到相对应的分片节点的主节点,存储到相应槽位上;
  4. 如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接切换至真正存储节点进行数据存储。

高可用
在搭建集群时,会为每一个分片的主节点,对应一个从节点,实现slaveof的功能,同时当主节点down,实现类似于sentinel的自动failover的功能。

  1. redis会有多组分片构成(3组);
  2. redis cluster 使用固定个数的slot存储数据(一共16384slot);
  3. 每组分片分得1/3 slot个数(0-5500 5501-11000 11001-16383);
  4. 基于CRC16(key) % 16384得到槽位号。

Cluster规划搭建

Cluster规划

6个Redis实例(一般放置到3台硬件服务器)      端口号:7000-7005

注意:一主一从的主从节点一般不放在同一个硬件服务器,这样即使一个物理硬件坏了,也不会造成数据丢失。

Cluster搭建

  1. 安装集群插件

    # epel源安装ruby支持
    [root@db01 ~]# yum install ruby rubygems -y
    # 查看gem源
    [root@db01 ~]# gem sources -l
    *** CURRENT SOURCES ***
    
    https://rubygems.org/        # 国外源
    # 配置国内源
    https://rubygems.org/
    [root@db01 ~]# gem sources -a http://mirrors.aliyun.com/rubygems/ 
    http://mirrors.aliyun.com/rubygems/ added to sources
    [root@db01 ~]# gem sources  --remove https://rubygems.org/
    https://rubygems.org/ removed from sources
    [root@db01 ~]# gem sources -l
    *** CURRENT SOURCES ***
    
    http://mirrors.aliyun.com/rubygems/
    # 安装使得ruby能够操纵redis的驱动
    [root@db01 ~]# gem install redis -v 3.3.3	
    
  2. 集群节点准备

    [root@db01 ~]# mkdir /data/700{0..5}
    [root@db01 ~]# cat > /data/7000/redis.conf <<EOF
    > port 7000
    > daemonize yes
    > pidfile /data/7000/redis.pid
    > loglevel notice
    > logfile "/data/7000/redis.log"
    > dbfilename dump.rdb
    > dir /data/7000
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    
    [root@db01 ~]# cat >> /data/7001/redis.conf <<EOF
    > port 7001
    > daemonize yes
    > pidfile /data/7001/redis.pid
    > loglevel notice
    > logfile "/data/7001/redis.log"
    > dbfilename dump.rdb
    > dir /data/7001
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    
    [root@db01 ~]# cat >> /data/7002/redis.conf <<EOF
    > port 7002
    > daemonize yes
    > pidfile /data/7002/redis.pid
    > loglevel notice
    > logfile "/data/7002/redis.log"
    > dbfilename dump.rdb
    > dir /data/7002
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    
    [root@db01 ~]# cat >>  /data/7003/redis.conf <<EOF
    > port 7003
    > daemonize yes
    > pidfile /data/7003/redis.pid
    > loglevel notice
    > logfile "/data/7003/redis.log"
    > dbfilename dump.rdb
    > dir /data/7003
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    
    [root@db01 ~]# cat >> /data/7004/redis.conf <<EOF
    > port 7004
    > daemonize yes
    > pidfile /data/7004/redis.pid
    > loglevel notice
    > logfile "/data/7004/redis.log"
    > dbfilename dump.rdb
    > dir /data/7004
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    
    [root@db01 ~]# cat >> /data/7005/redis.conf <<EOF
    > port 7005
    > daemonize yes
    > pidfile /data/7005/redis.pid
    > loglevel notice
    > logfile "/data/7005/redis.log"
    > dbfilename dump.rdb
    > dir /data/7005
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    
  3. 启动节点

    [root@db01 ~]# redis-server /data/7000/redis.conf 
    [root@db01 ~]# redis-server /data/7001/redis.conf 
    [root@db01 ~]# redis-server /data/7002/redis.conf 
    [root@db01 ~]# redis-server /data/7003/redis.conf 
    [root@db01 ~]# redis-server /data/7004/redis.conf 
    [root@db01 ~]# redis-server /data/7005/redis.conf 
    
    [root@db01 ~]# ps -ef | grep redis
    root       5236      1  0 16:30 ?        00:00:00 redis-server *:7000 [cluster]
    root       5238      1  0 16:30 ?        00:00:00 redis-server *:7001 [cluster]
    root       5242      1  0 16:30 ?        00:00:00 redis-server *:7002 [cluster]
    root       5246      1  0 16:30 ?        00:00:00 redis-server *:7003 [cluster]
    root       5251      1  0 16:30 ?        00:00:00 redis-server *:7004 [cluster]
    root       5256      1  0 16:30 ?        00:00:00 redis-server *:7005 [cluster]
    
  4. 将节点加入集群管理

    [root@db01 ~]# redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
    
  5. 集群状态查看

    # 集群主节点状态
    [root@db01 ~]# redis-cli -p 7000 cluster nodes | grep master
    38520cd483a791f510cafc0ba84f4c9e219b1950 127.0.0.1:7002 master - 0 1633854985580 3 connected 10923-16383
    e710e18bbfc97174c3bc8375a287136cb8fa0340 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
    364265dadb7ac31408ebb0939c928d8faebd4f02 127.0.0.1:7001 master - 0 1633854984569 2 connected 5461-10922
    # 集群从节点状态
    [root@db01 ~]# redis-cli -p 7000 cluster nodes | grep slave
    780b01a903618e377fe7320010edf89d7c8f2e54 127.0.0.1:7003 slave e710e18bbfc97174c3bc8375a287136cb8fa0340 0 1633854991204 4 connected
    01e914d87391807f813df24a2cc9200ae4e6a8f8 127.0.0.1:7004 slave 364265dadb7ac31408ebb0939c928d8faebd4f02 0 1633854990689 5 connected
    f7d6597c1b7d6d947c10d85c5c3974979c0b932f 127.0.0.1:7005 slave 38520cd483a791f510cafc0ba84f4c9e219b1950 0 1633854989970 6 connected
    

集群节点管理

增加节点
  1. 增加新的节点
    [root@db01 ~]# mkdir /data/700{6,7}
    # 编辑配置文件
    [root@db01 ~]# cat > /data/7006/redis.conf <<EOF
    > port 7006
    > daemonize yes
    > pidfile /data/7006/redis.pid
    > loglevel notice
    > logfile "/data/7006/redis.log"
    > dbfilename dump.rdb
    > dir /data/7006
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    [root@db01 ~]# cat >  /data/7007/redis.conf <<EOF
    > port 7007
    > daemonize yes
    > pidfile /data/7007/redis.pid
    > loglevel notice
    > logfile "/data/7007/redis.log"
    > dbfilename dump.rdb
    > dir /data/7007
    > protected-mode no
    > cluster-enabled yes
    > cluster-config-file nodes.conf
    > cluster-node-timeout 5000
    > appendonly yes
    > EOF
    # 启动节点
    [root@db01 ~]# redis-server /data/7006/redis.conf 
    [root@db01 ~]# redis-server /data/7007/redis.conf 
    
  2. 添加主节点到集群
    [root@db01 ~]# redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000
    
  3. 重新划分slot(节点必须分配槽位才能存储数据)
    [root@db01 ~]# redis-trib.rb reshard 127.0.0.1:7000
    ...
    How many slots do you want to move (from 1 to 16384)? 4096
    What is the receiving node ID? 7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500
    Source node #1:all
    ...
    
  4. 查看主节点状态
    [root@db01 ~]# redis-cli -p 7000 cluster nodes | grep master
    7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500 127.0.0.1:7006 master - 0 1633855850989 7 connected 0-1364 5461-6826 10923-12287
    38520cd483a791f510cafc0ba84f4c9e219b1950 127.0.0.1:7002 master - 0 1633855849465 3 connected 12288-16383
    e710e18bbfc97174c3bc8375a287136cb8fa0340 127.0.0.1:7000 myself,master - 0 0 1 connected 1365-5460
    364265dadb7ac31408ebb0939c928d8faebd4f02 127.0.0.1:7001 master - 0 1633855850479 2 connected 6827-10922
    
  5. 添加从节点
    redis-trib.rb add-node --slave --master-id 7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500 127.0.0.1:7007 127.0.0.1:7000
    
  6. 查看从节点状态
    [root@db01 ~]# redis-cli -p 7000 cluster nodes | grep slave
    752799ebc2f45c75e5e75bed385f734a7eff769f 127.0.0.1:7007 slave 7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500 0 1633855914654 7 connected
    780b01a903618e377fe7320010edf89d7c8f2e54 127.0.0.1:7003 slave e710e18bbfc97174c3bc8375a287136cb8fa0340 0 1633855913532 4 connected
    01e914d87391807f813df24a2cc9200ae4e6a8f8 127.0.0.1:7004 slave 364265dadb7ac31408ebb0939c928d8faebd4f02 0 1633855914244 5 connected
    f7d6597c1b7d6d947c10d85c5c3974979c0b932f 127.0.0.1:7005 slave 38520cd483a791f510cafc0ba84f4c9e219b1950 0 1633855915265 6 connected
    
删除节点
  1. 将删除节点的slot移动走

    [root@db01 ~]# redis-cli -p 7000 cluster nodes | grep master
    7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500 127.0.0.1:7006 master - 0 1633856335206 7 connected 5461-6826 10923-12287
    38520cd483a791f510cafc0ba84f4c9e219b1950 127.0.0.1:7002 master - 0 1633856335304 3 connected 12288-16383
    e710e18bbfc97174c3bc8375a287136cb8fa0340 127.0.0.1:7000 myself,master - 0 0 8 connected 0-5460
    364265dadb7ac31408ebb0939c928d8faebd4f02 127.0.0.1:7001 master - 0 1633856334289 2 connected 6827-10922
    
    # 先将0-1364的1365个节点还给7000
    [root@db01 ~]# redis-trib.rb reshard 127.0.0.1:7000
    How many slots do you want to move (from 1 to 16384)? 1365
    What is the receiving node ID? e710e18bbfc97174c3bc8375a287136cb8fa0340     # 7000ID
    Source node #1:7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500                     # 7006ID
    Source node #2:done
    ...
    # 再将5461-6826的1366个节点还给7001
    [root@db01 ~]# redis-trib.rb reshard 127.0.0.1:7000
    How many slots do you want to move (from 1 to 16384)? 1366
    What is the receiving node ID? 364265dadb7ac31408ebb0939c928d8faebd4f02.
    Source node #1:7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500
    Source node #2:done
    ...
    # 最后将10923-12287的1365个节点还给7002
    [root@db01 ~]# redis-trib.rb reshard 127.0.0.1:7000
    How many slots do you want to move (from 1 to 16384)? 1365
    What is the receiving node ID? 38520cd483a791f510cafc0ba84f4c9e219b1950
    Source node #1:7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500
    Source node #2:done
    
    # 再查看主节点状态
    [root@db01 ~]# redis-cli -p 7000 cluster nodes | grep master
    7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500 127.0.0.1:7006 master - 0 1633856474041 7 connected
    38520cd483a791f510cafc0ba84f4c9e219b1950 127.0.0.1:7002 master - 0 1633856472107 10 connected 10923-16383
    e710e18bbfc97174c3bc8375a287136cb8fa0340 127.0.0.1:7000 myself,master - 0 0 8 connected 0-5460
    364265dadb7ac31408ebb0939c928d8faebd4f02 127.0.0.1:7001 master - 0 1633856473632 9 connected 5461-10922
    
  2. 删除节点

    # 删除主节点
    [root@db01 ~]# redis-trib.rb del-node 127.0.0.1:7006 7b04c76eeb0939d6b04f7880cb3aa5ed7e5a2500   
    # 删除从节点
    [root@db01 ~]# redis-trib.rb del-node 127.0.0.1:7007 752799ebc2f45c75e5e75bed385f734a7eff769f
    

注意:删除master节点之前首先要使用reshard移除master的全部slot,然后再删除当前节点。

Redis的多API支持

redis单实例连接

  1. 启动redis实例
    [root@db01 ~]# redis-server /data/6379/redis.conf
    
  2. python3连接redis
    [root@db01 ~]# python3
    Python 3.6.8 (default, Nov 16 2020, 16:55:22) 
    [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import redis
    >>> r = redis.StrictRedis(host='192.168.1.5', port=6379, db=0,password='123456')
    >>> r.set('a', '1')
    True
    >>> r.get('a')
    b'1'
    

sentinel集群连接

  1. 开启sentinel集群
    [root@db01 ~]# redis-server /data/6380/redis.conf
    [root@db01 ~]# redis-server /data/6381/redis.conf
    [root@db01 ~]# redis-server /data/6382/redis.conf
    [root@db01 ~]# redis-sentinel /data/26380/sentinel.conf &
    
  2. python连接sentinel集群
    [root@db01 ~]# python3
    Python 3.6.8 (default, Nov 16 2020, 16:55:22) 
    [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from redis.sentinel import Sentinel
    >>> sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1) 
    >>> sentinel.discover_master('mymaster')  
    ('127.0.0.1', 6381)
    >>> sentinel.discover_slaves('mymaster')  
    [('127.0.0.1', 6380), ('127.0.0.1', 6382)]
    

读写分离配置

# 写节点
>>> master = sentinel.master_for('mymaster', socket_timeout=0.1,password="123")  
# 读节点
>>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1,password="123")  
# 读写分离测试
>>> master.set('test', '123')  
True
>>> slave.get('test')  
b'123'

Cluster集群连接

>>> from rediscluster import StrictRedisCluster  
>>> startup_nodes = [{"host":"127.0.0.1", "port": "7000"},{"host":"127.0.0.1", "port": "7001"},{"host":"127.0.0.1", "port": "7002"}]  

>>> rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True)  
>>> rc.set("a", "1")  
True  
>>> print(rc.get("a"))  
b'1'
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值