SpringCloud服務發現註冊Eureka +Ribbon + Feign
- 什麽是服務註冊和發現?
- 基於Eureka的註冊服務器
- 服務生產者
- 結合Ribbon服務消費者
- 結合Feign的服務生產者和消費者
什麽是服務註冊和發現
假設有2個微服務A和B分別在端點http:// localhost:8181 /和http:// localhost:8282 /上運行,如果想要在A服務中調用B服務,那麽我們需要在A服務中鍵入B服務的url,這個url是負載均衡器分配給我們的,包括負載平衡後的IP地址,那麽很顯然,B服務與這個URL硬編碼耦合在一起了,如果我們使用了服務自動註冊機制,就可以使用B服務的邏輯ID,而不是使用特定IP地址和端口號來調用服務。
我們可以使用Netflix Eureka Server創建Service Registry服務器,並將我們的微服務同時作為Eureka客戶端,這樣一旦我們啟動微服務,它將自動使用邏輯服務ID向Eureka Server註冊。然後,其他微服務(同樣也是Eureka客戶端)就可以使用服邏輯務ID來調用REST端點服務了。
Spring Cloud使用Load Balanced RestTemplate創建Service Registry並發現其他服務變得非常容易。
除了使用Netflix Eureka Server作為服務發現,也可以使用Zookeeper,但是根據CAP定理,在需要P網絡分區容忍性情況下,強一致性C和高可用性A只能選擇一個,Zookeeper是屬於CP,而Eureka是屬於AP,在服務發現方面,高可用性才是更重要,否則無法完成服務之間調用,而服務信息是否一致則不是最重要,A服務發現B服務時,B服務信息沒有及時更新,可能發生調用錯誤,但是調用錯誤總比無法連接到服務註冊中心要強。否則,服務註冊中心就成為整個系統的單點故障,存在極大的單點風險,這是我們為什麽需要分布式系統的首要原因。
基於Eureka的註冊服務器
讓我們使用Netflix Eureka創建一個Service Registry,它只是一個帶有Eureka Server啟動器的SpringBoot應用程序。
使用Intellij的Idea開發工具是非常容易啟動Spring cloud的:
可以從https://start.spring.io/網址,選擇相應組件即可。
由於我們需要建立一個註冊服務器,因此選擇Eureka Server組件即可,通過這些自動工具實際上是能自動生成Maven的配置:
<dependency>
????<groupId>org.springframework.cloud</groupId>
????<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
我們需要給SpringBoot啟動類添加@EnableEurekaServer註釋,以使我們的SpringBoot應用程序成為基於Eureka Server的Service Registry。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class ServiceRegistryApplication {
????public static void main(String[] args) {
????????SpringApplication.run(ServiceRegistryApplication.class, args);
????}
}
默認情況下,每個Eureka服務器也是Eureka客戶端,客戶端一定會需要一個服務器URL來定位,否則就會不斷報錯,由於我們只有一個Eureka Server節點(獨立模式),我們將通過在application.properties文件中配置以下屬性來禁用此客戶端行為。
SpringCloud有properties和YAML兩種配置方式,這兩種配置方式其實只是形式不同,properties配置信息格式是a.b.c,而YAML則是a:b:c:,兩者本質是一樣的,只需要其中一個即可,這裏以properties為案例:
spring.application.name=jdon-eureka-server
server.port=1111
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
現在運行ServiceRegistryApplication並訪問http:// localhost:1111,如果不能訪問,說明沒有正常啟動,請檢查三個環節:pom.xml是否配置正確?需要Eureka和配置
SpringBoot的註釋@EnableEurekaServer是否增加了?
最後,application.properties是否配置?
SpringCloud其實非常簡單,約定大於配置,默認只要配置服務器端口就可以了,然後是一條註釋@EnableEurekaServer,就能啟動Eurek服務器了。
服務器準備好後,我們就要準備服務生產者,向服務器裏面註冊自己,服務消費者則是從服務器中發現註冊的服務然後調用。
服務生產者
服務生產者其實首先是Eureka的客戶端,生產者將自己註冊到前面啟動的服務器當中,引如果是idea的導航,選擇CloudDiscovery的EurekaDiscovery,如果是 Maven則引入包依賴是:
<dependency>
????<groupId>org.springframework.cloud</groupId>
????<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
這樣,spring-cloud-starter-netflix-eureka-client這個jar包就放入我們系統的classpath,為了能夠正常使用這個jar包,還需要配置,只需要在application.properties中配置eureka.client.service-url.defaultZone屬性即可自動註冊Eureka Server:
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
當我們的服務在Eureka Server註冊時,它會持續發送一定時間間隔的心跳。如果Eureka服務器沒有從任何服務的實例接收到心跳,它將認為這個服務實例已經關閉並從自己的池中剔除它。
以上是服務生產者註冊服務的過程,比較簡單,為了使我們的服務生產者能的演示代碼夠運行起來,我們還需要新建一個服務生產者代碼:
@RestController
public class ProducerService {
@GetMapping("/pengproducer")
public String sayHello(){
return "hello world";
}
}
這段代碼是將服務暴露成RESTful接口,@RestController是聲明Rest接口,/pengproducer是REST的訪問url,通過get方式能夠獲得字符串:hello world
因為REST屬於WEB的一種接口,因此需要在pom.xml中引入Web包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然後在application.properties中加入有關REST接口的配置:
spring.application.name=PengProducerService
server.port=2111
指定我們的生產者服務的名稱是PengProducerService,REST端口開在2111。
現在可以在idea中啟動我們的應用了,這樣我們啟動這個項目,就可以在http://127.0.0.1:2111/ 訪問這個REST服務。同時,因為我們之前已經啟動了註冊服務器,訪問http://localhost:1111/你會發現PengProducerService出現在服務列表中:
上面啟動應用服務是在idea編輯器中,我們還可以通過命令行啟動我們的服務生產者:
java -jar -Dserver.port=2112 producer-0.0.1-SNAPSHOT.jar
這個是在端口2112開啟我們的服務端點了。現在再問http://localhost:1111/,你會看到可用節點Availability Zones下面已經從(1)變為(2),現在我們的服務生產者已經有兩個實例在運行,當服務的消費者訪問這個兩個實例時,它可以根據負載平衡策略比如輪詢訪問其中一個服務生產者實例。
總結一下,為了讓服務生產者註冊到Euraka服務器中,只需要兩個步驟:
- 1. 引入spring-cloud-starter-netflix-eureka-client包
- 2. 配置Eurake服務器的地址
請註意,spring-cloud-starter-netflix-eureka-client包是Spring Cloud升級後最新的包名,原來是spring-cloud-starter-eureka,裏面沒有netflix,這是過去版本,Spring Boot 1.5以後都是加入了netflix的,見Spring Cloud Edgware Release Notes
另外,這裏不需要在SpringBoot主代碼中再加入@enablediscoveryclient 或 @enableeurekaclient,只要eureka的client包在maven中配置,也就會出現在系統的classpath中,這樣就會默認自動註冊到eureka服務器中了。
這部分×××:百度網盤。
下面我們準備訪問這個服務生產者PengProducerService的消費者服務:
結合Ribbon的服務消費者
上個章節我們已經啟動了兩個服務生產者實例,如何通過負載平衡從兩個中選擇一個訪問呢?這時就需要Ribbon,為了使用Ribbon,我們需要使用@LoadBalanced元註解,那麽這個註解放在哪裏呢?一般有兩個DiscoveryClient 和 RestTemplate,這兩個的區別是:
1. DiscoveryClient可以獲得服務提供者(生產者)的多個實例集合,能讓你手工決定選擇哪個實例,這裏負載平衡的策略比如round robin輪詢就不會派上,實際就沒有使用Ribbon:
List<ServiceInstance> instances=discoveryClient.getInstances("PengProducerService");
ServiceInstance serviceInstance=instances.get(0);
2.RestTemplate則是使用Ribbon的負載平衡策略,使用@LoadBalanced註釋resttemplate並使用zuul代理服務器作為邊緣服務器。那麽對zuul邊緣服務器的任何請求將默認使用Ribbon進行負載平衡,而resttemplate將以循環方式路由請求。這部分代碼如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.client.RestTemplate;
@Controller
public class ConsumerService {
@Autowired
private RestTemplate restTemplate;
public String callProducer() {
ResponseEntity<String> result =
this.restTemplate.getForEntity(
"http://PengProducerService/pengproducer",
String.class,
"");
if (result.getStatusCode() == HttpStatus.OK) {
System.out.printf(result.getBody() + " called in callProducer");
return result.getBody();
} else {
System.out.printf(" is it empty");
return " empty ";
}
}
}
RestTemplate是自動註射進這個控制器,在這控制器,我們調用了服務生產者http://PengProducerService/pengproducer,然後獲得其結構。
這個控制器的調用我們可以在SpringBoot啟動函數裏調用:
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(ConsumerApplication
.class, args);
ConsumerService consumerService = ctx.getBean(ConsumerService.class);
System.out.printf("final result RestTemplate=" + consumerService
.callProducer() + " \n");
}
}
註意到@LoadBalanced是標註在RestTemplate上,而RestTemplate是被註入到ConsumerService中的,這樣通過調用RestTemplate對象實際就是獲得負載平衡後的服務實例。這個可以通過我們的服務提供者裏面輸出hashcode來分辨出來,啟動兩個服務提供者實例,每次運行ConsumerService,應該是依次打印出不同的hashcode:
hello world1246528978 called in callProducerfinal result RestTemplate=hello world1246528978
再次運行結果:
hello world1179769159 called in callProducerfinal result RestTemplate=hello world1179769159
hellow world後面的哈希值不同,可見是來自不同的服務提供者實例。
如果系統基於https進行負載平衡,那麽只需要兩個步驟:
1.application.properties中激活ribbon的https:
ribbon.IsSecure=true
2.代碼中RestTemplate初始化時傳入ClientHttpRequestFactory對象:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
CloseableHttpClient httpClient = HttpClientUtil.getHttpClient();
HttpComponentsClientHttpRequestFactory clientrequestFactory = new HttpComponentsClientHttpRequestFactory();
clientrequestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(clientrequestFactory);
return restTemplate;
}
這部分×××:百度網盤
結合Feign的服務生產者和消費者
上篇是使用Ribbon實現對多個服務生產者實例使用負載平衡的方式進行消費,在調用服務生產者時,返回的是字符串類型,如果返回是各種自己定義的對象,這些對象傳遞到消費端是通過JSON方式,那麽我們的消費者需要使用Feign來訪問各種Json對象。
需要註意的是:Feign = Eureka +Ribbon + RestTemplate,也就是說,使用Feign訪問服務生產者,無需前面章節那麽關於負載平衡的代碼了,前面我們使用RestTemplate進行負載平衡訪問,代碼還是挺復雜
現在我們開始Feign的實現:首先我們在服務的生產者那邊進行修改,讓我們生產者項目變得接近實戰中項目,增加領域層、服務層和持久層。
假設新增Article領域模型對象,我們就需要倉儲保存,這裏我們使用Spring默認約定,使用JPA訪問h2數據庫,將Article通過JPA保存到h2數據庫中:
要啟用JPA和h2數據庫,首先只要配置pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Article領域模型對象作為需要持久的實體對象:配置實體@Entity和@Id主鍵即可:
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String body;
private Date startDate;
然後我們建立一個空的Article倉儲接口即可:
@Repository
public interface ArticleRep extends JpaRepository<Article,Long> {
}
這樣,關於Article的CRUD實現就已經有了,不需要自己再編寫任何SQL語句。這樣我們編寫一個Service就可以提供Article對象的CRUD方法,這裏只編寫插入和查詢批量兩個方法:
@Service
public class ArticleService {
@Autowired
ArticleRep articleRep;
public List<Article> getAllArticles(){
return articleRep.findAll();
}
public void insertArticle(Article article){
articleRep.save(article);
}
}
我們在REST接口中暴露這兩種方法:
- 1. get /articles是查詢所有對象
-
2. post /article是新增
@RestController public class ProducerService { @Autowired ArticleService articleService; @GetMapping("/articles") public List<Article> getAllArticles(){ return articleService.getAllArticles(); } @GetMapping("/article") public void publishArticle(@RequestBody Article article){ articleService.insertArticle(article); }
上面服務的生產者提供了兩個REST url,我們在消費者這邊使用/articles以獲得所有文章:
@FeignClient(name="PengProducerService")
public interface ConsumerService {
@GetMapping("/articles")
List<Article> getAllArticles();
}
這是我們消費者的服務,調用生產者 /articles,這是一個接口,無需實現,註意需要標註FeignClient,其中寫入name或value微服務生產者的application.properties配置:
spring.application.name=PengProducerService
當然,這裏會直接耦合PengProducerService這個名稱,我們以後可以通過配置服務器更改,這是後話。
然後需要在應用Application代碼加入@EnableFeignClients:
@SpringBootApplication
@EnableFeignClients
public class FeignconsumerApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(FeignconsumerApplication
.class, args);
ConsumerService consumerService = context.getBean(ConsumerService
.class);
System.out.printf("#############all articles ok" + consumerService
.getAllArticles());
}
在FeignconsumerApplication我們調用了前面接口ConsumerService,而ConsumerService則通過負載平衡調用另外一個生產者微服務,如果我們給那個生產者服務加入一些Articles數據,則這裏就能返回這些數據:
#############all articles ok[[email protected], [email protected]]
說明調用成功。
在調試過程中,曾經出現錯誤:
Load balancer does not have available server for client:PengProducerService
經常排查是由於生產者項目中pom.xml導入的是spring-cloud-starter-netflix-eureka-client,改為pring-cloud-starter-netflix-eureka-server就可以了,這是SpringBoot 2.0發現的一個問題。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
本章的代碼下載:百度網盤
總結
通過這個項目學習,我們如同蠶絲剝繭層層搞清楚了Spring Cloud的微服務之間同步調用方式,發現基於REST/JSON的調用代碼最少,也是最方便,Feign封裝了Ribbon負載平衡和Eureka服務器訪問以及REST格式處理。
SpringCloud服務發現註冊Eureka +Ribbon + Feign