pipeline、lua、redis事务以及操作springboot操作redis的一些学习以及思考

1、jedis操作完为什么要调用close,StringRedisTemplate也要close吗?为什么

ai答:在使用 Jedis 进行 Redis 操作时,调用 jedis.close() 的目的是释放与 Redis 服务器的连接并关闭 Jedis 实例,以便释放资源并确保连接的正确关闭。

具体原因如下:

  1. 资源释放:调用 jedis.close() 方法会自动释放 Jedis 实例占用的资源,包括网络连接、线程资源等。如果不调用 close() 方法,这些资源可能会一直保持占用状态,导致资源泄漏。
  2. 连接池管理:在使用连接池时,调用 close() 方法会将 Jedis 实例归还给连接池,以供后续的复用。否则,如果不归还连接,可能会导致连接池耗尽而无法提供更多的连接,从而无法执行后续的操作。这对于高并发的应用尤为重要。
  3. 请求完成:当 Jedis 实例的任务完成后,通过关闭 Jedis 实例可以显式地结束与 Redis 的会话,并告知 Redis 连接已经不再需要。这也有助于减轻 Redis 服务器的负担。

至于 StringRedisTemplate,它是 Spring Data Redis 提供的 Redis 操作模板类,实际上内部也是使用 Jedis 或 Lettuce 来实现 Redis 的操作。但是与 Jedis 不同,StringRedisTemplate会自动管理底层的连接和释放,无需手动调用 close() 方法来释放资源。在 Spring 容器中,StringRedisTemplate是通过连接工厂来管理连接的。

因此,在使用 StringRedisTemplate 进行 Redis 操作时,通常不需要手动调用 close() 方法来关闭连接。连接的打开和关闭将由 Spring 框架自动管理,以确保资源的正确释放。

我的理解:
也就是说两者都会close,z只不过StringRedisTemplate底层会通过spring管理进行close

2、StringRedisTemplate和RedisTemplate他们使用的都是同一个数据源,同时注入springboot里会产生冲突吗?

ai答:

在 Spring Boot 中,StringRedisTemplateRedisTemplate 是用来操作 Redis 的两个不同模板类。它们通常使用相同的 Redis 数据源,但是它们的用途和操作方式是不同的。

如果同时注入 StringRedisTemplateRedisTemplate,通常不会产生冲突。因为 Spring Boot 会为每个模板类提供不同的操作类型和数据转换器。

StringRedisTemplate 是针对操作 Redis 字符串类型数据(String),底层使用的是 Redis 字符串操作命令。它的操作方法返回的结果类型是 String
RedisTemplate 是一个通用的 Redis 操作模板,可以用来操作任何类型的数据。它使用的是 Redis 对象序列化方式来处理数据的存取。你可以使用 RedisTemplate 的各种操作方法来操作各种复杂的 Redis 数据类型,如 Hash、List、Set、Sorted Set 等。

因此,StringRedisTemplateRedisTemplate 在使用时一般不会冲突,因为它们是针对不同的数据类型和操作的。

当你需要操作 Redis 字符串类型数据时,使用 StringRedisTemplate;当你需要更灵活地操作 Redis 并处理各种数据类型时,使用 RedisTemplate。在配置中使用不同的模板类按需注入即可。

我的理解:
通过ai的回答,大概就是说StringRedisTemplate和 RedisTemplate 同时使用不会冲突,哪怕是连接同一个数据源,两者是不同的bean对象

3、Java使用pipeline来操作redis时,对redis节点而言时异步还是同步?会不会阻塞redis其他操作?

ai答:

在使用 Pipeline 来操作 Redis 时,对 Redis 节点而言是异步的。Pipeline 允许我们将多个 Redis 命令打包成一个批处理(batch)发送到 Redis 服务器,并在一次网络往返中进行处理。这样可以显著减少网络开销和延迟,并且提高 Redis 的性能。

具体来说,Pipeline 的工作原理是将多个命令发送到 Redis 服务器后,不等待服务器返回结果,而是将所有命令依次写入到缓冲区中。当所有命令都写入到缓冲区后,再通过一次网络通信将缓冲区的内容发送给 Redis 服务器。这样可以减少每次命令的网络通信开销和延迟。

由于 Pipeline 采用批处理方式,所以它在执行期间不会进行等待和阻塞,而是将所有命令尽快发送给 Redis 服务器。这意味着其他操作可以在 Pipeline 执行期间继续进行,不会被阻塞。当 Redis 服务器执行完批处理中的所有命令后,将一次性返回所有命令的结果。

需要注意的是,虽然 Pipeline 本身是异步的,但 Redis 单节点在执行 Pipeline 的过程中仍然是同步的。这是因为 Redis 是单线程的,一次只能执行一个操作,无论是单独的命令还是 Pipeline 中的命令。因此,在执行 Pipeline 期间,其他操作仍然需要等待 Pipeline 中的命令执行完毕才能执行。

综上所述,使用 Pipeline 来操作 Redis 是异步的,不会阻塞 Redis 的其他操作。但是,在 Redis 单节点上执行 Pipeline 仍然是同步的,一次只能执行一个操作。如果需要同时执行多个不相关的操作,可以考虑使用多个 Redis 连接或者使用 Redis 集群来实现并发操作。

我的理解:

通过这些描述,应就知道了redis单节点在执行pipeline命令时是会阻塞其他操作的。

4、Java使用pipeline技术来操作redis会不会出现部分命令执行失败的情况?如何确保全部pipeline里的命令全部执行成功?redis是否具有事务?

ai答:

在使用 Pipeline 进行批量命令操作时,如果中途出现了网络故障或其他异常情况,可能会导致部分命令执行失败。Redis Pipeline 本身并不提供保证所有命令都能成功执行的机制。

为了确保全部 Pipeline 中的命令都执行成功,可以在执行完 Pipeline 后,通过检查每个命令的返回结果来确认命令是否成功执行。如果返回结果中有错误信息(如 nil 或错误码),则视为命令执行失败。

示例代码如下:

java

复制

RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.openPipeline();

for (int i = 0; i < commands.size(); i++) {
    connection.set(key[i], value[i]);
}

List<Object> results = connection.closePipeline();

// 检查每个命令的执行结果
for (Object result : results) {
    if (result instanceof RedisException) {
        // 命令执行失败
        // 处理失败逻辑
    }
    // 处理成功逻辑
}

需要注意的是,这种方式只能检查到在执行 Pipeline 期间出现的错误,对于执行后出现的其他错误或异常情况是无法捕获的。因此,建议在实际应用中使用 Pipeline 时,需要在业务逻辑上做好异常处理,以应对可能出现的失败情况。

关于 Redis 是否具备事务功能,Redis 提供了 MULTI 和 EXEC 命令用于实现事务。通过 MULTI 命令开启事务,并在 EXEC 命令中提交事务。在事务执行期间,Redis 会按照顺序执行事务中的命令,保证操作的原子性。

然而,需要特别注意的是,Redis 的事务并不是 ACID(原子性、一致性、隔离性和持久性)的事务。在 Redis 执行事务期间,其他客户端仍然可以读取和修改相关的键值对。事务中的命令也不会回滚,即使其中某个命令执行失败,也会继续执行后续的命令。

综上所述,使用 Pipeline 进行批量命令操作时,需要根据返回结果来确认命令是否执行成功。Redis 提供了事务功能,但它的特性与传统 ACID 事务不完全一致,需要根据具体场景来选择是否使用事务。

我的理解:

通过ai的回答,也就是说

1、pipeline在传输过程中如果故障,那么可能导致返回的redis返回的数据因为部分原子行命令全部丢失导致返回的数据量过少或者说由于一些redis命令因为部分原子行命令残缺分而导致报错。

2、redis执行pipeline里的命令集可能出错,有可能出现返回nil或者其他错误信息而导致报错。

这种都是想要服务器进行异常捕获和处理的,所以说对于pipeline的使用还是要谨慎的,好像有些代码没经过处理,晚点要去完善一下。

5、redis的事务有什么特点?一般在哪里使用的?什么场景下使用这种事务功能?redis的事务可以确保多个命令同时执行成功吗?会不会回滚呢?

ai答:

Redis的事务具有以下特点:

  1. 原子性:Redis事务中的一系列操作要么全部执行成功,要么全部失败,不会部分执行。

  2. 单线程执行:Redis事务在执行期间是串行的,客户端可以连续发送多个命令到服务器,但是这些命令会按顺序被串行执行。

  3. 隔离性:Redis事务在执行期间隔离其他客户端的操作,事务中的命令不会被其他命令打断。

  4. 延迟执行:Redis事务中的命令都会先被放入一个队列中,只有在执行EXEC命令时才会一次性执行。

需要注意的是,Redis的事务机制并不提供回滚功能。一旦事务被提交,其中的所有命令都会按顺序执行,即使其中某个命令失败,也不会回滚已经执行的命令。

Redis的事务通常在以下场景中使用:

  1. 批量操作:将多个命令按顺序组织为一个事务,减少网络通信和服务器响应延迟。

  2. 原子性操作:通过将多个操作包装成一个事务,确保这些操作要么全部成功,要么全部失败。

  3. 简化并发控制:利用Redis事务的隔离性特点,可以在高并发场景下简化并发控制的复杂性。

  4. 减少网络开销:通过使用事务,可以减少与服务器之间的通信次数,提高性能。

需要特别注意的是,Redis的事务并不像传统关系型数据库中的事务一样提供ACID特性中的持久性和隔离性,事务执行中的命令也不会自动回滚。因此,在使用Redis事务时,需要仔细考虑业务逻辑和出错处理,以确保满足应用需求。

我的理解:

感觉差点意思有待验证

感觉ai答的不够到位,要去看文章,找资料去查看一下redis的事务。应该是回滚就全部执行失败?另外提交后可能会出现部分成功部分失败?

6、Java使用pipeline技术操作redis以及使用Lua脚本操作redis有什么区别?哪个更好?各种的优缺点是什么?分别适用于什么场景?

Java使用 Pipeline 技术操作 Redis 主要是通过管道将多个命令一次性发送给服务器,并在返回时一并接收结果,可以减少网络延迟,提高性能。Pipeline 适用于批量操作和提升吞吐量的场景。

Lua 脚本是 Redis 内建的脚本语言,可以通过执行 Lua 脚本来实现复杂的操作和业务逻辑。使用 Lua 脚本操作 Redis 的好处是可以将复杂的操作封装为一个脚本,减少网络开销,提高性能。Lua 脚本适用于需要对 Redis 进行复杂计算和处理的场景。

下面是两种方法的优缺点和适用场景的总结:

Pipeline 的优点:

  • 批量操作:Pipeline 适用于需要批量执行多个命令的场景,通过一次性发送多个命令,减少了客户端与服务器之间的通信次数。
  • 提升吞吐量:Pipeline 可以在一次请求中发送多个命令,减少了网络延迟时间,提高了性能。
  • 简单易用:使用 Java 客户端的 Pipeline API 相对简单,易于理解和维护。

Pipeline 的缺点:

  • 无回滚机制:Pipeline 无法保证所有命令都执行成功,需要手动检查每个命令的执行结果。

Lua 脚本的优点:

  • 复杂计算处理:Lua 脚本可以编写复杂的逻辑,结合 Redis 的数据结构和操作,实现更灵活和强大的功能。
  • 原子性操作:Lua 脚本执行是原子性的,可以保证多个命令在一个单独的 Lua 脚本中依次执行,确保操作的原子性。

Lua 脚本的缺点:

  • 学习和编写成本:Lua 脚本相对于 Java 代码来说,需要学习新的语法和规则,在编写和调试方面有一定的门槛。
  • 可读性和可维护性:Lua 脚本相对于 Java 代码来说,可读性和可维护性较低,不容易理解和修改。

根据场景的不同,可以根据以下几个维度进行选择:

  • 批量操作和提升吞吐量:如果需要批量执行多个命令和提升系统的吞吐量,建议使用 Pipeline。
  • 复杂计算和业务逻辑:如果需要进行复杂的计算和处理,建议使用 Lua 脚本。

通常情况下,Pipeline 和 Lua 脚本可以结合使用,根据具体要求和场景选择适合的方法。

我的感受:

对于以下的回答:

Pipeline 的缺点:

- 无回滚机制:Pipeline 无法保证所有命令都执行成功,需要手动检查每个命令的执行结果。

感觉确实是这样除非可以在pipeline命令集也加上事务来确保,不过好像很难加上事务,几乎加不上好像,不过可以尝试下。有待验证

另外我感觉lua基本也可以提高吞吐量,不过不知道是不是,于是问了下ai:

7、根据上一个回答,那么Lua 脚本操作redis不可以批量操作和提升吞吐量吗?原因是什么?

ai答:
抱歉给您带来了误解。实际上,Lua 脚本操作 Redis 也可以进行批量操作和提升吞吐量。典型的做法是将多个操作封装到一个 Lua 脚本中,然后一次性执行该脚本。这样可以减少客户端和服务器之间的通信次数,提高操作的效率。

Lua 脚本在 Redis 中的执行是原子性的,即脚本中的多个命令会依次执行,中间不会有其他客户端的操作干扰。因此,将多个操作打包到一个 Lua 脚本中不仅可以提升吞吐量,还可以保证操作的原子性。

需要注意的是,Lua 脚本的执行是在 Redis 服务器端进行的,而不是在客户端。因此,如果 Lua 脚本中的操作涉及到与客户端的交互,比如读取客户端的输入,就无法在脚本中进行批量操作和提升吞吐量。

总的来说,使用 Lua 脚本操作 Redis 可以实现批量操作和提升吞吐量,尤其对于需要在一个原子操作中完成多个操作的场景非常有用。

我的理解:

感觉要去找资料来捋一捋pipeline和lua脚本的关系和优缺点以及应用场景。

目前是感觉pipeline可以实现的lua都可以实现。而且说不定lua实现得效果更好。

另外对于ai的回答:

需要注意的是,Lua 脚本的执行是在 Redis 服务器端进行的,而不是在客户端。因此,如果 Lua 脚本中的操作涉及到与客户端的交互,比如读取客户端的输入,就无法在脚本中进行批量操作和提升吞吐量。

这里调用一次lua脚本,那么肯定是要等redis执行完成才可以继续让redis进行操作其他命令,那我多次操作调用lua脚本,并且lua脚本里面定义了要批量处理redis数据的内容,那么这样不是也可以进行进行批量操作和提升吞吐量吗?所以说我对这里很疑惑,还是保留意见,有待验证

根据这个回答我又想到了其他问题:

8、lua脚本被redis执行时是同步还是异步?会不会阻塞其他操作?是不是等lua脚本完全执行才执行其他非lua脚本的命令?

ai回答:

在 Redis 中,当执行 Lua 脚本时,是同步执行的。也就是说,Redis 会在执行完 Lua 脚本之后,再继续执行其他非 Lua 脚本的命令。

在执行 Lua 脚本期间,Redis 会阻塞其他操作。这是因为执行 Lua 脚本时,会获取一个全局的 Redis 锁,这样可以保证脚本的原子性和安全性。当 Lua 脚本执行完之后,Redis 会释放该全局锁,其他操作才能继续执行。

需要注意的是,虽然 Lua 脚本执行时会阻塞其他操作,但阻塞的时间一般是非常短暂的,因为 Lua 脚本通常是高效的、基于内存计算的操作。这使得 Lua 脚本的执行速度相对较快。

在实际应用中,如果需要在 Lua 脚本执行期间不阻塞其他操作,可以使用 Redis 的 EVALSHA 命令来执行 Lua 脚本的 SHA1 校验和。通过事先将 Lua 脚本进行编译,然后再执行编译后的 SHA1 校验和,可以避免每次执行 Lua 脚本时的传输和编译开销,从而提高性能。

我的感受:

evalsha sha1是什么单词?

同理想出另一个问题:

9、Java使用pipeline技术操作redis执行时是同步还是异步?会不会阻塞其他操作?是不是等pipeline里的所有命令完全执行完毕才执行其他非pipeline的命令?

Java 使用 Pipeline 技术操作 Redis 时,默认是同步执行的。也就是说,当发送 Pipeline 中的命令时,会等待所有命令执行完毕并收到相应结果后,才会继续执行其他非 Pipeline 的命令。

在执行 Pipeline 命令期间,会阻塞其他操作。这是因为 Java 客户端需要等待 Redis 服务器返回所有 Pipeline 命令的结果,然后才能继续处理其他操作。

需要注意的是,虽然 Pipeline 命令会阻塞其他操作,但阻塞的时间较短,因为 Pipeline 是将多个命令一次性发送给 Redis 服务器,从而减少了网络通信的延迟。

如果需要在 Java 使用异步的方式执行 Redis 命令,可以使用 Redis 的异步 Java 客户端,例如 Lettuce 或 Jedis。这些异步客户端可以通过使用 Callback 或 Future 等方式实现非阻塞式的执行 Redis 命令。

总结起来,Java 使用 Pipeline 技术操作 Redis 是同步执行的,会阻塞其他操作,需要等待所有 Pipeline 命令执行完毕才能执行其他非 Pipeline 的命令。如果需要异步执行 Redis 命令,可以选择使用 Redis 的异步 Java 客户端。

我的感受:

应该是可以阻塞其他非pipeline命令也可以异步吧,可以试着去实现或者复现一下.

个人的理解:

这里的lua和pipeline我觉得应该都差不多,为什么说pipeline侧重点是批量操作和高吞吐量?可能是不需要lua编写复杂逻辑,另外pipeline的使用肯定是要结合java等其他编程语言,所以说方便操作些,而lua脚本里如果没有这么多可以用或者说好用的api,那么可能无法处理一些批量的操作

当然了,这种还是需要去找资料证实。

二、学习-知识库迭代

然后我去做一些资料:

文章1

需要注意到是用 pipeline 方式打包命令发送,redis 必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。
pipeline 中发送的每个 command 都会被 server 立即执行,如果执行失败,将会在此后的响应中得到信息;也就是 pipeline 并不是表达“所有command 都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
2.Redis Lua 脚本
Redis 在2.6推出了脚本功能,允许开发者使用 Lua 语言编写脚本传到 Redis 中执行。

使用脚本的好处如下:

1)减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。

2)原子操作:Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过 redis 的批量操作命令(类似mset)是原子的。

3)替代 redis 的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代A Redis script is transactional by definition, so everything you can do with a Redis t ransaction, you can also do with a script, 2 and usually the script will be both simpler and faster.
注意,不要在 Lua 脚本中出现死循环和耗时的运算,否则 redis 会阻塞,将不接受其他的命令, 所以使用时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞 redis。

来自(106条消息) 【Redis】高阶使用(二):管道与 lua 脚本_管道 结合 lua脚本_A minor的博客-CSDN博客

资料2:

lua
Redis在2.6版引入了对Lua的支持

使用Lua可以非常明显的提升Redis的效率。
只要脚本所对应的函数曾经在 Lua 里面定义过, 那么即使用户不知道脚本的内容本身, 也可以直接通过脚本的 SHA1 校验和来调用脚本所对应的函数, 从而达到执行脚本的目的 —— 这就是 EVALSHA 命令的实现原理
当 Lua 脚本里本身有调用 Redis 命令时(执行 redis.call 或者 redis.pcall ), Redis 和 Lua 脚本之间的数据交互会更复杂一些。
redis pipeline
pipeline引入,降低了多次命令-应答之间的网络交换次数,并不能缩小redis对每个命令的处理时间

什么时候使用pipeline,什么时候使用lua
当多个redis命令之间没有依赖、顺序关系(例如第二条命令依赖第一条命令的结果)时,建议使用pipline;
如果命令之间有依赖或顺序关系时,pipline就无法使用,此时可以考虑才用lua脚本的方式来使用。
redis执行lua脚本好处
减少网络开销,本来多次网络请求的操作,可以用一个请求完成,原来多次请求的逻辑均放在redis服务器上完成。使用lua,减少了网络往返时延;
原子操作:redis会将整个脚本作为一个整体执行,不会被其他命令插入。
复用:客户端发送的脚本会永久存储在redis中,意味着其他客户端可以复用这一脚本而无需使用代码完成同样逻辑。

链接:https://www.jianshu.com/p/f06472f537bb

对于减少网络开销,本来多次网络请求的操作,可以用一个请求完成,原来多次请求的逻辑均放在redis服务器上完成。使用lua,减少了网络往返时延;我觉得应该pipeline也可以吧。

文章3:

管道(pipeline)
可以一次性发送多条命令并在执行完后一次性将结果返回,pipeline 通过减少客户端与 redis 的通信次数来实现降低往返延时时间,而且 Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。

通俗点:pipeline就是把一组命令进行打包,然后一次性通过网络发送到Redis。同时将执行的结果批量的返回回来

3、适用场景
有些系统可能对可靠性要求很高,每次操作都需要立马知道这次操作是否成功,是否数据已经写进 redis 了,那这种场景就不适合。

还有的系统,可能是批量的将数据写入 redis,允许一定比例的写入失败,那么这种场景就可以使用了,比如10000条一下进入 redis,可能失败了2条无所谓,后期有补偿机制就行了,比如短信群发这种场景,如果一下群发10000条,按照第一种模式去实现,那这个请求过来,要很久才能给客户端响应,这个延迟就太长了,如果客户端请求设置了超时时间5秒,那肯定就抛出异常了,而且本身群发短信要求实时性也没那么高,这时候用 pipeline 最好了。

管道可以提升我们程序中的响应时间,同时我们不能完全依赖于它的"事务"机制,只需要把管道当批处理工具即可,在某些场合下,更需要结合管道和lua脚本一起使用。


Lua脚本
Lua脚本会将多个命令和操作当成一个命令在redis中执行,也就是说该脚本在执行的过程中,不会被任何其他脚本或命令打断干扰。正是因此这种原子性,lua脚本才可以代替multi和exec的事务功能。同时也是因此,在lua脚本中不宜进行过大的开销操作,避免影响后续的其他请求的正常执行。

使用lua脚本的好处
lua脚本是作为一个整体执行的,所以中间不会被其他命令插入;
可以把多条命令一次性打包,所以可以有效减少网络开销;
lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用,也减少了代码量;
应用
我们可以在redis里使用eval命令调用lua脚本,且该脚本在redis里作为单条命令去执行不会受到其余命令的影响,非常适用于高并发场景下的事务处理。同样我们可以在lua脚本里实现任何想要实现的功能,迭代,循环,判断,赋值 都是可以的。



Pipeline(管道)VS Lua(脚本)
原子性
脚本会将多个命令和操作当成一个命令在redis中执行,也就是说该脚本在执行的过程中,不会被任何其他脚本或命令打断干扰,具有原子性,在执行脚本的时候不会被其他的命令插入,因此更适合于处理事务;
而管道虽然也会将多个命令一次性传输到服务端,但在服务端执行的时候仍然是多个命令,如在执行CMD1的时候,外部另一个客户端提交了CMD9,会先执行完CMD9再执行管道中的CMD2,因此事实上管道是不具有原子性的。

使用场景
就场景上来说,正因为Lua脚本会被视为一个命令去执行,因为Redis是单线程执行命令的,所以我们不能在lua脚本里写过于复杂的逻辑,否则会造成阻塞,因此lua脚本适合于相对简单的事务场景;
而管道因为不具有原子性,因此管道不适合处理事务,但管道可以减少多个命令执行时的网络消耗,可以提高程序的响应速度,因此管道更适合于管道中的命令互相没有关系,不需要有事务的原子性,且需要提高程序响应速度的场景。

链接:https://www.jianshu.com/p/dcbf9b9949e9

我的思考:

从这里就可以知道原来lua脚本不可以耗时太长,pipeline因为不是原子行操作,在执行pipeline命令集的过程中还可以会执行其他客户端发来的命令。另外pipeline里的批量操作一般可以说不太相关的,也就是不需要是顺序关系。

文章4:

Redis 管道、事务、Lua 脚本对比 - 掘金 (juejin.cn)

Pipelining(管道)
Redis 管道是三者之中最简单的,当客户端需要执行多条 redis 命令时,可以通过管道一次性将要执行的多条命令发送给服务端,其作用是为了降低 RTT(Round Trip Time) 对性能的影响,比如我们使用 nc 命令将两条指令发送给 redis 服务端

$ printf "INCR x\r\nINCR x\r\n" | nc localhost 6379
:1
:2

可以看到,管道只是简单的将多个命令拼接在一起,命令之间用换行符(/r/n)分割,并没有在第一条命令前或最后一条命令后面添加开始/结束标志位
redis 服务端接收到管道发送过来的多条命令后,会一直执命令,并将命令的执行结果进行缓存,直到最后一条命令执行完成,再所有命令的执行结果一次性返回给客户端


Pipelining 的优势
在性能方面, Pipelining 有下面两个优势:

将多条命令打包一次性发送给服务端,减少了客户端与服务端之间的网络调用次数,节省了 RTT 
避免了上下文切换,当客户端/服务端需要从网络中读写数据时,都会产生一次系统调用,系统调用是非常耗时的操作,其中设计到程序由用户态切换到内核态,再从内核态切换回用户态的过程。当我们执行 10 条 redis 命令的时候,就会发生 10 次用户态到内核态的上下文切换,但如果我们使用 Pipeining 将多条命令打包成一条一次性发送给服务端,就只会产生一次上下文切换

Pipelining 原子性
我们都知道, redis 执行命令的时候是单线程执行的,所以 redis 中的所有命令都具备原子性,这意味着 redis 并不会在执行某条命令的中途停止去执行另一条命令
但是 Pipelining 并不具备原子性,想象一下有两个客户端 client1 和 client2 同时向 redis 服务端发送 Pipelining 命令,每条 Pipelining 包含 5 条 redis 命令。 redis 可以保证 client1 管道中的命令始终是顺序执行的, client2 管道中的命令也是一样,始终按照管道中传入的顺序执行命令
但是 redis 并不能保证等 client1 管道中的所有命令执行完成,再执行 client2 管道中的命令,因此,在服务端中的命令执行顺序有可能是下面这种情况

这种行为显示 Pipelining 在执行的时候并不会阻塞服务端。即使 client1 向客户端发送了包含多条指令的 Pipelining ,其他客户端也不会被阻塞,因为他们发送的指令可以插入到 Pipelining 中间执行


只有在 Pipelining 内所有命令执行完后,服务端才会把执行结果通过数组的方式返回给客户端。在执行 Pipelining 内的命令的时候,如果某些指令执行失败, Pipelining 仍会继续执行
比如下面的例子
shell复制代码$ printf "SET name huangxy\r\nINCR name\r\nGET name\r\n" | nc localhost 6379
+OK
-ERR value is not an integer or out of range
$6
huangxy

Pipelining 中第二条指令执行失败, Pipelining 并不会停止,而是会继续执行,等所有命令都执行完的时候,再将结果返回给客户端,其中第二条指令返回的是错误信息
Pipelining 的这个特性会导致一个问题,就是当 Pipelining 中的指令需要读取之前指令设置 key 的时候,需要额外小心,因为 key 的值有可能会被其他客户端修改。此时 Pipelining 的执行结果往往就不是我们所预期的

文章里说:

Pipelining 的这个特性会导致一个问题,就是当 Pipelining 中的指令需要读取之前指令设置 key 的时候,需要额外小心,因为 key 的值有可能会被其他客户端修改。此时 Pipelining 的执行结果往往就不是我们所预期的`

这里引起了我的思考,也就是说其他非pipeline命令可能会修改pipeline中部分命令输入参数的数据,这里就可能有问题了,大概是这样:

pipeline里有2个过程:

过程1:查询到的key1会传递给过程2的输入参数得到结果a,目标值只有结果a,那么如果非pipeline命令对key1进行修改呢?(比如书将key1-value这种键值对删除或者篡改子类的)发生的后果不堪设想,所以说对于强相关的要用lua脚本,所以pipeline命令集执行完毕返回的结果要进行异常的捕获和检查,比如书检查异常吗,或者说检查是不是nil等等之类的。

和之前文章3的思考差不多。

现在是2023-07-04-15:20
我的思考:
分析:
然后对于项目里的updateAllLikeListToDatabaseByPipeline1这个方法,使用pipeline实现的,然后目前好像没有对结果集命令返回的结果做异常捕获处理,应该是吧??要去看看完善一下,或者说这个早就已经处理了呢?得去检查一下。
比较updateAllLikeListToDatabaseByPipeline1这个方法以及执行单个lua脚本的情况:
另外使用pipeline确实需要容忍异常或者部分命令执行失败的后果,命令集部分执行失败意味着结果集少了一部分,导致没办法将所有博客like_list更新成功,但是因为是定时任务弄得,所以说这次不行可以等下次再将缓存得数据持久化到数据库里,这个应该算是兜底了。然后呢,对于实在一次异常也容忍不了那就去执行lua脚本吧,不过如果用lua脚本也是一样要返回数据呀,lua原子行操作如果失败岂不是结果集得不到数据了吗?于是还不是靠定时任务兜底吗?另外lua脚本执行失败会导致缓存数据一点也没有持久化到数据库,与pipeline相比应该是逊了,从这一点来看的话pipeline方案至少可以将执行成功的结果集返回,如果从这里比较,那么pipeline应该是更好点。

分析不断执行lua脚本直到成功方案:
不过也可以去尝试实现lua,脚本如果lua脚本有执行失败,那就全部回滚,另外再通知服务器执行失败,服务器得到的redis执行结果result里可以找到lua脚本的执行情况,如果一定要执行成功那么就设置一个while循环,判断result里面的情况,如果result符合,那么就知道本次执行成功,反之则重新执行,直到成功。
结论:
lua脚本可以尝试一下,后面可以找时间复现。使用lua脚本应该可以优化updateAllLikeListToDatabaseByPipeline1这个方法。

对于原子性而言,pipeline技术匹配度<lua技术匹配度
另外对于lua脚本的执行因为会阻塞其他操作,为了防止redis宕机,可以结合pipeline和lua这两个方案,
怎么结合?
缓存数据持久化同步到数据库可以分时间来执行,晚上少人时的因为流量少,访问得少可以用lua脚本,白天如果想同步,那就用pipeline。
不过还是不能保证晚上lua会不会因为redis其他操作执行阻塞而导致redis宕机,所以说时没办法斩草除根的,所有技术都不是百分百完美,只能最大限度地进行优化和缓解,那么晚上哪个点使用lua脚本就值得研究了,如果可以通过人工智能算法统计网站该业务缓存流量的访问频率和时间,得到lua脚本执行的最佳时机以及pipeline的最佳时机那就更好了。

另外如果以上优化都实现了,需要再深度优化,那么可以考虑redis集群,比较同步缓存数据到数据库时这个项目使用的是单节点,如果是多节点,可以选取从节点来同步然后再结合之前优化的内容应该就可以再上一层楼,可用性应该会更强。

另外除了pipeline还有redis的事务,那么redis事务是多个命令执行的吗?也就是可以和lua那样做到一样的效果吗?如果是的话那么哪个好?另外和pipeline比较redis事务怎么样?a待盘查.

接下来继续看吧:

Pipelining 使用场景

对性能有要求
需要发送多个指令到服务端
不需要上个命令的返回结果作为下个命令的输入

Transactions(事务)
redis 中的事务,跟我们之前在学关系型数据库的时候所了解到的事务概念有点区别。 redis 中的事务机制主要是用来对多个命令进行排队,并在最后决定是否需要执行事务中的所有命令与否
与管道不同,事务使用特殊的命令来标记事务的开始和结束( MULTI 、 EXEC 、 DISCARD )。服务器还可以对事务中的命令进行排队(这样客户端可以一次发送一条命令)。除此之外,一些第三方库还喜欢在客户端中对事务的命令进行缓存,然后通过在管道中发送整个事务的方式对其进行优化

事务的优点
事务提供了 WATCH 命令,使我们可以实现 CAS 功能,比如通过事务,我们可以实现跟 INCR 命令一样的功能
ini复制代码WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

事务的原子性
redis 事务具备原子性,当一个事务正在执行时,服务端会阻塞其接收到的其他命令,只有在事务执行完成时,才会执行接下来的命令,因此事务具备原子性

事务的局限性
跟 Pipelining 一样,只有在事务执行完成时,才会把事务中多个命令的结果一并返回给客户端,因此客户端在事务还没有执行完的时候,无法获取其命令的执行结果
如果事务中的其中一个命令发生错误,会有以下两种可能性:

当发生语法错误,在执行 EXEC 命令的时候,事务将会被丢弃,不会执行
当发生运行时错误(操作了错误的数据类型)时, redis 会将报错信息缓存起来,继续执行后面的命令,并在最后将所有命令的执行结果返回给客户端(报错信息也会返回)。这意味着 redis 事务中没有回滚机制

事务使用场景
需要原子地执行多个命令
不需要事务中间命令的执行结果来编排后面的命令

从这里再结合项目a已盘查部分,但未i而彻底.:

现在是2023-07-04-15:20
我的思考:
这里相比较的话如果lua和事务相比,那肯定是lua更好了,如果是对于事务里命令集的原子行而言。因为rediss事务如果提交,对于语法错误等命令机会执行失败,其他的则执行成功;而lua的话就会全部失败要么就全部成功。
而对于redis事务和pipeline技术的选择?哪个好?

从资料中我得到的信息是:

1、pipeline和lua都是发送命令集到redis服务器的。

2、redis服务器会对pipeline进行异步处理,也就是说不会阻塞其他命令,有异步的意思。

3、lua脚本是整个作为原子行命令,还会阻塞其他操作,所以不可以异步。

产生新的问题:

但是我还不知道redis事务里的命令是这么发送到redis服务器的,另外执行redis事务命令时是怎么样子的呢?到底是异步还是会阻塞其他操作?根据文章4,redis事务是可以将事务里的命令一条一条发送给redis服务器的,我好像也试着用过Java代码来实现过了,但是能不能像pipeline和lua那样一次发送所有命令呢?可以去找一下资料b待盘查.

剩下的文章:

Lua 脚本的原子性
Lua 脚本跟事务一样具备原子性,当脚本执行中时,服务端接收到的命令会被阻塞
Lua 脚本的局限性
Lua 脚本在功能上没有过多的限制,但要注意的一点是,Lua 脚本在执行的时候,会阻塞其他命令的执行,所以不宜在脚本中写太耗时的处理逻辑
Lua 脚本的使用场景

需要原子性地执行多个命令
需要中间值来组合后面的命令
需要中间值来编排后面的命令
常用于扩展 redis 功能,实现符合自己业务场景的命令

链接:https://juejin.cn/post/6926414112788316174

文章5

Redis 事务 | 菜鸟教程 (runoob.com)

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:

开始事务。
命令入队。
执行事务。

于是就知道事务可以批量执行了,也可以执行单个,也就是事务里的命令减少到单个应该勉强算是单个执行吧。那么redis事务时异步还是?我的推测是:

当redis没有接收到EXEC 时,都是异步,其他操作不阻塞;当收到EXEC 时便会阻塞其他操作直到该事务所有命令全部被执行完毕。这里应该是对应于文章中的:

redis 事务具备原子性,当一个事务正在执行时,服务端会阻塞其接收到的其他命令,只有在事务执行完成时,才会执行接下来的命令,因此事务具备原子性

至于redis未收到exec命令时能不能执行其他操作还需要去验证。不过按照文章4和文章5的意思大概可以异步,还是要去验证.

问下ai吧:

10、redis在开启事务但未接收到exec期间可以异步执行其他操作吗?

ai答:

在 Redis 中,开启事务但未接收到 EXEC 命令时,Redis 会进入事务状态,但是事务中的命令不会立即执行。在这个阶段,Redis 不会阻塞其他操作,可以继续执行其他非事务的命令。

在事务期间,Redis 会将所有事务中的命令缓存起来,直到接收到 EXEC 命令才会执行这些命令。因此,在未执行 EXEC 命令之前,Redis 可以并行执行其他非事务命令。这就意味着可以在事务等待期间异步执行其他操作。

需要注意的是,事务等待期间的所有命令都会按照顺序执行,即使其中某些命令可能被异步执行。只有在接收到 EXEC 命令时,才会将缓存的事务命令按照顺序执行,并返回操作结果。

总结起来,Redis 在开启事务但未接收到 EXEC 命令期间,可以异步执行其他操作。然而,在执行事务期间所有命令的顺序是有保证的,并会在接收到 EXEC 命令后按照顺序执行。

我的感受:

应该是证实了我对文章的理解,确实是异步的。当然了这里也可以自己去验证一下。

后面发现验证成功了:

192.168.10.100-docker-redis6379:0>MULTI
"OK"
192.168.10.100-docker-redis6379:0>set haha "yyyy"
"QUEUED"
192.168.10.100-docker-redis6379:0>EXEC
1) "OK"
2) "OK"
3) "OK"

在没有exec前执行这些是可以成功的:

192.168.10.100-docker-redis6379:0>get haha
"yyyy"

文章6

11、pipeline是顺序执行的吗

  • pipeline缓冲的指令发送到服务器端时,本身是按照指令缓冲的顺序执行,因为缓冲指令是使用队列的方式实现的,但是缓冲指令中间可能会穿插其他客户端发送来的命令

Redis Pipeline介绍及应用 - 掘金 (juejin.cn)

感觉对于一个管道的命令应该是顺序执行的,但是可能会被其他命令穿插。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值