「Redis系列」- Redis进阶

370 阅读8分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

前言

本文主要介绍Redis的使用场景、事务机制和持久化机制

往期推荐:

「Redis 系列」- Redis入门


一. Redis 常见的使用场景

1.1 用于缓存热点数据

缓存是Redis 使用最频繁的场景,由于Redis读写性能的优异,慢慢成为服务端缓存组件的首选

缓存的处理逻辑:

简单来说,就是读取数据的时候,先读Redis,若是Redis不存在数据,则读数据库,并将数据放入缓存中;但不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况


1.2 用于限时业务

由于Redis可以为Key设置过期时间,所以很多有时限的东西都可以用Redis来实现

比如经典的短信验证码,用户提交验证码后,查询key是否已经过期了,过期了这个验证码就算失效了


1.3 作为业务的计数器

Redes的单线程模型保证了在Redis中操作数据是线程安全的,还有命令可以实现原子性的递增和递减

可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等

再比如也可以给文章统计访问量、点赞数量


1.4 分布式锁

主要利用Redis 的 setnx 命令实现,如果不存在则成功设置缓存同时返回1,否则返回0,在Java中,一般用 Redission 来实现分布式锁

分布式锁常用于秒杀等场景


1.5 点赞,收藏,好友、关注等相互关系的存储

Redis 利用集合的一些命令,比如求交集、并集、差集等

在应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能


二. Redis中的事务

2.1 事务相关的命令

MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:

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

MULTI:开启一个事务,返回OK,此时用户可以按顺序输入命令,这些命令会被放入一个队列,而不会立即执行

EXEC:提交事务队列,事务中的命令会被按顺序执行

DISCARD:放弃事务

WATCH:使用乐观锁监控key,在同一个客户端中,假如一个key在事务开启前被WATCH标记,事务开启后如果这个值被其他客户端修改,事务将执行失败

例:

> MULTI
OK
> SET NUMBER 1  -- 设置一个Key初始值是1
QUEUED
> INCR NUMBER   -- 自增这个key
QUEUED
> EXEC          -- 执行事务
OK
2

2.2 为什么Redis中的事务不支持回滚?

Redis官方认为,在使用Redis的过程中,Redis只会因为错误的语法而失败,或是命令用在了错误类型的键上面如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的;因此,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中

此外,因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速


三. Redis的持久化机制

3.1 为什么需要持久化

Redis 是一个基于内存的数据库,假如服务器宕机了,内存中的数据将全部丢失。假如这些缓存由系统直接从数据库重新拉取恢复,不仅会给数据库带来大量的压力,而且会造成系统响应缓慢。所以,对于Redis来说,实现数据的持久化从而避免给数据库加压


3.2 RDB

RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值

RDB允许用户手动触发,也允许配置为自动触发

save:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用

bgsave:Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短

> save
OK
> bgsave
Background saving started

3.2.1 RDB持久化的配置

在生产环境,不可能由开发人员时不时的上线操作备份,所以会设置成周期执行

可以通过配置redis.conf来开启RDB

# 周期性执行条件的设置格式为
save <seconds> <changes>

# 例子
save 900 1      #如果900秒内有1条Key信息发生变化,则进行快照,可以根据实际压力调整

# 以下设置方式为关闭RDB快照功能
save ""

# ------- 分割线---------
#其他配置
# 文件名称
dbfilename dump.rdb

# 文件保存路径
dir /data/redis/data/

# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes

# 是否压缩
rdbcompression yes

# 导入时是否检查
rdbchecksum yes

3.2.2 RDB优缺点

优点

  • RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
  • Redis加载RDB文件恢复数据要远远快于AOF方式;

缺点

  • RDB方式实时性不够,无法做到秒级的持久化;
  • 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
  • RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;
  • 版本兼容RDB文件问题;

针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决


3.3 AOF 持久化

AOF(Append Only File)是指将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的

3.3.1 AOF持久化配置

默认情况下,Redis是没有开启AOF的,可以通过配置redis.conf文件来开启AOF持久化

# appendonly参数开启AOF持久化
appendonly yes

# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"

# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./

# 同步策略
appendfsync always

3.3.2 回写策略

对于 appendfsync,Redis提供了三个参数:

  1. appendfsync always 每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
  2. appendfsync everysec 每秒钟同步一次,显示地将多个写命令同步到硬盘
  3. appendfsync no 让操作系统决定何时进行同步

为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度


3.3.3 AOF重写

随着AOF记录的命令越来越多,文件也会越来越大,为了解决这个问题,Redis通过重写来为AOF文件“减重”

Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令

重写执行的过程

  1. AOF重写过程是由后台进程BGREWRITEAOF 来完成的,主线程fork出后台的BGREWRITEAOF 子进程,fork会把主线程的内存拷贝一份给BGREWRITEAOF 子进程
  2. 在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令
  3. 当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致
  4. 服务器用新的 AOF 文件替换旧的 AOF 文件,AOF重写完成

Reference

pdai:Redis知识体系详解

JavaGuide:Redis基础知识