获取中...

-

Just a minute...

TL;DR
第n次尝试向我的blog中尝试添加一些’东西’, 这总是会在一段时间后转变为写笔记, 直到我再次意识到blog的存在. 由于不是很擅长写文章, 或者说不是很喜欢写文章, 在我看来, 如果blog文章仅仅是技术性的阅读记录, 那么这种文章早有范本, 而不需要自己大费周章只是为了达到一些差不多的效果. 至少, 需要先尝试阅读理解后尝试将思考记录, 这对于技术性blog而言才算得上一点阅读价值. 尽早停止边阅读技术总结文章边记录的习惯也许才能真正培养写blog的习惯.
显然, 我已经多次记录redis的一些关键点, 只是为了能够在面试中取得一点小胜利. 直到我意识到我始终只是了记录了一些难点却总是会在2周后忘记大部分内容时, 我一次又一次回到原点从而进行着不断的循环. 我不想带着一个只是为了解决一些特殊问题的想法去认识真正的redis, 我更乐意带着一个为什么的想法去剖析解决问题背后可能的原理, 也许这可能会存在偏差与误解, 但是计算机中不存在这种偏差, 显然编程语言给了一种重塑一切的能力.

TOC
Introduction
Data Type
Core
Compare
Apply


Introduction

redis是一个基于内存的非关系性数据库, 至少可以知道的是: io速度快,没有一般的表结构. 假设一切东西都能按照sql的关系模型抽象, 那么一切可能的东西都能按照实体-属性相互关联, 仔细想一下都会觉得有一点牵强. 短期内的强关联显然不会动用sql型数据库, 长期的关联性才可能需要考虑sql的使用. 因此对于redis而言, 直接将其视作一种k-v结构的大型字典.

redis存在一些基本特性. redis具备极快的io速度, 基于内存的io速度显然相比于sql的磁盘io速度要快得多,但是这也是redis一大局限: 存在物理存储上限, 内存的空间大小远没有磁盘的大. 因此造就了其核心的使用价值 cache. cache的命中显著提高了cpu的执行效率, 同理在数据库访问层面, redis显著提高了数据的访问效率. redis包含多种数据结构, 这会在数据类型部分指出, 事实上数据结构是很有意思的部分. redis存在所谓的事务处理, 但是我更喜欢称为单执行流, 所有的指令执行支持原子性操作. redis也支持持久化操作, 这更像是一种妥协政策, 更多地是为了针对故障的保守处理.

Data Type

redis作为一个k-v字典数据库, key数据类型只能为stirng, value根据不同场景下选择合适的数类型.

string

string底层采用的是封装的结构体sds, sds是一种优化的string存储容器, 内部包含部分string的相关信息, 从而降低相关信息查找的时间来提高效率. 类型底层采取的是5种不同的string类型根据可能的长度进行划分, 一般性数据存储可以采用string.

string可以用于: json对象的序列化缓存 | 简易计数器 | 分布式锁 | session状态管理

list

list底层采用的是quicklist, quicklist内部采用双端链表和listpack来优化结构, list与一般的双端链表最大区别就是存在前后指针优化, 这使得可以极大程度上减少内存的无效占用, list的操作与一般双端链表基本一致.

list可以用于: 低要求消息队列

hash

hash底层采用dict, 同时结合了listpack来优化内存结构. hashredis中类似嵌套dict结构, 操作与一般的hashtable一致.

hash可以用于: 属性频繁更新的json对象 | 购物车

set

set底层采用dictinset, dict主要用于记录inset出现的key. 内部数据通常是无序的, 因此适合用于防止唯一标识重复计算.

set可以用于: 文章点赞 | 共同关注 | 防止用户刷单 | 抽奖

zset

zset底层采用dictskiplist, 内部数据通过浮点score映射从而实现数据排序, 核心实现为sorted set. 主要用于在唯一的数据上进行排序.

zset可以用于: 排行榜 | 范围查找

bitmap

bitmap不属于主要运用的数据类型, 通过bit的0-1状态进行高效状态记录.

bitmap可以用于: 用户状态 | 每日签到 | 连续状态追踪

hyperloglog

hyperloglog主要用于海量高效数据记录, 存在误差率, 不确保100%准确.

hyperloglog可以用于: 百万级网页用户检索记录

geo

geo特定用于地理位置点存储, 能够高效记录地图点位置.

geo可以用于: 位置导航 | 位置计算 | 范围检测

stream

stream类似list, 主要用于解决list的遗留问题.

stream主要用于: 消息管道 | 高效消息队列

Core

redis核心原理包含很多层面, 从一条redis指令到达redis服务器并被执行发生了很多过程, 这基本上被视作为一个通用的c/s模型, 显然在现代化架构层面设计中存在多客户端连接操作的应用程序在一定程度上都需要以c/s模型为基本设计.

process

redis采用单线程执行指令, 由于内部核心基于事件驱动模型, 事件分发模块总是单线程的, 进而使得redis在指令执行上呈现出单线程样式. 而6.0引入io多路复用处理后针对网络io进行了多线程优化, 在指令处理事件方面不再成为redis主要瓶颈, 但在指令执行层面依然依赖单一指令执行队列, 因此redis即使将所有可以优化的地方采取了多线程处理, 但是在指令执行层面仍然是单线程. redis主要瓶颈显然就是指令执行效率. 但是基于内存的特点使得该瓶颈在低压环境下应该没有明显差距, 如果长期处于高压环境下, 就需要通过集群策略来保证redis的高可用.

redis的核心处理流程与大部分c/s应用保持高度一致, 由应用层的client发起网络请求到指定的server进行解析处理返回响应. 显然, redis的优化更多在业务模式下的数据结构选择与使用, 因为一般的底层已经经过了优化, 在数据结构设计层面也经过了优化. 同时基于内存的数据库本身速度存在一定的优势, 显然redis作为一个数据的临时存储是一个极佳的选择, 可以认为redis就像是databasecache层, 缓存命中越高, 一个系统整体的效率越高.

persistent

redis基于内存, 存在数据易丢失性问题, 为了解决这个问题, redis存在2种持久化方案. 思考redis为什么需要持久化? 如果仅仅作为缓存, 显然数据也许并没有数据库中的价值高, 即使在丢失后, 通过适当的数据预热手段可以恢复这些缓存, 但是针对一些热点数据, 显然redis在这种情况下会在很大程度上使得存在数据库不一致性, 假设某些热点数据采取的是异步写回数据库, 这些数据从数据库层面上看存在价值也需要持久化, 总是无法确保redis能够在进行异步写回数据库的过程中不出现意外情况, 因此短期内的redis数据持久化存在一定的重要作用.

RDB快照在指定间隔会给redis中数据做一次全量记录, 快照显然是完美的, 因为所有数据都以原本的方式记录下来, 没有任何偏差. 恢复时只需要读取一个.rdb后缀的快照文件即可. 但是快照不能在时间连续的间隔中持续执行, 这种成本显然是巨大的. 因此在RDB间隔期间采取AOF刷盘策略, 借助一个AOF缓冲通道不断监视写操作, 并将写操作指令异步通过缓冲通道写入磁盘中实现AOF刷盘, 但是这可能导致AOF记录异常庞大, 因此还需要借助一个AOF重写机制通过AOF重写通道替换旧AOF记录. AOF重写主要基于内存数据快照进行指令的压缩实现AOF记录的压缩, 状态快照的记录通过主线程创建的子进程单独操作记录, RDB也是通过子进程与主线程共享数据从而实现快照的记录, 内存状态记录后, 主线程的写指令进入AOF重写通道异步追加到新AOF尾部, 同时子进程处理内存数据并生成对应压缩指令记录到磁盘中.

expired key

redis主要采用惰性删除和定期删除. 惰性删除允许key超过过期时间存在等待被操作后执行删除, 定时删除则采取一段时间间隔后执行扫描过期key并尝试部分删除并且在删除后进行阈值确认, 当过期key仍然高于25%时继续执行删除直到低于阈值.

mode

redis服务主要包含垂直拓展和横向拓展. 垂直拓展针对每一个单点redis服务, 而横向拓展针对redis集群部署. 前者关注硬件设备的提升, 后者关注架构模式的设计.

redis主要包含主从模式, 哨兵模式, 集群模式. 在主从模式下, master节点负责实际的写操作, slave节点同步来自master的数据, 同时提供主要的读操作. 但是当master节点出现问题后, 不具备故障转移策略. 此时通过部署哨兵集群, 用于监控并在master失效后重新选择, 一般情况下哨兵模式的redis集群已经具备高可用的特点, 此时还可以通过集群模式进一步增加redis的负载均衡能力.

关于主从, 哨兵, 集群模式的细节可以尝试思考并猜测可能的设计.

Compare

memcached

pros:

  1. 简单的缓存解决方案(基于内存)
  2. 多线程, 大数据集内存利用优于redis
  3. 支持原子操作

cons:

  1. 支持数据结构有限
  2. 无内置事务, 集群模式
  3. 无内置持久化策略
  4. pub/sub模式

Apply

热点信息cache

for: 降低数据库查询压力
solution: 针对信息进行redis缓存, 请求命中缓存直接返回缓存响应, 否则查询数据库并更新缓存
key points:

  • key具备强关联性, eg: object:attribute:id
  • ttl设置恰当, 热点key需要合理设置过期期限, 并能够延长
  • value空处理, 短时限key空值缓存防止缓存穿透. redis降级处理

分布式lock

for: 分布式共享资源可写一致性
solution: 操作可写资源前, 所有分布式线程需要获取分布式锁后执行写操作, 操作完成后需要释放锁
key points:

  • lock唯一性, 规范分布式锁级别, 确保不存在资源竞争
  • ttl合理, 避免锁长时间被占用, 对耗时操作延长锁期限
  • atomic操作, 防止意外释放锁
  • retry机制, 获取锁失败重试

计数器

for: 特殊业务需求记录
solution: 秒杀场景下计数, 端点访问限制
key points:

  • ttl限时计数
  • value上限

排行榜

for: 排名, 范围查询
solution: 热点排行榜, 范围数据聚合

会话管理

for: 分布式状态管理
solution: 统一管理状态, 保持状态一致性
key points:

  • ttl合理, 定时更新状态

消息队列

for: 简易管道消息队列, 运用对消息要求不严格的场景(请求限流)
solution: 采用pub/sub方式, 消费者阻塞读取队列中消息, 避免轮询

二进制状态记录

for: 大型bool记录存储
solution: bitmap记录二进制状态记录

地理信息存储

for: 地理信息存储, 范围搜索

海量数据统计

for: 大日志数据集合记录并去重, 基于概率模型, 存在一定误差

相关文章
评论
分享
  • Kafka

    TL;DR消息队列是现代化软件架构中在消息系统中最为核心的中间件, 可以想象在还不存在pub/sub模式下的软件架构为了解决传统点对点的queue模式, ...

    Kafka
  • Rime配置默认中文简体

    不同的linux distro关于rime的配置文件的位置不同,可以先查询自己distro的配置文件位置后再按照下列步骤进行。 前言我们知道: 1)Ri...

    Rime配置默认中文简体
  • HELLO

    欢迎来到我的个人博客,距离上次搭建博客已经过去了半年之久,去年兴致勃勃地花了几天时间搭建的个人博客,最后也只是浅浅地以学业繁重而告一段落。现在回想一下,自...

    HELLO
Please check the parameter of comment in config.yml of hexo-theme-Annie!