现有代码存在的问题
- 通过服务发现组件客户端总能找到服务端
- 通过配置服务器管理微服务的配置,让微服务的配置更加简单高效
- 通过ribbon实现客户端的负载均衡
分析现有代码存在的问题:
UserDTO userDTO = restTemplate.getForObject("http://ms-user/users/{userId}", UserDTO.class, userId);
-
当我是新员工,看到这行代码是很懵的,没法知道这行的代码的作用。代码可读性差。
-
现在构建的url比价简单,实际项目中构建的url非常复杂,构造非常困难。
-
编程风格不统一,url拼接错误,IDE都会有任何提示,
使用Feign重构前面的代码
什么是feign?
NetFlix开源的声明式的HTTP客户端 Feign makes writing java http clients easier
https://github.com/openfeign/feign
添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类添加注解EnableFeignClients
package com.cloud.msclass;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableFeignClients
@SpringBootApplication
public class MsClassApplication {
public static void main(String[] args) {
SpringApplication.run(MsClassApplication.class, args);
}
/**
* spring web提供的轻量级http client
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
定义接口
package com.cloud.msclass.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.cloud.msclass.domain.dto.UserDTO;
//@FeignClient(url = "http://localhost:8081",name="xxxx") 此为不使用feign模式
@FeignClient(name = "ms-user") // 底层使用ribbon去请求
public interface MsUserFeignClient {
@GetMapping("/users/{userId}")
UserDTO findUserById(@PathVariable("userId") Integer userId);
}
修改代码
package com.cloud.msclass.service;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.cloud.msclass.domain.dto.UserDTO;
import com.cloud.msclass.domain.entity.Lesson;
import com.cloud.msclass.domain.entity.LessonUser;
import com.cloud.msclass.feign.MsUserFeignClient;
import com.cloud.msclass.repository.LessonRepository;
import com.cloud.msclass.repository.LessonUserRepository;
@Service
public class LessonService {
@Autowired
private LessonRepository lessonRepository;
@Autowired
private LessonUserRepository lessonUserRepository;
@Autowired
private RestTemplate restTemplate;
@Autowired
private MsUserFeignClient msUserFeignClient;
public Lesson buyById(Integer id) {
// 1. 根据id查询lesson
Lesson lesson = this.lessonRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("该课程不存在"));
// 2. 根据lesson.id查询user_lesson,那么直接返回lesson
LessonUser lessonUser = this.lessonUserRepository.findByLessonId(id);
if (lessonUser != null) {
return lesson;
}
// TODO 登录实现后需重构
// 3. 如果user_lesson==null && 用户的余额 > lesson.price 则购买成功
Integer userId = 1;
UserDTO userDTO = this.msUserFeignClient.findUserById(userId);
BigDecimal money = userDTO.getMoney().subtract(lesson.getPrice());
if (money.doubleValue() < 0) {
throw new IllegalArgumentException("余额不足");
}
// TODO 购买逻辑 ... 1. 调用用户微服务的扣减金额接口 2.向lesson_user表插入数据
return lesson;
}
}
访问课程服务 http://localhost:8010/lesssons/buy/1
另外:Feign也实现了负载均衡
Feign的核心组件
细粒度配置自定义
和ribbon类似 支持细粒度配置
支持代码和配置两种方式
自定义Feign的日志级别
NONE(默认值) : 不打印日志
BASIC : 仅仅记录请求方法、URL、响应状态代码以及执行时间
HEADERS:记录BASIC级别的基础上,记录请求和响应的header
FULL : 记录请求和响应的header、body和元数据
首先通过代码配置方式:
package com.cloud.msclass.feign;
import org.springframework.context.annotation.Bean;
import feign.Logger;
// 如果这个类添加了Configuration注解,则需要将该类放到启动类扫描的包之外 否则会被所有feign共享
// 也是父子容器的问题
public class MsUserFeignClientConfiguration {
@Bean
public Logger.Level loggerLevel() {
return Logger.Level.FULL;
}
}
package com.cloud.msclass.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.cloud.msclass.domain.dto.UserDTO;
//@FeignClient(url = "http://localhost:8081",name="xxxx") 此为不使用feign模式
@FeignClient(name = "ms-user", configuration = MsUserFeignClientConfiguration.class) // 底层使用ribbon去请求
public interface MsUserFeignClient {
@GetMapping("/users/{userId}")
UserDTO findUserById(@PathVariable("userId") Integer userId);
}
另外还需要设置对应类的日志级别
logging:
level:
com.cloud.msclass.feign.MsUserFeignClient: debug
重新启动课程微服务
通过配置属性方式
注释掉java代码的配置
package com.cloud.msclass.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import com.cloud.msclass.domain.dto.UserDTO;
//@FeignClient(url = "http://localhost:8081",name="xxxx") 此为不使用feign模式
//@FeignClient(name = "ms-user", configuration = MsUserFeignClientConfiguration.class) // 底层使用ribbon去请求
@FeignClient(name = "ms-user") // 底层使用ribbon去请求
public interface MsUserFeignClient {
@GetMapping("/users/{userId}")
UserDTO findUserById(@PathVariable("userId") Integer userId);
}
添加配置
feign:
client:
config:
ms-user:
logger-level: full
全局配置自定义
首先注释掉细粒度配置
#feign:
# client:
# config:
# ms-user:
# logger-level: full
-
代码方式
在配置启动类上面注解中添加属性defaultConfiguration
@EnableFeignClients(defaultConfiguration = GlobalFeignClientConfiguration.class)
@SpringBootApplication
package com.cloud.msclass.feign;
import org.springframework.context.annotation.Bean;
import feign.Logger;
// 如果这个类添加了Configuration注解,则需要将该类放到启动类扫描的包之外
public class GlobalFeignClientConfiguration {
@Bean
public Logger.Level loggerLevel() {
return Logger.Level.FULL;
}
}
- 配置属性方式
注释掉启动类相关配置
@EnableFeignClients //(defaultConfiguration = GlobalFeignClientConfiguration.class)
@SpringBootApplication
feign:
client:
config:
default:
logger-level: full
Feign支持的配置项
代码支持的配置项
配置属性支持的配置项
Feign对继承的支持
官方不建议使用 服务端和客户端继承相同的接口 带来了紧耦合 每个微服务应该是独立的 而继承是紧耦合的
因此使用继承是不符合微服务的思想的
现状是很多的公司在使用 因为实体类经常需要增加字段 比如User增加了字段 而UserDTO没有增加字段
权衡利弊
多参数请求构造
参考 7Feign多参数请求构造.md
常见问题总结
参考7Feign常见问题总结.md
RestTemplate与Feign
如何选择?
原则1: 尽量使用Feign Feign后续的优化
原则2: 尽量只使用一种 保持统一的风格和体验是非常重要的 增加学习成本和理解成本
性能调优
- 配置连接池 提升15%左右
添加依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<!-- <dependency> -->
<!-- <groupId>io.github.openfeign</groupId> -->
<!-- <artifactId>feign-okhttp</artifactId> -->
<!-- </dependency> -->
修改系统配置
feign:
client:
config:
default:
logger-level: full
httpclient:
# 让feign使用apache httpclient 而不是默认的Client.Default
enabled: true
# feign的最大连接数
max-connections: 200
# feign单个路径的最大连接数
max-connections-per-route: 50
实际项目使用过程中,进行压测,不断修改参数直到最优值
- 合理的日志级别
本章总结
- Feign是什么?
- 自定义配置 细粒度配置 全局配置
- 常见问题总结
- 核心组件