操作系统缓存与缓冲

缓存与缓冲

缓冲区是一块临时存储数据的区域,这些数据后面会被传输到其他设备上。缓冲区更像消息队列,用以弥补高速设备和低速设备通信时的速度差,平衡读写速度。例如:IO中内核缓冲区Ring Buffer。

缓存:存在于速度相差较大的两种硬件之间,提高低速设备的访问速度,或者减少复杂耗时的计算带来的性能问题。缓存不一定在内存中,硬盘也可以!!比如从磁盘随机读取数据慢,从数据库查询数据慢。

缓存分类

常见的缓存主要就是静态缓存、分布式缓存和热点本地缓存这三种。

  • 静态缓存:对静态数据做缓存。

  • 分布式缓存:何针对动态请求做缓存,例如Redis。

  • 热点本地缓存主要部署在应用服务器的代码中,用于阻挡热点查询对于分布式缓存节点或者数据库的压力。

缓存的读写/更新策略?

Cache Aside(旁路缓存)策略

读策略的步骤是:

  • 从缓存中读取数据;
  • 如果缓存命中,则直接返回数据;
  • 如果缓存不命中,则从数据库中查询数据;
  • 查询到数据后,将数据写入到缓存中,并且返回给用户。

写策略的步骤是:

  • 更新数据库中的记录;
  • 删除缓存记录。

采取删除缓存的策略,不采用更新缓存的策略;以及先删除缓存再更新数据库都会产生数据不一致的问题。

Cache Aside 存在的最大的问题是当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。如果你的业务对缓存命中率有严格的要求,那么可以考虑两种解决方案:

  • 一种做法是在更新数据时也更新缓存,只是在更新缓存前先加一个分布式锁,因为这样在同一时间只允许一个线程更新缓存,就不会产生并发问题了。当然这么做对于写入的性能会有一些影响;

  • 另一种做法同样也是在更新数据时更新缓存,只是给缓存加一个较短的过期时间,这样即使出现缓存不一致的情况,缓存的数据也会很快地过期,对业务的影响也是可以接受。

应用:

如果我们的业务对缓存命中率有很高的要求,可以采用「更新数据库 + 更新缓存」的方案,因为更新缓存并不会出现缓存未命中的情况

但是在两个更新请求并发执行的时候,会出现数据不一致的问题。

解决办法:

  • 在更新缓存前先加个分布式锁,保证同一时间只运行一个请求更新缓存,就会不会产生并发问题了,当然引入了锁后,对于写入的性能就会带来影响。
  • 在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期,对业务还是能接受的。
redis延迟双删是什么?

针对「先删除缓存,再更新数据库」方案在「读 + 写」并发请求而造成缓存不一致的解决办法是「延迟双删」。

shell 复制代码
#删除缓存
redis.delKey(X)
#更新数据库
db.update(X)
#睡眠
Thread.sleep(N)
#再删除缓存
redis.delKey(X)

怎么保证「先更新数据库 ,再删除缓存」这两个操作能执行成功?

  • 重试机制。

    • 引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

      • 如果应用删除缓存失败 ,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。

      • 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。

  • 订阅 MySQL binlog,再操作缓存。

    • 先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。

    • 可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。

      • Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。

Read/Write Through(读穿 / 写穿)策略

  • Write Through 的策略是这样的:先查询要写入的数据在缓存中是否已经存在,如果已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,如果缓存中数据不存在,我们把这种情况叫做"Write Miss(写失效)"。不写入缓存中,而是直接更新到数据库中。

Write Back(写回)策略

写回的应用:操作系统层面的 Page Cache,Mysql日志的异步刷盘,亦或是消息队列中消息的异步写入磁盘,大多采用了这种策略。

缺点:因为缓存一般使用内存,而内存是非持久化的,所以一旦缓存机器掉电,就会造成原本缓存中的脏块儿数据丢失。所以你会发现系统在掉电之后,之前写入的文件会有部分丢失,就是因为 Page Cache 还没有来得及刷盘造成的。

主要区别:对于脏数据的落盘发生在命中或者挑选cache块。

应用
mysql中redo log binlog 的刷盘:凡是跟Page Cache打交道的操作
  • redo log的刷盘时机

    • MySQL 正常关闭时;
    • 当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘;
    • InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘。
    • 每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘

    innodb_flush_log_at_trx_commit 参数:

    • 当设置该参数为 0 时,表示每次事务提交时 ,还是将 redo log 留在 redo log buffer 中 ,该模式下在事务提交时不会主动触发写入磁盘的操作。

      操作系统把缓存在 redo log buffer 中的 redo log ,通过调用 write() 写到操作系统的 Page Cache,然后调用 fsync() 持久化到磁盘。所以参数为 0 的策略,MySQL 进程的崩溃会导致上一秒钟所有事务数据的丢失;

    • 当设置该参数为 1 时,表示每次事务提交时,都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘,这样可以保证 MySQL 异常重启之后数据不会丢失。

    • 当设置该参数为 2 时,表示每次事务提交时,都只是缓存在 redo log buffer 里的 redo log 写到 redo log 文件,注意写入到「 redo log 文件」并不意味着写入到了磁盘,因为操作系统的文件系统中有个 Page Cache,Page Cache 是专门用来缓存文件数据的,所以写入「 redo log文件」意味着写入到了操作系统的文件缓存。

      调用 fsync,将缓存在操作系统中 Page Cache 里的 redo log 持久化到磁盘。所以参数为 2 的策略,较取值为 0 情况下更安全,因为 MySQL 进程的崩溃并不会丢失数据,只有在操作系统崩溃或者系统断电的情况下,上一秒钟所有事务数据才可能丢失

binlog cache的刷盘时机?
  • sync_binlog = 0 的时候,表示每次提交事务都只 write,不 fsync,后续交由操作系统决定何时将数据持久化到磁盘;
  • sync_binlog = 1 的时候,表示每次提交事务都会 write,然后马上执行 fsync;
  • sync_binlog =N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。

redo log buffer 和 binlog cache都和page cache打交道,所以缓存都是写回策略。

redis 持久化刷盘

如果想要应用程序向文件写入数据后,能立马将数据同步到硬盘,就可以调用 fsync() 函数,这样内核就会将内核缓冲区的数据直接写入到硬盘,等到硬盘写操作完成后,该函数才会返回。

  • Always 策略就是每次写入 AOF 文件数据后,就执行 fsync() 函数;
  • Everysec 策略就会创建一个异步任务来执行 fsync() 函数;
  • No 策略就是永不执行 fsync() 函数

其实也是page cache。

kafka中数据持久化用到了page cache页缓存,同时实现了顺序读写

kafka持久化所用到的零拷贝+页缓存:

  • 零拷贝:Kafka的数据加工处理操作交由Kafka生产者和Kafka消费者处理。Kafka Broker应用层不关心存储的数据,所以就不用走应用层,传输效率高。
  • PageCache:Kafka重度依赖底层操作系统提供的PageCache功 能。当上层有写操作时,操作系统只是将数据写入PageCache。当读操作发生时,先从PageCache中查找,如果找不到,再去磁盘中读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。

参考:

小林coding

极客时间操作系统40讲

相关推荐
岁月变迁呀10 小时前
Redis梳理
数据库·redis·缓存
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭10 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
Code apprenticeship12 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
fpcc14 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
Ewen Seong14 小时前
mysql系列5—Innodb的缓存
数据库·mysql·缓存
安全二次方security²14 小时前
SMMU软件指南SMMU编程之虚拟机结构和缓存
缓存·cache·smmu·arm安全架构·系统mmu·虚拟机结构·vms
.生产的驴17 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
kong79069281 天前
电商项目-网站首页高可用(二)
缓存·二级缓存·网站首页高可用
一个懒鬼1 天前
Windows脚本清理C盘缓存
windows·缓存
科马1 天前
【Redis】缓存
数据库·redis·spring·缓存