本文你将学到什么?
本文将以原理 + 实战的方式, 首先对微服务相关的概念进行知识点扫盲, 然后开始手把手教你搭建这一整套的微服务系统
项目完整源码下载
https://github.com/bz51/SpringBoot-Dubbo-Docker-Jenkins
这套微服务框架能干啥?
这套系统搭建完之后, 那可就厉害了:
微服务架构你的整个应用程序将会被拆分成一个个功能独立的子系统, 独立运行, 系统与系统之间通过 RPC 接口通信这样这些系统之间的耦合度大大降低, 你的系统将非常容易扩展, 团队协作效率提升了 N 个档次这种架构通过眼下流行的 SpringBoot 和阿里巴巴吊炸天的 Dubbo 框架来实现
容器化部署你的各个微服务将采用目前处于浪潮之巅的 Docker 来实现容器化部署, 避免一切因环境引起的各种问题, 让你们团队的全部精力集中在业务开发上
自动化构建项目被微服务化后, 各个服务之间的关系错中复杂, 打包构建的工作量相当可怕不过没关系, 本文将借助 Jenkins, 帮助你一键自动化部署, 从此你便告别了加班
知识点扫盲篇
咳咳, 敲黑板啦! 笔记赶紧记起来, 课后我要检查的! 检查不合格的同学放学后留下来!
知识点 1: 微服务
微服务一次近几年相当火, 成为程序猿饭前便后装逼热门词汇, 你不对它有所了解如何在程序猿装逼圈子里混? 下面我用最为通俗易懂的语言介绍它
要讲清楚微服务, 我先要从一个系统架构的演进过程讲起
单机结构
我想大家最最最熟悉的就是单机结构, 一个系统业务量很小的时候所有的代码都放在一个项目中就好了, 然后这个项目部署在一台服务器上就好了整个项目所有的服务都由这台服务器提供这就是单机结构
那么, 单机结构有啥缺点呢? 我想缺点是显而易见的, 单机的处理能力毕竟是有限的, 当你的业务增长到一定程度的时候, 单机的硬件资源将无法满足你的业务需求此时便出现了集群模式, 往下接着看
集群结构
集群模式在程序猿界由各种装逼解释, 有的让你根本无法理解, 其实就是一个很简单的玩意儿, 且听我一一道来
单机处理到达瓶颈的时候, 你就把单机复制几份, 这样就构成了一个集群集群中每台服务器就叫做这个集群的一个节点, 所有节点构成了一个集群每个节点都提供相同的服务, 那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)
但问题是用户的请求究竟由哪个节点来处理呢? 最好能够让此时此刻负载较小的节点来处理, 这样使得每个节点的压力都比较平均要实现这个功能, 就需要在所有节点之前增加一个调度者的角色, 用户的所有请求都先交给它, 然后它根据当前所有节点的负载情况, 决定将这个请求交给哪个节点处理这个调度者有个牛逼了名字负载均衡服务器
集群结构的好处就是系统扩展非常容易如果随着你们系统业务的发展, 当前的系统又支撑不住了, 那么给这个集群再增加节点就行了但是, 当你的业务发展到一定程度的时候, 你会发现一个问题无论怎么增加节点, 貌似整个集群性能的提升效果并不明显了这时候, 你就需要使用微服务结构了
微服务结构
先来对前面的知识点做个总结
从单机结构到集群结构, 你的代码基本无需要作任何修改, 你要做的仅仅是多部署几台服务器, 没太服务器上运行相同的代码就行了但是, 当你要从集群结构演进到微服务结构的时候, 之前的那套代码就需要发生较大的改动了所以对于新系统我们建议, 系统设计之初就采用微服务架构, 这样后期运维的成本更低但如果一套老系统需要升级成微服务结构的话, 那就得对代码大动干戈了所以, 对于老系统而言, 究竟是继续保持集群模式, 还是升级成微服务架构, 这需要你们的架构师深思熟虑权衡投入产出比
OK, 下面开始介绍所谓的微服务
微服务就是将一个完整的系统, 按照业务功能, 拆分成一个个独立的子系统, 在微服务结构中, 每个子系统就被称为服务这些子系统能够独立运行在 web 容器中, 它们之间通过 RPC 方式通信
举个例子, 假设需要开发一个在线商城按照微服务的思想, 我们需要按照功能模块拆分成多个独立的服务, 如: 用户服务产品服务订单服务后台管理服务数据分析服务等等这一个个服务都是一个个独立的项目, 可以独立运行如果服务之间有依赖关系, 那么通过 RPC 方式调用
这样的好处有很多:
系统之间的耦合度大大降低, 可以独立开发独立部署独立测试, 系统与系统之间的边界非常明确, 排错也变得相当容易, 开发效率大大提升
系统之间的耦合度降低, 从而系统更易于扩展我们可以针对性地扩展某些服务假设这个商城要搞一次大促, 下单量可能会大大提升, 因此我们可以针对性地提升订单系统产品系统的节点数量, 而对于后台管理系统数据分析系统而言, 节点数量维持原有水平即可
服务的复用性更高比如, 当我们将用户系统作为单独的服务后, 该公司所有的产品都可以使用该系统作为用户系统, 无需重复开发
那么问题来了, 当采用微服务结构后, 一个完整的系统可能有很多独立的子系统组成, 当业务量渐渐发展起来之后, 而这些子系统之间的关系将错综复杂, 而且为了能够针对性地增加某些服务的处理能力, 某些服务的背后可能是一个集群模式, 由多个节点构成, 这无疑大大增加了运维的难度微服务的想法好是好, 但开发运维的复杂度实在是太高为了解决这些问题, 阿里巴巴的 Dubbo 就横空出世了
知识点 2:Dubbo
Dubbo 是一套微服务系统的协调者, 在它这套体系中, 一共有三种角色, 分别是: 服务提供者 (下面简称提供者) 服务消费者 (下面简称消费者) 注册中心
你在使用的时候需要将 Dubbo 的 jar 包引入到你的项目中, 也就是每个服务都要引入 Dubbo 的 jar 包然后当这些服务初始化的时候, Dubbo 就会将当前系统需要发布的服务以及当前系统的 IP 和端口号发送给注册中心, 注册中心便会将其记录下来这就是服务发布的过程与此同时, 也是在系统初始化的时候, Dubbo 还会扫描一下当前系统所需要引用的服务, 然后向注册中心请求这些服务所在的 IP 和端口号接下来系统就可以正常运行了当系统 A 需要调用系统 B 的服务的时候, A 就会与 B 建立起一条 RPC 信道, 然后再调用 B 系统上相应的服务
这, 就是 Dubbo 的作用
知识点 3: 容器化部署
当我们使用了微服务架构后, 我们将一个原本完整的系统, 按照业务逻辑拆分成一个个可独立运行的子系统为了降低系统间的耦合度, 我们希望这些子系统能够运行在独立的环境中, 这些环境之间能够相互隔离
在 Docker 出现之前, 若使用虚拟机来实现运行环境的相互隔离的话成本较高, 虚拟机会消耗较多的计算机硬件 / 软件资源 Docker 不仅能够实现运行环境的隔离, 而且能极大程度的节约计算机资源, 它成为一种轻量级的虚拟机
知识点 4: 自动化构建
当我们使用微服务架构后, 随着业务的逐渐发展, 系统之间的依赖关系会日益复杂, 而且各个模块的构建顺序都有所讲究对于一个小型系统来说, 也许只有几个模块, 那么你每次采用人肉构建的方式也许并不感觉麻烦但随着系统业务的发展, 你的系统之间的依赖关系日益复杂, 子系统也逐渐增多, 每次构建一下你都要非常小心谨慎, 稍有不慎整个服务都无法正常启动而且这些构建的工作很 low, 但却需要消耗大量的精力, 这无疑降低了开发的效率不过没关系, Jenkins 就是来帮助你解决这个问题的
我们只需在 Jenkins 中配置好代码仓库各个模块的构建顺序和构建命令, 在以后的构建中, 只需要点击立即构建按钮, Jenkins 就会自动到你的代码仓库中拉取最新的代码, 然后根据你事先配置的构建命令进行构建, 最后发布到指定的容器中运行你也可以让 Jenkins 定时检查代码仓库版本的变化, 一旦发现变动就自动地开始构建过程, 并且让 Jenkins 在构建成功后给你发一封邮件这样你连立即构建的按钮也不需要按, 就能全自动地完成这一切构建过程
实战动手篇
1. 学习目标
接下来我会带着大家, 以一个在线商城为例, 搭建一套能够自动化部署的微服务框架这个框架能做如下几件事情:
基于 SpringBoot 快速开发我们将选择目前热度很高的 SpringBoot, 最大限度地降低配置复杂度, 把大量的精力投入到我们的业务开发中来
基于 Dubbo 的微服务化我们会使用阿里巴巴的开源框架 Dubbo, 将我们的系统拆分成多个独立的微服务, 然后用 Dubbo 来管理所有服务的发布和引用有了 Dubbo 之后, 调用远程服务就像调用一个本地函数一样简单, Dubbo 会帮我们完成远程调用背后所需要的一切
基于 Docker 的容器化部署由于使用了微服务架构后, 我们的系统将会由很多子系统构成为了达到多个系统之间环境隔离的目的, 我们可以将它们部署在多台服务器上, 可这样的成本会比较高, 而且每台服务器的性能可能都没有充分利用起来所以我们很自然地想到了虚拟机, 在同一台服务器上运行多个虚拟机, 从而实现环境的隔离, 每个虚拟机上运行独立的服务然而虚拟机的隔离成本依旧很高, 因为它需要占用服务器较多的硬件资源和软件资源所以, 在微服务结构下, 要实现服务环境的隔离, Docker 是最佳选择它比虚拟机更加轻量级, 占用资源较少, 而且能够实现快速部署
基于 Jenkins 的自动化构建当我们采用了微服务架构后, 我们会发现这样一个问题整个系统由许许多多的服务构成, 这些服务都需要运行在单独的容器中, 那么每次发布的复杂度将非常高首先你要搞清楚这些服务之间的依赖关系启动的先后顺序, 然后再将多个子系统挨个编译打包发布这些操作技术难度低, 却又容易出错那么有什么工具能够帮助我们解决这些问题呢? 答案就是 Jenkins
它是一款自动化构建的工具, 简单的来说, 就是我们只需要在它的界面上按一个按钮, 就可以实现上述一系列复杂的过程
2. 项目背景介绍
本文我以一个大家都非常熟悉的在线商城作为例子, 一步步教大家如何搭建微服务框架, 它有如下功能:
产品管理: 产品的增删改查
订单管理: 订单的增删改查购物车功能
用户管理: 用户的登录注册权限管理收货地址等等
数据分析: 提供对本系统数据分析的功能
注意: 本文的 IDE 使用的是 intelliJ IDEA, 推荐大家也用这个, 用了都说好, 用了你就会爱上它
3. 创建项目的组织结构
在动手之前, 我先来说一说这一步的目标:
创建一个 Maven Project, 命名为 Gaoxi
这个 Project 由多个 Module 构成, 每个 Module 对应着微服务的一个子系统, 可独立运行, 是一个独立的项目
这也是目前主流的项目组织形式, 即多模块项目
在 Gaoxi 这个项目下创建各个子模块, 每个自模块都是一个独立的 SpringBoot 项目:
Gaoxi-User
用户服务
Gaoxi-Order
订单服务
Gaoxi-Product
产品服务
Gaoxi-Analysis
数据分析服务
Gaoxi-Controller
本系统的控制层, 和以往三层结构中的 Controller 层的作用一样, 都是用作请求调度, 只不过在微服务架构中, 我们将它抽象成一个单独的系统, 可以独立运行
Gaoxi-Common-Service-Facade
它处于本系统的最底层, 被所有模块依赖, 一些公用的类库都放在这里
Gaoxi-Redis
我们将 Redis 封装成一个单独的服务, 运行在独立的容器中, 当哪一个模块需要使用 Redis 的时候, 仅需要引入该服务即可, 就免去了各种繁琐的重复的配置而这些配置均在 Gaoxi-Redis 系统中完成了
下面开始动手
3.1 创建 Project
New 一个 Project
选择 Spring Initializr
设置 groupIdartifactIdversion
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi</artifactId>
- <version>0.0.1-SNAPSHOT</version>
Project 创建完毕! 接下来在 Project 下面创建 Module
3.2 创建 Module
在 Project 上 New Module
和刚才一样, 选择 Spring Initializr, 设置 groupIdartifactIdversion
依次创建好所有的 Module, 如下图所示:
3.3 构建模块的依赖关系
目前为止, 模块之间没有任何联系, 下面我们要通过 pom 文件来指定它们之间的依赖关系, 依赖关系如下图所示:
Gaoxi-UserGaoxi-AnalysisGaoxi-ProductGaoxi-Order 这四个系统相当于以往三层结构的 Service 层, 提供系统的业务逻辑, 只不过在微服务结构中, Service 层的各个模块都被抽象成一个个单独的子系统, 它们提供 RPC 接口供上面的 Gaoxi-Controller 调用它们之间的调用由 Dubbo 来完成, 所以它们的 pom 文件中并不需要作任何配置而这些模块和 Gaoxi-Common-Service-Facade 之间是本地调用, 因此需要将 Gaoxi-Common-Service-Facade 打成 jar 包, 并让这些模块依赖这个 jar, 因此就需要在所有模块的 pom 中配置和 Gaoxi-Common-Service-Facade 的依赖关系
此外, 为了简化各个模块的配置, 我们将所有模块的通用依赖放在 Project 的 pom 文件中, 然后让所有模块作为 Project 的子模块这样子模块就可以从父模块中继承所有的依赖, 而不需要自己再配置了
下面开始动手:
首先将 Common-Service-Facade 的打包方式设成 jar
当打包这个模块的时候, Maven 会将它打包成 jar, 并安装在本地仓库中这样其他模块打包的时候就可以引用这个 jar
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi-common-service-facade</artifactId>
- <version>0.0.1</version>
- <packaging>jar</packaging>
将其他模块的打包方式设为 war
除了 Gaoxi-Common-Service-Facade 外, 其他模块都是一个个可独立运行的子系统, 需要在 web 容器中运行, 所以我们需要将这些模块的打包方式设成 war
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi-user</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>war</packaging>
在总 pom 中指定子模块
modules 标签指定了当前模块的子模块是谁, 但是仅在父模块的 pom 文件中指定子模块还不够, 还需要在子模块的 pom 文件中指定父模块是谁
- <modules>
- <module>Gaoxi-Analysis</module>
- <module>Gaoxi-Order</module>
- <module>Gaoxi-Product</module>
- <module>Gaoxi-User</module>
- <module>Gaoxi-Redis</module>
- <module>Gaoxi-Controller</module>
- <module>Gaoxi-Common-Service-Facade</module>
- </modules>
在子模块中指定父模块
- <parent>
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
- </parent>
到此为止, 模块的依赖关系配置完毕! 但要注意模块打包的顺序由于所有模块都依赖于 Gaoxi-Common-Servie-Facade 模块, 因此在构建模块时, 首先需要编译打包安装 Gaoxi-Common-Servie-Facade, 将它打包进本地仓库中, 这样上层模块才能引用到当该模块安装完毕后, 再构建上层模块否则在构建上层模块的时候会出现找不到 Gaoxi-Common-Servie-Facade 中类库的问题
3.4 在父模块的 pom 中添加所有子模块公用的依赖
- <dependencies>
- <!-- Spring Boot -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <!-- Spring MVC -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- Spring Boot Test -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- MyBatis -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.1</version>
- </dependency>
- <!-- Mysql -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <!-- Dubbo -->
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <!-- gaoxi-common-service-facade -->
- <dependency>
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi-common-service-facade</artifactId>
- <version>0.0.1</version>
- </dependency>
- <!-- AOP -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
- <!-- guava -->
- <dependency>
- <groupId>com.google.guava</groupId>
- <artifactId>guava</artifactId>
- <version>23.3-jre</version>
- </dependency>
- </dependencies>
当父模块的 pom 中配置了公用依赖后, 子模块的 pom 文件将非常简洁, 如下所示:
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi-user</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>war</packaging>
- <name>gaoxi-user</name>
- <parent>
- <groupId>com.gaoxi</groupId>
- <artifactId>gaoxi</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
- </parent>
当项目的结构搭建完成之后, 接下来你需要配置 Docker 环境, 并将这些项目打包进容器中, 验证下是否能正常启动
4. 创建 Docker 容器
4.1 安装 Docker
在使用 Docker 之前, 你当然先要安装 Docker, 安装过程较为简单, 基本上就是傻瓜式操作, 这里就不作过多介绍了, 你可以在 Docker 的官网下载相应系统的安装包
https://www.docker.com/
4.2 获取 Tomcat 镜像
在微服务架构中, 一个完整的系统被拆分成了多个被称为微服务的子系统, 这些子系统可以独立运行在 Web 容器中所以我们需要为这些系统提供运行的 Web 容器, 这里我们选择大家较为熟悉的 Tomcat
我们知道, Tomcat 依赖于 Java 环境, 安装 Tomcat 之前要进行一系列环境的配置: 安装 Java 配置环境变量安装 Tomcat 等等这些操作还是有些繁琐的不过没关系, 当使用了 Docker 之后, 这些过程都可以轻而易举地完成
我们只需从 Docker Hub 上找到 Tomcat 的镜像资源, 然后从上面拉取下来就可以使用你可以使用 Tomcat 官方的镜像, 也可以使用我发布在 Docker Hub 上的 Tomcat 镜像
注意点: 推荐使用我的 Tomcat 镜像资源 chaimm/tomcat, 因为这个镜像中除了配置 Tomcat 的安装环境以外, 还有一些本项目中要用到的 Jenkins 相关的配置
采用如下命令从 Docker Hub 上拉取镜像:
docker pull chaimm/tomcat:1.1
简单解释下, docker pull 是从从 Docker Hub 上拉取镜像的命令, 后面的 chaimm/tomcat 是镜像的名称,:1.1 是镜像的版本号目前这个镜像的最新版本号是 1.1, 推荐大家拉取这个
4.3 创建 Tomcat 容器
这里再简单介绍下镜像和容器的关系
镜像就好比是面向对象中的类, 容器就好比类创建的对象在面向对象中, 类定义了各种属性, 类可以实例化出多个对象; 而在 Docker 中, 镜像定义了各种配置信息, 它可以实例化出多个容器容器就是一台可以运行的虚拟机
接下来我们需要为所有的微服务创建各自的容器:
- gaoxi-user
- gaoxi-product
- gaoxi-order
- gaoxi-analysis
- gaoxi-controller
- gaoxi-redis
以创建 gaoxi-user 容器为例, 采用如下命令创建容器:
docker run --name gaoxi-user-1 -p 8082:8080 -v /usr/web/gaoxi-log:/opt/tomcat/gaoxi-log chaimm/tomcat:1.1
--name: 指定容器的名字
-p: 指定容器的端口映射
-p 8082:8080 表示将容器的 8080 端口映射到宿主机的 8082 端口上
-v: 指定容器数据卷的映射
xxx:yyy 表示将容器 yyy 目录映射到宿主机的 xxx 目录上, 从而访问宿主机的 xxx 目录就相当于访问容器的 yyy 目录
chaimm/tomcat:1.1: 表示容器所对应的镜像
这条命令执行成功后, 你就可以通过你的 IP:8082 访问到 gaoxi-user-1 容器的 tomcat 了如果你看到了那只眼熟了猫, 那就说明容器启动成功了!
接下来, 你需要按照上面的方法, 给剩下几个系统创建好 Tomcat 容器
注意点: 这里要注意的是, 你需要给这些 Tomcat 容器指定不同的端口号, 防止端口号冲突当然, 在实际开发中, 你并不需要将容器的 8080 端口映射到宿主机上, 这里仅仅是为了验证容器是否启动成功才这么做的
5. 整合 Dubbo
5.1 创建 zookeeper 容器
Dubbo 一共定义了三种角色, 分别是: 服务提供者服务消费者注册中心注册中心是服务提供者和服务消费者的桥梁, 服务消费者会在初始化的时候将自己的 IP 和端口号发送给注册中心, 而服务消费者通过注册中心知道服务提供者的 IP 和端口号
在 Dubbo 中, 注册中心有多种选择, Dubbo 最为推荐的即为 ZooKeeper, 本文采用 ZooKeepeer 作为 Dubbo 的注册中心
创建 ZooKeeper 容器也较为简单, 大家可以直接使用我创建的 ZooKeeper 镜像, 通过如下命令即可下载镜像:
docker pull chaimm/zookeeper-dubbo:1.0
该镜像中不仅运行了一个 zookeeper, 还运行了一个拥有 dubbo-admin 项目的 tomcatdubbo-admin 是 Dubbo 的一个可视化管理工具, 可以查看服务的发布和引用的情况
使用如下命令启动容器:
docker run --name zookeeper-debug -p 2182:2181 -p 10000:8080 chaimm/zookeeper-dubbo:1.0
-p 2182:2181: 将容器的 2181 端口映射到宿主机的 2182 端口上, 该端口是 ZooKeeper 的端口号
-p 10000:8080: 将容器的 8080 端口映射到宿主机的 10000 端口上, 该端口是 Dubbo-Admin 所在 Tomcat 的端口号
启动成功后, 你就可以通过
你的 IP:10000/dubbo-admin-2.8.4/
访问到 Dubbo-Admin, 如下图所示:
5.2 父 pom 文件中引入 dubbo 依赖
- <!-- Spring Boot Dubbo 依赖 -->
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
5.3 发布服务
假设, 我们需要将 Gaoxi-User 项目中的 UserService 发布成一项 RPC 服务, 供其他系统远程调用, 那么我们究竟该如何借助 Dubbo 来实现这一功能呢?
在 Gaoxi-Common-Service-Facade 中定义 UserService 的接口
由于服务的发布和引用都依赖于接口, 但服务的发布方和引用方在微服务架构中往往不在同一个系统中, 所以需要将需要发布和引用的接口放在公共类库中, 从而双方都能够引用接口如下所示:
- public interface UserService {
- public UserEntity login(LoginReq loginReq);
- }
在 Gaoxi-User 中定义接口的实现
在实现类上需要加上 Dubbo 的 @Service 注解, 从而 Dubbo 会在项目启动的时候扫描到该注解, 将它发布成一项 RPC 服务
- @Service(version = "1.0.0")
- public class UserServiceImpl implements UserService {
- @Override
- public UserEntity login(LoginReq loginReq) {
- // 具体的实现代码
- }
- }
在 Gaoxi-User 的 application.properties 中配置服务提供者的信息
- spring.dubbo.application.name=user-provider # 本服务的名称
- spring.dubbo.registry.address=zookeeper://IP:2182 # ZooKeeper 所在服务器的 IP 和端口号
- spring.dubbo.protocol.name=dubbo # RPC 通信所采用的协议
- spring.dubbo.protocol.port=20883 # 本服务对外暴露的端口号
- spring.dubbo.scan=com.gaoxi.user.service # 服务实现类所在的路径
按照上面配置完成后, 当 Gaoxi-User 系统初始化的时候, 就会扫描 spring.dubbo.scan 所指定的路径下的 @Service 注解, 该注解标识了需要发布成 RPC 服务的类 Dubbo 会将这些类的接口信息 + 本服务器的 IP+spring.dubbo.protocol.port 所指定的端口号发送给 Zookeeper,Zookeeper 会将这些信息存储起来
这就是服务发布的过程, 下面来看如何引用一项 RPC 服务
5.4 引用服务
假设, Gaoxi-Controller 需要调用 Gaoxi-User 提供的登录功能, 此时它就需要引用 UserService 这项远程服务下面来介绍服务引用的方法
声明需要引用的服务
引用服务非常简单, 你只需要在引用的类中声明一项服务, 然后用 @Reference 标识, 如下所示:
- @RestController
- public class UserControllerImpl implements UserController {
- @Reference(version = "1.0.0")
- private UserService userService;
- @Override
- public Result login(LoginReq loginReq, HttpServletResponse httpRsp) {
- // 登录鉴权
- UserEntity userEntity = userService.login(loginReq);
- }
- }
在 Gaoxi-Controller 的 application.properties 中配置服务消费者的信息
- spring.dubbo.application.name=controller-consumer # 本服务的名称
- spring.dubbo.registry.address=zookeeper://IP:2182 # zookeeper 所在服务器的 IP 和端口号
- spring.dubbo.scan=com.gaoxi # 引用服务的路径
上述操作完成后, 当 Gaoxi-Controller 初始化的时候, Dubbo 就会扫描 spring.dubbo.scan 所指定的路径, 并找到所有被 @Reference 修饰的成员变量; 然后向 Zookeeper 请求该服务所在的 IP 和端口号当调用 userService.login()的时候, Dubbo 就会向 Gaoxi-User 发起请求, 完成调用的过程这个调用过程是一次 RPC 调用, 但作为程序猿来说, 这和调用一个本地函数没有任何区别, 远程调用的一切都由 Dubbo 来帮你完成这就是 Dubbo 的作用
6. 自动化构建
Jenkins 是一个自动化构建工具, 它可以帮助我们摆脱繁琐的部署过程, 我们只需要在一开始配置好构建策略, 以后部署只需要一键完成
6.1 创建 Jenkins 容器
Jenkins 采用 Java 开发, 也需要 Java 环境, 但我们使用 Docker 后, 一切都采用容器化部署, Jenkins 也不例外
拉取镜像
这里我们使用 Jenkins 官方提供的镜像, 大家只需执行如下命令拉取即可:
docker pull docker.io/jenkins/jenkins
启动容器
由于 Jenkins 运行在 Tomcat 容器中, 因此我们将容器的 8080 端口映射到宿主机的 10080 端口上:
docker run --name jenkins -p 10080:8080 docker.io/jenkins/jenkins
初始化 Jenkins
然后你需要访问 IP:10080Jenkins 会带着你进行一系列的初始化设置, 你只要跟着它一步步走就行了, 比较傻瓜式
6.2 在 Jenkins 中创建项目
接下来我们要做的是, 在 Jenkins 中为每一个服务创建一个项目, 每个项目中定义了构建的具体流程由于我们将整个项目分成了 6 个微服务, 所以我们需要在 Jenkins 中分别为这 6 个服务创建项目那句开始吧~
点击页面左侧的新建按钮:
输入项目名称 gaoxi-user, 选择构建一个 Maven 项目, 然后点击 OK:
配置 Git 仓库
选择 Git, 然后输入本项目 Git 仓库的 URL, 并在 Credentials 中输入 Git 的用户名和密码, 如下图所示:
构建触发器
选择第一项, 如下图所示:
Pre Step
Pre Step 会在正式构建前执行, 由于所有项目都依赖于 Gaoxi-Common-ServiceFacade, 因此在项目构建前, 需要将它安装到本地仓库, 然后才能被当前项目正确依赖
因此, 在 Pre Step 中填写如下信息:
Build
然后就是正式构建的过程, 填写如下信息即可:
OK,Gaoxi-User 的构建过程就配置完成了当我们点击立即构建按钮时, Jenkins 首先会从我们指定的 Git 仓库中拉取代码, 然后执行 Pre Step 中的 Maven 命令, 将 Gaoxi-Common-Serivce-Facade 打包安装到本地仓库然后执行 Build 过程, 将 Gaoxi-User 进行编译打包
但此时 Gaoxi-User 仍然只是一个本地 war 包, 并没有部署到 Tomcat 容器中, 而我们采用了容器化部署后, Jenkins 服务和 Gaoxi-User 服务并不在同一个 Docker 容器中, 那么究竟该如何才能将 Jenkins 本地编译好的 war 包发送到 Gaoxi-User 容器中呢? 这就需要使用 Jenkins 的一个插件 Deploy Plugin
6.3 远程部署
下载插件
首先你需要下载 Deploy Plugin, 下载地址如下:
https://wiki.jenkins.io/displ...
安装插件
在系统管理>插件管理>高级上传 deploy.hpi 进行安装
在父项目的 pom 文件中增加远程部署插件:
- <plugin>
- <groupId>org.codehaus.cargo</groupId>
- <artifactId>cargo-maven2-plugin</artifactId>
- <version>1.6.5</version>
- <configuration>
- <container>
- <!-- 指明使用的 tomcat 服务器版本 -->
- <containerId>tomcat8x</containerId>
- <type>remote</type>
- </container>
- <configuration>
- <type>runtime</type>
<cargo.remote.username>Tomcat 的用户名</cargo.remote.username>
<cargo.remote.password>Tomcat 的密码</cargo.remote.password>
- </configuration>
- </configuration>
- <executions>
- <execution>
- <phase>deploy</phase>
- <goals>
- <goal>redeploy</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
为 Tomcat 设置用户名和密码
修改 gaoxi-user 容器中 tomcat 的 tomcat-users.xml 文件, 增加 tomcat 的 manager 用户
注意: 如果你使用了 chaimm/tomcat 镜像, 那么其中 Tomcat 配置都已经完成, 默认用户名: admin 默认密码: jishimen2019 强烈建议修改用户名和密码
修改 Jenkins 中 gaoxi-user 的配置
在构建后操作中增加如下配置:
- WAR/EAR files: 表示你需要发布的 war 包
- Containers: 配置目标 Tomcat 的用户名和密码
7. Maven 的 profile 功能
在实际开发中, 我们的系统往往有多套环境构成, 如: 开发环境测试环境预发环境生产环境而不同环境的配置各不相同如果我们只有一套配置, 那么当系统从一个环境迁移到另一个环境的时候, 就需要通过修改代码来更换配置, 这样无疑增加了工作的复杂度, 而且易于出错但好在 Maven 提供了 profile 功能, 能帮助我们解决这一个问题
父项目的 pom 中添加 profile 元素
首先, 我们需要在总 pom 的中添加多套环境的信息, 如下所示:
- <profiles>
- <profile>
- <id>dev</id>
- <properties>
- <profileActive>dev</profileActive>
- </properties>
- <activation>
- <activeByDefault>true</activeByDefault>
- </activation>
- </profile>
- <profile>
- <id>test</id>
- <properties>
- <profileActive>test</profileActive>
- </properties>
- </profile>
- <profile>
- <id>prod</id>
- <properties>
- <profileActive>prod</profileActive>
- </properties>
- </profile>
- </profiles>
父项目的 pom 中添加 resource 元素
resource 标识了不同环境下需要打包哪些配置文件
- <resources>
- <resource>
- <!-- 标识配置文件所在的目录 -->
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- <!-- 构建时将这些配置文件全都排除掉 -->
- <excludes>
- <exclude>application.properties</exclude>
- <exclude>application-dev.properties</exclude>
- <exclude>application-test.properties</exclude>
- <exclude>application-prod.properties</exclude>
- </excludes>
- </resource>
- <resource>
- <directory>src/main/resources</directory>
- <filtering>true</filtering>
- <!-- 标识构建时所需要的配置文件 -->
- <includes>
- <include>application.properties</include>
- <!-- ${profileActive}这个值会在 maven 构建时传入 -->
- <include>application-${profileActive}.properties</include>
- </includes>
- </resource>
- </resources>
父项目的 pom 中添加插件 maven-resources-plugin
该插件用来在 Maven 构建时参数替换
- <plugin>
- <artifactId>maven-resources-plugin</artifactId>
- <version>3.0.2</version>
- <configuration>
- <delimiters>
- <delimiter>@</delimiter>
- </delimiters>
- <useDefaultDelimiters>false</useDefaultDelimiters>
- </configuration>
- </plugin>
在子项目中创建配置
分别为 dev 环境 test 环境 prod 环境创建三套配置, application.proerpties 中存放公用的配置
在 application.properties 中添加 spring.profiles.active=@profileActive@
spring.profiles.active=@profileActive@
修改 Jenkins 的配置
在所有 Jenkins 中所有 Maven 命令的末尾添加 - P test, 在打包的时候 - P 后面的参数将会作为 @profileActive@的值传入系统中, 从而根据该值打包相应的 application-{profileActive}.properties 文件
8. 开发流程
到此为止, 所有准备工作都已经完成, 接下来就可以进入代码开发阶段下面我以一个例子, 带着大家感受下有了这套微服务框架后, 我们的开发流程究竟有了哪些改变? 下面以开发一个用户登录功能为例, 介绍下使用本框架之后开发的流程
8.1 开发目标
在 Gaoxi-User 系统中实现登录的业务逻辑, 并发布成 RPC 服务
在 Gaoxi-Controller 中远程调用登录服务, 并向前端提供登录的 REST 接口
8.2 开发登录服务
首先需要在 Gaoxi-Common-Service-Facade 中创建 UserService 接口, 并在其中声明登录的抽象函数
- public interface UserService {
- public UserEntity login(LoginReq loginReq);
- }
PS: 为什么要将 UserService 放在 Gaoxi-Common-Service-Facade 中?
在这个项目中, Gaoxi-User 是 UserService 服务的提供方, Gaoxi-Controller 是 UserService 服务的引用方由于二者并不在同一个系统中, 所以必须要借助于 Dubbo 来实现远程方法调用而 Dubbo 发布服务和引用服务的时候, 都是根据服务的接口标识服务的, 即服务引用方和发布方都需要使用服务的接口, 因此需要将服务的接口放在所有项目共同依赖的基础模块 Gaoxi-Common-Service-Facade 中
然后在 Gaoxi-User 中开发 UserService 的实现 UserServiceImpl
UserServiceImpl 上必须要加上 Dubbo 的 @Service 注解, 从而告诉 Dubbo, 在本项目初始化的时候需要将这个类发布成一项服务, 供其他系统调用
- @Service(version = "1.0.0")
- @org.springframework.stereotype.Service
- public class UserServiceImpl implements UserService {
- @Autowired
- private UserDAO userDAO;
- @Override
- public UserEntity login(LoginReq loginReq) {
- // 校验参数
- checkParam(loginReq);
- // 创建用户查询请求
- UserQueryReq userQueryReq = buildUserQueryReq(loginReq);
- // 查询用户
- List<UserEntity> userEntityList = userDAO.findUsers(userQueryReq);
- // 查询失败
- if (CollectionUtils.isEmpty(userEntityList)) {
- throw new CommonBizException(ExpCodeEnum.LOGIN_FAIL);
- }
- // 查询成功
- return userEntityList.get(0);
- }
- }
8.3 引用登录服务
当 UserService 开发完毕后, 接下来 Gaoxi-Controller 需要引用该服务, 并向前端提供一个登录的 REST 接口
若要使用 userService 中的函数, 仅需要在 userService 上添加 @Reference 注解, 然后就像调用本地函数一样使用 userService 即可 Dubbo 会帮你找到 UserService 服务所在的 IP 和端口号, 并发送调用请求但这一切对于程序猿来说是完全透明的
- @RestController
- public class UserControllerImpl implements UserController {
- @Reference(version = "1.0.0")
- private UserService userService;
- /**
- * 登录
- * @param loginReq 登录请求参数
- * @param httpRsp HTTP 响应
- * @return 登录是否成功
- */
- @GetMapping("/login")
- @Override
- public Result login(LoginReq loginReq, HttpServletResponse httpRsp) {
- // 登录鉴权
- UserEntity userEntity = userService.login(loginReq);
- // 登录成功
- doLoginSuccess(userEntity, httpRsp);
- return Result.newSuccessResult();
- }
- }
8.4 自动构建服务
上面的代码完成后, 接下来你需要将代码提交至你的 Git 仓库接下来就是自动化部署的过程了
你需要进入 Jenkins, 由于刚才修改了 Gaoxi-User 和 Gaoxi-Controller 的代码, 因此你需要分别构建这两个项目
接下来 Jenkins 会自动从你的 Git 仓库中拉取最新的代码, 然后依次执行 Pre StepBuild 构建后操作的过程由于我们在 Pre Step 中设置了编译 Gaoxi-Common-Service-Facade, 因此 Jenkins 首先会将其安装到本地仓库; 然后再执行 Build 过程, 构建 Gaoxi-User, 并将其打包成 war 包最后将执行构建后操作, 将 war 包发布到相应的 tomcat 容器中
至此, 整个发布流程完毕!
8.5 查看服务的状态
当 Jenkins 构建完成后, 我们可以登录 Dubbo-Admin 查看服务发布和引用的状态
当我们搜索 UserService 服务后, 可以看到, 该服务的提供者已经成功发布了服务:
点击消费者我们可以看到, 该服务已经被 controller-consumer 成功订阅:
9. 总结
总结一下, 这套框架有如下优势:
微服务架构
我们借助于 SpringBoot 和 Dubbo 实现了微服务架构微服务架构的理念就是将一个原本庞大复杂的系统, 按照业务功能拆分成一个个具有独立功能可以独立运行的子系统, 系统之间若有依赖, 则通过 RPC 接口通信从而最大限度地降低了系统之间的耦合度, 从而更加易于扩展更加易于维护
容器化部署
我们借助于 Docker 实现了容器化部署容器能够帮助我们屏蔽不同环境下的配置问题, 使得我们只需要有一个 Dockerfile 文件, 就可以处处运行和虚拟机一样, Docker 也拥有环境隔离的能力, 但比虚拟机更加轻量级, 由于每个容器仅仅是一条进程, 因此它可以达到秒级的启动速度
自动化构建
我们借助于 Jenkins 实现了所有项目的自动化构建与部署我们只需要点击立即构建这个按钮, Jenkins 就可以帮助我们梳理好错综复杂的项目依赖关系, 准确无误地完成构建, 并将 war 包发送到相应的 web 容器中在启动的过程中, Dubbo 会扫描当前项目所需要发布和引用的服务, 将所需要发布的服务发布到 ZooKeeper 上, 并向 ZooKeeper 订阅所需的服务
有了 Jenkins 之后, 这一切都是自动化完成也许你并没有太强烈地感受到 Jenkins 所带来的便利但是你想一想, 对于一个具有错综复杂的依赖关系的微服务系统而言, 如果每个服务的构建都需要你手动完成的话, 你很快就会崩溃, 你大把的时间将会投入在无聊但又容易出错的服务构建上而 Jenkins 的出现能让这一切自动化完成
来源: https://segmentfault.com/a/1190000013617164