負載均衡
? spring cloud 體系中,我們知道服務之間的呼叫是通過http協議進行呼叫的,注冊中心就是維護這些呼叫的服務的各個服務串列,在Spring中提供了RestTemplate,用于訪問Rest服務的客戶端,Spring Cloud體系中也是使用RestTemplate進行服務之間的呼叫,
? 負載均衡(Load Balance),通常指將請求分攤到各個操作單元上進行處理,精髓在于均衡,也就是平均,負載均衡在我們日常開發中也是經常聽到的,下面介紹幾種常見的負載均衡技術實作,
-
Nginx
? Nginx是我們平時使用的較多的負載均衡技術實作,性能也是很不錯,一個http請求請求至Nginx服務器,Nginx服務器根據請求和一定的演算法將請求轉發至真正的服務器,以達到負載均衡的目的,至于Nginx里面具體的負載均衡演算法,了解過Nginx的人應該會比較熟悉,沒了解過的請自己去看吧,
-
DNS決議
? 在DNS服務器上配置多個域名對應ip的記錄,一個域名可以對應多組的ip地址,DNS服務器在決議域名時根據相應的演算法,把通過域名的請求分配到合適的真實服務器上去,這樣也可以達到一定的負載均衡,不至于一個服務器的壓力過大,
RestTemplate
我們先看下官方是怎么定義的:
Synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries such as the JDK HttpURLConnection, Apache HttpComponents, and others. The RestTemplate offers templates for common scenarios by HTTP method, in addition to the generalized exchange and execute methods that support of less frequent cases.
? 我們可以看到,RestTemplate采用的是同步方式執行Http請求的類,底層使用的是JDK的HttpURLConnection或者Apache的HttpComponents的類別庫,或者其他的類別庫,RestTemplate還提供了模版使得開發人員能夠更簡單發送Http請求,
? RestTemplate中定義了很多和Rest資源互動的API,下面就介紹2個我們平時常用的GET和POST請求,在RestTemplate中是怎么請求的,
GET請求:
RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build();
ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
exchage是一個通用的請求方法,接受一個RequestEntity物件,可以設定路徑,請求頭,請求資訊等,最后回傳一個ResponeseEntity物體,
當然GET請求也可以是getForEntity()和getForObject()2中型別:
//getForEntity()
ResponseEntity<User> responseEntity = this.restTemplate.getForEntity(uri, User.class);
User user = responseEntity.getBody();
//getForObject()
User user = this.restTemplate.getForObject(uri, User.class);
POST請求:
HttpEntity<MultiValueMap> request = new HttpEntity<>(map, header); ResponseEntity<String> exchangeResult = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
map中存放post的資料,header里存放請求頭相關的資訊,最后回傳一個ResponeseEntity物體類,
同樣的,POST請求也是有著對應的getForObject()和getForEntity()型別:
//postForObject()
User user = this.restTemplate.postForObject(uri, user, User.class);
//postForEntity()
ResponseEntity<User> responseEntity = this.restTemplate.postForEntity(uri, user, User.class);
請他的請求,也是類似,這里就不在此列舉了,感興趣的可以查看RestTesmplate的api
Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon實作的客戶端負載均衡組件,區別于Nginx的服務端負載均衡的實作,在結合Spring Cloud Eureka組件使用時,ribbonServerList會被重寫,改為通過Eureka的注冊中心來獲取服務串列,可以通過簡單的幾行配置來實作客戶端的負載均衡,
接下來我們來使用Spring Cloud Ribbon來實作客戶端的負載均衡
提前專案準備:
1.Eureka:首先我們先啟動一個Eureka 服務端作為注冊中心
2.然后開啟2個服務,注冊到Eureka服務中去,我這里開啟了2個oauth服務注冊到Eureka服務端中
3.開始構建具有負載均衡功能的服務消費方apiGateWay
? 這里使用apiGateWay作為服務消費方的原因是我們應用一般都是需要權限管理,驗證登錄用戶的,這里apiGateWay通過呼叫oauth應用服務來驗證用戶的合法性,
-
首先添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> -
啟用服務發現客戶端
這里apiGateWay作為服務消費方,就相當于http請求程序充當了客戶端的角色,被呼叫的服務就是服務端,先在客戶端的啟動類中宣告要使用的RestTemplate
@SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy @Slf4j public class ApiGatewayApplication { public static void main(String[] args){ SpringApplication.run(ApiGatewayApplication.class); log.info("ApiGatewayApplication啟動"); } @Bean @LoadBalanced RestTemplate restTemplate(){ return new RestTemplate(); } } -
撰寫測驗類
@RestController @Slf4j public class RibbonClient { @Autowired RestTemplate restTemplate; @GetMapping("/demo") public String ribbonClientDemo(String paramName){ log.info("請求引數paramName:{}",paramName); return restTemplate.getForObject("http://oauth/demo?paramName="+paramName,String.class); } } -
啟動應用,訪問http://localhost:38763/demo?paramName=demo
請求的埠是:38765 或者是 請求的埠是:38764然后我們多次發起請求,看被呼叫的服務的日志,可以發現請求確實被均勻的分配到開啟的2個服務的,
oauth(埠:38764)
請求的引數是:demo serverPort:38764oauth(埠:38765)
請求的引數是:demo serverPort:38765可以看到,2個服務被輪詢呼叫,Ribbon默認的均衡策略是以輪詢的方式去選擇服務器,
1.RandomRule:隨機選取負載均衡策略,隨機Random物件,在所有服務實體中隨機找一個服務的索引號,然后從上線的服務中獲取對應的服務, 2.RoundRobinRule:線性輪詢負載均衡策略, 3.WeightedResponseTimeRule:回應時間作為選取權重的負載均衡策略,根據平均回應時間計算所有服務的權重,回應時間越短的服務權重越大,被選中的概率越高,剛啟動時,如果統計資訊不足,則使用線性輪詢策略,等資訊足夠時,再切換到WeightedResponseTimeRule, 4.RetryRule:使用線性輪詢策略獲取服務,如果獲取失敗則在指定時間內重試,重新獲取可用服務, 5.ClientConfigEnabledRoundRobinRule:默認通過線性輪詢策略選取服務,通過繼承該類,并且對choose方法進行重寫,可以實作更多的策略,繼承后保底使用RoundRobinRule策略, 6.BestAvailableRule:繼承自ClientConfigEnabledRoundRobinRule,從所有沒有斷開的服務中,選取到目前為止請求數量最小的服務, 7.PredicateBasedRule:抽象類,提供一個choose方法的模板,通過呼叫AbstractServerPredicate實作類的過濾方法來過濾出目標的服務,再通過輪詢方法選出一個服務, 8.AvailabilityFilteringRule:按可用性進行過濾服務的負載均衡策略,會先過濾掉由于多次訪問故障而處于斷路器跳閘狀態的服務,還有并發的連接數超過閾值的服務,然后對剩余的服務串列進行線性輪詢, 9.ZoneAvoidanceRule:本身沒有重寫choose方法,用的還是抽象父類PredicateBasedRule的choose,如果沒有符合我們業務需求的,那么我們也可以根據業務需求自己實作一個IRule,可以通過繼承
AbstractLoadBalancerRule來實作我們自己的負載均衡演算法:@Slf4j public class MyRule extends AbstractLoadBalancerRule { @Override public void initWithNiwsConfig(IClientConfig iClientConfig){ } /** * 自定義均衡策略 * 這里簡單實作回傳串列的第一個服務 */ @Override public Server choose(Object o) { log.info("key:" + o); //獲取服務串列 List<Server> allServers = getLoadBalancer().getAllServers(); log.info(allServers.toString()); return allServers.get(0); } }我們通過實作choose方法來實作我們自己的負載均衡演算法,
getLoadBalancer().getAllServers()可以獲取到所有的服務資訊,然后我們可以根據自己的策略來確定選擇哪一個服務器,自定義策略之后呢,我們需要在服務呼叫方添加一個注解配置
@FeignClient+配置類來修改spring cloud的默認配置,首先啟動類上添加注解:
@SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy @Slf4j @FeignClient(value = "https://www.cnblogs.com/mzhblog/p/oauth",configuration = RuleConfig.class) public class ApiGatewayApplication { public static void main(String[] args){ SpringApplication.run(ApiGatewayApplication.class); log.info("ApiGatewayApplication啟動"); } @Bean @LoadBalanced RestTemplate restTemplate(){ return new RestTemplate(); } }然后創建RuleConfig類來把策略修改為我們剛剛實作的策略:
@Configuration public class RuleConfig { @Bean public IRule ribbonRule() { return new MyRule(); } }重新啟動應用,然后我們在發起請求,就會發現這次請求就只會轉發給同一個服務,也就是server串列里的第一個服務,而不在是輪詢的進行請求,
然后可以看到
MyRule類列印的日志:請求引數paramName:demo key:default [192.168.3.2:38764, 192.168.3.2:38765]服務串列中依舊是2個服務在運行,但請求通過我們自定義的策略,永遠只會發送給第一個服務,而不會轉發給第二個服務,
當然我們也可以通過組態檔配置,這就自己去研究吧,,,
feign
我們先來看下官網是怎么定義的:
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.
Feign是一個宣告示的Http客戶端,它是的寫Http客戶端更加簡單,它只需要通過注解相應的介面就可以實作,雖然通過上面的Ribbon里介紹的也可以創建Http客戶端發起請求,但不是那么的優雅,Feign是NetFlix開發的宣告式、模塊式的HTTP客戶端,可以幫助我們更好的、更快的開發呼叫HTTP API,
Feign應用
-
首先在工程中加入相應的依賴:
<!-- feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> -
然后添加組態檔:
server.port=38763 spring.application.name=api-gateway spring.cloud.config.discovery.enabled=true spring.cloud.config.discovery.service-id=config-server [email protected]@ eureka.client.service-url.defaultZone=http://localhost:38761/eureka/ -
創建啟動類,添加注解
@EnableFeignClients,開啟Feign支持:@SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy @Slf4j @EnableFeignClients public class ApiGatewayApplication { public static void main(String[] args){ SpringApplication.run(ApiGatewayApplication.class); log.info("ApiGatewayApplication啟動"); } } -
創建一個介面類IOauthClient,加入注解@FeignClient來指定這個介面要呼叫的服務的名稱
//使用@FeignClient注解來指定要呼叫的服務名稱,即注冊到eureka里的服務名稱 @FeignClient(name = "oauth") public interface IOauthClient { /** * value 指定要呼叫的介面 * method 指定呼叫的方式是 GET、POST、PUT、DELETE等 * @RequestParam 指定要傳入的引數 */ @RequestMapping(value = "https://www.cnblogs.com/demo",method = RequestMethod.GET) public String demo(@RequestParam("paramName") String paramName); } -
然后在業務需要的地方呼叫該介面
@RestController @Slf4j public class DemoController { @Autowired IOauthClient iOauthClient; @GetMapping("/demo") public String demo(String paramName){ log.info("請求引數為:{}",paramName); return iOauthClient.demo(paramName); } } -
啟動應用,然后發起請求
然后我們就可以看到頁面上請求的回傳值:
請求的埠是:38764是不是和前面講Ribbon一樣的,不過這種實作方式是不是和前面的方式更加優雅呢?尤其是在呼叫的介面變多之后,這種實作方式是不是更加簡單?
-
注意
這里使用
Feign的時候,需要注意的問題:1.GET請求多個引數的時候,需要使用@RequestParam
2.POST請求使用@RequestBody注解引數
參考資料
- https://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign
- https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html
歡迎關注我的公眾號

本文由博客群發一文多發等運營工具平臺 OpenWrite 發布
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/115579.html
標籤:Java
