当前位置:网站首页 > 数据科学与大数据 > 正文

重启docker守护进程不关闭容器(docker容器重启会保留数据吗)



移动互联网时代,对于每个公司、企业来说,用户的行为数据非常重要。重要到什么程度,用户在这个页面停留多久、点击了什么按钮、浏览了什么内容、什么手机、什么网络环境、App什么版本等都需要清清楚楚。一些大厂的蛮多业务成果都是基于用户操作行为进行推荐后二次转换。另一方面是以日志的作用帮助开发者分析线上问题的一种辅助手段。

那么有了上述的诉求,那么技术人员如何满足这些需求?引出来了一个技术点-“埋点”

业界中对于代码埋点主要有3种主流的方案:代码手动埋点、可视化埋点、无痕埋点。简单说说这几种埋点方案。

  • 代码手动埋点:根据业务需求(运营、产品、开发多个角度出发)在需要埋点地方手动调用埋点接口,上传埋点数据。
  • 可视化埋点:通过可视化配置工具完成采集节点,在前端自动解析配置并上报埋点数据,从而实现可视化“无痕埋点”
  • 无痕埋点:通过技术手段,完成对用户行为数据无差别的统计上传的工作。后期数据分析处理的时候通过技术手段筛选出合适的数据进行统计分析。

1. 代码手动埋点

该方案情况下,如果需要埋点,则需要在工程代码中,写埋点相关代码。因为侵入了业务代码,对业务代码产生了污染,显而易见的缺点是埋点的成本较高、且违背了单一原则

例1:假如你需要知道用户在点击“购买按钮”时的相关信息(手机型号、App版本、页面路径、停留时间、动作等等),那么就需要在按钮的点击事件里面去写埋点统计的代码。这样明显的弊端就是在之前业务逻辑的代码上面又多出了埋点的代码。由于埋点代码分散、埋点的工作量很大、代码维护成本较高、后期重构很头痛。

例2:假如 App 采用了 Hybrid 架构,当 App 的第一版本发布的时候 H5 的关键业务逻辑统计是由 Native 定义好关键逻辑(比如H5调起了Native的分享功能,那么存在一个分享的埋点事件)的桥接。假如某天增加了一个扫一扫功能,未定义扫一扫的埋点桥接,那么 H5 页面变动的时候,Native 埋点代码不去更新的话,变动的 H5 的业务就未被精确统计。

优点:产品、运营工作量少,对照业务映射表就可以还原出相关业务场景、数据精细无须大量的加工和处理

缺点:开发工作量大、前期需要和运营、产品指定的好业务标识,以便产品和运营进行数据统计分析

2. 可视化埋点

可视化埋点的出现,是为解决代码埋点流程复杂、成本高、新开发的页面(H5、或者服务端下发的 json 去生成相应页面)不能及时拥有埋点能力

前端在「埋点编辑模式」下,以“可视化”的方式去配置、绑定关键业务模块的路径到前端可以唯一确定到view的xpath过程。

用户每次操作的控件,都生成一个 xpath 字符串,然后通过接口将 xpath 字符串(view在前端系统中的唯一定位。以 iOS 为例,App名称、控制器名称、一层层view、同类型view的序号:“GoodCell.21.RetailTableView.GoodsViewController.*baoApp”)到真正的业务模块(“宝App-商城控制器-分销商品列表-第21个商品被点击了”)的映射关系上传到服务端。xpath 具体是什么在下文会有介绍。

之后操作 App 就生成对应的 xpath 和埋点数据(开发者通过技术手段将从服务端获取的关键数据塞到前端的 UI 控件上。 iOS 端为例, UIView 的 accessibilityIdentifier 属性可以设置我们从服务端获取的埋点数据)上传到服务端。

优点:数据量相对准确、后期数据分析成本低

缺点:前期控件的唯一识别、定位都需要额外开发;可视化平台的开发成本较高;对于额外需求的分析可能会比较困难

3. 无痕埋点

通过技术手段无差别地记录用户在前端页面上的行为。可以正确的获取 PV、UV、IP、Action、Time 等信息。

缺点:前期开发统计基础信息的技术产品成本较高、后期数据分析数据量很大、分析成本较高(大量数据传统的关系型数据库压力大)

优点:开发人员工作量小、数据全面、无遗漏、产品和运营按需分析、支持动态页面的统计分析

4. 如何选择

结合上述优缺点,我们选择了无痕埋点+可视化埋点结合的技术方案。

怎么说呢?对于关键的业务开发结束上线后、通过可视化方案(类似于一个界面,想想看 Dreamwaver,你在界面上拖拖控件,简单编辑下就可以生成对应的 HTML 代码)点击一下绑定对应关系到服务端。

那么这个对应关系是什么?我们需要唯一定位一个前端元素,那么想到的办法就是不管 Native 和 Web 前端,控件或者元素来说就是一个树形层级,DOM tree 或者 UI tree,所以我们通过技术手段定位到这个元素,以 Native iOS 为例子,假如点击商品详情页的加入购物车按钮会根据 UI 层级结构生成一个唯一标识 “addCartButton.GoodsViewController.GoodsView.*BaoApp” 。但是用户在使用 App 的时候,上传的是这串东西的 MD5到服务端。

这么做有2个原因:服务端数据库存储这串很长的东西不是很好;埋点数据被劫持的话直接看到明文不太好。所以 MD5 再上传。

一言以蔽之就是:AOP -> Event Collector -> Event Cache -> Data Upload

  • AOP:通过 runtime hook 的能力做到提供合适的时机去生成点击事件的数据
  • Event Collector:将步骤1产生的数据统一收集(一般是一个内存存储的数据结构)
  • Event Cache:mmap,当内存中的数据达到一定的阀值,或者应用程序的生命周期切换的时候将内存中的数据同步到缓存中(数据库、磁盘、文件)
  • Data Uploader:制定一定的策略,当达到触发条件的时候再去上传数据(App达到阀值,生命周期的切换等);App从前台进入后台的时候去上传数据(后台线程保活策略);数据上传格式的选择(zip压缩文件、protoBuf)

1. 数据的收集

实现方案由以下几个关键指标:

  • 现有代码改动少、尽量不要侵入业务代码去实现拦截系统事件
  • 全量收集
  • 如何唯一标识一个控件元素

2. 不侵入业务代码拦截系统事件

以 iOS 为例。我们会想到 AOP(Aspect Oriented Programming)面向切面编程思想。动态地在函数调用前后插入相应的代码,在 Objective-C 中我们可以利用 Runtime 特性,用 Method Swizzling 来 hook 相应的函数

为了给所有类方便地 hook,我们可以给 NSObject 添加个 Category,名字叫做 NSObject+MethodSwizzling

3. 全量收集

我们会想到 hook AppDelegate 代理方法、UIViewController 生命周期方法、按钮点击事件、手势事件、各种系统控件的点击回调方法、应用状态切换等等。

以统计页面的打开时间和统计页面的打开、关闭的需求为例,我们对 UIViewController 进行 hook

4. 如何唯一标识一个控件元素

xpath 是移动端定义可操作区域的唯一标识。既然想通过一个字符串标识前端系统中可操作的控件,那么 xpath 需要2个指标:

  • 唯一性:在同一系统中不存在不同控件有着相同的 xpath
  • 稳定性:不同版本的系统中,在页面结构没有变动的情况下,不同版本的相同页面,相同的控件的 xpath 需要保持一致。

我们想到 Naive、H5 页面等系统渲染的时候都是以树形结构去绘制和渲染,所以我们以当前的 View 到系统的根元素之间的所有关键点(UIViewController、UIView、UIView容器(UITableView、UICollectionView等)、UIButton...)串联起来这样就唯一定位了控件元素。

为了精确定位元素节点,参看下图

假设一个 UIView 中有三个子 view,先后顺序是:label、button1、button2,那么深度依次为: 0、1、2。假如用户做了某些操作将 label1 从父 view 中被移除了。此时 UIView 只有 2 个子view:button1、button2,而且深度变为了:0、1。

容器挂起状态保留吗_埋点

可以看出仅仅由于其中某个子 view 的改变,却导致其它子 view 的深度都发生了变化。因此,在设计的时候需要注意,在新增/移除某一 view 时,尽量减少对已有 view 的深度的影响,调整了对节点的深度的计算方式:采用当前 view 位于其父 view 中的所有 与当前 view 同类型 子view 中的索引值。

我们再看一下上面的这个例子,最初 label、button1、button2 的深度依次是:0、0、1。在 label 被移除后,button1、button2 的深度依次为:0、1。可以看出,在这个例子中,label 的移除并未对 button1、button2 的深度造成影响,这种调整后的计算方式在一定程度上增强了 xpath 的抗干扰性。

另外,调整后的深度的计算方式是依赖于各节点的类型的,因此,此时必须要将各节点的名称放到  中,而不再是仅仅为了增加可读性。

在标识控件元素的层级时,需要知道「当前 view 位于其父 view 中的所有 与当前 view 同类型 子view 中的索引值」。参看上图,如果不是同类型的话,则唯一性得不到保证。

5. 同类型的 view 的唯一定位问题

有个问题,比如我们点击的元素是 UITableViewCell,那么它虽然可以定位到类似于这个标示 xxApp.GoodsViewController.GoodsTableView.GoodsCell,同类型的 Cell 有多个,所以单凭借这个字符串是没有办法定位具体的那个 Cell 被点击了。

当然有解决方案啦。

  • 找出当前元素在父层同类型元素中的索引。根据当前的元素遍历当前元素的父级元素的子元素,如果出现相同的元素,则需要判断当前元素是所在层级的第几个元素
    对当前的控件元素的父视图的全部子视图进行遍历,如果存在和当前的控件元素同类型的控件,那么需要判断当前控件元素在同类型控件元素中的所处的位置,那么则可以唯一定位。举例:GoodsCell-3.GoodsTableView.GoodsViewController.xxApp

容器挂起状态保留吗_App_02

6. 同类型的view,但是点击的意义却不一样。如何唯一标识?

问题5说明的是在一个界面上有多个不同的 view,他们的类型是同一种(CycleBannerView,但是数据源不一样,那么当数据源长度大于1的时候会轮播,下面会展示 UIPageControl。如果数据源是1个,那么就不会轮播和展示 UIPageControl)。情况6是同一种类型的 View,但是根据展示的内容不一样,点击的意义也不一样。也就是运营需要去知道用户到底点击的是哪一个。如下图所示,「立即抢购」和「分享赚佣金」是同一种类型的 View,但是点击意义不一样,需要我们需要唯一标识出来。之前的方法通过 “viewPath 配合同类型的 view 去加索引值“ 的方式还是没有办法唯一标识出来。所以想到一个方案,给 NSObject 添加一个分类,在分类里面添加一个协议。让需要复用但需要唯一标识的 view 去实现协议方法,因为是给 NSObject 分类添加的协议,所以 view 不需要去指定遵循。

容器挂起状态保留吗_埋点_03

关键步骤:

  • 添加 NSObject 的 Category。在分类里面声明唯一标识的协议
  • 在生成 viewPath 的地方去拿出当前 view 的唯一标识(view 调用协议方法)。然后拼接之前拿出的 viewPath

容器挂起状态保留吗_App_04

容器挂起状态保留吗_控件_05

7. 疑惑点

根据在同一个 view 上会有多个 subview,那么生成的 xpath 会携带在同类型 views 中的索引,所以一个登录、注册按钮的 xpath 可能为 ...btn1、...btn2。那么在版本A上线后运行了一段时间,上传并统计了数据。过了一段时间版本迭代,UI 搞事情,把登录和注册按钮的位置欢乐,变成了注册、登录。按照之前的逻辑生成的 xpath 为 ...btn1、...btn2。那么新的 xpath 虽然唯一,但是点击产生的数据会和之前的埋点数据意义不一样。别怕,你忘了还有一步绑定的逻辑。绑定的这一步会把每次开发的功能,通过可视化界面去将 xpath 和功能名称绑定一下。看下面的动图。所以不用担心虽然生成了唯一的 xpath,但是 App 在不同版本之间 UI 控件位置更换造成之前的统计数据在分析的时候不准确的问题。因为在绑定的时候就将新的 xpath 和功能名称进行了绑定,接口携带版本号。所以分析的时候注意版本号就好了。sql 一句话的事情。

容器挂起状态保留吗_App_06

8. 数据如何处理

A. 如何处理业务数据

利用系统提供的 accessibilityIdentifier 官方给出的解释是标识用户界面元素的字符串

*/

A string that identifies the user interface element.

default == nil

*/

@property(nullable, nonatomic, copy) NSString *accessibilityIdentifier NS_AVAILABLE_IOS(5_0);

服务端下发唯一标识

接口获取的数据,里面有当前元素的唯一标识。比如在 UITableView 的界面去请求接口拿到数据,那么在在获取到的数据源里面会有一个字段,专门用来存储动态化的经常变动的业务数据。

B. 基础数据

设计上分为2个 pod 库,一个是 TriggerKit(专门用来 hook 机会需要的所有事件,页面停留时间、页面标识、view标识),另一个是 Appmonitor(专门用来提供基础数据、埋点数据的维护、上传机制)。所以在 Appmonitor 里面有个类叫做 UserTrackDataCenter 的类,专门提供一些基础数据(系统版本、操作系统、地理位置、网络等信息)。

对外暴露出一些方法,用来将埋点数据交给 Appmonitor 去维护埋点数据,达到合适的“机制”再去上传埋点数据到服务端。

9. 曝光时间的统计

曝光的意义是什么?

我们的产品中可能有合作伙伴的广告,我们需要收取服务费。那如何计价?CPM(cost per Mille)每千人成本、CPC(cost per click)每点击成本、CPA(cost per action)每行动成本,根据这些指标来计算价格。或者自己的产品中运营人员在商城中投放了一次新的活动,为了这次活动在某个钻石展位放了设计人员精心设计的炫酷 Banner。这次活动后运营人员想分析在这个图片的作用下有多少人点击了这个活动页。

何为曝光?

一个 view 或者一个组件或者一个资源位在屏幕上可见区域内停留的时间称为一次曝光。那么这个时间怎么统计?有一个点需要注意,那就是当用户在快速滑动的过程中页面上的元素或者组件都会在页面可见区域内快速闪过,那这种算一次曝光吗?当然不算啊,想了想设置了一个时间临界值,大于这个临界值那么算做一次有效的曝光。

A. 有效曝光的判断

显示在屏幕可见区域如何判断?一个 View 显示在屏幕可见区域内,那么它肯定是经过从未初始化到初始化,再到设置 Frame 或者 Bounds 或者 Alpha 或者 Hidden 的。且它的根 view 一定是 UIWindow 对象。所以上面这句话进行分析整理就是下面的条件

  • 自身 frame 的改变或者父视图 bounds 的改变
  • alpha 小于 0.1 或者 hidden 为 YES
  • 根视图为 window

对于上面的三点可以用 AOP 进行判断。 hook 掉相应的方法,然后处理判断是否在可见区域内显示。最后的一个点经过一番查找,看到了一个 api  ,根据它可以判断 view 是否显示到屏幕中(文档中说明:当它的 window 对象发送改变的时候会调用 view 的 didMoveToWindow 方法)。

B. 曝光代码的执行效率优化

设想一下,某个复杂的页面可能是一个大的 UIViewController 顶部是店铺的基本信息,下面是 2个 UIViewController:左侧负责展示商品的一级、二级、三级分类,且负责选中和未选中的 UI 效果;右侧负责展示商品信息(顶部有商品的排序查找 ,下面是商品展示的 UICollectionView)。由于页面结构复杂,UI 层级嵌套严重,所以代码层面不注意的话,页面上计算量会比较大,CPU 负荷严重,直接影响着手机的 。改进的手段是在合适的地方提前 return 掉(比如 hidden 等于 YES 或者 aplha 小于 0.1 的时候)。

另外一个方面就是当用户在滑动页面到感兴趣的模块的时候,开始点击执行某个逻辑,但此时我们的无痕埋点的代码也在偷偷的工作,那么势必会对用户体验造成影响。该方案的改进是监听 ,等到 RunLoop 空闲的时候判断当前 view 是否是一次有效的曝光。

实际上发现某些 view 的判断会比较特殊,比如当在 UITableView 的 cell 判断的时候,我们发现 cell 的 superview 为 UITableViewWrapperView 时,我们使用 UITableViewWrapperView 的父视图来计算。

iOS 11 以下 UITableViewWrapperView 大小为屏幕中第一个完整的屏幕大小视图,且会随着 contentOffset 的改变而改变。所以当 UITableViewWrapperView 滑出屏幕可见区域的时候,cell 判断父视图是否可见的时候不准确。

整个流程见下面的流程图。

容器挂起状态保留吗_数据_07

10. 数据的上报

数据通过上面的办法收集完了,那么如何及时、高效的上传到后端,给运营分析、处理呢?

App 运行期间用户会点击非常多的数据,如果实时上传的话对于网络的利用率较低,所以需要考虑一个机制去控制用户产生的埋点数据的上传。

思路是这样的。对外部暴露出一个接口,用来将产生的数据往数据中心存储。用户产生的数据会先保存到 AppMonitor 的内存中去,设置一个临界值(memoryEventMax = 50),如果存储的值达到设置的临界值 memoryEventMax,那么将内存中的数据写入文件系统,以 zip 的形式保存下来,然后上传到埋点系统。如果没有达到临界值但是存在一些 App 状态切换的情况,这时候需要及时保存数据到持久化。当下次打开 App 就去从本地持久化的地方读取是否有未上传的数据,如果有就上传日志信息,成功后删除本地的日志压缩包。

App 应用状态的切换策略如下:

  • didFinishLaunchWithOptions:内存日志信息写入硬盘
  • didBecomeActive:上传
  • willTerimate:内存日志信息写入硬盘
  • didEnterBackground:内存日志信息写入硬盘

下面的代码是 App 埋点数据的保存与上传

使用的时候就是在 hook 系统事件的时候,去调用统计页面上传数据

容器挂起状态保留吗_容器挂起状态保留吗_08

总结下来关键步骤:

  1. hook 系统的各种事件(UIResponder、UITableView、UICollectionView代理事件、UIControl事件、UITapGestureRecognizers)、hook 应用程序、控制器生命周期。在做本来的逻辑之前添加额外的监控代码
  2. 对于点击的元素按照视图树生成对应的唯一标识(addCartButton.GoodsView.GoodsViewController) 的 md5 值
  3. 在业务开发完毕,进入埋点的编辑模式,将 md5 和关键的页面的关键事件(运营、产品想统计的关键模块:App层级、业务模块、关键页面、关键操作)给绑定起来。比如 addCartButton.GoodsView.GoodsViewController.tbApp 对应了 tbApp-商城模块-商品详情页-加入购物车功能。
  4. 将所需要的数据存储下来
  5. 设计机制等到合适的时机去上传数据

埋伏模块分为2个pod组件库,TriggerKit 负责拦截系统事件,拿到埋点数据。Appmonitor 负责收集埋点数据,本地持久化或内存储存,等到合适时机去上传埋点数据。

  1. 通过接口获取数据,给对应的 view 的 accessibilityIdentifier 属性绑定埋点数据

容器挂起状态保留吗_埋点_09

  1. hook 系统事件,点击拿到 view,获取 accessibilityIdentifier 属性值
  2. 将数据向的数据中心发送,数据中心处理数据(埋点数据结合App基础信息,图上 UserTrackDataCenter 对象)。根据情况将数据存储到内存或者本地,等到合适的时机去上传
到此这篇重启docker守护进程不关闭容器(docker容器重启会保留数据吗)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • orecal数据库(orecal数据库招标)2025-02-09 09:09:06
  • 自动驾驶数据集(自动驾驶数据集的研究方向)2025-02-09 09:09:06
  • iotdb数据库查询语句(数据库查询out of memory)2025-02-09 09:09:06
  • 自动驾驶数据平台开发(自动驾驶数据平台开发流程)2025-02-09 09:09:06
  • 小米手机数据迁移到苹果手机13(小米手机数据迁移到苹果手机需要花多少钱)2025-02-09 09:09:06
  • 数据中台技术方案(数据中台建设方案)2025-02-09 09:09:06
  • orcale数据库下载(oracle数据库软件下载)2025-02-09 09:09:06
  • 达梦客户端安装 linux(linux如何安装达梦数据库)2025-02-09 09:09:06
  • 大数据学什么软件(大数据学什么软件比较好)2025-02-09 09:09:06
  • .sql文件有什么用(sql的数据文件是什么)2025-02-09 09:09:06
  • 全屏图片