2024年最头疼的一件事,就是技术群里还有人问我“电商返利APP有必要上微服务吗”。我的回答始终是:不是有没有必要,而是你的业务体量、迭代频率和团队规模,到了哪个阶段就该做哪个阶段的事。磨刀不误砍柴工,但刀太钝了砍不动柴,刀太快了也容易误伤自己。
我做电商返利系统这套Java架构,前后折腾了快三年,从最早的单体应用一路演进到基于Service Mesh的微服务架构,中间踩了不少坑,也沉淀了不少实打实的数据和方案。今天不聊虚的,就把这套架构演进的完整路径、关键决策背后的取舍逻辑、以及性能权衡中那些文档里不会写明白的细节,一次性摊开来讲清楚。这篇内容会比较长,建议先收藏再细看,尤其适合正在做微服务改造、或者已经在微服务里挣扎想上Service Mesh的Java团队参考。
1. 从单体到微服务:返利业务的拆分解法
1.1 单体应用撑到什么时候就该拆了
先交代一下背景。这个电商返利APP,核心业务链路是:用户从APP点击商品链接、完成下单后,平台通过订单同步接口拿到订单数据,匹配商品佣金规则、计算返利金额,最后经过审核和结算周期把返利打到用户账户。
最初这套系统就是一个典型的单体Java应用,Spring Boot + MyBatis + MySQL,单机部署,所有业务模块都在这一个工程里。说实话,在用户量十万级、日订单量几千笔的时候,单体架构完全够用,部署简单、排查问题方便、事务好控制,团队十来个人维护起来也没多大压力。
但业务数据不会原地踏步。用了不到一年半,系统就开始出现明显症状:
- 订单同步高峰时段,Tomcat线程池被打满,接口响应时间从平均200毫秒飙升到3秒以上。
- 佣金规则频繁调整,每次改规则都要重新发版,发布窗口越来越长,回归测试范围越来越大。
- 结算模块对账跑批时,CPU和内存被吃满,直接影响用户查询返利进度的在线接口。
- 数据库连接池竞争激烈,慢查询越来越多,DBA天天发告警。
这时候团队内部开始争论要不要拆分。我当时的判断是:已经出现典型的资源抢占和故障扩散问题,单靠加机器、加缓存已经治标不治本了,必须从架构层面做手术。不过我也同步强调了一个原则,拆分的目的是为了解耦资源和隔离故障,不是为了追微服务的时髦。所以第一刀下去之前,我们先做了一件事:按业务域梳理模块边界。
1.2 返利系统的业务域划分逻辑
把单体拆成微服务,最核心的一步是确定拆分维度。我们最终是按“业务能力”而非“技术分层”来拆的,这让后续的独立部署和独立扩容成为可能。
整个系统被拆成了六个核心微服务:
| 服务名称 | 核心职责 | 独立扩容场景 |
|---|---|---|
| 用户服务 | 用户注册、登录、账户信息、返利余额 | 大促拉新活动导致注册流量高峰 |
| 商品服务 | 商品信息聚合、佣金规则配置、类目管理 | 商品库数量增长、规则频繁变更 |
| 订单服务 | 订单同步、订单状态维护、订单查询 | 电商大促节点订单量暴涨 |
| 结算服务 | 返利计算、账单生成、提现处理 | 每月固定结算周期前的跑批计算 |
| 任务服务 | 签到、邀请返利、活动任务 | 活动运营期任务并发激增 |
| 消息推送服务 | 订单状态通知、返利到账推送 | 推送高峰期与核心交易隔离 |
每个服务的职责边界都以“业务动词”为锚点,比如“用户能做什么”归用户服务,“订单产生后怎么流转”归订单服务。边界清晰之后,数据表也跟着拆,每个服务独占自己的库表,禁止跨服务直接查表,只能通过API或者异步消息通信。
这里有个非常关键的教训:千万不能按“功能模块”来拆,比如单独拆一个“秒杀服务”“优惠券服务”,这种拆法听起来合理,但实际运行中你会发现它们的业务耦合度极高,往往需要频繁互相调用,最后变成分布式单体,比不拆还难受。我们第一次尝试就是这么翻车的,后来花了一个月重构才回到正轨。
1.3 拆分后的服务通信方案选型
服务之间要通信,第一反应是选HTTP/REST还是Dubbo这类RPC框架。我个人的经验是:外部系统之间用REST,内部服务之间用RPC。
理由很简单,内部服务调用频率高、接口关系紧密,RPC的二进制序列化效率远高于JSON文本传输,而且Dubbo自带服务治理能力,包括负载均衡、容错、降级、直连和限流,这些在微服务早期阶段都是实打实的刚需。我们用Dubbo做了第一阶段的微服务化改造,整体效果立竿见影:
- 订单服务高峰期CPU使用率从85%降到了40%以下。
- 结算服务跑批不再影响在线接口,因为已经完全独立部署了。
- 发布频率从两周一次提升到每周可发布三到四次,各个服务互不阻塞。
通信协议选型上还有一个细节,Dubbo的默认负载均衡策略是随机算法,但在我们这种订单量不均匀的场景下,实际效果不如一致性哈希。后来我们统一改成了一致性哈希,按用户ID进行服务节点路由,同一用户的请求尽量落到同一台机器上,这样本地缓存命中率提升了将近三成,接口耗时也稳定了不少。
2. 分布式改造的必经关卡:基础设施与数据一致性
2.1 注册中心与配置中心选型
从单体到微服务的第一个大障碍,不是写代码,而是基础设施的补齐。原来单体应用只需一个IP一个端口,拆成微服务后,服务实例是动态伸缩的,IP随时在变,所以必须引入注册中心。
我们当时在Nacos、Consul、Zookeeper之间做了对比。Zookeeper是老牌选择,但它的CP模型在服务发现场景下其实不是最优解,因为注册中心更看重的是“最终能看到”而非“绝对一致”。Consul的性能不错,但生态在国内相对小众。最终选了Nacos作为注册中心和配置中心,理由有三:
- 同时支持注册中心和配置中心,省去一套维护成本。
- 支持AP模式的临时实例,服务短暂不可达时不会直接剔除,避免了微服务之间的雪崩。
- 配置中心支持动态刷新,配合Spring Cloud的@RefreshScope,改配置不需要重启服务。
这里要提醒一下,服务注册中心的健康检查机制一定要认真调整。默认的心跳配置往往不适合所有服务,比如任务服务本身没有高频外部请求,心跳间隔如果设置得太短,容易被误判为不健康而摘除,导致下游调用直接报错。我们最终把心跳间隔调到了30秒,超时时间60秒,同时配置了合理的故障摘除策略,才消停了一阵子。
2.2 分布式事务与幂等设计
拆分之后,我最担心的就是数据一致性问题。单体时代的@Transactional事务控制,在微服务架构下彻底失效了。订单同步和返利计算是跨服务调用的,一旦中间某一步失败,数据就会处于不一致状态,这在返利业务中是不能接受的,因为用户看得见自己的返利金额。
我们采取了一套组合拳,而不是迷信某一种分布式事务方案:
- TCC补偿机制用于核心的资金类操作。比如提现操作,先Try阶段冻结余额,Confirm阶段执行扣款,Cancel阶段释放冻结。TCC的代码工作量不小,但资金安全容不得半点妥协。
- RocketMQ事务消息用于订单同步后的返利计算触发。Step 1 发送半消息,Step 2 执行本地事务,Step 3 确认消息后可被消费端处理。
- 对账+补偿任务兜底所有场景。每天凌晨3点跑批,比对订单服务、结算服务的账单数据,发现不一致自动触发补偿流程。
还有一个看似基础但极其重要的点,就是接口幂等。微服务之间调用,一次请求可能因为网络超时而重试,如果接口不幂等,一个订单就会被计算两次返利。我们在所有写接口上都加了幂等控制,基于Redis SETNX实现幂等键,同时数据库层面建了唯一约束,双保险之后这个坑才算彻底填平。
2.3 数据同步与缓存策略的重新设计
单体时代,Redis缓存只用来处理用户会话和热点商品信息。拆分后,数据被分散到各个服务各自的库中,跨服务查数据就没那么轻松了。一个典型的场景是订单列表页需要展示商品名称和返利比例,这些数据分别在订单服务、商品服务、结算服务中。
一开始怎么做都别扭。RPC同步调用的方式链路太长,平均要串行调三个服务,接口耗时直接翻倍。后来我们调整思路,采用“数据本地化 + 异步更新”的方案:
- 订单服务本地冗余一个商品快照表,存商品名称、主图、佣金比例等展示用字段。
- 商品服务任何变更,通过MQ广播一条商品变更消息,订单服务订阅消息后异步更新本地快照。
- 查询请求只查本地数据,不跨服务调用。
这个方案带来的收益是巨大的,订单查询接口的P99耗时从800毫秒降到了不到150毫秒。代价自然也有:数据不是强一致的,商品更名或者佣金比例调整后,可能要等上几秒才能同步到订单服务的本地表里。不过业务上完全可以接受,因为用户看到的是订单历史快照,本来也没必要跟着每次变更走。
3. 容器化改造与弹性伸缩基础
3.1 为什么选择容器化作为微服务的载体
微服务拆完之后,部署变成了痛苦之源。六个服务,每个服务三到四个实例,加起来二十来个Java进程要管理,用传统的Shell脚本部署简直就是灾难。环境差异、依赖冲突、版本管理混乱,各种问题层出不穷。这也是我们下定决心做容器化的主要原因——不给微服务配一套灵活敏捷的部署底座,微服务带来的灵活就被运维成本吃掉了。
所以我们采用了一个逐步推进的路径:先把所有服务打包成Docker镜像,标准化构建产物;然后引入容器编排引擎做统一调度,支撑服务的自动化部署、弹性扩缩容以及基本的生产发现能力;最后配置基于CPU和内存阈值的HPA策略,让服务能够应对突发流量。
整个过程大概用了一个季度,收益从第二个季度开始明显体现。以前上线一个服务要半小时,现在从提交代码到生产环境完成滚动发布只要不到十分钟。而且弹性伸缩真正起作用了,大促前不用再人工评估流量、逐个服务去加机器,HPA会根据实时负载自动伸缩Pod。
3.2 Java服务容器化的内存调优细节
Java应用容器化最坑的地方,不是Dockerfile写不对,而是JVM与容器资源配额之间的适配问题。旧版JVM无法自动识别容器限制的内存和CPU,导致明明Pod内存上限只有1GB,JVM却按宿主机几个GB的物理内存来设置堆大小,最终结果就是Pod直接被OOM Killer杀掉,服务频繁重启。
我们的解决方法是:
- 升级JDK到17,JVM真正支持容器感知,能自动读取cgroup限制。
- 显式设置JVM参数 -XX:MaxRAMPercentage=75.0,让堆占用不超过容器内存的75%。
- 预留25%的内存给元空间、线程栈、直接内存和JIT编译器使用。
- 配置JVM参数 -XX:+ExitOnOutOfMemoryError,OOM时快速退出让K8s重启实例,而不是进入不可用状态。
这几个参数看似简单,但没有踩过坑的人不会理解它们的价值。我们的系统在上容器化的第一个月,OOM重启次数是两位数,优化之后就归零了。
还有一个细节,容器中的DNS解析和网络通信也会影响性能,Java应用启动时如果配置了自定义DNS,可能会因为解析超时拖慢启动速度。我们的规避方式是在Deployment里配置合理的dnsConfig,包括适当的超时和重试次数,并预留了足够的启动探针容忍度,保证服务在JVM冷启动阶段不会被误杀。
3.3 K8s环境下的配置与密钥管理
微服务化之后,配置项的数量直接爆炸。每个服务有各自的数据源配置、MQ配置、Redis配置,加上不同环境的差异,原来写死在application.yml里的做法完全行不通了。我们做了两层设计:
- 环境基础配置放Nacos Config,按namespace区分dev/test/prod。
- 敏感信息如数据库密码、Redis密码,使用K8s Secret保存,挂载到环境变量。
有的团队嫌麻烦,把密码直接Base64编码写在ConfigMap里,这种操作等于把保险柜钥匙贴在门口。云上的密钥管理服务或者K8s的External Secrets方案,才是正经选择。我在实际项目里还见过因为ConfigMap更新没触发Pod滚动,导致配置热更新失效的,所以后来引入了配置版本化机制,每次配置变更都会自动触发一次滚动发布到关联工作负载,确保配置一定生效。
4. Service Mesh引入落地:从流量治理到零侵入改造
4.1 为什么在Dubbo阶段之后还要引入Service Mesh
服务数量多了之后,即使有Dubbo的服务治理能力,依然有几个顽疾是无解的:
第一个是治理规则的配置和运维成本高。每个服务的消费者端都要配置各种负载均衡策略、重试策略、超时时间、熔断降级开关,这些配置散落在各个服务中。想统一调整超时时间?不好意思,你得改十几个服务。
第二个是语言异构问题。虽然核心服务是Java,但有些辅助服务用了不同的技术栈,Dubbo这种绑定语言生态的RPC框架在这些场景下就成了限制而非助力。
第三个问题是中间件SDK的升级之痛。每一次框架的升级,都意味着所有Java服务都要改依赖、重新发版,还经常会因为版本不一致出各种兼容性问题。
Service Mesh的核心思想就是把服务通信和治理能力从业务代码里剥离出来,下沉到一个叫Sidecar的代理进程中。业务应用只需要关心业务逻辑,网络通信、流量控制、可观测性和安全性都由代理层接管。对我们而言,引入Service Mesh不是要干掉Dubbo,而是想解决那些框架SDK覆盖不到或者说治理成本过高的长尾问题。
最终引入方案是:核心交易链路保留Dubbo,走高性能的原有RPC通信通道;边缘业务和跨技术栈服务之间的通信切换到Istio管理;同时,所有服务的流量监测和治理规则,逐步统一收口到Mesh控制平面来做。
4.2 基于Istio的灰度发布与流量治理实践
我们做的第一批Service Mesh试点,选择的是消息推送服务和任务服务。这两个服务调用关系简单、变更频率高,非常适合做灰度发布验证。
Istio带来的第一个显著收益,就是灰度发布变得前所未有的简单。以前做灰度发布,要搞一套复杂的路由规则和负载均衡配置,要么用Nginx层做权重分流,要么改服务代码识别灰度用户。有了Istio后,VirtualService和DestinationRule两个资源就能搞定流量切分:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: task-service-vs spec: hosts: - task-service http: - route: - destination: host: task-service subset: v2 weight: 20 - destination: host: task-service subset: v1 weight: 80这段配置的意思是,新上线的v2版本只承担百分之二十的流量,v1版本承担百分之八十。观察一段时间后,如果v2的黄金指标(错误率、P99延迟、CPU内存)表现稳定,就把权重调到100%。如果发现问题,把权重直接归零即可完成回滚。整个过程的代码零改动,这在之前是完全不敢想象的。
流量管理方面,我们还利用服务条目和虚拟服务的能力,把内部服务对商品服务佣金规则缓存组件的外部调用也纳入了Mesh治理范围,配合熔断策略,规避了试点期间不少次上游抖动导致的核心链路性能波动。
4.3 Sidecar代理的资源开销实测分析
Mesh引入之后,我听到最多的质疑就是Sidecar带来的额外延迟和资源损耗。这个必须用数据说话,拍脑袋没意义。我们专门做了对比压测,在同样的业务规模和流量模型下,对比同一批服务启用和停用Sidecar的性能表现。
实测数据是这样的:启用Istio Sidecar后,服务间调用延迟平均增加2到5毫秒,P99延迟增加大概5到8毫秒。在Java服务内部调用动辄50到150毫秒的业务耗时面前,这个额外开销其实并不算大。资源开销方面,每个Sidecar(Envoy代理)会额外占用大约100MB内存和0.2个核CPU的常驻资源。如果你同时跑200个Pod,那就额外吃掉了20个核和20GB内存。
所以结论很明确:Service Mesh不是免费的,它是以可观的资源消耗和少量延迟为代价,换来了流量治理、可观测性、安全和运维效率上的收益。是否值得上,核心要看你的集群规模和治理复杂度。如果只有三五个服务,强行上Istio纯粹是自找麻烦;但当你有一二十个服务且流量调度、灰度发布、安全策略成为高频需求时,这笔投入就算得过来了。
4.4 跨服务可观测性:调用链追踪与指标监控
微服务化的最大痛点之一就是问题定位难。单体应用时期的日志追踪思路,在跨服务调用面前完全失效——一个请求可能需要串联订单服务、商品服务、结算服务,出问题后根本不知道卡在哪一环。
我们用Mesh的分布式追踪能力,配合OpenTelemetry规范,把全链路调用链数据统一采集起来。每个服务在入口处生成一个traceId,通过W3C的traceparent协议在HTTP头中传递,跨服务时自动透传。前端用户端也会生成一个requestId,网关层在入口把requestId和traceId绑定,这样用户反馈一个问题时,我们可以直接从日志平台拉出这个请求的完整调用链。
除了调用链,三个黄金指标必须每个服务都要配齐:请求速率、错误率、延迟分布。我们采用Prometheus + Grafana的常规组合,在Grafana上做了一个电商返利业务的可观测大屏,按服务维度展示核心指标,按用户维度展示漏斗转化率,按接口维度展示慢查询TopN。
我还要强调一个看上去不起眼但实际价值很大的细节:统一日志格式。微服务化之后,谁还按自己的习惯打印日志,谁就是排查问题时的灾难制造者。我们强制所有服务必须使用统一的日志切面,包含时间戳、traceId、服务名、版本号、调用者信息、业务关键字。这样,一套日志模板走天下,查问题的时候可以像看连续剧一样把跨服务日志串起来看。
5. 性能权衡的核心方法与实测数据复盘
5.1 性能拆解的三个关键层面
我见过很多团队做性能分析时特别混乱,一上来就拿着压测工具猛测,测出瓶颈就拍脑袋定方案。其实性能根本不是一个宏观指标能说明白的,必须拆开看,每个层面单独分析、单独优化。
我们主要从三个层面做拆分:
网关层。入口的流量治理和协议转换是否高效,有没有超时等待、慢速攻击、无效请求拖垮后端。网关性能指标主要看QPS上限、平均响应时间、并发连接数。
服务间调用层。Dubbo和Mesh通道下的调用耗时、重试情况、熔断触发次数、连接池使用率。这一层最容易出现问题的就是超时参数和重试阈值设置不合理,导致雪崩。
数据层。缓存命中率、数据库连接池活跃数、慢SQL数量、锁等待时间。几乎所有的性能瓶颈最后都会在数据层暴露出真实面目。
我们做的第一步是异步调用链路分析。基于全链路Trace数据,把所有核心接口的调用链耗时占比拉出来做排序,谁最慢一眼就知道。第二步才是针对瓶颈做优化。这个顺序很重要,如果先闷头优化某个服务,结果却发现瓶颈在网关超时重试机制,工程上的浪费就会非常明显。
5.2 性能瓶颈定位与优化实例:慢SQL与缓存
这里分享一个真实的优化案例。我们的返利余额查询接口,高峰期P99延迟一度涨到1.2秒,用户体验明显卡顿。
第一步通过调用链定位,发现80%的耗时集中在用户服务的数据库查询。第二步看慢SQL日志,发现是返利明细分页查询,对一个大表做了深分页OFFSET 300000 LIMIT 20,MySQL需要先扫描前面三十万行才能返回结果,这种写法在高并发场景下必慢无疑。
我们的优化是典型的组合拳:
- 改写分页逻辑,从“偏移量分页”改成“键集分页”(Keyset Pagination),通过WHERE id > 上一页最大id来取下一页数据,避免深分页扫描。
- 给查询条件涉及的字段建立联合索引,合理包括排序字段,避免文件排序。
- 热点查询结果缓存到Redis,Key设计为userId + 分页条件,缓存时间五分钟。
优化后,同样的接口P99延迟从1.2秒降到了160毫秒,数据库压力最直观,慢查询数量直接减少了95%。这个案例的价值不在于某个技术多高端,而在于它示范了一个有效的拆解过程:调用链定位、瓶颈识别、针对性优化。
5.3 压测方案与容量规划的计算思路
容量规划不是玄学,核心就是两个词:压测+核算。不做压测就拍脑袋扩容,那是给云厂商做慈善。
我们的压测方案剥离了线上风险,在测试环境搭建与生产等规模的性能测试集群,用分布式压测工具模拟真实流量模型,包括日常平均流量、高峰流量、秒杀流量和水位流量。压测过程中设置了四档泳道:单接口压测、单服务压测、全链路压测和故障演练压测。
容量规划的关键公式没那么复杂:
所需实例数 = 预估峰值QPS / 单实例可承受QPS。先通过压测确定单实例的承载上限,再用这个公式做核算。比如订单服务单实例压测结果是800QPS,预估大促峰值是8000QPS,再加上百分之三十的安全余量,订单服务就需要13个实例。
有一个经验之谈,扩缩容永远要前置而不是后置。弹性的自动扩容有生效时间,在流量陡增的场景,要么提前预留buffer,要么降低扩容阈值并缩短评估周期。我们有次大促就因为HPA的扩容策略太保守,流量高峰持续了三分钟才完成扩容,这三分钟的延迟直接导致了部分用户请求超时,教训非常深刻。
5.4 性能与成本之间的决策模型
架构演进中,性能从来不是唯一约束,成本预算才是。每做一个技术决策,我们都会过一遍“性能-成本-复杂度”的三角模型:
| 方案 | 性能收益 | 额外成本 | 维护复杂度 | 我们的决策 |
|---|---|---|---|---|
| 增加数据库只读副本 | 高 | 高(每副本都是真金白银) | 低 | 只在数据服务配备两个只读副本 |
| 引入本地缓存层 | 中 | 低(仅增加内存) | 中 | 在订单和任务服务中推广 |
| 使用分库分表中间件 | 高 | 中 | 高 | 当前数据量未触发,暂不执行 |
| 全面上Service Mesh | 中(有损耗) | 高 | 高 | 核心用Dubbo,边缘用Mesh |
| 采用自研流量治理SDK | 中 | 中 | 中 | 已通过Mesh标准协议收敛治理能力 |
这张表的结论是:架构决策没有银弹,只有基于自身业务规模和预算约束下的最优取舍。我们的原则是核心链路追求极致性能和稳定性,所以保留Dubbo;长尾场景追求运维效率和灵活治理,所以用Mesh。让每一分钱都花在实际业务收益上,而不是花在技术噱头上。
6. 演进过程中的踩坑记录与关键总结
6.1 关键优化决策的收益量化
复盘整条演进路径,有几个关键决策的收益是可以用数字量化的:
- 微服务拆分后,订单服务高峰CPU使用率从85%降到40%以下,在线接口P99耗时从3秒降到400毫秒。
- 数据本地化方案落地后,跨服务查询接口P99耗时从800毫秒降到150毫秒。
- 容器化与HPA落地后,新服务上线时间从半小时缩短到10分钟,大促扩缩容无需人工介入。
- Service Mesh试点后,服务变更上线的灰度发布操作从小时级缩短到分钟级,单个服务变更的失败回滚时间缩短到秒级。
- 全链路压测和容量规划模型落地后,大促期间未再出现因容量不足导致的P0故障。
这一串数据背后,不是某个单点技术的功劳,而是整套方法论和工程节奏的胜利。
6.2 从单体到Mesh的演进节奏建议
给准备走这条路团队的建议,我浓缩成一句话:先单体后微服务,先微服务后容器化,先容器化后Mesh,千万不要跳步。
原因很简单:每一步的引进都需要前一步打下的基础。没有微服务拆分和注册中心,直接上Mesh会发现服务粒度太粗,Mesh的治理优势完全发挥不出来;没有容器化标准化部署,上了Mesh之后Sidecar的编排会让你头疼到怀疑人生。
每一步的节奏建议至少稳定运行三到六个月,压测和监控指标达到预设标准后再迈入下一步。避免被技术媒体的趋势文章带节奏,架构演进是一件“慢就是快”的事,稳稳的才最快。
6.3 最终经验总结与踩坑清单
最后把三年踩坑的核心经验浓缩成一份清单,每一条都是用真实故障换来的,照单执行能帮你省下大量不必要的学费:
第一,拆微服务先找业务边界,不要按技术分层拆;分布式单体是最尴尬的中间态。
第二,JVM参数必须适配容器配额,升级JDK版本并且使用容器感知内存参数配置,否则你会有一位长期相伴的OOM伙伴。
第三,接口幂等是分布式系统的安全网,涉及资金和数据一致性的接口,务必要做双保险。
第四,缓存和本地数据快照是很实用的降本增效手段,但要接受最终一致性,不要在不该要强一致的场景强行追求强一致。
第五,所有治理规则、流量策略一定要考虑“运营人员能否看懂并操作”,写反人类的规则,最终会反噬你自己。
第六,性能优化永远从监控数据出发、以调用链分析为依据,杜绝主观猜测。数据指向哪里,你的优化才打向哪里。
第七,也是最后一条:技术选型唯一重要的标准,是它能不能以可接受的成本解决当前业务的实际痛点,而不是它是不是当下最“火”的架构。做架构决策最怕的不是技术难度,而是团队自己骗自己,用高端技术掩盖业务上还没定义清楚的问题。