導(dǎo)讀惯殊,前面哆嗦了好幾篇携御,其實只是講了微服務(wù)的一些概念诱咏,以及服務(wù)的注冊與發(fā)現(xiàn)苔可。但好像還在望其冰山一角。沒錯胰苏,接下來我們將望其另一角...
這里我們注意到有幾個問題:
1 我們之前模擬注冊到 Eureka Server 上的 Eureka Client 服務(wù)是空白的... 也就是一句業(yè)務(wù)代碼都沒有硕蛹??硕并?
2 之前我們提到微服務(wù)就是把龐大的雜糅在一起的各個業(yè)務(wù)模塊拆分成多個服務(wù)獨立開發(fā)法焰、運行、維護(hù)倔毙,以降低耦合度埃仪。可是陕赃,如何拆分卵蛉?
3 拆分完了之后,服務(wù) A 如何調(diào)用服務(wù) B么库?
一傻丝、服務(wù)拆分
微服務(wù)拆分(或者服務(wù)拆分)并不是一個新鮮的名詞,在軟件工程領(lǐng)域已經(jīng)有不少年頭诉儒,而且還有專門針對服務(wù)拆分的研究與職位葡缰。
這里展開有很多個話題可以研究和討論。當(dāng)然忱反,此文中服務(wù)拆分不是重點泛释。這里,我只是簡要分享一些微服務(wù)拆分上的一些原則温算,而這些也是結(jié)合微服務(wù)的特點來拆分怜校,比如從不同的業(yè)務(wù)功能模塊、業(yè)務(wù)數(shù)據(jù)的相關(guān)性注竿、不同服務(wù)的負(fù)載情況等等...
具體的拆分細(xì)則很多茄茁,如果對服務(wù)拆分感興趣,可以從網(wǎng)上找到很多優(yōu)秀的作者文章巩割,比如
伯樂在線的服務(wù)拆分與架構(gòu)演進(jìn)的文章: http://blog.jobbole.com/109902/
二裙顽、服務(wù)示例
基于服務(wù)拆分原則,這里舉一個簡單的拆分實例喂分。比如锦庸,企業(yè)的內(nèi)部用戶管理就可以拆分為一個單獨的服務(wù)。用戶的數(shù)據(jù)可以對于其他系統(tǒng)獨立蒲祈,而用戶的業(yè)務(wù)又可以對應(yīng)到人力資源管理里甘萧。并且萝嘁,用戶的操作又是企業(yè)內(nèi)部各業(yè)務(wù)部門和各系統(tǒng)都需要用到的,如果每個業(yè)務(wù)部門都維護(hù)一套自己的用戶管理模塊扬卷,不僅大量重復(fù)工作牙言,而且各部門之間的數(shù)據(jù)還可能不一致而造成某些沖突...
將用戶的數(shù)據(jù)獨立出去,其他系統(tǒng)只需要來調(diào)用對應(yīng)接口就能進(jìn)行用戶的相關(guān)操作怪得,而用戶的管理則統(tǒng)一由某一業(yè)務(wù)部門進(jìn)行咱枉,比如人力資源部門。
此處徒恋,我們模擬一個非常簡單的用戶服務(wù)蚕断,包含簡單的添加用戶,獲取一個用戶入挣,獲取所有用戶的接口亿乳。
1 創(chuàng)建一個 Eureka Client 項目
根據(jù)前面第六篇中的 Eureka Client 項目的創(chuàng)建與注冊先創(chuàng)建一個 Eureka Client 的空白項目 user-service
2 準(zhǔn)備項目配置
本示例項目使用了 mysql 存儲用戶數(shù)據(jù),使用 spring data jpa (如果對 jpa 不太熟径筏,可自行搜索引擎葛假,它非常簡單,因為它就是方便數(shù)據(jù)庫操作而開發(fā)的)來進(jìn)行持久層操作滋恬。因此聊训,需要添加 MySQL 的依賴,spring-boot-starter-data-jpa 的依賴恢氯。另外带斑,spring-boot-starter-web 依賴則是確保本項目能夠作為一個后臺 web 項目運行。
(1) pom.xml
pom.xml 中添加如下依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
(2) application.yml
本配置文件中主要添加了 mysql 的連接信息的配置酿雪,jpa 相關(guān)的
# application name
spring:
application:
name: user-service
# mysql jpa config
datasource:
url: jdbc:mysql://192.168.174.200:3306/microservice?characterEncoding=UTF-8&autoReconnect=true&useSSL=false
username: ms
password: ms123
dbcp:
validation-query: SELECT 1
test-while-idle: true
jpa:
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
# server monitor port
server:
port: 8080
# eureka server cluster
eureka:
client:
service-url:
defaultZone: http://hadoop1:8000/eureka/,http://hadoop2:8000/eureka/,http://hadoop3:8000/eureka/
注:
- 數(shù)據(jù)庫連接配置 spring.datasource.url 的配置請確保你有一臺裝有 MySQL 的數(shù)據(jù)庫并且能夠連接遏暴,且已經(jīng)有一個可用的數(shù)據(jù)庫及能夠訪問的數(shù)據(jù)庫賬號
- Eureka server 的地址是前面我搭建的運行在虛擬機(jī)上的集群
(3) 創(chuàng)建實體類
創(chuàng)建一個用戶的實體類侄刽,此類對應(yīng)于我們的數(shù)據(jù)庫中的數(shù)據(jù)表 user指黎,并且表的創(chuàng)建由 spring data jpa 框架進(jìn)行。
package com.jiangzhuolin.userservice.bean;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name="user")
@EntityListeners(AuditingEntityListener.class)
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
private String phone;
private String email;
private String address;
@CreatedDate
@Column(name = "create_time")
private Date createTime;
@LastModifiedDate
@Column(name = "update_time")
private Date updateTime;
... 省略 getter 與 setter 方法
(4) 創(chuàng)建 repository 接口
repository 接口類似于我們的 dao 層的接口州丹,它直接繼承 JpaRepository醋安,好處是我們多數(shù)情況下的簡單的數(shù)據(jù)庫操作完全不用我們自己實現(xiàn),由 jpa 自動幫我們生成對應(yīng)的數(shù)據(jù)庫操作墓毒。當(dāng)然吓揪,我們也可以自己實現(xiàn)更多的自定義的數(shù)據(jù)庫操作,在此不贅述所计。
package com.jiangzhuolin.userservice.repository;
import com.jiangzhuolin.userservice.bean.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, String> {
User save(User user);
User findUserById(long id);
@Override
Page<User> findAll(Pageable pageable);
}
(5) 創(chuàng)建對外服務(wù)響應(yīng)類 controller
直接可以對我們的 repository 進(jìn)行注入從而方便的使用其中的數(shù)據(jù)操作方法柠辞。
package com.jiangzhuolin.userservice.controller;
import com.jiangzhuolin.userservice.bean.User;
import com.jiangzhuolin.userservice.repository.UserRepository;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Resource
private UserRepository userRepository;
@RequestMapping(value = "/save", method = RequestMethod.POST)
@ResponseBody
public User save(User user) {
User savedUser = userRepository.save(user);
savedUser.setPassword("");
return savedUser;
}
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@ResponseBody
public User getOne(@PathVariable("id") long id) {
User user = userRepository.findUserById(id);
user.setPassword("");
return user;
}
@RequestMapping(value = "/all")
@ResponseBody
public List<User> findAll() {
List<User> userList = userRepository.findAll();
for (User user : userList) {
user.setPassword("");
}
return userList;
}
}
(6) 打包程序
打包方法請參考之前的文章,此處不贅述主胧。
(7) 運行 client 并注冊
將打包后的程序上傳到服務(wù)器叭首,此處我上傳的我的兩臺虛擬機(jī)環(huán)境 192.168.174.200/201习勤,并執(zhí)行如下命令運行。
[root@hadoop1 user-service]# pwd
/data/user-service
[root@hadoop2 user-service]# nohup java -jar user-service-0.0.1-SNAPSHOT.jar &
注:
- 確保之前的 Eureka Server 集群是正常運行的
(8) 測試驗證
- 查看數(shù)據(jù)庫
當(dāng)我們服務(wù)正常啟動以后查看我們的數(shù)據(jù)庫 microservice焙格,發(fā)現(xiàn)已經(jīng)自動生成了 user图毕,與 hibernate_sequence 兩張表。其中 user 對應(yīng)項目中創(chuàng)建的實體類 User眷唉,此表現(xiàn)在為空予颤,另一個表 hibernate_sequence 是用來控制自動生成主鍵的 ID 的序號用的,初始狀態(tài)為 1冬阳。
mysql> show tables;
+------------------------+
| Tables_in_microservice |
+------------------------+
| hibernate_sequence |
| user |
+------------------------+
2 rows in set (0.03 sec)
mysql> select * from user;
Empty set
mysql> select * from hibernate_sequence;
+----------+
| next_val |
+----------+
| 1 |
+----------+
1 row in set (0.02 sec)
- 調(diào)用接口
使用 postman 調(diào)用 http://192.168.174.200:8080/user/save 接口蛤虐,向數(shù)據(jù)庫中寫入一個用戶。
使用 postman 調(diào)用 http://192.168.174.201:8080/user/1 接口肝陪,從數(shù)據(jù)庫中讀取剛才寫入的用戶笆焰。
使用 postman 調(diào)用 http://192.168.174.200:8080/user/all 接口,從數(shù)據(jù)庫中讀取所有用戶數(shù)據(jù)见坑。
可以看到嚷掠,之前在 192.168.174.200/201 上運行的兩個 user-service 服務(wù)都已經(jīng)正常運行并能夠正常調(diào)用接口。
- 查看 Eureka Server 上的注冊情況
可以看到 user-service 的兩個服務(wù)都已經(jīng)被注冊到 Eureka Server 集群中荞驴。
- 可以看到 user-service 里有一個 windows 的服務(wù)不皆,這是我本地的服務(wù),實際上我已經(jīng)停掉了熊楼。另外之前運行過的 client-test 的服務(wù)也都停止了霹娄,可是為什么這些應(yīng)用還在 Eureka 后臺顯示呢,上方還有一行紅字鲫骗,這代表什么呢?
其實犬耻,這就是與上篇中提到的 Eureka 的自我保護(hù)機(jī)制有關(guān),這個參數(shù)默認(rèn)為 true 表示打開: eureka.server.enable-self-preservation: true. 至于自我保護(hù)的詳情执泰,此處不贅述枕磁,可參考上篇文章或在網(wǎng)上搜索。
三术吝、服務(wù)消費之 Ribbon
3.1 Ribbon 是干嘛的
從上面的服務(wù)調(diào)用可能發(fā)現(xiàn)计济,我們服務(wù)調(diào)用不就是使用接口的 URL 地址調(diào)用就行了嗎 (比如使用 http client)?最多就是需要參數(shù)的再傳點參數(shù)排苍?為什么還要把服務(wù)調(diào)用單獨拎出來說一說沦寂?是吃飽了沒事干嗎?是的淘衙,我吃飽了传藏,但事還是挺多的。
從上面我們的服務(wù)調(diào)用來看,好像確實沒什么問題毯侦,比如我們的物流系統(tǒng)需要我們的用戶信息了西壮,那我們在物流系統(tǒng)中使用 URL 地址調(diào)用不就可以了嗎?不過叫惊,事情他就怕一個大字款青。比如,我們的用戶服務(wù)使用的系統(tǒng)很多霍狰,調(diào)用頻率也很高抡草,我們想做一些負(fù)載均衡,把訪問壓力分散開來蔗坯,于是我們在多個服務(wù)器上啟動了多個用戶服務(wù)的進(jìn)程(此處假如啟了 100 個)康震。那么問題來了,這 100 個里宾濒,你選哪一個服務(wù)來用腿短?什么,你說隨緣挑選绘梦,那你可真是太棒了橘忱。
有人可能會說,用 nginx 做個反向代理并負(fù)載均衡呀卸奉《鄢希可是,這里有個問題榄棵,那就是沒有服務(wù)的可用性檢測凝颇,如果你拿到的是一個異常宕掉的服務(wù),那么調(diào)用就出問題了...
好了好了疹鳄,不多廢話了拧略,上面的這些問題,服務(wù)有效性瘪弓、負(fù)載均衡在 Spring Cloud 里也有解決方案垫蛆,服務(wù)有效性依靠 Eureka 的心跳機(jī)制來解決,而負(fù)載均衡則是基于 Ribbon 來實現(xiàn)杠茬。Ribbon 是一個用于對 HTTP/TCP 請求進(jìn)行控制的負(fù)載均衡客戶端月褥,官網(wǎng)地址:
http://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html
Ribbon 的詳情此處不表弛随,如有興趣瓢喉,可向往官網(wǎng)查閱。Ribbon 大概的思路是舀透,先從 Eureka Server 端獲取已注冊的可用服務(wù)列表到客戶端栓票,然后根據(jù) Ribbon 實現(xiàn)的各種策略(如輪詢,隨機(jī),過濾無效鏈接等)對服務(wù)進(jìn)行挑選(負(fù)載均衡)走贪。
3.2 如何在 Eureka Client 使用 Ribbon
示例:現(xiàn)在我們的物流服務(wù)中需要調(diào)用用戶的信用用以完善部分的物流信息與流程佛猛。那么,我們怎么從前面的用戶服務(wù)中調(diào)用用戶信息呢坠狡?
第一步继找,創(chuàng)建一個物流的 Eureka Client 服務(wù)
此處過程省略,請參考前文 Eureka Client 的創(chuàng)建與注冊逃沿。第二步婴渡,在物流服務(wù)中調(diào)用 user-service 服務(wù)
LogisticsController.java 類如下:
package com.jiangzhuolin.logisticsservice.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping(value = "/logistics/")
public class LogisticsController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/message")
public String message(String userId) {
String response = restTemplate.getForObject("http://USER-SERVICE/user/{userId}", String.class, userId);
return response;
}
}
需要注意的是,RestTemplate 類在 pring boot 1.4 版本以后便不再支持自動注入了凯亮,需要進(jìn)行聲明边臼。參考:
https://stackoverflow.com/questions/28024942/how-to-autowire-resttemplate-using-annotations
此處由于使用的是 2.02 的 spring boot,因此假消,在啟動類中作了一個簡單的聲明
package com.jiangzhuolin.logisticsservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class LogisticsServiceApplication {
public static void main(String[] args) {
SpringApplication.run(LogisticsServiceApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
其中 @Bean 表示聲明一個 Bean 對象柠并,而 @LoadBalanced 表示聲明一個基于 Ribbon 的負(fù)載均衡。
- 第三步富拗,運行程序
此處臼予,我為項目配置監(jiān)聽端口為 8070 并在本機(jī)的 IDEA 中直接運行,然后啃沪,在瀏覽器中調(diào)用 logistics 的接口 http://localhost:8070/logistics/message?userId=1
瘟栖,得到結(jié)果如下所示:
至此,我們基于 Ribbon 的服務(wù)消費示例就完成了谅阿,是不是異常地簡單呢~
三半哟、總結(jié)
本文淺顯地聊了服務(wù)拆分及服務(wù)消費一些場景和使用示例。雖然篇幅挺長签餐,但實際都是粗淺的表面寓涨,僅限于使用層面。不過氯檐,人類的歷史來看戒良,不都是從淺入深的過程。從樹上掉下蘋果撿起來就啃到發(fā)現(xiàn)萬有引力歷經(jīng)不少年頭冠摄,所以糯崎,先學(xué)怎么用沒毛病。扯遠(yuǎn)了河泳,扯遠(yuǎn)了...
由此沃呢,我們也看出,Spring Cloud 依托于 Spring 的大而全的套件拆挥,基本已經(jīng)幫我們實現(xiàn)了很多的造輪子工作薄霜,甚至不僅造好了輪子,連車架都造好了,我們只需要上車就走惰瓜,奔向那詩與遠(yuǎn)方...
對不起否副,串場了,告辭告辭~
本文項目源碼地址:
user-service: https://github.com/jiangzhuolin/user-service
logistics-service: https://github.com/jiangzhuolin/logistics-service