Introdução
Feign pode ocultar a solicitação Rest e fingir ser um controlador semelhante ao Spring MVC. Não há necessidade de emendar urls, emendar parâmetros, etc. sozinho, e deixar tudo para o Feign.
Primeiro caso
-
Dependências de importação em consumidores de serviço
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
@EnableFeignClients
Adicionar anotações à classe de inicializaçãoO balanceamento de carga da fita foi integrado automaticamente no Feign, portanto, não há necessidade de defini-lo
RestTemplate
você mesmo@SpringCloudApplication @EnableFeignClients // 开启Feign注解 public class ConsumerApplication { public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }
-
Escreva um cliente Feign
@FeignClient(value = "user-service") // 添加FeignClient,指定服务ID public interface UserClient { /** * 声明一个feign的接口,它的实现是服务提供者的controller实现 */ @GetMapping("/user/{id}") User getById(@PathVariable("id") Long id); }
-
Chamado no código, use userClient para acessar:
@Autowired // 注入UserClient private UserClient userClient; public User getUserById(@PathVariable long id) { User user = userClient.getById(id); return user; }
Explicação detalhada da anotação @FeignClient
-
A anotação @FeignClient só pode ser utilizada na interface Interface. Feign irá gerar classes de implementação através de um proxy dinâmico.
- A anotação FeignClient é modificada por @Target(ElementType.TYPE), indicando que o destino da anotação FeignClient está na interface
-
@FeignClient , declare que este é um cliente Feign e especifique o nome do serviço por meio do atributo name / value
-
O método definido na interface adota totalmente as anotações do SpringMVC, e o Feign gerará URLs de acordo com as anotações e acesso para obter os resultados
-
A classe de inicialização do serviço deve ser anotada com @EnableFeignClients para que o Fegin entre em vigor
Os atributos comuns da anotação @FeignClient são os seguintes:
- name / value : Especifique o nome de FeignClient. Se o projeto usar Ribbon (registro), o atributo name será usado como o nome do microsserviço para descoberta de serviço
- url : Geralmente usado para depuração, você pode especificar manualmente o endereço chamado por @FeignClient. vazio por padrão
- A url pode ser obtida no arquivo de configuração, se houver será chamada através da url, caso contrário será chamada de acordo com o nome do serviço. formato é
url = "${xxx.xxx.xxx: }"
- A url pode ser obtida no arquivo de configuração, se houver será chamada através da url, caso contrário será chamada de acordo com o nome do serviço. formato é
- configuração : Classe de configuração do Feign, você pode personalizar o Codificador, Decodificador, LogLevel, Contrato do Feign, e você pode especificar configurações diferentes para cada cliente do Feign
- fallback : define uma classe de processamento tolerante a falhas. Quando a chamada de uma interface remota falha ou atinge o tempo limite, a lógica tolerante a falhas da interface correspondente será invocada. A classe especificada por fallback deve implementar a interface marcada por @FeignClient
- fallbackFactory : classe de fábrica, usada para gerar exemplos de classe de fallback, através deste atributo, a lógica tolerante a falhas comum de cada interface pode ser realizada e o código duplicado pode ser reduzido
- path : Define o prefixo unificado do FeignClient atual, usado quando server.context-path, server.servlet-path são configurados no projeto
- decode404 : Quando ocorrer um erro http 404, se o campo for verdadeiro, o decodificador será chamado para decodificação, caso contrário, uma FeignException será lançada
Método de chamada:
-
Método 1: O provedor de interface está no centro de registro
Se o provedor de serviços estiver registrado no centro de registro, o valor de nome ou valor será: o nome do serviço do provedor de serviços. Um nome ou valor deve ser especificado para todos os clientes
@FeignClient(value="run-product",fallback = ProductClientServiceFallBack.class)
-
Método 2: Uma única interface http, o provedor de interface não está registrado no centro de registro
@FeignClient(name="runClient11111",url="localhost:8001")
O valor de name aqui é: o nome do cliente chamador
Os dois métodos acima podem ser chamados normalmente. name pode ser o nome do serviço do registro e, quando o atributo url é adicionado, o valor do nome não tem nada a ver com o nome do serviço do registro.
Configuração do Feign Client
A configuração simulada é estendida com base na configuração da faixa de opções e pode oferecer suporte à configuração de tempo limite de nível de serviço, portanto, a configuração simulada e a configuração da faixa de opções têm o mesmo efeito.
A ordem de prioridade do SpringCloud para configuração é a seguinte:
- Simular configuração local > Simular configuração global > Configuração local da faixa de opções > Configuração global da faixa de opções
- A ordem de prioridade dos atributos do arquivo de configuração e das classes de configuração é: configuração do atributo do arquivo de configuração > configuração do código da classe de configuração
feign:
client:
config:
default: # 全部服务配置
connectTimeout: 5000 # 建立连接的超时时长,单位:毫秒。默认为1000
readTimeout: 5000 # 指建立连接后从服务端读取到可用资源所用的超时时间,单位:毫秒。默认为1000
loggerLevel: FULL # 日志级别
errorDecoder: com.example.SimpleErrorDecoder # Feign的错误解码器,相当于代码配置方式中的ErrorDecoder
retryer: com.example.SimpleRetryer # 配置重试,相当于代码配置方式中的Retryer
requestInterceptors: # 配置拦截器,相当于代码配置方式中的RequestInterceptor
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false # 是否对404错误解码
encode: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
serverName: # 单独给某⼀服务配置。serverName是服务名,使⽤的时候要⽤服务名替换掉这个
connectTimeout: 5000
readTimeout: 5000
Fingir solicitações para adicionar cabeçalhos
Solução 1: adicione informações de cabeçalhos à anotação @RequestMapping no método
O atributo da anotação @RequestMapping contém uma matriz de cabeçalhos. Você pode adicionar os cabeçalhos necessários à anotação @RequestMapping no método especificado, que pode ser embutido em código ou configuração de leitura =
Da mesma forma, as anotações @PostMapping e @GetMapping do grupo @RequestMapping são aplicáveis
@FeignClient(name = "server",url = "127.0.0.1:8080")
public interface FeignTest {
@RequestMapping(value = "/test",headers = {
"app=test-app","token=${test-app.token}"})
String test();
}
Solução 2: adicione informações de cabeçalhos à anotação @RequestMapping na interface
Se todos os métodos na mesma interface exigirem os mesmos cabeçalhos, você poderá adicionar cabeçalhos à anotação @RequestMapping na interface para que os métodos de toda a interface sejam adicionados com os mesmos cabeçalhos
@FeignClient(name = "server",url = "127.0.0.1:8080")
@RequestMapping(value = "/",headers = {
"app=test-app","token=${test-app.token}"})
public interface FeignTest {
@RequestMapping(value = "/test")
String test();
}
Solução 3: use a anotação @Headers para adicionar informações de cabeçalhos (não recomendado)
@FeignClient(name = "server",url = "127.0.0.1:8080")
@Headers({
"app: test-app","token: ${test-app.token}"})
public interface FeignTest {
@RequestMapping(value = "/test")
String test();
}
Olhando para a documentação oficial do openfeign, descobriu-se que ele usa @Headers para adicionar cabeçalhos. O teste descobriu que não teve efeito . A nuvem Spring usa seu próprio SpringMvcContract para analisar anotações, então você precisa implementar um contrato você mesmo para dar suporte às anotações @Headers. Para implementação específica, consulte (https://juejin.im/post/6844903961653149709)
Solução 4: personalize o RequestInterceptor para adicionar informações de cabeçalho
Feign fornece uma interface interceptadora RequestInterceptor, que pode interceptar solicitações feign implementando a interface RequestInterceptor. A interface fornece um método apply() para implementar o método apply()
A implementação do método apply() para adicionar cabeçalhos diretamente interceptará todas as solicitações e adicionará cabeçalhos. Se nem todas as solicitações falsas precisarem ser usadas, esse método não é recomendado
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Value("${test-app.token}")
private String token;
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("app","test-app");//静态
requestTemplate.header("token",token);//读配置
}
}
Solução 5: Personalize a implementação do RequestInterceptor para adicionar dados dinâmicos ao cabeçalho
Nenhuma das soluções acima é adequada para colocar dados dinâmicos em cabeçalhos. Em cenários gerais, informações dinâmicas como assinaturas calculadas e IDs de usuário podem precisar ser definidas em cabeçalhos, portanto, uma solução mais completa é necessária. O esquema 1/2/3 não pode definir um valor dinâmico e o esquema 4 pode definir um valor dinâmico, mas não faz distinção entre as solicitações. Portanto, o esquema 5 é aprimorado com base no esquema 4. A implementação específica é a seguinte:
No código de chamada da solicitação, obtenha o objeto HttpServletRequest, encapsule o valor que precisa ser adicionado aos cabeçalhos em um mapa e coloque-o no campo de atributo de HttpServletRequest
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
String signedMsg = getSignedMsg(reqJson); // 计算签名字符串
Map<String, String> reqMap = new HashMap<>();
reqMap.put("content-type", "application/json"); //常量字段
reqMap.put("accessKey", accessKey); //常量字段
reqMap.put("signedMsg", signedMsg); //动态计算/获取字段
request.setAttribute("customizedRequestHeader", reqMap);
Obtenha a chave especificada no campo de atributo do objeto HttpServletRequest no RequestInterceptor personalizado e inclua todos os parâmetros no mapa correspondente à chave nos cabeçalhos.
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 设置自定义header
// 设置request中的attribute到header以便转发到Feign调用的服务
Enumeration<String> reqAttrbuteNames = request.getAttributeNames();
if (reqAttrbuteNames != null) {
while (reqAttrbuteNames.hasMoreElements()) {
String attrName = reqAttrbuteNames.nextElement();
if (!"customizedRequestHeader".equalsIgnoreCase(attrName)) {
continue;
}
Map<String,String> requestHeaderMap = (Map)request.getAttribute(attrName);
for (Map.Entry<String, String> entry : requestHeaderMap.entrySet()) {
requestTemplate.header(entry.getKey(), entry.getValue());
}
break;
}
}
}
}
Balanceamento de carga (faixa de opções)
O próprio Feign integrou as dependências do Ribbon e a configuração automática, e suporta o Ribbon por padrão.
A faixa de opções integrada do Fegin define o tempo limite da solicitação por padrão, que é de 1000ms por padrão. Como há um mecanismo de repetição dentro da faixa de opções, assim que o tempo expirar, a solicitação será reiniciada automaticamente
Pode ser modificado por configuração:
A configuração global usa ribbon.=
ribbon:
ReadTimeout: 2500 # 数据通信超时时长,单位:ms。默认为1000
ConnectTimeout: 500 # 连接超时时长,单位:ms。默认为1000
OkToRetryOnAllOperations: false # 是否对所有的异常请求(连接异常和请求异常)都重试。默认为false
MaxAutoRetriesNextServer: 1 # 最多重试多少次连接服务(实例)。默认为1。不包括首次调用
MaxAutoRetries: 0 # 服务的单个实例的重试次数。默认为0。不包括首次调用
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 切换负载均衡策略为随机
Especifique a configuração do serviço <nome do serviço>.ribbon.=
serverName: # 单独给某⼀服务配置。serverName是服务名,使⽤的时候要⽤服务名替换掉这个
ribbon:
connectTimeout: 5000
readTimeout: 5000
mecanismo de tolerância a falhas
suporte Hystrix
O Feign também possui integração com o Hystix por padrão, mas está desativado por padrão. Ele precisa ser habilitado com os seguintes parâmetros:
feign:
hystrix:
enabled: true # 开启hystrix熔断机制
hystrix:
command:
default: # 全局默认配置
execution: # 线程隔离相关
timeout:
enabled: true # 是否给方法执行设置超时时间,默认为true。一般不改。
isolation:
strategy: THREAD # 配置请求隔离的方式,这里是默认的线程池方式。还有一种信号量的方式semaphore,
thread:
timeoutlnMilliseconds: 10000 # 方式执行的超时时间,默认为1000毫秒,在实际场景中需要根据情况设置
circuitBreaker: # 服务熔断相关
requestVolumeThreshold: 10 # 触发熔断的最小请求次数,默认20
sleepWindowInMilliseconds: 10000 # 休眠时长,单位毫秒,默认是5000毫秒
errorThresholdPercentage: 50 # 触发熔断的失败请求最小占比,默认50%
serverName: # 单独给某⼀服务配置
execution:
timeout:
enabled: true
isolation:
strategy: THREAD
thread:
timeoutlnMilliseconds: 10000
Perceber:
-
O período de tempo limite do Hystix deve ser maior do que o tempo total de repetição da Faixa de opções. Caso contrário, após o tempo limite do comando Hystrix, o comando será fundido diretamente e o mecanismo de repetição não terá significado.
Faixa de opções: número total de tentativas = número de servidores acessados * número máximo de tentativas para um único servidor
Ou seja, o número total de novas tentativas = (1+MaxAutoRetriesNextServer)*(1+MaxAutoRetries)
Tempo limite do Hystrix > (soma do tempo limite da faixa de opções) * número de novas tentativas
Portanto, sugere-se que o período de timeout do histrix seja:
( ( 1+MaxAutoRetriesNextServer) * (1+MaxAutoRetries ) ) * (ReadTimeout + connectTimeout)
- MaxAutoRetries: Ribbon Configuration: O número de novas tentativas para uma única instância do serviço. Não inclui primeira chamada
- MaxAutoRetriesNextServer: Ribbon configuration: Quantas vezes repetir o serviço de conexão (instância) no máximo. Não inclui primeira chamada
- ReadTimeout: Configuração da fita: tempo limite de comunicação
- connectTimeout: Configuração da faixa de opções: tempo limite de estabelecimento da conexão
Suporte sentinela
O Sentinel pode fornecer a função de isolamento de semáforo por meio do controle de fluxo do número de modo de encadeamentos simultâneos. E combinado com o modo de rebaixamento do fusível baseado no tempo de resposta, ele pode rebaixar automaticamente quando o tempo médio de resposta de recursos instáveis é relativamente alto, evitando que muitas chamadas lentas ocupem o número simultâneo e afetem todo o sistema.
confiar
<!--Sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
Habilite o suporte do Feign para Sentinel no arquivo de configuração
feign:
sentinel:
enabled: true
Como usar o Feign após habilitar o suporte ao mecanismo tolerante a falhas
Após o Feign habilitar o suporte de mecanismo tolerante a falhas Hystrix ou Sentinel, ele pode ser usado das duas maneiras a seguir:
- Solução 1: herdar diretamente a interface tolerante a falhas e implementar uma solução tolerante a falhas para cada método
- Solução 2: implemente a interface FallbackFactory
Solução 1: herdar diretamente a interface tolerante a falhas e implementar uma solução tolerante a falhas para cada método
-
Defina uma classe como a classe de processamento de fallback. Herde diretamente a interface tolerante a falhas e implemente uma solução tolerante a falhas para cada método
@Component public class UserClientFallback implements UserClient { @Override public User getById(Long id) { return new User(1L, "我是备份-feign", 18, new Date()); } }
-
Use o atributo fallback na anotação @FeignClient para especificar uma classe de processamento tolerante a falhas customizada
@FeignClient(value = "user-service",fallback = UserClientFallback.class) public interface UserClient { @GetMapping("/user/{id}") User getById(@PathVariable("id") Long id); }
-
verificação de teste
Solução 2: implemente a interface FallbackFactory. Você pode obter informações de erro de serviço específicas, o que é conveniente para solução de problemas posterior
@FeignClient(value="34-SPRINGCLOUD-SERVICE-GOODS", fallbackFactory = GoodsRemoteClientFallBackFactory.class)
public interface GoodsRemoteClient {
@RequestMapping("/service/goods")
public ResultObject goods();
}
@Component
public class GoodsRemoteClientFallBackFactory implements FallbackFactory<GoodsRemoteClient> {
@Override
public GoodsRemoteClient create(Throwable throwable) {
return new GoodsRemoteClient() {
@Override
public ResultObject goods() {
String message = throwable.getMessage(); // message即为错误信息
System.out.println("feign远程调用异常:" + message);
return new ResultObject();
}
};
}
}
compactação de solicitação (feign.compression)
O Spring Cloud Feign oferece suporte à compactação GZIP para solicitações e respostas para reduzir a perda de desempenho durante a comunicação. A função de compressão de pedidos e respostas pode ser habilitada pelos seguintes parâmetros:
feign:
compression:
request:
enabled: true
response:
enabled: true
Você também pode definir o tipo de dados da solicitação e o limite inferior do tamanho que aciona a compactação. Somente as solicitações que excederem esse tamanho serão compactadas:
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
nível de registro
Passe logging.level.xx=debug
para definir o nível de log. No entanto, isso não tem efeito no cliente Fegin. Como @FeignClient
o cliente modificado pela anotação criará uma nova instância de Fegin.Logger quando estiver em proxy, é necessário especificar o nível desse log adicionalmente.
Feign suporta 4 níveis de log:
- NONE: Não registra nenhuma informação de log, que é o valor padrão.
- BÁSICO: Registre apenas o método de solicitação, URL, código de status de resposta e tempo de execução
- CABEÇALHOS: Com base no BASIC, as informações do cabeçalho da solicitação e resposta são gravadas adicionalmente
- COMPLETO: registre detalhes de todas as solicitações e respostas, incluindo informações de cabeçalho, corpo da solicitação e metadados.
configuração global
Método 1: implementação das propriedades do arquivo de configuração
feign:
client:
config:
default: # 将调用的微服务名称设置为default即为配置成全局
loggerLevel: FULL
Método 1: implementação de código
//在启动类上为@EnableFeignClients注解添加defaultConfiguration配置
@EnableFeignClients(defaultConfiguration = FeignConfig.class)
Refinado (especificar a configuração do serviço)
Método 1: implementação do arquivo de configuração
feign:
client:
config:
server-1: # 想要调用的微服务名称
loggerLevel: FULL
Método 2: implementação de código
1) Escreva uma classe de configuração para definir o nível de log
@Configuration
public class FeignConfig {
@Bean
Logger.Level level(){
return Logger.Level.FULL;
}
}
2) Especifique a classe de configuração em FeignClient: (pode ser omitido)
@FeignClient(value = "user-service", configuration = FeignConfig.class)
// 添加FeignClient,指定服务ID
public interface UserClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id") Long id);
}
Exemplo de log simulado para cada visita: