Springboot整合Zookeeper分布式组件实例

news2024/11/23 1:22:11

一、Zookeeper概述

1.1 Zookeeper的定义

Zookeeper是一个开源的分布式协调服务,主要用于分布式应用程序中的协调管理。它由Apache软件基金会维护,是Hadoop生态系统中的重要成员。Zookeeper提供了一个高效且可靠的分布式锁服务,以及群集管理功能,在分布式系统中起到了“守护神”的作用。

1.2 Zookeeper的核心理念

Zookeeper基于以下关键概念构建:

  • 数据模型:Zookeeper的数据模型是一个层次结构,这个层次类似于一个文件系统,与liunx的文件系统类似,整体可以看作为一棵树。它由节点组成,节点也成为ZNode,每个节点可以有子节点。节点可以存储数据,但数据尺寸有限默认存储为1MB的数据。

  • 节点(ZNode):Zookeeper中的每个数据单元称为ZNode。ZNode有两种类型:持久(Persistent)和临时(Ephemeral)。持久节点在客户端断开连接后仍存在,而临时节点在客户端断开连接后会被自动删除。

  • 观察者(Watcher):客户端可以在ZNode上设置观察者,当ZNode的数据或子节点发生变化时,Watcher会通知对应的客户端。

  • 有序性(Orderliness):Zookeeper通过全局顺序来确保所有操作的顺序一致。

  • 数据一致性 :每个server保存一份相同的数据拷贝,客户端无论请求到被集群中哪个server处理,得到的数据都是一致的。

  • 集群服务:在Zookeeper集群服务由一个领导者(leader),多个跟随者(follower)组成的集群。领导者(leader)负责进行投票的发起和决议,更新集群服务状态。跟随者用于接收客户请求并向客户端返回结果,在选举Leader过程中参与投票。集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。

二、Zookeeper的应用场景

2.1 分布式锁服务

Zookeeper能够非常有效地实现分布式锁。这在需要同步或并发控制的分布式系统中尤为重要。通过利用Zookeeper的临时ZNode特性,可以实现锁的自动释放,防止死锁。

2.2 统一配置管理

在分布式系统中,应用程序的配置管理成为一个复杂的问题。Zookeeper提供了一种集中式管理配置的方式,所有的配置文件可以存储在Zookeeper中,并且可以动态更新。当配置变化时,Zookeeper可以通知到所有客户端,从而使应用程序能够立即响应变化。

2.3 命名服务

Zookeeper可以作为分布式系统的命名服务,通过维护名称和元数据的映射关系,提供高效的名称解析能力。

2.4 集群管理

Zookeeper能够监控集群中各个节点的状态,决定节点是否健康,而节点的加入和离开能够动态调整。

三、Zookeeper的安全管理操作方法

3.1 基本安全措施

  • 验证(Authentication):Zookeeper支持基于客户端和服务器之间的认证机制。通过设置用户和密码,可以限制对ZNode的访问。

3.1.1认证方式
  • world:默认方式,开放的权限,意解为全世界都能随意访问。
  • auth:已经授权且认证通过的用户才可以访问。
  • digest:用户名:密码方式认证,实际业务开发中最常用的方式。
  • IP白名单:授权指定的Ip地址,和指定的权限点,控制访问。

  • ACL(Access Control Lists):通过设定ACL,能够控制ZNode的读写权限。ACL规则可以根据不同的需求设定,如只读、完全控制等。

3.1.2 ACL授权流程
  • 添加认证用户

addauth digest 用户名:密码

  • 设置权限

setAcl /path auth:用户名:密码:权限

  • 查看Acl设置

getAcl /path

完整的操作如下代码

-- 添加授权用户
[zk: localhost:2181] addauth digest user1:123456
-- 创建节点
[zk: localhost:2181] create /testNode testNode 
-- 节点授权
[zk: localhost:2181] setAcl /testNode  auth:user1:123456:cdrwa
-- 查看授权
[zk: localhost:2181] getAcl /testNode 

3.2 数据加密

在Zookeeper的配置文件中,可以启用数据传输加密(例如SSL/TLS)来保证数据在网络传输中的安全性。

3.3 安全配置示例

# zookeeper configuration 
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider 
requireClientAuthScheme=sasl 
digest.authenticationHandler.sasl.clientAllowedProtocols=GSSAPI:CRAM-MD5

四、Zookeeper与Spring Boot 2的整合

4.1 引入依赖

在Spring Boot 2项目中,首先需要引入Curator依赖,这是用于简化Zookeeper操作的一个高层次API。Curator框架在Zookeeper原生API接口上进行二次包装。提供ZooKeeper各种应用场景:比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等API封装。

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-client</artifactId>
    <version>2.12.0</version>
</dependency>

4.2 Springboot项目yml配置

application.properties中,配置Zookeeper的连接信息:

zoo:
  keeper:
    #开启标志
    enabled: true
    #服务器地址
    server: 127.0.0.1:2181
    #命名空间,被称为ZNode
    namespace: testNode
    #权限控制,加密
    digest: user1:123456
    #会话超时时间
    sessionTimeoutMs: 3000
    #连接超时时间
    connectionTimeoutMs: 60000
     #最大重试次数
    maxRetries: 2
    #初始休眠时间
    baseSleepTimeMs: 1000

4.3 编写配置类

编写Zookeeper配置类,用于初始化Zookeeper客户端:

@Configuration
public class ZookeeperConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperConfig.class) ;
    
    //注入Zookeeper配置文件类,用于获取yml的配置项值
    @Autowired
    private ZookeeperParam zookeeperParam ;

    private static CuratorFramework client = null ;
    /**
     * 初始化
     */
    @PostConstruct
    public void init (){
        //重试策略,初试时间1秒,重试10次
        RetryPolicy policy = new ExponentialBackoffRetry(
                zookeeperParam.getBaseSleepTimeMs(),
                zookeeperParam.getMaxRetries());
        //通过工厂创建Curator
        client = CuratorFrameworkFactory.builder()
                .connectString(zookeeperParam.getServer()) //链接的服务的地址
                .authorization("digest",zookeeperParam.getDigest().getBytes()) //认证方式
                .connectionTimeoutMs(zookeeperParam.getConnectionTimeoutMs())
                .sessionTimeoutMs(zookeeperParam.getSessionTimeoutMs())
                .retryPolicy(policy).build();
        //开启连接
        client.start();
        LOGGER.info("zookeeper 初始化完成...");
    }
    public static CuratorFramework getClient (){
        return client ;
    }
    public static void closeClient (){
        if (client != null){
            client.close();
        }
    }
}

4.4 示例代码

示例代码展示如何在Spring Boot 2项目中使用Zookeeper:

Zookeeper接口类
public interface ZookeeperService {
    /**
     * 判断节点是否存在
     */
    boolean isExistNode (final String path) ;
    /**
     * 创建节点
     */
    void createNode (CreateMode mode,String path ) ;
    /**
     * 设置节点数据
     */
    void setNodeData (String path, String nodeData) ;
    /**
     * 创建节点
     */
    void createNodeAndData (CreateMode mode, String path , String nodeData) ;
    /**
     * 获取节点数据
     */
    String getNodeData (String path) ;
    /**
     * 获取节点下数据
     */
    List<String> getNodeChild (String path) ;
    /**
     * 是否递归删除节点
     */
    void deleteNode (String path,Boolean recursive) ;
    /**
     * 获取读写锁
     */
    InterProcessReadWriteLock getReadWriteLock (String path) ;
}
Zookeeper接口实现类IMPL
@Service
public class ZookeeperServiceImpl implements ZookeeperService {
    private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperServiceImpl.class);
    @Override
    public boolean isExistNode(String path) {
        CuratorFramework client = ZookeeperConfig.getClient();
        client.sync() ;
        try {
            Stat stat = client.checkExists().forPath(path);
            return client.checkExists().forPath(path) != null;
        } catch (Exception e) {
            LOGGER.error("isExistNode error...", e);
            e.printStackTrace();
        }
        return false;
    }
    @Override
    public void createNode(CreateMode mode, String path) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        try {
            // 递归创建所需父节点
            client.create().creatingParentsIfNeeded().withMode(mode).forPath(path);
        } catch (Exception e) {
            LOGGER.error("createNode error...", e);
            e.printStackTrace();
        }
    }
    @Override
    public void setNodeData(String path, String nodeData) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        try {
            // 设置节点数据
            client.setData().forPath(path, nodeData.getBytes("UTF-8"));
        } catch (Exception e) {
            LOGGER.error("setNodeData error...", e);
            e.printStackTrace();
        }
    }
    @Override
    public void createNodeAndData(CreateMode mode, String path, String nodeData) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        try {
            // 创建节点,关联数据
            client.create().creatingParentsIfNeeded().withMode(mode)
                  .forPath(path,nodeData.getBytes("UTF-8"));
        } catch (Exception e) {
            LOGGER.error("createNode error...", e);
            e.printStackTrace();
        }
    }
    @Override
    public String getNodeData(String path) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        try {
            // 数据读取和转换
            byte[] dataByte = client.getData().forPath(path) ;
            String data = new String(dataByte,"UTF-8") ;
            if (StringUtils.isNotEmpty(data)){
                return data ;
            }
        }catch (Exception e) {
            LOGGER.error("getNodeData error...", e);
            e.printStackTrace();
        }
        return null;
    }
    @Override
    public List<String> getNodeChild(String path) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        List<String> nodeChildDataList = new ArrayList<>();
        try {
            // 节点下数据集
            nodeChildDataList = client.getChildren().forPath(path);
        } catch (Exception e) {
            LOGGER.error("getNodeChild error...", e);
            e.printStackTrace();
        }
        return nodeChildDataList;
    }
    @Override
    public void deleteNode(String path, Boolean recursive) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        try {
            if(recursive) {
                // 递归删除节点
                client.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);
            } else {
                // 删除单个节点
                client.delete().guaranteed().forPath(path);
            }
        } catch (Exception e) {
            LOGGER.error("deleteNode error...", e);
            e.printStackTrace();
        }
    }
    @Override
    public InterProcessReadWriteLock getReadWriteLock(String path) {
        CuratorFramework client = ZookeeperConfig.getClient() ;
        // 写锁互斥、读写互斥
        InterProcessReadWriteLock readWriteLock = new InterProcessReadWriteLock(client, path);
        return readWriteLock ;
    }
}
Zookeeper业务API场景使用
@Api("Zookeeper接口使用实例")
@RestController
public class ZookeeperController {
    @Autowired
    private ZookeeperService zookeeperService ;
    @ApiOperation(value="查询节点数据")
    @GetMapping("/getNodeData")
    public HttpResult getNodeData (String path) {
       
        return HttpResult.create(HttpStatus.SUCCESS,zookeeperService.getNodeData(path));
    }
    @ApiOperation(value="判断节点是否存在")
    @GetMapping("/isExistNode")
    public HttpResult isExistNode (final String path){
        
        return HttpResult.create(HttpStatus.SUCCESS,zookeeperService.isExistNode(path));
    }
    @ApiOperation(value="创建节点")
    @GetMapping("/createNode")
    public HttpResult createNode (CreateMode mode, String path ){
        zookeeperService.createNode(mode,path) ;
        return HttpResult.create(HttpStatus.SUCCESS);
    }
    @ApiOperation(value="设置节点数据")
    @GetMapping("/setNodeData")
    public HttpResult setNodeData (String path, String nodeData) {
        zookeeperService.setNodeData(path,nodeData) ;
        return HttpResult.create(HttpStatus.SUCCESS);
    }
    @ApiOperation(value="创建并设置节点数据")
    @GetMapping("/createNodeAndData")
    public HttpResult createNodeAndData (CreateMode mode, String path , String nodeData){
        zookeeperService.createNodeAndData(mode,path,nodeData) ;
        return HttpResult.create(HttpStatus.SUCCESS);
    }
    @ApiOperation(value="递归获取节点数据")
    @GetMapping("/getNodeChild")
    public HttpResult getNodeChild (String path) {
        return HttpResult.create(HttpStatus.SUCCESS,zookeeperService.getNodeChild(path));
    }
    @ApiOperation(value="是否递归删除节点")
    @GetMapping("/deleteNode")
    public HttpResult deleteNode (String path,Boolean recursive) {
        zookeeperService.deleteNode(path,recursive) ;
        return HttpResult.create(HttpStatus.SUCCESS);
    }
}
接口返回HttpResult统一类
import com.fasterxml.jackson.annotation.JsonInclude;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author Lqzhang
 * @date 2020/5/19
 */
@Data
@ApiModel(value = "HttpResult", description = "统一返回数据结构")
public class HttpResult<T> {

    @ApiModelProperty(value = "返回状态码")
    private Integer code;

    @ApiModelProperty(value = "返回信息")
    private String msg;

    @ApiModelProperty(value = "返回数据")
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    private T data;

    public static <E> HttpResult<E> create(HttpStatus httpStatus) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(httpStatus.getCode());
        httpResult.setMsg(httpStatus.getMessage());
        return httpResult;
    }

    public static <E> HttpResult<E> create(HttpStatus httpStatus, String msg) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(httpStatus.getCode());
        httpResult.setMsg(msg);
        return httpResult;
    }

    public static <E> HttpResult<E> create(HttpStatus httpStatus, E data) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(httpStatus.getCode());
        httpResult.setMsg(httpStatus.getMessage());
        httpResult.setData(data);
        return httpResult;
    }

    public static <E> HttpResult<E> create(HttpStatus httpStatus, String msg, E data) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(httpStatus.getCode());
        httpResult.setMsg(msg);
        httpResult.setData(data);
        return httpResult;
    }

    public static <E> HttpResult<E> create(Integer code, String msg, E data) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(code);
        httpResult.setMsg(msg);
        httpResult.setData(data);
        return httpResult;
    }

    public static <E> HttpResult<E> success() {
        return success(null);
    }

    public static <E> HttpResult<E> success(E data) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(200);
        httpResult.setMsg("操作成功");
        httpResult.setData(data);
        return httpResult;
    }

    public static <E> HttpResult<E> fail() {
        return fail(HttpStatus.FAIL.getMessage());
    }

    public static <E> HttpResult<E> fail(String message) {
        HttpResult<E> httpResult = new HttpResult<>();
        httpResult.setCode(HttpStatus.FAIL.getCode());
        httpResult.setMsg(message);
        return httpResult;
    }
}
/**
 * 请求结果状态枚举常量类
 *
 * @author Lqzhang
 */
public enum HttpStatus {

    SUCCESS(200, "请求成功"),

    NO_DATA(201, "没有查询到对应的数据"),

    FAIL(203, "请求异常"),

    PARAM_ERROR(204, "参数名错误或参数为空,请检查"),

    NO_LOGIN(205, "没有授权"),

    SAVE_ERROR(206, "操作失败"),

    NO_DATA_IN_AUTH(207, "权限范围内没有查询到数据"),

    ARREARS(208, "账户可用余额已不足,请充值"),

    UNBOUND_PHONE(210, "用户账户未绑定微信手机"),

    USER_NOT_EXITS(211, "用户不存在"),

    PASSWORD_ERROR(212, "密码错误"),

    TOKEN_EXPIRED(403, "当前登录凭证已失效,请重新登录"),

    SERVICE_NOT_OPENED(215, "该功能未开通,联系管理员开通使用"),;

    /**
     * 状态码
     */
    private int code;
    /**
     * 状态信息
     */
    private String message;

    HttpStatus(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getStatusTypeCode(HttpStatus httpStatus) {
        return httpStatus.getCode();
    }

    public String getStatusTypeMessage(HttpStatus httpStatus) {
        return httpStatus.getMessage();
    }

    public static HttpStatus getStatusTypeByCode(int code) {
        HttpStatus httpStatus = null;
        for (HttpStatus status : values()) {
            if (status.getCode() == code) {
                httpStatus = status;
                break;
            }
        }
        return httpStatus;
    }

    public static HttpStatus getStatusTypeByMessage(String message) {
        HttpStatus httpStatus = null;
        for (HttpStatus status : values()) {
            if (status.getMessage().equals(message)) {
                httpStatus = status;
                break;
            }
        }
        return httpStatus;
    }

}

结论

Zookeeper作为分布式系统中的重要组件,提供了多种功能和强大的协调能力。在实际应用中,可以利用Zookeeper实现分布式锁、统一配置管理、命名服务及集群管理等功能。通过与Spring Boot 2的整合,能更好地在应用中利用Zookeeper这些功能,以提升系统的可用性和可靠性。希望通过本文的介绍,您对Zookeeper有更加深入的了解,并能够在实际项目中加以应用。

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

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

相关文章

深入探讨:UART与USART在单片机中串口的实际应用与实现技巧

单片机&#xff08;Microcontroller Unit, MCU&#xff09;是一种集成了处理器、存储器和输入输出接口的微型计算机。它广泛应用于嵌入式系统中&#xff0c;用于控制各类电子设备。UART和USART是单片机中常见的通信接口&#xff0c;负责串行数据传输。下面我们详细介绍它们在单…

【机器学习系列】Python实战:使用GridSearchCV优化AdaBoost分类器及其基分类器

目录 一、AdaBoost的标准实现中是否支持使用不同类型的基分类器&#xff1f; 二、Adaboost的参数 三、Python实现Adaboost (一)导入库和数据集 &#xff08;二&#xff09; 划分训练集 &#xff08;三&#xff09;选择基分类器--决策树 &#xff08;四&#xff09;创建Ada…

数据结构_栈和队列

目录 一、栈 1.1 栈的使用 1.2 模拟实现栈 二、队列 2.1 队列的使用 2.2 环形队列 2.3 双端队列 总结 一、栈 栈是只允许在固定的一端进行元素的插入和删除操作的一种特殊线性表。其中进行元素的插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈遵循先进后…

MQTT服务器/MQTT_C#客户端/Websoket连MQTT

1 . 搭建MQTT服务器 找到上传中的 emqx-5.3.2-windows-amd64 打开bin如下: 链接: emqx-5.3.2-windows-amd64 如果安装失败 在上传中找到链接: VC_redist.x64.exe 安装。 正确后在浏览器输入 http://127.0.0.1:18083 会有如下mqtt服务端管理页面: 进入客户端认证,创建一个…

72. UE5 RPG 实现召唤技能数量的限制,并优化技能相关

在上一篇文章里&#xff0c;我们实现了召唤技能&#xff0c;并且能够无限的召唤。所以&#xff0c;这属于一个bug&#xff0c;我们不能无限制的去召唤&#xff0c;这会影响游戏的体验。所以&#xff0c;在这篇里面&#xff0c;我们实现一下对召唤物数量的限制&#xff0c;并优化…

32.双击列表启动目标游戏

上一个内容&#xff1a;31.加载配置文件中的游戏到辅助列表 以 31.加载配置文件中的游戏到辅助列表 它的代码为基础进行修改 效果图&#xff1a; 添加列表双击事件 实现代码&#xff1a; LPNMITEMACTIVATE pNMItemActivate reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR…

掌控浮动布局,主宰页面之美

浮动有很多的效果 实现文字环绕效果导致一个元素脱离文档流可以让块级元素水平排列浮动元素可以用 marign, 但是不能使用 margin: 0 auto; 同时也会带来很多不好的效果 如果我想要让文字在图片的旁边应该怎么做&#xff1f; 这里我们就可以使用浮动float: left;去实现 <…

Java | Leetcode Java题解之第168题Excel表列名称

题目&#xff1a; 题解&#xff1a; class Solution {public String convertToTitle(int columnNumber) {StringBuffer sb new StringBuffer();while (columnNumber ! 0) {columnNumber--;sb.append((char)(columnNumber % 26 A));columnNumber / 26;}return sb.reverse().t…

【odoo | JSON-RPC】无会话(session_id)控制的api,外部api密钥的另一种表现!

概要 在Odoo中&#xff0c;JSON-RPC&#xff08;JSON Remote Procedure Call&#xff09;是一种基于JSON格式的远程过程调用协议&#xff0c;用于客户端和服务器之间的通信。此文章将介绍 JSON-RPC中无会话(session_id)控制的api&#xff0c;也是外部api密钥的另一种表现方式。…

【Linux基础IO】重定向以及原理分析

我们先来看下面一个情况&#xff1a; #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define filename "text.txt"int main(){close(1);//关…

差分数组汇总

本文涉及知识点 算法与数据结构汇总 差分数组 令 a[i] ∑ j : 0 i v D i f f [ i ] \sum_{j:0}^{i}vDiff[i] ∑j:0i​vDiff[i] 如果 vDiff[i1]&#xff0c;则a[i1…]全部 如果vDiff[i2]–,则a[i2…]全部–。 令11 < i2 &#xff0c;则&#xff1a; { a [ i ] 不变&…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 身高差值排序(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

重学java 77.JDK新特性 ③ Stream流

The road is long,it can be really hard.Whatever you do,you hold on to that foolishly hopeful smile —— 24.6.19 Stream流 stream流中的"流"不是特指"IO流",它是一种"流式编程"(编程方式),可以看做是"流水线 package S109Stream;im…

在 Visual Studio 2022 (Visual C++ 17) 中使用 Visual Leak Detector

在 Visual C 2022 中使用 Visual Leak Detector 1 问题描述1.1 内存泄漏的困扰和解决之道1.2 内存泄漏检测工具的选择1.3 VLD的现状 2 安装和设置VLD的环境变量2.1 安装VLD文件2.2 VLD安装后的目录和文件说明2.2.1 include子目录说明2.2.2 lib子目录说明2.2.2.1 目录整理 2.2.3…

【全文档】软件项目经理需要掌握的文档有哪些?

软件项目经理在项目管理过程中需要编写多种文档&#xff0c;以下是常见的十五个文档&#xff1a; 项目计划&#xff1a; 详细描述了项目的范围、时间、成本、资源、沟通计划等关键信息&#xff0c;是项目管理的核心文档。 需求文档&#xff1a; 记录了项目的业务需求、功能需求…

VMR,支持30+种编程语言的SDK版本管理器,支持Windows/MacOS/Linux。

官方文档地址&#xff1a;documents 官方项目地址&#xff1a;github 欢迎安装使用&#xff0c;分享转发&#xff0c;前往github star。 跨平台&#xff0c;支持Windows&#xff0c;Linux&#xff0c;MacOS支持多种语言和工具&#xff0c;省心受到lazygit的启发&#xff0c;拥…

【stm32-新建工程-CubeMX】

stm32-新建工程-CubeMX ■ CubeMX 生产工程 ■ CubeMX 生产工程

数据库 |试卷八试卷九试卷十

1.基数是指元组的个数 2.游标机制 3.触发器自动调用 4.count(*)统计所有行&#xff0c;不忽略空值null&#xff0c;但不但要全局扫描&#xff0c;也要对表的每个字段进行扫描&#xff1b; 5.eacherNO INT NOT NULL UNIQUE&#xff0c;为什么不能断定TeacherNO是主码&#xff…

JAVAEE之网络原理(2)_传输控制协议(TCP)的连接管理机制,三次握手、四次挥手,及常见面试题

前言 在上一节中&#xff0c;我们简单介绍了 TCP 协议的相关概念和格式&#xff0c;而且还介绍了TCP 协议原理中的 确认应答机制、超时重传机制&#xff0c;在本节中我们将会继续介绍 TCP协议原理中的其他机制。 连接管理机制&#xff08;安全机制&#xff09; 在正常情况下&…

数学建模基础:统计模型

目录 前言 一、概率与统计基础 二、统计模型 三、Matlab统计工具箱 四、实例示范&#xff1a;市场调查分析 步骤 1&#xff1a;数据导入 步骤 2&#xff1a;数据可视化 步骤 3&#xff1a;建立多元线性回归模型 步骤 4&#xff1a;模型验证 步骤 5&#xff1a;模型应…