-
Notifications
You must be signed in to change notification settings - Fork 7.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: 表达式体验优化与引擎更换 #2849
Comments
多步表达式的方式也是我一直赞成的方式,现有热门的脚本语言都有REPL,使用方式简单而且深入人心。
|
Arthas和QLExpress的用户,看到了内部技术产品联合很兴奋!可以支持需求的快速迭代,这个效率是用外部工具比不了的。 |
idea的插件,生成的表达式 后面也会改成QLExpress 吗 |
Arthas 较复杂的表达式体验确实有待优化, 引入脚本语言确实是一个好的idea,能解决一些痛点问题 |
这个要看一下后续如何演进 ,ognl 和 QLExpress 应该都会支持,可能独立一个插件出来。 |
我在正文中用的方式是类似 shell 的赋值语句: n=${原生Arthas命令} 如果是类似 PIPE 的方式, 在命令行上要如何表达呢? 我理解类似管道命令? 但是在表达式场景下也需要绑定某个变量, 才能拿这个变量使用 |
这个想法很好啊, 听起来是一个更加上层的诊断平台了 |
感觉可以分开为两个事情来讨论:
QLExpress 支持
表达式执行之后,把结果保存到某些
|
@hengyunabc 多步演进的话,实现层面没啥问题。推广层面存在两个重大问题:
多步演进也得考虑这两个推广问题,不然很容易流产。至少要有足够的运营素材,方便推广。这也是我建议把两件事一起做的原因。 |
|
ognl 和 arthas 本身都是增量市场,不需要考虑推广问题。ognl 是 arthas 第一次集成表达式,属于新增功能,肯定会有用户尝试。QLE 做存量功能替换的话,用户动力没这么大。不会有用户为了新引擎,看文档,知道并且加这个选项的,第 1 步最大的可能没用户知道选项,然后没用户用,这是最大的可能。 大佬肯定也不希望这变成一个烂尾工程,如果要多步演进的话,避免烂尾,我觉得要考虑得更周到点:
|
背景
@hengyunabc 在去年发起过一个关于考虑不同的表达式引擎 #2747
我们在阿里内部进行了一些简单的交流, 希望能够借着 Arthas 4 的机会, 进行一次表达式体验的优化, 让用户能够更加方便地在黑屏情况下操作 Arthas 表达式。本次优化的主要目标有三个:
本文希望就这三个问题表达想法, 并与社区的朋友们一起讨论。
提议一: 更符合 Java 程序员的习惯
ognl 语法简洁, 但终究和 Java 的语法相差甚远。虽说现在有工具和 AI 的加持的, 但在需要黑屏操作, 或者微调的时候, 还是相当麻烦。
我是 QLExpress 的开源支持者之一, 并且也计划在今年推出 4.0 版本。我提议将 Arthas 的表达式更换为 QLExpress, 考虑如下:
在 Arthas的一些特殊用法文档说明 这个 issue 中, 大家讨论了 ognl 在各种特殊场景下的写法, 借助 issue 中的一些例子, 展示在 QLExpress 中的写法:
l.{ #this.name }
l*.name
l.{? #this.name == null }
l.filter(i -> i.name == null)
l.{? #this.age > 10 }.size()
l.filter(i -> i.name == null).size()
l.{? #this.age > 10 }.size().(#this > 20 ? #this - 10 : #this + 10)
l.filter(i -> i.age > 10).size().let(i -> i > 20? i - 10: i + 10)
l.{^ #this.name != null}
l.findFirst(i -> i.name != null)
l.{$ #this.name != null}
l.findLast(i -> i.name != null)
@com.taobao.container.Test@m
com.taobao.container.Test.m
@java.lang.Thread@currentThread()
java.lang.Thread.currentThread()
@com.taobao.container.Test$InnerClassName@m
com.taobao.container.Test.InnerClassName.m
@com.alibaba.fastjson.JSON@parseObject("{\"name\":\"aaa\"}", xxxx.class)
service.method({'a':1,'b':2, 'class':a.b.c}, {'a':1,'b':2, 'class':a.b.c.d})
提议二: 多步表达式
即使使用了 QLExpress, 以上的某些写法也有些太复杂。我们在咨询了一些用户后发现, 表达式难以编写的原因, 只有部分是因为对 ognl 语法不了解, 更深层次的原因还是在于: 让开发者一次性写对复杂的逻辑。
因此我们提议在 Arthas 中支持多步表达式: 即支持将某个命令的结果保存下来, 然后用类似 REPL(Read–eval–print loop) 的表达式交互界面逐步完成复杂的数据操作。
这里给两个例子说明操作过程。
getStatic 获取静态变量并处理
假设在
com.alibaba.arthas.Test
类中有一个静态变量 n, 它是一个 Person 列表, 我从中筛选出姓名为li
的人的年龄。原本 OGNL 表达式的写法如下:
$ getstatic com.alibaba.arthas.Test n 'iterator.{? #this.name=="li"}.age'
如果使用 QLExpress 一次性完成, 则写法如下:
$ getstatic com.alibaba.arthas.Test n 'this.filter(i -> i.name=="li").age'
整个处理过程, 其实分为筛选元素, 和取 age 属性两步完成。下面演示两种多步表达式用法:
多步表达式用法一:
多步表达式用法二:
watch 获得方法参数/返回值并且处理
实践中, 有时候会碰到, 业务列表/对象实在太大了, 连一屏幕都显示不下, 此时去抠关键信息就如同大海捞针。有了多步表达式, 就可以一点点去筛选关键信息的位置。
watch 也比较特殊, 因为它执行后会等待在那里, 直到用户按下 Ctrl + C 或者超过最大数目限制。因为我们将 watch 命令的返回值定义为最近 5 个 expression 参数的返回值(小于 5 个则保存全部请求)。
举个例子, 对于
$ n=${watch demo.MathGame primeFactors "{params,returnObj}" -x 2}
假设 watch 到 10 个请求后, 用户按 Ctrl + C 停止, 则 n 中保存的值为:
假设
com.taobao.container.Test
类的test
方法的第一个参数为 Person 类型的列表。 我们想从最近一次调用该方法的参数中, 筛选出姓名为li
的人的年龄, 则多步式写法如下:提议三: 疑难问题解决
和 arthas-idea-plugin 的作者 @WangJi92, 以及其他用户的交流中, 我们找到下面几个 arthas 表达式使用起来复杂, 甚至无法解决的问题。
调用含有复杂对象的方法
假设有一个
a.b.c.MyService
中有一个方法为myMethod
, 它接受单个类型为 Person 的参数, Person 中只有一个属性为 name。则调用该方法在目前 Arthas 中只有两个方式:vmtool -x 3 --action getInstances --className a.b.c.MyService --express '#p=new a.b.c.Person(),#p.setName("li"),instances[0].myMethod(#p)'
这个方法仅仅是可行, 实际用起来体验很差。特别是对象嵌套层次复杂时, 因为没法格式化, 让广大程序员的近视度数又增加了。
vmtool -x 3 --action getInstances --className a.b.c.MyService --express 'instances[0].myMethod(@com.alibaba.fastjson.JSON@parseObject("{\"name\":\" li\"}",@a.b.c.Person@class))'
这里的表达式也很复杂, 需要转义 Json, 并且依赖应用中包含的 Json 序列化库。但是比较模板化, 只需要在别的编辑工具中将 Json 编辑好, 转义后复制到模板中即可。
arthas-idea-plugin 就利用类似的表达式模板, 自动生成包含复杂对象的调用, 如下图:
在表达式替换为 QLExpress4 后, 我们计划的写法如下:
vmtool -x 3 --action getInstances --className a.b.c.MyService --express 'instances[0].myMethod({"name":"li", "class": "a.b.c.Person"}))'
模板化的表达式更少了, 不再需要烦人的转义, 也不依赖应用中的 Json 序列化框架。书写嵌套对象也更加 easy, 一个嵌套对象的案例如下:
方法参数为泛型类
OGNL 不支持泛型, 如果参数类中含有泛型, 甚至连序列化也帮不了你。
因为泛型的序列化需要借助带泛型的
TypeReference
, 而 OGNL 中无法书写泛型。相关的表达式生成工具只能绕道, 先将属性序列化出来后, 再调用 set 方法设置进对象中。假设方法签名是这样的:
Pair 类的定义如下:
想用 vmtool 调用它, 采用 OGNL + 序列化的写法就特别麻烦。必须要先序列化 left 属性, 再序列化 right 属性, 最后再设置进 Pair 对象中:
vmtool -x 3 --action getInstances --className a.b.c.MyService --express 'instances[0].myMethodGeneric((#p=@com.alibaba.fastjson.JSON@parseObject("{\"num\":2}",@a.b.c.Pair@class),(#p.setLeft(@com.alibaba.fastjson.JSON@parseObject("{\"name\":\" li\"}",@a.b.c.Person@class))),(#p.setRight(@com.alibaba.fastjson.JSON@parseObject("{\"address\":\"street 404\"}",@a.b.c.Home@class))),#p))'
这还只是在嵌套了一层的情况下, 如果对象层次更多, 即使工具能够生成, 也复杂得根本看不明白, 修改不了, 也没有任何工具可以将其结构化。
如果使用 QLExpress4, 则可以将其写得非常清晰:
vmtool -x 3 --action getInstances --className a.b.c.MyService --express 'instances[0].myMethodGeneric({"name":"li","age":{"year":31,"mouth":5,"class":"a.b.c.Age"},"class":"a.b.c.Person"})'
调用泛型方法
上一种情况虽然复杂, 至少还能借助工具生成。
和相关工具作者交流后, 他们表示, 如果调用的方法本身带有泛型方法的话, 则工具无法生成, 只能让开发者手工做微调, 比如下面的泛型方法:
因为不知道开发者想要赋予泛型的类型是什么, 工具无法生成准确的表达式。
如果让开发者手工微调的话, 因为表达式复杂度很高, 也几乎不可能, 于是陷入了两难, 几乎是不可用的状态。
当然, 这个问题如果工具作者想解的话, 也不是不能解, 比如加一个交互步骤, 让用户先手动选择想要的泛型类, 之后再执行生成。
不过直接采用上面 QLExpress4 的写法更简洁。
常见 Q & A
问: Arthas 经常会在线上使用, 较复杂的表达式体验能够防止线上的误操作, 或者某些恶意操作。多步式表达式会不会导致这些行为过于容易呢?
答: Arthas 能够做任何想得到的 Java 调用, 不是不能做, 只是做起来比较复杂。那自然会有人开发脚本生成工具来简化操作, 比如 @WangJi92 的 arthas-idea-plugin, 就能够自动生成任意复杂的 Java 调用脚本。与其让别人来简化, 不如 Arthas 自己来简化, 还更可控一些, 未来用更有效的机制保证安全。
问: 多步式体验优化和 QLExpress 的关系是什么?
答: 没有技术上的关系, OGNL 也可以开发多步式的交互。但是既然已经用了 QLExpress4 做表达式体验优化, 就希望一步到位, 将体验优化到极致。提供一个新的功能特性, 也能够吸引用户升级, 并且尝试了解新的表达式引擎。
问: 复杂表达式编写是个低频操作, 有必要优化低频操作的体验吗?
答: 高频的简单表达式写法我们维持不变, 比如最常用的 watch 表达式
{params,returnObj,throwExp}
, 或者简单的对象取属性, 写法和原来保持一致。仅仅在需要更加复杂的处理时, 才需要了解 QLExpress4 的写法, 整体上是一个帕累托改进, 即在没有损失高频操作体验的情况下, 提升低频操作体验。而且之前的 "低频" 也不一定是真的 "低频", 可能是因为太麻烦, 开发者懒得这么用, 现在我们将它变简单了, 用户说不定能想出更多玩法呢。如果有更多问题, 也欢迎大家在评论区讨论。
The text was updated successfully, but these errors were encountered: