- "大爷你没事吧",
- "没事没事, 路有点滑,"
- "大爷, 你的鞋都被人追掉了"
- "现在的小姑娘真是的, 撩下就动手, 多亏你小伙子, 小伙子你是做什么工作的"
- "后端 java 开发"
- "哦, 有前途, 这样你救了我, 我今天就把我的不传之谜 传授于你,"
- "? 啥?"
说着大爷颤颤巍巍的从口袋里掏出了一本笔记, 赫然写着
- "小伙子 你知道 redis 的速度么?"
- "单机读可达 10000 次 / S, 写可达 5000 次 / s"
- "恩 ,RDS 巨擘 mysql , 经过了这么多年优化 才 1000 次 / S,500 次 / S, 那么有么有想过 redis 为何这么吊么,"
- "redis 是操作内存, 操作速度自然比 mysql 的磁盘操作要快太多了"
- "哎, 世人肤浅, 都只看到了 redis 的内存操作主导了优势, 在我看来, 这个原因也只是 1/3(相比较 RDS 而言, 最大优势是内存, 但是相对对于 memcached 等同类产品)"
- "? 另外 2 分是?"
- "哼哼,"
老爷子舔了舔手指, 慢慢翻开.
" 数据结构! 大家都知道 Redis 提供了 5 种数据类型的支持, 却不知他们的其中奥妙, 着实可惜, 今日老夫就让你见识见识,
Redis 最基础的是 SDS(simple dynamic string), 动态字符串, 与 java c 的字符串不同, 他的定义是不单单是一个 char 数组构成, 每个 sds 都会比它真实占用的字符长度都长, 通过一个空闲标识符表示 sds 当前空闲字符有多少, 如此设计, 在一定长度范围的内的字符串都可以使用此 sds, 而且不会频繁的进行内存分配, 直到此 sds 不能容纳分配的字符串, 如果遇到这种情况情况, 才需要进行扩扩容, 妙不可言; 这是 Redis 的最基础的, 所有的 Redis k-v 中的字符串都是依托于 sds, 这是其一;
其二 dict, 字典, 类似于 java 中的 hashmap: 数组, 负载因子 hash 算法;
不同于它的就是, 每个 dict 拥有两个数组, 一个简单的 hash 算 ""? 两个数组? 简单的 hash 算法"
- "java hashmap 的 hash 算法是啥, 对 key 进行 hash, 将 hash 值与当前的数组长度进行计算, 获取当前 key 在数组中的索引值"
- "redis 的很简单, 就是 key 和一个定值运算, 简单粗暴不是不伤手, 保证了最快的执行速度, 至于为啥两个数组, 和 hashmap 一样, 也要扩容, 初始的 dic 的数组长度不能太大, 随着数据的增加, 超过了负载因子, dic 的数组必须进行扩容, hashmap 怎么扩容?"
"新生一个更长的数组, 遍历老数组向新数组的迁移"
" 原理差不多, 但是 hashmap 的扩张是一次性的, 而 Redis 的扩容是渐进式, 不影响当前使用 dic 的正常使用, 一点点划拉, 直到迁移完成, 这是其二,
其三堪称一个杰作, 你知道 MySQL 的索引的结构么?"" 我知道 B + 树,"
"B + 树, 源于 B 树, 也是自动平衡的树, 随机查询性能举世无双, 经过 B + 树的改进, 压缩了整棵树的高度, 在搜索性能又上一层楼, 但是 二叉树也有自己的弊端, 数据插入的平衡维持: 左旋右旋, 旋的我脑壳疼, 拖了性能, 而我们即将说到的 redis 第三点, 就是跳跃表, 在随机搜索方面略低于 B + 树, 但是对于数据插入, 跳跃表使用了历史上最屌的算法: 抛硬币, 唯一能和这个算法媲美的只有掷骰子, 推牌九, 斗地主, 扎金花等等"
"... 这算哪门子算法, 尤其后面几个, 最多算民间艺术"
大爷, 没有理睬, 继续说道,
" 在跳跃表是由 N 层链表组成, 最底层是最完整的的数据, 每次数据插入, 率先进入到这个链表 (有序的), 插入完成后, 通过抛硬币的算法, 判断是否将数据向上层跑, 如果是 1 的话, 就抛到上层, 然后继续抛硬盘, 判断是否继续向上层抛, 直到抛出了 0 结束整个操作, 每抛到一层的时候, 如果当前层没有数据, 就构造一个链表, 将数据放进去, 然后使用指针指向来源地址, 就这样依次类推, 形成了跳跃表, 每次查询, 从最上层遍历查询, 如果找到就返回结果, 否则就在此层找到最接近查询的值, 将查询操作移到另外一层, 就是刚才说到来源地址, 所在层, 重复查询, 第一次听到这跳跃表, 简直比左旋右旋还头大, 渐渐的领会到其要义: 没事多推牌九, 斗地主 扎金花
- "哦, 原理还有这个玩意, 好神奇哦, 这些数据结构就能解决 redis 的速度问题?"
- "非也非也"
- "这是存储方面巧妙的地方之一, 更主要是单线程 + 多路 I/O 复用模型"
- "单线程? 单线程会更快..."
"如果说单线程更快的话 那简直是骗人的, 不过单线程的模式解决了数据存储的顽疾: 数据并发安全, 任何运行多线程同时访问数据库都会存在这个问题, 所以才有了 mysql 的 mvcc 和锁, Memcached 的 cas 乐观锁, 来保证数据不会出现并发导致的数据问题, 但是 redis 使用单线程就不存在这个问题: 1, 单线程足够简单, 无论在 redis 的实现还是作为调用方, 都不需要为数据并发提心吊胆, 不需要加锁. 2. 不会出现不必要的线程调度, 你知道多线程, 频繁切换上下文, 也会带来很多性能消耗"
"额 ... 什么是切换上下文?"
"你记得上次有个帅哥讲内存模型的时候 (两程序员玩" 锁 ", 一人抢救无效身亡), 有提到工作内存么? 线程每次执行需要把数据从主内存读到工作内存, 然而当线程被调度到阻塞的时候, 这些工作内存的数据需要被快照到线程上下文中, 其实就是一个记录各个线程状态的存储结构, 等到线程被唤醒的时候, 再从上下文中读取, 称之为上下文切换; 减少上下文切换操作, 也是使用单线程的奥妙;"
"...."
"再说 多路 I/O 复用模型, 这个也是 java 的 NIO 体系使用的 IO 模型, 也是 linux 诸多 IO 模型中的一种, 说白了就是当一个请求来访问 redis 后, redis 去组织数据要返回给请求, 这个时间段, redis 的请求入口不是阻塞的, 其他请求可以继续向 redis 发送请求, 等到 redis io 流完成后, 再向调用者返回数据, 这样一来, 单线程也不怕会影响速度了" 哦 听起来很厉害, 不过我再想 Redis 真的是单线程就完全安全么?"" 当然不是... 一个有趣的场景, 如果请求 1 2 3 同时发送修改 Redis 的一个 key 值, 这个是不可预期的, 无法判断那个才是正确的, 如果你认定第一个修改的为正确的, 就需要借助 Redis 其他的特性来完成, 比如说 watch, 如果要保重多个操作原子性, 就需要 multi"
"算了, 反正我也听不懂, 我先走了"
- "少年留步,"
- "大爷你松手, 我听着呢"
"下面我必须要叮嘱你, redis 持久化一定要慎重, AOF RDB, 切不可滥用, redis 集群的数据库同步, 与 mysql 同步不同, 别有洞天, 最后老夫再传授你" 如何避免缓存击穿 "的奥秘"
"? 缓存击穿? 是啥?"
"缓存一般作为 RDS 的前置系统和服务器直连, 减轻 rds 的负担, 常理而言, 如果服务器查询缓存而不得的话, 需要从 rds 中获取然后更新到缓存中, 但是如果在" 从 rds 中获取然后更新到缓存中 ", 这个阶段, 缓存尚未更新成功, 大量请求进来的话, rds 势必压力暴增, 甚至雪崩, 或者歹人恶意攻击, 一直查询 rds 和缓存中未存在 key, 也会导致缓存机制失效, rds 压力暴增, 称之为缓存击穿," 前辈 那该咋儿办?"" 缓存永不失效, 定时同步 rds Redis, 不允许应用直接请求查询 rds, 所有的查询以缓存中为准"
- "那且不是数据不能实时展示了么,"
- "对的, 不过你就认为服务器临时上了一个厕所, 耽误个几分钟, 这样会好受些"
- "如果定时器挂了怎么办"
- "服务器挂了怎么办? 其实道理一样的, 如何避免服务器宕机, 就如何避免定时器挂机"
- "哦"
年轻人大概有些明白, 或者有些恍惚, 低头间, 只看到大爷的背影, 渐行渐远,
- "大爷, 未请教尊姓大名"
- "黄见红"
- "前辈可留下秘籍, 供晚辈瞻仰"
- "拿去"
大爷从大手一辉
书落在了少年手中, 还有淡淡的体温, 少年打开, 呢喃着
- "redis 设计与实现..."
- "小子里面有我二维码, 别忘了付款"
- "...."
惯例上图:
本文章旨在为大家分析一项技术所涵盖的体系, 对于细节方面有些处理粗糙, 如有不妥请多多指正
欢迎大家加微信骚扰: treenpool
请持续关注, http://treenpool.com 国内第一款专注于知识体系搭建工具
来源: http://www.jianshu.com/p/853ed32e29ab