1.2 探索起步

https://icyfenix.cn

更新日志

2021年11月12日

更新一篇务虚文章,ArchSummit2021主题演讲:《从软件的历史看架构的未来》

2021年9月8日

招聘&内推
对大型软件架构、云原生、实时大数据、企业级软件研发感兴趣的同学,欢迎投简历至:felix.zhouzhiming[at]huawei[dot]com。
团队气氛、水平都很好,对标范围华为18-22级,正编非OD。

2021年6月21日

更新了《凤凰架构:构建可靠的大型分布式系统》的纸质书的豆瓣评价,以及在京东、当当等网店的销售连接。
衷心感谢在本书撰写过程中,各位读者在本站的建议、意见、讨论、勘误指正与鼓励支持。

2021 年 6 月 17 日

发布一个开源项目:Fenix-CLI
Fenix-CLI是一个云原生运行环境命令行客户端,目标是替代Kubernetes的kubectl、Docker的docker cli 和Istio的istioctl 命令行工具,提供具备一致性的、便捷的交互式的操作环境。
https://github.com/fenixsoft/fenix-cli

2021 年 5 月 24 日

增加了纸质书的介绍页面

2021 年 4 月 9 日

为极客时间课程写的结语:程序员之路

2021 年 3 月 17 日

看到JetBrains Projector正式发布了,对于重度依赖轻薄本甚至是 Pad 做终端的开发人员来说,是 VSC Remote 外的另一个选择,正好在它的基础上做了个一条命令建立 OpenJDK 调试 IDE 的懒人包:
OpenJDK with CLion 懒人包
https://github.com/fenixsoft/openjdk-for-dummies

2021 年 2 月 24 日

修复了 Gitalk 无法登录留言的问题。
Gitalk 默认的 Github CORS 代理不再免费提供服务了,目前使用Heroku的免费 Dyno(一种 Serverless 实例)额度作为 CORS 代理,半个小时内没有访问会自动终止进程,冷启动需要一点时间,受此影响评论登录功能可能偶尔会有点慢。
实体书的编辑进度比想像中的慢,大概会在 4-5 月份才会出来。

2020 年 12 月 7 日

QCon2020 主题演讲:云原生时代,Java 的危与机

2020 年 11 月 18 日

文档在极客时间上的音频公开课:构建可靠的大型分布式系统。

2020 年 10 月 18 日

整部文档所有计划的内容均已完成,全文合计 399,677 字。
预计文档的音频版本预计会在 11 月份极客时间中,以免费公益课程的形式公开,感谢极客邦的编辑及配音主播。
预计文档的图书版本预计会在 2021 年 5 月左右,由机械工业出版社出版。

2020 年 10 月 16 日

完成了“透明通讯的涅槃”章节。
一年前,此文档刚开篇不久,我写下“原始分布式时代”和“远程服务调用”就设想过要以这一篇去结尾。

2020 年 10 月 10 日

完成了“资源与调度”章节

2020 年 10 月 7 日

完成了“容器存储与生态”章节

2020 年 10 月 4 日

完成了“Kubernetes 存储设计”章节

2020 年 9 月 28 日

完成了“容器网络与生态”章节
随着这篇文章的更新,整部文档超过了 30 万字,按计划应该在 35 万字以内结束。

2020 年 9 月 23 日

完成了“Linux 网络虚拟化”章节

2020 年 9 月 14 日

完成了“应用为中心的封装”章节

2020 年 9 月 10 日

完成了“以容器构建系统”章节

2020 年 9 月 8 日

完成了“虚拟化容器”与“容器的崛起”章节

2020 年 9 月 4 日

完成了“聚合度量”章节
目前到了 26 万字,整部文档所规划的框架里,只剩下介绍云原生的“不可变基础设施”这最后一大块了,希望今年内能全部写完。

2020 年 9 月 1 日

完成了“事件日志”章节
完成了“链路追踪”章节

2020 年 8 月 29 日

完成了“可观测性”章节

2020 年 8 月 27 日

写了几篇谈理论的务虚文章:

  • 向微服务迈进
  • 目的:微服务的驱动力
  • 前提:微服务需要的条件
  • 边界:微服务的粒度
  • 治理:理解系统复杂性

2020 年 8 月 14 日

重写了事务处理中的“本地事务”一节。

2020 年 8 月 11 日

重写了安全架构中的“认证”一节。

2020 年 8 月 7 日

提供了新的架构演示“AWS Lambda 为基础的无服务架构”。

2020 年 8 月 5 日

完成了“缓存”章节。

2020 年 7 月 23 日

完成了“服务安全”章节。

2020 年 7 月 20 日

更新了基于 Spring Cloud和基于 Istio的 Fenix’s Bookstore 的代码,提供了 RSA SHA256 的 JWT 令牌实现,以配合后面两节的主题。
完成了“零信任网络”章节。
这部文档的总字数在今天突破了 20 万字,留个纪念。

2020 年 7 月 13 日

完成“流量控制”章节。

2020 年 7 月 8 日

完成“服务容错”章节。

2020 年 7 月 2 日

完成“客户端负载均衡”章节。

2020 年 6 月 29 日

完成“网关路由”章节。

2020 年 6 月 18 日

重写了“远程服务调用”章节。
GraalVM增加了视频 PPT 讲解:GraalVM——云原生时代的 Java。
正在与某知识服务商合作,未来本文档的主要内容会提供成音频稿。并且仍然会以公开课的性质免费提供。
更新了服务架构演进史,大概增加了 40%的内容。

2020 年 6 月 13 日

提供了新的架构演示“基于 Istio 实现的后端工程”。

2020 年 5 月 25 日

提供了新的架构演示“基于 Kubernetes 实现的后端工程”。

2020 年 5 月 15 日

完成“服务发现”章节。

2020 年 5 月 9 日

完成“分布式共识算法”章节。

2020 年 5 月 5 日

创建更新日志页面。
在目录中增加根据 Git 提交时间生成的内容更新标识。

2020 年 5 月 2 日

完成“服务架构演进史”章节。
查了 Git 文档是在 2019 年 12 月 23 日创建的,今天在微博上开始小范围公开。

2019 年 12 月 23 日

此文档第一次 Git 提交:Initialize repository

https://icyfenix.cn


如何开始

《凤凰架构:构建可靠的大型分布式系统》这个开源文档项目的是笔者对自己在软件架构方面知识的总结,它是完全免费开放的,但免费的、开源的文档并不意味着你使用它时就没有成本,也不见得这个文档中所有的内容对每一个开发人员来说都是必要的。鲁迅说浪费别人的时间等于谋财害命,为了避免浪费阅读者的时间和精力,笔者除了自身力求在知识点准确性和叙述流畅性方面保证质量之外,同时也在本文中简要介绍每一章的主题和所面向的读者类型,本文档各章节之间并没有明显的前后依赖关系,阅读时有针对性的查阅是完全可行的,无需一篇不漏地顺序阅读,在此目录中列出了各文章的明细及字数,希望有助于你制定阅读计划。

引导篇 探索起步

字数: 20,605 字
这部分面向于准备对文档介绍的内容亲身实践的探索者。
这部分没有知识性的内容,是整部文档的引导,也可以视为这个文档附带示例工程的说明书,以及相应运行环境的部署手册。之所以将它安排在目录的第一位,是因为笔者相信如果你是一名驾驶初学者,最合理的学习路径应该是先把汽车发动,然后慢慢行驶起来,而不是马上从“引擎动力原理”、“变速箱构造”入手去设法深刻地了解一台汽车。相信计算机技术也是同理,先从运行程序,看看效果,搭建好开发、调试环境,对即将进行的工作有一个整体的认知开始是很有好处的。

工程提示

本文档所涉及到的工程均在 GitHub 上存有独立的项目,以方便构建、阅读、运行和 fork。这部分中的部分内容,是由这些工程的 README.md 文件人工同步而来,并没有通过持续集成工具自动处理,所以可能有偶尔更新不一致的情况,如可能,建议到这些项目的 GitHub 页面上查看最新情况,在右上角有相应的超链接。如这些工程对你有用,望请不吝给个 Star(当前 Star 数:4,598)。

这部分所提供的工程是作为后面所述知识的演示样例,由于数量确实不少,并无必要一次性地把上面所有的工程都运行起来。因为它们是采用不同的技术来解决同一个问题,所以每个工程执行后,最终看到的界面效果均是一样的,只是实现的架构不同。而通过不同的架构、技术去解决同一个问题,这也正是这批工程的最大价值所在。

如果你本身对某些架构风格已经熟练掌握,那笔者的建议是不妨选择一种你目前关注的架构风格去运行起来,然后与你熟悉的技术方案进行比对(引导篇 探索起步)、了解它解决的问题与背景(第一部分 演进中的架构)、思考这种架构涉及到哪些标准方案(第二部分 架构师的视角)、理清分布式系统中新的挑战与应对(第三部分 分布式的基石),以及如何将这些纯粹的技术问题隐藏起来,使其不会干扰业务代码(第四部分 不可变基础设施)。

第一部分 演进中的架构

字数: 20,313 字
这部分适合所有开发者,但尤其推荐刚刚从单体架构向微服务架构转型的开发者去阅读。
架构并不是“发明”出来的,是持续进化的结果。“服务架构演进史”这部分,笔者假借讨论历史之名,来梳理微服务发展里程中出现的大量名词、概念,借着微服务的演变过程,我们将从这些概念起源的最初,去分析它们是什么、它们取代了什么、以及它们为什么能够在斗争中取得成功,为什么变得不可或缺的支撑,又或者它们为什么会失败,在竞争中被淘汰,或逐渐湮灭于历史的烟尘当中。

第二部分 架构师的视角

字数: 123,489 字
这部分讨论与风格无关的架构知识,适合所有技术架构师、系统设计、开发人员。
“架构师”这个词的外延非常宽泛,不同语境中有不同所指,这部文档中的技术架构师特指的是企业架构中面向技术模型的系统设计者,这意味着讨论范围不会涉及到贴近于企业战略、业务流程的系统分析、信息战略设计等内容,而是聚焦于贴近一线研发人员的技术方案设计者。这部分将介绍作为一个架构师,你应该在做架构设计时思考哪些问题,有哪些主流的解决方案和行业标准做法,各种方案有什么优点、缺点,不同的解决方法会带来什么不同的影响,等等。以达到将“架构设计”这种听起来抽象的工作具体化、具象化的目的。

这部分介绍的内容与具体哪一种架构风格无关,作为后续实践的基础,讨论的是普适的架构技术与技巧,无论你是否关注微服务、云原生这些概念,无论你是从事架构设计还是从事编码开发,了解这里所列的基础知识,对每一个技术人员都是有价值的。

第三部分 分布式的基石

字数: 72,585 字
这部分面向于使用分布式架构的开发人员。
只要选择了分布式架构,无论是 SOA、微服务、服务网格或者其他架构风格,涉及与远程服务交互时,服务的注册发现、跟踪治理、负载均衡、故障隔离、认证授权、伸缩扩展、传输通讯、事务处理,等等,这一系列问题都是无可避免的。不同的架构风格,其区别是到底要在技术规范上提供统一的解决方案,还是由应用系统自行去解决,又或者在基础设施层面将一类问题隔离掉,这部分将会讨论这类问题的解决思路、方法和常见工具。

第四部分 不可变基础设施

字数: 89,115 字
这部分面向于基础设施运维人员、技术平台的开发者。
“不可变基础设施”这个概念由来已久。2012 年 Martin Fowler 设想的“凤凰服务器”与 2013 年 Chad Fowler 正式提出的“不可变基础设施”,都阐明了基础设施不变性所能带来的益处。在云原生基金会(Cloud Native Computing Foundation,CNCF)所定义的“云原生”概念中,“不可变基础设施”提升到了与微服务平级的重要程度,此时它的内涵已不再局限于方便运维、程序升级和部署的手段,而是升华为向应用代码隐藏分布式架构复杂度、让分布式架构得以成为一种可普遍推广的普适架构风格的必要前提。在云原生时代、后微服务时代中,软件与硬件之间的界线已经彻底模糊,无论是基础设施的运维人员,抑或技术平台的开发人员,都有必要深入理解基础设施不变性的目的、原理与实现途径。

第五部分 技术方法论

字数: 14,792 字
这部分面向于在企业中能对重要技术决策进行拍板的决策者。
这部文档的主体内容是务实的,多谈具体技术,少谈方向理论。只在这部分中会集中讨论几点与分布式、微服务、架构等相关的相对务虚的话题。

笔者认为对于一个技术人员,成长主要的驱动力是实践,在开发程序、解决问题中增长自身的知识,再将知识归纳、总结、升华成为理论的,所以笔者将这部分安排到了整部文档的末尾,也是希望大家能先去实践,再谈理论。同时,笔者也认为对于一名研究人员,或者企业中真正能决定技术方向的决策者,理论与实践都不可缺少,涉及决策的场景中,成体系的理论知识甚至比实践经验还要关键,因为执行力再强也必须用在正确的方向上才有价值。如果你对自己的规划是有朝一日要从一名技术人员发展成研究或者管理角色,补充这部分知识是必不可少的。

篇外 随笔文章

字数: 36,018 字
这部分无特定读者对象,内容是笔者日常文章的整理。
这部分是一些笔者所了解的开发、设计中心得感悟的集合,由于它们还不具备足够的系统性,没有安排入前面的知识框架之中。但有一些或精彩,或有价值,或实用的技巧,笔者不想错过,所以安排了这一章相对独立的内容。

另外,这部分内容类似于笔者的随笔博客,将不会出现在文档的音频版与传统纸质书版本中。

篇外 附录

字数: 12,035 字
这部分面向刚开始接触云原生环境的设计者、开发者。
这一章内容主要是云原生环境搭建和程序发布过程,原本它们并不属于笔者准备讨论的重点话题,至少没有到单独开一章的必要程度。但由于容器化的服务编排环境本身构建、管理和运维都有一定的复杂性,尤其是在国内特殊的网络环境下,无法直接访问到 Google 等国外的代码仓库,以至于不得不通过手工预载镜像或者代理的方式来完成环境搭建。为了避免刚刚接触这一领域的读者在入门第一步就受到不必要的心理打击,笔者专门设置了这个目录章节。这章与其他几章讨论设计思想、实现原理的风格差异很大,它是整部文档唯一的讨论具体如何操作的内容。

市面上介绍如何安装环境的书籍、资料已经不计其数,肯定有相当一部分读者这章的内容本身就是了解的,已掌握的读者建议无需仔细阅读,在有需要的时候,可当作工具查阅。

https://icyfenix.cn


技术演示工程

除文档部分外,笔者同时还建立了若干配套的代码工程,这是针对不同架构、技术方案(如单体架构、微服务、服务网格、无服务架构,等等)的演示程序。它们既是文档中所述知识的实践示例,亦可作为实际项目新创建时的可参考引用的基础代码。

说明

本小节内容是由这些工程的 README.md 文件同步而来,由于未经过持续集成工具自动处理,所以可能有偶尔更新不一致的情况,如可能,建议到这些项目的 GitHub 页面上查看最新情况。

文档工程

前端工程

后端工程

https://icyfenix.cn


前端工程

属性状态/信息
Releasev1.0
buildpassing
Doc LicenseCC 4.0
LicenseApache 2.0
AuthorIcyFenix

提示

如果你此时并不曾了解过什么是 “The Fenix Project”,建议先阅读这部分内容。

Fenix’s Bookstore 的主要目的是展示不同的后端技术架构,相对而言,前端并非其重点。不过,前端的页面是比起后端各种服务来要直观得多,能让使用者更容易理解我们将要做的是一件什么事情。假设你是一名驾驶初学者,合理的学习路径肯定应该是把汽车发动,然后慢慢行驶起来,而不是马上从”引擎动力原理”、“变速箱构造”入手去设法深刻地了解一台汽车。所以,先来运行程序,看看最终的效果是什么样子吧。

运行程序

以下几种途径,可以马上浏览最终的效果:

方式一:直接访问在线部署的网站 从互联网已部署(由 Travis-CI 提供支持)的网站(由 GitHub Pages 提供主机),直接在浏览器访问: http://bookstore.icyfenix.cn/

方式二:通过 Docker 容器方式运行

$ docker run -d -p 80:80 --name bookstore icyfenix/bookstore:frontend

然后在浏览器访问:http://localhost

方式三:通过 Git 上的源码,以开发模式运行

# 克隆获取源码 
$ git clone https://github.com/fenixsoft/fenix-bookstore-frontend.git 
 
# 进入工程根目录 
$ cd fenix-bookstore-frontend 
 
# 安装工程依赖 
$ npm install 
 
# 以开发模式运行,地址为localhost:8080 
$ npm run dev

然后在浏览器访问:http://localhost:8080

https://icyfenix.cn

也许你已注意到,以上这些运行方式,均没有涉及到任何的服务端、数据库的部署。现代软件工程里,基于 MVVM 的工程结构使得前、后端的开发可以完全分离,只要互相约定好服务的位置及模型即可。Fenix’s Bookstore 以开发模式运行时,会自动使用 Mock.js 拦截住所有的远程服务请求,并以事先准备好的数据来完成对这些请求的响应。

同时,你也应当注意到,以纯前端方式运行的时候,所有对数据的修改请求实际都是无效的。譬如用户注册,无论你输入何种用户名、密码,由于请求的响应是静态预置的,所以最终都会以同一个预设的用户登陆。也是因此,我并没有提供”默认用户”、“默认密码”一类的信息供用户使用,你可以随意输入即可登陆。

https://icyfenix.cn

不过,那些只维护在前端的状态依然是可以变动的,典型的如对购物车、收藏夹的增删改。让后端服务保持无状态,而把状态维持在前端中的设计,对服务的伸缩性和系统的鲁棒性都有着极大的益处,多数情况下都是值得倡导的良好设计。而其伴随而来的状态数据导致请求头变大、链路安全性等问题,都会在服务端部分专门讨论和解决。

构建产品

当你将程序用于正式部署时,一般不应部署开发阶段的程序,而是要进行产品化(Production)与精简化(Minification),你可以通过以下命令,由 node.js 驱动 webpack 来自动完成:

$ npm run build

或者使用 --report 参数,同时输出依赖分析报告:

$ npm run build --report

编译结果存放在 /dist 目录中,应将其拷贝至 Web 服务器的根目录使用。对于 Fenix’s Bookstore 的各个服务端而言,则通常是拷贝到网关工程中静态资源目录下。

与后端联调

同样出于前后端分离的目的,理论上后端通常只应当依据约定的服务协议(接口定义、访问传输方式、参数及模型结构、服务水平协议等)提供服务,并以此为依据进行不依赖前端的独立测试,最终集成时使用的是编译后的前端产品。

不过,在开发期就进行的前后端联合在现今许多企业之中仍是主流形式,由一个人”全栈式”地开发某个功能时更是如此,因此,当要在开发模式中进行联调时,需要修改项目根目录下的 main.js 文件,使其不导入 Mock.js,即如下代码所示的条件语句判断为假:

if (process.env.NODE_ENV === 'development') {
  require('./mock/mock.js')
}

编译前端代码

# 编译前端代码 
$ npm run build 
# 编译前端代码并生成报告 
$ npm run build --report 

关于 Mock.js 在生产环境的使用

也有其他一些相反的情况,需要在生产包中仍然继续使用 Mock.js 提供服务时(譬如 Docker 镜像 icyfenix/bookstore:frontend 就是如此),同样应修改该条件,使其结果为真,在开发模式依然导入了 Mock.js 即可。

工程结构

Fenix’s Bookstore 的工程结构完全符合 vue.js 工程的典型习惯,事实上它在建立时就是通过 vue-cli 初始化的。此工程的结构与其中各个目录的作用主要如下所示:

/** 
 * 默认在开发模式中启用mock.js代替服务端请求 
 * 如需要同时调试服务端,请修改此处判断条件 
 */
// eslint-disable-next-line no-constant-condition
if (process.env.MOCK) { 
  require('./api/mock')
}
+---build                           webpack编译配置,该目录的内容一般不做改动 
+---config                          webpack编译配置,用户需改动的内容提取至此 
+---dist                            编译输出结果存放的位置 
+---markdown                        与项目无关,用于支持markdown的资源(如图片) 
+---src 
|   +---api                         本地与远程的API接口 
|   |   +---local                   本地服务,如localStorage、加密等 
|   |   +---mock                    远程API接口的Mock 
|   |   |   \---json                Mock返回的数据 
|   |   \---remote                  远程服务 
|   +---assets                      资源文件,会被webpack哈希和压缩 
|   +---components                  vue.js的组件目录,按照使用页面的结构放置 
|   |   +---home 
|   |   |   +---cart 
|   |   |   +---detail 
|   |   |   \---main 
|   |   \---login 
|   +---pages                       vue.js的视图目录,存放页面级组件 
|   |   \---home 
|   +---plugins                     vue.js的插件,如全局异常处理器 
|   +---router                      vue-router路由配置 
|   \---store                       vuex状态配置 
|       \---modules                 vuex状态按名空间分隔存放 
\---static                          静态资源,编译时原样打包,不会做哈希和压缩 

组件

Fenix’s Bookstore 前端部分基于以下开源组件和免费资源构建:

  • Vue.js:渐进式 JavaScript 框架
  • Element:一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库
  • Axios:Promise based HTTP client for the browser and node.js
  • Mock.js:生成随机数据,拦截 Ajax 请求
  • DesignEvo:一款由 PearlMountain 有限公司设计研发的 logo 设计软件

协议

本作品代码部分采用 Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:

  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。

本作品文档部分采用 知识共享署名 4.0 国际许可协议 进行许可。 遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:

  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0 国际许可协议进行许可。

单体架构:Spring Boot

项目状态

Release v1.0 build passing coverage 90% License Apache 2.0 Doc License CC 4.0
Author IcyFenix

如果你此时并不曾了解过什么是“The Fenix Project”,建议先阅读这部分内容。

单体架构是 Fenix’s Bookstore 服务端的起始版本,它与此后基于微服务(Spring Cloud、Kubernetes)、服务网格(Istio)、无服务(Serverless)架构风格实现的其他版本,在业务功能上的表现是完全一致的。如果你不是针对性地带着解决某个具体问题、了解某项具体工具、技术的目的而来,而是有较充裕的时间,希望了解软件架构的全貌与发展的话,笔者推荐以此工程入手来探索现代软件架构,因为单体架构的结构是相对直观的易于理解的,对后面接触的其他架构风格也起良好的铺垫作用。

运行程序

以下几种途径,可以运行程序,浏览最终的效果:

通过 Docker 容器方式运行

$ docker run -d -p 8080:8080 --name bookstore icyfenix/bookstore:monolithic 

然后在浏览器访问:http://localhost:8080,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。 默认会使用 HSQLDB 的内存模式作为数据库,并在系统启动时自动初始化好了 Schema,完全开箱即用。但这同时也意味着当程序运行结束时,所有的数据都将不会被保留。 如果希望使用 HSQLDB 的文件模式,或者其他非嵌入式的独立的数据库支持的话,也是很简单的。以常用的 MySQL/MariaDB 为例,程序中也已内置了 MySQL 的表结构初始化脚本,你可以使用环境变量 PROFILES 来激活 Spring Boot 中针对 MySQL 所提供的配置,命令如下所示:

$ docker run -d -p 8080:8080 --name bookstore icyfenix/bookstore:monolithic -e PROFILES=mysql 

此时你需要通过 Docker link、Docker Compose 或者直接在主机的 Host 文件中提供一个名为 mysql_lan 的 DNS 映射,使程序能顺利链接到数据库,关于数据库的更多配置,可参考源码中的 application-mysql.yml

通过 Git 上的源码,以 Maven 运行

# 克隆获取源码 
$ git clone https://github.com/fenixsoft/monolithic_arch_springboot.git 
 
# 进入工程根目录 
$ cd monolithic_arch_springboot 
 
# 编译打包
# 采用Maven Wrapper,此方式只需要机器安装有JDK 8或以上版本即可,无需包括Maven在内的其他任何依赖
# 如在Windows下应使用mvnw.cmd package代替以下命令 
$ ./mvnw package 
 
# 运行程序,地址为localhost:8080 
$ java -jar target/bookstore-1.0.0-Monolithic-SNAPSHOT.jar 

然后在浏览器访问:http://localhost:8080,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。

通过 Git 上的源码,在 IDE 环境中运行

以 IntelliJ IDEA 为例,Git 克隆本项目后,在 File Open 菜单选择本项目所在的目录,或者 pom.xml 文件,以 Maven 方式导入工程。

IDEA 将自动识别出这是一个 SpringBoot 工程,并定位启动入口为 BookstoreApplication,待 IDEA 内置的 Maven 自动下载完所有的依赖包后,运行该类即可启动。 如你使用其他的 IDE,没有对 SpringBoot 的直接支持,亦可自行定位到 BookstoreApplication,这是一个带有 main() 方法的 Java 类,运行即可。 可通过 IDEA 的 Maven 面板中 Lifecycle 里面的 package 来对项目进行打包、发布。

在 IDE 环境中修改配置(如数据库等)会更加简单,具体可以参考工程中 application.ymlapplication-mysql.yml 中的内容。

技术组件

Fenix’s Bookstore 单体架构后端尽可能采用标准的技术组件进行构建,不依赖于具体的实现,包括:

  • JSR 370:Java API for RESTful Web Services 2.1 (JAX-RS 2.1) RESTFul 服务方面,采用的实现为 Jersey 2,亦可替换为 Apache CXF、RESTeasy、WebSphere、WebLogic 等。
  • JSR 330:Dependency Injection for Java 1.0 依赖注入方面,采用的的实现为 SpringBoot 2 中内置的 Spring Framework 5。虽然在多数场合中尽可能地使用了 JSR 330 的标准注解,但仍有少量地方由于 Spring 在对 @Named@Inject 等注解的支持表现上与本身提供的注解差异,使用了 Spring 的私有注解。如替换成其他的 CDI 实现,如 HK2,需要较大的改动。
  • JSR 338:Java Persistence 2.2 持久化方面,采用的实现为 Spring Data JPA。可替换为 Batoo JPA、EclipseLink、OpenJPA 等实现,只需将使用 CrudRepository 所省略的代码手动补全回来即可,无需其他改动。
  • JSR 380:Bean Validation 2.0 数据验证方面,采用的实现为 Hibernate Validator 6,可替换为 Apache BVal 等其他验证框架。
  • JSR 315:Java Servlet 3.0 Web 访问方面,采用的实现为 SpringBoot 2 中默认的 Tomcat 9 Embed,可替换为 Jetty、Undertow 等其他 Web 服务器。

有以下组件仍然依赖了非标准化的技术实现,包括:

  • JSR 375:Java EE Security API specification 1.0 认证/授权方面,在 2017 年才发布的 JSR 375 中仍然没有直接包含 OAuth2 和 JWT 的直接支持,因后续实现微服务架构时对比的需要,单体架构中选择了 Spring Security 5 作为认证服务,Spring Security OAuth 2.3 作为授权服务,Spring Security JWT 作为 JWT 令牌支持,并未采用标准的 JSR 375 实现,如 Soteria。
  • JSR 353/367:Java API for JSON Processing/Binding 在 JSON 序列化/反序列化方面,由于 Spring Security OAuth 的限制(使用 JSON-B 作为反序列化器时的结果与 Jackson 等有差异),采用了 Spring Security OAuth 默认的 Jackson,并未采用标准的 JSR 353/367 实现,如 Apache Johnzon、Eclipse Yasson 等。

工程结构

Fenix’s Bookstore 单体架构后端参考(并未完全遵循)了 DDD 的分层模式和设计原则,整体分为以下四层:

  1. Resource:对应 DDD 中的 User Interface 层,负责向用户显示信息或者解释用户发出的命令。请注意,这里指的“用户”不一定是使用用户界面的人,可以是位于另一个进程或计算机的服务。由于本工程采用了 MVVM 前后端分离模式,这里所指的用户实际上是前端的服务消费者,所以这里以 RESTful 中的核心概念“资源”(Resource)来命名。
  2. Application:对应 DDD 中的 Application 层,负责定义软件本身对外暴露的能力,即软件本身可以完成哪些任务,并负责对内协调领域对象来解决问题。根据 DDD 的原则,应用层要尽量简单,不包含任何业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使它们互相协作,这一点在代码上表现为 Application 层中一般不会存在任何的条件判断语句。在许多项目中,Application 层都会被选为包裹事务(代码进入此层事务开始,退出此层事务提交或者回滚)的载体。
  3. Domain:对应 DDD 中的 Domain 层,负责实现业务逻辑,即表达业务概念,处理业务状态信息以及业务规则这些行为,此层是整个项目的重点。
  4. Infrastructure:对应 DDD 中的 Infrastructure 层,向其他层提供通用的技术能力,譬如持久化能力、远程服务通讯、工具集,等等。

协议

本文档代码部分采用 Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:

  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。

本作品文档部分采用 知识共享署名 4.0 国际许可协议 进行许可。 遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:

  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0 国际许可协议进行许可。

微服务:Spring Cloud

项目状态

Release v1.0 build passing coverage 81% License Apache 2.0 Doc License CC 4.0
Author IcyFenix

如果你此时并不曾了解过什么是“The Fenix Project”,建议先阅读这部分内容。

直至现在,由不同编程语言、不同技术框架所开发的微服务系统中,基于 Spring Cloud 的解决方案仍然是最为主流的选择。这个结果既是 Java 在服务端应用所积累的深厚根基的体现,也是 Spring 在 Java 生态系统中统治地位的体现。从 Spring Boot 到 Spring Cloud 的过渡,令现存数量极为庞大的、基于 Spring 和 Spring Boot 的单体系统得以平滑地迁移到微服务架构中,令这些系统的大部分代码都能够无需修改,或少量修改即可保留重用。微服务时代的早期,Spring Cloud 就集成了 Netflix OSS (以及 Spring Cloud Netflix 进入维护期后对应的替代组件)成体系的微服务套件,基本上也能算“半透明地”满足了在微服务环境中必然会面临的服务发现、远程调用、负载均衡、集中配置等非功能性的需求。

笔者个人是一直不太倾向于 Spring Cloud Netflix 这种以应用代码去解决基础设施功能问题的“解题思路”,以自顶向下的视角来看,这既是虚拟化的微服务基础设施完全成熟之前必然会出现的应用形态,也是微服务进化过程中必然会被替代的过渡形态。不过,笔者的看法如何无关重要,基于 Spring Cloud Netflix 的微服务在当前是主流,直至未来不算短的一段时间内仍会是主流,而且以应用的视角来看,能自底向上观察基础设施在微服务中面临的需求和挑战,能用我们最熟悉的 Java 代码来解释分析问题,也有利于对微服务的整体思想的深入理解,所以将它作为我们了解的第一种微服务架构的实现是十分适合的。

需求场景

小书店 Fenix’s Bookstore 生意日益兴隆,客人、货物、营收都在持续增长,业务越发复杂,对信息系统并发与可用方面的要求也越来越高。由于业务属性和质量属性要求的提升,信息系统需要更多的硬件资源去支撑,这是合情合理的,但是,如果我们把需求场景列的更具体些,便会发现“合理”下面的许多无可奈何之处:

  • 譬如,制约软件质量与业务能力提升的最大因素是人而非硬件。多数企业即使有钱也很难招到大量的靠谱的开发者。此时,无论是引入外包团队,抑或是让少量技术专家带着大量普通水平的开发者去共同完成一个大型系统就成为了必然的选择。在单体架构下,没有什么有效阻断错误传播的手段,系统中“整体”与“部分”的关系没有物理的划分,系统质量只能靠研发与项目管理措施来尽可能地保障,少量的技术专家很难阻止大量螺丝钉式的程序员或者不熟悉原有技术架构的外包人员在某个不起眼的地方犯错并产生全局性的影响,并不容易做出整体可靠的大型系统。
  • 譬如,技术异构的需求从可选渐渐成为必须。Fenix’s Bookstore 的单体版本是以目前应用范围最广的 Java 编程语言来开发,但依然可能遇到很多想做 Java 却不擅长的事情。譬如想去做人工智能,进行深度学习训练,发现大量的库和开源代码都离不开 Python;想要引入分布式协调工具时,发现近几年 ZooKeeper 已经有被后起之秀 Golang 的 Etcd 蚕食替代的趋势;想要做集中式缓存,发现无可争议的首选是 ANSI C 编写的 Redis,等等。很多时候为异构能力进行的分布式部署,并不是你想或者不想的问题,而是没有选择、无可避免的。
  • 譬如,……

微服务的需求场景还可以列举出很多,这里就不多列举了,总之,系统发展到一定程度,我们总能找到充分的理由去拆分与重构它。在笔者设定的演示案例中,准备把单体的 Fenix’s Bookstore拆分成为“用户”、“商品”、“交易”三个能够独立运行的子系统,它们将在一系列非功能性技术模块(认证、授权等)和基础设施(配置中心、服务发现等)的支撑下互相协作,以统一的 API 网关对外提供与原来单体系统功能一致的服务,应用视图如下图所示:

运行程序

以下几种途径,可以运行程序,浏览最终的效果:

通过 Docker 容器方式运行

微服务涉及到多个容器的协作,通过 link 单独运行容器已经被 Docker 官方声明为不提倡的方式,所以在工程中提供了专门的配置,以便使用 docker-compose 来运行:

# 下载docker-compose配置文件 
$ curl -O https://raw.githubusercontent.com/fenixsoft/microservice_arch_springcloud/master/docker-compose.yml 
 
# 启动服务 
$ docker-compose up 

然后在浏览器访问:http://localhost:8080,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。

通过 Git 上的源码,以 Maven 编译、运行

由于笔者已经在配置文件中设置好了各个微服务的默认的地址和端口号,以便于本地调试。如果要在同一台机运行这些服务,并且每个微服务都只启动一个实例的话,那不加任何配置、参数即可正常以 Maven 编译、以 Jar 包形式运行。由于各个微服务需要从配置中心里获取具体的参数信息,因此唯一的要求只是“配置中心”的微服务必须作为第一个启动的服务进程,其他就没有别的前置要求了。具体的操作过程如下所示:

# 克隆获取源码 
$ git clone https://github.com/fenixsoft/microservice_arch_springcloud.git 
 
# 进入工程根目录 
$ cd microservice_arch_springcloud 
 
# 编译打包
# 采用Maven Wrapper,此方式只需要机器安装有JDK 8或以上版本即可,无需包括Maven在内的其他任何依赖
# 克隆后你可能需要使用chmod给mvnw赋予执行权限,如在Windows下应使用mvnw.cmd package代替以下命令 
$ ./mvnw package 
 
# 工程将编译出七个SpringBoot Jar
# 启动服务需要运行以下七个微服务组件
# 配置中心微服务:localhost:8888 
$ java -jar ./bookstore-microservices-platform-configuration/target/bookstore-microservice-platform-configuration-1.0.0-SNAPSHOT.jar 
# 服务发现微服务:localhost:8761 
$ java -jar ./bookstore-microservices-platform-registry/target/bookstore-microservices-platform-registry-1.0.0-SNAPSHOT.jar 
# 服务网关微服务:localhost:8080 
$ java -jar ./bookstore-microservices-platform-gateway/target/bookstore-microservices-platform-gateway-1.0.0-SNAPSHOT.jar 
# 安全认证微服务:localhost:8301 
$ java -jar ./bookstore-microservices-domain-security/target/bookstore-microservices-domain-security-1.0.0-SNAPSHOT.jar 
# 用户信息微服务:localhost:8401 
$ java -jar ./bookstore-microservices-domain-account/target/bookstore-microservices-domain-account-1.0.0-SNAPSHOT.jar 
# 商品仓库微服务:localhost:8501 
$ java -jar ./bookstore-microservices-domain-warehouse/target/bookstore-microservices-domain-warehouse-1.0.0-SNAPSHOT.jar 
# 商品交易微服务:localhost:8601 
$ java -jar ./bookstore-microservices-domain-payment/target/bookstore-microservices-domain-payment-1.0.0-SNAPSHOT.jar 

由于在命令行启动多个服务、通过容器实现各服务隔离、扩展等都较繁琐,笔者提供了一个 docker-compose.dev.yml 文件,便于开发期调试使用:

以上两种本地运行的方式可任选其一,服务全部启动后,在浏览器访问:http://localhost:8080,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试

通过 Git 上的源码,在 IDE 环境中运行

以 IntelliJ IDEA 为例,Git 克隆本项目后,在 File Open 菜单选择本项目所在的目录,或者 pom.xml 文件,以 Maven 方式导入工程。

待 Maven 自动安装依赖后,即可在 IDE 或者 Maven 面板中编译全部子模块的程序。

本工程下面八个模块,其中除 bookstore-microservices-library-infrastructure 外,其余均是 SpringBoot 工程,将这七个工程的 Application 类加入到 IDEA 的 Run Dashboard 面板中。

在 Run Dashboard 中先启动“bookstore-microservices-platform-configuration”微服务,然后可一次性启动其余六个子模块的微服务。

配置与横向扩展

工程中预留了一些的环境变量,便于配置和扩展,譬如,想要在非容器的单机环境中模拟热点模块的服务扩容,就需要调整每个服务的端口号。预留的这类环境变量包括:

使用Maven编译出JAR包后,可使用以下命令直接在本地构建镜像运行

$ docker-compose -f docker-compose.dev.yml up

修改配置中心的主机和端口,默认为localhost:8888

CONFIG_HOST sh sh https://icyfenix.cn 微服务:Spring Cloud 技术组件 Fenix’s Bookstore 采用基于 Spring Cloud 微服务架构,微服务部分主要采用了 Netflix OSS 组件进行支持,它们包括: 配置中心:默认采用Spring Cloud Config ,亦可使用Spring Cloud Consul 、Sprin g Cloud Alibaba Nacos 代替。 服务发现:默认采用Netflix Eureka ,亦可使用Spring Cloud Consul 、Spring Clou d ZooKeeper 、Etcd 等代替。 服务网关:默认采用Netflix Zuul ,亦可使用Spring Cloud Gateway 代替。 服务治理:默认采用Netflix Hystrix ,亦可使用Sentinel 、Resilience4j 代替。 CONFIG_PORT

修改服务发现的主机和端口,默认为localhost:8761

REGISTRY_HOST REGISTRY_PORT

修改认证中心的主机和端口,默认为localhost:8301

AUTH_HOST AUTH_PORT

修改当前微服务的端口号

譬如,你打算在一台机器上扩容四个支付微服务以应对促销活动的流量高峰

可将它们的端口设置为8601(默认)、8602、8603、8604等

真实环境中,它们可能是在不同的物理机、容器环境下,这时扩容可无需调整端口

PORT

SpringBoot所采用Profile配置文件,默认为default

譬如,服务默认使用HSQLDB的内存模式作为数据库,如需调整为MySQL,可将此环境变量

调整为mysql

因为笔者默认预置了名为applicaiton-mysql.yml的配置,以及HSQLDB和MySQL的数据库

脚本

如果你需要支持其他数据库、修改程序中其他的配置信息,可以在代码中自行加入另外的

初始化脚本 PROFILES

Java虚拟机运行参数,默认为空

JAVA_OPTS https://icyfenix.cn 微服务:Spring Cloud 进程内负载均衡:默认采用Netfilix Ribbon ,亦可使用Spring Cloud Loadbalancer 代替。 声明式 HTTP 客户端:默认采用Spring Cloud OpenFeign 。声明式的 HTTP 客户端其 实没有找替代品的必要性,如果需要,可考虑Retrofit ,或者使用 RestTemplete 乃至 于更底层的OkHTTP 、HTTPClient 以命令式编程来访问,多写一些代码而已了。 尽管 Netflix 套件的使用人数很多,但考虑到 Spring Cloud Netflix 已进入维护模式,笔者 均列出了上述组件的代替品。这些组件几乎都是声明式的,这确保了它们的替代成本相当 低廉,只需要更换注解,修改配置,无需改动代码。你在阅读源码时也会发现,三个“platf orm”开头的服务,基本上没有任何实际代码的存在。 其他与微服务无关的技术组件(REST 服务、安全、数据访问,等等),笔者已在Fenix’s B ookstore 单体架构中介绍过,在此不再重复。 协议 本作品代码部分采用Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对 代码进行修改,再发布,可以将代码用作商业用途。但要求你: 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。 本作品文档部分采用知识共享署名 4.0 国际许可协议 进行许可。 遵循许可的前提下, 你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演 绎、修改、转换或以本作品为基础进行二次创作。但要求你: 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使 用,请联系作者。 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署 名 4.0 国际许可协议进行许可。 https://icyfenix.cn 微服务:Kubernetes 微服务:Kubernetes Release Release v1.0 v1.0 build build passing passing License License Apache 2.0 Apache 2.0 Doc License Doc License CC 4.0 CC 4.0 Author Author IcyFenix IcyFenix 如果你此时并不曾了解过什么是“The Fenix Project”,建议先阅读这部分内容。 2017 年,笔者曾在文章中描述其为“后微服务时代”的开端,这年是容器生态发展历史中具 有里程碑意义的一年。在这一年,长期作为 Docker 竞争对手的RKT 容器 一派的领导者 C oreOS 宣布放弃自己的容器管理系统 Fleet,未来将会把所有容器管理的功能移至 Kuberne tes 之上去实现。在这一年,容器管理领域的独角兽 Rancher Labs 宣布放弃其内置了数年 的容器管理系统 Cattle,提出了“All-in-Kubernetes”战略,从 2.0 版本开始把 1.x 版本能够 支持多种容器管理工具的 Rancher,“反向升级”为只支持 Kubernetes 一种容器管理系统。 在这一年,Kubernetes 的主要竞争者 Apache Mesos 在 9 月正式宣布了“Kubernetes on M esos ”集成计划,由竞争关系转为对 Kubernetes 提供支持,使其能够与 Mesos 的其他一 级框架(如 HDFS 、Spark 和 Chronos ,等等)进行集群资源动态共享、分配与隔 离。在这一年,Kubernetes 的最大竞争者 Docker Swarm 的母公司 Docker,终于在 10 月 被迫宣布 Docker 要同时支持 Swarm 与 Kubernetes 两套容器管理系统,事实上承认了 Ku bernetes 的统治地位。这场已经持续了三、四年时间,以 Docker Swarm、Apache Mesos 与 Kubernetes 为主要竞争者的“容器战争”终于有了明确的结果,Kubernetes 登基加冕是容 器发展中一个时代的终章,也将是软件架构发展下一个纪元的开端。 需求场景 https://icyfenix.cn 微服务:Kubernetes 当引入了基于 Spring Cloud 的微服务架构后,小书店 Fenix’s Bookstore 初步解决了扩容 缩容、独立部署、运维和管理等问题,满足了产品经理不断提出的日益复杂的业务需求。 可是,对于团队的开发人员、设计人员、架构人员来说,并没有感觉到工作变得轻松,微 服务中的各种新技术名词,如配置中心、服务发现、网关、熔断、负载均衡等等,就够一 名新手学习好长一段时间;从产品角度来看,各种 Spring Cloud 的技术套件,如 Config、 Eureka、Zuul、Hystrix、Ribbon、Feign 等,也占据了产品的大部分编译后的代码容量。 之所以微服务架构里,我们选择在应用层面而不是基础设施层面去解决这些分布式问题, 完全是因为由硬件构成的基础设施,跟不上由软件构成的应用服务的灵活性的无奈之举。 当 Kubernetes 统一了容器编排管理系统之后,这些纯技术性的底层问题,便开始有了被广 泛认可和采纳的基础设施层面的解决方案。为此,Fenix’s Bookstore 也迎来了它在“后微服 务时代”中的下一次架构演进,这次升级的目标主要有如下两点: 目标一:尽可能缩减非业务功能代码的比例。 在 Fenix’s Bookstore 中,用户服务(Account)、商品服务(Warehouse)、交易服务 (Payment)三个工程是真正承载业务逻辑的,认证授权服务(Security)可以认为是同 时涉及到了技术与业务,而配置中心(Configuration)、网关(Gateway)和服务注册 中心(Registry)则是纯技术性。我们希望尽量消除这些纯技术的工程,以及那些依附 在其他业务工程上的纯技术组件。 目标二:尽可能在不影响原有的代码的前提下完成迁移。 得益于 Spring Framework 4 中的 Conditional Bean 等声明式特性的出现,对于近年来 新发布的 Java 技术组件,声明式编程 (Declarative Programming)已经逐步取代命 令式编程 (Imperative Programming)成为主流的选择。在声明式编程的支持下,我 们可以从目的而不是过程的角度去描述编码意图,使得代码几乎不会与具体技术实现产 生耦合,若要更换一种技术实现,只需要调整配置中的声明便可做到。 从升级结果来看,如果仅以 Java 代码的角度来衡量,本工程与此前基于 Spring Cloud 的 实现没有丝毫差异,两者的每一行 Java 代码都是一模一样的;真正的区别在 Kubernetes 的实现版本中直接删除了配置中心、服务注册中心的工程,在其他工程的 pom.xml 中也删 除了如 Eureka、Ribbon、Config 等组件的依赖。取而代之的是新增了若干以 YAML 配置文 件为载体的Skaffold 和 Kubernetes 的资源描述,这些资源描述文件,将会动态构建出 D NS 服务器、服务负载均衡器等一系列虚拟化的基础设施,去代替原有的应用层面的技术组 件。升级改造之后的应用架构如下图所示: https://icyfenix.cn 微服务:Kubernetes 运行程序 在已经部署 Kubernetes 集群的前提下,通过以下几种途径,可以运行程序,浏览最终的 效果: 直接在 Kubernetes 集群环境上运行: 工程在编译时已通过 Kustomize 产生出集成式的资源描述文件,可通过该文件直接在 K ubernetes 集群中运行程序:

资源描述文件

$ kubectl apply -f https://raw.githubusercontent.com/fenixsoft/microservice_arch_kubernetes/ma ster/bookstore.yml sh https://icyfenix.cn 微服务:Kubernetes 命令执行过程一共需要下载几百 MB 的镜像,尤其是 Docker 中没有各层基础镜像缓存 时,请根据自己的网速保持一定的耐心。未来 GraalVM 对 Spring Cloud 的支持更成熟 一些后,可以考虑采用 GraalVM 来改善这一点。当所有的 Pod 都处于正常工作状态 后,在浏览器访问:http://localhost:30080 ,系统预置了一个用户(user:icyfeni x,pw:123456 ),也可以注册新用户来测试。 通过 Skaffold 在命令行或 IDE 中以调试方式运行: 一般开发基于 Kubernetes 的微服务应用,是在本地针对单个服务编码、调试完成后, 通过 CI/CD 流水线部署到 Kubernetes 中进行集成的。如果只是针对集成测试,这并没 有什么问题,但同样的做法应用在开发阶段就相当不便了,我们不希望每做一处修改都 要经过一次 CI/CD 流程,这将非常耗时且难以调试。 Skaffold 是 Google 在 2018 年开源的一款加速应用在本地或远程 Kubernetes 集群中构 建、推送、部署和调试的自动化命令行工具,对于 Java 应用来说,它可以帮助我们做到 监视代码变动,自动打包出镜像,将镜像打上动态标签并更新部署到 Kubernetes 集 群,为 Java 程序注入开放 JDWP 调试的参数,并根据 Kubernetes 的服务端口自动在本 地生成端口转发。以上都是根据skaffold.yml 中的配置来进行的,开发时 skaffold 通 过dev 指令来执行这些配置,具体的操作过程如下所示: 服务全部启动后,在浏览器访问:http://localhost:30080 ,系统预置了一个用户(us er:icyfenix,pw:123456 ),也可以注册新用户来测试 由于面向的是开发环境,基于效率原因,笔者并没有像传统 CI 工程那样直接使用 Mave n 的 Docker 镜像来打包 Java 源码,这决定了构建 Dockerfile 时,要监视的变动目标将 是 Jar 文件而不是 Java 源码,Skaffold 的执行是由 Jar 包的编译结果来驱动的,只在进

克隆获取源码

$ git clone https://github.com/fenixsoft/microservice_arch_kubernetes.git && cd microservice_arch_kubernetes

编译打包

$ ./mvnw package

启动Skaffold

此时将会自动打包Docker镜像,并部署到Kubernetes中

$ skaffold dev sh https://icyfenix.cn 微服务:Kubernetes 行 Maven 编译、输出了新的 Jar 包后才会更新镜像。这样做一方面是考虑到在 Maven 镜像中打包不便于利用本地的仓库缓存,尤其在国内网络中,速度实在难以忍受;另外 一方面,是笔者其实并不希望每保存一次源码时,都自动构建和更新一次镜像,毕竟比 起传统的 HotSwap 或者 Spring Devtool Reload 来说,更新镜像重启 Pod 是一个更加重 负载的操作。未来 CNCF 的Buildpack 成熟之后,应该可以绕过笨重的 Dockerfile,对 打包和容器热更新做更加精细化的控制。 另外,对于有 IDE 调试需求的同学,推荐采用Google Cloud Code (Cloud Code 同时 提供了 VS Code 和 IntelliJ Idea 的插件)来配合 Skaffold 使用,毕竟是同一个公司出品 的产品,搭配起来能获得几乎与本地开发单体应用一致的编码和调试体验。 技术组件 Fenix’s Bookstore 采用基于 Kubernetes 的微服务架构,并采用 Spring Cloud Kubernetes 做了适配,其中主要的技术组件包括: 环境感知:Spring Cloud Kubernetes 本身引入了 Fabric8 的Kubernetes Client 作为容 器环境感知,不过引用的版本相当陈旧,如 Spring Cloud Kubernetes 1.1.2 中采用的是 Fabric8 Kubernetes Client 4.4.1,Fabric8 提供的兼容性列表中该版本只支持到 Kubernet es 1.14,实测在 1.16 上也能用,但是在 1.18 上无法识别到最新的 Api-Server,因此 Ma ven 引入依赖时需要手工处理,排除旧版本,引入新版本(本工程采用的是 4.10.1)。 配置中心:采用 Kubernetes 的 ConfigMap 来管理,通过Spring Cloud Kubernetes Co nfig 自动将 ConfigMap 的内容注入到 Spring 配置文件中,并实现动态更新。 服务发现:采用 Kubernetes 的 Service 来管理,通过Spring Cloud Kubernetes Discov ery 自动将 HTTP 访问中的服务转换为FQDN 。 负载均衡:采用 Kubernetes Service 本身的负载均衡能力实现(就是 DNS 负载均 衡),可以不再需要 Ribbon 这样的客户端负载均衡了。Spring Cloud Kubernetes 从 1. 1.2 开始也已经移除了对 Ribbon 的适配支持,也(暂时)没有对其代替品 Spring Cloud LoadBalancer 提供适配。 服务网关:网关部分仍然保留了 Zuul,未采用 Ingress 代替。这里有两点考虑,一是 In gress Controller 不算是 Kubernetes 的自带组件,它可以有不同的选择(KONG、Ngin x、Haproxy,等等),同时也需要独立安装,作为演示工程,出于环境复杂度最小化考 虑未使用 Ingress;二是 Fenix’s Bookstore 的前端工程是存放在网关中的,移除了 Zuul https://icyfenix.cn 微服务:Kubernetes 之后也仍然要维持一个前端工程的存在,不能进一步缩减工程数量,也就削弱了移除 Zu ul 的动力。 服务熔断:仍然采用 Hystrix,Kubernetes 本身无法做到精细化的服务治理,包括熔 断、流控、监视,等等,我们将在基于 Istio 的服务网格架构中解决这个问题。 认证授权:仍然采用 Spring Security OAuth 2,Kubernetes 的 RBAC 授权可以解决服务 层面的访问控制问题,但 Security 是跨越了业务和技术的边界的,认证授权模块本身仍 承担着对前端用户的认证、授权职责,这部分是与业务相关的。 协议 本文档代码部分采用Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对 代码进行修改,再发布,可以将代码用作商业用途。但要求你: 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。 本作品文档部分采用知识共享署名 4.0 国际许可协议 进行许可。 遵循许可的前提下, 你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演 绎、修改、转换或以本作品为基础进行二次创作。但要求你: 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使 用,请联系作者。 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署 名 4.0 国际许可协议进行许可。 https://icyfenix.cn 服务网格:Istio 服务网格:Istio Release Release v1.0 v1.0 build build passing passing License License Apache 2.0 Apache 2.0 Doc License Doc License CC 4.0 CC 4.0 Author Author IcyFenix IcyFenix 如果你此时并不曾了解过什么是“The Fenix Project”,建议先阅读这部分内容。 当软件架构演进至基于 Kubernetes 实现的微服务时,已经能够相当充分地享受到虚拟化 技术发展的红利,如应用能够灵活地扩容缩容、不再畏惧单个服务的崩溃消亡、立足应用 系统更高层来管理和编排各服务之间的版本、交互。可是,单纯的 Kubernetes 仍然不能解 决我们面临的所有分布式技术问题,在此前对基于 Kubernetes 的架构中“技术组件”的介绍 里,笔者已经说明光靠着 Kubernetes 本身的虚拟化基础设施,难以做到精细化的服务治 理,譬如熔断、流控、观测,等等;而即使是那些它可以提供支持的分布式能力,譬如通 过 DNS 与服务来实现的服务发现与负载均衡,也只能说是初步解决了的分布式中如何调用 服务的问题而已,只靠 DNS 难以满足根据不同的配置规则、协议层次、均衡算法等去调节 负载均衡的执行过程这类高级的配置得益于 Kubernetes 的强力支持,小书店 Fenix’s Bookstore 已经能够依赖虚拟化基础设施进行扩容缩容,将用户请求分散到数量动态变化的 Pod 中处理,可以应对相当规模的用户量了。不过,随着 Kubernetes 集群中的 Pod 数量规模越来越庞大,到一定程度之后,运维的同学无奈地表示已经不可能够依靠人力来跟进微服务中出现的各种问题了:一个请求在哪个服务上调用失败啦?是 A 有调用 B 吗?还是 C 调用 D 时出错了?为什么这个请求、页面忽然卡住了?怎么调度到这个 Node 上的服务比其他 Node 慢那么多?这个 Pod 有 Bug,消耗了大量的 TCP 链接数……

而另外一方面,随着 Fenix’s Bookstore 程序规模与用户规模的壮大,开发团队人员数量也变得越来越多。尽管根据不同微服务进行拆分,可以将每个服务的团队成员都控制于“2 Pizza Teams ”的范围以内,但一个很现实的问题是高端技术人员的数量总是有限的,人多了就不可能保证每个人都是精英,如何让普通的、初级的程序员依然能够做出靠谱的代码,成为这一阶段技术管理者的要重点思考的难题。这时候,团队内部出现了一种声音:微服务太复杂了,已经学不过来了,让我们回归单体吧……

在上述故事背景下,Fenix’s Bookstore 迎来了它的下一次技术架构的演进,这次的进化的目标主要有以下两点:

目标一:实现在大规模虚拟服务下可管理、可观测的系统。 必须找到某种方法,针对应用系统整体层面,而不是针对单一微服务来连接、调度、配置和观测服务的执行情况。此时,可视化整个系统的服务调用关系,动态配置调节服务节点的断路、重试和均衡参数,针对请求统一收集服务间的处理日志等功能就不再是系统锦上添花的外围功能了,而是关乎系统是否能够正常运行、运维的必要支撑点。

目标二:在代码层面,裁剪技术栈深度,回归单体架构中基于 Spring Boot 的开发模式,而不是 Spring Cloud 或者 Spring Cloud Kubernetes 的技术架构。 我们并不是要去开历史的倒车,相反,我们是很贪心地希望开发重新变得简单的同时,又不能放弃现在微服务带来的一切好处。在这个版本的 Fenix’s Bookstore 里,所有与 Spring Cloud 相关的技术组件,如上个版本遗留的 Zuul 网关、Hystrix 断路器,还有上个版本新引入用于感知适配 Kubernetes 环境的 Spring Cloud Kubernetes 都将会被拆除掉。如果只观察单个微服务的技术堆栈,它与最初的单体架构几乎没有任何不同——甚至还更加简单了,连从单体架构开始一直保护着服务调用安全的 Spring Security 都移除掉(由于 Fenix’s Bookstore 借用了 Spring Security OAuth2 的密码模式做为登陆服务的端点,所以在 Jar 包层面 Spring Security 还是存在的,但其用于安全保护的 Servlet 和 Filter 已经被关闭掉)。

从升级目标可以明确地得到一种导向,我们必须控制住服务数量膨胀后传递到运维团队的压力,让“每运维人员能支持服务的数量”这个比例指标有指数级地提高才能确保微服务下运维团队的健康运作。对于开发团队,我们可以只要求一小部分核心的成员对微服务、Kubernetes、Istio 等技术有深刻的理解即可,其余大部分开发人员,仍然可以基于最传统、普通的 Spring Boot 技术栈来开发功能。升级改造之后的应用架构如下图所示:

图片说明

(原文此处为应用架构示意图,略)

运行程序

在已经部署 Kubernetes 与 Istio 的前提下,通过以下几种途径,可以运行程序,浏览最终的效果:

在 Kubernetes 无 Sidecar 状态下运行: 在业务逻辑的开发过程中,或者其他不需要双向 TLS、不需要认证授权支持、不需要可观测性支持等非功能性能力增强的环境里,可以不启动 Envoy(但还是要安装 Istio 的,因为用到了 Istio Ingress Gateway),工程在编译时已通过 Kustomize 产生出集成式的资源描述文件:

# Kubernetes without Envoy资源描述文件 
$ kubectl apply -f 
https://raw.githubusercontent.com/fenixsoft/servicemesh_arch_istio/master/bookstore-dev.yml 

请注意资源文件中对 Istio Ingress Gateway 的设置是针对 Istio 默认安装编写的,即以 istio-ingressgateway 作为标签,以 LoadBalancer 形式对外开放 80 端口,对内监听 8080 端口。在部署时可能需要根据实际情况进行调整,你可观察以下命令的输出结果来确认这一点:

$ kubectl get svc istio-ingressgateway -nistio-system -o yaml

在浏览器访问:http://localhost,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。

在 Istio 服务网格环境上运行: 工程在编译时已通过 Kustomize 产生出集成式的资源描述文件,可通过该文件直接在 Kubernetes with Envoy 集群中运行程序:

# Kubernetes with Envoy资源描述文件
$ kubectl apply -f 
https://raw.githubusercontent.com/fenixsoft/servicemesh_arch_istio/master/bookstore.yml

当所有的 Pod 都处于正常工作状态后(这个过程一共需要下载几百 MB 的镜像,尤其是 Docker 中没有各层基础镜像缓存时,请根据自己的网速保持一定的耐心。未来 GraalVM 对 Spring Cloud 的支持更成熟一些后,可以考虑采用 GraalVM 来改善这一点)。在浏览器访问:http://localhost:30080,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。

注意

通过 Istio 服务网格运行的访问端口默认为 30080,与无 Sidecar 状态下的默认端口不同,请根据实际部署情况确认。

技术组件

本工程采用 Istio 服务网格架构,主要技术组件包括:

  • 数据平面:由 Istio 注入的 Envoy 边车代理(Sidecar)构成,负责流量拦截、路由、负载均衡、服务发现、断路器、超时控制、重试、故障注入、可观测性等功能。
  • 控制平面:Istiod(Istio 控制平面组件)负责管理和下发配置到所有 Envoy,统一管理流量规则、安全策略和遥测收集。
  • 入口网关:Istio Ingress Gateway,作为集群入口,负责外部流量进入服务网格的负载均衡与路由,替代了原有的 Zuul 网关。
  • 配置中心:仍采用 Kubernetes 的 ConfigMap,通过 Istio 的配置模型(如 VirtualService、DestinationRule)实现更精细的流量管理。
  • 服务熔断与治理:通过 DestinationRule 中的 trafficPolicy.connectionPooloutlierDetection 等配置,实现连接池管理、异常点检测与驱逐,不再依赖 Hystrix。
  • 认证授权:由 Istio 的 RequestAuthenticationAuthorizationPolicy 结合 Kubernetes RBAC 来提供服务间的安全通信(mTLS)和访问控制,Spring Security 仅保留用于用户登录凭证管理。
  • 可观测性:通过 Envoy 自动采集指标、日志和分布式追踪数据,配合 Prometheus、Grafana、Kiali、Jaeger 等工具实现全面的可观测性。

协议

本文档代码部分采用 Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:

  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。

本作品文档部分采用 知识共享署名 4.0 国际许可协议 进行许可。遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:

  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0 国际许可协议进行许可。

Kubernetes with Envoy 资源描述文件

$ kubectl apply -f https://raw.githubusercontent.com/fenixsoft/servicemesh_arch_istio/master/bookstore.yml

当所有的 Pod 都处于正常工作状态后(这个过程一共需要下载几百 MB 的镜像,尤其是 Docker 中没有各层基础镜像缓存时,请根据自己的网速保持一定的耐心。未来 GraalVM 对 Spring Cloud 的支持更成熟一些后,可以考虑采用 GraalVM 来改善这一点),在浏览器访问:http://localhost,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。

通过 Skaffold 在命令行或 IDE 中以调试方式运行

这个运行方式与此前调试 Kubernetes 服务是完全一致的。在本地针对单个服务编码、调试完成后,通过 CI/CD 流水线部署到 Kubernetes 中进行集成的。如果只是针对集成测试,这并没有什么问题,但同样的做法应用在开发阶段就相当不便了,我们不希望每做一处修改都要经过一次 CI/CD 流程,这将非常耗时且难以调试。

Skaffold 是 Google 在 2018 年开源的一款加速应用在本地或远程 Kubernetes 集群中构建、推送、部署和调试的自动化命令行工具,对于 Java 应用来说,它可以帮助我们做到监视代码变动,自动打包出镜像,将镜像打上动态标签并更新部署到 Kubernetes 集群,为 Java 程序注入开放 JDWP 调试的参数,并根据 Kubernetes 的服务端口自动在本地生成端口转发。以上都是根据 skaffold.yml 中的配置来进行的,开发时 skaffold 通过 dev 指令来执行这些配置,具体的操作过程如下所示:

# 克隆获取源码
$ git clone https://github.com/fenixsoft/servicemesh_arch_istio.git && cd servicemesh_arch_istio
 
# 编译打包
$ ./mvnw package
 
# 启动 Skaffold
# 此时将会自动打包 Docker 镜像,并部署到 Kubernetes 中
$ skaffold dev

服务全部启动后,在浏览器访问:http://localhost,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试,注意这里开放和监听的端口同样取决于 Istio Ingress Gateway,可能需要根据系统环境进行调整。

调整代理自动注入

项目提供的资源文件中,默认是允许边车代理自动注入到 Pod 中的,这会导致服务需要有额外的容器初始化过程。开发期间,我们可能需要关闭自动注入以提升容器频繁改动、重新部署时的效率。如需关闭代理自动注入,请自行调整 bookstore-kubernetes-manifests 目录下的 bookstore-namespaces.yaml 资源文件,根据需要将 istio-injection 修改为 enable 或者 disable

关闭边车代理的影响

如果关闭了边车代理,意味着你的服务丧失了访问控制(以前是基于 Spring Security 实现的,在 Istio 版本中这些代码已经被移除)、断路器、服务网格可视化等一系列依靠 Envoy 代理所提供能力。但这些能力是纯技术的,与业务无关,并不影响业务功能正常使用,所以在本地开发、调试期间关闭代理是可以考虑的。

技术组件

Fenix’s Bookstore 采用基于 Istio 的服务网格架构,其中主要的技术组件包括:

  • 配置中心:通过 Kubernetes 的 ConfigMap 来管理。
  • 服务发现:通过 Kubernetes 的 Service 来管理,由于已经不再引入 Spring Cloud Feign 了,所以在 OpenFeign 中,直接使用短服务名进行访问。
  • 负载均衡:未注入边车代理时,依赖 KubeDNS 实现基础的负载均衡,一旦有了 Envoy 的支持,就可以配置丰富的代理规则和策略。
  • 服务网关:依靠 Istio Ingress Gateway 来实现,已经移除了 Kubernetes 版本中保留的 Zuul 网关。
  • 服务容错:依靠 Envoy 来实现,已经移除了 Kubernetes 版本中保留的 Hystrix。
  • 认证授权:依靠 Istio 的安全机制来实现,实质上已经不再依赖 Spring Security 进行 ACL 控制,但 Spring Security OAuth 2 仍然以第三方 JWT 授权中心的角色存在,为系统提供终端用户认证,为服务网格提供令牌生成、公钥 JWKS 等支持。

协议

本作品代码部分采用 Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:

  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。

本作品文档部分采用 知识共享署名 4.0 国际许可协议 进行许可。 遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:

  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0 国际许可协议进行许可。

无服务:AWS Lambda

Release v1.0 | License Apache 2.0 | Doc License CC 4.0 | Author IcyFenix

如果你此时并不曾了解过什么是 “The Fenix Project”,建议先阅读这部分内容。

无服务架构(Serverless)与微服务架构本身没有继承替代关系,它们并不是同一种层次的架构,无服务的云函数可以作为微服务的一种实现方式,甚至可能是未来很主流的实现方式。在这部文档中我们的话题主要还是聚焦在如何解决分布式架构下的种种问题,相对而言无服务架构并非重点,不过为保证架构演进的完整性,笔者仍然建立了无服务架构的简单演示工程。

不过,由于无服务架构原理上就决定了它对程序的启动性能十分敏感,这天生就不利于 Java 程序,尤其不利于 Spring 这类启动时组装的 CDI 框架。因此基于 Java 的程序,除非使用 GraalVM 做提前编译、将 Spring 的大部分 Bean 提前初始化,或者迁移至 Quarkus 这类以原生程序为目标的框架上,否则是很难实际用于生产的。

运行程序

Serverless 架构的 Fenix’s Bookstore 基于亚马逊 AWS Lambda 平台运行,这是最早商用,也是目前全球规模最大的 Serverless 运行平台。从 2018 年开始,中国的主流云服务厂商,如阿里云、腾讯云都推出了各自的 Serverless 云计算环境,如需在这些平台上运行 Fenix’s Bookstore,应根据平台提供的 Java SDK 对 StreamLambdaHandler 的代码进行少许调整。

假设你已经完成 AWS 注册、配置 AWS CLI 环境 以及 IAM 账号的前提下,可通过以下几种途径,运行程序,浏览最终的效果:

通过 AWS SAM Local 在本地运行

AWS CLI 中附有 SAM CLI,但是版本较旧,可通过 此地址 安装最新版本的 SAM CLI。另外,SAM 需要 Docker 运行环境支持,可参考 此处部署

首先编译应用出二进制包,执行以下标准 Maven 打包命令即可:

$ mvn clean package

根据 pom.xmlassembly-zip 的设置,打包将不会生成 SpringBoot Fat JAR,而是产生适用于 AWS Lambda 的 ZIP 包。打包后,确认已在 target 目录生成 ZIP 文件,且文件名称与代码中提供了 sam.yaml 中配置的一致,在工程根目录下运行如下命令启动本地 SAM 测试:

$ sam local start-api --template sam.yaml

在浏览器访问:http://localhost:3000,系统预置了一个用户(user:icyfenixpw:123456),也可以注册新用户来测试。

通过 AWS Serverless CLI 将本地 ZIP 包上传至云端运行

确认已配置 AWS 凭证后,工程中已经提供了 serverless.yml 配置文件,确认文件中 ZIP 的路径与实际 Maven 生成的一致,然后在命令行执行:

$ sls deploy

此时 Serverless CLI 会自动将 ZIP 文件上传至 AWS S3,然后生成对应的 Layers 和 API Gateway,运行结果如下所示:

$ sls deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service bookstore-serverless-awslambda-1.0-SNAPSHOT-lambda-package.zip file to S3 (53.58 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: spring-boot-serverless
stage: dev
region: us-east-1
stack: spring-boot-serverless-dev
resources: 10
api keys:
  None
endpoints:
  GET - https://cc1oj8hirl.execute-api.us-east-1.amazonaws.com/dev/
functions:
  springBootServerless: spring-boot-serverless-dev-springBootServerless
layers:
  None
Serverless: Removing old service artifacts from S3...

访问输出结果中的地址(譬如上面显示的 https://cc1oj8hirl.execute-api.us-east-1.amazonaws.com/dev/)即可浏览结果。

数据库建议

需要注意,由于 Serverless 对响应速度的要求本来就较高,所以不建议再采用 HSQLDB 数据库来运行程序了,每次冷启动都重置一次数据库本身也并不合理。代码中有提供 MySQL 的 Schema,建议采用 AWS RDB MySQL/MariaDB 作为数据库来运行。

协议

本作品代码部分采用 Apache 2.0 协议 进行许可。遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:

  • 署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
  • 保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。

本作品文档部分采用 知识共享署名 4.0 国际许可协议 进行许可。遵循许可的前提下,你可以自由地共享,包括在任何媒介上以任何形式复制、发行本作品,亦可以自由地演绎、修改、转换或以本作品为基础进行二次创作。但要求你:

  • 署名:应在使用本文档的全部或部分内容时候,注明原作者及来源信息。
  • 非商业性使用:不得用于商业出版或其他任何带有商业性质的行为。如需商业使用,请联系作者。
  • 相同方式共享的条件:在本文档基础上演绎、修改的作品,应当继续以知识共享署名 4.0 国际许可协议进行许可。