13-服务发现、负载均衡
服务发现、负载均衡
服务发现
Service Discovery,本质也是一个服务(与配置服务类似)
好处
快速水平伸缩,而不是垂直伸缩。不影响客户端
水平伸缩:服务实例数动态增加或减少
提高应用程序的弹性
一个服务(发生故障)挂了,服务中心会将其删除,同时也会通知客户端
Eureka服务发现引擎
服务端向服务中心注册自己的信息:服务名、IP地址、端口号;
客户端向服务中心提供需要访问的服务名,服务中心向其返回实例有哪些、端口以及IP
Ribbon,客户端负载均衡
主要提供客户侧的软件负载均衡算法
服务端启动后会向服务中心注册
客户端访问服务时,Ribbon按照一定策略返回服务的实例
- 轮询策略(按照顺序)
- 随机策略
访问时获取的服务信息也有缓存
服务调用关系
注册服务
eurekasvr
添加依赖
1
2
3
4
5
6
7<!-- 服务端 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>启动类加上
@EnableEurekaServer
注解1
2
3
4
5
6
7
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}application.yml
配置文件1
2
3
4
5
6
7
8
9
10
11server:
port: 8761
eureka:
client:
registerWithEureka: false # 当前服务是否要注册到服务中心去
fetchRegistry: false # 是否要(定时?)更新服务信息
server:
waitTimeInMsWhenSyncEmpty: 5 # 服务注册后等待多久才对外提供服务信息(单位:毫秒)
serviceUrl: # 当前服务的url
defaultZone: http://localhost:8761
confsvr
application.yml
中添加:
1 | spring.cloud.config.discovery.enabled=true |
organizationservice
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>启动类添加
@EnableEurekaClient
注解意味着当前服务启动时,需要与EurekaSever进行交互,将当前服务的信息登记到EurekaServer中去
application.yml
中配置需要登记的信息1
2
3
4
5
6
7
8eureka:
instance:
preferIpAddress: true # 记住ip地址而不是机器的机器名
client:
registerWithEureka: true # 服务信息需要注册到服务中心去
fetchRegistry: true # 定期刷新
serviceUrl: # 服务中心的Url
defaultZone: http://localhost:8761/eureka/
查找和调用服务(客户端如何调用服务端的接口)
- 第三方库:Ribbon,本地缓存,本地负载均衡
- 三种方式
- Spring DiscoveryClient(不建议)
- 使用支持Ribbon的RestTemplate
- 使用Netflix Feign
licensingservice
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>配置文件
1
2
3
4
5
6
7
8eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
注:以下代码都是在licensingservice中进行编写
Spring DiscoveryClient(比较原始,不结合Ribbon)
启动类添加注解
@EnableDiscoveryClient
不指定实现工具- 或
@EnableEurekaClient
指定实现工具为Eureka
1
2
3
4
5
6
7
8
// @EnableEurekaClient // 二选一
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}创建类
OrganizationDiscoveryClient
可以在client包下)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.web.client.RestTemplate; 、// springweb里的,和服务发现没关系
public class OrganizationDiscoveryClient {
// 注入对象
private DiscoveryClient discoveryClient;
// 获取实例
public Organization getOrganization(String organizationId) {
//
RestTemplate restTemplate = new RestTemplate();
// 指定服务名,获取实例(可能有多个)
List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice");
if (instances.size() == 0) return null;
// 组装出实例的uri
String serviceUri = String.format("%s/v1/organizations/%s", instances.get(0).getUri().toString(), organizationId);
System.out.println("!!!! SERVICE URI: " + serviceUri);
// 给organization发送一个get请求(url见上一行代码)
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
serviceUri,
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}
}
支持Ribbon的RestTemplate
启动类无需添加注解,但需要注入bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import org.springframework.cloud.client.loadbalancer.LoadBalanced;
public class Application {
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
public IRule ribbonRule() {
return new RandomRule();
}
}创建client类
- 可以发现这里没有获取实例(也就是没有IP地址和端口号),而是直接使用了服务名,原理就是在启动类中对restTemplate进行了拦截(基于Ribbon的注解
@LoadBalanced
实现的效果)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class OrganizationRestTemplateClient {
RestTemplate restTemplate;
public Organization getOrganization(String organizationId){
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
"http://organizationservice/v1/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}
}- 可以发现这里没有获取实例(也就是没有IP地址和端口号),而是直接使用了服务名,原理就是在启动类中对restTemplate进行了拦截(基于Ribbon的注解
Netflix Feign(最简便)
添加依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>启动类添加注解
1
2
3
4
5
6
7
8
9
10
11import org.springframework.cloud.netflix.feign.EnableFeignClients;
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}创建client接口并添加注解
@FeignClient("organizationservice")
1
2
3
4
5
6
7
8
public interface OrganizationFeignClient {
// 需要返回的数据格式
Organization getOrganization(; String organizationId)
}使用时直接将接口的对象注入
@Autowired
部署脚本
docker-compose的作用是启动所有的服务,所以在此之前要将所有的服务进行打包镜像
=> 在每个子项目的根目录下运行
mvn clean package docker:build
位置:总项目根目录/docker/default/docker-compose.yml
1 | version: '2' |
Eureka使用
- GET请求
http://localhost:8761/eureka/apps/服务名
- 未在HEADERS中指定返回格式,则默认是HTML格式
- 若指定格式为
application/json
,则为JSON格式
- 也可以直接在浏览器中打开
http://localhost:8761
负载均衡
在通过docker-compose启动实例时,改成如下命令:
1
docker-compose up --scale organizationservice=3 # 表示启动三个organizationservice实例
策略自定义:对RestTemplate和Feign都有用
在
organizationservice
的启动类中,添加代码:1
2
3
4
public IRule ribbonRule() {
return new RandomRule(); // 随机策略,现有最好的策略
}
策略参考
- RoundRobinRule 轮询
- RandomRule 随机