Spring Cloud Alibaba-全链路灰度设计

文章目录

    • 灰度发布概念
    • 灰度发布架构
    • Spring Cloud Alibaba技术架构下的灰度发布实现
      • 基础设计
        • HttpHeader设计
      • Spring Cloud Gateway改造
        • Spring Cloud Gateway实现灰度发布过滤器
      • 后端服务自定义Spring MVC请求拦截器
      • OpenFeign改造
      • 自定义Loadbalancer
    • 测试
        • 微服务注册元信息修改
        • 自定义LoadBalancer使用
    • 源码

灰度发布概念

– 摘自百度百科

​ 灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

​ 对比灰度发布,还有蓝绿发布,滚动发布,笔者在工作中用到最多的是滚动发布,滚动发布也并不能平滑的将一部分指定的流量切到新系统上,只能做到切部分流量这部分流量是无法指定的也是随机的,所以基于此笔者就着手做全链路灰度发布相关的研究。蓝绿发布,滚动发布的具体概念自行Google吧。

灰度发布架构

​ 这里我画了一个图,简单描述一下灰度发布,这里有微服务A集群和微服务B集群,每个集群中蓝颜色的是生产实例,绿色就代表发布的灰度版本实例;可以看到网络流量是通过微服务网关为入口流入微服务集群,所以这里微服务网关就负责过滤流量管理灰度规则等,流量经过微服务网关后,可以看到灰度流量就指向了灰度版本,微服务A通过RPC调用微服务B时也通过loadbalancer也将灰度流量发到了微服务B的灰度实例上,这里图中实际缺少了一个点,就是流量由微服务网关和微服务A集群中间也是有个loadbalancer的。

在这里插入图片描述

​ 通过上面的图和对于灰度发布架构的描述,我这里总结一下实现微服务全链路灰度发布的核心:

  1. 微服务过滤流量并管理灰度发布规则;
  2. loadbalancer负责进行灰度流量的重新路由;

Spring Cloud Alibaba技术架构下的灰度发布实现

​ 实现灰度发布的技术方案有很多,比如Nginx + Lua脚本方式,Nginx即可以作为微服务网关又可以做负载均衡;再比如Service Mesh技术Istio + Envoy,Istio作为控制平台管理下发灰度规则,Envoy作为网络调用组件,如果是Service Mesh技术架构可以完全采用这种架构。如果是传统的微服务架构就有可能需要自己研发一套灰度发布的组件,所以通过上面对灰度发布架构的研究,我们大致知道了如何实现灰度发布系统,我这里就基于Spring Cloud Alibaba传统微服务架构实现全链路灰度发布功能。

基础设计

实现灰度发布需要终端应用(客户端)和服务端做一些约定,这个约定就代表着是否是灰度发布的客户端的网络调用,当然如果不做约定也是可以实现灰度功能的,这就需要服务端的组件对流量做更细致的过滤,比如从网络调用的报文中过滤出灰度发布应用的网络调用,这对服务端来说显然更加麻烦,也不利于维护,所以我这里采用客户端和服务端约定的方式来设计。

HttpHeader设计

​ 客户端和服务端在Http请求头中约定一个固定字段来标识,此标识可以代表“是否走灰度”,也可以设置成一个“用户ID”,“客户端IP”等,如果是“用户ID”,“客户端IP”那就在网关层有个配置,网关匹配到对应的参数就走灰度。

​ HttpHeader增加gray字段作为灰度标记

{
  "gray":"123"
}

Spring Cloud Gateway改造

​ Spring Cloud Gateway在架构中是微服务网关,在灰度发布的作用就是管理灰度发布规则,设置灰度标记到HttpHeader并且传递下去。管理灰度发布规则需要一个配置,我这里选择放到配置文件当中,先实现一个Spring的自定义配置绑定,代码如下:

@Configuration
@RefreshScope
@ConfigurationProperties("spring.cloud.gateway.gray")
@Data
public class GrayProperties {

    /**
     * 灰度开关
     */
    private Boolean enabled;

    /**
     * 灰度匹配内容
     */
    private List<String> matches = new ArrayList<>();
}

对应的在Spring Cloud Gateway的application.yml中的配置示例如下:

spring:
  cloud:
    gateway:
      gray:
        enabled: true
        matches: 
          - 123
          - 456
          - 10.1.1.10

解释下配置文件,spring.cloud.gateway.gray.enabled控制开启关闭灰度发布,spring.cloud.gateway.gray.matches配置的是灰度发布规则匹配,该值是一个list,也就是说只要匹配到HttpHeaders中的gray的值就走灰度发布逻辑。

Spring Cloud Gateway实现灰度发布过滤器

这个过滤器是灰度发布流量过滤的一个核心,大致逻辑是通过处理HttpHeaders中的gray值决定是够要走灰度发布,如果走灰度发布将HttpHeaders中的gray值设置为true即可,这里也可以和客户端约定两个字段,一个是匹配规则,一个灰度控制,我这里就把它放在一个字段中了。

spring.cloud.gateway.gray.enabled为false是不会走到这个过滤器,还有一点要注意,必须要实现Ordered接口,并且设置其顺序为Ordered.HIGHEST_PRECEDENCE,因为灰度发布过滤这属于一个最高等级的过滤器,要先执行。

​ 为了保证线程隔离,通过GrayRequestContextHolder存取灰度标记。

@Component
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.gray.enabled", havingValue = "true")
@AllArgsConstructor
public class GrayscalePublishFilter implements GlobalFilter, Ordered {

    private final GrayProperties grayProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            GrayRequestContextHolder.setGrayTag(false);
            if (grayProperties.getEnabled()) {
                var headers = exchange.getRequest().getHeaders();
                if (headers.containsKey("gray")) {
                    List<String> grayValues = headers.get("gray");
                    if (!Objects.isNull(grayValues) && grayValues.size() > 0) {
                        // 灰度标记为true,直接走灰度
                        String grayValue = grayValues.get(0);
                        // 配置中的值匹配到header中的灰度值,走灰度(可是用户ID,IP,APP版本号等等,只要匹配到就走灰度)
                        if (grayProperties.getMatches().stream().anyMatch(grayValue::equals)) {
                            GrayRequestContextHolder.setGrayTag(true);
                        }
                    }
                }
                var newRequest = exchange.getRequest().mutate()
                        .header("gray", GrayRequestContextHolder.getGrayTag().toString())
                        .build();
                var newExchange = exchange.mutate()
                        .request(newRequest)
                        .build();
                return chain.filter(newExchange);
            }
            return chain.filter(exchange);
        } finally {
            GrayRequestContextHolder.remove();
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
public class GrayRequestContextHolder {

    private static final ThreadLocal<Boolean> GARY_TAG = new ThreadLocal<>();

    public static void setGrayTag(final Boolean tag) {
        GARY_TAG.set(tag);
    }

    public static Boolean getGrayTag() {
        return GARY_TAG.get();
    }

    public static void remove() {
        GARY_TAG.remove();
    }
}

后端服务自定义Spring MVC请求拦截器

​ 自定义Spring MVC拦截器的目的是下游服务收到请求,通过拦截器检查HttpHeader中是否有灰度标记,如果有灰度标记那么就将灰度标记保存到Holder中,之后如果有后续的RPC调用同样的将灰度标记传递下去

@SuppressWarnings("all")
public class GrayHandlerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String gray = request.getHeader("gray");
        // 如果HttpHeader中灰度标记为true,则将灰度标记放到holder中,如果需要就传递下去
        if (gray!= null && gray.equals("true")) {
            GrayRequestContextHolder.setGrayTag(true);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        GrayRequestContextHolder.remove();
    }
}

​ 配置拦截器:

@Configuration
@ConditionalOnClass(value = WebMvcConfigurer.class)
public class GrayWebMvcAutoConfiguration {

    /**
     * Spring MVC 请求拦截器
     * @return WebMvcConfigurer
     */
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new GrayHandlerInterceptor());
            }
        };
    }
}

OpenFeign改造

​ RPC框架OpenFeign改造,实现OpenFeign拦截器,支持从Holder中取出灰度标记,并且放到调用下游服务的请求头中,将灰度标记传递下去。

public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        // 如果灰度标记为true,将灰度标记通过HttpHeader传递下去
        if (GrayRequestContextHolder.getGrayTag()) {
            template.header(GrayConstant.HEADER_GRAY_TAG, Collections.singleton(GrayConstant.HEADER_GRAY_TAG_VALUE));
        }
    }
}

​ 配置OpenFeign拦截器

@Configuration
@ConditionalOnClass(value = RequestInterceptor.class)
public class GrayFeignInterceptorAutoConfiguration {


    /**
     * Feign拦截器
     * @return FeignRequestInterceptor
     */
    @Bean
    public FeignRequestInterceptor feignRequestInterceptor() {
        return new FeignRequestInterceptor();
    }
}

自定义Loadbalancer

​ 这里基础的LoadBalancer框架使用的是Spring Cloud LoadBalancer,所以需要引入LoadBalancer,代码如下:

				<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>

​ 我这里的是将负载均衡作为一个单独模块开发的,如果有需要灰度发布的微服务引用该模块并且配置自定义Loadbalancer即可,Loadbalancer的核心逻辑是根据HttpHeaders中的灰度发布标记,从服务发现的服务列表中筛选出灰度发布的机器实例,然后再通过loadbalancer算法就行负载均衡返回一个服务实例,RPC调用不用管,我这里使用的是OpenFeign作为RPC框架。

​ Spring Cloud LoadBalancer自定义Loadbalancer实现ReactorServiceInstanceLoadBalancer接口,代码如下:

getInstances方法包含了筛选服务列表逻辑,如果从HttpHeaders中获取到gary字段并且该字段值是true就走灰度发布;至于负载均衡逻辑完全拷贝了spring cloud gatewayRoundRobinLoadBalancer的负载均衡逻辑。

@Slf4j
public class GrayscaleLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    final AtomicInteger position;

    final String serviceId;

    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

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

    public GrayscaleLoadBalancer(int seedPosition, String serviceId, ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this.position = new AtomicInteger(seedPosition);
        this.serviceId = serviceId;
        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(supplier, serviceInstances, request));
    }

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

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

        instances = getInstances(instances, request);
        // Do not move position when there is only 1 instance, especially some suppliers
        // have already filtered instances
        if (instances.size() == 1) {
            return new DefaultResponse(instances.get(0));
        }

        // Ignore the sign bit, this allows pos to loop sequentially from 0 to
        // Integer.MAX_VALUE
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;

        ServiceInstance instance = instances.get(pos % instances.size());

        return new DefaultResponse(instance);
    }

    private List<ServiceInstance> getInstances(List<ServiceInstance> instances, Request request) {
        DefaultRequest<RequestDataContext> defaultRequest = Convert
                .convert(new TypeReference<DefaultRequest<RequestDataContext>>() {
                }, request);
        RequestDataContext dataContext = defaultRequest.getContext();
        RequestData requestData = dataContext.getClientRequest();
        HttpHeaders headers = requestData.getHeaders();
        // 获取灰度标记
        String gray = CollectionUtil.get(headers.get("gray"), 0);
        // 灰度标记不为空并且标记为true, 筛选ServiceInstance
        if (StringUtils.isNotBlank(gray) && StringUtils.equals("true", gray)) {
            return instances.stream()
                    .filter(instance -> StringUtils.isNotBlank(instance.getMetadata().get(GrayConstant.HEADER_GRAY_TAG))
                            && gray.equals(instance.getMetadata().get(GrayConstant.HEADER_GRAY_TAG)))
                    .collect(Collectors.toList());
        } else {
            return instances;
        }
    }
}

配置自定义LoadBalancer,代码如下:

@Configuration
public class LoadBalancerGrayAutoConfiguration {

    @Bean
    @ConditionalOnProperty(value = "spring.cloud.loadbalancer.gray.enabled", havingValue = "true", matchIfMissing = true)
    @ConditionalOnBean(LoadBalancerClientFactory.class)
    public ReactorLoadBalancer<ServiceInstance> grayReactorLoadBalancer(Environment environment,
                                                                        LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new GrayscaleLoadBalancer(name, loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
    }
}

测试

​ 三个微服务ruuby-gateway,order-svc,account-svc,调用的关系式通过ruuby-gateway调用order-svc,order-svc内部通过OpenFeign调用

微服务注册元信息修改

微服务注册增加灰度服务标记配置,微服务注册到服务注册中心(Nacos)时通过附加元数据的方式来标记该服务是一个灰度发布的微服务

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          gray: true

自定义LoadBalancer使用

通过如下配置开启自定义LoadBalancer。

spring:
  cloud:
    loadbalancer:
      gray:
        enabled: true

代码中配置LoadBalancer,在微服务启动类上通过注解开启使用自定义LoadBalancer。

@SpringBootApplication
@EnableDiscoveryClient
@LoadBalancerClients(defaultConfiguration = {LoadBalancerGrayAutoConfiguration.class})
public class GateWayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GateWayApplication.class, args);
    }
}

account-svc,现在order-svc,account-svc服务都是灰度版本,测试自定义LoadBalancer效果,下面是服务元数据中的灰度标记

在这里插入图片描述

我们在Postman中设置HttpHeaders的灰度标记gray,设置其值为123,因为我们在网关中配置的matches中有123。

在这里插入图片描述

源码

  • 实现代码放到了github上地址

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在windows11环境下安装CUDA11.6+Anaconda3+pyToach1.13搭建炼丹炉

0.电脑环境 系统&#xff1a;win11 显卡&#xff1a;NVIDIA GTX1650 还有一个pyCharm&#xff0c;其他也用不到了&#xff0c;需要的文章中会进行说明 1.安装CUDA11.6 目前2023.03出来的pyToach2.0是用不到了&#xff0c;因为最低版本支持CUDA11.7。我的显卡是1650&#xff0c…

阿里巴巴高管换血,吴永明接替张勇

文章目录 经济学人 &#x1f4b0; 第 26 周&#x1fa78; 阿里巴巴高管换血&#xff0c;吴永明接替张勇&#x1f304; 孙正义再出山&#x1f43f;️ 英特尔加码德国&#xff01;投资330亿美元建两座芯片工厂&#xff01;&#x1f30a; 亚马逊被指控强加 Prime 服务✈️ 印度航空…

Jmeter多接口测试之参数传递

目录 前言&#xff1a; 接口示例 正则表达式提取器 正则表达式提取实例 Json提取器 Json提取器实例 前言&#xff1a; 在进行多接口测试时&#xff0c;有些情况下需要将前一个接口返回数据作为后一个接口的参数&#xff0c;以模拟实际场景。JMeter作为一款常用的性能测试…

【EXCEL】如何查找特殊字符 问号‘?’星号 ‘*’

目录 0.环境 1.适用场景 1&#xff09;直接搜索问号的结果&#xff1a; 2&#xff09;修改【查找内容】后&#xff0c;搜索结果变为精准定位&#xff1a; 2.具体做法 0.环境 windows wps&#xff08;或excel&#xff0c;这里试了&#xff0c;此问题wps和excel表格是通用…

中间件解析漏洞

服务器解析漏洞算是历史比较悠久了&#xff0c;但如今依然广泛存在。在此记录汇总一些常见服务器&#xff08;WEB server&#xff09;的解析漏洞&#xff0c;比如IIS6.0、IIS7.5、apache、nginx等 2|0 二、IIS5.x-6.x解析漏洞&#xff08;针对asa/asp/cer&#xff09; 2|11、打…

区块链中怎么惩罚虚假信息的矿工,工作量证明POW,共识算法

目录 区块链中怎么惩罚虚假信息的矿工 工作量证明POW 什么是工作量证明&#xff1f; 现在出现了另一个问题&#xff1a;如果其他人偷看了小明的答案并且抢答了怎么办&#xff1f; 为什么区块可以安全广播&#xff1f; 共识算法 小结 区块链中怎么惩罚虚假信息的矿工 1…

三分钟学习一个python小知识4-----------我的对python中numpy的理解, 我列举了关于numpy常用的10个例子来深入理解numpy

这里写目录标题 1、NumPy是什么2、NumPy的常见应用---必须掌握2.1.创建一个数组2.2.数组的属性2.3.取数组中的元素2.4.数组的运算2.5.数组的转置2.6. 数组的索引和切片2.7. 数组的重塑2.8. 数组的广播2.9. 数组的聚合操作2.10. 数组的排序 总结 1、NumPy是什么 NumPy是专门用于…

Spring MVC简介附入门案例

目录 一、SpringMVC简介 1.1 MVC模型 1.2 SpringMVC 二、SpringMVC入门案例 2.1 创建项目 2.2 引入依赖和tomcat插件 2.3 修改web.xml文件 2.4 新建springmvc.xml文件 2.5 编写控制器 2.6 配置运行方式 2.7 运行测试 三、SpringMVC执行流程 3.1 SpringMVC的组件…

Kangas:计算机视觉中的pandas

Kangas&#xff1a;计算机视觉中的pandas 介绍Kangas的优点 使用Kangas读取CSV文件读取图像文件Kangas DataGrid训练模型 介绍 在计算机视觉领域&#xff0c;Kangas是一种在图像数据处理和分析方面越来越受欢迎的工具之一。就像Pandas改变了数据分析人员处理表格数据的方式一样…

macOS FreeBSD 如何刷新 DNS 缓存

macOS FreeBSD 如何刷新 DNS 缓存 全文&#xff1a;如何刷新 DNS 缓存 (macOS, Linux, Windows) Unix Linux Windows 如何刷新 DNS 缓存 (macOS, FreeBSD, RHEL, CentOS, Debian, Ubuntu, Windows) 请访问原文链接&#xff1a;https://sysin.org/blog/how-to-flush-dns-cach…

AI绘画:切换黑色风格,安装更棒的主题!

从Stable Diffusion 出现的第一天起&#xff0c;我就开始关注这个项目&#xff0c;后来也出过几期教程。 一直以来都是只追求功能实现&#xff0c;不管界面的问题。现在我突然想要换一个帅气的主题了…就是这么突然&#xff01; 默认的白色主题太….普通…既没有设计感啊&…

智能井盖传感器:以科技破解城市顽疾

在城市的道路网络中&#xff0c;井盖扮演着重要的角色&#xff0c;用于覆盖下方的管道和设施&#xff0c;然而&#xff0c;由于井盖的老化、损坏或被盗&#xff0c;常常会导致安全问题的发生&#xff0c;如路面塌陷、行人受伤等。井盖的状态监测和维护一直是城市管理者面临的挑…

社区分享|JumpServer开源堡垒机一直都是我的第一选择

编者注&#xff1a;本文由JumpServer开源堡垒机社区用户James Wei供稿。 “我最开始接触到的堡垒机就是JumpServer&#xff0c;最了解的也是JumpServer&#xff0c;已经完全习惯了JumpServer的使用习惯&#xff0c;这也是我一直选择JumpServer的一个重要原因。” ——JumpSer…

跟着AIGC学Linux:简介(一)

文章目录 1.Linux内核的历史2.基本概念3.发行版4.Linux应用领域5.Linux VS Windows横向对比表格&#xff1a; 1.Linux内核的历史 Linux内核最初由一位名叫Linus Torvalds的芬兰计算机科学家于1991年创建。当时&#xff0c;Torvalds是一名赫尔辛基大学的学生&#xff0c;他在寻…

Microsoft365有用吗?2023最新版office有哪些新功能?

office自97版到现在已有20多年&#xff0c;一直是作为行业标准&#xff0c;格式和兼容性好&#xff0c;比较正式&#xff0c;适合商务使用。包含多个组件&#xff0c;除了常用的word、excel、ppt外&#xff0c;还有收发邮件的outlook、管理数据库的access、排版桌面的publisher…

【Windows】虚拟串口工具VSPD7.2安装

【Windows】虚拟串口工具VSPD7.2安装 1、背景2、VSPD7.2安装3、创建虚拟串口 1、背景 ​Virtual Serial Ports Driver​是由著名的软件公司Eltima制作的一款非常好用的​虚拟串口工具​&#xff0c;简称&#xff1a;VSPD。 VSPD其功能如同 Windows机器上COM 串行端口的仿真器…

永磁同步直线电机学习笔记——什么是直线电机?

永磁同步直线电机&#xff08;Permanent Magnet Linear Synchronous Motor&#xff0c;简称PMLSM&#xff09;是一种电动机&#xff0c;它通过将永磁体和线圈组合在一起&#xff0c;将电能转化为机械运动。与传统的旋转电机不同&#xff0c;PMLSM是一种直线运动电机&#xff0c…

【Java高级语法】(十七)Stream流式编程:释放流式编程的效率与优雅,狂肝万字只为全面讲透Stream流!~

Java高级语法详解之Stream流 1️⃣ 概念及特征2️⃣ 优势和缺点3️⃣ 使用3.1 语法3.2 常用API详解3.3 案例 4️⃣ 应用场景5️⃣ 使用技巧6️⃣ 并行流 ParallelStream&#x1f33e; 总结 1️⃣ 概念及特征 Java的Stream流是在Java 8中引入的一种用于处理集合数据的功能强大且…

【小沐学Web】Node.js搭建HTTPS 服务器

文章目录 1、简介1.1 HTTPS协议1.2 Node.js中的HTTPS 2、生成自签名证书2.1 key文件2.2 csr文件2.3 crt文件 4、代码测试4.1 Node.js简介4.2 Node.js的http模块4.3 Node.js的Express模块4.4 Node.js的https模块4.5 Node.js的httpsexpress模块 结语 1、简介 1.1 HTTPS协议 HTTP…

HackTheBox - 学院【CPTS】复习3 - XSS、文件包含、文件上传、命令注入

XSS 登录表单 document.write(<h3>Please login to continue</h3><form actionhttp://OUR_IP><input type"username" name"username" placeholder"Username"><input type"password" name"password&…
最新文章