根据哔哩哔哩 狂神说讲解视频记录笔记,并添加自己的一些理解
1. 关系数据库简介
关系数据库是数据项之间具有预定义关系的数据项的集合。以二维表格形式存储数据,支持SQL增删改查
详细可查看文章:关系数据库是什么?
1.1、SQL
SQL(结构化查询语言)是用于与关系数据库通信的主要接口。SQL 于 1986 年成为美国国家标准协会 (ANSI) 的标准。流行的所有关系数据库引擎都支持标准的 ANSI SQL,其中一些引擎还对 ANSI SQL 进行了扩展,可支持特定于该引擎的功能。SQL 可用于添加、更新或删除数据行,检索事务处理和分析应用程序的数据子集,以及管理数据库的所有方面。
1.2、ACID合规性
所有数据库事务都必须遵守 ACID,即必须是原子的、一致的、隔离的和持久的,以确保数据的完整性。
- 原子性要求事务作为一个整体成功执行,如果事务的任一部分执行失败,则整个事务都将无效。
-
一致性指事务执行前后,数据库必须从一个合法状态转移到另一个合法状态,保证数据的完整性和业务规则(如约束、触发器等)不被破坏。口语说就是满足现实世界规则
-
隔离对于实现并发控制至关重要,它确保每个事务对其自身是独立的。
-
持久性要求在成功完成事务后,对数据库所做的所有更改都是永久性的。
2. 关系型数据库架构演进概述
关系型数据库架构演进一篇不错的文章:Sql Or NoSql,看完这一篇你就懂了
2.1、单机Mysql时代
90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题:
- 数据量增加到一定程度,单机数据库就放不下了
- 数据的索引(B+ Tree),一个机器内存也存放不下
- 访问量变大后(读写混合),一台服务器承受不住。
2.2、Memcached(缓存) + Mysql + 垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!
发展过程:优化数据结构和索引–》文件缓存(IO)–》Memcached(当时最热门的技术)
优化过程经历了以下几个过程:
- 优化数据库的数据结构和索引(难度大)
- 文件缓存,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了
- MemCache,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。
2.3、分库分表 + 水平拆分 + Mysql集群
2.4、如今最近的年代
如今信息量井喷式增长,各种各样的数据出现(用户定位数据,图片数据等),大数据的背景下关系型数据库(RDBMS)无法满足大量数据要求。Nosql数据库就能轻松解决这些问题。目前一个基本的互联网项目:
[
3.为什么要用NoSQL ?
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!
什么是Nosql
NoSQL = Not Only SQL(不仅仅是SQL)
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
Nosql特点
NoSQL数据库的核心特点可以总结为:
1. 灵活的数据模型
- 支持键值(Key-Value)、文档(Document)、列族(Column-Family)、图(Graph)等非结构化或半结构化数据,无固定表结构(Schema-less)。
2. 高扩展性与分布式
- 通过水平扩展(分片、集群)轻松应对海量数据和高并发,如MongoDB的分片、Redis的集群模式。
3. 高性能
- 牺牲部分ACID特性(如强一致性),优先保证高吞吐和低延迟(如Redis单机10万+ QPS)。 读一秒11万次,写8万
4. 弱化事务支持
- 多数NoSQL仅支持单文档/单键原子操作,少数(如MongoDB 4.0+)支持多文档事务。
5. 适用场景
- 适合大数据、实时分析、高并发读写(如社交网络、物联网日志)。
一句话总结:NoSQL以灵活性、扩展性、高性能为核心,牺牲严格一致性,换取对海量异构数据的高效处理。
4. NoSQL的四大分类
4.1、KY键值对
- 新浪:Redis
- 美团:Redis+Tair
- 阿里、百度:Redis + memcache
4.2、文档型数据库(bson格式和json一样):
- MongoDB(一般必须要掌握)
MongoDB 是一个超灵活的“文档型”数据库,你可以把它想象成一个超级能装的 JSON 文件柜,不用像传统数据库那样死板地提前规定表格格式,想存啥就存啥,随时加字段都没问题!
- 写数据超自由:不用先建表结构,数据像存 JSON 一样直接扔进去,特别适合需求老变、数据结构不固定的场景(比如用户画像、日志分析)。
- 查询超强大:支持复杂的查找、聚合统计,甚至能全文搜索,比 Excel 高级多了。
- 扩展超简单:数据大了就加机器,自动分片(Sharding),不用像 MySQL 那样头疼分库分表。
- 速度够快:高频读写场景下(比如游戏玩家数据、实时评论),性能比传统数据库更猛。
举个栗子🌰
存一篇博客文章,连带标题、作者、标签、评论,直接一个 JSON 扔进去:
{
"title": "为什么程序员爱MongoDB?",
"author": "码叔",
"tags": ["数据库", "NoSQL", "吐槽"],
"comments": [
{ "user": "小白", "text": "学到了!" },
{ "user": "大佬", "text": "不如Redis快" }
]
}
- 但是事务支持弱:早期版本只能单条操作保证原子性,现在虽然支持多文档事务,但性能会下降。
- 吃内存:数据全放内存才快,机器配置不能太抠门。
总结:MongoDB 就是数据库里的“乐高”,随拼随用,适合快速迭代的互联网项目,但别拿它当银行记账系统用 😉
4.3、列存储数据库
- HBase
- 分布式文件系统
4.4、图关系数据库
不是用来存图型,而是放的关系,比如朋友圈社交网络,广告推荐
Redis入门
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务。
“超快内存大缓存”,但比缓存猛多了!—— 它像你的大脑记忆,数据全放内存里,读写速度堪比闪电(每秒10万+操作),专门解决那些“慢数据库”搞不定的高频、高并发问题
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
Redis能做什么?
- 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
- 高效率、用于高速缓冲
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(eg:浏览量)
- 。。。
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- …
官网:https://redis.io/
中文网:http://www.redis.cn/
介绍:http://www.redis.cn/topics/introduction
5. 基础知识
redis默认有16个数据库,16个数据库为:DB 0~DB 15 默认使用DB 0 ,可以使用select n
切换到DB n,dbsize
可以查看当前数据库的大小,与key数量相关。
127.0.0.1:6379> config get databases # 命令行查看数据库数量databases
1) "databases"
2) "16"
127.0.0.1:6379> select 8 # 切换数据库 DB 8
OK
127.0.0.1:6379[8]> dbsize # 查看数据库大小
(integer) 0
# 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
127.0.0.1:6379> set name sakura
OK
127.0.0.1:6379> SELECT 0
OK
127.0.0.1:6379[8]> get name # db0中并不能获取db8中的键值对。
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 8
OK
127.0.0.1:6379> keys *
"counter:rand_int"
"mylist"
"name"
"key:rand_int"
"myset:rand_int"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5
keys *
:查看当前数据库中所有的key。
flushdb
:清空当前数据库中的键值对。
flushall
:清空所有数据库的键值对。
Redis是单线程的,Redis是基于内存操作的。
所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。
Redis为什么单线程还这么快?QPS达到10W+
- 误区1:高性能的服务器一定是多线程的?
- 误区2:多线程(CPU上下文会切换!)一定比单线程效率高!
CPU>内存>硬盘的速度
核心:Redis是将所有的数据放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!),对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存存储数据情况下,单线程就是最佳的方案。
五大数据类型
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作 数据库,高速缓存和消息队列代理(消息中间件MQ)。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
Redis-key
命令查询:http://www.redis.cn/commands.html
在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
下面学习的命令:
exists key
:判断键是否存在del key
:删除键值对move key db
:将键值对移动到指定数据库expire key second
:设置键值对的过期时间ttl key
:查看过期剩余时间type key
:查看value的数据类型
127.0.0.1:6379> keys * # 查看当前数据库所有key
(empty list or set)
127.0.0.1:6379> set name qinjiang # set key
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> move age 1 # 将键值对移动到指定数据库
(integer) 1
127.0.0.1:6379> EXISTS age # 判断键是否存在
(integer) 0 # 不存在
127.0.0.1:6379> EXISTS name
(integer) 1 # 存在
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> del age # 删除键值对
(integer) 1 # 删除个数
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> EXPIRE age 15 # 设置键值对的过期时间
(integer) 1 # 设置成功 开始计数
127.0.0.1:6379> ttl age # 查看key的过期剩余时间
(integer) 13
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key过期,-1表示key未设置过期时间
127.0.0.1:6379> get age # 过期的key 会被自动delete
(nil)
127.0.0.1:6379> keys *
"name"
127.0.0.1:6379> type name # 查看value的数据类型
string
关于TTL命令
Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:
- 当前key没有设置过期时间,所以会返回-1.
- 当前key有设置过期时间,而且key已经过期,所以会返回-2.
- 当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.
关于重命名RENAME
和RENAMENX
RENAME key newkey
修改 key 的名称RENAMENX key newkey
仅当 newkey 不存在时,将 key 改名为 newkey 。
String(字符串)
常用命令及其示例:
- 普通的set、get直接略过。
- append
APPEND key value
: 向指定的key的value后追加字符串,返回一个字符串长度
127.0.0.1:6379> set msg hello
OK
127.0.0.1:6379> append msg " world"
(integer) 11
127.0.0.1:6379> get msg
“hello world”
- decr/incr
DECR/INCR key
: 将指定key的value数值进行+1/-1(仅对于数字)
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> incr age
(integer) 21
127.0.0.1:6379> decr age
(integer) 20
INCRBY/DECRBY key n
: 按指定的步长对数值进行加减
127.0.0.1:6379> INCRBY age 5
(integer) 25
127.0.0.1:6379> DECRBY age 10
(integer) 15
INCRBYFLOAT key n
: 为数值加上浮点型数值
127.0.0.1:6379> INCRBYFLOAT age 5.2
“20.2”
STRLEN key
: 获取key保存值的字符串长度
127.0.0.1:6379> get msg
“hello world”
127.0.0.1:6379> STRLEN msg
(integer) 11
GETRANGE key start end
: 截取,按起止位置获取字符串(闭区间,起止位置都取)
127.0.0.1:6379> get msg
“hello world”
127.0.0.1:6379> GETRANGE msg 3 9
“lo worl”
修改指定位置字符SETRANGE key offset value
:用指定的value 替换key中 offset开始的值
127.0.0.1:6379> set msg hello
OK
127.0.0.1:6379> setrange msg 2 hello
(integer) 7
127.0.0.1:6379> get msg
"hehello"
127.0.0.1:6379> set msg2 world
OK
127.0.0.1:6379> setrange msg2 2 ww
(integer) 5
127.0.0.1:6379> get msg2
"wowwd"
getset GETSET key value
: 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
先get再set,如果get不到就set一个新键值
127.0.0.1:6379> GETSET msg test
“hello world”
SETNX key value
: 仅当key不存在时进行set
127.0.0.1:6379> SETNX msg test
(integer) 0
127.0.0.1:6379> SETNX name sakura
(integer) 1
- *
SETEX key seconds value
: set 键值对并设置过期时间
127.0.0.1:6379> setex name 10 root
OK
127.0.0.1:6379> get name
(nil)
MSET key1 value1 [key2 value2..]
: 批量set键值对
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK
MSETNX key1 value1 [key2 value2..]
: 批量设置键值对,仅当参数中所有的key都不存在时执行
127.0.0.1:6379> MSETNX k1 v1 k4 v4
(integer) 0
MGET key1 [key2..]
: 批量获取多个key保存的值
127.0.0.1:6379> MGET k1 k2 k3
1) “v1”
2) “v2”
3) “v3”
psetexPSETEX key milliseconds value
: 和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间
对象形式
set user:{name:zhangsan,age:2}
127.0.0.1:6379[1]> set user:1:name zhangsan
OK
127.0.0.1:6379[1]> set user:1:age 2
OK
127.0.0.1:6379[1]> get user:1:name
"zhangsan"
127.0.0.1:6379[1]> mget user:1:name user:1:age
1) "zhangsan"
2) "2
String类似的使用场景:value除了是字符串还可以是数字,用途举例:
- 计数器
- 统计多单位的数量:uid:123666:follow 0
- 粉丝数
- 对象存储缓存
List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 2^(32 - 1) 个元素 (4294967295, 每个列表超过40亿个元素)。
首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等。
正如图Redis中List是可以进行双端操作的,所以命令也就分为了LXXX和RLLL两类,有时候L也表示List例如LLEN
LPUSH/RPUSH key value1[value2..]
从左边/右边向列表中PUSH值(一个或者多个)。LRANGE key start end
获取list 起止元素==(索引从左往右 递增)==LPUSHX/RPUSHX key value
向已存在的列名中push值(一个或者多个)LINSERT key BEFORE|AFTER pivot value
在指定列表元素的前/后 插入valueLLEN key
查看列表长度LINDEX key index
通过索引获取列表元素LSET key index value
通过索引为元素设值,相当于更新操作LPOP/RPOP key
从最左边/最右边移除值 并返回RPOPLPUSH source destination
将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部LTRIM key start end
通过下标截取指定范围内的列表LREM key count value
List中是允许value重复的 count > 0:从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。BLPOP/BRPOP key1[key2] timout
移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。BRPOPLPUSH source destination timeout
和RPOPLPUSH功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
操作示例:
---------------------------LPUSH---RPUSH---LRANGE--------------------------------
127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1}
(integer) 1
127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1}
(integer) 2
127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3}
(integer) 3
127.0.0.1:6379> get mylist # 普通的get是无法获取list值的
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 获取起止位置范围内的元素
"k2"
"k1"
"k3"
127.0.0.1:6379> LRANGE mylist 0 2
"k2"
"k1"
"k3"
127.0.0.1:6379> LRANGE mylist 0 1
"k2"
"k1"
127.0.0.1:6379> LRANGE mylist 0 -1 # 获取全部元素
"k2"
"k1"
"k3"
---------------------------LPUSHX---RPUSHX-----------------------------------
127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失败
(integer) 0
127.0.0.1:6379> LPUSHX list v1 v2
(integer) 0
127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左边 PUSH k4 k5
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
"k5"
"k4"
"k2"
"k1"
"k3"
---------------------------LINSERT--LLEN--LINDEX--LSET----------------------------
127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素后 插入ins_key1
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1
"k5"
"k4"
"k2"
"ins_key1"
"k1"
"k3"
127.0.0.1:6379> LLEN mylist # 查看mylist的长度
(integer) 6
127.0.0.1:6379> LINDEX mylist 3 # 获取下标为3的元素
"ins_key1"
127.0.0.1:6379> LINDEX mylist 0
"k5"
127.0.0.1:6379> LSET mylist 3 k6 # 将下标3的元素 set值为k6
OK
127.0.0.1:6379> LRANGE mylist 0 -1
"k5"
"k4"
"k2"
"k6"
"k1"
"k3"
---------------------------LPOP--RPOP--------------------------
127.0.0.1:6379> LPOP mylist # 左侧(头部)弹出
"k5"
127.0.0.1:6379> RPOP mylist # 右侧(尾部)弹出
"k3"
---------------------------RPOPLPUSH--------------------------
127.0.0.1:6379> LRANGE mylist 0 -1
"k4"
"k2"
"k6"
"k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 将mylist的最后一个值(k1)弹出,加入到newlist的头部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1
"k1"
127.0.0.1:6379> LRANGE mylist 0 -1
"k4"
"k2"
"k6"
---------------------------LTRIM--------------------------
127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
"k4"
"k2"
初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2
---------------------------LREM--------------------------
127.0.0.1:6379> LREM mylist 3 k2 # 从头部开始搜索 至多删除3个 k2
(integer) 3
删除后:mylist: k2,k2,k2,k4,k2,k2,k2,k2
127.0.0.1:6379> LREM mylist -2 k2 #从尾部开始搜索 至多删除2个 k2
(integer) 2
删除后:mylist: k2,k2,k2,k4,k2,k2
---------------------------BLPOP--BRPOP--------------------------
mylist: k2,k2,k2,k4,k2,k2
newlist: k1
127.0.0.1:6379> BLPOP newlist mylist 30 # 从newlist中弹出第一个值,mylist作为候选
"newlist" # 弹出
"k1"
127.0.0.1:6379> BLPOP newlist mylist 30
"mylist" # 由于newlist空了 从mylist中弹出
"k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 超时了
127.0.0.1:6379> BLPOP newlist 30 # 我们连接另一个客户端向newlist中push了test, 阻塞被解决。
"newlist"
"test"
(12.54s)
小结
- list实际上是一个双向链表,before Node after , left/right 都可以插入值
- 如果key不存在,则创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
应用:
消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)
Set(集合)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 2^(32 - 1) 。
SADD key member1[member2..]
向集合中无序增加一个/多个成员SCARD key
获取集合的成员数SMEMBERS key
返回集合中所有的成员SISMEMBER key member
查询member元素是否是集合的成员,结果是无序的SRANDMEMBER key [count]
随机返回集合中count个成员,count缺省值为1SPOP key [count]
随机移除并返回集合中count个成员,count缺省值为1SMOVE source destination member
将source集合的成员member移动到destination集合SREM key member1[member2..]
移除集合中一个/多个成员SDIFF key1[key2..]
返回所有集合的差集 key1- key2 - …SDIFFSTORE destination key1[key2..]
在SDIFF的基础上,将结果保存到集合中==(覆盖)==。不能保存到其他类型key噢!SINTER key1 [key2..]
返回所有集合的交集SINTERSTORE destination key1[key2..]
在SINTER的基础上,存储结果到集合中。覆盖SUNION key1 [key2..]
返回所有集合的并集SUNIONSTORE destination key1 [key2..]
在SUNION的基础上,存储结果到及和张。覆盖SSCAN KEY [MATCH pattern] [COUNT count]
在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分
代码示例
---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------
127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成员 m1~m4
(integer) 4
127.0.0.1:6379> SCARD myset # 获取集合的成员数目
(integer) 4
127.0.0.1:6379> smembers myset # 获取集合中所有成员
"m4"
"m3"
"m2"
"m1"
127.0.0.1:6379> SISMEMBER myset m5 # 查询m5是否是myset的成员
(integer) 0 # 不是,返回0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1 # 是,返回1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1
---------------------SRANDMEMBER--SPOP----------------------------------
127.0.0.1:6379> SRANDMEMBER myset 3 # 随机返回3个成员
"m2"
"m3"
"m4"
127.0.0.1:6379> SRANDMEMBER myset # 随机返回1个成员
"m3"
127.0.0.1:6379> SPOP myset 2 # 随机移除并返回2个成员
"m1"
"m4"
将set还原到{m1,m2,m3,m4}
---------------------SMOVE--SREM----------------------------------------
127.0.0.1:6379> SMOVE myset newset m3 # 将myset中m3成员移动到newset集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
"m4"
"m2"
"m1"
127.0.0.1:6379> SMEMBERS newset
"m3"
127.0.0.1:6379> SREM newset m3 # 从newset中移除m3元素
(integer) 1
127.0.0.1:6379> SMEMBERS newset
(empty list or set)
下面开始是多集合操作,多集合操作中若只有一个参数默认和自身进行运算
setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
-----------------------------SDIFF------------------------------------
127.0.0.1:6379> SDIFF setx sety setz # 等价于setx-sety-setz
"m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
"m4"
"m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
"m5"
-------------------------SINTER---------------------------------------
共同关注(交集)
127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
"m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
"m2"
"m6"
-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集
"m4"
"m6"
"m3"
"m2"
"m1"
"m5"
127.0.0.1:6379> SUNION setx sety # setx sety 并集
"m4"
"m6"
"m2"
"m1"
"m5"
Hash(哈希)
就是一个键(Key)对应一个 字段-值(Field-Value)的映射表,像Java里的HashMap
Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。
HSET key field value
将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0HMSET key field1 value1 [field2 value2..]
同时将多个 field-value (域-值)对设置到哈希表 key 中。HSETNX key field value
只有在字段 field 不存在时,设置哈希表字段的值。HEXISTS key field
查看哈希表 key 中,指定的字段是否存在。HGET key field value
获取存储在哈希表中指定字段的值HMGET key field1 [field2..]
获取所有给定字段的值HGETALL key
获取在哈希表key 的所有字段和值HKEYS key
获取哈希表key中所有的字段HLEN key
获取哈希表中字段的数量HVALS key
获取哈希表中所有值HDEL key field1 [field2..]
删除哈希表key中一个/多个field字段HINCRBY key field n
为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段HINCRBYFLOAT key field n
为哈希表 key 中的指定字段的浮点数值加上增量 n。HSCAN key cursor [MATCH pattern] [COUNT count]
迭代哈希表中的键值对。
代码示例
------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura # 将studentx哈希表作为一个对象,设置name为sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc # 重复设置field进行覆盖,并返回0
(integer) 0
127.0.0.1:6379> HSET studentx age 20 # 设置studentx的age为20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 设置sex为1,tel为15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 设置已存在的field
(integer) 0 # 失败
127.0.0.1:6379> HSETNX studentx email 12345@qq.com
(integer) 1 # 成功
----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在
-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 获取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 获取studentx中name、age、tel字段的value
"gyc"
"20"
"15623667886"
127.0.0.1:6379> HGETALL studentx # 获取studentx中所有的field及其value
"name"
"gyc"
"age"
"20"
"sex"
"1"
"tel"
"15623667886"
"email"
"12345@qq.com"
--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field
"name"
"age"
"sex"
"tel"
"email"
127.0.0.1:6379> HLEN studentx # 查看studentx中的字段数量
(integer) 5
127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value
"gyc"
"20"
"1"
"15623667886"
"12345@qq.com"
-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx
"name"
"age"
"email"
-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段数值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整数字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于结构化数据存储,Sring更加适合字符串存储!
Zset(有序集合)
与Set不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
ZADD key score member1 [score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数ZCARD key
获取有序集合的成员数ZCOUNT key min max
计算在有序集合中指定区间score的成员数ZINCRBY key n member
有序集合中对指定成员的分数加上增量 nZSCORE key member
返回有序集中,成员的分数值ZRANK key member
返回有序集合中指定成员的索引ZRANGE key start end
通过索引区间返回有序集合成指定区间内的成员ZRANGEBYLEX key min max
通过字典区间返回有序集合的成员ZRANGEBYSCORE key min max
通过分数返回有序集合指定区间内的成员==-inf 和 +inf分别表示最小最大值,只支持开区间()==ZLEXCOUNT key min max
在有序集合中计算指定字典区间内成员数量ZREM key member1 [member2..]
移除有序集合中一个/多个成员ZREMRANGEBYLEX key min max
移除有序集合中给定的字典区间的所有成员ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员ZREMRANGEBYSCORE key min max
移除有序集合中给定的分数区间的所有成员ZREVRANGE key start end
返回有序集中指定区间内的成员,通过索引,分数从高到底ZREVRANGEBYSCORRE key max min
返回有序集中指定分数区间内的成员,分数从高到低排序ZREVRANGEBYLEX key max min
返回有序集中指定字典区间内的成员,按字典顺序倒序ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序ZINTERSTORE destination numkeys key1 [key2 ..]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的scoreZUNIONSTORE destination numkeys key1 [key2..]
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中ZSCAN key cursor [MATCH pattern\] [COUNT count]
迭代有序集合中的元素(包括元素成员和元素分值)
代码示例
-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 # 向有序集合myzset中添加成员m1 score=1 以及成员m2 score=2..
(integer) 2
127.0.0.1:6379> ZCARD myzset # 获取有序集合的成员数
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2
----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 将成员m2的score +5
"7"
127.0.0.1:6379> ZSCORE myzset m1 # 获取成员m1的score
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"
--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 获取成员m1的索引,索引按照score排序,score相同索引值按字典顺序顺序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 获取索引在 0~1的成员
"m1"
"m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 获取全部成员
"m1"
"m3"
"m2"
testset=>{abc,add,amaze,apple,back,java,redis} score均为0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成员
"abc"
"add"
"amaze"
"apple"
"back"
"java"
"redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分页 按索引显示查询结果的 0,1,2条记录
"abc"
"add"
"amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录
"apple"
"back"
"java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
"abc"
"add"
"amaze"
"apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
"apple"
"back"
"java"
-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
"m1"
"m3"
"m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
"m1"
"m3"
--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3
------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成员abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成员
(integer) 2
testset=> {abc,add,apple,amaze,back,java,redis} score均为0
myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score递减排序,然后按索引,返回结果的 0~3
"m9"
"m7"
"m4"
"m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序结果的 索引的2~4
"m4"
"m3"
"m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score递减顺序 返回集合中分数在[2,6]之间的成员
"m4"
"m3"
"m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
"java"
"back"
"apple"
"amaze"
-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score递减顺序,返回成员m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4
mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小红、小刚的数学成绩
enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小红、小刚的英语成绩
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 将mathscore enscore进行合并 结果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合并后的score是之前集合中所有score的和
"xm"
"160"
"xg"
"177"
"xh"
"188"
127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取两个集合的成员score最小值作为结果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
"xm"
"70"
"xg"
"87"
"xh"
"93"
应用案例:
Redis的有序集合(Zset) 是一个超级实用的数据结构,结合了 Set的去重能力 和 按分数(Score)排序 的特性,非常适合需要 排行榜、优先级队列、时间线 等场景的业务。以下是它的典型应用案例和实际公司业务场景:
📌 典型应用案例
- 实时排行榜(Top N)
- 场景:游戏积分榜、电商销量榜、热搜榜。比如微博热搜榜。腾讯游戏战力等级排行榜,实时更新
- 实现:用分数(Score)存储数值(如积分、销量),自动排序。
- 命令示例:
ZADD leaderboard 100 "玩家A" 200 "玩家B" # 添加分数和成员 ZREVRANGE leaderboard 0 9 # 获取TOP 10
-
延迟队列/任务调度
- 场景:订单超时取消、定时任务。比如电商平台订单超时未支付自动取消,外卖平台骑手接单超时重新派单
- 实现:用时间戳作为分数,定时扫描到期任务。
ZADD delay_queue <到期时间戳> "任务ID" ZRANGEBYSCORE delay_queue 0 <当前时间戳> # 获取到期任务
-
时间轴(Timeline)
- 场景:社交媒体的用户动态、新闻feed流。
- 实现:用发布时间戳作为分数,按时间倒序展示。
ZADD user:1:feed <时间戳> "动态ID" ZREVRANGE user:1:feed 0 10 # 获取最新10条
-
带权重的去重集合
- 场景:广告竞价(按出价排序)、优先客服分配(VIP客户优先)。
- 实现:用权重值(如出价金额)作为分数。
三种特殊数据类型
geospatial地理位置
地理位置查询
http://www.redis.cn/commands/geoadd.html
Hyperloglog(交集并集)
本质:概率算法,用于估算海量数据的 唯一值数量(如UV统计)。
用途:统计网站日活用户(允许少量误差,但内存仅需 12KB,百万级UV误差率<1%)。
可以用于求共同好友(交集)
Bitmaps(范围查询)
二进制位存储 存储键值 0或1,例如是否签到
http://www.redis.cn/topics/data-types-intro.html#bitmaps
Geospatial(地理位置)
基于Zset实现的经纬度存储
Stream(消息流)
本质:Redis 5.0 引入的持久化消息队列(类似Kafka)
6. 事务
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
Redis单条命令式保存原子性的,但是事务不保证原子性!
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会执行!
-
Redis事务没有隔离级别的概念
redis事务操作: -
开启事务(multi)
-
命令入队(……)
-
取消事务(discard)
-
执行事务(exec)
监控
悲观锁
查看修改等都会添加排它锁
效率低
乐观锁
乐观,不会加锁。更新数据的时候会判断一下,在此期间是否有人修改过这个数据
获取version
更新的时候比较version
watch(监视)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get money
"85"
127.0.0.1:6379> get out
"20"
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> incrby money 5
(integer) 85
该watch是乐观锁,当第一个线程中的事务执行时,判断是否有其他线程对money进行操作,如果有,那么当前事务执行失败
unwatch解锁
Java连接工具
Jedis:早期工具,jedis采用的直连,多个线程操作是不安全的,如果想要避免不安全,需要使用jedis pool 连接池
lettuce:官方推荐,采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况下
10. 持久化操作
RDB
定时快照:在指定时间间隔内,将内存中的数据集 全量快照(Snapshot)保存到磁盘(默认文件 dump.rdb)。
触发方式:
手动触发:执行 SAVE(阻塞主线程)或 BGSAVE(后台异步保存)。
自动触发:在 redis.conf 中配置 save ,例如:
save 900 1 # 900秒内至少1个key被修改则触发
save 300 10 # 300秒内至少10个key被修改则触发
save 60 10000 # 60秒内至少10000个key被修改则触发
AOF
日志记录:将所有 写操作命令 追加到文件(默认 appendonly.aof),重启时重新执行这些命令恢复数据。
同步策略(appendfsync 配置):
always:每个命令都同步到磁盘(最安全,但性能最差)。
everysec(默认):每秒同步一次(平衡性能与安全)。
no:由操作系统决定何时同步(最快,但可能丢失数据)。
如果同时开启两种持久化方式
- 当redis重启时,会优先载入aof文件来恢复原始数据,因为在通常情况下,aof文件保存的数据集要比rdb文件保存的数据集完整
- rdb更适合用于备份数据库,(aof日志文件不断变化不好备份),
11. Redis发布订阅
菜鸟教程;https://www.runoob.com/redis/redis-pub-sub.html
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
- 开启本地 Redis 服务,开启两个 redis-cli 客户端。
- 在第一个 redis-cli 客户端输入 SUBSCRIBE runoobChat,意思是订阅
runoobChat
频道。 - 在第二个 redis-cli 客户端输入 PUBLISH runoobChat “Redis PUBLISH test” 往 runoobChat 频道发送消息,这个时候在第一个 redis-cli 客户端就会看到由第二个 redis-cli 客户端发送的测试消息。
序号 | 命令及描述 |
---|---|
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 …]] 指退订给定的频道。 |
应用场景
- 实时消息系统
- 实时聊天
12. 主从复制
80%情况下都是在进行读操作。
将一台redis服务器(主机)的数据复制到其他redis服务器(从机)上,也就是读写分离,
配置一个redis集群至少需要有三台服务器
面临问题
-
机器故障。我们部署到一台 Redis 服务器,当发生机器故障时,需要迁移到另外一台服务器并且要保证数据是同步的。而数据是最重要的,如果你不在乎,基本上也就不会使用 Redis 了。
-
容量瓶颈。当我们有需求需要扩容 Redis 内存时,从 16G 的内存升到 64G,单机肯定是满足不了。当然,你可以重新买个 128G 的新机器。
解决办法
要实现分布式数据库的更大的存储容量和承受高并发访问量,我们会将原来集中式数据库的数据分别存储到其他多个网络节点上。
主从复制的作用
\1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
\2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
\3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
\4. 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
\5. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
配置
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 连接的从机数量
master_replid:27369f8e13738be7ad0affaf9ab68094f5d9202f
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
复制三个配置文件,然后修改配置信息
- 端口号
- pid名字
- log文件名字
- dump.rdb文件名字
127.0.0.1:6380> slaveof 127.0.0.1 6379 #认定6379端口为主机
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
如果master主机设置了auth密码,那么需要在slave从机中设置masterauth masterPassword
这样配置成功后,只能主机写入数据,并且自动复制到从机上
如果是使用命令行来配制的主从关系,那么重启后,就会变成主机。不过,只要变成从机,立刻就会从主机中复制数据。
复制原理
Redis 的主从复制(Replication)并不是直接复制 AOF 文件到从机,而是通过更高效的方式同步数据。以下是详细流程和核心机制:
一、Redis 主从复制的核心原理
- 全量同步(首次连接或故障恢复)
- 触发条件:从节点首次连接主节点,或主从复制中断时间过长(超过
repl-backlog
缓冲区保留的数据)。 - 流程:
- 主节点生成当前数据的 RDB 快照(通过
BGSAVE
后台进程),同时缓存期间的写命令到repl-backlog
缓冲区。 - RDB 文件传输:主节点将 RDB 文件发送给从节点。
- 从节点加载 RDB:从节点清空自身数据,加载接收到的 RDB 文件。
- 同步缓冲区的增量命令:从节点加载完 RDB 后,主节点将
repl-backlog
中缓存的写命令发送给从节点执行,最终达到数据一致。
- 主节点生成当前数据的 RDB 快照(通过
- 关键点:
- 使用 RDB 而非 AOF:RDB 是二进制压缩格式,比 AOF 文件更小、传输更快。
- 主节点无需依赖 AOF:即使主节点未开启 AOF,仍可通过 RDB 完成全量同步。
- 增量同步(正常运行时)
- 触发条件:主从连接短暂中断后恢复,且
repl-backlog
缓冲区未溢出。 - 流程:
- 从节点通过
PSYNC
命令发送自身复制的偏移量(repl_offset
)给主节点。 - 主节点检查
repl-backlog
缓冲区,将缺失的增量命令发送给从节点。
- 从节点通过
- 优势:避免全量同步的开销,仅传输差异数据。
二、为什么不用 AOF 文件复制?
-
效率问题:
- AOF 文件通常比 RDB 文件大(尤其是未重写时),传输和加载更耗时。
- RDB 是紧凑的二进制格式,更适合全量数据迁移。
-
设计目标:
- 主从复制的核心目标是 快速同步数据,而非持久化。RDB 在内存快照生成和传输上更高效。
-
混合持久化场景:
- 即使主节点开启了混合持久化(AOF 含 RDB 前缀),主从复制仍优先使用纯 RDB 格式同步,而非直接复制 AOF 文件。
三、主从复制与持久化的关系
对比项 | 主从复制 | 持久化(AOF/RDB) |
---|---|---|
目的 | 数据实时同步到从节点 | 数据持久化到磁盘,防止重启丢失 |
数据格式 | 全量同步用 RDB,增量同步用命令传播 | AOF(文本命令)或 RDB(二进制快照) |
触发条件 | 从节点连接主节点或断线重连 | 定时(RDB)或持续追加(AOF) |
是否依赖磁盘 | 否(直接内存到内存传输) | 是(需写磁盘文件) |
四、常见误区澄清
-
误区:主从复制依赖主节点的 AOF 文件。
事实:主从复制完全独立于 AOF 机制,即使主节点关闭 AOF,复制仍可正常工作。 -
误区:从节点通过读取主节点的 AOF 文件恢复数据。
事实:从节点通过主节点发送的 RDB 快照 + 增量命令 同步数据,与 AOF 无关。 -
误区:主从复制会导致主节点频繁生成 RDB 文件。
事实:全量同步仅在从节点初次连接或断线太久时触发,平时通过增量命令同步。
五、配置建议
-
优化全量同步:
- 调大
repl-backlog-size
(默认 1MB),减少全量同步概率。 - 避免主节点频繁重启,防止从节点全量同步。
- 调大
-
监控复制状态:
- 使用
INFO replication
查看slave_repl_offset
和master_repl_offset
,确认主从数据是否一致。 - 监控
latest_fork_usec
(RDB 生成耗时),避免主节点因 fork 阻塞。
- 使用
-
高可用扩展:
- 主从复制 + Sentinel(哨兵)实现自动故障转移。
- 使用 Redis Cluster 分片分散数据压力。
总结
Redis 主从复制的本质是 内存数据的实时同步,通过 RDB 快照 + 增量命令传播实现高效复制,而非依赖 AOF 文件。这种设计在性能、可靠性和灵活性上取得了平衡,适用于高可用和读写分离场景。
层层链路
从机依旧可以拥有另外的从机
master宕机后,slave变成主机
使用命令SLAVEOF no one
让自己变成主机,其他节点手动连接最新的主机。如果这个时候原来的主机修复了,不会改变现有模式。
哨兵模式
学习官网:http://www.redis.cn/topics/sentinel.html
概述
- 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。哨兵通过发送命令,等待redis服务器响应
- 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
哨兵是一个独立的进程,独立运行。
单机哨兵
作用:
当检测到master宕机后,会自动将slave转换为master,然后通过发布订阅模式通知其他从服务器,修改配置文件,让他们切换依赖的主机
多机哨兵
多个哨兵存在时,如果一个哨兵发现了master出现问题(主观下线),并不会立刻切换主机,而是等其他哨兵也发现master出问题(客观下线)后,才会投票选出新的主机
配置哨兵配置文件sentinel.conf
# sentinel monitor 被监视的名称(自己任意起)host port 1
sentinel monitor myredis 127.0.0.1 6379 1
# 1 表示主机宕机后,slave投票选出新的主机
启动哨兵
redis-sentinel kconfig/sentinel.conf
当主机修复后,只能当新的主机下的从机。
优点
- 哨兵集群,基于主从复制模式,所有的主从配置优点,他全有
- 主从可以切换,故障可以转移,系统的可用性更好
缺点 - Redis不容易在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
- 实现哨兵模式的配置很麻烦,配置文件需要很多配置,具体官网查看。
13. Redis缓存穿透和雪崩
缓存处理流程
穿透概念
数据查不到
查询一个不存在的数据(如恶意请求 id=-1),导致每次请求都绕过缓存直接查数据库,造成数据库压力激增。
当用户量非常大,并且缓存都没有命中,于是都去请求持久层数据库。这会给持久层数据库造成很大压力,就相当于出现了缓存穿透。
解决方案
1)布隆过滤器
概念:本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行效验,如果请求不符合就被丢弃,从而避免了对底层存储系统的压力。
参考知乎:https://zhuanlan.zhihu.com/p/43263751
2)缓存空对象
当查询底层数据库后,即使查询为空也会被保存在缓存中,同时设置过期时间。
这种方法有两个问题:
- 如果空值被缓存起来,意味着需要更多空间来存储更多键,
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对需要保持一致性的业务有影响。
3)参数校验
在业务层拦截非法请求
缓存击穿(量大,缓存过期)
概念
指一个key是一个热点,在不停的扛着大并发,大并发集中对着一个点进行访问,当这个key在实效的瞬间,持续的大并发就穿破缓存,直接请求数据库,如同凿开一个洞。
解决方法
1. 设置热点数据永不过期
2. 加互斥锁
分布式锁:使用分布式锁,保证对于每一个key,同一时刻只能有一个线程去查询后端服务,其他线程没有获取的分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到分布式锁,因此对分布式锁的考验很大。
缓存雪崩
概念:某一个时间段,缓存集中失效,或redis宕机。
其实集中过期,倒不是非常致命,比较致命的是缓存服务器某个结点宕机或断网。
自然形成的缓存雪崩一定是在某个时间段集中创建缓存,这个时候,数据库是可以顶住压力的。
解决方式:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 缓存永不过期 + 后台异步更新
- 多级缓存:本地缓存 + redis缓存
1. 设置热点数据永不过期
2. 加互斥锁
分布式锁:使用分布式锁,保证对于每一个key,同一时刻只能有一个线程去查询后端服务,其他线程没有获取的分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到分布式锁,因此对分布式锁的考验很大。