关键词:OT数仓 工业物联网 智能工厂 时序库 数据采集 数字化转型 IT/OT融合

    从技术演化的视角来看,工业互联网发展的核心在于OT技术(控制技术)与IT技术(信息技术)的融合。如果把时间尺度拉长,思考未来10年OT技术与IT技术融合的路线图,可能有两条路线和模式:一是在现有技术、产品、网络、系统架构不变的基础上,以标准统一驱动数据融合,可以称之为“存量嫁接式融合”;二是推动OT、IT底层技术体系基于云边端进行解构,在解构基础上重新封装并实现融合,可以称之为“数字原生式融合”。未来10年,可以预期的是这两条技术路线将会长期并行并交叉融合。这一趋势可以用四个关键词来描述:“软硬解耦”、“能力复用”、“云边一体”和“生态迭代”。

--安筱鹏,工业互联网平台演进的四个关键词

1、摘要

      在制造企业数字化转型中,如何实现工厂设备数据的联网采集,如何实现OT/IT的融合,是近年来的热门话题。本人在智能制造IT领域工作多年,去年有机会加入一家营业收入近千亿中国企业500强排名200多位的大型制造企业研究院从事IT/OT融合方面工作和进行OT落地建设,结合最近休息时间的思考,给出基于开源技术打造制造业数字原生的OT数仓建设思路,供行业同仁参考。

2、前言

    OT ( Operational Tech)操作技术是直接对工业的物理过程、资产和事件进行监控和/或对过程、资产和事件实施改变控制的硬件和软件。 OT 其实就是工业控制系统( PLC、 DCS、 SCADA 等) 及其应用软件的总称。OT 专业人员偏好使用 PLC、 DCS、 RTU、 HMI、 SCADA,以及嵌入式计算技术。

    OT和IT的巨大鸿沟,使得IT人员更多的是从技术角度去思考,而OT人员从设备角度看待问题,往往导致双方在碰到具体业务问题和应用场景时,很难快速达成共识和进行技术实现。

    目前大的公有物联网云平台,一般使用时先建模,再收集存储数据方式实现。这个在互联网环境下安全性有保障,但成套体系仿照搭建起来工作量很大且易用性会受到很大的考验。大型制造企业一般具备较为完善的IT基础设施条件和基本的网络安全防护,也可以采用边建设边治理的形式,数据采集存储与数据管理同步进行。

3.架构设计


3.1.设计原则

    OT数仓设计时遵循几个主要原则:标准化、安全性、先进性、易用性。

3.2.架构设计

    OT数仓在架构设计上采用比较松散的设计。我们可以将整个数仓分为五层,从下往上分别为OT数据源、边缘数据接入、数据存储、PaaS服务和SaaS应用。OT、IT底层技术基于云边端进行解构,在解构基础上重新封装并实现融合,实现软硬解耦和能力复用,最终实现云边一体的OT工业数仓。

     边缘数据接入:边缘数据接入,能采用OPC尽量采用OPC进行连接,OPC驱动不支持的再采用其他方案。直接与OT设备对接只能依照OT设备协议进行,偏IT的设备也可以采用RestfulApi,MQTT等协议。
    边缘端设备必须要考虑时间同步问题,时间不同步数据虽然可以接受和存储,但对于后续的数据分析和流式计算会带来麻烦。
    数据存储:提供Kafka缓存、Redis、时序库多种存储方式,提供Mysql主数据和元数据存储。
    PaaS服务:提供OT数仓的存储、读取、计算、数据管理等通用功能,为上层SaaS应用提供多样化的OT数据服务。
    SaaS应用:基于OT数据的应用,比如设备监控、生产监控、生产过程回溯等。这个不在本次范围内详述。

3.3.关键组件技术选型

3.3.1.时序库选型

    OT数据是典型的时序数据,在数据写入要求按照指定的时间粒度持续写入,支持实时、高并发写入,无须更新或删除操作。数据读取方面写多读少,多时间粒度、指定维度读取,实时聚合。
    时序库选型原则:高压缩、高吞吐、低资源消耗、强聚合计算
    重点测试了几款开源数据库:Influxdb、Hive、Cassandra、ElasticSearch、TDengine。
    在现有硬件条件下,各款数据库的写入速度都表现强劲,差别在于压缩和聚合计算能力。
    Influxdb:老牌的时序库,部署简单、无依赖,存储压缩率高。在IT设施软件监控方面应用很多,相关生态完善。聚合计算方面较弱,需要使用专用API查询。开源版本不支持集群。
    Hive:基于Hadoop,比较重,适合于T+1场景。
    Cassandra:开源的分布式和分散式/分布式存储系统(数据库),基于Key查询,扩充方便。需要预先设计好组合Key,没有压缩,聚合计算功能弱,不适合于时序数据存储。
    ElasticSearch:搜索引擎的首选。它可以快速地储存、搜索和分析海量数据,没有压缩和聚合计算。
    TDengine:涛思数据专为物联网、车联网、工业互联网、IT运维等设计和优化的大数据平台,非常年轻的一个国产时序数据库,资源需要很低,迭代速度很快。在超级表模式下,可以使用大部分的标准SQL语法查询数据,具有非常灵活的标签功能。2.0开始支持支持集群。
    在开始选型对比时,把时序库是否支持集群作为重要指标考虑。在测试对比过程中,想到时序数据库没有修改和删除、没有事务需求,完全可以利用Kafka中间件一次生产多次消费特点,可以非常简单的实现分库分表和冗余。
    经过测试后,TDengine能更好的满足业务需要,尤其是标签的修改功能可以实现原始数据存储后,再对数据进行标签化管理从而实现数据治理的能力,方便后期维护管理,最终选定作为时序数据存储数据库。

3.3.2.关系数据库选型

    OT数仓对于关系数据库没有特殊的要求,支持CRUD即可,Oracle、DB2、SQL Server等商用数据库和开源的Mysql都可以。本方案选用MySql8,Innodb存储引擎,集群。

3.3.3.影子设备缓存和应用缓存

    OT数仓为实时设备监控提供基于K-V的Redis缓存。Redis有全部数据的最新快照,监控等数据通过Redis获取,可以极大减轻时序数据库的查询压力,减少系统资源耗用。

4.技术实现


4.1.设备物模型

    OT设备从空间上看,有归属的组织和安装位置等属性;时间上看,设备属性数据会根据时间而变化。从IT/OT融合角度,每个数据项具有集团->公司->制造部->车间->产线/班组->工序->设备->测点的多级数据层级体系的主数据;具备正品、次品等数据属性,具备生产、质量、设备等数据分类属性的元数据,具备时间和值的变化状态。

    OT数据建模分两部分:数据存储建模和数据管理建模。
    数据存储建模:OT数据主要有几个属性:数据项名称、数据类型、数据值和数据更新时间。考虑到数据追踪需要,增加一个数据更新次数项,采集服务启动后连续工作期间数据每变化一次值增1。通用存储模型定义参考如下:

    //Ot数据模型
    public class OtData
    {
        //otId,可以是设备、网关、采集服务等,平台内唯一
        string otId;

        //测点/数据项名称,同otId下唯一
        string item;

        //数据时戳"yyyy-MM-dd HH:mm:ss.fff",数据精度根据需要可以到ns,一般到ms
        string ts;

        //测点数据类型
        string vt;

        //测点值
        string value;

        //更新计数器
        long counter;
    }

    上述的通用模型不是最优的,也可以结合数据特性进行针对性建模,后续的采集与处理基于此模型进行。
    数据管理建模:OT数据表现为简单的K-V特性,需要为设备的附加属性建模,数据的查询使用主要还是通过数据的管理体系进行。比如我们需要查询某个车间下的所有正品产量数据,就可以通过产量、车间编码标签条件查询并进行分类分组的统计、汇总、求平均值等聚合计算。随着业务的需求,在系统建成后,还会为数据增加数据处理标签。数据管理建模时,可以采用固定标签和预留标签结合的形式进行,比如车间编码、产线编码、工序单元编码、设备资产编号等主数据信息和其他分类信息,然后预留一定量的标签列供扩展。

 4.2.数据采集与接入

    数据的采集在不同行业难度相差极大,大致分为几个步骤:需要采集哪些数据、从哪采集、采用哪种物理层通讯协议连接、弱电施工(如果已经接入通讯网则不需要)、设备提供的数据通讯协议、数据项清单、解析协议、数据处理转换、数据缓存与上传等步骤。
    数据采集解析后,每个单独的数据包含数据名称、数据类型、数据值、更新时间和更新次数几项内容,数据质量(GOOD/BAD/UNCERTAIN等)根据需要决定是否保存。
    采集数据时,每个设备最好具有心跳数据,通过心跳数据监控设备通讯连接情况较为简单。比如PLC等设备自身具有时钟信息,会周期性变化,可以采集后用于做通讯心跳数据。条码等简单设备,也可以在通讯层做好通讯连接判定的同时,虚拟一个数据项目在通讯连接正常时随时间变化,模拟心跳。
    数据采集后可以按设备物模型定义先在采集端做缓存和做上传队列缓存。数据上传根据云端数据接入协议进行,可以选用Restful API、HTTP、MQTT等。如果通过NB-Iot/4G/5G等无线流量上传,为节省费用,可以每次多上传数据,先压缩再上传等;内网可以直接传送。在设计时,上传队列和上传服务采用接口配对,方便根据需要更换上传协议。
    工业标准接口OPC UA支持数据订阅,数据采集与上传时也可以采用订阅模式,预计可以减少90%以上的数据通讯和处理工作量以及存储开销。在订阅模式下,为了避免数据在传输过程中丢失,可以定期将一定周期内没有更新的数据,从缓存中再次上传。再次上传时不改变数据的采集时间、更新次数等内容,服务端收到后,会尝试重新写入时序库。
    数据采集时,要尽量部署设备时间同步服务,做到所有设备时间的准确性,为后续的数据分析打下基础。

4.3.云端接入

    云端接入主要是在服务侧建立与设备的通讯,用于接受数据的上传和指令的下达。安全机制可以采用基于IT和OT工业防火墙的保护、MQTT的验证、基于Token的验证等,根据需要选用熟悉的即可。
    云端数据的解析,采用和数据上传一致的格式即可,推荐使用JSON格式。
    数据指令的下达,可以建立白名单,仅分配指定的有限服务具备权限,对允许的数据项、数据范围进一步检查。在指令接受端可以进一步的配置数据处理白名单检查,务必确保设备数据的安全,防止攻击和误操作。

4.4.Kafka缓存

    Kafka是一种高吞吐量的分布式发布订阅消息系统,即使是在非常普通的硬件环境Kafka也可以支持每秒数百万的消息。采用Kafka做缓存,使云端接入和解析与后续的数据处理进行解耦,数据分析与处理服务的短暂暂停升级时也不影响数据的上传。Kafka的消费组与微服务也是天然的契合,方便系统弹性扩展。
    云端数据解析后,先把数据存入Kafka缓存,使用时需要先设计好Kafka的分区、Topic、Key策略、缓存期限和存入数据质量。
    Kafka数据在生产者产生数据时,根据Key策略分配到不同数据分区。数据消费时,同一个Topic下的分区数据会确保先进先出,而消费组同时读取到数据的消费者数量,不超过分区数量;超过分区数量时,多出的消费者处于等待状态,不会读到数据。
    分区数量可以使用Kafka集群数量的2倍以上,方便将来集群数量扩容时的水平扩展,但也不宜过多,过多的分区也会影响I/O通吐量;分区数量也需要考虑数据处理的微服务数量,分区数量需要不少于微服务数量,使微服务的性能达到最优。生产数据时会提供一个Key,同一个云端接入连接或同一个设备可以使用同一个Key,确保上传的数据先进先出。
    同一种数据格式的数据,可以使用同一个Topic。也可以根据需要建立多个Topic方便后续数据处理的分库分表。
    Kafka的数据保存期限默认只保存7天的数据,时间一到就删除数据,当遇到磁盘过小,存放的数据量过大,可以设置缩短这个时间。
    Kafka是否使用集群和集群Broker数量,可以根据企业IT资源情况决定。一般中央节点和重要的区域节点,建议使用集群,边缘侧节点根据情况而定。

4.5.Redis缓存

    Redis是一个高性能的key-value数据库,它支持存储的value类型很多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。
    采集的数据解析存入Kafka后,消费时保存一份数据到Redis。在实时监控等场景前端页面和服务查询数据最新值时,可以不经过时序库直接读取,减轻I/O压力和系统负荷。
    使用Redis时,需要设计好Redis的Key与Value结构,可以考虑每个设备一个Key,或相同设备组(产品)一个Key,Value使用hash类型方便快速索引。Value中的可以直接存储Json格式的数据。

4.6.TDengine时序存储

    时序数据的特点,是数据基于时间变化,数据的价值随时间变化而降低。时序数据一般有保存期限要求,数据按时间顺序排列先进先出。

4.6.1.TDengine常用概念

    TDengine官网有很详细的中文文档,参考:涛思官网文档
    对于OT数仓建模,主要关注几个基本概念(下图来自于TDengine官网):

    建库:根据具体的业务需要相似的数据采集点建一个库,比如每个车间一个库,或每个车间的生产数据、能源数据分别建库;数据点规模不大时,也可以一个工厂建一个库。建库时需要考虑数据的保存期限。TDengine的压缩比非常高,保存期限可以设长一点,过期数据会自动淘汰。测试库保存期限可以设置得更短一点,比如一个月,减少资源占用。
    超级表:超级表是TDengine独有概念,也是用好TDengine的精髓所在,在使用前务必多花时间充分理解。超级表创建语法:

CREATE STABLE [IF NOT EXISTS] stb_name (timestamp_field_name TIMESTAMP, field1_name data_type1 [, field2_name data_type2 ...]) TAGS (tag1_name tag_type1, tag2_name tag_type2 [, tag3_name tag_type3]);

超级表数据插入语法:

INSERT INTO
    tb_name
        [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)]
        [(field1_name, ...)]
        VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path
    [tb2_name
        [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)]
        [(field1_name, ...)]
        VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path
    ...];

    不同版本语法上可能有所差异,使用前注意查看官方文档。

    刚接触超级表时可能难于理解,可以将超级表参照MySQL进行理解。超级表对应于MySQL:TAGS表名+标签=主表,field_nam列可以理解为数据表=明细表。
每个超级表,从MySQL等关系数据库角度可以理解管理标签的主表和存储数据的子表。上述定义转换为MySQL定义如下:
    主表:

    子表:

主表和子表之间通过tb_name关联。

4.6.2.集群与分库分表

    集群模式:TDengine当前的开源版支持集群,但集群功能与关系数据库集群相比较弱,某一个节点故障较长时间后,就难再次接入,节点数量需要事先规划好,不能动态扩充。
    独立多节点模式:可以利用时序数据特点采用独立的多节点模式,自行管理分库分表。全部数据已经事先存入Kafka,可以部署多个消费组重复消费数据解析,然后根据指定规则(比如Topic、Key)存入指定的节点。这种模式的优点是非常灵活,缺点的对数据的标签更改等需要在多个节点上重复执行。独立多节点模式,曾在生产环境连续几个月使用,数据量上百亿时都可以做到数据完全一致。

4.6.3.通用模型设计

    在“设备物模型”章节我们设计了一个简单的通用物模型,实际有效数据有ts、value、counter三项,其中ts和counter类型的固定的。value可能具有多种类型,比如int8、int16、int32、bool、float、double、string等。
    为方便后续查询,将整数类型合并,浮点类型float和double合并(根据实际需要决定使用float或double,此次使用float),使用int、float、bool和binary(String),在存储压缩和查询简便方面进行折中。
    数据标签固定的有otId和item两个,工厂、车间、工序、数据分类等根据实际业务设计并进行适当预留。
    超级表创建命令如下:

CREATE TABLE IF NOT EXISTS ot_int(ts timestamp, v int, counter int) TAGS (otId binary(64),item binary(128),Tags1 binary(16),Tags2 binary(16),Tags3 binary(16),Tags4 binary(16),Tags5 binary(16),Tags6 binary(16));
CREATE TABLE IF NOT EXISTS ot_float(ts timestamp, v float, counter int) TAGS (otId binary(64),item binary(128),Tags1 binary(16),Tags2 binary(16),Tags3 binary(16),Tags4 binary(16),Tags5 binary(16),Tags6 binary(16));
CREATE TABLE IF NOT EXISTS ot_string(ts timestamp, v binary(128), counter int) TAGS (otId binary(64),item binary(128),Tags1 binary(16),Tags2 binary(16),Tags3 binary(16),Tags4 binary(16),Tags5 binary(16),Tags6 binary(16));
CREATE TABLE IF NOT EXISTS ot_bool(ts timestamp, v bool, counter int) TAGS  (otId binary(64),item binary(128),Tags1 binary(16),Tags2 binary(16),Tags3 binary(16),Tags4 binary(16),Tags5 binary(16),Tags6 binary(16));

4.6.4.子表命名

     超级表写入数据时,需要一个子表名。使用超级表并结合标签后,子表名仅仅是在数据插入时的一种需要,查询完全可以抛开子表名直接使用标签查询。    

    设备的item命名规则和TDengine的一般不一致,不能采用简单的otId+item形式做为子表名。
    子表名设计常规方法是数据项全部维护到数据库,并对每个数据项单独唯一编码,解析时先加载全部数据到服务的map中缓存查找;新数据必须先维护才能写入。也可以利用标签进行数据查找和分类,采用算法直接生成子表名。MD5信息摘要算法(MD5 Message-Digest Algorithm)就是一个很好的算法,otId+item做入参,生成一个32位的唯一的规则字符串,在前面加一个“T”做前缀,最终形成33位的子表名称。也可以给不同的超级表加不同的子表前缀。

    需要特别注意的是:子表名是独立于超级表的存在,同一个库下不同的超级表的子表名不允许重复。
    转换后的子表名仅在数据库保存和数据标签维护时使用,查询是用otId和item做条件,锁定唯一数据项,像普通关系数据库一样查数据即可。

4.7.Nginx与微服务

    Nginx是开源的、高性能、高可靠的 Web 和反向代理服务器。
    在大型OT系统中建议综合采用企业防火墙、Nginx网关和微服务网关进行路由。
    企业防火墙进行域名到服务IP的映射,Nginx实现大的路径分发,然后再由微服务网关进行进一不分发。
    微服务网关有SpingCloud版本一致性问题,系统构建时需要考虑微服务的粒度,方便升级维护。微服务内部可以使用RPC协议通讯,对外API建议采用Restful API并结合Nginx分发。

5.跨区域多节点设计

    大型企业的生产园区一般分散在不同城市,OT数仓建议根据企业的机房位置部署,结合OT网关建立跨区域的多节点OT数仓系统。
    在子节点部署一套新的OT系统,包括时序库、Redis缓存、Kafka缓存、OT服务等,子节点和中央节点之间通过Kafka同步工具同步数据。这个模式下,相当于一种特殊的分库分表,需要注意的是,对时序库数据标签的修改,需要在对于标签的所有节点执行。本地的监控等功能,可以直接访问部署在同一区域的OT数仓,上层AI分析等可以访问中央库数据。
    企业可以建立统一的二级域名访问OT数仓数据,比如ot.agile-iot.com/api/{factoryId}/query查询数据,根据factoryId进行路由到正确的PaaS服务地址。区域节点可以直接分配三级域名如cs.ot.agile-iot.com方便在企业防火墙级先路由,避免在区域与中央节点网络故障时的访问失效。
中央节点是建立集团集中的OT数仓,还是直接使用分布式OT数仓,需要根据企业实际情况决定。

6.应用实践

    在某大型制造业头部企业中,TDengine单节点分配8C/16G资源可以轻松支撑起每天上亿的数据写入,每3亿左右的数据量占用1G的存储。

    部署时相关软件版本:

Centos7.9/8.0

redis6.0.5 

Kafka2.13-2.6.0

mysql 8.0

TDengine 2.1.3.2(从2.0.10.0开始,期间多次升级)

JDK1.8

7、补充

    在此系统刚开始搭建时,TDengine的集群功能刚开源不久,远远不如目前版本的强大,当时权衡后还是采用的多副本独立部署。最近再次对TDengine集群功能测试时发现可靠性已经增强很多。

    另外,在超级表设计上,当时采用的是4组独立的超级表,这个非常节省存储空间,但对查询造成一定的麻烦,比如查一个设备下的所有数据时,需要查4个超级表。后来改进为仅一个超级表,由以前的单值模型改为多值模型(实际还是单值,一个OPC数据只可能是一个固定的类型),增加一个类型标签,同时仅一个值有效。这个模式下存储空间多10%左右,但灵活性强很多。参考模型如下:

CREATE TABLE IF NOT EXISTS ot(ts timestamp, vint int, vfloat float,vstring binary(128),vbool bool,counter int) TAGS (otId binary(64),item binary(128),vtype  binary(16),Tags1 binary(16),Tags2 binary(16),Tags3 binary(16),Tags4 binary(16),Tags5 binary(16),Tags6 binary(16));

  新增一个vtype标签用于存储数据的类型,vint/vfloat/vstring/vbool用来存储实际有效的数据,同时仅一个有效。vfloat在不使用时可以用0填充,不允许为空;vstring可以用NULL填充。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐