文档章节

Redis scan命令的一次坑

2019在路上
 2019在路上
发布于 01/05 23:35
字数 1109
阅读 202
收藏 3

Redis作为当前服务架构不可或缺的Cache,其支持丰富多样的数据结构,Redis在使用中其实也有很多坑,本次博主遇到的坑或许说是Java程序员会遇到的多一点,下面就听博主详细道来。

线上服务堵塞

String key = keyOf(appid);
int retryCount = 3;
int socketRetryCount = 3;
Exception ex = null;
while(retryCount > 0 && socketRetryCount > 0) {
    try {
        return redisDao.getMap(key);
    }catch (Exception e) {

    }
}

12月2日被告知服务出现异常,查看日志发现其运行到上述代码getMap方法处后日志就没有内容了。

问题分析

"pool-13-thread-6" prio=10 tid=0x00007f754800e800 nid=0x71b5 waiting on condition [0x00007f758f0ee000]
    java.lang.Thread.State: WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x0000000779b75f40> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
    at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:583)
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:442)
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:363)
    at redis.clients.util.Pool.getResource(Pool.java:49)
    at redis.clients.jedis.JedisPool.getResource(JedisPool.java:99)
    at org.reborndb.reborn.RoundRobinJedisPool.getResource(RoundRobinJedisPool.java:300)
    at com.le.smartconnect.adapter.spring.RebornConnectionFactory.getConnection(RebornConnectionFactory.java:43)
    at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
    at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:91)
    at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:78)
    at xxx.run(xxx.java:80)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)

    Locked ownable synchronizers:
- <0x000000074f529b08> (a java.util.concurrent.ThreadPoolExecutor$Worker)

从线程日志可以看出服务堵塞在获取redis连接处.

分析:

  • 代码配置中redis最大连接为3000
  • redis配置中session_max_timeout为0,即永不断开连接

一次修改分析

从以上两点分析得出,redis连接被耗尽,于是查找代码得知由于重写spring-data-redis中的hscan方面导致,代码如下:

RedisConnection rc = redisTemplate.getConnectionFactory().getConnection();
if (rc instanceof JedisConnection) {
    JedisConnection JedisConnection = (JedisConnection) rc;
    return new ConvertingCursor<Map.Entry<byte[], byte[]>, Map.Entry<String, String>>(
            JedisConnection.hScan(rawValue(key), cursor, scanOptions),
            new Converter<Map.Entry<byte[], byte[]>, Map.Entry<String, String>>() {

            @Override
            public Entry<String, String> convert(final Entry<byte[], byte[]> source) {

                return new Map.Entry<String, String>() {

                @Override
                public String getKey() {
                    return hashKeySerializer.deserialize(source.getKey());
                }

                @Override
                public String getValue() {
                    return hashValueSerializer.deserialize(source.getValue());
                }

                @Override
                public String setValue(String value) {
                    throw new UnsupportedOperationException(
                        "Values cannot be set when scanning through entries.");
                }
            };

        }
    });
} else {
    return hashOps.scan(key, scanOptions);
}

上述代码返回ConvertingCursor后未释放连接,导出连接被占满。

二次修改分析

于是修改代码为正常释放连接

try {
    ...
}finally {
    RedisConnectionUtils.releaseConnection(rc, factory);
}

代码经过上线,再次跑程序查看线上日志发现报了大量的Connection time out.

于是博主就思考是不是由于重写代码不对,尝试使用spring-data-redis的原生代码,即直接调用hashOps.scan(key, scanOptions)方法,再次上线。

上线后观察日志:发现这次不是报Connection time out,日志中大量报Unknown reply:错误。

分析如下:

由于代码是在多线程环境下运行,有几百个线程去调用hscan操作,spring-data-redis原生的代码执行完一次hscan操作后就会关闭连接并返回一个迭代器Cursor,但是遍历Cursor时在本次count后会再次根据游标重新使用该连接进行查询,可是连接却已经被关闭,这时会使用新的连接是可以正常迭代的,但是一旦复用到其他线程使用的连接则会导致报错Unknown reply.

三次修改分析

经过思考后得出结论,redis在执行scan操作时一旦连接被释放,那么scan操作将不会进行下去,则报Connection time out.

查阅官方文档得出结论,redis的scan操作需要full iteration,即最优方式是一个连接将以此scan任务执行完全后释放该连接。

redis-scan-doc

修改代码如下:

RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection rc = factory.getConnection();
if (rc instanceof JedisConnection) {
    JedisConnection JedisConnection = (JedisConnection) rc;
    Cursor<Map.Entry<String, String>> cursorResult = new ConvertingCursor<Map.Entry<byte[], byte[]>, Map.Entry<String, String>>(
            JedisConnection.hScan(rawValue(key), cursor, scanOptions),
            new Converter<Map.Entry<byte[], byte[]>, Map.Entry<String, String>>() {
            ...
            });
return new ScanResult<Map.Entry<String, String>>(cursorResult, factory, rc);}


public void releaseConnection() throws IOException{
    IOException ex = null;
    if(cursor != null) {
        try {
            cursor.close();
        } catch (IOException e) {
            ex = e;
        }
    }
    try {
        RedisConnectionUtils.releaseConnection(rc, factory);
    } catch (Exception e) {

    }
    if(ex != null) {
        throw ex;
    }
}

将连接返回给业务代码,并在业务代码执行完毕后将连接释放,问题解决。

总结

  1. 连接一旦开启就必须释放,否则造成内存泄漏或服务堵塞不可用
  2. 重写代码时需要谨记仔细查阅官方文档给出的方案并实施
  3. 多线程下使用redis的scan操作需要使用一个连接遍历完Cursor,而不能复用连接,否则导致报错Unknown reply.

 

本文转载自:https://www.jianshu.com/p/d9f0a547bd0e

2019在路上
粉丝 42
博文 141
码字总数 72844
作品 0
杭州
高级程序员
私信 提问
在RedisTemplate中使用scan代替keys指令

这个命令千万别在生产环境乱用。特别是数据庞大的情况下。因为Keys会引发Redis锁,并且增加Redis的CPU占用。很多公司的运维都是禁止了这个命令的 当需要扫描key,匹配出自己需要的key时,可以...

物种起源-达尔文
08/30
68
0
【临实战】使用 Python 从 Redis 中删除 4000W 个 KEY

SCAN 命令 DEL 命令 使用 Python SCAN 使用 Python DEL 成果展示

2018/07/03
0
0
Java使用Pipeline对Redis批量读写(hmset&hgetall)

一般情况下,Redis Client端发出一个请求后,通常会阻塞并等待Redis服务端处理,Redis服务端处理完后请求命令后会将结果通过响应报文返回给Client。 这有点类似于HBase的Scan,通常是Client端...

引鸩怼孑
2015/05/26
15.6K
0
Redis 面试知识点笔记(三)从海量数据里查询某一固定前缀的key

问:从海量数据里查询某一固定前缀的key有哪些方法? 使用keys pattern keys指令一次性返回所有匹配的key,如果键的数量过大,服务器会卡顿,对内存消耗和redis服务器都是隐患,对线上业务也有...

断风格男丶
05/13
57
0
.NET Core 实现 Redis 批量查询指定格式的Key

一. 问题场景 Redis 作为当前最流行的内存型 NoSQL 数据库,被许多公司所使用,作为分布式缓存。我们在实际使用中一般都会为 key 带上指定的前缀或者其他定义的格式。当由于我们程序出现bug...

晓晨master
2018/09/05
0
0

没有更多内容

加载失败,请刷新页面

加载更多

分享一个 pycharm 专业版的永久使用方法

刚开始接触Python,首先要解决的就是Python开发环境的搭建。 目前比较好用的Python开发工具是PyCharm,他有社区办和专业版两个版本,但是社区版支持有限,我们既然想好好学python,那肯定得用...

上海小胖
37分钟前
6
0
Spring Cloud Alibaba 实战(二) - 关于Spring Boot你不可不知道的实情

0 相关源码 1 什么是Spring Boot 一个快速开发的脚手架 作用 快速创建独立的、生产级的基于Spring的应用程序 特性 无需部署WAR文件 提供starter简化配置 尽可能自动配置Spring以及第三方库 ...

JavaEdge
今天
7
0
TensorFlow 机器学习秘籍中文第二版(初稿)

TensorFlow 入门 介绍 TensorFlow 如何工作 声明变量和张量 使用占位符和变量 使用矩阵 声明操作符 实现激活函数 使用数据源 其他资源 TensorFlow 的方式 介绍 计算图中的操作 对嵌套操作分层...

ApacheCN_飞龙
今天
8
0
五、Java设计模式之迪米特原则

定义:一个对象应该对其他对象保持最小的了解,又叫最小知道原则 尽量降低类与类之间的耦合 优点:降低类之间的耦合 强调只和朋友交流,不和陌生人说话 朋友:出现在成员变量、方法的输入、输...

东风破2019
昨天
25
0
jvm虚拟机结构

1:jvm可操作数据类型分为原始类型和引用类型,因此存在原始值和引用值被应用在赋值,参数,返回和运算操作中,jvm希望在运行时 明确变量的类型,即编译器编译成class文件需要对变量进行类型...

xpp_ba
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部