Redis

448 阅读27分钟

NoSQL

为什么使用NoSQL

  1. 单机MySQL架构:
    • APP->DAO->Mysql Instance
    • 数据量总大小存在瓶颈
    • 数据索引一个机器的内存放不下
    • 访问量不能承受
  2. Memcached(缓存)+MySQL+垂直拆分
    • APP->DAO->Cache->多个 Mysql Instance
  3. MySQL 主从复制,读写分离
    • Memcached 只能缓解数据库的读取压力
    • 读写集中在一个库上让数据库不堪重负,使用主从复制来达到读写分离,使用 Mysql 的 master-slave 模式
    • APP->DAO->Cache->写入 master/从 slave 读取
  4. 分库分表 + 水平拆分 + mysql集群
    • Mysql 写压力出现瓶颈
    • 由于高并发场景,开始使用 InnoDB(行级锁)
    • 使用分库分表来缓解写压力和数据增长扩展问题
  5. mysql 的扩展性瓶颈
    • 存储大文本字段,导致数据库表非常大,恢复时非常慢
    • mysql 扩展性不好
  6. 现在的架构
    • 用户->防火墙->负载均衡->APP服务器集群->数据库服务器集群+缓存数据库等等

大数据时代的三V:海量(Volume),多样(Variety),实时(Velocity) 互联网的三高:高并发、高可扩展性、高性能

NoSQL介绍

NoSQL = Not Only SQL,泛指非关系型数据库,随着 web2.0 网站的兴起,传统的关系型数据库在应对超大规模和高并发的 SNS 类型的网站显得力不从心。

NoSQL 数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

网站上的数据很多存储时不需要固定的模式,无需多余操作就可以横向扩展。

NoSQL 特点

  1. 易扩展:数据之间没有关系,所以易扩展
  2. 高性能:NoSQL 数据库有非常高的读写性能,在大数据下同样表演优秀。得益于它的无关系性,数据库的结构简单。
  3. 数据模型灵活多样:无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式

RDBMS 和 NoSQL 的比较

RDBMS:

  • 高度组织化结构化数据
  • 结构化查询语言
  • 数据和关系都存储在单独的表中
  • 严格的一致性
  • 支持事务

NoSQL:

  • 没有声明性查询语言
  • 没有预定义模式(KV,列存储,文档,图形数据库)
  • 最终一致性,而非ACID
  • CAP定理
  • 高性能、高可用性和可伸缩性

应用实例

多数据源、多数据类型的存储:

  1. 商品基本信息(不会变动的):存放在关系型数据库
  2. 商品描述、详情、评价(大文本):存放在文档数据库 MongoDB
  3. 商品的图片(图片类):存放在分布式文件系统(HDFS)
  4. 商品关键字:搜索引擎(ISearch)
  5. 商品波段性的热点高频信息:内存数据库(Tair,Redis,Memcache)

大数据、高并发、多类型的大型互联网应用的难点和解决方案:

  1. 难点:
    • 数据类型多样
    • 数据源多样且变化重构
    • 数据源改造而数据服务平台不需要大面积重构
  2. 解决办法:统一数据平台服务层:UDSL
    • 在网站应用集群和底层数据源之间,构建一层代理
    • 统一的模型数据映射:业务模型属性和底层不同类型数据源的模型数据映射
    • 统一的查询和更新API
    • 性能优化策略:热点缓存平台

NoSQL 数据模型

RDBMS: 使用 ER 图,有1对1,1对N,N对N 多种关系。当出现新的模块,会很复杂。

NoSQL: BSON:是一种类 json 的二进制形式的存储格式,简称 Binary JSON,支持内嵌的文档对象和数组对象。

高并发的操作是不太建议有关联查询的,互联网公司用冗余数据来避免关联查询。分布式事务是支持不了太多的并发的。

聚合模型

  • kv 键值对
  • bson
  • 列簇:一个行对应多个列族:如123对应' name : xxx, gender : xxx '等
  • 图形:社交网络图

NoSQL 四大分类

  1. KV 键值对:redis
  2. 文档型数据库(bson):Mongodb:基于分布式文件存储的数据库。
  3. 列存储数据库:HBase、分布式稳健性系统
  4. 图关系数据库:用于社交网络、推荐系统等,用于构建关系图谱,如 Neo4J , InfoGrid。

对比:

分类 举例 应用场景 数据模型 优点 缺点
键值对 Redis 内容缓存,处理大量数据的高访问负载 键值对,通常使用hash table实现 查找速度快 数据无结构化
列簇 HBase 分布式文件系统 以列簇形式存储,将同一列存在一起 查找速度快,扩展性强 功能局限
文档型数据库 MongoDB web应用 键值对,value为格式化数据 表结构可变且无需预先定义 查询性能不高,缺乏统一的查询语法
图形 InfoGrid 社交网络、推荐系统、关系图谱 图结构 利用图的相关算法 需要对整个图做计算,不好做分布式

CAP 和 BASE

传统的ACID:原子性、一致性、隔离性、持久性

CAP:强一致性、可用性、分区容错性

一个分布式系统不可能同时很好的满足一致性、可用性、分区容错性,最多只能同时较好的满足两个。由于当前的网络肯定会出现延迟丢包等问题,所以分区容错性是必须要实现的,所有智能在一致性和可用性之间权衡,没有NoSQL能同时保证这三点。

其中能够满足CA(一致性和可用性)的是 RDBMS,但是它不能保证分区容错性。

能满足CP(一致性和分区容错性)的是 MongoDB , HBase , Redis。

能满足AP(可用性和分区容错性)的是大多数网站架构的选择,要保证网站的可用性。

一致性和可用性的抉择

对于网站来说,RDBMS 的很多主要特性往往无用武之地。

  • 数据库事务一致性需求:很多 web 实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求也不高,允许实现最终一致性。
  • 数据库的写实时性和读实时性需求:对关系数据库来说,插入一条数据之后立刻查询,可以读出这条数据,但是对于很多web应用来说并不要求这么高的实时性。
  • 对复杂的 SQL 以及多表关联查询的需求低:大数据量的web系统非常忌讳多个大表的关联查询,往往更多知识单标的主键查询和简单条件分页查询,SQL的功能被极大弱化

BASE

BASE 是为了解决 RDBMS 强一致性引起的可用性降低的问题而提出的解决方案。

BASE 是:

  • 基本可用 Basically Available
  • 软状态 Soft state
  • 最终一致性 Eventually consistent

它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上的改观。

分布式和集群

分布式:不同的多台服务器部署不同的工程,他们之间通过 RPC 进行通信和调用,对外提供服务和组内协作

集群:不同的多台服务器上面部署相同的工程,通过分布式调度软件进行统一的调度,对外提供服务和访问。

Redis

Redis介绍

Redis 全称 REmote DIctionary Server(远程字典服务器)。 Redis 是开源免费的,用 C 语言编写的,是一个高性能的 key-value 分布式内存数据库,是基于内存运行并支持持久化的 NoSQL 数据库。

Redis 具有如下特点:

  • 支持数据持久化,可以将内存中的数据保持在磁盘中
  • 不仅仅支持 k-v 类型数据,还提供 list, set, zset, hash 等数据结构的存储
  • 支持数据的备份,即 master - slave 模式的数据备份

Redis 支持的功能:

  • 内存存储和持久化:支持异步将内存中的数据写到硬盘上,同时不影响继续服务
  • 取最近 N 个数据的操作
  • 模拟 HttpSession 这种需要设定过期时间的功能
  • 发布、订阅消息系统
  • 定时器、计数器

Redis 是单进程的,对读写等事件的相应是通过对 epoll 函数的包装来做到的。默认16个数据库,下标从0开始,初始默认使用0号库,使用 select databseid切换数据库。使用FLUSHDB清空当前数据库,使用FLUSHALL清空所有数据库。使用DBSIZE查看当前数据库的key的数量。默认端口是6379。

为什么要使用 Redis

首先从高性能的角度考虑。

用户首次访问数据库内容时,从硬盘上读取数据。如果将该用户访问的数据存放在缓存中,下次就可以直接从缓存中获取,也就是从内存中获取,速度更快。当数据库对应数据改变之后,同步改变缓存中的响应数据。

然后从高并发的角度考虑。

直接操作缓存能承受的请求远远大于直接访问数据库,这样可以把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存而不用经过数据库。

Redis 数据类型

Redis 有五种数据类型

  • String:字符串,一个 key 对应一个 value。是二进制安全的,能包含任何数据,比如图片等。一个 redis 中的 字符串 value 最多可以是512M。
  • Hash:是一个键值对集合,是一个 String 类型的 key 和 value 的映射表,类似于 Map<String,Object>
  • List:是简单的字符串列表,按照插入顺序排序,可以从头或尾添加元素,底层是个链表
  • Set:是 String 类型的无序集合,是通过 HashTable 实现的
  • Zset(sorted set):String类型的集合,并且每个元素都会关联一个 double 类型的分数,成员是唯一的,score 可以重复,通过分数为成员进行从小到大的排序

Redis key

  • keys *查看 key 列表
  • exists key判断 key 是否存在
  • move key db移动到 db
  • expire key seconds为给定的 key 设置过期时间
  • ttl key查看还有多少秒过期(-1 表示永不过期,-2 表示已经过期)过期后,key 被删除
  • type key查看 key 的类型

Redis String

Redis 没有直接使用 C 语言的传统字符串,而是自己构建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,将 SDS 作为 Redis 的默认字符串表示。

C 字符串只用于作为字符串字面量,用于无须对字符串修改的地方。当需要一个可以被修改的字符串值是,就会使用SDS来表示字符串值。

struct sdshdr{
    int len;//记录已使用字节数量
    int free;//记录未使用字节数量
    char buf[];//字节数组
}
  • 相比于 C 字符串,由于 SDS 存储了更多的信息,因此可以在 O(1) 时间复杂度内实现 STRLEN 命令。
  • SDS 杜绝了缓冲区溢出的情况
  • SDS 减少了修改字符串时带来的内存重分配次数
  • SDS 不仅仅能保存文本还能保存二进制数据
  • SDS 还可以使用一部分<string.h>库中的函数

SDS 通过空间与分配和惰性空间释放两种优化策略减少内存重分配。

  • 空间预分配用于优化SDS增长操作。当需要对SDS进行空间扩展,程序不仅为 SDS 分配修改所必须要的空间,还会为 SDS 分配额外的未使用空间。
    • 如果修改SDS后长度小于1MB,程序分配和len属性同样大小的未使用空间。
    • 如果修改后SDS长度大于1MB,程序分配1MB的未使用空间
  • 惰性空间释放用于优化SDS缩短操作。当需要缩短SDS保存的字符串,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用 free 属性将这些字节数量记录起来,并等待将来使用。

String 可以存储字符串、整数或浮点数。用于常规的 key-value 缓存引用。还用于常规计数:微博数、粉丝数等。

常见操作:

  • set/get/del/append/strlen key:设置/获取/删除/在字符串尾部添加/获取字符串长度
  • incr/decr/incrby/decrby key [num]:对数字进行加减(如果key对应的value未定义,认为是0)
  • getrange key start end/set range key start newstr:获取/设置指定区间范围内的值
  • setex key seconds:set with expire
  • setnx key value:set if not exist
  • mset/mget/msetnx:同时设置多个 kv 或获取多个 k对应的v
  • getset:先 get 再 set

Redis List

Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表头或列表尾部。一个列表最多包含 2^32-1(超过40亿)元素。

Redis list 的实现是一个双向链表,可以支持反向查找和遍历。

Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。

常见操作:

  • lpush/rpush k v1 v2 ...:从左边/右边添加元素
  • lpop/rpop:从左边/右边头部出元素
  • lrange key start end:从左到右列出所有元素
  • lindex key index:按照下标获取元素
  • lrem key n value:删除 n 个 值为 value 的元素
  • ltrim key start end:截取指定范围(含)的值赋值给 key
  • rpoplpush l1 l2:将 l1 中尾部元素移动到 l2 头部
  • lset key index value:指定下标更改元素
  • linsert key before/after value1 value2:将指定值 v2 插入到 v1 的前边或后边

Redis set

Set 是 String 类型的无序集合,成员不能有重复。

Redis 中集合是通过哈希表来实现的,所以增加、删除和查找的复杂度都是O(1),同 List 一样,集合中的最大成员数也是 2^32-1。

可以基于 set 轻易实现交集、并集、差集的操作。

比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。

常见操作:

  • sadd/smembers/sismember:添加元素,列出元素,判断元素是否包含在 set
  • scard set获取集合元素个数
  • srem set value删除元素
  • srandmember set n从 set 的元素中随机取 n 个
  • spop set随机出栈
  • smove set1 set2 value将 set1 中的 value 添加到 set2
  • sdiff set1 set2 ...求 set1 和之后所有集合的差集
  • sinter set1 set2...求各个集合的交集
  • sunion set1 set2...求各个集合的并集

Redis hash

Hash 是一个 String 类型的 field 和 value 的映射表,适合用于存储对象。 每个 hash 可以存储 2^32-1 个键值对。仍然是 kv 模式,但是其中的 v 是一个键值对。

  • hset key field value为 key 添加一个 field : value 对
  • hget key field取值
  • hmset/hmget一次性设置/获取多个值
  • hgetall key获取 key 对应的所有键值对
  • hdel key field删除对应 key 中的 field
  • hexists key查看是否存在
  • hkeys/hvals获取 key 中的 fields/values
  • hincrby/hincrbyfloat key field value为 filed 添加值

Redis Zset

和set相比,sorted set增加了一个double类型的权重参数score,使得集合中的元素能够按score进行从小到大的排列。

在有序集合中,成员是唯一的,score可以重复。与set类似的,也是通过哈希表实现。

举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。

  • zadd zset score1 value1 score2 value2添加元素
  • zrangebyscore zset start end
    • 输出分数在 start 到 end 之间的 values
    • 在命令后添加 withscores 会连分数一起打印
    • 在分数前添加 (表示不包含该值
    • 在命令后添加limit start count输出从 start 之后的 count 个
  • zrem zset value删除元素
  • zcard zset统计个数
  • zcount zset min max统计区间内个数
  • zscore/zrank zset value对应value的成绩/排名
  • zrevrank/zrevrange输出倒序排名/倒序输出

redis.conf

所在目录:/opt/redis/redis.conf

单位:1k 代表 1000 bytes, 1kb 代表 1024 bytes,对大小写不敏感。

INCLUDES 部分:可以在其中通过 include path包含其他的配置文件。

GENERAL 部分:通用部分

  • daemonize:是否以守护进程方式运行
  • pidfile:以守护进程方式运行时,指定 redis 的 pid 写入的文件路径
  • port: 指定端口号,默认 6379
  • bind: 绑定的主机地址
  • timeout: 客户端空闲对应时间后关闭连接,0代表不关闭
  • tcp-keepalive: 单位是秒,设置为0时不会进行 keepalive 检测
  • loglevel: 日志级别(debug/verbose(默认)/notice/warning)
  • logfile: 日志写入的文件
  • database: 数据库的个数,默认为 16

SNAPSHOTTING 快照部分 在持久化部分说明

SECURITY 安全部分:访问密码设置,可以在客户端中通过config set requirepass password来设置密码,设置成功后,再次使用 redis 需要 auth password通过后,才能继续进行操作。

LIMITS 限制部分:

  • Maxclients: 默认为 10000
  • Maxmemory:设置默认内存大小,单位为 bytes
  • Maxmemory-policy: 到达内存大小时,过期策略:
    • volatile-lru:使用 LRU 算法,删除过期的 key
    • allkeys-lru: 使用 LRU 算法,移除 key
    • volatile-random:随机移除过期的 key
    • allkeys-random:随机移除 key
    • volatile-ttl:移除 ttl 最小的也即最近要过期的 key
    • noeviction: 永不过期,不进行移除,返回一个 error,是默认的
  • maxmemory-samples:默认选择5个,从中移除 key

持久化 RDB

简介

RDB 是指 Redis DataBase

RDB 在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读入内存。

Redis 会单独 fork 一个紫禁城来进行持久化,先将数据写入到一个临时文件,等持久化过程结束了,再用临时文件替换上次持久化好的文件。整个过程中,朱金城不进行任何 IO 操作,确保了性能。其缺点是最好一次持久化后的数据可能丢失。

Fork: 复制一个与当前进程一样的进程,新进程的所有数据(变量、环境变量、程序计数器)都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。

RDB 保存的是 dump.rdb 文件。

SNAPSHOT 配置

在 SNAPSHOT 部分中进行声明,如save 900 1表示在900秒内有1个key变动了,就会进行 RDB 存储。默认的是1分钟改了1万次,或者5分钟改了10次,或者15分钟内改了1次,会触发快照写入硬盘。

同时,在客户端使用 save 也可以进行保存。

Stop-writes-on-bgsave-error: 默认为yes,表示后台保存出错时,前台停止写入。

触发 RDB 快照

  1. 配置文件中的快照配置会触发 RDB
  2. 使用 save 或者 bgsave
    • save 命令会阻塞其他,只进行保存
    • bgsave 会在后台异步进行快照操作,同时还可以响应客户端请求
  3. 使用 flushall 命令也会产生 dump.rdb 文件,但是是空文件

快照恢复

将备份文件 dump.rdb 移动到 redis 安装目录并启动服务即可

优缺点

优点:

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高

缺点:

  • 在一定间隔时间内做一次备份,如果 redis 意外 down 掉,会丢失最好一次快照后的所有修改
  • Fork 的时候,内存中的数据被克隆,占用空间

持久化 AOF

AOF 是指 Append Only File。

AOF 介绍

AOF 以日志的形式来记录每个写操作,将所有执行过的写指令记录下来,只能追加文件不可以修改,redis 启动时会读取改文件重新构建数据。

AOF 保存到 appendonly.aof 文件

APPEND ONLY MODE 配置

在 redis.conf 的 APPEND ONLY MODE 中,是关于 AOF 的配置。

appendonly 默认为 no,不开启。修改为yes,启动 aof。当 redis 重启是,会默认重新加载 AOF 文件。当文件有异常时,使用 redis-check-aof --fix 进行修复。

appendfilename 设置保存的文件名,默认为 appendonly.aof

AOF 和 RDB 能同时开启,在启动时,会使用 AOF 文件。

Appendfsync:

  • Always: 同步持久化,每次发生数据变更会立即记录到磁盘,性能差,数据完整性好
  • Everysec: 默认设置,异步操作,每秒记录一次,可能会丢失最后一秒的数据
  • No

Rewrite 机制

AOF 采用文件追加方式,文件会越来越大,增加了重写机制,当 AOF 文件的大小超过所设定的阈值, Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集,使用命令bgrewriteaof

原理:AOF 文件持续增长而过大时,fork 一个新锦成将文件重写(先写临时文件再 rename),遍历新进程的内存中数据,每条记录有一条 set 语句。重写 AOF 文件时并没有去读旧的 AOF 文件,而是将整个内存中的数据库内容重写了一个新的 AOF 文件,这点和快照有些类似。

Redis 会记录上次重写时的 AOF 的大小,默认配置是 AOF 文件大小是上次 rewrite 后大小的一倍并且文件大于 64M 时触发。分别在设置文件中的 auto-aof-rewrite-percentageauto-aof-rewirte-min-size中设置。

优缺点

优点:更少的数据丢失 缺点:相同数据集数据 AOF 文件大小远大于 RDB 文件,恢复速度更慢;AOF运行效率比 RDB更慢。

两种持久化的选择

RDB 能够在指定时间间隔对数据进行快照存储

AOF 记录每次写操作,在重启时重新执行来恢复原始数据。AOF 以 redis 协议追加保存每次写操作到文件末尾。Redis 能对 AOF 文件进行重写,使得 AOF 体积不至于过大。

如果只希望数据在服务器运行的时候存在,可以不使用任何持久化方式。

如果同时开始两种持久化方式:当 redis 重启的时候会优先载入 AOF 文件来恢复原始数据,这是因为通常 AOF 文件数据集更加完整。

但是,也不建议只使用 AOF,因为 RDB 更适合用于备份数据库,快速重启,可以作为一个以防万一的手段。

性能建议:

  • RDB 文件只做后备用途,建议只在 Slave 上持久化 RDB 文件,并且只要 15 分钟备份一次即可
  • 如果开启 AOF,好处是最坏也只会丢失不超过两秒的数据,代价是带来了持续的IO。并且 rewrite 的最好将重写的新数据写到新文件必然会造成阻塞,应该尽量减少 rewrite 的频率。
  • 如果不开启 AOF,仅靠主从复制也可以实现高可用性。这样能省掉 IO 同时减少了 rewrite 带来的系统波动。代价是主从服务器都挂掉时,会丢失十几分钟的数据,启动时也要比较主从中的 RDB 文件,载入比较新的那个。

Redis 事务

事务本质是一组命令的集合,一个事务中的所有命令都会序列化,按顺序串行化执行而不会被其他命令插入。

事务用于在一个队列中,一次性、顺序性、排他性的执行一系列命令。

三个阶段:

  • 开启
  • 入队
  • 执行

使用:

  • 使用MULTI命令开启一个事务
  • 输入多个命令,Redis 将会将他们放到队列中
  • 使用EXEC执行事务中的所有命令
  • 使用DISCARD取消事务
  • 使用WATCH key[key...]监视一个或者多个 key ,如果事务执行之前这写 key 被其他命令改动,该事务会被打断
  • 使用UNWATCH取消 WATCH 命令对所有的 key 的监视

特性:

  1. 单独的隔离操作:事务中的所有命令都会序列化、按顺序执行。事务在执行过程中,不会被其他客户端发送来的命令请求打断
  2. 没有隔离级别的概念:队列中命令未提交之前不会实际的被执行
  3. 不保证原子性:一个事务中,如果有一条命令执行失败,其余命令仍然可以执行成功,没有回滚

Watch 监控

悲观锁:每次在操作之前都上锁。

乐观锁:每次去取数据的时候认为别人不会修改,所以不会上锁,但是在更新的时候会判断在此期间有没有人更新这个数据,可以使用版本号机制,提交版本必须大于记录当前版本才能执行更新。

watch 命令类似于乐观锁,在事务提交时,如果 key 的值已经被别的客户端改变,整个事务队列都不会被执行。

Redis 发布订阅

发布订阅是进程间的一种消息通信模式:发送者发送消息,订阅者接收消息。

一个频道 channel 可以被多个客户端 client 订阅。当有新消息通过 PUBLISH 命令发送给 channel 时,这个消息就会被发送给订阅它的客户端们。

Redis 主从复制

主机数据更新后,根据配置和策略,自动同步到备用机的主从机制,Master 以写为主, Slave 以读为主。主要用于读写分离和容灾恢复。

在从库中配置 slaveof 主库ip 主库端口。每次与 master 断开之后,都需要重新连接,同时能在 redis.conf 中配置。可以使用info replication查看状态。

复制的缺点是:由于所有写操作都是先在 master 上操作,然后同步更新到 slave 上,所以主从同步会有一定的延迟。

中心化:一个 master,多个 slave

在 slave 中不能进行写操作,会报错。

当 master 关闭后, slave 仍然是 slave。当 master 重新启动后, slave 仍然能进行主从复制。

当 slave 关闭后,如果没有在 redis.conf 中进行配置,则 slave 重新启动后,会成为自己的 master,不再是 slave。

去中心化:主从链

上一个 slave 可以是下一个 salve 的 master,slave 同样可以接受其他 slave 的连接和同步请求,形成一个主从的链,可以有效减轻了 master 的写压力。其中,除了 头部的 master 外,其余的都是 slave。

中途变更转向:会清除之前的数据,重新建立拷贝最新的。

反客为主

当 master 关闭后,slave 可以执行slaveof no one成为 master,和剩余的 slave 构成新的主从库。

复制原理

  • Slave 启动成功连接到 master 后会发送一个 sync 命令。
  • Master 接到命令启动后台的存盘进程,同时收集所有接受到的用于修改数据集的命令,在后台进程执行完毕之后, master 将传送整个数据文件到 slave,以完成一次完全同步。
  • 全量复制:slave 接收到数据库文件数据后,将其存盘并加载到内存
  • 增量复制:master 继续讲新的收集到的修改命令传送给 slave,完成同步
  • 只要重新连接 master,一次全量复制将被自动执行

哨兵模式

用于从后台监控 master 是否故障,故障了根据投票数自动将 slave 转换为 master。

步骤:

  • 在 redis 目录下新建sentinel.conf文件
  • 在该文件中配置sentinel moniter dbname dbIP dbport count,其中 count 表示得票数为 count 可以成为 master
  • 启动哨兵redis-sentinel /redispath/sentinel.conf
  • 如果之前的 master 重新启动,会成为现在哨兵指定的 master 的 slave

缓存

高性能:当用户第一次访问数据库中数据时,会比较慢,因为是从硬盘上读取的。将用户访问的数据存放在缓存中,这样下一次就可以直接从缓存中获取了,操作缓存就是操作内存,速度快。当数据库中对应数据改变后,同步改变缓存数据即可。

高并发:把数据库的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存,而不用经过数据库。

使用 Redis 做缓存的原因

  • redis 支持更丰富的数据类型,能支持更复杂的应用场景
  • redis 支持数据的持久化
  • redis 支持集群
  • redis 使用单进程的多路 IO 复用模型,采用 epoll

缓存雪崩

缓存同一时间大面积失效(设置缓存时才用了相同的过期时间),后边的请求都落到数据库上,造成数据库短时间承受大量请求而崩溃。

解决:

  • 事前:尽量保证 redis 集群的高可用性,选择合适的内存淘汰策略
  • 始终:限流、降级、mysql缓存等防止 mysql 崩溃
  • 事后:使用 redis 持久化机制保存的数据尽快恢复

缓存穿透

当查询一个不存在的数据时,由于缓存在不命中时被动写入,如果在存储层查不到数据则不写入缓存,导致这个不存在的数据每次请求都要去存储层查询,失去了缓存的意义。并且可以利用不存在的 key 频繁攻击应用。

解决办法:

  1. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,避免了对底层存储系统的查询压力。
  2. 如果查询返回结果为空,仍然将这个空结果进行缓存,但是设置一个较短的过期时间。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决办法:

  1. 使用互斥锁,当 key 对应的 value 为空的时候,先加锁,然后从数据库加载,加载完毕后释放锁。其他线程获取锁失败后,睡眠 50 ms 后再试。类似于 redis 的 setnx
  2. 异步更新:不使用缓存服务提供的过期时间,而是业务层在数据中存储过期时间信息,由业务程序判断是否过期并更新,在发现了数据即将过期时,将缓存的时效延长,程序可以派遣一个线程去数据库中获取最新的数据,其他线程这时看到延长了的过期时间,就会继续使用旧数据,等派遣的线程获取最新数据后再更新缓存。