做啥網(wǎng)站最掙錢網(wǎng)站按天扣費(fèi)優(yōu)化推廣
????????開發(fā)的MES,往往都要做生產(chǎn)執(zhí)行跟蹤掃描,這樣會產(chǎn)生大量的掃描數(shù)據(jù),用關(guān)系型數(shù)據(jù)庫,很容易造成查詢沖突的問題。
????????生產(chǎn)跟蹤掃描就發(fā)生的密度是非常高的,每個零部件的加工過程,都要被記錄下來,特別是在大型工廠。寫入密度高,但是每次寫入的記錄都很少,如果程序設(shè)計是實時往數(shù)據(jù)庫寫入記錄,那基本是每次一條記錄,且寫入的頻率很高,導(dǎo)致表鎖時常發(fā)生,對查詢掃描日志會帶來很大的壓力。
??????? 解決跟蹤掃描的問題,最好的辦法還是用緩存(Redis),而且要共享緩存,就是不同用戶使用同一個緩存空間,防止相同的零部件掃描時候,不會出現(xiàn)相互沖突。實際生產(chǎn)過程中,掃描沖突是不應(yīng)該發(fā)生的,但是偏偏就是會發(fā)生,例如操作員后補(bǔ)掃描。程序發(fā)起掃描請求時候,先去查詢緩存是否存在掃描記錄,如果有,則根據(jù)緩存中的數(shù)據(jù)進(jìn)行掃描校驗,例如是否存在掃描的編號、重復(fù)掃描、掃描次數(shù)限制等;如果緩存中沒有數(shù)據(jù),則表明零部件從來沒有被掃描過,則從數(shù)據(jù)庫中加載基礎(chǔ)信息,例如零部件的信息、工序的信息、工作中心的信息等,然后這些信息也放到緩存中,基礎(chǔ)信息不能存放太久,一般掃描也就毫秒級完成,算1秒吧,可以設(shè)置緩存超時清除,我這里是設(shè)置10分鐘的絕對超時,10分鐘后重新從數(shù)據(jù)庫加載基礎(chǔ)數(shù)據(jù)。
??????? 掃描數(shù)據(jù)一直保存在緩存中,根據(jù)生產(chǎn)周期,也可以設(shè)定一個延期超時,例如90天,每訪問一次,就重置超時時間。掃描記錄,可以全部存放到內(nèi)存緩存中(MemoryCache),用一個定時器往數(shù)據(jù)庫中寫入掃描記錄,不管掃描是否成功,日志都要寫入數(shù)據(jù)庫保存。如果出現(xiàn)數(shù)據(jù)庫事務(wù)沖突或者超時,則后續(xù)繼續(xù)往數(shù)據(jù)庫中寫入,直到寫入成功后,才從內(nèi)存緩存中移除掃描數(shù)據(jù)。
??????? 一般來說,程序設(shè)計查詢掃描記錄,從緩存中查詢即可,數(shù)據(jù)結(jié)構(gòu)采用Hash保存:
??????? Key:????????????????Scan_零部件編號,注意,部件編號這里是全局唯一
??????? HashKey??????? 工序編號
??????? Value??????????????? 掃描數(shù)據(jù)json????????[ {'scanTime':'2024-01-01','operator':'001'}]
??????? 因為同一個工序,可能需要掃描2次或者以上的,所以value的設(shè)計是個集合
當(dāng)查詢的時候,只需要把key的所有數(shù)據(jù)一次性讀取出來丟去前端即可,完全不需要查詢數(shù)據(jù)庫。不管是寫入還是讀取,都比數(shù)據(jù)庫快很多,代碼也很簡潔。當(dāng)然,之類最好把操作緩存的功能封裝成一個工具類,畢竟設(shè)計到內(nèi)存緩存和Redis。
??????? 在常規(guī)的需求中,以上功能已經(jīng)完全滿足日常生產(chǎn)需求,但是,如果超過了90天后,掃描緩存被清除了呢?那么,就需要在數(shù)據(jù)庫中查詢組織數(shù)據(jù),并重新寫入到緩存中。那么,使用clickhouse查詢,那是完全優(yōu)于使用關(guān)系數(shù)據(jù)庫,例如oracle、sqlserver、mysql、postgresql。不僅僅是構(gòu)造掃描緩存,可能還要做一些分析,這里只是用最簡單的一個查詢來說明clickhouse和關(guān)系數(shù)據(jù)庫之間的一些差異。
??????? 第一,要把數(shù)據(jù)同步到clickhouse。前邊也已經(jīng)踩坑了,系統(tǒng)使用的mssql,開發(fā)語言是C#,研究了很久,沒有現(xiàn)成的方案同步,或者說,還沒弄明白。本來打算flink-cdc同步數(shù)據(jù),結(jié)果弄出一堆問題來,還是沒搞通,所以,這個事暫時耽擱下來,自己寫了一個cdc的同步工具,也就2天時間,夠用就好了,湊合著,后續(xù)再研究flink。flink官方?jīng)]有現(xiàn)成的clickhouse-sink,哪位大神指點(diǎn)一二!
??????? 第二,涉及到的查詢關(guān)鍵字。用到的關(guān)鍵字做一個簡單的說明:
??????? limit n by exp???????
????????根據(jù)字段取第n條記錄,這個關(guān)鍵字不能和distinct同時使用,只取其一。例如掃描記錄出現(xiàn)重復(fù)的掃描日志,要取最后一次,這樣需要配合order by才能實現(xiàn)。limit在clickhouse中,不僅僅用于分頁,例如記錄中出現(xiàn)(key,value)= {a,1},{a,2},order by value limit 1 by key,則會得到{a,1},第二條記錄會被過濾掉。
??????? first_value(exp1) over(partition by exp2 order by exp3)
??????? 根據(jù)字段exp2分組,exp3的順序,取第一條記錄exp1的值。例如掃描記錄有5個步驟,取第一個步驟的工序編號 first_value(processid) over(partition by partId order by scanTime)
??????? groupArray(exp1) over(partition by exp2 order by exp3 Rows BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
??????? 加載分組中字段后的exp1字段的所有值,例如部件掃描了5個工序,這個查詢構(gòu)造出一個列表字段,返回掃描的所有工序編號 {001,002,003,004,005},同理,用相同的方法把掃描時間也查出來,放到另外一個字段,順序是一樣的
??????? 上述關(guān)鍵字,會對查詢條件有限制的,出現(xiàn)在where中的字段,必須在select中出現(xiàn),類似group by的要求,這個好辦,最一個子查詢就可以解決了:
??????? with tb as (select ... from ... where ...)
??????? select * from tb
這樣就完全規(guī)避了查詢限制
上個例子,一般的關(guān)系數(shù)據(jù)庫是很難實現(xiàn)這樣的分組查詢,當(dāng)然,用后端代碼也能構(gòu)造出這樣的結(jié)構(gòu),就另說了。
在mes中,有很多復(fù)雜的查詢,生產(chǎn)庫使用mssql,查詢庫使用clickhouse,實現(xiàn)了讀寫分離。mssql通過cdc與clickhouse同步,開發(fā)的cdc同步組件,同時支持rabbitmq,可以把變更數(shù)據(jù)通過rabbitmq分發(fā)出去,讓其他應(yīng)用做實時數(shù)據(jù)統(tǒng)計和分析。
clickhouse驅(qū)動在開發(fā)語言中還是很豐富的,我使用的是clickhouse.client,在nuget中直接安裝即可,使用起來和ado差不多,git中有比較完整的文檔。這里報告一個bug,執(zhí)行executeNonQuery返回整數(shù)時候,無論是否成功,都返回0,這個就很蛋疼了!
??????? 一開始的時候,讀寫分離用的是ssis同步到另外一臺mssql,雖然這樣很方便,但是也有不方便的時候,例如要修改目標(biāo)數(shù)據(jù)庫的內(nèi)容,會導(dǎo)致ssis同步報錯,關(guān)鍵是,當(dāng)數(shù)據(jù)量上億的時候,再怎么優(yōu)化也很慢,占用的磁盤空間也大,clickhouse經(jīng)過壓縮后,壓縮比例普遍在15~25%左右,可以節(jié)約大量的磁盤空間。只要設(shè)置好表的分區(qū)分片,性能那是杠杠的。
????