順便給大家推薦一個(gè)Java技術(shù)交流群:908676731,里面會(huì)分享一些資深架構(gòu)師錄制的視頻資料:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多!
創(chuàng)新互聯(lián)建站長(zhǎng)期為成百上千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開(kāi)放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為定日企業(yè)提供專業(yè)的網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì),定日網(wǎng)站改版等技術(shù)服務(wù)。擁有10余年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開(kāi)發(fā)。
假設(shè)有2個(gè)微服務(wù)A和B分別在端點(diǎn)http:// localhost:8181 /和http:// localhost:8282 /上運(yùn)行,如果想要在A服務(wù)中調(diào)用B服務(wù),那么我們需要在A服務(wù)中鍵入B服務(wù)的url,這個(gè)url是負(fù)載均衡器分配給我們的,包括負(fù)載平衡后的IP地址,那么很顯然,B服務(wù)與這個(gè)URL硬編碼耦合在一起了,如果我們使用了服務(wù)自動(dòng)注冊(cè)機(jī)制,就可以使用B服務(wù)的邏輯ID,而不是使用特定IP地址和端口號(hào)來(lái)調(diào)用服務(wù)。
我們可以使用Netflix Eureka Server創(chuàng)建Service Registry服務(wù)器,并將我們的微服務(wù)同時(shí)作為Eureka客戶端,這樣一旦我們啟動(dòng)微服務(wù),它將自動(dòng)使用邏輯服務(wù)ID向Eureka Server注冊(cè)。然后,其他微服務(wù)(同樣也是Eureka客戶端)就可以使用服邏輯務(wù)ID來(lái)調(diào)用REST端點(diǎn)服務(wù)了。
Spring Cloud使用Load Balanced RestTemplate創(chuàng)建Service Registry并發(fā)現(xiàn)其他服務(wù)變得非常容易。
除了使用Netflix Eureka Server作為服務(wù)發(fā)現(xiàn),也可以使用Zookeeper,但是根據(jù)CAP定理,在需要P網(wǎng)絡(luò)分區(qū)容忍性情況下,強(qiáng)一致性C和高可用性A只能選擇一個(gè),Zookeeper是屬于CP,而Eureka是屬于AP,在服務(wù)發(fā)現(xiàn)方面,高可用性才是更重要,否則無(wú)法完成服務(wù)之間調(diào)用,而服務(wù)信息是否一致則不是最重要,A服務(wù)發(fā)現(xiàn)B服務(wù)時(shí),B服務(wù)信息沒(méi)有及時(shí)更新,可能發(fā)生調(diào)用錯(cuò)誤,但是調(diào)用錯(cuò)誤總比無(wú)法連接到服務(wù)注冊(cè)中心要強(qiáng)。否則,服務(wù)注冊(cè)中心就成為整個(gè)系統(tǒng)的單點(diǎn)故障,存在極大的單點(diǎn)風(fēng)險(xiǎn),這是我們?yōu)槭裁葱枰植际较到y(tǒng)的首要原因。
讓我們使用Netflix Eureka創(chuàng)建一個(gè)Service Registry,它只是一個(gè)帶有Eureka Server啟動(dòng)器的SpringBoot應(yīng)用程序。
使用Intellij的Idea開(kāi)發(fā)工具是非常容易啟動(dòng)Spring cloud的:
可以從https://start.spring.io/網(wǎng)址,選擇相應(yīng)組件即可。
由于我們需要建立一個(gè)注冊(cè)服務(wù)器,因此選擇Eureka Server組件即可,通過(guò)這些自動(dòng)工具實(shí)際上是能自動(dòng)生成Maven的配置:
????
????
我們需要給SpringBoot啟動(dòng)類添加@EnableEurekaServer注釋,以使我們的SpringBoot應(yīng)用程序成為基于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);
????}
}
默認(rèn)情況下,每個(gè)Eureka服務(wù)器也是Eureka客戶端,客戶端一定會(huì)需要一個(gè)服務(wù)器URL來(lái)定位,否則就會(huì)不斷報(bào)錯(cuò),由于我們只有一個(gè)Eureka Server節(jié)點(diǎn)(獨(dú)立模式),我們將通過(guò)在application.properties文件中配置以下屬性來(lái)禁用此客戶端行為。
SpringCloud有properties和YAML兩種配置方式,這兩種配置方式其實(shí)只是形式不同,properties配置信息格式是a.b.c,而YAML則是a:b:c:,兩者本質(zhì)是一樣的,只需要其中一個(gè)即可,這里以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
現(xiàn)在運(yùn)行ServiceRegistryApplication并訪問(wèn)http:// localhost:1111,如果不能訪問(wèn),說(shuō)明沒(méi)有正常啟動(dòng),請(qǐng)檢查三個(gè)環(huán)節(jié):pom.xml是否配置正確?需要Eureka和配置
SpringBoot的注釋@EnableEurekaServer是否增加了?
最后,application.properties是否配置?
SpringCloud其實(shí)非常簡(jiǎn)單,約定大于配置,默認(rèn)只要配置服務(wù)器端口就可以了,然后是一條注釋@EnableEurekaServer,就能啟動(dòng)Eurek服務(wù)器了。
服務(wù)器準(zhǔn)備好后,我們就要準(zhǔn)備服務(wù)生產(chǎn)者,向服務(wù)器里面注冊(cè)自己,服務(wù)消費(fèi)者則是從服務(wù)器中發(fā)現(xiàn)注冊(cè)的服務(wù)然后調(diào)用。
服務(wù)生產(chǎn)者其實(shí)首先是Eureka的客戶端,生產(chǎn)者將自己注冊(cè)到前面啟動(dòng)的服務(wù)器當(dāng)中,引如果是idea的導(dǎo)航,選擇CloudDiscovery的EurekaDiscovery,如果是 Maven則引入包依賴是:
????
????
這樣,spring-cloud-starter-netflix-eureka-client這個(gè)jar包就放入我們系統(tǒng)的classpath,為了能夠正常使用這個(gè)jar包,還需要配置,只需要在application.properties中配置eureka.client.service-url.defaultZone屬性即可自動(dòng)注冊(cè)Eureka Server:
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
當(dāng)我們的服務(wù)在Eureka Server注冊(cè)時(shí),它會(huì)持續(xù)發(fā)送一定時(shí)間間隔的心跳。如果Eureka服務(wù)器沒(méi)有從任何服務(wù)的實(shí)例接收到心跳,它將認(rèn)為這個(gè)服務(wù)實(shí)例已經(jīng)關(guān)閉并從自己的池中剔除它。
以上是服務(wù)生產(chǎn)者注冊(cè)服務(wù)的過(guò)程,比較簡(jiǎn)單,為了使我們的服務(wù)生產(chǎn)者能的演示代碼夠運(yùn)行起來(lái),我們還需要新建一個(gè)服務(wù)生產(chǎn)者代碼:
@RestController
public class ProducerService {
@GetMapping("/pengproducer")
public String sayHello(){
return "hello world";
}
}
這段代碼是將服務(wù)暴露成RESTful接口,@RestController是聲明Rest接口,/pengproducer是REST的訪問(wèn)url,通過(guò)get方式能夠獲得字符串:hello world
因?yàn)镽EST屬于WEB的一種接口,因此需要在pom.xml中引入Web包:
org.springframework.boot
spring-boot-starter-web
然后在application.properties中加入有關(guān)REST接口的配置:
spring.application.name=PengProducerService
server.port=2111
指定我們的生產(chǎn)者服務(wù)的名稱是PengProducerService,REST端口開(kāi)在2111。
現(xiàn)在可以在idea中啟動(dòng)我們的應(yīng)用了,這樣我們啟動(dòng)這個(gè)項(xiàng)目,就可以在http://127.0.0.1:2111/ 訪問(wèn)這個(gè)REST服務(wù)。同時(shí),因?yàn)槲覀冎耙呀?jīng)啟動(dòng)了注冊(cè)服務(wù)器,訪問(wèn)http://localhost:1111/你會(huì)發(fā)現(xiàn)PengProducerService出現(xiàn)在服務(wù)列表中:
上面啟動(dòng)應(yīng)用服務(wù)是在idea編輯器中,我們還可以通過(guò)命令行啟動(dòng)我們的服務(wù)生產(chǎn)者:
java -jar -Dserver.port=2112 producer-0.0.1-SNAPSHOT.jar
這個(gè)是在端口2112開(kāi)啟我們的服務(wù)端點(diǎn)了?,F(xiàn)在再問(wèn)http://localhost:1111/,你會(huì)看到可用節(jié)點(diǎn)Availability Zones下面已經(jīng)從(1)變?yōu)?2),現(xiàn)在我們的服務(wù)生產(chǎn)者已經(jīng)有兩個(gè)實(shí)例在運(yùn)行,當(dāng)服務(wù)的消費(fèi)者訪問(wèn)這個(gè)兩個(gè)實(shí)例時(shí),它可以根據(jù)負(fù)載平衡策略比如輪詢?cè)L問(wèn)其中一個(gè)服務(wù)生產(chǎn)者實(shí)例。
總結(jié)一下,為了讓服務(wù)生產(chǎn)者注冊(cè)到Euraka服務(wù)器中,只需要兩個(gè)步驟:
請(qǐng)注意,spring-cloud-starter-netflix-eureka-client包是Spring Cloud升級(jí)后最新的包名,原來(lái)是spring-cloud-starter-eureka,里面沒(méi)有netflix,這是過(guò)去版本,Spring Boot 1.5以后都是加入了netflix的,見(jiàn)Spring Cloud Edgware Release Notes
另外,這里不需要在SpringBoot主代碼中再加入@enablediscoveryclient 或 @enableeurekaclient,只要eureka的client包在maven中配置,也就會(huì)出現(xiàn)在系統(tǒng)的classpath中,這樣就會(huì)默認(rèn)自動(dòng)注冊(cè)到eureka服務(wù)器中了。
這部分×××:百度網(wǎng)盤。
下面我們準(zhǔn)備訪問(wèn)這個(gè)服務(wù)生產(chǎn)者PengProducerService的消費(fèi)者服務(wù):
上個(gè)章節(jié)我們已經(jīng)啟動(dòng)了兩個(gè)服務(wù)生產(chǎn)者實(shí)例,如何通過(guò)負(fù)載平衡從兩個(gè)中選擇一個(gè)訪問(wèn)呢?這時(shí)就需要Ribbon,為了使用Ribbon,我們需要使用@LoadBalanced元注解,那么這個(gè)注解放在哪里呢?一般有兩個(gè)DiscoveryClient 和 RestTemplate,這兩個(gè)的區(qū)別是:
1. DiscoveryClient可以獲得服務(wù)提供者(生產(chǎn)者)的多個(gè)實(shí)例集合,能讓你手工決定選擇哪個(gè)實(shí)例,這里負(fù)載平衡的策略比如round robin輪詢就不會(huì)派上,實(shí)際就沒(méi)有使用Ribbon:
List instances=discoveryClient.getInstances("PengProducerService");
ServiceInstance serviceInstance=instances.get(0);
2.RestTemplate則是使用Ribbon的負(fù)載平衡策略,使用@LoadBalanced注釋resttemplate并使用zuul代理服務(wù)器作為邊緣服務(wù)器。那么對(duì)zuul邊緣服務(wù)器的任何請(qǐng)求將默認(rèn)使用Ribbon進(jìn)行負(fù)載平衡,而resttemplate將以循環(huán)方式路由請(qǐng)求。這部分代碼如下:
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 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是自動(dòng)注射進(jìn)這個(gè)控制器,在這控制器,我們調(diào)用了服務(wù)生產(chǎn)者h(yuǎn)ttp://PengProducerService/pengproducer,然后獲得其結(jié)構(gòu)。
這個(gè)控制器的調(diào)用我們可以在SpringBoot啟動(dòng)函數(shù)里調(diào)用:
@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是標(biāo)注在RestTemplate上,而RestTemplate是被注入到ConsumerService中的,這樣通過(guò)調(diào)用RestTemplate對(duì)象實(shí)際就是獲得負(fù)載平衡后的服務(wù)實(shí)例。這個(gè)可以通過(guò)我們的服務(wù)提供者里面輸出hashcode來(lái)分辨出來(lái),啟動(dòng)兩個(gè)服務(wù)提供者實(shí)例,每次運(yùn)行ConsumerService,應(yīng)該是依次打印出不同的hashcode:
hello world1246528978 called in callProducerfinal result RestTemplate=hello world1246528978
再次運(yùn)行結(jié)果:
hello world1179769159 called in callProducerfinal result RestTemplate=hello world1179769159
hellow world后面的哈希值不同,可見(jiàn)是來(lái)自不同的服務(wù)提供者實(shí)例。
如果系統(tǒng)基于https進(jìn)行負(fù)載平衡,那么只需要兩個(gè)步驟:
1.application.properties中激活ribbon的https:
ribbon.IsSecure=true
2.代碼中RestTemplate初始化時(shí)傳入ClientHttpRequestFactory對(duì)象:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
CloseableHttpClient httpClient = HttpClientUtil.getHttpClient();
HttpComponentsClientHttpRequestFactory clientrequestFactory = new HttpComponentsClientHttpRequestFactory();
clientrequestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(clientrequestFactory);
return restTemplate;
}
這部分×××:百度網(wǎng)盤
上篇是使用Ribbon實(shí)現(xiàn)對(duì)多個(gè)服務(wù)生產(chǎn)者實(shí)例使用負(fù)載平衡的方式進(jìn)行消費(fèi),在調(diào)用服務(wù)生產(chǎn)者時(shí),返回的是字符串類型,如果返回是各種自己定義的對(duì)象,這些對(duì)象傳遞到消費(fèi)端是通過(guò)JSON方式,那么我們的消費(fèi)者需要使用Feign來(lái)訪問(wèn)各種Json對(duì)象。
需要注意的是:Feign = Eureka +Ribbon + RestTemplate,也就是說(shuō),使用Feign訪問(wèn)服務(wù)生產(chǎn)者,無(wú)需前面章節(jié)那么關(guān)于負(fù)載平衡的代碼了,前面我們使用RestTemplate進(jìn)行負(fù)載平衡訪問(wèn),代碼還是挺復(fù)雜
現(xiàn)在我們開(kāi)始Feign的實(shí)現(xiàn):首先我們?cè)诜?wù)的生產(chǎn)者那邊進(jìn)行修改,讓我們生產(chǎn)者項(xiàng)目變得接近實(shí)戰(zhàn)中項(xiàng)目,增加領(lǐng)域?qū)?、服?wù)層和持久層。
假設(shè)新增Article領(lǐng)域模型對(duì)象,我們就需要倉(cāng)儲(chǔ)保存,這里我們使用Spring默認(rèn)約定,使用JPA訪問(wèn)h3數(shù)據(jù)庫(kù),將Article通過(guò)JPA保存到h3數(shù)據(jù)庫(kù)中:
要啟用JPA和h3數(shù)據(jù)庫(kù),首先只要配置pom.xml:
org.springframework.boot
spring-boot-starter-data-jpa
com.h3database
h3
runtime
Article領(lǐng)域模型對(duì)象作為需要持久的實(shí)體對(duì)象:配置實(shí)體@Entity和@Id主鍵即可:
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String body;
private Date startDate;
然后我們建立一個(gè)空的Article倉(cāng)儲(chǔ)接口即可:
@Repository
public interface ArticleRep extends JpaRepository {
}
這樣,關(guān)于Article的CRUD實(shí)現(xiàn)就已經(jīng)有了,不需要自己再編寫(xiě)任何SQL語(yǔ)句。這樣我們編寫(xiě)一個(gè)Service就可以提供Article對(duì)象的CRUD方法,這里只編寫(xiě)插入和查詢批量?jī)蓚€(gè)方法:
@Service
public class ArticleService {
@Autowired
ArticleRep articleRep;
public List getAllArticles(){
return articleRep.findAll();
}
public void insertArticle(Article article){
articleRep.save(article);
}
}
我們?cè)赗EST接口中暴露這兩種方法:
2. post /article是新增
@RestController
public class ProducerService {
@Autowired
ArticleService articleService;
@GetMapping("/articles")
public List getAllArticles(){
return articleService.getAllArticles();
}
@GetMapping("/article")
public void publishArticle(@RequestBody Article article){
articleService.insertArticle(article);
}
上面服務(wù)的生產(chǎn)者提供了兩個(gè)REST url,我們?cè)谙M(fèi)者這邊使用/articles以獲得所有文章:
@FeignClient(name="PengProducerService")
public interface ConsumerService {
@GetMapping("/articles")
List getAllArticles();
}
這是我們消費(fèi)者的服務(wù),調(diào)用生產(chǎn)者 /articles,這是一個(gè)接口,無(wú)需實(shí)現(xiàn),注意需要標(biāo)注FeignClient,其中寫(xiě)入name或value微服務(wù)生產(chǎn)者的application.properties配置:
spring.application.name=PengProducerService
當(dāng)然,這里會(huì)直接耦合PengProducerService這個(gè)名稱,我們以后可以通過(guò)配置服務(wù)器更改,這是后話。
然后需要在應(yīng)用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我們調(diào)用了前面接口ConsumerService,而ConsumerService則通過(guò)負(fù)載平衡調(diào)用另外一個(gè)生產(chǎn)者微服務(wù),如果我們給那個(gè)生產(chǎn)者服務(wù)加入一些Articles數(shù)據(jù),則這里就能返回這些數(shù)據(jù):
#############all articles ok[com.example.feignconsumer.domain.Article@62b475e2, com.example.feignconsumer.domain.Article@e9474f]
說(shuō)明調(diào)用成功。
在調(diào)試過(guò)程中,曾經(jīng)出現(xiàn)錯(cuò)誤:
Load balancer does not have available server for client:PengProducerService
經(jīng)常排查是由于生產(chǎn)者項(xiàng)目中pom.xml導(dǎo)入的是spring-cloud-starter-netflix-eureka-client,改為pring-cloud-starter-netflix-eureka-server就可以了,這是SpringBoot 2.0發(fā)現(xiàn)的一個(gè)問(wèn)題。
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
本章的代碼下載:百度網(wǎng)盤
通過(guò)這個(gè)項(xiàng)目學(xué)習(xí),我們?nèi)缤Q絲剝繭層層搞清楚了Spring Cloud的微服務(wù)之間同步調(diào)用方式,發(fā)現(xiàn)基于REST/JSON的調(diào)用代碼最少,也是最方便,F(xiàn)eign封裝了Ribbon負(fù)載平衡和Eureka服務(wù)器訪問(wèn)以及REST格式處理。
順便給大家推薦一個(gè)Java技術(shù)交流群:908676731,里面會(huì)分享一些資深架構(gòu)師錄制的視頻資料:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多!