## 最小存储单元SDS结构体 ----- 在redis里面是采用SDS(simple dynamic string)来封装char[]的,这个也是redis存储的最小单元。 ``` struct __attribute__ ((__packed__)) sdshdr8 { uint8_t len; /* used */ uint8_t alloc; /* excluding the header and null terminator */ unsigned char flags; /* 3 lsb of type, 5 unused bits */ char buf[]; }; ``` - len: 表示字符串的真正长度(不包含NULL结束符在内)。 - alloc: 表示字符串的最大容量(不包含最后多余的那个字节)。 - flags: 总是占用一个字节。其中的最低3个bit用来表示header的类型。header的类型共有5种,在sds.h中有常量定义。 - buf[]:存放元素的坑,不一定和元素的实际个数相等 ## 常用数据结构 --- - String:最基本的数据类型,二进制安全的字符串,最大512M - List: 双向队列,做队列使用的话,常用的也就四个:LPOP,LPUSH,RPOP,RPUSH - Set/Hash: HashTable,无序的字符串集合,不存在重复的元素。 - SortedSet: 在CURD的分析上都是Log(N)的复杂度,可以与平衡二叉树媲美,它就是1987年才出来的新型高效数据结构“跳跃表(SkipList)”,SkipList牛逼的地方在于跳出了树模型的思维,用多层链表的模式构造了Log(N)的时间复杂度,层的高度增加与否,采用随机数的模式. ## 发布/订阅模式 --- ``` SUBSCRIBE channel [channel ...] 订阅给定的一个或多个频道的信息。 ``` ``` PUBLISH channel message 将信息 message 发送到指定的频道 channel 。 ``` ``` PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道。 每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类。 ``` ## 单线程的 Redis 为什么这么快 --- - 纯内存操作 - 单线程操作,避免了频繁的上下文切换 - 采用了非阻塞 I/O 多路复用机制,封装了各个平台的网络接口 ``` #ifdef HAVE_EVPORT #include "ae_evport.c" #else #ifdef HAVE_EPOLL #include "ae_epoll.c" #else #ifdef HAVE_KQUEUE #include "ae_kqueue.c" #else #include "ae_select.c" #endif #endif #endif ``` ## 8种淘汰算法 --- - noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。 - allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的Key。(推荐使用,目前项目在用这种)(最近最久使用算法) - allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。(应该也没人用吧,你不删最少使用 Key,去随机删) - volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。(不推荐) - volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。(依然不推荐) - volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key优先移除。(不推荐) > volatile-lru -> Evict using approximated LRU among the keys with an expire set. > allkeys-lru -> Evict any key using approximated LRU. > volatile-lfu -> Evict using approximated LFU among the keys with an expire set. > allkeys-lfu -> Evict any key using approximated LFU. > volatile-random -> Remove a random key among the ones with an expire set. > allkeys-random -> Remove a random key, any key. > volatile-ttl -> Remove the key with the nearest expire time (minor TTL) > noeviction -> Don't evict anything, just return an error on write operations. ## 缓存穿透解决方案 --- - 利用互斥锁,缓存失效的时候先去获取锁,获取到锁以后再进行数据库读取,没得到锁就继续休眠。 - 异步更新策略,直接返回成功或者失败,交给异步队列去处理获取数据灌进Redis - 通过布隆过滤器判断key的合法性,不合法直接返回。 ## 缓存雪崩解决方案 --- - 缓存加随机失效时间,避免集体失效 - 双缓存,一个有缓存时间,一个无失效时间,异步更新2个缓存 ## 并发竞争key --- 先抢锁,抢到以后set,带上时间戳,进行时间戳比对,进行set操作 ## Redis-cli操作命令 --- ``` redis-cli -h 192.168.1.216 < /usr/commands.txt redis-cli -h 192.168.1.3 --stat redis-cli -h 192.168.1.216 -r 10 -i 1 INFO | grep used_memory_human ``` ## 提升批处理性能 --- - 管道 pipeline - 批量 batch操作 - lua脚本 ## 哨兵(Sentinel)和复制(Replication) --- - 哨兵Sentinel可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能。 - Replication则是负责让一个Redis服务器可以配备多个备份的服务器。 ## 持久化 --- redis的持久化指的是redis会把内存的中的数据写入到硬盘中,在redis重新启动的时候加载这些数据,从而最大限度的降低缓存丢失带来的影响。 ### 持久化机制:RDB和AOF --- - RDB 默认开启,会按照配置的指定时间将内存中的数据快照到磁盘中,创建一个dump.rdb文件,Redis启动时再恢复到内存中。 Redis会单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。 需要注意的是,每次快照持久化都会将主进程的数据库数据复制一遍,导致内存开销加倍,若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;都会将内存数据完整写入磁盘一次,所以如果数据量大的话,而且写操作频繁,必然会引起大量的磁盘I/O操作,严重影响性能,并且最后一次持久化后的数据可能会丢失; - AOF 以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,Redis启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括flushDB也会执行。 主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。 因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发。 当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。一般情况下,只要使用默认开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要快很多。 开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化。 RDB优点 - RDB是紧凑的二进制文件,适合备份,全量复制等场景RDB恢复数据远快于AOF RDB缺点 - RDB无法实现实时或者秒级持久化新老版本无法兼容RDB格式 AOF优点 - 可以更好地保护数据不丢失appen-only模式写入性能比较高适合做灾难性的误删除紧急恢复 AOF缺点 - 对于同一份文件,AOF文件要比RDB快照大AOF开启后,写的QPS会有所影响,相对于RDB来说写QPS要下降数据库恢复比较慢,不合适做冷备。 ## Redis相比memcached有哪些优势 --- - memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型 - Redis的速度比memcached快很多 - Redis可以持久化其数据 - Redis支持数据的备份,即master-slave模式的数据备份。 ## Redis集群方案 --- - twemproxy - codis,目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点。 - Redis cluster3.0自带的集,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。 ## Redlock分布式锁 --- 安全特性:互斥访问,即永远只有一个 client 能拿到锁 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区 容错性:只要大部分 Redis 节点存活就可以正常提供服务 ``` 在Redis的分布式环境中,我们假设有N个Redis master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。我们确保将在N个实例上使用与在Redis单实例下相同方法获取和释放锁。现在我们假设有5个Redis master节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作: 得到当前的时间,单位毫秒 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态 ``` ## 哈希槽 --- Redis集群没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。 ## 其他 --- - 最大节点个数16384个 - 初始数据库16个 - Redis集群目前无法做数据库选择,默认在0数据库 ## 应用场景 --- ### 秒杀 - 提前预热数据,放入Redis - 商品列表放入Redis List - 商品的详情数据 Redis hash保存,设置过期时间 - 商品的库存数据Redis sorted set保存 - 用户的地址信息Redis set保存 - 订单产生扣库存通过Redis制造分布式锁,库存同步扣除 - 订单产生后发货的数据,产生Redis list,通过消息队列处理 - 秒杀结束后,再把Redis数据和数据库进行同步 ### 排行榜 Mac Mojave系统和开发环境安装笔记 debian9虚拟内存开启交换分区
No Leanote account? Sign up now.