缘起
近期在 CR 代码的时候,愈发感觉对代码和服务的掌控越来越弱,细想下,这种感觉源自核心链路代码和非核心链路代码混合在一起 -- 混乱的关系无法预估表现、有限的精力容易顾己失彼。
在哪
窘境:
- 核心链路调用非核心接口 -- 乍一看可以静态分析调用关系来检查,但却无法解决多态的情况!
- 资源共享导致接口不稳定 -- 由于每个 API 有不同的资源使用率、会带来不同程度的资源消耗,在这种情况下,评估接口的表现就变成了运气,尽管我们可以得知不同在业务模型 ( 接口比例 ) 下的表现,但问题是怎么确定当前的业务模型?
- 不同场景不同的开发模式和资源使用 -- 从关注点上来看,服务可以分为两类:关注响应时间和关注吞吐量,这就类似于流处理和批处理。关注响应时间的模式:能提前计算则提前计算、能简单处理则简单处理 ( 例如基本上就是 KV 访问然后内存计算 );而关注吞吐量的场景则是使用各类框架或者 DSL 定义逻辑,此时开发效率大于性能,性能优化更多集中在基础设施层。
去哪
隔离核心链路和非核心链路 -- 集中资源优先保本;
隔离后台链路和前台链路 -- 可预估服务稳定表现。
通常来说,核心链路功能相对稳定且代码占比较小但流量巨大,这就给治理带来很大的便利和性价比。
路径
难点:
- 迭代并行 -- 大改动长周期意味着会产生很多冲突,带来更大的犯错概率;
- 缺乏单测 -- 意味代码变更没有可靠质量保证手段,能做的只有拷贝粘贴。
按照以往经验,要想更好地实施,一定要每一步的改动都足够小并且同时是可以独立部署独立交付,否则很难稳步推进,简单顺手的事情谁会拒绝呢?例如:针对已经开放出给终端的 API,让终端修改调用新服务是可行但不友好。跟着迭代修改是一个屡试不爽的策略,慢但稳,而这就是当前的关注点,
按照上述的分析,如下几步走 ( 以 Spring Boot 为例 ): 0. 梳理链路
- 完全拷贝原有代码,修改依赖 ID ,删除非核心 API
- 网关路由
public class RedirectFilter extends ZuulFilter {
@Autowired
private RedirectProperties redirectProperties;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* Be caution, need run after PreDecorationFilter!
*/
@Override
public int filterOrder() {
return 6;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String serviceId = (String ) ctx.get(FilterConstants.SERVICE_ID_KEY);
String uri = (String) ctx.get(FilterConstants.REQUEST_URI_KEY);
return redirectProperties.needToRedirect(serviceId, uri);
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
String serviceId = (String ) ctx.get(FilterConstants.SERVICE_ID_KEY);
String uri = (String) ctx.get(FilterConstants.REQUEST_URI_KEY);
ctx.set(FilterConstants.SERVICE_ID_KEY, redirectProperties.redirect(serviceId, uri));
return null;
}
}
- 修改 RPC ID,链路调用方配合修改 ( 包名可以不用修改减少修改量 );
- 每次修改的代码的同步两个项,当然这个同步指的是两边均存在的代码 ( 后续逐步抽离公共代码,在测试上优先保障核心链路 );
- 数据库配置隔离,读写分离。
以上的动作都是简单的复制,不涉及任何的逻辑代码修改,最大程度降低了出错概率,剩下的交给集成测试,至此完成第一阶段架构治理 -- 隔离维稳。
治理是一个体力活,不仅仅考验技术,更考验心理:换位思考、求真务实、Step by Step,才能通向曙光!