服务稳定、高可用

 

服务的稳定性对业务的进行极其重要的;

如果我们自己是一个普通用户,要访问某个网站,但这个网站一个页面的打开你要等十来秒甚至几十秒的时间,请问你还有看下去的欲望吗? 服务的稳定性及响应速度差会给你的业务带来以下损失:其一,用户很可能因这个问题而选择你的竞争对手;其二,使你的网站或产品的可信度下降;第三,从SEO的角度来看对搜索引擎极度不友好。说一个案例吧,在聚美优品3周年庆抢购活动上,虽然它的前期营销做得非常好,确实吸引了很多用户在那一天去访问他们的网站并产生了大量的订单,但不幸的是那一天因为他们的服务器不堪重负宕机了,很多用户还在网上排队等待。事后用户挂在嘴边的就是“还是淘宝给力,聚美优品就是没淘宝给力,一个活动网站就塌了”,虽然说这不是本质上的问题,但往往用户会认为你不及人家,而且是连带你这个品牌的所有,特别对于一些大品牌。

所以,我们服务必须保证服务稳定、高可用。

如何做到稳定、高可用呢?

 

我们大部分服务都是如下结构

 

防范使用方

2.1 服务接口api参数易用性

l参数定义:定义具有明确含义的参数名称,不要出现a、b、x等的参数。

l参数的类型:每一个参数有明确的数据类型,不要全部都是String,而尽量给每一个参数定义不同合适的数据类型,避免api方法的误用和减少数据类型的转换。

l参数的数量:服务接口api中参数个数不宜太多,一般3个左右比较合适,如果个数太多那就建议使用参数类吧

2.2 良好的接口使用说明

l参数有中文:某些情况下我们的接口会传参中文,中文参数就会有编码要求UTF-8,GBK,如果不约束具体的编码,可能接口就会有乱码甚至保存异常。

l无分页:某些业务逻辑中无分页,但默认情况下要返回前N条数据,如果没有说明就会造成使用方的困惑,徒增沟通时间成本。

l有分页:定义好默认一页记录数,还是允许使用方自行定义返回记录数。根据业务场景还是要约束一页记录数,避免数量过大而影响性能。

……

2.3 接口异常处理

        明确、详细的异常信息在调试api、快速定位解决问题中十分有用,因此我们必须设计良好的异常信息,以便自己记录和提供给使用方参考。

l参数检验异常

为了服务接口的安全,我们都会把参数做校验及转换。如参数id是整型类型的 id=81 我们就会做整型类型的校验,如果不是整型的就会返回“参数id必须是整型”的提示信息及自定义的错误代码。

l业务逻辑异常

在内部处理业务逻辑时,如果出现异常我们也要记录相应的异常信息及自定义的错误代码,而不能只给使用方提示“服务器发生错误”这样信息。估计看到这样的信息会抓狂,我们的目的是当使用方拿到错误代码对照接口api使用说明就会很快定位到问题所在。

 

2.4 接口api版本演进或版本控制

随着业务的发展,老的接口api已不能满足新的业务逻辑,此时是改造老的接口还是增加新的接口?如果在老的接口中进行改造来支持新的业务逻辑,但是不得不维护老的业务逻辑,无形中增加工作量和难度。实际中我们可以新增加接口api,老的api和新的api同时提供服务,然后将老的api平滑过度到新的api。

我们可以定义api版本格式如下

/XXX/api/v2/…

 

2.5 接口api业务功能分级与隔离

        对相关的服务或接口区分核心和非核心,并进行隔离部署,避免服务互相影响。

对于服务的依赖区分核心依赖和非核心依赖,在流量突增的情况下,降级非核心依赖。

优雅降级:对于调用超时的非核心服务,可以设定一个阈值,如果调用超时的次数超过这个阈值,便自动将该服务降级。此时服务调用者跳过对该服务的调用,并指定一个休眠的时间点,当时间点过了之后,再次对该服务进行重试,如果服务恢复,则取消降级,否则继续保持该服务的降级状态。

降级一般就是通过系统中的开关机制实现的。

 

2.6 服务接口api限流

服务接口api的降级和限流都是流量控制的处理策略,同时也是系统的自我保护策略。虽然牺牲了一部分业务功能和高并发量,但换来的系统的可用性。

 

常用的限流算法:

l漏桶(Leaky Bucket)算法

l令牌桶算法(Token Bucket)

 

怀疑第三方

 

3.1 设置合理的超时时间

如果超时就应该断掉请求连接,把业务处理的线程让给别的请求,加快处理同时也减轻了所依赖服务的压力。

 

3.2 合理的重试机制

        需要结合自己的业务以及异常来仔细斟酌是否使用重试机制。比如调用某第三方服务,报了个异常,有些同学就不管三七二十一就直接重试,这样是不对的,比如有些业务返回的异常表示业务逻辑出错,那么你怎么重试结果都是异常;又如有些异常是接口处理超时异常,这个时候就需要结合业务来判断了,有些时候重试往往会给后方服务造成更大压力,起到雪上加霜的效果。

 

3.3 异步调用服务接口

我们的服务接口中都会穿插着业务逻辑,如果使用httpsyncclient异步调用服务接口数据,此时处理自己的业务逻辑再结合异步返回的数据组织最终的业务数据,从而提高了服务接口处理能力。

 

3.4 有兜底,制定好业务降级方案

如果第三方服务挂掉怎么办?我们业务也跟着挂掉?显然这不是我们希望看到的结果,如果能制定好降级方案,那将大大提高服务的可靠性。

 l对数据做缓存处理

l友好的错误提示

……

 

 

做好自己

 做好自己是个非常大的话题,从需求分析、架构设计 、代码编写、测试、code review、上线、线上服务运维等阶段都可以重点展开介绍。

这次简单分享下架构设计、代码编写上的几条经验原则。

 

4.1 单一职责原则

单一职责原则,在我们的需求分析、架构设计、编码等各个阶段都非常有指导意义。

  在需求分析阶段,单一职责原则可以界定我们服务的边界,服务边界如果没界定清楚,各种合理的不合理的需求都接,最后会导致服务出现不可维护、不可扩展、故障不断的悲哀结局。

  对于架构来讲,单一职责也非常重要。比如读写模块放置在一起,导致读服务抖动非常厉害,如果读写分离那将大大提高读服务的稳定性。

  从代码角度上讲,一个类只干一件事情,如果你的类干了多个事情,就要考虑将他分开。这样做的好处是非常清晰,以后修改起来非常方便,对其它代码的影响就很小。再细粒度看类里的方法,一个方法也只干一个事情,即只有一个功能,如果干两件事情,那就把它分开,因为修改一个功能可能会影响到另一个功能。

 

4.2 控制资源的使用

CPU资源限制

内存资源限制

网络资源限制

磁盘资源限制

4.2.1 cpu资源限制

a)计算算法优化

  如果服务需要进行大量的计算,比如推荐排序服务,那么务必对你的计算算法进行优化。

b)锁

  对于很多服务而言,没有那么多耗费计算资源的算法,但cpu使用率也很高,这个时候需要看看锁的使用情况,建议是如无必要,尽量不用显式使用锁。

c) 习惯问题

  比如写循环的时候,千万要检查看看是否能正确退出,有些时候一不小心,在某些条件下就成为死循环,很著名的案例就是《多线程下HashMap的死循环问题》。比如集合遍历时候使用性能较差的遍历方式、String +检查,如果有超过多个String相加,是否使用StringBuffer.append

d)尽量使用线程池

  通过线程池来限制线程的数目,避免线程过多造成的线程上下文切换的开销。

e)jvm参数调优

  jvm参数也会影响cpu的使用,如《发布或重启线上服务时抖动问题解决方案》。

4.2.2 内存资源限制

a)Jvm参数设置

  通过JVM参数的设置来限制内存使用,jvm参数调优比较靠经验,可以参考《Linux与JVM的内存关系分析》。

b)初始化java集合类大小

  使用java集合类的时候尽量初始化大小,在长连接服务等耗费内存资源的服务中这种优化非常重要;

c)使用内存池/对象池

d)使用线程池的时候一定要设置队列的最大长度

  之前看过好多起故障都是由于队列最大长度没有限制最后导致内存溢出。

e)如果数据较大避免使用本地缓存

  如果数据量较大,可以考虑放置到分布式缓存如redis、tair等,不然gc都可能把自己服务卡死;

f)对缓存数据进行压缩

  比如之前做推荐相关服务时,需要保存用户偏好数据,如果直接保存可能有12G,后来采用短文本压缩算法直接压缩到6G,不过这时一定要考虑好压缩解压缩算法的cpu使用率、效率与压缩率的平衡,一些压缩率很高但是性能很差的算法,也不适合线上实时调用。

  有些时候直接使用probuf来序列化之后保存,这样也能节省内存空间。

g)清楚第三方软件实现细节,精确调优

  在使用第三方软件时,只有清楚细节后才知道怎么节约内存。

4.2.3 网络资源限制

a)减少调用的次数    

  减少调用的次数。经常看到有同学在循环里用redis/tair的get,如果意识到这里面的网络开销的话就应该使用批量处理;又如在推荐服务中经常遇到要去多个地方去取数据,一般采用多线程并行去取数据,这个时候不仅耗费cpu资源,也耗费网络资源,一种在实际中常常采用的方法就是先将很多数据离线存储到一块 ,这时候线上服务只要一个请求就能将所有数据获取。

b)减少传输的数据量

  一种方法是压缩后传输,还有一种就是按需传输,比如经常遇到的getData(int id),如果我们返回该id对应的Data所有信息,一来人家不需要,二来数据量传输太大,这个时候可以改为getData(int id, List<String> fields),使用方传输相应的字段过来,服务端只返回使用方需要的字段即可。

 

4.2.4 磁盘资源限制

打日志要控制量,并定期清理。1)只打印关键的异常日志;2)对日志大小进行监控报警。3)定期对日志进行清理,比如用crontab,每隔几天对日志进行清理;4)打印日志到远端,对于一些比较重要的日志可以直接将日志打印到远端HDFS文件系统里。

 

posted @ 2019-02-12 17:54  过山车  阅读(1122)  评论(0编辑  收藏  举报