07 声明式http客户端 feign

现有代码存在的问题
  1. 通过服务发现组件客户端总能找到服务端
  2. 通过配置服务器管理微服务的配置,让微服务的配置更加简单高效
  3. 通过ribbon实现客户端的负载均衡

分析现有代码存在的问题:

UserDTO userDTO = restTemplate.getForObject("http://ms-user/users/{userId}", UserDTO.class, userId);
  1. 当我是新员工,看到这行代码是很懵的,没法知道这行的代码的作用。代码可读性差。

  2. 现在构建的url比价简单,实际项目中构建的url非常复杂,构造非常困难。

  3. 编程风格不统一,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也实现了负载均衡
image-20200216185358709.png

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

image-20200216201102674.png

全局配置自定义

首先注释掉细粒度配置

#feign:
#  client:
#    config:
#      ms-user:
#        logger-level: full
  1. 代码方式

    在配置启动类上面注解中添加属性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;
	}
}
  1. 配置属性方式

注释掉启动类相关配置

@EnableFeignClients //(defaultConfiguration = GlobalFeignClientConfiguration.class)
@SpringBootApplication
feign:
  client:
    config:
      default:
        logger-level: full
Feign支持的配置项

代码支持的配置项
image-20200216202248001.png
在这里插入图片描述

配置属性支持的配置项
在这里插入图片描述

Feign对继承的支持

官方不建议使用 服务端和客户端继承相同的接口 带来了紧耦合 每个微服务应该是独立的 而继承是紧耦合的

因此使用继承是不符合微服务的思想的

现状是很多的公司在使用 因为实体类经常需要增加字段 比如User增加了字段 而UserDTO没有增加字段

权衡利弊

多参数请求构造

参考 7Feign多参数请求构造.md

常见问题总结

参考7Feign常见问题总结.md

RestTemplate与Feign

如何选择?

原则1: 尽量使用Feign Feign后续的优化

原则2: 尽量只使用一种 保持统一的风格和体验是非常重要的 增加学习成本和理解成本

性能调优
  1. 配置连接池 提升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

实际项目使用过程中,进行压测,不断修改参数直到最优值

  1. 合理的日志级别
本章总结
  1. Feign是什么?
  2. 自定义配置 细粒度配置 全局配置
  3. 常见问题总结
  4. 核心组件
发布了21 篇原创文章 · 获赞 1 · 访问量 317

猜你喜欢

转载自blog.csdn.net/m0_37607945/article/details/104543372