GithubHelp home page GithubHelp logo

xizhibei / blog Goto Github PK

View Code? Open in Web Editor NEW
422.0 40.0 29.0 24.24 MB

个人博客,(Node.js/Golang/Backend/DevOps),欢迎 Star, Watch 订阅以及评论

Home Page: https://blog.xizhibei.me

License: Other

Shell 31.45% HTML 68.55%
blog issue-blog

blog's People

Contributors

dependabot[bot] avatar xizhibei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

MySQL分库分表实践

这些日子为了解决业务上的挑战,想要解决MySQL的性能提升方案。

目前找了主要有:

  1. 分库分表
  2. 读写分离

读写分离最简单,牺牲一点一致性能减少读的压力,分表也比较简单,但是目前没有特别合适的中间件,结合我们的业务场景,分库是目前最适合下点功夫的。(另外还需要做高可用,可以做双主,但是不需要我过多参与,因此这里不谈。)

背景介绍

  • MongoDB用来存储大部分非事务相关的业务
  • MySQL用来做电商相关业务,所有数据几乎都与账户ID相关
  • 账户ID是MongoDB的ObjectId
  • 对于普通用户来说没有关联到其他用户的操作
  • 管理员需要统计以及查看所有的用户数据

分库算法

一致性hash

需要停机,遍历所有库,将需要移动的数据移动到新的库上。【1】【3】

具体步骤

停机 => 数据迁移(不删除旧数据) => 调整服务器配置 => 上线 => 遍历检查并删除各个库冗余的数据

优点
  • 平滑提升性能,节约服务器
  • 迁移过程中移动数据很少
缺点
  • 线上迁移时间时间会比较久
  • 计算速度没有取模快

取模

  • 也算是一种一致性hash,只不过更简单;
  • 拆库的方案是按2的倍数去拆,比如先拆成2个库,然后需要扩容的时候拆成4个,不然的话可能每次迁移都得全部重新迁移;
具体步骤

停机 => 所有的库复制一份 => 调整服务器配置 => 上线 => 遍历检查并删除各个库冗余的数据

优点
  • 数据会非常均匀;
  • 由于不用操作迁移数据库,直接使用备份恢复的方式,线上扩容速度会快很多;
  • 一次拆库之后能坚持很长时间;
缺点
  • 比较耗服务器,比如每个库最多放100W个用户的数据,那数据涨到150W左右的时候就应该拆分了;
    无论怎么拆,性能都是跳跃性提升的,不能平滑提升;
  • 由于我们使用的是mongodb的ObjectId,数据不均匀的可能性非常高;

好了,无论怎么分,都会涉及到停机升级的问题,但是停机意味着损失收入,那么为了解决停机的问题,我们找到了一个迁移方案:索引表。【2】

经过上面的比较,为了让数据库不停机以及迁移尽可能快,我们选择一致性hash方案再加上索引表

  • 每次根据用户ID取库的时候,先查索引表,如果没有,再计算;
  • 开启定时任务,定期扫描,迁移数据,再更新索引表;

迁移需要注意一点就是需要锁住数据,手动进行两阶段事务,不然可能会造成数据不一致,然后这个迁移任务尽量在凌晨无人的时候进行,而且也只是单个用户,因此这样可以将对用户的影响减到最少。【2】

几个问题

  1. 是否需要公共库?
    如果是公共库,很可能是跟事务不相关的,移到MongoDB即可。
  2. MySQL ID自增怎么解决?
    换成ObjectID,或者UUID之类的。
  3. Node.js有没有相关的中间件?
    可以看看TribeDB,但是不满足我们目前的需求。(蠢蠢欲动想要造轮子了
  4. select *,order by之类的需要跨库,或者联合所有库的操作怎么办?
    这些操作在我们这一般是管理后台才要用,所以可以放到其它工具去做,比如elasticsearch,而且这样的查询会影响数据库性能,我们巴不得砍掉这些查询

其他问题可以看看【2】,内容很详实。

Reference

【1】. https://cnodejs.org/topic/5502a23573263b0e4eef9b85
【2】. http://www.infoq.com/cn/articles/yupoo-partition-database
【3】. https://github.com/3rd-Eden/node-hashring

爬虫方案之PhantomJS

发现zombie.js还是太弱了,很多地方不能满足需求,包括request的HTTP头也不合乎标准,于是硬着头去尝试下PhantomJS。

这一试,我就后悔了,这么好的东西我居然不先尝试!怎么说呢,它比zombie好用太多,加载处理速度方面也是远远超过。唯一比较遗憾的是没有原生的nodejs接口,它只是一个环境,像mocha这样有自定义的变量,需要用它去执行js文件。所以如果要用到爬虫上面的话,需要通过第三方的包去包装一层。比如我用的https://github.com/amir20/phantomjs-node

目前我没找到这个库一些常用的方法utils,比如waitFor之类的没有实现,所以只能手动实现了,下面贴出我自己实现的:

async function waitFor(testFx, {
  timeout = 3000,
  delay = 100,
  silence = false,
}) {
  function sleep(t) {
    return new Promise((resolve) => {
      setTimeout(resolve, t);
    });
  }

  const start = new Date().getTime();

  async function _waitFor() {
    if (new Date().getTime() - start > timeout) {
      const e = new Error('Timeout');
      if (silence) return e;
      throw e;
    }
    const result = await testFx();
    if (!result) {
      await sleep(delay);
      return await _waitFor();
    }
  }

  return await _waitFor();
}

当然了,其他成熟的框架也有,比如

SlimerJS

跟PhantomJS几乎一样,API接口一致,但是没有PhantomJS不能获取response body的问题,是个替代品,但是它是基于Gecko的

CasperJS

包装了PhantomJS以及SlimerJS,提供了一些语法糖,高层实现,挺适合做e2e测试的

SpookyJS

基于CasperJS,提供了nodes的API,就相当于上面phantomjs-node对PhantomJS封装,如果下次再做的话,我会考虑这个包。

轻量级消息队列Kue的一些使用总结

最早接触kue是在0.7之前的版本,功能很少,很弱,但还是很好用的,那时候找过一些其他的消息队列,如rabbitmq,kafka之类的,但是都觉得太重了,跟nodejs的融合还是不太合适。

找着找着,就找到了tj大神搞的kue,基于redis的subpub做的优先级消息队列,适合做一些离线任务,比如统计运算、发邮件消息以及异步业务逻辑。

建议初次使用多看看文档,把一些方法全部记下来作为模板,比如创建第一个任务的时候:

queue.create(YOUR_FIRST_JOB, jobData)
.removeOnComplete(true)
.priority('normal')
.attempts(5)
.backoff( {delay: 60*1000, type:'exponential'} )
.ttl(ms('1h'))
.save();

几个坑

文档

文档很弱,继承了一贯懒的作风,很多不明白的地方还是看代码来的快,如果你发现文档中没有的内容,或者写错的内容,尽快提交pr,我上次给改了文档,2个月才给merge。

延时任务

不能自动执行延时任务,必须执行一个周期性检查(记得是500ms,也可以自定义)的方法,然后还不能在多个进程中执行,不然会有race condition出现,即同一时刻同时执行同一个任务。
新的版本中kue自动处理,已经没有这个问题了。

任务堵塞

还有一旦redis的连接不稳定,或者没有处理好throw error,就会出现任务永远处于active状态,因为kue认为这个任务没有完成。如果这时候设置的
新的版本中,提供了ttl超时自动失败,以及watchStuckJobs这个方法,建议在创建job之前就调用这个方法。

queue.watchStuckJobs(interval=1000)
运维

除了kue自带的dashboard,还有kue-ui,感觉比自带的好用一些。还有就是经常查看是否有失败的任务,用kue自带的一些方法定期去执行,然后把结果用邮件发送之类的。

部署&重启

如果是用的pm2,记得不要直接restart或者reload,而是要用gracefulReload,pm2会给进程发送一个shutdown的message,等它自动退出,如果超出时间未响应才会kill,因为对于kue来说,可能正在跑某个人物,如果这时候直接restart会影响到任务。

// remember to set env PM2_GRACEFUL_LISTEN_TIMEOUT
// and process.send('online') when job app start
process.once( 'message', function ( msg ) {
  if (msg !== 'shutdown') return;
  queue.shutdown( 5000, function(err) {
    console.log( 'Kue shutdown: ', err||'' );
    process.exit( 0 );
  });
});

Reference

https://github.com/Automattic/kue
http://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/

谈谈如何在创业公司里面工作

年关快过了,大家基本上留的留,走的走,有之前不少朋友去了创业公司,再加上最近接触了不少来面试的新人,他们不少来自于创业公司,我面试了他们之后很有感触。

毕竟我经历过创业公司从生到死,因此,来谈谈如何在创业公司工作。

两个故事

近几年来,互联网行业蓬勃发展,尤其是去年,2014年开始,各种O2O公司站在了风口上,于是你也风风火火得加入了一家O2O的公司。原因?很简单,因为觉得行业势头好,而且好多朋友在里面,虽然工资降了,高期权也不错。

一年半,随着潮水退去,你们发现自己成了那个没穿裤子的:把2C的业务做成了2VC,于是没钱了,散了。

期权成了废纸一张,少掉的工资也相当于打了水漂。

不过,你算是学到了不少东西,比如创业公司要求你样样都干,于是你硬着头皮上,学了不少,当然,成长了也不少。如果说开始创业之前是大公司里做个钉子,那么你在创业公司里面,成了独当一面的『人物』了。

其实公司倒闭之后,你怕找不到好工作,哪知道出去试了一圈之后,几乎都得到offer了,而且一个比一个高。于是,让你更加确定一点:你在创业公司里面的努力没有白费。

另一个故事:

你作为在创业公司的早期员工,随着公司慢慢发展,自己成了技术负责人。于是付出了很多之后,很执迷于回报,比如会这么想:『我这么努力,为公司付出那么多,老板应该给我加工资跟期权』。于是就会做很多无效的工作:整天都在忙,但不知道忙什么,什么琐事都参与,不会管理自己,天天加班到很晚,快下班也不忘朋友圈里面晒下自己的办公桌。

然后有一天,公司开始盈利了,开始招更多的能人了,这时候,CEO把你叫到办公室,一阵寒暄之后,『XX啊,感谢你这几年为公司的付出,这是你X个月的工资,你走吧』。突然,你懵逼了,因为CEO当初跟你说的期权只是口头的,根本就没跟你签实际的期权协议,合同上的工资也只有每月发给到你工资卡上的钱的一半。这时候真实欲哭无泪,因为你相当于被扫地出门了,这时候真是委屈。

好了,编不下去了……

其实你仔细回想下,这种狗血的利益纠葛事件在现实中真是一件接着一件。是的,你可以说**社会没有所谓的契约精神,人与人之间没有信任关系。

只是,当真是这样?你还是『Too young, too naive』。

如果你以为,作为早期加入创业公司的你,CEO会一直看好你,一直留着你,你就可以不思进取了,那就大错特错了。

人与人交往的基础是什么?价值!更何况是公司呢。天下熙熙,皆为利来;天下壤壤,皆为利往

那么,现在我们可以进入正文了。

几个关键点

价值第一

公司是什么?一个利益共同体,大家为了共同的利益而努力,然后分享获得的利益。不妨角色转换下:当你手下的一个员工对公司没有价值的时候,你会采取什么措施?你只能请他走人,如果他不走,那么就相当于告诉大家,你的公司是允许吃白饭的人存在的,那群很有能力很有价值的人,会作如何感想。

在索取价值之前,仔细想想,你能贡献什么价值

这个在大公司一样,你可以向Boss要求加工资或者期权,但是,请你先想想,你对公司能提供多大的价值呢?

不要做无效的努力

如果你认为,加班就能让老板不赶你走,那就太单纯了。你的价值体现绝对不会体现在加班工作上面,不妨想想公司现阶段有什么问题,你能帮着解决什么问题,这才是你的价值所在。

做好随时走人的准备

公司随时都在成长,但是如果你跟不上脚步的话,那就准备走人吧。认准一点:不要把公司给你的报酬当成理所当然:时刻都要学习与成长,你要赶上公司发展的脚步,在里面扮演更重要的角色,于是你的价值就更高,而至于你想要的薪资与期权,CEO会双手奉上。

其实,这也是一种心态,几个小测试就能说明:

如果现在让你走人,你是否能很轻易找到新的满意的公司,而且他们给你的报酬更高?
你在公司是无可取代的么,公司为了取代你,需要付出多少?

如果是的,那么老板是会拼命留你的,你提出的合理回报,你都能得到。
如果不是,那么你该回想下,自己究竟在忙什么呢?为何这么长时间以来,自己都没有任何长进。

结束语

其实,去哪工作都一样,增强自己的实力才是王道,遇到能带你一起下海创业并分享给你成果的领导难得,只是如果不珍惜,自己又不长进,任谁也不会留你的。

另外,不是说你可以全身而退就可以不顾得失了,保护好自己的利益同样重要,别到头来除了精神回报一无所有,不过这是另外的话题了,有机会再说。

杨绛先生

今天在朋友圈刷屏了,不想跟风,干脆在这里写点东西,权当念想。

第一次听说杨绛先生,是在高中,有篇语文中学到的,关于她跟钱钟书先生的故事,记不清了,那时候很奇怪,为啥女的也可以叫先生,后来才知晓,先生是一种很少的女士能有的尊称。

她的那句**『你的问题就在于读书太少而想的太多』**,足够受用终生,很多人,尤其是我自己,在几年短短的职业生涯中,感受到,很多问题,不能干想。

刚工作的时候,记得天天刷知乎,觉得上面的知识好多,于是如饥似渴的天天看,每看到一篇好文章便觉得,『哇,好棒,今天又学到了一个小知识』。
直到有一天,猛然发现,自己知道的小知识很多,但是都是乱七八糟的,不成体系,以为自己看了几篇分析经济的文章便懂经济了,以为看了摄影技巧自己就会拍照了。。。

工作中,碰到很多技术问题,想到的都是直接去查Google,不是说不好,而是,往往有这么个习惯:查了之后,比如stack overflow上面有人贴出了代码,于是直接复制粘贴。现在,我觉得,好习惯在于你遇到了某个问题,查到了解决方案,明白了问题原因,探究了原理,尝试不同的解决方案,并总结,如果发现自己的解决方案更好,那么不妨开启自己的开源之路,去实现这个,甚至这类问题的通用性解决方案。

另外,多看书,技术书没得说,多看更要多实践,然后其他的书也得多看看,拓展下自己的思路,你终会发现,很多问题在各个领域都是相同的,往深远了说的话,都是哲学。(插播一条有意思的小知识,维基百科上,你去点击大部分词条内容里面的链接,来个深度搜索,你会发现90%以上都会最终到了哲学相关的词条)

别多想,多看书,好了,我要去看书了。

持续交付的实践与思考

一直以来都在做团队里的基础性工作,直到最近,成果开始慢慢展现,尤其是上周刚看完《持续交付》这本书后,总结已经做的工作,又有了些新的感悟。

过去一段时间,我都是围绕着 Gitlab 的一些功能来开展的,从最开的代码与应用配置管理,然后到 CI 系统,提升代码质量,到最后完整的持续交付流程提升团队工作效率。

那么我就结合《持续交付》这本书的内容,正文开始。

为什么要有持续交付

  1. 不敢发布新功能:每次发布都会心惊胆战,因为一下子发布了太多功能,测试又没做好,而这时候产品催着要上线。另外按照以往的经验,由发布而出问题的的概率最高;
  2. 线上事故处理时间长:每次出线上事故,不能很快定位问题,如果是由于同事线上改动了什么,就更难定位问题了,而在之后大家可能会狠狠责怪那个同事,事实上,他也很无辜,因为没有人告诉他什么能做,什么不能做,或者该怎么做;
  3. 发布周期太长:由于每次功能完全开发完之后才会发布上线,而如果涉及到数据库更改或者迁移,发布周期可能会拖得太长;
  4. 协作问题:比如新人来到团队时,由于被限制了各种线上的权限,尤其是发布权限,会导致很多工作开展不顺利,而每次发布求助于其他同事的话,会造成沟通问题,进一步造成了协作上的问题。另一个,往往由于习惯于口头上说下要发布了,然后发布过程对其他人而言是不可见的,若是线上还需要操作些什么的话,几乎对其他人是更不可见的;

持续交付能做到什么

  1. 让软件构建、部署、测试和发布过程对所有人可见:由此,大家可以学习过程,了解其他人做了什么,在一个规范的流程中工作,促进协作;
  2. 改善反馈,更早发现问题:在一个完整的流水线中,可以设置多个『检查点』,简答来说,就是加入多个测试环节,保证代码质量,而在这个过程中,越早发现问题,修复的成本越低;
  3. 通过一个完全自动化的过程在任意环境上部署和发布软件的任意版本:在陈浩大神的 《从Gitlab误删除数据库想到的》 这篇文章里,提到:人管代码,代码管机器,其实持续交付就能达到这个效果。并且在一种极端情况下,假如你使用的云服务商挂了,你也能在其它云服务商上面快速重建你的整个线上系统(当然,没那么简单)。

持续交付的几个原则

  1. 为软件发布创建一个可重复且可靠的过程:一键发布与回滚,如果发布出了问题,你几乎可以确定不是发布脚本本身的问题,因为这个脚本已经经过多次检验;
  2. 将几乎所有的事情自动化:手工发布及漫长又不可靠,采用自动化脚本,节省时间,节省精力;
  3. 把所有的东西纳入版本控制:采用变更管理,方便审计,将所有的变更都在掌控之中,出问题可以快速定位问题;
  4. 提交并频繁做让你感到痛苦的事情:小步快走,分散痛苦与风险,
  5. 提高内建质量:越早发现问题,修复成本越低,等 QA 或者用户告诉你应用有问题的时候,修复成本非常高,也增加了你计划外的工作;
  6. 『Done』意味着『已发布』:没有完成百分之多少这种说法,反推着你将任务分解,将功能尽快发布到生成环境中去,减少了产品反馈时间,提高了竞争力,也减少了风险;
  7. 交付是每个成员的责任:流程标准,每个人都可以参与到流程的每一个部分,促进协作,也帮助新人适应与提高;
  8. 持续改进:不断演进,改善发布流程,这个流程需要持续的改进,提高吞吐,增加产量,提高质量;

持续交付的实践

在目前的团队持续交付流程中,我们在每个环节的实践如下:

  1. 提交:单元测试 mocha/ava,以及测试覆盖率 nyc;
  2. 编译打包:即打包 docker 镜像,推送到 registry ,目前还没有完全引入 docker 集群,因此部署过程省略;
  3. 部署文档:用 ansible 脚本自动将 api 文档以及测试覆盖情况部署至静态网页文档服务器,这个时候可以直接将分支对应的文档给客户端开发,还可以直接查看测试覆盖率情况,确认与改进单元测试;
  4. Review:即提交 Merge Request,大家一起 Code Review;
  5. 部署应用:需要 Review 通过后,将代码合并至 master 分支,然后手工点击按钮部署;
  6. 回滚:假如应用出问题,可以点击按钮一键回滚;

可以看到,目前主要是缺失预发布环境,这一步还需要将环境搭建脚本完善之后才能做到,而目前公司运维团队还是与我们开发团队分离的,因此达到相要的效果还是得做些努力。

总结

目前我已经做到:将应用配置单独管理起来,以及部署相关的脚本都已经实现,因此才能很快地将持续交付流程跑起来。其实可以这么说:应用配置管理与自动部署脚本是持续交付流程的前提与基础。

而持续交付流程一旦完善了,能极大改善我们开发团队的交付能力,甚至倒逼产品与运维团队提高他们的效率:

  • 对于产品团队,我们能要求他们将需求拆分尽可能的细,一旦开发完成我们能快速部署,于是他们能更快得到反馈,从而提高产品的试错能力。
  • 对于运维团队,我们已经将部署流程优化了,于是他们能更专心于基础设置的维护与改善,同时也可以促进协作,推行全面的配置管理。

Reference

  1. https://about.gitlab.com/direction/
  2. https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/
  3. https://github.com/everpeace/concourse-gitlab-flow
  4. https://docs.gitlab.com/ce/ci/environments.html
  5. https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/

管理后台最佳伴侣之NgAdmin

这几天为了给系统新做个后台,到处找开源项目,真给我找到了,https://github.com/marmelab/ng-admin

用完只能说,太方便了~~~这么好的项目为啥我现在才发现,之前我是使用angular一步步搭起来了,自从用了ng-admin之后,2天时间系统已经成形了,结合rest API,后台的开发很简单,待我这个项目做好之后写篇总结。

不过可以先贴段代码,这里是跟后台交互的API,自定义的接口风格都可以在这里修改,由于我实现的后台使用的是mongoose,因此可以根据它的查询风格修改如下:

import store from 'store';

export function requestInterceptor(RestangularProvider) {
  // use the custom query parameters function to format the API request correctly
  RestangularProvider.addFullRequestInterceptor(function (element, operation, what, url, headers, params) {
    if (operation == "getList") {
      // custom pagination params
      if (params._page) {
        params.skip = (params._page - 1) * params._perPage;
        params.limit = params._perPage;
        delete params._page;
        delete params._perPage;
      }
      // custom sort params
      if (params._sortField) {
        let field = params._sortField;
        if (params._sortField === 'id') {
          field = '_id';
        }
        params.sort = `${params._sortDir === 'ASC' ? '' : '-'}${field}`;
        delete params._sortField;
        delete params._sortDir;
      }
      // custom filters
      if (params._filters) {
        params.filters = params._filters;
        delete params._filters;
      }
    }
    const token = store.get('JWT_TOKEN');
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
    return {params: params, headers: headers};
  });
}

export function responseInterceptor(RestangularProvider) {
  RestangularProvider.addResponseInterceptor(function (data, operation, what, url, response) {
    // your intercepter logic here

    return data;
  });
}

然后在后台的接口直接使用传过来的参数:

export function getAdminDataList(Model, query) {
  const filters = JSON.parse(query.filters);
  let q = Model.find(filters);

  if (query.sort) {
    q = q.sort(query.sort);
  }
  if (query.limit) {
    q = q.limit(Number(query.limit));
  }
  if (query.skip) {
    q = q.skip(Number(query.skip));
  }
  return q.exec();
}

关于管理后台

一些开发团队往往有这样的想法:『管理后台是给自己人用的,差点无所谓』。我却认为,你做的管理后台是给员工用的,如果差的话,他们用着难受,尤其是客服,他们的态度会被影响,然后传染给你的用户。同时,管理后台是工具,如果差的话就意味着效率低,效率低对团队意味着什么,我也不用细说了。

退一步说,管理后台的使用者也是你的用户,建议让管理后台的产品或者开发人员去客服边上待上几个小时,当他们看到自己做的东西让别人那么难受的时候,就会知道该怎么做了。
来自一个给客服同事造成困扰的工程师

管理道路

很多同行都会想,当自己做到一定程度了,应该会去做技术专家,或者管理岗位。

只是,现在的很多同行是这样的,我很喜欢钻研技术,并且,我还想往管理岗位上发展,但是遇到一些团队管理的时候,往往不想去处理。

原因?很简单,因为搞定团队内部的琐事,把团队带的井井有条,远远比不上自己去钻研新技术,提高团队的生产力带来的快乐要多。所以,这样的人,不太适合往管理岗位发展。而管理岗位做的好人,很大一部是在技术上已经或者从来没有快乐的人去做的。每个人都会倾向于自己得心应手的那部分,快乐还是非常重要的,在自己的舒适区里面,快乐是永恒的,付出很少就能得到很多回报。

但是,从另一个方面来说,做技术的人,往往倾向于听从于技术能力比自己强的领导:沟通简单,很多事情说几个术语就能让领导理解,领导也能从过往的经验给出一些意见跟建议。

所以,就这样了?

显然不是,并不是就这样没戏了。

当技术做到一定程度,你就可以尝试带小弟了,一个关键的点就在于,你愿不愿意跳出自己的舒适区并且承担责任:你的时间不再仅仅属于你自己,你得花时间教他们,帮团队解决基础设施问题,跟领导沟通汇报,团队内部有问题了及时解决,有时候,你可能一整天都会在处理各种各样的琐事上面,只有下班之后才能做自己的事情,看点书写点代码之类。

也许,初期你会觉得不值得,有点浪费时间,还不如自己上呢。没错,现在的我就是这样,很多东西自己做的效率非常高。只是,这样的话,下面的人得不到锻炼,他们的效率提不上了,你可以说招更多的牛人一起共事,有钱的公司当然可以这么干,但是为了成本以及长远考虑,肯定需要自己去招一些技术能力一般,但基础不错的,培养他们成为牛人的成就感不会比你解决一个技术难题少。更重要的是,以后这帮人是可以帮你做事的,一个人再牛逼肯定比不上一个好的团队。

你要硬扯一些特例,我也明白,牛人是有,甚至作战能力秒过很多团队,这是战术,而公司更多需要的是战略:牛逼的团队。

所以,成为牛叉的技术专家还是带出一个牛叉的技术团队,都可以选。

我自己的话,觉着,挑战下其它领域也挺有意思呢。一个人可以走的很快,但是带起一个好的团队,可以走的更远

《目标》读后感

最近刚看完了《目标》,是《凤凰系统》里面提到的一本书,同样是小说题材,有趣而容易理解。

书的核心点在于 『 TOC 制约法』 ( TOC 即 Theory of Constraints ),简单来说,就是需要发现与改进系统中的 『瓶颈点』

故事简介

作为工厂厂长的小说主人公 Rogo ,通过大学老师 Jonah 的指导,突破常规,大胆革新,运用 TOC 方法让工厂摆脱险境,扭亏为盈。

其实对于现代的互联网公司,这本书任然非常具有借鉴意义:因为方法论都是相通的。比如刚开始遇到的痛点,Rogo 的工厂,一开始就是生产秩序混乱,生产成本居高不下,以及交货周期非常长。

对比当下互联网的公司,其实这个问题一直都有,尤其是创业公司:产品开发流程混乱,随意更改、插入新需求,开发周期非常长,开发成本也非常高。CEO 整天头疼不已:为啥我的这帮属下,整天这么忙,每个都这么狼性,整天加班,为啥产品开发出来总是那么差呢?

下面简单总结下小说里面提到的一些内容:

目标

在小说中,Jonah 与 Rogo 的第一次见面,就说出了 Rogo 目前面临的问题,于是告诉他找出『目标』,就与我们的常识一致:找出目标,我们才能知道接下来做事的方向,一旦方向偏差,我们也能快速找回来。

但是,在遇到问题的时候,能找到目标却很难:需要头脑非常冷静,你不能仅仅忙于解决目前遇到的紧急的事情,因此你需要能停下来分析与思考,才能找出目标是什么,以及接下来的做事方向。

一般来说,大部分的我们以及公司的目标只有一个:『赚钱』

附上摘抄的两句话

第一句:

我想要告诉你的是,除非你知道目标是什么,否则生产力就毫无意义可言。 -- Jonah

第二句:

Rogo ,如果你和世界上其他人几乎没什么两样,你毫不质疑的就接受了很多事情,表示你没有真正的思考。 -- Jonah

三个指标

找到目标之后,如何衡量当前的方向与目标的差异呢?

『指标』

  • 有效产出:整个系统通过销售而获得收入的速度;
  • 存货:整个系统投资在采购上的金钱;
  • 营运费用:整个系统为了把存货转为有效产出而花的钱;

对应到互联网公司就是:

  • 有效产出:新产品、新功能给公司带来的收入;
  • 存货:开发到一半,或者完成之后没有交付到用户手中的产品或新功能;
  • 营运费用:公司对人员、机器、市场、研发等等的投入;

对应到 IT 服务系统就是:

  • 有效产出:吞吐量提高,响应时间减少;
  • 存货:响应还没有到达前端 UI 的显示上;
  • 营运费用:服务器、电力、运维等投入;

其实,这三个指标就能帮我们做紧靠目标的决策,而不是根据以往的经验或者成见来做决策。

五个步骤

  • 确定唯一目标
  • 找出系统瓶颈
  • 决定如何挖掘制约因素的潜能
  • 确定每件事都要配合比瓶颈的节奏
  • 给瓶颈松绑

这些其实就是具体的问题解决流程,贯穿整个小说中故事的主线,也给了我们解决现实中遇到问题时候的解决思路。

比如当我们遇到服务器瓶颈的时候:

  • 我们唯一的目标是『用户能在我们提供的服务上,消费更多』,我们就要提供更好更稳定的服务;
  • 服务定位就像找 bug,好好用好监控系统;
  • 一旦定位到瓶颈服务之后,我们的策略有:给瓶颈服务提供单独的服务器、升级服务器、将功能拆分,只用它来处理最关键的输入等等;
  • 分配人员资源跟时间,制定策略;
  • 实施策略,从而给瓶颈服务松绑;

(其实我们有成熟的策略:即在立方体的xyz上面拓展)。

转变思维

『整个系统的产出,取决于瓶颈的产出』,这一点对于我来说是醍醐灌顶的:当我以为,瓶颈损失了一个小时的产出之后,仅仅是局限于瓶颈本身的时候,事实却恰恰相反,因为瓶颈损失的一个小时内,系统没有任何有效产出,所以也相当于整个系统损失了一个小时,或者说:『系统真正每个小时的成本是整个系统的总营运费用除以瓶颈的有效工作时间』。

学会沟通

当你发现问题的时候,或者觉得这本书提到的方法可以应用到你的公司的时候,你会如何做?是自己一个人默默推进么?

显然不是,主人公获得老师指导之后,首先做的是跟属下的人沟通讲解,试图让他们一起帮忙理解,同时也为之后的改革赢得了他们的支持。

与精益**的区别

从解决问题的视角来说,我认为精益**是『贪心』,它认为局部最优就是全局最优,而 TOC 是动态规划,因为它真正从全局来解决问题。

或者说,两者的格局也不一致:通常的管理者会认为只要公司里每个员工都用 100% 的付出,甚至通过加班加点来达到更多的产能,就能使公司又更多的盈利,但格局更高的管理者会从系统整体去思考,每个员工的都很忙碌的公司往往不是高效率的公司,因为无谓的忙碌加班只会浪费资源:有效产出无法增加,增加的只是无用的存货

问题需要从源头上解决:俗话说的『上面得病,下面吃药』,说的就是这种没有从源头上解决问题的做法。公司是一个完整的系统,如果不能以系统的视角来看待整个公司,我们只能在局部挣扎,就如同物理学上的力学原理:所有的力量发挥到最大,就能使物体的移动速度最快吗?别忘了,作用在物体上的合力才是使物体移动真正有效的力量。

公司文化的力量

其实最后 Rogo 升职之后,开始考虑公司文化上的问题了,他看到了不只是从前工厂里的产品线,而是整个公司的组织与流程,他意识到:瓶颈也是无处不在,管理也是无处不在。

P.S.

恩,TOC 是个好东西。

2016年总结

在这个跨年的时刻,躺在床上,听着音乐,想着快过去的2016,以及即将到来的2017。

2016回顾

年初经历了加入了一年半的创业公司倒闭,开始到处找工作。然后,发现自己似乎落后这个世界好远(因为差点把自己按15k给买了==#),毕竟当自己专注于创业这件事的时候,对外界似乎关注太少。

不过,正是这创业期间不断逼自己成长,让我能很快适应新工作。

在新公司呆了一年不到,时间过得太快。总的来说,自己还是觉得成长有限,很多时候都是靠自己摸着石头过河,也有段时间,自己都静不下心来好好学习。

坚持了大半年的博客,这是我有些意外的,原本只是想随便写点东西,之前也写过,但是一直没坚持,现在在github,专门开了个blog的repo,开始写博客。也许你会问为什么在这里写,为啥不搞个静态生成器,github的page还是不错的,的确,对外展示漂亮的博客吸引人眼球,但是:我懒,我只是想安安静静写点东西,记录下自己的想法,总结下经验,这里简单,干净,方便,对我就足够了。

技术上,这一年走过不少弯路,但也攒了不少经验,学习的东西也更多,更深入了。

我想,这一年来,最大的收获就是『决断』能力,几次处理线上事故的经验,让我能更快也更自信的处理线上事故,敢于做出大胆的决策。

2017年计划

总体来说:『打好基础』,自己的基础知识,以及团队的基础。

  1. DevOps:努力将SLA做到至少三个9;
  2. 管理自己:希望自己能更好安排与管理自己的时间;
  3. 读书:读更多书,很多时候,只是自己想的太多,看的太少;
  4. 写博客:沉淀知识,也多交朋友,互相学习与成长;
  5. 旅行:多走走,外面的世界很大,祖国大好河川去的太少;
  6. 多交流:沉浸在自己的小世界里,总会闭塞;

CI系统搭建

不知道,你有没有遇到类似的情况:

  • 不重视测试,开发新功能都是手工测试
  • 每次开发新功能,都会懒得去做回归测试,线上经常出问题
  • 新同事来,不熟悉系统,提交的代码会把系统搞坏
  • 测试覆盖率一直非常低

这时候,你需要个CI,也就是持续集成。

我理想中的团队开发流程中,CI是最重要的一环,团队成员按照git flow开发,然后提交,等待CI测试通过,最后提交pull request,让同事Code Review。

在以后,还可以加入CD,即当提交到master通过之后,构建并打包docker,部署到正式环境。

好处:

  • 快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
  • 防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。

CI也是敏捷开发流程中最重要的一环,是否还记得这句口号?

自动化一切

搭建

我选择的系统是gitlab,自带pipeline可以当成CI&CD,老牌的jenkins也可以考虑,只是插件式的不如写个pipeline的定义文件来得方便。当然了,第三方服务如Travis, Codeship也不错。
只是,我的考虑是:首先,我的项目中带有私有的npm包,目前我已经搭了一个npm private registry;其次,国内的服务让人很不放心,国外又太慢;

最方便的简单搭建可以看看这个:
https://github.com/sameersbn/docker-gitlab

配置 .gitlab.yml

由于是nodejs,项目,这里就直接贴我的了,其他的,你们自己解决了^_^。

另外,建议executor选择shell,简单,可配置性强,另外,node的version可以用nvm解决。

variables:
  NODE_VERSION: v4.2.2
  NODE_ENV: development
  npm_config_registry: http://<you_private_registry>/
  npm_config_loglevel: warn
  PHANTOMJS_CDNURL: https://npm.taobao.org/mirrors/phantomjs
  SELENIUM_CNDURL: https://npm.taobao.org/mirrors/selenium
  ELECTRON_MIRROR: https://npm.taobao.org/mirrors/electron/
  SASS_BINARY_SITE: https://npm.taobao.org/mirrors/node-sass
  NODEJS_ORG_MIRROR: http://npm.taobao.org/mirrors/node
  NVM_NODEJS_ORG_MIRROR: http://npm.taobao.org/mirrors/node
  NVM_IOJS_ORG_MIRROR: http://npm.taobao.org/mirrors/iojs

before_script:
  - source ~/.nvm/nvm.sh && nvm install $NODE_VERSION

# This folder is cached between builds
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
  key: "$CI_BUILD_REF_NAME"
  untracked: true
  paths:
  - node_modules/

test_all:
  stage: test
  script:
    - npm install
    - npm run lint
    - npm run coverage

代码的覆盖率

我选的是istanbul,npm 的script加一个:
./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- test/

gitlab上对应的正则填这个:
Lines\s*:\s*(\d+.\d+)%

Reference

http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html
https://www.zhihu.com/question/23444990

应用配置管理实践

自从 上次 简单提到应用配置管理的几种方式以来,我都在尝试不同的方式,到目前为止,仍觉得剥离单独管理加上动态配置服务器的方式最好用,接下来谈谈原因以及具体实践。

原因

记录变更与审计

其实这个涉及到 CD,即持续交付的范畴,为了保证线上应用环境的稳定性,如果配置无法追踪的话,即意味着你不能在出问题的时候,快速定位原因,处理问题。

因此,放到 CVS 系统中就可以非常方便审计,在更新提交历史中查找即可。

自动化

与 CD 配合,即你可以提交到 CVS 系统后,采用 CD 方式直接将各个不同环境中的配置更新。

简单,直观,简洁

在 CVS 中,可以直接看到修改的内容,同事也能知道你修改过什么。

环境统一

这意味着,你可以快速重建自动化生产环境,或者很方便地搭建预发布环境,而由于两个环境相似,一旦在预发布环境上验证过相关的脚本之后,就可以在生产环境中出问题时候,排除脚本问题。

实践

git submodule

主项目中,可以直接 git submodule 结合起来:

cd project-root/
git submodule add https://github.com/your-group/project-config config

npm private module

即相当于拆出一个单独的配置管理模块。
发布时:

npm set registry http://private-registry-url/
npm adduser --registry http://private-registry-url/

npm publish

安装时:

npm install @your-group/config --registry 

具体的 npm private registry,你可以付费使用 npmjs 的服务,也可以自己搭建一个。

搭建的话可以看这里:https://github.com/verdaccio/verdaccio,用 docker 部署非常方便:

docker run -d --name verdaccio -d -p 4873:4873 verdaccio/verdaccio

动态配置

可以使用 etcd,Node.js 应用中使用 nconf 以及 nconf-etcd2

npm i nconf nconf-etcd2 --save

在配置文件中:

nconf.env();

nconf.defaults({
    NODE_ENV: 'development',
    ETCD_HOSTS: 'localhost:2379',
});

nconf.add('etcd', {
    namespace: nconf.get('NODE_ENV'),
    hosts: nconf.get('ETCD_HOSTS').split(',')
});

然后,在项目直接使用 nconf.get 即可,当然了,为了达到动态更新的目的,可以使用 node-etcd 提供的 watch 方法,实时监听配置变化。

P.S.

不知道你注意到没有,我在这篇文章中,特意在配置管理之前加了『应用』两个字,这是有原因的:

配置管理不仅包括应用的配置,而且还包括线上环境所有相关的配置,如基础设施的配置:服务器的配置,网络的配置等。

在 《持续交付》 这本书中,给配置管理的定义是:

配置管理是指一个过程,通过该过程,所有与项目相关的产物,以及他们之间的关系,都被唯一的定义、存储、检索和修改。

显然,应用配置管理只是配置管理的一小部分而已,而配置管理是所有自动化的基础与前提,做好配置管理至关重要。

多余的不在这里赘述,下次需要专门讲讲持续交付。

APM 以及 Node.js 探针原理

关于 APM

APM 能帮企业以及应用开发者提供很多帮助,它的功能集中在监控、分析、优化上面,从应用中部署探针直接采集信息,集中处理,从多维度形成报告,简而言之:相当于给了一副看到应用程序不足的眼镜

多余的不再赘述,可以看文末参考链接。

几个选择

目前几个好用的,并且支持 Node.js 应用的:

  • NewRelic
  • OneAPM
  • Tingyun

从好用程度来说,NewRelic 秒杀全部,只是国内的 APM 其实也发展起来了,有着本地化的优势,能给国内企业一些本地化的解决方案,NewRelic 天高皇帝远,也是有着这方面的弊端的。

目前我们的服务器端监控用的 NewRelic,选择原因主要是强大、速度快、细节清楚。而手机上 APP 的 APM 选择的 Tingyun,因为它本地化优势很明显。

选择的时候不妨多试用比较,从自身需求出发去考察与筛选。

好了,非常简单的介绍了下,不难发现,对于 APM 来说,很重要的一点在于数据的采集,也就是需要部署探针去采集应用的各项数据,那么,探针是如何实现的?

Node.js 探针原理

说明细节之前,不妨来做个小题目:如何记录一个函数的运行时间?

不卖关子,很简单,写个函数,直接包装下即可:

function a() {...}

// 同步
function b() {
  const start = Date.now();
  a();
  const time = Date.now() - start();
}

// 异步
function b() {
  const start = Date.now();
  return a()
    .finally(() => {
      const time = Date.now() - start();
    })
}

现在你已经会做 APM 了,对的,不是开玩笑,原理即是如此。

当然了,实际做的时候,会有很多复杂的实现在那,比如,如何非侵入性地部署这个探针,以及如何在一次请求过程链中记录多个被调用函数的 metrics 等等。

众所周知,Node.js 由于它的异步原理,没有向有线程模型的同步语言中的**『线程本地存储』**,因此不是能很简单的不侵入代码,而做到记录每次请求的所有过程的,那么,现有的 APM 探针是如何做到的?

首先可以看看这个项目:async-listener

简单介绍下,就是把所有的 Node.js 基础模块中每个异步函数中 callback 参数做个包装,即针对以下这几个事件做成 hook,就相当于一个 async listener 了:

  • create: 进入了 Node.js 的事件队列;
  • before: 出了事件队列,马上要执行之前;
  • after: 执行完毕了;
  • error: 出错,执行的函数 throw error;

举个例子,比如 fs.writeFile(file, callback),包装之后:

fs.writeFile = function wrapWriteFile(file, callback) {
  asyncListener.create();
  process.addListener('uncaughtException', asyncListener.error);
  
  function callbackWrap(err, rst) {
    asyncListener.before();
    callback(err, rst);
    asyncListener.after();
  }
  fs.writeFile(file, (err, content) => {
    callbackWrap(err, content);
  });
}

于是每当一个异步函数执行时,对应的 hook 都会把相应的 metrics 记录到一个专门的 storage 中,而这个 storage 在整个调用过程中是共享的,所以最后就可以发送至专门的处理中心去了。

对了,利用这个原理,还可以做很多有意思的事情,比如 Continuation-Local Storage

P.S.1

到目前为止,async-listener 这个项目只是作为一个 monkey patch 的存在,官方至今没有实现,他们给出的解释也很简单:实现这个功能需要损失一定的性能,难度比较高。回到探针上面,其实探针也会让你的应用损失一定的性能,只是这个损失不会很大,而换来的效果收益确是远远高于这个损失的。

P.S.2

再扯一点,async listener 只是针对所有的 Node.js 基础模块做了包装,那么,它是如何在我们自己实现的模块**享的呢?

这是基础知识点,留给你了。

提示:Node.js 异步的特性来自于何处?传染性是什么意思?

Background Transcation

上面提到的探针,都是直接帮你把每次 Web Transcation 记录下来,那么,如何记录一些离线任务?

基本上,如果官方不提供 wrap 的话,只能自己侵入性的部署探针了,接下来以 NewRelic 为例:

文档在 这里 ,一开始文档里面没有很清楚的使用方法说明,也没有发现其它网站上现成可直接参考的例子,最后我还是从它的源码中发现了 例子

首先是函数定义:

/*
 * 开始 Transcation
 * @param name Transcation name
 * @param [group] Transcation group name, default: NodeJs
 * @param handle 要被追踪的函数
 */
newrelic.createBackgroundTransaction(name, [group], handle)

// 结束 Transcation,一定需要再结束的时候调用,不然整个 Transcation 会超时
newrelic.endTransaction()

然后是以 Automattic/kue 为具体的例子:

const newrelic = require('newrelic');
const queue = require('kue').createQueue();

/*
 * @return Promise
 */
function aLongBgJob() {
  ...
}

const TRAN_NAME = 'example';
queue.process(TRAN_NAME, (job, done) => {
  const wrapedJob = newrelic.createBackgroundTransaction(TRAN_NAME, aLongBgJob);
  aLongBgJob(job.data)
  .then(function(result) {
    newrelic.endTransaction()
    done();
  })
  .catch(function(error) {
    newrelic.noticeError(error)
    newrelic.endTransaction()
    done();
  })
})

当然了,最好是针对这些任务队列框架做个中间件 wrap 适配,统一处理,这里只是举个例子。

另外,千万记得不可以这样:

aLongBgJob = newrelic.createBackgroundTransaction(TRAN_NAME, aLongBgJob);

这样做的话,aLongBgJob 会被反复 wrap ,如果调用次数多的话,相当于被包装了很多次,于是会导致调用栈溢出,最后拖垮整个应用,别问我怎么知道的🙈。

其它两家的没找到文档,代码里面似乎也不明显。

Reference

  1. http://www.infoq.com/cn/articles/depth-2016-overview-of-apm
  2. http://www.infoq.com/cn/articles/tingyun-cto-interview
  3. http://www.infoq.com/cn/articles/oneapm-hexiaoyang-interview

Docker ELK搭建经验总结之二

运维了这么些日子的ELK,解决了些问题,总结如下:

禁止内存换出memlock

在docker中运行elasticsearch有个很大的问题就是

bootstrap.mlockall: true

这个选项不起作用,总是会报错,后来查了一下,在普通机器上需要运行这个命令:

ulimit -l unlimited

但是如果你在docker里面运行这个命令,会报错,提示无权限

解决方法是在运行的时候设置ulimit,我用的docker-compose,于是可以这样设置:

   environment:
      ES_HEAP_SIZE: 4g
      MAX_LOCKED_MEMORY: unlimited
      MAX_OPEN_FILES: 131070
    ulimits:
      memlock: 9223372036854775807 #2^63 - 1 as unlimited

需要说明下的是,docker的ulimit选项不支持string,那么你可以设置一个足够大的数字,比如int64的最大值即可。

系统IO瓶颈

在运行一段时间后,发现我设置的redis broker每隔一段时间就会溢出,然后数据被drop掉,这段时间es的索引速度也几乎降到0,查看hot threads以及iostat之后看到几乎被merge线程占满。

curl localhost:9200/_nodes/hot_threads

这时候解决方案是,要不换SSD,要不限制merge,我毫不犹豫的选择了后者。。。SSD太贵了。。。

可以考虑设置如下:

indices.store.throttle.type: merge
indices.store.throttle.max_bytes_per_sec: 20mb
indices.merge.scheduler.max_thread_count: 1

具体数值可以按需调整,我调整之前,ES(2.3.1)默认是

indices.store.throttle.type: none
indices.store.throttle.max_bytes_per_sec: 10gb
indices.merge.scheduler.max_thread_count: 9

参考

https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-merge.html
https://www.elastic.co/guide/en/elasticsearch/guide/current/indexing-performance.html

express中的trust proxy设置

一般来说,我们的项目都是放在反向代理后面的,比如nginx,haproxy之类的,这时候就会有个问题,你获取的IP地址可能一直是前面代理的IP,而不是用户端的IP,于是这时候express就需要设置下trust proxy了

默认是false,也就是不信任任何代理,比如你所在的私有网络是 10.0.0.0/8,那么直接设置:

app.set('trust proxy', '10.0.0.0/8');

即可,然后在代码中获取 req.ip 就可获得客户端的地址,req.ips 里面放是从 x-forwarded-for
取出的地址,当然了,地址会经过过滤处理,只会根据你设置的规则,只会把根据设置的规制,把受信任的IP地址留下。

然后,如果你觉得某个代理IP是值得信任的,也可以单独设置:

app.set('trust proxy', '10.0.0.0/8', '123.123.123.123');

当然,特殊的规制还可以用函数:

app.set('trust proxy', ip =>  /^123/.test(ip));

具体请参考官方文档

运维大招之重启

没有什么运维问题是一起重启解决不了的,如果有,那就两次。 --- 🙈

今天又栽在mongodb索引上了,哥们又在我不知情的情况下,加入了新建索引的代码,也怪我,没有review。两亿多数据啊。。。。天。。。。

于是我们的平均响应时长就跟过山车似的,一波未平,一波又起,甚至一度达到了我入职以来的最高值,6kms。

没办法,先重启,回滚了刚上线的代码,怀疑是update操作太频繁。等先把响应时长降下去之后,才发现是索引的问题,查看建索引进度,快结束了,那就等等吧,等了10多分钟,建立完毕,长吁一声,以为完事了。

哎,一度把复制集索引的建立过程给忘了,于是几个从库一起复现刚才的过山车,只不过更激烈~

一个字:删!

先把主库上的刚建立的索引给删了,然后遍历每个从库,kill掉index builder的opid,这里取了个巧,因为等待的任务数已经太高,而建索引时间已经远远超过100s,于是全部kill:

for m in mongo{1..10}
do
    mongo $m/dbname --eval "db.currentOp().inprog.forEach(function (p) {if (p.secs_running>100) printjson(db.killOp(p.opid))})"
done

好了,这下改完事了吧~

显然没,backend app内存报警OOM,varnish全部挂掉 😂

原因就是因为等待的op太多,把内存挤满了。

这时候,一直重启大法就行了,哪个出问题立马重启,于是等了好几个小时才完全稳定下来。

总结

好了,开头只是在胡说八道,但是说出了最简单朴实的临时解决方案:重启,的确它可以解决很多问题,但是终究只是临时的,比如今天这种场景,很多请求可以直接让它超时挂掉,而不是一直等在那里:Fail fast!

其它的呢,需要抓紧时间解决或者完善:代码上线的review,staging环境建立、CI搭建、线上流量测试以及重要接口的压测。

爬虫方案之zombie.js

这几天在做爬虫的东西,腾讯的登录真能把人搞死。。。
首先是对http头做了校验,一旦顺序或者大小写不对,立马报403错误,然后最后密码加密那块md5 + salt + RSA + 还有自创的TEA加密,我完全被搞晕了。

只能说它们的安全措施是做得非常好的,我搞了几天,完全用它的JS代码,本来还有希望的,今天再一看,登录相关的逻辑又改了。。。

所以,为了以后不蛋疼,想到用简单粗暴点的方案,直接模拟浏览器登录,搜了下,zombie还有phantomjs都可以,但phantomjs似乎重了点,先拿zombie试试:https://github.com/assaf/zombie

npm install zombie --save

这个项目看起来是做了很久了,但是最新的文档很差,我看了以前的文档是有的。

如下是个登录后取得cookie的例子:

function async getCookies(username, password) {
    // browser.debug();
    // browser.userAgent = ua;
    await browser.visit('http //example.com/login');

    await browser.wait({
        duration: '10s',
        element: '#submit',
    });

    await browser
        .fill('username', account.username)
        .fill('password', account.password)
        .click('#submit');

    return browser.cookies
}

缺点

太慢;
耗内存;

优点

能模拟真实用户登录;
可以在一定程度上绕过对方系统的反爬虫机制;
对于像腾讯登录那样复杂的前端加密验证可以简单绕过解决;

如何快速生成iTerm2 Dynamic Profile

日常服务器运维中,很多情况下,我们得登录到远程,当服务器一旦多了之后,管理还是挺麻烦的。之前一直用的SSH Shell这个小工具,一直用的挺顺畅,直到看到它在不断接收文本太多之后一次又一次奔溃,终于打算放弃。

后来仔细回想,似乎iTerm 2的Profile的就能达到类似的效果:

  1. 新建Profile,在"Send text at start"中输入"ssh host",保存;
  2. CMD + O,选择host,确定,就可以直接连到服务器上面;

只是,那么多的profile要我手工录入无异于侮辱我是个工程师的身份。

赶紧问谷老哥,恩,『自从iTerm2版本2.9.20140923之后,增加了个Dynamic Profile的功能』,简单来说,你需要根据特定的格式,xml或者json,提供一个配置文件,然后iTerm2会直接自动加载。只是!!!需要你来生成这个配置文件,恩,二话不说,毕竟在装逼的路上,咱得不点鼠标,少敲键盘

本来想着,干脆开脚本做个工具,但是!!!我是懒人好不,写个脚本费事不,有现成工具不用作甚!

⚠️ 接下来继续的话需要以下几个基础知识:

  • ansible (常用运维工具)
  • js-beautify (Node.js格式化代码工具)

首先,根据提示,为了制作这个profile.json(输出),我们需要根据先用的host列表来生成(输入)。很自然的想到,目前是根据inventory来管理hosts列表的,那么,我们是不是可以根据这个来直接生成呢?

对的,用ansible的template module:

# profile.j2
{
  "Profiles": [
{% for group in groups %}
  {% if group != 'all' %}
  {% for host in groups[group] %}
    {
      "Name" : "{{host}}",
      "Guid" : "{{group}}-{{host}}",
      "Initial Text" : "ssh {{host}} tmux a",
      "Tags": ["{{group}}"]
    },
  {% endfor %}
  {% endif %}
{% endfor %}
  ]
}

然后一条命令搞定:

ansible localhost -m template -a "src=./profile.j2 dest=./profile.json" --connection=local

接下来作为强迫症的我,必须要format一下:

js-beautify -r -s 2 ./profile.json

会有个小问题,因为上面的模板中,最后个元素后面还有逗号,严格来说是不允许的,但是iTerm2不介意,所以不用管。

然后移到目录即可:

mv ./profile.json /Users/x/Library/Application\ Support/iTerm2/DynamicProfiles/

现在,CMD + O,直接输入host名称,按确定,立马连上,那叫一个爽~
另外,不妨看看iTerm2上面的Profiles选项,你会看到,上面的组织方式,完全是按照你在inventory上面的分组一致,恩,不谢~

Reference

记一次类微博API重构

最近看APM上面的API响应,发现有个API在挑事,平均响应时长4s,已经到了不可忍受的地步。

仔细一看,发现是动态有关的API,类似于微博,进一步分析发现,这个API设计不合理:用了mongo的aggregate,占了响应时长的90%以上,这个不适合在API使用,因为非常耗数据库的计算性能。

Mongo数据库结构

User: {
  _id: ObjectId,
  followings: Number,
  followers: Number,
}
TweetPage: {
  user: ObjectId,
  count: Number,
  oldest: Date,
  newest: Date,
  tweets: [{
    _id: ObjectId,
    title: String,
    content: String,
  }]
}
Follower: {
  user: ObjectId,
  followers: [ObjectId]
}
Following: {
  user: ObjectId,
  followers: [ObjectId]
}
ReTweet: { //转发的Tweet
  _id: ObjectId,
  title: String,
  content: String,
}

每个用户有一个TweetPage,于是每次请求TimeLine,都会把所有当前用户follow的用户的TweetPage取出来,做一个TweetPage.aggregate,由此,这个API才会那么耗时间。

主要原因

  • mongo的aggregate太耗时间
  • 数据结构设计不合理,TweetPage里面的Tweet数组太大,随着时间越来越久,数组会越来越大,类微博是一个典型的高读写比的场景,取TimeLine的计算时间太长

于是,重构方向就是针对这种场景下的数据高读写比,重新设计数据结构。同时,还需要顾及以后的产品改进方向,而且重构不可改变API的行为,所以不添加任何新行为。

具体步骤

  • 第一步:跑通测试,由于这部分模块长久不维护,测试大部分已经失效,首先需要修改测试,并且补充必要的测试,同时这一步也是帮助你理解相关的业务逻辑
  • 第二步:格式化代码,应该来说,还是由于时间长久的原因,之前的团队里,代码风格不统一,因此格式化代码很有必要,也是为了之后的重构代码不被格式改变所污染,这里需要单独做一个git commit
  • 第三步:重新设计数据结构,当前数据结构中的ReTweet是个妥协的存在,因为TweetPage设计失败了,ReTweet是为了转发的API而添加的内容。因此,Tweet有必要拿出来,单独作为一个collection,TweetPage也可以去掉了。同时,为了提高获取TimeLine接口的性能,单独添加TimeLine collection,每一个用户一个document,每当用户添加或者删除Tweet,或者follow关系改变的时候,主动更新follow的用户TimeLine,获取TimeLine的时候,直接获取当前用户的TimeLine即可,然后取出对应的Tweet。进一步,可将一些热门Tweet放在缓存中。由此,解决了微博的高读写比问题;
  • 第四步:实现,此过程中必须不断重新跑测试,来保证重构的效果;
  • 第五步:编写相关的数据迁移脚本,指定上线步骤以及回滚步骤;

说在最后

此次重构,其中包含了一个计算机科学中非常朴素的简单原理:用空间换时间,因为用户『写』微博的时候比『读』微博的次数少,因此可以用增加写的时间与空间,来换取读的时间减少,也不需要更多的计算。

团队内部推广之我见

一些经验较少的管理者或者项目经理在团队内部推广新技术、新工具的时候,往往,会遇到阻力。

在这里,先讲一个小故事:

小 A,在一个传统互联网公司,刚上任他所在的小团队开发技术经理不久。为了提高公司技术团队的工作效率,最近学习了不少 DevOps 有关的技术与文化,想在团队以及公司内部推广起来,于是他组织了一场分享会,蒙着头花了不少时间去做 PPT,分享的那天,来了不少周围的开发以及运维同事,过程中小 A 激情满满,会后大家也表示应该好好学习与实践。但是,之后发现大家真正落实的情况很少,大部分还是继续着之前的做法,一直不变。小 A 很气愤:明明这是一项非常牛逼的技术/工具,为什么这帮人不好好实践呢?于是变得很气愤,抱怨大家的不支持以及不作为。

我相信你如果想在团队内部推广的时候,往往也会遇到类似的情况:理想很美好,现实很残酷

如果小 A 太年轻而没有经验,他往往会很沮丧,毕竟这是对大家有好处的东西,推广难度为什么那么难。

原因

我也经历过类似的过程,我把原因归结为如下几个:

急于实施

按照信息论的观点,你在推广这些新技术,新文化的时候,大家不一定有着跟你同样的信息量,或者说有些人的信息量甚至比你更多些。

另一方面,你其实对这个技术、文化,并不是那么的了解,导致你向大家推广的时候,你的实践经验并没有得到大家的认可,你会缺乏实践经验去帮助同事们去实施以及提高。

缺乏支持

需要肯定的是,小 A 的做法还是正确的:他在这个过程中,把大家集中在一起分享,告诉大家这是个好文化,而不是通过自己的权力直接命令手下的同事改变。

那么,如果小 A 事先去跟领导以及一些老同事去交流,一个一个的听取他们的意见,补充自己的不足,并让他们提前了解到这个东西的好处,在这个过程中取得大部分权威人员的支持之后,情况会好很多。

利益冲突

小 A 所在的公司,是传统的开发与运维分开的技术团队,开发只负责开发,一旦开发测试完毕,就交给运维去部署,然后只管回家睡大觉。

很明显,对于开发同事来说,如果让他们来负责一部分上线运维工作的话,他们会非常不乐意,毕竟如果上线有问题,他们就会非常难受:一方面需要对上线事故负责,另一方面,本来他们只管在家睡大觉就行了,何必那么痛苦呢?

而对于运维同事来说,痛苦归痛苦,但是开发来负责上线了,是不是要来抢我的饭碗呢?

其实,小 A 要做的是让同事们明白,推广这个文化,对公司以及大家长远来说都是有好处的:

开发会对自己的代码更负责任,因为他们直接面对了用户,能够及时发现自己代码的不足之处,也能够快速定位并且修改 bug,而不是等运维同事把问题反馈了之后才慢慢修改,中间去除了沟通成本。

运维能更专注于公司运维基础平台的维护,不用为线上发布问题头疼,也不用傻傻等着开发修改 bug 才能恢复线上服务的部分功能,甚至在这个过程中跟开发扯皮,互相指责对方。

而对于公司整体来说,小 A 提倡的 DevOps 能够帮助公司节约成本,提高工作效率,减少线上事故的发生次数以及处理时间。只是,这种文化需要公司整体的支持,一个人起的力量是很弱小的。

改变环境

上面简单说了一些,这里说说,心态上的问题。

小 A 最后的心态不好,埋怨解决不了任何问题。同样的,我们需要明白,当向现有的团队引入任何变化的时候,大家都是比较谨慎的,毕竟如果这个东西实施之后变得不好,浪费时间精力还能忍受,万一把公司搞乱套,甚至影响线上服务的话,还不如维持现状。

因此,我认为,改变自己的心态最重要,静下心来,慢慢提高自己,然后慢慢去改变周围的同事,让他们深切体会到现有的痛处,以及能得到的好处,从而赢得他们的支持。

然后呢?慢慢改变领导者的心意,最后改变公司里的文化环境。

其实就那么简单:改变自己,这是最容易的,但也是很难意识到的。

只要小 A 能一步步坚持自己的想法,不抱怨,静下心来,一步一步去改变,成功就会越来越近。

插一句,为了静下心来,不妨背一遍荀子的《劝学》:『蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。』

戒骄戒躁,规划好自己的目标,打好基础,慢慢布局,一步步去实现。

Reference

  1. https://zh.wikisource.org/zh-hans/%E8%8D%80%E5%AD%90/%E5%8B%B8%E5%AD%B8%E7%AF%87

关于生产环境日志收集

日志收集的重要性不言而喻,总结下在日志方面做的一些尝试:

日志收集的选型

目前nodejs比较流行的工具有winston,log4js,还有bunyan,也是我用过比较多的,总体看来的话,winston功能丰富,log4js老牌有保障,bunyan比较轻量级,我最后选择了bunyan是因为看中它的轻量级,怎么说呢,winston太复杂了以至于影响到了我们系统的性能,具体可以看看这个:A Benchmark of Five Node.js Logging Libraries

收集工具

当然是ELK,有条件的话最好搭一个集群,还有SSD的机器。这个成本呢,可以这么来跟你老板说:『养兵千日用兵一时』,有些致命的线上错误,只能用这个日志来发现与调试,同时呢,方便我们开发与运维,对提高系统性能,提早发现问题有很大的帮助。

日志传输方式

首选UDP,传输快,不占用应用机器的磁盘,缺点么,丢数据,UDP么;
TCP的话,如果日志集群足够稳定可以考虑,几乎不会丢数据;
然后还有elastic公司的filebeat,这个就需要把日志输入到应用机器的文件里面了,只要日志写入到文件,几乎不会丢数据,只是部署比较麻烦,在一定程度上,限制了应用机器的快速横向拓展;

日志规范

最好是每个请求都只有一条日志,这样ELK的压力会小一点,如果请求一条数据,返回一条数据,那就是x2了,如果重要的接口,需要多条日志,那就是更多了。

可以考虑在每条日志中加入reqId,即请求ID,每次请求若有多条日志,reqId是把它们串起来的唯一线索,(用bunyan另一点就是它的child logger,可以将reqId放在生成的child logger上,然后附在res或者req上面,后续的middleware直接打日志即可,不用管reqId)然后也需要写在response header里面返回给客户端,这样的话,可以方便调试,APP开发的同事直接给这个reqId,就可以查询了,日后如果客户端也需要上传日志,也需要将reqId上报。

其它呢,能记上的都记上,可以方便日后的日志分析。

日志安全

日志里面可能会有一些敏感信息,反正端口不能暴露在外网,然后如果真的需要的话,最简单的方式是用nginx做个反向代理,做个basic auth,但是!!!不建议这么干!

其他的方案么,sheild,哎。。。要收费,Search Guard 挺不错的,功能丰富,细粒度的权限控制,只是配置起来真的挺麻烦,人员多了之后可以考虑配置一下。

其它作用

日志除了查询与分析之外,还有个作用就是报警,这个目前还没做,不过做起来也挺简单,定时去ES查询相关的结果,直接根据结果决定是否报警即可。

进程管理器pm2运维小结

pm2这个进程管理器是越来越好用了,最新的版本已经开始支持docker,不过我不看好,docker镜像内的node进程挂掉就应该让集群管理器去处理,简单有效,并且也准守Fail fast这个准则。

正文开始,我把最近用到的几个点总结下,(之前的 #4 也有一些总结) 。

使用yml配置文件

还在使用json文件来配置么?放弃吧,yml更友好,更具可读性,配置项也跟json保持一致。
http://pm2.keymetrics.io/docs/usage/application-declaration/

设置最大内存

不知道你是否了解node.js的内存限制,一般默认的大小为1.76G(64bit机器),一旦超过,就会自动崩溃,并且在*nix平台上生成core.pid文件,所以有时候你会看到working dir中生成了一堆的core文件,不妨用file命令看下,相信你会发现都是node进程生成的。

解决方案很简单,pm2设置max-memory-restart参数,这个值必须小于1.76G。当然了,如果你确实需要大内存,可以设置node参数:--max-old-space-size=xxxM,这个值可以随便设置,但建议多测试后再决定,可能大内存限制下会出些问题。

拓展一下

关于core文件,本来是用来调试段错误的,但是如果你不是写C++ AddOn,或者调试什么node的bug,那么可以禁用掉:用Linux本身的方案来解决。

  1. 限制大小:ulimit -c filesize(单位是kb),超过会被裁减;
  2. 更改生成的core路径以及文件名;

默认情况下,/proc/sys/kernel/core_pattern为core, /proc/sys/kernel/core_uses_pid为1,即生成的core文件默认为working dir中的core.pid。
如何修改:

%%	单个%字符
%p	dump进程的进程ID
%u	dump进程的用户ID
%g	dump进程的组ID
%s	导致core dump的信号
%t	core dump的时间
%h	主机名
%e	程序文件名

echo '/data/core-files/core.%t.%p' > /proc/sys/kernel/core_pattern

禁用pid拓展名

echo 0 > /proc/sys/kernel/core_uses_pid

当然了,都是临时的,永久修改需要编辑/etc/sysctl.conf,加入如下两行:
kernel.core_pattern = /data/core-files/core.%t.%p
kernel.core_uses_pid = 0

自启动脚本

不知道你是否每次重启之后是怎么启动pm2的,但是如果不是自动启动的就有点落后了。如果用init.d或者systemd,那也不错,也就维护有点小麻烦,每次调整都得更新脚本。其实pm2已经有了相关的命令来帮你完成这一系列的操作:

生成自启动脚本

pm2 startup centos(ubuntu, centos, redhat, gentoo, systemd, darwin, amazon之一,具体请看文档)

生成dump.pm2文件

pm2 save(或者dump)

经过这一步之后,机器重启之后,就能保证pm2自动启动了。

crash恢复

pm2 resurrect

不过,这个命令不适合手工去用的,可以设置个crontab脚本,一旦检测到pm2 daemon的进程不存在,则执行这个命令。

关于API文档

先从一次面试经历说起,记得有次面试,我问被面试者一个API文档的问题:

『用gitbook。』
我呆了一下
『那如果你们修改了API接口,怎么修改文档呢?』
『我们文档也用git管理,直接修改提交。』
『那你们有没有某次代码修改之后,忘记修改文档了?』
『……』

我想在实际开发中,这样的做法很容易顾此失彼,忘记更新代码,造成了文档与代码不一致的情况。

众所周知,好的文档能提高使用这个项目的工程师的开发效率,相信你也遇到过这样的情况,某个开源项目,照着文档实现后发现结果不理想,甚至无法运行的情况,然后只能去看代码。(其实从另一个角度说,这也能逼着使用者去看代码,理解原理。。。)

只是,在同一个项目里的时候,APP工程师不用去看服务器工程师实现的代码吧?于是一份好的文档能省不少事,提高各自的开发效率。如果你遇到过,Android跟iOS开发工程师分别来问你API接口的情况,你就很能理解了。

一个我认为理想中的跟APP工程师协同开发流程是:先跟所有人,包括产品经理一起看文档,弄清需求,分拆任务,约定好接口,参数之类的。然后服务器工程师按照约定写一个mock API提供给APP工程师,之后两边各自开发测试,直到最后联调通过。

其中约定好的API接口,由于在开发过程中可能会改,于是,一个可以动态更新的API文档就很重要。有这么几个选择:

  • apidoc.js
  • Evernote之类的笔记共享
  • gitbook

很明显,我认为将文档跟代码放在一起是最好的选择,文档按照一定的格式写在注释里面(没错,我就是来安利apidoc.js的 😆 ),代码一改,直接在代码上面的文档修改,然后在测试环境部署时,加个pre-update生成新文档即可,通知APP工程师即可。

这样的话,可以避免一定的文档与代码不一致的情况。(当然,如果服务器工程师懒的写文档的话,那也谁也救不了。

CentOS 7 使用 ansible 搭建 kubernetes

一直以来,我使用的是 rancher,它提供的 k8s 集群非常棒,基于 docker 镜像安装,免去了很多的安装配置细节,如果对于 k8s 的运行原理不想太深入了解、图方便快捷或者只是想尝试功能,那么它会让你绝对满意:在界面上点击配置下就能搭建起一个集群了。

但是这次我就需要自己真正在机器上搭建起一个原生的集群,折腾下自己。众所周知,k8s 的安装,绝对是个坑,尤其是国内,不翻墙基本上就没戏,对比国外的技术环境,瞬间觉得,国内的技术环境下,能做出好的世界性项目绝对不容易,当然 GFW 除外。

好了,吐槽结束,下面开始正题。

工具准备

首先是代理或者 VPN,这个没的说,不然很多东西都得浪费一整天去下载。

其次是机器,一般来说,4 台机器就差不多就可以搭建一个 k8s 环境:1 台 master,3 台 node。目前,我手里面只有 CentOS 7.2 (内核版本:3.10.0-123.4.4.el7.x86_64),接下来的例子也是以它为例来讲解。

最后是安装工具:ansible,我提到过很多次,是 IaaS 的代表工具之一。还有要用到的代码:https://github.com/kubernetes/contrib/tree/master/ansible

技术准备

如果不了解 k8s 就直接开始安装的话,会有问题,或者说,出了问题之后,你很难去解决。

在这里不方便多说,请移步文档:https://kubernetes.io

然后是 ansible,如果不了解具体的搭建过程,相信你会有种不安全感。

好吧,知道你懒,我简单解释下 playbooks/deploy-cluster.yml

这里面是 k8s 经典的搭建步骤:首先搭建 etcd 集群,然后是 docker, 然后是网路层,一般使用 flannel(当然,也有 opencontrail 以及 contiv,但是我没尝试过),最后才是 k8s 层。

这个过程中要部署的模块如下:

  • master:
    • etcd
    • flannel
    • kube-apiserver
    • kube-controller-manager
    • kube-scheduler
  • node:
    • etcd
    • docker
    • flannel
    • kubelet
    • kube-proxy

一些修改

添加 inventory/inventory 文件:

[masters]
master1.example.k8s.cluster

[nodes]
node[1:3].example.k8s.cluster

[etcd]
node[1:3].example.k8s.cluster

其实这个项目是有不少问题的,害得我一直无法安装成功,其中一个问题我提交了一个 commit 去修改,但是目前还没被合并进主分支。你可以参照着修改。

另外,它提供的 flannel role 问题也不少,在 CentOS7 下没有修改 docker 的 options,导致一度 k8s pod 的 IP 地址有问题。这里需要说明下:如果你了解 k8s 控制 docker 集群网络的原理,这个问题很容易解决,因为它就是通过 flannel 之类的网络层工具,用 docker 的 bip 参数去把 cluster 的节点串联起来

因此,我提供的临时解决方案是直接在 /etc/systemd/system/docker.service.d/ 中加入如下 drop in 配置文件:

# docker-option.conf
[Service]
EnvironmentFile=-/run/flannel/docker
# DOCKER_NETWORK_OPTIONS=" --bip=172.16.0.1/24 --ip-masq=true --mtu=1404"
ExecStart=/usr/bin/dockerd $DOCKER_NETWORK_OPTIONS

另外,就是 针对实际项目的配置修改了:

# inventory/group_vars/all.yml

# 代理配置
http_proxy: "<your proxy here>"
https_proxy: "<your proxy here>"
no_proxy: "127.0.0.1,localhost,get.daocloud.io"

# yum 源下,包名不是 docker
docker_package_name: "docker-engine"

# 使用 国内 daocloud 提供的 yum 源,不用翻墙
docker_source_type: "custom-repository"
docker_repository_baseurl: "https://get.daocloud.io/docker/yum-repo/main/centos/$releasever/"
docker_repository_gpgkey: "https://get.daocloud.io/docker/yum/gpg"

# 1.6.* 有较多不兼容的修改,导致无法配置成功,而 1.5.6 是 目前 GCE 上的默认版本
kube_version: 1.5.6

# packageManager 会有依赖包问题,我测试下来一直无法安装成功
kube_source_type: github-release

# 默认的 443 会有 bind 权限问题
kube_master_api_port: 8443

# 以下两个都建议关闭,按照 dashboard 这个项目的说明来安装:https://github.com/kubernetes/dashboard
kube_ui: false
kube_dash: false

的确,这个 kubernetes/contrib 目前很烂,这么多子项目都堆在一起,维护的又不是很好,但是,大部分还是可以用的,所以也就凑合着用吧。

开始搭建

cd scripts
./deploay-cluster.sh

有问题欢迎交流。

k8s Ingress 实践

一般来说,我们从外部访问 k8s 里面的应用,有以下种方式:

  1. Ingress:有些云服务商有提供,自己也能安装自己的 ingress controller;
  2. Service NodePort:在 Node 上暴露一个 30000-32767 的端口,可以通过 NodeIp:NodePort 的方式访问;
  3. Service LoadBalancer:取决于云服务商,目前似乎只有 AWS、GCE 以及国内的阿里云有提供;
  4. Kubectl Proxy:通过本地执行 kubectl proxy,然后访问 http://localhost:8001/api/v1/proxy/namespaces/namespace/services/service-name 即可;
  5. Kubectl Port-forward,与 Proxy 类似,测试可以,正式环境就不用考虑了;

显然,在一个云服务商不支持的环境下,ingress 是我们最佳的选择。

概念

Ingress 是允许外部可以从集群内部的相关服务的一系列规则。

用 Ingress,你可以定义一些列的转发规则,具体的实现是由 Ingress controller 完成,一般是 Nginx 或者 HAProxy。

其它的细节,不妨看看官方文档。

另外,对于 Ingress controller,这个项目里面的介绍也可以参考:https://github.com/kubernetes/ingress

部署 Ingress controller

由于 Ingress controller 属于集群的基础服务,我们想要它随着 Node 的启动之后,首先启动,因此 DaemonSet 最合适。

接下来搬运下代码:

# https://github.com/kubernetes/ingress/blob/master/examples/daemonset/nginx/nginx-ingress-daemonset.yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: nginx-ingress-lb
  labels:
    name: nginx-ingress-lb
  namespace: kube-system
spec:
  template:
    metadata:
      labels:
        name: nginx-ingress-lb
      annotations:
        prometheus.io/port: '10254'
        prometheus.io/scrape: 'true'
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.7
        name: nginx-ingress-lb
        readinessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          timeoutSeconds: 1
        ports:
        - containerPort: 80
          hostPort: 80
        - containerPort: 443
          hostPort: 443
        env:
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
        args:
        - /nginx-ingress-controller
        - --default-backend-service=$(POD_NAMESPACE)/default-http-backend

P.S.

  1. 由于声明了 hostPort,各个 Node 的相关端口不能被占用;
  2. 默认情况下,如果提供了默认 https 证书,则会将 80 端口转发至 443 端口,可以通过在创建 Ingress 时,设置 metadata 的配置:ngress.kubernetes.io/ssl-redirect: "false",或者 为 Ingress 提供全局 ConfigMap 配置:ssl-redirect: false;
  3. 由于 Ingress 目前还是处于测试阶段,生产环境的测试还不一定能完全放心地上,因此可以先在 staging 环境使用,这时候可以考虑 goreplay 之类的工具去复制线上的流量去测试。

Ref

  1. https://kubernetes.io/docs/concepts/services-networking/ingress/
  2. https://kubernetes.io/docs/concepts/services-networking/service/

记一次mongodb索引紧急事件

今天正在家里加班的时候(哎。。。不多说。。。),突然接到电话,mongo出问题了,同事把某个collection的索引给删了,新代码上线之后,mongo不断在重建索引,经过立马回滚代码之后,还是继续在重建索引,用db.currentOp来看,索引一到99%就重建,也就是重新开始建立索引。

在APM上面看到响应时间慢了5倍左右,一直不断重建索引的影响很大。我第一直觉是mongo在不断执行重建索引的命令,于是吩咐同事把schema里面建立索引的语句删掉,再观察。过了几分钟之后,还是不行,猜测可能是有排队的op,于是killOp,立马正常了,再查看索引,已经建立完毕,响应时间也恢复正常。(找时间测试下具体原因,也许是mongo的bug)

这次事故,应该说暴露出了我们运维方面的漏洞,相信同事非常自责。不过,我觉得这不单单是他一个人的责任,团队管理上面问题更大,一直在说要限制每个人的生产服务器权限,但是一直没时间做,觉得业务开发更重要,今天就暴露问题了。

因此,我觉得,当团队发展到一定程度,可以是招新人的时候,一定要限制新人的生产服务器权限。然后,建立staging环境,这个权限可以给高点,方便所有人测试,所有的操作最好先staging环境测试再到生产环境使用,同时,也给新人机会去学习。

然后,数据库服务器最好一开始就设置权限,禁止非生产服务器之外的APP连接,drop database这样的事故发生过不止一次。同时,这也是方便以后,不然以后从没有权限迁移到有权限也是很头疼的一件事。

下次谈谈staging环境建立的事儿。

谈重构

最近总能发现,很多东西,我在实践中总结出的的经验,都是别人在书中提到过的,就比如《重构》这本书,我一直认为重构不应该拿出专门的时间,而是应该在开发新功能或者修改bug的时候去做,作者也非常强调这种观点:

不用专门时间,随时随地,比如以下三种情况:
添加功能时
修补错误时
复审代码时

比如在一个人员缺少的阶段,如果你跟产品汪或者CEO说,我需要几个星期甚至几个月去专门重构下,相信他们砍了你的心都有了。不是说这时间不该花,而是,这个时间可以算在功能开发上面,你跟产品沟通清楚:『
我们开发的这个功能是基于某个以前的功能的,但是需要改进它,这样的话,以前的功能可以更稳当或者更快,然后呢,以后的相关开发可以更快更好,并且这个重构只是将这个功能的开发时间延长了一到两天』,让产品汪明白重构的价值之后,相信他们也会理解到这个时间是必须给的。

另一个小的原因在于,你在重构之后,大脑正处于高度集中状态,会对相关的代码以及功能非常熟悉,开发也会快一些。

然后再谈谈其它的几点:

坚持以持续不断的重构行为来整理代码

我觉得代码洁癖是每个软件工程师应该具备的素养,当你看到别人或者自己一两个星期之前写的代码感觉很烂的时候,就应该做点什么了。

在不改变代码外在行为的前提下,对代码做出修改,以改进程序内部结构

此时最适合用TDD了,以前偷懒欠下的债,这时候改还了,把单元测试补上,然后开始重构。测试是重构的灵魂啊,把CI搭起来吧。

三次法则:
第一次,只管去做,第二次做类似的事,会产生反感,但无论如何还是可以去做,第三次再做类似的事,你就应该重构。

这个说的应该是完美心理,跟代码洁癖是一起的,应为洁癖来自于完美心理,只是为了效率来说,刚开始可以不用考虑太多,做出来功能是最重要的,然后通过功能的表现来慢慢重构,因为,好代码往往诞生于来自于重构之后。

PS:

虽然觉得别人已经总结出了经验,但是,自己去发现不是更有意思点么,就像看电影,如果直接告诉你结局,那这部电影你可能不会想去看了,更何况,提前看了也不一定马上能理解。

当然了,多看这种书没坏处,毕竟人一生那么短,有些弯路也没必要再走一遍。

ELK 最近的一些总结

不要执迷于登录服务器敲命令,而是用自动化脚本完成所有的事情

各种博客,包括我自己,一眼望过去,全是教你一步步怎么搭建某个服务,其实这是一个**『反模式 anti-pattern』**。

为什么?

  • 首先,不可重复,他人在他们的服务器上搭建的时候,环境有可能不一致,相同的命令,可能会有完全不同的效果;
  • 其次,浪费时间,当你登录服务器一步一步操作,意味着你只能一步步操作,也就是说你不能并行部署服务;

因此,你得充分利用工具的力量,ansible,puppet,chef 等之类的部署工具可以将整个过程变为可重复执行的代码,每次只要执行代码,就可以保证得到相同的结果,节省你还有他人的时间,如果这个过程是大家经常用的,不妨开源出去。

不要过分浪费时间在挖掘服务器的性能上

能用 SSD 硬盘就用上,花不了多少钱,服务器调优的时间,完全可以去开发更多的业务功能或者更有价值的事情上去。

而当你的老板觉得这个 ELK 日志集群服务器很花钱的时候,告诉他能得到什么:

  • 快速定位问题的能力,日志里面记录着所有问题的蛛丝马迹;
  • 更多维度的监控能力,也就是快速发现问题的能力。不妨计算下,宕机一小时,你们会损失多少钱;
  • 可用来分析用户行为,作为个性推荐的基础数据;
  • 可用来反爬虫,平台大了之后,总会有爬虫来爬你数据的,遇到恶意爬虫,甚至会造成服务器的不稳定,也是造成损失的因素;

ELK之外的一些工具

Curator

这个工具是用来运维的,包括删除老的数据,备份数据等等,可能是因为配置太复杂了,命令行确实太复杂,所以 Curator 提供了基于 yml 格式的配置文件来执行相关操作。

举个最常用的例子,删除旧的 indices,释放空间:

# config.yml
---
client:
  hosts:
    - localhost
  port: 9200
  url_prefix:
  use_ssl: False
  certificate:
  client_cert:
  client_key:
  ssl_no_validate: False
  http_auth:
  timeout: 30
  master_only: False

logging:
  loglevel: INFO
  logfile: /var/log/elasticsearch-curator.log
  logformat: default
  blacklist: ['elasticsearch', 'urllib3']
# action.yml
---
actions:
  1:
    action: delete_indices
    description: >-
      Delete indices older than 4 days
    options:
      ignore_empty_list: True
      timeout_override:
      continue_if_exception: False
    filters:
    - filtertype: pattern
      kind: regex
      value: ^logstash-
      exclude:
    - filtertype: age
      source: name
      direction: older
      timestring: '%Y.%m.%d'
      unit: days
      unit_count: 4
      exclude:

然后每天凌晨定时执行:

0 0 * * * /usr/bin/curator --config <config-dir>/config.yml <config-dir>/action.yml >> /dev/null 2>&1

grafana

这个不属于 elastic 公司,它提供的是图像监控功能,因此这一部分它比 kibana 的 visualize 强大一些,不过,各有长处。

另外,用 docker 部署很方便的 :P

docker run \
  -d \
  --restart=always \
  -p 3000:3000 \
  --name=grafana \
  -v /data/grafana:/var/lib/grafana \
  -e "GF_SECURITY_ADMIN_PASSWORD=test" \
  grafana/grafana

filebeat

这个是最近才出来的,从文件中收集日志数据,然后发送到 logstash 或者 elasticsearch,它的一个很有意思的功能就是,可以根据 ELK 集群的负载情况,自动调整发送的速度,于是 logstash 这里就可以拿掉 broker 作为缓冲了。

metricbeat

这个就是用来收集应用的 metric 的,相当于 zabbix-agent,刚出来不久,但是挺有发展前景。我给它加了个 pm2 的 module :https://github.com/xizhibei/beats/tree/master/metricbeat/module/pm2 ,用来收集 pm2 跑 node 的一些监控信息。

异构数据库之数据同步实践

一般来说,我们的不少业务中,需要用到数据同步,而其中涉及到的本质无非是**『数据一致性』。首先,能想到的数据同步的例子肯定是数据库,由于数据库领域存在的 CAP 理论,一定会有数据同步的过程来达到数据一致性,只是那是属于相同数据库之间的同步,在不同数据库的情况下同步数据的话,叫做『异构同步』**。

不过目前先放下一致性原理,以现有的一个业务场景为例,来说说如何实现异构同步。

业务场景

线上有一个采取了分表与分库的 MySQL 数据库集群,主要存放订单等数据,业务上想要看到订单的统计结果,但是如果直接在集群上跑脚本的话,容易将数据库拖垮,因此,需要同步数据至其它数据源进行统计,比如 ES、HBase 等。

解决方案

完整克隆

这个最容易想到,数据库 A 完整导出数据,然后导入至数据库 B,缺点显而易见,不适用于持续增长的数据,可以勉强适用于每天导出部分数据,然后用 Excel 统计。(对的,一开始业务部门要数据的时候,你就会这么干,如果你还在这么干的话,考虑下其它方案吧,后期他们一遍又一遍来找你导数据的时候,你会发飙的。)

标记同步

如果业务简单,插入到数据库中的数据不会发生变化,即日志型数据表,这时候就可以根据标记,比如时间戳来同步,即使发生故障,也可以从上一次同步的位置开始重新同步。

实时同步

即将线上产生的数据,实时写到两个数据库,显然会拖慢应用,可以考虑做成离线的后台任务队列去做。只是在数据实时性要求不是那么高的情况下,不建议使用。

数据库日志同步

比如 MySQL 的 binlog,MongoDB 的 oplog,包含了所有的数据库记录变动记录,可以解析相应的 log 数据格式,来达到完全同步到其它数据库的目的。这种方法兼容程度高,几乎不会丢数据,只是可能需要较多的开发,工具的话也有,目前还没有尝试过。

需要提醒的一点是:如果是云服务器厂商提供的数据库实例,可能你无法获取相应的 log,就比如 MySQL 的 binlog 。即使你能获取,还有一点很头疼的是,你需要将 binlog 设置成 row 模式,才能去同步,这样会造成 binlog 非常大,高写入场景下不适合。

一些 Node.js 数据同步脚本实践经验

首先是几个比较好用的 cli 工具:

  • commander:用来处理命令行参数,另外还有个最近出来,挺火的:Caporal.js
  • chalk:输出各种颜色,用来调节写脚本时候郁闷的心情;
  • progress:进度条,不知道同步进度的时候会很蛋疼,而一直用 console.log 或者 debug 的话,容易输出一堆没用的 log 。(相信我,当你同事看到进度条在刷刷的动的时候,尤其是产品🐶,他们会用崇拜的眼神来看你的 😝);

然后,注意内存,同步脚本的时候,这个脚本所占的内存会比较大,可以考虑设置大一些:

node --max-old-space-size=2000 ./script.js

还有并发数,如果开的比较高,需要注意连接池

最后,如果使用 http 协议发送数据,则需要在跑脚本的机器上设置文件句柄限制(数字自己调整即可,这里只是个例子):

ulimit -n 100000000

记一次mongo性能提升报告

最近的一直进行的工作包括提升mongo的性能,在我接手的这段时间里,在newrelic上的平均响应时长是200-300ms,遇到任务堵塞还会暴涨到600ms左右一小段时间。

第一次

尝试是升级[email protected],效果是很明显的,响应时长立马降下来了,平均响应时长100ms左右。但,我们观察到mongo的连接数直接升了一倍,app进程在阶段性重启,大约是20分钟一次,因为我们用pm2的时候,设置了最大内存,一旦超过就自动重启,于是我们放弃了。

期间给mongo-core改了一个问题,如果使用readReference=secondaryPreferred的话,如果监听的是open事件的话,立马开始读数据库则所有的请求都会到master上面去。看了代码之后,我尝试监听fullsetup事件(顺便吐槽下,这代码,真烂。。。),但请求只会到master以及一个secondary上面去。

于是我改代码,我把'all'事件给暴露出来了,于是当监听all事件的话,就能均匀分布到所有的节点上门去了。当然了,这种情况只会在这种特殊情况下出现,具体测试可以看这里:https://github.com/xizhibei/node-mongodb-test-utils

同时呢,还有另一个问题,就是连接数爆的问题,这真是它的bug,我那天刚给它改了,等我想提交的时候,作者自己改了,因为这问题不止我一个人发现。目前最新的[email protected]已经没有这个问题了。其实响应时长降低的原因就在于几乎是每一个请求就会被分配一个连接,所以连接数非常多,响应快,但是代价是内存泄露以及连接数爆棚。

第二次

调整连接参数,就是之前提到过的 #2 ,调整了连接池以及keepAlive的数值。

第三次

尝试升级到[email protected],顺利,响应时长有小幅度的下降。但是当时又出现了一个问题,其中一个数据库的连接数很高,TCP TIME_WAIT是Established的两倍,然后会几个小时就暴增一次,于是nodejs进程出现大量fail connect错误,整体响应时长也暴增。

另外,3.8.394.4.12的对比效果不明显,反而是3.8.39的效果稍好一些

第四次

经过了第三次,我突然想到,数据库本身是不是有问题,我看了下数据库版本,3.0.3,然后去官网一查,我去,果然有问题!其中刚好就是这个版本,有一个复制集中任务阻塞的bug,在3.0.4中修复了https://docs.mongodb.org/manual/release-notes/3.0/

于是安排运维升级,升级到最新的3.2版本,运维说不行,没法直接升级,于是用export与restore,但是之后无法加入集群,具体原因不清楚,以后再查,先升级要紧,于是改3.0.11,顺利升级。

戏剧般的效果出来了,响应时长瞬间将至40ms左右,升级的那台数据库的CPU与查询数瞬间暴涨,但是任务数却没怎么升高,连接数正常,其他数据库的查询几乎没有了,也就是说,这一台数据库就几乎抗住了所有的查询。后来陆续升级了,目前一切正常。

这结果,我梦里都能笑出来,目前性能提升10倍左右,平均响应时长30ms,newrelic的apdex指数文档在0.98,之前是0.7-0.9。

两点教训

一、很多性能问题还是得靠升级解决;
二、性能问题要从基础开始排查;

后记

我深知基础设施的改进能带来多大的改变,量变产生质变,你能比竞争对手快10倍就意味着你领先非常多了,对于我们来说,性能决定了我们能承载多少用户,能活多久。对于团队发展到一定程度之后的技术债务,尽早还清,所以也希望能分清团队里重要与紧急的任务。

使用 docker runner 加速 gitlab CI&CD

在之前的 CI&CD 实践中,我们一直使用的是 Shell runner,简单来说,就是在一台机器上配置好所有的环境,然后序列地去执行任务。

很明显,好处是配置非常简单,也很容易 Debug,出了问题,登录到机器上去查找即可;然而坏处就是配置迁移麻烦,也非常容易被破坏环境,而且单台机器上并发比较麻烦,好些的方法是需要配置多个机器,只是这就有些有点浪费资源了。

因此为了更快,我们需要并行地去执行 CI&CD 任务,这就需要换种更好些的方式了。

选择其他 Runner

因此在剩下的几个方式中:

  • Docker:最简单,从 Shell 迁移来说,工作量不大;
  • Docker Machine & Docker Machine SSH:需要多台机器,配置复杂;
  • Parallels OR VirtualBox:虚拟机,太重量级了;
  • SSH:与 Shell 一个意思,换成远程执行 Shell 命令;
  • Kubernetes:有点复杂,目前团队 k8s 仍处于引入阶段,暂时不考虑;

考虑到目前团队规模小,CI & CD 任务量也不是很高,因此在单台机器上部署 Docker Runner 即可。

Docker runner 介绍

首先,使用 docker runner 需要有两个前提:

  1. 有自己的 private docker registry,推荐使用 gitlab 自带的 registry 功能;
  2. 编译好自己的 docker image:
  • build image:用来跑任务,里面必须要有 git,用来拉代码,还得有其它工具用来跑任务;
  • service image:用来配合跑任务,比如数据库 mongo, redis 等,编译 docker 镜像: docker:dind,其中通过 docker 的 link 功能,通过配置域名来连接至相关的服务;

另外,使用 docker runner 的话,有两种方式去编译镜像:

  1. 挂载 /var/run/docker.sock,使用比较简单,直接使用 share docker daemon 的方式,共享缓存也简单,能够加快编译速度;缺点是有权限问题,因为在 ci 里面完全可以执行 docker rm -f $(docker ps -a -q) 这样危险的命令;
  2. Docker in docker & Privileged mode,也就是在 docker container 里面编译镜像,没有了权限问题的担忧,编译镜像互相隔离;缺点么,就是无法共享缓存,从而导致编译速度会变慢,因此在 docker:dind 中建议使用 overlay fs driver 以及本地的一些镜像缓存来加快速度;

考虑到线上环境是隔离的,而且管理人员局限于开发以及运维,可以牺牲一些安全性,我们采用了第一个选项。

具体配置文件

concurrent = 2
check_interval = 0

[[runners]]
  name = "docker-runner"
  url = "https://git.example.com/ci"
  token = "<you token here>"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "docker:git"
    privileged = false
    disable_cache = false
    volumes = [
      "/data/gitlab-runner-cache:/cache",
      "/data/gitlab-runner-npm-cache:/root/.npm",
      "/var/run/docker.sock:/var/run/docker.sock"
    ]
    shm_size = 0
    pull_policy = "if-not-present"
  [runners.cache]

注意 /data/gitlab-runner-npm-cache:/root/.npm 这个配置,这是为了 Node.js 项目的 npm 缓存共享,这样安装的时候能够更快些。

优化建议

  1. 打开 gitlab 的 registry 功能,它以 docker 官方的 registry:2 为基础,用 gitlab 自己的账号系统加入了权限管理功能,在 ci 中也可以直接使用私有镜像;
  2. 如果是 centos,docker 的 devicemapper fs driver 请优化下:https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/ ,或者使用 overlay;
  3. 使用 alpine 为基础镜像,体积最小,国内的镜像也挺快的,比如:mirrors.tuna.tsinghua.edu.cn;
  4. 由于每个 pipeline 相互独立,并发数可以调高些,比如就是团队的开发人数;
  5. 搭建 npm private registry: https://github.com/verdaccio/verdaccio ,来加快 npm install 速度;

Reference

  1. http://docs.gitlab.com/runner/executors/docker.html
  2. http://docs.gitlab.com/runner/configuration/advanced-configuration.html
  3. https://docs.gitlab.com/ce/ci/docker/using_docker_build.html

Git 协作流程

前言

在使用 SVN 的时代,一旦一个文件被锁定,其他人都无法修改的情况时常出现,着实让人头痛。Git 横空出世之后,大家因为它带来的便捷性非常有价值纷纷改为这个:它有一系列非常有意义的功能:回滚与重复修改、强大的分支以及 tag 管理、更清晰的历史修改追踪等等。只是,Git 也是由人来使用的,当单个人使用时,无论怎么折腾都没事,而当多人合作开发的时候,就会有各种各样的问题,比如由于 Git 的灵活性,所带来的分支管理与协作流程的问题。

Git Flow

这是最常见的协作流程,并且它已经有现成的工具支持:git flow,简单来说,就是把分支分为:

  • master:主分支,用于追踪线上生产环境的代码;
  • develop:开发分支,用于追踪开发环境的代码;
  • feature/a-new-feature:特性分支,用于不用人员开发新功能,从 develop 开始,结束后合并至 develop 分支;
  • hotfix/vx.x.1:热修复分支,用于紧急修复上线,从 master 开始,结束后合并至 develop 与 master 分支,并打上 hotfix tag ;
  • release/vx.0.0:预发布分支,从 develop 开始,结束后合并至 develop 与 master 分支,并打上 release tag ;

这个模型还是很有用了,将分支管理规范起来,多人协作很方便。只是,实际使用中,会有一些问题:

  • 繁琐,尤其是热修复的时候,因此有时候热修复分支会被滥用:上线小功能也会用热修复。另外,有时候开发人员也会犯错:合并代码出错,忘记合并回 develop,刚在本地合并完,发现已经落后于线上的代码;
  • 当团队使用线上 Git 管理工具的时候,就会有点麻烦了:与 Pull/Merge request 以及 Code Review 配合起来不方便,一般我们很鸡肋地让开发人员 feture 分支合并到 develop 的时候,以及 hotfix 分支合并到 master 分支的时候做 Pull/Merge request 以及 Code Review
  • 与 CI & CD 流程配合起来不方便,当 hotfix 与 release 分支合并回 develop 分支的时候,按照 CI 的理论,你是应该重新 build 的,只是,这样会显得很多余;

Github flow

其实你不妨观察下 Github 上的各种开源项目,就会很容易发现他们使用的流程很简单:只有 master 这个长期存在的分支,以及其他的 feature 分支。所有的 feature 分支都会向 master 分支合并,Code Review 以及 Pull request 在这个过程能很好的结合在一起。

只是,由于太简单,因此有些需求点,它做不到:代码部署的问题,即 master 的代码不代表生产环境的代码,即无法追踪。

Gitlab flow

Gitlab flow 算是这三者里面,最适合使用 gitlab 管理工具的团队使用的流程:它引入了一个概念叫做:Upstream first

简单来说,它就是在 Github flow 的基础上,分别引入不同的 downstream 分支策略:

生产环境分支策略

对于像手机应用一样的项目,再添加一个 production 分支即可,production 为 master 的 downstream 分支,所有合并到 master 的代码,如果需要发布,则合并到 production,用以追踪,(很明显,手机应用需要提交审核,这时候我们也需要合并代码至 master);

多环境分支策略

对于使用 CI & CD 的后端项目来说,可以在上面的基础上引入了 pre-production 分支,用于追踪预发布以及生产环境。即 master 为 pre-production 的 upstream,而 pre-production 为 production 的 upstream ;

发布分支策略

而对于发布软件来说,可以直接在 master 下面,设置多个不同的发布分支,比如各种开源软件:node、mongodb 等。当每次发布新版本时,都会创建一个分支,然后如果有修改的话,也只是继续在 master 上修改,修改完后 cherry pick 到对应的发布分支上。

P.S. 我的思考

其实这么一一说下来,选择就很明显了,尤其我之前提到过 CI 系统搭建

所以我继续谈谈,Gitlab flow 如何跟自动化结合起来:

比如后端服务器项目,当然选择的是多环境分支策略,那么,当有新代码合并到 pre-production 以及 production 的时候,就可以利用 webhook,通知相应的服务器,自动把代码 pull 下来,执行 pre hook 、重启应用以及 post hook ,这个过程就相当于部署到相应的环境中去。而所谓的分支追踪,也就是为了达到这个目的:将对应的分支以及各种对应的环境中的代码保持同步

顺便推荐个工具:Git-Auto-Deploy

另外,Gitlab 还提供了 environment 功能,可以在 .gitlab-ci.yml 中配置,然后直接点击按钮,就可以自动的把代码合并到 pre-production 以及 pre-production 合并到 production 中去。具体可以参考 这个

说了这么多,其实也就是想说明:一个规范的流程多么重要,以及当一个流程规范了之后,就可以把重复执行的部分交给机器去做,也就是自动化。

Reference

  1. http://docs.gitlab.com/ee/workflow/gitlab_flow.html
  2. http://nvie.com/posts/a-successful-git-branching-model/
  3. http://scottchacon.com/2011/08/31/github-flow.html
  4. http://www.ruanyifeng.com/blog/2015/12/git-workflow.html

ELK搭建两三事之数据需求

不知道大家如何应对运营以及营销同事的『临时』数据需求,在公司早期的时候,没有专门的BI工程师,其他工程师不够,开发任务紧。于是一次两次还好,多了之后,工程师会很烦,美女妹子的话,写个脚本导出个excel,其他随便给导出个CSV文件,然后没准同事用的是Windows,用Excel打开直接是乱码 😂

当然了,笑归笑,作为一个负责任的工程师 🌚 ,你必须得帮他们解决问题,也得意识到数据对于各个部门的决策的反馈是多么重要,有助于公司的加速成长。退一步说,之后几乎是不用帮着导数据了。(什么?不明白?那。。。解决问题之后就可以约营销妹子了,懂不?

由于这几天在搭日志系统,很自然想到ELK强大的功能,于是进一步想到,能不能把数据库的数据直接同步到elasticsearch,这样的话,他们可以直接到kibana导出数据,或者直接在上门配置好图表,这样就根本不用导出数据了。

由于在公司里面做的是node.js项目,为了同步mongo的数据到elaticssearch,做了几个尝试:

River elasticsearch

mongo-river-elaticsearch
作为elasticsearch的插件,看到很多文章都推荐它,细看了下,通过ES的API配置,还支持filter,但是它不支持最新的elasticsearch/2.3.1,所以直接跳过,如果你的项目中有的话,可以尝试

Mongoosastic

mongoosastic
作为一个mongoose的plugin,需要耦合到系统中去,也需要一些开发,但是可以精确到字段级别,配置更灵活,可以考虑;

Mongo connector

mongo-connector
试了下,配置什么的都挺灵活,关键不用耦合到项目中去,只是我们的mongo数据库中,类型不严格,同步时导致经常报错,主要是类型错误,如果能更灵活一点,到字段级别,就更好了 😎 ;

其实也可以考虑参考mongo connector自己造个轮子

之后再细说

PS.

Mongo connector配置文件可以参考如下:

{
    "mainAddress": "mongodb://localhost:27017/gleeman",
    "continueOnError": false,
    "oplogFile": "/var/log/mongo-connector/oplog.timestamp",
    "noDump": false,
    "batchSize": -1,
    "verbosity": 0,

    "logging": {
        "type": "file",
        "filename": "/var/log/mongo-connector/mongo-connector.log",
        "__format": "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s",
        "__rotationWhen": "D",
        "__rotationInterval": 1,
        "__rotationBackups": 10,

        "__type": "syslog",
        "__host": "localhost:514"
    },

    "namespaces": {
        "include": [
            "db.source1",
            "db.source2"
        ],
        "__mapping": {
            "db.source1": "db.dest1",
            "db.source2": "db.dest2"
        }
    },

    "docManagers": [{
        "docManager": "elastic2_doc_manager",
        "targetURL": "localhost:9200",
        "__bulkSize": 1000,
        "__uniqueKey": "_id",
        "__autoCommitInterval": null
    }]
}

谈谈项目的配置管理

众所周知,配置在项目开发中非常重要,一般来说,我们都会根据环境会有不同的配置,比如本地开发会配置本地机器的数据库,测试时还会给数据库设置不同的本地数据库,然后线上线下也会有不同的配置,其中非常值得注意的便是:生产环境的配置,因为这些配置非常敏感,需要加密。

把配置直接放进代码管理repo是最低级的做法:历史记录会有永远记录着配置中的敏感信息,看过不少初学者这么干,一搜历史记录所有的密码都出现了,github一搜一大堆,更有甚着,把密码替换掉,然后commit。。。哎。。。

如果这个项目只是公司内部,不会被外人发现,那也倒勉强可以接受,只是一旦公司大了,这些敏感信息也不是所有开发人员可以看到的,于是就很麻烦了。

那么,接下来说说,怎么解决:

我们的需求:安全 & 方便

剥离

一般来说,最简单的方式:从项目文件中剥离,并ignore,git中是放入.gitignore。然后配置单独管理,只有被授权的开发人员才能获取查看。只是这样的话,单独管理增加成本,前期几乎也是对所有人可见,新开发人员需要获取开发配置后才能继续,每次修改生产环境配置的话,都得一个个修改配置文件,以及对CI以及CD不友好(一般来说,CI还有CD都是直接清理本地非repo文件,然后拉取新代码,于是你得添加一个专门获取配置的过程)。

加密配置

比如Blackbox,是将配置文件加入repo,但是呢,是加密过的,利用GPG加密,安全性还是不错的,也很方便管理,只是,会有一些学习成本,在接受范围之内。

单独管理动态

动态以及非初始化配置,可以放到专门的配置管理服务器上,比如Vault、zookeeper、etcd还有consul,其中vault非常适合敏感信息的配置,它甚至可以做到即使黑客侵入内网,也不会像静态文件那么容易获取信息。还有个非常棒的优点:统一管理,不用担心每个服务器上的配置不统一,也能动态的更新。

谈谈如何当一名合适的面试官

缘起

今天项目负责人跟我一起面试了一个候选人,他说沟通能力不行,直接pass,而我却不死心,因为没问清楚技术问题,于是直接叫他做个系统设计题,哎……直接给我转移话题,麻蛋。
但是,我也浪费了负责人的时间,要是上次我直接说这人沟通能力不行,也就没今天的事了。

另外,今天晚上,跟一同行交流『如何面试』的问题,恩,发现自己这块儿一直没有重视起来。

正文开始

人才的重要性不言而喻,面试是为公司项目选择合适的员工,作为一个面试官,你有权决定他能不能进入下个环节,因此,在短短一两个小时之内,搞清楚被面试者是不是符合需求至关重要。

先说说几个遇到过的问题:

  1. 一个劲想考到被面试者,尽问些细枝末节的问题

没错,细节一定程度上可以衡量面试者的知识水平,但总是问细节的话,容易对他的认识有偏颇,毕竟还是有知识方面的『二八理论』。

  1. 按个人喜好,肆意批判不同的技术工具、体系与文化

有自己喜好的文化,固然没有问题,但是以此来判断被面试者的能力,却只是显得狂妄自大而已。深以为,对不同的文化,我们需要有包容之心,你对不同文化的批判很大程度上是你没有经历过而已。同样的,对于企业来说,一个成功的企业文化绝对不会只是单个人的:求同存异才能使文化发展。

  1. 不尊重面试者

不提前看简历,迟到,对被面试者的回答不满意时显得不耐烦(不耐烦是我犯过的错……)。毕竟被面试者千里迢迢赶过来面试,如果对方没有达到要求,那么请尽量告诉他有哪些地方不足,需要改进。这点在我面试小红书的时候很有体会,对方是个谷歌出来的工程师,当我回答不出来的时候,他会主动给我提示,甚至教我,告诉我哪些地方不足。(后来给我通过了,没去,但我到现在还是很感激他,他就是我学习的榜样)。

  1. 问题不提前准备

有时候,你没有准备好问题,临时起意想问个问题,表述不清,让被面试者理解不了(当然,可能他也有理解上的问题)。然后你认为对方不会,他认为你莫名其妙。另外,提前准备的还有想清楚招聘什么样的人,以及对他的定位是怎样的。

总体来说,面试过程中,我们需要了解这几个点:

  • 为人:『习武先习德』,这个很容易理解,每个公司都会考察,大部分时候是HR的责任,小公司里面可能需要你兼任,谈话的过程中,尝试了解他为什么求职,他说话语气、态度还有仪表,另外还有对工作的热情、毅力、自律与进取心等等;
  • 知识:比如js中prototype是什么意思,Node.js的异步是什么意思,mongodb的索引是如何创建的,Linux如何查看负责情况。一般来说,技术领域往往有很成型的技术栈,沿着技术栈挑几个从简单到难,由浅入深的一路问就行,记得有道题很有名:『在浏览器中,从输入网址到最终看到页面,说说在这个过程发生了什么?』;
  • 经验:比如你做过的系统是如何设计的,如何优化的,有没有遇到什么问题,这是经验的体现所在,一般来说,可以从简历上的经验问起,然后拓展开来问知识,如果说不清,那就是有问题,或者经验欠缺,甚至是简历作假。另外,配合着还可以给做过的系统加需求,看看他如何应对;
  • 能力:这个很关键,往往是我们最看重的,因为往往知识跟经验是能力的副产品:沟通能力,合作能力,领导能力,学习能力,解决问题的能力等等,而能力也是在短短一两个小时中比较难以衡量的;

如何考察能力

一般认为,直接让被面试者做题是简单而有效的,以考察服务器开发工程师为例:

  • 算法&数据结构题:这是基础题,不用特别复杂的题目,比如写个字符串匹配;
  • 系统设计题:算法之后的题目,这里考察被面试者的技术沟通能力、基础知识运用能力、以及系统分析能力,比如设计个订单系统、微博系统,从最简单的需求开始做;

这里最关键的地方在于:不需要被面试者给出完美的答案,而是他如何思考的,你可以提示他,甚至让他查资料,看看他设计的思路。算法题的话,看他如何考虑边界条件,是不是以及如何写单元测试,变量函数命名、代码风格等;系统题的话不断给他加需求,在一些关键点上给他提尖锐的问题,看他如何应对,思路是否清晰。

向被面试者学习

这点我想特别强调,把被面试者当成是你的同事,而且『三人行则必有我师』,你可以将工作中的问题包装成面试题,或者干脆让被面试者教你一些他擅长的东西,甚至让他说说他喜欢的一本书,你可能会在这个过程中重新认识对方。

最后

  1. 再次强调请耐心,对被面试者的尊重,能反映你的修养与你所在公司的形象;
  2. 犹豫就是不通过,每个进来的人必须高于目前团队的平均水平;
  3. 面试后半段,尝试问他业余时间做些什么,也许他会成为你以后的朋友;

Reference

记一次Mongo连接配置调优

话说在我接手公司的项目后花时间去优化API性能的时候,在newrelic上面看到,大部分都是mongo的时间,因此很有必要花大时间去优化。

我们用的是[email protected],然后修正了其中的复制集排序选择问题(3.8.39已经修正了,目前我正在比较测试,包括目前最新的4.4.x),先把项目的连接配置贴出来:

var options = {
    replset: {
        strategy: 'statistical',
        readPreference: 'secondaryPreferred',
        socketOptions: {
            connectTimeoutMS: 5000,
            keepAlive: 1,
        }
    },
    server: {
        poolSize: 10,
        slaveOk: true,
        auto_reconnect: true
    }
}

来,给你一分钟,找找其中的问题


好,不知道你想的是否跟我一样:

  1. replset里面没有配置poolSize,默认的是5,有点小,这个数字需要根据实际项目调整,但5的话,对于高并发项目太小;
  2. socketOptions里面的keepAlive设置的是1,这个参数可不是Boolean,它是Number,表示时间间隔,属于TCP连接的参数,单位是毫秒,如果是1的话,就表示每隔一毫秒就给mongo数据库发一个空的TCP keepAlive包【可以参见这里,以及Node.js net模块文档】,所以数据库被『DDOS』了,所以可以设置为1000ms;
  3. 由于项目生产环境用的是复制集,server参数在这里是没用的;
  4. connectTimeoutMS设置有点小,可以是30秒,或者更大点;

关于pm2的弱项

部署 deploy

由于我们线上项目全部部署在高配主机上,使用的是pm2部署,于是当我刚接手的时候,十几台主机全部用pm2部署的意义不知你是否明白,每台部署至少1分钟,然后串行部署,于是开始部署之后,可以很悠闲地去喝杯茶了

解决方案么,可以考虑用ansible,chef,puppet之类的工具,我用ansible部署之后,1-2分钟就可以全部部署完毕。

负载均衡 load balance

为了图方便,我们会在主机上部署的时候使用多个cluster,但是pm2的负载均衡很弱。

我在测试环境粗略试过,在一台16核16G主机上,1000QPS,pm2部署10个cluster(同一个端口),然后用haproxy+10个node进程(占用不同端口),性能提升10倍以上。

由于这个测试很粗略,测试报告就不写了,只是从理论上也可以想明白,毕竟pm2不是专门负责load balance的,用一个专门的LB去处理的话,肯定可以提升性能。

因此解决方案可以考虑docker + 负载均衡(如haproxy),对于我们来说,目前项目人手太少,不太可能使用Mesos或Kubernetes之类的编排管理工具,docker swarm的话,可以考虑,因为简单一些比较容易上手,只是也会比较废精力,暂时不考虑。

所以,我觉得对于我们这样的小团队来说,可以采用简单点的方案,使用ansible+docker compose来实现,即在每台机器上用相同的docker-compose部署haproxy+app。

一些后话

最近一直在看有关docker的东西,对于docker,我觉得的确是个好东西,只是,当我们把一项新技术引入到现有的团队的时候,需要考虑更多的东西。

比如,它到底能解决什么痛点,生态圈如何,上手难度如何,运维成本多少,简单来说:算算引入的成本与收益。我们很容易犯的错误就是, 拿着一把锤子,然后看什么都是钉子。

基于 ElasticSearch 的日志统计反爬虫策略

之前在 接口限流 中提到过,用 rate limit 的方式可以起到反爬虫的效果,但是一旦遇到利用代理 IP 来爬你网站的时候,就会遇到问题了

这时候,你可以付钱买服务解决,只是,如果有时间与精力,也可以考虑自己去试试,就当是一次锻炼。

用日志去解决爬虫,也是一种简单而有效的做法,可以使用 ElasticSearch 提供的 aggregate 功能,去筛选出可疑的 IP 以及 UA,接下来怎么做,可以参考之前的说法。

具体方案

对于这类的爬虫,它仍会有显著的特征:在短时间内,只请求几个接口,而长时间里,它的请求量会非常高。

于是,我们的思路就很简单:

统计过去一段时间 t 里所有的请求次数,首先以 ip 分组,然后以请求的地址 transcation 分组,最后,挑选出这一段时间里面,请求次数超过 n 的,并且请求的唯一 transcation 少于 k 的。

所以,利用 Terms Aggregation,我们的 ES 请求可以这么实现(注意:我使用的 ES 版本是 5.x):

{
    "size": 0,
    "query": {
        "bool": {
            "must": [{
                    "range": {
                        "@timestamp": {
                            "gte": <start>,
                            "lte": <end>,
                            "format": "epoch_millis",
                        }
                    }
                }
            ]
        }
    },
    "aggs": {
        "ips": {
            "terms": {
                "field": "ip.keyword",
                "size": 200,
                "order": {
                    "_count": "desc"
                }
            },
            "aggs": {
                "transactions": {
                    "terms": {
                        "field": "transaction.keyword",
                        "size": 10,
                        "order": {
                            "_count": "desc"
                        }
                    }
                }
            }
        }
    }
}

如果不清楚 ES 的 aggs 语法,我就简单说明下:这个查询首先以 ip.keyword 做 terms 聚集,以结果的 count 数量降序排列,并只取前 200 个,然后以同样的 transaction.keyword 作为子 aggs,『分割』ip 的聚集结果,同样的,每个 ip 下的 transaction.keyword 只取前 10 个。

获得结果后,按照一定的条件过滤即可,比如在这个例子中,我们可以选取单个 ip 请求数大于 2000 并且请求的唯一 transaction 数量少于 10 的。

还可以怎么做

还可以考虑使用 Significant Terms Aggregation,它可以帮助我们查找不寻常的 terms,不过用这个之前,需要一些理解,从官方的例子中,还是可见一斑的:

{
    "query" : {
        "terms" : {"force" : [ "British Transport Police" ]}
    },
    "aggregations" : {
        "significantCrimeTypes" : {
            "significant_terms" : { "field" : "crime_type" }
        }
    }
}

这个查询能得到 British Transport Police 这个部门所处理的不寻常的犯罪案件:

{
    ...

    "aggregations" : {
        "significantCrimeTypes" : {
            "doc_count": 47347,
            "buckets" : [
                {
                    "key": "Bicycle theft",
                    "doc_count": 3640,
                    "score": 0.371235374214817,
                    "bg_count": 66799
                }
                ...
            ]
        }
    }
}

从结果中可以看到,Bicycle theft 这类案件,在总体案件中,只占 1% (66799/5064554),但是在它这个部门中,却占到了 7% (3640/47347),这显然是不寻常的。

那么,应用到我们的场景中,可以改成这样:

{
    "size": 0,
    "query": {
        "bool": {
            "must": [{
                    "range": {
                        "@timestamp": {
                            "gte": <start>,
                            "lte": <end>,
                            "format": "epoch_millis",
                        }
                    }
                }
            ]
        }
    },
    "aggs": {
        "ips": {
            "terms": {
                "field": "ip.keyword",
                "size": 200,
                "order": {
                    "_count": "desc"
                }
            },
            "aggs": {
                "transactions": {
                    "significant_terms": {
                        "field": "transaction.keyword",
                        "size": 10
                    }
                }
            }
        }
    }
}

于是就能得到,Top 200 的 ip 中,我们到底能找到它们多少不寻常的 transaction。

在这个例子中,我们的筛选根据 score 即可。

好了,接下来,得到过滤后的结果怎么做呢?

之后怎么做

配合 Nginx 或者 HAProxy,做 IP 的黑名单,将流量导入蜜罐或者直接屏蔽。

哎。。。知道你懒,简单介绍下具体方案:

选一个配置管理工具 etcd/redis/consul/zookeeper 其中任意一个,然后将结果写到里面,然后通过 confd 动态生成 blacklist,之后 reload Nginx 或者 HAProxy 即可,这个有机会也可以再说说。

缺点

这种策略的缺点也是很明显:反应慢,毕竟需要采集日志之后才能采取措施。而且,这个过程需要人工不断去调整参数。

因此,这个可以用来与 rate limit 配合使用,用来查找一些 rate limit 防范不了的隐蔽的爬虫。

其它的想法

可以用机器学习,因为机器人总会表现得跟正常用户有区别:访问的轨迹,访问频率等等,都可以作为机器学习的输入。

不过这个需要投入更多的时间与精力,不是万不得已,不用考虑,倒是作为自己锻炼提高的过程可以尝试下。爬虫、反爬虫,是会在互联网中永远存在的东西,相互斗法,其乐无穷 😄 。

Reference

  1. https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html
  2. https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-significantterms-aggregation.html

关于公共服务的思考

前几日与一同行交流,一一交流下来,发现什么叫『固步自封』,跟外界交流少了,很多东西便会搞不清楚,甚至脱离主流。

比如最近一直在为团队做基础设施方面的工作,但是,会有一种吃力不讨好的感觉,虽然搭建完毕之后自己会很很有成就感,但是随之而来的维护成本却是很让人头疼。

是的,『能花钱的,就不要花时间』

我也想反驳,但后来仔细回想,没有立即反驳是因为我认同这句话。我当时想反驳的便是:公共服务就像公交车,的确有时候会很方便,可是一旦你想自由些便是很困难,这时候便需要私家车。没错,私家车成本高,还要花时间金钱维护,但是,它就是比公交车方便。

所以,对于一个创业公司来说,你完全可以用公交服务,比如代码托管,文档管理,项目管理,云服务器,监控服务,CI&CD服务。实际上,现在公共服务越来越多,创业成本实际上越来越低,最后可能到一种程度了之后,只要使用的人搭积木即可了,所有的服务都可以是公共的、现成的。

只是,我觉得高成本的服务才是有做成公共服务的硬需求的,也是价值非常高的,比如云服务主机,安全,APM,大数据等。

好了,话说回来,我列出有些部分为什么不用公共服务:
两个词:成本与收益

成本:

  • 金钱:应该算是机器了,无论云服务或者自己买机器
  • 时间:搭建与维护
  • 人力:需要专门的人去维护
  • 安全:数据不会泄露

收益

  • 时间:反馈时间,形成一个高效的正负反馈系统
  • 可用性:满足需求,甚至比公共服务好用

这里插一句前提,国内的很多公共服务并不怎么好用,而且让人怀疑,虽然他们一再声明不会去查看甚至泄露用户数据。而国外的服务有非常多好用的,但是由于网络问题,得投入VPN成本,另外,他们很多是使用美元结算的,换算成RMB之后,很贵,不过他们的成本本来就很高。(T_T国外好幸福。。。

比如当初选择gitlab,一个是因为github有时候太慢,线上部署代码的时候太慢,换成coding之后,又觉得不如github好用。于是自己用gitlab,然后那时候,gitlab已经比较新,自带了pipeline,可以直接作为CI&CD。

然后是日志系统,目前国内好用的服务比较少,国外有个loggy不错,只是按照我们的需求来的话,至少得要$5000+了,还不如自己搭建了,另外,由于需要上传数据到国外服务器,VPN的成本不会太低,更别说有延时了。(其实还有点小私心,想要借此机会熟悉ELK

好了,每个人都会有自己的选择,但是从个人角度来说:生命不息,折腾不止

PS

目前就我接触下来,用过的产品中,ping++挺棒,起码会让人觉得好用,文档非常棒,虽然现在的公司因为担心数据没用。再吐槽下个推,文档真让人头大。。。

PPS

国内的服务目前还处于发展阶段,我觉得做出好产品是需要高成本的,并且跟创业公司是互相成就的,有条件的情况下还是多支持国内的同胞吧。。。

Reference

  1. https://github.com/qinghuaiorg/free-for-dev-zh
  2. https://github.com/ripienaar/free-for-dev

软件开发中的完美心理

很多时候,会发现会有一种求完美的心理:

  • 这个功能实现好差,重构下吧;
  • 新出了个工具或者框架,在项目中试试吧;
  • 试过新工具或者框架之后,恩,好棒,用来改改其他的项目;
  • 哎呀,时间不够了,但是还想做更好,怎么办。。。放着吧。。。;

应该说,大部分时间,完美心理可以促进我们主动去学习新知识,新框架,尤其是最近几年,技术是日新月异,我们能及时跟进潮流。

只是,有时候,求完美的心理容易导致另一种心理:拖延心理。因为学习以及应用到实际中是需要时间的,如果发现目前没有时间的话,会把任务搁置起来,想着有时间了可以好好搞搞。哥们,别想了,你没时间的!

还有一种情况,就是一个功能过度开发,或者过度优化,从而导致项目延期,这对于开发人员来说,求完美的确是个好素质,要做就要做到最好。可是,如果是在创业初期,在做MVP的时候,你必须学会妥协,那时候需要的是速度,可以拖欠一些技术债务,因为需要的是生存。(呃,不差钱,不缺时间的土豪请略过……

所以,开发一个软件,你就必须得承认以及接受不完美,然后你才能有机会去做得更好。

PS

这个主题是由最近的一个感悟引起的:

每个公司,每种职业或者岗位都会有自己优缺点,没有所谓完美的工作,都会有让人委屈的地方,但是如果你只是看到不足的地方,这个点会越来越大,直到吞没所有优点。明星光鲜吧?他们苦恼而又想做的事正是你天天做的事,逛街,旅游,平淡的幸福。乞丐很难受?开玩笑,他们天天蹲着,趴着或者躺着就能天天拿钱,而且可能月收入比你工资高。

人生亦是如此,每个时间段都会有苦有甜,欢笑伴着泪水,正如老话说得好,哪会有一直顺利的人生,总会有起有伏,正是这些构成了你独一无二的人生。

首先是承认自己的人生不完美,然后接受,最后你才能去改变。受得了委屈,你才能有更大的胸怀,有人说胸怀就是委屈撑大的

勇敢的*年啊,快去创造奇迹~

Node.js APP dockerize

今天把做的项目直接做了docker化,一个是前端纯静态化的,另一个是后端Node.js App,对于这两个项目,想用最简单的方式来使用docker

静态项目最容易,直接用nginx,将编译好的静态文件直接丢到volums里面即可:

version: '2'
services:
  nginx:
    image: nginx:latest
    volumes:
      - ./build:/user/share/nginx/html:ro
    ports:
      - "8080:80"

后端项目有点麻烦,需要写Dockerfile,我觉得dockerfile的编写原则就是:越是不怎么会改动的内容,越往上放,然后把经常变动的东西放到最后,这样每次build的话会用到cache,下次不用重新build了。

这里还用到了gosh,比sudo更棒,还有tini,解决僵尸进程问题

# For your nodejs app

# Change your node.js version here
FROM node:latest

MAINTAINER Xu Zhipei "[email protected]"

RUN groupadd -r app && useradd -r -m -g app app

ENV UBUNTU_MIRROR http://ftp.sjtu.edu.cn
RUN echo "deb ${UBUNTU_MIRROR}/ubuntu/ trusty main restricted universe multiverse" > /etc/apt/sources.list \
    && echo "deb ${UBUNTU_MIRROR}/ubuntu/ trusty-security main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb ${UBUNTU_MIRROR}/ubuntu/ trusty-updates main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb ${UBUNTU_MIRROR}/ubuntu/ trusty-proposed main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb ${UBUNTU_MIRROR}/ubuntu/ trusty-backports main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb-src ${UBUNTU_MIRROR}/ubuntu/ trusty main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb-src ${UBUNTU_MIRROR}/ubuntu/ trusty-security main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb-src ${UBUNTU_MIRROR}/ubuntu/ trusty-updates main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb-src ${UBUNTU_MIRROR}/ubuntu/ trusty-proposed main restricted universe multiverse" >> /etc/apt/sources.list \
    && echo "deb-src ${UBUNTU_MIRROR}/ubuntu/ trusty-backports main restricted universe multiverse" >> /etc/apt/sources.list

RUN apt-get update \
    && apt-get install -y --force-yes ca-certificates wget --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# grab gosu for easy step-down from root
ENV GOSU_VERSION 1.7
RUN set -x \
    && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
    && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
    && export GNUPGHOME="$(mktemp -d)" \
    && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
    && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
    && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true

# grab tini for signal processing and zombie killing
ENV TINI_VERSION v0.9.0
RUN set -x \
    && wget -O /usr/local/bin/tini "https://github.com/krallin/tini/releases/download/$TINI_VERSION/tini" \
    && wget -O /usr/local/bin/tini.asc "https://github.com/krallin/tini/releases/download/$TINI_VERSION/tini.asc" \
    && export GNUPGHOME="$(mktemp -d)" \
    && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 6380DC428747F6C393FEACA59A84159D7001A4E5 \
    && gpg --batch --verify /usr/local/bin/tini.asc /usr/local/bin/tini \
    && rm -r "$GNUPGHOME" /usr/local/bin/tini.asc \
    && chmod +x /usr/local/bin/tini \
    && tini -h

STOPSIGNAL SIGTERM

RUN mkdir -p /usr/src/app
RUN chown -R app /usr/src/app
WORKDIR /usr/src/app

ENV NPM_CONFIG_LOGLEVEL http
ENV NPM_CONFIG_REGISTRY https://registry.npm.taobao.org
ENV PHANTOMJS_CDNURL https://npm.taobao.org/mirrors/phantomjs

# for app port
ENV PORT 3000

CMD ["gosu", "app", "tini", "node", "app.js"]

EXPOSE 3000

# frequently change begins here
COPY . /usr/src/app

RUN npm install --production

RUN npm build

Reference

https://github.com/tianon/gosu
https://github.com/krallin/tini
https://github.com/docker-library/kibana/

ELK5升级小记

早就听说ES5的版本了,一直没有在意,但是直到最近正式版出来之后,才有了要升级现在的ES集群的想法,首先便是日志集群(2.3版本),之前一直有问题:丢日志现象很严重,尤其是GC导致的静止现象,然后所用占用的空间非常大。

看ES5的介绍,以及网上其他人的经验,ES5集成了Lucene 6,性能提高不少,简单来说:磁盘空间少一半 => 索引时间少一半 => 查询性能提升25%。当然了,其它还包括:索引merge次数更少,用的内存更少了,GC静止出现的次数也会更少

我看了下文档,需要注意的地方很多,也暗自感叹,幸好选择的是日志集群,目前出点事挂掉也不会有很大的影响。看到了ES的好多新功能,其中就有reindex这个功能,于是我就想到,不要直接升级原来的集群,而是把新集群搭建好,测试完毕之后,用reindex的方式把旧数据迁移过来,这样对旧日志系统的影响最小,几乎不会影响到旧有的系统,中间过渡起来也方便。事实上,官方推荐由1.x升级到5.x的方案就是reindex。同时呢,还需要注意如果原来的索引很大,持续时间会很长,所以可以用ES5的task方式去做:

POST _reindex?wait_for_completion=false

好了,现在直接罗列一些升级过程的中的坑,希望对大家有帮助,不过建议还是先通读一遍官方的注意事项:

ES升级:

  1. 去除了ES_HEAP_SIZE,改用vm.options里面的配置,如果不改的话,集群不会启动;
  2. reindex,需要在elasticsearch.yml中指定reindex.remote.whitelist;
  3. 很好用的marvel插件现在集中在x-pack全家桶中了,我暂时没装,似乎还有些坑,需要单独测试下;
  4. kopf插件没了,head也没了,都改成了单独的app;

所以,kopf还想用的话,用这个:

docker run -d -p 9000:9000 --restart=always --name cerebro yannart/cerebro

Logstash升级:

  1. 变得非常慢,启动时间少则20秒,多则一分钟,比之前慢太多;
  2. 配置改为logstash.yml中的一些参数,当然了,命令行参数也还在;
  3. workers的配置去除,改为logstash.yml中的pipleline.output.workers参数,由此带来的兼容问题比较蛋疼;
  4. elasticsearch output改动很大,建议多测试后再上;

Kibana

  1. Marvel插件没了,marvel改成了x-pack,需要单独安装;
  2. Sense变成了dev-tools集成在kibana中;
  3. Timelion插件也没了,直接集成在kibana中;

其它

reindex的接口,似乎没有批量的功能,也就是索引名不变,直接迁移过来的功能,网上工具也几乎没有。不过还好,ES的npm包也更新了,直接自己实现调用即可,下面直接贴出我的迁移代码

'use strict';

const Promise = require('bluebird');
const elasticsearch = require('elasticsearch');

const oldClient = new elasticsearch.Client({
  hosts: ['old-es-host:9200'],
  apiVersion: '2.3',
  log: 'warning',
});

const client = new elasticsearch.Client({
  hosts: ['new-es-host:9200'],
  apiVersion: '5.x',
  log: 'warning',
});


oldClient.cat.indices({format: 'json'})
.then((indices) => {
  Promise.map(indices, i => {
    if (i.index.indexOf('logstash') === 0) {
      console.log(i.index, i['store.size']);
      return client.reindex({
        // 如果原来的索引很大,还是用task的方式比较好
        waitForCompletion: false,
        body: {
          source: {remote:{host:'http://new-es-host:9200'}, index: i.index},
          dest: {index: i.index},
        },
      });
    }
  }, {concurrency: 1});
})
.catch(e => console.log(e))
.then(() => process.exit(0))

Reference

  1. http://www.infoq.com/cn/news/2016/08/Elasticsearch-5-0-Elastic

接口限流

在业务安全性方面,我们常常会用到接口限流,主要是为了防止系统压力过大、保证每个用户请求的资源保持均匀以及屏蔽恶意请求。

几个常见的场景如下:

  • 恶意注册
  • 爬虫的过度抓取
  • 秒杀场景

目前实现API接口限流的方式有几种常见的,简单来说原理很简单,无非是在一个固定的时间段内,限制API的请求速率,一般来说是根据IP,如果是登录用户的话,还可以用用户的ID。

Token Bucket与Leaky Bucket

  • Token Bucket:定时的往每个bucket内放入token,然后每个请求都会减去token,假如没有token的话就采取限流措施,能够限制平均请求频率;
  • Leaky Bucket:做个缓冲队列,所有请求进入bucket排队,超过bucket size则丢弃,能够应付流量暴涨,请求集中;

两个算法,都有优缺点,都有适合的场景,比如Leaky Bucket很适合秒杀,因为需要应对所有用户的请求,用在正常业务中,容易卡着正常业务;而Token Bucket的话,更适合用在我们正常业务中的场景,限制接口的请求频率。

拿TJ的一个项目来说:RateLimiter

算是一个Token Bucket的实现,每过一个duration,就让bucket重新填满,优点是处理简单,快速,缺点还是是无法避免请求的集中效应,比如你限制每个小时1000次,那就是说,每个小时的开始的一秒内,爬虫可以用1000个并发(实际上,如果上个小时,爬虫没有任何请求,则可以在上个小时结束的一秒以及这个小时开始的时候的2秒内请求2000次)来搞垮你的服务器。当然了,你可以再加入更小的duration,比如10分钟内100次,1分钟内10次,这样的确可以避免这种情况,但是效率比较低下,毕竟要经过三层,而且,每层都要计数,一旦一层超过了,其它两层可能无法计数了,如果反过来,先检查最大的1小时,则还是会遇到每小时开始时候重置的问题。

所以,我们需要这个固定duration滑动起来,不会有reset,就可以一定程度上避免了集中效应。

Token Bucket滑动窗口限流

目前查到的Node.js实现的项目中,只有两个实现了滑动窗口限流:

只是实现略复杂,效率可能比不上上面的,简单来说是把duration切分成多个block,然后单独计数,时间每经过一个block的长度,就向前滑动一个block,然后每次请求都会计算那个duration直接的block内的请求数量。只是,如果还是单个duration的话,并不能解决集中效应。

简单比较下这两个滑动窗口的方案:

redback/RateLimit:
  • 优点:实现简单优雅,代码容易看懂,由于没用lua脚本(需要支持script相关命令),很多云服务商提供的redis可以用了;
  • 缺点:功能不够丰富,代码很久没更新了,只支持node_redis;
ratelimit.js:
  • 优点:使用lua脚本实现了具体逻辑,减少通讯时间,效率高,支持多个duration,并且还有白黑名单功能,支持ioredis以及node_redis;
  • 缺点:可能还是因为lua脚本,如果不熟悉的话,看起来比较吃力;

限制了后怎么做

对于恶意请求,假如对方的反反爬虫做的一般的话,完全可以直接将对方的IP加入黑名单,但如果对方用的IP代理,那就不是限流这个方案能解决的了,需要更高级的反爬虫方案。

但是我想提一点,对方既然爬了你的数据,肯定有对方的用处,假如对方没有恶意,请求频率也没有让你的机器有太多压力,那也就算了,毕竟你可能也在爬其它人的数据,大家都是搞技术的,没准对方背着万恶的KPI呢。

但是,如果对方恶意爬取,那么你完全可以在探测到对方的请求之后,返回空数据,甚至以假乱真的数据欺骗对方,让对方无利可图,对方也可能就会主动放弃了。

如何设置限制参数

查日志,统计下用户的正常请求就行。

PS1:

express以及koa框架下,如果是在反向代理服务器nginx或者haproxy之类的后面,这时候获取用户的IP的话,则需要设置trust proxy,具体可以看这里:#3

PS2:

可能你注意到了,我介绍的三个项目都是基于redis做的,原因无非是快,效率高,多进程多机器状态共享,对于其它的我觉得mencached勉强可以考虑,如果基于内存的话,无法处理多进程,多机器的情况。

什么?MongoDB?MySQL?别逗了。。。

Reference

关于Redis缓存

今天尝试了用redis的set作为缓存用途,恩,好疼。。。

是这样的,看到项目中用到应用缓存的地方很少,为了减轻MySQL数据库的压力,特地引入redis作为已购买物品的缓存,但是物品不能重复购买,因此这个缓存还得支持重复性检查。

于是很自然想到redis里面的hash或者set,然后,有个问题就是,当hash或者set没有member的时候,他们是不会存在于redis之内的,也就是hgetall或者smembers返回为空,于是为了缓存没有购买物品的用户,比如存一个自定义的特殊值,来确保这个值是存在的,即制造一个自定义空值,那么,为了区分这个key有没有存在,必须得使用exists或者type命令,exists会返回0或者1,type会返回none或者其他类型。

来,直接说结论:不要用exists或者type!!!!!

首先这俩命令会很慢,其次程序中会先使用exists,存在才会继续使用smembers或者hgetall,进一步增加时间开销。

还是老老实实用get跟setex吧,效率高多了。另外,在这种场景下,还有个数据一致性问题,因为重复性检查可能需要锁,不然会有数据不一致现象。

不过我的解决方案是允许重复,然后如果有重复的,在异步业务流程中手动回滚,并及时reload缓存。

PS

推荐一个缓存模块,用起来挺顺手:
https://github.com/BryanDonovan/node-cache-manager

Node Version Manager

关于

NVM,简单来说,就是管理 node 版本的,自从 Node.js 跟 io.js 合并之后,node 版本迭代就非常快了。然而这时候,我们可能会需要用到多个 node 版本:旧的用来兼容以前的老项目,新的用来应用到新项目;另外线上升级 node 版本的话,也需要很方便去操作。

三个选择

creationix/nvm

老牌的 NVM,一般来说 Node.js 入门都是用的这个,相信也一直很熟悉这个工具。它提供的 sandbox 功能可以让你很方便得切换 node 环境,甚至在不同的窗口中使用不同的 node 版本且互不影响,只是带来的麻烦就是每次安装新版本,你都得重新安装全局的包。

还有个很方便的点在于,你可以在项目根目录下,添加一个 .nvmrc 文件,直接填入 node 版本号,这样你就可以很方便地使用 nvm use 这个命令来切换项目所需的 node 版本了。

如果使用国内镜像,在环境变量中添加:

export NODEJS_ORG_MIRROR=http://npm.taobao.org/mirrors/node

tj/n

TJ 大神写的,简单一个脚本,很适合用于线上单一环境,但是本地的话,似乎没有其它两个好用,功能还不是很强大。

对于安装在服务器上的情景,可以直接使用默认的 N_PREFIX,即/usr/local/,它提供的 Makefile 也只是把 n 这个 bash 脚本拷贝至 /usr/local/bin ,然后它就会把可执行文件放到 /usr/local/bin 下面,使用 ansible 的时候,会很方便。

不足之处在于,它不能提供 nvm 一样的 sandbox 功能,即各个版本之前,安装的全局包会有冲突,所以切换之后可能需要 npm rebuild

如果要用国内镜像来安装 node,可以这样:

NODE_MIRROR=http://npm.taobao.org/mirrors/node/ n latest

另外,提供下我的 ansible 安装配置:

---

- name: install N
  get_url:
    url: https://raw.githubusercontent.com/tj/n/master/bin/n
    dest: /usr/local/bin/n
    mode: u=rwx,g=rx,o=x

- name: install node
  shell: n {{node_version}}
  environment:
    NODE_MIRROR: http://npm.taobao.org/mirrors/node/
  
- name: install global npm packages
  npm:
    name: "{{item}}"
    global: yes
    registry: https://registry.npm.taobao.org
  with_items:
    - pm2
    - yarn

jasongin/nvs

这个比较新,属于新造的轮子,我在试用了之后,直接把 nvm 卸载了,恩,好用太多。

可以说是集成了 nvm 与 n 的功能:可以跟 n 一样在命令行中直接选择 node 版本(但是可以直接下载安装),又有 nvm 的 sandbox 以及那些管理功能。再加上本身是用 Node.js 写的,所以有问题也可以自己查看,很方便地定制。

它的自动切换 node 版本比 nvm 更强大,不同之处在于它使用的是 .node-version 文件,而当你 cd 进项目时,它会自动切换,而不用你手工切换。当然了,需要事先打开这个功能:nvs auto on。其实这个功能很有意思,算是对 shell hack,有兴趣的不妨查看下源码。

对了,它直接集成了镜像源管理功能,深得我心:

nvs remote cn-node http://npm.taobao.org/mirrors/node/
nvs add cn-node/4.7.2

迁移至不同的 NVM

pm2

这是非常需要注意的一点,当你使用 pm2 来管理的情况下,因为当你之前已经有旧的 NVM 来管理的时候,很容易出问题,环境变量之类的都会变掉,尤其是 global 的 node modules,路径会改变。

因此,需要重新部署项目,注意:是更新内存中的 pm2,而不是仅仅安装新的 pm2 版本那么简单。

ansible

上面已经提到过,ansible不会主动加载你 .bashrc 或者 .zshrc 之类的,因此当使用 nvm 或者 nvs 管理线上服务器版本的话,可能需要手动 source 一下,或者自己更改 PATH 这个环境变量,具体可以看 这里

Webpack在后端APP中的使用

一直都说,webpack一般只作为前端项目的开发工具使用,非常方便,但是因为它太好用了,而且react后端渲染也需要用到,所以纯粹用到后端项目也是可以的。

比如我用在koa项目中,为了更好得使用babel,(babel-node太慢了。。。项目大了之后挺要命的。。。),用webpack的hot reload,缓存,然后部分编译,不要太爽~

以下是我今天创建的项目中用到的文件,需要node v4以上版本,然后项目中用到koa2,于是async-await很自然想用起来了。

webpack.config.js

const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');

const includePaths = [
  path.join(__dirname, './src'),
];

module.exports = {
  entry: './src/server.js',
  target: 'node',
  plugins: [
    new webpack.BannerPlugin(
      'require("source-map-support").install();',
      {raw: true, entryOnly: false}
    ),
    new webpack.NoErrorsPlugin(),
    new webpack.HotModuleReplacementPlugin(),
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        include: includePaths,
        loader: 'babel',
        query: {
          presets: ['es2015-node4'], // node v4中很多特性已经默认打开,目前不用es2015这个presets了
          plugins: ['transform-async-to-generator'],
          cacheDirectory: '.tmp',
        },
      },
      {
        test: /\.json$/,
        include: includePaths,
        loader: 'json',
      },
    ],
  },
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'backend.js',
  },
  devtool: 'sourcemap',
  externals: nodeExternals(),
};

Reference

[1] http://jlongster.com/Backend-Apps-with-Webpack--Part-I

HTTPS两三事

为什么我们需要

近来HTTPS越来越被大家重视,尤其是在http2出来之后。

流量劫持

国内的网络环境很恶劣,运营商各种流量劫持,DNS劫持,只是为了在你页面上加上几个小广告,或者更严重的,是否还记得12306抢票软件拖垮了github?对于流量劫持,很简单的方案就是用HTTPS,流量加密之后就可以解决。(说句题外话,DNS劫持的话,httpDNS不错)

Http Session劫持

正常来说,你肯定在公共场合用到过WiFi,那你是否知道,假如我跟你在一个子网内,你用的是HTTP的话,我可以很轻易的嗅探到你的Cookie,或者你的用户名密码,然后登陆你的账号。。。

具体怎么做就不说了,想象以下,你正在转账,或者付款。所以目前大部分付款有关的地址都是HTTPS的,为了防止你的交易甚至账户信息被劫持。

另外,就算同网络下没有人劫持,你别忘了,还有更大的公网呢,这些公网都是由人去管理的,恩,不多说了。

同理,没有加密的SMTP,POP3之类的协议,也会非常不安全。

为什么现在没有完全普及开来

很简单:成本,使用HTTPS是有成本的,证书本身还好,但是,一旦请求量上去之后,就得堆上更多的机器。另外,国内大部分公司的安全跟隐私意识不够强,也没有法律说必须要用。只是目前CPU成本越来越低,已经低到可以用这点成本来换取流量的安全以及用户的信任了。

另外,这是未来的趋势,来看几个消息吧:

  1. Deprecating Powerful Features on Insecure Origins,Google Chrome表示它的API必须在HTTPS下调用;
  2. Deprecating Non-Secure HTTP,Mozilla公司明确表态,逐步淘汰http;
  3. Apple will require HTTPS connections for iOS apps by the end of 2016,这个估计知道的人多些,简单来说就是App Store上面的APP必须使用HTTPS;

很简单,以前你可以说我们传输的内容不重要,为了省几个小钱,所以不管了。但是以后不行了,几个科技巨头都宣布你必须要用HTTPS,否则你就会被强制淘汰。科技巨头的力量还是很强大的,这也是正是巨头应该做的:Make the world a better place。

另外,用了HTTPS后意味着可以用HTTP2,而HTTP2能抵消掉性能的损失,或者,进一步提高性能。

怎么用

一般来说,放在web app里面是不合适的,相当于每个web app都得部署了,而且拓展起来不方便。好一点的做法是直接部署在load balance上面,比如nginx或者haproxy。

下面简单说说Nginx上如何配置:
首先肯定不能像12306那样用self signed证书,申请个正规的证书吧,免费的也有,但是,最好还是付钱的,免费的最贵啊。

申请之后,会有example.crt,还有example.key,这两个放在对应的目录下,调整下配置即可。

    listen                   443 ssl;
    server_name       example.com;
    add_header         Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

    ssl_certificate        /etc/nginx/certs/example.crt;
    ssl_certificate_key /etc/nginx/certs/example.key;
    ssl_protocols         TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers             HIGH:!aNULL:!MD5;

怎样会更好

  • 禁用SSLv3,不安全
  • 使用HSTS,加入一个http头,强制浏览器在max-age之前只用https连接。

Reference

Docker ELK搭建

记得上次说到的 #1 中提到,需要搭建ELK来实现数据需求,其实还有另一个需求:日志系统。

联系到最近一直在看的docker,我就直接用它来部署了,申请了台8核16G机器,CentOS7,其实ELK还是得深入看看,毕竟docker只是用来部署,而不是帮你解决日志系统的问题。

项目地址在这里:https://github.com/xizhibei/docker-elk

目前的系统架构已经完全不跟fork之前的项目一样了,简单介绍下:

基础结构

logstash => elasticsearch => kibana
这个最简单,对于项目的早期来说,请求量不高,完全够了。

加Broker

logstash-shipper => broker => logstash-indexer => elasticsearch => kibana
等用户量与请求量大了一个量级,就需要有一个broker来扛冲击了,比如用redis,rabbitmq之类的,当请求量突然增加,或者ES临时扛不住了,broker就可以暂时缓解下。

如果使用redis的话,就得忍受数据丢失的可能了,一般来说使用channel来做的,然后你必须设置一个参数:

client-output-buffer-limit pubsub 4gb 2gb 30

不然的话,redis会卡住,然后shipper无法传送数据,indexer无法接收数据,整个ELK就卡住了。

如果需要保证不丢失数据,可以换成rabbitmq或者kafka之类的。

加load balancer

上面说到的,日志来源都可以是UDP,ELK即使挂了也不会影响,但是如果对于数据丢失无法忍受,则需要换成TCP,以及进一步使用load balancer + 多个logstash shipper。

这些天运维的经验

  1. docker中ES,如果在一台机器上与其它应用共享,则必须设置mem limit,比如在我的机器中就设置为8G,然后heap size可以设置为一半(官方推荐),不然ES会把所有内存吞干净,让整个系统非常卡;
  2. ES轻易不要随便重启或更新,恢复会非常慢;
  3. redis不要用list,用了之后,redis在内存满的时候直接卡死,不会清楚,而且会dump到磁盘上,重启恢复也会非常慢,必须手动删掉;
  4. 如果用了redis的channel,redis的output的workers只能设置为1,不然subpub的buffer不会删掉,redis内存一直在涨;

数据库分页最佳实践

2016-06-22 更新:

MongoID不能保证连续增长,因此,在这种情况下,不适合此种方案,如果是为了保证速度以及效率的话,可以放缓存,比如redis以及memcached之类的。

正文

说起分页,最容易想起的就是offset+limit,在mongodb里面可以用skip+limit实现,应该说这是最容易实现的功能,与前端的交互来说非常合理,你只需告诉前端有多少页,以及每页的大小即可。

但是

这样效率太低,因为无论是哪个数据库,都会强制扫描offset或者skip的数据,即不会用到索引,所以,越到后面,skip的数据越多,效率越低。(当然了,数据量不大的话效果不明显。)

解决方案是用条件直接筛选索引,通过索引的限定直接去取数据,这样效率最高。

举个栗子:

// 已创建createdAt索引
Order
  .find({createdAt: {$gte: nextId}})
  .sort({createdAt: -1})
  .limit(limit)
  .exec()

你需要分页展示订单信息,只要前端获取nextId即可,尤其是APP,目前大部分的APP展示大量信息的时候,会选择瀑布流加无限翻滚,就是到用户翻滚到页面尾部的时候才会去请求以及加载下一页,因此不需要给前端一共多少页这样的信息,只需要告诉还有没有数据即可。

具体方案

前端第一次请求
http://example.com/orders
API返回order数组,以及在返回的头部信息中有x-next-id: 123456
当用户快拉到底部时,请求第二页数据
http://example.com/orders?nextId=123456
API返回order数组,以及在返回的头部信息中有x-next-id: 12345678
...
最后一次API返回的数组中只有2个文档,这时候头部中无x-nexd-id信息,即无更多数据

具体实现:

// 已创建createdAt索引
const orders = await Order
    .find({createdAt: {$gte: nextId}})
    .sort({createdAt: -1})
    .limit(limit + 1) //需要多取一个doc来获得nextId
    .exec();
if (docs.length === limit + 1) {
    return {
        docs: docs.slice(0, -1),
        nextId: docs[docs.length - 1]._id,
    };
}
return {
    docs,
    nextId: null,
};

一个需要注意的地方

不知道你发现没有,上面这个其实是可能有问题的,因为当有重复的createdAt字段存在时,就会有问题:
比如limit10个,恰好createdAt字段全部相同,于是返回的nextId跟前一次的nextId相同,那么就会造成死循环了。当然,这是最差情况,只是出现重复数据的概率挺高。

解决方案很简单,直接用unique字段,或者加上unique字段,比如id字段,query的时候用两个字段一起筛选,返回的nextId也是包含着两个字段。

Reference

http://stackoverflow.com/questions/10072518/what-is-the-best-way-to-do-ajax-pagination-with-mongodb-and-nodejs

npm的生态系统

nodejs这些年来的发展迅猛,一个重要的条件就是npm,npm这个包管理工具很强大,如果没有这个工具,很难想象没有npm的nodejs会发展到什么程度,golang或许可以是个参照:发展到现在,一直没有一个统一并且非常规范的包管理工具。

我一直想很相信,任何东西的发展,离不开好的基础,无论是个人或是国家:

个人,需要多学习,多锻炼,提升自己,打好基础,比如语文,英语跟数学。可能有点好笑,但是,一个人的语文能力很大程度影响甚至决定他的语言能力,沟通表达能力,这些基础的能力在工作学习中的重要性不言而喻;英语的话,与外国先进的人或者事物交流的能力;数学,逻辑推理能力。所以,所有优秀的工程师都是有着非常扎实的基础。

国家,需要努力搞好经济建设,以此继续建设衣食住行,一些国家的基础设施,需要国家投入大量人力物力财力去建设,比如,铁路,电网,电信,公路等等。国家近些年来互联网的高速发展得益于基础设施的建设,智能手机就是一个很好的基础,以此形成的移动互联网,带来了多少机遇。这就是浪潮之巅啊。

最近刚开始看KK的失控,对系统的感悟越来越多,今天联系到npm,随便说点,我认为,nodejs的迅猛发展离不开它建立的生态系统,而npm是其中一个非常重要的基础,提供的基础核心价值『方便』,一条命令就可以安装所有依赖,并且开放整个系统,任何人都可以贡献安装包。

方便 + nodejs官方 => 使用的人多 => 开源组件越来越多,质量越来越棒 => 更多的人以及组织参与,完全是个正向循环。

所以,如果以后要发明个语言,一定要提供官方的包管理器 😄

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.