Redis学习笔记

Redis学习笔记

第1章 初识Redis

简介

Redis是一种基于键值对的NoSql数据库。
与很多键值对数据库不同的是,Redis中的值是有string、hash、list、set、zset、Bitmaps、HyperLogLog、GEO等多种数据结构和算法组成,因此redis可以满足很多应用场景,而且因为redis会将所有数据都放在内存中,所以它的读写性能非常惊人。不仅如此,redis还可以将内存的数据利用快照和日志的形式保存到硬盘上,这样在断电或故障是,内存中的数据不会丢失。
此外,redis还提供了键过期、发布订阅、事务、流水线、Lua脚本等附加功能。

特性

  1. 速度快
  2. 基于键值对的数据结构服务器
  3. 丰富的功能:键过期、发布订阅、支持lua脚本、事务功能、流水线功能(批量命令)
  4. 简单稳定
  5. 客户端语言多
  6. 持久化
  7. 主从复制
  8. 高可用和分布式

典型的使用场景

缓存:加快数据的访问速度
排行榜系统:例如按照热度排名
计数器应用:浏览数等
社交网络:赞踩、粉丝、共同好友、推送、下拉刷新
消息队列系统

不可以怎么用

不能够存储大规模数据(无限多的数据),不要把冷数据放在redis中

第2章 API的理解和使用

几个Redis全局命令

redis有5种数据结构,它们是键值中的“值”,对于“键”来说有一些通用的命令
全局命令

数据结构和内部编码

Redis对外的的数据结构:string、hash、list、set、zset
每种数据结构都有自己底层的内部编码实现,而且是多种实现,用来应对合适的场景。
可以用object encoding命令查询内部编码
内部编码
这样设计的好处:
1-解耦内部编码实现和对外的数据结构和命令;
2-不同场景下选用不同的编码(ziplist省内存等);

单线程架构

redis使用单线程架构和I/O多路复用模型来实现高性能内存数据库服务。
redis单线程处理命令,从客户端到达的命令不会立即执行,都会先进入一个队列,然后逐个执行,不会存在多个命令被同时执行的情况。
为什么这么快?
1-纯内存访问:内存响应时长是100ns
2-非阻塞io:redis使用epoll作为I/O多路复用技术的实现以及使用自身事件模型;
3-单线程避免线程切换和竟态产生的消耗

1、字符串

字符串类型的值实际可以是字符串、数字、二进制,但是最大值不能超过512M。
常用命令
字符串常用命令
命令的时间复杂度
时间复杂度
内部编码
redis会根据当前值的类型和长度决定使用哪种内部编码实现
int:8个字节长整型
embstr:小于等于39个字节的字符串
raw:大于39个字节的字符串
典型使用场景
1、缓存功能:减小数据库的压力
2、计数
3、共享session
4、限速:流控

2、哈希

常用命令
哈希的常用命令
哈希命令的时间复杂度
时间复杂度
内部编码
1.ziplist(压缩列表):
优点:节省内存。
使用条件:哈希类型元素个数小于hash-max-ziplist-entried(默认512) && 所有值都小于hash-max-ziplist-value(默认64字节)
2.hashtable(哈希表):
在ziplist不满足使用的时候专用hashtable,为了加快速度
使用场景
哈希类型和关系型数据库2点不同:
1-哈希类型是稀疏的,关系型数据库是完全结构化的
2-关系型数据库可做复杂的关系查询,redis如果模拟这类操作的话开发困难,维护成本高;
3种方法缓存用户信息对比
缓存用户信息对比

3、列表

用来存储多个有序字符串。
一个列表最多可存储232-1个元素。
可对两端插入和弹出,可获取指定范围的元素列表、指定索引下的元素(可充当队列和栈)。
2个特点
元素有序
列表元素可重复
常用命令
常用命令
lrange 0-1:从左到右获取列表所有元素。
命令时间复杂度
时间复杂度
内部编码
1、ziplist
使用条件:元素个数小于list-max-ziplist-entried(默认512) && 所有值都小于hash-max-ziplist-value(默认64字节)
2.linkedlist(哈希表):
在ziplist不满足使用的时候专用hashtable,为了加快速度
3.quicklist(v3.2以后)
使用场景
1.消息队列
消息队列
2.文章列表
用哈希存储每篇文章的内容,用list存储每篇文章。
设置文章内容
hmset article:1 title xx cotent xxx
hmset article:2 title xx3 content x4
放进list
lpush user:1:article article:1 article”2
分页获取
articles = lrange user:1:articles 0 9
for article in {articles}
hgetall {article}
总结:自定义写入和读取的规则,然后将这几种数据结构进行组合使用
这里写图片描述

4、集合

用来保存多个字符串。
不允许有重复元素。
无序,不能同步下表获取元素。
一个集合最多可存储232-1个元素。
除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。
命令
命令
集合间操作
集合操作
命令时间复杂度
时间复杂度
intset(整数集合):
条件
1、当集合中的元素都是整数
2、个数小于set-max-intset-entries配置(默认512)
hashtable(哈希表):
当intset不能满足时用hashtable
使用场景
标签(给用户打标签)

5、有序集合

元素不能重复,元素可以排序。
为每个元素设置一个分数作为排序的依据。(分数可以重复)
提供了获取指定分数和元素范围查询、计算成员排名等功能。
有序集合
列表、集合、有序集合三者异同点
异同
命令
命令
集合间的操作
交集:zinterstore destination numkeys key [key …] [weight weight [weight…]] [aggregate sum|min|max]
按照元素求交集,然后对他们的分数*weight做sum|min|max操作
并集:zunionstore destination numkeys key [key …] [weight weight [weight…]] [aggregate sum|min|max]
时间复杂度
复杂度
内部编码
ziplist:
使用条件
1、元素个数小于zset-max-zpilist-ectries配置(默认128个)
2、每个元素的值小于zset-max-ziplist-value(默认64字节)
skulist
使用场景
排行榜系统
排行榜

6、键管理

单键
单键
遍历键
遍历键
数据库管理
1.切换数据库:select dbIndex
Redis使用数字作为多个数据库的实现。
redis默认配置中有16个数据库,select 0将切换到第一个数据库,select 15切换到第16个。各个数据库没有关系,可以存在相同的键。
默认使用index=0的那个数据库
数据库管理
但是,redis3.0中已经弱化了多数据库这个功能,原因有3:
1、redis是单线程,如果使用多个数据库,使用的是同一个cpu,彼此间还是会影响。
2、部分redis客户端不支持这种方式,而且容易弄混。
3、多数据使用方式会让调试和运维不同业务的数据库变得困难,如果有一个慢查询会影响其他数据库,会使别的业务方定位问题变得困难。
所以如果需要使用多个数据库功能,可以在一台机器上部署多个reids实例,彼此用端口来区分。(可以充分发挥多cpu资源)
2.清除数据库:flushdb/flushall
flushdb:只清除当前数据库
flushall:清除所有数据库
存在2个问题:
1、误操作,后果不堪设想(rename-command配置可以规避这个问题)
2、存在阻塞redis的可能性

第3章 小功能大用处

Redis提供了如慢查询分析、Redis Shell、Pipeline、事务与Lua脚本、Bitmaps、HyperLogLog、发布订阅、GEO等附加功能,这些功能可在某些场景发挥重要功能。
小工具

3.1 慢查询分析

慢查询日志:记录下来执行超过阈值的那些命令,做分析。
Redis执行一条命令的4个步骤:
1.发送命令
2.命令排队
3.命令执行(慢查询统计的时间)
4.返回结果
执行步骤
慢查询的两个配置参数
慢查询分析配置
修改参数的方法:
1-修改配置文件
2-用config set命令
config rewrite:将配置持久化到本地配置文件
慢查询日志的存放和访问
存放在内存中,可以通过一组命令来对慢查询进行访问和管理
慢查询访问
实践
1、slowlog-max-len:增大慢查询列表可见或慢查询被提出的可能,例如线上可设置1000以上;
2、slowlog-log-slower-than:默认是10ms,根据并发进行调整。对于高并发场景,命令执行在1ms以上,那么qps最多只能到1000,所以高QPS场景建议设置1ms;
3、当客户端出现超时请求时,首先应该查询是否有慢查询,因为慢查询只记录了命令的执行时间,如果有多个慢查询的话会影响到后续的请求
4、可以定期执行slowget命令,并将其进行持久化,防止man查询日志丢失

3.2 Redis Shell

Redis提供了redis-cli、redis-server、redis-benchmark等shell工具,有时可以很巧妙的解决一些问题。
redis-cli有很多有用的参数,下面介绍一些重要参数的含义和使用场景。
shell
redis-server详解
redis-server除了启动redis外,–test-memory选项可用来检测当前os是否稳定分配指定容量内存给redis,这种检测可以有效避免因为内存问题造成redis崩溃。
redis-benchmark详解
可以为redis做基准性能测试,提供了很多选项帮助开发和运维人员测试redis的相关性能。
redis-benchmark

3.3 Pipeline

概念
发送命令——命令排队——执行命令——返回结果
Round Trip Time (RTT :往返时间)= 1 + 4
Redis提供了批量操作命令(mget、mset),节约了rtt,但是很多命令不支持批量操作,那么当客户端和服务器之间会耗费大量的rtt。pipeline机制能改善上面这类问题:将一组命令进行组装,通过一次rtt传输给redis,再将这组执行结果按顺序返回给客户端。
redis-cli的–pipe选项使用的就是pipeline机制。
大部分开发人员倾向于使用高级语言客户端中的pipeline,目前大部分客户端都支持pipeline。

3.4 事务与lua

为保证多条命令的原子性,redis提供了事务功能以及集成lua脚本来解决这个问题。
redis中的事务
命令放在multi和exec中间,如果要取消用discard代替exec

multi
sadd user:a:follow user:b
sadd user:b:fans user:a
exec

multi
sadd user:a:follow user:b
discard

出错情况
出错情况
Lua用法简述
目标:作为嵌入式程序移植到其他应用程序,由c语言实现,许多应用选用做脚本语言。(魔兽世界、愤怒的小鸟、ngnix)
Redis将Lua作为脚本语言帮助开发者定制自己的redis命令。
Lua
1、数据类型及逻辑处理
blleans
numbers
strings
tables
数据类型
2、Lua的Redis API
Lua可以使用redis.call函数实现对redis的访问(redis.call(“set”, “hello”, “world”))
redis.pcall也可以实现对redis的访问,与上面不同的是,遇见错误会继续执行
案例
Lua脚本功能为Redis带来的好处:
1、Lua脚本在redis中是原子执行的;
2、lua脚本可以帮助开发和运维人员创造出自己定制的命令,并将这些命令常驻在redis内存中,实现复用;(将原来需要多步执行的命令放在一个lua脚本中,一起执行)
3、lua脚本可以将多条命令一次性打包,有效减少网络开销;
Redis如何管理Lua脚本
Redis提供了4个命令实现对Lua脚本的管理:
管理LUA

3.5 Bitmaps

数据结构模型
Bitmaps本身不是一种数据结构,实际上是字符串,但是可以对字符串的位进行操作
Bitmaps单独提供了一套命令,所以在redis中使用bitmaps和使用字符串方法不太相同。
可以把bitmaps想象成一个以位为单位的数组,每个单元只能存储0和1,数组的下标在bitmaps中叫做偏移量;
bitmap示意图
命令
例子:将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的记做0,用偏移量作为用户id。
命令
示意图
Bitmas分析
场景有所限制。
如果网站有1亿用户,每天访问的用户有5千万,那么每天用集合类型和bitmaps分别存储活跃用户可以得到。
对比1
但如果网站每天的独立访问用户很少,使用bitmaps就不太合适了。
对比2

3.6 HyperLogLog

HyperLogLog并不是一种新的数据结构(实际为字符串类型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以使IP、Email、ID等。
HyperLogLog提供了3个命令:pfadd、pfcount、pfmerge。
HyperLogLog示意
3个命令
命令
集合类型和HyperLogLog占用空间对比
对比
注意:
HyperLogLog内存占用率非常小,但是存在错误率,所以开发者需要权衡,只需要考虑以下两条:
1、只为了计算独立总数,不需要获取单条数据;
2、可以容忍一定的误差率;

3.7 发布订阅

Redis提供了基于“发布/订阅”模式的消息机制,消息发布者和订阅者通过redis中的频道进行通信。
Redis提供了若干命令支持该功能。
示意图
命令
redis主要提供了发布消息、订阅消息、取消订阅以及按照模式订阅和取消订阅等命令。
命令
使用场景
聊天室、公告牌、服务之间利用消息解耦

3.8 GEO

Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信息用来实现注入附近位置、摇一摇依赖于地理位置信息的功能。
不详细叙述

第4章 客户端

4.1 客户端通信协议

几乎所有主流编程语言都有Redis客户端,有2个原因:
1、通信协议基于TCP协议;
2、Redis制定了RESP(Redis Serilization Protocol)实现客户端与服务端的正常交互,这种协议简单高效;
发送命令格式
命令格式
返回结果格式
返回格式
示意图

4.2 Java客户端Jedis

Java有很多优秀的Redis客户端,介绍下使用较为广泛的客户端Jedis
基本使用方法

public class RedisClient {
   public static final String KEY = "name";
   public static void main(String[] args) {
      Jedis jedis = null;
      try {
         //链接
         jedis = new Jedis("127.0.0.1", 6379);

         //set
         String result = jedis.set(KEY, "arab");
         System.out.println(result);

         //get
         String name = jedis.get(KEY);
         System.out.println(name);
      } catch (Exception e) {

      } finally {
         if (jedis != null)
            jedis.close();
      }
   }

   public static void initWithMultiParams() {
      //另外一种初始化方式
      Jedis jedis = new Jedis("127.0.0.1", 6379, 100, 100);
      jedis.set(KEY, "arab");

      String name = jedis.get(KEY);
      System.out.println(name);
   }
}

参数除了可以使字符串,Jedis还提供了字节数组的参数
Stringset(byte[] key, byte[] value)
那么就能将Java对象序列化为二进制,当需要的时候使用get(final byte[] key)将字节数组取出,然后反序列化。
Jedis连接池的使用方法
上面使用的是Jedis直连方式(每次都会新建TCP连接,然后再断开,比较低效),因此一般生产环境中使用连接池对Jedis连接进行管理,用完了还回去。
直连和连接池对比
优劣
Jedis提供了JedisPool作为对Jedis的连接池,同时使用了Apache的通用对象池工具common-pool作为资源的管理工具。

//另外一种初始化方式
Jedis jedis = null;
try {
   //连接池有很多参数可以设置
   GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
   JedisPool jedisPool = new JedisPool(poolConfig, HOST, PORT);

   jedis = jedisPool.getResource();
   //host port connectionTimeOut soTimeOut
   new Jedis(HOST, PORT, 100, 100);
   jedis.set(KEY, "arab");

   String name = jedis.get(KEY);
   System.out.println(name);
} catch (Exception e) {

} finally {
   //把连接还给连接池
   jedis.close();
}

参数含义

4.3 客户端管理

Redis提供了客户端相关API对其状态进行监控和管理。
客户端API
1. client list
列出与Redis服务器相连的所有客户端连接信息;
其中列出的信息中:
1-1 输入缓冲区:
Redis为客户端分配了输入缓冲区(将客户端发送的命令临时保存,redis会从输入缓缓冲区拉去命令并执行),输入缓冲区为客户端发送明星到redis执行命令提供了缓冲功能;
qbuf:缓冲区总容量
qbuf-free:剩余容量
以上两个不支持修改,要求每个客户端缓冲区大小不能超过1G,超过客户端将被关闭。
示意图·
使用不当会产生两个问题:
1、缓冲区超过1G,client会被关闭;
2、输入缓冲区不受maxMemory控制,如果超内存使用可能会产生数据丢失、键值淘汰、OOM等情况。
造成缓冲区过大的几个原因:
1、redis处理速度跟不上输入缓冲区的输入速度,并且每次进入缓冲区的命令包含了大量bigkey;
2、redis发生了阻塞,短期内不能处理命令,造成客户端输入命令积压;
如何解决这些问题?
1、定期执行client list命令,手机qbuf和qbuf-free找到异常链接记录并分析
2、通过info命令的info client模块,找到最大的输入缓冲区,可以设置一下报警;
两种方法比较
比较
1-2 输出缓冲区:obl oll omem
Redis为每个客户端分配了输出缓冲区(保存命令执行的结果返回给客户端),为redis和客户端交互返回结果提供缓冲。
示意图
注意:可以通过client-output-buffer-limit进行设置,可以按照客户端的不同分为:普通客户端、发布订阅客户端、slave客户端。
client-output-buffer-limit
如果缓冲区大于,客户端立即被关闭;如果超过了softlimit并持续了softseconds秒,客户端被关闭;
和输入缓冲区相同的是,输出缓冲区也不会收到maxmemory的限制,如果使用不当会造成maxmemory用满产生的数据丢失、键值淘汰、OOM等。
实际上输出缓冲区有2部分组成:固定缓冲区(16KB)和动态缓冲区。
固定缓冲区返回比较小的执行结果;动态缓冲区返回比较大的结果。
固态缓冲区放满后会放在动态缓冲区中。
示意图
obl:固定缓冲区长度
oll:动态缓冲区列表长度
omem:使用的字节数
监控输出缓冲区的方法和输入缓冲区一样。
1-3 客户端的存活状态
age:当前客户端已经连接的时间
idle:最近一次的空闲时间
1-4 客户端的限制 maxclients和timeout
maxclients:最大客户端连接数(一旦超过,连接将被拒绝,默认10000)
tmeout:限制连接的最大空闲时间,一旦超过将被关闭(默认为0 不关闭);
1-5 客户端类型
客户端类型
1-6 其他
其他参数
2. client setName和client getName
为客户端设置名称、获取客户端名称
3. client kill
client kill ip:port 杀掉指定ip地址和端口的客户端
4. client pause
client pause timeout(毫秒) 阻塞客户端timeout毫秒数,在此期间客户端连接将被阻塞。
注意:
1、只对普通和发布订阅客户端有效,对于主从复制无效。
2、可以用一种可控的方式将客户端从一个redis节点切换到另一个redis节点
3、在生产环境中暂停客户端成本非常高。
5. monitor
用于监控redis正在执行的命令。
监控
使用monitor有风险,因为monitor客户端能够监听所有的命令,一旦redis并发过大,monitor客户端的输出缓冲会暴涨,可能会瞬间占用大量的内存。
客户端相关配置
配置

4.5 客户端常见异常

无法从连接池获取到连接
JedisPool中保存有限个Jedis。
maxWaitMills:等待时间,如果获取不到资源,抛异常;
或者设置blockWhenExhausted=false,获取不到直接异常;
为什么连接池没有资源?
1、服务端:连接池设置过小;
2、客户端:没有正确使用连接池,如没有进行释放。
3、客户端:存在慢查询,造成jedis对象归还速度比较慢;
4、服务端:客户端命令执行过程阻塞;
客户端读写超时
原因:
1、超时时间设置过短
2、命令本身比较慢
3、客户端与网络端网络不正常
4、redis自身发生阻塞
客户端连接超时
原因:
1、连接超时设置过短;
2、redis发生阻塞造成tcp-backlog已满,造成新的连接失败;
3、客户端与服务端网络不正常;
客户端缓冲区异常
原因:
1、输出缓冲区满;(例如输出缓冲区为1M,但是获取了一个bigkey 3M)
2、长时间闲置连接被服务器主动断开;
3、不正常并发读写;
Lua脚本正在执行
redis正在执行lua脚本,并且超过了lua-time-limit,如果此时调用redis,会收到异常
redis正在加载持久化文件
jedis调用redis时,如果redis正在加载持久化文件,会受到异常
redis使用的内存超过maxmemory
Jedis执行写操作,如果使用内存大于maxmemory,会受到异常;
客户端连接数过大
如果客户端连接数大于maxclients;

第5章 持久化

5.1 RDB

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,可以手动也可以自动触发
触发机制
手动触发分别对应save和bgsave命令。
save:阻塞当前redis服务器,直到RDB过程完成为止,对于内存较大的实例会造成长时间阻塞,线上不建议使用。
bgsave:redis进程执行fork操作创建子线程,TDB持久化过程由子进程负责,完成后自动结束,阻塞只发生在fork阶段,一般时间很短。
bgsave是对save的优化,所以redis内部涉及RDB的操作都采用bgsave方式,save已经废弃。
以下方式会自动触发RDB:
1、使用save的相关配置,save m n(在m秒内存在n此修改时,自动猝发bgsave);
2、如果从节点执行全量复制操作,主节点自动执行bgsave生成rdb文件并发送给从节点;
3、指定debugload命令重新加载redis时;
4、默认情况下执行shutdown命令时,如果没有开启AOF持久化功能,则自动执行bgsave。
流程说明
bgsave是主流触发rdb持久化方式
运转流程
流程
RDB的优缺点
优点:
RDB是一个紧凑的二进制文件,代表redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景用于灾难恢复。加载Rdb恢复数据远快于AOF方式。
缺点:
1、无法做到实时持久化/秒级持久化(因为每次都要fork,属于重量级操作)
2、redis演进过程中有多个格式rdb版本,存在老版本redis无法兼容新版rdb格式的问题
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式。

5.2 AOF

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。
AOF主要作用是解决了数据持久化的实时性,目前已经是Reis持久化的主流方式。
使用AOF
开启AOF功能需要设置配置:appendonly yes,默认不开启。
appendiflename:设置AOF文件名,默认是appendonly.aof。
AOF工作流程操作
工作流程
1、命令写入:
写入的内容直接是文本协议格式。
2、文件同步:
Redis提供了多种AOF缓冲区同步文件策略,由参数appensync控制:
命令
wirte:触发延迟写机制。wirte写后直接返回,同步硬盘操作依赖于系统调度机制(缓冲区页空间满或达到特定时间周期)
fsync:针对单个文件操作,做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。
eveysec是建议的同步策略,也是默认配置(理论上只有系统突然宕机情况下丢失1秒的数据)。
3、重写机制:用来压缩越来越大的文件
AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
为什么重写后AOF文件会变小?
1)进程内已经超时的数据不在写入文件;
2)旧的AOF文件含有无效命令,如del key1等,重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令;
3)多条写命令可以合并为一个,如lpush list a, lpush list b
AOF可以手动触发(bgrewriteaof)和自动触发(根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage)
手动触发
AOF重写发生的事情:
重写流程
4、重启加载:
重新加载流程
5、文件校验:
加载损坏的AOF时会拒绝启动;
对于格式错误的AOF文件,先进行备份,然后采用redis-check-aof-fix命令进行修复,修复后使用diff-u对比数据的差异,找出丢失的数据,有些可以人工修改补全。
如果AOF文件结尾不完整,可以使用aof-load-truncated配置来兼容这种情况,默认开启。

5.3 问题定位与优化

持久化非常影响Redis的性能,所以针对持久化常见出现的问题进行分析、定位和优化。
fork操作
无论是RDB还是AOF,操作系统都要做一次fork操作,而fork操作本身是一个重量级的操作(会复制父进程的内存表,跟父进程占用的内存正相关,虚拟机性能更差)。
改善:
1.优先使用物理机或者高效支持fork操作的虚拟机;
2.控制Redis进程最大可用内存,线上建议每个Redis实例内存控制在10GB内;
3.合理配置linux内存分配策略,避免内存不足导致fork失败;
4.降低fork操作频率;
子进程开销监控和优化
子进程负责AOF或RDB文件重写,主要涉及CPU、内存、硬盘三部分的消耗。
cpu优化:
不要做绑定单核cpu操作。
内存优化:
如果部署多个redis实例,尽量保证同一时刻只有一个子进程在工作(隔离部署);
避免在大量写入时做子进程重写操作(内存消耗过大)(避免在高峰时间段进行操作)
硬盘优化:
1.避免和高硬盘负载服务部署在一起;
2.在aof重写期间避免fsync操作;
3.避免使用机械硬盘;
4.如有多个redis实例,配置不同实例分盘存储aof文件;
AOF追加阻塞
开启aof持久化时,常用的同步硬盘策略是everysec。(redis使用另外一条线程每秒整形fsync同步硬盘,当系统硬盘资源繁忙时,会造成redis主线程阻塞。)
1、主线程写入AOF缓冲区
2、AOF线程执行一次同步磁盘操作,记录最近一次同步时间。
3、主线程负责对比上次AOF同步时间(上次同步时间在2秒内,主线程直接返回,大于2秒,阻塞直至完成);
优化的方式和硬盘优化方式一样

5.4 多实例部署

Redis单线程架构导致无法充分利用CPU多核性能,通常做法是在一台机器上部署多个Redis实例,但是多个实例存在的一个问题是必须会产生对CPU和IO的竞争。
宗旨是:保证机器内每个Redis实例AOF重写串行化执行
使用的方法是:
1、外部程序定时轮询监控机器上所有Redis实例;
2、对于开始AOF的实例,确认增长率;
3、当增长率超过特定阈值,执行gbrewriteaof命令手动触发当前实例的AOF重写;
4、运行检查期间循环检查aof_rewrite_in_progress和aof_current_rewrite_time_sec指标,直到AOF重写结束;
5、确认实例AOF重写完成后,在检查其他实例并重复2-4部操作。

第6章 复制

在分布式系统中为了解决单点问题,通常会把数据复制到个副本部署到其他机器,满足故障恢复和负载均衡等需求。
Redis为我们提供了复制功能,实现了相同数据的多个Redis副本。

6.1 配置

建立复制
每个节点只能有一个主节点,而主节点可以有多个从节点。
数据流是单向的,从主节点流向从节点。
配置的三种方式
[image:546104EA-8F88-4016-86FA-9195F8996F57-19775-0007170F84B16D4F/7D01508B-A7D2-4FD1-BAD7-EA73A711C0DD.png]
slave本身是异步命令,后续同步流程在节点内部异步执行。
info replication命令查看复制相关状态
断开复制
断开复制状态:执行slave of no one命令
断开复制会执行2个操作:
1、断开与主节点复制关系
2、从节点晋升为主节点
切主操作:换一个master当主节点
1、断开与旧主的复制关系
2、与新主节点建立复制关系
3、删除从节点当前所有数据
4、对新主节点进行复制操作
传输延迟
repl-disable-tcp-nodelay参数:控制是否关闭TCP_NODELAY,默认为关闭。
控制是否合并TCP数据包,来节省带宽。
1、关闭:任何tcp包都会立即发送——delay小,但是占用带宽。
2、开启:合并小的tcp数据包——节省带宽,delay变大。

6.2 拓扑

Redis复制拓扑可支持单层或多层复制关系。
1、一主一从
2、一主多从
3、树状:可以降低主节点负载和需要传送给从节点的数据量。

6.3 原理

复制的过程
1、保存主节点信息;从节点只保存主节点的地址信息变直接返回(复制流程还没有开始)
2、主从建立socket连接:无限重试到链接成功或者执行slaveofnoone取消复制;
3、发送ping命令(1、检测主从指尖socke是否可用;2、检测主节点当前是否接受处理命令)
4、权限验证
5、同步数据集(全量同步和部分同步)
6、命令持续复制:主节点持续把写命令发送给从节点;
数据同步
Redis2.8以上使用psync完成主从数据同步,分为全量复制和部分复制。
全量复制:用于初次复制场景
部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景。
复制偏移量
主和从都会维护一份复制偏移量(已经写入命令的长度和)。每秒钟,从节点会上报自身的复制偏移量给主,所以主不仅保存自己的也保存从节点的偏移量。
通过对比可以知道主从节点数据是否一致。
复制积压缓冲区
主节点上的一个固定长度队列,默认为1MB。用于保存最近写入从的命令,用于数据的重传。
主节点运行ID:
唯一识别Redis节点
psync命令
格式:psync {runId} {offset}
命令运行流程:
1、从发送psync给主
2、主根据psync参数和自身数据情况决定响应结果:
+FULLRESYNC:全量复制
+CONTINUE:部分复制
+ERR:主节点版本低于2.8,进行全量复制
全量复制
全量复制是Redis最早支持的复制方式,也是主从第一次建立复制时必须经历的阶段。
流程
1、发送psync:psync ? -1
2、回复+FULLRESYNC
3、从节点接收主节点的响应数据保存运行ID和偏移量offset
4、主节点执行bgsave保存RDB文件到本地
5、主节点发送RDB文件给从节点(传送有可能失败:时间大于repl-timeout,所以需要合理配置)
6、在从接收RDB期间,主可以继续接收写命令,这部分写命令会写入复制客户端缓冲区内,当从加载完RDB后,主再把缓冲区的命令发送过去(缓冲区有可能溢出,需要合理配置)
7、从节点接收完主节点传过来的全部数据后清空自身旧数据。
8、从节点清空数据后开始加载RDB文件(读写分离情况下,有可能脏读,那么就要关闭slave-server-stale-data参数)
9、从节点加载完RDB后,如果开启了AOF持久化,那么会立刻做bgrewriteof操作。
部分复制
是对全量复制做出的一种优化措施。
1、主从节点间网络出现中断时,如果超过repl-timeout时间,主会认为从故障并中断复制
2、中断期间,主的命令写入复制缓冲区;
3、从连上主
4、执行psync操作,带上响应的参数
5、如果复制缓冲区中存在偏移量的数据,那么表示可以复制,否则进入全量复制
6、进行复制步骤
心跳
主从建立复制后,他们维护着一个长连接并彼此发送心跳命令。
主每10秒ping一下从;
从每1秒发送replconf ack {offset}给主
异步复制
在持续写入阶段,命令的发送是异步的。

第7章 Redis的噩梦:阻塞

7.1 发现阻塞

发现Redis异常有2种方式:
1、加日志,对异常进行统计、计算并做报警;
2、用现成的监控工具

7.2 内在原因

定位到具体的Redis节点异常后,先排查是否是Redis自身的原因导致的。
主要围绕以下三个方面进行排查:
1、API或数据结构使用不合理;
2、CPU饱和的问题;
3、持久化相关的阻塞;
API或数据结构使用不合理
1、发现慢查询:
用slowlog get {n}查询最近n条慢查询命令,针对这些命令做优化
1)修改为低算法度的命令
2)调整大对象
2、如何发现大对象:
利用Redis自带的大对象的工具 命令:redis-cli -h {ip} -p {port}
CPU饱和
单线程的Redis处理命令时只能使用一个cpu。
发现qps不是特别高但是cpu却饱和了,那么有一种情况是对内存做了过度的优化导致cpu复杂度变高。
持久化阻塞
持久化引起主线程阻塞的操作主要有:fork操作、AOF刷盘阻塞、HUgepage写操作阻塞。

第八章 理解内存

学习如何高效利用内存
从以下三个方面进行学习:
1、内存消耗分析
2、管理内存的原理与方法
3、内存优化技巧

8.1 内存消耗

内存使用的统计
我们可以使用info memory来观测相关指标
重点需要关注的是used_memory_res和used_memory以及他们的比值mem_framentation_ratio
mem_framentation_ratio>1 表示 有内存碎片,如果比值太大说明碎片太多(多出的部分并没有被用来存储数据)
mem_framentation_ratio<1 表示 内存被置换到了硬盘
内存消耗划分
redis进程内消耗主要包括:
自身内存:太小,可以忽略不计
对象内存:占用最多的一块儿,要避免使用太长的key
缓冲内存:包括客户端缓冲、复制积压缓冲、AOF缓冲区
内存碎片:redis默认的内存分配器使用jemalloc。
容易造成内存碎片的原因有:频繁的更新操作、大量过期键删除。
出现内存碎片时可以采取的手段是——数据对齐和安全重启。
子进程内存消耗
子进程是指RDB/AOF对数据做持久化。
写时复制技术:子进程和父进程共享内存页,当父进程需要写的时候将内存也取出copy一份
THP:会增加写时复制技术使用的内存量
1、redis产生的子进程并不需要消耗1倍的父进程内存,实际消耗根据期间写入命令量决定,依然要预留一些内存防止溢出
2、设置sysctl vm.overcommit-memory=1允许内核可以分配所有的物理内存,防止redis进程fork时因剩余内存不足失败
3.关闭THP

8.2 内存管理

主要通过2个手段实现内存的管理
1、设置内存上限
2、回收策略
设置内存上限
redis使用maxmemory参数限制最大可用内存,主要目的是:
1、超出内存上限时,使用LRU释放空间
2、防止所用内存超过服务器物理内存
动态调整内存上限
可以使用config set memory对内存上限进行动态修改
内存回收策略
主要包括
1、删除过期键对象:
删除过期对象的策略:如果redis一直监控着键是否过期消耗太大,所以redis使用以下两种策略相结合的方式来对过期的键值进行删除
1)惰性删除:当客户端读取带有超时属性的键时,如果已经超过键设置的过期时间,会执行删除操作并返回空;
2)定时任务删除:redis内部维护一个定时任务,默认每秒运行10次(可配);定时任务中删除过期键逻辑采用了自适应算法(每次检查20个key,如果过期超过25%那么继续执行,直到25%以下),根据键的过期比例,使用快慢两种速率模式(超时时间不一样快1毫秒 慢25毫秒)回收键。
2、内存使用达到maxmemory上限时触发内存溢出控制策略:
当redis所用内存达到maxmemory上限时会触发响应的溢出控制策略。具体策略受maxmemory-policy参数控制。
Redis支持6种策略。通过config set maxmemory-policy设置。
noeviction:默认,不删除任何数据,拒绝所有写入操作并返回错误。
volatile-lru:根据lru删除设置了超时属性的键,直到腾出足够空间,如果没有可删的,使用noeviction。
allkeys-lru:根据lru不管数据有没有设置超时属性,直到有足够空间。
allkeys-random:随机删除所有键,直到腾出足够空间。
volatile-random:随机删除过期键,直到腾出足够空间。
volatile-ttl:根据键值对象的ttl属性,删除最近将要过期数据,如果没有会提到noeviction。

内存优化技巧

1、关闭vm选项,即vm-enabled 为 no;
2、Redis Hash是value内部为一个HashMap,如果该Map的成员数比较少,则会采用类似一维线性的紧凑格式来存储该Map, 即省去了大量指针的内存开销,配置如下:
hash-max-zipmap-entries 1024 成员数量大于将会采用hashmap形式 解决方法是分段
hash-max-zipmap-value 512 :key的字节大于512字节将会转成hashmap 解决方法是md5加密
HashMap的优势就是查找和操作的时间复杂度都是O(1)的,而放弃Hash采用一维存储则是O(n)的时间复杂度
list-max-ziplist-entries 512
list数据类型多少节点以下会采用去指针的紧凑存储格式。
list-max-ziplist-value 64
list数据类型节点值大小小于多少字节会采用紧凑存储格式。
set-max-intset-entries 512
set数据类型内部数据如果全部是数值型,且包含多少节点以下会采用紧凑格式存储。
3、Redis内部实现没有对内存分配方面做过多的优化,在一定程度上会存在内存碎片,不过大多数情况下这个不会成为Redis的性能瓶颈,不过如果在Redis内部存储的大部分数据是数值型的话,Redis内部采用了一个shared integer的方式来省去分配内存的开销,即在系统启动时先分配一个从1~n 那么多个数值对象放在一个池子中,如果存储的数据恰好是这个数值范围内的数据,则直接从池子里取出该对象,并且通过引用计数的方式来共享,这样在系统存储了大量数值下,也能一定程度上节省内存并且提高性能,这个参数值n的设置需要修改源代码中的一行宏定义REDIS_SHARED_INTEGERS,该值默认是10000,可以根据自己的需要进行修改,修改后重新编译就可以了。

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值