目錄
6,在consumer-ribbon中使用斷路器Hystrix
7,在consumer-feign中使用斷路器Hystrix
1,創建父工程
菜單欄File->new->project,選擇Java就可以。
一路next,填好工程名字,選好地址,最後finish。
2,創建euraka-server
Euraka是SpringCloud的註冊中心,生產者和消費者都要在註冊中心註冊。
在工程上右鍵,新建module,選擇Srping Initializr。
右側Choose Initializr Service URL選擇Default就可以,next。
填好Project信息,我填的:
Group:com.lkforce.cloud
Artifact:eureka-server
Description:spring cloud學習
Package:eureka.server
其他的都默認。
next,左側選擇Spring Cloud Discovery,中間選擇Eureka Server。
next,填個module名字,finish。
然後就是漫長的創建module和下載jar包的過程,然後我們得到了這樣一個module:
改一下配置文件,在application.property裏:
server.port: 8761
spring.application.name: eurka-server
eureka.instance.hostname: localhost
eureka.client.registerWithEureka: false
eureka.client.fetchRegistry: false
eureka.client.service-url.defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
在啓動類前面加上註解@EnableEurekaServer,否則服務啓動後無法訪問Euraka:
加好註解,啓動服務,在地址欄輸入http://localhost:8761/,可以看到Eureka服務的界面:
其中紅框的部分就是已經註冊在Eureka的服務,現在還沒有。雖然Eureka本身也是一個服務,但因爲之前的配置:
eureka.client.registerWithEureka: false
使得Eureka自己不會把自己註冊上去。
3,創建provider
provider是服務的提供者。
創建一個新的module,還是選擇Spring Initializr,Group和前面的eureka-server寫成一樣的com.lkforce.cloud,Artifact和Name就叫provider-server吧:
next,左側還是選擇Spring Cloud Discovery,中間選擇Eureka Discovery Client,另外,爲了提供對外服務,我們還要加上Web中的Spring Web選項:
next直到完成,我們得到了這樣的module:
然後依然是改application.properties配置文件:
server.port: 8762
spring.application.name: provider-a
eureka.client.service-url.defaultZone: http://localhost:8761/eureka/
還要記得在啓動類前面加註解,這次是@EnableEurekaClient,表示這個其實是一個Eureka的客戶端。
這個時候,此客戶端中不提供服務,也不會請求其他服務,啓動起來之後會立刻關閉。
下面創建一個Spring Cloud的Rest接口,然後啓動類就成了這樣:
package provider.server;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class ProviderServerApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderServerApplication.class, args);
}
@Value("${server.port}")
String port;
@RequestMapping("/hi")
public String home(@RequestParam(value = "name", defaultValue = "this is default value") String name) {
return "hi " + name + " ,i am from port:" + port;
}
}
注意,要讓Rest接口生效,需要添加註解@RestController。
啓動服務,再次訪問http://localhost:8761/:
出現了剛剛啓動的provider服務,Application的名字是配置文件中的:
spring.application.name: provider-a
不過給轉成了大寫,看起來這個配置大小寫不敏感。
4,創建consumer,Ribbon模式
consumer是服務的消費者。
consumer調用provider有兩種模式,Ribbon模式和Feign模式,先創建一個Ribbon模式的consumer。
依然是創建module,還是選擇Spring Initializr,Group和前面的eureka-server寫成一樣的com.lkforce.cloud,Artifact和Name就叫consumer-ribbon吧:
next,和provider一樣選擇Spring Cloud Discovery->Eureka Discovery Client,還有Web->SpringWeb,然後我們得到了這樣的module:
照例修改配置文件:
server.port: 8764
spring.application.name: consumer-ribbon
eureka.client.service-url.defaultZone: http://localhost:8761/eureka/
照例在啓動類前添加註解:@EnableEurekaClient。
下面創建一個對外接口,並在接口中調用上面provider的接口。
首先我們要有一個RestTemplate的對象,如果寫在啓動類裏,我們的啓動類就會變成這樣:
package consumer.ribbon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
public class ConsumerRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerRibbonApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
其中@LoadBalanced註解代表開啓負載均衡。
創建Service,用來調用provider接口:
package consumer.ribbon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
public String hiService(String name) {
return restTemplate.getForObject("http://provider-a/hi?name="+name,String.class);
}
}
注意,這裏http後面直接寫Application的名字,不是項目名字,別寫錯了。
創建一個controller,使用這個RibbonService:
package consumer.ribbon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HiController {
@Autowired
RibbonService ribbonService;
@RequestMapping(value = "/hi")
public String hi(@RequestParam String name) {
return ribbonService.hiService(name);
}
}
啓動服務,在地址欄輸入:http://localhost:8764/hi?name=test,可以看到結果:
5,創建consumer,Feign模式
Feign模式包含Ribbon和Hystrix。
照例創建module,還是選擇Spring Initializr,Group和前面的eureka-server寫成一樣的com.lkforce.cloud,Artifact和Name就叫consumer-feign:
next,除了選擇Spring Cloud Discovery->Eureka Discovery Client,還有Web->SpringWeb之外,還需要Spring Cloud Routing下的OpenFeign:
然後我們得到了這樣的module:
修改application.property:
server.port: 8765
spring.application.name: consumer-feign
eureka.client.service-url.defaultZone: http://localhost:8761/eureka/
在啓動類上加上註解:@EnableEurekaClient和@EnableFeignClients。
下面寫一個Service,用來調用provider:
package consumer.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "provider-a")
public interface FeignService {
@GetMapping("/hi")
String sayHiFromClientOne(@RequestParam(value = "name") String name);
}
再寫一個controller,提供對外接口並調用上面的service:
package consumer.feign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HiController {
@Autowired
FeignService feignService;
@GetMapping(value = "/hi")
public String sayHi(@RequestParam String name) {
return feignService.hiService( name );
}
}
不知道爲什麼IDEA提示我feignService無法被Autowired,但是實際上並不影響編譯和運行。
啓動服務,在地址欄輸入http://localhost:8765/hi?name=test,可以看到結果:
6,在consumer-ribbon中使用斷路器Hystrix
斷路器Hystrix,作用是在provider被調用失敗多次後,斷路器打開,consumer再次調用該provider時,執行指定的斷路處理方法,可以防止雪崩。
在Ribbon模式使用斷路器時,首先向consumer-ribbon的pom文件中加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
第二步,啓動類添加@EnableHystrix註解。
第三步,給RibbonService的方法前添加@HystrixCommand註解,用來設置斷路方法名,並寫好斷路方法,於是RibbonServiceService變成了這樣:
package consumer.ribbon;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "hiServiceError")
public String hiService(String name) {
return restTemplate.getForObject("http://provider-a/hi?name=" + name, String.class);
}
public String hiServiceError(String name) {
return "hi," + name + ",斷路機制啓動,hiServiceError";
}
}
創建的註解@HystrixCommand(fallbackMethod = "hiServiceError")代表斷路後要執行的方法是hiServiceError方法。
下面啓動服務,啓動euraka-server,provider-server,consumer-ribbon,在地址欄輸入:
http://localhost:8764/hi?name=test
顯示的是:
hi test ,i am from port:8762
然後關閉provider-server,再次在地址欄輸入:
http://localhost:8764/hi?name=test
顯示的是:
hi,test,斷路機制啓動,hiServiceError
7,在consumer-feign中使用斷路器Hystrix
feign模式使用斷路器,不用引入其他的jar包。
首先,要在consumer-feign的配置文件中加入:
feign.hystrix.enabled: true
打開斷路器。
第二,和consumer-ribbon中配置斷路的方式不同,consumer-feign中把斷路處理配置在Service類上,在此之前,我們要有一個斷路處理類:
package consumer.feign;
import org.springframework.stereotype.Component;
@Component
public class FeignServiceHystric implements FeignService {
@Override
public String hiService(String name) {
return "hi," + name + ",斷路機制啓動,hiServiceError";
}
}
這個斷路處理類實現了原Service接口,所以要實現原Service中的所有方法,而這些實現的方法就會成爲原Service的斷路處理方法。
@Component註解別忘了加。
第三,有了斷路處理類,下面在原Service上添加配置,於是原Service變成了這樣:
package consumer.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "provider-a",fallback = FeignServiceHystric.class)
public interface FeignService {
@GetMapping("/hi")
String hiService(@RequestParam(value = "name") String name);
}
在@FeignClient註解裏添加的fallback = FeignServiceHystric.class參數,就是指定斷路處理類的參數。
下面啓動服務,啓動euraka-server,provider-server,consumer-feign,在地址欄輸入:
http://localhost:8765/hi?name=test
顯示的是:
hi test ,i am from port:8762
然後關閉provider-server,再次在地址欄輸入:
http://localhost:8765/hi?name=test
顯示的是:
hi,test,斷路機制啓動,hiServiceError
另外,如果配置文件中沒有加入feign.hystrix.enabled: true,那麼斷路器的配置不會生效,請求provider時不會執行斷路器處理類的方法,而是會拋出異常:
java.net.SocketTimeoutException: connect timed out
頁面呈現的效果則會是這樣:
8,創建路由網關 ZUUL
照例創建module,還是選擇Spring Initializr,Group和前面的eureka-server寫成一樣的com.lkforce.cloud,Artifact和Name就叫zuul-server:
next,除了選擇Spring Cloud Discovery->Eureka Discovery Client,還有Web->SpringWeb之外,還需要Spring Cloud Routing下的Zuul:
創建module後,修改application.property:
server.port: 8769
spring.application.name: service-zuul
eureka.client.serviceUrl.defaultZone: http://localhost:8761/eureka/
zuul.routes.api-a.path: /api-a/**
zuul.routes.api-a.service-id: consumer-ribbon
zuul.routes.api-b.path: /api-b/**
zuul.routes.api-b.service-id: consumer-feign
其中zuul.routes就是路由部分
在啓動類添加@EnableEurekaClient和@EnableZuulProxy註解
啓動服務,在地址欄輸入:
http://localhost:8769/api-a/hi?name=test
請求就會轉到consumer-ribbon服務的/hi?name=test接口,界面展示:
地址欄輸入:
http://localhost:8769/api-b/hi?name=test
請求就會轉到consumer-feign服務的/hi?name=test接口,界面展示:
9,在路由網關中創建過濾器
用到的依然是Zuul組件,我們需要定義一個過濾器類,繼承ZuulFilter類,比如:
package zuul.server;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class ThisIsAFilter extends ZuulFilter {
/**
* pre 路由之前攔截
* routing 路由時攔截
* post 路由後攔截
* error 發送錯誤調用
*/
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
/**
* 是否需要過濾
*
* @return true則表示需要過濾
*/
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String key = request.getParameter("key");
if (key != null) {
return false;
}
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String name = request.getParameter("name");
if (name.length() > 5) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("error:name.length>5");
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
以上是一個自定義的過濾器類,繼承ZuulFilter類。
繼承ZuulFilter類後,需要重寫以下方法:
filterType(),返回過濾的節點。返回pre字符串表示在路由之前過濾。
filterOrder(),表示過濾順序。
shouldFilter(),是否需要過濾,返回true則表示需要過濾。上面的代碼中,請求沒有key參數則需要過濾。
run(),過濾的方法,具體的過濾邏輯。在上面的代碼中,name參數的長度大於5則請求失敗,不會分發給服務。
啓動服務,在地址欄輸入:
http://localhost:8769/api-a/hi?name=test
則頁面展示:
hi test ,i am from port:8762
地址欄輸入:
http://localhost:8769/api-a/hi?name=testaaa
則頁面展示:
error:name.length>5
地址欄輸入:
http://localhost:8769/api-a/hi?name=testaaa&key=1
則頁面展示:
hi test ,i am from port:8762
10,創建配置中心,config-server
在創建配置中心的server之前,我們需要有一個可訪問的配置文件倉庫,可以放在git,svn,或者本地。我在git上創建了一個工程,裏面有兩個文件:
項目路徑:
https://github.com/liankgun/spring-cloud-config-repository
文件:
spring-cloud-config-repository/config-dev.properties
內容:
hello:this is config-dev.properties
文件:
spring-cloud-config-repository/config-test.properties
內容:
hello:this is config-test.properties
下面創建module,還是選擇Spring Initializr,Group和前面的eureka-server寫成一樣的com.lkforce.cloud,Artifact和Name就叫config-server:
next,選擇Spring Cloud Config->Config Server:
然後我們得到了這樣的module:
修改配置文件:
server.port: 8770
spring.application.name: config-server
spring.cloud.config.server.git.uri: https://github.com/liankgun/spring-cloud-config-repository
給啓動類添加註解:@EnableConfigServer
啓動服務,在瀏覽器地址欄輸入:
http://localhost:8770/config-dev.properties
返回結果:
也就是在github中添加的config-dev.properties文件的內容。
地址欄輸入:
http://localhost:8770/config-test.properties
返回結果:
也就是在github中添加的config-test.properties文件的內容。
地址欄輸入:
http://localhost:8770/config-client/dev.properties
返回結果:
圖32
也就是在github中添加的config-dev.properties文件的文件信息。
說明config-server可用
11,創建配置中心,config-client
配置中心可以統一管理服務的外部參數,方便的進行統一配置和統一修改。
新建module,還是選擇Spring Initializr,Group和前面的eureka-server寫成一樣的com.lkforce.cloud,Artifact和Name就叫config-client:
next,選擇Spring Cloud Config->Config Client和Web->SpringWeb:
然後我們得到了這樣的module:
修改配置文件名,默認的配置文件名是application.properties,需要修改爲bootstrap.properties,或者bootstrap.yml,據說config-server端口是8888時不用改名。
另外修改配置文件內容:
server.port: 8771
spring.application.name: config-client
spring.cloud.config.name: config-client
spring.cloud.config.label: master
spring.cloud.config.uri: http://localhost:8770/
spring.profiles.active: dev
要注意,spring.cloud.config.name這一項的名字得和github上的配置文件名字對應,比如上面的配置:
spring.cloud.config.name: config-client
那麼在github上的配置文件的名字就得是config-client開頭,後面加下劃線再加環境名,比如:
- config-client-dev.properties
- config-client-test.properties
- config-client.properties
- config-client-dev.yml
- config-client-test.yml
- config-client.yml
沒有環境名的就代表默認配置。
這次啓動類不用加什麼註解。
創建一個controller,提供對外接口:
package config.client;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/config")
public class ConfigClientController {
@Value("${hello}")
private String hello;
@GetMapping("/getConfigHello")
public String getConfigHello() {
return hello;
}
}
啓動服務,在地址欄輸入:
http://localhost:8771/config/getConfigHello
返回結果:
也就是github上config-client-dev.properties這個配置文件中,hello參數的值。
之所以可以獲得hello參數,是因爲這個配置:
spring.cloud.config.uri: http://localhost:8770/
再加上:
spring.cloud.config.name: config-client
再加上:
spring.profiles.active: dev
所以config-client服務一啓動的時候,就加載了
這個配置文件的內容。
另外,以上的配置是沒有自動刷新功能的,也就是說,如果github上配置文件的內容變了,server端和client端不能立即響應,關於自動刷新的策略網上有很多。
完