一、系统架构的演变
随着互联网的发展,网站应用的规模不断扩大。需求的激增,带来的是技术上的压力。系统架构也因此不断的演进、升级、迭代。从单一应用,到垂直拆分,到分布式服务,到SOA,以及现在火热的微服务架构,还有在Google带领下来势汹涌的Service Mesh。我们到底是该乘坐微服务的船只驶向远方,还是偏安一隅得过且过?
其实生活不止眼前的苟且,还有诗和远方。所以我们今天就回顾历史,看一看系统架构演变的历程;把握现在,学习现在最火的技术架构;展望未来,争取成为一名优秀的Java工程师。
1.1 集中式架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。
存在的问题:
1.代码耦合,开发维护困难
2.无法针对不同模块进行针对性优化
3.无法水平扩展
4.单点容错率低,并发能力差
1.2 垂直拆分
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分。
优点:
1.系统拆分实现了流量分担,解决了并发问题
2.可以针对不同模块进行优化
3.方便水平扩展,负载均衡,容错率提高
缺点:
系统间相互独立,会有很多重复开发工作,影响开发效率
1.3 分布式服务
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
优点:
将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
缺点:
系统间耦合度变高,调用关系错综复杂,难以维护
1.4 流动计算架构(SOA)
SOA :面向服务的架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
以前出现了什么问题?
服务越来越多,需要管理每个服务的地址
调用关系错综复杂,难以理清依赖关系
服务过多,服务状态难以管理,无法根据服务情况动态管理
服务治理要做什么?
服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
动态监控服务状态监控报告,人为控制服务状态
缺点:
服务间会有依赖关系,一旦某个环节出错会影响较大
服务关系复杂,运维、测试部署困难,不符合DevOps思想
1.5 微服务
就目前而言,业界对微服务并没有一个统一的标准的定义。但通常而言,微服务架构是一种架构模式或者架构风格,它提倡将单一应用程序划分成一组小的服务。每个服务运行在其独立的进程中。服务之间互相协调互相配合,为用户提供最终价值。服务之间采取轻量级的通信机制互相沟通(通常是基于HTTP的Restful API).每个服务都围绕这具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应尽量的避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务,可以使用不同的语言来编写这些服务,也可以使用不同的数据存储。
二、服务调用方式
RPC和HTTP
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下2种:
RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。
现在热门的Rest风格,就可以通过http协议来实现。
如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。
相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。
三、初识springcloud
微服务是一种架构方式,最终肯定需要技术架构去实施。
微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?
1.后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。
2.技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了
3.群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?SpringCloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。
4.使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而SpringCloud完全支持SpringBoot的开发,用很少的配置就能完成微服务框架的搭建
3.1 springcloud简介
SpringCloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:
1.Eureka:服务治理组件,包含服务注册中心,服务注册与发现机制的实现。(服务治理,服务注册/发现)
2.Zuul:网关组件,提供智能路由,访问过滤功能
3.Ribbon:客户端负载均衡的服务调用组件(客户端负载)
4.Feign:服务调用,给予Ribbon和Hystrix的声明式服务调用组件 (声明式服务调用)
5.Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。(熔断、断路器,容错)
3.2 微服务场景模拟
首先,我们需要模拟一个服务调用的场景
搭建两个工程:oracle-service-provider(服务提供方)和oracle-service-consumer(服务调用方)。
服务提供方:使用mybatis操作数据库,实现对数据的增删改查;并对外提供rest接口服务。
服务消费方:使用restTemplate远程调用服务提供方的rest接口服务,获取数据。
(1)创建服务提供者springclouddemo1_provider
- 在项目中引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 需要手动引入通用mapper的启动器,spring没有收录该依赖 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- Application.yml配置
server:
port: 8086
spring:
datasource:
username: root
password: 123
url: jdbc:mysql://localhost:3306/ssm?serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
- 数据表
- 实体类
package com.bianyiit.pojo;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
@Table(name = "account") //实体类绑定的表
@Data //lombok插件
public class Account implements Serializable {
private static final long serialVersionUID = 3024382278891947015L;
@Id //标识当前id为主键
@GeneratedValue(strategy= GenerationType.IDENTITY) //主键自增长
private Integer id;
private String name;
private Double money;
}
- UserMapper
package com.bianyiit.mapper;
import com.bianyiit.pojo.Account;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.common.Mapper;
//mapper插件对于单表的增删改查操作都不用我们自己写 tk.mybatis.mapper.common.mapper
@Repository
public interface AccountMapper extends Mapper<Account> {
//集成Mapper(里面的泛型为实体类)
}
- UserService
package com.bianyiit.service;
import com.bianyiit.mapper.AccountMapper;
import com.bianyiit.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
@Autowired
AccountMapper accountMapper;
//根据id查询Account
public Account findAccountById(Integer id){
return accountMapper.selectByPrimaryKey(id); //通用mapper中提供了很多方法可以让我们去操作单表
}
}
- UserController
package com.bianyiit.controller;
import com.bianyiit.pojo.Account;
import com.bianyiit.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/account")
public class AccountController {
@Autowired
AccountService accountService;
@GetMapping("/findAccountById/{id}") //localhost:8080/account/findAccountById/2
public Account getAccountById(@PathVariable("id") Integer id){
return accountService.findAccountById(id);
}
}
- 编写springboot启动类
package com.bianyiit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.bianyiit.mapper") //设置包扫描mapper,通用mapper提供的注解(tk.mybatis.spring.annotation.MapperSan)
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class);
}
}
- 启动并测试
启动项目,访问接口:http://localhost:8081/account/findAccountById/2
(1)创建服务消费者springclouddemo1_consumer
- 导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 配置application.yml
server:
port: 8084
- 提供User实体
package com.bianyiit.pojo;
import lombok.Data;
import java.io.Serializable;
//这里不用使用@Table注解修饰,因为不需要去连接数据库查表,只需要进行数据的封装
@Data
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
}
- 提供配置类
package com.bianyiit.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 MyConfig {
//注册RestTemplate,用于服务之间的远程调用
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 提供AccountController
package com.bianyiit.controller;
import com.bianyiit.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/consumer")
public class AccountController {
@Autowired
RestTemplate restTemplate; //使用它做一个远程调用
@GetMapping("/findAccountById/{id}")
public Account findAccountById(@PathVariable("id") Integer id){
//http://localhost:8081/account/findAccountById?id=2 @RequstParam
//http://localhost:8081/account/findAccountById/2 @PathVariable
return restTemplate.getForObject("http://localhost:8081/account/findAccountById/"+id,Account.class); //进行远程调用
}
}
- 启动并访问,访问路径:http://localhost:8082/consumer/findAccountById/2
3.3 以上案例存在的问题
四、Eureka注册中心
4.1 认识Eureka
首先我们来解决第一问题,服务的管理。
- 问题分析
在刚才的案例中,oracle-service-provider对外提供服务,需要对外暴露自己的地址。而consumer(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。 - 网约车
这就好比是网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美! - Eureka做什么?
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。同时,服务提供方与Eureka之间通过“心跳”机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。
4.2 Eureka原理图
- Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
- 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
- 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
4.3 搭建Eureka Server
- 引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencies>
<!--Eureka启动器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<!--统一管理springcloud的版本号-->
<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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
- 编写application.yml配置
server:
port: 10087
spring:
application:
name: springclouddemo1_enableEureka #注册的服务名称,会在eureka中显示
eureka:
client:
service-url: #配置eureka的默认访问地址
defaultZone: http://127.0.0.1:10086/eureka
- 修改引导类,在类上添加@EnableEurekaServer注解
package com.bianyiit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer 声明当前应用是一个Eureka服务中心
public class ServiceEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceEurekaApplication.class, args);
}
}
- 启动服务并访问,启动服务,并访问:http://127.0.0.1:10086/
4.4 将服务注册到eureka
- 修改springclouddemo1_provider的applicaton.yml配置文件
server:
port: 8086
spring:
datasource:
username: root
password: 123
url: jdbc:mysql://localhost:3306/ssm?serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
application:
name: springclouddemo1-provider #注册到eureka的服务名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka #连接eureka的地址
- 在springclouddemo1启动类ServiceProviderApplication上开启eureka客户端
- 重启项目,访问Eureka监控页面查看
4.5 从Eureka拉取服务
- 修改springclouddemo1_consumer的applicaton.yml配置文件![]
server:
port: 8084
spring:
application:
name: springclouddemo1_consumer
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
- 在springclouddemo1_consumer启动类ServiceConsumerApplication上开启eureka客户端
- 重启项目,访问Eureka监控页面查看
4.6 改造服务消费方拉取服务列表硬编码问题
package com.bianyiit.controller;
import com.bianyiit.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/consumer")
public class AccountController {
@Autowired
RestTemplate restTemplate; //使用它做一个远程调用
@Autowired
DiscoveryClient discoveryClient; //服务消费方在eureka注册中心拉取的服务列表都封装到DiscoveryClient里面
@GetMapping("/findAccountById/{id}")
public Account findAccountById(@PathVariable("id") Integer id){
// 根据服务名称,获取服务实例。有可能是集群,所以是service实例集合,需要根据application.yml中注册的服务名称去填写
List<ServiceInstance> instances = discoveryClient.getInstances("springclouddemo1_provider");
// 因为只有一个Service-provider,所以获取第一个实例
ServiceInstance instance = instances.get(0);
String url="http://"+instance.getHost()+":"+instance.getPort()+"/account/findAccountById/"+id;
return restTemplate.getForObject(url,Account.class);
}
}
五、Eureka详解
5.1 Eureka基础架构
Eureka架构中的三个核心角色:
- 服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的oracle-eureka。 - 服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的oracle-service-provider。 - 服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现oracle-service-consumer。
5.2 搭建高可用的Eureka(了解)
- Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,但是如果这个EurekaServer挂掉了会影响整个应用。事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心,一个Eureka服务中心挂掉了还有其他的服务注册中心。
- 服务同步:
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
动手搭建高可用的EurekaServer:
需求: 我们假设要运行两个EurekaServer的集群,端口分别为:10086和10087。只需要把bianyi-eureka启动两次即可。
复制一个新的Eureka注册中心
将10086端口的Eureka注册到10087端口号的Eureka,然后启动该Eureka注册中心
server:
port: 10086
spring:
application:
name: springclouddemo1_enableEureka #注册的服务名称,会在eureka中显示
eureka:
client:
service-url: #配置eureka的默认访问地址
defaultZone: http://127.0.0.1:10087/eureka
注意:此时启动会报错,很正常。因为10087服务没有启动
由于两个Eureka共用一个application.yml,所以启动完上一个之后,直接更改applicaiton.yml中的端口即可,将10087端口的Eureka注册到端口号是10086端口号的Eureka,然后启动该Eureka注册中心
server:
port: 10087
spring:
application:
name: springclouddemo1_enableEureka #注册的服务名称,会在eureka中显示
eureka:
client:
service-url: #配置eureka的默认访问地址
defaultZone: http://127.0.0.1:10086/eureka
注意:为什么要10086端口的Eureka注册到10087端口的Eureka呢??
因为多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
假如有三个Eureka注册中心,只需要三个Eureka首尾相连形成一个闭环即可
分别启动:
六、负载均衡Ribbon
在刚才的案例中,我们启动了一个oracle-service-provider,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。但是实际环境中,我们往往会开启很多个oracle-service-provider的集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。不过Eureka中已经帮我们集成了负载均衡组件:Ribbon,简单修改代码即可使用。
什么是Ribbon:
接下来,我们就来使用Ribbon实现负载均衡
6.1 负载均衡演示
注意:实现负载均衡,服务的名称不能带下划线,否则会消费者报java.lang.IllegalStateException: Request URI does not contain a valid hostname:的异常
- 需求:启动两个服务实例, 端口分别为8081和8083
启动两个服务实例,启动方式和启动多个Eureka步骤一样
- 开启负载均衡
因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码。
在服务消费方的引导类MyConfig上的RestTemplate的配置方法上添加@LoadBalanced注解
package com.bianyiit.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 MyConfig {
//注册RestTemplate,用于服务之间的远程调用
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用
访问页面,查看结果:http://localhost:8082/consumer/findAccountById/2
6.2 负载均衡的策略
Ribbon默认的负载均衡策略是简单的轮询,在springcloud中使用RibbonLoadBalanceClient来进行负载均衡的。其中有一个choose方法,找到choose方法的接口方法,是这样介绍的:
现在这个就是负载均衡获取实例的方法。
我们注入这个类的对象,然后对其测试:
package com.bianyiit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ServiceConsumerApplication.class)
public class LoadBalanceTest {
@Autowired
LoadBalancerClient client;
@Test
public void testLoadBalance(){
for(int i = 1;i<=100;i++){
ServiceInstance instance = client.choose("springclouddemo1-provider");
System.out.println(instance.getHost() + ":" +instance.getPort());
}
}
}
测试结果:
符合了我们的预期推测,确实是轮询方式。
我们也可以改变默认的负载均衡策略
SpringBoot也帮我们提供了修改负载均衡规则的配置入口,在springclouddemo1_consumer的application.yml中添加如下配置:
server:
port: 8084
spring:
application:
name: springclouddemo1_consumer
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
springclouddemo1-provider: #注意这里填写的是你的服务提供者注册的名字
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #改变负载均衡策略为随机
再次测试,发现结果变成了随机:
springcloud入门案例参考源码
链接:https://pan.baidu.com/s/1uy8m2DEPEtG8Cu2NupB1ng
提取码:gauq