Redis介绍
- 特点及优点
1、开源的,使用C编写,基于内存且支持持久化
2、支持数据类型丰富,字符串strings,散列hashes,列表lists,集合sets,有序集合sorted sets 等
3、支持多种编程语言(C C++ Python Java PHP ... )
4、单进程单线程
- Redis亮点功能
1、持久化
将内存中数据保存到磁盘中,保证数据安全,方便进行数据备份和恢复
2、过期键功能
为键设置一个过期时间,让它在指定时间内自动删除
<节省内存空间>
# 音乐播放器,日播放排名,过期自动删除
3、事务功能
弱事务型的数据库,只是具备简单的事务功能
4、主从复制
如何让redis保持高可用状态,官方提供主从搭建方案
5、Sentinel哨兵
在搭配了基础的主从结构后,哨兵可做到自动故障
终端中执行 测试命令 感受 飞一样的速度
redis-benchmark -q -n 10000与其他数据库对比
1、MySQL : 关系型数据库,表格,基于磁盘,慢
2、MongoDB:键值对文档型数据库,值为类似JSON文档,数据结构相对单一
3、Redis的诞生是为了解决什么问题??
# 解决硬盘IO带来的性能瓶颈
- 应用场景
1、缓存
做为缓存存储介质,此业务场景最为常见;查询优先走redis,没有则走mysql;可有效的降低mysql查询压力
2、并发计数
由于redis本身是单进程单线程的,可以有效解决并发请求计数场景;例如微博点赞
3、排行榜
各大实时排行榜 - 如电商/游戏中的排行
4、生产者消费者模型
从当生产者消费者模型的中间层;生产者可将任务分发给redis,消费者从redis中获取任务
- redis版本
1、最新版本:5.0
2、常用版本:2.4、2.6、2.8、3.0(里程碑)、3.2、3.4、4.0(教学环境版本)、5.0
- Redis安装&链接
# 安装
sudo apt-get install redis-server
# 服务端启动
sudo /etc/init.d/redis-server status | start | stop | restart
# 客户端连接
redis-cli -h IP地址 -p 6379 -a 密码
redis配置及基础命令
配置文件详解
- 配置文件所在路径
配置文件所在路径:/etc/redis/redis.conf
mysql的配置文件在哪里?# /etc/mysql/mysql.conf.d/mysqld.cnf
修改配置文件前 先备份一下
sudo cp /etc/redis/redis.conf /etc/redis/redis_bak.conf
sudo chown redis:redis /etc/redis/redis_bak.conf
- 设置连接密码
1、requirepass 密码
2、重启服务
sudo /etc/init.d/redis-server restart
3、客户端连接
redis-cli -h 127.0.0.1 -p 6379 -a 123456
127.0.0.1:6379>ping
重启redis 失败 , 大概率为配置文件配置有误导致
解决方案1: 终端中 直接执行 redis-server /etc/redis/redis.conf 启动服务,若有报错,按照报错提示修改redis.conf
解决方案2:还原备份后的配置文件
sudo mv /etc/redis/redis.conf /etc/redis/redis_error.conf
sudo mv /etc/redis/redis_bak.conf /etc/redis/redis.conf允许远程连接
1、注释掉本地IP地址绑定
69行: # bind 127.0.0.1 ::1
2、关闭保护模式(把yes改为no)
88行: protected-mode no
3、重启服务
sudo /etc/init.d/redis-server restart
基础命令
- 通用命令 适用于所有数据类型
select number
说明:切换数据库(默认redis有16个数据库,0-15为具体数据库的编号,默认进入redis为db0)
info
说明:查看redis服务的整体情况
keys 表达式
说明:查找所有符合给定模式 的 key
样例:
KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS h*llo 匹配 hllo 和 heeeeello 等
特殊说明:正式环境中,请勿使用此命令;由于redis单进程单线程,当key很多时,当前命令可能阻塞redis
type key
说明:返回当前键的数据类型
exists key
说明:返回当前键是否存在
返回值:1 代表当前key存在; 0 代表当前key不存在
del key
说明:删除key
rename key newkey
说明:重命名当前key的名字
flushdb
说明:清除当前所在数据库数据
flushall
说明:清除所有数据库数据
数据类型
字符串类型(string)
- 特点
1、字符串、数字,都会转为字符串来存储
2、以二进制的方式存储在内存中
- 注意:
key命名规范
可采用 - wang:email
key命名原则
1、key值不宜过长,消耗内存,且在数据中查找这类键值的计算成本
2、不宜过短,可读性较差
3、一个字符串类型的值最多能存储512M内容字符串常用命令-必须掌握
set key value nx ex
说明:设置一个字符串的key
特殊参数:
nx -> not exist 代表当key不存在时,才存储这个key
ex -> expire 过期时间,单位s
get key
说明:获取key的值
返回值:key的值 或者 'nil'
strlen key
说明:获取key存储值的长度
getrange key start stop
说明:获取指定范围切片内容 【包含start stop】
setrange key index value
说明:从索引值开始,用value替换原内容;返回最新长度
mset key1 value1 key2 value2 key3 value3
说明:批量添加key和value
mget key1 key2 key3
说明:批量获取key的值
incrby key 步长 将key增加指定步长
decrby key 步长 将key减少指定步长
incr key : +1操作
decr key : -1操作
incrbyfloat key step
- 应用场景
缓存
说明:将mysql中的数据存储到redis字符串类型中
并发计数 - 点赞/秒杀
说明:通过redis单进程单线程的特点,由redis负责计数,并发问题转为串行问题
带有效期的验证码
说明:借助过期时间,存放验证码;到期后,自动消亡
过期时间
默认情况下,key没有过期时间,需要手动指定
方案1:直接用set的ex参数
set key value ex 3
方案2:使用expire通用命令
1、set key value
2、expire key 5 # 秒
3、pexpire key 5 # 毫秒
查看过期时间 ttl key - 通用命令
返回值:
-1 : 代表当前key 没有过期时间
>0 : 代表当前key的剩余存活时间
-2 : 代表当前key不存在
删除过期时间 persist key
说明:把带有过期时间的key 变为 永久不过期
返回值:1代表删除过期时间成功 / 0代表当前key没过期时间 or key不存在
Redis删除过期key机制
- 删除机制
每个redis数据库中,都会有一个特殊的容器负责存储带有过期时间的key以及它对应的过期时间, 这个容器称之为 “过期字典”
针对过期字典中的key, redis 结合 惰性删除 和 定期删除 两大机制,有效删除过期数据
- 删除机制 - 惰性删除
当调用key时,检查是否过期,如果过期则删除
- 删除机制 - 定期删除
主动定期扫描过期字典中的数据,检查是否过期
- 最大内存检查
最后一道保险 - maxmemory 配置选项
一旦内存量超过 最大限制,redis会在执行命令时触发 内存淘汰
(需手动在redis配置文件中激活 maxmemory配置项)
主流淘汰机制如下:
volatile-lru:从已设置过期时间的内存数据集中挑选最近最少使用的数据 淘汰;
volatile-ttl: 从已设置过期时间的内存数据集中挑选即将过期的数据 淘汰;
volatile-random:从已设置过期时间的内存数据集中任意挑选数据 淘汰;
allkeys-lru:从内存数据集中挑选最近最少使用的数据 淘汰;
allkeys-random:从数据集中任意挑选数据 淘汰;
no-enviction:禁入大多写命令列表数据类型(List)
- 基础概念
1、元素是字符串类型
2、列表头尾增删快,中间增删慢,增删元素是常态
3、元素可重复
4、最多可包含2^32 -1个元素
5、索引同python列表
- 列表常用命令
增加数据
1、从列表头部压入元素
LPUSH key value1 value2
返回:list长度
2、从列表尾部压入元素
RPUSH key value1 value2
返回:list长度
3、从列表src尾部弹出1个元素,压入到列表dst的头部
RPOPLPUSH src dst
返回:被弹出的元素
4、在列表指定元素后/前插入元素
LINSERT key after|before value newvalue
返回:
1、如果命令执行成功,返回列表的长度
2、如果没有找到 pivot ,返回 -1
3、如果 key 不存在或为空列表,返回 0
# 查看
5、查看列表中元素
LRANGE key start stop
# 查看列表中所有元素: LRANGE key 0 -1
6、获取列表长度
LLEN key
# 删
7、从列表头部弹出1个元素
LPOP key
8、从列表尾部弹出1个元素
RPOP key
9、列表头部,阻塞弹出,列表为空时阻塞
BLPOP key timeout
10、列表尾部,阻塞弹出,列表为空时阻塞
BRPOP key timeout
# 关于BLPOP 和 BRPOP
1、如果弹出的列表不存在或者为空,就会阻塞
2、超时时间设置为0,就是永久阻塞,直到有数据可以弹出
3、如果多个客户端阻塞再同一个列表上,使用First In First Service原则,先到先服务
11、删除指定元素
LREM key count value
count>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count
count<0:表示从尾部开始向表头搜索,移除与value相等的元素,数量为count
count=0:移除表中所有与value相等的值
返回:被移除元素的数量
12、保留指定范围内的元素
LTRIM key start stop
返回:ok
样例:
LTRIM mylist1 0 2 # 只保留前3条
# 应用场景: 保存微博评论最后500条
LTRIM weibo:comments 0 499
# 改
13、将列表 key 下标为 index 的元素的值设置为 value
LSET key index newvalue应用场景: 1、存储微博评论,做切割,只保留最新的 xx 个 2、生产者消费者模型,做中间层,存放生产者的任务 python交互redis
除用redis客户端进行redis操作外,也可以使用Python直接操作redis
检查当前Ubuntu是否安装
sudo pip3 freeze|grep -i 'redis'
安装Python的redis模块
sudo pip3 install redis
- 使用流程
import redis
# 1. 创建数据库连接对
r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='123456')
# 2. 连接对象.redis命令 即可
例如: r.set(key, value)
说明:大部分命令跟redis终端中使用雷同
位图操作bitmap
定义
1、位图不是真正的数据类型,它是定义在字符串类型中 2、一个字符串类型的值最多能存储512M字节的内容,位上限:2^32# 1MB = 1024KB
# 1KB = 1024Byte(字节)
# 1Byte = 8bit(位)
常用命令
SETBIT 命令
-
说明:设置某位置上的二进制值
-
语法:
SETBIT key offset value
-
参数:offset - 偏移量 从0开始
value - 0或者1
-
示例:
# 默认扩展位以0填充
127.0.0.1:6379> SET mykey ab
OK
127.0.0.1:6379> GET mykey
"ab"
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> GET mykey
"\xe1b"
127.0.0.1:6379>
SETBIT 命令 两种情况
1)key 不存在时: 初始化最小字节数的bit位,默认补0
2)key 存在时:
1满足长度要求,直接修改当前bit位
2在原有value上初始化最小字节数的bit位
GETBIT 命令
- 说明:获取某一位上的值
- 语法:
GETBIT key offset
- 示例:
127.0.0.1:6379> GETBIT mykey 3
(integer) 0
127.0.0.1:6379> GETBIT mykey 0
(integer) 1
127.0.0.1:6379>
BITCOUNT 命令
- 说明:统计键所对应的值中有多少个 1
- 语法:
BITCOUNT key start end
- 参数:start/end 代表的是 字节索引
- 示例:
127.0.0.1:6379> SET mykey1 ab
OK
127.0.0.1:6379[4]> BITCOUNT mykey
(integer) 6
127.0.0.1:6379[4]> BITCOUNT mykey 0 0
(integer) 3
应用场景案例
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,
诸如此类,以此作为数据,从而决定让哪些用户参加重要活动 —— 这个模式可以使用 SETBIT 和 BITCOUNT 来实现。
比如说,每当用户在某一天上线的时候,我们就使用 SETBIT ,以用户名作为 key ,将那天所代表的网站的上线日作
为 offset 参数,并将这个 offset 上的为设置为 1 。
举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令
SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,得出的结果就是
peter 上线的总天数。
Python中使用位试图
Py中操纵位图跟终端中执行完全一样
1、生成连接对象r
2、调用r.setbit / getbit / bitcount
数据类型-Hash散列
- 定义
1、由field和关联的value组成的键值对
2、field和value是字符串类型
3、一个hash中最多包含2^32-1个键值对
- 优点
- 缺点(不适合hash情况)
1,使用过期键功能:键过期功能只能对键进行过期操作,而不能对散列的字段进行过期操作
2,存储消耗大于字符串结构
- 基本命令操作
# 1、设置单个字段
HSET key field value
HSETNX key field value
# 2、设置多个字段
HMSET key field value field value
# 3、返回字段个数
HLEN key
# 4、判断字段是否存在(不存在返回0)
HEXISTS key field
# 5、返回字段值
HGET key field
# 6、返回多个字段值
HMGET key field filed
# 7、返回所有的键值对
HGETALL key
# 8、返回所有字段名
HKEYS key
# 9、返回所有值
HVALS key
# 10、删除指定字段
HDEL key field
# 11、在字段对应值上进行整数增量运算
HINCRBY key filed increment
# 12、在字段对应值上进行浮点数增量运算
HINCRBYFLOAT key field increment
python操作hash
# 1、更新一条数据的属性,没有则新建
hset(name, key, value)
# 2、读取这条数据的指定属性, 返回字符串类型
hget(name, key)
# 3、批量更新数据(没有则新建)属性,参数为字典
hmset(name, mapping)
# 4、批量读取数据(没有则新建)属性
hmget(name, keys)
# 5、获取这条数据的所有属性和对应的值,返回字典类型
hgetall(name)
# 6、获取这条数据的所有属性名,返回列表类型
hkeys(name)
# 7、删除这条数据的指定属性
hdel(name, *keys)
应用场景:用户维度数据统计
用户维度统计
原理:基于 hash 压缩特点,和字段可计数
统计数包括:关注数、粉丝数、喜欢商品数、发帖数
用户为key,不同维度为field,value为统计数
比如关注了5人
HSET user:10000 fans 5
HINCRBY user:10000 fans 1
应用场景: 缓存 - redis+mysql+hash组合使用
-
原理
用户想要查询个人信息
示例:
1、到redis缓存中查询个人信息
2、redis中查询不到,到mysql查询,并缓存到redis
3、再次查询个人信息
哈希结构是如何实现的?
集合数据类型(set)
- 特点
1、无序、去重
2、元素是字符串类型
3、最多包含2^32-1个元素
类似于Python中所学集合
- 基本命令
# 1、增加一个或者多个元素,自动去重;返回值为成功插入到集合的元素个数
SADD key member1 member2
# 2、查看集合中所有元素
SMEMBERS key
# 3、删除一个或者多个元素,元素不存在自动忽略
SREM key member1 member2
# 4、元素是否存在
SISMEMBER key member
# 5、随机返回集合中指定个数的元素,默认为1个
SRANDMEMBER key [count]
# 6、弹出成员
SPOP key [count]
# 7、返回集合中元素的个数,不会遍历整个集合,只是存储在键当中了
SCARD key
# 8、把元素从源集合移动到目标集合
SMOVE source destination member
# 9、差集(number1 1 2 3 number2 1 2 4 结果为3)
SDIFF key1 key2
# 10、差集保存到另一个集合中
SDIFFSTORE destination key1 key2
# 11、交集
SINTER key1 key2
SINTERSTORE destination key1 key2
# 12、并集
SUNION key1 key2
SUNIONSTORE destination key1 key2
应用场景
社交类平台,共同好友 - 交集
纯随机类抽奖
防止元素重复
黑/白名单
思考:如何做一个每位学生都是随机考题的考试系统
有序集合sortedset
- 特点
1、有序、去重
2、元素是字符串类型
3、每个元素都关联着一个浮点数分值(score),并按照分值从小到大的顺序排列集合中的元素(分值可以相同)
4、最多包含2^32-1元素
- 示例
一个保存了员工薪水的有序集合
分值 | 6000 | 8000 | 10000 | 12000 | |
---|---|---|---|---|---|
元素 | lucy | tom | jim | jack |
一个保存了正在阅读某些技术书的人数
分值 | 300 | 400 | 555 | 666 | 777 |
---|---|---|---|---|---|
元素 | 核心编程 | 阿凡提 | 本拉登 | 阿姆斯特朗 | 比尔盖茨 |
- 有序集合常用命令
# 1、有序集合中添加一个成员 返回值为 成功插入到集合中的元素个数
zadd key score member
# 2、查看指定区间元素(升序)
zrange key start stop [withscores]
# 3、查看指定区间元素(降序)
zrevrange key start stop [withscores]
# 4、查看指定元素的分值
zscore key member
# 5、返回指定区间元素
zrangebyscore key min max [withscores] [limit offset count]
参数说明:
min/max : 最小值/最大值区间,默认闭区间(大于等于或小于等于); (min ,可开启开区间即(大于或小于)
offset : 跳过多少个元素
count : 返回几个
limit 选项跟mysql 一样
# 6、删除成员
zrem key member
# 7、增加或者减少分值
zincrby key increment member
# 8、返回元素排名
zrank key member
# 9、返回元素逆序排名
zrevrank key member
# 10、删除指定区间内的元素 (默认闭区间,可做开区间)
zremrangebyscore key min max
# 11、返回集合中元素个数
zcard key
# 12、返回指定范围中元素的个数 (默认闭区间,可做开区间)
zcount key min max
# 13、并集
zunionstore destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]
# zunionstore salary3 2 salary salary2 weights 1 0.5 AGGREGATE MAX
# 2代表集合数量,weights之后 权重1给salary,权重0.5给salary2集合,算完权重之后执行聚合AGGREGATE
# 14、交集:和并集类似,只取相同的元素
zinterstore destination numkeys key1 key2 weights weight AGGREGATE SUM(默认)|MIN|MA
应用场景
各种排行榜
五大数据类型及应用场景
类型 | 特点 | 使用场景 |
---|---|---|
string | 简单key-value类型,value可为字符串和数字 | 常规计数(微博数, 粉丝数等功能) |
hash | 是一个string类型的field和value的映射表,hash特别适合用于存储对象 | 存储部分可能需要变更的数据(比如用户信息) |
list | 有序可重复列表 | 消息队列等 |
set | 无序不可重复列表 | 存储并计算关系(如微博,关注人或粉丝存放在集合,可通过交集、并集、差集等操作实现如共同关注、共同喜好等功能) |
sorted set | 每个元素带有分值的集合 | 各种排行榜 |
事务
基础概念
场景:小明账户100元, 小红账户50元
需求:小明转账50元给小红
数据库语句: <1,小明账户-50元> <2,小红账户+50元>
问题:如果执行1句时,数据库进程死掉了...
事务是逻辑上对数据的的一组操作, 这组操作要么一次全部成功,或者这组操作全部失败;是不可分割的一个工作单位
事务四大特性(ACID):
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
1、原子性(Atomicity)
事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败
2、一致性(Consistency)
是事务对数据完整性约束的遵循。这些约束可能包括主键约束、外键约束或是一些用户自定义约束。事务执行的前后都是合法的数据状态,不会违背任何的数据完整性
3、隔离性(Isolation)
事务与事务之间互不打扰
4、持久性(Durability)
一个事务一旦成功提交,它对数据库的改变必须是永久的,即便是数据库发生故障也应该不回对其产生任何影响。
Redis事务
redis是弱事务型的数据库,并不具备ACID的全部特性 redis具备隔离性:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
事务命令
1、MULTI # 开启事务 mysql begin2、命令1 # 执行命令
3、命令2 ... ...
4、EXEC # 提交到数据库执行 mysql commit
5、DISCARD # 取消事务 mysql 'rollback'
使用步骤
# 开启事务
127.0.0.1:6379> MULTI
OK
# 命令1入队列
127.0.0.1:6379> INCR n1
QUEUED
# 命令2入队列
127.0.0.1:6379> INCR n2
QUEUED
# 提交到数据库执行
127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 1
事务中命令错误处理
# 1、命令语法错误,命令入队失败,直接自动discard退出这个事务
这个在命令在执行调用之前会发生错误。例如,这个命令可能有语法错误(错误的参数数量,错误的命令名)
处理方案:语法错误则自动执行discard
案例:
127.0.0.1:6379[7]> MULTI
OK
127.0.0.1:6379[7]> get a
QUEUED
127.0.0.1:6379[7]> getsss a
(error) ERR unknown command 'getsss'
127.0.0.1:6379[7]>
127.0.0.1:6379[7]>
127.0.0.1:6379[7]> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
# 2、命令语法没错,但类型操作有误,则事务执行调用之后失败,无法进行事务回滚
我们执行了一个由于错误的value的key操作(例如对着String类型的value施行了List命令操作)
处理方案:发生在EXEC之后的是没有特殊方式去处理的:即使某些命令在事务中失败,其他命令都将会被执行。
案例
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set num 10
QUEUED
127.0.0.1:6379> LPOP num
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get num
"10"
127.0.0.1:6379>
pipeline 流水线
批量执行redis命令,减少通信io
原理:效仿redis的事务,客户端将多个命令打包,一次通信发给redis,可明显降低redis服务的请求
注意:
1,此为客户端技术
2,如果一组命令中,一个命令需要上一个命令的执行结果才可以执行,则无法使用该技术
示例
import redis
# 创建连接池并连接到redis
pool = redis.ConnectionPool(host = '127.0.0.1',db=0,port=6379)
r = redis.Redis(connection_pool=pool)
pipe = r.pipeline()
pipe.set('fans',50)
pipe.incr('fans')
pipe.incrby('fans',100)
pipe.execute()
性能对比
# 创建连接池并连接到redis
pool = redis.ConnectionPool(host = '127.0.0.1',db=0,port=6379)
r = redis.Redis(connection_pool=pool)
def withpipeline(r):
p = r.pipeline()
for i in range(1000):
key = 'test1' + str(i)
value = i+1
p.set(key, value)
p.execute()
def withoutpipeline(r):
for i in range(1000):
key = 'test2' + str(i)
value = i+1
r.set(key, value)python 操作 redis事务
python操作事务,需要依赖 流水线技术
with r.pipeline(transaction=true) as pipe
pipe.multi()
pipe.incr("books")
pipe.incr("books")
values = pipe.execute()
watch - 乐观锁
作用: 事务过程中,可对指定key进行监听,命令提交时,若被监听key对应的值未被修改时,事务方可提交成功,否则失败
> watch books
OK
> multi
OK
> incr books
QUEUED
> exec # 事务执行失败
(nil)
watch之后,再开一个终端进入redis
> incr books # 修改book值
(integer) 1
python操作watch
信用卡提升额度,每次提升额度时,当前余额都变为原来的两倍
数据持久化
基础概念
what - 将数据从掉电易失的内存放到永久存储的设备上why - 因为所有的数据都在内存上,所以必须得持久化
redis提供两种持久化方案
RDB 默认开启
AOF
RDB模式(默认开启)
1、保存真实的数据
2、将服务器包含的所有数据库数据以二进制文件的形式保存到硬盘里面
3、默认文件名 :/var/lib/redis/dump.rdb
文件名及目录可在配置文件中修改 【/etc/redis/redis.conf】
263行: dir /var/lib/redis # 表示rdb文件存放路径
253行: dbfilename dump.rdb # 文件名创建rdb文件的两种方式
方式一:redis终端中使用SAVE或者BGSAVE命令
127.0.0.1:6379> SAVEOK
# 特点
1、执行SAVE命令过程中,redis服务器将被阻塞,无法处理客户端发送的命令请求,在SAVE命令执行完毕后,服务器才会重新开始处理客户端发送的命令请求
2、如果RDB文件已经存在,那么服务器将自动使用新的RDB文件代替旧的RDB文件
# 工作中定时持久化保存一个文件
127.0.0.1:6379> BGSAVE
Background saving started
# 执行过程如下
1、客户端 发送 BGSAVE 给服务器
2、服务器马上返回 Background saving started 给客户端
3、服务器 fork() 子进程做这件事情
4、服务器继续提供服务
5、子进程创建完RDB文件后再告知Redis服务器
# 配置文件相关
/etc/redis/redis.conf
263行: dir /var/lib/redis # 表示rdb文件存放路径
253行: dbfilename dump.rdb # 文件名
# 两个命令比较
SAVE比BGSAVE快,因为需要创建子进程,消耗额外的内存
# 补充:可以通过查看日志文件来查看redis都做了哪些操作
# 日志文件:配置文件中搜索 logfile
logfile /var/log/redis/redis-server.log
方式二:设置配置文件条件满足时自动保存(使用最多)
# redis配置文件默认
218行: save 900 1
219行: save 300 10
表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么自动执行BGSAVE命令
220行: save 60 10000
1、只要三个条件中的任意一个被满足时,服务器就会自动执行BGSAVE
2、每次创建RDB文件之后,服务器为实现自动持久化而设置的时间计数器和次数计数器就会被清零,并重新开始计数,所以多个保存条件的效果不会叠加
# 该配置项也可以在命令行执行 [不推荐]
redis>save 60 10000
Redis关闭
redis在正常关闭时,也会执行保存rdb操作
注意:异常关闭,无法自动触发RDB操作
RDB缺点
1、创建RDB文件需要将服务器所有的数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,
所以服务器需要隔一段时间才创建一个新的RDB文件,也就是说,创建RDB文件不能执行的过于频繁,否则会严重影响服务器的性能
2、可能丢失数据
AOF(AppendOnlyFile)
1、存储的是命令,而不是真实数据
2、默认不开启
# 开启方式(修改配置文件)
1、/etc/redis/redis.conf
672行: appendonly yes # 把 no 改为 yes
676行: appendfilename "appendonly.aof"
2、重启服务
sudo /etc/init.d/redis-server restart
AOF持久化原理及优点
# 原理
1、每当有修改数据库的命令被执行时,
2、因为AOF文件里面存储了服务器执行过的所有数据库修改的命令,所以给定一个AOF文件,服务器只要重新执行
一遍AOF文件里面包含的所有命令,就可以达到还原数据库的目的
# 优点
用户可以根据自己的需要对AOF持久化进行调整,让Redis在遭遇意外停机时不丢失任何数据,或者只丢失一秒钟的
数据,这比RDB持久化丢失的数据要少的多
特殊说明
# 因为
虽然服务器执行一个修改数据库的命令,就会把执行的命令写入到AOF文件,但这并不意味着AOF文件持久化不会丢失任何数据,
在目前常见的操作系统中,执行系统调用write函数,将一些内容写入到某个文件里面时,为了提高效率,系统通常不会直接将
内容写入硬盘里面,而是将内容放入一个内存缓存区(buffer)里面,等到缓冲区被填满时才将存储在缓冲区里面的内容真正写
入到硬盘里
# 所以
1、AOF持久化:当一条命令真正的被写入到硬盘里面时,这条命令才不会因为停机而意外丢失
2、AOF持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间
3、越早将命令写入到硬盘,发生意外停机时丢失的数据就越少,反之亦然
策略 - 配置文件
# 打开配置文件:/etc/redis/redis.conf,找到相关策略如下
1、701行: alwarys
服务器每写入一条命令,就将缓冲区里面的命令写入到硬盘里面,服务器就算意外停机,也不会丢失任何已经成功执行的命令数据
2、702行: everysec(# 默认)
服务器每一秒将缓冲区里面的命令写入到硬盘里面,这种模式下,服务器即使遭遇意外停机,最多只丢失1秒的数据
3、703行: no
服务器不主动将命令写入硬盘,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失命令数量不确定
# 运行速度比较
always:速度慢
everysec和no都很快,默认值为everysec
AOF重写
思考:AOF文件中是否会产生很多的冗余命令?
为了让AOF文件的大小控制在合理范围,避免胡乱增长,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件
-- 新的AOF文件记录的数据库数据和原由的AOF文件记录的数据库数据完全一样
-- 新的AOF文件会使用尽可能少的命令来记录数据库数据,因此新的AOF文件的提及通常会小很多
-- AOF重写期间,服务器不会被阻塞,可以正常处理客户端发送的命令请求
示例
原有AOF文件 | 重写后的AOF文件 |
---|---|
select 0 | SELECT 0 |
sadd myset peiqi | SADD myset peiqi qiaozhi danni lingyang |
sadd myset qiaozhi | SET msg 'hello tarena' |
sadd myset danni | RPUSH mylist 2 3 5 |
sadd myset lingyang | |
INCR number | |
INCR number | |
DEL number |
AOF重写-触发
1、客户端向服务器发送BGREWRITEAOF命令
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
2、修改配置文件让服务器自动执行BGREWRITEAOF命令
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 解释
1、只有当AOF文件的增量大于100%时才进行重写,也就是大一倍的时候才触发
# 第一次重写新增:64M
# 第二次重写新增:128M
# 第三次重写新增:256M(新增128M)
RDB和AOF持久化对比
RDB持久化 | AOF持久化 |
---|---|
全量备份,一次保存整个数据库 | 增量备份,一次保存一个修改数据库的命令 |
保存的间隔较长 | 保存的间隔默认为一秒钟 |
数据还原速度快 | 数据还原速度一般,冗余命令多,还原速度慢 |
执行SAVE命令时会阻塞服务器,但手动或者自动触发的BGSAVE不会阻塞服务器 | 无论是平时还是进行AOF重写时,都不会阻塞服务器 |
用redis用来存储真正数据,每一条都不能丢失,都要用always,有的做缓存,有的保存真数据,我可以开多个redis服务,
不同业务使用不同的持久化,新浪每个服务器上有4个redis服务,整个业务中有上千个redis服务,分不同的业务,每个持久
化的级别都是不一样的。
数据恢复(无需手动操作)
既有dump.rdb,又有appendonly.aof,恢复时找谁?
先找appendonly.aof
配置文件常用配置总结
# 设置密码
1、requirepass password
# 开启远程连接
2、bind 127.0.0.1 ::1 注释掉
3、protected-mode no 把默认的 yes 改为 no
# rdb持久化-默认配置
4、dbfilename 'dump.rdb'
5、dir /var/lib/redis
# rdb持久化-自动触发(条件)
6、save 900 1
7、save 300 10
8、save 60 10000
# aof持久化开启
9、appendonly yes
10、appendfilename 'appendonly.aof'
# aof持久化策略
11、appendfsync always
12、appendfsync everysec # 默认
13、appendfsync no
# aof重写触发
14、auto-aof-rewrite-percentage 100
15、auto-aof-rewrite-min-size 64mb
# 设置为从服务器
16、salveof <master-ip> <master-port>
Redis相关文件存放路径
1、配置文件: /etc/redis/redis.conf
2、备份文件: /var/lib/redis/*.rdb|*.aof
3、日志文件: /var/log/redis/redis-server.log
4、启动文件: /etc/init.d/redis-server
# /etc/下存放配置文件
# /etc/init.d/下存放服务启动文件
Redis主从复制
- 基础知识
高可用 - 是系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。
目标:消除基础架构中的单点故障。
redis单进程单线程模式,如果redis进程挂掉,相关依赖的服务就难以正常服务
redis高可用方案 - 主从搭建 + 哨兵
- 定义
1、一个Redis服务可以有多个该服务的复制品,这个Redis服务成为master,其他复制品成为slaves
2、master会一直将自己的数据更新同步给slaves,保持主从同步
3、只有master可以执行写命令,slave只能执行读命令
- 作用
分担了读的压力(高并发)
- 原理
从服务器执行客户端发送的读命令,比如GET、LRANGE、SMEMMBERS、HGET、ZRANGE等等,客户端可以连接slaves
执行读请求,来降低master的读压力
-
实现方式
-
方式一(Linux命令行实现)
redis-server --slaveof <master-ip> <master-port> --masterauth <master password>
# 从服务端
redis-server --port 6300 --slaveof 127.0.0.1 6379
# 从客户端
redis-cli -p 6300
127.0.0.1:6300> keys *
# 发现是复制了原6379端口的redis中数据
127.0.0.1:6300> set mykey 123
(error) READONLY You can't write against a read only slave.
127.0.0.1:6300>
# 从服务器只能读数据,不能写数据
-
方式二(Redis命令行实现)
# 两条命令
1、>slaveof IP PORT
2、>slaveof no one
# 服务端启动
redis-server --port 6301
# 客户端连接
tarena@tedu:~$ redis-cli -p 6301
127.0.0.1:6301> keys *
1) "myset"
2) "mylist"
127.0.0.1:6301> set mykey 123
OK
# 切换为从
127.0.0.1:6301> slaveof 127.0.0.1 6379
OK
127.0.0.1:6301> set newkey 456
(error) READONLY You can't write against a read only slave.
127.0.0.1:6301> keys *
1) "myset"
2) "mylist"
# 再切换为主
127.0.0.1:6301> slaveof no one
OK
127.0.0.1:6301> set name hello
OK
-
方式三(利用配置文件)
# 每个redis服务,都有1个和他对应的配置文件
# 两个redis服务
1、6379 -> /etc/redis/redis.conf
2、6300 -> /home/tarena/redis_6300.conf
# 修改配置文件
vi redis_6300.conf
slaveof 127.0.0.1 6379
port 6300
# 启动redis服务
redis-server redis_6300.conf
# 客户端连接测试
redis-cli -p 6300
127.0.0.1:6300> hset user:1 username guods
(error) READONLY You can't write against a read only slave
-
问题:
问题:master挂了怎么办?
早期解决方案:运维人员 - 手动挑选从服务 - 手动将从切换成主 - 再将其余的从指向最新的主
缺点:故障时间较长,对运维人员要求较高
能否有自动化的方案解决? - 哨兵
Sentinel哨兵
Redis之哨兵 - sentinel
1、Sentinel会不断检查Master和Slaves是否正常
2、每一个Sentinel可以监控任意多个Master和该Master下的Slaves
原理:正如其名,哨兵进程定期与 redis主从进行通信,当哨兵认为redis主 '阵亡'后【通信无返回】,自动将切换工作完成
案例演示
1、环境搭建
# 共3个redis的服务
1、启动6379的redis服务器
sudo /etc/init.d/redis-server start
2、启动6380的redis服务器,设置为6379的从
redis-server --port 6380
tarena@tedu:~$ redis-cli -p 6380
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
3、启动6381的redis服务器,设置为6379的从
redis-server --port 6381
tarena@tedu:~$ redis-cli -p 6381
127.0.0.1:6381> slaveof 127.0.0.1 6379
2、安装并搭建sentinel哨兵
# 1、安装redis-sentinel
sudo apt install redis-sentinel
验证: sudo /etc/init.d/redis-sentinel stop
# 2、新建配置文件sentinel.conf
port 26379
sentinel monitor tedu 127.0.0.1 6379 1
# 3、启动sentinel
方式一: redis-sentinel sentinel.conf
方式二: redis-server sentinel.conf --sentinel
#4、将master的redis服务终止,查看从是否会提升为主
sudo /etc/init.d/redis-server stop
# 发现提升6381为master,其他两个为从
# 在6381上设置新值,6380查看
127.0.0.1:6381> set name tedu
OK
# 启动6379,观察日志,发现变为了6381的从
主从+哨兵基本就够用了
sentinel.conf解释
# sentinel监听端口,默认是26379,可以修改
port 26379
# 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
sentinel monitor <master-name> <ip> <redis-port> <quorum>
# 如果master有密码,则需要添加该配置
sentinel auth-pass <master-name> <password>
# master多久失联才认为是不可用了,默认是30秒
sentinel down-after-milliseconds <master-name> <milliseconds>
python操作master
from redis.sentinel import Sentinel
#生成哨兵连接
sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
#初始化master连接
master = sentinel.master_for('tedu', socket_timeout=0.1, db=1)
slave = sentinel.slave_for('tedu',socket_timeout=0.1, db=1)
#使用redis相关命令
master.set('mymaster', 'yes')
print(slave.get('mymaster'))