旧服务优化思路
高可用
讲互联网的技术,不能不说高可用;
从某些书上面写的,主要通过“冗余+自动故障转”来实现的;
对于以下分层,每一层上面都使用上面两种方式:
(1)【客户端层】到【反向代理层】
(2)【反向代理层】到【站点层】
(3)【站点层】到【服务层】
(4)【服务层】到【缓存层】
(5)【服务层】到【数据库读】
(6)【服务层】到【数据库写】
具体事例
记录一下具体实际工作中遇到一些烂项目,如何让它有基础的保障
下面的事例中主要是服务层、缓存层、数据库,其中几处分类是有一定的重叠的
监控
服务本身除了提供基本的 api 延迟、qps、状态码统计,还需要加上对于依赖的监控
主要核心的监控数据包含以下
a)服务本身提供的能力:api延迟(每类path)、qps、状态码统计(包括400主要是参数问题例如被攻击、500、404主要是被扫描)
b)服务间调用:http接口延迟+错误量统计、grpc接口延迟+错误量统计
c)存储调用:redis(缓存)延迟+错误量统计、mysql(存储)延迟+错误量统计、es、hbase
d)中间件调用:rabbitmq(或者nsq,消息中间件)的延迟+错误量统计
e)服务占用资源:进程内存监控、进程cpu占用、协程数量、channel(go语言)、内核线程数量错误上报(注意不是日志收集)
无论是采取nsq + es + kibana、还是简单的sentry,都应该将错误上报出来,不应该将错误(异常)掩盖,尽量让错误尽早的暴露出来降级
对于依赖的服务,不能直接的信任,需要对多方面的降级
a) 需要增加超时参数,不能过度信任依赖服务能在极短时间内有所返回
b) 返回值不能完全信任,做好返回值是任意值的准备长链
grpc 默认是http2,所以默认就是常量,主要需要注意负载均衡
主要http调用需要使用http11(Connection:keep-alive)熔断
对于依赖服务如果出现长时间不可用,需要做一些熔断
如果发现了依赖服务已经不可用了(此时需要将错误信息上报上去)接着熔断防止危害扩散
这里面需要注意一点就是,熔断是依赖服务的更强降级(我的理解)
之前遇到一个场景,A调用B(http接口,有降级),B服务的量级坑不住,调用疯狂超时,导致链接数(establish,time_out)暴涨,端口资源耗尽。而且这种场景下,采用长链只能起到优化,严重起来依然是灾难,最好保护手段是熔断
6. 缓存
a)
对于稍大流量(5k)的服务,不是很建议使用ssdb,之前做压测的时候
官方推荐使用多线程,但是官方库却只有单线程的client
1. 在磁盘、cpu、内存都正常的情况下,32G SSDB磁盘数据,单线程(官方原生库)100000/s没有问题,但是我们要的是并发,只有一个线程是有问题的,5k的流量在排队等待一个ssdb线程返回数据是很快就会出现问题的(假设是10ms,没办法redis都是按μs做单位的)
2. 然后无论怎么调整协程数都是有问题的,链接数超过500过后,但线程的执行性能严重下降,最后设置了3000链接数,最终也只是支持18000并发
b)
redis 的压测,基本跟网上差不多,5万/s 的qps 依然很快,这里面注意的是能到10万/s主要是利用了pipeline
,如果撇开了pipeline
在6万/s左右就会有点问题了
c)
如果缓存量非常巨大的话,例如超过32G左右的时候,需要使用redis集群将数据分散在几个节点上面
之前优化的时候主要使用了twemproxy
,优劣势网上都有,就不多提了
需要注意的是,对于单点twemproxy
只能解决存储的问题,并不能解决并发读写性能问题
之前遇到了一个场景,5万qps的redis操作,单点的twemproxy
严重退化到平均操作延迟30~40ms (其中get操作约3万,expire约3k)
最后解决方案是又重新部署了同样配置的twemproxy
,这里面注意两点,一个是设计key的时候,尽量保证hash_tag
;第二使用是nodes names
7. 大单表
a)读写分离
主要解决的是读性能瓶颈
(单库读上限),当然本身sql语句有问题,读写分离并不能解决什么问题,如果是全同步的话,依然会卡死写实例
;如果是半同步的话,会导致数据不一致,有缓存的话,简直是灾难;
公司之前使用的是多主模式,mgr
+ mycat
- 线性提升数据库读性能
- 通过消除读写锁冲突
b)水平切分
- 线性降低单库数据容量
- 线性提升数据库写性能
水平切分,一般有两种维度,一种是服务自己去做业务拆分;一种是中间件,例如阿里云的hdb(或者drds);
自己拆业务比较彻底一点,但是会复杂一点;中间件要简单一点,但是依赖设计的distribute key(sql语句需要命中,否则会全节点扫描);