首页 > 编程知识 正文

transaction注解原理,动态网站的主要技术

时间:2023-05-06 10:52:01 阅读:20412 作者:3248

《ASM和Presto动态代码生成概述》

EmbedVersionEmbedversion类中的embedversion方法用于将SQL查询发送到Presto,并由任务执行程序启动任务运行器执行任务。 嵌入式版本方法实际上是初始化Runnable实例。 例如,启动任务运行器的代码片段可能如下所示:

executor.execute (embed version.embed version ) newtaskrunner (); 其中,TaskRunner实现了Runnable接口。 嵌入式版本的嵌入式版本方法实现如下:

publicrunnableembedversion (runnable runnable ) ) require nonnull (runnable,' runnable is null ' ); try{return(runnable ) runnableconstructor.invoke ) runnable; }可移植可移植(catch )可移植(throwifunchecked )可移植; thrownewruntimeexception (可持续); }其中,runnableConstructor是一个使用ASM生成代码的类,实现如下:

//在此,类名约为presto _ null _ _ test version _ _ _ 2021 10 11 _ 105831 _ 1,//其父类为Object,运行nable接口为实classdefinitionclassdefinition=new class definition (a (公共,FINAL ), makeclassname ) base class name (server config ) )定义了名为//runnable的局部变量,类型为runnablefielddefinitionfield=class definition runnnable )即parameter parameter=arg (' runnable ',type ) runnable.class ); //定义了此类的构造函数。 参数为runnable,参数类型为runnablemethoddefinitionconstructor=class definition.declare constructor (a ) public ), 在Parara )//构造方法中,实际上可以将参数runnable的值代入局部变量runnableconstructor.getBody ().comment ) ' super(runnable ) ' . append(constructor.getthis ().invoke constructor (object.class ).append(constructor.getthis ) ).append ) 实际上,运行Runnable接口中的run方法methoddefinition run=class definition.declare method (a (公共)、' run '、type ) vype ) ).append(run.getthis ().getfield ) ) field ).invokeinterface ) runnable.class,' run ',void.class ).ret ) //定义此类并将其加载到ClassLoader中,然后单击Class? extendsrunnablegeneratedclass=define class (class definition,Runnable.class,ImmutableMap.of ),getClass ).getClass this.runnable constructor=constructormethodhandle (generated class,Runnable.class ); 上面是Presto通过使用Java字节码动态生成的类,生成的类如下所示

package com.facebook.presto.$gen;public final class Presto_null__testversion____20211011_105831_1 implements Runnable { private Runnable runnable; public Presto_null__testversion____20211011_105831_1(Runnable runnable) { this.runnable = runnable; } public void run() { this.runnable.run(); }}

看起来内容其实很简单。EmbedVersion 类算是 Presto 里面动态代码生成最简单的例子了。

concat 函数实现

下面我们来看下稍微复杂的,也就是 Presto 里面内置函数的实现。Presto 的内置函数的实现很多也是用到代码生成技术,比如 map_filter、transform_keys 以及 transform_values 等。我们这里也举一个比较简单的例子,也就是 concat 函数的实现。比如下面的 SQL 查询:

select concat(o_orderstatus, o_orderpriority) from orders limit 10;

在 Presto 里面,concat 函数的实现就是通过代码生成进行的,其实现代码可以参见 com.facebook.presto.operator.scalar.ConcatFunction。Presto 接收到上面的 SQL 查询后,会在 Coordinator 端进行解析,并生成相应的 Tasks,提交给 Worker 执行。在 Worker 端,执行 Task 的时候,会调用 LocalExecutionPlanner 的 plan 方法生成 LocalExecutionPlan 其实就是本地可执行的计划,在 plan 方法里面会调用 com.facebook.presto.sql.planner.LocalExecutionPlanner.Visitor 对 Coordinator 传过来的 PlanNode 进行变量生成 PhysicalOperation。在我们的例子中,会在 com.facebook.presto.sql.planner.LocalExecutionPlanner.Visitor#visitScanFilterAndProject 里面对 concat(o_orderstatus, o_orderpriority) 进行代码生成,最终调用到 com.facebook.presto.operator.scalar.ConcatFunction 的 generateConcat 方法,其就是 Presto 的 concat 函数实现逻辑,如下:

// arity 代表 Concat 函数输入参数的个数private static Class<?> generateConcat(TypeSignature type, int arity){ checkCondition(arity <= 254, NOT_SUPPORTED, "Too many arguments for string concatenation"); // 定义动态代码生成的类名,生成的类名大概是 varchar_concat2ScalarFunction_20211011_062900_3 样子的 ClassDefinition definition = new ClassDefinition( a(PUBLIC, FINAL), makeClassName(type.getBase() + "_concat" + arity + "ScalarFunction"), type(Object.class)); // 生成类的构造函数,这里是使用 private 修饰的 // Generate constructor definition.declareDefaultConstructor(a(PRIVATE)); // Generate concat() // 定义 concat 函数的参数,比如 arg0、arg1;类型是 Slice List<Parameter> parameters = IntStream.range(0, arity) .mapToObj(i -> arg("arg" + i, Slice.class)) .collect(toImmutableList()); // 定义一个名为 concat 的函数,它的修饰符是 public static, // 返回类型是 Slice,输入参数是上面定义的 arg0、arg1 等。 MethodDefinition method = definition.declareMethod(a(PUBLIC, STATIC), "concat", type(Slice.class), parameters); Scope scope = method.getScope(); BytecodeBlock body = method.getBody(); // 定义一个名为 length 的局部变量,类型为 int Variable length = scope.declareVariable(int.class, "length"); // length 变量初始化为0 body.append(length.set(constantInt(0))); // 下面是计算 concat 函数每个参数的长度(其实就是调用 string 的 length 方法) // 然后再把得到的字符串长度加到 length 里面,并赋值给 length for (int i = 0; i < arity; ++i) { body.append(length.set(generateCheckedAdd(length, parameters.get(i).invoke("length", int.class)))); } // 定义一个名为 result 的局部变量,类型为 Slice Variable result = scope.declareVariable(Slice.class, "result"); // 调用 Slices 的 allocate 方法分配出长度为 length 空间的 Slice 对象,并赋值给 result body.append(result.set(invokeStatic(Slices.class, "allocate", Slice.class, length))); // 定义一个名为 position 的局部变量,类型为 int,赋值为 0 Variable position = scope.declareVariable(int.class, "position"); body.append(position.set(constantInt(0))); // 下面是循环调用 result 的 setBytes 方法,并分别把 arg0、arg1 里面的内容放到 result 里面去 // 最后计算 arg0 或 arg1 字符串长度再加上 position 的值,结果再赋值给 position for (int i = 0; i < arity; ++i) { body.append(result.invoke("setBytes", void.class, position, parameters.get(i))); body.append(position.set(add(position, parameters.get(i).invoke("length", int.class)))); } // 返回 result body.getVariable(result) .retObject(); // 定义生成的类,并把它加载打破 DynamicClassLoader 里面去 return defineClass(definition, Object.class, ImmutableMap.of(), new DynamicClassLoader(ConcatFunction.class.getClassLoader()));}private static BytecodeExpression generateCheckedAdd(BytecodeExpression x, BytecodeExpression y){ // 调用 ConcatFunction 类里面的 checkedAdd 静态方法 return invokeStatic(ConcatFunction.class, "checkedAdd", int.class, x, y);}@UsedByGeneratedCodepublic static int checkedAdd(int x, int y){ try { return addExact(x, y); } catch (ArithmeticException e) { throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Concatenated string is too large"); }}

为了方便理解,我对 generateConcat 方法的实现进行了注释,应该很好理解。为了性能问题,最终生成的函数会进行缓存,下一次再调用 concat 函数,只要函数签名一样,就不用再一次进行 concat 代码的生成。比如我们前面的例子是对两个字符串进行合并(函数签名为 presto.default.concat(varchar,varchar):varchar ),如果下一次还是调用这个函数就不用再进行代码生成了。但是如果下一次是对三个字符串进行合并,还是要进行一次代码生成的。

到这里,大家可能还是不太明白 Presto 代码生成到底生成了什么东西。这里我就进一步介绍一下。如果运行我们上面的 SQL 查询,Presto 生成的 concat 实现大概如下面所示:

package com.facebook.presto.$gen;import com.facebook.presto.operator.scalar.ConcatFunction;import io.airlift.slice.Slice;import io.airlift.slice.Slices;public final class varchar_concat2ScalarFunction_20211011_062900_3 { private varchar_concat2ScalarFunction_20211011_062900_3() { } public static Slice concat(Slice arg0, Slice arg1) { int length = 0; int length = ConcatFunction.checkedAdd(length, arg0.length()); length = ConcatFunction.checkedAdd(length, arg1.length()); Slice result = Slices.allocate(length); int position = 0; result.setBytes(position, arg0); int position = position + arg0.length(); result.setBytes(position, arg1); int var10000 = position + arg1.length(); return result; }}

注意,Presto 里面生成的是 Java 字节码,这里只是为了说明的方便,给出了 Java 源代码。可以看到,最终生成的代码其实很好理解。Presto 里面对两个字符串进行 concat 其实就是执行上面的代码片段。

总结

本文通过两个例子简单的介绍了 Presto 里面动态代码生成的用法,通过上面两个例子,相信大家应该能够大概了解 Presto 里面的动态代码生成的技术和用法。后面有空我会介绍一下表达式这块代码生成和整个 page 处理逻辑是怎么联系到一起的,敬请关注。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。