Redis实现分布式锁原理和Redisson框架实现分布式锁,全网最详细讲解

news2024/12/24 22:11:07

声明:我的大部分篇幅都讲的分布式锁的原理和实现,如果想直接用Redisson框架实现分布式锁,可以直接翻至最后面

关于分布式锁,适用于并发量特别大的微服务集群,能做到同步的实现资源的获取

我其实没有经过真实项目的分布式锁的实践,以下的作为我学习的参考,但据我了解一般使用redis作为分布式锁的公司具体实现也如我下述的redisson框架,只要不是像淘宝、京东那样并发量特别高的项目都基本适用,如果以后有机会使用到了分布式锁的应该场景我也会更新本文

我会从分布式锁的原理解析、代码、框架一一解析,本文解析代码部分仅供参考

需要用到的知识点(必须会用

一、JMeter用于做压力测试: jmeter安装与使用,全图文讲解

二、Nginx用于做负载均衡(多服务):Windows安装Nginx并配置负载均衡

三、Redis用于存储Mock数据:Windows安装Redis做到双击启动

四、启动两个相同服务但端口不同的项目:idea实现同时启动两个相同服务但不同端口的项目,全图解

五、redis工具类:Redis工具类(redisTemplate)以及 redisTemplate 的用法

图解分布式锁(秒杀场景)

图解为什么需要用到分布式锁(我都忘记画返回用户的数据操作了,但不影响,知道最后需要返还给用户消息就可以了)

场景一、单服务不需要分布式锁(为了省事,我就只画两个用户)

1、不加锁就会造成数据的脏读

在这里插入图片描述

2、使用synchronized实现加锁处理

在这里插入图片描述

场景二、集群(分布式锁实现)(多个相同服务但端口不同的项目,经过负载均衡到不同的服务)(为了省事,我就只画两个服务)

1、只在服务层面加锁是达不到效果的

在这里插入图片描述

2、需要用到redis的setnx请求

原理

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

如上面代码所示setnx请求是指,先查找查找是否有mykey这个key,如果没有则放入一个Hellovalue并返回 1(就是表名插入成功),如果已经存在mykey这个key则返回 0(表示插入失败)

由于redis是单线程的所有在redis方法会让进来的请求进行排队,下面用户一的请求比用户二的请求快一丢丢访问redis,实现分布式锁

  • 用户一在服务一使用setnx这个方法插入一个key返回成功,并表示用户一拿到分布式锁

  • 同一时间用户二在服务二使用setnx这个方法插入一个相同的key,这时提示插入失败,返回失败,然后服务二就自旋拿锁或者直接返回用户稍后重试(直接返回用户不友好)

  • 之后用户一处理完请求,修改redis数据,最后把key删除掉,这样其他的服务就可以拿到锁了,就可以使用setnx命令尝试加锁,拿到锁后操作redis的数据,拿不到锁的执行上一个步骤(这里不是有三个点吗,就是执行第二个点后面的内容)

在这里插入图片描述

代码解析分布式锁原理(秒杀场景)

初始化Redis数据(用于存储秒杀使用的商品)

1、使用redis配置一个用于秒杀服务的mock数据,我这里设置key为goods,value为50,每次使用完数据,需要重新让goods数据变为50(工具:Another Redis Desktop Manager)

在这里插入图片描述

场景一、单服务下的秒杀实现(加锁)(不需要分布式锁)(对应图解场景一)

SpringBoot作为实现分布式的基本框架,只跑单个服务的时候,使用的是一个jvm来控制代码。我们假设一个秒杀的项目实现:

1、写一个接口用于测试秒杀

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
        try {
//            取goods的值
            Integer goods = (Integer) redisUtil.get("goods");
            if (goods <= 0){
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "商品已经取完");
                return "商品已经取完";
            }
//            这里模拟一下延时 0.1秒 (因为数据量太少,这样可以很直观的看出加锁和不加锁的区别)
            Thread.sleep(100);
//            用户拿到了这个商品,所以这个商品需要自减一
//            使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
            System.err.println(Thread.currentThread().getName() +
                    Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//            由于用户已经取到了商品,所以redis中的数据也需要更新
            redisUtil.set("goods",goods);
        } catch (InterruptedException e) {
            return "错误";
        }
        return "你已经成功获取商品";
    }
}

2、配置JMeter

一瞬间有20个用户去抢goods这个商品

在这里插入图片描述

在这里插入图片描述

3、测试

按理说,每个用户只抢一个商品,那么会剩余30个商品,我们来看看下述情况,发现全部的用户都拿到的是第50这个数据,所以减1操作,后都在redis中存储的是49,就造成了数据的脏读,而修改此处代码非常的简单

控制台:

在这里插入图片描述

redis:

在这里插入图片描述

4、解决单服务下的脏读问题(加锁),记得修改redis中的goods数据至50,还是请求20次

只需要修改代码,加入锁就可以了,这样就实现了基于jvm层面的加锁了

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
        try {
//            加锁
            synchronized (this){

//            取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }
//            这里模拟一下延时 0.1秒 (因为数据量太少,这样可以很直观的看出加锁和不加锁的区别)
                Thread.sleep(100);
//            用户拿到了这个商品,所以这个商品需要自减一
//            使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//            由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
            }
        } catch (InterruptedException e) {
            return "错误";
        }
        return "你已经成功获取商品";
    }
}

5、测试

控制台:

在这里插入图片描述

redis:

在这里插入图片描述

场景二、集群下的秒杀实现(分布式锁实现)(对应图解场景二)

1、启动Nginx,配置负载均衡,并启动两个服务(8080端口、9090端口),测试 场景一 4 的代码会出现怎样的错误,记得修改redis中的goods数据至50(以下操作不知道怎么处理的,可以看需要用到的知识点)

Nginx配置(nginx.conf),并启动:

worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
	# 负载均衡配置访问路径 serverList名字随便取
	upstream serverList{
	   # 这个是tomcat的访问路径
	   server localhost:8080;
	   server localhost:9090;
	}
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
			proxy_pass http://serverList;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

重新配置JMeter:

让两个服务分别处理25个请求

在这里插入图片描述

在这里插入图片描述

两个项目启动,并清空控制台:

在这里插入图片描述

2、测试

后面有很多两个项目取出了相同的商品,我这里就不拉开展示了,知道有问题就好了

8080端口:

在这里插入图片描述

9090端口:

在这里插入图片描述

redis:并不是我们想象的0,所以使用单个jvm下面的加锁是行不通的

在这里插入图片描述

3、修改代码,实现加redis分布式锁

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
        try {
//            自旋
            while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁
                boolean lock = redisUtil.setnx("lock", "先随便输入什么东西都可以啦~");
                if (!lock){
                    continue;
                }

//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }
//               这里加了锁就不需要
//                Thread.sleep(100);

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
//            解锁
                redisUtil.delete("lock");
                return "你已经成功获取商品";
            }
        } catch (Exception e) {
            return "错误";
        }
    }
}

4、测试,记得修改redis中的goods数据至50

8080端口:

在这里插入图片描述

9090端口:

在这里插入图片描述

redis:

在这里插入图片描述

一个redis分布式锁的基础已经完成了,但是这样实现的分布式锁有很多的问题,在高并发的条件下根本不够看,下面是进阶教学(以下方案均经过测试)

问题一、在解锁之前出现异常,导致不能解锁,那么其他的服务都不可以访问redis

在这里插入图片描述

解决:修改解锁的代码至finally代码块中

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
//            自旋
        while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁
                boolean lock = redisUtil.setnx("lock", "先随便输入什么东西都可以啦~");
                if (!lock){
                    continue;
                }

            try {
//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
                return "你已经成功获取商品";
            } catch (Exception e) {
                e.printStackTrace();
                return "请重试";
            } finally {
//            解锁
                redisUtil.delete("lock");
            }
        }
    }
}

问题二、如果在执行到解锁之前,服务直接挂掉了,那么其他的服务都不可以访问redis

解决:设置缓存时间(10s)

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
//            自旋
        while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁,并设置缓存时间 10秒
                boolean lock = redisUtil.setnx("lock", "先随便输入什么东西都可以啦~",10, TimeUnit.SECONDS);
                if (!lock){
                    continue;
                }

            try {
//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
                return "你已经成功获取商品";
            } catch (Exception e) {
                e.printStackTrace();
                return "请重试";
            } finally {
//            解锁
                redisUtil.delete("lock");
            }
        }
    }
}

问题三、假如第一个服务跑了11秒,但10秒后,然后锁的时间到了,那么第二个服务就可以拿到锁并访问redis了,这样在第二个服务期间,第一个服务完成了,那么第一个服务会释放掉第二个服务的锁,这样就导致了锁的失效问题

解决一:设置一个唯一标识(uuid),删除的时候判断是不是该请求设置的锁就可以了,但是,这里有个问题,就是10秒后,虽然第一个服务不可以删除除自己以外的锁,但是有其他的服务拿到这把锁进行redis操作,如果第一个服务在第二个服务修改redis数据之后再去修改redis,那么第三个服务拿到redis的数据就是有问题的,这样也会导致锁失效问题

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;

    @RequestMapping("/demo")
    public String demo(){
//        设置自旋超时时间
        long timeoutAt = System.currentTimeMillis();
//        设置唯一标识
        String uuid = UUID.randomUUID().toString();
//            自旋
        while (true){
                long now = System.currentTimeMillis();
//                5秒超时,退出
                if (now - timeoutAt > 5000){
                    System.out.println("连接超时请重试");
                    return "连接超时请重试";
                }
//               加锁,设置超时时间和唯一标识
                boolean lock = redisUtil.setnx("lock", uuid,10, TimeUnit.SECONDS);
                if (!lock){
                    continue;
                }

            try {
//               取goods的值
                Integer goods = (Integer) redisUtil.get("goods");
                if (goods <= 0){
                    System.err.println(Thread.currentThread().getName() +
                            Thread.currentThread().getId() + "商品已经取完");
                    return "商品已经取完";
                }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
                redisUtil.set("goods",goods);
                return "你已经成功获取商品";
            } catch (Exception e) {
                e.printStackTrace();
                return "请重试";
            } finally {
                if (uuid.equals(redisUtil.get("lock"))){
//            解锁
                    redisUtil.delete("lock");
                }
            }
        }
    }
}

解决二:设置心跳检测,可以去看看这篇Redis分布式锁如何解决锁超时问题?

问题四、串行太慢了怎么办

解决:拆分:使用不同的锁和不同的关键字,比如goods为50个商品,可以拆分为:goods_1:10,goods_2:10,goods_3:10,goods_4:10,goods_5:10

问题五、使用Redis集群时,主节点挂了,而子节点刚好没有同步到刚刚上传的key,导致锁失效

解决:使用zookeeper实现分布式锁

等等等等。。。。。。

使用Redisson框架实现分布式锁

Redisson原理图

在这里插入图片描述

加入jar包

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.20.0</version>
</dependency>

配置Bean

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redisson(){
        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0).setPassword("123456");
        // 创建RedissonClient对象
        return Redisson.create(config);
    }
}

测试(别看代码少,其实这个是经过多年实战,很有保障的)

@RestController
public class DemoController {
    @Autowired
    private RedisUtil redisUtil;
    @Resource
    private Redisson redisson;

    @RequestMapping("/test")
    public String Test(){
        RLock lock = redisson.getLock("lock");
//        设置超时时间30秒
        lock.lock(30,TimeUnit.SECONDS);
        try {
//               取goods的值
            Integer goods = (Integer) redisUtil.get("goods");
            if (goods <= 0){
                System.err.println(Thread.currentThread().getName() +
                        Thread.currentThread().getId() + "商品已经取完");
                return "商品已经取完";
            }

//               用户拿到了这个商品,所以这个商品需要自减一
//               使用System.err输出一下数据,这样显示控制台输出是红色比较好观察
            System.err.println(Thread.currentThread().getName() +
                    Thread.currentThread().getId() + "取出了第" + goods-- + "商品");
//               由于用户已经取到了商品,所以redis中的数据也需要更新
            redisUtil.set("goods",goods);
            return "你已经成功获取商品";
        } catch (Exception e) {
            e.printStackTrace();
            return "请重试";
        }finally {
            lock.unlock();
        }

    }
}

8080端口:

在这里插入图片描述

9090端口:

在这里插入图片描述

redis:

在这里插入图片描述

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

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

相关文章

MySQL调优笔记——慢SQL优化记录(1)

上周&#xff0c;项目出现线上问题&#xff0c;在这家公司做的是一个SAAS平台&#xff0c;总用户量大约10万人&#xff1b; 经过排查&#xff0c;发现是SQL问题&#xff0c;导致数据库响应慢&#xff0c;进而拖垮了整体服务&#xff1b; 通常&#xff0c;查询耗时较长的SQL涉…

Java优先级队列-堆

Java优先级队列-堆 &#x1f490;1. 二叉树的顺序存储&#x1f490;&#x1f383; 1.1 存储方式&#x1f383;&#x1f47b;1.2 下标关系&#x1f47b; &#x1f338;2. 堆(heap)&#x1f338;&#x1f31e;2.1 概念&#x1f31e;&#x1f31d;2.2 操作-向下调整&#x1f31d;&…

SER | 语音情绪识别中的TIM-NET_SER项目复现

大家好&#xff0c;今天复现的是目前语音情绪识别的SOTA论文&#xff0c;论文中文名称是 时间建模的重要性&#xff1a; 用于语音情感识别的新型时空情感建模方法 。论文中训练的数据集有英文德语等几个语音情绪识别中常见的语音情绪数据集&#xff0c;以对比精度权重等效果~各…

Android 下一代架构指南:DDD

移动端架构与网站架构的区别是什么&#xff1f;网易新闻客户端的架构演进历程是怎样的&#xff1f;为什么要选择 DDD 思想来指导重构&#xff1f;DDD 落地中应当关注哪些方面&#xff1f;带着这些问题我们来看下文。&#xff08;节选自网易新闻App架构重构实践&#xff09; 当…

Kafka吞吐量

目录 kafka的架构和流程 小文件对HDFS影响&#xff1a; 解决办法&#xff1a; kafka的架构和流程 ⾸先Kafka从架构上说分为⽣产者Broker和消费者,每⼀块都进⾏了单独的优化,⽐如⽣产者快是因为数据的批量发送&#xff0c;Broker快是因为分区,分区解决了并发度的问题,⽽且⽂…

媒体宣传的优势与重要性

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体宣传日益成为企业和品牌宣传推广的重要手段&#xff0c;媒体的宣传报道更有权威性&#xff0c;能够帮助品牌进行背书&#xff0c;更有权威性&#xff0c;另外媒体的报道在搜索引擎中…

基于GPS/北斗卫星技术的无盲区车辆调度系统

基于GPS/北斗卫星技术的无盲区车辆调度系统 现代车辆调度系统是一种集全球卫星定位技术&#xff08;GPS&#xff09;、地理信息技术&#xff08;GIS&#xff09;和现代通信技术于一体的高科技项目。它将移动目标的动态位置&#xff08;经度与纬度&#xff09;、时间和状态等信息…

linux环境搭建jmeter、ant、git、Jenkins、jdk、Tomcat

我在搭建环境时&#xff0c;将jmeter、ant、jdk、Tomcat都放在陆opt文件夹下 1.下载jmeter、ant、Jenkins&#xff08;Jenkins.war包&#xff09;、jdk、Tomcat Linux环境下安装Jenkins&#xff0c;需要jdk版本大于11 2.环境配置 jdk配置 vim /etc/profile 添加配置信息&am…

飞书接入ChatGPT - 将ChatGPT集成到飞书机器人,直接拉满效率 【飞书ChatGPT机器人】

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话,在下面操作步骤中,使…

LBS找外贸客户 外贸怎么找客户

随着全球贸易的不断发展&#xff0c;越来越多的企业开始寻找更多的客户和销售机会。而随着移动互联网的普及&#xff0c;LBS已经成为了人们生活和工作中不可或缺的一部分。在商业领域中&#xff0c;LBS被广泛应用于定位、导航、营销等方面&#xff0c;为企业提供了更加便捷、精…

如何在Mac VM Fusion上安装和使用Plan 9

我在 Mac 上使用 VM Fusion 安装 Plan 9 的时候遇到了很多问题&#xff0c;官方文档和有些前两年的国外的一些博客并没有写清楚&#xff0c;甚至出现了“误导”的情况&#xff08;有些情况变了&#xff09;。所以来写本文帮助其他也遇到的问题的人。 如果你能看到这篇博客&…

上传ChatGPT相关资源,瓜分¥5000元奖金池

一、活动时间 资源类型时间上传地址上传【ChatGPT的原理分析】资源4月17日-4月30日https://upload.csdn.net/creation/uploadResources?taskId643925fde212675bb64a3984&utm_sourceblog上传【Chatgpt的多种使用方法】资源4月15日-4月30日https://upload.csdn.net/creatio…

介绍NPOI 的颜色卡、名称以及索引

文章目录 前言 遍历NPOI颜色 前言 使用NPOI的颜色时&#xff0c;一些颜色类的名称很难想象出具体对应的颜色&#xff0c;所以有了下面的对照表&#xff0c;方便使用。 NPOI 颜色的索引范围是 8~64,超出范围无效。 色彩类名索引Index名称#000000HSSFColor.Black8黑色#ffffffH…

【C++ 一】C++ 入门、数据类型、运算符

C 入门、数据类型、运算符 文章目录 C 入门、数据类型、运算符前言1 C 初识1.1 第一个C程序1.1.1 创建项目1.1.2 创建文件1.1.3 编写代码1.1.4 运行程序 1.2 注释1.3 变量1.4 常量1.5 关键字1.6 标识符命名规则 2 数据类型2.1 整型2.2 sizeof 关键字2.3 实型&#xff08;浮点型…

【最详细最完整】windows 安装 Oracle Java环境

windows 安装Oracle Java环境 一、安装教程二、验证Java环境 前言&#xff1a;公司有个app的项目&#xff0c;我是打算使用uniapp来实现&#xff0c;那么调试是需要使用到java环境&#xff0c;所以我本地就得安装java环境&#xff0c;接着我找了好多文章发现没有相对完整的&…

在头部大厂做了13年云计算后,这次他想系统地聊聊FinOps!

随着企业上云战略的深入普及&#xff0c;越来越多的企业开始关注云成本优化。伴随着企业对IT资源的投入不断增加&#xff0c;企业迫切需要解决成本与效率&#xff0c;以及如何将云成本优化落到实处的问题。 FinOps是将财务和业务整合到一起的变革&#xff0c;可以帮助企业更好…

抖音数字人主播app

抖音数字人主播app是指一款利用计算机生成的虚拟数字人&#xff0c;在抖音平台上进行实时音视频传输和互动的应用程序。该软件可以让用户创建自己的虚拟数字人&#xff0c;并在抖音平台上进行实时互动和交流。 抖音数字人主播app通常需要包含以下功能&#xff1a; 3D建…

本地JAR打镜像,并启动

1.准备好jar&#xff0c;和Dokerfile文件。 2.使用命令打镜像 docker build -t wstest . 3. 查看镜像 4. 由于服务是两个端口。使用以下命令 5.优化怎么随着docker的开启而启动 docker run --restartalways -p 8089:8089 -p 8069:8069 wsserver docker run --restartalways -…

C++基础入门——语法详解篇(下)

文章目录 一、缺省参数 1、1 缺省参数的概念 1、2 缺省参数的分类 1、2、1 全部缺省 1、2、2 半缺省参数 二、引用 2、1 引用的概念 2、2 引用特征 2、3 引用的使用场景 2、3、1 引用做参数 2、3、2 常引用 2、3、3 引用做返回值 2、4 引用总结 三、内联函数 3、1 内联函数的引…

谷歌浏览器的跨域设置、配置、新老版本Chrome

文章目录 1、个人开发中的使用习惯2、老版本Chrome浏览器(版本号49之前)3、新版本Chrome浏览器(版本号49之后) 1、个人开发中的使用习惯 下载好谷歌浏览器以后&#xff0c;快捷方式一份放在桌面上&#xff0c;一份放在开始菜单栏&#xff0c;桌面的重命名为dev(可以随意命名)&a…