Redis: pipeline加速Redis请求,实现批处理

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis: pipeline加速Redis请求,实现批处理

 Redis是基于TCP连接进行通信

Redis是使用客户端 - 服务器模型的TCP服务器,称为请求/响应协议。

这意味着通常一个请求是通过以下步骤完成的:

    1. 客户端向服务器发送查询,并通常以阻塞的方式从套接字读取服务器响应。
    2. 服务器处理命令并将响应发送回客户端。

    知道redis是基于TCP连接进行通信的,每一个request/response都需要经历一个RTT(Round-Trip Time 往返时间),如果需要执行很多短小的命令,这些往返时间的开销是很大的,在此情形下,redis提出了管道来提高执行效率。

    pipeline的思想

      1. 如果client执行一些相互之间无关的命令或者不需要获取命令的返回值,那么redis允许你连续发送多条命令,而不需要等待前面命令执行完毕。
      2. 比如我们执行3INCR命令,如果使用管道,理论上只需要一个RTT+3条命令的执行时间即可,如果不适用管道,那么可能需要额外的两个RTT时间。
      3. 因此,管道相当于批处理脚本,相当于是命令集。

      pipeline不是打包的命令越多越好

        1. 通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。
        2. 需要注意到是用 pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。
        3. 打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。具体多少合适需要根据具体情况测试。

        pipeline常用API

        20181022150840717.png

        image.gif

        package redis.clients.jedis;
        import redis.clients.jedis.exceptions.JedisDataException;
        import java.io.Closeable;
        import java.io.IOException;
        import java.util.ArrayList;
        import java.util.List;
        public class Pipeline extends MultiKeyPipelineBase implements Closeable {
          private MultiResponseBuilder currentMulti;
          private class MultiResponseBuilder extends Builder<List<Object>> {
            private List<Response<?>> responses = new ArrayList<Response<?>>();
            @Override
            public List<Object> build(Object data) {
              @SuppressWarnings("unchecked")
              List<Object> list = (List<Object>) data;
              List<Object> values = new ArrayList<Object>();
              if (list.size() != responses.size()) {
                throw new JedisDataException("Expected data size " + responses.size() + " but was "
                    + list.size());
              }
              for (int i = 0; i < list.size(); i++) {
                Response<?> response = responses.get(i);
                response.set(list.get(i));
                Object builtResponse;
                try {
                  builtResponse = response.get();
                } catch (JedisDataException e) {
                  builtResponse = e;
                }
                values.add(builtResponse);
              }
              return values;
            }
            public void setResponseDependency(Response<?> dependency) {
              for (Response<?> response : responses) {
                response.setDependency(dependency);
              }
            }
            public void addResponse(Response<?> response) {
              responses.add(response);
            }
          }
          @Override
          protected <T> Response<T> getResponse(Builder<T> builder) {
            if (currentMulti != null) {
              super.getResponse(BuilderFactory.STRING); // Expected QUEUED
              Response<T> lr = new Response<T>(builder);
              currentMulti.addResponse(lr);
              return lr;
            } else {
              return super.getResponse(builder);
            }
          }
          public void setClient(Client client) {
            this.client = client;
          }
          @Override
          protected Client getClient(byte[] key) {
            return client;
          }
          @Override
          protected Client getClient(String key) {
            return client;
          }
          public void clear() {
            if (isInMulti()) {
              discard();
            }
            sync();
          }
          public boolean isInMulti() {
            return currentMulti != null;
          }
          /**
           * Synchronize pipeline by reading all responses. This operation close the pipeline. In order to
           * get return values from pipelined commands, capture the different Response<?> of the
           * commands you execute.
           */
          public void sync() {
            if (getPipelinedResponseLength() > 0) {
              List<Object> unformatted = client.getAll();
              for (Object o : unformatted) {
                generateResponse(o);
              }
            }
          }
          /**
           * Synchronize pipeline by reading all responses. This operation close the pipeline. Whenever
           * possible try to avoid using this version and use Pipeline.sync() as it won't go through all the
           * responses and generate the right response type (usually it is a waste of time).
           * @return A list of all the responses in the order you executed them.
           */
          public List<Object> syncAndReturnAll() {
            if (getPipelinedResponseLength() > 0) {
              List<Object> unformatted = client.getAll();
              List<Object> formatted = new ArrayList<Object>();
              for (Object o : unformatted) {
                try {
                  formatted.add(generateResponse(o).get());
                } catch (JedisDataException e) {
                  formatted.add(e);
                }
              }
              return formatted;
            } else {
              return java.util.Collections.<Object> emptyList();
            }
          }
          public Response<String> discard() {
            if (currentMulti == null) throw new JedisDataException("DISCARD without MULTI");
            client.discard();
            currentMulti = null;
            return getResponse(BuilderFactory.STRING);
          }
          public Response<List<Object>> exec() {
            if (currentMulti == null) throw new JedisDataException("EXEC without MULTI");
            client.exec();
            Response<List<Object>> response = super.getResponse(currentMulti);
            currentMulti.setResponseDependency(response);
            currentMulti = null;
            return response;
          }
          public Response<String> multi() {
            if (currentMulti != null) throw new JedisDataException("MULTI calls can not be nested");
            client.multi();
            Response<String> response = getResponse(BuilderFactory.STRING); // Expecting
            // OK
            currentMulti = new MultiResponseBuilder();
            return response;
          }
          @Override
          public void close() throws IOException {
            clear();
          }
        }

        image.gif

          • Pipeline在某些场景下非常有用,比如有多个command需要被“及时的”提交,而且他们对相应结果没有互相依赖,而且对结果响应也无需立即获得,那么pipeline就可以充当这种“批处理”的工具;而且在一定程度上,可以较大的提升性能,性能提升的原因主要是TCP链接中较少了“交互往返”的时间。

          通过Jedis操作pipeline

            @Test
                public void testPipeline() {
                    Jedis jedis = null;
                    Pipeline pipeline = null;
                    try {
                        // 创建一个jedis的对象。
                        jedis = new Jedis("ip", 6379);
                        jedis.auth("密码");
                        // 获取一个管道对象
                        pipeline = jedis.pipelined();
                        // 删除已经存在的key
                        pipeline.del("pipelinedList");
                        // 循环添加
                        for (int i = 0; i < 100; i++) {
                            pipeline.rpush("pipelinedList",i+"");
                        }
                        // 执行
                        pipeline.sync();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        // 关闭pipeline
                        if(pipeline != null){
                            try {
                                pipeline.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        // 关闭jedis。
                        if(jedis != null){
                            jedis.close();
                        }
                    }
                }

            image.gif

            pipelineVS事务

              • 管道和事务是不同的,pipeline只是表达“交互”中操作的传递的方向性,pipeline也可以在事务中运行,也可以不在。
              • 无论如何,pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的相应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行
              • 简单来说就是管道中的命令是没有关系的,它们只是像管道一样流水发给server,而不是串行执行,仅此而已;但是如果pipeline的操作被封装在事务中,那么将有事务来确保操作的成功与失败。
                • pipeline 只是把多个redis指令一起发出去,redis并没有保证这些指定的执行是原子的;multi相当于一个redis的transaction的,保证整个操作的原子性,避免由于中途出错而导致最后产生的数据不一致

                pipelineVS脚本

                  • 使用管道可能在效率上比使用script要好,但是有的情况下只能使用script。因为在执行后面的命令时,无法得到前面命令的结果,就像事务一样,所以如果需要在后面命令中使用前面命令的value等结果,则只能使用script或者事务+watch
                  • 使用Redis脚本(在Redis版本2.6或更高版本中可用),可以使用执行服务器端所需的大量工作的脚本更高效地处理一些 pipelining 用例。
                  • 脚本的一大优势是它能够以最小的延迟读取和写入数据,使得读取,计算,写入等操作非常快速(在这种情况下,流水线操作无法提供帮助,因为客户端先需要读命令的回应,它才可以调用写命令)。
                  • 有时,应用程序可能还想在 pipeline 中发送EVALEVALSHA命令。这是完全可能的,Redis通过SCRIPT LOAD命令明确地支持它(它保证可以调用EVALSHA而没有失败的风险)。

                  来源: Using pipelining to speedup Redis queries – Redis

                  来源: redis中的事务、lua脚本和管道的使用场景_fangjian1204的专栏-CSDN博客_if redis.call

                  相关实践学习
                  基于Redis实现在线游戏积分排行榜
                  本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
                  云数据库 Redis 版使用教程
                  云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
                  目录
                  相关文章
                  |
                  缓存 NoSQL 网络协议
                  Redis: pipeline加速Redis请求,实现批处理
                  Redis: pipeline加速Redis请求,实现批处理
                  430 0
                  Redis: pipeline加速Redis请求,实现批处理
                  |
                  13天前
                  |
                  NoSQL Linux Redis
                  06- 你们使用Redis是单点还是集群 ? 哪种集群 ?
                  **Redis配置:** 使用哨兵集群,结构为1主2从,加上3个哨兵节点,总计分布在3台Linux服务器上,提供高可用性。
                  33 0
                  |
                  21天前
                  |
                  负载均衡 监控 NoSQL
                  Redis的集群方案有哪些?
                  Redis集群包括主从复制(基础,手动故障恢复)、哨兵模式(自动高可用)和Redis Cluster(官方分布式解决方案,自动分片和容错)。此外,还有如Codis、Redisson和Twemproxy等第三方工具用于代理和负载均衡。选择方案需考虑应用场景、数据规模和并发需求。
                  31 2
                  |
                  27天前
                  |
                  NoSQL Redis
                  Redis集群(六):集群常用命令及说明
                  Redis集群(六):集群常用命令及说明
                  27 0
                  |
                  2月前
                  |
                  运维 NoSQL 算法
                  Redis-Cluster 与 Redis 集群的技术大比拼
                  Redis-Cluster 与 Redis 集群的技术大比拼
                  46 0
                  |
                  21天前
                  |
                  NoSQL Java 测试技术
                  面试官:如何搭建Redis集群?
                  **Redis Cluster** 是从 Redis 3.0 开始引入的集群解决方案,它分散数据以减少对单个主节点的依赖,提升读写性能。16384 个槽位分配给节点,客户端通过槽位信息直接路由请求。集群是无代理、去中心化的,多数命令直接由节点处理,保持高性能。通过 `create-cluster` 工具快速搭建集群,但适用于测试环境。在生产环境,需手动配置文件,启动节点,然后使用 `redis-cli --cluster create` 分配槽位和从节点。集群动态添加删除节点、数据重新分片及故障转移涉及复杂操作,包括主从切换和槽位迁移。
                  31 0
                  面试官:如何搭建Redis集群?
                  |
                  25天前
                  |
                  存储 缓存 NoSQL
                  【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群功能分析)(一)
                  【Redis深度专题】「核心技术提升」探究Redis服务启动的过程机制的技术原理和流程分析的指南(集群功能分析)
                  61 0
                  |
                  1月前
                  |
                  NoSQL Redis Docker
                  使用Docker搭建一个“一主两从”的 Redis 集群(超详细步骤)
                  使用Docker搭建一个“一主两从”的 Redis 集群(超详细步骤)
                  61 0
                  |
                  1月前
                  |
                  存储 监控 NoSQL
                  Redis 架构深入:主从复制、哨兵到集群
                  大家好,我是小康,今天我们来聊下 Redis 的几种架构模式,包括主从复制、哨兵和集群模式。
                  Redis 架构深入:主从复制、哨兵到集群
                  |
                  1月前
                  |
                  运维 负载均衡 NoSQL
                  【大厂面试官】知道Redis集群和Redis主从有什么区别吗
                  集群节点之间的故障检测和Redis主从中的哨兵检测很类似,都是通过PING消息来检测的。。。面试官抓抓脑袋,继续看你的简历…得想想考点你不懂的😰。
                  67 1