负载均衡对于一个系统架构来说非常重要,是必须要有的一个基础设施,它能够有效的缓解网络压力和流量扩容。我们知道的负载均衡可能分为两种,一种是硬件的负载均衡,比如F5服务器或者SLB负载设施;另外一种是软件负载均衡设施,比如我们耳熟能详的Nginx反向代理负载均衡、Lvs(基于IP级别)负载均衡、Haproxy(TCP通讯层面)负载均衡等等。但是只要是负载均衡服务都是能以类似的架构方式来构建。
•Spring Cloud Ribbon是一个基于HTTP和TCP的客户端复杂均衡工具。他基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松的将面向服务的Rest模板请求自动转换成客户端负载均衡的服务调用。在服务架构中,以后我们学习的API网关以及Feign代理都是通过Ribbon为基础进行调用服务的。
对于Spring Cloud Ribbon 的负载均衡我们基于RestTemplate去使用,只需要在配置BEAN中加入注解@LoadBalanced即可, Ribbon负载均衡器:
•注意问题:我们的RestTemplate加上@LoadBalanced注解后会走这个类(轮训策略类)RibbonLoadBalancerClient,serverid必须是我们访问的服务名称 ,当我们直接输入ip的时候获取的server是null,就会抛出异常;
模拟实现:
1. 创建spring-cloud-02-eureka引入依赖:
<parent>
<groupId>com.cc</groupId>
<artifactId>spring-cloud-matser</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-02-eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<!-- 版本配置项 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-02-eureka-server</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.cc.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
配置application.properties:
spring.application.name=eureka
server.port=8001
##主机名称,在hosts文件中配置后可以直接使用配置的名字
eureka.instance.hostname=eureka1
##关闭自我保护机制,如不关闭,eureka容易出现在一定时间内无法使用,就不能保证AP高可用
eureka.server.enable-self-preservation=false
##定时清理不可用的服务列表信息,配置定时时间周期,默认60秒(服务下线)
eureka.server.eviction-interval-timer-in-ms=10000
##客户端向服务器端(注册中心)发送的心跳间隔时间
eureka.instance.lease-renewal-interval-in-seconds=10
##服务器注册租期到期的时间:Eureka服务在收到最后一次心跳的时候算起,如果后续30秒内没有收到任何的心跳,Eureka主动标示该服务会被剔除,然后由定时任务清理服务列表
eureka.instance.lease-expiration-duration-in-seconds=30
##注册中心的访问地址
eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
#高可用:将eureka1的注册地址配置为eureka2,即互相配置对方
#eureka.client.service-url.defaultZone=http://eureka2:8002/eureka
创建Application.java:
package com.cc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer //启用服务发现,注册中心
@SpringBootApplication //springboot 核心配置
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 创建spring-cloud-02-provider-1和spring-cloud-02-provider-2用于模拟两个生产者,除名字和端口外其他内容一致:
配置pom:
<parent>
<groupId>com.cc</groupId>
<artifactId>spring-cloud-matser</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-02-provider-1</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 标示这个工程是一个服务,需要引入此jar -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 动态刷新的一个模块jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-02-provider-1</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.cc.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
配置application.properties:
#两个结点做负载均衡,service那么必须相同
spring.application.name=provider-service
##微服务方式尽量不要加server.context-path,只用名字加方法实现
#server.context-path=/provider
server.port=7001
##需要引入eureka注册中心的地址
##下面两条配置代表注册到注册中心后显示自己的IP地址,实际工作中尽量加上
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${server.port}
##租期到期时间间隔
#eureka.instance.lease-expiration-duration-in-seconds=30
##租期更新时间间隔
#eureka.instance.lease-renewal-interval-in-seconds=10
##开启健康检查,必须要引入spring-boot-starter-actuator动态刷新jar
eureka.client.healthcheck.enabled=true
eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
package com.cc.springcloud.entity;
import java.io.Serializable;
public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String id;
private String name;
public User(String id, String name) {
super();
this.id = id;
this.name = name;
}
public User() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
----------------------------------------------------------------------------------------------------------------------------------------
package com.cc.springcloud.api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.cc.springcloud.entity.User;
@RestController
public class UserController {
@RequestMapping(value="/getUser",method= {RequestMethod.GET})
public User getUser(@RequestParam("id")String id) throws InterruptedException {
System.out.println("provider-1---->1"+id);
Thread.sleep(1000);
return new User(id,"张三");
}
@RequestMapping(value="/postUser",method= {RequestMethod.POST})
public User postUser(@RequestParam("id")String id) throws InterruptedException {
System.out.println("provider-1---->1"+id);
Thread.sleep(1000);
return new User(id,"李四");
}
}
--------------------------------------------------------------------------------------------------------------------------
package com.cc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient //标示是一个具体的服务,需要向注册中心注册
@SpringBootApplication //springboot 核心配置
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3.创建spring-cloud-02-consumer来模拟消费者:
配置pom:
<parent>
<groupId>com.cc</groupId>
<artifactId>spring-cloud-matser</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>spring-cloud-02-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 标示这个工程是一个服务,需要引入此jar -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 动态刷新的一个模块jar -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 引入ribbon组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>spring-cloud-02-consumer</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.cc.springcloud.Application</mainClass>
</configuration>
</plugin>
</plugins>
</build>
配置application.properties:
spring.application.name=consumer-service
##微服务方式尽量不要加server.context-path,只用名字加方法实现
#server.context-path=/provider
server.port=7003
##需要引入eureka注册中心的地址
##下面两条配置代表注册到注册中心后显示自己的IP地址,实际工作中尽量加上
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ipAddress}:${server.port}
##租期到期时间间隔
#eureka.instance.lease-expiration-duration-in-seconds=30
##租期更新时间间隔
#eureka.instance.lease-renewal-interval-in-seconds=10
##开启健康检查,必须要引入spring-boot-starter-actuator动态刷新jar
eureka.client.healthcheck.enabled=true
eureka.client.service-url.defaultZone=http://eureka1:8001/eureka
User类一致;
创建ConsumerController:
package com.cc.springcloud.api;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.cc.springcloud.entity.User;
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/get")
public String get() {
ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://provider-service/getUser?id=001", User.class);
User user = responseEntity.getBody();
System.out.println("username"+user.getName());
return "get success";
}
@RequestMapping(value="/post")
public String post() {
//模拟表单提交此处使用Map报错
//Map<String,String> params = new HashMap<String,String>();
//使用org.springframework.util.MultiValueMap类表单提交对象
MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
params.set("id", "002");
ResponseEntity<User> responseEntity = restTemplate.postForEntity("http://provider-service/postUser", params,User.class);
User user = responseEntity.getBody();
System.out.println("username"+user.getName());
return "post success";
}
}
创建Application:
package com.cc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient //标示是一个具体的服务,需要向注册中心注册
@SpringBootApplication //springboot 核心配置
public class Application {
@Bean
@LoadBalanced //用于实现内部的服务负载均衡机制:service-id或service-name
public RestTemplate restTemplate() {
//通过工厂模式创建返回RestTemplate
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
//设置超时时间
httpComponentsClientHttpRequestFactory.setConnectTimeout(10000);
httpComponentsClientHttpRequestFactory.setConnectionRequestTimeout(1000);
httpComponentsClientHttpRequestFactory.setReadTimeout(20000);
return new RestTemplate(httpComponentsClientHttpRequestFactory);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.整体启动模拟访问:
模拟实现了Ribbon负载均衡;