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

OpenTSDB 详解

$
0
0

1. OpenTSDB介绍

OpenTSDB用HBase存储所有的时序(无须采样)来构建一个分布式、可伸缩的时间序列数据库。它支持秒级数据采集所有metrics,支持永久存储,可以做容量规划,并很容易的接入到现有的报警系统里。OpenTSDB可以从大规模的集群(包括集群中的网络设备、操作系统、应用程序)中获取相应的metrics并进行存储、索引以及服务,从而使得这些数据更容易让人理解,如web化、图形化等。 对于运维工程师而言,OpenTSDB可以获取基础设施和服务的实时状态信息,展示集群的各种软硬件错误,性能变化以及性能瓶颈。对于管理者而言,OpenTSDB可以衡量系统的SLA,理解复杂系统间的相互作用,展示资源消耗情况。集群的整体作业情况,可以用以辅助预算和集群资源协调。对于开发者而言,OpenTSDB可以展示集群的主要性能瓶颈,经常出现的错误,从而可以着力重点解决重要问题。 OpenTSDB使用LGPLv2.1+开源协议,目前版本为2.X。

2. 安装OpenTSDB

2.1 依赖

OpenTSDB依赖jdk和 Gnuplot,Gnuplot需要提前安装,版本要求为最小4.2,最大4.4,执行以下命令安装即可:
yum install gnuplot autoconf
apt-get install gnuplot
OpenTSDB是用java编写的,但是项目构建不是用的java的方式而是使用的C、C++程序员构建项目的方式。运行时依赖: 可选的编译时依赖:
  • GWT 2.4 (ASLv2)
可选的单元测试依赖:

2.2 下载并编译源代码

首先安装必要依赖:
yum install gnuplot automake autoconf git -y
下载源代码,可以指定最新版本或者手动checkout
git clone git://github.com/OpenTSDB/opentsdb.git
cd opentsdb
./build.sh

2.3 安装

  • 1. 首先安装一个单节点或者多节点集群的hbase环境,hbase版本要求为0.94
  • 2. 设置环境变量并创建opentsdb使用的表,需要设置的环境变量为 COMPRESSIONHBASE_HOME,前者设置是否启用压缩,或者设置hbase home目录。如果使用压缩,则还需要安装lzo
  • 3. 执行建表语句 src/create_table.sh
  • 4. 启动TSD
tsdtmp=${TMPDIR-'/tmp'}/tsd    # For best performance, make sure
mkdir -p "$tsdtmp"             # your temporary directory uses tmpfs
./build/tsdb tsd --port=4242 --staticroot=build/staticroot --cachedir="$tsdtmp" --auto-metric
如果你使用的是hbase集群,则你还需要设置 --zkquorum--cachedir对应的目录会产生一些临时文件,你可以设置cron定时任务进行删除。添加 --auto-metric,则当新的数据被搜集时自动创建指标。 你可以将这些参数编写到配置文件中,然后通过 --config指定该文件所在路径。 从源代码安装gnuplot、autoconf、opentsdb以及tcollector,可以参考: OpenTSDB & tcollector 安装部署(Installation and Deployment)

3. 使用向导

3.1 配置

OpenTSDB的配置参数可以在命令行指定,也可以在配置文件中指定。配置文件使用的是java的properties文件,文件中key为小写,支持逗号连接字符串但是不能有空格。所有的OpenTSDB属性都以tsdb开头,例如:
# List of Zookeeper hosts that manage the HBase cluster
tsd.storage.hbase.zk_quorum = 192.168.1.100
配置参数优先级: 命令行参数 > 配置文件 > 默认值 你可以在命令行中通过 --config指定配置文件所在路径,如果没有指定,OpenTSDB会从以下路径寻找配置文件:
  • ./opentsdb.conf
  • /etc/opentsdb.conf
  • /etc/opentsdb/opentsdb.conf
  • /opt/opentsdb/opentsdb.conf
如果一个合法的配置文件没有找到并且一些必须参数没有设置,TSD进程将不会启动。 配置文件中可配置的属性请参考: Properties

3.2 基本概念

在深入理解OpenTSDB之前,需要了解一些基本概念。
  • Cardinality。基数,在数学中定义为一个集合中的一些元素,在数据库中定义为一个索引的一些唯一元素,在OpenTSDB定义为:
  • 一个给定指标的一些唯一时间序列
  • 和一个标签名称相关联的一些唯一标签值
在OpenTSDB中拥有高基数的指标在查询过程中返回的值要多于低基数的指标,这样花费的时间也就越多。 Compaction。在OpenTSDB中,会将多列合并到一列之中以减少磁盘占用空间,这和hbase中的Compaction不一样。这个过程会在TSD写数据或者查询过程中不定期的发生。 Data Point。每一个指标可以被记录为某一个时间点的一个数值。Data Point包括以下部分:
  • 一个指标:metric
  • 一个数值
  • 这个数值被记录的时间戳
  • 多个标签
Metric。一个可测量的单位的标称。 metric不包括一个数值或一个时间,其仅仅是一个标签,包含数值和时间的叫 datapoints,metric是用逗号连接的不允许有空格,例如:
  • hours.worked
  • webserver.downloads
  • accumulation.snow
Tags。一个metric应该描述什么东西被测量,在OpenTSDB中,其不应该定义的太简单。通常,更好的做法是用Tags来描述具有相同维度的metric。Tags由tagk和tagv组成,前者表示一个分组,后者表示一个特定的项。 Time Series。一个metric的带有多个tag的data point集合。 Timestamp。一个绝对时间,用来描述一个数值或者一个给定的metric是在什么时候定义的。 Value。一个Value表示一个metric的实际数值。 UID。在OpenTSDB中,每一个metric、tagk或者tagv在创建的时候被分配一个唯一标识叫做UID,他们组合在一起可以创建一个序列的UID或者 TSUID。在OpenTSDB的存储中,对于每一个metric、tagk或者tagv都存在从0开始的计数器,每来一个新的metric、tagk或者tagv,对应的计数器就会加1。当data point写到TSD时,UID是自动分配的。你也可以手动分配UID,前提是 auto metric被设置为true。默认地,UID被编码为3Bytes,每一种UID类型最多可以有16,777,215个UID。你也可以修改源代码改为4Bytes。UID的展示有几种方式,最常见的方式是通过http api访问时,3 bytes的UID被编码为16进制的字符串。例如,UID为1的写为二进制的形式为 000000000000000000000001,最为一个无符号的byte数组,其可以表示为 [0,0,1],编码为16进制字符串为 000001,其中每一位左边都被补上0,如果其不足两位。故,UID为255的会显示为 [0,0,255]0000FF。 关于为什么使用UID而不使用hashes,可以参考: why-uids TSUID。当一个data point被写到OpenTSDB时,其row key格式为: <metric_UID><timestamp><tagk1_UID><tagv1_UID>[...<tagkN_UID><tagvN_UID>],不考虑时间戳的话,将其余部分都转换为UID,然后拼在一起,就可以组成为TSUID。 Metadata。主要用于记录data point的一些附加的信息,方便搜索和跟踪,分为UIDMeta和TSMeta。 每一个UID都有一个metadata记录保存在 tsdb-uid表中,每一个UID包括一些不可变的字段,如 uidtypenamecreated字段表示什么时候被创建,还可以有一些额外字段,如 descriptionnotesdisplayName和一些 custom key/value对,详细信息,可以查看  /api/uid/uidmeta 同样,每一个TSUID可以对应一个TSMeta,记录在 tsdb-uid中,其包括的字段有 tsuidmetrictagslastReceivedcreated,可选的字段有 descriptionnotes,详细信息,可以查看 /api/uid/tsmeta 开启Metadata有以下几个参数:
  • tsd.core.meta.enable_realtime_uid
  • tsd.core.meta.enable_tsuid_tracking
  • tsd.core.meta.enable_tsuid_incrementing
  • tsd.core.meta.enable_realtime_ts
metadata的另外一个形式是 Annotations,详细说明,请参考 annotations Tree

3.3 数据存储方式

OpenTSDB使用HBase作为后端存储,在安装OpenTSDB之前,需要先启动一个hbase节点或者集群,然后再执行建表语句 src/create_table.sh创建hbase表。建表语句如下:
create '$UID_TABLE',
  {NAME =&gt; 'id', COMPRESSION =&gt; '$COMPRESSION', BLOOMFILTER =&gt; '$BLOOMFILTER'},
  {NAME =&gt; 'name', COMPRESSION =&gt; '$COMPRESSION', BLOOMFILTER =&gt; '$BLOOMFILTER'}

create '$TSDB_TABLE',
  {NAME =&gt; 't', VERSIONS =&gt; 1, COMPRESSION =&gt; '$COMPRESSION', BLOOMFILTER =&gt; '$BLOOMFILTER'}

create '$TREE_TABLE',
  {NAME =&gt; 't', VERSIONS =&gt; 1, COMPRESSION =&gt; '$COMPRESSION', BLOOMFILTER =&gt; '$BLOOMFILTER'}

create '$META_TABLE',
  {NAME =&gt; 'name', COMPRESSION =&gt; '$COMPRESSION', BLOOMFILTER =&gt; '$BLOOMFILTER'}
从上面可以看出一共创建了4张表,并且可以设置是否压缩、是否启用布隆过滤、保存版本号等等,如果追求hbase读写性能,还可以预建分区。

3.3.1 Data Table Schema

在OpenTSDB中,所有数据存储在一张叫做 tsdb的表中,这是为了充分利用hbase有序和region分布式的特点。所有的值都保存在列族 t中。 rowkey为 <metric_uid><timestamp><tagk1><tagv1>[...<tagkN><tagvN>],UID默认编码为3 Bytes,而时间戳会编码为4 Bytes OpenTSDB的tsdb启动之后,会监控指定的socket端口(默认为4242),接收到监控数据,包括指标、时间戳、数据、tag标签,tag标签包括tag名称ID和tag值ID。例如:
myservice.latency.avg 1292148123 42 reqtype=foo host=web42
对于指标myservice.latency.avg的ID为:[0, 0, -69],reqtype标签名称的ID为:[0, 0, 1], foo标签值的ID为:[0, 1, 11], 标签名称的ID为:[0, 0, 2] web42标签值的ID为:[0, -7, 42],他们组成rowkey:
[0, 0, -69, 77, 4, -99, 32, 0, 0, 1, 0, 1, 11, 0, 0, 2, 0, -7, 42]
 `-------'  `------------'  `-----'  `------'  `-----'  `-------'
 metric ID  base timestamp  name ID  value ID  name ID  value ID
                            `---------------'  `---------------'
                                first tag         second tag
row表示格式为: 每个数字对应1 byte
  • [0, 0, -69] metric ID
  • [77, 4, -99, 32] base timestamp = 1292148000. timestamps in the row key are rounded down to a 60 minute boundary。也就是说对于同一个小时的metric + tags相同的数据都会存放在一个row下面
  • [0, 0, 1] "reqtype" index
  • [0, 1, 11] "foo" index
  • [0, 0, 2] "host" index
  • [0, -7, 42] "web42" index
NOTE:可以看到,对于metric + tags相同的数据都会连续存放,且metic相同的数据也会连续存放,这样对于scan以及做aggregation都非常有帮助 column qualifier 占用2 bytes或者4 bytes,占用2 bytes时表示以秒为单位的偏移,格式为:
  • 12 bits:相对row表示的小时的delta, 最多2^ 12 = 4096 > 3600因此没有问题
  • 4 bits:format flags
    • 1 bit: an integer or floating point
    • 3 bits: 标明数据的长度,其长度必须是1、2、4、8。 000表示1个byte, 010表示2byte, 011表示4byte, 100表示8byte
占用4 bytes时表示以毫秒为单位的偏移,格式为:
  • 4 bits:十六进制的 1或者 F
  • 22 bits:毫秒偏移
  • 2 bit:保留
  • 4 bits: format flags
    • 1 bit: an integer or floating point,0表示整数,1表示浮点数
    • 3 bits: 标明数据的长度,其长度必须是1、2、4、8。 000表示1个byte, 010表示2byte, 011表示4byte, 100表示8byte
举例: 对于时间戳为1292148123的数据点来说,其转换为以小时为单位的基准时间(去掉小时后的秒)为129214800,偏移为123,转换为二进制为 1111011,因为该值为整数且长度为8位(对应为2byte,故最后3bit为 100),故其对应的列族名为: 0000011110110100,将其转换为十六进制为 07B4 value 使用8bytes存储,既可以存储long,也可以存储double。 总结一下, tsdb表结构如下: opentsdb-tsdb-schema

3.3.2 UID Table Schema

一个单独的较小的表叫做 tsdb-uid用来存储UID映射,包括正向的和反向的。存在两列族,一列族叫做 name用来将一个UID映射到一个字符串,另一个列族叫做 id,用来将字符串映射到UID。列族的每一行都至少有以下三列中的一个:
  • metrics 将metric的名称映射到UID
  • tagk 将tag名称映射到UID
  • tagv 将tag的值映射到UID
如果配置了metadata,则 name列族还可以包括额外的metatata列。
  • id 列族
Row Key - 将会是一个分配到UID的字符串,例如,对于一个指标可能有一个值为 sys.cpu.user或者对于一个标签其值可能为 42 Column Qualifiers - 上面三种列类型中一种。 Column Value - 一个无符号的整数,默认被编码为3个byte,其值为UID。 例如以下几行数据是从 tsdb-uid表中查询出来的数据,第一个列为row key,第二列为"列族:列名",第三列为值,对应为UID
proc.stat.cpu id:metrics \x00\x00\x01
host id:tagk \x00\x00\x01
cdh1 id:tagv \x00\x00\x01
  • name 列族
Row Key - 为UID Column Qualifiers - 上面三种列类型中一种或者为 metrics_metatagk_metatagv_meta Column Value - 与UID对应的字符串,对于一个 *_meta列,其值将会是一个UTF-8编码的JSON格式字符串。不要在OpenTSDB外部去修改该值,其中的字段顺序会影响 CAS调用。 例如,以下几行数据是从 tsdb-uid表中查询出来的数据,第一个列为row key,第二列为"列族:列名",第三列为值,对应为UID
\x00\x00\x01 name:metrics proc.stat.cpu
\x00\x00\x01 name:tagk host
\x00\x00\x01 name:tagv cdh1
\x00\x00\x01 name:tagk_meta {"uid":"000001","type":"TAGK","name":"host","description":"","notes":"","created":1395213193,"custom":null,"displayName":""}
\x00\x00\x01 name:tagv_meta {"uid":"000001","type":"TAGV","name":"cdh1","description":"","notes":"","created":1395213193,"custom":null,"displayName":""}
\x00\x00\x01 name:metric_meta {"uid":"000001","type":"METRIC","name":"metrics proc.stat.cpu","description":"","notes":"","created":1395213193,"custom":null,"displayName":""}
总结一下, tsdb-uid表结构如下: opentsdb-tsdb-uid-schema 上图对应的一个datapoint如下:
proc.stat.cpu 1292148123 80 host=cdh1
从上图可以看出 tsdb-uid的表结构以及数据存储方式,对于一个data point来说,其被保存到opentsdb之前,会对 metricstagktagvmetric_metatagk_metatagv_meta生成一个UID(如上图中的 000001),然后将其插入hbase表中,rowkey为UID,同时会存储多行记录,分别保存 metricstagktagvmetric_metatagk_metatagv_meta到UID的映射。

3.3.3 Meta Table Schema

这个表是OpenTSDB中不同时间序列的一个索引,可以用来存储一些额外的信息。这个表名称叫做 tsdb-meta,该表只有一个列族 name,两个列,分别为 ts_metats_ctr,该表中数据如下:
\x00\x00\x01\x00\x00\x01\x00\x00\x01 name:ts_ctr \x00\x00\x00\x00\x00\x00\x00p
\x00\x00\x01\x00\x00\x01\x00\x00\x01 name:ts_meta {"tsuid":"000001000001000001","displayName":"","description":"","notes":"","created":1395213196,"custom":null,"units":"","dataType":"","retention":0,"max":"NaN","min":"NaN"}

\x00\x00\x02\x00\x00\x01\x00\x00\x01 name:ts_ctr \x00\x00\x00\x00\x00\x00\x00p
\x00\x00\x02\x00\x00\x01\x00\x00\x01 name:ts_meta {"tsuid":"000002000001000001","displayName":"","description":"","notes":"","created":1395213196,"custom":null,"units":"","dataType":"","retention":0,"max":"NaN","min":"NaN"}
Row Key 和 tsdb表一样,其中不包含时间戳, <metric_uid><tagk1><tagv1>[...<tagkN><tagvN>] TSMeta Column 和UIDMeta相似,其为UTF-8编码的JSON格式字符串 ts_ctr Column 计数器,用来记录一个时间序列中存储的数据个数,其列名为 ts_ctr,为8位有符号的整数。

3.3.4 Tree Table Schema

索引表,用于展示树状结构的,类似于文件系统,以方便其他系统使用,例如: Graphite

3.4 如何写数据

3.5 如何查询数据

3.6 CLI Tools

tsdb支持以下参数:
[root@cdh1 build]# ./tsdb 
usage: tsdb &lt;command&gt; [args]
Valid commands: fsck, import, mkmetric, query, tsd, scan, uid
通过以下命令创建指标:
./tsdb mkmetric mysql.bytes_received mysql.bytes_sent
执行上述命令的结果如下:
metrics mysql.bytes_received: [0, 0, -93]
metrics mysql.bytes_sent: [0, 0, -92]

3.11 Utilities

3.12 Logging

4. HTTP API

5. 谁在用OpenTSDB

  • StumbleUpon StumbleUpon is the easiest way to find cool new websites, videos, photos and images from across the Web
  • box Box simplifies online file storage, replaces FTP and connects teams in online workspaces.
  • tumblr 一个轻量级博客,用户可以跟进其他的会员并在自己的页面上看到跟进会员发表的文章,还可以转发他人在Tumblr上的文章

6. KairosDB

KairosDB是一个快速可靠的分布式时间序列数据库,主要用于Cassandra当然也可以适用与HBase。KairosDB是在OpenTSDB基础上重写的,他不仅可以在HBase上存储数据还支持Cassandra。 KairosDB主页: https://code.google.com/p/kairosdb/

7. 参考资料

转自:http://blog.javachen.com/2014/01/22/all-things-opentsdb/#

linux logrotate - 日志翻滚

$
0
0
【基本介绍】
logrotate是用来更新备份系统的日志文件(日志翻滚),这里介绍apache为什么可以每天生成一个新的日志,而nginx需要自己去切割日志。
这里不介绍logroate具体配置文件。

【lograte流程】
在系统的cron.daily里面有logrotate脚本每天执行,脚本会调用/etc/logrotate.d里面的配置文件,里面包括了httpd,syslog等。httpd配置里面会reload apache.这样就实现了每天的日志翻滚。




【参考】
http://www.techrepublic.com/article/manage-linux-log-files-with-logrotate/

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


ITeye推荐



生孩子到底有多危险?

$
0
0

近日,一个医学专业名词占据了各大网站热搜榜的榜首。它就是“羊水栓塞”。湖南湘潭产妇死亡的新闻引发了国内舆论界的一片混战。然而对于一个产科医生而言,看到的更多是公众对分娩风险的忽视和对产科知识的匮乏。当然这其中媒体缺乏医学常识的评论和报道更让人不安。因此有了这篇对于孕产妇死亡的相关介绍。

孕产妇死亡逐年减少但仍不容忽视

提到孕产妇死亡就不得不提到一个概念——孕产妇死亡率。它是指每10万活产中,由于同怀孕或孕期分娩期间和孕期终止后42天内护理有关或因此而加重的任何原因(不包括偶然或事故原因)死亡的妇女人数(不论孕期的长短和怀孕地点) [1]。孕产妇死亡率是衡量一个国家和地区社会经济发展水平的一项重要指标,也是评价孕产妇保健工作水平及医疗产科质量的重要指标之一。为保障孕产妇健康、提高妇女生存质量,2000年,联合国成员国共同签署了千年发展目标,承诺到2015年,孕产妇死亡率在1990年基础上降低3/4 [2];按照此要求,中国孕产妇死亡率在2015年应下降至22.2/10万。中国20世纪90年代国务院先后颁布实施了《中国儿童发展纲要》和《中国妇女发展纲要》,将妇女和儿童健康纳入国民经济和社会发展总体规划。2014年5月世界卫生组织(WHO)最新报道,每天约有1000名妇女死于与妊娠和分娩有关的可预防疾病。 所有孕产妇死亡有99%发生在发展中国家。1990年至2008年,世界各地的孕产妇死亡率下降了三分之一。一项针对中国31个省市的监测调查 [3]显示:1996-2010年,孕产妇死亡率从64.1/10万下降到30.0/10万,下降了53.20%,但城市和农村变化趋势不同。农村由86.4/10万下降至30.1/10万,而城市孕产妇死亡率在1996-2002年呈现波动,在2003-2007年间相对平稳,2008后有所反弹,2010年接近1996年的水平。

1996-2010年全国和城乡孕产妇死亡率 图片来源:研究论文

但是即使孕产妇死亡率在逐年下降,但是妊娠和分娩本身来说,风险仍然存在。并且对于一个家庭来讲,孕产妇死亡一旦发生,后果将不堪设想,因此提高对妊娠和分娩的认识和重视非常必要。

孕产妇死亡中发生率最高和致死率最高的两种

首先妊娠、分娩是生理现象,不能称之为疾病,但是,这一时期孕妇身体的各系统和没有怀孕时比较会发生很大变化,易引起各种并发症,甚至造成孕产妇死亡,孕产妇死亡的原因比较多,可以是一种原因,也可以多种原因共存。导致孕产妇死亡的主要原因包括产后出血、产褥感染、妊娠合并心脏病、妊娠高血压病(子痫)等,还有不得不提的一个凶险疾病就是,羊水栓塞。

研究表明 [3]: 1996年,中国孕产妇主要死因构成中,排在前3位的疾病依次为产科出血(47.9%)、妊娠期高血压疾病(12.9%)和羊水栓塞(6.8%),而2010年的顺位是产科出血(27.8%)、妊娠期高血压疾病(12.3%)和心脏病(10.9%)。虽然产后出血的构成比明显下降,但是它仍是造成孕产妇死亡的首位原因。

经阴道分娩的出血量一般在400毫升左右,如果在胎儿娩出后的24小时内,产妇出血量超过500毫升,在医学上则称为产后出血。产后出血多是由于子宫收缩乏力而引起的。凡双胎妊娠、巨大儿、羊水过多、产妇年龄大于35岁、妊娠合并高血压综合征、分娩次数多等因素,均易于发生宫缩乏力和产后出血。另外,胎儿娩出后,如果胎盘未能及时完整地排出,也会引起产后出血。再者,经阴道分娩发生产道裂伤出血,也有可能导致产后出血。有凝血功能障碍的产妇也容易发生出血,而且常常是难以控制的大量出血。产后出血如果治疗抢救不及时,极易发生出血性休克,最终导致孕产妇死亡。

而与产后出血相比,近期被公众所了解的羊水栓塞则被公认为产科的死神。主要因为羊水栓塞无法预测,跟分娩方式没有直接的关系,虽然发病率低(人卫版教科书中,羊水栓塞的发病率为4-6/10万),但是致死率很高(80%以上 [4])。一旦发生,对于任何一个家庭都是无法承受。

羊水栓塞就是指在分娩过程中羊水突然进入母体血液循环引起急性肺栓塞,过敏性休克,弥散性血管内凝血,肾功能衰竭或猝死的严重的分娩期并发症。通俗一点说:就是在分娩的时候,羊水通过子宫创口进入母体的血液里。羊水中含有上皮组织、胎儿毳毛、胎脂、胎粪、粘蛋白等和促凝物质,当这些东西进入母体血液,引起母体对胎儿抗原产生一系列过敏反应,引发很严重的并发症,造成急性呼吸循环衰竭、全身弥漫性出血、全身多脏器损伤等,来势汹汹,极大可能引发死亡。近年研究认为,羊水栓塞主要是过敏反应,是羊水进入母体循环后,引起母体对胎儿抗原产生的一系列过敏反应,故建议命名为“妊娠过敏反应综合征”。 其可能诱因包括:①经产妇居多;②多有胎膜早破或人工破膜史;③常见于宫缩过强或缩宫素(催产素)应用不当;④胎盘早期剥离、前置胎盘、子宫破裂或手术产易发生羊水栓塞;⑤死胎不下可增加羊水栓塞的发病率。

值得提醒的是因为羊水栓塞发生迅速,往往来不及抢救,因此在羊水栓塞的救治中,尽早诊断和处理尤为重要。一旦发现,终止妊娠,结束分娩可以有效去除病因。但是,一旦发生产后大出血,在其他措施不能有效止血的情况下,子宫切除应当机立断。

另外,有些报道中提到“有准备的破腹产是减少和避免羊水栓塞的有效方法”,这种提法有些偏颇。应该说在早期羊水栓塞发生后,迅速选择剖宫产去除病因是有效的救治手段。

孕产妇死亡的预防

尽管羊水栓塞如此不可预见,但是对于孕产妇死亡,仍然有一些预防方法可以应用:

1、定期正规产检:常规产前保健能及早发现、治疗孕期各种并发症 ,保证孕期安全。孕妇一定要定期正规产检,加强对高危因素的重视。并积极配合医生,遵医嘱进行及时治疗,避免可能出现的异常情况,是防止严重并发症发生的有效措施。

2、积极了解孕产期知识:通过孕妇学校,加强健康教育宣传使孕妇及家属充分了解孕产期保健及孕产期并发症风险预后的相关知识,让准妈妈们真正认识到产前保健的重要性 ,提高孕产妇及家属的自我保护意识和主动寻求产前保健的能力。

3、积极有效的治疗:恰当、及时的治疗,很大程度上影响母体与胎儿的预后。早期发现,早期诊断,早期治疗对于保障母婴安全非常重要。

4、密切配合医生处理:高龄产妇、早产、过期产及经产妇。尤其是胎膜早破、子宫体或子宫颈发育不良的孕妈妈容易发生羊水栓塞,一定要积极密切配合医生,及时处理。

总之,看到这样的事件发生,每个产科医生都感到痛心,但是似乎每到事情发生时,公众才会关注这个疾病。如果可以,请在妊娠和分娩前了解相关知识,生孩子不是什么稀罕的事情,但其中有险也不可忽视的,希望想要为人父母的你多多了解,认真对待。(编辑:粉条er)

参考文献:

  1. 联合国《千年发展目标监测指标》
  2. ​United Nations General Assembly United Nations Millennium Declaration (2011.8.2)
  3. 周远洋等,1996-2010年全国孕产妇死亡率变化趋势,中华预防医学杂志[J],2011,45(10),934-937.
  4. 乔福元等,羊水栓塞的急救盒处理,中国实用妇科与产科杂志[J],2005,21(2),75-77

文章题图:forgeychiropractic.com 

你可能感兴趣

  1. 孕妇“尿失禁”后果很严重
  2. 我外甥把我外甥女吃掉了!
  3. 为了孩子,理解胎盘
  4. 辨男女,孕期常规检查真的能行吗?
  5. 男宝宝更容易“早产”吗?
  6. 10斤巨婴:孩子不是越大越好
  7. 男人也生子?寄生胎!
  8. 为什么要做唐氏筛查?
  9. 相比50年前,现在生孩子需要更长时间
  10. 怀孕了,也要管住嘴哦
  11. 怀男孩肚子尖,怀女孩肚子圆?
  12. 孕妇血压高?快补钙!

新时代的 Linux 系统管理员都需要哪些技能?

$
0
0

你上次编译内核是多久以前的事儿了?新生代 Linux 管理员的答案很简单:我压根儿没编译过内核。我也从没编译过内核,当然自己弄着玩儿的不算。我实在想不到我为啥需要自定义内核,所以我就用我的“开箱即用”内核了。

Linux 老鸟们可能会笑话我们,但你不能否认:随着越来越多的企业采用 Linux,新生代的 Linux 管理员虽然生就一身相当不错的技术实力,却因缺乏编译内核这类简单却基础的技能而和一个优秀的 Linux 管理员之间存在差距。我们能用时下最前沿的技术搭建一套高性能、高可用的 Web 基础设施,但你别让我们修理一台无法启动的 Linux 机器——我们会建议你扔了它,然后换个新的虚拟机。

过去的十来年里发生了很多有意思的事儿:Linux 不但变成了商品,而且其层出不穷的发行版也越来越强大和灵活。如今,一个普通的系统管理员完全不必掌握编译内核这类底层技能了。

然后,我们又见证了虚拟机技术的商品化。使用 Amazon 云主机或者 VPS 的用户也许永远不用在裸机上部署 Linux。随着混合云和私有云的日益普及,甚至很多企业级的系统管理员都不需要在裸机上部署 Linux——登录 Web 管理界面,一次性就能搞定不止 5 台 Apache 虚拟机。

时下最新的两个趋势:一个是配置管理,另一个看起来很前沿(其实一点儿都不前沿)—— 像 docker 这样的部署工具包,它们隐藏了更多 Linux 底层技术细节。每当客户要求我们在 OlinData上配置 Linux 机器时,我们的第一个动作就是架设 Puppet。有了功能强大值得信赖的 Puppet模块,手工配置也更快更简单了。

例如,我可以这样使用 Puppet 在新机器上安装 Apache:

  node 'web01.olindata.com' {
  include apache
  apache::vhost{ 'www.olindata.com':
  docroot => '/var/www/olindata'
  }
  }

在这种环境下,我甚至都不用在机器上打开日志。通过 Jenkins 这样的不间断部署工具,我可以完全自动化部署我的基础设施代码,并确保它们通过了我预置的测试。

系统管理员技能日益升级

就算有更新的工具对我们隐藏更多的技术细节,坚持 Linux 训练对系统管理员来说仍然很有价值。了解基础知识很关键,这些抽象高级工具把管理员从旧的任务中解放了出来,那么系统管理员就应该强化自己使用这些高级工具的能力。熟悉高级工具对系统管理员提升层次来说很关键,这会迫使系统管理员不断练习编写代码一类的能力,并借此发挥这些新玩意儿更大的潜力。

底层 Linux 技能需求会完全消失么?当然不会。除了商业服务器部署之外,Linux 还有大量其他用途。同时,人们也从底层操作知识中获益颇丰。最重要的是,如果你的简历上体现出了这些技能,我(还有很多其他老板)总是会优先选择你,而不是那些尚未掌握这些技能的候选人。因为你也不知道啥时候你就用上这些技能了!

Walter Heck 是 荷兰开源培训顾问公司 Olindata(一个 Linux 基金会授权的培训机构)的创始人兼 CEO,这里是计划中由 OlinData 提供的 Linux 基金会官方培训课程单。

[Python] 发送email的几种方式

$
0
0

python发送email还是比较简单的,可以通过登录邮件服务来发送,linux下也可以使用调用sendmail命令来发送,还可以使用本地或者是远程的smtp服务来发送邮件,不管是单个,群发,还是抄送都比较容易实现。

先把几个最简单的发送邮件方式记录下,像html邮件,附件等也是支持的,需要时查文档即可

1 登录邮件服务

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#python2.7x
#send_simple_email_by_account.py  @2014-07-30
#author: orangleliu'''
使用python写邮件 simple
使用126 的邮箱服务
'''

import smtplib
from email.mime.text import MIMEText

SMTPserver = 'smtp.126.com'
sender = 'liuzhizhi123@126.com'
password = "xxxx"

message = 'I send a message by Python. 你好'
msg = MIMEText(message)

msg['Subject'] = 'Test Email by Python'
msg['From'] = sender
msg['To'] = destination

mailserver = smtplib.SMTP(SMTPserver, 25)
mailserver.login(sender, password)
mailserver.sendmail(sender, [sender], msg.as_string())
mailserver.quit()
print 'send email success'

2调用sendmail命令 (linux)

# -*- coding: utf-8 -*-
#python2.7x
#send_email_by_.py
#author: orangleliu
#date: 2014-08-15'''
用的是sendmail命令的方式

这个时候邮件还不定可以发出来,hostname配置可能需要更改
'''

from email.mime.text import MIMEText
from subprocess import Popen, PIPE

def get_sh_res():
    p = Popen(['/Application/2.0/nirvana/logs/log.sh'], stdout=PIPE)
    return str(p.communicate()[0])

def mail_send(sender, recevier):
    print "get email info..."
    msg = MIMEText(get_sh_res())
    msg["From"] = sender
    msg["To"] = recevier
    msg["Subject"] = "Yestoday interface log results"
    p = Popen(["/usr/sbin/sendmail", "-t"], stdin=PIPE)
    res = p.communicate(msg.as_string())
    print 'mail sended ...'

if __name__ == "__main__":
    s = "957748332@qq.com"
    r = "zhizhi.liu@chinacache.com"
    mail_send(s, r)

3 使用smtp服务来发送(本地或者是远程服务器)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#python2.7x
#send_email_by_smtp.py
#author: orangleliu
#date: 2014-08-15'''
linux 下使用本地的smtp服务来发送邮件
前提要开启smtp服务,检查的方法
#ps -ef|grep sendmail
#telnet localhost 25

这个时候邮件还不定可以发出来,hostname配置可能需要更改
'''
import smtplib
from email.mime.text import MIMEText
from subprocess import Popen, PIPE


def get_sh_res():
    p = Popen(['/Application/2.0/nirvana/logs/log.sh'], stdout=PIPE)
    return str(p.communicate()[0])

def mail_send(sender, recevier):
    msg = MIMEText(get_sh_res())
    msg["From"] = sender
    msg["To"] = recevier
    msg["Subject"] = "Yestoday interface log results"
    s = smtplib.SMTP('localhost')
    s.sendmail(sender, [recevier], msg.as_string())
    s.quit()
    print 'send mail finished...'

if __name__ == "__main__":
    s = "zhizhi.liu@chinacache.com"
    r =  s
    mail_send(s, r)




作者:lzz957748332 发表于2014-8-15 22:03:56 原文链接
阅读:6 评论:0 查看评论

HIVE优化提示-如何写好HQL

$
0
0
一、     Hive join优化
1.     尽量将小表放在join的左边,我们这边使用的hive-0.12.0,所以是自动转化的,既把小表自动装入内存,执行map side join(性能好), 这是由参数hive.auto.convert.join=true 和hive.smalltable.filesize=25000000L)参数控制(默认是25M),如果表文件大小在25M左右,可以适当调整此参数,进行map side join,避免reduce side join。 也可以显示声明进行map join:特别适用于小表join大表的时候,SELECT /*+ MAPJOIN(b) */ a.key, a.value FROM a join b on a.key = b.key
2.     注意带表分区的join, 如:
SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key) WHERE a.dt='2014-08-07' AND b.dt='2014-08-07'
因为hive是先join再where的,所以如果在b中找不到a表的记录,b表中的所以列都会列出null,包括ds列,这样left outer的查询结果与where子句无关了,解决办法:
SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key AND a.dt='2014-08-07' AND b.dt='2014-08-07'')
3.     怎样写exist/in子句?
Hive不支持where子句中的子查询,SQL常用的exist in子句需要改写。这一改写相对简单。考虑以下SQL查询语句:
SELECT a.key, a.value  FROM a  WHERE a.key in  (SELECT b.key  FROM B);
可以改写为
SELECT a.key, a.value  FROM a LEFT OUTER JOIN b ON (a.key = b.key)  WHERE b.key <> NULL;
一个更高效的实现是利用left semi join改写为:
SELECT a.key, a.val  FROM a LEFT SEMI JOIN b on (a.key = b.key);
4.     Hive join只支持等值连接,不支持非等值连接。
5.     合理的使用map join, 场合:小表A join 大表,
二、     合理设置map与reduce的个数。
1、如何合并小文件,减少map数?
如果一个表中的map数特别多,可能是由于文件个数特别多,而且文件特别小照成的,可以进行如下操作,合并文件,:
   set mapred.max.split.size=100000000; // 100M
  set mapred.min.split.size.per.node=100000000;
   set mapred.min.split.size.per.rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; //  合并小文件
2、如何适当的增加map数?
如果表A只有一个文件,大小为120M,包含几千万记录,可以考虑用多个map任务完成
set mapred.reduce.tasks=10;
                   create table a_1 as
                   select * from a
                   distribute by rand(123); //将a表的记录,随机的分散到包含10个文件的a_1表中
3、hive如何确定reduce数, reduce的个数基于以下参数设定:
hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max(每个任务最大的reduce数,默认为999)
计算reducer数的公式很简单N=min(参数2,总输入数据量/参数1)
即,如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务;所以调整以下参数:
set hive.exec.reducers.bytes.per.reducer=500000000; (500M)
set mapred.reduce.tasks = 15;
三、     如果设计和使用bucket,
Buckets 对指定列计算 hash,根据 hash 值切分数据,目的是为了并行,每一个 Bucket 对应一个文件。 将 user 列分散至 32 个 bucket, 首先对 user 列的值计算 hash,对应 hash 值为 0 的 HDFS 目录为:/wh/pvs/dt=2014-08-01/ctry=US/part-00000; hash 值为 20 的 HDFS 目录为:/wh/pvs/dt=2014-08-01/ctry=US/part-00020
所用场合:对某一列进行分区,比如对用户ID进行分区,例如:
CREATE TABLE weblog (user_id INT, url STRING, source_ip STRING)
> PARTITIONED BY (dt STRING)
> CLUSTERED BY (user_id) INTO 96 BUCKETS; // 按照日期分区后,再按照user_id把日志放在96个篮子里。插入数据的时候:
hive> SET hive.enforce.bucketing = true;
hive> FROM raw_logs
> INSERT OVERWRITE TABLE weblog
> PARTITION (dt='2009-02-25')
> SELECT user_id, url, source_ip WHERE dt='2009-02-25'
四、     Count(distinct)
当count distinct 的记录非常多的时候,设置以下两个参数:
hive>  hive.map.aggr = true
hive> set hive.groupby.skewindata=true;
hive>  select count (distinct gid) from cookie_label_summary where i_date=20130924;
五、     Group by
Group By的方法是在reduce做一些操作,这样会导致两个问题:
map端聚合,提前一部分计算:hive.map.aggr = true 同时设置间隔:hive.groupby.mapaggr.checkinterval
均衡处理:hive.groupby.skewindata
这是针对数据倾斜的,设为ture的时候,任务的reduce会把原来一个job拆分成两个,第一个的job中reduce处理处理不同的随即分发过来的key的数据,生成中间结果,再由最后一个综合处理。
六、     Order by, Sort by ,Dristribute by,Cluster By
1、     order by VS Sort by: order by是在全局的排序,只用一个reduce去跑,所以在set hive.mapred.mode=strict 模式下,order by 必须limit,否则报错。Sort by只保证同一个reduce下排序正确。
2、     Distribute by with sort by: Distribute by 是按指定的列把map 输出结果分配到reduce里。所以经常和sort by 来实现对某一字段的相同值分配到同一个reduce排序。
3、     Cluster by 实现了Distribute by+ sort by 的功能
作者:today20080808 发表于2014-8-15 17:24:07 原文链接
阅读:0 评论:0 查看评论

Redis客户端之Spring整合Jedis

$
0
0

1.下载相关jar包,并引入工程:

jedis-2.4.2.jar

commons-pool2-2.0.jar

2.将以下XML配置引入spring

<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool"><constructor-arg index="0" ref="jedisPoolConfig"/><constructor-arg index="1"><list><bean name="slaver" class="redis.clients.jedis.JedisShardInfo"><constructor-arg index="0" value="${redis.slaver.host}"/><constructor-arg index="1" value="${redis.slaver.port}" type="int"/></bean><bean name="master" class="redis.clients.jedis.JedisShardInfo"><constructor-arg index="0" value="${redis.master.host}"/><constructor-arg index="1" value="${redis.master.port}" type="int"/></bean></list></constructor-arg></bean><bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxTotal" value="2048" /><property name="maxIdle" value="200" /><property name="numTestsPerEvictionRun" value="1024"/><property name="timeBetweenEvictionRunsMillis" value="30000" /><property name="minEvictableIdleTimeMillis" value="-1" /><property name="softMinEvictableIdleTimeMillis" value="10000" /><property name="maxWaitMillis" value="1500"/><property name="testOnBorrow" value="true" /><property name="testWhileIdle" value="true"/><property name="testOnReturn" value="false"/><property name="jmxEnabled" value="true"/><property name="jmxNamePrefix" value="youyuan"/><property name="blockWhenExhausted" value="false"/></bean>

3.将shardedJedisPool注入相关的类中即可使用

 

 

 

 

@Resource
    private ShardedJedisPool shardedJedisPool;


    /**
     * 设置一个key的过期时间(单位:秒)
     * @param key key值
     * @param seconds 多少秒后过期
     * @return 1:设置了过期时间  0:没有设置过期时间/不能设置过期时间
     */
    public long expire(String key, int seconds) {
        if (key==null || key.equals("")) {
            return 0;
        }

        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.expire(key, seconds);
        } catch (Exception ex) {
            logger.error("EXPIRE error[key=" + key + " seconds=" + seconds + "]" + ex.getMessage(), ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return 0;
    }

    /**
     * 设置一个key在某个时间点过期
     * @param key key值
     * @param unixTimestamp unix时间戳,从1970-01-01 00:00:00开始到现在的秒数
     * @return 1:设置了过期时间  0:没有设置过期时间/不能设置过期时间
     */
    public long expireAt(String key, int unixTimestamp) {
        if (key==null || key.equals("")) {
            return 0;
        }

        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.expireAt(key, unixTimestamp);
        } catch (Exception ex) {
            logger.error("EXPIRE error[key=" + key + " unixTimestamp=" + unixTimestamp + "]" + ex.getMessage(), ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return 0;
    }

    /**
     * 截断一个List
     * @param key 列表key
     * @param start 开始位置 从0开始
     * @param end 结束位置
     * @return 状态码
     */
    public String trimList(String key, long start, long end) {
        if (key == null || key.equals("")) {
            return "-";
        }
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.ltrim(key, start, end);
        } catch (Exception ex) {
            logger.error("LTRIM 出错[key=" + key + " start=" + start + " end=" + end + "]" + ex.getMessage() , ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return "-";
    }
    /**
     * 检查Set长度
     * @param key
     * @return
     */
    public long countSet(String key){
    	if(key == null ){
    		return 0;
    	}
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.scard(key);
        } catch (Exception ex) {
            logger.error("countSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
    	return 0;
    }
    /**
     * 添加到Set中(同时设置过期时间)
     * @param key key值
     * @param seconds 过期时间 单位s
     * @param value
     * @return
     */
    public boolean addSet(String key,int seconds, String... value) {
    	boolean result = addSet(key, value);
    	if(result){
    		long i = expire(key, seconds);
    		return i==1;
    	}
    	return false;
    }
    /**
     * 添加到Set中
     * @param key
     * @param value
     * @return
     */
    public boolean addSet(String key, String... value) {
    	if(key == null || value == null){
    		return false;
    	}
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.sadd(key, value);
            return true;
        } catch (Exception ex) {
            logger.error("setList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    
    /**
     * @param key
     * @param value
     * @return 判断值是否包含在set中
     */
    public boolean containsInSet(String key, String value) {
    	if(key == null || value == null){
    		return false;
    	}
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.sismember(key, value);
        } catch (Exception ex) {
            logger.error("setList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }
    /**
     * 获取Set
     * @param key
     * @return
     */
    public  Set<String> getSet(String key){
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.smembers(key);
        } catch (Exception ex) {
            logger.error("getList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }

    /**
     * 从set中删除value
     * @param key
     * @return
     */
    public  boolean removeSetValue(String key,String... value){
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.srem(key, value);
            return true;
        } catch (Exception ex) {
            logger.error("getList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }
    /**
     * 从list中删除value 默认count 1
     * @param key
     * @param values 值list
     * @return
     */
    public  int removeListValue(String key,List<String> values){
    	return removeListValue(key, 1, values);
    }
    /**
     * 从list中删除value
     * @param key
     * @param count 
     * @param values 值list
     * @return
     */
    public  int removeListValue(String key,long count,List<String> values){
    	int result = 0;
    	if(values != null && values.size()>0){
    		for(String value : values){
    			if(removeListValue(key, count, value)){
    				result++;
    			}
    		}
    	}
    	return result;
    }
    /**
     *  从list中删除value
     * @param key
     * @param count 要删除个数
     * @param value
     * @return
     */
    public  boolean removeListValue(String key,long count,String value){
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.lrem(key, count, value);
            return true;
        } catch (Exception ex) {
            logger.error("getList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
    	return false;
    }
    /**
     * 截取List
     * @param key 
     * @param start 起始位置
     * @param end 结束位置
     * @return
     */
    public List<String> rangeList(String key, long start, long end) {
        if (key == null || key.equals("")) {
            return null;
        }
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.lrange(key, start, end);
        } catch (Exception ex) {
            logger.error("rangeList 出错[key=" + key + " start=" + start + " end=" + end + "]" + ex.getMessage() , ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }
    /**
     * 检查List长度
     * @param key
     * @return
     */
    public long countList(String key){
    	if(key == null ){
    		return 0;
    	}
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.llen(key);
        } catch (Exception ex) {
            logger.error("countList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
    	return 0;
    }
    /**
     * 添加到List中(同时设置过期时间)
     * @param key key值
     * @param seconds 过期时间 单位s
     * @param value 
     * @return 
     */
    public boolean addList(String key,int seconds, String... value){
    	boolean result = addList(key, value);
    	if(result){
    		long i = expire(key, seconds);
    		return i==1;
    	}
    	return false;
    }
    /**
     * 添加到List
     * @param key
     * @param value
     * @return
     */
    public boolean addList(String key, String... value) {
    	if(key == null || value == null){
    		return false;
    	}
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.lpush(key, value);
            return true;
        } catch (Exception ex) {
            logger.error("setList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }
    /**
     * 添加到List(只新增)
     * @param key
     * @param value
     * @return
     */
    public boolean addList(String key, List<String> list) {
    	if(key == null || list == null || list.size() == 0){
    		return false;
    	}
    	for(String value : list){
    		addList(key, value);
    	}
        return true;
    }
    /**
     * 获取List
     * @param key
     * @return
     */
    public  List<String> getList(String key){
    	ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.lrange(key, 0, -1);
        } catch (Exception ex) {
            logger.error("getList error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }
    /**
     * 设置HashSet对象
     *
     * @param domain 域名
     * @param key    键值
     * @param value  Json String or String value
     * @return
     */
    public boolean setHSet(String domain, String key, String value) {
        if (value == null) return false;
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.hset(domain, key, value);
            return true;
        } catch (Exception ex) {
            logger.error("setHSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    /**
     * 获得HashSet对象
     *
     * @param domain 域名
     * @param key    键值
     * @return Json String or String value
     */
    public String getHSet(String domain, String key) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.hget(domain, key);
        } catch (Exception ex) {
            logger.error("getHSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }

    /**
     * 删除HashSet对象
     *
     * @param domain 域名
     * @param key    键值
     * @return 删除的记录数
     */
    public long delHSet(String domain, String key) {
        ShardedJedis shardedJedis = null;
        long count = 0;
        try {
            shardedJedis = shardedJedisPool.getResource();
            count = shardedJedis.hdel(domain, key);
        } catch (Exception ex) {
            logger.error("delHSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return count;
    }

    /**
     * 删除HashSet对象
     *
     * @param domain 域名
     * @param key    键值
     * @return 删除的记录数
     */
    public long delHSet(String domain, String... key) {
        ShardedJedis shardedJedis = null;
        long count = 0;
        try {
            shardedJedis = shardedJedisPool.getResource();
            count = shardedJedis.hdel(domain, key);
        } catch (Exception ex) {
            logger.error("delHSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return count;
    }

    /**
     * 判断key是否存在
     *
     * @param domain 域名
     * @param key    键值
     * @return
     */
    public boolean existsHSet(String domain, String key) {
        ShardedJedis shardedJedis = null;
        boolean isExist = false;
        try {
            shardedJedis = shardedJedisPool.getResource();
            isExist = shardedJedis.hexists(domain, key);
        } catch (Exception ex) {
            logger.error("existsHSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return isExist;
    }

    /**
     * 全局扫描hset
     *
     * @param match field匹配模式
     * @return
     */
    public List<Map.Entry<String, String>> scanHSet(String domain, String match) {
        ShardedJedis shardedJedis = null;
        try {
            int cursor = 0;
            shardedJedis = shardedJedisPool.getResource();
            ScanParams scanParams = new ScanParams();
            scanParams.match(match);
            Jedis jedis = shardedJedis.getShard(domain);
            ScanResult<Map.Entry<String, String>> scanResult;
            List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>();
            do {
                scanResult = jedis.hscan(domain, String.valueOf(cursor), scanParams);
                list.addAll(scanResult.getResult());
                cursor = Integer.parseInt(scanResult.getStringCursor());
            } while (cursor > 0);
            return list;
        } catch (Exception ex) {
            logger.error("scanHSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }


    /**
     * 返回 domain 指定的哈希集中所有字段的value值
     *
     * @param domain
     * @return
     */

    public List<String> hvals(String domain) {
        ShardedJedis shardedJedis = null;
        List<String> retList = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            retList = shardedJedis.hvals(domain);
        } catch (Exception ex) {
            logger.error("hvals error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return retList;
    }

    /**
     * 返回 domain 指定的哈希集中所有字段的key值
     *
     * @param domain
     * @return
     */

    public Set<String> hkeys(String domain) {
        ShardedJedis shardedJedis = null;
        Set<String> retList = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            retList = shardedJedis.hkeys(domain);
        } catch (Exception ex) {
            logger.error("hkeys error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return retList;
    }

    /**
     * 返回 domain 指定的哈希key值总数
     *
     * @param domain
     * @return
     */
    public long lenHset(String domain) {
        ShardedJedis shardedJedis = null;
        long retList = 0;
        try {
            shardedJedis = shardedJedisPool.getResource();
            retList = shardedJedis.hlen(domain);
        } catch (Exception ex) {
            logger.error("hkeys error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return retList;
    }

    /**
     * 设置排序集合
     *
     * @param key
     * @param score
     * @param value
     * @return
     */
    public boolean setSortedSet(String key, long score, String value) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.zadd(key, score, value);
            return true;
        } catch (Exception ex) {
            logger.error("setSortedSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    /**
     * 获得排序集合
     *
     * @param key
     * @param startScore
     * @param endScore
     * @param orderByDesc
     * @return
     */
    public Set<String> getSoredSet(String key, long startScore, long endScore, boolean orderByDesc) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            if (orderByDesc) {
                return shardedJedis.zrevrangeByScore(key, endScore, startScore);
            } else {
                return shardedJedis.zrangeByScore(key, startScore, endScore);
            }
        } catch (Exception ex) {
            logger.error("getSoredSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }

    /**
     * 计算排序长度
     *
     * @param key
     * @param startScore
     * @param endScore
     * @return
     */
    public long countSoredSet(String key, long startScore, long endScore) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            Long count = shardedJedis.zcount(key, startScore, endScore);
            return count == null ? 0L : count;
        } catch (Exception ex) {
            logger.error("countSoredSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return 0L;
    }

    /**
     * 删除排序集合
     *
     * @param key
     * @param value
     * @return
     */
    public boolean delSortedSet(String key, String value) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            long count = shardedJedis.zrem(key, value);
            return count > 0;
        } catch (Exception ex) {
            logger.error("delSortedSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    /**
     * 获得排序集合
     *
     * @param key
     * @param startRange
     * @param endRange
     * @param orderByDesc
     * @return
     */
    public Set<String> getSoredSetByRange(String key, int startRange, int endRange, boolean orderByDesc) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            if (orderByDesc) {
                return shardedJedis.zrevrange(key, startRange, endRange);
            } else {
                return shardedJedis.zrange(key, startRange, endRange);
            }
        } catch (Exception ex) {
            logger.error("getSoredSetByRange error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }

    /**
     * 获得排序打分
     *
     * @param key
     * @return
     */
    public Double getScore(String key, String member) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.zscore(key, member);
        } catch (Exception ex) {
            logger.error("getSoredSet error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return null;
    }

    public boolean set(String key, String value, int second) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.setex(key, second, value);
            return true;
        } catch (Exception ex) {
            logger.error("set error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    public boolean set(String key, String value) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.set(key, value);
            return true;
        } catch (Exception ex) {
            logger.error("set error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    public String get(String key, String defaultValue) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.get(key) == null?defaultValue:shardedJedis.get(key);
        } catch (Exception ex) {
            logger.error("get error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return defaultValue;
    }

    public boolean del(String key) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            shardedJedis.del(key);
            return true;
        } catch (Exception ex) {
            logger.error("del error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return false;
    }

    public long incr(String key) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.incr(key);
        } catch (Exception ex) {
            logger.error("incr error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return 0;
    }

    public long decr(String key) {
        ShardedJedis shardedJedis = null;
        try {
            shardedJedis = shardedJedisPool.getResource();
            return shardedJedis.decr(key);
        } catch (Exception ex) {
            logger.error("incr error.", ex);
            returnBrokenResource(shardedJedis);
        } finally {
            returnResource(shardedJedis);
        }
        return 0;
    }



    private void returnBrokenResource(ShardedJedis shardedJedis) {
        try {
            shardedJedisPool.returnBrokenResource(shardedJedis);
        } catch (Exception e) {
            logger.error("returnBrokenResource error.", e);
        }
    }

    private void returnResource(ShardedJedis shardedJedis) {
        try {
            shardedJedisPool.returnResource(shardedJedis);
        } catch (Exception e) {
            logger.error("returnResource error.", e);
        }
    }

 



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


ITeye推荐



使用Jedis的ShardedJedis做Redis集群

$
0
0

之前一直没仔细看过ShardedJedis的代码,最近遇到了shard后集群扩容后的 数据迁移问题

今天在看ShardedJedis源码的时候,发现ShardedJedis并没有使用节点的Ip和port做hash,而是用的instance的顺序或者name,太赞了。


private void initialize(List<S> shards) {
 nodes = new TreeMap<Long, S>();

for (int i = 0; i != shards.size(); ++i) {
 final S shardInfo = shards.get(i);
 if (shardInfo.getName() == null)
 for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
 nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n),
 shardInfo);
 }
 else
 for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
 nodes.put(
 this.algo.hash(shardInfo.getName() + "*"
 + shardInfo.getWeight() + n), shardInfo);
 }
 resources.put(shardInfo, shardInfo.createResource());
 }
 }

配置的时候也非常简单:

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxTotal" value="1000"/><property name="maxIdle" value="10"/><property name="minIdle" value="1"/><property name="maxWaitMillis" value="30000"/><property name="testOnBorrow" value="true"/><property name="testOnReturn" value="true"/><property name="testWhileIdle" value="true"/></bean><bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" destroy-method="destroy"><constructor-arg ref="jedisPoolConfig"/><constructor-arg><list><bean class="redis.clients.jedis.JedisShardInfo"><constructor-arg value="127.0.0.1"/><constructor-arg type="int" value="7000"/><constructor-arg value="instance:01"/></bean><bean class="redis.clients.jedis.JedisShardInfo"><constructor-arg value="127.0.0.1"/><constructor-arg type="int" value="7001"/><constructor-arg value="instance:02"/></bean><bean class="redis.clients.jedis.JedisShardInfo"><constructor-arg value="127.0.0.1"/><constructor-arg type="int" value="7003"/><constructor-arg value="instance:03"/></bean></list></constructor-arg></bean>

一开始你可以设置足够多的instance,数据扩容的时候,只需要将几个instance的数据copy到别的机器上。
然后修改配置文件的ip和端口即可。很方便吧?

另外,Jedis还提供了对jedis sentinel pool的封装,所以发生主从切换的时候,web server都不需要重新配置和deploy。高可用性的极佳体现啊。


@Autowired private JedisSentinelPool pool;

public void mymethod() {
 Jedis jedis = null;
 try {
 jedis = pool.getResource();
 jedis.hset(....
 } catch (JedisException je) {
 throw je;
 } finally {
 if (jedis != null) pool.returnResource(jedis);
 }
 }

spring bean的配置:

<bean id="redisSentinel" class="redis.clients.jedis.JedisSentinelPool"><constructor-arg index="0" value="mymaster" /><constructor-arg index="1"><set><value>hostofsentinel:26379</value></set></constructor-arg><constructor-arg index="2" ref="jedisPoolConfig" /></bean>
您可能也喜欢:

Redis Cluster的安装和配置(beta-6)

Redis集群增删节点的数据迁移问题

Jedis sharding

Redis在新浪微博中的应用

关于我
无觅

MALLET简介

$
0
0
MALLET:基于Java语言的用于统计自然语言处理,文件分类,聚类,主题建模,信息提取,和其他的用于文本的机器学习应用的Java包。

MALLET包括复杂的用于 文件分类的工具:
有效的用于转换文本到“特征”的程序,多种多样的算法(包括朴素贝叶斯,最大熵,和决策树)。以及一些通用的指标用于评估分类器性能。

除了分类,MALLET包括 序列标注的工具,像从文本中提供命名实体的应用。算法包括隐马尔科夫模型,最大熵马尔科夫模型,和条件随机场。这些方法在一个扩展的系统中实现,用于有限状态机转换器。

主题建模对于分析大规模的无标签文本集合非常有用。
MALLET中的 主题建模(topic modeling)工具集包括有效的,基于采样的Latent Dirichlet Allocation, Pachinko Allocation, and Hierarchical LDA的实现.

MALLET中的很多算法依赖于数值优化(NUmerical Optimization)。MALLET包括有效的有限内存BFGS的实现(在许多其他优化方法中),

除了复杂的机器学习应用,MALLET包括转换文本文件为数值表示,然后进行有效处理的程序。该处理是通过一种灵活的“pipes”系统来实现的。它处理各种不同的任务,像标记字符串,去除停顿词,转换序列为计数向量。

另外,MALLET的一种称为GRMM的附加包,包含对通用图形化模型中的推理的支持。利用任意图像结构来训练CRFs。

注:MALLET工具集是一个开源软件,基于CPL发布。

作者:wuxiaoer717 发表于2014-8-16 10:15:41 原文链接
阅读:0 评论:0 查看评论

CSS之box-sizing的用处简单介绍

$
0
0

前几天才发现有 box-sizing 这么个样式属性,研究了一番感觉很有意思,

通过指定容器的盒子模型类型,达到不同的展示效果

例如:当一个容器宽度定义为 width:100%;  之后,如果再增加 padding 或者 border 则会溢出父容器,是向外扩张的




如果使用该样式,指定为 box-sizing: border-box; 则 padding 和 border 就不会再溢出,而是向内收缩的,这个效果感觉非常实用,





特别是 input 和 textarea 等 现在设置 100% 再直接增加内边距和边框也不用再进行复杂的计算和适配了



作者:zsjangel 发表于2014-8-16 22:41:27 原文链接
阅读:0 评论:0 查看评论

从事“大数据”工作的三大方向 十大职位

$
0
0
转载:http://tieba.baidu.com/p/2857041806
随着大数据的趋势引起的越来越多的重视,各大企业对与大数据相关高端人才的需求也越来越紧迫。这一趋势,也给想要从事大数据方面工作的人员提供了难得的职业发展机遇。
目前,大数据方面的工作人员主要有三大就业方向:大数据系统研发类人才、大数据应用开发类人才和大数据分析类人才。在此三大方向中,各自的基础岗位一般为大数据系统研发工程师、大数据应用开发工程师和数据分析师。
 从企业方面来说,大数据人才大致可以分为产品和市场分析、安全和风险分析以及商业智能三大领域。产品分析是指通过算法来测试新产品的有效性,是一个相对较新的领域。在安全和风险分析方面,数据科学家们知道需要收集哪些数据、如何进行快速分析,并最终通过分析信息来有效遏制网络入侵或抓住网络罪犯。
对于想从事大数据工作的求职者来说,如何根据自身条件进行职位选择?下面介绍十种与“大数据”相关的热门职位:
一、ETL研发
随着数据种类的不断增加,企业对数据整合专业人才的需求越来越旺盛。ETL开发者与不同的数据来源和组织打交道,从不同的源头抽取数据,转换并导入数据仓库以满足企业的需要。
ETL研发,主要负责将分散的、异构数据源中的数据如关系数据、平面数据文件等抽取到临时中间层后进行清洗、转换、集成,最后加载到数据仓库或数据集市中,成为联机分析处理、数据挖掘的基础。
目前,ETL行业相对成熟,相关岗位的工作生命周期比较长,通常由内部员工和外包合同商之间通力完成。ETL人才在大数据时代炙手可热的原因之一是:在企业大数据应用的早期阶段,Hadoop只是穷人的ETL。
二、Hadoop开发
Hadoop的核心是HDFS和MapReduce.HDFS提供了海量数据的存储,MapReduce提供了对数据的计算。随着数据集规模不断增大,而传统BI的数据处理成本过高,企业对Hadoop及相关的廉价数据处理技术如Hive、HBase、MapReduce、Pig等的需求将持续增长。如今具备Hadoop框架经验的技术人员是最抢手的大数据人才。
三、可视化工具开发
海量数据的分析是个大挑战,而新型数据可视化工具如Spotifre,Qlikview和Tableau可以直观高效地展示数据。
可视化开发就是在可视开发工具提供的图形用户界面上,通过操作界面元素,由可视开发工具自动生成应用软件。还可轻松跨越多个资源和层次连接您的所有数据,经过时间考验,完全可扩展的,功能丰富全面的可视化组件库为开发人员提供了功能完整并且简单易用的组件集合,以用来构建极其丰富的用户界面。
过去,数据可视化属于商业智能开发者类别,但是随着Hadoop的崛起,数据可视化已经成了一项独立的专业技能和岗位。
四、信息架构开发
大数据重新激发了主数据管理的热潮。充分开发利用企业数据并支持决策需要非常专业的技能。信息架构师必须了解如何定义和存档关键元素,确保以最有效的方式进行数据管理和利用。信息架构师的关键技能包括主数据管理、业务知识和数据建模等。
五、数据仓库研究
数据仓库是为企业所有级别的决策制定过程提供支持的所有类型数据的战略集合。它是单个数据存储,出于分析性报告和决策支持的目的而创建。为企业提供需要业务智能来指导业务流程改进和监视时间、成本、质量和控制。
数据仓库的专家熟悉Teradata、Neteeza和Exadata等公司的大数据一体机。能够在这些一体机上完成数据集成、管理和性能优化等工作。
六、OLAP开发
随着数据库技术的发展和应用,数据库存储的数据量从20世纪80年代的兆(M)字节及千兆(G)字节过渡到现在的兆兆(T)字节和千兆兆(P)字节,同时,用户的查询需求也越来越复杂,涉及的已不仅是查询或操纵一张关系表中的一条或几条记录,而且要对多张表中千万条记录的数据进行数据分析和信息综合。联机分析处理(OLAP)系统就负责解决此类海量数据处理的问题。
OLAP在线联机分析开发者,负责将数据从关系型或非关系型数据源中抽取出来建立模型,然后创建数据访问的用户界面,提供高性能的预定义查询功能。
七、数据科学研究
这一职位过去也被称为数据架构研究,数据科学家是一个全新的工种,能够将企业的数据和技术转化为企业的商业价值。随着数据学的进展,越来越多的实际工作将会直接针对数据进行,这将使人类认识数据,从而认识自然和行为。因此,数据科学家首先应当具备优秀的沟通技能,能够同时将数据分析结果解释给IT部门和业务部门领导。
总的来说,数据科学家是分析师、艺术家的合体,需要具备多种交叉科学和商业技能。
八、数据预测分析
 营销部门经常使用预测分析预测用户行为或锁定目标用户。预测分析开发者有些场景看上有有些类似数据科学家,即在企业历史数据的基础上通过假设来测试阈值并预测未来的表现。
九、企业数据管理
企业要提高数据质量必须考虑进行数据管理,并需要为此设立数据管家职位,这一职位的人员需要能够利用各种技术工具汇集企业周围的大量数据,并将数据清洗和规范化,将数据导入数据仓库中,成为一个可用的版本。然后,通过报表和分析技术,数据被切片、切块,并交付给成千上万的人。担当数据管家的人,需要保证市场数据的完整性,准确性,唯一性,真实性和不冗余。
十、数据安全研究
数据安全这一职位,主要负责企业内部大型服务器、存储、数据安全管理工作,并对网络、信息安全项目进行规划、设计和实施。数据安全研究员还需要具有较强的管理经验,具备运维管理方面的知识和能力,对企业传统业务有较深刻的理解,才能确保企业数据安全做到一丝不漏。

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


ITeye推荐



结网–用户体验三要素

$
0
0

1

用户体验三要素:别让我等!别让我想!别让我烦!

别让我等

曾有很多研究表明:用户最满意的打开网页时间是2-5秒,如果等待超过10秒,99%的用户会关闭这个网页。也许这样讲,各位还不会有太多感触,接下来,我列举一组数据:Google网站访问速度每慢400ms就导致用户搜索请求下降0.59%;Amazon每增加100ms网站延迟将导致收入下降1%;雅虎如果有400ms延迟会导致流量下降5-9%。

怎么样?现在感受如何?是不是该马上去做测试工作呢。

这时候你也许会问:什么原因会导致网站打开慢?有什么办法解决呢?别急,我一一道来。以下内容有些偏技术层面,各位需要耐心阅读。

1、减少HTTP请求数

用户在打开一个网页的时候,后台程序响应用户所需的时间并不多,用户等待的时间主要花费在下载网页元素上了,即HTML、CSS、JavaScript、Flash、图片等,统计显示,每增加一个元素,网页载入的时间就增加25-40毫秒(取决于用户的带宽情况)。

所以,想要提高网页打开速度,就要减少HTTP请求数,方法有3种:

1)减少不必要的HTTP请求,例如用CSS圆角代替圆角图片,减少图片的使用。

2)合并文件,对于文本文件,可以直接合并内容。例如将多个JS(JavaScript的简称)文件合并成一个,将多个CSS文件合并成一个。

3)优化缓存,对于没有变化的网页元素(如页头、页尾等),用户再次访问的时候没有必要重新下载,直接从浏览器缓存里读取就可以了。

2、使用CDN(Content Delivery Network,内容分发网络)

CDN由一系列分散到各个不同地理位置上的Web服务器组成,它根据和用户在网络上的靠近程度来指定某台服务器响应用户的请求。当你的网站图片很多事,就一样要使用CDN了,比如现在的电商网站,几乎都在使用CDN。

3、压缩网页元素

网页中的每个元素越小,下载所需的时间就越少,这个很好理解。现在比较成熟和流程的压缩网页的方式,是通过Gzip,我自己的实操经验来看,一般可以将网页文本内容减少70%以上。

4、样式表放在网页Head部分

这也是我实际操作过的案例,把样式表(CSS文件)移到网页的Head部分,可以提高页面的加载速度,让页面元素顺序显示。

5、把JS文件放到网页底部

网页打开时,所有元素是顺序显示的。由于JS文件的特殊性,其相比其他元素来说,会加载的很慢,在JS文件下载完成之前,其他后面元素的顺序显示将被阻塞,因此把JS文件尽量放在底部,意味着内容能被快速显示。

6、把样式表和JS脚本放到外部文件中

尽管将样式表和JS脚本直接写入网页HTML中,可以减少外部文件调用数量,但是,这样做会增加网页的文件大小。综合来看,将样式表和JS脚本放到外部文件中,也许用户首次访问时会有点慢,但是后续在访问网站时,用户直接通过浏览器缓存就可以用,从而达到减少HTTP请求数的目的,为最优的做法。

写在最后的话:

在提升网页打开速度经常被忽视的一个问题是响应。对于用户来说,每次的操作,不管返回结果是慢,还是快,都要及时予以响应,最典型的例子就是:当用户点击打开一张图片时,是否有百分比数字显示的进度条,就是一个典型的响应设计。

一流的网站用户体验绝对不是一蹴而就的,要进行充分的可用性测试,收集用户的反馈,持续改进。

别让我想

用户不会使用一个网站绝对不是用户的错,他会打开电脑,会使用键盘和鼠标,会打开浏览器上网,经过这么步骤最终到达了你的网站,然后发现网站上一团糟,搞不懂这是什么,那是什么,也懒得学习如何使用,于是就会眼都不眨一下就关闭你的网站。这是很现实的一个用户行为。

在网站能够快速触达用户之后,网站运营人员面临的下一个重要问题就是:如何留住用户?

一、包装好你的网站

1、你是谁

我们辨识一个人,并不需要记住他从头到脚所有细节,看他的脸就OK了;当我们辨识一个网站时,也是同样的道理,网站的Logo就是脸。Logo可以让用户轻松知道网站是谁,无论浏览到网站中的任何一个网页,都能知道这是哪个网站在提供服务。

2、你是干什么的

有了网站Logo,还要告诉用户,网站是提供什么服务的,是购物网站,新闻网站,还是社交网站。尤其是对于一个新网站,清晰的一句话介绍至关重要。

3、你能带来什么好处

这就是网站的差异化优势介绍,相比同行竞争网站来说,用户使用你的网站,好处是什么,可以和“你是干什么的”整合成一句话至于Logo下方。

经过对网站的基础包装之后,我们算是完成了留住用户的第一步工作,接下来,我们希望用户在网站中做进一步的尝试,如何做呢?

二、关注用户及其任务,给予明确指引

对于用户来说,登陆任何一个网站,一定都有其目的。上购物网站是买东西,上门户网站是为看新闻,上SNS网站是为分享信息、联络朋友等,要达到这些目的,一定需要进行一系列操作。如果能让用户以最方便、最快捷的方式完成这些操作,顺利完成任务,毋庸置疑,用户一定会留下。

举例来说,如果是一家电商网站,是否有清晰的站内搜索功能提示,清晰的商品分类导航,醒目的购买按钮,以及灵活的付款结算系统等,这些都是围绕着用户及其任务(网上购物)展开的。因此,在设计网站的时候,我们一定要给予用户醒目、清晰的操作指引,以帮助用户尽快且顺利地进入下一步,下一步,直至最终完成任务(下单付款)。

满足用户,其实很简单,做到贴心、用心、上心,用户自然会向你靠近。

别让我烦

用户都是喜欢偷懒的,如果你的网站操作效率很低,就会令用户烦躁,进而导致不好的体验,甚至出现坏口碑。有一个粗略的说话是,完成任务的难度与其所需步骤的平方成正比,那么,缩短完成路径就是帮用户偷懒,就是好的用户体验。

举个正面的网站实例来说明。登陆美国亚马逊网站,当你进入一个商品页面之后可以无刷新切换商品的规格,比如我要购买一台苹果笔记本电脑,我打开了商品页(大家自己登录试试),商品右侧会有个版本切换的选项,在这里我可以快速查看不同配置的电脑价格,只需将鼠标移动上去,商品图片、价格等信息就会自动发生变化。这种人性化的设计,为想要比较商品的多个版本的用户提供了极高的操作效率,用户自然喜欢。

目标信息要醒目而亲近

在关注缩短完成路径这个问题的时候,优化操作步骤是第一位的,因为我们首先要简化用户的任务。接下来,我们要在任务内部优化指点设备(鼠标或手指等)运动轨迹和眼球运动轨迹等细节。根据费茨定律,使用指点设备到达一个目标的时间与以下2个因素有关:

1.设备当前位置和目标位置的距离,距离越短,所用时间越短。

2.目标的面积,面积越大,所用时间越短。

通俗来说,就是如果我们希望用户注意或点击某个元素(如文字、图片、按钮等),那么这个元素就不应该距离指点设备的当前位置太远(比如出现在屏幕的右侧),并且它的面积要足够大。伴随着Web2.0的热浪,网站设计也有了一系列的革新,其中最大的一个革新就是“以大为美”,大大的LOGO,大大的图片,大大的按钮,它们不光看起来更有冲击力,也更方便用户的识别和点击。

提醒方式要温和并及时

除了简化任务流程之外,在用户完成任务的过程中,网站有时需要给用户提供帮助和指引。之前常见的做法是采用弹框方式进行提示,用户需要关闭对话框才能继续自己的任务,无形中降低了操作效率。

最佳的提醒方式应该转向温柔的方式,将打断降低到最低,举个例子:当在你注册一个邮箱时,当输入用户名后,最佳的检验方式应是自动查询此用户名是否可用,如不可用,应给出建议,yeah.net邮箱就做的很好,图略(大家可以登录尝试)。

其实,有时候操作效率的降低,并不是因为功能设计得不够好,或者提示和建议处理得不够好,而是由于强行的塞进了一些用户不想要的信息或任务。比如,用户正在阅读一篇文章,忽然飘出来一个广告挡住了用户想要阅读的部分,用户怎么能不恼火?

经营网站,尽管是盈利为本,但是过分的不重视用户体验,忽视用户感受,小心衣食父母离你而去,并一去不返。

当用户犯错的时候,你是如何做的呢?

在我们做好一切运营优化后,有一种情况需要另外注意,就是用户会自己出错。

当用户出错的时候他也会很烦躁,他不会认为是他自己的问题,反倒会把责任推给网站,所以要尽可能降低用户出错的机会。

举一个网站的例子,当用户登录一个网站时,首先是输入用户名,现在最常见的用户名是采用邮箱。当用户在用户名的输入框内输入邮箱地址时,不可避免的会出现错误,比如少了1个字母,或少写了1个数字,或者邮箱后缀写错了,结果肯定是登陆不进去。用户一定会恼火,最佳的提醒方式应该是什么呢?

当用户输入用户名,光标离开输入框之后,网站应自动校验此用户名是否已注册(可用),如若用户填写错误,应及时给予提醒,如:用户名不存在,邮箱格式不对等,让用户的出错在萌芽时就完成纠正。

再举个非网站的例子。笔记本电脑电源线可能会绊倒人,同时笔记本电脑也会被摔倒地上,这很可能不在保修范围之内,这种情况很糟糕。看看Apple是怎么做的,Apple推出了名为Magsafe的电源接口以减少这种出错,Magsafe采用磁力的方式连接电源线与笔记本,当电源线受到外力时,会自动脱离笔记本。是不是很贴心的设计?

这样的例子还有很多很多,这里不再一一列举。记住:用户是否喜欢一个网站,不仅取决于他使用网站获得的好处,也取决于他在网站中获得的体验,两方面都是用户价值所在,缺一不可。

转自: http://jianshu.io/p/65934891ca45


(关注更多人人都是产品经理观点,参与微信互动(微信搜索“人人都是产品经理”或“woshipm”)

HttpOnly 隐私嗅探器

$
0
0
作者:zjcqoo

0x00 前言

终于赶上个想点东西的日子,原本打算继续更新那拖了好久的流量劫持系列的博客和工具。不过中午闲来无事去乌云逛了圈,发现百度的漏洞又双叒叕上头条了!而且还是那个 BDUSS 泄露的问题,记不得这已经是第几回了。尽管这事和咱没啥关系,但本着拯救世界和平的目的,还是打算去研究下:)

既然是 cookie 泄露,无非就两种。要么就是硬伤,例如之前 Apache 就会因为太多的请求头,导致 HttpOnly 也被输出到错误页里,当然这种严重的漏洞,随着补丁的更新很快就能解决;另一个当然就是内伤,由于程序猿的粗心大意,把重要的数据不加掩盖就放到页面里了。

前者很好解决,把在线的服务器都扫描一遍,修复一个就少一个。而后者就不那么容易了,产品经常更新迭代,谁也不能保证每次升级之后是否出现新的隐患。

既然找不到一劳永逸的方案,不如就用最简单最原始的土办法 —— 暴力穷举:把网页一个个抓回来,看看里面是否出现隐私数据了。当然你会说这也太挫太低效了,不过事实上这种方案还是有意义的,至少能很快的找出一些明显的问题。而且在此基础上,我们还可以再做优化和改进,让它变得更完善。

说到抓网页,大家总是先想到蜘蛛。虽然全自动化的流程是我们的终究目标,但在目前遐想阶段,就开始搞这么复杂的一套系统,还是不合适。而且如今遍地都是 AJAX 的交互网页,蜘蛛也很难爬到一些动态数据。

所以,不如先从最简单的开始:Chrome 插件。在我们平时看网页的时候,同时对页面进行分析。这样既节省了蜘蛛服务,而且还充分利用了真实的用户行为,覆盖到更多的动态交互内容。

0x01 获取隐私数据

使用 Chrome 插件来实现这个基本功能,是非常简单的。只要得到了带 HttpOnly 的 cookie 值,在浏览页面时扫描下 HTML 字符就可以了。

首先得获取浏览器的 cookie 数据库,得到我们想要的。例如,我们获取所有百度域下的 cookie:

chrome.cookies.getAll({domain: 'baidu.com'}, function(cookies) {
    console.dir(cookies);
});

稍加过滤即可获得 HttpOnly 的数据。

详细 API 可以 参考这里。关于 Chrome 插件开发这里就不详细介绍了。

值得注意的是,有些 cookie 值很简单,例如 1、true、ok 之类的;或者很常见,例如用户名、日期数字等,这些都得排除掉,否则会有太多的匹配。

0x02 扫描页面内容

有了关键字列表,我们就可以对页面内容做分析了。

我们新建一个 content 脚本,向 background 脚本获取列表。之后在页面的 DOMContentLoaded 事件里,即可对文档的 HTML 进行关键字扫描了:

// content.js
chrome.extension.sendRequest('GET_LIST', function(list) {

    window.addEventListener('DOMContentLoaded', function() {
        var html = document.documentElement.outerHTML;

        list.forEach(function(item) {
            if (html.indexOf(item) >= 0) {
                alert('found: ' + item);
            }
        });
    });
});

// background.js
chrome.extension.onRequest.addListener(function(message, sender, sendResponse) {
    if (message == 'GET_LIST') {
        sendResponse(list);
    }
});

到此,一个最简易的隐私嗅探器完成了。我们马上就来试验下,看看是否有效果。

先试试百度首页上的几个产品。不到 10 秒钟,就逮到了一个:

打开源文件一搜,果然看见了传说中带 HttpOnly 的 BDUSS:

赶紧测试其他页面。开始的几分钟时间里,不断发现新的泄漏点:

。。。

不过十分钟后,出现的越来越少了。我们是不是漏了些什么东西?

0x03 扩大扫描范围

显然,如果只扫描 HTML 内容,这和爬虫有什么区别?

我们之所以做成浏览器插件,就是为了能得到真实的用户行为操作。要是放弃了那些通过交互产生的动态数据,意义就大幅下降了。

遗憾的是,Chrome API 里并没有获得网络数据的接口。即使是 HTML,我们也是通过 <html>元素的 outerHTML 勉强得到的。

不过对于 AJAX 这类请求,我们有很简单的解决方案:钩子程序。

我们向页面中里注入一段脚本,覆盖原生 XMLHttpRequest 的方法,即可监控动态的数据了:

var _xhr_open = XMLHttpRequest.prototype.open;

function handlerXhrReady() {
    check(this.responseText, 'XHR: ' + this._url);
}

XMLHttpRequest.prototype.open = function(method, url) {
    if (!this._url) {
        this._url = url;
        this.addEventListener('readystatechange', handlerXhrReady);
    }
    _xhr_open.apply(this, arguments);
};

当页面使用 AJAX 加载数据时,我们即可监控其中的数据了。

类似的,我们还可以跟踪 URL 的变化,监控 HttpOnly 的数据有没有输出到地址栏里:

var address = location.href;

window.addEventListener('hashchange', function(e) {
    check(location.hash, 'location.hash');
});

var _pushState = History.prototype.pushState,
    _replaceState = History.prototype.replaceState;

History.prototype.pushState = function(data, title, url) {
    check(url, 'pushState');
    _pushState.apply(this, arguments);
};

History.prototype.replaceState = function(data, title, url) {
    check(url, 'replaceState');
    _replaceState.apply(this, arguments);
};

function scanAddress() {
    check(location.href, 'location.href');
}

对于这些调用 DOM API 的接口,我们都可以通过事件监听,或者钩子程序进行捕捉。

我们再来测试下,很快就发现刚才遗漏的一例:

对于其他网站,同样也存在这类问题:

多的不计其数。。。

由于 Chrome 插件以及 DOM 规范的限制,另一种常见的内容仍无法轻易获取,那就是 JSONP 脚本。外链脚本的 text 内容是无法获得的,而且使用 JSONP 大多是为了跨域,因此通过 xhr 去代理自然不可行。如果使用纯前端解决这个问题的话,可以尝试分析 JSONP 的回调接口,并勾住它。

0x04 更智能的分析

到目前为止,我们只是用最简单的字符串匹配,来检验是否泄露。现实中,泄露点的字符稍有转义,我们就无法识别了。因此,我们需要更智能的匹配算法。

例如某个 HttpOnly 的值为 hello|1234567,现实中很有可能是以 hello%7C1234567的形式存在的。脚本转义、URL 转义、HTML 实体转义...所以无论在哪,符号往往是不靠谱的,但字母和数字则相对稳定的多。

因此,我们可以提取数据中的字符数字(\w) 部分,作为扫描项。隐私数据虽然很长,但大多都是冗余的,因此提取其中部分作为特征,仍然不会有错失。

即使有少量误报,也没什么事。 宁可误报,也不漏报。

除了转义,还有可能将隐私数据经过简单的编码之后输出了,例如 Base64。这些弱算法可以轻易还原出原始数据,但通常很难发现。

因此,我们可以将所有的数据,事先做一些简单的算法,同时将它们也作为检测目标,这样就能更深层次的挖掘泄漏点了。

0x05 深度嗅探

事实上,除了 HttpOnly 的 cookie,我们还可以将其他数据也作为侦测目标。

我们可以创建一个测试账号,将其手机号,密码,身份证等各种私密信息都作为待检测的隐私数据,这样就能全方位的分析出,哪些信息遭到了泄露。

例如,曾经有网站为了偷懒,将找回密码的答案直接输出到了页面脚本变量里,而不是在后端判断。这时,我们就能很快追踪到泄漏点。

0x06 终极嗅探

更新中...

非财会出身的老板应该懂得哪些财务知识?

$
0
0
这是个好问题,但不好答。所谓术业有专攻,对老板来说,财务知识掌握的当然越多越好,但你不大可能把这些都说明白,所以我们财务人员只能有针对的做些解答,这个度很不好把握,只能在此提些个人建议,仅供参考。
首先要能看懂三张表,这是基础,老板们要明白,资产负债表提供的是资产负债和权益在某时点的信息,损益表现金流量反映的是期间发生额之类。在此建议不需要关注所有信息,抓住重点就行,也不要只看数字,要看数字背后的东西。
1、货币资金:不仅要看余额,还要看发生额,每个月现金的流入流出情况对经营者来说是很重要的,要有资金计划,充分利用资金,不能有闲置,更不能短缺;
2、往来款项:这里要关注授信,关注账龄,每个月要有个客户账龄分析表,超账期的,超授信的都是风险,都是利润,要重点关注,对业务来说,放几天不收,他们就能从中搞到好处,对财务来说,这都是工作量,人都有私心,所以老板只能靠自己;
3、存货:存货也是资产,闲置一样是浪费,怎么利用老板们应该都很清楚,在此就提醒二点,一是存货明细账和存货盘点真的很重要,千万不要不当一回事;二是重视发出商品,发出商品其实是债权,发出货的同时钱也就出去了,一样要有账期,一样要算利息,更要及时了解及时结算;
4、固定资产:别看固定资产金额大,反而不用特别关注,只要让管理部门做好登记,不要让资产闲置浪费就行了。
5、成本和费用:老板要学会分清成本和费用的关系,成本是要和收入配比结转的,费用是当期发生的,两者不是一回事,所以老板要关注个人借款挂账的情况,这也是对公司资源的占用。还要关注成本归集的合理性,关注有没有为了提高业绩把费用当成本核算的情况。
6、预算管理:这个其实很重要,业务报费用合不合理其实财务很难把关,没有预算,老板也只能听业务说,老板也是人,老板也不想让员工骂,天天做恶人,有预算就有了依据,无形中就多了一道关口。
7、内部控制:这个老板们其实多少都会点,再学点理论知识就够了。内控的核心就规范化、流程化,一切都要按制度来。那制度的核心又是什么呢?个人理解就是分工要科学,一人为私,两人为公,部门之间,员工之间要能通力合作,又要相互牵制。
就聊到这里,有空再补充,欢迎知友在此讨论。

— 完 —
本文作者: 猫大叔

【知乎日报】 你都看到这啦,快来点我嘛 Σ(▼□▼メ)

此问题还有 14 个回答,查看全部。
延伸阅读:
对非财会专业的人来说,怎么很容易的区分《财务管理 》、《管理会计》、《 财务会计》这三本书的区别?
非财务专业人士是否可以考取ACCA?

CNZZ网站流量统计原理简析

$
0
0
这是我的域名www.iyizhan.com,暂无内容,其中只有一个页面index.html,
在index.html上放置了如下的 js 脚本:
<script 
src="http://s11.cnzz.com/stat.php?id=5364825&web_id=5364825" 
language="JavaScript">
</script>


1.当用户访问这个页面时,会请求src,对应的是上面的脚本的源文件:
Request URL:http://s11.cnzz.com/stat.php?id=5364825&web_id=5364825
请求的query:
id:5364825 //网站id
web_id:5364825 //网站id
脚本会对其中的参数赋值,其中this.q = "CNZZDATA" + this.c为cookie名 
cnzz.js中的函数L会为生成一个我的网站域下的cookie,即
名称: CNZZDATA5364825
内容: CNZZDATA5364825=cnzz_eid%3D2115650326-1406861325-%26ntime%3D1406869345
//CNZZDATA5364825=cnzz_eid=2115650326-1406861325-&ntime=1406869345
域: www.iyizhan.com
其中的ntime会随着访问而更新。
//上面的cookie值表示的仅仅是三个时间值。


并触发如下的链接请求:


2.Request URL:
http://hzs14.cnzz.com/stat.htm?id=5364825&r=&lg=zh-cn&ntime=1406861325&cnzz_eid=124331323-1406861325-&showp=1280x800&t=zhaohf-CNZ...&h=1&rnd=1196942655
(这个请求一个1x1的不可见像素,其中的参数代表不同的含义)
id:5364825 //网站id
r:weibo.com //refer,来源。
lg:zh-cn //页面语言
ntime:1406861325 //now time请求时的时间戳
cnzz_eid:124331323-1406861325- //一个随机数(过期时间)-首次访问时间,差即cookie的生命周期
showp:1280x800 //页面(屏幕)大小
t:zhaohf-CNZ...//页面的title
h:1 //?
rnd:1196942655 //random,一个随机数,目的是每次访问都要请求


请求头中如下:
Accept:image/webp,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8
Cookie:
cna=YuVcDKp/IhQCAXL/FAmu3PJk; 
PHPSESSID=7e0dc5ea1b0fa3b5357f0b46cb4245d5; 
lzstat_uv=22071519872809337448|3037573; 
UC_SID=e3a977d0ecd733fb2adb54b5a3cfc3ae; 
TJ_LG=cnzz; 
Host:hzs14.cnzz.com
Proxy-Connection:keep-alive
Referer:http://www.iyizhan.com/
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36


3.Request URL:http://c.cnzz.com/core.php?web_id=5364825&t=z
这对应的是一个js脚本。


4.Request URL:http://cnzz.mmstat.com/9.gif?abc=1&rnd=659454759

服务器写入cookie:
Set-Cookie:aui=137828086; expires=Mon, 29-Jul-24 03:49:34 GMT; path=/; domain=.cnzz.mmstat.com
Set-Cookie:atpsida=69e4ef05fb4ee7ea895ac82a_1406864974; expires=Mon, 29-Jul-24 03:49:34 GMT; path=/; domain=.cnzz.mmstat.com


5.Request URL:http://pcookie.cnzz.com/app.gif?&cna=YuVcDKp/IhQCAXL/FAmu3PJk
请求另一张图片,其中的参数
cna=YuVcDKp/IhQCAXL/FAmu3PJk
是上一步生成的cookie
回传给服务器
返回的是在cnzz.com下的cookie:
Set-Cookie:cna=YuVcDKp/IhQCAXL/FAmu3PJk; expires=Mon, 29-Jul-24 03:49:34 GMT; path=/; domain=.cnzz.com


------------------------------------------------------------------


由上可见,用户第一次访问网站时,会向cnzz发如下的5个请求(如下图):
1.请求第一个 js 脚本(我命名为cnzz.js)
2.上一步的 js 在用户端浏览器上执行,向cnzz请求一个html文件,
其中的内容只有1x1不可见像素,作用是发送parameter数据
3.请求第二个 js 脚本(我命名为core.js)
4.此脚本在用户的浏览器执行后会触发一个新的请求,向cnzz.mmstat.com请求一个1x1图片,
目的是服务器端生成cookie返回给浏览器
5.浏览器得到cookie之后将cookie发送给pcookie.cnzz.com,这个应该是专门处理cookie的服务器

,然后把新的cookie设置到cnzz.com域上,并返回给用户浏览器


------------------------------------------------------------------
以上是用户首次与CNZZ交互的全部过程。
之后访问的话,就只有前3个请求(如下图),即请求cnzz.stat.php,stat.html,core.php
并在cookie中带有 cna=YuVcDKp/IhQCAXL/FAmu3PJk,用来判断有几个Unique Visitor.


没有接着发请求是因为:返回的第二个js文件中的
"0" === m && k.callRequest([l + "//cnzz.mmstat.com/9.gif?abc=1"])
可以发现重复访问的时候,返回的m = "1",即不会执行这个请求。
------------------------------------------------------------------
//注:点击【站长统计】即可看到统计结果,不过基本为0。

作者:ozhaohuafei 发表于2014-8-17 9:09:51 原文链接
阅读:0 评论:0 查看评论

开放的安卓系统遇上了麻烦

$
0
0

对于供应商来说,从来没有一条推文显得如此之真实或者具有灾难性。几年前,为了回应史蒂夫·乔布斯批评 Android 并不是真的开放,Google 的安迪·鲁宾发了一条推文说任何人都可以拉取 Android 的分支并修改,这是开放的真实体现。

对于 Google 来说,不幸的是许多 OEM 对此话信以为真。

如今,Google 的 Android 系统正处于蓬勃发展时期,但同时 Android 的碎片化也使 Google 或它的应用开发者生态系统收入最小化了,不幸的是,根据最新的 ABI 研究报告,情况只会越来越糟。

释放自由基因

虽然这些年来对于 Android 到底有多开放大众有诸多顾虑,安迪·鲁宾的推文还是让它烟消云散了。

Google 开放源码主席确认了鲁宾的观点,告诉笔者已有超过 1000 万行基于开放源码许可的 Android 代码对所有人开放了。

是的,Google 控制了 Android 的整个开发流程,而且可能会选择性地对第三方开放源码,但相对的,Google 一直是 Android(和许多其它开源工程)的卓越管家。

也许管家做的太好,Android 这个领地里付出代价的,却是应用开发者。

开源一直是 Google Android 操作系统的优点,不像之前的移动操作系统如 iOS(只有苹果可用)或 Windows(收费,基于微软许可),Android 可以免费使用。

有多好?Android 从之前的默默无名,发展成为了如今移动设备领域的领军力量。

奇怪的是,Android 的应用开发者并没有分到应得的一杯羹。

iOS 开发者一直都比 Android 开发者赚的更多,尽管 Android 的声势浩大,但系统的碎片化让其开发者很难赚到钱。

有多难?根据 Vision Mobile 的资料显示,64% 的 Android 开发者处于“App 贫困线“以下,即每个 App 每月赚取 500 美元。

对 Google 来说也许同样不是好事。碎片化同样伤害到了 Google,与第三方开发者感受到的不太一样,但同样造成了伤害。

Google 已经开始着手于解决碎片化的问题,一方面迫使 OEM 厂商尽快升级到最新版本的 Android 系统来部署 Google 移动服务包或 Google 应用,另一方面也在 Google Play 里引入新的开发 API,来保障 OEM 或用户能体验到最新的 android。

这么做的一个后果,是让所有的终端用户都运行官方的 Android 版本,但诸多的 OEM 厂商考虑的是自家独特的 Android 体验。

ABI 研究显示,从 2014 第一季度到第二季度,相比整体市场的3%,“分叉版”Android 手机(AOSP 智能手机)实现了 20% 的增长。换句话说,分叉版 Android 已经占据了 20% 的全球智能机市场,而且增长速度远远超过了整体市场。

开放的安卓系统遇上了麻烦

同样增长迅速的是具备 OHA 认证的 Android 手机市场,这些官方的 Android 手机占据了 65% 的智能机发货份额,增长率为 13%。

如果都是些小本经营的 OEM 厂商,可能无关紧要,但根据 VisionMobile 的数据显示,Android 的最大增长来自于一个乐于孤立开发软件的地区:亚洲。

开放的安卓系统遇上了麻烦

ABI 研究分析人员 Nick Spencer 解释了其中意味:

AOSP 的增长动力来源于中国和印度的手持设备制造商的快速发展,不仅是在其国内市场,同样包括扩散到的全亚洲以及其他地区。中国和印度以 51% 的份额第一次成为了智能机最大的出货大户,其中多数为低成本制造商,一些开始进入中端厂商的地盘,特别是小米和金立这样的制造商,逐渐成为了三星的有力威胁。

换言之,世界上最大的两个市场充斥了 AOSP/分叉版的 Android 供应商,他们开始将产品远销海外,从而挑战 Google 在全球统一化 Android 的能力。

按互联网服务收费

Google 还能从这些分叉版 Android 系统挣到钱吗?也许。Asymco 解释道,除开中国,Google 从每个互联网用户身上每年大概可以挣到 6.3 美元。因此理论上来说,这些 Android 手机(或 iOS、Windows)很大一部分应该能为 Google 变现,因为它们都携带了连接到 Google 网络的网关入口。

实际上,并非如此。

Asymco 进一步解释,虽然 OEM 厂商有很多理由拉取 Android 分支,但最有可能的理由是灵活性。

在价格和本地化上竞争的供应商寻求的是,相比对手能更快速地行动,等待的成本过于高昂。对于这些供应商来说,一些基于各类“联盟”的要求过于细致,难以达到,这样造成的结果是“更加开放”版本的 android 开始逐渐威胁到“不那么开放”版本的 Android。

这些“更加开放”的 Android 经常会包含“一个独特的用户界面和一系列的服务”,包括中国制造的搜索和其它非 Google 应用,换言之,分叉版 Android 可能没法为 Google 赚取用户的 6.3 美元了。

笔者认为 Google 会对 Android 施加更多的控制来最小化碎片化对用户的影响,最大化自己的收益,而 Android 的开源也严重削弱了苹果曾经在手机领域坚不可摧的桎梏。在一个苹果的世界里,Google 的盈利能力是不稳定的。

换言之,对于 Google 来说,“过于开放的 Android”仍然远远好于“过于封闭的 iOS”。(沙鸥)

本文链接

深度学习word2vec笔记之应用篇

$
0
0

声明:

1)该博文是Google专家以及多位博主所无私奉献的论文资料整理的。具体引用的资料请看参考文献。具体的版本声明也参考原文献

2)本文仅供学术交流,非商用。所以每一部分具体的参考资料并没有详细对应,更有些部分本来就是直接从其他博客复制过来的。如果某部分不小心侵犯了大家的利益,还望海涵,并联系老衲删除或修改,直到相关人士满意为止。

3)本人才疏学浅,整理总结的时候难免出错,还望各位前辈不吝指正,谢谢。

4)阅读本文需要机器学习、概率统计算法等等基础(如果没有也没关系了,没有就看看,当做跟同学们吹牛的本钱),基础篇url: http://blog.csdn.net/mytestmy/article/details/26961315 。

5)此属于第一版本,若有错误,还需继续修正与增删。还望大家多多指点。请直接回帖,本人来想办法处理。

6)本人手上有word版的和pdf版的,有必要的话可以上传到csdn供各位下载。

 

 

 

好不容易学了一个深度学习的算法,大家是否比较爽了?但是回头想想,学这个是为了什么?吹牛皮吗?写论文吗?参加竞赛拿奖吗?

不管哪个原因,都显得有点校园思维了。

站在企业的层面,这样的方式显然是不符合要求的,如果只是学会了,公式推通了,但是没有在工作中应用上,那会被老大认为这是没有产出的。没有产出就相当于没有干活,没有干活的话就……呃……不说了。

下面就给大家弄些例子,说说在互联网广告这一块的应用吧。

 

一.对广告主的辅助

1.1基本概念

互联网广告的广告主其实往往有他们的困惑,他们不知道自己的目标人群在哪里。所谓目标人群,就是广告主想向他们投广告的那帮人。就像互联网广告的一个大牛的一句名言——我知道互联网广告有一半是浪费的,问题是我不知道是哪一半。

这个困惑就给媒体带来一个义务——要帮助广告主定向他们的目标人群。

对于普通的广告主来说,比如说一个化妆品广告的广告主,它的目标人群很明显就是年轻的女性。注意关键词“年轻”和“女性”,这是决定媒体这边能否赚到钱的关键词。要知道对于媒体来说,广告主是它们的客户,满足客户的要求,客户就给它们钱,不满足客户的要求,就没有人为媒体买单;没有人为媒体买单,媒体就没有钱养它们的员工和机器,也弄不来新闻和互联网的其他内容,那样媒体公司就垮了……

那么在媒体这边,需要做的的工作就很明确了——满足它们的客户(也就是广告主)的需求。怎么满足呢?这工作说容易也容易,说简单也简单,就是把喜欢这个广告主的广告的人找出来,然后帮这个广告主把他们的广告投放给这些人,让这些人看到这个广告主的广告。

这个工作带来的问题就真多了,媒体又不是什么神人,比如说一个新闻网站,浏览这个网站的每天有100万人,这个新闻网站的员工不可能一个个去访问他们的用户(浏览这个网站的人),整天问他们你喜不喜欢化妆品啊,喜不喜欢体育啊之类的问题。

那怎么办呢?媒体的员工只好猜了,但是哪怕是猜都很费劲,想想都头疼,一百万人啊,一个个猜也得吃力不讨好啊。这时候计算机的作用就来了,用计算机猜嘛,而且不一定需要全部瞎猜的,因为用户如果注册了的话,还有一些用户的个人信息可以参考的。一般的网站注册的时候都要求提供年龄性别之类的个人信息,有时候要要求写一些个人的兴趣什么的标签。这个时候这些数据就用上大用处了。

网站可以把注册用户的个人信息保存下来,然后提供广告主选择。如上面的那个化妆品的广告主,它就可以跟媒体提它的要求——我要向年轻的女性投放广告。媒体这个时候就可以提供一些条件给这个广告主选择,如媒体说我有很多用户,18到80岁的都有,然后男性女性用户都有。广告主就可以根据这些条件选择自己的目标用户,如选择了18到30岁的女性用户作为目标人群。选中了目标人群后,广告主和媒体就可以谈价钱了,谈好了价钱广告主就下单,然后媒体就帮广告主投广告,然后媒体的钱就赚到了。

 

1.2兴趣挖掘的必要性

上面多次提到的“目标人群”,就是广告主最关心的事情。客户最关心的事情自然也是媒体最关心的事情。所以媒体会尽力帮助它们的客户去定向它们的目标人群。

一般所谓的定向也不是媒体亲自有一个人来跟广告主谈的,是媒体建立好一个页面,这个页面上有一些选项,比如年龄,性别,地域什么的,都是条件。广告主在上面把自己的目标人群符合的条件输入,然后下单购买向这些人投放广告的机会。

媒体为了更好地赚钱,肯定是愿意把这个页面上的条件做得更加丰富一点,让更多的广告主觉得这个网站的用户里面有它们的目标人群,从而让更多的广告主愿意过来下单。

广告主的定向其实有粗细之分的,有些广告主粗放点,它们有钱,选的定向条件比较宽,就说女性的用户,全部都投放;有些就定向得比较窄,比如说,北京的20到25岁的女性,并且要喜欢羽毛球的用户。对于定向宽的广告主好处理,问题就是这些定向窄的广告主,它们还希望知道用户的兴趣所在,这就麻烦了。

为啥麻烦呢?一个用户的兴趣鬼才知道呢。就算当面问,人家也不乐意回答,何况就凭借一点点东西瞎猜。但是为了赚钱,瞎猜也得上的了,工业界为了赚这个钱,诞生了整整一个行业——数据挖掘,甚至在学术界还有一个更加生猛的名字——机器学习。学术界的那个名字和解释都是相当大气的:让机器学会像人一样思考。工业界就务实一点,只是对数据内容本身做一个挖掘,获取到啥呢?一般就是用户的兴趣啊,爱好啊什么的。这些东西供谁使用呢?暂时看来只有广告主愿意为这些掏钱,其他的就有些媒体做来让自己推荐的内容不至于让用户那么反感而已。

上面有个名词“数据”,没错了,这个词是互联网广告业,甚至是数据挖掘行业的核心的东西。所谓数据,这里简单点说就可以认为是用户的年龄、性别、地域等用户的基本属性;复杂点说可以说是用户兴趣、爱好,浏览记录等;更高级的有用户的交易数据(当然这个高级的数据很少媒体能搞得到)等。

解释完“数据”这个词,结合一下广告这个场景,就可以得到活在媒体公司里面的互联网广告行业数据挖掘工程师的工作是什么了。他们的工作就是:根据用户自身的基本属性和用户流量的网页记录以及内容,想方设法让计算机猜出用户的兴趣爱好。用户的兴趣爱好“挖掘”出来后,就可以作为定向条件放到上面说的那个网页上面供广告主选择了。这事情整好了,广告投了有人点击,公司的钱就赚到了;没整好,广告没人点击,广告主不乐意下单了,公司就赚不到钱……怎么着?炒这些工程师的鱿鱼去。

上面可以看到了,辅助广告主定位它们的目标人群是很重要的。

经过一番的探索,word2vec在互联网广告上面也是可以辅助广告主定向他们的目标人群的,下面就讲讲这个算法在互联网广告的应用吧。

1.3利用word2vec给广告主推荐用户

为了用上word2vec,把场景转换到一个新闻媒体如A公司。

在A公司的多个页面中,电商公司B有他们的一个主页,专门介绍他们公司一些产品促销,抢购和发布会什么的。

公司A目前有很多用户的浏览数据,如用户u浏览了公司A的页面a1,a2,a3等。

把这些数据处理一下,整合成word2vec能处理的数据,如下

U1  a1,a2,a3……

U2  a2,a3,a5,……

U3  a1,a3,a6,……

其中u1,u2,u3表示不同的用户,后面的一串表示这些用户的浏览记录,如U1  a1,a2,a3表示用户u1先浏览了页面a1,再浏览a2,然后浏览了a3,……

这些数据还不符合word2vec的输入数据格式,把第一列去掉,变成下面的样子

a1,a2,a3……

a2,a3,a5,……

a1,a3,a6,……

这些数据就可以作为word2vec的输入数据了。

就把这些数据作为word2vec的训练数据,词向量维度为3,进行训练,完成后得到下面的输出

A1  (0.3,-0.5,0.1)

A2  (0.1,0.4,0.2)

A3  (-0.3,0.7,0.8)

……

An  (0.7,-0.1,0.3)

就得到了每个页面的向量。

这些向量有啥意义呢?其实单个向量的意义不大,只是用这些向量可以计算一个东西——距离,这个距离是页面之间的距离,如页面a1和a2可以用欧式距离或者cos距离计算公式来计算一个距离,这个距离是有意义的,表示的是两个网页在用户浏览的过程中的相似程度(也可以认为是这两个页面的距离越近,被同一个人浏览的概率越大)。注意这个距离的绝对值本身也是没有意义的,但是这个距离的相对大小是有意义的,意思就是说,假设页面a1跟a2、a3、a4的距离分别是0.3、0.4、0.5,这0.3、0.4、0.5没啥意义,但是相对来说,页面a2与a1的相似程度就要比a3和a4要大。

那么这里就有玄机了,如果页面a1是电商公司B的主页,页面a2、a3、a4与a1的距离在所有页面里面是最小的,其他都比这三个距离要大,那么就可以认为同一个用户u浏览a1的同时,浏览a2、a3、a4的概率也比较大,那么反过来,一个用户经常浏览a2、a3、a4,那么浏览a1的概率是不是也比较大呢?从实验看来可以这么认为的。同时还可以得到一个推论,就是用户可能会喜欢a1这个页面对应的广告主的广告。

这个在实验中实际上也出现过的。这里模拟一个例子吧,如a1是匹克体育用品公司在媒体公司A上的官网,a2是湖人队比赛数据页,a3是热火队的灌水讨论区,a4是小牛队的球员讨论区。这个结果看起来是相当激动人心的。

根据这样的一个结果,就可以在广告主下单的那个页面上增加一个条件——经常浏览的相似页面推荐,功能就是——在广告主过来选条件的时候,可以选择那些经常浏览跟自己主页相似的页面的用户。举个例子就是,当匹克体育用品公司来下单的时候,页面上给它推荐了几个经常浏览页面的粉丝:湖人队比赛数据页,热火队的灌水讨论区,小牛队的球员讨论区。意思是说,目标人群中包括了经常浏览这三个页面的人。

这个功能上线后是获得过很多广告主的好评的。

这样word2vec这个算法在这里就有了第一种用途。

 

二. 对ctr预估模型的帮助

根据另一篇博文《互联网广告综述之点击率系统》,里面需要计算的用户对某广告的ctr。在实际操作的时候,这个事情也是困难重重的,其中有一个冷启动问题很难解决。冷启动问题就是一个广告是新上线的,之前没有任何的历史投放数据,这样的广告由于数据不足,点击率模型经常不怎么凑效。

但是这个问题可以使用同类型广告点击率来缓解,意思就是拿一个同行的广告的各种特征作为这个广告的特征,对这个新广告的点击率进行预估。

同行往往太粗糙,那么怎么办呢?可以就利用跟这个广告主比较相似的广告的点击率来预估一下这个广告的点击率。

上面说过,可以得到每个页面的词向量。这里的方法比较简单,如在媒体公司A上面有1000个广告主,它们的主页分别是a1、a2、……、a1000。

根据上面的方法,得到了这1000个词向量,然后运行kmean或者其他聚类算法,把这1000个广告主聚成100个簇,然后每个簇里面的广告主看成是一个。

这里可以模拟一个例子,聚类完成后,某个簇c里面包含了几个广告主的主页,分别是京东商城,天猫,唯品会,当当,聚美优品,1号店,蘑菇街,卓越,亚马逊,淘宝这10个,这10个的目标人群看起来基本是一致的。

这里的看成是一个簇是有意义的,比如说第一个簇c1,c1这个簇里面的所有历史投放数据和实时数据可以做特征,来预估这个流量对这个簇的ctr。得到这个ctr后,就很有用了,如果某广告投放数据比较充分,就直接预估这个广告的ctr;如果某广告的历史投放数据很少,就用这个广告主所在的簇的ctr来代替这个广告,认为对簇的ctr就是这个广告的ctr,这样能让一个新广告也能得到相对靠谱的预估ctr,保证不至于乱投一番。

 

三.一些总结

如何应用好一个算法,确实是很多算法工程师的一个重大课题。

数据挖掘算法工程师经常要面对的一个难题就是:这个算法怎么用到我们的数据上面来?有不少同学会认为是:我到了公司,就发明一个很牛逼的算法,把公司的原来的问题解决掉,然后大大增加了效果,获得了领导的好评。这个天真烂漫的想法就不评价了,免得被说打击人。互联网企业里面的真实情况是算法工程师面对那一团乱遭的数据,得想尽办法去把数据整合成能用的格式。

拿上面的(1.3)中的例子,那个把数据组合成a1,a2,a3……这样一行行的,然后进入word2vec去进行训练是最难想到的而且是最核心的东西,虽然明着说是word2vec这个算法厉害,实际上面是“把数据整合成合适的方式交给word2vec进行训练”这个想法重要,因为尝试了很多想法,做了很多实验才能想到这样的一招的。

还有数据的整合其实也费了很多功夫的,比如说媒体有些用户是一些机器的账号,人家乱搞的,要想办法排除掉的,而“想办法排除”这么简单一句话,真正要做的工作真是多多的有。

哪怕结果都训练出来了,怎么解释这个结果是好的?这个问题也是得想了一段时间的,后来是实验发现了利用词向量的距离来评价相似性这个东西最靠谱,然后才用上的。

一个数据挖掘的过程其实不简单,这个博客也没办法一一体现做的过程里面的那些各种折腾,各种不顺畅。

数据挖掘工程师经常要面对的另一个难题就是:明明理论上推得杠杠的,算法性能也是杠杠的,但是对于互联网广告的效果,怎么就那么不咸不淡的呢?

这个问题真没有什么统一的答案,这种现象多了去了。经常遇到的原因有:数据本身处理的方式不对和算法不合适。

所谓数据本身处理的方式,可以参看博文《互联网广告综述之点击率特征工程》,里面说的那些方法不是从哪本书上面看到的,是经过比较长时间实践,然后各种折腾,各种特征取舍,各种胡思乱想,各种坑踩出来的。可能志在学术的人看起来都简单,实际上课本那些东西,学生们吹起牛皮来不眨眼的那些东西,一跟真实应用场景结合起来就各种坑要踩的了。

拿上面的(二)中的例子来看。方法简单得不得了,但是可以想象一下,word2vec牛逼啊,kmeans牛逼啊,第一次聚类出来的结果也不过如此。后来又加入了每个广告主的行业和地域作为特征,而且这个加特征,就是直接把行业和地域处理一下,连接到广告主的词向量后面的。如a1的词向量是(0.3,-0.5,0.1),然后假设只有两个行业,体育和化妆品,处理成二值特征,占据第4和5两个index,第4个特征为1,第5个特征为0表示体育类广告主,反过来,第4个特征为0,第5个特征为1表示化妆品;再对地域的下标做了一下处理,成为二值特征,比如说占据了6到10这5个位置(假设第6个位置为1,其余7到10为0表示北京;第7个位置为1,其余为0表示广东,以此类推)。

经过了上面的处理,再用kmeans进行聚类,从聚类后一个个簇去看,结果看起来才顺眼了很多。上面的行业和地域特征的加入,也是用了比较多的经验的,不是凭空乱整出来的一个吹牛皮的东西,当然谁有更好的方法,也可以提出来试试看。另外还希望大家注意关键字“一个个簇去看”,这个工作真是费时费力,比较辛苦的。

以上举了一些例子,也把互联网广告的数据挖掘算法工程师的一些工作中的成功和不成功的地方都说出来了,基本上算是实话实说,希望对大家有点帮助吧。有过类似经历的人能看懂,没啥兴趣的就呵呵吧。

 

 

致谢

多位同事提供的建议与指导。

多位google研究员有关word2vec的资料。

 

本文转载自: mytestmy的专栏

浅析jQuery EasyUI响应式布局的实现方案

$
0
0

首先解释一下本篇文章标题中提到的“jQuery EasyUI响应式布局”,这里是指EasyUI里布局类组件的fit属性,也就是实现自适应的属性。到了1.4版本,新增了一个宽度百分比的概念,既可以用在布局类组件上,也可用在表单类组件上,但是其实现方案跟fit是类似的。

也就是说,jQuery EasyUI的自适应布局包含两块内容:

  • 布局类和表格类组件的fit属性设置为true,也就是宽度和高度都100%;
  • 布局类组件,表格类组件和表单类组件的宽度设置为百分比;

因为EasyUI比较复杂的DOM结构设计,导致响应式布局无法使用css里原生的百分比去实现,通常宿主DOM,都会被包装得面目全非。最后组件呈现的时候,全部是以具体的像素值显示的。

1.4版本的代码来做分析,我们看看EasyUI的fluid神秘面纱后的逻辑到底是个什么样的?特别是组件多层嵌套的时候,它是如何做到每个组件都能自适应的。

追本溯源

先来追本溯源,fluid的本质是浏览器窗口调整大小的时候,页面的布局能相应的做调整,而在这个过程中,唯一能利用的事件是window的resize,所以EasyUI响应式布局的实现,一定是在window的resize事件中处理的。

我们在源码中搜索"$(window)"关键字,共搜索到20个左右,但是跟resize事件相关的只有两个地方。一个是panel组件里绑定的resize;另一个是window组件里绑定的resize事件。

window组件绑定的resize

为什么要把window组件放到前面看?那是因为window组件里绑定的resize跟fluid实在没什么关系,来看代码:

  1. $(window).resize(function () {
  2.         $("body>div.window-mask").css({width: $(window)._outerWidth(), height: $(window)._outerHeight()});
  3.         setTimeout(function () {
  4.             $("body>div.window-mask").css({width: _26d().width, height: _26d().height});
  5.         }, 50);
  6.     });

这个事件处理程序,像个一丝不挂的普通姑娘,没啥内涵,目的单纯而直白——只是为了实时调整window组件的蒙版大小,他的功能相对于fluid来讲实在微不足道,所以我们一眼嫖过去,不做过多讨论。

panel组件绑定的resize

panel组件绑定的resize事件处理程序是相当有内涵的,她绝对不是一丝不挂,而且穿了好几层情趣内衣,我们需要一层一层的扒,需要耐心。先来看事件处理程序的定义:

  1. // 定义的了一个定时器的引用,老鸟们应该都知道他的目的有两个
  2. // 一是防止短时间内多次触发window的resize事件处理程序;
  3. // 二是解决某些浏览器调整一次窗口却多次触发resize事件的问题
  4. var _23f = null;
  5. $(window).unbind(".panel").bind("resize.panel", function () {
  6.     // 100ms内触发多次的话,则终止对前一次的事件处理程序的调用
  7.     if (_23f) {
  8.         clearTimeout(_23f);
  9.     }
  10.     // 重置定时器
  11.     _23f = setTimeout(function () {
  12.         // 这里主要是看body是否是一个layout实例(是layout的话,body元素上会有layout样式)
  13.         var _240 = $("body.layout");
  14.         // 如果body是一个layout,则调用layout的resize方法,
  15.         // layout的resize方法最终调用的也是panel的resize方法
  16.         // 所以layout的resize显然是个多层情趣内裤,我们不急着扒。
  17.         if (_240.length) {
  18.             _240.layout("resize");
  19.         } else {
  20.             // 如果body不是一个layout组件,则调用panel组件的doLayout方法
  21.             // 这个层数应该少点,而且地处核心位置,我们先扒这个doLayout
  22.             $("body").panel("doLayout");
  23.         }
  24.         // 清空定时器
  25.         _23f = null;
  26.     }, 100);
  27. });

当我们的页面上引用了jquery.easyui.min.js这个伪开源的文件之后,这段代码会被执行一次。这段代码虽然不长,但是里面调用了resize和doLayout方法。

layout的resize方法,其底层调用的是panel的reszie方法(这层内衣我迅速的扒了,不信的同学可以自己看layout的代码)。所以,最后的焦点全部落到panel组件的两个方法上: doLayout和resize

doLayout方法

找到panel组件的doLayout代码(搜"doLayout"关键字即可):

  1. function doLayout (jq, all) {
  2.     return jq.each(function () {
  3.         // 缓存this
  4.         var _24a = this;
  5.         // _24b变量判断当前容器是不是body
  6.         var _24b = _24a == $("body")[0];
  7.         // find函数的选择器真的很长,最后还用了filter方法来进一步过滤find出来结果
  8.         // 这个写法看起来似乎很简洁明了,但是,他是否高效呢?这个问题先放一放
  9.         // 我们先弄清楚这个变量s到底是什么?这个必须要拿例子来说明,注释里我试了很多方式去表达,都觉得表达不清楚。
  10.         var s = $(this).find("div.panel:visible,div.accordion:visible,div.tabs-container:visible,div.layout:visible,.easyui-fluid:visible").filter(function (_24c, el) {
  11.             var p = $(el).parents("div.panel-body:first");
  12.             if (_24b) {
  13.                 return p.length == 0;
  14.             } else {
  15.                 return p[0] == _24a;
  16.             }
  17.         });
  18.         // 找到的需要做fluid布局后,触发绑定在他们DOM上自定义的的"_reszie"事件
  19.         s.trigger("_resize", [all || false]);
  20.     });
  21. }

对于doLayout函数中的s变量,分两种情况举例子。

如果当前容器是body:

  1. <!-- 当前容器 -->
  2. <body>
  3.     <div id="a1">
  4.         <div id="a21" class="accordion">
  5.             <div id="a3">
  6.                 <div id="a4" class="accordion">...</div>
  7.             </div>
  8.         </div>
  9.         <div id="a22" class="accordion">...</div>
  10.     </div>
  11. </body>

s只包含"a21"和 "a22",其实也就是离当前容器最近的包含特征样式的子孙级元素。

如果当前元素是div.panel-body:

  1. <div class="panel-body">
  2.     <div id="a1">
  3.         <div id="a21" class="accordion">
  4.             <div id="a3">
  5.                 <div id="a4" class="accordion">...</div>
  6.             </div>
  7.         </div>
  8.         <div id="a22" class="accordion">...</div>
  9.     </div>
  10. </div>

s也只包含"a21"和 "a22",跟body情况是一样的,只是寻找方式不一样。

这个doLayout函数的目的就比较清楚了: 它负责寻找当前容器的下一级(不一定是子级别,也可能是孙子,重孙子等)需要做响应式布局的组件,然后触发绑定在这些组件上自定义的"_reszie"事件。

由此,我们也可以推断: 每一个含有响应式特性的组件,其DOM结构里面肯定存在一个绑定了自定义的"_resize"事件的事件处理程序。

到这里, “下一级需要做响应式布局的组件”完成了自适应,那么 “下下级需要做响应式布局的组件”(响应式组件多层嵌套)是怎么完成自适应的呢?。

不急,我们还是拿panel组件自定义的"_resize"来看:

  1. function _13(_14) {
  2.     $(_14).addClass("panel-body")._size("clear");
  3.     var _15 = $("<div class=\"panel\"></div>").insertBefore(_14);
  4.     _15[0].appendChild(_14);
  5.     _15.bind("_resize", function (e, _16) {
  6.         if ($(this).hasClass("easyui-fluid") || _16) {
  7.             // _3函数就是panel组件resize方法的实现
  8.             _3(_14);
  9.         }
  10.         return false;
  11.     });
  12.     return _15;
  13. };

也就是说自定义的"_resize"事件处理程序里,调用组件自身的resize方法,看来想知道“下下级”的响应式布局是如何完成的,必须还是去问resize方法。

resize方法

我们直接看代码:

  1. // panel组件resize方法的实现
  2. function _3(_4, _5) {
  3.     var _6 = $.data(_4, "panel");
  4.     var _7 = _6.options;
  5.     var _8 = _6.panel;
  6.     var _9 = _8.children("div.panel-header");
  7.     var _a = _8.children("div.panel-body");
  8.     if (_5) {
  9.         $.extend(_7, {width: _5.width, height: _5.height, minWidth: _5.minWidth, maxWidth: _5.maxWidth, minHeight: _5.minHeight, maxHeight: _5.maxHeight, left: _5.left, top: _5.top});
  10.     }
  11.     _8._size(_7);
  12.     _9.add(_a)._outerWidth(_8.width());
  13.     if (!isNaN(parseInt(_7.height))) {
  14.         _a._outerHeight(_8.height() - _9._outerHeight());
  15.     } else {
  16.         _a.css("height", "");
  17.         var _b = $.parser.parseValue("minHeight", _7.minHeight, _8.parent());
  18.         var _c = $.parser.parseValue("maxHeight", _7.maxHeight, _8.parent());
  19.         var _d = _9._outerHeight() + _8._outerHeight() - _8.height();
  20.         _a._size("minHeight", _b ? (_b - _d) : "");
  21.         _a._size("maxHeight", _c ? (_c - _d) : "");
  22.     }
  23.     _8.css({height: "", minHeight: "", maxHeight: "", left: _7.left, top: _7.top});
  24.     _7.onResize.apply(_4, [_7.width, _7.height]);
  25.     // 关键代码只有这一行,瞧,它又调用了panel组件的doLayout方法!
  26.     $(_4).panel("doLayout");
  27. };

结论

  • 步骤一:window的resize事件触发doLayout方法,当前容器(上下文)是body;
  • 步骤二:doLayout方法搜索“下一级响应式组件”,触发“下一级响应式组件”的"resize"方法;
  • 步骤三:“下一级响应式组件”的"resize"方法调用doLayout方法,也就是回到“步骤一”只不过当前容器(上下文)换成“下一级响应式组件”,它再次执行的时候,找的就是“下下级响应式组件”。

如此循环调用,一层一层顺序作响应式处理,直到找不到为止。值得注意的是,响应式处理不一定是从body开始,比如tabs组件在切换标签页的时候。

 性能问题

doLayout对“下一级响应式布局组件”的搜索代码是有很大优化空间的,当页面DOM结构很复杂的时候,特别是有大数据量表格的时候,在IE下的doLayout的搜索效率惨不忍睹,可以使用“children方法+递归调用+对普通表格不搜索”的方案优化。

android webview 使用以及一些常见的异常处理

$
0
0

android中的提供webview控件,可以方便开发人员是自己的应用嵌入网页浏览功能,但实际开发中却会遇到 一些问题,这个稍后会介绍到,

效果图:

 

先来看个实例:

 

  1. public class MainActivity extends Activity {  
  2.     final String COMPANY_WEB="http://www.csdn.net";  
  3.     private WebView mWebView;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         mWebView = (WebView) findViewById(R.id.webview);  
  10.         setWebView();  
  11.           
  12.     }  
  13.   
  14.     private void setWebView(){  
  15.         WebSettings webSettings = mWebView.getSettings();  
  16.           webSettings.setJavaScriptEnabled(true);    
  17.           webSettings.setAllowFileAccess(true);    
  18.           webSettings.setBuiltInZoomControls(true);    
  19.           webSettings.setPluginsEnabled(true);  
  20.           
  21.         mWebView.setWebViewClient(new MonitorWebClient());  
  22.           
  23.         mWebView.loadUrl(COMPANY_WEB);  
  24.     }  
  25.       
  26.     private class MonitorWebClient extends WebViewClient{  
  27.   
  28.         @Override  
  29.         public void onPageStarted(WebView view, String url, Bitmap favicon) {  
  30.               
  31.             super.onPageStarted(view, url, favicon);  
  32.         }  
  33.           
  34.         @Override  
  35.         public void onPageFinished(WebView view, String url) {  
  36.               
  37.             super.onPageFinished(view, url);  
  38.         }  
  39.   
  40.         @Override  
  41.         public boolean shouldOverrideUrlLoading(WebView view, final String url) {  
  42.             String website=Uri.parse(url).getHost();  
  43.               
  44.             if (COMPANY_WEB.equals(website)) {  
  45.                     // This is my web site, so do not override; let my WebView load the page  
  46.                     return false;  
  47.               }else{  
  48.                  view.loadUrl(url);            
  49.                  return true;   
  50.              }  
  51.         //  return super.shouldOverrideUrlLoading(view, url);  
  52.         }  
  53.     }  
  54.       
  55.     @Override  
  56.     public boolean onKeyDown(int keyCode, KeyEvent event)  
  57.     {  
  58.         if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack())  
  59.         {  
  60.             mWebView.goBack();  
  61.             return true;  
  62.         }  
  63.         return super.onKeyDown(keyCode, event);  
  64.     }  
  65.       
  66. }  

 

 

相关权限:

 

  1. <uses-permission android:name="android.permission.INTERNET" />  
  2.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
  3.     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  

 

 

ok,测试了一下相关链接也可以出现正常访问,以上是以csdn网站为例,如果将网站换成http://www.qq.com开始也没有问题,点击导航栏也可以正常访问,再点击图片连接就会出现 eventhub.removemessages(int what = 107) is not supported before the webviewcore is set up 异常信息,有人说是没有以http://开头,这个也试了一下,没有解决问题,期待有人能解决。回过头来在首页点击相关新闻链接后会发现出现空白页无法正常访问,后来研究发现这个和网站结构有关系,看来webview并不能完全实现浏览器功能。

接下来就是简单的 异常处理了,主要就是重写WebViewClient类中的onReceivedError()方法和onReceivedSslError()方法来进行 处理了。

说完 异常简单 处理后再来说说提高网站的访问速度,尤其是带有大量的flash,swf动画和各种css样式功能,这个时候我们就应该 使用缓存了,适当的设置缓存大小 以及合适的模式来进行优化了,webview有两种模式可设置如下:

1,LOAD_DEFAULT,根据cache-control决定是否从 网络上取数据。
2,LOAD_CACHE_ELSE_NETWORK,只要本地有,无论是否过期,或者no-cache,都 使用缓存中的数据。
如:m.taobao.com的cache-control为no-cache,在模式LOAD_DEFAULT下,无论如何都会从 网络上取数据,如果没有 网络,就会出现错误页面;在LOAD_CACHE_ELSE_NETWORK模式下,无论是否有 网络,只要打开过一次,都 使用缓存。
m.sina.com.cn的cache-control为max-age=60,在两种模式下都 使用本地缓存数据。

总结:根据以上两种模式,建议缓存策略为,判断是否有 网络,有的话, 使用LOAD_DEFAULT,无 网络时, 使用LOAD_CACHE_ELSE_NETWORK。


好说的也差不多了,来看一下优化后的代码:

 

  1. public class MainActivity extends Activity {  
  2.   
  3.     final String COMPANY_WEB="http://www.deczh.com/";  
  4.     private WebView mWebView;  
  5.     private Context activity;  
  6. //  private ProgressDialog progressDialog;  
  7.     //history web site  
  8. //  private Stack<String> webHistory=new Stack<String>();  
  9.       
  10.     @Override  
  11.     protected void onCreate(Bundle savedInstanceState) {  
  12.         super.onCreate(savedInstanceState);  
  13.         setContentView(R.layout.activity_main);  
  14.         mWebView = (WebView) findViewById(R.id.webview);  
  15.         setWebView();  
  16.         activity=this;  
  17.         mHandler.sendEmptyMessageDelayed(0, 1000);  
  18.     }  
  19.       
  20.     private void setWebView(){  
  21.         WebSettings webSettings = mWebView.getSettings();  
  22.          //java script  
  23.           webSettings.setJavaScriptEnabled(true);    
  24.           webSettings.setJavaScriptCanOpenWindowsAutomatically(true);  
  25.           // access Assets and resources  
  26.           webSettings.setAllowFileAccess(true);    
  27.           //zoom page  
  28.           webSettings.setBuiltInZoomControls(true);    
  29.           webSettings.setPluginsEnabled(true);  
  30.           //set xml dom cache  
  31.           webSettings.setDomStorageEnabled(true);  
  32.           //提高渲染的优先级  
  33.           webSettings.setRenderPriority(RenderPriority.HIGH);    
  34.           //set cache  
  35.           String appCachePath = getDir("netCache", Context.MODE_PRIVATE).getAbsolutePath();  
  36.           webSettings.setAppCacheEnabled(true);  
  37.           webSettings.setAppCachePath(appCachePath);  
  38.           webSettings.setAppCacheMaxSize(1024*1024*5);  
  39.           webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);  
  40.           
  41.         mWebView.setWebViewClient(new MonitorWebClient());  
  42.         mWebView.setWebChromeClient(new AppCacheWebChromeClient());  
  43.     }  
  44.       
  45.     private class MonitorWebClient extends WebViewClient{  
  46.   
  47.         @Override  
  48.         public void onReceivedError(WebView view, int errorCode,  
  49.                 String description, String failingUrl) {  
  50.             //错误提示  
  51.             Toast toast = Toast.makeText(getBaseContext(), "Oh no! " + description,  
  52.                     Toast.LENGTH_LONG);  
  53.             toast.setGravity(Gravity.TOP | Gravity.CENTER, 0, 0);  
  54.             toast.show();  
  55.             //错误<strong>处理</strong>  
  56.             try {  
  57.                 mWebView.stopLoading();  
  58.             } catch (Exception e) {  
  59.             }  
  60.             try {  
  61.                 mWebView.clearView();  
  62.             } catch (Exception e) {  
  63.             }  
  64.             if (mWebView.canGoBack()) {  
  65.                 mWebView.goBack();  
  66.             }  
  67.         //  super.onReceivedError(view, errorCode, description, failingUrl);  
  68.         }  
  69.         //当load有ssl层的https页面时,如果这个网站的安全证书在Android无法得到认证,WebView就会变成一个空白页,而并不会像PC浏览器中那样跳出一个风险提示框  
  70.         @Override  
  71.         public void onReceivedSslError(WebView view, SslErrorHandler handler,  
  72.                 SslError error) {  
  73.             //忽略证书的错误继续Load页面内容  
  74.              handler.proceed();  
  75.             //handler.cancel(); // Android默认的<strong>处理</strong>方式  
  76.              //handleMessage(Message msg); // 进行其他<strong>处理</strong>  
  77.         //  super.onReceivedSslError(view, handler, error);  
  78.         }  
  79.   
  80.         @Override  
  81.         public void onPageStarted(WebView view, String url, Bitmap favicon) {  
  82.              /*if (progressDialog == null) { 
  83.                  // If no progress dialog, make one and set message 
  84.                  progressDialog = new ProgressDialog(activity); 
  85.                  progressDialog.setMessage("Loading please wait..."); 
  86.                  progressDialog.show(); 
  87.                  // Hide the webview while loading 
  88.                  mWebView.setEnabled(false); 
  89.              }*/  
  90.               
  91.         //  super.onPageStarted(view, url, favicon);  
  92.         }  
  93.           
  94.         @Override  
  95.         public void onPageFinished(WebView view, String url) {  
  96.            /* if (progressDialog != null&&progressDialog.isShowing()) { 
  97.                 progressDialog.dismiss(); 
  98.                 progressDialog = null; 
  99.                 mWebView.setEnabled(true); 
  100.             }*/  
  101.               
  102.             /*if(!webHistory.contains(url)) 
  103.                 webHistory.push(url);*/  
  104.               
  105.         //  super.onPageFinished(view, url);  
  106.         }  
  107.   
  108.         @Override  
  109.         public boolean shouldOverrideUrlLoading(WebView view, final String url) {   Log.e(getClass().getSimpleName(), "website= "+url);  
  110.         //  String website=Uri.parse(url).getHost();  
  111.             String processUrl=url;  
  112.             if(!processUrl.startsWith("http://"))  
  113.                 processUrl="http://"+processUrl;  
  114.               
  115.             if (COMPANY_WEB.equals(url)) {  
  116.                // This is my web site, so do not override; let my WebView load the page  
  117.                  return false;  
  118.               }  
  119.             else{  
  120.                  view.loadUrl(processUrl);            
  121.                  return true;   
  122.              }  
  123.         //   return super.shouldOverrideUrlLoading(view, url);  
  124.         }  
  125.     }  
  126.       
  127.      private class AppCacheWebChromeClient extends WebChromeClient {  
  128.             @Override  
  129.             public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {  
  130.             //    Log.e(APP_CACHE, "onReachedMaxAppCacheSize reached, increasing space: " + spaceNeeded);  
  131.                 quotaUpdater.updateQuota(spaceNeeded * 2);  
  132.             }  
  133.         }  
  134.        
  135.     private boolean pause=false;  
  136.     @Override  
  137.     public void onPause() {  
  138.         super.onPause();  
  139.         if (mWebView != null) {  
  140.             mWebView.pauseTimers();  
  141.             mWebView.onPause();  
  142.             this.pause=true;  
  143.         }  
  144.     }  
  145.   
  146.     @Override  
  147.     public void onResume() {  
  148.         super.onResume();  
  149.         if (mWebView != null&&pause) {  
  150.             mWebView.resumeTimers();  
  151.             mWebView.onResume();  
  152.             this.pause=false;  
  153.         }  
  154.     }  
  155.       
  156.     @Override  
  157.     public boolean onKeyDown(int keyCode, KeyEvent event)  
  158.     {  
  159.         if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()){  
  160.             mWebView.goBack();  
  161.             return true;  
  162.         }  
  163.          
  164.         return super.onKeyDown(keyCode, event);  
  165.     }  
  166.       
  167.       
  168. }  


相关权限:

 

 

  1. <uses-permission android:name="android.permission.INTERNET" />  
  2.     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
  3.     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />  
  4.   
  5.     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
  6.     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  


经过代码优化后,访问速度明显提升,部分错误得到 处理,但是还是会有 异常处理不了,如果谁有好的错误 处理方法,希望留言讨论,大家一起进步。

 

好了就先说到这里吧!



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


ITeye推荐



Android WebView常见问题及解决方案汇总

$
0
0

如有转载,请声明出处: 时之沙:  http://blog.csdn.net/t12x3456

 

Android WebView常见问题解决方案汇总:

就目前而言,如何应对版本的频繁更新呢,又如何灵活多变地展示我们的界面呢,这又涉及到了web app与native app之间孰优孰劣的争论. 于是乎,一种混合型的app诞生了,灵活多变的部分,如淘宝商城首页的活动页面,一集凡客诚品中我们都可以见到web 页面与native页面的混合,既利用了web app的灵活易更新,也借助了native app本身的效率.
当然,就会用到webview这样的一个控件,这里,我把自己使用过程中遇到的一些问题整理下来.

首先上张图对WebView进行一个基本的回顾:

 

20131106091520406.jpg

以上思维导图原文件下载地址:

http://download.csdn.net/detail/t12x3456/6509195

 

然后看一下具体的问题及解决方案:

1.为WebView自定义错误显示界面:

01/**
02    * 显示自定义错误提示页面,用一个View覆盖在WebView
03    */
04   protected  void  showErrorPage() {
05       LinearLayout webParentView = (LinearLayout)mWebView.getParent();
06        
07       initErrorPage();
08       while  (webParentView.getChildCount() > 1) {
09           webParentView.removeViewAt(0);
10       }
11       LinearLayout.LayoutParams lp = newLinearLayout.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT);
12       webParentView.addView(mErrorView, 0, lp);
13       mIsErrorPage = true;
14   }
15   protected  void  hideErrorPage() {
16       LinearLayout webParentView = (LinearLayout)mWebView.getParent();
17        
18       mIsErrorPage = false;
19       while  (webParentView.getChildCount() > 1) {
20           webParentView.removeViewAt(0);
21       }
22   }
23 
24 
25   protected  void  initErrorPage() {
26       if  (mErrorView == null) {
27           mErrorView = View.inflate(this, R.layout.online_error, null);
28           Button button = (Button)mErrorView.findViewById(R.id.online_error_btn_retry);
29           button.setOnClickListener(new  OnClickListener() {
30               public  void  onClick(View v) {
31                   mWebView.reload();
32               }
33           });
34           mErrorView.setOnClickListener(null);
35       }
36   }

 

 

2.WebView cookies清理:

1CookieSyncManager.createInstance(this);
2CookieSyncManager.getInstance().startSync();
3CookieManager.getInstance().removeSessionCookie();


3.清理cache 和历史记录:

1webView.clearCache(true);
2webView.clearHistory();

 

4.判断WebView是否已经滚动到页面底端:

1getScrollY()方法返回的是当前可见区域的顶端距整个页面顶端的距离,也就是当前内容滚动的距离.
2getHeight()或者getBottom()方法都返回当前WebView 这个容器的高度
3getContentHeight 返回的是整个html 的高度,但并不等同于当前整个页面的高度,因为WebView 有缩放功能, 所以当前整个页面的高度实际上应该是原始html 的高度再乘上缩放比例. 因此,更正后的结果,准确的判断方法应该是:
4if(WebView.getContentHeight*WebView.getScale() == (webview.getHeight()+WebView.getScrollY())){ //已经处于底端 }

 

5.URL拦截:

Android WebView是拦截不到页面内的fragment跳转的。但是url跳转的话,又会引起页面刷新,H5页面的体验又下降了。只能给WebView注入JS方法了。

 

 

 6.处理WebView中的非超链接请求(如Ajax请求): 

 有时候需要加上请求头,但是非超链接的请求,没有办法再shouldOverrinding中拦截并用webView.loadUrl(String url,HashMap headers)方法添加请求头

  目前用了一个临时的办法解决:

首先需要在url中加特殊标记/协议, 如在onWebViewResource方法中拦截对应的请求,然后将要添加的请求头,以get形式拼接到url末尾

在shouldInterceptRequest()方法中,可以拦截到所有的网页中资源请求,比如加载JS,图片以及Ajax请求等等

Ex:

01@SuppressLint("NewApi")
02@Override
03public  WebResourceResponse shouldInterceptRequest(WebView view,String url) {
04    // 非超链接(如Ajax)请求无法直接添加请求头,现拼接到url末尾,这里拼接一个imei作为示例
05 
06    String ajaxUrl = url;
07    // 如标识:req=ajax
08    if  (url.contains("req=ajax")) {
09       ajaxUrl += "&imei="  + imei;
10    }
11 
12    return  super.shouldInterceptRequest(view, ajaxUrl);
13 
14}



 

7.在页面中先显示图片:

1@Override
2public  void  onLoadResource(WebView view, String url) {
3  mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_LOAD_RESOURCE, url);
4    if  (url.indexOf(".jpg") > 0) {
5     hideProgress(); //请求图片时即显示页面
6     mEventListener.onWebViewEvent(CustomWebView.this, OnWebViewEventListener.EVENT_ON_HIDE_PROGRESS, view.getUrl());
7     }
8    super.onLoadResource(view, url);
9}



 8.屏蔽掉长按事件 因为webview长按时将会调用系统的复制控件:
     

 

1mWebView.setOnLongClickListener(new  OnLongClickListener() {
2           
3          @Override
4          public  boolean  onLongClick(View v) {
5              return  true;
6          }
7      });


9.在WebView加入 flash支持:

1String temp = "<html><body bgcolor=\""  "black"
2                "\"> <br/><embed src=\""  + url + "\" width=\""  "100%"
3                "\" height=\""  "90%"  "\" scale=\""  "noscale"
4                "\" type=\""  "application/x-shockwave-flash"
5                "\"> </embed></body></html>";
6String mimeType = "text/html";
7String encoding = "utf-8";
8web.loadDataWithBaseURL("null", temp, mimeType, encoding, "");


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


ITeye推荐



Viewing all 15843 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>