后端存储实战课
李玥
京东零售计算存储平台部资深架构师
立即订阅
3271 人已学习
课程目录
已更新 15 讲 / 共 26 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | 从今天起,换种方式学存储
免费
课前加餐 | 电商系统是如何设计的?
创业篇 (7讲)
01 | 创建和更新订单时,如何保证数据准确无误?
02 | 流量大、数据多的商品详情页系统该如何设计?
03 | 复杂而又重要的购物车系统,应该如何设计?
04 | 事务:账户余额总是对不上账,怎么办?
05 | 分布式事务:如何保证多个系统间的数据是一致的?
06 | 如何用Elasticsearch构建商品搜索系统?
07|MySQL HA:如何将“删库跑路”的损失降到最低?
高速增长篇 (6讲)
08 | 一个几乎每个系统必踩的坑儿:访问数据库超时
09 | 怎么能避免写出慢SQL?
10 | 走进黑盒:SQL是如何在数据库中执行的?
11 | MySQL如何应对高并发(一):使用缓存保护MySQL
12 | MySQL如何应对高并发(二):读写分离
13 | MySQL主从数据库同步是如何实现的?
后端存储实战课
登录|注册

11 | MySQL如何应对高并发(一):使用缓存保护MySQL

李玥 2020-03-21
你好,我是李玥。
通过前面几节课的学习,相信你对 MySQL 这类关系型数据库的能力,已经有了定量的认知。
我们知道,大部分面向公众用户的互联网系统,它的并发请求数量是和在线用户数量正相关的,而 MySQL 能承担的并发读写的量是有上限的,当系统的在线用户超过几万到几十万这个量级的时候,单台 MySQL 就很难应付了。
绝大多数互联网系统,都使用 MySQL 加上 Redis 这对儿经典的组合来解决这个问题。Redis 作为 MySQL 的前置缓存,可以替 MySQL 挡住绝大部分查询请求,很大程度上缓解了 MySQL 并发请求的压力。
Redis 之所以能这么流行,非常重要的一个原因是,它的 API 非常简单,几乎没有太多的学习成本。但是,要想在生产系统中用好 Redis 和 MySQL 这对儿经典组合,并不是一件很简单的事儿。我在《08 | 一个几乎每个系统必踩的坑儿:访问数据库超时》举的社交电商数据库超时故障的案例,其中一个重要的原因就是,对缓存使用不当引发了缓存穿透,最终导致数据库被大量查询请求打死。
今天这节课,我们就来说一下,在电商的交易类系统中,如何正确地使用 Redis 这样的缓存系统,以及如何正确应对使用缓存过程中遇到的一些常见的问题。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《后端存储实战课》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(18)

  • 李玥 置顶
    Hi,我是李玥。

    这里回顾一下上节课的思考题:

    课后请你选一种你熟悉的非关系型数据库,最好是支持 SQL 的,当然,不支持 SQL 有自己的查询语言也可以。比如说 HBase、Redis 或者 MongoDB 等等都可以,尝试分析一下查询的执行过程,对比一下它的执行器和存储引擎与 MySQL 有什么不同。

    谈一下我的理解:

    我们拿一个分布式数据库Hive来看一下它的执行器和存储引擎。严格来说,Hive并不是一个数据库,它只是一个执行器,它的存储引擎就是HDFS加上Map-Reduce。在Hive中,一条SQL的执行过程是和MySQL差不多的,Hive会解析SQL,生成并优化逻辑执行计划,然后它就会把逻辑执行计划交给Map-Reduce去执行了,后续生成并优化物理执行计划,在HDFS上执行查询这些事儿,都是Map-Reduce去干的。顺便说一下,Hive的执行引擎(严格来说是物理执行引擎)是可以替换的,所以就有了Hive on Spark,Hive on Tez这些。
    2020-03-21
    4
  • Geek_3894f9
    数据加版本号,写库时自动增一。更新缓存时,只允许高版本数据覆盖低版本数据。

    作者回复: 👍👍👍

    2020-03-21
    2
    8
  • 公号-云原生程序员
    Cache Aside 在高并发场景下也会出现数据不一致。
    读操作A,没有命中缓存,就会到数据库中取数据v1。
    此时来了一个写操作B,将v2写入数据库,让缓存失效;
    读操作A在把v1放入缓存,这样就会造成脏数据。因为缓存中是v1,数据库中是v2.
    2020-03-21
    1
    8
  • GaGi
    对于Cache aside和read/write through而带来的数据不一致问题,工作中是这样解决:
    a写线程,b读线程:
    b线程:读缓存->未命中->上写锁>从db读数据到缓存->释放锁;
    a线程:上写锁->写db->删除缓存/改缓存->释放锁;
    这样来保证a,b线程并发读写缓存带来的脏数据问题;

    作者回复: 👍👍👍

    2020-03-21
    3
    3
  • 0x12FD16B
    Cache Aside 模式在下面的场景下:
    读写线程之间在执行 Cache Aside Pattern 操作的时候,写线程删除了缓存,读线程从 DB 读到老的数据,把老的数据放到了缓存中,这样就会在缓存中产生脏数据。
    2020-03-21
    2
    1
  • Mq
    老师好,写数据跟删缓存不是一致的,写完数据到删缓存这段时间内其他并发访问都是脏数据,这种思维方式感觉不解决一致性问题,都会有可能出现脏读,只是时间长短问题
    2020-03-21
    1
  • 约书亚
    A,B两个进程
    B read cache x=null
    B read DB x=1
    A write DB x=2
    A delete cache
    B write cache x=1
    这时数据库里x=2,缓存中x=1,直到缓存过期之前一直是脏数据。这种概率算是比较小的了。

    文章中提到用灰度来解决问题,似乎解决不了基于类似redis这种做分布式缓存时的问题。

    2020-03-21
    2
    1
  • AAAAAAres
    如果读缓存不存在,然后去从库读数据来写缓存的话,主从延迟也会导致缓存中有脏数据
    2020-03-23
  • 1
    是不是model的话使用缓存,列表的话是不是不适合用缓存?列表应该怎么去缓存?

    作者回复: 这个还是得看业务,很多列表也可以缓存的,比如说一些排行榜数据。

    2020-03-23
  • 刘楠
    一直用的Cache Aside,
    2020-03-22
  • 陶金
    package main

    import (
    "fmt"
    "sync"
    "time"
    )

    type BaseModel struct {
    data int
    mu sync.RWMutex
    }


    var cache *BaseModel
    var db *BaseModel
    const FIRST_DATA = 1
    const SECNOD_DATA = 2
    const EMPTY_DATA = 0

    func init() {
    cache = new (BaseModel)
    db = new (BaseModel)
    db.setData(FIRST_DATA)
    }


    func main() {

    go read()
    go write(SECNOD_DATA)

    time.Sleep(3 * time.Second)
    fmt.Println("db's data is %d, cache's data is %d", db.getData(), cache.getData())
    }


    func read() {
    data := cache.getData()
    if data == 0 {
    data = db.getData()
    time.Sleep(2* time.Second)
    cache.setData(data)
    }
    }


    func write(data int) {
    time.Sleep(1*time.Second)
    db.setData(data)
    cache.setData(EMPTY_DATA)
    }


    func (self *BaseModel) getData() int {
    self.mu.RLock()
    defer self.mu.RUnlock()
    return self.data
    }

    func (self *BaseModel) setData(data int) {
    self.mu.Lock()
    defer self.mu.Unlock()
    self.data = data
    }

    大概场景如下:
    1. 初始数据库中数据为“1”,缓存无数据
    2. 线程A为读线程,读取缓存未果,然后读取数据库中的记录为“1”,这时候缓存阻塞住。
    3. 线程B为写线程,先把数据库中的数据更新为“2”,再删除缓存,结束。
    4. 此时线程A解除阻塞,然后把记录“1”更新到缓存中。

    此时缓存中数据为“1”, 数据库中数据为“2”, 缓存落后于数据库中的数据。
    2020-03-22
  • 每天晒白牙
    思考题
    Cache Aside 模式如何产生脏数据?
    首先 Cache Aside 这种模式和 Read/Write Through 模式的读取操作一样,都是先尝试读缓存,如果命中直接返回;未命中的话读数据库,然后更新缓存。
    写操作不是更新缓存,而是把缓存中的数据删掉
    那怎么出现脏数据?
    假设有下面两个线程对 key 分别进行读写操作
    读线程 t1
    写线程 t2
    按照下面的流程进行操作
    1. t1 读缓存未命中,然后从数据库中读到 value1
    2. t2 更新 key 为 value2,并尝试删缓存(此时缓存中并没有)
    3. t1 把从 db 中读到的 value1写回缓存

    这时 db 中 key 的 value 为新数据 value2,缓存中为旧数据 value1,产生了不一致。
    这种情况只发生在读线程从 bd 读到旧数据往 cache 中写前,有写线程更新了 db,然后读线程把老数据写回 cache


    Read/Write Through 发生脏数据的情况
    第一种情况是并发读写
    对 key 进行读写的两个线程
    读线程 t1
    写线程 t2

    按照如下时间顺序操作
    1.t1 尝试读缓存但未命中,然后去 db 中读取到了数据 value1,但还未更新缓存兄弟的数据
    2. t2 更新数据库 value2,更新成功后常识读取缓存,未命中,所以更新缓存为 value2
    3.t1 线程继续把之前从 db 中读到的旧数据 value1 写回缓存
    这样 db 中是新数据,但缓存中是旧数据

    第二种情况是并发写
    这种情况是db 中产生了 ABA 问题
    比如有两个写线程 t1,t2,分别按下面的先后顺序操作
    1.t1 尝试把 key 更新为 value1,但响应丢失
    2.t2 尝试把 key 更新为 value2,还未响应结果
    3.t1 发生重试操作
    4.t2 响应成功
    5.t1 响应成功
    本来写应该按先后顺序的,t2后到,数据库和缓存中应该是 value2,但因为 t1 发生了重试,导致数据库和缓存中是 value1了,产生了ABA问题,解决办法是在更新时加上 version 版本号判断

    所以没啥万能的方法,需要根据业务场景来制定方法
    2020-03-22
  • Aliliin
    a读到老数据的同时并没来得及写入缓存,然后b正好更新了db清空缓存,a写入之前读到的数据写入缓存。
    只想到这种情况,不知道靠谱吗。
    2020-03-21
  • 正在减肥的胖籽。
    任何一种方式缓存使用方式和数据库之间都会有脏数据的产生,我现在的解决方案就是看业务方能接受多长时间的脏数据,然后缓存就设置多久的过期时间。2.或者数据库更新成功后,用MQ去通知刷新缓存。
    2020-03-21
  • 小美
    Cache Aside解决的只是并发写请求导致的缓存数据不一致问题。对于读写这种场景并没有彻底解决。
    A:读,缓存穿透,查库。
    B:写,更新数据库。
    B:写,删除缓存。
    A:读,回写缓存。导致不一致。
    目前针对这种问题我们这边才去的方案是写请求后用MQ延迟删除缓存。老师有什么好的方法和实践吗?

    作者回复: 你可以参考一下“GaGi”同学的留言,用锁来解决并发问题。

    2020-03-21
  • 往事随风,顺其自然
    老师有个问题请教你,我这边有个业务,合同编号,存在redis 🀄️和数据库中,每次先查redis 获取合同编号后面虚寒,然好加1⃣️,保存回去,外去更新数据库,做了数据库合同编号重复,检验,但是每次还是有合通编号重复的,请问这个怎么解决?市并发导致?

    作者回复: 使用Redis命令INCR是可以保存原子性的。

    如果是GET出来,在程序内加一,在SET回去,确实会存在并发问题。

    2020-03-21
  • 肥low
    我觉得Cache Aside还是没有解决主从延迟带来的问题😱
    2020-03-21
  • 南山
    只有一个体会: 清楚各种缓存策略的缺陷,想用缓存只能是结合当前业务来是否用,用什么策略
    2020-03-21
收起评论
18
返回
顶部