开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

技术人沟通中的几个误区

发表于 2017-11-27
沟通这个话题很大,所以今天的阐述的内容只针对80%以上的技术领域从业者会面对的场景。即便只是讲技术团队中的沟通,也足够讲几天的课程了。所以,我选择了“沟通误区”这个话题作为切入点,通过一些具体事例来谈谈技术人沟通中的问题,来看看如何将沟通做到更好。

误区1: 加强沟通就是多和人说话

这一点最大的问题,就在于狭义了的理解了沟通的意义。对于技术人来说,沟通的方式是多样化的,比如说,书写技术方案文档,每日工作邮件,添加代码注释,提交代码的时候是否有完整的git comments等等。而且,以上这几点对技术人而言,比直接说话沟通更重要,因为如果已经有技术文档,就不用每次来一个新人,就需要重新语言沟通一遍,提升时间效率;如果有代码注释,其他程序员就可以快速的知道API中的输入输出和参数的含义,做到快速接入,而不需要找到原作者交流一遍。

对于技术人而言,真正的沟通其实早已经融入到他自身的工作中去了,

沟通是桥梁, 但沟通并不单纯是自下而上的。在技术团队内部,沟通很多时候,是和团队里的其他”技术”进行的。需要通过合适的方法,将信息在技术团队中同步开来。这也是为什么,越来越多的技术团队开始引入JIRA,Confluence, Code Review Board等各种工具,帮助大家做好沟通。

误区2: 沟通是为了取悦别人

这个问题还是比较常见的,实际工作中,一些技术的同学,在需求评审过程中,对于业务或者产品提出的需求,没有经过认真的准备和思考,就一味的答应,认为这就能得到产品或者业务对自己的认可,以为这就解决了沟通的矛盾和问题。

在我看来,沟通的第一要素,就是输出确定性。这就意味着,准确的回答比满足对方的要求更重要,将偏差暴露在早期,远比最后无法达到,带来的失望好太多,更别说,早期问题可以有时间去找其余的解决方案。对于个人而言,沟通的确定性是个人专业的一种体现。

沟通能力与代码量积累成反比。沟通不好会多写好多冤枉代码,而且还没有得到满意的结果。

误解3:沟通能力=会聊天

技术人的沟通能力绝对 != 会聊天,而是拥有足够的专业能力+会聊天, 技术水平是”沟通能力”的基础。如果没有扎实的技术基本功, 对每个环节都没有深入认知的前提下,即使会聊天, 善于沟通,所传递的信息也可能是错误的,我也见过很多沟通伶俐的工程师,进度汇报非常漂亮。但是,他代码的质量可能糟糕到我不得不考虑重新更换一次人员。

反过来说,如果一个技术人总是能用一个简短的比喻,或者小故事,让完全不懂技术的业务人员知道他在做什么, 能讲明白引入某一项技术的优势和作用。那么他的沟通能力就是很强的。

工作本身分两种,一种是“社会化”的,就是跟人打交道的,比如销售、市场,还有一种是“非社会化”的,做技术开发的明显是属于这一类。那么“社会化”和“非社会化”之间的沟通,说白了,就是学会用别人的语境沟通,这是个很重要的技巧。

扫描二维码或手动搜索微信公众号【架构栈】: ForestNotes

欢迎转载,带上以下二维码即可

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Scala 技术周刊 第 27 期

发表于 2017-11-27

这里有最新的 Scala 社区动态、技术博文。
微信搜索 「scalacool」关注我们,及时获取最新资讯。


深度阅读

  • Replacing IntelliJ with Emacs for Scala Development
    用 Emacs 进行 Scala 开发
  • The power of Scala Type Classes
    TypeClass
  • Scala 与设计模式(五):Adapter 适配器模式
    Scala 与设计模式系列
  • So, what’s wrong with SBT?
    关于 SBT 的槽点以及事实
  • Akka Streams pitfalls to avoid — part 1
    Akaa Stream 的陷阱
  • Should you use Scala for Startup?
    初创公司适合用 Scala 吗

一周速递

  • Akka 2.5.7 发布
  • akka-stream-kafka 0.18 for Akka 2.5.7 发布
  • scalafix v0.5.6 发布
  • Quill 2.3.0 发布

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

线程之间传递 ThreadLocal 对象

发表于 2017-11-27

为了介绍线程间传递 ThreadLocal 对象这个事情,请先耐心一些跟我一起来看看我是怎么遇到线程间传递 ThreadLocal 对象这个需求的。

一起看这么个场景,大致上是下面这样,是 clojure 的代码。但是请不要担心,它非常短也非常简单:

1
2
3
4
5
6
7
8
9
10
复制代码;; 定义一个动态绑定的变量,实际就是个 Java ThreadLocal 对象
(defonce ^:dynamic *utc* false)

;; 处理某 Http 请求的函数, 参数 req 内是用户传来的 Key -> Value 格式的参数
(defn some-http-handler-function [req]
;; 首先需要从 req 内读取 API 版本信息 所有请求都会有这个参数
;; 如果 API 版本是 1.1 则认为使用的是 utc 时间,所以给 *utc* 变量绑定一个 true 值
(binding [*utc* (= (:api-version req) "1.1")]
;; 绑定好 *utc* 后,开始处理 req 请求,process 返回值作为 Http 请求的结果
(process req))

some-http-handler-function 是一个处理 Http 请求的函数,这个 Http 请求的 content-type 是 JSON。Server 收到请求后会将 body 内的 JSON 参数转换为 Key -> Value 的 map 当做 req 参数传入 some-http-handler-function 函数做处理。

我们的 Http 接口要求请求中必须带着一些时间参数,并且时间参数须是符合 iso8601 格式的字符串,形如 2017-09-23T12:15:42.972Z。我们的 API 分为很多版本,1.1 版本之前的 API 没有对用户提供的这个时间参数的时区做约定,所以我们默认以当前服务器所在时区来解析用户传来的时间参数。1.1 版本之后的 API,我们跟用户约定时间参数必须是 UTC 时间,服务器也就直接以 UTC 时间来解析时间参数,从而不再有时区差异问题。

因为相同的 some-http-handler-function 函数,要兼容处理 1.1 版本 API 请求和老版本 API 请求,所以要在请求中带着 API 版本信息,并在收到请求后,根据 req 中的 API 版本信息,判断是否使用 UTC 时间。因为处理请求的函数有多层嵌套,比如 (process req) 可能调用 (do-process req)、(do-after-do-process req)等等,又不想让所有嵌套的函数调用都带着是否使用的 UTC 时间的参数,就简单的将是否使用
UTC 时间这个事情记录在一个动态绑定的 \*utc\* 参数上。

如果不了解 clojure,可以将 \*utc\* 简单理解为一个全局的 Java ThreadLocal 对象,同一个线程存入一个值后,比如将 \*utc\* 这个字符串绑定为 true 后,在该线程后续调用的所有函数、方法内,都能直接拿到 \*utc\* 参数的值,从而不用在所有该线程调用的函数、方法内都带着 \*utc\* 参数。让代码简单一些。

最初 (process req) 是个同步的调用,绑定完 \*utc\* 参数的主线程会完成 process 函数内所有逻辑,完成 Http 请求的处理并负责将结果发给用户。但后来为了隔离,将 process 函数内增加了线程池,会从线程池找一个空闲线程来实际处理 Http 请求,当前主线程会 Block 住等待 process 执行结果。由于 Java ThreadLocal 对象是不能在线程之间传递的,所以主线程虽然绑定了 \*utc\* 参数,但是 process
内的业务线程并不知道这个事情,于是出现即使在处理 1.1 版本 API 的请求时,所有时间参数也均采用服务器本地时区来解析,而不是 UTC 来解析,造成了 Bug。

这就是我遇到的线程之间传递 ThreadLocal 对象需求的来源。我们的解决办法很简单粗暴,就是将 \*utc\* 这个参数直接放在 process 函数的参数之中,等到 process 内的线程实际运行时,重新为线程池内实际执行 process 工作的业务线程绑定 \*utc\* 参数。

当时就在想如果有机制能在一些特定情况之下,让 ThreadLocal 对象绑定的 Value 在不同线程之间能共享,对上面这种场景处理就会比较方便。主线程将处理请求的任务交给业务线程之后,即使两个线程共享 \*utc\* 参数,但因为都不修改这个参数值,所以并不会引起问题。

下面我们看看这种 ThreadLocal 对象绑定值如何在不同线程之间传递。

父子进程之间传递 ThreadLocal 对象

这个实际上 JDK 自己有实现,是 InheritableThreadLocal 类。慢慢介绍一下它的原理。

Thread 类内实际有两个 ThreadLocal.ThreadLocalMap,一个是给 ThreadLocal 使用的,还一个是给 InheritableThreadLocal 使用的。给 InheritableThreadLocal 使用的 ThreadLocalMap 特殊一些,在一个线程 fork 一个新的子线程的时候,父线程会检查自己 Thread 内为 InheritableThreadLocal 使用的 ThreadLocalMap 是否为空,不为空则将其拷贝给子线程的为
InheritableThreadLocal 使用的 ThreadLocalMap。Thread 的 init 可以看到这块逻辑:

1
2
3
复制代码if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

ThreadLocal.createInheritedMap 会调用 ThreadLocalMap 的拷贝构造函数实现大致为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

for each Entry e in parentTable {
if e != null {
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
setKeyValueToThisThreadLocalMap(key, value);
}
}
}
}

这么一来创建子线程之后,父线程的给 InheritableThreadLocal 使用的 ThreadLocalMap 就在子线程中有了一个副本。默认情况下父子线程的 ThreadLocalMap 内的 key 都指向同一个 InheritableThreadLocal 对象,Value 也指向同一个 Value。从上面能看到子线程存储 ThreadLocalMap 的 Value 实际上是 key.childValue(e.value) 就是说能在使用 InheritableThreadLocal
的时候覆盖 childValue 方法从而根据父线程的 Value 提供子线程的 Value。

但是对于开篇那个问题的场景,InheritableThreadLocal 无法使用。在那个场景中,相当于是一个 Thread A 发配一个 Runnable 或者 Callable 到一个线程池中,让线程池内的线程去执行 Runnable/Callable。Thread A 和线程池内的线程并没有父子关系,所以 Thread A 不能将绑定的 InheritableThreadLocal 值传递给线程池内负责实际执行 Runnable/Callable的线程。


Thread A 传递给线程池内线程的只有 Runnable/Callable,所以如果想要实现将 ThreadA 的 ThreadLocal 值传递给线程池内实际负责执行 Runnable/Callable 的 Thread,就一定是需要在 Runnalbe/Callable 做文章,传递 Runnable/Callable 的时候将 Thread A 的 ThreadLocal 值存到 Runnable/Callable 中,之后线程池线程,比如是 Thread 1,在运行
Runnable/Callable 时从中读出这些 ThreadLocal 值并存入Thread 1 的 ThreadLocalMap 中,实现 ThreadLocal 对象值的传递。这也是 GitHub - alibaba/transmittable-thread-local
库最基础的实现原理。

感觉很多东西都是这样,仔细想想原理感觉不难,但难的是能想到这个点子。之前遇到开篇问题的时候只是想绕过的笨办法,没想想怎么从 ThreadLocal 原理上来解决这个问题。下面记录一下这个库的部分实现,但是这里不准备直接记录 transmittable thread local 是怎么实现,而是看看能不能在现有了解东西的基础上,一步一步推测出来它怎么实现。

transmittable-thread-local

通过上面的描述,应该已对 transmittable thread local 的实现原理有个大致的猜想。首先是需要有个专门的 Runnable 或 Callable,用于读取原 Thread 的 ThreadLocal 对象及其值并存在 Runnable/Callable中,在执行 run 或者 call 的时候将存在 Runnable/Callable 中的 ThreadLocal 对象和值读出来,存入调用 run 或 call 的线程。

读取原 Thread 上所有的(或者说会发生这种线程间传递的) ThreadLocal 对象及其值比较麻烦,ThreadLocal 和 InheritableThreadLocal 都没有开放内部的 ThreadLocalMap,不能直接读取。所以要么自己完全实现一套 ThreadLocalMap 机制,像 Netty 的 FastThreadLocal 那样;要么就是自己实现 ThreadLocal 的子类,在每次调用 ThreadLocal 的 set/get/remove 等接口的时候,为
Thread 记录到底绑定了哪些需要发生线程间传递的 ThreadLocal 对象。后者更简单和更可靠一些,所以可能选择后者更稳妥,transmittable-thread-local 这个库也是这么做的。

通过 Runnable/Callable 传递 ThreadLocal 对象及其值的方法是有了,父子线程之间传递可以复用 InheritableThreadLocal 的实现,所以新的 TransmittableThreadLocal 对象需要继承 InheritableThreadLocal 从而获取它的父子线程间传递 ThreadLocal 对象及其值的能力。到目前为止,大致的类图如下:


使用的时候必须使用使用 TransmittableThreadLocal,创建 Runnable 或 Callable 也必须使用 TtlCallable 或者 TtlRunnable。TransmittableThreadLocal 覆盖实现了 ThreadLocal 的 set、get、remove,实际存储 ThreadLocal 值的工作还是 ThreadLocal 父类完成,TransmittableThreadLocal 只是为每个使用它的 Thread 单独记录一份存储了哪些
TransmittableThreadLocal 对象。拿 set 来说就是这个样子:

1
2
3
4
5
复制代码public final void set(T value) {
super.set(value);
if (null == value) removeValue();
else addValue();
}

addValue() 和 removeValue 都是将当前 TransmittableThreadLocal 对象存入 TransmittableThreadLocal 内一个 static 的并且是 InheritableThreadLocal 的 WeakHashMap 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
@Override
protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
}
@Override
protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
return new WeakHashMap<TransmittableThreadLocal<?>, Object>(parentValue);
}
};
private void addValue() {
if (!holder.get().containsKey(this)) {
holder.get().put(this, null); // WeakHashMap supports null value.
}
}

相当于是和 Thread 绑定的所有 TransmittableThreadLocal 对象都保存在这个 holder 中,TransmittableThreadLocal 对应的 Value 则还是保存在 ThreadLocal 的 ThreadLocalMap 中。holder 只是为了记录当前 Thread 绑定了哪些 TransmittableThreadLocal 对象,好在 TtlRunnable 或 TtlCallable 构造的时候通过 holder 取出这些 TransmittableThreadLocal
存入 TtlRunnable 或 TtlCallable。

剩下的更多细节感觉就不用再多记录了,有兴趣的话可以下载这个库的代码看看。上面原理虽然看上去简单,但是这个库还在易用性上做了很多增强,还是值得看看值得使用一下的。

头图是随便找的,具体来自:Regifting 101

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

阿里智能化运维体系介绍

发表于 2017-11-27

摘要:智能化运维的终极目标,就是将运维人员从繁琐的工作中解放出来,提高整体运维效率,降低运维成本,实现业务系统的高可用性。

目前业界真正的智能化运维的落地实践其实并不多,大多还是停留在自动化甚至人工化阶段,然而智能化运维是大势所趋。阿里又是如何应对呢?下面请看来自阿里巴巴研发效能团队负责人、阿里研究员毕玄的演讲《智能时代的新运维》。

阿里的运维体系承载着怎样的责任?#### 阿里的运维体系介绍

阿里的运维团队,主要覆盖五个层面:### 一.资源的规划与支付是运维的基石

整个运维团队需要负责资源的规划、资源的交付。

Quota**管理**:比如我们会跟业务团队做一些预算的管理,对于每个业务团队首先需要有预算。只要你有预算,运维团队一定会把资源交给你,没有预算一切免谈。

规划:比如阿里每年的双十一交易,业务团队要给出下一年的交易额将做到多少,至于背后需要增加多少的机器量,业务团队根本不关心。所以需要运维团队来做从业务需求到资源的转化和规划,这对于公司来讲非常重要,因为意味着最终我在基础设施上要投多少钱,还有节奏的控制。

采购:当规模大了以后,怎么样合理规划资源的数量和交付节奏是非常重要的,比如5月份采购这批机器和6月份采购这批机器,是完全不同的概念。还需要资源的采购,比如SSD采购紧张,供应量不够。通常大公司会有更多的渠道获得更好的供应量,小公司就会很困难。怎么做好供应链控制是非常重要的。

资源调度:对于资源团队来讲,调度也很重要,我们交出去的机器是怎么样的交法,怎么保证可用性、稳定性, Bootstrap等,每个业务都有自己的规划,按照业务需求怎么把整个业务环境全部交给业务方。阿里目前就遇到了很大的挑战,比如在国际化的扩张上,我们可能这个月需要在这里建个点,下个月需要在另一个地方建个点,怎么快速的完成整个资源,不仅仅是机器资源的交付,还有软件资源的交付,是非常重要的。我们现在在扩展东南亚的业务,怎么样在东南亚快速的完成整个软件资源的交付,对于我们的竞争是非常重要的。

二.变更 是运维不可避开的坑

对于运维团队来讲,变更也是经常要做的部分,变更信息的收拢,做应用层面的变更,基础网络的IDC等等。

三.监控 预测潜在的故障

监控对于阿里来讲主要分为基础、业务、链路,在监控的基础上要去做一些报警等。

四.稳定性 是不少企业追求的目标

稳定性这个概念我们以前认为针对的是大公司,因为它可能会影响到大众的生活,会比较敏感。但是现在新型的互联网公司,如外卖,ofo、摩拜等,它的稳定性要求比以前很多创业型公司更高,因为它有在那个点必须能用,如果不能用,对用户会有直接的影响。所以稳定性可能在整个运维行业会得到越来越高的重视,但是对于很多中小型公司,稳定性的投入相当大的。

### 五.一键建站 让规模化有力保障

像阿里在稳定性上主要会去做多活体系的建设,然后故障的修复、故障定位,然后还有一套全链路的压测。规模化是很多运维团队很痛苦的事情,可能今年机器在这个机房,明年你的基础设施团队可能告诉你,这个机房不够用了,我们要换个机房。反正在阿里巴巴,很多的运维人员都说了,我们每年的工作中有一项不用写的工作就是搬迁。虽然基础设施团队会承诺说三年内不会再搬,可是到了明年他会跟你说,由于某些原因我们还是再搬一下,搬完之后三年不会让你再搬。但是从我们过去发展的三年,每年都在搬。未来我们确实相信阿里巴巴,可能在未来搬迁会相对更少一点,我们认为不能让搬迁成为阿里巴巴运维团队的核心竞争力。

我们在规模化层面做了很多事情,比如说我们做了一键建站,对于阿里来讲,我们对机器资源的交付时间,要求会越来越高。比如说双十一,是提前一个月交付资源还是提前两个月还是提前三个月,对我们来讲付出的钱是完全不一样,而且可能相差非常大。

所以,技术层面能不能更好的把这个时间缩短,是非常重要的。所以一键建站的重要目的就是这个,每年双十一我们都会拓展出非常多个站点,通过一键建站快速完成整个过程。搬迁就是我说的,反正我们每年都要搬,那我们应该把搬迁这套系统做得更好。还有腾挪,阿里很多时候因为需要做一些业务资源的复用,最好是有一个机柜,这个时候怎么更好完成挪的过程也是很麻烦。

我们还需要做一些单元的调整,因为对阿里的交易系统来讲是有单元的概念的,我们怎么更好的控制一个单元内机器的比率是非常重要的。一个单元的机器数可能是比较固定的,那如果比率搭配不好,就意味着瓶颈点会非常明显。

以上,正是阿里巴巴的运维团队所覆盖的五个领域。整个运维体系的演进过程,差不多都是从最早的脚本到工具到自动化,到未来的智能化。

从工具化到自动化过关斩将从工具化到自动化这个层面,过程并没有那么的容易,以及对整个行业来讲,目前更多的工作仍然是在探寻自动化,怎么样让自动化真正的被实现得更好。

这个行业的发展跟其他传统的软件,标准的软件研发行业,我觉得很不一样。比如说阿里从工具化到自动化这个过程中,我们认为工具化,其实挑战相对小,即使传统的运维人员也很容易写一些工具,比如用Python去写更多的工具体系。但是如果你的工具最重要变成能够到自动化这个阶段,就意味着对工具的要求会越来越高,比如说工具的质量,如果你写出来的工具经常有问题,规模一大就扛不住,这个时候对于大家来讲慢慢会越来越失去信任感。最后会很难完成这个过程。

运维团队转型研发团队 组织能力是最大的壁垒

阿里过去走这条路的过程中,我们觉得最大的挑战是组织的能力问题。运维团队怎么样更好的完成朝研发团队的转型,这个过程对于很多运维团队来讲都是巨大的挑战。对于一个组织来讲怎么完成这个过程也是非常重要的。

我想很多团队都有这个感受,工具研发的团队跟做运维操作的团队之间,很容易产生一些冲突等等。所以阿里巴巴在走这个过程的时候,思考的核心就是怎么让一个运维团队真正从组织能力上,演变成我们所需要的更好的团队。

阿里在走这条路的时候,走了四个过程。这个过程阿里在不断的摸索,最终到现在为止我们认为阿里的方式相对来讲还是不错的。我们最早跟大部分公司一样,有一个专职的工具研发团队和一个专职的运维团队。工具研发团队做工具,做出来给运维团队用。这个过程中容易出现的最明显的问题就是工具做完了,运维团队说这个工具太难用了,不符合需求。要么就是运维团队执行的过程中,经常出问题,出问题还要找工具研发团队来帮忙查问题在哪里。本来运维几行脚本全部能搞定的问题,结果还要依赖工具团队。慢慢这个局面越来越难突破,很难改变。

所以阿里后来做了一个尝试,既然两个团队很难做很好的结合,那有一种方式是工具研发团队做完工具以后,比如说做了一个发布,做完这个功能以后,这个运维工作就彻底交给工具研发团队,不让运维团队做了,运维团队就可以做一些别的事情。这个模式看起来就是逐步接管的模式,让工具研发团队逐步解耦。

这个做了一段时间,碰到的最大问题还是组织能力问题。对于运维工具来讲,质量怎么做到很高,运维好像很容易做的样子,但是实际上运维工具相当难做,它的复杂度比在线业务更大,就是它不是逻辑上的复杂,更多的是环境层面的复杂。因为比如会涉及网络涉及服务器涉及机房等等,这跟业务完全不一样。所以做了一段时间之后,我们觉得这还是一个问题。

将工具的研发和运维融为一体 突破组织能力问后面我们做完这轮之后又开始做另外一个方向的尝试,让工具的研发团队和运维团队做一个融合。所谓的融合就是把很多工具研发的人分派给运维团队,到运维团队去做。我们期望通过工具研发的人带动整个运维团队转变成研发型团队。这是我们的思路。

阿里巴巴在走前面这三步的时候,大概花了近一年半左右,意味着这其中我们大概做了三轮组织结构调整。因为我们认为这些都是要有组织层面的保障才能被实现的。

DevOps**是如何真正落地的**

去年6月,我们做了一个最大的组织结构调整,把日常的运维工作交给研发做,研发自己会把日常的运维工作都做掉。但并不是说所有运维工作,现在仍然有一个做运维的团队,这个运维团队相对来讲更不一样,跟以前有非常大的不同。

我们认为这是DevOps真正的被彻底的执行。因为这个好处是,日常的运维工作交给了研发,运维团队转变成研发团队这个过程非常困难,其实不完全是能力上的差距,更大的原因是,运维团队要承担非常多的日常杂活,尤其像集团性的公司,不管是阿里、腾讯、百度都一样,集团性的公司多数支撑的BU都是无数个。你一个人支撑二十个BU一个BU里面一天有一个人找你,你一天就不用干别的活了,你一天就在跟他们不断的聊天,做操作,嘴里又叫着这个团队要升级,要做组织升级,要转变成研发团队,实际上就是逼别人走向了一条死路。

所以我们认为,谷歌的做法,谷歌在SRE那本书提到的是,会强制留50%的时间给研发团队做研发工作。这个说实话,在大多数公司很难执行这个政策,除非运维团队跟研发团队有非常强的话语权。但这个很难。所以阿里的做法我认为更为彻底,阿里告诉研发团队,以后日常运维的工作不要找运维团队,自己干。这可能粗暴了一点,在运维体系还没有准备得很好的情况下做了这个事情,所以后面相对来讲也导致了问题,比如说运维工具四处建设、重复建设等等现象。

但是从组织层面上来讲,我们很欣慰的看到,在做完这轮组织调整过后的一年后,运维团队的大多数人更多的时间是投入在研发工作上,而不是投入在日常的杂事上。我们看到了一个团队的能力,在经过这一轮的调整得到了非常好的升级。而这对于组织来讲是最大的利好。所以我们认为,这种模式是阿里现在最为推崇也最为看好的一个方向,这样整个运维团队将专注在我刚才讲的五个部分的系统层面的研发以及建设上,而不是杂活上。这是阿里从工具化到自动化,最主要是这样的一个过程。

成功率是衡量自动化运维的关键指标

对于自动化来讲最重要的问题是成功率,比如我们看所有的运维操作中,我们最关心的指标是成功率。比如一个运维系统里面的功能,在一个星期内,比如说会用几十万次,我们只关注成功率能不能做到4个9以上,否则算一下工单数就懂了,这个运维团队得有多少人支持这件事情,这些人又没有时间去干研发的活,又要投入大量的精力做支持性的工作。所以我们在成功率上要做到非常高的保障,运维系统我们以前看过是面临最大的挑战,我以前的背景全部是做在线业务型的系统,比如淘宝的交易等等。

后来我们发现运维系统有个最大的不同在于,运维系统对于成功率的追求比在线业务型系统更高一些。在线业务型系统,比如说我在访问后面一个地方有问题的时候,我们会选择尽快把这个过程失败掉,而不是把时间不断的拖长以及不断的试错。在线系统会更加快的把错误往外抛。但是对于运维系统来讲如果也这样做,就意味着这个成功率非常难保障。所以运维系统要有更好的思考,怎么保障一次运维操作,这背后可能有几十个系统,而且多数是无数的团队写的,阿里以前碰到的情况就是无数个系统,质量层次不起,什么都有。怎么保证在这么复杂的环境下,保证对外的,对用户层面这个成功率可以做到很高的。这是一个很大的问题。

规模带来的挑战也是不容小觑

随着规模的不断增长,所有开源类型的运维类的系统,在规模化,当你的机器规模等等其他规模上升到一个程度以后,通常来讲都会面临非常巨大的挑战。阿里巴巴所有的这种类型的系统,我们论证都是自己做是比较靠谱。最大的原因是规模,规模上去以后会遇到很多问题。像代码托管、代码编译什么的,以前认为不会有太大的问题,事实证明规模上来以后这些里面全都是问题。我们也要投入非常大的精力去做规模方面的解决。

所以我觉得,阿里从以前的工具化走向更加自动化的过程中,我们探讨的核心问题就是能不能有一个非常好的组织去完成这个过程。能让运维的团队更加转型向DevOps这样的方向。所以我们一直说,我们一直很纠结运维团队到底应该叫什么名字,我们一致认为,运维研发团队,我们觉得不大对,你的主要的活其实是干研发而不是运维。但是叫研发运维又有点奇怪。后来阿里巴巴基本上是叫研发团队。因为我们认为运维的研发团队和在线业务的研发团队没有本质区别,都是做研发的,只是一个在解决运维领域的业务问题。刚才讲的五个层次,运维领域的业务问题,也是业务,没有什么区别。在线业务,比如解决交易的问题,解决其他问题,这是完全一样的。两个研发团队没有本质区别。

所以这个过程,阿里经过过去这一年的组织调整以后,我们看到整个自动化层面,阿里有了很好的进展,但是离我们的期望还要更加努力继续往前演进。

阿里巴巴在智能化领域的探寻之路

现在智能化这个话题特别火热,就像我们说,AI这个名字兴起的时候,我们忽然发现,阿里巴巴所有的业务都讲AI+自己的业务,被所有人狂批一通。我们要想清楚,具不具备AI化的前提,可能前提都不具备就不断探讨这个名字。因为业界在不断的炒热非常多的名词,让大家去跟随。

自动化是智能化的前提对于我们来讲,我们认为,比如说就像我对这个团队,我自己的团队讲的一样,我认为智能化最重要的前提是,一是自动化。如果你的系统还没有完成自动化的过程,我认为就不要去做智能化,你还在前面的阶段。智能化非常多的要求都是自动化,如果不够自动化,意味着后边看起来做了一个很好的智能化的算法等等,告诉别人我能给你很大的帮助,结果发现前面自动化过程还没有做完全。

一个最典型的case,阿里巴巴以前一直在讲,我们认为资源的搭配上,其实可以做得更好。比如说你半夜流量比较小,白天流量比较大,你能不能更好的做一些弹性,把资源释放出来去干点别的,然后白天再把它补起来。这从算法层面上并没有那么复杂,从算法层面做到一个简单的提升是很容易做的。

所以,当时我们就有很多团队做了一个东西,可以做到这一点。结果等到落地的时候发现,业务不能自动伸缩。如果你想,比如说有些机器上面负载特别高,有些机器特别低,我们希望负载能拉得更均衡,在线业务更加稳定化,做一个算法,比如说背包,更好的去做组合,结果就是这个东西做完了,给出了建议说最好这个应用调到那台机器,那台应用调到这台机器。给完之后业务团队看了一眼,我们不干,因为干这些工作全部要手工干,你还每天给我建议,更不要干了,每天就来调机器了。

所以首先你要想明白你的前提,自动化,具不具备自动化的能力,不具备的话没有必要在这方面做过多的投入。

数据结构化是智能化的源动力

目前AI领域基本是靠暴力,暴力破解,未来可能有别的方向,但是目前的AI基本上是靠大量数据的积累去寻找一个东西出来,所以它一定需要有大量的数据积累,数据包括非常多的东西,对于运维来讲,可能基础层面的数据,机器的数据,运维变更的数据,上面还有一些场景化的数据,比如你解决故障,有没有更好的结构化的收集数据,这是非常重要的。数据这个层面比较难做的在于, 在最开始阶段,多数公司的运维数据都是不够结构化的,结构化不会做得那么好,当然会有结构化,但是结构化的因素不会足够好。

就像阿里巴巴在讲,我们在电商领域AI化,我们最大的优势就是不断对外部讲,我们拥有的是结构化的商品数据,其他公司最多从我们这里扒结构化的商品数据。你扒过去之后还要自己分析,并且做商品结构的调整,这非常困难。但是阿里巴巴自己天然,所有人都会帮你把结构做得非常好。所以对运维来讲也是一样,如果你想在智能化上有更多的突破,数据怎么更好的做结构化,是一个非常大的挑战。你很难想清楚。这两个地方是我觉得首先要想清楚的。

智能化最适合的运维场景

从目前来看,对于运维场景来讲,智能化特别适合解决的问题就两种,对于所有行业好像都差不多,第一是规模,第二是复杂。规模就意味着,我有很多的机器,在很多机器中我要寻找出一个机器的问题,这对于,因为规模太大了,这时候对于用传统的方式,将非常难解决这个问题。或者你要投入非常大的人力等等,有点得不偿失。规模上来以后怎么更好的解决规模的问题,智能化会带来一些帮助。

第二是复杂,比如说你的应用从原来的一个应用变成了几千个、上万个、几十万个,这时候你要寻找出其中哪个应用的问题,将是非常复杂的问题。所以复杂度的问题是人类用人脑非常难推演的,但是机器相对来讲是更容易做的。这是阿里有些团队希望尝试智能化的方向,通常我们会看是不是在前面的这些前提条件上都具备。如果都具备了,那可以去探索一下。所以我讲,阿里其实目前处于整个智能化运维的探索阶段,而不是全面展开阶段。

阿里巴巴智能化运维五步走

简单讲一下我们在各个领域目前在智能化这个领域,在运维这五个领域,对于我们讲,智能化我们看到的一些可能性,包括我们正在做的事情。

### 一.资源的重点是成本

1.**基础设施选型**

对于资源这一块,整个公司层面最为关注的问题,就是成本。你交付的资源具不具备最低的成本,这个智能化确实可以给非常大的帮助。比如第一点,怎么更好的规划这家公司机型、网络和整个数据中心,这为什么要用智能化的手段在于,一个数据中心的选址来自非常多的因素,除了政府层面的政策因素之外,还有很多其他因素需要考虑,比如说气候等等各种各样的因素,都需要在这个阶段去考虑。

你需要通过大量数据的积累来分析,比如在中国,在海外,到底有那些地方是对你的业务发展策略来讲最适合的,是在哪里,这要确定一个范围,在一个范围基础上是进一步的人的建立。对于网络、机型来讲,目前我们认为最可以做的在于,可能因为阿里的模式跟有些公司不一样,阿里更多的机器都来自同一个部门,基本上是同一个部门在教阿里巴巴所有的机器。这就有巨大的好处了,因为都在一个团队。比如阿里巴巴在去年开始建设统一的调度系统,更大的好处就来了,因为大家所有的资源都来自同一个地方,这个地方就收集了整个阿里巴巴的所有的资源需求、数据,数据全部在它手上。

如果你结合这个数据,以及它实际的运行情况,更好的就可以去推导,比如说对于阿里巴巴来讲最合适的机型是什么,这个阿里大概在去年就开始做尝试。在去年以前所有的过程,阿里巴巴,比如说明年我的服务器的机型,所谓机型,这里讲的机型的含义主要是比率问题,不是选择下一代什么样的CPU,那是硬件发展决定的。但是比率因素,以前我们更多的是人脑拍,人肉智能。人肉智能在一定阶段是更加高阶的,过了那个阶段之后人就比不过机器了。团队说我们明年要买的机型里面的配置大概是这样的,人算了一下,就这样吧,就可以拍掉。去年开始我们引入了一套系统,这套系统会分析所有的数据以及钱,最重要的是钱,然后分析一下整个过程,推演对我们来说最合算的是什么。所以适合的机型到底是什么。

如果有一套非常好的推演的系统,来推演你的机型、网络、IDC未来应该怎么规划,这对于成本领域将会产生巨大的帮助。比如说网络,现在的发展,万兆,25G、45G、100G,你认为对于你的公司来讲最合适的是什么?多数公司八成就是人脑一拍就决定了,但是事实上可能不是这样。

2. DC**大脑,让控制更加智能化**

DC大脑,这个现在比较火,这个领域现在非常火爆,火爆的主要原因有可能是因为去年谷歌的一篇文章,谷歌去年发表了一篇文章,里面有一个消息透露了一下,他们通过更好的智能化,去控制整个机房的智能等等。比如说控制空调的出口,就是那个风向往哪边吹,控制这个,然后为谷歌节省了非常多的钱,非常可观。所以对于很多数据中心团队来讲,现在都在研究这个领域。因为这个领域实在太省钱了。

我们后来类比了一下,我们说其实大多数人,可能你很难感觉数据中心,但是你最容易感觉的是另外一个地方,你的办公室。比如说我们以前说,阿里巴巴一到夏天的时候,办公室实在是太冷了,比外面冷多了。如果能够更好的控制温度,对于我们来讲就会有巨大的帮助,对公司来讲可能会更加省钱。所以怎么样做好这个非常重要。

3.**弹性伸缩最大的前提是实现自动化**

弹性伸缩,这是无数运维团队都想做的事情,研发团队说,业务团队说,我要一百台机器,你也不好反驳他,最后上线了一百台,你发现他用十台就够了。但是你也很难跟他纠结这个问题,好像无数的运维团队都在尝试弹性伸缩。但是我说了,弹性伸缩最大的前提就是自动化,如果没有自动化也没有什么意义。

4.**资源画像让资源更好搭配**

资源怎么更好的搭配,阿里巴巴在尝试做资源的画像。对于所有的在线业务来讲,它的趋势比较好预测,多数在线业务,只有少数的在线业务不大好预测。多数在线业务是一个模式,如果预测得非常好,让资源有合理的搭配,对于这家公司的资源将会产生巨大的帮助。

二.可以下降30%由变更引起的故障

在变更这个领域我们觉得首先是效率问题。阿里巴巴现在大概有几万的研发人员,我们又把运维这个工作交给研发了,那怎么让研发在这个过程中,把变更这件事情做得更有效率和更没有感觉,是阿里巴巴现在追求的一个重点。这个重点我们认为,智能化是可以发挥巨大的帮助的。上面讲的第一个案例是讲的文件分发过程当中的智能的流控。比如一次发布要一个小时,那意味着多数研发是需要去盯一个小时的,他虽然不一定要一直看着,但是到发完之后是要去看一下,这挺耗精力的。

另外一个方向是现在业界很火的无人值守,怎么做到在发布过程中,对于研发来讲最好是无感,我制定了在某天发,只要测试通过了我就可以自动完成这个过程,有问题稍微控制一下就好了,没有问题就当这件事情没发生。这对于有众多研发团队,或者当然,如果你有运维团队在做这件事情,对运维团队来讲就更有帮助了,意味着运维很多人可能就去掉了一大块活。

所以,变更这个领域,我们最希望做的是朝这个方向去发展。目前来看阿里巴巴的尝试,我们可以看到变更引发的故障比率是最高的,目前已经铺的这个领域中,可以下降30%因为变更引起的故障,拦截主要是用来拦截问题。

三.监控AI化

智能报警

这个领域现在是AI进入运维行业中最火的领域,所有公司都在做。第一个是阿里在做的,阿里也不例外,我们也同样在做。第一个是智能,大家比如说做运维的都知道,你写完了一个业务,要配监控报警的阈值的,比如说CPU到多少应该报警,然后响应时间到多少应该报警。阿里在尝试的一个方向是让你不要去配,阿里根据分析来决定什么情况下需要报警,这对于研发来讲有巨大的帮助。

异常检测直接影响到效率

第二点是异常检测,这是很多公司都在做的。异常检测之所以要做,最大的原因就是因为效率,如果不做,其实也ok,但是要投入非常大的人力。比如说交易跌了,那到底是,比如对于我们来讲,交易跌了,只要跌了就需要分析到底什么因素。而这个因素很有可能,最后你发现根本跟我们没关系,可能是外部原因,国家节日等等,各种各样的因素造成的。尤其是小规模的业务,比如我们的海外业务,波动非常大,如果一波动就认为是问题,这对于整个公司的效率来讲是巨大的影响。

所以我们认为,如果异常检测做得非常好,对我们的效率会有非常大的帮助。这张图是通常来讲,做异常检测,运维的数据都是时序化,根据时序有各种各样的算法,上面列了业界常用的算法。最左上角的算法是阿里巴巴自己研究的算法,从我们目前的测试情况来看,我们可以看到阿里巴巴自己研究的算法的准确率等等,得比业界高非常多。细节我不讲了,最重要的原因是这个东西马上会在某个会议上发表一篇论文,大家以后会看到。

四.稳定性是以效率为原则

故障修复要精准且快速

稳定性对我们来讲最重要的是效率问题。第一个是故障的修复,故障出现在越大的公司越大的规模越复杂的业务场景中,出现是不可避免的,一定会出现,关键是出现之后怎么尽快把故障修复掉。故障修复这个领域,阿里巴巴尝试了非常多的方案,也尝试了很多年。很多的案例都是,这个过程需要慢慢的积累,原因在于信任感地当故障出现的时候,我们都说公司的很多团队都处于高度紧张的状态,这个时候有一套系统抛出了,现在多数这种系统都是抛出三个决定,给你三个建议,然后你来选。有时候经验丰富的处理故障的人一看,你抛出的三个建议都不靠谱。当十个故障中,有八次,不用八次,如果有个四五次都是这样的,以后所有人都不会看这套系统了,太不靠谱了,还不如人来判断。这个系统难度非常高,需要整个公司坚定地朝这个方向走,并且更好的积累很多的数据。

故障修复,阿里现在只尝试了一些非常简单的案例,对于阿里来讲,比如一个机房出故障,因为整个阿里巴巴交易体系的架构是支持多点的,对于我们来讲如果在某种情况下,我们判断一个机房出故障,我们可以自动的做一些流量的切换等等。但阿里现在也认为,智能化在稳定性,尤其故障修复这种动作上,还是要非常小心,万一没事切出了问题,这影响更大。

用智能化做好故障定位

我们以前一直都认为定位这个问题不是个大问题,如果我能快速修复,定位,你慢慢定好了,定个两天我也无所谓。但是现在阿里特别重视的原因在于,故障定位损耗了我们非常多的人力,耗费了我们非常大的团队力量。所以我们认为需要有更智能化的方法,把故障定位出来,以助研发团队更专注投入在其他事情上。比如现在故障一出来,研发查了半天,一看,跟它都没有什么关系。所以就浪费了很多,这张图是我们现在在做的一套系统,从一个异常,那里标一二三四五,当有一个异常出来之后,第一步发现,第二步不断的分析,一直定位到最后到底是哪个地方出了问题,我们的目标是最后尽可能定位到代码层面的问题,或者是网络或者是基础设施等等。

五.边压边弹 做好规模化运维

目前对阿里来讲最重要的问题还是效率问题。比如说我们在每年准备双十一容量的时候,很多人都知道阿里有全链路压测,一个最重要的目的就是调整容量,怎么把一个机房的容量调整成比率是最合适的,比如说A应用可能是瓶颈,但是事实上如果搭配得好,A应用就不再是瓶颈。所以怎么样让一个固定机器数下做一个最好的搭配,我们以前是压一轮调整一下,再压一轮再调整一下,这非常耗费一堆人通宵的精力。我们认为这个过程需要提升,现在改成非常简单的模式,流量过来以后不断的自动调整容量比例,我们会有一个所谓边压边弹,一边压测一边调整比例。相信很多运维同学都干过这个事情,因为业务方给你一个指标,你是要算的,而且很难算的很精准。边压边弹意味着你不需要算得很精准,粗略算一个数就可以了,后面靠这套系统自动给你调平衡。

未来运维领域需要突破的防线

无人化 让梦想照进现实

我认为现在运维这个领域中最大的挑战仍然是,能不能真正的走向无人化,整个过程中是完全没有人的。

从目前来看,要做到无人化最重要的是质量问题,质量做得不够好是没有办法无人化的。另外如果出问题了能不能自动修复等等,所以我们认为无人化对运维领域是最大的挑战,能不能把这个落地变成现实,奠定了智能化的基础。如果说智能化所有的动作要人介入,那基本就不用做了。

智能化 带来效率上的质变

在智能化这一点上,第一点是有效性的问题,如果这个智能表现得比人的智力还差一些,这个慢慢就没有人相信这个东西了。所以怎么样把有效性提升上来,另外最重要的是要看到智能化给运维领域带来效率上的质变。智能化投入非常大,要做大量的收集做大量的分析。所以最好带来的是质变而不只是量变,如果只是量变可能投入都收不回来。对于所有公司而言,更少的人更低的成本是非常重要的。人最好投入在一些更重要的研发等等事情上。

原文发布时间为:2017-11-23

本文作者:毕玄

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PHP高性能分布式应用服务器框架-SwooleDistrib

发表于 2017-11-27

选择SD框架助力企业开发

今年年底历时2年多的迭代,这是SD框架硕果满满的一年,通过不断的迭代和改进SD框架已经在圈内有良好的口碑,不少新生的框架借鉴了SD的设计思想,SD框架也被不少创业型公司和大型企业使用。

SD框架到底是什么技术

SD框架全称SwooleDistributed,从名称上看一个是Swoole一个是Distributed,他是基于Swoole扩展的可以分布式部署的应用服务器框架。
借助于PHP的高效开发环境,Swoole的高性能异步网络通信引擎,以及其他的高可用的扩展和工具,SD框架提供给广大开发者一个稳定的高效的而且功能强大的应用服务器框架。

入门成本

老实的说相对比目前热门的FPM框架来说,SD的入门成本相对还是比较高的,因为设计理念不同以及和传统PHP-FPM环境完全不同的运行环境,对于长时间使用LAMP(LANP)技术的开发人员来说会有一段时间的适应期,如果开发应用简单涉及到的系统复杂度低,那么SD上手还是比较容易,根据简单的例子和文档几乎立即就能开启SD的探索之旅,但是如果开发的是复杂的应用那么SD包含的众多组件还是需要你慢慢熟悉上手的。

SD框架到底包含哪些强大的功能呢

我们这里列举下SD提供的各种各样的功能以及模块组件

  • 混合协议
    SD框架支持长连接协议TCP,WebSocket,短连接协议HTTP,以及UDP。
    通过配置开放不同的端口开发者可以轻松管理不同的协议,并且可以共用一套业务代码,当然你可以通过智能路由进行代码的隔离。
    长连接可以配置不同的数据传输协议,比如二进制协议文本协议等等,通过框架提供的封装器解包器接口可以自定义各种各种的协议封装,并且各种协议之间可以自动转换,比如你通过广播发送一个信息,该信息流向不同客户端,客户端间采用不同协议,那么框架会根据不同的端口自动转换不同的协议封装。
    你也可以通过Http给所有长连接客户端发送推送消息,类似这种混合协议协作的业务在SD框架上会异常简单。
  • MVC以及智能路由
    框架的设计是MVC架构,其中每一个层级都可以继续划分子层级,开发者可以将Controller继续分层通过不同文件夹进行管理,也可以将Model进行细分,划分为业务层和数据层,这都看开发者自身的系统设计。智能路由将处理解包器解包后的数据,负责将这些数据传递到Controller层。
  • 中间件
    SD框架还提供了中间件,中间件可以对流入的数据进行处理,比如清理异常数据,修改数据,流量统计,搜集日志等功能。中间件可以设置多个,他们和端口进行绑定。
  • 对象池
    SD框架内大多数的对象都使用了对象池技术,对象池技术有利于系统内存的稳定,减少GC的次数,提高系统的运行效率,事实证明对象池对系统稳定做出了极大的贡献,开发者也可以使用这一套对象池技术,增加对对象的复用,减少GC和NEW的频率,对系统毛刺现象和内存泄露方面都有很大的稳定性提升。
  • 异步客户端以及连接池
    Mysql,Redis,Http客户端,Tcp客户端,等等其他更为复杂的客户端,在SD框架中均为异步的模式,异步解决了系统整体的并发能力,但异步客户端需要提供连接池维持,SD框架提供了连接池,开发者不需要自己管理连接池,只需要使用即可。
  • 协程
    异步事件回调解决的是并发性能,但造成的是业务代码的混乱。SD框架提供了协程解决了这一问题,通过yield关键字提供对异步的同步写法,消除了业务书写上的大量回调嵌套,你可以通过yield+同步的写法实现异步的性能。
    协程提供了一整套完整的体系,包括超时,异常,休眠,多路选择,以及创建用户协程等等功能。
  • 定时任务
    顾名思义定时执行的任务。
  • 任务投递
    支持将耗时任务投递到Task进程。
  • 自动Reload
    可以开启框架的自动Reload功能,这样代码修改会被立即响应。

上面描述的都是一些基础功能,大家开发应用时经常用到的,那么下面则是一些高级功能。

  • 集群以及微服务
    框架提供集群部署,通过开启集群开关,部署Consul工具服务器,我们就可以开启集群之旅,框架中消息功能都是支持集群环境的。通过暴露API,监听API,我们可以实现微服务,微服务中我们又提供了健康监控,熔断,超时,负载均衡,请求迁移等等功能。
    集群采用的是对等网络,没有中间节点,没有单点隐患,设计理念如下图所示。

image

image

  • 订阅与发布
    SD提供的订阅发布功能也是支持集群环境,并且它严格的按照MQTT所定义的订阅发布规范,并且实现了所有的功能。这恐怕是最好最优秀的订阅发布功能了。
  • 事件派发
    跨进程跨服务器的事件派发功能,很多SD框架的基础设施都是基于这个搭建的。
  • 用户进程管理以及进程间RPC
    SD框架重新封装了用户进程,开发者可以启动自己的用户进程,用户进程可以是异步的也可以是同步的,也是支持各种连接池和协程,用户进程的用处很多,同样框架也支持用户进程和Worker进程间互相RPC调用。
  • 集群下的定时任务
    通过Consul可以设置定时任务,并且会同步到集群所有的服务器上去执行,集群服务器会选举出一个Leader,可以通过获取是否是Leader来决定这个任务是否被执行。
  • Context上下文
    这个是在消息处理整个流程中被共享的上下文,很实用,很方便。

接下来介绍的是SD特色组件

  • 异步AMQP客户端以及分布式任务系统
    消息队列协议AMQP,框架提供了一个支持AMQP协议的异步客户端,可以和RabbitMQ联动,通过框架提供的分布式任务组件,可以搭建分布式任务系统。
  • 异步MQTT客户端
    异步的MQTT客户端可以和MQTT服务实现订阅与发布
  • MQTT简易集群服务器
    支持QOS0级别的简易MQTT服务器,支持集群部署。
  • 服务器监控系统
    提供了一个服务器监控后台,可以对集群进行监控,也可以监控某一台服务器的具体运行状况。
    下面是一些截图

SD框架远远不止现在

SD框架一直在高速发展中,有更多开发者的参与才会有更好的未来。
附带SD框架的文档以及官网
官网
文档
GitHub
如果你喜欢,请打个星星支持下~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

数据库高可用的定义和度量

发表于 2017-11-27

作者:徐文韬

什么是高可用

高可用是系统的一个特性,保证系统能在足够长的时间内提供指定程度的服务等级。再细化一下,可以说是在有限的故障条件下,提供一定级别的稳定服务。

在传统领域,SLA用于在商业上定义系统的高可用。

SLA全称是service level agreement,在网络服务供应商领域被广泛使用,约定了最小带宽,同时服务客户数,最大故障时间等等一系列指标。在软件领域,最广泛使用的指标是平均服务时间。

见下图


因为软件领域的各项指标不好度量,很难约束,因此其他指标也很少提到。但可以想像,作为高可用的系统,不止要有达标的故障时间,同时要保证在服务时间达到用户可接受的服务质量,对于数据库而言,类似tps,事务平均时延,99%时延等。

各家SLA展播


从这几家对比看,AWS是最强的,阿里也差不多了,腾讯云是相对较差的,看一下服务条款的完善程度就能明显地感受到。

评价高可用的标准

评价系统高可用,可以有几个维度:

  1. 有限故障下数据是否丢失,系统的服务等级降低幅度是否合理;
  2. 高压力下系统的服务等级;
  3. 服务变更下系统的服务等级;

有一个关于故障条件下系统表现较好的分级,见下表:


摘自《来自 Google 的高可用架构理念与实践》

数据库系统的一些度量方法

数据持久度

数据库系统可以通过副本备份等方式有效提高数据持久度,抵御磁盘损坏等故障造成数据丢失的风险。

当然随着现在分布式存储的发展,持久度已经很少有人关心了,但是对于直接使用磁盘的情况,这仍然是一个需要考虑的问题。

平均服务时间

对于计算服务可用时间,引入3个来自工业界的概念:

  1. MTBF (Mean Time Between Failures) =平均故障間隔時間
  2. MTTF (Mean Time To Failure) =平均故障時間
  3. MTTR (Mean Time To Repair) =平均修復時間

高可用时间=MTBF/(MTBF+MTTF)

显然,这里存在执行上的问题,假设tcp超时时间是2min,那么低于两分钟是很难确定到底是系统故障还仅仅是软件处理较慢。或者软件由于资源(比如IO)受限被卡住,这是客户也是很难判断是否发生了故障。

对于系统管理员来说,同样存在类似的问题,心跳检测是最常见的监控手段,但是心跳时间也很难设置太短,这是受网络条件限制的,常常,故障的发现就是以分钟计算的。

RTO/RPO

RTO和RPO是传统数据库领域常见的两个衡量高可用的指标。

  1. RTO(Recovery time objective):故障恢复耗时
  2. RPO(Recovery point objective):恢复后数据对应的时间点,即丢失的数据量转换为时间

举个简单的例子,数据库同城同步备机,故障后RPO必然是0,tikv一般情况下RPO也是0。RTO也是秒级的,对于不同的故障,结果也不同

请求成功率

对于分布式系统来说,从系统层面考察平均服务时间的意义并不是很大,对于很多分布式系统来说,单机的故障并不能影响系统整体稳定的继续运行,从这个角度来说,系统可用性可以说是100%的。这时,计算请求的成功数可能更适合这样的系统,如下:

可用性=成功请求数/总请求数

当然这种指标更方便观察系统的内部错误,对于事务回滚这种行为,并不能认为是失败的请求。这是和业务行为及事务语义相关的。

长稳

上面提及SLA,也提到了,其实在传统领域,不止可服务时间受人关注,服务质量指标(SLO)同样受人关注。

大家都知道木桶原理,数据库做为基础软件,既是吞吐没有下降,一时的性能抖动可能导致业务软件的性能大幅下降。

衡量数据库服务质量通常有几个指标:

  1. 吞吐量,对于数据库系统,一般是qps,或者tps;
  2. 时延,关于时延,一般有如下几个指标, 平均时延,95%时延,99%时延,最大时延;
  3. 回滚率

制定合理的高可用目标

不客气的说,对于绝大部分系统,在正常故障下,2个9到3个9已经足够用了,不考虑系统变更,这也是一个很容易达到的指标。

而提升一个9,系统设计和实现的复杂度都要提升很多,所得未必偿所失。

打个比方,我们看到阿里rds的SLA是99.95%,而且是按月结算的,那么每个月允许的故障时间大概是4min,加上1min的不计时间,算5min,严格来说,这5min包含故障发现和故障处理。可以想像,如果是人来处理,5min都未必能及时登录到系统上。必然是全自动的故障处理,这个要求对系统的自动化故障处理能力的要求就非常高了。很多大型互联网公司也未必有这个开发能力,当然,也没有这个必要。

不过只要不发生大规模的故障,赔100倍的时间,对阿里也不算什么。不按客户损失赔偿,都是玩笑罢了。

参考资料

  1. 《SRE: google运维解密》
  2. 来自 Google 的高可用架构理念与实践
  3. 关于SLA,你到底知多少?
  4. 云服务器(ECS)服务等级协议(SLA)
  5. 腾讯云服务SLA
  6. 《the tail at scale》

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Laravel 中的简单工厂和抽象工厂

发表于 2017-11-27

设计模式之工厂模式分为两种:简单工厂和抽象工厂模式,工厂模式属于“创建”型的设计模式,顾名思义用来实例化对象,调用者不需要关心对象的具体位置和实现,工厂模式隐藏了背后的细节,从而实现了解耦的目的。

在大多数面向对象的语言中,实例化对象基本上都是 new ClassA() 这样创建实例,有两个原因不建议这么做:1.上层使用者没有和对象的创建过程隔离,导致代码耦合度高 2.创建对象实例的过程可能比较复杂,比如一些前置的过程。所以把这个创建过程交给工厂方法,调用者不用关心实例是怎么创建的。

工厂是生产产品的,这里产品指的就是具体的实例。简单工厂和抽象工厂设计模式都是为了解决类的实例化,避免 new XX()带来的代码耦合。

简单工厂模式

简单工厂模式可以通过一个工厂方法,或者说一个函数,比如传入一个参数,工厂方法根据这个参数返回具体类的实例。它相当于多了一层封装,你只要关心需要什么实例,它就返回什么实例,而不用直接实例化,实例化的过程交给了工厂方法。

Laravel 中一个数据库连接的工厂类:

1
复制代码vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php
1
复制代码protected function createConnection($driver, $connection, $database, $prefix = '', array $config = []){    if ($this->container->bound($key = "db.connection.{$driver}")) {        return $this->container->make($key, [$connection, $database, $prefix, $config]);    }    switch ($driver) {        case 'mysql':            return new MySqlConnection($connection, $database, $prefix, $config);        case 'pgsql':            return new PostgresConnection($connection, $database, $prefix, $config);        case 'sqlite':            return new SQLiteConnection($connection, $database, $prefix, $config);        case 'sqlsrv':            return new SqlServerConnection($connection, $database, $prefix, $config);    }    throw new InvalidArgumentException("Unsupported driver [$driver]");}

这里根据配置文件数据库驱动的配置,返回对应数据库的连接对象的实例。

简单工厂模式符合 单一职责原则 ,也就是每个类只做一件事情,但不符合 开放封闭原则 ,每次增加和减少所需实例都需要改动代码,不能做到很好的扩展。

抽象工厂模式

抽象工厂模式在简单工厂模式基础上,多了一层抽象工厂接口,它的思想是,假如有多个工厂生产同样的产品,他们有同样的行为,那么把这些产品的创建行为抽象出来,每个工厂实现这个抽象接口。

适用场景

有多个工厂,生产同一类型的产品,如下所示:

工厂1

产品1 产品2 产品3 工厂2

产品1 产品2 产品3 以 Mac 系统和 Windows 系统为例,都有创建 button 和创建 border 功能,这两个工厂都可以生产 button 和 border,那么把创建这两个产品的方法抽象出来,这个抽象出来的方法叫做抽象工厂类,两个工厂都要实现这个抽象类。

PHP 抽象工厂代码示例:

1
复制代码abstract class AbstractFactory { abstract public function CreateButton(); abstract public function CreateBorder();}class MacFactory extends AbstractFactory{ public function CreateButton() { return new MacButton(); } public function CreateBorder() { return new MacBorder(); }}class WinFactory extends AbstractFactory{ public function CreateButton() { return new WinButton(); } public function CreateBorder() { return new WinBorder(); }}class Button{}class Border{}class MacButton extends Button{ function __construct() { echo 'MacButton is created' . "\n"; }}class MacBorder extends Border{ function __construct() { echo 'MacBorder is created' . "\n"; }}class WinButton extends Button{ function __construct() { echo 'WinButton is created' . "\n"; }}class WinBorder extends Border{ function __construct() { echo 'WinBorder is created' . "\n"; }}?>

从工厂取出实例:

1
复制代码<?$type = 'Mac'; //value by user.if(!in_array($type, array('Win','Mac')))    die('Type Error');$factoryClass = $type.'Factory';$factory=new $factoryClass;$factory->CreateButton();$factory->CreateBorder();?>

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【英】 使用 helmet 库来保护你的 Express 网

发表于 2017-11-27

Express is a great way to build a web server using Node.js. It’s easy to get started with and allows you to configure and extend it easily thanks to its concept of middleware.
While there are a variety of frameworks to create web applications in Node.js, my first choice is always Express. However, out of the box Express
doesn’t adhere to all security best practices. Let’s look at how we can use modules like helmet to improve the security of an application.

Set Up

Before we get started make sure you have Node.js and npm (or yarn) installed. You can find the download and installation instructions on the Node.js website.

We’ll work on a new project but you can also apply these features to your existing project.

Start a new project in your command line by running:

1
2
3
复制代码mkdir secure-express-demo
cd secure-express-demo
npm init -y

Install the Express module by running:

1
复制代码npm install express --save

Create a new file called index.js in the secure-express-demo folder and add the following lines:

1
2
3
4
5
6
7
8
9
10
11
复制代码const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
 
app.get('/', (req, res) => {
  res.send(`<h1>Hello World</h1>`);
});
 
app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});

Save the file and let’s see if everything is working. Start the server by running:

1
复制代码node index.js

Navigate to http://localhost:3000 and you should be greeted with Hello World.

hello-world.png

Inspecting the Headers

giphy.gif

We’ll start by adding and removing a few HTTP headers that will help improve our security. To inspect these headers you can use a tool like curl by running:

1
复制代码curl http://localhost:3000 --include

The --include flag makes sure to print out the HTTP headers of the response. If you don’t have curl installed you can also use your favorite browser developer tools and the networking pane.

At the moment you should see the following HTTP headers being transmitted in the response:

1
2
3
4
5
6
7
复制代码HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:36:10 GMT
Connection: keep-alive

One header that you should keep an eye on is X-Powered-By. Generally speaking headers that start with X- are non-standard headers. This one gives away which framework you are using. For an attacker, this cuts the attack space
down for them as they can concentrate on the known vulnerabilities in that framework.

Putting the helmet on

giphy.gif

Let’s see what happens if we start using helmet. Install helmet by running:

1
复制代码npm install helmet --save

Now add the helmet middleware to your application. Modify the index.js accordingly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码const express = require('express');
const helmet = require('helmet');
const PORT = process.env.PORT || 3000;
const app = express();
 
app.use(helmet());
 
app.get('/', (req, res) => {
  res.send(`<h1>Hello World</h1>`);
});
 
app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});

This is using the default configuration of helmet. Let’s take a look at what it actually does. Restart the server and inspect the HTTP headers again by running:

1
复制代码curl http://localhost:3000 --inspect

The new headers should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码HTTP/1.1 200 OK
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:50:42 GMT
Connection: keep-alive

The X-Powered-By header is gone, which is a good start, but now we have a bunch of new headers. What are they all doing?

X-DNS-Prefetch-Control

This header isn’t much of an added security benefit as much as an added privacy benefit. By setting the value to off it turns off the DNS lookups that the browser does for URLs in the page. The DNS lookups are done for performance improvements
and according to MDN they can improve the performance of loading images by 5% or more. However this look up can make it appear like the user visited
things they never visited.

The default for this is off but if you care about the added performance benefit you can enable it by passing { dnsPrefetchControl: { allow: true }} to the helmet() call.

X-Frame-Options

X-Frame-Options allows you to control whether the page can be loaded in a frame like <frame/> <iframe/> or <object/>. Unless you really need to frame your page you should just disable
it entirely by passing the option to the helmet() call:

1
2
3
4
5
复制代码app.use(helmet({
  frameguard: {
    action: 'deny'
  }
}));

X-Frame-Options is supported by all modern browsers. It can however also be controlled via a Content Security Policy as we’ll see later.

Strict-Transport-Security

This is also known as HSTS (HTTP Strict Transport Security) and is used to ensure that there is no protocol downgrade when you are using HTTPS. If a user visited the website once on HTTPS it makes sure that the browser will not allow any future communication
via HTTP. This feature is helpful to avoid Man-In-The-Middle attacks.

You might have seen this feature in action if you tried to access a page like https://google.com while being on a public WiFi with a captive portal. The WiFi is trying to redirect you to their captive portal but since you visited google.com before via HTTPS and it set the Strict-Transport-Security header, the browser will block this redirect.

You can read more about it on the MDN page or the OWASP wiki page.

X-Download-Options

This header is only protecting you from an old IE security vulnerability. Basically if you host untrusted HTML files for download, users were able open these directly (rather than saving them to disk first) and they would execute in the context of
your app. This header ensures that users download files before they open them so that they can’t execute in your app’s security context.

You can find more information about this on the helmet page and this MSDN blog post.

X-Content-Type-Options

Some browsers perform “MIME sniffing” meaning that rather than loading things based on the Content-Type that the server will send, the browser will try to determine the content type based on the file content and execute it based on that.

Say your page offers a way to upload pictures and an attacker uploads some HTML instead, if the browser performs MIME sniffing, it could execute the code as HTML and the attacker would have performed a successful XSS attack.

By setting this header to nosniff we are disabling this MIME sniffing.

X-XSS-Protection

This header is used to turn on basic XSS protections in the user’s browser. It can’t avoid every XSS opportunity but it can determine basic ones. For example, if it detects that the query string contains something like a script tag, the browser could
block the execution of the same code in the page knowing that it’s likely an XSS attack. This header can have three different values. 0, 1 and 1; mode=block. If you want to learn more about which mode
you should choose, make sure to check out this blog post about X-XSS-Protection and it’s potential dangers.

Upgrading your helmet

These are just the default settings that helmet provides. Additionally it allows you to configure headers like the Expect-CT,
Public-Key-Pins, Cache-Control and Referrer-Policy.
You can find more about the respective headers and how to configure them in the helmet documentation.

Protecting your page from unwanted content

giphy.gif

Cross-site scripting is a constant threat to web applications. If an attacker can inject and run code in your application it can be a nightmare for you and your users. One solution that tries to stop the risk of unwanted code being executed on
your page is a Content Security Policy or CSP.

A CSP allows you to specify a set of rules that define the origins from which your page can load various resources. Any resource that violates these rules will be blocked by the browser automatically.

You can specify these rules via the Content-Security-Policy HTTP header but you can also set them in an HTML meta tag if you don’t have a way to modify the HTTP headers.

This is what such a header might look like:

1
2
3
4
5
6
7
8
复制代码Content-Security-Policy: default-src 'none';
    script-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
    style-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
    img-src 'self';
    font-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==' fonts.gstatic.com;
    object-src 'none';
    block-all-mixed-content;
    frame-ancestors 'none';

In this example you can see that for fonts we only allow them if they are from our own domain or from fonts.gstatic.com aka Google Fonts. Images we’ll only allow from our own domain and not any external domain. For scripts and styles, however, we
only allow them if they have a certain nonce. This means we don’t even care about the origin but we care about a very specific value. This nonce has to be specified in a way like below:

1
2
复制代码<script src="myscript.js" nonce="XQY ZwBUm/WV9iQ3PwARLw=="></script>
<link rel="stylesheet" href="mystyles.css" nonce="XQY ZwBUm/WV9iQ3PwARLw==" />

When the browser receives the HTML it will make sure to clear out the nonce for security purposes, so other scripts won’t be able to access them to add additional unwanted scripts.

We are also disabling any mixed content meaning that HTTP content inside our HTTPS page is not permitted. Other things that we don’t permit are any <object /> elements and anything that isn’t an image, stylesheet
or script since the default-src is mapping to none. Additionally we are disabling iframing with the frame-ancestors value.

We could write these headers manually but luckily there are plenty of solutions to use CSP in Express. helmet comes with support for CSP but in the case of nonce it
forces you to generate them yourself. I’m personally using a module called express-csp-header for this.

Install express-csp-header by running:

1
复制代码npm install express-csp-header --save

Consume and enable CSP by adding the following lines to your index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
复制代码const express = require('express');
const helmet = require('helmet');
const csp = require('express-csp-header');
 
const PORT = process.env.PORT || 3000;
const app = express();
 
const cspMiddleware = csp({
  policies: {
    'default-src': [csp.NONE],
    'script-src': [csp.NONCE],
    'style-src': [csp.NONCE],
    'img-src': [csp.SELF],
    'font-src': [csp.NONCE, 'fonts.gstatic.com'],
    'object-src': [csp.NONE],
    'block-all-mixed-content': true,
    'frame-ancestors': [csp.NONE]
  }
});
 
app.use(helmet());
app.use(cspMiddleware);
 
app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <style nonce=${req.nonce}>
      .blue { background: cornflowerblue; color: white; }
    </style>
    <p class="blue">This should have a blue background because of the loaded styles</p>
    <style>
      .red { background: maroon; color: white; }
    </style>
    <p class="red">This should not have a red background, the styles are not loaded because of the missing nonce.</p>
  `);
});
 
app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});

Restart your server and navigate to http://localhost:3000 you should see one paragraph with a blue background because the styles have been loaded. The other paragraph is not styled because of the missing nonce.

csp-output.png

The CSP header allows you also to specify a report URL that a violation report should be sent to and you can even execute CSP in report-only mode if you want to gather some data first before enforcing it strictly.

You can find more information about CSP on the respective MDN page and the current browser support on the caniuse.com page but
overall all major browsers support it.

Summary

giphy.gif

Unfortunately there is no silver bullet in security and new vulnerabilities constantly pop up but setting these HTTP headers in your web applications is easy to do and significantly improve your app’s security. If you want to do a quick check of how
your HTTP headers hold up to security best practices, make sure to check out securityheaders.io.

If you want to learn more about web security best practices, make sure to check out the Open Web Applications Security Project aka. OWASP. It covers a wide selection of topics and links to additional
helpful resources.

If you have any questions or any other helpful tools to improve the security of your Node.js web applications, feel free to ping me:

  • Twitter: @dkundel
  • Email: dkundel@twilio.com
  • GitHub: dkundel

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

PHP 的 错误/异常 处理总结

发表于 2017-11-27

错误

这里说的错误,可能是由 语法解析、运行时等各种原因产生的信息引起的

常见的错误类型

运行时错误

  • E_ERROR - 致命错误
+ 定义:致命的运行时错误
+ 后果:脚本终止不再继续运行
  • E_WARNING - 警告
+ 定义:运行时警告 (非致命错误)
+ 后果:给出提示信息,但是脚本不会终止运行
  • E_NOTICE - 通知
+ 定义:运行时通知
+ 结果:给出通知信息,但是脚本不会终止运行

其他类型错误

  • 编译时错误
    eg. E_PARSE E_COMPILE_ERROR E_COMPILE_WARNING …
  • 用户产生的信息
    eg. E_USER_WARNING E_USER_ERROR E_USER_NOTICE
  • … 等

具体如下图:

参考:PHP-错误处理-预定义常量

错误处理

这里只针对运行时错误进行处理,其他(如:语法错误 Zend 引擎产生的错误 等)不在讨论范围内。

设置一般错误的处理函数

核心方法:set_error_handler

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
复制代码<?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");

/**
* 回调函数原型 : bool handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] )
*/
set_error_handler(function ($errno, $errstr) {
$err_type = '';
$return = true;
if (E_WARNING === $errno) {
$err_type = 'warning';
$return = false;
} elseif (E_NOTICE === $errno) {
$err_type = 'notice';
} elseif (E_ERROR === $errno) {
$err_type = 'error';
}
echo sprintf("This is error callback, err_type:%s, err_no:%d, err_str:%s \n", $err_type, $errno, $errstr);
return $return;
});

function sayHere($line)
{
echo sprintf("I am here.Line:%d \n", $line);
}

/* warning */
function test($a) {}
test();
sayHere(__LINE__);

/* notice */
echo $notice_msg;
sayHere(__LINE__);

/* fatal */
$i = '';
while(1) {
$i .= 'a';
}

sayHere(__LINE__);

结果如下:

这里我们看到,set_error_handler 只对 E_WARNING E_NOTICE 进行了捕获,并且当回调函数遇到
E_NOTICE 返回 true 的时候,我们看到底层对标准错误的输出,但是遇到 E_WARNING 返回 false,我们并没有看到底层对标准错误的输出。

总结,来自于官方手册:

  1. set_error_handler 第二个参数指定的错误类型都会绕过 PHP 标准错误处理程序
  2. 以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING

备注:此方法可有针对性的对服务产生的消息进行收集,处理。比如:在框架初始化时,注册一个定制化的错误回调。

那致命错误有没有办法处理呢?接着看。

设置致命错误处理函数

我们知道致命错误会引起:脚本终止不再继续运行。
那么,我们就可以利用 register_shutdown_function 方法做一些处理。
作用:注册一个会在php中止时执行的函数

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
复制代码<?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");

/**
* 回调函数原型 : 参数由 register_shutdown_function 的参数决定
*/
register_shutdown_function(function () {
echo "This will shutdown. \n";
});

function sayHere($line)
{
echo sprintf("I am here.Line:%d \n", $line);
}

function test($a)
{
return;
}

/* warning */
test();
sayHere(__LINE__);

/* notice */
echo $notice_msg;
sayHere(__LINE__);

/* fatal */
$i = '';
while(1) {
$i .= 'a';
}
sayHere(__LINE__);

结果如下:

如前所述,发生致命错误,进程退出,但是中止之前执行了我们注册的回调函数。


异常

说明:我们这里指用户自定义的异常。

try-catch 捕获

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码<?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");

class UserException extends \Exception
{
}

try {
throw new \UserException('This is exception');
} catch (\UserException $e) {
echo 'UserException:' . $e->getMessage() . PHP_EOL;
} catch (\Exception $e) {
echo 'Exception:' . $e->getMessage() . PHP_EOL;
} finally {
echo 'here is finally' . PHP_EOL;
}

结果如下:

1
2
3
复制代码➜  answer git:(master) ✗ php exception.php
UserException:This is exception
here is finally

这是常见的捕获,不做过多说明,参见:异常处理

未捕获的异常

那么,如有抛出去的异常未被 catch,怎么办?
我们先看一下,未被 catch 会怎么样:

1
2
3
4
5
6
7
复制代码<?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");

throw new \Exception('I am an exception');

echo 'I am here' . PHP_EOL;

结果如下:

1
2
3
4
5
6
复制代码➜  answer git:(master) ✗ php throw.php

Fatal error: Uncaught exception 'Exception' with message 'I am an exception' in /Users/javin/github/answer/throw.php:5
Stack trace:
#0 {main}
thrown in /Users/javin/github/answer/throw.php on line 5

会出现 致命错误,脚本中断,那么,我们当然可以用上边所说的 register_shutdown_function 来处理。
这样的话,就没有合其他致命错误区分了,那么,有没有专门处理未捕获的异常呢?
答案是有的,它就是:set_exception_handler

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码<?php
/* 让错误信息在标准输出可见 */
ini_set("display_errors","On");

/**
* 回调函数签名:void handler ( Exception $ex )
*/
set_exception_handler(function ($e) {
echo sprintf("This is exception, msg:%s\n", $e->getMessage());
});

throw new \Exception('I am an exception');
echo 'I am here' . PHP_EOL;

结果如下:

1
2
复制代码➜  answer git:(master) ✗ php throw.php
This is exception, msg:I am an exception

结论:set_exception_handler 可以对未捕获的异常进行处理,但是脚本仍然会因为致命错误而中断。


结尾

本文对 异常处理 做了简要的总结,其中涉及到三个核心方法 set_error_handler register_shutdown_function set_exception_handler,其详细说明,请参见 官方手册 。
同时 PHP-7 中也有一些新的特性,比如: Error 类

参考:PHP 7 错误处理

最后,强烈建议开启编辑器的 语法检查 功能,不管是 IDE,还是 GUI 文本编辑器,还是 vim,这样可以避免很多不必要的错误。如果有使用版本控制,可以给对应的软件加上 语法检查 的钩子。

可以参考:

  • 我的 vim-配置
  • 自动化检测PHP语法和编程规范(Git pre-commit)

以上如有错误,请多多指正。如有遗漏,请多多补充。🙏

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【英】 使用 Rust 来写原生 Nodejs 模块

发表于 2017-11-27

TL:DR - Use Rust instead of C++ to write native Node.js modules!

RisingStack faced a shocking event last year: we reached the maximum speed that Node.js had to offer at the time, while our server costs went over the roof. To increase the performance of our application (and decrease our costs), we decided to
completely rewrite it, and migrate our system to a different infrastructure - which was a lot of work, needless to say.

I figured out later that we could have just implemented a native module instead!

Back then, we weren’t aware that there was a better method to solve our performance issue. Just a few weeks ago I found out that another option could have been available. That’s when I picked up Rust instead of C++ to implement a native module. I figured out that it is a great choice thanks to the safety and ease of use it provides.

In this Rust tutorial, I’m going to walk you through the steps of writing a modern, fast and safe native module.

The Problem with our Node.js Server Speed

Our issue began in late 2016 when we’ve been working on Trace, our Node.js monitoring product, which was recently merged with Keymetrics in October 2017.

Like every other tech startup at the time, we’ve been running our services on Heroku to spare some expenses on infrastructure costs and maintenance. We’ve been building a microservice architecture application, which meant that our services have
been communicating a lot over HTTP(S).

This is where the tricky part comes in: we wanted to communicate securely between the services, but Heroku did not offer private networking, so we had to implement our own solution. Therefore, we looked into a few solutions for
authentication, and the one we eventually settled with was http signatures.

To explain it briefly; http signatures are based on public-key cryptography. To create an http signature, you take all parts of a request: the URL, the body and the headers and you sign them with your private key. Then, you can give your public
key to those who would receive your signed requests so they can validate them.

Time passed by and we noticed that CPU utilization went over the roof in most of our http server processes. We suspected an obvious reason - if you’re doing crypto, it’s like that all the time.

However, after doing some serious profiling with the v8-profiler we figured out that it actually wasn’t the crypto! It was the URL parsing that took the most CPU time. Why? Because to do the authentication, we had to parse the URL to validate request signatures.

To solve this issue, we decided to leave Heroku (what we wanted to do for other reasons too), and create a Google Cloud infrastructure with Kubernetes & internal networking - instead of optimizing our URL parsing.

The reason for writing this story/tutorial is that just a few weeks ago I realized that we could have optimized URL parsing in an other way - by writing a native library with Rust.

Naive developer going native - the need for a Rust module

It shouldn’t be that hard to write native code, right?

Here at RisingStack, we’ve always said that we want to use the right tool for the job. To do so, we’re always doing research to create better software, including some on C++ native modules when necessary.

Shameless plug: I’ve written a blogpost about my learning journey on native Node.js modules too. Take a look!

Back then I thought that in most cases C++ is the right way to write fast and efficient software.. However, as now we have modern tooling at our disposal (in this example - Rust), we can use it to write more efficient, safe and fast code with
much less effort than it ever required.

Let’s get back to our initial problem: parsing an URL shouldn’t be that hard right? It contains a protocol, host, query parameters…

URL-parsing-protocol)
(Source the Node.js documentation)

That looks pretty complex. After reading through the URL standard I figured out that I don’t want to implement it myself, so I started to look for alternatives.

I thought that surely I’m not the only person who wants to parse URLs. Browsers probably have already solved this issue, so I checked out chromium’s solution: google-url. While
that implementation can be easily called from Node.js using the N-API, I have a few reasons not to do so:

  • Updates: when I just copy-paste some code from the internet I immediately get the feeling of danger. People have been doing it for a long time, and there are so many reasons it didn’t work out so well.. There is just no easy
    way of updating a huge block of code that is sitting in my repository.
  • Safety: a person with not so much C++ experience cannot validate that the code is right, but we’ll eventually have to run it on our servers. C++ has a steep learning curve, and it takes a long time to master it.
  • Security: we all heard about exploitable C++ code that is out there, which I’d rather avoid because I have no way to audit it myself. Using well maintained open-source modules gives me enough confidence to not worry about
    security.

So I’d much prefer a more approachable language, with an easy to use update mechanism and modern tooling: Rust!

A few words about Rust

Rust allows us to write fast and efficient code.

All of the Rust projects are managed with cargo - think about it as npm for Rust. Project dependencies can be installed with cargo, and there is a registry full of packages waiting for you to use.

I found a library which we can use in this example - rust-url, so shout out to the Servo team for their work.

We’re going to use Rust FFI too! We had already covered using Rust FFI with Node.js in a previous blogpost two years ago. Since then quite a lot has
changed in the Rust ecosystem.

We have a supposedly working library (rust-url), so let’s try to build it!

How do I build a Rust app?

After following instructions on rustup.rs, we can have a working rustc compiler, but all we should care about now is cargo. I don’t want to go into much detail about how it works,
so please check out our previous Rust blogpost if you’re interested.

Creating a new Rust Project

Creating a new Rust project is as simple as cargo new --lib <projectname>.

You can check out all of the code in my example repository github.com/peteyy/rust…

To use the Rust library that we have, we can just list it as a dependency in our Cargo.toml

1
2
3
4
5
6
7
8
复制代码
[package]
name = "ffi"
version = "1.0.0"
authors = ["Peter Czibik <p.czibik@gmail.com>"]

[dependencies]
url = "1.6"

There is no short (built in) form for adding a dependency as you do with npm install - you have to manually add it yourself. However, there is a crate called cargo edit that adds a similar functionality.

Rust FFI

To be able to use Rust modules from Node.js, we can use the FFI provided by Rust. FFI is a short-term for Foreign Function Interface. Foreign function interface (FFI) is a mechanism by which a program written in one programming language can call
routines or make use of services written in another.

To be able to link to our library we have to add two things to Cargo.toml

1
2
3
4
5
6
7
复制代码
[lib]
crate-type = ["dylib"]

[dependencies]
libc = "0.2"
url = "1.6"

We have to declare that our library is a dynamic library. A file ending with the extension .dylib is a dynamic library: it’s a library that’s loaded at runtime instead of at compile time.

We will also have to link our program against libc. libc is the standard library for the C programming language, as specified in the ANSI C standard.

The libc crate is a Rust library with native bindings to the types and functions commonly found on various systems, including libc. This allows us to use C types from our Rust code, which we will have to do if we’d like to accept
or return anything from our Rust functions. :)

Our code is fairly simple - I’m using the url and libc crate with the extern crate keyword. To expose this to the outer world through FFI, it is important to mark our function as pub extern.
Our function takes a c_char pointer which represents the String types coming from Node.js.

We need to mark our conversion as unsafe. A block of code that is prefixed with the unsafe keyword is used to permit calling unsafe functions or dereferencing raw pointers within a safe function.

Rust uses the Option<T> type to represent a value that can be empty. Think of it as a value that can be null or undefined in your JavaScript. You can (and should) explicitly check every time you try
to access a value that can be null. There are a few ways to address this in Rust, but this time I’m going with the simplest method: unwrap which
will simply throw an error (panic in Rust terms) if the value is not present.

When the URL parsing is done, we have to convert it to a CString, that can be passed back to JavaScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码
extern crate libc;
extern crate url;

use std::ffi::{CStr,CString};
use url::{Url};

#[no_mangle]
pub extern "C" fn get_query (arg1: *const libc::c_char) -> *const libc::c_char {

let s1 = unsafe { CStr::from_ptr(arg1) };

let str1 = s1.to_str().unwrap();

let parsed_url = Url::parse(
str1
).unwrap();

CString::new(parsed_url.query().unwrap().as_bytes()).unwrap().into_raw()
}

To build this Rust code, you can use cargo build --release command. Before compilation, make sure you add the url library to your list of dependencies in Cargo.toml for this project too!

We can use the ffi Node.js package to create a module that exposes the Rust code.

1
2
3
4
5
6
7
8
9
10
11
12
复制代码
const path = require('path');
const ffi = require('ffi');

const library_name = path.resolve(__dirname, './target/release/libffi');
const api = ffi.Library(library_name, {
get_query: ['string', ['string']]
});

module.exports = {
getQuery: api.get_query
};

The naming convention is lib*, where * is the name of your library, for the .dylib file that cargo build --release builds.

This is great; we have a working Rust code that we called from Node.js! It works, but you can already see that we had to do a bunch of conversion between the types, which can add a bit of an overhead to our function calls. There should be a much
better way to integrate our code with JavaScript.

Meet Neon

Rust bindings for writing safe and fast native Node.js modules.

Neon allows us to use JavaScript types in our Rust code. To create a new Neon project, we can use their own cli. Use npm install neon-cli --global to install it.

neon new <projectname> will create a new neon project with zero configuration.

With our neon project done, we can rewrite the code from above as the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码
#[macro_use]
extern crate neon;

extern crate url;

use url::{Url};
use neon::vm::{Call, JsResult};
use neon::js::{JsString, JsObject};

fn get_query(call: Call) -> JsResult<JsString> {
let scope = call.scope;
let url = call.arguments.require(scope, 0)?.check::<JsString>()?.value();

let parsed_url = Url::parse(
&url
).unwrap();

Ok(JsString::new(scope, parsed_url.query().unwrap()).unwrap())
}

register_module!(m, {
m.export("getQuery", get_query)
});

Those new types that we’re using on the top JsString, Call and JsResult are wrappers for JavaScript types that allows us to hook into the JavaScript VM and execute code on top of it. The Scope allows us to bind our new variables to existing JavaScript scopes, so our variables can be garbage collected.

This is much like writing native Node.js modules in C++ which I’ve explained in a previous blogpost.

Notice the #[macro_use] attribute that allows us to use the register_module! macro, which allows us to create modules just like in Node.js module.exports.

The only tricky part here is accessing arguments:

1
复制代码let url = call.arguments.require(scope, 0)?.check::<JsString>()?.value();

We have to accept all kinds of arguments (as any other JavaScript function does) so we cannot be sure if the function was called with single or multiple arguments. That is why we have to check for the first element’s existence.

Other than that change, we can get rid of most of the serialization and just use Js types directly.

Now let’s try to run them!

If you downloaded my example first, you have to go into the ffi folder and do a cargo build --release and then into the neon folder and (with previously globally installed neon-cli) run neon build.

If you’re ready, you can use Node.js to generate a new list of urls with the faker library.

Run the node generateUrls.js command which will place a urls.json file in your folder, what our tests will read and try to parse. When that is ready, you can run the “benchmarks” with node urlParser.js. If
everything was successful, you should see something like this:

Rust-Node-js-success-screen

This test was done with 100 URLs (randomly generated) and our app parsed them only once to give a result. If you’d like to benchmark parsing, increase the number (tryCount in urlParser.js) of URLs or the number of times (urlLength in urlGenerator.js).

You can see the winner in my benchmark is the Rust neon version, but as the length of the array increases, there will be more optimization V8 can do, and they will get closer. Eventually, it will surpass the Rust neon implementation.

Rust-node-js-benchmark

This was just a simple example, so of course, there is much to learn for us in this field,

We can further optimize this calculation in the future, potentially utilizing concurrency libraries provided by some crates like rayon.

Implementing Rust modules in Node.js

Hopefully, you’ve also learned something today about implementing Rust modules in Node.js along with me, and you can benefit from a new tool in your toolchain from now on. I wanted to demonstrate that while this is possible (and fun), it is not
a silver bullet that will solve all of the performance problems.

Just keep in mind that knowing Rust may come handy in certain situations.

If you have any questions or comments, let me know in the section below - I’ll be here to answer them!

Follow @RisingStack

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…929930931…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%