最近一段时间整理了关于一些知识的总结,其中就拿出Redis来说说,其他的整理的有些杂还在梳理,相信不久就会和大家见面,期待ne.......,不废话了,开始!
Redis作为非关系型数据库,终是要涉及到持久化的,毕竟缓存可没落地,很可能丢失的。
Redis持久化主要为:
- RDB全量持久,AOF增量持久:RDB耗时长非实时记录应配合AOF使用,从而避免停机大量丢失数据。
- Redis重启时:RDB重构内存+AOF重放近期指令=完整恢复(2者配合使用)
- AOF可设置记录指令间隔,断电后最多丢间隔时间内记录(为了平衡性能可设置适合间隔)
- RDB原理:父进程创建子进程,子进程进行RDB,父进程仍可读写但写脏的数据与子进程分离,父进程处理请求先开辟内存空间复制内存数据再修改副本数据(脏数据)、子进程在创建时获取到内存数据后就快照不再进行变化,以此数据进行RDB
- RDB优点:父子进程分工性能好,数据集大仍启动快, 缺点:但持久化间隔时间期间-可能丢失数据。
- AOF优点:记录操作命令-安全性高, 缺点:文件大恢复慢、数据集大启动较慢。默认开RDB,当同时开2者时优先AOF
而Redis常常提到单线程,但其实已今非昔比了,单线程确保数据安全同时避免了锁开销,但为了响应更多QPS,其模块已经细化。
- Redis基于内存操作,CPU不是瓶颈、单线程避免切换上下文、多路复用IO提高IO利用率
- Redis6版前:仅网络IO与数据读写是单线程,其他模块支持多线程 ,是Reactor模型
- Redis6版后:因QPS请求较多时,select监听处理时都会阻塞很多请求,网络IO改为多线程 充分利用多核提高处理性,但为解决线程安全问题数据读写仍是单线程。多线程处理网络IO请求-接收与发送、主线程处理内存数据的读写
- Keys扫出指定的key列表因单线程无线程切换则阻塞较短时间、scan无阻塞扫但有重复需要再去重总体耗时长
Redis基础数据类型有很多,其中挑些重点谈谈
Redis结构(字符串最大512M): string=k v、 list=k、 set=k、 hash=k v、 zset=权值 k
- 基本数据类型:String、list、Hash、set、Zset、Geospatial、Hyperloglog、bitmap
- Redis由C语言开发,却不用C语言的字符串-而用注解构建的动态字符串SDS
- 因为C语字符串-不记录长度-遍历得到长度O(n)、 而Redis字符串-变量保存长度O(1)。
- 拼接字符串时:C因不计长度-未计算所需内存空间-拼接可能缓存区溢出-数据错乱,而Redis记录长度-拼接时先判断-是否需要扩容。
- 修改字符串内存重分配次数时:C每次创建字符串-就需开辟N+1长空间,1为额外存-空字符。而Redis空间最大化-为避免多次内存分配:分配SDS+多余空间+1,再拼接字符串时-用到多余空间-免分配,1存空字符。
- 惰性空间释放-为避免多余空间浪费:缩减字符串时-不立即回收,再次操作-未用多余空间时-回收多余空间
- 二进制安全:C语言通过遍历字符串-判断到空字符时-认为结尾,后面忽略图片等二进制数据-中间常有空字符,数据易错。而Redis因保存的是字符串长度-不存在判断空字符问题,Redis则常用于-保存小文件二进制数据
- Redis为了对内存做极致的优化,不同长度的字符串使用不同的结构体来表示
- 字典(hash(数组+链表)):字典有2个Hashtable,通常仅1个存值,扩/缩容则分配新Hashtable旧值仍在旧Hashtable,新值存到新Hashtable,此时查值-查2者,旧Hashtable执行(reHash)渐进式迁移到新Hashtable,迁完-删旧Hashtable,元素数=数组长,扩容2倍 (若此时在bgsave持久化命令,元素数=5*数组长-才扩容)元素数=1/10数组长,缩容,不考虑是否在bgsave
- 压缩列表:Redis为节约内存使用的数据结构,zset和Hash在元素少时,压缩存一块连续内存空间
- list作队列:Lpush生产/Rpop消费,满-插/空-取:调sleep睡眠等待、调用bRpop阻塞L/R=左/右 主题订阅:(生产/消费=1:N):pub/sub,若消费者下线-丢失,需要专业MQ
- 延时队列:sortedset,时间戳为score、消息内容为key、zadd添加
- Redis Stream:仅追加内容的消息链表,所有加入消息串起来,每个消息标唯一ID,消息多占内存大。
- Zset:Zset=Sortedset+HashMap;Zset的值唯一(如set),且值有score作为值的权重,如热榜跳跃表:单链表—多指针,节点随机个数指针-避免增删值-维护固定层更复杂。遍历O(logN):从上到下-从少到多-确定范围-似二分查,在高并发下操作-比树形结构变动小、实现简单对比于红黑树,默认最大32层
- Geospatial(计算距离/半径,地理位置)、Hyperloglog(统计不重复的数据)、Bitmap(位图,也实现布隆过滤)
- 管道Pipeline(批量执行1组命令,返全部结果)、Pub/Sub(订阅发布,异步队列)
- Geospatial=经度 纬度 地名、 Hyperloglog=k k k...(存在误差)、 BitMap=n 0/1(索引 01)
前面讲过,缓存是高速读写但空间有限的,前面的页面置换算法也是这个道理,固然Redis也有自己的“页面置换算法”,即淘汰策略
Redis功能(缓存MySQL数据、共享用户session): 万级数据-Redis扛不住会打DB上(考虑到: 淘汰策略)
- 内存空间优化:设置内存上限+淘汰策略、 控制key长度、 设置过期时间、 避免存bigKey(String在10KB下,list/set/Hash/Zset元素数10万下)、选适合的数据类型、 数据压缩后写入Redis
- Redis保证高性能:避免存bigKey(若需存-则开lazy-free:即后台线程耗时去释放bigKey,减少主线程消耗)、不用复杂度过高的命令、 O(N)复杂度命令-要注意N大小、批量命令代替单命令操作多key、 避免key集中过期。
- 定期删除+惰性删除(定期随机检查是否删除 + 访问数据时被判断是否删除),仍未淘汰并超过内存限制,8种-内存淘汰(回收)机制:volatile-lru已设过期时间-LRU删、 allkeys-lru未设过期时间-LRU删、volatile-lfu已设过期时间-LFU删、 allkeys-lfu未设过期时间-LFU删、volatile-random已设过期时间-随机删、allkeys-random未设过期时间-随机删、volatile-ttl已设置过期时间-删将要过期的、noeviction 禁止再加数据-报错。LFU指删除最近最少频率使用的,默认noeviction机制。
- 缓存雪崩:同一时间大量key失效,大量QPS冲击数据库,解决办法:加锁/队列控制QPS量、错开失效时间、热点数据永不过期
- 缓存穿透:大量QPS访问不存在的key,绕开缓存冲击库,解决办法:验证参数不合法返回、布隆过滤器判断key不在库就返回
- 缓存击穿:大量QPS访问1个key,若key瞬间失效则冲击数据库,解决办法:设置此key永不过期、 加互斥锁
- 缓存一致性:先清缓存-后更新库,存在问题:可能访问时-缓存已清-数据库还未更新,读到旧值。解决办法:延时双删:先清缓存-后更新库-sleep睡眠-再清缓存:sleep时间>访问到旧值的线程-读写操作缓存时间,sleep结束后-清缓存-仅保证后续线程读库中新值;若先更新库-后清缓存,也存在问题:可能访问时-缓存未清或失败,读到旧值。解决办法:消息队列监听数据库binlog,再通知清缓存,但仍有延迟-造成缓存还未清期间-读到旧数据;设置过期时间,值放缓存中-设超时时间,过期后-库中读新值,仅更新库,不操作缓存,等数据过期-到库中读新值,适于一致性要求不高,但更新库频繁-不适。
- 频繁修改,则(删除>更新)缓存。如数据库改100次,缓存仅删1次,后续再调的数据存入缓存。
- 布隆过滤器(bitmap实现):防缓存穿透、缓存宕机-请求旧值-可能误判、首次请求获参数-二次命中(首次获空-存空)。缺点:存在误判可能:若值不存在,但对应组合位均1,则误判值存在,删除困难:删1个值-若将对应组合位均置为0,可能影响其他值。
- Redis内存操作-快,适存小数据,不适长时间存储,Kafka磁盘操作,适存大数据体,可长存储
Redis事务以及分布式锁
- Redis事务非原子性:一组命令绑为事务,其中某命令运行时错误-不回滚,命令编译错误-提示异常,单命令-原子性 事务执行(如Lua):一次性、顺序性、排他性 multi开,exec执行
- watch监视:实现-乐观锁:A线程-watch值-待执行事务时,B在A执行exec命令前修改值,事务失败(因watch的值已被修改),需unwatch解锁再watch重获,A重获新值后-再watch-执行事务,B获取值-不造成A事务失败,watch通过判断值是否改变-决定是否获锁
- Redis分布式锁(保证分布式下仅1个获锁) set创建/覆盖、setnx创建/失败、mset原子操作:
- Redis分布式锁实现:setnx只允许被1客端占用,set if not exists (key不存在-则设置)
- Redis分布式锁:setnx获锁,expire设锁失效时间,Lua脚本-将两指令结合到一起setex原子操作命令:获取锁-并设置失效时间
- 设置锁超时时间-解决长时间占锁的死锁:获锁线程执行时间长或挂掉-超时后服务器释放它,问题:但下一线程获锁后-可能执行上一线程未完成的数据。解决办法:设置后台线程定时检测-快超时且未执行完时-续期(重设超时时间)
- 释放别人锁问题:A线程获锁超时后-被自动释放-B线程获锁执行-A再释放锁(为B的锁)。解决办法:线程设自己唯一标识(uuid)-先判断属于自己-再释放锁。
- 单点/多点问题:主从复制时:A获锁后-主机挂了-则使用从机-B在从机中获取了锁,解决:RedLock、ZooKeeper(无设置过期锁-但session维护与客端定时心跳)、RedLock:互斥访问(永远仅1节点获锁)、 避免死锁(即使网络分区-也可避免)、容错性(大部分Redis节点存活-则可正常提供服务)
Redis集群(Ping测试连通):
- 集群作用:数据分区突破单内存容量、集群提高响应能力、高可用—自动故障转移,Redis高可用:主宕机,从自动升为主供服务
- Redis扩展性:单机内存不足时,用集群分片存储
- 数据分区:一致性hash分区:hash分区虚拟围环-节点定位,删节点-数据移下节点/增节点-上节点分给数据,若节点少-迁移影响大、hash值%节点数(但增删节点需全部重计算映射)、带虚拟节点的一致性hash分区:在一致性hash分区基础上-为节点添加槽空间,1节点含多槽-存数据,槽为节点虚拟的,每个槽包含一定范围的hash数据,槽为单位,增删节点-数据据hash存对应范围节点槽,对节点影响小
- 节点间通信:集群中节点都有TCP端口、普通端口-为客端连接、集群端口(普通端口+10000)-用于节点连接
- 通信模式:单对单、广播、Gossip协议, 其中主要是广播与Gossip协议。广播:消息发给所有节点-数据一致,但CPU带宽消耗大。Gossip协议:每个节点-根某些规则与部分节点通信-带宽耗低,但做到节点所收消息一致较慢
- 哨兵机制:监控主/从节点是否工作、自动故障转移-主挂选新主、配置提供-连接哨兵获主节点地址、通知(底层Ping)-将故障转移结果通知客户端
- 哨兵:至少3个:(若2个:若主库挂且附带其哨兵挂,仅剩1个无法执行-故障转移)、且不保证数据0丢失
- 故障转移-选新主机: 淘汰主观下线/已断线/最后回复ping>5s的从机、 淘汰断连超设置时间10倍的从机、都淘汰后-选复制偏移量最大的、偏移量不可用或相同时-选最小运行ID的
- Redis同步:从机发命令,主机执行持久化命令(生成RDB,后续修改命令存缓存,都发给从机),从机收到通过RDB与缓存中写命令进行全量同步,后续同步用AOF增量同步
- 主库最好不做持久化、需持久化-则从库启AOF设每秒同步1次、主库从库在同一局域网内避免压力大的主库-再增加从库、 主从复制用单向链的结构
- Redis集群:异步主从复制、非数据强一致性-可能写丢失、集群最多16384个节点
- Redis分区(异地多集群):客户端分区:指数据-存/读在哪个Redis、 代理分区 :代理-客户端请求,决定去哪个Redis节点-写/读数据,查询路由:请求任意节点-再转发正确Redis节点,或直接找到正确节点
- Redis分区缺点:跨分区多Key-不支持事务、不可用长Sortedset、数据处理复杂(如持久化)、动态扩/缩容复杂
Redis脑裂问题:指主节点与其哨兵网断,则主节点与其他从节点断连,哨兵集群重新选主节点,之前的客户端连接旧主节点,已执行写操作,网络恢复后旧主节点降为从节点,新主节点去同步更新从节点,则写操作丢失 ,因同步从节点前,从节点会先被清空。
尽量解决办法:根据redis参数,设置最小从节点数,设为2即为至少有2个从节点的主节点才对外提供写服务(断连后的旧主节点因无2个从节点而不提供服务)、极端情况需要人工补偿
Redis有16个数据库,不同数据-存储-不同数据库,减少-增删改清除数据操作
Redis删除大key问题,因单线程,删除大key期间无法处理其他请求,高峰时可能崩溃。
解决办法:scan分批删除、hset分批删
最后记一下Redis相关命令吧:
- String:
set k1 v1 设置kv键值对,已存在则修改
Get k1 获取value值
Exists k1 判断是否存在k1
Append k1 one 追加、不存在则创建
Strlen k1 value字符串长度
Incr k1 每次执行value+1
Decr k1 每次执行value-1
Getrange k1 0 3 截取字符串0~3, 0~-1代表全部
Setrange k1 1 x 替换指定位置的字符串
Setnx k1 v1 不存在则设置,存在则失败 ,分布式锁
Setex k1 9 v1 不存在则设置,存在则失败,过期时间9s,ttl k1查看剩余时间
Mset k1 v1 k2 v2 设置多个值,批量设置,获取:mget k1 k2
Mset user:id:name root user:id:age 20 设置对象
Getset k1 v2 先获取k1的value再设置为v2
- List
Lpush mylist A 从list左边插入A,Rpush从右边插入
Lpop mylist A 从list左边移出A,Rpop从右边移出
Lrange mylist 0 3 获取list的0~3的值
Llen mylist 获取list长度
Lrem mylist 1 A 删除1个A,因list中值可重复
Ltrim mylist 0 2 截断list剩余0~2的元素
Lset mylist 0 B 更新0位的值为B,不存在报错,exist list 判断存在
Linsert mylist before A C 在A前面插入值B,插后面after
- Set
Sadd myset A 添加1个值,不可重复
Scard myset 获取元素个数
Srem myset A 移出元素A
Smembers myset 查看所有元素
Sismember myset A 判断元素是否存在
Srandmember myset 随机获取1个元素
Spop myset 移出set的首个插入的元素
Sdiff myset1 myset2 两个set差集
Sinter myset1 myset2 两个set交集
Sunion myset1 myset2 两个set并集
- Hash
Hset myhash k1 v1 设置kv键值对,已存在则覆盖
Hmget myhash k1 k2 获取多个键值对
Hgetall myhash 获取hash1全部值
Hdel myhash k1 删除hash的值
Hlen myhash 获取hash的元素数量
Hexists myhash k1 判断hash是否存在k1
Hkeys myhash 获取hash所有的key
Hvals myhash 获取hash的所有values
Hincrby myhash k1 2 每次执行k1的value+2
Hdecrby myhash k1 2 每次执行k1的value-2
Hsetnx myhash k1 v2 存在k1则失败,不存在则设置
- Zset
Zadd myzset 1 A 设置Zset的权值,K值不可重复
Zrange myzset 0 3 查看zset的0~3位的值
Zrangebyscore myzset -inf +inf 据权值排序查看,负无穷~正无穷范围
Zrangebyscore myzset -inf 500 withscores 据权值排序,负无穷~500范围,含权值
Zrange myzset 0 3 获取zset的0~3范围的值
Zrem myzset A 移出zset的值
Zcard myzset 获取zset元素个数
- Geospatial //主体名称可以为对象:如china:city,经度纬度有范围边界
Geoadd china:city 110.10 40.4 beijing 添加地理位置,经度 纬度 key
Geopos china:city beijing 获取指定位置的经度纬度
Geodist china:city beijing shanghai km 获取2位置的距离,默认m,可指定km
Georadius china:city 110 30 500 km 以此经纬度为中心,500km内的位置
Georadius china:city 110 30 500 km withdist withcoord count 5 限制数量为5
Georadiusbymember china:city beijing 500 km 以北京为中心500km内元素
Geohash china:city beijing shanghai 将2位置的距离转为hash串
Geospatial基于Zset实现,可以使用Zset的命令
- Hyperloglog //内存占用低 12KB,但有错误率
Pfadd myHyper a b c d e 添加元素
Pfcount myHper 统计元素数量,去重后的数量
Pfmerge myHyper3 myHyper1 myHyper2 将2个集合合并为myHyper3,去重合并
- Bitmap
Setbit user1 0 1 设置索引0位为1
Getbit user1 0 获取0位索引的值
Bitcount user1 统计为1的索引数量
- 事务与锁
Multi 开启事务,后续将命令加入队列
Exec 执行事务,队列中命令一次性、顺序、非原子性执行
Discard 放弃事务,队列中命令语法错误直接报错,命令异常则跳过
Watch k1 监控k1,数据被释放前未被其他线程修改,成功,UNwatch、执行/放弃事务,释放
HSCAN key 0 COUNT 100
HDEL key fields