服務消費
在Spring Cloud(一)服務注冊與發(fā)現(xiàn)中,我們提供了服務提供者的工程項目份企,既然有服務的提供方赦肋,那么如何去消費所提供的服務第队,SpringCloud中提供了多種方式來實現(xiàn)傅蹂,該模塊主要就介紹了服務消費,內容包含了服務消費(基礎)算凿,服務消費(Ribbon)份蝴,服務消費(Feign)。
服務消費(基礎)
創(chuàng)建一個服務消費者工程氓轰,命名為:service-consumer
,并在pom.xml
中引入依賴:
<parent>
<groupId>com.wkedong.springcloud</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.39</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
在消費者的pom文件中婚夫,我們依賴了config配置中心(方便配置文件管理),zipkin和sleuth服務追蹤(后續(xù)文章會進行介紹)署鸡,fastjson(在調用接口時使用的是Json傳參),commons-fileupload(上傳文件所需依賴)
在對工程的配置文件案糙,bootstrap.yml
如下:
eureka:
client:
healthcheck:
enabled: true #健康檢查開啟
serviceUrl:
defaultZone: http://localhost:6060/eureka/ #注冊中心服務地址
server:
port: 7010 #當前服務端口
spring:
## 從配置中心讀取文件
cloud:
config:
uri: http://localhost:6010/
label: master
profile: dev
name: service-consumer
application:
name: service-consumer #當前服務ID
zipkin:
base-url: http://localhost:6040 #zipkin服務地址
sleuth:
enabled: true #服務追蹤開啟
sampler:
percentage: 1 #zipkin收集率
創(chuàng)建服務應用的主類ServiceConsumerApplication.java
如下:
/**
* @author wkedong
* 2019/1/5
* Consumer
*/
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(ServiceConsumerApplication.class).web(true).run(args);
}
}
其實在主類中我們注入了restTemplate限嫌,注解
@LoadBalanced
已經默認開啟了負載均衡的配置。
主類中我們初始化RestTemplate时捌,用來真正發(fā)起REST請求怒医。關于RestTemplate的介紹可以查看詳解RestTemplate
創(chuàng)建ConsumerController.java
來實現(xiàn)/testGet
,/testPost
奢讨,/testFile
接口稚叹,如下:
/**
* @author wkedong
* 消費者
* 2019/1/5
*/
@RestController
public class ConsumerController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/testGet")
public String testGet() {
logger.info("===<call testGet>===");
return restTemplate.getForObject("http://service-producer/testGet", String.class);
}
@PostMapping(value = "/testPost")
public String testPost(@RequestParam("name") String name) {
logger.info("===<call testPost>===");
JSONObject json = new JSONObject();
json.put("name", name);
return restTemplate.postForObject("http://service-producer/testPost", json, String.class);
}
@PostMapping(value = "/testFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String testFile(@RequestParam("file") MultipartFile file) {
logger.info("===<call testFile>===");
File path = null;
if (file != null) {
try {
String filePath = "D:\\tempFile";
path = new File(filePath); //判斷文件路徑下的文件夾是否存在,不存在則創(chuàng)建
if (!path.exists()) {
path.mkdirs();
}
File savedFile = new File(filePath + "\\" + file.getOriginalFilename());
boolean isCreateSuccess = savedFile.createNewFile(); // 是否創(chuàng)建文件成功
if (isCreateSuccess) {
//將文件寫入
file.transferTo(savedFile);
}
HttpHeaders headers = new HttpHeaders();
FileSystemResource fileSystemResource = new FileSystemResource(savedFile);
MediaType type = MediaType.parseMediaType("multipart/form-data");
headers.setContentType(type);
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("file", fileSystemResource);
HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(param, headers);
return restTemplate.postForObject("http://service-producer/testFile", files, String.class);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (path != null) {
path.delete();
}
}
}
return "文件有誤";
}
}
- 這里的請求的地址我們使用了魔法值拿诸,在后續(xù)真正的項目實施時扒袖,可以定義一個常量類來統(tǒng)一管理儲存請求地址。
- 文件上傳接口不能直接調用服務端接口傳輸MultipartFile文件亩码,所以做了部分處理季率。
工程至此創(chuàng)建完成了,分別啟動注冊中心描沟,服務提供方飒泻,和該工程,并訪問 http://localhost:7010/testGet 啊掏,會出現(xiàn)以下頁面:
Hello, Spring Cloud! My port is 6070 Get info By Mybatis is {"address":"江蘇南京","birthday":"1994-12-21","name":"wkedong"}
服務消費(Ribbon)
Spring Cloud Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon實現(xiàn)的一套客戶端負載均衡的工具蠢络。它是一個基于HTTP和TCP的客戶端負載均衡器。它可以通過在客戶端中配置ribbonServerList來設置服務端列表去輪詢訪問以達到均衡負載的作用迟蜜。
上面是基礎的使用Spring封裝的RestTemplate來進行服務的消費刹孔,在此基礎上實現(xiàn)負載均衡的配置只需要在pom文件中新增依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
我們將service-consumer
工程拷貝一份改名為service-consumer-ribbon
,配置文件修改為:
eureka:
client:
healthcheck:
enabled: true #健康檢查開啟
serviceUrl:
defaultZone: http://localhost:6060/eureka/ #注冊中心服務地址
server:
port: 7030 #當前服務端口
spring:
## 從配置中心讀取文件
cloud:
config:
uri: http://localhost:6010/
label: master
profile: dev
name: service-consumer-ribbon
application:
name: service-consumer-ribbon #當前服務ID
zipkin:
base-url: http://localhost:6040 #zipkin服務地址
sleuth:
enabled: true #服務追蹤開啟
sampler:
percentage: 1 #zipkin收集率
新建RibbonController.java
實現(xiàn)/testRibbon
接口:
/**
* @author wkedong
* RobbinDemo
* 2019/1/5
*/
@RestController
public class RibbonController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private RestTemplate restTemplate;
@GetMapping(value = "/testRibbon")
public String testRibbon() {
logger.info("===<call testRibbon>===");
//執(zhí)行http請求Producer服務的provide映射地址娜睛,返回的數據為字符串類型
//PRODUCER:提供者(Producer服務)的注冊服務ID
//provide :消費方法
return restTemplate.getForObject("http://service-producer/testRibbon", String.class);
}
}
然后啟動eureka髓霞,config,service-producer畦戒,service-consumer-ribbon訪問
/testRibbon
接口觀察返回數據方库,負載均衡的效果可以通過啟動多個service-producer來進行觀察。
啟動多個服務端之后障斋,調用該接口會發(fā)現(xiàn)端口號在變化纵潦,這就是Ribbon實現(xiàn)的輪值負載均衡機制。
服務消費(Feign)
上面是基礎及實現(xiàn)了負載均衡來使用Spring封裝的RestTemplate來進行服務的消費垃环,下面介紹下利用Feign來進行服務的消費邀层。
Spring Cloud Feign
Spring Cloud Feign是一套基于Netflix Feign實現(xiàn)的聲明式服務調用客戶端。它使得編寫Web服務客戶端變得更加簡單遂庄。
我們只需要通過創(chuàng)建接口并用注解來配置它既可完成對Web服務接口的綁定寥院。它具備可插拔的注解支持,包括Feign注解涛目、JAX-RS注解秸谢。它也支持可插拔的編碼器和解碼器凛澎。
Spring Cloud Feign還擴展了對Spring MVC注解的支持,同時還整合了Ribbon和Eureka來提供均衡負載的HTTP客戶端實現(xiàn)估蹄。
這里繼續(xù)使用之前的服務注冊中心eureka塑煎,配置中心config和服務提供者service-producer,在此基礎上新建工程service-consumer-feign元媚,在pom文件中引用相應依賴:
<parent>
<groupId>com.wkedong.springcloud</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.39</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
主要是多了spring-cloud-starter-feign依賴轧叽,實現(xiàn)feign的操作
在配置文件bootstrap.yml
添加如下配置:
eureka:
client:
healthcheck:
enabled: true #健康檢查開啟
serviceUrl:
defaultZone: http://localhost:6060/eureka/ #注冊中心服務地址
server:
port: 7020 #當前服務端口
spring:
## 從配置中心讀取文件
cloud:
config:
uri: http://localhost:6010/
label: master
profile: dev
name: service-consumer-feign
application:
name: service-consumer-feign #當前服務ID
zipkin:
base-url: http://localhost:6040 #zipkin服務地址
sleuth:
enabled: true #服務追蹤開啟
sampler:
percentage: 1 #zipkin收集率
工程應用主類中追加@EnableFeignClients
注解,聲明為Feign服務應用:
/**
* @author wkedong
*/
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceConsumerFeignApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ServiceConsumerFeignApplication.class).web(true).run(args);
}
@Configuration
class MultipartSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
}
支持文件上傳刊棕,設置feign文件Encoder配置炭晒。
創(chuàng)建一個Feign的客戶端接口定義。使用@FeignClient
注解來指定這個接口所要調用的服務名稱甥角,接口中定義的各個函數使用Spring MVC的注解就可以來綁定服務提供方的REST接口网严,這里綁定service-producer
中的/testFeign
和/testFile
接口:
/**
* @author wkedong
* FeignDemo
* 2019/1/14
*/
@FeignClient("service-producer")
public interface FeignService {
@GetMapping(value = "/testFeign")
String testFeign();
@PostMapping(value = "/testFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String testFeignFile(@RequestPart(value = "file") MultipartFile file);
}
創(chuàng)建一個Controller來調用該客戶端:
/**
* @author wkedong
* FeignDemo
* 2019/1/14
*/
@RestController
public class FeignController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
FeignService feignService;
@GetMapping("/testFeign")
public String testFeign() {
logger.info("===<call testFeign>===");
return feignService.testFeign();
}
@PostMapping(value = "/testFeignFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String testFeignFile(@RequestParam("file") MultipartFile file) {
logger.info("===<call testFeignFile>===");
File path = null;
if (file != null) {
try {
String filePath = "D:\\tempFile";
//判斷文件路徑下的文件夾是否存在,不存在則創(chuàng)建
path = new File(filePath);
if (!path.exists()) {
path.mkdirs();
}
File tempFile = new File(filePath + "\\" + file.getOriginalFilename());
// 是否創(chuàng)建文件成功
boolean isCreateSuccess = tempFile.createNewFile();
if (isCreateSuccess) {
//將文件寫入
file.transferTo(tempFile);
}
DiskFileItem fileItem = (DiskFileItem) new DiskFileItemFactory().createItem("file",
MediaType.MULTIPART_FORM_DATA_VALUE, true, file.getOriginalFilename());
try (InputStream input = new FileInputStream(tempFile); OutputStream os = fileItem.getOutputStream()) {
IOUtils.copy(input, os);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid file: " + e, e);
}
MultipartFile multi = new CommonsMultipartFile(fileItem);
return feignService.testFeignFile(multi);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (path != null) {
path.delete();
}
}
}
return "文件有誤";
}
}
通過Spring Cloud Feign來實現(xiàn)服務調用的方式更加簡單了嗤无,通過@FeignClient
定義的接口聲明我們需要依賴的微服務接口震束。具體使用的時候就跟調用本地方法一樣的進行調用。
由于Feign是基于Ribbon實現(xiàn)的当犯,所以它自帶了客戶端負載均衡功能垢村,也可以通過Ribbon的IRule進行策略擴展。另外嚎卫,F(xiàn)eign還整合的Hystrix來實現(xiàn)服務的容錯保護嘉栓,后續(xù)文章中再對Hysrix進行介紹。
至此代碼編寫結束拓诸,啟動eureka侵佃,config,service-producer奠支,service-consumer-feign訪問/testFeign
接口觀察返回數據馋辈,負載均衡的效果可以通過啟動多個service-producer來進行觀察。
文章目錄:
整體demo的GitHub地址:Spring-Cloud