单体应用与分布式应用

什么是单体应用?

单体应用是指将一个应用程序的所有功能模块集中在一个单一的执行单元中,通常打包为一个单独的可执行文件或软件包。这个单体可以是一个WAR包,也可以是一个JAR包、一个EXE文件、一个容器镜像等。

单体应用的优缺点

优点:

整个项目是一个web工程,运行在一个JVM中,整合性较好,开发便捷,容易管理

缺点:

1.项目越大,代码量越大,造成编译、打包 费时,越来越影响效率

2.业务增多时会造成代码冗余重复,代码复用度不高,造成浪费

前后台两个项目 -> 前端: http://localhost:9999

​ 后端: http://b:9999/adminLogin.html

例: 两个项目实体类重复

3.可伸缩性差,单体应用中的功能模块的使用场景,并发量,消耗的资源类型各有不同,对于资源的利用又互相影响,这样使我们对各个业务模块的系统容量很难给出较准确的评估

当项目只在一个规定的时间上线一个新功能时,且对并发要求度高,只能对该项目的对应的硬件升级或流量进行扩充,无法对其进行分配,对于一个单体应用来说造成了资源空间的浪费

4.系统错误隔离性差,可用性差,任何一个模块的错误均可能造成整个系统的宕机

实际业务要求其中一个服务崩溃时,其它服务均可独立运行

SOA

面向服务的架构思想

将应用程序划分为一组自治、可独立开发和部署的服务,这些服务通过消息传递或网络调用进行通信。

SOA的三种架构思想

SOAP: 基于HTTP + XML; webservice

缺点: xml返回的 XML 数据量较大,有大量的数据冗余 例如标签名、属性名等,这会增加数据大小)

优点: 协议严格,稳定性高,安全性高

基于SOAP的天气预报: http://www.webxml.com.cn/zh_cn/weather_icon.aspx

REST: 基于HTTP + JSON; springcloud

优点: 数据简洁

缺点:字符流数据,明码传输,安全性低

RPC: 基于SOCKET+文本的形式,二进制数据; alibaba dubbo 典型的RPC架构;

字节流数据,可数据加密,安全性高

微服务

什么是微服务?

微服务是SOA的一种落地方案,SOA是一种面向服务的架构思想,微服务也同样推崇这种思想.

微服务架构是将一个大型应用程序分解为多个小型、自治且可独立部署的服务,以提高系统的可维护性、灵活性和扩展性。

使用脚手架实现快速开发 springboot

云服务

什么是云服务?

云服务是一种通过互联网提供的各种资源和服务的模式,例如计算、存储、数据库、分析、机器学习等。

云服务可以根据用户的需求动态地扩展合伙缩减,而无需管理底层的硬件或软件

云服务种类及其区别

微服务与云结合

微服务架构的核心概念之一就是每个服务都被打包和部署为离散的独立程序。服务实例应迅速启动,服务的每一个实例都是完全相同的。

基于云的微服务以弹性的概念为中心。在需要时,可以在云上几分钟之内快速布置启动新的虚拟机和容器,如服务需求下降,可以关闭虚拟服务器。这样可显著提高应用程序的水平可伸缩性,也使应用程序更有弹性.

SpringCloud简介

cloud是一系统框架的集合( 服务注册与发现nacos/eureka, 服务远程调用openFeign/Feign ,服务降级,服务熔断, 服务限流 sentinel/hystrix ,分布式事务seata,配s置中心nacos/spring cloud config, 总线nacos/stream , 网关 gateway, 链路追踪 sleuth 等等,部分 )

cloud与微服务的关系: 微服务是思想, cloud是解决方案

CAP理论

CAP理论指出一个分布式系统中最多满足以下三项中特性其中两项

C: 一致性(Consistency)

A: 可用性(Availability)

P: 分区容错性(Partition tolerance) => 一定满足

给定一个业务场景

1.直接返回旧数据 满足 AP

2.等待同步后返回新数据 CP

服务注册与发现(nacos/eureka)

1.服务注册: Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如IP地址、端口等信息

2.服务心跳: 检测 Nacos Client定时心跳持续通知Nacos Server说明服务可用,防止被删除

3.服务同步: Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。

为什么要使用nacos server集群?

防止单体故障

4.服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给NacosServer,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存I

5.服务健康检查: 15s内没接收到服务心跳=>healthy=false,30s没接收到删除该实例

boot与cloud版本的对应

springboot版本: https://spring.io/projects/spring-boot#learn

boot 2.7.0-2.7.10

cloud版本: https://spring.io/projects/spring-cloud#overview

cloud 2021.0.6

版本对应表: https://start.spring.io/actuator/info

Nacos开始

nacos安装

1
...

注册服务到nacos

原理图

添加依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${latest.version}</version>
</dependency>

添加配置到application.yml文件

1
2
3
4
5
6
7
8
9
server:
port: 9000
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务器的地址
application:
name: resfood #服务名

启动类添加 @EnableDiscoveryClient 注解 开启服务注册发现功能

1
2
3
4
5
@SpringBootApplication
@EnableDiscoveryClient
public class xxxApplication{
...
}

运行application启动类注册服务

在浏览器输入localhost:8848/nacos/index.html 在服务列表即可查看到注册的服务

配置多个服务节点(负载均衡)

查看服务列表的该服务下的服务详情,即可看到服务集群中存在了两个服务节点

配置服务消费端

在父项目下新建一个resorder的模块

1.application.yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server:
port: 8000


spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务器地址
application:
name: resorder #注册到nacos服务上的服务名

#日志文件
logging:
level:
root: info
#指定RestTemplate包下的信息,以便在后续运行结果中能看到详细的请求信息
org.springframework.web.client: debug
org.apache: error
file:
# name: mylog.log
path: logs/

2.配置启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient //需要导入依赖
public class ResorderApp {
public static void main(String[] args) {
SpringApplication.run(ResorderApp.class,args);
}
}

@EnableDiscoveryClient依赖导入

1
2
3
4
5
<!--父项目已经约定了版本,子模块不需要配置版本,直接导入即可-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3.新增配置类

resfood模块的config包中加入以下配置文件

1
2
3
4
5
6
7
8
@Configuration
public class WebConfig {
@LoadBalanced //负载均衡 =>Because: resfood服务下有两个服务节点,请求会访问哪个节点? 默认轮询策略
@Bean //IOC resTemplate 对象托管到spring中
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

4.controller的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("resorder")
@Slf4j //日志信息需要导入lombok依赖
public class ResorderController {
//注入RestTemplate
@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "addCart" ,method = {RequestMethod.GET,RequestMethod.POST} )
public Map<String,Object> addCart(@RequestParam Integer fid, @RequestParam Integer num, HttpSession session) {
Map<String, Object> map = new HashMap<>();
//注入的 RestTemplate 对象,通过 getForObject 方法,向地址为 “http://localhost:9000/resfood/findById?fid=” + fid 的 Restful 服务发送了请求,获取指定 ID 的商品信息,并将其封装为 Map 对象返回,如果要使用负载均衡策略localhost应该改为服务名resfood。
Map<String,Object> result = this.restTemplate.getForObject("http://resfood/resfood/findById?fid=" + fid,Map.class);
//日志输出返回的结果
log.info("发送请求后返回的商品信息:" + result);
return map;
}

启动resorderApp把服务注册到nacos服务器上,在nacos服务列表检查服务是否注册成功

5.在postman中检测该服务是否可用

负载均衡

客户端负载均衡

a.将请求的地址由固定的 ip:端口 的方式改为 通过服务名访问

1
2
3
4
5
//负载均衡需要用服务名resfood访问才能起作用

//(resfood) 服务名 请求前缀@RequestMapping
String url = "http://resfood/resfood/findById?fid="+fid;
Map<String,Object> result = this.restTemplate.getForObject(url,Map.class);

b.映入loadbalancer依赖

在resorder模块 客户端(消费端)加入此依赖

1
2
3
4
5
<!--引入riboon,客户端保存服务列表信息-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

c.在客户端组件RestTemplate上加入@LoadBalanced

1
2
3
4
5
6
7
8
@Configuration
public class WebConfig {
@LoadBalanced //负载均衡 => resfood服务下有两个服务节点 一个请求会访问哪个节点?
@Bean //IOC restemplate 对象托管spring
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

配置不同的负载均衡策略

1
2
3
4
5
6
7
8
9
//方案一: 全局配置负载均衡策略
//@LoadBalancerClient(name = "resfood",configuration={ MyBalancerConfinguration.class })

//方案二: 对每个服务分别指定负载均衡策略
@LoadBalancerClients(
value = {
@LoadBalancerClient(value = "resfood",
configuration =MyBalancerConfinguration.class)},
defaultConfiguration = LoadBalancerClientConfiguration.class)

自定义负载均衡器

通过spring-cloud-starter-loadbalancer启动配置的源码剖析

1.在外部库中找到此jar包

发现starter类中导入了loadbalancer负载均衡的依赖

2.跟踪spring-cloud-loadbalancer在外部库的jar包

在此jar包中发现其中的spring工厂中启用了负载均衡的配置类

3.继续跟踪该自动配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//是一个Spring配置类,不是代理对象生成,关闭代理模式
@Configuration(
proxyBeanMethods = false
)
//负载均衡客户端可以配置多个,这里是所有的客户端都遵循此配置类
@LoadBalancerClients
//将 LoadBalancerClientsProperties.class 类型的配置属性绑定到 Spring 环境中,并启用
@EnableConfigurationProperties({LoadBalancerClientsProperties.class})
//优于ReactorLoadBalancerClientAutoConfiguration和 LoadBalancerBeanPostProcessorAutoConfiguration进行自动配置。
@AutoConfigureBefore({ReactorLoadBalancerClientAutoConfiguration.class, LoadBalancerBeanPostProcessorAutoConfiguration.class})
@ConditionalOnProperty(
value = {"spring.cloud.loadbalancer.enabled"},
havingValue = "true",
matchIfMissing = true //没有配置默认为true值
)

//这里LoadBalancerClientFactory产生LoadBalancer客户端,是我们需要的
//声明一个 Bean,用于提供负载均衡器的工厂实例
@ConditionalOnMissingBean //没有其他同类型的 Bean 定义的情况下,才创建该Bean。
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {
...
}

4.追踪LoadBalancerClientFactory

找到该bean工厂中的getInstance方法

1
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId)

该方法的作用是获取指定服务的负载均衡实例,返回一个ReactiveLoadBalancer类型的对象。其中,serviceId参数表示服务的标识符,根据serviceId获取相关的负载均衡客户端,并返回一个负载均衡实例;

5.继续追踪ReactorServiceInstanceLoadBalancer

1
2
>ReactorLoadBalancer的标识接口,允许选择服务实例对象
>在服务接口中选取一种进行查看,这里选的是轮询负载均衡

6.查看RoundRobinLoadBalancer源码

轮询算法:

原子整型 默认为0

  1. 初始时 this.position 的值为 0。

  2. this.position.incrementAndGet()this.position 的值加 1,变为 1。

  3.  & 2147483647
      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61

    进行位与运算:

    - 1 的二进制表示为:`00000000000000000000000000000001`
    - 2147483647 的二进制表示为:`01111111111111111111111111111111`
    - 进行按位与运算:`00000000000000000000000000000001`
    - 得到的结果是 1。

    >4. `(pos % instances.size())` 计算 1 除以 3 的余数,结果为 1。

    >5. 通过 `instances.get(1)` 获取 `instances` 列表中索引为 1 的元素。

    >接下来继续执行相同的运算步骤即可

    >`(pos % instances.size())`=0时,算法完成一个循环,归0

    ![](/img/SpringCloud/loadbalancer-starter/6.png)

    #### 自定义一个负载均衡器

    >只访问一个服务 `OnlyOneLoadBalancer` 负载均衡器

    *1.写一个`LoadBalancer`类实现`ReactorServiceInstanceLoadBalancer` 接口*

    ```java
    @Slf4j
    public class OnlyOneLoadBalancer implements ReactorServiceInstanceLoadBalancer {
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public OnlyOneLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
    this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
    ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
    return supplier.get(request).next().map((serviceInstances) -> {
    return processInstanceResponse(supplier, serviceInstances);
    });
    }

    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
    List<ServiceInstance> serviceInstances) {
    Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
    if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
    ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
    }
    return serviceInstanceResponse;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
    log.info("自定义负载");
    if (instances.isEmpty()) {
    return new EmptyResponse();
    }
    //固定访问第1个服务
    ServiceInstance instance = instances.get(0);

    return new DefaultResponse(instance);
    }
    }

2.用于自定义负载均衡器的配置类

定义了一个名为OnlyOneLoadBalancerConfiguration的@Configuration注解类,使用@Bean注解托管,声明了一个名为OnlyOneReactorServiceInstanceLoadBalancer的方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class OnlyOneLoadBalancerConfiguration {

//配置自定义策略
@Bean
public ReactorServiceInstanceLoadBalancer OnlyOneReactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
//使用getProperty方法从Environment对象中获取负载均衡器的名称属性值
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
//随机RandomLoadBalancer;轮询RoundRobinLoadBalancer
return new OnlyOneLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
}
}

3.在客户端组件RestTemplate上加入@LoadBalanced

这段代码配置了使用自定义的负载均衡器去负载均衡访问名为”resfood”和”resorder”的服务,并且通过@LoadBalanced注解将RestTemplate设置为负载均衡的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
//服务名绑定负载均衡策略
@LoadBalancerClients(
//resfood和resorder服务使用自定义的负载均衡策略,其它的服务使用默认的轮询负载均衡策略
value = {
@LoadBalancerClient(value = "resfood",configuration = OnlyOneLoadBalancerConfiguration.class),
@LoadBalancerClient(value = "resorder",configuration = OnlyOneLoadBalancerConfiguration.class),
},defaultConfiguration = LoadBalancerClientConfiguration.class
)
public class WebConfig {
@LoadBalanced //负载均衡 => resfood服务下有两个服务节点 一个请求会访问哪个节点?
@Bean //IOC restemplate 对象托管spring
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

OpenFeign

OpenFeign

SpringCloud OpenFeign是一种声明式的REST客户端

原理

开发者只需要定义服务接口并用注解描述服务即可,其余都交给OpenFeign 处理

OpenFeign在项目启动时生成动态代理类,代理类集成了负载均衡器,可以自动选择服务实例并发送请求

特点

自动集成Ribbon,实现客户端负载均衡。

自动集成Hystrix,实现服务降级和熔断。

自动集成Eureka或Consul,从服务注册中心获取服务列表。

可以自定义Feign的组件,如编码器,解码器,拦截器等

Feign

HTTP请求调用的轻量级框架(底层jdk面向接口的动态代理)

封装了HTTP 调用了流程,面向接口编程 =>java注解方式调用Http请求

请求模板化,要进行适配,根据传入的参数应用在对应的请求上,进而转化为真正的请求

为什么使用Feign和OpenFeign

Feign和OpenFeign的作用是简化和优化微服务架构中服务之间的HTTP调用。

传统的HTTP调用需要手动编写大量的代码来处理请求的创建、发送、接收和解析等过程,而且还需要处理负载均衡、熔断等场景。这使得开发者要花费大量的精力来处理这些细节,增加了开发的复杂度和工作量。

Feign开始

实现Feign 客户端发送 HTTP 请求到指定的 GitHub 仓库地址,并获取该仓库的贡献者列表,并将结果打印输出的功能。

1.引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>10.4.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-gson</artifactId>
<version>10.4.0</version>
</dependency>
</dependencies>

2.实体类

OpenFeign代码贡献者的数据实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.yc;

public class Contributor {
String login;
int contributions;
int id;
String node_id;
String avatar_url;
String gravatar_id;
String url;
String html_url;
String followers_url;

@Override
public String toString() {
return "Contributor{" +
"login='" + login + '\'' +
", contributions=" + contributions +
", id=" + id +
", node_id='" + node_id + '\'' +
", avatar_url='" + avatar_url + '\'' +
", gravatar_id='" + gravatar_id + '\'' +
", url='" + url + '\'' +
", html_url='" + html_url + '\'' +
", followers_url='" + followers_url + '\'' +
'}';
}

}

3.Feign 的接口定义

该接口使用 Feign 提供的注解和方法规范,声明了一个获取 GitHub 贡献者列表的方法(contributors)

1
2
3
4
5
6
7
8
public interface GitHub {
// 传入的参数后可以拼接成url: https://api.github.com/repos/OpenFeign/feign/contributors
@RequestLine("GET /repos/{owner}/{repo}/contributors") //feign 提供的规范
// SpringMVC的注解形式 @RequestMapping({value="/repos/{owner/{repo}/contributor,method={GET})"}
// List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

}

4.测试

调用 GitHub 接口中的 contributors() 方法,传入 “OpenFeign” 和 “feign” 作为 owner 和 repo 的值,以获取贡献者列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyApp {
public static void main(String[] args) {
// 生成的代理类文件的保存路径
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
GitHub github = Feign.builder() //Feign客户端的构造器
.decoder(new GsonDecoder()) //gson解码器将json数据转为java对象

// target() 方法指定我们要请求的 GitHub 接口访问地址的前缀为2 “https://api.github.com”
.target(GitHub.class, "https://api.github.com");

//打印输出者信息
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor);
}
}
}

OpenFeign-Api暴露

OpenFeign API暴露是指将一个Feign客户端声明为一个Spring Bean并对外暴露,使其它组件可以注入和使用,这样可以方便地在其它组件中使用Feign客户端来进行微服务之间的HTTP调用。

1.导入OpenFeign的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.导入项目的bean(实体类)

1
2
3
4
5
><dependency>
<artifactId>res-bean</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
></dependency>

3.创建api服务,在此服务中加入要公开 API接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@FeignClient("resfood")//指定服务名称
public interface ResfoodApi {

@RequestMapping(value = "resfood/detailCountAdd")
public Map<String,Object> detailCountAdd(@RequestParam Integer fid);


@GetMapping( "resfood/findById/{fid}")
public Map<String,Object> findById(@PathVariable Integer fid);


@GetMapping("resfood/findAll")
// @ApiOperation(value = "查询所有菜品")
public Map<String,Object> findAll();


@RequestMapping("resfood/findByPage")
public Map<String,Object> findByPage(@RequestParam int pageno,@RequestParam int pagesize,@RequestParam(required = false) String sortby,@RequestParam (required = false) String sort) ;

}

4.在调用端引入依赖和api服务端

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<dependency>
<groupId>org.example</groupId>
<artifactId>res-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

5.注入api接口到controller或业务层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping("resorder")
@Slf4j
public class ResorderController {
//注入api接口
@Autowired
private ResfoodApi resfoodApi;

@RequestMapping(value = "addCart" ,method = {RequestMethod.GET,RequestMethod.POST} )
public Map<String,Object> addCart(@RequestParam Integer fid, @RequestParam Integer num, HttpSession session) {
Map<String, Object> map = new HashMap<>();
//利用Openfeign中暴露的api接口直接调用
Map<String,Object> result = resfoodApi.findById(fid);
log.info("发送请求后返回的商品信息:" + result);
return map;
}

}

6.在主启动类上添加@EnableFeignClients

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDiscoveryClient
//指定api的包路径
@EnableFeignClients(basePackages = {"com.yc.api"})
public class ResorderApp {
public static void main(String[] args) {
SpringApplication.run(ResorderApp.class,args);
}
}

发送请求,控制台查看结果

一般使用步骤: 1.创建api服务. 在此服务中加入要公开 API接口 org.springframework.cloud spring-cloud-starter-openfeign 2. 在这个api服务中开发 接口: @FeignClient(“resfood”) public interface ResfoodApi { @RequestMapping( value=”resfood/detailCountAdd” , method=RequestMethod.GET) public Map<String, Object> detailCountAdd(Integer fid); } 3. 调用端开发: res-api org.example 1.0-SNAPSHOT org.springframework.cloud spring-cloud-starter-openfeign b)开启openfeign的客户端 @EnableFeignClients(basePackages= {“com.yc.api”}) c)注入api接口到controller或业务层. @Autowired private ResfoodApi resfoodApi;

Feign中的组件

。。。

OpenFeign日志配置

官方文档地址: https://docs.spring.io/spring-cloud-openfeign/docs/3.1.8/reference/html/#feign-logging

yml配置

在客户端(调用api)的yml配置文件添加以下代码

1
2
3
logging:
level:
com.yc.api: DEBUG #被调用api的包路径,官方文档要求DEBUG日志级别才能输出

客户端单独配置

1
>Logger.Level
  • NONE,无日志记录(默认)。
  • BASIC,仅记录请求方法和 URL 以及响应状态码和执行时间。
  • HEADERS,记录基本信息以及请求和响应标头。
  • FULL,记录请求和响应的标头、正文和元数据。

openfeign客户端单独配置

1
2
3
4
5
6
7
8
@Configuration
public class OpenFeignLogConfiguration {
@Bean
Logger.Level feignLoggerLever(){
//FULL输出所有的日志
return Logger.Level.FULL;
}
}

发送请求查看日志

网络压缩

为什么要使用网络压缩?

在网络传输中数据存在一些冗余的字符,像json数据中的换行符、空格,都会带来大量的流量损耗,而OpenFeign 提供了对 Gzip 压缩的支持,在网络传输中,通过对 HTTP 请求和响应进行 Gzip 压缩,可以减少传输数据的大小,缩短传输时间,降低网络带宽的消耗和服务器压力,从而提高系统的性能和稳定性。

配置

1.yml配置

1
2
3
4
5
6
7
8
9
10
feign:
compression:
request:
mime-types: #可以被压缩的类型
- text/xml
- application/xml
- application/json
min-request-size: 2048 #超过2048字节进行压缩
response:
enabled: true

2.源码剖析

理解配置项为什么要这样配置

请参考: https://forestcat.blog.csdn.net/article/details/109077317

扩展

原子整型

AtomicInteger.incrementAndGet();

i++ ;

底层

i=0;

i=i+1;

多线程编程无法保证执行顺序