Ribbon是基於Netflix Ribbon 實現的一套客戶端負載均衡的工具java
負載均衡LB (Load Balance),簡單的來講就是將用戶的請求平攤的分配到多個服務上, 從而達到系統的HA(高可用)mysql
負載均衡的分類web
因爲Ribbon是在客戶端進行負載均衡的操做, 所以咱們只須要操做consumer服務就好了算法
<!--Ribbon--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <version>1.4.6.RELEASE</version> </dependency> <!--Eureka--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.6.RELEASE</version> </dependency>
注意spring
package com.wang.springcloud.config; 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 @LoadBalanced //Ribbon //註冊RestTemplate @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
注意sql
package com.wang.springcloud.controller; import com.wang.springcloud.pojo.Dept; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; 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.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @ApiModel("Consumer Controller") @RestController public class DeptConsumerController { //消費者, 不該該有service層!(爲了實現解耦) //RestFul模板 ==> RestTemplate, 裏面有不少方法供咱們直接調用, 須要註冊到Spring中 //遠程調用provider的url //在ConfigBean中註冊了, 這裏能夠直接自動裝配(AutoWired是按照類型自動裝配) //RestTemplate的方法 ==> url, 請求的實體(request), 響應的類型(response)Class<T> //RestTemplate實質上就是提供多種便捷訪問遠程http服務的方法, 是一個簡單的RestFul服務模板 @Autowired private RestTemplate restTemplate; //經過Ribbon, 咱們這裏的地址應該是一個變量, 不要寫死, 經過服務名來訪問(provider的spring.applicatoion.name的大寫), 不要忘了寫http:// private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT"; @ApiOperation("經過部門編號查詢一個部門") @RequestMapping(value = "/consumer/dept/get/{id}", method = RequestMethod.GET) public Dept get(@PathVariable("id") @ApiParam("部門編號") Long id) { //因爲咱們在provider中的list是get方法, 這裏調用getForObject方法 return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class); } @ApiOperation("經過部門名稱添加一個部門") @RequestMapping(value = "/consumer/dept/add", method = RequestMethod.POST) public boolean add(@ApiParam("部門的名稱") Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class); } @ApiOperation("查詢所有的部門") @RequestMapping(value = "consumer/dept/list", method = RequestMethod.GET) public List<Dept> list() { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class); } }
注意數據庫
這裏的核心代碼是app
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
經過服務名來訪問provider負載均衡
因爲是經過服務名來訪問provider, 所以作provider的被發現的服務要有相同的 spring.applicatoion.name運維
這裏本質上仍是url, 不要忘了寫 http://
server: port: 8080 #Eureka配置 eureka: client: register-with-eureka: false #咱們這裏是消費者, 是客戶端, 所以不須要想Eureka中註冊本身 service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
注意
package com.wang.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; //Ribbon 和 Eureka 整合之後, 客戶端能夠直接調用, 不用關心IP地址和端口號 @SpringBootApplication @EnableEurekaClient public class DeptConsumer_8080 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_8080.class, args); } }
注意
這裏添加了三個服務的提供者, 端口分別爲8001, 8002, 8003
注意
三個服務的提供者內容基本相同, 由於要作負載均衡
注意要修改application的配置, 主要是端口號
因爲Ribbon是經過服務的名稱進行發現並負載均衡的, 注意這裏的Spring的名字要相同, 即
#Spring的配置 spring: application: name: springcloud-provider-dept #三個服務名稱一致是前提!
這裏以第三個數據庫爲例, 給出建庫的sql代碼
create database /*!32312 IF NOT EXISTS*/`db03` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use `db03`; /*Table structure for table `dept` */ drop table if exists `dept`; create table `dept` ( `deptno` bigint(20) not null auto_increment, `dname` varchar(60) default null, `db_source` varchar(60) default null, primary key (`deptno`) ) engine=innodb auto_increment=1 default charset=utf8mb4 comment='部門表'; /*Data for the table `dept` */ 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());
@Bean public IRule myRule() { return new RandomRule(); }
注意
注意
package com.wang.springcloud; import com.wang.myRule.WangRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; //Ribbon 和 Eureka 整合之後, 客戶端能夠直接調用, 不用關心IP地址和端口號 @SpringBootApplication @EnableEurekaClient //在微服務啓動的時候, 就能夠去加載咱們自定義的Ribbon類 @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT", configuration = WangRule.class) public class DeptConsumer_8080 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_8080.class, args); } }
注意
package com.wang.myRule; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class WangRandomRule extends AbstractLoadBalancerRule { /** * 每一個服務訪問5次, 換下一個服務 * * total=0, 默認=0, 若是=5, 咱們指向下一個服務節點 * index=0, 默認0, 若是total=5, index+1 */ // @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE") private int total = 0; //總共被調用的次數 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) { return null; } // //生成區間隨機數 // int index = chooseRandomInt(serverCount); // //從活着的服務中隨機獲取一個 // server = upList.get(index); //-============================================ if (total < 5) { server = upList.get(currentIndex); total++; } else { total = 0; currentIndex++; //index從0開始, list的大小要-1, 這裏用 >= if (currentIndex >= upList.size()) { currentIndex = 0; } //從活着的服務中, 獲取指定的服務來進行操做 server = upList.get(currentIndex); } //-============================================ if (server == null) { Thread.yield(); continue; } if (server.isAlive()) { return (server); } server = null; Thread.yield(); } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub } }
package com.wang.myRule; import com.netflix.loadbalancer.IRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class WangRule { @Bean public IRule myRule() { return new WangRandomRule(); } }