原文链接
批量操作优化:
- 在使用redis的时候,客户端通过socket向redis服务端发起请求后,等待服务端的返回结果。
- 客户端请求服务器一次就发送一个报文 -> 等待服务端的返回 -> 关闭连接
- 如果是100个请求、1000个请求,那就得请求100次、1000次
- 所以使用多个请求的时候使用管道来操作(
如果管道打包的命令太多占用的内存也会越大,适量
) -
以下是使用3种方式进行的测试(
循环写入1000次
):public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { template.opsForHash().put("user1", "status" + i, "value" + i); } System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); final byte[] rawKey = rawKey("user2"); template.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { for (int i = 0; i < 1000; i++) { final byte[] rawHashKey = rawHashKey("status" + i); final byte[] rawHashValue = rawHashValue("value" + i); connection.hSet(rawKey, rawHashKey, rawHashValue); } return null; } }); System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); final byte[] rawKey2 = rawKey("user3"); template.execute(new RedisCallback<Object>() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.openPipeline(); for (int i = 0; i < 1000; i++) { final byte[] rawHashKey = rawHashKey("status" + i); final byte[] rawHashValue = rawHashValue("value" + i); connection.hSet(rawKey2, rawHashKey, rawHashValue); } return connection.closePipeline(); } }); System.out.println(System.currentTimeMillis() - start); }
-
请求结果
20:54:51.653 [main] DEBUG o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 20:54:51.693 [main] DEBUG o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 42283 20:54:51.695 [main] DEBUG o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 20:55:33.922 [main] DEBUG o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 42228 20:55:33.923 [main] DEBUG o.s.d.r.core.RedisConnectionUtils - Opening RedisConnection 20:55:34.058 [main] DEBUG o.s.d.r.core.RedisConnectionUtils - Closing Redis Connection 136
- 可以看出在使用管道打包发送请求所用的时间不到1s。而发送1000次请求所用的时间达到了42s。
从请求结果看到多次 Opening RedisConnection 和
Closing Redis Connection 的日志.
阅读源代码我们可以发现我们对redis的所有操作都是通过回调execute函数执行的,其代码如下:
public <T> T execute(RedisCallback<T> action, boolean exposeConnection) { return execute(action, exposeConnection, false); } // execute实现如下: // org.springframework.data.redis.core.RedisTemplate<K, V> --- 最终实现 public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) { Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it"); Assert.notNull(action, "Callback object must not be null"); RedisConnectionFactory factory = getConnectionFactory(); RedisConnection conn = null; try { if (enableTransactionSupport) { // only bind resources in case of potential transaction synchronization conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport); } else { conn = RedisConnectionUtils.getConnection(factory); } boolean existingConnection = TransactionSynchronizationManager.hasResource(factory); RedisConnection connToUse = preProcessConnection(conn, existingConnection); boolean pipelineStatus = connToUse.isPipelined(); if (pipeline && !pipelineStatus) { connToUse.openPipeline(); } RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse)); T result = action.doInRedis(connToExpose); // close pipeline if (pipeline && !pipelineStatus) { connToUse.closePipeline(); } // TODO: any other connection processing? return postProcessResult(result, connToUse, existingConnection); } finally { if (!enableTransactionSupport) { RedisConnectionUtils.releaseConnection(conn, factory); } } }
这里面每次执行action.doInRedis(connToExpose)前都要调用RedisConnectionUtils.getConnection(factory);获得一个连接,进入RedisConnnectionUtils类中,getConnection(factory)最终调用的是doGetConnection(factory, true, false, enableTranactionSupport)这个函数。这个函数我们可以看下api文档,发现实际上并不是真的创建一个新的redis连接,它只是在connectFactory中获取一个连接,也就是从连接池中取出一个连接。当然如果connectFactory没有连接可用,此时如果allowCreate=true便会创建出一个新的连接,并且加入到connectFactory中。
基本上可以确定真实的情况是spring-data-redis已经帮我们封装了连接池管理,我们只需要调用一系列操作函数即可,这给操作redis带来了极大的方便。