简单上手,直接照搬,就可搭建微服务(Hoxton.SR8) 2020.8.28发布,SpringCloud搭建的文章正在整理,干货不要错过哦
-
SpringCloud微服务小白也能搭(Hoxton.SR8)(一)Eureka|服务的注册与发现
-
SpringCloud微服务小白也能搭(Hoxton.SR8)(七)Gateway|服务网关
-
SpringCloud微服务小白也能搭(Hoxton.SR8)(八)Sleuth|服务链路跟踪
摘要:
Spring Cloud Netflix Ribbon 是Spring Cloud Netflix 子项目的核心组件之一,主要给服务间调用及API网关转发提供负载均衡的功能,在微服务架构中,很多服务都会部署多个,其他服务去调用该服务的时候,如何保证负载均衡是个不得不去考虑的问题。负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡功能。本文将对其用法进行详细介绍。
1.创建一个 user-service 模块
注意:配置及搭建参考之前第(一)文章的 客户端 配置
1.1 创建 UserApiController 提供 接口服务
package com.zqh.www.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* 用户服务类
*/
@RestController
@RequestMapping("/api/user")
public class UserApiController {
@Value("${server.port}")
private int port;
@GetMapping("/getUserList")
public Map<String, Object> getUserList() {
System.out.println("我是:" + port + "有人调用我啦");
ArrayList<User> dataList = new ArrayList<>();
User user = new User();
user.setUserId(1);
user.setUserName("我是测试1");
dataList.add(user);
User user2 = new User();
user2.setUserId(2);
user2.setUserName("我是测试2");
dataList.add(user2);
HashMap<String, Object> resultMap = new HashMap<>();
resultMap.put("code", "200");
resultMap.put("msg", "获取成功");
resultMap.put("data", dataList);
return resultMap;
}
// 为了方便演示,所以我就直接在这里创建了一个类,大家不要照搬哦
class User {
private Integer userId;
private String userName;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
}
2.创建ribbon-service模块,用于调用user-service
注意:配置及搭建参考之前第(一)文章的 客户端 配置
2.1 创建 RibbonApiController 调用 UserApiController
package com.zqh.www.controller;
import com.zqh.www.utils.RestTemplateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/api/ribbon")
public class RibbonApiController {
@Autowired
private RestTemplateUtils restTemplateUtils;
private String userServiceUrl = "http://user-service";
// private String userServiceUrl = "http://localhost:8084";
@GetMapping("/getUserList")
public Map<String, Object> getUserList() {
return restTemplateUtils.exchange(userServiceUrl + "/api/user/getUserList", HttpMethod.GET, null, null, null, Map.class);
}
}
2.2 RestTemplateUtils
package com.zqh.www.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Component
public class RestTemplateUtils {
private final static Logger logger = LoggerFactory.getLogger(RestTemplateUtils.class);
@Autowired
private RestTemplate restTemplate;
/**
* @param url 请求路径
* @param method 请求方式? HttpMethod.POST/HttpMethod.GET
* @param uriVariables 表单传参的参数
* @param jsonObject JSON格式传参的参数
* @param requestHeader 请求头
* @param responseType 返回结果Class
* @return
* @Description: JSON格式参数和表单参数一起传
*/
public <T> T exchange(String url, HttpMethod method, Map<String, Object> uriVariables, Object jsonObject, HttpHeaders requestHeader, Class<T> responseType) {
// 一般的表单传参
String uriVariablesParam = "";
if (null != uriVariables && 0 != uriVariables.size()) {
StringBuffer uriSb = new StringBuffer("?");
uriVariables.forEach((k, v) -> {
uriSb.append(k).append("=").append("{").append(k).append("}").append("&");
});
uriVariablesParam = uriSb.substring(0, uriSb.length() - 1).toString();
}
HttpHeaders requestHeaders = requestHeader;
if (null == requestHeaders) {
requestHeaders = new HttpHeaders();
}
HttpEntity<Object> requestEntity = new HttpEntity<>(null, requestHeaders);
if (null != jsonObject) {
requestEntity = new HttpEntity<>(jsonObject, requestHeaders);
}
url += uriVariablesParam;
long startTime = System.currentTimeMillis();
if (null == uriVariables || 0 == uriVariables.size()) {
T body = restTemplate.exchange(url, method, requestEntity, responseType).getBody();
logger.info("【接口请求】【{}】【处理时间:{}】【普通参数:{}】【JSON参数:{}】【返回结果:{}】", url, System.currentTimeMillis() - startTime, uriVariables, jsonObject, body);
return body;
}
T body = restTemplate.exchange(url, method, requestEntity, responseType, uriVariables).getBody();
logger.info("【接口请求】【{}】【处理时间:{}】【普通参数:{}】【JSON参数:{}】【返回结果:{}】", url, System.currentTimeMillis() - startTime, uriVariables, jsonObject, body);
return body;
}
}
2.3 RibbonConfig
package com.zqh.www.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class RibbonConfig {
/**
* 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,以及根据微服务名称调用接口的能力
*
* @return
*/
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.registerModule(simpleModule);
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> converters = new ArrayList<>();
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper);
converters.add(jsonConverter);
restTemplate.setMessageConverters(converters);
return restTemplate;
}
}
3.单服务测试调用
4.负载服务
注意:
- 先开启一个user-service 不用关闭,然后复制一个userService,改一下名字
- 修改 user-service 的yml 文件,注意之前的服务是没有关闭的还是在跑的,然后启动刚刚新配置的应用
- 此时的 eureka 注册中心会出现2个user-service
4.1 测试调用
第一次调用:发现8086端口的user-service 被调用了,8084的user-service未被调用
第一次调用:发现8084端口的user-service 被调用了,8086的user-service未被调用,负载完成
5 ribbon 常用配置
注意:
- 配置不生效,文档找了没发现原因。。如果有读者知道原因,请告知,非常感谢
5.1 全局配置
ribbon:
httpclient:
enabled: true
ConnectTimeout: 3000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
5.2 单独服务配置
user-service:
ribbon:
httpclient:
enabled: true
ConnectTimeout: 3000 #服务请求连接超时时间(毫秒)
ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
OkToRetryOnAllOperations: true #对超时请求启用重试机制
MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
MaxAutoRetries: 1 # 切换实例后重试最大次数
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法
6. 总结
- 使用ribbon来进行服务调用,并测试了服务负载的情况下接口调用情况
- 处理使用 RestTemplate 返回结果为List 类型时数据缺失