Redis 基础教程

本贴最后更新于 325 天前,其中的信息可能已经事过景迁

Redis

启动与关闭

linux 默认安装路径在/usr/local/bin 下

启动 redis-server

#指定配置文件启动
redis-server tmp_redisConf/redis.conf

进入 redis 控制台

redis-cli -p 6379

关闭 redis

#关闭
shutdown
#退出
exit

库基本基本命令

redis 默认有 16 个数据库,默认使用第 0 个数据库

image.png

数据库命令

#选择数据库
select 0
#查看数据库大小
dbsize
#清空数据库
flushdb
#清空全部数据库
FLUSHALL

redis 是单线程的!为什么还这么快

1.redis 将所有数据放在了内存中,所有操作都基于内存。按照官方的说法,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。而且单线程容易实现,没必要再用多线程,会增加上下文切换的时间。

key 基本命令

#是否存在某个key
exists key
#设置过期
expire key time
#所有key
keys *
#key类型
type key
#查看key过期时间
ttl key

五大基本类型

String

#加字符串,如果当前key不存在,会新建一个key
append key value
#长度
strlen key
#自增和自减
incr key 步长
decr key 步长
#截取字符串  计头计尾
getrange key start end
#替换指定位置开始的字符串
setstring key index value
#新建key并指定过期时间
setex key time value
#如果key不存在则设置(分布式锁中经常使用)
setnx key value
#一次设置多个值
mset k1 v1 k2 v2
mget k1 k2 k3
msetnx k1 v1 k2 v2  #原子性操作,一个失败都失败 
#获取并设置 如果不存在,返回null,并设置。如果存在,返回值,并设置新值
getset 

List

#lrange 从list队头开始取数据,lpush放数据到队头,rpush放数据到队尾
127.0.0.1:6379> lpush list1 one
(integer) 1
127.0.0.1:6379> lpush list1 two
(integer) 2
127.0.0.1:6379> lpush list1 three
(integer) 3
127.0.0.1:6379> lrange list1 0 1
1) "three"
2) "two"
127.0.0.1:6379> lrange list1 0 0
1) "three"
127.0.0.1:6379> rpush list1 four
(integer) 4
127.0.0.1:6379> lrange list1 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> 
# 从队头或队尾移除元素
127.0.0.1:6379> lpop list1 1
1) "three"
127.0.0.1:6379> rpop list1 1
1) "four"
#根据下标查找元素
127.0.0.1:6379> lindex list1 1
"one"
#list长度
127.0.0.1:6379> llen list1
(integer) 2
#移除list中的元素,精确移除,根据value。例子中,移除list中两个 three
lrem list1 2 three
#通过下标截取list
127.0.0.1:6379> llen list1
(integer) 2
#从一个列表取出元素放到另一个列表,可以分别指定操作在队尾还是队头
127.0.0.1:6379> rpoplpush list1 list2
"one"
#往list中指定下标添加value,如果改下标存在值会替换原来的值,如果不存在会报错
lset list1 0 five
#linsert,往指定元素后面(after)或前面(before)插入元素
127.0.0.1:6379> lrange list1 0 -1
1) "three"
2) "two"
127.0.0.1:6379> linsert list1 before two one
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "three"
2) "one"
3) "two"

队列(lpush,rpop) 栈(lpush,lpop)

Set

127.0.0.1:6379> sadd myset one  #往set中添加元素
(integer) 1
127.0.0.1:6379> sadd myset two
(integer) 1
127.0.0.1:6379> sadd myset one
(integer) 0
127.0.0.1:6379> smermber myset
(error) ERR unknown command 'smermber', with args beginning with: 'myset' 
127.0.0.1:6379> smembers myset #查看set所有元素
1) "one"
2) "two"
127.0.0.1:6379> sismember myset one #查看是否包含某个元素
(integer) 1
127.0.0.1:6379> srem myset one #删除某个元素
(integer) 1
#从set中随机获取指定个数
127.0.0.1:6379> SRANDMEMBER myset 2
1) "one"
2) "four"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "three"
2) "two"
127.0.0.1:6379> spop myset 1 #随机删除指定个数的值
1) "two"
127.0.0.1:6379> smove myset myset2 one #从一个set移动数据到另一个set
(integer) 1
#两个set的差集(第一个set有而第二个set没有的),交集,并集
127.0.0.1:6379> SMEMBERS myset
1) "four"
2) "three"
127.0.0.1:6379> SMEMBERS myset2
1) "three"
2) "one"
127.0.0.1:6379> sdiff myset myset2
1) "four"
127.0.0.1:6379> sinter myset myset2
1) "three"
127.0.0.1:6379> sunion myset myset2
1) "one"
2) "three"
3) "four"

Hash

#String 类型的命令加一个h之后,都可以用
127.0.0.1:6379> hset myhash field1 1
(integer) 1
127.0.0.1:6379> hget myhash field1 
"1"
127.0.0.1:6379> hgetall myhash
1) "field"
2) "2"
3) "field1"
4) "1"
5) "field2"
6) "1"
127.0.0.1:6379> HDEL myhash field #删除字段
(integer) 1
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash
1) "1"
2) "1"
127.0.0.1:6379> HINCRBY myhahs field1 1
(integer) 1
127.0.0.1:6379> HINCRBY myhash field1 1
(integer) 2
127.0.0.1:6379> hget myhash field1
"2"
127.0.0.1:6379> hsetnx myhash field1 3
(integer) 0
127.0.0.1:6379> hget myhash field1
"2"
127.0.0.1:6379> hsetnx myhash field3 3
(integer) 1
127.0.0.1:6379> hget myhash field3
"3"

Hash 跟适合存对象类型,String 类型适合存字符串

ZSet

#添加一个值。与set不同,要设置一个序号
127.0.0.1:6379> zadd myzset 1 one
(integer) 1
127.0.0.1:6379> zadd myzset 2 two 3 three
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1
1) "one"
2) "two"
3) "three"
#根据设置的序号排序  inf代表无穷大
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf
#排序并附带输出序号 
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
 #移除
127.0.0.1:6379> zrem myzset one
(integer) 1
#元素数
127.0.0.1:6379> zcard myzset
(integer) 2
#指定区间元素数
127.0.0.1:6379> zcount myzset 0 1
(integer) 0
127.0.0.1:6379> zcount myzset 0 2
(integer) 1

三种特殊类型

Geospatial

#key 经度 纬度 名称
geoadd china:city 15.13 124.323 shandong
#获取经纬度
geopos china:city shandong
#获取距离  单位 M KM FT MI
geodist china:city shandong beijing 单位
#查找集合中的  在某个经纬度 某个半径内的所有元素
127.0.0.1:6379> GEORADIUS china:city 23 43 10000 km
1) "shandong"
2) "beijing"
3) "chongqing"
#显示到中心位置的距离
GEORADIUS china:city 23 43 10000 km withdist
#显示定位信息
GEORADIUS china:city 23 43 10000 km withcoord
#筛选指定数量
GEORADIUS china:city 23 43 10000 km count 2
#用元素做中心位置
GEORADIUSBYMEMBER china:city beijing 1000 km

Hyperloglog

什么是基数

一个集合中不重复元素的个数。

应用:统计网站访问人数,一个人多次访问算作一次

传统方式,用 set 保存访问的用户 id,然后通过 set 中的元素数量作为访问量。

这个方式保存了大量 id,我们的目的是计数而不是保存用户 id。

而使用 hyperloglog 只需要很小的内存就可以是这个功能

127.0.0.1:6379> pfadd mylog a b c c b a
(integer) 1
127.0.0.1:6379> PFCOUNT mylog
(integer) 3
127.0.0.1:6379> pfadd mylog1 f e a b
(integer) 1
#合并
127.0.0.1:6379> PFMERGE mylog2 mylog mylog1
OK
127.0.0.1:6379> PFCOUNT mylog2
(integer) 5

bitmaps

#统计周一到周五是否登录
127.0.0.1:6379> setbit login 1 1
(integer) 0
127.0.0.1:6379> setbit login 2 1
(integer) 0
127.0.0.1:6379> setbit login 3 0
(integer) 0
127.0.0.1:6379> setbit login 4 1
(integer) 0
127.0.0.1:6379> setbit login 5 0
(integer) 0
#查看某个元素
127.0.0.1:6379> getbit login 4
(integer) 1
#统计1的个数
127.0.0.1:6379> bitcount login
(integer) 4

事务

redis 单条命令是原子性的,但是事务不是原子性的。

redis 事务也没有隔离级别的概念。所有的命令在事务中,并没有被直接执行,只有发起执行命令时才会被执行。

redis 事务本质:一组命令的集合。一个事务中所有命令都会被序列化。

特性:一次性(所有命令会一次性按顺序执行),顺序性,排他性。

-------- set set set 执行--------

redis 事务三个阶段:

  • 开启事务
  • 命令入队
  • 执行事务

锁:redis 可以实现乐观锁

127.0.0.1:6379> multi   #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec  #一次依次执行所有命令
1) OK
2) OK
3) "v2"
4) OK

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> discard  #放弃事务
OK

watch 相当于乐观锁

127.0.0.1:6379> watch money   #监控 相当于加锁
OK
127.0.0.1:6379> multi    #开启事务
OK
127.0.0.1:6379(TX)> DECRBY money 10   
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10  #命令入队过程中,如果其他线程修改了监视的值,那么这个事务会执行失败
QUEUED
127.0.0.1:6379(TX)> exec 
(nil)                           #执行失败,如果执行失败,重新获取最新之
127.0.0.1:6379> unwatch    #先解锁
OK
127.0.0.1:6379> watch money  #再重新监视,这样可以获取最新的值
OK

redis.conf

daemonize yes #是否开启守护进程
pidfile /var/run/redis_6379.pid   #如果以后台方式运行,要指定一个pid文件


# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice         #设置日志级别
logfile ""        #日志的文件位置名
always-show-logo no   #redis-server启动时的图案



#持久化默认设置
Unless specified otherwise, by default Redis will save the DB:
#   * After 3600 seconds (an hour) if at least 1 change was performed
#   * After 300 seconds (5 minutes) if at least 100 changes were performed
#   * After 60 seconds if at least 10000 changes were performed


stop-writes-on-bgsave-error yes  #持久化出错了是否继续工作
rdbcompression yes  #是否压缩rdb文件
rdbchecksum yes    #保存rdb文件时,进行错误校验
dir ./    #rdb文件目录
#密码设置,设置密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "371328199808175531yishengyyds"
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "371328199808175531yishengyyds"
127.0.0.1:6379> auth 371328199808175531yishengyyds
OK

maxclients 10000    #最大连接数
maxmemory <bytes> #最大内存
maxmemory-policy noeviction  #内存满了应对策略

RDB(Redis DataBase)

RDB 是 redis 默认的持久化策略,可以将 redis 内存中的数据以内存快照的方式保存到磁盘中,避免数据意外丢失。

RDB 持久化既可以手动执行,也可以通过配置文件设置条件定期执行。

RDB 生成的文件名默认为 dump.rdb,是一个二进制文件.

创建 RDB 文件的方式:

  • 手动执行 save 或者 bgsave 命令时
  • 满足配置文件中的条件时

save 命令和 bgsave 命令都可以生成 rdb 文件,它们以不同的方式调用 rdbSave()函数完成 RDB 文件的生成。

  • save 命令是阻塞式的创建 RDB 文件,阻塞 Redis 服务器进程,直到 rdb 文件创建完成。在这个过程中,所有客户端请求都会被拒绝。
  • bgsave 命令会创建一个 fork 子进程调用 rdbSave()函数,不会阻塞主进程,这期间还是可以急促处理客户端请求。
  • bgsave 执行过程中,拒绝 save 和 bgsave 请求,避免产生 2 个 rdbsave()调用。

RDB 文件载入

通过命令查看 redis 启动目录,并将 dump 文件放在启动目录下。

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"

RDB 优缺点:

  • 优点
    RDB 文件是二进制存储的,节省磁盘空间。
    RDB 保存的是数据库状态快照,恢复速度快
  • 缺点
    虽然 redis 再 fork 时使用了·写时拷贝技术,但是如果数据量庞大还是会比较消耗性能。
    如果 redis 以外宕机,会导致最后一次保存快照之后修改的数据丢失

AOF(append only file)仅追加文件

aof 这种方式会保存所有写操作的命令,不保存读命令。

被写入 AOF 文件中的所有命令都是以 Redis 的命令请求协议格式保存的

#配置文件中设为yes即可开启
appendonly no
#保存文件名
appendfilename "appendonly.aof"
#默认每秒保存一次
# appendfsync always
appendfsync everysec
# appendfsync no

aof 持久化实现可以分为命令追加(append),文件写入,文件同步(sync)三个步骤

  • 命令追加

    当 AOF 持久化功能打开时,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。

  • 文件写入

    (1)通过 write() 系统调用,将 aof_buf 缓冲区的数据写入到 AOF 文件 ,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache ,等待内核将数据写入硬盘;

    (2)具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。

    (为了提高文件的写入效率,在现代操作系统中,当用户调用 write 函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过 了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。)

  • 文件同步

    如果由内核决定将数据写入硬盘的话,如果服务器宕机,那么就会有数据丢失的风险。为了解决这个问题,系统提供了 fsync 和 fdatasync 两个同步函数三种写回策略 ,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

always:服务器在每次写操作后都将 aof_buf缓冲区中的所有内容写入到AOF文件,然后立即执行fsync()函数同步AOF文件到磁盘,所以always的效率是最慢的,但也是最安全的。可靠性高,性能低。
	everysec:服务器在每次写操作后都要 将aof_buf缓冲区中的所有内容写入到AOF文件,并且每隔一秒就要在子线程中对AOF文件进行一次同步,创建一个异步任务执行fsync()函数。可靠性和性能都适中。
	no:将缓冲区的内容写入AOF文件后,何时进行同步由操作系统控制,不执行fsync()函数。性能好,可靠性低,宕机可能会丢失较多数据。

AOP 后台重写机制

随着写操作的不断执行,AOF 文件会变得越来越大,这就会带来一些性能问题(比如恢复慢)。所以当 AOF 文件大小超过阈值时,redis 就会 fork 一个进行 AOF 重写。redis 服务器会 创建一个新的 AOF 文件来覆盖现有的 AOF 文件 ,新的文件中减少了冗余的命令。

为什么要创建一个新的文件而不直接对原先文件进行修改删除呢?

因为如果在原先文件上操作,重写失败的话,原先的文件就会被污染,这就导致我们无法回到之前的状态了。而创建一个新的文件就算重写失败了,对原文件也没有影响。

eg.现在执行如下两条命令

set name xiaoli
set name yisheng

没有重写的 AOF 文件中,会记录这两条命令。而经过重写的 AOF 文件中,只会保留第二条命令。因为 AOF 文件是为了记录数据库的状态,历史数据就没必要进行保存了。

AOF 优缺点

  • 文件完整行好,丢失数据可能性小。相比于 RDB

缺点

  • aof 文件远大于 RDB 文件,回复速度也慢。
  • aof 运行效率比 rdb 慢。

Redis 发布订阅

redis 发布订阅是一种消息通信模式:发送者发送消息,订阅者接收消息。微信,微博,关注系统。

Redis 可以订阅任意数量的频道。

image.png

频道订阅结构:

redis 底层结构中,定义了一个 channels 字典(pubsub_channels),字典中,key 代表频道(channels)名称,value 是一个链表。这个链表存放的是订阅这个链表的客户端

这个时候有两种情况(执行 subscribe channels 命令时,会同时创建频道):

  • 该渠道是首次被订阅:首次被订阅说明在字典中并不存在该渠道的信息,那么程序首先要创建一个对应的 key,并且要赋值一个空链表,然后将对应的客户端加入到链表中。此时链表只有一个元素。
  • 该渠道已经被其他客户端订阅过:这个时候就直接将对应的客户端信息添加到链表的末尾就好了。

应用:实时消息系统,实时聊天,订阅、关注系统

模式订阅结构

模式渠道的订阅与单个渠道的订阅类似,不过服务器是将所有模式的订阅关系都保存在服务器状态的 pubsub_patterns 属性里面。

模式渠道的订阅与单个渠道的订阅类似,不过服务器是将所有模式的订阅关系都保存在服务器状态的 pubsub_patterns 属性里面。

Redis 主从复制

主从复制,是指将一台 redis 服务器的数据,复制到其他 redis 服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master 以写为主,Slave 以读为主。redis80% 情况是读操作。

默认情况下,每台 redis 的服务器都是主节点; 且一个主节点可以有多个从节点(或者没有从节点),但一个从节点只能有一个主节点。

主从复制的作用包括:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现了问题,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  • 负载均衡:在主从复制的基础上,配合读写分离,可以有主节点提供写服务,有从节点提供读服务(即写 redis 数据时应用连接主节点,读数据时应用连接从节点),分担服务器负载;尤其是在写少读多的情况下,通过多个从节点分担读负载,可以大大提高 redis 服务器的并发量。
  • 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。一般来说,要将 redis 运用于项目工程中,只使用一台 redis 是万万不能的(宕机、一主二从),原因如下:1、从结构上,单个 redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载马,压力较大。2、从容量上,单个 Redis 服务器内存容量有限,就算一台 Redis 服务器内存容量为 256G,也不能将所有内存用作 Redis 存储内存,一般来说,单台 Redis 最大使用内存不应该超过 20G。

3767b536225b4334a00ada08bdb7a1ee.png

环境配置

只需要配置从机,不需要配置主机。

每台服务器默认是 master

127.0.0.1:6379> info inplication
127.0.0.1:6379> info replication
# Replication
role:master            #角色master
connected_slaves:0     #连接的从机
master_failover_state:no-failover
master_replid:d9803af0e01d0de577ad0636c1ca95d4147126e2
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

复制三个配置文件,修改配置信息

  1. 端口
  2. pid 名字
  3. log 文件名
  4. rdb 文件名(dump)

image.png

修改完成后启动三个服务

默认情况下,每台 redis 服务器都是主节点。

配置一主(6379)二从(6380,6381)

#6380配置6379为master
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
#6381配置6379为master
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK

这是使用命令配置的只是暂时的,要像设置永久的主从配置,要从配置文件配置

# replicaof <masterip> <masterport>
  replicaof 127.0.0.1 6379                      #配置主机

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the replica to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the replica request.
# 
# masterauth <master-password>           #如果主机有密码,在这里配置

细节

主机可以写进行读写,从机只能读

主机断开连接,但是没有写操作了,如果主机再次启动了,那么从机依旧可以直接获取到主机写的数据。

从机可以拿到主机所有的数据,即使是没有成为从机之前的数据

复制原理

Slave 启动成功连接到 master 后会发送一个 sync 同步命令

Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master 将传送整个数据文件到 slave,并完成一次完全同步。

全量复制:而 slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制:Master 继续将新的所有收集到的修改命令依次传给 slave,完成同步

但是只要是重新连接 master,一次完全同步(全量复制)将被自动执行。

另一种模式

6380 设置 6379 为主机,6381 设置 6380 为主机,这种情况下,6380 依旧是一个从机,不可以写数据。

6379 的写的数据,在 6380 和 6381 中都可以读到。

以上两种模式,工作中都不会使用

以上两种模式,可以使用 slaveof no one 命令让自己变成主机。其他节点可以手动连接到这个节点,如果之前的主机回来了,会成为光杆司令,只能重新配置

哨兵模式

自动选举老大的模式

主从切换技术的方法是 ∶ 当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用.l 这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis 从 2.8 开始正式提供了 Sentinel (哨兵)架构来解决这个问题。

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。

image.png

这里的哨兵有两个作用

通过发送命令,让 Redis 服务器返回监控其运行状态,包括主服务器和从服务器。

当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对 Redis 服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

image.png

1) 主观下线

主观下线,适用于主服务器和从服务器。如果在规定的时间内(配置参数:down-after-milliseconds),Sentinel 节点没有收到目标服务器的有效回复,则判定该服务器为“主观下线”。比如 Sentinel1 向主服务发送了 PING 命令,在规定时间内没收到主服务器 PONG 回复,则 Sentinel1 判定主服务器为“主观下线”。

2) 客观下线

客观下线,只适用于主服务器。 Sentinel1 发现主服务器出现了故障,它会通过相应的命令,询问其它 Sentinel 节点对主服务器的状态判断。如果超过半数以上的 Sentinel 节点认为主服务器 down 掉,则 Sentinel1 节点判定主服务为“客观下线”。

3) 投票选举

投票选举,所有 Sentinel 节点会通过投票机制,按照谁发现谁去处理的原则,选举 Sentinel1 为领头节点去做 Failover(故障转移)操作。Sentinel1 节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。

对上对述过程做简单总结:

Sentinel 负责监控主从节点的“健康”状态。当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接 Redis 集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。

哨兵配置文件

#Example sentinel.conf

#哨兵sentine1实例运行的端口默认26379
port 26379

# 哨兵sentine1的工作目录
dir /tmpl

#哨兵sentine1监控的redis主节点的ip port
# master-name可以自己命名的主节点名字只能由字母A-z、数字0-9、这三个字符".-_"组成。
# quorum配置多少个sentine1哨兵统一认为master主节点失联那么这时客观上认为主节点失联
了# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentine1 monitor mymaster 127.0.0.1 6379 2

#当在Redis实例中开启了requirepass foobared授权密码这样所有连接Redis实例的客户端都要提供密码
#设置哨兵sentinel连接主从的密码注意必须为主从设置一样的验证密码
# sentine1 auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passwOrd

# 指定多少毫秒之后主节点没有应答哨兵sentine1 此时哨兵主观上认为主节点下线默认30秒
# sentine7 down-after-mi77iseconds <master-name> <mil7iseconds>
sentine7 down-after-mi17iseconds mymaster 30000

#这个配置项指定了在发生failover主备切换时最多可以有多少个s1ave同时对新的master进行_同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的s1ave因为replication而不可用。
可以通过将这个值设为1来保证每次只有一个slave 处于不能处理命令请求的状态。
#sentine1 para1le1-syncs <master-name> <nums1aves>
sentinel para1le1-syncs mymaster 1

# 故障转移的超时时间failover-timeout可以用在以下这些方面:
#1。同一个sentine1对同一个master两次fai1over之间的间隔时间。
#2,当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的fai1over所需要的时间。
#4.当进行faiToveri时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按paralle1-syncs所配置的规则来了
#默认三分钟
# sentinel failovertimeout <master-name> <mil7iseconds>
sentine1 failover-timeout mymaster 180000

# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentine7有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SNS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentine1.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentine1无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <seript-path>
sentinel notification-script mymaster /var/redi/notify.sh

#客户端重新配置主节点参数脚本
#当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。#以下参数将会在调用脚本时传给脚本:
# <master-name>_<role> <state> <from-ip> <from-port> <to-ip> <to-port>
#目前<state>总是failover",
# 参数 from-ip,from-port,to-ip,to-port是用来和旧的master和新的master(即旧的slave)
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
#sentinel client-reconfig-script <master-name> <script-path>
sentine1 client-reconfig-script mymaster /var/redis/reconfig.sh

#启动redis-sentinel 
redis-sentinel tmp_redisConf/sentinel.conf 

哨兵日志

FIDBDG5Y2CQX4.png

优缺点

如果主机断开了连接,哨兵会重新选取 master,之后,如果之前的主机再重新连接,也只能做从机(slave)

优点:

1.哨兵集群,基于主从复制,具有所有的主从配置优点

2.主从可以切换,故障可以转移,系统的可用性就会更好

3.哨兵模式就是主从模式的升级,从手动到自动,更加健壮

缺点:

1.redis 不好在线扩容,集群容量一旦到达上线,在线扩容十分麻烦

2.实现哨兵模式的配置其实是很麻烦的

redis 缓存穿透和雪崩

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。

缓存穿透解决方案

  • 布隆过滤器
  • 在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截,当收到一个对 key 请求时先用布隆过滤器验证是 key 否存在,如果存在在进入缓存层、存储层。
  • 缓存空对象
  • 当存储层没有命中时,即使返回空对象也将其缓存起来,同时设置一个过期时间,之后再次查询这个数据时会从缓存中获取,保护了后端数据源。
  • 这么做有两个问题,占用内存空间;即使设置了过期时间,但是缓存层和存储层还是会有一段窗口的不一致,这对需要保持一致性的业务会有影响。

缓存击穿

概念

相较于缓存穿透,缓存击穿的目的性更强,一个存在的 key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB,造成瞬时 DB 请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个 key 的缓存不可用而导致击穿,但是其他的 key 依然可以使用缓存响应。

比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

1.设置热点数据永不过期

这样就不会出现热点数据过期的情况,但是当 Redis 内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。

2.加互斥锁(分布式锁)

第二种方式使用了加锁的方式,锁的对象就是 key,这样,当大量查询同一个 key 的请求并发进来时,只能有一个请求获取到锁,然后获取到锁的线程查询数据库,然后将结果放入到缓存中,然后释放锁,此时,其他处于锁等待的请求即可继续执行,由于此时缓存中已经有了数据,所以直接从缓存中获取到数据返回,并不会查询数据库。

缓存雪崩

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

解决方案

redis 高可用
这个思想的含义是,既然 redis 有可能挂掉,那我多增设几台 redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)

限流降级(在 SpringCloud 讲解过!)
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数
据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让
缓存失效的时间点尽量均匀。

服务降级:是指发生缓存雪崩,针对不同的数据采用不同的处理方式:

  • 当业务应用访问的是非核数据时候,暂时停止从缓存中查询这些数据,而是直接返回预定义信息或者空数据。
  • 当业务应用访问的是核心数据,仍然允许查询缓存,如果缓存缺失也可以继续通过数据库读取。
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    284 引用 • 247 回帖 • 182 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...