一、项目总体介绍
1、项目背景
我们在上一节用zookeer实现了将我们的微服务应用注册到zookeeper上并在程序中进行相关的应用信息的查询,在一个大的系统中每个微服务大多需要相互调用各种服务提供的API进行响应的操作,作为两个独立的应用他们是如何进行通讯的,当下的RPC框架有不少解决方案。
2、功能介绍
今天我们用spring cloud组件Feign进行服务间的通讯。Feign内部实现了负载均衡,我们可以选择对应的负载均衡方式进行服务的转发。
3、项目模块介绍
本次项目演示分为两个模块
1、spring-cloud-user-client
用户客户端应用
2、spring-cloud-user-server
用户服务端应用
二、项目核心代码展示
2.1、spring-cloud-user-service 模块介绍
2.1.1、项目目录结构
2.1.2、项目核心代码展示
------------------------项目依赖---------------------------------
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-zookeeper-all
2.1.2.RELEASE
org.apache.zookeeper
zookeeper
3.4.12
-------------------------数据库配置文件------------------------------
#应用名称
spring.application.name=spring-cloud-user-server
#HTTP访问端口
server.port=9095
------------------------启动文件application-------------------------
package com.gpdi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class SpringCloudUserServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudUserServerApplication.class, args);
}
}
--------------------------Controller文件---------------------------------------
package com.gpdi.controller;
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.RestController;
@RestController
public class UserController {
@GetMapping(value = "getMessage")
public String getMessage(String name) {
System.out.println("ServerController 接收到消息 - getMessage : " + name);
return "Hello," + name;
}
@GetMapping(value = "sayHello")
public String sayHello(String name) throws InterruptedException {
System.out.println("ServerController 接收到消息 - say : " + name);
return "Hello, " + name;
}
@Autowired
private DiscoveryClient discoveryClient;
}
2.2、spring-cloud-user-client
2.2.1、项目目录结构
2.2.2、项目核心代码
--------------------------项目依赖文件-----------------------------
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-openfeign
2.0.0.RELEASE
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-zookeeper-all
org.apache.zookeeper
zookeeper
-----------------------application配置文件------------------------------------
#服务名称
spring.application.name=spring-cloud-user-client
#HTTP访问端口
server.port=8082
-----------------------项目启动文件----------------------------------------------
package com.gpdi;
import com.gpdi.service.feign.UserService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableFeignClients(clients = UserService.class) // 引入 FeignClient
@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan(basePackages = {"com.gpdi.service.feign","com.gpdi.controller"})
@EnableScheduling
public class SpringCloudUserClientApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SpringCloudUserClientApplication.class)
.web(WebApplicationType.SERVLET)
.run(args);
}
}
----------------------用Feign实现调用的第一种方式(用RestTemplate进行调用)-------------
//注入Ribbon的RestTemplate
@LoadBalanced
@Bean
public RestTemplate loadBalancedRestTemplate() {
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
/**
* @desc:通过Ribbon的方式进行调用
*/
@GetMapping(value = "/getMessage")
public String getMessage(String name) {
//服务名称+接口名称+参数 (/spring-cloud-user-server/getUser?name=1)
return restTemplate.getForObject("http://spring-cloud-user-server/getMessage?
name=" + name, String.class);
}
------------------用Feign实现调用的第二种方式(用Feign发布接口进行调用)--------------
package com.gpdi.service.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "spring-cloud-user-server")
public interface UserService {
@GetMapping(value="/sayHello")
String sayHello(@RequestParam("name") String name);
}
/**
@desc:通过Feign的方式进行调用,
*/
@GetMapping(value = "/sayHello")
public String sayHello(@RequestParam String name) {
return helloService.sayHello(name);
}
@Autowired
private UserService helloService;
---------------------第三种方式:自定RestTemplate(包含自定义负载均衡算法)----------------
package com.gpdi.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface CustomizedLoadBalanced {
}
package com.gpdi.annotation;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class RequestMappingMethodInvocationHandler implements InvocationHandler {
private final ParameterNameDiscoverer parameterNameDiscoverer
= new DefaultParameterNameDiscoverer();
private final String serviceName;
private final BeanFactory beanFactory;
public RequestMappingMethodInvocationHandler(String serviceName,
BeanFactory beanFactory) {
this.serviceName = serviceName;
this.beanFactory = beanFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 过滤 @RequestMapping 方法
GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
if (getMapping != null) {
// 得到 URI
String[] uri = getMapping.value();
// http://${serviceName}/${uri}
StringBuilder urlBuilder = new StringBuilder("http://").append(serviceName).append("/").append(uri[0]);
// 获取方法参数数量
int count = method.getParameterCount();
// 方法参数是有顺序
// FIXME
// String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
// 方法参数类型集合
Class>[] paramTypes = method.getParameterTypes();
Annotation[][] annotations = method.getParameterAnnotations();
StringBuilder queryStringBuilder = new StringBuilder();
for (int i = 0; i < count; i++) {
Annotation[] paramAnnotations = annotations[i];
Class> paramType = paramTypes[i];
RequestParam requestParam = (RequestParam) paramAnnotations[0];
if (requestParam != null) {
String paramName = "";
// paramNames[i];
// HTTP 请求参数
String requestParamName = StringUtils.hasText(requestParam.value()) ? requestParam.value() :
paramName;
String requestParamValue = String.class.equals(paramType)
? (String) args[i] : String.valueOf(args[i]);
// uri?name=value&n2=v2&n3=v3
queryStringBuilder.append("&")
.append(requestParamName).append("=").append(requestParamValue);
}
}
String queryString = queryStringBuilder.toString();
if (StringUtils.hasText(queryString)) {
urlBuilder.append("?").append(queryString);
}
// http://${serviceName}/${uri}?${queryString}
String url = urlBuilder.toString();
// 获取 RestTemplate , Bean 名称为“loadBalancedRestTemplate”
// 获得 BeanFactory
RestTemplate restTemplate = beanFactory.getBean("loadBalancedRestTemplate", RestTemplate.class);
return restTemplate.getForObject(url, method.getReturnType());
}
return null;
}
}
package com.gpdi.loadbalance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.stream.Collectors;
public class LoadBalancedRequestInterceptor implements ClientHttpRequestInterceptor {
// Map Key service Name , Value URLs
private volatile Map> targetUrlsCache = new HashMap<>();
@Autowired
private DiscoveryClient discoveryClient;
@Scheduled(fixedRate = 10 * 1000) // 10 秒钟更新一次缓存
public void updateTargetUrlsCache() { // 更新目标 URLs
// 获取当前应用的机器列表
// http://${ip}:${port}
Map> newTargetUrlsCache = new HashMap<>();
discoveryClient.getServices().forEach(serviceName -> {
List serviceInstances = discoveryClient.getInstances(serviceName);
Set newTargetUrls = serviceInstances
.stream()
.map(s ->
s.isSecure() ?
"https://" + s.getHost() + ":" + s.getPort() :
"http://" + s.getHost() + ":" + s.getPort()
).collect(Collectors.toSet());
newTargetUrlsCache.put(serviceName, newTargetUrls);
});
this.targetUrlsCache = newTargetUrlsCache;
}
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
// URI: "/" + serviceName + "/say?message="
URI requestURI = request.getURI();
String path = requestURI.getPath();
String[] parts = StringUtils.split(path.substring(1), "/");
String serviceName = parts[0]; // serviceName
String uri = parts[1]; // "/say?message="
// 服务器列表快照
List targetUrls = new LinkedList<>(targetUrlsCache.get(serviceName));
int size = targetUrls.size();
// size =3 , index =0 -2
int index = new Random().nextInt(size);
// 选择其中一台服务器
String targetURL = targetUrls.get(index);
// 最终服务器 URL
String actualURL = targetURL + "/" + uri + "?" + requestURI.getQuery();
// 执行请求
System.out.println("本次请求的 URL : " + actualURL);
URL url = new URL(actualURL);
URLConnection urlConnection = url.openConnection();
// 响应头
HttpHeaders httpHeaders = new HttpHeaders();
// 响应主体
InputStream responseBody = urlConnection.getInputStream();
return new SimpleClientHttpResponse(httpHeaders, responseBody);
}
private static class SimpleClientHttpResponse implements ClientHttpResponse {
private HttpHeaders headers;
private InputStream body;
public SimpleClientHttpResponse(HttpHeaders headers, InputStream body) {
this.headers = headers;
this.body = body;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
}
//自定义负载均衡
@Autowired // 依赖注入自定义 RestTemplate Bean
@CustomizedLoadBalanced
private RestTemplate myRestTemplate;
@Bean
@Autowired
@CustomizedLoadBalanced
public RestTemplate restTemplate() { // 依赖注入
return new RestTemplate();
}
/**
* @desc:通过自定义RestTemplate的方式进行服务调用
*/
@GetMapping(value = "/getMessageOfMy")
public String myLoadBalance(String name) {
return myRestTemplate.getForObject("/spring-cloud-user-server/getMessage?name=" + name, String.class);
}