Redis数据库

Redis是什么数据库?

Redis概述

Redis是一个非常快速的、开源的、使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型(RDB和AOF)、(NoSQL)非关系类型的、Key-Value数据库,并提供多种语言的API。

Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。

Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。

Redis主要特点

  • 基于键值对的数据结构:为值提供五种可能的数据类型:字符串(string),列表(list),集合(set),哈希(hash)和有序集合(sortset)。提供了这些数据类型独有的操作,并且具有记录良好的时间复杂度(Big 0表示法)
  • 高性能:由于内存特性(Redis 所有数据都存放在内存中),项目维护者将复杂性保持在最低限度的承诺以及基于单线程模型法,Redis在读写方面具有出色的性能。
  • 没有依赖关系的清理轻量级:用ANSI C编写,没有外部依赖关系。适用于所有POSIX环境。
  • 高可用性:内置支持异步,非阻塞,主从复制,以确保数据的高可用性目前有一种名为Redis Sentinel的高可用性解决方案目前可以使用,但仍被视为正在进行的工作。

Redis应用场景

  1. 缓存-键过期时间

    缓存 session 会话

    缓存用户信息,找不到再去 mysql 查,查到然后回写到redis

    优惠卷过期时间

  2. 排行榜-列表&有序集合

    热度排名排行榜

    发布时间排行榜

  3. 计数器应用-天然支持计数器

    帖子浏览数

    视频播放次数

    商品浏览数

  4. 社交网络-集合

    踩/赞,粉丝,共同好友/喜好,推送,打标签

  5. 消息队列系统

    配合elk实现日志收集

Redis部署

  • centos7镜像阿里源
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

目录规划

### redis 下载目录
/data/
### redis 安装目录
/data/redis
### redis 数据目录
/data/redis_{PORT}/redis_{PORT}.rdb
### redis 运维脚本
/root/scripts/redis_shell.sh

安装命令

官网:https://redis.io/

### 编辑 hosts 文件
[root@db01 ~]# vim  /etc/hosts
10.0.0.51 db01
10.0.0.52 db02
10.0.0.53 db03

yum -y install gcc automake autoconf libtool make
make distclean
mkdir -p /data/
mkdir -p /data/redis_6379
cd /data/
wget http://download.redis.io/releases/redis-3.2.12.tar.gz
tar  -xf redis-3.2.12.tar.gz
mv  /data/redis-3.2.12/ /data/redis
cd /data/redis
make && make install

### 环境变量
vim  /etc/profile
### 添加以下一行
export PATH=/data/redis/src:$PATH
### 生效配置
source /etc/profile
  • 添加配置文件
cat > /data/redis_6379/redis.conf<<EOF
daemonize yes
bind 10.0.0.51 127.0.0.1
port 6379
logfile /data/redis_6379/redis.log
pidfile /var/run/redis6379.pid
databases 16
dir /data/redis_6379
dbfilename redis_6379.rdb
EOF
  • 连接测试
### 进入后台启动
redis-server &
### 连接测试
redis-cli
127.0.0.1:6379> set num 10
OK
127.0.0.1:6379> get num
"10"

启动关闭服务

  1. 文件启动和关闭

注:前提将指的的配置文件创建好

### 启动
[root@db01 ~]# redis-server .data/redis_6379/redis.conf
[root@db01 ~]# ps -ef | grep redis
[root@db01 ~]# netstat -lntup
### 关闭
[root@db01 ~]# redis-cli shutdown
  1. systemctl管理redis启动、停止、开机启动

用service来管理服务的时候,是在/etc/init.d/目录中创建一个脚本文件,来管理服务的启动和停止,在systemctl中,也类似,文件目录有所不同,在/lib/systemd/system目录下创建一个脚本文件 redis.service,里面的内容如下:

cat > /lib/systemd/system/redis6379.service <<EOF
[Unit]
Description=Redis persistent key-value database
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/redis-server /data/redis_6379/redis.conf --supervised systemd
ExecStop=/usr/local/bin/redis-cli -h -p 6379 shutdown
Type=forking
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.targe
EOF
  • 创建用户
groupadd redis
useradd redis -M -g redis -s /sbin/nologin
chown -R redis.redis /data/redis_*

注:Type类型设置为forking会导致当我执行systemctl命令后shell阻塞在那里,没有像平时执行命令那样自动结束(只能自己按Ctrl+C强制结束)systemctl status redis.service 查看程序发现目标程序启动是成功的, 但状态为activating (start)而不是activating (running)态

Type

介绍

Type=oneshot

这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。

Type=notify

与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。

Type=dbus

若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。

Type=idle

systemd会等待所有任务处理完成后,才开始执行 idle 类型的单元。其他行为与 Type=simple 类似。

Type=forking

systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便 systemd 能够跟踪服务的主进程

Type=simple

(默认值) systemd认为该服务将立即启动。服务进程不会 fork 。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket 激活型。

  • 创建软连接

注:设置开机自启

ln -s /lib/systemd/system/redis6379.service  /etc/systemd/system/multi-user.target.wants/redis6379.service
  • 开机启动
创建完redis.service后
systemctl daemon-reload    --更新systemctl
systemctl start redis
systemctl restart redis
systemctl stop redis

##systemctl 进入redis
redis-cli -p 6379

配置文件说明

### 以守护进程模式开启
daemonize yes
### 绑定的主机地址
bind 10.0.0.51
### 监听端口
port 6379
### pid文件和log文件的保存地址
pidfile /opt/redis_6379/pid/redis_6379.pid
logfile /opt/redis_6379/log/redis_6379.log
### 设置数据库的数量,m默认数据库为0
database 16
### 指定本地持久文件的文件名,默认是dump.rdb
dbfilenam dump.rdb
### 本地数据库的目录
dir /data/redis_6379

redis全局操作命令

写入key

set k1 v1
set k2 v2
set k3 v3

查看所有的key

注:线上禁止使用!

127.0.0.1:6379>  keys *
1) "k3"
2) "k2"
3) "k1"

查看有多少key值,是估值

127.0.0.1:6379>  DBSIZE
(integer) 3

注:返回1,存在估值,返回 0 则不存在

查看是否存在这个KEY

127.0.0.1:6379> EXISTS k1
(integer) 1
127.0.0.1:6379> EXISTS k1 k2 k3
(integer) 3
127.0.0.1:6379> EXISTS k1 k2 k4
(integer) 2
#这里实际就创建了两个key值,没有k4,所以显示2

状态码:

  • 0:表示这个key不存在

  • 1:表示这个key存在

  • N:表示有N个key存在

删除key

127.0.0.1:6379> DEL k1
(integer) 1
127.0.0.1:6379> DEL k2 k3
(integer) 2

状态码:

  • 0:表示这个key不存在

  • 1:表示这个key存在,并且删除成功

  • N:表示有多个key存在,并且删除N个key

键过期

# 设置过期时间
127.0.0.1:6379> EXPIRE k1 100
(integer) 1

# 取消过期时间
127.0.0.1:6379> PERSIST K1
(integer) 1

状态码:

  • 0:表示这个key不存在

  • 1:表示这个key存在,并且设置过期之间成功

查看key是否过期

127.0.0.1:6379> TTL k1
(integer) 96
127.0.0.1:6379> TTL k1
(integer) 95
127.0.0.1:6379> TTL k1
(integer) 94
127.0.0.1:6379> TTL k1
(integer) 93

注:过期后的key直接删除

状态码:

  • -1:这个key存在,并且永不过期

  • -2:这个key不存在

  • N:这个key存在,并且在N秒后过期

字符串操作

  1.设置一个key

127.0.0.1:6379> set k1 v1
OK

  2.查看一个key

127.0.0.1:6379> get k1
"v1"

  3.设置多个key

127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK

  4.查看多个key

127.0.0.1:6379> MGET k1 k2 k3
1) "v1"
2) "v2"
3) "v3"

  5.天然计数器

#加1:
127.0.0.1:6379> set k1 1

##进行+1操作
127.0.0.1:6379> INCR k1
(integer) 2
127.0.0.1:6379> INCR k1
(integer) 3
127.0.0.1:6379> INCR k1
(integer) 4

#查看
127.0.0.1:6379> get k1
"4"

#加N:
127.0.0.1:6379> INCRBY k1 100
(integer) 104
#减1:
127.0.0.1:6379> INCRBY k1 -1
(integer) 103
减N:
INCRBY K1 -N

列表操作

  1.插入列表

## LPUSH:从列表左侧插入数据
## RPUSH:从列表右侧插入数据

127.0.0.1:6379> LPUSH list1 A
127.0.0.1:6379> LPUSH list1 B
127.0.0.1:6379> LPUSH list1 C
127.0.0.1:6379>  RPUSH list1 D

## 查看列表的长度:
127.0.0.1:6379> LLEN list1
(integer) 4

## 查看列表的内容:
127.0.0.1:6379> LRANGE list1 0 -1
1) "C"
2) "B"
3) "A"
4) "D"

## 删除列表元素
LPOP:从列表左侧删除
EPOP:从列表右侧删除

127.0.0.1:6379> LPOP list1
"C"
127.0.0.1:6379> RPOP list1
"D"

## 删除列表内容
127.0.0.1:6379> DEL list1
(integer) 1

哈希类型

Redis hash 是一个 string 类型的field 和 value 的映射表,hash 特别适合用于存储对象。

Redis 中每个 hash 可以存储40多亿键值对。

哈希:

  • Hash看起来就像一个'hash'的样子.由键值对组成.

  • HMSET指令设置hash中的多个域.

  • HGET取回单个域.

  • HMGET取回一系列的值.

## 生成一个hash类型
127.0.0.1:6379> HMSET user:1 name xiaozhang job it age 28
127.0.0.1:6379> MHSET user:2 name abc job it age 18
127.0.0.1:6379> MHSET user:3 name def job it age 19

## 查看hash里的多个值
127.0.0.1:6379> MHGET user:1 name age job
1) "xiaozhang"
2) "28"
3) "it"


## 查看hash里的所有的值
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "xiaozhang"
3) "job"
4) "it"
5) "age"
6) "28"
  • mysql数据和redis哈希对比:
user表:
uid  name          job  age 
1     xiaozhang  it   28 
2     xiaoya      it   28 
3     yazhang      it   28 

mysql查询数据
select * from user where id=3

redis缓存mysql数据
名字        key1    k1值             key2    k2值        key3    k3值
uid:1      name      xiaozhang       job        it        age        28
uid:2      name      xiaoli       job        it        age        28
uid:3      name      xiaotian       job        it        age        28

集合

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

集合通俗来讲就像qq上的共同好友

## 创建集合
127.0.0.1:6379> SADD set1 1 2 3
(integer) 3
127.0.0.1:6379> SADD set2 1  3 5 7 
(integer) 5

## 查看集合的成员:
127.0.0.1:6379> SMEMBERS set1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SMEMBERS set2
1) "1"
2) "3"
3) "5"
4) "7"

## 查看集合的差集,以前面一个差集为基准对比后面的,前面有,后面没有则选出来
127.0.0.1:6379> SDIFF set1 set2
1) "2"
127.0.0.1:6379> SDIFF set2 set1
1) "5"
2) "7"

## 查看集合的交集
127.0.0.1:6379> SINTER set1 set2
1) "1"
2) "3"

## 查看集合的并集
127.0.0.1:6379> SUNION set1 set2
1) "1"
2) "2"
3) "3"
4) "5"
5) "7"

注:集合不允许出现重复的值

有序集合

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

应用场景:排行榜应用

  • 国家排名例子:
127.0.0.1:6379> ZADD country 0 china 0 usa 0 japan 0 uk
(integer) 4
127.0.0.1:6379> ZINCRBY country 10000 china
"10000"
127.0.0.1:6379> ZINCRBY country 100 usa
"100"
127.0.0.1:6379> ZINCRBY conurty 1000 japan
"1000"
127.0.0.1:6379> ZINCRBY country 1000000 uk
"1000000"

127.0.0.1:6379> ZREVRANGE country 0 -1
1) "uk"
2) "china"
3) "usa"
4) "japan"

redis 消息模式

介绍

消息模式是为了帮助解决在架构中,资源有效利用方面提供有效的协调。

redis 的消息模式有两种形式:消息队列发布订阅

发布订阅功能

原理图:

pulisher 发布者

channel 频道

subscriber 订阅者

命令:

命令 解释
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同时在线。

发布订阅的例子:

## 窗口一:
127.0.0.1:6379> SUBSCRIBE taobao
## 窗口二:
127.0.0.1:6379> PUBLISH taobao "hello word"

订阅多频道:

## 窗口一:
127.0.0.1:6379> PSUBSCRINE  tianmao*
## 窗口二:
127.0.0.1:6379> PUBLISH tian* "hello china"

redis 事务

redis事务可以一次执行多个命令,并且带有一下三个重要保证:

  • 批量操作在发送EXEC命令前被放入队列缓存。

  • 收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。

  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历一下三个阶段:

  • 开始事务。

  • 命令入列。

  • 执行事务。

redis事务和MySQL事务的区别:

  1. redis的事务是基于:队列实现的,redis是乐观锁机制,仅实现原子性的保证,属于弱事务支持。

  2. MySQL的事务时基于:事务日志、悲观锁机制、MVCC、ISOLASTION等机制一起保证,强事务支持。

底层原理图:

注:命令相同,同时分两个消息队列,谁先执行EXEC就算谁的。

  • 开启事务功能时(multi)
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

127.0.0.1:6379> multi 
OK
127.0.0.1:6379> set x 10
QUEUED
127.0.0.1:6379> set y 20
QUEUED
127.0.0.1:6379> DISCARD
OK
  • redis乐观锁实现

例子:买票,比如我们有两个人同时去买同一张从北京到上海的机票都放入搜藏了,但是谁先下单,这张机票就是谁的。

session 1: 
watch ticket 
multi  
decr ticket
exec 

session 2: 
multi 
decr ticket
exec

redis持久化

RDB和AOF优缺点

  • RDB持久化:

    可以在指定的时间间隔内生成数据集的 时间点快照(point-in-time snapshot),新快照会覆盖老快照。

    优缺点:恢复速度快。适合用于做备份,主从复制基于RDB持久化功能实现。

    缺点:可能会丢失数据

  • AOF持久化(append-only log file):

    类似于MySQL的binlog,重写,1秒写一次或者每次操作都记录

    优点:安全,有可能会丢失1秒数据

    缺点:文件比较大,恢复速度慢

配置RDB

vim /data/redis_6379/redis.conf
save 900 1
save 300 10
save 60 10000
dir /data/redis_6379/
dbfilename redis_6379.rdb

配置分别表示:
900秒(15分钟)内有1个更改
300秒(5分钟)内有10个更改
60秒内有10000个更改

结论:

  1. 执行shutdown的时候,内部会自动执行bgsave,然后在执行shutdown

  2. pkillkillkillall都类似于执行shutdown命令,会触发bgsave持久化

  3. 恢复的时候,rdb文件名称要和配置文件里写的一样

  4. 如果没有配置save参数,执行shutdown不会自动bgsave持久化

  5. 如果没有配置save参数,可以手动执行bgsave触发持久化保存

  • 常用命令:
ll /data/redis_6379/ 
cat /opt/redis_6379/conf/redis_6379.conf 
vim /opt/redis_6379/conf/redis_6379.conf 
pkill redis
redis-server /data/redis_6379/redis.conf 
redis-cli -h db01
redis-cli -h db01 shutdown
bash for.sh

配置AOF

AOF持久化配置

appendonly yes
appendfsync always
appendfsync everysec
appendfsync no

vim /data/6379/redis.conf
appendfilename "redis_6379.aof"
appendonly yes
appendfsync everysec  
  • 面试:

redis 持久化方式有哪些?有什么区别?

  • rdb:基于快照的持久化,速度更快,一般用作备份,主从复制依赖于rdb持久化功能。

  • aof:一追加的方式记录redis操作日志的文件。可以最大程度的保证redis数据安全,类似于MySQL的binlog

save bgsave区别?

共同点:都能实现RDB持久化功能

不同点:

  • SAVE:前台,阻塞redis正常写入,直到持久化完成。

  • BGSAVE:后台,开启子线程,异步的持久化功能,不会阻塞redis正常写入

  • 实验:

    如果aof和rdb文件同时存在,redis会如何读取:

实验步骤:

  1. 插入一条数据

    aof:有记录

    rdb:没有记录

  2. 复制到其他地方

  3. 把redis停掉

  4. 清空数据目录

  5. 把数据文件拷贝过来

    aof:有记录

    rdb:没有记录

  6. 启动redis

  7. 测试,如果有新插入的数据,就表示读取的是aof,如果没有表示读取的是rdb

实验结论:如果2中数据格式都存在,优先读取aof

redis主从复制

原理

  1. 副本库通过slaveof 10.0.0.51 6379命令,连接主库,并发送SYNC给主库

  2. 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库

  3. 副本库接受后会应用RDB快照

  4. 主库会陆续将中间产生的新的操作,保存并发送给副本库

  5. 到此,我们主从复制集就正常工作了

  6. 在此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库

  7. 所有复制相关信息,从info信息中都可以查到。即使重启任何节点,它的主从关系都在。

  • 2.8+ 版本的PSYNC功能

  1. 如果发生主从关系断开时,主从数据没有任何损失,在下次重连之后,从库发送SYNC给主库

  2. 主库只会将从库缺失的数据同步给从库应用,达到快速恢复主库的目的

原理图:

  • 主从数据一致性保证
min-slaves-to-write 1
min-slaves-max-lag  3 ---三秒内证明从库还活着
  • 主库是否要开启持久化

如果不开,有可能主库重启操作,造成所有主从数据丢失

主从复制实现

实例规划:

节点 port 数据目录
主节点 6380 6380 /data/redis_6380
从节点 6381 6381 /data/redis_6381
从节点 6382 6382 /data/redis_6382

注:准备两个或两个以上redis实例

  1.准备实例

mkdir /data/redis_638{0..2}
#配置文件示例:
cat >> /data/redis_6380/redis.conf <<EOF
port 6380
daemonize yes
pidfile /data/redis_6380/redis.pid
loglevel notice
logfile "/data/redis_6380/redis.log"
dbfilename dump.rdb
dir /data/redis_6380
requirepass 123
masterauth 123
EOF

cat >>   /data/redis_6381/redis.conf <<EOF
port 6381
daemonize yes
pidfile /data/redis_6381/redis.pid
loglevel notice
logfile "/data/redis_6381/redis.log"
dbfilename dump.rdb
dir /data/redis_6381
requirepass 123
masterauth 123
EOF

cat >>   /data/redis_6382/redis.conf <<EOF
port 6382
daemonize yes
pidfile /data/redis_6382/redis.pid
loglevel notice
logfile "/data/redis_6382/redis.log"
dbfilename dump.rdb
dir /data/redis_6382
requirepass 123
masterauth 123
EOF
  • 启动
redis-server /data/redis_6380/redis.conf
redis-server /data/redis_6381/redis.conf
redis-server /data/redis_6382/redis.conf

  2.开启主从

6381/6382命令行

redis-cli -p 6381 -a 123 SLAVEOF 127.0.0.1 6380
redis-cli -p 6382 -a 123 SLAVEOF 127.0.0.1 6380

  3.查询主从状态

redis-cli -p 6380 -a 123 info replication
redis-cli -p 6381 -a 123 info replication
redis-cli -p 6382 -a 123 info replication

  4.解除主从

[root@db01 ~]# redis-cli -p 6381 -a 123 SLAVEOF no one

redis-sentinel(哨兵)

哨兵的作用:

  1. 监控redis集群

  2. 自动选主,切换(6381 slaveof no one)

  3. 2号库(6382)指向新主库(6381)

  4. 应用透明

  5. 自动处理故障节点

  • sentinel搭建过程
## 创建目录
mkdir /data/redis_26380
cat > /data/redis_26380/sentinel.conf<<EOF
port 26380
dir "/data/redis_26380"
sentinel monitor mymaster 127.0.0.1 6380 1
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123 
EOF

配置文件参数说明:

  1. sentinel monitor mymaster 127.0.0.1 6380 1:只要有一个选票证明节点有问题,进行切换主库,多个哨兵是选票改为2

  2. sentinel down-after-milliseconds mymaster 5000:主库宕机5秒就进行切库

启动:
[root@db01 26380]# redis-sentinel /data/redis_26380/sentinel.conf  &>/tmp/sentinel.log &
关闭命令:
[root@db01 26380]# redis-cli -p 26380 shutdown

如果出现问题:

  1. 重新准备1主2从环境

  2. kill掉sentinel进行

  3. 删除sentinel目录下的所有文件

  4. 重新搭建sentinel

  • 停库主测试:
## 窗口一:
[root@db01 ~]# redis-cli -p 6380 shutdown
[root@db01 ~]# redis-cli -p 6381 -a 123 info replication  --查看主节点现在是谁?
[root@db01 ~]# redis-cli -p 6381
info replication

## 窗口二:
[root@db01 ~]# tail -f /tmp/sentinel.log
10237:X 14 Mar 22:19:29.318 # +promoted-slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
10237:X 14 Mar 22:19:29.318 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6380
10237:X 14 Mar 22:19:29.319 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
10237:X 14 Mar 22:19:30.378 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
10237:X 14 Mar 22:19:30.379 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
10237:X 14 Mar 22:19:30.454 # +failover-end master mymaster 127.0.0.1 6380
## 故障转移端是127.0.0.1 6380实例
10237:X 14 Mar 22:19:30.455 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6382
## 主节点切换到了6382实例上面
10237:X 14 Mar 22:19:30.455 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6382
10237:X 14 Mar 22:19:30.455 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
10237:X 14 Mar 22:19:35.528 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382

10237:X 14 Mar 22:22:58.321 # -sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
## 让启动好的6380节点接入6382为主节点
10237:X 14 Mar 22:23:08.242 * +convert-to-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6382
  • Sentinel 管理命令:
redis-cli -p 26380
PING :返回 PONG 。
SENTINEL masters :列出所有被监视的主服务器
SENTINEL slaves <master name> 
SENTINEL get-master-addr-by-name <master name> : 返回给定名字的主服务器的 IP 地址和端口号。 
SENTINEL reset <pattern> : 重置所有名字和给定模式 pattern 相匹配的主服务器。 
SENTINEL failover <master name> : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移。

注:哨兵一般有三个,这可以根据上面在的配置内容在创建两个,选票数则修改为2

redis cluster 分布式集群

介绍

高性能:

  1. 在多分片节点中,将16384个槽位,均匀分布到多个分片节点中

  2. 存数据时,将key做crc16(key),然后和16384进行取模,得出槽位(0-16383)

  3. 根据计算得出的槽位值,找到想对应的分片节点的主节点,存储到相应槽位上

  4. 如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接却换至真正存储节点进行数据存储

高可用:

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

  1. redis会为多组分片构成(3组)

  2. redis cluster 使用固定个数的slot存储数据(一个16384slot)

  3. 每组分片分得1/3 slot个数(0-5500 5501-11000 11001-16383)

  4. 基于CRC16(key)% 16384 --> 值 (槽位号)

分布式架构部署

6个redis实例,一般会放到3台硬件服务器

注:在企业规划中,一个分片的两个分到不同的物理服务器,防止硬件主机宕机造成的整个分片数据丢失。

原理图:

  1.安装集群插件

EPEL源安装ruby支持
yum install ruby rubygems -y

  2.配置gem源,并安装驱动

## gem安装需要repo源
curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

gem sources -l
gem sources -a http://mirrors.aliyun.com/rubygems/ 
gem sources  --remove https://rubygems.org/
gem sources -l
gem install redis -v 3.3.3

  3.集群节点准备

mkdir /data/redis_700{0..5}
cat > /data/redis_7000/redis.conf <<EOF
port 7000
daemonize yes
pidfile /data/redis_7000/redis.pid
loglevel notice
logfile "/data/redis_7000/redis.log"
dbfilename dump.rdb
dir /data/redis_7000
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF

cat >> /data/redis_7001/redis.conf <<EOF
port 7001
daemonize yes
pidfile /data/redis_7001/redis.pid
loglevel notice
logfile "/data/redis_7001/redis.log"
dbfilename dump.rdb
dir /data/redis_7001
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF

cat >> /data/redis_7002/redis.conf <<EOF
port 7002
daemonize yes
pidfile /data/redis_7002/redis.pid
loglevel notice
logfile "/data/redis_7002/redis.log"
dbfilename dump.rdb
dir /data/redis_7002
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF


cat >>  /data/redis_7003/redis.conf <<EOF
port 7003
daemonize yes
pidfile /data/redis_7003/redis.pid
loglevel notice
logfile "/data/redis_7003/redis.log"
dbfilename dump.rdb
dir /data/redis_7003
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF


cat >> /data/redis_7004/redis.conf <<EOF
port 7004
daemonize yes
pidfile /data/redis_7004/redis.pid
loglevel notice
logfile "/data/redis_7004/redis.log"
dbfilename dump.rdb
dir /data/redis_7004
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF

cat >> /data/redis_7005/redis.conf <<EOF
port 7005
daemonize yes
pidfile /data/redis_7005/redis.pid
loglevel notice
logfile "/data/redis_7005/redis.log"
dbfilename dump.rdb
dir /data/redis_7005
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF

## 启动节点:
redis-server /data/redis_7000/redis.conf 
redis-server /data/redis_7001/redis.conf 
redis-server /data/redis_7002/redis.conf 
redis-server /data/redis_7003/redis.conf 
redis-server /data/redis_7004/redis.conf 
redis-server /data/redis_7005/redis.conf 
[root@db01 ~]# ps -ef |grep redis
root       8854      1  0 03:56 ?        00:00:00 redis-server *:7000 [cluster]     
root       8858      1  0 03:56 ?        00:00:00 redis-server *:7001 [cluster]     
root       8860      1  0 03:56 ?        00:00:00 redis-server *:7002 [cluster]     
root       8864      1  0 03:56 ?        00:00:00 redis-server *:7003 [cluster]     
root       8866      1  0 03:56 ?        00:00:00 redis-server *:7004 [cluster]     
root       8874      1  0 03:56 ?        00:00:00 redis-server *:7005 [cluster]  

  4.将节点加入集群管理

利用ruby脚本 创建 副本为1.

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
... 敲yes

注:0,1,2说主节点,3,4,5是对应的从节点。主从节点不能在同一台主机.

  5.查看集群状态

 

集群主节点状态
redis-cli -p 7000 cluster nodes | grep master

集群从节点状态
redis-cli -p 7000 cluster nodes | grep slave

 

集群节点管理

  1.添加新的节点

## 创建目录
mkdir /data/redis_700{6..7}

## 配置文件信息
cat > /data/redis_7006/redis.conf <<EOF
port 7006
daemonize yes
pidfile /data/redis_7006/redis.pid
loglevel notice
logfile "/data/redis_7006/redis.log"
dbfilename dump.rdb
dir /data/redis_7006
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF

cat >  /data/redis_7007/redis.conf <<EOF
port 7007
daemonize yes
pidfile /data/redis_7007/redis.pid
loglevel notice
logfile "/data/redis_7007/redis.log"
dbfilename dump.rdb
dir /data/redis_7007
protected-mode no
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF

## 启动服务节点
redis-server /data/redis_7006/redis.conf
redis-server /data/redis_7007/redis.conf
netstat -lntup  | grep 700*

  2.添加主节点

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
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: b64016c2d42836ed5bbf16115e1785957b552805 127.0.0.1:7000
   slots:1365-5460 (4096 slots) master
   1 additional replica(s)
M: 881a713dcd1569a2426a69038f76e9718884e227 127.0.0.1:7006
   slots:0-1364,5461-6826,10923-12287 (4096 slots) master
   0 additional replica(s)
M: 50d0e7523be1a7459d49d4a2a6880682d4943679 127.0.0.1:7002
   slots:12288-16383 (4096 slots) master
   1 additional replica(s)
S: 254e2e852db3c2123e76d83bedda87f9dc3b02a6 127.0.0.1:7004
   slots: (0 slots) slave
   replicates 273b098710b782d485f3bca4e0613478dc04cb5b
S: d0126c22cc22bad1e4120c08916184af55682ea2 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 50d0e7523be1a7459d49d4a2a6880682d4943679
S: 8d2ccb6fb81f5dfb101fe7aa84ed19513626effe 127.0.0.1:7003
   slots: (0 slots) slave
   replicates b64016c2d42836ed5bbf16115e1785957b552805
M: 273b098710b782d485f3bca4e0613478dc04cb5b 127.0.0.1:7001
   slots:6827-10922 (4096 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? 881a713dcd1569a2426a69038f76e9718884e227
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1:all
Do you want to proceed with the proposed reshard plan (yes/no)? yes

  4.添加一个从节点

## 查看节点
[root@db01 ~]# redis-cli -p 7000 cluster nodes | grep master
881a713dcd1569a2426a69038f76e9718884e227 127.0.0.1:7006 master - 0 1584256924836 7 connected 0-1364 5461-6826 10923-12287
50d0e7523be1a7459d49d4a2a6880682d4943679 127.0.0.1:7002 master - 0 1584256925341 3 connected 12288-16383
b64016c2d42836ed5bbf16115e1785957b552805 127.0.0.1:7000 myself,master - 0 0 1 connected 1365-5460
273b098710b782d485f3bca4e0613478dc04cb5b 127.0.0.1:7001 master - 0 1584256925846 2 connected 6827-10922

## 添加从节点,查看自己的id号
redis-trib.rb add-node --slave --master-id 881a713dcd1569a2426a69038f76e9718884e227 127.0.0.1:7007 127.0.0.1:7000

删除节点

  1. 将需要删除节点slot移动走

将7006上的slot都给移走,需要分三次分别移给700070017002

## 查看master节点
redis-cli -p 7000 cluster nodes | grep master

redis-trib.rb reshard 127.0.0.1:7000

  • 第二次还原slot给7001节点

 

  • 第三次还原slot给7002节点

 

注:这里一定要查好slot号,否则会留下几个删除不了节点(这里留了一个😭)

解决办法:

每个分片都是1365个,0也算,重新给slot分片,比以前多了一个,然后把分片给了那三个节点

  2.删除节点

删除master节点之前首先要使用reshard移除master的全部slot,然后在删除当前节点

 

redis-trib.rb del-node 127.0.0.1:7006 881a713dcd1569a2426a69038f76e9718884e227
>>> Removing node 881a713dcd1569a2426a69038f76e9718884e227 from cluster 127.0.0.1:7006
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

redis-trib.rb del-node 127.0.0.1:7007 efad54782609d6bcae5a7635399c91f08cb75a0a
>>> Removing node efad54782609d6bcae5a7635399c91f08cb75a0a from cluster 127.0.0.1:7007
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

redisAPI支持-以python为例

安装python

yum install -y python36 
python3 -V
yum install -y python36-pip

pip3 install redis 
pip3 install redis-py-cluster

python连接单节点redis

python3
>>>import redis
>>>r = redis.StrictRedis(host='10.0.0.51', port=6379, db=0,password='123')
>>>r.set('oldboy', 'oldguo')
>>>r.get('oldboy')

python连接 sentinel集群

[root@db01 ~]# redis-server /data/redis_6380/redis.conf
[root@db01 ~]# redis-server /data/redis_6381/redis.conf
[root@db01 ~]# redis-server /data/redis_6382/redis.conf 
[root@db01 ~]# redis-sentinel /data/redis_26380/sentinel.conf &
--------------------------------
## 导入redis sentinel包
>>>from redis.sentinel import Sentinel

##指定sentinel的地址和端口号
>>> sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1)

##测试,获取以下主库和从库的信息
>>> sentinel.discover_master('mymaster')
>>> sentinel.discover_slaves('mymaster')

配置读写分离
#写节点
>>> master = sentinel.master_for('mymaster', socket_timeout=0.1,password="123")

#读节点
>>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1,password="123")

###读写分离测试   key     
>>> master.set('oldboy','123')
>>> slave.get('oldboy')

python3连接 redis cluster

>>> from rediscluster import RedisCluster
>>> startup_nodes = [{"host":"127.0.0.1", "port": "7000"},{"host":"127.0.0.1", "port": "7001"},{"host":"127.0.0.1", "port": "7002"}]
### Note: decode_responses must be set to True when used with python3  
>>> rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)
>>> rc.set("foo", "bar")  
>>> print(rc.get("foo"))

一些概念

缓存穿透

  • 概念

    访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。

  • 解决方案

    采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤; 访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间。

缓存雪崩

  • 概念

    大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。

  • 解决方案

    可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效

缓存击穿

  • 概念

    一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。

  • 解决方案

    在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

 

posted @ 2020-03-15 20:22  kerwin-  阅读(461)  评论(0编辑  收藏  举报