前言

CNCF 与 Cloud Native 这两个技术词汇最近频频走进了程序员的视野,一切和他能搭上边的软件意味着标准、开放、时尚,也更能俘获技术哥哥们的心;这篇文章不想去带大家重温这个词汇后面的软件体系,笔者觉得单凭用到了这些开源软件,不等于我们自己的软件就已经是 Cloud Native,在使用哑铃和成为肌肉男之间还隔着科学使用和自律锻炼两道工序;在此,笔者想根跟大家聊聊让我们的应用真正变得 Cloud Native 时的理论依据:微服务的十二要素。这篇文章也是先从作者自身项目的角度(一个基于 EDAS 的微服务架构),来阐述对这十二条要素的前两条 —— 仓库(Code Base)与依赖(Dependency)的理解

Code Base 的原文释义是:"一份基准代码,多份部署,基准代码和应用之间总是保持一一对应的关系;不同环境中的相同应用,应该来源于同一份代码"。我的理解有两个:

  1. 一个应用,产生自同一个仓库。
  2. 一个仓库,只产生一个应用。

为什么推演出这么两个结论呢?让我们先看一个实际的项目。

为什么是一个应用?

​ 给大家举一个一个仓库包含多个应用的反例,笔者自己的一个项目是一个的微服务的架构,和大部分的微服务架构一样,一开始是由一个单体的应用拆解而来,拆解之后,大致简化成四个服务:微服务网关(Gateway),两个后台服务(UserService, OrderService),后台管理控制台服务(Admin),简单的架构示意图如下:

​ 在拆分的过程一开始为了项目上线的减少风险,将拆分之后的应用都放在了一个 GIT 仓库中进行管理,同时也共用了同一个库。重构之后仓库的目录如下:

~/workspace/java/app/ $ tree -L 2
.
├── README.md
├── service-api # 通用的 API 接口定义
│   ├── userservice-api # 服务 UserService 的声明
│   ├── orderservice-api # 服务 OrderService 的声明
│   ├── rpc-api # 远程服务调用相关的接口声明
│   ├── common-api # UserService 与 OrderService 都依赖的声明
|   .....
├── service-impl # 对应 API 的相关具体业务实现
│   ├── userservice-impl
│   ├── orderservice-impl
│   ├── common-impl
|   .....
├── web-app # Web 应用工程
│   ├── admin
│   ├── userservice
│   ├── orderservice
│   ├── gateway

​ 一开始这些服务之间的发布和改动彼此都不受影响,这一过程持续了大约两个迭代,随着迭代的不断进行和新人的加入,后来我们线上发现一个很奇怪的现象,每次用户进入刷新订单的地址列表的时候,会伴随这一次用户 Token 的刷新而导致用户被踢出,线上的排查过程在 EDAS 的分布式链路跟踪系统 EagleEye 的帮助下,马上就定位到了出问题的代码:

// User Service 中
public class User {
public void refresh() {
// 刷新登录 token
}
} // Order Service 中
public class OrderUser extends User {
// 函数少了一个字母,导致 refresh 调用了父类的 refresh
public void refesh() {
// 刷新地址列表
}
}

​这个故障,我先邀请大家一起思考一下几个问题:

  1. 从编码角度,如何避免上述重写的方法因为名字误写造成故障?
  2. 从设计角度,OrderUser 和 User,是否是继承关系?
  3. 这个问题的根因是什么?

以上的几个问题中,第一个问题的答案,很多同学都知道,就是使用 Java 自带的 Annotation @Override,他会自动强制去检查所修饰的方法签名在父子类中是否一致。第二个问题,需要从领域边界来说,这是一个典型的边界划分的问题,即:订单中的用户,和会员登录中的用户,是不是相同的“用户”?会员中的用户,其实只需要关心用户名密码,其他都是这个用户的属性;而订单中的用户,最重要的肯定是联系方式,即一个联系方式,确定一个人。虽然他们都叫做用户,但是在彼此的上下文中,肯定是不一样的概念。所以这里的 OrderUser 和 User 是不能用继承关系的,因为他们就不是一个 "IS A" 的关系。
__仓库共享__,加上没有多加思考的模型,导致依赖混乱;如果两个 User  对象之间代码上能做到隔离,不是那么轻易的产生“关系”,这一切或许可以避免。

为什么是一个仓库?

严格意义上说,一个应用的所有代码都肯定来源于不同的仓库?我们所依赖的三方库如(fastjson, edas-sdk 等)肯定是来源于其他的仓库;这些类库是有确切的名称和版本号,且已经构建好的"制品",这里所说的一个仓库,是指源码级别的“在制品”。可能在很多的项目中不会存在这样的情况,以 GIT 为例,他一般发生在 submodule 为组织结构的工程中,场景一般是啥呢?在我们这个工程中确实是有一个这样的例子:

为了解掉第一个问题,我们决定拆仓库,仓库的粒度按照应用粒度分,同时把 common 相关的都拆到一个叫做 common 仓库中去;业务服务都好说,这里特殊处理的是 admin 应用,admin 是一个后台管理应用,变化频度特别大,需要依赖 UserService 和 OrderService 一大堆的接口。关于和其他仓库接口依赖的处理,这里除了常见的 Maven 依赖方式之外,还有另外一个解决方案就是 git submodule,关于两个方案的对比,我简单罗列在了下表之中:

  优点 缺点
Maven 依赖 可指定已固化的版本进行依赖 必须发布成二方包
Submodule 依赖 灵活、可直接共享代码库 变更不可控

我觉得如果这个项目组只有一两个人的时候,不会带来协作的问题;上面的方案随便哪一个都是不需要花太多时间做特殊讨论,挑自己最熟悉最拿手的方案肯定不会有错,所谓小团队靠技术吗,说的就是这么个道理;我们当时是一个小团队,同时团队中也有同学对 submodule 处理过类似的情况,所以方案的选择上就很自然了。

后来随着时间的推移,团队慢慢变大,就发现需要制定一些流程和和规范来约束一些行为,以此保障团队的协作关系的时候;这时候发现之前靠一己之力打拼下来的地盘在多人写作下变得脆弱不堪,尤其是另外一个 submodule 变成一个团队进行维护的时候,submodule 的版本管理几乎不可预期,而且他的接口变动和改动是完全不会理会被依赖方的感受的,因为他也不知道是否被依赖;久而久之,你就会明白什么叫做你的项目被__腐化__了。简单理解__腐化__这个词就是,你已经开始害怕你所做的一切改动,因为你不知道你的改动是否会引来额外的麻烦。从这个角度也可以去理解为什么一门语言设计出来为什么要有 __private__、__public__这些表示范围的修饰词。正因为有这些词的存在,才让你的业务代码的高内聚成为的有可能,小到设计一个方法一个类、再引申到一个接口一个服务、再到一个系统一个仓库,这个原则始终不变。

上述问题带来的解法很简单,就是变成显示依赖的关系,所谓显示依赖是指的两个依赖之间是确定的。什么是确定的?确定 == No Supprise !对,不管什么时候,线上还是线下,我依赖你测试环境的接口返回是一个整数,到了线上,返回的也必须是一个整数、不能变成浮点数。而让确定性变得可行的,不是君子协定;只能是一个版本依赖工具。比如说 Java 中的 Maven 正式的版本依赖。

结语

职责内聚、依赖确定,是我们的应用变得真正 Cloud Native 的前提。没有了这些基本的内功,懂的开源软件再多、对微服务栈再熟悉,也会有各种意想不到的事情出来,试想一下,如果应用的职责到处分散,那到时候扩容到底扩谁呢?如果依赖方变得及其不确定,谁又来为每次发版的不确定的成本买单?Be Cloud Native,请从应用代码托管的住所开始。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

微服务浪潮中,程序猿如何让自己 Be Cloud Native的更多相关文章

  1. 微服务架构中API网关的角色

    [上海尚学堂的话]:本文主要讲述了Mashape的首席技术执行官Palladino对API网关的详细介绍,以及API网关在微服务中所起的作用,同时介绍了Mashape的一款开源API网关Kong. A ...

  2. 微服务架构中APIGateway原理

    背景 我们知道在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,这些小系统通常以提供 Rest ...

  3. 还不知道如何实践微服务的Java程序员,这遍文章千万不要错过!

    作者:古霜卡比 前言 本文将介绍微服务架构和相关的组件,介绍他们是什么以及为什么要使用微服务架构和这些组件.本文侧重于简明地表达微服务架构的全局图景,因此不会涉及具体如何使用组件等细节. 要理解微服务 ...

  4. Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案

    Java生鲜电商平台-SpringCloud微服务架构中分布式事务解决方案 说明:Java生鲜电商平台中由于采用了微服务架构进行业务的处理,买家,卖家,配送,销售,供应商等进行服务化,但是不可避免存在 ...

  5. Java生鲜电商平台-SpringCloud微服务架构中核心要点和实现原理

    Java生鲜电商平台-SpringCloud微服务架构中核心要点和实现原理 说明:Java生鲜电商平台中,我们将进一步理解微服务架构的核心要点和实现原理,为读者的实践提供微服务的设计模式,以期让微服务 ...

  6. Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲

    Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲 Java生鲜电商平台:   微服务是当前非常流行的技术框架,通过服务的小型化.原子化以及分布式架构的弹性伸缩和高可用性, ...

  7. 在微服务架构中service mesh是什么?

    在微服务架构中service mesh是什么 什么是 service mesh ? 微服务架构将软件功能隔离为多个独立的服务,这些服务可独立部署,高度可维护和可测试,并围绕特定业务功能进行组织. 这些 ...

  8. 微服务架构中的Redis

    了解如何将Redis与Spring Cloud和Spring Data一起使用以提供配置服务器,消息代理和数据库. Redis可以广泛用于微服务架构中.它可能是少数流行的软件解决方案之一,你的应用程序 ...

  9. 微服务架构中的BFF到底是啥?

    在<技术中台与业务中台都是啥玩意>一文中留下一个问题:BFF是啥?为啥在API网关和业务中台之间加入了一层BFF?考虑到在实际工作中,我的大部分同事都问过这个问题,这里我也总结一下进行答复 ...

随机推荐

  1. 用python写一个非常简单的QQ轰炸机

    闲的没事,就想写一个QQ轰炸机,按照我最初的想法,这程序要根据我输入的QQ号进行轰炸,网上搜了一下,发现网上的案列略复杂,就想着自己写一个算了.. 思路:所谓轰炸机,就是给某个人发很多信息,一直刷屏, ...

  2. Class 和 普通构造函数区别

    1. Class 在语法上更加贴合面向对象的写法 2. Class在实现继承上更加易读.易理解 3. 更易于写java等后端语言 4.本质还是语法糖,使用prototype

  3. 解决audio控制播放音量

    在写手机端项目时,可能会遇到使用audio播放音乐,那么怎样控制audio默认播放的音量呢?下面时解决办法 volume 属于是控制audio 播放音乐的音量,其范围0-1,1表示音量最大 getVi ...

  4. mongodb 数据导出

    后台找我导数据 以此记录 在mongodb  bin目录下执行 ./mongoexport -d xxx(db name)-c xxx(Collection name)-u xxx(username) ...

  5. Android Studio 真机调试 连接手机

    前提:adb环境已经配置 手机端: 1.打开手机开发者权限,”设置“ 中找到 “版本号”,连续多次点击,会提示打开“开发者”.我的是 “设置” --> "关于手机" --&g ...

  6. 操作XML

    别人已经写过很好的XML辅助类,可以直接引用后使用: 我这里自己写一个xml的操作类,目前能实现的是对一个不含集合的类可以操作,含集合的类无法将集合里的数据读取出来, 首先定义一个XML特性,用于区分 ...

  7. Lesson 29 Taxi!

    Text Captain Ben Fawcett has bought an unusual taxi and has begun a new serivice. The 'taxi' is a sm ...

  8. [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型)

    [.net 面向对象程序设计深入](26)实战设计模式——策略模式 Strategy (行为型) 1,策略模式定义 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模 ...

  9. 依赖注入[7]: .NET Core DI框架[服务注册]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...

  10. HttpClient 调用WebAPI时,传参的三种方式

    public void Post() { //方法一,传json参数 var d = new { username = " ", password = " ", ...