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

常见邮件发送失败原因分析以及解决方法

$
0
0
本文来自:http://www.maikongjian.com/style/info/shownews2.asp?id=439


一.发到 sina.com 的邮件会被退回

我发到 sina 的邮件会被退回,并提示"remote server said: 553 Spam Mail http://mail.sina.com.cn/FAQ.html";错误,怎么办?

访问http://mail.sina.com.cn/FAQ.html我们可以得知,sina进行了helo域验证。请注意设置邮件系统中的HELO域,如果按照上述操作后,还是无法发往新浪的邮箱,则有以下几种可能:
1) MX指向可能有误,请仔细检查MX指向是否正确
2) 您的服务器IP地址可能被新浪屏蔽了,您需要与新浪客服联系,新浪客服联系邮件:antispam@staff.sina.com.cn 新浪客服联系电话:010-82624488-5602,与对方联系时,请提供您的服务器信息:主机IP/域名/退信内容/发信地址/收信地址/发送时间,以便及时解决您的问题。同时务必留下对方的工号,这样方便督促解决问题。

http://mail.sina.com.cn/FAQ.html#q3
1.为什么新浪服务器会产生553的退信信息?
这是由于邮件发送方服务器的HELO域设置不规范造成的。如果新浪服务器无法对该域进行正确的DNS解析, 将会导致新浪反垃圾系统认为该服务器是垃圾邮件发送者从而拒收. 不规范的HELO域的示例如下:

HELO office
HELO mailserver
HELO localhost.localdomain

解决方法: 参考本文档后半部分提供的设置方法,正确设置贵方SMTP服务器的HELO域, 使新浪能过对该域名进行正确的DNS解析

2.SMTP服务器能否设置为公司内网的域名?
请设置贵公司的服务器对外发送的HELO域为正确的贵公司域名,否则将因为和第一条同样的原因造成新浪拒收。


3.SMTP服务器的HELO域已设置正确,怎么还产生553的退信信息?
这是由于发邮件服务器在内网的某个网段中。如果邮件经防火墙发送, 而防火墙没有对邮件的源IP地址进行映射, 新浪的服务器就会认为防火墙的IP是发件方SMTP服务器的IP地址,而邮件源IP地址与HELO域名MX解析不一致. 这种情况也会使新浪拒收邮件。解决方法: 使HELO后面的域名MX解析为防火墙的IP地址, 或者使防火墙把经过它的邮件的IP映射为其真正的IP地址, 即不要改变邮件的源IP地址.


4.SMTP服务器的HELO域能否包含特殊字符?
不可以。如果发邮件服务器的HELO域中包含有无法识别的字符, 就会导致新浪对其DNS解析失败, 从而拒绝接收其邮件.
包含有无法识别字符的HELO域示例: eev?bj?md02.vatech


5.国外网站应如何正确设置SMTP服务器?
新浪要求所有中国大陆境外的CMTP服务器必须提供反向DNS解析。对于中国境外的CMTP服务器,如果不提供反向DNS解析,新浪将认为该服务器为垃圾邮件发送者的服务器并拒收该地址发来的邮件。
解决方法: 注册SMTP服务器公网IP地址的RDNS服务, 并把它对应的域名写到服务器的HELO域中.


6.MS Exchange Server 服务器HELO域的设置方法
具体位置在:
A.Exchange 2000:SERVER---主机名---SMTP---DEFAULT SMTP VIRTUAL---属性---DELIVERY---ADVANCED---MASQUERADE NAME
B.Exchange 2003:服务器-主机名-协议-SMTP---默认虚拟SMTP服务器---属性--传递-高级-完全限制的域名,如下图:




7.Notes服务器HELO域的设置方法
具体位置在:服务器---基本---全限定的Internet主机名

8.imail的HELO应该是 localhost 的general面板上的 Host Name
如Host Name这里是HI.com 那么imail 所用的就是helo HI.com
另外 Imail管理面板-》服务-》SMTP-》advance->Hello Message->HIi.com 只不过修改是smtp的banner信息

9.MDaemon Server的HELO在Setup-》Primary Domain-》HELO Domain Name中设定。

10Winmail Server的设定:管理工具->系统设置->SMTP设置->基本参数->HELO/EHLO 主机名。可用nslookup命令查找,YourDomain MX preference = 10, mail exchanger =后面的内容即为正确的HELO/EHLO 主机名。



如满足以上要求,发到 sina 还是被退回相同的信件,请和 sina 联系。联系方式如下:
邮箱地址:antispam@staff.sina.com.cn 或 lanjiang@staff.sina.com.cn
联系电话:010-82624488 转 5602
也可以申请个 sina 的邮箱,邮件系统中递送失败后启用中继递送,中继服务器: smtp.sina.com.cn, 再输入您在 sina 注册的用户名和密码。

------------------------------------------------------------------------------------------------
日前,新浪邮件根据国际通行标准,对新浪邮件的反垃圾策略进行调整。
在此期间,如出现发信到新浪情况异常或收到来自新浪的退信提示,请参考日志或错误代码,按照RFC标准调整您的发信机制,谢谢您的合作!

首先,请检查发信服务器的HELO信息是否为完整的域名,如不是将会被系统默认为垃圾邮件,相关设置问题可参考系统返回信息和服务器基本

设置的帮助文档:http://antispam.sina.com.cn/

其次,在发信服务器上做telnet测试,将telnet的结果保存并发送到antispam@staff.sina.com.cn邮箱,同时请附上发送方的服务器IP地址、

域名、单位名称、联系电话、联络人等信息,以便进行具体的技术咨询。

telnet测试格式如下:

telnet sinamx.sina.com.cn 25
220 sinamail.sina.com,cn ESMTP Postfix <Postfix Rulse!>
helo xx.com (您的正确HELO信息)
250 sinamail.sina.com.cn
mail from:<xxx@xxx.com>(您的企业邮箱名)
250 ok
rcpt to:<xxx@sina.com>(您的测试邮箱,必须为新浪邮箱)
250 ok
rcpt to:<emai_test@sina.com>(我们的测试邮箱)
250 ok
data
354 请继续 - go ahead
From: xxx@xxx.com
To: xxx@sina.com
Subject: abc
Date: Thu, 18 Mar 2004 14:50:55 +0800
MIME-Version: 1.0
Content-Type: multipart/alternative
这是一封telnet的测试信件
.(“.”不可忽略)
quit


如果能够出现220 sina.com ESMTP这条语句,则可判断您并不是因为被视为垃圾邮件而被拒收,且查询时不在黑名单中。
请注意:如果您使用的是WINDOWS服务器,注意在以上操作的时候不要使用DELETE、后退等键修改。如输入错误,请退出并按步骤重新进行telnet测试。

附件一:错误代码的含义及解决办法

首先,发送方邮件服务器必须严格按照RFC821(Simple Mail Transfer Protocol)国际标准,同时要符合RFC1035(Domain names -implementation and specification)国际标准所指定的域名命名法。
其次,因不符合目前新浪的反垃圾策略被拒,得到的错误代码为以下几种,请投诉方根据日志或错误代码进行修改。
如果投诉方是普通用户,则请投诉方向他的邮件服务提供商提供相关资料,由其管理员进行修改操作。

3.1 错误代码:450 4.7.1 Client host rejected: cannot find your hostname
错误原因:对方服务器未设置反向解析
处理方式:请参考http://www.5dmail.net/html/2006-1-20/2006120225656.htm

3.2 错误代码:450 4.7.1 Helo command rejected: Host not found
错误原因:对方服务器在HELO命令中提供的域名不能被解析
处理方式:请检查其HELO命令提供的域名信息,且保证其符合RFC1035国际标准的规定。

3.3 错误代码:504 5.5.2 Helo command rejected: need fully-qualified hostname
错误原因:对方服务器的HELO信息不是完整的域名
处理方式:请对方检查其HELO命令提供的域名信息,且保证其符合RFC1035国际标准的规定。

3.4 错误代码:501 5.5.2 Helo command rejected: Invalid name
错误原因:对方服务器的HELO信息包含非法的字符
处理方式:RFC规定,HELO命令提供的信息不得包含中文字符。
请对方按照RFC1035国际标准规定,对其HELO信息设置进行修改。

3.5 错误代码:554 5.7.1 Recipient address rejected: Dynamic IP
错误原因:对方服务器使用动态IP处理方式:请对方查看中华人民共和国信息产业部令第38号《互联网电子邮件服务管理办法》中的第四条:第四条 提供互联网电子邮件服务,应当事先取得增值电信业务经营许可或者依法履行非经营性互联网信息服务备案手续。未取得增值电信业务经营许可或者未履行非经营性互联网信息服务备案手续,任何组织或者个人不得在中华人民共和国境内开展互联网电子邮件服务。

3.6 错误代码:554 5.7.1 Service unavailable; See http://antispam.sina.com.cn/denyip.php?IP=*.*.*.*
错误原因:对方服务器IP被国际权威的反垃圾组织列入黑名单处理方式:请对方访问http://www.dnsstuff.com/tools/ip4r.ch?ip=查询并申请解封。解封后即恢复正常。


二.发到 tom.com 的邮件会被退回

1.确认您邮件系统使用的域名是合法的,而不是随意填写的。
2.确认您的邮件服务器使用的IP地址是固定的,而不是动态的IP地址。
3.正确设置HELO/EHLO 主机名。(方式见Sina部份)(可用nslookup命令查找,YourDomain MX preference = 10, mail exchanger =后面的内容即为正确的HELO/EHLO 主机名。 )
4.如满足以上三点,发送到 TOM.COM 还是被退回相同的信件:
A>请到http://whitelist.mail.tom.com/tom/whitelist/register.html申请加入到TOM.COM的白名单。
B>也可向whitelist@tom.com咨询。

三.发到 aol.com 的邮件会被退回


我发到AOL.COM的邮件会被退回,并提示"554- (RTR:DU) http://postmaster.info.aol.com/errors/554rtrdu.html";错误,怎么办?
AOL.COM除了要求您的邮件服务器使用合法的域名,固定公网IP地址,还需要该IP地址能够作反向解析。但国内要申请作反向解析比较困难,所以我们可以使用中继服务器转发,例如:
可以申请个免费邮箱(比如Sina),邮件系统外发递送 中设置递送失败后启用中继递送,中继服务器: smtp.sina.com.cn, 再输入您在 sina 注册的用户名和密码

四.发到 163 的邮件会被退回


http://mail.163.com/news/helo.htm

  从2004年3月30日起,@163.com电子邮箱增加HELO检查。

  向163.com信箱投递信件的发送方服务器必须设置规范的HELO域,即:
与网易服务器 连接的 IP 必须在 HELO 后面跟的域名的 A纪录和MX纪录里面。否则认为该服务器是垃圾发送者而拒收。


  如果您有任何疑问,请联系24小时**电话:020-83568090-4


 若你的邮件仍无法到达网易邮箱,并收到退信,请根据退信的返回字段,在下面的表单查询具体的退信原因。



退信代码


说 明

554 HL:IHU
该IP的发送行为触犯了网易的服务条款,被临时挂起。请检查是否有用户不正当的发送行为。

554 HL:IPB
该IP不在网易允许的发送地址列表里。

450 HL:MEP
该IP发送行为异常,被临时禁止连接。

450 HL:REP
该IP发送行为异常,被临时禁止连接。

554 HL:ICC
该IP短期内发送了大量信件,超过了网易的限制,被临时禁止连接。请检查是否有用户发送病毒或者垃圾邮件。

554 HL:IFQ
该IP短期内发送了大量信件,超过了网易的限制,被临时禁止连接。请检查是否有用户发送病毒或者垃圾邮件。

554 HL:ITC
该IP短期内发送了大量信件,超过了网易的限制,被临时禁止连接。请检查是否有用户发送病毒或者垃圾邮件。

554 MI:SPB
此用户不在网易允许的发信用户列表里。

550 MI:NHD
HELO命令不允许为空。

550 MI:IMF
发信人电子邮件地址不合规范。请参考http://www.rfc-editor.org/关于电子邮件规范的定义。

550 MI:SPF
发信IP未被发送域的SPF许可。请参考http://www.openspf.org/关于SPF规范的定义。

450 MI:CEL
发送行为异常,该发件人被临时禁止发信。

450 MI:DMC
发送行为异常,该发件人被临时禁止发信。

450 MI:CCL
发送行为异常,该发件人被临时禁止发信。

554 MI:SFQ
短期内发送了大量信件,超过了网易的限制,该发件人被临时禁止发信。

550 MI:STC
短期内发送了大量信件,超过了网易的限制,该发件人被临时禁止发信。

550 RP:FRL
禁止发信到非网易用户。

550 RP:RCL
群发收件人数量超过了限额。

550 RP:CEL
发件人发送行为异常。

450 RP:DRC
群发收件人数量超过了限额。

450 RP:CCL
发件人发送行为异常。

550 RP:QRC
该用户短期内发送了大量信件,超过了网易的限制,被临时禁止发信。

550 RP:TRC
该用户短期内发送了大量信件,超过了网易的限制,被临时禁止发信。

450 DT:SPM
发送的邮件内容包含了未被网易许可的信息,或违背了网易的反垃圾服务条款。

550 DT:SPM
发送的邮件内容包含了未被网易许可的信息,或违背了网易的反垃圾服务条款。

450 DT:RBL
发信IP位于一个或多个RBL里。请参考http://www.rbls.org/关于RBL的相关信息。

554 IP in blacklist
该IP不在网易允许的发送地址列表里。

552 Requested mail action aborted: exceeded mailsize limit
发送的信件大小超过了网易邮箱允许接收的最大限制。

500 Error: bad syntaxU
发送的smtp命令语法有误。

550 Invalid User
请求的用户不存在。

550 User in blacklist
该用户不被允许给网易用户发信。

550 User suspended
请求的用户处于禁用或者冻结状态。

451 Requested action aborted: local error in processing
系统暂时出现故障,请稍后再次尝试发送。
  如果帮助中心仍不能解答你的问题,请仔细填写以下表单。我们的工作人员接到请求后,会在三个工作日内给您答复。http://feedback.mail.126.com/antispam/feedback.htm

五. 收到退信错误提示为"554 5.7.1 Rejected xxx.xxx.xxx.xxx found in dnsbl.sorbs.net",怎么办?
这是因为收件人所在的邮件服务器使用RBL过滤垃圾邮件,而您的邮件服务器IP地址在RBL列表中,因此被拒绝了。一般此类的退信会包含一个连接,可以按网站上面的要求来申请把您的邮件服务器IP地址从列表中移除。


六.为什么直接发新邮件到 hotmail.com 不成功,而回复 hotmail.com 的邮件可以成功?


近期不能向 hotmail.com 发信是因为 hotmail.com 仅接收可信任域名发来的邮件,比如象 163.com, yahoo.com, 21cn.com 等大的邮局系统。您可能需要和 hotmail.com 联系以确保您的域名被加入到其信任列表中。

解决办法1:在每一封发往 hotmail.com 邮件的正文中包含:
http://www.hotmail.com
http://g.msn.com
http://explorer.msn.com
以上任一内容时,您发送的邮件才可以通过 hotmail.com 的过滤。

解决办法2:只要附带2k以上的附件,邮件就可以通过 hotmail.com 的过滤。

其它需要检查的:

1、检查您的hotmail邮箱选项->防止垃圾邮件->垃圾邮件过滤器:没有选择“专用”。
2、检查您的hotmail邮箱选项->防止垃圾邮件->删除垃圾邮件:没有选择“立即删除垃圾邮件”。
3、如果您的邮件里包含了可执行文件,hotmail有可能会限制打开。Hotmail 已经永久地阻止具有潜在不安全因素的附件。

下列扩展名的文件不能附加到邮件中:
.ade
.adp
.asp
.bas
.bat
.chm
.cmd
.com
.cpl
.crt
.exe
.hlp
.hta
.inf
.ins
.isp
.its
.js
.jse
.lnk
.mdb
.mde
.mdt
.mdw
.msc
.msi
.msp
.mst
.pcd
.pif
.reg
.scr
.sct
.shb
.shs
.tmp
.url
.vb
.vbe
.vbs
.vsd
.vsmacros
.vss
.vst
.vsw
.ws
.wsc
.wsf
.wsh

详细信息可以参考 hotmail受限文件(http://help.msn.com/!data/zh_cn/data/HotmailPIMv10.its51/$content$/PIM_REF_RESTRICTEDATTACHMTS.HTM?H_APP=MSN+Hotmail)。如果要发送这些扩展名附件,建议先压缩成zip文件。

5、确认邮件大小没有超过hotmail邮箱大小限制。

6.如果以上方法不可用,请直接使用以下连接填写反馈,就跟美国方面联系了 让他把你的邮箱域名加入他们的白名单。这个地址是英文的。因为现在他们仅接受英文提交。你进入这个页面把该填的都填下,发过去就可以了 。他们处理了以后会给你回信的。
http://support.msn.com/default.aspx?productkey=edfsmsbl&mkt=en-us

如果你读不懂,不要担心,下面给出它中文的页面。但是你不能用来提交问题哦!
http://support.msn.com/eform.aspx?productKey=edfsmsbl&page=support_home_options_form_byemail&ct=eformts

填表时,请注意,你最好使用hotmail的邮箱,作为微软给你回复信件的地址。否则,其他邮箱非常有可能收不到!
在小于5个小时内,HOTMAIL会给你回信的。然后,你再等个2、3小时,你就能发现,你可以给HOTMAIL发邮件了!



七.发给Yahoo的邮件出现"553 Mail from 61.185.81.172 not allowed - VS99-IP1 deferred - see help.yahoo.com/help/us/mail/defer/defer-02.html "的退信及处理办法

*关于发往yahoo中文的邮件问题。请到以下连接提交,yahoo中国将有专人联系处理。

http://surveylink.yahoo.com/wix/p3258028.aspx



无法将您的邮件投递至以下指定地址:
xxb1977@yahoo.com.cn :

553 Mail from 61.185.81.172 not allowed - VS99-IP1 deferred - see help.yahoo.com/help/us/mail/defer/defer-02.html


see help.yahoo.com/help/us/mail/defer/defer-02.html (针对退信为 553 not allowed - VS99-IP1 deferred的错误)

网页概要:
1、Open proxies and open relays
开放代理以及启用匿名转发的邮件可能会被YAHOO邮件所拒绝接收

If an IP address is determined to be an open relay or open proxy, Yahoo! reserves the right to reject all SMTP traffic from that IP address for a minimum period of 60 days.

YAHOO方面一般认为邮件来源IP启用了open relay or open proxy,将会禁止所有来自于此IP的 SMTP数据流,最少60天

2、open relay(连接地址http://www.abuse.net/relay.html)
检测邮件系统是否可以使用匿名转发的功能
(WINWEBMAIL版本里面,建议使用SMTP身份验证功能,开启启用匿名转发的功能,我测试过,在3.6.3.1版本,以上两个功能开通的情况下,是可以通过OPEN RELAY的测试的,邮件服务器一般情况不会轻易给人利用成为垃圾邮件转发中转点)

如果得到的测试结果是:
Relay test result
All tests performed, no relays accepted.(证明邮件系统是不存在OPEN RELAY的情况的)


3、Open proxies(http://www.unicom.com/sw/pxytest/ )
我看了半天还是没有明白里面的含义(英文水平有限)
网页里面的内容是在LINUX或者UNIX下面进行有关的操作,检测服务器是否存在成为代理跳板或作为代理服务器的问题
估计在WINWEBMAIL的邮件系统下,作为服务器,大家应该不会使用邮件系统的同时也开放PROXY的服务吧

4、How to remove your server

You should check (and secure) your server for open relays (such as http://www.abuse.net/relay.html) and open proxies (such as http://www.unicom.com/sw/pxytest/ -- these may require installing software on servers outside your network for proper testing). After you are reasonably sure your network is secure, please submit your IP addresses to Yahoo! for retesting

关键的一步了:如果是你已经比较确认你的邮件系统没有开放REPLY以及PROXY的话(当然你的域名设置,邮件MX设置,HELO方面设置需要正确,并没有进入有关的黑名单的情况下)please submit your IP addresses to Yahoo! for retesting

http://add.yahoo.com/fast/help/us/mail/cgi_retest(这个网页就是向YAHOO方面申述自己的IP)

1. What is your name? (自己填吧)

Name:

2. What is your email address? (自己

Email address:
For example: example_email@yahoo.com

3. IPs in the form 255.255.255.255 (separate multiple IP submissions by new lines):

Please check these for open proxies and open relays and ensure they have been removed from all open proxy and open relay DNSbls before resubmitting.

(for example: 255.255.255.255)

输入需要RETEST的IP地址,例如是61.144.56.101(多个IP的话,需要一个IP地址占用一行)

4. Indicate the error message(s) you have received. (提交你所收到的错误信息)

553 Mail from 61.185.81.172 not allowed - VS99-IP1 deferred - see help.yahoo.com/help/us/mail/defer/defer-02.html



5. Optionally, add a comment to your submission.

你需要补充的说明

6. Send the form to us:

点击“SEND”

Yahoo!, and survey partners working on behalf of Yahoo!, may contact me to participate in customer satisfaction surveys.
(这句弄不懂是什么意思,接下来估计一段时间以后,对方会发送邮件到你上面填写的邮件地址,说明有关的情况,或者24小时以后自己做有关的测试,看能否发送邮件到YAHOO.COM)

隔段时间yahoo会有回信(回信内容如下):
Hello,

Thank you for contacting Yahoo! Customer Care.

We would appreciate it if you could provide more information to help us
troubleshoot the issue. Please note that if you are not the system
administrator for the mail server(s) affected, we encourage you to
contact the administrator so they can address the possible issues
regarding mailings from the mail server. Please reply to this email
with the following information:

* Explain the details of one event with the delivery problems to Yahoo!
Mail. Please provide the entire message, including the full text of the
error message returned, and full header information.

Within a Yahoo! Mail account, you can display this information by
clicking the "Full Headers" link located within the message in the
bottom right-hand corner.

* The specific server_domain name and IP address of the email server(s)
that have the delivery issue to Yahoo! Mail.

IP Address: xxx.xxx.xxx.xxx
Mail Server Domain Name: server_name.domain.com

* Log messages from your mail server showing which IP you connected to
and what responses you got from the remote server at the time you
received the corresponding failures/timeouts for the event you are
reporting.

* The results from a plain text message, sent to Yahoo! Mail, without
HTML, links, graphics or attachments, from the server having delivery
issues. Is the email delivered to Yahoo! Inbox?

We appreciate your assistance. This information will be helpful in
determining the reason why you are having issues when delivering mail
to
Yahoo! Mail accounts.

Thank you again for contacting Yahoo! Customer Care.

Regards,

Thomas Immer

Yahoo! Customer Care
27861806


然后你按他们的要求回复一下就可以了:

内容如下:

IP Address: 服务器的IP
Mail Server Domain Name: 邮箱的域名,我的是mail.xxxx.com

我上次问了一下yahoo中国的朋友,他告诉我,将以上的内容填写在下面的页面“问题描述”处:
http://help.cn.yahoo.com/feedback.html?product=mail
Yahoo中国邮箱客户服务部电话:010-65833721

八.为什么发附件到gmail返回552 5.7.0 Illegal Attachment错误信息?

作为预防可能存在的病毒的一种安全手段,Gmail 不允许用户接收可能包含破坏性可执行代码的可执行文件(例如文件名以 .exe 结束的文件)。即使这些类型的文件以压缩格式(.zip、.tar、.tgz、.taz、.z、.gz、.rar)发送,Gmail 也不会接收。如果有任何这种类型的邮件发送到了您的 Gmail 帐户,该邮件会退回发送方。

解决办法:将exe文件改名,或者将压缩文件改名。
比如test.ex_,或者test.zip_

九.为什么发往21cn的邮件会被退回?并且包含这样的类似退信信息“553 Mail data refused by AISP, rule [2640449].”

1.如果您的邮件里包含了可执行文件,21cn会拒收。如果要发送可执行文件附件,建议先压缩成zip文件。详细信息请咨询21cn(http://free.21cn.com/huodong/email.html)

2.21CN的反垃圾邮件措施是什么?
  a) 系统要求smtp身份认证,以确认发信人身份
  b) 接收外域邮件时,当每封收件人超过20个,系统将拒收该邮件
  c) 邮件软件中加入反垃圾邮件模块
  d) 针对用户投诉和管理员搜集的与垃圾邮件有关的IP和关键字,经技术分析后加以拒收

3.21cn已对动态域名如(3322.org)建立的邮件服务器进行了拒收。请注意。

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


ITeye推荐




不装逼地说,在 Google 到底能学到啥?

$
0
0

前言

这个问题是提给自己的,算是对我 Google 十年的一个小结。

强调“不装逼”,主要是提醒自己不要陷入下列俗套:

  • 离职后靠黑前雇主、八卦前雇主、揭秘前雇主搏出位(这条是恶行)
  • 满足大众猎奇心理
  • 吹嘘自己,贬低他人

我 2006 年 3 月加入 Google,2016 年 9 月离开。离开时的头衔/职位是 Staff Software Engineer / Manager。前后 10.5 年,大致分三期:

  • 前两年关注 Linux 桌面搜索产品和 Google 的开源项目。
  • 中间三年花了许多力气在 Google 中国相关的产品上,诸如输入法、谷歌音乐之类。
  • 后面四五年大致都在 Knowledge Graph 的范畴内工作,这些工作和 Google 搜索、Google Now 最近几次大幅度的变革密不可分。

其间,代码写了不少,团队也带过好几个。除此以外,这些年还长期在 Google Doodles 团队作为 20% 的开发人员帮忙开发那些好玩的首页涂鸦——嗯,这是真的,好多好玩的 Doodle 里面,都有我的一点小贡献。

学到了啥?嗯,很多,很杂,拣最重要的三四条来说说:

1.第一次真实地生活在一个差异化的世界里

国内教育氛围向来是不喜欢差异化的,个人爱好、冒险精神、特立独行之类的语词,总会让家长、老师乃至领导、官员操碎了心。从小到大,我基本生活在一个试图将所有孩子圈养在尺子、框子、笼子里的世界;可以想象,像我这样的 70 后一脚踏入 Google 时,会有怎样的感慨。

Google、Apple、Facebook、Twitter……对雇员来说,这些公司本身就是一种鼓励差异化生存的大家庭、大社会。其中,Google 又总是扮演引领者的角色,这是在 Google 工作很值得骄傲的一件事。

差异化的最大好处,就是你有机会认识形形色色的神奇人物,然后,当你和人生观、生活方式、个人爱好乃至行为习惯差异巨大的人一起工作时,你会真真切切地感受到,自己生活的这个世界一点儿也不单调,这里有太多好玩的人、好玩的事等着你去发现。

在 Google,看见什么样的差别都不该奇怪。

往小了说,身边既有缩在角落里闷头写代码,讨厌和人交流的社交恐惧症患者,也有精神焕发的社交明星。办公室里,有带狗上班的爱心族,有重视家庭的好爸好妈,有整夜整夜奋斗的梦想家,有痴迷奇特爱好的技术极客……

就拿爱好来说,跟我一起工作过的同事里,说起来好玩的爱好就包括:天天在车库里打造奇形怪状的自行车,休一个无薪假期去帮别人竞选总统,每个周末都跑到一个从没去过的地方留下到此一游照,研究古音韵学,出远海调查海洋生物,长期倒卖二手摄影器材,写科幻小说,尝试大气层外跳伞并打破世界纪录……

当然,要尊重差异化,你就没法阻止任何人从早睡到晚然后每天只花两个小时干完别人十二个小时才能干完的活儿。

往大了说,Google 对 LGBT 群体的支持众所皆知。我当初更多地将这种支持理解为“政治正确”层面的东西。没过多少时间,我就知道我的理解有多么肤浅。有一次,Google 旧金山办公室一对男同在公司餐厅的浪漫求婚视频广为传播,感动了许多同事。事后我才想起,自己曾在硅谷一次会议上,见过那对幸福恋人中的一个。

另有一次,自己所在的整个团队收到了一位高管宣布自己性别改变的邮件,说从那天开始,大家需要称呼他为“她”。与许多人理解的相反,这些发生在身边的事情并没有时时提醒你 LGBT 群体的存在,反之,经历愈多,你会愈加淡化对他们的注意——他们或她们就是人类的普通成员,与你我并没有太大的分别。

去年在 YouTube 上追看 Google 推介的纪录片 HUMAN 时,我已经很清楚,Diversity 这个词早已是自己血液的一部分,我的大脑与纪录片制作者的大脑之间,完全没有任何隔阂。那时,我会对着屏幕上一张张陌生的面孔陷入冥想。

我觉得,这个世界最可笑的一件事就是人类进化明明得益于基因与性格的差异,许多人竟会嘲笑别人与自己的不同之处,并竭力迫使他人改变,恨不得全世界的人都如自己一般固执、愚昧。性取向如此,恋爱、婚姻、家庭、工作、事业无不如此。

Google 员工经常面临一个典型的两难困境:因为许多同事早早离开 Google 去融资、创业、上市、发财,像我这样安心在 Google 工作了十年的普通工程师就变成了另类——当面问我“为什么还没离开 Google”的人,他们眼睛里鄙夷的目光藏都藏不住;反过来,在家人眼中,我打算离开 Google 的举动无异于自己砸碎金饭碗,放着稳定的收入和丰厚的福利不要,非要和不确定性为伍。

其实,如果懂得“多样性”的重要,这种左右两难的困局就不再成立。既然有人选择快节奏和世俗生活,为什么我就不能辞了职,慢悠悠地随着自己的兴趣,由着性子,从不同的角度理解这个世界与自我的关系?为什么“慢生活”就必须是一种逃避?

宇宙很大,人很渺小。安全感是扯淡,特立独行地活着才最重要。

2. 看技术的心态完全不同了

这个主要是说我自己。到 Google 以前,我在国内做银行业的业务软件研发,给工行、中行之类的大企业做软件。这和 Google 这种面向最终用户的互联网背景是有天壤之别的。

以前看待技术,觉得是身外之物,是工具,是砖木,是用来解决用户需求的必需品。这个心态其实也没什么错,但不自觉地就把自己放在技术追随者的位置上了。

那时的我,很多时间都拼命在了解、学习和追赶新技术,生怕落伍。从这个语言追到那个语言,从这个框架追到那个框架,从这个模式追到那个模式,从这个平台追到那个平台……根本停不下来。

那时的我只是个技术的“用户”,就像搬砖盖房子,如果不天天关心今年流行什么材质的砖,明年流行什么样的房子架构,后年流行什么样的房子外观,那肯定被客户和其他程序员骂“老土”。

到 Google 撸胳膊挽袖子一忙活,才发现以前的自己狭隘了,小气了,井底之蛙了。原来以前追的好多顶尖技术,根本就是 Google 工程师主导或参与鼓捣出来的。而且,Google 内部还藏着许多外界不大知道的神奇玩意儿。最最重要的不同是——自己现在是引领技术潮流的大团队中的一员了。

以前是不断学别人怎么设计房子,看别人推荐什么材料盖房子。现在,最顶尖的房屋设计专家、材料专家就在身边,自己也很快就能和他们一样指导别人盖房子了。这种感觉,就像跳进了一个大宝藏,还清楚地知道自己并非盗贼,而是宝藏的主人。盗窃宝藏 vs. 创建宝藏,这两者间的差别很微妙。

心态一下子大不一样了,从技术的“用户”变成了技术的“主人”。

比如,有段时间要解决 C++ 的 ABI 相关问题,猛想起 C++ 标准委员会的相当一部分人都是在 Google 工作的,有一年的全体大会还是在 Google 总部开的——那直接拉着既是同事也是 C++ 权威决策人的家伙一起开会讨论不就是了?

类似的,Linux 内核的维护者、Python 的发明人、UNIX 的元老、Google Brain 的创建者……跟那么多牛人在一个公司里工作,你肯定不好意思只是单方面地跟人讨教,但凡有机会,你总会希望自己也像那些牛人一样,为技术发展做点儿贡献,哪怕只是一丁点儿。

再比如,像 MapReduce、Bigtable、TensorFlow 之类由 Google 原创、对业界影响深远的技术,在 Google 内部可不仅仅是身外的工具,它们都是 Google 工程师这个大集体的作品和骄傲。因为大家都是主人,对哪些东西不爽,可以去鼓捣源代码,可以去提交自己的补丁或者新功能,甚至推翻重做。

别小瞧这推翻重做,虽然很难很难,因为你得一边说服老板和用户,一边找到足够的开发人手,但事实上, Google 内部重新发明一遍、两遍、三遍的框架、工具、库、接口、服务比比皆是。一言不合就动手做个新版本、新系统,这毛病既带来数不清的流程混乱,也带来一山又比一山高的良性竞争——表面的混乱之下,良性竞争引发的技术飞跃常常超出想象。

在 Google,工程师有好几万,不能说每个人都渴望做技术的主人,但踌躇满志的大有人在。因为 Google 走在技术最前沿,有追求的工程师确实没脸当个纯粹的技术追随者。当然,我的意思不是说 Google 里没人去做那些不那么酷的“苦力活儿”,而是说大多数人都有个争强好胜的心态,即便是做相对简单的技术工作,也时常会想想怎么能做出世界一流的效果来。

拿面试来说,有个工程师想出了一道与月球相关的面试题,把算法、编程、设计、维护问题放在太阳系的大背景下,层层追问。我在一次内部面试技术培训时拿这道题当过样例。结果,参加讨论的工程师表达了截然相反的两种意见,有人说这题设计精妙如天马行空,另一些人则批评这题目远离实际如镜花水月。

其实,Google 的技术宅们几乎每天都在深入实际与憧憬未来这两个极端的对位、矛盾、转化中工作。常说的“仰望星空、脚踏实地”远不能形容 Google 工程师的两面性。

一方面,工程师们深知自己的代码是如何参与了这个地球乃至这个星系里最前卫、最大胆的计算机系统,如何为诸如十年后的搜索引擎、拥有人工智能的手机或机器人、量子计算机、基因工程、无人驾驶汽车等贡献力量;

另一方面,工程师们“极客”和“宅”的一面常常在外人难以注意的工作细节里表露无遗——这里有十数年如一日致力于优化编译器的语言高手,有设计最好的代码审读系统的工具专家,有亲自动手实现软硬件原型的技术总监,有坚持为地球上每一种人类语言提供输入输出解决方案的国际化团队……

这是心态的差别,或者说,是技术境界的差别,烙在 Google 工程师的基因里,旁人想学也未必学得到。

3. “管理”二字的意义完全不同了

在 Google,有时候工程师很难管理,因为大多数人都想法新、主意多、眼光高、个性强。在 Google,有时候工程师也很容易管理,只要鼓励他们把一件看似普通的事儿做出世界级的水准,他们自己就有足够优秀的执行力,用不着督促。

在 Google 做技术经理带团队,和我以前在其他公司带团队,完全是两码事。这也许和技术团队的平均水平有关,但 根本还是管理境界的问题。

记得以前在别的公司,花大力气搞开发流程管理,现在想想,大多是繁文缛节,程式化,教条化,最极端的像 ISO9000 之类的流程认证,弄得所有人筋疲力尽,效果未必有多好。

到了 Google,发现一个秘诀, 再多的规章制度,再多的流程,不如一套好用的工具来得有效。比如 Code Style 和 Code Review,以前能把技术经理烦死,三令五申也执行不下去,顶多三天热度之后,大家就阳奉阴违了。在 Google,这件事不完全是个制度的问题。

刚进来的工程师没有过 Readability Review,他就没法方便地自主提交代码,这是代码管理工具设置的硬性限制。这直接把工程师们送到评审委员会那里接受“再教育”,没错,真的是“再教育”,连 Python 之父 Guido van Rossum 也花了挺大力气才通过了 Python 语言代码的 Readability Review。

接下来,提交新代码前,各种静态、动态检查工具自动运行,帮你报出一系列风格错误、编译错误、单元测试错误和简单的逻辑错误,你得先依着工具的提示,把这些低级别错误改一遍,然后才进入 Peer Review 的环节。整个 Code Review 都在非常方便的网页工具里完成,写代码的和审阅代码的人可以方便地交互、讨论,甚至在线修改代码。

工具的“强制性”保证了制度的执行,工具的“便捷性”最大程度减轻了工程师执行制度的负担,二者相辅相成。当然,Google 内部也不乏对制度敷衍了事的,但相对其他公司,Google 的确做得更好些。

说到管理,在 Google 带技术团队的其实都苦哈哈的。我就先后两次把团队交给别人带,自己乐得去做些单纯的代码工作。道理很简单,头衔是 Manager,可你没法高高在上指手画脚,Google 最好的团队带头人都是冲在第一线带着大家一起干,除了主动包揽大家不想干的脏活、累活、杂活之外,还要做管理者必须的非技术工作,比如给每个人写评语、定奖金,帮每个人申请升职,跟心理负担重的谈心……

一个人做两份工,吃力不讨好,对团队成员的晋升也没有决定权(这事儿也挺神的),这种 Manager 的活儿,谁愿意干谁干去,我是不大喜欢干的。不过,不喜欢归不喜欢,Google 这种挺不一样的管理既显着混乱无序,又运行良好,确实很神奇。

严格地说,聪明人在一起,只需要激励,不需要管理,Google 的办法主要也是强调这一点。

必须坦白,我加入 Google 时,工程师才三千人上下,无序管理、自发管理、扁平管理占主流。Google 越来越大以后,大公司病也如约而至。流程越来越复杂,层级越来越多,职权重叠和模糊越来越严重,不同团队之间管理风格的差异也越来越大。

但即便如此,Google 基因里那种蔑视陈规、抵制办公室政治、抗拒繁文缛节的管理风格还是能在许多团队带头人的身上找到——这是 Google 肌体里的健康因素,活力因子,弥足珍贵。

顺便说一句,最近中文圈子热传不写代码/不会写代码的 CTO 一事,许多 Google 工程师出身的 CEO/CTO 都在朋友圈里晒自己写的代码,以表明态度。我自己对技术管理者写不写代码没啥倾向,写不写都可以是好的管理者,但如果“不会写代码”还以此为荣,就完全没法接受,这个是底线。

我在 Google 面试了不少 Manager 和 Director 的候选人,都考过对方写代码或者至少讨论一段代码的能力——不是要他一定在管理过程中写代码,而是怕他和 Google 工程师没法交流,和 Google 技术基因没法共存。

在 Google 做技术管理,学不到啥成文的规矩,能学到的其实主要靠“悟”。

4. 看待职业生涯的心态也完全不同了

这个要因人而异,很多人来 Google 还是奔着一份优越的薪资待遇来的。所以,这里只谈我自己。

有一种感觉是我开始在 Google 工作才有的。而且,那感觉越来越清晰,越来越吸引人,以至于十年下来,我几乎把这种感觉视为我工作时的第一推动力了。怎么说呢,这种感觉可能很多人都有,描述出来大概是:

在整个职业生涯里,至少要有一部分(哪怕是一小部分)时间,可以比较纯粹地为了开心而工作。一家公司是否适合自己,主要就是看这家公司能不能,或者在多大程度上能满足这个需求。

我在 Google 做不同项目,有时很吃力,有时很痛苦,有时很紧张,当然有时也会开心。但普通项目没法让我比较纯粹地享受那种开心、愉悦的幸福感,于是乎,我在最近五六年里,把我的 20% 时间,都投入到了 Google Doodles 这个既有趣,也适合我的项目里。

Doodle,嗯,首页涂鸦,纯粹为了让用户开心而设立的项目。这项目既需要画画的艺术家,也需要写动画、音效和游戏代码的成员,不但好玩,还特别有品,特别有文化。因为参与其中,我有机会跟 Google 总部那些厉害的艺术家一起合作,真的开心。

有一次做 Google 生日的 Doodle,大家选中的是美国小孩子在生日常玩的一种叫 Piñata 的游戏。在电脑里实现这样的游戏,需要简洁的美术风格,支持 JavaScript 的物理引擎,还有平滑、高效的动画引擎,这些是技术细节,不展开谈。

可在技术之外,我们这群追求开心的人硬是嚷着要亲自玩一次现实里的 Piñata。那次在 Mountain View,就在 Google 总部有一只霸王龙骨架的那片空地上,我们把一个真的 Piñata 挂在树上,轮流用竹竿去打,直到打出一地的糖果来。十几个艺术家和工程师开心得像小孩子。

就是很纯粹的,很简单的,很开心的那种体验。无论工作里有多少烦恼,至少要给自己保留这样一块空间,叫心情家园也好,叫随便什么名字也好,哪怕再小,也得有那么一个。

对我来说,在参与 Doodles 项目的过程里,可以和 Doodles 纪念的伟大科学家、艺术家们跨时空交流,可以和正在设计、制作的小动画、小游戏随时互动,可以预测到最终用户看到每个 Doodle 时的快乐,这的确是一件超赞的事。

很幸运,Google 可以为我这样追求开心的人提供合适的机会。很不幸,(我确切地知道)不少公司从不考虑员工的这种需求。这大概也是公司基因决定的事,没法强求。正因为有了在 Google 的十年工作经历,我才会毫不犹豫地将职业生涯中最重要的追求定义为“开心”。

十年职业生涯,除了快乐,还有历史的厚重感。特别是 Google 中国这十年,我有幸亲历历史,可算人生里的大风浪。不幸,许多历史是不能详尽言说或评价的,所谓春秋笔法,绝不仅仅是史官的政治妥协,更多情况下也是从更高维度审视历史的一种大智慧。

Google 与中国那些事儿,写得太感情用事了,褒贬就缺乏力量;太直白了,话语权就容易被粗暴剥夺;太详尽了,就难免掉进结果论与动机论对立的死循环。

许多年以后,当互联网这个词不再具有产业先导精神,当获取信息如呼吸空气般自由,那时的人类史家和人工智能史学机器人一定会一起编撰诸如《互联网信息审查史》、《科技产业自由竞争和地区保护史》、《网络政治生态与网民意识形态史》、《信息交流史》、《禁毁网络文章和敏感网络关键词从考》、《网络经济与政治壁垒》、《互联网背景下的政府行为》、《维基百科的诞生和消亡》、《社交网络的全球化历程》等学术著作。

只有节奏慢下来了,才有足够的清醒程度,可以从不同视角审视历史。今天哪怕是板上钉钉的史论,交给子孙后代时,许多也会有新的争议和不同角度的阐发吧?

那时,我的后代也许可以用极为八卦的口吻悠悠地对子孙辈说,在某年某月某日某公司与某国政府无法就互联网审查达成一致的那个历史性时刻,我们家族的祖上是有人在现场目击历史的。有时,真的很想穿越到未来,只为了评估一下,今天我们极为看重的事情是不是早已被子孙们淡忘。

总的说来,我喜欢一种本质上不是消极避世的“慢生活”,“快乐”和“多样化”是这种生活的力量源泉。离开 Google 后,我坚信这种生活就是自己的未来。

想法渐多,身心渐老。下一个十年,请慢一些到来吧。

原文出处: 王咏刚

本文转载自公众号 「半轻人」(ID:ban-qing-ren)

为什么必须在航班起飞前 45 分钟办理登机?

$
0
0
又是跟着 @章北海大哥来的,关于这个问题,我还真能说道两句。
你说我一个修飞机的怎么来抢答机场运行的题呢,这就跟历史的进程有关了。毕业之前是先和某机场签了就业协议跑去实习,干的什么?配载外场。
这个活概括来说呢,就是要根据配载部门结算做出的装载单,在现场核对装载情况,有没有漏装误装,还有就是实际装载和装载单有差异的时候要临机处置,避免装载原因造成航班延误。
说说我了解的航班货物和旅客行李装载流程吧。首先要知道的是,在确定所有旅客托运完行李以前配载结算部门是不可能知道实际行李数目和重量的,所在在计算配载的时候会根据购票人数估算一个值计算,估计出一个用于装行李的空间然后在货舱的剩余空间里配上货物。
航班起飞前大约一个小时我们配载外场和装卸工人来到飞机下,首先拉过来的是货站的货物——很多旅客这时候还没托运行李呢。这时候第一个风险源出现了,货物可能比货站给出的估计值要大,或者形状特殊,或者有特殊的装载要求导致占用空间比实际要大,这时候就要我们做出判断:问一问结算还有多少人没过安检,判断大概行李量,然后根据货舱容量和货物装载进度决定是否拉下部分货物不装以便给行李装载留下足够的空间和时间。
旅客开始登机也就是起飞前半小时左右,,行李会陆续通过传送系统到达行李分拣站,按照航班装车拉到飞机下,结算会通知外场最终行李重量,部分晚到旅客的行李就不等了,结算会通知外场还有几个人没来准备预留空间,等到了再另车送来。这个时候出现了第二个风险源:就算所有旅客的行李都到齐了,分拣站也只知道重量而没有体积数据,很可能同样一吨的行李体积相差达到一两个立方…以往很多旅客用软质包装托运行李,装卸工人还能你懂的,现在用硬壳旅行箱的越来越多了就…总之如果行李体积超过预估又要赶工拉下货物并且通知结算重做舱单。
好了,现在我们把航空公司承运宝贵的货物拉下了好几方,终于把旅客的行李一件不漏的装上了飞机,眼看飞机还有十五分钟就要起飞,突然对讲机里又传来了值机小姐姐甜美的声音:xxxx配载外场,减两个大人一个小孩,找编号xxxxx,xxxxx共两件行李。哦,有人托运了行李没上飞机。怎么办啊我也很绝望啊,找呗。如果是晚到的行李你多留了个心眼放在舱门口了还好,如果他早早的混进了第一批行李放进了货舱深处…可能就要把行李全部翻出来找一遍,航班就这么延误了。
胡乱说了一通,摆结论吧。其实和楼上说的差不多,提前到达是为了能够尽早统计实际人数和行李量给计算配载和现场装载留下余量,而提前截止登机,除了你人和行李赶不上航班以外,还有留给机务绕机和最后的装载应急处置的时间。在最后的十五分钟里,外场向结算通报最终装载结果,结算发送舱单到飞机上,客货舱门关闭廊桥撤离,配载和装载人员撤离,机务进行最后的绕机检查,这时候这架飞机的行李系统已经关闭不再接受新的托运,而就算旅客噔噔噔的跑到飞机底下,飞机也不可能再开门了。

————————————————————

嗨呀补充一点关于这份工作吧,就当记个笔记,不让自己忘了这份只干了两个月的活。
上面瞎写的这一点只摘取了跟时间有关的部分,其实配载外场,或者在一些公司叫现场控制遇到的麻烦事远不止这些。在监控散货舱飞机的时候常常遇到结算预配的装载不合理,毕竟他们看不到货物实际长什么样。出于对航空公司负责的态度,我们是尽量想办法把货物装上去的。主要就是舱位的互相调换,比如2舱的货多出来了而3舱还有空余,就把多出来的货放去3舱。但是这是需要考虑的问题就非常多,舱位移动之后配载有没有超过飞机的重心极限?如果多航段的航班,会不会出现较远航站的货物挡住了较近航段的?最关键的,做出调整舱位决定时往往行李还没有拉出来,会不会幸幸苦苦把货装完,结果没地方装行李了?一般过站航班保障时间50分钟左右,有的航空公司要求到35分钟,外场要同时兼顾到飞行安全、航班时效、航空公司利益和旅客的利益,属于看起来没什么专业技能(其实一眼看出各机型货舱剩余容积和货物体积也很厉害啦哈哈哈哈可是我还做不到_(:з」∠)_)但是实际操作起来非常费脑子的活。
除了监控和调度之外还要负责特殊货物(危险品、活体动物等)的交接、货物行李完整性的检查、现场其他工作人员和设备稽查之类的破事,有时候再有旅客闹出点事,弄得手忙脚乱,其他人还觉得你们不就是在机坪上站着吗有什么忙的。

来源:知乎 www.zhihu.com
作者: OldHentai

【知乎日报】千万用户的选择,做朋友圈里的新鲜事分享大牛。 点击下载

此问题还有 7 个回答,查看全部。
延伸阅读:
国内航空公司从波音、空客等订购飞机是如何交货的?
最近几十年来民航客机技术有哪些进步?

WebRTC 点对点直播

$
0
0

摘自: villainhr

WebRTC 全称为: Web Real-Time Communication。它是为了解决 Web 端无法捕获音视频的能力,并且提供了 peer-to-peer(就是浏览器间)的视频交互。实际上,细分看来,它包含三个部分:

  • MediaStream:捕获音视频流

  • RTCPeerConnection:传输音视频流(一般用在 peer-to-peer 的场景)

  • RTCDataChannel: 用来上传音视频二进制数据(一般用到流的上传)

但通常,peer-to-peer 的场景实际上应用不大。对比与去年火起来的 直播业务,这应该才是 WebRTC 常常应用到的地方。那么对应于 Web 直播来说,我们通常需要两个端:

  • 主播端:录制并上传视频

  • 观众端:下载并观看视频

这里,我就不谈观众端了,后面另写一篇文章介绍(因为,这是在是太多了)。这里,主要谈一下会用到 WebRTC 的主播端。
简化一下,主播端应用技术简单可以分为:录制视频,上传视频。大家先记住这两个目标,后面我们会通过 WebRTC 来实现这两个目标。

WebRTC 基本了解

WebRTC 主要由两个组织来制定。

  • Web Real-Time Communications (WEBRTC) W3C 组织:定义浏览器 API

  • Real-Time Communication in Web-browsers (RTCWEB) IETF 标准组织:定义其所需的协议,数据,安全性等手段。

当然,我们初级目标是先关心基本浏览器定义的 API 是啥?以及怎么使用?
然后,后期目标是学习期内部的相关协议,数据格式等。这样循序渐进来,比较适合我们的学习。

WebRTC 对于音视频的处理,主要是交给 Audio/Vidoe Engineering 处理的。处理过程为:

engineer.svg-62.3kB

  • 音频:通过物理设备进行捕获。然后开始进行 降噪消除回音抖动/丢包隐藏编码

  • 视频:通过物理设备进行捕获。然后开始进行 图像增强同步抖动/丢包隐藏编码

最后通过 mediaStream Object暴露给上层 API 使用。也就是说 mediaStream 是连接 WebRTC API 和底层物理流的中间层。所以,为了下面更好的理解,这里我们先对 mediaStream 做一些简单的介绍。

MediaStream

MS(MediaStream)是作为一个辅助对象存在的。它承载了音视频流的筛选,录制权限的获取等。MS 由两部分构成: MediaStreamTrack 和 MediaStream。

  • MediaStreamTrack 代表一种单类型数据流。如果你用过 会声会影的话,应该对 轨道这个词不陌生。通俗来讲,你可以认为两者就是等价的。

  • MediaStream 是一个完整的音视频流。它可以包含 >=0 个 MediaStreamTrack。它主要的作用就是确保几个轨道是同时播放的。例如,声音需要和视频画面同步。

这里,我们不说太深,讲讲基本的 MediaStream对象即可。通常,我们使用实例化一个 MS 对象,就可以得到一个对象。

// 里面还需要传递 track,或者其他 stream 作为参数。
// 这里只为演示方便
let ms = new MediaStream();

我们可以看一下 ms上面带有哪些对象属性:

  • active[boolean]:表示当前 ms 是否是活跃状态(就是可播放状态)。

  • id[String]: 对当前的 ms 进行唯一标识。例如:"f61641ec-ee78-4317-9415-58acac066a4d"

  • onactive: 当 active 为 true 时,触发该事件

  • onaddtrack: 当有新的 track 添加时,触发该事件

  • oninactive: 当 active 为 false 时,触发该事件

  • onremovetrack: 当有 track 移除时,触发该事件

它的原型链上还挂在了其他方法,我挑几个重要的说一下。

  • clone(): 对当前的 ms 流克隆一份。该方法通常用于对该 ms 流有操作时,常常会用到。

前面说了,MS 还可以其他筛选的作用,那么它是如何做到的呢?
在 MS 中,还有一个重要的概念叫做: Constraints。它是用来规范当前采集的数据是否符合需要。因为,我们采集视频时,不同的设备有不同的参数设置。常用的为:

{"audio": true,  // 是否捕获音频
    "video": {  // 视频相关设置
        "width": {"min": "381", // 当前视频的最小宽度
            "max": "640" 
        },"height": {"min": "200", // 最小高度
            "max": "480"
        },"frameRate": {"min": "28", // 最小帧率
             "max": "10"
        }
    }
}

那我怎么知道我的设备支持的哪些属性的调优呢?
这里,可以直接使用 navigator.mediaDevices.getSupportedConstraints()来获取可以调优的相关属性。不过,这一般是对 video 进行设置。了解了 MS 之后,我们就要开始真正接触 WebRTC 的相关 API。我们先来看一下 WebRTC 基本API。

WebRTC 的常用 API 如下,不过由于浏览器的缘故,需要加上对应的 prefix:

W3C Standard           Chrome                   Firefox
--------------------------------------------------------------
getUserMedia           webkitGetUserMedia       mozGetUserMedia
RTCPeerConnection      webkitRTCPeerConnection  RTCPeerConnection
RTCSessionDescription  RTCSessionDescription    RTCSessionDescription
RTCIceCandidate        RTCIceCandidate          RTCIceCandidate

不过,你可以简单的使用下列的方法来解决。不过嫌麻烦的可以使用 adapter.js来弥补

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia

这里,我们循序渐进的来学习。如果想进行视频的相关交互,首先应该是捕获音视频。

捕获音视频

在 WebRTC 中捕获音视频,只需要使用到一个 API,即, getUserMedia()。代码其实很简单:

navigator.getUserMedia = navigator.getUserMedia ||
    navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

var constraints = { // 设置捕获的音视频设置
  audio: false,
  video: true
};

var video = document.querySelector('video');

function successCallback(stream) {
  window.stream = stream; // 这就是上面提到的 mediaStream 实例
  if (window.URL) {
    video.src = window.URL.createObjectURL(stream); // 用来创建 video 可以播放的 src
  } else {
    video.src = stream;
  }
}

function errorCallback(error) {
  console.log('navigator.getUserMedia error: ', error);
}
// 这是 getUserMedia 的基本格式
navigator.getUserMedia(constraints, successCallback, errorCallback);

详细 demo 可以参考: WebRTC。不过,上面的写法比较古老,如果使用 Promise 来的话,getUserMedia 可以写为:

navigator.mediaDevices.getUserMedia(constraints).
    then(successCallback).catch(errorCallback);

上面的注释大概已经说清楚基本的内容。需要提醒的是,你在捕获视频的同时,一定要清楚自己需要捕获的相关参数。

有了自己的视频之后,那如何与其他人共享这个视频呢?(可以理解为直播的方式)
在 WebRTC 中,提供了 RTCPeerConnection的方式,来帮助我们快速建立起连接。不过,这仅仅只是建立起 peer-to-peer 的中间一环。这里包含了一些复杂的过程和额外的协议,我们一步一步的来看下。

WebRTC 基本内容

WebRTC 利用的是 UDP 方式来进行传输视频包。这样做的好处是延迟性低,不用过度关注包的顺序。不过,UDP 仅仅只是作为一个传输层协议而已。WebRTC 还需要解决很多问题

  1. 遍历 NATs 层,找到指定的 peer

  2. 双方进行基本信息的协商以便双方都能正常播放视频

  3. 在传输时,还需要保证信息安全性

整个架构如下:

WebRTC_stack.svg-39.5kB

上面那些协议,例如,ICE/STUN/TURN 等,我们后面会慢慢讲解。先来看一下,两者是如何进行信息协商的,通常这一阶段,我们叫做 signaling

signaling 任务

signaling 实际上是一个协商过程。因为,两端进不进行 WebRTC 视频交流之间,需要知道一些基本信息。

  • 打开/关闭连接的指令

  • 视频信息,比如解码器,解码器的设置,带宽,以及视频的格式等。

  • 关键数据,相当于 HTTPS 中的 master key用来确保安全连接。

  • 网关信息,比如双方的 IP,port

不过,signaling 这个过程并不是写死的,即,不管你用哪种协议,只要能确保安全即可。为什么呢?因为,不同的应用有着其本身最适合的协商方法。比如:

  • 单网关协议(SIP/Jingle/ISUP)适用于呼叫机制(VoIP,voice over IP)。

  • 自定义协议

  • 多网关协议

signaling.svg-59.5kB

我们自己也可以模拟出一个 signaling 通道。它的原理就是将信息进行传输而已,通常为了方便,我们可以直接使用 socket.io来建立 room提供信息交流的通道。

PeerConnection 的建立

假定,我们现在已经通过 socket.io建立起了一个信息交流的通道。那么我们接下来就可以进入 RTCPeerConnection一节,进行连接的建立。我们首先应该利用 signaling进行基本信息的交换。那这些信息有哪些呢?
WebRTC 已经在底层帮我们做了这些事情-- Session Description Protocol (SDP)。我们利用 signaling传递相关的 SDP,来确保双方都能正确匹配,底层引擎会自动解析 SDP (是 JSEP 帮的忙),而不需要我们手动进行解析,突然感觉世界好美妙。。。我们来看一下怎么传递。

// 利用已经创建好的通道。
var signalingChannel = new SignalingChannel(); 
// 正式进入 RTC connection。这相当于创建了一个 peer 端。
var pc = new RTCPeerConnection({}); 

navigator.getUserMedia({ "audio": true })
.then(gotStream).catch(logError);

function gotStream(stream) {
  pc.addStream(stream); 
  // 通过 createOffer 来生成本地的 SDP
  pc.createOffer(function(offer) { 
    pc.setLocalDescription(offer); 
    signalingChannel.send(offer.sdp); 
  });
}

function logError() { ... }

那 SDP 的具体格式是啥呢?
看一下格式就 ok,这不用过多了解:

v=0
o=- 1029325693179593971 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:nHtT
a=ice-pwd:cuwglAha5fBmGljFXWntH1VN
a=fingerprint:sha-256 24:63:EB:DD:18:1B:BB:5E:B3:E8:C5:D7:92:F7:0B:44:EC:22:96:63:64:76:1A:56:64:DE:6B:CE:85:C6:64:78
a=setup:active
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=inactive
a=rtcp-mux
...

上面的过程,就是 peer-to-peer 的协商流程。这里有两个基本的概念, offeranswer

  • offer: 主播端向其他用户提供其本省视频直播的基本信息

  • answer: 用户端反馈给主播端,检查能否正常播放

具体过程为:

webRTC (1).png-7.7kB

  1. 主播端通过 createOffer 生成 SDP 描述

  2. 主播通过 setLocalDescription,设置本地的描述信息

  3. 主播将 offer SDP 发送给用户

  4. 用户通过 setRemoteDescription,设置远端的描述信息

  5. 用户通过 createAnswer 创建出自己的 SDP 描述

  6. 用户通过 setLocalDescription,设置本地的描述信息

  7. 用户将 anwser SDP 发送给主播

  8. 主播通过 setRemoteDescription,设置远端的描述信息。

不过,上面只是简单确立了两端的连接信息而已,还没有涉及到视频信息的传输,也就是说 UDP 传输。UDP 传输本来就是一个非常让人蛋疼的活,如果是 client-server 的模型话还好,直接传就可以了,但这偏偏是 peer-to-peer 的模型。想想,你现在是要把你的电脑当做一个服务器使用,中间还需要经历如果突破防火墙,如果找到端口,如何跨网段进行?所以,这里我们就需要额外的协议,即,STUN/TURN/ICE ,来帮助我们完成这样的传输任务。

NAT/STUN/TURN/ICE

在 UDP 传输中,我们不可避免的会遇见 NAT(Network address translator)服务器。即,它主要是将其它网段的消息传递给它负责网段内的机器。不过,我们的 UDP 包在传递时,一般只会带上 NAT 的 host。如果,此时你没有目标机器的 entry的话,那么该次 UDP 包将不会被转发成功。不过,如果你是 client-server 的形式的话,就不会遇见这样的问题。但,这里我们是 peer-to-peer 的方式进行传输,无法避免的会遇见这样的问题。

NAT_error.svg-30.4kB

为了解决这样的问题,我们就需要建立 end-to-end 的连接。那办法是什么呢?很简单,就是在中间设立一个 server用来保留目标机器在 NAT 中的 entry。常用协议有 STUN, TURN 和 ICE。那他们有什么区别吗?

  • STUN:作为最基本的 NAT traversal服务器,保留指定机器的 entry

  • TURN:当 STUN 出错的时候,作为重试服务器的存在。

  • ICE:在众多 STUN + TURN 服务器中,选择最有效的传递通道。

所以,上面三者通常是结合在一起使用的。它们在 PeerConnection 中的角色如下图:

ICE.svg-39.2kB

如果,涉及到 ICE 的话,我们在实例化 Peer Connection 时,还需要预先设置好指定的 STUN/TRUN 服务器。

var ice = {"iceServers": [
     {"url": "stun:stun.l.google.com:19302"}, 
     // TURN 一般需要自己去定义
     {
      'url': 'turn:192.158.29.39:3478?transport=udp','credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=','username': '28224511:1379330808'
    },
    {'url': 'turn:192.158.29.39:3478?transport=tcp','credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=','username': '28224511:1379330808'
    }
]};

var signalingChannel = new SignalingChannel();
var pc = new RTCPeerConnection(ice); // 在实例化 Peer Connection 时完成。

navigator.getUserMedia({ "audio": true }, gotStream, logError);

function gotStream(stream) {
  pc.addStream(stream); // 将流添加到 connection 中。

  pc.createOffer(function(offer) {
    pc.setLocalDescription(offer); 
  });
}

// 通过 ICE,监听是否有用户连接
pc.onicecandidate = function(evt) {
  if (evt.target.iceGatheringState == "complete") { 
      local.createOffer(function(offer) {
        console.log("Offer with ICE candidates: " + offer.sdp);
        signalingChannel.send(offer.sdp); 
      });
  }
}
...

在 ICE 处理中,里面还分为 iceGatheringStateiceConnectionState。在代码中反应的就是:

  pc.onicecandidate = function(e) {
    evt.target.iceGatheringState;
    pc.iceGatheringState
  };
  pc.oniceconnectionstatechange = function(e) {
    evt.target.iceConnectionState;
    pc.iceConnectionState;
  };

当然,起主要作用的还是 onicecandidate

  • iceGatheringState: 用来检测本地 candidate 的状态。其有以下三种状态:

    • new: 该 candidate 刚刚被创建

    • gathering: ICE 正在收集本地的 candidate

    • complete: ICE 完成本地 candidate 的收集

  • iceConnectionState: 用来检测远端 candidate 的状态。远端的状态比较复杂,一共有 7 种: new/checking/connected/completed/failed/disconnected/closed

不过,这里为了更好的讲解 WebRTC 建立连接的基本过程。我们使用单页的连接来模拟一下。现在假设,有两个用户,一个是 pc1,一个是 pc2。pc1 捕获视频,然后,pc2 建立与 pc1 的连接,完成伪直播的效果。直接看代码吧:

  var servers = null;
  // Add pc1 to global scope so it's accessible from the browser console
  window.pc1 = pc1 = new RTCPeerConnection(servers);
  // 监听是否有新的 candidate 加入
  pc1.onicecandidate = function(e) {
    onIceCandidate(pc1, e);
  };
  // Add pc2 to global scope so it's accessible from the browser console
  window.pc2 = pc2 = new RTCPeerConnection(servers);
  pc2.onicecandidate = function(e) {
    onIceCandidate(pc2, e);
  };
  pc1.oniceconnectionstatechange = function(e) {
    onIceStateChange(pc1, e);
  };
  pc2.oniceconnectionstatechange = function(e) {
    onIceStateChange(pc2, e);
  };
  // 一旦 candidate 添加成功,则将 stream 播放
  pc2.onaddstream = gotRemoteStream;
  // pc1 作为播放端,先将 stream 加入到 Connection 当中。
  pc1.addStream(localStream);

  pc1.createOffer(
    offerOptions
  ).then(
    onCreateOfferSuccess,
    error
  );
  
function onCreateOfferSuccess(desc) {
  // desc 就是 sdp 的数据
  pc1.setLocalDescription(desc).then(
    function() {
      onSetLocalSuccess(pc1);
    },
    onSetSessionDescriptionError
  );
  trace('pc2 setRemoteDescription start');

  // 省去了 offer 的发送通道
  pc2.setRemoteDescription(desc).then(
    function() {
      onSetRemoteSuccess(pc2);
    },
    onSetSessionDescriptionError
  );
  trace('pc2 createAnswer start');
  pc2.createAnswer().then(
    onCreateAnswerSuccess,
    onCreateSessionDescriptionError
  );
}

看上面的代码,大家估计有点迷茫,来点实的,大家可以参考 单页直播。在查看该网页的时候,可以打开控制台观察具体进行的流程。会发现一个现象,即, onaddstream会在 SDP协商还未完成之前就已经开始,这也是,该 API 设计的一些不合理之处,所以, W3C 已经将该 API 移除标准。不过,对于目前来说,问题不大,因为仅仅只是作为演示使用。整个流程我们一步一步来讲解下。

  1. pc1 createOffer start

  2. pc1 setLocalDescription start // pc1 的 SDP

  3. pc2 setRemoteDescription start // pc1 的 SDP

  4. pc2 createAnswer start

  5. pc1 setLocalDescription complete // pc1 的 SDP

  6. pc2 setRemoteDescription complete // pc1 的 SDP

  7. pc2 setLocalDescription start // pc2 的 SDP

  8. pc1 setRemoteDescription start // pc2 的 SDP

  9. pc2 received remote stream,此时,接收端已经可以播放视频。接着,触发 pc2 的 onaddstream 监听事件。获得远端的 video stream,注意此时 pc2 的 SDP 协商还未完成。

  10. 此时,本地的 pc1 candidate 的状态已经改变,触发 pc1 onicecandidate。开始通过 pc2.addIceCandidate方法将 pc1 添加进去。

  11. pc2 setLocalDescription complete // pc2 的 SDP

  12. pc1 setRemoteDescription complete // pc2 的 SDP

  13. pc1 addIceCandidate success。pc1 添加成功

  14. 触发 oniceconnectionstatechange检查 pc1 远端 candidate 的状态。当为 completed状态时,则会触发 pc2 onicecandidate事件。

  15. pc2 addIceCandidate success。

此外,还有另外一个概念, RTCDataChannel我这里就不过多涉及了。如果有兴趣的可以参阅 webrtc, web 性能优化进行深入的学习。

Java 开源博客 Solo 1.9.0 发布 - 新皮肤

$
0
0

这个版本主要是改进了评论模版机制,让大家更方便皮肤制作,并发布了一款新皮肤:9IPHP。

Solo 是一款 一个命令就能搭建好的 Java 开源博客系统,并内置了 15+ 套精心制作的皮肤。除此之外,Solo 还有着非常活跃的 社区,文章分享到社区后可以让很多人看到,产生丰富的交流互动。

项目地址:

HTTPS的二三事

$
0
0

前几篇博文都是有关HTTPS的东西,有人可能会问,什么是HTTPS?为什么要用HTTPS?

因此,本文主要来解答这些疑惑

Https的故事

  1. Alice和Bob是情人,他们每周都要写信。Alice 写好后,送到邮局,邮局通过若干个快递员到Bob,Bob回信过程类似。这是可以看成的简单的http的传输。
  2. 有一天,Alice觉得,要是写的信中途被人拆开了呢?毕竟一些小秘密不想让人知道啊。于是和Bob商定,采用加密的方法写。加密的方法比如使用古典密码的方案,如凯撒密码(A->E,B->F,即每个字母向右移动4个,(y + 4) mod 26)。这样加密了信的内容。
  3. 但是有一天,Alice收到Bob的信,信的内容经过解密后为分手之类的话。Alice跑去问Bob,Bob发现有人伪造了自己的信件,于是Alice和Bob商定,以后信上最后按上自己的指纹(就是数字证书),用来保证这封信别人伪造不了,此外,还用上校验码(数字签名),将信的内容经过hash,附带在信的末尾。在收信的一方收到后,首先查看是否有对方的指纹,并计算hash值和信的末尾所附带的是否一致,如果一致才进行解密查看。其实这样通信可以说从http到了https。

为什么需要HTTPS

通过上面的故事,现在可以来介绍一下https了。

https也叫做HTTP over TLS, HTTP over SSL, and HTTP Secure,Https 相比http来说,加密了传输过程和数据,能有效的保证了数据来源的可靠性、保密性以及数据的完整性。

  • 保密性从何说起呢?这个很好理解。再http中,数据使用明文传输,容易受到第三方的窃取和修改,用户的隐私也容易泄漏。而使用https后,对于传输过程和数据都进行了加密,使得数据难以被窃听,隐私也不容易被泄漏。
  • 数据的完整性呢?http中数据为明文,可能会被中间的劫持,然后修改。比较著名的有流量劫持,可以看知乎: 如何看待众多互联网公司联合声明:呼吁运营商严格打击流量劫持?。而https加密了内容,并且对传输的数据进行了校验,一旦被篡改,通信双方回立刻发现。
  • 此外,还有来源的可靠性。比如访问hrwhisper.me,你可能访问一台假的服务器,而该服务器提供和真正服务器不一样的内容。使用https,服务器提供了身份的证书,能防止身份被冒充。

Https的过程

1. 客户端问候Clinethello

客户端向浏览器发出加密通信的请求。
这个请求有如下信息:

  • 支持的加密通信协议版本、加密算法
  • 随机数(一个时间戳加上28字节的随机数),后面用于生成对话密钥。

2. 服务器问候SeverHello

服务器接收到客户端请求,发送问候,该问候包含以下信息:

  • 确认使用的加密通信协议版本和加密方法,比如TLS 1.2版本以及RSA公钥加密。(如果浏览器与服务器支持的版本不一致,服务器关闭加密通信。)
  • 一个服务器生成的随机数,稍后用于生成”对话密钥”。
  • 服务器证书。

3. 客户端回应

客户端受到服务器问候后,首先会验证服务器证书是否真实有效。该证书需要和服务器的域名一致,且在有效期内,并且由可信机构CA颁布。若该证书无效,会向用户显示警告,由用户选择是否继续访问(典型的看12306)。如果证书没有问题,客户端向服务器发送下面的信息:

  • 随机数(pre-master secret),且该随机数用服务器公钥加密,防止被窃听
  • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
  • 客户端握手结束信息。前面发送的所有内容的hash值,用来供服务器校验。

4. 服务器的最后回应

服务器收到客户端的pre-master secret后,结合之前的两个随机数,计算生成本次会话所用的“会话密钥”,然后向客户端发送下面信息:

  • 编码改变通知
  • 服务器握手结束信息。前面发送的所有内容的hash值,用来供客户端校验。

 

经历了四次握手后,客户端与服务器进入加密通信,其实之后就是普通的http协议,只不过用会话密钥加密加密内容。此外,一般而言,https连接只在第一次握手时使用非对称加密,通过握手交换对称加密密钥,在之后的通信走对称加密。因为一般而言,如RSA等非对称加密耗费的CPU资源较大。

 

Https的几个问题

会话密钥

会话密钥是怎么产生的呢?客户端和服务器在Hello都产生了一个随机数,结合pre-master secret一起生成了会话密钥(服务器和客户端由相同的三个随机数)。可以看如下图

为什么上面的需要3个随机数,而不是只用pre-master secret生成会话密钥?

因为每个主机并不是都能产生完全的随机数的。有很多产生的只是弱随机数而已,比如范围小,可能被猜测。如果该随机数被破解,那么之前一系列握手都是没什么用的。因此用三个随机数一起生成密钥能使得伪随机数更接近随机。

数字签名和数字证书

https中使用了数字签名和数字证书。
数字签名是私人的,用于加密摘要和报文。
数字证书是由CA中心派发的,用于证明身份。
详情可以看   数字签名是什么?

Https一定安全么

不一定。

比如使用弱密钥交换协议 Diffie-Hellman 。

此外,也和使用的安全传输协议有关,建议的是使用TLS1.0以上的版本(如TLS1.0, TLS1.1,TLS1.2,TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。)而SSL2.0有严重的漏洞,SSL3.0也有 https://www.openssl.org/~bodo/ssl-poodle.pdf

如果你的站点采用了https,可以用可以用 https://www.ssllabs.com/ssltest/ 测试安全性。

HSTS协议

HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器创建连接。可在保证安全性的前提下,提高网站的响应速度。此外HSTS可以很大程度上解决SSL剥离攻击,因为只要浏览器曾经与服务器创建过一次安全连接,之后浏览器会强制使用HTTPS,即使链接被换成了HTTP。

 

小结

Https 为我们带来更安全的传输。Chrome 56中,将输入密码的非https网站标记为不安全。 Google 把https 作为网站排名的一个因素……

https需要数字证书,那么如何获取证书呢?过去证书需要购买,而现在我们应该感谢 Let’s Encrypt 项目,可以免费的签发个人的证书,这样能更好的推广https, 从而有一天真正实现Https as default。

关于Let’s Encrypt获取证书可以参考本博客  Let’s Encrypt! Centos 获取https证书攻略

参考资料

HTTPS的二三事,首发于 细语呢喃

老司机程序员用到的各种优秀资料、神器及框架整理

$
0
0

前言

成为一名专业程序员的道路上,需要坚持练习、学习与积累,技术方面既要有一定的广度,更要有自己的深度。

笔者作为一位tool mad,将工作以来用到的各种优秀资料、神器及框架整理在此,毕竟好记性不如烂键盘,此项目可以作为自己的不时之需。

本人喜欢折腾,记录的东西也比较杂,各方面都会有一些,内容按重要等级排序,大家各取所需。

这里的东西会持续积累下去,欢迎Star,也欢迎发PR给我。

目录


<!– END doctoc generated TOC please keep comment here to allow auto update –>

资料篇

技术站点

  • 在线学习: CourseraedXUdacityMIT公开课MOOC学院
  • Hacker News:非常棒的针对编程的链接聚合网站
  • Techmeme:美国知名科技新闻和博客聚集网站,类似的还有(Panda, Hacker & Designer News)
  • Programming reddit:同上
  • Java牛人必备: Program Creek
  • stackoverflow:IT技术问答网站
  • GitHub:全球最大的源代码管理平台,很多知名开源项目都在上面,如Linux内核,OpenStack等
  • LeetCode:来做做这些题吧,看看自己的算法水平如何?这可比什么面试宝典强多了。
  • Kaggle,Topcoder: 机器学习、大数据竞赛
  • 掘金:高质量的技术社区
  • 开发者头条
  • InfoQ:企业级应用,关注软件开发领域
  • V2EX: way to explore
  • 国内老牌技术社区:OSChina、博客园、CSDN、51CTO
  • 免费的it电子书: http://it-ebooks.info/
  • 在线学习: http://www.udemy.com/
  • 优质学习资源: http://plus.mojiax.com/
  • 代码练习: http://exercism.io/ and https://www.codingame.com
  • DevStore:开发者服务商店
  • MSDN:微软相关的官方技术集中地,主要是文档类

必看书籍

  • SICP( Structure and Interpretation of Computer Programs)
  • 深入理解计算机系统
  • 代码大全2
  • 人件
  • 人月神话
  • 软件随想录
  • 算法导论(麻省理工学院出版社)
  • 离线数学及其应用
  • 设计模式
  • 编程之美
  • 黑客与画家
  • 编程珠玑
  • The Little Schemer
  • Simply Scheme_Introducing_Computer_Science
  • C++ Prime
  • Effective C++
  • TCP/IP详解
  • Unix 编程艺术
  • 软件随想录
  • 计算机程序设计艺术
  • 职业篇:程序员的自我修养,程序员修炼之道,高效能程序员的修炼
  • 《精神分析引论》弗洛伊德
  • 《失控》《科技想要什么》《技术元素》凯文凯利
  • 程序开发心理学
  • 天地一沙鸥
  • 搞定:无压力工作的艺术

大牛博客

GitHub篇

学习资料篇

Swift相关

客户端

Framework

小工具

游戏

工作、工具篇

优秀项目篇

工具篇

平台工具

  • Phabricator: 软件开发平台,Facebook出品,现已开源,CodeReview神器(从这个往下一直到GitLab之间的工具统统可以忽略了)
  • Redmine/Trac:项目管理平台
  • Jenkins/Jira(非开源):持续集成系统(Apache Continuum,这个是Apache下的CI系统,还没来得及研究)
  • git,svn:源代码版本控制系统
  • GitLab/Gitorious:构建自己的GitHub服务器
  • Postman:RESTful,api测试工具,HTTP接口开发必备神器
  • Sonar:代码质量管理平台
  • Nessus: 系统漏洞扫描器
  • gitbook: https://www.gitbook.io/写书的好东西,当然用来写文档也很不错的(发现不少产品的文档就是用的它)
  • Travis-ci:开源项目持续集成必备,和GitHub相结合, https://travis-ci.org/
  • Trello:简单高效的项目管理平台,注重看板管理
  • 日志聚合:graylog、ELK(推荐新一代的graylog,基本上算作是开源的Splunk了)
  • 开源测试工具、社区(Selenium、OpenQA.org)
  • Puppet:一个自动管理引擎,可以适用于Linux、Unix以及Windows平台。所谓配置管理系统,就是管理机器里面诸如文件、用户、进程、软件包这些资源。无论是管理1台,还是上万台机器Puppet都能轻松搞定。其他类似工具:CFEngine、SaltStack、Ansible
  • Nagios:系统状态监控报警,还有个Icinga(完全兼容nagios所有的插件,工作原理,配置文件以及方法,几乎一模一样。配置简单,功能强大)
  • Ganglia:分布式监控系统
  • fleet:分布式init系统
  • Ansible:能够大大简化Unix管理员的自动化配置管理与流程控制方式。
  • GeoLite免费数据库
  • jsHint:js代码验证工具
  • haproxy: 高可用负载均衡(此外类似的系统还有nginx,lvs)
  • linux OS性能分析工具:dstat,iostat,iotop,nmon
  • kimono:将网页信息转换为api接口的工具
  • 集群管理工具:pdsh,ClusterSSH,mussh(可以用它快速管理Hadoop集群)ipa-server做统一的认证管理
  • influxdb: 分布式时序数据库,结合Grafana可以进行实时数据分析
  • dot: 程序员绘图利器(是种语言,也是个工具)
  • Graph::Easy: (Ascii Art工具)字符流程图绘制,实乃程序员装逼神器。其他类似的工具Asciiflow, vi插件:drawit!
  • spf13-vim: 让你的vim飞起来!
  • Kubernetes: 容器集群管理系统
  • Gatling: 服务器性能压力测试工具
  • systemtap: Linux内核探测工具、内核调试神器
  • Cygwin:Windows下的类UNIX模拟环境
  • MinGW:Windows下的GNU工具集

常用工具

  • Mac下的神兵利器
  • asciinema: 终端录屏神器
  • Fiddler:非常好用的Web前端调试工具,当然是针对底层http协议的,一般情况使用Chrome等自带的调试工具也足够了,特殊情况还得用它去处理
  • Charles: Mac上的Web代理调试工具,类似Fiddler
  • wireshark:知名的网络数据包分析工具
  • PowerCmd:替代Windows Cmd的利器
  • RegexBuddy:强大的正则表达式测试工具
  • Soure Insight:源代码阅读神器
  • SublimeText:程序员最爱的编辑器
  • Database.NET:一个通用的关系型数据库客户端,基于.NET 4.0开发的,做简单的处理还是蛮方便的
  • Navicat Premium:支持MySql、PostgreSQL、Oracle、Sqlite和SQL Server的客户端,通用性上不如Database.NET,但性能方面比Database.NET好很多,自带备份功能也用于数据库定时备份。
  • Synergy : 局域网内一套键盘鼠标控制多台电脑
  • DameWare:远程协助工具集(我在公司主要控制大屏幕用)
  • Radmin: 远程控制工具,用了一段时间的DameWare,还要破解,对Win7支持的不好,还是发现这个好用
  • Listary:能极大幅度提高你 Windows 文件浏览与搜索速度效率的「超级神器」
  • Clover:给资源管理器加上多标签,我平时工作的时候就用它,像Chrome一样使用资源管理器,甚是方便啊(这是Windows平台的)
  • WinLaunch:模拟Mac OS的Launch工具
  • Fritzing:绘制电路图
  • LICEcap:gif教程制作
  • git,svn:版本控制系统
  • Enigma Virtual Box(将exe,dll等封装成一个可执行程序)
  • Open DBDiff(针对SqlServer)数据库同步
  • SymmetricDS:数据库同步
  • BIEE,Infomatica,SPSS,weka,R语言:数据分析
  • CodeSmith,LightSwitch:代码生成
  • Pandoc:Markdown转换工具,出书用的。以前玩过docbook,不过现在还是Markdown盛行啊。
  • Window Magnet[Mac]:增强Mac窗口管理功能,想Win7一样具有窗口拖放到屏幕边缘自动调整的功能
  • log explorer:查看SqlServer日志
  • dependency walker:查询Windows应用程序dll依赖项
  • Shairport4w:将iPhone,iPad,iPod上的音频通过AirPlay协议传输到PC上
  • ngrok:内网穿透工具
  • Axure:快速原型制作工具,还有个在线作图的工具国内的一个创业团队做的,用着很不错 http://www.processon.com/
  • Origami: 次世代交互设计神器
  • 百度脑图: http://naotu.baidu.com/
  • tinyproxy:(Linux)小型的代理服务器支持http和https协议
  • EaseUS Partition Master:超级简单的分区调整工具,速度还是蛮快的,C盘不够用了就用它从D盘划点空间吧,不用重装系统这么折腾哦。
  • CheatEngine:玩游戏修改内存值必备神器(记得我在玩轩辕剑6的时候就用的它,超级方便呢)
  • ApkIDE:Android反编译神器
  • 翻、墙工具(自|由|门、天行浏览器,免费的VPN: http://www.mangovpn.com/),发现最方便还属Lantern,免费用起来超级方便(更新于2015-08-22)
  • 设计工具:Sketch、OmniGraffle
  • MindManger:思维导图
  • MagicDraw:Uml图工具
  • innotop:MySql状态监测工具
  • 墨刀:比Axure更为简单的原型工具,可以快速制作原型
  • Karabiner: Mac专用,修改键盘键位的神器,机械键盘必备
  • Timing:Mac专用,统计你的时间都花在哪了
  • f.lux: 护眼神器,过滤蓝光,程序员护眼必备良品
  • LaTeX: 基于ΤΕΧ的排版系统, 让写论文更方便
  • Antlr:开源的语法分析器,可以让你毫无压力的写个小parser

第三方服务

  • DnsPod:一个不错的只能DNS服务解析提供商
  • DigitalOcean:海外的云主机提供商,价格便宜,磁盘是SSD的,用过一段时间整体上还可以,不过毕竟是海外的,网速比较慢。国内的就是阿里云了。还有个比较知名的是:Linode,据说速度上比DigitalOcean好很多
  • 移动端推送服务:个推、JPush、云巴
  • LeanCloud:移动应用开发服务,包括:数据存储、用户管理、消息推送、应用统计、社交分享、实时聊天等服务
  • Color Hunt: 漂亮炫酷的配色网站,程序员的福音
  • Heroku: PaaS平台

爬虫相关(好玩的工具)

  • Phantomjs(Web自动化测试,服务端渲染等)
  • berserkJS(基于Phantomjs的改进版本)
  • SlimerJS
  • CasperJS
  • selenium
  • HtmlUnit(开源的java 页面分析工具,也是个Headless的浏览器)

安全相关

  • sql注入检测:sqlmap、haviji
  • 端口扫描:nmap
  • 渗透测试:BurpLoader
  • sqltools: sql漏洞利用工具
  • snort: 入侵检测

Web服务器性能/压力测试工具/负载均衡器

  • ab: ab是apache自带的一款功能强大的测试工具
  • curl-loader: 真实模拟、测试Web负载
  • http_load: 程序非常小,解压后也不到100K
  • webbench: 是Linux下的一个网站压力测试工具,最多可以模拟3万个并发连接去测试网站的负载能力。
  • Siege: 一款开源的压力测试工具,可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。
  • squid(前端缓存),nginx(负载),nodejs(没错它也可以,自己写点代码就能实现高性能的负载均衡器):常用的负载均衡器
  • Piwik:开源网站访问量统计系统
  • ClickHeat:开源的网站点击情况热力图
  • HAProxy:高性能TCP /HTTP负载均衡器
  • ElasticSearch:搜索引擎基于Lucene
  • Page Speed SDK和YSLOW
  • HAR Viewer: HAR分析工具
  • protractor:E2E(end to end)自动化测试工具

大数据处理/数据分析/分布式工具

  • Hadoop:分布式的文件系统,结合其MapReduce编程模型可以用来做海量数据的批处理(Hive,Pig,HBase啥的就不说了),值得介绍的是Cloudera的Hadoop分支CDH5,基于YARN MRv2集成了Spark可直接用于生产环境的Hadoop,对于企业快速构建数据仓库非常有用。
  • Spark:大规模数据处理框架(可以应付企业中常见的三种数据处理场景:复杂的批量数据处理(batch data processing);基于历史数据的交互式查询(interactive query);基于实时数据流的数据处理(streaming data processing)),CSND有篇文章介绍的不错
  • 除了Spark,其他几个不错的计算框架还有:Kylin,Flink,Drill
  • Ceph:Linux分布式文件系统(特点:无中心)
  • Storm:实时流数据处理,可以看下IBM的一篇介绍 (还有个Yahoo的S4,也是做流数据处理的)
  • Druid: 实时数据分析存储系统
  • Ambari: 大数据平台搭建、监控利器;类似的还有CDH
  • Tachyon:分布式内存文件系统
  • Mesos:计算框架一个集群管理器,提供了有效的、跨分布式应用或框架的资源隔离和共享
  • Impala:新一代开源大数据分析引擎,提供Sql语义,比Hive强在速度上
  • presto: facebook的开源工具,大数据分布式sql查询引擎
  • SNAPPY:快速的数据压缩系统,适用于Hadoop生态系统中
  • Kafka:高吞吐量的分布式消息队列系统
  • ActiveMQ:是Apache出品,最流行的,能力强劲的开源消息总线
  • MQTT:Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分
  • RabbitMQ:记得OpenStack就是用的这个东西吧
  • ZeroMQ:宣称是将分布式计算变得更简单,是个分布式消息队列,可以看下云风的一篇文章的介绍
  • 开源的日志收集系统:scribe、chukwa、kafka、flume。这有一篇对比文章
  • Zookeeper:可靠的分布式协调的开源项目
  • Databus:LinkedIn 实时低延迟数据抓取系统
  • 数据源获取:Flume、Google Refine、Needlebase、ScraperWiki、BloomReach
  • 序列化技术:JSON、BSON、Thrift、Avro、Google Protocol Buffers
  • NoSql:ScyllaDB(宣称是世界上最快的NoSql)、Apache Casandra、MongoDB、Apache CouchDB、Redis、BigTable、HBase、Hypertable、Voldemort、Neo4j
  • MapReduce相关:Hive、Pig、Cascading、Cascalog、mrjob、Caffeine、S4、MapR、Acunu、Flume、Kafka、Azkaban、Oozie、Greenplum
  • 数据处理:R、Yahoo! Pipes、Mechanical Turk、Solr/ Lucene、ElasticSearch、Datameer、Bigsheets、Tinkerpop
  • NLP自然语言处理:Natural Language Toolkit、Apache OpenNLP、Boilerpipe、OpenCalais
  • 机器学习:TensorFlow(Google出品),WEKA、Mahout、scikits.learn、SkyTree
  • 可视化技术:GraphViz、Processing、Protovis、Google Fusion Tables、Tableau、Highcharts、EChats(百度的还不错)、Raphaël.js
  • Kettle:开源的ETL工具
  • Pentaho:以工作流为核心的开源BI系统
  • Mondrian:开源的Rolap服务器
  • Oozie:开源hadoop的工作流调度引擎,类似的还有:Azkaban
  • 开源的数据分析可视化工具:Weka、Orange、KNIME
  • Cobar:阿里巴巴的MySql分布式中间件
  • 数据清洗:data wrangler, Google Refine

Web前端

  • Material Design: 谷歌出品,必属精品
  • Vue.js: 借鉴了Angular及React的JS框架,设计理念较为先进
  • GRUNT: js task runner
  • Sea.js: js模块化
  • knockout.js:MVVM开发前台,绑定技术
  • Angular.js: 使用超动感HTML & JS开发WEB应用!
  • Highcharts.js,Flot:常用的Web图表插件
  • NVD3: 基于d3.js的图表库
  • Raw:非常不错的一款高级数据可视化工具
  • Rickshaw:时序图标库,可用于构建实时图表
  • JavaScript InfoVis Toolkit:另一款Web数据可视化插件
  • Pdf.js,在html中展现pdf
  • ACE,CodeMirror:Html代码编辑器(ACE甚好啊)
  • NProcess:绚丽的加载进度条
  • impress.js:让你制作出令人眩目的内容展示效果(类似的还有reveal)
  • Threejs:3DWeb库
  • Hightopo:基于Html5的2D、3D可视化UI库
  • jQuery.dataTables.js:高度灵活的表格插件
  • Raphaël:js,canvas绘图库,后来发现百度指数的图形就是用它绘出来的
  • director.js:js路由模块,前端路由,Nodejs后端路由等,适合构造单页应用
  • pace.js:页面加载进度条
  • bower:Web包管理器
  • jsnice:有趣的js反编译工具,猜压缩后的变量名, http://www.jsnice.org/
  • D3.js: 是一个基于JavaScript数据展示库(类似的还有P5.js)
  • Zepto.js:移动端替代jQuery的东东,当然也可以使用jquery-mobile.
  • UI框架:Foundation,Boostrap,Pure,EasyUI,Polymer
  • 前段UI设计师必去的几个网站:Dribbble,awwwards,unmatchedstyle,UIMaker
  • Mozilla 开发者中心: https://developer.mozilla.org/en-US/
  • 图标资源:IcoMoon(我的最爱),Font Awesome, Themify Icons,FreePik,Glyphicons
  • artDialog:非常漂亮的对话框
  • AdminLTE:github上的一个开源项目,基于Boostrap3的后台管理页面框架
  • Respond.js:让不懂爱的IE6-8支持响应式设计
  • require.js: js模块加载库
  • select2:比chosen具有更多特性的选择框替代库
  • AngularUI:集成angular.js的UI库
  • normalize.css: 采用了现代化标准让各浏览器渲染出的html保持一致的库
  • CreateJS:Html5游戏引擎
  • Less,Compass:简化CSS开发
  • emojify.js:用于自动识别网页上的Emoji文字并将其显示为图像
  • simditor:一个不错的开源的html编辑器,简洁高效
  • Sencha: 基于html5的移动端开发框架
  • SuperScrollorama+TweenMax+skrollr:打造超酷的视差滚动效果网页动画
  • jquery-smooth-scroll:同上,平滑滚动插件
  • Animate.css:实现了各种动画效果的css库
  • Emmet:前端工程师必备,ZenCode的前身
  • React: facebook出品的js UI库
  • highlight.js:专门用来做语法高亮的库
  • GoJS: Html5交互式图表库,看demo更适合层次结构的图表。
  • 10 Pure CSS (Mostly) Flat Mobile Devices: http://marvelapp.github.io/devices.css/
  • CodePen: http://codepen.io/
  • jsfiddle: http://jsfiddle.net/前端js,html,css测试利器

语言篇

折腾中:Scala、Python、Lua、JavaScript、Go

待折腾:

Scala

Java

  • 常用的IDE:IntelliJ IDEA(强烈推荐),Eclipse,Netbeans
  • fastutil: 性能更好的Java集合框架
  • Guava: 谷歌的Java工具包,应用广泛
  • Curator:Netflix公司开源的一个Zookeeper client library,用于简化Zookeeper客户端编程,现在已经是apache下的一个独立项目了。Spark的HA也用的这货。
  • Rx(Reactive Extensions)框架:Vert.x, RxJava(Android中用的比较多), Quasar
  • FindBugs: 代码静态分析工具,找出代码缺陷
  • Java反编译工具:Luyten,JD-Gui
  • Drools: 规则引擎
  • Jersey: Java RESTful 框架
  • canal: 阿里巴巴出品,binlog增量订阅&消费组件
  • Web开发相关:Tomcat、Resin、Jetty、WebLogic等,常用的组件Struts,Spring,Hibernate
  • Netty: 异步事件驱动网络应用编程框架,用于高并发网络编程比较好(NIO框架,spark 1.2.0就用netty替代了nio)
  • MINA:简单地开发高性能和高可靠性的网络应用程序(也是个NIO框架),不少手游服务端是用它开发的
  • jOOQ:java Orm框架
  • Janino: 超级小又快的Java编译器,Spark的Tungsten引起用的它
  • Activiti:工作流引擎,类似的还有jBPM、Snaker
  • Perfuse:是一个用户界面包用来把有结构与无结构数据以具有交互性的可视化图形展示出来.
  • Gephi:复杂网络分析软件, 其主要用于各种网络和复杂系统,动态和分层图的交互可视化与探测开源工具
  • Nutch:知名的爬虫项目,hadoop就是从这个项目中发展出来的
  • web-harvest:Web数据提取工具
  • POM工具:Maven+Artifactory
  • Akka:一款基于actor模型实现的 并发处理框架
  • EclEmma:覆盖测试工具
  • Shiro:安全框架
  • joda-time:简化时间处理
  • parboiled:表达式解析
  • dozer: 深拷贝神器
  • dubbo: 阿里巴巴出品的分布式服务框架
  • jackson databind: json序列化工具(fastjson,simplejson)
  • Atomikos: 分布式事务管理
  • BoneCP:性能很赞的数据库连接池组件,据说比c3p0快好多
  • ProGuard: obconfuscation tool, 强大的混淆工具
  • S-99:Scala相关的99个问题

Python

  • PyCharm:最佳Python IDE
  • Eric,Eclipse+pydev,比较不错的Python IDE
  • PyWin:Win32 api编程包
  • numpy:科学计算包,主要用来处理大型矩阵计算等,此外还有SciPy,Matplotlib
  • GUI相关:PyQt,PyQwt
  • supervisor:进程监控工具
  • PyGame: 基于Python的多媒体开发和游戏软件开发模块
  • Web框架: Django 开源web开发框架,它鼓励快速开发,并遵循MVC设计

.NET

  • Xilium.CefGlue:基于CEF框架的.NET封装,基于.NET开发Chrome内核浏览器
  • CefSharp:同上,有一款WebKit的封装,C#和Js交互会更简单
  • netz:免费的 .NET 可执行文件压缩工具
  • SmartAssembly:变态的.net代码优化混淆工具
  • NETDeob0:.net反混淆工具,真是魔高一尺道高一丈啊(还有个de4dot,在GitHub上,都是开源的)
  • ILMerge:将所有引用的DLL和exe文件打成一个exe文件
  • ILSpy:开源.net程序反编译工具
  • Javascript.NET:很不错的js执行引擎,对v8做了封装
  • NPOI: Excel操作
  • DotRAS:远程访问服务的模块
  • WinHtmlEditor: Winform下的html编辑器
  • SmartThreadPool:使用C#实现的,带高级特性的线程池
  • Snoop: WPF Spy Utility
  • Autofac: 轻量级IoC框架
  • HtmlAgilityPack:Html解析利器
  • Quartz.NET:Job调度
  • HttpLib:@CodePlex,简化http请求
  • SuperSocket:简化Socket操作,基于他的还有个SuperWebSocket,可以开发独立的WebSocket服务器了
  • DocX:未安装Office的情况下操作Word文件
  • Dapper:轻量级的ORM类,性能不错
  • HubbleDotNet:支持接入数据库的全文搜索系统
  • fastJSON:@CodeProject,高性能的json序列化类
  • ZXing.NET:@CodePlex,QR,条形码相关
  • Nancy:轻量级Http服务器,做个小型的Web应用可以摆脱IIS喽(Nancy.Viewengines.Razor,可以加入Razor引擎)
  • AntiXSS:微软的XSS防御库Microsoft Web Protection Library
  • Jint:JavaScript解释器
  • CS-Script:将C#代码文件作为脚本执行
  • Jexus:Linux下 高性能、易用、免费的ASP.NET服务器
  • Clay:将dynamic发挥的更加灵活,像写js一样写C#
  • DynamicJSON:不必定义数据模型获取json数据
  • SharpPcap:C#版的WinPcap调用端,牛逼的网络包分析库(自带PacketNotNet用于包协议分析)
  • Roslyn:C#,VB编译器
  • ImageResizer: 服务端自由控制图片大小,真乃神器也,对手机端传小图,PC端传大图,CMS用它很方便
  • UI相关:DevExpress, Fluent(Office 07风格), mui(Modern UI for WPF)
  • NetSparkle:应用自动更新组件
  • ConfuserEx: 开源.net混淆工具
  • ServiceStack: 开源高性能Web服务框架,可用于构建高性能的REST服务
  • Expression Evaluator:Eval for C#,处理字符串表达式
  • http://nugetmusthaves.com/
  • Reactive Extensions (Rx):异步,事件驱动编程包, Rx = Observables + LINQ + Schedulers

C & C++

  • Thrift:用来进行可扩展且跨语言的服务的开发(类似的还有个Avro,Google protobuf)。
  • libevent:是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。(对了还有个libev呢)
  • Boost:不多说了,准C++标准库
  • Valgrind/Ptmalloc/Purify: 调试工具
  • NetworkServer架构:acceptor->dispatcher->worker(这个不算工具哦)
  • breakpad:崩溃转储和分析模块,很多crashreport会用到
  • UI界面相关:MFC、BCG和QT这类的就不说了,高端一点的还有Html和DirectUI技术:libcef(基于chrome内核的,想想使用html5开发页面,还真有点小激动呢)、HtmlLayout、Duilib、Bolt,非C++的,还有node-webkit也不错,集成了node和webkit内核。

其他

游戏开发相关

  • MINA:使用Java开发手游和页游服务器(对了还有Netty,也很猛的,都是基于NIO的)
  • HP-Socket:见有有些页游服务器使用这个构建的
  • Unreal: 虚幻引擎,C++,基于这个引擎的游戏很多
  • OGRE:大名鼎鼎的3D图形渲染引擎,天龙八部OL、火炬之光等不少游戏都用了这个引擎
  • OpenVDB:梦工厂C++的特效库,开源的
  • cocos2d:跨平台2D游戏引擎
  • unity3d:跨平台3D游戏引擎,很火的哦
  • Nodejs:也有不少使用它来开发手游和也有服务器(网易的Pomelo)

日志聚合,分布式日志收集

  • Scribe:Facebook的(nodejs + scribe + inotify 同步日志)
  • logstash:强大的日志收集系统,可以基于logstash+kibana+elasticsearch+redis开发强大的日志分析平台
  • log.io: nodejs开发的实时日志收集系统

RTP,实时传输协议与音视频

  • RTP,RTCP,RTSP-> librtp,JRTPLIB(遵循了RFC1889标准)
  • 环形缓冲区,实时数据传输用
  • SDL,ffmpeg,live555,Speex
  • Red5:用Java开发开源的Flash流媒体服务器。它支持:把音频(MP3)和视频(FLV)转换成播放流; 录制客户端播放流(只支持FLV);共享对象;现场直播流发布;远程调用。

告诉你个秘密: 找不到收藏按钮的同学,直接扫码打赏就可以了 然后【个人中心】【我的钱包】就有这篇记录了(强烈推荐+99)

安居客Android项目架构演进

$
0
0

入职安居客三年从工程师到Team Leader,见证了Android团队一路走来的发展历程。因此有心将这些记录下来与大家分享,也算是对自己三年来一部分工作的总结。希望对大家有所帮助,更希望能得到大家宝贵的建议。

三网合并

三年前入职时安居客在业务上刚完成了三网合并(新房、二手房、好租和商业地产多个平台多个网站合成现在的anjuke.com,这在公司的历史上称之为三网合并),因此app端也将原先的新房、二手房、好租和商业地产多个app合并成为了现在的安居客app。所谓的合并也差不多就是将多个项目的代码拷贝到了一起组成了新的Anjuke Project。下面这张图能更加直观的呈现当时的状况。
clipboard.png

这一时期代码结构混乱、层次不清,各业务技术方案不统一,冗余代码充斥项目的各个角落;甚至连基本的包结构也是胡乱不堪,项目架构更是无从谈起。大家只不过是不停地往上堆砌代码添加新功能罢了。于是我进入公司的第一件事就是向Leader申请梳理了整个项目的结构。

而后随着项目的迭代,我们不断引入了Retrofit、UniversalImageLoader、OKHttp、ButterKnife等一系列成熟的开源库,同时我们也开发了自己的UI组件库UIComponent、基础工具库CommonUtils、基于第三方地图封装的MapSDK、即时聊天模块ChatLibrary等等。这之后安居客项目架构大致演变成了由基础组件层、业务组件层和业务层组成的三层架构。如下图:
clipboard.png

其中业务层是一种非标准的MVC架构,Activity和Fragment承担了View和Controller的职责:
clipboard.png

前面这种分层的架构本身是没太大问题的,即使到了现在我们的业务项目也已然是基于这种分层的架构来构建的,只不过在不断的迭代中我们做了些许调整(分层架构后面在介绍组件化和模块化的时候会详细介绍)。但是随着业务的不断迭代,我们慢慢发现业务层这种非标准的MVC架构带来了种种影响团队开发效率的问题:

  • Activity和Fragment越来越多的同时承担了Controller和View的职责,导致他们变得及其臃肿且难以维护;

  • 由于Controller和View的揉合,导致单元测试起来很困难;

  • 回调嵌套太多,面对负责业务时的代码逻辑不清晰,难以理解且不利于后期维护;

  • 各层次模块之间职责不清晰等等

鉴于三网合并时期我还未加入安居客,所以对这一块的理解难免有偏差,如果有安居客的老同事发现文章中的描述有不对的地方还望批评指正。

由RxJava驱动的MVP架构

一种技术架构无法满足所有的业务项目,更不可能有一种架构方案能够一劳永逸。正如上一节中提到的随着业务的不断迭代,现有架构的缺陷逐渐浮出水面,项目架构必需不断升级迭代才能更好地服务于业务。

MVP的设计与实现

在研究了Google推出的基于MVP架构的demo后,我们发现MVP架构能解决现在所面临过的很多问题,于是我们学习并引入到了我们的项目中来,并针对性的做了部分调整。下图呈现的是安居客MVP方案:
clipboard.png

以前面提到的三层架构的方案来看是这样的:
clipboard.png

基于此架构我在GitHub上开源了一个项目 MinimalistWeather,有兴趣的小伙伴可以去clone下来看看,如果觉得对你有帮助就给个star吧。 :)

  • View Layer: 只负责UI的绘制呈现,包含Fragment和一些自定义的UI组件,View层需要实现ViewInterface接口。Activity在项目中不再负责View的职责,仅仅是一个全局的控制者,负责创建View和Presenter的实例;

  • Model Layer: 负责检索、存储、操作数据,包括来自网络、数据库、磁盘文件和SharedPreferences的数据;

  • Presenter Layer: 作为View Layer和Module Layer的之间的纽带,它从model层中获取数据,然后调用View的接口去控制View;

  • Contact: 我们参照Google的demo加入契约类Contact来统一管理View和Presenter的接口,使得某一功能模块的接口能更加直观的呈现出来,这样做是有利于后期维护的。

另外这套MVP架构还为我们带来了一个额外的好处: 我们有了足够明确的开发规范和标准。细致到了每一个类应该放到哪个包下,哪个类具体应该负责什么职责等等。这对于我们的Code Review、接手他人的功能模块等都提供了极大的便利。前面提到的 MinimalistWeather就是为了定规范定标准而开发的。

这一时期我们还在项目中引入了RxJava,很好的解决了前面提到的嵌套回调的问题,同时能够帮助我们简化复杂业务场景下的代码逻辑(当然RxJava的好处远远不止这么一点,对RxJava不了解的同学可以去翻翻我之前 一系列关于RxJava的文章)。我们也将网络库升级到了Retrofit2+OKHttp3,它们和RxJava之间能更好的配合。

MVP带来的新问题及解决方案

是不是升级到了MVP架构就高枕无忧了呢?很明显不是这样!MVP架构也会带来以下新的问题:

  • 由于大量的业务逻辑处理转移到了Presenter层,在一些复杂的业务场景中Presenter同样会变得臃肿难懂。细心的同学可能注意到了前面的架构图中的Model层有个Data Repository模块,Data Repository在这里有两个作用:一是可以将原本由Presenter处理的部分逻辑转移到这里来处理,包括数据的校验、部分单纯只与数据相关的逻辑等等,向Presenter屏蔽数据处理细节,比如作为Presenter就不必关心Model层传递过来的数据到底是来至网络还是来至数据库还是来至本地文件等等;二是我们引入了RxJava,但是只有网络层中的Retrofit能返回Observable对象,其他模块都是返回的还是一些非Observable的Java对象,为了能在整个Presenter层中都体验RxJava带来的美妙之处,因此可以通过Data Repository做一层转换;

  • 现在的MVP架构中最重的部分就是Model Layer了,这一点从前面的架构图中就能体现。因此这就要求我们在Model层的设计过程中职责划分要足够清晰,分包更明确,耦合度更低。至于分包大家可以参考 MinimalistWeather的方案:db包为数据库模块、http包为网络模块、preference包是对SharedPreferences的一些封装、repository包就是前面提到的Data Repository模块;

  • 同时还有一点需要注意,很多人在使用RxJava的过程中往往忘记了对生命周期的管理,这很容易造成内存泄露。 MinimalistWeather中采用了CompositeSubscription来管理,你也可以使用RxLifecycle这类开源库来管理生命周期。

组件化与模块化

去年下半年我们Android团队内部成立了技术小组,基础组件的开发是技术小组很重要的一部分工作,所以组件化是我们正在做的事;模块化更多的是现有的方案受到来自业务上的挑战以及受到了Oasis Feng在MDCC上的分享和整个大环境的启发,现在正处于设计规划和demo开发的阶段。

组件化

组件化不是个新概念,通俗的讲组件化就是基于可重用的目的,将一个大的软件系统拆分成一个个独立组件。

组件化的带来的好处不言而喻:

  • 避免重复造轮子,节省开发维护成本;

  • 降低项目复杂性,提升开发效率;

  • 多个团队公用同一个组件,在一定层度上确保了技术方案的统一性。

现在的安居客有是三个业务团队:安居客用户app、经纪人app、集客家app。为了避免各个业务团队重复造轮子,团队中也需要有一定的技术沉淀,因此组件化是必须的。从本篇的第一节大家就能看到组件化的影子,只不过在这之前我们做的并不好。现在我们需要提供更多的、职能单一、性能更优的组件供业务团队使用。根据业务相关性,我们将这些组件分为:基础组件和业务组件。后面在介绍模块化的时候会有进一步的描述。

模块化

自从Oasis Feng在去年的MDCC2016上分享了模块化的经验后,模块化在Android社区越来越多的被提起。我们自然也不落俗的去做了一些研究和探索。安居客现在面临很多问题:例如全量编译时间太长(我这台13款的MacBook Pro打一次包得花十多分钟);例如新房、二手房、租房等等模块间耦合严重,不利于多团队并行开发测试;另外在17年初公司重新将租房app捡起推广,单独让人来开发维护一个三年前的项目并不划算,所以我们希望能直接从现在的安居客用户端中拆分出租房模块作为一个单独的app发布上线。这样看来模块化似乎是一个不错的选择。

所以我们做模块化的目的大致是这样的:

  • 业务模块间解耦

  • 单个业务模块单独编译打包,加快编译速度

  • 多团队间并行开发、测试

  • 解决好租App需要单独维护的问题,降低研发成本

15年Trinea还在安居客的时候开发了一套插件化框架,但受限于当时的团队规模并且插件化对整个项目的改造太大,因此在安居客团队中插件化并未实施下来。而模块化其实是个很好的过渡方案,将项目按照模块拆分后各业务模块间解耦的问题不存在了,后续如有必要,再进行插件化改造只不过是水到渠成的事。

来看看安居客用户app的模块化设计图:
clipboard.png

整个项目分为三层,从下往上分别是:

  • Basic Component Layer: 基础组件层,顾名思义就是一些基础组件,包含了各种开源库以及和业务无关的各种自研工具库;

  • Business Component Layer: 业务组件层,这一层的所有组件都是业务相关的,例如上图中的支付组件AnjukePay、数据模拟组件DataSimulator等等;

  • Business Module Layer: 业务module层,在Android Studio中每块业务对应一个单独的module。例如安居客用户app我们就可以拆分成新房module、二手房module、IM module等等,每个单独的Business Module都必须准遵守前面提到的MVP架构。

同时针对模块化我们也需要定义一些自己的游戏规则:

  • 对于Business Module Layer,各业务模块之间的通讯跳转采用路由框架Router来实现(可能会采用成熟的开源库,也可能会选择重复造轮子);

  • 对于Business Component Layer,单一业务组件只能对应某一项具体的业务,对于有个性化需求的对外部提供接口让调用方定制;

  • 合理控制各组件和各业务模块的拆分粒度,太小的公有模块不足以构成单独组件或者模块的,我们先放到类似于CommonBusuness的组件中,在后期不断的重构迭代中视情况进行进一步的拆分(这一点的灵感来源于 Trinea的文章);

  • 上层的公有的业务或者功能模块可以逐步下放到下层,合理把握好度就好;

  • 各Layer间严禁反向依赖,横向依赖关系由各业务Leader和技术小组商讨决定。

对于模块化项目,每个单独的business module都可以单独编译成APK。在开发阶段需要单独打包编译,项目发布的时候又需要它作为项目的一个module来整体编译打包。简单的说就是开发时是application,发布时是library。因此需要你在business module的gradle配置文件中加入如下代码:

if(isBuildModule.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}

如果我们需要把租房模块打包成一个单独的租房app,像下面这样就好:
clipboard.png

我们可以把Basic Component Layer和Business Component Layer放在一起看做是Anjuke SDK,新的业务或者项目只需要依赖Anjuke SDK就好(这一点同样是受到了 Trinea文章的启发)。甚至我们可以做得更极致一些,开发一套自己的组件管理平台,业务方可以根据自己的需求选择自己需要的组件,定制业务专属的Anjuke SDK。业务端和Anjuke SDK的关系如下图所示:
clipboard.png

最后看看安居客模块化的整体设计图:
clipboard.png

模块化拆分对于安居客这种比较大型的商业项目而言,由于历史比较久远很多代码都运行五六年了;各个业务相互交叉耦合严重,所以实施起来还是有很大难度的。过程中难免会有预料不到的坑,这就需要我们对各个业务有较深的理解同时也要足够的耐心和细致。虽然辛苦,但是一旦完成模块化拆分对整个团队及公司业务上的帮助是很大的。

以上是我的简单总结以及对模块化的一些思考,不足之处还望大家批评指正。后面模块化的demo完善后我会把它放到GitHub,并再出一篇文章详细介绍模块化的设计实现细节。

参考资料:

如果你喜欢我的文章,就关注下我的知乎专栏或者在GitHub上添个star吧!


快速解读GC日志

$
0
0

本文是 Plumbr 发行的 Java垃圾收集手册的部分内容。文中将介绍GC日志的输出格式, 以及如何解读GC日志, 从中提取有用的信息。我们通过 -XX:+UseSerialGC选项,指定JVM使用串行垃圾收集器, 并使用下面的启动参数让 JVM 打印出详细的GC日志:

-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps

这样配置以后,发生GC时输出的日志就类似于下面这种格式(为了显示方便,已手工折行):

2015-05-26T14:45:37.987-0200: 151.126: 
  [GC (Allocation Failure) 151.126:
    [DefNew: 629119K->69888K(629120K), 0.0584157 secs]
    1619346K->1273247K(2027264K), 0.0585007 secs] 
  [Times: user=0.06 sys=0.00, real=0.06 secs]

2015-05-26T14:45:59.690-0200: 172.829: 
  [GC (Allocation Failure) 172.829: 
    [DefNew: 629120K->629120K(629120K), 0.0000372 secs]
    172.829: [Tenured: 1203359K->755802K(1398144K), 0.1855567 secs]
    1832479K->755802K(2027264K),
    [Metaspace: 6741K->6741K(1056768K)], 0.1856954 secs]
  [Times: user=0.18 sys=0.00, real=0.18 secs]

上面的GC日志暴露了JVM中的一些信息。事实上,这个日志片段中发生了 2次垃圾回收事件(Garbage Collection events)。其中一次清理的是年轻代(Young generation), 而第二次处理的是整个堆内存。下面我们来看,如何解读第一次GC事件,发生在年轻代中的小型GC(Minor GC):

2015-05-26T14:45:37.987-02001: 151.1262:[ GC3( Allocation Failure4)
151.126: [ DefNew5: 629119K->69888K6(629120K)7
, 0.0584157 secs] 1619346K->1273247K8(2027264K)9, 0.0585007 secs10]
[Times: user=0.06 sys=0.00, real=0.06 secs]
11

  1. 2015-05-26T14:45:37.987-0200– GC事件(GC event)开始的时间点.
  2. 151.126– GC时间的开始时间,相对于JVM的启动时间,单位是秒(Measured in seconds).
  3. GC– 用来区分(distinguish)是 Minor GC 还是 Full GC 的标志(Flag). 这里的 GC表明本次发生的是 Minor GC.
  4. Allocation Failure– 引起垃圾回收的原因. 本次GC是因为年轻代中没有任何合适的区域能够存放需要分配的数据结构而触发的.
  5. DefNew– 使用的垃圾收集器的名字. DefNew这个名字代表的是: 单线程(single-threaded), 采用标记复制(mark-copy)算法的, 使整个JVM暂停运行(stop-the-world)的年轻代(Young generation) 垃圾收集器(garbage collector).
  6. 629119K->69888K– 在本次垃圾收集之前和之后的年轻代内存使用情况(Usage).
  7. (629120K)– 年轻代的总的大小(Total size).
  8. 1619346K->1273247K– 在本次垃圾收集之前和之后整个堆内存的使用情况(Total used heap).
  9. (2027264K)– 总的可用的堆内存(Total available heap).
  10. 0.0585007 secs– GC事件的持续时间(Duration),单位是秒.
  11. [Times: user=0.06 sys=0.00, real=0.06 secs]– GC事件的持续时间,通过多种分类来进行衡量:
    • user– 此次垃圾回收, 垃圾收集线程消耗的所有CPU时间(Total CPU time).
    • sys– 操作系统调用(OS call) 以及等待系统事件的时间(waiting for system event)
    • real– 应用程序暂停的时间(Clock time). 由于串行垃圾收集器(Serial Garbage Collector)只会使用单个线程, 所以 real time 等于 user 以及 system time 的总和.

通过上面的分析, 我们可以计算出在垃圾收集期间, JVM 中的内存使用情况。在垃圾收集之前, 堆内存总的使用了 1.54G (1,619,346K)。其中, 年轻代使用了 614M(629,119k)。可以算出老年代使用的内存为: 967M(990,227K)。

下一组数据( ->右边)中蕴含了更重要的结论, 年轻代的内存使用在垃圾回收后下降了 546M(559,231k), 但总的堆内存使用(total heap usage)只减少了 337M(346,099k). 通过这一点,我们可以计算出, 有 208M(213,132K) 的年轻代对象被提升到老年代(Old)中。

这个GC事件可以用下面的示意图来表示, 上方表示GC之前的内存使用情况, 下方表示结束后的内存使用情况:

这里写图片描述

如果你想学习更多, 请查看完整的 Java垃圾收集手册, 本示例是从其中抽取的。

原文链接: Understanding Garbage Collection Logs

翻译日期: 2015年10月18日

翻译人员: 铁锚 http://blog.csdn.net/renfufei

作者:renfufei 发表于2015/10/18 21:00:27 原文链接
阅读:324 评论:0 查看评论

Java GC 调优

$
0
0

关于 Java GC 已经有很多好的文档了, 比如这些:

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

http://blog.csdn.net/fenglibing/article/details/6321453

但是这里还是想再重点整理一下 Java GC 日志的格式, 可以作为实战时的备忘录.

同时也会再整理一下各种概念

一, JDK 6 提供的各种垃圾收集器

先整理一下各种垃圾收集器.

垃圾收集器:

  • 新生代收集器: Serial, ParNew, Parallel Scavenge (MaxGCPauseMillis vs. GCTimeRatio 响应时间 vs. 吞吐量)
  • 老生代收集器: Serial Old, Parallel Old, CMS

垃圾收集器搭配总结:

  • CMS 只能配 Serial 或 ParNew
  • Parallel Scavenge 只能配 Serial Old 或 Parallel Old
  • Serial 不能配 Parallel Old

组合起来有以下几种:

  1. Serial + Serial Old ( UseSerialGC): GC 线程在做事情时, 其他所有的用户线程都必须停止 (即 stop the world)
  2. Serial + CMS: 一般不会这样配合使用
  3. ParNew + CMS ( UseConcMarkSweepGC): 新生代的 GC 使用 ParNew, 有多个 GC 线程同时进行 Minor GC (主要是在多核的环境用多线程效果会好); 而老生代使用 CMS (CMS 后面会重点讲)
  4. ParNew + Serial Old ( UseParNewGC): 新生代用 ParNew 的时候, 也可以选择老生代不用 CMS, 而用 Serial Old (实际上, 这个组合也不太常用)
  5. Parallel Scavenge + Serial Old ( UseParallelGC): Parallel Scavenge 收集器的目的是达到一个可控制的吞吐率 (适用于各种计算任务); 这个组合中老生代仍旧使用 Serial Old
  6. Parallel Scavenge + Parallel Old ( UseParallelOldGC): 新生代使用 Parallel Scavenge, 而 Parallel Old 是老年代版本的 Parallel Scavenge

这里要注意一下 UseParallelGC vs. UseParallelOldGC, 如果没有调好配置, UseParallelOldGC 有可能比 UseParallelGC 的性能还要差 (参考: http://softwareopolis.blogspot.com/2012/12/jvm-tuning-useparalleloldgc.html)

总结下来, 有 3 种场景:

  • 客户端程序: 一般使用 UseSerialGC (Serial + Serial Old). 特别注意, 当一台机器上起多个 JVM, 每个 JVM 也可以采用这种 GC 组合
  • 吞吐率优先的服务端程序: UseParallelGC (或者 UseParallelOldGC)
  • 响应时间优先的服务端程序: UseConcMarkSweepGC

二, Java 性能调优步骤

预先设定调优目标 -> 部署方案 (单 JVM 部署 vs. 多 JVM 部署) -> 32位 or 64位 -> 优化内存占用 -> 优化响应时间 -> 优化吞吐率

优化需要重点考量的 3 个因素:

  • 内存占用
  • 响应时间
  • 吞吐率

调优过程中的 3 原则:

  • Minor GC 尽可能多的回收新生代对象
  • 只要条件允许, 尽可能开大 JVM 的堆大小
  • 优化中需要重点考量的 3 个因素, 只对其中的 2 个重点调优

三, 内存占用调优

  • 打开 GC 日志

  • Xms 和 Xmx 的值设置一致

  • Xmn 设置为新生代的值

  • 应用运行稳定以后, 观察老生代和永生代所占的空间大小, 即 Live Data Size

利用 Live Data Size 估算调优开始时的各项配置:

  • JVM 堆总大小: 3 - 4 倍 老生代的 Live Data Size
  • 永生代大小: 1.2 - 1.5 倍 永生代的 Live Data Size
  • 新生代大小: 1 - 1.5 倍老生代 Live Data Size
  • 老生代大小: (堆总大小 - 新生代大小), 即 2 -3 倍 老生代 Live Data Size

四, 响应时间调优

调优响应时间可能的方法:

  • 优化程序
  • 调整部署 (单 JVM 或者 多 JVM)
  • 减小对象分配, 降低 GC 频率

调优步骤:

监控 Minor GC 响应时间频率 -> 调整新生代大小:

  • Minor GC 频率高 -> 增大新生代
  • Minor GC 时间长 -> 减小新生代

监视 Full GC 响应时间频率 -> 调整老生代大小:

  • 老生代空闲空间多长时间被填满决定了 Full GC 的频率 (空闲空间 = 老生代总空间 - Live Data Size)
  • 填满空闲空间的速度由提升率决定
  • 例如, 每次 Minor GC 提升 20M, 200M 的空闲空间 10 次 Minor GC 后会被填满
  • 如果发现 Full GC 过于频繁, 增大老生代大小 (同时保持新生代大小不变)
  • 但增大老生代大小会增加 Full GC 的时间. 所以需要对老生代大小进行调优. 如果调不好, 可以考虑用 CMS 替代 PS

五, CMS

CMS 的细节可以参考: http://blog.griddynamics.com/2011/06/understanding-gc-pauses-in-jvm-hotspots_02.html

这里只着重整理一下调优相关的东东.

  • CMS 可以不做 compacting, 所以可能避免 stop-the-world 的 compacting 过程
  • 调优 CMS 的目的, 就是避免 stop-the-world 的 compacting 过程发生 (实际很难做到)
  • CMS 可以和应用程序线程并行的运行, 也就是在应用程序运行的同时回收老生代空间, 避免老生代用完
  • 但一旦老生代真的用完了, 就会发生恐怖的 stop-the-world 的 compacting GC
  • 从 PS 迁移到 CMS 时, 最好先把老生代的大小扩大 20% - 30%
  • 老生代不做 compacting, 所以老生代的内存碎片率特别关键. 有 2 个方法可以减轻内存碎片率: 1) 增大内存 2) 优化新生代到老生代的提升率

1. PrintTenuringDistribution

先介绍一下对象 age的概念. JVM 中的一个对象新被创建时 age 是 0; 之后每次 Minor GC 后, 这个对象如果还在新生代中, 这个对象的 age 数加一.

通过一个调优例子来说明 PrintTenuringDistribution 的用法

打开 JVM 的参数:

-XX:+PrintTenuringDistribution

这样 gc.log 会有 Tenuring 相关的信息. 例如:

Desired survivor size 8388608 bytes, new threshold 1 (max 15)
- age 1: 16690480 bytes, 16690480 total

  • threshold 1表示对象在新生代中存活的阈值是 1. 也就是说, 一旦一个对象的 age 大于 1, 这个对象会晋升到老年代.
  • age 1: 16690480 bytes: 表示在新生代中年纪是 1 的对象大小共有 16690480 bytes
  • Desired survivor size 8388608 bytes表示当前时刻, 需要的目标 survivor 空间为 8388608 bytes

简单点说, 从这个输出, 我们发现, 当前时刻 survivor 目标空间只有 8388608 bytes, 而小于实际占用的 16690480 bytes 的空间. 因此, 我们需要扩充 survivor 空间的大小.

需要把 survivor 空间扩大到多大呢?

需要 16690480 bytes 的空间来装入所有 age 为 1 的对象. 而 CMS 会用一个比率来估算需要分配多少 survivor 目标空间 (这个比率的默认值是 50%). 所以, survivor 空间需要扩充到: 16690480 / 50% = 33,380,960 bytes

结论: 把 survivor 空间的大小扩容到 33,380,960 bytes (大约 32M) 是合适的

这里再详细说明一下前面提到的 CMS 用来估算 survivor 目标空间占用的那个比率.

这个比率默认值是 50 (代表50%). 可以通过设置 JVM 的参数来配置, 例如:

-XX:TargetSurvivorRatio=90

不过一般来说, 极少情况需要配置这个值, 默认的 50 就 OK 了.

总之, 我们通过打开 PrintTenuringDistribution 获取更多的 GC 信息来优化对象从新生代到老生代的提升率, 以及优化 Minor GC 的响应时间.

2. 避免老生代的 stop-the-world

要避免老生代的 stop-the-world 就是要保证 CMS 回收的内存的速度比从新生代晋升到老生代的速度快. 这样才能保证老生代不被填满而造成 stop-the-world.

要保证 CMS 回收的速度, 要靠两个因素来保证:

  • 老生代越大, 发生 stop-the-world 的概率越低
  • CMS 开始的时机越早, 发生 stop-the-world 的概率越低

对第一个因素, 我们尽量给老生代分配更多的空间就行了.

下面详细说说第二个因素.

GC 参数中有一个:

-XX:CMSInitiatingOccupancyFraction

默认值是68. 其含义是, 每次当老生代的总空间的 68% 被占用的时候, 就进行 CMS.

这个值调得越小, CMS 就会越早发生, 发生 stop-the-world 的概率就会更小;

这个值调得越大, CMS 就会越晚发生, 发生 stop-the-world 的概率就会更大.

所以我们可以通过调节这个值来调节 CMS 发生的时机, 避免 stop-the-world 的发生. 但是也不能调的太小了, 太小了的话, 会触发不必要的 CMS, 降低了吞吐率

另外需要注意, -XX:CMSInitiatingOccupancyFraction 要和 -XX:+UseCMSInitiatingOccupancyOnly 参数一起使用才会生效

六, 吞吐率

在响应时间调好以后, 可以考虑调吞吐率. 这里主要讲一下 CMS 吞吐率的调优方法:

  • 增加新生代的大小
  • 增加老生代的大小
  • 调整 eden 和 survivor 的比率
  • 调整 CMSInitiatingOccupancyFraction 参数

注意: 以上 4 个调整方法需要和响应时间的调整做 trade off

七, 其他一些可以考虑调整的 JVM 参数:

  • -XX:PermSize
  • -XX:MaxPermSize
  • -XX:+PrintGCDetails
  • -XX:+PrintGCDateStamps
  • 打开或关闭 DisableExplicitGC
  • -XX:+PrintTenuringDistribution
  • -XX:+CMSParallelRemarkEnabled
  • -XX:CMSMaxAbortablePrecleanTime
  • -XX:ParallelGCThreads=
  • -XX:+UseNUMA
  • -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
  • -XX:+CMSScavengeBeforeRemark

另外, 需要特别注意 -XX:+ParallelRefProcEnabled 这个参数的使用, 在有些 JDK 版本, 设置了这个参数会导致程序 hang 住 (具体参考: http://www.codelve.com/archives/145 )

八, Reference

  • http://hllvm.group.iteye.com/group/topic/27945
  • << Java Performance >> Charlie Hunt, Binu John

系统性能监控之 Swap Space

$
0
0

对线上服务器进行性能监控时, 需要关注各种性能指标, 从各个方面来对系统性能进行监控. 例如, 系统负载, cpu 占用, 内存占用, 网络带宽等. 其中 Swap Space 的使用状况也是值得关注的一项, 本文对在 Linux 环境中监控 Swap Space 的相关内容进行了总结.

一, 首先简单的介绍一下什么是 Swap Space

用户进程内存空间中数据有两种:

  1. 从文件系统中读进来的数据 (主要有文件内容高速缓存, 程序代码和共享库)
  2. 程序使用的堆栈空间

而Linux 是一个分页请求系统, 用户进程使用的所有内存需要映射到物理内存:

  1. 应用程序的地址空间是虚拟空间, 是按被分成固定大小的页 (page) 来管理的
  2. 物理内存也是按固定大小的页框 (page frames) 来组织
  3. 虚拟页面 (page) 需要由操作系统映射到物理内存中的页框 (page frames)

为了更好的利用物理内存, Linux 会对在物理内存中的页面进行回收

  • 如果存放的是程序代码或共享库, 可以直接回收 (以后需要的时候可以从文件里面直接读入)
  • 如果缓存的是文件内容, 如果是脏页, 先写回磁盘后再回收; 如果不是脏页可以直接回收
  • 而对于程序使用的堆栈空间, 由于没有对应的文件 (叫做 "匿名页", anonymous pages), 只能是备份到磁盘上一块专门的分区, 这个专门的分区就是 Swap Space.
  • 一旦回收算法既不能从文件缓存回收内存, 又不能从正在使用的匿名页中回收内存, 而系统需要满足更多的内存请求, 这个时候只能用最后一招了: OOM kill

从 swap space 的原理得出以下结论:

  • Swap Space 不会拖慢系统. 事实上, 不分配 swap space 并不代表就没有换进换出发生 (非匿名页还是可能会被换进换出).有了 Swap Space, 只是说在试图回收内存时, Linux 有了更多的选择
  • Swap Space 只是匿名页专用的. 在任何情况下, 程序代码, 共享库, 文件系统cache不会使用 Swap Space
  • 由上面的 1, 2 两点可以知道: "尽量给 Swap Space 分配很小的空间" 唯一的好处就是不浪费磁盘空间 (也就是说最小化 Swap Space 大小并不能提升系统性能)
  • 系统监控时, 要特别注意是否发生了 OOM 的情况, 一旦 OOM 发生, Linux 会自己挑选一个消耗内存较大, 又不 "重要" 的程序 kill 掉来回收内存 (但很多时候被 kill 掉的正好是某个应用程序)

二, 对 Swap Space 进行监控

先利用 free 命令来监控 swap space 的使用率. 例如:

img

最后一行显示 swap space 的使用状况:

  • total 显示 swap space 的总容量 (2G)
  • used 显示已用空间大小 (43M 左右)
  • free 显示空闲空间大小 (1.95G 左右)

对系统进行监控时, 可以监控 used占用了total的比例.

但是特别需要注意的是, 这个比值较高时, 并不等于系统内存紧张了. 只有当在 Swap Space 上有大量的换入换出操作时才说明出现了内存紧张.

例如, 某个程序启动时使用了大量的内存, 造成系统的 Swap Space 消耗过大. 但启动完成之后, 进入平稳运行期时, 并不需要很多内存 (从这个例子也可以看出来, Swap Space 分配过小可能造成有时候本来可以启动起来的程序启动不起来).

可以用 vmstat 或 sar -W 命令来监控换入换出操作的状况

例如, 使用 sar -W:

img

或者使用 vmstat:

img

在这个例子里面, 可以看出, 虽然 Swap Space 被使用了一点, 但是没有在 Swap Space 上的换入换出, 所以系统性能还是不错的.

总之, 系统性能可能出现以下几种情况:

  • 极好: Swap Space 没有被使用. 物理内存的使用也很少.
  • 好: Swap Space 用了一点, 但是在 swap space 上没有换进换出的操作
  • 还行: Swap Space 用了一点. 有少量的在 Swap Space 上的 换进换出的操作. 不过系统的吞吐量还是不错 (CPU在 User 上的消耗还比较高, 也就是说明此时在 Swap Space 上的换入换出还没有造成系统瓶颈)
  • 有问题: 在 Swap Space 上有大量的换入换出操作, 同时 CPU 在 Sys 和 Wait 上的消耗很高 (这种情况就是说明内存不够用了, 需要查看是否有程序发生了内存泄漏)
  • 有问题: 系统运行良好, 但是启动新程序会由于内存不足而失败 (这种情况是 Swap Space 分配得太小了)

三, 和 Swap Space 相关的系统参数的调节

vm.swappiness (缺省值是60). 设置成0, Linux 会尽可能的避免把内存交换出去 (可以让系统有好的响应时间). 相反, 如果设置成100, Linux 会尽量的使用 swap space.

vm.overcommit_memory (缺省值是0):

  • 0: 允许 overcommit (当收到申请内存的请求时, Linux 用一套heuristic方法来决定是否要 overcommit), 但是就像前面提到的, overcommit 之后, 当真正需要用到这些内存时, 如果回收算法又不能回收到任何物理内存, 只好OOM kill 掉一个进程
  • 1: 总是允许 overcommit (Redis 就建议这样设置)
  • 2: 不允许进行 overcommit, 一旦申请的空间超出总空间 (总空间 = 物理空间 + swap space size x overcommit_ratio, overcommit_ratio 缺省值是 50%), 就报错

参考

  • http://www.linuxjournal.com/article/10678
  • http://hi.baidu.com/_kouu/item/4c73532902a05299b73263d0

你转的每一篇朋友圈热文,都是你缴纳的智商税

$
0
0


图片来自周星驰《西游降魔篇》    

朋友圈的刷屏就像是冬天里的静电,总在你猝不及防的时候突然来一发。这两天刷屏的是《就算老公一毛钱股份都没拿到》。故事很老梗,就是功成名就的CEO,甩掉老臣,后者老婆看不过,于是写了一篇控诉贴。总阅读量应该早已经数百万。 

于是,朋友圈被点燃了,网友们纷纷化身正义的朝阳群众,纷纷口诛笔伐这个忘恩负义、丧尽天良、人面兽心、人人得而诛之的、比江南皮革厂厂长黄鹤还要王八蛋的CEO。但是高潮比周杰伦口中的龙卷风还来得有些猝不及防,很快,剧情开始反转,有说男主工作能力不是一般差的,有说分红不是100万而是高达税后200万+,有说女主就是心机婊的,他们并不穷,他们买了奔驰,去毛里求斯度蜜月,结婚婚纱穿的是Vera Wang。 

Vera Wang啊!美帝最有权势的家族之一克林顿、希拉里的女儿结婚穿的婚纱品牌。于是,穿着优衣库,攥着美团优惠券,情人节晚上都要去刷今夜特价酒店、刚刚泪水涟涟的朝阳群众们怒了。老子结婚只能穿隔壁王大妈做的衣服,还把约P的钱都打赏给你了,你却给我看这个。

朝阳群众再次倒戈, 纷纷讨伐这个招摇撞骗沽名钓誉他们眼中口中的绿茶婊。一圈折腾下来,比约个炮还辛苦。"MD, 又上当了! 咦, 我为什么要说又呢?"

就在刚刚过去不久的罗一笑事件中,相同的套路和戏码刚刚落幕。其实类似的情节已经重复了不止一遍一遍又一遍。张小龙同学估计都得让微信专门准备一批服务器,用来盛放这些热贴和群众们的回复。但是,朝阳群众们乐此不疲,他们就像传说中的金鱼的记忆力一样,每隔7秒就忘了之前的遭遇。 

张小龙同学估计没有想到,他发明的朋友圈,本来是给金鱼们发发吃喝玩乐的生活日常,最多是装装逼,晒晒虚荣心的个人小花园。没想到却成了金鱼们个人智商最好的测试仪。每一篇热贴,就成了他们纷纷缴纳的智商税。足够绕地球八百圈。 

每次遇到这样的事,朝阳群众必然会上演同样的戏码:流泪、声讨、转发、打赏、吃惊、骂街、删帖。只有集齐了这七龙珠,朴实的群众们才可以呼唤自己心中的G点。伴随着他们的泪水和打赏,大脑里的小跳蛋急促的刺激着他们心理高潮的G点。“我是一个多么正义的boy或秃头大叔啊”“我是一个多么有爱的girl或中年妇女啊。” 

每一次热贴刷屏,都伴随着一波朝阳群众的热泪:“我在地铁上看哭了。”“我是边哭边转的。”“我今天哭的都上不了班了(罗一笑事件回帖)”。为啥群众们这么喜欢哭啊?看新闻哭,看选秀哭,看电影哭,看热贴还是哭,群众们,得了沙眼要早治疗啊,出门左拐,莆田系眼科医院。“为什么我的眼里常含泪水? 因为我对这朋友圈爱得深沉……  ”但为啥事件翻转后你还哭呢?“为什么我的眼里又含泪水? 因为我又TM被骗了……  ”

截图来自于《一毛钱》回帖                                

朝阳群众还有个癖好就是打赏。一言不合就打赏,这充分说明了挺直了腰杆的中国人民站起来了,这是群众们翻身做主人之后发家致富的铁证。这次热贴打赏超过8000次。上一次罗一笑事件,在原贴并没有任何要求募捐的情况下。群众们纷纷打赏发钱,最后竟然冲垮了微信设定的每天五万的上限规则。直奔数百万。这就像是热心群众上街去ATM捐钱,结果捐钱的人太多,直接导致了整个银行的系统瘫痪。这种情况在全世界也是独一无二的。 

估计张小龙同学第二天听到下属汇报时,脸吃惊得就像是国外百货公司的售货员看到刷卡的东北大妈一样,“龟儿子的,忒有钱了。”也许,在朝阳群众眼中,打钱就是最好的爱的供养和表白吧。

乐善好施无论是在古今中外,都绝对是个人美德。但给热贴打赏不一样,尤其是给有几套房或者穿着Vera Wang开着奔驰的贴主打赏。这就像当年网民听到深圳市政府用纳税人的钱,给身价数百亿的互联网大佬每个月3000块住房补贴有异曲同工之妙。如果世界上有一件很操蛋的事,那就是你被一个假故事骗了同情和眼泪。如果世界上还有一个更操蛋的事,那就是一个比你有钱的人,不仅骗了你的泪,还骗了你的钱。这种十万点伤害把刚刚还在挤得像鸡笼一样的地铁上,伸长脖子挂着眼睛哭花了妆的群众万箭穿心,刚刚充满了同情和爱心的小心脏瞬间射得像个刺猬一样。 

勒庞在那本广为人知的《乌合之众》里说:群体中的个人不但在行动上和他本人有着本质的差别,甚至在完全失去独立性之前,他的思想和感情就已经发生了变化,这种变化是如此深刻,它可以让一个守财奴变得挥霍无度,把怀疑论者改造成信徒,把老实人变成罪犯,把懦夫变成豪杰。 

朋友圈的智商税还会继续交下去,正如那位伟人说的,“坚持基本路线不动摇”,因为在我们的教育和经验里,独立人格和思考的那一页就是开着天窗的空白页,哪怕是那些早已经物质自由拎着LV穿着高级套装的白领(白痴)们。 

最后,送给多水多金的群众们一句莎士比亚在《麦克白》中话:“醒来啊,麦克白,把沉睡赶走!”

南七道:南七道新媒创始人,虎嗅年度作者,互联网明星创业公司脸萌、FaceU等品牌操盘手,关注互联网和科技创业。

垃圾回收原来是这么回事

$
0
0

  最近想复习一下JVM的知识。然后发现网上不少文章在写JVM的垃圾回收算法时,都比较偏向于具体实现,而少有站在更高角度来看垃圾回收算法的文章。而我本人想对垃圾回收算法有个全景的认识,所以,就找到了这本《垃圾回收的算法与实现》(以下简称《垃圾回收》)。本篇博客就是尝试对“全景”的总结。

  以下为方便讨论,垃圾回收缩写成GC。

  为什么要有GC

  我时而听到C++程序员说我们是被GC惯坏了的一代。的确是这样的,我本人在学习GC算法时,大脑里第一问题就是为什么需要GC这样的东西。说明我已经认为GC是理所当然了。

  总的一句话:没有GC的世界,我们需要手动进行内存管理,而手动内存管理是纯技术活,又容易出错。

  既然我们写的大多程序都是为了解决现实业务问题,那么,我们为什么不把这种纯技术活自动化呢?但是自动化,也是有代价的。 这是我的个人理解,不代表John McCarthy本人的理解

  “垃圾”的定义

  首先,我们要给个“垃圾”的定义,才能进行回收吧。书中给出的定义:把分配到堆中那些不能通过程序引用的对象称为非活动对象,也就是死掉的对象,我们称为“垃圾”。

  GC的定义

  因为我们期望让内存管理变得自动(只管用内存,不管内存的回收),我们就必须做两件事情: 1. 找到内存空间里的垃圾;2. 回收垃圾,让程序员能再次利用这部分空间 。(《垃圾回收》 P2)只要满足这两项功能的程序,就是GC,不论它是在JVM中,还是在Ruby的VM中。

  但这只是两个需求,并没有说明GC应该何时找垃圾,何时回收垃圾等等更具体的问题,各类GC算法就是在这些更具体问题的处理方式上施展手脚。

  GC的历史

  John McCarthy身为Lisp之父和人工智能之父,同时,他也是GC之父。1960年,他在其 论文中首次发布了GC算法(其实是委婉的提出)。

  《垃圾回收》的作者认为:

从50年前GC算法首次发布以来,众多研究者对其进行了各种各样的研究,因此许多GC算法也得以发布。但事实上,这些算法只不过是把前文中提到的三种算法进行组合或应用。也可以这么说,1963年GC复制算法诞生时,GC的根本性内容就已经完成了。(《垃圾回收》 P4)

  那我们常常听说的分代垃圾回收又是怎么回事?作者是这样说的:人们从众多程序案例中总结出了一个经验:“大部分的对象在生成后马上就变成了垃圾,很少有对象能活得很久”。分代垃圾回收利用该经验,在对象中导入了“年龄”的概念,经历过一次GC后活下来的对象年龄为1岁。(垃圾回收》 P141)

分代垃圾回收中把对象分类成几代,针对不同的代使用不同的GC算法,我们把刚生成的对象称为新生代对象,到达一定年龄的对象则称为老年代对象。(《垃圾回收》 P142)

  好了,这下我总算知道为什么要分代了,我的总结是: 将对象根据存活概率进行分类,对存活时间长一些的对象,可以减少扫描“垃圾”的时间,以减少GC频率和时长。 根本思路就是对对象进行分类,才能针对各个分类采用不同的垃圾回收算法,以对各算法进行扬长避短。

  留一个问题给读者:我们知道分代垃圾回收所采用的堆结构是:

  为什么新生代空间要分成“生成空间”和“幸存空间”,而幸存空间又分成两块大小相等的幸存空间1,幸存空间2?

  这些GC算法共同解决的问题

  上面我们说了,GC的定义只给出了需求,三种算法都为实现这个需求,那么它们总会遇到共同要解决的问题吧? 我尝试总结出:

  • 如何分辨出什么是垃圾?
  • 如何、何时搜索垃圾?
  • 如何、何时清除垃圾?

  这样,只要涉及到垃圾回收,我就可以从这2点需求,3个共同问题(两点三共)出发来讨论、学习。

  如何评价GC算法?

  如果没有评价标准,我们当然无法评估这些GC算法的性能。作者给出了4个标准:

  • 吞吐量: 单位时间内的处理能力
  • 最大暂停时间:GC执行过程中,应用暂停的时长。较大的吞吐量和较短的最大暂停时间不可兼得
  • 堆的使用效率:就是堆空间的利用率。可用的堆越大,GC运行越快;相反,越想有效地利用有限的堆,GC花费的时间就越长。
  • 访问的局部性:把具有引用关系的对象安排在堆中较近的位置,就能提高在缓存中读取到想利用的数据的概率。

  好吧。两点三共,四标~

  小结

  搞清楚为什么要GC,要实现GC都要解决什么问题,而各类算法又是怎么解决的,最后怎么评价这些算法。GC原来是这么回事。

  但是这不是GC的全部。但是提供我一个思考GC的思考框架。

  以上就是《垃圾回收的算法与实现》的读书笔记。如果想更深入,可以阅读《垃圾回收算法手册:自动内存管理的艺术》。

2016 移动应用质量大数据报告

$
0
0

2016年,在“互联网+”战略的推动下,移动互联网与越来越多传统行业的结合更加紧密,用户使用移动互联网的工作场景、生活场景、消费场景都在悄然发生着改变, 移动互联网产品在智能硬件、医疗、汽车、旅游、教育等市场也都在不断探索新的可能性。

开发者除了需要关注用户需求外,更需要重视产品质量与口碑,这将极大的影响用户对产品的评价及产品后续的传播力度。

下面我们将为您带来2016年度 Android & iOS 移动应用质量大数据报告,让您清晰了解行业动态,精准定位自身产品位置。

 •  移动设备总数达10.7亿

2016年移动设备总数持续攀升, Android 设备总数从2015年的6.6亿提升至7.3亿, iOS设备总数从2015年的3.0亿提升至3.4亿。

 •  iOS应用数突破220W

截至2017年2月,iOS 应用数量突破220万,Android 应用数量约300万+。

 •  游戏崩溃率远高于应用崩溃率

无论是Android还是iOS,游戏的崩溃率都远高于应用,在iOS中的差异尤为明显。


接下来让我们从Android开始,从多个维度进行应用质量分析。

 •  应用分类中视频、社交等行业崩溃率较高

Android应用行业整体崩溃率在2.0%~3.6%之间。其中视频、社交、音乐类应用的崩溃率较高,出行、新闻、儿童类应用的崩溃率较低。

 •  游戏行业崩溃率普遍在4%以上

Android游戏行业崩溃率在4.3%~7.3%之间,八个游戏大类中,角色扮演、经营策略以及网络游戏的崩溃率均在7%以上,休闲益智、体育竞速崩溃率保持在5%以下。

 •  中小规模产品崩溃率更高

根据产品规模日活(DAU)区间分析崩溃率,产品规模越大,崩溃率越低。DAU达百万级别的产品崩溃率平均在1.5%以下,对比各DAU区间崩溃率,游戏崩溃率均大于应用。

 •  Android 应用平均ANR率在1%以下

出现ANR将弹出对话框,严重影响用户体验,所以发现应用ANR率上升情况也不容小觑。 游戏相比应用一般需要用到更多的资源,占用更多的内存和CPU,游戏 ANR发生几率约为应用的1.5倍。

 •  华为市场占比第一,OPPO和VIVO跻身前五

华为2016年国内市场存量以17%的市场占有率荣获第一,OPPO,VIVO跻身前五。相比之下,小米和三星在2016年市场份额有所下降。五大厂商的市场存量占整个Android市场的66.20%,共同瓜分了庞大的国内市场。

机型崩溃率方面(统计机型上发生的崩溃),主流厂商基本保持在2%左右。而联想和酷派机型的崩溃率超过3%,根据数据分析其崩溃原因,我们发现,Java类型的崩溃中除空指针异常外,出现最多的是ActivityNotFoundException和ClassNotFoundException异常,兼容性问题较多;从崩溃时间来看,联想和酷派机型上闪退(崩溃发生在启动后0~5s)的占比高达28%(平均比例为22%),说明应用开发者对这两个厂商机型的适配力度相对较弱。

 •  Android 系统版本5.1超过4.4成为主流

Android 5.1系统已成为主流版本,其中5.1、4.4、6.0、5.0版本的市场占有率分列前四,共占市场总量的83.39%,建议开发者优先关注这四个系统版本的适配情况。

另外5.1,6.0系统版本机型上的崩溃率在2%以下,而2.3系统版本崩溃率高达9.17%,大部分应用已放弃对2.3系统版本的适配,Google去年也宣布将在2017年正式结束对Android 2.3/3.0的系统支持。

 •  空指针异常在Java代码中最为常见

不出所料,NullPointerException依然是最常见的Java异常,该异常影响面广但容易修复,开发者想快速降低崩溃率可以优先解决此类异常。相较于2015年,IllegalStateException从5%提升至10%,OutOfMemoryError从3%提升至6%。

 •  SIGSEGV占Native崩溃类型的78%

从崩溃总数来看,Native崩溃数约为Java的1.4倍,而Natvie崩溃中高达78%为SIGSEGV异常(SIG 是信号名的通用前缀, SEGV 是 segmentation violation 的缩写,常见于内存错误)。

 •  Android应用闪退现象更加严重

相较于2015年, 闪退(启动后0~5s内崩溃)问题占比提升10个百分点(12%提升到22%),Android系统由于其碎片化特征,厂商、机型、系统版本、分辨率等适配问题,使得应用闪退现象更加严重。

 •  造成闪退最常见的几种异常原因

根据数据分析,导致闪退问题除空指针异常外,找不到类和方法、权限问题、so加载异常、状态异常等问题比较明显,71%的闪退是由这5种问题引起,机型适配测试可以有效降低闪退问题的发生。


了解完Android,我们再看看iOS平台上的应用质量有何差异


 •  iOS应用不同行业间崩溃率差距较大

iOS应用行业整体崩溃率在1.6%~4.6%之间,崩溃较低的行业如导航、商品指南类应用保持在2%以下,体育、社交、图书行业崩溃率在4%以上。

 •  游戏行业崩溃率相对较高

iOS游戏崩溃率在4.2%~9.9%之间,其中体育竞技类游戏的崩溃率最高。

 •  不同日活(DAU)区间游戏产品的崩溃率差距较大

不同DAU区间游戏产品的崩溃率分化更加严重, DAU低于1千的游戏,平均崩溃率高达9%。DAU达百万级的游戏产品基本属于精品游戏阵营,这个区间内的游戏崩溃率得到了有效控制。

 •  游戏卡顿发生概率高

游戏的卡顿发生率高达到19.9%,苹果官方并没有提供iOS卡顿的具体定义,Bugly官方定义的iOS卡顿默认为:3s无响应,卡顿问题会影响用户体验产品的流畅性,同样需要重视。

 •  主流机型崩溃率基本在2%以下

iPhone 6占比为21.81%,与2015相比差距不大(2015年占比:21.44%),iPhone 6系列机型仍然是市场主流机型,iPhone 7系列暂时只占总量的7.81%。 iOS主流机型的崩溃率基本保持在2%以下,新机型崩溃率相对更低。iPhone系列机型中,iPhone 4与iPhone 5的崩溃率最高,在3%以上。

 •  iOS 10.2成为主流系统版本

随着版本的升级迭代,iOS 10.X已迅速成为主流系统版本,占市场总量的49.49%。 iOS主流系统版本崩溃率基本上都在2%以下,并且高版本如9.X和10.X系列的应用崩溃率相对较低。

 •  NSInvalidArgument异常占OC异常的75%

对比2015年的占比,NSInvalidArgument异常从49%提升至75%,NSRangeException从19%下降至6%。

 •  运行中的崩溃问题大幅缩减

相较2015年数据,运行中的崩溃(运行60s以上闪退)问题占比,从78%下降至65%,缩减了13个百分点。

springboot集成shiro 实现权限控制

$
0
0

shiro

apache shiro 是一个轻量级的身份验证与授权框架,与spring security 相比较,简单易用,灵活性高,springboot本身是提供了对security的支持,毕竟是自家的东西。springboot暂时没有集成shiro,这得自己配。

shiro 内置过滤器

请看博文:
http://blog.csdn.net/hxpjava1/article/details/7035724

本文实现:

本文实现从数据库读取用户信息,获取当前用户的权限或角色,通过配置文件过滤用户的角色或权限。拥有相应的角色或者相应的权限的用户可以访问相应的url。

数据库设计

这里写图片描述

1. 添加依赖

<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.5</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.5</version></dependency><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>1.2.1</version></dependency>

2. 添加shiro 配置

package com.us.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;


/**
 * shiro配置类
 * Created by cdyoue on 2016/10/21.
 */
@Configuration
public class ShiroConfiguration {
    /**
     * LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,
     * 负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。
     * 主要是AuthorizingRealm类的子类,以及EhCacheManager类。
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * HashedCredentialsMatcher,这个类是为了对密码进行编码的,
     * 防止密码在数据库里明码保存,当然在登陆认证的时候,
     * 这个类也负责对form里输入的密码进行编码。
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(2);
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }

    /**ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,
     * 负责用户的认证和权限的处理,可以参考JdbcRealm的实现。
     */
    @Bean(name = "shiroRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ShiroRealm shiroRealm() {
        ShiroRealm realm = new ShiroRealm();
//        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

//    /**
//     * EhCacheManager,缓存管理,用户登陆成功后,把用户信息和权限信息缓存起来,
//     * 然后每次用户请求时,放入用户的session中,如果不设置这个bean,每个请求都会查询一次数据库。
//     */
//    @Bean(name = "ehCacheManager")
//    @DependsOn("lifecycleBeanPostProcessor")
//    public EhCacheManager ehCacheManager() {
//        return new EhCacheManager();
//    }

    /**
     * SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。
//     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
//        securityManager.setCacheManager(ehCacheManager());
        return securityManager;
    }

    /**
     * ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。
     * 它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());

        Map<String, Filter> filters = new LinkedHashMap<>();
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setRedirectUrl("/login");
//        filters.put("logout",null);
        shiroFilterFactoryBean.setFilters(filters);

        Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>();
        filterChainDefinitionManager.put("/logout", "logout");
        filterChainDefinitionManager.put("/user/**", "authc,roles[ROLE_USER]");//用户为ROLE_USER 角色可以访问。由用户角色控制用户行为。
        filterChainDefinitionManager.put("/events/**", "authc,roles[ROLE_ADMIN]");
        //        filterChainDefinitionManager.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取,此处是用权限控制

        filterChainDefinitionManager.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);


        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        return shiroFilterFactoryBean;
    }

    /**
     * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    /**
     * AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,
     * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor aASA = new AuthorizationAttributeSourceAdvisor();
        aASA.setSecurityManager(securityManager());
        return aASA;
    }


}

3. 添加Realm 验证

package com.us.shiro;

import com.us.bean.Permission;
import com.us.bean.Role;
import com.us.bean.User;
import com.us.dao.PermissionDao;
import com.us.dao.UserDao;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by cdyoue on 2016/10/21.
 */

public class ShiroRealm extends AuthorizingRealm {
    private Logger logger =  LoggerFactory.getLogger(this.getClass());

    @Autowired
    private UserDao userService;
    @Autowired
    private PermissionDao permissionService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("doGetAuthorizationInfo+"+principalCollection.toString());
        User user = userService.getByUserName((String) principalCollection.getPrimaryPrincipal());


        //把principals放session中 key=userId value=principals
        SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(user.getId()),SecurityUtils.getSubject().getPrincipals());

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //赋予角色
        for(Role userRole:user.getRoles()){
            info.addRole(userRole.getName());
        }
        //赋予权限
        for(Permission permission:permissionService.getByUserId(user.getId())){
//            if(StringUtils.isNotBlank(permission.getPermCode()))
                info.addStringPermission(permission.getName());
        }

        //设置登录次数、时间
//        userService.updateUserLogin(user);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        logger.info("doGetAuthenticationInfo +"  + authenticationToken.toString());

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String userName=token.getUsername();
        logger.info(userName+token.getPassword());

        User user = userService.getByUserName(token.getUsername());
        if (user != null) {
//            byte[] salt = Encodes.decodeHex(user.getSalt());
//            ShiroUser shiroUser=new ShiroUser(user.getId(), user.getLoginName(), user.getName());
            //设置用户session
            Session session = SecurityUtils.getSubject().getSession();
            session.setAttribute("user", user);
            return new SimpleAuthenticationInfo(userName,user.getPassword(),getName());
        } else {
            return null;
        }
//        return null;
    }

}

4. 添加controller

package com.us.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.web.bind.annotation.*;

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

/**
 * Created by cdyoue on 2016/10/21.
 * 登陆控制器
 */
@RestController
public class LoginController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(
            @RequestParam(value = "username", required = true) String userName,
            @RequestParam(value = "password", required = true) String password,
            @RequestParam(value = "rememberMe", required = true, defaultValue = "false") boolean rememberMe
    ) {
        logger.info("==========" + userName + password + rememberMe);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        token.setRememberMe(rememberMe);

        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            e.printStackTrace();
//            rediect.addFlashAttribute("errorText", "您的账号或密码输入错误!");
            return "{\"Msg\":\"您的账号或密码输入错误\",\"state\":\"failed\"}";
        }
        return "{\"Msg\":\"登陆成功\",\"state\":\"success\"}";
    }

    @RequestMapping("/")
    @ResponseBody
    public String index() {
        return "no permission";
    }
}

此处代码不全,只是写了springboot 整合的主要类,如果需要全部代码请移步。。。

源码: https://github.com/527515025/springBoot

作者:u012373815 发表于2017/2/26 23:52:51 原文链接
阅读:54 评论:0 查看评论

Java日文分词器之Kuromoji

$
0
0

Kuromoji是一个开源的,基于java语言开发的轻量级的日文分词工具包。捐赠给了ASF之后被内置在Lucene 和 Solr中,作为默认的日文分词器(默认的中文分词器是smartcn)。但他也不依赖Lucene 或 Solr,可以单独使用。采用维特比算法(Viterbi algorithm),默认使用IPA字典。 

其他有名的还有lucene-gosen : http://code.google.com/p/lucene-gosen/ 以及Google・Amazon・楽天等日本大的搜索引擎使用的Rosette : http://www.basistech.jp/base-linguistics/japanese/ 。Rosette是商业版的,能够适用于很多语言,比如:中文,日文,韩文,英文等等。 

http://www.atilika.org/ 

版本:kuromoji-0.7.7.jar 

(1)2行代码即可分词 

Java代码   收藏代码
  1. Tokenizer tokenizer = Tokenizer.builder().build();  
  2. List<Token> tokens = tokenizer.tokenize(word);  


分词后的Token: 

Java代码   收藏代码
  1. for (Token token : tokens) {  
  2.     System.out.println("==================================================");  
  3.     System.out.println("allFeatures : " + token.getAllFeatures());  
  4.     System.out.println("partOfSpeech : " + token.getPartOfSpeech());  
  5.     System.out.println("position : " + token.getPosition());  
  6.     System.out.println("reading : " + token.getReading());  
  7.     System.out.println("surfaceFrom : " + token.getSurfaceForm());  
  8.     System.out.println("allFeaturesArray : " + Arrays.asList(token.getAllFeaturesArray()));  
  9.     System.out.println("辞書にある言葉? : " + token.isKnown());  
  10.     System.out.println("未知語? : " + token.isUnknown());  
  11.     System.out.println("ユーザ定義? : " + token.isUser());  
  12. }  



(2)3中分词模式 

Java代码   收藏代码
  1. String word = "日本経済新聞でモバゲーの記事を読んだ。";  
  2. Builder builder = Tokenizer.builder();  
  3.   
  4. // Normal  
  5. Tokenizer normal = builder.build();  
  6. List<Token> tokensNormal = normal.tokenize(word);  
  7. disp(tokensNormal);  
  8.   
  9. // Search  
  10. builder.mode(Mode.SEARCH);  
  11. Tokenizer search = builder.build();  
  12. List<Token> tokensSearch = search.tokenize(word);  
  13. disp(tokensSearch);  
  14.   
  15. // Extends  
  16. builder.mode(Mode.EXTENDED);  
  17. Tokenizer extended = builder.build();  
  18. List<Token> tokensExtended = extended.tokenize(word);  
  19. disp(tokensExtended);  

 

引用
日本経済新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 | 
日本 | 経済 | 新聞 | で | モバゲー | の | 記事 | を | 読ん | だ | 。 | 
日本 | 経済 | 新聞 | で | モ | バ | ゲ | ー | の | 記事 | を | 読ん | だ | 。 |



(3)自定义词典 

Java代码   收藏代码
  1. // 使用自定义字典  
  2. InputStream is = UserDictSample.class.getClassLoader().getResourceAsStream("resources/userdict_ja.txt");  
  3.   
  4. Builder builder = Tokenizer.builder();  
  5. builder.userDictionary(is);  
  6. Tokenizer userTokenizer = builder.build();  
  7.   
  8. List<Token> tokens2 = userTokenizer.tokenize(word);  
  9.   
  10. StringBuilder sb2 = new StringBuilder();  
  11. for (Token token : tokens2) {  
  12.     sb2.append(token.getSurfaceForm() + " | ");  
  13. }  
  14. System.out.println(sb2.toString());  



引用
稀 | 勢 | の | 里 | 寛 | 
稀勢の里 | 寛 |



resources/userdict_ja.txt: 

引用
# 単語,形態素解析後の単語(単語を分ける場合は、スペースで区切る),読み,品詞 
稀勢の里寛,稀勢の里 寛,キセノサト ユタカ,カスタム人名



(4)汉字转片假名 

Java代码   收藏代码
  1. String word = "東京特許許可局";  
  2.   
  3. Builder builder = Tokenizer.builder();  
  4. builder.mode(Mode.NORMAL);  
  5. Tokenizer tokenizer = builder.build();  
  6. List<Token> tokens = tokenizer.tokenize(word);  
  7.   
  8. StringBuilder sb = new StringBuilder();  
  9. for (Token token : tokens) {  
  10.     sb.append(token.getReading() + " | ");  
  11. }  
  12. System.out.println(sb.toString());  

 

文章出自:http://rensanning.iteye.com/blog/2008575



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


ITeye推荐



浅谈ElasticSearch的嵌套存储模型

$
0
0


最近一个半月都在搞SparkStreaming+Hbase+Redis+ES相关的实时流项目开发,其中重度使用了ElasticSearch作为一个核心业务的数据存储,所以这段时间更新文章较少,现在开发基本完事,接下来的会写几篇有关ElastiSearch的使用心得。


大多数时候我们使用es都是用来存储业务比较简单的数据,比如日志log类居多,就算有一些有主外键关联的数据,我们也会提前join好,然后放入es中存储。

的确,扁平化后的数据存入索引,无论是写入,更新,查询都比较简单。但是有一些业务却没法扁平化后存储。比如我们这次的业务数据。由于业务本身比较复杂,先看下数据实体模型。






总共是三层模型,可以看到User(用户)包含多个Quest(题目),每个题目又包含多个Kp(知识点),其中User,Quest,Kp都是一个实体类,可以包含多个属性,按照es的字段类型应该叫做object类型,先说说为什么不能扁平化处理在写入索引,因为一旦扁平化其实只有统计知识点相关的聚合才是正确的,若想统计题目和人的一些聚合指标有些是查不出来的,因为一旦扁平前2级数据会被冗余放大好多倍,导致计算指标会出现问题。常规的count+distinct可以出来,但是一些sum指标就不对了,会多算冗余数据的和,而且没法再减出去,如果想做可能需要多次查询才能搞定,理想情况下,一次查询就能搞定大部分查询或聚合
所以只有嵌套设计才能贴合真实的数据模型,换做关系型数据需要三张表,用mongodb也可以但是查询+聚合就没有es这么强大和高效


三层嵌套的好处就是贴合实际的数据实体模型,但是带来的弊端也非常明显,对深层嵌套数据的删除,修改比较麻烦,虽然也能做到,但是每一层的数据量越大,性能可能就越低,所以嵌套方案,适合存储和查询多级嵌套数据,且更新和删除操作少的业务情况,尽量没有修改和删除。



es的嵌套查询和聚合支持都比较完善,并且支持嵌套反转查询。嵌套数据的添加可以使用script脚本方式来完成,直接将java的bean给转换完为json提交即可。

下面来看下动态mapping+嵌套类型设置,一个模板如下:


{"order": 0,"template": "work*","settings": {"index": {"number_of_replicas": "0","number_of_shards": "3"
    }
  },"mappings": {"_default_": {"dynamic_templates": [
        {"nested_kps": {"mapping": {"type": "nested"
            },"path_match": "quests.kps"
          }
        },
        {"nested_quests": {"mapping": {"type": "nested"
            },"match": "quests"
          }
        },
        {"string_fields": {"mapping": {"index": "not_analyzed","type": "string"
            },"match": "*","match_mapping_type": "string"
          }
        },
        {"message": {"mapping": {"index": "analyzed","type": "string"
            },"match": "message","match_mapping_type": "string"
          }
        },
        {"date_fields": {"mapping": {"doc_values": true,"type": "date"
            },"match": "*","match_mapping_type": "date"
          }
        },
        {"float_fields": {"mapping": {"doc_values": true,"type": "float"
            },"match": "*","match_mapping_type": "float"
          }
        },
        {"double_fields": {"mapping": {"doc_values": true,"type": "double"
            },"match": "*","match_mapping_type": "double"
          }
        },
        {"integer_fields": {"mapping": {"doc_values": true,"type": "integer"
            },"match": "*","match_mapping_type": "integer"
          }
        },
        {"long_fields": {"mapping": {"doc_values": true,"type": "long"
            },"match": "*","match_mapping_type": "long"
          }
        }
      ],"_all": {"enabled": false
      }
    }
  },"aliases": {}
}


嵌套类型的关键词是nested,如果一个类型是nested,就相当于是设置了Java里面的List是一个集合对象list,可以有多个同一种类型的实体类数据,每个数据里面还可以有自己的嵌套类型或其他类型,上面的动态mapping里面数据类型设置各个类型的定义,并且根据path设置了嵌套的动态mapping设置。这样以来就相当于设置了三层嵌套。

到此我们应该能理解嵌套模型的定义和使用场景了,下篇会给出如何插入数据和使用script追加数据。


有什么问题可以扫码关注微信公众号:我是攻城师(woshigcs),在后台留言咨询。
技术债不能欠,健康债更不能欠, 求道之路,与君同行。




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


ITeye推荐



如何正确地在Android手机上安装国产软件?

$
0
0

tumblr_inline_o6xf9b3IAi1r19dv9_1280.jpg

国产软件往往会申请与之功能不符的权限,读取着用户手机信息,开机后就驻足系统,这些对于注重隐私的用户来说都是问题。如何“干净”地安装国产流氓软件呢?笔者整理了一些思路供大家探讨。

国产app的全家桶问题一直被大家诟病,一个应用启动后就会“唤醒”其“家族”内的其他应用,有时还会通过其他方式确保应用始终在后台运行;与此同时,很多应用还会申请与之功能完全不符的权限,这些严重破坏了Android系统的体验。

要获得一个纯净的体验,我们需要解决的是两种问题,首先是软件自启动、驻足后台、频繁唤醒;其次是应用对手机信息的读取。对应这两个问题,我们要寻找各种解决方案,而手机本身的环境也是我们需要考虑的因素。笔者将“环境“分为三类:已安装Xposed框架、已root、未root,三种环境下解决问题的难度也各不相同。

Xposed

Xposed简单来说是一个框架,它可以不修改APK的情况下改变系统和应用程序的行为,所有的更改都在内存中。

如果你安装了Xposed,问题就变得简单了,得益于其强大的可定制性,Xposed中有大量应用软件能够满足你的需求。绿色守护(Greenify)的Xposed模式可以阻止应用在后台运行并且禁止链式唤醒,最近新加入的“处方”模式还可以屏蔽软件广播。

Android平台的应用程序分为七个优先级:前台进程、可见进程、主要服务、次要服务、后台进程、内容供应节点、空进程。

第6级别就是一些动作标记,程序设定了遇到什么节点时将采取什么动作,比如:插入耳机线,就是一个“内容供应节点”,收音机程序遇到这个“节点”就会激活并自启,变成后台进程了。要想收音机不会因此启动,有两个办法,要么屏蔽这个节点,使你无论是否插入耳机线,都不产生这个节点的信号,要么我们冻结收音机程序,让他感受不到这个节点。一些无良的应用,动不动就自己在后台启动了,就是这个道理。绿色守护能将一个程序绿化,就是使其感受不到“指定的”“内容供应节点”,从而当发生这些“节点”时,程序不会自动启动,除非用户自己让它启动(用户的点击程序图标也是一个“内容供应节点”)。

想要更直接地阻止应用后台运行,也可以使用“阻止运行”和“黑域”。

除了绿色守护,为了保障你的隐私,你还应该安装Xprivacy,这款软件可以设置软件读取隐私信息的权限。对于某些“流氓”应用强制用户开启权限的情况,Xprivacy还可以伪造随机信息从而保障隐私。原理也是利用Xposed框架能够修改内存信息的方法修改软件获取到的信息。

565009367188452541.png  186969661959223167.png 

Root

Android平台本来有类似iOS推送服务APNS的GCM(Google Cloud Messaging),但由于众所周知的原因,这项服务在国内非常不稳定,于是各大厂商就纷纷推出自己的推送服务,而利用这些推送服务,往往一个app开启后就会其他应用都可能关联启动,尤其以百度、阿里、腾讯为甚。这也就是前面所说的“全家桶”。

如果你Root了手机,写轮眼(My Android Tools)可以说是一款神器,它能够禁用activity、service、receiver、provider,其中的Service就包括软件用来接收推送的服务,因此,写轮眼(My Android Tools)可以阻止软件唤醒。除此之外由于能够禁用activity、service,它能做的还不止这些,经过合理配置,写轮眼能够帮你屏蔽广告,优化软件。

348759852042097543.png

对于那些不常用的软件,可以使用“冻结”类应用将他们禁用。“冰箱”就是其中的代表。它的原理非常简单,adb中提供了一个adb shell pm disable命令,在root权限下可以直接用来冻结app,而冰箱基本可以理解为使用了adb shell pm disable命令。不过它提供了非常方便的launcher,让你能够快速地启动冻结app。

436404800914951612.png

保护隐私方面,对于已经Root的手机,可以使用AppOps应用控制应用权限。很多国产ROM甚至系统原生ROM都有权限控制,但首先,这些权限往往不完整;其次,AppOps能将权限设置为ignore,很多国产软件会强制用户开启部分权限,设置ignore后,应用会得到数据,但数据为空,从而绕过这些权限申请。比如如果我们直接拒绝微信使用电话权限,就无法正常进入微信,而用AppOps将权限设为忽略就可以顺利进入。

548807995870182378.png237945419321612689.png

左图为 通过系统权限管理拒绝微信读取设备ID,右图为 通过AppOps忽略微信读取设备ID请求

未Root

未Root手机其实本文的重点。笔者自从去年入手了S7 edge后就一直纠结于是否Root。Root后永久无法使用Samsung Pay,更可怕的是还会导致前置摄像头黑屏、系统卡顿发热等各种问题。可是又无法忍受各种流氓软件在手机里捣乱,因此一直在寻找方案。

对于TouchWiz系统,我们可以利用KNOX Standard SDK的机制冻结应用(包括系统应用),常见的利用这一机制的软件包括“空调狗”、“冻住”,它们都可以做到应用使用后立即冻结,从根源拒绝“关联启动”等流氓行为,只可惜这一机制只针对三星系列手机。

59004780468787091.png

隐私方面,可以通过AppOps将部分权限设置为ignore。实际上AppOps在Android 4.4.2之前是集成在系统中的,4.4.2之后,Google不知处于何种想法将其从系统中移除了,系统自身没有权限进行管理。上文中所使用的AppOps是以root为前提的。如果手机没有root,只有两种相对比较麻烦的方法,两种方法都需要电脑端操作。

一是通过电脑端adb进行设置:

adb shell appops set 包名 权限名称 ignore   

其中权限名称包括:

READ_CONTACTS 读取联系人

POST_NOTIFICATION 通知

CALL_PHONE 拨打电话

READ_SMS 读取短信

WRITE_SMS 写入短信

RECEIVE_SMS 接收短信

OP_READ_PHONE_STATE 读取设备ID(包括IMEI)

FINE_LOCATION 定位权限

COARSE_LOCATION 定位权限

VIBRATE 震动

CAMERA 摄像头

WRITE_CLIPBOARD 写入剪切板

READ_EXTERNAL_STORAGE 读取外部存储空间

WRITE_EXTERNAL_STORAGE写入外部存储空间

BOOT_COMPLETED 开机时启动

GET_ACCOUNTS 获取设备账号信息

第二种方法是使用App Ops手机端软件,在免root模式下,App Ops使用远程adb进行权限控制,而远程adb仍然需要在电脑端开启:adb tcpip 5555,将端口号5555输入App Ops即可使用。但需要注意的是,重启后需要重新开启远程adb。

然而,想要完全将流氓软件隔离开来,我们要用到类似“沙盒”的机制。好在Android自从5.0以后就加入了Android for Work功能,功能的初衷是为了让大家在工作时能够使用自己的私人设备,通过Android for Work,我们可以建立“个人”和“工作”两套档案,工作档案由企业的IT管理员管理,为了安全起见,在两套方案中的应用无法互相访问,应用数据存储的区域其实也是不一样的,Island中的/data目录、内置存储空间、通话记录、联系人、日历等数据是与原用户独立的。

利用这种思路,我们也可以将这套机制用来对付流氓应用,将应用安装到工作档案后,它就无法获取到个人档案的应用和数据。Island就是利用Android for Work将流氓应用隔离在“岛”上的应用,这款软件与绿色守护出自同一作者,通过上述方式来实现类似沙盒的效果。

除了沙盒,Island还自带了冻结应用的功能。然而,使用了Island中自带的冻结功能后再次解冻时,该应用的AppOps状态就会被恢复到初始状态(也就是允许所有权限)需要重新设置权限。如上文所说,进行AppOps设置的两种方法都需要用到电脑,因此冻结后再解冻软件需要重新设置权限的成本较高。

124869754338902445.png

接下来我们就在沙盒环境内解决唤醒和隐私的问题。

唤醒问题我们可以通过在Island中安装绿色守护来解决。尽管是在非root环境下,它依然能够强制关闭程序(原理是通过Android辅助功能模拟点击“强制停止”程序)。建议开启“嗜睡模式”,它能够部分禁止应用的后台行为,该模式利用的是Android 6.0引入的Doze Mode。在睡眠状态下,系统将停止一些软件运行,例如一些非即时通讯软件的后台就会在锁屏的状态下被Android清理掉。从而达到节省电量、延长续航时间的作用。绿色守护中的“嗜睡模式”会将进入Doze Mode的时间缩短,从而进一步节省电量。

main-qimg-74497cf9d299509541c6a2c2b15a0158.png 

而隐私问题则可以通过在沙盒中安装App Ops来控制,或者同样地,可以通过电脑端adb操作,但需要注意的是由于沙盒实际上是在Android中使用了多用户,因此需要在参数中指定用户:

adb shell pm list users
adb shell appops get com.eg.android.AlipayGphone --user XX   

adb2.png

除了上述方案,笔者最近还发现了一款名叫“容器”的应用。

27464467823348405.png

与Island的思路完全不同,该应用利用的是VirtualApp和文件夹重定向。VirtualApp会在你的App内创建一个虚拟空间,你可以在虚拟空间内任意的安装、启动和卸载APK,这一切都与外部隔离,如同一个沙盒。目前“容器”尚在开发中,但按照作者的说法,今后会加入隐私、权限的控制和应对流氓行为的功能。

总结

要想解决流氓app的唤醒和隐私读取问题,Xposed平台和Root后的手机上有各种简单的方案,包括Greenify与XPrivacy,而非root环境下则相对困难,我们可以使用Island创造Android for Work环境,我们可以把这环境看作沙盒,将应用安装在沙盒内,应用的启动问题则通过Android 6.0后引入的Doze Mode来解决。

想这么多方法来安装国产App,一方面是为了设备保持流畅的状态,不至于被各种唤醒的应用长期占据内存和耗电,另外也考虑到隐私问题。折腾安装国产应用,实际上是无奈之举,也希望国内的Android生态能够良性发展。

*本文作者:JohnChu,转载请注明来自FreeBuf(FreeBuf.COM)

Twitter数据挖掘及其可视化

$
0
0

前阵子有学弟学妹问我毕设做的啥,于是我决定记录一下去年毕设的内容。

主要是基于twitter的内容有:

  • 实时热点话题检测
  • 情感分析
  • 结果可视化
  • Twitter数据挖掘平台的设计与实现

毕设、论文内容

毕设从16年3月开始做,做到5月初,开始写论文,当时写的论文一共有七章,写了一个礼拜,从早到晚- -| 共24834字。,数据有的从15年11月左右开始抓的。

论文总共有七章,结构安排如下:

第一章,引言。主要介绍了Twitter下进行的数据挖掘的背景,以及国内外研究现状,并简要的说明了本文的主要研究内容。

第二章,Twitter相关的内容介绍。主要介绍Twitter的一些特殊语法,如用户提及@,hashtags等,接着讨论了Twitter中大量存在的拼写错误、缩写、重复字母现象,最后介绍了Twitter的REST API与StreamAPI。

第三章,实时热点话题挖掘。该章节首先介绍了LDA相关的模型,接着提出了WOLDA算法,以及最具有代表性推文的计算方法。

第四章,情感分析。该章节介绍了Twitter下的情感分析分类、以及机器学习的一般过程,接着介绍本文使用机器学习和情感词典相结合的方法。

第五章,数据可视化。介绍了几种基于统计的可视化方法,还有主题分析和情感分析的可视化的方法,可以更直观的表示结果。

第六章,Twitter数据挖掘平台的设计与实现。结合了前面几章的内容,介绍实现该系统的细节。

第七章总结了本文的工作,针对目前的不足,提出下一步改进的方案。

本篇博文为缩减版,除了略去一二章外,其它章节也做了精简。

实时热点话题挖掘

Twitter从2006年以来,发展迅猛。举两个数据来讲,

  • 2015年5月,Twitter拥有超过5亿的用户,其中有超过3.32亿的活跃用户
  • 2016年2月28日莱昂纳多获得第88届奥斯卡最佳男主角奖时,据统计,这一消息在Twitter上的讨论达到每分钟44万次

可以看出,Twitter的数据量是十分庞大的。为了能够了解Twitter上人们在谈论些什么,我们希望能够有一种有效的方 式来获取 Twitter 实时的热点话题。要求该方式:

  1. 能处理流数据并且对模型进行周期性的更新 。
  2. 产生的主题 与过去的 主题有关联 以便 观测话题的演变 。
  3. 资源占用稳定,不随时间增大而以便保证效率和对新话题的敏感 。

LDA模型

首先想到的就是主题模型。

2003年,D.Blei等人提出了广受欢迎的LDA(Latentdirichlet allocation)主题模型 [8]。LDA除了进行主题的分析外,还可以运用于文本分类、推荐系统等方面。

LDA模型可以描述为一个“上帝掷骰子”的过程,首先,从主题库中随机抽取一个主题,该主题编号为K,接着从骰子库中拿出编号为K的骰子X,进行投掷,每投掷一次,就得到了一个词。不断的投掷它,直到到达预计的文本长度为止。简单的说,这一过程就是“随机的选择某个主题,然后从该主题中随机的选择词语”。按照之前的描述,一篇文档中词语生成的概率为:

词语文档主题词语主题主题文档

可以用矩阵的乘法来表示上述的过程:

 

回到LDA模型来说,LDA模型的输入是一篇一篇用BOW(bag ofwords)表示的文档,即用该文档中无序的单词序列来表示该文档(忽略文档中的语法和词语的先后关系)。LDA的输出是每篇文档的主题分布矩阵和每个主题下的单词分布矩阵。简而言之,LDA主题模型的任务就是已知左边的矩阵,通过一些方法,得到右边两个小矩阵。这里的“一些方法”即为LDA采样的方法,目前最主要的有两种,一种是变分贝叶斯推断(variationalBayes, VB),另一种叫做吉布斯采样(Gibbs Sampling),其中吉布斯采样也被称为蒙特卡洛马尔可夫 (Markov Chain MonteCarlo,MCMC)采样方法。

总的来说,MCMC实现起来更加简单方便,而VB的速度比MCMC来得快,研究表明他们具有差不多相同的效果。所以,对于大量的数据,采用VB是更为明智的选择。

Hoffman OLDA

虽然VB的速度相对而言比较快,但是对于巨大的数据来说,VB计算量仍十分巨大的,对此,Hoffman提出了Online variational Bayes (VB)算法(下面简称为OLDA),将数据集其分为一些小的batch, 然后更新,运算速度得到了巨大的提升。

 

WOLDA

虽然Hoffman提出的OLDA算法可以对后加进来的文档不断的更新,但是,该算法仍不能称得上是在线的算法。原因如下:

  1. 该算法采用静态词库(忽略不在词库中的词),而对于Twitter来说,新词不断涌现,缩写词、网络流行语、特殊事件人名、地名频繁出现,基本无法预测。即使我们拥有一个囊括了所有词的词库,那么这个词库也必然是巨大的,造成矩阵过于稀疏,运算效率低下。
  2. OLDA算法对旧话题“淡忘”速度越来越慢。如果一开始出现了所谓的“离题”(topic drift)现象,结果将会十分差劲,这不利于新话题的检测。

为此,改进的算法命名为WOLDA。

WOLDA采用动态的词库,(滑动时间窗口)

  • 时间分为一个个时间片
  • 只保留时间窗口L内的词 && 词频 > min_df(预设值)

对于1~L个时间片,对词频不小于min_df的词作为当前WOLDA的词库。

第L+1个时间片到来时,删除第1个时间片的文档,对第2个到第L+1个时间窗口内的文档重新计算词频,并将词频不小于min_df的词作为当前WOLDA的词库。

模型的更新方法为,对于新词,进行随机的初始化,而对于原本存在词库中的词有:

$$\lambda = C * \lambda$$

贡献因子C使得模型具有事件演变的能力,它将连续时间切片上的前后模型相结合。在具体的实现上,对于给定贡献因子C,我们只需要反解出OLDA中的更新次数t,将OLDA的更新次数重新设置为t即可,公式如下:

$$t = (1-C)^{-\frac{1}{\kappa}}-\tau_0$$

此外,还需要更新OLDA相应参数,如单词总数W和文档长度D。

算法描述如下:

定义窗口大小 L,贡献因子c,最小的词频 min_df
for n = 0 to ∞ do
  对时间片n的文档集合进行预处理,如去除停止词等操作。
  if n==1:
     该文档集过滤词频小于min_df的,正常运行OLDA
  else if 2 <= n <= L:把第2~n的文档所有词重新计算词频,词频不小于min_df的词作为当前OLDA词库,新的词随机初始化。计算t,更新W和D,运行OLDA算法。
  else if n > L:删除第n-L篇文档,将第n–L+1 ~ n的文档的所有词重新计算词频,词频不小min_df的词作为当前OLDA词库,新的词随机初始化。计算t,更新W和D,运行OLDA算法。
end for

最具有代表性的推文计算

行WOLDA算法后,我们得到了每个主题下对应的主题词,主题词有时候对于主题的描述不够直观,为此我们希望从该主题下,能找到最具有代表性的推文,用来帮助解释和说明该主题的内容。本小节提出几种最具有代表性的推文的计算方法,并在之后的实验中加以对比。

KL-mean

KL散度(Kullback–Leibler divergence)又称为相对熵(relative entropy),它可以用来衡量两个概率分布的相似程度。对于离散型的随机变量,其概率分布P和Q的KL散度定义如下:

$$D_{KL}(P||Q) = \sum_iP(i) ln\frac{P(i)}{Q(i)}$$

通常情况下KL散度是非对称的,因此这里采用KL-mean方式(求P和Q KL散度以及Q和P KL散度的均值)

$$D_{KL-mean}(P||Q) = \frac{1}{2}(D_{KL}(P||Q) +D_{KL}(Q||P) )$$

使用KL-mean距离计算最具有代表性的推文伪代码如下:

pro_matrix: 主题-单词矩阵
features = []
for tweet_id,tweet in tweets do
   topic_id根据文档-主题矩阵得到当前推文最大可能从属的主题序号
   feature = [0 …] // 长度为词库的大小的全0数组
   for word_id, each word in tweet do: //word_id 为当前单词在词库中的下标
            feature[word_id] = word_cnt * pro_matrix[topic_id][word_id] 
            //当前单词出现的次数乘以相应的主题-单词矩阵中的概率
        end for
   features.append(feature)
end for
对于所有相同主题序号的推文,计算其feature的平均值作为主题的中心。
接着使用KL-mean距离计算每条推文与其主题中心的距离dis
对于每个主题,找到与类中心最小距离的推文,该推文即为最具有代表性的推文。

余弦距离

余弦距离常常用来衡量相似度(通过计算两个向量夹角的余弦值)。其定义如下:

$$D_{cos}(P,Q) = \frac{P·Q}{||P||·||Q||}$$

使用余弦距离计算最具有代表性的推文的方法与KL散度的方法过程类似,只不过最后采用了余弦距离来计算每条推文与其主题中心的距离。

最大熵

在信息学中,熵(Entropy)常常被用来衡量信息不确定度的大小,信息的不确定度,表明其信息量也越大,同时熵也越大。熵的计算公式如下:

$$Entropy(X) = -\sum_iP(x_i)log_2P(x_i)$$

p: 主题-单词矩阵
entropy = []
for tweet_id,tweet in tweets do
   topic_id根据文档-主题矩阵得到当前推文最大可能从属的主题序号
   cur_entropy = 0
   for word_id, each word in tweet do: //word_id 为当前单词在词库中的下标
           cur_entropy += -p[topic_id][word_id] * log2 (p[topic_id][word_id])
        end for
   entropy.append(feature)
end for
对于每个主题,找到熵最大的推文,该推文即为最具有代表性的推文

 

情感分析

为什么要进行情感分析?Twitter的作为一个微博客服务,它的推文中又充斥着大量的观点见解,进行情感分析也同样具有广阔的应用场景,比如说以下的这个方面:

  1. 情感分析可以帮助用户做出是否购买的决策。例如,消费者在犹豫是否购买产品时,会很自然的去查看其他人对于该商品的评价。如果“好评”居多,该消费者可能就会进行购买;反之,如果“差评”占大多数,那么该消费者一般而言就不会进行购买了。如果能针对Twitter这种既有强时效性又有广泛话题领域的社交媒体进行情感分析,那将给用户带来更多的便利。
  2. 情感分析还可以帮助企业进行市场调研。企业在推出一款新的产品之后,可以通过情感分析来从大量的用户评价中得到有用的信息,如用户喜欢什么,不喜欢哪一方面,对公司的产品和服务有哪些正面或负面的影响。从而企业可以了解自身的优势和不足,可以更好的制定相应的措施进行服务的改进,从而在激烈的市场竞争中占据主动地位。
  3. 舆情监控。由于用户可以在社交媒体上相对自由的发表自己的观点,这使得社交媒体成为了舆情话题产生和传播的重要方式。通过对社交媒体的情感分析,可以为政府了解民意、引导舆论提供有效的工具。对于负面的消息,可以较为及时的安抚好民众的情绪,避免事态进一步恶化。同时,政府也可以制定相应的策略来改善现有的服务。
  4. 事件预测。随着互联网发展,越来越多的民众愿意到网上发表自己对某一事件的看法,无论是在诸如Twitter、新浪微博这样的微博客,还是在贴吧、知乎等站点上。一个典型的例子就是最近阿里人工智能运用神经网络、情绪感知等技术对《我是歌手》第四季总决赛的歌王进行了成功的预测。此外,Twitter这一个平台也常常被拿来预测选举、股票等。

情感分析方法

本文采用的情感分析可以说是一个标准的机器学习的分类问题。

目标是给定一条推文,将其分为正向情感、负向情感、中性情感。

预处理

  • POS标注
    • CMU ArkTweetNLP
  • 字母连续三个相同
    • 替换 “coooooooool”=>“coool”
  • 删除非英文单词
  • 删除URL
  • 删除@
    • 删除用户的提及@username
  • 删除介词、停止词
  • 否定展开
    • 将以”n’t”结尾的单词进行拆分,如”don’t” 拆分为”do not”,这里需要注意对一些词进行特殊处理,如”can’t”拆分完之后的结果为”can not”,而不是”ca not”。
  • 否定处理
    • 从否定词(如shouldn’t)开始到这个否定词后的第一个标点(.,?!)之间的单词,均加入_NEG后缀。如perfect_NEG。 “NEG”后缀

特征提取

  • 文本特征
    • N-grams
      • 1~3元模型
      • 使用出现的次数而非频率来表示。不仅是因为使用是否出现来表示特征有更好的效果 [16],还因为Twitter的文本本身较短,一个短语不太可能在一条推文中重复出现。
    • 感叹号问号个数
      • 在句子中的感叹号和问号,往往含有一定的情感。为此,将它作为特征。
    • 字母重复的单词个数
      • 这是在预处理中对字母重复三次以上单词进行的计数。字母重复往往表达了一定的情感。
    • 否定的个数
      • 否定词出现后,句子的极性可能会发生翻转。为此,把整个句子否定的个数作为一个特征
    • 缩写词个数等
    • POS 标注为[‘N’, ‘V’, ‘R’, ‘O’, ‘A’] 个数(名词、动词、副词、代词、形容词)
  • 词典特征(本文使用的情感词典有:Bing Lius词库 [39]、MPQA词库 [40]、NRC Hashtag词库和Sentiment140词库 [42]、以及相应的经过否定处理的词库 [45]
    • 推文中的单词在情感字典个数 (即有极性的单词个数)
    • 推文的 总情感得分:把每个存在于当前字典单词数相加,到推文的 总情感得分:把每个存在于当前字典单词数相加,到推文的 总情感得分:把每个存在于当前字典单词数相加,到推文总分,这个数作为一特征。
    • 推文中单词最大的正向情感得分和负。
    • 推文中所有正向情感的单词分数 和以及 所有负向情感单词的分数和。
    • 最后一个词的分数
  • 表情特征
    • 推文中正向 情感 和负向的表情个数
    • 最后一个表情的极性是 否为正向

特征选择

本文 特征选择主要是针对于 N-grams 特征 的,采用方法如下:

设定min_df(min_df>=0)以及threshold(0 <= threshold <= 1)
对于每个在N-grams的词:
统计其出现于正向、负向、中性的次数,得到pos_cnt, neg_cnt, neu_cnt,以及出现总数N,然后分别计算
pos = pos_cnt / N
neg = neg_cnt / N
neu = neu_cnt / N
对于 pos,neg,neu中任一一个大于阈值threshold 并且N > min_df的,保留该词,否则进行删除。

上述算法中滤除了低频的词,因为这可能是一些拼写错误的词语;并且,删除了一些极性不那么明显的词,有效的降低了维度。

分类器选择

在本文中,使用两个分类器进行对比,他们均使用sklearn提供的接口 。第一个分类器选用SVM线性核分类器,参数设置方面,C = 0.0021,其余均为默认值。第二个分类器是最大熵分类器,其中,设置参数C=0.01105。在特征选择上,min_df=5, threshold=0.6。

实验

  • SemEval(国际上的一个情感分析比赛)训练数据和测试数据
  • 评价方法采用F-score
  • 对比SemEval2016结果如下
测试集名SVM(F-score/Rank)MaxEnt(F-score/Rank)
2013 Tweet0.701 / 50.714 / 3
2013 SMS0.719 / 10.722 / 1
2014 Tweet0.693 / 80.692 / 8
2014 Tweet sarcasm0.478 / 60.478 / 6
2014 Live Journal0.712 / 40.726 / 2

 

数据可视化

为什么要进行数据可视化呢?因为可以更快速、更轻松的提取出数据的含义。例如

  • 将3标注为红色容易找出所有的3
    •   
  • 画柱状图容易找数组【 321, 564, 1391, 245, 641, 798,871 】中的最大值

简单的统计结果可视化

Hashtag统计

由于Hashtag是用户手动添加的、用来表明当前发表的推文的主题。因此对其进行统计,然后进行可视化也是具有一定意义的。简单的说,进行hashtag统计的可以有柱状图、饼状图、趋势图三种方法。

 

地理位置信息的可视化

Twitter的API返回字段中,有几个字段是和地理位置相关的,用来表示该推文的发表位置,或者某地点和该推文相关。我们可以对地理位置信息进行统计计数。一个可视化的办法就是在地图上根据经纬度坐标画一个个的点,但是当有多个点再一个小区域的时候可读性较差,因此本文使用的是热力图。一个样例图如下:

 

话题结果可视化

在LDA主题模型中,输出结果有两个矩阵,其中一个是主题-单词矩阵,这也是本小节要探讨的可视化内容。

为了能够很好的表示出主题以及对应的单词,本文提出可以使用矩形树图(TreeMap)、气泡图(Bubble)、以及旭日图(Sunburst)来表示LDA的结果。

矩形树图

矩形树图是由一个个矩形递归组成的。

同一个颜色表示同一主题,而矩形大小表示概率大小。

在图形交互方面,矩形树图支持点击后放大查看。

 

气泡图

同一个主题同一个圈,同一个圈内的圆大小表示概率的大小。

在图形交互方面,气泡图支持点击后放大查看某一主题下的内容。

 

旭日图

旭日图它可以说是饼状图的升级版。在最内圈的数据为每个主题,同时,用不同的颜色加以区分,内圈所占的大小就反映了主题的热度。接着,对于每个主题,向外延伸出对应的主题词,每个主题词占的面积大小就反映了其概率的大小。此外,本文做出了特殊的处理,将主题词中更重要的主题词在加一层显示。

最重要的主题词计算方法为:按主题的概率从大到小排序,然后,从大到小进行遍历,对概率和进行累加,当对某一项i累加后的和大于0.4,则从第一个主题词到第i个主题词为该主题的最重要的主题词。

旭日图的用户交互为,点击某一块区域,则图形变化为某主题下的单词概率分布饼图。

 

情感分析的可视化

针对于情感分析,我们的任务是对于给定一些推文,判断其实情感类别。在分类结果完成后,我们可以对分类的结果进行统计。可以采用类似于对Hashtag的统计结果进行可视化的方法,如柱状图、饼状图,这里不再赘述。此外,还可以用“仪表盘”的方式来进行可视化。

 

Twitter数据挖掘平台的设计与实现

本章基于前面几个章节所讨论的问题与相关的算法,设计并实现了Twitter数据挖掘与可视化系统。这个系统主要包含数据抓取模块、数据存储模块、主题分析模块、情感分析模块、WEB模块一共六大模块。开发系统时使用 Git进行版本控制,并且提交到Github这个开源代码网站,方便多个人共同进行开发和维护。

系统的总体框架

本文系统的后端使用了Python的Django框架,前端可视化采用了D3.js和Echarts,除此之外,使用了JQuery + Bootstrap进行用户界面的快速开发。数据存储方面,使用的是MongoDB数据库。

下面是框架图:

 

数据抓取模块

数据抓取模块的主要功能是根据用户想要追踪的信息,向Twitter发送相应的请求。对于数据挖掘的平台来说,一个健壮的数据挖取模块是十分必要的。这个模块除了应对超过API的限定的速率错误外,各种HTTP的错误也是需要进行处理的。Twitter常见的HTTP错误及应对措施如下:

错误代码错误描述应对措施
401无OAuth验证或验证失败提示进行OAuth验证
404URI请求不合法或查询内容不存在返回空
429超出速率限制等待15分钟继续,或者换另外一个账号继续抓取
500,502,503,504服务器错误等一段时间继续,每次错误将等待时间延长,超过一定的错误次数报错。

更多的Twitter从错误代码详见 Twitter开发者平台

数据存储模块

数据存储的功能主要是对于数据抓取模块所抓取的数据进行存储,方便日后的继续研究;以及对存储内容的读取、查询。主要用到了MongoDB数据库。

MongoDB

MongoDB是由C++语言编写的,一个基于分布式文件存储的开源数据库系统。同时,MongoDB也是一种NoSQL(Not only SQL)数据库。

它可以方便的进行数据分片,采用水平扩展的方式,添加更多的节点,来保证服务器的性能,并且成本相对垂直扩展来说更加低廉,同时,它原生的支持Map-Reduce操作。

文献[46]比较了MongoDB和MySQL的优缺点。文献[47]和文献[48]比较了mongodb和关系型数据库如MySQL,MS-SQL数据库的速度,结论是mongodb具有更快的速度。

关系型数据库与NoSQL数据库 数据大小-查询时间对比图如下 [33]

 

正是由于MongoDB有更好更稳定的性能,且数据格式为JSON和twitter返回的一致。因此本系统选择了MongoDB作为数据库,并采用了索引技术。

数据压缩

MongoDB将数据存储为一个文档,数据结构由键值对(key=>value)组成。MongoDB 文档的存储类型是BSON,它类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。它不像MySQL之类的关系型数据库,必须先指定好数据表中的列,而可以随意的增加或删除文档中的字段(即关系型数据库中的列),但每一条数据都要保存相应的字段名。

Twitter 返回的原始推文信息是十分庞大的,除了140字符的文字,还有许多其他的字段。若将其展开,其内容将会超过5KB,这大约我们认为的140个字符文字的37倍!并且由于Twitter中有大量的字段经常为空,用MongoDB进行存储时,不管该字段是否为空,都会进行存储,这样一来将占据大量的存储空间,并且其占据的空间与字段长度成正相关的关系。比如说,”in_reply_to_user_id” 一栏往往为空,但是存储中仍会有这个字段。

对此,需要对字段进行删减,只保留一些有用的字段,当该字段为空的时候不存储,且对字段进行重命名操作,减少其长度。

更新后所保留的字段如下表:

字段名原字段名类型
geocoordinatesarray
datecreated_atdate
likefavorite_countint
idid_strstring
reply_idin_reply_to_status_id_strstring
reply_user_idin_reply_to_user_id_strstring
quoted_idquoted_status_id_strstring
retweet_idretweeted_status[‘id_str’]string
retweet_countretweet_countint
texttextstring
use_iduser[‘user_id_str’]string
hashtagsentities[‘hashtags’][‘text’]array
urlsentities[‘usrls’][‘expanded_url’]array
user_mentionsentities[‘mentions’][‘id_str’]array

个直观的对比就是1000多万条推文的时候,按Twitter API返回的结果直接进行存储,就有97.906GB的空间。而精简后的只剩下了9.949GB,体积减少了将近90%,但是携带的有用的信息几乎没少,这有利于数据的查询和存储。

主题分析模块

主题的模块主要是对推文数据进行主题的挖掘,这些推文数据可以来自抓取模块中实时获取的Twitter数据,也可以来自数据存储模块中获取历史的数据。通过对数据的实时计算,计算出主题词以及最具有代表性的推文,并按照话题的热度进行排序,返回给前台页面。

对于在线的数据流,本文使用了多线程,一个线程进行调用Stream API请求,一个线程进行WOLDA模型的计算,一个线程负责接收WOLDA的结果。具体的流程如下:

 

其中,涉及到了线程安全,如从结果队列中取出数据的时候,需要对其进行加锁等。

使用在线数据时,默认对每分钟的数据进行计算。采用一分钟的计算间隔是基于如下几点考虑:(1)若时间过短,那么推文数过少,更新没有太多的意义(2)若设置时间过长,则可能无法第一时间捕捉到紧急的话题等。因此,本文采用一分钟的间隔,这样可以保证话题的实时性又不会有过多无用的计算。

此外,用户可自定义WOLDA算法的相应参数,如时间窗口L,主题数K等。

情感分析模块

情感分析模块调用了抓取模块,将用户待查询的关键字作为参数,然后对Twitter返回的推文进行情感分析。将推文进行情感分析之后,并且返回一些参考的推文在前台展示。

这其中,需要用到之前的情感分析算法,只不过分类器是已经训练好的,只需要对推文进行相应的预测即可。

WEB模块

WEB模块主要作用是用户交互,包括了用户的界面、用户自定义的参数处理、结果的可视化等。

为了改善用户体验,使用了AJAX技术,在获取服务器分析的结果时,添加等待效果。(见 为AJAX添加等待效果

此外,利用了Google Map API在地图上点击来获取地区的经纬度,方便对某地区进行话题追踪。(见 使用google map API获取经纬度)

界面展示

 

总结与展望

主要内容总结

  1. 提出了WOLDA算法,该算法改进自Hoffman的OLDA算法。WOLDA算法使用动态的词库,能更好的处理流式数据并进行周期性的更新,并且该方式资源占用稳定,不随时间的增大而无限的增大,保证效率和对新话题的敏感程度。同时,提出了最具有代表性推文的计算方法,方便对于主题的理解和分析。
  2. 结合了基于情感词典的方法和基于机器学习的方法,将情感词典作为分类器的一部分特征,在SemEval2016最新的结果中,取得较为靠前的排名。
  3. 提出了Twitter进行可视化的方法,包括简单的统计、主题模型结果的可视化、情感分析结果的可视化。
  4. 基于本文中算法和可视化的方法,设计并实现了Twitter数据挖掘与可视化平台。

工作展望

本文所涉及的相关研究仍有不足,为此,以下列出了主要可以改进的内容:

热点话题方面

  1. 本文WOLDA仍需要手动的指定主题的个数K,这个K值将影响结果的好坏,K如果设置过大,那么原本属于一个主题的将会被拆分成多个主题;若设置过小,则多个主题可能会被合并为一个。为此,需要合适的方法来动态的设置主题个数。
  2. WOLDA算法本质上仍是一个LDA模型,无法克服LDA对于Twitter这样的的短文本效果不佳的状况。它不能简单的采用LDA-AT的模型进行改进,因为在短时间内,同一作者的推文往往数量极少,因此可能需要对整个模型本身进行重构。
  3. 推文中有大量的无意义的内容,可以进一步使用命名实体识别(NER)来进行去除。

情感分析方面

  1. 进一步的提高对于反语分类的精度。
  2. 可以采用word2vector来代替ngram来表示词的特征
  3. 情感分析的对象可以不受限于文本,Twitter的图片等多媒体信息也是可以进行研究的。

数据挖掘系统

  1. 进一步提高系统的稳定性与用户操作的便利性。
  2. 随着数据的不断增多,对于数据存储,由于采用MongoDB,可以方便的采用数据分片的方式来解决。相应的算法可以考虑移植到Spark上运行,提高对海量数据的运算能力。

除了以上的几个改进方面外,本论文只探讨了Twitter下的数据挖掘,未来可以转向对新浪微博进行相关的研究。

本文的代码已在Github开源:  twitterDataMining

 

Twitter数据挖掘及其可视化,首发于 细语呢喃

防范 CSRF 跨站请求伪造

$
0
0

CSRF(Cross-site request forgery,中文为 跨站请求伪造)是一种利用网站可信用户的权限去执行未授权的命令的一种恶意攻击。通过 伪装可信用户的请求来利用信任该用户的网站,这种攻击方式虽然不是很流行,但是却难以防范,其危害也不比其他安全漏洞小。

本文将简要介绍CSRF产生的原因以及利用方式,然后对如何避免这种攻击方式提供一些可供参考的方案,希望广大程序猿们都能够对这种攻击方式有所了解,避免自己开发的应用被别人利用。

CSRF也称作 one-click attack或者 session riding,其简写有时候也会使用 XSRF

什么是CSRF?

简单点说,CSRF攻击就是 攻击者利用受害者的身份,以受害者的名义发送恶意请求。与XSS(Cross-site scripting,跨站脚本攻击)不同的是,XSS的目的是获取用户的身份信息,攻击者窃取到的是用户的身份(session/cookie),而CSRF则是利用用户当前的身份去做一些未经过授权的操作。

CSRF攻击最早在2001年被发现,由于它的请求是从用户的IP地址发起的,因此在服务器上的web日志中可能无法检测到是否受到了CSRF攻击,正是由于它的这种隐蔽性,很长时间以来都没有被公开的报告出来,直到2007年才真正的被人们所重视。

CSRF有哪些危害

CSRF可以盗用受害者的身份,完成受害者在web浏览器有权限进行的任何操作,想想吧,能做的事情太多了。

  • 以你的名义发送诈骗邮件,消息
  • 用你的账号购买商品
  • 用你的名义完成虚拟货币转账
  • 泄露个人隐私

产生原理以及利用方式

要完成一个CSRF攻击,必须具备以下几个条件:

  • 受害者已经登录到了目标网站(你的网站)并且没有退出
  • 受害者有意或者无意的访问了攻击者发布的页面或者链接地址

(图片来自网络,出处不明,百度来的)

整个步骤大致是这个样子的:

  1. 用户小明在你的网站A上面登录了,A返回了一个session ID(使用cookie存储)
  2. 小明的浏览器保持着在A网站的登录状态,事实上几乎所有的网站都是这样做的,一般至少是用户关闭浏览器之前用户的会话是不会结束的
  3. 攻击者小强给小明发送了一个链接地址,小明打开了这个地址,查看了网页的内容
  4. 小明在打开这个地址的时候,这个页面已经自动的对网站A发送了一个请求,这时候因为A网站没有退出,因此只要请求的地址是A的就会携带A的cookie信息,也就是使用A与小明之间的会话
  5. 这时候A网站肯定是不知道这个请求其实是小强伪造的网页上发送的,而是误以为小明就是要这样操作,这样小强就可以随意的更改小明在A上的信息,以小明的身份在A网站上进行操作

利用方式

利用CSRF攻击,主要包含两种方式,一种是基于GET请求方式的利用,另一种是基于POST请求方式的利用。

GET请求利用

使用GET请求方式的利用是最简单的一种利用方式,其隐患的来源主要是由于在开发系统的时候没有按照HTTP动词的正确使用方式来使用造成的。 对于GET请求来说,它所发起的请求应该是只读的,不允许对网站的任何内容进行修改

但是事实上并不是如此,很多网站在开发的时候,研发人员错误的认为GET/POST的使用区别仅仅是在于发送请求的数据是在Body中还是在请求地址中,以及请求内容的大小不同。对于一些危险的操作比如删除文章,用户授权等允许使用GET方式发送请求,在请求参数中加上文章或者用户的ID,这样就造成了只要请求地址被调用,数据就会产生修改。

现在假设攻击者(用户ID=121)想将自己的身份添加为网站的管理员,他在网站A上面发了一个帖子,里面包含一张图片,其地址为 http://a.com/user/grant_super_user/121

<img src="http://a.com/user/grant_super_user/121"/>

设想管理员看到这个帖子的时候,这个图片肯定会自动加载显示的。于是在管理员不知情的情况下,一个赋予用户管理员权限的操作已经悄悄的以他的身份执行了。这时候攻击者121就获取到了网站的管理员权限。

POST请求利用

相对于GET方式的利用,POST方式的利用更加复杂一些,难度也大了一些。攻击者需要伪造一个能够自动提交的表单来发送POST请求。

//

只要想办法实现用户访问的时候自动提交表单就可以了。

如何防范

防范原理

防范CSRF攻击,其实本质就是要求网站 能够识别出哪些请求是非正常用户主动发起的。这就要求我们 在请求中嵌入一些额外的授权数据,让网站服务器能够区分出这些未授权的请求,比如说在请求参数中添加一个字段,这个字段的值从登录用户的Cookie或者页面中获取的(这个字段的值必须对每个用户来说是随机的,不能有规律可循)。攻击者伪造请求的时候是无法获取页面中与登录用户有关的一个随机值或者用户当前cookie中的内容的,因此就可以避免这种攻击。

防范技术

Synchronizer token pattern

令牌同步模式(Synchronizer token pattern,简称STP)是在用户请求的页面中的所有表单中嵌入一个token,在服务端验证这个token的技术。token可以是任意的内容,但是一定要保证无法被攻击者猜测到或者查询到。攻击者在请求中无法使用正确的token,因此可以判断出未授权的请求。

Cookie-to-Header Token

对于使用Js作为主要交互技术的网站,将CSRF的token写入到cookie中

Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/

然后使用javascript读取token的值,在发送http请求的时候将其作为请求的header

X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql

最后服务器验证请求头中的token是否合法。

验证码

使用验证码可以杜绝CSRF攻击,但是这种方式要求每个请求都输入一个验证码,显然没有哪个网站愿意使用这种粗暴的方式,用户体验太差,用户会疯掉的。

简单实现STP

首先在index.php中,创建一个表单,在表单中,我们将session中存储的token放入到隐藏域,这样,表单提交的时候token会随表单一起提交

<?php
$token = sha1(uniqid(rand(), true));
$_SESSION['token'] = $token;
?><form action="buy.php" method="post"><input type="hidden" name="token" value="<?=$token; ?>" />
    ... 表单内容
</form>

在服务端校验请求参数的 buy.php中,对表单提交过来的token与session中存储的token进行比对,如果一致说明token是有效的

<code><?php if ($_POST['token'] != $_SESSION['token']) {
    // TOKEN无效
    throw new \Exception('Token无效,请求为伪造请求');
}
// TOKEN有效,表单内容处理
</code?></code>

对于攻击者来说,在伪造请求的时候是无法获取到用户页面中的这个 token值的,因此就可以识别出其创建的伪造请求。

解析Laravel框架中的VerifyCSRFToken中间件

在Laravel框架中,使用了 VerifyCSRFToken这个中间件来防范CSRF攻击。

在页面的表单中使用 {{ CSRF_field() }}来生成token,该函数会在表单中添加一个名为 _token的隐藏域,该隐藏域的值为Laravel生成的token,Laravel使用随机生成的40个字符作为防范CSRF攻击的token。

$this->put('_token', Str::random(40));

如果请求是ajax异步请求,可以在 meta标签中添加token

<meta name="CSRF-token" content="{{ CSRF_token() }}"/>

使用 jquery作为前端的框架时候,可以通过以下配置将该值添加到所有的异步请求头中

$.ajaxSetup({
    headers: {'X-CSRF-TOKEN': $('meta[name="CSRF-token"]').attr('content')
    }
});

在启用session的时候,Laravel会生成一个名为 _token的值存储到session中。而使用前面两种方式在页面中加入的token就是使用的这一个值。在用户请求到来时, VerifyCSRFToken中间件会对符合条件的请求进行CSRF检查

if (
  $this->isReading($request) ||
  $this->runningUnitTests() ||
  $this->shouldPassThrough($request) ||
  $this->tokensMatch($request)
) {
  return $this->addCookieToResponse($request, $next($request));
}

throw new TokenMismatchException;

if语句中有四个条件,只要任何一个条件结果为 true则任何该请求是合法的,否则就会抛出 TokenMismatchException异常,告诉用户请求不合法,存在CSRF攻击。

第一个条件 $this->isReading($request)用来检查请求是否会对数据产生修改

protected function isReading($request)
{
    return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}

这里判断了请求方式,如果是 HEADGETOPTIONS这三种请求方式则直接放行。你可能会感到疑惑,为什么GET请求也要放行呢?这是因为Laravel认为这三个请求都是请求查询数据的, 如果一个请求是使用GET方式,那无论请求多少次,无论请求参数如何,都不应该最数据做任何修改

第二个条件顾名思义是对单元测试进行放行,第三个是为开发者提供了一个可以对某些请求添加例外的功能,最后一个 $this->tokensMatch($request)则是真正起作用的一个,它是Laravel防范CSRF攻击的关键

$sessionToken = $request->session()->token();
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
  $token = $this->encrypter->decrypt($header);
}

if (! is_string($sessionToken) || ! is_string($token)) {
  return false;
}

return hash_equals($sessionToken, $token);

Laravel会从请求中读取 _token参数的的值,这个值就是在前面表单中添加的 CSRF_field()函数生成的。如果请求是异步的,那么会读取 X-CSRF-TOKEN请求头,从请求头中读取token的值。

最后使用 hash_equals函数验证请求参数中提供的token值和session中存储的token值是否一致,如果一致则说明请求是合法的。

你可能注意到,这个检查过程中也会读取一个名为 X-XSRF-TOKEN的请求头,这个值是为了提供对一些javascript框架的支持(比如Angular),它们会自动的对异步请求中添加该请求头,而该值是从Cookie中的 XSRF-TOKEN中读取的,因此在每个请求结束的时候,Laravel会发送给客户端一个名为 XSRF-TOKEN的Cookie值

$response->headers->setCookie(
    new Cookie('XSRF-TOKEN', $request->session()->token(), time() + 60 * $config['lifetime'],
        $config['path'], $config['domain'], $config['secure'], false
    )
);

写在最后

本文只是对CSRF做了一个简单的介绍,主要是侧重于CSRF是什么以及如何应对CSRF攻击。有一个事实是我们无法回避的: 没有绝对安全的系统,你有一千种防御对策,攻击者就有一千零一种攻击方式,但不管如何,我们都要尽最大的努力去将攻击者拦截在门外。如果希望深入了解如何发起一个CSRF攻击,可以参考一下这篇文章 从零开始学CSRF。

作为一名web方向的研发人员,无论你是从事业务逻辑开发还是做单纯的技术研究,了解一些安全方面的知识都是很有必要的,多关注一些安全方向的动态,了解常见的攻击方式以及应对策略,必将在你成长为一名大牛的路上为你“推波助澜”。

参考

防范 CSRF 跨站请求伪造,首发于 文章 - 伯乐在线

Viewing all 15907 articles
Browse latest View live
<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>