当前位置: 首页 > news >正文

邢台做wap网站的地方wordpress 站内资讯

邢台做wap网站的地方,wordpress 站内资讯,保定网站建设设计,青岛专业网站营销目录 第一章 基础应用篇Zset并发问题 - 分布式锁再谈分布式锁客户端在请求时加锁失败策略redis异步队列位图Hyperloglog布隆过滤器GeoHashscan 命令字典结构rehash扩容大 key 扫描 第二章 原理篇线程IO模型RESP 序列化协议持久化管道事务PubSub内存管理 第三章 集群篇CAP主从同… 目录 第一章 基础应用篇Zset并发问题 - 分布式锁再谈分布式锁客户端在请求时加锁失败策略redis异步队列位图Hyperloglog布隆过滤器GeoHashscan 命令字典结构rehash扩容大 key 扫描 第二章 原理篇线程IO模型RESP 序列化协议持久化管道事务PubSub内存管理 第三章 集群篇CAP主从同步快照同步哨兵Cluster 拓展篇Stream过期策略从节点过期策略淘汰策略近似LRU异步线程redis安全通信 - spiped 第一章 基础应用篇 Zset zset 内部的排序功能是通过 “跳表” 数据结构来实现的 list 、set 、hash 、zset 这四种数据结构是容器型数据结构它们共享下面两条通用规则。 1.create if not exists如果容器不存在那就创建一个再进行操作。比如 rpush 操作刚开始是没有列表的 Redis 就会自动创建一个然后再 rpush 进去新元素。 2.drop if no elements如果容器里的元素没有了那么立即删除容器释放内存。这意昧着 lpop 操作到最后一个元素列表就消失了。 如果一个字符串已经设置了过期时间然后又调用 set 方法修改了它它的过期时间会消失。 并发问题 - 分布式锁 分布式锁本质上要实现的目标就是在 Redis 里面占一个坑当别的进程也要来占坑时发现那里已经有一根大萝卡了就只好放弃或者稍后再试。setnx来占用完了调用 del 指令释放坑。 setnx lock:xxx true OK do something criticaldel lock:xxx Integer) 1问题1如果逻辑执行到中间出现异常了可能会导致 del指令没有被调用这样就会陷入死锁锁永远得不到释放。 解决1拿到锁之后再给锁加上一个过期时间比如5s这样即使中间出现异常也可以保证 5s 之后锁会自动释放。 setnx lock : xxx true OKexpire lock:xxx 5 do something criticaldel lock : xxx (integer) 1问题2如果在 setnx 和 expire 之间服务器进程突然挂掉了就会导致 expire 得不到执行也会造成死锁。 解决2这种问题的根源就在于 setnx和 expire 是两条指令而不是原子指令。如果这两条 指令可以一起执行就不会出现问题。在 Redis 2.8 版本中引了 set 指令的扩展参数使 得 setnx 和 expire 指令可以一起执行这也是分布式锁的奥义所在 。 set lock:xxxtrue ex 5 nx OK do something criticaldel lock:xxx问题3Redis 的分布式锁不能解决超时问题如果在加锁和释放锁之间的逻辑执行得太长以至于超出了锁的超时限制就会出现问题 。 因为这时候第一个线程持有的锁过期了临界区的逻辑还没有执行完而同时第二个线程就提前重新持有了这把锁导致临界区代码不能得到严格串行执行 。 解决3将 set 指令的 value 参数设置为一个随机数释放锁时先匹配随机数是否一致然后再删除 key确保当前线程占有的锁不会被其他线程释放。 问题4匹配 value 和删除 key 不是一个原子操作 解决4使用 Lua 脚本来处理了因为 Lua 脚本可以保证连续多个指令的原子性执行。 问题5但是这也不是一个完美的方案 它只是相对安全一点 因为如果真的超时了当前线程的逻辑没有执行完其他线程也会乘虚而入。 可重入性是指线程在持有锁的情况下再次请求加锁如果一个锁支持同一个线程的多次加锁 那么这个锁就是可重入的。 比如 Java 语言里有个 ReentrantLock 就是可重入锁。 再谈分布式锁 问题在 Sentinel 集群中当主节点挂掉时从节点会取而代之此时这种set nx方式是有缺陷的。 如果第一个客户端在主节点中申请成功了一把锁但是这把锁还没有来得及同步到从节点主节点突然挂掉了然后从节点变成了主节点这个新的主节点内部没有这个锁所以当另一个客户端过来请求加锁时就会批准。这样就会导致系统中同样一把锁被两个客户端同时持有不安全性由此产生。 不过这种不安全也仅在主从发生 failover 的情况下才会产生而且持续时间极短业务系统多数情况下可以容忍。 解决Redlock。使用 Redlock需要提供多个 Redis 实例这些实例之前相互独立没有主从关系。加锁时它会向过半节点发送 set(key, value, nxTrue, exxx指令只要过半节点 set 成功就认为加锁成功。释放锁时需要向所有节点发送 de指令。不过Redlock 算法还需要考虑出错重试、时钟漂移等很多细节问题同时因为 Redlock 需要向多个节点进行读写意昧着其相比单实例 Redis 的性能会下降一些。 客户端在请求时加锁失败策略 1.直接抛出异常通知用户稍后重试。 直接抛出特定类型的异常 这种方式比较适合由用户直接发起的请求。用户看到错误对话框后会先阅读对话框的内容再点击重试这样就可以起到人工延时的效果。如果考虑到用户体验可以由前端的代码替代用户来进行延时重试控制。它本质上是对当前请求的放弃由用户决定是否重新发起新的请求。 2.sleep 会儿然后再重试。 sleep 会阻塞当前的消息处理线程会导致队列的后续消息处理出现延迟 。如果碰撞得比较频繁或者队列里消息比较多 sleep 可能并不合适。如果因为个别死锁的key 导致加锁不成功线程会彻底堵死导致后续消息永远得不到及时处理 。 3.将请求转移至延时队列过一会儿再试 。 这种方式比较适合异步消息处理将当前冲突的请求扔到另一个队列延后处理以避开冲突。 延时队列的实现 延时队列可以通过 Redis 的 zset 有序列表来实现。我们将消息序列化成一个字符串作为 zset 的 value这个消息的到期处理时间作为 score然后用多个线程轮询zset 获取到期的任务进行处理 。多个结程是为了保障可用性万一挂了一个线程还有其他线程可以继续处理。因为有多个线程所以需要考虑并发争抢任务确保任务不会被多次执行。 retry_ts time.time() 5 # 5s 后重试 redis . zadd (” delay- queue ”, retry_ts, value) values redis. zrangebyscore (” delay-queue ”, 0 , time.time() , start0 , num1) # 检查 delay-queue 集合中是否有元素的分数小于或等于当前时间戳,拿出第一个0-time.time分数范围# 从消息队列中移除该消息 success redis. zrem (” delay-queue”, value) if success: # 因为有多进程并发的可能 最终只会有一个进程可以抢到消息msg json . loads(value)Redis 的 zrem 方法是多线程多进程争抢任务的关键它的返回值决定了当前实例有没有抢到任务因为方法可能会被多个线程、多个进程调用同一个任务可能会被多个进程、多个线程抢到要通过 zrem 来决定唯一的属主。 redis异步队列 Redis 的 list列表数据结构常用来作为异步消息队列使用 用 rpush 和 lpush操作入队列用 lpop 和 rpop 操作出队列 问题1客户端通过队列的 pop 操作来获取消息然后进行处理。处理完了再接着获取消息再进行处理。如此循环这便是作为队列消费者的客户端的生命周期。可是如果队列空了客户端就会陷入 pop 的死循环不停地 pop没有数据接着再 pop还没有数据。这就是浪费生命的空轮询。空轮询不但拉高了客户端的CPU 消耗 Redis 的 QPS 也会被拉高如果这样空轮询的客户端有几十个 Redis 的慢查询可能会显著增多。 解决1通常我们使用 sleep 来解决这个问题让线程睡一会睡个 1s 就可以了。不但客户端的 CPU 消耗能降下来 Redis 的 QPS 也降下来了 。 问题2但是又有个问题那就是睡眠会导致消息的延迟增大。如果只有 1 个消费者那么这个延迟就是 ls。如果有多个消费者这个延迟会有所下降因为每个消费者的睡眠时间是岔开的。 有什么办法能显著降低延迟呢 解决2睡眠的时间缩短点。这种方法当然可以不过有没有更好的解决方案呢当然也有那就是 blpop/brpop 。这两个指令的前缀字符 b 代表的是 blocking也就是阻塞读。阻塞读在队列没有数据的时候会立即进入休眠状态一旦数据到来则立刻醒过来。消息的延迟几乎为零。用blpop/brpop 替代前面的 lpop/rpop就完美解决了上面的问题。 位图 在我们平时的开发过程中会有一些 bool 型数据需要存取比如用户 年的签到记录签了是 1 没签是 0要记录 365 天。如果使用普通的 key/value每个用户要记录 365 个当用户数上亿的时候需要的存储空间是惊人的。为了解决这个问题 Redis 提供了位图数据结构这样每天的签到记录只占据一个位 365 天就是 365 个位 46 个字节一个稍长一点的字符串就可以完全容纳下这就大大节约了存储空间。位图的最小单位是比特 bit 每个 bit 的取值只能是 0 或 1,位图不是特殊的数据结构它的内容其实就是普通的字符串也就是 byte 数组。 bin(ord (h)) 0b1101000 127.0.0.1:6379 setbit s 1 1 (integer) 0 127.0.0.1:6379 setbit s 2 1 (integer) 0 127.0.0.1:6379 setbit s 4 1 (integer) 0 127.0.0.1:6379 get s h 通过 bitcount 统计用户一共签到了多少天通过 bitpos 指令查找用户从哪天开始第一次签到 。如果指定了范围参数 start end 就可以统计在某个时间范围内用户签到了多少天用户自某天以后的哪天开始签到。 遗憾的是 start 和 end 参数是字节索引也就是说指定的位范围必须是 8 的倍 数而不能任意指定。这很奇怪老钱不能理解 Antirez 为什么要这样设计。因为 这个设计 我们无法直接计算某个月内用户签到了多少天而必须将这个月所覆盖 的字节内容全部取出来 getrange 可以取出字符串的子串然后在内存里进行统计 这非常烦琐。 127.0.0.1 : 6379 set w hello OK 127.0.0.1 : 6379 bitcount w (integer) 21 127.0.0.1 : 6379 bitcount w 0 0 第一个字符 中 1 的位数 (integer ) 3 127.0.0.1 : 6379 bitcount w 0 1 前两个字符 中 l 的位数 (integer ) 7Hyperloglog UV 是 “Unique Visitors” 的缩写中文意思是 “独立访客”。在网站分析中UV 指的是在一定时间范围内通常是一天访问网站的不重复的访客数量。如果一个人在同一天内多次访问同一个网站他仍然只被计算为一个 UV也就是同一个用户一天之内的多次访问请求只能计数一次。 一个简单的方案就是为每一个页面设置一个独立的 set 集合来存储所有当天访问过此页面的用户ID。当一个请求过来时我们使用 sadd 将用户 ID 塞进去就可以了。通过 scard 可以取出这个集合的大小这个数字就是这个页面的 UV 数据。但是如果页面访问量非常大比如一个爆款页面可能有几千万个 UV就需要一个很大的 set 集合来统计这就非常浪费空间。如果这样的页面很多那所需要的存储空间是惊人的。 r.sadd(page1_set, user_id)HyperLogLog 提供不精确的去重计数方案虽然不精确但是也不是非常离谱标准误差是 0 . 81%, HyperLogLog 提供了两个指令 pfadd 和 pfcount根据字面意思很好理解一个是增加计数一个是获取计数。 pfadd 和 set 集合的 sadd 的用法是一样的来一个用户 ID就将用户 ID 塞进去就是。pfcount 和 scard 的用法是一样的直接获取计数值。 127.0.0.1 : 6379 pfadd codehole user1 (integer) 1 127.0.0.1 : 6379 pfcount codehole (integer) 1 127.0.0.1 :6379 pfadd codehole user2 (integer) 1 127.0.0.1 :6379 pfcount codehole (integer) 2pfmerge用于将多个 pf 计数值累加在一起形成一个新的 pf 值。 注意事项 HyperLogLog 这个数据结构不是免费的 。这倒不是说使用这个数据结构要花钱 而是因为它需要占据 12KB的存储空间所以不适合统计单个用户相关的数据。如果用户有上亿个可以算算这个空间成本是非常惊人的。但是相比 set 存储方案HyperLogLog 所使用的空间那就只能算九牛之一毛了 。不过你也不必过于担心因为 Redis 对 HyperLogLog 的存储进行了优化在计数比较小时它的存储空间采用稀疏矩阵存储空间占用很小仅仅在计数慢慢变大、稀疏矩阵占用空间渐渐超过了阎值时才会一次性转变成稠密矩阵才会占用 12KB的空间。 一句话收尾HyperLogLog 能解决很多精确度要求不高的统计问题。 布隆过滤器 HyperLogLog 数据结构来进行估数。但是如果我们想知道某一个值是不是已经在 HyperLogLog 结构里面了它就无能为力了。 讲个使用场景比如我们在看抖音时它会给我们不停地推荐新的内容而它每次推荐时都要去重以去掉那些我们已经看过的内容。那么问题来了抖音推荐系统是如何实现推送去重的你可能会想到服务器已经记录了用户看过的所有历史记录当推荐系统推送短视频时可以从每个用户的历史记录里进行筛选以过滤掉那些已经存在的记录。问题是当用户量很大、每个用户看过的短视频又很多的情况下使用这种方式推荐系统的去重工作在性能上能跟得上吗 实际上如果历史记录存储在关系数据库里去重就需要频繁地对数据库进行exists 查询当系统并发量很高时数据库是很难扛住压力的。你可能又想到了缓存但是将如此多的历史记录全部缓存起来那得浪费多大存储空间啊而且这个存储空间是随着时间线性增长的就算你撑得住一个月你 能撑得住几年吗但是不缓存的话性能又跟不上这该怎么办 高级数据结构布隆过滤器闪亮登场了它就是专门用来解决这种去重问题的。它在起到去重作用的同时在空间上还能节省 90%以上只是会有一定的误判概率。 当布隆过滤器说某个值存在时这个值可能不存在当它说某个值不存在时那就肯定不存在。打个比方当它说不认识你时肯定就是真的不认识而当它说认识你时却有可能根本没见过你只是因为你的脸跟它认识的某人的脸比较相似某些熟脸的系数组合所以误判以前认识你。布隆过滤器对于已经见过的元素肯定不会误判它只会误判那些没见过的元素 套在上面的使用场景中布隆过滤器能准确过滤掉那些用户已经看过的内容那些用户没有看过的新内容它也会过滤掉极小一部分误判) 但是绝大多数新内容它都能准确识别。这样就可以保证推荐给用户的内容都是无重复的。 Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布 隆过洁、器作为一个插件加载到 Redis Server 中 布隆过洁、器有两个基本指令 bf.add 和 bf.exists 。 bf.add 添加元素 bf.exists 查询 元素是否存在 127 . 0.0.1:6379 bf.add codehole userl (integer) 1 127 . 0 . 0.1:6379 bf.add codehole user2 (integer) 1 127.0.0.1:6379 bf.exists codehole userl (integer) 1 127.0.0.1:6379 bf.exists codehole user2 工 nteger) 1 127.0.0.1:6379 bf.exists codehole user3 (integer) 0向布隆过滤器中添加 key 时会使用多个 hash 函数对 key 进行 hash算得一个整数索引值然后对位数组长度进行取模运算得到一个位置每个 hash 函数都会算得一个不同的位置 ,再把位数组的这几个位置都置为 1 就完成了 add 操作。 向布隆过滤器询问 key 是否存在时跟 add 一样也会把 hash 的几个位置都算出来看看位数组中这几个位置是否都为 1 只要有一个位为 0那么说明布隆过滤器中这个 key 不存在。如果这几个位置都是 1 并不能说明这个 key 就一定存在只是极有可能存在因为这些位被置为 1 可能是因为其他的 key 存在所致。如果这个位数组比较稀疏判断正确的概率就会很大如果这个位数组比较拥挤判断正确的概率就会降低。 布隆过滤器器可以显著降低数据库的 IO 请求数量 。 当用户来查询某个 row 时可以先通过内存中的布隆过滤器过滤掉大量不存在的 row 请求然后再去磁盘进行查询 。 免费的在线布隆空间占用计算器地址是 htφs收risives . github.io/bloomcalculator 。 GeoHash 我们可以使用 Redis 来实现类似单车的“附近的单车”、美团“附近的餐馆 ” 这样的功能了。 地图元素的位置数据使用二维的经纬度表示。 在使用 Redis 进行 Geo 查询时我们要时刻想到它的内部结构实际上只是一个zset (skiplist。通过 zset 的 score 排序就可以得到坐标附近的其他元素实际情况要复杂一些不过这样理解足够了通过将 score 还原成坐标值就可以得到元素的原始坐标。 127.0.0.1:6379 geoadd company 116.48105 39.996794 juejin (integer) 1 127.0.0.1:6379 geoadd company 116.514203 39.905409 ireader (integer) 1geodist 指令可以用来计算两个元素之间的距离携带集合名称、两个名称和距离单位。 127.0.0.1 : 6379 geodist company juejin ireader km # km是千米 10.5501georadiusbymember 指令是最为关键的指令之一 它可以用来查询指定元素附近 的其他元素 它的参数非常复杂 # 范围 20 公里以内最多 3 个元素按距离正排它不会排除自身 127.0.0.1:6379 georadiusbymember company ireader 20 km count 3 asc 1)”ireader” 2)”juejin” 3)”meituan根据坐标值来查询附近的元素的指令 georadius这个指令更加有用它可以根据用户的定位来计算“附近的车”“附近的餐馆”等。它的参数和 georadiusbyrnernber 基本一致唯一的差别是将目标元素改成经纬度坐标值。 127.0.0.1:6379 georadius company 116.514202 39.905409 20 km withdist count 3 asc 1) 1)”ireader2)” 0.000” 2) 1)” juejin”2) ” 10.5501 ” 3) 1) ” meituan ”2) “ 11.5748”scan 命令 在平时线上 Redis 维护工作中有时候需要从 Redis 实例的成千上万个 key 中找出特定前缀的 key 列表来手动处理数据可能是修改它的值也可能是删除 key 。这里就有 个问题 如何从海量的 key 中找出满足特定前缀的 key 列表 Redis 提供了 一个简单粗暴的指令 keys 用来列出所有满足特定正则字符串规则的 key 。 127.0 . 0 . 1:6379 set codeholel a OK 127.0 . 0 . 1:6379 set codehole2 b OK 127.0 . 0 . 1:6379 set codehole3 c OK 127.0.0.1:6379 keys * 1) ” codeholel ” 2 ) ” code3hole 3 ) ” codehole3 ”缺点: 1.没有 offset 、limit 参数一次性吐出所有满足条件的 key 万一实例中有几百万个 key 满足条件满屏的字符串刷屏没有尽头时你就知道难受了。 2.keys 算法是遍历算法复杂度是 O(n). 因为 Redis 是单线程程序顺序执行所有指令其他指令必须等到当前的 keys指令执行完了才可以继续。 scan提供 limit 参数可以控制每次返回结果的最大条数(控制查询的槽数量)。复杂度虽然也是O(n)但它是通过游标分步进行的不会阻塞线程 。 # scan 0 游标从0开始, limit 1000 表示限定服务器单次遍历的字典槽位数量。 127.0.0.1:6379 scan 0 match key99* count 1000 1 ) ” 13976” # 下次scan就从13976开始,直到返回的是0. 游标值不为零意昧着遍历还没结束。 2) 1)” key9911 ”2)” key9974 ”3) ” key9994 ”4” key991 。 ”5) ” key9907 ”6) ” key9989 ”7) ” key9971 ”8) ” key99 ”9) ” key9966 ”10)” key992 ”11)” key9903 ”12) ” key9905 ”字典结构 在 Redis 中所有的 key 都存储在一个大字典中这个字典的结构和 Java 中的HashMap 一样它是一维数组数组里每个元素下挂着链表结构。 scan 指令返回的游标就是第一维数组的位置索引我们将这个位置索引称为槽 slot 。如果不考虑字典的扩容缩容直接按数组下标按个遍历就行了。 limit 参数就表示需要遍历的槽位数之所以返回的结果可多可少是因为每个槽位上挂的链表元素不同, 有些槽位可能是空的。每一次遍历都会将 limit 数量的槽位上挂接的所有链表元素进行模式匹配过滤后一次性返回给客户端. scan 的遍历顺序非常特别。它不是从第一维数组的第 0 位 直遍历到末尾而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。 rehash扩容 Java 的 HashMap 在扩容时会一次性将旧数组下挂接的元素全部转移到新数组下面 。如果 HashMap 中元素特别多线程就会出现卡顿现象 。 Redis 为了解决这个问题采用 渐进式 rehash 。 它会同时保留旧数组和新数组然后在定时任务中以及后续对 hash 的指令操作中渐渐地将旧数组中挂的元素迁移到新数组上。这意昧着要操作处于 rehash 中的字典需要同时访问新旧两个数组结构。如果在旧数组下面找不到元素还需要去新数组下面寻找。 大 key 扫描 redis - cli - h 127.0.0.1 - p 7001 -bigkeys第二章 原理篇 线程IO模型 Redis、Node.js、Nginx 都是单线程程序 Redis 会将每个客户端套接字都关联一个指令队列客户端发出的指令通过指令队列来顺序处理先到先服务。 Redis 同样也会为每个客户端套接字关联一个响应队列Redis 服务器通过响应队列来将指令的返回结果回复给客户端。 Redis 的定时任务会记录在一个被称为最小堆的数据结构中。在这个堆中最快要执行的任务排在堆的最上方。 RESP 序列化协议 Redis 协议将传输的结构数据分为 5 种最小单元类型单元结束时统一加上回车换行符号 \r\n。 1.单行字符串以“”符号开头。 2.多行字符串以“$”符号开头后跟字符串长度 。 3.整数值以“”符号开头后跟整数的字符串形式 。 4.错误消息以“”符号开头。 5.数组以“”号开头后跟数组的长度 。 客户端向服务器发送的指令只有一种格式多行字符串数组。比如一个简单的set 指令 set author codehole 会被序列化成下面的字符串 。 *3\r\n$3\r\nset\r\n$6\r\nauthor\r\n$8\r\ncodehole\r\n控制台输出: *3 $3 set $6 author $8 Codehole服务器向客户端回复的响应要支持多种数据结构所以消息响应在结构上要复杂不少不过再复杂的响应消息也是以上 5 种基本类型的组合。 持久化 Redis 的持久化机制有两种第一种是快照第二种是 AOF 日志。 快照是一次全量备份 AOF 日志是连续的增量备份。 快照是内存数据的二进制序列化形式在存储上非常紧凑。AOF 日志记录的是内存数据修改的指令记录文本。 单个 Redis 的内存不宜过大内存太大会导致 rdb 文件过大进一步导致主从同步时全量同步时间过长在实例重启恢复时也会消耗很长的数据加载时间 为了不阻塞线上的业务 Redis 需要一边持久化一边响应客户端的请求。持久化的同时内存数据结构还在改变比如一个大型的 hash 字典正在持久化结果一个请求过来把它给删掉了可是还没持久化完呢这该怎么办呢 Redis 使用操作系统的多进程Copy On Write 机制来实现快照持久化。 Redis 在持久化时会调用 glibc 的函数 fork 产生一个子进程快照持久化完全交给子进程来处理父进程继续处理客户端请求。子进程刚刚产生时它和父进程共享内存里面的代码段和数据段。这是 Linux 操作系统的机制为了节约内存资源所以尽可能让它们共享。在进程分离的瞬间内存的增长几乎没有明显变化。 fork 函数会在父子进程同时返回在父进程里返回子进程的 pid在子进程里返回零。 子进程做数据持久化不会修改现有的内存数据结构它只是对数据结构进行遍历读取然后序列化写到碰盘中。但是父进程不一样它必须持续服务客户端请求然后对内存数据结构进行不间断的修改。 父进程不一样持续服务客户端请求然后对内存数据结构进行修改。这个时候就会使用操作系统的 cow 机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成当父进程对其中一个页面的数据进行修改时会将被共享的页面复制一份分离出来然后对这个复制的页面进行修改每个页面的大小只有 4KB。子进程因为数据没有变化它能看到的内存里的数据在进程产生的一瞬间就凝固不会变化这也是为什么 Redis 的持久化叫“快照”的原因。接下来子进程就可以非常安心地遍历数据进行序列化写磁盘了。 redis AOF先执行指令才将曰志存盘。 而leveldb 、hbase 等存储引擎它们都是先存储曰志再做逻辑处理。 问题当程序对 AOF 日志文件进行写操作时实际上是将内容写到了内核为文件描述符分配的一个内存缓存中然后内核会异步将脏数据刷回磁盘的。这就意昧着如果机器突然宕机 AOF 日志内容可能还没有来得及完全刷到磁盘中这个时候就会出现日志丢失。 解决Linux 的 glibc 提供了 fsync(int 但函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要 Redis 进程实时调用 fsync 函数就可以保证 AOF 日志不丢失。但是fsync 是一个磁盘 IO 操作它很慢 所以在生产环境的服务器中 Redis 通常是每隔 1s 左右执行一次 fsync 操作 Redis 的主节点不会进行持久化操作持久化操作主要在从节点进行。从节点是备份节点没有来自客户端请求的压力。 管道 管道技术本质上是由客户端提供的跟服务器没有什么直接的关系。 客户端请求–得到响应 --请求 – 得到响应其实就是写-读-写-读 调整读写顺序改成写-写-读-读两个连续的写操作和两个连续的读操作总共只会花费一次网络来田这就是管道操作的本质。客户端通过对管道中的指令列表改变读写顺序就可以大幅节省 IO 时间。管道中指令越多效果越好。 服务器根本没有任何区别对待还是收到一条消息、执行一条消息、回复一条消息的正常流程。 我们以为write 操作是要等到对方收到消息后才会返回。 实际上write 操作只负责将数据写到本地操作系统内核的发送缓冲区中然后就返回了。剩下的事交给操作系统内核异步将数据送到目标机器。但是如果发送缓冲满了那么就需要等待缓冲空出空闲空间来这个就是写操作 IO 操作的真正耗时。 我们以为read 操作是从目标机器拉取数据 实际上read 操作只负责将数据从本地操作系统内核的接收缓冲区中取出。但是如果缓冲是空的那么就需要等待数据到来这个就是 read 操作 IO 操作的真正耗时。 所以对于 value redis.get(key这样一个简单的请求来说 write 操作几乎没有耗时直接写到发送缓冲中就返回而 read 就比较耗时了因为它要等待消息经过网络路由到目标机器处理后的响应消息再回送到当前的内核读缓冲才可以返回这才是一个网络来回的真正开销。 而对于管道来说连续的 write 操作根本就没有耗时之后第一个 read 操作会等待一个网络的来回开销然后所有的响应消息就都已经送回到内核的读缓冲了后续的 read 操作直接就可以从缓冲中拿到结果瞬间就返回了。 事务 每个事务的操作指令都有 begin 、commit 、rollback, 对应到redis里就是 multi 、exec 、discard。 multi OKincr books QUEUEDincr books QUEUEDexec (integer) 1 (integer) 2所有的指令在 exec 之前不执行而是缓存在服务器的一个事务队列中服务器一旦收到exec 指令才开始执行整个事务队列执行完毕后一次性返回所有指令的运行结果。 事务在遇到指令执行失败后后面的指令还会继续执行。所以 Redis 的事务不具备原子性仅仅是满足了事务的隔离性中的串行化–当前执行的事务有着不被其他事务打断的权利。 Redis 事务在发送每个指令到事务缓存队列时都要经过一次网络读写当一个事务内部的指令较多时需要的网络 IO 时间也会线性增长所以通常 Redis的客户端在执行事务时都会结合 pipeline 一起使用这样可以将多次 IO 操作压缩为单次 IO 操作。比如我们在使用 Python 的 Redis 客户端执行事务时是要强制使用pipeline 的 。 pipe redis.pipeline(transactiontrue) pipe.multi () pipe.incr (”books ” ) pipe.incr (” books ” ) values pipe.execute()分布式锁是一种悲观锁watch机制是一种乐观锁 PubSub 消息多播允许生产者只生产一次消息由中间件负责将消息复制到多个消息队列每个消息队列由相应的消费组进行消费。redis单独使用了个模块来支持消息多播这个模块的名字叫作 PubSub。 内存管理 Redis 如果使用 32bit 进行编译内部所有数据结构所使用的指针空间占用会少一半如果你的 Redis 使用内存不超过 4GB可以考虑使用 32bit 进行编译能够节约大量内存。 如果 Redis 内部管理的集合数据结构很小它会使用紧凑存储形式压缩存储。 如果当前 Redis 内存有 10GB 当删除了 1GB 的 key 后再去观察内存会发现内存变化不会太大。原因是操作系统是以页为单位来回收内存的 Redis 虽然删除了 1GB 的 key但是这些 key 分散到了很多页面中这个页上只要还有一个 key 在使用那么它就不能立即被回收。如果执行 flushdb 内存会立刻被回收了原因是所有的 key 都被干掉了。 Redis 虽然无法保证立即回收已经删除的 key 的内存但是它会重新使用那些尚未回收的空闲内存。 Redis 为 了保持自身结构的简单性在内存分配方面直接做了甩手掌柜将内存分配的细节丢给了第三方内存分配库去实现默认使用facebook的 jemalloc。 第三章 集群篇 CAP C : Consistent 一致性 A : Availability 可用性 P : Partition tolerance 分区容忍性 分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的 这意味着必然会有网络断开的风险 这个网络断开的场景的专业词汇叫作网络分区 。 在网络分区发生时两个分布式节点之间无法进行通信我们对一个节点进行的修改操作将无法同步到另外一个节点所以数据的一致性将无法满足 。 如果我们在在网络分区发生时暂停节点服务不再提供修改数据的功能那可用性将无法满足。 用一句话概括 CAP 原理就是 当网络分区发生时 一致性和可用性两难全 。 当客户端在 Redis 的主节点修改了数据后立即返回 即使在主从网络断开的情况下主节点依旧可以正常对外提供修改服务 所以 Redis 满足可用性 。 Redis 保证最终一致性 Redis 的主从数据是异步同步的从节点会努力追赶主节点 最终从节点的状态会和主节点的状态保持一致。 主从同步 Redis 同步的是指令流主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中然后异步将 buffer 中的指令同步到从节点从节点一边执行同步的指令流来达到和主节点一样的状态一边向主节点反馈自己同步到哪里了偏移量〉 。因为内存的 buffer 是有限的所以 Redis 主节点不能将所有的指令都记录在内存 buffer中 。 Redis 的复制内存 buffer是一个定长的环形数组如果数组内容满了就会从头开始覆盖前面的内容 。 如果因为网络状况不好从节点在短时间内无法和主节点进行同步那么当网络状况恢复肘 Redis 的主节点中那些没有同步的指令在buffer 中有可能已经被后续的指令覆盖掉了从节点将无法直接通过指令流来进行同步这个时候就需要用到更加复杂的同步机制一一快照同步。 快照同步 快照同步是个非常耗费资源的操作它首先需要在主节点上进行一次 bgsave将当前内存的数据全部快照到磁盘文件中然后再将快照文件的内容全部传送到从节点 。 从节点将快照文件接受完毕后立即执行一次全量加载加载之前先要将当前内存的数据清空加载完毕后通知主节点继续进行增量同步 。 在整个快照同步进行的过程中主节点的复制 buffer 还在不停地往前移动如果快照同步的时间过长或者复制 buffer 太小都会导致同步期间的增量指令在复制buffer 中被覆盖这样就会导致快照同步完成后无法进行增量复制然后会再次发起快照同步如此极有可能会陷入快照同步的死循环。 当从节点刚刚加入到集群时它必须先进行一次快照同步同步完成后再继续进行增量同步。 Redis 支持无盘复制。所谓无盘复制是指主服务器直接通过套接字将快照内容发送到从节点生成快照是一个遍历的过程主节点会一边遍历内存一边将序列化的内容发送到从节点从节点还是跟之前一样先将接收到的内容存储到磁盘文件中再进行一次性加载。 wait 指令可以让Redis的异步复制变成同步复制确保系统的强一致性。 wait 1 0 # 等待 wait 指令之前的所有写操作同步到 N 个从节点 # 最多等待时间 t。 如果时间 tO 表示无限等待直到 N 个从节点同步完成 。哨兵 客户端来连接集群时会首先连接 Sentinel 通过 Sentinel来查询主节点的地址然后再连接主节点进行数据交互。当主节点发生故障时客户端会重新向 Sentinel 要地址 Sentinel 会将最新的主节点地址告诉客户端这样应用程序将无须重启即可自动完成节点切换 。 Redis 主从采用异步复制意昧着当主节点挂掉时从节点可能没有收到全部的同步消息这部分未同步的消息就丢失了。Sentinel 无法保证消息完全不丢失但是也能尽量保证消息少丢失。 它有两个选项可以限制主从延迟过大。 # 主节点必须至少有一个从节点在进行正常复制否则就停止对外写服务丧失可用性。 min-slaves-to-write 1 # 怎么定义正常复制和异常复制第二个参数控制单位是秒。如果在 10s 内没有收到从节点反馈就意味着从节点同步不正常 min-slaves-max- lag 10Sentinel 的默认端口是 26379 从地址有多个Redis 客户端默认采用轮询方案 问题当 Sentinel 进行主从切换时客户端如何知道地址变更了 解决redis-py 在建立连接的时候进行了主节点地址变更判断。连接池建立新连接肘会去查询主节点地址然后跟内存中的主节点地址进行比对如果变更了就断开所有连接重新使用新地址建立新连接。如果是旧的主节点挂掉了那么所有正在使用的连接都会被关闭然后在重连时就会用上新地址。 问题2如果是 Sentinel 主动进行主从切换的但主节点并没有挂掉而之前的主节点连接已经建立了且在使用中没有新连接需要建立那么这个连接是不是一直切换不了 解决2在处理命令的时候捕获了一个特殊的异常 ReadOnlyError在这个异常里将所有的旧连接 全部关闭了后续指令就会进行重连。主从切换后之前的主节点被降级为从节点所有的修改性的指令都会抛出ReadonlyError。如果没有修改性指令虽然连接不会得到切换但是数据不会被破坏 所以即使不切换也没关系。 Cluster Redis Cluster是去中心化的每个节点负责整个集群的一部分数据每个节点负责的数据多少可能不一样它们之间通过一种特殊的二进制协议交互集群信息。 Redis Cluster 将所有数据划分为 16384 个槽位每个节点负责其中一 部分槽位。槽位的信息存储于每个节点中当 Redis Cluster 的客户端来连接集群时也会得到并缓存一份集群的槽位配置信息 。这样当客户端要查找某个 key 时可以直接定位到目标节点。 另外 Redis Cluster 的每个节点会将集群的配置信息持久化到配置文件中所以必须确保配置文件是可写的 而且尽量不要依靠人工修改配置文件。 Redis Cluster 默认会对 key 值使用哈希算法进行 hash得到一个整数值然后用这个整数值对 16 384 进行取模来得到具体槽位。 Redis Cluster 允许用户强制把某个 key 挂在特定槽位上。 如果客户端向一个错误的节点发出了指令后该节点会发现指令的 key 所在的槽位并不归自己管理 这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址告诉客户端去连接这个节点以获取数据。客户端在收到指令后 要纠正本地的槽位映射表后续所有 key将使用新的槽位映射表。 拓展篇 Stream 支持多播的可持久化消息队列借鉴了 Kafka 的设计。 它有一个消息链表将所有加入的消息都串起来 每个消息都有个唯一的 ID 和对应的内容消息 ID 可以由服务器自动生成也可以客户端自己指定但是形式必须是 “整数 整数” 而且后面加入的消息的 ID 必须要大于前面的消息 ID 。。 消息是持久化的 Redis 重启后内容还在。每个 Stream 都可以挂多个消费组 Consumer Group 每个消费组会有个游标 last_delivered_id 在 Stream 数组之上往前移动表示当前消费组已经消费到哪条消息 了。每个消费组的状态都是独立的相互不受影响。 增删改查等指令说明如下。 xadd 向 S仕earn 追加消息。xdel 从 Stream 中删除消息这里的删除仅仅是设置标志位不影响消息总长度。 3 . xrange获取 S位earn 中的消息列表会自动过滤已经删除的消息。xlen 获取 Stream 消息长度。de 删除整个 Stream 消息列表中的所有消息。 # *号表示服务器自动生成ID后面顺序跟着 key、value # 名字叫 laoqian 年龄 30 岁 127.0.0.1:6379 xadd codehole * name laoqian age 30 1527849609889-0 # 生成的消息 ID 127.0.0.1:6379 xadd codehole *name xiaoyu age 29 1527849629172-0过期策略 Redis 会将每个设置了过期时间的 key 放入一个独立的字典中。 1、定时策略遍历这个字典来删除到期的 key。 2、使用惰性策略来删除过期的key。所谓惰性策略就是在客户端访问这个 key 的时候 Redis 对 key 的过期时间进行检查如果过期了就立即删除。 定时策略Redis 默认每秒进行 10 次过期扫描过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略从过期字典中随机选出 20 个 key。删除这 20 个 key 中已经过期的 key 。 如果过期的 key 的比例超过 1/4 那就重复从随机选20个key步骤开始。同时为了保证过期扫描不会出现循环过度导致结程卡死的现象算法还增加了扫描时间的上限默认不会超过 25ms 。 问题1当客户端请求到来时服务器如果正好进入过期扫描状态客户端的请求将会等待至少25ms 后才会进行处理如果客户端将超时时间设置得比较短比如10ms 那么就会出现大量的链接因为超时而关闭 业务端就会出现很多异常而且这时还无法从 Redis 的 slowlog 中看到慢查询记录因为慢查询指的是逻辑处理过程慢不包含等待时间。 解决1所以业务开发人员一定要注意过期时间如果有大批量的 key 过期要给过期时间设置一个随机范围而不能全部在同一时间过期。 从节点过期策略 从节点不会进行过期扫描从节点对过期的处理是被动的。主节点在 key 到期时会在 AOF 文件里增加一条 del 指令同步到所有的从节点从节点通过执行这条 del 指令来删除过期的 key 。 因为指令同步是异步进行的所以如果主节点过期的 key 的 del 指令没有及时同步到从节点的话就会出现主从数据的不一致主节点没有的数据在从节点里还存在比如上一节的集群环境分布式锁的算法漏洞就是因为这个同步延迟产生的。 淘汰策略 1.noeviction 不会继续服务写请求del 请求可以继续服务读请求可以继续进行。这是默认的淘汰策略。 2.volatile-lru 从设置了过期时间的 key中最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰这样可以保证需要持久化的数据不会突然丢失。 3.volatile-ttl跟上面几乎一样不过淘汰的策略不是 LRU而是比较 key 的剩余寿命时的值ttl 越小越优先被淘汰。 4.volatile-random跟上面几乎一样不过淘汰的 key 是过期 key 集合中随机的key 。 5.allkeys-lru 区别于 volatile-lru这个策略要淘汰的 key 对象是全体的 key 集合而不只是过期的 key 集合。 6.allkeys-random跟上面几乎一样不过淘汰的 key 是随机的 key 。 volatile-xxx 策略只会针对带过期时间的 key 进行淘汰 allkeys - xxx 策略会对所有的 key 进行淘汰。如果你只是拿 Redis 做缓存那么应该使用 allkeys-xxx 策略客户端写缓存时不必携带过期时间。如果你还想同时使用 Redis 的持久化功能那就使用volatile-xxx 策略这样可以保留没有设置过期时间的 key 它们是永久的 key 不会被 LRU 算法淘汰。 近似LRU Redis 使用的是近似 LRU 算法它跟 LRU 算法还不太一样。之所以不使用LRU 算法是因为其需要消耗大量的额外内存需要对现有的数据结构进行较大的改造。 近似 LRU 算法很简单在现有数据结构的基础上使用随机采样法来淘汰元素能达到和 LRU 算法非常近似的效果。 Redis 为实现近似 LRU 算法给每个 key 增加了一个额外的小字段这个字段的长度是 24 个 bit也就是最后一次被访问的时间截。 LRU 淘汰只有懒惰处理。当 Redis 执行写操作时发现内存超出 rnaxmemory就会执行一次 LRU 淘汰算法。这个算法也很简单就是随机采样出 5 该数量可以设置个key然后淘汰掉最旧的key如果淘汰后内存还是超出 maxmemory那就继续随机采样淘汰直到内存低于 maxmemory为止。 异步线程 一直以来我们都知道 Redis 是单线程的单线程为 Redis 带来了代码的简洁性和丰富多样的数据结构。不过 Redis 内部实际上并不是只有一个主线程它还有几个异步线程专门用来处理一些耗时的操作。 问题删除指令 del 会直接释放对象的内存大部分情况下这个指令非常快没有明显延迟。不过如果被删除的 key 是一个非常大的对象比如一个包含了上千万个元素的 hash那么删除操作就会导致单线程卡顿。 解决Redis 为了解决这个卡顿问题在 4.0 版本里引入了 unlink 指令它能对删除操作进行懒处理丢给后台线程来异步回收内存。会将这个 key 的内存回收操作包装成一个任务塞进异步任务队列后台线程会从这个异步队列中取任务。 unlink key OK会不会出现多个线程同时并发修改数据结构的情况存在 关于这一点打个比方。可以将整个 Redis 内存里面所有有效的数据想象成一棵大树。当 unlink 指令发出时它只是把大树中的一个树枝剪断了然后扔到旁边的火堆异步线程池里焚烧。在树枝离开大树的瞬间它就再也无法被主线程中的其他指令访问到了因为主线程只会沿着这棵大树来访问。 Redis 提供了 flushdb 和 flushall 指令用来清空数据库这也是极其缓慢的操作。 Redis 4.0 同样给这两个指令带来了异步化在指令后面增加 async 参数就可以将整棵大树连根拔起扔给后台线程慢慢焚烧 flushall async OKRedis 需要每秒 1该数量可设置〉次同步 AOF 曰志到磁盘确保消息尽量不丢失需要调用 sync 函数这个操作比较耗时会导致主线程的效率下降所以 Redis也将这个操作移到异步线程来完成。执行 AOF Sync 操作的线程是一个独立的异步线程和前面的懒惰删除线程不是一个线程同样它也有一个属于自己的任务队列队列里只用来存放 AOF Sync 任务 。 redis安全通信 - spiped 想象这样一个应用场景公司有两个机房。因为一个紧急需求需要跨机房读取 Redis 数据。应用部署在 A 机房存储部署在 B 机房。如果使用普通 tcp 直接访问因为跨机房所以传输数据会暴露在公网上客户端服务器交互的数据存在被窃昕的风险。 Redis 本身并不支持 SSL 安全链接 不过有了 SSL 代理软件我们可以让通信数据得到加密就好像 Redis 穿上了一层隐身外套一样 spiped 就是这样的一款 SSL 代理软件它是 Redis 官方推荐的代理软件。 spiped 会在客户端和服务器各启动一个 spiped 进程左边的 spiped 进程负责接受来自 Redis Client 发送过来的请求数据 加密后传送到右边的 spiped 进程 。 右边的 spiped 进程将接收到的数据解密后传递到 Redis Server。然后 Redis Server 再走一个反向的流程将响应回复给 Redis Client 。 spiped 进程需要成对出现相互之间需要使用相同的共享密钥来加密消息。每一个 spiped 进程都会有个监听端口 (server socket用来接收数据同时还会作为一个客户端socket client将数据转发到目标地址。
http://www.hkea.cn/news/14352047/

相关文章:

  • seo赚钱培训佛山推广优化公司
  • 做网站彩票代理多少钱啊好搜搜索引擎
  • 吉林省长春市建设局网站石家庄市工程勘察设计咨询业协会
  • 职业学校网站建设方案做啥英文网站赚钱
  • 注册网站平台要多少钱网站风格细节
  • 网站维护知识成都专业网站制作建设
  • 公司直招的招聘网站c#网站开发模板
  • 华为手机网站建设策划方案论文网站建设与管理总结
  • 南通网站排名优化报价说明电子商务网站的建设流程
  • 整站wordpress下载商城类网站建设的服务器选择
  • 商城网站 运营去柬埔寨做网站是传销吗
  • 网站页面分析百度网页版入口官网
  • 宁波网站建设h5网站建设培训珠海
  • 专门做进口产品的网站6WordPress注册小工具
  • 网站定制公司蒙特抖音代运营收费价格表
  • 建设网站的机构网站建设济南
  • 做音频的网站linux中怎么打开wordpress
  • wordpress用户注册不通过邮箱验证优化器
  • 门户网站建设管理工作小型网站设计及建设
  • 电视剧在线观看完整版免费网站asp网站域名授权
  • 扬州市规划建设局网站杭州哪些做网站公司
  • 档案网站建设思考青岛官网优化推广
  • 手机网站关键词排名查询用redis加速wordpress
  • 麻江网站建设网络设计初步方案包括
  • 东营市做网站的公司中国建筑土木建设有限公司网站
  • 长春网站设计公司网站正在建设中mp4
  • 新彊生产建设兵团网站asp网站有的打不开
  • 怎么做淘宝店网站收录公司简介制作
  • 网站备案信息核验单怎么百度推广有哪些售后服务
  • 微软网站设计中企建设网站