大貨車燃油盜竊案件管控應用詳述

1、項目業務介紹

盜竊大貨車燃油基本是深夜凌晨在高速服務區、出入口和路邊等地作案。 由於夜間監控模糊,監控被大貨車遮擋,同時嫌疑人故意繞開有監控的路 段,導致作案車輛監控抓拍線索非常少。因此,在偵查大貨車燃油盜竊案 件時,常常會遇到作案車輛發現難,行車軌跡研判難這到兩個難題。 


爲此我們開發了幾種找車的方法: 

1、 其次是模型找盜油車,我們根據民警積累的經驗開發了一套計算模型,是一些車輛、人員的時空、外觀特性信息,每天kafka監聽後臺大數據過車數據,通過過車數據和業務模型的條件匹配過濾出嫌疑車輛。再以 車輛線索和案件匹配,給用戶提供了一種新的、高效的研判思路。

2、 接下來是人臉找相關車,結合用戶經驗,案件中有大量前科人員作案,這 裏主要利用 rabbitMQ監聽盜油前科等嫌疑人員人臉名單庫布控報警,找出這些人 員出現的地點還有駕乘的車輛信息,爲案件提供線索。

4、 最後是規律找生活車,盜油人員的生活規律一般是駕駛生活車輛到盜油車 存放地並駕駛盜油車作案,作案回到盜油車存放點後又駕駛生活車輛回到 居住地。利用該盜油車及生活交替出現的規律,找出盜油人員的生活車輛。通過http接口調用查詢車輛軌跡,然後再利 用盜油車和生活車出行軌跡的規律進行分析研判,對明確嫌疑人的身份和 落腳區域起到很大幫助。 

2、項目架構


開發使用的技術棧:

spring boot 開發框架、consul註冊中心,feign服務調用與項目其他組件通信,ribbon後端負載均衡,postgresql 數據庫、kafka 和 rabbit MQ 消息監聽,mybatis 數據接入層框架,redis 緩存,xxl-job定時任務,sharding-jdbc分庫分表。 

部署架構:

Java Web做集羣部署,最直接需要注意的應該就是緩存類的共享以及定時任務等的衝突。緩存共享比如session共享、項目中用到的內存緩存等,需要在所有集羣內都可以共享到。然後就是一些只需要一個節點執行成功,其他節點不需要重複執行的業務,如定時任務等。

本應用方案爲:nginx代理+前端負載均衡;部署兩臺服務器;kafka,rmq,redis,數據庫也都是集羣部署;定時任務使用xxl-job,其調度中心集羣部署保證高可用。

3、項目亮點 

(1)與用戶深度溝通,得出細緻的業務模型。

(2)創造性的提出利用生活車和盜油車軌跡規律進行案件分析研判的思路。兩種車軌跡交點很有可能爲落腳點。

4、項目遇到的問題

4.1 kafka 0.10.2.0 消息堆積、重複消費

前提:部署了兩臺服務器。

問題出現現象:

1.其他訂閱的消費者組均能夠正常消費,只有本應用出現堆積

2.日誌中發現,本應用的兩臺服務器都在消費

3.日誌中還發現,搜索同一車牌號,有很多重複消費日誌。

4.查看應用消費日誌發現,該類有大量異常日誌,是一個數據庫異常,因爲車牌+過車時間是唯一索引,因此在添加相同車牌時間數據的時候報錯了。

5、kafka消費端日誌報commitException:

08-09 11:01:11 131 pool-7-thread-3 ERROR [] - 
commit failed 
org.apache.kafka.clients.consumer.CommitFailedException: Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member. This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time message processing. You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records.
        at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.sendOffsetCommitRequest(ConsumerCoordinator.java:713) ~[MsgAgent-jar-with-dependencies.jar:na]
        at org.apache.kafka.clients.consumer.internals.ConsumerCoordinator.commitOffsetsSync(ConsumerCoordinator.java:596) ~[MsgAgent-jar-with-dependencies.jar:na]
        at org.apache.kafka.clients.consumer.KafkaConsumer.commitSync(KafkaConsumer.java:1218) ~[MsgAgent-jar-with-dependencies.jar:na]
        at com.today.eventbus.common.MsgConsumer.run(MsgConsumer.java:121) ~[MsgAgent-jar-with-dependencies.jar:na]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_161]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_161]
        at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]

這個錯誤的意思是,消費者在處理完一批poll的消息後,在同步提交偏移量給broker時報的錯。初步分析日誌是由於當前消費者線程消費的分區已經被broker給回收了,因爲kafka認爲這個消費者死了,那麼爲什麼呢?

查閱資料分析推測:

因爲一個group只能有一臺消費,兩臺出現消費是否因爲出現連接出現問題,重新負載均衡了。

一條記錄被循環消費,應該是因爲本地提交消費位移失敗,纔回出現。

這裏就涉及到問題是消費者在創建時會有一個屬性max.poll.interval.ms,默認爲300s,
該屬性意思爲kafka消費者在每一輪poll()調用之間的最大延遲,消費者在獲取更多記錄之前可以空閒的時間量的上限。如果此超時時間期滿之前poll()沒有被再次調用,則消費者被視爲失敗,並且分組將重新平衡,以便將分區重新分配給別的成員。

    public void start(KafkaConsumer consumer, String metadata) {
        try {
            while (true) {
                ConsumerRecords records = consumer.poll(Duration.ofSeconds(2));
                if (records != null && !records.isEmpty()) {
                    log.info("---poll msg success from {} -----", metadata);
                    process(records);
                    consumer.commitAsync();
                } 
            }
        } catch (Throwable e) {
            log.error("consumer exception", e);
        } finally {
            try {
                consumer.commitSync();
            } finally {
                consumer.close();
            }
        }
    }

kafka的偏移量(offset)是由消費者進行管理的,偏移量有兩種,拉取偏移量(position)與提交偏移量(committed)。拉取偏移量代表當前消費者分區消費進度。每次消息消費後,需要提交偏移量。在提交偏移量時,kafka會使用拉取偏移量的值作爲分區的提交偏移量發送給協調者。
如果沒有提交偏移量,下一次消費者重新與broker連接後,會從當前消費者group已提交到broker的偏移量處開始消費。
所以,問題就在這裏,當我們處理消息時間太長時,已經被broker剔除,提交偏移量又會報錯。所以拉取偏移量沒有提交到broker,分區又rebalance。下一次重新分配分區時,消費者會從最新的已提交偏移量處開始消費。這裏就出現了重複消費的問題。

問題出現原因:

項目上線時max.poll.interval.ms的配置爲300秒,max.poll.records是500

(1)這次問題出現的原因爲隨着項目推進和成熟,平臺接入的抓拍機數量增多,項目初期抓拍機數量爲220臺,每天抓拍量200-250萬之間,峯值抓拍數量爲90條/s。條件判斷加入庫按0.2s計算,由於一批數據一般是500以內,耗時100秒,所以沒出現問題。

(2)之後抓拍機數量擴充到5100臺,每天抓拍量達到3500萬,峯值抓拍數量爲1400條/s。消費者線程處理能力趕不上消息的生產能力。消息就像滾雪球一樣越來越多,出現消息堆積現象。因此修改max.poll.records爲1500, 導致消息一次性poll()到的消息達到1500條,一批數據消費時間爲300秒,從而可能會超出max.poll.interval.ms。導致消費者組重平衡,因此連接到另外一臺消費服務器,然而另外一臺服務器也出現超時,又進行Rebalance...如此循環,纔出現了兩臺服務器都進行消費,並且一直重複消費。

解決辦法:

(1)增大max.poll.interval.ms時間爲600s,防範峯值流量。

(2) max.poll.records默認是500,減少該值也能解決這個問題,但是會造成消息堆積。

(3)批量入庫,縮短單批次消息處理時間。

之前是每條記錄走兩次查詢是否是被盜車和套牌車接口,入庫一次

現在被盜車和套牌車信息放入redis緩存並定時更新,一批次消息統一入庫一次。

(4)增加消費者線程數量

每臺服務器開啓2個消費者線程。一共兩臺服務器、4個消費者線程,同時增加兩個分區使分區數量等於消費者數量。

(5)另外,發現平臺的kafka broker和consumer client版本不一致也會導致性能下降:Producer、Consumer 和 Broker 的版本是相同的,它們之間的通信可以享受 Zero Copy 的快速通道;相反,一個低版本的 Consumer 程序想要與 Producer、Broker 交互的話,就只能依靠 JVM 堆中轉一下,丟掉了快捷通道,就只能走慢速通道了。因此,在優化 Broker 這一層時,你只要保持服務器端和客戶端版本的一致,就能獲得很多性能收益了。

效果:

省去了絕大部分接口調用和數據庫讀寫阻塞時間,處理一批記錄耗時700-800ms,單個消費者平均每秒處理700條數據。3個消費者實例即能夠抵禦當前流量衝擊。

流程:人臉/車輛抓拍數據獲取流程、名單庫布控流程

過車抓拍量統計:

(1)項目剛投入使用時:

(2)項目成熟時 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章