当前位置: 首页 > news >正文

【Java微服务组件】分布式协调P1-数据共享中心简单设计与实现

欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
欢迎评论交流,感谢您的阅读😄。

目录

  • 引言
  • 设计一个共享数据中心
    • 选择数据模型
      • 键值对设计
    • 数据可靠性设计
      • 持久化
        • 快照 (Snapshotting/RDB-like)
        • 操作日志(Write-Ahead Log -WAL /Append-Only File AOF-like)
        • 快照与日志融合使用(Snapshot + WAL)
      • 一致性
    • 监控数据状态
      • 状态通知机制
      • 集群间数据同步
  • 实现一个共享数据中心

引言

分布式微服务架构中,有一些常见的分布式协调问题,如配置管理、命名服务(共享服务实例的地址信息)、分布式锁、集群管理、Master选举等。
这些协调问题可以简单归纳为数据的共享与状态监控,我们需要解决这些问题来保障架构的可用、性能,同时降低耦合、开放拓展等。

为此,我们的框架需要一个可靠的、一致的、可观察的共享数据存储。
ZooKeeper就是这样的一个存在。

不过在深入了解ZooKeeper之前,我们先就这些特性来简单实现一下共享数据存储。
在上篇注册中心中,我们设计了主从读写分离、数据安全结构与读写锁,实现了简单的注册中心。其数据共享主要是集群内数据共享。
本篇共享数据存储是面对整个架构的所有服务的。

设计一个共享数据中心

首先,我们需要考虑数据以什么样的形式、什么样的结构存在。
是仅存在于内存还是需要持久化,持久化是否需要进行事务特性实现以确保一致性……等等。

需要考虑的问题很多,首先,让我们来进行数据结构的选型、决定数据以什么样的形式存在。

选择数据模型

数据模型可以简单分为以下几种:

数据模型类型说明
键值对(Key-Value)这是最简单和最常见的数据模型。
比如Redis。
key一般为String类型、value的值灵活多变。

这样的结构易于实现,但查询能力优先,只能按键查找。
文档型(Document-Oriented)比如MongoDB。
数据以类似JSON或BSON的文档形式存储。

适合存储半结构化数据(不完全符合严格的表结构,但包含一些内部组织标记或元数据的数据),结构灵活、具备层次结构、可解析、字描述。

这样的结构易于解析和传输大量数据,但查询更难。
列式(Columnar)比如HBase或Cassandra。

适合大规模数据分析。

关系型(Relational)比如MySQL。

适合数据间有复杂关系,有事务需求时的选择。

实现一个完整的关系型数据库非常困难。
图(Graph)比如 Neo4j。

适合数据之前关系非常重要复杂的情况。

显然实现起来更复杂。

对于一个共享数据存储中心,它需要能存储各式各样的值,且用户服务可以很快速地获取共享的值。
那么选择“键值对”作为数据存储的结构是个很合理的选择。

键值对设计

键值对应该如何设计呢?
了解基本数据结构的我们很容易想到使用Hash计算的方式映射,用数组进行存储。
在Java中简单来说就是使用HashMap。共享数据存储中心需要考虑多个用户服务同时调用的情况,因此,我们的结构应当是线程安全的,即使用ConcurrentHashMap,或者对普通的HashMap使用锁,如读写锁。

数据可靠性设计

数据是仅内存(In-Memory Only)还是需要持久化(Persistence)呢?

  • 仅内存
    仅内存性能高,但共享数据存储中心一旦重启或崩溃,数据容易丢失。

  • 持久化
    持久化虽然更可靠,但是复杂度显著提升。
    需要以什么形式持久化?
    持久化是否需要事务来确保一致性(内存和持久化存储的一致性、并发访问的一致性)?
    以什么样的方式持久化?
    等,有很多需要考虑的地方,越可靠越复杂。
    因此必须在复杂度和可靠中做取舍,这往往也取决于对数据丢失的容忍度和数据的重要性。

持久化

持久化设计需要考虑持久化方式(Durability)与崩溃恢复(Crash Recovery)。从内存到磁盘的持久化有3种方式。

快照 (Snapshotting/RDB-like)

定期将内存中的整个数据结构完整地序列化到磁盘(分布式的“状态转移”)。
快照时,系统会创建一个当前内存数据结构的副本,对副本进行序列化操作。快照文件通常是二进制格式,考虑存储效率和加载速度。例如Redis的RDB文件。
快照操作后台线程执行,对正在运行的操作影响小。
但如果两次快照之间发生故障,快照之间的部分数据会丢失。且快照过程比较耗时且消耗I/O,尤其数据量大时。

快照可以设置的触发策略如下:

  • 基于时间的策略 (Time-based)
  • 基于操作数量\数据变化的策略(Change-based)
  • 基于日志文件大小(Log-size-based - 通常与WAL/AOF结合
  • 手动触发 (Manual Trigger)
  • 系统关闭 (On Shutdown)
操作日志(Write-Ahead Log -WAL /Append-Only File AOF-like)

数据操作日志 是一种按时间顺序记录所有对数据产生修改的“操作”的日志文件。 它记录的是如何达到当前数据状态的过程,而不是数据状态本身(分布式的“操作转移”)。
数据需要先记录到日志,然后再更新到内存中。

  • 追加写入(Append-Only)
    新的操作日志条目总是被添加到日志文件的末尾。顺序写入的方式通常比随机写入磁盘效率高。
  • 预写(Write-Ahead)
    在数据真正被修改到内存中的持久化结构(在数据被刷到数据文件)之前,描述该修改的日志条目必须首先被安全地写入到持久化的操作日志中并刷盘 (fsync)。

日志条目内容一般包含:操作类型、操作参数、事务信息、时间戳或其他序列号(LSN)。

操作日志恢复数据时,会从日志头开始读取文件并按顺序重新执行,从而在内存中重建数据状态。

这种方式数据持久性更好,崩溃时只丢失最后未刷盘的少量操作。缺点是恢复时需要重放所有日志,可能较慢,日志文件会不断增长,需要定期进行压缩或与快照结合。

快照与日志融合使用(Snapshot + WAL)

快照只能恢复数据的最终状态,且两次快照之间数据会丢失,虽然效率高,但是数据量大时I/O消耗大。
而日志模式虽然不会丢失太多数据,但是重放日志效率更低,日志数量多时恢复效率会显著下降。
因此,生产级方案往往是快照与日志融合。

  • 融合方案
    定期做快照,同时记录快照之后的WAL。恢复时先加载最近的快照,再重放后续的WAL。

比如Redis和MySQL(InnoDB)就是用的融合方案。

  • MySQL
    MySQL的InnoDB设计有重做日志Redo Log,在数据被刷入磁盘之前,数据修改的记录(Redo Log)必须先被写入到Redo Log Buffer,并从Buffer刷到磁盘的Redo Log文件中。
    在InnoDB中,Redo Log有一个概念为检查点(Checkpoint),它记录一个LSN(Log Sequence Number)。
    数据恢复时不需要重放所有的Redo Log,只需要从最近的Checkpoint开始重放。
    Checkpoint 确保了其记录点之前的所有脏页都已刷盘,因此Checkpoint之前的Redo Log文件都可以被覆盖。。这个机制解决了日志文件不断增长的问题。

  • Redis
    Redis有两种持久化方案:RDB与AOF,且可以同时开启。
    RDB对应快照,AOF对应操作日志。
    Redis应对操作日志不断增大的机制是 AOF重写(AOF Rewrite)。AOF重写在不中断服务的情况下,创建一个新的更小的AOF文件,新文件包含达到当前数据集状态所需的最小命令集。即去掉数据状态变更过程,只保留最新数据状态。
    Redis从4.0开始RDB-AOF混合持久化。AOF重写时,新的AOF文件可以配置为以RDB格式开头,后跟增量的AOF命令(aof-use-rdb-preamble yes)。
    新的 AOF 文件首先包含一个 RDB 快照部分(记录重写开始时的数据状态),然后是重写期间发生的增量写命令。
    这使得恢复时可以先加载 RDB 部分,然后只重放少量的 AOF 命令,大大加快了恢复速度,同时保留了 AOF 的高持久性。

比如我们计划使用ConcurrentHashMap,那么快照时就需要将这个ConcurrentHashMap序列化。
常见的方式是快照方式是非阻塞式的后台复制——写时复制(Copy-on-Write COW)。快照策略选择按数据量进行触发。
快照与日志融合方案使用MySQL的更为简单。

一致性

在通过持久化的方式来保证数据的可靠后,我们的共享数据存储中心有了一定的可用性保障。这时我们需要开始考虑数据的一致性。
但一个完整的ACID事务系统是极其困难的,设计到并发控制、恢复管理、日志管理等多个复杂的子系统。
因此,简单实现可以暂不追求完整的ACID事务。
仅先考虑基本的一致性,如简单原子性,批量操作提交视为一个整体。集群数据同步的一致性。

监控数据状态

状态通知机制

当数据发生变更时,我们需要一个机制能可靠地通知所有相关节点,并保证它们获取到的最新的、一致性的数据。
我们很容易想到观察者模式。当数据被修改时,告诉共享数据的订阅者数据已更改。
因此通知机制的前提是需要有一张注册表。
在上篇的注册中心我们已经知道,注册表可以通过心跳来维持其有效性。
但还有另一种做法,就是ZooKeeper的的Watcher机制。
每次修改数据通知完订阅者后,删除其在注册表中的信息,每次getData()时再重新注册。即一次性触发 (One-time Trigger)。
这样可以精简设计,省去心跳机制。

集群间数据同步

简单追求集群数据同步的强一致性,共享数据做读写分离处理提升性能。

实现一个共享数据中心

简单实现两个model类,用于存储在内存的共享数据对象ShareData

  
import lombok.Data;/*** 内存中的共享数据** @author crayon* @version 1.0* @date 2025/5/14*/
@Data
public class ShareData {private String id;/*** 数据更新时间戳*/private Long lsn;/*** 数据*/private Object data;/*** 数据版本*/private int version;public ShareData(String id1, String initialValueForKey1, int version) {this.id = id1;this.data = initialValueForKey1;this.version = version;this.lsn = System.currentTimeMillis();}public void incrementVersion() {this.version++;}
}

与用于序列化的PersistenceData

package com.crayon.datashare.model;import lombok.Data;import java.io.Serializable;/*** 内存共享数据的序列化对象** @author crayon* @version 1.0* @date 2025/5/15*/
@Data
public class PersistenceData implements Serializable {/*** 数据序列化时间*/private Long serialDateTime;/*** 操作类型*/private String operaType;/*** 数据key*/private String key;/*** 共享数据*/private ShareData shareData;public PersistenceData(Builder builder) {this.key = builder.key;this.shareData = builder.shareData;this.operaType = builder.operaType;this.serialDateTime = System.currentTimeMillis();}@Overridepublic String toString() {return "PersistenceData{" +"serialDateTime=" + serialDateTime +", operaType='" + operaType + '\'' +", key='" + key + '\'' +", shareData=" + shareData +'}';}// 建造者模式public static class Builder {private String key;private ShareData shareData;private String operaType;public Builder key(String key) {this.key = key;return this;}public Builder shareData(ShareData shareData) {this.shareData = shareData;return this;}public Builder operaType(String operaType) {this.operaType = operaType;return this;}public PersistenceData build() {return new PersistenceData(this);}}}

实现最重要的数据共享中心服务端


import com.crayon.datashare.model.ShareData;import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 简单的共享数据存储中心** <p>* 功能如下:* <p>* API-获取数据* API-存储数据* API-注册信息* <p>* 数据量到了应规模时进行序列化快照,存储数据时日志追加** @author crayon* @version 1.0* @date 2025/5/14*/
public class ShareDataServer {/*** 使用ConcurrentHashMap存储共享数据* <p>* keyName -> ShareData* </p>* <p>* 集群做读写分离设计,Leader-Follower 模型 。* master写同步到slave,slave节点读,负载均衡采用随机策略。* </p>* <p>* 数据容量暂不设置上限与对应清理机制。* </p>* <p>* 没有选举机制,也没有逻辑时钟* </p>*/private static ConcurrentHashMap<String, ShareData> shareDataMaster = new ConcurrentHashMap<>();private static ConcurrentHashMap<String, ShareData> shareDataSlave1 = new ConcurrentHashMap<>();private static ConcurrentHashMap<String, ShareData> shareDataSlave2 = new ConcurrentHashMap<>();/*** 用于随机获取从节点*/private static Random random = new Random();/*** <p>* keyName -> ReentrantReadWriteLock* </p>* 线程安全方案一:* <p>* 使用读写锁控制共享数据安全。* ConcurrentHashMap 操作数据是安全的,但是共享数据内容是可变的(Mutable)。* 当需要组合多个ConcurrentHashMap操作时,其是不安全的。* 其他线程可能在ConcurrentHashMap多个操作之间,对可变对象进行更改。* <p>* 因此需要读写锁来保证写入时候数据安全。* 在共享数据中,因为原子操作为:写数据+日志追加,所以更需要使用锁来控制。* <p>* 在分布式系统中,共享数据中心本身常被作为分布式锁使用。* <p>* 如果不是需要WAL,其实可以通过不可变对象(Immutable Objects)来消除数据共享来简化并发问题* </p>*/private static ConcurrentHashMap<String, ReentrantReadWriteLock> readWriteLocks = new ConcurrentHashMap<>();/*** 订阅者集合* <p>* 采取一次性触发机制(One-time Trigger),省去心跳检测的麻烦* 每次通知订阅者时,会从集合中移除订阅者,订阅者每次需要重新注册* 比如在调用get时重新注册* <p>* 订阅者也可以封装成一个对象,这里简单一点=ip:port* <p>* keyName -> Set<ip:port>* 使用线程安全的Set,如 ConcurrentHashMap.newKeySet()* </p>*/private static ConcurrentHashMap<String, Set<String>> subscribers = new ConcurrentHashMap<>();/*** Watcher*/private static Notifier notifier = new Notifier();/*** 序列化服务* 日志操作,数据恢复(暂无)等*/private static SerializableService serializableService = new SerializableService();/*** 获取共享数据* 采取一次性触发机制(One-time Trigger)由Server完成** @param key* @param ipPort (可选) 客户端标识,用于重新注册Watcher* @param watch  (可选) 是否要设置Watcher* @return*/public ShareData get(String key, String ipPort, boolean watch) {ReentrantReadWriteLock readWriteLock = readWriteLocks.computeIfAbsent(key, k -> new ReentrantReadWriteLock());readWriteLock.readLock().lock();try {ConcurrentHashMap<String, ShareData> readNode = getReadNode();ShareData shareData = readNode.get(key);if (watch && null != ipPort && !"".equals(ipPort) && null != shareData) {register(key, ipPort);}return shareData;} finally {readWriteLock.readLock().unlock();}}/*** 注册订阅者** @param key* @param ipPort*/public void register(String key, String ipPort) {// 使用ConcurrentHashMap.newKeySet() 创建一个线程安全的Setsubscribers.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()).add(ipPort);}/*** 添加共享数据* 组合 日志追加 + 添加 + 集群同步* <p>* 原子操作设计:* 一般这种带集群同步的标准方案是共识算法(Consensus Algorithm)。太复杂了,搞不来。** </p>** @param key* @param value*/public boolean set(String key, ShareData value) {ReentrantReadWriteLock readWriteLock = readWriteLocks.computeIfAbsent(key, k -> new ReentrantReadWriteLock());readWriteLock.writeLock().lock();try {// 1、写入日志 WALboolean logSuccess = serializableService.appendLog(OperaTypeEnum.SET.getType(), key, value);if (!logSuccess) {return false;}// 2、写入内存MastershareDataMaster.put(key, value);/*** 3、集群同步* 简单模拟,没有处理网络失败、异步、其他复杂ack机制等*/syncToSlave(key, value);// 4、通知订阅者,从注册表移除// 获取并移除,实现一次性触发Set<String> currentSubscribers = subscribers.remove(key);if (currentSubscribers != null && !currentSubscribers.isEmpty()) {for (String subscriberIpPort : currentSubscribers) {// 实际应用中,这里会通过网络连接向客户端发送通知notifier.notify(subscriberIpPort, key, OperaTypeEnum.CHANGE.getType());}}} catch (Exception e) {// 实际生产需要回滚等事务操作、日志记录等return false;} finally {readWriteLock.writeLock().unlock();}return true;}/*** 集群同步* <p>* 只在set操作中调用** @param key* @param value*/private void syncToSlave(String key, ShareData value) {shareDataSlave1.put(key, value); // 模拟同步到slave1shareDataSlave2.put(key, value); // 模拟同步到slave2}/*** 50%概率随机取节点** @return*/private ConcurrentHashMap<String, ShareData> getReadNode() {return random.nextBoolean() ? shareDataSlave1 : shareDataSlave2;}}

封装操作类型枚举


/*** 操作类型枚举*/
public enum OperaTypeEnum {GET("GET"),SET("SET"),CHANGE("CHANGE");private String type;OperaTypeEnum(String type) {this.type = type;}public String getType() {return type;}}

实现序列化方法


import com.crayon.datashare.model.PersistenceData;
import com.crayon.datashare.model.ShareData;import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;/*** 序列化服务** <p>* 序列化:采用快照+日志方式* 按数据量策略进行快照* <p>* 文件内容:类Redis融合方案* 快照内容+日志内容。文件前面是RDB格式,后面是AOF格式* <p>* RDB格式:* AOF格式* <p>* 文件大小处理:采用保留数据最终状态的压缩方案** <p>* 恢复机制:** @author crayon* @version 1.0* @date 2025/5/15*/
public class SerializableService {/*** 假设的日志文件名*/private static final String MASTER_LOG_FILE = System.getProperty("user.dir") + "/wal/master_wal.log";/*** 日志追加* <p>* 简化的日志格式,实际应该至少有操作类型、时间戳、序列号、状态码,* 数据库的话会有数据库的一些信息,如数据库名字、server id等* </p>* 生产日志会有压缩、刷盘等操作,这里简化了*/public boolean appendLog(String operaType, String key, ShareData value) {PersistenceData persistenceData = new PersistenceData.Builder().operaType(operaType).key(key).shareData(value).build();try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(MASTER_LOG_FILE, true)))) {// out.flush(); // 可以考虑更频繁的flush或根据策略fsyncout.println(persistenceData.toString());return true;} catch (IOException e) {System.err.println("Error writing to WAL: " + e.getMessage());return false;}}
}

封装一个用于通知的监视者Watcher


/*** @author crayon* @version 1.0* @date 2025/5/16*/
public class Notifier {/*** 通知订阅者** @param subscriberIpPort* @param key* @param operaType*/public void notify(String subscriberIpPort, String key, String operaType) {// 调用订阅者的接口,让订阅者进行相应的处理}
}

封装一个客户端

/*** 简单模拟客户端* <p>*     订阅和获取、更新数据等操作* </p>* * @author crayon* @version 1.0* @date 2025/5/16*/
public class SubscriberClient {private String ipPort;private ShareDataServer shareDataServer = new ShareDataServer();public SubscriberClient(String ipPort) {this.ipPort = ipPort;}public void subscribe(String key) {shareDataServer.register(key, ipPort);}public ShareData get(String key, String ipPort) {return shareDataServer.get(key, ipPort, true);}}

test


import com.crayon.datashare.client.SubscriberClient;
import com.crayon.datashare.model.ShareData;
import com.crayon.datashare.server.ShareDataServer;/*** 简单数据共享中心演示** @author crayon* @version 1.0* @date 2025/5/14*/
public class Demo {public static void main(String[] args) {ShareDataServer shareDataServer = new ShareDataServer();// 模拟客户端1注册对 key1 的订阅SubscriberClient subscriberClient8080 = new SubscriberClient("127.0.0.1:8080");subscriberClient8080.subscribe("key1");SubscriberClient subscriberClient8081 = new SubscriberClient("127.0.0.1:8081");subscriberClient8081.subscribe("key1");// 模拟客户端2注册对 key2 的订阅subscriberClient8081.subscribe("key2");System.out.println("\n Setting data for key1...");shareDataServer.set("key1", new ShareData("id1", "Initial Value for key1", 1));System.out.println("\n Getting data for key1 by client1 (will re-register watcher)...");ShareData data1 = shareDataServer.get("key1", "client1_ip:port", true);System.out.println("Client1 got: " + data1);System.out.println("\n Setting data for key1 again (client1 should be notified)...");shareDataServer.set("key1", new ShareData("id1", "Updated Value for key1", 2));System.out.println("\n Setting data for key2...");shareDataServer.set("key2", new ShareData("id2", "Value for key2", 1));System.out.println("\n Client2 getting data for key1 (not subscribed initially, but sets a watch now)...");ShareData data1_by_client2 = shareDataServer.get("key1", "client2_ip:port", true);System.out.println("Client2 got (for key1): " + data1_by_client2);System.out.println("\n Simulating a read from a random slave for key1:");ShareData slaveData = shareDataServer.get("key1", null, false); // No re-registerSystem.out.println("Read from slave for key1: " + slaveData);}}

结果展示如下
在这里插入图片描述

相关文章:

【Java微服务组件】分布式协调P1-数据共享中心简单设计与实现

欢迎来到啾啾的博客&#x1f431;。 记录学习点滴。分享工作思考和实用技巧&#xff0c;偶尔也分享一些杂谈&#x1f4ac;。 欢迎评论交流&#xff0c;感谢您的阅读&#x1f604;。 目录 引言设计一个共享数据中心选择数据模型键值对设计 数据可靠性设计持久化快照 &#xff08…...

数据库--向量化基础

本文包含内容有: 向量化、SIMD的概念及关系SSE,AVX-512八种基础的SIMD操作,并用具体例子解释,给出伪代码。一、快速了解向量化、SIMD 1.1 向量化 向量化是指将原本需要循环处理的多个数据元素,通过一条指令同时处理多个数据,从而减少循环次数,提高计算效率。 传统方式…...

handsome主题美化及优化:10.1.0最新版 - 2

文章目录 前言基础设置优化开启全站 HTTPS添加 GZIP 压缩美化永久链接自定义后台路径启用 Emoji 支持 功能增强每日新闻自动更新文章嵌入外部网页时光机栏目配置自定义音乐播放器音量 自定义CSS配置文章标题居中显示标题背景美化文章版式优化LOGO 扫光特效头像动画效果图片悬停…...

JWT令牌

1. JWT概述 JWT即JSON Web Token&#xff0c;是一个开放标准&#xff0c;用于在各方之间安全地传输信息。并且JWT经过数字签名&#xff0c;安全性高。通俗来说&#xff0c;也就是以JSON形式作为Web应用中的令牌&#xff0c;用于信息传输&#xff0c;在数据传输过程中可以完成数…...

Qwen3技术报告解读

https://github.com/QwenLM/Qwen3/blob/main/Qwen3_Technical_Report.pdf 节前放模型&#xff0c;大晚上的发技术报告。通义&#xff0c;真有你的~ 文章目录 预训练后训练Long-CoT Cold StartReasoning RLThinking Mode FusionGeneral RLStrong-to-Weak Distillation 模型结构…...

RAG-MCP:突破大模型工具调用瓶颈,告别Prompt膨胀

大语言模型&#xff08;LLM&#xff09;的浪潮正席卷全球&#xff0c;其强大的自然语言理解、生成和推理能力&#xff0c;为各行各业带来了前所未有的机遇。然而&#xff0c;正如我们在之前的探讨中多次提及&#xff0c;LLM并非万能。它们受限于训练数据的时效性和范围&#xf…...

Flask框架入门与实践

Flask框架入门与实践 Flask是一个轻量级的Python Web框架&#xff0c;以其简洁、灵活和易于上手的特点深受开发者喜爱。本文将带您深入了解Flask的核心概念、基本用法以及实际应用。 什么是Flask&#xff1f; Flask是由Armin Ronacher于2010年开发的微型Web框架。与Django等…...

PD 分离推理的加速大招,百度智能云网络基础设施和通信组件的优化实践

为了适应 PD 分离式推理部署架构&#xff0c;百度智能云从物理网络层面的「4us 端到端低时延」HPN 集群建设&#xff0c;到网络流量层面的设备配置和管理&#xff0c;再到通信组件和算子层面的优化&#xff0c;显著提升了上层推理服务的整体性能。 百度智能云在大规模 PD 分离…...

罗杰斯高频板技术解析:低损耗基材如何定义 5G 通信未来

在 5G 通信与尖端电子技术加速融合的时代&#xff0c;高频 PCB 作为信号传输的核心载体&#xff0c;对材料性能与工艺精度提出了极致要求。猎板 PCB 深耕行业多年&#xff0c;始终以罗杰斯&#xff08;Rogers&#xff09;板材为核心介质&#xff0c;构建起从材料适配到精密制造…...

QML 动画控制、顺序动画与并行动画

目录 引言相关阅读基础属性说明工程结构示例代码解析示例1&#xff1a;手动控制动画&#xff08;ControlledAnimation.qml&#xff09;示例2&#xff1a;顺序动画&#xff08;SequentialAnimationDemo.qml&#xff09;示例3&#xff1a;并行动画&#xff08;ParallelAnimationD…...

【动态导通电阻】GaN HEMT动态导通电阻的精确测量

2023 年 7 月,瑞士洛桑联邦理工学院的 Hongkeng Zhu 和 Elison Matioli 在《IEEE Transactions on Power Electronics》期刊发表了题为《Accurate Measurement of Dynamic ON-Resistance in GaN Transistors at Steady-State》的文章,基于提出的稳态测量方法,研究了氮化镓(…...

2:OpenCV—加载显示图像

加载和显示图像 从文件和显示加载图像 在本节中&#xff0c;我将向您展示如何使用 OpenCV 库函数从文件加载图像并在窗口中显示图像。 首先&#xff0c;打开C IDE并创建一个新项目。然后&#xff0c;必须为 OpenCV 配置新项目。 #include <iostream> #include <ope…...

Qt控件:交互控件

交互控件 1. QAction核心功能API 1.2 实例应用情况应用场景 1. QAction ##1. 1简介与API QAction 是一个核心类&#xff0c;用于表示应用程序中的一个操作&#xff08;如菜单项、工具栏按钮或快捷键触发的功能&#xff09;。它将操作的逻辑与 UI 表现分离&#xff0c;使代码更…...

在vue3中使用Cesium的保姆教程

1. 软件下载与安装 1. node安装 Vue.js 的开发依赖于 Node.js 环境&#xff0c;因此我们首先需要安装 Node.js。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;它允许你在服务器端运行 JavaScript 代码&#xff0c;同时也为前端开发提供了强大的工具支…...

zst-2001 下午题-历年真题 试题一到三

试题一 问题一 1 问题一 2 注意每句话中的“给”… 问题一 3 问题二 1 问题二 2 问题二 3 问题三 1 步骤一.看父图的数据流在子图有没有缺失 步骤二.看加工有没有输入输出 步骤三.阅读理解 问题三 2 实体和存储不能划线 问题三 3 试题二 问题一 1 问题一 2 问题一 3 问题二…...

STM32的ADC模块中,**采样时机(Sampling Time)**和**转换时机(Conversion Time),获取数据的时机详解

在STM32的ADC模块中&#xff0c;**采样时机&#xff08;Sampling Time&#xff09;和转换时机&#xff08;Conversion Time&#xff09;**是ADC工作流程中的两个关键阶段&#xff0c;直接影响采样精度和系统实时性。以下是详细解析&#xff1a; 1. 采样时机&#xff08;Samplin…...

iOS音视频解封装分析

首先是进行解封装的简单的配置 /// 解封装配置 class KFDemuxerConfig {// 媒体资源var asset: AVAsset?// 解封装类型&#xff0c;指定是音频、视频或两者都需要var demuxerType: KFMediaType .avinit() {} }然后是实现解封装控制器 import Foundation import CoreMedia i…...

探究电阻分压的带负载能力

我们经常使用两个电阻去分压来获得特定的电压,那么我是两个大阻值电阻分压获得的电压驱动能力强,还是小阻值电阻分压得到的电压驱动能力强呢? 一、电压相同时,电流的大小 下面是两个阻值分压得到的仿真图 电路分析: VCC都是5V,探针1和探针2测到的电压都是1.67V; 根据…...

14、Python时间表示:Unix时间戳、毫秒微秒精度与time模块实战

适合人群&#xff1a;零基础自学者 | 编程小白快速入门 阅读时长&#xff1a;约5分钟 文章目录 一、问题&#xff1a;计算机中的时间的表示、Unix时间点&#xff1f;1、例子1&#xff1a;计算机的“生日”&#xff1a;Unix时间点2、答案&#xff1a;&#xff08;1&#xff09;U…...

PCL 绘制二次曲面

文章目录 一、简介二、实现代码三、实现效果一、简介 这里基于二次曲面的公式: z = a 0 + a 1 x + a 2 y + a...

消息队列与Kafka基础:从概念到集群部署

目录 一、消息队列 1.什么是消息队列 2.消息队列的特征 3.为什么需要消息队列 二、Kafka基础与入门 1.Kafka基本概念 2.Kafka相关术语 3.Kafka拓扑架构 4.Topic与partition 5.Producer生产机制 6.Consumer消费机制 三、Zookeeper概念介绍 1.zookeeper概述 2.zooke…...

计算机指令分类和具体的表示的方式

1.关于计算机的指令系统 下面的这个就是我们的一个简单的计算机里面涉及到的指令&#xff1a; m就是我们的存储器里面的地址&#xff0c;可以理解为memory这个意思&#xff0c;r可以理解为rom这样的单词的首字母&#xff0c;帮助我们去进行这个相关的指令的记忆&#xff0c;不…...

pcie phy-电气层-gen1/2(TX)

S IP物理层讲解 在synopsys IP中对于phy层的内容分离的比较多&#xff1a; cxpl中&#xff1a; u_cx_phy_logical&#xff1a;包含ts序列的解析&#xff08;smlh&#xff09;&#xff1b; pipe层协议的转换&#xff08;rmlh,xmlh)&#xff1b;pipe转dllp包&#xff08;rplh&…...

Baklib加速企业AI数据智理转型

Baklib智理AI数据资产 在AI技术深度渗透业务场景的背景下&#xff0c;Baklib通过构建企业级知识中台架构&#xff0c;重塑了数据资产的治理范式。该平台采用智能分类引擎与语义分析模型&#xff0c;将分散在邮件、文档、数据库中的非结构化数据转化为标准化的知识单元&#xf…...

深度学习驱动下的目标检测技术:原理、算法与应用创新

一、引言​ 1.1 研究背景与意义​ 目标检测作为计算机视觉领域的核心任务之一&#xff0c;旨在识别图像或视频中感兴趣目标的类别&#xff0c;并确定其在图像中的位置&#xff0c;通常以边界框&#xff08;Bounding Box&#xff09;的形式表示 。其在现实生活中有着极为广泛且…...

window 显示驱动开发-使用有保证的协定 DMA 缓冲区模型

Windows Vista 的显示驱动程序模型保证呈现设备的 DMA 缓冲区和修补程序位置列表的大小。 修补程序位置列表包含 DMA 缓冲区中命令引用的资源的物理内存地址。 在有保证的协定模式下&#xff0c;用户模式显示驱动程序知道 DMA 缓冲区和修补程序位置列表的确切大小&#xff0c;…...

《指针与整数相加减的深入解析》

&#x1f680;个人主页&#xff1a;BabyZZの秘密日记 &#x1f4d6;收入专栏&#xff1a;C语言 &#x1f30d;文章目入 一、指针与整数相加的原理二、指针与整数相减的原理三、使用场景&#xff08;一&#xff09;数组操作&#xff08;二&#xff09;内存遍历 四、注意事项&…...

C++_STL_map与set

1. 关联式容器 在初阶阶段&#xff0c;我们已经接触过STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、 forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面 存储的是元素本身。那什么是…...

1949-2022年各省农作物播种面积数据(22个指标)

1949-2022年各省农作物播种面积数据&#xff08;22个指标&#xff09; 1、时间&#xff1a;1949-2022年 2、来源&#xff1a;各省年鉴、国家统计局、农业部、农业年鉴 3、范围&#xff1a;31省 4、指标&#xff1a;年度标识、省份编码、省份名称、农作物总播种面积、粮食作…...

汽车二自由度系统模型以及电动助力转向系统模型

汽车二自由度系统模型与电动助力转向系统&#xff08;EPS&#xff09;的详细建模方案&#xff0c;包含理论推导、MATLAB/Simulink实现代码及参数说明&#xff1a; 一、二自由度汽车模型 1. 模型描述 包含以下两个自由度&#xff1a; 横向运动&#xff08;侧向加速度&#xf…...

【学习笔记】计算机操作系统(四)—— 存储器管理

第四章 存储器管理 文章目录 第四章 存储器管理4.1 存储器的层次结构4.1.1 多层结构的存储器系统4.1.2 主存储器与寄存器4.1.3 高速缓存和磁盘缓存 4.2 程序的装入和链接4.2.1 程序的装入4.2.2 程序的链接 4.3 连续分配存储管理方式4.3.1 单一连续分配4.3.2 固定分区分配4.3.3 …...

51单片机的lcd12864驱动程序

#include <reg51.h> #include <intrins.h>#define uchar...

(03)数字化转型之库存管理:从进库到出库的数字化运营

在当今竞争激烈的商业环境中&#xff0c;高效的库存管理已成为企业降低成本、提高运营效率的关键。本文将系统性地介绍库存管理的全流程&#xff0c;包括进库、出库、移库、盘点等核心环节&#xff0c;帮助企业构建科学合理的库存管理体系。 一、进库管理&#xff1a;从计划到执…...

windows编程中加载DLL的两种典型方式的比较

文章目录 DLL定义头文件定义CPP实现DLL的调用代码直接使用通过LoadLibrary调用导入表的依赖LoadLibrary使用DLL库中的类DLL中定义工厂函数调用时的代码补充:为什么LoadLibrary不能直接导出类在windows的编程中,使用DLL是一个非常常见的操作。一般来说,有两种集成DLL的方式:…...

存储器上如何存储1和0

在计算机存储器中&#xff0c;数据最终以**二进制形式&#xff08;0和1&#xff09;**存储&#xff0c;这是由硬件特性和电子电路的物理特性决定的。以下是具体存储方式的详细解析&#xff1a; 一、存储的物理基础&#xff1a;半导体电路与电平信号 计算机存储器&#xff08;…...

【笔记】记一次PyCharm的问题反馈

#工作记录 最近更新至 PyCharm 社区版的最新版本后&#xff0c;我遇到了多个影响使用体验的问题。令人感到不便的是&#xff0c;一些在旧版本中非常便捷的功能&#xff0c;在新版本中却变得操作复杂、不够直观。过去&#xff0c;我一直通过 PyCharm 内置的故障报告与反馈机制反…...

logrotate按文件大小进行日志切割

✅ 编写logrotate文件&#xff0c;进行自定义切割方式 adminip-127-0-0-1:/data/test$ cat /etc/logrotate.d/test /data/test/test.log {size 1024M #文件达到1G就切割rotate 100 #保留100个文件compressdelaycompressmissingoknotifemptycopytruncate #这个情况服务不用…...

基于大模型的脑出血智能诊疗与康复技术方案

目录 一、术前阶段1.1 数据采集与预处理系统伪代码实现流程图1.2 特征提取与选择模块伪代码实现流程图1.3 大模型风险评估系统伪代码实现流程图二、术中阶段2.1 智能手术规划系统伪代码实现流程图2.2 麻醉智能监控系统伪代码实现流程图三、术后阶段3.1 并发症预测系统伪代码片段…...

P21-RNN-心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、RNN 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;简称 RNN&#xff09;是一类以序列数据为输入&#xff0c;在序列的演进方向进行递归…...

懒汉式单例模式的线程安全实现

懒汉式单例模式的线程安全实现 懒汉式单例模式的核心特点是延迟实例化(在第一次使用时创建对象),但其基础实现存在线程安全问题。以下是不同线程安全实现方式的详细说明和对比: 1. 非线程安全的基础懒汉式 public class UnsafeLazySingleton {private static UnsafeLazyS…...

Java 常用的Arrays函数

文章目录 ArrayssorttoStringbinarySearchequalsfill 数组拷贝copyOfcopyOfRangearraycopy 二维数组定义遍历deepToString空指针异常 Arrays sort int[] array new int[]{1,20,3}; Arrays.sort(array);// 1 3 20toString 帮助数组转为字符串 int[] array new int[]{1,2,3…...

FEKO许可证与版本兼容性问题

随着电磁仿真技术的不断进步&#xff0c;FEKO软件不断更新迭代&#xff0c;为用户提供更强大的功能和更优秀的性能。然而&#xff0c;在升级过程中&#xff0c;FEKO许可证与版本兼容性问题往往成为用户关注的焦点。本文将为您详细解读FEKO许可证与版本兼容性问题&#xff0c;帮…...

HarmonyOs开发之——— ArkWeb 实战指南

HarmonyOs开发之——— ArkWeb 实战指南 谢谢关注!! 前言:上一篇文章主要介绍HarmonyOs开发之———合理使用动画与转场:CSDN 博客链接 一、ArkWeb 组件基础与生命周期管理 1.1 Web 组件核心能力概述 ArkWeb 的Web组件支持加载本地或在线网页,提供完整的生命周期回调体…...

冰箱磁力贴认证标准16CFR1262

在亚马逊平台&#xff0c;冰箱磁力贴这类可能被儿童接触到的产品&#xff0c;有着严格的规范哦。必须得遵守 16 CFR 1262 标准&#xff0c;还得有符合该标准的测试报告和 GCC 证书&#xff0c;不然产品就可能被禁止销售或者面临召回&#xff0c;那可就损失大啦&#xff01; ​ …...

Java中的锁机制全解析:从synchronized到分布式锁

在多线程编程中&#xff0c;锁是保证线程安全的核心工具。本文将详解Java中常见的锁机制及其实际应用场景&#xff0c;帮助开发者选择最合适的锁方案。 一、内置锁&#xff1a;synchronized 原理 通过JVM内置的监视器锁&#xff08;Monitor&#xff09;实现&#xff0c;可修…...

OptiStruct实例:3D实体转子分析

上一节介绍了1D转子的临界转速分析。在1D转子模型中&#xff0c;转子是以集中质量单元的形式建模的。此种建模方法不可避免地会带来一些简化和局部特征的缺失。接下来介绍OptiStruct3D实体转子的建模及临界转速分析实例。 3D实体转子建立详细的转子网格模型&#xff0c;然后将…...

简单记录坐标变换

以三维空间坐标系为例 rTt代表机械手末端相对robot root坐标系的变换关系 rTt dot p_in_tool 可以把tool坐标系下表示的某点转到root坐标系表示 其中rTt表示tool相对于root坐标系的平移和旋转 以二维图像坐标系为例说明 1坐标系定为图片坐标系左上角&#xff0c;横平竖直的…...

自定义快捷键软件:AutoHotkey 高效的快捷键执行脚本软件

AutoHotkey 是一种适用于 Windows 的免费开源脚本语言&#xff0c;它允许用户轻松创建从小型到复杂的脚本&#xff0c;用于各种任务&#xff0c;例如&#xff1a;表单填充、自动点击、宏等。 定义鼠标和键盘的热键&#xff0c;重新映射按键或按钮&#xff0c;并进行类似自动更…...

【Android构建系统】了解Soong构建系统

背景介绍 在Android7.0之前&#xff0c;Android使用GNU Make描述和执行build规则。Android7.0引入了Soong构建系统&#xff0c;弥补Make构建系统在Android层面变慢、容易出错、无法扩展且难以测试等缺点。 Soong利用Kati GNU Make克隆工具和Ninja构建系统组件来加速Android的…...

显性知识的主要特征

有4个主要特征&#xff1a; 客观存在性静态存在性可共享性认知元能性...