网站:
中文 http://www.redis.cn/
英文 https://redis.io/
文章目录
前置知识:
一. 为什么会缓存?
-
以前数据都是存在文件磁盘里面的,java中从磁盘获取文件需要通过I/O流获取
- 磁盘有磁道和扇区,一扇区512byte,带来一个成本变大-索引
- 4K:操作系统,无论你读多少,都是最少4k从磁盘取
- 随着文件变大,磁盘I/O会成为瓶颈,导致速度变慢
-
数据在磁盘和内存中,体积不一样的,因为磁盘没有指针指向值,需要多份索引
- 磁盘 内存 寻址 ms毫秒级 G/M 带宽 ns纳秒级 很大 时间单位大小: 秒>毫秒>微秒>纳秒
磁盘比内存在寻址上慢了10W倍
I/O文章:http://t.csdn.cn/9AR32
-
关系型数据库
-
1页数据 data page : 等同于操作系统读取大小(4K),避免浪费(小于4k)
-
索引页等同于1页data page 4k
-
索引页和数据页都是存磁盘的,查询时,会在内存中准备一个数形状来查询
-
特点:关系型数据库建表时,必须给出schema(架构),即类型:字节宽度
存:倾向于行级存储
这样当插入的时候,如果这行数据有列为null值,会进行占位,当更改数据时,直接在这一列中进行插入即可避免了空间上的转换
-
数据库:表很大,性能下降?
- 如果表有索引,增删改很慢,
查询速度?
- 1个或少量查询依然很快
- 并发大的时候会受硬盘带宽影响
-
二. memcache和redis的区别
两者虽然都是Key Value存储方式,但是Redis是有数据类型的,而Memcached没有数据类型。
这也是Redis的使用主键取代了Memcached的原因:
同样从两者取数据,再取之前先往Memcached中和Redis中存入同样的一个数组的数据结构(Memcached没有数据类型,Redis可以表示为list)。
如果要取数组中的第一个元素,对比:
Memcached : 需要把整个数组结构从Memcached服务器都取出来到Client转换成json后经过计算得到这个元素(计算逻辑发生在Client)。
Redis : 根据list类型,list类型自身提供方法直接从Redis服务器取出这个元素返回给Client(计算发生在Redis服务器)。
对比总结:
Memcached:如果很多人都这么获取的话,网卡IO就会成为最大的瓶颈,Client端要有你实现的代码去解码计算。
Redis:因为有类型,但是类型有不是太重要的,重要的是Redis服务器对每种类型都有自己的方法index(),lpop(),Client端的代码也会比较轻盈。
三. redis的keys
127.0.0.1:6379> get k1
"ooxx lisi "
127.0.0.1:6379> help TYPE //查看type命令的帮助文档
TYPE key
summary: Determine the type stored at key
since: 1.0.0
group: generic
127.0.0.1:6379> type k1
string //查看到类型为string
127.0.0.1:6379>
在Redis中key是一个object,value的type是根据设定元素时所用的方法来确定的,并且value的type会在key中进行登记,其作用是规避Client使用非value类型的方法进行操作。换言之就是Client调用非当前value对应Type的方法时,会先和key里面登记的type匹配,如果匹配失败直接返回错误,而不是在value计算报错的时候返回异常。
127.0.0.1:6379> set k2 "hello" //添加元素key=k2,value=hello
OK
127.0.0.1:6379> TYPE k2 //查看k2的类型为string
string
127.0.0.1:6379> set k3 99 //添加元素key=k3,value=99
OK
127.0.0.1:6379> TYPE k3 //查看类型依然是string
string
127.0.0.1:6379> OBJECT encoding k2 //查看k2编码为embstr
"embstr"
127.0.0.1:6379> OBJECT encoding k3 //查看k3编码为int
"int"
127.0.0.1:6379> APPEND k3 9 //使用append命令后 encoding发生了改变
(integer) 3
127.0.0.1:6379> OBJECT encoding k3
"raw"
127.0.0.1:6379> INCR k3 //进行数值运算+1,enconding又发生了改变
(integer) 1000
127.0.0.1:6379> OBJECT encoding k3
"int"
127.0.0.1:6379>
新增两个元素,一个是传统字符串,另外一个是数值类型,在查看类型的时候都是string,但是编码却是不一样的(embstr和int),因为面向Redis使用类型,除了字符串的操作 还有 计算的操作,并且发现有些方法能改变enconding。
那么现在有疑问了,k3的enconding为int时,他的长度为多少:
127.0.0.1:6379> APPEND k3 000 //为了看出效果又在k3后面追加了3歌灵
(integer) 7
127.0.0.1:6379> get k3
"1000000"
127.0.0.1:6379> INCR k3 //进行+1操作,让encoding更改为int
(integer) 1000001
127.0.0.1:6379> OBJECT encoding k3
"int"
127.0.0.1:6379> STRLEN k3 //不可理解的事情发生了,这里并没有按照int类型长度,而是按照一字符一个字节计算的
(integer) 7 //这是为啥呢?
127.0.0.1:6379> set k1 aaa //再新建一个k1字符串
OK
127.0.0.1:6379> get k1
"aaa"
127.0.0.1:6379> STRLEN k1 //k1的长度为3
(integer) 3
127.0.0.1:6379> APPEND k1 中 //这个时候如果在k1的后面添加一个中文“中”
(integer) 6
127.0.0.1:6379> STRLEN k1 //k1的长度从3变成了6,一个中文占了3个长度
(integer) 6
127.0.0.1:6379> GET k1 //查看k1的value,发现k1值为aaa+3个16进制
"aaa\xe4\xb8\xad"
127.0.0.1:6379>
这是因为Redis是二进制安全的,那什么是二进制安全呢?
首先对于数据传输IO, 有字节流和字符流。
如果有Client通过socket访问Redis的时候,Redis只是从socket中拿出了字节流,并没有拿出字符流。why?
因为Redis只是拿出字节流并且没有按照某一个编码集转换的话,双方Client只要有统一的编解码,就不会破坏数据。
知道了redis是二进制安全之后,那为什么key要有记录一个enconding呢?因为在为了优化数值计算,在Redis存储数据的时候,会先判断数据是字符串还是数值,如果是数值,encoding则为int。
这样做的好处在于 下次如果有计算发生,key发现encoding为int,则可以直接把数据从内存取出进行计算,省去了检查value成本。
encoding并没有影响数据的存储,从我们检查数据长度的时候,依然是按照一字符一个字节存储的。
那么“中”字为什么占了3个长度?
这是因为我们的远程连接工具 设定的是UTF-8的编码,Redis是二进制安全的,我们使用的远程连接工具直接把 “中” 按照UTF-8编成了3个字节长度的二进制传送给远程Redis,所以显示长度为3。
如果我们把远程连接工具的编码更改成GBK,那么“中”字的长度就位2.
总结:
- redis中不同的数据结构都有自己的本地方法,创建元素时根据调用的方法设定元素的类型。
- 一个OS可以有多个redis instance ,每个instance可以有多个DB,每个DB存储的都是键值对,DB之间隔离。
- key是一个object类型,key里面记录了value的name、type、encoding、length等信息,其目的都是为了优化redis性能。
一.简介
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
二.数据类型
-
字符串(strings)
值可以是任何种类的字符串(包括二进制数据),值的长度不能超过512MB
linux可以通过 help @string 查看帮助文档
-
1.1 字符串
-
指令 说明 set k v 普通设置值 get k v 获取值 mset k1 v1 k2 v2 批量设置 mget k1 k2 批量获取 exists k 判断是否存在 set k v nx 存在就不设置(新增),成功返回1,失败为0 set k v xx 存在就设置(更新) del k 删除key type k 返回数据类型 Object encoding k 返回编码类型 set k v ex 时间 设置有时间的k, 具有原子性 append k v 拼接值 expire k 时间 给key设置存活时间 ttl k 获取key剩余存活时间
-
1.2 数组
- 可以自增自减少
指令 说明 incr k 值自增 incrby k 值 指定增加值 decr k 值自减 decrBy k 值 指定增加减 场景 :秒杀,详情页,点赞,评论,规避并发下,对数据库的事务操作,完全由redis内存操作代替
-
1.3 bitmaps
- 一个字节有8个二进制位,值只能位1或0
指令 说明 setBit k 二进制位 1/0 设置二进制位的值为1或者0 getBit k 二进制位 获取二进制位的值 bitcount k 字符串索引开始 字符串索引结束 获取范围字符串里面有多少字节数 bitpos k 字节索引开始 字节索引结束 查找字符串里面bit值为1从开始的位置到结束 bittop and/or/not/xor destkey k1 k2 kn 对一个或多个key求逻辑并/或/异或/非,并将结果保存到destkey,除了not操作之外,其他操作都可以接受多个key 场景 :
-
有用户系统,统计用户登录天数,且窗口随机
setbit name1 1 1
setbit name1 7 1
setbit name1 364 1
strlen name1
bitcount name1 -2 -1
人为key 天为二进制位天 1 2 3 4 5 6 7…364
name1 1 0 0 0 0 0 1… 1
-
活跃用户统计
setbit 20200101 1 1
第一个1 代表mysql 映射的某个用户
第二个1代表将1这个位的二进制数设为1
setbit 20200102 1 1
setbit 20200102 7 1
bitop or destkey 20200101 20200102
bitcount destkey 0 -1
天为key 用户id 二进制位
-
-
散列(hashes)
key-vale结果
指令 说明 Hset bk sk v1 sk v2 设置一个大key里面可以有很多小key和对应的值 Hget bk sk 获取指定大key里面小key的值 hincrby bk sk 值 给值增加分数 Hdel bk sk 删除key中某个字段 Hlen bk 获取所有key中所有字段数量 场景 :点赞,收藏,详情页,针对某个任务或某个事务的特点
-
列表(lists)
存储有序,双向链表,逆向存取实现队列,先进先出,顺向存储,实现栈,先进后出
指令 说明 Lpush k v 存链表的最左节点 Rpush k v 存链表的最右节点 Lpop k 从链表最左边获取元素并删除 Rpop k 从链表最右边获取元素并删除 Lrange k start end 范围获取元素 ltrim k start end 从左边截取指定长度 Brpop/Blpop k timeout 阻塞式获取,只有当获取到值和超时了才会结束 Llen 返回list的长度 场景 :
-
集合(sets)
无序去重
指令 说明 sadd k v1 v2 存储一个或多个元素 smembers k 获取全部元素 sismember k 3 判断指定元素是否存在 scard k 获取集合里面元素数量 sinter k1 k2 获取两个集合的交集 sinterstore destkey k1 k2 获取指定集合的交集并把结果存入destkey中 sunionstore destkey k k2 获取指定集合的合并集,把结果存入destkey中 场景 :
-
有序集合(sorted sets)
根据分数排列(左小右大),去重,具备集合操作,排序是如何实现的?
通过skip list跳跃表
指令 说明 zadd k score fk score fk 给集合添加元素并设置分数 zcard k 获取元素个数 zcount k min max 返回指定分数内的数量 zincrby k score fk 给某个元素增加分数 Zrangebyscore k min max 获取指定范围分数内的元素,结果从小到大展示 Zrevrangebyscore k max min 获取指定范围分数内的元素,结果从大到小展示 withscore 命令后面加这个可以展示分数 Zunionstore 结果k k数 k1 k2 权重 aggration 聚合操作 权重聚合命令 场景 :
三.管道,订阅,驱动事件,事务
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务,通常是阻塞的,
客户端发送请求,监听scoket,服务端处理命令并将结果返回给客户端
-
管道(pipelining)
pipeline就是把一组命令进行打包,然后一次性通过网络发送到Redis,同时将执行的结果批量返回回来
通过安装 yum install nc, nc localhost 6379 连接服务,命令之间用换行符linux:\n window:\r\n
指令: echo -e “操作命令\n操作命令” | nc localhost 6379
优点:
1. pipepeline通过打包命令,一次性执行,可以节省 连接->发送命令->返回结果 所产生的往返时间 2. 减少的I/O的调用次数
缺点:
1. 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。这样速度几乎是相同的,但是在回复这10k命令队列需要非常大量的内存用来组织返回数据内容。 2. 责任链模式,这个模式的缺点是,每次它对于一个输入都必须从链头开始遍历,这确实存在一定的性能损耗。 3. 不保证原子性 4. 批量执行的时候Redis 采用多路I/O复用模型,非阻塞IO,所以 `Pipeline` 批量写入的时候,Redis不会进行锁定导致其他应用无法再进行读写, 一定范围内不影响其他的读操作
-
订阅
-
通过channel,先订阅某个channel,在发布消息到channel上,像消息队列中的主题模式
-
分为三个角色发布者,订阅者,channel,发布者和订阅者属于Redis的客户端,channel属于Redis的服务端
-
场景:
-
直播室消息聊天,监听消息
-
实时聊天信息:
实时性消息通过发布订阅用zset存储
历史性消息3天内的也用zset存储,分数为最近日期,按分数截取最近三天内数据,超过的删除
更久的数据存入数据库
指令 说明 publish channel message 发送消息到指定的频道 subscribe channel1 channel2 订阅一个或多个频道 help @pubsub 查看订阅帮助指令 -
-
-
事务
-
ACID事物的原则
-
MULTI 命令用于开启一个事务,它总是返回
OK
。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。另一方面, 通过调用 DISCARD , 客户端可以清空事务队列, 并放弃执行事务。
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
-
为什么redis不支持回滚?
- Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
-
当有个事物同时进行时,谁先执行exec指令,那么就先执行对于事物内的指令
指令 说明 multi 开启事物,可以输入命令,放入队列 exec 提交事物,只执行正确的命令 discard 取消事物 watch 监控某一个key,当key发生变动时,那么事物内指令取消 help @transactions 查看事务帮助指令 -
-
缓存和数据库的区别
-
缓存: 不是全量数据,缓存随着访问变化,存的是热数据,注重速度,
redis作为缓存放的是热数据因为内存容量有限,掉电易失
-
数据库:全量数据,不能丢失,注重速度+持久性
-
-
key的过期淘汰策略
-
设置时间
- set key v ex 时间
- get key 不会延长有效时间
- set key 会对过期时间影响,为-1
- expire/expireat key 时间重新设置时间,一个为倒计时,一个为定时
- ttl key 查看有效时间
-
淘汰策略
-
被动:当需要用到这个过期的key时,redis才会去比较时间戳,发现过去了就触发回收策略
很占用内存
-
主动:周期轮询判断,牺牲一点空间换性能
测试随机的20个keys进行相关过期检测。
删除所有已经过期的keys。
如果有多于25%的keys过期,重复步奏1.
-
-
回收策略
-
maxmemory 设定内存大小 1G-10G
-
maxmemory-policy 回收策略,当设置了大量的过期key时使用volatile-lru,没事时使用allkeys-lru,具体根据情况定
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。(常用)
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。(常用)
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
-
-
四.常见问题
- 同一时间有大量的key过期,导致所有的请求都到达数据库,导致数据库雪崩宕机
- 解决:
- 设置过期时间分布不均匀的key
- 给请求的数据设置分布式锁,保证访问同一个数据时只有一个请求会去查数据库
- 启动热点数据预热加载
- 搭建集群:主从读写分离,哨兵,副本分片
- 热点数据永不过期(可搭配数据同步)
- 缓存中有某一个热点key过期了,同时有大量的请求这个热点key,导致所有的请求到达数据库,导致数据库宕机
- 解决:
- 给请求的数据设置分布式锁,保证访问同一个数据时只有一个请求会去查数据库
- 热点数据永不过期
- 当请求频繁访问一个缓存和数据库中都不存在的数据,会导致每次请求都需要连接数据库查询,少量的还好,当有大量的请求时候,也会造成数据库宕机
- 解决:
- 布隆过滤器,布谷鸟过滤器
- 但是还是会发生误判问题,还是需要搭配分布式锁(设置时间)来使用,保证相同的key,只有一个请求会去查数据库
- **布隆过滤器**
- 用一个bitmap记录,bitmap原始数值全都是0,当一个元素存进来时,会经过k个hash函数得到k个数量值,并且将bitmap对应映射的位置设置为1(会把有的数据都进行这个操作)
- 当下一个值在进来时,也会进行k个hash函数,得到对应的k个数量值,如果对应映射的位置都是1,那么这个元素会被判定为存在,有一个为0,则过滤掉
- 问题:
1. 会出现误判:如果一个值不存在,但是他经过hash函数得到的值,所对应映射位置的值都为1,那么这个值会被判定存在
2. 没法删除数据:
一是,由于有误判的可能,并不确定数据是否存在数据库里
二是,当你删除某一个数据包对应位图上的标志后,可能影响其他的数据包,例如上面例子中,如果删除数据包1,也就意味着会将bitmap1,3,6位设置为0,此时数据包2来请求时,会显示不存在,因为3,6两位已经被设置为0。
3. 查询性能弱:因为布隆过滤器需要使用多个 hash 函数探测位图中多个不同的位点,这些位点在内存上跨度很大,会导致 CPU 缓存行命中率低。
4. 空间利用效率低: 因为在相同的误判率下,布谷鸟过滤器的空间利用率要明显高于布隆,空间上大概能节省 40% 多。不过布隆过滤器并没有要求位图的长度必须是 2 的指数,而布谷鸟过滤器必须有这个要求。从这一点出发,似乎布隆过滤器的空间伸缩性更强一些
- **布隆过滤器增强版**
为了解决上面布隆过滤器的问题,出现了一个增强版的布隆过滤器(Counting Bloom Filter),这个过滤器的思路是将布隆过滤器的bitmap更换成数组,当数组某位置被映射一次时就+1,当删除时就-1,这样就避免了普通布隆过滤器删除数据后需要重新计算其余数据包Hash的问题,但是依旧没法避免误判。
- **布谷鸟过滤器**
- **布谷鸟哈希**: 最简单的布谷鸟哈希结构是一维数组结构,会有两个 hash 算法将新来的元素映射到数组的两个位置。如果两个位置中有一个位置为空,那么就可以将元素直接放进去。但是如果这两个位置都满了,它就不得不「鸠占鹊巢」,随机踢走一个,然后自己霸占了这个位置。
问题:
1. 但是会遇到一个问题,那就是如果数组太拥挤了,连续踢来踢去几百次还没有停下来,这时候会严重影响插入效率。这时候布谷鸟哈希会设置一个阈值,当连续占巢行为超出了某个阈值,就认为这个数组已经几乎满了。这时候就需要对它进行扩容,重新放置所有元素。
2. 还会有另一个问题,那就是可能会存在挤兑循环。比如两个不同的元素,hash 之后的两个位置正好相同,这时候它们一人一个位置没有问题。但是这时候来了第三个元素,它 hash 之后的位置也和它们一样,很明显,这时候会出现挤兑的循环。不过让三个不同的元素经过两次 hash 后位置还一样,这样的概率并不是很高,除非你的 hash 算法太挫了。
优化:
1. 改良的方案之一是增加 hash 函数,让每个元素不止有两个巢,而是三个巢、四个巢。这样可以大大降低碰撞的概率,将空间利用率提高到 95%左右。
2. 另一个改良方案是在数组的每个位置上挂上多个座位,这样即使两个元素被 hash 在了同一个位置,也不必立即「鸠占鹊巢」,因为这里有多个座位,你可以随意坐一个。除非这多个座位都被占了,才需要进行挤兑。很明显这也会显著降低挤兑次数。这种方案的空间利用率只有 85%左右,但是查询效率会很高,同一个位置上的多个座位在内存空间上是连续的,可以有效利用 CPU 高速缓存。
3. 所以更加高效的方案是将上面的两个改良方案融合起来,比如使用 4 个 hash 函数,每个位置上放 2 个座位。这样既可以得到时间效率,又可以得到空间效率。这样的组合甚至可以将空间利用率提到高 99%,这是非常了不起的空间效率。
- **布谷鸟过滤器**:布谷鸟过滤器和布谷鸟哈希结构一样,它也是一维数组,但是不同于布谷鸟哈希的是,布谷鸟哈希会存储整个元素,而布谷鸟过滤器中只会存储元素的指纹信息(几个bit,类似于布隆过滤器)。这里过滤器牺牲了数据的精确性换取了空间效率。正是因为存储的是元素的指纹信息,所以会存在误判率,这点和布隆过滤器如出一辙。
首先布谷鸟过滤器还是只会选用两个 hash 函数,但是每个位置可以放置多个座位。这两个 hash 函数选择的比较特殊,因为过滤器中只能存储指纹信息。当这个位置上的指纹被挤兑之后,它需要计算出另一个对偶位置。
布谷鸟过滤器巧妙的地方就在于设计了一个独特的 hash 函数,使得可以根据 p1 和 元素指纹 直接计算出 p2,而不需要完整的 x 元素。
fp = fingerprint(x)
p1 = hash(x)
p2 = p1 ^ hash(fp) // 异或
从上面的公式中可以看出,当我们知道 fp 和 p1,就可以直接算出 p2。同样如果我们知道 p2 和 fp,也可以直接算出 p1 —— 对偶性。
p1 = p2 ^ hash(fp)
所以我们根本不需要知道当前的位置是 p1 还是 p2,只需要将当前的位置和 hash(fp) 进行异或计算就可以得到对偶位置。而且只需要确保 hash(fp) != 0 就可以确保 p1 != p2,如此就不会出现自己踢自己导致死循环的问题。
也许你会问为什么这里的 hash 函数不需要对数组的长度取模呢?实际上是需要的,但是布谷鸟过滤器强制数组的长度必须是 2 的指数,所以对数组的长度取模等价于取 hash 值的最后 n 位。在进行异或运算时,忽略掉低 n 位 之外的其它位就行。将计算出来的位置 p 保留低 n 位就是最终的对偶位置。
五.持久化
-
前置linux知识
-
管道
-
衔接前一个命令的输出作为后一个命令的输入
-
管道回创建子进程
-
指令: echo $$ | more 查看当前进程号
echo $ BASHPID | more
$$ 优先级要高于管道,所以 $ $ 会显示父进程号,一个的会显示子进程号
-
-
父子进程
-
常规: 进程是数据隔离的
-
进阶:通过export的环境变量,父进程可以让子进程看到数据
但是父子进程对数据的修改对方看不到,操作的是虚拟内存指向磁盘的指针
-
-
进程创建的成本
- 速度:创建子进程的速度
- 内存空间: 创建子进程需要占用的空间
-
fork(系统调用)
- 创建子进程
- 速度快,创建的是虚拟内存
- 空间小,复制的是磁盘数据的指针
- 创建子进程
-
copy on write(内核机制)
- 写时复制: 需要操作值的时候才会去复制这个值的磁盘指针地址
- 创建子进程是并不会发生复制
- 提高创建子进程的速度
- 操作的是指针(地址映射)
-
-
RDB
- 把全量数据生成快照/副本,通过fork子进程进行文件写入,时间段进行持久化
- 时点性:
- 阻塞主进程,不对外提供服务,save指令
- 非阻塞,主进程还对外提供服务,fork子进程,当修改时会触发copy on write,复制值的磁盘指针指针;时间点数据落地,bgsave指令/通过配置文件配置 save 时间 操作了多少次命令
- 弊端:
- 不支持拉链,只有一个dump.db文件,不覆盖,不更新,需要人为备份文件
- 丢失数据相对多,时点与时点之间的窗口数据容易丢失,8点实现了RDB,9点要做RDB,但是宕机了,那么这一个小时数据就丢失了,所以4.0之后搭配aof一起使用
- 优点:
- 类似java中的序列化,恢复速度相对较快
-
AOF(append on file)
-
redis的写操作记录到文件中
-
优点:
- 数据丢失相对较少
- RDB和AOF同时开启
- 若AOF开启,重启服务后只会用AOF恢复
- 4.0之后,AOF文件前面是RDB全量数据,后面是记录新增的写操作命令
-
弊端:
-
体量大,回复慢
-
解决:
-
4.0以前,重写bgrewrite,删除抵消的命令,合并重复的命令,最终也是一个纯指令的文件
-
4.0以后,重写bgrewrite,将老的数据RDB到AOF中,将增量的以指令的方式append到Aof中,
aof文件是混合体,文件开头是"REDIS",保存了RDB恢复快的优势,保存了AOF数据丢失少的优势
-
-
-
指令:
- aof默认是关闭的,通过 appendonly yes 开启
- aof-use-rdb-preamble yes 开启混合
- bgrewrite配置文件指定执行条件
- auto-aof-rewrite-percentage 100
- auto-aof-rewrite-size 64 mb
-
写操作会触发IO
- no: appendfsync no 内核满了就调用一次flush,会丢失buffer大小的数据
- always; appendfsyc always 每笔都调一次flush,数据最可靠
- everysec: appendfsyc everysec 每秒中调一次flush,接近丢一个buffer(1秒数据)
-
六.集群
-
redis弊端
- 单进程,单实例,单机
- 单点故障
- 内存容量有限
- 高并发访问压力
- 单进程,单实例,单机
-
AKF(X轴,Y轴,Z轴)
-
轴:
- X :添加备机,全量,镜像
- 主备: 只要主对外提供服务,只有当主宕机了,从才能对外提供服务
- 主从: 主和从都对外提供服务,主可以读写,从只能读
- Y:按业务,功能拆分
- Z:按优先级,逻辑再拆分
- X :添加备机,全量,镜像
-
一变多,数据一致性问题
-
同步阻塞
- 强一致
- 所有节点阻塞直到操作全部完成,数据一致,破坏了可用性
-
异步
- 弱一致性
- 可容忍部分数据的丢失
- 服务端不等待所有的数据同步完,直接返回
-
中间件同步阻塞,如kafka
- 最终一致性
- 利用消息中间件的可靠性,高效性实现,中间过程也可能出现数据不一致现象
- 强调强一致性
-
对主做HA(高可用)
-
投票机制:过半,否则,势力范围不够,会出现脑裂,网络分区
分区容忍性(能容忍就不是坏事,不需要强一致性时可用)
-
集群一般使用基数台:与下一个偶数台的风险相同,但是下一个偶数台成本更高
-
-
-
-
主从复制
-
用异步的方式
-
从追随主的指令(数据同步全量)
- 5.0之前slaveof 服务地址 端口
- 5.0之后replicaof 服务地址 端口
- 放弃追随: replicaof 服务地址 端口
-
数据同步
- 全量
- 当丛第一次连接,主生成RDB文件给从
- 从太久没有连接,offset超过了,需要做全量RDB
- 增量
- 从宕机了,offset是在范围内,做增量同步
- 全量
-
配置文件参数
- replica-server-stale-data yes 同步时能否查询
- replica-read-only yes 是否只读
- replica-diskless-sync no 从磁盘n或者网络同步
- repl-backing-size 1mb 增量复制 根据场景设置大小
-
弊端
- 需要人工维护主的故障问题
- 不是绝对的实时同步,可能连最终一致性都谈不上
- 可通过对主做HA,哨兵监控
-
优点
-
解决单点故障,读写分离提高了并发访问量
但是内存容量问题还是未解决(分片集群)
-
-
-
哨兵sentinel
-
监控redis-server的运行状态
-
故障自动转移
-
哨兵通过发布订阅可以知道其他哨兵的信息及从的信息
-
主宕机了,从会通过投票方式自动推选出新的主,并追随,不需要手动维护
哨兵会在一定时间内对服务发出ping,当服务没有响应pong,则任务服务主观下线,当超过半数的哨兵认为下线了,
就成了客观下线,这时候哨兵也会选出一个领导,来执行选举新的主的指令
-
哨兵指令
- 启动哨兵(知道哨兵进程不对外提供存储服务)
- redis-server 配置文件.conf --sentinel
- redis-sentinel
- 查看目前有的发布订阅管道
- psubscibe *
- 哨兵配置文件(redis源码目录)
- port 端口(哨兵也是个进程)
- sentinel monitor mymaster 服务地址 端口 数量(需要多少哨兵认为下线,才成为主观下线)
- 启动哨兵(知道哨兵进程不对外提供存储服务)
-
-
容量有限方案,可从github上查看twitter查看
- 数据可拆分
- 按逻辑,业务拆分
- 数据不可拆分(sharding 分片)
- 利用hash+取模(redis台数),modula
- 弊端:取模的值固定,不利于分布式下的扩展性
- 随机数random,lpush存,rpop取
- 写数据的client去不出值
- key相当于topic,redis进程相当于parition,就像消息队列kafka
- 一致性hash,利用算法 kemata
- 映射算法
- data,node都参与计算
- 规划一个环形hash环
- 虚拟节点,解决数据倾斜问题
- 物理节点,node映射的点
- node先进行hash得到映射点,新增data时找距离最近的node点
- 优点
- 可分担其他节点的压力,不会造成节点的全局洗牌
- 缺点
- 造成一部分数据不能命中,压到mysql,击穿
- 粗糙解决方案:获取数据的时候,可从距离最近的两个node去取
- 更倾向于缓存而不是数据库
- 利用hash+取模(redis台数),modula
- 数据可拆分
-
redis连接成本高
- 可以用proxy代理方式,如nginx反向代理和负载均衡
- 代理层有逻辑实现modula,random,kemata
- twemproxy 可从github上查看readme文档 下载release编译后的代码(predixy)
- 数据是分治在多组主从里面,所以不支持keys *,watch,multi,exec等操作,就算数据都在一个server里面也不行,会检测到有多个实例存储数据
- 用lvs+proxy ,keepalived监控实例
- 预分区
- cluster集群
- 多主多从,每个主分配了均匀的插槽,插槽数总共为16384个,可人工进行迁移
- 客户端直接连接代理,通过计算key,重定向路由到目标槽位的server
- 可以人工设置{值},来把一类的值放到同一个server,来支持事物,watch,keys *
- 可以用proxy代理方式,如nginx反向代理和负载均衡