Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 15896 articles
Browse latest View live

高性能kv存储之Redis、Redis Cluster、Pika:如何应对4000亿的日访问量?

$
0
0

一、背景介绍

随着360公司业务发展,业务使用kv存储的需求越来越大。为了应对kv存储需求爆发式的增长和多使用场景的需求,360web平台部致力于打造一个全方位,适用于多场景需求的kv解决方案。目前,我们线上大规模使用的kv存储有Redis,Redis cluster以及Pika。

为什么说是爆发式的需求增长呢?早在2015年9月份,公司Redis的日访问量还处于800亿,到了2016年第三季度日访问量已经突破2500亿,2017年第一季度日访问量已经接近4000亿。短短的一年半时间,日访问量增长了5倍。下面给大家分别简单介绍一下Redis,Redis Cluster以及Pika的特点和使用场景。

二、kv存储之Redis  

1、Redis介绍  

Redis做为大家熟知的开源内存数据库,在很多项目中被广泛的使用。它支持String、Hash、List、Set、Zset、Geo、Hyperloglogs等多数据结构。同时也支持主从复制、Lua脚本、事务、数据持久化、高可用和集群化等等

2、Redis特性

1)高性能:Redis虽然是单线程的,但是它同样拥有着超高的性能。我们线上的普通PC Server上,经过测试,每秒请求数OPS能够达到10w左右。

2)多样化数据结构:Redis支持String、Hash、List、Set、Zset、Geo等多数据结构。

3)持久化:RDB持久化:快照式持久化方式,将内存中的全量数据dump到磁盘。它的优势是加载速度比AOF快,劣势是快照式的全量备份,没有增量数据,造成数据丢失。

AOF持久化:AOF记录Redis执行的所有写操作命令。恢复数据的过程相当于回放这些写操作。使用AOF的持久化方式,优势是有灵活的刷盘策略,保证数据的安全性。劣势是AOF文件体积比RDB大,占用磁盘多,数据加载到内存的数据慢。

4)多重数据删除策略:

①惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,删除掉这个过期key。

②定期删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key。

③主动删除:当前已用内存超过maxmemory限定时,触发主动清理策略,该策略由启动参数的配置决定,可配置参数及说明如下:

  • volatile-lru:从已设置过期时间的样本中根据LRU算法删除数据 (redis3.0之前的默认策略)
  • volatile-ttl:从已设置过期时间的样本中挑选过期时间最小的数据删除
  • volatile-random:从已设置过期时间的样本中随机选择数据删除
  • allkeys-lru:从样本中根据LRU算法删除数据
  • allkeys-random:从样本中任意选择删除数据
  • noenviction:禁止从内存中删除数据 (从redis3.0 开始默认策略)

maxmemory-samples 删除数据的抽样样本数,redis3.0之前默认样本数为3,redis3.0开始默认样本数为5,该参数设置过小会导致主动删除策略不准确,过大会消耗多余的cpu。

5)高可用:Redis自身带有哨兵的组件,可以监控Redis主从的运行状态和自动的故障切换,实现Redis的高可用。

3、Redis使用场景

一般场景:OPS < 10W,数据量较小

进阶场景:单点写入可以支撑,但读取量巨大,可以采用读写分离,1主多从的方案;

写入读取量都很大,单点写入无法支撑,可以采用Hash分片方式。

但是,无论数读写分离的方式还是Hash分片的方式,在的Redis的架构中没有引入中间件或者更加智能的驱动的情况下,都需要从代码上去保证,这一定程度上增加了开发人员的代码复杂度。同时随着业务的增长,扩展性也较差。那么如何更加理想的去解决这个问题,使用Redis Cluster会是一个更加简洁有效的方案。

三、kv存储之Redis Cluster  

1、Redis Cluster介绍

Redis Cluster 是一个分布式、无中心节点的、高可用、可线性扩展的内存数据库,Redis Cluster的功能是普通单机 Redis 的功能的一个子集。Redis Cluster为了保证一致性而牺牲了一部分容错性: 系统会在保证对网络断线和节点失效具有有限抵抗力的前提下, 尽可能地保持数据的一致性。

2、Redis Cluster重要概念:

①hash slots——哈希槽

Redis 集群没有使用一致性hash,而是引入了哈希槽(hash slot)的概念。Redis 集群一共有16384个hash slot,集群使用CRC16校验后对16384取模来计算键key属于哪个槽。

②cluster node——集群节点

集群中的每个主节点负责处理16384个hash slot中的一部分。每个node的hash slot数量可以灵活手工调整。

③cluster map——集群信息表

集群中的每个节点都记录整个集群的Cluster map信息,集群信息包括每个节点的唯一id号,ip地址,port端口号,role 在集群中的角色,主节点负责的hash slot的范围,节点状态等。节点之间通过Gossip协议进行通信,传播集群信息,并发现新节点向其他节点发送ping包,检查目标节点是否正常运行。

3、Redis Cluster架构  

Redis Cluster架构

4、Redis Cluster数据路由  

①client执行命令,计算key对应的hash slots

②根据本地缓存的cluster map信息,连接负责该hash slots的数据节点获取数据

如果slot不在当前连接的节点,返回moved错误,重定向客户端到新的目的服务器上获取数据,并更新client本地缓存的cluster map

如果slot在当前节点且key存在,则执行操作结果给客户端

如果slot迁出中,返回ask错误,重定向客户端到迁移的目的服务器上获取数据

5、使用场景  

大容量、高并发、可线性扩展

刚才仅仅是对Redis Cluster做了简单的介绍,关于集群的创建、数据的迁移、集群的扩容和缩容、hash slots重分布、集群的reblance等日常操作,使用Redis Cluster中遇到的问题以及在改进smart driver道路上解决的问题等等,如果有同学感兴趣的话,欢迎私下共同交流学习。

四、kv存储之Pika

1、Pika是什么

Pika 是DBA需求,基础架构组开发的大容量、高性能、持久化、支持多数据结构的类Redis存储系统,目前已经开源,最新版本为Pika 2.2版本。它所使用的nemo引擎本质上是对Rocksdb的改造和封装,使其支持多数据结构的存储,并在nemo引擎之上封装redis接口,使其完全支持Redis协议。Pika兼容string、hash、list、zset、set等多数据结构,使用磁盘而非内存存储数据解决了Redis由于存储数据量巨大而导致内存不够用的容量瓶颈。

2、Pika PK Redis

Pika PK Redis之优势:

①大容量存储:Pika数据容量受制于磁盘,最大使用空间等于磁盘空间的大小,而Redis数据容量受限于主机内存

②秒级启动:Pika 在写入的时候, 数据是落盘的,Pika 重启不用加载所有数据到内存,不需要进行回放数据操作。而Redis启动需要将所有数据从磁盘加载到内存,随着容量增加,启动时间递增到分钟级甚至更长时间。

③秒级备份:Pika的备份方式,是将所有数据文件做快照。Redis无论是用RDB还是AOF的方式来实现数据备份的目的,都需要将全量的数据写入到磁盘,备份速度慢。

④秒删数据:Pika的数据删除是标记删除,Pika Key的元信息上有版本信息,表示当前key的有效版本,已删除的数据在Compact合并数据的过程中删除。而单线程的Redis在大量删除数据时候会影响线上业务,删除大对象会阻塞住Redis的主线程,删除速度慢。

⑤同步续传:Pika写入数据会有write2file日志文件,只要该文件未删除,无需全量同步数据,均可断点续传数据。而Redis一旦主从同步缓冲区被循环重写,容易导致全量数据重传。

⑥高压缩比:Pika存储的数据默认会被压缩,相对于Redis,Pika有5~10倍的压缩比。所以Redis的数据存储到Pika,数据体积会小很多。

⑦高性价比:相对于Redis使用昂贵的内存成本,Pika使用磁盘存储数据,性价比极高

Pika PK Redis之劣势:

①读写性能较弱:Pika是持久化的,基于磁盘的kv存储。而Redis是内存数据库。虽然pika是多线程的,但是在大多场景下,性能还是略逊色于Redis

②多数据结构性能损耗:Pika底层使用Rocksdb存储引擎,它并不支持多数据结构,Pika在Rocksdb的上层进行了改造和封装,实现了对多数据结构的支持。同时在性能上,会有一些损耗。

③兼容大部分Reids接口:Pika兼容了90% Redis接口,使其易用性得到大大提升。但是目前还没有做到完全兼容。

3、Pika整体架构

4、Pika使用场景

①业务量并没有那么大,使用Redis内存成本太高

②数据量很大,使用Redis单个服务器内存无法承载

③经常出现时间复杂度很高的请求让Redis间歇性阻塞

④读写分离且不希望故障切主后影到从库,能够快速切换

5、Pika使用现状

①内部:目前Pika已经在360内部的各个业务线广泛使用,共计覆盖43个主业务线和76个子业务线,近千亿的日访问量和数十T的数据规模,节约了大量昂贵的服务器内存,降低了使用成本。

②社区:在业内,据不完全统计,很多著名互联网公司也使用了Pika,例如新浪微博、美团、58同城、迅雷、万达电商、环信…………

6、Redis如何迁移到Pika  

那么,在适用于 Pika的业务场景下,我们如何将Redis数据迁移到Pika呢?

Pika自带的工具集,其中aof_to_pika这个工具可以帮助我们完成很平滑的这个任务。由于该工具依赖于AOF来发送数据,所以原Redis必须要开启AOF,并关闭AOF重写的策略。aof_to_pika通过reader线程读取aof文件中的内容,根据设定的单次发送长度拼装成数据块,将要发送的数据库加入队列。同时sender线程不断的从队列中读取数据发送到目的Pika完成数据的重放。

除了Redis的迁移工具之外。考虑有同学可能也使用到了ssdb,那么Pika也很贴心的提供了从ssdb迁移数据到Pika的工具ssdb_to_pika。

五、多场景的业务需求

最后,针对于业务多变的kv存储需求,常常有两个重点是我们最为关注的,一个是数据量,另一个是访问量。围绕着数据量和访问量的大小,往往决定了我们是使用Redis、Redis Cluseter or Pika。

kv存储

六、QA

Q1:Pika支持集群吗?

A1:Pika目前主持主从结构,也支持codis。自身目前还不支持分布式的集群化,我们还在做多数据结构集群化的调研。

Q2:Pika 与Redis什么关系?替代吗还是补充?看着像是补充。 Pika 如果是替代Redis,那使用磁盘如何保证高性能。

A2: Pika与Redis是使用场景上互相补充的关系。目前线上的Pika机器都使用的ssd,一般场景下ops在5w以内场景都能轻松应对。

Q3:Redis持久化方式如何选取?还是不做持久化?

A3:持久化多数场景下选择AOF方式,做不做持久化取决于业务对数据安全性的要求,毕竟纯缓存的数据一旦服务器宕机或者数据库崩溃,数据都会全部丢失。

Q4 : 张老师你好,Pika挂固态硬盘读写性能是否可以pk Redis?360 业务有这样应用吗?

A4:目前360内部使用Pika的应用,全部使用的都是SSD硬盘。

Q5 : Pika 里边 zset 是落地的么,zset是怎么实现的呢? 对scan 类的命令支持怎样?Pika 性能如何?

A5: 是落地的,大致原理是将一条key-score-members转换成3条rocksdb的kv来存储。scan是都支持的,和Redis一样。Pika的性能我们认为还不错,能够满足多数场景,但是建议大家要部署在SSD上,和内存比SSD还是便宜太多的,另外非常欢迎大家用测试比较Pika与相似项目的性能!

Q6 : 老师,像类似于fastdfs也是存储在硬盘的,请问Pika与他们在使用场景上有什么不同呢?

A6: 就我个人知识了解,fastdfs是一个分布式文件系统,存储小文件、图片等,与Pika面向的场景不一样,Pika是为了解决Redis单机内存不足而设计的一个在线数据库,当然,只要单机磁盘容量够,也是可以存储二进制文件到pika的。

Q7: Pika和Aerospike相比有哪些优势呢?Aerospike开源版本内存加持久化后,执行删除操作,重启后删除的数据会重新从磁盘加载,Pika有没有这个弊病?

A7: Pika的设计初衷其实就是为满足业务在Redis存储中,因为内存不足而造成业务损失,所以,我们的Pika的命令基本与Redis保持一致,并且client也是复用Redis的,这样,业务从Redis切换到Pika,无任何复杂度,这一点,我个人看Aerospike是比不了的。另外,Pika是不会加载删除数据。

Q8:redis 中热键大键如何处理?发现大部分故障都源于此

A8: 热点数据需要进行业务逻辑上的拆分或者多级缓存分担压力。我们线上也因为大键造成了一些困扰,例如:网卡带宽被打死、del大键造成Redis堵塞等,从Redis本身,确实没有太好的办法来解决,只能从业务层面分析出现大键的原因,做出响应的对策,例如:对大键进行压缩存储、或者存储大键到有多线程处理的pika中等等。

作者介绍

张恒,360web平台部DBA ,主要负责公司kv存储、数据仓库gp运维以及私有云数据库平台建设。

文章来自微信公众号:高效运维开发


[译] Android 开发最佳实践

$
0
0

Android 开发最佳实践

Futurice公司Android开发者中学到的经验。 遵循以下准则,避免重复发明轮子。若你对开发iOS或Windows Phone 有兴趣, 请看 iOS Good PracticesWindows client Good Practices这两篇文章。

摘要

  • 使用 Gradle 和它推荐的工程结构
  • 把密码和敏感数据放在gradle.properties
  • 不要自己写 HTTP 客户端,使用Volley或OkHttp库
  • 使用Jackson库解析JSON数据
  • 避免使用Guava同时使用一些类库来避免 65k method limit(一个Android程序中最多能执行65536个方法)
  • 使用 Fragments来呈现UI视图
  • 使用 Activities 只是为了管理 Fragments
  • Layout 布局是 XMLs代码,组织好它们
  • 在layoutout XMLs布局时,使用styles文件来避免使用重复的属性
  • 使用多个style文件来避免单一的一个大style文件
  • 保持你的colors.xml 简短DRY(不要重复自己),只是定义调色板
  • 总是使用dimens.xml DRY(不要重复自己),定义通用常数
  • 不要做一个深层次的ViewGroup
  • 在使用WebViews时避免在客户端做处理,当心内存泄露
  • 使用Robolectric单元测试,Robotium 做UI测试
  • 使用Genymotion 作为你的模拟器
  • 总是使用ProGuard 和 DexGuard混淆来项目

Android SDK

将你的 Android SDK放在你的home目录或其他应用程序无关的位置。 当安装有些包含SDK的IDE的时候,可能会将SDK放在IDE同一目录下,当你需要升级(或重新安装)IDE或更换的IDE时,会非常麻烦。 此外,如果你的IDE是在普通用户下运行,而不是在root下运行,还要避免把SDK放到一下需要sudo权限的系统级别目录下。

构建系统

你的默认编译环境应该是 Gradle. Ant 有很多限制,也很冗余。使用Gradle,完成以下工作很方便:

  • 构建APP不同版本的变种
  • 制作简单类似脚本的任务
  • 管理和下载依赖
  • 自定义秘钥
  • 更多

同时,Android Gradle插件作为新标准的构建系统正在被Google积极的开发。

工程结构

有两种流行的结构:老的Ant & Eclipse ADT 工程结构,和新的Gradle & Android Studio 工程结构, 你应该选择新的工程结构,如果你的工程还在使用老的结构,考虑放弃吧,将工程移植到新的结构。

老的结构:

old-structure
├─ assets
├─ libs
├─ res
├─ src
│  └─ com/futurice/project
├─ AndroidManifest.xml
├─ build.gradle
├─ project.properties
└─ proguard-rules.pro

新的结构

new-structure
├─ library-foobar
├─ app
│  ├─ libs
│  ├─ src
│  │  ├─ androidTest
│  │  │  └─ java
│  │  │     └─ com/futurice/project
│  │  └─ main
│  │     ├─ java
│  │     │  └─ com/futurice/project
│  │     ├─ res
│  │     └─ AndroidManifest.xml
│  ├─ build.gradle
│  └─ proguard-rules.pro
├─ build.gradle
└─ settings.gradle

主要的区别在于,新的结构明确的分开了'source sets' ( main, androidTest),这是Gradle的一个理念。 通过这个你可以做到,例如,添加源组‘paid’和‘free’在src中,让你的应用程序具有付费和免费的两种模式的源代码。

你的项目引用第三方项目库时(例如,library-foobar),拥有一个顶级包名 app从第三方库项目区分你的应用程序是非常有用的。 然后 settings.gradle不断引用这些库项目,其中 app/build.gradle可以引用。

Gradle 配置

常用结构参考 Google's guide on Gradle for Android

小任务除了(shell, Python, Perl, etc)这些脚本语言,你也可以使用Gradle 制作任务。 更多信息请参考 Gradle's documentation

密码在做版本release时你app的 build.gradle你需要定义 signingConfigs.此时你应该避免以下内容:

不要做这个. 这会出现在版本控制中。

signingConfigs {
	release {
		storeFile file("myapp.keystore")
		storePassword"password123"keyAlias"thekey"keyPassword"password789"}
}

而是,建立一个不加入版本控制系统的 gradle.properties文件。

KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789

那个文件是gradle自动引入的,你可以在 buld.gradle文件中使用,例如:

signingConfigs {
	release {try{
			storeFile file("myapp.keystore")
			storePasswordKEYSTORE_PASSWORDkeyAlias"thekey"keyPasswordKEY_PASSWORD}catch(ex) {thrownewInvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
		}
	}
}

使用 Maven 依赖方案代替使用导入jar包方案如果在你的项目中你明确使用某些 jar文件,那么它们可能成为固定的版本,如 2.1.1.下载jar包更新他们是很繁琐的, 这个问题Maven很好的解决了,这在Android Gradle构建中也是推荐的方法。你可 以指定版本的一个范围,如 2.1.+,然后Maven会自动升级到制定的最新版本,例如:

dependencies {
	compile'com.netflix.rxjava:rxjava-core:0.19.+'compile'com.netflix.rxjava:rxjava-android:0.19.+'compile'com.fasterxml.jackson.core:jackson-databind:2.4.+'compile'com.fasterxml.jackson.core:jackson-core:2.4.+'compile'com.fasterxml.jackson.core:jackson-annotations:2.4.+'compile'com.squareup.okhttp:okhttp:2.0.+'compile'com.squareup.okhttp:okhttp-urlconnection:2.0.+'}

IDEs and text editors

IDE集成开发环境和文本编辑器

无论使用什么编辑器,一定要构建一个良好的工程结构。编辑器每个人都有自己的 选择,让你的编辑器根据工程结构和构建系统运作,那是你自己的责任。

当下首推 Android Studio,因为他是由谷歌开发,很好地支持Gradle,包含很多有用的检测和分析工具,默认使用最新的工程结构,它就是为Android开发定制的。

你也可以使用纯文版编辑器如Vim,Sublime Text,或者Emacs。如果那样的话,你需要使用Gradle和 adb命令行。

不再推荐使用Eclipse和ADT开发,因为 谷歌在2015年年末结束了对ADT的支持,并呼吁开发者尽快迁移到Android Studio。

无论你使用何种开发工具,避免将你的编辑器配置文件(比如Android Studio的iml文件)加入到版本控制,因为这些文件通常包含与本地机器有关的配置,可能会影响你的同事。

最后,善待其他开发者,不要强制改变他们的开发工具和偏好。

类库

Jackson是一个将java对象转换成JSON与JSON转化java类的类库。 Gson是解决这个问题的流行方案,然而我们发现Jackson更高效,因为它支持替代的方法处理JSON:流、内存树模型,和传统JSON-POJO数据绑定。不过,请记住, Jsonkson库比起GSON更大,所以根据你的情况选择,你可能选择GSON来避免APP 65k个方法的限制。其它选择: Json-smartand Boon JSON

网络请求,缓存,图片执行请求后端服务器,有几种交互的解决方案,你应该考虑实现你自己的网络客户端。使用 VolleyRetrofit。Volley 同时提供图片缓存类。如果你选择使用Retrofit,那么考虑使用 Picasso来加载图片和缓存,同时使用 OkHttp作为高效的网络请求。Retrofit,Picasso和OkHttp都是同一家公司开发(注: 是由 Square公司开发),所以它们能很好的在一起运行。 OkHttp 同样可以和Volley在一起使用 Volley.

RxJava是函数式反应性的一个类库,换句话说,能处理异步的事件。 这是一个强大的和有前途的模式,同时也可能会造成混淆,因为它是如此的不同。 我们建议在使用这个库架构整个应用程序之前要谨慎考虑。 有一些项目是使用RxJava完成的,如果你需要帮助可以跟这些人取得联系: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. 我们也写了一些博客: [1], [2], [3], [4].

如若你之前有使用过Rx的经历,开始从API响应应用它。 另外,从简单的UI事件处理开始运用,如单击事件或在搜索栏输入事件。 若对你的Rx技术有信心,同时想要将它应用到你的整体架构中,那么请在复杂的部分写好Javadocs文档。 请记住其他不熟悉RxJava的开发人员,可能会非常难理解整个项目。 尽你的的全力帮助他们理解你的代码和Rx。

Retrolambda是一个在Android和预JDK8平台上的使用Lambda表达式语法的Java类库。 它有助于保持你代码的紧凑性和可读性,特别当你使用如RxJava函数风格编程时。 使用它时先安装JDK8,在Android Studio工程结构对话框中把它设置成为SDK路径,同时设置JAVA8_HOMEJAVA7_HOME环境变量, 然后在工程根目录下配置 build.gradle:

dependencies {
	classpath'me.tatarka:gradle-retrolambda:2.4.+'}

同时在每个module 的build.gradle中添加

applyplugin:'retrolambda'android {
	compileOptions {
	sourceCompatibilityJavaVersion.VERSION_1_8targetCompatibilityJavaVersion.VERSION_1_8}

retrolambda {
	jdkSystem.getenv("JAVA8_HOME")
	oldJdkSystem.getenv("JAVA7_HOME")
	javaVersionJavaVersion.VERSION_1_7}

Android Studio 提供Java8 lambdas表带是代码提示支持。如果你对lambdas不熟悉,只需参照以下开始学习吧:

  • 任何只包含一个接口的方法都是"lambda friendly"同时代码可以被折叠成更紧凑的语法
  • 如果对参数或类似有疑问,就写一个普通的匿名内部类,然后让Android Studio 为你生成一个lambda。

当心dex方法数限制,同时避免使用过多的类库Android apps,当打包成一个dex文件时,有一个65535个应用方法强硬限制 [1][2][3]。 当你突破65k限制之后你会看到一个致命错误。因此,使用一个正常范围的类库文件,同时使用 dex-method-counts工具来决定哪些类库可以再65k限制之下使用,特别的避免使用Guava类库,因为它包含超过13k个方法。

Activities and Fragments

Fragments应该作为你实现UI界面默认选择。你可以重复使用Fragments用户接口来 组合成你的应用。我们强烈推荐使用Fragments而不是activity来呈现UI界面,理由如下:

  • 提供多窗格布局解决方案Fragments 的引入主要将手机应用延伸到平板电脑,所以在平板电脑上你可能有A、B两个窗格,但是在手机应用上A、B可能分别充满 整个屏幕。如果你的应用在最初就使用了fragments,那么以后将你的应用适配到其他不同尺寸屏幕就会非常简单。

  • 屏幕间数据通信从一个Activity发送复杂数据(例如Java对象)到另外一个Activity,Android的API并没有提供合适的方法。不过使用Fragment,你可以使用 一个activity实例作为这个activity子fragments的通信通道。即使这样比Activity与Activity间的通信好,你也想考虑使用Event Bus架构,使用如 Otto或者 greenrobot EventBus作为更简洁的实现。 如果你希望避免添加另外一个类库,RxJava同样可以实现一个Event Bus。

  • Fragments 一般通用的不只有UI你可以有一个没有界面的fragment作为Activity提供后台工作。 进一步你可以使用这个特性来创建一个 fragment 包含改变其它fragment的逻辑而不是把这个逻辑放在activity中。

  • 甚至ActionBar 都可以使用内部fragment来管理你可以选择使用一个没有UI界面的fragment来专门管理ActionBar,或者你可以选择使用在每个Fragment中 添加它自己的action 来作为父Activity的ActionBar. 参考.

很不幸,我们不建议广泛的使用嵌套的 fragments,因为 有时会引起 matryoshka bugs。我们只有当它有意义(例如,在水平滑动的ViewPager在 像屏幕一样fragment中)或者他的确是一个明智的选择的时候才广泛的使用fragment。

在一个架构级别,你的APP应该有一个顶级的activity来包含绝大部分业务相关的fragment。你也可能还有一些辅助的activity ,这些辅助的activity与主activity 通信很简单限制在这两种方法 Intent.setData()Intent.setAction()或类似的方法。

Java 包结构

Android 应用程序在架构上大致是Java中的 Model-View-Controller结构。 在Android 中 Fragment和Activity通常上是控制器类( http://www.informit.com/articles/article.aspx?p=2126865). 换句话说,他们是用户接口的部分,同样也是Views视图的部分。

正是因为如此,才很难严格的将fragments (或者 activities) 严格的划分成 控制器controlloers还是视图 views。 最还是将它们放在自己单独的 fragments包中。只要你遵循之前提到的建议,Activities 则可以放在顶级目录下。 如果你规划有2到3个以上的activity,那么还是同样新建一个 activities包吧。

然而,这种架构可以看做是另一种形式的MVC, 包含要被解析API响应的JSON数据,来填充的POJO的 models包中。 和一个 views包来包含你的自定义视图、通知、导航视图,widgets等等。 适配器Adapter是在数据和视图之间。然而他们通常需要通过 getView()方法来导出一些视图, 所以你可以将 adapters包放在 views包里面。

一些控制器角色的类是应用程序级别的,同时是接近系统的。 这些类放在 managers包下面。 一些繁杂的数据处理类,比如说"DateUtils",放在 utils包下面。 与后端交互负责网络处理类,放在 network包下面。

总而言之,以最接近用户而不是最接近后端去安排他们。

com.futurice.project
├─ network
├─ models
├─ managers
├─ utils
├─ fragments
└─ views
   ├─ adapters
   ├─ actionbar
   ├─ widgets
   └─ notifications

资源文件 Resources

  • 命名遵循前缀表明类型的习惯,形如 type_foo_bar.xml。例如: fragment_contact_details.xml, view_primary_button.xml, activity_main.xml.

组织布局文件如果你不确定如何排版一个布局文件,遵循一下规则可能会有帮助。

  • 每一个属性一行,缩进4个空格
  • android:id总是作为第一个属性
  • android:layout_****属性在上边
  • style属性在底部
  • 关闭标签 />单独起一行,有助于调整和添加新的属性
  • 考虑使用 Designtime attributes 设计时布局属性,Android Studio已经提供支持,而不是硬编码 android:text(译者注:墙内也可以参考stormzhang的这篇博客 链接)。
<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/name"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:text="@string/name"style="@style/FancyText"/><includelayout="@layout/reusable_part"/></LinearLayout>

作为一个经验法则, android:layout_****属性应该在 layout XML 中定义,同时其它属性 android:****应放在 styler XML中。此规则也有例外,不过大体工作 的很好。这个思想整体是保持layout属性(positioning, margin, sizing) 和content属性在布局文件中,同时将所有的外观细节属性(colors, padding, font)放 在style文件中。

例外有以下这些:

  • android:id明显应该在layout文件中
  • layout文件中 android:orientation对于一个 LinearLayout布局通常更有意义
  • android:text由于是定义内容,应该放在layout文件中
  • 有时候将 android:layout_widthandroid:layout_height属性放到一个style中作为一个通用的风格中更有意义,但是默认情况下这些应该放到layout文件中。

使用styles几乎每个项目都需要适当的使用style文件,因为对于一个视图来说有一个重复的外观是很常见的。 在应用中对于大多数文本内容,最起码你应该有一个通用的style文件,例如:

<stylename="ContentText"><itemname="android:textSize">@dimen/font_normal</item><itemname="android:textColor">@color/basic_black</item></style>

应用到TextView 中:

<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/price"style="@style/ContentText"/>

你或许需要为按钮控件做同样的事情,不要停止在那里。将一组相关的和重复 android:****的属性放到一个通用的style中。

将一个大的style文件分割成多个文件你可以有多个 styles.xml文件。Android SDK支持其它文件, styles这个文件名称并没有作用,起作用的是在文件 里xml的 <style>标签。因此你可以有多个style文件 styles.xml, style_home.xml, style_item_details.xml, styles_forms.xml。 不用于资源文件路径需要为系统构建起的有意义,在 res/values目录下的文件可以任意命名。

colors.xml是一个调色板在你的 colors.xml文件中应该只是映射颜色的名称一个RGBA值,而没有其它的。不要使用它为不同的按钮来定义RGBA值。

不要这样做

<resources><colorname="button_foreground">#FFFFFF</color><colorname="button_background">#2A91BD</color><colorname="comment_background_inactive">#5F5F5F</color><colorname="comment_background_active">#939393</color><colorname="comment_foreground">#FFFFFF</color><colorname="comment_foreground_important">#FF9D2F</color>
	...<colorname="comment_shadow">#323232</color>

使用这种格式,你会非常容易的开始重复定义RGBA值,这使如果需要改变基本色变的很复杂。同时,这些定义是跟一些环境关联起来的,如 button或者 comment, 应该放到一个按钮风格中,而不是在 color.xml文件中。

相反,这样做:

<resources><!--grayscale--><colorname="white">#FFFFFF</color><colorname="gray_light">#DBDBDB</color><colorname="gray">#939393</color><colorname="gray_dark">#5F5F5F</color><colorname="black">#323232</color><!--basic colors--><colorname="green">#27D34D</color><colorname="blue">#2A91BD</color><colorname="orange">#FF9D2F</color><colorname="red">#FF432F</color></resources>

向应用设计者那里要这个调色板,名称不需要跟"green", "blue", 等等相同。 "brand_primary", "brand_secondary", "brand_negative" 这样的名字也是完全可以接受的。 像这样规范的颜色很容易修改或重构,会使应用一共使用了多少种不同的颜色变得非常清晰。 通常一个具有审美价值的UI来说,减少使用颜色的种类是非常重要的。

像对待colors.xml一样对待dimens.xml文件与定义颜色调色板一样,你同时也应该定义一个空隙间隔和字体大小的“调色板”。 一个好的例子,如下所示:

<resources><!--font sizes--><dimenname="font_larger">22sp</dimen><dimenname="font_large">18sp</dimen><dimenname="font_normal">15sp</dimen><dimenname="font_small">12sp</dimen><!--typical spacing between two views--><dimenname="spacing_huge">40dp</dimen><dimenname="spacing_large">24dp</dimen><dimenname="spacing_normal">14dp</dimen><dimenname="spacing_small">10dp</dimen><dimenname="spacing_tiny">4dp</dimen><!--typical sizes of views--><dimenname="button_height_tall">60dp</dimen><dimenname="button_height_normal">40dp</dimen><dimenname="button_height_short">32dp</dimen></resources>

布局时在写 margins 和 paddings 时,你应该使用 spacing_****尺寸格式来布局,而不是像对待String字符串一样直接写值。 这样写会非常有感觉,会使组织和改变风格或布局是非常容易。

避免深层次的视图结构有时候为了摆放一个视图,你可能尝试添加另一个LinearLayout。你可能使用这种方法解决:

<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><RelativeLayout...><LinearLayout...><LinearLayout...><LinearLayout...></LinearLayout></LinearLayout></LinearLayout></RelativeLayout></LinearLayout>

即使你没有非常明确的在一个layout布局文件中这样使用,如果你在Java文件中从一个view inflate(这个inflate翻译不过去,大家理解就行) 到其他views当中,也是可能会发生的。

可能会导致一系列的问题。你可能会遇到性能问题,因为处理起需要处理一个复杂的UI树结构。 还可能会导致以下更严重的问题 StackOverflowError.

因此尽量保持你的视图tree:学习如何使用 RelativeLayout, 如何 optimize 你的布局和如何使用 <merge>标签.

小心关于WebViews的问题.如果你必须显示一个web视图, 比如说对于一个新闻文章,避免做客户端处理HTML的工作, 最好让后端工程师协助,让他返回一个 " " HTML。 当绑定WebViews到引用它的Activity,而不是绑定到ApplicationContext时。 WebViews 也能导致内存泄露。 当使用简单的文字或按钮时,避免使用WebView,这时使用TextView或Buttons更好。

测试框架

Android SDK的测试框架还处于初级阶段,特别是关于UI测试方面。Android Gradle 目前实现了一个叫 connectedAndroidTest的测试, 它 使用一个JUnit 为Android提供的扩展插件 extension of JUnit with helpers for Android.可以跑你生成的JUnit测试,

只当做单元测试时使用 Robolectric,views 不用它是一个最求提供"不连接设备的"为了加速开发的测试, 非常时候做 models 和 view models 的单元测试。 然而,使用Robolectric测试时不精确的,也不完全对UI测试。 当你对有关动画的UI元素、对话框等,测试时会有问题, 这主要是因为你是在 “在黑暗中工作”(在没有可控的界面情况下测试)

** Robotium使写UI测试非常简单。 ** 对于UI测试你不需 Robotium 跑与设备连接的测试。 但它可能会对你有益,是因为它有许多来帮助类的获得和分析视图,控制屏幕。 测试用例看起来像这样简单:

solo.sendKey(Solo.MENU);
solo.clickOnText("More");//searches for the first occurence of "More" and clicks on itsolo.clickOnText("Preferences");
solo.clickOnText("Edit File Extensions");Assert.assertTrue(solo.searchText("rtf"));

模拟器

如果你全职开发Android App,那么买一个 Genymotion emulatorlicense吧。 Genymotion 模拟器运行更快的秒帧的速度,比起典型的AVD模拟器。他有演示你APP的工具,高质量的模拟网络连接,GPS位置,等等。它同时还有理想的连接测试。 你若涉及适配使用很多不同的设备,买一个Genymotion 版权是比你买很多真设备便宜多的。

注意:Genymotion模拟器没有装载所有的Google服务,如Google Play Store和Maps。你也可能需 要测试Samsung指定的API,若这样的话你还是需要购买一个真实的Samsung设备。

混淆配置

ProGuard是一个在Android项目中广泛使用的压缩和混淆打包的源码的工具。

你是否使用ProGuard取决你项目的配置,当你构建一个release版本的apk时,通常你应该配置gradle文件。

buildTypes {
	debug {
		minifyEnabledfalse}
	release {
		signingConfig signingConfigs.release
		minifyEnabledtrueproguardFiles'proguard-rules.pro'}
}

为了决定哪些代码应该被保留,哪些代码应该被混淆,你不得不指定一个或多个实体类在你的代码中。 这些实体应该是指定的类包含main方法,applets,midlets,activities,等等。 Android framework 使用一个默认的配置文件,可以在 SDK_HOME/tools/proguard/proguard-android.txt目录下找到。自定义的工程指定的 project-specific 混淆规则,如在 my-project/app/proguard-rules.pro中定义, 会被添加到默认的配置中。

关于 ProGuard 一个普遍的问题,是看应用程序是否崩溃并报 ClassNotFoundException或者 NoSuchFieldException或类似的异常, 即使编译是没有警告并运行成功。 这意味着以下两种可能:

  1. ProGuard 已经移除了类,枚举,方法,成员变量或注解,考虑是否是必要的。
  2. ProGuard 混淆了类,枚举,成员变量的名称,但是这些名字又被拿原始名称使用了,比如通过Java的反射。

检查 app/build/outputs/proguard/release/usage.txt文件看有问题的对象是否被移除了。 检查 app/build/outputs/proguard/release/mapping.txt文件看有问题的对象是否被混淆了。

In order to prevent ProGuard from stripping awayneeded classes or class members, add a keepoptions to your proguard config: 以防 ProGuard 剥离需要的类和类成员,添加一个 keep选项在你的 proguard 配置文件中:

-keep class com.futurice.project.MyClass { *; }

防止 ProGuard 混淆一些类和成员,添加 keepnames:

-keepnames class com.futurice.project.MyClass { *; }

更多例子请参考 Proguard

在构建项目之初,发布一个版本来检查ProGuard规则是否正确的保持了重要的部分。 同时无论何时你添加了新的类库,做一个发布版本,同时apk在设备上跑起来测试一下。 不要等到你的app要发布 "1.0"版本了才做版本发布,那时候你可能会碰到好多意想不到的异常,需要一些时间去修复他们。

Tips每次发布新版本都要写 mapping.txt。每发布一个版本,如果用户遇到一个bug,同时提交了一个混淆过的堆栈跟踪。 通过保留 mapping.txt文件,来确定你可以调试的问题。

DexGuard如果你需要核心工具来优化,和专门混淆的发布代码,考虑使用 DexGuard, 一个商业软件,ProGuard 也是有他们团队开发的。 它会很容易将Dex文件分割,来解决65K个方法限制问题。

###数据存储

SharedPreferences

如果你只是需要持久化存储简单的标记位,并且你的应用运行在单一进程,那么SharedPreferences可能就满足了你的需求。它是一个非常好的选择。

这里有两个使你可能不使用SharedPreferences的原因:

  • Performance: Your data is complex or there is a lot of it
  • 性能问题:你的很多数据结构负责的数据需要存储。
  • Multiple processes accessing the data: You have widgets or remote services that run in their own processes and require synchronized data
  • 多线程访问数据:你有多个控件或者运行在各自线程上的远程的服务需要同步数据。

ContentProviders

如果SharedPreferences不足以满足你的需求,那么你可以使用平台标准的ContentProviders,它不仅快速,并且线程安全。

使用ContentProviders的唯一问题是建立他们需要大量的模板代码,并且少有高质量的教程。如果可以,我们可以通过使用第三方库Schematic,极大降低了冗余操作,去生成ContentProviders.

你可能仍然需要亲自写一些解析代码去从Sqlite读取数据对象,或者进行相反的操作。如果可以序列化数据对象,例如通过Gson,只持久化存储最终是字符串。通过这种方式虽然会降低性能,但是从另一个角度来讲,你不需要为每一个数据结构声明表结构。

使用ORM我们通常不推荐使用对象关系映射第三方库除非你有非常复杂的数据结构,并且你确定你真的需要它。他们通常比较复杂,并且需要时间去学习。如果你决定了在你的应用中使用ORM,你应该注意它是否是线程安全的,而对于目前大多数ORM解决方案都是非线程安全的。

使用StethoStetho 是一个Facebook 开源的Android调试工具,它是Chrome Developer Tools的扩展。通过它可以检测应用的网络情况。它也允许你可以检测应用的数据库,shared preferences。但是,你应该确保Stetho只有在Debug状态下得以开启,而不是在正式发布版本中。

使用LeakCanaryLeakCanary 是可以在应用运行中检测,定位内存泄露的Java库。使用它应是你开发应用过程中的一部分。更多详细的配置和使用情况请参照wiki。你只需要记得它在你的正式版本中你是不需要配置的。

致谢

感谢Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton 这些人和Futurice 开发者分享他们的Android开发经验。

License

Futurice OyCreative Commons Attribution 4.0 International (CC BY 4.0)

Translation

Translated to Chinese by andyiac

腾讯的数字经济报告给各省一个提醒 不要在数字化时代落后于竞争

$
0
0

4 月 20 日,在阿里巴巴集团总部所在的杭州这座城市,腾讯联合滴滴出行、美团点评、京东和携程等公司在中国互联网+数字经济峰会上发布了一年一度的《中国互联网+数字经济指数》,这份报告根据腾讯系公司的共享大数据为基础,是目前中国极少数的以「互联网+」为主题应用于公共事业和经济活动的数字化报告,基本上从某些程度反映了中国各个省份和地区互联网+的发展现状。

报告显示,2016 年全国数字经济总量已占据全国 GDP 总量的 30.61%,已成为国民经济的重要组成部分。无论是对新增就业的带动,还是对 GDP 的拉升,数字经济都表现出强劲的活力。不同区域数字经济的发展差异,也重构了中国数字经济新版图。「互联网+指数」为数字经济提供确实可参照标准,在政府、技术的推进下,未来数字经济将进一步发挥赋能作用,有效消弭地区经济鸿沟,助力精准扶贫。

不过这份报告最有价值的地方是它以指标的形式,精准刻画全国 31 个省、自治区和直辖市以及 351 个城市数字经济发展状况。其中,广东、北京、上海、浙江、江苏、福建、四川、山东、湖北、湖南位居「互联网+数字经济指数」榜单排名前十。

「互联网+指数报告」显示,互联网+数字经济指数每增长一个点,GDP 就能增长 1406.02 亿元。截止 2016 年年底,我国互联网+数字经济指数增加了 161.95 点,据此估算,2016 年全国数字济总量达到了 22.77 万亿元,占据全国 GDP 总量的 30.61%,数字经济对 GDP 的拉动效应明显。

当然,在带动就业增长上,数字经济表现更为突出,已然成为新的增长引擎。在促进各个相关产业发展、全方位拉动就业、降低地区整体失业率等方面,发展数字经济带来的正面效果明显。报告显示,互联网+数字指数每增加一点,城镇登记失业率大概下降 0.02%,由此可以估计数字经济发展使得全国 31 个省级行政单位城镇登记失业率平均下降大约 0.10%,2016 年全年带动 280.17 万新增就业人数,带来新增就业占比达到 21.32%。

数字经济重构了中国经济版图

随着地区数字经济差异化加深,重构了中国经济版图。从互联网+数字经济四大分指数的聚类分析,全国 351 个城市可以按照数字经济发展水平被划分为 5 个层次。北京、上海、广州、深圳构成数字经济一线城市,四个一线城市在总指数中占比为 29.0%;成都、杭州、南京等 14 市构成数字经济二线城市,在总指数中占比 19.17%;大连、宁波、青岛等 19 市构成数字经济三线城市,在总指数中占比 12.80%;保定、唐山、扬州等 65 市构成数字经济四线城市,在总指数中占比 16.83%;全国其他 249 个城市构成数字五线城市,在总指数中占比 22.20%。

报告还显示,大力发展数字经济,有助于消弭城市间的发展鸿沟,对精准扶贫发展方向也有参考性作用。报告指出,互联网+指数每增长一个点,内陆省份 GDP 上升幅度相较于东部沿海省份高 1619.48 亿元。这代表发展数字经济对于内陆地区经济体的提升和带动作用更为突出,有效缩小与东部沿海地区经济发展水平的差距,有效消弭城市间的发展鸿沟。报告还以西藏那曲为例,虽然其在金融、交通、医疗、教育等领域的「互联网+」指数排名都处于末端,但旅游行业表现突出,2016 年那曲旅游业数字产品同比增长 393.39%,在全国排名靠前。这显示那曲可以利用独特的自然风光和风土人情,有针对性地合理有序开放了旅游资源,实现精准扶贫。

互联网+的民生效应

报告还显示,在过去的 2016 年,智慧民生成为各城市数字经济增长的动力来源之一,政府、服务、用户全面触网,增量用户大爆发,智慧民生与产业、创业创新呈现高度相关。

尤其是通过连接政府,智慧民生加速向后线城市下沉扩散。数字显示,2016 年,四五线城市智慧民生领域的整体增速分别为 147.23%、150.40%,高于二三线城市。在增速前 100 位的城市中,四五线城市占据了 64 席。公安、医疗、人社、公积金等服务在四五线城市的可获得率相较于 2015 年大幅提高,满意度更是与一线城市相差无几。这也带动了数字经济在四五线城市的亮眼表现。

可以预见,随着移动互联网基础设施的广泛普及、互联网+不断深化落地、传统行业数字化与互联网化进程加速,中线、底线城市数字经济发展速度将进入高速增长期,而一线城市增长将适当放缓,这将有效弥补城市发展之间的鸿沟,对社会治理、消弭数字鸿沟起到支撑作用,为后发城市和地区带来了新的发展机遇。

[译] 使用 APK Analyzer 分析你的 APK

$
0
0

本文来自“天天P图攻城狮”公众号(ttpic_dev)

本文是对 《Analyze Your Build with APK Analyzer》 的翻译。

Android Studio 2.2包含了APK Analyzer,通过它我们能够直观地看到APK的组成。使用APK Analyzer不仅能够减少你花在debug上的时间,而且还能减少你的APK大小。使用APK Analyzer,你能够实现:

  • 查看APK中文件的绝对大小和相对大小。(译注:相对大小指的是该文件占整个APK大小的百分比)

  • 理解DEX文件的组成。(译注:能看到DEX文件中包含了哪些类)

  • 快速查看APK中文件的最终版本(比如AndroidManifest.xml)。(译注:AndroidManifest.xml、resources.arsc本来是二进制形式显示的,APK Analyzer能够解析并显示这些文件的内容)

  • 对比两个APK。(译注:新版APK和旧版APK之间文件大小的差异)

这里有3种方法访问APK Analyzer:

  • 拖拽APK到Android Studio的编辑窗口。

  • 切换到Project视图,并且双击APK文件。

  • 在菜单栏中选择Build > Analyzer APK,并且选择APK。

注意:当使用APK Analyzer分析debug的APK,请使用Build > Build APK生成的APK。点击Run将会生成Instant Run的APK。APK Analyzer不能分析通过Instant Run生成的APK,判断APK是否是Intant Run生成的可以通过看APK中是否有instant-run.zip文件。

译注:APK Analyzer是让我们更有效地制定减包方案的实用工具,它能够让我们发现使得APK变大的元凶。

查看文件和大小信息

APK就是一个zip包。APK Analyzer显示每个文件或目录作为一个实体,实体的层级说明了APK文件的结构。

如图1,APK Analyzer对每个实体显示了raw file size和download file size。Raw File Size表示实体在磁盘中解压后的大小,而Download Size表示实体在Google Play中压缩后的大小。% of Total Download Size表示实体的download size占APK总的download size的百分比。

译注:图1中,最上面的5.8M是指APK在磁盘中的大小,而4.9M是指从Google Play中下载的大小。

图1:在APK Analyzer中的文件大小

译注:通过查看各个目录或文件的大小,我们能发现APK中哪个区域的文件太大了,比如如果dex太大,我们是不是引入了重复功能的第三方库(比如Glide和Fresco),或者抽取出一个精简的第三方库(精简ffmpeg库);如果res目录太大,我们可以看看哪些图片过大了,需要通过tinypng压缩,或者用webp代替。

查看AndroidManifest.xml

如果项目中包含了多个AndroidManifest.xml或者包含提供manifest文件的library,在APK中他们将被合并为一个manifest文件。在APK中manifest文件是普通的二进制文件,但是在APK Analyzer中查看manifest文件,该文件是以XML形式显示的。这种显示形式让我们能理解应用中的任何变化。比如,你能看到library中的AndroidManifest.xml是怎么合入最后的AndroidManifest.xml的。

另外,这种显示形式提供了lint的能力,会在右上角显示警告、错误提示。图2显示了manifest文件的错误提示。

图2:manifest文件的一个错误

查看代码和资源实体

不同的构建任务会改变APK文件最后的实体。比如混淆压缩规则能改变你最后的代码和图片资源。在APK Analyzer中能够快速查看文件的最终版本:点击实体,就能在下面看到文件的内容,包含文字和图片实体的预览。

图3:最终图片资源的预览

APK Analyzer也能展示文本或二进制文件。比如点击resources.arsc能够让你看到针对配置特定的值(例如一个字符串资源的特定语言的翻译)。如图4,你能看到每个字符串资源的翻译。

图4:翻译的字符串资源预览

查看DEX文件

APK Analyzer的DEX文件浏览器让你能够快速了解DEX文件的信息。我们能看到类、包、总的引用和声明个数,这些信息能够帮助我们决定是否使用multi-dex或者移除依赖使得满足64K方法数限制。

图5展示了一个中等大小的APP(方法数接近64K)。每个包、类、方法都列有Defined Method和Referenced Method。Referenced Method列是DEX文件中引用的全部方法,它包含了你定义的方法、依赖的library、定义在标准Java和Android包中的方法。Defined Method列只包含了定义在DEX文件中方法,因此它是Referenced Method方法的子集。注意当你引入一个依赖,在依赖中定义的方法会包含在Defined Method和Referenced Method中。还要注意,混淆压缩也会改变DEX文件的内容。

图5:一个使用了multi-dex的应用

比较APK文件

APK Analyzer能比较两个不同APK中各个实体的大小。这对于我们了解为何你的APP相比上个版本变大了是很有用的。

在发布一个新版APK之前,在APK Analyzer中导入你即将发布的APK。在右上角点击Compare With,选择上一个版本的APK,点击OK。然后就会出现类似图6的对话框,允许你比较之间的差别。

图6显示了一个APP的debug和release包的差别,不同的编译类型会导致实体的不同。

译注:Old Size是先选择的APK,New Size是后选择的APK。

图6:debug和release的APK之间的差别



如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~

Leaf——美团点评分布式ID生成系统

$
0
0

背景

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需求;特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识。此时一个能够生成全局唯一ID的系统是非常必要的。概括下来,那业务系统对ID号的要求有哪些呢?

  1. 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。

上述123对应三类不同的场景,3和4需求还是互斥的,无法使用同一个方案满足。

同时除了对ID号码自身的要求,业务还对ID号生成系统的可用性要求极高,想象一下,如果ID生成系统瘫痪,整个美团点评支付、优惠券发券、骑手派单等关键动作都无法执行,这就会带来一场灾难。

由此总结下一个ID生成系统应该做到如下几点:

  1. 平均延迟和TP999延迟都要尽可能低;
  2. 可用性5个9;
  3. 高QPS。

常见方法介绍

UUID

UUID(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例: 550e8400-e29b-41d4-a716-446655440000,到目前为止业界一共有5种方式生成UUID,详情见IETF发布的UUID规范 A Universally Unique IDentifier (UUID) URN Namespace

优点:

  • 性能非常高:本地生成,没有网络消耗。

缺点:

  • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
  • ID作为主键时在特定的环境会存在一些问题,比如做DB主键的场景下,UUID就非常不适用:

    ① MySQL官方有明确的建议主键要尽量越短越好[4],36个字符长度的UUID不符合要求。

    All indexes other than the clustered index are known as secondary indexes. In InnoDB, each record in a secondary index contains the primary key columns for the row, as well as the columns specified for the secondary index. InnoDB uses this primary key value to search for the row in the clustered index. If the primary key is long, the secondary indexes use more space, so it is advantageous to have a short primary key.

    ② 对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。

类snowflake方案

这种方案大致来说是一种以划分命名空间(UUID也算,由于比较常见,所以单独分析)来生成ID的一种算法,这种方案把64-bit分别划分成多段,分开来标示机器、时间等,比如在snowflake中的64-bit分别表示如下图(图片来自网络)所示:

image

41-bit的时间可以表示(1L<<41)/(1000L*3600*24*365)=69年的时间,10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求,还可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器,可以根据自身需求定义。12个自增序列号可以表示2^12个ID,理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。

这种方式的优缺点是:

优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

应用举例Mongdb objectID

MongoDB官方文档 ObjectID可以算作是和snowflake类似方法,通过“时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。

数据库生成

以MySQL举例,利用给字段设置 auto_increment_incrementauto_increment_offset来保证ID自增,每次业务使用下列SQL读写MySQL得到ID号。

begin;
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
commit;

image

这种方案的优缺点如下:

优点:

  • 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
  • ID号单调自增,可以实现一些对ID有特殊要求的业务。

缺点:

  • 强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
  • ID发号性能瓶颈限制在单台MySQL的读写性能。

对于MySQL性能问题,可用如下方案解决:在分布式系统中我们可以多部署几台机器,每台机器设置不同的初始值,且步长和机器数相等。比如有两台机器。设置步长step为2,TicketServer1的初始值为1(1,3,5,7,9,11...)、TicketServer2的初始值为2(2,4,6,8,10...)。这是Flickr团队在2010年撰文介绍的一种主键生成策略( Ticket Servers: Distributed Unique Primary Keys on the Cheap )。如下所示,为了实现上述方案分别设置两台机器对应的参数,TicketServer1从1开始发号,TicketServer2从2开始发号,两台机器每次发号之后都递增2。

TicketServer1:
auto-increment-increment = 2
auto-increment-offset = 1

TicketServer2:
auto-increment-increment = 2
auto-increment-offset = 2

假设我们要部署N台机器,步长需设置为N,每台的初始值依次为0,1,2...N-1那么整个架构就变成了如下图所示:

image

这种架构貌似能够满足性能的需求,但有以下几个缺点:

  • 系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做?假设现在只有一台机器发号是1,2,3,4,5(步长是1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,比如14(假设在扩容时间之内第一台不可能发到14),同时设置步长为2,那么这台机器下发的号码都是14以后的偶数。然后摘掉第一台,把ID值保留为奇数,比如7,然后修改第一台的步长为2。让它符合我们定义的号段标准,对于这个例子来说就是让第一台以后只能产生奇数。扩容方案看起来复杂吗?貌似还好,现在想象一下如果我们线上有100台机器,这个时候要扩容该怎么做?简直是噩梦。所以系统水平扩展方案复杂难以实现。
  • ID没有了单调递增的特性,只能趋势递增,这个缺点对于一般业务需求不是很重要,可以容忍。
  • 数据库压力还是很大,每次获取ID都得读写一次数据库,只能靠堆机器来提高性能。

Leaf方案实现

Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话:

There are no two identical leaves in the world

"世界上没有两片相同的树叶"

综合对比上述几种方案,每种方案都不完全符合我们的要求。所以Leaf分别在上述第二种和第三种方案上做了相应的优化,实现了Leaf-segment和Leaf-snowflake方案。

Leaf-segment数据库方案

第一种Leaf-segment方案,在使用数据库的方案上,做了如下改变:

  • 原方案每次获取ID都得读写一次数据库,造成数据库压力大。改为利用proxy server批量获取,每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。
  • 各个业务不同的发号需求用biz_tag字段来区分,每个biz-tag的ID获取相互隔离,互不影响。如果以后有性能需求需要对数据库扩容,不需要上述描述的复杂的扩容操作,只需要对biz_tag分库分表就行。

数据库表设计如下:

+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field       | Type         | Null | Key | Default           | Extra                       |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag     | varchar(128) | NO   | PRI |                   |                             |
| max_id      | bigint(20)   | NO   |     | 1                 |                             |
| step        | int(11)      | NO   |     | NULL              |                             |
| desc        | varchar(256) | YES  |     | NULL              |                             |
| update_time | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+

重要字段说明:biz_tag用来区分业务,max_id表示该biz_tag目前所被分配的ID号段的最大值,step表示每次分配的号段长度。原来获取ID每次都需要写数据库,现在只需要把step设置得足够大,比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step,大致架构如下图所示:

image

test_tag在第一台Leaf机器上是1~1000的号段,当这个号段用完时,会去加载另一个长度为step=1000的号段,假设另外两台号段都没有更新,这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000,更新号段的SQL语句如下:

Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit

这种模式有以下优缺点:

优点:

  • Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
  • ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
  • 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
  • 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。

缺点:

  • ID号码不够随机,能够泄露发号数量的信息,不太安全。
  • TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
  • DB宕机会造成整个系统不可用。

双buffer优化

对于第二个缺点,Leaf-segment做了一些优化,简单的说就是:

Leaf 取号段的时机是在号段消耗完的时候进行的,也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间,并且在这期间进来的请求也会因为DB号段没有取回来,导致线程阻塞。如果请求DB的网络和DB的性能稳定,这种情况对系统的影响是不大的,但是假如取DB的时候网络发生抖动,或者DB发生慢查询就会导致整个系统的响应时间变慢。

为此,我们希望DB取号段的过程能够做到无阻塞,不需要在DB取号段的时候阻塞请求线程,即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的TP999指标。详细实现如下图所示:

image

采用双buffer的方式,Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时,如果下一个号段未更新,则另启一个更新线程去更新下一个号段。当前号段全部下发完后,如果下个号段准备好了则切换到下个号段为当前segment接着下发,循环往复。

  • 每个biz-tag都有消费速度监控,通常推荐segment长度设置为服务高峰期发号QPS的600倍(10分钟),这样即使DB宕机,Leaf仍能持续发号10-20分钟不受影响。

  • 每次请求来临时都会判断下个号段的状态,从而更新此号段,所以偶尔的网络抖动不会影响下个号段的更新。

Leaf高可用容灾

对于第三点“DB可用性”问题,我们目前采用一主两从的方式,同时分机房部署,Master和Slave之间采用 半同步方式[5]同步数据。同时使用公司Atlas数据库中间件(已开源,改名为 DBProxy)做主从切换。当然这种方案在一些情况会退化成异步模式,甚至在 非常极端情况下仍然会造成数据不一致的情况,但是出现的概率非常小。如果你的系统要保证100%的数据强一致,可以选择使用“类Paxos算法”实现的强一致MySQL方案,如MySQL 5.7前段时间刚刚GA的 MySQL Group Replication。但是运维成本和精力都会相应的增加,根据实际情况选型即可。

image

同时Leaf服务分IDC部署,内部的服务化框架是“MTthrift RPC”。服务调用的时候,根据负载均衡算法会优先调用同机房的Leaf服务。在该IDC内Leaf服务不可用的时候才会选择其他机房的Leaf服务。同时服务治理平台OCTO还提供了针对服务的过载保护、一键截流、动态流量分配等对服务的保护措施。

Leaf-snowflake方案

Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景,比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。面对这一问题,我们提供了 Leaf-snowflake方案。

image

Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的:

  1. 启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
  2. 如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
  3. 如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。

image

弱依赖ZooKeeper

除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,恰好机器出现问题需要重启时,能保证服务能够正常启动。这样做到了对三方组件的弱依赖。一定程度上提高了SLA

解决时钟问题

因为这种方案依赖时间,如果机器的时钟发生了回拨,那么就会有可能生成重复的ID号,需要解决时钟回退的问题。

image

参见上图整个启动流程图,服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点:

  1. 若写过,则用自身系统时间与leaf_forever/${self}节点记录时间做比较,若小于leaf_forever/${self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警。
  2. 若未写过,证明是新服务节点,直接创建持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize。
  3. 若abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约。
  4. 否则认为本机系统时间发生大步长偏移,启动失败并报警。
  5. 每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。

由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。 或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警,如下:

 //发生了回拨,此刻时间小于上次发号时间
 if (timestamp < lastTimestamp) {

            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    //时间偏差大小小于5ms,则等待两倍时间
                    wait(offset << 1);//wait
                    timestamp = timeGen();
                    if (timestamp < lastTimestamp) {
                       //还是小于,抛异常并上报
                        throwClockBackwardsEx(timestamp);
                      }    
                } catch (InterruptedException e) {  
                   throw  e;
                }
            } else {
                //throw
                throwClockBackwardsEx(timestamp);
            }
        }
 //分配ID

从上线情况来看,在2017年闰秒出现那一次出现过部分机器回拨,由于Leaf-snowflake的策略保证,成功避免了对业务造成的影响。

Leaf现状

Leaf在美团点评公司内部服务包含金融、支付交易、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。目前Leaf的性能在4C8G的机器上QPS能压测到近5w/s,TP999 1ms,已经能够满足大部分的业务的需求。每天提供亿数量级的调用量,作为公司内部公共的基础技术设施,必须保证高SLA和高性能的服务,我们目前还仅仅达到了及格线,还有很多提高的空间。

作者简介

照东,美团点评基础架构团队成员,主要参与 美团大型分布式链路跟踪系统Mtrace和美团点评分布式ID生成系统Leaf的开发工作。曾就职于阿里巴巴,2016年7月加入美团。

最后做一个招聘广告:如果你对大规模分布式环境下的服务治理、分布式会话链追踪等系统感兴趣,诚挚欢迎投递简历至:zhangjinlu#meituan.com。

参考资料

  1. 施瓦茨. 高性能MySQL[M]. 电子工业出版社, 2010:162-171.
  2. 维基百科:UUID.
  3. snowflake.
  4. MySQL: Clustered and Secondary Indexes.
  5. 半同步复制 Semisynchronous Replication.

外卖订单量预测异常报警模型实践

$
0
0

一、前言

外卖业务的快速发展对系统稳定性提出了更高的要求,每一次订单量大盘的异常波动,都需要做出及时的应对,以保证系统的整体稳定性。如何做出较为准确的波动预警,显得尤为重要。

从时间上看,外卖订单量时间序列有两个明显的特征(如下图所示):

  • 周期性。每天订单量的变化趋势都大致相同,午高峰和晚高峰订单量集中。
  • 实时性。当天的订单量可能会受天气等因素影响,呈现整体的上涨或下降。

订单量波动预警,初期外卖订单中心使用的是当前时刻和前一时刻订单量比较,超过一定阈值就报警的方式,误报率和漏报率都比较大。后期将业务数据上传到美团点评的服务治理平台,使用该平台下的基线报警模型进行监控报警。基线数据模型考虑到了订单量时间序列的周期性特征,但是忽略了实时性特征,在实际使用中误报率依然很高,大量的误报漏报导致RD对于报警已经麻木,出现问题时不能及时响应,因此,急需一种新的异常检测模型,提高报警的准确率。

@图2.1 异常检测模型 | center | 300x0

图1.1 外卖订单大盘趋势图

二、异常检测的定义

异常,意为“异于正常”。异常检测,就是从一组数据中寻找那些和期望数据不同的数据。监控数据都是和时间相关的,每一个监控指标只有和时间组合一起才有其具体的含义。按照时间顺序,将监控指标组成一个序列,我们就得到了监控指标的时间序列。

基于预测的异常检测模型如下图所示, xt是真实数据,通过预测器得到预测数据,然后 xtpt分别作为比较器的输入,最终得到输出 ytyt是一个二元值,可以用+1(+1表示输入数据正常),-1(-1表示输入数据异常)表示。

@图2.1 异常检测模型 | center | 0x0

图2.1 异常检测模型

异常检测主要有两种策略:

  • 异常驱动的异常检测(敏感性):宁愿误报,也不能错过任何一个异常,这适用于非常重要的检测。简单概括,就是“宁可错杀一千,不能放过一个”。
  • 预算驱动的异常检测(准确性):这种策略的异常检测,从字面理解就是只有定量的一些预算去处理这些报警,那么只能当一定是某种问题时,才能将报警发送出来。

这两种策略不可兼容的。对于检测模型的改善,可以从两个方面入手,一是预测器的优化,二是比较器的优化。我们从这两个方面描述模型的改善。

三、预测器设计

预测器,就是用一批历史数据预测当前的数据。使用的历史数据集大小,以及使用的预测算法都会影响最终的预测效果。

外卖订单量具有明显的周期性,同时相邻时刻的订单量数据也有很强的相关性,我们的目标,就是使用上面说的相关数据预测出当前的订单量。下面,我们分析几种常用的预测器实现。

3.1 同比环比预测器

同比环比是比较常用的异常检测方式,它是将当前时刻数据和前一时刻数据(环比)或者前一天同一时刻数据(同比)比较,超过一定阈值即认为该点异常。如果用图2.1模型来表示,那么预测器就可以表示为用当前时刻前一时刻或者前一天同一时刻数据作为当前时刻的预测数据。

如果将不同日期、时刻的监控数据以矩阵方式存储,每一行表示一天内不同时刻的监控数据,每一列表示同一时刻不同日期的监控数据,那么存储矩阵如下图所示:

@图3.1 同比环比 | center | 0x0
图3.1 同比环比

假如需要预测图中黄色数据,那么环比使用图中的蓝色数据作为预测黄点的源数据,同比使用图中红色数据作为预测黄点的源数据。

3.2 基线预测器

同比环比使用历史上的单点数据来预测当前数据,误差比较大。 t时刻的监控数据,与
t-1, t-2,...时刻的监控数据存在相关性。同时,与 t- k, t-2 k,...时刻的数据也存在相关性( k为周期),如果能利用上这些相关数据对 t时刻进行预测,预测结果的误差将会更小。

比较常用的方式是对历史数据求平均,然后过滤噪声,可以得到一个平滑的曲线(基线),使用基线数据来预测当前时刻的数据。该方法预测 t时刻数据(图中黄色数据)使用到的历史数据如下图所示(图中红色数据):

@图3.2 历史数据求平均 | center
图3.2 历史数据求平均

基线数据预测器广泛应用在业务大盘监控中,预测效果如图3.3所示。从图中可以看出,基线比较平滑,在低峰期预测效果比较好,但是在外卖的午高峰和晚高峰预测误差比较大。

@图3.3  octo上的基线数据预测 | center| 0x0
图3.3 基线数据预测

3.3 Holt-Winters预测器

同比环比预测到基线数据预测,使用的相关数据变多,预测的效果也较好。但是基线数据预测器只使用了周期相关的历史数据,没有使用上同周期相邻时刻的历史数据,相邻时刻的历史数据对于当前时刻的预测影响是比较大的。如外卖订单量,某天天气不好,很多用户不愿意出门,那么当天的外卖的订单量就会呈现整体的上涨,这种整体上涨趋势只能从同一周期相邻时刻的历史数据中预测出来。如图3.4所示,预测图中黄色数据,如果使用上图中所有的红色数据,那么预测效果会更好。

@图3.4 | center
图3.4 Holt-Winters预测

本文使用了Holt-Winters来实现这一目标。

Holt-Winters是三次指数滑动平均算法,它将时间序列数据分为三部分:残差数据 a( t),趋势性数据 b( t),季节性数据 s( t)。使用Holt-Winters预测 t时刻数据,需要 t时刻前包含多个周期的历史数据。相关链接: Exponential smoothingHolt-Winters seasonal method

各部分的迭代计算公式(周期为 k):

如图3.5所示,(a)显示了某一段时间内外卖订单的原始提单监控数据(分钟统计量,周期为1天),图(b)显示了其Holt-Winters的分解图(四幅图分别对应原始数据、残差数据分量、趋势数据分量、周期数据分量)。将订单量时间序列分解为残差数据 a( t),趋势数据 b( t),周期数据 s( t)后,就可以使用下面的公式预测未来不同时刻时刻的订单量,其中 h表示未来时刻距离当前时刻的跨度。

外卖订单量,是按分钟统计的离散时间序列,所以如果需要预测下一分钟的订单量,令 h=1。

@(a) | center | 0x0

(a)

@(b) | center | 0x0
(b)

图3.5 某一段时间的外卖提单数据和Holt-Winters算法分解图

3.4 外卖报警模型中的预测器

在外卖订单量异常检测中,使用Holt-Winters预测器实时预测下一分钟订单量,每次需要至少5天以上的订单量数据才能有较好的预测效果,数据量要求比较大。

在实际的异常检测模型中,我们对Holt-Winters预测器进行了简化。预测器的趋势数据表示的是时间序列的总体变化趋势,如果以天为周期看待外卖的订单量时间序列,是没有明显的趋势性的,图3.5(b)的分解图也证明了这一点。因此,我们可以去掉其中的趋势数据部分。

各部分的迭代公式简化为(3-1):

预测值:

h越大,预测值 Yhat[ t+ h] 的误差也就越大。实时的订单流监控,令 h=1,每当有新的监控数据时,更新输入序列,然后预测下一分钟数据。

Holt-Winters每一次预测都需要大量的输入数据序列。从上面模型的简化公式可以看出,对残差数据 a( t)的预测是对序列( a( t- m), a( t- m+1),... a( t-2), a( t-1))的一次指数滑动平均,对周期数据 s( t)的预测是对序列( s( t- mk) , s( t-( m-1) k),... s( t- k))的一次滑动平均,大量的输入数据是用于周期数据 s( t)的计算。

a( t)和 s( t)是互相关联的迭代计算过程,如果从周期性角度看公式 (3-1),可以发现:计算当前周期内的 a( t)时,使用的是上一周期计算出来的 s( t- k),当前周期计算出的 s( t)是用于下一周期 a( t+ k)的计算。为了将算法应用到线上的实时预测,我们可以将Holt-Winters算法拆分为两个独立的计算过程:

  1. 定时任务计算序列的周期数 s( t)。
  2. 对残差序列做实时预测。

下面就分别从这两个步骤介绍外卖报警模型中的预测器实现。

3.4.1 计算序列的周期性数据

时间序列的周期性数据不需要实时计算,按周期性更新即可,如外卖订单大盘监控, s( t)只需要每天更新一次即可。对于 s( t)的计算,可以有多种方法,可以使用上面提到的Holt-Winters按公式(3-1)计算出时间序列的周期性数据(如图3.5b所示),或直接使用前一天的监控数据作为当天的周期数据(这两种方式都需要对输入序列进行预处理,保证算法的输入序列不含有异常数据)。也可以用上面3.2节提到的,将历史数据做平均求出基线作为序列的周期性数据。

目前外卖订单中心报警模型采用的是Holt-Winters计算周期数据的方式。在将该模型推广到外卖其他业务线监控时,使用了计算基线数据作为周期数据的方式,这里简单对比一下两种方式的优劣。

1. 使用Holt-Winters算法计算周期数据

  • 优点:如果序列中含有周期性的陡增陡降点,Holt-Winters计算出的周期数据中会保留这些陡增陡降趋势,因此可以准确的预测出这些趋势,不会产生误报。比如外卖订单的提单数据,在每天的某个时刻都有一个定期陡降,使用该方式可以正确的预测出下降的趋势。如图3.6所示,蓝色线是真实数据,棕色线是预测数据,在该时刻,棕色线准确的预测出了下降点。
  • 缺点:需要对输入数据进行预处理,去除异常数据。如果输入序列中含有异常数据,使用Holt-Winters时可能会把这些异常数据计算到周期数据中,影响下一周期的预测从而产生误报(Holt-Winters理论上也只是滑动平均的过程,因此如果输入数据中含有比较大的异常数据时,存在这种可能性,实际应用中订单的报警模型也出现过这种误报)。

@图3.6 holtwinters周期数据预测 | center | 0x0

图3.6 Holt-Winters周期数据预测

2. 历史数据平均求基线

  • 优点:计算出的周期数据比较平滑,不需要对输入序列进行预处理,计算过程中可以自动屏蔽掉异常数据的影响,计算过程简单,如图3.3所示的基线数据。
  • 缺点:周期数据比较平滑,不会出现陡增陡降点,因此对于周期性出现的陡增陡降不能很好的预测,出现误报。比如外卖活动的大盘(如图3.7所示,红线是真实数据,黑线是预测数据),提前下单优惠在每天某个时刻会出现周期性陡降,使用该方式会出现误报。

@图3.7 基线周期数据预测 | center | 500x0

图3.7 基线周期数据预测

两种求周期数据的方式各有优劣,可以根据各自的监控数据特点选择合适的计算方式。如果监控数据中含有大量的周期性的陡增陡降点,那么推荐使用方式1,可以避免在这些时间点的误报。如果监控数据比较平滑,陡增陡降点很少,那么推荐方式2,计算简单的同时,也能避免因输入数据预处理不好而造成的意料之外的误报。

3.4.2 残差数据实时预测

计算出周期数据后,下一个目标就是对残差数据的预测。使用下面的公式,实际监控数据与周期数据相减得到残差数据,对残差数据做一次滑动平均,预测出下一刻的残差,将该时刻的残差、周期数据相加即可得到该时刻的预测数据。残差序列的长度设为60,即可以得到比较准确的预测效果。

对于实时预测,使用的是当天的周期数据和前60分钟数据。最终的预测结果如图3.8(a)(b)所示,其中蓝色线是真实数据,红色线是预测数据。

@ 图3.8a) | center | 500x0
(a)

@ 图3.8b | center | 500x0

(b)

图3.8 预测结果曲线

四、比较器设计

预测器预测出当前时刻订单量的预测值后,还需要与真实值比较来判断当前时刻订单量是否异常。一般的比较器都是通过阈值法,比如实际值超过预测值的一定比例就认为该点出现异常,进行报警。这种方式错误率比较大。在订单模型的报警检测中没有使用这种方式,而是使用了两个串联的Filter(如图4.1所示),只有当两个Fliter都认为该点异常时,才进行报警,下面简单介绍一下两个Filter的实现。

@图4.1 比较器模型 | center | 500x0

图4.1 比较器模型
  • 离散度Filter:根据预测误差曲线离散程度过滤出可能的异常点。一个序列的方差表示该序列离散的程度,方差越大,表明该序列波动越大。如果一个预测误差序列方差比较大,那么我们认为预测误差的报警阈值相对大一些才比较合理。离散度Filter利用了这一特性,取连续15分钟的预测误差序列,分为首尾两个序列(e1,e2),如果两个序列的均值差大于e1序列方差的某个倍数,我们就认为该点可能是异常点。
  • 阈值Filter:根据误差绝对值是否超过某个阈值过滤出可能的异常点。利用离散度Filter进行过滤时,报警阈值随着误差序列波动程度变大而变大,但是在输入数据比较小时,误差序列方差比较小,报警阈值也很小,容易出现误报。所以设计了根据误差绝对值进行过滤的阈值Filter。阈值Filter设计了一个分段阈值函数 y= f( x),对于实际值 x和预测值 p,只有当| x- p|> f( x)时报警。实际使用中,可以寻找一个对数函数替换分段阈值函数,更易于参数调优。

@图4.2 分段阈值filter | center | 500x0

图4.2 分段阈值Filter

五、报警模型最终效果

最终的外卖订单异常报警模型结构图如图5.1所示,每天会有定时Job从ETL中统计出最近10天的历史订单量,经过预处理模块,去除异常数据,经过周期数据计算模块得到周期性数据。对当前时刻预测时,取60分钟的真实数据和周期性数据,经过实时预测模块,预测出当前订单量。将连续15分钟的预测值和真实值通过比较器,判断当前时刻是否异常。

@图4.2 分段阈值filter | center | 500x0

图5.1 报警模型结构图

新的报警模型上线后,外卖订单量的异常检测的漏报率和误报率都有显著的提升,上线半年以来,对于每一次的异常都能准确的检测出来,漏报率近乎为0。误报率在通常情况下限制在了每周0~3次误报。

报警模型目前应用在外卖订单量的异常检测中,同时推广到了外卖业务的其他各种大盘监控中,取得了不错的效果。在报警模型上线后,我们发现并解决了一些系统隐患点,如:

  • 点评侧外卖提单量在每天定时有一个下降尖刺,经过排查是因为客户端冷启动短时间内大量的请求,导致SLB性能达到瓶颈,从而导致接口成功率下降。
  • 点评侧外卖订单取消量经常会有尖刺,经过排查发现是由于在支付时,需要进行跨机房的账号转换,专线网络抖动时造成接口超时。
  • 外卖订单量在每天某些时刻都有陡降趋势,经过排查,是因为这些点大量商家开始休息导致的。

六、总结

将机器学习中的预测算法运用到外卖订单的异常检测中,极大的提高了异常检测的准确性和敏感性,提升了系统稳定运维的效率。该报警模型也有很广泛的应用场景,美团点评的各个业务线的监控数据,绝大多数都是含有明显周期性的时间序列,本文提出的模型都能运用到这些监控数据的异常检测中。

当然,模型还有进一步完善的空间,如:

  • 历史数据的预处理优化。在进行周期数据计算时,对于输入序列的预处理,如果能够排除绝大部分的异常数据,那么最终检测的误报率将会进一步的降低。
  • 在不会产生持续误报的情况下替换有异常的实时数据。对于当前数据的预测,利用的都是前60分钟的真实数据,但是这些数据可能本身就存在异常数据,那么就存在一种情况,当出现异常时,真实数据开始下跌,预测数据紧接着也会下跌(如图3.8b所示)。这种情况有时候可能满足需求(比如只在异常开始的时候进行报警,异常持续时间内不再报警,防止报警太多造成的信息轰炸),有时候可能不满足需求(比如要求预测数据不跟随异常变化而变化,这种情况可以应用在故障期间的损失统计中)。如果需要预测值不随异常变化而变化,一种可能的方法是,当检测到当前数据是异常数据时,将预测数据替换当前的真实数据,作为下一时刻预测器的输入,这样可以防止异常数据对于下一时刻预测值的影响。

七、作者简介

东杰,2015年校招进入美团点评,目前负责外卖订单活动中心的支付系统以及订单活动SRE相关工作,是订单活动中心对外需求主要接口人之一。

最后来个硬广告:美团外卖订单活动中心长期招聘Java高级开发工程师,有兴趣同学可以发送简历到:heshi#meituan.com。

页面架构HTML+CSS ●▽● 各种布局各种实现

$
0
0

CSS Reset

1.作用

(1)清除浏览器默认样式
(2)全局样式定义

2.特别注意

(1)项目开发初期就定义好
(2) reset.css在引入的时候一定要放在第一位
(3)不同的产品 reset.css不一样

3.table合并边框间距

table {
  border-collapse: collapse; // 合并边框
  border-spacing: 0; //边框间距。当 `border-collapse` 值为 `seperate` 时生效

}

4.一个并不完整也并不通用的reset.css样例

    html,body,h1,h2,h3,h4,h5,h6,div,dl,dt,dd,ul,ol,li,p,blockquote,pre,hr,figure,table,caption,th,td,form,fieldset,legend,input,button,textarea,menu{margin:0;padding:0;}
    header,footer,section,article,aside,nav,hgroup,address,figure,figcaption,menu,details{display:block;}
    table{border-collapse:collapse;border-spacing:0;}
    caption,th{text-align:left;font-weight:normal;}
    html,body,fieldset,img,iframe,abbr{border:0;}
    i,cite,em,var,address,dfn{font-style:normal;}
    [hidefocus],summary{outline:0;}
    li{list-style:none;}
    h1,h2,h3,h4,h5,h6,small{font-size:100%;}
    sup,sub{font-size:83%;}
    pre,code,kbd,samp{font-family:inherit;}
    q:before,q:after{content:none;}
    textarea{overflow:auto;resize:none;}
    label,summary{cursor:default;}
    a,button{cursor:pointer;}
    h1,h2,h3,h4,h5,h6,em,strong,b{font-weight:normal;}
    del,ins,u,s,a,a:hover{text-decoration:none;}
    body,textarea,input,button,select,keygen,legend{font:12px/1.14 arial,simsun;color:#333;outline:0;}
    body{background:#fff;}
    a,a:hover{color:#333;}

布局解决方案

居中布局

1.水平居中

父元素和子元素宽度未知。

<div class="parent"><div class="child">child</div></div>

要达到的效果是这样:
图片描述

方法一:flex + justify-content

主要代码:

.parent { 
  display: flex;
  justify-content: center;
}

没啥好解释,直接看 神奇的flex实现栗子吧 (~ ̄▽ ̄)~

方法二:absolute + transform

主要代码:

.parent { position: relative; }
.child { 
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
}

原理是: left: 50%;在子元素的左侧添加了一段距离,这段距离是父元素宽度的50%,接着因为translateX(50%) 设置百分比时的参照物是自身宽度,所以向左偏移了自身宽度的50%,就居中啦 ╮(‵▽′)╭

动动小手看看栗子

方法三:inline-block + text-align

主要代码:

.parent { text-align: center; }
.child { display: inline-block; }

这种方法有一个问题是: parent设置了 text-align: center;后, 因为这个属性可继承,会导致 child中的文字也会居中,而这个效果是我们未必需要的,所以我们很多时候需要在 .child中加一句 text-align: left;

自己看看栗子

方法四:table + margin

主要代码:

.child { display: table; margin: 0 auto; }

table的特点:宽度为内容宽度 的 块状元素,所以也可以用 margin: 0 auto;居中。

优点:只设置子元素样式就可以了,不需关心父元素。

看看栗子

不喜欢这第四个方案,table是辣么有语义的一个样式,为什么随便把人家变成table ( ̄. ̄)

2.垂直居中

父元素和子元素高度未知。

意欲达到的效果:

图片描述

方法一:flex+ align-items

.parent {
  display: flex;
  align-items: center;
}

水平居中的方法一

栗子

方法二:absolute + transform

.parent { position: relative; }
.child {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

水平居中的方法二

栗子

方法三:table-cell + vertical-align

.parent {
  display: table-cell;
  vertical-align: middle;
}

vertical-align 可以作用在 inline元素, inline-table元素,以及 table-cell元素上。

栗子

3.水平垂直居中

父元素和子元素宽高都未知。

图片描述

方法一:flex + justify-content + align-items

.parent {
  display: flex;
  justify-content: center;
  align-items: center;
}

综合了 水平居中垂直居中的方法一

栗子

方法二: absolute + transform

.parent { position: relative; }
.child {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

综合了 水平居中垂直居中的方法二

栗子

方法三:[inline-block + text-align] + [table-cell + vertical-align]

.parent {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}
.child {
  display: inline-block;
}

栗子

多列布局

1.一列定宽 + 一列自适应

图片描述

<div class="parent"><div class="left"><p>left</p></div><div class="right"><p>right</p><p>right</p></div></div>

方法1:float + margin

.left {float: left; width: 100px;}
.right { margin-left: 120px;} //有20px是间距

方法2:(对方法一的改进)float + margin + (fix)

因为方法1在低版本浏览器有兼容性问题,所以改进一下。

// 首先在right外面加了right-fix这个div
<div class="parent"><div class="left"><p>left</p></div><div class="right-fix"> <div class="right"><p>right</p><p>right</p></div></div></div>

CSS改动:

STEP1:

// .left 和 .right 设置暂时不变
.right-fix {float: right; width: 100%;}

效果为:(注意:我们把right-fix设置为白色背景,只是为了方便观察。)

图片描述

STEP2:

可以看到,由于right-fix宽度为100%,所以跑到了left下面一行。想要回到同一行,需要给right-fix设置一个负的margin-left值-100px。

.right-fix { margin-left: -100px; }

关于为什么设置了 margin-left: 100px;就可以使得回到同一行呢?是因为设置了负的margin-left值之后,浏览器计算right-fix元素的宽度后,会加上-100px,也就是减掉100px,这也就是left的宽度,所以left 与 right-fix 加起来没有超过整行的宽度。
想要进一步了解负的margin值可以参考这篇文章: CSS布局奇淫巧计之-强大的负边距

效果如图:

图片描述

STEP3:

不幸的是,因为html文档中right-fix处于left后面,所以left被right-fix遮住了,实际应用中right-fix虽然没有背景色,但是我们还是不会希望它覆盖在left上面。

所以,我们需要提高 left 的层级。如何提高呢?由于设置了position: relative;的元素层级要高于普通元素,所以加上这样一条:

.left{ position: relative; }

具体可以参考张鑫旭写的一篇讲解 position:relative;很详细的文章: CSS 相对/绝对(relative/absolute)定位系列(四)

最终达到我们要的效果:

图片描述

到jsfiddle中自己试试去

方法3:float + overflow

.left{
  width: 100px;
  margin-right: 20px;
}
.right {
  overflow: hidden;
}

原理是:设置了overflow:hidden; 之后,会触发BFC模式,而BFC模式内部的布局不受外部影响,所以不会受浮动影响,不会围绕left而是跑到left右边去了。

方法4:table

.parent{
  display: table;
  width: 100%;
  table-layout: fixed; //加速table渲染,实现了布局优先
}
.left, .right {
  display: table-cell;
}
.left {
  width: 100px;
  padding-right: 20px;//因为table-cell不能设margin,所以设置padding来加间距
}

根据table的特性,left设置了100px后,right就占了剩余宽度。

方法5:flex

.parent{ display: flex; }
.left{ width: 100px; margin-right: 20px; }
.right{ flex: 1; }

So easy.

2.多列定宽 + 一列自适应

再加一列定宽就行啦 o(≧v≦)o

3.不定宽 + 一列自适应

图片描述

不定宽意思是:
1.可以随意更改宽度:比如改为100px,200px,同时不需要更改其他样式也可以做到两列自适应布局。
2.或不设置宽度而是由里面子元素的宽度决定。

以下方法对应 [一列定宽+一列自适应] 中的方法

方法1: float + margin ?

不好意思,做不到。

方法2: float + margin +(fix) ?

不好意思,也做不到。

方法3: float + overflow ?

阔以! right的样式没有依赖于width的宽度。代码量也少,很棒棒哦!

方法4:table

阔以! right的样式没有依赖于width的宽度,即不关心width的宽度。

方法5:flex

强大的flex当然可以~(傲娇脸 )

4.两列不定宽 + 一列自适应

没错,跟你想的一样,加一列不定宽的就行了,样式都一样 ㄟ( ▔, ▔ )ㄏ

5.等分布局

图片描述

C + G = 4*(W + G)
以下例子假设间距G = 20px

结构:

<div class="parent"><div class="column"><p>1</p></div><div class="column"><p>2</p></div><div class="column"><p>3</p></div><div class="column"><p>4</p></div></div>

方法1:float

.parent{ margin-left: -20px; }//就是上面公式中等号左边的G
.column{
  float: left;
  width: 25%;
  padding-left: 20px;//这里要注意,因为我们用padding来表示间距,所以如果你是给p元素设置了background-color,会发现没有间距,p标签的width才是上图中的W
  box-sizing: border-box;
}

方法2:table

<div class="parent-fix"><div class="parent"><div class="column"><p>1</p></div><div class="column"><p>2</p></div><div class="column"><p>3</p></div><div class="column"><p>4</p></div></div></div>
.parent-fix{
  margin-left: -20px;
}
.parent {
  display: table;
  width: 100%;
}
.column {
  display: table-cell;
  padding-left: 20px;//因为单元格不能设置margin,所以间距只能用padding来做。
}

因为table的width默认是随内容宽度变化的,所以需要手动设置 width: 100%;。又因为明确设置了宽度的元素就没办法用将margin设为负值的方式增加20px宽度了,所以需要在外面加一个父元素 parent-fix
这里大家可以自己试试比较一下给 parent-fix设置width为100%不设置width时parent-fix实际宽度(用调试工具里的查看元素看)的区别来理解。

呐,jsfiddle示例在这

方法3:flex

.parent { display: flex; }
.column { flex: 1; } 
.column + .column { margin-left: 20px; }//好用的兄弟选择器 (。・`ω´・)

上面这个等分布局这个整理完在另一篇CSS布局的文章里面给个链接

6.一列定宽+一列自适应(当其中较高的一列高度变化,另一列同步变化)

右侧变高,左侧高度随之变化:

右侧变高,左侧高度随之变化1

↓↓

右侧变高,左侧高度随之变化2

方法1:table

table的列之间有天然等高的特性。

就是上面 1.一列定宽 + 一列自适应中的 方法4:table

方法2:flex

flex也是天然的等高 <( ̄︶ ̄)> 因为它默认的align-items为stretch,即在交叉轴上默认拉伸占满整个容器。

仍旧是上面 1.一列定宽 + 一列自适应中的 方法5:flex

简单到不好意思给栗子

方法3:float

仍旧是参照上面 1.一列定宽 + 一列自适应中的 方法3:float + overflow,float并没有天然等高,所以要在这个基础上做改动。

.left{
  width: 100px;
  margin-right: 20px;
}
.right {
  overflow: hidden;
}
//增加部分
.left, .right{
  padding-bottom: 9999px;//使得有背景色的部分变的很高
  margin-bottom: -9999px;//用负的margin抵消掉很高的padding,让高度变回left和right中较高的那部分的内容高度,以便parent用overflow: hidden;去隐藏掉超出部分
}
.parent {
  overflow: hidden;//隐藏掉超出边界的部分 
}

其实left的实际高度并没有变,是一种伪等高,只是背景变高。

栗子

7.全等四宫格

图片描述

这是练习题,置几试试吧。

<div class="parent"><div class="outer"><div class="column>1</div><div class="column>2</div></div><div class="outer"><div class="column>3</div><div class="column>4</div></div></div>

方法1:flex

.parent {
  display: flex;
  flex-wrap: wrap;
  align-content: space-between;
}
.outer {
  flex-basis: 100%;
  display: flex;
  justify-content: space-between;
}

一颗仅供参考的栗子

方法2:float

我的栗子

方法3:table

一个栗子不一定对

全屏布局

1.定宽(px)+自适应

图片描述

只有主内容区 right 随内容滚动。

方法1.position

<div class="parent"><div class="top">top</div><div class="left">left</div><div class="right"><div class="help-right">right</div></div><div class="bottom">bottom</div></div>
html, body, .parent {height: 100%; overflow: hidden;}//为了让整个页面不滚动
.top {
  position: absolute;
  top: 0; 
  left: 0; right: 0; //注意这个很棒的设置!可以自动占满整行 ヾ(o◕∀◕)ノ 
  height: 100px;
}
.left {
  position: absolute;
  left: 0;
  top: 100px; bottom: 50px;
  width:200px;
}
.right {
  position: absolute;
  left: 200px; right: 0;
  top: 100px; bottom: 50px; //这也是上下占满除了top和bottom之外的所有高度
  overflow: auto;//让主内容区可以滚动
}
.help-right {//假装有很多内容
  width: 1000px;
  height: 1000px;
}
.bottom{
  position: absolute;
  bottom: 0; 
  left: 0; right: 0;
  height: 50px;
}

动手写写才记得住

方法2.flex

<div class="parent"><div class="top">top</div><div class="middle"><div class="left">left</div><div class="right"><div class="help-right">right</div></div></div><div class="bottom">bottom</div></div>
html, body, .parent {height: 100%; overflow: hidden;}
.parent {display: flex; flex-direction: column;}
.top { height: 100px; }
.middle {flex: 1; display: flex;}
.left { width:200px; }
.right { flex: 1; overflow: auto; }
.help-right { width: 1000px; height: 1000px; }
.bottom{ height: 50px; }

栗子

2.百分比定宽(%)+自适应

方法1.position , 方法2.flex :

把原来的用px写的定宽改成百分比就可以了。是相对于body的高度和宽度来变化的。感觉top和bottom高度设置百分比不是很实用。

3.自适应+自适应

图片描述

方法1.position

定宽的高度和宽度影响旁边栏的布局,所以实现不了 - 。-

方法2.flex

阔以实现,而且相当简单 ╮(╯▽╰)╭ 把刚刚设置了高度和宽度的地方去掉就可以了 ∑(っ °Д °;)っ

惊人的栗子

方法3.Grid

阔以实现,但是因为还是W3C的草案,所以会经常变化,不稳定,而且浏览器支持也不好。

响应式

想要达到的效果

只写一个网站,在多个终端显示,在小屏幕上会隐藏部分元素。

现在的情况

在PC端浏览器中可以正常访问的网站,到了手机上之后,内容就会变得特别小。
原因:所有的移动设备都有一个viewport(视窗),这个视窗不是手机屏幕大小,而是一个虚拟的窗口,比如iPhone4的viewport宽度为980px(如下图所示)。显示的时候再按照比例将这980px的内容压缩显示到实际的屏幕宽度中。

图片描述

所以为防止让页面缩小,在移动设备中,我们会做如下设置

<meta name="viewport" content="
 width=device-width //让宽度等于设备宽度,因为不同的移动设备宽度不同 iphone4为320px
 ,initial-scale=1.0 //初始缩放1.0, 即不缩放,网站就不会被缩小了
 ,user-scalable=no //防止用户手动缩放
">

设置结束之后,如何具体开发?

方法1.宽度尽量自适应,而不要用定宽。

方法2.用媒体查询 @media

@media screen and (max-width: 320px) {
  //最大宽度为320px,即视窗宽度小于等于320px
  div{..}
  .class-name{...}
}

@media screen and (min-width: 320px) and (max-width: 769px){
  //最小宽度为320px,最大宽度为769px,即视窗宽度大于320px,小于769px
}

响应式

目的

减少卡顿
利于SEO
便于代码维护

方法

1. 减少页面请求

减少css文件请求

(1)多个css文件合并成一个
(2)少量css样式内联
(3)避免用import的方式引入css文件,因为每个import语句都会产生一个css请求,并且是同步的请求。

2.减少资源文件大小

(1)减少图片大小
选择合适的图片格式,小尺寸、半透明的用png,大尺寸、色彩绚丽用jpg(因为jpg会对图片进行压缩)
压缩图片

(2)css值缩写
margin,padding,border,font,border-radius等属性

(3)省略值为0 的单位

margin: 0 10px;
line-height: .5;
background-position: 50% 0;

(4)颜色值最短表示

red
rgb(0,0,0)
rgba(0,0,0,0)
#000000
#000

(5)css选择器合并

.left, .right {...}

(6)文件压缩
用工具对文件进行自动压缩,去掉空格。

3.提升页面性能

加载顺序

css通常放在head中,而js通常放在body底部,因为js会阻碍其他资源加载。

减少标签数量。

选择器长度

body .menu ul li a { ... } //太长了
.menu a { ... } //更好

避免耗性能属性

比如:

expression
filter
border-radius
box-shadow
gradients

给图片设置固定宽高,并且图片实际宽高与设置宽高相同,否则浏览器会回流设置多次宽高

所有表现用css实现

4.通过规范提高代码可读性,可维护性

(1)规范:缩进,变量名等
(2)语义化:除了标签,css、id名最好也尽量有意义
(3)尽量避免Hack,一定要用也要统一的标识,比如IE7用*
(4)模块化:相关联的结构做成一个个模块,复用性更强
(5)添加注释

规范与模块化

规范

1.注释的文字两侧需加空格,防止因编码问题导致注释失效

2.为避免命名污染,可以给class加前缀,比如:

g- 布局命名
m- 模块命名

3.语义化命名

//结构化命名
top { ... }

//改用语义化命名
nav { ... }

4.属性的书写顺序

图片描述

模块化

什么是模块化

  1. 一系列相关联的结构组成的整体

  2. 带有一定的语义,而非表现

比如,翻页器(或叫分页器paging)、轮播图。

怎么做?

  1. 为模块分类命名(如.m-, .md-)

  2. 以一个主选择器开头(模块根节点)

  3. 使用以主选择器开头的后代选择器(模块子节点)

<div class="m-nav"><ul><li class="z-crt"><a>链接</a></li><li><a>链接</a></li></ul></div>
//根节点
.m-nav { ... }
//子节点
.m-nav ul{ ... }
.m-nav li{ ... }
.m-nav a{ ... }
.m-nav .z-crt a{ ... }/* 交互状态变化 */

若有一个模块只是比上述模块多了一个按钮,其余部分完全相同,怎么办?

怎样扩展?

为根节点加一个class就好了,这里我们加一个 m-nav-1

<div class="m-nav m-nav-1"><ul><li class="z-crt"><a>链接</a></li><li><a>链接</a></li></ul><a class="btn">我是新加的a标签</a></div>
//变化的部分在 .m-nav-1 这个新class中写
.m-nav-1 { ... }
.m-nav-1 a{ ... }
.m-nav-1 .btn{ ... }

网易的规范和代码库

规范页:包含了CSS规范、HTML规范和工程师规范

代码库:包含了常用的布局方式、常见模块和元件的实现以及一些bug、技巧等

——————
教是最好的学。

从NLP到“自然语言理解”,Facebook如何让Messenger更懂人类?

$
0
0

雷锋网按:Facebook的AML和FAIR团队合作进行自然语言处理对自然语言理解进行着合作研究。在2017年4月19日举办的F8开发者大会上,Facebook向公众介绍了他们的研究进展、自然语言理解在Facebook产品中的应用,并且介绍了平民化的自然语言理解平台CLUE,希望依靠大家的力量,继续丰富自然语言理解的应用。

演讲者:Facebook工程主管Benoit Dumoulin,技术项目主管Aparna Lakshmiratan。雷锋网AI科技评论听译。


(首先上台的是Benoit)大家好,我是Benoit,我是Facebook自然语言理解团队的负责人。

我们团队隶属于AML(applied machine learning,机器学习应用小组)。今天我想给大家讲讲几个我们团队最近的工作成果,讲讲我们正在研究什么,同时也会讲到我们遇到、并且正在努力解决的问题和挑战。等一下我还会请一个同事上台来介绍一个有意思的产品,其中就用到了我们团队开发的技术。

Facebook为什么需要自然语言理解?

进入正题,当你打开Facebook之后,你肯定会用到文字。文字是重要的沟通方式,Facebook的每个产品里面都能看到文字,Instagram,Messenger,Whatsapp等等,文字真的到处都是。而你每天在Facebook看到的或者写下的文字,是没有任何要求,任何人都可以随意发挥的,不需要戏剧化或者怎样。所以如果我们能够理解这些文字,那我们就肯定可以让每个用户的使用感受都得到提升。

理由是这样的,拿我自己举例吧,其实我是法国裔加拿大人,在蒙特利尔长大的(观众喝彩),(笑)。那我呢,工作是科学家,业余时间喜欢冰球,所以当我每天打开Facebook的时候,我想看到我朋友们的新动向、想跟他们聊天;我也想知道最新的冰球比赛结果如何,你看现在就有一场,我挺关心我喜欢的俱乐部怎么样了——不告诉你们叫什么(笑)。同时呢,除了看这些新闻,我还需要关注机器学习方面的动向,我得知道下一场会议在什么时候、都有谁会去、我的朋友们去不去、是谁组织的、有没有什么优秀论文等等,各种各样的事情。为了达到这样的目的呢,我们就需要想办法理解这些文字内容,然后根据我的兴趣进行匹配。

我们经常提到“自然语言理解”和“内容识别”,要表达什么意思呢?简单说就是能给别人解释——一段文字经过算法处理以后,我们能够做出结论或者进行引申。在我们能做的事情里面,最基本的之一就是给文字分类。

比如(右边)这段内容是我发的,把它输到机器里,就能训练它如何把内容分类成为话题,然后就能跟别人解释了,“哦,这段内容是关于厨艺的。” (左边)还有一段关于篮球的内容是我的朋友Jole发的,不过如果你仔细看一下文字的话,你就会发现它也是关于厨艺的,只是不那么明显。不过不用担心,我们训练的机器也可以处理这样的内容。

我们当然还能做更棒的,更深入地理解一段话,区分出其中的实体。这是另一个朋友的动态,要去看演出,这个乐队我没怎么听说过。当你仔细看一下文字部分就会发现,名字挺逗, 乐队名字是“Muckbucket Sunshine”,演出地点是“BOM BOM ROOM”。那如果这种时候我们能区分出实体,知道了是哪个乐队、在哪个场馆演出,然后就可以把它匹配给喜欢音乐的人;然后呢,我们不需要把所有音乐相关的信息都推荐给他们,只需要推荐跟这个乐队或者这个场馆相关的就可以了。我们所讲的“内容识别”大概就是这样。

对于Messenger,你们在听过昨天的演讲、参加一些活动以后就会知道,一旦能够理解文字内容了,就可以做一些很有趣的事情。比如这段对话,我和我的两个朋友打算一起出去喝酒。区分一下实体的话,就是我们要去palo alto,时间也能看得出来。这里我想强调一下,人类可以很容易地理解这段对话,实际上我们3个人用了3种不同的方式来表达时间。

Facebook的目标和方法

所以我们的目标就是设计和训练人工智能,让它们对文字内容的理解准确度达到人类水平,确实是这样。这个目标其实挺难达到的,我试着跟你们解释一下,Facebook上面的文字完全是自由地书写的,没有任何限制,用户们想怎么写就怎么写,对不对。

我们打算怎样达到这个目标呢,不是什么秘密,我们用了深度学习的方法。我们最重要的方法之一是来自这篇论文的。它的名字叫《从零开始进行文本理解》(《Text understanding from scratch》),是几年前公开的,其中介绍了可以用深度学习,就是只需一个标准的网络模型就能进行语言识别,并且解决大多数在其中遇到的问题。我们跟Facebook的研究团队一起研究了这篇论文,然后我们就决定建立一个能够支持论文中这样的算法的平台。

这是我们在Facebook建的这个平台建好以后的样子,我们把它叫做Deep Text,是帮我们达成文本识别方面目标的秘密武器。我们把这个平台建立得可以灵活切换,还可以升级拓展。我们每天会拿很多文本给它训练或者处理。它需要支持多种语言,如果你参与了之前的演讲,你一定明白我们有很多用其它语言的用户,他们同样会用不同的语言创造很多内容,我们也需要理解。还有,这个方面有许许多多的问题需要解决,我们希望这个平台能够以无缝的方式解决许多种不同的问题。

我刚才解释过的内容分类、实体识别和区分只是众多待解决问题中的几个,我们还可以做很多别的事情。我等下还会说一个叫做“文本相似性”的,也是能够很自然地用这样的平台解决的。现在我们回过头来再讲一下Deep Text,看看它是怎么解决文本分类的问题的。

刚才说过Deep Text用到了深度学习,其中很有意思的一点就是单词是以向量的形式表示的。我们在向量空间里表示这些词,这样一来,语义学上相似的词也就会处在更接近的位置上;不怎么相似的词就会离得很远。这里是一张这种表示方法的示意图,里面的蓝色和粉色点就是单词。可以看到,几个球类运动的名称挨得比较近,其它概念性的词虽然也跟运动相关,但是离得就要远一些。

我们实际的措施,基本上是基于卷积网络的,这是一种很自然的吸收组合上下文的方法。这里的“上下文”是指,如果你单独拿一个词出来,它可能会有好多种含义,但是当这个词在句子中、有上下文的时候,那这个词的意思就会变得明确得多。这就是我们这种方法可以很自然地做到这件事的原因。基本上你也自己能做一个话题或者分类识别器,就用这样的网络。

刚才我提到了文本相似性,它可以很好地说明我们这个平台的灵活性。关于文本相似性,我们就拿上一页的那种网络,然后把它拓展成了一个复杂得多的模型,像这样。在这里我们要做的是,用一侧的网络对一份文本内容进行分析建模,我们把这种模型叫做“双塔模型”,如果你是业内人士的话,你肯定明白我是什么意思。所以你用一侧的网络对一条文本进行建模,用另一侧的网络对另一条文本进行建模,然后再用一个函数对语义区别大的进行惩罚。

有了这样的方法,我们就可以做很厉害的事情了。比如这个,我不是喜欢冰球嘛,那么有了这样的模型,如果有一个我喜欢的动态,那这个模型就可以学会找到更多的语义类似的动态,然后我就可以看到更多自己喜欢的东西。这个模型挺厉害的,我们在很多Facebook的产品中都用到了这个模型。

这是我们当前所做的,对于未来,我们想要做得更好。这是我们在通往与人类类似的文本识别准确率路上的小目标之一,我们可以把文字和图片或者视频进行联合识别。还是回到我朋友Jole的这个动态,文字部分是很隐晦的,但是这张图片非常好理解。所以如果把两者加以结合,让图片和文字里面的信息都发挥作用,建立一个联合识别的模型,就肯定会对这份内容有很好的理解。

以上就是我们最近在做的事情,我们也一直努力做出更好的成果。那么我就讲到这里,下面我会邀请我的同事上台,她会继续给大家讲一些的实际产品,其中就用到了我们团队开发的技术。

(Benoit走下讲台,观众鼓掌,Aparna走上讲台)

谢谢Benoit,我是Aparna,我来跟大家讲讲目前Deep Text是如何运用在真实的Facebook产品中的。我不知道大家有没有想过,实际上人们会用Facebook的群组做各种各样的事情,尤其是用它来买卖东西。

自然语言理解已经在改善用户体验

大概一年之前,群组的商务团队找到我们,让我们帮忙看看有没有办法知道一个动态是不是关于卖东西的;如果真的要卖东西,我们能不能帮用户更好地达成他的目标,来给用户更好的使用体验。这其中的难点是,当用户想要卖东西的时候,我们不想给用户发动态的方式加上任何束缚,我们还是希望用户用自然语言、自由地表达,但是我们还是要能够识别出用户想要卖东西的意愿。当我们有了Deep Text以后,我们就可以做一个高精确度的分类器,它可以阅读用户的动态,看看用户有没有卖东西的打算。而且它还可以识别得更深,就像刚才Benoit说的那样,识别文本中的实体,在这个场景下就可以是产品名称、价格、主要特性,然后我们就可以给用户提供很有吸引力的使用体验。

再举一个例子,社交推荐,我在自己的Facebook上都经常会使用这个功能。借助Deep Text,我们可以观察用户的动态,如果你在让你朋友帮忙推荐东西的,比如好吃的饭馆、好玩的活动,又比如这个例子里,想找一个靠谱的理发店。我们能做的就是,用Deep Text判断用户想要做什么样的事情,一旦判断出来,接下来的事情会给用户很棒的体验,当你的朋友给出建议以后,评论会分类,找出其中的实体,更重要的是把找到的实体链接到Facebook的地址簿里,然后展示成美观的地图,方便用户互动,最终达成自己的目标。

最后再说一个应用的话,你们可能在昨天的演讲里已经听过了,那就是Messenger中的M建议。当M觉得它能够帮助你做什么的时候,它就会自动在你的聊天中跳出来。它吸引人的地方,就是缩短了从“想做”到“做成”之间的距离。所以M可以帮忙做很多你想要做的事情。比如,当M发现你要去哪里的时候,它可以帮你叫一辆车,看你喜欢Uber还是lift,而且可以不用离开Messenger界面就叫到车。现在这段视频就演示了当M发现用户打算叫外卖,只要点一下,就可以用一种新的体验叫到外卖。

而且你还会发现这种体验是共享化的,对话里的每个人都可以参与这个点餐的过程。点好以后仍然通过M就可以完成付款。所以这些事情都可以不用离开Messenger界面就可以搞定,而随着M变得越来越智能,我们也希望有更多类似这样的建议可以给更多的用户意愿带来方便。

接下来我想讲讲,当我们在研发这些产品的时候都遇到了一些什么样的困难。

第一个困难是,很难批量复制机器学习所需要的专家。大多数时候,我们团队的机器学习专家和产品团队的工程师一起合作,做出这些非常高准确度的分类器。但是你会注意到,在我提到的这些应用例子里,我们还想给很多很多别的用户意愿做出分类器,还有很多很多的实体我们想要识别提取,但是很明显,我们没办法像批量化建立功能一样地让机器专家们也批量化。

第二个问题是重复使用性,我们AML团队很看重这件事。回想一下前面我举的两个例子,其中有一个提取地址的功能,我们会觉得这个功能既要用在Messenger的M建议功能里,也要用在社交推荐里是很自然的事情。所以我们也在想如何才能建立一个平台,让不同的程序都能共享和重复使用这个平台的模型、特性以及数据,这样它们就都可以找到突破口,避免开发的时候要每次重复做类似的工作。

第三个难点你们应该都理解,机器学习的关键是数据,高质量的有标签数据;要获得这样的数据,大家可能都知道,不仅仅是难,而且还很费钱。所以我们也在想 如何优化标签,让我们在训练这些分类器和提取器的时候尽可能提高标签的使用效率。


介绍一个平台给你认识一下吧,它叫做CLUE

这几点就把我们引向了CLUE。CLUE是一个语言识别引擎,是一个自助式的平台。

而CLUE做的事情,是让机器学习变得民主化,它用优秀的人工智能和漂亮的用户界面吸引着人们进入,这样Facebook的每个用户,不一定非要是机器学习专家,都可以到CLUE里面用最先进的自然语言理解技术建立一个实体提取器和内容分类器。 这样,当任何的新应用需要的时候,都可以由CLUE这个统一的入口来建立提取器和分类器。

第二个方面是,CLUE是建立于Deep Text和Facebook的AI开发平台FB LearnerFlow2和Caffe2之上的,相信你们今天已经听说过它们了。对Facebook整体来说,这些怕平台极大地提高了灵活性,让人们可以共享和重复使用模型、数据和特性。

第三个方面是对标签效率的提升。CLUE使用了“主动学习”功能,这个功能在机器学习社区经常可以见到,让它可以为获取到的标签做出优化,让分类器只学习它还需要学习的、还没有弄明白的标签。

接下来,我会站在一个想要运用CLUE的工程师的角度,利用它自助式的设计,建立一个分类器,就比如叫外卖的吧。那么你首先需要做的事情是收集一些数据,你需要先给分类器一些正确的样本,比如能表现出叫外卖意愿的短语或者句子。你可以看到,CLUE提供了一个非常平易近人的用户界面,带有一些关键词搜索来提示你,方便你输入带有这种意愿的语句。

在这个例子里,“我想吃赛百味或者汉堡王”就带有叫外卖的意愿,算是一个不错的开始。你可以添加许多这样的确实具有意愿的句子,来达成你的目标。正确和不正确的样本都行,当你开始收集以后,你需要做的就是给他们加标签。

同样地,CLUE有一个美观的用户界面来让你做这件事。你不仅可以标出哪些有意愿,哪些没意愿,而像Benoit前面说的那样,还可以给实体加标签。对我们这个例子来讲,“赛百味”和“汉堡王”都是值得加标签的内容,可以帮助你达成你的目标。

当你添加好标签以后,CLUE就会自动地在后台训练出一个分类器。在你添加标签的过程中,你就已经得到了一个可以使用的分类器了。如果你才刚刚开始的话,效果可能还不是很好,但是毕竟有一个了。然后你就可以看看分类器运行的状况如何。

CLUE支持多种可视化方式,比如召回率曲线、AUC曲线等等,很多种图形供你选择。现在你还可以监控你的分类器运行状况如何,来看看你计划的任务完成得如何。

你还可以用CLUE做一件厉害的事情,就是你可以用这种初始状态的分类器去分类真实的Facebook信息流,看看结果如何。我来演示一下要怎么做,在用这个分类器对真实的信息流分类以后,可能很多东西已经分类对了,但是更重要的是,能看到有哪些东西分类错了。

在这个例子里,CLUE就告诉你,你的分类器好像有点弄混了,分不清“能送上门吗”和“我们端着面条说吧”。这就可以提示你如何改进数据和标签,给分类器更正这些错误,给怎样得到越来越好的结果指明了方向。

这就是我前面提到过的主动学习循环。随着你调整好了你的模型,当你对结果满意的时候,就可以点一下CLUE里面的“部署”按钮,就这么简单。点一下按钮,你的分类器就可以部署到所有的数据中心里,这样,只要有需要的Facebook产品团队都可以到其中选择、并把你的分类器应用到他们的程序中。

算上Deep Text和CLUE,我们使用在Facebook产品里面的模型已经超过了200个。这里面很厉害的事情是,不仅机器学习的专家们使用这些模型,Facebook里不同产品团队的科学家和工程师也在用这些模型。

在这场演讲的开头,Benoit说我们真正的目标是通过各种各样的应用,借助数百万计的内容把人和人连接起来。今天的Facebook已经做得很好的事情,是通过利用社交信息流,借助你朋友们所说的话、所做的事把你和内容连接起来。我们很想要拓展这件事情,想要把你和这世界中你感兴趣的数百万计的内容也连接起来。即便今天说了这么多,其实我们也只是刚刚开始,为了让AI能够达到这些目标、提供这些新的用户体验,还有很多的事情等待我们去做。谢谢大家!

雷锋网AI科技评论听译

 via Natural Language Understanding @Facebook


(收藏)PhantomJS

$
0
0
http://javascript.ruanyifeng.com/tool/phantomjs.html

JavaScript 标准参考教程(alpha)
开发工具
PhantomJS
GitHub
TOP
PhantomJS

来自《JavaScript 标准参考教程(alpha)》,by 阮一峰

目录
概述
REPL环境
webpage模块
open()
evaluate()
includeJs()
render()
viewportSize,zoomFactor
onResourceRequested
onResourceReceived
system模块
应用
过滤资源
截图
抓取图片
生成网页
参考链接
概述
有时,我们需要浏览器处理网页,但并不需要浏览,比如生成网页的截图、抓取网页数据等操作。PhantomJS的功能,就是提供一个浏览器环境的命令行接口,你可以把它看作一个“虚拟浏览器”,除了不能浏览,其他与正常浏览器一样。它的内核是WebKit引擎,不提供图形界面,只能在命令行下使用,我们可以用它完成一些特殊的用途。

PhantomJS是二进制程序,需要安装后使用。

$ npm install phantomjs -g
使用下面的命令,查看是否安装成功。

$ phantomjs --version
REPL环境
phantomjs提供了一个完整的REPL环境,允许用户通过命令行与PhantomJS互动。键入phantomjs,就进入了该环境。

$ phantomjs
这时会跳出一个phantom提示符,就可以输入Javascript命令了。

phantomjs> 1+2
3

phantomjs> function add(a,b) { return a+b; }
undefined

phantomjs> add(1,2)
3
按ctrl+c可以退出该环境。

下面,我们把上面的add()函数写成一个文件add.js文件。

// add.js

function add(a,b){ return a+b; }

console.log(add(1,2));

phantom.exit();
上面的代码中,console.log()的作用是在终端窗口显示,phantom.exit()则表示退出phantomjs环境。一般来说,不管什么样的程序,exit这一行都不能少。

现在,运行该程序。

$ phantomjs add.js
终端窗口就会显示结果为3。

下面是更多的例子。

phantomjs> phantom.version
{
  "major": 1,
  "minor": 5,
  "patch": 0
}

phantomjs> console.log("phantom is awesome")
phantom is awesome

phantomjs> window.navigator
{
  "cookieEnabled": true,
  "language": "en-GB",
  "productSub": "20030107",
  "product": "Gecko",
  // ...
}
webpage模块
webpage模块是PhantomJS的核心模块,用于网页操作。

var webPage = require('webpage');
var page = webPage.create();
上面代码表示加载PhantomJS的webpage模块,并创建一个实例。

下面是webpage实例的属性和方法介绍。

open()
open方法用于打开具体的网页。

var page = require('webpage').create();

page.open('http://slashdot.org', function (s) {
  console.log(s);
  phantom.exit();
});
上面代码中,open()方法,用于打开具体的网页。它接受两个参数。第一个参数是网页的网址,这里打开的是著名新闻网站Slashdot,第二个参数是回调函数,网页打开后该函数将会运行,它的参数是一个表示状态的字符串,如果打开成功就是success,否则就是fail。

注意,只要接收到服务器返回的结果,PhantomJS就会报告网页打开成功,而不管服务器是否返回404或500错误。

open方法默认使用GET方法,与服务器通信,但是也可以使用其他方法。

var webPage = require('webpage');
var page = webPage.create();
var postBody = 'user=username&password=password';

page.open('http://www.google.com/', 'POST', postBody, function(status) {
  console.log('Status: ' + status);
  // Do other things here...
});
上面代码中,使用POST方法向服务器发送数据。open方法的第二个参数用来指定HTTP方法,第三个参数用来指定该方法所要使用的数据。

open方法还允许提供配置对象,对HTTP请求进行更详细的配置。

var webPage = require('webpage');
var page = webPage.create();
var settings = {
  operation: "POST",
  encoding: "utf8",
  headers: {
    "Content-Type": "application/json"
  },
  data: JSON.stringify({
    some: "data",
    another: ["custom", "data"]
  })
};

page.open('http://your.custom.api', settings, function(status) {
  console.log('Status: ' + status);
  // Do other things here...
});
evaluate()
evaluate方法用于打开网页以后,在页面中执行JavaScript代码。


var page = require('webpage').create();

page.open(url, function(status) {
  var title = page.evaluate(function() {
    return document.title;
  });
  console.log('Page title is ' + title);
  phantom.exit();
});

网页内部的console语句,以及evaluate方法内部的console语句,默认不会显示在命令行。这时可以采用onConsoleMessage回调函数,上面的例子可以改写如下。


var page = require('webpage').create();

page.onConsoleMessage = function(msg) {
  console.log('Page title is ' + msg);
};

page.open(url, function(status) {
  page.evaluate(function() {
    console.log(document.title);
  });
  phantom.exit();
});

上面代码中,evaluate方法内部有console语句,默认不会输出在命令行。这时,可以用onConsoleMessage方法监听这个事件,进行处理。

includeJs()
includeJs方法用于页面加载外部脚本,加载结束后就调用指定的回调函数。

var page = require('webpage').create();
page.open('http://www.sample.com', function() {
  page.includeJs("http://path/to/jquery.min.js", function() {
    page.evaluate(function() {
      $("button").click();
    });
    phantom.exit()
  });
});
上面的例子在页面中注入jQuery脚本,然后点击所有的按钮。需要注意的是,由于是异步加载,所以phantom.exit()语句要放在page.includeJs()方法的回调函数之中,否则页面会过早退出。

render()
render方法用于将网页保存成图片,参数就是指定的文件名。该方法根据后缀名,将网页保存成不同的格式,目前支持PNG、GIF、JPEG和PDF。

var webPage = require('webpage');
var page = webPage.create();

page.viewportSize = { width: 1920, height: 1080 };
page.open("http://www.google.com", function start(status) {
  page.render('google_home.jpeg', {format: 'jpeg', quality: '100'});
  phantom.exit();
});
该方法还可以接受一个配置对象,format字段用于指定图片格式,quality字段用于指定图片质量,最小为0,最大为100。

viewportSize,zoomFactor
viewportSize属性指定浏览器视口的大小,即网页加载的初始浏览器窗口大小。

var webPage = require('webpage');
var page = webPage.create();

page.viewportSize = {
  width: 480,
  height: 800
};
viewportSize的Height字段必须指定,不可省略。

zoomFactor属性用来指定渲染时(render方法和renderBase64方法)页面的放大系数,默认是1(即100%)。

var webPage = require('webpage');
var page = webPage.create();

page.zoomFactor = 0.25;
page.render('capture.png');
onResourceRequested
onResourceRequested属性用来指定一个回调函数,当页面请求一个资源时,会触发这个回调函数。它的第一个参数是HTTP请求的元数据对象,第二个参数是发出的网络请求对象。

HTTP请求包括以下字段。

id:所请求资源的编号
method:使用的HTTP方法
url:所请求的资源 URL
time:一个包含请求时间的Date对象
headers:HTTP头信息数组
网络请求对象包含以下方法。

abort():终止当前的网络请求,这会导致调用onResourceError回调函数。
changeUrl(newUrl):改变当前网络请求的URL。
setHeader(key, value):设置HTTP头信息。
var webPage = require('webpage');
var page = webPage.create();

page.onResourceRequested = function(requestData, networkRequest) {
  console.log('Request (#' + requestData.id + '): ' + JSON.stringify(requestData));
};
onResourceReceived
onResourceReceived属性用于指定一个回调函数,当网页收到所请求的资源时,就会执行该回调函数。它的参数就是服务器发来的HTTP回应的元数据对象,包括以下字段。

id:所请求的资源编号
url:所请求的资源的URL r- time:包含HTTP回应时间的Date对象
headers:HTTP头信息数组
bodySize:解压缩后的收到的内容大小
contentType:接到的内容种类
redirectURL:重定向URL(如果有的话)
stage:对于多数据块的HTTP回应,头一个数据块为start,最后一个数据块为end。
status:HTTP状态码,成功时为200。
statusText:HTTP状态信息,比如OK。
如果HTTP回应非常大,分成多个数据块发送,onResourceReceived会在收到每个数据块时触发回调函数。

var webPage = require('webpage');
var page = webPage.create();

page.onResourceReceived = function(response) {
  console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response));
};
system模块
system模块可以加载操作系统变量,system.args就是参数数组。

var page = require('webpage').create(),
    system = require('system'),
    t, address;

// 如果命令行没有给出网址
if (system.args.length === 1) {
    console.log('Usage: page.js <some URL>');
    phantom.exit();
}

t = Date.now();
address = system.args[1];
page.open(address, function (status) {
    if (status !== 'success') {
        console.log('FAIL to load the address');
    } else {
        t = Date.now() - t;
        console.log('Loading time ' + t + ' ms');
    }
    phantom.exit();
});
使用方法如下:

$ phantomjs page.js http://www.google.com
应用
Phantomjs可以实现多种应用。

过滤资源
处理页面的时候,有时不希望加载某些特定资源。这时,可以对URL进行匹配,一旦符合规则,就中断对资源的连接。


page.onResourceRequested = function(requestData, request) {
  if ((/http:\/\/.+?\.css$/gi).test(requestData['url'])) {
    console.log('Skipping', requestData['url']);
    request.abort();
  }  
};

上面代码一旦发现加载的资源是CSS文件,就会使用request.abort方法中断连接。

截图
最简单的生成网页截图的方法如下。

var page = require('webpage').create();
page.open('http://google.com', function () {
    page.render('google.png');
    phantom.exit();
});
page对象代表一个网页实例;open方法表示打开某个网址,它的第一个参数是目标网址,第二个参数是网页载入成功后,运行的回调函数;render方法则是渲染页面,然后以图片格式输出,该方法的参数就是输出的图片文件名。

除了简单截图以外,还可以设置各种截图参数。

var page = require('webpage').create();
page.open('http://google.com', function () {
    page.zoomFactor = 0.25;
    console.log(page.renderBase64());
    phantom.exit();
});
zoomFactor表示将截图缩小至原图的25%大小;renderBase64方法则是表示将截图(PNG格式)编码成Base64格式的字符串输出。

下面的例子则是使用了更多参数。

// page.js

var page = require('webpage').create();

page.settings.userAgent = 'WebKit/534.46 Mobile/9A405 Safari/7534.48.3';
page.settings.viewportSize = { width: 400, height: 600 };

page.open('http://slashdot.org', function (status) {
if (status !== 'success') {
    console.log('Unable to load!');
    phantom.exit();
  } else {
var title = page.evaluate(function () {
    var posts = document.getElementsByClassName("article");
  posts[0].style.backgroundColor = "#FFF";
  return document.title;
  });

    window.setTimeout(function () {
      page.clipRect = { top: 0, left: 0, width: 600, height: 700 };
    page.render(title + "1.png");
    page.clipRect = { left: 0, top: 600, width: 400, height: 600 };
      page.render(title + '2.png');
    phantom.exit();
    }, 1000);  
  }
});
上面代码中的几个属性和方法解释如下:

settings.userAgent:指定HTTP请求的userAgent头信息,上面例子是手机浏览器的userAgent。
settings.viewportSize:指定浏览器窗口的大小,这里是400x600。
evaluate():用来在网页上运行Javascript代码。在这里,我们抓取第一条新闻,然后修改背景颜色,并返回该条新闻的标题。
clipRect:用来指定网页截图的大小,这里的截图左上角从网页的(0. 0)坐标开始,宽600像素,高700像素。如果不指定这个值,就表示对整张网页截图。
render():根据clipRect的范围,在当前目录下生成以第一条新闻的名字命名的截图。
抓取图片
使用官方网站提供的rasterize.js,可以抓取网络上的图片,将起保存在本地。

phantomjs rasterize.js http://ariya.github.com/svg/tiger.svg tiger.png
使用rasterize.js,还可以将网页保存为pdf文件。

phantomjs rasterize.js 'http://en.wikipedia.org/w/index.php?title=Jakarta&printable=yes' jakarta.pdf
生成网页
phantomjs可以生成网页,使用content方法指定网页的HTML代码。

var page = require('webpage').create();
page.viewportSize = { width: 400, height : 400 };
page.content = '<html><body><canvas id="surface"></canvas></body></html>';
phantom.exit();
官方网站有一个例子,通过创造svg图片,然后截图保存成png文件。





已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



分布式系统间请求跟踪

$
0
0

一、请求跟踪基本原理

现在的很多应用都是由很多系统系统在支持,这些系统还会部署很多个实例,用户的一个请求可能在多个系统的部署实例之间流转。为了跟踪一个请求的完整处理过程,我们可以给请求分配一个唯一的 ID traceID,当请求调用到另一个系统时,我们传递这个 traceID。在输出日志时,把这个 traceID也输出到日志里,这样,根据日志文件,提取出现这个 traceID的日志就可以分析这个请求的完整调用过程,甚至进行性能分析。

当然,在一个系统内部,我们不希望每次调用一个方法时都要传递这个 traceID,因此在 Java 里,一般把这个 traceID放到某种形式的 ThreadLocal变量里。

日志类库在输出日志时,就从这个 ThreadLocal变量里取出 traceID,跟要输出的日志信息一起写入日志文件。

这样对于应用的开发者来说,基本不需要关注这个 traceID

二、远程调用间传递跟踪信息

如何使用的是自定义的 RPC 实现,这些 RPC 一般都预留了扩展机制来传递一些自定义的信息,我们可以把 traceID作为自定义信息进行传递。

对于 Hessian 这种基于 HTTP 协议的 RPC 方法,它把序列化后的调用信息作为 POST的请求体。如果我们不想去修改 Hessian 的调用机制,可以把 traceID放到 HTTP 的请求头里。

在客户端只需要提供封装好的 RPC 调用代理。

在服务端通过 Filter得到 traceID放入 ThreadLocal变量。

三、线程间传递跟踪信息

在实际的应用中,我们还可能把请求交给别的线程去异步处理,这就涉及在线程间传递 traceID

当我们直接用 java.util.concurrent.ExecutorService.submit(Runnable task)提交一个任务时,显然是不会自动传递这个 traceID的,我们需要做一些封装来透明地传递 traceID

下面是一个可跟踪任务的定义,利用 org.slf4j.MDC来保存旧线程里的 traceID,在新线程执行子类任务时初始化跟踪信息。

package net.coderbee.util.concurrent;

import org.slf4j.MDC;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * Created by coderbee on 2017/4/21.
 */
class TraceableTask {
    private Map<String, String> context;

    public TraceableTask() {
        context = MDC.getCopyOfContextMap();
    }

    protected void clearContext() {
        MDC.clear();
    }

    void initContext() {
        if (context == null || context.isEmpty()) {
            init();
        } else {
            MDC.setContextMap(context);
        }
    }

    private void init() {
        Map<String, String> context = new HashMap<String, String>();
        String traceID = UUID.randomUUID().toString().replace("-", "");
        context.put("trace_id", traceID);

        MDC.setContextMap(context);
    }

}

Runnable, Callable<T>分别定义一个子类如下:

package net.coderbee.util.concurrent;

public abstract class TraceableRunnable extends TraceableTask
        implements Runnable {

    public final void run() {
        super.initContext();

        run0();
    }

    protected abstract void run0();
}


package net.coderbee.util.concurrent;

import java.util.concurrent.Callable;

public abstract class TraceableCallable<T> extends TraceableTask
        implements Callable<T> {

    public final T call() {
        super.initContext();

        return call0();
    }

    protected abstract T call0();
}

这两个子类利用了 Java 在对象实例化时总是会执行父类构造函数的特点,使得子类不需要显式保存 traceID

当我们实例化 TraceableRunnableTraceableCallable时,是在旧的线程里执行的,因此它的 traceID会保存在 TraceableTaskcontext属性里。

当另一个线程执行这些任务实例时,首先执行的是 runcall方法, TraceableTask会把保存的 context设置到当前线程的 ThreadLocal里,这就完成了 traceID的传递。

[译] 普通程序员如何向人工智能靠拢?

$
0
0

「范式大学推荐课程」第 4 篇文章:普通程序员如何向人工智能靠拢?


相信看到这篇文章的朋友,几乎都想成为机器学习科学家。


事实上,绝大多数的付费课程,基本上都有完全免费的课程放在另一个地方。我们只是把这些信息整理好,告诉你在哪儿可以找到他们,以及通过什么样的顺序进行学习。


这样,哪怕你是还没毕业的大学生,或者是初入职场的工程师,都可以通过自学的方式掌握机器学习科学家的基础技能,并在论文、工作甚至日常生活中快速应用。


在这里我们推荐一份用户友好型的机器学习教程,你可以通过几个月的学习成为机器学习科学家,完全免费。


一份用户友好型的机器学习教程


当你学习机器学习课程时,有没有被信息过载所淹没?

大部分的学习者都遇到了这个问题,这不是他们的错,因为绝大多数的机器学习课程都过于关注个别算法了。 


没错,虽然算法很重要,但他们还是把太多时间花在了算法上。 


以至于......你几乎很难在短时间内走完一遍机器学习的流程,从而感受到通过它解决具体数据问题的巨大兴奋。


这些机器学习课程关注于算法是因为它容易教。相比之下,如果机器学习老师要带你走一遍机器学习的流程,那么他需要搭建计算环境,完成数据采集、清洗、拆分,特征处理,模型调参和模型预测,甚至他还需要一个面向学习者的交互界面。老师哪有这么多的工具,与其手把手带着学生走一遭,还不如学习机器学习算法。 


但这样的问题是,很难有人能坚持通过自学,成为一个卓越的机器学习科学家。哪怕他是数学博士,或者技术高超的程序员,都很容易陷在细节中而难以有具体项目实现的成就感。 


这份教程将会带来完全不同的思路。它非常适合自学者,即便完全没有编程的基础,也能通过恰当的工具快速实现机器学习模型,解决工作、生活中遇到的具体问题。


值得注意的是,我们享用了世界顶级的机器学习资源,而不需要花费 1 分钱。 


自我学习的方法

我们推荐通过 Doing Shit(不是技术术语)完成你的学习。


在这之前你也许已经学习过机器学习了,但从我和朋友们的经验来看,往往会被各种神秘的符号、公式、大量的教科书和论文整的晕头转向,然后再也不想碰这恼人的玩意了。


我们的方法会更加友好,它的学习过程就像小朋友学习一样,你会了解一些基础的知识(但不一定要完全弄懂),然后通过好用的工具快速实现出来就好了。而当你被建模出来的结果吸引,那时候我们才谈算法背后的数学逻辑和计算逻辑。


所以我们会在学习中做很多机器学习项目,这样的好处是当你面对一个工作机会时,你就是一个经验丰富的机器学习科学家了!


当然自学本身是需要自律的,这本教程将一直陪伴着你,以下是 4 个步骤。

1.前提条件 (不需要完全弄懂)

统计学、编程和数学(也可以不需要编程) 


2.海绵模式 

把自己浸泡在机器学习的各种理论中 


3.目标实践 

通过机器学习包实践 9 个有意思的题目 


4.机器学习项目 

深度参与到感兴趣的项目和领域中



步骤一:前提条件 

机器学习之所以看起来很吓人,是因为总伴随着那些晦涩难懂的术语。实际上,即便你是中文系毕业的,也可以学好机器学习。不过,我们需要你在一些领域有基础的理解。


好消息是,一旦你满足了前提条件,其余的将会非常容易。事实上,几乎所有的机器学习都是把统计学和计算机科学的概念应用于数据领域


任务:确保你了解基础的统计学、编程和数学 


统计学:理解统计学、特别是贝叶斯概率对许多机器学习算法来说都是至关重要的。 

免费的指南:How to Learn Statistics for Data Science, The Self-Starter Way 

https://elitedatascience.com/learn-statistics-for-data-science


编程:懂得编程将会更灵活的应用机器学习。 

免费的指南:How to Learn Python for Data Science, The Self-Starter Way 

https://elitedatascience.com/learn-python-for-data-science


数学:对原始算法的研究需要线性代数、多变量计算的基础。 

免费的指南:How to Learn Math for Data Science, The Self-Starter Way 

https://elitedatascience.com/learn-math-for-data-science


你可以先看看这些教程,给你的机器学习道路打下知识基础。


步骤二:海绵模式 

海绵模式是尽可能吸收足够多的机器学习理论知识。 


现在有些人可能会想:“如果我不打算进行原创性研究,为什么在可以使用现有机器学习包的时候,还需要学习理论?” 

这是一个合理的问题! 


然而,如果你想把机器学习更灵活的应用于日常工作,学习一些基础理论还是很有好处的,而且你并不需要完全弄懂。下面我们会剧透学习机器学习理论的 5 个理由。

(1)规划和数据采集

数据采集真是一个昂贵和耗时的过程!那么我需要采集哪些类型的数据?根据模型的不同,我需要多少数据?这个挑战是否可行? 

(2)数据假设和预处理 

不同的算法对数据输入有不同的假设,那我应该如何预处理我的数据?我应该正则化吗?假如我的模型缺少一些数据,它还稳定吗?离群值怎么处理? 


(3)解释模型结果 

简单的认为机器学习是一个“黑盒子”的概念是错误的。是的,并不是所有的结果都直接可以解释,但你需要诊断自己的模型然后改善它们。我要怎么评估模型是过拟合还是欠拟合?我要向业务利益相关者怎么解释这些结果?以及模型还有多少的改善空间? 


(4)改进和调整模型 

你的第一次训练很少会达到最佳模式,你需要了解不同的调参和正则化方法的细微差别。如果我的模型是过拟合了,我该如何补救?我应该花更多时间在特征工程上,还是数据采集上?我可以组合我的模型吗? 


(5)驱动商业价值 

机器学习从来不会在真空中完成。如果你不了解武器库中的工具,就无法最大化发挥它们的效能。在这么多结果指标中,哪些是优化的参考指标?哪个更为重要?或者还有其他的算法会表现更好吗? 


好消息是,你不需要一开始就知道所有问题的答案。所以我们推荐你从学习足够的理论开始,然后快速进入到实践。这样的话,你比较能够坚持下来,并在一段时间后真正精通机器学习。


以下是一些免费的机器学习资料。


2.1 机器学习视频课程

这是来自哈佛大学和耶鲁大学的世界级课程。
任务:完成至少一门课程 

哈佛大学数据科学课程 

端到端的数据科学课程。相比吴恩达的课程,它对机器学习的重视程度较低,但是从数据收集到分析,你可以在这里学到整个数据科学的工作流程。 

课程主页: http://cs109.github.io/2015/


斯坦福大学机器学习课程 

这是吴恩达的著名课程,这些视频说清楚了机器学习背后的核心理念。如果你的时间只能上一节课,我们建议这个。 

课程主页:

https://www.youtube.com/watch?v=qeHZOdmJvFU&list=PLZ9qNFMHZ-A4rycgrgOYma6zxF4BZGGPW&index=1



2.2 机器学习参考资料


接下来我们推荐行业中两本经典的教材。 


任务:看这些 PDF 作为教科书 

An Introduction to Statistical Learning 

Gentler 在书里介绍了统计学习的基本要素,适合所有机器学习的学习者。 

PDF 地址: http://www-bcf.usc.edu/~gareth/ISL/ISLR%20Sixth%20Printing.pdf


Elements of Statistical Learning 

严格的介绍了机器学习理论和数学,推荐给机器学习的研究员。 

PDF 地址: http://statweb.stanford.edu/~tibs/ElemStatLearn/


2.3 成功的关键


以下是每个步骤成功的关键。 

A:注重大局,总是问为什么 

每当你被介绍一个新概念时,问一句“为什么”。为什么在某些情况下要使用决策树而不是回归?为什么要规范参数?为什么要拆分数据集?当你了解为什么使用每个工具时,你将成为真正的机器学习从业者。 

B:接受你不会记得所有学过的东西 

不要疯狂的做笔记,也不要每个课程都复习 3 次。在自己的实际工作中,你会经常需要回过头查看。


C:继续前进,不要气馁 

尽量避免在一个话题上拖太久的时间。即便是对于机器学习教授来说,有些概念也很不好解释。但是当你在实践中开始应用时,你会很快就懂得概念的真实含义。 

D:视频比教科书更有效 

从我们的经验来看,教科书是很好的参考工具,但它很难坚持。我们强烈推荐视频讲座的形式。 


步骤三:有目的实践 

在海绵模式之后,我们会通过刻意练习的方式磨练技能,把机器学习能力提高到一个新水平。目标包括三个方面:

1.实践完整的机器学习流程:包括数据收集、清洗、预处理,建立模型,调整参数和模型评估。 


2.在真实的数据集中练习,逐渐建立哪种模型适合哪种挑战的直觉。


3.深入到一个具体主题中,例如在数据集中应用不同类型的聚类算法,看哪些效果最好。 


在完成这些步骤后,当你开始解决大型项目时就不会不知所措了。

3.1 机器学习工具

为了快速实现机器学习模型,我们推荐使用现成的建模工具。这样的话,你会在短时间内练习整个机器学习的工作流程,而无需在任何一个步骤花费太多时间。这会给你非常有价值的“大局直觉”(Big Picture Intuition)。


Python:Scikit-Learn 

Scikit-learn 和 Sklearn 是通用机器学习中 Python 的黄金标准库,它具有常规算法的实现。 


R:Caret 

Caret 为 R 语言中的模型包提供一个统一的界面。它还包括了预处理、数据拆分、模型评估的功能,使其成为一个完整的端到端解决方案。 


3.2 实践数据集

学习了工具后,你还需要一些数据集。数据科学和机器学习的艺术,很多都在于解决问题时的几十个微观决定。我们会在不同的数据集中看到建模的结果。


任务:从以下选项中选择 5 到 10 个数据集。我们建议从 UCI 的机器学习库开始,例如你可以选择 3 个数据集,分别用于回归、分类和聚类。 

在进行机器学习工程的时候,想想以下问题:


  • 你需要为每个数据集执行哪些类型的预处理?

  • 你需要进行降维操作吗?你可以使用什么方法? 

  • 你可以如何拆分数据集? 

  • 你怎么知道模型是否出现“过拟合”? 

  • 你应该使用哪些类型的性能指标? 

  • 不同的参数调整会如何影响模型的结果? 

  • 你能够进行模型组合以得到更好的结果吗? 

  • 你的聚类结果和直观的相符么?


    UCI 机器学习报告 

    UCI 机器学习报告采集了超过 350 个不同的数据集,专门为机器学习提供训练数据。你可以按照任务搜索(回归、分类或聚类),也可以按照行业、数据集大小搜索。 

    http://archive.ics.uci.edu/ml/


    Kaggle 

    Kaggle.com 以举办数据科学比赛闻名,但是该网站还拥有超过 180 个社区数据集,它们包含了有趣的话题,从用户宠物小精灵到欧洲足球比赛的数据应有尽有。 

    https://www.kaggle.com/datasets


    Data.gov 

    如果你正在寻找社会科学或者与政府有关的数据集,请查看 Data.gov。这是美国政府开放数据集合,你可以搜索超过 190,000 个数据集。 

    https://www.data.gov/


    步骤四:完成机器学习项目 

    好了,现在到了真正有趣的部分了。到目前为止,我们已经涵盖了前提条件、基本理论和有目的实践。现在我们准备好进入更大的项目。 


    这一步骤的目标是将机器学习技术整合到完整的、端到端的分析中。 


    4.1 完成一个机器学习项目

    泰坦尼克号幸存者预测挑战是一个非常受欢迎的机器学习实践项目,事实上,这是  Kaggle.com 上最受欢迎的比赛。 


    我们喜欢以这个项目作为起点,因为它有很多伟大的教程。你可以从中了解到这些有经验的数据科学家们是怎么处理数据探索、特征工程和模型调参的。 

    Python 教程 

    我们真的非常喜欢这个教程,因为它教会你如何进行数据预处理和纠正数据。教程由 Pycon UK 提供。 

    教程地址: https://github.com/savarin/pyconuk-introtutorial


    R 教程

    在 R 中使用 Caret 包来处理几个不同的模型。本教程很好总结了端到端的预测建模过程。 

    教程地址: http://amunategui.github.io/binary-outcome-modeling/


    这是一个“不负责任”的快速教程:仅仅是个教程,跳过了理论讲解。不过这也很有用,而且它显示了如何进行随机森林操作。 

    教程地址: http://will-stanton.com/machine-learning-with-r-an-irresponsibly-fast-tutorial/


    4.2 从头写个算法

    为了对机器学习有更深的理解,没有什么比从头写个算法有帮助了,因为魔鬼总是在细节里。


    我们建议从一些简单的开始,例如逻辑回归、决策树或者 KNN 算法。

     

    这个项目也为你提供了一个将数据语言翻译成程序语言的实践。当你想把最新的学术界研究应用于工作时,这个技能将会十分方便。 

    而如果你卡住了,这里有一些提示: 


    • 维基百科有很多好资源,它有很多常见算法的伪代码。

    • 为了培养你的灵感,请尝试查看现有机器学习软件包的源代码。

    • 将你的算法分解,为采样、梯度下降等编写单独的功能 

    • 从简单开始,在尝试编写随机森林前,先执行一个决策树。


      4.3 选择一个有趣的项目或领域

      如果你没有好奇心,你是很难学好的。但目前为止,也许你已经找到了想坚持下去的领域,那么开始建模吧!


      老实说这是机器学习最好的部分了。这是一个强大的工具,而一旦你开始理解,很多想法都会主动找上门。 

      好消息是,如果你一直在跟踪,也准备好从事这份工作,那么你的收获会远超你的想象! 


      我们也推荐了 6 个有趣的机器学习项目。 

      地址: https://elitedatascience.com/machine-learning-projects-for-beginners

      恭喜你到达了自学指南的终点


      这里有一个好消息,如果你已经遵循并完成了所有任务,那么你在应用机器学习上将会比 90% 自称是数据科学家的人更好。 


      而更好的消息是,你还有很多东西要学习。例如深度学习、强化学习、迁移学习、对抗生成模型等等。 


      成为最好的机器学习科学家的关键是永远不要停止学习。在这个充满活力、激动人心的领域,开始你的旅程吧!


      该教程由 EliteDataScience 提供,我们翻译了这份教程,略有改动。这是原文链接: https://elitedatascience.com/learn-machine-learning


      「范式大学」由第四范式发起,致力于成为“数据科学家”的黄埔军校。「范式大学系列课程」会和大家推荐戴文渊、杨强、陈雨强等机器学习领域顶尖从业人士的最新分享,以及由第四范式产品团队推荐和整理的机器学习材料。








      人脸检测与识别的趋势与再分析

      $
      0
      0



      最近因为种种原因,这方面的知识有得到大家的认可和对其有很大的兴趣,所以今天想再一次分享这知识,让已明白的人更加深入理解,让初学者有一个好的开端与认知,谢谢大家的支持!





      现在打开谷*公司的搜索器,输入 “face detect”,估计大家都能够想到,都是五花八门的大牛文章,我是羡慕啊!(因为里面没有我的一篇,我们实验室的原因,至今没有让我发一篇有点权威的文章,我接下来会写4张4A纸的检讨,去自我检讨下为什么?-----蓝姑)

      原归正传,让我开始说说人脸这个技术,真的是未来不可估计的IT技术,不知道未来会有多少企业为了这个技术潜心研究,现在就来看看最近的技术和未来的发展吧!

      我先大概说下遇到的一些问题:


      Ø 图像质量:人脸识别系统的主要要求是期望高质量的人脸图像,而质量好的图像则在期望条件下被采集。图像质量对于提取图像特征很重要,因此,即使是最好的识别算法也会受图像质量下降的影响;

      Ø 照明问题:同一张脸因照明变化而出现不同,照明可以彻底改变物体的外观;

      Ø 姿势变化:从正面获取,姿势变化会产生许多照片,姿态变化难以准确识别人脸;

      Ø 面部形状/纹理随着时间推移的变化:有可能随着时间的推移,脸的形状和纹理可能会发生变化;

      Ø 相机与人脸的距离:如果图像是从远处拍摄的,有时从较长的距离捕获的人脸将会遭遇质量低劣和噪音的影响;

      Ø 遮挡:用户脸部可能会遮挡,被其他人或物体(如眼镜等)遮挡,在这种情况下很难识别这些采集的脸。


      就先说这些问题吧,还有其他问题,读者你可以自己再去总结一些,其实很easy!


      在没有DL出现之前,大家都是在用传统的机器算法和统计学的算法来对以上问题进行研究,仔细想想,大牛真的好厉害,能想出那么多经典的算法,下面我先简单介绍几个:

      1) 基于Adaboost人脸检测

      Adaboost人脸检测算法,是基于积分图、级联检测器和Adaboost算法的方法,该方法能够检测出正面人脸且检测速度快。其核心思想是自动从多个弱分类器的空间中挑选出若干个分类器,构成一个分类能力很强的强分类器。

      缺点:而在复杂背景中,AdaBoost人脸检测算法容易受到复杂环境的影响,导致检测结果并不稳定,极易将类似人脸区域误检为人脸,误检率较高。

      2) 基于特征的方法(引用“Summary of face detection based on video”)

      基于特征的方法实质就是利用人脸的等先验知识导出的规则进行人脸检测。

      ① 边缘和形状特征:人脸及人脸器官具有典型的边缘和形状特征,如人脸轮廓、眼睑轮廓、虹膜轮廓、嘴唇轮廓等都可以近似为常见的几何单元;

      ② 纹理特征:人脸具有特定的纹理特征,纹理是在图上表现为灰度或颜色分布的某种规律性,这种规律性在不同类别的纹理中有其不同特点;

      ③ 颜色特征:人脸的皮肤颜色是人脸表面最为显著的特征之一,目前主要有RGB,HSV,YCbCr,YIQ,HIS等彩色空间模型被用来表示人脸的肤色,从而进行基于颜色信息的人脸检测方法的研究。

      3) 基于模板的方法

      基于模板匹配的方法的思路就是通过计算人脸模板和待检测图像之间的相关性来实现人脸检测功能的,按照人脸模型的类型可以分为两种情况:

      ① 基于通用模板的方法,这种方法主要是使用人工定义的方法来给出人脸通用模板。对于待检测的人脸图像,分别计算眼睛,鼻子,嘴等特征同人脸模板的相关性,由相关性的大小来判断是否存在人脸。通用模板匹配方法的优点是算法简单,容易实现,但是它也有自身缺点,如模板的尺寸、大小、形状不能进行自适应的变化,从而导致了这种方法适用范围较窄;

      ② 基于可变形模板的方法,可变形模板法是对基于几何特征和通用模板匹配方法的一种改进。通过设计一个可变模型,利用监测图像的边缘、波峰和波谷值构造能量函数,当能量函数取得最小值时,此时所对应的模型的参数即为人脸面部的几何特征。这种方法存在的不足之处在于能量函数在优化时十分复杂,消耗时间较长,并且能量函数中的各个加权系数都是靠经验值确定的,在实际应用中有一定的局限性。

      4) 基于统计理论的方法

      基于统计理论的方法是指利用统计分析与机器学习的方法分别寻找人脸与非人脸样本特征,利用这些特征构建分类,使用分类进行人脸检测。它主要包括神经网络方法,支持向量机方法和隐马尔可夫模型方法。基于统计理论的方法是通过样本学习而不是根据人们的直观印象得到的表象规律,因此可以减小由于人眼观测不完整和不精确带来的错误而不得不扩大检测的范围,但是这种方法需要大量的统计特性,样本训练费时费力。

      以上也都是通过快速阅读得到的一些结论,大部分都是直接引用文章作者的语句。其中在这些方法中,都有很多改进,比如PCA+Adaboost,HMM等。。。。。。


      现在用传统的技术已经不能再有新的突破,所以现在流行了DL架构,打破了人类的极限,又将检测,识别,跟踪等技术上升到另一个高度。

      现在来简单讲讲最近几年神经网络的牛X之处。

      1)Retinal Connected Neural Network (RCNN) 

      2)Rotation Invariant Neural Network (RINN) 

      3)Principal Component Analysis with ANN (PCA & ANN)

      4)Evolutionary Optimization of Neural Networks

      5)Multilayer Perceptron (MLP)

      6) Gabor Wavelet Faces with ANN 

      还有好多就不一一介绍看了(先进的没有介绍,因为想必大家都有阅读,所以。。。嘿嘿,相信大家通过大量阅读一定已经有了自己的想法,赶快去实现吧!)。在此推荐读者你阅读《Recent Advances in Face Detection》,分析的特别详细,希望对大家有帮助,谢谢!

      对了,现在不是因为图像中的人脸检测,识别都已经很出色了,很多团队都做到接近满分了,所以现在来说说未来的趋势,也许这已经不算趋势,因为现在已经有很多人在这条路上摸爬打滚,而且有些团队也有一些成就,希望接下来大家在这领域都能取得好成就。加油!!!


      下面我来给大家提供一些公开的数据库网址:

      ■Annotated Database (Hand, Meat, LV Cardiac, IMM face) (Active Appearance Models)
      ■AR Face Database (http://cobweb.ecn.purdue.edu/~aleix/aleix_face_DB.html)
      ■BioID Face Database (BioID Face Database | facedb | BioID)
      ■Caltech Computational Vision Group Archive (Cars, Motorcycles, Airplanes, Faces, Leaves, Background) (Computational Vision: Archive)
      ■Carnegie Mellon Image Database (motion, stereo, face, car, ...) (CMU VASC Image Database)
      ■CAS-PEAL Face Database (The PEAL Face Database)
      ■CMU Cohn-Kanade AU-Coded Facial Expression Database (http://www.ri.cmu.edu/projects/project_421.html
      ■CMU Face Detection Databases (http://www.ri.cmu.edu/projects/project_419.html)
      ■CMU Face Expression Database (http://amp.ece.cmu.edu/projects/FaceAuthentication/download.htm)
      ■CMU Face Pose, Illumination, and Expression (PIE) Database (http://www.ri.cmu.edu/projects/project_418.html)
      ■CMU VASC Image Database (motion, road sequences, stereo, CIL’s stereo data with ground truth, JISCT, face, face expressions, car) (CMU VASC Image Database)
      ■Content-based Image Retrieval Database (Index of /groundtruth)
      ■Face Video Database of the Max Planck Institute for Biological Cybernetics (Welcome)
      ■FERET Database (frvt.org)
      ■FERET Color Database (The Color FERET Databasehttp://face.nist.gov/colorferet/ )
      ■Georgia Tech Face Database (http://www.anefian.com/face_reco.htm)
      ■German Fingerspelling Database (http://www.anefian.com/face_reco.htm)
      ■Indian Face Database (http://http://www.cs.umass.edu/~vidit/IndianFaceDatabase)
      ■MIT-CBCL Car Database (Pedestrian Data)
      ■MIT-CBCL Face Recognition Database (CBCL FACE RECOGNITION DATABASE)
      ■MIT-CBCL Face Databases (CBCL SOFTWARE)
      ■MIT-CBCL Pedestrian Database (New Page 1)
      ■MIT-CBCL Street Scenes Database (CBCL StreetScenes Database Download Page:)
      ■NIST/Equinox Visible and Infrared Face Image Database (http://www.equinoxsensors.com/products/HID.html)
      ■NIST Fingerprint Data at Columbia (Link)
      ■ORL Database of Faces (The Database of Faces)
      ■Rutgers Skin Texture Database (http://www.caip.rutgers.edu/rutgers_texture/)
      ■The Japanese Female Facial Expression (JAFFE) Database (Japanese Female Facial Expression (JAFFE) Database
      ■The Ohio State University SAMPL Image Database (3D, still, motion) (http://sampl.ece.ohio-state.edu/database.htm)
      ■The University of Oulu Physics-Based Face Database (Center for Machine Vision and Signal Analysis)
      ■UMIST Face Database (http://images.ee.umist.ac.uk/danny/database.html)
      ■USF Range Image Data (with ground truth) (USF Range Image Database)
      ■Usenix Face Database (hundreds of images, several formats) (Link)
      ■UCI Machine Learning Repository (http://www1.ics.uci.edu/~mlearn/MLSummary.html)
      ■USC-SIPI Image Database (collection of digitized images) (SIPI Image Database)
      ■UCD VALID Database (multimodal for still face, audio, and video) (VALID Database)
      ■UCD Color Face Image (UCFI) Database for Face Detection (http://ee.ucd.ie/~prag/)
      ■UCL M2VTS Multimodal Face Database (http://www.tele.ucl.ac.be/PROJECTS/M2VTS/m2fdb.html)
      ■Vision Image Archive at UMass (sequences, stereo, medical, indoor, outlook, road, underwater, aerial, satellite, space and more) (SIPI Image Database)
      ■Where can I find Lenna and other images? (comp.compression Frequently Asked Questions (part 1/3)Section - [55] Where can I find Lenna and other images?)
      ■Yale Face Database (http://cvc.yale.edu/projects/yalefaces/yalefaces.html)
      ■Yale Face Database B (http://cvc.yale.edu/projects/yalefaces/yalefaces.html)


      最后我附上我近期做的效果图,是基于视频中人脸检测与识别的,因为没有标准,公共的数据集,所以我就用室内场景剧作为训练数据,最后的效果很不错,希望以后有同学做人脸的,我们可以一起讨论,共同进步,谢谢!

      有兴趣的朋友,可以看我上传的视频,谢谢!(发现检测过程还是有一些问题,主要是因为训练数据集不够)


      网址:http://pan.baidu.com/s/1eR6ppQyy

      密码:gs9g



      人口流向逆转带来什么变化

      $
      0
      0
      • 人口流向逆转带来什么变化
      • 2017-04-25 13:52文 /李迅雷已阅 5749 
      • 据说,最近上海房租开始下跌。无论这属于短暂下跌还是趋势性下跌,我们都不应忽视的事实是:这两年上海常住人口的数量几乎不增加了。2016年,上海常住人口仅增加了4.7万人,其中,户籍常住人口增加6万人,这意味着外来常住人口减少了1.3万人,上海的外来人口连续两年减少了。不仅是上海如此,北京也有类似情况。2016年,北京常住人口仅增加2.4万人,外来人口常住占比也在减少。那么,从全国范围来看,人口流向是否真的出现扭转了呢?如果是逆转,将对中国经济产生怎样的影响?

        人口流向已发生部分逆转

        根据官方数据,2015年全国流动人口首次出现减少,流动人口数量从2014年的2.52亿降至2015年的2.47亿,减少了约500万人,2016年进一步减少200多万人,至2.45亿。

        流动人口数量的减少,可以分为两种情况:一种是户口迁移至居住地,另一种是户籍不变,人又回到了户籍所在地,即返乡。估计第一种情况较少,第二种情况较多,因为我国的户籍管理制度比较严格,虽然流动人口大部分流向一二线城市,但一二线城市的户口很难落户口,最终使得大部分老龄外出劳动力不得不选择叶落归根。

        我们不妨将上海和安徽人口变化的情况进行比较:2000-2010年,安徽的常住人口为负增长,上海则增长了40%;2010年至今,上海人口仅增长了4.9%,而安徽人 口的增速也达到4%,尤其是2014年之后,安徽人口增速大大超过上海。这表明最近三年以来,人口流向已悄然改变,长期以来作为人口净流入地区的上海,出现了人口净流出现象。

        2001-2016年上海常住人口的增速变化

        与此同时,长期以来作为人口净流出省份的安徽,则出现了人口流入现象。

        2001-2016年安徽常住人口的增速变化

        除了安徽省,其他一些在2010年之前人口净流出的省份,也明显出现了人口净流入的现象,如2007-2010年四川常住人口年均减少0.34%,2014-2015年则年均增长0.78%。当然,一个地区的人口增长除了迁入因素外,还应考虑人口的自然增长率。按照2015年的统计数据,人口自然增长超过30万的省份分别是河北、安徽、福建、江西、山东、广西、广东和云南,那么,只要这些省的常住人口增加数量超过30万人,就可视为人口净流入。

        部分省、市不同时期的常住人口年均增速

        我国东部沿海地区中,人口流入增速明显减少的是江苏省。2007-2015年,江苏省人口净流入规模逐年减少,2016年常住人口仅增加22万人,这主要靠本省新生人口的贡献,而来自外省市人口的流入非常少。此外,江苏还是人口老龄化比较严重的省份,人口自然增长率不足千分之三。不过,广东和浙江在经历了2011-2013年常住人口增速减少之后,2014年以来人口增速再度加快,这表明其经济依然充满活力。

        部分省会城市不同时期的常住人口年均增速

        若观察直辖市和省会城市人口变化的情况,发现上海和北京的人口增速在2011年以后都出现了明显下降,这主要是源于国家对超大城市人口流入进行严格限制,不仅采取苛刻的入户政策,而且还通过整治民办的农民工学校和整治群租房等手段来限制低端人口流入。又如最近设立的雄安新区,作为北京的副中心,实际就是要分流北京的人口和产业,这对于北京高企的房价显然有一定的打压作用,同时对北京减轻交通及人口等压力是有利的。从客观上讲,由于生活成本和经营成本的大幅上升,中低端制造业撤离超大城市是其人口增速放缓或负增长的重要原因。

        而中部地区的省会城市如武汉、合肥等,其人口在经历了2011-2013年增速大幅放缓之后,2014年以后增速再度加快,此外,杭州、广州等省会城市的人口增速也非常快,与其经济高增长有密切关系,因此,当前中国正在经历以省会城市人口快速增长为主要特征的大城市化过程。不过,也有少数省会城市的人口增速在不断下降,如南京、西安及东北的省会城市,表明它们经济活力略显不足。

        2011年,是中国经济经历了两年四万亿强刺激后开始下行的第一年,伴随着大宗商品价格的下跌与民间投资增速的不断下滑,中国新增外出农民工数量也出现下降。时至今日(第一季度数据),外出农民工数量居然负增长,这是否意味着劳动力薪酬在不同区域间的差距缩小了,因为交通更加便捷了?除此之外,农民工老龄化现象和农业可转移人口的减少也是外出农民工数量减少的原因。

        有咨询机构统计了2016年国内人才(大专以上学历)的流向分布,发现杭州、长沙、武汉、深圳、上海和成都是人才流入量的前六大城市,中西部城市占了一半。这说明高学历者的流向也出现了分散化趋势,不像过去那样都集中在北上广深地区。

        二线城市崛起的原因及影响

        胡润研究院首次发布的2017年全球房价指数显示,2016年涨幅最大的前十大城市都在中国,合肥、厦门和南京这三座二线城市的涨幅位居前三,无锡、杭州、福州、郑州的涨幅也处于前十位。这表明中国二线城市房价崛起,房价暴涨与人口增速加快有关,有些则是住宅供地稀缺所致。

        房价涨幅与人口增量关联度大的城市,必然有产业发展机会和投资热点与之相随,如杭州的网络经济高度发达,郑州作为中国的交通枢纽具有承接产业转移的优势。也就是说,这些二线城市将享受规模经济和大城市化带来的好处,并向周边辐射,从而形成新的增长极。

        作为一线城市的上海和北京,由于经营成本和生活成本的高企,中低端制造业必然会不断外迁,从而导致第三产业的比重不断上升。由于行政区划规则所限,北京、上海很难完全照搬东京、巴黎建成大都市圈的模式,这也抑制了北京和上海未来的发展空间,如上海规划到2040年常住人口为2500万左右,北京则规划到2020年将常住人口控制在2300万。严格的户籍制度以及对人口流向高度管制的政策,使得一线城市的发展空间受到限制,这就反过来促进了二线城市的崛起。

        同时,由于政府对经济增长起到举足轻重的作用,包括在基础设施建设方面投入巨大,这促进了中国交通运输条件的改善,如高铁和高速公路的发展,使得增长极的辐射范围进一步扩大,产业转移也更加便利,这也推动了一批三四线城市经济的高速增长和人口的大量流入,这些案例主要发生在长三角、珠三角、京津冀、中部城市集群和成渝地区。

        从2016年国内房价上涨前20位的城市来看,除去一线城市之外,二线及三线城市的对应GDP增速都明显超过6.7%的全国总体水平,如合肥为9.8%,厦门为7.9%,南京为8%,杭州为9.5%,武汉为7.8%,济南为7.8%,南昌为9%,郑州为8.4%,福州为8.5%,广州为8.2%,长沙为9.4%,无锡为7.5%。这说明一个城市的房价走势,与该城市的经济活力有一定相关性。又如,东北三省经济增速下行,使得人口不断流出,房价也随之下跌。

        不过,比较一下上述城市过去两年的GDP走势,会发现大部分城市2016年的经济增速低于2015年,那么,房价走势是否也不会长期与经济增长相背离?从长期来看,房价与人口流向的正相关性更大些。

        因此,随着一线城市房地成本的大幅提高,以及中低收入群体在超大城市和一般城市之间薪酬差距的缩小,人口的集聚度将有所下降,如两年来安徽全省的人口增速超过其省会城市合肥的人口增速。当然,这只是经济发展过程中的阶段性现象,随着今后投资增速的回落,就业形势将变得严峻,当中小城市的就业机会减少,则人口集聚度又会上升。

        流动性下降预示经济将继续回落

        流动人口的减少,是人力资源流动性下降的标志,也是城市化进程放缓的标志。不少人总在憧憬着经济的周期性上升,但更应该正视人口流、资金流增速回落的大趋势。中国不仅流动人口的数量在回落,货币增速也在回落,1990-2010年这二十年,M2平均增长速度为20.5% ,如今则降了一半至10% 。

        随着人口开始从超大城市反流到二线或三线城市,居民的货币存量是否也会发生相应的变化呢?

        部分省市不同时期的居民年均储蓄增速变化

        通过数据对比发现,2014-2015年大部分地区的储蓄增速与人口增速的变化相对一致,但北京和广东的表现则比较特殊,前者是人口增速下降、储蓄增速上升,后者则是人口增速上升而储蓄增速大幅下降。对于北京居民储蓄增速超过上海的现象,我的理解是上海的人口老龄化程度要比北京严重,即上海大约是两个人抚养一个人,而北京则是三个人抚养一个人。至于广东的储蓄增速为何那么低,是否与低端劳动力比重较高有关呢?

        同样异常的还有安徽和四川,尽管人口增速上升,但储蓄增速不升反降,说明这些省份的经济增长质量不高。四川的人口老龄化程度在全国排名第二,它曾经是人口流出最多的省份之一,这两年人口流入量增加,会否是外出农民工叶落归根的现象呢?

        如果进一步观察这些人口增速上升明显省份的省会城市居民储蓄变化情况,可以发现储蓄增速都有所上升,其中杭州尤为明显,说明浙江经济和省会杭州的经济比较健康。不过,从总体来看,人口老龄化是人口流动性下降的主要原因,农业可转移人口的减少也是人口流动性下降的重要原因。

        部分城市不同时期的居民年均储蓄增速变化

        人口老龄化将在两个层面对经济增长产生不利影响:一是劳动人口减少,导致人力成本上升;二是由于作为消费主流人群的25-45岁年龄段人口也开始下降,而这一年龄段群体也是汽车和住房消费的主力群体,这对消费的增长,尤其是对起到经济引擎作用的住房和汽车消费的影响非常显见。有人说,人口老龄化或劳动力数量的下降并不可怕,可以用机器人来取代,但机器人不能取代消费,消费不振导致产能过剩,这也是日本已经持续20多年衰退的原因所在。

        当然,经济增速下行是正常的现象,即便是日本、韩国、德国这几个经济成功转型的国家,当人口流动性下降、城市化进程大幅放缓后,GDP从高增长变为中速增长,增速几乎打了对折。若是刻意追求GDP的增速维持中高速增长,代价将是巨大的,如债务高企、货币泛滥和资产泡沫。因此,面对未来,不应抱有太多侥幸心理或幻想,降低杠杆率、逐步消除资产泡沫,否则的话,系统性金融风险的爆发就难以避免。

        综上所述,随着中国人口流动性的下降,以农业人口转移为特性的城镇化进程已进入后期(外出农民工接近零增长),但以二线城市人口流入为主要特征的大城市化进程仍在持续,同时,外出人口的回流(如部分农民工的告老还乡)现象还会加速,这使得改革开放以来人口“孔雀东南飞”的趋势发生了部分逆转。人口流向逆转的主要原因是人口正迈向深度老龄化,流动人口减少总体看会导致经济下行压力加大,故资产泡沫会随着经济下行而逐步破裂。

        当然,人口逆流也带来了结构性投资机会,因为人口流动意味着区域经济的分化和结构再调整。在二线城市崛起过程中,可以紧随人口流向和资金流向,把握好产业投资与资产配置再布局的机会。

        财联社声明:文章内容仅供参考,不构成投资建议。投资者据此操作,风险自担。

      • 分享到:
      • 暂无评论
        分享你的评论?请先 登录

      微信做搜索:未来是「微信网」还是「万维网」?

      $
      0
      0

      看到微信要做搜索的新闻其实并不惊讶,毕竟近几个月来微信一直「小动作」不断,3月份微信指数推出之后,广告主在微信平台投放广告时就会问「你们有没有基于微信热词的广告报价啊」,似乎预示着一些零碎的出招结束之后,微信还是会踏入移动搜索领域。

      动作很快,4月24日,搜狗发布公布2017年Q1财报的同一天,腾讯的微信事业群内部架构作了一些调整,微信事业群下成立搜索应用部。负责微信的搜索业务、阅读推荐业务、AI技术研究及落地、微信数据平台建设和数据能力的应用。

      腾讯内部的一封邮件对大家很关注的「搜索应用部」进行了解释,其中包括四个产品中心:

      1、搜索产品中心,在充分运用微信数据能力的基础上,打造微信搜索服务及精准阅读推荐服务,由基础产品部下搜索产品中心整体平移而来。

      2、广告用户技术中心,为微信广告业务建设完备的用户体验评价系统及对比试验系统,负责小程序及搜索、搜索生态的建设工作,由基础产品部下广告用户技术中心整体平移而来。

      3、模式识别中心,负责语音识别、图像处理、对话机器人及自然语言处理等方向的技术研究及落地等工作,由技术架构下模式识别中心整体平移而来。

      4、数据中心,负责微信数据平台的建设,为各相关业务提供用户画像及数据分析、数据挖掘能力的支持,由技术架构部下数据中心整体平移而来。

      看到上面四块内容, 有人说,此次架构调整标志着微信将搜索框正式上升至战略高度,而微信在移动搜索领域的野心也暴露无疑。

      其实关于微信做搜索的消息从未停歇过,本文我们还是回到微信这个产品本身去聊聊布局搜索的一些可能性吧,欢迎大家留言讨论。

      一、有节奏的上功能,况且同行也衬托的好

      搜索入口统一,用户使用路径短,体验根据用户的需求慢慢增强。

      微信在移动端的搜索没有一下子放个什么大招,基本是在根据产品的形态发展来逐步给搜索功能添砖加瓦的。

      最初微信只是一个通讯工具的时候,搜索的基本是一些本地内容,比如通讯录好友、聊天记录收藏等。

      在今年微信官方公布的一份《2017年微信用户&生态研究报告》里有这样一个数据:

      截止到2016年12月,微信及WeChat合并月活跃用户数达8.89亿。

      微信的好友规模调查显示,45%的用户微信关系超过200人。

      这两个结果对比起来看基本能表明,满足了熟人关系链沟通之后,你的微信里「泛好友」越来越多了,用户关系链稳定之后,对朋友圈内容、公众号文章的消费也快速增长起来,微信在产品上也顺势提供了「朋友圈内容搜索」、「公众号账号、文章搜索」等功能。

      微信顶部的一个搜索框是唯一的一个搜索入口,承载着最短的用户使用路径

      当微信开拓线下场景,铺设小程序的时候,微信搜索开始支持小程序了。现在的微信搜索入口,还加入了小说、音乐、表情等垂直内容,对于靠吃微信红利的营销类用户来说,今年3月微信推出基于微信大数据的移动端指数“微信指数”,一时间刷爆朋友圈,也让人觉得微信搜索背后隐藏着搜索排名、关键词广告、竞价、广告展示、周边增值服务等巨大的商业价值。

      从整个微信搜索的发展过程来看,每当用户因为满足不了需求咬牙切齿骂微信的时候,它总是能给你送上一点不大不小但确实能解决问题的功能,不免让人觉得,微信内部除了「用完即走」,是不是还有「吊你胃口」的产品哲学啊。

      相反它的老对手百度在移动端的体验就比较尴尬了,光是「如何关闭手机端百度首页下的推荐?」这一需求,就有60多位知乎网友争相去提供解决方案。一个是用户盼着早点上功能,一个是用户迫不及待帮你砍功能,可以说是高下立判了。

      且不说是因为微信占据了我们大部分的手机使用时间,如果微信能逐步提供一个干净的全网搜索结果(事实上2015年微信就对部分用户开放测试了站外搜索功能),作为用户,又有什么理由拒绝呢。

      二、搜索即服务,微信手里的好牌越来越多

      搜索引擎发展到一定阶段都会去扩展垂直内容、整合垂直资源。百度旗下就有百科、视频、文库、新闻等,视频服务有爱奇艺,O2O外卖服务有百度糯米,电影票有百度票务等等。

      除搜索之外,百度在移动端提供的一些服务

      搜狗在早期靠着输入法、浏览器和搜狗搜索“三驾马车”的绝杀战略风光无限,今年搜狗的Q1财报公布了,营收方面,第一季度搜狗收入达到人民币11.2亿元,较上年同期增长16%,其中移动搜索收入占比72%。 不过,再看看几乎同一时间发布的搜狐2017年Q1财报,以美元计算的话,搜狗的营收则变成了1.62亿美元,同比增长10%,环比下降5%。

      除了搜狗搜索,搜狗在移动端提供的一些服务

      搜狗援引CTR数据认为,在PC、WAP、APP三端总覆盖人数,以及移动端月活是行业第二,360看到对这个排名想必肯定也不会答应。而且现在搜狗变成腾讯系搜索引擎,说不定未来还得面临来自微信搜索的内部竞争。

      再看看微信呢,在腾讯的支撑下,可以说是一个风口上的服务品类都没落下。

      共享经济:摩拜单车和滴滴出行
      生活服务:美团外卖和大众点评
      消费升级:京东优选和美丽说
      内容时代:阅文集团、知乎、腾讯新闻、天天日报

      微信钱包里的第三方服务未来会越来越多,逐步包揽我们生活里的一切消费需求。

      三、订阅号提供信息,小程序提供服务,百度没做成的微信来做

      移动端一个搜索入口,用户既能搜索到信息(订阅号提供),也能搜索到服务(小程序提供),这样的一个搜索形态可能是百度在移动端上想做而没做好的。

      我们在 《中国互联网的抑郁:抄与被抄都很痛》一文中提到过,移动互联网时代,一个完备的账户体系是不可或缺的资产,阿里的账户体系包含了你所有的金融生活,腾讯的账户体系中包含了你所有的社交关系。而百度呢,因为账号体系的薄弱带来的用户关系链缺失让它在日后的服务交易型业务中有些力不从心。

      广告对于搜索引擎来说是一个不可或缺的盈利来源。百度搜索和微信搜索在广告方面代表着两种截然不同的方式,前者靠竞价,损害了用户体验和利益之后成了众矢之的,虽然在今日头条等一众产品的倒逼之下也做了信息流广告,但口碑这东西估计是看不到了。后者一开始就依靠用户的关系链做朋友圈的信息流广告,点赞、转发等用户行为给一些预算充足、体验良好的口碑广告带来了不少机会。

      搜索推荐不分家,百度的账号体系薄弱带来的数据建模、用户画像等方面带来的影响也让他在和微信的对峙中处于下风。订阅号的内容通过朋友圈分发,大量的用户交互行为(转发、点赞、评论、打赏)都能成为一个搜索引擎中重要的排序因子。

      四、面对日益壮大的微信,传统的互联网搜索引擎应该感到警惕了

      这事儿在历史上已经上演过。

      谷歌和必应就曾在搜索引擎里加上社交网络的信息来满足用户更多的需求。这里需要的了解的是,Google曾经的网页搜索排名会依靠200多种不同的因素来决定,比如PageRank(网页的权重)、Anchor text(超链接的文本内容)、HTML title (标签内容)等等。

      我们可以假设这样一个场景,如果我在朋友圈发表了一条我很满意并且得了很多赞和评论的状态,我在微信公众号里发表了一篇我觉得质量很好的原创文章,那么即便我在微信的网页里有PageRank了,但我仍然希望能有一些SocialRank来增加一些内容的权重。

      阅读数、点赞数、评论数、转载数都是很好的排序因子,百度想从网页收集这类信息就比较麻烦,还有一点就是公众号的原创识别,网页搜索做原创识别一直是个不小的难题。想象一下,如果你发布的一条内容它得到了更多的转发和点赞的话,在网页搜索里,你的这条内容将会有一个更大的权重。

      「人」这个因素在互联网时代变得越发重要,这也是为什么面对日益壮大的Facebook,Google显得非常的忧伤。在国内其实也是一样的道理, 如果我要搜索一些权威资料的话,传统的搜索引擎可能会给出更为准确的答案。但如果我想搜索身边发生的事情,想知道我的朋友们都在看什么玩什么,那么微信就更可能提供给我感兴趣的内容。

      搜索2.0时代的PageRank在社交网络时代会演化成何种形态其实也蛮让人期待的。

      五、互联网应该是开放的还是一个生态一个生态相互独立的?

      互联网时代,封闭和开放是个永恒的命题。

      如果未来微信搜索越做越大,它完全可以创造出一个独立于万维网之外的一个平行世界。这个平行时间里,你需要的优质信息公众号提供,你需要的娱乐社交朋友圈提供,你需要的生活服务第三方小程序提供,当他能满足你一切要求的时候,你还会怀念外面的世界么。

      如果未来真的有两个世界,那么如何平衡两个世界之间的利益就只能交给张小龙和他的微信团队了。微信搜索在更好的广告模式、更高效率的内容分发以及更健康的搜索生态上仍然需要不断地改进和迭代。

      所以,移动端搜索这场战役还远没有结束,PC端搜索群雄卡位割据的局面又出现了,我们仍然可以期待未来会有一个体验更好的中文搜索服务出现,到时候它是读作「微信搜索」还是「搜狗搜索」就显得没那么重要了。

      更多精彩内容,关注钛媒体微信号(ID:taimeiti),或者下载钛媒体App

      我们如何使用HAProxy实现单机200万SSL连接

      $
      0
      0

      导读:架构师需要精确的了解服务的支撑能力,也希望通过调优来发挥单个节点最大的价值。本文分享了压测及调优 HAProxy 实现 200 万并发 SSL 连接的过程,由高可用架构翻译,转载请注明出处。

      高可用架构

      先观察上面截图,可以看到两个关键信息:

      • 这台机器已经建立了 238 万个 TCP 连接
      • 使用内存大约在 48G。

      下面将会介绍在单个 HAProxy 机器上实现这种规模访问所需的配置。本文是负载测试 HAProxy 系列文章的最后一篇。有时间的读者建议阅读本系列的前两篇(见文末链接),它将帮助您了解相应的内核调优方法。

      在这个配置过程中,我们也使用了很多小组件帮助我们达到目标。

      在展开最终 HAProxy 配置之前,我想给大家回顾一下负载测试的历程及想法,心急的读者可以直接跳到文章后段查阅相关 HAProxy 配置。

      测试目标

      我们要测试的组件是 HAProxy 1.6 版。生产环境是在 4 核 30 G 的机器上运行该软件,当前所有的连接都是非 SSL 的。

      测试目标有两方面:

      1. 当将整个负载从非 SSL 连接转移到 SSL 连接时, CPU 使用率增加的百分比。CPU 的使用率肯定会增加,这是由于 5 次握手的加长和数据包加密的开销所带来。
      2. 其次,希望能够测试单个 HAProxy 每秒请求数和最大并发连接数的上限

      目标一主要因为业务方面功能需要通过 SSL 进行通信。 目标二是为了可以在生产环境中部署最少规模的 HAProxy 机器。

      组件和配置

      • 使用多台客户端机器来执行 HAProxy 压力测试。
      • 有各种配置的 HAProxy 1.6 的机器
        • 4核,30G
        • 16核,30G
        • 16核,64G
      • 相关后端服务器,用于支持所有并发访问。

      HTTP 和 MQTT

      我们的整个基础设施支持两种协议:

      • HTTP
      • MQTT

      在我们的技术栈中,没有使用 HTTP 2.0,因此在 HTTP 上没有长连的功能。所以在生产环境中,单个 HAProxy 机器(上行 + 下行)的最大数量的 TCP 连接在(2 * 150k)左右。虽然并发连接数量相当低,但每秒请求的数量却相当高。

      另一方面,MQTT 是一种不同的通信方式。它提供高质量的服务参数和持久的连接性。因此,可以在 MQTT 通道上使用双向长连通信。对于支持 MQTT(底层 TCP)连接的 HAProxy,在高峰时段会看到每台机器上大约有 600 – 700k 个 TCP 连接。

      我们希望进行负载测试,这将为我们提供基于 HTTP 和 MQTT 连接的精确结果。

      有很多工具可以帮助我们轻松地测试 HTTP 服务器,并且提供了高级功能,如结果汇总,将文本转换为图形等。然而,针对 MQTT,我们找不到任何压力测试工具。我们确实有一个自己开发的工具,但是它不够稳定,不足以支持这种负载。

      所以我们决定使用客户端测试 HTTP 负载,并在 MQTT 服务器使用相同配置。

      初始化设置

      考虑到相关内容对于进行类似的压力测试或调优的人来说有帮助,本文提供了很多相关细节,篇幅稍微有些长。

      • 我们采用了一台 16 核 30G 机器来运行 HAProxy,考虑到 HAProxy 的 SSL 产生的 CPU 巨大开销,因此没有直接使用目前生产环境。
      • 对于服务器端,我们使用了一个简单的 NodeJs 服务器,它在接收到 ping 请求时用 pong 进行回复。
      • 对于客户端,我们最终使用 Apache Bench。使用 ab 的原因是因为它是一个大家熟悉和稳定的负载测试工具,它也提供了很好的测试结果汇总,这正是我们所需要的。

      ab 工具提供了许多有用的参数用于我们的负载测试,如:

      • -c,指定访问服务器的并发请求数。
      • -n,顾名思义,指定当前负载运行的请求总数。
      • -p,包含 POST 请求的正文(要测试的内容)。

      如果仔细观察这些参数,您会发现通过调整所有这三个参数可以进行很多排列组合。示例 ab 请求将看起来像这样

      ab -S -p post_smaller.txt -T application/json -q -n 100000 -c 3000

      http://test.haproxy.in:80/ping

      这样的请求的示例结果看起来像这样

      我们感兴趣的数字是:

      • 99% 的返回请求的响应延迟时间。
      • Time per request:每个请求的时间
      • No. of failed requests:失败请求数。
      • Requests per second: 每秒请求量

      ab 的最大问题是它不提供控制每秒发起请求量,因此我们不得不调整 -c 并发级别以获得所需的每秒钟请求数,并导致很多后文提到的问题和错误。

      测试图表

      我们不能随机地进行多次测试来获得结果,这不会给我们提供任何有意义的信息。我们必须以某种具体的方式执行这些测试,以便从中获得有意义的结果。来看看这个图。

      测试图表

      该图表明,在某一点之前,如果不断增加请求数量,延迟将几乎保持不变。然而, 达到某个临界点,延迟将开始呈指数级增长。这就是该机器的临界点。

      Ganglia

      在提供一些测试结果之前,我想提一下 Ganglia。

      Ganglia 是用于高性能计算系统(如集群和网格)的可扩展分布式监控系统。

      看看截图,了解 Ganglia 是什么,以及它提供的关于底层机器的信息。

      Ganglia

      Ganglia

      通过 Ganglia 可以监测 HAProxy 机器上一些重要参数。

      1. TCP  established 这告诉我们在系统上建立的 TCP 连接总数。注意:这是上行和下行连接的总和。
      2. packets sent and received 发送和接收的 TCP 数据包的总数。
      3. bytes sent and received 这将显示发送和接收的字节数。
      4. memory 随着时间的推移使用的内存数。
      5. network 通过线路发送数据包而消耗的网络带宽。

      以下是通过通过负载测试找到的已知限制。

      700k TCP 连接,

      50k 发送包数量,60k 接收包数量,

      10-15MB 发送及接收的字节数,

      14-15G 内存峰值,

      7MB 带宽。

      所有这些值都是基于每秒数据

      HAProxy Nbproc

      最初,当我们开始测试 HAProxy 时,发现使用 SSL 情况下,CPU 很早就到了瓶颈,而每秒请求数都很低。 在使用 top 命令后,发现 HAProxy 只使用 1 个 CPU 核。 而我们还有 15 个以上的核没用。
      Google 了 10 分钟后,我们在 HAProxy 中找到某个设置,可以让 HAProxy 使用多个核。

      它被称为 nbproc,具体设置请看这篇文章 [8]:
      调整此设置是我们的负载测试策略的基础。  让我们可以方面的进行 HAProxy 组合以便测试。

      使用 AB 进行压力测试

      当开始负载测试之旅时,我们不清楚应该测量的指标和需要达到的目标。

      最初,我们只有一个目标: 通过改变所有下面提到的参数来找到临界点

      我保留了各种负载测试结果的表格。 总而言之,做了 500 多次测试,以达到最终的效果。 您可以清楚地看到,每次测试都有很多不同的部分。

      单客户端问题

      我们看到客户端正在成为瓶颈,因为我们不断增加每秒的请求数。 ab 使用单个核,从文档中可以看出,它不提供使用多核的功能。
      为了有效地运行多个客户端,我们发现一个有用的 Linux 工具叫做 Parallel [7]。 顾名思义,它可以帮助您同时运行多个命令来达到并行的目的。 正是我们想要的。
      看一下使用 Parallel 运行多个客户端的示例命令。

       Parallel

      上述命令将运行 3 个 ab 客户端击访问同一个 URL。 这有助于我们消除客户端瓶颈。

      Sleep 及 Times 参数的问题

      下面是 Ganglia 中的一些参数。让我们简单讨论一下。

      1. packets sent and received 为了产生更多数据,可以在 post 请求中添加更多数据
      2. tcp_established 这是想实现的目标。想象一下,如果单个 ping 请求大约需要一秒钟,那么每秒需要大约 700k 个请求来达到 tcp_established 的目标。现在这个数字在生产环境中可能看起来更容易达到,但是在测试场景中不太可能达到。

      我们在 POST 调用中加入了一个 sleep 参数,它指定了服务端发送返回之前需要 sleep 的毫秒数。这将模拟长时间运行的生产环境请求。如果让请求 sleep 20 分钟的话,只需要每秒发出 583 个请求就能达到 700k 并发连接的标准。

      此外,我们还在 POST 调用中引入了另一个参数: times。服务器在返回请求时应该在 TCP 连接上写入响应的指定次数,这有助于模拟更多的数据。

      Apache Bench (AB) 的问题

      虽然使用 AB 也得到了不少测试结果,但同时也遇到了很多问题。我不会在这里提到所有问题,因为不是这篇文章重点(下面介绍另一个客户端)。

      我们非常满意从 ab 上获得的结果,但是它不支持在一段时间内生成所需指定的 TCP 连接数。不知何故,我们设置的 sleep 参数在 ab 上无法生效。

      虽然在一台机器上可以运行多个 ab 客户端并且可以用工具合并结果,但是在多台客户机上运行此设置对我们来说仍然是一件痛苦的事情。那时我还没有听说过 pdsh [4] 这个工具。

      此外,我们也没有关注过超时的问题。在 HAProxy,ab 客户端和服务器上有一些默认的超时设置,我们完全忽略了这些。后文会讲到。

      我们一开始就提到通过临界点图来检测系统的上限,但讲了这么多有点偏离了最主要目标。然而,要得到有意义的结果只能着眼于这一点。

      使用 AB 碰到的一个问题是到了某个点 TCP 连接数不再增加。我们有大约 40 – 45 个客户端运行在 5 – 6 台客户端机上,但依然不能达到想要的规模。理论上,TCP 连接的数量应该随着 sleep 时间的增加而增加,但对我们来说并非如此。

      引入 Vegeta

      因此我们需要寻找一个负载测试工具,这些工具需要具有更好的扩展性和更好的功能性,最终,我们找到了 Vegeta [6]。
      从我的个人经验来看,我已经看到 Vegeta 具有极高的扩展性,与 ab 相比,它提供了更好的功能。 在我们的负载测试中,单个 Vegeta 客户端能够产生相当于 15 倍 ab 的吞吐量。

      下面,我将提供使用 Vegeta 的负载测试结果。

      使用 Vegeta 进行负载测试

      首先,看看我们用来运行一个 Vegeta 客户端的命令。 进行测试的命令称为 attack:(酷吧?)

      我们太喜欢 Vegeta 提供的参数了,来看看下面的一些参数。

      • -cpus = 32 指定此客户机要使用的 CPU 核数。 由于要生成的负载量,我们不得不将客户机扩展到 32 核 64G。 虽然上面的速度也不是特别高。 但是当有大量处于 sleep 状态的连接时,维持这些连接也会产生比较大的开销。
      • -duration = 10m 我想这是不言自明的。如果没有指定任何持续时间,测试将永远运行。
      • -rate = 2000 每秒请求的数量。

      所以如上图所示,我们在一台 4 核机器上每秒达到了 32k 请求量。 如果你记得临界点图,在这种情况下,非 SSL 请求的临时点是 31.5k。
      从负载测试中看更多的结果。

      16k 的 SSL 连接也不错。 请注意,在我们的负载测试过程中,必须从头开始,因为我们采用了一个新的客户端,它给了我们比 ab 更好的结果。 所以不得不再来一遍。

      CPU 核数的增加导致机器在未达到 CPU 限制前,每秒可以用的请求数增加。

      如果将 CPU 核数从 8 个增加到 16 个,我们发现每秒的请求数量并没有大幅度增加。如果在生产环境中使用 8 核机器,那么我们不会分配所有的核给 HAProxy,或者是它的任何其他进程。 所以我们决定用 6 核机器进行一些测试,看看是否能得到可接受的数字。

      结果还不错。

      引入 sleep

      我们现在对负载测试结果非常满意。 然而,这并没有模拟真正的生产场景。 当我们引入 sleep,才开始模拟生产环境的情况。

      echo “POST https://test.haproxy.in:443/ping” | vegeta -cpus=32 attack -duration=10m  -header=”sleep:1000″  -body=post_smaller.txt-rate=2000 -workers=500  | tee reports.bin | vegeta report

      因此,x 毫秒的随机 sleep 时间将导致服务器 sleep 时间为 0 < x < 1000 。 因此上述负载测试将给出平均 ≥ 500ms 的延迟。

      最后一个单元格中的含义是 TCP established, Packets Rec, Packets Sent

      从表中可以看到,6 核机器可以支持的最大请求量从 20k 减少到 8k。 显然,sleep 有其影响,影响的是 TCP 连接的数量。 然而这距离我们设定的 700K 目标还很远。

      里程碑 #1

      我们如何增加 TCP 连接的数量? 很简单,不断增大 sleep 时间,连接数应该上升。 我们一直增加 sleep 时间并在 60 秒的 sleep 时间停了下来。 这意味着大约 30 秒的平均延迟。

      Vegeta 可以提供成功请求百分比的结果参数。 我们看到,在上述的 sleep 时间,只有 50% 的调用是成功的。 请看下面的结果。

      我们达到了 400 万个 TCP 连接,在每秒 8k 请求和 60s 的 sleep 时间的情况下。 60000R 的 R 表示随机。

      我们的第一个的发现是,在 Vegeta 中有一个默认的超时时间是 30 秒,这就解释了为什么 50% 的请求会失败。 所以我们在后续测试中将超时调整到 70 秒,并随着需求的变化而不断变化。

       Vegeta

      在客户端调整超时值之后,我们可以轻松地达到 700k 标准。 唯一的问题是这些不可持续,只是峰值的数据。 系统达到了 600k 或 700k 的峰值链接,但并没有坚持很长时间。

      但是我们想要得到图上连接持续很久的效果

      这显示了稳定保持 780k 连接的状态。如果仔细查看上面的统计信息,每秒的请求数量非常多。然而,在生产环境中,我们在单个  HAProxy 机器上的请求数量要少得多(约 300 个)。

      我们确信,如果减少生产环境的 HAProxy 的数量(约 30 个,这意味着每秒 30 * 300〜9k 的连接),我们将会达到机器 TCP 连接限制,而不是 CPU。

      所以我们决定实现每秒 900 个请求、30MB/s 的网络流量,以及 210 万 TCP 连接。我们选用这些数字,因为这将是单个生产环境 HAProxy 机器的 3 倍流量。

      到目前为止,我们已经配置了 HAProxy 使用 6 核。我们只想测试 3 核,因为这是我们在我们的生产机器上使用的最简单的方法(如前所述,我们的生产机器是 4 核 30G,所以用 nbproc = 3 进行更改将是最简单的)。

      里程碑 #2

      现在我们对每秒请求的最大限制可以随机器不同而变化,所以我们只剩下一个任务,如上所述,实现 3 倍的生产负载:

      • 每秒 900 个请求
      • 建立了 210 万个 TCP 链接。
      • 30 MB/s 网络。

      在 220k 的测试环境下,我们再次陷入僵局。 无论客户机数量多少或睡眠时间多少,TCP 连接数似乎都停留在那里。

      我们来看一些估算数据。 220k TCP 连接,每秒 900 个请求 = 110,000 / 900〜= 120 秒。达到了 110k,因为 220k 连接包括上行和下行。

      当我们在 HAProxy 开启日志时,我们怀疑 2 分钟是系统某处的限制。 我们可以看到 120,000 ms 是日志中大量连接的总时间。

      在进一步调查中,我们发现 NodeJs 的默认请求超时为 2 分钟。 瞧!

      但我们的高兴显然很短暂,在 130 万,HAProxy 连接数突然下降到 0,并再次开始增长。我们很快检查了 dmesg 命令,里面可以查到 HAProxy 进程一些有用的内核信息。

      基本上,HAProxy 进程已经耗尽内存。因此,我们决定增加机器内存,并将其转移到 nbproc = 3 的 16 核 64GB 的机器,经过调整,我们终于可以达到 240 万长连。

      后端代码

      下面是正在使用的后端服务器代码。 我们还在服务器代码中使用 statsd 来获取客户端接收的每秒请求的统计数据。

      我们还有一个小脚本运行多个服务器。 我们有 8 台机器,每台机器部署了 10 个后端服务。 我们真的认为有条件的话可以进行无限扩容进行压测。

      客户端代码

      对于客户端,每个 IP 有最大 63k TCP 连接的限制。 如果您不确定这个概念,请参阅本系列之前的文章。

      所以为了实现 240 万个连接(双向,来自客户机的是 120 万),我们需要约 20 台机器。 我们在所有机器上运行 Vegeta 命令,甚至找到了一种方法来使用像 csshx [3] 这样的工具,但仍然需要合并所有的 Vegeta 客户端的结果。

      查看下面的脚本。

      脚本

      Vegeta 提供了名为 pdsh [4] 的工具信息,可让您在多台计算机上同时运行命令。 此外,Vegeta 可以让我们将多个结果合并成一个,这就是我们想要的。

      HAProxy 配置

      下面可能是很多读者最关心的,我们在测试中使用的 HAProxy 配置。 最重要的部分是 nbproc 和 maxconn 参数。 maxconn 设置 HAProxy 允许提供的最大 TCP 连接数(单向)。

      对 maxconn 设置的更改导致 HAProxy 进程的 ulimit 增加。 看看下面

      HAProxy 配置

      最大打开文件已增加到 400 万,因为 HAProxy 的最大连接数设置为 200 万。

      参阅文章 [5] 获得更多 HAProxy 优化。

      HAProxy 优化

      相关链接

      1. 系统一 https://medium.com/@sachinmalhotra/load-testing-haproxy-part-1-f7d64500b75d
      2. 系列二 https://medium.com/@sachinmalhotra/load-testing-haproxy-part-2-4c8677780df6
      3. csshx: https://github.com/brockgr/csshx
      4. pdsh: https://github.com/grondo/pdsh
      5. haproxy 配置: https://www.linangran.com/?p=547
      6. Vegeta: https://github.com/tsenart/vegeta
      7. Parallel: http://www.shakthimaan.com/posts/2014/11/27/gnu-parallel/news.html
      8. nbproc setup: http://blog.onefellow.com/post/82478335338/haproxy-mapping-process-to-cpu-core-for-maximum文章来自微信公众号:高可用架构

      一款开源的视频直播项目 --EvilsLive

      $
      0
      0

      EvilsLive

      License MIT  Build Status

      项目介绍

      EvilsLive 是一个视频直播件开发工具包(SDK), 目前只支持 Android, 以后还会支持 Web/IOS 等平台。 主要负责视频直播的采集、推流、预览、播放、美图支持。

      欢迎大家 Star, 你们的关注才是我们持续的动力!

      注意:

      目前此项目还在持续更新中....

      功能特点

      • 视频编码:H.264
      • 推流协议:RTMP
      • 预览与推流分辨率可分别自由设置
      • 支持前、后置摄像头动态切换
      • 支持软编、硬编及软编兼容模式
      • 网络自适应,可根据实际网络情况动态调整目标码率,保证流畅性
      • 音频编码:AAC
      • 支持动态横竖屏推流
      • 音视频目标码率:可设

      项目结构

      • android/app:这里是测试的 sameple
      • android/videolib: 直播相关核心功能实现
      • doc: 文档说明

      Snapshot

      snapshot

      Usage

      要求:

      Android Studio >= 2.2.3

      Android API >= 19

      Step 1:

      在进行推流之前,你需要有流媒体服务(视频上传默认rtmp协议),本地搭建或搭建在公有云上,随你意。

      例如:rtmp://127.0.0.1/live/hjd_phone

      具体可参照 ossrs/srs搭建rtmp server

      Step 2:

      具体如何使用可以参照 app中的代码

      测试环境:

      Android 版本: 6.0

      硬件: HuaWei H60-L02

      TODO LIST

      • 推流(视频)功能
      • 推流(音频)功能
      • 推流流程优化, 支持硬编、多种流媒体协议等
      • 拉流功能
      • 添加测试用例
      • 机型适配

      遗留问题

      • 视频采集数据native层处理
      • 支持多种体系结构
      • 支持 Web/IOS 平台

      Copyright and License

      Copyright 2016-2017 ThinkKeep

      Code released under the MIT License.

      数据显示世界总人口数已突破75亿大关

      $
      0
      0

      Worldometers网站的人口统计表明,地球总人口已达75亿。该网站根据源于联合国,世界卫生组织以及世界银行的可靠数据实时计算地球的人口总数。以下是关于地球人口你需要知道的6个事实:

      image1-1-1592x896.png

      1.超过一半的地球总人口年龄在30岁及以下。尼日利亚和摩纳哥分别是最年轻和年长的国家。前者15岁以下的人口达1.89亿,而后者过半的人口年龄在50岁以上。

      2.联合国最新数据表示,男性总人口比女性大约多6.5亿。美属萨摩亚以男女比例1.36:1的男女差异占世界性别差异最大的国家,而海底则以1.01:1的比例成为男女人口比例差异最小的国家。

      3.中国以13亿8千5百万的人口稳居世界人口总数榜首。联合国预测印度有望在五年内反超中国。印度目前以每年1.57的人口增长率占世界总人口增长率的20%。

      4.中国、印度、美国、印度尼西亚、巴西、巴基斯坦、尼日利亚七个国家一共占世界半数的人口。

      5.世界上人数最少的国家依然是皮特凯恩群岛。

      6.世界人口保持每年8千万左右的增长率,这样算来,将在2024年击穿80亿大关。

      创业公司该不该开会?怎样开?

      $
      0
      0

      好的会议是能产出成果且每个人能按计划执行,而糟糕的会议是在浪费每个人的时间。一个典型糟糕的会议特征是,有人在开会期间处理邮件、微信、玩手机。

      有人说,创业公司开会是在浪费时间,如果每天的会议都是 CEO 开动员大会,那显然是浪费时间的。这篇文章说的开会,并不是领导一个人在台上「我简单讲三点」的一对多讲话,而是汇集集体智慧的会议,比如头脑风暴,比如讨论执行方案、讨论产品功能、市场推广等。这种会议是一种高效的推进项目手段,但并不是每个公司、每个会议组织者都知道如何把会议的效率最大化。包括上了规模的上市公司。

      博客

      不久前,我参与到一家上市公司的讨论会,会议一共有 5 方,这家公司的两个部门和 3 个外部合作伙伴,这个会议从中午 1 点半开始,一直开到晚上 11 点,长达 9 个小时。虽然组织方提前发了会议议程,并安排了每 2 个小时 15 分钟休息时间,但我依然认为这是一次效率极低的会议。有几个核心原因:

      1. 会议原计划开到下午 5 点,但光在调试投影仪这件事上,就花了 20 分钟。
      2. 讨论过程往往只涉及两方或三方,其它非直接参与方的时间被浪费。
      3. 会议时间过长,参与者极容易走神,经常一句话要说两遍,浪费参与者时间。一般来说,人们集中精神开会 2 小时已经很疲惫了。
      4. 虎头蛇尾。即使讨论得异常激烈,一直没法争论出结果,但由于会议时间太长消耗了各方的精力,最后往往是双方妥协回到最初的形态,也就是「保持不变」,但这不应该是开会的目的。
      5. 会议主持人没有控制好讨论的节奏和时间。

      比较庆幸的是,因为下午 5 点约了其他人,我直接离开了会议室。我没有因此觉得错过了什么,因为接下来几个小时的讨论与我没有直接关系,我的时间不应该被浪费在会议室里。

      关于如何高效开会,你在网上可以找到上百篇不同的文章和知乎问答,大多数都是从自身公司出发,讲述自己公司是如何高效开会的。这篇文章,我将结合我参与过的大小会议、我看过的文章和书,以及我和身边的朋友参与过的管理培训,试图讲述一种适合在不同规模公司开会的基本方法论,也就是指南。

      使用说明

      作为一份指南,这篇文章的长度很长,但我相信阅读这篇文章的人多多少少都组织、主持过团队开会,你只需要细读那些你还未有结构化思路的部分,其它部分速读并不会影响你的吸收。

      指南包含的细节关键点很多,但不同的公司、不同的会议,可能有些步骤可以跳过,有些细节可以忽略。请根据自身业务,删减出一份更适合你的操作步骤。

      建议你用 OmniFocus 或其它 todolist 工具,比如我之前创业的「轻单」,制作你的操作步骤。

      会前准备

      开会前,作为会议组织者,需要做一些准备工作。

      确定理想结果

      开会的目的是什么?我们的产品遇到了什么样的问题,需要通过开会来解决?在开会前,我们需要对会议的结果做合适的预期,以下是一些可以参考的关键点:

      1. 你期待在会议上达成什么样的决策或共识?
      2. 这些决策和共识对公司内外有什么良性影响?
      3. 如果无法形成决策或共识,会出现什么问题?问题有多严重?
      4. 有哪些原因(人或技术难度)可能会导致会议无法达成理想的结果?
      5. 如何定义会议是成功的(比如有执行方案产出,或每个人都达成了共识)?

      判断开会的必要性

      当我们自我解答上述疑问后,我们紧接着不是召集所有人开会,而是判断开会的必要性。

      比如,这次会议的目的是要让所有人清晰了解产品下一版本的功能, 这时我们需要思考,是否有必要开会。发邮件能不能达到目的?微信发群公告能不能达到目的?公司内部论坛发置顶帖能不能达到目的?如果能,那就没有开会的必要,因为每个人的时间都很宝贵,我们不应该把时间浪费在「听会」这件事上。

      又比如,有些创业公司习惯早上开晨会,让每个人说前一天的工作、今天准备做什么,这样的晨会是否真的有必要?是否可以通过使用 Trello 这样的协作工具完成?对于互联网公司来讲,很多人习惯在家里办公或半夜写代码,晨会是否有必要强制召开?是否有其它工具可以替代晨会?

      关于会议的必要性,有以下几个判断原则:

      1. 时间紧迫,必须在极短时间让所有人的信息同步。
      2. 必须集中相关人员,才能解决刻不容缓的问题。
      3. 必须让相关部门(人员)面对面达成共识,事情才能推进。
      4. 邮件、微信沟通效率过低,需要通过面对面的头脑风暴讨论出可行方案。
      5. 必须面对面消除部门之间的分歧,确保项目能顺利开展。

      只邀请合适的人参会

      每个人的时间都很宝贵,尤其是在创业公司里,时间是按秒计算的。 一个更好的做法是在开会前,与有可能「捣乱」的人私下交流。这样的人其实是没有必要被召集到会议室中的。

      在大多数情况下,这些人不需要被邀请到会议室:

      1. 他只需要知道会议的结果或执行方案,一封邮件对他来说更有用。
      2. 同一部门已经有其他人参与且其他人可以代表该部门发表意见。
      3. 他的作用是为会议提供必要信息(比如数据),但不需要参与讨论。
      4. 他仅在少数议题上发挥作用,给他单独开个小会即可。

      所以,相应地,我们邀请参会的人,应该是能在大多数议题上参与,且能发表意见和讨论的,如果一个人只是聆听,他压根不应该被叫到会议上当中,一方面他会在会议中做自己的事,其他人会觉得尴尬,另一方面,这也是在浪费他的时间。另外,受到会议结果直接影响的人、参与制定执行方案的人也应该被邀请到会议当中。

      我的一个朋友,他曾经在一家很喜欢开会的公司工作,他很无奈地告诉我,经常有一些会议,本来没有邀请他,但到了会议召开过程中,负责人突然把他叫到会议室。他为此感到很不爽,一方面他没有做任何会前准备,其次他没有与其他参与者同步信息,他提出的意见可能会很别扭,又或者主持人还需要花很长的时间把其他人晾在一边给他专门做信息同步,第三,他的时间也很宝贵,可能他正在专注做另一个工作,但却被拉到会议室里了。

      所以,作为会议主持人,一定要在会议前就 100% 确定参与者名单,并提前通知他们。 请尊重每个人的时间,并不是你的项目才是最紧急重要的

      会前信息同步与任务分发

      一个会议的时间不宜过长,我的体会是,超过 2 小时的会议就变得低效了。低效的原因很多,其中一个很关键的原因是与会者没有做会前准备,导致在开会期间大家还纷纷地打开电脑找资料,主持人还需要花大量时间做信息同步。

      这些工作,应该在会议前做好。

      在开会前,应该给每个参会者发一封邮件,告诉所有人:

      1. 我们遇到了什么问题,为什么要开会?
      2. 开会的理想结果是什么?
      3. 大家在开会前需要了解哪些基本信息?
      4. 每个人在开会前需要分别准备哪些材料?
      5. 大家有哪些初步想法和需求?

      同时,你还应该与核心的参会者在会前私下交谈:

      1. 他是否需要了解额外的信息,而这些信息不方便同步给所有人。
      2. 了解他是否有特殊的需求。
      3. 一对一明确每个人各自需要准备的材料,而不是仅仅群发邮件。

      这与项目管理的原则是一样的,除了要让所有人信息同步,还应点对点确保每个人都做好会前准备。

      制定议程和传达

      当我们已经完成的信息同步和相关需求的收集,结合会议的目标,这时我们就可以制定会议议程了。制定议程除了需要明确每个环节需要讨论的主题、时间和负责人之外,还应考虑议程的顺序,我在考虑议程顺序时,一般遵循 2 个原则:

      1. 循序渐进、逻辑相关。让讨论的问题一步一步深化。
      2. 参与者人数多的议题放在前面,随后递减,让个人不需要参与全程讨论的人可以回去工作。

      制定完议程,我们可以给所有与会者发一封邮件,让大家做补充,有时我们一个大脑制定的议程不一定是足够完善的。

      私下搞定那些潜在的「捣乱者」

      开会过程中可能会出现一些「捣乱者」,他们并不是故意捣蛋,但会延长开会的时间以及让其他参会者感觉进入到垃圾时间。

      比如,本次开会的目的是确定下一个版本的产品开发进度,当大家在纷纷讨论进度表的时候,有一个人突然跳起来说,我觉得这个版本的功能不对,不应该这样做,现在讨论进度没有意义,我们应该回到功能讨论。

      这时,作为会议主持人你有两个选择,打断他的发言,继续讨论进度,他可能会因此不爽,从而消极参会或离场;让他发言,其他人感到不爽,因为进度已经讨论了一半。

      无论是哪种选择,对会议的影响都是消极的, 一个更好的做法是在开会前,与有可能「捣乱」的人私下交流,跟他们 100% 明确,产品功能已经锁定,会议是讨论进度的,不会做功能变更;公司已经确认了这个方向,不会做改变,我们应该相信公司的决策。

      怎样找到这些可能会「捣乱」的人?一方面看性格,另一方面可以从日程的工作中观察,总能发现一些蛛丝马迹。

      提前 10 分钟把会议室的设备准备好

      别在开会过程中寻找 VGA 转接口,别在会议开始时发现自己搞不定投影仪。作为会议主持人,应该在开会前就预订好会议室,并在会议室里准备好开会所需要的各种道具、调试好所有的设备。

      理由很简单,开会需要高效, 别在本来只需要付出一个人 10 分钟时间的事情上,浪费所有人 10 分钟时间

      开会技巧

      好了,现在我们进入到开会的流程。

      沟通基本原则

      一个好的会议主持人,需要有良好的沟通技巧,在很多关于如何沟通、如何好好说话的书和文章里,都提到这些基本的沟通互动原则:

      1. 维护自尊,加强自信
      2. 仔细聆听,善意回应
      3. 寻求帮助,鼓励参与
      4. 分享观点,坦诚交流
      5. 给予支持,鼓励承担

      这些原则不是我创立的,我也相信,大多数人在开会和沟通过程中,或多或少都用过这些原则。简单说一下这 5 个原则在开会这件事上的作用(假设你是会议主持人)。

      维护自尊是指当参会者表现出消极或缺乏信心时,你应当鼓励他们,让他们树立起信心,从而能积极参与到后续的讨论当中。很多时候,称赞是有必要的。比如当一个新人胆颤心惊发表了一个观点后,虽然他的意见可能很一般,但为了鼓励他,你可以说,「这个想法不错,我们先记录下来」。

      仔细聆听和善意回应,是让会议能正常进行的基本原则。没有人喜欢自己的发言被其他人忽略,即使他们是带着情绪发言的。作为主持人,当你遇到有人很愤怒的发言时,你可以仔细聆听完后,安抚对方的情绪,并重新用自己的语言,温和地复述,这样,其他人会更容易接受。

      寻求帮助是鼓励参与的好方法。有些人开会开着开着可能就不再发言了,但这并不是你想要的结果,你可以通过向对方提问的方式,让他重新参与。比如,当上一个人已经发言完毕后,你可以问他,「刚才 XX 的方案你觉得如何」,事实上,这不是一种好的鼓励参与方式,因为对方可以消极地回答我觉得挺好的。这时,你可以换一种寻求帮助的方式,「刚才 XX 的方案中提到了 YY,YY 是你所负责的项目,你能给 XX 提供怎样的支持」,这样,他就可能没法消极应对了。

      情人之间要 坦诚相对,开会时也应该一样。如果你对别人坦诚,别人也极有可能对你坦诚,这样开会才能找到根本问题,从根上解决。如果每一个都遮遮掩掩,说一半不说一半,生怕其它部门的人抢了自己的功劳,对公司来讲没有益处。作为会议主持人,应该先从自己做起,自己先真实地表达看法,坦诚地说出观点和信息,然后鼓励其他人坦诚交流。

      给予支持,鼓励承担是指,作为会议主持人,我们可能不是项目的负责人,又或者项目会被拆分为很多小项目,每个小项目都应该有相应的负责人。我们应该鼓励每个人都成为项目的一个小的负责人,这样大家才能更有责任感地参与,但有时有些人可能怕事不敢承担,这时就需要给予适当的鼓励了。比如,你可以说,「XX,这部分的工作你最熟悉,由你来承担,如果在资源上有问题,随时跟我沟通,我来协调给你调派」。

      开始开会

      熟悉了一些沟通原则后,让我们来正式开会。

      开会的第一件事,是召集人也就是主持人,开启本次会议。虽然你已经给大家发过邮件,说了本次会议的目的和理想目标,但在正式进入讨论前,依然需要和所有人强调。你需要强调的包括:

      1. 会议的理想结果。
      2. 达成理想结果的意义:对公司、对团队、对每一个人、对客户、市场的意义和好处。
      3. 如果不能达成理想结果,会有什么坏处和严重后果。
      4. 不在本次开会中讨论的内容。
      5. 会议议程。

      第四点尤为重要,大家的时间都很宝贵,不要在会议上讨论超出议程的事项,尤其在大家对超出议程的事项没有做任何准备的时候。

      说明问题

      接下来,我们按照会议议程一个问题一个问题地讨论,在讨论方案之前,应该把你在会前收集到的信息、问题与所有人同步。

      这个过程最好不要自己发言,而是鼓励对应的负责人发言。因为在会前你已经让相应的负责人准备开会所需要的材料了,这时你让他们分别讲述从他们角度看到的数据、事实,以及遇到的问题。

      当相应的负责人说明完问题后,鼓励大家参与到讨论当中,讨论应该就问题本身展开,每个人因为立场的不同,会发表不同的看法,又或者其它部门的人能帮助发言人发现问题的根本,这个讨论环节应该是开放和平等的。

      当问题被剖析得足够充分,我们将进入到第三步,也就是「形成多个方案」。

      形成多个方案

      问题被剖析之后,当然要寻找解决问题的办法。

      这个时候,你可以询问,是否有人有解决方案,如果有,让其提出,并针对方案进行讨论,让所有人指出方案可能存在的问题以及是否有更好的解决方案。

      如果没有,作为会议主持人,你应当自己提出一个方案,你提出的方案不一定是最优的,你的目的是要激发大家的讨论, 有时候你甚至可以提出一个你自己都认为不可行的方案,引起大家的讨论

      注意在征求参与人员方案时,尽量使用开放式提问,而不是「是或否」。比如,当 XX 提出了一个方案后,你可以点名提问 YY,一种更好的提问方式不是「你对 XX 的方案有没有想法」,而是「你对 XX 的方案有什么顾虑和想法」或者「 XX 的方案有些不能解决的问题,对于这个问题,YY 你觉得应该怎样解决」。

      整个讨论结束后,一般来说,会形成多个解决方案。

      达成共识

      在第四步中,我们需要从第三步的多个方案中,选出最优的方案,并达成执行的具体方案。

      最优的方案应该是最接近理想结果的方案,而不是少数人坚持的方案,也不是投票表决的方案。作为主持人,你应当时刻记住会议的理想结果,从结果出发,选择最合适的方案,并让所有人参与到方案的调整过程。

      当方案确定下来,接下来就要讨论具体的执行细节了。这些是可能被讨论到的:

      1. 每个事项的负责人与分工
      2. 哪些部门参与,有哪些额外的资源需要公司领导协调
      3. 完成目标的最终时间点
      4. 关键事项的检查点(里程碑)

      至此,一个议程就结束了,后续的议程可以按照这 3 个步骤重复进行。

      会议总结

      当所有议程讨论完毕后,总结和感谢是很有必要的。

      在会议结束前,有 7 点是需要做的:

      1. 回顾会议上做出的所有决策和共识。
      2. 回顾所有确定的执行方案。
      3. 让方案的事项负责人复述他负责的事项和执行时间点。
      4. 定义项目紧接下来的沟通方式,比如每天晨会、使用 Trello、微信沟通等。
      5. 建议不同部门和个人之间,私下自行讨论执行的细节。
      6. 再次回顾项目的目标,鼓励所有参会者。
      7. 感谢所有人付出时间来参会,并表达对项目的信心。

      至此,线下开会过程已经结束了,但整个开会的过程还未结束,有两件事,依然需要去做。

      会后事项

      会议室的讨论结束,并不意味着开会的结束,往往,这只是项目的开始。为了更好地推进项目,也确保每个人明确自己的职责,有两件事是必须做的。

      会议记录

      会议上应当安排一个人做会议记录,并在会后将记录发给所有人。记录人最好不是主持人,因为主持人需要做的是沟通和推进工作,做记录会分散注意力。

      一个好的会议记录是这样的:

      1. 开会前,用熟练的办公软件建立了会议记录的基本格式,开会时,直接录入,而不用去理会排版、格式、错别字的问题。
      2. 文字记录的同时,开启录音记录。我一般使用 Evernote ,这样我的录音可以和会议记录存在同一个文件里。
      3. 会议结束后,记录者重新整理出一份结构化的记录,发给所有人。所谓结构化,就是每个人都能清晰且快速地回顾会议、明确责任。

      除了作为备忘录,会议记录的必要性还在于,它可以用来追究责任、避免路线走偏。

      后续跟进

      因为会议主持人往往是项目负责人,会议结束后,你还需要时刻跟进进度,尤其是会议结束后的第一天,你需要确保每个人都按照前一天定下的计划在开展工作,这个时候,一个项目管理软件对你来说可能是必要的。

      同时,你还需要对下一次开会做出计划,在达成什么里程碑的时候,或遇到什么样问题的时候,召开下一次会议。

      特殊情况的处理

      如果会议按照前面的流程推进,当然是最好不过了,但往往开会时会遇到一些困难情况,比如:

      1. 参会人员之间由讨论变成冲突,甚至有可能演变成肢体冲突
      2. 无论如何讨论,个别人员一直坚持己见,并尝试主导会议
      3. 有些人的个人习惯可能会降低会议的质量,比如一直在玩手机
      4. 有人中途在会议室电话,导致会议不得不被中断,等待他结束通话
      5. 当方案达成时,一些人表现出异常消极,甚至起了辞职的念头
      6. 有人的发言过长,其他人已经显得不耐烦
      7. 在某些问题上,多方一直僵持,无法讨论出结果

      对于这些问题,有几个干预建议:

      强调会议议程和理想结果

      当与会人员由讨论变成冲突,我们可以再次强调会议的理想结果,把他们从两个人的冲突中抽回来,回到讨论当中。

      鼓励轮流发言

      轮流发言是打断独占发言的好方法,当一个人喋喋不休,其他人已经不耐烦时,可以用一种合适的沟通方式打断他的发言。比如,你可以说,「不好意思我打断一下,我们今天会议的时间只有 1 个小时,我们必须完成整个议程的讨论,我们先听听 XX 的意见,完了有时间你再继续说你的想法」。

      用总结的方式打断独占发言

      用总结的方式打断独占发言是一种更好的方法,比如,还是前面的情况,你可以说,「我听懂了,你的想法是 XXXXX,其他人有什么意见么?」

      强调开会规则

      当发现有人一直打断其他人发言,或在过程中玩手机、接电话时,你可以强调会议规则,要求每个人发言过程中不允许其他人中断,要求大家把手机盖起来,如果要接电话请离开会议室等。

      休息时间

      休息时间可以用来解决两个问题,如果个别人员一直坚持己见阻碍会议正常进行,你可以在休息时间与他私下交谈,交谈方式可以参考前面提到的沟通技巧,以确保会议能够继续顺利进行。

      另一个可以被解决的问题是个别人员的消极回应,我们可以私下站在对方的角度,讲述方案看起来对他的影响是负面的,但如果方案做成了,其实对他有哪些好处,从而建立他的信心。

      推后解决问题

      如果有些问题实在无法在会议上解决,不妨考虑推后解决。往往在大家冷静之后,事情会有所进展,要么,双方找到更好的解决办法,要么,其中一方消气之后,稍作妥协。

      没有必要就无法解决的问题,在会议上浪费所有人的时间。

      总结

      开会不一定会浪费时间,做好充分准备,并善用沟通技巧的会,能快速让参与成员达成共识、形成方案。

      以上,是我开过那么多会,以及我在 Evernote 里所有关于高效开会和沟通的内容整理,希望对大家有用。如果你是创业公司的一员,除了这篇文章,这篇《 创业公司的员工都有哪些特点?》的文章可能也适合你。


      © 可能吧 |查看原文 |进行评论

      没数据积累和用户画像,我是这么做头条产品的……

      $
      0
      0

      本文作者从0到1规划头条产品,在此想把自己的实操经验分享出来,值得一阅。

      本来默默划船,在交流会上谈个性化推荐都不惹人注意的今日头条,毫无置疑现在已经被整个BAT围剿,内容领域的企业不自觉把今日头条当做竞争对手,非内容领域的互联网公司也都想来分一杯内容的羹,一夜间,互联网遍地都是feed流,不谈内容推荐算法都不好意思上桌了。

      笔者近期有幸从0到1规划头条产品,想把自己的实操经验分享出来,如果对感兴趣的朋友有帮助自然开心,更希望得到业界大佬的批评和指正,毕竟一个人摸索前进,还是很危险的。

      1. 明确定位

      经常使用阅读产品很大的感受是大平台很容易出现资讯没深度,垂直的内容资讯只在某几个如科技,互联网等几个领域做的还不错,我当时的设想是有没有可能做行业内深度资讯,尤其是一开始切入那些并未互联网化过深的行业,通过一个行业的试点,形成行业头条,在沉淀优质行业知识的同时,以最低成本去复制到其他行业。

      思考了挺久之后开始和老板汇报了,省去10000字具体说服过程,最终同意了,因为团队某公司与一个传统行业A有交集,所以一开始的切入行业就是行业A了,下面开始具体执行了,看着一共10多个技术人员,我陷入了深思……

      劣势简直不要太明显:

      • 没有数据积累;
      • 没有用户画像;
      • 团队没人从事过行业A。

      我要开始作死地做头条产品了……

      2. 头条产品整体设计

      我开始从三个层面去搭建产品,底层类型标签层,中层数据抓取分析层,顶层业务应用层。

      底层类型标签层

      底层根据具体行业进行梳理,本来这个过程应该产品和具体行业从业人员配合梳理,但是碍于资源有限,那就我来吧,肯定不足够详尽,但是一开始可以先跑起来。

      底层类型标签层分为类型和标签,类型有层级性,数据库预留到7级,实际梳理到3级就差不多了,如行业A,A公司是一个一级类型,A行业制造公司是二级分类,具体制造公司名称是3级类型,每个类型独立建表,每个表里关联海量标签到类型上,如行业A技术这个类型里我们找到行业A技术术语词典,删选后就作为标签关联到A技术这个类型下面,类型数最后梳理了600多,标签数量有10万多,数据库预留状态位,可以视情况进行启用关闭。

      中层数据抓取分析层

      数据抓取分析层分为爬虫部署,内容来源处理,数据归类。

      1、爬虫部署

      我以一个技术外行的角度把爬虫分为两类,一类是不定向爬虫,都是一个个单独网站,这种技术消耗较大,需挨个处理,如各个A行业公司的官网新闻中心和行业A平台网站,需单独处理,另一类定向爬虫,主要是有搜索功能的大资讯平台,如今日头条等,代码可复用,写好之后我直接建了一张表,专门放搜索爬虫的关键词,一堆关键词一套代码就可以实现,输入进去就把含有这些关键词的新闻抓取出来了,现在这张表关键词也有700多了,爬取来的内容量实在太大,建议用mongedb处理。

      2、内容来源处理

      数据过来后先进行来源梳理,划分优质来源和垃圾来源,提升优质来源内容的权重,优质来源主要是各公司官网,垃圾来源是指对具体行业而言,大量无意义的内容来自同一个来源,那么将他认定为垃圾来源,比如一个叫xx说车的来源在建筑行业被认定为垃圾来源,但是将来复制到汽车这个领域的时候,就不再是垃圾来源了,垃圾来源是一个长期的活,现在大概700多了,大部分垃圾来源是今日头条的头条号。

      3、数据归类

      过滤完垃圾源之后,就开始数据归类了,本质上是将新闻内容归到我们建立的一个个类型上,因为做行业资讯,希望一开始数据准度较高,我当时想了两种方案,第一种是将类型根据自己关联的海量标签按权重建立一个个模型,所有抓取来的文章做全文的分词处理,大量文章统计词频,每篇文章所有分词就有一个总的频率值,和类型模型比对,取相关性较高的,另一种就是把类型下面所属的标签和所有筛选过垃圾源的文章比对,含有标签的文章归到所属类型下面,含有同一类型标签越多,说明该文章相关性越高,为了快速上线就用第二种方案,但是相对,精度就差了一些,当然随着人工的介入,筛出一系列垃圾源,类型和标签维护工作的持续,内容准度好了一些。

      顶层业务应用层

      业务展现层主要是梳理目标用户感兴趣的关键词,将这些关键词关联到类型标签层的类型,这样,用户订阅关键词之后就可以看到这个关键词所属的内容,前台现在以及上线2个产品,一个订阅平台,行业头条,与之配套的是后台管理中心。

      1、订阅平台

      订阅平台半封闭,面向行业A企业用户和行业A自媒体从业者,释放出他们感兴趣的关键词,内容准度更高,企业用户订阅关键词,可以看到相关的资讯,看到平台具有的能力后,有欲望定制更多关键词,后台审核后继续部署爬虫,推送数据给用户,同时记录用户的所有行为数据。

      2、行业头条

      行业头条完全开放,面向准行业从业者以及泛行业爱好者,释放出更多关键词,但是较订阅平台,内容质量稍差,但是目标用户较广,所以寄希望记录用户的所有行为数据(如评论,阅读量,换一批事件,关注关键词等),得到用户反馈,建立用户画像,以达到根据不同用户画像推荐关键词的效果,为真正的推荐做准备。

      3、后台管理中心

      含有新闻管理,来源管理(优质来源,垃圾来源),类型/标签管理,用户行为管理,推送管理,关键词审核排期管理,评论搜索管理等,具体就不再详述了,有机会再详细介绍,简单的把产品框架梳理了一张图,和上面的论述结合起来,可能更方便理解。

      (注:侵权必究)

      3. 致同行

      不要动不动就要再造个今日头条,如果你的体验和算法做不到比他强百分之五十以上,正面硬刚基本没戏,找准自己的切入点,认清自己的优势;

      内容推荐从来都很危险,如果用户不需要的时候推荐,除非做到让用户惊喜,否则就是减分,用户一定要用的产品,用户只能忍着,可有可无的产品,极有可能被用户卸载,这点做公众号的朋友肯定深有感触,每次推送内容都怕掉粉。。

      因为对搜索一直比较有兴趣,所以简单阐述一下自己对输入法产品想做内容的建议吧。

      用户有自己了解资讯的需求:

      • 主动获取:RSS抓取(google订阅),关注/订阅(即刻)
      • 被打获取:平台推荐(传统门户,新闻网站),垂直类媒体资讯(36K,虎嗅等,最近冯大辉的readhub),个性化推荐(头条,一点资讯)

      这一类需求竞争极其大,还有一类是基于特定场景下,对资讯的了解诉求。

      比如找工作时,想了解某家公司;吃饭时,想了解附近餐馆的情况。

      这一类诉求特别长尾,目前多是怎么被满足的呢?

      主动搜索,到百度,知乎等平台搜索,但得到想要的资讯路径很长,比如你和朋友吃饭,你想知道附近有哪些好馆子,搜到的代价就就极高这种场景大量发生在哪里?聊天和查询的时候!这正是我觉得输入法切入资讯的机会,具体来讲:

      • 当和别人聊天说要跳槽,谈的某家公司,输入法输入时有个提示(如颜色变化等)能方便的推送公司的最新资讯;
      • 聊天约饭,方便推送出附近饭馆和评价;
      • 和男朋友说要买赵丽颖同款,男朋友能方便看到这些商品的资讯;

      这些诉求的背后数据,词汇出现的频率,输入法公司应该有足够的积累,大可根据词频做内容准备,当用户在输入东西的时候,给用户一个意外的惊喜,来达到资讯推荐的目的,希望有从事输入法这块的朋友能给予指导吧。

       

      作者:小呆(公众号:小呆自留地),野路子出身的产品,非常诚恳的希望有同行能够给出批评和建议~谢谢

      本文由 @小呆 原创发布于人人都是产品经理。未经许可,禁止转载。

      关于人工智能时代如何创业,李开复给科学家们上了一课

      $
      0
      0

      4 月 27 日,由长城会主办的全球移动互联网大会(GMIC)北京站在国家会议中心召开。在这场行业大会上,人工智能无疑是最受关注的一个领域。

      大会开场,GMIC 邀请物理学家、剑桥大学教授史蒂芬·霍金(Steven Hawking)以视频的方式做了一场主题为“让人工智能造福人类及其赖以生存的家园”。演讲中,霍金再次强调他对人工智能的忧思:

      强大的人工智能的崛起,要么是人类历史上最好的事,要么是最糟的。

      霍金演讲结束后,创新工场创始人及首席执行官李开复也围绕人工智能展开了演讲,主题为:“人工智能时代的科学家创业。”但在李开复看来,霍金提出的“超级智能”和“未来人工智能”碾压人类和要避免这个状况,并不是一个可根据今天科学推测出的必然结果。

      李开复认为人工智更现实更重要的意义应该是以下四件事情:

      • 人工智能将创造巨大财富,让人类第一次有机会脱离贫困。
      • 我们要担心今天手中拥有巨大人工智能力量和数据的公司,他们用数据来作恶。
      • 看到人工智能将要取代 50% 人的工作(在未来 10—15 年之间),这些人怎么办?教育怎么办?
      • 科学家尤其是人工智能科学家有什么使命有什么机会,是不是都要出来创业还是跟着霍金一起去寻找人类的未来。

      第四个话题也就是李开复今天演讲的主题。李开复是早期参与人工智能的科学家之一,同时又投身创业。在人工智能时代科学家如何投身创业这个话题上,作为“过来人”的李开复给出了以下建议:

      第一,创新固然重要,但不是最重要的,最重要的是做有用的创新。但科学家们往往会被自己的研究的东西所打动,也认为他所看到的是全世界人类所需要的。但是事实可能并不是这样。

      第二,科学家和创业者有非常大的不同:

      科学家追求的科研突破,创业者追求的是商业回报,科学家讲究严谨,科学家讲究速度,科学家要慢工出细活,而创业者要快速迭代。

      这六件事情往往是背道而驰的。科学家因为成年累月在这样的文化和基因之下发展,可能会把公司带到一个不那么务实、或者不那么快速迭代,或者不追求做过,或者不专注。

      第三,基于以上两个认识,李开复认为在人工智能的创业时代,应该让创业者能做他们擅长的事,让科学家做他们擅长的事。李开复介绍到,目前斯坦福用技术授权的方式,CMU 让教授创业是占股份的方式,这都是非常好的让科学家参与到创业和创造商业价值的方式。

      以下是演讲原文:

      李开复:霍金教授做了非常精彩的演讲,我认为人类有像霍金教授这样有远见的科学家,帮助我们的顶尖天才和世界最重要的政治家来帮我们策划一个更美好的未来是非常重要的。但是他提出的“超级智能”和“未来人工智能”碾压人类和要避免这个状况,我个人认为并不是一个可根据今天科学推测出的必然结果。当然非必然事件不代表我们不要关注它,还是要有聪明的人来想它,但我认为人工智能对于今天在座每一位最重要的意义应该是下面四件事情:

      第一人工智能将创造巨大财富,让人类第一次有机会脱离贫困。第二我们要担心今天手中拥有巨大人工智能力量和数据的公司,他们用数据来作恶。第三看到人工智能将要取代 50% 人的工作(在未来10—15年之间),这些人怎么办,还有更重要的是教育怎么办。第四个是非常重要的话题,科学家尤其是人工智能科学家有什么使命有什么机会,是不是都要出来创业还是跟着霍金一起去寻找人类的未来。

      我自己也是一个科学家创业,非常早的时候做了人工智能,我记得申请我卡内基梅隆博士时,作文就是申请人工智能+我们未来改造未来的世界,希望参与这个行业。

      1986 年还没有 AlphaGo、深蓝的时候,我写了第一篇文章,有关击败世界黑白棋冠军。1988 年做博士论文,当时是第一个非语音识别,在《纽约时报》上有所报道。之后我进入苹果,这里可以看到一个很萌的照片,在《早安美国》节目上谈到了语音识别将如何进入主流,也被《华尔街报日报》报道,当天股票涨了 2.5 毛钱,后来跌回来了。

      所以我充满的信心,因为我做着各方面顶尖的人工智能工作,认为人工智能和其他技术,比如说虚拟世界即将步入主流,于是就从 SGA 公司做一个内部创业。

      当时我们做的是,能不能让每一个网页充满了 3D,3D 的游戏、动画让网页做得更精彩,让人们浏览的不是网页而是一个一个房间,一定程度上和今天的 VR 非常相似,这次创业非常失败,2000 万美元的投入,100 个员工,几乎全军覆没。从这个失败里我得到了一些教训,我想跟大家分享。

      今天有一次在 MIT 演讲的时候,要求每一位讲者演讲完后留下一句话,我留下的那句话是——“创新固然重要,但不是最重要的,最重要的是做有用的创新”。

      科学家们往往会被自己的研究、自己酷的东西所打动,也认为他所看到的酷的东西是全世界人类所需要的。但是事实可能并不是这样。我们想的是 3D 有多酷,但没有想到 3D 硬件是否准备好,用户对 3D 世界有什么需求,为什么 VC 投资我们,怎么把创业做成有赚钱、有经济价值的创业,我基本错过了所有创业者应该走的路。我当年做的是顶尖研究,进入的是顶尖公司,做出的产品依然遭遇了滑铁卢,人工智科学家应该怎么想?

      其实本质上,科学家和创业者有非常大的不同,科学家追求的科研突破,创业者追求的是商业回报,科学家讲究严谨,科学家讲究速度,科学家要慢工出细活,而创业者要快速迭代,这六件事情往往是背道而驰的。

      这 6 件事情中最重要的一件,是我读博士、做教授过程中,深深体会到在科研领域里每次问的第一个问题是什么,这件事情别人是否做过,是不是全新的,如果有人做过一定要看别人,看自己有没有增加的价值,增加的价值不如突破的价值大,所以每个科学家不断要求创新,所起创新是做前人所未做过的工作,这句话代表了科学、追求科学精神的一种含义。

      但是一个创业者,或者一个 VC,他更重视的是什么?怎么样打造产品,怎么样产生商业价值。甚至在 VC 今天投资过程中,想想我们投的每一个团队冒了人才的风险、商业的风险、竞争的风险、执行的风险,我们非常不要再冒科技风险了,所以我们更宁愿看一个团队说:这个技术已经被证明了,只是把它应用在场景里。

      刚才两段话让大家看到了科学家本质和创业者、VC 本质截然不同,一个仅仅追求做前人未做过的工作不考虑它有没有用,而只需要赚钱不希望冒科技风险,这两个通常走不到一起。而且走到了一起,科学家因为成年累月、在这样的文化和基因之下发展,可能会把公司带到一个不那么务实、或者不那么快速迭代,或者不追求做过,或者不专注,科学家很聪明,每个人有好多点子,一个创业公司每天出个点子公司会死掉,因为什么都做。

      所以精益创业之父 STEVE BLANK 帮助科学家创业,总结是科学家必须要小心,因为题目往往是冷僻的,没有多大市场;第二,选题跟风口有很大差异;第三,科学家不太愿意承认自己很可能不具备把技术霜叶转换成价值的洞察力和执行力。每个想创业的科学家都一定要真诚的问自己会否面对这些问题,我想你面对大家的时候希望保持自信,但是你自己应该知道,对你来说是不是一个问题。

      当然,今天,科学创业大家了一个有史以来最好的时机,可以看到除了今天谈的人工智能之外,在区块链、生命科学、高能电视、细胞扩增、基因编程,几乎每个领域都是创业的机会,我在这里绝对没有打压科学家参与创业的意思,只是参加创业的时候一定要想清楚,过去那么多科学家包括自己碰到的死穴怎么去避免,有成功的案例我的朋友李博士创造了有价值的公司,国内也会看到很多类似的公司。但看到更多的是教授恪守他的岗位,美国谷歌就是典型例子,斯坦福和谷歌两个创始人的教授,没有参与,但技术授权有 3.4 亿美金,这是一个模式。

      谈到 AI 创业,为什么 AI 需要 AI 科学家?每一个时代的科学公司都有做这样的创业,互联网时代注定是被海龟创业,因为海龟在国外看到了互联网的崛起带到中国。移动互联网应用方面注定是产品经理的创业,因为这个时代我们需要快速迭代产品,因此使那些既懂技术又懂用户和市场的人成为时代的傲儿。在 O2O 时代,把地面销售和后台技术整合起来,这是我们需要衡量的。美团、滴滴就是这样的搭配。

      人工智能时代来到了,最核心的、最需要的一定是 AI 科学家,因为今天 AI 技术还没有进入主流,AI 平台还没有产生,因此 AI 应用还不能井喷,只有少数手中掌握着如何把 AI 应用起来的科学家能够创业。但这些科学家有刚才讲的所有科学家的特点:追求创新、写论文、不太知道市场。

      大部分科学家的创业,在中国都是在做人脸识别,做计算机识别,在座的张宏江博士是这方面的是鼻祖,他的徒子徒孙每个人都在做人脸识别工作。这一定程度上看到我们的科学家是缺乏想象力的,人工智能有那么多的应用,人脸识别绝对不是最好的一个,科学家需要一个懂企业销售的人跟他们搭配,这里当然是乱搭,两个没有关系的人,但就是像的李菲菲、胡温马(音)这样的人搭配,才能碰撞出真正的超级独角兽的 AI 构思。

      AI 本身不是一个消费者的应用,当然 BAT 非常幸运的应用在消费者,但我们 AI 创业不能自带流量,没有流量和数据有什么用?所以做出来的 AI 还是给企业应用,企业金融、医疗有数据,我们结果他的方案,在他的公司需要企业销售,需要懂 AI 的解决方案,这才是一个黄金搭配来解决 AI 创业。所以 AI 科学家的参与是非常重要的。

      AI 最大突破是七年前深度学习。我们可以理解深度学习是一个超级 EXCEL 表,很多数据丢进去以后,再丢一个数据,就能够做出预测、判断或者分类,很多脸丢进去就认识谁是谁,很多棋盘、一次一次围棋比赛丢进去就知道下哪一步棋,很多淘宝商品和用户丢进去,就知道你想买什么了。所以未来的 AI 肯定可以知道你今天晚上想吃什么,比你更知道;知道你去哪里度假,比你更知道;甚至在座未婚的可能喜欢什么样的配偶比你更知道。这就是一个先知的、对未来能够做非常强大预测的 AI。AI 在这个阶段是单领域大数据驱动的引擎,可以把它认为是一个黑核,可以进入各种领域。

      AI 扩张一定会经过下面三个阶段。

      第一个阶段,把已有的大数据用起来,BAT 在用,今日头条、快手、滴滴、美团都在用。另外,金融领域可以用,比如我们投资的智融集团的用钱宝,一个月放出 30 亿的贷款,因为可以把已有的数据、用户数据激活做小额贷款,还有医疗。

      第二个阶段是把没有的数据收集起来、上传起来,用各种摄像头把人脸收集起来,收集了 500 亿张人脸,所以随时识别 300 万张人脸,这不是一个人类的功能,而是超人类的功能。

      第三个是无人驾驶的机器人时代的来临,从工业走向商业走向家机器人,从 2 到 3、4,走向全方位的无人驾驶。这是我们投资的聚石科技。

      这三步曲大概是未来五年、十年、十五年的蓝图。刚才霍金描述的未来是真实的,不太确定的是 AI 会否有意识、人类情感、掌控人类、做我们的工具、会否自我重新迭代、自我重新重写等等,这些是未知的,但已知的可以推出这些应用。应用推出来以后会产生巨大结果、产生巨大价值,国家征税,取代大量的工作,这些工作可以用大量的征税补助下岗人重新训练自己,以改造教育。

      所以人工智能时代对经济有巨大改变,50% 下岗人该怎么办,未来教育该怎么办都是我们需要解决的问题。(PPT 图)从 12 点这边逆时针转,红色是必然被取代的工作,绿色是被修改的工作,黄色是暂时还不能取代的工作,转到最上面是最难取代的工作,能取代和不能取代很容易解释,能取代的就是大数据可以针对一个目标函数做一个决策,比人更好的决策,那你就可以取代了。大部分工作都是这样的。绿色代表,是当有一天机器比医生可以做更好的诊断,但机器是冷冰冰的,医生可以包装一个人性化的接口,让病人得到安慰,让安慰剂起效应启动,使病人存活和心理感受更好。

      右边这些是现在人工智能还不能做的,包括艺术、人类学、管理者、决策者,更包括最大的发明家。所以 AI 时代的人才结构,我们看到有大量的服务型人才,包括罗辑思维说的叠衣师、爱心陪伴,人的爱是不能被机器取代的。往上是会把人工智能当作工具的人,比如说医师变成 AI 工具和人的过渡者,再上是发明每一个领域的新技术掌控者,再上是跨领域的工作者,懂很多领域。当然最最顶尖的就是发明新的 AI,掌控 AI,他们写人不但是最聪明的最懂技术的,而且抱有最大的科学技术革命。

      AI 怎么参与?有四条路子,从走下角讲起来:

      第一,自己撸起袖子做创业,这个最困难,因为科学家本质是创新,而不是创造商业价值。往下找个商业合伙人一起看。右上,留在学校,继续做创新,把技术授权出去,让学生或者别人做,这是我大大鼓励的。最后一个是提供开源,发布数据和内容。今天 AI 科学家苦于没有 BAT 手中的数据,希望用更开源的方法把手中的工作做出来。所以真的希望鼓励科学家,看清楚学家才是科研的净土,不要因为能够赚钱就加入到公司或者创业。顶尖科学家是我们稀缺的资源,希望你们恪守自己的岗位,把技术推到更高的一个层次,深度学习只是人工智能的第一步,未来还有很多机会。

      恪守自己的科研岗位也会得到商业价值,比如经过技术授权。如果你决定创业,也希望你能够了解,创业的话就要知道客户才是上帝,需要知道怎么样去拿最有价值的 VC 钱帮助你补足短板,比如创新工场,要能够有纪律、有效率的解决问题,而不是一个又一个问题的提出,不是提出问题,而是解决问题,解决的时候重视效率,做你擅长的事,找合作伙伴,补足你的短板。

      作为社会,必须做很多事情让教授既能得到利益,也能得到名声,还能够做有趣的创业。在报酬方面,让整个研究界提高科学家的报酬。在生育和大奖方面,比如图灵奖和科学奖都是很好的支持。资源方面应该给更多的数据,不仅让 BAT 有最大的数据,教授也有最大的数据。时间上要思考怎解决不浪费时间的问题。斯坦福用技术授权的方式,CMU 让教授创业是占股份的方式,这都是非常好的方式,国际上尤其在中国都要好好思考,怎么样把土壤做得让创业者能做他们擅长的事,让科学家做他们擅长的事,让两者有机的结合,而不要强逼每个科学家都一定要出去创业。

      所以结论是,科学家挺苦逼的,非常重要,我们应该以珍惜国宝的态度支持顶尖的科学家,谢谢大家。

      #欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

      爱范儿 |原文链接· 查看评论· 新浪微博


      Viewing all 15896 articles
      Browse latest View live


      <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>