尤娜系统的第一次飞行中换引擎的架构垂直拆分改造

davisl
发布于 2022-9-29 11:36
浏览
0收藏

从前,有一个简单的通道系统叫尤娜……

 

转眼离上次尤娜系统削峰填谷的改造已经过去4个月了。小B又来找尤娜,说她还有几个客户想对接B系统,尤娜系统对接的不错,所以希望这些客户都直接对接尤娜,小B会给利润分成。这是件双方都受益的好事,尤娜一口答应了。

 

未来需求预测

 

尤娜请我喝咖啡,我们一起聊到尤娜系统未来可能的业务。尤娜说她还有几个朋友,也苦于和各个公司开展业务,对接成本高。尤娜出马一定可以说服他们用尤娜作为通道代理,各方受益。尤娜还说到虽然现在短期可以不考虑成本,长期希望作为代理公司可以盈利。另外,现在对接的不多,利润分成可以线下打款,以后业务做大了,估计还需要系统来完成整个业务闭环。让我好好考虑一下。

 

我其实有点迷茫了:现在设计到哪一步是才是合理的呢?找到了《M*N个策略造成类爆炸怎样重构?》这篇文章,里面提到:架构和代码不是设计出来的,是演进出来的。我觉得有道理,先把现在相对确定的需求设计完成:
1>B接新业务

2>新公司接新业务

3>考虑成本

4>做新业务的同时保证老业务的稳定性

 

架构设计

尤娜系统的第一次飞行中换引擎的架构垂直拆分改造-鸿蒙开发者社区

目前尤娜系统是这样的,我们买了一个域名,绑定到一台nginx反向代理服务器。nginx是24核128G高配物理机。目前线上高峰时刻2000TPS,尤娜系统调用B系统平均耗时300ms,TP99是800ms(99%的请求在800ms内完成)。加上kafka全链路高峰时刻加上排队,TP99要2分钟。由于kafka削峰填谷,给B公司的请求并发量不超过500TPS。

 

尤娜系统使用了24台4核8G的机器来运行,目前容量满足需求,有一定的冗余,但具体多少还没有压测过。web容器我用的是jetty。网上说建议worker线程数设置为100或者200。我设置成了5000,似乎工作的挺好。

 

但是从成本的角度,新业务仍然使用像A系统这样同步调用的模式有很大的性能问题。性能问题全靠堆机器来支撑,所以目前的模式,公司是在做赔本的买卖。开展新业务可以从一开始就规定使用异步的模式,这样可以避免2分钟请求处理不完,一直占用线程,打满线程池的问题。所以单独从新业务的角度,架构是这样的:

尤娜系统的第一次飞行中换引擎的架构垂直拆分改造-鸿蒙开发者社区

但这是个理想化的模型,客户未必会认可。如果客户要同步调用我们也需要支持。那就需要对这个同步的模型做一个改造:也拆分成两个。老模型改造后的架构是这样的:

尤娜系统的第一次飞行中换引擎的架构垂直拆分改造-鸿蒙开发者社区

现在就面临一个问题:老系统和新系统有复用的模块,也有不同的模式。是分开建设还是代码统一维护?是分开部署还是部署在一起?就这个问题我列了优缺点对比及相应的解决方案,请尤娜老板来做决议。

尤娜系统的第一次飞行中换引擎的架构垂直拆分改造-鸿蒙开发者社区

复用方案从稳定性上偏弱,我参考了《服务的容灾与容错》分析可以采用线程池隔离的方案,又通过《hystrix线程池隔离的原理与验证》和《Java线程池总结》找到了具体的实施方法。最终尤娜采纳了复用方案。

 

所以最终的架构方案是这样的:

尤娜系统的第一次飞行中换引擎的架构垂直拆分改造-鸿蒙开发者社区

系统迁移方案

 

尤娜系统要垂直拆分:一拆二。从《服务治理的技术血脉》这篇文章我了解到垂直拆分是服务治理的开始,以后事情会越来越多。目前首要的事情是要平滑进行拆分后的迁移。

 

首先,我按照尤娜的建议,定义了标准的接入规范。对外暴露两种接入方式:

●  同步调用

    ◆  同步调用接口/b (兼容老逻辑,保持接口不变)

●  异步调用

    ◆  异步调用接口/asyn/b
    ◆  异步回调接口,由接入方提供

 

同时规定这些接口必须透传一个18位的请求编号,这个请求编号是做全链路跟踪用的。因为在和A对接时没有这个号,所以我做了兼容,如果没有传,我就自己生成一个。生成方法我参考的是《惊艳面试官-Java中关于随机数生成8种方式的思考》里的随机数生成,18位基本上可以保证每天千亿级别请求量的情况下不重复。

 

异步部分是新逻辑,该在哪个工程代码下开发就在哪里开发。老逻辑,从消费端可以将逻辑迁移到新系统,外部不感知。因为消费端是真正的逻辑处理部分,所以我将这部分叫做“尤娜系统逻辑处理”。

 

在完全迁移之前,老系统保持不动。尤娜系统逻辑处理订阅kafka消息,需要灰度来承接流量。为此,我选择在凌晨1点到5点的低峰期上线,这个时间段,1分钟只有几笔请求。但这是一次大升级,还是需要和小A的公司、小B的公司打声招呼。最好他们也能一起升级把跟踪号加上。并请小A的公司出人一起确认业务状态。尤娜出面轻松的帮我搞定了小A公司的事项。小B的公司不同意做任何变更,这个也不是必需的,所以这个作罢。

 

凌晨1点,我准时启动了一台新的逻辑处理服务。过了十几分钟,终于有个请求被这台机器消费了。我通过跟踪日志、数据库数据确认处理和老逻辑完全一致。请小A公司的人确认请求结果符合预期。打算第二天再部署一台。

 

我的打算是因为逻辑处理服务耗时TP99是800ms,最高请求并发量是500TPS。按单台并发量是200来算,3台4C8G足够。留有一定冗余容灾,部署6台。灰度发布,3天部署完。之后老尤娜系统,也就是新的尤娜系统api网关,会每天停止一台的消费,只做生产者。

 

这就是尤娜系统的第一次飞行中换引擎的垂直拆分改造过程。

 

复盘

 

整个过程给我印象最深的是沟通问题。阿里黑话叫:拉通、对齐。领域驱动设计DDD里叫通用语言。这些都在说沟通的问题。现在很多事情,技术不是难题,关键是沟通。和领导的沟通,和外部的沟通。沟通要放在第一位。

 

为什么网上说建议worker线程数设置为100或者200。我设置成了5000,似乎工作的挺好?

 

先上使用spring boot设置方法的代码:

 

@Configuration
public class JettyConfig {
    @Bean
    public ConfigurableServletWebServerFactory webServerFactory()
    {
        JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
        factory.setPort(9000);

        // Tweak the connection config used by Jetty to handle incoming HTTP
        // connections
        final QueuedThreadPool threadPool = new QueuedThreadPool();
        //默认最大线程连接数200
        threadPool.setMaxThreads(5000);
        //默认最小线程连接数8
        threadPool.setMinThreads(20);
        //默认线程最大空闲时间60000ms
        threadPool.setIdleTimeout(60000);
        factory.setThreadPool(threadPool);
        return factory;
    }
}
 

设置maxThreads主要是两点考虑和限制:

1>JVM设置时有Xss参数代表每个线程分配的内存资源大小。我设置为128K。这样,最大5000*128K占不到1G内存。堆内存设置为3G。内存共8G,还有足够的内存可以使用。

2>线程数多会影响上下文切换,如果CPU密集型处理可能会影响响应速度。但是目前线程主要是在等待IO,也没有很强的时效性要求,所以设置成5000问题不大。

 

分类
标签
已于2022-9-29 11:36:55修改
收藏
回复
举报
回复
    相关推荐