项目实战 — 消息队列(6){内存数据管理}

news2024/11/26 9:52:40

目录

一、设计数据结构

二 、实现管理方法

🍅 1、实现交换机管理

🍅 2、实现队列管理

🍅 3、实现绑定管理

        🎈插入绑定操作

        🎈删除绑定

🍅 4、进行消息管理

🍅 5、发送消息到指定队列

🍅 6、表示“未被确认”的消息管理

🍅 7、从硬盘上读取数据

三、测试交换机操作

🍅 1、“准备工作”和收尾工作

🍅 2、测试 交换机

🍅 3、测试 队列

🍅 4、测试 绑定

🍅 5、测试 消息的增删查

🍅 6、测试 发送消息

🍅 7、测试 “未确认”的信息 

 🍅 8、测试 从硬盘上读取数据

四、小结


一、设计数据结构

对于MQ来说,主要是以内存存储数据为主,硬盘存储数据为辅。

关于内存数据管理,作出如下的数据结构:

交换机:使用HashMap,key是name, value是Exchange对象

队列:使用HashMap,key是name,value是MSGQueue对象

绑定:使用嵌套的HashMap,key是exchangeName,value是一个HashMap(其中key是queueName,value是Binding对象)

消息:使用HashMap,key是messageId,value是Message

表示队列和消息之间的关联:使用嵌套的HashMap,key是queueName,value是一个LinkedList。LinkedList中每个元素又是一个Message对象。

表示“未被确认”的消息:关于未被确认:存储了当前队列中哪些消息被消费者取走了,但是还没有应答。使用嵌套的HashMap,key是queueName,value是HashMap(其中key是messageId,value是Message对象)。后续实现消息确认的逻辑,需要根据ack响应的内容,会提供一个messageId,根据该messageId把结构中的Message对象找到并且移除

这里有两种应答模式(ACK):

 1.自动应答,消费者取了元素,该消息就算是被应答,就可以被删除了

 2.手动应答,消费者取了元素,该消息还不断被应答, 需要消费者主动再调用一个basicAck方法,此时才被认为是真的应答了,才能删除这个消息。

 

由于, 这个类会涉及到多线程的请求,所以这里的HashMap都统一使用ConcurrentHashMap,因为HashMap是线程不安全的,ConcurrentHashMap相对而来线程安全,所以上面说的使用HashMap都使用ConcurrentHashMap

public class MemoryDataCenter {
//    交换机:key是exchangeMame,value是exchange对象
    private ConcurrentHashMap<String, Exchange> exchangeMap = new ConcurrentHashMap<>();
//    队列:key表示queueName,value表示MSGQueue对象
    private ConcurrentHashMap<String, MSGQueue> queueMap = new ConcurrentHashMap<>();
//    绑定:第一个key表示exchangeName,第二个key表示queueName,value都表示Binding对象
    private ConcurrentHashMap<String,ConcurrentHashMap<String, Binding>> bindingsMap = new ConcurrentHashMap<>();
//    消息:key是messageId,value是Message对象
    private ConcurrentHashMap<String, Message> messageMap = new ConcurrentHashMap<>();
//    表示队列和消息之间的关联:key表示queueName,value表示一个Message链表,里面存放的是Message对象
    private ConcurrentHashMap<String, LinkedList<Message>> queueMessageMap = new ConcurrentHashMap<>();
//  表示“未被确认”的消息:第一个key表示queueName,第二个key表示messageId,value表示Message对象
    private ConcurrentHashMap<String,ConcurrentHashMap<String,Message>> queueMessageWaitAckMap = new ConcurrentHashMap<>();
}


二 、实现管理方法

🍅 1、实现交换机管理

 public void insertExchange(Exchange exchange){
        exchangeMap.put(exchange.getName(),exchange);
        System.out.println("[MemoryDataCenter]新交换机添加成功!exchangeName = " + exchange.getName());
    }

    public Exchange getExchange(String exchangeName){
        return exchangeMap.get(exchangeName);
    }

    public void deleteExchange(String exchangeName){
        exchangeMap.remove(exchangeName);
        System.out.println("[MemoryDataCenter]交换机删除成功! exchangeName = " + exchangeName);
    }


🍅 2、实现队列管理

  public void insertQueue(MSGQueue queue){
        queueMap.put(queue.getName(),queue);
        System.out.println("[MemoryDataCenter]队列删除成功!queueName = " + queue.getName());
    }

    public MSGQueue getQueue(String queueName){
        return queueMap.get(queueName);
    }

    public void deleteQueue(String queueName){
        queueMap.remove(queueName);
        System.out.println("[MemoryDataCenter]删除队列成功!queueName = " + queueName);
    }


🍅 3、实现绑定管理

        🎈插入绑定操作

注意点:

(1)这里是嵌套的HashMap,所以再插入钱,要先使用exchangeName查找对应的哈希表是否存在,不存在就创建。

(2)线程安全问题。插入要先判断绑定是否存在,不存在才插入。不是原子操作,要加锁。

        🎈得到绑定

注意:这里有两个版本

        (1)根据exchangeName和queueName确定一个binding

        (2)根据exchangeName获取到所有的binding

public Binding getBinding(String exchangeName,String queueName){
        ConcurrentHashMap<String,Binding> bindingMap = bindingsMap.get(exchangeName);
        if (bindingMap == null){
            return null;
        }
        return bindingMap.get(queueName);
    }

public ConcurrentHashMap<String,Binding> getBindings(String exchangeName){
        return bindingsMap.get(exchangeName);
    }

        🎈删除绑定

 public void deleteBinding(Binding binding) throws MqException {
//        现根据exchangeName找到所有的binding
        ConcurrentHashMap<String,Binding> bindingMap = bindingsMap.get(binding.getExchangeName());
        if (bindingMap == null){
//            该交换机没有绑定任何队列,报错
            throw new MqException("[MemoryDataCenter]绑定不存在!exchangeName = " + binding.getExchangeName()
                    + ",queueName" + binding.getQueueName());
        }
        bindingMap.remove(binding.getQueueName());
        System.out.println("[MemoryDataCenter]绑定删除成功!exchangeName = " + binding.getQueueName()
                + ",queueName = " + binding.getQueueName());
    }

🍅 4、进行消息管理

//   添加消息
    public void addMessage(Message message){
        messageMap.put(message.getMessageId(),message);
        System.out.println("[MemoryDataCenter]新消息添加成功!messageId = " + message.getMessageId());
    }

//    根据id查询消息
    public Message getMessage(String messageId){
        return messageMap.get(messageId);
    }

//    根据id删除消息
    public void removeMessage(String messageId){
        messageMap.remove(messageId);
        System.out.println("[MemoryDataCenter]消息被移除!messageId = " + messageId);
    }


🍅 5、发送消息到指定队列

//    发送消息到指定队列
    public void sendMessage(MSGQueue queue,Message message){
//        把消息放到对应的队列数据结构中
//        现根据队列的名字,找到该队列对应的消息链表
        LinkedList<Message> messages = queueMessageMap.computeIfAbsent(queue.getName(),k -> new LinkedList<>());
//        把数据加到messages里面
        synchronized (messages){
            messages.add(message);
        }
//            把该消息往消息中心中插入,假设message已经在消息中心存在,重复插入也没有关系
//        主要就是相同messageId,对应的message内容一定是一样的
        addMessage(message);
        System.out.println("[MemoryDataCenter]消息被投递到到队列中! messageId = " + message.getMessageId());
    }

    //    从队列中取消息
    public Message pollMessage(String queueName){
//        根据队列名,查找对应的消息链表
        LinkedList<Message> messages = queueMessageMap.get(queueName);
//        如果没找到,说明队列中没有任何消息
        if (messages == null) {
            return null;
        }
        synchronized (messages){
            if (messages.size() == 0){
                return null;
            }
//        链表中铀元素,就进行头删
            Message currentMessage = messages.remove(0);
            System.out.println("[MemoryDataCenter]消息从队列中取出!messageId = " + currentMessage.getMessageId());
            return currentMessage;
        }
    }

//    获取指定队列中的消息的个数
    public int getMessageCount(String queueName){
        LinkedList<Message> messages = queueMessageMap.get(queueName);
        if (messages == null){
//            队列中没有消息
            return 0;
        }
        synchronized (messages){
            return messages.size();
        }
    }


🍅 6、表示“未被确认”的消息管理

//    添加未确认的消息
    public void addMessageWaitAck(String queueName,Message message){
        ConcurrentHashMap<String,Message> messageHashMap = queueMessageWaitAckMap.computeIfAbsent(queueName,
                k -> new ConcurrentHashMap<>());
        messageHashMap.put(message.getMessageId(),message);
        System.out.println("[MemoryDataCenter]消息进入待确认队列!messageId = " + message.getMessageId());
    }

//    删除之前未确认,但是现在已经确认的消息
    public void removeMessageWaitAck(String queueName,String messageId){
        ConcurrentHashMap<String,Message> messageHashMap = queueMessageWaitAckMap.get(queueName);
        if (messageHashMap == null){
            return;
        }
        messageHashMap.remove(messageId);
        System.out.println("[MemoryDataCenter]消息从待确认队列删除!messageId = " + messageId);
    }

//    获取指定的未确认的消息
    public Message getMessageWaitAck(String queueName,String messageId){
        ConcurrentHashMap<String ,Message> messageHashMap = queueMessageWaitAckMap.get(queueName);
        if (messageHashMap == null){
            return null;
        }
        return messageHashMap.get(messageId);
    }


🍅 7、从硬盘上读取数据

把之前硬盘中持久化存储的各个维度的数据都恢复到内存中来,

主要是以下几步:

(1)恢复所有的交换机数据

(2)恢复所有的队列

(3)恢复所有的绑定数据

(4)恢复所有的消息数据

注意:关于“未被确认的消息”:“未被确认的消息”是内存中的数据,不需要从硬盘上恢复,一旦在等待ack的过程中,服务器重启了,此时这些“未被确认的消息”,就会恢复成“未被取走的消息”,这些消息在硬盘在硬盘在硬盘上存储的时候,就当作是“未被”取走

//    从硬盘上读取数据,
//    把之前硬盘中持久化存储的各个维度的数据都恢复到内存中来
    public void recovery(DiskDataCenter diskDataCenter) throws IOException, MqException, ClassNotFoundException {
//        先清空之前的数据
        exchangeMap.clear();
        queueMap.clear();
        bindingsMap.clear();
        messageMap.clear();
        queueMessageMap.clear();

//        1.恢复所有的交换机数据
        List<Exchange> exchanges = diskDataCenter.selectAllExchanges();
        for (Exchange exchange : exchanges){
            exchangeMap.put(exchange.getName(),exchange);
        }
//        2.恢复所有的队列
        List<MSGQueue> queues = diskDataCenter.selectAllQueues();
        for (MSGQueue queue : queues) {
            queueMap.put(queue.getName(),queue);
        }
//        3.恢复所有的绑定数据
        List<Binding> bindings = diskDataCenter.selectAllBindings();
        for (Binding binding : bindings){
            ConcurrentHashMap<String ,Binding> bindingMap = bindingsMap.computeIfAbsent(binding.getExchangeName(),
                    k -> new ConcurrentHashMap<>());
        bindingMap.put(binding.getQueueName(), binding);
        }
//        4.恢复所有的消息数据
//        遍历所有队列,再根据每个队列的名字获取到有的消息
        for (MSGQueue queue :queues){
            LinkedList<Message> messages = diskDataCenter.loadAllMessageFromQueue(queue.getName());
            queueMessageMap.put(queue.getName(),messages);
            for (Message message : messages){
                messageMap.put(message.getMessageId(),message);
            }
        }
    }


三、测试交换机操作

🍅 1、“准备工作”和收尾工作

@SpringBootTest
public class MemoryDataCenterTests {
    private MemoryDataCenter memoryDataCenter = null;

    @BeforeEach
    public void  setUp(){
        memoryDataCenter = new MemoryDataCenter();
    }

    @AfterEach
    public void tearDown(){
        memoryDataCenter =null;
    }
}

创建以一个测试交换机和队列,以便于后面使用:

//    创建测试交换机
    private Exchange createTestExchange(String exchangeName){
        Exchange exchange = new Exchange();
        exchange.setName(exchangeName);
        exchange.setType(ExchangeType.DIRECT);
        exchange.setDurable(true);
        return exchange;
    }

//    创建一个测试队列
    private MSGQueue createTestQueue(String queueName){
        MSGQueue queue = new MSGQueue();
        queue.setName(queueName);
        queue.setDurable(true);
        return queue;
    }


//    创建一个测试消息
 public Message createTestMessage(String content){
        Message message = Message.createMessageWithId("testRoutinKey",null,content.getBytes());
        return message;
    }


🍅 2、测试 交换机

//    针对交换机进行测试
    @Test
    public void testExchange(){
//        1.先构造一个交换机并且插入
        Exchange expectedExchange = createTestExchange("testExchange");
        memoryDataCenter.insertExchange(expectedExchange);
//        2.查询这个交换机,比较结果是否一致
        Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
        Assertions.assertEquals(expectedExchange,actualExchange);
//        3.删除这个交换机
        memoryDataCenter.deleteExchange("testExchange");
//        4.再次查找
        actualExchange = memoryDataCenter.getExchange("testExchange");
        Assertions.assertNull(actualExchange);
    }


🍅 3、测试 队列

 @Test
    public void testQueu(){
//        1、创建一个队列并且插入
        MSGQueue expectedQueue = createTestQueue("testQueue");
        memoryDataCenter.insertQueue(expectedQueue);
//        2、查询该队列并且比较
        MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
        Assertions.assertEquals(expectedQueue,actualQueue);
//        3、删除这个队列
         memoryDataCenter.deleteQueue("testQueue");
//        4、再次查询看是否能够查询到
        actualQueue = memoryDataCenter.getQueue("testQueue");
        Assertions.assertNull(actualQueue);
    }
//    针对绑定进行测试
    @Test
    public void testBinding() throws MqException {
        Binding expectedBinding = new Binding();
        expectedBinding.setExchangeName("testExchange");
        expectedBinding.setQueueName("testQueue");
        expectedBinding.setBindingKey("testBindingKey");
        memoryDataCenter.insertBinding(expectedBinding);

        Binding actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
        Assertions.assertEquals(expectedBinding,actualBinding);

        ConcurrentHashMap<String,Binding> bindingMap = memoryDataCenter.getBindings("testExchange");
        Assertions.assertEquals(1,bindingMap.size());
        Assertions.assertEquals(expectedBinding,bindingMap.get("testQueue"));

        memoryDataCenter.deleteBinding(expectedBinding);

        actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
        Assertions.assertNull(actualBinding);
    }


🍅 4、测试 绑定

//    针对绑定进行测试
    @Test
    public void testBinding() throws MqException {
        Binding expectedBinding = new Binding();
        expectedBinding.setExchangeName("testExchange");
        expectedBinding.setQueueName("testQueue");
        expectedBinding.setBindingKey("testBindingKey");
        memoryDataCenter.insertBinding(expectedBinding);

        Binding actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
        Assertions.assertEquals(expectedBinding,actualBinding);

        ConcurrentHashMap<String,Binding> bindingMap = memoryDataCenter.getBindings("testExchange");
        Assertions.assertEquals(1,bindingMap.size());
        Assertions.assertEquals(expectedBinding,bindingMap.get("testQueue"));

        memoryDataCenter.deleteBinding(expectedBinding);

        actualBinding = memoryDataCenter.getBinding("testExchange","testQueue");
        Assertions.assertNull(actualBinding);
    }


🍅 5、测试 消息的增删查

@Test
    public void testMessage(){
        Message expectedMessage = createTestMessage("testMessage");
        memoryDataCenter.addMessage(expectedMessage);

        Message actualMessage = memoryDataCenter.getMessage(expectedMessage.getMessageId());
        Assertions.assertEquals(expectedMessage,actualMessage);

        memoryDataCenter.removeMessage(expectedMessage.getMessageId());
        actualMessage = memoryDataCenter.getMessage(expectedMessage.getMessageId());
        Assertions.assertNull(actualMessage);
    }


🍅 6、测试 发送消息

//    测试发送消息
    @Test
    public void testSendMessage(){
//        1、创建一个队列,创建十条消息,把这些消息都插入到队列中
        MSGQueue queue = createTestQueue("testQueue");
        List<Message> expectedMessages = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Message message = createTestMessage("testMessage" + 1);
            memoryDataCenter.sendMessage(queue,message);
            expectedMessages.add(message);
        }
//        2、从队列中取出这些消息
        List<Message> actualMessages = new ArrayList<>();
        while (true){
            Message message = memoryDataCenter.pollMessage("testQueue");
            if (message == null){
                break;
            }
            actualMessages.add(message);
        }
//        3、比较取出的消息和之前的消息是否一致
        Assertions.assertEquals(expectedMessages.size(),actualMessages.size());

        for (int i = 0; i < expectedMessages.size(); i++) {
            Assertions.assertEquals(expectedMessages.get(i),actualMessages.get(i));
        }
    }


🍅 7、测试 “未确认”的信息 

  @Test
    public void testMessageWaitAck(){
        Message expectedMessage = createTestMessage("expectedMessage");
        memoryDataCenter.addMessageWaitAck("testQueue",expectedMessage);

        Message actualMessage = memoryDataCenter.getMessageWaitAck("testQueue",expectedMessage.getMessageId());
        Assertions.assertEquals(expectedMessage,actualMessage);

        memoryDataCenter.removeMessageWaitAck("testQueue",expectedMessage.getMessageId());
        actualMessage = memoryDataCenter.getMessageWaitAck("testQueue",expectedMessage.getMessageId());
        Assertions.assertNull(actualMessage);
    }


 🍅 8、测试 从硬盘上读取数据

 测试这个用例,主要分为3步:

        (1)在硬盘上构造好数据

        (2)执行恢复操作

        (3)对比结果

@Test
    public void testRecovery() throws IOException, MqException, ClassNotFoundException {
        //        后续操作雨MyBatis有关,所以需要启动SpringApplication
        TigerMqApplication.context = SpringApplication.run(TigerMqApplication.class);

        // 1. 在硬盘上构造好数据
        DiskDataCenter diskDataCenter = new DiskDataCenter();
        diskDataCenter.init();

        // 构造交换机
        Exchange expectedExchange = createTestExchange("testExchange");
        diskDataCenter.insertExchange(expectedExchange);

        // 构造队列
        MSGQueue expectedQueue = createTestQueue("testQueue");
        diskDataCenter.insertQueue(expectedQueue);

        // 构造绑定
        Binding expectedBinding = new Binding();
        expectedBinding.setExchangeName("testExchange");
        expectedBinding.setQueueName("testQueue");
        expectedBinding.setBindingKey("testBindingKey");
        diskDataCenter.insertBinding(expectedBinding);

        // 构造消息
        Message expectedMessage = createTestMessage("testContent");
        diskDataCenter.sendMessage(expectedQueue, expectedMessage);

        // 2. 执行恢复操作
        memoryDataCenter.recovery(diskDataCenter);

        // 3. 对比结果
        Exchange actualExchange = memoryDataCenter.getExchange("testExchange");
        Assertions.assertEquals(expectedExchange.getName(), actualExchange.getName());
        Assertions.assertEquals(expectedExchange.getType(), actualExchange.getType());
        Assertions.assertEquals(expectedExchange.isDurable(), actualExchange.isDurable());
        Assertions.assertEquals(expectedExchange.isAutoDelete(), actualExchange.isAutoDelete());

        MSGQueue actualQueue = memoryDataCenter.getQueue("testQueue");
        Assertions.assertEquals(expectedQueue.getName(), actualQueue.getName());
        Assertions.assertEquals(expectedQueue.isDurable(), actualQueue.isDurable());
      

        Binding actualBinding = memoryDataCenter.getBinding("testExchange", "testQueue");
        Assertions.assertEquals(expectedBinding.getExchangeName(), actualBinding.getExchangeName());
        Assertions.assertEquals(expectedBinding.getQueueName(), actualBinding.getQueueName());
        Assertions.assertEquals(expectedBinding.getBindingKey(), actualBinding.getBindingKey());

        Message actualMessage = memoryDataCenter.pollMessage("testQueue");
        Assertions.assertEquals(expectedMessage.getMessageId(), actualMessage.getMessageId());
        Assertions.assertEquals(expectedMessage.getRoutingKey(), actualMessage.getRoutingKey());
        Assertions.assertEquals(expectedMessage.getDeliverMode(), actualMessage.getDeliverMode());
        Assertions.assertArrayEquals(expectedMessage.getBody(), actualMessage.getBody());

        // 4. 清理硬盘的数据, 把整个 data 目录里的内容都删掉(包含了 meta.db 和 队列的目录).
        TigerMqApplication.context.close();
        File dataDir = new File("./data");
        FileUtils.deleteDirectory(dataDir);
    }

 


 四、小结

  这一块的内容,主要就是借助内存中的一系列数据结构,保存、管理交换机、队列、绑定、消息,使用到了哈希表、链表、嵌套的结构等。

这里还频繁的使用了加锁的操作,具体场景考虑是否要加锁(特别是有插入操作)。

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

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

相关文章

英文音频转文字app让音频秒变文字

小明是一名大学生&#xff0c;他在上大学的时候经常需要通过听老师讲课来获取知识。但是&#xff0c;他发现自己很难在听课的同时完整地记录下老师所讲的内容。于是&#xff0c;他开始寻找音频转文字手机软件有哪些。经过一段时间的探索&#xff0c;他找到了三款不错的软件&…

我开源的 c#+wpf 模仿网易云音乐播放器

MusicApp 介绍 gitee地址&#xff1a;https://gitee.com/liu_guo_feng/music-app 我开源的 c#wpf 模仿网易云音乐播放器 项目页面功能完成列表 首页(待完善) 每日推荐音乐 歌单详情 带播放列表 歌词页(待完善) 换肤功能(待完善) 系统托盘 … 预览 仅供学习使用 不作任何商业用…

查看单元测试用例覆盖率新姿势:IDEA 集成 JaCoCo

1、什么是 IDEA IDEA 全称 IntelliJ IDEA&#xff0c;是 Java 编程语言开发的集成环境。IntelliJ 在业界被公认为最好的 Java 开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE 支持、各类版本工具(git、SVN 等)、JUnit、CVS 整合、代码分析、 创新的 GUI…

Python入门自学进阶-Web框架——39、redis、rabbitmq、git——2

RabbitMQ的exchange&#xff0c;即交换机有不同的类型&#xff1a; 1.direct Exchange(直接交换机) 匹配路由键&#xff0c;只有完全匹配消息才会被转发 2.Fanout Excange&#xff08;扇出交换机&#xff09; 将消息发送至所有的队列 3.Topic Exchange(主题交换机) 将路由按模…

【机器学习 | 决策树】利用数据的潜力:用决策树解锁洞察力

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

山东气象 × 和鲸科技:第一届齐鲁风云气象智能算法挑战赛圆满收官

7 月 20 日&#xff0c;中国气象局印发《人工智能气象应用工作方案&#xff08;2023 - 2030年&#xff09;》&#xff0c;旨在加快布局国产人工智能气象应用技术体系建设&#xff0c;推动人工智能技术在气象观测、预报及服务中的深度融合应用&#xff0c;为监测精密、预报精准、…

网络安全 Day30-运维安全项目-堡垒机部署

运维安全项目-堡垒机部署 1. 运维安全项目-架构概述2. 运维安全项目之堡垒机2.1 堡垒机概述2.2 堡垒机选型2.3 环境准备2.4 部署Teleport堡垒机2.4.1 下载与部署2.4.2 启动2.4.3 浏览器访问teleport2.4.4 进行配置2.4.5 安装teleport客户端 2.5 teleport连接服务器 1. 运维安全…

【Windows】Windows开机密码重置

文章目录 前言一、问题描述二、操作步骤2.1 安装DaBaiCai_d14_v6.0_2207_Online.exe2.2 插入U盘2.3 打开大白菜&#xff0c;点击“一键制作USB启动盘”2.4 等待进度条走完2.5 重启电脑&#xff0c;开机按“F12”或者“F8”&#xff08;具体百度一下&#xff0c;对应品牌电脑开机…

DEVICENET转ETHERCAT网关连接西门子支持ethercat吗

你有没有遇到过生产管理系统中&#xff0c;设备之间的通讯问题&#xff1f;两个不同协议的设备进行通讯&#xff0c;是不是很麻烦&#xff1f;今天&#xff0c;我们为大家介绍一款神奇的产品&#xff0c;能够将不同协议的设备进行连接&#xff0c;让现场的数据交换不再困扰&…

Appium 移动端自动化测试 -- 常用元素操作

点击元素&#xff08;element&#xff09; element.click() 输入内容 element.send_keys(‘input_string’) 清空输入框内容 element.clear() 获取元素属性 element.get_property("text_length")element.get_attribute("class") 获取元素的text文本…

MySQL 慢查询探究分析

目录 背景&#xff1a; mysql 整体结构&#xff1a; SQL查询语句执行过程是怎样的&#xff1a; 知道了mysql的整体架构&#xff0c;那么一条查询语句是怎么被执行的呢&#xff1a; 什么是索引&#xff1a; 建立索引越多越好吗&#xff1a;   如何发现慢查询&#xff1…

Appium Android 自动化测试 -- 元素定位

自动化测试元素定位是难点之一&#xff0c;编写脚本时会经常卡在元素定位这里&#xff0c;有时一个元素能捣鼓一天&#xff0c;到最后还是定位不到。 Appium 定位方式和 selenium 一脉相承&#xff0c;selenium 中的定位方式Appium 中都支持&#xff0c;而 Appium 还增加了自己…

每天一道leetcode:剑指 Offer 32 - III. 从上到下打印二叉树 III(中等广度优先遍历)

今日份题目&#xff1a; 请实现一个函数按照之字形顺序打印二叉树&#xff0c;即第一行按照从左到右的顺序打印&#xff0c;第二层按照从右到左的顺序打印&#xff0c;第三行再按照从左到右的顺序打印&#xff0c;其他行以此类推。 示例 给定二叉树: [3,9,20,null,null,15,7…

pytorch Stream 多流处理

CUD Stream https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-language-extensions 中指出在kenel的调用函数中最后一个可选参数表示该核函数处在哪个流之中。 - 参数Dg用于定义整个grid的维度和尺寸&#xff0c;即一个grid有多少个block。为dim3类型。…

电脑开不了机如何解锁BitLocker硬盘锁

事情从这里说起&#xff0c;不想看直接跳过 早上闲着无聊&#xff0c;闲着没事干&#xff0c;将win11的用户名称改成了含有中文字符的用户名&#xff0c;然后恐怖的事情发生了&#xff0c;蓝屏了… 然后就是蓝屏收集错误信息&#xff0c;重启&#xff0c;蓝屏收集错误信息&…

【软件工程】2.4 建立动态模型

目录 2.4 建立动态模型 2.4.1 活动图 验证密码活动图 转账活动图 存款活动图 取款活动图 2.4.2 顺序图 2.4.3 状态图 ATM类状态图 总行状态图 分行状态图 2.4.4 协作图 总博客&#xff1a; 2.4 建立动态模型 开发交互式系统&#xff0c;动态模型非常重要 步骤&#…

电商运营必备!6步教你做好电商复盘

一、业绩及关键指标 1、总体完成情况&#xff1a;GMV、UV、转化率、客单价、件单价、连带率、UV价值等&#xff0c;达成率如何&#xff0c;数据同比、环比增幅&#xff0c;与大盘对比情况如何。 2、分阶段及日销情况&#xff1a;预热、预售、开门红、日销、大cu高潮期等销售达…

QFileDialog 对话框类

QFileDialog 对话框类 QFileDialog 对话框类是 QDialog 类的子类, 通过这个类可以选择要打开/保存的文件或者目录。关于这个类我们只需要掌握一些静态方法的使用就可以了。 /* 通用参数:- parent: 当前对话框窗口的父对象也就是父窗口- caption: 当前对话框窗口的标题- dir: 当…

Unity游戏源码分享-仿帝国时代游戏Demo-uRTS Toolkit

Unity游戏源码分享-仿帝国时代游戏Demo-uRTS Toolkit 游戏的架构值得参考 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88189905

医院临床病例管理系统,促进医疗数据的管理

医疗行业的不断发展&#xff0c;临床病例管理系统也逐渐成为医院管理重要的一环&#xff0c;医院临床病例管理系统是一款集医学信息管理、科研分析以及教学为一体的平台&#xff0c;为医生提供更加高效、准确的病例管理方式&#xff0c;提高诊疗质量&#xff0c;为医学科研提供…