mysql缓存方案

缓存方案主要用于缓存用户定义的热点数据,用户可以通过缓存来获取数据,从而降低数据库的压力。
内存访问速度远大于磁盘访问速度,mysql缓存的数据跟业务无关,缓存的是最近操作的数据。mysql作为项目的主要数据库,缓存数据库只是辅助功能。

缓存一致性

  • mysql有,redis无,正常
  • mysql无,redis有,不正常
  • 都有,数据不一致,不正常
  • 都有,数据一致,正常
  • 都没有,正常

读写策略

  • 读策略:先读缓存,缓存存在直接返回,缓存不存在,去访问Mysql获取,若存在,再写缓存
  • 写策略:
    • 安全:先删除缓存中数据,再写mysql,再同步到redis.这样mysql的压力会大
    • 效率:先写缓存,设置过期时间(防止mysql写失败),再写mysql,最后将mysql中的数据同步到redis中

同步方案

  • 触发器 + UDF(user defined function,c++扩展代码),每次表变更都需要与redis重新建立连接
  • go-mysql-transfer、canal(java,考虑分布式))伪装成mysql的从数据库,bin log->i/o thread->relay log,既要连接mysql,也要连接redis
    vim /etc/mysql/my.cnf

    1
    2
    3
    4
    [mysqld]
    log-bin = mysql-bin
    binlog-format = ROW
    server_id = 1 # 主数据库id
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CREATE TABLE `user` {
    `id` BIGINT,
    `nick` VARCHAR(100),
    `height` INT8,
    `sex` VARCHAR(1),
    `age` INT8,
    } ENGINE=InnoDB DEFAULT CHARSET=utf8;
    insert into `user` values(1001, 'nephen', 180, 1, 20);
    update `user` set age = age + 1 WHERE id = 1001
    delete from `user` where id = 1001;
    show master status;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # mysql配置
    addr: 192.168.130.120:3306
    user: root
    pass: 123456
    charset: utf8
    slave_id: 1001
    flavor: mysql

    # 规则配置
    rules:
    schema: test # 数据库名
    table: user # 表名
    order_by_column: id # 排序字段
    column_underscore_to_camel: true # 列名称下划线转驼峰
    lua_file_path: lua/t_user.lua
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    local ops = require("redisOps")
    local row = ops.rawRow()
    local action = ops.rawAction()

    if action == "insert" or action == "update" then
    local id = row["id"]
    local key = "user:" .. id
    local name = row["nick"]
    local sex = row["sex"]
    local height = row["height"]
    local age = row["age"]
    ops.HSET(key, "id", id)
    ops.HSET(key, "nick", name)
    ops.HSET(key, "sex", sex)
    ops.HSET(key, "height", height)
    ops.HSET(key, "age", age)
    elseif action == "delete" then
    local id = row["id"]
    local key = "user:" .. id
    ops.DEL(key)
    end
    1
    2
    keys *
    hgetall user:1001

异常处理

以效率为主的读写策略时。

  • 缓存穿透,mysql和redis都没有该数据,用户又一直读取不存在的数据,造成mysql访问吞吐量降低。
    • 在redis中设置key为nil,标记不存在的数据,以后访问该key不需要去访问mysql,少量数据可以使用这种方法。
    • reids中部署布隆过滤器,可以确定某个key一定不存在,不要放在server,因为可能有多个server
  • 缓存击穿,redis没有,mysql有,此时有大量并发请求,造成mysql访问吞吐量降低
    • 过热的数据(这个数据不被删除)不过期
    • 分布式锁,【先读缓存,缓存存在直接返回,缓存不存在,去访问Mysql获取,若存在,再写缓存】加锁,会影响并发性能
  • 缓存雪崩,大量缓存数据集中失效(过期时间没有错开),但是mysql数据存在
    • 间隔设置过期时间
    • 重启时,预先导入数据到缓存(预热)

弊端

  • 不支持多语句事务, 同一个key必须经过同一个连接,hash(key) % n选择固定的连接,避免多线程
  • 不支持回滚
nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!