【SpringCloud系列】开发环境下重写Loadbalancer实现自定义负载均衡

news2025/1/23 2:10:40

前言

        spring-cloud-starter-netflix-ribbon已经不再更新了,最新版本是2.2.10.RELEASE,最后更新时间是2021年11月18日,详细信息可以看maven官方仓库:https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-starter-netflix-ribbon,SpringCloud官方推荐使用spring-cloud-starter-loadbalancer进行负载均衡。我们在开发的时候,多人开发同一个微服务,都注册到同一个nacos,前端请求的时候,网关Gateway默认轮训请求注册中心的服务,OpenFeign也会轮询请求注册中心的服务,这样就会导致前端有时会无法请求到我们本地写的接口,而是请求到别人的服务中。所以我们可以重写Loadbalancer默认的负载均衡策略,实现自定义负载均衡策略,不管是Gateway还是OpenFeign都只能请求到我们自己本地的服务。

        我的版本如下:

        <spring-boot.version>2.7.3</spring-boot.version>
        <spring-cloud.version>2021.0.4</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>

一、添加负载方式配置

        1、定义负载均衡方式的枚举

public enum LoadBalancerTypeEnum {

    /**
     * 开发环境,获取自己的服务
     */
    DEV,

    /**
     * 网关,根据请求地址获取对应的服务
     */
    GATEWAY,

    /**
     * 轮循
     */
    ROUND_ROBIN,

    /**
     * 随机
     */
    RANDOM;

}

        2、添加配置类,默认使用轮训方式

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * 负载均衡配置项
 */
@Data
@ConfigurationProperties(prefix = "spring.cloud.loadbalancer")
public class LoadBalanceProperties {

    private LoadBalancerTypeEnum type = LoadBalancerTypeEnum.ROUND_ROBIN;

}

二、参考默认实现自定义

        默认的负载均衡策略是这个类:

org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer

        我们参考这个类实现自己的负载均衡策略即可,RoundRobinLoadBalancer实现了ReactorServiceInstanceLoadBalancer这个接口,实现了choose这个方法,如下图:

        在choose方法中调用了processInstanceResponse方法,processInstanceResponse方法中调用了getInstanceResponse方法,所以我们我们可以复制RoundRobinLoadBalancer整个类,只修改getInstanceResponse这个方法里的内容就可以实现自定义负载均衡策略。

        在自定义的类中,我们实现了四种负载均衡策略

        1、getRoundRobinInstance方法是直接复制的RoundRobinLoadBalancer类中的实现;

        2、getRandomInstance方法参考org.springframework.cloud.loadbalancer.core.RandomLoadBalancer类中的实现;

        3、getDevelopmentInstance方法是返回所有服务中和当前机器ip一致的服务,如果没有,则轮训返回;

        4、getGatewayDevelopmentInstance方法是返回所有服务中和网关请求头中ip一致的服务。

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ip.IpUtils;
import com.ruoyi.common.loadbalance.config.LoadBalancerTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 自定义 SpringCloud 负载均衡算法
 * 负载均衡算法的默认实现是 {@link org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer}
 *
 */
@Slf4j
public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final String serviceId;
    private final AtomicInteger position;
    private final LoadBalancerTypeEnum type;
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public CustomSpringCloudLoadBalancer(String serviceId,
                                         LoadBalancerTypeEnum type,
                                         ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this(serviceId, new Random().nextInt(1000), type, serviceInstanceListSupplierProvider);
    }

    public CustomSpringCloudLoadBalancer(String serviceId,
                                         int seedPosition,
                                         LoadBalancerTypeEnum type,
                                         ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this.serviceId = serviceId;
        this.position = new AtomicInteger(seedPosition);
        this.type = type;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

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

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

    private Response<ServiceInstance> getInstanceResponse(Request request, List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }

        if (Objects.equals(type, LoadBalancerTypeEnum.ROUND_ROBIN)){
            return this.getRoundRobinInstance(instances);
        }else if (Objects.equals(type, LoadBalancerTypeEnum.RANDOM)){
            return this.getRandomInstance(instances);
        }else if (Objects.equals(type, LoadBalancerTypeEnum.DEV)){
            return this.getDevelopmentInstance(instances);
        }else if (Objects.equals(type, LoadBalancerTypeEnum.GATEWAY)){
            return this.getGatewayDevelopmentInstance(request, instances);
        }
        return this.getRoundRobinInstance(instances);
    }

    /**
     * 获取网关本机实例
     *
     * @param instances 实例
     * @return {@link Response }<{@link ServiceInstance }>
     * @author : lwq
     * @date : 2022-12-15 14:22:13
     */
    private Response<ServiceInstance> getGatewayDevelopmentInstance(Request request, List<ServiceInstance> instances) {

        //把request转为默认的DefaultRequest,从request中拿到请求的ip信息,再选择ip一样的微服务
        DefaultRequest<RequestDataContext> defaultRequest = Convert.convert(new TypeReference<DefaultRequest<RequestDataContext>>() {}, request);
        RequestDataContext context = defaultRequest.getContext();
        RequestData clientRequest = context.getClientRequest();
        HttpHeaders headers = clientRequest.getHeaders();
        String requestIp = IpUtils.getIpAddressFromHttpHeaders(headers);
        log.debug("客户端请求gateway的ip:{}", requestIp);

        //先取得和本地ip一样的服务,如果没有则按默认来取
        for (ServiceInstance instance : instances) {
            String currentServiceId = instance.getServiceId();
            String host = instance.getHost();
            log.debug("注册服务:{},ip:{}", currentServiceId, host);
            if (StringUtils.isNotEmpty(host) && StringUtils.equals(requestIp, host)) {
                return new DefaultResponse(instance);
            }
        }
        return getRoundRobinInstance(instances);
    }


    /**
     * 获取本机实例
     *
     * @param instances 实例
     * @return {@link Response }<{@link ServiceInstance }>
     * @author : lwq
     * @date : 2022-12-15 14:22:13
     */
    private Response<ServiceInstance> getDevelopmentInstance(List<ServiceInstance> instances) {
        //获取本机ip
        String hostIp = IpUtils.getHostIp();
        log.debug("本机Ip:{}", hostIp);

        //先取得和本地ip一样的服务,如果没有则按默认来取
        for (ServiceInstance instance : instances) {
            String currentServiceId = instance.getServiceId();
            String host = instance.getHost();
            log.debug("注册服务:{},ip:{}", currentServiceId, host);
            if (StringUtils.isNotEmpty(host) && StringUtils.equals(hostIp, host)) {
                return new DefaultResponse(instance);
            }
        }
        return getRoundRobinInstance(instances);
    }

    /**
     * 使用随机算法
     * 参考{link {@link org.springframework.cloud.loadbalancer.core.RandomLoadBalancer}}
     *
     * @param instances 实例
     * @return {@link Response }<{@link ServiceInstance }>
     * @author : lwq
     * @date : 2022-12-15 13:32:11
     */
    private Response<ServiceInstance> getRandomInstance(List<ServiceInstance> instances) {
        int index = ThreadLocalRandom.current().nextInt(instances.size());
        ServiceInstance instance = instances.get(index);
        return new DefaultResponse(instance);
    }

    /**
     * 使用RoundRobin机制获取节点
     *
     * @param instances 实例
     * @return {@link Response }<{@link ServiceInstance }>
     * @author : lwq
     * @date : 2022-12-15 13:28:31
     */
    private Response<ServiceInstance> getRoundRobinInstance(List<ServiceInstance> instances) {
        // 每一次计数器都自动+1,实现轮询的效果
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }

}

        其中的工具类如下:

import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.URLUtil;
import org.springframework.http.HttpHeaders;

/**
 * 获取IP方法
 */
public class IpUtils{

    /**
     * 获取IP地址
     *
     * @return 本地IP地址
     */
    public static String getHostIp(){
        try{
            return InetAddress.getLocalHost().getHostAddress();
        }catch (UnknownHostException e){
        }
        return "127.0.0.1";
    }

    /**
     * 获取客户端IP
     *
     * @param httpHeaders 请求头
     * @return IP地址
     */
    public static String getIpAddressFromHttpHeaders(HttpHeaders httpHeaders){
        if (httpHeaders == null){
            return "unknown";
        }
        //前端请求自定义请求头,转发到哪个服务
        List<String> ipList = httpHeaders.get("forward-to");
        String ip = CollectionUtil.get(ipList, 0);
        //请求自带的请求头
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ipList = httpHeaders.get("x-forwarded-for");
            ip = CollectionUtil.get(ipList, 0);
        }
        //从referer获取请求的ip地址
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            //从referer中获取请求的ip地址
            List<String> refererList = httpHeaders.get("referer");
            String referer = CollectionUtil.get(refererList, 0);
            URL url = URLUtil.url(referer);
            if (Objects.nonNull(url)){
                ip = url.getHost();
            }else {
                ip = "unknown";
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ipList = httpHeaders.get("Proxy-Client-IP");
            ip = CollectionUtil.get(ipList, 0);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ipList = httpHeaders.get("X-Forwarded-For");
            ip = CollectionUtil.get(ipList, 0);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ipList = httpHeaders.get("WL-Proxy-Client-IP");
            ip = CollectionUtil.get(ipList, 0);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ipList = httpHeaders.get("X-Real-IP");
            ip = CollectionUtil.get(ipList, 0);
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
    }
}

        getIpAddressFromHttpHeaders方法中,是从请求头总拿到了自定义的请求头forward-to,要想实现此功能,就需要前端发送请求的时候携带这个请求头,例如

        在若依的request.js中可以这么写: config.headers['forward-to'] = '192.168.0.145'

三、配置负载均衡策略

       默认的配置在这里:

org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientConfiguration

        我们参考这个配置,实现自己的配置。启用上面写好的配置类LoadBalanceProperties,然后传到自定义的负载均衡策略类CustomSpringCloudLoadBalancer,其他的复制就可以。

import com.ruoyi.common.loadbalance.core.CustomSpringCloudLoadBalancer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
 * 自定义负载均衡客户端配置
 *
 */
@SuppressWarnings("all")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(LoadBalanceProperties.class)
public class CustomLoadBalanceClientConfiguration {

    @Bean
    @ConditionalOnBean(LoadBalancerClientFactory.class)
    public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(LoadBalanceProperties loadBalanceProperties,
                                                                   Environment environment,
                                                                   LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new CustomSpringCloudLoadBalancer(name, loadBalanceProperties.getType(),
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
    }

}

        然后使用LoadBalancerClients注解加载一下配置

import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;

/**
 * 自定义负载均衡自动配置
 *
 */
@LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
public class CustomLoadBalanceAutoConfiguration {

}

四、使用

        将以上代码独立成一个模块,然后再其他微服务中的pom文件中引入,然后添加对应的配置就可以实现自定义负载均衡了

        1、在微服务中配置如下即可实现调用其他服务时,调用自己本地开发环境的微服务

    spring.cloud.loadbalancer.type=dev

        2、在网关中配置如下即可实现调用固定某个服务

 spring.cloud.loadbalancer.type=gateway

写在最后的话

        最开始只有想法,但是不知道怎么实现,百度也没找到合适的方案。所以就开始看源码,研究了一下,然后照着源码写,测试了一下真的就实现了。所以,多看看源码还是有好处的。

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

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

相关文章

【计算机图形学】裁剪算法(逐边裁剪法 Weiler-Atherton裁剪法)

一 实验目的 编写直线段、多边形裁剪算法熟悉逐边裁剪法、Weiler-Atherton裁剪法的使用 4&#xff1a;用逐边裁剪法实现多边形裁剪&#xff08;代码最上方功能区注明是否处理退化边&#xff09; 无退化实验结果如下图所示&#xff1a; 图形初始化&#xff1a;&#xff08;红色…

GaussDB工作级开发者认证—第五章GaussDB数据库操作与管理

一、数据库对象基本操作 查询数据库&#xff1a; postgres# \l --使用元命令查看数据库 postgres# select * from pg_database; --通过系统表查看数据库 表注意事项: 只有表的所有者有权限执行ALTER TABLE命令&#xff0c;系统管理员默认拥有此权限 不能修改分区表的tables…

creator-assetbundle分包

title: creator-assetbundle分包 categories: Cocos2dx tags: [creator, 分包, assetbundle] date: 2023-04-10 15:55:22 comments: false mathjax: true toc: true creator-assetbundle分包 前篇 Asset Bundle 介绍 - https://docs.cocos.com/creator/manual/zh/asset/bundle…

国家出手管人工智能AI了

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 全球都在封杀AI&#xff0c;国家也出手了&#xff0c;人工智能AI的强监管来了!这次反应速度算是很快了。国家出手&#xff0c;AI必须管。 国家网信办拟针对生成式人工智能服务出台管理办法&#…

【万象奥科】RZ/G2UL网关内存压力测试

测试目的 内存压力测试的目的是测试系统内存的稳定性和可靠性&#xff0c;以便确定系统是否能够在各种负载情况下正常运行。其主要目的有&#xff1a; 测试内存的正确性&#xff1a;通过模拟各种内存负载情况&#xff0c;例如写入随机数据、重复写入相同数据、使用指定的模式…

原型模式解读

目录 模式引进问题 原型模式 原型模式原理结构图-uml 类图 原型模式解决克隆羊问题的应用实例 深拷贝和浅拷贝 浅拷贝的介绍 深拷贝基本介绍 重写 clone 方法来实现深拷贝 通过对象的序列化实现实现深拷贝&#xff08;推荐&#xff09; 原型模式的注意事项和细节 模式…

阿里云linux云服务器 安装指定版本node.js

我们在实例管理中找到自己的服务器 然后点击右侧的 远程连接 接着点击理解登录 进入命令窗口 我们在这上面输入 curl -h阿里云的服务器都还是最好会有 curl的 然后 我们输入 sudo curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash下把nv…

带头部表头和侧边表头样式的布局

原型设计的页面中的表格除了头部还有左侧侧边是表头的一个表格&#xff0c;查阅组件文档&#xff0c;发现表格table没有两个表头的布局。 思路&#xff1a; 1、使用div自己布局 2、使用表格table&#xff0c;修改其第一列样式背景&#xff0c;展示除了的样子看着像是有多个表头…

突发!ChatGPT疯了!

‍数据智能产业创新服务媒体——聚焦数智 改变商业今天&#xff0c;笔者正常登录ChatGPT&#xff0c;试图调戏一下他。但是&#xff0c;突然震惊的发现&#xff0c;ChatGPT居然疯了。之所以说他是疯了&#xff0c;而不是崩溃了&#xff0c;是因为他还能回复我&#xff0c;但回…

【计算机网络】1、概念、TCP | UDP | 本地 socket 编程

文章目录一、网络基本概念1.1 端口&#xff08;port&#xff09;1.2 IP 地址 网络地址&#xff08;network&#xff09;和 主机&#xff08;host&#xff09;1.3 子网&#xff08;subnet&#xff09;1.4 子网掩码&#xff08;netmask&#xff09;1.5 保留网段1.6 CIDR 表述形式…

【计算机网络复习】第三章 传输层 3

拥塞 网络边缘主机发送到网络中的负载超出了网络的承载能力&#xff0c;即导致拥塞 u 网络拥塞的特征 时延增大 — 由于在路由器缓存中排队而导致 数据包丢失 — 由于路由器的缓存溢出而导致 u 随着网络负载的增加 传输时延增大 吞吐量下降 拥塞控制&#…

ptuning v2 的 chatglm垂直领域训练记录

thunlp chatglm 6B是一款基于海量高质量中英文语料训练的面向文本对话场景的语言模型。 THUDM/ChatGLM-6B: ChatGLM-6B&#xff1a;开源双语对话语言模型 | An Open Bilingual Dialogue Language Model (github.com) 国内的一位大佬把chatglm ptuning 的训练改成了多层多卡并…

Spring 事务

目录 一、事务简介 二、在Spring中实现事务 编程式事务 声明式事务 三、事务的传播机制 一、事务简介 事务&#xff1a;就是将一组操作封装成为一个整体执行单元&#xff0c;要么全部执行&#xff0c;要么都不执行。 假如事务执行了一半发生了错误就会对已经执行的部分进…

Linux嵌入式学习之Ubuntu入门(三)用户、用户组及文件权限

系列文章目录 一、Linux嵌入式学习之Ubuntu入门&#xff08;一&#xff09;基本命令、软件安装及文件结构 二、Linux嵌入式学习之Ubuntu入门&#xff08;二&#xff09;磁盘文件介绍及分区、格式化等 文章目录系列文章目录用户与用户组创建用户和用户组图形化创建命令创建文件…

【人工智能】— 逻辑Agent、一般逻辑、Entailment 蕴涵、命题逻辑、前向链接、反向链接、Resolution归结

【人工智能】— 逻辑Agent、逻辑智能体Knowledge bases一个简单的基于知识的智能体一般逻辑Entailment 蕴涵Models模型蕴涵与推理命题逻辑逻辑连接词枚举推理有效性可满足性推导和证明霍恩子句Forward chaining 前向链接Proof of completeness&#xff08;完备性&#xff09;Ba…

QT学习开发笔记(项目实战之智能家居物联网项目1 )

智能家居物联网项目 本章介绍使用 Qt 开发智能家居中的一个物联应用。简单直白的说就是通过云服务器远程控 制设备&#xff08;与设备通信等&#xff09;。本章可以直接做毕设&#xff0c;是毕设物联网项目的一大福音&#xff01;本章将实现远 程点亮开发板 LED 作为一个项目实…

springboot从2.1.3升级到2.3.5后控制台自动输出http请求日志RequestResponseBodyMethodProcessor

springboot从2.1.3升级到2.3.5后控制台自动输出http请求日志RequestResponseBodyMethodProcessor和RequestMappingHandlerMapping推荐使用第二个方案简单 明了 方便 快捷方案一第一步定义TurboFilter第二步配置logback方案二 直接配置logback的配置XML推荐使用第二个方案简单 明…

Insomnia 简单使用方法

文章目录1. 新建工程2. 新建若干文件夹3. 设置环境变量4. 授权以及进行请求的链式调用 (chaining requests)4. 1 解决办法 14. 2 解决办法 2Insomnia 同 Postman&#xff0c; 用于测试后端 endpoint&#xff0c;很容易使用。使用步骤如下&#xff1a;1. 新建工程 2. 新建若干文…

字节跳动软件测试面试过了,起薪20k

普通二本计算机专业毕业&#xff0c;从毕业后&#xff0c;第一份接触测试的工作是在一家通讯小公司&#xff0c;大部分接触的工作是以功能测试为主&#xff0c;一直都是几千块钱工资&#xff0c;还一度被派出差&#xff0c;以及兼职各种产品、运维、运营的活&#xff0c;感觉自…

四十五、docker之nginx手动部署前端项目

nginx的作用&#xff1a; 一、静态文件服务器和反向代理django服务 django框架中的静态文件服务只在开发调试时提供服务&#xff0c;当以生产模式运行时需要将静态文件部署到静态文件服务器上。 1. 收集django项目中的静态文件 在配置文件中配置STATIC_ROOT 我们在我的项目…