当前位置: 首页 > news >正文

后端的一些经验与心得

先简单介绍一下我的经历,最早在学校的时候,是在社团里写php和Java,创业时期写js,oc和Ruby,现在是全职用Rails写后端了。

项目简介

我们的主要业务有两块,社区和电商

整体业务的峰值qps大概在3000,也算是pv过10亿的站点了,后端team有4个人,除了一个八年老司机,其他人参加工作的年限都不是太久。

我们面对的是一个巨大的基于Rails的历史遗留系统,最早的开发成员均已离开,导致我们常常面对遗留代码一脸蒙逼,到处是没有人知道的逻辑,丑陋的实现,以及很多性能跟不上的接口。

与巨石应用的斗争

日常工作的重中之重,就是与这个monolith的战斗!

性能篇

以往每年我们搞活动,服务器都会挂,经济损失不少,所以优化性能,保证活动期间的访问是第一要务。

原来的活动整体设计还是比较科学的,活动页面本身是静态化托管到cdn的,从来没有出现过问题,主要瓶颈是商品详情页面。我们利用redis做了三层cache,解决了这个问题。第一层是数据库的缓存,直接把商品信息缓存到redis里,避免了频繁的数据库访问,第二层是单条数据的渲染缓存,可以理解成一小段html,第三层是整个数据集的渲染缓存。第二个瓶颈出现在一些静态资源上,全面迁移到云存储解决。做完这两件事之后,上上次活动是我们有史以来第一次,没有挂。

就在我们觉得,优化做的不错的时候,上次活动却又挂了。

要知道我们特意买了新服务器,美滋滋觉得这下稳了,没想到...

上次活动挂的原因有以下几点

  1. redis hmget,我们通过gem提供的API,缓存了一个巨大的省市区列表,但是没有注意到缓存是分离的,获取整个列表,其实就是一条hmget获取所有独立的缓存片段,这个操作block了redis,导致访问极度缓慢。我们紧急把整个列表转成json,直接贴到代码里返回hotfix了这个问题
  2. 突然无法通过redis sential进行连接,这套sential系统是由已经离职的运维搭建的,我们绕开sential直接连接redis,解决了这个问题
  3. fd limit, 做完以上两点,依然时常502, 发现运维修改的是root用户的fd数量...坑爹....
  4. 在支付回调中有一段用于统计的sql,订单量大了以后slow query,block了数据库,我们直接注释了这段可有可无的老代码,解决。

总结一下,对于web应用的场景来说,大都是读多写少,缓存读请求,异步写请求,是我们经常采用的两种效果不错的方式。在数据库层面,对于遗留代码中效率低下的查询进行重写,重点改写了所有N+1查询,对一些逐条插入的语句用batch insert合并写入操作,也有不错的提升。

替换篇

做的比较有意思的事,是写了我们内部用的个推GEM。原来使用的是github上开源的一个GEM,但是已经很久没更新了,无法适应我们的使用需求。我基于个推最新的HTTPS的API,写了一个Ruby的包装。

这里要吐槽的是个推的技术水平。推送服务是做的不错,但API怎么做的这么low。他们定义了一个叫authorize的http header用来传递身份信息...违背了RFC关于HTTP头必须大写开头的规范。一些语言的标准库(Go、Ruby...)会自动帮你把authorize转化成Authorize,导致个推那边一直返回auth error...而个推的接口又是HTTPS的,抓包调试很困难,浪费了我很长时间调试这个问题。

重构篇 重构的主要方针就是拆分,尽可能把功能从巨石应用中拆出去。如果一时半会难以拆分的,代码上也尽可能让逻辑高度内聚,方便以后迁移。

消息系统的重构 消息系统是一个,出点问题没什么,但做得好会非常出彩的功能。我一直觉得,像知乎这种社区的成功,除了内容,很大一部分要归功于消息的体验。目前,我们几乎所有页面,都会展示新消息的数量,导致每次请求都会去主数据库的消息表做count,计算各种消息的数量返回给前端。我正在着手把整个系统迁移到另一个独立的数据库,以后可以作为单独的服务供内部调用,降级限流什么的都很方便。

搜索的重构

原来的搜索是基于Solr的java工程,是一个我们内部没人维护好多年的烂摊子,虽然各方面表现都不错。我们还是决定未来要用Elasticsearch换掉它。

新系统 我新写了内部的财务系统,过程中遇到很多问题,写的也很痛苦,但最终效果还是不错。因为原来的各种报表都是直接基于生产数据库的,对业务会有冲击,新系统写了一个同步模块,可以增量同步订单数据到财务系统的专用数据库,这样就不会对业务带来影响。

遇到的比较大的坑就是内存爆炸。有一些耗时计算我放到了消息对列里,整个worker进程的内存占用疯狂上升。最终发现是Ruby内存模型的问题。

我通过时间换空间的方式,把之前加载全部数据做计算,改成了加载部分数据做计算,然后汇总结果这样的方式,极大降低了内存占用,并通过每天重启worker进程,解决了最主要的内存问题。

这个项目让我真实感觉到,有些场景真的不是Ruby擅长的领域。Ruby的内存模型,就是尽量分配对象,从不真正回收,只会重用。Ruby VM启动就有大量空对象等着被分配,假如我加载了很多数据,空对象不够用了,VM就向操作系统申请一批内存,用完后也不释放,等着下次重用。而报表计算的最佳场景就是能加载大量数据,算一下结果,算完释放掉内存。

监控 可以看我之前的文章使用ELK构建分布式日志分析系统

代码篇 在日常编码、重构的过程中,经常使用的技术是

  1. 设计模式
  2. 元编程

运用设计模式,写出符合OOP规范的代码。分割每个类的职责,尽量让各个功能的逻辑内聚,只提供彼此间调用的接口,这是我最近才刚领悟的代码整洁之道。

元编程抽象代码,我很早就在使用的奇技淫巧。现在却用的越来越少了,因为它违背了OOP,可维护性比较差,对使用者的水平有很大要求,也容易坑队友。

简单地说,我代码中的if/else越来越少了,类越来越多了,改动起来越来方便了,改动影响的部分越来少了,美滋滋。

结语

用一句古老的名言,软件开发没有银弹。

相关文章:

  • 超过父控件的部分不能响应事件怎么办
  • WKWebView的使用总结(oc与js交互使用心得)
  • JavaScript 中的错误隔离
  • golang测试
  • java在线聊天项目 客户端登陆窗口LoginDialog的注册用户功能 修改注册逻辑 增空用户名密码的反馈 增加showMessageDialog()提示框...
  • 八个维度,谈谈产品经理的分类与发展方向
  • 第一课 安装 登陆CentOS 7
  • 创建和使用数据库
  • CSS中使用expression完美设置页面最小宽度
  • Android 从SetContentView()谈起
  • 为什么volatile不能保证原子性而Atomic可以?
  • SQL优化常用方法44
  • NoSQL
  • session再次理解
  • 陈杰:无服务器架构,让云端开发更纯粹
  • DataBase in Android
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • ECMAScript入门(七)--Module语法
  • ES2017异步函数现已正式可用
  • FastReport在线报表设计器工作原理
  • Hibernate最全面试题
  • HTTP请求重发
  • JavaScript设计模式系列一:工厂模式
  • js对象的深浅拷贝
  • JS学习笔记——闭包
  • Koa2 之文件上传下载
  • spring学习第二天
  • Vue2.x学习三:事件处理生命周期钩子
  • Zsh 开发指南(第十四篇 文件读写)
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 工作中总结前端开发流程--vue项目
  • 构建二叉树进行数值数组的去重及优化
  • 构造函数(constructor)与原型链(prototype)关系
  • 欢迎参加第二届中国游戏开发者大会
  • 前端js -- this指向总结。
  • 数组的操作
  • 我有几个粽子,和一个故事
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 湖北分布式智能数据采集方法有哪些?
  • ​用户画像从0到100的构建思路
  • #pragma multi_compile #pragma shader_feature
  • (03)光刻——半导体电路的绘制
  • (04)odoo视图操作
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (二十三)Flask之高频面试点
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (十六)Flask之蓝图
  • (数据结构)顺序表的定义
  • (四)c52学习之旅-流水LED灯
  • (四)图像的%2线性拉伸
  • (转载)Google Chrome调试JS
  • .NET Core使用NPOI导出复杂,美观的Excel详解
  • .Net Winform开发笔记(一)
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)