30w+數(shù)據(jù)使用RedisTemplate?pipeline空指針NullPointerException異常分析
正文
為了實(shí)現(xiàn)布控預(yù)警的業(yè)務(wù)需求,在flink流式處理時(shí)查詢(xún)r(jià)edis進(jìn)行比對(duì),然后方案是放在redis里的數(shù)據(jù)格式為Set,key使用的直接是布控對(duì)象的值,比如身份證號(hào)、車(chē)牌號(hào)、手機(jī)號(hào)、imsi碼等等,然后value的值為該布控對(duì)象所屬的任務(wù)ID,之所以是這樣設(shè)計(jì),是因?yàn)榭赡苡卸鄠€(gè)任務(wù)都設(shè)置了該布控對(duì)象,這時(shí)候比對(duì)到了該布控對(duì)象,會(huì)根據(jù)任務(wù)id查詢(xún)對(duì)應(yīng)的策略進(jìn)行不同的業(yè)務(wù)處理。
目前一個(gè)任務(wù)的布控對(duì)象的數(shù)據(jù)量批量插入20w+的數(shù)據(jù)到redis中,數(shù)據(jù)量比較大,所以用到了pipe,在使用過(guò)程中發(fā)生了錯(cuò)誤,發(fā)生了NullPointerException異常,進(jìn)行了排查記錄如下
首先debug代碼
偽代碼就是
redisTemplate.executePipeLined(connection -> {
objectives.parallelStream().forEach(obj -> {
connection.setCommands().sAdd(rawKey,rawValue);
});
})objectives是所有布控對(duì)象,這里我用到了并行流(后面我改成串行就沒(méi)報(bào)錯(cuò)了,原因后面講),代碼是沒(méi)啥問(wèn)題的,然后報(bào)錯(cuò)地方是執(zhí)行完上述邏輯后,執(zhí)行connection.closePipeLine()里的result.getResultHolder()該result是null,發(fā)現(xiàn)了異常,為啥list元素里會(huì)是空的呢,原來(lái)lettuce的pipe是初始化一個(gè)arraylist進(jìn)行存儲(chǔ)的,而我外面是一個(gè)并行流,相當(dāng)于是并發(fā)調(diào)用connection.setCommands().sAdd()->pipe.add(),是這里發(fā)生了并發(fā)操作list的錯(cuò)誤導(dǎo)致的。
我自己寫(xiě)了一個(gè)測(cè)試用例,并行流調(diào)用arraylist.add,然后去遍歷里面的元素,也是會(huì)報(bào)元素為空導(dǎo)致的NullPointerException
沒(méi)想到啊,雖然lettuce底層是共享一個(gè)鏈接,使用的是netty的異步模型,不過(guò)也防止不了開(kāi)發(fā)人員在外面是并發(fā)操作這個(gè)命令啊,lettuce是線(xiàn)程安全的庫(kù),但是這個(gè)LettuceConnection是spring這邊實(shí)現(xiàn)的,結(jié)果來(lái)這一出...bug蠻多的,之前用spring-data-jpa那塊分頁(yè)也有點(diǎn)問(wèn)題,模塊太多了,依賴(lài)多個(gè)庫(kù)之后配置多了可能spring的開(kāi)發(fā)人員都弄不清關(guān)系了,給他們提了一個(gè)bug
https://github.com/spring-projects/spring-data-redis/issues/2653
調(diào)試分析
接上面,我在調(diào)試的時(shí)候,pipe里有些返回值error是command timed out after 10 seconds,這就是原因,執(zhí)行redis命令超時(shí)了,為啥會(huì)超時(shí)呢,由于我使用的是Lettuce的redis客戶(hù)端庫(kù)(最后發(fā)現(xiàn)就是這個(gè)庫(kù)的原因),我開(kāi)始自己調(diào)試,不調(diào)試不要緊,一調(diào)試震驚了,我在上面的forEach循環(huán)里打斷點(diǎn),結(jié)果發(fā)現(xiàn)執(zhí)行一個(gè)sAdd就會(huì)立即把值flush推到redis服務(wù)器,馬上就生效了。
這說(shuō)明lettuce的pipe根本就是個(gè)擺設(shè),于是進(jìn)去sAdd()看了一下,在DefaultEndpoint.write()方法里有個(gè)autoFlushCommands為true,所以其實(shí)每次sAdd()都會(huì)直接flush到服務(wù)器而不是添加到緩沖區(qū)buffer。
這里不得不說(shuō)一下原因了,lettuce底層使用的是netty,是一個(gè)異步非阻塞(通過(guò)Future-Listener機(jī)制實(shí)現(xiàn)異步事件,網(wǎng)絡(luò)IO使用的是同步非阻塞IO,即NIO)的線(xiàn)程和請(qǐng)求/響應(yīng)模型(一般情況是不需要禁用自動(dòng)刷新的,可見(jiàn)官網(wǎng)說(shuō)明),所以我們才會(huì)在上面報(bào)command timed out after 10 seconds時(shí)沒(méi)拋出來(lái)這個(gè),因?yàn)槭褂玫氖莕etty的future每個(gè)請(qǐng)求都是異步的。
這個(gè)錯(cuò)誤是lettuce拋出來(lái)的,因?yàn)閚etty的異步模型,所以lettuce會(huì)給每個(gè)command(Future)設(shè)置一個(gè)ScheduleFuture定時(shí)任務(wù),時(shí)間為timeout,里面的邏輯是如果command到時(shí)間了還沒(méi)isDone()就會(huì)設(shè)置一個(gè)超時(shí)異常。
后面看了官網(wǎng)文檔,其實(shí)建議我們用默認(rèn)的自動(dòng)flush就行了,我于是做了下對(duì)比,先把超時(shí)時(shí)間改大點(diǎn),不讓拋異常,然后比較性能,并行流因?yàn)橛衎ug,所以這里我用反射把pipe的List實(shí)現(xiàn)arraylist改成了線(xiàn)程安全的list,并進(jìn)行測(cè)試
我的布控對(duì)象測(cè)試數(shù)據(jù)解析后大概key有28w
| 實(shí)現(xiàn)方式 | 耗時(shí) | 線(xiàn)程安全的list |
|---|---|---|
| 并行流關(guān)閉自動(dòng)flush | 平均10.08/s | |
| 并行流開(kāi)啟自動(dòng)flush | 平均9.16/s | |
| 串行流關(guān)閉自動(dòng)flush | 平均15.36/s | 平均16.20/s |
| 串行流開(kāi)啟自動(dòng)flush | 平均6.74/s | 平均8.06/s |
可以看到官網(wǎng)推薦的自動(dòng)每次執(zhí)行就flush的性能更高些
lettuce設(shè)置timeout超時(shí)時(shí)間,spring.redis.timeout,我之前設(shè)置的是10s,可以自行調(diào)整
lettuce設(shè)置pipe的禁用自動(dòng)刷新,這里需要重新構(gòu)造LettuceConnectionFactory,然后設(shè)置PipelingFlushPolicy
我在官網(wǎng)找到了說(shuō)明https://lettuce.io/core/release/reference/#_pipelining_and_co...里面也說(shuō)明了pipe默認(rèn)每個(gè)命令在發(fā)出后都寫(xiě)入傳輸?shù)脑?,有興趣的可以看看
以上就是30w+數(shù)據(jù)使用RedisTemplate pipeline空指針NullPointerException異常分析的詳細(xì)內(nèi)容,更多關(guān)于RedisTemplate pipeline空指針異常的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot?aop里的@Pointcut()的配置方式
這篇文章主要介紹了springboot?aop里的@Pointcut()的配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
MybatisPlus逆向工程的項(xiàng)目實(shí)踐
Mybatis-Plus逆向工程,是MP官方提供的一款代碼生成器,可以自動(dòng)生成對(duì)應(yīng)的實(shí)體類(lèi)、Mapper接口和配置文件,,本文主要介紹了MybatisPlus逆向工程的項(xiàng)目實(shí)踐,感興趣的可以了解一下2024-03-03
Spring boot配置多數(shù)據(jù)源代碼實(shí)例
這篇文章主要介紹了Spring boot配置多數(shù)據(jù)源代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
java面試應(yīng)用上線(xiàn)后Cpu使用率飆升如何排查
這篇文章主要為大家介紹了java面試中應(yīng)用上線(xiàn)后Cpu使用率飆升如何排查的方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Spring Cloud詳解實(shí)現(xiàn)聲明式微服務(wù)調(diào)用OpenFeign方法
這篇文章主要介紹了Spring Cloud實(shí)現(xiàn)聲明式微服務(wù)調(diào)用OpenFeign方法,OpenFeign 是 Spring Cloud 家族的一個(gè)成員, 它最核心的作用是為 HTTP 形式的 Rest API 提供了非常簡(jiǎn)潔高效的 RPC 調(diào)用方式,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2022-07-07
MybatisPlus自定義Sql實(shí)現(xiàn)多表查詢(xún)的示例
這篇文章主要介紹了MybatisPlus自定義Sql實(shí)現(xiàn)多表查詢(xún)的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
Hibernate用ThreadLocal模式(線(xiàn)程局部變量模式)管理Session
今天小編就為大家分享一篇關(guān)于Hibernate用ThreadLocal模式(線(xiàn)程局部變量模式)管理Session,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
MyBatis中insert操作返回主鍵的實(shí)現(xiàn)方法
在使用MyBatis做持久層時(shí),insert語(yǔ)句默認(rèn)是不返回記錄的主鍵值,而是返回插入的記錄條數(shù)。這篇文章主要介紹了MyBatis中insert操作返回主鍵的方法,需要的朋友可以參考下2016-09-09
詳解Huffman編碼算法之Java實(shí)現(xiàn)
Huffman編碼是一種編碼方式,常用于無(wú)損壓縮。本文只介紹用Java語(yǔ)言來(lái)實(shí)現(xiàn)該編碼方式的算法和數(shù)據(jù)結(jié)構(gòu)。有興趣的可以了解一下。2016-12-12

