一.Ribbon介绍
使⽤ Eureka 后我们可以通过Eureka服务端找到我们注册的微服务客户端,然后通过我们的⽹络请求调⽤,但是在实际开发中,我们的微服务都是集群⽤于提⾼可⽤性,那么我们在调⽤这些服务的时候,如何实现负载均衡,我们就需要⽤到Ribbon 官⽅⽂档中说明Feign 中⾃带 ribbon,所以使⽤Feign的时候不需要导⼊Ribbon的依赖包
Ribbon是Netflix发布的云中间层服务开源项⽬,其主要功能是提供客户端侧负载均衡算法。Ribbon客户端组件提供⼀系列完善的配置项如连接超时,重试等。简单的说,Ribbon是⼀个客户端负载均衡器,我们可以在配置⽂件中列出Load Balancer后⾯所有的机器,Ribbon会⾃动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使⽤Ribbon实现⾃定义的负载均衡算法
下图展示了Eureka使⽤Ribbon时候的⼤致架构:
Ribbon⼯作时分为两步:第⼀步先选择 Eureka Server, 它优先选择在同⼀个Zone且负载较少的Server;第⼆步再根据⽤户指定的策略,在从Server取到的服务注册列表中选择⼀个地址。其中Ribbon提供了多种策略,例如轮询round robin、随机Random、根据响应时间加权等
二.Ribbon的使用
准备两个商品服务,分别指向不同的端口8001和8002
application-8001.yml
spring:
application:
name: goods
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8001
application-8002.yml
spring:
application:
name: goods
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
server:
port: 8002
goodsController
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Value("${server.port}")
private String port;
@GetMapping("/msg")
public String msg(){
String msg = "this is goods page"+port;
return msg;
}
}
当访问该路径时,可以看到对应的端口,这就就完成简单的负载均衡(轮询)
Ribbon是客户端的负载均衡
springcloud中,ribbon默认采用轮询的方式
添加配置指定负载均衡策略
IRule接口的实现类,则是Ribbon提供的各种负载均衡策略
具体配置:
服务名:
ribbon:
NFLoadBalancerRuleClassName: 负载均衡策略的类全路径名
如:
GOODS:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
RoundRobinRule:轮询策略
RandomRule:随机策略
AvailabilityFilteringRule:可用过滤策略
WeightedResponseTimeRule:响应时间权重策略
RetryRule:轮询失败重试策略
BestAvailableRule:并发量最小可用策略
.....
三.eureka集群实现负载均衡
准备七个服务
三个eureka服务 #7001,7002,7003
三个生产者 #8001,8002,8003
一个消费者 #80
eureka集群环境搭建:
eureka-7001服务:
1.依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2.application.yml配置
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
#prefer-ip-address: true
client:
register-with-eureka: false
fetch-registry: false
service-url: #分别指向另外两个服务
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
spring:
application:
name: eureka7001.com
3.添加监听器,方便监听注册服务的启动和注册
@Component
public class EurekaListener {
@EventListener
public void canceledEvent(EurekaInstanceCanceledEvent event){
String appName = event.getAppName();
System.out.println(appName+"已经下线!!!");
}
@EventListener
public void canceledEvent(EurekaInstanceRegisteredEvent event){
String appName = event.getInstanceInfo().getAppName();
System.out.println(appName+"刚刚注册到注册中心!!!");
}
@EventListener
public void canceledEvent(EurekaInstanceRenewedEvent event){
String appName = event.getAppName();
System.out.println(appName+"前来续约!!!");
}
@EventListener
public void canceledEvent(EurekaRegistryAvailableEvent event){
System.out.println("启动中心启动中!!!");
}
@EventListener
public void canceledEvent(EurekaServerStartedEvent event){
System.out.println("注册中心已经启动!!!");
}
4.启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
7002和7003的eureka服务对应着上面将7001改成对应的7002和7003
生产者环境搭建:
provider-dept-8001服务:
1.依赖
<dependencies>
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--完善监控信息-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
</dependencies>
2.application.yml
server:
port: 8001
mybatis:
type-aliases-package: com.qf.scloudbibik.pojo
config-location: classpath:mybatis.xml
mapper-locations: classpath:mapper/*.xml
spring:
application:
name: provider-dept
datasource:
password: 123
username: root
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql:///db01
eureka:
client:
service-url: #指向所有注册中心的地址
defaultZone: http://localhost:7002/eureka/,http://localhost:7001/eureka/,http://localhost:7003/eureka/
instance:
instance-id: springcloud-provider-dept8001
3.controller(此处省去service和mapper代码)
@RestController
public class DeptController {
@Autowired
private IDeptService deptService;
@GetMapping("/dept/add")
public boolean addDept(Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id){
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll(){
return deptService.queryAll();
}
//注册进来的微服务,获取一些消息
@RequestMapping("/dept/discovery")
public Object discovery(){
//获取微服务列表的清单
List<String> services = client.getServices();
System.out.println(services);
//得到一个具体的微服务信息
List<ServiceInstance> instances = client.getInstances("PROVIDER-DEPT");
for (ServiceInstance instance : instances){
System.out.println(instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri()+"\t"+instance.getServiceId());
}
return client;
}
}
4.启动类
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class DeptProviderApplicaiton_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderApplicaiton_8001.class,args);
}
}
另外两个生产者服务依次按上面搭建,只需修改对应的端口名称和数据库
消费者:
consumer-dept-80服务
1.依赖
<dependencies>
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
</dependencies>
2.application.yml
server:
port: 80
eureka:
client:
register-with-eureka: true
service-url: #指向所有的eureka
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
spring:
application:
name: consumer-dept
3.restTemplate配置
@SpringBootConfiguration
public class ConfigBean {
//配置负载均衡实现restTemplate,默认策略是轮询
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
4.controller
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
// private static final String REST_FUL_PREFIXX="http://127.0.0.1:8001";
//此处应指向生产者集群的application.name(服务名)
private static final String REST_FUL_PREFIXX="http://PROVIDER-DEPT";
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_FUL_PREFIXX+"/dept/get/"+id,Dept.class);
}
@RequestMapping("/consumer/dept/add ")
public boolean add(Dept dept){
return restTemplate.getForObject(REST_FUL_PREFIXX+"/dept/add"+dept,Boolean.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(REST_FUL_PREFIXX+"/dept/list",List.class);
}
}
5.启动类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient
public class DeptConsumerApplication_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerApplication_80.class,args);
}
}
启动所有服务,访问localhost:7002/,或其他端口,出现如下页面说明搭建成功
再访问localhost/consumer/dept/list,测试成功
四.Ribbon自定义负载均衡策略
自定义负载均衡策略:
//自定义负载均衡策略(随机策略的基础上修改~~~)
public class MyselfRule extends AbstractLoadBalancerRule {
/*
* 自定义策略:每个服务访问5次,然后换下一个服务
* total=0,默认为0,每执行一次加1,如果=5,设为0并指向下一个节点
* index=0,默认为0,如果total=5,index+1,当执行完对应的服务次数,需再次重启
*
*/
private int total = 0; //被调用的次数
private int currentIndex= 0; //当前是谁在提供服务
private Logger logger = LoggerFactory.getLogger(getClass());
public MyselfRule() {
}
//@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers(); //获得活着的服务
List<Server> allList = lb.getAllServers(); //获得全部的服务
// logger.info("全部的服务"+allList);
// logger.info("活着的服务"+upList);
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//随机策略
// int index = this.chooseRandomInt(serverCount); //生成区间随机数
// server = (Server)upList.get(index); //从活着的服务中,随机获取一个
//-----------------------------------
//自定义策略
if(total<5){
server = upList.get(currentIndex);
logger.info("执行轮数:"+currentIndex+"执行次数:"+total+"当前服务:"+server);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex>upList.size()){
currentIndex = 0;
}
server = upList.get(currentIndex); //从活着的服务中,获取指定的服务来进行操作
}
//-----------------------------------
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
1.在配置类中指定自定义策略
@Configuration
public class MyRule {
@Bean
public IRule random(){
return new MyselfRule();
}
}
2.修改启动类配置
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient
//在微服务启动的时候就能去加载我们自己定义的负载均衡策略
//name值为生产者集群的服务名,confuguration为自定义策略的类
@RibbonClient(name = "PROVIDER-DEPT",configuration = MyselfRule.class)
public class DeptConsumerApplication_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerApplication_80.class,args);
}
}
注意:
也就是说当我们自定义负载均衡策略时,自定义的类所在的包不能和启动类位于同一级或者不能位于其子包中,使用@ComponentScan注解时,指定的路径要避免自定义的策略类,如:
启动服务,多次访问localhost/consumer/dept/list,然后查看控制台