SpringCloud微服務治理一(介紹,環境搭建,Eureka)
SpringCloud微服務治理二(Robbin,Hystix,Feign)
SpringCloud微服務治理三(Zuul網關)html
在剛纔的案例中,咱們啓動了一個user-service,而後經過DiscoveryClient來獲取服務實例信息,而後獲取ip和端口來訪問。java
可是實際環境中,咱們每每會開啓不少個user-service的集羣。此時咱們獲取的服務列表中就會有多個,到底該訪問哪個呢?算法
通常這種狀況下咱們就須要編寫負載均衡算法,在多個實例列表中進行選擇。spring
接下來,咱們就來使用Ribbon實現負載均衡。json
首先咱們啓動兩個user-service實例,一個8081,一個8082。mybatis
Eureka監控面板:app
由於Eureka中已經集成了Ribbon,因此咱們無需引入新的依賴。直接修改代碼:負載均衡
在RestTemplate的配置方法上添加@LoadBalanced
註解:框架
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
複製代碼
修改調用方式,再也不手動獲取ip和端口,而是直接經過服務名稱調用:dom
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
// 地址直接寫服務名稱便可
String baseUrl = "http://user-service/user/";
ids.forEach(id -> {
// 咱們測試屢次查詢,
users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
// 每次間隔500毫秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return users;
}
}
複製代碼
Ribbon默認的負載均衡策略是簡單的輪詢,咱們能夠測試一下:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {
@Autowired
RibbonLoadBalancerClient client;
@Test
public void test(){
for (int i = 0; i < 100; i++) {
ServiceInstance instance = this.client.choose("user-service");
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
}
複製代碼
結果:
符合了咱們的預期推測,確實是輪詢方式。
SpringBoot也幫咱們提供了修改負載均衡規則的配置入口:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
複製代碼
格式是:{服務名稱}.ribbon.NFLoadBalancerRuleClassName
Eureka的服務治理強調了CAP原則中的AP,便可用性和可靠性。它與Zookeeper這一類強調CP(一致性,可靠性)的服務治理框架最大的區別在於:Eureka爲了實現更高的服務可用性,犧牲了必定的一致性,極端狀況下它寧願接收故障實例也不肯丟掉健康實例,正如咱們上面所說的自我保護機制。
可是,此時若是咱們調用了這些不正常的服務,調用就會失敗,從而致使其它服務不能正常工做!這顯然不是咱們願意看到的。
咱們如今關閉一個user-service實例:
由於服務剔除的延遲,consumer並不會當即獲得最新的服務列表,此時再次訪問你會獲得錯誤提示:
可是此時,8081服務實際上是正常的。
所以Spring Cloud 整合了Spring Retry 來加強RestTemplate的重試能力,當一次服務調用失敗後,不會當即拋出一次,而是再次重試另外一個服務。
只須要簡單配置便可實現Ribbon的重試:
spring:
cloud:
loadbalancer:
retry:
enabled: true # 開啓Spring Cloud的重試功能
user-service:
ribbon:
ConnectTimeout: 250 # Ribbon的鏈接超時時間
ReadTimeout: 1000 # Ribbon的數據讀取超時時間
OkToRetryOnAllOperations: true # 是否對全部操做都進行重試
MaxAutoRetriesNextServer: 1 # 切換實例的重試次數
MaxAutoRetries: 1 # 對當前實例的重試次數
複製代碼
根據如上配置,當訪問到某個服務超時後,它會再次嘗試訪問下一個服務實例,若是不行就再換一個實例,若是不行,則返回失敗。切換次數取決於MaxAutoRetriesNextServer
參數的值
引入spring-retry依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
複製代碼
咱們重啓user-consumer-demo,測試,發現即便user-service2宕機,也能經過另外一臺服務實例獲取到結果!
Hystix是Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。
正常工做的狀況下,客戶端請求調用服務API接口:
當有服務出現異常時,直接進行失敗回滾,服務降級處理:
當服務繁忙時,若是服務出現異常,不是粗暴的直接報錯,而是返回一個友好的提示,雖然拒絕了用戶的訪問,可是會返回一個結果。
首先在user-consumer中引入Hystix依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
複製代碼
咱們改造user-consumer,添加一個用來訪問的user服務的DAO,而且聲明一個失敗時的回滾處理函數:
@Component
public class UserDao {
@Autowired
private RestTemplate restTemplate;
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
@HystrixCommand(fallbackMethod = "queryUserByIdFallback")
public User queryUserById(Long id){
long begin = System.currentTimeMillis();
String url = "http://user-service/user/" + id;
User user = this.restTemplate.getForObject(url, User.class);
long end = System.currentTimeMillis();
// 記錄訪問用時:
logger.info("訪問用時:{}", end - begin);
return user;
}
public User queryUserByIdFallback(Long id){
User user = new User();
user.setId(id);
user.setName("用戶信息查詢出現異常!");
return user;
}
}
複製代碼
@HystrixCommand(fallbackMethod="queryUserByIdFallback")
:聲明一個失敗回滾處理函數queryUserByIdFallback,當queryUserById執行超時(默認是1000毫秒),就會執行fallback函數,返回錯誤提示。在原來的業務邏輯中調用這個DAO:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
ids.forEach(id -> {
// 咱們測試屢次查詢,
users.add(this.userDao.queryUserById(id));
});
return users;
}
}
複製代碼
改造服務提供者,隨機休眠一段時間,以觸發熔斷:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) throws InterruptedException {
// 爲了演示超時現象,咱們在這裏然線程休眠,時間隨機 0~2000毫秒
Thread.sleep(new Random().nextInt(2000));
return this.userMapper.selectByPrimaryKey(id);
}
}
複製代碼
而後運行並查看日誌:
id爲九、十、11的訪問時間分別是:
id爲12的訪問時間:
所以,只有12是正常訪問,其它都會觸發熔斷,咱們來查看結果:
雖然熔斷實現了,可是咱們的重試機制彷佛沒有生效,是這樣嗎?
其實這裏是由於咱們的Ribbon超時時間設置的是1000ms:
而Hystix的超時時間默認也是1000ms,所以重試機制沒有被觸發,而是先觸發了熔斷。
因此,Ribbon的超時時間必定要小於Hystix的超時時間。
咱們能夠經過hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
來設置Hystrix超時時間。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 設置hystrix的超時時間爲6000ms
複製代碼
Feign能夠把Rest的請求進行隱藏,假裝成相似SpringMVC的Controller同樣。你不用再本身拼接url,拼接參數等等操做,一切都交給Feign去作。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
複製代碼
@FeignClient("user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
複製代碼
@FeignClient
,聲明這是一個Feign客戶端,相似@Mapper
註解。同時經過value
屬性指定服務名稱改造原來的調用邏輯,再也不調用UserDao:
@Service
public class UserService {
@Autowired
private UserFeignClient userFeignClient;
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
ids.forEach(id -> {
// 咱們測試屢次查詢,
users.add(this.userFeignClient.queryUserById(id));
});
return users;
}
}
複製代碼
咱們在啓動類上,添加註解,開啓Feign功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients // 開啓Feign功能
public class UserConsumerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
複製代碼
Feign中自己已經集成了Ribbon依賴和自動配置:
所以咱們不須要額外引入依賴,也不須要再註冊RestTemplate
對象。
另外,咱們能夠像上節課中講的那樣去配置Ribbon,能夠經過ribbon.xx
來進行全局配置。也能夠經過服務名.ribbon.xx
來對指定服務配置:
user-service:
ribbon:
ConnectTimeout: 250 # 鏈接超時時間(ms)
ReadTimeout: 1000 # 通訊超時時間(ms)
OkToRetryOnAllOperations: true # 是否對全部操做重試
MaxAutoRetriesNextServer: 1 # 同一服務不一樣實例的重試次數
MaxAutoRetries: 1 # 同一實例的重試次數
複製代碼
經過下面的參數來開啓:
feign:
hystrix:
enabled: true # 開啓Feign的熔斷功能
複製代碼
可是,Feign中的Fallback配置不像Ribbon中那樣簡單了。
1)首先,咱們要定義一個類,實現剛纔編寫的UserFeignClient,做爲fallback的處理類
@Component
public class UserFeignClientFallback implements UserFeignClient {
@Override
public User queryUserById(Long id) {
User user = new User();
user.setId(id);
user.setName("用戶查詢出現異常!");
return user;
}
}
複製代碼
2)而後在UserFeignClient中,指定剛纔編寫的實現類
@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
複製代碼
Spring Cloud Feign 支持對請求和響應進行GZIP壓縮,以減小通訊過程當中的性能損耗。經過下面的參數便可開啓請求與響應的壓縮功能:
feign:
compression:
request:
enabled: true # 開啓請求壓縮
response:
enabled: true # 開啓響應壓縮
複製代碼
同時,咱們也能夠對請求的數據類型,以及觸發壓縮的大小下限進行設置:
feign:
compression:
request:
enabled: true # 開啓請求壓縮
mime-types: text/html,application/xml,application/json # 設置壓縮的數據類型
min-request-size: 2048 # 設置觸發壓縮的大小下限
複製代碼
注:上面的數據類型、壓縮大小下限均爲默認值。
前面講過,經過logging.level.xx=debug
來設置日誌級別。然而這個對Fegin客戶端而言不會產生效果。由於@FeignClient
註解修改的客戶端在被代理時,都會建立一個新的Fegin.Logger實例。咱們須要額外指定這個日誌的級別才能夠。
1)設置com.leyou包下的日誌級別都爲debug
logging:
level:
com.leyou: debug
複製代碼
2)編寫配置類,定義日誌級別
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
複製代碼
這裏指定的Level級別是FULL,Feign支持4種級別:
3)在FeignClient中指定配置類:
@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class, configuration = FeignConfig.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
複製代碼
4)重啓項目,便可看到每次訪問的日誌: