Redis使用Pipeline(管道)批量处理

Redis 批量处理

在开发中,有时需要对Redis 进行大批量的处理。

比如Redis批量查询多个Hash。如果是在for循环中逐个查询,那性能会很差。

这时,可以使用 Pipeline (管道)。

Pipeline (管道)

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

RedisTemplate的管道操作

RedisTemplate的管道操作,使用executePipelined()方法。
org.springframework.data.redis.core.RedisTemplate#executePipelined(org.springframework.data.redis.core.SessionCallback<?>)

	@Override
	public List<Object> executePipelined(SessionCallback<?> session, @Nullable RedisSerializer<?> resultSerializer) {

		Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
		Assert.notNull(session, "Callback object must not be null");

		RedisConnectionFactory factory = getRequiredConnectionFactory();
		// 绑定连接
		RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
		try {
			return execute((RedisCallback<List<Object>>) connection -> {
				//打开管道
				connection.openPipeline();
				boolean pipelinedClosed = false;
				try {
					Object result = executeSession(session);
					//callback的返回值,只能返回null,否则会报错。
					if (result != null) {
						throw new InvalidDataAccessApiUsageException(
								"Callback cannot return a non-null value as it gets overwritten by the pipeline");
					}
					//关闭管道,并获取返回值
					List<Object> closePipeline = connection.closePipeline();
					pipelinedClosed = true;
					return deserializeMixedResults(closePipeline, resultSerializer, hashKeySerializer, hashValueSerializer);
				} finally {
					if (!pipelinedClosed) {
						connection.closePipeline();
					}
				}
			});
		} finally {
			RedisConnectionUtils.unbindConnection(factory);
		}
	}

  • 注意:
    (1) executePipelined()的callback参数,实现execute() 方法只能返回null,否则会报错。
    报错: InvalidDataAccessApiUsageException: Callback cannot return a non-null value as it gets overwritten by the pipeline
    源码如下:
Object result = executeSession(session);
if (result != null) {
throw new InvalidDataAccessApiUsageException(
"Callback cannot return a non-null value as it gets overwritten by the pipeline");
}

(2)在executePipelined()中, 使用 get()方法 得到的value会是null。
在 redisTemplate进行管道操作,结果值只能通过 executePipelined()方法的返回值List获取.

	/**
	 * Get the value of {@code key}.
	 *
	 * @param key must not be {@literal null}.
	 * 在管道(pipeline)中使用 get()方法 得到的value会是null
	 * @return {@literal null} when used in pipeline / transaction.
	 */
	@Nullable
	V get(Object key);
  • redisTemplate获取管道返回值:
List<Object> list = stringRedisTemplate.executePipelined(new SessionCallback<String>() {
            @Override
            public  String execute(@NonNull RedisOperations operations) throws DataAccessException {
                //idList是多个id的集合
                for (String id : idList) {
                    //key由前缀加唯一id组成
                    String key = KEY_PREFIX + id;
                    //在管道中使用 get()方法 得到的value会是null。value通过executePipelined()的返回值List<Object>获取。
                    operations.opsForHash().get(key, field);
                }
                //Callback只能返回null, 否则报错:
                // InvalidDataAccessApiUsageException: Callback cannot return a non-null value as it gets overwritten by the pipeline
                return null;
            }
        });

list.forEach(System.out::println);

Jedis操作管道

RedisTemplate操作管道比较方便,但如果要组装key和value的map,就会比较麻烦。
在这种情况下,可以使用Jedis。
比如,Jedis批量查询多个Hash,可以使用 Pipeline (管道)。
源码见: redis.clients.jedis.PipelineBase#hget(java.lang.String, java.lang.String)
hget()方法,跟普通hash的hget()有点类似,不过返回值是 Response

    public Response<String> hget(String key, String field) {
        this.getClient(key).hget(key, field);
        return this.getResponse(BuilderFactory.STRING);
    }

示例:

public void testPipLine() {
        Map<String, Response<String>> responseMap = new HashMap<>();
        //try-with-resources, 自动关闭资源
        //先连接jedis,再拿到 pipeline
        try (Jedis jedis = getJedis();
            Pipeline pipeline = jedis.pipelined()) {
            for (String id : idList) {
                //前缀加唯一id
                String key = KEY_PREFIX +  id;
                //使用pipeline.hget查询hash的数据
                Response<String> response = pipeline.hget(key, field);
                responseMap.put(id, response);
            }
            pipeline.sync();
        } catch (Exception ex) {
            log.error("responses error.", ex);
        }

        Map<String, String> map = new HashMap<>();
        //组装map。response.get()在pipeline关闭后才能执行,否则拿到的value都是null
        responseMap.forEach((k,response) -> map.put(k, response.get()));

        map.forEach((k,v)-> System.out.println(k+",val:"+v));

}


  private static Pool<Jedis> jedisPool = null;

  /**
   * 连接redis,获取jedisPool
   * @return
   */
    public Jedis getJedis() {
        if (jedisPool == null) {
            JedisPoolConfig poolConfig = new JedisPoolConfig();
            poolConfig.setMaxTotal(maxTotal);
            poolConfig.setMaxIdle(maxIdle);
            poolConfig.setMaxWaitMillis(maxWaitMillis);
            poolConfig.setTestOnBorrow(testOnBorrow);
            //配置可以写在配置中心/文件
            jedisPool = new JedisPool(poolConfig, host, port, timeout, password, database);
        }
        return jedisPool.getResource();
    }

参考资料

https://redis.io/docs/manual/pipelining/
https://www.cnblogs.com/expiator/p/11127719.html

posted on 2023-01-29 21:04  乐之者v  阅读(873)  评论(0编辑  收藏  举报

导航