一、系统结构
(1)Ribbon内置负载均衡算法
Ribbon的负载均衡算法需要实现IRule接口
- RoundRobinRule
轮询策略。Ribbon默认采用的策略。
- RandomRule
随机策略,从所有可用的provider中随机选择一个。
- RetryRule
先按照RoundRobinRule策略获取provider,若获取失败,则在指定的时限内重试。默认的时限为500毫秒。
- BestAvailableRule
选择并发量最小的provider,即连接的消费者数量最少的provider
- AvailabilityFilteringRule
该算法规则是:过滤掉处于断路器跳闸状态的provider,或已经超过连接极限的provider,对剩余provider采用轮询策略。
- ZoneAvoidanceRule
复合判断provider所在区域的性能及provider的可用性选择服务器。
- WeightedResponseTimeRule
“权重响应时间”策略。根据每个provider的平均响应时间计算其权重,响应时间越快权重越大,被选中的机率就越高。在刚启动时采用轮询策略。后面就会根据权重进行选择
二、创建提供者03-provider-8082
(1)创建工程
(2)依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.abc</groupId>
<artifactId>03-provider-8082</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>03-provider-8082</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--修改MySQL驱动版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3)application.yml配置
server:
port: 8082
spring:
jpa:
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: none
# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8
username: root
password: root
application:
name: abcmsc-provider-depart # 暴露微服务名称
logging:
# 设置日志输出格式
pattern:
console: level-%level %msg%n
level:
root: info
org.hibernate: info
org.hibernate.type.descriptor.sql.BasicBinder: trace
org.hibernate.type.descriptor.sql.BasicExtractor: trace
com.abc.provider: debug
# 指定Eureka服务中心
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
# defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
# instance:
# instance-id: abcmsc-provider-depart-8081
(4)实体类
package com.abc.provider.bean;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "fieldHandler"})
public class Depart {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
(5)service接口
package com.abc.provider.service;
import com.abc.provider.bean.Depart;
import java.util.List;
public interface DepartService {
boolean saveDepart(Depart depart);
boolean removeDepartById(int id);
boolean modifyDepart(Depart depart);
Depart getDepartById(int id);
List<Depart> listAllDeparts();
}
(6)service接口实现类
package com.abc.provider.service;
import com.abc.provider.bean.Depart;
import com.abc.provider.repository.DepartRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DepartServiceImpl implements DepartService {
@Autowired
private DepartRepository repository;
// 读取配置文件中的属性值
@Value("${server.port}")
private int port;
// 插入
@Override
public boolean saveDepart(Depart depart) {
// repository的save()方法需要注意:
// 参数对象若id为null,则执行的是insert
// 参数对象若具有id,则执行的是update
Depart obj = repository.save(depart);
if(obj != null) {
return true;
}
return false;
}
// 根据id删除
@Override
public boolean removeDepartById(int id) {
// 若指定id的对象不存在,则deleteById()会抛出异常
if(repository.existsById(id)) {
repository.deleteById(id);
return true;
}
return false;
}
@Override
public boolean modifyDepart(Depart depart) {
// repository的save()方法需要注意:
// 参数对象若id为null,则执行的是insert
// 参数对象若具有id,则执行的是update
Depart obj = repository.save(depart);
if(obj != null) {
return true;
}
return false;
}
@Override
public Depart getDepartById(int id) {
// repository.getOne(id)指定的id对象不存在,则会抛出异常
if(repository.existsById(id)) {
Depart depart = repository.getOne(id);
// 部门名称后加上端口号
depart.setName(depart.getName() + port);
return depart;
}
Depart depart = new Depart();
depart.setName("no this depart" + port);
return depart;
}
@Override
public List<Depart> listAllDeparts() {
List<Depart> departs = repository.findAll();
for(Depart depart : departs) {
// 部门名称后加上端口号
depart.setName(depart.getName() + port);
}
return departs;
}
}
(7)数据层
package com.abc.provider.repository;
import com.abc.provider.bean.Depart;
import org.springframework.data.jpa.repository.JpaRepository;
// 第一个泛型:当前Repository的操作对象类型
// 第二个泛型:当前Repository的操作对象的id类型
public interface DepartRepository extends JpaRepository<Depart, Integer> {
}
(8)控制层
package com.abc.provider.controller;
import com.abc.provider.bean.Depart;
import com.abc.provider.service.DepartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequestMapping("/provider/depart")
@RestController
public class DepartController {
@Autowired
private DepartService service;
// 注入服务发现客户端
@Autowired
private DiscoveryClient client;
@PostMapping("/save")
public boolean saveHandle(@RequestBody Depart depart) {
return service.saveDepart(depart);
}
@DeleteMapping("/del/{id}")
public boolean deleteHandle(@PathVariable("id") int id) {
return service.removeDepartById(id);
}
@PutMapping("/update")
public boolean updateHandle(@RequestBody Depart depart) {
return service.modifyDepart(depart);
}
@GetMapping("/get/{id}")
public Depart getHandle(@PathVariable("id") int id) {
return service.getDepartById(id);
}
@GetMapping("/list")
public List<Depart> listHandle() {
return service.listAllDeparts();
}
@GetMapping("/discovery")
public Object discoveryHandle() {
// 获取Eureka中所有的微服务名称
List<String> springApplicationNames = client.getServices();
// 遍历所有微服务
for(String name: springApplicationNames) {
// 根据微服务名称获取到所有提供该服务的主机信息
List<ServiceInstance> instances = client.getInstances(name);
for(ServiceInstance instance: instances) {
String host = instance.getHost();
int port = instance.getPort();
System.out.println(host + " : " + port);
}
}
return springApplicationNames;
}
}
(9)启动类
package com.abc.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProviderApplication8082 {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication8082.class, args);
}
}
三、创建提供者03-provider-8083
(复制03-provider-8082,命名03-provider-8083
(1) 修改配置文件
四、创建提供者03-provider-8084
复制03-provider-8082,命名03-provider-8084
(1) 修改配置文件
五、创建消费者03-consumer-loadbalance-8080
(1)创建项目
(2)依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.abc</groupId>
<artifactId>03-consumer-loadbalance-8080</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<!--feign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--actuator依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--eureka客户端依赖-->
<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-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3)application.yml配置
spring:
application:
name: abcmsc-consumer-depart
eureka:
client:
service-url:
defaultZone: http://localhost:8000/eureka
# defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
feign:
client:
config:
default:
connectTimeout: 5000 # 指定Feign客户端连接提供者的超时时限 取决于网络环境
readTimeout: 5000 # 指定Feign客户端从请求到获取到提供者给出的响应的超时时限 取决于业务逻辑运算时间
compression:
request:
enabled: true # 开启对请求的压缩
mime-types: text/xml, application/xml
min-request-size: 2048 # 指定启用压缩的最小文件大小
response:
enabled: true # 开启对响应的压缩
# 修改负载均衡策略方式一
# abcmsc-provider-depart: # 要负载均衡的提供者微服务名称
# ribbon: # 指定要使用的负载均衡策略
# NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
(4)实体类
package com.abc.consumer.bean;
import lombok.Data;
@Data
public class Depart {
private Integer id;
private String name;
}
(5)Feign接口
package com.abc.consumer.service;
import com.abc.consumer.bean.Depart;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Service
@FeignClient("abcmsc-provider-depart")
@RequestMapping("/provider/depart")
public interface DepartService {
@PostMapping("/save")
boolean saveDepart(@RequestBody Depart depart);
@DeleteMapping("/del/{id}")
boolean removeDepartById(@PathVariable("id") int id);
@PutMapping("/update")
boolean modifyDepart(@RequestBody Depart depart);
@GetMapping("/get/{id}")
Depart getDepartById(@PathVariable("id") int id);
@GetMapping("/list")
List<Depart> listAllDeparts();
}
(6)控制层
package com.abc.consumer.controller;
import com.abc.consumer.bean.Depart;
import com.abc.consumer.service.DepartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
@Autowired
private DepartService service;
@PostMapping("/save")
public boolean saveHandle(@RequestBody Depart depart) {
return service.saveDepart(depart);
}
@DeleteMapping("/del/{id}")
public boolean deleteHandle(@PathVariable("id") int id) {
return service.removeDepartById(id);
}
@PutMapping("/update")
public boolean updateHandle(@RequestBody Depart depart) {
return service.modifyDepart(depart);
}
@GetMapping("/get/{id}")
public Depart getHandle(@PathVariable("id") int id) {
return service.getDepartById(id);
}
@GetMapping("/list")
public List<Depart> listHandle() {
return service.listAllDeparts();
}
}
(7)Ribbon负载均衡配置
package com.abc.consumer.codeconfig;
import com.abc.consumer.irule.CustomRule;
import com.netflix.loadbalancer.IRule;
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;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class DepartCodeConfig {
// 开启消息者端的负载均衡功能,默认是轮询策略
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
// 指定Ribbon使用随机算法策略
// @Bean
// public IRule loadBalanceRule() {
// return new RandomRule();
// }
// 指定Ribbon使用随机算法策略
// @Bean
// public IRule loadBalanceRule() {
// List<Integer> ports = new ArrayList<>();
// ports.add(8083);
// return new CustomRule(ports);
// }
}
(8)自定义负载均衡类
从所有可用的provider中排除掉指定端口号的provider,剩余provider进行随机选择
package com.abc.consumer.irule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 自定义负载均衡算法:
* 从所有可用的provider中排除掉指定端口号的provider,剩余provider进行随机选择。
*/
public class CustomRule implements IRule {
private ILoadBalancer lb;
// 要排除的提供者端口号集合
private List<Integer> excludePorts;
public CustomRule() {
}
public CustomRule(List<Integer> excludePorts) {
this.excludePorts = excludePorts;
}
@Override
public Server choose(Object key) {
// 获取所有可用的提供者主机
List<Server> servers = lb.getReachableServers();
// 获取所有排除了指定端口号的提供者
List<Server> availableServers = this.getAvailableServers(servers);
// 从剩余的提供者中随机获取可用的提供者
return this.getAvailableRandomServers(availableServers);
}
// 获取所有排除了指定端口号的提供者
private List<Server> getAvailableServers(List<Server> servers) {
// 若没有要排除的主机,则返回所有
if(excludePorts == null || excludePorts.size() == 0) {
return servers;
}
// 创建一个集合,用于存放可用的主机
List<Server> aservers = new ArrayList<>();
for (Server server : servers) {
boolean flag = true;
// 判断当前遍历主机是否是要被排除的主机
for(Integer port : excludePorts) {
if(server.getPort() == port) {
flag = false;
break;
}
}
// 将不被排除的主机存放到集合中
if (flag) {
aservers.add(server);
}
}
return aservers;
}
// 从剩余的提供者中随机获取可用的提供者
private Server getAvailableRandomServers(List<Server> availableServers) {
// 获取一个[0,availableServers.size())的随机数
int index = new Random().nextInt(availableServers.size());
return availableServers.get(index);
}
@Override
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
@Override
public ILoadBalancer getLoadBalancer() {
return lb;
}
}
(9)启动类
package com.abc.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
// 指定Feign接口所在的包
@EnableFeignClients(basePackages = "com.abc.consumer.service")
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
六、创建00-eurekaserver-8000
(1)创建项目
(2)依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.abc</groupId>
<artifactId>00-eurekaserver-8000</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
(3)application.yml配置
server:
port: 8000
eureka:
instance:
hostname: localhost # 指定Eureka主机
client:
register-with-eureka: false # 指定当前主机是否向Eureka服务器进行注册
fetch-registry: false # 指定当前主机是否要从Eurka服务器下载服务注册列表
service-url: # 服务暴露地址
defaultZone: http://localhost:8000/eureka
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
# server:
# enable-self-preservation: false # 关闭自我保护
(4)启动类
package com.abc.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 开启Eureka服务
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
七、测试
(1)启动eureka
(2)启动03-provider-8082
(3)启动03-provider-8083
(4)启动03-provider-8084
(5)启动03-consumer-loadbalance-8080
(6)eureka服务列表
(7)效果
发送第一次
发送第二次
发送第三次