RabbitMQ 从入门到精通:从工作模式到集群部署实战(四)
#作者:闫乾苓
系列前几篇:
《RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)》:link
《RabbitMQ 从入门到精通:从工作模式到集群部署实战(二)》: link
《RabbitMQ 从入门到精通:从工作模式到集群部署实战(三)》:link
文章目录
- 高可用测试
- classic队列测试
- 工作原理
- 仲裁队列测试
- 工作原理
高可用测试
测试集群是使用RabbitMQ Cluster Kubernetes Operator 部署的3节点集群
对常用的classic、quorum两种类型的队列进行测试,使用2个python脚本作为消息的Producer和Consumer客户端连接到服务器集群,停止集群中1个节点,不影响消息的生产和消费。
classic队列测试
工作原理
RabbitMQ的经典队列(Classic Queue)镜像复制是一种高可用性特性,它通过在集群中的多个节点上复制队列的内容来提供数据冗余,从而确保在节点故障时队列及其消息仍然可以从其他节点恢复,提高了系统的可靠性和容错能力。以下是RabbitMQ经典队列镜像复制的工作原理:
镜像队列的组成
- 主节点(Leader):
负责处理所有的写入操作和部分读取操作。
将所有写入的消息同步给跟随者节点。 - 跟随者节点(Followers):
复制主节点的数据,以提供冗余和容错能力。
在主节点失效时,可以参与新的领导者选举,选出一个新的领导者继续提供服务。
镜像复制过程
- 消息写入:
- 当生产者向镜像队列发送消息时,消息首先被写入主节点上的队列。
- 主节点随后将消息同步到跟随者节点。这个过程通常是异步的,以保证性能,但可以通过配置来调整同步策略。
- 如果配置为要求确认(acknowledgment)模式,则主节点只有在收到所有跟随者的确认后才会认为消息已被成功存储。
- 消息读取:
- 消费者可以从任何节点上的镜像队列中消费消息。
- 默认情况下,消费者连接到哪个节点就从那个节点消费。
- 在主节点失效的情况下,消费者会被重定向到新的领导者节点。
镜像队列的类型
- 单活镜像队列:
在单活镜像队列中,只有一个节点处于活动状态,其他节点处于备份状态。
活动节点负责处理消息的生产和消费,而备份节点则负责复制活动节点的数据。 - 多活镜像队列:
在多活镜像队列中,所有节点都处于活动状态,都可以处理消息的生产和消费。
这种方式可以提高系统的吞吐量和可用性。
镜像队列的故障切换与恢复
- 故障切换:
当主节点失效时,剩余的跟随者节点之间会进行新的领导者选举,选出一个新的领导者继续提供服务。
镜像队列可以自动切换到另一个副本,确保服务的连续性。 - 恢复:
在主节点恢复后,它可以重新加入集群并成为跟随者节点。
跟随者节点会与新的领导者节点进行同步,以确保数据的一致性。
镜像队列的配置与管理
- 配置镜像策略:
使用策略(Policy)来配置镜像策略。
策略使用正则表达式来配置需要应用镜像策略的队列名称,以及在参数中配置镜像队列的具体参数。 - 管理UI:
RabbitMQ提供了管理UI,可以通过该UI来查看和管理镜像队列的状态、配置和性能。
测试脚本
脚本需要安装pika
pip3 install pika
Producer写入classic队列脚本
Exchange的Routing key和队列名一样
import pika
import time
import random
import string# RabbitMQ连接参数
rabbitmq_host = '192.168.123.242' # 替换为你的RabbitMQ服务器地址
rabbitmq_port = 16027 # RabbitMQ端口
rabbitmq_vhost = '/' # 虚拟主机
rabbitmq_username = 'admin' # RabbitMQ用户名
rabbitmq_password = 'Admin.123' # RabbitMQ密码
rabbitmq_queue = 'queue_classic_02' # 目标队列名
rabbitmq_exchange = 'exchange-02' # 自定义exchange名
num_messages = 1000000 # 要发送的消息数量
message_size_kb = 1 # 每条消息的大小(KB)
message_size_bytes = message_size_kb * 1024 # 每条消息的大小(字节)
rate_limit_msgs_per_sec = 500 # 每秒发送的消息数量(速率限制)# 计算发送每条消息后的暂停时间(秒)
sleep_time_between_messages = 1 / rate_limit_msgs_per_sec# 建立连接和通道
credentials = pika.PlainCredentials(rabbitmq_username, rabbitmq_password)
connection = pika.BlockingConnection(pika.ConnectionParameters(host=rabbitmq_host,port=rabbitmq_port,virtual_host=rabbitmq_vhost,credentials=credentials
))
channel = connection.channel()# 声明自定义的exchange(如果需要持久化,设置durable=True)
channel.exchange_declare(exchange=rabbitmq_exchange, exchange_type='direct', durable=True)# 确保队列存在(如果需要持久化,也设置durable=True)
channel.queue_declare(queue=rabbitmq_queue, durable=True)# 生成随机字符串的函数,长度为指定的字节数
def generate_random_message(size_bytes):letters = string.ascii_letters + string.digits # 字母和数字return ''.join(random.choice(letters) for _ in range((size_bytes * 8) // len(letters))).encode('utf-8')[:size_bytes]# 批量发送消息,并增加速率控制
for i in range(num_messages):message = generate_random_message(message_size_bytes)routing_key = rabbitmq_queue # 使用队列名作为routing key(对于direct类型的exchange)# 设置消息的properties,包括delivery_mode=2(持久化消息)properties = pika.BasicProperties(delivery_mode=2) # 设置消息的持久化channel.basic_publish(exchange=rabbitmq_exchange,routing_key=routing_key,body=message,properties=properties # 将properties传递给basic_publish方法)print(f"Sent message {i+1}")# 根据速率限制暂停一段时间time.sleep(sleep_time_between_messages)# 关闭连接
connection.close()
Consumer消费classic队列脚本
import pika
import time# RabbitMQ 连接参数
rabbitmq_host = '192.168.123.242'
rabbitmq_port = 16027 # 默认RabbitMQ端口,如果使用了非默认端口,请修改
rabbitmq_user = 'admin' # 默认RabbitMQ用户,如果使用了其他用户,请修改
rabbitmq_password = 'Admin.123' # 默认RabbitMQ密码,如果使用了其他密码,请修改
queue_name = 'queue_classic_02' # 您的队列名
rate_limit = 500 # 每秒处理消息的最大数量(这里作为示例,您可以根据需要调整)# 创建连接参数
credentials = pika.PlainCredentials(rabbitmq_user, rabbitmq_password)
parameters = pika.ConnectionParameters(host=rabbitmq_host, port=rabbitmq_port, credentials=credentials)# 连接到 RabbitMQ 服务器
connection = pika.BlockingConnection(parameters)
channel = connection.channel()# 确保队列存在(这一步是可选的,因为如果在队列不存在时尝试消费,RabbitMQ会自动创建它,但声明队列可以确保它具有预期的属性)
channel.queue_declare(queue=queue_name, durable=True)# 消费消息的回调函数
def callback(ch, method, properties, body):# 打印接收到的消息print(f" [x] Received message: {body.decode('utf-8')}")# 根据速率限制调整睡眠时间# 注意:这里的速率限制是基于消息的处理时间,而不是消息的接收时间# 如果处理时间很短,但实际希望减慢消费速度,可以保留这个sleep# 如果处理时间已经足够长,或者希望尽可能快地处理消息,可以移除这个sleeptime.sleep(1 / rate_limit)# 手动确认消息(如果队列配置为手动消息确认)# 注意:这一步是在启用了QoS(basic_qos)之后才有意义的ch.basic_ack(delivery_tag=method.delivery_tag)# 设置QoS(Quality of Service),限制未确认消息的数量
channel.basic_qos(prefetch_count=1)# 监听队列,并将回调函数设置为callback
channel.basic_consume(queue=queue_name, on_message_callback=callback)print(f" [*] Waiting for messages in {queue_name}. To exit press CTRL+C")
try:# 开始消费消息,这是一个阻塞调用channel.start_consuming()
except KeyboardInterrupt:print(" [*] Interrupted by user. Shutting down.")
finally:# 确保在退出时关闭连接connection.close()
消息数据丢失测试
ha-mode: exactly镜像复制策略测试:
创建queue
rabbitmqadmin declare queue name=queue_classic_01 durable=true
创建exchange及bingding
rabbitmqadmin declare exchange name=exchange-01 type=direct durable=true
rabbitmqadmin declare binding source=exchange-01 destination=queue_classic_01 routing_key=queue_classic_01
设置镜像复制策略
rabbitmqadmin declare policy name=ha-three-replicas pattern="^queue_classic_01$" definition='{"ha-mode":"exactly","ha-params":3,"ha-sync-mode":"automatic"}' apply-to=queues
使用脚本写如10000条数据(需要开启消息内容持久化参数,delivery_mode=2)
手动删除1个pod,模拟集群节点宕机
Pod重建后,队列中的消息仍存在
继续进行删除2个pod测试
从结果看,删除2个pod的情况下,数据丢失了。
ha-mode: all镜像复制策略测试:
创建queue
rabbitmqadmin declare queue name=queue_classic_02 durable=true
创建exchange及bingding
rabbitmqadmin declare exchange name=exchange-02 type=direct durable=true
rabbitmqadmin declare binding source=exchange-02 destination=queue_classic_02 routing_key=queue_classic_02
设置镜像复制策略(允许个节点宕机)
rabbitmqadmin declare policy name=ha-three-replicas2 pattern="^queue_classic_02$" definition='{"ha-mode":"all","ha-sync-mode":"automatic"}' apply-to=queues
使用脚本写如10000条数据(需要开启消息内容持久化参数,delivery_mode=2)
删除2个pod,模拟2个节点宕机
Pod 重新后,数据仍然在
镜像策略对队列数据冗余的影响
镜像复制策略ha-mode: exactly
测试一是使用以下策略:
rabbitmqadmin declare policy name=ha-three-replicas pattern="^queue_classic_01$" definition='{"ha-mode":"exactly","ha-params":3,"ha-sync-mode":"automatic"}' apply-to=queues
- ha-mode: exactly:这表示队列的镜像将精确复制到指定数量的节点上。
- ha-params: 3:这指定了镜像的数量为3。
- ha-sync-mode: automatic:这表示队列的镜像将自动同步到指定的节点上
数据丢失原因分析:
如果集群中只有3个节点:
- 由于设置了ha-params: 3,这意味着队列queue_classic_01的镜像需要被复制到3个节点上。
- 在一个只有3个节点的集群中,如果2个节点宕机,那么只剩下1个节点存活。
- 由于没有足够的存活节点来存储3个镜像,因此这个队列的镜像将无法完全复制,并且会导致数据丢失(或者更准确地说,是数据将不再具有冗余性,因为只有一个存活节点上有数据)。
如果集群中有超过3个节点:
- 在这种情况下,即使2个节点宕机,仍然有足够的存活节点来存储3个镜像。
- 因此,数据不会丢失,因为至少有一个存活节点上保存有队列的完整数据,并且其他存活节点上也可能有该数据的镜像。
镜像复制策略ha-mode: all
ha-mode: all:这表示队列的镜像将被复制到集群中的所有节点。
ha-sync-mode: automatic:这表示队列的镜像将自动同步到所有节点上。
现在,我们来分析在3节点的RabbitMQ集群中,如果2个节点宕机,数据的情况:
队列镜像的复制:
由于 ha-mode 被设置为 “all”,队列 queue_classic_01 的镜像将被复制到集群中的所有3个节点上。
节点宕机的影响:
- 在一个3节点的集群中,如果2个节点宕机,那么只剩下1个节点存活。
- 但是,由于队列的镜像已经被复制到所有3个节点,因此即使2个节点宕机,剩下的1个节点上仍然保存有队列的完整数据。
数据的丢失:
- 在这种情况下,数据不会丢失,因为至少有一个存活节点上保存有队列的完整数据。
- 然而,队列的可用性可能会受到影响,因为RabbitMQ通常需要在多个节点上维护队列的镜像以确保高可用性。在只有一个存活节点的情况下,如果该节点也宕机,那么数据将无法访问,直到至少一个其他节点恢复并重新加入集群。
综上所述,在3节点的RabbitMQ集群中,如果您设置了 ha-mode: all 并且2个节点宕机,数据不会丢失,因为至少有一个存活节点上保存有队列的完整数据。
但是,请注意,这种配置下的队列可用性可能会受到影响,特别是在只有一个存活节点的情况下。为了保持高可用性,建议确保集群中有足够数量的健康节点,或者考虑使用其他容错和恢复策略。
pod节点宕机数量测试
同时运行2个python脚本连接Rabbitmq服务的k8 集群Node IP和NodePort,分别用于生产和消费消息,观察使用ha-mode:all 参数时,服务和数据的可用性。
通过web UI管理界面可以看到连接的是哪个Rabbitmq的pod节点
删除1个pod, 只要是生产和消费消息客户端正在连接的pod,生产和消费均不受影响。
Web管理界面可以看到节点故障及恢复的过程
同时删除2个pod节点,只有在删除的pod节点不是队列的leader,并且是生产或者消费客户端的连接的pod节点不是leader,同时删除2个pod节点, 生产和消费服务也不会中断。
如果删除的pod节点是leader节点,或者生产或者消费客户端连接的pod节点,相应的连接会中断,但重新连接后,不影响生产和消费数据。
仲裁队列测试
工作原理
RabbitMQ的Quorum Queue(仲裁队列)是其提供的一种高可用队列实现,旨在解决镜像队列的性能和同步问题。以下是RabbitMQ仲裁队列的工作原理:
基本概念
仲裁队列:RabbitMQ从3.8.0版本开始引入仲裁队列功能,作为镜像队列的替代方案。仲裁队列具有队列复制的能力,可以保障数据的高可用和安全性。
Raft算法:仲裁队列使用Raft算法实现了持久的、复制的FIFO(先进先出)队列。Raft算法是一种用于管理复制日志的共识算法,它提供了数据一致性和容错性。
工作原理
- 队列复制:
仲裁队列会在RabbitMQ节点间进行队列数据的复制。
当一个节点宕机时,由于数据已经复制到其他节点,因此队列仍然可以提供服务。 - 消息写入:
在仲裁队列中,消息要有集群中多半节点同意后,才会被写入队列。
这种写入机制保证了消息在集群内部不会丢失。 - 消息读取:
消费者可以从仲裁队列中读取消息,并进行相应的处理。
读取过程不会影响队列的复制和写入操作。 - 容错与安全性:
仲裁队列通过复制和Raft算法保证了数据的高可用性和安全性。
即使部分节点宕机,只要剩余节点数量超过半数,仲裁队列仍然可以正常工作。 - 性能优化:
仲裁队列使用了Raft协议,相比镜像队列的算法更有效率,可以提供更好的消息吞吐量。
同时,仲裁队列也支持节点的滚动升级,提高了系统的可维护性。
使用场景与限制 - 使用场景:
仲裁队列适用于长期存在的队列,以及对容错和数据安全方面要求较高的场景。
它不适合用于临时使用的队列,如transient临时队列、exclusive独占队列,或者经常会修改和删除的队列。 - 限制:
仲裁队列当前会将所有消息始终保存在内存中,直到达到内存使用极限。因此,在内存使用量较高时,可能会导致集群不可用。
仲裁队列的磁盘和内存配置与普通队列不同,写入放大可能导致更大的磁盘使用。因此,在使用仲裁队列时,需要进行合理的规划和监控。
综上所述,RabbitMQ的仲裁队列通过复制和Raft算法实现了数据的高可用性和安全性。然而,在使用时也需要注意其限制和性能特点,以确保系统的稳定运行。
测试脚本
Producer
Exchange的Routing key和队列名一样
import pika
import time
import random
import string# RabbitMQ连接参数
rabbitmq_host = '192.168.123.242' # 替换为你的RabbitMQ服务器地址
rabbitmq_port = 16027 # RabbitMQ端口(注意:这通常不是默认端口,确保这是正确的)
rabbitmq_vhost = '/' # 虚拟主机
rabbitmq_username = 'admin' # RabbitMQ用户名
rabbitmq_password = 'Admin.123' # RabbitMQ密码
rabbitmq_queue = 'queue_quorum_01' # 目标队列名(注意:这里应该是队列名,而不是“quorum队列名”,除非你有特别的定义)
rabbitmq_exchange = 'exchange_quorum_01' # 自定义exchange名
num_messages = 10000 # 要发送的消息数量
message_size_kb = 1 # 每条消息的大小(KB)
message_size_bytes = message_size_kb * 1024 # 每条消息的大小(字节)# 建立连接和通道
credentials = pika.PlainCredentials(rabbitmq_username, rabbitmq_password)
connection = pika.BlockingConnection(pika.ConnectionParameters(host=rabbitmq_host,port=rabbitmq_port,virtual_host=rabbitmq_vhost,credentials=credentials
))
channel = connection.channel()# 声明自定义的exchange(如果需要持久化,设置durable=True)
channel.exchange_declare(exchange=rabbitmq_exchange, exchange_type='direct', durable=True)# 确保队列存在(对于RabbitMQ的普通队列,你需要声明它;但对于Quorum队列,这通常是由RabbitMQ集群管理的)
# 注意:如果你的队列是Quorum队列,并且已经由RabbitMQ集群正确配置,那么你可能不需要再次声明它。
# 但是,如果你想让这个队列也接收来自新exchange的消息,你可能需要重新绑定它(这取决于你的配置)。
# 这里我们假设队列已经存在并且配置正确。# 生成随机字符串的函数,长度为指定的字节数
def generate_random_message(size_bytes):letters = string.ascii_letters + string.digits # 字母和数字# 注意:由于我们后面进行了切片操作[:size_bytes],所以最终长度会是正确的# 但是,这种方法可能不是最高效的,因为它首先生成了一个更大的字符串,然后又切掉了多余的部分。# 一个更优化的方法是直接生成指定大小的字节数组。# 不过,为了保持与原始脚本的一致性,我们在这里还是使用原始方法。return ''.join(random.choice(letters) for _ in range((size_bytes * 8) // len(letters))).encode('utf-8')[:size_bytes]# 批量发送消息
for i in range(num_messages):message = generate_random_message(message_size_bytes)routing_key = rabbitmq_queue # 使用队列名作为routing key(对于direct类型的exchange)# 设置消息的properties,包括delivery_mode=2(持久化消息)properties = pika.BasicProperties(delivery_mode=2) # 设置消息的持久化channel.basic_publish(exchange=rabbitmq_exchange,routing_key=routing_key,body=message,properties=properties # 将properties传递给basic_publish方法)print(f"Sent message {i+1}")# 关闭连接
connection.close()
consumer
import pika
import time# 连接参数
rabbitmq_host = '192.168.123.242' # 替换为你的RabbitMQ服务器IP
rabbitmq_port = 16027 # 替换为你的RabbitMQ服务器端口,如果使用了默认端口则无需修改
rabbitmq_user = 'admin' # 替换为你的RabbitMQ账号
rabbitmq_password = 'Admin.123' # 替换为你的RabbitMQ密码# 队列名(已经存在的Quorum队列)
queue_name = 'queue_quorum_01'# 连接到RabbitMQ服务器
credentials = pika.PlainCredentials(rabbitmq_user, rabbitmq_password)
parameters = pika.ConnectionParameters(host=rabbitmq_host,port=rabbitmq_port,credentials=credentials
)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()# 定义一个回调函数来处理从队列中接收到的消息
def callback(ch, method, properties, body):# 假设消息是字符串(不是JSON),因为生产者发送的是字符串message = body.decode('utf-8') # 解码为字符串print(f"Received message: {message}")# 模拟消息处理时间time.sleep(0.01) # 假设处理每条消息需要1秒# 在这里处理消息# 告诉RabbitMQ使用callback函数来处理从指定队列中接收到的消息
# 设置QoS(Quality of Service),每次只处理一条消息,直到手动ack
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=False)# 告诉RabbitMQ我们完成了消息的设置,现在我们可以手动ack消息了
def manual_ack(delivery_tag):channel.basic_ack(delivery_tag=delivery_tag)# 包装callback函数以支持手动ack
def wrapped_callback(ch, method, properties, body):callback(ch, method, properties, body)manual_ack(method.delivery_tag)# 替换原有的callback为支持QoS的callback
channel.basic_consume(queue=queue_name, on_message_callback=wrapped_callback)print(f'Waiting for messages in {queue_name}. To exit press CTRL+C')
channel.start_consuming()
高可用测试
创建queue
rabbitmqadmin declare queue name=queue_quorum_01 durable=true arguments='{"x-queue-type": "quorum"}'
创建exchange及bingding
rabbitmqadmin declare exchange name=exchange_quorum_01 type=direct durable=true
rabbitmqadmin declare binding source=exchange_quorum_01 destination=queue_quorum_01 routing_key=queue_quorum_01
手动删除1个pod节点,模拟3节点集群中1个节点宕机的故障,只要客户端连接的不是被停止的pod节点,客户端生产和消费都是正常的。通过web管理界面看,队列的状态是:running
此时如果继续删除第2个pod节点,模拟3节点集群中2个节点宕机的故障,在k8s中使用operator部署的RabbitMQ集群,在手动执行删除第2个pod是,命令将被挂起(无反应)直到operator通过内容部控制机制将第1个删除的pod重启成功,才会继续执行第2个pod的删除操作。此时Rabbimq服务是正常状态(如果客户端连接的是被删除pod节点,连接会被断开,重连后连接被svc 负载到其他pod节点,可以正常读写数据)。这应该是RabbitMQ operator控制的效果,在3节点的集群中,确保同时只能1个pod节点宕机,服务不受影响。
另外在裸金属部署的3节点RabbitMQ集群中进行了类似测试,使用“rabbitmq stop_app”同时停止2个节点(非读写客户端正在连接的节点),此时RabbitMQ处于“minority”(少数)状态,这正是quorum队列需要超过半数节点正常才能正常工作的工作机制。
此时,写客户端(producer)的连接状态虽然为‘running’但实际测试是没有数据写入到服务器,读客户端(consumer)的连接状态为“flow”,也不能从服务器获取数据。
相关文章:
RabbitMQ 从入门到精通:从工作模式到集群部署实战(四)
#作者:闫乾苓 系列前几篇: 《RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)》:link 《RabbitMQ 从入门到精通:从工作模式到集群部署实战(二)》: lin…...
Ubuntu 下 nginx-1.24.0 源码分析 - ngx_get_options函数
声明 就在 main函数所在的 nginx.c 中: static ngx_int_t ngx_get_options(int argc, char *const *argv); 实现 static ngx_int_t ngx_get_options(int argc, char *const *argv) {u_char *p;ngx_int_t i;for (i 1; i < argc; i) {p (u_char *) argv[i]…...
TCP长连接、HTTP短轮询、HTTP长轮询、HTTP长连接、WebSocket的区别
1.TCP长连接 (1)概念:该连接属于传输层的协议。客户端和服务器之间建立连接后,不立即断开该连接,而是一直保持这个状态,以便后续数据的持续、连续传输。(2)应用场景:适合…...
在 Flownex 中创建自定义工作液
在这篇博文中,我们将了解如何在 Flownex 中为流网添加和定义一种新的流体温度相关工作材料。 Flownex 物料管理界面 在 Flownex 中使用与温度相关的流体材料时,了解其特性与温度的关系非常重要。这种了解可确保准确预测各种热条件下的流体行为࿰…...
基于Spring Boot的图书个性化推荐系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
【抽象代数】1.1. 运算及关系
集合与映射 定义1. 设 为 的子集,定义 到 的映射 : 使得 ,称 为 到 的嵌入映射。 定义2. 设 为 的子集, 为 到 的映射, 为 到 的映射,如果 ,称为的开拓, 为 的限制&…...
拥抱开源,助力创新:IBM永久免费云服务器助力开源项目腾飞
近年来,开源项目蓬勃发展,为全球科技进步做出了巨大贡献。然而,服务器成本高昂常常成为开源项目的巨大障碍。许多优秀的项目因缺乏资源而难以持续发展,甚至夭折。令人振奋的是,IBM云计算平台推出了一项重磅活动&#x…...
Windows Docker笔记-简介摘录
Docker是一个开源的容器化平台,可以帮助开发人员将应用程序与其依赖项打包在一个独立的容器中,然后在任何安装的Docker的环境中快速、可靠地运行。 几个基本概念和优势: 1. 容器 容器是一个轻量级、独立的运行环境,包含了应用程…...
threejs 建筑设计(室内设计)软件 技术调研之五 墙体生成后自动生成房间(地面)
运用threejs 开发 建筑设计(室内设计)软件 技术调研 四 墙体添加真实门窗并保持原材质 在线体验地址:http://47.96.130.245:8080/design/index.html 实现功能: 墙体材质变换后,自动根据墙体的顶点生成相应的房间 视…...
C++:string类的模拟实现
目录 1.引言 2.C模拟实现 2.1模拟实现构造函数 1)直接构造 2)拷贝构造 2.2模拟实现析构函数 2.3模拟实现其他常规函数 1)c_str函数 2)size函数 3)begin/end函数 4)reserve函数 5)re…...
UE制作2d游戏
2d免费资产: Free 2D Game Assets - CraftPix.net 需要用到PaperZD插件 官网下载后启用即可 导入png素材 然后全选 - 创建Sprite 创建 人物基类 设置弹簧臂和相机 弹簧臂设置成旋转-90 , 取消碰撞测试 设置子类Sprite 拖到场景中 绑定设置输入映射,让角色移动跳跃 神似卡拉比…...
verilog练习:i2c slave 模块设计
文章目录 前言1. 结构2.代码2.1 iic_slave.v2.2 sync.v2.3 wr_fsm.v2.3.1 状态机状态解释 2.4 ram.v 3. 波形展示4. 建议5. 资料总结 前言 首先就不啰嗦iic协议了,网上有不少资料都是叙述此协议的。 下面将是我本次设计的一些局部设计汇总,如果对读者有…...
mysql的语句备份详解
使用mysqldump工具备份(适用于逻辑备份) mysqldump是 MySQL 自带的一个非常实用的逻辑备份工具,它可以将数据库中的数据和结构以 SQL 语句的形式导出到文件中。 1. 备份整个数据库 mysqldump -u [用户名] -p [数据库名] > [备份文件名].…...
BFS算法篇——广度优先搜索,探索未知的旅程(上)
文章目录 前言一、BFS的思路二、BFS的C语言实现1. 图的表示2. BFS的实现 三、代码解析四、输出结果五、总结 前言 广度优先搜索(BFS)是一种广泛应用于图论中的算法,常用于寻找最短路径、图的遍历等问题。与深度优先搜索(DFS&…...
短剧APP开发:短剧风口下的机遇
今年春节以来,各种精品短剧不断上线,短剧赛道热度持续上升,展现出了强大的经济效益,为影视市场提供了越来越多的机会。短剧的持续火爆也带动了短剧APP的发展,“短剧”迎来了爆发式增长,本文将探讨短剧APP开…...
PT2021K 单触控同步输出 IC
1. 产品概述 PT2021K 是一款电容式触摸控制 ASIC ,支持 1 通道触摸输入, 1 通道同步型开关输出。具有低 功耗、高抗干扰、宽工作电压范围、高穿透力的突出优势。 2. 主要特性 工作电压范围: 2.4~5.5V 待机电流约 9uAV DD 5V&…...
[论文笔记] Deepseek-R1R1-zero技术报告阅读
启发: 1、SFT&RL的训练数据使用CoT输出的格式,先思考再回答,大大提升模型的数学与推理能力。 2、RL训练使用群体相对策略优化(GRPO),奖励模型是规则驱动,准确性奖励和格式化奖励。 1. 总体概述 背景与目标 报告聚焦于利用强化学习(RL)提升大型语言模型(LLMs)…...
选择LabVIEW开发外协还是自己做开发?
在决定是否选择外协进行LabVIEW开发时,选择合适的外协团队是至关重要的。一个专业的外协团队不仅能提高项目的开发效率,还能帮助解决技术难题,确保项目的高质量交付。在选择团队时,以下几个方面值得特别注意: 1. 团…...
网络工程师 (20)计算机网络的概念
一、定义 计算机网络是指将地理位置不同、具有独立功能的多台计算机及其外部设备,通过通信线路及通信设备连接起来,在网络操作系统、网络管理软件及网络通信协议的管理和协调下,实现信息传递和资源共享的计算机通信系统。 二、组成 资源子网&…...
RockyLinux9.4安装教程
Rocky Linux 9.4 安装教程 Rocky Linux 是由 CentOS 创始人 Gregory Kurtzer 发起的一个社区企业操作系统,旨在与 Red Hat Enterprise Linux (RHEL) 保持二进制兼容。本教程将详细介绍如何在您的系统上安装 Rocky Linux 9.4。 步骤 1: 下载 Rocky Linux 9.4 ISO 文件 打开 R…...
单片机之基本元器件的工作原理
一、二极管 二极管的工作原理 二极管是一种由P型半导体和N型半导体结合形成的PN结器件,具有单向导电性。 1. PN结形成 P型半导体:掺入三价元素,形成空穴作为多数载流子。N型半导体:掺入五价元素,形成自由电子作为多…...
TOTP实现Google Authenticator认证工具获取6位验证码
登录遇到Google认证怎么办? TOTP是什么?(Google Authenticator) TOTP(Time-based One-Time Password)是一种基于时间的一次性密码算法,主要用于双因素身份验证。其核心原理是通过共享密钥和时间同步生成动态密码,具体步骤如下: 共享密钥:服务端与客户端预先共享一个…...
114,【6】攻防世界 web wzsc_文件上传
进入靶场 传个桌面有的 直接空白了 我们 访问一下上传的东西 /index 没显示用于解析的.htaccess和.user.ini 文件,还两个都不显示 .htaccess 和 .user.ini 文件分别用于 Apache 服务器和 PHP-FPM 环境的目录级配置 但上传的时候bp查看状态码是200,…...
Rust unresolved import `crate::xxx` 报错解决
问题阐述 该问题出现在我使用actix编写的crud后端api中,我的后端结构如下: D:. | handle_err.rs | lib.rs | main.rs | ---application | mod.rs | user_service.rs | ---domain | dto.rs | mod.rs | user.rs | ---infrastru…...
dl学习笔记(9):pytorch数据处理的完整流程
1)自动导入常用库的设置方式 在开始之前,这里介绍一下自动导入常用的你需要加载的库的操作方式。 首先在我们的目录下找到ipython文件,如下图: 然后找到里面的startup文件: 然后新建一个文本文档,输入你每…...
结构化表达(三):归纳分组
目录 归纳分组 一、如何归纳分组 二、如何掌握更多模型 归纳分组 一、如何归纳分组 整理思路,多用分类模型,列如: 1、内部、外部分类。 2、市场营销学中的4P:产品、渠道、价格、促销。 3、战略3C:公司、客户、竞…...
BUU17 [RoarCTF 2019]Easy Calc1
自用 源代码 $(#calc).submit(function(){$.ajax({url:"calc.php?num"encodeURIComponent($("#content").val()),type:GET,success:function(data){$("#result").html(<div class"alert alert-success"><strong>答案:&l…...
openGauss 3.0 数据库在线实训课程2:学习客户端工具gsql的使用
openGauss数据库状态查看 前提 我正在参加21天养成好习惯| 第二届openGauss每日一练活动 课程详见:openGauss 3.0.0数据库在线实训课程 学习目标 学习openGauss数据库客户端工具gsql的使用。 课程作业 gsql是openGauss提供在命令行下运行的数据库连接工具&am…...
mac环境下,ollama+deepseek+cherry studio+chatbox本地部署
春节期间,deepseek迅速火爆全网,然后回来上班,我就浅浅的学习一下,然后这里总结一下,我学习中,总结的一些知识点吧,分享给大家。具体的深度安装部署,这里不做赘述,因为网…...
C# SQlite使用流程
前言 不是 MySQL 用不起,而是 SQLite 更有性价比,绝大多数的应用 SQLite 都可以满足。 SQLite 是一个用 C 语言编写的开源、轻量级、快速、独立且高可靠性的 SQL 数据库引擎,它提供了功能齐全的数据库解决方案。SQLite 几乎可以在所有的手机…...
mysql8 C++源码中创建表函数,表字段最大数量限制,表行最大存储限制
在 MySQL 8 的 C 源码中,表的最大字段数量限制体现在 MAX_FIELDS 宏定义中。这个宏定义了表中可以拥有的最大字段数量。 代码中的体现 在 mysql_prepare_create_table 函数中,有以下代码段检查表的字段数量是否超过最大限制: cpp if (alt…...
stable diffusion安装包与常用模型下载
本地安装包(直接借用秋叶大佬的包)🙏 1.最好用的 Forge 版整合包 网盘:https://pan.quark.cn/s/4e9459b17c94 解压密码:bilibili-秋葉aaaki 2.最好用的整合包! https://pan.baidu.com/s/1MjO3CpsIvTQID…...
Redis | 十大数据类型
文章目录 十大数据类型概述key操作命令数据类型命令及落地运用redis字符串(String)redis列表(List)redis哈希表(Hash)redis集合(Set)redis有序集合(ZSet / SortedSet&…...
19爬虫:使用playwright登录超级鹰
本次案例一共解决了如下两个问题: (1)如何使用playwright截图,特别是验证码图片 (2)在playwright中如何判断一个元素可见 1.截图 我们首先解决第一个问题,截图。如果前期接触过selenium的小伙…...
强化学习笔记6——异同策略、AC、等其他模型总结
异步两种方法:1:经验回放 2:数据动作非同时产生 举例QLearning为什么是异策略? 生成动作时e的概率从Q表选,1-e概况随机。 更新策略时,贪心策略选择Q_max作为动作。 策略优化两种主要方法:基于梯…...
【工具变量】上市公司企业渐进式创新程度及渐进式创新锁定数据(1991-2023年)
测算方式: 参考顶刊《经济研究》孙雅慧(2024)老师的做法,用当期创新和往期创新的内容重叠度作为衡量渐进式创新程度的合理指标。通过搜集海量专利摘要,测算当前专利申请和既有专利的内容相似度,反映企业在…...
高级测试工程师,在接口自动化方面,如何用AI提升?DeepSpeek回答
思考过程: 嗯,用户现在的问题是作为高级测试工程师,在接口自动化方面如何用AI来提升。首先,我需要回顾之前的对话历史,看看之前给过哪些建议。之前讨论过AI在测试中的核心应用场景,比如智能测试用例生成、自…...
每日一题——插入排序实现数据流中的中位数
插入排序实现数据流中的中位数 题目描述功能要求数据范围 解题思路算法流程 代码实现代码详解1. 全局变量2. Insert 函数3. GetMedian 函数 复杂度分析Insert 函数GetMedian 函数空间复杂度(整体) 注意事项 题目描述 设计一个算法,用来计算数…...
arcgis for js范围内天地图高亮,其余底图灰暗
在GIS地图开发中,有时我们需要突出显示某个特定区域,而将其他区域灰暗处理,以达到视觉上的对比效果。本文将介绍如何使用ArcGIS for JavaScript实现这一功能,具体效果为:在指定范围内,天地图高亮显示&#…...
【Unity】从父对象中获取子对象组件的方式
1.GetComponentInChildren 用于获取对与指定组件或游戏对象的任何子级相同的游戏对象上的组件类型的引用。 该方法在Unity脚本API的声明格式为: public T GetComponentInChildren(bool includeInactive false) includeInactive参数(可选)…...
code run使用vs2015工具链构建
"cpp": "cmd.exe /C \"\"D:\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" x86 && cl.exe $fileName /Fe:$fileNameWithoutExt.exe && $dir$fileNameWithoutExt.exe\"" 效果如下: // hello#includ…...
matlab快速入门(2)-- 数据处理与可视化
MATLAB的数据处理 1. 数据导入与导出 (1) 从文件读取数据 Excel 文件:data readtable(data.xlsx); % 读取为表格(Table)CSV 文件:data readtable(data.csv); % 自动处理表头和分隔符文本文件:data load(data.t…...
UnityShader学习笔记——动态效果
——内容源自唐老狮的shader课程 目录 1.原理 2.Shader中内置的时间变量 3.Shader中经常会改变的数据 4.纹理动画 4.1.背景滚动 4.1.1.补充知识 4.1.2.基本原理 4.2.帧动画 4.2.1.基本原理 5.流动的2D河流 5.1.基本原理 5.2.关键步骤 5.3.补充知识 6.广告牌效果 …...
Docker Desktop安装到其他盘
Docker Desktop 默认安装到c盘,占用空间太大了,想给安装到其他盘,网上找了半天的都不对 正确安装命令: start /w "" "Docker Desktop Installer.exe" install --installation-dirF:\docker命令执行成功&am…...
详细教程 | 如何使用DolphinScheduler调度Flink实时任务
Apache DolphinScheduler 非常适用于实时数据处理场景,尤其是与 Apache Flink 的集成。DolphinScheduler 提供了丰富的功能,包括任务依赖管理、动态调度、实时监控和日志管理,能够有效简化 Flink 实时任务的管理和部署。通过 DolphinSchedule…...
稻盛和夫如何描述能力
1. 能力的三要素 稻盛和夫认为,能力由以下三个核心要素组成: 知识(Knowledge):掌握的专业知识、技术技能和行业经验。 技能(Skill):将知识应用于实际工作的能力,包括解决…...
【LeetCode 刷题】贪心算法(4)-区间问题
此博客为《代码随想录》二叉树章节的学习笔记,主要内容为贪心算法区间问题的相关题目解析。 文章目录 55. 跳跃游戏45. 跳跃游戏 II452. 用最少数量的箭引爆气球435. 无重叠区间763. 划分字母区间56. 合并区间 55. 跳跃游戏 题目链接 class Solution:def canJump…...
javaEE初阶————多线程初阶(3)
大家新年快乐呀,今天是第三期啦,大家前几期的内容掌握的怎么样啦? 1,线程死锁 1.1 构成死锁的场景 a)一个线程一把锁 这个在java中是不会发生的,因为我们之前讲的可重入机制,在其他语言中可…...
Deep Sleep 96小时:一场没有硝烟的科技保卫战
2025年1月28日凌晨3点,当大多数人还沉浸在梦乡时,一场没有硝烟的战争悄然打响。代号“Deep Sleep”的服务器突遭海量数据洪流冲击,警报声响彻机房,一场针对中国关键信息基础设施的网络攻击来势汹汹! 面对美国发起的这场…...
【AI应用】免费的文本转语音工具:微软 Edge TTS 和 开源版 ChatTTS 对比
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】【AI应用】 我试用了下Edge TTS,感觉还不错,不过它不支持克隆声音(比如自己的声音) 微软 Edge TTS 和 开源版 ChatTTS 都是免费的 文本转语音&…...