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

相关文章

Pandas使用SQLite3实战

《Pandas使用SQLite3实战》本文主要介绍了Pandas使用SQLite3实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1 环境准备2 从 SQLite3VlfrWQzgt 读取数据到 DataFrame基础用法:读

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Redis 中的热点键和数据倾斜示例详解

《Redis中的热点键和数据倾斜示例详解》热点键是指在Redis中被频繁访问的特定键,这些键由于其高访问频率,可能导致Redis服务器的性能问题,尤其是在高并发场景下,本文给大家介绍Redis中的热... 目录Redis 中的热点键和数据倾斜热点键(Hot Key)定义特点应对策略示例数据倾斜(Data S

redis+lua实现分布式限流的示例

《redis+lua实现分布式限流的示例》本文主要介绍了redis+lua实现分布式限流的示例,可以实现复杂的限流逻辑,如滑动窗口限流,并且避免了多步操作导致的并发问题,具有一定的参考价值,感兴趣的可... 目录为什么使用Redis+Lua实现分布式限流使用ZSET也可以实现限流,为什么选择lua的方式实现

Redis中管道操作pipeline的实现

《Redis中管道操作pipeline的实现》RedisPipeline是一种优化客户端与服务器通信的技术,通过批量发送和接收命令减少网络往返次数,提高命令执行效率,本文就来介绍一下Redis中管道操... 目录什么是pipeline场景一:我要向Redis新增大批量的数据分批处理事务( MULTI/EXE

Python实战之屏幕录制功能的实现

《Python实战之屏幕录制功能的实现》屏幕录制,即屏幕捕获,是指将计算机屏幕上的活动记录下来,生成视频文件,本文主要为大家介绍了如何使用Python实现这一功能,希望对大家有所帮助... 目录屏幕录制原理图像捕获音频捕获编码压缩输出保存完整的屏幕录制工具高级功能实时预览增加水印多平台支持屏幕录制原理屏幕

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Redis中的常用的五种数据类型详解

《Redis中的常用的五种数据类型详解》:本文主要介绍Redis中的常用的五种数据类型详解,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Redis常用的五种数据类型一、字符串(String)简介常用命令应用场景二、哈希(Hash)简介常用命令应用场景三、列表(L

Redis解决缓存击穿问题的两种方法

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re... 目录引言解决办法互斥锁(强一致,性能差)逻辑过期(高可用,性能优)设计逻辑过期时间引言缓存击穿:给

Redis中如何实现商品秒杀

《Redis中如何实现商品秒杀》:本文主要介绍Redis中如何实现商品秒杀问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录技术栈功能实现步骤步骤一:准备商品库存数据步骤二:实现商品秒杀步骤三:优化Redis性能技术讲解Redis的List类型Redis的Set