微服务VS单体应用
文章地址:Microservices Are a Tax Your Startup Probably Can’t Afford / 微服务是您的初创公司可能无法负担的负担
一些摘录总结:
- 结构良好的单体应用可以让团队专注于交付,而不是处理紧急情况。通过一些“最佳实践(Best practice)”案例,避免单体应用内部糟糕的模块化
- 把微服务视为拓展工具,在遇到实际拓展瓶颈时考虑,而不是作为项目的初始模板。过早地引入微服务将遇到重复的基础设施、脆弱的本地设置和缓慢迭代的问题
- 按照业务逻辑拆分微服务,在早期产品阶段可能会过早地固化结构。这种随意的服务边界,最终会导致共享的数据库、服务间调用仅用来实现简单工作流、伪装成分离的耦合。因此又回到了第2点,将微服务视为拓展工具,按实际瓶颈拆分微服务
- 单个项目中通常会包括一些基础内容(代码风格检查Lint、测试基础设施、本地环境配置、文档、CI/CD配置),在处理微服务时这些内容将乘以服务的数量。早期可以利用共享设施的单代码库结构来简化工作
- 微服务引入了一些无形的网络:服务发现、API版本控制、重试熔断降级、分布式链路追踪、集中式日志和告警
- 微服务适用的情况:工作负载隔离、差异化的拓展需求、模块间使用不同的运行时
早期模拟微服务拆分:
使用内部标志或部署开关来模拟未来的服务拆分
单代码库的实践:
这里提到了一点Go语言项目从单体应用过渡到微服务的一种组织方式:
- 项目的早期开发时,如果已经拆分了(微服务)模块(module),可以多个模块使用单一的工作空间
- 同时,Go 的模块化设计允许我们在开发时在 go.mod 通过 replace 指令把依赖替换为本地路径,例如:
replace github.com/my-org/common-lib => ../common-lib
即,把原本依赖的远程库 github.com/my-org/common-lib 替换为本地的 ../common-lib 文件夹。这样利用 replace 实现本地模块的替换,有利于前期软件项目的快速迭代和开发
- 后期扩展后,再将各模块独立出来放到不同的 Git 仓库,能够轻松地过渡成完整的分布式的架构
选择微服务需要关注的部分:
- 评估支撑你微服务架构的技术栈,投资于开发者体验工具:可能需要实现一些工具来实现生产和本地开发环境的配置。
- 专注于服务间通信的可靠通信协议:异步消息需要确保消息模式一致且标准化。REST需要关注 OpenAPI 文档。服务间通信客户端必须实现许多开箱即用的功能:指数退避的重试、超时。一个典型的基础 gRPC 客户端需要手动添加这些额外功能,以确保不会遭受瞬时错误。
- 确保单元测试、集成测试和端到端测试设置稳定,并且能够随着引入代码库中的服务级别分离数量而扩展。
- 尽量使微服务间共用的共享库(utils,可能是包含监控和通信的通用辅助工具)尽可能小,避免重大变更而导致整个依赖服务重建。
- 尽早注意服务日志观测:添加结构化 JSON 日志并创建各种关联 ID 以用于调试;或者早期能够利用一些基本的输出丰富日志信息的辅助工具。
线上故障应急处理要点
从先前某运营商的内网系统到现在社区化线上电商平台的开发,今后面临的是更高用户量和数据量的生产环境,故障时周末的随时响应可能也将成为常态。
- 故障止血优先:在故障发生时,首要任务是快速恢复服务,确保业务正常运行;立即追查责任归属在当前情况下时毫无意义的。
- 止血的最快手段:在服务侧发生的故障,通过识别系统产品的变动,能够快速定位问题并制定止血方案。
- 谨慎执行止血方案:即使止血方案明确,执行过程中仍需小心谨慎。设计新功能时需要考虑回滚无问题,故障止血时也应考虑灰度发布,逐步推向全网。
- 高效沟通:排查思路需要尽可能清晰地同步给相关研发;同时研发要敢于下判断,并说清楚自己的判断依据,才能达到集思广益