redis remote dictionary service 远程字典服务
-
项目中redis用来做什么?
- 存储session,用户登录信息
- 计数器 比如限制ip、设备号登录,获取验证码等
- 存储一定过期时间的数据(重点在时效性、比如审批有一定的时效性这种)
- 用作缓存热点的数据
- 暂时存储一些不太重要的数据,一定的条件触发持久化
-
redis存储缓存如何解决和mysql的数据一致性问题
- cache-aside
- 缓存命中,直接读取缓存数据
- 缓存未命中,查询数据库把数据放到缓存中
- 写请求:先更新数据库,再删除缓存
- 为什么是删除缓存,而不是更新
- 删除简单,更新可能涉及到其他计算
- 写操作很多时,缓存可能还没用到就被更新了
- 两个写请求同时发起的时候可能会有并发问题(a写db,b写db,b写cache,a写cache)
- 基于binlog管理缓存
- go里面使用的go-mysql-org/go-mysql这个库
- update和delete时删除缓存
- 业务解耦。
- cache-aside
-
redis 数据过期策略和
- Redis并没有简单粗暴的使用定时删除方式,而是采用了“定期清理+惰性删除策略”
- Redis默认每隔100ms检查,是否有过期的key,有过期key则删除 不是检查所有 而是随机抽取进行检查
- 当我们获取某个key的时候,Redis会检查一下这个key如果设置了过期时间是否过期了,如果过期了此时就会删除。
- 如果没有检查到 而且没有请求到 则会造成占用内存越来越高 需要内存淘汰机制
-
内存淘汰机制
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,新写入操作会报错
- 事务
- 单独的隔离操作,命令串行化,不会被其他客户端的命令请求打断
- multi 开启一个事务
- discard 清空事务队列,放弃事务
- exec 执行这个事务
- watch 可以监控一个或者多个键,如果有键被修改了,之后的事务就不会执行
- 事务的失败
- 入队的命令错误:拒绝执行并放弃这个事务
- 执行指令时候的错误:其他指令继续执行,不会回滚
- 为什么不支持回滚
- redis命令只会因为错误的语法而失败,失败是因为编程出现了错误或者命令用在了错误的键上,错误应该是发生在开发过程中就被排除掉的。
- 不支持回滚可以使redis内部更加简单和快速
6 redis为什么是单线程
- redis是基于内存的操作,不涉及大量的cpu计算,大部分的数据结构读取都是o(1)的,单线程可以满足需求,而且不会有上下文切换的时间。
- 模型简单,便于维护
- redis 为什么这么快?
- 数据存储在内存中
- 底层的数据结构操作简单 时间复杂度在O(1)
- 单线程 保证了操作的原子性 没有上下文切换和竞争的开销
- 使用非阻塞多路复用I/O技术,I/O请求处理高效 说Redis是单线程并不严谨,所谓单线程只是Redis在处理网络请求时使用了单线程。由于使用了非阻塞、多路复用的I/O技术,几乎纯内存无延迟的操作,使用单个线程足以满足使用需求。但是,这并不是说Redis内只有一个线程,它还有其他线程来完成辅助工作,比如:数据过期策略、内存淘汰机制、主从复制等都有相关职责的线程
- redis 数据持久化
参考 参考 详细
juejin.cn/post/714760…
-
RDB 快照模式 snapshot 全量模式
- Redis采用多线程COW(Copy On write)机制来实现快照持久化
- 写入二进制文件dump.rdb
- redis 调用 fork,现在有了子进程和父进程。
- 父进程继续处理 client 请求,子进程负责将内存内容写入到临时文件。由于 os 的实时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时 os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程地址空间内的数据是 fork时刻整个数据库的一个快照。
- 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。client 也可以使用 save 或者 bgsave 做一次快照持久化。 save 操作是在主线程中保存快照的,会阻塞所有client 请求。每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步变更数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘 io 操作,可能会严重影响性能。
-
AOF日志模式 增量模式
- AOF日志文件存储的是Redis服务器的顺序指令序列,AOF日志只记录内存改变的指令。
- Redis的增量持久化,存在于每次处理完写命令之后,通过propagate函数触发。
- Redis的AOF包含三种同步策略:
- always 每次执行完命令,直接同步触发fsync方法,强制数据落地磁盘。会降低redis的吞吐量,每次当落地成功,才响应给客户端,因此此种方式,很大程度的有很好的容错能力
- every second 每秒异步触发一次fsync方法。
- no 不显式调用fsync方法,由操作系统决定什么时候落地。
- 对于Redis数据的回放,即对数据的重写,全量和增量,触发时机一致。
-
优缺点
-
- rdb 无法做到实时/秒级持久化
- rdb 每次都要fork操作创建一个进程 频繁操作成本较高
- rdb 恢复数据较快
- aof文件较大
- aof恢复数据较慢
redis 的数据结构 参考
- string 字符串 二进制安全 "\0" 最长512mb
- 使用场景 存储简单的键值类型 incr decr操作 原子性的 可以用于库存加减等场景
- set get mget incr decr
- hash
- key field 都是string类型
- hget hset
- 两层key 删除时删除所有内容 过期时间只能设置所有
- 扩容与收缩 扩容 负载因子>=1 缩容 负载因子>=5 采用渐进式rehash实现
- 优点是把rehash操作分散到每一个字典操作和定时函数上,避免了一次性集中式rehash带来的服务器压力。
- 缺点是在rehash期间需要使用两个hash表,占用内存稍大
- list 插入顺序排序的字符串元素集合 有序可重复 底层双向链表
- lpush lpop 头 rpush rpop 尾
- 当作轻量级的队列使用
- set 无序去重的集合 元素唯一 提供了求交集并集的方法
- 编码格式 intset hashtable
- 保存一些不重复的字符且顺序可以是无序的
- 如果value可以转成整数值,并且长度不超过512的话就使用intset存储,否则采用hashtable。
- zset有序集合 内部维护了一个score参数来实现,适用于排行榜和带权重的消息队列等场景。
- zset的编码有两种,分别是:ziplist、skiplist。当zset的长度小于 128,并且所有元素的长度都小于 64 字节时,使用ziplist存储;否则使用 skiplist 存储。
- 可以在使用积分排行榜等场景下使用
redis主从复制
//启动Redis实例作为主数据库
redis-server
//启动另一个实例作为从数据库
redis-server --port 6380 --slaveof 127.0.0.1 6379
- 从节点初次连接到主节点,触发一次全量复制,主节点生成一份rdb文件,再缓存新收到的写命令
- 从节点先把rdb文件持久化存储,之后再从磁盘读取到内存
- 主节点把缓存的写命令发送到从节点,从节点同步数据
缓存穿透
查询一个不存在的数据,db查不到数据就不会写缓存,会打到db
解决
- 缓存空值数据
- 使用布隆过滤器 查询不存在的会被过滤掉,避免对db的查询压力
缓存雪崩
缓存具有相同的过期时间,某一个时刻所有缓存失效,请求都到db
解决:
- 在原有的失效时间上加一个随机值,让不同数据的过期时间分散一些
缓存击穿
大量请求同时查询一个存在的key,key失效,大量请求打到db
解决:
- 第一个请求的数据可以访问数据库,拿到数据放到缓存之后,其他请求再去访问缓存