暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Oracle与新基建的工业互联网(二)

甲骨文云技术 2020-05-07
650

本文是《Oracle与新基建的工业互联网》系列文章的第二篇,是Oracle数据库处理物联网平台工作负载的原理与实践篇,用生活化的超市购物做类比来解释原理,也有大量代码级的最佳实践。

全文阅读大概需要15分钟。
背景

在过去的十年里,智能设备的应用迅速增长。从手机和平板电脑到智能仪表和健身设备,一切都连接到互联网并共享数据,从而实现远程访问、自动软件更新、错误报告和传感器读数的传输。Gartner估计,到今年为止,将有超过260亿台联网设备。

随着这些智能设备的出现,数据库接收捕获和处理数据的频率和数量都有了巨大的增长。这种场景通常被称为物联网(IoT)。能够以高效、及时的方式接收捕获和分析快速增长的数据量,对于企业保持其竞争优势至关重要。如何选择管理这些数据的最佳平台,是许多不同行业的企业所面临的一个常见问题。
有些人认为物联网工作负载需要NoSQL数据库,因为所需的捕获率超过了传统关系数据库的能力。这是一种误解。如果处理得当,关系数据库的性能很容易超过NoSQL数据库。
数据捕获操作的性能受许多变量的影响,这些变量包括用于插入数据的方法、插入模式的使用、并行执行的使用,以及提交的速率。对于分析查询也是如此。接下来将介绍使用Oracle数据库实时接收捕获和分析大量数据时确保最优性能的原理和最佳实践。
Oracle数据库技术如何应对物联网负载的要求
物联网工作负载的关键要求无非三个方面,它们是:
  • 可扩展性

  • 灵活性

  • 实时分析

可扩展性
可扩展性是系统提供与可用硬件资源成比例且仅受其限制的吞吐能力。Oracle数据库提供了纵向扩展(增加单个服务器的硬件容量)或横向扩展(增加集群中的服务器数量)的能力。对于物联网项目,普遍的共识是,横向扩展解决方案是首选,因为它允许以较低的成本实现可扩展性。Oracle数据库的横向扩展架构是真正应用集群(RAC)或Oracle 分片(Sharding)。

真正应用集群

RAC支持任何应用程序连接到共享存储的数据库服务器池,应用代码和使用都保持不变。如果池中的服务器出现故障,数据库将继续在其余服务器上运行。当您需要更多处理能力时,只需向池中添加另一台服务器,且没有停机时间。

Oracle分片

Oracle Sharding 是分区技术的扩展,可以简单的理解为同一个表的不同分区可以放在不同的数据库里。一些全球规模 OLTP 应用更喜欢将大型数据库分片,放入多个较小数据库的服务器组,避免单个大型系统数据库的可扩展性或可用性的极端情况。客户一般喜欢自定义数据模型和应用程序,使事务能够自动路由到特定的分片。Oracle原生SQL 分片表可达到1000 个分片,分片可在线添加和重组,具有隔离数据、工作负载和用户的线性可扩展性。

数据库的配置

如何配置 Oracle 数据库,以确保性能随着Oracle横向扩展架构节点数量的增加而扩展?

首先,加载和查询大量数据的一个重要问题,是底层表的空间管理。目标是尽可能快速高效地加载数据,同时保证物理存储不会对将来的数据访问造成障碍。你可以这样思考这个问题,就像你计划去一家超市进行每周大采购一样。

如果你知道需要买很多商品,当进入超市时,你会选择购物车而不是手提购物篮。购物车将确保您有足够的空间购买所需的一切,当您结帐时您也很容易地拿取出所有物品。

同样的,Oracle 建议使用 BIGFILE 表空间来减少必须管理的数据文件的数量。当创建BIGFILE表空间时,初始数据文件将格式化。每次扩展数据文件时,还必须格式化扩展部分。这是一个代价高昂的操作,应当最小化。为获得最佳性能,Oracle 建议通过在BIGFILE表空间上指定较大的自动扩展大小,将数据加载期间的空间分配操作量降至最低。

本地管理的表空间可以有两种类型的扩展区管理选项:自动分配AUTOALLOCATE(默认)和统一UNIFORM。使用AUTOALLOCATE,数据库根据对象的属性和大小选择可变扩展区大小,而UNIFORM 则强制分配预定义的固定大小的扩展区。对于高速率的捕获工作负载(如物联网),Oracle建议使用 AUTOALLOCATE。但是,AUTOALLOCATE的默认分配策略从非常小的扩展区大小开始,这对于像物联网这样的重捕获工作负载来说可能是保守的。因此,我们建议您在表创建时,指定较大的初始扩展区大小(最小为 8MB),以强制自动分配AUTOALLOCATE从该值开始向上增加。这将避免等待分配新扩展区的进程。另外,为执行数据加载的用户在表空间上授予无限制的配额,以避免每次在表空间中请求额外空间时产生任何计算开销。

下面的示例创建表空间TS_DATA,应用了上述讨论的最佳方法:

数据加载机制

现在让我们讨论一下数据捕获。使用Oracle数据库,可以通过两种方式将数据插入表或分区:常规插入或直接路径插入。您可以将数据捕获视为类似于将商品放入购物车并在超市付款。

您永远不会一次挑选一个商品并付款,然后再挑选待购清单中的下一个商品(单行插入,逐个提交)。您将逛遍超市,收集待购清单中的所有商品,然后一次性付清(数组插入,统一提交)。当您希望高效地将数据插入数据库时,也是如此。

常规插入

常规插入,使用SQL INSERT语句向表或分区添加新行。Oracle数据库自动维护表上的所有引用完整性约束和表上的任何索引。数据库还尝试重用已构成表的数据库块中任何现有可用空间。常规INSERT语句的所有信息都记录在重做日志中,以防出现故障。尽管可以使用INSERTALL添加多行,但是通常,使用INSERT命令一次添加一行,随后跟着一个COMMIT。

单个会话可以实现500行/秒的捕获,仅通过一个常规的单行插入跟着一个COMMIT。要通过单行插入捕获更大的数据量,需要有数百个并发会话执行相同的插入语句,这可能导致共享池和集群锁级别的争用。为了减少锁定开销,您可以考虑禁用表级锁定。禁用表级锁将阻止任何DDL命令(删除、截断、添加列等)。)发生在对象上,但也会加快每个insert语句的速度。示例如下:

提交频率

为了保留数据库更改,必须进行事务提交。每一次INSERT后跟着一个COMMIT语句,就会生成大量redo,这反过来会导致RAC节点上的磁盘和CPU利用率很高,特别是如果有大量并发会话时。因此,建议只在插入多行(例如100行)后一次COMMIT提交。这将减少redo生成量和CPU消耗量,并将单个会话的数据捕获率从每秒550行提升到每秒7000行。

但是,与单行常规插入相比,更有效的替代方法是利用数组插入并在每次数组插入后提交。

数组插入

Oracle的数组接口允许用一条语句插入多条记录或多行。您可以利用任何Oracle数据库应用程序编程接口(API)来使用数组插入,而不管您使用哪种编程语言(Python、Java、JavaScript、.NET、PL/SQL、C/C++等)。数组接口显著减少了每行生成的redo(比单行插入少6倍)和数据库服务器上的CPU消耗(比单行插入少15倍),从而实现测试环境中的单个会话每秒插入18000次。每个会话使用更少的CPU还可以并发更多会话。

当插入大量数据时,数组插入还减少了网络往返和上下文切换的次数。这种减少会带来很大的性能提升。

直接路径加载和外部表

与常规插入相比,另一种更有效的方法是使用Oracle的直接路径加载。如果要不会的数据来自较大的平面文件,直接路径加载比常规插入更可取。这就像你计划大量囤货采购一样。

为了快速大量囤货,你肯定不会反复多次去超市购买类似的东西,相反,一般会去大型量贩式超市,选用最大的购物平板车一次性直接买完。当您用直接路径加载数据时,就是类似后者的操作。

直接路径加载先解析输入数据,将每个输入字段的数据转换为其相应的Oracle数据类型,然后为数据构建列数组结构。这些列数组结构用于格式化Oracle数据块和构建索引键。然后,新格式化的数据库块直接写入数据库,绕过标准的SQL处理引擎和数据库缓冲区缓存。

直接路径加载通常是通过从外部表中执行CREATE TABLE ASSELECT语句或者INSERT AS SELECT语句来实现的。为了让INSERT AS SELECT语句绕过数据库缓冲区缓存,必须使用APPEND提示。当每隔几分钟或更长时间以“批处理模式”加载数据时,直接路径加载是合适的。直接路径加载通常使用外部表,这使得外部数据(文件)在数据库中作为虚拟表可见,从而可以直接并行查询,而无需首先在数据库中加载外部数据,如下图所示:

外部表和常规表的主要区别在于,外部表是只读表,其元数据metadata存储在数据库中,但其数据存储在数据库之外的文件中。外部表使用标准的CREATE TABLE语法创建的,但它需要一个额外的ORGANIZATIONEXTERNAL子句。此附加子句指定所需访问驱动程序类型(ORACLE_LOADER, ORACLE_DATAPUMP, ORACLE_HDFS, 或ORACLE_HIVE),访问参数,文件所在的目录名称以及表中列的定义。

为了确保使用外部表进行高性能和可扩展的数据加载,对外部文件的访问需要快速,并且应该使用并行执行。
改善直接路径加载的建议
首先应该考虑,外部临时文件的位置。临时文件应位于外部共享存储上,集群中的所有RAC节点均可访问到。共享存储的IO吞吐量直接影响加载速度,因为数据的加载速度永远不会超过其读取速度。

为了保证最佳性能,不应将临时文件放在数据库使用的相同物理磁盘上,以避免争夺IO带宽。此建议不适用于Oracle Exadata,因为它具有足够的IO容量,可以将数据库文件系统(DBFS)上的外部数据文件像数据库文件一样在Exadata存储服务器上进行条带化打散。

如果共享存储IO吞吐量明显低于数据库的捕获速率,请考虑压缩外部数据文件并在加载之前预处理数据。注意,这是数据解压缩的CPU资源与IO带宽之间的折衷权衡;它还对如何使用并行施加了一些限制,如本文后面的“并行化直接路径加载”部分所述。

其次,外部临时文件中的数据格式也会对加载性能产生重大影响,因为解析列格式和应用字符集转换可能会占用大量的CPU资源。我们建议您仅使用单字符分隔符作为记录终止符和字段分隔符,因为它们可以比多字符分隔符更有效地处理。还建议外部数据文件中使用的字符集与数据库的字符集匹配,以避免字符集转换(单字节或固定宽度字符集是最有效的)。

如果确实需要进行数据变换和转换,最好由Oracle在加载过程中完成,而不是预处理文件。这可以通过在数据库中使用SQL完成,或利用外部表的预处理功能,作为外部表的初始化数据访问的一部分来完成。

接着,加载期间的锁定问题。在直接路径加载操作或任何并行DML期间,Oracle将以独占方式锁定整个目标表。锁定可以防止对表或其分区执行其他DML或DDL操作;但是,表中的数据对于来自其他会话的查询是完全可访问的。可以使用分区扩展语法防止获取表级锁,该语法只锁定指定的分区。分区扩展语法示例如下:

然后,考虑并行化直接路径加载。并行执行是一种常用的加速操作的方法,它将操作分成更小的子任务。就像如果你的家人和你一起去超市,你会把一个大的购物清单分成两份一样,你可以利用数据库中的并行执行来加速数据捕获和查询。

Oracle数据库中的并行执行基于协调器(通常简称为查询协调器,Query Coordinator ,简称QC)和并行执行(PX)服务器进程的原理。QC是发起并行SQL语句的会话,PX服务器是代表发起会话并行执行工作的各个进程。QC将工作分发给PX服务器进程,并在将结果返回给最终用户之前对结果进行汇总。

为了实现可扩展的直接数据加载,必须并行处理外部文件。从处理的角度来看,这意味着输入数据必须可以被划分成工作单元,称为颗粒度,然后由PX服务器进程并发处理。
最后,数据压缩的问题。加载大量数据总是让人问起这个问题:是否需要在数据加载期间压缩数据。这是在最大限度地提高数据捕获性,和改进查询性能(因为必须从磁盘读取的数据更少),以及节省空间三者之间的权衡。
在我们的超市采购比喻中,压缩问题类似于你如何组织购物车中的商品。如果你在购物车里放了很多的商品,花一点时间整理它们,也好过直接把它们扔进去了事。
要以压缩格式加载数据,只需将目标表(或分区)声明为COMPRESSED。Oracle提供以下压缩算法:
  • COMPRESS/COMPRESSFOR DIRECT_LOAD —块级压缩,仅用于直接路径操作
  • COMPRESSFOR ALL—块级压缩,用于直接路径操作和常规DML,是高级压缩选项的一部分
  • COMPRESSFOR [QUERY|ARCHIVE] [HIGH|LOW]— 列压缩,仅用于直接路径操作,是Exadata存储的独有特性

无论选择何种压缩技术,在数据加载操作期间都会消耗额外的CPU资源。然而,压缩数据的“利“可能远远大于”弊“,因为数据通常只被捕获一次,从不更改,也不会被查询多次。

内存优化的行存储 
Oracle数据库19c提供了一种通过内存优化行存储捕获流或物联网数据的替代方法。使用内存优化的行存储快速捕获,可以绕过正常的Oracle事务机制,并将数据捕获到大型池中的新临时缓冲区中。然后,缓冲区的内容通过延迟的异步过程定期写入磁盘。由于应用程序不必等待数据写入磁盘,插入语句返回得非常快。

但是,在将数据持久化到磁盘之前无法查询数据,如果数据库在将捕获的数据持久化到磁盘之前关闭,则可能丢失数据。这与传统上在Oracle数据库中处理事务的方式非常不同,传统上一旦写入/提交到数据库中,数据会被日志记录下来,就不会丢失。因此,如果需要完全符合ACID准则,应用程序有责任在丢失数据之前检查所有数据是否已被持久化。应用程序可以使用DBMS_MEMOPTIMIZE包确认数据被持久化。也可以使用DBMS_MEMOPTIMIZE包强制刷新缓冲区的内容。
为了使用Memoptimized Rowstore 快速捕获功能,必须首先通过将MEMOPTIMIZE  FOR WRITE子句添加到CREATE TABLE或ALTER TABLE语句中来启用一个或多个表进行快速捕获。然后对所有后续的INSERT语句使用MEMOPTIMIZE_WRITE提示。语法示例如下。您也可以通过查询视图V$MEMOPTIMIZE_WRITE_AREA来监控快速捕获缓冲区的使用情况。

灵活性

物联网目前还处于初级阶段,每一个新设备都有新的用例。能够轻松适应数据格式的变化,以及能够快速分析和管理大量数据至关重要。
JSON支持
为了确保最大的模式灵活性,物联网数据通常以JSON的形式发送。JSON文档实际上允许物联网系统无模式化,因为每个文档可以包含一组不同的属性和值。Oracle数据库12c提供了对JSON的原生支持,就像过去对XML的支持一样。Oracle了解JSON数据结构,并将JSON数据以原生结构保存在数据库中。然而,与XML不同,JSON文档没有新的数据类型。相反,JSON以文本形式存储在任何表列中,使用VARCHAR2、CLOB或BLOB数据类型。使用现有的数据类型可以确保JSON数据自动得到所有现有数据库功能的支持,包括Oracle Text和Database In-memory的功能。这将使得数据可以实时捕获和处理。
对于现有的数据库用户或应用程序来说,使用标准的SQL访问JSON文档中的信息也非常容易。下面的命令从METER_READINGS表的JSON列存储中提取每个抄表的城市:
分区
管理TB甚至PB的数据需要高效率和可扩展性。Oracle分区为您提供了这两种能力,同时对应用程序查询完全透明。

正如超市被划分为不同的区域(水果、蔬菜、肉类、软饮料等)一样,分区允许将表、索引或索引组织表(index-organizedtable)细分为更小的部分。每个部分都称为一个分区,有自己的名称和存储特性。从数据库管理员的角度来看,分区表有多个部分,可以一起管理或单独管理。但是,从应用程序的角度来看,分区表与非分区表是相同的。

通过提高可管理性、可用性和性能,分区可以为高捕获率的工作负载提供巨大的好处。
分区的可管理性

假设这样一个场景:表中存储了两年的智能仪表读数100 TB。每小时需要将一组新的仪表读数加载到表中,并且需要删除最旧的一小时数据。如果仪表读数表是按小时划分的RANGE分区,则新数据可以直接加载到最新的分区中,而最旧的一小时数据使用以下命令可以在不到一秒钟的时间内删除:

分区还可以帮助您压缩旧数据。例如,您可以将数据加载到未压缩的分区中,而表的其余部分以压缩格式存储;一段时间后,也可以使用ALTER TABLE MOVE PARTITION命令压缩当前分区。

分区的性能

分区也有助于提高查询性能,因为它确保只扫描必要的数据来响应查询,就像超市里的过道一样,只访问你感兴趣的那排过道的商品。
让我们假设企业用户主要每天访问一次仪表数据,例如每天使用的总电量。然后,对该表按小时进行RANGE分区将确保以最有效的方式访问数据,因为总共17,520个分区(2年)中只需要扫描24个分区来响应业务用户的查询。避免扫描不相关分区的能力称为分区修剪。
如果对METER_READINGS表进行了子分区,则可以进一步进行分区修剪。子分区允许每个分区内的数据细分为更小的独立部分。假设METER _ READINGS表是按meter_id建立HASH子分区的,并且形成了32个子分区。然后,某一个家庭在给定的一天用了多少电量的一个查询,将只访问组成该天的24个RANGE分区中的1/32的数据。Oracle使用线性HASH算法创建子分区。为了确保数据在HASH分区之间均匀分布,强烈建议HASH分区的数量是2的幂(例如,2、4、8等)。在RAC环境中,它也应该是RAC节点数的倍数。

分区的关联性

在RAC环境中,为了使数据捕获操作期间实现线性可扩展,将分区与特定节点关联至关重要。
你可以像在去超市之前整理购物清单一样,用类似方式考虑将数据关联起来。通过将你需要的所有水果、蔬菜,以及所有冷冻食品一起列出来,你可以只去超市的每个过道一次,而不必在各个过道之间来回穿梭,寻找清单上的所有商品。
为了将分区关联到特定的RAC节点,您需要做两件事:
  • 为仅连接到一个节点的每个(子)分区创建一个唯一的数据库服务

  • 按(子)分区键对传入数据进行排序,以便每个数组插入只包含一个(子)分区的数据

创建唯一的数据库服务

在我们的例子中,METER _ READINGS表有32个HASH子分区。因此,我们需要创建32个唯一的数据库服务,每个子分区一个。这些服务将使应用程序能够在每次需要将数据插入指定子分区时连接到特定节点。服务应均匀分布在各RAC节点上。假设我们有8个RAC节点,我们将为每个节点创建4个服务。

如果一个节点发生故障,最初连接到该节点的每个服务都将切换到另一个剩余节点,从而确保没有剩余节点被额外的工作压垮。
对传入数据进行排序
为了确保每个RAC节点只插入到一个子分区集中,我们需要创建数组插入或外部表,这些表只包含一个给定(子)分区的数据。为此,需要根据所选的分区策略对传入数据进行排序。在我们的示例中,METER_READINGS表按小时进行RANGE分区,并按meter_id HASH进行子分区。因此,必须首先按time_id列对数据进行排序,然后在每个小时内必须按HASH子分区对数据进行排序。但是如何确定一个meter_id将属于哪个HASH子分区?首先需要将meter_id转换为Oracle编号,然后使用开源文件lookup.c中的HASH()函数。
实时分析
实时分析物联网场景中捕获的数据必然会带来业务收益。这种收益是:
  • 优化业务处理流程并降低运营成本
  • 预测设备故障
  • 规划新产品或服务
  • 提供差异化的客户体验。
Oracle数据库为提高实时分析能力而提供的不同技术。
并行执行
物联网工作负载分析通常需要在数万亿条数据记录中实时执行查询。实现实时分析的关键是有效利用所有可用的硬件资源。
如前所述,并行执行是一种常用的加速数据库操作的方法。它允许将一个任务分成多个并发执行的子任务。默认情况下,数据库配置为支持即开即用的并行执行。除此之外,可以用自动并行度(AutoDOP)来控制如何以及何时对每个单独的SQL语句使用并行度。并行执行是大规模物联网部署的一个关键特征,始终是推荐的。
索引
提高数据库查询性能的最传统方法是对查询中涉及的表创建索引,因为它们通常提供了更快的数据访问路径。
你可以把索引想象成悬挂在超市每个过道上的标志,告诉你苹果或大米的确切位置。
Oracle数据库提供多种索引,包括:B-Tree索引、反转键索引、基于函数的索引、位图索引、语义索引和文本索引。
对于分区表,可以将索引创建为本地索引或全局索引。本地索引从表中“继承”分区策略。因此,为基础表的每个分区构建本地索引的每个分区。这种耦合可以优化分区维护;例如,当删除表分区时,Oracle也只需删除相应的索引分区。全局分区索引是使用与表不同的分区键或分区策略分区的索引。将索引与其表分区解耦自然意味着表上的任何分区维护操作都可能导致索引维护操作。
对于主要是分析性的物联网工作负载,Oracle建议采用本地索引。
保持索引事务一致的开销
当表中存在索引时,插入到表中的每一行都必须有相应的条目插入到索引中。对于常规插入,这会增加每行使用的CPU、每行生成的redo量以及每行修改的块数。如果多个进程插入索引上的同一位置,则索引可能会引入争用。与没有索引的情况相比,存在一个本地分区索引会使插入一行的CPU使用率增加5倍。由于需要记录对索引和表的所有更改,生成的重做量增加了6倍,块更改的数量要高出20倍。这将导致每秒可插入的行数减少5倍。如果要增加两个本地分区索引,捕获率将比不用索引时每秒减少13倍。
使用外部表的直接路径加载操作也需要维护索引,但维护效率更高,因为索引不是逐行维护的。在数据库内部,索引维护被延迟到所有数据加载之后,但在提交事务并使加载的行可见之前。尽管如此,当存在索引时,对性能仍有很大的影响。
部分可用索引
通过使用部分可用索引,可以最大限度地减少索引对数据捕获的影响。部分可用索引允许仅在表中的一个分区子集上创建本地和全局索引。通过只允许在表中的稳定分区(很少或没有数据捕获的旧分区)上构建索引,索引的存在对捕获性能的影响最小。
仅在索引分区内进行数据的分析查询,使用索引会更快的。仅在非索引分区中进行数据的查询,将必须扫描整个分区,但是由于它被大量修改,数据很可能在内存的数据库缓冲区缓存。进行多个分区的数据分析查询(有些带索引,有些不带索引),可以利用表扩展(Table Expansion)的查询转换。表扩展允许优化器生成一个计划,该计划在主要读取分区上使用索引,并在活动更改分区上使用全表扫描。
物化视图
通常在物联网工作负载下,业务收益来自于识别模式或异常,而不是查看单个条目。预汇总和预聚合数据通过减少每个查询所需的系统资源量,显著提高了查询性能和总体系统可扩展性。理想情况下,汇总和聚合应该对应用程序层透明,以便它们可以随着时间的推移而优化和发展,而不必对应用程序本身进行任何更改。
数据库中的物化视图(Materialized Views ,简称MV)提供了汇总或聚合数据的能力,并且对应用程序完全透明。查询重写(queryrewrite)的特性会自动重写SQL查询去访问MV,从而使物化视图对应用程序保持透明。
Oracle内存数据库Database In-Memory(IM)
如果需要更快速方法来分析物联网数据,请考虑利用Oracle内存数据库(Database In-Memory)。利用内存数据库,物联网数据可以以新的内存优化列格式发布到内存中,以提高即席分析查询的性能。
数据库在传统行格式和新列格式之间均保持完全的事务一致性,就像它在表和索引之间保持一致性一样。Oracle优化器完全了解列格式中存在哪些数据,自动将分析查询路由到列格式进行,将OLTP操作路由到行格式进行,从而确保所有工作负载的卓越性能和完整的数据一致性,而无需任何应用程序的更改。存储中保留数据的单个副本(以行格式),因为不会生成额外的redo或undo操作,因此不会产生额外的存储成本或对数据加载的影响。将表放入IM列存储的语法示例如下:
与其他厂商内存列式解决方案不同的是,并非数据库中的所有数据都需要以列式格式发布到内存中。在内存数据库Database In-memory中,只有关键性能的表或分区才应该进入到内存中。这使得企业能够对当前感兴趣的数据进行实时分析,同时以很低的成本将历史数据高效地存储在磁盘上。对于同时访问行和列格式数据的查询,数据库将使用其在内存、闪存和磁盘上的优化功能来访问和聚合数据。
结论
通过本文原理与实践结合的论述,可以看到Oracle数据库不仅能够充分满足物联网平台的数据负载处理要求,更是新基建工业互联网发展中,运行物联网平台关键数据管理解决方案的最佳选择。

扫描下方QR Code即刻预约ADW演示

  编辑:范宏伟

最后修改时间:2020-05-08 15:11:53
文章转载自甲骨文云技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论