消息队列 -提供上层服务接口
目录
- 前言
- 封装数据库
- 封装内存操作
- 内存的设计思想
- 应答模式
- 代码实现
- 测试代码
前言
我们之前已经将 数据库 的操作 和文件的操作 都完成了, 但是对于上层调用来说, 并不关心是于数据库中存储数据还是往文件中存储数据, 因此 我们提供一个类, 封装一下 上述俩个类中的操作, 并将这个类 提供给上层调用
封装数据库
package com.example.demo.mqServer.dataCenter;import com.example.demo.Common.MqException;
import com.example.demo.mqServer.core.Binding;
import com.example.demo.mqServer.core.Exchange;
import com.example.demo.mqServer.core.MSGQueue;
import com.example.demo.mqServer.core.Message;import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;/*
* 管理所有硬盘上的数据
* 1: 数据库: 交换机 绑定 队列
* 2: 数据文件 : 消息
* 上层逻辑如果需要操作硬盘, 统一通过这个类来使用 , 上层代码不关系当前数据是存储在数据库还是文件中的*/
public class DiskDataCenter {
// 用来管理数据库中的数据private DataBaseManager dataBaseManager = new DataBaseManager();
// 用来管理数据文件中的数据private MessageFileManager messageFileManager = new MessageFileManager();
// 对上述实例进行初始化public void init(){dataBaseManager.init();messageFileManager.init();}// 封装交换机操作public void insertExchange(Exchange exchange){dataBaseManager.insertExchange(exchange);}public void deleteExchange(String exchangeName){dataBaseManager.deleteExchange(exchangeName);}public List<Exchange> selectAllExchange(){return dataBaseManager.selectAllExchanges();}// 封装队列操作public void insertQueue(MSGQueue queue){dataBaseManager.insertQueue(queue);}public void deleteQueue(String queueName){dataBaseManager.deleteQueue(queueName);}public List<MSGQueue> selectAllQueue(){return dataBaseManager.selectAllQueues();}// 封装绑定操作public void insertBinding(Binding binding){dataBaseManager.insertBinding(binding);}public void deleteBinding(Binding binding){dataBaseManager.deleteBinding(binding);}public List<Binding> selectAllBinding(){return dataBaseManager.selectAllBindings();}// 封装消息操作public void sendMessage(MSGQueue queue, Message message) throws IOException, MqException {messageFileManager.sendMessage(queue, message);}public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException, MqException {messageFileManager.deleteMessage(queue, message);// 对于删除消息, 我们要查看是否需要垃圾回收if (messageFileManager.checkGC(queue.getName())) {messageFileManager.gc(queue);}}public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {return messageFileManager.loadAllMessageFromQueue(queueName);}}
封装内存操作
上面我们完成的都是往硬盘上面保存数据的操作, 无论是 往数据库中添加 队列 交换机 类, 还是往文件存储消息 都是针对硬盘上的操作 ,所以我们现在写关于内存的操作
内存的设计思想
我们关于交换机和队列, 我们都使用哈希表来存储 key 是各自的名字也是主键, value 是对应的 交换机和队列
绑定:使用嵌套的哈希表来存储 , 第一个key是 交换机名称, 第二个key 是队列名称
消息: 使用哈希表来存储, key 是消息ID , value 是消息对象(每一个消息都是一个哈希表)
队列和消息之前的关系: 使用嵌套的哈希表来存储 , key是队列名, value是一个LinkedList 里面存放的消息
还有关于确认应答: 我们采用嵌套的哈希表来存储 , key是队列名, value 是哈希表,( key是消息ID, value是消息)
应答模式
我们这里实现的MQ消息队列, 支持俩种应答模式
第一种: 自动应答, 也就是说当消费者取走了元素, 就当做这个消息被应答了, 此时这个消息就可以被当做成了垃圾了
第二种: 确认应答 : 消费者取走了元素, 还不能被当做是应答了, 必须消费者主动调用一个确认应答方法, 此时才能认为这个消息无用了, 可以删除了
代码实现
package com.example.demo.mqServer.dataCenter;import com.example.demo.Common.MqException;
import com.example.demo.mqServer.core.Binding;
import com.example.demo.mqServer.core.Exchange;
import com.example.demo.mqServer.core.MSGQueue;
import com.example.demo.mqServer.core.Message;import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;// 使用这个类来统一管理内存中的数据
// 处理各种映射关系
// 该类提供的方法, 可能在多线程情况下处理, 所以使用线程安全的哈希表
public class MemoryDataCenter {// 根据交换机名称来获取交换机对象 , key 是 exchangeName, value 是 Exchange 对象private ConcurrentHashMap<String , Exchange> exchangeMap = new ConcurrentHashMap<>();// 根据队列名来获取队列对象 , key 是 queueName, value 是 MSGQueue 对象private ConcurrentHashMap<String , MSGQueue> queueMap =new ConcurrentHashMap<>();//根据交换机名, 和队列名 来获取 绑定对象 , 第一个 key 是 exchangeName, 第二个 key 是 queueNameprivate ConcurrentHashMap<String , ConcurrentHashMap<String , Binding>> bindingsMap = new ConcurrentHashMap<>();//根据消息名称 来获取 消息对象, key 是 messageId, value 是 Message 对象private ConcurrentHashMap<String , Message> messageMap = new ConcurrentHashMap<>();// 根据队列名称, 来获取消息列表 , key 是 队列名称, value 是 Message 对象private ConcurrentHashMap<String, LinkedList<Message>> queueMessageMap = new ConcurrentHashMap<>();// 手动应答 , 第一个 key 是 queueName, 第二个 key 是 messageId ,private ConcurrentHashMap<String, ConcurrentHashMap<String, Message>> queueMessageWaitAckMap = new ConcurrentHashMap<>();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);}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);}public void insertBinding(Binding binding) throws MqException {ConcurrentHashMap<String , Binding> bindingMap = bindingsMap.get(binding.getExchangeName());if (bindingMap == null){bindingMap = new ConcurrentHashMap<>();bindingsMap.put(binding.getExchangeName(), bindingMap);}// 再根据 queueName 查一下. 如果已经存在, 就抛出异常. 不存在才能插入.synchronized (binding){if (bindingMap.get(binding.getMsgQueueName()) != null) {throw new MqException("[MemoryDataCenter] 绑定已经存在! exchangeName=" + binding.getExchangeName() +", queueName=" + binding.getMsgQueueName());}bindingMap.put(binding.getMsgQueueName(), binding);}System.out.println("[MemoryDataCenter] 新绑定添加成功! exchangeName=" + binding.getExchangeName()+ ", queueName=" + binding.getMsgQueueName());}// 获取绑定, 写两个版本:// 1. 根据 exchangeName 和 queueName 确定唯一一个 Binding// 2. 根据 exchangeName 获取到所有的 Bindingpublic 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 {ConcurrentHashMap<String , Binding> bindingMap = bindingsMap.get(binding.getExchangeName());if (bindingMap == null){// 该交换机没有绑定任何队列. 报错.throw new MqException("[MemoryDataCenter] 绑定不存在! exchangeName=" + binding.getExchangeName()+ ", queueName=" + binding.getMsgQueueName());}bindingMap.remove(binding.getMsgQueueName());System.out.println("[MemoryDataCenter] 绑定删除成功! exchangeName=" + binding.getExchangeName()+ ", queueName=" + binding.getMsgQueueName());}// 添加消息public void addMessage(Message message) {messageMap.put(message.getMessageId(),message);}// 根据 id 查询消息public Message getMessage(String messageId) {return messageMap.get(messageId);}// 根据 id 删除消息public void removeMessage(String messageId) {messageMap.remove(messageId);}// 发送消息到指定队列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 的内容一定是一样的. (服务器代码不会对 Message 内容做修改 basicProperties 和 body)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();}}// 添加未确认的消息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.computeIfAbsent(queueName,k -> new ConcurrentHashMap<>());if (messageHashMap == null)return;messageHashMap.remove(messageId);}// 获取指定的未确认的消息public Message getMessageWaitAck(String queueName, String messageId) {ConcurrentHashMap<String, Message> messageHashMap = queueMessageWaitAckMap.computeIfAbsent(queueName,k -> new ConcurrentHashMap<>());if (messageHashMap == null)return null;return messageHashMap.get(messageId);}// 这个方法就是从硬盘上读取数据, 把硬盘中之前持久化存储的各个维度的数据都恢复到内存中.public void recovery(DiskDataCenter diskDataCenter) throws IOException, MqException, ClassNotFoundException {List<Exchange> exchanges = diskDataCenter.selectAllExchange();exchangeMap.clear();for (Exchange exchange:exchanges) {exchangeMap.put(exchange.getName(),exchange);}List<MSGQueue> queues = diskDataCenter.selectAllQueue();queueMap.clear();for (MSGQueue q :queues) {queueMap.put(q.getName(),q);}List<Binding> bindings = diskDataCenter.selectAllBinding();bindingsMap.clear();for (Binding binding : bindings) {ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.computeIfAbsent(binding.getExchangeName(),k -> new ConcurrentHashMap<>());bindingMap.put(binding.getMsgQueueName(), binding);}// 4. 恢复所有的消息数据// 遍历所有的队列, 根据每个队列的名字, 获取到所有的消息.messageMap.clear();queueMessageMap.clear();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);}}// 注意!! 针对 "未确认的消息" 这部分内存中的数据, 不需要从硬盘恢复. 之前考虑硬盘存储的时候, 也没设定这一块.// 一旦在等待 ack 的过程中, 服务器重启了, 此时这些 "未被确认的消息", 就恢复成 "未被取走的消息" .// 这个消息在硬盘上存储的时候, 就是当做 "未被取走"}}
测试代码
package com.example.demo.mqServer.dataCenter;import com.example.demo.Common.MqException;
import com.example.demo.MqApplication;
import com.example.demo.mqServer.core.*;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.context.SpringBootTest;import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class MemoryDataCenterTest {private MemoryDataCenter memoryDataCenter = null;@BeforeEachpublic void setUp() {memoryDataCenter = new MemoryDataCenter();}@AfterEachpublic void tearDown() {memoryDataCenter = null;}// 创建一个测试交换机private Exchange createTestExchange(String exchangeName) {Exchange exchange = new Exchange();exchange.setName(exchangeName);exchange.setType(ExchangeType.direct);exchange.setAutoDelete(false);exchange.setDurable(true);return exchange;}// 创建一个测试队列private MSGQueue createTestQueue(String queueName) {MSGQueue queue = new MSGQueue();queue.setName(queueName);queue.setDurable(true);queue.setExclusive(false);queue.setAutoDelete(false);return queue;}// 针对交换机进行测试@Testpublic 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);}// 针对队列进行测试@Testpublic void testQueue() {// 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);}// 针对绑定进行测试@Testpublic void testBinding() throws MqException {Binding expectedBinding = new Binding();expectedBinding.setExchangeName("testExchange");expectedBinding.setMsgQueueName("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);}// 测试消息的增删查private Message createTestMessage(String content) {Message message = Message.createMessageWithId("testRoutingKey", null, content.getBytes());return message;}@Testpublic 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);}@Testpublic void testSendMessage() {// 1. 创建一个队列, 创建 10 条消息, 把这些消息都插入队列中.MSGQueue queue = createTestQueue("testQueue");List<Message> expectedMessages = new ArrayList<>();for (int i = 0; i < 10; i++) {Message message = createTestMessage("testMessage" + i);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));}}@Testpublic 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);}@Testpublic void testRecovery() throws IOException, MqException, ClassNotFoundException {// 由于后续需要进行数据库操作, 依赖 MyBatis. 就需要先启动 SpringApplication, 这样才能进行后续的数据库操作.MqApplication.context = SpringApplication.run(MqApplication.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.setMsgQueueName("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());Assertions.assertEquals(expectedQueue.isAutoDelete(), actualQueue.isAutoDelete());Assertions.assertEquals(expectedQueue.isExclusive(), actualQueue.isExclusive());Binding actualBinding = memoryDataCenter.getBinding("testExchange", "testQueue");Assertions.assertEquals(expectedBinding.getExchangeName(), actualBinding.getExchangeName());Assertions.assertEquals(expectedBinding.getMsgQueueName(), actualBinding.getMsgQueueName());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 和 队列的目录).MqApplication.context.close();File dataDir = new File("./data");FileUtils.deleteDirectory(dataDir);}
}
相关文章:
消息队列 -提供上层服务接口
目录 前言封装数据库封装内存操作内存的设计思想 应答模式 代码实现测试代码 前言 我们之前已经将 数据库 的操作 和文件的操作 都完成了, 但是对于上层调用来说, 并不关心是于数据库中存储数据还是往文件中存储数据, 因此 我们提供一个类, 封装一下 上述俩个类中的操作, 并将…...

maven引入本地jar包的简单方式【IDEA】【SpringBoot】
前言 想必点进来看这篇文章的各位,都是已经习惯了Maven从中央仓库或者阿里仓库直接拉取jar包进行使用。我也是🤡🤡。 前两天遇到一个工作场景,对接三方平台,结果对方就是提供的一个jar包下载链接,可给我整…...

【爬虫逆向案例】某易云音乐(评论)js逆向—— params、encSecKey解密
声明:本文只作学习研究,禁止用于非法用途,否则后果自负,如有侵权,请告知删除,谢谢! 【爬虫逆向案例】某易云音乐(评论)js逆向—— params、encSecKey解密 1、前言2、行动…...

【uni-app】【Android studio】手把手教你运行uniapp项目到Android App
运行到Android App基座 选择运行到Android App基座 选择运行项目 1、连接手机,在手机上选择 传输文件。 2、打开 设置-> 关于本机 -> 版本信息->连续点击4-5次版本号 ,输入手机密码,系统就进入了开发者模式。 3、设置 > 其他设…...

多线程(JavaEE初阶系列6)
目录 前言: 1.什么是线程池 2.标准库中的线程池 3.实现线程池 结束语: 前言: 在上一节中小编带着大家了解了一下Java标准库中的定时器的使用方式并给大家实现了一下,那么这节中小编将分享一下多线程中的线程池。给大家讲解一…...

shell清理redis模糊匹配的多个key
#!/bin/bash# 定义Redis服务器地址和端口 REDIS_HOST"localhost" REDIS_PORT6380# 获取匹配键的数量 function get_matching_keys() {local key_pattern"$1"redis-cli -h $REDIS_HOST -p $REDIS_PORT -n 0 KEYS "$key_pattern" }# 删除匹配的键 …...

【电网异物检测硕士论文摘抄记录】电力巡检图像中基于深度学习的异物检测方法研究
根据国家电力行业发展报告统计,截止到 2018 年,全国电网 35 千伏及以上的输电线路回路长度达到 189 万千米,220 千伏及以上输电线路回路长度达73 万千米。截止到 2015年,根据国家电网公司的统计 330 千伏及以上输电线路故障跳闸总…...

C++共享数据的保护
虽然数据隐藏保护了数据的安全性,但各种形式的数据共享却又不同程度地破坏了数据的安全。因此,对于既需要共享有需要防止改变的数据应该声明为常量。因为常量在程序运行期间不可改变,所以可以有效保护数据。 1.常对象 常对象:它…...
MyBatisPlus学习记录
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率 MyBatisPlus简介 入门案例 创建新模块,选择Spring初始化,并配置模块相关基础信息选择当前模块需要使用的技术集(仅选择MySQL …...

如何开启一个java微服务工程
安装idea IDEA常用配置和插件(包括导入导出) https://blog.csdn.net/qq_38586496/article/details/109382560安装配置maven 导入source创建项目 修改项目编码utf-8 File->Settings->Editor->File Encodings 修改项目的jdk maven import引入…...
libhv之hio_t分析
上一篇文章解析了fd是怎么与io模型关联。其中最主要的角色扮演者:hio_t 1. hio_t与hloop的关系 fd的值即hio_t所在loop ios变量中所在的index值。 hio_t ios[fd] struct hloop_s { ...// ios: with fd as array.index//io_array保存了和hloop关联的所有hio_t&…...

C语言的转义字符
转义字符也叫转移序列,包含如下: 转移序列 \0oo 和 \xhh 是 ASCII 码的特殊表示。 八进制数示例: 代码: #include<stdio.h> int main(void) {char beep\007;printf("%c\n",beep);return 0; }结果: …...

【腾讯云 Cloud Studio 实战训练营】CloudStudio体验真正的现代化开发方式,双手插兜不知道什么叫对手!
CloudStudio体验真正的现代化开发方式,双手插兜不知道什么叫对手! 文章目录 CloudStudio体验真正的现代化开发方式,双手插兜不知道什么叫对手!前言出现的背景一、CloudStudio 是什么?二、CloudStudio 的特点三、CloudS…...

Pandas时序数据分析实践—时序数据集
1. 跑步运动为例,对运动进行时序分析 时序数据是指时间序列数据,是按照时间顺序排列的数据集合,每个数据点都与一个特定的时间戳相关联。在跑步活动中,我们可以将每次跑步的数据记录作为一个时序数据样本,每个样本都包…...
use strict 是什么意思?使用它区别是什么?
use strict 是什么意思?使用它区别是什么? use strict 代表开启严格模式,这种模式下使得 JavaScript 在更严格的条件下运行,实行更严格解析和错误处理。 开启“严格模式”的优点: 消除 JavaScript 语法的一些不合理…...

常见OOM异常分析排查
常见OOM异常分析排查 Java内存溢出Java堆溢出原因解决思路总结 Java内存溢出 java堆用于存储对象实例,如果不断地创建对象,并且保证GC Root到对象之间有可达路径,垃圾回收机制就不会清理这些对象,对象数量达到最大堆的容量限制后就会产生内存溢出异常. Java堆溢出原因 无法在…...
kubernetes网络之网络策略-Network Policies
Kubernetes 中,Network Policy(网络策略)定义了一组 Pod 是否允许相互通信,或者与网络中的其他端点 endpoint 通信。 NetworkPolicy 对象使用标签选择Pod,并定义规则指定选中的Pod可以执行什么样的网络通信࿰…...

交换机VLAN技术和实验(eNSP)
目录 一,交换机的演变 1.1,最小网络单元 1.2,中继器(物理层) 1.3,集线器(物理层) 1.4,网桥(数据链路层) 二,交换机的工作行为 2.…...

8.Winform界面打包成DLL提供给其他的项目使用
背景 希望集成一个Winform的框架,提供权限菜单,根据权限出现各个Winform子系统的菜单界面。不希望把所有的界面都放放在同一个解决方案下面。用各个子系统建立不同的解决方案,建立代码仓库,进行管理。 实现方式 将Winform的UI界…...

海量数据存储组件Hbase
hdfs hbase NoSQL数据库 支持海量数据的增删改查 基于Rowkey查询效率特别高 kudu 介于hdfs和hbase之间 hbase依赖hadoopzookeeper,同时整合框架phoenix(擅长读写),hive(分析数据) k,v 储存结构 稀疏的(为空的不存…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...