Spring Cloud LoadBalancer 入门与实战

一、什么是 LoadBalancer?

LoadBalancer(负载均衡器) 是一种网络设备或软件机制,用于分发传入的网络流量负载(请求)到多个后端目标服务器上,从而实现系统资源的均衡利用和提高系统的可用性和新能。

1.1 负载均衡分类

负载均衡分为服务器端负载均衡和客户端负载均衡

1. 服务器端负载均衡指的是存放在服务器端的负载均衡器,例如 Nginx、HAProxy、F5 等。

2. 客户端负载均衡指的是嵌套在客户端的负载均衡器,例如 Ribbon、Spring Cloud LoadBalancer。

1.2 常见的负载均衡策略

  1. 询轮:按照顺序将每个新的请求分发给后端服务器,依次循环。这是一种最简单的负载均衡策略,适用于后端服务器的性能相近,且每个请求的处理时间大致相同的情况。

  2. 随机选择:随机选择一个后端服务器来处理每个新的请求。这种策略适用于后端服务器性能相似,且每个请求的处理时间相近的情况,但不保证请求的分发是均匀的。

  3. 最少连接:最少连接策略将请求分发给当前连接数最少的后端服务器。这可以确保负载均衡在后端服务器的连接负载上均衡。但需要维护连接计数。

  4. IP 哈希:IP 哈希策略使用客户端的 IP 地址来计算哈希值,然后将请求发送到与哈希值对应的后端服务器。这种策略可用于确保来自同一客户端的请求都被发送到同一台后端服务器,适用于需要会话保持的情况。

  5. 加权轮询:加权轮询给每个后端服务器分配一个权重值,然后按照权重值比例来分发请求。这可以用来处理后端服务器性能不均衡的情况,将更多的请求发给性能更高的服务器。

  6. 加权随机选择:加权随机选择与加权轮询类似,但是按照权重值来随机选择后端服务器。这也可以用来处理后端服务器性能不均衡的情况,但是分发更随机。

  7. 最短响应时间 :最短响应时间策略会测量每个后端服务器的响应时间,并将请求发送到响应时间最短的服务器。这中策略可以确保客户端获得最快的响应,适用于要求低延迟的应用。

二、默认负载均衡策略

Spring Cloud LoadBalancer 负载均衡策略默认的是轮询,这一点可以通过 Spring Cloud LoadBalancer 的配置类 LoadBalancerClientConfiguration 中发现,它的部分源码如下:

@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;public LoadBalancerClientConfiguration() {}@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}//...
}

 RoundRobinLoadBalancer 核心实现源码:

private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {if (instances.isEmpty()) {if (log.isWarnEnabled()) {log.warn("No servers available for service: " + this.serviceId);}return new EmptyResponse();} else if (instances.size() == 1) {return new DefaultResponse((ServiceInstance)instances.get(0));} else {//当有多个实例时,& Integer.MAX_VALUE 摒弃负数,确保下标为正数int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;//取模进行轮询ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());return new DefaultResponse(instance);}}

三、随机负载均衡策略

代码实现

设置局部负载均衡器:

//balancer-service:注册实例名称
//RandomLoadBalanceConfig.class 自定义负载均衡器类名
@LoadBalancerClient(name = "balancer-service",configuration = RandomLoadBalanceConfig.class)
public class RandomLoadBalanceConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}
}

设置全局负载均衡器:

@SpringBootApplication
@EnableFeignClients
@LoadBalancerClients(defaultConfiguration= RandomLoadBalanceConfig.class)
public class ConsumerApplication {public static void main(String[] args) {SpringApplication.run(ConsumerApplication.class, args);}}

四、设置Nacos权重负载均衡器

Nacos 中支持两种负载均衡器,一种是权重负载均衡器,另一种是第三方 CMDB(地域就近访问)标签负载均衡器,我们可以将 Spring Cloud Loadbalancer 直接配置为 Nacos 的负载均衡器,它默认就是权重负载均衡策略。

它的配置有以下两步:

1. 创建 Nacos 负载均衡器

@LoadBalancerClient(name = "balancer-service",configuration = NacosLoadBalanceConfig.class)
public class NacosLoadBalanceConfig {@Resourceprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return new NacosLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name,nacosDiscoveryProperties);}
}

五、创建自定义负载均衡器 

5.1 创建自定义负载均衡器

自定义负载均衡器只需要参考官方负载均衡器写就可

public class CustomizeLoadBalance implements ReactorServiceInstanceLoadBalancer {private static final Logger log = LoggerFactory.getLogger(NacosLoadBalancer.class);private final String serviceId;private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;private final NacosDiscoveryProperties nacosDiscoveryProperties;private static final String IPV4_REGEX = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}";private static final String IPV6_KEY = "IPv6";public static String ipv6;@Autowiredprivate InetIPv6Utils inetIPv6Utils;@PostConstructpublic void init() {String ip = this.nacosDiscoveryProperties.getIp();if (StringUtils.isNotEmpty(ip)) {ipv6 = Pattern.matches("((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}", ip) ? (String)this.nacosDiscoveryProperties.getMetadata().get("IPv6") : ip;} else {ipv6 = this.inetIPv6Utils.findIPv6Address();}}private List<ServiceInstance> filterInstanceByIpType(List<ServiceInstance> instances) {if (StringUtils.isNotEmpty(ipv6)) {List<ServiceInstance> ipv6InstanceList = new ArrayList();Iterator var3 = instances.iterator();while(var3.hasNext()) {ServiceInstance instance = (ServiceInstance)var3.next();if (Pattern.matches("((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}", instance.getHost())) {if (StringUtils.isNotEmpty((CharSequence)instance.getMetadata().get("IPv6"))) {ipv6InstanceList.add(instance);}} else {ipv6InstanceList.add(instance);}}if (ipv6InstanceList.size() == 0) {return (List)instances.stream().filter((instancex) -> {return Pattern.matches("((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}", instancex.getHost());}).collect(Collectors.toList());} else {return ipv6InstanceList;}} else {return (List)instances.stream().filter((instancex) -> {return Pattern.matches("((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}", instancex.getHost());}).collect(Collectors.toList());}}public CustomizeLoadBalance(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.nacosDiscoveryProperties = nacosDiscoveryProperties;}public Mono<Response<ServiceInstance>> choose(Request request) {ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier)this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);return supplier.get(request).next().map(this::getInstanceResponse);}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances) {if (serviceInstances.isEmpty()) {log.warn("No servers available for service: " + this.serviceId);return new EmptyResponse();} else {try {String clusterName = this.nacosDiscoveryProperties.getClusterName();List<ServiceInstance> instancesToChoose = serviceInstances;if (StringUtils.isNotBlank(clusterName)) {List<ServiceInstance> sameClusterInstances = (List)serviceInstances.stream().filter((serviceInstance) -> {String cluster = (String)serviceInstance.getMetadata().get("nacos.cluster");return StringUtils.equals(cluster, clusterName);}).collect(Collectors.toList());if (!CollectionUtils.isEmpty(sameClusterInstances)) {instancesToChoose = sameClusterInstances;}} else {log.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", new Object[]{this.serviceId, clusterName, serviceInstances});}//获取request 对象ServletRequestAttributes attributes= (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request= attributes.getRequest();String ipAddress=request.getRemoteAddr();System.out.println(ipAddress);int hash=ipAddress.hashCode();//自定义负载据衡器策略,通过ip hash后,取模获取下标int index=(hash&Integer.MAX_VALUE)%serviceInstances.size();System.out.println(index);//获取服务实例ServiceInstance instance=serviceInstances.get(index);return new DefaultResponse(instance);} catch (Exception var5) {log.warn("NacosLoadBalancer error", var5);return null;}}}
}

5.2 封装并设置自定义负载均衡器

@LoadBalancerClient(name = "balancer-service" ,configuration = CoustomizeHashLoadBalanceConfig.class)
public class CoustomizeHashLoadBalanceConfig {@Resourceprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Beanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty("loadbalancer.client.name");return  new CustomizeLoadBalance(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name,nacosDiscoveryProperties);}
}

六、缓存

Spring Cloud LoadBalancer 在获取实例时有两种选择:

  1. 即时获取:每次从注册中心得到最新的健康实例,效果好、开销大
  2. 缓存服务列表:每次得到服务列表之后,缓存一段时间,这样既能保证性能,同时也能兼容一定的及时性。

Spring Cloud LoadBalancer 中默认开启了缓存服务列表的功能。

Spring Cloud LoadBalancer 默认缓存的重要特性有两项:

  1. 缓存的过期时间为 35s
  2. 缓存保存个数为 256 个
spring:cloud:loadbalancer:cache:ttl: 10capacity: 128#不开启缓存enabled:false

注意:尽管在不开启缓存对于开发和测试很有用,但其效率远低于将缓存开启,因此建议在生产环境中始终启用缓存

七、执行原理

OpenFeign 底层是通过 HTTP 客户端对象 RestTemplate 实现接口请求的,而负载均衡器的作用只是在请求客户端发送请求之前,得到一个服务的地址给到 RestTemplate 对象,而 Spring Cloud LoadBalancer 的整体类图如下:

通过查看 Spring Cloud LoadBalancer 源码可以发现,@LoadBalancer 注解由 spring-cloud-commons 实现,查看实现逻辑我们发现 spring-cloud-commons 存在自动配置类 LoadBalancerAutoConfiguration,当满足条件时,将自动创建 LoadBalancerInterceptor 并注入到RestTemplate 中,部分源码如下: 

    @Configuration(proxyBeanMethods = false)@Conditional({RetryMissingOrDisabledCondition.class})static class LoadBalancerInterceptorConfig {LoadBalancerInterceptorConfig() {}@Beanpublic LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return (restTemplate) -> {List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);restTemplate.setInterceptors(list);};}}

LoadBalancerInterceptor 又实现了 ClientHttpRequestInterceptor 接口,并实现 Intercept 方法,用于实现负载均衡的拦截处理,实现源码:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {private LoadBalancerClient loadBalancer;private LoadBalancerRequestFactory requestFactory;public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {this.loadBalancer = loadBalancer;this.requestFactory = requestFactory;}public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));}public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {URI originalUri = request.getURI();String serviceName = originalUri.getHost();Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}
}

其中有一个 LoadBalancerClient 负载均衡客户端,用于进行负载均衡逻辑,从服务列表中选择出一个服务进行地址进行调用,默认实现为 BlockingLoadBalancerClient,源码如下:

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {String hint = this.getHint(serviceId);LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter(request, this.buildRequestContext(request, hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onStart(lbRequest);});ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);if (serviceInstance == null) {supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));});throw new IllegalStateException("No instances available for " + serviceId);} else {return this.execute(serviceId, serviceInstance, lbRequest);}}public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {DefaultResponse defaultResponse = new DefaultResponse(serviceInstance);Set<LoadBalancerLifecycle> supportedLifecycleProcessors = this.getSupportedLifecycleProcessors(serviceId);Request lbRequest = request instanceof Request ? (Request)request : new DefaultRequest();supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance));});try {T response = request.apply(serviceInstance);Object clientResponse = this.getClientResponse(response);supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.SUCCESS, lbRequest, defaultResponse, clientResponse));});return response;} catch (IOException var9) {supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.FAILED, var9, lbRequest, defaultResponse));});throw var9;} catch (Exception var10) {supportedLifecycleProcessors.forEach((lifecycle) -> {lifecycle.onComplete(new CompletionContext(Status.FAILED, var10, lbRequest, defaultResponse));});ReflectionUtils.rethrowRuntimeException(var10);return null;}}public ServiceInstance choose(String serviceId) {return this.choose(serviceId, ReactiveLoadBalancer.REQUEST);}//通过不同的负载均衡器选择不同的服务public <T> ServiceInstance choose(String serviceId, Request<T> request) {//获取负载均衡器ReactiveLoadBalancer<ServiceInstance> loadBalancer = this.loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;} else {//根据负载均衡器得到一个请求实例Response<ServiceInstance> loadBalancerResponse = (Response)Mono.from(loadBalancer.choose(request)).block();return loadBalancerResponse == null ? null : (ServiceInstance)loadBalancerResponse.getServer();}}

 小结:OpenFeign 通过拦截器调用 RestTemplate 实现和服务的交互,RestTemplate 通过HttpClient 进行请求,而在交互之前通过调用 Spring Cloud LoadBalancer中的LoadBalancerClient中的choose 方法 ,根据服务 id 和负载均衡策略得到某个服务地址,再进行调用。

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

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

相关文章

Redis 实现高并发库存扣减方案

背景 公司的电商系统下单 操作库存是一个频繁操作&#xff0c;需要高效地扣减库存&#xff0c;把对销售库存的操作抽出来独立设计一个库存中心系统。 功能包括库存的批量添加、获取、下单、支付、回退等的操作。 解决的业务痛点 需要高效不超卖 方案 一、使用msql乐观锁 …

JAVA之开发神器——IntelliJ IDEA的下载与安装

一、IDEA是什么&#xff1f; IEAD是JetBrains公司开发的专用于java开发的一款集成开发环境。由于其功能强大且符合人体工程学&#xff08;就是更懂你&#xff09;的优点&#xff0c;深受java开发人员的喜爱。目前在java开发工具中占比3/4。如果你要走java开发方向&#xff0c;那…

几种不同的方式禁止IP访问网站(PHP、Nginx、Apache设置方法)

1、PHP禁止IP和IP段访问 <?//禁止某个IP$banned_ip array ("127.0.0.1",//"119.6.20.66","192.168.1.4");if ( in_array( getenv("REMOTE_ADDR"), $banned_ip ) ){die ("您的IP禁止访问&#xff01;");}//禁止某个IP段…

FTP与TFTP

1、TFTP&#xff08;简单文件传输协议&#xff09; TFTP是TCP/IP协议族中一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。 基于UDP协议 端口号&#xff1a;69 特点&#xff1a;简单、轻量级、易于实现 传输过程&…

对象存储-MinIO-学习-01-安装部署

目录 一、介绍 二、环境信息 三、下载安装包 1、MinIO官网下载地址 2、选择版本 &#xff08;1&#xff09;MinIO Server &#xff08;2&#xff09;MinIO Client &#xff08;3&#xff09;MinIO SDK 四、MinIO SDK安装步骤 1、安装minio库 2、导入minio库报错&…

知识图谱入门笔记

自学参考&#xff1a; 视频&#xff1a;斯坦福CS520 | 知识图谱 最全知识图谱综述 详解知识图谱的构建全流程 知识图谱构建&#xff08;概念&#xff0c;工具&#xff0c;实例调研&#xff09; 一、基本概念 知识图谱&#xff08;Knowledge graph&#xff09;&#xff1a;由结…

基于单片机的温控光控智能窗帘设计探讨

摘 要&#xff1a; 文章使用的核心原件是 AT89C52 单片机&#xff0c;以此为基础进行模块化的设计&#xff0c;在整个设计中通过加入光检测模块和温度检测模块&#xff0c;从而对室内的温度和光照强度进行检测&#xff0c;然后将检测得到的数据传输给单片机&#xff0c;单片机…

【目标跟踪】CoTracker 环境配置

配置 CoTracker 环境 首先下载 conda&#xff0c;然后安装虚拟环境。 1.创建环境&#xff1a;如果环境不存在&#xff0c;你需要创建一个新的 conda 环境。可以使用以下命令创建名为 cotracker 的环境&#xff1a; conda create -n cotracker python3.x 其中 3.x 是你想要安…

coze搭建工作流和Agent

coze搭建工作流和Agent Agent LLM 记忆感知规划使用工具 LLM是大语言模型&#xff0c;prompt提示词影响LLM的输出质量 描述需求——>背景——>解决思路&#xff0c;提示词文档。 当有明确的需求和实现需求的路径时&#xff0c;可以通过搭建工作流来完成标准化任务为…

JVM内存泄露的ThreadLocal详解

目录 一、为什么要有ThreadLocal 二、ThreadLocal的使用 三、实现解析 实现分析 具体实现 Hash冲突的解决 开放定址法 链地址法 再哈希法 建立公共溢出区 四、引发的内存泄漏分析 内存泄漏的现象 分析 总结 错误使用ThreadLocal导致线程不安全 一、为什么要有Thr…

【JavaEE】 简单认识CPU

&#x1f435;本篇文章将对cpu的相关知识进行讲解 一、认识CPU 下图是简略的冯诺依曼体系结构图 上图中&#xff0c;存储器用来存储数据&#xff0c;注意在存储器中都是以二进制的形式存储数据的&#xff0c;CPU就是中央处理器&#xff0c;其功能主要是进行各种算术运算和各种…

Java版Flink使用指南——分流导出

大纲 新建工程编码Pom.xml自定义无界流分流 测试工程代码 在之前的案例中&#xff0c;我们一直使用的是单个Sink来做数据的输出。实际上&#xff0c;Flink是支持多个输出流的。本文我们就来讲解如何在Flink数据输出时做分流处理。 我们将基于《Java版Flink使用指南——自定义无…

公司内部配置GitLab,通过SSH密钥来实现免密clone、push等操作

公司内部配置GitLab&#xff0c;通过SSH密钥来实现免密clone、push等操作。以下是配置SSH密钥以实现免密更新的步骤&#xff1a; 1.生成SSH密钥 在本地计算机上打开终端或命令提示符。输入以下命令以生成一个新的SSH密钥&#xff1a;ssh-keygen -t rsa -b 4096 -C "your…

自动驾驶事故频发,安全痛点在哪里?

大数据产业创新服务媒体 ——聚焦数据 改变商业 近日&#xff0c;武汉城市留言板上出现了多条关于萝卜快跑的投诉&#xff0c;多名市民反映萝卜快跑出现无故停在马路中间、高架上占最左道低速行驶、转弯卡着不动等情况&#xff0c;导致早晚高峰时段出现拥堵。萝卜快跑是百度 A…

Mac VSCode 突然闪退、崩溃、打不开了

1、 思路历程 VSCode 作为前端常用开发工具&#xff0c;其重要性就不一一描述了。 所以 VSCode 突然打不开了&#xff0c;真的是让我一脸懵逼。 本来以为问题不大&#xff0c;于是 &#xff1a; 1、重启了一下VSCode 2、关机重启了一下电脑&#xff1b; 3、清理了一下缓存&am…

RequestContextHolder多线程获取不到request对象

RequestContextHolder多线程获取不到request对象&#xff0c;调用feign接口时&#xff0c;在Feign中的RequestInterceptor也获取不到HttpServletRequest问题解决方案。 1.RequestContextHolder多线程获取不到request对象 异常信息&#xff0c;报错如下&#xff1a; 2024-07-0…

设计模式8-桥模式

设计模式8-Bridge 桥模式 由来与目的模式定义结构代码推导1. 类和接口的定义2. 平台实现3. 业务抽象4. 使用示例总结1. 类数量过多&#xff0c;复杂度高2. 代码重复3. 不符合单一职责原则4. 缺乏扩展性改进后的设计1. 抽象和实现分离&#xff08;桥接模式&#xff09;2. 抽象类…

24/7/10总结

flex布局 父项常见属性 justify-content:设置主轴上的子元素排列方式 flex-wrap:设置子元素是否换行 align-items:设置侧轴上的子元素的排列方式&#xff08;单行&#xff09; 拉伸要把子盒子里的高度给去掉 如果两个align-items都是center并且主轴是y轴就是这种效果…

【鸿蒙学习笔记】使用动画

官方文档&#xff1a;使用动画 目录标题 属性动画&#xff1a;通用属性发生改变时而产生的属性渐变效果animationanimateTo自定义属性动画 AnimatableExtend 转场动画&#xff1a;是页面或组件的切换动画 , 显示/隐藏 切换时的动画出现/消失转场&#xff1a;实现一个组件出现或…

Allegro纠纷管理:构建和谐交易的桥梁

在Allegro这个欧洲领先的电商平台&#xff0c;每天都有成千上万的交易发生&#xff0c;纠纷可能源于多种原因&#xff0c;包括但不限于商品描述不符、运输损坏、未收到货、退货退款问题等。纠纷留言便成为了连接买卖双方&#xff0c;解决争议的桥梁。在Allegro平台上&#xff0…