 Protobuf Schema 中 `map<_, _>` 字段編碼失敗的規(guī)則引擎實(shí)踐)
后端物聯(lián)網(wǎng)消息隊(duì)列通信【免費(fèi)下載鏈接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles項(xiàng)目地址https://gitcode.com/gh_mirrors/em/emqx點(diǎn)擊查看免費(fèi)下載EMQX 的 Schema Registryapps/emqx_schema_registry支持以 Protobuf、Avro、JSON 等格式定義編解碼 Schema并在規(guī)則引擎中通過schema_encode/schema_decode等 SQL 函數(shù)對(duì)消息載荷進(jìn)行轉(zhuǎn)換。本文以倉庫變更記錄 changes/ee/fix-15839.en.md 為線索完整還原Protobuf Schema 使用map_, _映射字段時(shí)編碼失敗這一問題的現(xiàn)象、根因與修復(fù)后的正確用法并結(jié)合源碼與測(cè)試用例emqx_schema_registry_serde_SUITE說明底層實(shí)現(xiàn)原理幫助讀者在規(guī)則引擎中正確使用帶 map 字段的 Protobuf 編解碼。問題現(xiàn)象帶 map 字段的 Protobuf Schema 編碼報(bào)錯(cuò)在 EMQX 規(guī)則引擎中schema_encode函數(shù)按注冊(cè)的 Schema 將傳入的 JSON 結(jié)構(gòu)編碼為二進(jìn)制消息。若 Protobuf Schema 中定義了map_, _字段編碼會(huì)失敗。復(fù)現(xiàn)所用 Schemaproto3 語法syntax proto3; message test { mapstring, string args 1; }規(guī)則 SQL 中使用該 Schema此處 Schema 注冊(cè)名為xxx消息類型為testSELECT schema_encode(xxx, json_decode(payload), test) as protobuf_test FROM t/#對(duì)如下 JSON 載荷進(jìn)行編碼{ args: { env: stag } }會(huì)拋出類似如下的錯(cuò)誤2025-06-17T06:59:22.72578500:00 [warning] tag: RULE_SQL_EXEC, clientid: c_emqx, msg: SELECT_clause_exception, reason: {error,{gpb_type_error,{bad_unicode_string,[{value,env},{path,test.args.key}]}},[{$schema_parser_xxx,mk_type_error,3,[{file,$schema_parser_xxx.erl},{line,437}]},{$schema_parser_xxx,-v_mapstring,string/3-lc$^0/1-0-,3,[{file,$schema_parser_xxx.erl},{line,429}]},{$schema_parser_xxx,v_mapstring,string,3,[{file,$schema_parser_xxx.erl},{line,429}]},{$schema_parser_xxx,v_msg_test,3,[{file,$schema_parser_xxx.erl},{line,404}]},{$schema_parser_xxx,encode_msg,3,[{file,$schema_parser_xxx.erl},{line,73}]},{emqx_schema_registry_serde,with_serde,2,[{file,emqx_schema_registry_serde.erl},{line,212}]}...注意該 issue 對(duì)應(yīng)倉庫中的修復(fù)記錄為 changes/ee/fix-15839.en.md修復(fù)后同一用法可正常編碼具體驗(yàn)證方式見下文測(cè)試用例驗(yàn)證一節(jié)。錯(cuò)誤日志剖析gpb_type_error與bad_unicode_string逐段解讀上述錯(cuò)誤堆棧可以定位到失敗點(diǎn)根因元組{gpb_type_error, {bad_unicode_string, [{value, env}, {path, test.args.key}]}}gpb 在編碼test.args這個(gè) map 時(shí)把 key值為env按 unicode 字符串校驗(yàn)該校驗(yàn)失敗。堆棧中的$schema_parser_xxx是 Schema Registry 為注冊(cè)的 Protobuf Schema 動(dòng)態(tài)生成的編解碼模塊見下文源碼原理v_mapstring,string、v_msg_test、encode_msg分別是 map 字段校驗(yàn)、消息校驗(yàn)和消息編碼函數(shù)。emqx_schema_registry_serde:with_serde/2是規(guī)則 SQL 函數(shù)進(jìn)入編解碼的統(tǒng)一入口對(duì)應(yīng)源碼 apps/emqx_schema_registry/src/emqx_schema_registry_serde.erl。從根因看失敗本質(zhì)是Protobuf 的 map 字段在 gpb 代碼生成時(shí)map 的 key 被生成為 unicode 字符串校驗(yàn)邏輯而 EMQX 規(guī)則引擎?zhèn)魅氲?key 是二進(jìn)制binary數(shù)據(jù)二者類型不匹配于是校驗(yàn)函數(shù)mk_type_error拋出bad_unicode_string。根因與修復(fù)方向maps_key_type編譯選項(xiàng)修復(fù)的關(guān)鍵在于控制 gpb 生成代碼時(shí) map key 的數(shù)據(jù)類型。在 Schema Registry 生成 Protobuf 編解碼模塊時(shí)統(tǒng)一傳入一組編譯選項(xiàng)見 apps/emqx_schema_registry/src/emqx_schema_registry_serde.erl 中的base_protobuf_opts/0base_protobuf_opts() - [ binary, strings_as_binaries, descriptor, {maps, true}, {maps_key_type, binary}, {maps_oneof, flat}, {verify, always}, {maps_unset_optional, omitted} ].其中與本問題直接相關(guān)的兩個(gè)選項(xiàng){maps, true}啟用 gpb 對(duì)map_, _字段的原生支持將 map 編譯為 Erlang map 結(jié)構(gòu){maps_key_type, binary}指定 map 的 key 統(tǒng)一采用binary類型而不是默認(rèn)的可能按 unicode charlist/字符串處理的key 類型。源碼中 protobuf_cache_key/2 的注釋也特別說明Need to take options into account, as changing them might, for example, change the type of map keys between versions編譯選項(xiàng)需要計(jì)入緩存指紋因?yàn)檫x項(xiàng)變化可能改變 map key 的類型這印證了maps_key_type是決定 map key 表示方式的關(guān)鍵配置。當(dāng)傳入的 map key 為二進(jìn)制、而生成的代碼按 unicode 字符串charlist校驗(yàn)時(shí)就會(huì)復(fù)現(xiàn) issue 中的bad_unicode_string錯(cuò)誤修復(fù)后的行為是讓 map key 按 binary 處理與規(guī)則引擎中json_decode(payload)產(chǎn)出的 binary key 保持一致。修復(fù)后的正確用法可運(yùn)行示例修復(fù)后仍使用原來的 Schema、SQL 與載荷即可正常編碼通過 Dashboard 或 HTTP API 在 Schema Registry 注冊(cè)上述test消息類型選擇 Protobuf源碼即為前文的 proto3 定義在規(guī)則引擎中創(chuàng)建規(guī)則SQL 保持SELECT schema_encode(xxx, json_decode(payload), test) as protobuf_test FROM t/#向主題t/#發(fā)布 JSON 載荷{args: {env: stag}}規(guī)則動(dòng)作即可獲得protobuf_test字段其值為按test消息編碼后的二進(jìn)制數(shù)據(jù)args中的鍵值對(duì)env stag被正確寫入 map 字段。編碼后的二進(jìn)制可直接經(jīng) MQTT 發(fā)布給對(duì)端或作為其他橋接動(dòng)作的載荷。測(cè)試用例驗(yàn)證map 類型與 oneof 的往返編解碼倉庫中的單元測(cè)試直接覆蓋了本修復(fù)場(chǎng)景見 apps/emqx_schema_registry/test/emqx_schema_registry_serde_SUITE.erl 的t_protobuf_map_types/1t_protobuf_map_types(_Config) - Source iolist_to_binary([ [ message test {, mapstring, string args 1;, }, message union {, oneof u {, int32 a 1;, string b 2;, }, } ] ]), Params #{type protobuf, source Source}, SerdeName maps, ok emqx_schema_registry:add_schema(SerdeName, Params), ExtraArgs0 [test], Original0 #{args #{hello world}}, assert_roundtrip(SerdeName, Original0, ExtraArgs0, ExtraArgs0), ...該用例注冊(cè)一個(gè)包含mapstring, string args字段的 Schema并以#{args #{hello world}}binary key/binary value做 encode→decode 往返斷言同時(shí)覆蓋oneof字段的往返編解碼。運(yùn)行該套件emqx_schema_registry_serde_SUITE即可回歸驗(yàn)證 map 字段修復(fù)。同套件中的t_protobuf_invalid_schema/1emqx_schema_registry_serde_SUITE.erl還驗(yàn)證了非法 Protobuf 源碼會(huì)以{error, {post_config_update, _, {invalid_protobuf_schema, _}}}的形式被拒注冊(cè)階段的源碼校驗(yàn)由 emqx_schema_registry.erl 與配置模塊 emqx_schema_registry_config.erl 協(xié)作完成。源碼原理Protobuf 編解碼模塊的生成與緩存理解該修復(fù)還需了解 EMQX Schema Registry 對(duì) Protobuf 的處理機(jī)制均在 apps/emqx_schema_registry/src/emqx_schema_registry_serde.erl規(guī)則 SQL 函數(shù)rsf_schema_encode/1L127-L134與rsf_schema_decode/1分別對(duì)應(yīng) SQL 中的schema_encode、schema_decode編碼結(jié)果統(tǒng)一iolist_to_binary/1轉(zhuǎn)為二進(jìn)制避免下游動(dòng)作誤按 JSON 列表處理。動(dòng)態(tài)代碼生成make_protobuf_serde_mod/2L489-L507調(diào)用 gpb 的gpb_compile:string/3將用戶提交的 Protobuf 源碼編譯成名為$schema_parser_SchemaName的模塊見protobuf_serde_mod_name/1再通過code:load_binary/3裝載錯(cuò)誤日志堆棧中的$schema_parser_xxx.erl即由此而來。編譯選項(xiàng)base_protobuf_opts/0L629-L639統(tǒng)一約束生成的代碼風(fēng)格map 相關(guān)行為由{maps, true}與{maps_key_type, binary}決定。編譯緩存為避免多節(jié)點(diǎn)重復(fù)編譯lazy_generate_protobuf_code/3L546-L557在 mria 事務(wù)內(nèi)加鎖執(zhí)行編譯結(jié)果按{SchemaName, OTP版本, MD5指紋}緩存于 mnesia 表?PROTOBUF_CACHE_TAB指紋protobuf_cache_key/2包含編譯選項(xiàng)哈希與全部源碼內(nèi)容因此修改源碼或升級(jí) OTP 都會(huì)觸發(fā)重新編譯。銷毀清理刪除 Schema 時(shí)通過destroy_protobuf_code/1L713-L722卸載模塊并刪除緩存條目測(cè)試用例t_destroy_protobuf/1與t_update_protobuf_cache/1L251-L307分別驗(yàn)證了緩存命中/失效與銷毀行為。在規(guī)則引擎中使用 Protobuf 編解碼的建議結(jié)合本次修復(fù)實(shí)踐中請(qǐng)注意以下幾點(diǎn)map 字段的鍵值類型規(guī)則 SQL 中json_decode(payload)產(chǎn)出的 JSON 對(duì)象鍵為二進(jìn)制字符串與maps_key_type binary的生成代碼對(duì)齊應(yīng)避免在載荷中使用非字符串類型的 map key如數(shù)字 key否則 gpb 仍可能報(bào)類型錯(cuò)誤。區(qū)分 encode 與 decodeschema_encode(SchemaName, Term, MessageType)輸入 JSON 結(jié)構(gòu)、輸出二進(jìn)制schema_decode(SchemaName, Binary, MessageType)反之。若把已解碼的 map 再傳給schema_decode會(huì)觸發(fā) eval_decode/2 中的顯式schema_decode_error提示Attempted to schema decode an already decoded message。Schema 變更需重新注冊(cè)修改 Protobuf 源碼后緩存指紋源碼 MD5變化會(huì)觸發(fā)重新編譯無需重啟節(jié)點(diǎn)但應(yīng)通過 Schema Registry 的更新接口重新提交避免舊模塊殘留相關(guān)緩存清理邏輯見t_update_protobuf_cache用例。驗(yàn)證手段可參考 emqx_schema_registry_serde_SUITE.erl 中的assert_roundtrip/3模式先 encode 再 decode 對(duì)比原始結(jié)構(gòu)快速確認(rèn) Schema 定義是否符合預(yù)期。小結(jié)本文從變更記錄 changes/ee/fix-15839.en.md 出發(fā)完整還原了Protobuf Schema 的map_, _字段編碼失敗問題其根因是 gpb 生成代碼時(shí) map key 的類型處理與規(guī)則引擎?zhèn)魅氲亩M(jìn)制 key 不一致導(dǎo)致gpb_type_error: bad_unicode_string修復(fù)通過編譯選項(xiàng){maps_key_type, binary}統(tǒng)一 map key 為二進(jìn)制類型。該修復(fù)已有回歸測(cè)試覆蓋emqx_schema_registry_serde_SUITE.erl相關(guān)實(shí)現(xiàn)集中在 emqx_schema_registry_serde.erl。在規(guī)則引擎中編寫含 map 字段的 Protobuf Schema 時(shí)保持 JSON 鍵為字符串并遵循 encode/decode 的輸入輸出約定即可穩(wěn)定完成編解碼。贊分享后端物聯(lián)網(wǎng)消息隊(duì)列通信【免費(fèi)下載鏈接】emqxThe most scalable and reliable MQTT broker for AI, IoT, IIoT and connected vehicles項(xiàng)目地址https://gitcode.com/gh_mirrors/em/emqx點(diǎn)擊查看免費(fèi)下載相關(guān)推薦EMQX Schema Registry 實(shí)戰(zhàn)指南在規(guī)則引擎中統(tǒng)一管理 Avro / Protobuf / JSON Schema 編解碼EMQX Schema Registry 實(shí)戰(zhàn)指南在規(guī)則引擎中統(tǒng)一管理 Avro / Protobuf / JSON Schema 編解碼 Schema Re后端物聯(lián)網(wǎng)消息隊(duì)列通信Buzz 如何識(shí)別轉(zhuǎn)錄中的說話人并修改說話人標(biāo)簽Buzz 如何識(shí)別轉(zhuǎn)錄中的說話人并修改說話人標(biāo)簽 Buzz 可以對(duì)已生成轉(zhuǎn)錄的音頻或視頻文件做說話人識(shí)別把每位說話人的句子標(biāo)上標(biāo)簽允許你把自動(dòng)生成的標(biāo)簽后端物聯(lián)網(wǎng)消息隊(duì)列通信EMQX 規(guī)則引擎租戶命名空間下的全局規(guī)則匹配修復(fù)limit_selects_in_namespace 機(jī)制與源碼剖析EMQX 規(guī)則引擎租戶命名空間下的全局規(guī)則匹配修復(fù) limit_selects_in_namespace 機(jī)制與源碼剖析 本篇文章圍繞 EMQX 變更記錄 f后端物聯(lián)網(wǎng)消息隊(duì)列通信創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考