讲故事环节
我先讲个故事
很久很久以前(其实现在大部分网站都是这样),我们做一个网站,所有的功能代码都会在一个代码库中,部署的时候 db 几台机器、nginx 几台机器、业务 server 几台机器。其中业务 server 的所有逻辑都是在一个进程中的,例如网站的登录、用户的评论、新建问题、某个活动等等。
一起看起来很美好。
突然有一天,老板说我们搞个红包活动吧,微信红包,上线后由于中国人的薅羊毛心态,活动页面 qps 瞬间暴增,导致网站的登录逻辑 qps 也暴增,活动页面压垮的同时,所有其他业务全部504,扩容时也是整个 server 一台一台的加。扩容后发现,其实 qps 大的页面就只有这个活动也,容器上其他页面全部都是资源浪费。
所以造成上面现象的问题有这么几个
- 降级逻辑,一个边缘东西跪了不应该影响全站(除非是极为核心逻辑、例如登录)
- 不能定点扩容
这时候就在想,如果登录是一个服务,自己的业务也写成一个服务,主站的其他业务不动,登录、活动、主站业务分机器部署,活动 qps 来的时候我只要扩展登录和活动的容器就好了。而且这样在活动的这个服务中可以方便的接入登录的降级。
什么是降级
简单的理解,降级就是指需要被降级的东西跪了的时候,业务不会5xx。
土办法的做法就是加 try except,这样就会有这么一个问题,例如下面的代码中,do_something 可能是 mysql 的一次写,其他耗时操作的一次查,当 do_something 彻底跪了的时候,如果用 try except,每个 qps 来的时候,do_something 会依然执行,例如 mysql 跪了起不来,do_something 每次都会去连接 mysql,然后500。
try:
do_something()
except Exception as ex:
logging.error(ex, exc_info=1)
那么我们明知道这个地方跪了,为什么还要一直打他呢?降级时可不可以在一定时间内返回一个默认值,不去真实的做 do_something,或者在接下来的一定次数的 qps 返回一个默认值?
所以什么是服务化
看看下面知乎的这个专栏好了
知乎服务化
知乎的一个回答
实施微服务,我们需要哪些基础框架? 这个捡着看
服务化的优点和缺点
优点
- 根据服务扩容简单
- 降级容易做(就可以评论跪了整个网站其他功能还活着)
- 上线时只上自己对应的服务即可,不影响其他业务(数据相关还会影响)
缺点
- 业务拆分定级,否则会服务爆炸(是个功能就写个服务,后期服务巨多无比)
- 数据一致性难以保持。当不用服务时,非常容易写事务或者其他逻辑保持数据一致性,服务化后难以做到这一点(回滚可能都是调用服务,依然可能失败)
- 不必要的服务间请求增多。不用服务时,很多东西查一次可以用上下文的形式传递,服务化后服务内部可能需要自己重新查一遍。
- 批量接口难以实现
- 服务间解耦,一般采用消息系统(kafka),需要消息系统稳定
- 非 rpc 层的单测是骗自己的,毕竟 rpc 调用的地方都会 mock
怎么服务化
业务拆分是逃不掉的,服务化后还需要慢慢替换原有实现。
我们重点说下技术手段
你可以选择 rest 或者 rpc,协议的话 HTTP 开销比较大,业界现在似乎比较主流的选择是 thrift 协议,然后做一个 rpc。给出饿了么实现的一套 python 版本 thriftpy。
所以坑爹的点就在于,你拿到一套 thrift 的协议的时候,例如 thriftpy,他不像 django、ruby on rails之类的会有非常明确的代码分层要求,如果内部不在强行定义规范,每个开发写出的服务都不一样。
那么当你服务有多个容器后,怎么方便分组部署呢?首先你需要服务发现 consul, 例如我们定义一个服务叫做用户服务 member,他可能在物理机 A 3001端口,3002端口,物理机 B 3001端口起了3个 rpc server,consul 可以注册一个 member,物理机端口的事儿同样注册在里面,业务方只要用 member 这个名字就好了,不必知道具体打的是哪一台物理机的那个 server。
配套食用的可能还有 haproxy 做负载均衡的,我没用过表示只知道这玩意儿。
你真的需要服务化么
这个是非常需要思考的问题。如果你的网站 qps 非常低,搞毛幺蛾子。