NAT网络地址转换实战:从PAT配置到会话表排查的运维指南
2026/10/23 12:12:08
Procedure 可以理解为:SQL 世界里的“存储过程”,但执行体可以启动 Flink 作业。
典型用途
CALL调用Procedure接口 + 定义call(...)org.apache.flink.table.procedures.Procedurepublic、非抽象、全局可访问call(...)方法要求(最关键)接口本身不定义方法,你需要自己定义名为call的方法:
硬性规则
call必须public
第一个参数必须是ProcedureContext
context.getExecutionEnvironment()拿到StreamExecutionEnvironment返回类型必须是数组:int[]、String[]、Row[]、Long[]等
而且 JVM 普通重载规则都适用
call(ctx, int)/call(ctx, String)call(ctx, Integer...)call(ctx, Object)等如果你用 Scala
scala.annotation.varargsjava.lang.Integer而不是Int)以支持 NULL下面这个示例展示了:Procedure 拿到StreamExecutionEnvironment,用fromSequence跑一个小 Job,然后把结果收集为数组返回。
importorg.apache.flink.streaming.api.environment.StreamExecutionEnvironment;importorg.apache.flink.table.procedure.ProcedureContext;importorg.apache.flink.table.procedures.Procedure;importorg.apache.flink.util.CloseableIterator;publicclassGenerateSequenceProcedureimplementsProcedure{publiclong[]call(ProcedureContextcontext,intn)throwsException{returngenerate(context.getExecutionEnvironment(),n);}publiclong[]call(ProcedureContextcontext,Stringn)throwsException{returngenerate(context.getExecutionEnvironment(),Integer.parseInt(n));}privatelong[]generate(StreamExecutionEnvironmentenv,intn)throwsException{long[]sequenceN=newlong[n];inti=0;try(CloseableIterator<Long>it=env.fromSequence(0,n-1).executeAndCollect()){while(it.hasNext()){sequenceN[i++]=it.next();}}returnsequenceN;}}要点
call可以重载context.getExecutionEnvironment()获取执行环境Flink Table/SQL 是强类型生态,因此 Procedure 的参数/返回值需要映射成 Flink DataType。
Flink 会通过反射自动推断(Automatic Type Inference),但在以下情况经常需要你“补注解”
call想吃多种输入类型、但希望统一输出类型(用@ProcedureHint)@DataTypeHint:给参数或返回值补充类型信息注意:call的返回值必须是T[],如果你给返回值加@DataTypeHint,其实标注的是数组元素类型 T。
importorg.apache.flink.table.annotation.DataTypeHint;importorg.apache.flink.table.annotation.InputGroup;importorg.apache.flink.table.procedure.ProcedureContext;importorg.apache.flink.table.procedures.Procedure;importorg.apache.flink.types.Row;importjava.math.BigDecimal;importjava.nio.ByteBuffer;importjava.time.Instant;publicclassOverloadedProcedureimplementsProcedure{publicLong[]call(ProcedureContextcontext,longa,longb){returnnewLong[]{a+b};}public@DataTypeHint("DECIMAL(12, 3)")BigDecimal[]call(ProcedureContextcontext,doublea,doubleb){returnnewBigDecimal[]{BigDecimal.valueOf(a+b)};}@DataTypeHint("ROW<s STRING, t TIMESTAMP_LTZ(3)>")publicRow[]call(ProcedureContextcontext,inti){returnnewRow[]{Row.of(String.valueOf(i),Instant.ofEpochSecond(i))};}@DataTypeHint(value="RAW",bridgedTo=ByteBuffer.class)publicByteBuffer[]call(ProcedureContextcontext,@DataTypeHint(inputGroup=InputGroup.ANY)Objecto){returnnewByteBuffer[]{MyUtils.serializeToByteBuffer(o)};}}@ProcedureHint:把“输入类型 -> 输出类型”的映射说清楚适合场景
call想统一处理多输入类型(例如Object...)call的输出类型一致,想全局声明一次importorg.apache.flink.table.annotation.DataTypeHint;importorg.apache.flink.table.annotation.ProcedureHint;importorg.apache.flink.table.procedure.ProcedureContext;importorg.apache.flink.table.procedures.Procedure;importorg.apache.flink.types.Row;@ProcedureHint(output=@DataTypeHint("ROW<s STRING, i INT>"))publicclassSumProcedureimplementsProcedure{publicRow[]call(ProcedureContextcontext,inta,intb){returnnewRow[]{Row.of("Sum",a+b)};}publicRow[]call(ProcedureContextcontext){returnnewRow[]{Row.of("Empty args",-1)};}}更极端的用法:完全用 hint 决定类型,call只要 JVM 能调用即可。
调用 procedure 时可以用“命名参数”,好处
null)通过@ArgumentHint标注参数名、类型、是否可选。
参数级别标注示例
importorg.apache.flink.table.annotation.ArgumentHint;importorg.apache.flink.table.annotation.DataTypeHint;importorg.apache.flink.table.procedure.ProcedureContext;importorg.apache.flink.table.procedures.Procedure;publicclassNamedParameterProcedureimplementsProcedure{public@DataTypeHint("INT")Integer[]call(ProcedureContextcontext,@ArgumentHint(name="a",isOptional=true)Integera,@ArgumentHint(name="b")Integerb){returnnewInteger[]{a+(b==null?0:b)};}}重要限制
@ArgumentHint已包含@DataTypeHint,在某些组合场景下不能混用(按文档要求)getProcedure+listProceduresProcedure 必须存在于 Catalog 才能被CALL。
你需要在 Catalog 中实现:
Catalog.getProcedure(ObjectPath procedurePath):返回 procedure 实例Catalog.listProcedures(String dbName):列出该库下有哪些 procedure示例(内存 catalog 内置 procedure)
importorg.apache.flink.table.catalog.GenericInMemoryCatalog;importorg.apache.flink.table.catalog.ObjectPath;importorg.apache.flink.table.catalog.exceptions.CatalogException;importorg.apache.flink.table.catalog.exceptions.DatabaseNotExistException;importorg.apache.flink.table.catalog.exceptions.ProcedureNotExistException;importorg.apache.flink.table.procedures.Procedure;importjava.util.*;importjava.util.stream.Collectors;publicclassCatalogWithBuiltInProcedureextendsGenericInMemoryCatalog{privatestaticfinalMap<ObjectPath,Procedure>PROCEDURE_MAP=newHashMap<>();static{PROCEDURE_MAP.put(ObjectPath.fromString("system.generate_n"),newGenerateSequenceProcedure());}publicCatalogWithBuiltInProcedure(Stringname){super(name);}@OverridepublicList<String>listProcedures(StringdbName)throwsDatabaseNotExistException,CatalogException{if(!databaseExists(dbName)){thrownewDatabaseNotExistException(getName(),dbName);}returnPROCEDURE_MAP.keySet().stream().filter(p->p.getDatabaseName().equals(dbName)).map(ObjectPath::getObjectName).collect(Collectors.toList());}@OverridepublicProceduregetProcedure(ObjectPathprocedurePath)throwsProcedureNotExistException,CatalogException{Procedurep=PROCEDURE_MAP.get(procedurePath);if(p!=null){returnp;}thrownewProcedureNotExistException(getName(),procedurePath);}}CALL catalog.db.proc(args...)注册 Catalog 后就能调用:
TableEnvironmenttEnv=TableEnvironment.create(...);tEnv.registerCatalog("my_catalog",newCatalogWithBuiltInProcedure("my_catalog"));// 调用tEnv.executeSql("CALL my_catalog.`system`.generate_n(5)");SQL 侧一般就是
CALL my_catalog.\system.generate_n(5)推荐用 Procedure
CALL ...不太推荐(或要谨慎)