项目复盘思考
對(duì)自己所參與過(guò)的事物做個(gè)復(fù)盤(pán)是個(gè)好習(xí)慣,能讓自己對(duì)事物有個(gè)更深的了解。比如說(shuō),讀了一本書(shū),那么這個(gè)時(shí)候你需要回去思考:這本書(shū)講了些啥,對(duì)你有何影響;又或者,你參與了一個(gè)活動(dòng),你完了后,你也可以對(duì)其進(jìn)行復(fù)盤(pán),思考下這個(gè)活動(dòng)的意義,這個(gè)活動(dòng)是讓你增加見(jiàn)識(shí)了還是讓你放松心情了等;再或者,你參與了一個(gè)項(xiàng)目,這個(gè)時(shí)候還是可以復(fù)盤(pán),考慮項(xiàng)目的進(jìn)展中的難點(diǎn)等。其實(shí)復(fù)盤(pán),就是對(duì)自己經(jīng)歷過(guò)的事物進(jìn)行思考總結(jié)。是一個(gè)很好的自我矯正升華的方式。
下面我以一個(gè)程序員參與的項(xiàng)目的復(fù)盤(pán)來(lái)簡(jiǎn)要說(shuō)明下,復(fù)盤(pán)過(guò)程中可能思考的問(wèn)題和方向。
拋出問(wèn)題
問(wèn)題解答
這里以之前我做過(guò)的一個(gè)出單項(xiàng)目為例子簡(jiǎn)單說(shuō)一下可能的問(wèn)題思考總結(jié)。
為什么要做這個(gè)項(xiàng)目
面對(duì)日益增加的互聯(lián)網(wǎng)合作伙伴對(duì)接平安的出單系統(tǒng),原有的系統(tǒng)已經(jīng)無(wú)法完全滿足與日俱增出單壓力。對(duì)此,針對(duì)保險(xiǎn)單較為單一的投保,分離業(yè)務(wù),做成一個(gè)互聯(lián)網(wǎng)出單系統(tǒng)。快速響應(yīng)合作伙伴的出單需求.
項(xiàng)目是怎么選型的
SpringCloud: 成熟的為服務(wù)體系,JAVA生態(tài),成員已有springboot基礎(chǔ). 公司內(nèi)部已有基于springcloud的成功案例.
MongoDB: 簡(jiǎn)單保單類型適合嵌套文檔形式存儲(chǔ). 分布式(支持分片,可擴(kuò)展), 高可用(可配置副本)
RockMq: 在保證了可靠性的前提下,隊(duì)列特性足夠豐富,可運(yùn)維程度比較高。其快速橫向擴(kuò)展的能力,也能保證未來(lái)幾年我們對(duì)其性能的要求。RocketMQ基于Java語(yǔ)言開(kāi)發(fā)
RabbitMQ由于采用同步刷盤(pán),其可靠性非常強(qiáng)(采用ACK機(jī)制),但是性能卻是這三款開(kāi)源產(chǎn)品中最差的,在實(shí)際的測(cè)試中也能很輕易的得到驗(yàn)證。所以RabbitMQ更適合使用在對(duì)消息可靠性要求很高,但對(duì)消息吞吐量不太敏感的場(chǎng)景中。這里額外提一下一個(gè)個(gè)人經(jīng)驗(yàn),我們?cè)趯?duì)開(kāi)源產(chǎn)品選型時(shí),建議大家需要了解該產(chǎn)品是基于什么協(xié)議/規(guī)范實(shí)現(xiàn)的,這樣大致就清楚了這款開(kāi)源產(chǎn)品可以解決什么問(wèn)題,適用于哪些場(chǎng)景。例如消息中間件領(lǐng)域的AMQP、JMS、STOMP協(xié)議,工作流領(lǐng)域的bpmn2.0標(biāo)準(zhǔn)。
Kafka是由LinkedIn開(kāi)發(fā)的一個(gè)分布式的消息系統(tǒng),使用Scala編寫(xiě),它以可水平擴(kuò)展和高吞吐率而被廣泛使用。目前越來(lái)越多的開(kāi)源分布式處理系統(tǒng)如Cloudera、Apache Storm、Spark都支持與Kafka集成。Kafka的高吞吐是其最重要的一個(gè)特點(diǎn),這也帶來(lái)了一個(gè)潛在隱患在異常情況下,其消息丟失率較高。所以它比較適用于將高吞吐作為首要考慮,能容忍少量消息丟失的場(chǎng)景。例如作為大數(shù)據(jù)平臺(tái)數(shù)據(jù)傳輸?shù)牧魇焦艿?#xff0c;其在日志采集場(chǎng)景中也被廣泛應(yīng)用。
RocketMQ的核心架構(gòu)設(shè)計(jì)參考了Kafka,有網(wǎng)友甚至提出它是Kafka Java版本的實(shí)現(xiàn)。不可否認(rèn)的是RocketMQ的設(shè)計(jì)實(shí)現(xiàn)的確參考了很多Kafka的設(shè)計(jì)思想,比如說(shuō)均采用了基于文件的存儲(chǔ)方案、Topic的并行化操作、底層網(wǎng)絡(luò)協(xié)議均基于TCP (RocketMQ使用netty成熟的解決方案,Kafka使用一套自行設(shè)計(jì)的基于TCP層的協(xié)議),但兩者之間相對(duì)特點(diǎn)還是比較明顯,RocketMQ在犧牲了部分性能的前提下,提供了更多的機(jī)制來(lái)保證消息的可靠性,同時(shí)也提供了更為豐富和實(shí)用的消息隊(duì)列特性。比如說(shuō)消息的查詢、消息的回溯。這些接口的提供,極大的便利了消息中間件的運(yùn)維,提高了在一些異常場(chǎng)景中的故障恢復(fù)速度。
最終選擇了RocketMQ,理由是RocketMQ在保證了可靠性的前提下,隊(duì)列特性足夠豐富,可運(yùn)維程度比較高。其快速橫向擴(kuò)展的能力,也能保證未來(lái)幾年我們對(duì)其性能的要求。另外RocketMQ基于Java語(yǔ)言開(kāi)發(fā),也降低了我們后續(xù)對(duì)其進(jìn)行擴(kuò)展和二次開(kāi)發(fā)的難度。
項(xiàng)目的規(guī)模(數(shù)據(jù)量、并發(fā)量、增量等)
1wQPS, 每日單量60w、目前已有6000w單量.
還有比如說(shuō)項(xiàng)目部署的規(guī)模等情況(eg. 服務(wù)器多少臺(tái),緩存服務(wù),消息中間件等是如何部署的)
項(xiàng)目中遇到了什么難點(diǎn)?是如何解決這些問(wèn)題的
壓測(cè)監(jiān)控查看各方面的系統(tǒng)資源并未達(dá)到瓶頸,但是并發(fā)量達(dá)不到預(yù)期
問(wèn)題解決和排查思路: 老一套方案,首先定位性能瓶頸: CPU/內(nèi)存/IO等
ps aux | grep appname 查看進(jìn)程ID
top -H pid查看cpu和內(nèi)存的使用情況(未發(fā)現(xiàn)應(yīng)用服務(wù)器的cpu和內(nèi)存有較高的使用率)
cpu繁忙一般可能的情況如下:
- 線程中的不合理循環(huán)
- 發(fā)生了頻繁的full gc
- 多線程的上下文頻繁切換
通過(guò)jmap/jstack命令查看內(nèi)存/線程信息(可能是線程出現(xiàn)等待導(dǎo)致,所以也需要查看線程信息)
通過(guò)Arthas工具查看接口執(zhí)行過(guò)程中每個(gè)方法的耗時(shí)情況
$trace xxx #cost
方法內(nèi)部調(diào)用路徑,并輸出方法路徑上的每個(gè)節(jié)點(diǎn)上耗時(shí), trace 命令能主動(dòng)搜索 class-pattern/method-pattern 對(duì)應(yīng)的方法調(diào)用路徑,渲染和統(tǒng)計(jì)整個(gè)調(diào)用鏈路上的所有性能開(kāi)銷和追蹤調(diào)用鏈路。trace 能方便的幫助你定位和發(fā)現(xiàn)因 RT 高而導(dǎo)致的性能問(wèn)題缺陷,但其每次只能跟蹤一級(jí)方法的調(diào)用鏈路
結(jié)果如下(包名經(jīng)過(guò)處理):
+---[0.001162ms] com.xxx.service.dto.ApplyResultDTO:setApplyPolicyNo()+---[0.511972ms] com.xxx.service.apply.save.ApplySaveService:fillApplyPolicyNo()+---[19.709352ms] com.xxx.service.apply.save.ApplySaveService:saveContractDTOInfo()+---[1.313593ms] com.xxx.service.apply.save.ApplySaveService:saveApplyParameterInfo()+---[min=4.35E-4ms,max=9.48E-4ms,total=0.002326ms,count=3] java.lang.StringBuilder:<init>()+---[min=5.1E-4ms,max=0.00146ms,total=0.004705ms,count=6] java.lang.StringBuilder:append()+---[0.001361ms] com.xxx.base.dto.BaseInfoDTO:getTransactionNo()+---[min=4.86E-4ms,max=6.61E-4ms,total=0.001777ms,count=3] java.lang.StringBuilder:toString()+---[523.544075ms] com.xxx.common.service.redis.RedisService:stringSet()+---[9.91E-4ms] com.xxx.common.util.StringUtils:isNotBlank()+---[min=6.07E-4ms,max=7.75E-4ms,total=0.001382ms,count=2] java.lang.String:equals()+---[min=5.03E-4ms,max=6.95E-4ms,total=0.001198ms,count=2] java.lang.Integer:intValue()+---[min=592.763104ms,max=868.853641ms,total=1461.616745ms,count=2] com.xxx.common.service.redis.RedisService:ObjectSet()通過(guò)trace 定位到是redis耗時(shí)后,定位redis問(wèn)題
解決方案
項(xiàng)目還有什么不足?有什么解決方案?
spring-cloud-zuul-ratelimit、 針對(duì)不同合作伙伴分別限流
調(diào)整jvm參數(shù),如增加老年代比例。減少gc耗時(shí)停頓時(shí)間
目前使用緩存的時(shí)候,策略是先刪除緩存后更新數(shù)據(jù)庫(kù)的,在并發(fā)情況下,可能會(huì)導(dǎo)致緩存和數(shù)據(jù)庫(kù)的數(shù)據(jù)不一致,該問(wèn)題需要解決。目前考慮的解決方案有如下幾種:
- 在寫(xiě)數(shù)據(jù)庫(kù)操作的時(shí)候。利用Redis加鎖,使得數(shù)據(jù)更新完成之后才能讀緩存
- 寫(xiě)數(shù)據(jù)庫(kù)后,啟動(dòng)一個(gè)延遲定時(shí)任務(wù),定時(shí)任務(wù)負(fù)責(zé)讀取數(shù)據(jù)庫(kù)的最新數(shù)據(jù)更新Redis中的緩存
- Redis讀寫(xiě)請(qǐng)求入隊(duì)列,根據(jù)請(qǐng)求類型決定是否需要阻塞等待,以保證數(shù)據(jù)的一致性
總結(jié)
上面只是給了一個(gè)例子,還有更多的思考點(diǎn)等著自己去發(fā)掘。再者每個(gè)人的經(jīng)歷也不一樣,思考方向也許也會(huì)有更多的不同,但是最重要的是一個(gè)保持思考總結(jié)的習(xí)慣,該習(xí)慣并不是一定要等事情完結(jié)后才去考慮,在過(guò)程中我們也需要不斷的思考總結(jié)。
人會(huì)在思考總結(jié)中進(jìn)步,如果只是走馬觀花一般,那就真的像網(wǎng)上傳的一樣(十年工作經(jīng)驗(yàn),其實(shí)就是一年的工作經(jīng)驗(yàn)重復(fù)可十年)。希望大家不是活成了段子,而是在不斷的進(jìn)步,看到越來(lái)越好的自己。
總結(jié)
- 上一篇: linux pam模块 cron,Lin
- 下一篇: 开源路由软件zebra的命令存储原理及使