Quantcast
Channel: IT瘾技术推荐

通过SQL定义用户浏览Session

$
0
0

PC时代,用户问页面时,我们会先检查用户的Cookie中是否存在SessionId,如果不存在,则会通过随机数的方式生成一个SessionId存入Cookie中。如果存在,我们会更新这个Cookie的失效时间(30分钟后)。即只要用户访问的间隔在30分钟内则被认为是同一个Seesion,超过30分钟则会生成一个新的SeesionId,将浏览定义为一个新的Session。

APP时代或者小程序的时代,通常我们会把App的每次打开作为一次Seesion来记录,Cookie的概念被抛弃,但中间忽略了一个重要的问题:在你使用App或者小程序的过程,非常有可能会被其他应用程序中断,比如电话、短信、微信、推送等。当用户切屏以后Session的记录就会发生改变,这时候统计的Session数据往往是不准确的。今天要分享的是如何通过SQL的方式来定义Session。

理清思路

用户浏览日志中,我们通常能够记录到用户的身份和访问时间。在Session定义中我们首先需要识别唯一用户,并按用户的浏览时间对日志进行排序,处理完成后需要计算日志间的时间差,并将大于30分钟的浏览定位为新的Session。

逻辑转SQL

1 计算每次访问的上次访问时间

可以使用窗口函数LAG实现。具体代码如下:

SELECT *, LAG(occurred_at, 1) OVER (PARTITION BY user_id ORDER BY occurred_at) AS last_event
FROM tutorial.playbook_events

执行效果如下:

其中:

  • user_id:用户身份ID
  • occurred_at:当前访问时间
  • last_event:上次访问时间

2 计算访问时间差,确定是否为新的访问

判断是否是新的访问主要通过两种方式:

  • 本次访问没有上次访问时间
  • 本次访问和上次访问时间差>30分钟(30分钟还是10分钟可以根据业务场景自己定义)
SELECT *
    , CASE 
        WHEN unix_timestamp(occurred_at, 'yyyy-MM-dd HH:mm:ss') - unix_timestamp(last_event, 'yyyy-MM-dd HH:mm:ss') >= 60 * 30
        OR last_event IS NULL THEN 1
        ELSE 0
    END AS is_new_session
FROM (
    SELECT *, LAG(occurred_at, 1) OVER (PARTITION BY user_id ORDER BY occurred_at) AS last_event
    FROM tutorial.playbook_events
) t1

执行效果如下:

2 定义SessionId

有了上面的数据,还缺一个SessionId,实现的方法还是通过窗口函数实现。具体代码如下:

SELECT user_id, occurred_at, SUM(is_new_session) OVER (ORDER BY user_id, occurred_at) AS global_session_id
    , SUM(is_new_session) OVER (PARTITION BY user_id ORDER BY occurred_at) AS user_session_id
FROM (
    SELECT *
        , CASE 
            WHEN unix_timestamp(occurred_at, 'yyyy-MM-dd HH:mm:ss') - unix_timestamp(last_event, 'yyyy-MM-dd HH:mm:ss') >= 60 * 30
            OR last_event IS NULL THEN 1
            ELSE 0
        END AS is_new_session
    FROM (
        SELECT *, LAG(occurred_at, 1) OVER (PARTITION BY user_id ORDER BY occurred_at) AS last_event
        FROM tutorial.playbook_events
    ) t1
) t2

执行效果如下:

参考链接:


从源码中来,到业务中去,React性能优化终极指南

$
0
0

前言:我们从React源码入手,结合有道精品课大前端的具体业务,运用三大原则对系统进行外科手术式的优化。同时介绍React Profiler这款工具如何帮我们定位性能瓶颈前言:我们从React源码入手,结合有道精品课大前端的具体业务,运用三大原则对系统进行外科手术式的优化。同时介绍React Profiler这款工具如何帮我们定位性能瓶颈

作者/ 安增平

编辑/ Ein

React性能优化是在业务迭代过程中不得不考虑的问题,大部分情况是由于项目启动之初,没有充分考虑到项目的复杂度,定位该产品的用户体量及技术场景并不复杂,那么我们在业务前期可能并不需要考虑性能优化。但是随着业务场景的复杂化,性能优化就变得格外重要。

我们从React源码入手,结合有道精品课大前端的具体业务,运用优化技巧对系统进行外科手术式的优化。同时介绍一下React Profiler这款性能优化的利器是如何帮我们定位性能瓶颈的。

本文中的项目代码全部是在有道大前端组开发项目中的工作记录,如有不足欢迎在留言区讨论交流,笔芯❤

页面加载流程

  1. 假设用户首次打开页面(无缓存),这个时候页面是完全空白的;
  2. html 和引用的 css 加载完毕,浏览器进行 首次渲染
  3. react、react-dom、业务代码加载完毕,应用第一次渲染,或者说 首次内容渲染
  4. 应用的代码开始执行,拉取数据、进行动态import、响应事件等等,完毕后页面进入 可交互状态;
  5. 接下来 lazyload 的图片等多媒体内容开始逐渐加载完毕;
  6. 直到页面的其它资源(如错误上报组件、打点上报组件等)加载完毕,整个页面加载完成。

我们主要来针对React进行剖析

React 针对渲染性能优化的三个方向,也适用于其他软件开发领域,这三个方向分别是:

  1. 减少计算的量:React 中就是减少渲染的节点或通过索引减少渲染复杂度;
  2. 利用缓存:React 中就是避免重新渲染(利用 memo 方式来避免组件重新渲染);
  3. 精确重新计算的范围:React 中就是绑定组件和状态关系, 精确判断更新的’时机’和’范围’. 只重新渲染变更的组件(减少渲染范围)。

如何做到这三点呢?我们从React本身的特性入手分析。

React 工作流

React 是声明式 UI 库,负责将 State 转换为页面结构(虚拟 DOM 结构)后,再转换成真实 DOM 结构,交给浏览器渲染。State 发生改变时,React 会先进行Reconciliation,结束后立刻进入Commit阶段,Commit结束后,新 State 对应的页面才被展示出来。

React 的 Reconciliation需要做两件事:

  1. 计算出目标 State 对应的虚拟 DOM 结构。
  2. 寻找「将虚拟 DOM 结构修改为目标虚拟 DOM 结构」的最优方案。

React 按照深度优先遍历虚拟 DOM 树的方式,在一个虚拟 DOM 上完成Render和Diff的计算后,再计算下一个虚拟 DOM。Diff 算法会记录虚拟 DOM 的更新方式(如:Update、Mount、Unmount),为Commit做准备。

React 的 Commit也需要做两件事:

  1. 将Reconciliation结果应用到 DOM 中。
  2. 调用暴露的hooks如:componentDidUpdate、useLayoutEffect 等。

下面我们将针对三个优化方向进行精准分析。

减少计算的量

关于以上 ReconciliationCommit两个阶段的优化办法,我在实现的过程中遵循 减少计算量的方法进行优化( 列表项使用 key 属性)该过程是优化的重点,React 内部的 Fiber 结构和并发模式也是在减少该过程的耗时阻塞。对于 Commit在执行hooks时,开发者应保证hooks中的代码尽量轻量,避免耗时阻塞,同时应避免在 CDM、CDU周期中更新组件。

列表项使用 key 属性

特定框架中,提示也做的十分友好。假如你没有在列表中添加key属性,控制台会为你展示一片大红

系统会时刻提醒你记得加Key哦~~

优化Render 过程

Render 过程:即Reconciliation中计算出目标 State 对应的虚拟 DOM 结构这一阶段 。

触发 React 组件的 Render 过程目前有三种方式:

  1. forceUpdate、
  2. State 更新、
  3. 父组件 Render 触发子组件 Render 过程。

优化技巧

PureComponent、React.memo

在 React 工作流中,如果只有父组件发生状态更新,即使父组件传给子组件的所有 Props 都没有修改,也会引起子组件的 Render 过程。

从 React 的声明式设计理念来看,如果子组件的 Props 和 State 都没有改变,那么其生成的 DOM 结构和副作用也不应该发生改变。当子组件符合声明式设计理念时,就可以忽略子组件本次的 Render 过程。

PureComponent 和 React.memo 就是应对这种场景的,PureComponent 是对类组件的 Props 和 State 进行浅比较,React.memo 是对函数组件的 Props 进行浅比较。

useMemo、useCallback 实现稳定的 Props 值

如果传给子组件的派生状态或函数,每次都是新的引用,那么 PureComponent 和 React.memo 优化就会失效。所以需要使用 useMemo 和 useCallback 来生成稳定值,并结合 PureComponent 或 React.memo 避免子组件重新 Render。

useMemo 减少组件 Render 过程耗时

useMemo 是一种缓存机制提速,当它的依赖未发生改变时,就不会触发重新计算。一般用在「计算派生状态的代码」非常耗时的场景中,如:遍历大列表做统计信息。

显然useMemo的作用是缓存昂贵的计算(避免在每次渲染时都进行高开销的计算),在业务中使用它去控制变量来更新表格

shouldComponentUpdate

在类组件中,例如要往数组中添加一项数据时,当时的代码很可能是 state.push(item),而不是 const newState = [...state, item]。

在此背景下,当时的开发者经常使用

shouldComponentUpdate 来深比较 Props,只在 Props 有修改才执行组件的 Render 过程。如今由于数据不可变性和函数组件的流行,这样的优化场景已经不会再出现了。

为了贴合shouldComponentUpdate的思想:给子组件传props的时候一定只传其需要的而并非一股脑全部传入:

传入到子组件的参数一定保证其在自组件中被使用到。

批量更新,减少 Render 次数

在 React 管理的事件回调和生命周期中,setState 是异步的,而其他时候 setState 都是同步的。这个问题根本原因就是 React 在自己管理的事件回调和生命周期中,对于 setState 是批量更新的,而在其他时候是立即更新的。

批量更新 setState 时,多次执行 setState 只会触发一次 Render 过程。相反在立即更新 setState 时,每次 setState 都会触发一次 Render 过程,就存在性能影响。

假设有如下组件代码,该组件在 getData() 的 API 请求结果返回后,分别更新了两个 State 。

该组件会在 setList(data.list) 后触发组件的 Render 过程,然后在 setInfo(data.info) 后再次触发 Render 过程,造成性能损失。那我们该如何解决呢:

  1. 将多个 State 合并为单个 State。例如 useState({ list: null, info: null }) 替代 list 和 info 两个 State。
  2. 使用 React 官方提供的 unstable_batchedUpdates 方法,将多次 setState 封装到 unstable_batchedUpdates 回调中。

修改后代码如下:

精细化渲染阶段

按优先级更新,及时响应用户

优先级更新是批量更新的逆向操作,其思想是:优先响应用户行为,再完成耗时操作。常见的场景是:页面弹出一个 Modal,当用户点击 Modal 中的确定按钮后,代码将执行两个操作:

  1. 关闭 Modal。
  2. 页面处理 Modal 传回的数据并展示给用户。

当操作2需要执行500ms时,用户会明显感觉到从点击按钮到 Modal 被关闭之间的延迟。

以下为一般的实现方式,将 slowHandle 函数作为用户点击按钮的回调函数。

slowHandle() 执行过程耗时长,用户点击按钮后会明显感觉到页面卡顿。

如果让页面优先隐藏输入框,用户便能立刻感知到页面更新,不会有卡顿感。

实现优先级更新的要点是将耗时任务移动到下一个宏任务中执行,优先响应用户行为。

例如在该例中,将 setNumbers 移动到 setTimeout 的回调中,用户点击按钮后便能立即看到输入框被隐藏,不会感知到页面卡顿。mhd项目中优化后的代码如下:

发布者订阅者跳过中间组件 Render 过程

React 推荐将公共数据放在所有「需要该状态的组件」的公共祖先上,但将状态放在公共祖先上后,该状态就需要层层向下传递,直到传递给使用该状态的组件为止。

每次状态的更新都会涉及中间组件的 Render 过程,但中间组件并不关心该状态,它的 Render 过程只负责将该状态再传给子组件。在这种场景下可以将状态用发布者订阅者模式维护,只有关心该状态的组件才去订阅该状态,不再需要中间组件传递该状态。

当状态更新时,发布者发布数据更新消息,只有订阅者组件才会触发 Render 过程,中间组件不再执行 Render 过程。

只要是发布者订阅者模式的库,都可以使用useContext进行该优化。比如:redux、use-global-state、React.createContext 等。

业务代码中的使用如下:

从图中可看出,优化后只有使用了公共状态的组件renderTable才会发生更新,由此可见这样做可以大大减少父组件和 其他renderSon… 组件的 Render 次数(减少叶子节点的重渲染)。

useMemo 返回虚拟 DOM 可跳过该组件 Render 过程

利用 useMemo 可以缓存计算结果的特点,如果 useMemo 返回的是组件的虚拟 DOM,则将在 useMemo 依赖不变时,跳过组件的 Render 阶段。

该方式与 React.memo 类似,但与 React.memo 相比有以下优势:

  1. 更方便。React.memo 需要对组件进行一次包装,生成新的组件。而 useMemo 只需在存在性能瓶颈的地方使用,不用修改组件。
  2. 更灵活。useMemo 不用考虑组件的所有 Props,而只需考虑当前场景中用到的值,也可使用 useDeepCompareMemo 对用到的值进行深比较。

该例子中,父组件状态更新后,不使用 useMemo 的子组件会执行 Render 过程,而使用 useMemo 的子组件会按需执行更新。业务代码中的使用方法:

精确判断更新的’时机’和’范围’

debounce、throttle 优化频繁触发的回调

在搜索组件中,当 input 中内容修改时就触发搜索回调。当组件能很快处理搜索结果时,用户不会感觉到输入延迟。

但实际场景中,中后台应用的列表页非常复杂,组件对搜索结果的 Render 会造成页面卡顿,明显影响到用户的输入体验。

在搜索场景中一般使用 useDebounce+ useEffect 的方式获取数据。

在搜索场景中,只需响应用户最后一次输入,无需响应用户的中间输入值,debounce 更适合。而 throttle 更适合需要实时响应用户的场景中更适合,如通过拖拽调整尺寸或通过拖拽进行放大缩小(如:window 的 resize 事件)。

懒加载

在 SPA 中,懒加载优化一般用于从一个路由跳转到另一个路由。

还可用于用户操作后才展示的复杂组件,比如点击按钮后展示的弹窗模块(大数据量弹窗)。

在这些场景下,结合 Code Split 收益较高。懒加载的实现是通过 Webpack 的动态导入和 React.lazy 方法。

实现懒加载优化时,不仅要考虑加载态,还需要对加载失败进行容错处理。

懒渲染

懒渲染指当组件进入或即将进入可视区域时才渲染组件。常见的组件 Modal/Drawer 等,当 visible 属性为 true 时才渲染组件内容,也可以认为是懒渲染的一种实现。懒渲染的使用场景有:

  1. 页面中出现多次的组件,且组件渲染费时、或者组件中含有接口请求。如果渲染多个带有请求的组件,由于浏览器限制了同域名下并发请求的数量,就可能会阻塞可见区域内的其他组件中的请求,导致可见区域的内容被延迟展示。
  2. 需用户操作后才展示的组件。这点和懒加载一样,但懒渲染不用动态加载模块,不用考虑加载态和加载失败的兜底处理,实现上更简单。

懒渲染的实现中判断组件是否出现在可视区域内借助react-visibility-observer依赖:

虚拟列表

虚拟列表是懒渲染的一种特殊场景。虚拟列表的组件有 react-window和 react-virtualized,它们都是同一个作者开发的。

react-window 是 react-virtualized 的轻量版本,其 API 和文档更加友好。推荐使用 react-window,只需要计算每项的高度即可:

如果每项的高度是变化的,可给 itemSize 参数传一个函数。

所以在开发过程中,遇到接口返回的是所有数据时,需提前预防这类会有展示的性能瓶颈的需求时,推荐使用虚拟列表优化。使用示例: react-window​react-window.vercel.app

动画库直接修改 DOM 属性,跳过组件 Render 阶段

这个优化在业务中应该用不上,但还是非常值得学习的,将来可以应用到组件库中。

参考 react-spring 的动画实现,当一个动画启动后,每次动画属性改变不会引起组件重新 Render ,而是直接修改了 dom 上相关属性值:

避免在 didMount、didUpdate 中更新组件 State

这个技巧不仅仅适用于 didMount、didUpdate,还包括 willUnmount、useLayoutEffect 和特殊场景下的 useEffect(当父组件的 cDU/cDM 触发时,子组件的 useEffect 会同步调用),本文为叙述方便将他们统称为「提交阶段钩子」。

React 工作流commit阶段的第二步就是执行提交阶段钩子,它们的执行会阻塞浏览器更新页面。

如果在提交阶段钩子函数中更新组件 State,会再次触发组件的更新流程,造成两倍耗时。一般在提交阶段的钩子中更新组件状态的场景有:

  1. 计算并更新组件的派生状态(Derived State)。在该场景中,类组件应使用 getDerivedStateFromProps钩子方法代替,函数组件应使用函数调用时执行 setState的方式代替。使用上面两种方式后,React 会将新状态和派生状态在一次更新内完成。
  2. 根据 DOM 信息,修改组件状态。在该场景中,除非想办法不依赖 DOM 信息,否则两次更新过程是少不了的,就只能用其他优化技巧了。

use-swr 的源码就使用了该优化技巧。当某个接口存在缓存数据时,use-swr 会先使用该接口的缓存数据,并在 requestIdleCallback 时再重新发起请求,获取最新数据。模拟一个swr:

  1. 它的第二个参数 deps,是为了在请求带有参数时,如果参数改变了就重新发起请求。
  2. 暴露给调用方的 fetch 函数,可以应对主动刷新的场景,比如页面上的刷新按钮。

如果 use-swr 不做该优化的话,就会在 useLayoutEffect 中触发重新验证并设置 isValidating 状态为 true·,引起组件的更新流程,造成性能损失。

工具介绍——React Profiler

React Profiler 定位 Render 过程瓶颈

React Profiler 是 React 官方提供的性能审查工具,本文只介绍笔者的使用心得,详细的使用手册请移步官网文档。

Note:react-dom 16.5+ 在 DEV 模式下才支持 Profiling,同时生产环境下也可以通过一个 profiling bundle react-dom/profiling 来支持。请在 fb.me/react-profi… 上查看如何使用这个 bundle。

“Profiler” 的面板在刚开始的时候是空的。你可以点击 record 按钮来启动 profile:

Profiler 只记录了 Render 过程耗时

不要通过 Profiler 定位非 Render 过程的性能瓶颈问题

通过 React Profiler,开发者可以查看组件 Render 过程耗时,但无法知晓提交阶段的耗时。

尽管 Profiler 面板中有 Committed at 字段,但这个字段是相对于录制开始时间,根本没有意义。

通过在 React v16 版本上进行实验,同时开启 Chrome 的 Performance 和 React Profiler 统计。

如下图,在 Performance 面板中,Reconciliation和Commit阶段耗时分别为 642ms 和 300ms,而 Profiler 面板中只显示了 642ms:

开启「记录组件更新原因」

点击面板上的齿轮,然后勾选「Record why each component rendered while profiling.」,如下图:

然后点击面板中的虚拟 DOM 节点,右侧便会展示该组件重新 Render 的原因。

定位产生本次 Render 过程原因

由于 React 的批量更新(Batch Update)机制,产生一次 Render 过程可能涉及到很多个组件的状态更新。那么如何定位是哪些组件状态更新导致的呢?

在 Profiler 面板左侧的虚拟 DOM 树结构中,从上到下审查每个发生了渲染的(不会灰色的)组件。

如果组件是由于 State 或 Hook 改变触发了 Render 过程,那它就是我们要找的组件,如下图:

站在巨人的肩膀上

Optimizing Performance React官方文档,最好的教程, 利用好 React 的性能分析工具。

Twitter Lite and High Performance React Progressive Web Apps at Scale 看看 Twitter 如何优化的。

-END-

盲盒是变相赌博OR智商税?

$
0
0

盲盒的起源

盲盒顾名思义,就是看不见内容的盒子,其内部放置着不同的物品,消费者凭运气抽中商品。小小的盒子里装着不同样式的玩偶,在拆封之前永远不知道里面是什么,正是这种随机化的体验,让用户欲罢不能。

盲盒营销最早可以追溯到明治末期的日本,其“前身”是日本百货公司在新年期间用来促销的福袋,福袋促销的方式通常用来作为商品尾货处理,用户既能获得趣味性体验,商家也可以成功清理库存。

福袋的营销思路延续到了80年代的日本模型市场中,逐渐商品化,于是出现了“扭蛋机”线下机器。

“扭蛋”与“福袋”大致相同,只是更集中在二次元、ACG等领域出现,销售的商品也大多是动漫IP手办、玩具模型、饰品挂件等。而盲盒营销在本土的早期应用,源于90年代中国开始的一系列“集卡式营销”。最为典型的代表案例就是小浣熊干脆面的水浒英雄卡。

到了21世纪,盲盒的概念逐渐定型,之后,国内很少有人讨论盲盒,直到2016年泡泡玛特大力发展盲盒产品,才让盲盒营销逐渐风靡。如今,盲盒已经成熟应用在各种营销场景,成为IP玩具礼品、线上线下互动营销常用的方式之一,并深受Z世代的喜爱。

泡泡玛特神话?

泡泡玛特曾是一家新三板公司,2019年4月从新三板摘牌之际总市值约20亿人民币。2020年12月11日,泡泡玛特在香港上市,开盘涨幅100.26%,市值一度超1100亿港元。(备注:当前市值711亿人民币)。

什么原因导致了一家公司在不到2年时间市值涨了35倍?是资本的炒作外还有什么内容支撑了其接近千亿的市值?伴随着好奇心一起研究下。

收入来源:IP

泡泡玛特的崛起,源于对潮流文化和内容的运作能力,并将授权IP(含独家授权)、自有IP应用于盲盒、手办、球关节娃娃及其他IP衍生品。其中自有IP“MOLLY”堪称最佳典范,生命力旺盛。根据年报,2020年8月上市的“MOLLY的一天”系列,截至2020年底单销售额破1亿元。包括自有IP、独家IP、非独家IP在内,自主IP已经成为泡泡玛特的收入核心来源,占2020年总收入的85%,相较于2019年有约3个百分点的提升。

从数据看泡泡玛特过于依赖大IP,潜在的风险是一旦大IP受欢迎程度疲软,对公司业绩会有很大影响。另外可也看出爆款IP的打造是非常难的。爆款IP成功原因是什么,出IP外,怎么的系列主题才能大受欢迎?

并不是每个IP均能获得成功,从泡泡玛特天猫官网我们可以看到在售的IP非常多,而真正被用户认可的也就那么3个。

泡泡玛特能够有这么多的线下收入,主要原因是线下门店开的比较多(主要在一二线)。

能够支持这么多门店的主要原因是门店的坪效较高。

而支撑坪效的核心原因是毛利率非常的高。

会员运营:高度粘性

2019 年泡泡玛特的 220 万名注册会员的复购率达 58%,超过 45%的潮玩消费者每年在潮玩上的花费超过 500 元人民币。

支撑起复购的主要原因或是是盲盒以12个为一套的设计方案。

盲盒背后的逻辑

间歇性强化

强化(Reinforcement)

强化(Reinforcement),是行为主义心理学中的一个重要概念,是关于理解和修正人的行为的一种学说。心理学研究发现,人类或动物为达到某种目的,会于所处的环境下采取特定行为;当这种行为带来的某种反应或后果对他有利时,这种行为就会在以后重复出现,而该结果就称为“强化物”;反之亦然,当其行为会对他带来不利时,这种行为就自然减弱或消失,个体对行为结果所产生的后续反应,就是以操作性条件反射进行的。而由于“强化物”的适时出现,增加了个体以后在相同情形下重复这种行为的概率,这表示“强化物”对于个体的反应起了强化作用。此种强化作用,即称之为“强化”。行为的强化有2种类型:

  • 正强化,也称积极强化、正向强化发生于一件渴求中的事或物作为一种结果而呈现,而这一结果刺激了这一渴求。在进行某个行为之后,增加对象喜爱的(通常是愉快的)刺激,并使该行为的出现频率增加。
  • 负强化,在进行某个行为之后,减少对象厌恶的(通常是不愉快的)刺激,并使该行为的出现频率增加。

强化还可划分为一级强化和二级强化两类。

  • 一级强化满足人和动物的基本生理需要,如食物、水、安全、温暖、性等。
  • 二级强化是指任何一个中性刺激如果与一级强化反复联合,它就能获得自身的强化性质。包括社会强化(社会接纳、微笑)、信物(钱、级别、奖品等)和活动(自由地玩、听音乐、旅游等)。如金钱,对婴儿它不是强化物,但当小孩知道钱能换糖时,它就能对儿童的行为产生效果。再如分数,也是在受到教师的注意后才具有强化性质的。

间歇性强化

当动物所处环境中够多的变因被减少或是被控制时,他们在强化后的行为型态将明显的能够被预测。甚至当强化的速率适应于特定方法时,非常复杂的行为也能够被预测。强化程序是用来测定将被强化的反应(特定行为的单独出现)的计划。有两种极端情况,一种是连续强化,指强化所有反应;另一种是消弱,指没有反应被强化。

  • 固定比率强化(Fixed ratio schedule,简称FR),每固定次数反应都被强化,如每20个反应提供老鼠一个小食。例子:每月奖金制度。
  • 固定时距强化(Fixed interval schedule,简称FI),从训练开始或先前一个强化之后经过特定时间长度之后强化,假设在这段期间至少有一次反应出现。例子:按销量核算佣金收入。
  • 变动比率强化(Variable ratio schedule,简称VR),在不同的反应次数强化,有一个大约的平均值,例如老虎机不知道中奖机会。
  • 变动时距强化(Variable interval schedule,简称VI),在经过一段不固定的时间之后强化,有一个大约的平均值,并假设在这段期间至少有一次反应。例如查看邮箱,不知道何时会有重要的信件或垃圾邮件,因为无法预测,所以会产生一个稳定的检查行为。

比率程序能够比间隔程序产生更高的反应频率。变化程序也比固定程序产生更高的反应频率。变化比率程序产生较高的反应频率,且对消弱有较大的抵抗力,赌博是变化比率程序最有代表性的例子。

比起每做一个行为就有奖励,与持续性强化(continous reinforcement)不同的是,间歇性强化(intermittent)指每一个反应都不一定有后果。与赌徒上瘾的逻辑相同,因为透过不能确定何时有回报,周不时的回报会鼓励赌徒,就会令到该行为更难去消失 (resistant to extinction)。目标习惯了有时会没有奖励(赌徒则没有中奖)但不是永远没有机会(会嬴钱的)。换言之,持续性强化的效果虽然快,但是快来也快去,所习得的行为也很快。

影响强化程序因素

  • 强化物的质与量:一般越多越容易强化某行为。一个刺激的效果也与成本效益有关,刺激的数量或是大小若是足够,对行为的刺激效果较大。例如特别庞大的乐透奖金,将使人愿意付出金钱和时间去购买彩券,如果奖金很少,可能不足以使人特地开车出门购买彩券。
  • 附带性:如果一个刺激并非总是伴随在行为之后,则刺激的效果将减少;如果一个刺激确实的附带在每一次行为之后,刺激的效果较大。
  • 强化延迟:个体进行一个行为过后,刺激回馈的立即性,会影响刺激的效果。行为与刺激的间隔愈短,效果愈大。例如一位在公路上超速的驾驶,若在一周之后才收到罚单,那么此罚单的效果将不如警察立即拦阻开罚单的效果。
  • 回应难度:如多容易就可以得到金钱,或若做好事做有多些分而获得小礼物,那做了坏事就扣分(response cost)。
  • 行为惯性(Behavioral momentum):当人们习惯了经常有回馈后,即使之后没有回馈仍会较大机会做该反应。
  • 规则化:一些已知的规则比起偶然发生的事难改变行为。
  • 强化历史:有过去经验而对比到未来会如何(behavioural contrast)。
  • 满足感,也可称为厌腻感:刺激的效果与动物个体对该刺激的胃口有关,个体对刺激的欲望愈大,刺激的效果也愈大;如果某动物个体已经对某个刺激感到满足或是厌腻,那么该刺激将不再有效果。

其中立即性和附带性能够以神经化学来解释,当生物个体受到强化刺激,则大脑中的多巴胺通道将被活化,这些通道组成的网络释放短暂的多巴胺脉冲到许多树突,因此散发强化刺激讯号到突触后神经元。造成刚被活化的的突触对输出讯号的感应加强,因此造成强化刺激之前的行为的出现概率增加。在统计学上显示对行为的强化刺激成功。然而当强化刺激的立即性和附带性减少,多巴胺对突触的影响能力也会减少。

好奇心的驱动

人天生对不确定性的事物抱有恐惧感,因此会本能地追寻一个确定的答案:

  • 它究竟是什么?
  • 是好是坏?
  • 有没有危险?
  • 有什么好处?

这种动机让人类对许多未知事物形成一种好奇心,希望去探索、了解,寻求确定的答案,最终获得掌控感。

苹果手机每年召开新品发布会前,都会预先发布一个特别设计的邀请函,设计上似乎包含了发布会的新品内容暗示,但又并不明说,引发了业界的各种猜测和消费者的强烈好奇心,于是眼巴巴地等着发布会的到来。盲盒则光明正大地把“我就不告诉你”写在了脸上,直接告诉消费者,盒子里不知道是哪一款玩偶,你猜!意不意外?惊不惊喜?想知道就买回去!这种不确定性和随机性成为一种未知的惊喜,利用了消费者对商品的好奇心和期待,这是一种“情绪营销”。当我们怀着各种猜想和期待打开盲盒,看到里面那个小小的玩偶所引发的情绪波动,会在大脑中形成强烈而深刻的印象和记忆。这种感受是惊喜愉悦的,从而进一步强化了我们对商品的认同感和喜爱感,并促使我们以后进行复购,不断去反复获取和体验购买行为带来的愉悦感。

成瘾性收集(收集心理)

在我们周围,总有一些喜欢收藏的人,他们热衷于收藏各类古玩、字画、邮票、旧票据、杂志、书信等物品。不同的人,收藏的目的不同:

  • 情趣爱好:对生活有比较高的情趣追求,记录美好生活。
  • 精神寄托:寻找某种与世界的连接点,能够找到存在感。

收藏成癖?:

  • 因极其缺乏某样东西、或失去过某样东西,因此渴望通过不断地购买、收藏来填补心灵上的“空缺感”“空虚感”
  • 人生阶段因为某些原因,造成了自身的心灵创伤,而收藏能给他们带来精神安慰、情感寄托(收藏哪些东西与其自身经历有关)
  • 成长过程中,因为一些偶然的机会,接触到了某些东西并对此“一见钟情”,通过收藏来满足自己的占有欲

盲盒的营销思路主要是利用好奇心和收集欲望营销,消费者主要是小年轻,好奇心推动消费者首次尝试,收集欲望推动他们重复购买,不是新鲜模式了,早年的小浣熊等各种集卡、日本扭蛋都是这个模式,扭蛋和盲盒的做工、成品更漂亮更贵,收藏价值会比集卡高一些;在新顾客中,冲动消费的居多,很多人买过几个,热情就消退了,19年注册会员的复购率只有58%,可以说一半依靠老顾客的重复购买,一半靠新顾客尝试,收藏价值一般,所以咸鱼这种二手平台上的挂单量非常大,绝大多数不是因为增值了要交易赚钱,而是觉得不需要了、或者同款单品重复了,要交易掉。

心理学上有个“禀赋效应”,就是当我们一旦拥有某项物品,我们对该物品价值的感知和评价就比未拥有之前大大提高,并且会越来越认同和喜爱。当人们把盲盒作为一种兴趣爱好来对待,为它投入大量的金钱、时间和精力,收集得越来越多之后,赋予它的价值和寄托的情感也会越来越大,也就越来越不容易舍弃。

泡泡玛特的盲盒并非各自独立,而是按照IP分为多个系列,一个系列为一套。集齐一套12个。除常规款外还有第13个“隐藏款”,被抽到的概率大概为1/144。此外还有各种纪念款、联名款。也就是说,你永远都不知道你这次会买到哪一款。当人们购买或被赠送了第一个盲盒玩偶,就会不断的购买新的盲盒以期与其配套,当还差一两个隐藏款就能集齐全套时,更是不开盒就心痒难耐。这个聪明的设置很容易让人们陷入上瘾状态,强迫性地进行反复购买,希望收集齐全一整套玩偶。一家人就是要整整齐齐嘛!如果在一直抽不到想要的玩偶时就此收手,会感觉之前花费的时间和金钱似乎都成为了无法挽回的“沉没成本”,强烈的损失厌恶让人只有继续不断的投入,直到抽到一个隐藏款似乎才能弥补之前的损失,才感觉之前的投入是“值得”的。这也是为什么首次购买时会给予你各种新人优惠,目的就是想让你“一次入坑,越陷越深”。

精神商品的满足

一个盲盒的售卖价格大约50~80元,从成本来看,它并没有这么贵,我们更多的是为它的情绪价值买单。泡泡玛特联创人曾总结了盲盒的三点特征:

  • 没有世界观
  • 没有价值观
  • 艺术性不强

这些特性也让人们能够没有认知负担地自由赋予盲盒各种情感价值,从中获得情感满足。可以说,盲盒除了满足人们的好奇心和收集癖,从深层心理因素来看,更是满足了当代年轻人的情感需求。这种情感需求是复杂而多样的,包括了安全感(减压)、社交、情感寄托等。玩盲盒成为一种完成任务的奖励和动力,满足了一些人的动机追求和内心需求,带来某种满足感。此外,随着玩盲盒人群的扩大、对玩偶款式收集的需求提升,盲盒也成为一种“社交货币”,成为进行社交的一个契机和驱动力,在年轻人中间形成独特的圈层。跟朋友一起时,如果大家都喜欢玩盲盒,自然会有更多的共同话题和情感联结。

盲盒的游戏机制不仅可以带给用户刺激感,还会激发出买家后续的互动行为,例如大部分盲盒爱好者在天猫等购物平台、泡泡玛特线下零售店或无人贩卖机上购入盲盒,在闲鱼等二手交易平台上交换自己抽取的手办,在百度贴吧等社区中交流“改娃”经验,达到线上线下消费场景互通的效果。其实盲盒本身就是一款社交产品,是一种新生代的社交货币。在小红书或抖音等社交平台上关注抽盒博主、观看拆盒短视频,是与平台上其他对盲盒感兴趣的用户一起互动,通过评论区或其他互动形式构成一种社会临场感,同时产生情感镜像机制,具有虚拟代偿的功用。并且同好者之间会产生共情反应,出现情感投射效果,分享者获得好评与流量,而关注者会继续强化购买动机、继而实施购买行为。根据戈夫曼的前台/后台的情境,用户个人产出盲盒相关的UGC内容,有利于构建新的自我形象,通过在社交平台上晒娃、换娃的行为,可以帮助用户塑造出娃妈、潮流玩家、盲盒达人等多种虚拟社交形象。总体来说,上述消费者参与行为实现了由盲盒联结起来的用户交往去地域化和去中心化。

赌徒和投机心态

  • 赌徒心态:拆盲盒的过程相当于买彩票,追求不确定性的刺激会导致玩家不断购买,从而成瘾。由于盲盒在拆开之前,消费者无法得知说购买的玩具款式,这种话信息的不确定性促使拆马哥的整个过程充满神秘感,另盲盒玩家感到兴奋和刺激,若抽中心仪的款式,这盲盒玩家心理上会等到额外惊喜,若未抽中心仪的款式,由于盲盒的单价较低,重复购买并不会对玩家造成较大的经济压力,此时笃定下次肯定抽中心仪款式的赌徒心态促使玩家再次购买盲盒。
  • 投机心态:此外,盲盒兴业存在二手市场,玩家一旦抽到重复的普通款盲盒,会将多余的款式放至闲鱼等二手买卖平台上流通销售。尤其是收藏孩子较高的隐藏款在盲盒二手市场上的溢价到达20-30倍,收到盲盒玩家热捧。部分盲盒玩家抱着投机的心态不断购买盲盒,并在二手平台上销售,从而赚取超额利润。

赌徒谬误(Gambler’s Fallacy)是一种错误的推理方式,认为随机事件发生的机会率与之前发生的事件有关,即认为某个事件发生的概率,会随着之前没有发生该事件的次数而上升。在多次抽不到隐藏款时,人们会错误的认为下次抽中的概率增加,从而更加在沉没成本和翻本效应的影响下无法自拔。隐藏款因为其稀缺性具有极高的溢价,购买盲盒就像一次赌博,几十元的投入就可能获得上万元的收益,因此很多人在大量的投入无果之后,更期待“一次开盒,全局翻盘”。而赌徒谬误则加强了翻本效应对决策的影响。

易得性启发

易得性启发是指人们对于某个事件发生的概率的评估,往往会依据这个事件在知觉或记忆中的易得性程度,容易知觉或回想的事件被判定为更容易发生。朋友圈、微博、b站等众多平台都有各种盲盒套装分享、隐藏款开箱分享,抽到隐藏款不仅是获得了喜爱的玩偶,更成为了一种向其他人炫耀的资本。而这种炫耀性行为不仅对自身的购买行为进行了正强化,还刺激了他人的购买行为。

实际上,大多数人只有在抽中了隐藏款、限量款时才会进行分享,而更多情况下的没有抽中的经历却因为没有被分享而被忽略,在这样的有偏信息来源下,我们会错误的认为似乎抽中隐藏款并没有那么难,而忽略了本身的概率,产生易得性启发式这一认知偏差,掉入消费陷阱。

盲盒背后的风险

随着泡泡玛特的流行,越来越多的商家也参与进入“盲盒”经济。包括玩偶盲盒、服装盲盒、鞋子盲盒、文具盲盒、键盘盲盒、美妆盲盒、饮料盲盒、机票盲盒、酒店盲盒…如果你也想加入,就需要考虑以下因素。

《规范促销行为暂行规定》已于2020年1月15日经国家市场监督管理总局2020年第1次局务会议审议通过,现予公布,自2020年12月1日起施行。

第三章 有奖销售行为规范

 

第十一条 本规定所称有奖销售,是指经营者以销售商品或者获取竞争优势为目的,向消费者提供奖金、物品或者其他利益的行为,包括抽奖式和附赠式等有奖销售。

抽奖式有奖销售是指经营者以抽签、摇号、游戏等带有偶然性或者不确定性的方法,决定消费者是否中奖的有奖销售行为。

附赠式有奖销售是指经营者向满足一定条件的消费者提供奖金、物品或者其他利益的有奖销售行为。

经政府或者政府有关部门依法批准的有奖募捐及其他彩票发售活动,不适用本规定。

第十二条 经营者为了推广移动客户端、招揽客户、提高知名度、获取流量、提高点击率等,附带性地提供物品、奖金或者其他利益的行为,属于本规定所称的有奖销售。

第十三条 经营者在有奖销售前,应当明确公布奖项种类、参与条件、参与方式、开奖时间、开奖方式、奖金金额或者奖品价格、奖品品名、奖品种类、奖品数量或者中奖概率、兑奖时间、兑奖条件、兑奖方式、奖品交付方式、弃奖条件、主办方及其联系方式等信息,不得变更,不得附加条件,不得影响兑奖,但有利于消费者的除外。

在现场即时开奖的有奖销售活动中,对超过五百元奖项的兑奖情况,应当随时公示。

第十四条 奖品为积分、礼券、兑换券、代金券等形式的,应当公布兑换规则、使用范围、有效期限以及其他限制性条件等详细内容;需要向其他经营者兑换的,应当公布其他经营者的名称、兑换地点或者兑换途径。

第十五条 经营者进行有奖销售,不得采用以下谎称有奖的方式:

(一)虚构奖项、奖品、奖金金额等;

(二)仅在活动范围中的特定区域投放奖品;

(三)在活动期间将带有中奖标志的商品、奖券不投放、未全部投放市场;

(四)将带有不同奖金金额或者奖品标志的商品、奖券按不同时间投放市场;

(五)未按照向消费者明示的信息兑奖;

(六)其他谎称有奖的方式。

第十六条 经营者进行有奖销售,不得采用让内部员工、指定单位或者个人中奖等故意让内定人员中奖的欺骗方式。

第十七条 抽奖式有奖销售最高奖的金额不得超过五万元。有下列情形之一的,认定为最高奖的金额超过五万元:

(一)最高奖设置多个中奖者的,其中任意一个中奖者的最高奖金额超过五万元;

(二)同一奖券或者购买一次商品具有两次或者两次以上获奖机会的,累计金额超过五万元;

(三)以物品使用权、服务等形式作为奖品的,该物品使用权、服务等的市场价格超过五万元;

(四)以游戏装备、账户等网络虚拟物品作为奖品的,该物品市场价格超过五万元;

(五)以降价、优惠、打折等方式作为奖品的,降价、优惠、打折等利益折算价格超过五万元;

(六)以彩票、抽奖券等作为奖品的,该彩票、抽奖券可能的最高奖金额超过五万元;

(七)以提供就业机会、聘为顾问等名义,并以给付薪金等方式设置奖励,最高奖的金额超过五万元;

(八)以其他形式进行抽奖式有奖销售,最高奖金额超过五万元。

第十八条 经营者以非现金形式的物品或者其他利益作为奖品的,按照同期市场同类商品的价格计算其金额。

第十九条 经营者应当建立档案,如实、准确、完整地记录设奖规则、公示信息、兑奖结果、获奖人员等内容,妥善保存两年并依法接受监督检查。

盲盒与实物彩票的边界越来越模糊,也必定会引起相关部门的关注。未来可能的监管方案:

  • 盲盒产品开出来的所有的产品要相同(不允许隐藏款存在—人为制造稀缺)
  • 任何盲盒类产品可单独指定购买,售价不允许超过盲盒单价的X%(比如100%)

参考链接:

为了效率不应该做的7件事

$
0
0

把自己弄得忙忙碌碌,但回头看碌碌无为。无意看到这篇文章,感觉对自己非常有用,记录下来自勉。

设想一下有一个不停工作的小业务员,努力工作并不能帮助他战胜成千上万的竞争对手。 时间是有限的商品。一个企业家最多可以每周7天每天工作24小时,他的竞争对手可以花更多的钱,建立一个更大的团队,花更多的时间在这个项目上。但是为什么有一些小的初创公司完成了大公司不能完成的事情?Facebook花了10亿美元收购了一个只有13名员工的Instagram。Snapchat,一家只有30名员工的年轻初创公司拒绝了莱斯Facebook和Google的收购。他们成功的部分原因是运气,其他主要靠效率。

成功的关键不是努力工作而是聪明的工作。忙与产出有着显著的区别。忙未必就说明有产出。 要想产出,更多的是要管理好你的精力而不是时间。要经营好你的生活。我们需要学会花费最小的精力得到最大的收益。

停止通过加班来增加产出

你有没有想过40小时的工作时间是从哪里来的?每周5 天,每天8小时的工作制是福特在1926年的发现。实验表明,把每天工作时间从 10 小时降至 8 小时,每周工作时间从 6 天降至 5 天后,生产力反而提升了。

1980年由商业圆桌会议发布的《施工项目的加班效应》指出,工作得越多,无论是短期还是长期上来看,你的工作效率和生产力都会下降。当每周工作时间超过60 小时,并持续超过两个月,生产力下降的累积效应将使完工日期推迟,而人数相同但每周只工作40 小时的团队执行同样工作,甚至还会更早完工。

在AlterNet的一篇文章中,Sara Robinson回顾美军执行的一项研究,这项发现“每晚都减少1小时睡眠,持续一周,将导致认知功能退化,等同于喝酒使血液酒精浓度升高至0.10 ”。当个人过于劳累,使其以比平常还要负面的角度看事情,导致普遍地心情低落。比心情更重要的是,其思维往往伴随着减少「主动思考与行动」──包括控制冲动、自我感觉良好、同情他人与情绪智力──的意愿。

维持高程度的生产力,避免让自己过度工作并睡眠充足很重要。下次您思想为何工作缺乏生产力,原因很简单,您是70%的人缺少足够的睡眠的一员。

不要老说“好的”

根据 20/80 原理(帕累托原理),20%的努力创造出80%的结果;但反过来20%的结果消耗了80%的努力。因此我们应该把精力集中在能产出80%结果的事情上,然后放弃其他的事情。如此就能把更多的时间集中在最重要的任务上。我们应该对低产出甚至无结果的任务停止说 “yes”。

这就引出一个问题:我们该对哪些事情说“yes”,对哪些事情说“no”呢?如果想不出哪些事情值得花时间,不妨来个简单的分离测试。跟踪自己所做的一切事情然后尽可能优化。

我们中的大多数往往都说了太多的“yes”,因为这比拒绝要容易得多。没人想当坏人。

2012 年的消费者杂志发表了一项研究,研究人员把 120 人分成了 2 组。一组人训练成说“我不能(I can’t)”,另一组则说“我不(I don’t)”。结果很有趣:告诉自己“我不能吃 X”的学生在 61% 的时间内选择了吃巧克力糖,而告诉自己“我不吃 X”的只在 36% 的时间里抵挡不住诱惑。在说法上作这么简单的一个变化就能显著改善健康食品的选择。所以,下次需要避免说 yes 的时候,直接说“我不”。

另一个避免不必要活动的技巧是 20 秒规则:对于不应该做的事情多给自己 20 秒的时间考虑。降低在你想丢弃的习惯上的能量消耗,提升你想培养的习惯上的能力。我们能降低或消除的越多的能量浪费,越能提高我们积极拥抱变化的能力。

停止什么都事必躬亲,让其他人帮忙

为什么品牌需要用户创建内容。消费者知道他们想要什么,以及他们想要如何让它更好,更甚于任何营销人员。 根据Octoly的报告,一支使用者自制影片的观看次数要比品牌自制影片多上十倍。当寻找关于一个特定品牌的资讯,超过半数(51%)的美国人相信使用者自制内容大过于品牌官网(16%)与媒体报导(14%),对营销人员来说,寻求品牌社区的帮忙至关重要。

成为一个伟大的内容创造者,并不是他要创造最好的内容,而是创造一个伟大的社区来帮能产生高质量的内容。

我们必须意识到,在需要的时候可以去寻求帮助,这一点很重要。让做得更好的人接管你的一些工作对你来说更好。这可以让你花更多的时间在自己最重要的任务上。不要把时间浪费在自己解决问题上,让专家帮助你。

很多时候,哪怕朋友不能帮你,他们的陪伴也能让你更有生产力。

在ADHD的治疗中有一种“双重身体”的概念,人们会在有其他人在场的情况下做更多的内容,尽管那些人没有协助或指导,也可以完成更多的工作事项。如果你面临的任务是沉闷或困难的,比如清理你的衣柜或整理您的所有账单,找一个朋友当你的“双重身体”吧。

停止完美主义

Dalhousie University心理学教授Simon Sherry博士Simon Sherry执行一项完美主义与生产力的研究 ,她指出:我们发现完美主义是绊倒教授的研究生产力的大石头。完美主义倾向越高的教授就越没有效率。

当一位完美主义者有以下问题:

  • 他们在一项花费的时间比任务要求所花费的时间还多。
  • 他们会拖延并等到最佳的时刻。 在企业中,如果这是最完美的时刻,就代表已经太迟了。
  • 他们过度聚焦在细节,反而忽略整体。

市场营销人员经常等待最好的时机,如果这样去做,最终会失去机会。 最好时刻就是现在

停止作重复的事情,并使它自动化

根据一项Tethy Solutions的研究,一个5人团队分别花3%、20%、25%、30%与70%的时间处理相同的事情,导入工作自动化软体两个月后,分别将处理重复事情的时间降至3%、10%、15%、15% 与10%。

你需要为了让自己的工作能够自动化而让自己成为一个程序员。如果你拥有这样的能力或资源是最好的,但并不是必须的。如果你不懂得开发,那就去买一个!

人们时常忘记时间就是金钱,因此经常土法炼钢地处理事情,因为这样比较容易,且不需要花费心力研究。假设您办了一个Instagram 活动,号召网友上传的照片总数只有30 张,您可以手动一张一张处理。 但如果总共有从5个不同平台上传的30000张照片与影片时,您就需要一个好的数位管理系统了。如果你不能找到解决方案,你可以雇佣一个专家来帮助你。在你的脑海中始终记得你需要花钱去赚钱,而时间是你最有价值的商品。

停止猜测,并开始用数据支撑决策

如果你可以为搜索引擎进行网站优化,那么你也可以优化你的人生,让它成长并发挥最大的潜能。

不同领域有许多研究可供参考。比方说,你是否知道下午 4 点的时候人最容易分心?这是宾州大学助理教授 Robert Matchock 领导的一项研究。哪怕你找不到需要的数据,进行分离测试也不需要花太多的时间。

要不断问问自己,你打算如何去衡量和优化自己所做的一切事情?

停止工作,并拥有无所事事的时间

大部分的人都没有了解到,当我们专注在某件事上,基本上就像是把自己锁在一个箱子里。 很重要的是要每隔一段时间离开工作现场,享受独处的时光。 独处时光对大脑与灵魂都有益处,波士顿环球时报的一篇文章:

哈佛研究表明,如果某人相信自己正在独自体验某件事情时,其记忆会更持久更精确。另一项研究表明,一定时间的独处可令人更具同理心。尽管没人会质疑早期过多的孤僻生活是不健康的,但一定时间的独处可令青少年改善情绪提高成绩。

对我们来说,要花时间去思考是很重要的。我们经常发现在我们不是刻意寻找解决方案的时候解决方案会突然出现。

我们不会因为熬夜而更有效率。 就像是生命中的每件事情,需要耗费心力。 如果您什么都不做只是坐着等,不会有什么改变,所以我们要更了解自己的限制与潜能,并将精力作有效的配置,过一个更成功、更快乐的人生。

原文链接: 7 Things You Need to Stop Doing to Be More Productive

潜在语义分析LSA初探

$
0
0

什么是潜在语义分析LSA?

潜在语义分析(Latent Semantic Analysis),是语义学的一个新的分支。传统的语义学通常研究字、词的含义以及词与词之间的关系,如同义,近义,反义等等。潜在语义分析探讨的是隐藏在字词背后的某种关系,这种关系不是以词典上的定义为基础,而是以字词的使用环境作为最基本的参考。这种思想来自于心理语言学家。他们认为,世界上数以百计的语言都应该有一种共同的简单的机制,使得任何人只要是在某种特定的语言环境下长大都能掌握那种语言。在这种思想的指导下,人们找到了一种简单的数学模型,这种模型的输入是由任何一种语言书写的文献构成的文库,输出是该语言的字、词的一种数学表达(向量)。字、词之间的关系乃至任何文章片断之间的含义的比较就由这种向量之间的运算产生。

向量空间模型是信息检索中最常用的检索方法,其检索过程是,将文档集D中的所有文档和查询都表示成以单词为特征的向量,特征值为每个单词的TF-IDF值,然后使用向量空间模型(亦即计算查询q的向量和每个文档$d_i$的向量之间的相似度)来衡量文档和查询之间的相似度,从而得到和给定查询最相关的文档。向量空间模型简单的基于单词的出现与否以及TF-IDF等信息来进行检索,但是“说了或者写了哪些单词”和“真正想表达的意思”之间有很大的区别,其中两个重要的阻碍是单词的多义性(polysems)和同义性(synonymys)。

  • 多义性指的是一个单词可能有多个意思,比如Apple,既可以指水果苹果,也可以指苹果公司
  • 同义性指的是多个不同的词可能表示同样的意思,比如search和find。

同义词和多义词的存在使得单纯基于单词的检索方法(比如向量空间模型等)的检索精度受到很大影响。总而言之,在基于单词的检索方法中,同义词会降低检索算法的召回率(Recall),而多义词的存在会降低检索系统的准确率(Precision)。

LSA和传统向量空间模型(vector space model)一样使用向量来表示词(terms)和文档(documents),并通过向量间的关系(如夹角)来判断词及文档间的关系;不同的是,LSA 将词和文档映射到潜在语义空间,从而去除了原始向量空间中的一些“噪音”,提高了信息检索的精确度。如果两个单词之间有很强的相关性,那么当一个单词出现时,往往意味着另一个单词也应该出现(同义词);反之,如果查询语句或者文档中的某个单词和其他单词的相关性都不大,那么这个词很可能表示的是另外一个意思(比如在讨论互联网的文章中,Apple更可能指的是Apple公司,而不是水果)。

LSA工具:

  • 2009年:Gensim
  • 2015年:fastText
  • 2016年:text2Vec

潜在语义分析LSA原理

假设有 n 篇文档,这些文档中的单词总数为 m (可以先进行分词、去词根、去停止词操作),我们可以用一个 m∗n 的矩阵 X 来表示这些文档,这个矩阵的每个元素 X_{ij} 表示第 i 个单词在第 j 篇文档中出现的次数(也可用tf-idf值)。下文例子中得到的矩阵见下图。

LSA试图将原始矩阵降维到一个潜在的概念空间(维度不超过n),然后每个单词或文档都可以用该空间下的一组权值向量(也可认为是坐标)来表示,这些权值反应了与对应的潜在概念的关联程度的强弱。这个降维是通过对该矩阵进行 奇异值分解(SVD, singular value decomposition)做到的,计算其用三个矩阵的乘积表示的等价形式,如下:

LSA的数学原理是矩阵分解(奇异值分解SVD),本质是线性变换(把文本从单词向量空间映射到语义向量空间,词->语义)

潜在语义分析LSA优缺点

LSA的优点:

  • 低维空间表示可以刻画同义词,同义词会对应着相同或相似的主题。
  • 降维可去除部分噪声,是特征更鲁棒。
  • 充分利用冗余数据。
  • 无监督/完全自动化。
  • 与语言无关。

LSA的缺点:

  • LSA可以处理向量空间模型无法解决的一义多词(synonymy)问题,但不能解决一词多义(polysemy)问题。因为LSA将每一个词映射为潜在语义空间中的一个点,也就是说一个词的多个意思在空间中对于的是同一个点,并没有被区分。
  • SVD的优化目标基于L-2 norm 或者 Frobenius Norm 的,这相当于隐含了对数据的高斯分布假设。而 term 出现的次数是非负的,这明显不符合 Gaussian 假设,而更接近 Multi-nomial 分布。
  • 特征向量的方向没有对应的物理解释。
  • SVD的计算复杂度很高,而且当有新的文档来到时,若要更新模型需重新训练。
  • 没有刻画term出现次数的概率模型。
  • 对于count vectors 而言,欧式距离表达是不合适的(重建时会产生负数)。
  • 维数的选择是ad-hoc的。
  • LSA具有词袋模型的缺点,即在一篇文章,或者一个句子中忽略词语的先后顺序。
  • LSA的概率模型假设文档和词的分布是服从联合正态分布的,但从观测数据来看是服从泊松分布的。因此LSA算法的一个改进PLSA使用了多项分布,其效果要好于LSA。

潜在语义分析LSA代码示例

# -*- coding: utf-8 -*-
from numpy import zeros
from scipy.linalg import svd
from math import log
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd


class LSA(object):
    """定义LSA类,w_dict字典用来记录词的个数,d_count用来记录文档号。
    """
    def __init__(self, stop_words, ignore_chars):
        self.stop_words = stop_words
        self.ignore_chars = ignore_chars
        self.w_dict = {}
        self.d_count = 0

    def parse(self, doc):
        """把文档分词,并滤除停用词和标点,剩下的词会把其出现的文档号填入到w_dict中去,
        例如,词book出现在标题3和4中,则我们有self.w_dict[‘book’] = [3, 4]。相当于建了一下倒排。
        """
        words = doc.split()
        for w in words:
            w = w.lower().translate(self.ignore_chars)
            if w in self.stop_words:
                pass
            elif w in self.w_dict:
                self.w_dict[w].append(self.d_count)
            else:
                self.w_dict[w] = [self.d_count]
        self.d_count += 1

    def build(self):
        """建立索引词文档矩阵
        所有的文档被解析之后,所有出现的词(也就是词典的keys)被取出并且排序。建立一个矩阵,其行数是词的个数,列数是文档个数。
        最后,所有的词和文档对所对应的矩阵单元的值被统计出来。"""
        self.keys = [k for k in self.w_dict.keys() if len(self.w_dict[k]) > 1]
        self.keys.sort()
        self.A = zeros([len(self.keys), self.d_count])
        for i, k in enumerate(self.keys):
            for d in self.w_dict[k]:
                self.A[i, d] += 1

    def print_A(self):
        """打印出索引词文档矩阵。
        """
        print(self.A)

    def TF_IDF(self):
        """用TF-IDF替代简单计数
        在复杂的LSA系统中,为了重要的词占据更重的权重,原始矩阵中的计数往往会被修改。
        最常用的权重计算方法就是TF-IDF(词频-逆文档频率)。基于这种方法,我们把每个单元的数值进行修改。
        wordsPerDoc 就是矩阵每列的和,也就是每篇文档的词语总数。DocsPerWord 利用asarray方法创建一个0、1数组(也就是大于0的数值会被归一到1),然后每一行会被加起来,从而计算出每个词出现在了多少文档中。最后,我们对每一个矩阵单元计算TFIDF公式
        """
        words_per_doc = np.sum(self.A, axis=0)
        docs_per_word = np.sum(np.asarray(self.A > 0, 'i'), axis=1)
        rows, cols = self.A.shape
        for i in range(rows):
            for j in range(cols):
                self.A[i, j] = (self.A[i, j] / words_per_doc[j]) * log(float(cols) / docs_per_word[i])

    def calc_SVD(self):
        """建立完词文档矩阵以后,用奇异值分解(SVD)分析这个矩阵。
        SVD非常有用的原因是,它能够找到我们矩阵的一个降维表示,他强化了其中较强的关系并且扔掉了噪音(这个算法也常被用来做图像压缩)。
        换句话说,它可以用尽可能少的信息尽量完善的去重建整个矩阵。为了做到这点,它会扔掉无用的噪音,强化本身较强的模式和趋势。
        利用SVD的技巧就是去找到用多少维度(概念)去估计这个矩阵。太少的维度会导致重要的模式被扔掉,反之维度太多会引入一些噪音。
        代码中降到了3维
        """
        self.U, self.S, self.Vt = svd(self.A)
        target_dimension = 3
        self.U2 = self.U[0:, 0:target_dimension]
        self.S2 = np.diag(self.S[0:target_dimension])
        self.Vt2 = self.Vt[0:target_dimension, 0:]
        print("U:\n", self.U2)
        print("S:\n", self.S2)
        print("Vt:\n", self.Vt2)

    def plot_singular_values_bar(self):
        """为了去选择一个合适的维度数量,我们可以做一个奇异值平方的直方图。它描绘了每个奇异值对于估算矩阵的重要度。
        下图是我们这个例子的直方图。(每个奇异值的平方代表了重要程度,下图应该是归一化后的结果)
  """
        y_value = (self.S * self.S) / sum(self.S * self.S)
        x_value = range(len(y_value))
        plt.bar(x_value, y_value, alpha=1, color='g', align="center")
        plt.autoscale()
        plt.xlabel("Singular Values")
        plt.ylabel("Importance")
        plt.title("The importance of Each Singular Value")
        plt.show()

    def plot_singular_heatmap(self):
        """用颜色聚类
        我们可以把数字转换为颜色。例如,下图表示了文档矩阵3个维度的颜色分布。除了蓝色表示负值,红色表示正值,它包含了和矩阵同样的信息。
        """
        labels = ["T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9"]
        rows = ["Dim1", "Dim2", "Dim3"]
        self.Vt_df_norm = pd.DataFrame(self.Vt2 * (-1))
        self.Vt_df_norm.columns = labels
        self.Vt_df_norm.index = rows
        sns.set(font_scale=1.2)
        ax = sns.heatmap(self.Vt_df_norm, cmap=plt.cm.bwr, linewidths=.1, square=2)
        ax.xaxis.tick_top()
        plt.xlabel("Book Title")
        plt.ylabel("Dimensions")
        plt.show()


if __name__ == '__main__':
    # 待处理的文档
    titles = [
        "The Neatest Little Guide to Stock Market Investing","Investing For Dummies, 4th Edition","The Little Book of Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock Market Returns","The Little Book of Value Investing","Value Investing: From Graham to Buffett and Beyond","Rich Dad's Guide to Investing: What the Rich Invest in, That the Poor and the Middle Class Do Not!","Investing in Real Estate, 5th Edition","Stock Investing For Dummies","Rich Dad's Advisors: The ABC's of Real Estate Investing: The Secrets of Finding Hidden Profits Most Investors Miss"
    ]
    # 定义停止词
    stopwords = ['and', 'edition', 'for', 'in', 'little', 'of', 'the', 'to']
    # 定义要去除的标点符号
    ignore_chars = ''',:'!'''

    mylsa = LSA(stopwords, ignore_chars)
    for t in titles:
        mylsa.parse(t)
    mylsa.build()
    mylsa.print_A()
    mylsa.TF_IDF()
    mylsa.print_A()
    mylsa.calc_SVD()
    mylsa.plot_singular_values_bar()
    mylsa.plot_singular_heatmap()

在 sklearn 中,LSA可以更加方便的实现:

import pandas as pd
import matplotlib.pyplot as plt
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.datasets import fetch_20newsgroups
from sklearn.decomposition import TruncatedSVD
import umap

dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove=('headers', 'footers', 'quotes'))
documents = dataset.data
print(len(documents))
print(dataset.target_names)

news_df = pd.DataFrame({'document': documents})

# remove everything except alphabets`
news_df['clean_doc'] = news_df['document'].replace("[^a-zA-Z]", " ")

# remove short words
news_df['clean_doc'] = news_df['clean_doc'].apply(lambda x: ' '.join([w for w in x.split() if len(w) > 3]))

# make all text lowercase-
news_df['clean_doc'] = news_df['clean_doc'].apply(lambda x: x.lower())

# tokenization
tokenized_doc = news_df['clean_doc'].apply(lambda x: x.split())

# remove stop-words
stop_words = stopwords.words('english')
tokenized_doc = tokenized_doc.apply(lambda x: [item for item in x if item not in stop_words])

# de-tokenization
detokenized_doc = []
for i in range(len(news_df)):
    t = ' '.join(tokenized_doc[i])
    detokenized_doc.append(t)

news_df['clean_doc'] = detokenized_doc

vectorizer = TfidfVectorizer(stop_words='english', max_features=1000, max_df=0.5, smooth_idf=True)
X = vectorizer.fit_transform(news_df['clean_doc'])
print(X.shape)

# SVD represent documents and terms in vectors
svd_model = TruncatedSVD(n_components=20, algorithm='randomized', n_iter=100, random_state=122)
svd_model.fit(X)
print(len(svd_model.components_))

terms = vectorizer.get_feature_names()
for i, comp in enumerate(svd_model.components_):
    terms_comp = zip(terms, comp)
    sorted_terms = sorted(terms_comp, key=lambda x: x[1],reverse=True)[:7]
    sorted_terms_words = [t[0] for t in sorted_terms]
    print("Topic " + str(i) + ": " + str(sorted_terms_words))

X_topics = svd_model.fit_transform(X)
embedding = umap.UMAP(n_neighbors=150, min_dist=0.5, random_state=12).fit_transform(X_topics)
plt.figure(figsize=(7, 5))
plt.scatter(embedding[:, 0], embedding[:, 1], c=dataset.target, s=10, edgecolor='none')
plt.show()

潜在语义分析实战:基于LSA的情感分类

数据集: Amazon.com 50万点评数据

字段说明:

  • Id:自增长ID,无含义
  • ProductId:产品ID
  • UserId:会员ID
  • ProfileName:会员昵称
  • HelpfulnessNumerator:评价点评有用数量
  • HelpfulnessDenominator:评价点评总数
  • Score:点评分
  • Time:点评时间
  • Summary:综合评价
  • Text:点评详情

这里只会用到2个字段:Score和Text。Score:总共5分,我们将1分、2分的看作是负面评论。4分、5分的看作是正面评论。将3分的中性评论直接删除。

加载Python包:

import numpy as np
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report
from sklearn.feature_selection import chi2
import matplotlib.pyplot as plt

准备数据:

df = pd.read_csv('Reviews.csv')
df.dropna(inplace=True)
df[df['Score'] != 3]
df['Positivity'] = np.where(df['Score'] > 3, 1, 0)
X = df['Text']
y = df['Positivity']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)
print("Train set has total {0} entries with {1:.2f}% negative, {2:.2f}% positive".format(
    len(X_train),
    (len(X_train[y_train == 0]) / (len(X_train) * 1.)) * 100,
    (len(X_train[y_train == 1]) / (len(X_train) * 1.)) * 100)
)

我们可以看到正面点评和负面点评并不均衡。Train set has total 426308 entries with 21.91% negative, 78.09% positive。情感分类我们使用决策树算法(随机森林)并设置class_weight=balanced

定义一个计算准确率的函数:

def accuracy_summary(pipeline, X_train, y_train, X_test, y_test):
    sentiment_fit = pipeline.fit(X_train, y_train)
    y_pred = sentiment_fit.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    print("accuracy score: {0:.2f}%".format(accuracy * 100))
    return accuracy

在进行LSA的时,如果如果不做限制,我们会使用Text中所有出现过的单词作为特征,这样的计算量和效果并不佳。取而代之的是我们的获取TOP的单词作为特征。我们分别使用10000,20000,30000做测试:

cv = CountVectorizer()
rf = RandomForestClassifier(class_weight="balanced")
n_features = np.arange(10000, 30001, 10000)


def nfeature_accuracy_checker(vectorizer=cv, n_features=n_features, stop_words=None, ngram_range=(1, 1), classifier=rf):
    result = []
    print(classifier)
    for n in n_features:
        vectorizer.set_params(stop_words=stop_words, max_features=n, ngram_range=ngram_range)
        checker_pipeline = Pipeline([
            ('vectorizer', vectorizer),
            ('classifier', classifier)
        ])
        print("Test result for {} features".format(n))
        nfeature_accuracy = accuracy_summary(checker_pipeline, X_train, y_train, X_test, y_test)
        result.append((n, nfeature_accuracy))
    return result


tfidf = TfidfVectorizer()
feature_result_tgt = nfeature_accuracy_checker(vectorizer=tfidf, ngram_range=(1, 3))

我们可以看到30000特征的时候准确率是最高的。我们可以查看更为详细的指标:

cv = CountVectorizer(max_features=30000, ngram_range=(1, 3))
pipeline = Pipeline([
    ('vectorizer', cv),
    ('classifier', rf)
])
sentiment_fit = pipeline.fit(X_train, y_train)
y_pred = sentiment_fit.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['negative', 'positive']))

使用卡方检验选择特征。我们计算所有特征的卡方得分,并将TOP 20进行可视化。

tfidf = TfidfVectorizer(max_features=30000, ngram_range=(1, 3))
X_tfidf = tfidf.fit_transform(df.Text)
y = df.Positivity
chi2score = chi2(X_tfidf, y)[0]

plt.figure(figsize=(12, 8))
scores = list(zip(tfidf.get_feature_names(), chi2score))
chi2 = sorted(scores, key=lambda x: x[1])
topchi2 = list(zip(*chi2[-20:]))
x = range(len(topchi2[1]))
labels = topchi2[0]
plt.barh(x, topchi2[1], align='center', alpha=0.5)
plt.plot(topchi2[1], x, '-o', markersize=5, alpha=0.8)
plt.yticks(x, labels)
plt.xlabel('$\chi^2$')
plt.show()

潜在语义分析LSA的进化

LSA

LSA 方法快速且高效,但它也有一些主要缺点:

  • 缺乏可解释的嵌入(我们并不知道主题是什么,其成分可能积极或消极,这一点是随机的)
  • 需要大量的文件和词汇来获得准确的结果
  • 表征效率低

pLSA

pLSA,即概率潜在语义分析,采取概率方法替代 SVD 以解决问题。其核心思想是找到一个潜在主题的概率模型,该模型可以生成我们在文档-术语矩阵中观察到的数据。特别是,我们需要一个模型 P(D,W),使得对于任何文档 d 和单词 w,P(d,w) 能对应于文档-术语矩阵中的那个条目。

主题模型的基本假设:每个文档由多个主题组成,每个主题由多个单词组成。pLSA 为这些假设增加了概率自旋:

  • 给定文档 d,主题 z 以 P(z|d) 的概率出现在该文档中
  • 给定主题 z,单词 w 以 P(w|z) 的概率从主题 z 中提取出来

从形式上看,一个给定的文档和单词同时出现的联合概率是:

$$P(D,W)=P(D)\sum_{Z}P(Z|D)P(W|Z)$$

直观来说,等式右边告诉我们理解某个文档的可能性有多大;然后,根据该文档主题的分布情况,在该文档中找到某个单词的可能性有多大。在这种情况下,P(D)、P(Z|D)、和 P(W|Z) 是我们模型的参数。P(D) 可以直接由我们的语料库确定。P(Z|D) 和 P(W|Z) 利用了多项式分布建模,并且可以使用期望最大化算法(EM)进行训练。EM 无需进行算法的完整数学处理,而是一种基于未观测潜变量(此处指主题)的模型找到最可能的参数估值的方法。有趣的是,P(D,W) 可以利用不同的的 3 个参数等效地参数化:

$$P(D,W)=\sum_{Z}P(Z)P(Z|D)P(W|Z)$$

可以通过将模型看作一个生成过程来理解这种等价性。在第一个参数化过程中,我们从概率为 P(d) 的文档开始,然后用 P(z|d) 生成主题,最后用 P(w|z) 生成单词。而在上述这个参数化过程中,我们从 P(z) 开始,再用 P(d|z) 和 P(w|z) 单独生成文档。

这个新参数化方法非常有趣,因为我们可以发现 pLSA 模型和 LSA 模型之间存在一个直接的平行对应关系:

其中,主题 P(Z) 的概率对应于奇异主题概率的对角矩阵,给定主题 P(D|Z) 的文档概率对应于文档-主题矩阵 U,给定主题 P(W|Z) 的单词概率对应于术语-主题矩阵 V。

尽管 pLSA 看起来与 LSA 差异很大、且处理问题的方法完全不同,但实际上 pLSA 只是在 LSA 的基础上添加了对主题和词汇的概率处理。pLSA 是一个更加灵活的模型,但仍然存在一些问题,尤其表现为:

  • 因为没有参数来给 P(D) 建模,所以不知道如何为新文档分配概率
  • pLSA 的参数数量随着我们拥有的文档数线性增长,因此容易出现过度拟合问题

我们将不会考虑任何 pLSA 的代码,因为很少会单独使用 pLSA。一般来说,当人们在寻找超出 LSA 基准性能的主题模型时,他们会转而使用 LDA 模型。LDA 是最常见的主题模型,它在 pLSA 的基础上进行了扩展,从而解决这些问题。

LDA

LDA 即潜在狄利克雷分布,是 pLSA 的贝叶斯版本。它使用狄利克雷先验来处理文档-主题和单词-主题分布,从而有助于更好地泛化。我们可以对狄利克雷分布其做一个简短的概述:即,将狄利克雷视为「分布的分布」。本质上,它回答了这样一个问题:「给定某种分布,我看到的实际概率分布可能是什么样子?」考虑比较主题混合概率分布的相关例子。假设我们正在查看的语料库有着来自 3 个完全不同主题领域的文档。如果我们想对其进行建模,我们想要的分布类型将有着这样的特征:它在其中一个主题上有着极高的权重,而在其他的主题上权重不大。如果我们有 3 个主题,那么我们看到的一些具体概率分布可能会是:

  • 混合 X:90% 主题 A,5% 主题 B,5% 主题 C
  • 混合 Y:5% 主题 A,90% 主题 B,5% 主题 C
  • 混合 Z:5% 主题 A,5% 主题 B,90% 主题 C

如果从这个狄利克雷分布中绘制一个随机概率分布,并对单个主题上的较大权重进行参数化,我们可能会得到一个与混合 X、Y 或 Z 非常相似的分布。我们不太可能会抽样得到这样一个分布:33%的主题 A,33%的主题 B 和 33%的主题 C。本质上,这就是狄利克雷分布所提供的:一种特定类型的抽样概率分布法。我回顾一下 pLSA 的模型:

在 pLSA 中,我们对文档进行抽样,然后根据该文档抽样主题,再根据该主题抽样一个单词。以下是 LDA 的模型:

根据狄利克雷分布 Dir(α),我们绘制一个随机样本来表示特定文档的主题分布或主题混合。这个主题分布记为θ。我们可以基于分布从θ选择一个特定的主题 Z。

接下来,从另一个狄利克雷分布 Dir(),我们选择一个随机样本来表示主题 Z 的单词分布。这个单词分布记为φ。从φ中,我们选择单词 w。

从形式上看,从文档生成每个单词的过程如下(注意,该算法使用 c 而不是 z 来表示主题):

通常而言,LDA 比 pLSA 效果更好,因为它可以轻而易举地泛化到新文档中去。在 pLSA 中,文档概率是数据集中的一个固定点。如果没有看到那个文件,我们就没有那个数据点。然而,在 LDA 中,数据集作为训练数据用于文档-主题分布的狄利克雷分布。即使没有看到某个文件,我们可以很容易地从狄利克雷分布中抽样得来,并继续接下来的操作。

LDA 无疑是最受欢迎(且通常来说是最有效的)主题建模技术。它在 gensim 当中可以方便地使用:

from gensim.corpora.Dictionary import load_from_text, doc2bow
from gensim.corpora import MmCorpus
from gensim.models.ldamodel import LdaModel

document = "This is some document..."

# load id->word mapping (the dictionary)
id2word = load_from_text('wiki_en_wordids.txt')

# load corpus iterator
mm = MmCorpus('wiki_en_tfidf.mm')

# extract 100 LDA topics, updating once every 10,000
lda = LdaModel(corpus=mm, id2word=id2word, num_topics=100, update_every=1, chunksize=10000, passes=1)

# use LDA model: transform new doc to bag-of-words, then apply lda
doc_bow = doc2bow(document.split())
doc_lda = lda[doc_bow]

# doc_lda is vector of length num_topics representing weighted presence of each topic in the doc

通过使用 LDA,我们可以从文档语料库中提取人类可解释的主题,其中每个主题都以与之关联度最高的词语作为特征。例如,主题 2 可以用诸如「石油、天然气、钻井、管道、楔石、能量」等术语来表示。此外,在给定一个新文档的条件下,我们可以获得表示其主题混合的向量,例如,5%的主题 1,70% 的主题 2,10%的主题 3 等。通常来说,这些向量对下游应用非常有用。

深度学习中的 LDA:lda2vec

那么,这些主题模型会将哪些因素纳入更复杂的自然语言处理问题中呢?我们谈到能够从每个级别的文本(单词、段落、文档)中提取其含义是多么重要。在文档层面,我们现在知道如何将文本表示为主题的混合。在单词级别上,我们通常使用诸如 word2vec 之类的东西来获取其向量表征。 lda2vec是 word2vec 和 LDA 的扩展,它共同学习单词、文档和主题向量。

lda2vec 专门在 word2vec 的 skip-gram 模型基础上建模,以生成单词向量。skip-gram 和 word2vec 本质上就是一个神经网络,通过利用输入单词预测周围上下文词语的方法来学习词嵌入。

通过使用 lda2vec,我们不直接用单词向量来预测上下文单词,而是使用上下文向量来进行预测。该上下文向量被创建为两个其它向量的总和:单词向量和文档向量。

单词向量由前面讨论过的 skip-gram word2vec 模型生成。而文档向量更有趣,它实际上是下列两个组件的加权组合:

  • 文档权重向量,表示文档中每个主题的「权重」(稍后将转换为百分比)
  • 主题矩阵,表示每个主题及其相应向量嵌入

文档向量和单词向量协同起来,为文档中的每个单词生成「上下文」向量。lda2vec 的强大之处在于,它不仅能学习单词的词嵌入(和上下文向量嵌入),还同时学习主题表征和文档表征。

参考链接 :

游戏排名算法:Elo、Glicko、TrueSkill

$
0
0

Elo等级分制度

Elo等级分制度(英语:Elo rating system)是指由匈牙利裔美国物理学家Arpad Elo创建的一个衡量各类对弈活动水平的评价方法,是当今对弈水平评估公认的权威标准,且被广泛用于国际象棋、围棋、足球、篮球等运动。网络游戏的竞技对战系统也采用此分级制度。

ELO等级分制度是基于统计学的一个评估棋手水平的方法。美国国际象棋协会在1960年首先使用这种计分方法。由于它比先前的方法更公平客观,这种方法很快流行开来。1970年国际棋联正式开始使用等级分制度。Elo模型原先采用正态分布。但是实践显明棋手的表现并非呈正态分布,所以现在的等级分计分系统通常使用的是逻辑分布。

两个选手(player)在排名系统的不同,可以用来预测比赛结果。两个具有相同排名(rating)的选手相互竞争时,不管哪一方获胜都会得到相同的得分(score)。如果一个选手的排名(rating)比他的对手高100分,则得64%;如果差距是200分,那么排名高的选手的期望得分(expected score)应为76%。(可以理解为,获胜的机率更高)

一个选手的Elo rating由一个数字表示,它的增减依赖于排名选手间的比赛结果。在每场游戏后,获胜者将从失利方获得分数。胜者和败者间的排名的不同,决定着在一场比赛后总分数的获得和丢失。在高排名选手和低排名选手间的系列赛中,高排名的选手按理应会获得更多的胜利。如果高排名选手获胜,那么只会从低排名选手处获得很少的排名分(rating point)。然而,如果低排名选分爆冷获胜(upset win),可以获得许多排名分。低排名选手在平局的情况下也能从高排名选手处获得少量的得分。这意味着该排名系统是自动调整的(self-correcting)。长期来看,一个选手的排名如果太低,应比排名系统的预测做得更好,这样才能获得排名分,直到排名开始反映出他们真正的实力。

计分方法

假设棋手A和B的当前等级分分别为$R_{A}$和$R_{B}$,则按Logistic distribution A对B的胜率期望值当为:

$$E_{A}=\frac {1}{1+10^{(R_{B}-R_{A})/400}}$$

类似B对A的胜率为:

$$E_{B}=\frac {1}{1+10^{(R_{A}-R_{B})/400}}$$

假如一位棋手在比赛中的真实得分$S_{A}$(胜=1分,和=0.5分,负=0分)和他的胜率期望值$E_{A}$不同,则他的等级分要作相应的调整。具体的数学公式为:

$$R_{A}^{\prime }=R_{A}+K(S_{A}-E_{A})$$

公式中$R_{A}$和$R_{A}^{\prime }$分别为棋手调整前后的等级分。在大师级比赛中{K通常为16。

例如,棋手A等级分为1613,与等级分为1573的棋手B战平。若K取32,则A的胜率期望值为$\frac {1}{1+10^{(1573-1613)/400}}$,约为0.5573,因而A的新等级分为1613 + 32*(0.5-0.5573) = 1611.166

国际足球等级分排名

世界足球Elo评级是由网站eloratings.net发布的男子国家联盟足球队的排名系统。它基于Elo评级系统,但也对足球运动修改了特定各种的变量。其中包括:净胜球的数量,比赛的重要程度,主场优势。这使得Elo评级方法可以用来对足球运动的球队进行排名,但目前还没有任何球队的官方,使用根据Elo系统算出的球队评级排名。

Elo评级背后的基本原则是将所有球队以一个联赛的形式进行赛后积分统计,球队不比赛等级分不会变动,这与国际足联计算世界排名的方式并不相同。Elo自己有一个复杂全面且有效的权重评分系统,这与国际足联计分系统中的第一步也不相同。另外在国际足联系统中,球队会在赛后计算后直接获得现有分数,而Elo系统中只是算出加减分数,然后再得出当前分数。

等级分的计算是基于以下公式:

$$R_{n}=R_{o}+KG(W-W_{e})$$

或者

$$P=KG(W-W_{e})$$

注解:

  • $R_{n}$ = 球队的新等级分
  • $R_o$ = 球队的旧等级分
  • K = 比赛的重要程度
  • G = 净胜球数
  • W =比赛结果
  • $W_{e}$ = 预期结果
  • P = 分数的变化

在计算球队的等级分时会将小数点四舍五入。

比赛的重要程度

比赛的权重指数由重要程度来衡量,这个指数反映了比赛的重要程度,指数越高比赛越重要。而这主要取决于该场比赛是属于什么类型的比赛。各主要赛事的重要程度指数如下表所示:

比赛类型指数 (K)
世界杯60
洲际锦标赛和洲际锦标赛决赛50
世界杯和洲际锦标赛的预选赛40
其他所有比赛30
友谊赛20

进球数

如果比赛是平局,或者是一球小胜:G=1

如果比赛赢了两个净胜球:$G=\frac {3}{2}$

如果比赛赢了三个净胜球或以上:这里的 N 指的是净胜球数 $G=\frac {11+N}{8}$

示例表:

净胜球K (G)系数
01
+11
+21.5
+31.75
+41.875
+52
+62.125
+72.25
+82.375
+92.5
+102.625

比赛结果

W是比赛的结果(胜一局为1,平一局为0.5,负一局为0)。注意:如果比赛拖进加时赛决胜负,那么加时赛的结果也会被计算在内。如果比赛由点球大战决定胜负,则比赛将会被判定为平局(W=0.5)。

比赛预期结果

$W_e$是比赛的预期结果,从下列公式得出:(比赛的结果符合预期为0.5):

$$W_{e}=\frac {1}{10^{-dr/400}+1}$$

这里的dr是评级分的差额(为主队增加100分)。dr为0时获得0.5、为120时,高排名的队伍为0.666,低排名的队伍为0.334、为800时,高排名的队伍为0.99,低排名的队伍为0.01。

国际足联女子世界排名

另一种更为被大众所熟知的足球排名:国际足联世界排名则不是基于Elo系统,而是由国际足球管理机构使用自己官方的国家球队评级系统进行计算。但是国际足联女子世界排名系统却采用了一种修改后的Elo系统计算公式。

计分规则

$$R_{aft}=R_{bef}+K(S_{act}-S_{exp})$$

$$S_{exp}=\frac {1}{1+10^{-x/2}}$$

$$x=\frac {R_{bef}-O_{bef}\pm H}{c}$$

所有球队的平均积分约为1300分。顶级国家通常超过2000分。为了进行排名,一支球队必须与正式排名的球队至少打过5场比赛,并且已经没有超过18个月停赛。即使球队没有正式排名,他们的积分评分也会保持不变,直到他们进行下一场比赛。

实质比赛结果

实际结果的主要组成部分是球队是赢球,输球还是平局,但球差也被考虑在内。

如果比赛结果为赢家和输家,输家将获得由附表给出的百分比,结果总是小于或等于20%(对于大于零的目标差异)。其结果是基于目标差异和他们得分的目标数量。剩下的百分数奖励给获胜者。例如,2-1场比赛的比赛结果分别为84%-16%,4-3场比赛的比赛结果为82%-18%,8-3场比赛的比赛结果为96.2%-3.8%。因此,即使他们赢得比赛,球队也有可能失分,假设他们没有赢得足够的胜利。

如果比赛以平局结束,球队将获得相同的结果,但数量取决于得分目标,因此结果不一定总和为100%。例如,0-0平局每队获得47%的收入,1-1平局每场获得50%,4-4平局每场获得52.5%。

进球

以下是从非胜者的角度(落败或和局)。获胜队伍的因素最多可达100%。

进球差
0123456 /+
非胜队的进球数给予比例(百分比)
0471584321
150168.94.83.72.61.5
251179.85.64.43.22
3521810.76.45.13.82.5
452.51911.67.25.84.43
5532012.586.553.5

胜者可以获得

进球差
123456 /+
落败队伍的进球数给予比例(百分比)
0859296979899
18491.195.296.397.498.5
28390.294.495.696.898
38289.393.694.996.297.5
48188.492.894.295.697
58087.59293.59596.5

中立场或主客场

在历史统计上,主队获得66%的积分,而客队获得34%的积分。

这也有助于定义缩放常数c,其值为200。除了导致预期结果差异为64%-36%的100点差异外,还导致会有300积分的差异,导致预期结果为85%-15%。

比赛系数

重要比赛重要比赛系数(M)K-value
国际足联女子世界杯比赛460
女子奥运足球比赛460
女子世界杯外围赛345
女子奥运会外围赛345
女子洲区决赛圈345
女子洲区外围赛230
两队间前10友谊赛230
友谊赛115

国际足联世界排名

2018年6月10日,国际足联公布了最新的排名系统:

$$P=P_{\text{before}}+I(W-W_{e})$$

其中:

  • $P_{before}$ – 球队比赛前的积分
  • I – 赛事重要性系数:
    • 05 – 于国际赛期期间以外的友谊赛
    • 10 – 于国际赛期期间之内的友谊赛
    • 15 – 国家联赛赛事(小组赛)
    • 25 – 国家联赛赛事(附加战平决赛)
    • 25 – 洲际赛预选赛赛事、世界杯预选赛
    • 35 – 洲际赛决赛圈赛事(四分之一决赛阶段以前)
    • 40 – 洲际赛决赛圈赛事(四分之一决赛阶段以后)
    • 50 – 国际足联世界杯赛事(四分之一决赛阶段以前)
    • 60 – 国际足联世界杯赛事(四分之一决赛阶段以后)
  • W – 比赛成绩:(假如一场赛事最终分出胜负,但仍然需要进行点球大战(意即两回合中的次回合赛事),这会被视为一场常规赛的胜负,而点球大战成绩不会计算。)
    • 0 – 于常规时间或加时赛后落败
    • 5 – 战平或于点球大战落败
    • 75 – 于点球大战胜出
    • 1 – 于常规时间或加时赛后胜出
  • We – 赛事之预计赛果:$W_{e}=\frac {1}{10^{-{\frac {dr}{600}}}+1}$,其中 dr 是两支球队之间比赛前的评分差

Glicko评分系统

Glicko评分系统(英文:Glicko rating system)及Glicko-2评分系统(英文:Glicko-2 rating system)是评估选手在比赛中(如国际象棋及围棋)的技术能力方法之一。此方法由马克·格利克曼发明,原为国际象棋评分系统打造,后作为等级分评分系统的改进版本广泛应用。格里克曼在此算法中的主要贡献是“评分可靠性”(Ratings Reliability,简称RD),即评分标准差(Ratings Deviation)。

Glicko与Glicko-2评分系统被发表至公有领域。诸多在线游戏服务器(如《Pokémon Showdown》、《Lichess》、《自由互联网国际象棋服务器》、《Chess.com》、《在线围棋服务器 (页面存档备份,存于互联网档案馆)》[1]、《反恐精英:全球攻势》、《军团要塞2》、《刀塔霸业》、《激战2》、《Splatoon 2》及《皇舆争霸》)和多个竞技性编程比赛都采用此种评分方法。[2]Glicko所使用的算法可在其网站上找到。

算法中,评分可靠性用于测量选手的评分,一评分可靠性(评分标准差)相当于一标准差。举个例子,一名评分为1500分的选手,其评分可靠性为50,表示有95%的可能性这名选手的真实实力约在1400至1600分(1500分的两个标准差)之间。选手的实力区间需增加并减去评分中的两个评分标准差来计算。在比赛结束后,选手的实力评分的波动根据评分标准差来计算:当选手的评分标准差较低(选手的评分已较为准确)或其对手的评分标准差较高时(对手的真实实力无法确定)时,选手的评分波动也较小。评分标准差将在比赛后减小,但将在一段时间不活跃后渐渐增大。

Glicko-2是Glicko评分系统的改进版本,引进了评分挥发度$\sigma$(Rating Volatility)的概念。澳大利亚国际象棋联盟采用稍加修改版的Glicko-2评分系统。

Glicko

Elo系统的问题在于无法确定选手评分的可信度,而Glicko系统正是针对此进行改进。假设两名评分均为1700的选手A、B在进行一场对战后A获得胜利,在美国国际象棋联赛的Elo系统下,A选手评分将增长16,对应地B选手评分将下降16。但是加入A选手是已经很久没玩,但B选手每周都会玩,那么在上述情况下A选手的1700评分并不能十分可信地用于评定其实力,而B选手的1700评分则更为可信。

虽然很多情况下并不是这么极端,但我觉得把选手评分的可信度考虑进入是很有必要的。因此Glicko系统扩展了Elo,将不再是仅计算选手评分(可以视为选手实力的“最佳猜测”),还加入了“评分误差”(RD,ratings deviation),从统计术语的概念来说,RD用于衡量一个评分的不确定度(RD值越高,评分越不可信)。高RD值意味着选手并不频繁地进行对战,或者该选手仅进行了很少次数的对战,而低RD值说明选手会很经常地进行对抗比赛。

在Glicko 系统中,选手的评分仅根据对战的结果而改变,但其RD值改变同事取决于游戏结果和未进行游戏的时间长度。该系统的一个特征是游戏的结果经常会减少选手的RD值,而未进行对战的时间则经常会增长选手的RD值。造成这个现象的原因是因为选手玩的局数越多,关于选手能力的信息就学习到越多,评分也就越真实;而随着时间流失,我们对玩家实力就越不确定,反映在RD值上就是增长。

一个很有趣的发现是在Glicko系统中,双方评分的变化并不像Elo那样经常是相同的。例如A选手的评分增长了X,在Elo系统中对手B的评分会减少X,而在Glicko系统中并非如此。实际上,在Glicko中,对手B的评分减少取决于双方的RD值。

由于Glicko系统会同时用评分和RD值、以区间的形式评定选手实力,因此相较于仅使用评分更具有实际意义。此处应用95%置信区间,那么区间下限是选手评分减去2倍的RD值,区间上限是选手评分加上2倍的RD值。例如一个选手的评分是1850、RD值是50,那么他的实际实力区间为1750~1950。选手的RD值越小,该区间越窄,也就是说我们有95%的把握可以确定选手的实力在一个较小的区间值。

为了应用该算法,我们需要对发生在同一个“评分周期(rating period)”的所有游戏进行计算。一个评分周期可以长达数月,也可以短到一分钟。在前面的例子中,选手的评分和RD值在评分周期的一开始是已知的,对战的结果是可观测的,那么在评分周期结束时就可以根据计算更新选手的评分和RD值(同时该值可以作为下一评分周期的前置评分和RD)。当每个选手在评分周期中稳定地进行5~10局对战时,Glicko系统表现得最好。评分周期的时间长度由相关人员自行设定。

若选手没有评分,则其评分通常被设为1500,评分标准差为350。

测算标准差

新的评分标准差RD可使用旧的评分标准差$RD_{0}$计算:

$$RD=\min ({\sqrt {{RD_{0}}^{2}+c^{2}t}},350)$$

t为自上次比赛至现在的时间长度(评分期),350则是新选手的评分标准差。若选手在一个评分期间内进行了多场比赛,此算法会将进行的比赛作为一场看待。评分期根据选手进行比赛的频繁程度,可能长至七个月,短至几分钟。常数c根据选手在特定时间段内的技术不确定性计算而来,计算方法可能通过数据分析,或是估算选手的评分标准差将在什么时候达到未评分选手的评分标准差得来。若一名选手的评分标准差将在100个评分期间内达到350的不确定度,则评分标准差为50的玩家的常数c可通过解$350=\sqrt {50^{2}+100c^{2}}$的方式计算而来。或$c=\sqrt {(350^{2}-50^{2})/100}\approx 34.6$

测算新评分

在经过m场比赛后,选手的新评分可通过下列等式计算:

$$r=r_{0}+{\frac {q}{{\frac {1}{RD^{2}}}+{\frac {1}{d^{2}}}}}\sum _{i=1}^{m}{g(RD_{i})(s_{i}-E(s|r_{0},r_{i},RD_{i}))}$$

其中:

  • $g(RD_{i})=\frac {1}{\sqrt {1+{\frac {3q^{2}(RD_{i}^{2})}{\pi ^{2}}}}}$
  • $E(s|r,r_{i},RD_{i})=\frac {1}{1+10^{\left({\frac {g(RD_{i})(r_{0}-r_{i})}{-400}}\right)}}$
  • $q=\frac {\ln(10)}{400}=0.00575646273$
  • $d^{2}=\frac{1}{q^{2}\sum _{i=1}^{m}{(g(RD_{i}))^{2}E(s|r_{0},r_{i},RD_{i})(1-E(s|r_{0},r_{i},RD_{i}))}}$
  • $r_{i}$表示选手个人的评分;
  • $s_{i}$表示每场比赛后的结果。胜利为1,平局为$\frac {1}{2}$,失败为0。

测算新评分标准差

原先用于计算评分标准差的函数应增大标准差值,进而反应模型中一定非观察时间内,玩家的技术不确定性的增长。随后,评分标准差将在几场游戏后更新:

$$RD’=\sqrt {({\frac {1}{RD^{2}}}+{\frac {1}{d^{2}}})^{-1}}$$

Glicko-2

Glicko-2 系统中的每个玩家都有一个评分 r、一个评分偏差 RD 和一个评分波动率$\sigma$。波动性度量表明玩家评级的预期波动程度。当球员表现不稳定时(例如,当球员在稳定一段时间后取得了异常强劲的成绩)时,波动性衡量指标较高,而当球员表现稳定时,波动性衡量指标较低。与最初的 Glicko 系统一样,通常以间隔的形式总结球员的实力(而不仅仅是报告评级)。一种方法是报告 95% 的置信区间。区间中的最小值是玩家的评分减去两倍的RD,最高值是玩家的评分加上两倍的RD。因此,例如,如果某个玩家的评分为 1850,RD 为 50,则该区间将从 1750 到 1950。那么我们可以说我们有 95% 的把握认为该玩家的实际实力在 1750 到 1950 之间。当 a球员的 RD 较低,区间会很窄,因此我们有 95% 的信心认为球员的实力处于较小的数值区间。波动率度量没有出现在这个区间的计算中。

计算方法

为了应用评级算法,我们将“评级期”内的一组游戏视为同时发生。玩家在评级期开始时会有评级、RD 和波动率,将观察游戏结果,然后在评级期结束时计算更新的评级、RD 和波动率(然后将其用作预-后续评级期的期间信息)。 Glicko-2 系统在评级周期中的游戏数量中等到大时效果最佳,例如在评级周期中每个玩家平均至少玩 10-15 场游戏。评级期的时间长度由管理员自行决定。 Glicko-2 的评分量表与原始 Glicko 系统的评分量表不同。但是,很容易在两个尺度之间来回切换。以下步骤假设评级是在原始 Glicko 量表上,但公式会转换为 Glicko-2 量表,然后在最后转换回 Glicko。

步骤 1. 在评分期开始时确定每个玩家的评分和 RD。 系统常数$\tau$限制了波动率随时间的变化,需要在应用系统之前设置。 合理的选择介于 0.3 和 1.2 之间,但应测试系统以确定哪个值会导致最大的预测准确性。 较小的 $\tau$ 值可防止波动性度量发生大量变化,从而防止基于非常不可能的结果的评级发生巨大变化。如果预计 Glicko-2 的应用涉及极不可能的游戏结果集合,那么 $\tau$ 应该设置为一个小值,甚至小到,比如,$\tau$ = 0.2。

  • 如果玩家未评级,将评级设置为 1500,将 RD 设置为 350。将玩家的波动率设置为06(该值取决于特定的应用程序)。
  • 否则,使用玩家最近的评分、RD 和波动率$\sigma$

步骤 2. 对于每个玩家,将评分和 RD 转换为 Glicko-2 量表:

$$\mu = (r – 1500)/173.7178$$

$$\phi  = RD/173.7178$$

$\sigma$的值,即波动率,不会改变。

我们现在想用 (Glicko-2) 评分 $\mu$、评分偏差$\phi$和波动率$\sigma$更新玩家的评分。他与 m 个等级为$\mu_1,…\mu_m$的对手对战。评分偏差$\phi _1,…,\phi _m$。 让$s_1,…,s_m$是对每个对手的得分(0 表示失败,0.5 表示平局,1 表示胜利)。 对手的波动率与计算无关。

步骤 3. 计算v。这是仅基于比赛结果的球队/球员评分的估计方差。

$$v=[\sum_{j=1}^{m} g(\phi_{j})^{2} \mathrm{E}(\mu, \mu_{j}, \phi_{j})\{1-\mathrm{E}(\mu, \mu_{j}, \phi_{j})\}]^{-1}$$

其中:

$$g(\phi) =\frac{1}{\sqrt{1+3 \phi^{2} / \pi^{2}}}$$

$$\mathrm{E}(\mu, \mu_{j}, \phi_{j}) =\frac{1}{1+\exp (-g(\phi_{j})(\mu-\mu_{j}))}$$

步骤 4.通过将前期评分与仅基于游戏结果的表现评分进行比较,计算$\Delta$,即评分的估计改进。

$$\Delta=v \sum_{j=1}^{m} g(\phi_{j})\{s_{j}-\mathrm{E}(\mu, \mu_{j}, \phi_{j})\}$$

g()和 E()在上一个步骤已经定义过了。

步骤 5.确定波动率的新值$\sigma ^{‘}$。 这个计算需要迭代。

1.让$a=\ln(\sigma^2)$,并定义

$$f(x)=\frac{e^{x}(\Delta^{2}-\phi^{2}-v-e^{x})}{2(\phi^{2}+v+e^{x})^{2}}-\frac{(x-a)}{\tau^{2}}$$

此外,定义收敛容差$\varepsilon $。 值 $\varepsilon  = 0.000001$ 是一个足够小选择。

2.设置迭代算法的初始值。

设置$A=a=\ln(\sigma^2)$

如果$\Delta ^2 > \phi ^2 + v$,设置$B=\ln(\Delta ^2-\phi ^2-v)$。

如果$\Delta ^2 \leq  \phi ^2 + v$,执行以下迭代:

  1. 让k=1
  2. 如果$f(a-k\tau )<0$,然后设置k=k+1,然后继续执行(2)

设置$B= a-k\tau$,选择值 A 和 B 来减小ln(\sigma^2)。

3.让$f_A = f(A)$和$f_B = f(B)$

4.当$|B-A|>\varepsilon$,执行以下步骤。

  • (a)让$C=A+(A-B)f_A/(f_B-f_A)$,让$f_C=f(C)$。
  • (b)如果$f_Cf_B<0$,然后设置$A \leftarrow B$和$f_A \leftarrow f_B$;否则,只需设置$f_A \leftarrow f_A/2$。
  • (c)设置$B \leftarrow C$ 和$f_B \leftarrow f_C$
  • (d)如果$|B-A| \leq \varepsilon$停止,否则重复以上三步。

5.一旦$|B-A| \leq \varepsilon$,设置$\sigma ^{‘} \leftarrow  e^{A/2}$

步骤 6.将评级偏差更新为新的预评级期值,\phi ^*。

$$\phi ^* = \sqrt{\phi ^2+\sigma ‘^{2}}$$

步骤 7.将评级和 RD 更新为新值$\mu ‘$和$\phi ‘$:

$$\phi^{\prime} =1 / \sqrt{\frac{1}{\phi^{* 2}}+\frac{1}{v}} $$

$$\mu^{\prime} =\mu+\phi^{\prime 2} \sum_{j=1}^{m} g(\phi_{j})\{s_{j}-\mathrm{E}(\mu, \mu_{j}, \phi_{j})\}$$

步骤 8.将评分和 RD 转换回原始比例:

$$r’=173.7178u’+1500$$

$$RD’=173.7178\phi ‘$$

请注意,如果玩家在评分期间未参加比赛,则仅适用第 6 步。 在这种情况下,玩家的评级和波动率参数保持不变,但 RD 根据:

$$\phi^{\prime}=\phi^{*}=\sqrt{\phi^{2}+\sigma^{2}}$$

TrueSkill

在电子竞技游戏中,特别是当有多名选手参加比赛的时候需要平衡队伍间的水平,让游戏比赛更加有意思。这样的一个参赛选手能力平衡系统通常包含以下三个模块:

  • 一个包含跟踪所有玩家比赛结果,记录玩家能力的模块。
  • 一个对比赛成员进行配对的模块。
  • 一个公布比赛中各成员能力的模块。

事实上目前已经有的游戏评分系统是Elo评分,但是Elo评分仅只是两名选手参加的游戏。TrueSkill系统是基于贝叶斯推断的评分系统,由微软研究院开发以代替传统Elo评分,并成功应用于Xbox Live自动匹配系统。TrueSkill评分系统是Glicko评分系统的衍伸,主要用于多人游戏中。TrueSkill评分系统考虑到了你水平的不确定性,综合考虑了玩家的胜率和可能的水平涨落。当玩家进行了更多的游戏后,即使你的胜率不变,系统也会因为对你的水平更加了解而改变对你的评分。

相较Elo评价系统,TrueSkill的优势在于:

  • 适用于复杂的组队形式,更具一般性。
  • 置于更完善的建模体系,容易扩展。
  • 继承了贝叶斯建模的好的特点,如模型选择等。

怎样进行能力计算

TrueSkill排名系统是针对玩家能力进行设计的,以克服现有排名系统的局限性,确保比赛双方的公平性,可以在联赛中作为排名系统使用。它为玩家排名使用的为贝叶斯定理。 系统的特点是假设每一个玩家的能力不是固定的,其能力水平的表现为一个钟型曲线(正态分布或高斯分布)。

绿色区域15~20代表了Ranking System对的评分。可以看出系统的评分是比较保守的。$\sigma$越小则越靠近$\mu$,相应的玩家的能力水平就较高。总的来说玩家的水平受“平均得分”和“玩家稳定性”综合影响。

由于TrueSkill排名系统使用高斯信仰分布来描述一个玩家的能力,也就意味着玩家的能力始终落在4倍的$\sigma$内(概率为99.993666%)。从微软追踪的65万玩家数据显示,有99.99%都落在了3倍的$\sigma$内。 有趣的是,TrueSkill排名系统可以使用1作为最初的不确定性做所有的计算,将相乘$\mu$和$\sigma$可以缩放到任何其他的范围。假设所有的计算都以初始值$\mu$=3和$\sigma$=1,如果一个玩家有50级,几乎所有的$\mu$的发生是在±3倍的初始$\sigma$,$\sigma$可得50/6 = 8.3。 两个玩家最大的区别在于$\mu$值得大小。假设$\sigma$相当,那么$\mu$高的玩家赢得机会就越大,这一原则也适用在TrueSkill排名系统。但并不表示$\mu$高的就一定会赢。在单个的配对比赛中,玩家的个人表现与玩家的能力是相当的,游戏结果也是有个人表现决定的。因此,可以认为能力的一个玩家在TrueSkill的排名是在大量游戏中的平均表现。个人表现的变化原则是能力表现的一个参数。

怎样更新能力值

TrueSkill排名系统只会根据比赛结果更新$\mu$和$\sigma$,它假设的情况为一个玩家的表现围绕着他的能力水品进行变化,如果一个玩家玩一个基于点数的游戏,他战胜了所有的其他10个对手和他和战胜了另外一场比赛只有一个对手的积分是一样的,但是这样两场比赛确实反映了不同选手的能力情况。通常会使$\sigma$下降。在计算一场新的比赛结果之前,TrueSkill排名系统会计算比赛的排名与选手在比赛前的排名的变化情况。排名的变化最终影响了玩家技能的不确定性$\sigma$。这个参数可以被TrueSkill用来记录玩家的技能的变化。并且$\sigma$永远不可能为0。

下面这张表格来自微软研究院,此表格给出了8个新手在参与一个8人游戏后$\mu$和$\sigma$的变化。

这里有个很有意思的现象:注意第四名Darren和第五名Eve,他们的$\sigma$是最小的,换句话说系统认为他们能力的可能起伏是最小的。这是因为通过这场游戏我们对他们了解得最多:他们赢了3/4个人,也输给了4/3个人。而对于第一名Alice,我们只知道她赢了7个人。 如果想知道更详细的定量分析可以先考虑最简单的两人游戏情况。

$$\begin{aligned}&\mu_{winner} \longleftarrow \mu_{winner}+\frac{\sigma_{winner}^{2}}{c} * v(\frac{\mu_{winner}-\mu_{loser }}{c}, \frac{\varepsilon}{c}) \\&\mu_{loser} \longleftarrow \mu_ {loser}-\frac{\sigma_{loser}^{2}}{c} * v(\frac{\mu_{winner}-\mu_{loser}}{c}, \frac{\varepsilon}{c}) \\&\sigma_{winner}^{2} \longleftarrow \sigma_{winner}^{2} *[1-\frac{\sigma_{winner}^{2}}{c} * w(\frac{\mu_{vinner}-\mu_{loser}}{c}, \frac{\varepsilon}{c}). \\&\sigma_{loser}^{2}<\sigma_{loser}^{2} * [1-\frac{\sigma_{loser}^{2}}{c} * w(\frac{\mu_{winner}-\mu_{loser}}{c}, \frac{\varepsilon}{c}). \\&c^{2}=2 \beta^{2}+\sigma_{winner}^{2}+\sigma_{loser}^{2}\end{aligned}$$

在上述的方程式中,唯一未知的就是选手的表现。另外还有就是游戏的模式。系数$\beta ^2$代表的是所有玩家的平均方差。v(.,.) 和w(.,.)是两个函数,比较复杂。$\varepsilon $是个与游戏模式有关的参数。 简而言之,你赢了$\mu$就增加,输了$\mu$减小;但不论输赢,$\sigma$都是在减小,所以有可能出现输了涨分的情况。

怎样进行选手匹配

势均力敌的对手能带来最精彩的比赛,所以当自动匹配对手时,系统会尽可能的为你安排可能与水平最为接近的玩家。TrueSkill评分系统采用了一个值域为(0,1)的函数来描述两个人是否势均力敌:结果越接近0代表差距越大,越接近1代表水平越接近。 假设有两个玩家A和B,他们的参数为$(\mu _{A},\sigma _{A})$和$(\mu _{B},\sigma _{B})$,则函数对这两个玩家的返回值为

$$e^{-\frac{(\mu_{A}-\mu_{B})^{2}}{2 c^{2}}} \sqrt{\frac{2 \beta^{2}}{c^{2}}}$$

c的值由如下公式给出

$$c^{2}=2 \beta^{2}+\mu_{A}^{2}+\mu_{B}^{2}$$

如果两人有较大几率被匹配在一起,光是平均值接近还不行(e指数上那一项),还得方差也比较接近才行(d)。

怎样创建能力排行榜

TrueSkill假设玩家的水平可以用一个正态分布来表示,而正态分布可以用两个参数:平均值和方差来完全描述。设Rank值为R,代表玩家水平的正态分布的两个参数平均值和方差分别为$\mu$和$\sigma$,则系统对玩家的评分即Rank值为 R=$\mu$-k*$\sigma$。k值越大则系统的评分越保守。在Xbox Live上,系统为每个玩家赋予的初值是$\mu = 25$以及 $\sigma = 25/3$,k=3。所以玩家的起始Rank值为R=25-3*25/3=0。

参考链接:

线性回归实战:波士顿房价预测

$
0
0

了解线性回归的原理后,为了更好的掌握相关的技能,需要进入实战,针对线性回归常见的方法有:Scikit和Statsmodels。

数据集的准备

美国波士顿房价的数据集是sklearn里面默认的数据集,sklearn内置的数据集都位于datasets子模块下。一共506套房屋的数据,每个房屋有13个特征值。

from sklearn.datasets import load_boston
boston_dataset = load_boston()

print(boston_dataset.keys())
print(boston_dataset.feature_names)
print(boston_dataset.DESCR)

输出内容:

dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])
['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO''B' 'LSTAT']
.. _boston_dataset:

Boston house prices dataset
---------------------------

**Data Set Characteristics:**  

    :Number of Instances: 506 

    :Number of Attributes: 13 numeric/categorical predictive. Median Value (attribute 14) is usually the target.

    :Attribute Information (in order):
        - CRIM     per capita crime rate by town
        - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
        - INDUS    proportion of non-retail business acres per town
        - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
        - NOX      nitric oxides concentration (parts per 10 million)
        - RM       average number of rooms per dwelling
        - AGE      proportion of owner-occupied units built prior to 1940
        - DIS      weighted distances to five Boston employment centres
        - RAD      index of accessibility to radial highways
        - TAX      full-value property-tax rate per $10,000
        - PTRATIO  pupil-teacher ratio by town
        - B        1000(Bk - 0.63)^2 where Bk is the proportion of black people by town
        - LSTAT    % lower status of the population
        - MEDV     Median value of owner-occupied homes in $1000's

    :Missing Attribute Values: None

    :Creator: Harrison, D. and Rubinfeld, D.L.

This is a copy of UCI ML housing dataset.
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/


This dataset was taken from the StatLib library which is maintained at Carnegie Mellon University.

The Boston house-price data of Harrison, D. and Rubinfeld, D.L. 'Hedonic
prices and the demand for clean air', J. Environ. Economics & Management,
vol.5, 81-102, 1978.   Used in Belsley, Kuh & Welsch, 'Regression diagnostics
...', Wiley, 1980.   N.B. Various transformations are used in the table on
pages 244-261 of the latter.

The Boston house-price data has been used in many machine learning papers that address regression
problems.   
     
.. topic:: References

   - Belsley, Kuh & Welsch, 'Regression diagnostics: Identifying Influential Data and Sources of Collinearity', Wiley, 1980. 244-261.
   - Quinlan,R. (1993). Combining Instance-Based and Model-Based Learning. In Proceedings on the Tenth International Conference of Machine Learning, 236-243, University of Massachusetts, Amherst. Morgan Kaufmann.

      CRIM    ZN  INDUS  CHAS    NOX  ...  RAD    TAX  PTRATIO       B  LSTAT
0  0.00632  18.0   2.31   0.0  0.538  ...  1.0  296.0     15.3  396.90   4.98
1  0.02731   0.0   7.07   0.0  0.469  ...  2.0  242.0     17.8  396.90   9.14
2  0.02729   0.0   7.07   0.0  0.469  ...  2.0  242.0     17.8  392.83   4.03
3  0.03237   0.0   2.18   0.0  0.458  ...  3.0  222.0     18.7  394.63   2.94
4  0.06905   0.0   2.18   0.0  0.458  ...  3.0  222.0     18.7  396.90   5.33

字段解释:

  • CRIM: 城镇人均犯罪率
  • ZN: 住宅用地所占比例
  • INDUS: 城镇中非住宅用地所占比例
  • CHAS: 虚拟变量,用于回归分析
  • NOX: 环保指数
  • RM: 每栋住宅的房间数
  • AGE: 1940 年以前建成的自住单位的比例
  • DIS: 距离 5 个波士顿的就业中心的加权距离
  • RAD: 距离高速公路的便利指数
  • TAX: 每一万美元的不动产税率
  • PTRATIO: 城镇中的教师学生比例
  • B: 城镇中的黑人比例
  • LSTAT: 地区中有多少房东属于低收入人群
  • MEDV: 自住房屋房价中位数(也就是均价)
boston = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)
boston['MEDV'] = boston_dataset.target

plt.figure(figsize=(10,8))
sns.distplot(boston['MEDV'], bins=30)
plt.show()

correlation_matrix = boston.corr().round(2)
sns.heatmap(data=correlation_matrix, annot=True)
plt.show()

可以看到与房价相关度比较高的字段为’LSTAT’和’RM’。绘制图片,看是否存在线性关系:

plt.figure(figsize=(20, 5))

features = ['LSTAT', 'RM']
target = boston['MEDV']

for i, col in enumerate(features):
    plt.subplot(1, len(features) , i+1)
    x = boston[col]
    y = target
    plt.scatter(x, y, marker='o')
    plt.title(col)
    plt.xlabel(col)
    plt.ylabel('MEDV')

数据准备:

from sklearn.model_selection import train_test_split

X = boston[['LSTAT','RM']]
y = boston['MEDV']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state=5)

sklearn.linear_model.LinearRegression

示例代码:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score

from sklearn.datasets import load_boston

boston_dataset = load_boston()

boston = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)
boston['MEDV'] = boston_dataset.target

X = boston[['LSTAT', 'RM']]
y = boston['MEDV']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=5)

lr = LinearRegression()
lr.fit(X_train, y_train)
# 打印截距
print(lr.intercept_)
# 打印模型系数
print(lr.coef_)
y_test_predict = lr.predict(X_test)

rmse = np.sqrt(mean_squared_error(y_test, y_test_predict))
r2 = r2_score(y_test, y_test_predict)
print(rmse, r2)

StatsModels OLS

Statsmodels的OSL是回归模型中最常用的最小二乘法求解法。

import pandas as pd
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
import statsmodels.api as sm

boston_dataset = load_boston()

boston = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)
boston['MEDV'] = boston_dataset.target

X = boston[['LSTAT', 'RM']]
y = boston['MEDV']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=5)

result = sm.OLS(y_train, X_train).fit()
print(result.params)
print(result.summary())

这里需要重要介绍的是如何读懂报告。

                                 OLS Regression Results                                
=======================================================================================
Dep. Variable:                   MEDV   R-squared (uncentered):                   0.947
Model:                            OLS   Adj. R-squared (uncentered):              0.947
Method:                 Least Squares   F-statistic:                              3581.
Date:                Fri, 06 Aug 2021   Prob (F-statistic):                   6.67e-257
Time:                        16:07:31   Log-Likelihood:                         -1272.2
No. Observations:                 404   AIC:                                      2548.
Df Residuals:                     402   BIC:                                      2556.
Df Model:                           2                                                  
Covariance Type:            nonrobust                                                  
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
LSTAT         -0.6911      0.036    -19.367      0.000      -0.761      -0.621
RM             4.9699      0.081     61.521      0.000       4.811       5.129
==============================================================================
Omnibus:                      121.894   Durbin-Watson:                   2.063
Prob(Omnibus):                  0.000   Jarque-Bera (JB):              389.671
Skew:                           1.370   Prob(JB):                     2.42e-85
Kurtosis:                       6.954   Cond. No.                         4.70
==============================================================================

Notes:
[1] R² is computed without centering (uncentered) since the model does not contain a constant.
[2] Standard Errors assume that the covariance matrix of the errors is correctly specified.

如何解读报告?来看一些名词解释:

  • Variable: 就是因变量,dependent variable,也就是输入的y的名称。
  • Model:这里就是OLS。
  • Method:使用最小二乘法的方法确定参数
  • Date:模型生成的日期。
  • Time:模型生成的具体时间。
  • Observations:样本量,就是输入的数据量。
  • Df Residuals:残差自由度,即degree of freedom of residuals。
  • Df Model:模型自由度,degree of freedom of model,模型参数个数(不包含常量参数),对应于coef中的行数
  • Covariance Type:协方差类型。
  • R-squared:可决系数,说明估计的准确性。“可决系数”是通过数据的变化来表征一个拟合的好坏。其值=SSR/SST,SSR是Sum of Squares for Regression,SST是Sum of Squares for Total,这个值范围在[0, 1],其值越接近1,说明回归效果越好。
  • R-squared:利用奥卡姆剃刀原理,对R-squared进行修正。公式$R_{adj}^2=1-\frac{(n-1)(1-R^2}{n-p-1}$。其中n是样本数量(No. Observations),p是模型中变量的个数(Df Model)。
  • F-statistic:统计检验 F 统计量,值越大越能推翻原假设(原假设是“模型不是线性模型”),所以值过大,说明我们的模型是线性模型。F = Model Mean Square / Error Mean Square
  • Prob (F-statistic):F检验的 P值,这个值越小越能拒绝原假设,值越小越证明模型是线性显著的。
  • Log likelihood:对数似然。对数函数和似然函数具有同一个最大值点。取对数是为了方便计算极大似然估计,通常先取对数再求导,找到极值点。这个参数很少使用,可以不考虑。
  • AIC:其用来衡量拟合的好坏程度,一般选择AIC较小的模型。
  • BIC:贝叶斯信息准则,其比AIC在大数据量时,对模型参数惩罚得更多,所以BIC更倾向于选择参数少的简单模型。
  • coef:指自变量和常数项的系数
  • std err:系数估计的标准误差。
  • t:就是我们常用的t统计量,这个值越大越能拒绝原假设。
  • P>|t|:统计检验中的P值,这个值越小越能拒绝原假设。
  • [0.025, 0.975]:这个是置信度为95%的置信区间的下限和上限。
  • Omnibus:基于峰度和偏度进行数据正态性的检验,其常和Jarque-Bera检验一起使用。
  • Prob(Omnibus): 基于峰度和偏度进行数据正态性的检验概率
  • Durbin-Watson:检验残差中是否存在自相关,其主要通过确定两个相邻误差项的相关性是否为零来检验回归残差是否存在自相关。
  • Skewness:偏度,反映数据分布的非对称程度
  • Kurtosis:峰度,反映数据分布陡峭或平滑程度
  • Jarque-Bera(JB):基于峰度和偏度对数据正态性的检验
  • Prob(JB):Jarque-Bera(JB)检验的 P值
  • No.:多重共线性的检验,即检验变量之间是否存在精确相关关系或高度相关关系。

数据分析该知道的IP地址知识

$
0
0

第一次接触到IP,还是在十多年前使用统计系统时,当时的统计系统中有个指标是IP地址。即记录每天有多少不同的IP访问您的网站,在后来是自己搭建统计系统时涉及到对IP地址省份、城市、区域的解析。最近在推进风控项目时又有遇到,所以抽时间把相关的知识点做下简单的整理。

什么是IP地址?

IP地址(英语:IP Address,全称Internet Protocol Address)。当设备连接网络,设备将被分配一个IP地址,用作标识。通过IP地址,设备间可以互相通讯,如果没有IP地址,我们将无法知道哪个设备是发送方,无法知道哪个是接收方。IP地址有两个主要功能:标识设备或网络 和 寻址(英语:location addressing)。常见的IP地址分为 IPv4 与 IPv6 两大类,IP地址由一串数字组成。IPv4 由十进制数字组成,并以点分隔,如:172.16.254.1;IPv6 由十六进制数字组成,以冒号分割,如:2001:db8:0:1234:0:567:8:1。这里主要讲解的是IPv4。

IP地址表示方法:

  • 点分十进制(最常用的表示方法):11.3.31
  • 二进制记法:10000000 00001011 00000011 00011111
  • 十六进制记法:0X810B0BEF

在对IP进行数据存储的时候,我们经常会将IP地址的int型数值。

import ipaddress

print(int(ipaddress.IPv4Address('36.154.120.82')))
print(str(ipaddress.IPv4Address(614103122)))

IP地址分类

每一类地址都由两个固定长度的字段组成:

  • 一个字段是网络号 net-id,它标志主机(或路由器)所连接到的网络
  • 一个字段则是主机号 host-id,它标志该主机(或路由器)

由于近年来已经广泛使用无分类 IP 地址进行路由选择,A 类、B 类和 C 类地址的区分已成为历史[RFC 1812]。所以这里不用过于记忆,只需了解 IP 分类的最初标准。

子网掩码

子网掩码必须结合IP地址一起使用,子网掩码的作用是将某个IP地址划分成网络地址和主机地址两部分。并且子网掩码设置不是任意的,如果设置过大(范围扩大),有可能因为错误的判断使数据不能正确到达目的主机,导致网络传输错误;如果网掩码设置得过小,会增加缺省网关的负担,造成网络效率下降。

因此,如果网络的规模不超过254台主机,采用“255.255.255.0”作为子网掩码就可以了,现在多数的局域网都不会超过这个数字,所以这是最常用的IP地址子网掩码。

当一个网段的掩码为255.255.255.0的时候,通常IP地址的最后一位不可以是255或0。因为0是网段的网络地址,而255是网段的广播地址,不能分配给主机。那什么情况下可以是255或0,假设一个网段为155.23.0.0,它的掩码是255.255.254.0,那它的主机可用地址范围是155.23.0.1到155.23.1.254,即155.23.1.0和155.23.0.255这两个地址是可用的。

子网掩码决定了可用的主机数量有多少,以及ip是否在同一个网段,举两个例子:

  • 以网段168.0.0为例:当子网掩码为255.255.255.0时,可用IP为192.168.0.1-192.168.0.254(255为广播地址),子网掩码为255.255.0.0时,可用IP为192.168.0.0-192.168.255.254。
  • 以168.10.1和192.168.100.1为例:当子网掩码为255.255.255.0时,他们不在一个网段,子网掩码为255.255.0.0时,他们在同一个网段。所以多个IP地址是否在同一个网段以及该网段有多少个可用的IP地址,由子网掩码决定。

无分类编址CIDR

一个IP地址包含两部分:标识网络的前缀和紧接着的在这个网络内的主机地址。在之前的分类网络中,IP地址的分配把IP地址的32位按每8位为一段分开。这使得前缀必须为8,16或者24位。因此,可分配的最小的地址块有256(24位前缀,8位主机地址,2^8=256)个地址,而这对大多数企业来说太少了。大一点的地址块包含65536(16位前缀,16位主机,2^16=65536)个地址,而这对大公司来说都太多了。这导致不能充分使用IP地址和在路由上的不便,因为大量的需要单独路由的小型网络(C类网络)因在地域上分得很开而很难进行聚合路由,于是给路由设备增加了很多负担。

无类别域间路由是基于可变长子网掩码(VLSM)来进行任意长度的前缀的分配的。在RFC 950(1985)中有关于可变长子网掩码的说明。CIDR包括:

  • 指定任意长度的前缀的可变长子网掩码技术。遵从CIDR规则的地址有一个后缀说明前缀的位数,例如:168.0.0/16。这使得对日益缺乏的IPv4地址的使用更加有效。
  • 将多个连续的前缀聚合成超网,以及,在互联网中,只要有可能,就显示为一个聚合的网络,因此在总体上可以减少路由表的表项数目。聚合使得互联网的路由表不用分为多级,并通过VLSM逆转“划分子网”的过程。
  • 根据机构的实际需要和短期预期需要而不是分类网络中所限定的过大或过小的地址块来管理IP地址的分配的过程。

CIDR用可变长子网掩码 (VLSM,Variable Length Subnet Masking),根据各人需要来分配IP地址,而不是按照一个全网络约定的规则。所以,网络/主机的划分可以在地址内的任意位置进行。这个划分可以是递归进行的,即通过增加掩码位数,来使一部分地址被继续分为更小的部分。整个互联网现在都在使用CIDR/VLSM网络地址。除此之外,CIDR也应用在其他方面,尤其是大型私人网络。在普通大小的局域网里则较少应用,因为这些局域网一般使用私有网络。

如何理解可变长子网掩码?假设IP地址是192.168.1.0~192.168.1.255,使用255.255.255.0即可实现。但如果现在需要将这些IP分为四份,分配给不同的网络:

  • 168.1.0-192.168.1.63
  • 168.1.64-192.168.1.127
  • 168.1.128 -192.168.1.191
  • 168.1.192-192.168.1.255

用可变长子网掩码表示就是:

  • 168.1.0/26
  • 168.1.64/26
  • 168.1.128/26
  • 168.1.192/26

 

这里的/26,代表的是11111111-11111111-11111111-11000000,即26个1,转化为IP就是255.255.255.192。

192.168.1.0对应的二进制是:11000000.10101000.00000001.00000000,在子网掩码下,前面26位保持不变,终止IP为:11000000.10101000.00000001.00111111,即192.168.1.63

内网IP与公网IP

内网IP地址、外网IP地址这个概念并不是固定的,而是相对的。如果用私有IP、公网IP或者局域网IP、互联网IP来理解就容易多了。看网络习惯书籍无法理解很多原因是因为教科书太古老,不与时俱进造成的。几乎所有的教科书都会告诉大家私有IP有3种:A类10.0.0.0~10.255.255.255,B类172.16.0.0~172.31.255.255,C类192.168.0.0~192.168.255.255。

RFC1918 规定区块名IP地址区块IP数量分类网络 说明最大CIDR区块 (子网掩码)主机端位长
24位区块10.0.0.0 – 10.255.255.25516,777,216单个A类网络10.0.0.0/8 (255.0.0.0)24位
20位区块172.16.0.0 – 172.31.255.2551,048,57616个连续B类网络172.16.0.0/12 (255.240.0.0)20位
16位区块192.168.0.0 – 192.168.255.25565,536256个连续C类网络192.168.0.0/16 (255.255.0.0)16位

事实上远远不止:

Address Block                    Name                              RFC                       
0.0.0.0/8                        "This host on this network"       [RFC1122], section 3.2.1.3
10.0.0.0/8                       Private-Use                       [RFC1918]                 
100.64.0.0/10                    Shared Address Space              [RFC6598]                 
127.0.0.0/8                      Loopback                          [RFC1122], section 3.2.1.3
169.254.0.0/16                   Link Local                        [RFC3927]                 
172.16.0.0/12                    Private-Use                       [RFC1918]                 
192.0.0.0/24[2]                  IETF Protocol Assignments         [RFC6890], section 2.1    
192.0.0.0/29                     IPv4 Service Continuity Prefix    [RFC7335]                 
192.0.0.8/32                     IPv4 dummy address                [RFC7600]                 
192.0.0.9/32                     Port Control Protocol Anycast     [RFC-ietf-pcp-anycast-08] 
192.0.0.170/32, 192.0.0.171/32   NAT64/DNS64 Discovery             [RFC7050], section 2.2    
192.0.2.0/24                     Documentation (TEST-NET-1)        [RFC5737]                 
192.31.196.0/24                  AS112-v4                          [RFC7535]                 
192.52.193.0/24                  AMT                               [RFC7450]                 
192.88.99.0/24                   Deprecated (6to4 Relay Anycast)   [RFC7526]                 
192.168.0.0/16                   Private-Use                       [RFC1918]                 
192.175.48.0/24                  Direct Delegation AS112 Service   [RFC7534]                 
198.18.0.0/15                    Benchmarking                      [RFC2544]                 
198.51.100.0/24                  Documentation (TEST-NET-2)        [RFC5737]                 
203.0.113.0/24                   Documentation (TEST-NET-3)        [RFC5737]                 
240.0.0.0/4                      Reserved                          [RFC1112], section 4      
255.255.255.255/32               Limited Broadcast                 [RFC919], section 7 

另外,部分阿里云服务器使用路由跟踪显示的defense.gov (DoD Network) IP地址,其实是阿里使用了美国国防部的公网IP当内网IP用。原因是无法从公网访问美国国防部的这些IP段,所以不会造成IP混乱。

IP地址匮乏问题

中国所获得的IPv4地址情况:

  • 中国大陆IPv4地址总数为74,391,296个,合4A+111B+31C
  • 中国台湾IPv4地址总数为16,280,064个,合248B+106C
  • 中国香港IPv4地址总数为06,288,640个,合95B+245C
  • 中国澳门IPv4地址总数为00,144,640个,合2B+53C

不管是电脑、手机号是网站服务器都需要使用IP,以上的IP远远不够。这也就导致了我们再也不能像十几年前一样通过简单的统计IP量来统计用户量。

3G/4G网络下的IP地址

用手机本身的流量上网的话,IP是随着你手机信号连接的基站的变化而变化的。手机从基站接入,传递数据给基站,然后基站传递给上层BSC,PCU从中剥离出分组业务数据,传递给SGSN,最后通过SGSN经由GGSN这个网关将数据汇入到茫茫internet中。

手机使用3g/4g信号上网,在同一区域只有1个IP,如果您去了别的城市用3g/4g信号上网则会变动IP。通过手机数据流量上网,即使是在异地,也会分配手机卡归属地的IP地址。比如手机卡归属地是北京,到了山东之后连接数据流量,这时候的IP地址还会是北京的IP。无论手机漫游到天涯海角,手机漫游所在地的SGSN根据手机号码,可以查询到手机的归属地,还可以查询到该归属地的GGSN的IP地址,然后使用GTP(GPRS Tunnel Protocol)在漫游地与归属地建立一个GTP隧道,如下所示:漫游地SGSN<——- GTP隧道—— > 归属地GGSN

漫游地把该手机所有的数据流量统统使用该GTP隧道,流到归属地的GGSN,归属地再剥离掉GTP隧道头,得到用户的报文,再做进一步的处理:

  • 如果是手机请求分配IP地址等参数,GGSN将IP地址等参数返回给手机用户,通过GTP隧道传输
  • 如果是访问互联网的流量,GGSN完成NAT,将用户流量发送到Internet

这样做的好处是,所有用户的数据流量都经过归属地的计费系统,方便统一计费。

举例:苏州移动用户的无线网络IP均为:

  • 136.67.48~117.136.67.95
  • 136.67.160~117.136.255

无论苏州移动的用户漫游到哪里,他的IP永远是上面几个。(由于目前已经取消了漫游费,所以手机归属地并不一定是常住地)

宽带/ADSL网络下的IP地址

以前,ADSL或宽带都是在连接时都会分配公网IP,但随着IPv4缺口的变大,很多运营商都不再提供公网IP,类似长城光缆这种二级运营商只给内网IP。目前联通和电信也开始只提供内网IP,然后NAT后大家共享一个公网地址。而通常运营商NAT后分配的地址为:100.64.0.0/10

IP地址归属地

全球IP地址分配由IANA(Internet Assigned Numbers Authority)负责管理,官方网站是:Internet Assigned Numbers Authority但IANA不负责具体的地址分配,具体的地址分配由其下属的几个互联网中心管理,其中包括:

  • APNIC(亚太互联网络信息中心)
  • AFRINIC(非洲互联网信息中心)
  • ARIN(北美互联网号码注册中心)
  • LACNIC(拉美互联网信息中心)
  • RIPE NCC(欧洲网络资讯中心)

分别负责具体的IP地址分配。理论上,所有IP地址的归属,都能从以上的几个中心网站上查到,其中APNIC的查询页面是: APNIC – Query the APNIC Whois Database可以说,大多数IP数据库中的原始数据都是来自这里。但这里只能查询到IP地址申请人所描述的信息,实际上根本无法精确到具体的街道、校区、宿舍等。而且IANA只负责分配,不负责管理具体这个IP地址怎么用。所以,理论上说,完全精确的IP地址数据库是根本不存在的。需要注意的是:凡是那种有精确楼号、街道、校区的IP地址数据库,都不一定是准确,都是经过人工修正的,如果有一天互联网接入商更改了IP地址分配的策略,那么这种数据库就不准了。国内使用的最多的纯真IP数据库就是基于WHOIS数据库创建的。

目前国内绝大多数IP库都由WHOIS数据库作为基础数据来源。WHOIS数据仅表示某个IP被哪个机构注册,但无从知晓该IP被用在何处,这就导致许多非运营商自己注册的IP地址无法被正确分类。

基于边界网关的IP数据

随着互联网规模的增加,为了处理大批量的路由数据,边界网关协议(即BGP,下同)应运而生,是互联网的基础协议之一。为了保证了全球网络路由的可达性,但凡需要在互联网中注册一个IP(段),都需要借助BGP协议对外广播,这样互联网中的其他自治域才能学习到这段地址的路由信息,其它主机才能成功访问这个IP(段)。因此可以说,BGP数据是最适合分析运营商IP地址的数据来源之一。

但是,目前国内绝大多数IP库都由WHOIS数据库作为基础数据来源。WHOIS数据仅表示某个IP被哪个机构注册,但无从知晓该IP被用在何处,这就导致许多非运营商自己注册的IP地址无法被正确分类。ipip.net是最早开始做BGP/ASN数据分析的公司之一,数据准确性甩其它库几条街。但很可惜是,ipip.net作为商业公司,绝大多数高质量的IP数据都是收费的,且价格不菲。但有类似的 开源版本

服务器如何获取IP

在讨论获取客户端IP 地址前,我们首先下弄明白的是以下三个的具体含义:REMOTE_ADDR,HTTP_CLIENT_IP,HTTP_X_FORWARDED_FOR

  • REMOTE_ADDR:访问端(有可能是用户,有可能是代理的)IP
  • HTTP_CLIENT_IP:代理端的(有可能存在,可伪造)
  • HTTP_X_FORWARDED_FOR:用户是在哪个IP使用的代理(有可能存在,也可以伪造)

没有使用代理服务器的情况:

  • REMOTE_ADDR = 您的 IP
  • HTTP_CLIENT_IP = 没数值或不显示
  • HTTP_X_FORWARDED_FOR = 没数值或不显示

使用透明代理服务器的情况:Transparent Proxies

  • REMOTE_ADDR = 最后一个代理服务器 IP
  • HTTP_CLIENT_IP = 代理服务器 IP
  • HTTP_X_FORWARDED_FOR = 您的真实 IP ,经过多个代理服务器时,这个值类似如下:98.182.163, 203.98.182.163, 203.129.72.215。

这类代理服务器还是将您的信息转发给您的访问对象,无法达到隐藏真实身份的目的。

使用普通匿名代理服务器的情况:Anonymous Proxies

  • REMOTE_ADDR = 最后一个代理服务器 IP
  • HTTP_CLIENT_IP = 代理服务器 IP
  • HTTP_X_FORWARDED_FOR = 代理服务器 IP ,经过多个代理服务器时,这个值类似如下:98.182.163, 203.98.182.163, 203.129.72.215。

隐藏了您的真实IP,但是向访问对象透露了您是使用代理服务器访问他们的。

使用欺骗性代理服务器的情况:Distorting Proxies

  • REMOTE_ADDR = 代理服务器 IP
  • HTTP_CLIENT_IP = 代理服务器 IP
  • HTTP_X_FORWARDED_FOR = 随机的 IP ,经过多个代理服务器时,这个值类似如下:98.182.163, 203.98.182.163, 203.129.72.215。

告诉了访问对象您使用了代理服务器,但编造了一个虚假的随机IP代替您的真实IP欺骗它。

使用高匿名代理服务器的情况:High Anonymity Proxies (Elite proxies)

  • REMOTE_ADDR = 代理服务器 IP
  • HTTP_CLIENT_IP = 没数值或不显示
  • HTTP_X_FORWARDED_FOR = 没数值或不显示 ,经过多个代理服务器时,这个值类似如下:98.182.163, 203.98.182.163, 203.129.72.215。

完全用代理服务器的信息替代了您的所有信息,就像您就是完全使用那台代理服务器直接访问对象。

备注:服务器如果做了负载均衡,则需要在在获取IP时使用HTTP_X_FORWARDED_FOR获取原IP。


服务高可用之限流

$
0
0

在不同场景下限流的定义也各不相同,可以是每秒请求数、每秒事务处理数、网络流量。通常我们所说的限流指的是限制到达系统并发请求数,使得系统能够正常的处理部分用户的请求,来保证系统的稳定性。

限流的英文是Rate limit(速率限制),维基百科中的定义比较简单。我们编写的程序可以被外部调用,Web 应用通过浏览器或者其他方式的 HTTP 方式访问,接口的访问频率可能会非常快,如果我们没有对接口访问频次做限制可能会导致服务器无法承受过高的压力挂掉,这时候也可能会产生数据丢失。而限流算法就可以帮助我们去控制每个接口或程序的函数被调用频率,它有点儿像保险丝,防止系统因为超过访问频率或并发量而引起瘫痪。

我们可能在调用某些第三方的接口的时候会看到类似这样的响应头:

X-RateLimit-Limit: 60 //每秒60次请求
X-RateLimit-Remaining: 23 //当前还剩下多少次
X-RateLimit-Reset: 1540650789 //限制重置时间

限流的行为指的就是在接口的请求数达到限流的条件时要触发的操作,一般可进行以下行为:

  • 拒绝服务:把多出来的请求拒绝掉(定向到错误页或告知资源没有了)
  • 服务降级:关闭或是把后端服务做降级处理。这样可以让服务有足够的资源来处理更多的请求(返回兜底数据或默认数据)
  • 特权请求:资源不够了,我只能把有限的资源分给重要的用户
  • 延时处理:一般会有一个队列来缓冲大量的请求,这个队列如果满了,那么就只能拒绝用户了,如果这个队列中的任务超时了,也要返回系统繁忙的错误了
  • 弹性伸缩:用自动化运维的方式对相应的服务做自动化的伸缩

常见限流算法

限流的实现方案有很多种:

  • 合法性验证限流:比如验证码、IP 黑名单等,这些手段可以有效的防止恶意攻击和爬虫采集
  • 容器限流:比如 Tomcat、Nginx 等限流手段,其中 Tomcat 可以设置最大线程数(maxThreads),当并发超过最大线程数会排队等待执行; Nginx 提供了两种限流手段:一是控制速率,二是控制并发连接数
  • 服务端限流:比如我们在服务器端通过限流算法实现限流

计数限流

最简单的限流算法就是计数限流了,例如系统能同时处理100个请求,保存一个计数器,处理了一个请求,计数器加1,一个请求处理完毕之后计数器减1。每次请求来的时候看看计数器的值,如果超过阈值要么拒绝。

应用场景:一些API接口的账号总调用次数

  • 优点:简单粗暴
  • 缺点:假设我们允许的阈值是1万,此时计数器的值为0, 当1万个请求在前1秒内一股脑儿的都涌进来,这突发的流量可是顶不住的。缓缓的增加处理和一下子涌入对于程序来说是不一样的。

固定窗口限流

它相比于计数限流主要是多了个时间窗口的概念。计数器每过一个时间窗口就重置。

规则如下:

  • 请求次数小于阈值,允许访问并且计数器 +1
  • 请求次数大于阈值,拒绝访问
  • 这个时间窗口过了之后,计数器清零

看起来好像很完美,实际上还是有缺陷的。

假设有个用户在第 59 秒的最后几毫秒瞬间发送 200 个请求,当 59 秒结束后 Counter 清零了,他在下一秒的时候又发送 200 个请求。那么在 1 秒钟内这个用户发送了 2 倍的请求,如下图:

QPS 限流算法

QPS 限流算法通过限制单位时间内允许通过的请求数来限流。

优点:

  • 计算简单,是否限流只跟请求数相关,放过的请求数是可预知的(令牌桶算法放过的请求数还依赖于流量是否均匀),比较符合用户直觉和预期。
  • 可以通过拉长限流周期来应对突发流量。如 1 秒限流 10 个,想要放过瞬间 20 个请求,可以把限流配置改成 3 秒限流 30 个。拉长限流周期会有一定风险,用户可以自主决定承担多少风险。

缺点:

  • 没有很好的处理单位时间的边界。比如在前一秒的最后一毫秒和下一秒的第一毫秒都触发了最大的请求数,就看到在两毫秒内发生了两倍的 QPS。
  • 放过的请求不均匀。突发流量时,请求总在限流周期的前一部分放过。如 10 秒限 100 个,高流量时放过的请求总是在限流周期的第一秒。

为了解决这个问题引入了滑动窗口限流。

滑动窗口限流

滑动窗口是针对计数器存在的临界点缺陷,所谓 滑动窗口(Sliding window) 是一种流量控制技术,这个词出现在 TCP 协议中。滑动窗口把固定时间片进行划分,并且随着时间的流逝,进行移动,固定数量的可以移动的格子,进行计数并判断阀值。

上图中我们用红色的虚线代表一个时间窗口(一分钟),每个时间窗口有 6 个格子,每个格子是 10 秒钟。每过 10 秒钟时间窗口向右移动一格,可以看红色箭头的方向。我们为每个格子都设置一个独立的计数器 Counter,假如一个请求在 0:45 访问了那么我们将第五个格子的计数器 +1(也是就是 0:40~0:50),在判断限流的时候需要把所有格子的计数加起来和设定的频次进行比较即可。

滑动窗口如何解决我们上面遇到的问题呢?

当用户在0:59 秒钟发送了 200个请求就会被第六个格子的计数器记录 +200,当下一秒的时候时间窗口向右移动了一个,此时计数器已经记录了该用户发送的 200 个请求,所以再发送的话就会触发限流,则拒绝新的请求。

其实计数器就是滑动窗口啊,只不过只有一个格子而已,所以想让限流做的更精确只需要划分更多的格子就可以了,为了更精确我们也不知道到底该设置多少个格子,格子的数量影响着滑动窗口算法的精度,依然有时间片的概念,无法根本解决临界点问题。

漏桶算法

漏桶算法是水先进入到漏桶里,漏桶再以一定的速率出水,当流入水的数量大于流出水时,多余的水直接溢出。把水换成请求来看,漏桶相当于服务器队列,但请求量大于限流阈值时,多出来的请求就会被拒绝服务。漏桶算法使用队列实现,可以以固定的速率控制流量的访问速度,可以做到流量的“平整化”处理。

漏桶算法实现步骤:

  • 将每个请求放入固定大小的队列进行存储
  • 队列以固定速率向外流出请求,如果队列为空则停止流出
  • 如队列满了则多余的请求会被直接拒绝

漏桶是一个比较好的限流整型工具, 但是漏桶算法也有一定的缺陷,因为水从桶的底部以固定的速率匀速流出,当有在服务器可承受范围内的瞬时突发请求进来,这些请求会被先放入桶内,然后再匀速的进行处理,这样就会造成部分请求的延迟。所以他无法应对在限流阈值范围内的突发请求。不过如果较起真来,我觉得这个缺点是不成立的。毕竟漏桶本就是用来平滑流量的,如果支持突发,那么输出流量反而不平滑了。如果要找一种能够支持突发流量的限流算法,那么令牌桶算法可以满足需求。

令牌桶算法

令牌桶和漏桶颇有几分相似,只不过令牌通里存放的是令牌。它的运行过程是这样的,一个令牌工厂按照设定值定期向令牌桶发放令牌。当令牌桶满了后,多出的令牌会被丢弃掉。每当一个请求到来时,该请求对应的线程会从令牌桶中取令牌。初期由于令牌桶中存放了很多个令牌,因此允许多个请求同时取令牌。当桶中没有令牌后,无法获取到令牌的请求可以丢弃,或者重试。下面我们来看一下的令牌桶示意图:

令牌桶其实和漏桶的原理类似,只不过漏桶是定速地流出,而令牌桶是定速地往桶里塞入令牌,然后请求只有拿到了令牌才能通过,之后再被服务器处理。当然令牌桶的大小也是有限制的,假设桶里的令牌满了之后,定速生成的令牌会丢弃。

  • 定速的往桶内放入令牌
  • 令牌数量超过桶的限制,丢弃
  • 请求来了先向桶内索要令牌,索要成功则通过被处理,反之拒绝

令牌桶算法和漏桶算法的方向刚好是相反的,我们有一个固定的桶,桶里存放着令牌(token)。一开始桶是空的,系统按固定的时间(rate)往桶里添加令牌,直到桶里的令牌数满,多余的请求会被丢弃。当请求来的时候,从桶里移除一个令牌,如果桶是空的则拒绝请求或者阻塞。

漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。

  • 令牌桶算法,放在服务端,用来保护服务端(自己),主要用来对调用者频率进行限流,为的是不让自己被压垮。所以如果自己本身有处理能力的时候,如果流量突发(实际消费能力强于配置的流量限制=桶大小),那么实际处理速率可以超过配置的限制(桶大小)。
  • 而漏桶算法,放在调用方,这是用来保护他人,也就是保护他所调用的系统。主要场景是,当调用的第三方系统本身没有保护机制,或者有流量限制的时候,我们的调用速度不能超过他的限制,由于我们不能更改第三方系统,所以只有在主调方控制。这个时候,即使流量突发,也必须舍弃。因为消费能力是第三方决定的。

令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。

限流组件

一般而言我们不需要自己实现限流算法来达到限流的目的,不管是接入层限流还是细粒度的接口限流其实都有现成的轮子使用,其实现也是用了上述我们所说的限流算法。

  • Google Guava提供的限流工具类 RateLimiter,是基于令牌桶实现的,并且扩展了算法,支持预热。
  • 阿里开源的限流框架 Sentinel中的匀速排队限流策略,就采用了漏桶算法。
  • Nginx 中的限流模块 limit_req_zone,采用了漏桶算法,
  • OpenResty 中的 limit.req库。
  • Netflix开源的 Hystrix
  • Resilience4j

Sentinel、Hystrix、resilience4j对比:

SentinelHystrixresilience4j
隔离策略信号量隔离(并发线程数限流)线程池隔离/信号量隔离信号量隔离
熔断降级策略基于响应时间、异常比率、异常数等异常比率模式、超时熔断基于异常比率、响应时间
实时统计实现滑动窗口(LeapArray)滑动窗口(基于 RxJava)Ring Bit Buffer
动态规则配置支持 多种配置源支持多种数据源有限支持
扩展性丰富的 SPI 扩展接口插件的形式接口的形式
基于注解的支持支持支持支持
限流基于 QPS,支持基于调用关系的限流有限的支持Rate Limiter
集群流量控制支持不支持不支持
流量整形支持预热模式、匀速排队模式等多种复杂场景不支持简单的 Rate Limiter 模式
系统自适应保护支持不支持不支持
控制台提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等简单的监控查看不提供控制台,可对接其它监控系统
多语言支持Java / C++JavaJava
开源社区状态活跃停止维护较活跃

用户体系搭建之ID-Mapping

$
0
0

ID-Mapping简介

在推进用户画像和风险控制时,遇到的最大的问题是用户身份信息的混乱:

  • 相同设备,不同账号间切换
  • 相同用户,不同渠道下账号不相同,如微信小程序和APP
  • 同个用户,在不同的设备商登录

ID-Mapping是大数据分析中非常基本但又关键的环节,ID-Mapping通俗的说就是把几份不同来源的数据,通过各种技术手段识别为同一个对象或主题,例如同一台设备(直接),同一个用户(间接),同一家企业(间接)等等,可以形象地理解为用户画像的“拼图”过程。一个用户的行为信息、属性数据是分散在很多不同的数据来源的,因此从单个数据来看,都相当于“盲人摸象”,看到的只是这个用户一个片面的画像,而ID-Mapping能把碎片化的数据全部串联起来,消除数据孤岛,提供一个用户的完整信息视图,同时让某一个领域的数据在另一个领域绽放出巨大的价值。

ID-Mapping有非常多的用处,比如:

  • 跨屏跟踪和跨设备跟踪,将一个用户的手机(App、小程序)、PC、平板等设备的上的行为信息串联到一起。
  • 风险防控层面,通过模型识别可能存在用户、设备伪造问题。

ID-Mapping行业内方案

阿里巴巴OneID

在阿里巴巴内部用户的ID类型包含:phone、PC cookie、IMEI与IDFA、淘宝账户、支付宝账户、邮箱等。而对于每个BU来说,他们知道的只是这个客户的片面属性,在开展营销活动时,只是针对一个手机号或一个邮箱做营销,但背后不能识别出来一个自然人、一个公司。为打破数据孤岛,创造更大的数据价值,阿里使用OneData作为核心方法论。

OneData体系包含:

  • OneModel:数据资产构建与管理
  • OneID:实体打通和画像
  • OneService:逻辑化服务

OneID 的做法是通过统一的实体识别和连接,打破数据孤岛,实现数据通融。简单来说,用户、设备等业务实体,在对应的业务数据中,会被映射为唯一识别(UID)上,其各个维度的数据通过这个 UID 进行关联。各个部门、业务、产品对业务实体的 UID 的定义和实现不一样,使得数据间无法直接关联,成为了数据孤岛。基于手机号、身份证、邮箱、设备 ID 等信息,结合业务规则、机器学习、图算法等算法,进行 ID-Mapping,将各种 UID 都映射到统一 ID 上。通过这个统一 ID,便可关联起各个数据孤岛的数据,实现数据通融,以确保业务分析、用户画像等数据应用的准确和全面。

网易ID-Mapping

网易产品线有网易云音乐、网易邮箱、网易新闻、网易严选等,不同应用上有不同的ID,如yanxuanid、oaid、musicid、phone、email、idfa、imei等。

要想标识唯一ID,网易采用的思路及方案为:结合各种账户、各种设备型号之间的关系对,以及设备使用规律等用户数据,采用规则规律、数据挖掘算法(连通图划分+社区发现)的方法,判别账户是否属于同一个人。

ID-Mapping过程中,常遇到的问题及对应方案如下:

  • 用户有多个设备信息。解决方案:定义相关的阈值进行关联。社区发现当前应用于营销场景,暂未用于风控或用户运营场景,因为这种方式会把一些异常的账号关联在一起,且会存在仅登录使用过一次的设备信息。
  • 设备过期,一般是2年半左右时间。解决方案:设定衰减系数,对单用户多设备加大衰减力度。

备注:通常一人多设备对应的场景有,借用朋友设备、设备脏数据、刷号等。

58同城 ID-Mapping

58业务场景丰富,其产品线包含58同城、赶集、安居客、中华英才网、转转、58到家等。在这种多用户、多业务线、多子公司的情况下,用户数据种类繁杂,构建画像的数据来自于日志、简历库、帖子库、用户信息库、商家库、认证信息库等数据源,其中仅日志就涉及到58、赶集、安居客等各个子产品的PC/M/APP日志。如何将众多数据源串联起来是构建用户画像面临的第一个问题,如下是58构建的ID-Mapping模型图。

从图中可以看出,不同业务线所拥有的ID标识不一:

  • 58同城:wuser、wbdid、wimei
  • 58赶集:guser、gbdid、gapud、gimei
  • 安居客:kimei

其中可以通过telep、bidua、appua、imei、idfa关联起来,由此建立不同ID之间的关联映射关系,就是ID-Mapping的过程。

美团ID-Mapping

美团与大众点评进行了合并,那同一个用户在两个APP上有不同的身份标识,美团要怎样进行唯一标识呢?我们来看看美团和大众点评的账号体系。美团采用手机号、微信、微博、美团账号的登录方式;大众点评采用的手机号、微信、QQ、微博的登录方式;其交集为手机号、微信、微博。最终,对于注册用户账户体系,美团采用了手机号作为用户的唯一标识。

从上述案例可看出,ID-Mapping有三种常见方法:

  • 基于账号体系企业中最常用的是基于账号体系来做ID的打通,用户注册时,给到用户一个uid,以uid来强关联所有注册用户的信息。
  • 基于设备:那对于未注册用户可以通过终端设备ID精准识别,包含Android/iOS两类主流终端的识别。通过SDK将各种ID采集上报,后台利用的ID关系库和校准算法,实时生成/找回终端唯一ID并下发。
  • 基于账号&设备:结合各种账户、各种设备型号之间的关系对,以及设备使用规律等用户数据,采用规则规律、数据挖掘算法的方法,输出关系稳定的ID关系对,并生成一个UID作为唯一识别该对象的标识码。

id-mapping实现方案

id-mapping技术手段1:按账号优先级

按账号优先级进行id-mapping是最简单的方案,将数据库中的手机号/uid/deviceid等按优先级取一个标识,作为这条数据的用户唯一标识。但这个方案存在严重的漏洞。

在现实的日志数据中,由于,用户可能使用各种各样的设备,有着各种各样的前端入口,甚至同一个用户拥有多个设备以及使用多种前端入口,就会导致,日志数据中对同一个人,不同时间段所收集到的日志数据中,可能取到的标识个数、种类各不相同。

比如,用户可能使用各种各样的设备和渠道:

  • 手机、平板电脑
  • 安卓手机、ios手机
  • 有PC、APP和小程序

产生问题:用户设备的标识,没办法轻易定制一个规则来取某个作为唯一标识:

  • cookieid:PC站存在用户cookies中的ID,会被清理电脑时重生成
  • unionid:微信提供的唯一身份认证
  • mac:手机网卡物理地址, 若干早期版本的ios,winphone,android可取到
  • imei(入网许可证序号):安卓系统可取到,若干早期版本的ios,winphone可取到,运营商可取到
  • imsi(手机SIM卡序号):安卓系统可取到,若干早期版本的ios,winphone可取到,运营商可取到
  • androidid :安卓系统id
  • openuuid(app自己生成的序号) :卸载重装app就会变更
  • idfa(广告跟踪码):用户可重置
  • deviceid(app日志采集埋点开发人员自己定义一种逻辑id,可能取自 android,imei,openudid等):逻辑上的id

从而导致:有些场景有某些数据,有些场景没有,从而没有办法行程整合的一份数据。要从这些纷繁复杂的各类id中,分辨出哪些id属于同一个受众(设备),用普通的“where x=y”这种简单条件逻辑很难实现。

id-mapping技术手段2:借助图计算

采用图计算手段,来找到各种id标识之间的关联关系,从而识别出哪些id标识属于同一个人

图计算的核心思想:将数据表达成“点”,点和点之间可以通过某种业务含义建立“边”。然后,我们就可以从点、边上找出各种类型的数据关系:比如连通性,比如最短路径规划,id_mapping(id打通)的最后目标,就是形成一个id映射字典:

整体流程:

  • 将当日数据中的所有用户标识字段,及标志字段之间的关联,生成点集合 、边集合
  • 将上一日的ids->guid的映射关系,也生成点集合、边集合
  • 将上面两类点集合、边集合合并到一起生成一个图
  • 再对上述的图执行“最大连通子图”算法,得到一个连通子图结果
  • 在从结果图中取到哪些id属于同一组,并生成一个唯一标识
  • 将上面步骤生成的唯一标识去比对前日的ids->guid映射表(如果一个人已经存在guid,则沿用原来的guid)

具体实现方案可使用图计算或图数据库实现。

总结

One ID的核心价值是打通数据孤岛,把不同时期孤立建设的系统,用统一的ID串联起来。One ID功能就像是在修桥梁,把各个数据孤岛贯通之后,这些孤岛就连成一片。数据孤岛被打破之后,我们就能更全面、更完整的了解我们的用户、产品、商家,能够更加精准的评价他们的价值,进行进一步的价值发现,为精细化运营夯实数据基础。One ID的核心技术是ID-Mapping,其原理是将各系统的关键要素抽象成图计算用的“点”和“边”,用图计算算法很轻易的判定同一个“对象”,从而构建一个个无向连通图,生成ID映射字典。这个ID映射字典就是一座座通往各个数据孤岛的桥梁。我们通过这些桥梁,可以把相同“对象”在不同孤岛中的数据串联起来。

参考链接:

用户画像TGI指标

$
0
0

什么是TGI

对于TGI指数,百科是这样解释的——TGI指数,全称Target Group Index,可以反映目标群体在特定研究范围内强势或者弱势。

TGI指数计算公式 = 目标群体中具有某一特征的群体所占比例 / 总体中具有相同特征的群体所占比例 * 标准数100

举个例子,假设一家外语学校里面有家烧烤店,每天晚上男生和女生顾客都是50%,你觉得男生还是女生更倾向于光顾这个烧烤店呢?既然男女既然都一半,那显然是说男女同等倾向于光顾这家店。如果你是这么想的,那就错了。因为还有一个隐含的概率你没有问,就是这个学校里男生和女生的比例是多少呢。我们通过调查了解到男生和女生的比例分别是20%和80%。有了这个比例,我们就可以算TGI了。

  • 烧烤店男生TGI=50%/20%*100=250
  • 烧烤店女生TGI=50%/80%*100=60

你看,虽然光顾的顾客中男女比例一样。但是我们算TGI的话,会发现男生的TGI远远大于女生的TGI,从这个意义上讲,男生光顾这家门店的倾向性远大于女生。

TGI的使用

我们为什么要看TGI?TGI可以帮我们找准目标顾客。像上面烧烤店的案例,通过计算TGI,我们很明显得知道男性顾客才是最喜欢我们门店的顾客,而女性用户并不喜欢我们这家店,这是最基本的现状。那基于这个现状,你接下来要做的到底是巩固喜欢这个门店的男性顾客群,还是开拓并不太喜欢这个门店的女性顾客群?这就是价值观问题了。找准了目标顾客之后,我们就可以针对目标顾客做更多的宣传推广。如果目标顾客是不清晰的话,那我们做宣传可能会漫无目的,结果就是吃力不讨好。

TGI指数表明不同特征用户关注的差异情况:

  • TGI指数 = 100 表示平均水平,
  • TGI指数 > 100,代表该类特征对目标群体的影响要大于平均水平,或者该类群体对某类问题的关注程度高于平均水平
  • TGI指数 < 100,代表该类特征对目标群体的影响要小于平均水平,或者该类群体对某类问题的关注程度低于平均水平

如何理解目标人群和基准人群?

目标人群是我们选择用来进行下钻数据分析的人群,画像数据都是基于此人群进行分析。百分比和TGI均指的是目标人群的某标签值的百分比和TGI。基准人群是应用于TGI指数计算的对比人群,默认的大盘人群的全量人群。

典型特征中的特征人群是如何定义的?

采用有层级结构的树形图。越上层的维度越显著区分人群。示例:

TGI实战

需求:公司要推出一款客单比较高的产品,打算在一些城市打样,需要确定哪些城市适合打样。

数据集:客户购买商品的订单数据,具体包括买家ID、省份、城市、下单日期、购买数量、实付金额,一共28832条数据。

import pandas as pd

df = pd.read_excel('tgi-dataset.xlsx')
df.head()

数据初步处理:计算每个用户的客单价

df_price = df[['城市', '买家ID', '实付金额', '购买数量']].groupby(['城市', '买家ID']).agg('sum').reset_index()
df_price['客单价'] = df_price['实付金额']/df_price['购买数量']
df_price.head()

方案一:按照城市的平均客单价排序,取平均客单价最高的城市进行打样

df_price_city = df_price[['城市','客单价','买家ID']].groupby(['城市']).agg({'客单价':'mean', '买家ID':'count'}).reset_index().rename(columns={'买家ID':'买家数量'})
df_price_city.sort_values('客单价',ascending=False).head()

客单价高的买家数量较少,需要再将买家数量较少的城市过滤:

df_price_city.loc[df_price_city['买家数量'] > df_price_city['买家数量'].mean(),:].head()

方案二:统计每个城市大于一定阈值的会员量,取会员量最高的城市

先查看下客单价分布:

df_price['客单价'].hist(bins=16)

假设阈值为50,则:

threshold = 50
df_price_high = df_price[df_price['客单价']>=threshold][['城市','买家ID']].groupby(['城市']).agg('count').reset_index().reset_index().rename(columns={'买家ID':'买家数量'})
df_price_high.sort_values('买家数量',ascending=False).head()

方案三:引入tgi,根据tgi的高低选择打样城市

判断是否为高客单价买家:

df_price['高客单'] = df_price['客单价'].apply(lambda x: 1 if x >=threshold else 0 )
df_price['低客单'] = df_price['客单价'].apply(lambda x: 1 if x < threshold else 0 )
df_price.head()

对数据进行汇总处理:

df_summary = df_price[['城市','高客单','低客单']].groupby(['城市']).agg('sum').reset_index()
df_summary['总人数'] = df_summary['高客单'] + df_summary['低客单']
df_summary['高客单占比'] = df_summary['高客单'] / df_summary['总人数'] 
df_summary.head()

计算总体高客单人数占比:

total_percentage = df_summary['高客单'].sum() / df_summary['总人数'].sum()
total_percentage

计算每个城市高客单TGI指数:

df_summary['高客单TGI指数'] = df_summary['高客单占比'] / total_percentage * 100
df_summary = df_summary.sort_values('高客单TGI指数',ascending = False)
df_summary.head()

筛选出人数大于平均值的人数,再计算更合理的TGI指数:

df_summary.loc[df_summary['总人数'] > df_summary['总人数'].mean(),:].head()

即时通讯协议之Qunar

$
0
0

Qunar 由于业务上对 IM 系统的需求,以及对 IM 需要支持的功能和扩展,结合市面上已有的 IM 的实现,实现了自己的一套完善的办公 IM 和客服 IM 系统。具备了以下几个重要特点:实时性,可靠性,一致性,安全性,扩展性,高并发。

Startalk是去哪儿开源的一款通用的,高性能的企业级im套件。Startalk 前身是去哪儿的Qtalk。其内核也在去哪儿旅行和去哪儿网站上扮演着着客服服务工具的角色。也就是说,一套内核同时为去哪儿网提供了内部企业办公和商家tob业务的支撑。

IM 常见实现方案

XMPP 协议

XMPP 是一个开放式的 XML 协议,设计用于准实时消息和出席信息以及请求-响应服务。

XMPP 协议单元包括三个大类:

  • Presence:Presence 决定了 XMPP 实体的状态,用来告诉服务器该实体是在线、离线或者繁忙;
  • Message:用户之间发送和接收的消息;
  • IQ:请求-响应类型的报文;

优点:XMPP 有大量的优秀实现,且社区环境良好,功能和扩展能力丰富。

缺点:报文较大,耗费网络流量和电量。

MQTT 协议

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的”轻量级”通讯协议,该协议构建于 TCP/IP 协议上,由 IBM 在 1999 年发布。MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

MQTT 是一个基于客户端-服务器的消息发布/订阅传输协议。 MQTT 协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。正是由于它的简单,也带来了他的缺点:需要自己去实现聊天,好友等 IM 的逻辑。

Qunar IM实现方案

协议的选择

基于上面调研的常用 IM 协议,我们最终选择了 XMPP 协议最为最开始的实现协议:

  • 因为可以用最小的开发工作来实现基本的聊天功能;
  • 可以使用已有的扩展插件,更快实现更多的功能;
  • 针对 XMPP 的缺点,做针对性的修改,将网络传输这一阶段,改成 protocol buffer 协议,弥补网络流量和电量的短板。

开源项目的选择

基于:

  • ejabberd 是基于 Jabber/XMPP 协议的即时通讯服务器;
  • 由 GPLv2 授权(免费和开放源码);
  • 采用 Erlang/OTP 开发,它的特点是,跨平台,容错,集群和模块;
  • Ejabberd 是可扩展性最好的一种 Jabber/XMPP 服务器之一,支持分布多个服务器,并且具有容错处理,单台服务器失效不影响整个 cluster 运作;
  • Erlang 的调度和 GC 策略更适合 IM 的实时性要求(见参考)。

团队最终选择使用 ejabberd 开源实现来快速实现自己的 IM 功能。

架构设计

  • 客户端通过两条连接来和服务器进行通讯
    • TCP 长连接( web 使用的 websocket ):该连接上交互的是和状态相关或者多端同步的报文;
    • HTTP 连接:和状态无关或者不需要多端同步的。
  • 负载均衡
    • TCP 长连接通过 LVS 或者 HA 来做负载均衡;
    • HTTP 通过 nginx 来做负载均衡。
  • 数据
    • 数据或直接放入数据库或者进入 MQ ,供需要方订阅消费;
    • 高频访问数据放到 redis ,供应用频繁查询,减小数据压力。
  • 管理维护
    • 提供内网接口,供其他系统扩展 IM 功能;
    • 提供监控和维护工具,方便系统维护和故障处理。

ejabberd 架构

消息流转过程为:

  • 客户端通过长连接,将消息发送给服务器,进入到负责该连接的ejabberd_c2s进程;
  • ejabberd_c2s 进程处理完之后,调动ejabberd_router:route(From,To,Stanza)来路由该消息;
  • ejabberd_router处理自己的公共逻辑;
  • 然后如果是发给该 IM 系统的消息,会将消息发送给ejabberd_local进程;
  • ejabberd_local判断如果To是具体某个人,则会把消息发送给负责该用户连接的 ejabberd_sm进程;
  • ejabberd_sm进程会查询收消息的用户,有几个在线设备,然后将改消息发送给该用户的多个ejabberd_c2s进程;
  • ejabberd_c2s进程将消息通过自己负责的 TCP 连接,将消息发送给客户端。

这样,一条消息就完整的从用户 A 传输到了用户 B,实现消息的即时沟通。

ejabberd 功能扩展

在上一节 ejabberd 架构图中,我们可以在每个步骤中添加 hook 函数,添加自己的扩展功能。我们添加的扩展功能有:

使用 protocolbuffer 协议

由于 XMPP 协议具有:XML 报文流量大、耗电高等缺点,我们通过使用 protocolbuffer 替换掉 xml 报文,实现客户端到服务器之间的流量传输,减少报文流量和降低手机端的耗电量。在服务端将 protocolbuffer 再转换成 ejabberd 服务器使用的 xml 格式,减少服务端 im 逻辑的修改。

消息可靠性

通过以下渠道来确保 IM 消息的可靠性:

  • 设备在线时,通过消息确认回执来保证消息正确的发送出去和接收到;
  • 设备不在线时,当再次登录的时候,通过 HTTP 接口,拉取从上次退出到这次登录时间段内所有的历史消息;
  • 每条消息具有唯一 id,确保发送消息时候的幂等性,重复发送同一条消息,只会展示和存储一条消息。

消息确认回执

我们在 ejabberd_c2s进程收到消息的时候,添加一个 hook 函数,用来作为服务器对收到消息的确认,同时将时间戳也返回给客户端,作为该消息的时间戳。

消息确认回执,主要解决的是问题是:保证消息至少一次发送成功,只有客户端收到了服务器的确认回执,才会认为消息到达了服务器,得到了响应的处理;否则客户端就会认为消息没有到达服务器,发送失败了。

消息同步

当我们同一个账号同时登录多个设备的时候,需要感知到在其他设备收发的消息,并同步展示给用户。所以我们在收发消息时,需要做必要的处理,以实现多设备同步的功能。

同步发送消息给其他设备:

发布消息到消息队列

为了扩展 IM 功能,我们需要把所有的 IM 的消息和时间发布到消息队列,供其他系统订阅消费,实现消息的统计、分析和存储。所以我们将时间和消息分类放入到 kafka,实现消息的异步处理。

目前发送发送的包括:消息、上下线事件、驼圈事件以及 @事件等

发送消息的 http 接口

为了给其他系统提供发送消息的服务,我们通过提供 http 接口的方式,来模拟来自用户的消息,实现该功能。

下发 IM 认证凭证

我们可以在 IM 里嵌入其它系统,来扩展 IM 的能力,包括但不限于移动 OA 、运维报警等系统。IM 客户端可以在跳转到其它系统的时候,带上 IM 的认证凭证,由其它系统来调用 IM 接口来认证身份。如果认证通过则表明是通过正在登录的 IM 客户端访问的,否则不允许访问该系统。这样就避免了让用户重复的认证身份,提高办公效率和用户体验。

IM 认证凭证的流程是:

  • 当 IM 长连接建立成功且认证通过后,服务器会通过长连接下发 token 给客户端
  • 客户端请求 IM 的 HTTP 接口的时候要带着 token ,用于身份认证
  • 客户端打开其它受信系统的时候,也会带上 token ,用于其它系统做身份认证
  • 当客户端与服务器的长连接断开的时候,服务器会销毁该 token ,使其失效。

增量拉取

在一些客户端和服务器之间需要同步的数据拉取上,我们采用增量更新的逻辑,减少每次服务器的响应数据集,加快客户端的登录和同步流程。

实现方式上,IM 采用以更新时间作为查询的 key ,服务器在每次更新数据的时候,都要更新 updatetime 字段。在客户端再次登录的时候,使用本地最新的更新时间去拉取数据,服务器如果存在比客户端更新的数据,则将增量的数据返回给客户端。

应用的场景有:

  • 组织架构更新
  • 消息历史更新
  • 群列表更新
  • 好友列表更新
  • 个人配置更新

IM 功能扩展

机器人实现

由于我们需要通过 IM 实现一些自助服务或者智能回复,我们需要在 IM 的扩展上实现该功能。

首先我们已经有了所有消息的队列服务功能,然后我们基于消息队列,订阅所有的消息,然后根据系统配置,将发送给特定机器人的消息,转发给对应的机器人服务。机器人服务收到发给自己的消息时,通过自己的系统配置或者自主学习的问题库,进行对应的操作,并调用 IM 的发消息接口,返回给咨询用户特定的消息。我们可以通过该方式,实现各种自助和智能化的服务。节省人力成本和提高办公效率。

客服系统实现

对于客服系统,和普通 IM 有一些不同之处。在用户看来,他是在和一个店铺或者一个官方客服在聊天。实际上,后面可能是多个不同的客服,可能还会用到排队、会话超时等逻辑,所以要在常用的 IM 功能上来做扩展。

客服系统订阅所有的 IM 消息,当用户发送消息给客服的时候,客服系统需要对咨询做排队,客服分配,会话建立,然后将用户发给客服的消息转换成发给具体某一个客服的消息,然后发送给客服。

用户-------------> 店铺     转换成     店铺-----------> 客服
客服-------------> 店铺     转换成     店铺-----------> 用户

数据指标

对于 IM 主要的指标,我们主要关注的有:

  • 同时在线数
  • 建立 TCP 的量
  • 消息量

下面是对应指标的实际数值

  • 同时在线数: 20W 左右
  • 建立 TCP 的量( QPS ):3W 左右
  • 收到消息的量( QPS ):3W 左右
  • 发出消息的量( QPS ):3W 左右

总结

通过实现基本的 IM 功能,以及各种扩展功能,我们总结出一些 IM 核心功能:

  • 提供稳定的 TCP 长连接服务
  • 提供统一的认证服务
  • 提供高性能的消息订阅和发送消息给其他服务的能力

相关链接:

参考链接:

使用zimg搭建图片服务器

$
0
0

一般的大型网站都会将图片存放在专门的服务器,这样可以很好的提升网站的性能。比较简单的方式是采用云厂商提供的服务,比如七牛云、又拍云等。今天要介绍的是一款开源的实现方案zing。

zimg简介

zimg是一套国人针对图片处理服务器而设计开发的开源程序,目的是解决图片服务中如下三个问题:

  • 大流量:对于一些中小型网站来说,流量问题就是成本问题,图片相对于文本来说流量增加了一个数量级,省下的每一个字节都是白花花的银子。所以凡是涉及到图片的互联网应用,都应该统筹规划,降低流量节约开支。
  • 高并发:高并发的问题在用户量较低时几乎不会出现,但是一旦用户攀升,或者遇到热点事件,比如网站被人上传了一张爆炸性的新闻图片,短时间内将会涌入大量的浏览请求,如果架构设计得不好,又没有紧急应对方案,很可能导致大量的等待、更多的页面刷新和更多请求的死循环。总的来说,就是要把图片服务的性能做得足够好。
  • 海量存储:Facebook用户上传图片上亿张,总容量超过了nPB,这样的数量级是一般企业无法承受的。虽然很难做出一个可以跟Facebook比肩的应用,但是从架构设计的角度来说,良好的拓展方案还是要有的。需要提前设计出最合适的海量图片数据存储方案和操作方便的拓容方案,以应对将来不断增长的业务需求。

以上三个问题,其实也是相互制约和钳制的,比如要想降低流量,就需要大量的计算,导致请求处理时间延长,系统单位时间内的处理能力下降;再比如为了存储更多的图片,必然要在查找上消耗资源,同样也会降低处理能力。所以,图片服务虽然看起来业务简单,实际做起来也不是一件小事。

zimg的定位:

  • zimg是图像存储和处理服务器。您可以使用URL参数从zimg获得压缩和缩放的图像。
  • zimg的并发I/O,分布式存储和及时处理能力非常出色。您不再需要在图像服务器中使用nginx。在基准测试中,zimg可以在高并发级别上每秒处理3000个以上的图像下载任务和每秒90000个以上的HTTP回显请求。性能高于PHP或其他图像处理服务器。
  • 用于中小型的图床服务

以下是zimg支持的功能:

  • 所有图片默认返回质量为75%,JPEG格式的压缩图片,这样肉眼无法识辨,但是体积减小
  • 获取宽度为x,被等比例缩放的图片
  • 获取旋转后的图片
  • 获取指定区域固定大小的图片
  • 获取特定尺寸的图片,由于与原图比例不同,尽可能展示最多的图片内容,缩放之后多余的部分需要裁掉
  • 获取特定尺寸的图片,要展示图片所有内容,因此图片会被拉伸到新的比例而变形
  • 获取特定尺寸的图片,但是不需要缩放,只用展示图片核心内容即可
  • 获取按指定百分比缩放的图片
  • 获取指定压缩比的图片
  • 获取去除颜色的图片
  • 获取指定格式的图片
  • 获取图片信息
  • 删除指定图片
  • 以上这些功能的提供,仅需要一个url+特定的参数,通过get方式就可以完成。

zimg的设计思路

想要在展现图片这件事情上有最好的表现,首先需要从整体业务中将图片服务部分分离出来。使用单独的域名和建立独立的图片服务器有很多好处,比如:

  • CDN分流。如果你有注意的话,热门网站的图片地址都有特殊的域名,比如微博的是sinaimg.cn,人人的是fmn.xnpic.com等等,域名不同可以在CDN解析的层面就做到非常明显的优化效果。
  • 浏览器并发连接数限制。一般来说,浏览器加载HTML资源时会建立很多的连接,并行地下载资源。不同的浏览器对同一主机的并发连接数限制是不同的。如果把图片服务器独立出来,就不会占用掉对主站连接数的名额,一定程度上提升了网站的性能。
  • 浏览器缓存。现在的浏览器都具有缓存功能,但是由于cookie的存在,大部分浏览器不会缓存带有cookie的请求,导致的结果是大量的图片请求无法命中,只能重新下载。独立域名的图片服务器,可以很大程度上缓解此问题。

图片服务器被独立出来之后,会面临两个选择,主流的方案是前端采用Nginx,中间是PHP或者自己开发的模块,后端是物理存储;比较特别一些的,比如Facebook,他们把图片的请求处理和存储合并成一体,叫做haystack,这样做的好处是,haystack只会处理与图片相关的请求,剥离了普通http服务器繁杂的功能,更加轻量高效,同时也使部署和运维难度降低。zimg采用的是与Facebook相似的策略,将图片处理的大权收归自己所有,绝大部分事情都由自己处理,除非特别必要,最小程度地引入第三方模块。

zimg的架构设计

为了极致的性能表现,zimg全部采用C语言开发,总体上分为三个层次,前端http处理层,中间图片处理层和后端的存储层。下图为zimg架构设计图:

  • http处理层引入基于libevent的libevhtp库,专门处理基本http请求 。
  • 图片处理层采用imagemagick库。
  • 存储层采用memcached缓存加直接读写硬盘的方案,后期可能会引入TFS4等。

为了避免数据库带来的性能瓶颈,zimg不引入结构化数据库,图片的查找全部采用哈希来解决。事实上图片服务器的设计,是一个在I/O与CPU运算之间的博弈过程,最好的策略当然是继续拆:CPU敏感的http和图片处理层部署于运算能力更强的机器上,内存敏感的cache层部署于内存更大的机器上,I/O敏感的物理存储层则放在配备SSD的机器上,但并不是所有人都能负担得起这么奢侈的配置。zimg折中成本和业务需求,目前只需要部署在一台服务器上。由于不同服务器硬件不同,I/O和CPU运算速度差异很大,很难一棒子定死。zimg所选择的思路是,尽量减少I/O,将压力放在CPU上,事实证明这样的思路基本没错,在硬盘性能很差的机器上效果更加明显;即使以后SSD全面普及,CPU的运算能力也会相应提升,总体来说zimg的方案也不会太失衡。

zimg的代码实现

虽然zimg在二进制实体上没有分模块,上面已经提到了原因,现阶段面向中小型的服务,单机部署即可,但是代码上是分离的。

main.c

main.c是程序的入口,主要功能是处理启动参数,部分参数功能如下:

  • -p [port] 监听端口号,默认4869
  • -t [thread_num] 线程数,默认4,请调整为具体服务器的CPU核心数
  • -k [max_keepalive_num] 最高保持连接数,默认1,不启用长连接,0为启用
  • -l 启用log,会带来很大的性能损失,自行斟酌是否开启
  • -M [memcached_ip] 启用缓存的连接IP
  • -m [memcached_port] 启用缓存的连接端口
  • -b [backlog_num] 每个线程的最大连接数,默认1024,酌情设置

zhttpd.c

zhttpd.c是解析http请求的部分,分为GET和POST两大部分,GET请求会根据请求的URL参数去寻找图片并转给图片处理层处理,最后将结果返回给用户;POST接收上传请求然后将图片存入计算好的路径中。为了实现zimg的总体设计愿景,zhttpd承担了很大部分的工作,也有一些关键点,下面捡重点的说一下:

  • 在zimg中图片的唯一Key值就是该图片的MD5,这样既可以隐藏路径,又能减少前端(指zimg前面的部分,可能是你的应用服务器)和zimg本身的存储压力,是避免引入结构化存储部分的关键,所以所有GET请求都是基于MD5拼接而成的。假如你的网站某个地方需要展示一张图片,这个图片原图的大小是1000*1000,但是你想要展示的地方只有300*300,你会怎么做呢?一般还是依靠CSS来进行控制,但是这样的话就会造成很多流量的浪费。为此,zimg提供了图片裁剪功能,你所需要做的就是在图片URL后面加上w=300&h=300(width和height)即可。
  • 在图片上传部分,如果我们的图片服务器前端采用Nginx,上传功能用PHP实现,需要写的代码很少,但是性能很差。首先PHP接收到Nginx传过来的请求后,会根据http协议(RFC1867)分离出其中的二进制文件,存储在一个临时目录里,等我们在PHP代码里使用$_FILES[“upfile”][tmp_name]获取到文件后计算MD5再存储到指定目录,在这个过程中有一次读文件一次写文件是多余的,其实最好的情况是我们拿到http请求中的二进制文件(最好在内存里),直接计算MD5然后存储。于是自己去阅读了PHP的源代码,自己实现了POST文件的解析,让http层直接和存储层连在了一起,提高了上传图片的性能。除了POST请求这个例子,zimg代码中有多处都体现了这种“减少磁盘I/O,尽量在内存中读写”和“避免内存复制”的思想,一点点的积累,最终将会带来优秀的表现。

zimg.c

zimg.c是调用imagemagick处理图片的部分,现阶段zimg服务于存储量在TB级别的单机图片服务器,所以存储路径采用2级子目录的方案。由于Linux同目录下的子目录数最好不要超过2000个,再加上MD5的值本身就是32位十六进制数,zimg就采取了一种非常取巧的方式:根据MD5的前六位进行哈希,1-3位转换为十六进制数后除以4,范围正好落在1024以内,以这个数作为第一级子目录;4-6位同样处理,作为第二级子目录;二级子目录下是以MD5命名的文件夹,每个MD5文件夹内存储图片的原图和其他根据需要存储的版本,假设一个图片平均占用空间200KB,一台zimg服务器支持的总容量就可以计算出来了:1024 * 1024 * 1024 * 200KB = 200TB

除了路径规划,zimg另一大功能就是压缩图片。从用户角度来说,zimg返回来的图片只要看起来跟原图差不多就行了,如果确实需要原图,也可以通过将所有参数置空的方式来获得。基于这样的条件,zimg.c对于所有转换的图片都进行了压缩,压缩之后肉眼几乎无法分辨,但是体积将减少67.05%。具体的处理方式为:

  • 图片裁剪时使用LanczosFilter滤镜;
  • 以75%的压缩率进行压缩;
  • 去除图片的Exif信息;
  • 转换为JPEG格式。

经过这样的处理之后可以很大程度的减少流量,实现设计目标。

zcache.c

zcache.c是引入memcached缓存的部分,引入缓存是很重要的,尤其是图片量级上升之后。在zimg中缓存被作为一个很重要的功能,几乎所有zimg.c中的查找部分都会先去检查缓存是否存在。比如:我想要a(代表某MD5)图片裁剪为100*100之后再灰白化的版本,那么过程是先去找a&w=100&h=100&g=1的缓存是否存在,不存在的话去找这个文件是否存在(这个请求所对应的文件名为 a/100*100pg),还不存在就去找这个分辨率的彩色图缓存是否存在,若依然不存在就去找彩色图文件是否存在(对应的文件名为 a/100*100p),若还是没有,那就去查询原图的缓,原图缓存依然未命中的话,只能打开原图文件了,然后开始裁剪,灰白化,然后返回给用户并存入缓存中。

可以看出,上面过程中如果某个环节命中缓存,就会相应地减少I/O或图片处理的运算次数。众所周知内存和硬盘的读写速度差距是巨大的,那么这样的设计对于热点图片抗压将会十分重要。

除了上述核心代码以外就是一些支持性的代码了,比如log部分,md5计算部分,util部分等。

zimg的部署安装(centos 7)

安装依赖库:

sudo yum install -y  wget openssl-devel cmake libevent-devel libjpeg-devel giflib-devel libpng-devel libwebp-devel ImageMagick-devel libmemcached-devel 
sudo yum install -y glibc-headers gcc-c++
sudo yum install -y build-essential nasm

安装依赖:

# openssl
mkdir /usr/local/zimg/openssl
cd /usr/local/zimg/openssl
wget http://www.openssl.org/source/openssl-1.0.1i.tar.gz
tar zxvf openssl-1.0.1i.tar.gz
cd openssl-1.0.1i
./config shared --prefix=/usr/local --openssldir=/usr/ssl
make && make install

# cmake
mkdir /usr/local/zimg/cmake
cd /usr/local/zimg/cmake
wget http://www.cmake.org/files/v3.0/cmake-3.0.1.tar.gz
tar xzvf cmake-3.0.1.tar.gz 
cd cmake-3.0.1
./bootstrap --prefix=/usr/local 
make && make install

# libevent
mkdir /usr/local/zimg/libevent
cd /usr/local/zimg/libevent
wget http://cloud.github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar zxvf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure --prefix=/usr/local 
make && make install

# libjpeg-turbo
mkdir /usr/local/zimg/libjpeg-turbo
cd /usr/local/zimg/libjpeg-turbo
wget https://downloads.sourceforge.net/project/libjpeg-turbo/1.3.1/libjpeg-turbo-1.3.1.tar.gz
tar zxvf libjpeg-turbo-1.3.1.tar.gz
cd libjpeg-turbo-1.3.1
./configure --prefix=/usr/local --with-jpeg8
make && make install

# webp
mkdir /usr/local/zimg/webp
cd /usr/local/zimg/
wget http://downloads.webmproject.org/releases/webp/libwebp-0.4.1.tar.gz
tar zxvf libwebp-0.4.1.tar.gz
cd libwebp-0.4.1
./configure
make
sudo make install

# jpegsrc
mkdir /usr/local/zimg/jpegsrc
cd /usr/local/zimg/
wget http://www.ijg.org/files/jpegsrc.v8b.tar.gz
tar -xf  jpegsrc.v8b.tar.gz
cd jpeg-8b
./configure --prefix=/usr/local --enable-shared --enable-static
make && make install

# imageMagic
mkdir /usr/local/zimg/imageMagick
cd /usr/local/zimg/
wget http://www.imagemagick.org/download/ImageMagick.tar.gz
tar zxvf ImageMagick.tar.gz
cd ImageMagick-6.9.1-10
./configure  --prefix=/usr/local 
make && make install

# libmemcached
wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
tar zxvf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18
./configure -prefix=/usr/local 
make && make install

可选的插件:

# memcached
wget http://www.memcached.org/files/memcached-1.4.19.tar.gz
tar zxvf memcached-1.4.19.tar.gz
cd memcached-1.4.19
./configure --prefix=/usr/local
make
make install

# beansdb
git clone https://github.com/douban/beansdb
cd beansdb
./configure --prefix=/usr/local
make

# benseye
git clone git@github.com:douban/beanseye.git
cd beanseye
make

# SSDB
wget --no-check-certificate https://github.com/ideawu/ssdb/archive/master.zip
unzip master
cd ssdb-master
make

# twemproxy
git clone git@github.com:twitter/twemproxy.git
cd twemproxy
autoreconf -fvi
./configure --enable-debug=log
make
src/nutcracker -h

构建zimg

cd /usr/local
#git clone https://github.com/buaazp/zimg -b master --depth=1
cd zimg   
make

安装成功后:

cd /usr/local/zimg/bin
./zimg conf/zimg.lua

打开http://localhost:4869看是否安装成功。

如果嫌手动安装太麻烦,就直接使用docker镜像

# 拉取zimg镜像
$ docker pull iknow0612/zimg
# 启动zimg容器
$ docker run -it -d -p 4869:4869 -v /data/zimg/:/zimg/bin/img --name my_zimg iknow0612/zimg sh app.sh

可以自己基于zimg再封装图片服务。

参考链接:

本机号码一键登录原理与应用

$
0
0

很多APP的目前都支持“本机号码一键登录”功能。本机号码一键登录是基于运营商独有网关认证能力推出的账号认证产品。用户只需一键授权,即可实现以本机号码注册/登录,相比先前的短信验证码流程体验更优。

目前市面上有很多厂商提供三网验证的服务,只不过是对三大运营商的包装。要了解具体的原理可直接看三大运营商相关的介绍。

中国移动

中国移动号码认证服务支支持移动、联通、电信三网号码。主要产品功能:

  • 一键登录:依托运营商的移动通信网络,采用通信网关取号技术,准确识别用户流量卡归属的手机号码。在获得用户授权后,App端(适配iOS和Android)可使用本机号码实现一键免密登录。
  • 本机号码校验:通过SDK/JSSDK提供的本机号码校验功能,调用网关鉴权方式,验证用户输入的手机号码或后台绑定的手机号码是否为本机流量卡归属号码,保证机卡不分离,用于快捷登入和安全风控等场景。本机号码校验现已适配iOS、Android、H5、小程序、快应用。

获取手机号码(一键登录):

本机号码校验:

取号方法

通过调用安卓的getPhoneInfo或iOS的getPhoneNumberCompletion,在用户无感知的情况下进行网络判断、蜂窝数据网络切换和网关取号等操作(以上操作均需消耗一定时间),返回取号是否成功的结果。

  • 取号所需网络环境:运营商取号能力是通过数据网关实现,取号过程须在数据流量打开的情况下才能进行。因此,用户如果关闭数据流量将无法取号;若当前信号弱或者网络有干扰时,时延会高于平均值,取号成功率降低。
  • 超时设置:SDK默认超时设置为8000ms,同时提供设置取号超的方法:安卓通过setOverTime设置,iOS通过setTimeoutInterval设置。
  • 运营商判断:SDK提供判断用户当前网络状态和流量卡所属运营商的方法,通过调用安卓SDK的getNetworkType或iOS的networkInfo可获得以上信息,以便对不同用户选择不同的运营商的SDK进行取号或选择不同的登录方式。
  • 关于取号缓存:应用取号或者授权成功后,SDK将取号的一个临时凭证缓存在本地,缓存能有效提高取号成功率、降低时延,并允许用户在未开启蜂窝网络时成功取号。SDK本身对缓存有处理逻辑,在某些场景下(如换卡)会让缓存提前失效,但若应用对安全性要求较高,也可以通过SDK提供的方法(安卓的delScrip和iOS的delectScrip)让缓存马上失效。

本机号码校验

通过调用安卓的mobileAuth或iOS的mobileAuthCompletion方法,可在不拉起授权页的情况下获得token。此时获得的token不能用于兑换用户的完整号码,只能用于校验本机号码与待校验号码的一致性。

  • 预取号:安卓的getPhoneInfo或iOS的getPhoneNumberCompletion所形成取号缓存scrip同样适用于本机号码校验,可提前进行取号以提高后续获取token的效率。
  • 适用场景:可在用户无感知的情况下校验本机号码与待校验号码的一致性,适用于所有基于手机号码进行风控的场景。

中国电信

中国电信天翼账号开放平台提供了:免密登录、手机号码认证、二次卡校验等服务。目前只支持中国电信用户。

免密认证:天翼账号免密认证方案,依托运营商的移动数据网络,采用“通信网关预登录”及 SIM卡识别等技术,准确识别用户手机号码,实现一键登录,并可有效规避短信验证码泄露风险。

手机号码校验:确认本机号码信息是否为当前用户本机号码。依托运营商的移动数据网络,采用“通信网关预登录”及 SIM卡识别等技术,判断用户输入的手机号与本机号码是否一致。

二次卡校验:众所周知,三大运营商每月注销的手机号约有2000多万,为避免手机号资源浪费,运营商会先回收已注销的手机号,然后重新销售,重新销售的卡即为二次卡。如果用户更换了手机号,且未与原账号解绑,可能存在安全风险。天翼账号二次卡校验方案, 使用独有的运营商二次号数据库,可快速检测用户号码更换状态,保障号码旧用户隐私安全。

中国联通

中国联通通信创新能力平台提供了 号码认证(一键登录)、匿名设备标识、匿名用户标识、空号识别、二次号验证、三要素验证等服务。

号码认证

依托运营商网关认证能力,面向互联网应用提供的本机手机号码一键注册登录及本机手机号码校验服务,支持APP、H5页面多场景应用。官方SDK,支持联通、移动、电信三网,智能化程度高,交互时间短,提升用户体验、提高拉新转化率;专利技术,性能可靠,降低空号注册登录、密码拦截盗取风险。

应用场景:

  • 一键登录
    • APP一键登录。手机在有蜂窝信号的环境中(若WIFI接入,SDK可瞬间切换至蜂窝信号再切回),可自动获取手机号码,帮助用户实现一键验证快捷登录,无需手动输入号码和短信验证,有利于提升用户体验,提高登录安全性。
    • H5一键登录。适用于手机接入蜂窝信号时,H5页面登录场景,用户只需输入4位本机号码即可实现快捷登录,减少降登录等待时间。

  • 本机校验。自动校验用户手机号与当前本机卡号的一致性,免输登录密码或短信验证码;适用于如手机号绑定、支付确认、积分兑换等需要具备安全校验能力的业务场景,提供仅限本机操作的安全风控机制。支持有蜂窝信号环境下的APP及正在使用蜂窝信号下的H5。

匿名设备标识

通过识别移动设备唯一ID,为客户提供基于用户和设备的标识方案,支持IOS和安卓系统,不依赖设备厂商,具备稳定性和唯一性,可关联安卓设备资产数据,帮助企业找回历史关联资产,实现基于用户画像的精准投放,有效识别设备篡改和营销作假,防止薅羊毛,避免金融风险等。

应用场景:

  • 风险识别:企业开展营销活动时,面对薅羊毛、黑产等,通过设备识别可有效识别判断参与用户,防止有限资源被无价值用户占用,支持APP/H5/小程序等全场景,覆盖安卓/IOS生态系统。适用于开展各类营销活动的企业,如电商、金融、游戏、生活等。
  • 场景营销:企业可基于不同营销场景(APP/H5/小程序)下的用户标识,进行跨应用用户分析与画像生成,实现精准营销推送。适用于需分析用户偏好、阅读习惯的企业,如购物、新闻、视频、娱乐、阅读等.

匿名用户标识

通过手机用户的公私网IP返回唯一串码,可以在保护用户手机号不泄露的情况下,提供用户唯一标识(即伪码)方案。仅支持联通用户。

应用场景:

  • 会员营销:会员营销活动时,用户领取会团优惠,平台方通过联通唯一识别平台将手机号转换成伪码供商户进行维系和发放。适用于入住商户无法获取平台用户手机号的场景。

空号识别

识别号码是否为真实有效的开机使用号码,对于不可达号码(例如关机、养卡等情况)采取相应的运营措施。识别过程一秒以内,支持大规模并发查询,且对用户无感知。帮助企业快速、高效、精准开展营销、维系等商业活动,减少营销成本。

应用场景:

  • 外呼中心:用于外呼中心,回访或者推介产品之前先对用户手机状态进行有效识别,关机、离网及不在服务区用户免拨叫,节省人力物力。
  • 存量用户维系:用于检测用户手机是否处于在网状态,便于企业精准开展后续营销活动。例如优惠券发放、权益下达等,为真实客户送权益,送利益。
  • 注册用户清洗:对注册会员的号码进行检测,对短期内高频次的注册行为进行监控,对可疑号码进行监控,及时清理批量注册的垃圾用户和数据、被占用数据,将恶意注册用户拒之门外,防止“薅羊毛党”恶意套利现象。使用号码检测功能,可以减少企业客户的营销维护成本,保护真实用户的权益。

二次号验证

次号验证产品是指核验手机号码在指定时间之后是否重新开户。

应用场景:

  • 注册/登录/密码找回:针对注册/登录/密码找回等场景,企业可通过使用联通二次号码验证产品识别当前注册登录号码是否为二次放号,从而避免二次放号用户使用原号码账号可能带来的用户隐私泄露和经济损失。

三要素验证

三要素验证产品提供联通用户的姓名、身份证号、手机号三要素的一致性核验服务。

应用场景:

  • 实名认证:针对金融、婚恋等应用系统中的实名注册场景,企业可通过联通三要素验证产品识别用户身份真实性和信息一致性,有助于防范用户风险。
  • 贷前审核:针对借贷、信用卡申请等金融场景,企业可以通过使用联通三要素验证产品识别申请用户信息真实度,助力用户风险评估和贷款决策。

Android/iOS判断是否使用代理或VPN

$
0
0

针对APP的黑产,我们提到部分用户会通过改变IP来绕过风控策略。更改IP比较方便的方法是使用代理IP或VPN。

在检测APP安全性是需要对是否使用代码和VPN做判断。以下为整理的一些代码供参考。

Android判断是否使用代理IP

private boolean isWifiProxy(Context context) {
    final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    String proxyAddress;
    int proxyPort;

    if (IS_ICS_OR_LATER) {
        proxyAddress = System.getProperty("http.proxyHost");
        String portStr = System.getProperty("http.proxyPort");
        proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
    } else {
        proxyAddress = android.net.Proxy.getHost(context);
        proxyPort = android.net.Proxy.getPort(context);
    }
    return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}

Android判断是否使用VPN

boolean checkVPN(ConnectivityManager connMgr) {
    //don't know why always returns null:
    NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_VPN);

    boolean isVpnConn = networkInfo == null ? false : networkInfo.isConnected();
    return isVpnConn;
}

iOS判断是否使用代理IP

#import "CETCProxyStatus.h"

@implementation CETCProxyStatus

+ (BOOL)getProxyStatus {
    NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]);

    NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[NSURL URLWithString:@"http://www.baidu.com"], (CFDictionaryRef)proxySettings) autorelease]);
    NSDictionary *settings = [proxies objectAtIndex:0];

    NSLog(@"host=%@", [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
    NSLog(@"port=%@", [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
    NSLog(@"type=%@", [settings objectForKey:(NSString *)kCFProxyTypeKey]);

    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@"kCFProxyTypeNone"]) {
        //没有设置代理
        return NO;
    } else {
        //设置代理了
        return YES;
    }
}

iOS判断是否使用VPN

- (BOOL)isVPNOn
{
   BOOL flag = NO;
   NSString *version = [UIDevice currentDevice].systemVersion;
   // need two ways to judge this.
   if (version.doubleValue >= 9.0)
   {
       NSDictionary *dict = CFBridgingRelease(CFNetworkCopySystemProxySettings());
       NSArray *keys = [dict[@"__SCOPED__"] allKeys];
       for (NSString *key in keys) {
           if ([key rangeOfString:@"tap"].location != NSNotFound ||
               [key rangeOfString:@"tun"].location != NSNotFound ||
               [key rangeOfString:@"ipsec"].location != NSNotFound ||
               [key rangeOfString:@"ppp"].location != NSNotFound){
               flag = YES;
               break;
           }
       }
   }
   else
   {
       struct ifaddrs *interfaces = NULL;
       struct ifaddrs *temp_addr = NULL;
       int success = 0;
       // retrieve the current interfaces - returns 0 on success
       success = getifaddrs(&interfaces);
       if (success == 0)
       {
           // Loop through linked list of interfaces
           temp_addr = interfaces;
           while (temp_addr != NULL)
           {
               NSString *string = [NSString stringWithFormat:@"%s" , temp_addr->ifa_name];
               if ([string rangeOfString:@"tap"].location != NSNotFound ||
                   [string rangeOfString:@"tun"].location != NSNotFound ||
                   [string rangeOfString:@"ipsec"].location != NSNotFound ||
                   [string rangeOfString:@"ppp"].location != NSNotFound)
               {
                   flag = YES;
                   break;
               }
               temp_addr = temp_addr->ifa_next;
           }
       }
       // Free memory
       freeifaddrs(interfaces);
   }


   return flag;
}

App深度链接与延迟深度链接

$
0
0

APP唤醒与场景还原

在App投放推广中,唤醒用户是常见的运营策略。想要让用户重新活跃起来,转化用户的行为,必须从场景上还原用户的路径,从根本上找到用户增长的奥秘。

在这个广告漫天的时代,相信大多数用户在使用App的时候都遇到类似的场景:在使用某资讯类App的时候,浏览到了淘宝的商品广告,当你点击该广告内容时,自动打开了你手机上已经安装的淘宝App并且定位到了该商品的详情页。

  • 作为用户,心里一定在想:“这购物真方便,都不要自己打开淘宝搜索商品了”。
  • 作为广告主,心里在想:“又拉活了一个用户,说不定还能带来一笔转化”。

那么,资讯类App是如何唤醒淘宝App的呢,淘宝App又是如何跳转至用户浏览的广告页面呢?

唤醒&场景还原,作为运营常用的拉活增长手段,有利于提升老用户在App的活跃度,场景化的唤醒更能激发用户的转化意愿。

其适用于如下几个营销场景:

  • 浏览器 -> 唤醒APP:用户A在手机上通过浏览器打开了某APP的M站或者官网,则引导用户打开该APP或者下载该APP。
  • 微信、QQ等 -> 唤醒APP:用户通过某APP分享了一条链接至微信或QQ,用户B点开该链接后,引导用户B打开该APP或者下载该APP。
  • 短信、邮件、二维码等-> 唤醒APP:用户A打开了某APP的推广短信,邮件或者扫描二维码等,引导用户打开该APP或者下载该APP。
  • 其他APP -> 唤醒APP:用户A通过第三方APP分享了一条链接至用户B,用户B点开该链接后,引导用户B打开指定APP或者下载指定APP。

常见唤醒APP方式

URL Scheme

URI Schema 是这几种调起方式中最原始的一种,只需要原生APP开发时注册scheme, 那么用户点击到此类链接时,会自动唤醒APP,借助于URL Router机制,则还可以跳转至指定页面。这种方式是当前使用最广泛,也是最简单的,但是需要手机APP支持URL Scheme。

iOS URL Scheme

iOS的沙盒机制

iOS选择沙盒来保障用户的隐私和安全,但沙盒也阻碍了应用间合理的信息共享,于是有了 URL Schemes 这个解决办法。一般来说,我们使用的智能设备上有许多我们的个人信息。比如:联系方式、银行卡/信用卡信息、支付宝/Paypal/各大商城的账户密码、照片甚至行程与位置信息等。

如果说,你设备上的每一个应用,不管是官方的还是你从任何商城安装的应用都可以随意地获取这些信息,那么你轻则收到骚扰信息和邮件、重则后果不堪设想。如何让这些信息不被其它应用随意使用,或者说,如何让这些信息仅在设备所有者本人知情并允许的情况下被使用,是所有智能设备与操作系统所要在乎的核心安全问题。

在 iOS 这个操作系统中,针对这个问题,苹果使用了名为「沙盒」的机制:应用只能访问它声明可能访问的资源。一切提交到 App Store 的应用都必须遵守这个机制。

在安全方面沙盒是个很好的解决办法,但是有些矫枉过正。敏感的个人信息我们不愿意透露,却不代表所有的信息我们都不想与其它应用共享。比如说我们要一次性地(没错,只按一次)把多个事件放到日历中,这些事件包含日期时间以及持续时间等信息,如果 App 之间信息不能沟通,就无法做到这点。类似于一次性添加多个日历事件这样的,我们在使用智能设备的过程中会遇到很多不必要的重复的步骤。大多数人对这些重复的步骤是不自觉的,就像当自己电脑里有一批文件需要批量重命名的时候,他们机械地重复着重命名的过程。但是当我们掌握了这些设备运行的模式,或者有了一些工具,我们就能将这些重复的步骤全部节省下来。在 iOS 上,我们可以利用的工具就是 URL Schemes。

URL Schemes 是什么

通过对比网页链接来理解 iOS 上的 URL Schemes,应该就容易多了。

URL Schemes 有两个单词:

  • URL,我们都很清楚,http://www.apple.com 就是个 URL,我们也叫它链接或网址;
  • Schemes,表示的是一个 URL 中的一个位置——最初始的位置,即 ://之前的那段字符。比如 http://www.apple.com 这个网址的 Schemes 是 http。

根据我们上面对 URL Schemes 的使用,我们可以很轻易地理解,在以本地应用为主的 iOS 上,我们可以像定位一个网页一样,用一种特殊的 URL 来定位一个应用甚至应用里某个具体的功能。而定位这个应用的,就应该这个应用的 URL 的 Schemes 部分,也就是开头儿那部分。比如短信,就是 sms:

你可以完全按照理解一个网页的 URL ——也就是它的网址——的方式来理解一个 iOS 应用的 URL,拿苹果的网站和 iOS 上的微信来做个简单对比:

网页(苹果)iOS 应用(微信)
网站首页/打开应用http://www.apple.comweixin://
子页面/具体功能http://www.apple.com/mac/weixin://dl/moments

在这里,http://www.apple.com 和 weixin:// 都声明了这是谁的地盘。然后在 http://www.apple.com 后面加上 /mac 就跳转到从属于 http://www.apple.com 的一个网页(Mac 页)上;同样,在 weixin:// 后面加上 dl/moments 就进入了微信的一个具体的功能——朋友圈。

但是,两者还有几个重要的区别:

  • 所有网页都一定有网址,不管是首页还是子页。但未必所有的应用都有自己的 URL Schemes,更不是每个应用的每个功能都有相应的 URL Schemes。实际上,现状是,大多数的应用只有用于打开应用的 URL Schemes,而有一些应用甚至没有用于打开应用的 URL Schemes。几乎没有所有功能都有对应 URL 的应用。所以,不要说某某应用烂,不支持国内应用。一个 App 是否支持 URL Schemes 要看那个 App 的作者是否在自己的作品里添加了 URL Schemes 相关的代码。
  • 一个网址只对应一个网页,但并非每个 URL Schemes 都只对应一款应用。这点是因为苹果没有对 URL Schemes 有不允许重复的硬性要求,所以曾经出现过有 App 使用支付宝的 URL Schemes 拦截支付帐号和密码的事件。
  • 一般网页的 URL 比较好预测,而 iOS 上的 URL Schemes 因为没有统一标准,所以非常难猜,通过猜来获取 iOS 应用的 URL Schemes 是不现实的。

基本 URL Schemes

基本 URL Schemes 的能力虽然简单有限,但使用情境却是最普遍的。所谓的基本 URL Schemes,是指一个 URL 的 Schemes 部分,比如上文提到的微信的 weixin:。这个部分的唯一功能,就是打开相应应用,而不能够跳转到任何功能。

绝大多数所谓支持 URL Schemes 的应用,一般都是只有这么一个部分,它一般是这个应用的名称,比如 OmniFocus 这款应用,它的基本 URL Schemes 就是 Omnifocus:。如果应用的主名称是个中文名的话,它的 URL Schemes 也许会是应用名的拼音,比如 墨客 这款应用,它的基本 URL Schemes 是 moke:。但网页 URL 和 iOS 应用的 URL 的三个重要区别,其中第三项,就是 iOS 上的 URL Schemes 并不规范,一个应用的 URL 可以是各种各样的:

  • Coursera 的 URL 是:coursera-mobile:
  • Duet 这款游戏的 URL 是:x-kumo-duet:
  • Monument 这款游戏的 URL 是:fb690517270143345:
  • Feedly 的 URL 是:fb129765800430121:
  • 扇贝新闻的 URL 是:wx95962d02b9c3e2f7:

它们目前并没有统一的规则,所以猜测一个应用的意义并不太大,你可以试试,但不要过于指望这种方式。下文会提到如何查找一个应用的基本 URL Schemes,只要那个应用支持 URL Schemes 就能找到。基本 URL Schemes 的能力虽然简单有限,但使用情境却是最普遍的。

复杂 URL Schemes

掌握复杂 URL Schemes 你才算初步有了打造适应自己使用情境的自动化流程的能力。iOS 应用的复杂 URL Schemes 就像网站的子页面的链接一样,在首页网址的基础上进行延伸。

具体来看,复杂 URL Schemes 有两种:一种是直接打开某个应用的某个功能,另一种是打开某个功能后直接填写预设的字符。就成为了一个更加实用的 URL Schemes,因为它不光直接让你进入了某个你需要的功能界面,还直接帮你填好了你需要的内容,而跳转后,你需要的只是按一下完成。

有了这样的 URL Schemes,应用之间才可以互相地协作。比如说,当我们在Mr. Reader上看到一篇文章里面写了一个不错的软件的时候,我们可以利用OmniFocus的 URL Schemes 将文章名保存到任务名的部分,把链接保存到备注的部分。在 iOS 8 的 Share Sheet 出现之前,这是唯一在 App 间传输信息的方式。

复杂 URL Schemes 有一个特殊的用例是 Jumpback,字典类 App 用它的很多,比如欧路词典。传统的使用欧陆字典查询单词的 URL Schemes 是:

eudic://dict/想查的单词

在这个基础上,加上一句 Jumpback:

eudic://dict/想查的单词?jumpback=指定URL

就能够做到查完单词以后,按左上角或左下角的返回按钮,回到你想要回到的 App。

并不是每个应用都有它的复杂 URL Schemes,但一般来说,有系统规范的复杂 URL Schemes 的应用都是同类应用中的佼佼者。

x-callback-URL

从一个应用的界面跳转到了另一个应用后,就会在左上角看到回到上一个应用的字样,轻触就能返回到上一个应用。这样的事情我们在打造自己的自动化操作的时候毫无疑问也会想要做到,前面说过的Jumpback是一个选择,除此之外还有更强力的代替者——x-callback-URL,它还有 iOS 9 上「返回上个应用」这一功能不能代替的地方。但是不可否认的是,x-callback-URL 的使用情境比较少,使用难度却又比较高。

我们前面谈到的 URL Schemes 都只有一个目的,不管结果是什么,跳转完成后就会停留在跳转后的应用的界面。但在使用 URL Schemes 的时候,运行结果有时候可能成功,有时候可能失败,有时候我们也会手动将其取消。

如果我们还想让应用根据不同的结果有对应的反应,就要用到 x-callback-URL。比如当上一个 URL Schemes 运行成功以后,我是要回到跳转前的应用?还是要接另一个动作(接上另一段 URL Schemes,打开另一个应用的某个功能)?无论是跳转回上个应用还是打开另一个动作,只要你在运行完一个 URL Schemes 后还想再利用一段新的 URL Schemes 做下一件事,就要靠 x-callback-URL,它的固定语法是:

  • 在一个 URL Schemes 后面接&x-success,表示前一个 URL 成功以后下一步做什么;
  • 在一个 URL Schemes 后面接&x-error,表示前一个 URL 失败以后下一步做什么;
  • 在一个 URL Schemes 后面接&x-cancel,表示取消前一个 URL 的操作结果后下一步做什么;
  • 还有一个&x-source 我们遇到了再说。

URL 编码(Encode)

URL 中的字符可以分为两类,一部分可以在链接中正常显示,比如字母、数字还有/等一部分符号。除此之外,全部不能正常显示,需要进行编码才能够作为 URL 的一部分出现。比如空格,在 URL 中就必须表示为%20转换的规则不用深究,网上有很多工具提供 URL 的编码和解码功能,可以把编码过的乱七八糟的 URL 解码为我们看得清爽的字符。这些工具当然也可以反过来把我们常用的字符转换成可以在 URL 中使用的字符。

所以理论上,所有 URL 不支持的字符,都要编码。

自定义 URL Scheme 进行跳转

如果我们希望别人打开我们的 app(名字叫做 SchemeDemo),需要注册自定义 URL Scheme,通过 info.plist –> URL Types –> item0 –> URL Schemes –> 你的TestScheme 来设置,详细步骤如下:

1、点击工程中的 info.plist 文件,当该文件显示在如下窗口时,在列表顶部鼠标选中 Information Property List,选择 +,然后向下滚动弹出的列表并选择 URL types,类型为 NSArray。

2、点击 URL types 左边剪头打开列表,可以看到 Item 0,一个字典实体。展开 Item 0,可以看到 URL Identifier,一个字符串对象。该字符串是你自定义的 URL scheme 的名字。建议采用反转域名的方法保证该名字的唯一性,比如 com.yourCompany.yourApp。

3、点击 Item 0 新增一行,从下拉列表中选择 URL Schemes,敲击键盘回车键完成插入。注意 URL Schemes 是一个数组,允许应用定义多个 URL schemes。

4、展开 URL Schemes 该数据并点击 Item 0。你将在这里定义自定义 URL scheme 的名字。只需要名字,不要在后面追加 ://,比如,如果你输入 iOSDevApp,你的自定义 url 就是 iOSDevApp://。

此时,整个定义如下图:

Android URL Scheme

Android中的自定义的URL Scheme是一种页面内跳转协议,也可以被称为URLRouter,就是通过类似打开网页的方式去通过路由打开一个Activity,而非直接通过显式Intent方式去进行跳转。这样隐式intent的方法跳转好处如下:

  • 降低耦合性:不需要知道具体要跳转哪个界面,只需要根据需求,按照约定好的URL路由协议发送Intent即可;
  • 更为安全:不显示Intent跳转,只要是符合协议的Intent都会有对应的Activity来匹配,避免了跳转到不该出现的页面;
  • 更为灵活: 有着更为广泛的应用场景,多种场景中都可以使用URL Scheme

URL Scheme协议格式

上文已经说过,URL Scheme是就通过类似打开网页的方式去通过路由打开一个Activity,其协议格式和我们打开网页输入的网址类似。

一个完整的完整的URL Scheme协议格式由scheme、host、port、path和query组成,其结构如下所示:

<scheme>://<host>:<port>/<path>?<query>

其中scheme既可以是Android中常见的协议,也可以是我们自定义的协议。Android中常见的协议包括content协议、http协议、file协议等,自定义协议可以使用自定义的字符串,当我们启动第三方的应用时候,多是使用自定义协议。

如下是一个自定义协议的URI:xl://goods:8888/goodsDetail?goodsId=10011002

通过上面的路径 Scheme、Host、port、path、query全部包含:

  • xl,即为Scheme,代表该Scheme 协议名称
  • goods,即为Host,代表Scheme作用于哪个地址域
  • 8888,即为port,代表该路径的端口号
  • goodsDetail,即为path, 代表Scheme指定的页面
  • goodsId,即为query,代表传递的参数

URL Scheme的使用方法

URL Scheme的使用方法简要言之就是先在manifest中配置能接受Scheme方式启动的activity;当需要调用时,将Scheme协议的URi以Data的形式加入到Intent中,隐式调用该activity。

1)在AndroidManifest.xml中对<activity >标签增加<intent-filter>设置Scheme

<activity android:name=".MainActivity"><intent-filter> <!--正常启动--><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter><intent-filter> <!--URL Scheme启动--><!--必有项--><action android:name="android.intent.action.VIEW"/><!--如果希望该应用可以通过浏览器的连接启动,则添加该项--><category android:name="android.intent.category.BROWSABLE"/><!--表示该页面可以被隐式调用,必须加上该项--><category android:name="android.intent.category.DEFAULT"/><!--协议部分--><data android:scheme="urlscheme"
            android:host="auth_activity"></intent-filter><intent-filter><action   android:name="emms.intent.action.check_authorization"/><category android:name="android.intent.category.DEFAULT"/><category android:name="emms.intent.category.authorization"/></intent-filter></activity>

上面的设置中可以看到,MainActivity包含多个<intent-filter>设置,第一个是正常的启动,也就是在应用列表中启动;第二个是通过URL Scheme方式启动,其本身也是隐式Intent调用的一种,不同在于添加了<data>属性,定义了其接受URL Scheme协议格式为urlschemel://auth_activity

这里需要说明下,URL Scheme协议格式中,组成URI的这些属性在<data >标签中都是可选的 ,但存在如下的依赖关系:

  • 如果没有指定scheme,那么host参数会被忽略
  • 如果没有指定host,那么port参数会被忽略
  • 如果scheme和host都没有指定,path参数会被忽略

当我们将intent对象中的Uri参数与intent-filter中的<data>标签指定的URI格式进行对比时,我们只对比intent-filter的<data>标签指定的部分,例如:

  • 如果intent-filter中只指定了scheme,那么所有带有该sheme的URI都能匹配到该intent-filter。
  • 如果intent-filter中只指定了scheme和authority(authority包括host和port两部分)而没有指定path,那么所有具有相同scheme和authority的URI都能匹配到该intent-filter,而不用考虑path为何值。
  • 如果intent-filter中同时指定了scheme、authority和path,那么只有具有相同scheme、authority和path的URI才能匹配到该intent-filter。

需要注意的是,intent-filter的<data>标签在指定path的值时,可以在里面使用通配符*,起到部分匹配的效果。

2)使用URL启动Activity

Uri data = Uri.parse("urlschemel://auth_activity");
 Intent intent = new Intent(Intent.ACTION_VIEW,data);
 //保证新启动的APP有单独的堆栈,如果希望新启动的APP和原有APP使用同一个堆栈则去掉该项
 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 try {
   startActivityForResult(intent, RESULT_OK);
 } catch (Exception e) {
   e.printStackTrace();
   Toast.makeText(MainActivity.this, "没有匹配的APP,请下载安装",Toast.LENGTH_SHORT).show();
 }

当然可以在网页中调用:

<a href="urlschemel://auth_activity">打开新的应用</a>

或者是在JS中调用:

window.location = "urlschemel://auth_activity";

3)如何判断URL Scheme是否有效

boolean checkUrlScheme(Intent intent){
    PackageManager packageManager = getPackageManager();
    List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
    return !activities.isEmpty();
}

将子APP在Home Launcher中隐藏

有时候需要把一些辅助性的、较为独立的APP在Home Launcher中隐藏起来,只允许一些特定的APP调用。这个时候,我们可以利用URL Scheme协议来做到这一点,设置AndroidManifest.xml中对<activity >标签如下:

<activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/><category android:name="android.intent.category.BROWSABLE"/><!--表示该页面可以被隐式调用,必须加上该项--><category android:name="android.intent.category.DEFAULT"/><!--协议部分--><data android:scheme="urlscheme"
            android:host="auth_activity"></intent-filter></activity>

因为Home Launcher列出的应用图标要求必须有Activity同时满足:

<action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/>

上面的配置中有多余的category和data限制存在,所以并不匹配,不会在Home Launcher出现,但是可以使用URL Scheme来启动。

Uri data = Uri.parse("urlschemel://auth_activity");
Intent intent = new Intent(Intent.ACTION_MAIN,data);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

这样就可以将一组APP设置一个统一的入口,然后根据实际需要在调用不同子APP,即所谓的APP业务组件化。

URL Scheme的缺点

提示框

我们只能通过固定协议格式的链接来实现跳转,而且打开H5页面时,会出现一个提示框:“是否打开XXX”。用户确认了才会跳转到App中,增加了用户流程。

未安装APP导致的异常

错误处理情况因平台不同,难以统一处理,部分APP会直接跳错误页(比如Android Chrome/41,iOS中老版的Lofter);也有的停留在原页面,但弹出提示“无法打开网页”(比如iOS7);iOS8以及最新的Android Chrome/43 目前都是直接停留在当前页,不会跳出错误提示。

场景支持情况

iOS在实际使用中,腾讯系的微信,QQ明确禁止使用,iOS9以后Safari不再支持通过js,iframe等来触发scheme跳转,并且还加入了确认机制,使得通过js超时机制来自动唤醒APP的方式基本不可用;Android平台则各个app厂商差异很大,比如Chrome从25及以后就同Safari情况一样。

命名冲突或劫持

如果手机中同時存在有两个应用都使用相同的 URL Scheme,那么唤起目标应用时,系统会优先唤起哪一个呢?Apple在后续iOS版本(iOS 11)采用了先到先得原则,如果使用了同一个URL Scheme,只有先安装的app会被启动。然而,攻击者还是可以通过其他方法来利用这个漏洞。

打开方式被禁

微信、QQ等把URL Scheme 打开App这种方式给禁了,但是它们都各自维护着一个白名单,如果Scheme不在该白名单内,那么就不能在他们的App内打开这个App(如果被封锁了那么用户只能通过右上角浏览器内打开App)

iOS Universal Links

Universal Link 是苹果在 WWDC 上提出的 iOS9 的新特性之一。此特性类似于深层链接,并能够方便地通过打开一个 Https 链接来直接启动您的客户端应用(手机有安装 App)。对比起以往所使用的 URL Scheme,这种新特性在实现 web-app 的无缝链接时能够提供极佳的用户体验。

当你的应用支持 Universal Link(通用链接),当用户点击一个链接是可以跳转到你的网站并获得无缝重定向到对应的 APP,且不需要通过 Safari 浏览器。如果你的应用不支持的话,则会在 Safari 中打开该链接。

使用 Universal Link(通用链接)可以让用户在 Safari 浏览器或者其他 APP 的 webview 中拉起相应的 APP,也可以在 APP 中使用相应的功能,从而来把用户引流到 APP 中。这具体是一种怎样的情景呢?举个例子,你的用户 safari 里面浏览一个你们公司的网页,而此时用户手机也同时安装有你们公司的 App;而 Universal Link 能够使得用户在打开某个详情页时直接打开你的 app 并到达 app 中相应的内容页面,从而实施用户想要的操作(例如查看某条新闻,查看某个商品的明细等等)。比如在 Safari 浏览器中进入淘宝网页点击打开 APP 则会使用 Universal Link(通用链接)来拉起淘宝 APP。

Universal link 的特点:

  • 唯一性: 不像自定义的 URL Scheme,因为它使用标准的 HTTPS 协议链接到你的 web 站点,所以一般不会被其它的 APP 所声明。另外,URL scheme 因为是自定义的协议,所以在没有安装 app 的情况下是无法直接打开的(在 Safari 中还会出现一个不可打开的弹窗),而 Universal Link(通用链接)本身是一个 HTTPS 链接,所以有更好的兼容性;
  • 安全: 当用户的手机上安装了你的 APP,那么系统会去你配置的网站上去下载你上传上去的说明文件(这个说明文件声明了当前该 HTTPS 链接可以打开那些 APP)。因为只有你自己才能上传文件到你网站的根目录,所以你的网站和你的 APP 之间的关联是安全的;
  • 可变: 当用户手机上没有安装你的 APP 的时候,Universal Link(通用链接)也能够工作。如果你愿意,在没有安装你的 app 的时候,用户点击链接,会在 safari 中展示你网站的内容;
  • 简单: 一个 HTTPS 的链接,可以同时作用于网站和 APP;
  • 私有: 其它 APP 可以在不需要知道你的 APP 是否安装了的情况下和你的 APP 相互通信。

Universal Link的优点:

  • Custom URL scheme是自定义的协议,因此在没有安装该app的情况下是无法直接打开的。而Universal Links本身也就是一个能够指向一个web页面或者app中的内容页的标准的web link(形如https://example.com) 因此能够很好的兼容其他情况。也就是说,当已经安装了这个app的时候,不需要加载任何web页面,app就会立即启动;当这个app没有安装的时候,就会默认地从当前浏览器中重定向到App Store中引导用户去下载安装这个app。
  • Universal links是从服务器上查询是哪个app需要被打开,因此不存在Custom URL scheme那样名字被抢占、冲突的情况。
  • Universal links支持从其他app中的UIWebView中跳转到目标app
  • 安全性,用universl link去打开的时候,只有你可以通过创建和上传一个允许这个网页去通过这个URL去打开你的app的文件。
  • 隐私性,提供Universal link给别的app进行app间的交流,然而对方并不能够用这个方法去检测你的app是否被安装。

Universal link 配置和运行

1) 配置 App ID 支持 Associated Domains

登录https://developer.apple.com/ 苹果开发者中心,找到对应的 App ID,在 Application Services 列表里有 Associated Domains 一条,把它变为 Enabled 就可以了。

2 )配置 iOS App 工程

Xcode 11.0 版本:工程配置中相应功能:targets->Signing&Capabilites->Capability->Associated Domains,在其中的 Domains 中填入你想支持的域名,也必须必须以 applinks:为前缀。

Xcode 11.0 以下版本:工程配置中相应功能:targets->Capabilites->Associated Domains,在其中的 Domains 中填入你想支持的域名,必须以 applinks:为前缀。

3) 配置和上传 apple-app-association

究竟哪些的 url 会被识别为 Universal Link,全看这个 apple-app-association 文件 Apple Document UniversalLinks.html

  • 你的域名必须支持 Https
  • 域名根目录或者.well-known目录下放这个文件apple-app-association,不带任何后缀。
  • 文件为 json 保存为文本即可
  • json 按着官网要求填写即可

apple-app-site-association模板:

{"applinks": {"apps": [],"details": [
            {"appID": "9JA89QQLNQ.com.apple.wwdc","paths": [ "/wwdc/news/", "/videos/wwdc/2015/*"]
            },
            {"appID": "ABCD1234.com.apple.wwdc","paths": [ "*" ]
            }
        ]
    }
}

说明:

  • appID:组成方式是yourapp’s bundle identifier。如上面的 9JA89QQLNQ 就是 teamId。登陆开发者中心,在 Account -> Membership 里面可以找到 Team ID。
  • paths:设定你的 app 支持的路径列表,只有这些指定的路径的链接,才能被 app 所处理。星号的写法代表了可识 别域名下所有链接。

上传指定文件:上传该文件到你的域名所对应的根目录或者.well-known 目录下,这是为了苹果能获取到你上传的文件。上传完后,自己先访问一下,看看是否能够获取到,当你在浏览器中输入这个文件链接后,应该是直接下载 apple-app-site-association 文件。

验证 Universal link 生效

可以使用 iOS 自带的备忘录程序,输入链接,长按链接,如果弹出菜单中有”在‘xxx’中打开”,即表示配置生效。或者将要测试的网址在Safari中打开,在出现的网页上方下滑,可以看到有在”xxx”应用中打开, 出现菜单。

当点击某个链接,直接可以进我们的 app 了,但是我们的目的是要能够获取到用户进来的链接,根据链接来展示给用户相应的内容。

在AppDelegate里中实现代理方法,官方链接: Handling Universal Links

Objective-C:

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb])
    {
        NSURL *url = userActivity.webpageURL;
        if (url是我们希望处理的)
        {
            //进行我们的处理
        }
        else
        {
            [[UIApplication sharedApplication] openURL:url];
        }
    }
     
    return YES;
}

Swift:

func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([Any]?) -> Void) -> Bool
{
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let incomingURL = userActivity.webpageURL,
        let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
        let path = components.path,
        let params = components.queryItems else {
            return false
    }
    print("path = \(path)")
    if let albumName = params.first(where: { $0.name == "albumname" } )?.value,
        let photoIndex = params.first(where: { $0.name == "index" })?.value {
        print("album = \(albumName)")
        print("photoIndex = \(photoIndex)")
        return true
    } else {
        print("Either album name or photo index missing")
        return false
    }
}

Universal link跨域问题

Universal Link有跨域问题,Universal Link必须要求跨域,如果不跨域,就不会跳转(iOS 9.2之后的改动)要求具备跨域能力即可, 假如当前网页的域名是A,当前网页发起跳转的域名是B,必须要求B和A是不同域名才会触发Universal Link,如果B和A是相同域名,只会继续在当前WebView里面进行跳转,哪怕你的Universal Link一切正常,根本不会打开App 2. Universal Link请求apple-app-site-association时机

  • 当我们的App在设备上第一次运行时,如果支持Associated Domains功能,那么iOS会自动去GET定义的Domain下的apple-app-site-association文件
  • iOS会先请求https://domain.com/.well-known/apple-app-site-association,如果此文件请求不到,再去请求https://domain.com/apple-app-site-association,所以如果想要避免服务器接收过多GET请求,可以直接把apple-app-site-association放在./well-known目录下

Universal Link更新

apple-app-association的更新时机有以下两种:

  • 每次App安装后的第一次Launch,会拉取apple-app-association
  • Appstore每次App的版本更新后的第一次Launch,也会更新apple-app-association

所以反复重新杀APP重开完全没用,删了App重装确实有用,但不可能让用户这么去做。也就是说,一旦不小心因为意外apple-app-association,想要挽回又让那部分用户无感,App再发一个版本就好了。

Universal Link用户行为

Universal Link 触发后打开App,这时候App的状态栏右上角会有文字提示来自XXApp,可以点状态栏的文字快速返回原来的AP

如果用户点了返回微信,就会被苹果记住,认为用户并不需要跳出原App打开新App,因此这个App的Universal Link会被关闭,再也无效。

想要开启也不是不行,让用户重新用safari打开,universal link的页面,然后会出现很像苹果smart bar的东西,那个东西点了后就能打开

H5 端的 Universal Link 业务部署

H5 端的 Universal Link 跳转,从产品经理的角度看,需要满足以下 2 个需求:

  • 如果已安装 App,跳转对应界面
  • 如果没安装 App,跳转 App 下载界面

H5 端部署 Universal Link 示例:

router.use('/view', function (req, res, next) {
    var path = req.path;
    res.redirect('https://www.xxx.com/view' + path + '?xxx=xxx');
});

整个效果就是

  • 跳转https://www.xxx.com/view/*
    • 已安装 App
      • 打开 App 触发 handleUniversalLink
      • 走到/view/分支,拼接阅读页路由跳转
    • 未安装 AppWebView
      • 原地跳转https://www.xxx.com/view/*
      • 命中服务器的重定向逻辑
      • 重定向到https://www.xxx.com/view/*
      • 打开相应的 H5 页面

Chrome Intent

在很多应用中需要我们从浏览器中直接启动应用,大多数采用的是上面提到的第一种scheme的方式,问题是如果手机中没有应用,该url会跳转到一个错误的界面。

Google官方在chrome中推出了一种Android Intents的方式来实现应用启动,通过在iframe中设置src为:

  • intent:HOST/URI-path // Optional host
  • #Intent;
  • package=[string];
  • action=[string];
  • category=[string];
  • component=[string];
  • scheme=[string];
  • end;

mainfest文件中定义要启动的activity:

<activity
    android:name=".ui.activity.SplashActivity"
    android:exported="true"
    android:screenOrientation="portrait"
    android:theme="@style/NormalSplash"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data
            android:host="app.puxinwangxiao.com"
            android:scheme="pxwxstudent" /></intent-filter></activity>

定义一个a标签为

<a href="intent://app.domain.com/#Intent;scheme=xyz;package=com.xxx.xxx;end">open Android App</a>

在浏览器中点击a标签,就可以启动应用程序的对应activity了。如果手机中没有相应的应用,防止跳转到错误页面,将a标签设置为:

<a href="intent://app.domain.com/#Intent;scheme= xyz;package=com.xxx.xxx;S.browser_fallback_url=https://www.domain.com;end">open Android App</a>

这样如果没有对应应用,该链接就会跳转到==S.browser_fallback_url==指定的url上。

备注:很多第三方浏览器会拦截掉chrome intent启动应用的请求。

Android App Link

类似 Universal Links, Android App Link采取类似的机制:使用标准的 Web 页面 URL,同时绑定对应的 App。在 Android >= 6 的系统中支持这一机制。

Android App Links有以下几点好处:

  • 安全性/特殊性:由于Android App Links使用了HTTP/HTTPS URL的方式向开发者的服务器进行连接认证,所以其他应用无法使用我们的链接
  • 无缝的用户体验:当用户未安装我们的应用时,由于使用的是HTTP/HTTPS URL,会直接打开一个网页,我们可以在这个网页中展示应用介绍等,而不是显示404或者是其他错误页面
  • 支持Instant Apps:可以使用App Links直接打开一个未安装的Instant App
  • 支持Google Search或其他浏览器:用户可以直接在Google Search/Google Assistant/手机浏览器/屏幕搜索中直接通过点击一个URL来打开我们的指定页面

要添加Android App Links到应用中,需要在应用里定义通过Http(s)地址打开应用的intent filter,并验证你确实拥有该应用和该网站。如果系统成功验证到你拥有该网站,那么系统会直接把URL对应的intent路由到你的应用。

为了验证你对应用和网站的所有权,以下两个步骤是必须的:

  • 在AndroidManifest里要求系统自动进行App Links的所有权验证。这个配置会告诉Android系统去验证你的应用是否属于在intent filter内指定的URL域名。
  • 在以下链接地址里,放置一个数字资产链接的Json文件,声明你的网址和应用之间的关系:https://domain.name/.well-known/assetlinks.json

在app中激活App links

告诉安卓系统去验证app与域名之间的关系。因为我们已经在app中注册了该域名,就不会再出现弹框。找到AndroidManifest.xml文件,在处理深度链接路由的activity(第三步将讲解如何创建这样的Activity)中添加android:autoVerify=”true”到intent-filter:

<activity
  android:name=".ParseDeepLinkActivity"
  android:alwaysRetainTaskState="true"
  android:launchMode="singleTask"
  android:noHistory="true"
  android:theme="@android:style/Theme.Translucent.NoTitleBar"><intent-filter android:autoVerify="true"><data android:scheme="http" android:host="yourdomain.com" /><data android:scheme="https" android:host="yourdomain.com" /><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /></intent-filter></activity>

这个配置告诉安卓去验证一个文件,这个文件地址是https://yourdomain.com/.well-known/statements.json。如果存在这个文件,同时验证成功,那么用户点击该域名之下的链接时,就可以直接到app,弹出框就可以避免。否则app就没有成为默认选项,弹出框就会呈现给用户。

上传web-app关联文件(statements.json)

基于安全的原因,这个文件必须通过SSL的GET请求获得。

[{"relation": ["delegate_permission/common.handle_all_urls"],"target": {"namespace": "android_app","package_name": "com.mycompany.myapp","sha256_cert_fingerprints": ["6C:EC:C5:0E:34:AE....EB:0C:9B"]
  }
}]

可以在AndroidManifest.xml 文件中找到app的package name。还需要通过在终端中执行ava keytool 产生一个sha256指纹:

keytool -list -v -keystore /path/to/app/release-key.keystore

你需要向keystore添加持有app release keys的 app路径。这个路径依赖于项目设置,因此不同的app是不同的。可以在谷歌的文档中找到更多关于如何找到keystore的信息。

最后,上传这个文件到服务器的/.well-known/statements.json。为了避免今后每个app链接请求都访问网络,安卓只会在app安装的时候检查这个文件。如果你能在请求https://yourdomain.com/.well-known/statements.json 的时候看到这个文件(替换成自己的域名哈),那么可以继续下一步了。

注:目前可以通过http获得这个文件,但是在M最终版里则只能通过HTTPS验证。确保你的web站点支持HTTPS请求。

在app中处理applink

public class ParseDeepLinkActivity extends Activity {
  public static final String PRODUCTS_DEEP_LINK = "/products";
  public static final String XMAS_DEEP_LINK = "/campaigns/xmas";
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    // Extrapolates the deeplink data
    Intent intent = getIntent();
    Uri deeplink = intent.getData();
    // Parse the deeplink and take the adequate action 
    if (deeplink != null) {
      parseDeepLink(deeplink);
    }
  }
  private void parseDeepLink(Uri deeplink) {
    // The path of the deep link, e.g. '/products/123?coupon=save90'
    String path = deeplink.getPath();
    if (path.startsWith(PRODUCTS_DEEP_LINK)) {
      // Handles a product deep link
      Intent intent = new Intent(this, ProductActivity.class);
      intent.putExtra("id", deeplink.getLastPathSegment()); // 123
      intent.putExtra("coupon", deeplink.getQueryParameter("coupon")); // save90
      startActivity(intent);
    } else if (XMAS_DEEP_LINK.equals(path)) {
      // Handles a special xmas deep link
      startActivity(new Intent(this, XmasCampaign.class));
    }  else {
      // Fall back to the main activity
      startActivity(new Intent(context, MainActivity.class));
    }
  }
}

深度链接 Deeplink

深度链接是指当用户打开移动应用时向其提供个性化的内容,或将用户带到应用内特定位置的操作。通过这种操作,您可以为用户提供优质的用户体验,从而极大加强用户与应用的互动。

  • 在浏览器或者短信中打开App,如果安装了就能直接打开App,否则引导下载。对于Android而言,这里主要牵扯的技术就是deeplink,也可以简单看成scheme。
  • 其实,AppLink就是特殊的Deeplink,只不过它多了一种类似于验证机制,如果验证通过,就设置默认打开,如果验证不过,则退化为deeplink,如果单从APP端来看,区别主要在Manifest文件中的android:autoVerify=”true”。
  • 还有在微信中,也可以作出这样操作。如果用户已经安装App,点击跳转App则会通过应用宝打开该应用并且跳转到相应的页面,这种也是一种AppLink。

总结来说,Deeplink,又叫深度链接技术,是指在App/短信/广告里点击链接,能直接跳转到目标App具体位置的技术,深度链接打破了网站与App间的壁垒,成为实现网站与App相互跳转的桥梁。开发者不仅可以通过deeplink实现网站到App互相跳转,也可以实现从多个平台(QQ、微信、微博、Twitter、Facebook、短信、各大浏览器等)到App内指定页的跳转。例如用户将电商App内的一个详情页链接通过短信形式发送给其他亲友,用户点击短信内的链接就能打开对应的H5页面,然后直接跳转到电商App内的指定详情页,而不是App首页。如果用户并未安装App,那么就会跳转到App下载页面。等用户安装打开App后仍然能跳转到指定页面。Deeplink技术不仅可以实现场景快速还原,缩短用户使用路径,更重要的是能够用于App拉新推广场景,降低用户流失率。

Deep Linking只是一个概念, 是指通过一个链接进入另一个网站/App,并直接浏览其内部的某个页面。 Deep Linking 给用户带来的是非常顺滑的浏览体验,尤其在 Web 世界中 Deep Linking 的实现非常容易。

但如果要进入 App 并定位到对应的页面则较为困难,URI Scheme, Universal Links, Android App Links, 以及 Chrome Intent 都是为了解决从 Web 页面 Deep Linking 到 App 而做的尝试。

每种实现方式都有其适用的平台和浏览器,要兼容多数浏览器需要根据 User Agent 应用不同的策略。 这些实现方式的行为也不一致,可能需要配合产品需求才能确定结合哪几种实现方式。 这些实现在下文有详细的介绍,下表中先大概列举各种实现的区别:

技术Universal LinkAndroid App LinkURI SchemeChrome Intent
平台要求>= iOS 9>= Android 6Chrome 1 < 25, iOSChrome 1 >= 25
未安装表现打开 Web 页面打开 Web 页面发生错误可以打开 Web 页面
能否不发生跳转不能不能
能否去下载页面不能
iframe 触发不支持不支持Chrome 1 <= 18, iOS < 9不支持
链接格式 2正常的 URL正常的 URL自定义协议的 URLintent 协议的 URL

JavaScript 获取成功与否

上述所有调起方式都必须通过页面请求(除了特定情况下的 iframe), 没有 JavaScript API 可用。理论上不存在调起结果回调。

但实践上可以通过 setTimeout 来检查页面是否还在运行,以及页面是否中断过。 原理是如果页面切走(这意味着成功调起),setTimeout 回调的触发时间点会延迟。 这一方式不够准确,但只有这一种办法。

  • 如果被判定为调起成功,则一定是调起成功的。
  • 如果被判定为调起失败,则有可能调起成功。

即存在很大概率的 False Negative,但不存在 False Positive。

关于国产浏览器

这一部分讨论这三个浏览器的表现:UC, 微信,QQ。它们占据了系统浏览器之外的大多数市场份额,表现也惊人地一致。

  • Android 下它们会拦截掉所有页面调起。需要提示用户从系统浏览器中打开。
  • iOS 下它们会拦截 URI Scheme,既不会弹框也不会调起。对于 Universal Link 会直接打开 Web 页面而不调起。

其中 UC 浏览器在 iOS < 9 的环境下尝试 URI Scheme 调起很可能会直接崩溃。 由于浏览器兼容性问题,以及 App 安装率不可能是 100%,调起成功率一般会很低尤其在 Android 下。

延迟深度链接(Deferred Deep Linking)

相比deeplink,它增加了判断APP是否被安装,用户匹配的2个功能;

  • 当用户点击链接的时候判断APP是否安装,如果用户没有安装时,引导用户跳转到应用商店下载应用。
  • 用户匹配功能,当用户点击链接时和用户启动APP时,分别将这两次用户Device Fingerprint(设备指纹信息)传到服务器进行模糊匹配,使用户下载且启动APP时,直接打开相应的指定页面。

通过上面的2个技术方案,不仅:

  • 可以让被分享者更快更便捷的回到APP,且回到指定的活动页面,而且:
  • 可以引导未安装APP的用户下载APP、
  • 分享者和被分享者的关系链会通过设备指纹信息记录下来,在业务场景中给出相应的奖励。

Deferred Deeplink可以先判断用户是否已经安装了App应用,如果没有则先引导至App应用商店中下载App, 在用户安装App后跳转到指定App页面Deeplink中。

除了上述Deeplink中的运营有点外,Deferred Deeplink在未安装App应用人群定向推广中效果更佳突出。另外国外的App运营在社交推广中广泛使用Deferred Deeplink技术,比如一个购物App中用户分享了一个自己喜欢的产品到社交账户中,如果没有使用Deferred Deeplink。其好友看到分享,点击下载安装打开App应用后,很可能找不到其好友分享的产品,导致较高的用户流失率。

Deferred DeepLink的实现思路

任何匹配的问题都可以转化到获取唯一标示的问题。很容易联想到http里面的session和cookies。由于手机系统的沙盒模式,阻断了App之间的数据共享。也就是说App的cookies跟手机浏览器的cookies是分开的,无法互通。

解决方案一:通过设备唯一ID

如Android的OIAD,iOS的idfv。此方案仅适合一个APP往另外一个APP引流的场景。

解决方案二:通过IP地址+设备信息(设备尺寸、操作系统等)+时间限定(比如10分钟)

解决方案三:剪切板方案

H5页面在点击下载时自动调用剪切板复制当前用户渠道ID( 口令码方案),APP每次启动时调用剪切板内容格式符合则认定该用户和H5用户为同一用户。

第三方库

支持deep linking 和 deferred deep linking 的第三方服务,比如 AppsFlyerBranch。涉及内容较多,这里就先不做展开。后面会单独进行深入分析。

微前端框架核心技术揭秘

$
0
0
微前端框架核心技术揭秘

2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”(Micro Frontend),其后该概念在web领域逐渐落地,在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义,带你走近微前端框架,揭秘那些“不为人知”的巧妙技术实现。

概念

什么是微前端呢?虽然它在2016年就被提出,但是直至今天,我们仍然只能描述它的轮廓,无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述:

  • 微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时,它们也可以进行并行开发。——《前端架构:从入门到微前端》
  • 微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域,做他们关注和专注的事情。团队是跨职能的,从数据库到用户界面开发端到端的功能。——译,micro-frontends.org
  • 微前端的核心价值在于 “技术栈无关”,这才是它诞生的理由,或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者,2020.11.20晚阿里云微前端线下沙龙
  • 真正要解决的是,当技术更新换代时,应用可兼容不同代际的应用。

微前端是一种架构,而非一个独立的技术点。我个人从两个角度去看微前端,一个是应用结构上,微前端是多个小应用聚合为一的应用形式;一个是团队意识上,微前端架构下,每个团队只负责独立(封闭)的功能,而且需要包含从服务端到客户端,团队协作意识与以往有较大不同。

微前端方案

如何在技术上落地实现微前端的概念呢?在前端技术领域出现了如下三种技术方案:

  • 基于接口协议的:子应用按照协议导出几个接口,主应用在运行过程中调用子应用导出的这几个接口
  • 基于沙箱隔离的:主应用创建一个隔离环境,让子应用基本不用考虑自己是在什么环境下运营,按照普通的开发思路进行开发即可
  • 基于模块协议的:主应用把子应用当作一个模块,和模块的使用方式无异

三种方案各有优劣,我们不能立即下结论哪一种更好。

方案类型典型技术优点缺点共同点
接口协议single-spa比较自由,可自主封装无法满足很多场景
  • 子应用/模块互不干涉
  • 技术栈无关
沙箱隔离qiankun开发思维简单直接沙箱带来的性能等问题
模块协议webpack module federation用模块思维理解引用脱离构建工具无法使用

就目前市面上的情况而言,基于沙箱隔离的微前端方案占据了主导,也就是本文将要深入阐述的微前端框架们,也都是这类方案。其中原因,笔者认为最主要的一点,是基于沙箱隔离的方案可以让应用以最小的成本,从原本的单体大应用迁移到微前端架构上来。

微前端框架对比评测

微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎,市面上有非常多的微前端框架,笔者在2021年做过一次收集,比较有典型意义。(虽然在那之后还出现了新的微前端框架,但其大部分原理一致,因此,以下这些框架足以说明情况。)

  • Mooa:基于Angular的微前端服务框架
  • Single-Spa:最早的微前端框架,兼容多种前端技术栈。
  • Qiankun:基于Single-Spa,阿里系开源微前端框架。
  • Icestark:阿里飞冰微前端框架,兼容多种前端技术栈
  • console-os是在阿里云控制台体系中孵化的微前端方案, 定位是面向企业级的微前端体系化解决方案。
  • Module Federation:webpack给出的微前端方案
  • Luigi:一套复杂的分布式前端应用解决方案
  • FrintJS:自主解决依赖的微前端框架
  • PuzzleJS:一套复杂的前后端编译时相结合的微前端解决方案
  • ngx-planet:基于angular的微前端框架
  • 麦饭(mfy):精巧简易的微前端框架

除了webpack的联邦模块方案需要结合构建来做,比较特殊外,其他方案都是在运行时完成应用聚合。

“子应用独立运行”指子应用不需要放到基座应用这个大环境下就能自己跑,便于调试和被不同基座引入。

“子应用嵌套子应用”是一个比较特殊的点,目前市面上能做到的框架不多。

微前端框架核心技术

在微前端架构中,存在“主应用”和“子应用”两个层级,而微前端框架的主要任务就是让子应用能够在主应用中有效运行。如上文所述,目前较多的微前端框架是基于(或支持)沙箱隔离实现的主子应用运行机制,笔者自己实现的小型微前端框架“ 麦饭”也属于此类,因此,本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是 资源加载环境隔离两大问题,此外,还有路由、通信等问题。

资源加载

微前端框架需要从服务端拉取子应用的代码文件,并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案,现在常见的有两种方案,分别是:以JS文件作为入口;以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本,获得JS导出的内容,但是这样,仅能加载脚本资源,无法加载CSS等样式资源。而以HTML文件为入口,则可以通过HTML文件内的文件引用,把对应的所有JS、CSS文件都一起加载,而且,web站点都是以HTML文件作为入口,这也正好可以让子应用的开发者按照web开发的思路来写子应用。

笔者在写麦饭这个框架的时候,希望直接引入子应用就能跑,所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件,这个函数可以根据入口文件,解析子应用的全部资源,并做缓存。

解析资源

框架在获得HTML入口文件地址后,通过HTTP请求获得该文件的内容,对内容进行解析,解析时需要做资源树分析,也就是通过HTML读取所有资源文件,比如link, script[src]。在读取资源时,可能还需要读取资源本身又引入的资源。大致逻辑如下图:

在解析过程中,还需要根据registerMicroApp(麦饭提供的注册接口)的配置,决定CSS rules怎么处理。解析获得CSS的技巧,是通过 <style>.sheet 读取 CSSStyleSheet 对象,从中抽离出所有CSS样式规则,再按配置逻辑生成最终的样式规则。

预加载/懒加载

在设计上,一个子应用的资源有两种可选加载形式。在麦饭中,假如你希望提前预加载子应用资源,可以在registerMicroApp时直接传入 importSource(…),这个函数一执行,就会去请求资源回来并做缓存。但是,假如你不需要预加载,你想在子应用需要进入界面时(或打算让子应用进入界面时)才加载资源,则配置为 () => importSource(…) ,这种配置会在子应用执行 bootstrap 的时候才去请求资源。

环境隔离

环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的,两个子应用之间,可能存在相互污染的问题,这就要求微前端框架实现一种能力,让子应用运行在自己的一个隔离环境中,从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有:

  • iframe:样式和脚本运行的隔离,缺点在于无法全屏弹出层
  • ShadowDOM:样式隔离,缺点在于弹出层被挂在document.body下面,而样式被放在ShadowDOM内部,无法正确渲染弹出层
  • 快照沙箱
  • 代理沙箱

也有框架把这些方案结合起来,在不同的场景下,主动或被动的使用其中的一种方案。其中,快照沙箱和代理沙箱是两种比较独特的技术方案:

快照沙箱

多个子应用在页面上相互切换,而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。

这种方案只适合同一时间只运行一个子应用的场景,例如腾讯云控制台。当子应用进入界面的时候,给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时,把window清理干净,再把快照上的属性重新添加到window上,复原了子应用挂载前的window。

代理沙箱

代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现:

1. 创建代理对象

比如上面提到window可能被污染。那就创建一个window的代理对象,例如fakeWin,实现如下:

这样处理之后,我们在读取时可能读取到原始window上的值,但是一旦我们写入新属性之后,再读就读到刚才写入的值,但对于原始的window来说,没有被污染。

2. 创建运行沙箱

要使代理对象作为全局对象给子应用的脚本使用,必须把子应用放在一个沙箱里面跑,这个沙箱使用我们制作的代理对象作为全局变量,这样子应用的脚本就会操作代理对象,从而与其他子应用起到代理隔离的效果。具体实现如下:

上面代码里面的window, document, location等,都是前面创建好的代理对象。

当然,这里只给出了一些最核心思路的代码,实际上在真正实现时,还要考虑各种特殊情况,需要进行多方面的处理。

通过代理沙箱,子应用就可以在主应用中独立运行,而不会对主应用上的其他子应用产生负面影响。不过,值得一提的是,由于代理沙箱实际上虚拟了一个给子应用的环境来运行,也就意味着需要消耗更多的计算资源,会给子应用的性能带来一定影响。同时,由于这种虚拟环境在某些情况下必须连接到真实环境进行操作,或者从另外一面反过来说,虚拟环境中不一定能提供子应用所需要的全部依赖,这就会导致子应用中某些功能失效,甚至影响整个子应用的表现效果。

路由映射

如果子应用有自己的路由系统,处理不好,子应用在切换路由时会污染父应用,导致浏览器url发生变化,结果把当前页面切到另外一个地方去了。为了解决这种问题,麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的,所以,不同层的应用得到的location是不同的,基座应用使用浏览器的location,但是它的子应用则不是,修改浏览器的url之后,可以通过路由映射机制,伪造子应用得到的url。具体实现是通过创建一个临时的iframe,利用代理沙箱的能力,将子应用的location代理到iframe里面的location上去。

得益于代理沙箱,子应用的url变化不会导致浏览器的url变化。

映射逻辑需要写一个map和reactive配置项,当浏览器的url发生变化时,通过map映射到子应用内部。子应用内部url发生变化时,通过reactive映射到浏览器,这样即使用户在某一时刻刷新浏览器,也可以通过url映射关系,准确还原子应用当前的界面。

挂载

在麦饭中,子应用需要通过一个 <mfy-app> 标签来决定子应用挂载在什么地方。和qiankun等框架不同,qiankun需要在子应用中决定挂载点,但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以,子应用在哪里挂载应该由父应用决定。

子应用被放在 <mfy-app> 中,给了开发者一些特殊的能力:

  • 可以放在 v-if 内部,DOM节点被移除后挂回来,子应用还在
  • 动画效果
  • keepAlive

在实现 <mfy-app> 时用到了一些比较 hack 的技巧。比如需要借助 <mfy-app> 这个节点所在作用域的顶层节点,在顶层节点DOM对象上挂载一些数据,通过这个技巧,确保节点被移除后,再被挂载回来时,还能正确还原之前界面。

keepAlive则是在 <mfy-app> 节点没有被移除的情况下,子应用执行 unmount 时,并没有实际销毁子应用构建的 DOM 树,而是放在内存中,当子应用再次 mount 的时候,直接把这个内存里面的 DOM 树挂载到 <mfy-app> 内部。

通信/应用树

这部分是麦饭设计中最复杂的部分,也是最终与其他微前端框架区别的地方。

我构建了一个这样的树状数据结构,称之为“应用树”。它表达了基于 MFY 开发的微前端应用中,应用于子应用的引用关系。

scope

scope概念是指一个应用起来之后,会创建一个scope(作用域),这个 scope 保存了该应用的一些运行时信息,同时通过了通信的接口方法。一个应用可能会有多个子应用,这些子应用都有自己的 scope,上下级应用之间可以通过scope完成通信,比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令,接到这个指令后,两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令,而 parent_app 再把这个指令转发给了 child_app_1,这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。

rootScope 是一个特殊的scope,它对应的是基座应用,是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象,所以,利用 rootScope 可以完成跨层应用间的直接通信(虽然不推荐)。

connectScope

每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现,在同一层,实现逻辑有点像react hooks,你不需要关心你处于应用树的哪个位置,对于子应用开发团队而言,只需要在代码中使用connectScope()函数,就可以直接连接到自己所在的作用域。如果你实现过react hooks的话,应该能理解它的一个实现原理。但是由于一些实现上的限制,你不能异步执行connectScope,必须在代码第一次执行时,同步调用connectScope获取当前子应用的scope。

状态共享

“如果子应用1修改了用户的某个状态,子应用2怎么对这个修改做出响应?”

这个问题涉及到一个状态共享问题。由于我在设计时,坚持每个子应用团队应该封闭开发的理念,开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用,或者还需要依赖其他应用的状态变化,这会让我在开发的时候一直处于对当前应用状态的未知状态,那这样就没法调试和测试了。因此,设计中我直接拒绝实现子应用间的状态共享。

但是在实际使用过程中,这种需求是存在的。因此,我建议使用通信的方式解决,子应用1发出一个消息,通过 rootScope,通知网络我改变了用户状态,那么其他子应用在接受到这个消息之后,自己决定是否要重新渲染界面。

思考

本文虽然已经通过笔者实现麦饭这个小型微前端框架,详细的阐述了一个微前端框架的核心技术实现,但是,也同时遗留了很多问题:

  • 跨域加载子应用问题
  • 子应用自己还要加载资源(angularjs模板)绝对路径问题
  • 登录态怎么传递?
  • 多语言怎么配置?
  • 代码共享(依赖)怎么处理?
  • 运行时对象多个怎么办?(例如每个子应用都有自己的jQuery)
  • 跨应用加载相同资源怎么办?(例如同时请求一个api拉取数据)

微前端不是万能的,坑也很多,所以应了那句话“没有银弹”。

结语

微前端是一种架构形式,一旦采用这种架构,就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式,因此,开发团队最好经过比较长一段时间的调研之后,才决定启用这种架构。从本文中,你也会发现,要实现微前端框架的核心能力,需要使用一些看上去不那么优雅的hack方法,既然是hack方法,就存在一定的弊端,比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点,在实际项目中,还需要面临更多问题,但这并不是说我在劝退大家,而是希望大家在选择时,根据实际的需求决定,不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣,可以在文章下面留言,我们一起探讨有关微前端框架的实现技术。

微前端框架核心技术揭秘

$
0
0
微前端框架核心技术揭秘

2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”(Micro Frontend),其后该概念在web领域逐渐落地,在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义,带你走近微前端框架,揭秘那些“不为人知”的巧妙技术实现。

概念

什么是微前端呢?虽然它在2016年就被提出,但是直至今天,我们仍然只能描述它的轮廓,无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述:

  • 微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时,它们也可以进行并行开发。——《前端架构:从入门到微前端》
  • 微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域,做他们关注和专注的事情。团队是跨职能的,从数据库到用户界面开发端到端的功能。——译,micro-frontends.org
  • 微前端的核心价值在于 “技术栈无关”,这才是它诞生的理由,或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者,2020.11.20晚阿里云微前端线下沙龙
  • 真正要解决的是,当技术更新换代时,应用可兼容不同代际的应用。

微前端是一种架构,而非一个独立的技术点。我个人从两个角度去看微前端,一个是应用结构上,微前端是多个小应用聚合为一的应用形式;一个是团队意识上,微前端架构下,每个团队只负责独立(封闭)的功能,而且需要包含从服务端到客户端,团队协作意识与以往有较大不同。

微前端方案

如何在技术上落地实现微前端的概念呢?在前端技术领域出现了如下三种技术方案:

  • 基于接口协议的:子应用按照协议导出几个接口,主应用在运行过程中调用子应用导出的这几个接口
  • 基于沙箱隔离的:主应用创建一个隔离环境,让子应用基本不用考虑自己是在什么环境下运营,按照普通的开发思路进行开发即可
  • 基于模块协议的:主应用把子应用当作一个模块,和模块的使用方式无异

三种方案各有优劣,我们不能立即下结论哪一种更好。

方案类型典型技术优点缺点共同点
接口协议single-spa比较自由,可自主封装无法满足很多场景
  • 子应用/模块互不干涉
  • 技术栈无关
沙箱隔离qiankun开发思维简单直接沙箱带来的性能等问题
模块协议webpack module federation用模块思维理解引用脱离构建工具无法使用

就目前市面上的情况而言,基于沙箱隔离的微前端方案占据了主导,也就是本文将要深入阐述的微前端框架们,也都是这类方案。其中原因,笔者认为最主要的一点,是基于沙箱隔离的方案可以让应用以最小的成本,从原本的单体大应用迁移到微前端架构上来。

微前端框架对比评测

微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎,市面上有非常多的微前端框架,笔者在2021年做过一次收集,比较有典型意义。(虽然在那之后还出现了新的微前端框架,但其大部分原理一致,因此,以下这些框架足以说明情况。)

  • Mooa:基于Angular的微前端服务框架
  • Single-Spa:最早的微前端框架,兼容多种前端技术栈。
  • Qiankun:基于Single-Spa,阿里系开源微前端框架。
  • Icestark:阿里飞冰微前端框架,兼容多种前端技术栈
  • console-os是在阿里云控制台体系中孵化的微前端方案, 定位是面向企业级的微前端体系化解决方案。
  • Module Federation:webpack给出的微前端方案
  • Luigi:一套复杂的分布式前端应用解决方案
  • FrintJS:自主解决依赖的微前端框架
  • PuzzleJS:一套复杂的前后端编译时相结合的微前端解决方案
  • ngx-planet:基于angular的微前端框架
  • 麦饭(mfy):精巧简易的微前端框架

除了webpack的联邦模块方案需要结合构建来做,比较特殊外,其他方案都是在运行时完成应用聚合。

“子应用独立运行”指子应用不需要放到基座应用这个大环境下就能自己跑,便于调试和被不同基座引入。

“子应用嵌套子应用”是一个比较特殊的点,目前市面上能做到的框架不多。

微前端框架核心技术

在微前端架构中,存在“主应用”和“子应用”两个层级,而微前端框架的主要任务就是让子应用能够在主应用中有效运行。如上文所述,目前较多的微前端框架是基于(或支持)沙箱隔离实现的主子应用运行机制,笔者自己实现的小型微前端框架“ 麦饭”也属于此类,因此,本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是 资源加载环境隔离两大问题,此外,还有路由、通信等问题。

资源加载

微前端框架需要从服务端拉取子应用的代码文件,并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案,现在常见的有两种方案,分别是:以JS文件作为入口;以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本,获得JS导出的内容,但是这样,仅能加载脚本资源,无法加载CSS等样式资源。而以HTML文件为入口,则可以通过HTML文件内的文件引用,把对应的所有JS、CSS文件都一起加载,而且,web站点都是以HTML文件作为入口,这也正好可以让子应用的开发者按照web开发的思路来写子应用。

笔者在写麦饭这个框架的时候,希望直接引入子应用就能跑,所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件,这个函数可以根据入口文件,解析子应用的全部资源,并做缓存。

解析资源

框架在获得HTML入口文件地址后,通过HTTP请求获得该文件的内容,对内容进行解析,解析时需要做资源树分析,也就是通过HTML读取所有资源文件,比如link, script[src]。在读取资源时,可能还需要读取资源本身又引入的资源。大致逻辑如下图:

在解析过程中,还需要根据registerMicroApp(麦饭提供的注册接口)的配置,决定CSS rules怎么处理。解析获得CSS的技巧,是通过 <style>.sheet 读取 CSSStyleSheet 对象,从中抽离出所有CSS样式规则,再按配置逻辑生成最终的样式规则。

预加载/懒加载

在设计上,一个子应用的资源有两种可选加载形式。在麦饭中,假如你希望提前预加载子应用资源,可以在registerMicroApp时直接传入 importSource(…),这个函数一执行,就会去请求资源回来并做缓存。但是,假如你不需要预加载,你想在子应用需要进入界面时(或打算让子应用进入界面时)才加载资源,则配置为 () => importSource(…) ,这种配置会在子应用执行 bootstrap 的时候才去请求资源。

环境隔离

环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的,两个子应用之间,可能存在相互污染的问题,这就要求微前端框架实现一种能力,让子应用运行在自己的一个隔离环境中,从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有:

  • iframe:样式和脚本运行的隔离,缺点在于无法全屏弹出层
  • ShadowDOM:样式隔离,缺点在于弹出层被挂在document.body下面,而样式被放在ShadowDOM内部,无法正确渲染弹出层
  • 快照沙箱
  • 代理沙箱

也有框架把这些方案结合起来,在不同的场景下,主动或被动的使用其中的一种方案。其中,快照沙箱和代理沙箱是两种比较独特的技术方案:

快照沙箱

多个子应用在页面上相互切换,而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。

这种方案只适合同一时间只运行一个子应用的场景,例如腾讯云控制台。当子应用进入界面的时候,给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时,把window清理干净,再把快照上的属性重新添加到window上,复原了子应用挂载前的window。

代理沙箱

代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现:

1. 创建代理对象

比如上面提到window可能被污染。那就创建一个window的代理对象,例如fakeWin,实现如下:

这样处理之后,我们在读取时可能读取到原始window上的值,但是一旦我们写入新属性之后,再读就读到刚才写入的值,但对于原始的window来说,没有被污染。

2. 创建运行沙箱

要使代理对象作为全局对象给子应用的脚本使用,必须把子应用放在一个沙箱里面跑,这个沙箱使用我们制作的代理对象作为全局变量,这样子应用的脚本就会操作代理对象,从而与其他子应用起到代理隔离的效果。具体实现如下:

上面代码里面的window, document, location等,都是前面创建好的代理对象。

当然,这里只给出了一些最核心思路的代码,实际上在真正实现时,还要考虑各种特殊情况,需要进行多方面的处理。

通过代理沙箱,子应用就可以在主应用中独立运行,而不会对主应用上的其他子应用产生负面影响。不过,值得一提的是,由于代理沙箱实际上虚拟了一个给子应用的环境来运行,也就意味着需要消耗更多的计算资源,会给子应用的性能带来一定影响。同时,由于这种虚拟环境在某些情况下必须连接到真实环境进行操作,或者从另外一面反过来说,虚拟环境中不一定能提供子应用所需要的全部依赖,这就会导致子应用中某些功能失效,甚至影响整个子应用的表现效果。

路由映射

如果子应用有自己的路由系统,处理不好,子应用在切换路由时会污染父应用,导致浏览器url发生变化,结果把当前页面切到另外一个地方去了。为了解决这种问题,麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的,所以,不同层的应用得到的location是不同的,基座应用使用浏览器的location,但是它的子应用则不是,修改浏览器的url之后,可以通过路由映射机制,伪造子应用得到的url。具体实现是通过创建一个临时的iframe,利用代理沙箱的能力,将子应用的location代理到iframe里面的location上去。

得益于代理沙箱,子应用的url变化不会导致浏览器的url变化。

映射逻辑需要写一个map和reactive配置项,当浏览器的url发生变化时,通过map映射到子应用内部。子应用内部url发生变化时,通过reactive映射到浏览器,这样即使用户在某一时刻刷新浏览器,也可以通过url映射关系,准确还原子应用当前的界面。

挂载

在麦饭中,子应用需要通过一个 <mfy-app> 标签来决定子应用挂载在什么地方。和qiankun等框架不同,qiankun需要在子应用中决定挂载点,但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以,子应用在哪里挂载应该由父应用决定。

子应用被放在 <mfy-app> 中,给了开发者一些特殊的能力:

  • 可以放在 v-if 内部,DOM节点被移除后挂回来,子应用还在
  • 动画效果
  • keepAlive

在实现 <mfy-app> 时用到了一些比较 hack 的技巧。比如需要借助 <mfy-app> 这个节点所在作用域的顶层节点,在顶层节点DOM对象上挂载一些数据,通过这个技巧,确保节点被移除后,再被挂载回来时,还能正确还原之前界面。

keepAlive则是在 <mfy-app> 节点没有被移除的情况下,子应用执行 unmount 时,并没有实际销毁子应用构建的 DOM 树,而是放在内存中,当子应用再次 mount 的时候,直接把这个内存里面的 DOM 树挂载到 <mfy-app> 内部。

通信/应用树

这部分是麦饭设计中最复杂的部分,也是最终与其他微前端框架区别的地方。

我构建了一个这样的树状数据结构,称之为“应用树”。它表达了基于 MFY 开发的微前端应用中,应用于子应用的引用关系。

scope

scope概念是指一个应用起来之后,会创建一个scope(作用域),这个 scope 保存了该应用的一些运行时信息,同时通过了通信的接口方法。一个应用可能会有多个子应用,这些子应用都有自己的 scope,上下级应用之间可以通过scope完成通信,比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令,接到这个指令后,两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令,而 parent_app 再把这个指令转发给了 child_app_1,这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。

rootScope 是一个特殊的scope,它对应的是基座应用,是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象,所以,利用 rootScope 可以完成跨层应用间的直接通信(虽然不推荐)。

connectScope

每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现,在同一层,实现逻辑有点像react hooks,你不需要关心你处于应用树的哪个位置,对于子应用开发团队而言,只需要在代码中使用connectScope()函数,就可以直接连接到自己所在的作用域。如果你实现过react hooks的话,应该能理解它的一个实现原理。但是由于一些实现上的限制,你不能异步执行connectScope,必须在代码第一次执行时,同步调用connectScope获取当前子应用的scope。

状态共享

“如果子应用1修改了用户的某个状态,子应用2怎么对这个修改做出响应?”

这个问题涉及到一个状态共享问题。由于我在设计时,坚持每个子应用团队应该封闭开发的理念,开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用,或者还需要依赖其他应用的状态变化,这会让我在开发的时候一直处于对当前应用状态的未知状态,那这样就没法调试和测试了。因此,设计中我直接拒绝实现子应用间的状态共享。

但是在实际使用过程中,这种需求是存在的。因此,我建议使用通信的方式解决,子应用1发出一个消息,通过 rootScope,通知网络我改变了用户状态,那么其他子应用在接受到这个消息之后,自己决定是否要重新渲染界面。

思考

本文虽然已经通过笔者实现麦饭这个小型微前端框架,详细的阐述了一个微前端框架的核心技术实现,但是,也同时遗留了很多问题:

  • 跨域加载子应用问题
  • 子应用自己还要加载资源(angularjs模板)绝对路径问题
  • 登录态怎么传递?
  • 多语言怎么配置?
  • 代码共享(依赖)怎么处理?
  • 运行时对象多个怎么办?(例如每个子应用都有自己的jQuery)
  • 跨应用加载相同资源怎么办?(例如同时请求一个api拉取数据)

微前端不是万能的,坑也很多,所以应了那句话“没有银弹”。

结语

微前端是一种架构形式,一旦采用这种架构,就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式,因此,开发团队最好经过比较长一段时间的调研之后,才决定启用这种架构。从本文中,你也会发现,要实现微前端框架的核心能力,需要使用一些看上去不那么优雅的hack方法,既然是hack方法,就存在一定的弊端,比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点,在实际项目中,还需要面临更多问题,但这并不是说我在劝退大家,而是希望大家在选择时,根据实际的需求决定,不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣,可以在文章下面留言,我们一起探讨有关微前端框架的实现技术。

在中国,真正达到月收入1万以上的有多少

$
0
0

作     者 | 菜乙己、董道力

数     据 | 董道力

编     辑 | 张晨阳、唐也钦

设     计 | 戚桐珲

2018 年,上海相亲角,有一位老大爷自称侄子高学历、年收三百万,并直言 " 月入 1 万如讨饭 ",引来大量网友自嘲式认领 " 乞丐 " 身份。

今年 2 月," 月收入一万 " 的鄙视链降临到了二线城市,有一位网友发帖声称,月收入一万在郑州 " 只能生存没法生活 ",还完房贷、减去日常生活开支,没剩多少钱,日子过得紧紧巴巴。

但另一方面,因为生活处境的差异,人们对 " 月收入 1 万 " 的体感其实是天差地别的。

今天,DT 君就通过数据的视角,看看月收入 1 万的打工人,在中国到底是个什么水平。

#01

在中国,每月可支配收入 1 万已经超过 99% 的人

尽管关于 " 月收入一万,生活艰难 " 的吐槽不绝于耳,但事实是,在中国,确实只有极少数人能够月入过万。

北京师范大学中国收入分配研究院的一份数据可以作为参考,该研究院一直在追踪中国收入分配情况,分别在 1988 年、1995 年、2002 年、2007 年、2013 年和 2018 年进行了六次入户调查,沉淀为中国家庭收入调查数据库(CHIP)。

CHIP 的最近一次调查时间为 2018 年,研究院官网并未公布相关数据,但该院研究人员在财新发布的一篇文章透露了部分数据结果,分层抽取了 7 万个样本的数据显示:人均可支配月收入(扣除个人所得税等之外可用于实际使用的可支配收入)在 10000 元以上的家庭占比仅为 0.61%,人均可支配月收入在 5000-10000 元区间的是 4.52%;而大部分中国家庭人均可支配月收入在 500-1500 元这个区间,占比约 40.71%。

#02

近 7 成应届毕业生,税前月入 6000 元以下

以上是比较宏观的统计数据,你可能会认为,统计样本中包含了家庭中的无收入人群,比如无退休金的老人、失业者、正在读书的孩子等,因而数据偏低,只看打工人的话,遍地是高薪。

但是当我们把人群缩小聚焦于已经毕业的本科生,能达到 " 月入 1 万 " 的仍然是少数群体。

涉及到更具体的就业人群划分,我们能找到的统计数据多是含税收入,如果考虑到手,还会更低。

麦可思《中国 2020 届大学毕业生培养质量跟踪评价》的数据显示,月收入(税前,包含工资、奖金、津贴等在内)在 1 万元以上的本科应届毕业生仅占该群体总人数的 4.3%,与此同时,有 68.1% 本科毕业生月收入在 6000 元以下。

即便是在工作三年后,也有相当大一部分本科生的月收入没有达到 1 万元。数据显示,毕业三年后,本科生的平均月收入为 8279 元。

也就是说,尽管互联网上似乎到处都是 " 年薪百万 " 的人,但在中国,月入 1 万,仍旧是一道卡住了大部分人的门槛。

#03

在哪里才能月工资过万?

在日常生活中,我们提及的月薪过万,常常是指税前工资。接下来我们一起来看一下,具体到不同的地域和城市的工资水平。

国家统计局在《中国统计年鉴 2021》中公布了全国各地区就业人员的平均工资(税前,包含工资、奖金、津贴等在内),我们进行了计算排名,可以看到,位列前三的地区分别是北京、上海,和西藏,广东仅排在第六。

这个结果与 " 北上广工资更高 " 的刻板印象有所出入,分析起来可能主要有两个原因:

一方面,因为数据是基于不同省份划分的,而广东省除了广州之外还会计入其余所有城市的人均工资,所以排名可能低于预期。

另一方面,因为海拔高、大气稀薄等自然环境因素,西藏的工作、生活环境相对其它地区更具挑战性,所以需要用有竞争性的、更高的工资才能吸引人才。且政策上的支持使得这样高薪招人的策略具有可持续性。

每月平均工作超过 1 万,在不同地区,所能带来的生活质量可能相去甚远。前段时间 DT 君就这个主题采访过 12 位生活在北上广的年轻人,即便都是大都市,它们之间的房价、物价也都各有不同,更不要说月薪 1 万在北上广,和在二三线城市之间的区别了。

对于普遍租房的年轻打工人来说,其中最明显直观的差别就是每个月的房租。根据麦可思研究院《2021 中国本科生就业报告》的数据,在北京和深圳,2020 届毕业生的住房成本占工资比已经超过了 40%,其次是上海、杭州,分别占比 38%、32%。而在重庆、长沙这些规模相对稍小的城市,虽然平均月收入更低,但只有 15% 左右的工资会被花在房租上。

另外,互联网在某种程度上抹平了一些差异,使得不同城市的人主动或被动地享用了同一套生活方式。一些生活在三四线城市的人,因为拥有更多的闲暇时间、更少的买房焦虑,可能更乐于在娱乐、潮流上消费。虽然没有月薪 1 万,但生活同样丰富。

换句话说," 月薪 1 万是什么水平 "" 月薪 1 万的生活是什么样子 " 这个问题,直接被 " 你在哪里月薪 1 万 " 所影响。

#04

做什么才能月薪过万?

影响薪资水平的远不止 " 在哪儿工作 " 这一个因素。同样重要的,是具体 " 做什么工作 "。

国家统计局《中国统计年鉴 2021》的数据显示,2020 年各行业平均月工资最高的三个行业分别是:信息传输、软件和信息技术服务业,科学研究和技术服务业,以及金融业。也只有在这三个行业中,就业人员平均月工资超过 1 万元。

但另一方面,这三个高薪行业都有着不同的高门槛和问题:

信息传输、软件和信息技术服务业,面临着高强度、高时长的脑力工作,尤其是近几年,从亚健康到猝死," 拿命换钱 " 的工作逻辑越来越被人批判,但普遍来看,只要身处其中,人们还是难以跳出内卷严重、35 岁晋升瓶颈等问题。

排在第二的科研行业,需要专业技术过硬的人才,紧随其后的金融行业非常看重高学历及相关经验。因此,高门槛和高月薪是密不可分的。

进一步的数据分析还显示,在不同地区,高薪行业的排名是不同的。

比如上海作为金融中心,它的金融行业就业人员平均工资在 27510.4 元 / 月,是全上海所有行业中最高的。

但在浙江,金融行业就业人员平均工资是 12150.3 元 / 月,在全省各行业排名并不靠前。浙江省平均工资最高的行业是信息传输、软件和信息技术服务行业,就业人员平均月入 19619.2 元。考虑到阿里巴巴总部就在杭州,这点并不让人意外。

写在最后

美国南加州大学经济学家理查德 • 伊斯特林在《经济增长可以在多大程度上提高人们的快乐》中提出了著名的伊斯特林悖论,即收入与幸福感不成正比的现象。其解释之一就是,收入和财富具有相对性,而这种相对性决定了它们真正的价值。

就像我们在前文中提到的,在不同城市,月薪过万的意义是不同的。

正因为大城市更容易月薪过万,所以在大城市月薪过万就没有那么稀有,反而会有更多高收入人群、高消费场所,且更容易产生攀比效应。即便许多消费选项也许并不包含在月薪 1 万打工人的生活成本之内,它们的存在还是会直接降低薪资在人们心中的价值。

另外,在薪资水平较高的社交圈中,当身边大多数人的收入与你相差无几,月薪 1 万的心理价值自然被削弱了。

英国经济心理学家克里斯 • 博伊斯等学者提出了 " 收入等级 " 的概念来解释这一现象。博伊斯指出,大多数人判断自己薪资水平时,不会考虑薪资本身价值,他们的判断更多受到这个薪资的 " 收入等级 " 所影响。而所谓 " 收入等级 " 是一个非常具象、微观的概念:它指代每个人心中,自己的收入在个人社交圈中排在什么位置——但没有人会把宏观的社会参照值(譬如 "99% 的中国人月收入低于 1 万 ")作为比较对象。

简单粗暴来说就是,在一个人心中," 月收入 1 万超过 99% 中国人 " 的认知所能带来的幸福感,大概远远不如 " 月收入 5000 超过 5 个亲朋好友 "。

另外值得注意的是,大多数人更在意 " 比上 ":与收入高于自己的人群的比较会对一个人的心理产生更大的影响。也就是说," 比下 " 所带来的心理安慰在 " 比上 " 所产生的的焦虑感面前,往往不值一提。

比如对于许多城市中产而言,他们大多数人早就月入过万,但依然无法停止对自身收入的焦虑。根据脉脉数据,即便是年薪 30 万以上的人群,其主要焦虑依然来自收入。

对此,财政部财政科学研究所原所长贾康认为:这种收入焦虑的根源," 还是负担多 "。从高昂的房贷按揭,到鸡娃的教育投资,月收入 1 万相较于大城市中产生活的预设花销而言,显得杯水车薪。

不可置否,人们关于 " 月薪 1 万 " 的悲欢并不相通,有人早就月入过万,害怕跌落,所以还在努力向上;有人月收入 5000,美食电影潮玩一样不落,生活得洒脱快乐;但无论如何,他们都在努力过着自己定义中的 " 好生活 "。

技术管理者的 4 个基本思考点

$
0
0

技术团队管理者在日常工作中可能经常会遇到如下一些状况:

  1. 自测质量差
  2. 转测 BUG 多
  3. 项目延期
  4. 加班赶工
  5. 高强度加班后,小伙伴状态不好,导致更多的问题出现

从第 1 点状况演变成第 5 种状况,第 5 点状况继续推动第 1 种状态的持续加强,从而导致整个团队的状态极差,陷入 BUG 多 –> 延期 –> 加班 –> BUG 更多 –> 更多的延期 的死循环。

除了上面的死循环,可能还会有一些非功能性的问题,如性能、扩展性问题等等。

当团队大时,还可能遇到有小团队,各小团队各行其事,各为其主,心不往一处,力不出一孔。 这些问题让技术团队的管理者焦头烂额。

那么如何解决这些问题呢?个人认为可以从以下 4 个方面来逐一思考和优化,从而在一定程度上解决这些问题。

1. 把正确的人放到合适的岗位

所有的执行最终都是落到人身上,有了正确的人,事情会事半功倍。 说到人,我们往往会提起人的「选用育留」,这是一个很大的题目,我们不做详细的讲述,只关注选和用的一小部分。

管理上有一个在大部分场景适用的套路:选拔优先于培养。 这个套路背后有两层逻辑:

  1. 改变一个人太难,比如有些人就是懒,抽一鞭子动一下;又或者家境优渥,态度佛系,无欲无求,根本就是油盐不进;又或者玻璃心,安全感差,都很难搞;
  2. 成本太高,即使这个人具备可培养性,但从 0 到 1 把一个人培养起来,时间成本太高,而管理者的时间很贵,公司等不起。

所以我们这里是选人,从现有的人中找到合适的,从人才市场找到合适的,能直接用的。

本小节主要是回答两个问题:

  1. 正确的人是怎样的?
  2. 如何把正确的人放到合适的岗位上?

1.1 选人

在人的层面,主要包括两个部分,执行者和管理者,这里管理者包括整个团队负责人自己。 不同的部分,要求不同,选人的标准也不同。去掉专业技能部分,去掉历史经验部分,我们希望我们的伙伴是这样的:

  • 喜欢和投入:对于技术热爱,对工作有投入度,能够专注于自己手上的工作,找到成就感;
  • 知道自己想要什么:有一定长远的规划,知道自己走在什么样的路上,不限于一时一城之得失;
  • 一路人:认同企业的价值观,价值观认同其本质上是「人以群分」;
  • 宁缺毋滥:如果实在没有人,宁缺毋滥吗?这是一个好问题,严格来说是这样的,但实际中往往我们会妥协一部分。

我们选人一个常见的问题是注重人当下的表现和历史的成绩,然而我们选人是要解决未来的问题,更要关注其潜力。 那么如何看一个人的潜力呢?如果具备以下的特性,大概率是一个有潜力的人:

  1. 有意愿,什么叫有意愿,就是指一个人想变好,有内在的动机去追求更高的目标;这里扩大一些,还包含积极的态度、好奇心和进取心;
  2. 有静气,特别是遇大事时有静气,临危不乱;遇到繁琐的事情能一步一步慢慢做好;
  3. 有度,做事有度,知道做事的边界在哪,进展有度,但是并不是事事划边界,事事划边界显得格局太小,多数事情还是从大局着眼;
  4. 能扛事,遇到事情找方法,不找借口,执行力强;
  5. 有所为,天赋决定了能达到的上限,努力程度决定了能达到的下限。努力去做,有所为,并且 以现在绝大多数人的努力程度之低,还远远没有达到比拼天赋的程度。 这里的有所为不仅仅是做事,更多的是学习,不停地学习,提升自己。

1.2 用人

1.2.2 人才梯队

人才梯队从时间上看分为现在和将来。 现在是指盘点现有人才情况,梳理人才结构,对团队中的人进行分层,形成「梯状」。

当前状态的人才梯队分为两个层面,一个是分层,另一个是分层后的职责。 当团队大一些后,需要明确团队的组织分层,这里可能是正式认命的 Leader,也可能是虚拟的负责人,不管是实的还是虚的,最终都会有一个层存在。

分层本质上是一个分饼的行为,什么样的人拥有什么样的资源,承担什么样的责任,行使什么样的权利。

作为一个研发团队的负责人,需要梳理这些层,确定是否有合适的人,这些层的负责人形成了我们所说的「人才梯队」。

德鲁克曾说:「一个组织应该使每个人,特别是每个管理人员和每个专业人员(但也包括每个管理单位)都理解自身的任务」。 在我们做分层的过程中,需要关注每一层的职责和其解决的问题,如我们在软件架构设计的时候一样,每一个分层都有其作用,无用的分层只会增加性能的损耗。

对于将来,未雨绸缪。

当现在的人才正在发挥作用时,培养接班人,我们经常称之为 B 角。当有人才变动时可以快速补位上去。常用的方法有内部培养,跨团队轮岗,外部招聘等。

这块特别狠的可能要算宇宙厂了,在现有人员已经能满足工作需要的同时,会继续招聘,如果更优,可能会换人。

1.2.2 艰难的决定

在我们构建人才梯队的时候,想要做到知人善任是一件很难的事情,并且让每个人的表现都达到预期水平更难。当有些人并不能胜任他当前的工作时,我们应该怎么做?这里可能有以下两种常见的方式:

  1. 调岗,一般这种情况可能只是人岗不匹配,或者有些变化跟不上,但是对于人的部分能力还是比较认可。此时我们在公司内给他找一个匹配的岗位,更好地发挥其潜力。在技术团队最常见的例子是有些高 P 的技术同学,被推到管理岗,过了一段时间,发现适应不了,此时我们可以将其调到更能发挥其能力的岗位上来。又或者一些管理者因为团队扩大或者一些其它原因,管理的范围一下子增加了很多,一段时间后发现无法搞定这样的团队,此时可能将其调整到稍微低一级的职位上更合适一些。
  2. 离开,尽量让对方体面的离开,公司和个人都体面的分手,该赔偿的赔偿,该理解的理解,如果在外面有合适的机会,顺手推一把,也是个不错的善缘,不枉同事一场。这里经常出现的问题是犹豫不决,最终导致大家都不开心,不欢而散。管理上常说:「心要慈,刀要快」,就是要规避这种犹豫。

1.3 管理者自己

在用人中还包括管理者自己,一个技术管理者,不仅仅要注重技术,不仅仅要设定明确的目标并排出优先顺序,跟进过程发现问题解决问题,拿到结果,还要注意另一个重要的点:业务参与者。

一个优秀的管理者和一个不那么优秀的管理者的主要差别就是他们对业务的参与程度。事实证明,对所负责业务参与的程度越深,你就越能做出更加明智的决策。

什么叫业务参与度?

我认为它不是事无巨细地了解进展,应该到一线去,怎么到一线去,去写代码,去做代码评审?

到一线去,有两个层次,一个是业务的一线,用户或产品,看用户的反馈,产品的实现,二是工作的一线,看工作流程的卡点,系统化的情况,可视化的情况。不要自己去做这些事情,主要是收集信息,决策或者发现问题解决问题,这里确定做什么的原则是: 你做的事情的价值大小,而价值大小可以从时间杠杆,团队杠杆上来指数级扩大,如果做一件事只有短期的一件事的价值,那这些事情就不是你应该做的,应该停下来去思考要做的事情。

2. 组织

说到组织,你是想要一个「令行禁止,使命必达」的组织,还是一个「简单可依赖」的组织?

组织是为实现共同目标而采取的一种分工协作体系,是人与人之间的关系,组织结构往往会随着组织的重大战略调整而调整。

而企业在商场中求生,随着外部环境的变化,行业的变化,内部环境的演进而会不断进行迭代,不断调整组织结构,因此我们经常需要重新设计组织结构。

2.1 设计组织结构

我们在设计组织结构的时候通常要思考以下五个问题:

  1. 组织的目标是什么?因为组织是为了共同目标而存在的人与人的关系,目标不清楚,组织结构肯定也是不清楚的。
  2. 组织由哪些层,哪些单位组成?组织是一个体系,由不同的单元组成,需要明确各层或单元分别是什么,职责是什么,其作为一个子结构需要有自己的目标和使命。
  3. 组织各部分的规模和形式应该是怎样的?根据现实的情况,在设计组织结构时需要着重考虑规模和形式,多少层级?流程型?考虑管理者的有效管理幅度。
  4. 组织的哪些部分应该结合在一起,哪些部分应该分开?
  5. 组织内各单元之间的关系和协同应该是怎样的?

设计组织结构最难的问题可能是分和合的问题,这里有一个原则: 凡是做出同样的贡献的活动可以结合在一个部门中统一管理,不论它们的技术专业是什么。那些不是做出同样贡献的活动则一般不应合在一起。

在考虑组织结构,特别是研发团队的组织结构的时候,千万不可忽略了康威定律,甚至我们有时需要「逆康威定律」,通过适配康威定律,在明确技术架构方向的基础上,以组织结构的调整来推动技术架构的演进。

2.2 组织的形态

每家企业都有自己的文化和组织形式,抽象出来大致可以分为权力型,流程型和交易型,落到研发团队内部,一般只有前两种。

现在许多人强调去中心化、去科层化、去权力化,实际上,在大多数情形下,权力连接还是最直接、最简单、最有效的机制。权力组织讲究的是执行。

组织中的权力主要有四种。除了决策权,还有建议权、审核权和知情权。

  • 决策权,对一个事情决定资源投在哪里,是不是可以做的权力;
  • 建议权是提出方案、预案的权力。注意,这是一种权力:建议权拥有者如果未提出建议,上级决策者不能替代或强压;
  • 审核权是对有关事项程序性、合规性的审查与核准。注意,这种权力不是对事项进行决策;只要有关事项符合程序、合乎标准和规范就予以通过放行。我们经常会看到一些企业的职能管理部门把审核权误当成了批准决定权,导致审批流程变长、决策效率降低,这是需要反思改进的。
  • 知情权是信息共享权,即获取信息的权力。

以上 4 个权限很容易让我们想到项目管理里面的 RACI 矩阵:

  • 谁负责:(R = Responsible),即负责执行任务的角色,他/她具体负责操控项目、解决问题。
  • 谁批准:(A = Accountable,决策权),即对任务负全责的角色,只有经他/她同意或签署之后,项目才能得以进行,是整个事情的决策者;
  • 咨询谁:(C = Consulted,建议权),拥有完成项目所需的信息或能力的人员,多提出建议。
  • 通知谁: (I =Informed,知情权),即应及时被通知结果的人员,却不必向他/她咨询、征求意见。

以上四种权力,决策权和建议权是权力主线,审核权和知情权是权力副线。主线是上下级逻辑,副线是平级或流程型逻辑,而我们往往理解的权力是主线权力。

在强权力主线的基础上,权力型组织的有以下 3 个缺点:

  1. 唯上,组织内的成员对决定其资源或发展的上级负责;
  2. 组织的发展由上级决定,当上级缺位、能力不足或脱离实际时可能会出现瞎指挥,乱指挥的情况;
  3. 层级过多,导致执行和决策的效率低下。

为规避这些缺点我们需要减少层级,增加连接和协同。

如果把管理层只分为三级,我们一般称之为高层、中级和基层。这三个层要解决的问题不同。高层解决长期发展的问题,中层解决系统效率的问题,基层解决的是事情本身的问题。

比如技术管理者常规上可以分为三个大层:

  • 高层管理:解决长期发展的问题,使企业/团队有前途,关注的是未来,不管是技术发展的未来,还是业务发展的未来;
  • 中层管理:解决系统效率的问题,使系统更有效率,要不断强化研发流程的统一性和适应性,确定并执行好标准和规范,提升协同的效率,标准化和系统化,同时为上层的选择和决策提供依据和保障;
  • 基层管理:解决事情本身的问题,而事情往往会落到一线的开发同学身上,于是经常我们基层管理者的主要工作是使一线开发同学工作有成就,培养一线开发同学,提高他们承担工作的能力和意愿,帮助他们解决问题。

我们期望是每个层能做好自己的事情,但是实际上往往是高层在做中层的事情,中层在做基层的事,基层在做一线的事情,错位了。于是,我们需要将管理层归位,各行其职,更专业的做事情。

在明确职责的基础上,尽量减少管理的层级,尽量不要出现 「副XX」 的岗位。

在协同方面我们用流程来解决协同合作的问题。流程本身的逻辑是对事情负责,对自身的任务及向下事项/环节负责。其驱动力是责任感和依赖。百度文化里面的「简单可依赖」能较好地说明流程的底层逻辑。当我们使用流程来解决协同的问题时,要经常迭代流程以优化协同的效率:

  • 注意流程是为了解决过程中的问题,不是为了设置卡点,每个人都有扩大自己权力的欲望,这是我们在流程建设中要特别注意的;
  • 尽量减少流程环节,聚合责任主体,我们经常遇到一个事情在某职能部门还需要经过两三道审核的情况,这种损耗可以通过聚合责任主体的方式来解决;
  • 控制流程时长,这里控制流程时长还包括流程环节的前置准备,就和我们开会一样,如果开会前准备充分,会议本身的时长就有极大可能减少。

用权力解决「力出一孔」的问题,用流程解决协同合作的问题。

简单来说,在权力体系的基础上,用流程连接各环节和负责主体,现在比较常见的矩阵式组织结构基本符合这个逻辑。当然这里有一个以流程为主还是以权力为主的问题,具体是哪种,还得看公司当前组织结构的逻辑。

2.3 分工的粗和细

Adam Smith 在 1776 年的《国富论》中提出了分工,分工对于手工业生产效率有较大的提高,其总结了三个优点:

  • 熟练程度的增加,当一个人专注于一块工作,不停的练习极大的增加了熟练度,熟练度的增加将导致质量和产量的增加;
  • 当熟练后,人们对于重复的操作进行机械化或自动化,从而更大的提高质量和产量;
  • 分工明确了输入和输出,在明确的分工下,从一个工序转为另一个工序的时长减少了。

自从分工提出来了,产生了大量的争论,有人提出了以下的一些缺点:

  • 一叶障目,不见泰山,分工越细,所关注的东西都小,当人陷于某个事情越来越小的部分时,其大局观往往受限,可能会导致局部提升而全局受损的情况;
  • 分工越细意味着沟通协作成本的增加,当分工获得的收益小于沟通协作的收益时,将产生极大的成本浪费;
  • 分工一定关系到组织结构的分块,当沟通不畅或没有沟通时,有可能出现组织上的「孤岛」

分工落到一个研发团队,我们通常按编程语言分为 Java、PHP、C++、Javascript,或按端分为安卓端、iOS 端、前端、后端、算法、数据等,又或者大一点分为开发、测试、运维,架构师。

虽然「术业有专攻」,但多跨一步会让分工后的协同更高效一些,这里最常见的可能是测试左移和测试右移。

  • 测试左移是指在研发流程中,把测试的覆盖范围从传统的测试节点中释放出来,将其向左扩展,介入代码提测之前的部分,如开发阶段阶段,需求评审阶段,让研发人员在架构设计时就考虑产品的可测试性,并尽量进行开发自测,同时评估需求的质量,比如分析需求的合理性以及完整性等。
  • 测试右移是指把测试的覆盖范围从传统的测试环节中切出来,将其向右扩展,更多地融入代码部署、发布,甚至上线之后的步骤中。

更极端一些,走全栈路线,一个人从头到尾完成需求的所有工序。但是这种方式,对于人员素质的要求,对于团队组织的要求和常规不一样,且人的精力是有限的,能在每个方面都做到精通的,少之又少,除非所做的事情只需要略懂即可。

思考一下,你所在团队应该如何来做?

成本和效率?组织大小?技术发展?架构演进?

3. 机制

3.1 DRI 机制

3.1.1 DRI 的由来

DRI 是 Directly Responsible Individual 的简称,中文翻译为「直接负责人」。最开始是从苹果流传出来的内部管理概念。

DRI 不是流程、过程,也不是框架,而是一个负责人,对某部分的整体负责,小到 BUG,大到技术方向。 DRI 是为了解决责任主体的问题,其有助于避免责任分散。责任分散这个概念也被称为「旁观者效应」,也就是人们身处团队中时无法对某事负起责任,责任分散到了团队中的每个成员身上,而不是集中在真正有责任的人身上,因为每个人认为那个责任应该由其他人承担,表现得像一个旁观者。

3.1.2 DRI 的职责

  • 聚焦目标;
  • 督促、监督团队成员完成自己的任务;
  • 清楚团队中发生的一切;
  • 统筹策划,搞定所有的干系人,从头到尾负责到底,说简单点就是团队成员专注的做好手上的事儿, DRI 排除干扰,解决各种烦人的问题来,发现问题,解决问题;
  • 有一定的领导责任。

简单来说,当你是某个事情的 DRI 后,这个事情就是你自己的事情。特别是当职责不清或者突发问题时,DRI 就需要发挥主人翁的精神,拉起团队成员去分析问题,解决问题。

3.1.3 什么人适合做 DRI

DRI 和工作年限无关,和是否资深无关,和技术工种无关,你想你就是。

但是在操作过程中,我们会根据实际的场景做一些偏重。如果是一个后台的活儿居多的项目,其 DRI 大概率是后台的开发同学,如果是一个质量问题较多的项目,其 DRI 大概率是一个 QA 同学。

这里我们会充分考虑同学的主观意愿,有些同学想,有些同学不愿意,只想做好手上的工作,那么他做好他手上工作的 DRI 就可以了。

DRI 无关项目大小,无关职位高低,无关所在层级,每一件大事,小事都需要有一个 DRI,且只有一个 DRI

这玩意儿换成中文其实就是我们在标语里面经常看到的「责任到人」差不多,但是更强调责任主体的唯一性。

3.2 构建良好的协同机制

公元前 221 年,秦始皇用了十年的时间,先后灭了韩、赵、魏、楚、燕、齐六国,完成了统一中国的大业。在以后,他陆续颁布了多条律法,以稳固国家的统治,包括「书同文」、「车同轨」、「度同制」等。

一个国家,一个组织,要想成为一个高效执行的团队,一定要有标准,一定要有人告诉大家怎样做是对的。 落到我们团队管理,标准,流程是必须要做的,特别是当你的团队是由若干个团队整合或融合的时候。

3.2.1 统一标准规范

当你的团队是由原来多个业务的团队融合而成,大家原来都有做一些标准,现在我们需要按统一的标准达成共识并推行下去。又或者本来标准不完善,需要系统梳理标准来达到标准的统一。

行业标准一般是为了互联互通,而对于研发团队的研发过程来说,标准主要是为了减少过程中的认知成本,提升研发的效率,比如数据库规范,大家按统一的规范来设计数据库,当有其它同学接手你负责模块的时候,能减少在基本结构的认知成本,以及在一些模块间整合或数据迁移时,对于工作会比较友好一些。

标准是什么,标准是一件行为准则,其关注的是结果。

标准和规范一般是为了告诉人们什么是好的,关注的结果,而统一标准是为了让大家互联互通。

标准不是为了成功,而是为了让整个事情不至于太坏,尽量不出现重大的问题。 具体到研发团队,我们一般需要统一如下一些标准:

  • 研发过程
    • 代码风格规范
    • 数据库设计规范
    • 代码分干管理规范
    • 代码提交规范
    • 错误码规范
    • Code Review 标准
    • 代码权限管理规范
  • 沟通协同
    • 架构规范
    • 技术方案规范
    • 文档规范
    • 接口规范
  • 质量标准
    • 代码质量标准
    • 自动化测试标准
    • 测试质量标准
    • 线上质量标准
  • 性能标准
    • 服务端性能标准
    • 客户端性能标准
    • 前端性能标准
  • 安全标准
    • 信息安全标准
    • 代码安全标准
    • 数据安全标准
    • 线上安全标准

3.2.2 统一流程

流程是什么?

流程是基于时间线,有一定先后序列规则的完成一件事的过程。流程是线性的、连续的。

统一流程是什么?

统一流程就是把一些验证过的好的做事方式,好的经验通过流程的方式固化下来,防止大家重蹈覆辙,在一个坑里踩多次,并且为不熟悉的同学做好指导。

我们做任何一件事都是有流程的,有些是设计过的,有些是自然而然的,设计过的流程可能是别人的经验。并且流程需要持续迭代。

在研发管理中我们常常会构建的流程如下:

  • 敏捷流程
    • 需求迭代流程
    • 紧急需求流程
    • 值班需求流程
  • 研发流程
    • 代码审核流程
    • 代码发布流程
    • 紧急发布流程
  • 协同
    • 对接流程
    • 资源申请流程
    • 线上问题/告警处理流程
    • 事故处理流程
    • 安全问题处理流程

3.2.3 统一工具

以上说了要统一标准,统一流程,这些第一步是要把这些标准和流程做出来,形成文档,落到知识库中。 如果只做到这一步,这些标准和流程可能就真的只是一个文档,情况好一点,有人来推进重视,可能会落实一些,但一旦这个推进人不在了,或者不再关注了,很多形式就不了了之。

要解决这个问题只能通过工具或系统,以工具或系统的形式固化标准和流程,把这此好的经验和方式以更物理的方式沉淀下来。再以这些工具或系统为杠杆,提升整体研发的效率,创造增量的价值。

4. 系统

这里的系统不仅仅是指使用某个系统,在使用系统的基础上整合,实现我们高效执行的目的。 前面我们有了机制,但是事情太多,不能让所有的事情用人来去解决,需要用系统来解决。

机制解决规模化的问题,系统解决规模固化的问题。 系统解决的问题有两个层面,一个是过程跟进,一个是结果度量。

4.1 工作流和过程跟进系统

一个研发部门可以看作一个系统,需求从一端进入,经历各种正确的工序,才能变成产品,如期从另一边离开。 当系统内部存在冲突,或者不和,或者互相针对,那么就会发生各种想象不到的问题,从而让整个系统的产出变少,甚至没有。

着眼于整个工作流,确认瓶颈点在哪,尽可能的运用各种技术和流程来确保工作在计划内有效的执行。因为我们知道:在代码投产之前,实际上并未产生任何价值,因为那只是困在系统里的半成品。

在实际中我们如何让大家更好的协同,更好的让这个工作流运转起来呢?以前可能是 Excel、Word 或者 todolist,再加上邮件或 IM 传来传去,现在更进一步有在线的表格协同,还有更完整的项目管理系统,如 Jira 、Trello、腾讯的 TAPD、阿里的 Teambition、禅道等。工具不同,但是目标是相同的,都是希望做到对项目执行的管控、对团队事务(问题)的跟踪,对需要多人协作任务的快速流转和处理。

除此之外,还需要文档管理,即过程中的物料也需要跟进起来,关联起来。至于是一个系统,还是多个系统,都是可以的。

系统虽有,但用得怎么样不好说,一个好的系统用不起来也是白搭,这里作为管理者需要推动起来的。

4.2 工作可视化

项目管理的系统用起来后,我们的工作流转就会落到系统里面,此时根据系统的数据,我们可以让工作可视化,透明化,能够清晰的观察工作流动的情况,从而发现瓶颈。发现问题是最难的,很多时候我们不知道有什么问题,包括自己。发现问题,才能解决问题,方法总是有的。 在资源有限的情况下,对非约束点的改进看起来很正确,但实质上毫无帮助,甚至会消耗宝贵资源拖累真正需要解决的问题。

我们可以构建一些看板,看一个产品从产品到设计、到研发实现,到测试完成,上线发布,再到线上问题跟进等等的情况。看效率,看一个需求从出现想法到用户看到需要多少时间,每个环节需要多少时间,哪些需求在哪些环节停留太久?不同的需求,不同的人,不同的产品做多个层次的对比,从而发现问题解决问题,让一切都在阳光下进行。

同时,我们可以让系统和数据告诉我们,整体团队的投入如何,有多少同学的工作是可以追溯的,有多少人力是隐藏在不为人知的地方的,能看到我们的时间都去哪了。

基于这样的看板,我们从两个角度优化整个系统:

  • 从左到右的流动,看从产品、设计、研发到运维的工作情况。为了最大程度地优化工作流,需要将工作可视化,减小每批次大小和等待间隔,通过内建质量杜绝向下游传递缺陷,并持续地优化全局目标;
  • 从右向左的反馈,每个阶段中,应用持续、快速的工作反馈机制。通过放大反馈环防止问题复发,并缩短问题发现时间,实现快速修复。通过这种方式,我们能从源头控制质量,并在流程中嵌入相关的知识。这样不仅能创造出更安全的工作系统,还可以在灾难性事故发生前就检测到并解决它。

总体来说,先透明出来,再优化,打开黑盒,问题会简单很多。

4.3 其它

以上的两个是从任务跟进和结果度量的角度,或者说从项目管理的角度来看整个团队的运转。 换一个角度,从研发同学工作本身,有没有需要系统化的地方?

代码管理是否系统化,Code Review、接口文档、接口自动化测试、Mock 数据、测试数据集管理、用户数据自动脱敏重放,代码从写完提交到代码库之后到上线,线上巡查等等这些是否有系统化?

这并不是今天我们要讲的话题,但是就系统化来说,这些都是必不可少的关键点。不管是哪方面,我们的原则是尽量减少人工介入,把人的经验变成代码和系统。

5. 后记

这篇文章务虚居多,也比较散,但是确实是技术管理者日常工作中要不停思考的点。 思考这些是用来帮助厘清思路,并不具备实操性,也就是不能实际的解决问题。 不同的公司,不同团队,问题不同,解决的方法也不同,欢迎一起探讨。

打开「黑盒」,问题会简单很多。常思考人、组织、机制和系统,这 4 个方面,发现其中的问题,并厘清解决问题的思路,一步一步,有节奏的去解决。

你好,我是潘锦,超过 10 年的研发管理和技术架构经历,出过书,创过业,带过百人团队,也在腾讯,A 股上市公司呆过一些年头,现在在一家 C 轮的公司负责一些技术方面的管理工作。早年做过 NOI 和 ACM,对前端架构、跨端、后端架构、云原生、DevOps 等技术始终保持着浓厚的兴趣,平时喜欢读书、思考,终身学习实践者,欢迎一起交流学习。微信公众号:架构和远方,博客:  www.phppan.com

营销中的本手、妙手和俗手

$
0
0

2022新高考Ⅰ卷语文作文带来和很多讨论,这个题目还是非常值得讨论的,结合自己的工作经历,我把本手、妙手和俗手联想到了营销方案。

作文材料:“本手、妙手、俗手”是围棋的三个术语。本手是指合乎棋理的正规下法;妙手是指出人意料的精妙下法;俗手是指貌似合理,而从全局看通常会受损的下法。对于初学者而言,应该从本手开始,本手的功夫扎实了,棋力才会提高。一些初学者热衷于追求妙手,而忽视更为常用的本手。本手是基础,妙手是创造。一般来说,对本手理解深刻,才可能出现妙手;否则,难免下出俗手,水平也不易提升。

针对“本手、妙手、俗手” 三个围棋术语柯洁也在个人社交媒体上发声解读:

  • “本手”字面意思为本分的一手,常常形容在棋局当下选择中庸的一手,介于不坏和好之间,但中庸并不等同于“平庸”。
  • “俗手”意为庸俗的一手在绝大多数情况下为贬义,但在某些情况下俗手的交换反而有可能成为当下最好的选择。
  • “妙手”意为卓越的一手,可遇不可求,其本身具有极强的隐蔽性和唯一性,在大多数对局中往往并不常见 ,很多人在对局中经常会过分拘泥于局部,下出“假妙手”而忽略全局思维的错误。想下出真正的“妙手”,必须在平日有一定的经验积累和训练才可能完成真正卓越的“妙手”。

营销中的本手

曾有人向营销大师菲利普·科特勒请教:哪一个词语可以精准地定义营销?大师给出的答案是:Demand Management——需求管理。营销就是A为B创造对方想要的价值,建立与维持关系,以获得回报的思维过程。

在讨论营销的“本手”在哪里的时候,我的理解是依照经典营销理论搭建起来的营销方案。

5C理论

5C是战略的考虑和判断。5C包括:顾客,公司,环境,合作者,竞争者。从这个五个C的进行思考,做出战略方向的基本盘。

纵向来看,5C框架中包含五个分析层面:顾客需要、公司能力与资源、竞争者、协作者和情境,它们之间相互关联,顾客需要是基础,其他部分都是围绕着顾客需要,由需要出发去构建和判断。

横向来看,顾客需要可以分为功能性需要、体验性需要、社会性需要和经济性需要,其中功能性需要又可以被划分为基于硬件的功能性需要和基于软件的功能性需要。基于这些联系和划分将帮助你在分析时更好地构建不同部分间的关系,从而对于营销分析结果建立更全面的认知。而你也会更加了解这个框架,帮助你在未来分析时灵活运用。

STP理论

所有的营销战略都建立在STP——市场细分(Segmenting)、目标市场选择(Targeting)以及品牌定位(positioning)的基础上。

一家公司在市场中发现有不同的需求和群体,并以更优的方式满足,此为目标市场,然后定位它的产品或服务,使得目标市场认知到公司独特的产品和形象。通过建立顾客优势,公司可以实现高客户价值和满意度,这可以引起高重复购买并实现公司高盈利。

公司不可能在大型、广泛或多样的市场中与所有顾客都建立联系。需要识别细分市场,以便能够为消费者服务。这一决策要求它们深入了解消费者行为,仔细思考是什么使得每一个细分市场与众不同。正确识别和满足细分市场通常是营销成功的关键。为了更有效地竞争,许多公司现在开始使用目标市场营销。关注那些最有可能被公司产品或服务满足的消费者,而不是分散其营销活动。有效的目标市场营销要求:

  • 识别并描绘出因需要和欲望不同而形成的独特购买者群体(市场细分);
  • 选择一个或多个细分市场进入(目标市场选择);
  • 对于每一个目标细分市场,确立并传达公司产品或服务的显著优势(品牌定位)。

市场细分(Segmenting)、目标市场选择(Targeting)以及定位(positioning)就是我们熟知的市场营销“STP战略”。

市场细分(Segmenting)

是由一群具有相似需要和欲望的顾客组成。细分市场与目标市场选择的关系,一言以蔽之就是,营销者的任务在于识别细分市场的适当数量和性质,并决定选择哪一个市场为目标。我们用两大组四个变量来细分消费者市场。一组是地理、人口统计特征和心理统计特征;另一组是行为因素。

目标市场选择(Targeting)

有很多统计技术可以用来进行市场细分。一旦公司识别了它的市场细分机会,它必须决定针对多少细分市场以及哪些细分市场。营销者逐渐将一些变量结合在一起以识别更小、更明确的目标群体。比如,一家银行不仅要识别富裕的退休群体,也要根据现有收入、资产、储蓄和风险偏好度在那个群体区分出一些更小的细分市场。

品牌定位(positioning)

没有一家公司会因为提供与其他公司相似的产品和服务而获得成功。作为战略营销管理过程的一部分,每款产品在目标市场心目中,都必须代表合理的那类事物。创造一个有竞争力的、差异化显著的品牌定位需要敏锐地了解顾客需求、公司能力和竞争行动。同时,还需要训练有素而又富有创造性的思维。

定位(positioning)是设计公司的产品和形象以在目标市场的心智中占据一个独特位置的行动。目标是要将品牌留在消费者心中,以实现公司的潜在利益最大化。一个好的品牌定位能够阐明品牌精髓、辨识为消费者达成的目标,并揭示如何以独特的方法实现,从而有助于指导营销战略。组织里的每个人都应该理解品牌定位,并以此作为决策的依据。一个好的定位既“立足于现在”,又“放眼于未来”。它需要有抱负,这样品牌才有成长和改进的空间。只基于当前市场状况的定位不够面向未来,当然定位不能脱离现实以致根本无法实现。定位的真正诀窍是在品牌现在是什么与可以是什么之间取得正确的平衡。

定位的结果之一就是成功地创立以顾客为基础的价值主张,即为什么目标市场应该购买这种产品或服务的一个令人信服的理由。一个价值主张抓住了产品或服务的主要优点并通过满足顾客的需求为客户提供价值。

定位要求市场营销人员定位和传达品牌与其他竞争者之间的相似点和差异点。具体而言,定位的决策需要:

  • 通过识别目标市场和相关竞争状况确定参考框架
  • 在参考框架下识别品牌联想的最佳共同点和差异点
  • 创建品牌真言来概括品牌定位和品牌精髓

如何细分市场

细分市场实际上是细分消费人群。对人群进行划分,我们有很多维度可以进行,比如年龄,地域,教育背景,职业,性格等。但这样说过于松散,我们要总结提炼成体系。

大体上我们可以分为客观变量和主观变量。如果对变量这个说法觉得陌生,可以理解为细分维度和衡量标准。客观的变量指的是固定的、外部的客观描述。其中又可以分为普遍的变量和营销特定的变量;而主观的维度就是对人群的内在的心理数据进行分类的方法。具体我就不再赘述,都放在了表格里方便你一目了然。

这里我要着重介绍一下年龄这个维度。说到年龄也不是指具体的年龄,而是按照年龄进行划分族群。之所以这个很重要,是因为在同一社会环境时期成长的人群他们的消费习惯会很大程度的一致。因此,这也就指明了按照年龄划分族群的根本是具有鲜明社会文化特征的年份。在美国,这会是婴儿潮一代,X代,千禧一代和i代等。而在中国,在近年,又可以再细分为80后,90后,00后等。

除了年龄,另外一个要提的是家庭结构和周期。刚才的年龄/年代是外部的环境,家庭周期是内部的环境,也就是家庭的影响。一个人的消费喜好和习惯很大程度是家庭培养的,同时在决策的制定上也是受到家庭成员的影响。另外, 不同的家庭生命阶段,消费需求也有明显的不同:即使是同一类产品的消费需求,在不同的家庭生命周期也有不同,比如买房这件事,比如保险,比如饮食。不同家庭的周期可以分为:年轻单身、年轻新婚、年轻父母、空巢父母和家庭分解期。见下图。

另外,地理分布也需要单独一提。不管是文化/民族,收入水准,教育层级,还是生活风格甚至媒体使用态度,都会存在地理上的分布不同,以及密度不同。这个地理分布对于零售网点的选址尤为重要。下图是伦敦的区域,按照生活方式不同的一个分布,大家可以感受一下其好用性。

目标市场选择

用合适的、最相关的维度细分了市场之后,就可以针对其中一个有着共同特特点的群体制定一个营销组合(4p),适合该群体的需求和偏好。在这个阶段,对目标人群的充分理解很好用的一个方法是侧写(profile)。就是对最具代表性的这个群体的特征集合在一个人身上,进行拟人化的描述。比如下图。

这个图是2020年在JSM期刊的一篇研究里,对服务的“价值共创”行为乐于参与和不乐于参与的用户性格的描述,对其性格特点进行总结合并然后拟人化,赋予其职业,学历背景和性格。比如乐于参与价值共创行为的拟人一:有想象力的冒险家(左一):女性,36岁,大学人类学学位学历;性格好奇,开放,爱讲话,有创意,喜爱开拓善于视觉化沟通与表达。口头禅是“我看到可能性。”

这样的目标受众的拟人化描述能够形象地描述理想的目标用户,保持这样活生生的形象在脑海中,可以对公司工作人员在产品设计到营销的各个层面能够很有效进行设计。并兼顾细分市场的差异(比如男性和女性特点兼顾或分开再细分)。

定位如何定

定位是旨在在目标受众心智中建立一个清晰的位置。其理想的目的是让消费者在该类别的购买需求产生时,能第一时间无提示地想到该品牌。从这个任务出发,品牌(或产品)的定位就需要个性强(区隔性高),以及需求关联性高。个性不强,会与其他品牌混淆,用户想不起来或者想错。需求关联性不高的话,用户在该购买需求产生时,想不到你。

对公司来讲,有一个清晰定位同时可以避免不必要的竞争:找到自己的位置满足一个特定细分市场,而不是在一个细分里几个品牌拼死厮杀。

在这个里需要介绍一个工具, 就是感知地图(perception map)或者也可以叫二维地图。做法是使用该产品最主要的两个变量(特点)将市场分为四个区域。如图所示。

这个图是巧克力某些品牌的定位感知地图。如果从情感-功能,社交-个人的两个维度来划分,是这样的关系。图中产品外观所在的位置就是他们在消费者感知的心目中的定位。

如此,如果有一个新的品牌进入,就可以从这个分析后选择一个自己品牌期望的位置。是提供具体的功能,还是宠爱自己的心理愉悦?是个人享受的,还是给大家分享、送人为目的?你能看到,这样的位置是深刻地决定该品牌的营销策略。比如提供心理愉悦价值的品牌,广告诉求里就不要讲成分和功效,而是渲染情调和氛围,以及广告可爱女主角吃到的愉悦感受。

除了二维的感知地图,还可以有多维度的感知地图,这个是高阶一点的操作,有时需要软件的协助。但是原理是一样的。这里只介绍概论的基础知识,就不涵盖这么多。以后有机会单独撰文介绍。

定位的原理很容易理解,也很容易制定。但是这里面有些问题要注意。就是定位的误差和带来的风险。除了分析市场,还要分析自身:这个定位是否适合你的公司。就想我在营销框架里讲的5C部分,你要先分析市场环境和自身优劣势。否则,就可能会定位过窄,或者过宽;这样要不没有足够受众,要么定位无效。或者定位困惑,甚至可疑—因为不符合。如下图所所示。

这是另一个二维图:从你宣称的定位的独特程度,和可信程度两个维度进行划分,分出四个风险的示意。这个字面就非常清楚,不需要我详解。如果疑问,可在评论里问我。

将大的行业市场进行细分,对目标人群进行细分,然后选择最适合或者最期望的一个群体,精准描述、分析他们的特点,然后在此基础上定位你的品牌与产品,从而做到精准的传递,减少战略的失误,降低战术的浪费。

4P理论

4P即产品(product)、价格(price)、渠道(place)、促销(promotion)4P是我们在做市场分析和制定市场营销战略时常用的工具,它更多的是要求企业对自己自省。在经营一个企业或者准备创业时,你的优势是什么?是优质的产品,还是掌握了供应链资源,能够凭借低廉的价格进入市场,抑或是多元化且丰富的渠道(人脉),再或者是你具备深厚的专业知识,可以制定出多变且丰富的促销玩法。

4P也就是教科书里的“营销组合”,也是营销的最基本的四元素:产品,价格,地点与推广。用最简单的话说,就是“做什么样产品,通过什么样的推广宣传、在什么地方(渠道)以什么价格卖出去”。地点(place)现在我们已经用“渠道”来替代。

同样是4P,针对不同的消费产品类别,他们的4P策略就明显不同。大众消费者的购买类型,可分为三种:

  • 便利购买型
  • 商场购物型
  • 专业购买型

便利购买型

便利型购买就是购买情形图个便利的购买。具体又可以分为三种:

  • 基本生存商品购买:比如米、面,麦片,酱油醋。
  • 冲动型购买:比如冰淇淋,巧克力,口香糖,杂志。
  • 应急购买:比如感冒冲剂;雨伞。

即使同为便利购买,他们的营销组合策略就都不同。

用表格来表示。

购买类型举例营销组合策略消费者行为
基本生存商品米、面、酱油醋最大化地曝光-广铺货;便利店低成本物流;生产商大量批发;低价;品牌重要日常购买(习惯性购买);购买频率高;低介入度;
冲动型口香糖、杂志最大化在付款台铺货无计划购买;购买迅速
应急型感冒药、雨伞尽可能在需求点附近铺货;价格敏感度低需求紧急,时间压力大

这里很容易看到,虽然都是图个便利,里面门道却很多;营销成败都在细节。比如基本生存商品,因为消费者购买频率高又长期需求,这类产品的利润就可以定的很低,靠规模出利润,也就是“薄利多销”。

因为是必需品,市场成熟竞争激烈,品牌的重要性就凸显:品牌是消费者做购买决策的线索,比如牙膏,比如洗衣粉。因为市场成熟,每个品牌的产品质量都相差无几价格也都类似,能让消费者做决策的线索太少,最后干脆靠品牌来选择。

也因为是习惯性购买,重复性购买的时候以及无须高介入度(购买决策的流程简单),那么对首次购买的用户抢夺就很严酷。如果首次没有抢到消费者,那么该消费者后来的更换品牌成本就很高,需要竞争品牌利用或者创造机会来撬动,比如,新功能产品的推出,比如免洗洗衣粉,免费样本派发,从而撬动消费者转投新品牌门下。曾经价格战也是一大利器,因为伤人伤己并不常用,但是变相降价,比如使用赠品战术,市场中也是屡见不鲜。

冲动型和应急型也很好理解。因为冲动,就要在冲动时赶紧促其完成购买,否则放在购物车时间久了就可能反悔,夜长梦多。因此,结账台是最好的去处。而应急产品,因为需求急,价格已经不是问题,重点是出现在何时的地点;地点决胜(place > price)。

商场购买型

商场购买是指正式一点的购买,比如买衣服、鞋子,家电之类,需要去商场“专门逛”。同样我们可以把商场购买分为两类,同质化和异质化产品。

  • 同质化是指该产品类别的竞品本质上类似,比如冰箱,洗衣机,(开户)银行。
  • 异质化产品是指该类别的竞品本质上不同,比如服装,家居,盘子等。

为什么同质不同质重要?因为它关系到购买决策,从而影响着营销组合4P:产品,价格,推广和销售渠道。

同样用图来看,直观一些。

购买类型举例营销组合策略消费者行为
同质冰箱,洗碗机需要足够曝光促进价格对比;价格敏感度高;网络购物渠道影响大消费者难以分辨哪家好,倾向低价
异质服装、家具,家居需要跟同类产品陈列一处;着重推介产品优势,适合人员推销;低价格敏感度延伸问题解决;消费者需要帮助做决策。品牌不是很重要因为消费者更对比质量与价格
  • 因为竞品同质,价格变得重要-这样最少即使买错了也不会买贵了。因为竞品同质,品牌变得重要。
  • 因为竞品不同质,决策依然困难,需要有人协助出主意。衣服是哪件都好看;家具比如沙发,也是各有各的好,有的皮质好,有的样子洋气。因此销售人员就很重要,帮助购买者做决定:哪件衣服气质好,哪件衣服显年轻。因为有可见的质量和价格做线索来做决定,品牌的角色就不是很重要。

专业购买型

专业购买(specialist purchase),是指商场内一般见不到、需要单独去一个地方购买的商品,比如买一辆汽车,比如买个大鱼缸,比如买个保险产品。

专业购买一般需要一定的知识,同时一般是价格偏高又不是日常常购品,因此属于高介入度。这样一来,现场的人员就很重要,高度介入购买者的决策的早期步骤就很重要。另外地点也要考虑,尤其是大城市。

以上的内容旨在展示,虽然市场营销的理论相同,但是市场的多样性非常高。同样的理论用在不同的情境、不同的产品类别、不同的购买情形,营销组合的具体实施就很大不同,需要因地制宜、量体打造,而掌握好精准度,要基于对消费者行为的深刻理解;始于洞察。

营销中的妙手

关于营销中的妙手,我的得理解是类似最近几年比较火的增长黑客概念。

增长黑客是什么不是什么?

Growth Hacking,字面意思就是获取增长的密钥,最早由互联网创业者 Sean Ellis 在 2010 年 6 月提出。说它指的是一种用户增长的方式,说的直白一点,就是通过某些手段和策略帮助公司形成快速成长。对创业公司、特别是初创公司来说,在没有广告预算、市场营销活动以及市场推广专员的情况下,Growth Hacking 也可以获得良好的效果。

根据维基百科,Growth hacking的定义是一个通过技术公司使用创造力、分析思维和社会准则来销售产品获得曝光率的销售手段。相较于把Growth Hacking看成一组技术驱动的营销工具,我更愿意把Growth Hacking看成一种技术驱动用户增长,又与行为经济学结合的产品思维。在Growth Hacking过程中,如何了解用户行为,如何了解用户的需求是增长的基础。

什么是增长黑客?

要解释 Growth Hacking,就要提到与之关联的另一个词——Growth Hacker。Sean Ellis 最先提出 “Growth Hacker” ,并帮助硅谷多家公司完成产品的快速增长,其中不少已经IPO,其中最著名的是Dropbox。当时Sean在Dropbox负责用户增长,他用了一年的时间,将用户的基数和使用频率提高了500%。Sean对黑客增长有一个很有趣的定义:黑客增长的唯一的使命就是Growth,因为公司的估值是与增长息息相关的,增长是所有公司核心指标。

Growth Hacker把若干的角色集于一身。首先,增长黑客是个工程师,其次是分析师,再次是产品经理,甚至还是个市场人员。最后,它还应该是一个心理学家。增长黑客通过心理学、产品、工程、数据驱动、业务运营的角度,用低成本的方法在最快的时间内实现增长。这种增长,不仅限于用户数量的增长,还有用户体验的增加,以及营业额的增长。所以,它的核心理论,是围绕各种基础框架,用最低的成本、最高的速度,来获取客户。

  • 一群以数据驱动营销、以市场指导产品,通过技术化手段贯彻增长目标的人。
  • 既了解技术,又深谙用户心理,擅长发挥创意、绕过限制,通过低成本的手段解决初创公司产品早期的增长问题。
  • 职责接近于专门为初创公司设立的市场推广部门,因为很少有用于营销的大笔经费,所以更多的是将注意力聚焦在产品策略本身带来的自发增长上。
  • 必须真正懂得产品的核心价值,能用最简单的语言描述这个产品是什么、解决什么问题,在此基础上清晰定位有关增长的问题,并寻求解答。

Facebook 安迪琼斯总结了Growth Hacking的5点职责:数据分析、用户获取、产品研发、文化建设、人才招聘,总结起来Growth Hacker的价值需要涵盖:

  • 策划产品架构:一款能够自增长并值得推广的产品,始于精心谋划的基础架构搭建,确保达成P/MF阶段。
  • 早期用户获取:通过各种手段精确地定位目标用户群体,以最大化的ROI完成渠道建设和用户积累。
  • 增长机制设计:加速用户获取,并助推您的用户激活、留存、营收及病毒传播,提高流程转化率。
  • 传授增长思维:为企业和员工树立增长思维,以数据驱动、精益创业和结果导向主导互联网产品的研发。

Growth Hacker该有的品质:

  • 痴迷数据。Growth hacker 对跟踪和推进指标的热情近乎痴迷。要是没有指标或者数据,Growth Hacker 会感到浑身不舒坦。对数据的强烈偏好促使 Growth Hacker 抛弃虚有其名的指标,转向可以做成生意和实现业务突破的指标。数据和指标是 Growth Hacker 发现发展道路至高无上的科学手段。Growth Hacker 对指标的看法不像报告机制那么严格,也不把数据当做钻研的方式,而是通过理论化和测试过程把它们当成做出更好产品的灵感之源。Growth Hacker 关注的是通过反复推进特定指标的办法来实现增长。这样的指标可以是注册转化率,可以是维里系数(viral coefficient)。数据可以启发新产品及可行动的市场细分。
  • 有创造力。尽管有数据和发展指标的驱动,Growth Hacker 一样也是富于创造力的问题解决者。Growth Hacker 绝不会止步于数据,而是会拓展出新的未知领域来找到增长办法。创意和分析的结合是 Growth Hacker 的典型特征。“创意人才按照直觉设计出对用户最好的东西,析人才则提供有深刻见地的洞察分析。真正的独角兽是那些能够完成从设计、开发、衡量到分析的全过程的事情,并能够根据结合了用户直觉与深度分析的结果不断迭代改进的人。” rowth hacker 做的事情跨越多个职能部门和学科,从 UI/UX 到指标决策,不一而足。创意和分析的结合使得 Gowth Hacker 对产品拥有融合的、系统性的视角。
  • 还很好奇。Growth Hacker对访客何以变为用户并被吸引感到痴迷,着魔于产品为何会一败涂地。对于令人困扰的用户,Growth Hacker 习惯去探究,找出推进和调整指标的新办法。身为Growth Hacker 必须永不止步。Facebook 已经有十多亿用户,但仍有一个增长团队。” Growth Hacker 总是保持好奇,对于学习永不知足。他们深入审视用户行为,在行为经济领域不断封疆拓土。“好的 Growth Hacker 对互联网有好奇心,对其运作方式有着深刻理解。好的 Growth Hacker 会去读《轻推》(Nudge) 和《怪诞行为学》(Predictably Irrational) 这两本书,发掘可能的破解发展之路。” 这种好奇心能够令其突破表象,把握产品和用户体验之精髓。Growth Hacker 对于有没有实现增长并不是很关心,而是渴望去理解用户的心态和产品流,以便让这种办法不断重复。

Growth hacker 是一种稀有物种,需要把数据、创造力及好奇心这三种不太可能凑到一起的东西混搭到一起。

增长黑客与传统营销有什么区别?

传统营销通过市场推广及投放引入流量,然后关注转化变现,最后关注留存、最后消亡。

传统营销的模式是否适合今天的精益创业?

  • 流量越来越贵,用户获取成本越来越高。
  • 竞争越来越大,市场占比会被不停的侵占。
  • 如果产品不够好,很难增加用户的粘度,用户会很快流失。获客成本高于生命周期的价值。

在过去,营销和产品开发部门往往不相一致:营销团队可能花大手笔去获取用户,但与此同时却无法获取任何关于产品开发的资源,而另一方面,产品开发作出自以为用户会使用的产品,在尚未真正理解、衡量他们的改动对用户实际的影响时,就开始去尝试吸引用户。“Growth Hacking” 的概念,实际是指一种新的认识,即当你专注于理解你的用户,理解他们是如何发现并接受你的产品时,你可以更有针对性地开发出那些真正能够获取更多用户、留住老用户的功能,而不是一味盲目地把钱花在营销上。

增长黑客的差异点:

  • “Make stuff people want.” – Paul Graham。Instagram当初起家于Burbn,一家基于地理位置的社交应用(当时支持拍照功能)。随着产品的发展,创始人发现用户对该应用的照片和筛选功能情有独钟。因此他们加大了这个功能的宣传工作,瞬间就引起了市场的响应。10万的用户在一周内又重新回到了Instagram,18个月后,Instagram就以1亿美金的高价被收购了。
  • “It’s a mindset not a toolkit.” – Aaron Ginn。相对于解决问题的方案本身而言,整体的考察和规划显得更加重要。“Growth Hacking” 其实是一种多变的心态,大体可以罗列为:数据研究,市场拓展能力,创造能力,不按“常规出牌”的思路。你如果去咨询100个“Growth Hacking” 他们的秘密武器是什么,也许会得到100个不同的答案。但是这些秘密武器不管是什么,之中都有一个共通的特征 – 花最少的成本,收获最大的利益.如果你能拥有这种心态,并且开始改变自己分析和解决问题(机遇)的方法,你会发现这将成为你伟大的财富。和投资者,媒体间的关系如同程序语言,技术培训一样,是需要特殊技能才能做好的领域。“Growth Hacking” 的心态,贯穿在所有的情形之中,且屡试不爽。不管你从事什么职业,就职于哪家公司,正在为什么产品做市场。
  • “I prefer the discipline of knowledge to the anarchy of ignorance. We pursue knowledge the way a pig pursues truffles.” – David Ogilvy。Dropbox 之前一直靠在网页上点击付费的形式拉取新客户,但是很快他们就意识到花300多美元才换来一个新用户将不再是一个可持续的策略,于是他们转向新的方式— 用户可以邀约自己的朋友来注册dropbox,同时将被赠与更多的免费空间(当然,还有一系列的产品来奖励用户)。不出所料,效果非常好。在接下来的几个月里,Dropbox的用户注册量直接飙升60%,迄今为止,Dropbox35%的用户都是通过邀约加入使用该产品的。这就是科学的方法取胜的最好佐证。凭着直觉或预感,动用上千万的资金的项目不再需要借口,只要你能一直跟踪广告带来的后期效果,并且了解用户在网站上留下的足迹,了解用户的真实反馈。没人定义说市场不能是一门艺术,但是科学确实能指导正确的方向,以免破财又走弯路。

增长黑客逻辑:体验场景 + 数据技术

  • 体验场景是增长黑客的内在逻辑之一,肖恩埃利斯将其形象地描述为「啊哈时刻」,就是用户在使用产品时「眼前一亮」的时刻。这种时候,产品必然释放了一种无法替代的用户体验,而这往往就是基于一种特定场景的产品的核心价值。例如,对于 Yelp(来说,这种体验产生于用户现场查阅当地餐馆及其他商家,并获得来自其他用户的真实评价时。再如,对于 Twitter(或微博类产品)来说,这种体验是用户看到自己关注的人的实时动态,感觉到对方「就在身边」。可以说,「啊哈时刻」是持续增长的基础,有了这个基础,使用者就会忍不住与朋友们进行分享,这就是产品指数级增长的基础。
  • 数据技术是增长黑客的另一个内在逻辑。当产品具备了「啊哈时刻」的属性,还需要引导用户去发现它,这就需要精确的数字分析。例如,Twitter 发现很快关注了 30 个人以上的用户比其他用户活跃得多,更容易留存为核心用户。深入挖掘数据之后,发现 30 个人以上的关注量形成的信息量,能够提供让用户为之「啊哈」的体验。这个阈值是如何发现的呢?原来,通过群组分析,Twitter 的增长团队发现每个月访问至少 7 次的用户中有 90%~100% 会留存到下一个月,而「7 次」这个临界值在统计结果中异常明显。再接下来,Twitter 发现每月至少访问 7 次的用户中,普遍关注的用户数都在 30 人以上,于是,30 人就成为阈值。再深入分析(用户采访),又发现除了 30 个人的关注数之外,「回关」(当别人关注了你时,你也回过来关注他)数据也是关键,1/3 左右的回关数是最理想的。因为,如果超过 1/3 的回关量,Twitter 就和其他社交网站(与微信朋友圈类似的)没有区别;而如果不到 1/3 的回关量,Twitter 就变成了新闻网站。接下来的事情就变得简单化,Twitter 的增长团队将重心放在了向用户精准推荐关注对象上。

你可以从网络上找到很多增长黑客的案例和奇技淫巧,从案例本身来讲确实是妙手,但是你要是照着执行或生搬硬套,会发现中间的很多妙手已变俗手。妙手之所以成为妙手是因为和体验产经和数据绑定,纯粹的照搬并不会带来“啊哈”时刻。

营销中的俗手

我们接触到的大都是营销方案都可称的上俗手,这里的俗并不是恶俗,而是老套。当一个产品通过某一种新型的营销方式爆火时,你会发现短时间内有一大批模仿者出现。即所谓的跟风营销。

跟风有两种,一种是跟热点,最近什么火就折腾什么,另外一种是跟经典,经典案例怎么做就怎么做,完全照搬。

跟风营销存在的问题:

  • 拿来主义并不是完全没有效果,而是效用非常的低
  • 缺乏深度思考,缺乏对案例成功背后的“前提”进行深入研究
  • 缺乏对数据的深入判断,不知道接下来如何改进。

本手、妙手和俗手得启示

本手、妙手和俗手给我个人得启示,更高是营销得时候尽可能得避免俗手,不要把过多得精力耗费在妙手上,而是从本手出发,在完善本手后在想着如何从妙手着手。

参考链接:

通俗易懂理解数据库概念

$
0
0

在Quora上曾经有个问题: Computer Programming: How would you explain a database in three sentences to your 8-year-old nephew被搬到了知乎上。

来自知乎的答案

中药铺说

电脑里面存了好多好多数据。数据就像各种各样中药,在没有数据库之前,中药就是一包包这样杂乱无章地堆放着(unorganized data),找也不好找(inaccessible)。如果你是药店的掌柜,你怎么来管理这些药呢?

聪明的古人就想了办法:把所有的中药都放到柜子里面(data table),柜子里面都是大小一样的小盒子(organized data model),每个盒子(a row / record)都在外面标签上写上药名,按笔划排序帮助快速查找(index / primary key),比如你要找三七,那么一定是三笔的头几个——现在找东西是不是方便多了?

如果要找一味温热的药,按名字找可就不大灵了,除了打开抽屉一个一个舔过去,还有什么办法快速找到吗?对啦,就是在抽屉上涂上颜色,比如温热的用粉红色,凉性的用蓝色(secondary key),你还可以用不同大小的抽屉代表药的其他属性(another secondary key)

如果有的药卖空了没有货怎么办?把整个抽屉拿走(delete a row)!如果新增加一种药怎么办?找一个空抽屉放上新药贴个标签呗(add a row)!如果整柜子中药都过期了怎么最快处理?把柜子搬走(drop a table),换个新柜子(create a table),再往里面添加新鲜中药。

如果有一种药量特别大,放在另外一个抽屉大一点儿的柜子里怎么办呢(splitted table)?在这个抽屉里放一张纸条,写上“此药在后堂第三个柜子第二个抽屉”(Foreign key / linked table)。

图书馆说

  • 图书馆就是一个数据库:图书馆的每本书都有一个编号,编号表示了书的类别和顺序号,同类别的书放在一个书架上,然后书按顺序摆在它所属的书架上,这么做的好处是方便查找书。
  • 图书馆管理员就是访问接口:你想找一本书时,他先找到这本书的类别和顺序号,然后他就到指定的书架上按顺序找到那本书,交给你。
  • 你和其他人提出的借书请求就是外部程序,一本书可以借给你也可以借给别人,但是图书馆管理员知道书的状态并负责把书放回原位。

一篇通俗解释文

什么是数据库呢?

每个人家里都会有冰箱,冰箱是用来干什么的?冰箱是用来存放食物的地方。同样的,数据库是存放数据的地方。正是因为有了数据库后,我们可以直接查找数据。例如你每天使用余额宝查看自己的账户收益,就是从数据库读取数据后给你的。

你可能会问了:我的数据就存放在自己电脑的excel表里就可以了,为什么还要搞个数据库呢?这是因为数据库比excel有更多的优势。数据库可以存放大量的数据,允许很多人同时使用里面的数据。

举个例子你就明白了,excel好比是一个移动硬盘,你使用了这个移动硬盘其他人就用不了了。数据库好比是网盘,很多人可以同时访问里面里的数据。而且网盘比移动硬盘能放更多的数据。

数据库是如何存放数据的?

数据库有很多种类,这里我们重点学习使用最广泛的关系数据库。关系数据库是由多个表组成的。如果你用过Excel,就会知道Excel是一张一张的二维表。每个表都是由行和列组成的。同样的,关系数据库里存放的也是一张一张的表,只不过各个表之间是有联系的。所以,简单来说:关系数据库=多张表+各表之间的关系

对应的,学会关系数据库我们只要掌握两点就可以:

  • 多张表里面,每一张表的结构
  • 各表之间的关系

我们接下来分别来看看这两个知识点。

表的结构

表的结构是指要了解关系数据库中每张表长什么样。每个表由一个名字标识。表包含带有列名的列,和记录数据的行。我们举个具体的例子就一目了然了。下面图片里的表名是:学生表,记录了每个学生的信息。

表中每一列都有一个名字来标识出该列,这个表里有4列,列名分别是学号,姓名,出生日期,性别。从列名上你也可以知道这一列对应记录的是什么数据。

表的每一行里记录着数据。这里的一行表示该名学生的信息,比如第2行是学号0002学生的信息,他的姓名是猴子,出生日期是1990-12-21,性别是女。

各表之间的关系

关系数据库是由多张表组成的,图片里是存放在学校数据库里的4张表。你能发现下面这4张表之间有什么关系吗?

什么是关系呢?你是你爸爸的儿子,你是你的儿子的爸爸,这就是生活中的关系。其实,数据之间也是有关系的。关系数据库里各个表之间如何建立起关系呢?我们来看图中“学生表”,“成绩表”这两个表之前的关系。

这两张表通过“学号”关联起来,为了更清楚的看到这两个表的关系,PPT里我用相同颜色代表同一个学生的信息。例如我想知道学生表里学号“0001” 的成绩是多少?那么我就可以在成绩表里去查找“学号”值是0001的行,最后在成绩表里发现有3行数据的学号都是“0001” ,对应的就找到了该学生的三门课程的成绩。

通过这个例子你应该对表之间的关系有了大概的了解。关系就是数据能够对应的匹配,在关系数据库中正式名称叫联结,对应的英文名称叫做join。联结是关系型数据库中的核心概念,务必记住这个概念,后面会在多表查询中具体学到。

什么是数据库管理系统?

前面讲的都是关系数据库原理方面的基本理论。理论有了,当然的就的有对应的软件实现才能用起来,不然再强大的理论都是一堆无用的东东。这就好比,建筑师如果只有设计草图是无法盖起楼房的,得有具体的建筑人员才能盖起楼房。

所以,上面讲的关系数据库原理就是“设计草图”,那么对应的“建筑人员”是谁呢?实现数据库原理的“建筑人员”就是数据库管理系统,用来管理数据库的计算机软件。

关系数据库管理系统有很多种,比如MySQL、Oracle、SQL Server等都是实现上面理论的关系数据库。

什么是SQL?

建筑施工人员通过使铲子,拉土机等工具来盖房子。那么,我们通过什么工具来操作数据库里的数据呢?这个工具就是SQL。

SQL是为操作数据库而开发的一种语言,它可以对数据库里的表进行操作,比如修改数据,查找数据。把数据库比如一碗米饭,里面放的米是数据。现在我们要吃碗里的米饭,怎么取出碗里的米饭呢?这时候我们拿一双筷子,用筷子操作碗里的米饭。这里的筷子就是SQL,用来操作数据库里的数据。

一篇来自老外的数据库简明教程

什么是数据库?

数据库是文件柜。数据库不一定要那么复杂。如果你去掉所有花哨的语义,你最终会得到一些简单而熟悉的东西。

字典、日历和文件柜特别有用,因为它们以排序的方式来表示信息,使得我们可以使用一种叫做二进制搜索(二分搜索)的过程来快速有效地检索信息。数据库也不例外,它们也是分类存放信息的容器。

什么是排序信息?

最简单的排序方式是按字母顺序排序(也就是按词法排序),这就是字典中单词的排序方式。但是,我们经常发现自己要按多个属性进行排序。例如,一个联系人列表通常是按姓,然后是名来排序的。这就是所谓的复合排序。

我们到处都在做这种复式排序,尤其是日期,是按年、月、日复合排序的。请注意,这种排序并不完全是按字母顺序排列的。数字排序与字母排序不同,因为字母排序是逐个字母进行比较的。例如,如果我们按字母顺序对数字进行排序,那么10将排在2之前,因为第一个数字 “1 “在 “2 “之前。用一种简单的方式来表示数字,可以按字母顺序排序,就是在数字的开头加上0。这对于有已知最大值的数,如一年中的月份,效果很好。而事实上,我们可以用ISO 8601日期格式来表示日期,可以按字母顺序排序。用这种方式表示日期是很方便的,因为它不需要领域知识来理解如何排序。

一般来说,词法编码很重要,因为它们允许数据库以正确的顺序存储任意信息,而不需要额外的关于被排序信息类型的知识。

数据库案例:警察部门

你可以用思考系统文件柜的方式来思考数据库。为了进一步探讨这个类比,让我们想象一下,你负责管理一个警察部门,你要跟踪你所在地区的每一个警察的各种信息: 姓名、警徽号、地址和辖区等细节。作为这些信息的管理员,你有责任确保所有信息都是最新的,并且易于访问。为了使事情简单化,你首先要把所有警察的信息放在一个文件柜里,按警徽号码分类。这样就可以很容易地根据警徽号码检索到任何官员的信息,例如,当一个警察晋升或搬到新的地址时。

读/写平衡

有一天,局长要求你提供第60分局中,每个警官的地址。所有这些信息都在一个档案柜里,按警徽号码分类,这是个很痛苦的要求,因为你必须扫描每一个档案,检查每个警官是否在第60分局。也许这只是一个一次性的要求,所以你可以劳而无功,但警察局长说,每个月他都要给辖区内逮捕人数最多的每个警员邮寄一张奖金支票。如果你每个月都要这样做,也许值得让这项工作快一点。

为了解决这个问题,你建立了另一个档案柜,在那里你按辖区对所有人员进行分类。为了简单起见,你把 “警徽#”柜里的所有信息都复印到 “辖区”柜里。现在,当局长要求你获取一个辖区内每一位警员的地址时,你可以使用这个新的文件柜来快速检索信息。这个新的档案柜让你更容易获得信息,但是每次你要更新信息的时候都会产生一个新的问题。如果一个新的官员加入一个辖区,或者从一个辖区搬到另一个辖区,你现在必须确保信息的正确性,并在两个不同的地方更新。

更糟糕的是,我们最终会浪费时间来更新不必要的信息。例如,也许我们不需要出生日期,也不需要我们制作的新分局档案柜里的官员照片,但我们还是会把它复制过来,这样信息就不会不同步。

另一方面,也许我们决定在辖区柜中存储的唯一信息就是警徽号。这样一来,更改和管理官员的信息所需的工作就少了很多 —— 我们只需要在原来的警徽号柜中进行更新。这也意味着,辖区柜的体积要小得多,因为我们没有那么多信息挤在里面。

但是有一个问题。现在如果你需要得到一个辖区内每个官员的地址,你就得先用你做的新档案柜来得到辖区内的官员名单,然后你就得用原来的警徽#档案柜来得到每个官员的地址。这样做还是比没有辖区柜要快,但没有之前扫描每个警察档案的方法快。

从根本上说,没有 “最好 “的方法 —— 这都是你在读取与写入信息时要做多少工作之间的权衡。

在这两种方法之间有一个中间地带,我们将地址和徽章号码都记录在我们创建的新辖区文件柜中。这就达到了一个很好的平衡,我们不必在辖区柜中更新我们不关心的信息。

然而,这确实带来了一些后勤方面的开销 —— 现在我们有了一份更新徽章号柜时必须遵循的程序清单。

查询规划

现在我们假设,每当有人对某位警官提出投诉时,我们都想在该警官的档案中记录下这一投诉。通常情况下,当有人提出投诉时,他们没有该官员的警徽号码,但他们有该官员的名字。

为了更方便地按姓名查找警员,我们建立了另一个档案柜,将所有警员按姓名分类。现在我们的档案系统是这样的。

三个文件柜。徽章号、辖区和姓名还有保持所有东西更新的程序表。现在,让我们想象一下,这个警察局从1万名警察发展到1千万名警察。你收到一个投诉,投诉人是121分局的史密斯警官,棕色头发,蓝眼睛。你正在考虑用两种不同的方式来查找这位警官 —— 你可以扫描121分局的每一位警官寻找史密斯警官,也可以扫描121分局的每一位姓史密斯的警官寻找这位警官。你做了一个假设,同名同姓的人可能比同一分局的人少,所以你决定先按名字查找警官。

在数据库中,这个过程被称为查询规划:你有一个问题(一些你想检索的信息)和一个现有的档案柜设置,你需要确定收集所有这些信息的最快方法。所以你查看了你创建姓名的数据库,结果发现121分局里居然有上百名叫史密斯的警官。再次,你决定值得想出一个更快的方法来做这件事。你想要的是类似于你创建的辖区柜的东西,并按警官姓名进行二级排序。但辖区柜不包括官员姓名,所以你必须在其中创建一个新的文件柜来包括他们。您决定将官员姓名复制到现有的辖区柜中,然后按姓名排序以节省空间,这样可能更有效率。

正如我们前面所讨论的那样,这是一个复合排序,因为我们要对多个属性进行排序:辖区,然后是姓名。

文件柜的流程

为了回应公众对警员投诉飙升的强烈抗议,警察局已经聘请了当地的审计公司对每个辖区进行审计。现在,每当增加或修改一名警员的信息时,我们都需要通知分配到该警员所在辖区的审计小组,告知他们这些变化。

我们开始把这些程序写在和以前一样的清单上,但我们开始意识到,这将是一个非常长的程序清单。

每当我们对警徽号码柜进行更改时,我们现在都需要扫描这个庞大的程序列表,这需要花费太多时间。所以我们要做的很简单,我们把所有的审计师地址移到一个新的档案柜里,然后在我们的程序列表中增加一个步骤,告诉职员在哪里查找审计师的地址。

一个名为 “辖区审计员 “的档案柜,里面有每个审计员的地址,并有一个单一的程序来查找相应的地址。这就容易管理多了,但你可以想象,随着组织复杂性的发展,程序清单会不断增加;人力资源部门每当地址发生变化或有人投诉时,都想知道;财务部每当某位官员晋升时,都需要知道,这样他们就可以适当调整工资单 —— 程序清单还在继续。

通知人力资源和财务部门的程序。不难看出,这个程序列表本身可以变成一个按属性分类的文件柜。这样一来,当我们只是更新一个地址的时候,就不需要把每一个属性的过程都读一遍。因此,每当我们对警徽号码柜进行更改时,我们就会在这个程序柜中查找每一个更改的属性,看看我们需要做什么。

一个程序档案柜,展示了随时向人力资源部门邮寄最新资料和地址变化的程序。

到目前为止,我们只为警徽号码柜维护了一个程序列表,但其他机构的文件柜也需要同样的程序列表。而事实上,这样做真的很有用,因为它可以让我们将程序列表划分为更有效的程序排序方式。

例如,假设有一个正在进行的调查,FBI想知道第50分局中姓科伦坡的官员是否有任何变化。

我们可以为警徽号码柜创建一个程序,每当一个名字或一个辖区发生变化时,就会检查,然后检查是否是第50辖区和姓科伦坡。

辖区属性发生变化时触发的存储过程,该过程说:”如果名字是科伦坡,辖区是50,那么邮寄给FBI”。

但是,每次更换辖区时,检查这个程序会浪费很多精力。例如,假设史密斯警官从60号分局调到61号分局 —— 当你查找程序时,你会看到这个程序,需要检查50号分局的科伦坡。

有一个更好的解决方案 —— 我们可以为辖区柜创建一个程序。回想一下,辖区柜是一个复合排序,先按辖区,再按姓名。因此,我们可以通过对程序使用复合排序来使我们的程序柜变得非常精细。

现在,当我把史密斯警官从60号辖区移到61号辖区时(下图中的第一个柜子),我会看到一个程序告诉我更新辖区柜子,更新辖区柜子后,我会查找辖区柜子的程序,看看是否有61号辖区和名字史密斯的程序。注意,我们能够跳过检查50号选区和名字Colombo的程序。这真是太有效率了!

将史密斯警官的辖区更新为61,更新辖区内的柜子,在辖区程序柜中没有看到相关程序。

程序列表,在更新柜子的时候,能够有效地查询到需要做哪些程序,这对于信息系统的自动化来说,是一个非常强大的抽象。甚至于,这种抽象超越了现有数据库系统的能力。

审计日志

当经过一段时间后,有这么多的人和这么多的信息变化,它变得重要的是要跟踪所有的细节,围绕着谁改变了什么,什么时候。例如,也许你打开琼斯警官的档案,上面写着他是一名侦探。而也许你以为他只是一个副手,所以你可能会倾向于问一些问题,比如 “他什么时候晋升的?”、”谁晋升的?”、”今年有多少人晋升?”。这些问题在我们目前的设置下是不可能回答的,你可以想象,如果你管理的是一家银行,你会经常问这类关于账户之间资金流动的问题。

在银行里,每一笔账户之间的转账都会被记录下来,并附上某种收据,永远保存下来。而在我们的警察局,改变任何信息都可能用某种表格来记录。例如,当警察局长想把琼斯警官提升为警探时,他就会填写一份 “提升表”,然后把它交给行政办公室的档案员。

档案员可能会把这张表格放在一个档案柜里,用来存放晋升表格,按级别和日期分类。然后我们查到一套这次晋升的程序,其中有一条就是简单地更新警徽号码柜里的警员等级。

这个过程肯定比简单地更新警徽号码柜中的官员等级需要更多的工作,但它允许我们审计变化,并回答一系列更广泛的问题,即事情如何随着时间的推移而变化。例如,我们可以从 “晋升表格 “柜中查看一段时间内所有级别的晋升情况。如果我们想确定一个官员何时晋升,我们可以创建一个单独的柜子,将这些晋升表格按徽章号,然后按日期分类。

当为了审计目的而保存历史变化列表时,通常必须建立机制以确保信息不被篡改。例如,假设一个不良行为人想秘密添加一个记录,显示琼斯警官在12月11日被降职,而在12月12日被提升(也许是为了制造一个欺诈性的丑闻,他们可以在新闻中写出来,在选举前诋毁警察局长)。

签名是这里的第一道防线。如果签名很难复制,那么就很难制造出一份欺诈性的降级表(注:我们在降级时也使用同样的晋升表)。另外,我们可以做的是在晋升表格中备注之前的级别。也就是说,如果不良行为人想要创建这个欺诈性的降级表格,他们还需要欺诈性地更新12月12日的晋升表格,以参考之前的降级等级。

在物理世界中,防范这种欺诈是相当困难的,不可避免地需要对系统的某种信任。但在现代世界,通过加密和单向哈希,我们可以为记录创建无法篡改的签名。这正是区块链防止比特币欺诈性交易的工作方式。

超越文件柜

到此为止,我们已经几乎涵盖了关于文件柜的所有有趣的东西,以及我们如何利用它们来管理大量的信息与操作程序。但对于任何一个现实的行政管理部门来说,还需要几个重要的系统。

  • 第一个是管理权限。谁有权限提拔一个警员?谁有权限读取某个警员的投诉?谁又有权限更改某位警员的地址?有很多方法来管理这个问题,大多数组织使用某种权限等级制度来保持简单。我将把回答这些问题作为读者的练习,但如果你有技术上的倾向,我会推荐你阅读 Google如何用他们的桑给巴尔数据库解决这个问题
  • 系统中另一个需要仔细管理的部分,是如何将信息邮寄给不同部门。邮寄东西需要时间,这在一个复杂的组织中会引起各种问题。例如,也许一个官员在12月12日获得晋升,而当财务部门在12月13日发送工资支票时,他们仍然没有收到邮件中的晋升信息,因此他们根据他们之前的级别发送了一份工资支票。如果涉及到协调问题,事情就更有难度了。例如,也许人力资源部门认定某位官员有太多的投诉,必须降级。人力资源部门将降级的邮件发给行政部门以及财务部门。同时,主管认为该官员应该得到晋升,于是将晋升邮件寄给行政部门和财务部门。

现在会发生什么呢?不管结果如何,最重要的是行政部门、人力资源部门和财务部门最终的结果是一致的(也就是所谓的最终一致性)。例如,如果财务部门决定处理晋升,而行政部门决定处理降级,那么他们的记录就会有所不同,该官员将获得比行政部门预期更大的薪水。

这些都是很难解决的问题,我会让读者去思考如何管理这类协调问题。但如果你有技术上的能力,我建议你阅读一下【 Automerge如何处理点对点软件的这些问题

认识数据库:简明数据库史

在工业时代,煤炭和钢铁的使用量是一个国家发达程度的指标。而到了信息时代,数据量将是新的发达程度指标,几乎所有行业竞争本质上都是数据的竞争。支撑数据增长的背后,是一代又一代不断演化的数据库引擎。

整个数据库大致经历了四个发展阶段。

第一阶段:非关系型数据库

在现代意义的数据库出来之前(20 世纪 60 年代),文件系统(File system)可以说是最早的数据库,程序员们读取文本文件,并通过代码提取文件中的关键数据,在脑海中尝试构造数据与数据之间的关系。当年能流行起来的编程语言,往往都有很强的文件和数据处理能力(比如 Perl 语言)。随着数据量的增长,数据维度的多元化,以及对于数据可信和数据安全的要求不断提升,简单的将数据存储在 txt 文本中,成为极其具有挑战的事情。

随后,人们开始提出数据库管理系统(Database Management System, DBMS)的概念。数据库的演进抽象来看是人们对 数据结构 和 数据关系 这两个维度展开的思考和优化。

层次模型和网络模型(1960)

第一阶段的数据库模型(Database model) 是层次模型(Hierarchical Databases)。

层次模型是最早的数据库模型。随着早期 IBM 大型机逐渐推广开来。这个模型相对于文本文件管理数据,是个巨大的提升,但也有很多问题。

层次模型的问题:

  • 尽管能比较好的表达 一对一 ( one to one) 结构,但在 多对多(many to many) 结构上难以表达。如:图中能较好的表达一个系有多个老师,但很难表达一个老师可能属于多个系。
  • 层次结构不够灵活。如:添加一个新的数据库关系有可能对整个数据库结构带来巨大变化,以至于在真正的开发中带来巨大的工作量
  • 查询数据需要脑海中随时有最新的结构图,且需要遍历树状结构做推导

而后在层次模型的基础之上,人们提出了优化方案,即:网络模型(Network Model)。

网络模型是关系型数据库出来之前最为流行的数据库模型。很好的解决了数据的多对多的问题。但依然存在以下问题:

  • 难以从代码层面实现和维护
  • 查询数据需要脑海中随时有最新的结构图

第二阶段:关系型数据库

模型初期(1970)

关系模型( Relational Model) 是相对网络模型的巨大飞跃。在网络模型中,不同类型的数据总是会依赖另一类数据,如图 1 中,Teachers 从属于 Departments,这是层次模型和网络模型在真实设计和开发中痛苦的根源(因为你总是要在脑海中记录当前的网络结构,想象一下一个拥有几千张表的复杂系统)

关系模型一大创新就是拆掉了表和表之间的链接,将关系只存储在当前表中的某一个字段中(fields),从而实现不同的表之间的相对独立。如下表:当你只看 Table2 的时候,你就知道 Product_code 会指向一个 产品的具体细节,Table2 和 Table1 在保持相对独立的同时,又自然而然的连接了起来。

Table2 中的 Product_code 列指向了 Table1 中对应的数据,从而建立 Table2 和 Table1 的关系

1970年,当 E.F.Codd 开发出这个模型时,人们认为是难以实现的,正如上面的例子一般,当你检索 Table2 时,遇到 Product_code 列,就需要再去 Table1 遍历一遍。受限于当时的硬件条件,这种检索方法总是会让机器难以负载。但很快,大家质疑的问题,在摩尔定律加持下,已经不再是问题。大家如今所听说的 IBM DB2, Ingres, Sybase, Oracle, Informix, MySQL 就是诞生在这个时代。

至此数据库领域诞生了一个大的分类:联机事务处理 OLTP(on-line transaction processing),代指一类专门用于日常事务的数据库,如银行交易用的增删改查数据库。后面还会提到另一类数据库,专门用于从大量数据中发现决策的辅助数据库 On-Line Analytical Processing – OLAP(联机分析处理)数据库。

数据仓库(1980s)

随着关系型数据库的发展,不同业务场景数据化,人们开始有了汇集不同业务场景数据,并尝试进行数据分析并辅助业务决策的想法(Decision Support System)。在此需求之上,诞生了数据仓库( Data warehouse)的概念。

如下图:一个企业往往把不同的业务场景数据存在不同的数据库中,在没有成熟的数据仓库产品之前,数据分析师往往需要自己做大量的前期准备工作来汇集自己所需的数据。而数据仓库本质上就是解决数据分析和挖掘的业务场景。

解释:ETL 是 Extract(提取),Transform(转换),Load(加载)的缩写。因为数据在不同的数据库或者系统中,可能存在格式不统一,单位不统一等等情况。需要做一次数据的预处理。

数据仓库是一个面向主题的、集成的、非易失的、随时间变化的用来支持管理人员决策数据集合。

OLAP(联机分析处理)

1980 年代有了数据仓库的概念和实现后,人们尝试在此基础上做数据分析。但分析的过程出现一些新的问题。最明显的是效率问题。因为之前的关系型数据库并不是为数据分析而打造。数据分析师想要的是一个支持多维的数据视图和多维数据操作的引擎。

如下面的数据魔方一般,相比于上面提到的关系型数据库中的二维数据展示和二维数据操作而言。OLAP 数据库对多个维度的数据可以快速的组建和操作。

数据魔方:将多个维度的数据组织和展示

1993 年,关系型数据库创始人Edgar F. Codd提出联机分析处理(OLAP)的概念。本质上是多维数据库和多维分析能力的概念。目标是满足决策支持或多维环境特定的查询和报表需求。

第三阶段:NoSQL

时间继续推进,互联网时代到来以后,数据量的暴增给关系型数据库也带来的新的挑战。最为明显的挑战有以下两点:

挑战一:数据列的扩展成本巨高

关系型数据库因为提前定义了 Table 的字段(Fields),当数据库已经拥有数以亿计条的数据之后,业务场景需要一列新的数据,你惊讶的发现,在关系型数据库的规则限制下,你必须要同时操作这数以亿计的数据爱完成新的一列的添加(不然数据库会有报错出现),对生产环境的服务器性能挑战极大。

可以想象一下 Facebook,Twitter, Weibo 这样的社交网站,每天字段都在不断的变化,来添加各种新的功能。

比如需要添加 status 列,你必须要在某一时刻同时为数以亿计的行,添加 Active 或者 In-Active 内容,否则数据库会无法满足合规约束

挑战二:数据库性能的挑战

业务规模不断上升之后,关系型数据库的性能问题开始浮出水面,虽然数据库供应商都提出了各种解决方案,但底层关系绑定式的设计依然是性能天花板的根本原因。开发人员开始尝试分库、分表、加缓存等极限操作来挤出性能。

在此挑战之上,人们提出了新的数据库模型 – NoSQL。

针对扩展数据列的问题,NoSQL 提出了新的数据存储格式,去掉了关系模型的关系性。数据之间无关联,这样就换回了架构上的扩展性。

新的数据结构,将相关性数据都放在一起

NoSQL 更底层的创新源自于天生为集群可扩展场景所打造。

而在 NoSQL 理论基础之上,根据企业应用场景又拓展出了四大类型的数据库:

  • 文档型数据库(Document-Oriented):如大名鼎鼎的 MongoDB、CouchDB。文档泛指一种数据的存储结构,如 XML、JSON、JSONB 等。
  • 键值数据库(Key-Value Database) :大家所听说的 Redis、Memcached、Riak 都是键值对数据库
  • 列式存储数据库(Column-Family):如 Cassandra、HBase
  • 图数据库(Graph-oriented):如 Neo4j、OrientDB 等。聚焦在数据间关系链的数据组织方式。

随着企业数据的不断变大,对数据处理能力也提出了新的要求。日常所听到的大数据(Big Data)一词,代表一个庞大的技术体系结构。包括了数据的采集,整理,计算,存储,分析等环节。数据库只是其中一环。如下图,饿了么2017 年大数据架构,文中所提到的数据库,基本上只代表了图中存储环节。大家日常所听到的 Hadoop、Kafka、Hive、Spark、Materialize等都是大数据引擎,千万不要搞混了。

数据库只是大数据概念中的一部分。

第四阶段:云原生数据库

随着云时代的到来,基于云环境所打造的云原生数据库不断地开始占了数据库市场份额。

云原生数据库和托管/自建数据库最大的区别就是:云原生数据库是面向独立资源的云化,其CPU、内存、存储等均可实现独立的弹性,利用大型云厂商的海量资源池,最大化其资源利用率,降低成本,同时支持独立扩展特定资源,满足多种用户不断变化的业务需求,实现完全的Serverless; 而托管数据库还是局限于传统的服务器架构,各项资源等比率的限制在一个范围内,其弹性范围,资源利用率都受到较大的限制,无法充分利用云的红利。

基于云原生数据库技术,未来创业团队无需花费巨大精力来应对海量数据来袭,只需聚焦在业务即可。

云原生数据库的代表如:阿里云的 PolarDB、腾讯云的 CynosDB、华为云的 TaurusDB、亚马逊云的 Aurora。

最后,以阿里 CIO 学院的一个数据库分布图结束这篇文章,图示中的数据库产品和分布图很好的代表了当前数据库产业的格局。

小结

数据库看起来相当复杂,但每个数据库的基本工作原理就像一个文件柜和存储程序的系统。当我们需要在许多地方保持信息的更新,并且我们希望对数据库的所有变化进行审计跟踪时,事情就开始变得更加复杂。但这种复杂性对于解决我们所解决的问题是必要的。

当谈到未来的计算机知识时,我知道并不是每个人都会成为程序员,但了解如何构建文件柜系统的细节,将使普通人能够从计算机中获得最大的好处。

参考链接:


使用Excel搭建推荐系统

$
0
0

在上一篇 重新认识Excel的文章中,提到了Excel无所不能,然后就想到了曾经看到的这篇关于如何使用Excel搭建推荐引擎的文章。于是找了出来做了下简单的翻译(只翻译了重点部分)。

在互联网上有无限的货架空间,找到你想看的东西可能会让人筋疲力尽。幸运的是,与决策疲劳作斗争是 Netflix 的工作……而且他们很擅长。太擅长了。他们神奇地向您推荐完美的电影,这样您的眼睛就会一直盯着管子,他们会把您的拖延变成周末沙发上的狂欢。该死的,Netflix。你的秘诀是什么?你怎么这么了解我们?“魔法”非常简单,本教程使用分步电子表格揭示了其中的秘密。

尽管自Netflix Prize 竞赛以来有大量关于推荐系统的论文或视频,但大多数要么 (A) 技术太高,初学者无法使用,要么 (B) 水平太高,不实用。

在这篇文章中,我们将从头开始构建一个电影推荐系统,其中包含简单的英语解释和可以在 Excel 中遵循的分步公式。所有梯度下降推导都是手工计算的,您可以使用 Excel 下拉过滤器来微调模型的超参数并增强您的理解。

学习内容

  • 构建帮助赢得 100 万美元 Netflix 奖金的算法版本 SVD++ 背后的确切步骤。
  • 机器如何实际学习(梯度下降)。即使您从未告诉过 Netflix,也可以观看 Netflix 了解您的电影品味。
  • 超参数调优。了解如何调整模型输入 (学习率、L2 正则化、# of epochs、权重初始化)以获得更好的预测。
  • 模型评估和可视化。了解训练数据和测试数据之间的区别,如何防止过度拟合,并了解如何可视化模型的特征。

在简要介绍了推荐系统之后,我将通过以下 4 个部分来构建一个模型来预测少数好莱坞明星的电影评分。

  • 第一部分:模型概览
  • 第二部分:观看魔法秀(权重初始化和训练)
  • 第三部分:魔法揭秘(梯度下降、导数)。我将逐步讲解机器学习魔法背后的数学,我将使用实数作为例子代入批量梯度下降的公式(不会使用“宏”或者Excel求解器之类的东西隐藏细节)。
  • 第四部分:模型评估和可视化

推荐系统简介

电影推荐系统可以简化为两大类:协同过滤(询问密友)和基于内容的过滤(标签匹配)

协同过滤

协同过滤基于类似行为进行推荐。

如果Ross和Rachel过去喜欢类似的东西,那么我们将Rachel喜欢而Ross没看过的电影向Ross推荐。你可以将他们看成是“协同”过滤网络货架上的噪音的“品味分身”。如果两个用户的评分有强相关性,那么我们就认定这两个用户“相似”。评分可以是隐式的,也可以是显式的:

  • 隐式(Binging)—— 整个周末,Ross和Rachel都沉溺于老剧《老友记》。尽管他们没人点赞,但我们相当确定他们喜欢《老友记》(以及他们可能有点自恋)。
  • 显式(喜欢)—— Ross和Rachel都点了赞。

协同过滤有两种:近邻方法和潜因子模型(矩阵分解的一种形式)。本文将聚焦一种称为 SVD++的潜因子模型。

基于内容的过滤

基于你过去喜欢的内容的明确标签(类型、演员,等等),Netflix向你推荐具有类似标签的新内容。

100 万美元的赢家

当数据集足够大时,协同过滤(CF)是电影推荐器中的不二之选。

虽然这两个大类之间有无数的混合和变化,但出人意料的是,当CF模型足够好时,加上元数据并没有帮助。这是为什么呢?人会说谎,行动不会。让数据自己说话。人们所说的他们喜欢的东西(用户偏好、调查等)与他们的行为之间存在很大差距。最好让人们的观看行为自己说话。窍门:想要改善Netflix推荐?访问 /WiViewingActivity清理你的观看记录,移除你不喜欢的项。)

2009年,Netflix奖励了一队研究人员一百万美元,这个团队开发了一个算法,将Netflix的预测精确度提升了10%. 尽管获胜算法实际上是超过100种算法的集成,SVD++(一种协同过滤算法)是其中最关键的算法之一,贡献了大多数收益,目前仍在生产环境中使用。

我们将创建的SVD++模型(奇异值分解逼近)和Simon Funk的博客文章中提到差不多。这篇不出名的文章是2006年Simon在Netflix竞赛开始时写的,首次提出了SVD++模型。在SVD++模型成功之后,几乎所有的Netflix竞赛参加者都用它。

SVD++ 关键思想

  • “奇异值”(电影评分)可以“分解”或由一组隐藏的潜在因素(用户偏好和电影特征)决定,这些潜在因素直观地代表了类型、演员等。
  • 潜在因素可以使用梯度下降和已知的电影评分迭代学习。
  • 用户/电影偏见会影响某人的评分,并且也会被学习。

简单,但功能强大。让我们深入挖掘。

第一部分:模型概览

数据

博客文章模型使用 30 个虚构评分(5 个用户 x 6 部电影)来简化教程。要在我们进行过程中跟随并试验模型,您可以在 此处下载电子表格(Excel 或 Google 表格) 。

拆分数据——训练集和测试集

我们将使用25项评价来训练模型,剩下5项评价测试模型的精确度。

我们的目标是创建一个在25项已知评价(训练数据)上表现良好的系统,并希望它在5项隐藏(但已知)评价(测试数据)上做出良好的预测。

如果我们有更多数据,我们本可以将数据分为3组——训练集(约70%)、验证集(约20%)、测试集(约10%)。

评价预测公式

评价预测是用户/电影特征的矩阵乘法(“点积”)加上用户偏置,再加上电影偏置。

公式为:

$$\hat{r}_{i,j}=((u_1 m_1)+(u_2 m_2)+(u_3 m_3)+u_{bias}+m_{bias})$$

其中:

  • $\hat{r}_{i,j}$表示用户i对电影j的预测评价
  • $u_1$、$u_2$、$u_3$为用户潜因子
  • $m_1$、$m_2$、$m_3$为电影潜因子
  • $u_{bias}$为用户偏置
  • $m_{bias}$为电影偏置

用户/电影特征

  • 直觉上说,这些特征表示类型、演员、片长、导演、年代等因素。尽管我们并不清楚每项特征代表什么,但是当我们将其可视化后(见第四部分)我们可以凭直觉猜测它们可能代表什么。
  • 出于简单性,我使用了3项特征,但实际的模型可能有50、100乃至更多特征。特征过多时,模型将“过拟合/记忆”你的训练数据,难以很好地推广到测试数据的预测上。
  • 如果用户的第1项特征(让我们假定它表示“喜剧”)值较高,同时电影的“喜剧”特征的值也很高,那么电影的评价会比较高。

用户/电影偏置

用户偏置取决于评价标准的宽严程度。如果Netflix上所有的平均评分是3.5,而你的所有评分的均值是4.0,那么你的偏置是0.5. 电影偏置同理。如果《泰坦尼克号》的所有用户的评分均值为4.25,那么它的偏置是0.75(= 4.25 – 3.50)。

RMSE —— 评估预测精确度

RMSE = Root Mean Squared Error (均方根误差)

RMSE是一个数字,尝试回答以下问题“平均而言,预测评价和实际平均差了几颗星(1-5)?”

RMSE越低,意味着预测越准……

观察:

  • 我们只在意绝对值差异。相比实际评分高估了1分的预测,和相比实际评分低估了1分的预测,误差相等,均为1。
  • RMSE是误差同数量级的平均,而不是误差绝对值的平均。在我们上面的例子中,误差绝对值的平均是75(1 + 1 + 0.25 = 2.25,2.25 / 3 = 0.75),但RMSE是0.8292. RMSE给较大的误差更高的权重,这很有用,因为我们更不希望有较大的误差。

超参数调整

通过电子表格的下拉过滤器,可以调整模型的3个超参数。你应该测试下每种超参数,看看它们对误差的影响。

  • 训练epoch数—— 1个epoch意味着整个训练集都过了一遍
  • 学习率—— 控制调整权重/偏置的速度
  • L2(lambda)惩罚因子—— 帮助模型预防过拟合训练数据,以更好地概括未见测试数据。

现在,让我们看一场魔法秀,看看模型是如何从随机权重开始,学习最优权重的。

第二部分:观看魔法秀(权重初始化和训练)

观看梯度下降的实际操作感觉就像您在观看 David Blaine 的魔术。

  • 他到底是怎么知道我会在52张牌中选这张的呢?
  • 等等,他刚刚是不是浮空了?

最后你深感敬畏,想要知道魔术是如何变的。我会分两步演示,接着揭露魔法背后的数学。

抽一张卡,随便抽一张(权重初始化)

在训练开始,用户/电影特征的权重是随机分配的,接着算法在训练中学习最佳的权重。

为了揭示这看起来有多么“疯狂”,我们可以随机猜测数字,然后让计算机学习最佳数字。下面是两种权重初始化方案的比较:

  • 简单—— 用户特征我随机选择了1、0.2、0.3,剩下的特征都分配0.1.
  • Kaiming He—— 更正式、更好的初始化方法,从高斯分布(“钟形曲线”)中随机抽样作为权重,高斯分布的均值为零,标准差由特征个数决定(细节见后)。

观赏魔术(查看训练误差)

看看使用以上两种方案学习权重最佳值的效果,从开始(epoch 0)到结束(epoch 50),RMSE训练误差是如何变化的:

如你所见,两种权重初始化方法在训练结束时都会收敛到类似的“误差”(0.12 与 0.17),但“  Kaiming He”方法更快地收敛到较低的误差。

关键要点:无论我们从哪个权重开始,机器都会随着时间的推移学习到好的值!

注意:如果你想要试验其他初始化权重,可以在电子表格的“hyperparameters_and_initial_wts”表的G3-J7、N3-Q8单元格中输入你自己的值。权重取值范围为-1到1.

想要了解更多关于Kaiming He初始化的内容,请接着读下去;否则,可以直接跳到第3部分学习算法的数学。

Kaiming He权重初始化

权重 = 正态分布随机抽样,分布均值为0,标准差为 (=SquareRoot(2/# of features))

电子表格中的值由以下公式得到:=NORMINV(RAND(),0,SQRT(2/3))

$$W_l \sim \mathcal{N}(0, \sqrt{\frac{2}{n_l}}) \text{and} \mathbf{b}=0$$

第三部分:魔法揭秘

现在,是时候书呆一点,一步一步地了解梯度下降的数学了。如果你不是真想知道魔法是如何起效的,那么可以跳过这一部分,直接看第4部分。

梯度下降是在训练时使用的迭代算法,通过梯度下降更新电影特征、用户偏好的权重和偏置,以做出更好的预测。

梯度下降的一般周期为:

  • 第 1 步 – 定义成本/损失函数以最小化和初始化权重
  • 第 2 步 — 计算预测
  • 第 3 步 – 计算梯度(相对于每个权重的成本变化)
  • 第 4 步——在最小化成本的方向上更新每个权重“一点点地”(学习率)
  • 第 5 步 — 重复第 2-4 步

你可以访问电子表格的“training”(训练)表,其中第11-16行是更新Tina Fey的第一项用户特征的过程。

由于数据集很小,我们将使用批量梯度下降。这意味着我们在训练时将使用整个数据集(在我们的例子中,一个用户的所有电影),而不是像随机梯度下降之类的算法一样每次迭代一个样本(在我们的例子中,一个用户的一部电影),当数据集较大时,随机梯度下降更快。

定义最小化的代价函数

我们将使用下面的公式,我们的目标是找到合适的潜因子(矩阵U、M)的值,以最小化SSE(平方误差之和)加上一个帮助模型提升概括性的L2权重惩罚项。

下面是Excel中的代价函数计算。计算过程忽略了1/2系数,因为它们仅用于梯度下降以简化计算。

L2正则化和过拟合

我们加入了权重惩罚(L2正则化或“岭回归”)以防止潜因子值过高。这确保模型没有“过拟合”(也就是记忆)训练数据,否则模型在未见的测试电影上表现不会好。

之前,我们没有使用L2正则化惩罚(系数为0)的情况下训练模型,50个epoch后,RMSE训练误差为0.12.

但是模型在测试数据上的表现如何呢?

如果我们将 L2 惩罚因子从 0.000 更改为 0.300,我们的模型应该可以更好地概括未见过的测试数据:

计算预测

我们将计算Tina的电影预测。我们将忽略《泰坦尼克号》,因为它在测试数据集中,不在训练数据集中。

计算梯度

目标是找到误差对应于将更新的权重的梯度(“坡度”)。

得出梯度之后,稍微将权重“移动一点点”,沿着梯度的反方向“下降”,在对每个权重进行这一操作后,下一epoch的代价应该会低一些。

“移动一点点”具体移动多少,取决于学习率。在得到梯度(3.3)之后,会用到学习率。

梯度下降法则:将权重往梯度的反方向移动,以减少误差

第1步:计算Tina Fey的第一个潜因子的代价梯度($u_1$)

1.1整理代价目标函数,取代价在Tina Fey的第一个潜因子($u_1$)上的偏导数。

$$\frac{\partial J(cost)}{\partial u_1}=\frac{1}{2} \sum_{(i, j): r(i,)=1}(\hat{r}_{i, j}-r_{i, j})^2+\frac{1}{2} \lambda(\sum_i\left\|u_i\right\|^2+\sum_j\left\|m_j\right\|^2)$$

1.2整理预测评价函数,改写为用户潜因子的平方和加上电影潜因子的平方和

$$\frac{\partial J}{\partial u_1}=\frac{1}{2} \sum(((u_1 m_1)+(u_2 m_2)+(u_3 m_3)+u_{bias }+m_{bias})-r_{i, j})^2+ \frac{1}{2} \lambda \sum(u_1^2+u_2^2+u_3^2)+\frac{1}{2} \lambda \sum (m_1^2+m_2^2+m_3^2)$$

1.3将公式每部分中的$u_1$视为常数,取$u_1$在公式每部分的代价上的偏导数。

1.3.1(Part 1 of 3)应用“链式法则”以得到偏导数。链式法则意味着我们将((外层函数的导数)*内层函数)* (内部函数的导数)

外层函数的导数:

$$\begin{gathered}=\frac{1}{2} \sum(((u_1 m_1)+(u_2 m_2)+(u_3 m_3)+u_{bias}+m_{bias})-r_{i, j})^2 \leftarrow \text { power rule} \\=2 \times \frac{1}{2}(((u_1 m_1)+(u_2 m_2)+(u_3 m_3)+u_{bias}+m_{bias})-r_{i, j})^1 \\=\text { (predicted rating }-\text { actual rating }) \\=(\text { error })\end{gathered}$$

内层函数的导数:

$$\begin{gathered}=\frac{1}{2} \sum((u_1 m_1)+(u_2 m_2)+(u_3 m_3)+u_{bias}+m_{bias})-r_{i, j} \leftarrow \text {constant rule} \\ ((u_1 m_1)+(0)+(0)+0+0)-0 \\ =m_1 \\ 1.3.1=(\text{error} x m_1)\end{gathered}$$

1.3.1(Part 2 of 3)应用“幂法则”以得到偏导数。根据幂法则,指数为2,所以将指数降1,并乘上系数1/2. $u_2$和$u_3$视作常数,变为0.

$$\begin{gathered}=\frac{1}{2} \lambda \sum(u_1^2+u_2^2+u_3^2) \\=2 \times \frac{1}{2} \times \lambda(u_1^1+0+0) \\=\lambda \times u_1 \\1.3 .2= (\lambda \times u_1)\end{gathered}$$

1.3.3 (Part 3 of 3 ) 应用“常数法则”以得到偏导数。

$$\begin{gathered}=\frac{1}{2} \lambda \sum(m_1^2+m_2^2+m_3^2) \\=0\end{gathered}$$

由于$u_1$对这些项毫无影响,结果是0。

$$1.3.3 =0$$

1.4 结合1.3.1、1.3.2、1.3.3得到代价在$u_1$上的偏导数。

$$\begin{aligned} \quad \text { Part } 1+\text { Part } 2+\text { Part } 3 \\ \frac{\partial J}{\partial u_1} &=(\text { error } x m_1)+(\lambda \times u_1)+0 \\ &=(\text { error } x m_1)+(\lambda \times u_1)\end{aligned}$$

第2步:对训练集中Tina看过的每部电影,利用前面的公式计算梯度,接着计算Tina看过的所有电影的平均梯度。

更新权重

学习Tina的旧$u_1$,学习率($\alpha$),以及上面计算的平均梯度,更新$u_1$。我们将使用的学习率为0.3。

Gradient descent formula:

$$New \ u_1= old \ u_1-\alpha (average \ gradient)$$

$$New \ u_1=(0.66)-0.3(1.92)$$

$$New \ u_1=(0.66)+0.58$$

$$New \ u_1=(0.08)$$

“training”(训练)表的X11-X16单元格对应上面的计算过程。

你可以看到,电影特征和用户/电影偏置以类似的方式更新。

每一个训练epoch更新所有的电影/用户特征及偏置。

第四部分:模型评估和可视化

现在我们已经训练好了模型,让我们可视化电影的2个潜因子。

如果我们的模型更复杂,包括10、20、50+潜因子,我们可以使用一种称为“ 主成分分析(PCA)”的技术提取出最重要的特征,接着将其可视化。

相反,我们的模型仅仅包括3项特征,所以我们将可视化其中的2项特征,基于学习到的特征将每部电影绘制在图像上。绘制图像之后,我们可以解释每项特征“可能代表什么”。

从直觉出发,电影特征1可能解释为悲剧与喜剧,而电影特征3可能解释为男性向与女性向。

这不是完美的解释,但还算一种合理的解释。《勇士》(warrior)一般归为剧情片,而不是喜剧片。不过其他电影基本符合以上解释。

总结

电影评价由一个电影向量和一个用户向量组成。在你评价了一些电影之后(显式或隐式),推荐系统将利用群体的智慧和你的评价预测你可能喜欢的其他电影。向量(或“潜因子”)的维度取决于数据集的大小,可以通过试错法确定。

我鼓励你实际操作下电子表格,看看改变模型的超参数会带来什么改变。

参考链接:

网站速度终极优化

$
0
0

好久没有折腾网站速度了。最近尝试再次优化提升网站的访问速度。

利用了CDN厂商Cloudflare的Page rules,创建了三条页面规则,将后台登录页面免除缓存外,其它页面全部都缓存在Cloudflare的全球CDN数据中心。

这样全球访客访问本网站的时候,除了第一次要从源主机上索取生成页面,其它都不再需要,直接从最近的CDN数据中心获取,这样既减轻了网站主机的资源消耗压力,又直接提升了访客的访问速度。

鉴于Google Anlytics统计数据越来越不好用(Cloudflare本身自带Web Analytics),再加上我也没有动机去使用它,索性把统计代码取消了,这样一来既减少了网页页面请求数量,也没有了cookies记录,因此访问过的网页会直接缓存在访客的浏览器中一段时间(设置为1天)。

去除了Google Anlytics统计功能后,访客在本网站上来回浏览的时候,短时间内去CDN上下载网页都不必了——CDN那边会返回304告诉浏览器页面没有改动,所以直接调用原缓存页面,进一步提升网站访问速度。

这样的网站速度终极优化方案,把本网站的加载速度基本控制在500毫秒以内,比秒开还要快。

这么多年后,再一次化繁为简、返朴归真,回归到博客网站写作的初心,其它的东西都不必要。

别焦虑了,这才是中国各行业平均工资的真相

$
0
0

年底将至,你的钱包还好吗?

不久前,国家统计局发布了《中国统计年鉴 2022》,年鉴中记录了 2021 年我国各地区和各行业的年平均工资。

你的工资超过了平均水平吗?近 10 年来各行业年平均工资有什么变化?哪些行业、哪些地区高薪且工资增速快?以下数据或许可以给你一些参考。

2012-2021 年整体年平均工资变化

先看整体状况。总的来看,非私营单位的年平均工资大幅高于私营单位的年平均工资。

2021 年,全国城镇非私营单位年平均工资突破了 10 万元大关,达到了 106837 元。相较于 2020 年,增长了约 9.71%。另一边,全国城镇私营单位年平均工资达到了 62884 元,第一次超过 6 万元,比 2020 年增加了约 8.93%。

(非私营单位指国有单位、城镇集体单位、联营、股份制、外商投资、港澳台商投资等单位,而私营单位指私营有限责任公司、私营股份有限公司、私营合伙企业和私营独资企业。)

再看增速。从年平均工资的增长水平来看,2021 年的平均工资增幅,在过去十年间处于比上不足比下有余的中间位置。

2021 年,城镇非私营单位和城镇私营单位的年平均工资同比增长分别是 9.7% 和 8.9%,都超过了 2020 年,可见不少公司都在努力从疫情的影响中恢复。

但对比早些年,年平均工资的增速并不算快:城镇非私营单位 2021 年年平均工资的增长水平,在过去十年间排名第七位。而城镇私营单位 2021 年年平均工资的增长水平,位于第四位。

不过,相较于城镇非私营和私营单位的年平均工资,你所处行业的平均水平可能更具有参考性。

2021 年,什么行业年平均工资最高?

分行业来看,不管是在非私营还是私营单位," 信息传输、软件和信息技术服务业 "、" 金融业 " 和 " 科学研究和技术服务业 " 的年平均工资都是最高的,这也符合大家近年来的普遍认知。

在各类单位中,非私营单位的 " 信息传输、软件和信息技术服务业 " 年平均工资最高,达到 20.15 万元,相当于每月税前平均工资 1.68 万。私营单位中的 " 信息传输、软件和信息技术服务业 " 年平均工资也达到了 11.46 万元,是私营单位中唯一一个年平均工资过 10 万的行业。

除了大家熟知的高薪行业:计算机、金融,还有两个行业的工资也不算低。一是 " 科学研究与技术服务业 ",在非私营单位和私营单位的平均年薪分别排名第二、第三。这个行业主要包括三大类,除了 " 研究和试验发展 ",还有 " 专业技术服务业 " 和 " 科技推广和应用服务业 "。

另一个 " 卫生和社会工作 " 的工资也不低,在非私营和私营单位的各行业中均排名第四,年平均工资分别达到了 12.68 万元和 6.78 万元。。

而 " 租赁和商务服务业 "、" 制造业 "、" 采矿业 "、" 文化体育和文化业 " 和 " 教育业 " 在不同类型单位工资排名能相差很大。

一个行业的好坏,工资的增长情况也很重要。

DT 君计算了各行业 2017-2021 年年均工资的年均复合增长率,并把各行业的年平均工资和复合增长率放置在散点图上,并以中位数作为分割线,切出四个象限。

整体来看,无论是在非私营单位还是私营单位," 信息传输、软件和信息技术服务业 " 和 " 卫生和社会工作 " 位于近年来最好的行业队列中,不仅工资高,成长性也强。而 " 水利、环境和公共设施管理业 "、 " 居民服务、修理和其他服务业 "、" 住宿和餐饮业 " 和 " 房地产业 " 则无论在哪个类型的单位都是 " 低收入低成长 "。

此外,在非私营单位中," 科技研究和技术服务业 " 平均工资和 5 年复合增长率都高于中位数。" 电力、热力、燃气及水生产和供应业 "、 " 公共管理、社会保障和社会组织 " 同样也属于高工资高成长的行业。" 金融业 "、" 教育业 " 和 " 文化、体育和娱乐业 " 行业薪资较高,但近 5 年来工资成长性比较一般。

在私营单位中," 金融业 "、" 制造业 "、" 采矿业 " 和 " 交通运输、仓储和邮政业 " 也进入高工资高成长行列。

" 科技研究和技术服务业 " 虽然是非私营单位中的高工资高成长行业,但私营单位中," 科技研究和技术服务业 " 成长性并不高,类似的还有 " 租赁和商务服务业 " 和 " 建筑业 "。

私营单位中," 水利、环境和公共设施管理业 "、" 农林牧渔业 "、" 居民服务、修理和其他服务业 "、" 教育业 " 等属于低工资低成长的行业。

除了北京上海,什么地方的工资又高增长又快
从各省市的年平均工资来看,北京和上海遥遥领先。2021 年北京非私营单位的年平均工资最高,达到了 19.47 万元,上海排名第二,年平均工资达到了 19.18 万元。西藏、天津、浙江、广东、江苏、青海和重庆这 7 个省市,年平均工资也超过了 10 万元的大关。

2021 年河南省非私营单位年平均工资全国最低,仅为 7.49 万元,也是全国唯一一个没过 8 万元的省。

在私营单位中,2021 年北京是全国唯一一个私营单位年平均工资破 10 万元的省市,排名 2、3 的分别是上海和广东。

与 2020 年相比,2021 年海南的非私营单位年同比增长率达到了惊人的 21.2%。排名第 2、3 的上海和湖北分别达到了 19.8% 和 16.8%。

但某一年的数据并不能代表一个地区的工资增长趋势,DT 君比较了各省市 2017-2021 年 5 年间年平均工资的复合增长率,同样把他们做成散点图,并以中位数为界分出四个象限。

非私营单位中,北京和上海是高工资高成长的代表。浙江、广东、江苏和宁夏等省份,在工资绝对值上稍弱于北京和上海,但在工资的增速上很快。

西藏和天津则是高工资低成长的代表,虽然工资处于中位数以上,但工资的增速远低于大部分省市。也有一些省份工资不高增长也慢,比如河北、甘肃和河南等。

在私营单位中,各省市的工资绝对值和增速有所不同。在高工资高成长的象限中,上海一枝独秀,工资上略次于北京,在成长性上远超其他省市。

北京、广东、江苏、浙江等省市,私营单位的年平均工资高于中位数,但增速并不快。而吉林、山西等省份,虽然工资不高但增速很快,高于中位数。

甘肃和云南等省私营单位的年平均工资和成长性都低于全国大部分省市。

写在最后
总的来说,本次统计数据可以简单总结为 4 点,或许对你有参考价值:

1、 非私营单位的年平均工资大幅高于私营单位的年平均工资。

2、在私营 + 非私营总共 37 个行业里,年平均工资超过 10 万的只有 13 个,并且有 4 个只是刚刚超过 10 万。

3、 除了 " 信息传输、软件和信息技术服务业 "," 卫生和社会工作 " 也是一个高薪、高成长的行业。

4、 虽然总体上,人们的收入遵循所在地区经济越发达、年平均工资越高的规律,但也有例外,西藏、青海、云南的年平均工资都高于中位数。如果考虑成长性,湖北、浙江、宁夏也是不错的选择。

最后,年平均工资纸面数据高并不是单单为了看着好看,起码员工的社保基数、奖金补偿金、退休金、最低工资等都和年平均工资成正比(如果你有的话)。

探索性数据分析详解

$
0
0

什么是探索性数据分析?

探索性数据分析(Exploratory Data Analysis,简称EDA) 是指对已有的数据(特别是调查或观察得来的原始数据)在尽量少的先验假定下进行探索,通过作图、制表、方程拟合、计算特征量等手段探索数据的结构和规律的一种数据分析方法。

探索性数据分析(EDA)与传统统计分析(Classical Analysis)的区别:

  • 传统的统计分析方法通常是先假设样本服从某种分布,然后把数据套入假设模型再做分析。但由于多数数据并不能满足假设的分布,因此,传统统计分析结果常常不能让人满意。
  • 探索性数据分析方法注重数据的真实分布,强调数据的可视化,使分析者能一目了然看出数据中隐含的规律,从而得到启发,以此帮助分析者找到适合数据的模型。“探索性”是指分析者对待解问题的理解会随着研究的深入不断变化。

探索性数据分析除了日常的数据分析外,也是算法模型搭建过程中的必要环节。特别适合数据比较杂乱,不知所措的场景。

探索性分析用一句话概况就是:折磨数据,它会坦白任何事情。

探索性数据分析的一般流程

探索性分析的一般流程:

  • 数据总览
  • 探索性分析每个变量
  • 探索性分析变量与target标签的关系
  • 探索性分析变量之间的关系

数据总览

在数据处理前首先要充分了解数据,了解数据包含以下两部分:

  • 了解数据的外部信息。即数据的现实意义。可通过业务知识与流量计获取采集方式进行了解。
  • 了解数据的内部信息。即数据的自身情况。可通过统计学的相关知识,如计算均值,标准差,峰度,偏度等。另外,也可以通过绘图,来深入了解数据,为创建有效特征提供思路。

对于数据总览一般可借助Pandas的一些函数对数据有些大概了解:

  • describe() # 查看所有数据平均值,四分位数等信息
  • info() # 查看所有数据的数据类型和非空值个数。
  • shape # 查看数据行列数
  • isnull().sum() # 查看数据各个特征为空值的个数

探索性分析每个变量

需要了解的内容包括:

  • 变量是什么类型
  • 变量是否有缺失值
  • 变量是否有异常值
  • 变量是否有重复值
  • 变量是否均匀
  • 变量是否需要转换

在分析每个变量可通过描述统计量和图表进行描述。数据类型分为数值型,类别型,文本型,时间序列等。这里主要指的是数值型(定量数据)和类别型(定性数据),其中数值型又可以分为连续型和离散型。

1)连续数据分析

数据分析分为两个方面,一是统计汇总,二是可视化。离散也是这样。

统计计算:

  • 在统计学中,想要描述一个数据,要从三个方面进行说明:
    • 集中趋势:均值,中位数,众数。对于正太分布的数据,均值的效果比较好,而对于有偏数据,因为存在极值,所有会对均值产生影响,此时,用中位数去进行集中趋势的描述。
    • 离散程度:方差和标准差。这两个用哪个都可,不过标准差是具有实际意义的。另外,还可以用极差,平均差,四分位差,离散系数(针对多组数据离散程度的对比)。
    • 分布形状:偏度skew(),衡量数据偏斜情况。峰度kurt(),衡量数据分布的平坦度。检验数据正态性。一般可绘制P-P图,Q-Q图来进行判断。或者通过计算偏度,峰度进行判断,也有其他别的方法,但了解的较少。
  • 数据转化。这步一般在特征工程中,这里提一下,通过box-cox可以将非正态数据转为正态数据。
  • 游程检验。非参数统计的一种方法,判断数据是否是随机出现的。连续,离散都可以用。
  • 通过describe(),可观察数据的大致情况。

可视化:

  • 对连续数据可视化主要有以下几个图形:
    • 直方图。可以大致看出数据的分布情况,但会受限于bins的取值并且图形不光滑。可在直方图上再画出核密度图(KDE),进行更详细的查看。
    • 核密度估计
    • 核密度图
    • 箱线图。反映原始数据的分布特征,还能进行多组数据的比较。可看出数据的离群点。
    • 散点图。利用索引和连续数据作散点图,直观看数据是否随机。
  • 类型转换
    • 将连续型数据转为离散型数据。比如,年龄,可以将其分组为少年,青年,壮年,老年等。这种处理方式的关键是如何分组,在数据噪声处理中有过描述,介绍了人为区分,等深等宽分组,无监督算法分组,聚类等方法。
    • 关于为什么要把连续型数据转为离散型数据,大概的好处有:去噪声,易理解,算法需要。

针对连续变量常见的描述统计量:平均值,中位数,众数,最小值,最大值,四分位数,标准差等

图表:频数分布表(需进行分箱操作),直方图,箱形图(查看分布情况)

2)离散数据分析

统计计算

  • 主要查看数据的结构。用众数看哪类数据出现的最多。利用value_counts()函数,查看各个类别出现的次数。

可视化

  • 饼图。对于查看数据结构比较直观,所占百分比。
  • 柱形图。对各类别出现次数进行可视化。可排序,这样观察数据更直观。

针对 无序型离散变量常见的描述统计量:各个变量出现的频数和占比

图表:频数分布表(绝对频数,相对频数,百分数频数),柱形图,条形图,茎叶图,饼图

针对 有序型离散变量常见的描述统计量:各个变量出现的频数和占比

图表:频数分布表,堆积柱形图,堆积条形图(比较大小)

变量间关系分析

当对单个数据分析完后,还要看各个数据与目标特征的关系,和除目标特征外,其他数据间的关系。

  • 探索性分析变量与target标签的关系
  • 探索性分析变量之间的关系

1)连续型变量与连续型变量关系

统计计算:

  • 协方差,可以得到两个变量间的相关性。但协方差越大,并不表明越相关。因为协方差的定义中没有考虑属性值本身大小的影响。
  • 相关系数考虑了属性值本身大小的影响,因此是一个更合适的统计量。取值在[-1,1]上,-1表示负相关,即变换相反,1表示正相关,0则表示不相关。相关系数是序数型的,只能比较相关程度大小(绝对值比较),并不能做四则运算。而相关系数一般常用的有三种:
    • Pearson相关系数:这个比较常用,主要用于正态的连续型数据间的比较。但在使用时,限制的条件比较多,对于偏态数据,效果不是很好。
    • Spearman相关系数:相比于Pearson,这个的限制条件比较少,不受异常值影响。可以应用在多种场合。但若对正太正态数据使用,则效果一般。
    • Kendall相关系数:限制条件同Spearman。一般用在分类数据的相关性上。
    • 注:Pearson和协方差,主要看数据间的关系是不是线性的,如不是线性,但有其他联系,这两个系数是判断不出来的。比如指数函数这种。而Spearman和Kendall则可以进行一定的判断,主要是单调增函数。

可视化

  • 散点图。可看出两个特征间的关系大致是什么样的。如果要具体探究数据间的关系,需要进行一定的计算。
  • 相关性热力图。如果是一个数据与另一个时间序列进行搭配,则这个图可以很好地看出变化趋势。

对于连续变量与连续变量之间的关系,可以通过散点图进行查看。对于多个连续变量,可使用散点图矩阵,相关系数矩阵,热图。

量化指标:皮尔逊相关系数(线性关系),互信息(非线性关系)

2)离散变量和离散变量关系

对于离散变量与离散变量之间的关系,可以通过交叉分组表(crosstab),复合柱形图,堆积柱形图,饼图进行查看。对于多个离散变量,可以使用网状图,通过各个要素之间是否有线条,以及线条的粗线来显示是否有关系以及关系的强弱。

量化指标:卡方独立性检验—>Cramer’s φ (Phi) or Cramer’s V

3)离散变量和连续变量关系

对于离散变量和连续变量之间的关系,可以使用直方图,箱线图,小提琴图进行查看,将离散变量在图形中用不同的颜色显示,来直观地观察变量之间的关系。

量化指标:独立样本t检验中的t统计量和相应的p值(两个变量),单因素方差分析中的η²(三个变量及以上)

4) 其他

检查数据的正态性:直方图,箱线图,Q-Q图(Quantile-Quantile Plot )

  • 直方图,箱线图:看图形是否对称
  • Q-Q图:比较数据的分位数与某个理论分布的分位数是否匹配

探索性数据分析的的产出

根据EDA我们可以得出以下结论:

  • 变量是否需要筛选、替换和清洗
  • 变量是否需要转换
  • 变量之间是否需要交叉
  • 变量是否需要采样

探索性数据分析的辅助工具

参考链接:

其他参考:

互联网黑灰产产业链初探

$
0
0

黑灰产的英文翻译是Black Market,被定义为通过人工方式或者技术手段实施的操纵网络信息内容,获取违法利益、破坏网络生态秩序的行为。对很多人来说,黑灰产的代名词就是“薅羊毛”。实际上,除了薅羊毛,每个行业都存在一些典型的黑灰产欺诈场景。

黑灰产的最大特点但就是逐利。只要是能产生利益的地方几乎逃不开黑灰产的觊觎。即使表面看上去获利很低,但黑灰产依然会想办法通过批量操作来规模获利。

 

黑灰产的典型形态

搬运洗稿

指使用技术或手动编辑,通过对他人文章进行同义词替换、语态语序转化、段落调整、删减、拼凑等方式进行二次表达而形成新的文章内容,本质是对他人原创作品的一种非正当性使用。搬运洗稿信息广泛存在于微信公众号推文、自媒体各类账号发文、视频剪辑拼接中。

搬运洗稿的产业链上游是技术员开发售卖洗稿工具,中游是专业洗稿团队批量接单,下游是自媒体账号为引流购买洗稿服务。

搬运洗稿的盈利模式丰富,不同参与主体有不同的盈利模式:

  • 洗稿团队靠批量洗稿和售卖洗稿教程获利;
  • 自媒体通过洗稿“爆款”文章赚取平台补贴和承接广告;
  • 洗稿平台通过发 “爆款”热文引流赚取流量收益。

搬运洗稿大多是“人工+机器”的双轨洗稿模式且多为招募网络写手组建专业洗稿团队批量接单。

恶意营销

指营销公司或个人为了取得商业利益,选择在特定时机利用煽动性话语刺激公众情绪、博取舆论关注,从而获取利益。如2020年华南海鲜市场供货商忏悔书事件,以抢流量、炒作为业的团伙将2016年旧帖拼接篡改后,再换上和疫情有关的标题“蹭热点”,通过“曝光巨大内幕” 等字样博取眼球,使帖文迅速获得超10万阅读量。

恶意营销的产业链是一条诱导变现的完整黑产链,上游是营销号注册,中游是内容生产引流,下游是自媒体诱导变现。

恶意营销的盈利模式有三种:

  • 通过“水涨船高”盈利,“涨粉”到一定程度实现流量变现;
  • 通过 “移花接木”盈利,通过插入跳转链接获得相应受益,吸引粉丝点开后实现恶意变现;
  • 通过“无中生有”盈利,通过编造谣言等不实信息进行所谓“撤稿费”的敲诈。

恶意营销的运营特点包括:

  • 蹭热点,夸大扭曲事实,借社会热点事件蹭流量是营销号最常见做法;
  • 燃情绪,渲染负面情绪,如“金钱、性、暴力、歧视”等主题的信息内容;
  • 搅浑水,正反搅动舆情,同一营销公司的水军持两种对立观点,“左右手互搏”,利用公众朴素情感,搅混舆论。

撰写黑稿

指部分企业出于打压竞争对手、巩固自身利益等目的,借自媒体之手发出黑稿抹黑对手,并安排“推手”或 “水军”进行二次传播。为了最大化发挥黑稿效力,黑稿通常选择在竞争对手的重要经营节点对外发布。

撰写黑稿的产业链表现为从注册自媒体账号,到生产内容引流,再到引流变现盈利,形成完整的黑稿产业链,其中包含授意企业、黑稿写手、黑稿发布者等参与者。

撰写黑稿的盈利模式:

  • 职业收取“封口费”,诋毁企业后上门“谈合作”,索要“封口费”“保护费”;
  • 拿钱帮抹黑竞争对手,利用竞争对手的负面信息撰写文章,在平台发布传播,混淆视听,获得高额收益。

撰写黑稿的运营特点:

  • 角度选取贴近社会热点,如人身健康、财产安全、隐私泄露、房价走向等;
  • 内容设定夸张不实,多是拼凑信息或者对其中一点进行放大,再添加有倾向性观点,配上博人眼球的标题;
  • 传播灵活形成矩阵,通常会由影响力较大的公众号发布负面新闻,其余“小号”跟进和炒作,并在微信、微博、今日头条等多个平台同步发布,同时雇“水军”评论和转载,尽可能实现攻击效果最大化;
  • 人员结构复杂多元,不仅有专门的自媒体,还有记者、公关公司、第三方评价机构等群体,这些人不仅对企业消息有更深层次的了解,还更为熟悉写作、 传播等规律,进一步增加了所谓自媒体的“效力”。

黑账号

指各平台出现账号倒卖的现象,形成一条成熟的产号、养号、卖号的产业。黑账号的产业链表现为上游产号、恶意注册账号,中游养号、恶意运营管理账号,下游出号,获得违法违规利润的特点。

黑账号的盈利模式包括:

  • 实施诈骗;
  • 向平台“薅羊毛”;
  • 开展广告营销等。

黑账号的运营特点包括:

  • “手机黑卡卡商”通过特殊渠道获得手机黑卡;
  • “黑产交易平台”为“手机黑卡卡商”和“虚假账号号商”提供交易中介服务;
  • “虚假账号号商”实现虚假账号注册并提供给下游网络犯罪。

刷粉刷量

指提高粉丝数量、互动数量,依靠流量产生利润, 形成刷流量产业。刷粉刷量的产业链上游是工具开发者和账号商,主要通过技术手段在各大网络平台注册账号,生产假粉丝;中游是流量业务代理商,为广撒网寻求客户,账号商通常会招募数量众多的代理商售卖粉丝;下游是需求用户。

刷粉刷量的盈利模式:

  • 注册账号引流变现,
  • 售卖软件,
  • 面对课程教学等获利。

刷粉刷量的运营特点:

  • 利用刷粉软件进行自动刷粉操作,
  • 招募真人粉丝模拟人工自主访问,
  • 自媒体刷粉刷量赚取流量。

除以上形态外,还涉及诱导粉丝消费、音视频软色情贩卖、刷帖控评、算法滥用、集群炒作、人海投诉等,还有问题组合出现的情况。网络信息内容黑灰产治理具有复杂性、系统性特点,从其产业链可见,在网络信息内容生态体系中,内容生产端始终是问题源头和重要环节。应对网络信息内容黑灰产挑战,需明晰各方责任,斩断非法利益链,实现协同共治。

黑灰产的发展趋势

与以前相比,黑灰产的攻击方式或手段有了很大变化,主要体现在以下三方面:

  • 从早期单一的兼职刷单,到如今的多行业、多场景、多任务的广泛渗透;
  • 从早期的只在 PC 端进行单一手法的兼职,到如今以移动端为主;
  • 从早期的线上群组媒介(QQ 群、YY 语音等),到如今的平台化和裂变化。

从成本和收益来看,黑灰产的攻击成本主要来源于其发起攻击时所需要的各种资源,主要有:

  • 账号资源:在目标业务上注册的虚假账号
  • IP资源:为绕过目标的 IP 风控,购买代理或秒拨 IP
  • 设备:在设备上安装目标应用
  • 自动化工具:批量操控多台设备的群控工具,修改设备信息,从而伪造新设备的改机工具等

黑灰产的收益则是业务营销费用的损失。有一些收益比较直接,比如现金红包,可以直接提现;还有一些收益需要变现,例如优惠券。不过,黑灰产有发卡平台、回收平台等成熟的变现渠道,所以变现也不成问题。

近年来,以云计算、大数据和人工智能等代表的新技术的出现或应用,在某种程度上也加速了黑灰产的发展。云计算的发展让个人搭建和维护一个网络平台的成本大大降低,其中包括黑灰产产业链中的一些平台,像提供虚假注册手机号的接码平台、提供海量IP地址的代理和秒拨平台、提供图像和滑动等验证码绕过服务的打码平台、提供交易和变现渠道的发卡平台等,很多搭建在国内或海外的云服务器上。这些平台的大量出现,打通了黑灰产的上下游供给,不仅降低了攻击成本,而且还提供了 API 接口等方式便于自动化,进一步提升了攻击效率,让黑灰产可以更容易地实施大规模的批量攻击,收益也更大。

从更大的角度黑灰产的发展趋势:

  • 以量取胜。目前,市面上大部分黑灰产都采取以量取胜,比如养号、刷量、薅羊毛等。使用这种策略的黑灰产从业者,它们充分利用“长尾效应”,本着“苍蝇腿上也是肉”的原则,把蛋糕做大。
  • 黑产资源平台化。过去几年,这个趋势已经很明显。在黑灰产领域,有很多需求庞大的基础资源,比如手机号、IP、设备、身份证、银行卡、支付渠道和自动化攻击工具等。各个类型的资源都发展出了专业的供应商,可以按需取用,极大降低了黑灰产从业者的从业门槛。
  • 技术对抗迭代转向资源效率迭代。黑灰产的一个典型变化是,从早期的技术对抗迭代模式,逐渐转向资源效率的迭代。在技术对抗迭代的时代,黑灰产从业者往往通过技术栈来保证自己的优势。当资源平台化后,黑灰产入行门槛大大降低。生产效率更多是通过资源利用率来提升,例如早期群控系统需要摆满房间的真实手机,后来被手机硬件厂商整合成箱控,一块主板能切割出几十台手机,再后来直接做成云手机的模式,连实体都不需要。

黑灰产的攻击方式

攻击服务可以划分为“过身份”、“多身份”、“批量化”三大模块,其中每个模块涉及到多种攻击技术。

  • 过身份:利用伪造身份、人脸等方式绕过平台认证,可以注册虚假账号并正常参与平台业务和活动;
  • 多身份:利用多开、改机、改定位等技术伪造多个“正常”设备,从而绕过平台对于单身份的限制;
  • 自动化:利用自动化脚本或群控工具,批量完成注册、登录、点击等业务操作。

机器作弊

通过自动化的机器程序来伪造真实的用户行为,它又分为协议攻击和脚本攻击两种。

  • 协议攻击指的是通过破解业务前端和服务器的通信协议,直接伪造并发起注册登录等业务请求;
  • 脚本攻击指的是通过编写按键精灵、autojs 等脚本,操控前端应用或网页的界面元素,比如输入框自动填入账号密码、自动点击登录按钮。

值得注意的是,前者不需要有设备安装业务应用,攻击成本更低,更容易规模化,危害也更大。

真人作弊

与机器作弊不同,真人作弊背后是一个个真实的人,黑灰产往往通过发布赏金任务,吸引真人协助其完成作恶。

不过,随着近年来甲方业务风控的不断加强,机器作弊很多时候会被识别出来,而真人作弊识别难度非常大,因此真人作弊越来越多,模式和形态越发多元和丰富。

黑灰产的产业链条

黑灰产之所以能有现在的发展,关键是其形成了一个分工明确、协助紧密的成熟产业链。据了解,整个黑灰产的产业链大致可以分为上游、中游和下游三个环节,其中,上游提供资源和技术,下游进行作恶和变现,而中游则连接上游和下游。

整个产业链中,比较关键的部分包括上游是否能持续稳定的提供可靠的资源和技术,下游是否能有稳定的变现途径或渠道,中游是否能高效的连接上游和下游,保持稳定的供需关系。如果这几点不出问题,整个产业链的运作就会很顺畅。

上游环节

黑卡运营商

手机黑卡是指没有在运营商进行实名认证,被不法分子利用进行薅羊毛攻击、实施通讯信息诈骗等违法犯罪活动的移动电话卡。

黑卡运营商通常与三大运营商代理勾结,他们在获得大量手机卡后通过加价转卖给下游手机卡商赚取利润。其黑卡主要来源有:实名卡、物联网卡、海外卡以及虚拟卡。

  • 实名卡:实名卡主要是通过拖库撞库、木马、钓鱼等方式从网上收集大量身份证信息,并通过黑卡运营商批量验证得到的。
  • 物联网卡:物联网卡是由三大运营商业提供的 4G/3G/2G 卡,硬件和外观与普通 SIM 卡相似,但采用专用号段,并加载针对智能硬件和物联网设备的专业化功能,满足智能硬件和物联网行业对设备联网的管理需求以及集团公司连锁企业的移动信息化应用需求。主要有基础通信、财务信息查询、终端状态查询、业务统计分析四大功能。物联网卡无需进行实名验证,由企业申请办理,一般仅需提供营业执照,实际操作中,营业执照通过财务公司操作,大概需要花费 1000 元左右即可成功注册,部分运营商对营业执照审核不够严谨,甚至会为灰产定制专用的物联网卡套餐。这种物联网卡多为免月租或者 1 元月租,根据能否接听电话,分为短信卡(也称“注册卡”)和语音卡。
  • 海外卡:在国家实行实名制后,黑卡运营商直接从海外购入手机卡,这些卡无需实名认证,花费很少,非常切合黑产利益。
  • 虚拟卡:由虚拟运营商提供的电话卡。虚拟运营商是指拥有技术、设备供应、市场营销等能力,与传统三大运营商在某项或者某几项业务上形成合作关系的合作伙伴。虚拟运营商就像是代理商,他们从移动、联通、电信三大基础运营商那里承包一部分通讯网络的使用权,然后通过自己的计费系统、客服号、营销和管理体系把通信服务卖给消费者。

猫池厂商

猫池厂商负责生产猫池设备,并将设备卖给卡商使用。猫池是一种插上手机卡就可以模拟手机进行收发短信、接打电话、上网等功能的设备,在正常行业有广泛的应用,如邮局、银行、证劵商、各类交易所、各类信息呼叫中心等,猫池设备可以实现对多张卡的管理。

手机卡商

手机卡商从黑卡运营商那里大量购买手机黑卡,将手机黑卡插入猫池设备并接入收码平台,然后通过收码平台接收各种验证码业务,根据业务类型的不同,每条验证码可以获得 0.1元-0.3元不等的收入。

接码平台

负责连接卡商和有手机验证码需求的群体,提供软件支持、业务结算等平台服务,通过业务分成获利,一般为30%左右。

打码平台

很多网站都会通过图片验证码来识别机器行为,对非正常请求进行拦截。因此打码平台已成为大多数黑产软件必备的模块之一,为黑产软件提供接口,突破网站为辨别机器还是人类行为而设置的图片验证码。

文字、图像、声音等验证码的技术难度较高,打码平台通常难以完全依赖技术手段实现自动操作。国内的打码平台,以往主要依靠低廉的劳动力。他们对无法技术解决的验证码使用人工打码进行破解。这种方式广泛传播到了大量第三世界国家,导致全球有近百万人以此为生。打码工人 平均每码收入1-2分钱,熟练工每分钟可以打码20个左右,每小时收入10-15 元。

随着技术的发展,打码平台也与时俱进,逐渐产生了使用人工智能打码的平台,引入大量验证码数据对识别系统进行训练,将机器识别验证 码的能力提高了2000倍,价格降低到了每千次15-20元,为撞库等需要验证的业务提供了极大的便利。

IP代理

IP作为互联网空间中最基础的身份标识,一直以来都是争夺对抗最激烈的攻防点。这是一个高度成熟产业,国内代理、国外代理、国内秒拨软件等。

因为目前机房的服务器IP基本已经被标识,所以这部分代理IP基本无法使用,所以需要有大量的家庭住宅IP。国外可以走代理,有专门的服务商在提供动态住宅IP,比如luminati代理、911代理以及oxylabs代理,单价很高。国内基本都是秒拨软件实现的,秒拨的底层思路就是利用国内家用宽带拨号上网(PPPoE)的原理,每一次断线重连就会获取一个新的IP,秒拨两个天然的优势:

  • IP池巨大:假设某秒波机上的宽带资源属于XX地区电信运营商,那么该秒拨机可拨到整个XX地区电信IP池中的IP,少则十万量级,多则百万量级;
  • 难以识别:因为秒拨IP和正常用户IP取自同一个IP池,秒拨IP的使用周期(通常在秒级或分钟级)结束后,大概率会流转到正常用户手中,所以区分秒拨IP和正常用户IP难度很大。

改机工具

刷新设备指纹,解决单台设备注册的上限。

Android和iOS都有很多相应的改机工具。Android改机大部分都基于Xposed框架,需要root;iOS大多基于Cydia框架,需要越狱。

  • Xposed号称Android上最强大的神器,它是一个框架,上面有很多模块,这些模块都依赖于xposed这个框架,之所以称xposed是第一神器,就是因为这些模块可以完成许多匪夷所思的功能,例如:修改微信的界面,自动抢红包模块,自定义程序的文本,防止微信消息撤回,防止BAT三大流氓的全家桶相互唤醒、连锁启动,锁屏后自动干掉APP防止后台运行耗电,还有很多修改App或手机数据的装B模块等等。

  • AWZ国内最新支持iOS全系系统的一键新机、全息备份、位置伪装、ASO辅助工具,手机一键新机,轻松修改设备参数,功能定时更新,多重保障软件稳定运行,为用户提供更好体验。为iPhone/iPad提供反越狱检测,修改系统序列号、型号、系统版本、运营商、地理位置、MAC、UUID、IMEI、IDFA、SSID应用全息备等一系列强大功能。

群控平台

群控是指通过一台电脑或者手机设备控制批量手机的行为,可以分为线控和云控两种形式。 线控是指信号 发生器与被控制的手机设备通过线缆进行连接的; 云控指手机搭载了云技术可以实现远程控制,可以用任意一台PC通过云端控制手机终端上的任何资料,随意调取自己所需的信息,或者使用另一部手机用ID登录云服务器。通过群控工具,可以实现一台终端对多台手机的控制,与改机工具进行搭配,可以在短时间内制造成千 上万不同设备的信息,适用于羊毛党的批量攻击。

  • 群控:指通过系统自动化控制集成技术,把多个手机操作界面直接映射到电脑显示器,实现由一台电脑来控制几十台甚至上百台手机的效果。以某社交平台群控为例,它是在群控体系基础上,针对其定制化、批量模仿正常个人用户操作的软硬件集成体系。它以群控体系+各种批量模仿脚本的方法,完成批量操作,其所有任务执行都是同时进行的。
  • 箱控:是群控的进化变种,把手机核心组件都做到一个箱子,去掉一些用不上的硬件,做成的专门的群控设备,一个箱子可操控多台手机的多个APP,具体方法是使用了一款名为appium的安卓自动化测试工具,通过文字、控件id、控件名称等直接定位到APP的控件进行操作。
  • 传统云控:通过无线连接。电脑通过后台发送指令到云端,云端的指令再发到手机群,继而执行任务。理论上,一台电脑可以控制上千台手机
  • 新型云控:主要指的是通过云端协议外挂的形式,发送数据包与服务器进行交互,自定义进行登录互联网帐号、绑定邮箱、更改密码等操作。一台电脑一个账号就可以操控上万个帐号。

中游环节

盗号

盗号,就是通过一定手段,盗取他人账号和密码,

  • 初级盗号以钓鱼为主,钓鱼就是以假乱真,欺骗你自己输入帐号密码。
  • 中级盗号以木马/键盘钩子记录为主,讲究驱动级钩子,过杀毒软件。
  • 高级盗号以入侵网站为主,讲究渗透,注入,提权,后门。

扫号

扫号就是利用了网络上公开的数据不停的对网站提交身份验证的数据包(比如常见的登录验证),来验证是否是本站会员,也就是撞库。

养号

批量注册小号,不断发作品、关注用户,修改头像,主要目的是为了降低账号被封的概率。

账号恶意注册是恶意行为的源头,整个流程已趋于专业化、从业人员十万级,形成了手机验证码服务平台、打码平台、注册软件开发团伙、垃圾账号分销平台等一条龙服务。 批量性恶意注册主要是通过软件实现的,具体流程如下图:

跳转号

指适用QQ号或者微博快捷登录的账号,激活绑定转换而成的号码。

恶意注册商也就是号商,注册海量的社交帐号,并通过脚本工具获取62数据或A16数据。

这两个数据是某社交平台用户登录新设备后生成的加密文件,这个文件储存在其安装目录中,下次运行时检测到该文件就可以自动登录。如果把这个文件中的数据导入到另一部设备中,那么这部设备也可以跳过登录验证的步骤直接登录账号。

恶意注册商就是通过这种手法,搭配注册的社交账号、密码进行售卖,黑产团伙购买后在云控平台上登录使用。

发卡平台

把数字商品做自动化交易的平台,在号商完成大量账号的注册后,他们会把恶意账号整理后集中在发卡平台中列出,供处在产业链下游的用号方直接线上批量采购。

互联网黑灰产业链的交易环节是由一个分层的交易环境构成,除了我们熟悉的暗网之外,交易量更大且交易内容更丰富的就要提到国内已经非常繁荣的“发卡平台灰色生态”,发卡平台灰色产业链支撑了国内黑灰产的规模化和效率提升。

发卡平台的本质是互联网黑灰色产业链发展到一定阶段后,对产业链自身效率提升的刚需引导出的产物。早期基于信任的个体交易早已不能满足快速发展的黑灰产的需求,发卡平台的出现,极大地提升了黑灰产交易双方的协作效率。

在发卡平台出售的商品中,账号类商品数量最多,占比为59.12%,账号是黑灰产进行攻击的基础资源,所售账号种类涉及到众多行业,比如社交、电商、娱乐、生活服务等等;发卡平台另一活跃商品类型为各大影视会员卡密,占比为23.18%,包括但不限于腾讯视频、爱奇艺、优酷等。

下游环节

引流

引流黑产下游主要包括黑五类产品销售、色情诈骗和杀猪盘等。

刷量

刷直播平台的播放量、点赞评论、收藏

薅羊毛

平台补贴

黑灰产的防控手段

某种意义上,黑灰产就像业务的影子一样,基本不可能“斩尽杀绝”,除非业务也“死掉”。当阳光斜射,影子就变大,唯有烈日高悬时,影子才会变得最小。研究黑产的四个要素:

  • 人:从事该项黑灰产的人,他们是谁?他们聚集在哪里?
  • 资源:从事该项黑灰产需要什么前置资源?从哪里获取?
  • 路径:攻击方法是什么?操作流程是什么样?网络路径是什么样?
  • 变现:黑灰产收益是通过什么方式变现?在哪里变现?

在黑灰产对抗过程中,研究者的挑战在于针对某个黑灰产业链,怎样弄清楚这四大要素(人、资源、路径、变现)。因为只有搞清楚这四大要素,研究者才能拥有全景视角,才能在整个黑灰产业链条上寻找最佳对抗点切入。

在黑灰产的对抗中,风控技术非常重要。一般来说,好的风控和产品的耦合度是比较高的,需要根据产品特征进行定制。在他看来,风控能力一般分为数据和策略两部分。

数据的来源通常分为三类:

  • 源自黑灰产业链的研究。直接在产业链研究的渠道上获取精准风控数据,比如黑产手机号、黑产出口IP等。
  • 源自用户恶意行为的分析。针对已知的恶意行为建立模型,筛选出恶意用户的相关数据。
  • 源自风险数据的关联分析。在上述两类数据的基础上,进行更大范围的关联分析,圈定更多风险数据。

在策略方面,策略需要根据产品具体设计,例如社交、游戏和电商等不同的产品线,它会有不同的风控策略体系。

  • 核身策略。它的意思是搞清楚不同账号是否对应了相同的人。最常见的就是不同账号出现了同一绑定的手机号、同一的收货地址、同一的 IP、同一设备的 ID、同一支付 ID、同一历史行为和同一地理位置等,这是最基础的风控规则。
  • 异常用户特征。长期未登录,只参与本次活动;设备登录过多个账号的用户;设备 root 用户;设备中装有特定作弊相关 app 的用户…
  • 不同用户出现聚集性特征。同 IP;同 WiFi 地址;同地理位置;连续手机号段;同样的操作路径、速度;不同用户出现交叉现象,比如互为好友等情况…

通过定义大量类似的规则,筛选出可疑风险用户群,在相应的产品或活动策略上进行降权处理,都能较好地降低黑灰产给企业带来的经济或口碑损失。

从大方向上黑灰产分为两大类:“手术刀型”和“狼牙棒型”。

  • 手术刀型:目标明确,常以点对点精准攻击为主,典型的表现行为比如入侵、诈骗和游戏外挂等。
  • 狼牙棒型:其特点是没有明确的目标,或者目标非常多,以量取胜。典型的有网络扫描、DDoS、恶意注册、撞库、刷量、薅羊毛、恶意爬虫和各种恶意机器人程序。

目前,大部分企业面临的业务安全问题,通常集中在“狼牙棒型”的黑灰产上。

企业要有效的打击黑灰产,最有效的方式是需要了解自身业务存在哪些黑灰产相关的攻击场景,熟悉对方的作恶成本,包括所需的资源、时间、金钱、渠道及收益等。

出行行业黑灰产研究

共享出行已是大众出行常用方式,业务部门推出多种营销活动以促进拉新率及留存率,有利益的地方就有黑产,此类现金烧钱业务场景下催生了拉新助力提现和代下单灰色服务,此类攻击上游黑产资源稳定,下游可直接/间接通过论坛、社交、发卡平台等渠道变现,攻击各家出行平台方式虽不同,下游获利变现渠道基本一致。

对黑产常攻击的几家出行平台,当前/历史开展的营销活动进行对比,结果如下表格所示:

新用户营销活动:

助力营销活动:

从上表可看出,对于新用户的拉新及老用户的留存各平台都投入了较多营销预算,这也是吸引众多黑产加入出行行业的原因。

出行行业风险分布

出行行业司机端的业务风险主要为抢单、刷单、账号解封、代开户、实名认证等,主要针对具有庞大司机群体的出行平台,随着出行平台的严厉打击,此类业务风险较前几年相比已很难形成规模化产业链。除个别头部企业,由于前期账号体系较为简易,导致市场存量黑号较多。

目前用户端是整个出行行业的重灾区,从注册登录到营销、下单、交易,整个流程存在不同的业务风险。

业务场景业务风险作弊方式
注册登录扫号、撞库、垃圾注册、虚假注册自动化软件 / 人工
营销虚假裂变、真人作弊自动化软件 / 人工
下单认证绕过、实名伪造、代下单人工
支付&绑卡预支付绕过、冒名代绑、黑卡支付人工

有钱赚的地方就有黑产,依附于出行市场下的黑色产业链已初具雏形。

出行行业常用黑产资源

黑产作案离不开各种资源,出行平台的特点及业务薄弱点决定了黑产使用的资源和工具,通常情况下,黑产会利用成熟的工具及资源作案。

IP资源

IP是绕不过的黑产资源。目前黑产所使用的IP主要为代理IP和秒拨IP两类,通常情况下,黑产会通过更换当前IP隐藏自己真实的IP,以绕过出行平台IP维度相关的风控策略。

移动端可通过代理类APP切换当前上网IP,近期黑产常用的代理APP有爱加速、豌豆代理等。

web端可购买代理IP网站提供的IP,或通过VPS拨号换IP。代理IP网站可以API形式批量输出IP,便于黑产集成在自动化软件中,常见的有熊猫代理、芝麻代理等。

而拨号VPS已成为一条成熟的产业链,随便打开一个VPS网站都可以看到如下信息,适用于营销、挂机等场景,符合黑产需求,自动化软件运行在VPS中即可切换IP,薅羊毛必备。

号码资源

号码资源已经是非常成熟的黑色产业链,而黑产通常走接码平台或线下对接的形式获取号码资源,用于批量注册账号。目前市面上活跃的接码平台有:海豚云、番茄云等。

接码平台项目分为公开对接和专属对接两类,随着政策的管控,平台存活周期变短,接码平台的号码质量也逐渐变差,公开对接的号码大多为N手号码,虚商号段及流量卡号码较多,黑产很难在各家出行平台获利,但仍能够在风控体系薄弱的平台赚的盆满钵满。

专属对接和线下对接是黑产首选的手机号资源,成本较公开对接卡贵,多为新卡,但省去了新老号检测这一步,可快速提升获利速度。

目前,市面上活跃的接码平台有近百个,而这些平台部分可通过搜索引擎直接搜索到,更多的则通过云盘的形式传播,通过QQ群、土豆群、电报群、黑产论坛、发卡平台等平台布局,链接上游卡商及下游开发者、羊毛党,将号码资源的价值发挥到极致。

除接码平台和线下对接外,近两年出现的拦截黑卡也被用于黑灰产项目,据我们观察,此类数据也通过API的形式下放,提供给下游黑产用于注册、营销等场景,涉及平台包括抖音、淘宝、拼多多等。

设备资源

模拟器、云手机、群控设备是黑产常用的设备资源,便于批量化操作,可达到一定的规模。市面上的模拟器已经较成熟,功能丰富,支持修改机型、模拟定位、多开系统等功能,成本低,符合黑产的作案要求。而很多平台对于模拟器、云手机的容忍度较高,这也纵容了黑产的作恶行为,吸引更多的羊毛党加入黑产大军。目前黑产使用较多的有:雷电模拟器、逍遥模拟器等。

云手机也是黑产常用的作案工具,很多平台在设备维度的检测很难追赶上市面上黑产工具的迭代速度,黑产在旧版本应用上屡试不爽。目前比较常见的云手机有红手指、多多云等。

群控也是黑产常用的手段,二手交易平台、QQ群、土豆群、电报群、发卡平台等媒介均可购买设备及群控管理工具,功能丰富,注册养号拉新等场景均可使用,满足各大工作室及个体户的需求。而在黑产论坛上,也可发现免费的群控系统,或付费的定制化群控系统,满足不同群体黑产团伙的需求。

工具资源

黑产通常使用多开、改机工具、虚拟定位等工具绕过设备维度的风险识别,相关的黑产工具资源也非常稳定。

多开软件可在设备中实现分身功能,便于黑产注册账号在诸多场景中获利。目前多开类应用主要分为三类,一类是通过修改Framework,常见的系统自带分身如小米分身、华为分身,此外还可通过修改APK、虚拟化技术实现等方式进行多开,如幻*分身、大*助手。

如下图所示,黑产利用多开分身制作多个曹*出行分身接码登录打车。目前市面上的主流多开软件自带模拟机型、虚拟定位等功能,伪造一个新设备,以绕过各家平台的检测。

目前iOS端主要为软改工具,如市面上传播度较广的爱伪装、igrimace、佐罗等,具备伪装设备越狱状态、可修改核心设备参数绕过设备恢复逻辑实现一键新机等功能,而如天下游、任我行等iOS端的虚拟定位软件,可实现修改定位功能。

Android主要为硬改和软改,软改通常修改的是应用层的数据,通过HOOK的方式篡改数据,硬改将相关硬件参数直接写入内存地址达到修改设备参数的目的。目前市面上流行的安卓端的改机类工具比较多,如抹机王、XX抹机神器、微霸改机、Magisk等。

虚拟定位类软件,市面上常见的大多数都是基于xposed二次开发的,需要配合其他工具一起使用,如改机工具、代理应用。

其他资源

很多出行平台需要经过实名、绑卡、认证等环节才能进行打车、拉新等操作,这时需要购买相应的数据以绕过风控,而相应数据的买卖也已经是成熟的产业链,可通过社交软件、发卡平台出售,也可通过众包平台真人作弊以达到相应的目的。

下图为某社交平台公开售卖料子信息的聊天截图。

而有些数据依托于发卡平台交易,如海外信用卡,其黑话为“毛c”,这些黑话很难从字面上去理解其含义,需要根据数据特征去判断,这也是业务情报团队监控时的难点。

对于滴*出行、曹*出行这种可绑定支付宝账号进行免密支付的平台,可通过购买支付宝账号进行预支付绕过,此类第三方账号的买卖也是非常成熟的产业链。

出行行业攻击成本

对于黑产来说,能够获利才是最终的目的,投入与产出需达到某个平衡点才值得花时间和人力作案。针对出行行业的攻击成本,我们粗略统计了相关的资源成本价格,可供参考。

成本的叠加并不能真实体现黑产的投入成本,真实成本需要考虑实际的黑产使用场景。IP、号码、料子等资源为必备资源,设备资源为可复用资源,而考虑到不同平台的攻击手法不同,设备资源与工具资源组合使用的方法不同,相应攻击成本体现为一个价格区间,而非一个固定值。

参考链接:

国内高铁票价的计算规则

$
0
0

高铁(包括 G、D 字头列车和一部分 C 字头列车)票价的计算是一个比较复杂的问题。它取决于线路的速度等级、里程、递远递减以及折扣等方面。

注意:

  • 以下的讨论均不包含既有线动车组以及除京津城际线以外的城际铁路动车组的情况;
  • 为不致混淆,本文中“高铁”指除第 1 条情形以外“开行 G、D 字头列车的线路”,不指代“G 字头列车”。

速度等级

线路的速度等级很大程度上决定了该线路上票价率(即每公里的票价)的高低。我国高铁车票定价时,速度等级分为两个:300 km/h 及以上(开行 G 字头列车为主)和 300 km/h 以下(开行 D 字头列车为主)。

  • 时速 300 km/h 及以上的线路,G 字头列车(以下简称 G 车)二等座票价率一般为46 元/公里,D 字头列车(以下简称 D 车)二等座票价率一般为 0.31 元/公里或 0.37 元/公里;
  • 时速 300 km/h 以下的线路,G 车和 D 车的二等座票价率大多相同,一般为31 元/公里或 0.37 元/公里。

国内部分高铁线路的票价率参见表(里程单位:公里,票价率单位:元/公里)。

利用表中给出的票价率,计算沈阳北—大连北的 G 车二等座票价。

解:沈阳北—大连北,383 公里(里程计算详见第二部分),383×0.46=176.18≈176 元。实际票价 175.5 元,大致相符。由于取消保险以及不同线路对尾数舍入的规定不同,计算出的票价与实际票价有 1~2 元的差距是正常的。

至于一等座、特等座和商务座的票价,则分别按照二等座票价的 1.6 倍、1.8 倍和 3 倍计算(广深港高速线福田段除外)。

客运运价里程

上面的例子中已经使用了沈阳北—大连北的里程数据。铁路部门在计算票价时,使用的并不是直线距离,也不是铁路线路的实际长度,而是一个叫做“铁路客运运价里程”的里程。客运运价里程通过《铁路客运运价里程表》公布,并在新线路开通之前通过《铁路客货运输专刊》进行更新。客运运价里程可以与实际里程有所出入。 火车Wiki在线客里表可查询。

如果某次列车在两条或以上线路上运行时(称为“跨线”,反之称为“本线”),运价里程通过线路间的“接算站”进行累加计算。计算里程时的经由可能与列车实际走向不一致。

【例】计算 G200 次列车淄博—北京南间的客运运价里程。

解:

  • 胶济客专线 淄博—济南 110
  • 京济联络线 济南—济南西 20
  • 京沪高速线 济南西—北京南 406
  • 共计 110+20+406=536 公里。

递远递减

远递减票价计算

高铁票价执行递远递减的原则:500 公里以下的部分无折扣,500~1000 公里的部分打九折,1000~1500 公里的部分打八折,以此类推。

【例】计算京沪高铁本线 G 车北京南—上海虹桥二等座票价。

解:

  • 北京南—上海虹桥,1318 公里,有递远递减。
  • 500×46+(1000-500)×0.46×0.9+(1318-1000)×0.46×0.8=554.024≈554 元,与实际票价完全一致。

【例】计算哈大高铁全程 G 车二等座票价。

解:先计算客运运价里程。

  • 京哈高速线沈哈段 哈尔滨西—沈阳北 538
  • 沈大高速线 沈阳北—大连北 383
  • 共计 921 公里,享受递远递减。
  • 500×46+(921-500)×0.46×0.9=404.294≈404.5 元,实际票价 403.5 元,基本一致。

运价里程通算

由于高铁客专大多数是由地方和中国铁路总公司合资修建,在不同省份或路局集团公司管内组建了不同的合资公司,为了便于进行收入核算和利润分配,并不是所有线路上的运价里程都可以直接相加参与递远递减(可以直接相加的情况称为“通算”)。甚至部分线路由于分属不同路局集团公司管辖,即使本线车里程也不享受递远递减,表 1 的备注栏中对这种线路予以了标注。并且,二等座票价率不同的线路一定不可通算。

目前我已知可以通算运价里程的线路有:

  • 京沪高速线、津秦高速线、合蚌高速线、合福高速线、宁杭高速线间发生直接跨线时,运价里程通算
  • 京广高速线各段间运价里程通算
  • 徐兰高速线西安北至徐州东间里程通算
  • 京哈高速线沈哈段与沈大高速线(即通俗所称的哈大高铁)间发生直接跨线时,运价里程通算

所谓“直接跨线”是指中间不经由其他任何线路从某线跨到另一条线。

除表 1 备注栏标注无递远递减的线路以外,还有以下情形:

  • 广深港高速线各段间不通算
  • 沪昆高速线沪杭段与其余部分不通算,杭州东至昆明南段通算
  • 徐兰高速线西宝段与宝兰段及西安北至徐州东段不通算
  • 武九客专线大阳段与阳九段不通算,但大阳段与武石城际线通算
  • 宁蓉线南京南至合肥南段、合肥南至宜昌东段、宜昌东至重庆北段、重庆北至成都东段相互之间不通算,各段内部通算
  • 杭深线杭甬段与宁波至深圳北段不通算,宁波至深圳北段内部通算
  • 海南环岛高铁线东段、西段间不通算
  • 南广线南宁至梧州南段与梧州南至广州南段不通算,即南广线无递远递减

若某列车跨经不通算的线路运行时,分别计算出各段票价,相加即得总票价。

下面举几个例子:

【例】计算 D2373 次列车南京南至汉口的二等座票价。

解:宁蓉线南京南至合肥南段、合肥南至宜昌东段间不通算,先列出各段运价里程。

  • 宁蓉线宁渝段 南京南—合肥南 157
  • 宁蓉线宁渝段 合肥南—汉口 359
  • 南京南至合肥南段,157×43=67.51≈67.5 元。
  • 合肥南至汉口段,359×37=132.83≈133 元。
  • 两段相加,得到总票价5+133=200.5 元,实际票价 200 元,基本一致。
  • 注意,宁蓉线上的列车实行浮动票价,并非所有经由和里程相同的列车票价都相同,具体请参见第五部分。

【例】计算 G1371 次列车(上海虹桥—昆明南)全程二等座票价。

解:沪昆高速线沪杭段与其余部分不通算,先列出各段运价里程。

  • 沪昆高速线 上海虹桥—杭州东 159
  • 沪昆高速线 杭州东—昆明南 2093
  • 上海虹桥至杭州东段,159×46=73.14≈73 元。
  • 杭州东至昆明南段里程通算,2093 公里享受递远递减。
  • 500×46+(1000-500)×0.46×0.9+(1500-1000)×0.46×0.8+(2000-1500)×0.46×0.7+(2093-2000)×0.46×0.6=807.668≈807.5 元。
  • 两段相加,得到总票价 73+807.5=880.5 元,实际票价 879 元,基本一致。

【例】计算 G1904/1 次列车(西安北—福州)全程二等座票价。

解:先计算客运运价里程。

  • 徐兰高速线 西安北—徐州东所 880
  • 徐州东联络线 徐州东所—徐州东 3
  • 京沪高速线 徐州东—蚌埠南 156
  • 合蚌高速线 蚌埠南—合肥北城 110
  • 合福高速线 合肥北城—福州 850
  • 西安北至徐州东段里程通算,883 公里享受递远递减。500×46+(883-500)×0.46×0.9=388.562≈388.5 元。
  • 徐州东至福州段里程通算,1116 公里享受递远递减。500×46+(1000-500)×0.46×0.9+(1116-1000)×0.46×0.8=479.688≈479.5 元。
  • 两段相加,得到总票价5+479.5=868 元,实际票价 867 元,基本一致。

不达速折扣

根据原铁道部于 2011 年 8 月 12 日发布的《关于新建高速铁路动车组列车下浮票价的通知》(铁运电〔2011〕108 号)文件,

对降速运营的京津城际线、沪昆高速线沪杭段、宁蓉线合宁段和合武段、石太客专线获太段、昌九城际线、海南东环线、沪深线甬厦段、长吉城际线和既有线提速区段上开行的动车组列车,各席别票价在现行票价基础上下浮 5%,不足 1 元的尾数按四舍五入处理。

列表中除宁蓉线合宁段和合武段已于 2018 年 7 月 1 日恢复原速度运行外,其他线路以及后来的部分运营时速不足 250 km/h 的线路均有不同幅度的票价下浮。折扣比例见表 1。

至此,所有高铁车票的定价规则就介绍完毕了。下面举两个例子,看看票价究竟是如何计算出来的。

【例】计算 G200 次列车淄博—北京南二等座票价。

解:

  • 对胶济客专线和京沪高速线分段计算。
  • 胶济客专线,110×31=34.1≈34 元。
  • 京济联络线和京沪高速线,426×46=195.96≈196 元。
  • 两段相加,得到总票价 34+196=230 元,实际票价 229 元,基本一致。

【例】计算 G1313 次列车汉口—重庆北二等座票价。

解:

  • 宁蓉线合肥南至宜昌东段、宜昌东至重庆北段间不通算,先列出各段运价里程。
  • 宁蓉线宁渝段 汉口—宜昌东 292
  • 宁蓉线宁渝段 宜昌东—重庆北 553
  • 汉口至宜昌东段,292×37=108.04≈108 元。
  • 宜昌东至重庆北段,享受递远递减,有不达速折扣。(500×31+(553-500)×0.31×0.9)×0.95=161.30≈161.5 元。
  • 两段相加,得到总票价 108+161.5=269.5 元,实际票价 270 元,基本一致。

浮动票价

2018 年 7 月 5 日起,铁路部门对早期开通的合肥至武汉、武汉至宜昌、贵阳至广州、柳州至南宁、上海至南京、南京至杭州 6 段线路上运行时速 200-250 公里的高铁动车组公布票价进行优化调整,明确执行票价以公布票价为最高限价,铁路相关企业可根据客流情况,分季节、分时段、分席别、分区段在限价内实行票价下浮,最大折扣幅度 6.5 折。

浮动票价以“公布票价”为上限,不同车次执行不同折扣。各列车公布票价可在12306查询,一般高于原来的固定票价。目前多条高铁线路执行浮动票价,包括已开通多年的京沪高铁、成渝高铁、沪宁城际等,新开通的高铁一般也直接按照浮动票价定价,例如今年通车的津兴城际、福厦高铁等。

实际上,国铁集团在2016年就获得了高铁自主定价权。当年1月1日实施的《关于改革完善高铁动车组旅客票价政策的通知》提到,高铁动车组可制定无折扣的公布票价,再根据公布票价确定实际的执行票价。到2017年4月份,铁路部门在东南沿海高铁首次执行浮动票价。2018年7月,浮动票价模式又扩大到合肥-武汉、武汉-宜昌、贵阳-广州、上海-南京、南京-杭州等高铁线路。2020年12月,京沪高铁也开始执行浮动票价。此后,更多线路采取这一定价模式。

铁路部门在执行浮动票价时,一般会表示票价“有升有降”。但统计部分线路票价发现,涨价的车次数一般高于降价车次数,总体上这些线路票价也上涨了。以京沪高铁为例,此前全程二等座的固定票价为553元,现在的公布票价为662元。12月20日,北京南站至上海虹桥站开行35趟列车,其中只有2趟列车二等座票价低于553元,有5趟列车与原固定票价持平,其余80%列车高于原票价。涨价的列车中,8趟列车的实际票价与公布票价(662元)一致。

参考链接:

互联网公司的管理神话破灭

$
0
0

价值观,OKR,花名,弹性工作制等……

在过去的 20 年里,互联网行业为企业管理界贡献了许多花活儿,这其中有些是中国互联网原创的,也有不少是从硅谷学习而来属于全球互联网行业通行。

当互联网行业处于黄金上升期的时候,几乎每家成功的互联网企业都要或多或少的对外输出一下自己的企业管理方法论,有的是通过创始人演讲,有的是通过出书,也有类似腾讯和阿里巴巴这样的,直接以被投企业家俱乐部的形式来影响自己投资的创业企业。

然而,随着互联网黄金增长期的结束,全球互联网普及红利(对国内来说是人口红利)的消失,我们惊讶发现从 2020 年开始,许多过去互联网行业高举高打的管理概念正在失效……或者说,它也许从来就没有有效过。

站在这个时间点去回顾互联网公司曾经沉淀下的那些方法论,我们会发现无论是阿里巴巴、腾讯、字节跳动,还是 Google、Amazon 和 Netflix。他们的企业管理方法论可能都存在错误归因——低估自己所乘着的时代东风,高估了自身的努力(管理行为)。

因为在企业管理界,有一些明显的错误答案,剩下的全都是“正确答案”。

这是我看最近一本书《 大厂人才》的感受,这本书由穆胜咨询合伙人娄珺(Samantha)出品。书中详细横向评测了字节跳动、腾讯、阿里巴巴、美团、华为等多个大厂的管理体系,并间或的对这些不同模式与传统企业管理制度做了比较。

原书作者在结论上总结的比较委婉,但我结合自身的大厂工作经历以及对本书阅读的感受,可以直说:所有互联网大厂的管理创新,可能都是画蛇添足。

以全书反思性最为明显的 OKR 为例,OKR 的“始作俑者”Google 在 2022 年启用了新的管理工具 GRAD(Googler Reviews and Devlopment, 谷歌员工评价和发展),GRAD 是一种更为绩效(偏向 KPI)而非目标导向的管理工具。

根据书中的描述,Google 使用 GRAD 取代 OKR 的主要原因可能有两个,第一个是在过去很长一段时间里,由于 OKR 的反 KPI 属性,导致日常业务中大量的经常性工作无法被考核,OKR 只能作为该公司更大的管理机制 Preformance Management 一环使用,并且只能覆盖 40% 的工作。第二个,则是 OKR 复杂的流程和评价机制本身让员工不堪重负,在许多时候,反复的对齐、拉通、复盘比简单的定期考核一个业务指标是否达成更让人痛苦。

OKR 在国内的忠实粉丝字节跳动也在 2023 年调整了它们的考核方式与考核节奏,比如从双月回顾改为季度回顾。

在 OKR 以外,一些曾经在发展早期宣誓要“去 KPI 化”的企业也在最近重新回到 KPI 考核的道路,比如百度和小米。

阿里巴巴的花名机制,也在实践中与原本的设立初衷背道而驰——原本是为了淡化职级加强扁平化管理,结果真的搞成了“江湖味儿”,公司内“门派林立”。腾讯的“赛马机制”和“自下而上”也一度使得公司缺乏 Big Picture,漏球了信息流和短视频这两个非常重要的业务。

从社会学的角度讲,现代企业的本质是个体分工协作的产物。它的作用是将一群人以特定的社会关系结合在一起,实现一个人无法实现的伟业。这意味着巴别塔可以有很多种建成方式,只要没有上帝来捣乱,任何一种方式都可以通天,而“上帝来捣乱”的方法,就是让每个人都觉得自己的方法是对的。

从 2023 年全球互联网行业的大裁员和惨状来看,互联网的企业管理方法论创新,别说可能不适用于其他企业,甚至可能连互联网行业自己都不合适。比如,早期的字节跳动被称为“App 工厂”,上一个火一个。从字节跳动在 2017 年全面启用飞书之后,就再也没有上线过任何一个现象级产品。腾讯方法论无法让腾讯复制微信,字节方法论也无法让字节复制抖音。

这意味着无论是飞书提出的“先进团队先用飞书”还是阿里的“每年将会向社会输出 1000 名在阿里工作 10 年以上的人才”,对于社会上的其他企业来说都是一个陷阱。

如果你相信这些从互联网公司来的工具、人才、方法论能够帮助你原本陷入困境的企业重振旗鼓,那你就要倒大霉咯。

这个观点倒不是书中提出的,而是我之前从字节的方法论观察到的。众所周知,虽然字节其实并不怎么输出企业管理方法论(比起阿里巴巴),但飞书却从诞生之出将“方法论”与产品捆绑进行销售。飞书在所有的媒体和互联网公司都备受好评,甚至连百度和阿里的一些团队也私下使用飞书进行一些边缘项目的协作,确实配得上“先进团队先用飞书”。

但是,这个世界本身就是由落后构成的。

我第一次认识到这一点,是我在一次参会成员年龄稍大的会议上,用飞书分享我自己的稿子(就是那个几万字的稿子)。我讲到一半,一个听众问我:你讲到第几页了?

我愣了,我讲到第几页了?飞书没有页数啊,因为飞书就没想过会有人把电子文档打印出来看。

提问者 50 多岁,以我和大多数互联网员工的视角看确实算是“岁数大了”。但他实际上也是从 90 年代开始用电脑的“弄潮儿”,你怎么都不能说人家“数字素养”不行?类似的情况其实还有几乎所有的在线文档都不支持“尾注”这个在学术写作中非常重要的功能(其实还有“修订模式”),以至于金山文档至今可以拿到腾讯文档和飞书文档都拿不到的市场份额。

是因为各种眼花缭乱的在线文档不够“先进”吗?不,是因为太过先进了,与落后的现实世界并不匹配。

除非你假定一个 35 岁全员退休的社会,否则向落后兼容,就是一个先进管理工具与生产力工具的最重要基础要素。

这个事情在 SaaS 市场其实被反复验证,所有人都说中国的 SaaS 市场不好做,然后找了许多许多理由。但时至 2024 年,就没有几家企业开门做公司敢不买 Microsoft Office,连免费的 WPS 都是因为和 Microsoft Office 做的“一模一样”,才能抢到这部分市场。以至于我之前和@ 汐笺聊 SaaS 的时候说:

你如果做了一个办公三件套,觉得自己很创新,和 Office 长得不像。那一定是你做错了,因为微软办公套件里的每个按钮都有一个你们整个团队那么多的产品和研发,还对应了 0.x%~x% 的市场份额。

你日常可以用飞书,用 notion,用 Obsidian,甚至你在小团队里也可以用这些新工具来协作。然而,一旦你要进入到更大规模的社会协作,你就离不开 Microsoft Office。字节跳动的 GR 和政府打交道也丢飞书链接吗?阿里巴巴和腾讯合作签合同难道用钉钉文档做前期互审吗?不可能的。

不仅在工具层面如此,在管理工具层面也是如此,OKR 是一个所谓“面向创新”的管理工具。但即便是在人类密集创新的最近半个世纪里,创新也并非企业的常态。创新带来的是新增长点,但这个点一旦被创出来了,后面的增长工作无一例外是由海量的人与资金在枯燥的日常工作中驱动的。

这两年其实也刚好有一个对比,一方面是由于裁员数量增大,职场社交软件脉脉上关于“空降阿里员工”的吐槽多了起来。大致的故事模版就是中小厂,来了一个阿里高 P,进来就搞一通管理+工具的改革,风风火火干三个月,轻则业务下降,重则公司倒闭。

另一方面,马斯克在 2022 年末收购推特,主打一个干翻所有管理理念,什么必要岗位,先裁掉一半员工再说。什么切换服务器要半年,我亲自坐私人飞机给你网线拔了——结果是 X 的主要业务数据并没有下滑,甚至在 AI 新业务上的进展也优于 Google。

书中也特意配了一个维度来证明这一点,这是我非常佩服作者的。她竟然能想到用几大公司离职员工的创业成功率来恒量一个企业的管理制度是否有效,而数据是这样的:

公司名称离职员工创办公司数量上市公司数量IPO上岸率
华为481255.2%
美团11532.6%
腾讯720182.5%
阿里巴巴941161.7%
字节跳动2100%
数据来源:穆圣咨询、IT桔子

这个维度并不严谨,因为这个数字本身就会有漏算,而且创业成功也并非 IPO 一个衡量指标。但是我们会非常明显的看到对自己价值观与方法论自视甚高的阿里巴巴在这个比较中是非常落下风的。

在这本书中,华为是作为“传统企业”被拉进来与互联网公司做对比的,因此在横评中的每一项,华为几乎都有优势,不免让人觉得这是不是作者在拉偏手。但华为的成功本身由华为的管理方法论决定的吗?华为的方法论在别的企业执行的下去吗?

众所周知,华为在人才管理方面曾做出过一些超乎民企可及范围的事情。类似的情况,拼多多也在电商消费纠纷上有一些“别人做不到的能力”。所以,不管你想要在自己的企业里实施奋斗者文化,还是想要对供应商实施“仅退款”政策,还是要创始人掂量一下自己的斤两。

所以,还是回到我在本文开头提到的,企业管理界有一些明显的错误答案,剩下的全都是“正确答案”。在吸收别人的经验时,错误的总比正确的更重要。只要你的企业还没黄,就不要轻易效仿别人。

尤其是在现在这个下行周期里,企业黄了也不一定是你做的不对, 试图模仿先进者只会让你黄的更快。


Google搜索引擎架构Caffeine

$
0
0

什么是 Google Caffeine?

Google Caffeine 是 Google 搜索引擎的一次架构重构,旨在满足不断增长的互联网内容和用户实时搜索需求。它替代了 Google 早期的分层索引系统,转而采用更加实时的索引机制,使搜索结果更加快速和新鲜。

发布时间:Google 于 2009 年 8 月发布 Caffeine 项目 Beta 版本,并于 2010 年 6 月正式启用。

Google Caffeine的产生背景

传统索引架构的局限性

在 Caffeine 出现之前,Google 使用的是一个分层的索引结构:

  • 分层索引:
    • 数据分为“主索引”和“增量索引”。
    • 主索引:周期性地更新,通常需要几天或几周。
    • 增量索引:包含最新的数据,但覆盖范围较小。
  • 更新延迟:对于新增的网页或内容,用户可能需要等待较长时间才能在搜索结果中看到。
  • 扩展性瓶颈:随着互联网内容的指数增长,传统架构难以高效处理。

Google Caffeine 的目标

  • 提高索引速度:支持更快地抓取和处理新增内容(如新闻、博客、社交媒体)。
  • 改进搜索结果的新鲜度:实现接近实时的搜索结果,用户能够快速获取最新信息。
  • 扩展性:提升 Google 数据中心的存储和处理能力,满足日益增长的互联网规模。
  • 提升性能:降低搜索延迟,改善用户体验。

Google Caffeine 的核心特性

  • 实时索引
    • Caffeine 实现了一个单层实时索引系统。
    • 新的网页和内容在被抓取后,几乎立即可供搜索使用。
  • 分布式处理
    • 利用 Google 的分布式文件系统(GFS),对网页进行分片存储和处理。
    • 大量服务器协同工作,提高数据处理速度。
  • 高效的分片机制
    • 把网页划分为多个独立的“索引片”(index shard),每个片可以独立更新和查询。
  • 改进抓取策略
    • 更频繁地抓取更新频率高的网站(如新闻网站)。
    • 对重要页面和内容优先处理。
  • 支持海量数据
    • Caffeine 的架构允许 Google 索引比以前多得多的网页内容,同时支持更多种类的数据(如图片、视频、实时内容)。

Caffeine 的技术优势

特性传统索引架构Google Caffeine
索引更新频率周期性(数天至数周)实时更新
处理速度较慢高效并行处理
扩展性有限动态可扩展
数据类型支持主要是网页多种数据类型
  • 速度与效率:每秒可以处理数以千计的网页更新,显著提高了索引更新的频率。
  • 可扩展性:动态扩展索引容量,适应互联网规模的持续增长。
  • 新鲜度:通过实时处理机制,确保用户搜索结果中始终包含最新内容。
  • 数据融合:Caffeine 不仅索引网页,还将图片、视频、社交媒体内容等多种类型数据整合到搜索结果中。

Google Caffeine的影响

对用户的影响

  • 更快的搜索结果:用户可以几乎实时地获得最新的新闻、博客和社交媒体内容。
  • 更全面的搜索覆盖:索引规模的扩大意味着用户能够搜索到更多样化的内容。
  • 搜索体验提升:减少了延迟,搜索结果更加相关。

对网站管理员的影响

  • 频繁抓取:Googlebot 的抓取频率增加,对频繁更新的网站尤为明显。
  • 关注内容更新:网站需要更注重内容质量和频繁更新,以便在搜索中保持竞争力。
  • 页面权重动态变化:页面排名可能会更快地反映内容的更新和链接关系的变化。

对互联网行业的影响

  • 推动实时搜索:其他搜索引擎(如 Bing、Yahoo)也纷纷改进索引技术,以缩短数据处理延迟。
  • 促进内容生产:更快速的内容索引激励了新闻网站和博客作者加速生产优质内容。
  • 提高搜索质量:用户能够获得更加多样化和相关的搜索结果。

Google Caffeine 是一次革命性的搜索引擎升级,标志着搜索技术进入实时化时代。它不仅大幅提升了 Google 的搜索能力,还推动了整个互联网生态系统的快速发展。如果对其架构或技术实现有更深入的兴趣,可以讨论相关的分布式系统和大规模数据处理技术(如 GFSBigtableMapReduce等)。

Google Caffeine后更新

Google Caffeine 的推出是 Google 搜索引擎历史上的重要里程碑,但它并不是终点。Caffeine 后,Google 持续在搜索算法、基础架构和用户体验方面进行升级,以应对互联网快速变化的需求和挑战。

Google 在 Caffeine 后的升级,主要集中在以下几个方向:

  • 更智能的查询理解:RankBrain、BERT、MUM。
  • 用户体验优化:移动优先索引、页面体验更新。
  • 实时性与动态性:实时数据索引和搜索。
  • 安全性与透明性:加强数据隐私和搜索可信度。
  • 生成式 AI 的应用:引入生成式 AI,提供更全面和互动的答案。

每一次升级都反映了 Google 对搜索体验优化的持续追求,同时适应了技术趋势和用户需求的变化。

以下是 Google 在 Caffeine 后的主要升级和改进:

RankBrain(2015 年)

RankBrain是 Google 搜索引擎引入的机器学习算法,用于更好地理解用户查询背后的意图,特别是长尾查询和模糊表达。

  • 核心功能
    • 将查询转换为向量形式(数值表示),便于理解和匹配用户意图。
    • 提升了对自然语言查询的处理能力。
  • 影响
    • 搜索结果变得更加相关和智能。
    • RankBrain 成为 Google 排名因素之一,与内容相关性和页面权威性共同决定排名。

BERT(2019 年)

BERT(Bidirectional Encoder Representations from Transformers)是 Google 推出的基于深度学习的自然语言处理技术,用于理解查询的上下文含义。

  • 核心改进
    • BERT 能双向分析查询中的词语和短语,而不是孤立地理解单个词。
    • 尤其对长查询和疑问句效果显著。
  • 应用场景:在多个语言版本的搜索中使用,改善了 70 多种语言的搜索质量。
  • 举例:查询 “2019年在巴西的游客需要签证吗?”
    • BERT 可以正确理解“在巴西”是对游客的修饰,而不是对签证的修饰。

移动优先索引(Mobile-First Indexing,2016 年启动,2021 年完成)

随着移动设备使用量的增加,Google 改变了索引策略,从桌面优先转向移动优先。

  • 核心概念:Google 搜索索引以网站的移动版本为基础,而非桌面版本。
  • 影响
    • 网站需要优化其移动页面,以确保排名不受影响。
    • 响应式设计和快速加载成为关键因素。

Page Experience Update(页面体验更新,2021 年)

Google 强调用户体验因素,将 Core Web Vitals(核心网页指标)纳入排名算法。

  • 核心指标
    • LCP(Largest Contentful Paint):页面主要内容的加载速度。
    • FID(First Input Delay):用户与页面首次交互的响应速度。
    • CLS(Cumulative Layout Shift):页面布局稳定性。
  • 其他因素
    • HTTPS 安全性。
    • 无侵入性广告。
  • 影响:更注重用户友好的设计,缓慢或交互不佳的网站可能失去排名。

MUM(Multitask Unified Model,2021 年)

MUM是 Google 搜索的多任务统一模型,旨在更智能地处理复杂查询,并提供综合答案。

  • 核心特点
    • 使用多模态模型,能够同时处理文本、图像甚至视频数据。
    • 支持多语言理解,并能将知识迁移到不同语言。
  • 应用场景:复杂查询(例如:计划登山旅行需要哪些准备?),MUM 可以整合多种资源,生成更全面的答案。
  • 改进:支持图文结合的搜索,比如用户上传一张图片并配以问题。

实时搜索与增强数据呈现

Google 持续增强搜索结果的动态性和直观性:

  • 实时搜索:整合新闻、社交媒体动态(如 Twitter)、股市变化等实时数据。
  • 丰富的结果展示
    • 使用知识图谱(Knowledge Graph)提供结构化信息。
    • 提供更直观的搜索结果卡片(例如:直接在搜索页面上显示天气、赛事结果、影片简介等)。

神经匹配(Neural Matching,2018 年)

Neural Matching是一种基于神经网络的算法,专注于更好地匹配用户查询和网页内容之间的相关性。

  • 特点:更偏向于全局语义理解,而非关键词匹配。
  • 应用场景:特别是在用户查询中未使用精确关键词的情况下表现突出。

AI 驱动的改进(2023 年及之后)

Google 不断使用更先进的 AI 模型改进搜索引擎,包括生成式 AI 的引入:

  • 生成式 AI 回答:提供直接的、简短的答案,尤其在用户提出的问题非常具体时。
  • 对话式搜索:搜索引擎逐渐具备类似 ChatGPT 的能力,能够与用户进行多轮对话。

数据隐私与安全性改进

近年来,Google 加强了对用户数据隐私的保护,同时确保搜索结果的可信度:

  • 隐私保护:为用户提供更多控制搜索记录的方式。
  • 搜索透明性:提供“关于此结果”(About This Result)的功能,帮助用户了解来源的可靠性。


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>