后端开发技能学习(十)redis学习(中篇)

文章目录

  • 后端开发技能学习(十)redis学习(中篇)
      • 客户端
          • Java客户端jedis
          • python客户端redis-py
      • 持久化
          • RDB
          • AOF
          • 问题定位与优化
          • 多实例部署
      • 复制
          • 配置
          • 原理
      • 阻塞
          • 发现阻塞
          • 内因
          • 外因

后端开发技能学习(十)redis学习(中篇)

客户端

redis中的客户端与服务端通信采用的RESP协议,该协议基于TCP协议,协议格式如下:
发送命令格式:

*<参数数量> CRLF
$<参数1的字节数量> CRLF
<参数1> CRLF
...
$<参数N的字节数量> CRLF
<参数N> CRLF

# 例如 set redis hello 的格式如下:
# $3
# SET
# $5
# redis
# $5
# hello

返回结果格式:

# 状态回复:
+OK
# 错误回复
-<错误信息>
# 整数回复
:<整数>
# 字符串回复
$<字符串>
# 多条字符串回复
*<多条字符串>
Java客户端jedis

Java有很多优秀的Redis客户端(详见:http://redis.io/clients#java),这里介绍使用较为广泛的客户端Jedis。
jedis请自行安装
1.基本使用

Jedis jedis = null;
try {
	jedis = new Jedis("127.0.0.1", 6379); // 连接ip和端口
	// 2. jedis执行set操作  
	jedis.set("hello", "world");
	// 3. jedis执行get操作, value="world"
	String value = jedis.get("hello");
} catch (Exception e){
	logger.error(e.getMessage(), e);
} finally {
	if (jedis != null) {
	jedis.close();
}

2.连接池使用方式
上述介绍的是Jedis的直连方式,每次连接都会新建一个TCP连接,使用后再断开连接。
实际生产环境中一般使用连接池的方式对Jedis连接进行管理,所有Jedis对象预先放在池子中(JedisPool),每次要连接Redis,只
需要在池子中借,用完了在归还给池子。以下是两种方式的优缺点
后端开发技能学习(十)redis学习(中篇)_第1张图片
Jedis提供了JedisPool这个类作为对Jedis的连接池,同时使用了Apache的通用对象池工具common-pool作为资源的管理工具,下面是使用JedisPool操作Redis的代码示例:

GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); // common-pool连接池配置,这里使用默认配置
JedisPool jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379); // 初始化连接池
Jedis jedis = null;
try { // 1. 从连接池获取jedis对象
    jedis = jedisPool.getResource();
    // 2. 执行操作
    jedis.set("java", "hello!");
    String java = jedis.get("java");
    System.out.println(java);
} catch (Exception e) {
    System.err.println(e.getMessage());
} finally {
    if (jedis != null) { // 如果使用JedisPool,close操作不是关闭连接,代表归还连接池
        jedis.close();
    }
}

前面GenericObjectPoolConfig使用的是默认配置,实际它提供有很多参数,如下:

GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
// 设置最大连接数为默认值的5倍
poolConfig.setMaxTotal(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL * 5);
// 设置最大空闲连接数为默认值的3倍
poolConfig.setMaxIdle(GenericObjectPoolConfig.DEFAULT_MAX_IDLE * 3);
// 设置最小空闲连接数为默认值的2倍
poolConfig.setMinIdle(GenericObjectPoolConfig.DEFAULT_MIN_IDLE * 2);
// 设置开启jmx功能
poolConfig.setJmxEnabled(true);
// 设置连接池没有连接后客户端的最大等待时间(单位为毫秒)
poolConfig.setMaxWaitMillis(3000);

更多参数见下表

后端开发技能学习(十)redis学习(中篇)_第2张图片

3.在Jedis中使用pipeline8
上一篇博客我们介绍了Redis的Pipeline工程,但还没有介绍具体实现,其在Jedis中的实现如下:

// 这里没有关闭jedis
String[] keys = {"d1","d2","d3"};
Jedis jedis = new Jedis("127.0.0.1");
// 1)生成pipeline对象
Pipeline pipeline = jedis.pipelined();
// 2)pipeline执行命令,注意此时命令并未真正执行
for (String key : keys) {
pipeline.set(key,key);
}
// 3)执行命令
pipeline.sync();

其中 pipeline.set() 方法并不会立刻执行,而是将该命令封装到pipeline对象中,pipeline.sync()才会真正执行。
除了pipeiline.sync() , jedis还提供了 pipeline.syncAndReturnAll() 返回命令的结果。
4.在Jedis中使用Lua脚本
待跟新。。。

python客户端redis-py

待更新。。。

持久化

Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。理解掌握持久化机制对于Redis运维非常重要。

RDB

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发。
1.手动触发
手动触发包含save和bgsave命令,save命令会阻塞当前Redis服务器直到持久化完成,而bgsave命令会创建子进程完成持久化操作,使阻塞只发生在fork阶段,时间很短。
2.自动触发
自动持久化会在以下场景触发
1)使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
2)如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点,更多细节见6.3节介绍的复制原理。
3)执行debug reload命令重新加载Redis时,也会自动触发save操作。
4)默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。

bgsave命令是最主流的RDB持久化方式,流程如下所示:
后端开发技能学习(十)redis学习(中篇)_第3张图片
RDB持久化优点
1.RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
2.Redis加载RDB恢复数据远远快于AOF的方式。
RDB的缺点:
1.RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
2.RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。

AOF

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。
AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。理解掌握好AOF持久化机制对我们兼顾数据安全性和性能非常有帮助。
开启AOF功能需要设置配置:appendonly yes,默认不开启。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化方式一致,通过dir配置指定。AOF的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如下图所示。
后端开发技能学习(十)redis学习(中篇)_第4张图片
1.文件写入
AOP命令写入的内容是redis命令的RESP协议格式(前面有提到)。
优点:兼容性、可读性强
2.文件同步
Redis提供了多种AOF缓冲区同步文件策略,由参数appendfsync控制,如下所示:

后端开发技能学习(十)redis学习(中篇)_第5张图片

配置为always时,每次写入都要同步AOF文件,在一般的SATA硬盘上,Redis只能支持大约几百TPS写入,显然跟Redis高性能特性背道而驰,不建议配置。
配置为no,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。
配置为everysec,是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性。理论上只有在系统突然宕机的情况下丢失1秒的数据。
3.重写机制
AOF提供了重写机制压缩aof文件的体积,有以下方式:
1)进程内已经超时的数据不再写入
2)删除无效命令
3)合并多条命令
AOF的重写可以通过 bgrewriteaof命令手动触发,也可以通过设置据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。

在恢复数据时,redis会优先加载AOF文件,只有aof关闭或者aof文件不存在时才加载RDB文件。

问题定位与优化

待更新。。。

多实例部署

待更新。。。

复制

在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。Redis也是如此,它为我们提供了复制功能,实现了相同数据的多个Redis副本。复制功能是高可用Redis的基础,后面章节的哨兵和集群都是在复制的基础上实现高可用的。复制也是Redis日常运维的常见维护点。

配置

参与复制的Redis实例划分为主节点(master)和从节点(slave)。默认情况下,Redis都是主节点。每个从节点只能有一个主节点,而主节点可以同时具有多个从节点。复制的数据流是单向的,只能由主节点复制到从节点。配置复制的方式有以下三种:
1)在配置文件中加入slaveof{masterHost}{masterPort}随Redis启动生效。
2)在redis-server启动命令后加入–slaveof{masterHost}{masterPort}生效。
3)直接使用命令:slaveof{masterHost}{masterPort}生效。

例如:
有两个端口为6379和6380的节点,可以在6380下使用

slaveof 127.0.0.1 6379

建立复制关系
查看复制关系:

127.0.0.1:6379>info replication
# role:master
# connected_slaves:1
# slave0:ip=127.0.0.1,port=6379,state=online,offset=43,lag=0
...
127.0.0.1:6380>info replication
# role:slave
# master_host:127.0.0.1
# master_port:6380
# master_link_status:up
# master_last_io_seconds_ago:4
# master_sync_in_progress:0

断开所有复制:

slaveof no one 

从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化。

原理

待更新。。。

阻塞

Redis是典型的单线程架构,所有的读写操作都是在一条主线程中完成的。当Redis用于高并发场景时,这条线程就变成了它的生命线。如果出现阻塞,哪怕是很短时间,对于我们的应用来说都是噩梦。导致阻塞问题的场景大致分为内在原因和外在原因:
内在原因包括:不合理地使用API或数据结构、CPU饱和、持久化阻塞等。
外在原因包括:CPU竞争、内存交换、网络问题等。

发现阻塞

当Redis阻塞时,线上应用服务应该最先感知到,这时应用方会收到大量Redis超时异常,比如Jedis客户端会抛出JedisConnectionException异常。常见的做法是在应用方加入异常统计并通过邮件/短信/微信报警,以便及时发
现通知问题。开发人员需要处理如何统计异常以及触发报警的时机。
更为细节的部分待更新。。。

内因

1.API或数据结构使用不合理
Redis原生提供慢查询统计功能(见上一篇博客)。发现慢查询后,开发人员需要作出及时调整。可以按照以下两个方向去
调整:
1)修改为低算法度的命令,如hgetall改为hmget等,禁用keys、sort等命令。
2)调整大对象:缩减大对象数据或把大对象拆分为多个小对象,防止一次命令操作过多的数据。

另外,Redis也提供发现大对象的工具(上一篇博客也有讲)

redis-cli --bigkeys

2.cpu饱和
单线程的Redis处理命令时只能使用一个CPU。而CPU饱和是指Redis把单核CPU使用率跑到接近100%。在linux中,使用top命令很容易识别出对应Redis进程的CPU使用率。
使用统计命令redis-cli-h{ip}-p{port}–stat可以获取当前Redis使用情况,该命令每秒输出一行统计信息,运行效果如下:
后端开发技能学习(十)redis学习(中篇)_第6张图片3.持久化阻塞
对于开启了持久化功能的Redis节点,需要排查是否是持久化导致的阻塞。持久化引起主线程阻塞的操作主要有:fork阻塞、AOF刷盘阻塞、HugePage写操作阻塞。

外因

1.cpu竞争
绑定CPU:部署Redis时为了充分利用多核CPU,通常一台机器部署多个实例。常见的一种优化是把Redis进程绑定到CPU上,用于降低CPU频繁上下文切换的开销。
但是当Redis父进程创建子进程进行RDB/AOF重写时,如果做了CPU绑定,会与父进程共享使用一个CPU。子进程重写时对单核CPU使用率通常在90%以上,父进程与子进程将产生激烈CPU竞争,极大影响Redis稳定性。因此对于开启了持久化或参与复制的主节点不建议绑定CPU。
2.内存交换
内存交换(swap)对于Redis来说是非常致命的,Redis保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把Redis使用的部分内存换出到硬盘,由于内存与硬盘读写速度差几个数量级,会导致发生交换后的Redis性能急剧下降。识别Redis内存交换的检查方法如下:
1)查询redis进程号

redis-cli -p 6383 info server | grep process_id
# process_id:4476

2)根据进程号查询内存交换信息

cat /proc/4476/smaps | grep Swap
# Swap: 0 kB
# Swap: 0 kB
# Swap: 4 kB
# Swap: 0 kB
# Swap: 0 kB
.....

如果交换量都是0KB或者个别的是4KB,则是正常现象,说明Redis进程内存没有被交换。预防内存交换的方法有:
·保证机器充足的可用内存。
·确保所有Redis实例设置最大可用内存(maxmemory),防止极端情况下Redis内存不可控的增长。
·降低系统使用swap优先级
3.网络问题
网络问题经常是引起Redis阻塞的问题点。常见的网络问题主要有:连接拒绝、网络延迟、网卡软中断等。
详情待更新。。。

你可能感兴趣的:(开发相关技术,数据库相关,redis,数据库,java)