利用Springboot+Dubbo,構建分布式微服務,全程注解開發
隨著互聯網的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分布式服務架構以及流動計算架構勢在必行,亟需一個治理系統確保架構有條不紊的演進。
一、先來一張圖
說起 Dubbo,相信大家都不會陌生!阿里巴巴公司開源的一個高性能優秀的服務框架,可以使得應用可通過高性能的 RPC 實現服務的輸出和輸入功能,同時可以和 Spring 框架無縫集成。
Dubbo 架構圖
節點角色說明:
- Provider:暴露服務的服務提供方
- Consumer:調用遠程服務的服務消費方
- Registry:服務注冊與發現的注冊中心
- Monitor:統計服務的調用次數和調用時間的監控中心
- Container:服務運行容器
二、實現思路
今天,我們以一個用戶選擇商品下訂單這個流程,將其拆分成3個業務服務:用戶中心、商品中心、訂單中心,使用 Springboot + Dubbo 來實現一個小 Demo!
服務交互流程如下:
本文主要是介紹 Springboot 與 Dubbo 的框架整合以及開發實踐,而真實的業務服務拆分是一個非常復雜的過程,比我們介紹的這個要復雜的多,上文提到的三個服務只是為了項目演示,不必過于糾結為什么要這樣拆分!
好了,廢話也不多說了,下面我們開擼!
- 1.在虛擬機創建 4 臺 centos7,任意選擇一臺安裝 zookeeper
- 2.構建微服務項目并編寫代碼
- 3.在 centos7 上部署微服務
- 4.遠程服務調用測試
三、zookeeper安裝
在使用 Dubbo 之前,我們需要一個注冊中心,目前 Dubbo 可以選擇的注冊中心有 zookeeper、Nacos 等,一般建議使用 zookeeper!
首先在安裝 Zookeeper 之前,需要安裝并配置好 JDK,本機采用的是Oracle Java8 SE。
- 安裝JDK(已經安裝可以忽略)
- yum -y install java-1.8.0-openjdk
- 查看java安裝情況
- java -version
- JDK安裝完成之后,下載安裝Zookeeper
- #創建一個zookeeper文件夾
- cd /usr
- mkdir zookeeper
- #下載zookeeper-3.4.14版本
- wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
- #解壓
- tar -zxvf zookeeper-3.4.14.tar.gz
- 創建數據、日志目錄
- #創建數據和日志存放目錄
- cd /usr/zookeeper/
- mkdir data
- mkdir log
- #把conf下的zoo_sample.cfg備份一份,然后重命名為zoo.cfg
- cd conf/
- cp zoo_sample.cfg zoo.cfg
- 配置zookeeper
- #編輯zoo.cfg文件
- vim zoo.cfg
- 啟動Zookeeper
- #進入Zookeeper的bin目錄
- cd zookeeper/zookeeper-3.4.14/bin
- #啟動Zookeeper
- ./zkServer.sh start
- #查詢Zookeeper狀態
- ./zkServer.sh status
- #關閉Zookeeper狀態
- ./zkServer.sh stop
出現如下信息,表示啟動成功!
四、項目介紹
- springboot版本:2.1.1.RELEASE
- zookeeper版本:3.4.14
- dubbo版本:2.7.3
- mybtais-plus版本:3.0.6
- 數據庫:mysql-8
- 構建工具:maven
- 服務模塊:用戶中心、商品中心、訂單中心
五、代碼實踐
5.1、初始化數據庫
首先在 mysql 客戶端,創建3個數據庫,分別是:dianshang-user、dianshang-platform、dianshang-business。
- 在 dianshang-user 數據庫中,創建用戶表 tb_user,并初始化數據
- 在 dianshang-platform 數據庫中,創建商品表 tb_product,并初始化數據
- 在 dianshang-platform 數據庫中,創建訂單表 tb_order、訂單詳情表 tb_order_detail
5.2、創建工程
數據庫表設計完成之后,在 IDEA 下創建一個名稱為dianshang的Springboot工程。
最終的目錄如下圖:
目錄結構說明:
- dianshang-common:主要存放一些公共工具庫,所有的服務都可以依賴使用
- dianshang-business:訂單中心,其中api模塊主要是提供dubbo服務暴露接口,provider模塊是一個springboot項目,提供服務處理操作
- dianshang-user:用戶中心,其中api模塊和provider模塊,設計與之類似
- dianshang-platform:商品中心,其中api模塊和provider模塊,設計與之類似
在父類pom文件中加入dubbo和zookeeper客戶端,所有依賴的項目都可以使用。
- <!-- lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.4</version>
- <scope>provided</scope>
- </dependency>
- <!-- Dubbo Spring Boot Starter -->
- <dependency>
- <groupId>org.apache.dubbo</groupId>
- <artifactId>dubbo-spring-boot-starter</artifactId>
- <version>2.7.3</version>
- </dependency>
- <!-- 因為使用的是 zookeeper 作為注冊中心,所以要添加 zookeeper 依賴 -->
- <dependency>
- <groupId>org.apache.zookeeper</groupId>
- <artifactId>zookeeper</artifactId>
- <version>3.4.13</version>
- <exclusions>
- <exclusion>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- </exclusion>
- <exclusion>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <!--使用curator 作為zookeeper客戶端-->
- <dependency>
- <groupId>org.apache.curator</groupId>
- <artifactId>curator-framework</artifactId>
- <version>4.2.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.curator</groupId>
- <artifactId>curator-recipes</artifactId>
- <version>4.2.0</version>
- </dependency>
溫馨提示:小編在搭建環境的時候,發現一個坑,工程中依賴的zookeeper版本與服務器的版本,需要盡量一致,例如,本例中zookeeper服務器的版本是3.4.14,那么在依賴zookeeper文件庫的時候,也盡量保持一致,如果依賴3.5.x版本的zookeeper,項目在啟動的時候會各種妖魔鬼怪的報錯!
5.3、創建用戶中心項目
在 IDEA 中,創建dianshang-user子模塊,并依賴dianshang-common模塊
- <dependencies>
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-common</artifactId>
- <version>1.0.0</version>
- </dependency>
- </dependencies>
同時,創建dianshang-user-provider和dianshang-user-api模塊。
- dianshang-user-api:主要對其他服務提供接口暴露
- dianshang-user-provider:類似一個web工程,主要負責基礎業務的crud,同時依賴dianshang-user-api模塊
5.3.1、配置dubbo服務
在dianshang-user-provider的application.yml文件中配置dubbo服務,如下:
- #用戶中心服務端口
- server:
- port: 8080
- #數據源配置
- spring:
- datasource:
- druid:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: "jdbc:mysql://localhost:3306/dianshang-user"
- username: root
- password: 111111
- #dubbo配置
- dubbo:
- scan:
- # 包名根據自己的實際情況寫
- base-packages: org.project.dianshang.user
- protocol:
- port: 20880
- name: dubbo
- registry:
- #zookeeper注冊中心地址
- address: zookeeper://192.168.0.107:2181
5.3.2、編寫服務暴露接口以及實現類
在dianshang-user-api模塊中,創建一個UserApi接口,以及返回參數對象UserVo!
- public interface UserApi {
- /**
- * 查詢用戶信息
- * @param userId
- * @return
- */
- UserVo findUserById(String userId);
- }
其中UserVo,需要實現序列化,如下:
- @Data
- @EqualsAndHashCode(callSuper = false)
- @Accessors(chain = true)
- public class UserVo implements Serializable {
- private static final long serialVersionUID = 1L;
- /**
- * 用戶ID
- */
- private String userId;
- /**
- * 用戶中文名
- */
- private String userName;
- }
在dianshang-user-provider模塊中,編寫UserApi接口實現類,如下:
- @Service(interfaceClass =UserApi.class)
- @Component
- public class UserProvider implements UserApi {
- @Autowired
- private UserService userService;
- @Override
- public UserVo findUserById(String userId) {
- QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
- queryWrapper.eq("user_id",userId);
- User source = userService.getOne(queryWrapper);
- if(source != null){
- UserVo vo = new UserVo();
- BeanUtils.copyProperties(source,vo);
- return vo;
- }
- return null;
- }
- }
其中的注解@Service指的是org.apache.dubbo.config.annotation.Service下的注解,而不是Spring下的注解哦!
接著,我們繼續創建商品中心項目!
5.4、創建商品中心項目
與用戶中心項目類似,在 IDEA 中,創建dianshang-platform子模塊,并依賴dianshang-common模塊
- <dependencies>
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-common</artifactId>
- <version>1.0.0</version>
- </dependency>
- </dependencies>
同時,創建dianshang-platform-provider和dianshang-platform-api模塊。
- dianshang-platform-api:主要對其他服務提供接口暴露
- dianshang-platform-provider:類似一個web工程,主要負責基礎業務的crud,同時依賴dianshang-platform-api模塊
5.4.1、配置dubbo服務
在dianshang-platform-provider的application.yml文件中配置dubbo服務,如下:
- #用戶中心服務端口
- server:
- port: 8081
- #數據源配置
- spring:
- datasource:
- druid:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: "jdbc:mysql://localhost:3306/dianshang-platform"
- username: root
- password: 111111
- #dubbo配置
- dubbo:
- scan:
- # 包名根據自己的實際情況寫
- base-packages: org.project.dianshang.platform
- protocol:
- port: 20881
- name: dubbo
- registry:
- #zookeeper注冊中心地址
- address: zookeeper://192.168.0.107:2181
5.4.2、編寫服務暴露接口以及實現類
在dianshang-platform-api模塊中,創建一個ProductApi接口,以及返回參數對象ProductVo!
- public interface ProductApi {
- /**
- * 通過商品ID,查詢商品信息
- * @param productId
- * @return
- */
- ProductVo queryProductInfoById(String productId);
- }
其中ProductVo,需要實現序列化,如下:
- @Data
- @EqualsAndHashCode(callSuper = false)
- @Accessors(chain = true)
- public class ProductVo implements Serializable {
- private static final long serialVersionUID = 1L;
- /**商品ID*/
- private String productId;
- /**商品名稱*/
- private String productName;
- /**商品價格*/
- private BigDecimal productPrice;
- }
在dianshang-platform-provider模塊中,編寫ProductApi接口實現類,如下:
- @Service(interfaceClass = ProductApi.class)
- @Component
- public class ProductProvider implements ProductApi {
- @Autowired
- private ProductService productService;
- @Override
- public ProductVo queryProductInfoById(String productId) {
- //通過商品ID查詢信息
- Product source = productService.getById(productId);
- if(source != null){
- ProductVo vo = new ProductVo();
- BeanUtils.copyProperties(source,vo);
- return vo;
- }
- return null;
- }
- }
接著,我們繼續創建訂單中心項目!
5.5、創建訂單中心項目
與商品中心項目類似,在 IDEA 中,創建dianshang-business子模塊,并依賴dianshang-common模塊
- <dependencies>
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-common</artifactId>
- <version>1.0.0</version>
- </dependency>
- </dependencies>
同時,創建dianshang-business-provider和dianshang-business-api模塊。
- dianshang-business-api:主要對其他服務提供接口暴露
- dianshang-business-provider:類似一個web工程,主要負責基礎業務的crud,同時依賴dianshang-business-api模塊
5.5.1、配置dubbo服務
在dianshang-business-provider的application.yml文件中配置dubbo服務,如下:
- #用戶中心服務端口
- server:
- port: 8082
- #數據源配置
- spring:
- datasource:
- druid:
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: "jdbc:mysql://localhost:3306/dianshang-business"
- username: root
- password: 111111
- #dubbo配置
- dubbo:
- scan:
- # 包名根據自己的實際情況寫
- base-packages: org.project.dianshang.business
- protocol:
- port: 20882
- name: dubbo
- registry:
- #zookeeper注冊中心地址
- address: zookeeper://192.168.0.107:2181
5.5.2、編寫服務暴露接口以及實現類
在dianshang-business-api模塊中,創建一個OrderApi接口,以及返回參數對象OrderVo!
- public interface OrderApi {
- /**
- * 通過用戶ID,查詢用戶訂單信息
- * @param userId
- * @return
- */
- List<OrderVo> queryOrderByUserId(String userId);
- }
其中OrderVo,需要實現序列化,如下:
- @Data
- @EqualsAndHashCode(callSuper = false)
- @Accessors(chain = true)
- public class OrderVo implements Serializable {
- private static final long serialVersionUID = 1L;
- /**訂單ID*/
- private String orderId;
- /**訂單編號*/
- private String orderNo;
- /**訂單金額*/
- private BigDecimal orderPrice;
- /**下單時間*/
- private Date orderTime;
- }
在dianshang-business-provider模塊中,編寫OrderApi接口實現類,如下:
- @Service(interfaceClass = OrderApi.class)
- @Component
- public class OrderProvider implements OrderApi {
- @Autowired
- private OrderService orderService;
- @Override
- public List<OrderVo> queryOrderByUserId(String userId) {
- QueryWrapper<Order> queryWrapper = new QueryWrapper<Order>();
- queryWrapper.eq("user_id",userId);
- List<Order> sourceList = orderService.list(queryWrapper);
- if(!CollectionUtils.isEmpty(sourceList)){
- List<OrderVo> voList = new ArrayList<>();
- for (Order order : sourceList) {
- OrderVo vo = new OrderVo();
- BeanUtils.copyProperties(order, vo);
- voList.add(vo);
- }
- return voList;
- }
- return null;
- }
- }
至此,3個項目的服務暴露接口已經開發完成!接下來我們來編寫怎么進行遠程調用!
5.6、遠程調用
5.6.1、編寫創建訂單服務
在dianshang-business-provider模塊中,編寫創建訂單接口之前,先依賴dianshang-business-api和dianshang-user-api,如下:
- <!--商品服務接口暴露 api-->
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-platform-api</artifactId>
- <version>1.0.0</version>
- </dependency>
- <!--用戶服務接口暴露 api-->
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-user-api</artifactId>
- <version>1.0.0</version>
- </dependency>
在dianshang-business-provider模塊中,編寫創建訂單服務,如下:
- @RestController
- @RequestMapping("/order")
- public class OrderController {
- @Autowired
- private OrderService orderService;
- @Autowired
- private OrderDetailService orderDetailService;
- @Reference(check =false)
- private ProductApi productApi;
- @Reference(check =false)
- private UserApi userApi;
- /**
- * 新增
- */
- @JwtIgnore
- @RequestMapping(value = "/add")
- public boolean add(String productId,String userId){
- LocalAssert.isStringEmpty(productId,"產品Id不能為空");
- LocalAssert.isStringEmpty(userId,"用戶Id不能為空");
- ProductVo productVo = productApi.queryProductInfoById(productId);
- LocalAssert.isObjectEmpty(productVo,"未查詢到產品信息");
- UserVo userVo = userApi.findUserById(userId);
- LocalAssert.isObjectEmpty(userVo,"未查詢到用戶信息");
- Order order = new Order();
- order.setOrderId(IdGenerator.uuid());
- order.setOrderNo(System.currentTimeMillis() + "");
- order.setOrderPrice(productVo.getProductPrice());
- order.setUserId(userId);
- order.setOrderTime(new Date());
- orderService.save(order);
- OrderDetail orderDetail = new OrderDetail();
- orderDetail.setOrderDetailId(IdGenerator.uuid());
- orderDetail.setOrderId(order.getOrderId());
- orderDetail.setProductId(productId);
- orderDetail.setSort(1);
- orderDetailService.save(orderDetail);
- return true;
- }
- }
其中的@Reference注解,是屬于org.apache.dubbo.config.annotation.Reference下的注解,表示遠程依賴服務。
參數check =false表示啟動服務時,不做遠程服務狀態檢查,這樣設置的目的就是為了防止當前服務啟動不了,例如用戶中心項目沒有啟動成功,但是訂單中心又依賴了用戶中心,如果check=true,此時訂單中心啟動會報錯!
5.6.2、編寫用戶查詢自己的訂單信息
同樣的,在dianshang-user-provider模塊中,編寫用戶查詢自己的訂單信息接口之前,先依賴dianshang-business-api和dianshang-user-api,如下:
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-business-api</artifactId>
- <version>1.0.0</version>
- </dependency>
- <dependency>
- <groupId>org.project.demo</groupId>
- <artifactId>dianshang-user-api</artifactId>
- <version>1.0.0</version>
- </dependency>
在dianshang-user-provider模塊中,編寫用戶查詢自己的訂單信息接口,如下:
- @RestController
- @RequestMapping("/user")
- public class UserController {
- @Reference(check =false)
- private OrderApi orderApi;
- /**
- * 通過用戶ID,查詢訂單信息
- * @param userId
- * @return
- */
- @RequestMapping("/list")
- public List<OrderVo> queryOrderByUserId(String userId){
- return orderApi.queryOrderByUserId(userId);
- }
- }
至此,遠程服務調用,編寫完成!
六、服務測試
在將項目部署在服務器之前,咱們先本地測試一下,看服務是否都可以跑通?
- 啟動用戶中心dianshang-user-provider
接著啟動訂單中心dianshang-business-provider
最后,我們來測試一下服務接口是否為我們預期的結果?
打開瀏覽器,輸入http://127.0.0.1:8082/order/add?productId=1&userId=1測試創建訂單接口,頁面運行結果顯示正常!
我們再來看看數據庫,訂單是否生成?
ok!很清晰的看到,數據已經進去了,沒啥問題!
我們再來測試一下在用戶中心訂單查詢接口,輸入http://127.0.0.1:8080/user/list?userId=1,頁面運行結果如下!
到此,本地服務測試基本通過!
七、服務器部署
在上文中,我們介紹了服務的構建、開發和測試,那如何在服務器端部署呢?
首先,修改各個項目的application.yml文件,將其中的數據源地址、dubbo注冊中心地址修改為線上能聯通的地址,然后在dianshang目錄下使用maven工具對整個工程執行如下命令進行打包!
- mvn clean install
也可以在 IDEA 環境下,通過maven配置clean install命令執行打包。
將各個項目target目錄下的dianshang-user-provider.jar、dianshang-platform-provider.jar、dianshang-business-provider.jar拷貝出來。
分別上傳到對應的服務器目錄,本服務器采用的是 CentOS7,總共4臺服務器,其中一臺部署zookeeper,另外三臺部署三個微服務項目。
登錄服務器,輸入如下命令,確保JDK已經安裝完成!
- java -version
關閉所有服務器的防火墻,放行端口訪問!
- #關閉防火墻
- systemctl stop firewalld.service
- #禁止開機啟動
- systemctl disable firewalld.service
- 啟動用戶中心服務,日志信息輸出到service.log(虛擬機ip:192.168.0.108)
- nohup java -jar dianshang-user-provider.jar > service.log 2>&1 &
- 啟動商品中心服務,日志信息輸出到service.log(虛擬機ip:192.168.0.107)
- nohup java -jar dianshang-platform-provider.jar > service.log 2>&1 &
- 啟動訂單中心服務,日志信息輸出到service.log(虛擬機ip:192.168.0.109)
- nohup java -jar dianshang-business-provider.jar > service.log 2>&1 &
打開瀏覽器,輸入http://192.168.0.109:8082/order/add?productId=1&userId=1測試創建訂單接口,頁面運行結果顯示正常!
我們再來測試一下在用戶中心訂單查詢接口,輸入輸入http://192.168.0.108:8080/user/list?userId=1,頁面運行結果如下!
很清晰的看到,輸出了2條信息,第二條訂單是在測試環境服務器生成的,第一條是本地開發環境生成的。
到此,服務器部署基本已經完成!
如果是生產環境,可能就需要多臺zookeeper來保證高可用,至少2臺服務器來部署業務服務,通過負載均衡來路由!
八、總結
整片文章比較長,主要是圍繞 springboot + dubbo 的整合,通過注解開發實現遠程服務調用像傳統的 springmvc 開發一樣輕松,當然還可以通過xml配置方式實現dubbo服務的調用,這個會在后期去介紹。
同時也介紹了服務器的部署,從中可以看出,開發雖然簡單,但是由于分布式部署,如何保證服務高可用成了開發人員頭等工作任務,所以,分布式微服務開發雖然開發簡單,但是如何確保部署的服務高可用?運維方面會帶來不少的挑戰!
九、參考
1、apache - dubbo - 官方文檔