note-Redis实战3 核心-数据安全与性能保障

2024-08-31 05:52

本文主要是介绍note-Redis实战3 核心-数据安全与性能保障,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

助记提要
  1. 快照持久化的作用和缺点
  2. Redis创建快照的时机
  3. AOF文件同步的三种配置
  4. AOF文件重写的方式
  5. Redis复制的配置项和控制命令
  6. Redis复制过程 5步
  7. Redis主从链
  8. 确认数据写入从服务器硬盘
  9. 故障处理的两步
  10. Redis事务命令 5个
  11. Redis事务的特点 3点
  12. 非事务型流水线
  13. 使用性能测试工具评估客户端的性能

4章 数据安全与性能保障

持久化和复制 故障恢复 事务和流水线

4.1 快照持久化

快照持久化是将某一时刻存储在内存里的所有数据写入硬盘。

快照持久化适合丢失数据不影响运行的程序,或者丢失的数据容易恢复的情况。

Redis的持久化配置项
# 快照持久化配置
save 60 1000
stop-writes-on-bgsave-error no
rdbcompression yes
dbfilename dump.rdb# AOF持久化配置
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb# 共享配置(快照文件和AOF文件的保存位置)
dir ./
快照的作用
  1. 进行数据备份
  2. 创建具有相同数据的副本
快照持久化的缺点

在新的快照文件创建完毕之前,如果系统发生崩溃会丢失最近一次创建快照之后更改的所有数据。
在Redis数据量大的时候,快照持久化需要的时间和内存占用会变大,可能造成性能问题或丢失数据。

Redis创建快照的时机
  1. 客户端向Redis发送BGSAVE命令。Redis会创建子进程将快照写入硬盘,主进程继续响应命令。
  2. 客户端向Redis发送SAVE命令。Redis主进程暂停响应命令,创建快照。
  3. 配置中设置save选项。save 60 10000配置表示从Redis创建上一个快照开始,60秒内有10000次写入的话就自动触发BGSAVE命令。设置多个save配置时,只要满足其中一个,就会创建快照。
  4. Redis收到SHUTDOWN命令或收到标准TREM信号时,会执行SAVE命令,完毕后关闭服务器。
  5. Redis从服务器向Redis主服务器发送SYNC命令来执行一次复制操作时,如果主服务器最近没有执行过BGSAVE命令,那么主服务器就会执行一次BGSAVE命令。

SAVE命令不常用。一般只会在内存不够执行BGSAVE命令,或者允许等待持久化操作完毕的情况下才会使用。

快照的使用实例
  1. 个人开发
    为了降低快照持久化的资源消耗,设置save 900 1
    Redis在上次成功生成快照的15分钟内有写操作,就开始BGSAVE操作。

  2. 日志聚合计算
    需要考虑可承受丢失多长时间内产生的新数据,和如何恢复中断的日志处理操作。

def process_logs(conn, path, callback):""":param conn: Redis连接;:param path: 日志文件路径;:param callback: 具体处理日志的回调函数;"""# 取当前的处理进度。current_file, offset = conn.mget('progress:file', 'progress:position')# 使用事务流水线pipe = conn.pipeline()def update_progress():# 更新当前处理的文件和位置pipe.mset({'progress:file': fname, 'progress:position': offset})pipe.execute()for fname in sorted(os.listdir(path)):if fname < current_file:continueinp = open(os.path.join(path, fname), 'rb')# 对于之前未完全处理的文件,忽略已处理部分if fname == current_file:inp.seek(int(offset, 10))else:offset = 0for lno, line in enumerate(inp):# 按行处理日志callback(pipe, line)offset += int(offset) + len(line)# 处理完1000行或一个完整文件的时候更新进度if not (lno + 1) % 1000:update_progress()update_progress()inp.close()
  1. 大数据
    Redis中的数据量达到数十个GB,且系统剩余内存不足时,执行BGSAVE可能导致长时间的停顿。
Redis所在系统Redis每占用1GB内存,BGSAVE创建子进程耗费的时间
真实硬件、VMWare、KVM虚拟机10~20ms
Xen云虚拟机200~300ms

为了避免Redis由于创建子进程出现停顿,可以手动发送BGSAVE或者SAVE来持久化。
手动发送BGSAVE也会停顿,但是可以控制停顿出现的时间。
SAVE虽然会阻塞Redis,但是它不需要创建子进程,且创建快照的速度比BGSAVE快。

4.2 只追加文件AOF

执行写命令时将被执行的写命令写到AOF文件末尾,记录数据发生的变化。
只要Redis从头到尾执行一次AOF文件包含的所有写命令,就能恢复AOF文件记录的数据集。

AOF文件同步

文件同步:
往硬盘写文件时,写入的内容会先存到缓冲区,再由操作系统在某个时候把缓冲区的内容写入硬盘。用户可以让操作系统将文件同步到硬盘,同步操作会一直阻塞直到指定的文件被写入硬盘为止。

不同appendfsync参数配置的同步频率

  • always
    每个Redis写命令时都写入硬盘。这样会使系统发生故障时造成的数据丢失捡到最少。但是会使Redis处理命令的速度受限于硬盘性能。

固态硬盘使用“appendfsync always”配置时,每次只写入一条命令,这种做法会引发严重的“写入放大”问题,降低固态硬盘的寿命。

  • everysec
    每秒执行一次同步,显式地把多个写命令同步到硬盘。
    这个配置下Redis的性能几乎不受影响。即使系统崩溃,也只会损失1秒内的数据。

  • no
    Redis不对AOF文件执行任何显式地同步操作,而是由操作系统决定何时对AOF文件进行同步。
    不影响Redis性能,但是系统崩溃时会丢失不定数量的数据。
    同时,如果硬盘的写入速度不够快,在缓冲区被等待写入硬盘的数据填满时,Redis写入操作会被阻塞,使Redis处理命令的速度变慢。

AOF文件重写

Redis不断运行,AOF文件体积会越来越大。
Redis重启后需要重新执行AOF文件记录的写命令来还原数据集,如果AOF文件很大,还原操作的时间会很长。

  • 重写命令BGREWRITEAOF
    使用BGREWRITEAOF命令会让Redis通过移除AOF文件中的冗余命令来重写AOF文件,使其体积变小。Redis会创建一个子进程来进行重写操作。创建子进程也会导致性能和内存占用问题。而且,删除过大的AOF文件可能导致系统挂起数秒。

  • 重写配置项
    auto-aof-rewrite-percentage,AOF文件体积超出上一次重写时的体积的百分比;
    auto-aof-rewrite-min-size,AOF体积大于改配置设定;
    同时满足上述两个配置的条件时,Redis自动执行BGREWRITEAOF操作。

4.3 复制

复制指让其他的服务器拥有一个不断更新的数据副本,拥有副本的服务器可以用来处理客户端的读请求。
在扩展平台以适应更高负载时,经常需要复制。

通常会使用一个主服务器向多个从服务器发送更新。从服务器接收到主服务器的数据初始副本后,客户端每次向主服务器写入时,从服务器都会实时更新。客户端就能向任何一个从服务器发送读请求了。

复制配置
  1. 主服务器的配置中需要设置dirdbfilename两个选项。
  2. 启动服务器时,指定一个包含slaveof host port选项的配置文件,Redis就会根据这个选项连接主服务器。

使用配置创建从服务器,从服务器启动时会先载入当下可用的任何快照或AOF文件,然后连接主服务器开启复制。

用户可以发送“SLAVEOF no one”的命令是服务器停止复制操作,且不再接受主服务器的更新。
用户能通过发送“SLAVEOF host port”命令让服务器开始复制一个新的主服务器。Redis会立即尝试连接主服务器,连接成功后开始复制。

Redis复制的过程
从服务器
主服务器
1. 连接主服务器,发送SYNC命令
2. 根据配置决定是继续使用现有数据响应,还是向客户端返回错误
3. 丢弃旧数据,接收主服务器的文件
4. 解释完快照文件,照常接受命令请求
5. 执行主服务器发来的缓冲区命令。之后接收并执行主服务器发来的所有命令
1. 等待...
2. 开启BGSAVE,缓冲区记录BGSAVE之后执行的所有写命令
3. BGSAVE完毕,向从服务器发送快照,期间的写命令继续记在缓冲区
4. 文件发完,向从服务器发送缓冲区中的写命令
5. 缓冲区的写命令发完,之后每执行一个写命令就像从服务器发送

初始连接主服务器时,从服务器上的原有数据会全部丢失。

  • 复制对主服务器的内存要求
    Redis在复制期间会尽可能地处理接收到的命令请求。此时如果主从服务器网络带宽不够,或是主服务器内存不足够创建子进程和记录命令的缓冲区,Redis的效率就会受到影响。
    因此最好让主服务器只使用50%-65%的内存,剩下的用于执行BGSAVE和创建缓冲区。

  • Redis不支持主主复制和多主复制
    互相设置为主服务器的两个Redis实例只会持续占用大量处理器资源并不断尝试与对方通信。客户端连接不同的服务器会得到不一致的数据或得不到数据。

  • 从服务器连接主服务器的时机
    多个从服务器同时连接同一个主服务器时,同步占用的带宽可能会使命令请求难以传给主服务器。
    在复制过程中有新的从服务器连接时,如果此时BGSAVE正在执行或已经执行完毕,主服务器会先和之前连接的从服务器完成复制的5步,然后和新连接的从服务器再执行一遍复制过程。

主从链

Redis的从服务器也可以拥有下一级的从服务器,形成主从链。
在读请求很多,需要更多从服务器来处理时,主从链可以避免多个从服务器同时从一个主服务器复制时造成的网络拥堵。

从服务器在与主服务器执行复制时,将断开和下一级从服务器的连接,导致下一级从服务器需要重连并重新同步。

检验硬盘写入

数据同步到多台从服务器上后,后续的读操作才能取到正确的数据。
为了确保这一点,除了在各个从服务器上配置appendonly yesappendfsync everysec选项外,最好在写操作后,主动确认数据确实写到了从服务器的硬盘上。

  1. 确认数据已发给从服务器
    用户可在主服务器写入完后,再往主服务器写入一个唯一的虚构值,然后检查该值是否存在于从服务器上。
  2. 确认数据已存在从服务器硬盘
    对于每秒同步一次AOF的Redis服务器来说,用户可以等待1秒来确保对数据的改动已保存到硬盘。
    更快的做法是,检查INFO命令的“aof_pending_bio_fsync”属性的值是否为0,为0则表示服务器已经把已知的所有数据都存到硬盘了。

以下函数可以在向主服务器写入数据后,检查数据是否存入从服务器硬盘。

def wait_for_sync(mconn, sconn):# 主服务器添加令牌identifier = str(uuid.uuid4())mconn.zadd('sync:wait', identifier, time.time())# 确认从服务器已连接主服务器,必要的话可以等待同步完成while not sconn.info()['master_link_status'] != 'up':time.sleep(0.001)# 检查从服务器是否有更新数据while not sconn.zscore('sync:wait', identifier):time.sleep(0.001)# 最多等待1秒deadline = time.time() + 1.01while time.time() < deadline:# 检查缓冲区的数据是否写入到硬盘if sconn.info()['aof_pending_bio_fsync'] == 0:breaktime.sleep(0.001)# 清理刚刚创建的令牌和之前遗留的令牌mconn.zrem('sync:wait', identifier)mconn.zremrangebyscore('sync:wait', 0, time.time()-900)

INFO命令提供了大量与Redis服务器当前状态有关的信息。如内存占用、客户端连接数、每个数据库包含的键数、上一次快照后执行的写次数等。

4.4 处理系统故障

验证快照文件和AOF文件

通过命令行程序检查AOF文件和快照文件的状态

# 检查AOF文件
redis-check-aof [--fix] <file.aof>
# 检查快照文件
redis-check-dump <dump.rdb>

redis-check-aof命令的–fix参数可以对AOF文件修复。它扫描AOF文件,找到不正确的命令后,会删除错误命令及其之后的所有命令。一般删除AOF文件末尾的不完整的命令。
快照文件出错时无法修复的,因此最好为它保留多个备份,并在数据恢复时,运计算快照的SHA1散列值和SHA256散列值验证内容。

Redis会在快照文件中包含快照文件自身的CRC64校验和。CRC校验可以发现网络传输错误和硬盘损坏。用户翻转文件中任意数量的二进制位,然后通过翻转最后64个二进制位的一个子集来产生与源文件相同的CRC64校验和。

更换故障主服务器
  • 新服务器做主服务器
    向从服务器发送一个SAVE命令,让它创建快照文件。然后把快照文件发送给新的服务器,在新服务器上启动Redis,让原先的从服务器成为新服务器的从服务器。

  • 新服务器做从服务器
    原先的从服务器升级为主服务器,然后为它创建从服务器。

4.5 Redis事务

Redis事务命令

MULTI,开始事务;
EXEC,结束事务。该命令调用之后,才会执行从MULTI开始输入的各个命令。
WATCH,对某个键进行监视,直到执行EXEC命令之前,如果有其他客户端抢先对被监视的键进行替换、更新或删除操作,执行EXEC命令时,事务会失败并返回错误。
UNWATCH,可以在WATCH命令之后、MULTI命令之前对连接进行重置。
DSICARD,可以在MULTI执行之后、EXEC执行之前对连接进行重置。

Redis事务特点
  • 事务延迟执行
    Redis执行事务时,会延迟执行已入队的命令,直到客户端发送EXEC命令为止。
    这种一次性发送多个命令,然后等待所有回复出现的做法通常称为流水线。
    可以减少客户端与Redis的网络通信次数,提升Redis的性能。

  • 无法以一致的形式读取数据
    由于Redis事务在EXEC执行前不会执行任何操作,因此用户无法根据读取到的数据来做决定。
    多个事务同时处理一个对象时通常需要用到二阶提交,事务不能以一致的形式读取数据,所以二阶提交无法实现。

  • Redis只有乐观锁没有悲观锁
    对数据加锁后,访问该数据的请求会被阻塞,直到事务完成。缺点是持有锁的客户端越慢,阻塞时间就越长。这种操作叫悲观锁。
    Redis为了减少等待时间,不会在WATCH的同时对数据加锁,只会在数据被修改的情况下,通知执行了WATCH命令的客户端。这种方式叫乐观锁。此时只需要事务失败后重试就行。

实现在市场里购买一件商品

需求:卖家可以把自己商品指定价格放到市场上;买家购买时,卖家会收到钱。

  • 数据结构
    | 说明 | 数据结构 | 名称 | 内容 |
    | ---- | ---- | ---- | ---- |
    | 用户信息 | 散列 | users:用户编号 | name, 用户名
    funds, 用户钱数 |
    | 用户包裹 | 集合 | inventory:用户编号 | 商品编号 |
    | 市场 | 有序集合 | market: | 成员为“商品编号.用户编号”,值为商品价格 |

将商品放在市场销售

def list_item(conn, itemid, sellerid, price):inventory = "inventory:%s" % selleriditem = "%s.%s" % (itemid, sellerid)end = time.time() + 5pipe = conn.pipeline()while time.time() < end:try:# 监视用户包裹的变化pipe.watch(inventory)# 检查用户是否仍然持有将售的商品if not pipe.sismember(inventory, itemid):pipe.unwacth()return None# 把将售商品加到市场pipe.multi()pipe.zadd("market:", item, price)pipe.srem(inventory, itemid)pipe.execute()# execute执行成功,对包裹的监视结束return Trueexcept redis.exceptions.WatchError:# 用户包裹发生变化,重试passreturn False

购买商品

def purchase_item(conn, buyerid, itemid, sellerid, lprice):buyer = "user:%s" % buyeridseller = "user:%s" % selleridinventory = "inventory:%s" % selleriditem = "%s.%s" % (itemid, sellerid)end = time.time() + 10pipe = conn.pipeline()while time.time() < end:try:# 监控市场和买家pipe.watch("market:", buyer)price = pipe.zscore("market:", item)funds = int(pipe.hget(buyer, "funds"))# 检查买家想买的商品价格是否变化,买家是否钱够if price != lprice or price > funds:pipe.unwatch()return None# 买家给钱拿商品pipe.multi()pipe.hincrby(seller, "funds", int(price))pipe.hincrby(buyer, "funds", int(-price))pipe.sadd(inventory, itemid)pipe.zrem("market:", item)pipe.execute()return Trueexcept redis.exceptions.WatchError:passreturn False

4.6 非事务型流水线

使用事务的一个好处是可以通过流水线来提高事务执行时的性能。
在需要执行大量命令的情况下,为了一次性发送所有命令来减少通信次数,可以在不使用MULTI和EXEC的情况下,使用流水线。

使用MULTI和EXEC也会消耗资源,且可能导致其他命令被延迟执行。

# 默认使用MULTI和EXEC
pipe = conn.pipeline()# 一次性发送执行的命令,但不使用MULTI和EXEC
pipe = conn.pipeline(False)

修改之前的创建令牌的函数,将标准的Redis连接换成流水线连接

def update_token_pipeline(conn, token, user, item=None):timestamp = time.time()# 设置流水线pipe = conn.pipeline()pipe.hset('login:', token, user)pipe.zadd('recent:', token, timestamp)if item:pipe.zadd('viewed:' + token, item, timestamp)pipe.zremrangebyrank('viewed:' + token, 0, -26)pipe.zincrby('viewed:', item, -1)# 执行被流水线包裹的命令pipe.execute()

4.7 Redis性能

要优化Redis的性能,需要先了解各个Redis命令能跑多快。

Redis的性能测试工具redis-benchmark

可以通过调用Redis附带的性能测试程序redis-benchmark来看。redis-benchmark可以展示一些常用命令在1秒内内能够执行的次数。默认情况下,它会使用50个客户端进行测试,但是为了和自己的客户端做对比,一般会用-c选项指定它只使用一个客户端。

redis-benchmark不会处理执行命令得到的回复,所以节约了对回复进行语法分析的时间。通常情况下,对于只使用单客户端的redis-benchmark来说,不使用流水线的python客户单的性能大概只有redis-benckmark展示的50%-60%。

性能问题的处理

如果自己的客户端性能只有redis-benchmark展示的25%-30%,或者客户端返回错误“Cannot assign requested address”,可能是每次发送命令时都创建了新的连接,也可能是以不正确的方式使用Redis的数据结构。

大部分Redis客户端都提供了内置的连接池。python的Redis客户端,对于每个Redis服务器,用户只需要创建一个redis.Redis()对象,它就会按需创建连接、重用已有连接并关闭超时的连接。

这篇关于note-Redis实战3 核心-数据安全与性能保障的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123069

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

黑神话,XSKY 星飞全闪单卷性能突破310万

当下,云计算仍然是企业主要的基础架构,随着关键业务的逐步虚拟化和云化,对于块存储的性能要求也日益提高。企业对于低延迟、高稳定性的存储解决方案的需求日益迫切。为了满足这些日益增长的 IO 密集型应用场景,众多云服务提供商正在不断推陈出新,推出具有更低时延和更高 IOPS 性能的云硬盘产品。 8 月 22 日 2024 DTCC 大会上(第十五届中国数据库技术大会),XSKY星辰天合正式公布了基于星

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动