深入了解ribbon源码

news2024/11/26 19:48:05

ribbon源码解析

自动装配

依赖

<!--添加ribbon的依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

spring-cloud-starter-netflix-ribbon
看到starter组件我们可以去依赖包的spring.factories文件下看看
在这里插入图片描述
RibbonAutoConfiguration
在这里插入图片描述

@AutoConfigurationBefore
表明当前配置类在某一个配置类加载前加载。
说明RibbonAutoConfiguration要在LoadBalancerAutoConfiguration之前加载

看下为我们注入的类

@Bean
	public SpringClientFactory springClientFactory() {
		SpringClientFactory factory = new SpringClientFactory();
		factory.setConfigurations(this.configurations);
		return factory;
	}

new SpringClientFactory()

	public SpringClientFactory() {
		super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
	}

在这里插入图片描述

我们看下父类:NamedContextFactory

	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
			String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

把RibbonClientConfiguration当作参数传入父类

我们看下RibbonClientConfiguration是怎么解析成bean定义的
在这里插入图片描述
RibbonApplicationContextInitializer实现了 ApplicationListener 所以
springboot启动的时候会去调用onApplicationEvent 方法

protected void initialize() {
		if (clientNames != null) {
			for (String clientName : clientNames) {
				this.springClientFactory.getContext(clientName);
			}
		}
	}

	@Override
	public void onApplicationEvent(ApplicationReadyEvent event) {
		initialize();
	}

我们看下getContext方法
org.springframework.cloud.context.named.NamedContextFactory#getContext

在这里插入图片描述
getContext方法调用了createContext方法
org.springframework.cloud.context.named.NamedContextFactory#createContext
在这里插入图片描述
这个defaultConfigType就是我们传入的RibbonClientConfiguration

我们看下register方法

public void register(Class<?>... annotatedClasses) {
		Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
		this.reader.register(annotatedClasses);
	}

org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register方法

public void register(Class<?>... annotatedClasses) {
		for (Class<?> annotatedClass : annotatedClasses) {
			registerBean(annotatedClass);
		}
	}

看到这里不就是我们spring解析配置类的方法,就是把配置类解析成bean定义

为什么RibbonClientConfiguration这个类要说这么多,因为这个类是为我们注入了许多核心的类

LoadBalancerAutoConfiguration

在这里插入图片描述

可以看到LoadBalancerAutoConfiguration生效的条件是容器中必须有LoadBalancerClient类
这个类在RibbonAutoConfiguration时会我们注入

在这里插入图片描述

收集具有@LoadBalanced表示的RestTemplate
这一步时spring帮我们做的
我们可以看下@LoadBalance注解

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

可以看到@LoadBalance是一个复合注解,底层还是用到了@Qualifier注解,
所以才可以收集到RestTemplate集合

RestTemplate
在这里插入图片描述
RestTemplate继承InterceptingHttpAccessor所以具有获取和设置拦截器的能力

在这里插入图片描述

loadBalancedRestTemplateInitializer

loadBalancedRestTemplateInitializer() 初始化一些东西,里面有个内部类,拿到当前的RestTemplate 进行遍历,遍历customizers专门用来定制RestTemplate组件,用每个customizers来定制每个RestTemplate
在这个方法中会为所有被@LoadBalanced注解标识的 RestTemplate添加ribbon的自定义拦截器LoadBalancerInterceptor

@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}

在restTemplateCustomizer方法里面会为每个放进来的RestTemplate 定制一个ClientHttpRequestInterceptor 拦截器,实现拦截器的是他的子类LoadBalancerInterceptor

创建Ribbon自定义拦截器LoadBalancerInterceptor

	@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

添加拦截器具体方法。
首先获取当前拦截器集合(List),然后将loadBalancerInterceptor添加到当前集合中,最后将新的集合放回到restTemplate中。

	@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

LoadBalancerInterceptor
当用resttemplate调用某个api的时候会进入到intercept方法
org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor#intercept

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		final URI originalUri = request.getURI();
		String serviceName = originalUri.getHost();
		Assert.state(serviceName != null,
				"Request URI does not contain a valid hostname: " + originalUri);
				//当前loadBalancer为RibbonLoadBalancerClient
		return this.loadBalancer.execute(serviceName,
				this.requestFactory.createRequest(request, body, execution));
	}

发起请求时ribbon会用LoadBalancerInterceptor这个拦截器进行拦截。在该拦截器中会调用LoadBalancerClient.execute()方法
具体执行execute()由子类RibbonLoadBlancerClient 执行

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#execute(j

@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
			throws IOException {
		return execute(serviceId, request, null);
	}

	
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
			throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server,
				isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}
	

ILoadBalancer
就是通过RibbonClientConfiguration配置类注入的

@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
			ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
			IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}

具体返回ZoneAwareLoadBalancer

ZoneAwareLoadBalancer
在这里插入图片描述

   public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }

看下父类DynamicServerListLoadBalancer

 public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
                                         ServerList<T> serverList, ServerListFilter<T> filter,
                                         ServerListUpdater serverListUpdater) {
        super(clientConfig, rule, ping);
        this.serverListImpl = serverList;
        this.filter = filter;
        this.serverListUpdater = serverListUpdater;
        if (filter instanceof AbstractServerListFilter) {
            ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
        }
        restOfInit(clientConfig);
    }

DynamicServerListLoadBalancer的父类BaseLoadBalancer

  public BaseLoadBalancer(IClientConfig config, IRule rule, IPing ping) {
        initWithConfig(config, rule, ping, createLoadBalancerStatsFromConfig(config));
    }

看下initWithConfig方法
在这里插入图片描述

com.netflix.loadbalancer.BaseLoadBalancer#setPingInterval

public void setPingInterval(int pingIntervalSeconds) {
        if (pingIntervalSeconds < 1) {
            return;
        }

        this.pingIntervalSeconds = pingIntervalSeconds;
        if (logger.isDebugEnabled()) {
            logger.debug("LoadBalancer [{}]:  pingIntervalSeconds set to {}",
        	    name, this.pingIntervalSeconds);
        }
        setupPingTask(); // since ping data changed
    }
    
       void setupPingTask() {
        if (canSkipPing()) {
            return;
        }
        if (lbTimer != null) {
            lbTimer.cancel();
        }
        lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
                true);
                //启动一个任务调度,每隔10秒 ping一次服务实例,检查状态,
        lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
        forceQuickPing();
    }

这一步主要就是启动一个任务调度,每隔10秒 ping一次服务实例,检查状态

restOfInit
在这里插入图片描述

  void restOfInit(IClientConfig clientConfig) {
        boolean primeConnection = this.isEnablePrimingConnections();
        // turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
        this.setEnablePrimingConnections(false);
        enableAndInitLearnNewServersFeature();

        updateListOfServers();
        if (primeConnection && this.getPrimeConnections() != null) {
            this.getPrimeConnections()
                    .primeConnections(getReachableServers());
        }
        this.setEnablePrimingConnections(primeConnection);
        LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
    }



  public void enableAndInitLearnNewServersFeature() {
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);
    }

enableAndInitLearnNewServersFeature方法

在这里插入图片描述

enableAndInitLearnNewServersFeature():定时更新
在PollingServerListUpdater中,创建了一个Runnable线程,里面就是执行UpdateAction的行为,在延迟一定的时间过后,每隔一定的时间就执行一下那个Runnable线程,就会执行UpdateAction中的操作来刷新注册表,
默认的是1秒钟过后,会第一次执行那个Runnable线程,
//以后是每隔30秒执行一下那个Runnable线程,】t刷新注册表到自己的ribbon的LoadBalancer中来

看下doupdate方法
在这里插入图片描述
updateListOfServers方法
在这里插入图片描述
我们这里注册中心以nacos为例子
com.alibaba.cloud.nacos.ribbon.NacosServerList#getUpdatedListOfServers

  public List<NacosServer> getUpdatedListOfServers() {
        return this.getServers();
    }

    private List<NacosServer> getServers() {
        try {
            String group = this.discoveryProperties.getGroup();
            List<Instance> instances = this.discoveryProperties.namingServiceInstance().selectInstances(this.serviceId, group, true);
            return this.instancesToServerList(instances);
        } catch (Exception var3) {
            throw new IllegalStateException("Can not get service instances from nacos, serviceId=" + this.serviceId, var3);
        }
    }

看下selectInstances 方法
com.alibaba.nacos.client.naming.NacosNamingService#selectInstances(java.lang.String, java.lang.String, boolean)

public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
        return this.selectInstances(serviceName, groupName, healthy, true);
    }



   public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy, boolean subscribe) throws NacosException {
        ServiceInfo serviceInfo;
        if (subscribe) {
            serviceInfo = this.hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        } else {
            serviceInfo = this.hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName), StringUtils.join(clusters, ","));
        }

        return this.selectInstances(serviceInfo, healthy);
    }

重点来了getServiceInfo方法

 public ServiceInfo getServiceInfo(String serviceName, String clusters) {
        LogUtils.NAMING_LOGGER.debug("failover-mode: " + this.failoverReactor.isFailoverSwitch());
        String key = ServiceInfo.getKey(serviceName, clusters);
        if (this.failoverReactor.isFailoverSwitch()) {
            return this.failoverReactor.getService(key);
        } else {
        	//从缓存中获取实例信息
            ServiceInfo serviceObj = this.getServiceInfo0(serviceName, clusters);
            //如果不存在则去nacos服务端调用
            if (null == serviceObj) {
                serviceObj = new ServiceInfo(serviceName, clusters);
                this.serviceInfoMap.put(serviceObj.getKey(), serviceObj);
                this.updatingMap.put(serviceName, new Object());
                //nacos服务端调用
                this.updateServiceNow(serviceName, clusters);
                this.updatingMap.remove(serviceName);
            } else if (this.updatingMap.containsKey(serviceName)) {
                synchronized(serviceObj) {
                    try {
                        serviceObj.wait(5000L);
                    } catch (InterruptedException var8) {
                        LogUtils.NAMING_LOGGER.error("[getServiceInfo] serviceName:" + serviceName + ", clusters:" + clusters, var8);
                    }
                }
            }
			
			//异步更新内存注册列表
            this.scheduleUpdateIfAbsent(serviceName, clusters);
            return (ServiceInfo)this.serviceInfoMap.get(serviceObj.getKey());
        }
    }

到这里定时任务基本上完了

然后继续走下边的流程 回到最初拦截的地方
在这里插入图片描述
//通过负载均衡算法选取一个实例后续进行调用

org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		// Use 'default' on a null hint, or just pass it on?
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

在这里插入图片描述
com.netflix.loadbalancer.BaseLoadBalancer#chooseServer

public Server chooseServer(Object key) {
        if (counter == null) {
            counter = createCounter();
        }
        counter.increment();
        //rule就是Irule
        if (rule == null) {
            return null;
        } else {
            try {
                return rule.choose(key);
            } catch (Exception e) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
                return null;
            }
        }
    }

通过Irue进行服务实力的选择

我们看下Irule实现类NacosRule

com.alibaba.cloud.nacos.ribbon.NacosRule#choose

 public Server choose(Object key) {
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer)this.getLoadBalancer();
            String name = loadBalancer.getName();
            NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
            //这个和定时任务调用的一样
            List<Instance> instances = namingService.selectInstances(name, true);
            if (CollectionUtils.isEmpty(instances)) {
                LOGGER.warn("no instance in service {}", name);
                return null;
            } else {
                List<Instance> instancesToChoose = instances;
                if (StringUtils.isNotBlank(clusterName)) {
                    List<Instance> sameClusterInstances = (List)instances.stream().filter((instancex) -> {
                        return Objects.equals(clusterName, instancex.getClusterName());
                    }).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                        instancesToChoose = sameClusterInstances;
                    } else {
                        LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{name, clusterName, instances});
                    }
                }

				//根据负载均衡策略选取一个
                Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
                return new NacosServer(instance);
            }
        } catch (Exception var9) {
            LOGGER.warn("NacosRule error", var9);
            return null;
        }
    }

这个方法其实就是从本地环从中获取服务实例,如果获取到就进行返回
获取不到则向nacos服务端发起远程调用获取服务实例集合
最后通过负载均衡算法选取一个实例节点后续进行调用

最后在拦截器方法中进行ip和端口替换并发起远程http调用

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

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

相关文章

skywalking agent使用kafka数据传输

安装Zookeeper 下载相应版本的zookeeper 解压文件 tar -vxzf apache-zookeeper-3.8.0-bin.tar.gz进入conf目录下&#xff0c;复制zoo_sample.cfg文件&#xff0c;这个是官方提供的配置样例&#xff0c;我们修改复制的文件名称未zoo.cfg。 进入bin目录&#xff0c;启动zookeep…

医院三级质控信息化支撑工具之一

建立组织,完善管理制度 新华社北京3月23日电 近日&#xff0c;中共中央办公厅、国务院办公厅印发了《关于进一步完善医疗卫生服务体系的意见》&#xff0c;并发出通知&#xff0c;要求各地区各部门结合实际认真贯彻落实。(原文地址:中共中央办公厅 国务院办公厅印发《关于进一步…

【读论文】Seeing Beyond the Brain:MinD-Vis

Seeing Beyond the Brain: Conditional Diffusion Model with Sparse Masked Modeling for Vision Decoding CVPR 2023 基于稀疏掩膜的条件扩散模型视觉解码 背景 了解大脑活动并恢复编码信息是认知神经科学的关键目标&#xff0c;但由于脑信号的复杂潜在表征以及相关数据&a…

Nginx 配置 安全认证 反向代理 HDFS web 页面

Nginx 配置安全认证 反向代理 HDFS web 页面 这样做的目的是&#xff1a;相对安全一些&#xff0c;之前都是直接“裸奔”经常被攻击很讨厌 文章目录 1、下载 NGINX2、解压 NGINX3、编译 NGINX4、编译后&#xff0c;确认 NGINX 安装目录5、配置 NGINX 为系统服务6、安装 密码生…

企业直播时摄像机拍摄参考与取景框裁切参考(组图)

看了《2023中国企业直播应用标准》之后&#xff0c;内容摘要&#xff1a; 企业品质直播标准要素&#xff1a;直播视觉、直播运营、演播厅管理、直播合规以及主播能力。 直播视觉&#xff1a;影响用户的品牌的认知度和好感度直播视觉是直播的第一印象&#xff0c;也是传达企业…

热图 -- pheatmap or ggplot2

文章目录 brief数据准备 pheatmap实例最朴素的方式数据缩放取消聚类更改每个小方格的大小聚类以及聚类方式和参数修改热图呈现的颜色修改legend ggplot2实例ggplot2实例变式添加 group bar做成dotplot pheatmap 多图组合问题 brief 这里主要记录了pheatmap 以及 ggplot2实现热…

shell脚本5数组

文章目录 数组1 数组定义方法2 获取数组长度2.1 读取数组值2.2 数组切片2.3 数组替换2.4 数组删除2.5 追加数组元素 3 实验3.1 冒泡法3.2 直接选择法3.3 反排序法 数组 1 数组定义方法 数组名(value0 valuel value2 …) 数组名( [0]value [1]value [2]value …) 列表名“val…

秒杀的异步优化

在jvm以外的服务&#xff0c;不受jvm内存的限制 不仅仅做数据的存储&#xff0c;还保证了数据的安全&#xff0c;持久化 1.基于List结构模拟消息队列 优点&#xff1a; 利用Redis存储&#xff0c;不受JVM内存限制 基于Redis的持久化机制&#xff0c;数据安全性有保证 可以满…

MQTT搭建笔记

提示&#xff1a;记录mqtt服务搭建及访问教程 文章目录 前言一、MQTT是什么&#xff1f;二、使用步骤1.MQTT服务器搭建2.MQTT集成 总结 前言 一直想了解下mqtt&#xff0c;本人又懒&#xff0c;被动型学习&#xff0c;刚好项目需要&#xff0c;此篇记录下MQTT搭建过程及心得体…

MS5147/MS5148模数转换器可pin对pin兼容ADS1247/ADS1248

‎ADS1246、ADS1247 和 ADS1248 是高度集成的精密 24 位模数转换器 &#xff08;ADC&#xff09;。这些器件具有一个板载、低噪声、可编程增益放大器 &#xff08;PGA&#xff09;、一个带有单周期建立数字滤波器的精密三角积分 &#xff08;ΔΣ&#xff09; ADC 和一个内部振…

【原创】H7-TOOL的CANFD Trace操作说明,不需要目标板额外做任何代码, 支持在线和脱机玩法(2023-05-15)

【原创】H7-TOOL的CANFD Trace操作说明&#xff0c;不需要目标板额外做任何代码, 支持在线和脱机玩法&#xff08;2023-05-15&#xff09; 【当前支持功能】 1、LUA小程序控制&#xff0c;使用灵活。 2、采用SWD接口直接访问目标板芯片的CANFD外设寄存器和CANFD RAM区实现&…

【Linux】自动化构建工具--make/Makefile调试器--gdb的使用

目录 一、自动化构建工具--make/Makefile使用原理项目清理 二、调试器--gdb的使用使用 一、自动化构建工具–make/Makefile 使用 一个工程的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的规则来指定&#xff0c;哪…

聊个简单的话题:如何分析性能需求?

目录 前言 需求评估分析 性能测试方案 前言 前几天还在北京出差时候&#xff0c;微信群有个同学问了一个问题&#xff0c;为什么800并发压测&#xff0c;服务器还没有报错&#xff1f;当时群里其他同学提了很多观点&#xff0c;比如&#xff1a; 并发不够&#xff0c;加并…

某医院Pad网络故障分析

分析背景 某医院为了加强信息安全管理&#xff0c;防止病人隐私信息泄露&#xff0c;采用部署“零信任”安全架构设计理念的企业移动安全支撑平台方案。 但在部署前期测试时&#xff0c;遇到了严重的性能问题。 在本次测试环境中&#xff0c;通过PAD访问患者转运业务&#x…

【深入浅出 Yarn 架构与实现】6-4 Container 生命周期源码分析

本文将深入探讨 AM 向 RM 申请并获得 Container 资源后&#xff0c;在 NM 节点上如何启动和清理 Container。将详细分析整个过程的源码实现。 一、Container 生命周期介绍 Container 的启动由 ApplicationMaster 通过调用 RPC 函数 ContainerManagementProtocol#startContain…

SWLLOE

前面讲过了workman&#xff0c;现在我们再了解另外一个swoole&#xff0c;首先我们要了解swoole是个啥&#xff1f;swoole其实是一个面向生产环境的 PHP 异步网络通信引擎&#xff0c;PHP Swoole 作为网络通信框架可以使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix…

Edge浏览器使用ChatGPT,新手这里看(免费快捷)

前言&#xff1a; ChatGPT受到越来越多人的关注&#xff0c;ChatGPT好不好用&#xff0c;我觉得仁者见仁、智者见智吧&#xff0c;不过首先得先使用感受一下才好回答。多数人都想体验/使用ChatGPT一波&#xff0c;但目前付费和各种渠道满天飞&#xff0c;让人很苦恼&#xff0c…

越来越多企业出现网络安全问题,是什么原因导致的?

近年来网络安全问题层出不穷&#xff0c;信息泄露、网络钓鱼、黑客攻击等问题频繁发生。 尽管有证据表明在一些全球知名的企业组织中存在价值数十亿美元的网络安全漏洞&#xff0c;但企业仍然没有认真对待网络安全。大公司在寻找各种理由来减少其网络安全预算&#xff0c;从而…

操作系统学习笔记(二)

目录 你如何理解“临界”这个词&#xff1f; 那你如何理解在计算机领域下的“临界”这个词呢&#xff1f; 如何理解计算机领域中的“同步”这个词呢&#xff1f; 你如何理解critical这个单词&#xff1f; 单标志法&#xff1a; 双标志先检查法 双标志后检查法&#xff0…

Spring Cloud Gateway路由到Amazon S3签名失败处理

Spring Cloud Gateway路由到Amazon S3签名失败处理 背景 最近在预研统一存储网关&#xff0c;想到就是使用Spring Cloud Gateway作为网关的入口&#xff0c;再反向代理到S3对象存储服务器。 软件版本 网关&#xff1a;Spring Cloud Gateway 3.1.2 s3对象存储&#xff1a;m…