思考java
Ribbon、Nginx、Feign 三者有什麼區別
1)、Ribbon是一套 【客戶端】 的 【負載均衡】 工具 2)、負載均衡(Load Balance)分爲 集中式LB 和 進程內LB 集中式LB : 即在服務的消費方和提供方之間使用獨立的LB設施(能夠是硬件,如F5, 也能夠是軟件,如nginx), 由該設施負責把訪問請求經過某種策略轉發至服務的提供方; 進程內LB : 將LB邏輯集成到消費方,消費方從服務註冊中心獲知有哪些地址可用,而後本身再從這些地址中選擇出一個合適的服務器。Ribbon就屬於進程內LB,它只是一個類庫,集成於消費方進程,消費方經過它來獲取到服務提供方的地址。 3)、Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。 簡單的說,Ribbon是Netflix發佈的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務鏈接在一塊兒。Ribbon客戶端組件提供一系列完善的配置項如鏈接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer(簡稱LB)後面全部的機器,Ribbon會自動的幫助你基於某種規則(如簡單輪詢,隨機鏈接等)去鏈接這些機器。咱們也很容易使用Ribbon實現自定義的負載均衡算法。
①、maven座標 ②、啓動類@EnnableXXXnginx
應爲是客戶端負載均衡工具,因此一系列操做都是在cloud-consumer-dept-80上進行的。git
2.1)、POM修改github
eureka客戶端、Ribbon客戶端、config客戶端(服務端都帶-server)web
<!-- Ribbon相關 --> <!--Ribbon client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!--Eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <!--config client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
2.2)、YML修改算法
新增eureka服務路徑spring
eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
2.3)、ConfigBean修改數據庫
應爲consumer是經過restTemplate訪問服務提供方的,因此要在restTemplate中增長負載均衡編程
package com.lee.cloud.cfgbean; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; //配置類 @Configuration public class ConfigBean { //RestTemplate提供了多種便捷訪問遠程HTTP服務的方法 //是一種簡單便捷的訪問restful服務模板類,是spring提供的用於訪問Rest服務的客戶端模板工具集 //相似JDBCTemplate RedisTemplate等 @Bean @LoadBalanced//負載均衡 public RestTemplate restTemplate(){ return new RestTemplate(); } }
2.4)、主啓動類修改服務器
@EnableEurekaClient //注意是Eureka客戶端不是Ribbon
2.5)、修改客戶端訪問類
由於原來訪問的都是localhost:8001路徑接口,如今訪問 多個路徑
將路徑改爲 微服務 在Eureka中註冊的實例名稱
// private static final String REST_URL_PREFIX = "http://localhost:8001"; private static final String REST_URL_PREFIX = "http://CLOUD-DEPT";
測試:
一、啓動 cloud-eureka-7001\cloud-eureka-7002\cloud-eureka-7003 二、啓動 cloud-provider-dept-8001 三、啓動 cloud-consumer-dept-80 四、訪問 http://localhost:80/consumer/dept/list 結論,Ribbon和Eureka整合後Consumer能夠直接經過服務實例名稱調用服務而不用再關心服務端的地址和接口(這裏尚未用到Load Balance,下一步會增長cloud-provider-dept來驗證)
3.1)、Ribbon架構圖
Ribbon在工做時分紅兩步 第一步先選擇 EurekaServer ,它優先選擇在同一個區域內負載較少的server. 第二步再根據用戶指定的策略,在從server取到的服務註冊列表中選擇一個地址。 其中Ribbon提供了多種策略:好比輪詢、隨機和根據響應時間加權。
3.2)、新增 兩個服務提供者
cloud-provider-dept-800二、cloud-provider-dept-8003
修改他們對應的POM 、YML、 啓動類
三個服務對外暴露的 服務實例名稱 必須一致
3.3)、新增對應的數據庫
不是必須的,只是爲了測試 負載均衡時,能夠看清楚到底訪問的哪一個服務而已
DROP DATABASE IF EXISTS cloudDB02; CREATE DATABASE cloudDB02 CHARACTER SET UTF8; USE cloudDB02; CREATE TABLE dept ( deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, dname VARCHAR(60), db_source VARCHAR(60) ); INSERT INTO dept(dname,db_source) VALUES('開發部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('財務部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('市場部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('運維部',DATABASE()); SELECT * FROM dept; ------------------------------------------------------------- DROP DATABASE IF EXISTS cloudDB03; CREATE DATABASE cloudDB03 CHARACTER SET UTF8; USE cloudDB03; CREATE TABLE dept ( deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, dname VARCHAR(60), db_source VARCHAR(60) ); INSERT INTO dept(dname,db_source) VALUES('開發部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('人事部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('財務部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('市場部',DATABASE()); INSERT INTO dept(dname,db_source) VALUES('運維部',DATABASE()); SELECT * FROM dept;
測試:
一、啓動3個Eureka服務 二、啓動3個服務提供方 三、啓動服務消費方 四、訪問http://localhost:80/consumer/dept/list 結論: Ribbon其實就是一個軟負載均衡的客戶端組件, 他能夠和其餘所需請求的客戶端結合使用,和eureka結合只是其中的一個實例。
IRule:根據特定算法中從服務列表中選取一個要訪問的服務 默認自帶的七種算法: 一、RoundRobinRule:輪詢 二、RandomRule:隨機 三、AvailabilityFilteringRULE:會先過濾掉因爲屢次訪問故障而處於斷路器跳閘狀態的服務, 還有併發的鏈接數量超過閾值的服務,而後對剩餘的服務列表按照輪詢策略進行訪問 四、WeightedResponseTimeRule:根據平均響應時間計算全部服務的權重,響應時間越快服務權重越大被選中的機率越高。 剛啓動時若是統計信息不足,則使用RoundRobinRule策略,等統計信息足夠,會切換到 WeightedResponseTimeRule 五、RetryRule:先按照RoundRobinRule的策略獲取服務,若是獲取服務失敗則在指定時間內會進行重試,獲取可用的服務 六、BestAvailableRule:會先過濾掉因爲屢次訪問故障而處於斷路器跳閘狀態的服務,而後選擇一個併發量最小的服務 七、ZoneAvoidanceRule:默認規則,複合判斷server所在區域的性能和server的可用性選擇服務器
更換負載均衡方式:
修改cloud-consumer-dept-80中的configBean
package com.lee.cloud.cfgbean; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; //配置類 @Configuration public class ConfigBean { //RestTemplate提供了多種便捷訪問遠程HTTP服務的方法 //是一種簡單便捷的訪問restful服務模板類,是spring提供的用於訪問Rest服務的客戶端模板工具集 //相似JDBCTemplate RedisTemplate等 @Bean @LoadBalanced//負載均衡--默認RoundRobinRule輪詢算法 public RestTemplate restTemplate(){ return new RestTemplate(); } //修改爲隨機算法 @Bean public IRule myRule(){ return new RandomRule(); } }
在自定義算法前咱們先看一下自帶默認的算法的結構 //輪詢 public class RoundRobinRule extends AbstractLoadBalancerRule //隨機 public class RandomRule extends AbstractLoadBalancerRule 他們都是繼承了AbstractLoadBalancerRule類 而AbstractLoadBalancerRule:實現了IRule接口。 public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware { private ILoadBalancer lb; public AbstractLoadBalancerRule() { } public void setLoadBalancer(ILoadBalancer lb) { this.lb = lb; } public ILoadBalancer getLoadBalancer() { return this.lb; } } public interface IRule { //選擇 Server choose(Object var1); //設置輪詢算法 void setLoadBalancer(ILoadBalancer var1); //獲取算法 ILoadBalancer getLoadBalancer(); } 因此咱們自定義算法的時候直接繼承AbstractLoadBalancerRule 實現 setLoadBalancer 和 getLoadBalancer方法便可 且在啓動該微服務的時候就能去加載咱們的自定義Ribbon配置類,從而使配置生效,形如: @RibbonClient(name="CLOUD-DEPT",configuration=MySelfRule.class)配置
注意:
官方文檔明確給出了警告: 這個自定義配置類不能放在@ComponentScan所掃描的當前包下以及子包下, 不然咱們自定義的這個配置類就會被全部的Ribbon客戶端所共享,也就是說 咱們達不到特殊化定製的目的了。
步驟:
自定義算法:
package com.lee.myrule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; @Configuration public class MySelfRule { @Bean public IRule myRule() { return new RandomRule_ZY(); } }
package com.lee.myrule import java.util.List; import java.util.Random; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; public class RandomRule_ZY extends AbstractLoadBalancerRule { private int total = 0; //總共被調用的次數,目前要求每臺被調用5次 private int currentIndex = 0;//當前提供服務的機器號 public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { /* * No servers. End regardless of pass, because subsequent passes * only get more restrictive. */ return null; } // int index = rand.nextInt(serverCount); // server = upList.get(index); if(total < 5) { server = upList.get(currentIndex); total++; }else { total = 0; currentIndex++; if(currentIndex >= upList.size()) { currentIndex = 0; } } if (server == null) { /* * The only time this should happen is if the server list were * somehow trimmed. This is a transient condition. Retry after * yielding. */ Thread.yield(); continue; } if (server.isAlive()) { return (server); } // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } }
主啓動類增長:
@RibbonClient(name="CLOUD-DEPT",configuration=MySelfRule.class)
測試:
http://localhost:80/consumer/dept/list
Feign是一個聲明式WebService客戶端。使用Feign能讓編寫Web Service客戶端更加簡單, 它的使用方法是定義一個接口,而後在上面添加註解,同時也支持JAX-RS標準的註解。Feign也支持可拔插式的編碼器和解碼器。Spring Cloud對Feign進行了封裝,使其支持了Spring MVC標準註解和HttpMessageConverters。Feign能夠與Eureka和Ribbon組合使用以支持負載均衡。 Feign是一個聲明式的Web服務客戶端,使得編寫Web服務客戶端變得很是容易, 只須要建立一個接口,而後在上面添加註解便可。 參考官網:https://github.com/OpenFeign/feign Feign能幹什麼 Feign旨在使編寫Java Http客戶端變得更容易。 前面在使用Ribbon+RestTemplate時,利用RestTemplate對http請求的封裝處理,造成了一套模版化的調用方法。可是在實際開發中,因爲對服務依賴的調用可能不止一處,每每一個接口會被多處調用,因此一般都會針對每一個微服務自行封裝一些客戶端類來包裝這些依賴服務的調用。因此,Feign在此基礎上作了進一步封裝,由他來幫助咱們定義和實現依賴服務接口的定義。在Feign的實現下,咱們只需建立一個接口並使用註解的方式來配置它(之前是Dao接口上面標註Mapper註解,如今是一個微服務接口上面標註一個Feign註解便可),便可完成對服務提供方的接口綁定,簡化了使用Spring cloud Ribbon時,自動封裝服務調用客戶端的開發量。 Feign集成了Ribbon 利用Ribbon維護了MicroServiceCloud-Dept的服務列表信息,而且經過輪詢實現了客戶端的負載均衡。而與Ribbon不一樣的是,經過feign只須要定義服務綁定接口且以聲明式的方法,優雅而簡單的實現了服務調用
上面咱們用Ribbon進行負載均衡,功能很強大,甚至能夠自定義算法。那麼Feign是怎麼出來的? 一、Ribbon直接調用咱們的微服務來進行訪問,如 private static final String REST_URL_PREFIX = "http://CLOUD-DEPT"; 可是你們目前都西關面向接口編程,好比webservice捷庫,好比咱們的DAO接口,這個已是你們的規範了。 因此SpringCloud提供了兩種方式: 一、微服務名字得到調用地址---->Ribbon 二、經過接口+註解,得到咱們的調用服務---->Feign
2.1)、參考cloud-consumer-dept-80建立cloud-consumer-dept-80-feign
注:不用拷貝controller
POM新增
<!--Feign相關,應爲Feign是基於Ribbon的,因此還須要Ribbon的東西--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
service新增
package com.lee.cloud.feign.service; import com.lee.cloud.entity.Dept; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.util.List; /** * 遠程調用服務端接口--底層其實也是經過restTemplate調用的 * CLOUD-DEPT 調用服務的實例名稱 */ @FeignClient(value = "CLOUD-DEPT") public interface DeptFeignService { @RequestMapping(value = "/dept/get/{id}",method = RequestMethod.GET) public Dept get(@PathVariable("id") long id); @RequestMapping(value = "/dept/list",method = RequestMethod.GET) public List<Dept> list(); @RequestMapping(value = "/dept/add",method = RequestMethod.POST) public boolean add(Dept dept); }
controller修改
package com.lee.cloud.controller; import com.lee.cloud.entity.Dept; import com.lee.cloud.feign.service.DeptFeignService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class DeptController_Consumer { @Autowired private DeptFeignService deptFeignService; @RequestMapping(value = "/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return deptFeignService.get(id); } @RequestMapping(value = "/consumer/dept/list") public List<Dept> list() { return deptFeignService.list(); } @RequestMapping(value = "/consumer/dept/add") public Object add(Dept dept) { return deptFeignService.add(dept); } }
啓動類:
package com.lee.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; @EnableEurekaClient @SpringBootApplication @EnableFeignClients public class DeptConsumer80Feign_App { public static void main(String[] args) { SpringApplication.run(DeptConsumer80Feign_App.class,args); } }
注意:Feign的負載均衡自定義方式 仍是和Ribbon的同樣
測試:
一、啓動3個Eureka cloud-eureka-7001|7002|7003 二、啓動3個微服務 cloud-provider-dept-8001|8002|8003 三、啓動服務消費者 cloud-consumer-dept-80-feign 四、訪問:http://localhost:80/consumer/dept/list