简化本地Feign调用

news2024/10/6 18:30:09

在平常的工作中,OpenFeign作为微服务间的调用组件使用的非常普遍,接口配合注解的调用方式突出一个简便,让我们能无需关注内部细节就能实现服务间的接口调用。

但是工作中用久了,发现 Feign 也有些使用起来麻烦的地方,下面先来看一个问题,再看看我们在工作中是如何解决,以达到简化 Feign 使用的目的。

先看问题

在一个项目开发的过程中,我们通常会区分开发环境、测试环境和生产环境,如果有的项目要求更高的话,可能还会有个预生产环境。

开发环境作为和前端开发联调的环境,一般使用起来都比较随意,而我们在进行本地开发的时候,有时候也会将本地启动的微服务注册到注册中心 nacos 上,方便进行调试。

这样,注册中心的一个微服务可能就会拥有多个服务实例,就像下面这样:

眼尖的小伙伴肯定发现了,这两个实例的 ip 地址有一点不同。

线上环境现在一般使用容器化部署,通常都是由流水线工具打成镜像然后扔到 docker 中运行,因此我们去看一下服务在 docker 容器内的 ip:

可以看到,这就是注册到 nacos 上的服务地址之一,而列表中192开头的另一个 ip,则是我们本地启动的服务的局域网地址。看一下下面这张图,就能对整个流程一目了然了。

总结一下:

  • 两个 service 都是通过宿主机的 ip 和 port,把自己的信息注册到 nacos 上
  • 线上环境的 service 注册时使用 docker 内部 ip 地址
  • 本地的 service 注册时使用本地局域网地址

那么这时候问题就来了,当我本地再启动一个 serviceB,通过FeignClient来调用 serviceA 中的接口时,因为 Feign 本身的负载均衡,就可能把请求负载均衡到两个不同的 serviceA 实例。

如果这个调用请求被负载均衡到本地 serviceA 的话,那么没什么问题,两个服务都在同一个192.168网段内,可以正常访问。但是如果负载均衡请求到运行在 docker 内的 serviceA 的话,那么问题来了,因为网络不通,所以会请求失败:

图片

说白了,就是本地的192.168和 docker 内的虚拟网段172.17属于纯二层的两个不同网段,不能互访,所以无法直接调用。

那么,如果想在调试时把请求稳定打到本地服务的话,有一个办法,就是指定在FeignClient中添加url参数,指定调用的地址:

@FeignClient(value = "serviceA",url = "http://127.0.0.1:8088/")
public interface ClientA {
    
@GetMapping("/test/get")
    String 
get();
}

但是这么一来也会带来点问题:

  • 代码上线时需要再把注解中的url删掉,还要再次修改代码,如果忘了的话会引起线上问题
  • 如果测试的FeignClient很多的话,每个都需要配置url,修改起来很麻烦

那么,有什么办法进行改进呢?为了解决这个问题,我们还是得从 Feign 的原理说起。

Feign 原理

Feign 的实现和工作原理,我以前写过一篇简单的源码分析,大家可以简单花个几分钟先铺垫一下,Feign 核心源码解析。明白了原理,后面理解起来更方便一些。

简单来说,就是项目中加的@EnableFeignClients这个注解,实现时有一行很重要的代码:

@Import(FeignClientsRegistrar.class)

这个类实现了ImportBeanDefinitionRegistrar接口,在这个接口的registerBeanDefinitions方法中,可以手动创建BeanDefinition并注册,之后 spring 会根据BeanDefinition实例化生成 bean,并放入容器中。

Feign 就是通过这种方式,扫描添加了@FeignClient注解的接口,然后一步步生成代理对象,具体流程可以看一下下面这张图:

后续在请求时,通过代理对象的FeignInvocationHandler进行拦截,并根据对应方法进行处理器的分发,完成后续的 http 请求操作。

ImportBeanDefinitionRegistrar

上面提到的ImportBeanDefinitionRegistrar,在整个创建FeignClient的代理过程中非常重要, 所以我们先写一个简单的例子看一下它的用法。先定义一个实体类:

@Data
@AllArgsConstructor
public class User {
    Long id;
    String name;
}

通过BeanDefinitionBuilder,向这个实体类的构造方法中传入具体值,最后生成一个BeanDefinition

public class MyBeanDefinitionRegistrar
        implements ImportBeanDefinitionRegistrar {
    
@Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder
                = BeanDefinitionBuilder.genericBeanDefinition(User.
class);
        builder.addConstructorArgValue(
1L);
        builder.addConstructorArgValue(
"Hydra");

        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        registry.registerBeanDefinition(User.
class.getSimpleName(),beanDefinition);
    }
}

registerBeanDefinitions方法的具体调用时间是在之后的ConfigurationClassPostProcessor执行postProcessBeanDefinitionRegistry方法时,而registerBeanDefinition方法则会将BeanDefinition放进一个 map 中,后续根据它实例化 bean。

在配置类上通过@Import将其引入:

@Configuration
@Import(MyBeanDefinitionRegistrar.class)
public class MyConfiguration {
}

注入这个User测试:

@Service
@RequiredArgsConstructor
public class UserService {
    
private final User user;

    
public void getUser(){
        System.out.println(user.toString());
    }
}

结果打印,说明我们通过自定义BeanDefinition的方式成功手动创建了一个 bean 并放入了 spring 容器中:

User(id=1, name=Hydra)

好了,准备工作铺垫到这结束,下面开始正式的改造工作。

改造

到这里先总结一下,我们纠结的点就是本地环境需要FeignClient中配置url,但线上环境不需要,并且我们又不想来回修改代码。

除了像源码中那样生成动态代理以及拦截方法,官方文档中还给我们提供了一个手动创建 FeignClient 的方法。

https://docs.spring.io/spring-cloud-openfeign/docs/2.2.9.RELEASE/reference/html/#creating-feign-clients-manually

简单来说,就是我们可以像下面这样,通过 Feign 的 Builder API 来手动创建一个 Feign 客户端。

简单看一下,这个过程中还需要配置ClientEncoderDecoderContractRequestInterceptor等内容。

  • Client:实际 http 请求的发起者,如果不涉及负载均衡可以使用简单的Client.Default,用到负载均衡则可以使用LoadBalancerFeignClient,前面也说了,LoadBalancerFeignClient中的delegate其实使用的也是Client.Default
  • EncoderDecoder:Feign 的编解码器,在 spring 项目中使用对应的SpringEncoderResponseEntityDecoder,这个过程中我们借用GsonHttpMessageConverter作为消息转换器来解析 json
  • RequestInterceptor:Feign 的拦截器,一般业务用途比较多,比如添加修改 header 信息等,这里用不到可以不配
  • Contract:字面意思是合约,它的作用是将我们传入的接口进行解析验证,看注解的使用是否符合规范,然后将关于 http 的元数据抽取成结果并返回。如果我们使用RequestMappingPostMappingGetMapping之类注解的话,那么对应使用的是SpringMvcContract

其实这里刚需的就只有Contract这一个,其他都是可选的配置项。我们写一个配置类,把这些需要的东西都注入进去:

@Slf4j
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({LocalFeignProperties.class})
@
Import({LocalFeignClientRegistrar.class})
@
ConditionalOnProperty(value = "feign.local.enable", havingValue = "true")
public class FeignAutoConfiguration {
    
static {
        log.info(
"feign local route started");
    }

    
@Bean
    @Primary
    public Contract contract(){
        
return new SpringMvcContract();
    }

    
@Bean(name = "defaultClient")
    
public Client defaultClient(){
        
return new Client.Default(null,null);
    }

    
@Bean(name = "ribbonClient")
    
public Client ribbonClient(CachingSpringLoadBalancerFactory cachingFactory,
                               SpringClientFactory clientFactory){
        
return new LoadBalancerFeignClient(defaultClient(), cachingFactory,
                clientFactory);
    }

    
@Bean
    public Decoder decoder(){
        HttpMessageConverter httpMessageConverter=
new GsonHttpMessageConverter();
        ObjectFactory<HttpMessageConverters> messageConverters= () -> 
new HttpMessageConverters(httpMessageConverter);
        SpringDecoder springDecoder = 
new SpringDecoder(messageConverters);
        
return new ResponseEntityDecoder(springDecoder);
    }

    
@Bean
    public Encoder encoder(){
        HttpMessageConverter httpMessageConverter=
new GsonHttpMessageConverter();
        ObjectFactory<HttpMessageConverters> messageConverters= () -> 
new HttpMessageConverters(httpMessageConverter);
        
return new SpringEncoder(messageConverters);
    }
}

在这个配置类上,还有三行注解,我们一点点解释。

首先是引入的配置类LocalFeignProperties,里面有三个属性,分别是是否开启本地路由的开关、扫描 FeignClient 接口的包名,以及我们要做的本地路由映射关系,addressMapping中存的是服务名和对应的 url 地址:

@Data
@Component
@ConfigurationProperties(prefix = "feign.local")
public class LocalFeignProperties {
    
// 是否开启本地路由
    private String enable;

    
//扫描FeignClient的包名
    private String basePackage;

    
//路由地址映射
    private Map<String,String> addressMapping;
}

下面这行注解则表示只有当配置文件中feign.local.enable这个属性为true时,才使当前配置文件生效:

@ConditionalOnProperty(value = "feign.local.enable", havingValue = "true")

最后,就是我们重中之重的LocalFeignClientRegistrar了,我们还是按照官方通过ImportBeanDefinitionRegistrar接口构建BeanDefinition然后注册的思路来实现。

并且,FeignClientsRegistrar的源码中已经实现好了很多基础的功能,比如扫扫描包、获取FeignClientnamecontextIdurl等等,所以需要改动的地方非常少,可以放心的大抄特超它的代码。

先创建LocalFeignClientRegistrar,并注入需要用到的ResourceLoaderBeanFactoryEnvironment

@Slf4j
public class LocalFeignClientRegistrar implements
        ImportBeanDefinitionRegistrarResourceLoaderAware,
        
EnvironmentAwareBeanFactoryAware{

    
private ResourceLoader resourceLoader;
    
private BeanFactory beanFactory;
    
private Environment environment;

    
@Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        
this.resourceLoader=resourceLoader;
    }

    
@Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        
this.beanFactory = beanFactory;
    }

    
@Override
    public void setEnvironment(Environment environment) {
        
this.environment=environment;
    }

 
//先省略具体功能代码...
}

然后看一下创建BeanDefinition前的工作,这一部分主要完成了包的扫描和检测@FeignClient注解是否被添加在接口上的测试。下面这段代码基本上是照搬源码,除了改动一下扫描包的路径,使用我们自己在配置文件中配置的包名。

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = ComponentScanner.getScanner(environment);
    scanner.setResourceLoader(resourceLoader);
    AnnotationTypeFilter annotationTypeFilter = 
new AnnotationTypeFilter(FeignClient.class);
    scanner.addIncludeFilter(annotationTypeFilter);

    String basePackage =environment.getProperty(
"feign.local.basePackage");
    log.info(
"begin to scan {}",basePackage);

    Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);

    
for (BeanDefinition candidateComponent : candidateComponents) {
        
if (candidateComponent instanceof AnnotatedBeanDefinition) {
            log.info(candidateComponent.getBeanClassName());

            
// verify annotated class is an interface
            AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
            AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
            Assert.isTrue(annotationMetadata.isInterface(),
                    
"@FeignClient can only be specified on an interface");

            Map<String, Object> attributes = annotationMetadata
                    .getAnnotationAttributes(FeignClient.
class.getCanonicalName());

            String name = FeignCommonUtil.getClientName(attributes);
            registerFeignClient(registry, annotationMetadata, attributes);
        }
    }
}

接下来创建BeanDefinition并注册,Feign 的源码中是使用的FeignClientFactoryBean创建代理对象,这里我们就不需要了,直接替换成使用Feign.builder创建。

private void registerFeignClient(BeanDefinitionRegistry registry,
                                 AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    Class clazz = ClassUtils.resolveClassName(className, 
null);
    ConfigurableBeanFactory beanFactory = registry 
instanceof ConfigurableBeanFactory
            ? (ConfigurableBeanFactory) registry : 
null;
    String contextId = FeignCommonUtil.getContextId(beanFactory, attributes,environment);
    String name = FeignCommonUtil.getName(attributes,environment);

    BeanDefinitionBuilder definition = BeanDefinitionBuilder
            .genericBeanDefinition(clazz, () -> {
                Contract contract = beanFactory.getBean(Contract.
class);
                Client defaultClient = (Client) beanFactory.getBean(
"defaultClient");
                Client ribbonClient = (Client) beanFactory.getBean(
"ribbonClient");
                Encoder encoder = beanFactory.getBean(Encoder.
class);
                Decoder decoder = beanFactory.getBean(Decoder.
class);

                LocalFeignProperties properties = beanFactory.getBean(LocalFeignProperties.
class);
                Map<String, String> addressMapping = properties.getAddressMapping();

                Feign.Builder builder = Feign.builder()
                        .encoder(encoder)
                        .decoder(decoder)
                        .contract(contract);

                String serviceUrl = addressMapping.get(name);
                String originUrl = FeignCommonUtil.getUrl(beanFactory, attributes, environment);

                Object target;
                
if (StringUtils.hasText(serviceUrl)){
                    target = builder.client(defaultClient)
                            .target(clazz, serviceUrl);
                }
else if (StringUtils.hasText(originUrl)){
                    target = builder.client(defaultClient)
                            .target(clazz,originUrl);
                }
else {
                    target = builder.client(ribbonClient)
                            .target(clazz,
"http://"+name);
                }

                
return target;
            });

    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    definition.setLazyInit(
true);
    FeignCommonUtil.validate(attributes);

    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
    beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);

    
// has a default, won't be null
    boolean primary = (Boolean) attributes.get("primary");
    beanDefinition.setPrimary(primary);

    String[] qualifiers = FeignCommonUtil.getQualifiers(attributes);
    
if (ObjectUtils.isEmpty(qualifiers)) {
        qualifiers = 
new String[] { contextId + "FeignClient" };
    }

    BeanDefinitionHolder holder = 
new BeanDefinitionHolder(beanDefinition, className,
            qualifiers);
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}

在这个过程中主要做了这么几件事:

  • 通过beanFactory拿到了我们在前面创建的ClientEncoderDecoderContract,用来构建Feign.Builder
  • 通过注入配置类,通过addressMapping拿到配置文件中服务对应的调用url
  • 通过target方法替换要请求的url,如果配置文件中存在则优先使用配置文件中url,否则使用@FeignClient注解中配置的url,如果都没有则使用服务名通过LoadBalancerFeignClient访问

resources/META-INF目录下创建spring.factories文件,通过 spi 注册我们的自动配置类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.feign.local.config.FeignAutoConfiguration

最后,本地打包即可:

mvn clean install

测试

引入我们在上面打好的包,由于包中已经包含了spring-cloud-starter-openfeign,所以就不需要再额外引feign的包了:

<dependency>
    <
groupId>com.cn.hydra</groupId>
    <
artifactId>feign-local-enhancer</artifactId>
    <
version>1.0-SNAPSHOT</version>
</
dependency>

在配置文件中添加配置信息,启用组件:

feign:
  local:
    enable: true
    basePackage: com.service
    addressMapping:
      hydra-service: http://127.0.0.1:8088
      trunks-service: http://127.0.0.1:8099

创建一个FeignClient接口,注解的url中我们可以随便写一个地址,可以用来测试之后是否会被配置文件中的服务地址覆盖:

@FeignClient(value = "hydra-service",
 contextId = 
"hydra-serviceA",
 url = 
"http://127.0.0.1:8099/")
public interface ClientA {
    
@GetMapping("/test/get")
    String 
get();

    
@GetMapping("/test/user")
    User 
getUser();
}

启动服务,过程中可以看见了执行扫描包的操作:

在替换url过程中添加一个断点,可以看到即使在注解中配置了url,也会优先被配置文件中的服务url覆盖:

使用接口进行测试,可以看到使用上面的代理对象进行了访问并成功返回了结果:

图片

如果项目需要发布正式环境,只需要将配置feign.local.enable改为false或删掉,并在项目中添加 Feign 原始的@EnableFeignClients即可。

总结

本文提供了一个在本地开发过程中简化 Feign 调用的思路,相比之前需要麻烦的修改FeignClient中的url而言,能够节省不少的无效劳动,并且通过这个过程,也可以帮助大家了解我们平常使用的这些组件是怎么与 spring 结合在一起的,熟悉 spring 的扩展点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Haproxy 搭建Web 群集】

目录 一、Haoroxy 基础了解1、常见的Web集群调度器2、Haproxy 应用分析 二、Haproxy 调度算法原理三、HAProxy的主要特性四、HAProxy负载均衡策略五、LVS、Nginx、HAproxy的区别1、Nginx的优点&#xff0c;缺点2、LVS的优点和缺点3、HAProxy的优点 六、Haproxy搭建 Web 群集实验…

跟着LearnOpenGL学习8--摄像机

文章目录 一、前言二、摄像机/观察空间2.1、摄像机位置2.2、摄像机方向2.3、右轴2.4、上轴2.5、LookAt2.6、LookAt测试 三、自由移动3.1、移动速度 四、视角移动4.1、欧拉角 五、鼠标输入5.1、缩放 六、摄像机类 一、前言 前面的教程中我们讨论了观察矩阵以及如何使用观察矩阵…

【软考系统架构师】进程与线程、并发和并行的理解

进程和线程的概念是软考里经常出现的概念&#xff0c;也是计算机领域的基础概念之一&#xff0c;看到一套非常形象的进程和线程的解释&#xff0c;记录一下 CPU 相当于一个工厂的能源核心&#xff0c;它一直运行&#xff0c;并向外提供动力。 什么是进程 但是这家工厂资金有限&…

A*算法与八数码问题(numpy)

努力是为了不平庸~ 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。 目录 一、引言 二、思路 1. 确定问题和目标&#xff1a; 2. 确定算法和数据结构&#xff1a; 3. 编写代码框架 4. 实现辅助函数&#xff1…

CreateML 使用以及机器学习基础概念

1. 前言 在学习 CreateML 之前&#xff0c;我们先了解一下什么是机器学习&#xff1f;目前还不存在被广泛认可的定义来准确定义机器学习是什么。第一个机器学习的定义来自于Arthur Samuel。他定义机器学习为&#xff0c;在进行特定编程的情况下&#xff0c;给予计算机学习能力…

C语言-类型转换

数据有不同的类型&#xff0c;不同类型数据之间进行混合运算时必然涉及到类型的转换问题. 转换的方法有两种&#xff1a; 自动转换: 遵循一定的规则,由编译系统自动完成. 强制类型转换&#xff1a; 把表达式的运算结果强制转换成所需的数据类型 1 自动转换 自动转换原则 …

阿里人手一本的Java性能调优手册,几乎涵盖了性能优化的所有操作

说起性能调优&#xff0c;想必大家都知道&#xff0c;但是就是没怎么用过&#xff0c;所以在Java性能优化上面临着很多的困扰&#xff0c;比如&#xff1a; 能力修炼中&#xff0c;由于常年接触 CRUD&#xff0c;缺乏高并发这一实践环境&#xff0c;对“性能优化”只能通过理论…

剑指 Offer 59 - I: 滑动窗口的最大值

第一眼看这个感觉思路没啥大问题&#xff0c;就是一个大循环不断后移&#xff0c;然后小循环维护一个k长度的队列。注意peekFirst和peekLast的使用&#xff0c;双端队列可以打破先进先出或者先进后出的局限性&#xff0c;因此peek没有默认值&#xff0c;得加上First或者Last来进…

干货 | 腾讯云李滨:云时代数据安全治理方法与实践

作者&#xff1a;李滨‍‍‍‍本文约2600字&#xff0c;建议阅读8分钟 本文与你分享腾旭在数据安全和人工智能的安全治理方法&#xff0c;以及实践上的一些经验。 今天很荣幸与大家分享在过去几年以内&#xff0c;腾讯在数据安全和人工智能的安全治理方法&#xff0c;以及实践上…

使用服务器的妙妙工具

为什么使用服务器&#xff1f; 一台电脑的性能终究是有限的&#xff0c;所以当计算量太大时&#xff0c;我们可以借助服务器&#xff0c;它的运算速度快&#xff0c;存储空间大。 和个人电脑不同&#xff0c;服务器通常由一个管理员进行管理&#xff0c;而有多用户同时使用。…

一行代码实现网页直接跳转抖音,2023年6月新方法

之前 发的文章 方法已经失效&#xff0c;以下为新版本方案&#xff01; 背景 大家可能都发现在一些APP中无法直接打开抖音的链接&#xff0c;就连抖音官方的分享链接在浏览器打开都无法直接唤醒APP。需要我们手动点击&#xff0c;如果我们在自己网站等推广希望直接拉起抖音可…

工厂方法模式(五)

过气的&#xff0c;终究是过气了 上一章简单介绍了工厂模式(四), 如果没有看过,请观看上一章 一.工厂方法模式 工厂方法模式&#xff0c;通过定义工厂父类负责定义创建对象的公共接口&#xff0c;而子类则负责生成具体的对象。 将类的实例化&#xff08;具体产品的创建&…

全网最火爆,性能测试基本原则方法总结,再不卷就被淘汰了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 性能测试关键点 …

2023 年牛客网最新版大厂 Java 八股文面试题总结(覆盖所有面试题考点)

序员真的是需要将终生学习贯彻到底的职业&#xff0c;一旦停止学习&#xff0c;离被淘汰&#xff0c;也就不远了。 金九银十跳槽季&#xff0c;这是一个千年不变的话题&#xff0c;每到这个时候&#xff0c;很多人都会临阵磨枪&#xff0c;相信不快也光。于是&#xff0c;大家…

什么是Vue的数据流(单向数据流)?如何进行数据流管理

什么是Vue的数据流&#xff08;单向数据流&#xff09;&#xff1f;如何进行数据流管理 在Vue中&#xff0c;数据流是指数据的传递和管理方式。Vue采用的是单向数据流&#xff0c;也就是说&#xff0c;数据是从父组件流向子组件&#xff0c;子组件不能直接修改父组件的数据。本…

夏季来临居民如何预防电气火灾隐患

安科瑞虞佳豪 居住场所预防电气火患 请注意以下几点 1.用具有生产许可证或CCC证书并与居住场所的环境相适应的电气线路以及插座、插线板、充电器、用电设备等电器产品&#xff1b; 2.不私自拉接电气线路&#xff1b;不随意更改电气线路保护装置&#xff1b; 3.不随意更换原…

泛娱乐社交(一)直播产品商业化解决方案

摘要 在过去几年的直播行业创业风口期中&#xff0c;直播的用户关注度疯狂增长&#xff0c;但用户质量却参差不齐。随着用户新鲜感一过&#xff0c;流失率变得相当严重&#xff0c;各大平台都在竭尽全力防御。然而&#xff0c;留住“凑热闹”的非直播受众用户并不是最关键的问…

SpringBoot(基础篇)

SpringBoot基础篇 入门案例 在创建SpringBoot项目时&#xff0c;会出现以下不需要的文件&#xff0c;如果每次都手动删除的话&#xff0c;就会很麻烦。 教你一招 在setting设置中找到Editor&#xff0c;选择File Types–>Ignored Files and Folders–>点击号&#xff…

pycharm使用之torch_geometric安装

正式安装之前要先查看一下torch的版本 一、查看torch版本 1、winR &#xff0c;输入cmd 2、输入python 3、 输入import torch&#xff0c;然后输入torch.__version__&#xff0c;最后回车 可以看到我的torch版本是1.10.0 二、下载合适的版本 1、打开链接 https://pytorch-…

千万级入口服务[Gateway]框架设计(一)

本文将以技术调研模式编写&#xff0c;非技术同学可跳过。 文章目录 背景问题[不涉及具体业务]目标技术选型语言框架模式实现一&#xff1a;go 原生组件Demo 实现Benchwork 基准性能小结实现二&#xff1a;开源 go-plugin 附录入口服务演变 背景 在历史架构的迭代中&#xff…