中间件学习笔记-Redis入门

以下学习内容笔记全部来源于B站教学视频:【狂神说Java】Redis最新超详细版教程通俗易懂,非常感谢来自狂神老师的教学系列,附B站学习视频链接:https://www.bilibili.com/video/av840034966

1. Redis(缓存)

1.1 Nosql

1.1.1 概述

单机mysql时代:

90年代,一个基本的网站访问量不会太大,单个数据库足够!

更多的使用静态网页html

网站瓶颈:

  • 数据量太大,一个机器放不下
  • 数据的索引,一个机器的内存也放不下
  • 访问量太大,读写混合,性能问题

缓存时代:

memcached(缓存)+mysql+读写分离(垂直拆分)

网站百分之80情况都是在读,每次都要去数据库查询太麻烦,减轻数据库的压力,可以使用缓存来保证效率(Cache)-------解决读的问题

发展过程:优化Mysql底层结构和索引–>文件缓存(IO操作)—>Memcached 当时最热门的技术!

分库分表+水平拆分+mysql集群:

技术和业务发展同时,对技术要求也越来越高

本质: 数据库(读、写)

早些年 MyISAM:表锁,影响效率,高并发下出现严重的锁问题

早些年Innodb:行锁

慢慢地使用分库分表来解决写的压力!并没有多少公司使用

Mysql集群,很好满足了个别需求

最近的年代:

2010-2020 技术发展迅速(定位、音乐、热榜)

Mysql等关系型数据库就不够用了,数据量很多,变化很快

目前一个基本的互联网项目:

中间件学习笔记-Redis入门

为什么要用Nosql

  • 用户的个人信息、地理位置、社交网络、用户自己产生的数据、用户日志等等爆发式增长!

这时候可以使用Nosql数据库处理以上的情况!

1.1.2 什么是NoSQL

NoSQL

NoSQL=not only sql

SQL:表格,行,列

泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!

NoSQL在当今大数据时代发展迅速,REDIS发展是最快的,、

NoSQL特点

  • 方便扩展(数据之间没有关系,很好扩展,没有耦合性)

  • 大数据量下的高性能(redis一秒可以写8W次,可以读取11w次,NoSQL的缓存级别,是一种细粒度的缓存,性能会比较高!)

  • 多类型(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人无法设计)

  • 传统RDBM和NoSQL:

    结构化组织

    SQL

    数据和关系都存在单独的表中

    数据操作语言,数据定义语言,

    严格的一致性

    基础的事务

​ 不仅仅是数据

​ 没有固定的查询语言

​ 键值对存储,列存储,文档存储,图形数据库(社交关系)

​ 最终一致性

​ CAP定理、 BASE (异地多活)初级架构师!

​ 基本的高性能、高可用、高可扩展

​ …

扩展:大数据的3v和3高

描述问题:海量、多样、实时

程序要求:高并发、高可用、高性能

1.1.3 NoSQL的分类

KV键值对

  • 新浪:redis
  • 美团:redis+Tair
  • 阿里、百度:redis+memcache

文档型数据库(bson格式json)

  • MongoDB(一般必须掌握)

    基于分布式文件存储的数据库,C++编写,处理大量的文档!

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

  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图形关系数据库

中间件学习笔记-Redis入门

  • 不是放图片的,是放关系的:朋友圈社交网络、广告推荐!

  • Ne04j, infoGrid

NoSQL的对比

中间件学习笔记-Redis入门

1.1.4 Redis

redis–Remote Dictionary Server

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。免费且开源,当前最热门的NoSql之一

能干嘛?

  • 内存存储、持久化,内存中是断电即失去,所以说持久化很重要(rdb/aof)

  • 效率高、可以用于高速缓存

  • 发布订阅系统

  • 地图信息分析

  • 计时器、计数器(浏览量)

特性

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

学习中需要用到的东西

  • 官网
  • 中文网
  • 下载地址:通过官网(官方不建议用windows开发使用)

1.2 Redis的安装

1.2.1 windows安装

  • 下载安装包 https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100

  • 解压到特定文件夹

中间件学习笔记-Redis入门

  • 开启Redis:双击运行运行服务(秒级启动)

中间件学习笔记-Redis入门

默认端口:6379

  • 使用Redis客户端连接

中间件学习笔记-Redis入门

测试连接:

中间件学习笔记-Redis入门

  • ping测试 返回PONG
  • 设置一个key值 然后get获取

windows下使用确实简单,但是推荐使用linux去开发使用

1.2.2 Linux安装

1.2.2.1 下载安装包

redis-6.0.6.tar.gz

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

1.2.2.2 解压安装包

并进入

tar -zxvf redis-6.0.6.tar.gz

中间件学习笔记-Redis入门

配置文件:redis.conf

1.2.2.3 基础环境安装

  1. yum install gcc-c++

  2. gcc -v

中间件学习笔记-Redis入门

  1. make(需要一点时间)

中间件学习笔记-Redis入门

报错,原因分析:gcc版本过低,升级一下

# 升级gcc版本
$ yum -y install centos-release-scl
$ yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
$ scl enable devtoolset-9 bash
# 升级后查看下版本
$ gcc -v

中间件学习笔记-Redis入门

重新执行make

中间件学习笔记-Redis入门

  1. make install (也可以不用执行,确认一下安装)

中间件学习笔记-Redis入门

所有东西已安装好了

1.2.2.4 确认安装路径

Redis默认安装路径:/usr/local/bin

中间件学习笔记-Redis入门

1.2.2.5 复制配置文件

复制Redis配置文件到当前目录下

# 创建配置文件文件夹并复制配置文件
$ mkdir hconfig
$ cp /opt/redis-6.0.6/redis.conf hconfig

中间件学习笔记-Redis入门

之后就使用这个配置文件进行启动

1.2.2.6 配置后台启动

Redis默认不是后台启动的,需要修改配置文件

# 修改daemonize为yes
$ vim redis.conf

中间件学习笔记-Redis入门

1.2.2.7 启动Redis服务

启动Redis服务

通过指定的配置文件启动服务

redis-server hconfig/redis.conf

中间件学习笔记-Redis入门

1.2.2.8 测试连接

测试连接

使用Redis客户端进行连接

# 连接
$ redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name yunmx   # 设置一个key
OK
127.0.0.1:6379> get name		# 读取一个key
"yunmx"
127.0.0.1:6379> keys *			# 查询所有的key
1) "name"
127.0.0.1:6379> 

中间件学习笔记-Redis入门

1.2.2.9 查看进程

查看Redis的进程是否开启

ps -ef |grep redis

中间件学习笔记-Redis入门

1.2.2.10 关闭Redis服务

关闭redis服务

shutdown

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> shutdown
not connected> exit

中间件学习笔记-Redis入门

1.3 Redis性能测试工具

1.3.1 redis-benchmark

官方自带的性能测试工具—>

redis-benchmark

命令参数:

中间件学习笔记-Redis入门

简单测试:

# 测试100个并发连接,每个并发20个请求
$ redis-benchmark -h localhost -p 6379 -c 100 -n 20

中间件学习笔记-Redis入门

1.3.2 如何分析

查看分析:

====== PING_INLINE ======
  100000 requests completed in 1.46 seconds   	# 100000次ping请求在1.46s内完成
  100 parallel clients							# 100的并发量
  3 bytes payload								# 3字节数据写入
  keep alive: 1									# 保证一台服务器测试(单机性能)
  host configuration "save": 900 1 300 10 60 10000
  host configuration "appendonly": no
  multi-thread: no

0.00% <= 0.5 milliseconds						# 0.5毫秒内完成百分之0的请求
1.37% <= 0.6 milliseconds
11.42% <= 0.7 milliseconds
23.22% <= 0.8 milliseconds
35.29% <= 0.9 milliseconds
47.60% <= 1.0 milliseconds
60.01% <= 1.1 milliseconds
72.40% <= 1.2 milliseconds
84.82% <= 1.3 milliseconds
94.78% <= 1.4 milliseconds
97.65% <= 1.5 milliseconds
98.50% <= 1.6 milliseconds
98.95% <= 1.7 milliseconds
99.28% <= 1.8 milliseconds
99.51% <= 1.9 milliseconds
99.67% <= 2 milliseconds
99.99% <= 3 milliseconds
100.00% <= 3 milliseconds						# 3毫秒内完成100%的请求(100000)
68587.11 requests per second					# 平均每秒完成的请求数

1.4 Redis基础知识

1.4.1 默认数据库

Redis默认数据库数量有16个,redis.conf配置文件可查看,默认使用的数据库为0

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

1.4.2 切换数据库

# 使用select 命令进行切换
[root@yunmx bin]# redis-cli -p 6379
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 2
OK
127.0.0.1:6379[2]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379[2]> select 15
OK
127.0.0.1:6379[15]> 

1.4.3 查看数据库大小

# DBSIZE 查看数据库大小
127.0.0.1:6379> set name hejie
OK
127.0.0.1:6379> DBSIZE
(integer) 1
127.0.0.1:6379> SET AGE NAN
OK
127.0.0.1:6379> DBSIZE
(integer) 2
127.0.0.1:6379>

1.4.4 查看所有的key

# 使用keys *
127.0.0.1:6379> keys *
1) "AGE"
2) "name"
127.0.0.1:6379>

1.4.5 清空数据库

# 清空当前数据库flushdb
127.0.0.1:6379> keys *
1) "AGE"
2) "name"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
# 清空所有数据库flush
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> set name hejie
OK
127.0.0.1:6379> set key1 hejie1
OK
127.0.0.1:6379> set key2 hejie2
OK
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
3) "name"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name yunmx
OK
127.0.0.1:6379[1]> set key1 yunmx1
OK
127.0.0.1:6379[1]> set key2 yunmx2
OK
127.0.0.1:6379[1]> keys *
1) "key1"
2) "key2"
3) "name"
127.0.0.1:6379[1]> flushall
OK
127.0.0.1:6379[1]> keys *
(empty array)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>

1.4.6 判断key是否存在

# 使用exists 判断
127.0.0.1:6379> set name hejie
OK
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists age     
(integer) 1							# 存在
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists key1  
(integer) 0							# 不存在
127.0.0.1:6379>

1.4.7 移动一个key

# move移动一个key
127.0.0.1:6379> set key1 hejie
OK
127.0.0.1:6379> set key2 hejie1
OK
127.0.0.1:6379> set key3 hejie2
OK
127.0.0.1:6379> move key1 1      	# 将key1移动给数据库1
(integer) 1
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "key1"
127.0.0.1:6379[1]>

1.4.8 设置key的生效时间

# EXPIRE 设置一个key的过期时间,单位时间:s
# TTL 查看一个key的过期时间
127.0.0.1:6379> keys *
1) "key3"
2) "key2"
127.0.0.1:6379> get key3
"hejie2"
127.0.0.1:6379> EXPIRE key3 10     # 设置key3 10秒后过期
(integer) 1
127.0.0.1:6379> ttl key3
(integer) -2					   # 表示已经过期
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> keys *
1) "key2"
127.0.0.1:6379>

1.4.9 Redis单线程

单线程为什么这么快?

C语言写的,官方表示每秒的QPS是100000+,基于内存操作,Redis的瓶颈和服务器的内存和网络带宽有关,既然可以用单线程实现,就用单线程喽。

误区:

高性能服务器一定是多线程的?

多线程一定比单线程效率高?

核心:Redis是将所有数据放在内存中的,所以说使用单线程去操作效率就是最高的,因为多线程会产生CPU上下文切换,消耗时间的操作,对于内存系统来说,没有上下文切换,效率就是最高的,多次读写都是在一个CPU上的

1.5 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入门

使用场景:value除了是字符串还可以是我们的数字

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

1.5.1 String(字符串)

### 常见用法
127.0.0.1:6379> set kye1 yunmx								# 设置一个key
OK
127.0.0.1:6379> get key1									# 获取一个key的值
(nil)
127.0.0.1:6379> keys *										# 查看当前库的所有key,输出key名
1) "kye1"
127.0.0.1:6379> EXISTS kye1									# 判断key是否存在
(integer) 1
127.0.0.1:6379> EXISTS key1
(integer) 0
127.0.0.1:6379> APPEND kye1 "helloworld"					# 向key追加内容,如果key不存在,就设置一个key
(integer) 15
127.0.0.1:6379> get kye1
"yunmxhelloworld"
127.0.0.1:6379> STRLEN kye1 								# 获取一个key的长度
(integer) 15
127.0.0.1:6379> 

### 自增长用法
127.0.0.1:6379> set count 0
OK
127.0.0.1:6379> get count
"0"
127.0.0.1:6379> incr count									# 自增1
(integer) 1
127.0.0.1:6379> get count
"1"
127.0.0.1:6379> incr count									
(integer) 2
127.0.0.1:6379> get count
"2"
127.0.0.1:6379> decr count									# 自减1
(integer) 1
127.0.0.1:6379> get count
"1"
127.0.0.1:6379> decr count
(integer) 0
127.0.0.1:6379> get count
"0"
127.0.0.1:6379> incrby count 5								# 自增;步长5
(integer) 5
127.0.0.1:6379> get count
"5"
127.0.0.1:6379> decrby count 5								# 自减;步长5
(integer) 0
127.0.0.1:6379> get count
"0"

### 获取字符串范围
127.0.0.1:6379> set key1 yunmxhejie							# 设置一个key
OK
127.0.0.1:6379> getrange key1 0 4							# 获取从标0到4范围的字符串
"yunmx"
127.0.0.1:6379> getrange key1 0 -1							# 获取全部的字符串
"yunmxhejie"
127.0.0.1:6379> 

### 替换
127.0.0.1:6379> set key1 yunmx123456						# 设置一个key
OK
127.0.0.1:6379> SETRANGE key1 0 hejie						# 从下标为0的位置开始替换
(integer) 11
127.0.0.1:6379> get key1
"hejie123456"
127.0.0.1:6379> 

### 设置过期时间
127.0.0.1:6379> setex key1 10 yunmx							# 设置一个key,过期时间为10秒
OK
127.0.0.1:6379> ttl key1									# 查看key的过期时间
(integer) 7
127.0.0.1:6379> ttl key1									# 返回-2表示已经过期
(integer) -2
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> 

### 不存在设置(分布式锁中会经常用到)
127.0.0.1:6379> setnx key2 redis							# setnx:不存在key值则创建
(integer) 1
127.0.0.1:6379> setnx key2 mysql							# setnx:存在key值则创建失败
(integer) 0
127.0.0.1:6379> keys *
1) "key2"
127.0.0.1:6379> 

### 批量设置和获取
127.0.0.1:6379> mset key1 1 key2 2 key3 3 key4 4			# 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "key1"
2) "key4"
3) "key3"
4) "key2"
127.0.0.1:6379> mget key1 key2 key3 key4					# 同时获取多个值
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> msetnx key5 5 key4 4						# 原子性操作:要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get key5
(nil)
127.0.0.1:6379> 

### 对象
127.0.0.1:6379> set user:1 {name:yunmx,age:25}				# 普通设置对象:key加json字符串
OK
127.0.0.1:6379> get user:1
"{name:yunmx,age:25}"
127.0.0.1:6379> mset user:1:name yunmx user:1:age 25		# 巧妙设计对象;如此设计,是完全可以的
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "yunmx"
2) "25"

### 组合命令
127.0.0.1:6379> getset db mysql								# 如果不存在,则设置
(nil)
127.0.0.1:6379> get db
"mysql"
127.0.0.1:6379> getset db MongoDB							# 如果存在,返回当前值,再设置新的值
"mysql"
127.0.0.1:6379> get db
"MongoDB"
127.0.0.1:6379> 

1.5.2 List(列表)

基本的数据类型

中间件学习笔记-Redis入门

基本的数据类型,列表

在redis里面,list可以完成栈、队列、阻塞队列!

用法:

  • 他实际是一个链表,before node after ,left right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,代表不存在
  • 两边插入或者改动值,效率最高;修改中间元素,相对来说,效率偏低一点

消息排队、消息队列(lpush Rpop) 栈(lpush lpop)

### 基础命令
127.0.0.1:6379> LPUSH key1 1								# 将一个值或者多个值添加到头部
(integer) 1
127.0.0.1:6379> LPUSH key1 2
(integer) 2
127.0.0.1:6379> LPUSH key1 3
(integer) 3
127.0.0.1:6379> LRANGE key1 0 -1							
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> LRANGE key1 0 1
1) "3"
2) "2"
127.0.0.1:6379> rpush key1 5								# 将一个值添加到尾部
(integer) 4
127.0.0.1:6379> LRANGE key1 0 -1
1) "3"
2) "2"
3) "1"
4) "5"

### 移除元素
127.0.0.1:6379> LRANGE key1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> LPOP key1									# 移除列表的第一个元素
"5"
127.0.0.1:6379> rPOP key1									# 移除列表的最后一个元素
"1"
127.0.0.1:6379> LRANGE key1 0 -1
1) "4"
2) "3"
3) "2"

### 获取元素、列表长度
127.0.0.1:6379> LRANGE key1 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> LINDEX key1 0								# 获取列表中的第一个元素
"4"
127.0.0.1:6379> LINDEX key1 1
"3"
127.0.0.1:6379> LINDEX key1 2
"2"
127.0.0.1:6379> LINDEX key1 3
(nil)
127.0.0.1:6379> LLEN key1									# 获取列表长度
(integer) 3

### 移除指定的值
 127.0.0.1:6379> LRANGE key1 0 -1
1) "2"
2) "4"
3) "4"
4) "3"
5) "2"
127.0.0.1:6379> LREM key1 1 2								# 移除一个2
(integer) 1
127.0.0.1:6379> LRANGE key1 0 -1
1) "4"
2) "4"
3) "3"
4) "2"
127.0.0.1:6379> LREM key1 2 4								# 移除2个4
(integer) 2
127.0.0.1:6379> LRANGE key1 0 -1
1) "3"
2) "2"

### 截取
127.0.0.1:6379> LRANGE key1 0 -1
1) "15"
2) "14"
3) "13"
4) "12"
5) "11"
6) "10"
127.0.0.1:6379> LTRIM key1 0 3								# 通过下标截取指定的长度,list已经被改变了
OK
127.0.0.1:6379> LRANGE key1 0 -1
1) "15"
2) "14"
3) "13"
4) "12"

### 组合命令:rpoppush
127.0.0.1:6379> rpush list1 1 2 3 4 5 
(integer) 5
127.0.0.1:6379> LRANGE list1 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> rpoplpush list1 list2					# 移除列表中的最后一个元素,并加入到新的列表中
"5"
127.0.0.1:6379> LRANGE list1 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> LRANGE list2 0 -1
1) "5"
127.0.0.1:6379> 

### 元素更新
127.0.0.1:6379> EXISTS list1						# 判断列表是否存在
(integer) 0
127.0.0.1:6379> lset list1 0 yunmx					# 不存在报错
(error) ERR no such key
127.0.0.1:6379> lpush lista1 1 2 3 4
(integer) 4
127.0.0.1:6379> LRANGE list1 0 0
(empty array)
127.0.0.1:6379> LRANGE list1 0 -1
(empty array)
127.0.0.1:6379> LRANGE lista1 0 0
1) "4"
127.0.0.1:6379> lset lista1 0 yunmx						# 存在修改当前下标的值
OK
127.0.0.1:6379> LRANGE lista1 0 0
1) "yunmx"

### 元素插入
127.0.0.1:6379> LRANGE lista1 0 -1
1) "yunmx"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> LINSERT lista1 before yunmx hejie		# 插入当前元素的前面
(integer) 5
127.0.0.1:6379> LINSERT lista1 after yunmx hejie1		# 插入当前元素的后面
(integer) 6
127.0.0.1:6379> LRANGE lista1 0 -1
1) "hejie"
2) "yunmx"
3) "hejie1"
4) "3"
5) "2"
6) "1"

1.5.3 Set(集合)

set中的值不能重复d ,是无序的

### 基础用法
127.0.0.1:6379> sadd set1 yunmx													# 往集合里面存值
(integer) 1
127.0.0.1:6379> sadd set1 hejie1
(integer) 1
127.0.0.1:6379> sadd set1 hejie01
(integer) 1
127.0.0.1:6379> sadd set1 hejie02
(integer) 1
127.0.0.1:6379> sadd set1 hejie02
(integer) 0
127.0.0.1:6379> SMEMBERS set1													# 查询集合里面所有的元素
1) "yunmx"
2) "hejie02"
3) "hejie01"
4) "hejie1"
127.0.0.1:6379> SISMEMBER set1 hejie											# 判断元素是否存在
(integer) 0
127.0.0.1:6379> SISMEMBER set1 hejie5
(integer) 0
127.0.0.1:6379> SISMEMBER set1 hejie1											# 存在返回1
(integer) 1

### 获取集合中的元素的个数
127.0.0.1:6379> SCARD set1
(integer) 4
127.0.0.1:6379> sadd set1 hejie09
(integer) 1
127.0.0.1:6379> SCARD set1
(integer) 5

### 移除set集合中的指定元素
127.0.0.1:6379> SREM set1 yunmx													# 移除set集合中的:yunmx
(integer) 1
127.0.0.1:6379> SCARD set1							
(integer) 4
127.0.0.1:6379> SMEMBERS set1												
1) "hejie01"
2) "hejie09"
3) "hejie1"
4) "hejie02"

### 抽随机用法(无序不重复)
127.0.0.1:6379> SRANDMEMBER set1												# 随机抽选出一个元素
"hejie02"
127.0.0.1:6379> SRANDMEMBER set1
"hejie09"
127.0.0.1:6379> SRANDMEMBER set1
"hejie02"
127.0.0.1:6379> SRANDMEMBER set1
"hejie09"
127.0.0.1:6379> SRANDMEMBER set1
"hejie01"
127.0.0.1:6379> SRANDMEMBER set1 2												# 随机抽选出指定的数量元素
1) "hejie1"
2) "hejie01"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "hejie09"
2) "hejie02"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "hejie1"
2) "hejie01"

### 随机移除元素
127.0.0.1:6379> SMEMBERS set1
1) "hejie01"
2) "hejie09"
3) "hejie1"
4) "hejie02"
127.0.0.1:6379> spop set1														# 随机移除一个元素
"hejie02"
127.0.0.1:6379> spop set1
"hejie1"
127.0.0.1:6379> SMEMBERS set1
1) "hejie01"
2) "hejie09"

### 指定的值移动另一个集合中
127.0.0.1:6379> SMEMBERS set1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SMEMBERS set2
1) "er"
2) "san"
3) "yi"
127.0.0.1:6379> smove set1 set2 1												# 将set1中的元素移动到set2,需要移动的值为set1中的1
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) "2"
2) "3"
127.0.0.1:6379> SMEMBERS set2
1) "1"
2) "er"
3) "san"
4) "yi"

### 共同关注:B站,微博(并集)
# 数字集合:差集、交集、并集
127.0.0.1:6379> sadd set1 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> sadd set2 4 5 6 7 8 9
(integer) 6
127.0.0.1:6379>
127.0.0.1:6379> SDIFF set1 set2												# 差集:set1中的元素不存在于set2
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SINTER set1 set2											# 交集
1) "4"
2) "5"
3) "6"
127.0.0.1:6379> SDIFF set2 set1
1) "7"
2) "8"
3) "9"
127.0.0.1:6379> SUNION set1 set2										  	# 并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"

1.5.4 Hash(哈希)

Map集合,key-Map集合,本质和string没有太大区别,还是一个简单的key-vlaue

应用

  • 用户信息之类的
  • 经常变动的信息
  • 更适合存储对象,string更适合字符串存储
### 基础用法
127.0.0.1:6379> hset hash1 name yunmx										# 设置一个key-vlaue 
(integer) 1
127.0.0.1:6379> hget hash1 name												# 获取一个字段值
"yunmx"
127.0.0.1:6379> hmset hash1 name hejie age 28								# 设置多个
OK
127.0.0.1:6379> hgetall hash1												# 获取所有元素
1) "name"
2) "hejie"
3) "age"
4) "28"
127.0.0.1:6379> hget hash1 age												# 获取一个字段值
"28"

### 删除一个值
127.0.0.1:6379> HDEL hash1 name												# 删除指定的key字段
(integer) 1
127.0.0.1:6379> hgetall hash1
1) "age"
2) "28"

### 获取hash所有的长度(键值对)
127.0.0.1:6379> HLEN hash1													# 获取hash中的键值对数量
(integer) 1
127.0.0.1:6379> hset hash1 age 18
(integer) 0
127.0.0.1:6379> hset hash1 age1 19
(integer) 1
127.0.0.1:6379> hset hash1 name hejie
(integer) 1
127.0.0.1:6379> HLEN hash1
(integer) 3

### 判断hash中字段是否存在
127.0.0.1:6379> HGETALL hash1
1) "age"
2) "18"
3) "age1"
4) "19"
5) "name"
6) "hejie"
127.0.0.1:6379> HEXISTS hash1 age											# 判断哈希中是否存在age字段
(integer) 1
127.0.0.1:6379> HEXISTS hash1 yunmx
(integer) 0

### 只获取所有的字段或值
127.0.0.1:6379> hkeys hash1
1) "age"
2) "age1"
3) "name"
127.0.0.1:6379> HVALS hash1
1) "18"
2) "19"
3) "hejie"

### 指定自增
127.0.0.1:6379> HINCRBY hash1 age 1											# 自增1
(integer) 19
127.0.0.1:6379> HINCRBY hash1 age -1
(integer) 18
127.0.0.1:6379> hsetnx hash1 age 1											# 如果存在,不设置
(integer) 0
127.0.0.1:6379> hsetnx hash1 age2 1											# 不存在,则添加新的键值对
(integer) 1

1.5.5 Zset(有序集合)

在set的基础上增加了一个值,多了一个计数位

场景思路:

  • set能做的东西它都能做,存储班级成绩表、工资表排序
  • 普通消息1,重要消息2,带权重进行判断
  • 排行榜应用实现:TOP N
### 基础用法
127.0.0.1:6379> zadd myset 1 one											# 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zadd myset 4 four 5 five									# 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1											# 查看所有的值
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"

### 排序如何实现
# 用法:从最小到最大
# ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> zadd salary 12000 yunmx										# 添加4个用户
(integer) 1
127.0.0.1:6379> zadd salary 11000 hejie
(integer) 1
127.0.0.1:6379> zadd salary 10000 hejie01
(integer) 1
127.0.0.1:6379> zadd salary 9000 yunmx01
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf								# -inf +inf  负无穷到正无穷 从小到大
1) "yunmx01"
2) "hejie01"
3) "hejie"
4) "yunmx"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores					# withscores表示带一些其他参数说明
1) "yunmx01"
2) "9000"
3) "hejie01"
4) "10000"
5) "hejie"
6) "11000"
7) "yunmx"
8) "12000"

### 移除元素
127.0.0.1:6379> ZRANGE salary 0 -1											# 查看所有的元素
1) "yunmx01"
2) "hejie01"
3) "hejie"
4) "yunmx"
127.0.0.1:6379> zrem salary yunmx01											# 移除特定的元素
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "hejie01"
2) "hejie"
3) "yunmx"

### 获取有序集合中的个数
127.0.0.1:6379> ZRANGE salary 0 -1
1) "hejie01"
2) "hejie"
3) "yunmx"
127.0.0.1:6379> ZCARD salary
(integer) 3

### 按区间计算
127.0.0.1:6379> ZRANGE salary 0 -1
1) "hejie01"
2) "hejie"
3) "yunmx"
127.0.0.1:6379> zcount salary 1000 9000										# 查看1000到9000之间的数量
(integer) 0
127.0.0.1:6379> zcount salary 1000 10000									# 查看1000到1-000之间的数量值
(integer) 1

1.5.6 geospatial(地理空间)

城市经纬度查询:http://www.jsons.cn/lngcode/

  • 朋友圈的定位
  • 附近的人
  • 打车距离计算

GEO实现的底层原理其实就是Zset!可以使用Zset来操作GEO!

只有6个命令

  • GEOADD
  • GEODIST
  • GEOHASH
  • GEOPOS
  • GEORADIUS
  • GEORADIUSBYMEMBER
### 添加位置
# geoadd key longitude(经度) latitude(维度) member [longitude latitude member ...]  可添加多个
# 参数:key 值(经度 维度 名称)
127.0.0.1:6379> geoadd china:city 104.065735 30.659462 chengdu
(integer) 1
127.0.0.1:6379> geoadd china:city 106.504962 29.533155 chongqing
(integer) 1

### 获取当前定位经纬度
127.0.0.1:6379> GEOPOS china:city chengdu									# 获取成都的经纬度
1) 1) "104.06573742628097534"
   2) "30.65946118872339099"
127.0.0.1:6379> GEOPOS china:city chongqing									# 获取重庆的经纬度
1) 1) "106.50495976209640503"
   2) "29.53315530684997015"

### 计算两个地方的距离
#单位:m/km/mi/ft
127.0.0.1:6379> geodist china:city chengdu chongqing m						# 计算出成都到重庆的直线距离  单位m 
"266056.2971"
127.0.0.1:6379> geodist china:city chengdu chongqing km						# 计算出成都到重庆的直线距离  单位km
"266.0563"

### 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
# GEORADIUS:
127.0.0.1:6379> GEORADIUS china:city 103 30 1 km							# 以103 30这个点为中心,以1km为半径
(empty array)
127.0.0.1:6379> GEORADIUS china:city 103 30 2 km
(empty array)
127.0.0.1:6379> GEORADIUS china:city 103 30 20 km
(empty array)
127.0.0.1:6379> GEORADIUS china:city 103 30 200 km
1) "chengdu-qingyangqu"
2) "chengdu"
3) "chengdu-jinjiangqu"
127.0.0.1:6379> GEORADIUS china:city 103 30 20000 km
1) "chengdu-qingyangqu"
2) "chongqing"
3) "chengdu"
4) "chengdu-jinjiangqu"					
127.0.0.1:6379> GEORADIUS china:city 103 30 20000 km withcoord				# 附带显示经纬度	
1) 1) "chengdu-qingyangqu"
   2) 1) "104.06151026487350464"
      2) "30.67387107851423167"
2) 1) "chongqing"
   2) 1) "106.50495976209640503"
      2) "29.53315530684997015"
3) 1) "chengdu"
   2) 1) "104.06573742628097534"
      2) "30.65946118872339099"
4) 1) "chengdu-jinjiangqu"
   2) 1) "104.06573742628097534"
      2) "30.65946118872339099"

### 找出位于指定范围内的元素,中心点是由给定的位置元素决定
# GEORADIUSBYMEMBER
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 10 km					# chengdu城市方圆10km的坐标
1) "chengdu"
2) "chengdu-jinjiangqu"
3) "chengdu-qingyangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 100 km
1) "chengdu"
2) "chengdu-jinjiangqu"
3) "chengdu-qingyangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 10 km
1) "chengdu"
2) "chengdu-jinjiangqu"
3) "chengdu-qingyangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 1000 km
1) "chengdu-qingyangqu"
2) "chongqing"
3) "chengdu"
4) "chengdu-jinjiangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 1000 km withcoord		# 附带坐标信息
1) 1) "chengdu-qingyangqu"
   2) 1) "104.06151026487350464"
      2) "30.67387107851423167"
2) 1) "chongqing"
   2) 1) "106.50495976209640503"
      2) "29.53315530684997015"
3) 1) "chengdu"
   2) 1) "104.06573742628097534"
      2) "30.65946118872339099"
4) 1) "chengdu-jinjiangqu"
   2) 1) "104.06573742628097534"
      2) "30.65946118872339099"

### Redis GEOHASH 命令 - 返回一个或多个位置元素的 Geohash 表示  不常用

1.5.7 HyperLogLog(基数统计)

基数:不重复的元素个数,可以接受误差

A(1 2 3 45 5 78)

B(3 4 6 7 8 9 0 9 78 )

基数统计的算法

  • 网页的UV(一个人访问一个网站,但是还是算做一个)

    传统的方式:set保存用户的ID,然后就可以统计set中的元素数量作为标准判断,如果保存大量的用户ID,就会比较麻烦,我们的目的是为了计数而不是保存ID

  • 占用内存是固定的,2^64不同的元素技术,只需要废12KB内存

0.81%的错误率!统计UV时候,可以忽略不计

127.0.0.1:6379> PFADD key1 a 1 2 3 4 b c 6 7 9								# 设置一个HyperLOGLOG		
(integer) 1
127.0.0.1:6379> PFCOUNT key1												# 查看里面不重复的数量个数			
(integer) 10
127.0.0.1:6379> PFADD key2 a a  1 2 3 4 b c 6 7 9 10 10 101 10 10 8 6
(integer) 1
127.0.0.1:6379> PFCOUNT key2
(integer) 13
127.0.0.1:6379> PFMERGE key3 key1 key2										# 合并两个key的元素数量
OK
127.0.0.1:6379> PFCOUNT key3												# 查看不重复的元素数量
(integer) 13

1.5.8 Bitmap(位图)

位存储0和1

2个状态的都可以使用此数据类型

  • 活跃、不活跃
  • 感染人数、未感染人数
127.0.0.1:6379> SETBIT key 0 1												# 使用Bitmaps记录一周的打卡
(integer) 0
127.0.0.1:6379> SETBIT key 1 0
(integer) 0
127.0.0.1:6379> SETBIT key 2 0
(integer) 0
127.0.0.1:6379> SETBIT key 3 1
(integer) 0
## 查看某一天是否有打卡
127.0.0.1:6379> getbit key 3
(integer) 1
127.0.0.1:6379> getbit key 2
(integer) 0

### 统计操作
# 统计打开天数
127.0.0.1:6379> BITCOUNT key
(integer) 2

1.6 Redis事务

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

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行

1.6.1 基本的事务操作

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,事务执行中,按照顺序执行

Redis单条命令保证原子性,但是事务不保证原子性!

  • 一次性
  • 顺序性
  • 排他性
  • 没有隔离级别的概念,没有幻读、脏读…
---入队---
command1
command2
command2
---执行---

三个阶段:

  1. 开启事务(MULTI)
  2. 命令入队(commands)
  3. 执行事务(EXEC)
### 正常执行
127.0.0.1:6379> MULTI								# 开启事务
OK
127.0.0.1:6379> set key1 1							# 命令入队
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> get key2
QUEUED
127.0.0.1:6379> exec								# 事务执行,按照顺序执行
1) OK
2) OK
3) OK
4) "1"
5) "2"

### 放弃事务 
127.0.0.1:6379> MULTI								# 开启事务
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> DISCARD								# 取消事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> get key1							# 放弃事务取不到相关的值
(nil)

### 事务错误一:命令有错,事务中所有的命令都不会被执行!
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> getset key3											# 错误的命令,直接报错
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.	# 执行事务报错
127.0.0.1:6379> get key1											# 结果是所有的命令都不会执行
(nil)

### 事务错误二:运行异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 "yunmx"
QUEUED
127.0.0.1:6379> incr key1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> get key3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range				# 代码结构没问题,抛出异常
3) OK
4) OK
5) "3"
127.0.0.1:6379> get key2
"2"

1.6.2 乐观锁

监控:watch

悲观锁:很悲观;认为任何时候都会出现问题,无论做什么都加锁,影响性能

乐观锁:很乐观,任务任何时候都不会出现问题,所以不会上锁,更新数据的时候去判断一下,在此期间,是否有人修改过这个数据,mysql用的version字段!

### Redis的测试监视
127.0.0.1:6379> set mymoney 1000
OK
127.0.0.1:6379> set myout 0
OK
127.0.0.1:6379> WATCH mymoney										# 监视
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRby mymoney 20
QUEUED
127.0.0.1:6379> INCRBY myout 20
QUEUED
127.0.0.1:6379> exec												# 事务正常结束,数据期间没有发生变动,正常执行了
1) (integer) 980
2) (integer) 20
127.0.0.1:6379>

### 使用watch当乐观锁,测试多线程修改值后事务提交失败
# 第一个进程开启事务
127.0.0.1:6379>  watch mymoney
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY mymoney 10
QUEUED
127.0.0.1:6379> INCRBY myout 10
QUEUED
127.0.0.1:6379> exec												# 未提交前执行第二个事务进行数据的修改
(nil)																# 事务提交后返回修改失败,
127.0.0.1:6379>
# 第二个进程修改我们的值
127.0.0.1:6379> get mymoney
"980"
127.0.0.1:6379> set mymoney 20
OK
127.0.0.1:6379> get mymoney
"20"
127.0.0.1:6379> set mymoney 1000
OK
127.0.0.1:6379> get mymoney
"1000"

### 可以使用unwatch解锁,再次使用watch获取最新的值,再去执行事务

1.7 Redis配置文件详解

1.7.1 redis.conf

启动时候就是通过配置文件来启动的

配置文件对大小写不敏感

中间件学习笔记-Redis入门

包含其他配置文件

中间件学习笔记-Redis入门

网络

中间件学习笔记-Redis入门

bind 127.0.0.1		# 绑定的ip
protected-mode yes	# 保护模式
port 6379			# 端口设置

GENERAL通用配置

中间件学习笔记-Redis入门

daemonize yes		# 已守护进程方式运行,默认是no,我们需要自己开启
pidfile /var/run/redis_6379.pid	# 如果以后台的方式运行,我们就需要指定一个pid文件
# 日志(4个级别)
# Specify the server verbosity level.
# 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 ""	#日志的文件位置名
databases 16	# 默认的数据库数量,16个
always-show-logo yes	# 是否总是显示logo 可以不显示,默认显示

快照SNAPSHOTTING

中间件学习笔记-Redis入门

# 规则
save 900 1		# 如果900秒内,至少有一个key进行修改,我们就进行持久化操作
save 300 10		
save 60 10000
# 我们可以设置自己的一个规则
stop-writes-on-bgsave-error yes	# 持久化出现错误的时候,是否继续进行工作,默认是yes
rdbcompression yes				# 是否压缩rdb文件,压缩就会消耗CPU资源
rdbchecksum yes					# 是否校验rdb文件,出错会进行修复
dir ./							# rdb文件保存的目录

快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件,.rdb .aof,如果没有持久化,那么数据就会断电及失

主从复制 REPLICATION

中间件学习笔记-Redis入门

安全SECURITY

中间件学习笔记-Redis入门

# requirepass foobared   默认没有密码
# 可以通过修改配置文件来设置密码
# 比如:
requirepass 123456

# 验证
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
# 也可以通过命令进行密码设置
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> config set requirepass "110"
OK

127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 110
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>

客户端CLIENTS

中间件学习笔记-Redis入门

# 客户端的一些限制
maxclients 10000   # 设置能连接上redis的客户端数量限制
maxmemory <bytes>	# redis配置的最大内存容量
maxmemory-policy noeviction	# 内存到达上限后的处理策略
	# 移除一些过期的key
	# 报错
	# ...
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key   
3、volatile-random:随机删除即将过期key   
4、allkeys-random:随机删除   
5、volatile-ttl : 删除即将过期的   
6、noeviction : 永不过期,返回错误

APPEND ONLY MODE模式 aof配置

中间件学习笔记-Redis入门

appendonly no   		# 默认不开启aof模式,默认使用的是rdb方式持久化的
appendfilename "appendonly.aof"			# 持久化的文件的名字

# appendfsync always			# 每次修改值都会写入,速度比较慢
appendfsync everysec			# 每秒执行一次sync,可能会丢失1s的数据!
# appendfsync no				# 不同步,这个时候操作系统自己同步数据,速度最快!

1.8 Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到硬盘中,那么一旦服务器进程退出,服务器中的数据状态也会消失,所以Redis提供了持久化功能

1.8.1 RDB(Redis DataBase)

RDB

在指定的时间间隔内,将内存中的数据集体快照写入磁盘中,也就是进行Snapshot快照,它恢复时是将快照文件直接读到内存里。

操作

单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作。这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高校。RDB的缺点是嘴鸥一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改此配置!

rdb保存的文件是dump.rdb

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

# 触发机制
1.save的规则满足的情况下
2.执行了FLUSHALL命令
3.退出redis也会触发

如何恢复rdb文件

  1. 只需要将rdb文件放在redis启动目录下即可,启动的时候会自动检查dump.rdb恢复其中的数据
# 查看存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"

优缺点

优点

  1. 适合大规模的数据恢复
  2. 如果你对数据的完整性要求不高

缺点

  1. 需要一定的时间间隔进行操作!如果redis意外宕机,最后一次修改的数据就没有了
  2. fork进程的时候,会占用一定的内存空间

1.8.2 AOF(Append Only File)

将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部执行一遍

AOF

以日志的形式来记录每个操作,将redis执行过的所有指令记录下来(读操作不会记录),只许追加文件但不可改写文件,redis启动之后会读取文件重新构建数据,换言之,redis重启的话根据日志文件的内容将写指令从前到 后执行一次以完成数据的恢复工作

aof保存的文件是appendonly.aof文件

中间件学习笔记-Redis入门

# 破坏aof文件后,尝试连接
[root@yunmx bin]# redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
# 使用检测文件自动修复这个AOF文件:redis-check-aof
[root@yunmx bin]# redis-check-aof --fix appendonly.aof
0x              62: Expected \r\n, got: 6173
AOF analyzed: size=122, ok_up_to=23, diff=99
This will shrink the AOF from 122 bytes, with 99 bytes, to 23 bytes
Continue? [y/N]: y
Successfully truncated AOF

优缺点

优点

  1. 每次修改都同步,文件完整性更好,
  2. 每秒同步一次,这样会丢失一秒的数据
  3. 从不同步,效率最高的!

缺点

  1. 相对于数据文件来说,aof远远大于rdb,修复的速度比rdb慢
  2. aof运行效率要比rdb慢,会进行IO操作

扩展:

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

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

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

  • 同时开启两种持久化方式

    • 在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
    • RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
  • 性能建议

    • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
    • 如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
    • 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。

1.9 Redis发布订阅

1.9.1 Redis发布订阅

订阅、发布消息图:

中间件学习笔记-Redis入门

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息

Redis客户端可以订阅任意数量的频道

第一个是:消息发送者 第二个是:频道 第三个是:消息订阅者

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

中间件学习笔记-Redis入门

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

中间件学习笔记-Redis入门

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

  • PSUBSCRIBE pattern [pattern …]
    订阅一个或多个符合给定模式的频道。
  • PUBSUB subcommand [argument [argument …]]
    查看订阅与发布系统状态。
  • PUBLISH channel message
    将信息发送到指定的频道。
  • PUNSUBSCRIBE [pattern [pattern …]]
    退订所有给定模式的频道。
  • SUBSCRIBE channel [channel …]
    订阅给定的一个或多个频道的信息。
  • UNSUBSCRIBE [channel [channel …]]
    指退订给定的频道。
# 测试
# 订阅一个频道
127.0.0.1:6379> SUBSCRIBE yunmx										# 订阅一个频道到yunmx
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yunmx"
3) (integer) 1

# 新窗口发送一个消息:发布者
[root@yunmx ~]# redis-cli
127.0.0.1:6379> PUBLISH yunmx test									# 发布者发布消息到指定名称的频道
(integer) 1

# 接收到的消息
127.0.0.1:6379> SUBSCRIBE yunmx		
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yunmx"
3) (integer) 1
1) "message"														# 接收的消息
2) "yunmx"															# 哪个频道
3) "test"															# 消息内容

Redis发布订阅的原理

使用C实现的,通过分析Redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现逻辑,加深对redis的理解。(俺也不懂C,俺也不是开发,咋整)

通过PUBLISH/SUBSCRIBE/PSUBSCRIBE等命令实现发布订阅功能

  • SUBSCRIBE:通过此命令订阅频道以后,redis-server里维护一个字典,字典的键就是一个个channel(频道),而字典的值则是一个链表,链表中保存了所有订阅这个频道的客户端。-----就是将客户端添加到给定channel的订阅链表中
  • PUBLISH:向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查询了记录了订阅这个频道所有的客户端连接,遍历整个链表,将消息发送给所有订阅者
  • PUB、SUB:就是发布、订阅,在redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值进行了消息发布以后,所有订阅它的客户端都会收到相应的消息,这一功能明显的用法就是用作实时消息系统,比如普通的即时聊天、群聊等功能

1.9.2 使用场景

  • 实时消息系统
  • 实时聊天------频道当作聊天室,将信息回显给所有人即可
  • 订阅,公众号订阅等
  • 稍微复杂的场景我们会使用消息中间件(MQ)

2.0 Redis主从复制

2.0.1 概念

主从复制

是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。master以写为主,slave以读为主。

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

主从复制的作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  4. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

一般来说,要将redis运用于生产项目中,只使用一台redis是万万不能的

  • 从结构上,单个redis服务器会发生单点故障,且单机处理所有的请求会导致负载过大
  • 从容量上,单机内存容量有限,一般来说,单台redis最大使用内存不应该超过20G

中间件学习笔记-Redis入门

2.0.2 配置主从

主从复制,读写分离!80%的情况都是在进行读操作!减缓服务器的压力,架构中经常使用!一主二 从!

测试场景

环境配置

只配置从库,不配置主库!

# 启动一个redis,查看信息
127.0.0.1:6379> info replication
# Replication
role:master														# 角色
connected_slaves:0												# 从机数量
master_replid:fa0e795a3e369ed7e46a40ee8818a51255ab6df3
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

复制一个配置文件,修改一下端口号、日志保存的文件名、rdb文件名、pid文件 ,在启动2个redis

[root@yunmx bin]# redis-server redis-conf/redis.conf1			# 6380
6038:C 12 Dec 2021 13:06:38.166 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
6038:C 12 Dec 2021 13:06:38.166 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6038, just started
6038:C 12 Dec 2021 13:06:38.166 # Configuration loaded
[root@yunmx bin]# redis-server redis-conf/redis.conf2			# 6381
6045:C 12 Dec 2021 13:06:39.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
6045:C 12 Dec 2021 13:06:39.641 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6045, just started
6045:C 12 Dec 2021 13:06:39.641 # Configuration loaded

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

未配置主从之前,三个节点都是主节点

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

中间件学习笔记-Redis入门

认老大

一主(79)二从(80,81)

# 6380配置
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379							# 认本机的6379端口服务的redis做老大
OK
127.0.0.1:6380> info replication
# Replication
role:slave														# 变成了从节点
master_host:127.0.0.1											# 主节点的信息
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5
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
# 查看主机的信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=168,lag=0			# 从机的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=168,lag=0
master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:168
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:168

真实的主从配置应该在配置文件中配置,这样的话是永久的,上述使用的是命令,只是暂时的

中间件学习笔记-Redis入门

replicaof <masterip> <masterport>   # 主机的IP地址和端口号
masterauth <master-password>		# 如果设定有密码,配置密码即可

2.0.3 特性验证

主机可以设置值,从机不能写,主机中所有信息和数据,从机都会保存

# 主机设置一个key
127.0.0.1:6379> set key1 yunmx
OK
127.0.0.1:6379>
# 从机中也会有,从机无法设置key
127.0.0.1:6380> keys *
1) "key1"
127.0.0.1:6380> get key1
"yunmx"
127.0.0.1:6380> set key2 yunmx2
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>

老大宕机后,从机还是从机,只是会显示主机状态不正常;主机恢复,从机依旧可以直接获取到主机写入的信息,保证了一定的高可用性

如果使用的是我们命令行配置的主从,如果从机宕机后,从机就会脱离主从了,需要再次命令行配置从机,变成从机以后,就能获取到key的值了

# 停掉6379主机服务,查看从机集群状态
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1065
master_link_down_since_seconds:10
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1065
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1065

# 恢复6379主机服务,验证是否还会同步数据
[root@yunmx ~]# redis-cli -p 6379
127.0.0.1:6379> set key2 yunmx2													# 恢复主节点,设置一个key
OK
127.0.0.1:6379>
# 从节点读取主机恢复后设置的key
127.0.0.1:6380> get key2														# 能够正常读取
"yunmx2"

# 停止从机服务,主机设定一个key,在恢复从机,进行验证
127.0.0.1:6379> set key3 yunmx3
OK

127.0.0.1:6380> get key3														# 从机无法获得key3的数据
(nil)

127.0.0.1:6381> get key3
"yunmx3"

2.0.4 复制原理

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

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

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

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

但是只要重新连接master,一次完全同步将被自动执行!我们的数据一定可以在从机中看到!

2.0.5 层层链路

上一个M链接下一个S!

可以完成主从复制!

中间件学习笔记-Redis入门

如果没有79,这个时候能不能选择一个老大出来呢,这个时候需要手动去配置!

谋朝篡位:slaveof no one 使自己变成主机!如果老大回来了,也是需要手动配置

2.1 Redis哨兵模式

自动版选老大的模式

2.1.1 概述

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

  • 能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

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

2.1.2 基本架构

中间件学习笔记-Redis入门

哨兵模式的作用:

中间件学习笔记-Redis入门

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

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

中间件学习笔记-Redis入门

使用哨兵模式,至少都会启动6个进程

假设主服务宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务不可用,这个现场叫主观下线。当后面的哨兵也检测到主服务不可用,并且数量达到一定值后,那么哨兵就会进行一次投票,投票的结果由一个哨兵发起,进行failover故障转移操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务实现切换主机,这个过程被称为客观下线

2.1.3 场景测试

我们目前测试的架构是一主二从

配置哨兵模式的配置文件

# 新建配置文件并编辑以下内容
sentinel monitor myredis 127.0.0.1 6379 1# 语法:sentinel monitor 被监控的名称 host port 1(1代表主机宕机后,从机投票让谁来接替成为主机,)

启动哨兵

[root@yunmx bin]# redis-sentinel redis-conf/sentinel.conf				# 启动一个哨兵
12680:X 12 Dec 2021 15:13:42.570 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
12680:X 12 Dec 2021 15:13:42.570 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=12680, just started
12680:X 12 Dec 2021 15:13:42.570 # Configuration loaded
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 6.0.6 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 12680
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

12680:X 12 Dec 2021 15:13:42.571 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
12680:X 12 Dec 2021 15:13:42.575 # Sentinel ID is ea7ccf0119a4cf2873cf3bb108da5c7af86d36bd
12680:X 12 Dec 2021 15:13:42.575 # +monitor master myredis 127.0.0.1 6379 quorum 1
12680:X 12 Dec 2021 15:13:42.575 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:13:42.580 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

手动宕机测试

# 关掉主机
127.0.0.1:6379> SHUTDOWN
not connected>
# 哨兵监控的一些信息
12680:X 12 Dec 2021 15:16:49.174 # +failover-state-select-slave master myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:49.241 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:49.241 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:49.324 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:50.181 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:50.181 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:50.237 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.182 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.182 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.234 # +failover-end master myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.234 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381		# 哨兵显示主机自动切换到了6381
12680:X 12 Dec 2021 15:16:51.234 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381
12680:X 12 Dec 2021 15:16:51.234 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
12680:X 12 Dec 2021 15:17:21.244 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
# 检查6381主机的信息
127.0.0.1:6381> info replication
# Replication
role:master								# 变成了master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=21944,lag=0
master_replid:a4719b795f6c088ed1a11408a2bc52cc48ece215
master_replid2:367d9493cb151b433bd535ad9e49603d1fa35013
master_repl_offset:21944
second_repl_offset:11812
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:21944

如果master宕机以后,这个时候就会从从机中随机选择一个服务器(有一个自己的投票算法)作为主机

主机恢复

# 重新开启之前宕机的主机。观察哨兵的反应
12680:X 12 Dec 2021 15:23:41.461 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
# 查看先前主机的信息
127.0.0.1:6379> info replication
# Replication
role:slave								# 变成从机
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:41645
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a4719b795f6c088ed1a11408a2bc52cc48ece215
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:41645
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:39205
repl_backlog_histlen:2441
# 只能归并到新的主机下,当作从机,这就是哨兵模式的规则!

优缺点

  • 哨兵集群,基于主从复制模式,所有的主从配置优点它多有

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

  • 就是主从模式的升级,手动到自动,更加健壮!

  • 不好在线扩容,集群容量一旦达到上线,在线扩容就十分麻烦!

  • 实现哨兵模式的配置其实很麻烦的,里面有很多选择!

哨兵模式的全部配置

<后续进行详细学习>

# Example sentinel.conf
 
# port <sentinel-port>
# The port that this sentinel instance will run on
# sentinel实例运行的端口
port 26379												# 哨兵进程运行的端口号
 
# sentinel announce-ip <ip>
# sentinel announce-port <port>
#
# The above two configuration directives are useful in environments where,
# because of NAT, Sentinel is reachable from outside via a non-local address.
#
# When announce-ip is provided, the Sentinel will claim the specified IP address
# in HELLO messages used to gossip its presence, instead of auto-detecting the
# local address as it usually does.
#
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
# will announce the specified TCP port.
#
# The two options don't need to be used together, if only announce-ip is
# provided, the Sentinel will announce the specified IP and the server port
# as specified by the "port" option. If only announce-port is provided, the
# Sentinel will announce the auto-detected local IP and the specified port.
#
# Example:
#
# sentinel announce-ip 1.2.3.4
 
# dir <working-directory>
# Every long running process should have a well-defined working directory.
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
# for the process to don't interferer with administrative tasks such as
# unmounting filesystems.
dir /tmp
 
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name : master Redis Server名称
# ip : master Redis Server的IP地址
# redis-port : master Redis Server的端口号
# quorum : 主实例判断为失效至少需要 quorum 个 Sentinel 进程的同意,只要同意 Sentinel 的数量不达标,自动failover就不会执行
#
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
# (Objectively Down) state only if at least <quorum> sentinels agree.
#
# Note that whatever is the ODOWN quorum, a Sentinel will require to
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
# Slaves are auto-discovered, so you don't need to specify slaves in
# any way. Sentinel itself will rewrite this configuration file adding
# the slaves using additional configuration options.
# Also note that the configuration file is rewritten when a
# slave is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
#
sentinel monitor mymaster 127.0.0.1 6379 2
 
# sentinel auth-pass <master-name> <password>
#
# Set the password to use to authenticate with the master and slaves.
# Useful if there is a password set in the Redis instances to monitor.
#
# Note that the master password is also used for slaves, so it is not
# possible to set a different password in masters and slaves instances
# if you want to be able to monitor these instances with Sentinel.
#
# However you can have Redis instances without the authentication enabled
# mixed with Redis instances requiring the authentication (as long as the
# password set is the same for all the instances requiring the password) as
# the AUTH command will have no effect in Redis instances with authentication
# switched off.
#
# Example:
#
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
# sentinel down-after-milliseconds <master-name> <milliseconds>
#
# Number of milliseconds the master (or any attached slave or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively
# Down).
# 选项指定了 Sentinel 认为Redis实例已经失效所需的毫秒数。当实例超过该时间没有返回PING,或者直接返回错误, 那么 Sentinel 将这个实例标记为主观下线(subjectively down,简称 SDOWN )
#
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000
 
# sentinel parallel-syncs <master-name> <numslaves>
#
# How many slaves we can reconfigure to point to the new slave simultaneously
# during the failover. Use a low number if you use the slaves to serve query
# to avoid that all the slaves will be unreachable at about the same
# time while performing the synchronization with the master.
# 选项指定了在执行故障转移时, 最多可以有多少个从Redis实例在同步新的主实例, 在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长。
sentinel parallel-syncs mymaster 1
 
# sentinel failover-timeout <master-name> <milliseconds>
#
# Specifies the failover timeout in milliseconds. It is used in many ways:
#
# - The time needed to re-start a failover after a previous failover was
# already tried against the same master by a given Sentinel, is two
# times the failover timeout.
#
# - The time needed for a slave replicating to a wrong master according
# to a Sentinel current configuration, to be forced to replicate
# with the right master, is exactly the failover timeout (counting since
# the moment a Sentinel detected the misconfiguration).
#
# - The time needed to cancel a failover that is already in progress but
# did not produced any configuration change (SLAVEOF NO ONE yet not
# acknowledged by the promoted slave).
#
# - The maximum time a failover in progress waits for all the slaves to be
# reconfigured as slaves of the new master. However even after this time
# the slaves will be reconfigured by the Sentinels anyway, but not with
# the exact parallel-syncs progression as specified.
# 如果在该时间(ms)内未能完成failover操作,则认为该failover失败
#
# Default is 3 minutes.
sentinel failover-timeout mymaster 180000
 
# SCRIPTS EXECUTION
#
# sentinel notification-script and sentinel reconfig-script are used in order
# to configure scripts that are called to notify the system administrator
# or to reconfigure clients after a failover. The scripts are executed
# with the following rules for error handling:
#
# If script exits with "1" the execution is retried later (up to a maximum
# number of times currently set to 10).
#
# If script exits with "2" (or an higher value) the script execution is
# not retried.
#
# If script terminates because it receives a signal the behavior is the same
# as exit code 1.
#
# A script has a maximum running time of 60 seconds. After this limit is
# reached the script is terminated with a SIGKILL and the execution retried.
 
# NOTIFICATION SCRIPT
#
# sentinel notification-script <master-name> <script-path>
#
# Call the specified notification script for any sentinel event that is
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
# This script should notify the system administrator via email, SMS, or any
# other messaging system, that there is something wrong with the monitored
# Redis systems.
#
# The script is called with just two arguments: the first is the event type
# and the second the event description.
#
# The script must exist and be executable in order for sentinel to start if
# this option is provided.
# 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,但是很常用。
#
# Example:
#
# sentinel notification-script mymaster /var/redis/notify.sh
 
# CLIENTS RECONFIGURATION SCRIPT
#
# sentinel client-reconfig-script <master-name> <script-path>
#
# When the master changed because of a failover a script can be called in
# order to perform application-specific tasks to notify the clients that the
# configuration has changed and the master is at a different address.
#
# The following arguments are passed to the script:
#
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#
# <state> is currently always "failover"
# <role> is either "leader" or "observer"
#
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected slave
# (now a master).
#
# This script should be resistant to multiple invocations.
#
# Example:
#
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

2.2 Redis缓存穿透和雪崩

服务的高可用问题!

Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时也会带来一些问题,其中,最要害的就是数据的一致性问题,从严格意义上来讲,这个问题无解,如果对数据一致性要求较高的,那么就不能使用缓存。还有一些典型的问题就是:缓存穿透、缓存雪崩、缓存击穿。目前,业界也都有比较流行的解决方案。

2.2.1 缓存穿透

查不到导致的

概念

用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

在这里插入图片描述

解决方案

  • 布隆过滤器

是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力

中间件学习笔记-Redis入门

  • 缓存空对象

当存储层不命中时候,即使返回的空对象也将其存储起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源

中间件学习笔记-Redis入门

缓存空对象的2个问题:

  1. 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能存在很多很多空值的键
  2. 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一定时间窗口的不一致,这对于需要保持一致性的业务会有影响

2.2.2 缓存击穿

量太大,缓存过期!

概念

  • 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

  • 缓存击穿是指一个key非常热点,在不停的扛着大并发, 大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞

  • 当某个key在过期的瞬间,会大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大

解决方案

  • 设置热点数据不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题

  • 加互斥锁

分布式锁:保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的 权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因为对分布式锁的考验很大

2.2.3 缓存雪崩

概念

是指某一个时间段,缓存集中过期失效,redis宕机

中间件学习笔记-Redis入门

解决方案

  • redis高可用

redis集群,保证服务的高可用(异地多活)

  • 限流降级

在缓存失效后,通过加锁、队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查看数据和写缓存,其他线程等待

  • 数据预热

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

上一篇:30_Scrapy框架_CrawlSpider


下一篇:k8s之pod讲解