码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 使用redis pipeline提升性能


    前言

    本篇来介绍一下redis pipeline,主要是由于最近一次在帮开发同学review代码的时候,发现对redis有个循环操作可以优化。场景大概是这样的,根据某个uid要从redis查询一批数据,每次大概1000个key左右,如果查得到就返回,否则查db,然后写回缓存。由于每次要查的key比较多,虽然redis单次查询很快,但如果key很多,每次查询redis都需要读写socket,与client间的网络数据传输,都需要消耗时间,累加起来也会变得非常慢。开发同学决定使用批量的方式,例如每次操作100个key,使用RedisTemplate批量查询代码如下:

    redisTemplate.opsForValue().multiGet(keys);
    

    如果查询到的是null,则表示缓存不存在或过期,则查询数据库,再批量写回redis,伪代码如下:

    for (Long id : list) {
        operations.opsForValue().set("key", id, 30, TimeUnit.MINUTES);
    }
    

    他并没有使用批量的方式,如果有100个,这里就需要执行100次set命令,经过了解后原因是批量写入并不能设置过期时间,我们看它的api确实只能设置key-value,但没有过期时间也是不行的。

    void multiSet(Mapextends K, ? extends V> map);
    

    单个循环设置肯定不行,除了自己执行方法会比较慢,影响用户体验,可能导致接口超时外,由于redis是单线程执行命令的,还会影响其它命令的执行,所以必须优化。
    优化的方式就是本篇要介绍的:pipeline。

    pipeline

    pipeline是管道的意思,它最主要的作用就是降低RRT(client-server数据传输往返时间)。在请求-响应过程,除了传递我们的数据,还需要协议信息,例如http协议的请求头,响应头,这些信息也会增加传输时间。举个例子,假设一次RRT是10ms,那么执行10条命令,就需要100ms,如果我们将其打包到一起执行,RRT就还是10ms(虽然传输的数据变多了,但协议本身的信息没有变多,基本可以忽略不计),传输效率提升了10倍。除此之外,redis server每次处理命令都需要对Socket进行IO操作,这涉及到用户态、内核态的切换,如果批量进行处理,对性能的提升也很有帮助。
    pineline将一批命令打包一起执行,但不保证他们的原子性,不像事务一样可以保证一起成功或失败,可能前面的命令执行成功了,后面的执行失败。
    这和我们平时操作数据库的思想是一样的,单个查询转换为批量查询,单个插入转换为批量插入,同样需要注意是,批量虽好,但不能一次过多,否则处理起来比较久,反而得不偿失。
    更多的知识可参考官方文档:https://redis.io/docs/manual/pipelining/

    我们使用springboot 2.x版本,使用spring-boot-starter-data-redis,它给我们默认集成的redis client是lettuce。在使用一个不熟悉或比较新的东西的时候,本人有一个习惯,会先google一下,例如:“RedisTemplate pipeline 注意事项”,“RedisTemplate pipeline 坑”,看看有没有前人踩过坑,借鉴一下。这次也一样,google之后果然发现有点坑,例如这篇提到的Spring Data Redis与Lettuce使用pipeline时,实际命令并不是一起执行的,有时是单条执行,有时是合并几条执行。

    我们自己写下测试代码如下:

    redisTemplate.executePipelined(new SessionCallback() {
    
    	@Override
    	public Object execute(RedisOperations operations) throws DataAccessException {
    		for (int i = 0; i < 100; i++) {
    			operations.opsForValue().set("testPipeline2" + i, i, 1, TimeUnit.MINUTES);
    		}
    		return null;
    	}
    });
    
    

    在set位置打个断点,然后到redis server使用monitor命令观察,看命令到底是不是一条一条给过来的。monitor命令会将server执行的命令都打印出来,生产环境慎用。
    按照上面的分析,正常情况下这些命令应该是一起发送到server端一起执行的,不会断断续续,但实际我们观察确实不是一起给过来,断断续续的,如下:

    我们把lettuce替换成jedis看看。

    <dependency>
    	<groupId>org.springframework.bootgroupId>
    	<artifactId>spring-boot-starter-data-redisartifactId>
    	<exclusions>
    		<exclusion>
                <groupId>io.lettucegroupId>
                <artifactId>lettuce-coreartifactId>
            exclusion>
    	exclusions>
    dependency>
    <dependency>
    	<groupId>redis.clientsgroupId>
    	<artifactId>jedisartifactId>
    dependency>
    

    还是执行上面的代码,打断点,使用jedis可以观察到,每次循环monitor都不会观察到有命令执行,直到最后才一批给过来。

    但我们不想直接替换lettuce为jedis,一个是它是spring boot默认集成的,拥有更好的性能,二是替换后不知道其它功能有没有影响,那怎么办呢?
    我们项目还使用redission分布式锁,其实redission也是一个redis client,理论上它应该实现所有client的功能,pipeline自然也有实现。
    我们使用redission如下:

    RBatch batch = redissonClient.createBatch();
    for (int i = 0; i < 100; i++) {
    	batch.getBucket("testBatch" + i).setAsync(i, 1, TimeUnit.MINUTES);
    }
    batch.execute();
    

    这次我们把断点打在execute位置,看看是不是execute时才一起提交到server执行,答案显然是的。

    接下来我们简单测试一下性能差距,分别是单个请求,使用lettuce,使用jedis,使用redission,执行10000次,耗时如下:
    单个请求:73029ms
    lettuce: 712ms
    jedis: 413ms
    redission: 341ms

    lettuce出乎意料执行还是很快,就想上面提到的,它有时还是会部分打包一起执行,但终究不是一次执行,有兴趣的可以深入了解一下。

    更多分享,欢迎关注我的github:https://github.com/jmilktea/jtea

  • 相关阅读:
    AI助力智能安检,基于目标检测模型实现X光安检图像智能检测分析
    FFmpeg介绍
    毫米波雷达在检测、分割、深度估计等多个方向的近期工作及简要介绍
    超快速成,零基础掌握C#开发中最重要的概念
    小程序添加悬浮在线客服源码
    proteus仿真-51单片机定时器程序
    [附源码]java毕业设计网上花店系统
    KTL 一个支持C++14编辑公式的K线技术工具平台 - 第九版,数据分析工具。支持通达信日线数据。增强即时插件编程体现。
    如何把JavaWeb项目部署到服务器
    Spring整合Mybatis
  • 原文地址:https://www.cnblogs.com/jtea/p/17645367.html
    • 最新文章
    • 攻防演习之三天拿下官网站群
      数据安全治理学习——前期安全规划和安全管理体系建设
      企业安全 | 企业内一次钓鱼演练准备过程
      内网渗透测试 | Kerberos协议及其部分攻击手法
      0day的产生 | 不懂代码的"代码审计"
      安装scrcpy-client模块av模块异常,环境问题解决方案
      leetcode hot100【LeetCode 279. 完全平方数】java实现
      OpenWrt下安装Mosquitto
      AnatoMask论文汇总
      【AI日记】24.11.01 LangChain、openai api和github copilot
    • 热门文章
    • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
      奉劝各位学弟学妹们,该打造你的技术影响力了!
      五年了,我在 CSDN 的两个一百万。
      Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
      面试官都震惊,你这网络基础可以啊!
      你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
      心情不好的时候,用 Python 画棵樱花树送给自己吧
      通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
      13 万字 C 语言从入门到精通保姆级教程2021 年版
      10行代码集2000张美女图,Python爬虫120例,再上征途
    Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
    正则表达式工具 cron表达式工具 密码生成工具

    京公网安备 11010502049817号