[Java复习]架构部署 超时重试 幂等防重
画一下你们系统的整体架构图,说说各个服务在生产环境怎么部署的?
核心:服务框架、注册中心、网关
即使你没有用很多微服务架构里的东西,只要有上述三个东西,配合上写一些文档,接口文档,分布式系统架构,其实就出来了,很多中小型公司,一个小型技术团队,后端开发工程师总共就10多个人。
常见生产实践问题:
关注分布式系统一些生产实践的问题,应对你们公司的流量,各个服务、网关、注册中心,都是如何部署的?
部署几台机器,每台机器的配置是什么,每天整体的流量有多少,高峰期的请求量有多少,你的整体系统是否抗住了?
各个服务之间的超时、重试、幂等性?
中小型系统,拆分10-20个微服务。大型互联网公司,一般几百个,几千个微服务。
中小型,一般2-3台机器足够,把服务上线,服务发现优化到极致。
服务上线:注册表多级缓存同步至1秒,拉取频率降低至1秒。
服务心跳:1秒报1次。
故障发现:1秒检查一次,2,3秒没有认为没有故障等。
服务注册中心没有压力,服务注册中心部署2台机器,每台4C8G,高可用,每秒轻松几百请求,甚至上千。前提是数据库SQL别写的太烂。
网关机器配置稍微高一些,4C8G,一台扛每秒几百个请求,部署3~4台,保证每台网关机器压力较小,进一步保证可靠性。
为什么网关Zuul之前还要配置Nginx?
Nginx反向代理,软负载,把请求均匀负载到多台Zuul的机器上。LVS和Nginx处于不同的负载维度,主要是运维工程师负责管理。
数据库,MYSQL,16C32G,物理机最佳,平时扛每秒几百请求,高峰期最多每秒扛三四千请求。
扛几千请求时机器会负载很高,CPU,IO,网络负载很高。DBA在优化一下。
你们系统每天有多大访问量?每个服务高峰QPS多少?压测过服务最大QPS吗?
每天服务多少请求量,高峰每秒qps,在代码里稍微加一些metrics代码,对自己运行过程中各种请求量,每秒请求量,成功次数,失败次数,在内存里直接做一些计数。
在负责的核心服务里,核心接口,开发一个简单的metric统计机制,AtomicLong,保证原则性,并发数据统计准确。
每个接口被调用时,可以对每个接口每分钟做一个metric统计,每个接口每天统计计数。
再通过Log4j, logback等日志组件,把次数直接打印到日志文件,统计出高峰期每秒系统被访问的次数,每条每个接口访问量。
响应延时:
计算一下每个接口从请求到执行完毕,需要耗费多长时间,算一下每个接口平均的请求延时,
TP99,TP95,TP90,TP50,TP99,99%的请求耗费的时间在100ms以内,但是1%的请求可能耗费的时间在100ms以上
TP99 = 100ms TP95 = 50ms,95%的请求耗费的时间多在50ms以内,但是5%的请求耗费的时间在50ms以上
压测工具,java压测工具,开源的可以用的,模拟出来同时有多少用户发起多少请求,每秒发起1000请求能抗住吗?每秒钟发起2000请求能抗住吗?
假设你的系统每秒钟最多抗800请求,如果你的压测工具每秒发起了1000个请求,此时会发现最多只有800个请求同时可以被处理,
剩余200个请求需要进行排队被阻塞住了,表示你这个系统每秒钟最多抗800个请求。
如果系统访问量比现在增加10倍,你们考虑过系统的扩容方案吗?
网关直接多部署10倍的机器即可,前面的Nginx做会负载均衡,把流量均匀分发给各个网关机器。
服务扩容,都很简单的,多加机器,部署启动,自动注册到注册中心去,此时其他服务会自动感知到你的服务多加了一些机器。
服务实例变多了10倍,此时几十个服务实例,几百个服务实例,对eureka机器会造成每秒几百请求,没问题,eureka机器,8核16G的配置,单机抗上千请求,很轻松。
数据库本来是每秒几百请求,10倍,每秒高峰期是三四千请求,横向扩容很麻烦,
此时可以考虑给单个数据库部署的机器提高配置,32核128G高配物理机,每秒钟抗几千请求问题不大。
总结: 最基本的操作就是扩容。
网关:横向加机器。
注册中心:纵向升配置。
数据库:纵向升配置。
当然还有很多其他专门针对分布式,高并发的优化和操作,不过加机器都是最简单直接的。
你们生产环境的服务是怎么配置超时和重试参数的?为什么要这样配置?
背景:Spring Cloud生产优化,系统第一次启动的时候,调用请求经常出现timeout。
原因:每个服务第一次被请求的时候,他会去初始化一个Ribbon的组件,初始化这些组件需要耗费一定的时间,所以很容易会导致超时。
解决方案:让每个服务启动的时候就直接初始化Ribbon相关的组件,避免第一次请求的时候初始化。
ribbon: eager-load: enabled: true zuul: ribbon: eager-load: enabled: true feign: hystrix: enabled: false
线上的服务,每个服务部署上线的时候,一般来说都需要配置相关的超时时间还有重试次数
订单服务 -> 积分服务、库存服务、仓促服务
订单服务对于其他服务的调用,一般来说限制在多长时间就必须认为是超时了,如果超时之后如何进行重试
积分服务部署了两台机器,机器1和机器2
订单服务在一次请求积分服务的机器1的时候,超过1秒钟,超时了;此时需要进行重试,对积分服务当前的这台机器1重试几次?如果说机器1不行,是否可以重试一下积分服务的机器2?
ribbon: ConnectTimeout: 3000 ReadTimeout: 3000 OkToRetryOnAllOperations: true MaxAutoRetries: 1 MaxAutoRetriesNextServer: 1
中小型的系统,没必要直接开启hystrix,资源隔离、熔断、降级,如果你没有设计好一整套系统高可用的方案。
增加重试机制依赖:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
如果出现服务请求重试,会不会出现类似重复下单的问题?
可能会。
订单服务 -> 创建订单 -> 库存服务 -> 扣减库存 -> wms服务 -> 通知发货 -> 积分服务 -> 增加积分
场景:
订单服务调用库存服务的时候,因为网络抖动,请求超时了,超过了秒钟,此时订单服务会重试,再次调用一下库存服务,发送一模一样的请求过去。
比如说,订单服务第一次请求库存服务,库存服务其实是把扣减库存的业务逻辑执行成功了,
只不过网络问题,导致响应迟迟没有返回给订单服务,可能在1.2s之后返回了响应给订单服务。
订单服务就认为请求超时了,他就再次发送了一个一模一样的请求给库存服务,库存服务可能会再次对库存进行扣减。
对于核心接口的防重幂等性,你们是怎么设计的?怎么防止重复下单问题?
常见方案:
1. 数据库唯一索引
2. 基于Redis实现幂等性防重
核心接口,幂等性都是自己保证,对应Create操作,通过DB唯一索引来保证;对于Update操作,建议在核心接口基于业务逻辑,配合Redis,来保证幂等性。
比如库存,定制化的针对接口开发幂等性的机制,比如说一旦库存扣减成功之后,就立马要写一条数据到redis里去,order_id_11356_stock_deduct,写入redis中,如果写入成功,就说明之前这个订单的库存扣减,没人执行过。
但是如果此时有一些重试的请求过来了,调用了你的库存扣减接口,他同时也进行了库存的扣减,但是他用同样的一个key,order_id_11356_stock_deduct,写入redis中,此时会发现已经有人写过key,key已经存在了。
此时你就应该直接对刚才的库存扣减逻辑做一个反向的回滚逻辑,
update product_stock set stock = stock - 100,update product_stock set stock = stock + 100,反向逻辑,回滚自己,避免重复扣减库存。
参考资料:
21天互联网Java进阶面试训练营(分布式篇)-- 中华石杉