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

前端工程与性能优化

$
0
0
作者:walter
book

每个参与过开发企业级 web 应用的前端工程师或许都曾思考过前端性能优化方面的问题。我们有雅虎 14 条性能优化原则,还有两本很经典的性能优化指导书:《高性能网站建设指南》、《高性能网站建设进阶指南》。经验丰富的工程师对于前端性能优化方法耳濡目染,基本都能一一列举出来。这些性能优化原则大概是在 7 年前提出的,对于 web 性能优化至今都有非常重要的指导意义。

然而,对于构建大型 web 应用的团队来说,要坚持贯彻这些优化原则并不是一件十分容易的事。因为优化原则中很多要求与工程管理相违背,比如“把 css 放在头部”和“把 js 放在尾部”这两条原则,我们不能让整个团队的工程师在写样式和脚本引用的时候都去修改同一份的页面文件。这会严重影响团队成员间并行开发的效率,尤其是在团队有版本管理的情况下,每天要花大量的时间进行代码修改合并,这项成本是难以接受的。因此在前端工程界,总会看到周期性的性能优化工作,辛勤的前端工程师们每到月圆之夜就会倾巢出动根据优化原则做一次最佳实践。

本文从一个全新的视角来思考 web 性能优化与前端工程之间的关系,通过解读百度前端集成解决方案小组(F.I.S)在打造高性能前端架构并统一百度 40 多条前端产品线的过程中所经历的技术尝试,揭示前端性能优化在前端架构及开发工具设计层面的实现思路。

性能优化原则及分类

笔者先假设本文的读者是有前端开发经验的工程师,并对企业级 web 应用开发及性能优化有一定的思考。因此我不会重复介绍雅虎 14 条性能优化原则,如果您没有这些前续知识的,请移步 这里来学习。

首先,我们把雅虎 14 条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点做一次梳理,如果按照优化方向分类可以得到这样一张表格:

优化方向优化手段
请求数量合并脚本和样式表,CSS Sprites,拆分初始化负载,划分主域
请求带宽开启 GZip,精简 JavaScript,移除重复脚本,图像优化
缓存利用使用 CDN,使用外部 JavaScript 和 CSS,添加 Expires 头,减少 DNS 查找,配置 ETag,使 AjaX 可缓存
页面结构将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出
代码校验避免 CSS 表达式,避免重定向

目前大多数前端团队可以利用 yui compressor或者 google closure compiler等压缩工具很容易做到“精简 javascript ”这条原则,同样的,也可以使用图片压缩工具对图像进行压缩,实现“图像优化”原则,这两条原则是对单个资源的处理,因此不会引起任何工程方面的问题;很多团队也通过引入代码校验流程来确保实现“避免 css 表达式”和“避免重定向”原则;目前绝大多数互联网公司也已经开启了服务端的 Gzip 压缩,并使用 CDN 实现静态资源的缓存和快速访问;一些技术实力雄厚的前端团队甚至研发出了自动 CSS Sprites 工具,解决了 CSS Sprites 在工程维护方面的难题。使用“查找 - 替换”思路,我们似乎也可以很好的实现“划分主域”原则。

我们把以上这些已经成熟应用到实际生产中的优化手段去除掉,留下那些还没有很好实现的优化原则,再来回顾一下之前的性能优化分类:

优化方向优化手段
请求数量合并脚本和样式表,拆分初始化负载
请求带宽移除重复脚本
缓存利用添加 Expires 头,配置 ETag,使 Ajax 可缓存
页面结构将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

诚然,不可否认现在有很多顶尖的前端团队可以将上述还剩下的优化原则也都一一解决,但业界大多数团队都还没能很好的解决这些问题,因此接下来本文将就这些原则的解决方案做进一步的分析与讲解,从而为那些还没有进入前端工业化开发的团队提供一些基础技术建设意见,也借此机会与业界顶尖的前端团队在工业化工程化方向上交流一下彼此的心得。

静态资源版本更新与缓存

如表格 2 所示,在“缓存利用”分类中保留了“添加 Expires 头”和“配置 ETag ”两项,或许有些人会质疑,明明这两项只要配置了服务器的相关选项就可以实现,为什么说它们难以解决呢?确实,开启这两项很容易,但开启了缓存后,我们的项目就开始面临另一个挑战:如何更新这些缓存。

相信大多数团队也找到了类似的答案,它和《高性能网站建设指南》关于“添加 Expires 头”所说的原则一样——修订文件名。即:

思路没错,但要怎么改变链接呢?变成什么样的链接才能有效更新缓存,又能最大限度避免那些没有修改过的文件缓存不失效呢?

先来看看现在一般前端团队的做法:

<script type="text/javascript" src="a.js?t=20130825"></script>

或者

<script type="text/javascript" src="a.js?v=1.0.0"></script>

大家会采用添加 query 的形式修改链接。这样做是比较直观的解决方案,但在访问量较大的网站,这么做可能将面临一些新的问题。

通常一个大型的 web 应用几乎每天都会有迭代和更新,发布新版本也就是发布新的静态资源和页面的过程。以上述代码为例,假设现在线上运行着 index.html 文件,并且使用了线上的 a.js 资源。index.html 的内容为:

<script type="text/javascript" src="a.js?v=1.0.0"></script>

这次我们更新了页面中的一些内容,得到一个 index.html 文件,并开发了新的与之匹配的 a.js 资源来完成页面交互,新的 index.html 文件的内容因此而变成了:

<script type="text/javascript" src="a.js?v=1.0.1"></script>

好了,现在要开始将两份新的文件发布到线上去。可以看到,a.html 和 a.js 的资源实际上是要覆盖线上的同名文件的。不管怎样,在发布的过程中,index.html 和 a.js 总有一个先后的顺序,从而中间出现一段或大或小的时间间隔。对于一个大型互联网应用来说即使在一个很小的时间间隔内,都有可能出现新用户访问,而在这个时间间隔中访问了网站的用户会发生什么情况呢:

  1. 如果先覆盖 index.html,后覆盖 a.js,用户在这个时间间隙访问,会得到新的 index.html 配合旧的 a.js 的情况,从而出现错误的页面。
  2. 如果先覆盖 a.js,后覆盖 index.html,用户在这个间隙访问,会得到旧的 index.html 配合新的 a.js 的情况,从而也出现了错误的页面。

这就是为什么大型 web 应用在版本上线的过程中经常会较集中的出现前端报错日志的原因,也是一些互联网公司选择加班到半夜等待访问低峰期再上线的原因之一。此外,由于静态资源文件版本更新是“覆盖式”的,而页面需要通过修改 query 来更新,对于使用 CDN 缓存的 web 产品来说,还可能面临 CDN 缓存攻击的问题。我们再来观察一下前面说的版本更新手段:

<script type="text/javascript" src="a.js?v=1.0.0"></script>

我们不难预测,a.js 的下一个版本是“ 1.0.1 ”,那么就可以刻意构造一串这样的请求“ a.js?v=1.0.1 ”、“ a.js?v=1.0.2 ”、……让 CDN 将当前的资源缓存为“未来的版本”。这样当这个页面所用的资源有更新时,即使更改了链接地址,也会因为 CDN 的原因返回给用户旧版本的静态资源,从而造成页面错误。即便不是刻意制造的攻击,在上线间隙出现访问也可能导致区域性的 CDN 缓存错误。

此外,当版本有更新时,修改所有引用链接也是一件与工程管理相悖的事,至少我们需要一个可以“查找 - 替换”的工具来自动化的解决版本号修改的问题。

对付这个问题,目前来说最优方案就是 基于文件内容的 hash 版本冗余机制 了。也就是说,我们希望工程师源码是这么写的:

<script type="text/javascript" src="a.js"></script>

但是线上代码是这样的:

<script type="text/javascript" src="a_8244e91.js"></script>

其中”_82244e91 ”这串字符是根据 a.js 的文件内容进行 hash 运算得到的,只有文件内容发生变化了才会有更改。由于版本序列是与文件名写在一起的,而不是同名文件覆盖,因此不会出现上述说的那些问题。那么这么做都有哪些好处呢?

  1. 线上的 a.js 不是同名文件覆盖,而是文件名 +hash 的冗余,所以可以先上线静态资源,再上线 html 页面,不存在间隙问题;
  2. 遇到问题回滚版本的时候,无需回滚 a.js,只须回滚页面即可;
  3. 由于静态资源版本号是文件内容的 hash,因此所有静态资源可以开启永久强缓存,只有更新了内容的文件才会缓存失效,缓存利用率大增;
  4. 修改静态资源后会在线上产生新的文件,一个文件对应一个版本,因此不会受到构造 CDN 缓存形式的攻击

虽然这种方案是相比之下最完美的解决方案,但它无法通过手工的形式来维护,因为要依靠手工的形式来计算和替换 hash 只,并生成相应的文件将是一项非常繁琐且容易出错的工作。因此,我们需要借助工具。有了这样的思路,我们下面就来了解一下 fis 是如何完成这项工作的。

首先,之所以有这种工具需求,完全是因为 web 应用运行的根本机制决定的:web 应用所需的资源是以字面的形式通知浏览器下载而聚合在一起运行的。这种资源加载策略使得 web 应用从本质上区别于传统桌面应用的版本更新方式,也是大型 web 应用需要工具处理的最根本原因。为了实现资源定位的字面量替换操作,前端构建工具理论上需要识别所有资源定位的标记,其中包括:

  • css 中的@import url(path)、background:url(path)、backgournd-image:url(path)、filter 中的 src
  • js 中的自定义资源定位函数,在 fis 中我们将其规定为__uri(path)。
  • html 中的 <script src=” path ”><link href=” path ”><img src=” path ”>、已经 embed、audio、video、object 等具有资源加载功能的标签。

为了工程上的维护方便,我们希望工程师在源码中写的是相对路径,而工具可以将其替换为线上的绝对路径,从而避免相对路径定位错误的问题(比如 js 中需要定位图片路径时不能使用相对路径的情况)。

image2

fis 有一个非常棒的资源定位系统,它是根据用户自己的配置来指定资源发布后的地址,然后由 fis 的资源定位系统识别文件中的定位标记,计算内容 hash,并根据配置替换为上线后的绝对 url 路径。

要想实现具备 hash 版本生成功能的构建工具不是“查找 - 替换”这么简单的,我们考虑这样一种情况:

image3

由于我们的资源版本号是通过对文件内容进行 hash 运算得到,如上图所示,index.html 中引用的 a.css 文件的内容其实也包含了 a.png 的 hash 运算结果,因此我们在修改 index.html 中 a.css 的引用时,不能直接计算 a.css 的内容 hash,而是要先计算出 a.png 的内容 hash,替换 a.css 中的引用,得到了 a.css 的最终内容,再做 hash 运算,最后替换 index.html 中的引用。

这意味着构建工具需要具备“递归编译”的能力,这也是为什么 fis 团队不得不放弃 gruntjs 等 task-based 系统的根本原因。针对前端项目的构建工具必须是具备递归处理能力的。此外,由于文件之间的交叉引用等原因,fis 构建工具还实现了构建缓存等机制,以提升构建速度。

在解决了基于内容 hash 的版本更新问题之后,我们可以将所有前端静态资源开启永久强缓存,每次版本发布都可以首先让静态资源全量上线,再进一步上线模板或者页面文件,再也不用担心各种缓存和时间间隙的问题了!

静态资源管理与模板框架

让我们再来看看前面的优化原则表还剩些什么:

优化方向优化手段
请求数量合并脚本和样式表,拆分初始化负载
请求带宽移除重复脚本
缓存利用使 Ajax 可缓存
页面结构将样式表放在顶部,将脚本放在底部,尽早刷新文档的输出

很不幸,剩下的优化原则都不是使用工具就能很好实现的。或许有人会辩驳:“我用某某工具可以实现脚本和样式表合并”。嗯,必须承认,使用工具进行资源合并并替换引用或许是一个不错的办法,但在大型 web 应用,这种方式有一些非常严重的缺陷,来看一个很熟悉的例子:

image4

某个 web 产品页面有 A、B、C 三个资源

image5

工程师根据“减少 HTTP 请求”的优化原则合并了资源

image6

产品经理要求 C 模块按需出现,此时 C 资源已出现多余的可能

image7

C 模块不再需要了,注释掉吧!但 C 资源通常不敢轻易剔除

image8

不知不觉中,性能优化变成了性能恶化……

事实上,使用工具在线下进行静态资源合并是无法解决资源按需加载的问题的。如果解决不了按需加载,则势必会导致资源的冗余;此外,线下通过工具实现的资源合并通常会使得资源加载和使用的分离,比如在页面头部或配置文件中写资源引用及合并信息,而用到这些资源的 html 组件写在了页面其他地方,这种书写方式在工程上非常容易引起维护不同步的问题,导致使用资源的代码删除了,引用资源的代码却还在的情况。因此,在工业上要实现资源合并至少要满足如下需求:

  1. 确实能减少 HTTP 请求,这是基本要求(合并)
  2. 在使用资源的地方引用资源(就近依赖),不使用不加载(按需)
  3. 虽然资源引用不是集中书写的,但资源引用的代码最终还能出现在页面头部(css)或尾部(js)
  4. 能够避免重复加载资源(去重)

将以上要求综合考虑,不难发现,单纯依靠前端技术或者工具处理的是很难达到这些理想要求的。现代大型 web 应用所展示的页面绝大多数都是使用服务端动态语言拼接生成的。有的产品使用模板引擎,比如 smarty、velocity,有的则干脆直接使用动态语言,比如 php、python。无论使用哪种方式实现,前端工程师开发的 html 绝大多数最终都不是以静态的 html 在线上运行的,接下来我会讲述一种新的模板架构设计,用以实现前面说到那些性能优化原则,同时满足工程开发和维护的需要,这种架构设计的核心思想就是:

考虑一段这样的页面代码:

<html><head><title>hello world</title><link rel="stylesheet" type="text/css" href="A.css"><link rel="stylesheet" type="text/css" href="B.css"><link rel="stylesheet" type="text/css" href="C.css"></head><body><div>html of A</div><div>html of B</div><div>html of C</div></body></html>

根据资源合并需求中的第二项,我们希望资源引用与使用能尽量靠近,这样将来维护起来会更容易一些,因此,理想的源码是:

<html><head><title>hello world</title></head><body><link rel="stylesheet" type="text/css" href="A.css"><div>html of A</div><link rel="stylesheet" type="text/css" href="B.css"><div>html of B</div><link rel="stylesheet" type="text/css" href="C.css"><div>html of C</div></body></html>

当然,把这样的页面直接送达给浏览器用户是会有严重的页面闪烁问题的,所以我们实际上仍然希望最终页面输出的结果还是如最开始的截图一样,将 css 放在头部输出。这就意味着,页面结构需要有一些调整,并且有能力收集资源加载需求,那么我们考虑一下这样的源码:

<html><head><title>hello world</title><!--[CSS LINKS PLACEHOLDER]--></head><body>
        {require name="A.css"}<div>html of A</div>
        {require name="B.css"}<div>html of B</div>
        {require name="C.css"}<div>html of C</div></body></html>

在页面的头部插入一个 html 注释“ <!--[CSS LINKS PLACEHOLDER]-->”作为占位,而将原来字面书写的资源引用改成模板接口(require)调用,该接口负责收集页面所需资源。require 接口实现非常简单,就是准备一个数组,收集资源引用,并且可以去重。最后在页面输出的前一刻,我们将 require 在运行时收集到的“ A.css ”、“ B.css ”、“ C.css ”三个资源拼接成 html 标签,替换掉注释占位“ <!--[CSS LINKS PLACEHOLDER]-->”,从而得到我们需要的页面结构。

经过 fis 团队的总结,我们发现模板层面只要实现三个开发接口,既可以比较完美的实现目前遗留的大部分性能优化原则,这三个接口分别是:

  1. require(String id):收集资源加载需求的接口,参数是资源 id。
  2. widget(String template_id):加载拆分成小组件模板的接口。你可以叫它为 load、component 或者 pagelet 之类的。总之,我们需要一个接口把一个大的页面模板拆分成一个个的小部分来维护,最后在原来的大页面以组件为单位来加载这些小部件。
  3. script(String code):收集写在模板中的 js 脚本,使之出现的页面底部,从而实现性能优化原则中的“将 js 放在页面底部”原则。

实现了这些接口之后,一个重构后的模板页面的源代码可能看起来就是这样的了:

<html><head><title>hello world</title><!--[CSS LINKS PLACEHOLDER]-->
        {require name="jquery.js"}
        {require name="bootstrap.css"}</head><body>
        {require name="A/A.css"}{widget name="A/A.tpl"}
        {script}console.log('A loaded'){/script}


        {require name="B/B.css"}{widget name="B/B.tpl"}
        {require name="C/C.css"}{widget name="C/C.tpl"}<!--[SCRIPTS PLACEHOLDER]--></body></html>

而最终在模板解析的过程中,资源收集与去重、页面 script 收集、占位符替换操作,最终从服务端发送出来的 html 代码为:

<html><head><title>hello world</title><link rel="stylesheet" type="text/css" href="bootstrap.css"><link rel="stylesheet" type="text/css" href="A/A.css"><link rel="stylesheet" type="text/css" href="B/B.css"><link rel="stylesheet" type="text/css" href="C/C.css"></head><body><div>html of A</div><div>html of B</div><div>html of C</div><script type="text/javascript" src="jquery.js"></script><script type="text/javascript">console.log('A loaded');</script></body></html>

不难看出,我们目前已经实现了“按需加载”,“将脚本放在底部”,“将样式表放在头部”三项优化原则。

前面讲到静态资源在上线后需要添加 hash 戳作为版本标识,那么这种使用模板语言来收集的静态资源该如何实现这项功能呢?答案是:静态资源依赖关系表。 假设前面讲到的模板源代码所对应的目录结构为下图所示:

image9

那么我们可以使用工具扫描整个 project 目录,然后创建一张资源表,同时记录每个资源的部署路径,可以得到这样的一张表:

{"res": {"A/A.css": {"uri": "/A/A_1688c82.css","type": "css"
        },"B/B.css": {"uri": "/B/B_52923ed.css","type": "css"
        },"C/C.css": {"uri": "/C/C_6dda653.css","type": "css"
        },"bootstrap.css": {"uri": "bootstrap_08f2256.css","type": "css"
        },"jquery.js": {"uri": "jquery_9155343.css","type": "js"
        },
    },"pkg": {}
}

基于这张表,我们就很容易实现 {require name=” id ”} 这个模板接口了。只须查表即可。比如执行{require name=” jquery.js ”},查表得到它的 url 是“/jquery_9151577.js ”,声明一个数组收集起来就好了。这样,整个页面执行完毕之后,收集资源加载需求,并替换页面的占位符,即可实现资源的 hash 定位,得到:

<html><head><title>hello world</title><link rel="stylesheet" type="text/css" href="bootstrap_08f2256.css"><link rel="stylesheet" type="text/css" href="A/A_1688c82.css"><link rel="stylesheet" type="text/css" href="B/B_52923ed.css"><link rel="stylesheet" type="text/css" href="C/C_6dda653.css"></head><body><div>html of A</div><div>html of B</div><div>html of C</div><script type="text/javascript" src="jquery_9155343.js"></script><script type="text/javascript">console.log('A loaded');</script></body></html>

接下来,我们讨论如何在基于表的设计思想上是如何实现静态资源合并的。或许有些团队使用过 combo 服务,也就是我们在最终拼接生成页面资源引用的时候,并不是生成多个独立的 link 标签,而是将资源地址拼接成一个 url 路径,请求一种线上的动态资源合并服务,从而实现减少 HTTP 请求的需求,比如:

<html><head><title>hello world</title><link rel="stylesheet" type="text/css" href="/combo?files=bootstrap_08f2256.css,A/A_1688c82.css,B/B_52923ed.css,C/C_6dda653.css"></head><body><div>html of A</div><div>html of B</div><div>html of C</div><script type="text/javascript" src="jquery_9155343.js"></script><script type="text/javascript">console.log('A loaded');</script></body></html>

这个“/combo?files=file1,file2,file3,…”的 url 请求响应就是动态 combo 服务提供的,它的原理很简单,就是根据 get 请求的 files 参数找到对应的多个文件,合并成一个文件来响应请求,并将其缓存,以加快访问速度。

这种方法很巧妙,有些服务器甚至直接集成了这类模块来方便的开启此项服务,这种做法也是大多数大型 web 应用的资源合并做法。但它也存在一些缺陷:

  1. 浏览器有 url 长度限制,因此不能无限制的合并资源。
  2. 如果用户在网站内有公共资源的两个页面间跳转访问,由于两个页面的 combo 的 url 不一样导致用户不能利用浏览器缓存来加快对公共资源的访问速度。

对于上述第二条缺陷,可以举个例子来看说明:

  • 假设网站有两个页面 A 和 B
  • A 页面使用了 a,b,c,d 四个资源
  • B 页面使用了 a,b,e,f 四个资源
  • 如果使用 combo 服务,我们会得:
    • A 页面的资源引用为:/combo?files=a,b,c,d
    • B 页面的资源引用为:/combo?files=a,b,e,f
  • 两个页面引用的资源是不同的 url,因此浏览器会请求两个合并后的资源文件,跨页面访问没能很好的利用 a、b 这两个资源的缓存。

很明显,如果 combo 服务能聪明的知道 A 页面使用的资源引用为“/combo?files=a,b ”和“/combo?files=c,d ”,而 B 页面使用的资源引用为“/combo?files=a,b ”,“/combo?files=e,f ”就好了。这样当用户在访问 A 页面之后再访问 B 页面时,只需要下载 B 页面的第二个 combo 文件即可,第一个文件已经在访问 A 页面时缓存好了的。

基于这样的思考,fis 在资源表上新增了一个字段,取名为“ pkg ”,就是资源合并生成的新资源,表的结构会变成:

{"res": {"A/A.css": {"uri": "/A/A_1688c82.css","type": "css"
        },"B/B.css": {"uri": "/B/B_52923ed.css","type": "css"
        },"C/C.css": {"uri": "/C/C_6dda653.css","type": "css"
        },"bootstrap.css": {"uri": "bootstrap_08f2256.css","type": "css"
        },"jquery.js": {"uri": "jquery_9155343.css","type": "js"
        },
    },"pkg": {"p0": {"uri": "/pkg/utils_b967346.css","type": "css","has": ["bootstrap.css", "A/A.css"]
        },"p1": {"uri": "/pkg/others_0d4552a.css","type": "css","has": ["B/B.css", "C/C.css"]
        }
    }
}

相比之前的表,可以看到新表中多了一个 pkg 字段,并且记录了打包后的文件所包含的独立资源。这样,我们重新设计一下{require name=” id ”}这个模板接口:在查表的时候,如果一个静态资源有 pkg 字段,那么就去加载 pkg 字段所指向的打包文件,否则加载资源本身。比如执行{require name=” bootstrap.css ”},查表得知 bootstrap.css 被打包在了“ p0 ”中,因此取出 p0 包的 url “/pkg/utils_b967346.css ”,并且记录页面已加载了“ bootstrap.css ”和“ A/A.css ”两个资源。这样一来,之前的模板代码执行之后得到的 html 就变成了:

<html><head><title>hello world</title><link rel="stylesheet" type="text/css" href="pkg/utils_b967346.css"><link rel="stylesheet" type="text/css" href="pkg/others_0d4552a.css"></head><body><div>html of A</div><div>html of B</div><div>html of C</div><script type="text/javascript" src="jquery_9155343.js"></script><script type="text/javascript">console.log('A loaded');</script></body></html>

css 资源请求数由原来的 4 个减少为 2 个。 这样的打包结果是怎么来的呢?答案是配置得到的。 我们来看一下带有打包结果的资源表的 fis 配置:

fis.config.set('pack', {'pkg/util.css': [ 'bootstrap.css', 'A/A.css'],'pkg/other.css': [ '**.css' ]
});

我们将“ bootstrap.css ”、“ A/A.css ”打包在一起,其他 css 另外打包,从而生成两个打包文件,当页面需要打包文件中的资源时,模块框架就会收集并计算出最优的资源加载结果,从而解决静态资源合并的问题。

这样做的原因是为了弥补 combo 在前面讲到的两点技术上的不足而设计的。但也不难发现这种打包策略是需要配置的,这就意味着维护成本的增加。但好在它有两个优势可以一定程度上弥补这个问题:

  1. 打包的资源只是原来独立资源的备份。打包与否不会导致资源的丢失,最多是没有合并的很好而已。
  2. 配置可以由工程师根据经验人工维护,也可以由统计日志生成,这为性能优化自适应网站设计提供了非常好的基础。

关于第二点,fis 有这样辅助系统来支持自适应打包算法:

image10

至此,我们通过基于表的静态资源管理系统和三个模板接口实现了几个重要的性能优化原则,现在我们再来回顾一下前面的性能优化原则分类表,剔除掉已经做到了的,看看还剩下哪些没做到的:

优化方向优化手段
请求数量拆分初始化负载
请求带宽拆分初始化负载
缓存利用使 Ajax 可缓存
页面结构尽早刷新文档的输出

“拆分初始化负载”的目标是将页面一开始加载时不需要执行的资源从所有资源中分离出来,等到需要的时候再加载。工程师通常没有耐心去区分资源的分类情况,但我们可以利用组件化框架接口来帮助工程师管理资源的使用。还是从例子开始思考:

<html><head><title>hello world</title>
    {require name="jquery.js"}</head><body><button id="myBtn">Click Me</button>
    {script}
        $('#myBtn').click(function(){
            var dialog = require('dialog/dialog.js');
            dialog.alert('you catch me!');
        });
    {/script}<!--[SCRIPTS PLACEHOLDER]--></body></html>

在 fis 给百度内部团队开发的架构中,如果这样书写代码,页面最终的执行结果会变成:

<html><head><title>hello world</title></head><body><button id="myBtn">Click Me</button><script type="text/javascript" src="/jquery_9151577.js"></script><script type="text/javascript" src="/dialog/dialog_ae8c228.js"></script><script type="text/javascript">
    $('#myBtn').click(function(){
        var dialog = require('dialog/dialog.js');
        dialog.alert('you catch me!');
    });</script><!--[SCRIPTS PLACEHOLDER]--></body></html>

fis 系统会分析页面中 require(id)函数的调用,并将依赖关系记录到资源表对应资源的 deps 字段中,从而在页面渲染查表时可以加载依赖的资源。但此时 dialog.js 是以 script 标签的形式同步加载的,这样会在页面初始化时出现资源的浪费。因此,fis 团队提供了 require.async 的接口,用于异步加载一些资源,源码修改为:

<html><head><title>hello world</title>
    {require name="jquery.js"}</head><body><button id="myBtn">Click Me</button>
    {script}
        $('#myBtn').click(function() {
            require.async('dialog/dialog.js', function( dialog ) {
                dialog.alert('you catch me!');
            });
        });
    {/script}<!--[SCRIPTS PLACEHOLDER]--></body></html>

这样书写之后,fis 系统会在表里以 async 字段来标准资源依赖关系是异步的。fis 提供的静态资源管理系统会将页面输出的结果修改为:

<html><head><title>hello world</title></head><body><button id="myBtn">Click Me</button><script type="text/javascript" src="/jquery_9151577.js"></script><script type="text/javascript" src="/dialog/dialog_ae8c228.js"></script><script type="text/javascript">
    $('#myBtn').click(function() {
        require.async('dialog/dialog.js', function( dialog ) {
            dialog.alert('you catch me!');
        });
    });</script><!--[SCRIPTS PLACEHOLDER]--></body></html>

dialog.js 不会在页面以 script src 的形式输出,而是变成了资源注册,这样,当页面点击按钮触发 require.async 执行的时候,async 函数才会查表找到资源的 url 并加载它,加载完毕后触发回调函数。

到目前为止,我们又以架构的形式实现了一项优化原则(拆分初始化负载),回顾我们的优化分类表,现在仅有两项没能做到了:

优化方向优化手段
缓存利用使 Ajax 可缓存
页面结构尽早刷新文档的输出

剩下的两项优化原则要做到并不容易,真正可缓存的 Ajax 在现实开发中比较少见,而尽早刷新文档的输出的情况 facebook 在 2010 年的 velocity 上提到过,就是 BigPipe 技术。当时 facebook 团队还讲到了 Quickling 和 PageCache 两项技术,其中的 PageCache 算是比较彻底的实现 Ajax 可缓存的优化原则了。fis 团队也曾与某产品线合作基于静态资源表、模板组件化等技术实现了页面的 PipeLine 输出、以及 Quickling 和 PageCache 功能,但最终效果没有达到理想的性能优化预期,因此这两个方向尚在探索中,相信在不久的将来会有新的突破。

总结

其实在前端开发工程管理领域还有很多细节值得探索和挖掘,提升前端团队生产力水平并不是一句空话,它需要我们能对前端开发及代码运行有更深刻的认识,对性能优化原则有更细致的分析与研究。fis 团队一直致力于从架构而非经验的角度实现性能优化原则;解决前端工程师开发、调试、部署中遇到的工程问题;提供组件化框架,提高代码复用率;提供开发工具集,提升工程师的开发效率。在前端工业化开发的所有环节均有可节省的人力成本,这些成本非常可观,相信现在很多大型互联网公司也都有了这样的共识。 本文只是将这个领域中很小的一部分知识的展开讨论,抛砖引玉,希望能为业界相关领域的工作者提供一些不一样的思路。欢迎关注 fis项目,对本文有任何意见或建议都可以在 fis 开源项目中进行反馈和讨论。


用HTML5里的window.postMessage在两个网页间传递数据

$
0
0

估计很少人知道HTML5 APIS里有一个 window.postMessage APIwindow.postMessage的功能是允许程序员跨域在两个窗口/frames间发送数据信息。基本上,它就像是跨域的AJAX,但不是浏览器跟服务器之间交互,而是在两个客户端之间通信。让我们来看一下 window.postMessage是如何工作的。除了IE6、IE7之外的所有浏览器都支持这个功能。

数据发送端

首先我们要做的是创建通信发起端,也就是数据源”source”。作为发起端,我们可以open一个新窗口,或创建一个iframe,往新窗口里发送数据,简单起见,我们每6秒钟发送一次,然后创建消息监听器,从目标窗口监听它反馈的信息。

//弹出一个新窗口
var domain = 'http://scriptandstyle.com';
var myPopup = window.open(domain 
            + '/windowPostMessageListener.html','myWindow');

//周期性的发送消息
setInterval(function(){
	var message = 'Hello!  The time is: ' + (new Date().getTime());
	console.log('blog.local:  sending message:  ' + message);
        //send the message and target URI
	myPopup.postMessage(message,domain);
},6000);

//监听消息反馈
window.addEventListener('message',function(event) {
	if(event.origin !== 'http://scriptandstyle.com') return;
	console.log('received response:  ',event.data);
},false);

这里我使用了 window.addEventListener,但在IE里这样是不行的,因为IE使用 window.attachEvent。如果你不想判断浏览器的类型,可以使用一些工具库,比如jQuery或Dojo。

假设你的 窗口正常的弹出来了,我们发送一条消息——需要指定URI(必要的话需要指定协议、主机、端口号等),消息接收方必须在这个指定的URI上。如果目标窗口被替换了,消息将不会发出。

我们同时创建了一个事件监听器来接收反馈信息。有一点极其重要,你一定要验证消息的来源的URI!只有在目标方合法的情况才你才能处理它发来的消息。

如果是使用iframe,代码应该这样写:

//捕获iframe
var domain = 'http://scriptandstyle.com';
var iframe = document.getElementById('myIFrame').contentWindow;

//发送消息
setInterval(function(){
	var message = 'Hello!  The time is: ' + (new Date().getTime());
	console.log('blog.local:  sending message:  ' + message);
        //send the message and target URI
	iframe.postMessage(message,domain); 
},6000);

确保你使用的是iframe的 contentWindow属性,而不是节点对象。

数据接收端

下面我们要开发的是数据接收端的页面。接收方窗口里有一个事件监听器,监听“message”事件,一样,你也需要验证消息来源方的地址。消息可以来自任何地址,要确保处理的消息是来自一个可信的地址。

//响应事件
window.addEventListener('message',function(event) {
	if(event.origin !== 'http://davidwalsh.name') return;
	console.log('message received:  ' + event.data,event);
	event.source.postMessage('holla back youngin!',event.origin);
},false);

上面的代码片段是往消息源反馈信息,确认消息已经收到。下面是几个比较重要的事件属性:

  • source– 消息源,消息的发送窗口/iframe。
  • origin– 消息源的URI(可能包含协议、域名和端口),用来验证数据源。
  • data– 发送方发送给接收方的数据。

这三个属性是消息传输中必须用到的数据。

使用window.postMessage

跟其他很web技术一样,如果你不校验数据源的合法性,那使用这种技术将会变得很危险;你的应用的安全需要你对它负责。 window.postMessage就像是PHP相对于JavaScript技术。 window.postMessage很酷,不是吗?

面向服务与微服务架构

$
0
0

背景

最近阅读了 Martin Fowler 和 James Lewis 合著的一篇文章  Microservices, 文中主要描述和探讨了最近流行起来的一种服务架构模式——微服务,和我最近几年工作的实践比较相关感觉深受启发。本文吸收了部分原文观点,结合自身实践经验来探讨下服务架构模式的演化。


面向服务架构(SOA)

面向服务架构 SOA 思想概念的提出已不是什么新鲜事,大概在10年前就有不少相关书籍介绍过。当时 java 企业应用领域 J2EE 依然是主流,应用程序被部署在庞大统一的符合 J2EE 规范的容器中运行,在单一进程中提供所有的功能。 而 SOA 提出的一些架构原则,在当时看来无疑是革命性的。 由于业已存在的大量单一庞大的应用,按照 SOA 的思想和架构原则来改造无疑相当于推翻重新开发一遍,在成本上很难接受。 因此早期的 SOA 通常和另外一个术语关联在一起——ESB(企业服务总线)。 当时在 SOA 的实施思路上无一例外的选择了 ESB 模式来整合集成大量单一庞大的应用,以保护企业前期投入成本。因此 ESB 其实是 SOA 特定历史阶段的一种实现方式。

然而,愿望总是美好的,现实却要残酷的多。过去这些年我们看到了很多实施 ESB 搞砸了的项目,投入几百万,产出几乎为 0,因此 SOA 这个概念也蒙上了不详的标签。 近几年流行起来的服务化架构,其拥护者开始拒绝使用包裹着失败阴影的 SOA 这个标签,而称其为微服务架构(Microservices Architecture Style)。但事实上 微服务架构依然是 SOA 架构思想的一种实现。


微服务架构(Microservices)

对微服务架构我们没有一个明确的定义,但简单来说微服务架构是:

采用一组服务的方式来构建一个应用,服务独立部署在不同的进程中,不同服务通过一些轻量级交互机制来通信,例如 RPC、HTTP 等,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。


微服务架构特征(Characteristics)

1. 通过服务实现组件化
传统实现组件的方式是通过库(library),传统组件是和应用一起运行在进程中,组件的局部变化意味着整个应用的重新部署。 通过服务来实现组件,意味着将应用拆散为一系列的服务运行在不同的进程中,那么单一服务的局部变化只需重新部署对应的服务进程。 另外将服务作为组件可以更明确的定义出组件的边界,因为服务之间的调用是跨进程的,清晰的边界和职责定义是设计时必须考虑的。

2. 按业务能力来划分服务与组织团队
康威定律(Conway's law)指出:

organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations.
任何设计系统的组织,最终产生的设计等同于组织之内、之间的沟通结构。

传统开发方式中,我们将工程师按技能专长分层为前端层、中间层、数据层,前端对应的角色为UI、页面构建师等,中间层对应的角色为服务端业务开发工程师,数据层对应着DBA等角色。 事实上传统应用设计架构的分层结构正反应了不同角色的沟通结构。 而微服务架构的开发模式不同于传统方式,它将应用按业务能力来划分为不同的服务,每个服务都要求在对应业务领域的全栈(从前端到后端)软件实现,从界面到数据存储到外部沟通协作等等。 因此团队的组织是跨功能的,包含实现业务所需的全面的技能。 近年兴起的全栈工程师正是因为架构和开发模式的转变而出现,当然具备全栈的工程师其实很少,但将不同领域的工程师组织为一个全栈的团队就容易的多。

3. 服务即产品
传统的应用开发都是基于项目模式的,开发团队根据一堆功能列表开发出一个软件应用并交付给客户后,该软件应用就进入维护模式,由另一个维护团队负责,开发团队的职责结束。 而微服务架构的倡导者提议避免采用这种项目模式,更倾向于让开发团队负责整个产品的全部生命周期。Amazon 对此提出了一个观点:

You buidl it, you run it.

开发团队对软件在生产环境的运行负全部责任,让服务的开发者与服务的使用者(客户)形成每天的交流反馈,来自直接客户端的反馈有助于开发者提升服务的质量。

4. 智能终端与哑管道
微服务架构抛弃了 ESB 过度复杂的业务规则编排、消息路由等。 服务作为智能终端,所有的业务智能逻辑在服务内部处理,而服务间的通信尽可能的轻量化,不添加任何额外的业务规则。

5. 去中心统一化
传统应用中倾向采用统一的技术平台或产品来解决所有问题。 不是每个问题都是钉子,也不是每个解决方案都是一个锤子。 问题有其具体性,解决方案也应有其针对性。 用最适合的技术方案去解决具体的问题,在大一统的传统应用中其实很难做到,而微服务的架构意味着,你可以针对不同的业务服务特征选择不同的技术平台或产品,有针对性的解决具体的业务问题。

6. 基础设施自动化
单一进程的传统应用被拆分为一系列的多进程服务后,意味着开发、调试、测试、集成、监控和发布的复杂度都会相应增大。 必须要有合适的自动化基础设施来支持微服务架构模式,否则开发、运维成本将大大增加。

7. Design for failure
正因为将服务独立在不同的进程中后,引入了额外的失败因素。 任何时刻对服务的调用都可能因为服务方不可用导致失败,这就要求服务的消费方需要优雅的处理此类错误。 这其实是相对传统应用开发方式的一个缺点,不过随着一些开源服务化框架的出现,对业务开发人员而言适当的屏蔽了类似的错误处理,不过开发人员依然需要知道对服务的调用是完全不同于进程内的方法或函数调用的。

8. 进化设计
一旦采用了微服务架构模式,那么在服务需要变更时我们要特别小心,服务提供者的变更可能引发服务消费者的兼容性破坏,时刻谨记保持服务契约(接口)的兼容性。 对于解耦服务消费方和服务提供方, 伯斯塔尔法则(Postel's law)特别适用:

Be conservative in what you send, be liberal in what you accept.
发送时要保守,接收时要开放。

按照伯斯塔尔法则的思想来设计实现服务调用时,发送的数据要更保守,意味着最小化的传送必要的信息,接收时更开放意味着要最大限度的容忍信息的兼容性。 多余的信息不认识可以忽略,而不应该拒绝或抛出错误。


微服务架构应用

采用微服务架构面临的第一个问题就是如何将一个单一应用拆分为多个服务。 有一个一般的原则是,单一服务提供的功能是可以独立被替换和升级的。 也就是说如果有 A 和 B 两个功能,如果 A 功能发生变化时同时 B 功能也需要变化,那么 A 和 B 这两个功能应该被划在一个服务中。

微服务架构应用的成功经验近年已越来越多,例如国外的 Amazon,Netflix,国内如阿里都采用微服务架构取得了很多正面的成功案例。 但通过上文所述微服务架构特征看出,其实微服务架构模式有利有弊,需要根据实际的业务、团队、环境进行仔细权衡利弊。 其中的服务拆分带来的额外开发、测试、运维、监控的复杂度,在现有的环境、团队下是否能够很好的支持。

另外,有人可能会说,我一开始不采用微服务架构方式,而是在单一进程内基于清晰定义的模块化方式,模块之间通过接口调用,到了适当阶段,必要的时候再将模块拆分为服务。 其实这个想法显得过于理想,因为进程内良好定义的接口通常不是很好的服务化接口。 一开始没有考虑服务化的设计方法,那么后期拆分时依然是一个痛苦的过程。


总结

正如,Martin Fowler 在其文中所说,微服务架构是否就是企业应用开发的未来,还有待时间的检验。 就目前的情况看,对此我们可以保持谨慎的乐观,这条路依然值得去探索。 实际任何的架构决策都是基于我们不完美的现状做出的,这正是架构取舍的微妙之处,超越任何的方法论。

作者:mindfloating 发表于2014-4-27 13:06:48 原文链接
阅读:0 评论:0 查看评论

在移动开发中常用的开源库总结

$
0
0

1、为什么需要开源库?

我个人觉得有以下几个原因:

1>我们的项目比较赶,但是又用到一些比较复杂的模块,这些模块不是系统自带的,或者说系统自带的满足不了需求,同时在一些开源网站上面又有类似的或者是满足我哦们需求的开源项目和库,拿来就可以减少我们很多的工作量。

2>开源库从另外一方面来说就是为了提高代码的重用性,大家使用了这个开源库,然后提交一些bug,通过大家的力量完善这个开源项目。

2、我常用的开源库?

我使用的一些开源项目主要都是在github上面很热门的项目:

图片加载:Android-Universal-Image-Loader

图片操作(放大、缩小)PhotoView

下拉刷新,加载更多:Android-PullToRefresh

导航栏:ActionBarSherlock


3、如何深入学习这些开源库(经典的开源项目)?

我个人觉得是这样一个过程:学以致用这四个字概括了整个过程。

第一层学习:如何使用这个开源项目,通过官方提供的文档、sample、当然别人用了这个开源项目也会写一些总结,可以用google搜索一些这个方面的资料,学会了这些,然后把这个项目运用到实际的项目中去。这是学以致用一

第二层学习:弄清这个开源的项目的流程和思路,基本理清这些思路,学习了它的思路和方法,我们应该可以尝试按照这种学来的思路用代码实现出来(这一步非常重要)。

第三层学习:学习这个开源项目使用的技术、代码中异常的处理、代码的参数验证、命名规则等等一些常用的技术,学习到这些,通过与平时自己的代码风格进行总结,在以后的代码中运用起来

第四层学习:学习这个开源项目的整体架构,学习它使用的设计模式。学习完以后进行总结,然后在以后的项目中运用这些知识。

第五层学习:开始找这个开源项目的bug,一般是通过在项目的中的运用以及为这个开源项目写测试用例来测出bug

第六层学习:开始修改这个开源项目,扩展这个开源项目,为这个开源项目做点贡献。


4、开源库对于开发者的利与弊?

开源项目的好处:从开发者方面来说,开源项目加快了我们开发项目的速度,提升了工作效率

开源项目的弊端:有一些开发者因为有开源项目就感觉自己不需要学习什么东西了,因为一切都可以用开源搞定,容易造成浮躁的心理,这样是很不好的。


作者:xie389124248 发表于2014-4-27 12:38:48 原文链接
阅读:9 评论:0 查看评论

Java程序挂掉的几种可能

$
0
0

今天花了一整天在跟踪一个问题,每次感觉已经快找到原因的时候发现现象又变了,我觉得从中吸取的教训可以给大家分享一下。

为了重现这个现象,我写了一个简单的例子。在本例中,先初始化了一个map,然后用一个无限循环将一些键值对插入到map里面:

class Wrapper {
      public static void main(String args[]) throws Exception {
            Map map = System.getProperties();
            Random r = new Random();
            while (true) {
        map.put(r.nextInt(), "value");
            }
      }
}

你可能也猜到了,这段代码编译执行后无法正常结束。当我用这组参数启动的话:

java -Xmx100m -XX:+UseParallelGC Wrapper

我会在终端中看到java.lang.OutOfMemoryError: GC overhead limit exceeded的异常信息。不过如果我调整一下堆大小或者是GC的类型的话,在我的Mac OS X 10.9.2 系统上用Oracle Hotspot JDK 1.7.0_45来运行,就会出现不同的情况。

比如说,我用一个较小的堆来运行这个程序,就像下面这样:

java -Xmx10m -XX:+UseParallelGC Wrapper

应用程序会抛出一段大家更熟悉的错误信息然后挂掉:java.lang.OutOfMemoryError: Java heap space。

如果你换成ParallelGC以外的GC策略的话,比如说-XX:+UseConcMarkSweepGC or -XX:+UseG1GC,你将会看到由默认的异常处理器所抛出的异常,并且你看不到堆栈信息了,因为堆已经没有空间了,甚至连异常的堆栈信息都没法填充了,因此它在创建异常的时候就挂掉了:

My Precious:examples vladimir$ java -Xmx100m -XX:+UseConcMarkSweepGC Wrapper
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"

这说明了什么?当资源紧缺的时候,你根本没法判断你的应用程序是怎么挂掉的,因此不要指望能出现你所预期的一系列错误提示。从上面这个例子中可以看到,你的程序可能会以三种完全不同的方式挂掉:

  1. GC的安全性检查失败:一旦GC花费的时间占到98%以上的话,JVM就会宣告投降了: java.lang.OutOfMemoryError: GC overhead limit exceeded。

  2. 无法为下一个操作分配足够的内存:如果无法满足下一条指令所需要分配的内存的话,你会收到一条”java.lang.OutOfMemoryError: Java heap space” 的错误信息。

  3. 你可能也总结出来了,还有一种情况是你的内存已经紧张到连JVM创建一条OutOfMemoryError异常,填充堆栈信息,打印到屏幕上这点要求都满足不了了。这种情况UncaughtExceptionHandler会捕获到这个错误,而不再走通常的错误流程。这个处理器恰如其名,当线程由于某个异常快要挂掉的时候,它开始出来收场了。出现这种情况的话,JVM会找到线程对应的 UncaughtExceptionHandler,然后调用它的uncaughtException方法。

因此当你捕获到内存不足的异常并自以为已经胸有成竹时,请再多思考一下 。系统已经处于崩溃的边缘,你原认为你能依赖的信息很可能会消失或者改变。留给你的只有一脸茫然,正如我前面那12个小时中那样。

如果你已经耐心读到这了,我推荐你关注下我们的 twitter帐号。我们每周都会发一些工作中碰到的一些性能调优的问题。

原创文章转载请注明出处: Java程序挂掉的几种可能

英文原文链接

Mahout学习之Mahout简介、安装、配置、入门程序测试

$
0
0

一、Mahout简介

查了Mahout的中文意思——驭象的人,再看看Mahout的logo,好吧,想和小黄象happy地玩耍,得顺便陪陪这位驭象人耍耍了...

附logo:

(就是他,骑在象头上的那个Mahout)


步入正文啦:

       Mahout 是一个很强大的数据挖掘工具,是一个分布式机器学习算法的集合,包括:被称为Taste的分布式协同过滤的实现、分类、聚类等。Mahout最大的优点就是基于hadoop实现,把很多以前运行于单机上的算法,转化为了MapReduce模式,这样大大提升了算法可处理的数据量和处理性能。

在Mahout实现的机器学习算法:

算法类

算法名

中文名

分类算法

Logistic Regression

逻辑回归

Bayesian

贝叶斯

SVM

支持向量机

Perceptron

感知器算法

Neural Network

神经网络

Random Forests

随机森林

Restricted Boltzmann Machines

有限波尔兹曼机

聚类算法

Canopy Clustering

Canopy聚类

K-means Clustering

K均值算法

Fuzzy K-means

模糊K均值

Expectation Maximization

EM聚类(期望最大化聚类)

Mean Shift Clustering

均值漂移聚类

Hierarchical Clustering

层次聚类

Dirichlet Process Clustering

狄里克雷过程聚类

Latent Dirichlet Allocation

LDA聚类

Spectral Clustering

谱聚类

关联规则挖掘

Parallel FP Growth Algorithm

并行FP Growth算法

回归

Locally Weighted Linear Regression

局部加权线性回归

降维/维约简

Singular Value Decomposition

奇异值分解

Principal Components Analysis

主成分分析

Independent Component Analysis

独立成分分析

Gaussian Discriminative Analysis

高斯判别分析

进化算法

并行化了Watchmaker框架

 

推荐/协同过滤

Non-distributed recommenders

Taste(UserCF, ItemCF, SlopeOne)

Distributed Recommenders

ItemCF

向量相似度计算

RowSimilarityJob

计算列间相似度

VectorDistanceJob

计算向量间距离

非Map-Reduce算法

Hidden Markov Models

隐马尔科夫模型

集合方法扩展

Collections

扩展了java的Collections类


二、Mahout安装、配置

一、下载Mahout
http://archive.apache.org/dist/mahout/

二、解压
tar -zxvf mahout-distribution-0.9.tar.gz

三、配置环境变量
3.1、配置Mahout环境变量
# set mahout environment
export MAHOUT_HOME=/home/yujianxin/mahout/mahout-distribution-0.9
export MAHOUT_CONF_DIR=$MAHOUT_HOME/conf
export PATH=$MAHOUT_HOME/conf:$MAHOUT_HOME/bin:$PATH
3.2、配置Mahout所需的Hadoop环境变量
 # set hadoop environment
export HADOOP_HOME=/home/yujianxin/hadoop/hadoop-1.1.2
export HADOOP_CONF_DIR=$HADOOP_HOME/conf
export PATH=$PATH:$HADOOP_HOME/bin
export HADOOP_HOME_WARN_SUPPRESS=not_null

四、验证Mahout是否安装成功
        执行命令mahout。若列出一些算法,则成功,如图:
        
       
五、使用Mahout 之入门级使用
5.1、启动Hadoop
5.2、下载测试数据
           http://archive.ics.uci.edu/ml/databases/synthetic_control/链接中的synthetic_control.data
5.3、上传测试数据
hadoop fs -put synthetic_control.data /user/root/testdata
5.4  使用Mahout中的kmeans聚类算法,执行命令:
mahout -core  org.apache.mahout.clustering.syntheticcontrol.kmeans.Job
花费9分钟左右完成聚类 。
5.5 查看聚类结果
    执行hadoop fs -ls /user/root/output,查看聚类结果。

齐活,收工。Mahout继续学习中......



作者:wl101yjx 发表于2014-4-27 10:56:05 原文链接
阅读:71 评论:0 查看评论

[译]所有编程皆为Web编程

$
0
0

原文作者:Jeff Atwood

Michael Braude对Web编程大受追捧表达了他的不屑:

大部分人想去做Web编程的原因是,他们不够聪明,因此也做不了别的事。他们不懂编译器、并发性、3D或类继承。他们根本不明白我为什么要使用接口或者抽象类。他们不理解虚函数、指针、引用、垃圾回收、终结器、传引用与传值的区别、C++的虚拟析构函数、或者C#的结构体与类之间的差别。他们对进程也一无所知。更别提瀑布、螺旋、敏捷了!他们从来没看过需求文档,也从来没写过设计文档;他们从没画过一张UML图,甚至听都没听说过有“顺序图”这种东西。

不过,他们确实有些手段:他们知道怎样匆匆拼凑一个ASP.NET网页,向数据库发一些(写得很糟糕的)SQL指令,填上一个数据集,然后用网格控件展示出来。他们也就会这些了吧。而且,他们在弄明白这些东西的时候,很可能也没费多长时间。

因此,请恕我冒犯——我才不想当Web程序员呢!我有两个理由。第一,那对我太没有挑战性了。第二,因为绝大多数互联网公司的工程师都很糟糕,更准确地说,因为你不必去琢磨深奥的东西就能成为一个Web开发者。在我看来,互联网正让我们变得越来越弱智。拼凑出一个网页真的不需要你有那么聪明。

我真心希望大家都错了,希望不必所有的东西都“转向Web”。因为如果真这样的话,将来的某一天,我要么会不情愿地接受这种无聊的转变,要么只能换一个职业了。

Web开发没有挑战性,所以吸引了不够格的软件开发者?让我们姑且不讨论这种荒谬的观点。即使过去真是这样,如今的情况也已经不同了。

我其实不想在Michael面前扮演“坏人”,告诉他这个坏消息:对于越来越多的用户来说,桌面应用软件已经完蛋了。几年来,大部分桌面应用软件都被Web应用代替了。随着网络浏览器进化得越来越强大和健壮,每天都有更多的桌面应用被取代。

你希望不必所有的东西都“转向Web”?醒醒吧!这已经发生了!

任何学习计算机历史的学生都可以告诉你,Web应用成为主流正是“最不强大原则”( The Principle of Least Power)所预示的:

计算机科学界过去花了40年的时间,致力于让语言尽可能地强大。时至今日,我们必须感激那些让我们选择不是最强大的解决方案(而选择最不强大的方案)的原因。计算机语言越不强大,你对用那种语言存储的数据能做的事情就越多。如果你以一种简单的陈述形式来书写,任何人都可以写一个程序去分析它。比方说,一个呈现天气信息的网页使用RDF(译者注:资源描述框架,一种用于描述Web资源的标记语言)来描述数据,用户可以把它装进一个表格,也许再做一下平均计算,绘制图表,然后结合其他信息做出某种推断。另一种极端的做法是,天气信息通过巧妙的Java小程序描绘出来。尽管这种方式可能做出很酷的用户界面,但它完全不能被分析。找到这个页面的搜索引擎将无法判断那是什么数据或者那些数据有什么用。要想知道一个Java小程序是做什么的,唯一的办法是亲眼目睹它运行起来。

互联网恰恰是做最简单(或者说最傻)而靠谱之事的具体表现。如果这吓到了你(让你感到不安了),那么我要小声地告诉你,你没有理由成为一名程序员。

所有应用都应该成为Web应用吗?当然不是。总有一些重要的例外,有些种类的软件跟网络也毫无关系。但是,这些是少数情况,是一些特殊应用。它们固然是重要的小生态环境,但不管怎么说,就只是“小生态”。

如果你希望尽可能多的用户来使用你的软件,绝没有比把它做成Web应用更好的方法了。对于现存软件来说,互联网是最高效、最普遍、最直接的分发网络。任何用户只要能连上网,有一个浏览器,不管他身处世界的任何一个角落,只须点两下鼠标就能与你写的软件开始交互了。哪怕是最蹩脚的Web应用,它的受众面和传播度都是令人震惊的;而且还在每天扩大着……我也曾因此杜撰了“ 阿特伍德定律”: 任何可以用JavaScript来写的应用,最终都将用JavaScript来写。

从工程的角度来看,用JavaScript来写Photoshop、Word或Excel毫无意义。但是,这是不可避免的。这事会发生的。实际上,这事已经在发生了。环顾一下你的四周吧。

作为一名软件开发者,最让我开心的是编写有人使用的软件。如果你的软件委身于一个二进制的EXE文件,它必须被购买、授权、运送、下载、安装、维护和升级,你的技艺又有什么意义呢?考虑到程序员与用户之间的所有这些传统障碍,软件行业居然还能生存下来,这真是一个奇迹!然而,在华丽而崭新的Web应用世界里,那些制约已经不复存在。边界没有了。软件可以无处不在!

Web编程还远远没有达到完美的境地。其实,还有点乱!没错,随便会写点代码的人就能三下两下地搞出一个糟糕的Web应用;也确实,99%的Web应用都似狗屎一堆。但是,这也意味着,相当“聪明”的程序员们正在将他们的成果展现在成百上千(或者成千上万,甚至几百万)的用户面前,而这在互联网盛行之前是绝无可能的。在我看来,让代码遭受冷落、让它们就此孤独终老,没什么比这更让人感到悲哀的了!把软件按照Web应用的形式重整一下,即使软件本身并不怎么样,这也使得程序员们能够把他们的软件展现在某个地方的某人面前。

如果受众面和技艺方面的观点不足以说服你,那就从商业的角度来考虑一下吧。 Mark Fletcher曾经给出过一份创业规则,其中就有这么一条:

你在做一个Web应用,对吧?这不是20世纪80年代了!纵然你的竞争对手把(桌面)应用软件做得别致优雅,你那简陋而寒酸的Web应用也仍然会比它更加成功。

要不了多久,所有编程都将是Web编程。对于普通的在职程序员来说,如果你不认为这是一个值得庆贺的理由,你也许应该转行了。

作者:happydeer 发表于2014-4-27 9:33:14 原文链接
阅读:348 评论:4 查看评论

你相信吗?几乎所有医疗设备都可被黑客入侵

$
0
0

你相信吗?几乎所有医疗设备都可被黑客入侵

美国知名科技媒体《连线》长期撰稿人吉姆-曾特(Kim Zetter)日前对现代医院医疗设备领域所面临的风险展开了一次详尽的调查。在调查中,曾特发现目前医院所使用的大多数医疗设备都存在着被黑客入侵的风险,而这一风险甚至可能会造成致命的后果。然而,许多医疗机构还是出于这样或那样的原因没有对此给予足够的重视。

以下是文章主要内容:

当我的同事斯科特-埃文(Scott Erven)被获准使用“中西部健康医疗中心”(Midwest health care facilities)大部分医疗器械的时候,他就肯定自己能够从中找到许多安全漏洞。但出乎他意料之外的是,埃文没有想到自己竟然能够找到如此之多的设备漏洞。

在这个为期两年的研究中,埃文和他的团队发现主要被用于为病患按剂量注入诸如吗啡这些药物的“药物输液泵”(drug infusion pumps)可以被远程控制以改变预先设定的输入剂量;具备蓝牙连接功能的心脏电击器可能被远程控制,并给予病患不恰当的电击次数;黑客可以通过入侵医院网络的方式访问诸如X光成像这些隐私数据,或者重置用于存储血液等医疗用冰箱的恒温设定。

除此之外,埃文和他的团队还发现别有用心的人士甚至可以在紧急时刻使医疗设备突然蓝屏、重启甚至是完全删除预先设定好的参数。

“许多医院都没有意识到他们医疗设备所面临的巨大风险,尽管已经有研究证明了这一事实,但医疗机构仍然没有对此给予足够的重视。他们没有对此展开测试,同时也没有重视起自己所面临的风险。”埃文说道。

埃文目前是负责全美 100 家医疗机构设备管理 Essentia Health 公司的信息安全主管,该公司在 2012 年对旗下业务所涉及医疗机构进行了一次全面调查,并允许埃文公布了部分研究数据。在埃文的报告中,他并没有指出具体哪个品牌的医疗设备存在风险,只是表示“现有广泛的医疗设备都存在着一些通用的安全漏洞,其中包括过于简单的用户名和密码,或者极其容易被黑客入侵的用户界面”。

而且,这些所存在漏洞的医疗设备大多是经由医疗机构的内部网络进行连接,但这些所谓的内部网络同时又与互联网相通。因此,黑客完全可以通过钓鱼方式入侵医院员工电脑,进而接入这一内部网络进行破坏。或者,黑客可以通过将笔记本带到医院连接内部网络的方式进行接入。

“这些机构中只有很少一部分设备能够真正抵御攻击,一旦你接入了他们的网络你就可以扫描,并发现绝大多数已连接设备,这非常容易。”埃文补充道。

情况堪忧

据悉,埃文是在将自己的研究和其他安全顾问的研究结论加以整合后才真正意识到这一领域所存在的严重安全问题的。而且,这些问题所覆盖的领域包括了心脏去颤器、药物输液泵、硬件密码等诸多方面。

“我们得到了来自管理层的支持来展开这次针对医疗设备领域的调查,我们几乎测试了所有当前环境下可能使用到的设备。”埃文表示。

在此次调查中,该团队发现医疗机构所广泛使用的嵌入式 web 服务(允许不同设备进行数据共享的一种服务)存在着巨大漏洞,

埃文认为:“许多嵌入式 web 服务允许设备间进行未经授权或者未加密的数据分享,因此我们可以人为改变它们所分享的医疗数据,而患者则可能因此得到错误的诊断或者处方药。医生们非常依赖于这些数据来进行判断,而我们却可以通过这些漏洞擅自修改数据。”

同时,这一团队还发现了存在于存储血液等医疗用冰箱设备中的漏洞。

“这些设备均拥有一个允许用户进行温度设定的网页用户界面,虽然这一设备会在温度超过预设范围的情况下向相关人员自动发送通知邮件提醒,但黑客完全可以通过破解密码的方式关闭这一功能,并擅自更改设定温度。”埃文说道。

而且,类似的入侵还可以被用于改变 CT 设备的照射强度等方面,并有可能对患者身体造成不必要的伤害。

后知后觉

报告显示,越来越多的医疗机构最近才开始意识到自己所面临巨大安全风险。而且,现有医疗设备在设计过程中更多的只是注重于可靠性、性能等方面的考虑,安全因素并没有纳入设计者的主要考虑范围内。

“医疗设备提供商目前没有任何现成的安全规划,他们在将产品推出市场前也不需要进行类似的安全分析。”埃文表示。

去年春天,美国食品及药物管理局(Food and Drug Administration)和国土安全部(Department Of Homeland Security)在得知大约 300 种医疗设备存在密码过于简单的问题后曾向相关医疗机构发出过类似警告,并要求它们对此进行检查。但事实上医疗机构本身能做的并不多,他们更多的只能依赖于设备生产商来解决现有设备的问题,并在未来的医疗设备中内置更加安全的加密机制。

目前,美国食品及药物管理局已经出台了要求医疗设备制造厂商强制检查出厂设备的安全性指导意见,而医疗机构也有权利要求自己的供应商全力履行这一义务。然而,许多供应商认为这样的要求可能很难真正满足,因为这需要他们将自己的系统重新提交给食品及药物管理局进行申报。(汤姆)

本文链接


Hadoop 优化总结(一)

$
0
0

1. 使用自定义Writable

自带的Text很好用,但是字符串转换开销较大,故根据实际需要自定义Writable,注意作为Key时要实现WritableCompareable接口

避免output.collect(new Text( ),new Text())

提倡key.set( ) value.set( ) output.collect(key,value)

前者会产生大量的Text对象,使用完后Java垃圾回收器会花费大量的时间去收集这些对象

 

2. 使用StringBuilder

不要使用Formatter StringBuffer( 线程安全

StringBuffer尽量少使用多个append方法,适当使用+

 

3. 使用DistributedCache加载文件

比如配置文件,词典,共享文件,避免使用static变量

 

4. 充分使用Combiner Parttitioner Comparator。

Combiner : 对map任务进行本地聚合

Parttitioner : 合适的Parttitioner避免reduce端负载不均

Comparator : 二次排序

比如求每天的最大气温,map结果为日期:气温,若气温是降序的,直接取列表首元素即可

 

5. 使用自定义InputFormat和OutputFormat

 

6. MR应避免

  • 静态变量:不能用于计数,应使用Counter

  • 大对象:Map List

  • 递归:避免递归深度过大

  • 超长正则表达式:消耗性能,要在map或reduce函数外编译正则表达式

  • 不要创建本地文件:变向的把HDFS里面的数据转移到TaskTracker,占用网络带宽

  • 不要大量创建目录和文件

  • 不要大量使用System.out.println,而使用Logger

  • 不要自定义过多的Counter,最好不要超过100个

  • 不要配置过大内存,mapred.child.java.opts -Xmx2000m是用来设置mapreduce任务使用的最大heap量

7.关于map的数目

map数目过大[创建和初始化map的开销],一般是由大量小文件造成的,或者dfs.block.size设置的太小,对于小文件可以archive文件或者Hadoop fs -merge合并成一个大文件.

map数目过少,造成单个map任务执行时间过长,频繁推测执行,且容易内存溢出,并行性优势不能体现出来。dfs.block.size一般为256M-512M

压缩的Text 文件是不能被分割的,所以尽量使用SequenceFile,可以切分

 

8.关于reduce的数目

reduce数目过大,产生大量的小文件,消耗大量不必要的资源,reduce数目过低呢,造成数据倾斜问题,且通常不能通过修改参数改变。

可选方案:mapred.reduce.tasks设为-1变成AutoReduce。

Key的分布,也在某种程度上决定了Reduce数目,所以要根据Key的特点设计相对应的Parttitioner 避免数据倾斜

 

9.Map-side相关参数优化

io.sort.mb(100MB):通常k个map tasks会对应一个buffer,buffer主要用来缓存map部分计算结果,并做一些预排序提高map性能,若map输出结果较大,可以调高这个参数,减少map任务进行spill任务个数,降低 I/O的操作次数。若map任务的瓶颈在I/O的话,那么将会大大提高map性能。如何判断map任务的瓶颈?

io.sort.spill.percent(0.8):spill操作就是当内存buffer超过一定阈值(这里通常是百分比)的时候,会将buffer中得数据写到Disk中。而不是等buffer满后在spill,否则会造成map的计算任务等待buffer的释放。一般来说,调整 io.sort.mb而不是这个参数。

io.sort.factor(10):map任务会产生很多的spill文件,而map任务在正常退出之前会将这些spill文件合并成一个文件,即merger过程,缺省是一次合并10个参数,调大io.sort.factor,减少merge的次数,减少Disk I/O操作,提高map性能。

min.num.spill.for.combine:通常为了减少map和reduce数据传输量,我们会制定一个combiner,将map结果进行本地聚集。这里combiner可能在merger之前,也可能在其之后。那么什么时候在其之前呢?当spill个数至少为min.num.spill.for.combine指定的数目时同时程序指定了Combiner,Combiner会在其之前运行,减少写入到Disk的数据量,减少I/O次数。

 

10.压缩(时间换空间)

MR中的数据无论是中间数据还是输入输出结果都是巨大的,若不使用压缩不仅浪费磁盘空间且会消耗大量网络带宽。同样在spill,merge(reduce也对有一个merge)亦可以使用压缩。若想在cpu时间和压缩比之间寻找一个平衡,LzoCodec比较适合。通常MR任务的瓶颈不在CPU而在于I/O,所以大部分的MR任务都适合使用压缩。

 

11. reduce-side相关参数优化

reduce:copy->sort->reduce,也称shuffle

mapred.reduce.parellel.copies(5):任一个map任务可能包含一个或者多个reduce所需要数据,故一个map任务完成后,相应的reduce就会立即启动线程下载自己所需要的数据。调大这个参数比较适合map任务比较多且完成时间比较短的Job。

mapred.reduce.copy.backoff:reduce端从map端下载数据也有可能由于网络故障,map端机器故障而失败。那么reduce下载线程肯定不会无限等待,当等待时间超过mapred.reduce.copy.backoff时,便放弃,尝试从其他地方下载。需注意:在网络情况比较差的环境,我们需要调大这个参数,避免reduce下载线程被误判为失败。

io.sort.factor:recude将map结果下载到本地时,亦需要merge,如果reduce的瓶颈在于I/O,可尝试调高增加merge的并发吞吐,提高reduce性能、

mapred.job.shuffle.input.buffer.percent(0.7):reduce从map下载的数据不会立刻就写到Disk中,而是先缓存在内存中,mapred.job.shuffle.input.buffer.percent指定内存的多少比例用于缓存数据,内存大小可通过mapred.child.java.opts来设置。和map类似,buffer不是等到写满才往磁盘中写,也是到达阈值就写,阈值由mapred.job,shuffle.merge.percent来指定。若Reduce下载速度很快,容易内存溢出,适当增大这个参数对增加reduce性能有些帮助。

mapred.job.reduce.input.buffer.percent (0):当Reduce下载map数据完成之后,就会开始真正的reduce的计算,reduce的计算必然也是要消耗内存的,那么在读物reduce所需要的数据时,同样需要内存作为buffer,这个参数是决定多少的内存百分比作为buffer。默认为0,也就是说reduce全部从磁盘读数据。若redcue计算任务消耗内存很小,那么可以设置这个参数大于0,使一部分内存用来缓存数据。

 



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


ITeye推荐



好的程序员到底好在哪里?

$
0
0

  原文地址: http://joshsymonds.com/blog/2013/11/03/what-makes-a-good-programmer-good/

  我这些年和许多程序员工作过——他们有些人超级棒,有些明显比较平常。因为我近来和一些熟练的程序员工作的很愉快,我花了一些时间考虑我羡慕他们什么。是什么让一个好的程序员那么好,差的程序员那么差?或者,简短一些,是什么让一个好的程序员那么好呢?

  根据我的经验,成为一个优秀的程序员与年龄、教育或者你挣钱的多少没有关系。关键在于你的表现,更深刻的说,是你如何思考。我注意到我羡慕的程序员有一致的习惯。比起他们所选语言的知识、对数据结构和算法的深入理解、或者几年的工作经验——更多的是他们交流的方式,管理自己的方式,和根据他们精湛的技巧可以知道他们接触编程的方法很有意义。

  当然,成为一个好的程序员需要的比任何人可以列举的都还要多,我不会基于这些实践的存在(或者缺失)而单独评判任何程序员。但当我看到时我确实能明确的知道,当我看到一个具有这些性格的程序员时,我会想,“这个人真的知道他们在做什么。”

  他们做研究

  或者称作“三思而后行”,或者称作“谷歌一下”。

  无论你怎么称呼它,你可能遇到的大多数编程问题几乎在一定形式上都已经被解决了。传道书早就记录在案,阳光底下无新事。在GitHub上的库文件列表中,在因特网上的博客中,或者恰好与某个人经验交流中,好的程序员知道要在解决一个问题之前先做研究。

  我曾经见过伟大的程序员急于给出解决方案,但是我曾经一起工作过的最糟糕的程序员,从来不咨询他人,从而导致做了大量的重复性工作或者恰好使用了错误方式来解决问题。于是很不幸的,他们最终为他们的错误付出代价。

  读错误信息(并以之行事)

  这包括对堆栈追踪的符号解析。是的,令人厌恶而且不幸——但如果你不愿意这么做,怎么知道哪里出错了?我知道的最高效的程序员不害怕深入挖掘问题。最低效的程序员看到错误甚至都不愿读错误信息。(这听起来挺可笑的,但我遇到的频率会让你吃惊。)

  更进一步说,伟大的程序员看到问题,会急迫的去解决它。对于他们来说,读错误信息仅仅是第一步;他们渴望深入问题并找出错误的根源。他们对推卸责任没有兴趣,他们对找到解决方案有兴趣。问题确实在他们这里止步。

  他们会去看源代码

  文档,测试和人:这些都可能会说谎。未必是故意撒谎,但是如果你想确切的知道代码是怎么工作的,你就必须亲自察看源代码。

  即使这不是你非常熟悉的语言也不要害怕 – 比如,如果你主要是一个Ruby程序员并且你怀疑Ruby的C语言包里有错误,那就去解压它看看再说。不错,你可能会一无所获。但是谁知道呢,你也可能会找到问题所在,比起什么都不做,你至少选择了一条更有机会的路。

  如果你工作在一个非开源的环境中,就不太好办了,这很不幸,不过道理是不变的。糟糕的程序员对查看源码通常没有太多兴趣,结果就是,跟那些愿意去研究一下源码的人相比,他们通常会被这些问题困扰的更久。

  他们说做就做

  好的程序员总是趋向于采取行动。他们似乎有种控制不住的强迫性:一旦他们确认了一个问题或者看到了一个新的特性需求,就会立即着手解决,有时甚至过早或者过于勇往直前。他们遇到问题的直觉反应就是正面解决它。

  有时这会带来麻烦 – 但是他们的热情正是他们能够做的很好的关键因素。当某些人还在拖延回避或者幻想问题能自己消失的时候,好的程序员已经开始动手了。

  更简单的来说(也许,太过直白):如果你看到一个人兴奋的发现并处理问题,很有可能你得到了一名好程序员。

  他们防患未然

  这可能是一个坏的程序员的特征:他们总是纠缠于一个又一个的人为失误,从来都是没有明白上一个就转向下一个。他们总是在抱怨他们程序中的错误部分,却耗费数小时对完美运行的代码来debug。他们让情绪占据主动,相信直觉而不是仔细明确的分析。

  如果你突然遇到一个问题——或者每一个问题看起来都像是世界末日一般,你极有可能是在犯错误而不是在解决潜在的问题。伟大的程序员会花费一些时间来了解是什么出了错,哪怕是真的是一场灾难,除了这些,他们还会把常出现的问题当成分配任务来处理掉。由于他们能更精确的解决大部分问题,从而不会提高你的团队的紧张程度。

  他们善于交流

  说到底,编程也是一种交流的方式。能够简洁明了地表达出你的观点之于写代码就如其之于写诗一样重要——长久以来,我发现那些能够写出精炼的电子邮件、优雅的报告或者仅仅是高效的备忘录的人通常也会是更优秀的程序员。

  这个发现对写程序和对英语一样使用。当然,把充斥着括号和只用一个字母命名的函数写在一行里面也是可以的,但是如果没有人能够理解你写的代码,又有什么意义呢?无论使用什么媒介,优秀的程序员会把时间花在如何将他们的观点更好地表达出来上面。

  他们激情四射

  我想这是最能够体现一个好的程序员的地方(并且,不仅在计算机行业,这点适用于任何行业)。

  如果你真正关心你做的东西——不只是把它当做一个工作区应付,而是一个兴趣、一件对你有着莫大魅力的事情,那么在这个行业里,相较于其他人而言,你就拥有了一项巨大的优势。好的程序员会一直保持者写代码的状态,他们每天花在这个行业里的时间都不低于8个小时:包括工作和空余时间。在编写项目和授业解惑两者之间,他们不会偏向任何一方。他们不会只是为了搞清楚某个东西的工作原理而整天痴迷于新技术或新的编程语言。

  当我自习观察一个周日正在做自己感兴趣的项目、在创造自己需要的工具、被新的、有趣的事物吸引的程序员的时候:我意识到我正在观察一个会领所有人都不由自主心生敬意的人。最后,伟大的程序员不会将他们的专业看做赚钱的工具,而是一种改变世界的手段。我想这就是早就一个伟大程序员的真正原因吧。编程,对于他们来说也就意味着创造世界。也只有这样的人,才值得我们由衷地敬佩和景仰。

Java实现aop案例

$
0
0
MyPersonService.java代码如下:
package com.shihuan.jdkaop.service;

public interface MyPersonService {
	
	public void findPerson(String name);
	
}


MyPersonServiceImpl.java代码如下:
package com.shihuan.jdkaop.service.impl;

import com.shihuan.jdkaop.service.MyPersonService;

public class MyPersonServiceImpl implements MyPersonService {

	public String myproperty = "shihuan...";
	@Override
	public void findPerson(String name) {
		System.out.println("在com.shihuan.jdkaop.service.impl.MyPersonServiceImpl.findPerson()中: " + name);
	}

}


SecurityHandler.java代码如下:
package com.shihuan.jdkaop.handler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.shihuan.jdkaop.service.MyPersonService;
import com.shihuan.jdkaop.service.impl.MyPersonServiceImpl;

public class SecurityHandler implements InvocationHandler {

	private Object targetObject;
	
	public Object createProxyInstance(Object targetObject){
		this.targetObject = targetObject;
		
		//创建一个代理对象,此对象是一个与目标对象实现了相同接口的对象,第一个参数为目标对象的类加载器,第二个参数为目标对象所实现的所有接口,第三个参数为实现了InvocationHandler接口的对象实例,这里是SecurityHandler本身
		return Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this);
	}
	
	/**
	 * InvocationHandler接口的方法
	 * @param proxy 代理对象
	 * @param method 被拦截到的方法
	 * @param args 被拦截到的方法的输入参数
	 * @return 代理对象最后又将拦截到的方法处理委派回给目标对象自己处理
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {

		MyPersonServiceImpl bean = (MyPersonServiceImpl) this.targetObject;
		
		Object result = null;
		
		System.out.println("before advice()");
		result = method.invoke(bean, args);
		System.out.println("after advice()");
		System.out.println("参数个数: " + args.length);
		for (int i = 0; i < args.length; i++) {
			System.out.println("每一个具体的参数值: " + args[i]);
		}
		System.out.println(bean.myproperty);
		return result;
	}

}


TestMyAop.java代码如下:
package com.shihuan.jdkaop.test;

import com.shihuan.jdkaop.handler.SecurityHandler;
import com.shihuan.jdkaop.service.MyPersonService;
import com.shihuan.jdkaop.service.impl.MyPersonServiceImpl;

public class TestMyAop {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		SecurityHandler myhandler = new SecurityHandler();
		
		MyPersonService mybz = (MyPersonService) myhandler.createProxyInstance(new MyPersonServiceImpl());
		
		mybz.findPerson("yushibo");
	}

}




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


ITeye推荐



全国深处理水厂不足2% 水源地合格比例或仅50%

$
0
0

本报记者 高江虹 实习记者 孟环宇 北京报道

“过去十年来我们一直呼吁要保护水资源,治污减排,但是水质状况并没有多少改善。”清华大学饮用水安全研究所所长刘文君叹了一口气,向21世纪经济报道记者无奈地表示,若再不加快在源头堵住污染水源的行为,水污染治理的时间又很漫长,很快我国将水危机频发。

4月11日兰州发生水污染事件之后,北京水质重金属含量严重超标的消息再次曝光:北京一些用户被告知家中自来水“重金属含量严重超标”,须加装净水器。

尽管此后北京市自来水集团专门入户检测,并辟谣“自来水重金属超标”,但水质安全仍再一次高度牵动人们的神经。

深处理水厂不足2%

4月14日,北京自来水水质监测中心去爆料现场检查看,净水器厂家所说的重金属超标不过是其偷换概念,其检测数据应该是矿物质可溶性固体的数值,而这种物体完全对人身体有益无害,在自来水制水过程中不需要去除。

4月17日,北京市自来水集团的企业代表聂燕冬再度回应称,北京的水质有保障,全过程的水质监测体系,具备很强的水质检测能力,有全面的水处理工艺。

事实上,聂燕冬所言不虚。北京的自来水厂设备与技术能力均在国内名列前茅,而其对水质的检测严格程度亦是数一数二的,因此其水质合格率相对也比较高。据了解,北京市自来水拥有全过程的水质监测体系,在水源建水厂,出水厂和管网终端等五个环节都设置了安全防线。出水环节有三级检测,在用户终端还有302个检测点,随时关注水质问题。

另外,北京自来水集团的水质检测中心检测能力达到了205项,远远超过国标的106项。因此,首创股份副总经理张恒杰评价起竞争对手北京市自来水厂的表现,仍客观地认为,北京的水已经比很多城市的水安全得多。

只是,全国有几个北京?其他城市有多少做到像北京一样设备先进、技术多样、检测严格?

国家城市给水排水工程技术研究中心总工程师郑兴灿指出,即便在新国标106项标准,并不是所有地区都能做到,少数只能做到10项或40项,目前做到深度处理的水厂不到2%。

所谓深度处理,指的是通过臭氧、活性碳等技术,清除各类有机、无机化合物,使污染水达标,最终实现直饮。与此相对应的是传统水处理工艺,即絮凝(加聚合氯化铝)、沉淀、过滤(通过石英砂、卵石等)、消毒(加氯气等)。两者的区别在于传统工艺主要处理灭杀水中微生物,而深度处理还可以解决重金属离子和有机化合物污染。

根据《全国城市饮用水安全保障规划(2006-2020年)》数据,全国近20%的城市集中式地下水水源水质劣于Ⅲ类。部分城市饮用水水源水质超标因子除常规化学指标外,甚至出现了致癌、致畸、致突变污染指标。

据了解,地下水受As、F、Fe、Mn、放射性物质,以及天然化学背景与人为污染;地表水受微量污染物、重金属,以及突发性污染事故污染;湖泊水源泥沙剧减,对下游水体中的重金属等沉淀不利,而水库内泥沙淤积又容易出现富营养化。

因此,当前我国的水质情况远超传统工艺能处理的范围,理应提升水厂的深度处理能力,但是现实情况却是不容乐观。

而刘文君提供了另外一个数据:现在有不少水厂采用的臭氧活性炭深度处理工艺,处理工艺的规模占城市供水规模的10%左右。也就是说,即使比例都提升至10%,仍意味着90%的水处理无法排除被有机化合物或重金属污染的风险。

水源地合格比例仅50%?

如果自来水厂的处理工艺尚未提升,但进厂的水源水质保持优良,亦可确保出厂水是安全合格的。

只是,我们自来水的水源真的优良吗?

据了解,我国自来水的水源,取自地表水和地下水,地表水又分为河流、湖泊和水库。无论哪种水,现在都是一种匮乏的状态。原因是我国是一个缺水严重的国家。虽然我国淡水资源总量为28000亿立方米,占全球水资源的6%,仅次于巴西、俄罗斯和加拿大,居世界第四位,但人均只有2100立方米,仅为世界人均水平的28%。这个数据是2012年一位国家发改委官员透露的。

在这个稀缺淡水资源的国家里,过去三十五年经济高速发展带来人们生活改善的同时,也带来了水质的噩梦。

根据2001年对我国七大水系断面监测,达到三类水质可以进入自来水厂的最低要求的仅占29.5%,而劣V类水质却高达44%;另外,我国浅层地下水资源污染比较普遍,全国浅层地下水大约有50%的地区遭到一定程度的污染,约一半城市市区的地下水污染比较严重。由于工业废水的肆意排放,导致80%以上的地表水、地下水被污染。

经过国家大力治理,水质状况有了改善。据2012年中国环境状况公报,长江、黄河、珠江、松花江、淮河、海河、辽河、浙闽片河流、西北诸河和西南诸河等十大流域的国控断面中,Ⅰ~Ⅲ类、Ⅳ~Ⅴ类和劣Ⅴ类水质断面比例分别为 68.9%、20.9%和 10.2%。主要污染指标为化学需氧量、五日生化需氧量和高锰酸盐指数。

也就是说,经过治理,11年间,劣V类水的比例大幅降至10.2%。不过,各地情况大不相同。海河劣Ⅴ类水比例高达40%以上,黄河、辽河与淮河也都有超过20%仍是劣Ⅴ类水。

刘文君指出,实际上自来水的水源要求是一类和二类水,但这些优质水源实在太少了,才允许选用三类水。不过,我国三类水的比例,在这十大流域中也并不高。目前三类水达到90%以上的,仅仅是西北和西南诸河,长江、珠江、闽浙片河流鲜有超过80%,黄河、松花江等连60%都达不到。

从这些合格比例不高的水域中再度筛选供应自来水厂的水源地,难度可想而知。刘文君还指出,水是流动的,即使水源地的水质达标,附近的河流污染的话,迟早会污染水源。

2007年9月,时任国家环保总局局长周生贤曾在《中华人民共和国水污染防治法(修订草案)》的说明会上透露,全国113 个重点环保城市的222个饮水地表水源的平均水质达标率仅为72%,不少地区的水源地呈缩减趋势。

后来,环保部对水源地的监测范围扩大,将地下水纳入进来,并在过去六年间加大水质治理,水源地的达标水质有了不小幅度的上升。2012 年,全国 113 个环境保护重点城市共监测 387 个集中式饮用水源地,其中地表水源地 240 个,地下水源地 147 个。水质达标率为 95.3%,与上年相比,上升 4.7 个百分点。

也就是说,在全国重点环保监测的113个城市,饮用水水源的水质仍有4.7%是不合格的,而在更多中小城市,饮用水水源的真实情况很可能更糟糕。北京大学环境与工程学院研究员刘永认为,大城市的水质是基本有保障的,真正危险的是广大农村和中小城市。随着工业化、城镇化和农业现代化不断推进,农村环境形势严峻。工矿污染压力加大,生活污染局部加剧,畜禽养殖污染严重。

《2012年中国环境公报》指出,试点村庄1370个饮用水源地监测断面(点位)水质达标率为77.2%。其中,地表水和地下水饮用水源地水质达标率分别为 86.6%和 70.3%。农村水质情况比重点城市糟得多。刘永指出,农村和中小城市的检测设备和技术都非常欠缺,水厂的处理工艺又传统原始,随着地表水和地下水污染加剧,这些区域的居民所喝的水里含些什么都不清楚,才真正可怕。

由于环保部公开的数据,只有113个重点大城市的信息,全国其他数千个城市的水质情况并未披露。记者多方问询都未能获得。一位不愿意披露姓名的学者告诉记者,水质情况很糟糕,但有多糟却是个国家机密。他估计真实的合格率只有50%左右。2012年住建部城市供水监测中心总工程师宋兰合接受《新世纪》周刊采访时曾认可水质合格率仅为50%的判断,他个人判断,剔除掉事实上不合格的三类水源,再剔除部分一二类水源中实际不合格的部分,中国城市水源地真正合格的比例大约为50%。

水源污染溯源

谁污染了我们的水?最大一个原因是我们自己。

随着我国城市化进程不断向前推进,居民生活用水均保持不断增长。根据国家统计局的数据显示,2000 年至2010 年,我国城市化率从36.22%上升至49.95%,10年间增长了13.63%;2000年,我国用水量为5498亿立方米,2011 年增长至6107.2亿立方米;同时我国人均用水量也从2000年的435.40立方米/年增长到了2010年的450.2立方米/年;与之相对应,产生了大量的污水排放,我国城镇生活污水排放量也由2003年2470115万吨/年上升到了2010年的3797830万吨/年,增长了53.75%。

与此同时,我国城市污水处理率较低, 其主要原因是我国的城市污水处理厂建设滞后。民族证券分析师于娃丽在2013年6月发布的一份《水务行业增长趋势不改》的报告中介绍, 美国现在平均每1 万人就拥有1 座污水处理厂, 英国和德国分别为每7000 和8000人拥有1座污水处理厂。我国城镇人口中, 平均每150 万人才拥有1座污水处理厂。截至2011年底,全国设市城市、县累计建成城镇污水处理厂3135座。

另一方面,我国工业经济的持续高速发展加大了工业用水的需求,从而也产生了大量工业废水。从2003 年开始,我国工业增加值保持了年均25%的增幅,而同期工业用水量也同样保持增长,年均增幅超过3%。国家统计局统计数据显示,随着用水量的增加,废污水的排放量亦随之增加,由2003年的2122527万吨增长至2010年的2374732万吨,增长了11.88%;但总体看来,各项污染物排放指标呈现出得到控制的趋势,2010 年,工业废水排放化学需氧量排放量为434.77 吨,氨氮排放量为27.28 万吨,对应于2003 年的511.8 万吨、40.36 万吨略有一定比例的下降,但各项污染物的绝对量仍然较大,水资源污染的形势依然严峻。

事实上,国家有严格的法律法规要求企业做好污水处理和排放工作,而且也有专门的规定要求污染企业远离水源地。“法律都有,就是缺乏严格执行。”刘文君说道。

刘文君感慨道,虽然国家在水源治理上日趋重视,近年投入极大,奈何水体一旦受到污染,治理起来需要漫长的过程,据欧美日等国的经验,让水质恢复正常很可能长达二十年的时间。

更令他忧心的是,随着东部经济结构调整,不少污染企业迁移到中西部地区,而我国多条河流的发源地集中在中西部,这些污染企业的排污治理问题不到位的话,等于把上游水域也污染了,或将加剧目前的水污染危机。

面对包括水在内的整体环境恶化,威立雅环境服务的中国总经理周小华亦感伤:“从1994年介入环保服务这么多年,环保好像并没有改善,我感到非常惭愧。”

拟投2万亿治理水污染

据了解,环保部和水利部正在酝酿《水污染防治行动计划》,该计划投资达2万亿,将致力于恢复污染的水质和保障水质安全。

除了投资,法律法规的保障上也将会加强。刘文君向记者透露,国家正在酝酿饮用水水源质量标准以及饮用水水源地管理制度。

刘文君解释道,目前的水源地水质分级,参考的是《地表水环境质量标准》(GB3838-2002)。该标准有109项地表水质指标,环保部通常只检测其中的23项基本项目,未测其余86项补充项目和特定项目。按此标准,按水质可将饮用水源分为五类。一二类为合格水源,三类以下均为不合格水源。

至于三类水是否合格,按照环保部的统计口径,三类水也是合格的水源。可是在刘文君看来,三类水实际上并不是合格的饮用水水源,只是能够进入自来水厂进行处理的最低要求。

此外,重要的是,环保部目前对水质的检测和分类,是基于地表水和各种生物要求的,并非单独为人类的饮用水设定的标准,因此水质中很多指标并不适用于自来水。

国家城市给水排水工程技术研究中心总工程师郑兴灿亦曾指出,我国的水质指标较为混乱,总氮指标为例,饮用水指标比湖泊地表水更低。

因此,不少专家都呼吁应该建立一套独立的饮用水水源质量标准,以及独立的水源地管理制度。据刘文君透露,这项工作在推进当中,何时出台质量标准尚无定论。

智能路由器后面的英雄OpenWRT

$
0
0

【文/搜狐IT 丁丁】小米日前正式推出了小米 路由器,在业界又一次引发了关于“抢占客厅”讨论。这两款小米路由使用的miWiFi系统,不出所料是基于开源OpenWRT(OpenWRT也基于Linux开发)系统的定制版本。360安全路由360SOS,是与硬件厂商共同基于开源Linux系统的定制版本。曾经以小米营销模式为榜样引起高调关注的极路由,其使用的HiWiFi系统,也基于OpenWRT进行了定制。而果壳路由和小度路由的系统,则基于厂商提供的Linux版SDK(软件开发工具包)进行开发。

 

上游厂商为路由器厂家提供“保姆式”服务

为什么新兴智能路由器厂商们这么喜欢开源操作系统?这要从硬件行业的潜规则说起。

目前硬件行业产品开发模式早已经定型。由于英特尔、AMD、NVIDIA等厂商产品推陈出新的速度非常快,从586年代开始,业界产品开发基本上是采用公板公模的模式。前端芯片厂商为这些品牌厂商提供全方位的硬件及软件技术支持。山寨机时代的MTK等厂商甚至提出了“保姆式”服务的口号,大大缩短了产品的研发上市周期。

当然,这种模式在路由器领域也同样适用。博通、高通、MTK等所有厂商的路由器芯片,都有着完善的软硬件技术支持服务。小米们只要采购量够大,都可以得到芯片厂商的“保姆式”服务。

硬件方面,路由器厂商只要在公板公模的基础上进行少量的优化,甚至只改一下PCB板的布线,就能生产出“与众不同”的路由器产品。

软件方面,一般则使用两种方式,一种是采用厂商提供的SDK进行开发,另一种则是干脆使用开源的软件系统进行开发定制。

 

路由器的“微创新”

在硬件门槛不高、相同硬件架构路由器性能相差不大的现实下,路由器厂家们主要从三个方面进行“差异化”竞争。一是产品的外观设计;二是电子元器件的优选;三是软件功能的设计。互联网的“微创新”观念在硬件创新领域一样适用。

产品外观设计的重要性不言而喻。之前移动电源厂商亿觅科技联合创始人覃康胤,在搜狐IT无穷俱乐部活动的分享中,特别强调了设计可以让硬件创业更加“性感”的观念。在硬件技术非常成熟的情况下,冷冰冰的技术不会让用户与产品之间产生黏性,不足以成为与同行竞争的护城河。而用好的设计提升用户体验,才是产品走向成功的一条正确道路。

电子元器件的优选也非常重要。小米手机在发布时,雷布斯同学曾经讲述了小米手机在元器件选型方面的心路历程,以凸显小米在产品设计上的用心和对用户的责任。

软件功能的设计其实是最重要的。360智能硬件产品总监刘之认为,现阶段路由器还处在“智能”与“功能”的交替点。很多现在厂商宣传的智能功能,其实传统的功能型路由器已经为用户提供了。在路由器硬件平台已经很完善的情况下,下一步的关键点是要看如何在软件方面进行突破,真正实现“智能”。

 

开源的短平快适合互联网节奏

由于这拨智能路由器浪潮基本上都由互联网公司引领,被用滥了的“互联网思维”对硬件创业一样有着根深蒂固的影响。基于上游芯片厂商的“保姆式”服务和同行的竞争,使用开源操作系统,利用开源系统丰富的插件和源代码,智能路由器产品可以在极短的时间内上线,并可以在软件方面快速升级迭代。

品牌厂商要做的主要是驱动的适配和软件界面的优化,以及互联网营销。目前这批智能路由器,很多都使用了MTK联发科的MT7620A方案。众所周知,MTK素有“价格屠夫”之称,MT7620A也是一款面向低端市场的802.11AC芯片。从去年开始,开源的OpenWRT就已经支持MT7620A的芯片,在终端厂商的包装下,经过软件定制和软件界面的优化,产品俨然有了高大上的感觉。

 

升级OpenWRT是趋势也有“挑战”

在黄冬看来,智能路由器的操作系统使用OpenWRT是一个趋势。目前遇到的问题是OpenWRT除了对部分硬件芯片在WiFi方面的稳定性还有待提高外,主要原因是卡在了上游芯片厂商这边。

果壳电子副总裁黄冬称,目前果壳路由的系统使用了芯片厂商提供的SDK进行二次开发。不过随着OpenWRT稳定性越来越好,之后果壳路由也将使用OpenWRT进行开发定制,以全面拥抱开源系统,并为极客人群开放。

据称,硬件芯片厂商其实非常不愿意在软件方面进行投入。他们提供的SDK软件开发包还基于较老的Linux Kernel(内核)2.6.X版本,厂商提供的开发者文档都已经有5、6年没有进行过修改。而OpenWRT系统则非常活跃,Kernel都已经升级到了3.3。如果要支持OpenWRT系统,芯片厂商的硬件驱动就要不断进行修改以便适配,这是芯片厂商不愿做的事情。两者之间还存在一道非常深的鸿沟。

不过,由于开源系统社区非常活跃,OpenWRT在欧美、俄罗斯、国内有大量的追随者,并且有不计其数的分支和代码贡献者。在这拨智能设备创新的热潮中,OpenWRT的价值也越来越得到人们的重视。相比Android系统,OpenWRT被认为是更加适合智能设备和机器人的软件平台。

 

(关注更多 钛媒体作者观点,参与钛媒体微信互动(微信搜索“钛媒体”或“taimeiti”))

lnmp下开启mail函数发送邮件及sendmail启动慢的问题

$
0
0

自从换了阿里云服务器之后,我也就没有理过博客的回复评论邮件通知功能。前2天有朋友跟我说,之前评论都有邮件通知,现在怎么没有了。我想应该是没有开始支持mail()函数的语言,VPS的好处就是自己可以随便折腾。于是研究下怎么开启mail()函数,本文做个记录,方便以后可能用到。

我的阿里云服务器使用的是LNMP环境,CentOS 5.8 64位。

1.安装 sendmail 组件

yum install sendmail

2.使用下面的命令重启php-fpm进程

/etc/init.d/php-fpm restart

3.检测sendmail是否运行正常

/etc/init.d/sendmail status

如果显示正在运行running 就可以。

可能用到的命令

/etc/init.d/sendmail start (启动sendmail)
/etc/init.d/sendmail stop  (关闭sendmail)
/etc/init.d/sendmail restart (重启sendmail)

4.配置php.ini,填写sendmail的绝对路径

使用命令打开编辑php.ini

vi /usr/local/php/etc/php.ini

输入?sendmail_path 查找定位(或者手动跳转找到sendmail_path),你会发现默认是下面的代码

;sendmail_path =

按 i 进入编辑,将这行修改为

sendmail_path = /usr/sbin/sendmail -t –i

按 Esc 键退出编辑,输入 :wq 保存退出

5.重启php-fpm进程

/etc/init.d/php-fpm restart

这样就成功了,如果不放心,新建一个php文件,命名为 mail.php ,可以使用下面代码测试下。

<?php
$txt  "hello,you are a good boy!";
// 以下的邮箱地址改成你的
$mail  'somebody@example.com';
// 发送邮件
mail($mail"My subject"$txt);
echo  'message was sent!';
?>

保存并上传至你的网站根目录,并在浏览器运行:

http://你的网址/mail.php

到时候你的邮箱就应该能收到邮件了,不过基本都在垃圾邮件里面,注意查看,如果收不到邮件的话,检查在操作过程时候正常,有木有重启sendmail或者重启php。一般来说,就能正常收到邮件了。

当然,功能是实现了,不过用上面的测试代码测试的时候,发现成功发一封邮件很慢很慢,要好几十秒甚至一分钟的时候。然后我在博客中发表或者回复评论的也是异常的慢,超过了用户等待的心理期限。优化这个效率势在必行了,不然只能砍掉这个功能。

网上方法很多很多,修改这个文件又是修改那个文件,感觉比较麻烦,于是找个了操作最少的修改,然后成功了。

修改/etc/hosts ,未修改之前

127.0.0.1 localhost.localdomain localhost

修改成

127.0.0.1 localhost.localdomain localhost 主机名称

查看本机的主机名称的命令是: hostname

完成修改之后重启下sendmail

service sendmail restart

此致,发表评论又回复到了几秒完成发表成功了。大家发表评论的时候看看相比慢不慢,同时能不能收到评论回复的通知邮件,感谢大家。

本文参考了  LNMP笔记:解决mail函数不能发送邮件 和  解决sendmail启动慢的问题 文章内容。



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


ITeye推荐



听资深用户说:豆瓣最让人着急的4个问题

$
0
0

作者头像
作者: 纪云/产品观察家
兴趣太广泛,科技很性感。
[核心提示]以慢为主的豆瓣,在移动互联网时代错过了什么?又有哪四个最让人着急的问题?

前不久跟几个互联网圈的朋友吃饭,聊到几个认识的豆瓣员工相继离职的事情,就说起了豆瓣这家公司,没想到越说越激动,到最后大家都开始急得为豆瓣直拍桌子了,大抵是因为恨铁不成钢吧,互联网大家都在追求快的时代,好不容易有一个以“慢”的产品打动人心,你希望它“慢慢”变得更好,可是我看到的,是在慢慢变得平常。

移动端的缺失

我们这几个人都是豆瓣的老用户,并且是重度的使用者。我从 08 年开始用,另外几位有的从 05 年豆瓣刚成立时就是成员,算是看着豆瓣一路走到今天。豆瓣对我们而言,不只是一个网站,而是自己过去 6、7 年人生经历的一部分。因为在豆瓣里记录、查找、讨论的内容都是读书、电影、音乐、这种直达个人内心、情感甚至精神层面的内容,它带给我们的不仅仅是内容本身,更是感情和牵绊。

但是说来奇怪,过去一年里,我们上豆瓣的频率在慢慢变少,以前怎么也得每天刷个十几次,现在可能好几个礼拜才有一次,于是就试着来找找原因。

我们发现最主要的原因是,我们坐在 PC 前的时间越来越少,一整天都跟手机难舍难分。但是豆瓣推出移动应用相当晚,并且水平也与豆瓣 web 端差距很大。同时,以前需要打开豆瓣才能满足的需求在移动端上有更好的满足方式。就拿听音乐这件事来说,其中一位朋友几乎全部转移到虾米上了。他每周在虾米上建立精选集推荐专辑,他的发现、分享、听音乐都在虾米完成。我也早从豆瓣 FM 转向了虾米,还穿插着用 Bod、8tracks 这种应用。虾米可以搜索且曲库比豆瓣 FM 大,而 Bod、8tracks 等都是一种新的音乐呈现形式,真正能让我发现很多新的合我口味的音乐。

后来我也找了一些数据,发现豆瓣在移动端其实动作不少,算起来过去 3 年里一共推出了 10 个移动 App,但是从用户量上看每一个都不亮眼。豆瓣 FM 算是表现最好的,因为在同行里算做的最早,但下载量也不过千万级别。

这些年豆瓣也并非一成不变,已经不记得广播具体改版过多少次,每隔一段时间也都有新产品推出,比如最近推的 达罗。说到这个产品,我有点不太明白豆瓣做它的逻辑,豆瓣是想在豆瓣之外另开一个社区吗?还是有什么其他想法?但在现在移动互联网环境下,这个产品并不是很性感和令人兴奋,我只是打开研究了一下就没有再去用的念头。

 

总而言之,豆瓣这样一家曾经让人无比喜爱的公司,今天似乎有点脱离移动互联网大潮,在移动时代,脱离移动就意味着死亡。

四个看得到却改不了的问题

上次聚会结束后,又找了一些人聊了聊这个话题,他们中的大部分人跟我一样是豆瓣用户,还有几个豆瓣员工,经过几次深聊,无论是从产品还是内容本身出发,总结了 4 个豆瓣最让人着急的问题。

1、能不能不那么自信?着急一点!

从豆瓣员工那里听说,阿北在不久前的豆瓣年会上承认豆瓣错失了 3 年时间,公司战略失误,原因在于 “对产品和技术能力过于自信”

我觉得这种自信很大程度上源自豆瓣在 PC 互联网上的成功。从公开的数据看,截至 2013 年,豆瓣注册用户 7900 万,2 亿月独立 UV,2.3 亿日 PV,39 万个小组,相当亮眼。但可能正是这种在 PC 互联网上的成功,导致在移动互联网到来时成为豆瓣的包袱,而没有将移动作为第一要务。

表现在产品上,则是移动端总是跟在 PC 端后面。看豆瓣这几年在移动端的动作,似乎只是将 Web 版豆瓣分拆平移,没有多少真正基于移动端的创新。反而像豆瓣阅读、豆瓣东西这种产品让人眼前一亮。这么一对比就可以看出,豆瓣在创新时还是基于 PC 互联网,而不是移动互联网。即便是最新推出的“达罗”、“事情”这几个产品,也都从 Web 豆瓣网开始测试,并非生于移动。

这种将桌面端产品平移到移动互联网的做法可能忽视了一个问题: 用户对同一个产品的需求在移动端和 PC 端不同。

拿豆瓣 FM 来说,它以个性化推荐电台为切入点,在业内首先找到这个需求并很好地满足了它,从而在短期内爆发。然而今天,它被晚了 1 年的 QQ 音乐、晚了 2 年的虾米移动端一一超过。从产品形态上看,QQ 音乐、虾米都从庞大的曲库入手,让用户能找到想听的音乐,并不断添加新的功能,如社区、精选集等,直至它们将个性化推荐也加入到产品中,豆瓣 FM 赖以安身立命的壁垒被他人打破,自身成了其他产品中的一部分。豆瓣 FM 没有基于移动端进化,而是照搬 PC 端单一的产品形态,结果是“起了大早,赶了晚集”。

在移动端的推广上,豆瓣也继承了桌面时代的做法——不推广。在桌面端,豆瓣的不同产品之间通过同一个账号体系关联在一起,可以发挥协同效应,所以能保证流量规模。但移动端入口分散,如果产品本身不能在短期内爆发就很容易被人遗忘,推广的重要性比桌面端大太多了。

一方面是豆瓣不愿意推广,另外在移动端推广成本居高不下的情况下,豆瓣一下子推出 10 个 App,想要都推广将会让豆瓣不堪重负。移动互联网已经过去 3 年,如果豆瓣能更接地气一样,着急一点,也许会更有帮助?

2、能不能不那么慢?快一点!

我喜欢豆瓣这种公司,跟它能一直按照自己的节奏走分不开,尤其在普遍浮躁的中国互联网环境下,这种做法尤其难得,也正因此,豆瓣在前 5 年里成长为了一家独一无二的了不起的公司。然而当移动互联网大潮到来时,还是希望豆瓣能更快一点。

看看豆瓣在小组这个产品上所错失的机会吧。很难想象直到 2012 年 12 月,豆瓣才推出豆瓣小组应用。在此 2 个月前,移动社交应用陌陌在版本更新中加入群组功能。8 个月后,从零开始的陌陌拥有超过 50 万个群组,近 2000 万人在线上与线下聚集,原本聚集在 PC 互联网上的群组用户迅速向陌陌迁移,其中就包括豆瓣小组用户。

小组是豆瓣社区中意外生长出来的一个产品,生命力出奇旺盛。但多年来产品形态一直停留在 BBS 形态上,小组里面沉淀了大量信息却没有用一个好的方式展现:没有分类、没有有效的搜索、也很晚才推出移动端……

PC 互联网时代就像是乡村公路,豆瓣可以骑着自行车慢悠悠地走,还可以惬意地欣赏沿途风景,可移动互联网到来,道路突然变成高速公路,旁边的人都开足马力狂奔,豆瓣的“慢”变得有点不合时宜。

对比另一家 10 年的“老”公司大众点评,它也曾是典型的慢公司,花了 5 年时间慢慢培养社区,可是当移动互联网到来时,大众点评抓住团购的机会,抓住 O2O 的机会,它在不断逼迫自己加快速度。它不仅成为团购领域的领先者,也正在从用户决策的上游往本地生活服务各个领域的下游延伸。所有这些呈现在外界的成功背后是痛苦地自我基因改造。因为大众点评意识到,移动互联网的速度与 PC 互联网完全不同,身处其中,必须变快。

当用户和同行都已经过河了,豆瓣似乎还在慢慢摸石头,我们怕豆瓣如果不再快一点,河道关闭,就真的没有机会了。

3、能不能不那么轻?重一点!

2013 年电影票市场的爆发对豆瓣而言是一个难得的机会。

豆瓣在电影领域有多年积累,凭借丰富的影片信息的电影评论占领了用户决策的上游,在这个时间点,向用户消费延伸是顺理成章的选择。豆瓣确实这样做了,对接售票系统、在电影院布置终端售票机、面向用户售票。那段时间我从豆瓣买了不少电影票,因为不仅便宜而且也非常方便,体验很顺畅:在广播上看到朋友看过或推荐这部电影,点进去看一下简介和影评,想看,于是直接点击购票。

可一段时间后发现,售票页面的下面悄悄加上了“本业务由格瓦拉电影提供”一行灰色的小字,下面还有一行字说“选择其他供应商:微信电影票”,同时,还有很多电影院只能看到排期不能买票。这种变化说明豆瓣放弃亲自从事电影票业务,而退回到给其他购票网站导流的位置。

这可以算是豆瓣第一次尝试本地生活服务,如果成功,无论在商业收入还是产业位置上,豆瓣电影都将很有作为。对用户而言,也带来了方便和价值。然而这一次 O2O 尝试失败了。

同行们却没闲着。猫眼电影,这个从美团分离出来的电影应用,从电影团购切入,进而提供更多影院和购票选择,同时,还具备媒体属性,能让用户在里面查影讯,看影评,俨然有将豆瓣短路的趋势。

阿北曾多次明确表示豆瓣不想做电商,专心做推荐引擎,跟电商和 O2O 线下相关的事情让其他公司去做。可现在的情况是,那些电商属性的公司正在反过来占领上游,阻击豆瓣的能力。当越来越多人通过猫眼电影看影讯买票时,豆瓣电影的价值就越来越小。

豆瓣一直追求用户价值,尤其是内向价值,这点很让人敬佩。可是随着移动互联网的到来,互联网已经越来越深入现实生活中,真正改变了人们生活中的很多事情。比如大众点评改变了找餐馆这件事,看电影这件事也被互联网公司彻底改变了,读书这件事也正在发生变化,如果豆瓣依然坚持只做以技术和产品为核心的轻公司,会错失这一波浪潮,将离用户越来越远。

4、能不能别那么老派?逆生长一点!

9 岁的豆瓣在互联网上可以算是一家老公司了。看看其他同龄公司,无一不遇到逆生长的困境。

15 年历史的腾讯 CEO 马化腾在如日中天时感概, “即使你什么错都没有,就错在太老了”,这句话让一众互联网人唏嘘不已。比豆瓣大一岁的 Facebook,在过去 3 年里就一直被人质疑在移动端动作不够快,同时还接二连三地被如 Snapchat、Whatsapp 这样的新兴社交工具挑战。

移动互联网世界实在变化太快,人性也变化太多,这让互联网公司们不得不担心自己的产品过时了,不再受人喜爱,尤其不受年轻人喜爱了。我们这些 80 后都表示不懂现在的年轻人了,不知道现在的年轻人还用不用豆瓣。倒是最近很火的几款移动互联网应用,比如给陌生人打电话的“比邻”、“找你玩”、“请吃饭”等,年轻人在社交时都很直接,而不像我们当年还发豆邮……

用户需求变化了,新的技术和模式也层出不穷。在音乐领域,从 Pandora 到 Spotify 再到 8tracks,听音乐这件事被挖掘出无数种方式。读书这件事也一样,像 oysterbooks 这种网站正在试图将音乐领域的模式复制在读书上。而如今日头条、今晚看啥这种基于社交网络进行数据挖掘向用户进行个性化推荐的新事物,其实该是有着深厚社区积累的豆瓣的机会。豆瓣本身真的太久没变化了,这让它显得过于老派,甚至有点过时。

互联网公司不得不一直保持年轻,能保持逆生长是生存必需,这或者通过改造自己来实现,或者如 Facebook 一样在资本层面更坚决。真心希望豆瓣在今年和未来更广袤的移动互联网世界上,能像阿北自己说的那样——回到当初创业时的状态,有非常多的事情可以颠覆,豆瓣能与更多人一起成长。

极客观察均为极客公园原创报道,转载请注明原文链接。

原文地址: http://www.geekpark.net/read/view/203680

关注极客公园,即时获得最新内容: Twitter | 微信:极客公园 | 新浪微博 | 花瓣网 | 人人小站 | Google+ | 点点


中断与性能

$
0
0

感谢同事【空蒙】的投稿

中断,会导致正在运行的CPU要停下手头的工作去响应,这需要工作任务的切换,就带来了我们熟知的上下文切换,而频繁上下文切换,是对系统性能的重要影响因素。

那怎么减少中断带来的影响呢?

现在CPU往往是多核,如16、32核,是否可以把中断绑定到其中一个CPU上,再把其他剩余的cpu用于应用的计算。因为之前是单核的原因,传统的很多做法是会把中断扔给cpu0处理,在linux下,可执行mpstat -P ALL 1,查看各个cpu上的中断情况。

image001

这虚拟机可以看到cpu2上的中断明显偏多,每秒有6k次,这样会对性能产生什么样的影响呢?再看上下文切换的情况

image002

此时上下文切换大于1w次,再看top里面cpu对软中断与硬中断的处理情况

image003

对应的也可发现,CPU2上处里更多的中断,hi与si。如果此时我们的应用跑在CPU2上,结果可想而知就是每秒约6k次的上下文切换。既然如此那我就设置下应用使用的CPU,让java进程不在CPU2上跑,会有什么效果呢?

image004
立马可以看到,cpu2上的us变的很低了,java进程在其他的cpu上运行了,但cpu2上继续响应hi与si,再看上下文切换情况

image005

可以看到,现在上下文切换,明显比之前的少了5~6k,基本上就是之前在cpu2上的中断次数,稍做改动,就把上下文切换减少了很多

那整体的cpu利用率情况呢:

image006

如上图中间这段的数据,是设置了java进程运行在CPU0、1、3、4上的,前后两段是全部CPU跑的,大概us比不设置降低了5%,us降低了点,性能上当然可以提升点了。

当其中一个CPU的hi和si明显比其他的高,而且系统的吞吐能力上不去,很可能是中断处理不均衡的问题,当中断的数量一个CPU够处理的时候,利用亲缘绑定CPU,减少中断引起的上下文切换,但当一个CPU中断处理不够时候,就用多个CPU处理,或者所有CPU平均分摊,但所有的CPU分摊,上下文切换的次数不会减少。所以真正如何处理这个中断,还是看系统与应用的实际情况。

网卡的中断

网卡接收数据后会产生中断,让CPU来处理,一个CPU没话说,只有它干活,多核时代怎么搞呢,很多老的设备还是绑定一个CPU上。为了能充分利用多核,Google的牛人搞了个RPS、RFS的patch,能够将网卡中断分散到多个CPU,主要就是hash到固定的CPU上,具体可google查看。

但是,这明显是我等屌丝的玩法,现在高富帅的玩法是,高级的网卡是支持多队列的,找新机器cat /proc/interrupts |grep eth0,可以看到eth0-TxRx-0 ~~eth0-TxRx-7,同时每个队列的中断对应一个CPU,这样就把中断响应分摊了。

但现在cpu32核,还是显得不够平均,所以这时候还可以使用RFS,看看效果,当然RFS需要linux 2.6.35以上支持,所以那些2.6.18内核的机器,快点退休吧。

T4的机器目前虚拟机对cpu的分配,是用cgroup跨core绑定的,也就是会跨物理cpu组成一个虚拟机,这会带来cache miss的问题,至于为什么不选择一个虚拟机尽量在一个core上,大师的答复是主要是为充分利用资源,我们的应用,还没有到cache miss影响更大,还是充分利用cpu运算能力先。

那会不会我们应用虚拟机分配到的CPU,刚好全是网卡队列对应绑定的那些呢?那不吃亏死了。。。。。。

(全文完)如果您喜欢此文请点赞,分享,评论。





您可能感兴趣的文章

基于 Struts 2 拦截器实现细粒度的基于角色的存取控制

$
0
0

原文参考:http://www.ibm.com/developerworks/cn/java/j-lo-struts2-rbac/

本文介绍如何利用 Struts 2 拦截器来为 Java Web 应用添加应用管理的基于角色的存取控制(Role-Based Access Control,RBAC)的设计和实现方法。相对于容器提供的存取控制,它能够更细粒度地控制资源,处理更加复杂的情况。

 

引言

Apache Struts 作为最成功的 MVC Web 框架早已得到了广泛的应用,但是其自身也暴露出不少缺点,从而引出了 Struts 2 。 Struts 2 摒弃了原来 Struts 1 的设计, 而是转向了 webwork2,并结合 Struts 已有的优点,试图打造出一个集众家所长的完美 Web 框架。 Struts 2 因此也具备 webwork2 中的一个非常重要的特性 - 拦截器 (Interceptor) 。拦截器会在 Action 执行之前和之后被执行(如下图),是一种典型 AOP 实现。

图 1. Struts 2 的体系结构

Struts 2 的体系结构

Struts 2 本身提供了一个  org.apache.struts2.interceptor.RolesInterceptor 拦截器以方便开发人员来实现存取控制。但该拦截器的实现是建立在 J2EE 容器提供的存取控制机制之上的。容器提供的存取控制实现粒度较粗,往往无法满足多数应用的需求。在许多项目中,用户所应该具有的权限是由多种因素而决定,往往在不同的上下文中拥有不同的角色。例如在一个社交项目中,一个用户会在不同的社团里拥有不同的角色,如成员,管理员,来宾等。他的具体角色取决于当前所处社团的标识符。另外,用户的角色还和他所要操作的资源类型有关。比如,在这个社交站点中,用户可以创建自己的日程表,把这个日程表共享给其他用户或者委托给其他人管理。这样对日程表这种类型资源,就会有创建者,阅览者和管理者三种角色。在更复杂应用中,用户的角色可能还会受更多因素决定,这就要求存取控制要有更细的粒度,能够处理更加复杂的逻辑。

为了满足这个需求,在基于 Struts 2 的 Web 应用开发中,我们也可以利用拦截器来实现一个应用托管的基于角色的存取控制(RBAC, Role-Based Access Control)系统, 让其能够管理更细粒度的资源。该系统在 Struts 2 的配置文件中定义 Action 可以由那些角色来调用,即对角色进行授权。拦截器在 Action 调用之前,对当前用户进行权限认证来决定 Action 是否应该被执行。

下面我们就基于 Hibernate+Spring+Struts2 框架来完成这个系统的实现。为了使系统结构更加清晰易于维护,我们将这个系统分为域模型层、持久层和服务层来实现。这种分层结构是目前 Web 开发广为使用的一种模式。

模型层实现

这系统中我们只需要一个实体 UserRole, 用来定义用户在不同的上下文中所具有的角色。在清单中,我们使用了 Java Persistence API (Hibernate 从 3.2 开始已经开始支持 JPA)中提供的 JDK 5.0 注解来对模型到数据库表之间的映射进行定义。

清单 1.
 @Entity 
 public class UserRole { 
  private Long id; 
  private User user; 
  private String objectType; 
  private Long objectId; 
  private String role; 
  public UserRole(Long userId, String role, String objectType, Long objectId) { 
    User user = new User(); 
    user.setId(userId); 
    this.user = user; 
    this.role = role; 
    this.objectType = objectType; 
    this.objectId = objectId; 
  } 
  @Id 
  @GeneratedValue(strategy = GenerationType.AUTO) 
  public Long getId() { 
    return id; 
  } 
  public void setId(Long id) { 
    this.id = id; 
  } 
  @ManyToOne 
  @JoinColumn(name = "userId", nullable = false) 
  public User getUser() { 
    return user; 
  } 
  public void setUser(User user) { 
    this.user = user; 
  } 
  public String getObjectType() { 
    return objectType; 
  } 
  public void setObjectType(String objectType) { 
    this.objectType = objectType; 
  } 
  public Long getObjectId() { 
    return objectId; 
  } 
  public void setObjectId(Long objectId) { 
    this.objectId = objectId; 
  } 
  public String getRole() { 
    return role; 
  } 
  public void setRole(String role) { 
    this.role = role; 
  } 
 }

注意这里边有两个比较特殊的字段  objectType 和  objectId,它们用来表明用户在具体哪个资源上拥有的角色。  objectType 指资源的类型, objectId 指资源的标识。比如我们要将用户 Mike 加为某个日程表的管理员,则表中新增记录的  user 字段为 Mike 在 user 表中的 ID, objectType 为“calendar”, objectID 为这个日程表 ID, role 为角色的名字“admin”。当然,如果您的应用中不同类型资源都使用唯一的全局 ID, objectType 这个字段也可以省略。

DAO 层实现

代码清单 2 定义了对 UserRole 进行 CRUD 的 DAO 接口,代码清单 3 则是它的实现。通过  @PersistenceContext 注解来让容器注入 JPA 中的实体管理器 EntityManager 。 UserRoleDaoImpl 调用 EntityManager 来对 UserRole 进行持久化到数据库中。

清单 2
public interface UserRoleDao { 
 public void create(UserRole userRole); 
 public void update(UserRole userRole); 
 public UserRole find(Long userId, String objectType, Long objectId); 
 }
清单 3
public class UserRoleDaoImpl implements UserRoleDao { 
  private EntityManager entityManager; 
  public EntityManager getEntityManager() { 
    return entityManager; 
  } 

  @PersistenceContext 
  public void setEntityManager(EntityManager entityManager) { 
    this.entityManager = entityManager; 
  } 

  public void create(UserRole userRole) { 
    entityManager.persist(userRole); 
  } 

  public UserRole find(Long userId, String objectType, Long objectId) { 
    Query query = entityManager.createQuery(
      "FROM UserRole ur WHERE ur.user.id=" + 
      userId + " AND ur.objectType='" + 
      objectType + "' AND ur.objectId=" + 
      objectId); 
    List result = query.getResultList(); 
    if (result.size() == 0) 
      return null; 
    return (UserRole)result.get(0); 
  } 

  public void update(UserRole userRole) { 
    entityManager.merge(userRole); 
  } 
 }

服务层实现

创建一个  RoleService 接口 (清单 4) 作为 façade, 清单 5 是具体实现。  RoleServiceImpl 的实现很简单,主要是封装了为用户分配角色和查询用户角色。注解 Transactional 用来将方法放置在一个事务中进行。在类声明上的  @Transactional(readOnly = true) 表示默认的事务为只读。  setUserRole 方法需要写入数据到数据库中,因此我们将其  readOnly 属性设置成 false.

清单 4
public interface RoleService { 
    public void setUserRole(Long userId, String role, String objectType, Long objectId); 
    public String findRole(Long userId, String objectType, Long objectId); 
 }
清单 5
@Transactional(readOnly = true) 
 public class RoleServiceImpl implements RoleService { 

 private UserRoleDao userRoleDao; 

 public void setUserRoleDao(UserRoleDao userRoleDao) { 
    this.userRoleDao = userRoleDao; 
 } 

  @Transactional(readOnly = false) 
  public void setUserRole(Long userId, String role, String objectType, Long objectId) { 
    UserRole userRole = new UserRole(userId, role, objectType, objectId); 
    UserRole userRoleInDB = userRoleDao.find(userId, objectType, objectId); 
    if (null == userRoleInDB) { 
      userRoleDao.create(userRole); 
    } else { 
      userRole.setId(userRoleInDB.getId()); 
      userRoleDao.update(userRole); 
    } 
  } 

  public String findRole(Long userId, String objectType, Long objectId) { 
    UserRole userRole = userRoleDao.find(userId, objectType, objectId); 
    if (userRole == null) { 
      return null; 
    } 
    return userRole.getRole(); 
  } 
 }

拦截器的实现

拦截器会在 Action 被执行之前被 Struts 2 框架所调用,我们利用这个特性来完成对用户身份的认证,只有用户具有正确角色方能执行 Action 。具体哪些角色可以执行 Action,需要在 Struts 2 的配置文件中指定,将在下一小节中详细阐述。这一点和 Struts 2 内置的 RolesInterceptor 类似,但我们的拦截器可以通过  objectType 和  objectId 来实现更加细粒度的认证。

要创建一个用于用户角色认证的拦截器。需要让其实现  com.opensymphony.xwork2.interceptor.Interceptor 接口并对  String intercept(ActionInvocation actionInvocation) throws Exception 方法进行实现。 如清单 6 。成员变量  roleService 是通过 Spring 的依赖注入被赋予 RoleServiceImpl 。  allowedRoles 和  disallowedRoles 分别存储了允许和不允许执行 Action 的角色,两者不能同时存在。 objectType 和  objectIdKey 分别表示资源的类型和资源 ID 在 HTTP 请求中的参数名。它们是做为 Interceptor 的参数在 Struts 2 配置文件中进行设置,会自动由 Struts 2 框架填充进来。

清单 6
public class RBACInterceptor implements Interceptor { 
   public static final String FORBIDDEN = "forbidden"; 
   private List<String> allowedRoles = new ArrayList<String>(); 
   private List<String> disallowedRoles = new ArrayList<String>(); 
   private RoleService roleService; 
   private String objectType; 
   private String objectIdKey;   
   public void setRoleService(RoleService roleService) { 
  this.roleService = roleService; 
   } 

   public void setObjectType(String objectType) { 
    this.objectType = objectType; 
  } 
  public void setObjectIdKey(String objectIdKey) { 
    this.objectIdKey = objectIdKey; 
  } 

  public void setAllowedRoles(String roles) { 
    if (roles != null) 
      allowedRoles = Arrays.asList(roles.split("[ ]*,[ ]*")); 
  } 

  public void setDisallowedRoles(String roles) { 
    if (roles != null) 
      disallowedRoles = Arrays.asList(roles.split("[ ]*,[ ]*")); 
  } 

  public void init() { 
  } 
    
  public void destroy() { 
  } 
    
  public String intercept(ActionInvocation actionInvocation) throws Exception { 
    HttpServletRequest request = ServletActionContext.getRequest(); 
    // Get object id 
    Long objectId = Long.valueOf(request.getParameter(objectIdKey)); 
    Map session = actionInvocation.getInvocationContext().getSession(); 
    // Get current user id 
    Long userId = (Long) session.get(Constant.KEY_CURRENT_USER); 
    // Get the user role 
    String userRole = roleService.findRole(userId, objectType, objectId); 

    if (!isAllowed(userRole)) { 
      // forbid invoking the action 
      return FORBIDDEN; 
    } else { 
      // allow invoking the action 
      return actionInvocation.invoke(); 
    } 
  } 

  // Check if the current user has correct role to invoke the action 
  protected boolean isAllowed(String userRole) { 
    if (allowedRoles.size() > 0) { 
      if (userRole == null) 
        return false; 
      return allowedRoles.contains(userRole); 
    } else if (disallowedRoles.size() > 0) { 
      if (userRole == null) 
        return true; 
      return !disallowedRoles.contains(userRole); 
    } 
    return true; 
  } 
 }

在  intercept 方法中我们根据当前用户的 ID,HTTP 请求参数中获得资源的 ID,所存取的资源类型来调用  RoleService 获得用户的角色。 然后再判断该角色是否在  allowedRoles 和  disallowedRoles 中来确定用户是否有权限调用 Action 。如果用户没权限,则将请求发送到名为“forbidden”的 result 。从这里可以看出,用户的角色验证与身份验证的作用完全不同。身份验证是验证用户是否网站注册用户,而角色认证是在用户为注册用户的前提下对用户相对于站内各种资源扮演的角色的辨别。

上面代码中用到了判断用户是否具有运行 Action 所要求的角色的函数  isAllowed()。它首先根据用户 ID 和 Action 作用于的对象的类型和 ID 从数据库查询到用户对应的角色,然后将用户角色与允许角色的列表逐个比较。如果允许角色列表包含用户实际角色则返回真,否则返回假;如果允许角色列表为空,则将用户角色与禁止角色的列表比较,如果用户的角色被禁止,则返回假,否则返回真。如果两个列表都为空,也返回真。这样既可以对某个 Action 配置允许访问角色列表,也可以配置拒绝访问列表。

使用

首先我需要在 Spring 的配置文件中添加系统中所涉及到各个 POJO,如清单 7 。

清单 7
<!-- Data Access Objects --> <bean id="userRoleDao" class="com.sample.security.dao.impl.UserRoleDaoImpl"/> <!-- Service Objects --> <bean id="roleService" 
    class="com. sample.security.service.impl.RoleServiceImpl" > <property name="userRoleDao" ref="userRoleDao" /> </bean> <!-- Interceptor Objects --> <bean id="RBACInterceptor" scope="prototype" 
    class="com. sample.security.interceptor. RBACInterceptor "> <property name="roleService"  ref="roleService" /> </bean>

然后需要在 Struts 配置文件中对需要进行存取控制的 Action 进行配置。首先定义我们实现的拦截器,并把其加到拦截器栈中。在 <interceptors> …… </interceptors> 中添加下面的代码。

<interceptor name="RBAC ” class="RBACInterceptor" />

现在我们可以将 RBAC 拦截器添加到任意的 interceptor-stack 中,或者直接配置到任意的 Action 。添加下面清单中的内容到 Struts 2 配置文件中,将能够对在一个日程表中删除会议进行控制。

清单 8
<action name="deleteMeeting" class="com.demo.action.DeleteMeetingAction"> <result>/WEB-INF/jsp/deleteMeetingResult.jsp</result> <result name="forbidden">/WEB-INF/jsp/forbidden.jsp</result> <interceptor-ref name="RBAC"> <param name="allowedRoles">admin, owner</param> <param name="objectType">calendar</param> <param name="objectIdKey">id</param> </interceptor-ref> <interceptor-ref name="defaultStack" /> </action>

至于用户角色的分配,我们可以定义一个 Action 通过  RoleService 来创建。如下面清单 9 的配置和清单 10 的代码实现了一个 Action 允许日程表的创建者来分配角色给其它人。

清单 9
<action name="assignCalendarRole" class="com.demo.action.AssignCalendarRoleAction"> <result>/WEB-INF/jsp/deleteMeetingResult.jsp</result> <result name="forbidden">/WEB-INF/jsp/forbidden.jsp</result> <interceptor-ref name="RBAC"> <param name="allowedRoles">owner</param> <param name="objectType">calendar</param> <param name="objectIdKey">id</param> </interceptor-ref> <interceptor-ref name="defaultStack" /> </action>
清单 10
public class AssignCalendarRoleAction extends ActionSupport { 
  private RoleService roleService; 
  private Long userId = 0; 
  private String userRole = "reader"; 
 private Long id = 0; 

  public AssignCalendarRoleAction (RoleService roleService) { 
    this.roleService = roleService; 
  } 

 public String execute() { 
 roleService.setUserRole(userId, userRole, "calendar", id); 
 return SUCCESS; 
  } 
 }

结束语

本文介绍了如何在 Spring+Hibernate+Struts2 框架中实现一个应用托管的 RBAC 系统,不同于容器提供的 RBAC,它能够更加细粒度地对各种资源进行存取控制。这里的实现非常简单,还需要许多地方可以进行扩展和完善(比如对用户组的支持),希望能对读者起到抛砖引玉的作用。



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


ITeye推荐



RAC环境下的sequence详解(原创)

$
0
0

在RAC环境中,序列的Cache问题可能会对性能有着决定性的影响,缺省的序列Cache值为20,这对RAC环境远远不够。
如果存在序列号使用的竞争,就可能在数据库中看到明显的队列等待:
enq: SQ - contention
在RAC情况下,可以将使用频繁的序列Cache值增加到10000,或者更高到50000,这些值在客户的环境中都有采用。
这是RAC设置和RAC使用的基本常识,不可或忘。
在以下测试中,可以显示Cache序列对于性能的影响:
http://space.itpub.net/14941137/viewspace-629941
摘要如下:
RAC两个会话分别处于不同node同时并发循环间断去取4万个值  :           
    nocache:               2100s
    cache =1000:         55s
差别却是好大。
单Instance数据库单会话循环不间断去1-4万个值  测试(在家里笔记本上测试结果)过程如下:
    nocache:             37.7s          10000   
    cache :20            4.31s          10000
    cache :100         2.92s           10000
    cache :1000       5.56s          40000
    nocache:             97.7s         40000
基本上cache 大于20的时候性能基本可以接受,最好设置100以上,
nocache的时候性能确实很差,最大相差20倍.
排序参数:oracle默认是NOORDER,如果设置为ORDER;在单实例环境没有影响,在RAC环境此时,多实例实际缓存相同的序列,此时在多个实例 并发取该序列的时候,会有短暂的资源竞争来在多实例之间进行同步。因次性能相比noorder要差,所以RAC环境非必须的情况下不要使用ORDER,尤 其要避免NOCACHE   ORDER组合;
在某些版本中存在BUG,会导致过度的 enq : SQ 竞争。

如在Oracle Database 11g中存在 IDGEN$ 序列 cache 设置过小问题,可能导致严重竞争,建议增加该序列的Cache值设置。

RAC环境下与sequence相关的锁

oracle为了在rac环境下为了sequence的一致性,使用了三种锁:row cache lock、SQ锁、SV锁。
row cache lock的目的是在sequence指定nocache的情况下调用sequence.nextval过程中保证序列的顺序性;
SQ锁是应用于指定了cache+noorder的情况下调用sequence.nextval过程中。
SV锁(dfs lock handel) 是调用sequence.nextval期间拥有的锁。前提是创建sequence时指定了cache 和order属性 (cache+order)。order参数的目的是为了在RAC上节点之间生成sequence的顺序得到保障。
创建sequence赋予的cache值较小时,有enq:sq-contention等待增加的趋势。
cache的缺省值是20.因此创建并发访问多的sequence时,cacheh值应取大一些。否则会发生enq:sq-contention等待事件。
rac上创建sequence时,如果指定了cache大小同时赋予了noorder属性,则各节点将会把不同范围的sequence值cache到内存上。

create sequence TX_SEND_SEQ_ACC
minvalue 1
maxvalue 999999999999999999999999999
start with 673560
increment by 1
cache 20;
RAC1取序列
SQL> select tx_send_seq_acc.nextval from dual;
   NEXTVAL
----------
    673560
SQL> select tx_send_seq_acc.nextval from dual;
   NEXTVAL
----------
    673561
RAC2取序列
SQL> select tx_send_seq_acc.nextval from dual;
   NEXTVAL
----------
    673580
SQL> select tx_send_seq_acc.nextval from dual;
   NEXTVAL
----------
    673581

若两个节点之间都必须通过依次递增方式使用sequence,必须赋予如下的order属性
如果是已赋予了cache+order属性的sequence,oracle使用SV锁进行同步。SV锁争用问题发生时的解决方法与sq锁的情况相同,就是将cache 值进行适当调整。
在RAC多节点环境下,Sequence的Cache属性对性能的影响很大。应该尽量赋予cache+noorder属性,并要给予足够的cache值。如果需要保障顺序,必须赋予cache+order属性。但这时为了保障顺序,实例之间需要不断的交换数据。因此性能稍差。

oracle RAC环境sequence不一致问题
Sequences in Oracle 10g RAC
Just recently I got a call from a developer. He had a table with a primary key populated by a sequence, a timestamp column with the current date and some other columns. He had a specific set of data that, when ordered by the primary key had out of order timestamps. He was puzzled how this could be. This is a RAC database and the sequence was created with the default values.  Not only the sequences cache was the default of 20, but it was “noordered”.  Being “noordered” Oracle will not guarantee the order in which numbers are generated.
Example of “noorder” sequence in 10g RAC:
Session 1 on node-A: nextval -> 101
Session 2 on node-A: nextval -> 102
Session 1 on node-B: nextval -> 121
Session 1 on node-B: nextval -> 122
Session 1 on node-A: nextval -> 103
Session 1 on node-A: nextval -> 104
The sequence cache is in the shared pool, therefore sessions on the same node can share the cached entry, but sessions on different nodes cannot. I wonder why Oracle doesnt make “ordered” the default for sequences.  So I explained to the developer how sequences work in RAC and how each node has its own “cache”.
We changed the sequence to “ordered” and increased the cache to 1000. Now selecting on either node gets the next number as he expected. I warned him that there would be some performance implications due to cluster synchronization. Him been a responsive developer, asked me what would be the impact, so I tested it out.
How does RAC synchronize sequences?
In Oracle 10g RAC, if you specify the “ordered” clause for a sequence, then a global lock is allocated by the node when you access the sequence.  This lock acquisition happens only at the first sequence access for the node (A), and subsequent uses of the sequence do not wait on this lock. If another node (B) selects from that sequence, it requests the same global lock and once acquired it returns the sequences next value.  The wait event associated with this activity is recorded as "events in waitclass Other" when looked in gv$system_event. So much for event groups, it couldn't be more obscure. That view shows overall statistics for the session.
However if you look in the gv$session_wait_history it shows as “DFS lock handle” with the “p1″ parameter been the object_id of the sequence. This second view has a sample of the last 10 wait events for a session.
In a SQL_TRACE with waitevents (10046 trace) it will be a "DFS lock handle" but in AWR or statspack reports it will be “events in waitclass Other”. So much for consistency.
How does that change our example?
Session 1 on node-A: nextval -> 101 (DFS Lock handle) (CR read)
Session 2 on node-A: nextval -> 102
Session 1 on node-B: nextval -> 103 (DFS Lock handle)
Session 1 on node-B: nextval -> 104
Session 1 on node-A: nextval -> 105 (DFS Lock handle)
Session 1 on node-A: nextval -> 106
(more selects)
Session 1 on node-A: nextval -> 998
Session 1 on node-B: nextval -> 999 (DFS Lock handle)
Session 1 on node-B: nextval -> 1000 (CR read)
The cache size also has some RAC synchronization implications. When the cached entries for the sequence are exhausted, the sequence object needs to be updated. This usually causes a remote CR (current read) over the interconnect for the block that has the specific sequence object. So a bit more activity here.
Test case:
create sequence test_rac;
declare
  dummy number;
begin
  for i in 1..50000 loop
    select test_rac.nextval into dummy from dual;
  end loop;
end;
/
Results:
50 000 loops with cache = 20 (default)
1 node = 5 seconds
2 nodes at same time = 14 seconds
2 nodes at same time ordered = 30 seconds
50 000 loops with cache = 1000
1 node = 1.5 seconds
2 nodes at same time = 1.8 seconds
2 nodes at same time ordered = 20 seconds
With a smaller cache, the “noordered” still has as significant impact as every 10 fetches (cache 20 divided by 2 nodes fetching) it has to synchronize between the 2 nodes
The conclusion
By default sequences in 10g RAC are created without ordering. Beware of using applications that rely on sequences to be ordered and using it in a RAC environment.  Consider changing all user sequences to “ordered” as a precaution and increasing the cache size.  The default cache value is still very low and even not-ordered sequences will cause contention in a highly-active sequence even in non-RAC and causing an additional block exchange every 20 values in RAC.
For high volume insert operations where ordering is not performed on the value returned from the sequence, consider leaving the sequence “noordered” but increasing the cache size significantly.
Either way, the sequence parameters should be reviewed, as chances are, the defaults are not what you need.  I remember reading somewhere that in Oracle 9i the “ordered” clause in RAC was equivalent to “nochache”.  I cant imagine how bad that would be in concurrent selects from the same sequence.  It would be interesting if someone running 9i RAC performs the test case and I would appreciate if you post the results in the comments.

参考至:http://www.aixchina.net/home/space.php?uid=20260&do=blog&id=26752

              http://blog.csdn.net/zftang/article/details/6321513

              http://www.eygle.com/archives/2012/05/oracle_rac_sequence_cache.html

              http://wenku.baidu.com/link?url=GpJLv3Lrl6YxxnYMkZZlMcKa9z9LCaAVyXsp-tjizHlvUluY3jQbPaZBAMiNvw1md9_yZEIWCl9CD2JeakbM--u8ngBz7Bs27lfLSNgEPg_

              http://bbs.csdn.net/topics/390310984

本文原创,转载请注明出处、作者

如有错误,欢迎指正

邮箱:czmcj@163.com



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


ITeye推荐



滚动条触事件的底获取及处理

$
0
0
1、获取滚动条触底事件;
2、屏蔽对一秒之内的多次触底的响应;
3、标记滚动条位置,实现刷新后的位置恢复


var height = 0;
var index=1;
var cal=1;

//获取滚动条上下滑动的距离
function getScrollTop(){
	var scrollTop = 0, bodyScrollTop = 0, documentScrollTop = 0;
	if(document.body){
		bodyScrollTop = document.body.scrollTop;
	}
	if(document.documentElement){
		documentScrollTop = document.documentElement.scrollTop;
	}
		scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;
		return scrollTop;
	}


//页面的总高度
function getScrollHeight(){
		var scrollHeight = 0, bodyScrollHeight = 0, documentScrollHeight = 0;
		if(document.body){
			bodyScrollHeight = document.body.scrollHeight;
		}
		if(document.documentElement){
			documentScrollHeight = document.documentElement.scrollHeight;
		}
		scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight;
		return scrollHeight;
	}


//浏览器显示窗口的高度
function getWindowHeight(){
		var windowHeight = 0;
		if(document.compatMode == "CSS1Compat"){
			windowHeight = document.documentElement.clientHeight;
		}else{
			windowHeight = document.body.clientHeight;
		}
		return windowHeight;
	}

	var timeout = false; 
	var result;
	window.onscroll = function(){
		//阀门算法思想:setTimeout处理完成后开启阀门,clearTimeout清理后关闭阀门
		if (timeout){clearTimeout(result);timeout = false;}
		result = setTimeout(function(){ 
			if(getScrollTop() + getWindowHeight() == getScrollHeight()){
				//alert("you are in the bottom!");
				//保存触底时滚动条滑动的高度,作为重置滚动条位置的依据
				height = getScrollTop();
				//alert(getScrollHeight());
				//alert(getScrollTop());
				//alert(getWindowHeight());
				if (cal < document.getElementById("mainForm:dataList:countPage").value){
					cal++;
					//alert(document.getElementById("mainForm:dataList:countPage").value);
					getNewDatas(20*index,20);
					index++;
				}
			}
		},1000);
		timeout = true;
	};


//重置滚动条的位置到上次滚动的位置
function setOnscroll(){
	 window.scrollTo(0, height);
}




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


ITeye推荐



HTML5游戏前端开发秘籍

$
0
0

QQ空间Android版4.2和4.5上线的玩吧游戏“空间疯狂套牛”是一款使用HTML5开发出的手机游戏,虽然还有很多不足,但其中使用的一些技术与技巧还是很有必要为大家分享出来,方便大家用秘籍打通各种关卡创造出更多更好的HTML5游戏。(本秘籍主要讲述使用HTML + CSS技术方面)

HTML5游戏前端开发秘籍

一、自适应

Android手机的屏幕碎片化非常严重,各种各样的分辨率非常之多,那么如何让游戏可以适配这些机型就显得尤为重要。这里也是前前后实验了多种方案。先想到用JS来动态的根据分辨率来设置相关元素的大小,但要么 在某些机型中获取失败,要么用 赋值缩放相关元素时造成游戏崩溃……

反反复复折腾良久之后,终于在克强同志宣布沪港通A股H股大升的日子里想到了用  absolute来解决:
关键代码:

1
2
3
4
5
6
7
8
.game {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    overflow: hidden;
    }

以上代码使游戏容器绝对定位之后, 上下左右四边都与手机屏幕贴紧,从而做到了适配各种屏幕。
然后使用 background-size:cover;让背景覆盖全屏,此代码可以使背景  自动缩放 至  覆盖 容器大小。

然后内部的元素 根据其所接近的边来做动态布局,下面是一个例子:
关键代码:

1
2
3
4
5
6
7
8
.runbox {
    position: absolute;
    bottom: 110px;
    left: 0;
    width: 100%;
    overflow: hidden;
    padding-bottom: 10px;
    }

以上代码是牛跑动的跑道容器,使用  bottom基于底边来定位(绳子是自下而上套出去,方便写套牛逻辑),并且100%宽,保证牛从屏幕边缘出现。

以下是一个动态居中的例子:
关键代码:

1
2
3
4
5
6
7
8
9
.welcome {
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -100px 0 0 -111px;
    background-image: url("img/wel.png");
    width: 273px;
    height: 228px;
    }

以上代码使元素用绝对定位后,用 top:50%; left:50%;使元素的左上角位于屏幕最中间,然后用  margin-left:-111px; margin-top:-100px; 使元素偏移自身宽高值的一半,从而达到元素居中的效果。

通过这两个例子,大家即可灵活使用来实现自适应的动态布局。
最终效果如下,无论屏幕怎么变化,都可以非常恰当的显示:
HTML5游戏前端开发秘籍

二、帧动画

好的游戏一定有好的动画,感谢设计师为游戏带来优美的视觉享受,如果在实现时有所折扣那是对设计师工作的不尊重,所以在实现动画这里,我们要谨慎小心。

一般来说游戏动画可以大约分解成两种,一种是元素自身动作动画,比如 像一张GIF一样不断的循环播放的动画(以下称为帧动画),另一种是这个’GIF’移动时的运行动画。通过合理的使用两种动画即可实现想要的效果

帧动画的实现,以牛向前跑去为例,分解为 牛的跑动 和 牛的移动 两种。牛的跑动 这里我们根据之前在做PC与手机联动的小游戏,如: 障碍越野赛爬花藤中秋小游戏中积累的经验,再结合手机浏览器主要是webkit内核,这里选用 animation-timing-function:steps(4, start); 做帧动画即可。他的原理还是在不断的移动背景图片,但每种背景会保留一段时间,通过他我们可以方便的制作帧动画,并且还有 animation 的大量控制属性可以用, 深入的话可以控制帧动画的细节如速度、方向、时间等,这里就不一一展开了。

牛的跑动 图片要切成这样(要保证每种状态的图片大小一致,并且合并到一张图片上):
HTML5游戏前端开发秘籍

具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.ox2 {
    background-image: url("img/ox2.png");
    -webkit-background-size: auto 73px;
    background-size: auto 73px;
    width: 97px;
    height: 73px;
    -webkit-animation: ox2 1s steps(4, start) 0ms infinite normal backwards;
    animation: ox2 1s steps(4, start) 0ms infinite normal backwards;
    }
@-webkit-keyframes ox2 {
    0% {
        background-position: 0 0
        }
    100% {
        background-position: -388px 0
        }
    }
 
@keyframes ox2 {
    0% {
        background-position: 0 0
        }
    100% {
        background-position: -388px 0
        }
    }

以上代码主要有几个要点:

  • 元素的容器宽高就是这个元素的大小 width: 97px; height: 73px;
  • 为了适配Retina所以图片是按2x大小做的,然后又通过  background-size: auto 73px; 将图片缩小到1/2大小
  • 因为有4个状态,所以animation-timing-function 设置为: steps(4, start)
  • keyframes用background-positon来移动背景图,这里因为我们为了适配Retina将图片缩小了一半,所以移动位置也要缩放一半 background-position: -388px 0

这里的难点在于图片的处理,以下是一些实例,供大家参考:
HTML5游戏前端开发秘籍
HTML5游戏前端开发秘籍
HTML5游戏前端开发秘籍

另外要注意绳子这里其实是有 三个状态,要分成三个CLASS来写,方便开发时切换不同的状态
状态一:绳子没套之前的旋转,这个动画要循环播放
HTML5游戏前端开发秘籍

状态二:绳子套出的动画,这个动画之播放一次
HTML5游戏前端开发秘籍

状态三:套中牛后牛的挣扎,这里主要是牛的动画,但要有一个绳子来表现牛被绳子套着
HTML5游戏前端开发秘籍
HTML5游戏前端开发秘籍《==== 这就是套着牛的那个绳,是没有动画的。

三、CSS transform性能

牛除了帧动画,还有运行动画,这个相对简单,或许你认为动态改top, left值就可以了,但这里之前在ISUX文章《 被解放的GPU》和《 High Performance Animations》中都有过实验和验证: 充分的发挥GPU是性能提升的快捷途径
所以我们在 让素材移动时时减少使用 top left 等属性来移动,而要使用 transform 来触发手机的GUP来提升性能
所以牛的移动等元素移动动画都是使用 trosfrom 来实现。
HTML5游戏前端开发秘籍
HTML5游戏前端开发秘籍

四、描边字

HTML5游戏前端开发秘籍
除了以上比较重要的点,还有一些细节,比如设计稿中有很多描边字,如果都切成图片将造成大量的图片素材, 工作量大,加载速度慢,难维护
这里反复实验,发现使用 text-shadow的多重属性即可实现,多重属性主要用  ,(逗号)来分隔,给元素可以设置多个不同参数的属性。
关键代码:

1
2
3
4
.text-shadow {
    color: #FFE339;
    text-shadow: 1px 0 0 #622E00, -1px 0 0 #622E00, 0 1px 0 #622E00, 0 -1px 0 #622E00;
    }

以上代码主要设置4个多重属性,然后分别让他们位于元素的上下左右,从而实现描边的效果。

注意: text-shadow的最后一个 ;(分号) 一定不能省略, CssGaga压缩时会删除最后一个属性的分号,导致页面出错,所以text-shadow不能放在最后!

五、扁平化

技术的问题说的差不多了,这里还要说下设计这里还有一个可优化的空间,这就是 扁平化,从技术角度来看扁平化有三大优点:

  1. 图片素材好制作
  2. 游戏打开速度快
  3. 运行速度快

前两点比较好理解,第三点的原因是因为图片比较小,所以占用的内存也小,运行速度当然会快一些,甚至于省电 :D
以下在几个很好的扁平化游戏

中秋小游戏
HTML5游戏前端开发秘籍
障碍越野赛
HTML5游戏前端开发秘籍
非常期待设计师们在扁平化游戏上的尝试。

六、2x图

最后这里说一个CssGaga的一个BUG,在制作兼容Retina的2x图时,如果2x图的长宽不是偶数,那么最后生成的1x雪碧图会有错位。
所以, 2x图长宽一定得是偶数大小
什么,你还不知道 CssGaga是什么,这是做页面的大神器啊,可以自动合并CSS、生成雪碧图、生成兼容Retina的代码、同步资源、生成提单列表等,快来使用吧!

以上即是HTML5游戏前端开发秘籍,还请大家多多指教。

Viewing all 15907 articles
Browse latest View live


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