引言(喝酒后的碎碎念,可以跳过,直接从前提看起)
- 离开薄荷 3 年了,从 18 年初进入薄荷到 21 年 9 月离开,在薄荷将近 4 年的时间,我从团队拿工资最低的技工程师,历经火箭升迁,压力成长,成为独当一面的部门主管,在项目管理(需求评审、架构设计、人员分配、跨端配合、安全审计)、团队管理(招聘淘汰、成员成长、梯队建设、制度文化)、发展创新(新技术调研落地、项目可行性分析、立项决策)上,都留下了不少的痕迹;
- 后面加入大型外企,半退休远程了,我才发现自己不适合做远程,我更喜欢融入一群人中默默贡献,比起老家深山中的幽静、智者乐水更适合我,况且在薄荷做 leader 后养成了一些权限依赖习惯,我在束手束脚的审批、infra、响应时间上难以适应,最后新工作两年不到就草草收场了;
- 个人的兴趣点一直在 iot 上,刚过去的 11 月底我终于鼓起勇气到了深圳,加入了一家高速发展的中型软硬件公司(1000 人+),CTO 如是对我说:“我希望你不要做体力活,做最能发挥你能力的事情就行了”,开放了几条业务线的代码,我便开始工作,用 8 年坚持每天刷 github trending(过年除外)的技术鼻子开始嗅嗅代码;
- 在迅速提交了 4 次重大解决方案后,我不到两周便迅速站稳了脚跟,得到了同事和创始人的信任。我才发现自己原来还在吃在薄荷的老本,于是打算把这些大家习以为常的东西整理一下再讲一遍,温故知新;
- 其实我没完全记住薄荷 10 条,因为几个太基础,我就忘记了,但鲁迅说过,中国人就喜欢凑个十数(真的,我基本上全文阅读过鲁迅全集,出自《再论雷峰塔的倒掉》),还是凑一些需要作为后端常识来认知的东西;
- 我现在写 go,ruby 和 rails 留下的最佳实践在其他语言的学习者中鲜有了解和实践,所以给了我降维打击的机会。
前提
- 互联网企业,尤其是面向终端用户和合作商的企业,安全是第一考虑要素;
- 软件工程师的上限是由努力和天赋决定的,但底线是由公司基因、研发流程和文化决定的;
- 很少有软件工程师注意到安全的话题,大多是在繁重的需求压力下完成开发,安全无小事,出事就是大问题。
正题索引
- 【数据库】不要在任何区分度低的字段上加索引(如 status、各种 enum)
- 【数据库】发布前一定要检查 migration,大表手动处理
- 【数据库】在 rails 中,transaction 开始的地方在第一个查询的地方,需要优化的话,将 before_action 挪后
- 【缓存】不要滥用 Rails.cache 方法,超过 1k byte 的 redis key,一定要做二级缓存
- 【业务】不要信任所有的前端传入 id,必须要做归属校验
- 【异步任务】尽可能实现幂等,至少本地数据库一定要实现幂等
- 【代码】任何 secret 不能硬编码到代码,小到短信 template_id,大到 jwt_secret,优先放环境变量,其次放数据库
- 【代码】开发前,先建立分支,写设计,review 后再编码
- 【常识】ruby 并不慢,大多数性能问题都是数据库、外部依赖的问题(先查 n+1,做好监控)
- 【常识】内存飙升问题
【数据库】不要在任何区分度低的字段上加索引(如 status、各种 enum)
大多数情况下(如果你清楚你的区分度,并且完全理解),不管 MySQL 还是 PG 都适用,在 status 上加索引只有坏处
- 即使命中,也大概率是半表扫描;
- 如果 status 和 id 等业务参数一起查,有概率会走 status 索引而不走业务参数的索引,需要知道详情请将这句问 GPT。
这个不多解释,GPT 能告诉怎么做才是最优。
【数据库】发布前一定要检查 migration,大表手动处理
各种数据记录表,如用户登陆记录等,如果需要增加字段,一定要清楚数据量,我们一般认为(有科学依据,以 B+ 树深度未依据)百万级、千万级、亿级数据锁表时间是有明显差距,百万级的表想要不停机发布,就需要上工具了,比如 percona-toolkit 等,先增加字段后,再手动添加 migration 记录
【数据库】在 rails 中,transaction 开始的地方在第一个查询的地方,需要优化的话,将 before_action 挪后
一个请求开始后,rails 会在第一次进行数据查询的地方,开启 transaction,待请求结束后,有框架方法执行兜底的 commit,如果请求中有网络操作,而你正好用的是 pg,那在并发上来后,会有长事务问题,最好先把网络请求进行完,确保 transaction 的过程时间短且可控。根据业务自行取舍,因为 before_action 挪后意味着 current_user 的查询也会要挪后。
【缓存】不要滥用 Rails.cache 方法,超过 1k byte 的 redis key,一定要做二级缓存
如果不是经常更新的内容,比如首页的 Banner 和广告这类首页接口,并发量最大,而且更新少,就太适合全部放到 Rails 应用中进行缓存,而不是每次都消耗内网带宽和延时从 redis 中拿,能将接口返回速度提升到微秒级别。
【业务】不要信任所有的前端传入 id,必须要做归属校验
千万不要写 Order.find(params[:id])
这样的代码,只要知道 order_id 任何人都可以访问这个资源,current_user.orders.find(params[:id])
是最佳方式。
【异步任务】尽可能实现幂等,至少本地数据库一定要实现幂等
幂等大家都知道,不过多解释,为什么说尽可能
,因为可能有外部网络操作。网络操作一定要放到最后进行,而且不能太慢返回,在 web api 的 k8s 的 preStop 生命周期函数中传入类似 sleep 10
,给 sidekiq 10s 做完退出大多数情况也能搞定。
【代码】任何 secret 不能硬编码到代码,小到短信 template_id,大到 jwt_secret,优先放环境变量,其次放数据库
安全无小事,希望大家所在的公司一辈子都不要被公众视野注意到(上市除外,打新记得叫我🤣)
【代码】开发前,先建立分支,写设计,review 后再编码
改天另写一篇,有完整且成熟、适合中国宝宝的流程推荐,如果你已经在这种流程了,那说一声恭喜,业务开发也可以很有趣的。
【常识】ruby 并不慢,大多数性能问题都是数据库、外部依赖的问题
首先完善监控,uptrace 这些社区工具都蛮好用的,买国内的 grafana 包装服务观测云这些也不错,至少能掌控系统整体运行状况,如果自研,一定要符合 open-telemetry 规范,不然轮子得很多。
【常识】内存飙升问题
一般是有外部服务,比如 qrcode、分享等图片生成、大 excel、网页生成 pdf 等,一方面我们需要在 dockerfile、服务器安装一大堆依赖,另外还会再占用一堆内存,优化方法也很简单,用 golang 或者就用 ruby 写一个服务,打包放到 serverless 里面去,按量计费,既不占用宝贵的 connection pool 资源,也能移除这种低频次高消耗接口对业务系统带来的损耗。
致谢
感谢薄荷健康的联合创始人、CTO @Vincent 4 年来的谆谆教诲和言传身教,作为华东 Ruby 黄埔军校毕业的一员,感谢你给一个热爱技术的年轻人的机会。
讨论
今天临时起意写这篇文章,有感于“回流”团队 CTO 智恒在公众号的技术和管理分享,觉得作为老 RubyConf 讲师,今年没能参加会议,有必要将这些年的沉淀分享下,线下不能线上总行嘛(我现在 996😂)。喝了 3 瓶乌苏,就开始敲键盘了,标点符号、排版、错别字等问题一堆,大家能就好啦🙏,有任何问题欢迎讨论,如果不便公开分享也请加我的 w:plugine