如何让QPS提升20倍

news2025/1/13 3:09:48

一、什么是QPS

QPS,全称Queries Per Second,即每秒查询率,是用于衡量信息检索系统(例如搜索引擎或数据库)或请求-响应系统(如Web服务器)每秒能够处理的请求数或查询次数的一个性能指标。以下是对QPS的详细解释:

一、定义与意义

  • 定义:QPS表示系统在单位时间(通常为一秒)内能够成功处理的请求数量。在高并发场景下,这个指标尤为重要,因为它直接关系到系统的稳定性和用户体验。
  • 意义:QPS是衡量服务器性能的关键指标之一,它直接反映了系统处理请求的能力。通过监控和优化QPS,可以确保系统在高负载下依然保持高效稳定运行。

二、计算方法

QPS的计算公式为:QPS = 总请求数 / 时间段(秒)。具体计算步骤如下:

确定时间窗口:根据实际需求和服务器负载情况来确定时间窗口,可以是1秒、1分钟、5分钟等。
收集查询日志:收集服务器在该时间窗口内的查询日志,并统计成功处理的请求总数。
计算QPS:将总请求数除以时间窗口的总秒数,即可得到平均每秒的查询率。
例如,如果在1分钟内系统处理了3000个请求,则QPS = 3000 / 60 = 50 QPS,意味着系统平均每秒处理了50个请求。

三、与并发数和响应时间的关系

QPS与系统的并发数和响应时间紧密相关。具体来说:
并发数:并发数是指系统在同一时间内处理的请求数量。并发数越高,系统在单位时间内能够处理的请求数量也就越多,从而可能提高QPS。
响应时间:响应时间是指系统从接收到请求到返回响应结果所需的时间。响应时间越短,系统在单位时间内能够处理的请求数量也就越多,同样可能提高QPS。
因此,在优化系统性能时,可以通过增加并发数和缩短响应时间来提升QPS。

二、同步代码

所谓的同步代码,也就是从我们接受到请求直到请求返回都是由一个线程处理的,如果处理代码中有阻塞那么这个时候此线程就会阻塞,在请求量比较大的情况下,也就是并发场景,这个时候会有很多的请求发过来,那么tomcat只有两百的线程,如果线程阻塞时间较长,那么tomcat的线程会被全部阻塞,导致无法处理外部请求,进而系统的吞吐量就会很低

2.1、示意图

在这里插入图片描述

在这张图片中可以清晰的看到,前端(移动端+pc端)发过来一个请求,这时tomcat会开启一个线程处理,这个线程从接受请求是开启直到请求返回都是一个线程在处理,那么就会存在上面所说的同步阻塞问题
到这里先思考三秒钟,该情况如何优化
这是同步代码的第一个问题。(大家别慌,我们先提出同步代码的所有问题,然后我们一一解决)
接下来继续探索下一个问题
以上我们聊了从接受请求到处理请求都是由一个线程处理,当并发量大并且代码有阻塞的情况下,会将tomcat的线程耗尽,从而达到tomcat的瓶颈。那造成这个问题的原因是什么呢?

  • 第一个:由于tomcat的线程是有限的(200)
  • 第二个:由于处理代码耗时,导致线程阻塞,进而导致tomcat线程耗尽

2.2、同步处理代码图解

在这里插入图片描述
在这张图中,大家可以清晰的看到当需要完成这一个任务时,需要先完成任务1,再完成任务2,然后完成任务3。那么所消耗的时间就是 :time > 任务1 + 任务2 + 任务3,在这里我举个实际生活中的场景,如果你要下单,那么需要调用 用户服务(查询用户信息)—>商品服务(查询商品信息)—>积分服务(修改积分)—>订单服务(生成订单)—>库存服务(减库存)

2.3、代码示例:伪代码模拟

JSONPObject createOrder(Integer userId,Integer goodsId){
        // 1、调用用户服务,获取用户信息
        User user = getUserById(userId); // 2s
        // 2、调用商品服务,获取商品详情
        Goods goods = getGoodsById(goodsId); // 2s
        // 3、调用积分服务,修改积分
        updatePoints(userId);  // 2s
        // 4、调用订单服务,生成订单
        createOrderByUserAndGoods(user,goods); // 2s
        // 5、调用库存服务,修改库存
        updateInventoryByGoodsId(goodsId);  //2s
        
        return null;
    }

这里只给出了个示例,实际中链路会很长,那这个时候是不是需要花费很长的时间,那这里也将是我们需要优化的点

三、异步代码优化

首先我们使用异步代码优化第二个问题,也就是刚刚提到的代码串行所造成的耗时,进而导致的线程阻塞。

3.1、图解异步代码

先来张图
在这里插入图片描述
在这幅图中可以清晰看到只要到我们的处理代码,我们开启了四个线程处理,在这里我将订单服务放到了用户服务和商品服务完成之后处理,这里和你的系统设计有关系,也可以和其他服务同时并发处理,那么经过这次优化后,处理时间 time > 前四个服务中最长的 + 订单服务,这样既完成了代码串行问题的优化。
很多小伙伴在这个时候是不是想着光理论没用,要能代码实现。放心,肯定会有代码实现的。

3.2、代码示例:

JSONPObject createOrder2(Integer userId, Integer goodsId) {
        // 1、调用用户服务,获取用户信息
        CompletableFuture<User> future1 = CompletableFuture.supplyAsync(() -> {
            // 2s
            return getUserById(userId);
        });
        // 2、调用商品服务,获取商品详情
        CompletableFuture<Goods> future2 = CompletableFuture.supplyAsync(() -> {
            return getGoodsById(goodsId); // 2s
        });
        // 3、调用积分服务,修改积分
        CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
            updatePoints(userId);  // 2s
        });
        // 4、调用订单服务,生成订单(在用户服务和商品服务调用结束后执行)
        CompletableFuture<Void> completableFuture = future1.thenCombineAsync(future2, (user, goods) -> {
            createOrderByUserAndGoods(user, goods); // 2s
            return null;
        });
        // 5、调用库存服务,修改库存
        CompletableFuture.runAsync(() -> {
            updateInventoryByGoodsId(goodsId);  //2s
        });
        return null;
    }

在这里大家你要纠结为什么用户服务和商品服务完成后调用订单服务,这个和你的业务逻辑有关系,怎么写都无所谓,在这里大量使用了CompletableFuture,接下来我详细介绍一下CompletableFuture

3.3、CompletableFuture 详讲

CompletableFuture是Java 8中引入的一个类,它实现了Future和CompletionStage接口,为异步编程提供了强大的支持。以下是对CompletableFuture的详细介绍:

3.3.1、基本概念与特性

  • 异步执行:CompletableFuture允许任务在后台线程中异步执行,不会阻塞主线程,从而提高了应用程序的响应性和性能。
  • 可组合性:CompletableFuture的操作可以组合成一个或多个CompletableFuture对象,构成复杂的异步计算链。这包括结果的转换、组合以及异常处理等。
  • 异常处理:通过exceptionally()等方法,CompletableFuture可以捕获计算中的异常并返回默认值,或者通过handle()等方法同时处理正常结果和异常。
  • 取消与超时:支持取消异步任务和设置超时时间,避免任务的无限等待。
  • 非阻塞式等待:提供了非阻塞式的等待方法,如join()和getNow(),可以在不阻塞当前线程的情况下获取任务的结果。
  • 并行处理:在处理多个耗时操作时,如I/O操作、数据库访问或网络请求,CompletableFuture可以并行执行这些任务,提高系统吞吐量和响应能力。

3.3.2、创建CompletableFuture实例

1、supplyAsync():用于创建返回结果的异步任务。例如:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 执行异步任务并返回结果
    return "Hello, CompletableFuture!";
});

2、runAsync():用于创建不返回结果的异步任务。例如:

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    // 执行异步任务
    System.out.println("Task running asynchronously");
});

3.3.3、任务编排方法

3.3.3.1、转换类方法
  • thenApply() / thenApplyAsync():将上一个任务的结果转换为新的结果。thenApply()在同一个线程中执行,而 thenApplyAsync()可能在新的线程中执行。
  • thenAccept() / thenAcceptAsync():处理上一个任务的结果,但不返回新的值。thenAccept()在同一个线程中执行,而thenAcceptAsync()可能在新的线程中执行。
  • thenRun() / thenRunAsync():在上一个任务完成后执行一个操作,不使用上一个任务的结果。
3.3.3.2、组合类方法
  • thenCompose() / thenComposeAsync():将两个CompletableFuture组合成一个。当一个任务依赖另一个任务的结果时,可以使用此方法。
  • thenCombine() / thenCombineAsync():组合两个独立任务的结果。需要两个独立任务的结果进行计算时,可以使用此方法。
3.3.3.3、多任务协调方法
  • allOf():等待所有任务完成。适用于需要等待多个任务都完成的场景。
  • anyOf():等待任意一个任务完成。适用于多个任务中只需要最快的结果的场景。
3.3.3.4、异常处理机制
  • exceptionally():处理异常并提供默认值。当CompletableFuture中的任务抛出异常时,可以捕获该异常并返回一个默认值。
  • handle() / handleAsync():处理正常结果和异常。无论任务是否成功完成,都可以使用此方法处理结果或异常。
  • whenComplete() / whenCompleteAsync():任务完成时的回调(正常或异常)。可以在任务完成后执行一些清理工作或记录日志等。
3.3.3.5、使用示例

以下是一个简单的使用示例,展示了如何创建CompletableFuture对象、进行任务编排以及处理异常:

public class CompletableFutureExample {
    public static void main(String[] args) {
        // 创建两个异步任务
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000); // 模拟耗时操作
                return "Result from future1";
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return "Task interrupted";
            }
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(500); // 模拟耗时操作
                return "Result from future2";
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return "Task interrupted";
            }
        });

        // 组合两个异步任务的结果
        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
            return result1 + " and " + result2;
        });

        // 处理异常并提供默认值
        CompletableFuture<String> safeFuture = combinedFuture.exceptionally(ex -> {
            return "Default value due to error: " + ex.getMessage();
        });

        // 获取结果并打印
        try {
            String result = safeFuture.get(); // 阻塞等待结果返回
            System.out.println(result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们创建了两个异步任务future1和future2,它们分别在不同的线程中执行并返回结果。然后,我们使用thenCombine()方法将这两个任务的结果组合成一个新的CompletableFuture对象combinedFuture。接着,我们使用exceptionally()方法处理可能发生的异常,并提供一个默认值。最后,我们使用get()方法阻塞等待结果返回并打印出来。

综上所述,CompletableFuture是Java异步编程的强大工具,它提供了一种简洁且强大的方式来处理异步任务。通过丰富的API和灵活的任务编排能力,CompletableFuture可以轻松地创建、组合和链式调用异步操作,从而提高了程序的响应速度和资源利用率。
大家看完这里CompletableFuture的介绍后,再回过头去看看我们写的伪代码就知道怎么回事儿了(为什么是异步的,为什么会提高系统执行时间)

3.4、接口异步

在这里我们已经优化了同步代码所造成的线程阻塞问题,那我们如何优化tomcat因线程有限(200)而造成的吞吐量下降问题呢?

首先我们分析一下问题在哪里:1、tomcat线程池有限(200);2、占用tomcat线程时间过长

占用时间过长我们已经做了优化,针对第一个问题,最简单的方法时配置tomcat的线程数量,但是这种方法并不是我们研究的重点。这里我们依然采用异步的方式去解决问题

解决的核心思路:tomcat主线程接受请求------> 交给子线程处理 ----->找tomcat线程返回

3.4.1、图解

在这里这样写大家可能看不懂,上图:
在这里插入图片描述
针对这张图,我i在这里做详细介绍:

前端发起请求,tomcat接受到请求后,通过Spring MVC的DispatcherServlet将请求交给响应的 controller 处理,但这个controller返回的是一个CompletableFuture对象,那么这个时候任务就会交给子线程处理,tomcat 线程将被释放,并且spring boot会开启一个监听器,监听你返回的 CompletableFuture 对象的状态,一旦CompletableFuture对象状态被修改为完成,那么这个时候就会找到tomcat线程返回相应的结果

3.4.2代码示例

controller

 @GetMapping("name")
   public CompletableFuture<String> getUserName(){
       return userService.getUserName();
   }

   @GetMapping("setName")
   public void setName(){
       userService.setUserName();
   } 

service

 CompletableFuture<String> completableFuture = new CompletableFuture<>();
 
    @Override
   public CompletableFuture<String> getUserName() {
       return completableFuture;
   }

   @Override
   public void setUserName() {
       completableFuture.complete("siyu");
   }

在这里你就会看到你请求name接口时并拿不到数据,当你在请求一下setName接口时name接口就拿到了值,这里就实现了异步操作,当然实际代码中你肯定不会这么用,这只是个示例,实际代码中设置name这一步你可能会用定时任务什么的去实现,我就不过多赘述了。

在这里优化思路已经讲完了。那来个实际优化案例,本例使用(异步+合并)的方式提升系统并发量

四、实际场景优化案例

controller 代码示例

@RestController
@RequestMapping("user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping
    public CompletableFuture<User> getUserById(@RequestParam("userId") Integer userId) throws ExecutionException, InterruptedException {
        return userService.getUser(userId);
    }
}

service 代码示例

public interface UserService {
    CompletableFuture<User> getUser(Integer userId) throws ExecutionException, InterruptedException;
}

serviceImpl 代码示例

@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    private final LinkedBlockingDeque<Request> blockingDeque = new LinkedBlockingDeque<>();
    private final ExecutorService executorService = Executors.newFixedThreadPool(16);
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(16);

    @Data
    static class Request {
        Integer userId;
        CompletableFuture<User> completableFuture;
    }

    @Override
    public CompletableFuture<User> getUser(Integer userId) {
        CompletableFuture<User> future = new CompletableFuture<>();
        Request request = new Request();
        request.userId = userId;
        request.completableFuture = future;
        blockingDeque.add(request);
        return future;
    }
    @PostConstruct
    public void init() {
        AtomicInteger count = new AtomicInteger(0);
        scheduler.scheduleAtFixedRate(() -> {
            if (blockingDeque.isEmpty()) return;
            List<Request> requests = new ArrayList<>();
            blockingDeque.drainTo(requests);
            Set<Integer> userIds = requests.stream().map(Request::getUserId).collect(Collectors.toSet());
            List<User> usersFromDb = userMapper.selectByIds(userIds);
            log.info("查询数据库{}次,处理{}个请求", count.incrementAndGet(), requests.size());
            Map<Integer, User> userMap = usersFromDb.stream().collect(Collectors.toMap(User::getUserId, user -> user));

            for (Request request : requests) {
                CompletableFuture.runAsync(() ->{
                    User user = userMap.getOrDefault(request.userId, null);
                    request.completableFuture.complete(user);
                }).exceptionally(ex ->{
                    log.error(ex.getMessage());
                    return null;
                });
            }
        }, 200, 200, TimeUnit.MILLISECONDS);
    }

    @PreDestroy
    public void destroy() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
                if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("Scheduler did not terminate!");
                }
            }
        } catch (InterruptedException ex) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("ExecutorService did not terminate!");
                }
            }
        } catch (InterruptedException ex) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

首先说明一下,本例中使用的userId,那这个场景肯能不是很多,比如说多人查询同一个热门商品,那就很好用了。

代码设计思想解读:

大量请求发过来后,构建 Request 对象,Request 对象包含请求的userId和一个Completablefuture对象,然后将 Request 放入阻塞队列,等待定时任务处理,接口直接返回Completablefuture对象。
定时任务从阻塞队列中定时弹出所有请求进行处理。拿到请求后,根据 userId 去重,然后调用批量查询接口查询数据,拿到数据后,比对 Request 中的userId和获取到数据的userId,如果相等,将获取后的数据设置到对应Request 的Completablefuture对象。完结散花。

五、祝愿

路漫漫其修远兮,吾将上下而求索。
愿明天的您遇见更好的自己

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

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

相关文章

vue 实现打包并同时上传至服务器端

将 publish_script 及以下文件 upload.server.js 添加到 主文件下&#xff0c;与 src 同级别 具体操作步骤&#xff1a; 1、安装 npm install scp2 2、将下面两条命令加入至 package.json 的 scripts 中 "upload": "node publish_script/upload.server.js&q…

2015年IMO第3题

△ A B C \triangle ABC △ABC 的垂心为 H H H, A H AH AH 为直径的圆交 △ A B C \triangle ABC △ABC 的外接圆 ⨀ O \bigodot O ⨀O 于 A A A, Q Q Q. H Q HQ HQ 为为直径的圆交 ⨀ O \bigodot O ⨀O 于 Q Q Q, K K K. M M M 为 B C BC BC 边中点, F F F 为 A…

新活动平台建设历程与架构演进

01 前言 历时近两年的重新设计和迭代重构&#xff0c;用户技术中心的新活动平台建设bilibili活动中台终于落地完成&#xff01;并迎来了里程碑时刻 —— 接过新老迭代的历史交接棒&#xff0c;从内到外、从开发到搭建实现全面升级&#xff0c;开启了活动生产工业化新时代&#…

《安富莱嵌入式周报》第348期:开源低功耗测试仪,开源创意万用表,续航100-300小时,开源PCB电机,自制shell和网络协议栈,开源水培自动化系统

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Tzr9Y3EQ7/ 《安富莱嵌入式周报》第348期&#xff1a;开源低功…

【Kaggle】练习赛《预测贴纸的销量》(下)

前言 上篇利用各地区的GDP数据还填充目标标签的缺失值&#xff1b;中篇顺着这个思路&#xff0c;利用这个原理来预测未来的销量&#xff0c;具体方法思路&#xff1a;先一一对国家、产品和商店进行汇总&#xff0c;然后对未来三年的每日销售额进行预测&#xff0c;然后再进行分…

RT-DETR代码详解(官方pytorch版)——参数配置(1)

前言 RT-DETR虽然是DETR系列&#xff0c;但是它的代码结构和之前的DETR系列代码不一样。 它是通过很多的yaml文件进行参数配置&#xff0c;和之前在train.py的parser argparse.ArgumentParser()去配置所有参数不同&#xff0c;所以刚开始不熟悉代码的时候可能不知道在哪儿修…

细说STM32F407单片机以DMA方式读写外部SRAM的方法

目录 一、工程配置 1、时钟、DEBUG、GPIO、CodeGenerator 2、USART3 3、NVIC 4、 FSMC 5、DMA 2 &#xff08;1&#xff09;创建MemToMem类型DMA流 &#xff08;2&#xff09;开启DMA流的中断 二、软件设计 1、KEYLED 2、fsmc.h、fsmc.c、dma.h、dma.c 3、main.h…

Proteus-8086调试汇编格式的一点心得

这阵子开始做汇编的微机实验&#xff08;微机原理与接口技术题解及实验指导&#xff0c;吴宁版本13章&#xff09;&#xff0c;中间出了挺多问题&#xff0c;解决后记录下。 先上电路图 用子电路来仿真发现仿真的时候子电路这块根本没有高低电平输出&#xff0c;只好把子电路拿…

FreeROTS学习 内存管理

内存管理是一个系统基本组成部分&#xff0c;FreeRTOS 中大量使用到了内存管理&#xff0c;比如创建任务、信号量、队列等会自动从堆中申请内存&#xff0c;用户应用层代码也可以 FreeRTOS 提供的内存管理函数来申请和释放内存 FreeRTOS 内存管理简介 FreeRTOS 创建任务、队列…

【西北工业大学主办 | EI检索稳定 | 高H值专家与会报告】2025年航天航空工程与材料技术国际会议(AEMT 2025)

2025 年航天航空工程与材料技术国际会议&#xff08;AEMT 2025&#xff09;将于2025年2月28日至3月2日在中国天津召开。本届会议由西北工业大学主办&#xff0c;由北京航空航天大学、北京理工大学作为支持单位加入&#xff0c;AEIC 学术交流中心协办。 AEMT 2025 旨在汇聚来自全…

目标检测跟踪中的Siamese孪生网络与普通卷积网络(VGG、ResNet)有什么区别?

1、什么是Siamese网络&#xff1f; Siamese网络又叫孪生网络&#xff0c;是一种特殊的神经网络架构&#xff0c;由一对&#xff08;或多对&#xff09;共享参数的子网络组成&#xff0c;用于学习输入样本之间的相似性或关系。最早在 1994 年由 Bromley 等人提出&#xff0c;最…

网络攻击行为可视化分析系统【数据分析 + 可视化】

一、系统背景 随着信息技术的快速发展&#xff0c;网络已成为现代社会不可或缺的一部分。然而&#xff0c;与此同时&#xff0c;网络攻击手段也日益多样化和复杂化&#xff0c;给企业和个人的信息安全带来了极大的威胁。传统的网络攻击分析方法往往依赖于人工分析和处理大量的…

一个运行在浏览器中的开源Web操作系统Puter本地部署与远程访问

文章目录 前言1.关于Puter2.本地部署Puter3.Puter简单使用4. 安装内网穿透5.配置puter公网地址6. 配置固定公网地址 &#x1f4a1; 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击跳转到网站…

C语言 操作符_位操作符、赋值操作符、单目操作符

1.位操作符 & - 按&#xff08;2进制&#xff09;位与 | - 按&#xff08;2进制&#xff09;位或 ^ - 按&#xff08;2进制&#xff09;位异或 只适用于整型 例&#xff1a;实现交换两个变量的值&#xff0c;要求不能新建变量 //3^3 0 -> a^a 0 //011 //011 //000 …

图像处理 | 图像二值化

在图像处理领域&#xff0c;图像二值化是一个重要的操作&#xff0c;它将彩色或灰度图像转换为只有两种颜色&#xff08;通常是黑白&#xff09;的图像。二值化广泛应用于文字识别、图像分割、边缘检测等领域&#xff0c;尤其在处理简洁和高对比度的图像时非常有效。本文将深入…

IP 地址与蜜罐技术

基于IP的地址的蜜罐技术是一种主动防御策略&#xff0c;它能够通过在网络上布置的一些看似正常没问题的IP地址来吸引恶意者的注意&#xff0c;将恶意者引导到预先布置好的伪装的目标之中。 如何实现蜜罐技术 当恶意攻击者在网络中四处扫描&#xff0c;寻找可入侵的目标时&…

Web基础之什么是HTTP协议

Q&#xff1a;什么是HTTP协议&#xff1f; 概念&#xff1a;Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 特点&#xff1a; 1&#xff0e;基于TCP协议&#xff1a;面向连接&#xff0c;安全 2&#xff0e;基…

#渗透测试#谷歌扩展学习#编写一个属于自己的谷歌扩展

目录 一、Chrome扩展程序是什么 二、如何自己编写一个简单谷歌扩展 1. 创建项目文件夹 2. 创建 manifest.json 文件 3. 创建 popup.html 文件 4. 创建 popup.js 文件 5. 加载扩展程序到Chrome浏览器 6. 测试扩展程序 三、Chrome插件图标设计技巧 1. 简洁明了 2. 独特…

LayerNorm的思考

文章目录 1. LayerNorm2. 图解3. softmax4. python 代码 1. LayerNorm y x − E [ x ] v a r ( x ) ϵ ∗ γ β \begin{equation} y\frac{x-\mathrm{E}[x]}{\sqrt{\mathrm{var}(x)\epsilon}}*\gamma\beta \end{equation} yvar(x)ϵ ​x−E[x]​∗γβ​​ 2. 图解 矩阵A …

ExplaineR:集成K-means聚类算法的SHAP可解释性分析 | 可视化混淆矩阵、决策曲线、模型评估与各类SHAP图

集成K-means聚类算法的SHAP可解释性分析 加载数据集并训练机器学习模型 SHAP 分析以提取特征对预测的影响 通过混淆矩阵可视化模型性能 决策曲线分析 模型评估&#xff08;多指标和ROC曲线的目视检查&#xff09; 带注释阈值的 ROC 曲线 加载 SHAP 结果以进行下游分析 与…