Tony Han

Happy hacking

Read this first

What’s SSD TRIM and why use it?

最近我们的一个 Mongo cluster 总是在周末的时候有大量的 IO 操作,并严重影响了 Mongo 的性能。然后发现问题出现的时候总是会执行一个 cron job – fstrim:

$ cat /etc/crontab | grep weekly
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
$ cat /etc/cron.weekly/fstrim
!/bin/sh
 trim all mounted file systems which support it
/sbin/fstrim --all || true

这是在 Ubuntu 16.04 上默认开启的(注意在 18.04 上变成了 systemd 来驱动,而不是 crontab),从 man 可以看到 fstrim 是用来清除文件系统上,特别是 SSD 不用的 block 的,并可以延长 SSD 的寿命。

通过 iostat 可以看到,fstrim 运行的时候,Mongo 的 IO write 数量增长了几千倍:

$ sudo iostat 3
Linux 4.4.0-1105-aws (mongo-03)     04/13/2020  _x86_64_    (64 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           3.27    0.00    0.58    0.18    0.00   95.97

Device:            tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
loop0             0.00         0.00         0.00          0          0
xvda              0.00
...

Continue reading →


从 D-state 造成的 high load 到 Erlang 内存分配调优

在 Tubi,我们有个电影/电视剧的 metadata 服务,叫 Content,是一个 Elixir(Erlang) 写的服务。当请求 Tubi 的时候,这个服务会负责把需要的 metadata,比如标题、描述、图片等返回给客户端,是一个比较关键的服务。最近线上因为客户端发送了过多的请求,导致服务器的 (normalized) load 很快地从 0.5 升高到 1.3,同时 latency 升高,最终通过扩容来解决。请求量增加导致 load 升高很正常,但不正常的是请求量只增加了30%,但 load 却增加了 160%,所以想搞清楚为什么。

重现线上问题

能够在 staging 环境复现,问题就等于解决了一半。我先在 staging 把相关的两个服务升级到了和 production 一样的机型,然后用 Vegeta 来产生一定的 load,用 Vegeta 的原因是可以产生固定 QPS 的 load,因为 staging 机器比 production 数量少,所以我按比例发送了 400 QPS 的请求。另外,因为 Content 支持一个 gRPC 接口请求多个 content 的数据,所以我也尽量让每个请求和 production 环境的平均值相同。

我先来模拟出问题前的情况

cat targets.txt | ./vegeta attack -rate=400 -max-body=0 > results.bin  -max-body=0 is used to avoid large logs

通过监控可以看到 load 达到了 0.5,p99 latency 差不多是 75ms,Erlang 的 run queue 也在正常水平,和我们线上的正常情况比较接近。

然后我逐步增加了 load,到 520 QPS

cat targets.txt | ./vegeta attack -rate=520 -max-body=0 > results.bin

然后发现 load 很快到了 1.3,latency 也到了 2s,Erlang 的 run...

Continue reading →


Elixir + gRPC: the road to production

原文发表在 Tubi 的 Medium 账号上,点击标题查看原文。

View →


k8s pod unhealthy 后不能正常重启 debug

之前遇到一个问题,当一个 pod 从 healthy 到 unhealthy 后,经常不能够正常启动。

比如 init delay 是 0,健康检查 period 是 10s,unhealthy threshold 是 3,那么应该在20s内启动即可,但实际发现不到 20s 就会被 terminate。通过观察 kubelet 的 log 发现,Liveness 的 probe 出现了2次就会被 kill:

Apr 19 02:28:32 ip-xxx kubelet[2449]: I0419 02:28:32.999475    2449 prober.go:111] Liveness probe for "xxx(21431e82-42fa-11e8-a986-02683fc2eb42):backend-xxx" failed (failure): Get http://ip:8080/status: dial tcp ip:8080: getsockopt: connection refused
Apr 19 02:28:44 ip-xxx kubelet[2449]: I0419 02:28:44.005553    2449 prober.go:111] Liveness probe for "xxx(21431e82-42fa-11e8-a986-02683fc2eb42):xxx" failed (failure): Get http://ip:8080/status: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
Apr 19 02:28:44 ip-xxx kubelet[2449]: I0419 02:28:44.310592    2449 kuberuntime_manager.go:550] Container "xxx" ({"docker"
...

Continue reading →


Kubernetes 源码解读 - 由一次 debug 学到的

在最近公司办的第一次 Open L 活动中,我们分享了为什么我们要用 Kubernetes,其中吸引我们的一方面就是 autoscaling,它能够根据 CPU 等指标动态调整 pod 的个数,以此提高机器的利用率。

但最近却发现它并不能按预期正常地工作,deployment 的 Horizontal Pod Autoscaler(HPA) 显示的 CPU 并不能反应实际情况,所以也就不能正常地对 pod 的数量进行调整。查了 log 又 Google 一番后,我们并没有发现根本原因和解决办法,试了一些可能的方案,比如升级 docker,也没有解决,于是我们决定借助 k8s 的相关源码来弄清问题的根由,也方便之后再出现其他问题时能够更快地解决。

(本文基于 Kubernetes v1.4.6 e569a27d02 和 heapster v1.2.0 eea8c965)

先看一下 HPA 的工作原理:
Horizontal_Pod_Autoscaling___Kubernetes.jpg

HPA 会被 controller manager 启动,不断从 heapster 抓取所有 pod 的 metrics,计算出平均值,再控制 deployment 伸缩。我们的问题很明显出在计算平均值的那前半段。

HPA 启动和运行

Kubernetes 那么多代码,从哪看起呢? 我们先查看了 controller manager 的 log,发现了关键的几行

W0429 12:42:11.182073       5 horizontal.go:105] Failed to reconcile neo-staging-web: failed to compute desired number of replicas based on CPU utilization for Deployment/backend/neo-staging-web: failed to get CPU utilization: failed to get CPU consumption and request: metrics obtained for 0/2 of pods
...

Continue reading →


我的 Elixir 2016

回顾自己 2016 年的技术方面,Elixir 应该最值得一说了。这一年,写了一些、参与了一些、看到了一些、想了一些,正好在这个时间点记录下来,算是对此的总结,也或许能从我的视角看到 Elixir 的一些发展。

开源项目

翻看今年的 GitHub,虽然也就 400 多个提交,不算多,但却是一直坚持在业余时间写代码的结果,主要就是 Elixir。除了代码量的收获,所有 Elixir 相关项目总共到达 200 多个 star,也算是对自己的一种鼓励。

ExChat

上半年还在继续开发 ExChat 项目,基本的群聊和私聊功能已经实现,但因为不擅长前端等各种原因,后来进展一直很慢,现在已经停止开发了。之后打算还是稍微维护一下,比如保持 Phoenix 版本更新,以及可以运行的状态,但目前没有增加新功能的计划了。除非有前端/客户端的同学感兴趣,让我只负责后端,倒是可以考虑重拾起来。

7 月份公司办了 Hack Week 活动,现在看来算是一个转折点,ExChat 从那之后就没再动过。当时我们用 Elixir 做后端,还拿了二等奖。

grpc-elixir

从那之后,我开始想,要用 Elixir 做点什么。继续做 ExChat 还是再拿 Phoenix 做个什么应用? 结果是可能对 Phoenix 越来越了解,但一方面,我并不希望 Phoenix 变成 Elixir 的 Rails,另一方面,做这种项目,业务逻辑会占很多时间,以我当前的精力恐怕是很难在业余时间维护好的。那就造轮子吧,什么轮子呢?写个数据库啥的,以我的水平暂时怕是写不出来的,就整个简单的吧。碰巧 Hack Week 的项目需要用 gRPC,当时因为没有 Elixir 的库,只能勉强用 Ruby 搭了个 proxy,把一个 gRPC 服务转成了 HTTP。于是就想着要做个 gRPC 的 Elixir 实现,毕竟随着微服务越来越流行,加上 Google 的推广,gRPC 应该会被越来越多的使用,如果 Elixir 没有这样的轮子的话,岂不是很被动。

于是我剩下几个月就一直在折腾...

Continue reading →


在 Elixir config 中使用 ENV 的一点技巧

看到了 Erlang Solution 的这篇文章,想到了这个话题。这篇文章虽然内容不多,但还是挺有用的。文中提到的编译时和运行时的区别,也是刚学 Elixir 搞不太清的问题。

而 Elixir 的 config 也并不是简单的启动前执行的代码,特别是在部署时。

有时我们可能会想在 config 里使用 ENV,比如:

config :my_app, api_key: System.get_env("API_KEY")

但直接这样写到 config 中是不行的。在部署时,比如通过 distillery 运行 mix release ,config 就会变成 sys.config,System.get_env(“API_KEY”) 已经被计算了,等运行的时候就不能动态得到 ENV 的实际值了。

有种做法就是像上边那篇文章中最后提到的方式,把 ENV 的获取逻辑放到函数里去做,这样就变成了运行时才会执行了:

 config.exs
config :my_app, api_key: {:env, "API_KEY"}

 my_app.ex
def api_key do
  get_env(Application.get_env(:my_app, :api_key))
end

def get_env({:env, key}), do: System.get_env(key)

Phoenix 的 config 支持从 ENV 中获取 port 就是这样处理的:

 https://github.com/phoenixframework/phoenix/blob/996a83a27d8ccdc7e0e3bdda9c21d537b19b2002/installer/templates/new/config/prod.exsL15
config :<%= app_name %>, <%= app_module %>.Endpoint,
  http: [:inet6, port: {:system, "PORT"}]
...

Continue reading →


写在流利说 Hack Week 之后

这周公司内部搞了为期一周的 Hackathon 活动,有十几支队伍参赛,很是热闹。

Hackathon 对我而言并不陌生,从大学起已经参加过三次 24 小时的,参与举办过一次 48 小时的。几乎每次参加 Hackathon 都会让我见识到很多牛人,也让我受到很大影响。

刚开始听说 Hackathon 就觉得很神奇、很让人热血沸腾,要在 24 小时完成构思、组队、开发、准备演示等全部的工作。粗想一下会觉得这怎么可能,但又让人按捺不住想去试一试的欲望。

第一次参加的景象还历历在目,它并不像现在很多的 Hackathon,有各种赞助商、高大上的场地,只是几个大学生发起的、面向大学生的,大概有二十个人参加,所有人都挤在一个可能还不到一百平米的住房里,待了一天时间,几乎都没怎么睡,就为做出一些东西。记得有几个师兄做了一个 web 上提交代码,然后返回编译结果的网站,对于当时基本不懂 web 开发的我,觉得这真是酷毙了。而我和另外一个朋友则用 C++ 写了一个 Windows 上类似泡泡堂的游戏。

除此之外,Hackathon 很有趣的另一点在于,因为很可能要和一些不认识的人组队,而大家的技术栈也不尽相同,所以经常需要快速学一个技术,然后马上做出点东西。虽然在时间本来就很紧张的几十个小时内还要去学个新技术,但因为目标明确,也倒挺有意思,而且经常有其他人带,所以很快就能上手。

从 Hackathon 也能看出技术的演进和趋势,开始是 Web,然后移动开发,现在则是各种硬件、人工智能,大家使用的编程语言也在不断变化。

公司的 Hack Week 跟以往的 Hackathon 差别其实很大。最明显的是时间长了,提前就可以想...

Continue reading →


ActiveRecord 和 Ecto 的比较

ActiveRecord 是 Ruby on Rails 的 Model 层,是一个 ORM(Object-relational mapping)。Ecto 是 Elixir 实现的一个库,类似于 ORM。不管是不是 ORM,二者本质上都是在各自的语言层面,对于数据库操作提供了抽象,能让我们更方便地和数据库交互,而不是直接通过 SQL 的方式,并且对表中的数据做了映射,从而方便进行后续逻辑的处理。

这篇文章并不打算来争个孰优孰劣,很多时候对比的作用更是加深对于事物的认识。(当然不代表我本人没有倾向,只是希望大家能够尽量保持客观)

定义映射关系

我们一般会以表为单位来操作数据库,二者也对表这个概念做了映射。假定我们有一个 users 的表,那么它们的定义(这篇文章的代码示范大多会使用这个模型)如下:

 ActiveRecord
class User < ActiveRecord::Base
end
 Ecto
defmodule User do
  use Ecto.Schema

  schema "users" do
    field :email, :string
  end
end

可以看到,ActiveRecord 更“智能”,通过继承了 ActiveRecord::Base 这个类,并且利用表名的转换约定(User 对应复数形式的表名 users)来完成映射。而 Ecto 中则是通过 DSL 定义了 schema,很显然,表名以及字段的指定都是显式指定的。

从代码上看,ActiveRecord 更简洁,但只看这个代码却不知道表名、字段等信息,需要通过查看 db schema 的定义来做进一步了解,而 Ecto 定义比较繁琐,但 schema 结构一目了然。

数据存放和使用

让我们先跳过数据库操作,直接到数据被取出来之后的部分。我们来定义一个操作——把 email @ 前的部分提取出作为 name:

 ActiveRecord
class User < ActiveRecord::Base
  def name
...

Continue reading →


Does 3% users matter?

好久没写博客,这几个月来的第一篇,居然不是技术的,实在是有愧于自己程序猿这个身份。无奈技术的博大精深,积累一篇高质量的文章实在是不容易,写的太烂也不敢发出来,怕被同行嗤笑。反倒因为不是做产品,才敢来聊一聊产品,虽然这雾中花,也不是我这俗人可以洞见的,但不论优劣,起码不至于会被骂得太惨。

这开头扯的差不多满意了,可以开始正文了。

早上开了一个会,提到了一个产品功能设计的一个依据是,3% 的用户如何如何,其它比例的如何如何。当时听了,觉得挺合理的,现在 PM 用数据把程序猿的嘴堵的严严的,有进步啊。但之后算了一下,按流利说的官方数字( http://liulishuo.com/about )——25,000,000 用户——来算的话,这 3% 有 750,000 个用户。

在这里,我意识到第一件事——思维惯性有多么可怕。3% 这个数字看起来不算很多,但当它放到不同上下文中去时,所代表的分量却不尽相同。即便是 1% 的比例,也可能影响着数以万计的用户。一个产品量级越大,相同的比例所影响的用户也就越多。当然,量级小的产品,比例的分量可能反而更重。所以比例这个数据能起到多少作用,真的是一个很模糊的问题。

随后,我又意识到另外一个问题——这个数据足以支撑一个决策吗?这些数据只是某几类用户的不同占比,但这些就足够了吗?如果这 3% 的用户日活都很不错呢?如果这 3% 的用户都是很优质的用户呢?印象中好像没有听到有关于这方面的数据,但如果假设成立的话,那是不是就意味着,这类用户反而需要我们投入更多的精力、花更多的心思呢?

这里引申出来的一个问题是,现在很流行 data-driven,但似乎并不是拿出了数据就可以用来 driven 了,起码得保证数据是可靠的、全面的吧。感觉很可能发生的事情是,有了一个想法,就想拿数据验证一下,然后也确实找了一些数据,也确实能够验证那个想法之后,就认为这是可行的,但有可能那些数据只是冰山一角。结果产品死了之后,还一定要说,我明明是 data-driven 的啊。就好像那句不知道是谁说的话一样,“人们只相信自己愿意相信的”。

...

Continue reading →