之前開(kāi)發(fā)的東華車(chē)管OBD的數(shù)據(jù)采集端一直比較穩(wěn)定,所采用的技術(shù)方案是以Netty為網(wǎng)絡(luò)框架,以Redis作為消息隊(duì)列和存儲(chǔ)工具业踢,進(jìn)行數(shù)據(jù)采集、存儲(chǔ)和發(fā)送礁扮,雖然經(jīng)歷了不少問(wèn)題知举,但是經(jīng)過(guò)一段時(shí)間的處理之后,相對(duì)來(lái)說(shuō)還是比較穩(wěn)定的太伊,最長(zhǎng)時(shí)間連續(xù)運(yùn)行近一個(gè)月沒(méi)有出現(xiàn)過(guò)任何問(wèn)題雇锡,不過(guò)最近我們的服務(wù)端幾乎每天都崩潰,這讓我非常的納悶僚焦,已經(jīng)穩(wěn)定運(yùn)行了好幾個(gè)月的平臺(tái)從未出現(xiàn)過(guò)如此嚴(yán)重的穩(wěn)定性問(wèn)題锰提,所以我也非常重視這個(gè)Bug,以最快的時(shí)間開(kāi)始著手解決問(wèn)題。
Bug的內(nèi)容是這樣的:
Could not get a resource from the pool
java.lang.NullPointerException
第一句是說(shuō)欲账,沒(méi)辦法從jedis連接池里獲取jedis對(duì)象了,第二句是說(shuō)芭概,空指針異常了赛不。
從錯(cuò)誤的本身理解,我的第一反應(yīng)是罢洲,壞了踢故,是不是我的Jedis連接池沒(méi)有正常回收惹苗,導(dǎo)致連接池中的連接數(shù)被用完了殿较?
當(dāng)然我是比較懷疑的,因?yàn)椋?br>
1.采集的車(chē)輛數(shù)據(jù)并不多桩蓉,由于并發(fā)造成的連接池連接數(shù)被用完的可能性微乎其微淋纲。
2.運(yùn)行了好幾個(gè)月了都沒(méi)有出現(xiàn)這種情況,在車(chē)輛沒(méi)有大批量增加且車(chē)輛總數(shù)并不多的情況下院究,怎么可能突然就開(kāi)始出現(xiàn)這種問(wèn)題呢洽瞬?
但是,Bug is Bug,該解決你就得解決业汰,所以我就立即從連接數(shù)回收的方向去入手伙窃,看看是不是我的Jedis寫(xiě)的有問(wèn)題。
虛驚一場(chǎng)
通過(guò)谷歌样漆,我找到了別人寫(xiě)的Jedis服務(wù)類(lèi):
// 生成多機(jī)連接信息列表
List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
shards.add( new JedisShardInfo("127.0.0.1", 6379) );
shards.add( new JedisShardInfo("192.168.56.102", 6379) );
// 生成連接池配置信息
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxTotal(30);
config.setMaxWaitMillis(3*1000);
// 在應(yīng)用初始化的時(shí)候生成連接池
ShardedJedisPool pool = new ShardedJedisPool(config, shards);
// 在業(yè)務(wù)操作時(shí)为障,從連接池獲取連接
ShardedJedis client = pool.getResource();
try {
// 執(zhí)行指令
String result = client.set("key-string", "Hello, Redis!");
System.out.println( String.format("set指令執(zhí)行結(jié)果:%s", result) );
String value = client.get("key-string");
System.out.println( String.format("get指令執(zhí)行結(jié)果:%s", value) );
} catch (Exception e) {
// TODO: handle exception
} finally {
// 業(yè)務(wù)操作完成,將連接返回給連接池if (null != client) {
pool.returnResource(client);
}
} // end of try block// 應(yīng)用關(guān)閉時(shí)放祟,釋放連接池資源
pool.destroy();
我又看了看我的Jedis回收寫(xiě)法:
public static void close(Jedis jedis) {
try {
jedis.close();
} catch (Exception e) {
if (jedis.isConnected()) {
jedis.quit();
jedis.disconnect();
}
}
}
發(fā)現(xiàn)了和人家寫(xiě)的怎么不一樣镑⒃埂?
這句:
pool.returnResource(client);
我怎么沒(méi)有寫(xiě)肮蛲住京景?
當(dāng)時(shí)大罵自己粗心,回收方法都沒(méi)寫(xiě)對(duì)怎么就能心安理得的發(fā)布程序呢骗奖?于是趕緊打開(kāi)IDE加上這句話(huà)确徙,可是當(dāng)我在我的程序中加上上邊這句代碼的時(shí)候,突然發(fā)現(xiàn)這行代碼是被遺棄的了执桌,旁邊寫(xiě)著提示:
Deprecated. starting from Jedis 3.0 this method will not be exposed. Resource cleanup should be done using @see redis.clients.jedis.Jedis.close()
意思是Jedis 3.0以后鄙皇,這句代碼已經(jīng)被jedis.close()方法替代了,所以我的寫(xiě)法是沒(méi)有問(wèn)題的仰挣,之前是虛驚一場(chǎng)伴逸。
初見(jiàn)端倪
好不容易緩口氣,但是依舊迷茫膘壶,既然我的回收寫(xiě)法是正確的错蝴,那為什么我的服務(wù)突然就無(wú)法從連接池獲取連接了呢洲愤?
帶著一頭的霧水,我開(kāi)始在服務(wù)器的Redis服務(wù)上找原因顷锰。
當(dāng)時(shí)我懷疑的是柬赐,是不是Redis的服務(wù)掛了?第一反應(yīng)去WIndows的服務(wù)里去看官紫,結(jié)果發(fā)現(xiàn)沒(méi)有掛肛宋,正常運(yùn)行中。
我想既然沒(méi)掛束世,那我連接一下看看吧酝陈。
于是就用redis-cli命令直接連接服務(wù),連上了沒(méi)問(wèn)題毁涉。
然后想說(shuō) 看看緩存隊(duì)列中還存著哪些數(shù)據(jù)吧沉帮?
于是就打了一個(gè)命令:
lrange obdMessage 0 -1
結(jié)果Redis直接報(bào)了警:
NOAUTH Authentication required
這里突然感覺(jué)不太對(duì)勁了,怎么好好的報(bào)了這個(gè)錯(cuò)誤呢贫堰?而且我的Redis是有密碼的遇西,不過(guò)很簡(jiǎn)單是 111111
我趕緊用命令:
auth 111111
來(lái)訪問(wèn)我的Redis,結(jié)果還是報(bào)錯(cuò)严嗜,直到這里我才發(fā)現(xiàn)粱檀,可能是我的Redis訪問(wèn)密碼被人動(dòng)了手腳。
著手解決
于是我重啟了Redis的服務(wù)漫玄,看看重啟后能不能訪問(wèn)Redis,結(jié)果還好茄蚯,重啟后Redis是可以訪問(wèn)了,我在查看存儲(chǔ)的數(shù)據(jù)的過(guò)程中睦优,突然發(fā)現(xiàn)好像多了一個(gè)字段:crackit渗常。
然后我去網(wǎng)上一搜,出現(xiàn)這個(gè)Crakit就說(shuō)明我的Redis肯定是被人入侵了汗盘,那么很有可能就就是我的Redis被人入侵之后皱碘,黑客改了我的Redis的密碼,導(dǎo)致我的Redis無(wú)法使用隐孽。
入侵的漏洞:
Redis 默認(rèn)情況下癌椿,會(huì)綁定在 0.0.0.0:6379,這樣將會(huì)將 Redis 服務(wù)暴露到公網(wǎng)上菱阵,如果在沒(méi)有開(kāi)啟認(rèn)證的情況下踢俄,可以導(dǎo)致任意用戶(hù)在可以訪問(wèn)目標(biāo)服務(wù)器的情況下未授權(quán)訪問(wèn) Redis 以及讀取 Redis 的數(shù)據(jù)。攻擊者在未授權(quán)訪問(wèn) Redis 的情況下可以利用 Redis 的相關(guān)方法晴及,可以成功在 Redis 服務(wù)器上寫(xiě)入公鑰都办,進(jìn)而可以使用對(duì)應(yīng)私鑰直接登錄目標(biāo)服務(wù)器。
入侵的特征:
Redis 可能執(zhí)行過(guò) FLUSHALL 方法,整個(gè) Redis 數(shù)據(jù)庫(kù)被清空
在 Redis 數(shù)據(jù)庫(kù)中新建了一個(gè)名為 crackit(網(wǎng)上流傳的命令指令) 的鍵值對(duì)琳钉,內(nèi)容為一個(gè) SSH 公鑰势木。
在 /root/.ssh 文件夾下新建或者修改了 authorized_keys 文件,內(nèi)容為 Redis 生成的 db 文件歌懒,包含上述公鑰
修復(fù)建議:
1.當(dāng)然就是改密碼了啦桌,之前的密碼太弱了,可能是不是被人家給碰上了歼培,直接就破解了震蒋,趕緊改一個(gè)復(fù)雜一點(diǎn)的密碼才是王道茸塞,如果你的Redis連密碼都沒(méi)有躲庄,那更不行了,看看我的遭遇钾虐,趕緊加上密碼吧噪窘!
在Redis服務(wù)的配置中加入密碼項(xiàng)目,默認(rèn)是注釋的效扫,你要手動(dòng)加入密碼倔监,打開(kāi)配置文件找到:
#requirepass foobared
去掉之前的注釋?zhuān)⑿薷臑樗枰拿艽a,保存文件并重啟Redis服務(wù)菌仁。
啟動(dòng)命令是:
redis-server --service-install redis.windows-service.conf --loglevel debug
注意 --loglevel debug 浩习,這里是加入了日志的啟動(dòng)方式,也是要在配置文件中進(jìn)行修改的,打開(kāi)配置文件找到:
loglevel verbose
去掉注釋并改為:
loglevel debug
并且由于要存放日志的話(huà)則要給存放的Redis日志啟用存儲(chǔ)路徑,在配置文件中搜索”logfile“就可以了济丘,然后給其指定路徑:
logfile "Logs/redis_log.txt"
注意:這里改完后谱秽,要啟動(dòng)服務(wù)前,在你的Redis的安裝目錄的根目錄下手動(dòng)創(chuàng)建一個(gè)名為"Logs"的文件夾摹迷,如果缺少這個(gè)文件夾峡碉,啟動(dòng)服務(wù)的時(shí)候會(huì)報(bào)錯(cuò)鲫寄。
2.改端口號(hào),默認(rèn)的6379端口肯定是不能用了鼠证,趕緊在redis的配置文件中修改端口號(hào)吧量九!
在Redis的配置文件中找到:
port 6379
改為別的未被占用的端口即可荠列。
3.禁止一些高危的命令:
修改Redis的配置文件,加入以下幾行命令:
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command CONFIG ""
rename-command EVAL ""
﹡以下的一些設(shè)置是網(wǎng)上的肌似,我沒(méi)用用川队,因?yàn)椴贿m合我的情況固额,僅供參考:
4.以權(quán)限運(yùn)行Redis服務(wù)
為 Redis 服務(wù)創(chuàng)建單獨(dú)的用戶(hù)和家目錄,并且配置禁止登陸
5.禁止外網(wǎng)訪問(wèn) Redis
修改Redis配置文件斗躏,添加或修改:
bind 127.0.0.1
使得 Redis 服務(wù)只在當(dāng)前主機(jī)可用
總結(jié)
淺薄白牟凇!粗心吧虮ぁ燕雁!一切的一切都是因?yàn)樽约涸诜?wù)器和Redis安全性這里不夠重視而導(dǎo)致的問(wèn)題,其實(shí)只要設(shè)置一個(gè)復(fù)雜一點(diǎn)的密碼率拒,估計(jì)也不會(huì)這么輕易的被入侵了猬膨,希望我的遭遇能給你帶來(lái)一些啟發(fā)勃痴,少走或者不走彎路沛申,趕緊加固自己的Redis安全防御吧铁材!