Redis从零开始学习

Olivia的小跟班 Lv4

题记

记录我的redis学习笔记。

1.数据库的演进及Nosql的初步认知

为什么要用 Nosql ?

咱们一起来看看数据库的发展过程

1、单机的 MYSQL 时代

应用访问数据库是 应用 – DAL 数据库访问层 – DB 数据库

image-20210815204834911

在单机的 MYSQL 时代,数据都不会太大,而且网页也是静态网页,一般网站的访问量也小,因此单数据库就完全够用了

上面这种网站,瓶颈就会很明显:

  • 数据量变得大了,一个机器放不下如何处理
  • 数据的索引 (B+ Tree),一个机器的内存放不下了如何处理
  • 读写的访问量,一个服务器承受不了了如何处理

随着历史进程的演进,上面这种网站必然会面临如上问题,就必须要解决

2、Memecache + MYSQL + 垂直拆分

为了解决上面的问题,一个机器处理不过来,我们就放多个机器,但是如何保证数据的一致性呢?

因此就想到了读写分离,专门指定一个数据库用于写数据,其他数据库用于读取数据,并且其他的数据库会同步用于写数据的 MYSQL 中的数据

img

渐渐的发现每次请求,都需要去操作数据库,这样耗时耗力,需要改变

因此在访问数据库之前,加入了缓存服务器 Memcache,第一次读取数据库,第二次读取的时候在数据库不改变的情况下,读取 Memcache 中的数据,减小数据库的压力

img

3、分库分表 + 水平拆分 + MYSQL 集群

随着技术发展,业务也跟着飞越发展,也就带来了更多的问题

最开始我们使用的 MYSQL 使用的引擎的是 MyISAM,他是表锁,十分影响效率,当在高并发的情况下,问题尤为明显

后面就有了 MYSQL 的 InnoDB 引擎,他是行锁,随着业务的不停增长,就有了 MYSQL 集群

img

使用集群的方式,每一个集群存储一部分数据,若数据量仍然上升,那么继续增加集群部署

4、现在

由于现代的数据量真的非常大了,大数据时代了,MYSQL 关系型数据库就不够用了,现在数据量多,变化快,以前使用 MYSQL ,设计的时候,就得把所有字段,可能用到的字段,全部想清楚,设计明白,对于现在瞬息万变的时代,这就很难了

现在就会期望,例如存储文件的有文件服务器,存储图片的有图片服务器,有专门的数据库来存这些数据,那么 MYSQL 的压力就会小很多

现在 的互联网项目简单来看是这个样子的

img

对于这样的互联网项目,就非常需要 Nosql 了,Nosql 能处理上述出现的问题

Nosql 是什么?

Nosql

Not only sql , 不仅仅是 sql

泛指非关系型数据库,随着 web2.0 互联网的诞生,传统的关系型数据库很难对付 web2.0 时代了

例如现在互联网用户需要存储个人信息,定位信息,社交网络等等数据,Nosql 对于这些数据存储不需要用一个固定的格式就可以存储,而且很轻易就可以横向扩展,例如 Map<string,interface {}> ,使用键值对来控制就很 nice 了

Nosql 的特点

  • 方便扩展,因为数据之间没有关系,很容易扩展
  • 高性能,例如 redis 1 秒能写 8 万次,能读 11 万次,且 Nosql 的存储是记录级别的,是一种细粒度的缓存,因此性能高
  • 数据类型多样,不需要事先设计数据库

咱们来对比一下传统型的关系型数据库(RDBMS)和 Nosql

RDBMS:

  • 结构化组织
  • sql 语句
  • 数据和关系都存在单独的表中
  • 数据操作,数据定义语言
  • 严格的一致性
  • 事务处理 等等

Nosql:

  • 不仅仅是数据
  • 没有固定的查询语言
  • 键值对存储,列存储,文件存储,图形数据库 例如社交关系等等
  • 最终一致性
  • CAP 定理和 BASE 原理
  • 高可用,高性能,高可扩展

顺便一起分享一下 3 V 和 3 高分别是什么:

3 V,主要是用来描述问题的:

  • 海量数据 volume
  • 多样性 variety
  • 实时性 velocity

3 高,主要是用来描述对程序的要求

  • 高并发
  • 高性能
  • 高可扩

Nosql 的四大分类

KV 键值对:

  • Redis

C 编写的,单进程的 Nosql, 阿里,腾讯,百度,字节等公司都在用使用

文档型数据库:

  • Mongdb

Mongdb 是一个基于分布式文件存储的数据库,C++ 编写的,主要用来处理大量的文档

Mongdb 还是一个介于关系型数据库和非关系型数据库中间的产品,他是非关系型数据库中功能最丰富的,最像关系型数据库的非关系型数据库

列存储型数据库:

  • HBase
  • 分布式文件系统

图关系型数据库:

  • Neo4j
  • InfoGrid

图关系型数据库不是存储图形,而是存放关系,例如社交关系网络

img

咱们来对比一下上述四种分类:

img

图片来源于网络,这张图片已经很清晰的描述了 KV 键值对,文档型数据库,列存储数据库,图形数据库的案例,数据模型,优缺点,现在对于 Nosql 会有了一个大体的认知了吧。

2.Redis是什么

Redis 是什么?

Redis(Remote Dictionary Service),远程字典服务

是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API

Redis 是免费且开源的,是当下最热门的 Nosql 技术之一,他也被成为结构化数据库

Redis 支持的语言有这么多

img

img

Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现 master - slave (主从同步)

Redis 能干嘛?

Redis 能干的事情非常的多,咱们列举一些:

  • 内存存储,持久化。 数据存储在内存中,若服务崩溃或者服务器宕机,内存中的数据就会丢失, 持久化相当重要,Redis 里面的持久化有 RDB,AOF
  • 可以用于高速缓存,Redis 效率很高
  • 做发布订阅系统
  • 做地图信息分析
  • 做计数器,计时器 等等

Redis 有啥特性

  • 多样的数据类型
  • 持久化
  • 集群
  • 事务处理

学习 Redis 需要用到的资料

官网:https://redis.io/

中文网站:redis.cn

下载地址:

image-20230606013158084

如何安装 Redis

window 下安装 Redis

1、官网上下载 windows Redis 的安装包:Releases · tporadowski/redis

2、解压安装包

解压 zip 安装包 Redis-x64-5.0.10.zip,这个压缩包现在有 14 M 了

img

  • redis-server

redis 服务端

  • redis-client

redis 客户端

  • redis-check-aof 和 redis-check-rdb

redis 的持久化工具

  • redis-benchmark

redis 的检测工具

3、打开 redis-server 进入服务端,可以看到如下界面

image-20230606013349003

4、打开客户端 redis-cli ,可以尝试输入 ping 命令,得到结果为 PONG 说明连接成功

image-20230606013417773

尝试 set key 和 get key ,没有问题

这里只是简单介绍一下 windows 下 redis 的安装和使用, 其实绝大部分的时间我们还是在 linux 下进行操作的,Redis 官网也是强烈建议我们在 Linux 下进行使用

linux 下安装 Redis

不写了,我个人都是在docker上用redis。

redis 基础知识

redis 默认是有 16 个数据库的,咱们可以查看 redis.conf 文件中有定义

image-20230606013617252

redis 默认是使用第 0 个数据库,咱们可以使用 select 指令来切换数据库

select

可以通过 select 指令来切换数据库

image-20230606013649115

可以看到 0 号数据库是有数据的, 3 号数据库还没有数据

keys

可以通过 keys * 指令来获取当前数据库所有 key , 一般很少用这个命令,因为数据量很大的时候,使用这个命令会很慢

image-20230606013714713

flushdb

清空当前数据库的 key 和 value

flushall

清空所有数据库的 key 和 value

image-20230606013753767

那么为什么 redis 的默认端口号是 6379 呢?

因为 redis 的作者是一个女明星 MERZ 的粉丝,MERZ 对应到 9 宫格手机键盘上就是 6379

redis 是单线程的

redis 是单线程的,但是不影响他很快,官方表示,redis 是基于内存操作的,CPU 不是 redis 的瓶颈,机器内存和网络带宽才是 redis 的瓶颈。

redis 是 C 语言写的,redis 官方提供的数据为 十万 + QPS

redis 单线程为什么这么快?

1、高性能的服务器一定是多线程的吗?这是一个误区,单线程一样可以非常快

2、多线程的效率一定比单线程的效率高吗?这也是一个误区,多线程会有 CPU 上下文切换,非常耗时

效率比较: CPU > 内存 > 硬盘

redis 核心:

redis 是将所有的数据都放到内存中,所以单线程去操作数据就会非常快,效率就很高,而多线程会出现 CPU 的上下文切换,对于内存而言,没有上下文切换系统效率是最高的。

3.Redis 数据结构之string和list基本使用及熟悉

Redis五大数据结构

Redis 是一个开源(BSD 许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。

Redis 内置了 复制(replication),LUA 脚本(Lua scripting), LRU 驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis 哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)

redis key 的基本命令

ping
查看客户端是否连接成功

set key value
设置 key 和 value

get key
获取 key 的值

keys *
获取所有 key

move key 1
删除 key

expire key number
对 key 设置过期时间

ttl key
查看 key 的剩余有效时间

type key
查看 key 的类型

EXISTS key
查看是否存在 key

image-20230612154757845

string 字符串

设置单个值

APPEND key value
在字符串后面追加字符串

STRLEN key
计算 key 对应 value 的长度

incr key
对 key 的值 +1

decr key
对 key 的值 -1

INCRBY key number
对 key +number

DECRBY key number
对 key -number

GETRANGE key start end
获取字符范围,获取 key 对应 value 的 第 start 到 end 的字符串

GETRANGE key 0 -1 和 get key 是一个效果

setrange key offset value
替换 key 对应 value 从左到有 offset 个偏移量后面的字符串

image-20230612155644904

setex key second value (set with expire)
设置 key 对应的过期时间 ,

ttl key
查看 key 的有效时间

setnx key value (if not exist)
如果 key 不存在,那就设置 , 这个指令通常用于分布式锁

image-20230612155930107

批量设置多个值

mset key value [key value …]
设置多个 key value

mget key [key …]
获取多个 key 对应的值

MSETNX key value [key value …]
设置多个值,如果 key 不存在,则设置值,这是一个原子操作,要么全部成功,要么全部失败

image-20230612160254332

设置对象

设置对象,咱们可以将 redis 的 key 设计成为我们业务需要的字符 key ,例如下面的例子

image-20230612161209128

  • getset

先 get 值,再 set 值

image-20230612161301599

string 字符串的使用场景

string 类型的使用场景非常多,如下列举一些:

  • 计数器
  • 统计多个单位的数量
  • 对象缓存存储
  • 分数,粉丝数,点赞量 等等

List

List 是基本数据类型,即列表

image-20210818201443260

在 redis 的 List 里面,咱们可以模拟栈,队列,阻塞队列等等

LPUSH key element [element …]

  • 从左边插入数据到 key 中,这个 key 是有个 list , list 类型的指令都是 开头L

RPUSH key element [element …]

  • 从右边插入数据到 key 中

LRANGE key start stop

  • 查看 list 的范围,LRANGE key 0 -1查看当前 list 的全部值

image-20230612162156890

LPOP key [count]
从 list 的左边移除数据,默认是 1 个 ,即为移除 list 的第一个元素

RPOP key [count]
从 list 的右边移除数据 , 即为移除 list 的最后一个元素

image-20230612162800550

  • LINDEX key index

查看 list 中第 index 个值,从 0 开始

image-20230612162943503

  • LLEN key

查看 list 的长度

image-20230612163110065

  • LREM key count element

删除 list 中指定的元素,可以指定删除多少个

LREM 删除 list 中不存在的数据,返回 0 ,0 为失败,

LREM 删除 list 中存在的数据,若期望删除 5 个,实际上只有 2 个,那么 redis 会返回 2 ,删除成功

image-20230612163657665

  • LTRIM key start stop

裁剪,剪枝,获取 list 的某一段,并裁剪下来

image-20230612163906646

  • RPOPLPUSH source destination

从源 list 中的右边取出数据,从目的 list 的左边加入数据

image-20230612164051869

  • LSET key index element

将 list 中的对应索引的数据,替换成 element ,如果索引本没有数据,则会报错

image-20230612164424670

  • LINSERT key BEFORE|AFTER pivot element

    list 中在指定元素的前面或者后面添加数据

image-20230612164624598

  • list 实际是一个链表,before node , after node ,left ,right 都是可以插入数据的
  • 如果 key 不存在,会创建新的 key ,即创建新的链表
  • 如果 key 存在,正常加入数据
  • 如果移除了所有的值,那么 key 就不存在了
  • 在 list 的两边插入和删除数据效率最高,从中间操作数据,效率相对较低

list 列表的应用场景:

  • list 可以做消息队列(FIFO),也可以做栈(FILO)

4.set 集合,hash 哈希,zset 有序集合初步认知

set集合

img

set 集合里面的数据是不能重读的

SADD key member [member …]
向 set 集合中添加元素

SMEMBERS key
查看集合中的所有元素

SISMEMBER key member
查看某一个数据是否在集合中

SCARD key
查看集合数据的个数,也就是集合的长度

SREM key member [member …]
移除集合中指定的元素

image-20230612170405317

  • SRANDMEMBER key [count]

随机获取集合里面的数据,可以指定个数

image-20230612170831341

  • SPOP key [count]

随机删除集合中的任意元素

image-20230612170942985

  • SMOVE source destination member

从指定的集合中取出一个元素,放到另外一个集合中

image-20230612171249387

  • SUNION key [key …]

取并集,对两个集合取并集

  • SINTER key [key …]

取交集,对两个集合取交集

image-20230612171453582

set 应用场景可以有,多个用户的共同关注,好友推荐,共同的粉丝,功能的爱好等等

hash 哈希

img

Map 集合,key-map,此时 key-value 是一个集合,本质上和 string 类型没有啥区别,还是一个简单的 key-value 形式

HSET key field value [field value …] / HMSET key field value [field value …]
在 hash key 中添加 1 个或者多个 key-value

HGET key field
获取 hash 里面的一个 key 对应的 value

HMGET key field [field …]
获取 hash 里面的多个 key 对应的 value

HGETALL key
获取 hash 里面所有的 键值对

image-20230612172431590

  • HDEL key field [field …]

删除 hash 里面的 key,一个或者多个

image-20230612172900297

  • HLEN key

获取 hash 的长度,也就是 key-value 的对数

image-20230612173005419

HINCRBY key field increment
在集合中的某个 key 上增加值,若值是正数,则是加,若值是负数则是减

HSETNX key field value
向 hash 中加入键值对,若不存在则添加,存在则添加失败

image-20230612173235588

hash 的应用场景有,经常变更数据的地方,尤其是一些用户的信息,经常变动的信息

hash 更适合对象的存储,string 更加适合字符串的存储

zset 有序集合

img

zset 也是集合,只是在 set 的基础上加上了一个值

ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member …]
向有序集合中添加 分数 和 值,可以添加多个

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
按照分数的顺序排序,-inf 为负无穷,+inf 为正无穷

ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]
查看有序集合里面的所有的值

image-20230612173737875

  • ZREM key member [member …]

删除有序集合里面的数据

image-20230612173848192

  • ZREVRANGE key start stop [WITHSCORES]

对有序集合从大到小排序

image-20230612174152473

其余的 api 我们可以到 redis 官网上去进行学习和实践,可以查看 redis 官方的中文文档,(redis.cn)

zset 的应用场景有:

set 排序,存储班级成绩信息,工资信息等等,用于排序和筛选的

带有权重的数据或者消息,排行榜的实现等等。

5.Redis的三种特殊数据类型

redis 三种特殊的数据类型

  • Geospatial 地理位置
  • Hyperloglog 基数统计
  • Bitmap 位图场景

Geospatial地理位置

redis 3.2 版本就推出了 Geospatial

官方文档上可以详细的看到用法:

Redis GEOADD 命令

img

Geospatial 可以使用在如下场景:

  • 附近的人
  • 打车计算距离
  • 朋友定位
  • 等一系列跟定位有关的场景

Geospatial 只有 六个命令

GEOADD

添加地理位置

  • 有效的经度从 - 180 度到 180 度。
  • 有效的纬度从 - 85.05112878 度到 85.05112878 度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。

GEOADD key [NX|XX] [CH] longitude latitude member [longitude latitude member …]
添加经纬度,城市名

image-20230612203353183

GEOPOS

  • GEOPOS key member [member …]

获取指定城市的经纬度信息

image-20230612203359393

GEODIST

GEODIST key member1 member2 [m|km|ft|mi]
其中有如下 4 个单位:

m :米

km:公里

ft:英尺

mi:英里

获取两个城市之间的距离

image-20230612203534257

GEOHASH

  • GEOHASH key member [member …]

返回一个或者多个 GEOHASH 表示的元素, 返回 11 个字符 Geohash 字符串

image-20230612204640464

GEORADIUS

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOR
指定经纬度作为原点,指定半径,查询在指定范围内的城市,这些城市都是在我们自己的集合里面

image-20230612205203398

GEORADIUSBYMEMBER

GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
找出位于指定元素周围的城市

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> GEORADIUSBYMEMBER city tianjin 1000 km
1) "nanjing"
2) "tianjin"
3) "shanghai"
127.0.0.1:6379> GEORADIUSBYMEMBER city tianjin 500 km
1) "tianjin"
127.0.0.1:6379> GEORADIUSBYMEMBER city tianjin 5000 km
1) "chongqing"
2) "chengdu"
3) "shenzhen"
4) "changsha"
5) "shanghai"
6) "nanjing"
7) "tianjin"

Geospatial 底层的原理就是使用 Zset 有序集合来实现的,我们可以使用 Zset 有序集合的命令来操作 Geo ,咱们是用 Zset 的指令来感受一波

image-20230612205453510

Hyperloglog 基数统计

基数是个啥?

基数,就是不重复的数,例如:

A = {1,2,3,4,5}

B = {2,3,4,5,6}

那么 A 和 B 的取并集的基数就是 6

简介

Hyperloglog 是 redis 2.8.9 版本开始有的这种数据结构

redis hyperloglog 基数统计也是一种算法

例如有这样的应用场景:

  • 网页访问人数统计,同一个用户多次访问网站,也是只算作 1

    传统的方式是使用 set 集合的方式来保存 id,统计 set 里面 id 的个数,来计算人数

    这种方式也没有问题,但是若数据量很大的时候,就会非常麻烦,因为我们拿 id 是没有用的,我们只需要计数而已

优点

img

Hyperloglog 占用的内存空间是固定的,2^16 次方,只需要占用 12 KB 内存,从内存的角度来技术选型,Hyperloglog 是首选哦

PFADD key element [element …]
向 Hyperloglog 中添加一个或者多个元素

PFMERGE destkey sourcekey [sourcekey …]
将多个 Hyperloglog 取并集,得到一个结果 Hyperloglog ,里面的数据是不会重复的

image-20230612210321757

Bitmaps 位图场景

Bitmaps 位图,位存储

一般用于,统计用户信息,活跃,不活跃,

登录,不登录

打卡,不打卡 等等,两种状态

Bitmaps 位图,也是一种数据结构,是操作二进制的方式来进行记录的,只有 0 和 1 两种状态

img

例如我们举个例子,来记录着一周每天的心情,1 是开心,0 是沮丧

  • SETBIT key offset value

设置 bit 位的值

  • GETBIT key offset

获取 bit 位的值

image-20230612210651229

6.Redis事务处理和监控事务

我们学过的事务都是保证原子性的,但是 redis 的事务中执行多个指令,是不保证原子性的

redis事务的本质

就是一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程,是按照顺序执行命令的,他们拥有

  • 一次性
  • 顺序性
  • 排他性

redis 的事务没有隔离级别的概念

redis 事务中,命令是这样执行的

命令放在事务中,并没有马上执行,而是发起执行命令的时候才会执行,通过 exec 触发

img

redis 是单条指令保证原子性,但是事务不保证原子性

执行一个事务的流程是这个样子的:

  • multi 开启事务
  • 各种命令入队
  • exec 执行事务

开启事务

MULTI

开启一个事务

EXEC

执行事务里面的指令

执行事务完毕后,需要再使用事务,那么需要再次开启事务

image-20230612211258960

放弃事务

DISCARD

放弃最近开启的事务

image-20230612211358511

事务出错

事务出错分为两种:

  • 编译型出错
  • 运行时出错

编译型出错

代码编写出错,无法通过编译,此时事务里面编写的指令,全部执行失败

image-20230612211605625

一旦编译出错,事务里面的所有指令都不会执行

运行时出错

程序运行时某一个指定的某个逻辑出现问题,仅仅影响本条执行的执行,并且本条指令会跑出异常,不影响事务中其他指令的执行

image-20230612211801337

根据上述结果,incr 指令的用法正确,但是 incr 只能对数字进行操作,name 是一个字符串,无法做自增操作,因此运行时报错,事务中不影响其他的指令执行

redis 的 watch 监控

img

watch 监控,可以说说 乐观锁 和 悲观锁

乐观锁:

  • 很乐观,认为什么时候都不会出问题,所以不会上锁,就更新数据的时候会去判断一下在此期间若数据发生了变化
  • 乐观锁会先获取一个基础数据版本
  • 更新数据的时候,会比较这个版本是否发生变化,若发生变化,则更新失败,若未发生变化,则更新数据

悲观锁:

  • 很悲观,无论什么时候都认为会出问题,都要加锁

redis 的监控测试

用 watch 来加锁模拟银行取钱

  • money 账户 有 2000

  • outer 账户 有 500

  • money 账户 给 outer 账户 500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> set money 2000
OK
127.0.0.1:6379> set outer 500
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 500
QUEUED
127.0.0.1:6379(TX)> INCRBY outer 500
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 1500
2) (integer) 1000

开始测试模拟多个线程来操作

redis 客户端 1

  • money 账户 有 2000
  • outer 账户 有 500
  • money 账户 给 outer 账户 500 ,还未开始执行

image-20230612212633220

客户端 1 的事务操作还未开始执行,客户端 2 就进行了如下操作

image-20230612212656464

此时执行客户端 1 的 exec 指令

image-20230612212717320

发现客户端 1 执行 exec 的时候,是一个 nil ,说明变更失败,是因为 watch 监控到 money 有变动,因此事务执行失败

unwatch

发现事务执行失败了,需要使用命令 unwatch 解除监控

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 1000
QUEUED
127.0.0.1:6379(TX)> INCRBY outer 1000
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 9000
2) (integer) 1500

这就是 redis 实现乐观锁的操作,一般使用场景会放到秒杀系统里面进行应用。

7.Redis配置文件详解

单位

redis 启动的时候会去读配置文件 redis.conf

image-20230613004048378

1
2
3
4
5
6
1k => 1000 bytes
1kb => 1024 bytes
1m => 1000000 bytes
1mb => 1024*1024 bytes
1g => 1000000000 bytes
1gb => 1024*1024*1024 bytes

redis 里面单位是不分大小写的,例如,1GB 1Gb 1gB 表示的都是同一个意思

INCLUDES包含

image-20230613004141940

环境中使用的 redis.conf 可以包含其他的 redis.conf,他们会整合成一个 配置文件来提供使用

NETWORK 网络

image-20230613004247931

1
2
3
bind 0.0.0.0 
protected-mode yes
port 6379

bind:绑定地址,若绑定 127.0.0.1 是本地访问,若需要远程访问,可以绑定一个真实的 ip 地址

protected-mode:保护模式是否开启,默认是开启

port:端口设置,默认端口是 6379,我们也可以修改成其他的可用端口,例如集群的时候就会修改到端口

GENERAL 通用

通用配置,常用的有

1
2
3
4
5
6
7
daemonize yes
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
always-show-logo no
set-proc-title yes

daemonize:是否以守护进程的方式运行,默认是 no,我们需要的话可以修改成 yes

pidfile:以后台方式运行 redis ,我们就需要制定一个 pid 文件

loglevel:日志等级

1
2
3
4
5
6
291 # Specify the server verbosity level.
292 # This can be one of:
293 # debug (a lot of information, useful for development/testing)
294 # verbose (many rarely useful info, but not a mess like the debug level)
295 # notice (moderately verbose, what you want in production probably)
296 # warning (only very important / critical messages are logged)

和我们项目中用到的一致:

  • debug:用于开发和测试的调试信息

  • verbose:罕见有用的信息

  • notice:提示信息,期望在生产环境中看到的

  • warning:告警信息,重要的信息会被打印到日志中

logfile:指定 redis 日志文件路径

databases:redis 数据库, 默认是 16 个

always-show-logo:是否总是显示 reids logo ,就是下面这个 logo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
               _._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 29303
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

SNAPSHOTTING 快照

快照,这里也就是 redis 的持久化,在规定的时间里面,执行了多少次操作,就会持久化到文件中

redis 的持久化有两种

  • RDB
  • AOF

redis 是内存数据库,程序宕机或者断电都会导致数据丢失,因此 redis 就有这样的持久化策略

1
2
3
# save 3600 1
# save 300 100
# save 60 10000

save 3600 1
如果 3600 s 内 redis 发生了 1 次操作, 那么就会做数据持久化

save 300 100
如果 300s 内 redis 发生了 100 次操作, 那么就会做数据持久化

save 60 10000
如果 60s 内 redis 发生了 10000 次操作, 那么就会做数据持久化

后面咱们详细写到持久化的时候,再来细说和实际测试持久化的事情

1
2
3
4
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dir ./

stop-writes-on-bgsave-error:redis 持久化出错是否继续执行 redis 程序,默认是打开的,持久化错误不能影响 redis 程序的执行,需要正常进行下去

rdbcompression:持久化的文件是否需要压缩,默认是开启的,这个功能会比较消耗性能

rdbchecksum:保存 rdb 持久化文件的时候,会进行错误检查校验

dir:rdb 文件的保存目录

REPLICATION 主从复制

主从复制的配置在这个位置

image-20230613004623406

详细的后续写到主动复制的时候详细的写

SECURITY 安全

redis 的安全相关配置文件,咱们来看看密码的事情

1
2
3
4
# The requirepass is not compatable with aclfile option and the ACL LOAD
# command, these will cause requirepass to be ignored.
#
# requirepass foobared

redis 默认是不设置密码的,但是我们为了远程访问安全,必须要设置密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 888888
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "888888"

退出 redis 客户端,再次连接 redis-server
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.

对 redis 设置了密码之后,退出 redis 客户端,再次连接 redis-server,发现 redis 报错权限不足,此时我们使用 redis-client 连接 redis-server 就需要密码了

1
2
3
4
5
6
7
127.0.0.1:6379> auth 888888
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "888888"

CLIENTS 客户端

限制客户端连接数

1
maxclients 10000Copy

maxclients

redis 限制客户端连接数 默认是 10000 个,这个数字我们也可以修改成我们所期望的

MEMORY MANAGEMENT 内存管理

1
2
maxmemory <bytes>
maxmemory-policy noeviction

maxmemory:redis 配置的最大内存容量,单位是字节

maxmemory-policy:redis 内存达到上限之后的处理策略是什么样的,有如下几个选型:

  • noeviction:永不过期,返回错误

  • volatile-ttl:删除即将过期的

  • allkeys-random:随机删除 key

  • volatile-random:随机删除即将过期的 key

  • allkeys-lru:删除 lru 算法的 key

  • volatile-lru:只对设置了过期时间的 key 进行 LRU

LRU(Least recently used,最近最少使用)

APPEND ONLY MODE 追加模式(aof 配置)

APPEND ONLY MODE 用于 AOF 的持久化

1
2
3
4
5
6
7
appendonly no

appendfilename "appendonly.aof"

# appendfsync always
appendfsync everysec
# appendfsync no

appendonly:默认是关闭的,redis 默认使用的是 rdb 持久化模式,基本是够用的

appendfilename:aof 持久化 文件的名字

appendfsync:持久化同步策略

  • always 每次修改都会同步,消耗性能
  • everysec 每秒执行一次同步,异常情况下会丢失上 1 s 的数据
  • no 不主动同步数据,系统会自动同步,这种方式速度最快,但是丢失数据的几率大

对于 redis 配置文件常用常改的位置都给大家分享到了,需要多多在实际学习和工作中多多应用,熟能生巧。

8.Redis 持久化 RDB 和 AOF

Redis 持久化

redis 是内存数据库,如果不将内存中数据库保存到磁盘上,那么服务器一旦宕机,或者 redis 进程退出,不仅数据会被丢失,服务器中的数据库状态也会被丢失。

因此 redis 提供了持久化的功能

redis 的持久化分为 RDB 和 AOF

RDB (Redis DatabBase)

在主从复制中,rdb 文件都作为备用的,放在从机上面

img

在指定时间间隔内将内存中的数据集快照写入到磁盘中,这就是快照 snapshot ,恢复快照的时候,是把快照文件读入到内存中。

redis 通过 fork 的方式创建一个子进程来专门做持久化的动作,

  • 先将数据写入到一个临时文件中,待持久化过程结束,再用这个临时文件替换上一次的持久化好的文件

整个过程中,主进程是不进行任务 IO 操作的,这就保障了极高的性能

如果需要进行大规模的数据恢复,且对于数据的完整性要求不那么敏感和严格,选择 RDB 的持久化方式比 AOF 的持久化方式更优,更加高效。

RDB 虽然性能高,但是在 最后一次持久化后的数据可能会被丢失,redis 默认就是使用的 RDB 持久化方式,一般情况下也不需要修改

1
2
3
4
5
6
save 60 3

# The filename where to dump the DB
dbfilename dump.rdb

dir ./

我们设置 60s 内若 有操作 redis 3 次,那就做一次持久化

dump.db 文件是生成在 dir 目录下的,我们这里的 dir 目录是 /root

进行上述操作之后,我们发现 /root 生成了 dump.rdb 文件 ,咱们将该文件删除掉,再尝试一次,果然也是正常生成的。

持久化的触发机制

  • 按照 save 的规则满足的情况下,就会触发持久化,例如上述的 60s 操作 redis 3 次就会触发 1 次持久化
  • 执行 flushall 命令的时候,也会触发持久化,生成 dump.db 文件
  • 退出 redis 的时候, 也会触发持久化,生成 dump.db 文件

备份就会自动生成一个 dump.db 文件

如何恢复持久化文件

1、只需要将 dump.db 文件放到 redis 的启动目录即可,redis 启动的时候会将该文件读入到内存中,进行数据恢复

2、查看 redis 的启动目录可以这样做

image-20230613143147628

这一块的配置,我们基本上不需要修改太多,默认的配置基本就够用了

RDB 的优势

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

RDB 的劣势

  • 需要在一定的时间间隔进行操作,如果 redis 意外宕机,最后一次写入的数据就会丢失
  • fork 子进程的时候需要占用一定的空间

AOF 持久化方式

AOF 是什么?

将我们的写命令全部记录下来,恢复的时候,将文件中的记录全部执行一遍

img

AOF 是 redis 的另外一种持久化方式,以日志的形式记录每一个写操作,将 redis 执行过的写操作全部记录下来,只允许追加文件,不允许改写文件

redis 启动的时候就会读取这个 aof 文件重建数据库,也就是说,redis 重启的时候,就会根据日志文件的内容将写指令按照写入顺序执行,完成数据恢复

aof 保存的是 appendonly.aof 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Please check https://redis.io/topics/persistence for more information.

appendonly no

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

# appendfsync always
appendfsync everysec
# appendfsync no

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

auto-aof-rewrite-min-size 64mb

当 aof 文件大于 64 mb 的时候,就会再创建一个子进程来写一个新的 aof 文件

img

关于 aof 的配置基本上其他的都是使用默认的配置即可,我们只需要把 aof 模式打开即可

1
appendonly yes

默认 appendonly 是不开启的,我们修改了配置之后,重启 redis-server 就会马上生效

重写规则说明

aof 默认是对文件无限追加,文件必然会越来越大

img

修改 redis.conf 为 aof 模式后,重启 redis-server 可以看到 appendonly.aof 文件

redis 客户端连接 server 进行操作 redis ,简单的设置几个值

image-20230613144100604

查看 appendonly.aof 文件

image-20230613144038771

appendonly.aof 文件里面存放的就是我们操作 redis 的写命令的记录

这个时候,我们认为的在 appendonly.aof 文件中修改一些值

启动 redis-server ,查看是否可以恢复数据

发现 redis-server 启动失败,是因为咱们的 appendonly.aof 文件被人为的修改过,此时咱们需要修复该文件,redis 有提供工具修改 aof 文件,redis-check-aof

使用指令:

1
redis-check-aof --fix appendonly.aof

修复 aof 文件后,咱们再次启动 redis-server 来回复磁盘数据,恢复成功,nice

aof 的优势和劣势

优势

  • 每一次操作 reids 都会被记录,文件的完整性好
  • 每秒同步一次,可能会丢失一秒的数据
  • 从不同步,这个效率是最高的

劣势

  • 相对于数据文件来说,aof 文件会远大于 rdb 文件,修复的速度也比 rdb 文件慢
  • aof 运行的效率比 rdb 慢,所以 redis 默认的配置是 rdb 持久化

小结

两种持久化方式简述

  • RDB 持久化的方式能够在指定的时间间隔内对数据进行快照存储

  • AOF 持久化的方式记录每次对服务器的写操作,当服务器重启或者宕机的时候,会重新执行这些记录里面的写操作命令来恢复数据,AOF 命令以 redis 协议追加保存每次写操作到文件末尾,redis 还能对 aof 文件进行后台重写,是的 AOF 文件的体积不至于过大

  • 如果需求是只做缓存,只期望服务器运行的时候数据存在,那么不用做持久化

两种持久化方式的开和不开

  • 可以同时开启两种持久化方式
    • 这种情况下,redis 重启会先载入 aof 文件来恢复数据,因为通常情况下 aof 文件保存的数据集比 rdb 的数据集要完整
    • rdb 数据集不是实时的,同时使用两种方式时,服务器重启有只会找 aof 文件,那么要不要只使用 aof 文件呢?

redis 的作者建议是不要只使用 aof 文件,因为 rdb 更加适合用于备份数据库,因为 aof 在不断的变化,不好备份,快速重启的时候,rdb 不会有 aof 可能潜在的 bug,留着 rdb 做一个兜底的机制

性能上的建议

对于 rdb 持久化

  • 由于 rdb 文件只用于备份数据,建议只在 slave 上面持久化 rdb 文件,15 分钟持久化一次就够了,也就是这一条指令 save 900 1
  • 如果打开 aof 持久化,好处就是在极端情况下丢失数据也不会超过 2s 的数据,启动脚本就简单的加载自己的 aof 文件即可,这样做也是有代价的
    • 代价之一就是 这样做带来了持续的 IO 操作
    • 代价之二就是 AOF 重写的最后将重写过程产生新数据写入到新文件造成的阻塞是不可避免的,只要硬盘许可,应该要尽量的减少 aof 重写的频率

对于 aof 持久化

  • aof 重写的基础大小值是 64 mb,我们可以设置成 5g 以上,默认超过原大小 100% 大小重写,这个参数可以设置成一个合理的参数
  • 如果不打开 aof 模式,仅仅靠主从复制实现高可用也是可以的,能够省掉一大笔 IO 消耗,也减少了重写带来系统的性能波动,这样做仍然是有代价的
    • 代价之一就是 如果主备 redis 同时挂掉(例如断电),会丢失十几分钟的数据,启动脚本也要比较主备的 rdb 文件,载入较新的那个 rdb 文件

9.Redis的发布和订阅是咋玩的

Redis 发布订阅

Redis 发布订阅(pub /sub)是一种消息通信模式

  • 发送者发送消息 pub
  • 接受者订阅消息 sub

例如微信,微博这样的关注系统

Redis 的客户端可以订阅任意数量的频道,不受限制

来看看图示

  • 消息发布者
  • 消息订阅者
  • 频道

img

这里的消息发布者,和消息订阅者都是 redis 客户端, 订阅者订阅某个频道,发布者在该频道中发布相关信息,例如文章,例如沸点,等等,消息订阅者就能实时收到刚才发布者发送的内容了

如下图中,频道 channel1

以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

img

当有新消息通过 PUBLISH 命令发送给频道 channel1 时

这个消息就会被发送给订阅它的三个客户端:

img

常用命令

下表列出了 redis 发布订阅常用命令:

序号 命令及描述
1 PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道。
2 PUBSUB subcommand [argument [argument …] 查看订阅与发布系统状态。
3 PUBLISH channel message 将信息发送到指定的频道。
4 [PUNSUBSCRIBE [pattern [pattern …] 退订所有给定模式的频道。
5 SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息。
6 UNSUBSCRIBE [channel [channel …] 指退订给定的频道。

实际测试和验证

subscribe channel [channel …]
订阅一个或者多个通道

PUBLISH channel message
向频道中发送消息

接收端:

接收端订阅 xiaomotong 频道,只要发送端有 publish 消息到频道中,接收端就能马上收到

发送端:

发送端向 xiaomotong 频道依次发送 message ,hellowrold

image-20230613150443969

那么这个实现的原理是啥呢?

实现原理

redis 是 C 语言实现的,单进程的开源组件

通过分析 redis 源码里面的 publish.c 文件,我们可以了解到 redis 发布订阅的底层实现,这能加深我们对 redis 的理解

redis 通过 publish ,subscribe 和 psubscribe 等命令来实现发布和订阅功能

img

例如我们每个人都会使用的微信:

subscribe

通过 subscribe 订阅某个频道后,redis-server 内部会维护一个字典,字典的键就是这个频道的名字,而字典的值是一个链表,这个链表里面保存了所有订阅这个频道的客户端

因此,我们就知道,subscribe 指令就是将客户端添加到频道的订阅链表里面

publish

redis 通过 publish 向频道中发送消息,redis-server 会使用给定的键作为频道的名字,在它自己维护的频道字典里面记录了订阅这个频道所有的客户端的链表,遍历这个链表,将消息发送给所有的订阅者

img

pub / sub

pub /sub 见名知意就是发布(publish)和订阅(subscribe)

在 redis 里面,我们可以设定对某一个 key 值,进行消息发布及消息订阅,当在一个 key 值上面进行了消息发布后,所有订阅他的客户端都会收到它刚才发布的消息,这一功能最明显的用法就是用作实时消息系统

例如我们平常都会使用到的聊天系统,即时通信系统等等

但是这里我们需要注意

Redis 集群为了实现所有订阅的客户端都可以接收到发布的消息,但是不同的客户端可能连接的是不同的主节点,为此 redis 会广播所有的发布的消息到所有的节点,如果发布的消息较大,网络开销将会很大,因此需要避免发送大消息

Redis 发布 / 订阅应用场景

1、实时消息系统

2、即时通信,频道作为聊天室,将信息回显给订阅频道的所有人

3、订阅系统,关注系统都是 ok 的

对于复杂的场景,我们就不用考虑 redis 了,可以直接使用专业的 MQ 开源组件,例如 rabbitMQ 或者 kafka

使用 Redis 发布 / 订阅 需要注意的点

使用 Redis 发布 / 订阅是有缺陷的

1、对于消息处理可靠性要求不强

2、消费能力无需通过增加消费方进行增强

10.Redis 集群搭建和主从复制

主从复制的理论

先来说说什么是主从复制

主从复制,就是将一台 redis 服务器的数据,复制到其他的 redis 服务器上, 前者为主节点 master,后者为从节点 slave

数据的复制是单向的,只能是主机复制到从机,master 以写为主,slave 以读为主

默认的情况下,每台 redis 服务器都是主节点,并且一个主节点都会有多个从节点或者没有从节点,但是反过来,一个从节点,只能有一个主节点

主从复制的作用:

  • 数据冗余

主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式

  • 故障恢复

当主节点出现异常问题时,从节点可以顶替上去提供服务,实现快速的故障恢复,实际上是一种服务的冗余

  • 负载均衡

主从复制的基础上,加上读写分离,可以由主节点提供写服务,由从节点提供读服务(也就是 写的时候连接 redis 的主服务器,读数据的时候连接 redis 的从服务器),这就可以分担服务器的压力

特别是在读多写少的情况下,通过多个从节点分担负载,可以大大的提供 redis 服务器的并发量

  • 是高可用的基础

主从复制是哨兵模式和集群能够实施的基础,so 主从复制是 redis 高可用的基石

在我们实际工作项目中,绝对不可能在生产环境中只有一台 redis 服务器,必须是集群模式的,最次也是 一主二从

  • 从结构来说,单个 reids 服务器会发生单点故障,且一台 redis 服务器承担所有读和写的压力,他太难了
  • 从 redis 服务器自身容量来看,单个 redis 服务器自身的容量是有限的,一般来说,单台的 redis 服务器最大使用内存不应该超过 20 GB

img

例如电商平台,进本上都是多读少写的方式,就会用到上述图示的架构,主从复制,读写分离,大部分情况都是在做读操作,很少一部分情况是在做写操作,这样可以减轻服务器的压力,架构中经常使用一主二从

环境配置

咱们在一台服务器上开启 3 个 redis-server 来模拟 reids 的集群,这是一个伪集群

查看 redis master 节点的信息,

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:87d84e1268d878bd3b1e7d5ea7f5060d4f71471e
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

将原有的 redis.conf 文件复制 3 份,分别是 6379.conf , 6380.conf ,6381.conf

1
2
3
root@iZuf66y3tuzn4wp3h02t7pZ:/usr/local/redis/redis-6.2.5# cp redis.conf 6379.conf
root@iZuf66y3tuzn4wp3h02t7pZ:/usr/local/redis/redis-6.2.5# cp redis.conf 6380.conf
root@iZuf66y3tuzn4wp3h02t7pZ:/usr/local/redis/redis-6.2.5# cp redis.conf 6381.conf

分别修改上述 3 个文件的如下 4 个位置:

  • redis server 的端口号 port 6379
  • 后台运行的 redis server pid 的名字,pidfile /var/run/redis_6379.pid
  • 日志文件,logfile “6379.log”
  • rdb 持久化文件,dbfilename dump6379.rdb

三个文件分别修改完毕后,分别启动三个 redis-server

1
2
3
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-server /usr/local/redis/redis-6.2.5/6379.conf
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-server /usr/local/redis/redis-6.2.5/6380.conf
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-server /usr/local/redis/redis-6.2.5/6381.conf

redis-server 后台启动后,我们查看一下是否启动成功

ps aux | grep reids

1
2
3
4
5
root@iZuf66y3tuzn4wp3h02t7pZ:~# ps aux |grep redis
root 4345 0.0 0.1 62776 3864 ? Ssl 08:50 0:00 redis-server 0.0.0.0:6379
root 4351 0.0 0.1 62776 3860 ? Ssl 08:50 0:00 redis-server 0.0.0.0:6380
root 4359 0.1 0.1 62776 3856 ? Ssl 08:50 0:00 redis-server 0.0.0.0:6381
root 4369 0.0 0.0 14436 1016 pts/0 S+ 08:50 0:00 grep --color=auto redis

使用 redis-cli 连接每一个不同端口的 redis-server,查看主从信息,发现每一个 redis-server 都是主机,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
127.0.0.1:6379> info replication
# Replication
role:master # 主机
connected_slaves:0 # 从机
master_failover_state:no-failover
master_replid:38cbcdac7d7f6b1c154643f94d6742111826cccb
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

开始设置主从(一主二从)

默认情况下,redis 服务器默认都是主节点,我们只需要配置从节点即可

我们设置 6379 为主,6380 为从,6381 为从

img

6380 和 6381 都设置好是 6379 的从节点,现在可以查看 6379 的配置信息如下,自己是 master ,有 2 个 slave

img

这就是设置好了 redis 集群,一主二从,这是通过命令的方式临时设置,若其中一个 redis 服务器重启后,自己也会变成 master,因此我们要持久的设置,可以去配置文件中设置

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli -p 6379
127.0.0.1:6379>
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379>
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli -p 6380
127.0.0.1:6380> get k2
"v2"
127.0.0.1:6380> set k4 v4
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli -p 6381
127.0.0.1:6381> get k3
"v3"
127.0.0.1:6381> set k5 v5
(error) READONLY You can't write against a read only replica.

主机上,可以读,可以写

从机上,只能读,不能写

测试断掉主机

1
2
3
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli -p 6379
127.0.0.1:6379> shutdown
not connected>

img

img

1
2
3
4
5
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli -p 6381
127.0.0.1:6381> keys *
1) "k3"
2) "k2"
3) "k1"

主机挂掉,从机仍然可以读取数据,只是还是不能写,主机起来后,主机仍然可以读和写,写操作又回来了,我们的快乐又回来了

复制的原理

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

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

全量复制

slave 服务接收到 master 传过来的数据后,将其存盘并加载到内存

增量复制

master 将新的收集到的所有修改的命令依次传递给 slave ,并完成同步

一旦重新连接 master 节点,一次完全的全量同步就会被执行

11.Redis的哨兵模式详解和实战

上一次我们说到的主从复制是这样搭建的

主机可以读,可以写

从机只能读,不能写

img

想一想,那么我们是不是也可以这样呢?

多个 redis-server 首尾相连

img

那么咱们部署的时候就是 6379 – 6380 – 6381

此时,若主机 6379 宕机掉,6380 会不会变成主机呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=0,lag=1
master_failover_state:no-failover
master_replid:f1e3db9e5e438f5d98e4cad23f684b12d790ae56
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

我们可以看到 6379 有一个从机,6380 自身也是作为从机(虽然 6380 是 6381 的主机)

img

此时将 6379 宕机掉,发现 6380 仍然是 slave

1
2
127.0.0.1:6379> shudown
not connected>

img

我们人为的将 reids 6380 的服务器修改为主机,看看 6379 redis-server 起来之后,是否可以把 master 抢回去

使用 slaveof no one 可以将自己设置成 master

img

启动 6379 redis-server

1
redis-server /usr/local/redis/redis-6.2.5/6379.conf

发现 6380 仍然是主机,6379 成为了光杆司令

实际项目中,我们肯定不会采取上面和上一次文章说到的部署方式,他们抵御风险的能力太低了

因为实际生产环境中,主机宕机了,若从机没有办法成为主机的话,岂不是在主机回复之前再也不能做写入操作了吗?这是很严重的问题

下面我们来详细看看 哨兵模式是如何解决上述问题的

哨兵模式

自动选举 master 的模式

介绍

主动切换 master 的方法是:

当主机服务器宕机后,以往的情况咱们需要手动把某一个从机服务器修改为主机服务器,需要人为处理,耗时耗力,且会造成一段时间内服务不可用的情况,这种做法是不可取的

所以有了哨兵模式,哨兵模式是 redis 2.8 版本开始真是提供的 sentinel 架构来解决上述的问题

哨兵模式,能够监控后台的主机服务器是否故障,若出现了故障,则会投票选举出一个从机服务器来做主机

哨兵模式是一种特殊的模式,Redis 提供了哨兵的命令

哨兵其实是一个独立的进程,作为进程,它会独立运行

其原理就是哨兵通过发送命令,等到 Redis 服务器响应,从而监控运行的多个 Redis 实例。

实际演练

img

上图中的架构图,哨兵有 2 个作用:

  • 通过发送命令,Redis 服务器返回监控状态信息,包括主服务器和从服务器的
  • 若哨兵检测到主服务器宕机,会自动将 slave 切换 master,然后通过发布订阅通知其他从服务器,修改配置文件,让他成为主机

可是一个哨兵来监控一个 redis 集群,出现问题的可能性会大很多,因此,我们的哨兵也可以是集群的,每隔哨兵之间还会互相监控的,就像下面这张图

img

主观下线

例如咱们举一个例子,如果 master 服务器宕机了,那么其中一个哨兵就会检测到,系统并不会马上执行 failover 的过程,仅仅是当前这个哨兵,判断 master 不可用,这个就是主观下线

客观下线

当其他两个哨兵也发现 master 服务器不可用的时候,那么哨兵之间就会产生投票,具体的投票算法我们后续再写,投票的结构由一个哨兵发起,进行 failover 故障转移的操作,切换成功之后,就会通过发布订阅模式,让每一个监控的哨兵把自己监控的服务器切换到这个 master 服务器上, 这个就是客观下线

我们来配置和开启一个哨兵:

同样在我们的配置文件目录,与 redis 是同级的目录下,创建一个 sentinel.conf 文件, 并写入配置,这个文件安装 redis 默认也会生成一个

img

如上,咱们关注的命令是

1
sentinel monitor mymaster 127.0.0.1  6379  1

image-20230613224103415

配置一个哨兵,进行监控 redis 集群

开启哨兵进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
root@iZuf66y3tuzn4wp3h02t7pZ:/usr/local/redis/redis-6.2.5# redis-sentinel sentinel.conf
18148:X 26 Aug 2021 22:22:36.187 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
18148:X 26 Aug 2021 22:22:36.187 # Redis version=6.2.5, bits=64, commit=00000000, modified=0, pid=18148, just started
18148:X 26 Aug 2021 22:22:36.187 # Configuration loaded
18148:X 26 Aug 2021 22:22:36.188 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 18148
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

18148:X 26 Aug 2021 22:22:36.189 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
18148:X 26 Aug 2021 22:22:36.193 # Sentinel ID is 7e01f5aa31aadb7fc54ed8ef2579c77120682dc9
18148:X 26 Aug 2021 22:22:36.193 # +monitor master mymaster 127.0.0.1 6379 quorum 1
18148:X 26 Aug 2021 22:22:36.193 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
18148:X 26 Aug 2021 22:22:36.196 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379

我们可以看到,开启哨兵进程之后,开始监控到 redis 集群,并输出了监控的 redis 的 ip 和端口

此时,咱们查看一个 master redis 服务器的信息,可以看到目前有 2 个 slave ,现在咱们让主机宕机

img

查看哨兵的日志,咱们可以看到 哨兵进程在 30 秒之后,开始进行投票选举主机

img

根据日志,我们可以看出,6379 的主机宕机之后,哨兵选举了 6381 的从机作为新的主机,自动故障恢复成功,nice

img

查看 6381 的 master 主机进程,查看到自己是主机,有 1 个从机

由于 6379 默认配置就是 主机,因此将 6379 服务器再次启动的时候,6379 就变成了光杆司令,从机就变成了 0 个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-server /usr/local/redis/redis-6.2.5/6379.conf
root@iZuf66y3tuzn4wp3h02t7pZ:~# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:80843f8a6497705983f6463b92d71ebd451ef385
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

sentinel.conf 配置文件里面的配置项也不多,下面咱们详细的一一说明一下:

哨兵模式详细配置如下:

port
哨兵 sentinel 实例运行的端口 , 默认是 26379,如果有哨兵集群,我们还需要配置每个哨兵端口

dir
哨兵 sentinel 的工作目录

sentinel monitor
哨兵 sentinel 监控的 redis 主节点的 ip port

master-name ,可以自己命名的主节点名字 只能由字母 A-Z、数字 0-9、这三个字符” . - _ “组成。

quorum 配置多少个 sentinel 哨兵统一认为 master 主节点失联那么这时客观上认为主节点失联了

sentine1 auth-pass
当在 Redis 实例中开启了 requirepass foobared 授权密码这样所有连接 redis 实例的客户端都要提供密码。设置哨兵 sentinel 连接主从的密码注意必须为主从设置一样的验证密码

sentinel down-after-mi 11i seconds <mi 11iseconds>
指定多少毫秒之后主节点没有应答哨兵 sentine1 此时哨兵主观上认为主节点下线默认 30 秒

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

sentinel failover-timeout
故障转移的超时时间 failover-timeout 可以用在以下这些方面:

1、同一个 sentinel 对同一 个 master 两次 failover 之间的间隔时间

2、当一个 slave 从一 个错误的 master 那里同步数据开始计算时间。直到 slave 被纠正为向正确的 master 那里同步数据时

3、当想要取消一个正在进行的 failover 所需要的时间

4、当进行 failover 时,配置所有 slave 指向新的 master 所需的最大时间

不过,即使过了这个超时,slaves 依然会被正确配置为指向 master , 但是就不按 parallel-syncs 所配置的规则来了

5、默认时间是三分钟

sentinel notification-script
当 sentinel 有任何警告级别的事件发生时(比如说 redis 实例的主观失效和客观失效等等),将会去调用这个脚本。这时这个脚本应该通过邮件,SMS 等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数:1、事件的类型 2、事件的描述。如果 sentinel . conf 配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则 sentinel 无法正常启动成功

sentinel client-reconfig-script
该配置是客户端重新配置主节点参数脚本

当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,通知相关的客户端关于 master 地址已经发生改变的信息

以下参数将会在调用脚本时传给脚本:

, 是 Teader 或者 observer 中的一个

, 一般是 failover

参数 from-ip, from-port, to-ip,to-port 是用来和旧的master和新的master (即旧的 s lave)通信的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
port 26379

dir /tmp

sentinel monitor mymaster 127.0.0.1 6379 2

sentine1 auth-pass mymaster MySUPER--secret-0123passwOrd

sentine1 down-after-mi 11iseconds mymaster 30000

sentine1 paralle1-syncs mymaster 1

sentine1 fai lover-ti meout mymaster 180000

sentine1 notificati on-script mymaster /var/redis/notify.sh

sentine1 client-reconfig-script mymaster /var/redis/reconfig.sh

12.Redis缓存穿透,缓存击穿,缓存雪崩

虽然我们在使用 redis 缓存的时候非常的爽,它大大的提高了我们应用程序的性能和效率,尤其是数据查询方面,咱们不用直接去持久化的数据库中查询数据,而是到内存中查询数据即可

事物总是有两面的,用的爽的同时,也必须面对它带来的问题,就是数据一致性的问题,这个问题,是一个权衡利弊的问题,咱们接着看

redis 缓存和一些持久化的数据库配合使用的时候,会出现一些高可用的问题,如:

  • 缓存穿透
  • 缓存击穿
  • 缓存雪崩

咱们能够解决上述问题,那就解决了一部分服务器高可用的问题

什么是缓存穿透

咱们先学习一部分,关于底层原理和实际源码分析,咱们之后再一起看

img

缓存穿透,就是用户想要查询一个数据,在 redis 中查询不到,即没有在缓存中命中,那么就会直接去持久化的 mysql 中进行查询,发现也没有这个数据,那么本次查询就失败了

当用户巨多的时候,查询缓存都没有查询到,那么这些全部都去查询持久化的 mysql 数据库,压力全部打到 mysql 上面,这就是缓存穿透

解决方案有一般有 2 种方式:

  • 使用布隆过滤器
  • 缓存空的对象

使用布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询到的参数都是以 hash 的方式存储,会先在控制层进行校验,不符合的话,则丢弃,这就避免了对底层存储系统的压力

img

布隆过滤器部署在 redis 的前面,去拦截数据,减少对 redis 的冲击,进而减小对持久化层的冲击

缓存空的对象

缓存空对象,就是当我们在持久化的数据库中没有查询到我们期望的数据时,那么就返回一个空对象,并且将这个空对象缓存起来,再对其设置一个过期时间

那么之后再有访问这个对象的请求时,缓存直接访问空对象即可,这就可以保护持久化数据层,减少对他的冲击压力

img

通过上述缓存空对象的方式,貌似也能解决问题,但是使用持久下去,会发现 key 值对应的空对象越来越多,会出现下面 2 个问题:

  • 非常多的空对象被缓存起来,那么对应就很多的 key 占用 内存空间,占用资源,内存压力直线上升
  • 如果空对象的过期时间到了,那么请求的压力还是会打到持久化数据库上面,这会影响数据的一致性业务

什么是缓存击穿

img

出现缓存击穿的情况是数据量太大,或者是缓存过期了

当某个 key 在过期的瞬间,有大量的请求这个 key 的数据,这种数据是热点数据,由于在缓存过期的瞬间,请求会同时访问到持久化的数据库来查询数据,并且会将数据会写到缓存中,此时就会导致数据库瞬间的压力过大,导致击穿

此处可以理解 击穿和穿透的区别:

击穿,是一个 key 非常热点,大量的访问都打在这个 key 上面,在 key 失效的瞬间,所有请求打在数据库上,就打出一个洞,击穿了

而穿透更多的是访问的数据不存在的情况,大量的请求访问的都是不存在的数据

缓存击穿的解决方案

  • 将热点数据设置不过期,不设置过期时间,就不会出现热点 key 过期的瞬间造成问题

  • 加上分布式锁,保证对于每一个 key ,同时只有一个服务进行访问,其他的服务没有获取到锁,就不能访问 redis 的这个 key,那么就需要等待获取锁

    这种方式,锁的压力就非常大了,访问 redis 前先去访问锁,相当于锁给 redis 挡了一层

什么是缓存雪崩

img

缓存雪崩就是在某一个时间段,缓存集中过期,或者 redis 宕机的情况会出现

例如:

在某些热点活动中,会设置某些商品在一个固定的时间内过期,那么在 redis 里面,这个固定的时间点,大量的 key 过期,这就导致在这个时间段缓存失效了,

且大量的请求数据都打在了持久化数据库上面了,这就很难受,在这种压力波峰下,压力全部打在持久化数据库上,这会造成持久化数据库宕机

上述的情况,key 集中过期问题还不是非常的痛,最痛的是 redis 宕机了,自然周期性的形成的波峰压力,咱们的持久化数据库还是能够顶得住压力的,偏偏是在 redis 异常宕机,一挂挂一片,这就很有可能将后方的持久化数据库全部打挂,这是毁灭性的压垮

缓存雪崩的解决方案:

  • 将 redis 做成高可用的

搭建 redis 集群,异地多活,既然担心 redis 会挂,那么我们就多准备一些 redis ,做成主备,或者异地多活

  • 限流降级

就是在缓存失效的时候,通过锁的方式来限制访问数据顺序,或者关掉一些不重要的服务,让资源和性能全力提供给我们的主要服务

  • 做数据预热

数据预热就是咱们在正式要上线之前,咱们就先将需要访问的数据预先访问一次,这样就可以将大量要访问数据库的数据写到缓存中

这样就可以在即将发生的高并发访问数据前手动的触发并加载不同的 key ,且会设置不同的过期时间,主要是可以将缓存失效的事情均衡一些,这样就尽量避免掉大量的 key 集中过期的情况。

13.Redis面试题

Redis 常问简单面试题

14.Go操作Redis

Go-Redis文档

  • 标题: Redis从零开始学习
  • 作者: Olivia的小跟班
  • 创建于 : 2023-06-05 00:25:55
  • 更新于 : 2023-06-14 22:00:22
  • 链接: https://www.youandgentleness.cn/2023/06/05/Redis从零开始学习/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
此页目录
Redis从零开始学习