Eureka

微服务的注册中心。

服务端

1.添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2.应用启动入口标注@EnableEurekaServer

1
2
3
4
5
6
7
8
9
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerDemoApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerDemoApplication.class, args);
}

}

3.在 application.yml 中配置相关信息

1
2
3
4
5
6
7
8
9
server:
port: 80
spring:
application:
name: eurekaServer # 应用自己的名称,会在eureka中注册后显示
eureka:
client:
service-url:
defaultZone: http://localhost/eureka # eureka的服务端地址

客户端

1.添加依赖(若是依赖管理中没有客户端的版本,则需要像下面这样手动添加,版本最好跟服务端保持一致)

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>

2.在 application.yml 中配置相关信息

修改端口和应用名称,eureka的服务端地址保持不变。

远程调用

服务的远程调用包含两个客户端:调用服务的是消费者,被调用服务的是提供者。一个客户端可用同时是消费者和提供者。

远程调用服务可使用 RestTemplate 类的实例的 getObject 方法。

比如,我写一个工具类。将构建 RestTemplate 实体的工厂函数注册为 bean。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package cn.kahvia.imageservice.utils;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RestTemplateUtil {

@Bean
public static RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

然后在有需要的地方自动装配一个 RestTemplate 对象,调用它的各种请求方法就行了。post示例如下。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package cn.kahvia.imageservice.controller;
import cn.kahvia.imageservice.pojo.UploadResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.*;

@RestController
@RequestMapping("/image")
public class ImageController {
@Autowired
RestTemplate restTemplate;
@PostMapping("/upload")
public void uploadImg(MultipartFile file, HttpServletRequest request){
String path=request.getServletContext().getRealPath("");//获取当前servlet上下文的绝对路径

File temp=new File(path,file.getOriginalFilename());//在这个路径下新建一个临时文件temp
try {//接受用户上传的文件,往temp中输出
InputStream inputStream=file.getInputStream();
FileOutputStream fileOutputStream=new FileOutputStream(temp);
BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(fileOutputStream);
int n=0;
byte b[] = new byte[1024];//1024个字节,也就1024byte,即1kb
while ((n=inputStream.read(b))!=-1)//read(b),是说从输入流中读取“b的大小”这么多的数据到b中,并返回读取的字节个数,-1代表读取完了
{
bufferedOutputStream.write(b);//把读取到的的,存放在b中的数据写入到输出流指向的文件中
}
bufferedOutputStream.flush();//刷新缓存区,刷新后,缓冲输出流指向的底层输入流,即fileOutputStream会立即将缓存的内容写入目的地
bufferedOutputStream.close();//先关闭上层输出流
inputStream.close();//再关闭底层输出流。按理说关了上层,底层也会自动关。
} catch (IOException e) {
throw new RuntimeException(e);
}

//定义一个空的map,用来存储请求第三方接口所需的数据
MultiValueMap<String,Object> map= new LinkedMultiValueMap<>();
//利用刚刚生成的临时文件,创建文件系统资源
FileSystemResource fileSystemResource=new FileSystemResource(temp);
//添加到数据map中,第三方所需的参数名为image
map.add("image",fileSystemResource);

//设置请求头,包括访问的浏览器,和请求体内容固定类型,传输文件要选择表单数据"multipart/form-data"
HttpHeaders headers = new HttpHeaders();
headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
headers.add("Content-Type","multipart/form-data");

//生成http请求
HttpEntity<MultiValueMap<String, Object>> entity=new HttpEntity<>( map,headers);
//发送http请求,返回目标对象。返回的json数据会自动封装为目标对象。
UploadResult uploadResult= restTemplate.postForObject("https://xxxxxx/api/upload",entity,UploadResult.class);
temp.delete();//删除中转文件
System.out.println(uploadResult.toString());
}


}

负载均衡

为 RestTemplate 的工厂函数添加 @LoadBalanced注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.kahvia.imageservice.utils;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class RestTemplateUtil {
@LoadBalanced
@Bean
public static RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

原理:远程调用其它服务端的接口时,发出的请求会被拦截,然后根据请求的地址获取主机名(如果是eureka服务获取到的就是服务名),获取到服务名后再去eureka注册中心中取得对应服务名的所有服务端地址,根据负载均衡轮询或者随机的规则选取一个合适的地址,替换请求的地址中的服务名后,再放行,从而实现负载均衡。

负载均衡的策略

  • RoundRobinRule:简单的轮询。Ribbon默认策略。
  • ZoneAvoidanceRule:以区域为基础,进行服务器的选择。使用Zone进行分类。
  • RandomRule:随机。
  • RetryRule:重连。
  • AvailabilityFilteringRule,BestAvailableRule,WeightedResponseTimeRule等

全局策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//全局配置,对所有微服务有效
@Component
public class RestTemplateUtil {
@LoadBalanced//使用负载均衡
@Bean
public static RestTemplate getRestTemplate(){
return new RestTemplate();
}

@Bean
public static IRule setBalanceRule(){//设置策略
return new BestAvailableRule();
}
}

局部策略

1
2
3
UserService: #为某个微服务配置负载均衡的规则
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Ribbon修改饥饿加载

Ribbon默认是懒加载,也就是说,Ribbon提供的负载均衡的服务 LoadBalancerClient 并不会随着服务端启动而启动。第一次请求远程调用时,会初始化负载均衡服务,所以第一次会比较慢。

如果想要让负载均衡服务随着服务端启动而启动,就需要设置饥饿加载。在 application.yml 文件中配置饥饿加载。

1
2
3
4
ribbon:
eager-load:
enabled: true #开启饥饿加载,减少第一次远程调用的时间
clients: UserService #饥饿加载的名称,多个则换行以-开头分隔