共计 13438 个字符,预计需要花费 34 分钟才能阅读完成。
最近有一件事件让我印象特地粗浅,作为引子和大家唠一唠:咱们在外部做一些极其的流量回归仿真试验时,在 TiKV(TiDB 的分布式存储组件)上观测到了异样的 CPU 使用率,然而从咱们的 Grafana Metrics、日志输入外面并没有看到异样,因而也一度困惑了好几天,最初靠一位老司机盲猜并联合 profiling 才找到真凶,真凶呈现在谁都没有想到的中央:Debug 用的日志模块(廓清一下:目前这个 Bug 曾经修复了,而且这个 Bug 的触发是在十分极其压力的场景下 + 日志级别全开才会呈现,请各位用户释怀)。
这篇文章并不是做 Bug 剖析,我感觉更重要的是,找问题过程中咱们应用的工具、老司机的思考过程。作为一个观察者,我看到年老的共事看着老司机熟练地操作 perf 和在各种各样工具和界面中切换那种企慕的眼神,我隐约感觉事件有点不对:这意味着这门手艺不能复制。
预先,我做了一些对于根底软件用户体验的调研,发现该畛域的实践和材料的确挺少(大多数是 ToC 产品的钻研,系统软件相干的大略只有 UNIX 哲学流派),而且不足系统化,依赖于作者集体「品尝」,然而软件体验的好和坏显然存在,例如一个有教训的工程师看到一个命令行工具,敲几下就晓得是否好用,是不是一个有「品尝」的工具。
很多时候「品尝」之所以被称为「品尝」,就是因为说不清道不明,这诚然是软件开发艺术性的一种体现,然而这也意味着它不可复制,不易被习得。我感觉这也不好,明天这篇以及可能接下来的几篇文章(尽管后几篇我还不晓得写啥,然而先立个 Flag)会试着总结一下好的根底软件体验到底从哪里来。
作为第一篇,本文将围绕可观测性和可交互性两个比拟重要的话题来谈。至于为什么把这两点放在一起聊,我先卖个关子,最初说。
可观测性
可观测性是什么?这可从我两年前发表的《我眼中的分布式系统可观测性》[1]一文中可见一斑,雷同的内容我在这里就不赘述。随着在 TiDB 中对可观测性实际的深刻,对这个话题有了更深的了解,为了更好的了解,咱们首先先明确一个问题:当咱们在聊可观测的时候,到底是谁在观测?
是谁在观测?
很多敌人可能会一愣,心想:这还用说,必定是人,总不能是机器。没错,确实是人在观测,但就是这么一个通俗的情理往往会被软件设计者疏忽,所以这两者的区别到底是什么?为什么强调人这个主体很重要?
要答复这个问题,须要分明一个事实:人的短期工作记忆是很无限的。大量的心理学钻研表明,人类工作记忆的容量大抵只有 4,即在短期同时关注 4 项信息 [2],再多的信息就要靠分模块的形式记忆,如咱们疾速记忆电话号码的形式,以 13800001111 为例,咱们通常不是一个个数字背,而是形如:138-0000-1111 进行分组。
在理解人的心智模型的一些根底假如和带宽后,我想很多系统软件开发者大略不再会夸耀:我的软件有 1000 多个监控项!这不仅不是坏事,反而让更多的信息毁坏了短期记忆的造成,引入了更多的乐音,让使用者在信息的陆地里花很多工夫找要害信息,以及不盲目的分类(我置信大脑的一个不盲目的后台任务就是对信息建索引和分类,留神这同样是耗费带宽的),所以第一个论断:软件应用一屏的界面外面最好只有 4 个要害信息。那么,接下来的一个问题是:哪些是要害信息?什么是乐音?
辨别要害信息和乐音
这个问题没有标准答案。对于系统软件来说,我的教训是:跟着要害资源走。软件其实很简略,实质就是对硬件资源的应用和调配,考究均衡的艺术。要害的硬件资源无非也就上面几个,对于上面每一个要害资源在某个采样时间段(单点没有太多意义),都能够通过一些简略的问题的询问,失去对系统运行状态的大抵图景:
- CPU:哪些线程在工作?这些线程都在干嘛?这些线程各自耗费了多少 CPU Time?
- 内存:以后内存中存储了哪些货色?这些货色的命中率状况?(通常咱们更关注业务缓存)?
- 网络 I/O:QPS/TPS 有异样吗?以后次要的网络 I/O 是由什么申请发动的?带宽还够吗?申请提早?长链接还是短链接(掂量 syscall 的开销)?
- 磁盘 I/O:磁盘在读写文件吗?读写哪些文件?大多数的读写是什么 Pattern?吞吐多大?一次 I/O 提早多大?
- 要害日志:不是所有日志都有用,只有蕴含特定关键字的日志,人们才会关怀。所以,有没有特定关键字的日志呈现?
通过以上规范问题的灵魂拷问,必然能够对系统运行状态有肯定的理解。
- 更进一步的要害是,这些零碎的指标肯定要和业务上下文分割在一起能力好用,举例说明,对于一个反对事务的数据库来说,假如咱们看到 CPU 线程和 call stack,发现大量的 CPU 工夫花在了 wait sleep idle 之类的事件上,同时也没有其余 I/O 资源瓶颈,此时,如果只看这些的数字可能会一脸懵,然而联合事务的抵触率来看可能柳岸花明,甚至能间接给出这些 lock 的等待时间都花在了哪些事务,甚至哪些行的抵触上,这对观测者是更有用的信息。
也并不是说其余的信息就没用,而是相当多的信息的价值是后验的,例如:绝大多数的 debug 日志,或者那些为了证实猜测的辅助信息,其实在解决未知问题时候简直没有帮忙,而且还须要观察者有大量的背景常识,这类信息最好的出现形式还是折叠起来,眼不见为净的好。
如果关上 TiDB 的外部 Grafana 就会看到大量这样的指标,如 stall-conditions-changed-of-each-cf(尽管我晓得这个指标的含意,然而我猜 TiDB 的用户里 99% 的人不晓得),而且从名字外面我看到了写下这个名字的工程师心田的挣扎,他肯定很想让其他人(或者本人)看懂这个名字指的是什么,然而比拟遗憾,至多在我这里没有胜利。
察看的下一步是什么?作出口头。在做出口头之前想想,有口头的前提是什么?咱们解决问题的口头大抵会遵循上面模式(我本人总结的,但任何一本认知心理学的书都会有相似的概念):察看—> 发现动机—> 猜测—> 验证猜测—> 造成打算—> 口头,而后再回到察看,重复循环。
这个外面人(或者是老司机的教训)体现比拟重要中央是在从察看到猜测这个环节,至于察看的动机而言无非有两种:
解决眼前的故障;
躲避潜在的危险(防止将来的故障)。
假如零碎没有问题,也不太须要做出扭转。我感觉这两步之所以重要,是因为基本上其余环节都能够用自动化,唯独这两步很难,因为须要用到:人的常识 / 教训和直觉。
对于一个领有好的可观测性的零碎,通常都是能很好利用人直觉的高手,举个小的例子:当关上一个零碎后盾界面时,咱们试着不去关注具体的文字信息,如果界面中的红色黄色的色块比拟多,咱们的直觉会通知本人这个零碎可能处于不太衰弱的状态,更进一步如果红色和黄色大抵都汇集在屏幕的某个具体位置上,咱们的注意力肯定会聚焦到这个地位;如果一个界面上全是绿色,那应该是比拟衰弱的状态。
怎么最大化利用人的直觉?或者说要疏导到什么中央?我认为最好的点是:危险的预判。
人的直觉用在哪?危险的预判
此处须要利用一些先验常识。在聊这个话题之前,我想分享一个我之前听过的小故事,当年福特工厂里有个电机坏了,而后找了个老师傅,他听了听声音,看了看机器运行状况,最初用粉笔在电机上画了一条线,说这个中央的线圈多绕了多少多少圈,将信将疑的工人们照做,果然问题解决了,而后老师傅开了个 1 万美元的维修费(过后算是天价),福特的老板问他凭啥画一条线就收那么多钱,老师傅开了个账单:画线 1 美元,晓得在哪画这条线 9999 美元。
故事的虚实暂且不聊,假如是真的,咱们能够看到直觉和教训,真的是能产生很多的价值,我过后听到这个故事的第一反馈是,这个老师傅必定这种状况见的多了(废话),而且这个问题肯定是常见问题。
其实解决问题最难局部是通过观察(尤其是一些特色点)排除掉绝大多数不靠谱的方向,另外要置信常见故障的起因是会收敛的。这时一个具备良好可观测性零碎的第一步就是能给使用者的直觉指引方向,这个方向就须要前人的常识来给出可能性最大的故障点以及相干的指标(例如 CPU 使用率等);第二步就是通过一些心理学小技巧把它展示进去。
上面以 TiDB 中行将会引入的一个小性能 TopSQL 加以佐证。这个性能说起来也很简略,咱们发现很多用户故障都和大量的 SQL 相干,这类的 SQL 的特色是领有和别的 SQL 有显著不同的 CPU footprint,然而每一条 SQL 的 footprint 独立看起来还挺失常的,所以 TopSQL 的性能就是答复:CPU 到底耗费了多少?在哪些 SQL 上?我试着不去解读上面这个截图,我猜聪慧的你马上就能晓得怎么用:
你的直觉会通知你,后半段那段密集的绿色占比如同和其余有什么不一样,将整体的 CPU 使用率推高了,感觉有问题的样子,没错,这大略就是正确的方向,好的可视化可能利用人的直觉疾速定位主要矛盾。
什么叫做“一个操作”?辨认操作的真正的生命周期
方才写第一点的时候想到还有一个常常被人疏忽的要害资源:工夫。原本想把工夫放到要害资源那节外面,然而想了想放在这里可能更加适合。
略微形而上一点来看,咱们当初的计算机都是图灵机的实现,我小学就晓得图灵齐备语言的最小性能汇合:读 / 写变量,分支,循环。用文学一点的说法是:所谓程序就是无数个轮回,大轮回嵌套着小轮回(循环),每个轮回中依据现状(变量)一直的做出抉择(分支)。
我说到这里可能聪慧的读者会猜到我想说什么:如果咱们探讨可观测性脱离了周期,就毫无意义。而周期的定义又是灵便的,对于人而言,大周期显然是一辈子,小周期能够是一年一日,甚至周期能够不必时间跨度作为单位,比方一份工作的周期…
对于一个数据库软件而言,什么是一个正当的周期?是一条 SQL 的执行周期?还是一个事务从 Begin 到 Commit?这里没有标准答案,然而我集体倡议,周期越贴近终端用户的应用场景越实用。
譬如,在数据库中,抉择单条 SQL 的执行作为周期不如抉择事务的周期,事务周期不如应用程序一个申请全链路的周期。其实 TiDB 在很早就引入了 OpenTracing 来追踪一个 SQL 的执行周期内到底调用了哪些函数,破费多少工夫,但最早只利用在了 TiDB 的 SQL 层外部(相熟咱们的敌人应该晓得咱们的 SQL 和存储是拆散的),没有在存储层 TiKV 实现,所以就会呈现一条 SQL 语句的执行过程往下追到 TiKV 就到了一个断头路;
起初咱们实现了把 TraceID 和 SpanID 传到了 TiKV 外部这个性能才算初步可用,至多把一个周期的图景变得更加残缺了,原本咱们打算就止步于此,然而起初产生了一个小事件,某天一个客户说:为什么我的利用拜访 TiDB 那么慢?而后我一看 TiDB 的监控,没有啊,SQL 到数据库这边根本都是毫秒就返回了,然而客户说:你看我这个申请也没干别的呀,两边怎么对不上?起初咱们把 Tracer 加进来当前才晓得客户这边的网络出了点问题。
这个案例揭示了我,如果能做到全链路的 Tracing,这里的全链路应该是从业务端申请开始计算,去对待生命周期才有意义。所以在此之后咱们在 TiDB 外面通过拓展 Session Variable,可能反对用户将 OpenTracing 协定的 Tracer 信息通过 Session Varible 传入到 TiDB 的体系中,买通业务层和数据库层,可能真正实现的一个全生命周期的跟踪,这个性能也会在很近的将来的版本中和大家见面。
说了这么多,总结几点:
工夫也是重要资源。
抓 Sample 也好,做 Trace 也好,选对周期很重要。
周期越贴近业务的周期越有用。
可观测性能救命的时刻:预先观测
我置信没有人会没事天天看着监控界面,其实认真想想,当咱们须要可观测性的时候,少数是曾经呈现了可感知的故障或者很明确的危险。此时的零碎可能曾经“不可救药”,或者在迫在眉睫的时候还不晓得啥起因导致,其中的根因或是之前某个工夫的一些不太显然的异样变动,这时候发现之前除了失常的 Metrics 外并没有更多的信息,咱们当然不会永远开着 CPU Profiler,通常 Profiler 都是手动触发,然而如果是在预先复盘起因的时候,可能有事发之前的 CPU Profile 记录,对于问题的解决和归因会有微小的帮忙,所以一个比拟好的计划是:在一个绝对短的工夫距离下(比方分钟级)主动的开启 Profiler,主动把诊断后果保留下来,就像定期做一个深度体检记录一样,老的记录定期删除就好了,万一出事,能够疾速往前回溯,救命的效率会更高。
另外置信我,做 Profile 其实也不会有什么显著的性能损耗(何况还是间歇性的),这个性能咱们叫做:Continuous Profiling,这个性能很实用,也会很快和大家见面。
依据咱们的教训,联合下面一节,有了欠缺的 Tracing 零碎,大部分的 Debug 过程在 Tracing + Log 就能找到问题的根因。
最好的可观测性是可能领导用户:“我接下来该做什么?”
上文中提到了口头,我在察看老师傅解决问题的时候发现一个特地有意思的景象:有教训的开发者总是可能很快通过观测,决定本人接下来该做什么,不须要查阅材料什么或者等着他人领导,齐全处于一个心流的状态(例如在 TiDB 外面看到数据在集群外部散布不均或者有热点,就晓得去批改调度策略或者手工 split region),然而新人在这一步总是会卡着,要么去 Google 要么去翻文档,心田 OS:「我看到问题了,而后怎么办?」,如果这个时候,零碎可能给一些接下来应该观测哪些指标,或者口头倡议,会更加敌对,目前能做到这一点的零碎不多,如果能做到这一点,置信你的零碎曾经在可观测性上做得很棒了。把这个点放在可观测性的最初其实是想借着这个话题引出可交互性。
可交互性
在聊根底软件的可交互性之前,我想先和大家回顾一下计算机的历史,在我看来计算机历史的一个侧写就是人机交互的进化史:从第一张图,看着一堆线我也不晓得怎么操作,到当初我素来没看过 iPhone 的说明书就可能纯熟应用,这个背地其实是多个学科的提高(包含不限于心理学、认知科学神经科学、哲学、计算机科学)。
回到咱们这个畛域,根底软件这个畛域因为离公众的确有点远,过来很多设计是由工程师实现的,咱们这类人,广泛有点不足对兽性的了解(no offense),一个很典型的逻辑是:“我本人是人,所以我理解人。我的设计本人能了解,因为我是人,所以别的人也能了解。如果他人不会用,就去看看文档就好了(此时还有一个厌弃脸)”。
当咱们复盘一些故障时,常常会得出「使用者操作不当」的论断,然而这真的是根因吗?我在之前的公司曾经验过一个事变给我留下了粗浅的印象:过后外部有一个本人做的分布式文件系统,就像所有的文件系统一样,它有一个 shell,能够反对一些 UNIX Style 的命令操作。
有一次,一个工程师执行了一行命令:rm -rf usr local/…(留神 usr 后边的空格),而后零碎很听话的开始删除本人 … 最初这件事件的复盘并没有嗔怪这个操作者,而是惩办了这个零碎的设计者(过后那个公司的老板),因为这是个坏的交互设计,哪怕在删除重要文件夹前确认一下或者通过权限零碎爱护一下都不至于产生这个事件,机器的确在依照逻辑工作,这个中央也没有 Bug(甚至这个删除还很高效,毕竟分布式系统 LOL)。
在起初作为工程师漫长的岁月中,我慢慢了解到一个情理:最好的工程师能在逻辑和理性两头找到一个均衡,良好的设计源于对技术和心理的了解,毕竟咱们是在为人写程序。
作为软件的使用者,咱们与其说是在应用,不如说咱们是在和软件「对话」。那既然是对话,那么就意味着这是一个交互的过程,什么是一个好的交互体验呢?我试着总结一些写给软件设计者的准则,试着第一次干这事,不排除当前会补充。
没人读文档:一条命令启动和摸索式学习
抵赖吧,没有人会看说明书。咱们拿到一部新的 iPhone 时候,第一反馈肯定是开机(很神奇吧,咱们仿佛下意识就晓得开机键在哪)必定不是看说明书找开机按钮,开机就开始通过手指来摸索新的世界,很通俗的情理,为什么在系统软件畛域就要先熟读文档能力上岗呢?
我常常教育咱们年老的产品经理:“你的用户充其量会在你的 GitHub 首页或者文档的 Quick Start 局部上停留 10 秒,甚至连看完这个文档的急躁都没有,他们的潜意识会寻找「深色背景的字」(shell 命令),而后把外面货色复制到本人的终端里看会产生什么,除此之外啥都不会做,如果这第一条命令失败了,不会再有前面什么事了,所以记住你只有一次机会”。
一个小例子就是过后在做 TiUP(TiDB 的装置部署工具)的时候,我重复告诫 TiUP 的产品经理,首页里不要废话,就一句命令,贴进去就能用:
TiUP 的首页(tiup.io)截图
其实这个例子能够更延展一点,我记得疫情之前有一年我在布鲁塞尔加入 FOSDEM,早晨在会场左近的酒吧和一位来自英国的 DevOps 聊天,可能也是喝多了,他说:“不能用一个 apt-get install 就装置胜利的系统软件不是一个好软件。”,话糙理不糙。
那你可能要问,如果的确有一些信息或者概念须要传递给用户,如果用认知心理学外面的概念,可称之为构建 Mental Model(心智模型),最好的形式是什么呢?我本人的教训是:摸索式的学习。反对这种认知构建模式的零碎通常须要有 Self-Explanatory 的能力,即通知用户第一步(例如 iPhone 的开机)之后用户的每一步都可能利用上一步行为的输入,决定下一步的行为实现学习。
举个例子:MySQL 的零碎表想必 MySQL 的用户都不会生疏,你只有用一个交互式的 mysql-client 链接到一个实例上,也不必等着零碎告知 INFORMATION_SCHEMA 外面有什么,只须要用户 SHOW TABLES 一下就晓得了,而后再应用 SELECT * FROM 语句就能够一步步摸索 INFORMATION_SCHEMA 外面具体表的内容。这就是一个 Self-Explanatory 的绝佳例子(这个例子外面有个前提就是 SQL 作为对立的交互语言)。
另一个特地好的例子是 Telegram 的 Botfather,我置信给 Telegram 写过机器人的敌人肯定会对 Botfather 的好用水平印象粗浅,我放一张图你就懂了:
用 Telegram 的 botfather 创立聊天机器人的过程
Telegram 是一个聊天软件,Botfather 奇妙的利用了 IM 的交互模式利用到了一个绝对干燥的 bot 开发流程外面,而不是凉飕飕的丢给用户一个 URL https://core.telegram.org/bot…,让用户本人钻研去。
这一节最初一句话想送给大家,有一个无从讲究的都市传说是这么说的:鱼的记忆工夫只有 7s,我想说,人也一样。祝你做出一个“鱼”都能用好的软件。
帮用户多想一步,通知用户半步,让用户本人走半步
我很喜爱看科幻小说,很多科幻小说摸索的一个终极哲学话题:咱们是否真的有自我意识?只管咱们认为咱们有,然而在软件输入 Unknown Error 的时候,你必定心愿有一个声音通知你接下来该怎么办,对吧?一个优良的根底软件,在输入负向反馈的时候,最好的做法就是倡议开发者接下来该干嘛。我举一个很经典的例子,所有的 Rust 开发者都有过被编译器调教的日子,然而这个过程严格来说其实并不苦楚,比方,看上面的截图:
Plain Text
error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable
--> error.rs:8:5
|
7 | fn change(some_string: &String) {
| ------- use `&mut String` here to make mutable
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ cannot borrow as mutable
之所以不苦楚是因为编译器明确通知了你哪里有问题、起因,以及下一步应该干嘛,一般编译器可能打印一个 cannot borrow as mutable 就惨绝人寰了,然而一个好体验的编译器会多帮你想一步。
回到自我意识的问题,我之前听过一个段子:一个测试工程师走进一家酒吧,要了 NaN 杯 Null,一个测试工程师化妆成老板走进一家酒吧,要了 500 杯啤酒并且不付钱,一万个测试工程师在酒吧门外呼啸而过,一个测试工程师走进一家酒吧,要了一杯啤酒 ’;DROP TABLE,最初测试工程师们称心地来到了酒吧,而后一名顾客点了一份炒饭,酒吧炸了 LOL。这个故事通知咱们,作为软件设计者,你永远没有方法穷举使用者的想法,与其让用户放飞想象力,不如你本人设计好故事线,一步步让用户跟着你的思路走。然而为什么还要留半步?
我的答案:
「参与感」会带来幸福感,人有时候挺矛盾的,一边心愿机器主动干完所有的事,一边还期待本人有主动权。有时候即软件曾经晓得下一步肯定是做某些事件,然而留下临门一脚让操作者实现相当于把成就感都赋予了操作者。
抉择的权力交给操作者,尤其在面对一些单向门的决定时,go or no-go 还是应该交给人。
对于这点,我还有几个小倡议:
- 对于一些操作可能会引发多个间断操作的模式(例如 terraform 的部署脚本,或者集群变更之类的性能),提供一个 Dry Run 模式是必要的,只输入操作,不执行操作。
- 对于下面这种批处理型的操作,尽可能设计 save point,不必每次都从新来(相似断点续传),体验会好很多。
-
遇到真的 Unknown Error 要输入各种帮忙 Debug 的上下文信息,最初在谬误日志里提醒用户到哪个链接提 Github Issue,而后最好在 URL Link 里帮用户把 Issue Title 填好(让用户本人决定是不是发 Issue)。
对立语言:控制器和管制对象
我访谈过很多零碎工程师,我有个必问的问题:你心中最好用的(数据库)cli 工具是哪个?绝大多数简直下意识的答复 redis-cli。其实我本人也会给出同样的答案,起初我想这是为什么呢?
「控制器」-「被管制对象」是一个在根底软件中十分常见的模式,就像咱们在操作电视机的时候,绝大多数工夫是通过遥控器一样,所以能够认为用户对电视机的第一和大多数触点其实是遥控器,所以类比到根底软件中,对于控制器的设计其实十分要害,做好控制器,我感觉关键点是:
- 构建对立的交互语言
- 自洽且简洁的概念模型
我略微用 redis-cli 作为例子解读一下。应用过 redis-cli 的敌人都晓得,所有的操作都遵循 [CMD] [ARG1] [ARG2] … 的模式,在 redis-cli 没有例外,不论是操作数据,还是批改配置,所有的一切都在一个对立的交互语言下,而且这个语言高深莫测,而且这个语言外面有一些很天然的约定,例如命令(CMD)永远是几个不蕴含符号的字母组成。
Bash
redis 127.0.0.1:6379> SET k v
OK
redis 127.0.0.1:6379> DEL k
(integer) 1
redis 127.0.0.1:6379> CONFIG SET loglevel "notice"
OK
redis 127.0.0.1:6379> CONFIG GET loglevel
1) "loglevel"
2) "notice"
redis-cli 的交互例子
其实这点在方才提到摸索式学习那节 MySQL 的例子也是一样的,SQL 自身就是一个对立的交互语言,只是没有 Redis 这么直观。
第二点是概念模型,Redis 的劣势在于它是一个 Key-Value 数据库,所以概念很简略:一切都是 Key-Value,察看它的 cli 工具,你认真品一品就晓得,作者在尝试将所有的性能和交互都往这个 Key-Value 的模型上映射,这个是很天然的,因为咱们之所以会应用 redis-cli,首先是咱们承受了 Redis 是一个 KV 数据库的事实,所以在应用 redis-cli 的时候的一个主动就成立心智假如就是 Key-Value 模式,这在应用 cli 的时候所有的操作都会变得很天然。这一点在很多优良的数据库软件外面利用的很多,例如 Oracle,实践上能够依赖 SQL 来对软件自身做所有操作,因为用户只有在应用 Oracle 就默认应该是晓得关系模型和 SQL。
说了侧面的例子,咱们聊个反例:大家晓得 TiDB 主我的项目(不包含其余工具,例如 cdc、binlog)至多有 3 个 Controller 工具:tidb-ctl tikv-ctl pd-ctl,尽管 TiDB 的确是一个由多个组件组成的分布式系统,然而对于用户来说,少数时候应用对象其实是 TiDB 作为一个整体(数据库软件),但几个 ctl 的应用形式都不太一样,比如说 pd-ctl 是一个可交互式的控制器,而且影响的范畴大略是 pd 自身和 TiKV,tikv-ctl 的性能上也有一些交加,然而只是针对单个 TiKV 实例应用,这点太令人费解了,TiKV 明明是一个分布式系统,然而 tikv-ctl 却是一个针对单点的控制器?那么管制 TiKV 到底应该用的哪个 ctl 呢?答案:少数时候用 pd-ctl(惊不惊喜,意不意外?)。
就像你有一个电视机,然而须要用三个遥控器来管制,而且真正管制电视的那个遥控器叫做:机顶盒,这种问题在日常生活中大家都认为是一个理所应当的设计问题,然而在根底软件畛域大家的容忍度怎么仿佛忽然就变高了?
No Surprise: 不怕麻烦,就怕惊喜(惊吓)
我不晓得是否是一个普遍现象,根底软件的用户在面对谬误(尤其是因为坏交互造成的),通常会先自责和内疚,认为是本人的问题,很少会归因于软件。尤其是当可能比拟纯熟的操作一些简单又决裂的软件的时候,很多人会感觉这是一种「技能」,毕竟没有人违心他人看着本人的蠢笨操作。
这背地其实有着很深层次起因(Hacker Culture 外面多少有点崇尚简单的偏向),然而我想说:这就是的软件的问题!就像我从不避讳说我就不会用 gdb,不是因为我智商不行而是因为这个货色真是太难用了。
然而我见过很多人真的是以纯熟应用命令行 gdb 作为夸耀的资本,回到后面提到的那个反例,我在一个 TiDB 的深度用户那边察看他们的操作员做日常的运维,这个操作员十分纯熟的在各种 ctl 之间切换和操作,他不感觉有啥问题,甚至感觉有点厉害,起初我想了下,人的适应性还是很强的,真正让人困扰的事其实并不是麻烦,而是当你在对系统做出一个操作的时候,通常会带着一个下意识的假如,例如一个性能的名字叫「xx 开关」的时候,用户在关上开关的时候的预期应该是有一个正反馈,然而如果后果并不是这样的话,用户会十分有挫败感。这里有个实在的故事,咱们在 TiDB 5.0 外面引入了一个新性能,叫做 MPP (Massively Parallel Processing),即大规模并行处理,咱们有个开关配置叫做:tidb_allow_mpp
不晓得大家有没有留神到问题:作为一个开关型的配置,当设置成 OFF 的时候,是一个 100% 的负反馈,这没有问题,然而问题在设置成 ON 的时候,这个性能是否启用会依赖优化器的判断,也就是有肯定概率 MPP 性能不会失效,这就像一个房间里有个管制灯的开关,当你关的时候,灯肯定不会亮,当你开开关的时候,灯不肯定亮(灯感觉房间内的光线足够,没必要亮 …),你肯定不会感觉这个灯智能,你肯定会感觉灯坏了。下面这个配置的一个更好的写法应该是:
tidb_mpp_mode = ON | OFF | AUTO
这个写法我都不必解释,你也不必看文档,是不是一眼就明确怎么用?好配置应该是自解释的。通常来说,配置项是毁坏用户体验的重灾区,后边讲反馈的时候开展讲讲。
UNIX 哲学外面有一条「宁静准则」,说的是如果程序没什么特地事件要表白,应该放弃宁静。具体的一个体现就是激励命令行程序如果胜利执行,不须要输入货色的话,就间接以 0 作为 return code 退出就好了,其实对于这一点我是持保留意见的,用户的行为如果是合乎预期的后果,应该用一个明确的正向反馈作为处分(例如打印一个 Success 都好),不要忘了兽性巨匠巴普洛夫。
反馈:裸露停顿,不要裸露外部细节
方才正好提到了反馈,我感觉将反馈称为好体验中最重要的一环都不为过。学过控制论的敌人的都晓得反馈是十分重要的概念,后面提到的 Self-Explanatory 之所以是个好体验就是因为反馈的及时性。
然而我诧异的是,很多根底软件在交互反馈局部设计得蹩脚得令人发指,举一个我相熟的例子,某些数据库软件在接管到一个简单查问的时候,当敲下回车,通常就 Hang 在那里了,可能的确数据库程序在后边辛苦的检索和扫描数据,而后隔了几分钟间接返回一个后果(或者挂了),过程中并没有反馈扫描了多少数据和预期要扫描多少数据,其实这个体验是很差的,因为这个信息就是停顿(这点上 ClickHouse 做得很好)。反馈是须要精心设计的,我的几个教训是:
反馈肯定要即时,最好是敲完回车后 200ms 内肯定要有反馈(人的生理反应工夫,超过这个工夫反馈人就会有卡顿感),顺滑的感觉是靠反馈发明的。
反馈停顿,不要反馈细节,不要反馈须要上下文能力读懂的细节(除非是 Debug Mode),这里给出一个咱们本人的反例(https://asktug.com/t/topic/2017):
Bash
MySQL [test]> SELECT COUNT(1) AS count, SUM(account_balance) AS amount, trade_desc AS type FROM b_test WHERE member_id =「22792279001」AND detail_create_date >=「2019-11-19 17:00:00」AND detail_create_date <「2019-11-28 17:00:00」group by trade_desc;
ERROR 9005 (HY000): Region is unavailable
这个 Case 坏在哪里呢?很显然,对用户来说,Region 是一个 TiDB 外部概念,一个很天然的问题是:什么是 Region(我在后面埋了个伏笔,不晓得你留神到没有)?为什么 Select 数据和 Region 相干?为什么 Region is unavailable?我该怎么解决这个问题?裸露给用户这个信息是无用的,反而给用户发明了乐音。这个 Case 的起因是 TiKV 太忙,无奈返回须要的数据,一个更好反馈应该是:具体的哪台 TiKV 因为哪些数据(用用户能了解的模式,如:哪张表,哪些行)读取不进去是因为 TiKV 太忙,最好还能通知用户为什么忙,怎么解决,切实解决不了至多贴个 FAQ 的链接(我见过有软件间接贴 StackOverflow 的 Search URL 的 LOL)。
对正反馈设置一些 milestone,例如一个服务器程序开始失常对外提供服务的时候,打印一个 Ascii Art,不同日志级别用一些带色彩 Label,这是给用户一个明确信号,这点 redis-server 做得很好。通常对于可交互命令行程序的反馈还是容易设计的,一个十分麻烦的事件是,根底软件通常十分依赖配置文件,配置的问题就是批改配置到确认失效的反馈周期通常很长,一个常常的场景是:批改配置 – 重启 – 察看成果,而且通常配置是存储在配置文件外面,这也造成批改文件操作的反馈感是极差的,因为用户也不晓得到底这个操作有没有失效,尤其是一些配置的失效并不是太显著,一些比拟好的实际如:程序在启动的时候打印一下读取了哪个配置文件以及这个配置文件的内容是什么;设计一个相似 print-default-config 之类的命令行性能,间接输入模板配置,省得用户本人 Google。
另外对于分布式系统来说,配置的问题更加简单,因为存在并不是本地配置和全局配置的区别,以及更新后的配置散发的问题,包含滚动重启的问题(重启过程能力让配置失效自身就不是一个好设计),诚实说目前我还没有特地好的计划,可能的思路是是应用相似 etcd 这样的分布式全局配置核心或者(对于数据库来说)通过一些全局的配置表来实现。然而总体的准则是:集中比扩散好;即时失效比重启失效好;对立交互(批改和读取配置的形式)比多种形式交互好。
写在最初
终于写得差不多了,然而这篇文章我感觉仅仅是抛砖引玉,肯定还有很多好的实际没有总结进去,也心愿有想法敌人找我一起探讨,我揭晓一下最开篇留下的一个悬念,为什么要在第一篇文章中将可观测性和可交互性放在一起写,其实这个是来自经典的认知心理学中的人口头的模型[3]:
当用户应用软件时,须要面对的两个鸿沟:一个是执行的鸿沟,在这里,用户要弄清楚如何操作,与软件「对话」;另一个是评估的鸿沟,用户要弄清楚操作的后果。咱们作为设计师的使命就是帮忙用户打消这两个鸿沟,正是对应到文章中的可观测性和可交互性。
设计出应用起来令人愉悦的软件是一门艺术,也不见得比设计出一个精妙的算法或者强壮的程序简略,从某种意义上来说更加难,因为这要求设计者真的要有对人和软件两者都有深刻的了解以及倾泻感情,最初送给大家一段来自 Steve Jobs 的话共勉:
The design is not just what it looks like and feels like. The design is how it works.
参考:
[1] 我眼中的分布式系统可观测性, 黄东旭, 2020
[2] Overtaxed Working Memory Knocks the Brain Out of Sync | Quanta Magazine
[3] The Design of Everyday Things, Donald Norman, 1988