分布式锁-01(单节点解决方案)

news2024/11/30 0:37:52

分布式锁概述

为什么需要分布式锁

 在单机部署的系统中,使用线程锁来解决高并发的问题,多线程访问共享变量的问题达到数据一致性,如使用synchornized、 ReentrantLock等。

但是在后端集群部署的系统中,程序在不同的JVM虚拟机中运行, 且因为synchronized或ReentrantLock都只能保证同一个JVM进程中保证有效,所以这时就需要使用分布式锁了。

什么是分布式锁

分布式锁其实就是,控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

 分布式锁的特点:

 分布式锁问题_业务介绍

 技术选型

 创建表

创建订单表

SET NAMES utf8mb4;

SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------

-- Table structure for t_order

-- ----------------------------

DROP TABLE IF EXISTS `t_order`;

CREATE TABLE `t_order`  (

  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

  `order_status` int(1) NULL DEFAULT NULL COMMENT '订单状态 1 待支付 2已支付',

  `receiver_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收货人名字',

  `receiver_mobile` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收货人手机',

  `order_amount` decimal(10, 2) NULL DEFAULT NULL COMMENT '订单价格',

  PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------

-- Records of t_order

-- ----------------------------

SET FOREIGN_KEY_CHECKS = 1;

创建商品表

SET NAMES utf8mb4;

SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------

-- Table structure for product

-- ----------------------------

DROP TABLE IF EXISTS `product`;

CREATE TABLE `product`  (

  `id` int(11) NOT NULL,

  `product_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名字',

  `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格',

  `count` bigint(50) UNSIGNED NULL DEFAULT NULL COMMENT '库存',

  `product_desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品描述',

  `version` int(255) NULL DEFAULT NULL COMMENT '乐观锁',

  PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------

-- Records of product

-- ----------------------------

INSERT INTO `product` VALUES (1001, '拯救者', 100.00, 5, '好用实惠', 1);

SET FOREIGN_KEY_CHECKS = 1;

创建订单商品关联表

SET NAMES utf8mb4;

SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------

-- Table structure for order_item

-- ----------------------------

DROP TABLE IF EXISTS `order_item`;

CREATE TABLE `order_item`  (

  `id` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

  `order_id` VARCHAR(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '订单ID',

  `produce_id` INT(11) NULL DEFAULT NULL COMMENT '商品ID',

  `purchase_price` DECIMAL(10, 2) NULL DEFAULT NULL COMMENT '购买价格',

  `purchase_num` INT(11) NULL DEFAULT NULL COMMENT '购买数量',

  PRIMARY KEY (`id`) USING BTREE

) ENGINE = INNODB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------

-- Records of order_item

-- ----------------------------

INSERT INTO `order_item` VALUES ('1529685823796764675', '1529685823796764674', 1001, 100.00, 1);

INSERT INTO `order_item` VALUES ('1529686091737296898', '1529686091737296897', 1001, 100.00, 1);

INSERT INTO `order_item` VALUES ('1529686091808600067', '1529686091808600066', 1001, 100.00, 1);

INSERT INTO `order_item` VALUES ('1529686091875708931', '1529686091875708930', 1001, 100.00, 1);

INSERT INTO `order_item` VALUES ('1529686092181880835', '1529686092181880834', 1001, 100.00, 1);

INSERT INTO `order_item` VALUES ('1529686092202852354', '1529686092202852353', 1001, 100.00, 1);

SET FOREIGN_KEY_CHECKS = 1;

分布式锁问题_创建SpringBoot项目

加入pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ss.demo</groupId>
    <artifactId>locktest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>locktest</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

修改application.yml:

spring:
  redis:
    host: localhost
    port: 6379
  application:
    name: locktest
  datasource:
    url: jdbc:mysql://localhost:3306/ssou?serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
server:
  port: 9091
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml

使用mybatis-plus工具反向工程操作对`t_order``product``order_item`三张表进行操作,把生成好的文件拷贝到项目中即可

主启动类:

package com.ss.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.ss.demo.mapper")
@SpringBootApplication
public class LocktestApplication {
    public static void main(String[] args) {
        SpringApplication.run(LocktestApplication.class, args);
    }
}

编写创建订单接口

在包com.ss.demo.service下的接口ITOrderService创建订单方法

package com.ss.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.ss.demo.domain.TOrder;

/**
 * <p>
 *  服务类
 * </p>
 */
public interface ITOrderService extends IService<TOrder> {
    /**
     * 创建订单方法
     * @param productId
     * @param count
     * @return
     */
    String createOrder(Integer productId, Integer count);
}

在实现类中TorderServiceImpl中进行实现:

package com.ss.demo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ss.demo.domain.OrderItem;
import com.ss.demo.domain.Product;
import com.ss.demo.domain.TOrder;
import com.ss.demo.mapper.OrderItemMapper;
import com.ss.demo.mapper.ProductMapper;
import com.ss.demo.mapper.TOrderMapper;
import com.ss.demo.service.ITOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
 * <p>
 *  服务实现类
 * </p>
 */
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private TOrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;
    /**
     * 创建订单方法
     * @param productId     商品Id
     * @param count         购买数量
     * @return
     */

@Transactional
    @Override
    public String createOrder(Integer productId, Integer count) {
        //根据商品id获取商品信息
        Product product = productMapper.selectById(productId);
        if(product == null) {
            throw new RuntimeException("购买商品不存在");
        }
        //校验库存
        if(count > product.getCount()) {
            throw new RuntimeException("库存不足");
        }
        //更新库存
        Integer iCount = product.getCount() - count;
        product.setCount(iCount);
        //更新操作
        productMapper.updateById(product);
        //创建订单操作
        TOrder order = new TOrder();
        order.setOrderStatus(1);
        order.setReceiverName("张三");
        order.setReceiverMobile("12345678765");
        //设置订单价格【商品单价*商品数量】
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));
        orderMapper.insert(order);  //插入订单操作
        //创建订单商品表的操作
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());     //订单Id
        orderItem.setProduceId(product.getId());  //商品Id
        orderItem.setPurchasePrice(product.getPrice()); //购买价格
        orderItem.setPurchaseNum(count);   //购买数量
        orderItemMapper.insert(orderItem);
        return order.getId();
    }
}

修改controller包下的类TOrderController

package com.ss.demo.controller;
import com.ss.demo.service.ITOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 */
@RestController
@RequestMapping("/order")
public class TOrderController {
    @Autowired
    private ITOrderService orderService;

    @PostMapping("/order")
    public String createOrder(Integer productId, Integer count) {
        return orderService.createOrder(productId, count);
    }
}

 

使用postmain进行测试:

我们使用过Jmeter进行超卖问题的发现

 

 

启动测试我们看看是否只创建了5个订单,还是出现超卖的问题

 

你会发现在数据库中创建了15个订单,发生了超卖的问题

下面我们先通synchronized锁的方式进行解决

修改service我们加锁:

package com.ss.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ss.demo.domain.OrderItem;
import com.ss.demo.domain.Product;
import com.ss.demo.domain.TOrder;
import com.ss.demo.mapper.OrderItemMapper;
import com.ss.demo.mapper.ProductMapper;
import com.ss.demo.mapper.TOrderMapper;
import com.ss.demo.service.ITOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

/**
 * <p>
 *  服务实现类
 * </p>
 */
@Slf4j
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private TOrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    /**
     * 创建订单方法
     * @param productId     商品Id
     * @param count         购买数量
     * @return
     */
    @Transactional
    @Override
    public synchronized String createOrder(Integer productId, Integer count) {
        //根据商品id获取商品信息
        Product product = productMapper.selectById(productId);
        if(product == null) {
            throw new RuntimeException("购买商品不存在");
        }
        log.info(Thread.currentThread().getName() +"库存数量" + product.getCount());
        //校验库存
        if(count > product.getCount()) {
            throw new RuntimeException("库存不足");
        }
        //更新库存
        Integer iCount = product.getCount() - count;
        product.setCount(iCount);
        //更新操作
        productMapper.updateById(product);
        //创建订单操作
        TOrder order = new TOrder();
        order.setOrderStatus(1);
        order.setReceiverName("张三");
        order.setReceiverMobile("12345678765");
        //设置订单价格【商品单价*商品数量】
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));
        orderMapper.insert(order);  //插入订单操作
        //创建订单商品表的操作
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());     //订单Id
        orderItem.setProduceId(product.getId());  //商品Id
        orderItem.setPurchasePrice(product.getPrice()); //购买价格
        orderItem.setPurchaseNum(count);   //购买数量
        orderItemMapper.insert(orderItem);
        return order.getId();
    }
}

重新启动项目

把表product的库存数量修改为2我们看看会不会出现问题

我们使用Jmeter进行测试

 

 数据库:

 

我们明明创建了2个库存,但是他确帮我们生成了3个订单,具体原因是我们使用synchronized是没有问题的他肯定是线程回一个一个的执行相关方法,比如我们第一个线程执行完成方法的最后一个语句后会立即释放我么的锁,这个时候第二个线程会立刻进入该方法中执行操作,这个时候第一个线程的事物提交操作可能还没有完成那么我们进行到该方法的中第二个线程查询的数据还是2,我们访问该方法的线程越多,库存数量越大,并发产生的问题就越大

那么我们该怎么解决呢,具体的想法是我们的锁结束我们的这个线程的操作的事物也应该结束才是最佳方案

所以这个时候我们要选择手动的提交事物操作才可以

Spring进行了统一的抽象,形成了 PlatformTransactionManager事务管理器接口 , 事务的 提交、回滚等操作 全部交给它来实现。

事务功能的总体接口设计

三个接口功能一句话总的来说事务管理器基于事务基础信息在操作 事务时候对事务状态进行更新。

PlatformTransactionManager : 事务管理器

TransactionDefinition : 事务的一些基础信息,如超时时间、隔离级别、传播属性等

TransactionStatus : 事务的一些状态信息,如是否是一个新的事务、是否已被标记为回滚

修改service方法:

package com.ss.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ss.demo.domain.OrderItem;
import com.ss.demo.domain.Product;
import com.ss.demo.domain.TOrder;
import com.ss.demo.mapper.OrderItemMapper;
import com.ss.demo.mapper.ProductMapper;
import com.ss.demo.mapper.TOrderMapper;
import com.ss.demo.service.ITOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

/**
 * <p>
 *  服务实现类
 * </p>
 */
@Slf4j
@Service
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrder> implements ITOrderService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private TOrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;


    @Autowired
    private PlatformTransactionManager platformTransactionManager;   //事物管理器接口

    @Autowired
    private TransactionDefinition transactionDefinition;

    /**
     * 创建订单方法
     * @param productId     商品Id
     * @param count         购买数量
     * @return
     */
    //@Transactional     //这里要注释掉
    @Override
    public synchronized String createOrder(Integer productId, Integer count) {
        //创建了事物
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
        //根据商品id获取商品信息
        Product product = productMapper.selectById(productId);
        if(product == null) {
            platformTransactionManager.rollback(transaction);   //回滚事物
            throw new RuntimeException("购买商品不存在");
        }
        log.info(Thread.currentThread().getName() +"库存数量" + product.getCount());
        //校验库存
        if(count > product.getCount()) {
            platformTransactionManager.rollback(transaction);   //回滚事物
            throw new RuntimeException("库存不足");
        }
        //更新库存
        Integer iCount = product.getCount() - count;
        product.setCount(iCount);
        //更新操作
        productMapper.updateById(product);
        //创建订单操作
        TOrder order = new TOrder();
        order.setOrderStatus(1);
        order.setReceiverName("张三");
        order.setReceiverMobile("12345678765");
        //设置订单价格【商品单价*商品数量】
        order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));
        orderMapper.insert(order);  //插入订单操作
        //创建订单商品表的操作
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());     //订单Id
        orderItem.setProduceId(product.getId());  //商品Id
        orderItem.setPurchasePrice(product.getPrice()); //购买价格
        orderItem.setPurchaseNum(count);   //购买数量
        orderItemMapper.insert(orderItem);
        platformTransactionManager.commit(transaction);    //提交事物
        return order.getId();
    }
}

启动项目再次测试

数据库复原为,库存为2

使用Jmeter测试

 

没毛病,以上单节点是绝对没有问题的,但是分布式就不是了

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

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

相关文章

可调整界面输出的桌面万年历设计

可调整界面输出的桌面万年历设计 本文主要介绍月历和生辰八字五行的界面输出方法。一个有趣的方法是可调整界面输出格式&#xff0c;显示几种屏幕排版的布局。本文示例了四个式样。算法的精髓是用一种简单的算法来设置调节屏幕打印输出。分三个显示内容&#xff0c;即月历、大字…

Docker入门实战---修改Docker镜像源

前言 现在大部分互联网公司在实施项目时几乎都会以微服务架构进行落地&#xff0c;那么微服务一旦多了之后就会面临一个如何友好的治理的问题&#xff0c;本人不会重点介绍治理的问题&#xff0c;而是会简单就治理的其中一个环节服务部署运维的问题进行介绍&#xff0c;服务部…

排序算法之桶排序

一、桶排序&#xff08;BucketSort&#xff09; 桶排序&#xff08;Bucket sort&#xff09;或所谓的箱排序&#xff0c;是一个排序算法&#xff0c;工作的原理是将数组分到有限数量的桶里。每个桶再个别排序&#xff08;有可能再使用别的排序算法或是以递归方式继续使用桶排序…

[论文阅读] (28)李沐老师视频学习——1.研究的艺术·跟读者建立联系

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

如何成功申请计算机软件著作权【流程完整记录】

致谢 &#xff1a;此博文的编写包括软著的申请&#xff0c;均借鉴了大佬【万里守约】的博客https://blog.csdn.net/qq_45625499/article/details/123463407 提示&#xff1a;此博文仅适合个人申请&#xff0c;因为我是自己一个人的项目&#xff0c;自己一个人申请软著 文章目录…

2023 Android开发者路线-第一部分

2023 Android开发者路线-第一部分 Android 生态系统处于不断发展的状态&#xff1a;每天都会引入新的库和资料&#xff0c;旨在加快开发速度并让我们作为开发人员的生活更轻松。 在这个由多个部分组成的系列中&#xff0c;您将按照我们的2023 年 Android 开发者路线图了解有关…

pyhton GUI编程之Tkinter美化皮肤ttkbootstrap

文章目录 pyhton GUI编程之Tkinter美化皮肤ttkbootstrap介绍 pyhton GUI编程之Tkinter美化皮肤ttkbootstrap介绍 tkinter 相对简单&#xff0c;学习入门很快&#xff0c;但是做出来的GUI界面不够美观&#xff0c;各个组件的外观都很老土&#xff0c;所谓 " 爱美之心&#…

发现一个国产BI软件,做财务数据分析效果绝了

如果是一般的财务数据分析&#xff0c;BI软件们都能做&#xff0c;但如果真要深入了解财务痛点&#xff0c;逐个击破财务数据分析难点&#xff0c;实现多维立体自助式的财务数据分析&#xff0c;那就难。就目前而言&#xff0c;财务数据分析做得好的国产BI软件也就一个奥威BI软…

使用docker构建ElasticSearch集群

目录 一、准备工作 二、编写docker-compose.yml 三、编写ElasticSearch和kibana的配置文件 四、执行构建ElasticSearch集群 五、验证结果&#xff1a; 六、可视化工具 ElasticSearch可视化工具介绍&#xff08;elasticsearch-head、kibana、elasticHD&#xff09; 一、e…

CTF权威指南 笔记 -第四章Linux安全机制-4.1-Stack Canaries

目录 Stack Canaries 简介 我们进行简单的例子 64 32 checksec Stack Canaries 是对抗栈溢出攻击的技术 SSP安全机制 Canary 的值 栈上的一个随机数 在程序启动时 随机生成并且保存在比返回地址更低值 栈溢出是从低地址向高地址进行溢出 如果攻击者要攻击 就一定要覆…

电动力学专题:圆柱形导体中趋肤效应

电动力学分析 金属导体内的电流密度方程 由Maxwell方程组导出Helmhltz方程 对于良导体,有\sigma/(\omega \eprsilon),因此有 圆柱形导线中电流密度分布 设电流沿Z轴方向流动,均匀导体,可简化为 通解&#xff1a; 安培环路定理 定态电磁波的Maxwell方程组 贝塞尔函数性质&…

【SQL】作为前端,应该了解的SQL知识(第三弹)

&#x1f4d1;视图 使用表时&#xff0c;会将数据保存在存储设备&#xff08;硬盘上&#xff09; 而使用视图时&#xff0c;并不会将数据保存在存储设备上&#xff0c;也不会将数据保存在任何地方。 视图里面保存的是 从表中取出数据所使用的SELECT语句&#xff08;视图中的…

zhangrelay博客置顶三篇点击量分析

230515只有三篇置顶&#xff0c;如下&#xff1a; 分别为&#xff1a; 20.03.13 &#xff1a; 901522.01.12 &#xff1a;1372923.04.15 &#xff1a;18836 熟悉zhangrelay博客风格的AI都清楚&#xff0c;他的博客内容都是筛选和设计过的。 置顶三篇阅读量差值为&#xff1…

C++--AVL树的插入,详解四种旋转规则(结尾附源代码链接)

AVL树的插入 前言左单旋右单旋左右双旋右左双旋检查是否这颗树是否是AVL树 前言 AVL树可以说是对二叉搜索树的优化&#xff0c;我们来看二叉树搜索树的下一面一种特殊情况&#xff1a; 当我们插入的数是上面的情况时&#xff0c;二叉树搜索树的特点就形同虚设了&#xff0c;这…

ChatGpt 2步制作流程图与思维导图,你确定不来看一下吗?

什么&#xff1f;你还不会使用ChatGpt。推荐下面这篇文章 ChatGPT保姆级教程&#xff0c;一分钟学会使用ChatGPT&#xff01; - 掘金 (juejin.cn) 如果没有谷歌账号推荐直接买一个&#xff0c;因为你在中国注册谷歌账号&#xff0c;被谷歌查到&#xff0c;也是使用不了ChatGp…

企业数字化转型过程中面临最大的挑战和问题是什么?

无论组织规模如何&#xff0c;业务的敏捷性、弹性以及生产力的高低都是决定其发展运营成功与否的关键因素。而一个良好的数字化转型战略则是企业发展进步的有力助推器。 麦肯锡称&#xff0c;借助数字化转型&#xff0c;可以实现 20% 至 50% 的经济收益和 20% 至 30% 的客户满…

【Spring Cloud Alibaba】Nacos的安装与介绍以及Nacos集群的安装

欢迎来到 Nacos 的世界&#xff01; Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性…

shell构建基本脚本

构建基本脚本 使用多个命令 一次使用多个命令&#xff0c;把它们放在一行&#xff0c;使用’;隔开 [rootmyserver ~]# date ; who; ls Sun May 14 23:39:34 CST 2023 root pts/0 2023-05-14 23:31 (192.168.10.1) anaconda-ks.cfg initial-setup-ks.cfg创建shel…

JavaScript学习-DOM事件进阶

事件流 事件流和两个阶段说明 事件流指的是事件完整执行过程中的流动路径 说明:假设页面里有个div&#xff0c;当触发事件时&#xff0c;会经历两个阶段&#xff0c;分别是捕获阶段、冒泡阶段 简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父 或者说从大的往下的是捕获&am…

阿里云服务器如何安装宝塔面板?

使用阿里云服务器安装宝塔面板教程&#xff0c;阿里云服务器网以CentOS操作系统为例&#xff0c;安装宝塔Linux面板&#xff0c;先远程连接到云服务器&#xff0c;然后执行宝塔面板安装命令&#xff0c;系统会自动安装宝塔面板&#xff0c;安装完成后会返回面板地址、账号和密码…