• 大数据:实时技术


    一、简介

    • 一般业务诉求:在第一时间拿到经过加工后的数据,以便实时监控当前业务状态并作出运营决策,引导业务往好的方向发展。
    • 按照数据的延时情况,数据时效性一般分为三种(离线、准实时、实时):
    1. 离线:在今天(T)处理 N 天前(T - N ≥ 1)的数据,延迟时间粒度为天;
    2. 准实时:在当前小时(H)处理 N 小时前(H - N,N > 0,如 0.5 小时、1 小时等)的数据,延迟时间粒度为小时;
    3. 实时:在当前时刻处理当前的数据,延迟时间粒度为秒;
    • 离线和准实时数据可以在批处理系统中实现(如 Hadoop、MaxCompute、Spark 等系统),只是调度周期不一样而已;而实时数据则需要在流式处理系统中完成;
    • 流式数据处理技术:业务系统每产生一条数据,就会立刻被采集并实时发送到流式任务中进行处理,不需要定时调度任务来处理数据;
    • 流式数据处理特质:

    • 时效性高:
    1. 数据实时采集、实时处理,延时粒度在秒级甚至毫秒级,业务方面能够在第一时间拿到经过加工处理后的数据。
    • 常驻任务:
    1.  区别于离线任务的周期调度,流式任务属于常驻进程任务,一旦启动后就会一直运行,直到人为的终止,因此即使成本会相对较高。
    2. 这一特点也预示着流式任务的数据源是无界的,离线任务的数据源是有界的。
    3. 是实时处理和离线处理最主要的差别。
    • 性能要求高
    1. 实时计算对数据处理的性能要求非常严格,如果处理吞吐量跟不上采集吞吐量,计算出来的数据就失去了实时的特性,因此,实时处理的性能优化占了任务开发的很大一部分工作。
    • 应用局限性

          # 计算成本较大,对于业务复杂的场景(如双流关联或者需要数据回滚的情况),其局限性导致支持不足;

          # 由于数据源是流式的,在数据具体有上下文关系的情况下,数据到达时间的不确定性导致实时处理跟离线处理得出的结构有一定的差异;

    二、流式技术架构

    • 在流式计算技术中,需要各个子系统之间相互依赖形成一条数据处理链路,才能产出结构对外提供实时数据服务。
    • 在实际技术选型时,可选的开源技术方案非常多,但是各个方案的整体架构是类似的,只是各个子系统的实现原理不太一样。
    • 流式技术架构中的系统跟离线处理是交叉的,两套技术方案并不是完全独立的,并且在业界中有合并的趋势;
    • 各个子系统按功能划分:

    1. 数据采集:数据的源头,一般来自于各个业务的日志服务器,这些数据被实时采集到数据中间件中,供下游实时订阅使用;
    2. 数据处理:数据被采集到的中间件中后,需要下游实时订阅数据,并拉取到流式计算系统的任务中进行加工处理。(这里需要提供流式计算引擎以支持流式任务的执行)
    3. 数据存储:数据被实时加工处理(比如聚合、清洗等)后,会写到某个在线服务的存储系统中,供下游调用方使用。(这里的写操作是增量操作,并且是源源不断的)
    4. 数据服务:在存储系统上会架设一层统一的数据服务层(比如提供 HSF 接口、HTTP 服务等),用于获取实时计算结果。
    • 从六十技术架构图来看,在数据采集和数据服务部分实时和离线是公用的,因为在这两层中都不需要关系数据的时效性。这样才能做到数据源的统一,避免六十处理和离线处理的不一致。

    1、数据采集

    • 数据采集是整个数据处理链路的源头,是所有数据处理的根节点,需要做到实时采集。
    • 所采集的数据都来自于业务服务器,可以分为两种采集数据类型:
    1. 数据库变更日志。(比如 MySQL 的 binlog 日志、HBase 的 hlog 日志、OceanBase 的变更日志、Oracle 的变更日志等)
    2. 引擎访问日志。(比如用户访问网站产生的 Apache 引擎日志、搜索引擎的接口查询日志等)
    • 不管是数据库变更日志还是引擎访问日志,都会在业务服务器上落地成文件,所以只要监控文件的内容发生变化,采集工具就可以把最新的数据采集下来。
    • 处于吞吐量以及系统压力上的考虑,并不是新增一条记录就采集一条,而是根据两个原则进行采集:
    1. 数据大小限制:当达到限制条件时,把目前采集到的新数据作为一批。(例如 512 KB 写一批)
    2. 时间阈值限制:当时间达到一定条件时,把目前采集到的新数据作为一批,避免在数据量少的情况下采集(例如 30 秒一批)。
    • 两个条件的参数需要根据业务的需求来设定。(当批次采集频繁时,可以降低延时,但必然会导致吞吐量下降。)
    • 采集到的数据需要一个数据交换平台(数据中间件)分发给下游。(数据中间件系统有很多实现方式,比如开源的系统有 Kafka,阿里内部采用的是 Time Tunnel ,还有 MetaQ、Notify 等消息系统)
    • 消息系统是数据库变更节点的上游,延时比数据中间件低很多,但是其支持的吞吐量有限。因此,消息系统一般用作业务数据库变更的消息中转。(对于较大的业务数据,一般通过数据中间件系统来中转,但是其支持的吞吐量高。)
    •  一般有些业务并没有通过消息系统对数据库进行更新,因此,从消息系统中获取的数据并不是最全的,而通过数据库变更日志拿到的业务变更过程数据肯定是全的。因此,为了和离线数据源保持一致,一般都是通过数据中间件来采集数据库变更数据这种方式来获取实时数据的(这需要在数据处理层对业务主键进行 merge 处理,比如一笔订单可能会被变更多次,会有多条变更记录,这时需要进行 merge 拿到最新的数据)。
    • 时效性和吞吐量是数据处理中的两个矛盾体,需要从业务的角度来权衡使用什么样的系统来做数据中转。

    2、数据处理

    • 实时计算任务部署在流式计算系统上,通过数据中间件获取到实时源数据后进行实时加工处理。
    • 业界使用较广泛的流计算引擎系统:Twitter 开源的 Storm 系统、虎牙开源的 S4 系统、Apache 的 Spark Streaming、Flink。
    • 阿里使用的是阿里云提供的 StreamCompute:涵盖了从数据采集到数据生产各个环节,力保流计算开发严谨、可靠。(提供的 SQL 语义的流式数据分析能力(StreamSQL),让流数据分析门槛不再存在。)
    • 流数据处理原理(以 Strorm 为例):
    1. spout:拓扑的输入。(从数据中间件中读取数据,并根据自定义的分发规则发送给下游的 blot,可以由多个输入源)
    2.  bolt:业务处理单元。(根据处理逻辑分为多个步骤,其相互之间的数据分发规则是自定义的)
    • 实时数据处理应用出于性能考虑,计算任务往往是多线程的,一般会根据业务主键进行分桶处理,并且大部分计算过程需要的数据都会在内存中,大大提高应用的吞吐量。(为了避免内存溢出,内存中过期的数据需要定时清理,可以按照 LRU(最近最少使用)算法或者业务时间集合归类清理)
    • 实时任务中的典型问题

    • 去重指标

    • 在 BI(商业智能)统计类实时任务中,去重指标在对资源消耗的评判指标总非常重要。
    • 由于实时任务为了追求处理性能,计算逻辑一般都在内存中完成,中间结果数据也缓存在内存中,带来了内存消耗过多的问题。
    • 在计算去重时,势必要把去重的明细数据保存下来,当去重的明细数据达到上亿甚至几十亿时,内存中放不下,此时需要分两种情况来看:
    1. 精确去重:明细数据必须保存下来,当遇到内存问题是,通过数据倾斜来进行处理,把一个节点的内存压力分到多个节点上。
    2. 模糊去重:在明细数据量非常大,而业务的精度要求不高时,可以使用相关的去重算法,把内存的使用量降到千分之一甚至万分之一,以提高内存的利用率。
    • 去重算法:

    • 布隆过滤器:
    1. 位数组算法的应用,不保存真时的明细数据,只保存明细数据对应哈希值的标记位。(采用此算法存储 1 亿条数据只需要 100 多 MB 内存)
    2. 会出现哈希值碰撞的情况,但是误差率可以控制,计算出来的去重值比真实值小。
    3. 适用场景:统计精度要求不高,统计纬度值非常多的情况。
    • 技术估计:
    1. 利用哈希的原理,按照数据的分散程度估算现有数集的边界,从而得出大概的去重值总和。(采用此算法存储 1 亿条数据只需要几 KB 的内存)
    2. 估算的去重值可能比真实值大,也可能比真实值小。
    3. 适用场景:统计精度要求不高,统计维度非常粗的情况。
    • 数据倾斜

    • 数据倾斜:对于集群系统,一般缓存是分布式的,即不同的节点负责一定范围的数据(一个节点上完成相关的计算任务),在数据量非常大的时候,造成有些节点上数据量非常大,而有些节点上的数据量很少(大量的缓存集中到了一台或几台服务节点上了),但是单个节点处理能力有限,必然会遇到性能瓶颈,这种现象称为数据倾斜。
    • 数据倾斜违背了并行计算的初衷,一个或部分节点要承受很大的计算压力,而其他节点计算完毕后要等到忙碌的节点,拖累了整体的计算时间,效率十分低下。
    • 不同的数据字段可能的数据倾斜一般有两种:
    1. 唯一值非常少:极少数值有非常多的记录值(唯一值少于几千);
    2. 唯一值比较多:这个字段的某些值有远远多于其它值的记录值,但是它的占比小于百分之一或千分之一;
    • 正常的数据分布理论上都是倾斜的;
    • 二八原理:80% 的财富集中在了 20% 的人手中,80% 的用户使用 20% 的功能,20% 的用户贡献了 80% 的访问量;
    • 数据倾斜在 MapReduce 编程模型中十分常见,用最通俗易懂的话来说,数据倾斜无非就是大量的相同 key 被 partition 分配到一个分区里,造成了 “一个人累死,其他人闲死” 的情况。
    • 解决方法:分桶处理(和离散处理的思路一样)
    1. 去重指标分桶:通过对去充值进行分桶 Hash,相同的值一定会被放在同一个桶中去重,最后再把每个桶里的值进行加和就得到总值。(这里利用了每个桶的 CPU和内存资源)
    2. 非去重指标分桶:数据随机分发到每个桶中,最后再把每个桶的值汇总。(主要利用的是各个桶的 CPU 能力)
    • 事务处理

    • 由于实时计算是分布处理的,系统的不稳定性必然会导致数据的处理有可能出现失败的情况。(比如网络的抖动导致数据发送不成功、机器重启导致数据丢失等)
    • 针对这些情况,流式计算系统提供了数据自动 ACK、失败重发以及事务信息等机制(都是为了保证数据的幂等性):
    • 超时时间:由于数据处理是按照批次来进行的,当一批数据处理超时时,会从拓扑的 spout 端重发数据。另外,批次处理的数据量不宜过大,应该增加一个限流的功能(限定一批数据的记录数或者容量等),避免数据处理超时;
    • 事务信息:每批数据都会附带一个事务 ID 的信息,在重发的情况下,让开发者自己根据事务信息去判断数据第一次到达和重发时不同的处理逻辑;
    • 备份机制:开发人员需要保证内存数据可以通过外部存储恢复,因此在计算中用到的中间结果数据需要备份到外出存储中;

    3、数据存储

    • 实时任务在运行过程中,会计算很多维度和指标,这些数据需要放在一个存储系统中作为恢复或者关联使用。其中会涉及 3 中类型的数据:
    1. 中间计算结果——在实时应用处理过程中,会有一些状态的保存(比如去重指标的明细数据),用于在发送故障时,使用数据库中的数据恢复内存现场。
    2. 最终结果数据——指通过 ETL 处理后的实时结果数据。这些数据是实时更新的,写的频率非常高,可以被下游直接使用;
    3. 维表数据——在离线计算系统中,通过同步工具导入到在线存储系统中,供实时任务来关联实时流数据。
    • 实时任务是多线程处理的,数据存储系统必须能够比较好的支持多并发读写,并且延时需要在毫秒级才能满足实时的性能需求。
    • 实践中,一般使用 HBase、Tair、MongoDB 等列式存储系统:
    1. 由于这些系统在写数据时先写内存再落磁盘,因此写延时在毫秒级;读请求也有缓存机制,多并发读是也可以达到毫秒级延时。
    2. 这些系统的缺点(以 HBase 为例):一张表必须有 rowkey,而 rowkey 是按照 ASCII 码来排序的,就像关系型数据库的索引一样,rowkey 的规则限制了读取数据的方式,如果业务方需要使用另一种读取数据的方式,就必须重新输出 rowkey。
    3. HBase 的一张表能够存储几 TB 甚至几十 TB 的数据,而关系型数据库必须要分库分表才能实现这个量级的数据存储,因此,对于海量数据的实时计算,一般会采用非关系型数据库,以应对大量的多并发读写。
    • 数据统计中表名设计和 rowkey 设计的实践经验:

       1)表名设计

    • 设计规则:汇总层标识 + 数据域 + 主维度 + 实践维度
    • 例:dws_trd_slr_dtr,表示汇总层交易数据,根据卖家(slr)主维度 + 0 点截止当日(dtr)进行统计汇总;
    1. 优点:所有主维度相同的数据都放在一张物理表中,避免表数量过多,难以维护。另外,可以从表名上直观的看到存储的是什么数据内容,方便排查问题;

       2)rowkey 设计

    1. 设计规则:MD5 + 主维度 + 维度标识 + 子维度1 + 时间维度 + 子维度2
    2. 例:卖家 ID 的 MD5 前四位 + 卖家ID + app + 一级类目ID + ddd + 二级类目ID;(卖家 ID 属于主维度,在查数据时是必传的)
    3. 以 MD5 的前四位作为 rowkey 的第一部分,可以把数据散列,让服务器整体负载是均衡的,避免热点问题。
    4. 每个统计维度都会生成一个维度标识,以便在 rowkey 上做区分;

    4、数据服务

    • 实时数据落地到存储系统中后,使用方就可以通过统一的数据服务(如 OneService)获取到实时数据,其好处是:
    1. 不需要直接连数据库,数据源等信息在数据服务层维护,这样当存储器迁移时,对下游是透明的;
    2. 调用方只需要使用服务层暴露的接口,不需要关心底层取数据逻辑的实现;
    3. 屏蔽存储系统间的差异,统一的调用日志输出,便于分析和监控下游使用情况;

    三、流式数据模型

    • 数据模型设计贯穿数据处理过程,流式数据处理也一样,需要对数据流建模分层。
    • 实时建模跟离线建模非常类似,数据模型整体分为五层:ODS、DWD、DWS、ADS、DIM。
    • 由于实时计算的局限性,每一层中并没有像离线做得那么宽,维度和指标也没有那么多,特别是涉及回溯状态的指标,在实时数据模型中几乎没有。

    1、数据分层

       1)ODS 层

    • ODS:操作数据层,是直接从业务系统采集过来的原始数据。
    • ODS 数据层包含了所有业务的变更过程,数据粒度是最细的。
    •  在 ODS 层,实时和离线在源头上是统一的,好处是用同一份数据加工出来的指标,口径基本是统一的,可以更方便进行实时和离线数据比对。

       2)DWD 层

    • DWD:是在 ODS 层基础上,根据业务过程建模出来的实时事实明细层。
    • 对于访问 DWD 层的数据(没有上下文关系,并且不需要等待过程的记录),会回溯到离线系统,供下游使用,最大程度的保证实时和离线数据在 ODS 层和 DWD 层是一致的。

     

       3)DWS 层

    • DWS 层:订阅明细层的数据后,在实时任务中计算各个维度的汇总指标。
    • 如果维度是各个垂直业务线通用的,则会放在实时通用汇总层,作为通用的数据模型使用。

       4)ADS 层

    • ADS:个性化维度汇总层。
    • 对于不是特别通用的统计维度数据,会放在 ADS 层,在 ADS 层,计算只有自身业务才会关注的维度和指标,跟其他业务线一般没有交集,通常用于垂直创新业务中。

       5)DIM 层

    • DIM:实时维表层。
    • DIM 层的数据基本上都是从离线维表层导出来的,抽取到在线系统中供实时应用调用。
    • 在 DIM 层,对实时应用来说是静态的,所有的 ETL 处理工作会在离线系统中完成。
    • 举例说明流数据模型的每一层所存储的数据:
    1. ODS 层:订单粒度的变更过程,一笔订单有多条记录;
    2. DWD 层:订单粒度的支付记录,一笔订单只有一条记录;
    3. DWS 层:卖家的实时成交金额,一个卖家只有一条记录,并且指标在实时刷新;
    4. ADS 层:外卖地区的实时成交金额,只有外卖业务使用;
    5. DIM 层:订单商品类目和行业的对应关系维表。
    1. ODS 层到 DIM 层的 ETL 处理是在离线系统中进行的,处理完成后会同步到实时计算所使用的存储系统,
    2. ODS 层和 DWD 层会放在数据中间件中,供下游订阅使用。
    3. DWS 层和 ADS 层落地到在线存储系统中,下游通过接口调用的形式使用。
    4. 在每一层中,按照重要性划分为 P0、P1、P2、P3等级,P0 属于最高优先级保障。根据不同的优先级给实时任务分配不同的计算和存储资源,力求重要的任务可以得到最好的保障。
    • 字段命名、表命名、指标命名是按照 OneData 规范来定义的,以便更好的维护和管理。

    2、多流关联

    • 多流关联:在流式计算中,常常要把两个实时流进行主键关联,以得到对应的实施明细表。
    • 在离线系统中,两个表关联非常简单,因为离线计算在任务启动时已经可以获得两张表的全量数据,只要根据关联键进行分桶关联即可。
    • 在流式系统中,流式计算的过程中,数据的到达是一个增量过程,并且数据到达的时间是不确定的和无序的,因此在数据处理过程中会涉及中间状态的保存和恢复机制等细节问题。
    • 多流关联的一个关键点就是需要相互等待,只有双方都到达了,才能关联:
    1. 比如:A 表和 B 表使用 ID 进行实时关联,由于无法知道两个表的到达顺序,因此在两个数据流的每条新数据到来时,都需要到另一张表中进行查找,如 A 表的某条数据到达,到 B 表的全量数据中查找,如果能查找到,说明可以关联上,拼接成一条记录直接输出到下游;但是如果关联不上,则需要放在内存或外出存储中等待,直到 B 表的记录也到达。
    • 不管是否关联成功,内存中的数据都需要备份到外部存储系统中,在任务重启时,可以从外部存储系统中恢复内存数据,这样才能保证数据不丢失。(在重启时,任务是续跑的,不会重新跑之前的数据)
    • 订单记录的变更有可能发生多次(比如订单的多个字段多次更新),在这种情况下,需要根据订单 ID 去重,避免 A 表和 B 表多次关联成功;否则输出到下游就会有多条记录,得到的数据有重复的。
    • 双流关联过程中,在实际处理时,考虑到查找数据的新能,实时关联这个步骤一般会把数据按照关联主键进行分桶处理,并且在故障恢复时也根据分桶来进行,以降低查找数据量和提高吞吐量。

    3、维表使用

    • 事实表:存放实实在在数据的表;
    • 维表:解释事实表中关键字维度的具体内容,不存放具体数据;
    • 一般一个事实表对应一个或多个维表;
    •  例:

    • 事实表

               

    • 维度表(此维表用来解释不同的机构代码代表的具体内容):

          

    • 在离散系统中,一般是根据业务分区来关联事实表和维表的,因为在关联之前维表的数据已经就绪了。
    • 在实时计算中,关联维表一般会使用当前的实时数据(T)去关联 T - 2 的维表数据,相当于在 T 的数据到达前把维表的数据准备好,并且一般都是一份静态的数据。
    • 在实时计算中,采用这种关联方式的原因:

       1)数据无法及时准备好

    • 当到达零点时,实时流数据必须去关联维表(因为不能等待,等待会失去实时的特性),而这个时候 T-1 的维表数据一般不能再零点马上准备就绪(因为 T-1 的数据需要在 T 这一天加工生产),因此去关联 T-2 维表,相当于在 T-1 的一天时间里加工好 T-2 的维表数据。

       2)无法准确获取全量的最新数据

    • 维表一般是全量的数据,如果需要实时获取到当天的最新维表数据,需要 T-1 的数据 + 当天变更才能获取到完整的维表数据。也就是说,维表也作为一个实时流输入,这就需要使用多流实时关联来实现。但是由于实时数据是无序的并且到达时间不确定,因此在维表关联上游歧义。

       3)数据的无序性

    • 在实时计算中,维表关联一般都统一使用 T-2 的数据,这样对于业务来说,起码关联到的维表数据是确定的(虽然维表数据有一定的延时,但是许多业务的维表在两天之间是很少的)。
    • 由于实时任务是常驻进程的,因此维表的使用分为两种形式:

    • 全量加载
    1. 使用情况:在维表数据较少的情况下,可以一次性加载到内存中,在内存中直接和实时流数据进行关联,效率非常高。
    2. 缺点:一致占用内存,并且需要定时更新。
    • 增量加载
    1. 使用情况:维表数据很多,没办法全部加载到内存中,可以使用增量查找和 LRU 过期的形式,让最热门的数据留在内存中。
    2. 优点:可以控制内存的使用量;
    3. 缺点:需要查找外部存储系统,运行效率会降低。

    四、大促挑战 & 保障

    1、大促特征

    1. 毫秒级延时
    2. 洪峰明显;(大促前会进行多次全链路压测和预案梳理,企鹅包系统能够承载主洪峰的冲击)
    3. 高保障性;(一般采用多链路冗余的方式对强保障性的数据进行保障)

    2、大促保障

      1)如何进行实时任务优化

    • 实时任务优化中经常考虑的要素:

       1/1)独占资源和共享资源的策略

    • 共享资源池可以被多个实时任务抢占,如果一个任务在运行时 80% 以上的时间都需要去抢资源,这时候就需要考虑给它分配更多的独享资源,避免抢不到 CPU 资源导致吞吐量急剧下降。

        1/2)合理选择缓存机制,尽量降低读写库次数

    • 内存读写性能是最好的,根据业务特性选择不同的缓存机制,让最热和最可能使用的数据留在内存中,读写库次数降低,吞吐量自然提升。

        1/3)计算单元合并,降低拓扑层级

    •  拓扑结构层级越深,性能越差。(因为数据在每个节点间传输时,大部分需要经过系列化和反序列化,而这个过程非常消耗 CPU 和时间)

        1/4)内存对象共享,避免字符拷贝

    • 在海量数据处理中,大部分对象都是以字符串形式存在的,在不同线程间合理共享对象,可以大幅降低字符拷贝带来的性能消耗。(需要注意不合理使用带来的内存溢出问题)

        1/5)在高吞吐量和低延时间取平衡

    • 高吞吐量和低延时,这两个特性是一对矛盾体,当把多个读写库操作或者 ACK 操作合并成一个时,可以大幅降低因为网络请求带来的消耗。(不过会导致延时增高,需要根据业务进行取舍)

      2)如何进行数据链路保障

    • 实时数据的处理链路:数据同步——数据计算——数据存储——数据服务,每一个环节出现问题,都会导致实时数据停止更新;
    • 为了保障实时数据的可用性,需要对整条计算链路都进行链路搭建,做到多机房容灾,甚至异地容灾:
    • 查找链路中问题的方法:
    1. 通过工具比对多条链路计算的结果数据,当某条链路出现问题时,它一定会比其它链路计算的值小,并且差异会越来越大。
    • 问题解决:一键切换到备链路,并且通过推送配置的形式让其秒级生效,所有的接口调用会立刻切换到备链路。

      3)如何进行压测

    • 压测方式:模拟大促的峰值情况,验证系统是否能够正常运行。
    • 压测都是在线上环境中进行的,分为:数据压测、产品压测。
    • 数据压测:

    • 主要是蓄洪压测,就是把几个小时甚至几天的数据积累下来,并在某个时刻全部开放,模拟大促洪峰流量的情况。(这里面的数据是真实的)
    • 产品压测:产品本身压测、前端页面稳定性压测。
    1. 产品本身压测(以 “双 11” 直播大屏为例):收集大屏服务端的所有读操作的 URL,通过压测平台进行压测流量回放。(按照 QPS :500 次 / 秒的目标进行压测,在也压测过程中不断的迭代优化服务端的性能,提升大屏应用处理数据的性能;)
    2. 前端页面稳定性测试:将大屏页面在浏览器中打开,并进行 8~24 小时的前端页面稳定性测试。(监控大屏前端 JS 对客户端浏览器的内存、CPU 等的消耗,检测出前端 JS 内存泄漏等问题并修复,提升前端页面的稳定性。)
  • 相关阅读:
    ftp卡死问题
    Windows 10 安装FileZilla Server
    The last packet successfully received from the server was 39,900 milliseconds ago问题解决
    java.sql.SQLException: Could not retrieve transaction read-only status from server 问题解决
    三主机配置 keepalived VIP高可用
    subprocess.Popen在win10下会有异常
    python 使用内置方法读取注册表
    过年有感
    java.security.InvalidKeyException: Illegal key size 解决办法
    Android Studio 检查Top Activity
  • 原文地址:https://www.cnblogs.com/volcao/p/13405293.html
Copyright © 2020-2023  润新知