缓存第一篇

336 阅读4分钟

缓存

为什么使用缓存

我们如果要优化某个接口的延迟,脑海中第一个想到的办法就是加缓存。缓存通过减少数据传输时间,减少了单个请求的响应延迟,提高了单位时间内能处理的请求数量。假设单线程进程中,某个接口的延迟为 10 ms,那么 TPS 为 100 次/秒。其中,读数据库花费了 3 ms。如果我们采用 Redis 缓存数据,将读取时间降到 1 ms,那么接口延迟为 8ms,TPS 为 125 次/秒。通过使用缓存,接口的吞吐量提高了 25%。

为什么缓存能减少数据传输时间呢?我们常用的缓存有两种:进程内缓存(例如 Map)和进程外缓存(例如 Redis)。

  • 当使用进程内缓存时,时间消耗在内存 IO;

  • 当使用进程外缓存时,时间消耗在网络 IO + 内存 IO;

  • 当使用数据库时,如果数据不在内存中,还得去磁盘加载。内存 IO、网络IO 和 磁盘 IO 的延迟为:

操作延迟
主存访问100 ns
同一个数据中心往返500,000 ns
磁盘寻址2,000,000 ns (2 ms)

从传输速度来说,内存 IO > 网络 IO + 内存 IO > 磁盘 IO,即进程内缓存 > 进程外缓存 > 数据库。

缓存的类型

有了缓存,读请求一般是这样执行的:

  • 先查看缓存中是否有数据
  • 如果有,则直接返回数据
  • 否则,从后端数据库读取数据,并把数据写到缓存中。

根据缓存中的数据是否接受写请求,我们可以把缓存分为只读缓存读写缓存

只读缓存

只读缓存:所有的写请求,包括增、删、改,直接发往后端的数据库,同时删除缓存中的数据。

  • 优点
    • 最新的数据都在数据库中
    • 数据已持久化,不会丢失
  • 缺点
    • 一个数据修改后就被读取,会产生缓存缺失,需要先去后端数据库读取数据,并写到缓冲中。也就是说,只读缓存不适合读写频繁的数据

读写缓存

读写缓存:除了读请求,写请求也会发送到缓存中,直接修改缓存中的数据。

  • 优点
    • 写入速度非常快
  • 缺点
    • 最新数据在缓存中,一旦出现掉电或宕机,内存中的数据就会丢失。

最新新数据在缓存中,需要将其同步到数据库中,有两种写回策略:同步直写和异步写回。

  • 同步直写:写请求同时发给缓存和数据库。只要两者都返回成功,才给客户端返回成功。
    • 优点
      • 即使缓存宕机,数据库中仍有最新的数据,保障了数据的可靠性。
    • 缺点
      • 增加了响应延迟。需要等到写入数据库成功,才能给客户端返回成功。
      • 多线程中,容易引起缓存和数据库数据不一致的情况,并且保证一致性需要花费很大的代价。
  • 异步写回:写请求只需要发给缓存。在数据被淘汰时,将最新数据写到数据中。
    • 优点
      • 写入速度非常快。
    • 缺点
      • 宕机时很可能丢失很久之前的数据。一种折中的方法是,定期将脏数据写到数据库,即使宕机,也只是丢失一部分数据(取决于写入间隔)。

同步直写优先考虑数据的可靠性,异步写回优先考虑写入性能。

适用场景

实际中,我们可以根据数据的特点,选择不同的缓存类型:

  • 如果你的数据读多写少,那么选择只读缓存更合适。例如,某个活动的排行榜数据、直播列表。
  • 如果你的数据读写频繁,或者对响应延迟要求高,那么选择读写缓存更合适。
    • 如果对数据的可靠性要求高,写回策略可以采用同步直写。例如,玩家的金币数据。
    • 如果对数据的读写性能要求高,写回策略可以采用异步写回。例如,某个活动的数据。