Iceberg:浅析基于Snapshot的事务过程

MVCC事务(乐观锁)

我们知道,Iceberg基于Snapshot(快照机制)实现了乐观无锁地数据并发读写能力(MVCC,Multi Versions Concurrency Controll),默认提供了快照级别的事务隔离,因此可以至少避免脏读的问题。

SQL-92 标准定义了如下 4 种隔离级别:

  1. 读未提交:会存在脏读、幻读、不可重复读等问题。
  2. 读已提交:会存在幻读、不可重复读等问题。
  3. 可重复读:存在幻读的问题等问题。
  4. 串行事务:最高级别的隔离,可以避免一切由于事务并发导致的问题。

幻读,是对于并发事务的INSERT/DELETE操作,会导致连续读取相同区间的数据记录时,数据条数不同。
不可重复读,是对于并发事务的UPDATE操作,会导致连续读取相同数据记录的数据发生了变化。

并发写

基于快照实现方案是解决事务并的方式之一,可以达到可重复读、串行,但会导致另外一个数据更新丢失问题,即写偏斜问题

事务A开始,读取字段X,更新字段Y,事务结束。
事务B开始,读取字段Y,更新字段X,事务结束。

上面两个并发事务A/B,会基于同一个快照读取分别读取了X、Y的值,同时分别更新了Y、X,完成自己的事务,由于这两个事务在写的时候不会产生数据更新的重叠问题,因此都可以成功,产生的结果就是X、Y的值被互换了
但如果事务是串行的话,不论是A事务先执行还是B事务,最终的结果则是X的值=Y的值

并发读

实际上,基于快照的读又分为两种情况,产生的结果也不相同:
基于旧快照: 由于旧的快照数据不可能再发生变化,因此可以避免一切并发导致的读问题。
基于当前快照: 由于最新快照会被写事务更新,因此不可避免地会导致幻读、不可重复读的问题。

Iceberg中的实现

事务测试

下面的测试演示了一个事务上的多次更新,每一次从对象创建的快照更新对象的commit,都会产生一个新的事务ID,但这些新产生的Snapshot都归属于创建事务时绑定的起始TableMetadata对象。

一个表会唯一绑定一个最新的TableMetadata实例,而此TableMetadata则包含子所有的历史Snapshots。
对于非事务的快照更新操作,一次commit就会产生一个新的Snapshot,同时也会产生一个新的TableMetadata。
而对于事务的更新,由于一个事务期间可能会基于起始TableMetadata的当前快照,执行一次或多次commit,因此这个TableMetadata包含所有的新生产的Snapshots。

  public void testMultipleOperationTransaction() {Assert.assertEquals("Table should be on version 0", 0, (int) version());// 先向表中追加一个文件,FILE_Ctable.newAppend().appendFile(FILE_C).commit();List<HistoryEntry> initialHistory = table.history();// 获取当前的MetadataTableMetadata base = readMetadata();// 创建一个事务实例Transaction txn = table.newTransaction();// 验证当前表的Metadata是的版本号为1Assert.assertSame("Base metadata should not change when commit is created", base, readMetadata());Assert.assertEquals("Table should be on version 1 after txn create", 1, (int) version());// 从事务对象,创建一个AppendFiles的实例,即MergeAppend的对象// 并追加两个新文件FILE_A、FILE_Btxn.newAppend().appendFile(FILE_A).appendFile(FILE_B).commit();// 前面的commit()方法,并不会更新表的TaleMetadataAssert.assertSame("Base metadata should not change when commit is created", base, readMetadata());Assert.assertEquals("Table should be on version 1 after txn create", 1, (int) version());// 获取表的追加文件后的最新快照信息Snapshot appendSnapshot = txn.table().currentSnapshot();// 通过事务对象创建一个文件删除器,即StreamingDelete的实例// 并删除FILE_Atxn.newDelete().deleteFile(FILE_A).commit();// 获取表的删除文件后的最新快照信息Snapshot deleteSnapshot = txn.table().currentSnapshot();// 但表对应的最新TableMetadata实例依然没有变化Assert.assertSame("Base metadata should not change when an append is committed", base, readMetadata());Assert.assertEquals("Table should be on version 1 after append", 1, (int) version());// 提交事务txn.commitTransaction();// TableMetadata发生了变化,版本号更新到了2Assert.assertEquals("Table should be on version 2 after commit", 2, (int) version());// 由于前面通过非事务操作提交一了一次数据文件,又通过事务提交了一次,因此会产生2个Manifest FilesAssert.assertEquals("Table should have two manifest after commit",2, readMetadata().currentSnapshot().allManifests(table.io()).size());// 由于最后一次的提交操作是delete file,因此产生的最后一个Snapshot就是deleteSnapshotAssert.assertEquals("Table snapshot should be the delete snapshot",deleteSnapshot,readMetadata().currentSnapshot());validateManifestEntries(readMetadata().currentSnapshot().allManifests(table.io()).get(0),ids(deleteSnapshot.snapshotId(), appendSnapshot.snapshotId()),files(FILE_A, FILE_B),statuses(Status.DELETED, Status.EXISTING));// 表最新的TableMetadata应该包含3个历史Snapshots,一个是通过非事务提交产生的;// 另外两个是在事务期间提交产生的。Assert.assertEquals("Table should have a snapshot for each operation", 3, readMetadata().snapshots().size());validateManifestEntries(readMetadata().snapshots().get(1).allManifests(table.io()).get(0),ids(appendSnapshot.snapshotId(), appendSnapshot.snapshotId()),files(FILE_A, FILE_B),statuses(Status.ADDED, Status.ADDED));// 验证所有的历史提交Assertions.assertThat(table.history()).containsAll(initialHistory);
}

创建事务

支持创建、替换以及通用的事务创建。

public final class Transactions {private Transactions() {}public static Transaction createOrReplaceTableTransaction(String tableName, TableOperations ops, TableMetadata start) {return new BaseTransaction(tableName, ops, TransactionType.CREATE_OR_REPLACE_TABLE, start);}public static Transaction replaceTableTransaction(String tableName, TableOperations ops, TableMetadata start) {return new BaseTransaction(tableName, ops, TransactionType.REPLACE_TABLE, start);}public static Transaction replaceTableTransaction(String tableName, TableOperations ops, TableMetadata start, MetricsReporter reporter) {return new BaseTransaction(tableName, ops, TransactionType.REPLACE_TABLE, start, reporter);}public static Transaction createTableTransaction(String tableName, TableOperations ops, TableMetadata start) {Preconditions.checkArgument(ops.current() == null, "Cannot start create table transaction: table already exists");return new BaseTransaction(tableName, ops, TransactionType.CREATE_TABLE, start);}public static Transaction createTableTransaction(String tableName, TableOperations ops, TableMetadata start, MetricsReporter reporter) {Preconditions.checkArgument(ops.current() == null, "Cannot start create table transaction: table already exists");return new BaseTransaction(tableName, ops, TransactionType.CREATE_TABLE, start, reporter);}public static Transaction newTransaction(String tableName, TableOperations ops) {return new BaseTransaction(tableName, ops, TransactionType.SIMPLE, ops.refresh());}public static Transaction newTransaction(String tableName, TableOperations ops, MetricsReporter reporter) {return new BaseTransaction(tableName, ops, TransactionType.SIMPLE, ops.refresh(), reporter);}
}

事务对象的实例化

public class BaseTransaction implements Transaction {private final String tableName;// 保存实际的表操作集合对象private final TableOperations ops;private final TransactionTable transactionTable;private final TableOperations transactionOps;// 保存所有的从事务创建出来的SnapshotUpdate的实现类的对象private final List<PendingUpdate> updates;// 保存事物期间删除的所有文件路径private final Set<String> deletedFiles =Sets.newHashSet(); // keep track of files deleted in the most recent commitprivate final Consumer<String> enqueueDelete = deletedFiles::add;private TransactionType type;private TableMetadata base;private TableMetadata current;private boolean hasLastOpCommitted;private final MetricsReporter reporter;BaseTransaction(String tableName,TableOperations ops,TransactionType type,TableMetadata start,MetricsReporter reporter) {this.tableName = tableName;this.ops = ops;// 创建一个事务类开的表对象,提供表相关的事务方法this.transactionTable = new TransactionTable();// 保存事务开始时的TableMetadatathis.current = start;// 事务表上的操作集,可以支持this.transactionOps = new TransactionTableOperations();this.updates = Lists.newArrayList();// 保存最新的TableMetadata,有可能与start不同,但版本号大于等待startthis.base = ops.current();this.type = type;this.hasLastOpCommitted = true;this.reporter = reporter;}/*** 在BaseTransaction实例构造时,会实例化一个此类的实例,它是对原始TableOperations的封装,例如HiveTableOperations,* 以支持从此事务创建的具体的操作的提交行为。*/public class TransactionTableOperations implements TableOperations {private TableOperations tempOps = ops.temp(current);@Overridepublic TableMetadata current() {return current;}@Overridepublic TableMetadata refresh() {return current;}@Override@SuppressWarnings("ConsistentOverrides")public void commit(TableMetadata underlyingBase, TableMetadata metadata) {// underlyingBase指向的是最新的TableMetadata,// metata指向的是事务创建时绑定的起始TableMetadataif (underlyingBase != current) {// 如果最新的TableMetadata发生了改变,那么就需要触发更新当前事务所绑定的TableMetadata到最新版本,以保证不会将事务期间产生的变更更新到旧的Metadata上// trigger a refresh and retrythrow new CommitFailedException("Table metadata refresh is required");}// 当事务引出的SnapshotUpdate对象commit时,所有变更提交到的TableMeatadata实例与事务绑定的最新的实例相同,// 则更新当前事务的各种引用,并不会真正提交变更到表BaseTransaction.this.current = metadata;this.tempOps = ops.temp(metadata);BaseTransaction.this.hasLastOpCommitted = true;}@Overridepublic FileIO io() {return tempOps.io();}@Overridepublic EncryptionManager encryption() {return tempOps.encryption();}@Overridepublic String metadataFileLocation(String fileName) {return tempOps.metadataFileLocation(fileName);}@Overridepublic LocationProvider locationProvider() {return tempOps.locationProvider();}@Overridepublic long newSnapshotId() {return tempOps.newSnapshotId();}}
}

事务中的ApendFiles

创建实例

对应于前文,事务测试章节,提到的如下代码行
txn.newAppend().appendFile(FILE_A).appendFile(FILE_B).commit();

public class BaseTransaction implements Transaction {@Overridepublic AppendFiles newAppend() {checkLastOperationCommitted("AppendFiles");// 创建一个新的AppendFiles实例,注意到这里为此实例绑定的TableOperations是带事务的,// AppendFiles append = new MergeAppend(tableName, transactionOps).reportWith(reporter);// 为append添加一个回调函数,当删除文件成功后,就将文件路径记录到当前事务维护的队列中append.deleteWith(enqueueDelete);// 添加 从事务新创建的SnapshotUpdate类型的对象 到缓存队列updates.add(append);return append;}
}

检查变更的合法性

所有继承自SnapshotProducer的实例类,可以实现自己的验证逻辑,其接口定义如下:

abstract class SnapshotProducer<ThisT> implements SnapshotUpdate<ThisT> {/*** Validate the current metadata.** <p>Child operations can override this to add custom validation.** @param currentMetadata current table metadata to validate* @param snapshot ending snapshot on the lineage which is being validated*/protected void validate(TableMetadata currentMetadata, Snapshot snapshot) {}
}

例如对于BaseRewriteFiles的更新,它的实现如下:

  @Overrideprotected void validate(TableMetadata base, Snapshot parent) {if (replacedDataFiles.size() > 0) {// if there are replaced data files, there cannot be any new row-level deletes for those data// filesvalidateNoNewDeletesForDataFiles(base, startingSnapshotId, replacedDataFiles, parent);}}

提交变更

MergeAppend类没有覆盖父类的commit()方法,故调用的实际上是SnapshotProducer::commit()方法,它与非事务模式下的Snapshot生成及更新过各相同,只是在将新的Snapshot应用到TableMetadata时,会将apply(TableMetadata base, Snapshot snapshot)方法路由到父类MergingSnapshotProducer,具体的代码见下:

class MergeAppend extends MergingSnapshotProducer<ThisT> extends SnapshotProducer<ThisT> {/*** 将新生产的Snapshot应用到目标TableMetadata。*/@Overridepublic List<ManifestFile> apply(TableMetadata base, Snapshot snapshot) {// filter any existing manifests// 过滤出可以从当前Snapshot可以访问的Manifest FilesList<ManifestFile> filtered =filterManager.filterManifests(SnapshotUtil.schemaFor(base, targetBranch()),snapshot != null ? snapshot.dataManifests(ops.io()) : null);// 获取最小的数据文件的序列号,由于TableMetadata记录的lastSequenceNumber并不一定与ManifestFile记录的所有合法文件的最小序列号相同,// 故在清理垃圾文件时需要找到最小的序列号long minDataSequenceNumber =filtered.stream().map(ManifestFile::minSequenceNumber).filter(seq ->seq!= ManifestWriter.UNASSIGNED_SEQ) // filter out unassigned in rewritten manifests.reduce(base.lastSequenceNumber(), Math::min);// 删除所有序列号小于minDataSequenceNumber的Delete files,这些文件后续也不会被用到deleteFilterManager.dropDeleteFilesOlderThan(minDataSequenceNumber);List<ManifestFile> filteredDeletes =deleteFilterManager.filterManifests(SnapshotUtil.schemaFor(base, targetBranch()),snapshot != null ? snapshot.deleteManifests(ops.io()) : null);// only keep manifests that have live data files or that were written by this commitPredicate<ManifestFile> shouldKeep =manifest ->manifest.hasAddedFiles()|| manifest.hasExistingFiles()|| manifest.snapshotId() == snapshotId();Iterable<ManifestFile> unmergedManifests =Iterables.filter(Iterables.concat(prepareNewManifests(), filtered), shouldKeep);Iterable<ManifestFile> unmergedDeleteManifests =Iterables.filter(Iterables.concat(prepareDeleteManifests(), filteredDeletes), shouldKeep);// update the snapshot summarysummaryBuilder.clear();summaryBuilder.merge(addedFilesSummary);summaryBuilder.merge(appendedManifestsSummary);summaryBuilder.merge(filterManager.buildSummary(filtered));summaryBuilder.merge(deleteFilterManager.buildSummary(filteredDeletes));// 将所有当前Snapshot产生的数据文件或删除文件,与历史的Manifest Files合并到一起,这些文件就是当前表可以访问的活数据。List<ManifestFile> manifests = Lists.newArrayList();Iterables.addAll(manifests, mergeManager.mergeManifests(unmergedManifests));Iterables.addAll(manifests, deleteMergeManager.mergeManifests(unmergedDeleteManifests));return manifests;}
}

提交TableMetadata

经过上一小节对事务绑定的TableMetadata的更新之后,就可以真正地执行提交动作,尝试通过TableOperations对象将最新的TableMetadata实例更新到目标表。

但由于我们现在是对整个事务流程中的某一个操作进行提交,因此不应该直接将这些变更到底层的表,即不应该调用底层的HiveTableOperations对象的commit()方法。还记得在前面提到在类BaseTransaction中定义了TransactionTableOperations吧,实际上这个在事务期间,都是通过这个Operations实例来完成TableMetadata的提交的,其具体的方法定义如下:

    public void commit(TableMetadata underlyingBase, TableMetadata metadata) {// underlyingBase指向的是最新的TableMetadata,// metata指向的是事务创建时绑定的起始TableMetadataif (underlyingBase != current) {// 如果最新的TableMetadata发生了改变,那么就需要触发更新当前事务所绑定的TableMetadata到最新版本,以保证不会将事务期间产生的变更更新到旧的Metadata上// trigger a refresh and retrythrow new CommitFailedException("Table metadata refresh is required");}// 当事务引出的SnapshotUpdate对象commit时,所有变更提交到的TableMeatadata实例与事务绑定的最新的实例相同,// 则更新当前事务的各种引用,并不会真正提交变更到表BaseTransaction.this.current = metadata;this.tempOps = ops.temp(metadata);BaseTransaction.this.hasLastOpCommitted = true;}

通过上面的commit()方法可以知道,不同于HiveTableOperations中的实现那样,这里仅仅是更新了当前事务的成员变量,以便后续的更新操作能够继续应用到此TableMetadata实例上。

事务提交

BaseTransaction::commitTransaction

  @Overridepublic void commitTransaction() {Preconditions.checkState(hasLastOpCommitted, "Cannot commit transaction: last operation has not committed");switch (type) {case CREATE_TABLE:commitCreateTransaction();break;case REPLACE_TABLE:commitReplaceTransaction(false);break;case CREATE_OR_REPLACE_TABLE:commitReplaceTransaction(true);break;case SIMPLE:commitSimpleTransaction();break;}}

commitSimpleTransaction

  private void commitSimpleTransaction() {// if there were no changes, don't try to commit// 在讲解AppendFiles的提交流程时,有提到它的commit()方法,实际上是更新当前事务的current引用,保存了更新的TableMetadata实例// 因此如果这里base == current,说明在之前没有调用于commit方法,因此不会执行事务的提交if (base == current) {return;}Set<Long> startingSnapshots =base.snapshots().stream().map(Snapshot::snapshotId).collect(Collectors.toSet());try {Tasks.foreach(ops).retry(base.propertyAsInt(COMMIT_NUM_RETRIES, COMMIT_NUM_RETRIES_DEFAULT)).exponentialBackoff(base.propertyAsInt(COMMIT_MIN_RETRY_WAIT_MS, COMMIT_MIN_RETRY_WAIT_MS_DEFAULT),base.propertyAsInt(COMMIT_MAX_RETRY_WAIT_MS, COMMIT_MAX_RETRY_WAIT_MS_DEFAULT),base.propertyAsInt(COMMIT_TOTAL_RETRY_TIME_MS, COMMIT_TOTAL_RETRY_TIME_MS_DEFAULT),2.0 /* exponential */).onlyRetryOn(CommitFailedException.class).run(underlyingOps -> {// 调用BaseTransaction::newAppend()方法后生成一个MergeAppend实例,同时这个实例会被添加到缓存队列中,// 这里就是遍历这个缓存队列,尝试再次对这个更新操作进行提交,即二次提交// 至于为什么要再一次提交,见后面的解析applyUpdates(underlyingOps);// 当所有的更新都提交完成时,就可以真正地执行底层表上的更新操作,更新最新的TableMetadataunderlyingOps.commit(base, current);});} catch (CommitStateUnknownException e) {throw e;} catch (PendingUpdateFailedException e) {cleanUpOnCommitFailure();throw e.wrapped();} catch (RuntimeException e) {cleanUpOnCommitFailure();throw e;}// the commit succeededtry {// clean up the data files that were deleted by each operation. first, get the list of// committed manifests to ensure that no committed manifest is deleted.// A manifest could be deleted in one successful operation commit, but reused in another// successful commit of that operation if the whole transaction is retried.Set<Long> newSnapshots = Sets.newHashSet();for (Snapshot snapshot : current.snapshots()) {if (!startingSnapshots.contains(snapshot.snapshotId())) {newSnapshots.add(snapshot.snapshotId());}}Set<String> committedFiles = committedFiles(ops, newSnapshots);if (committedFiles != null) {// delete all of the files that were deleted in the most recent set of operation commitsTasks.foreach(deletedFiles).suppressFailureWhenFinished().onFailure((file, exc) -> LOG.warn("Failed to delete uncommitted file: {}", file, exc)).run(path -> {if (!committedFiles.contains(path)) {ops.io().deleteFile(path);}});} else {LOG.warn("Failed to load metadata for a committed snapshot, skipping clean-up");}} catch (RuntimeException e) {LOG.warn("Failed to load committed metadata, skipping clean-up", e);}}

applyUpdates

  private void applyUpdates(TableOperations underlyingOps) {// base保存的是在创建事务实例时,绑定的最新的TableMetadataif (base != underlyingOps.refresh()) {// 由于commit事务之前,表的Metadata很可能发生了变化,因此就需要我们将所有的更新操作修正到最新的实例上// use refreshed the metadatathis.base = underlyingOps.current();this.current = underlyingOps.current();for (PendingUpdate update : updates) {// re-commit each update in the chain to apply it and update currenttry {update.commit();} catch (CommitFailedException e) {// Cannot pass even with retry due to conflicting metadata changes. So, break the// retry-loop.throw new PendingUpdateFailedException(e);}}}}

乐观锁更新TableMetadata

Iceberg基于乐观锁的实现,以达到无锁提交TableMetadata的目的,因此在更新TableMetadata时,需要总是尝试获取表锁以便能够顺利更新表数据,例如对于Hive表保存Metadata时,则需要通过HiveTableOperations::doCommit(...)方法执行上锁、更新、释放锁的流程。

更新本地表

table.refresh();

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

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

相关文章

关于“最终”的最终决定

在Java中&#xff0c;过度使用final感觉就像是SHOUTING。 在很多时候它已经过时且不合适。 Java和JavaScript 这主要是关于Java中的final关键字&#xff0c;但是我对JavaScript中与之对应的const看法略有变化。 我可以认为const是一件好事&#xff0c;而final是一件坏事&#…

PCM设备在电网系统中的应用介绍

近年来&#xff0c;随着我国社会经济的快速发展和电力通信行业的不断进步&#xff0c;通信网络技术和网络系统不断完善和改进的同时&#xff0c;网络系统运行过程中也出现了很多的问题&#xff0c;要对其进行优化&#xff0c;否则可能影响电力通信网络的正常运行电力通信网络系…

PCM设备在雷达通信系统应用

PCM设备系统是目前国内企业最常用的综合接入通信设备&#xff0c;PCM设备所具优势众多&#xff0c;因此它能成为目前企业最常用的通信传输接入设备之一&#xff0c;而且在类似雷达通信系统等重要行业中广泛应用着。 PCM设备利用标准的E1数据传输通道&#xff0c;采用PCM制式&a…

ejb的maven_针对新手的Java EE7和Maven项目-第3部分-定义ejb服务和jpa实体模块

ejb的maven从前面的部分恢复 第1部分 第2部分 我们在第三部分继续介绍&#xff0c;我们已经有一个父pom&#xff0c;并且已经为我们的war模块定义了pom。 在我们的原始设置中&#xff0c;我们定义了我们的应用程序将包含一个ejb jar形式的服务jar。 这是我们的Enterprise Jav…

PCM设备终端烧毁板卡是什么原因?

最近&#xff0c;有新客服反映他们之前在某家设备厂家买了PCM设备&#xff0c;老是会烧毁终端板。所以另外选择了我们飞畅科技。那么&#xff0c;PCM设备终端烧毁板卡是什么原因呢&#xff1f;接下来飞畅科技的小编就来为大家具体分析下PCM设备终端烧毁板卡的原因&#xff0c;一…

全部隐藏!

在较早的文章中 &#xff0c;我写了以下几句话&#xff1a; 在面向对象的代码库中&#xff0c;该工具包应尽可能离散。 使用开发套件的次数越多&#xff0c;您的代码实际面向对象的次数就越少&#xff0c;或者您的抽象并不是最好的。 。 我认为有必要详细说明这一点&#xff0…

E1 PCM设备的主要特点介绍

E1 PCM设备是目前国内企业最常用的综合接入通信设备&#xff0c;它利用标准的E1数据传输通道&#xff0c;采用PCM制式&#xff0c;直接提供语音、数据、图像等多种用户接口。接下来飞畅科技的小编来为大家详细介绍下E1 PCM设备的主要特点&#xff0c;一起来看看吧&#xff01; …

docker集群_使用Docker,Chef和Amazon OpsWorks进行集群范围的Java / Scala应用程序部署...

docker集群Docker非常适合在单个节点上运行隔离的容器。 但是&#xff0c;大多数软件系统都在多个节点上运行&#xff0c;因此&#xff0c;除了Docker之外&#xff0c;我们还需要某种方法来指定哪些容器应在哪些节点上运行。 我要解决的特定问题如下&#xff1a;我有两个Scala…

PCM信号是什么信号?

PCM信号是模拟信号呢&#xff0c;还是数字信号呢&#xff1f;当然是数字信号啦&#xff01;PCM的完整定义是&#xff1a;将模拟信号的抽样量化值变换成代码称为脉冲编码调制&#xff08;PCM设备&#xff09;。 在光纤通信系统中&#xff0c;光纤中传输的是二进制光脉冲“0”码…

SDH光传输设备是什么?SDH设备特点介绍

SDH光传输设备是一种&#xff0c;将复接、线路传输以及交换功能融合为一体的、并且由统一网管系统操作的综合信息传送网络。SDH光传输设备&#xff0c;他可实现网络有效管理、能实时业务监控、能动态网络维护、不同厂商设备间的互通等多项功能。 SDH光传输设备能大大提高网络资…

通过MicroProfile上下文传播增强了CDI上下文和隔板

当将CDI与异步执行方法&#xff08;例如ManagedExecutorService &#xff0c;传统上不可能访问在原始线程中处于活动状态的所有CDI范围。 MicroProfile Context Propagation可以定义线程执行上下文并将其传递到完成阶段&#xff0c;尽管我们的代码是异步执行的&#xff0c;但它…

ProtoBuff3.0.0在Ubuntu上安装

ProtoBuff3.0.0在Ubuntu上安装 最近安装ns3&#xff0c;运行别人代码&#xff0c;编译build.py时出现需要update Proto的问题&#xff0c;本来想安装最新版本&#xff0c;因为需要翻墙&#xff0c;就参考大部分博客&#xff0c;安装3.0.0版本。&#xff08;备注&#xff1a;直…

SDH与PDH的区别介绍

PDH准同步数字系列&#xff0c;1-4次群2048K、8488K、34368K、139264K&#xff0c;有美国/日本标准和欧洲标准&#xff0c;我国沿用的是欧洲标准&#xff1b;30/32路组成一个1次群&#xff1b;各个厂家产品大多互相不兼容。 SDH同步数字系列&#xff0c;网元都带PDH接口&#…

Ubuntu 系统入门

1 Ubuntu 常用文件夹处理命令 1、 cd … //返回上一层目录 cd / //返回根文件夹 2、ls 列出文件名称 3、unzip XXX 对zip 压缩包解压 4、sudo baobab 进行磁盘空间分析 5、 利用vim修改只读文件内容 ::w !sudo tee % &#xff08;注意空格&#xff09; 2 Ubuntu显示隐藏文件 …

什么是MSTP(多业务传输平台)?

Multi-Service Transfer Platform简称MSTP&#xff0c;他是基于SDH的多业务传送平台的&#xff0c;同时也实现TDM、ATM、以太网等业务的接入、处理和传送&#xff0c;提供统一网管的多业务节点。 当前通信时代随着不断增长的IP数据、话音、图像等多种业务传送需求扩展&#xf…

spring boot示例_Spring Boot上的Spring社交示例,或者我如何停止担心和喜欢自动配置...

spring boot示例对于Spring Boot 1.1.0.RC1&#xff0c;添加了自动配置和Spring Social的启动程序pom&#xff0c;这意味着我不必向pom添加一百个依赖关系&#xff0c;并且将为我处理许多毫无意义的Spring配置。 让我们来看一个例子。 我将实现一个两页的Web应用程序。 一个将…

安装虚拟机后,启动出错的解决办法

安装虚拟机后&#xff0c;启动出错&#xff0c;需要找到路径中的配置文件 使用记事本或其他应用打开 在vmci0.prensent值改为FALSE,才可以正常启动

什么是E1接口,E1的使用注意事项

欧洲的30路脉码调制PCM简称E1&#xff0c;速率是2.048Mbit/s 。 我国采用的是欧洲的E1标准。E1接口有G&#xff0e;703非平衡的75 ohm&#xff0c;平衡的120 ohm2种接口。接下来就由飞畅科技的小编来为大家详细介绍下使用E1的三种方法及注意事项&#xff0c;一起来看看吧&#…

Java序列化魔术方法及其示例使用

在上一篇文章中&#xff0c; 您需要了解有关Java序列化的所有知识 &#xff0c;我们讨论了如何通过实现Java序列化来启用类的可序列化性。 Serializable接口。 如果我们的类未实现Serializable接口&#xff0c;或者该类具有对非Serializable类的引用&#xff0c;则JVM将抛出No…

主机文件复制到Ubuntu系统中

实现这个功能&#xff0c;需要VMware Tool安装 在虚拟机选项卡中安装VMware Tool 将DVD盘中压缩包复制到home文件夹 解压后&#xff0c;进入该文件夹&#xff0c;打开终端&#xff0c;执行安装文件 安装完成后重启电脑即可 ------------------------------------ 参考链接…