Text
如何写一手漂亮的 Vue
目前犹身为前端开发者,且在使用 Vue,那么就有了此文;这不仅是纪录或分享,也是在漫漫之路上下求索,更希望能探讨和指点,以资见识,提升其效
The post 如何写一手漂亮的 Vue appeared first on WEB前端 - 伯乐在线.
0 notes
Text
Google 地圖即時分享位置功能開放! Android iPhone 皆可共用
我和老婆一直有彼此共享位置的需要,之前因為兩人都在台北基隆通勤上下班,所以會透過即時共享定位來追蹤一起下班約吃晚餐的時間。而現在開始開車帶老婆上下班,更需要掌握彼此的位置。 所以之前我們利用了一些不同的方法共享定位,早期是 Google+,後來利用第三方的 App「 My Family 」,但現在更方便了,直接透過我們兩人都愛用的 Google 地圖就能解決需求。 今天開始, Google 地圖內建的「即時分享位置」功能正式開放,而且我的 Android 手機與老婆的 iPhone 手機都同時開放,這馬上成為我們兩人的即時共享位置解決方案。
如何開啟分享位置功能?
要開啟 Google 地圖上的「分享位置」功能很簡單,直接打開地圖左方的選單,如果你看到「分享位置( 新功能)」的選項,那麼表示你獲得更新了。
如何跟他人分享位置?
進入「分享位置」,就能開始跟其他親朋好友共享位置,我們可以設定分享的時限。 例如臨時跟朋友有活動需要共享位置,可以設定一兩個小時後自動解除分享。 如果像我跟老婆的需求,就可以開啟「直到手動關閉為止」。 接著在下面選擇一個聯絡人把位置共享給他即可。
在 Google 地圖上隨時看到朋友的位置:
如果我共享給他的聯絡人有 Google 帳號,那麼他就可以開始在他的 Google 地圖 App 中,即時看到我的頭像出現在地圖上,表示我目前所在位置。(或者也可以用共用連結的方式,跟他人分享你的位置) 你也不用擔心這會多消耗電力,其實開啟 GPS 定位,正常使用 Google 地圖,是不會大幅度影響手機電力的,可以參考下面兩篇文章的說明: 手機 GPS定位 WiFi定位 怎樣省電精準?地圖實測比較心得 不裝省電 App,從最有效開始 Android 手機省電步驟
建立家人捷徑:
進入該朋友的位置分享畫面中,還能將家人頭像加入 Android 的桌面捷徑,方便我隨時開啟追蹤模式。 在一天簡單的測試中,我覺得 Google 地圖內建的「即時分享位置」功能更新很即時,確實可以即時回報家人好友的定位,並且開啟與關閉都很容易,對於需要分享位置資訊的朋友來說,可以好好利用。 延伸閱讀相關文章:
最好用自動記錄旅行足跡 App 推薦!旅遊與運動必備
自訂地圖麻煩?我都這樣輕鬆在 Google 地圖規劃旅行
台灣版 Google 地圖欠缺功能就用這 11 款 App 補足
轉貼本文時禁止修改,禁止商業使用,並且必須註明來自電腦玩物原創作者 esor huang(異塵行者),及附上原文連結:Google 地圖即時分享位置功能開放! Android iPhone 皆可共用
喜歡這篇文章嗎?歡迎追蹤我的Facebook、Twitter、Google+,獲得更多有趣、實用的數位科技消息,更歡迎直接透過社群聯繫我。
0 notes
Text
统计学家证明了一个重要猜想,但几乎被人遗忘
昨天发生的事情今天可能就传遍全世界了,但偶尔也有例外。鲜为人知的退休德国统计学家 Thomas Royen 在 2014 年 7 月 17 日刷牙时突然发现了一个著名的几何、概率论和统计学猜想的证明。名叫 Gaussian correlation inequality (GCI)的猜想于 1950 年代提出,困扰了数学家数十年。统计学家
Donald Richards
说,他知道有人在这个猜想上工作了 40 年,他本人为此工作了 30年,运用越来越先进和复杂方法却仍然没能证明,甚至开始怀疑猜想是错误的。而 Royen 以前并没有过多关注这个猜想,2014 年 7 月,他发现 GCI 可以扩展为他所擅长的一个有关统计分布的声明。17 日早晨,他找到了如何计算扩展 GCI 关键导数的方法,完成了证明。他没听说过 LaTeX,所以
论文
是写在微软的 Microsoft Word 上,上传到 arxiv.org,并发送给知名的统计学家核查,其中之一就是 Richards。证明只有几页纸,使用的是经典方法,Richards 懊恼为什么他和其他人都错过了,同时也感到了解脱。他甚至帮助将论文重新用 LaTeX 排版,使其显得更专业。然而在论文发表之后,至今仍然有很多知名统计学家没听说过 GCI 猜想被证明了。原因之一是论文发表在不知名的印度期刊《Far East Journal of Theoretical Statistics》,而 Royen 本人还是这个期刊的编辑。于是证明遭到了怀疑和忽视。在一个非常容易交流的时代
它却因为缺乏交流而被忽视
。
0 notes
Text
为什么机器学习行业的发展离不开 “开源”
2016 年底,Google DeepMind 开源了它们的机器学习平台 — DeepMind Lab。尽管像霍金教授这样的专家曾就人工智能技术发出过警告,谷歌仍决定向其他开发人员开源其软件,这也是它们进一步发展机器学习能力的一部分。他们不是唯一一家这样做的科技公司,Facebook ��年开源了其深度学习的软件,Elon Musk 的非营利组织 OpenAI 也发布了 Universe,这是一个可用于训练 AI 系统的开放软件平台。所以,为什么谷歌、OpenAI,以及其他的公司或机构都选择开源了它们的平台,这将会对机器学习的采用产生怎样的影响?
为什么开源机器学习?
上面所提到的例子给了我们美好的愿景,其实如果仔细观察,会留意到机器学习一直是开源的,而且开放的研发是机器学习有如今这样关注度的根本原因。
通过向公众提供自己学习平台,Google 已经验证了其 AI 研究的意识越来越高。这样做其实有很多优点,例如可为 Alphabet 发掘到新的人才和有能力的创业公司。同时,开发者能访问 DeepMind Lab 将有助于解决他们研究机器学习的一个关键问题 —— 缺乏训练环境。OpenAI 为 AI 推出了一个新的虚拟学校,它使用游戏和网站来训练 AI 系统。
目前非常需要向公众提供机器学习平台这样的举动。
5 个开源机器学习项目的优势
重现科学的结果和公平的比较算法:在机器学习中,经常使用数值模拟来提供实验验证和方法比较。这种方法之间的比较是基于严格的理论分析的。开源工具和技术提供了一个机会,可以使用公开的源代码彻底地进行研究,而不依赖于提供方。
快速查找和修复 bug:当你使用开源软件执行机器学习项目时,易于检测和解决软件中的 bug。
以低成本、重用的方法加快科学研究的发展:众所周知,科学的进步总是以现有的方法和发现为基础,机器学习领域也不例外。机器学习中开源技术的可用性可很好地将大量现有资源投入研究和项目。
长期的可用性和支持:无论是个人研究者、开发者,还是数据科学家,开源可能都可以作为一种媒介,以确保每个人都可以在改变工作后使用他/她的研究或发现。因此,通过在开源许可证下发布代码可增加获得长期支持的机会。
各行业更快地采用机器学习技术:开源软件有显著的典范,它支持着创建数十亿美元的机器学习公司和行业。研究人员和开发者采用机器学习的主要原因是有免费提供高质量的开源实现。
加快开源机器学习的采用曲线
开源机器学习的进步将使���人工智能的采用曲线更加陡峭,从而促使开发者和创业公司努力使 AI 更智能。软件平台的可用性正在改变企业开发 AI 的方式,促使他们跟随 Google,Facebook 和 OpenAI 的脚步进行更透彻的研究。
开放机器学习平台的转变是确保 AI 可为每个人所用而不是只被掌握在少数技术巨头手中的重要阶段。
个人认为,科技巨头发布开源机器学习项目有三个原因:
雇佣已经与开源社区接触并通过开源项目建立了对机器学习的认识的工程师
控制一个机器学习平台,使它们为自己更广泛的 SDK 或云平台策略更好地工作
发展整个市场,因为他们的市场份额已经达到了饱和点
当一家创业公司发布一个开源项目时,它会引起注意,其中一些会被转化为付费客户和招聘。根据创业公司自己的定义,他们是尝试在特定市场上立足,而不是扩大现有市场。开源是无摩擦的,为另一个用户提供服务并使组织能够解决实际问题不会花费任何东西,从而使代码具有更大影响。
开源打破了建立专利技术的公司的限制。其中一个连锁效应可能是关注价值所在的转变,随着整个 AI 技术的商业化,关注点已从核心机器学习技术转向构建最佳模型,这需要大量的数据和领域专家来创建和训练模型。对于这点,具有网络影响力的大型企业具有天然优势。
开源机器学习中的最佳框架
现在有大量的开源机器学习框架,使机器学习工程师能够:
构建、实施和维护机器学习系统
生成新项目
创建新的有影响力的机器学习系统
一些重要的框架包括:
Apache Singa 是一个通用、分布式、深度学习的平台,用于在大型数据集上训练大型深度学习模型。它被设计有基于层次抽象的本能编程模型。支持各种流行的深度学习模型,包括卷积神经网络(CNN),受限玻尔兹曼机(RBM),以及循环神经网络(RNN)等能量模型。为用户提供了许多内置图层。
Shogun 是历史最悠久,也是最受尊敬的机器学习库之一。Shogun 于 1999 年创建,采用 C++ 编写,但不只限于在 C++ 中使用。感谢 SWIG 库,Shogun 可用于以下编程语言和环境:
Java
Python
C#
Ruby
R
Lua
Octave
Matlab
Shogun 旨在面向广泛的特性类型和学习环境进行统一的大规模学习,如分类、回归、降维、聚类等。它包含了几项独有的最先进的算法,如丰富的高效 SVM 实现,多内核学习,内核假设检验,以及 Krylov 方法等。
TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。TensorFlow 使用数据流图进行数值计算,通过节点(Nodes)和线(edges)的有向图来阐述数学计算。节点在图中表示数学操作,也可以表示数据输入(feed in)的起点/输出(push out)的终点,或者是读取/写入持久变量(persistent variable)的终点。图中的线则表示在节点间相互联系的多维数据数组,这些数据 “线” 可以输运 “size 可动态调整” 的多维数据数组,即 “张量”(tensor)
Scikit-Learn 通过构建在数个现有的 Python 包(NumPy,SciPy 和 matplotlib)之上,用于数学和科学工作,充分利用了 Python 的广度。生成的库可以用于交互式 “工作台” 应用程序,也可以嵌入到其他软件中并重用。该套件在 BSD 许可证之下发布,因此它完全是开源和可重用的。Scikit-learn 包括许多用于标准机器学习任务(如聚类,分类,回归等)的工具。由于 scikit-learn 是由一大群开发者和机器学习专家开发的,所以新技术有希望会很快被引入。
MLlib (Spark) 是 Apache Spark 的机器学习库。其目标是使实用的机器学习具有更好的可扩展性和易于使用。它由常见的学习算法和实用程序组成,包括分类、回归、聚类、协同过滤、降维,以及较底层的优化原语和高层的管道 API。Spark MLlib 被认为是在 Spark Core 之上的分布式机器学习框架,主要由于其分布式的基于内存的 Spark 架构,几乎是 Apache Mahout 使用的基于磁盘的实现的九倍。
Amazon Machine Learning 是一项使任何技能水平的开发者都能轻松使用机器学习技术的服务。Amazon Machine Learning 提供了可视化工具和向导,指导你完成创建机器学习(ML)模型的过程,而无需学习复杂的 ML 算法和技术。它连接到存储在 Amazon S3,Redshift 或 RDS 中的数据,可以对所述的数据运行二进制分类,多类分类或回归,以创建一个模型。
Apache Mahout 是 Apache 软件基金会的一个自由开源项目。目标是为协作过滤、聚类和分类等多个领域开发免费的分布式或可扩展的机器学习算法。Mahout 为各种数学运算提供了 Java 库和 Java 集合。Apache Mahout 是使用 MapReduce 范例在 Apache Hadoop 之上实现的。如果大数据存储在 Hadoop 分布式文件系统(HDFS)中,Mahout 提供的数据科学工具,可以在这些大数据集中自动找到有意义的模式,从而将这些大数据快速轻松地转化为 “大信息”。
最后要说的
机器学习确实可以在开源工具的帮助下解决真正的科学技术问题。如果机器学习是为了解决真正的科学技术问题,社区需要建立在彼此的开源软件工具之上。我们认为,机器学习开源软件有一个紧急需求,它将满足多个角色,其中包括:
更好的方法来重��结果
为质量软件实施提供学术认可的机制
通过站在其他人的肩膀(不一定是技术巨头)上以加速研究过程
为什么机器学习行业的发展离不开 “开源”,首发于文章 - 伯乐在线。
1 note
·
View note
Text
Etsy 在试验了一些 SEO 小技巧后得出的结论
1,网页标题越短越好,搜索关键词匹配的百分比越高越好;2,Meta description尽量长;3,网页里要使用 H1.
最近我们成功地做了个标题标签的对比测试,试验(标题标签能否)提升搜索引擎优化(SEO),结果和方法分享在 Code as Craft 中的一篇文章 。在这篇文章中,我们想分享 SEO 测试中学到的更多知识。我们决定在之前一系列成功的 SEO 深入试验(基础上)加倍下注,其中包括这些变化:
Title 标签
Meta 描述
H1
我们得出了 3 个惊人结论:
缩短标题标签,能改善访问量(和其他重要指标);
Meta 描述对自然搜索流量有显著影响;
H1 对自然搜索流量也有明显影响。
方法笔记
SEO 对比测试的设置和方法理论的详细信息,请参阅上述 Code as Craft 的引用文章。
在这个测试中,我们用了 6 个变量和 2 个对照组。对照组实验前后保持一致。
为了得到影响访问量变化的准确可能原因,我们最常用 Google 的 CausalImpact 安装包 来实施 Causal Impact 建模,实现测试组数据和 控制组数据的标准化。在某些情况下,需要使用 双重差分法 ,因为在处理强烈季节性波动时,此方法预估作用大小更可信。
这个测试也受强季节性、假期的其他事件���体育赛事及美国选举的影响。在最后的试验分析中,根据这些影响来调整统计建模,确保准确衡量测试版本的因果效应。
学习经验
收获 1 :短标题标签取胜
这个试验的结果和我们之前的 SEO 标题标签试验的结论是一致的,较短的标题标签,带来的访问量更多。目前我们已经在多重包含很多不同变量的独立试验中,验证了较短标题标签更利于优化的假设,现在确信对于 Etsy ,较短的标题标签更利于优化(由自然流量衡量)。我们假设会通过一些不同的因果机制产生这种影响:
1、低 Levenshtein 距离 和/或高比例匹配目标搜索查询,更受 Google 搜索算法青睐,因而 Etsy 在 Google 搜索结果中排名有所提升。根据 Wikipedia :“两个单词的 Levenshtein 距离是一个单词变成另外一个单词要求的最少单个字符编辑数量(即插入、删除或替换)。”
2、只包含目标搜索关键词的较短标题标签,对搜索用户而言,显得更相关/诱人。
收获2:Meta描述问题
我们发现在网页上改变 meta description 能导致访问量明显变化。看起来(文字)较长的描述性 meta description 有利于优化,相反,(文字)较短的简洁 meta description 表现较差。我们假定较长的 meta description 通过 2 种可能的因果机制表现更出色:
1、较长的 meta description 在搜索结果页面占更多位置,提高了点击率。
2、较长的 meta description 看起来更权威或内容更丰富,提高了点击率。
收获3:H1事宜
我们发现一个 H1 的变化能给自然搜索流量带来明显影响。但是,网页上 H1 部分的变化似乎和标题标签的变化以难以预料的方式相互影响。例如,在这个试验中,一个标题标签的某个变量变化,提升了访问量。但即使一个 H1 自己以不同变量改变导致访问量轻微增加,当标题标签的变化和一个 H1 标签的变化结合起来时,标题标签的正面效应(也)减弱了。这强调了 在SEO 测试之前, Etsy 页面即使看似微小改动的重要性。
对突发事件的解释说明
唐纳德·特朗普效应
我们不得不(在一些其他事件中)控制的例子事件,那就是“特朗普效应”。2016年11月9日和10日,我们在一个测试组中观察到一次数据的剧烈偏离。经过调查,发现偏离是由于美国总统选举的第二天,日常访问相关“唐纳德·特朗普”的页面大量增加(+2000% 到 +5180%)所致。尽管这些页面的流量剧增很短暂,只持续了几天,不过他们确实有过度倾斜的可能或减弱试验结果的统计意义。所以,在进行因果影响分析的试验时,这些页面被删除和控制住了。
结论
我们的 SEO 试验说明了网页改动前运行 SEO 测试的重要性,会继续给大家提供意外发现的结果。然而,需要注意的是,我们试验的结果只是对 Etsy,不一定是适用于普遍网站的最佳实践。因此,我们鼓励大家通过严格的 SEO 测试,发现对网站(优化)效果最好的策略。
Etsy 在试验了一些 SEO 小技巧后得出的结论,首发于文章 - 伯乐在线。
0 notes
Text
为什么优秀的程序员喜欢命令行?
优秀的程序员
要给优秀的程序员下一个明确的定义无疑是一件非常困难的事情。擅长抽象思维、动手能力强、追求效率、喜欢自动化、愿意持续学习、对代码质量有很高的追求等等,这些维度都有其合理性,不过又都略显抽象和主观。
(图片来自:http://t.cn/R6I1yhJ)
我对于一个程序员是否优秀,也有自己的标准,那就是TA对命令行的熟悉/喜爱程度。这个特点可以很好的看出TA是否是一个优秀的(或者潜在优秀的)程序员。我周围就有很多非常牛的程序员,无一例外都非常擅长在命令行中工作。那什么叫熟悉命令行呢?简单来说,就是90%的日常工作内容可以在命令行完成。
当然,喜欢/习惯使用命令行可能只是表象,其背后包含的实质才是优秀的程序员之所以优秀的原因。
自动化
Perl语言的发明者Larry Wall有一句名言:
The three chief virtues of a programmer are: Laziness, Impatience and Hubris. – Larry Wall
懒惰(Laziness)这个特点位于程序员的三大美德之首:唯有懒惰才会驱动程序员尽可能的将日常工作自动化起来,解放自己的双手,节省自己的时间。相比较而言,不得不说,GUI应用天然就是为了让自动化变得困难的一种设计(此处并非贬义,GUI有着自己完全不同的目标群体)。
(图片来自:http://t.cn/R6IBgYV)
GUI更强调的是与人类的直接交互:通过视觉手段将信息以多层次的方式呈现,使用视觉元素进行指引,最后系统在后台进行实际的处理,并将最终结果以视觉手段展现出来。
这种更强调交互过程的设计初衷使得自动化变得非常困难。另一方面,由于GUI是为交互而设计的,它的响应就不能太快,至少要留给操作者反应时间(甚至有些用户操作需要人为的加入一些延迟,以提升用户体验)。
程序员的日常工作
程序员除了写代码之外,还有很多事情要做,比如自动化测试、基础设施的配置和管理、持续集成/持续发布环境,甚至有些团队还需要做一些与运维相关的事情(线上问题监控,环境监控等)。
开发/测试
基础设施管理
持续集成/持续发布
运维(监控)工作
娱乐
而这一系列的工作背后,都隐含了一个自动化的需求。在做上述工作时,优秀的程序员会努力将其自动化,如果有工具就使用工具;如果没有,就开发一个新的工具。这种努力让一切都尽可能自动化起来的哲学起源于UNIX世界。
而UNIX哲学的实际体现则是通过命令行来完成的。
Where there is a shell, there is a way.
UNIX编程哲学
关于UNIX哲学,其实坊间有多个版本,这里有一个比较详细的清单。虽然有不同的版本,但是有很多一致的地方:
小即是美
让程序只做好一件事
尽可能早地创建原型(然后逐步演进)
数据应该保存为文本文件
避免使用可定制性低下的用户界面
审视这些条目,我们会发现它们事实上促成了自动化一切的可能性。这里列举一些小的例子,我们来看看命令行工具是如何通过应用这些哲学来简化工作、提高效率的。一旦你熟练掌握这些技能,就再也无法离开它,也再也忍受不了低效而复杂的各种GUI工具了。
命令行如何提升效率
一个高阶计算器
在我的编程生涯早期,读过的最为振奋的一本书是《UNIX编程环境》,和其他基本UNIX世界的大部头比起来,这本书其实还是比较小众的。我读大二的时候这本书已经出版了差不多22年(中文版也已经有7年了),有一些内容已经过时了,比如没有返回值的main函数、外置的参数列表等等,不过在学习到HOC(High Order Calculator)的全部开发过程时,我依然被深深的震撼到了。
简而言之,这个HOC语言的开发过程需要这样几个组件:
词法分析器lex
语法分析器yacc
标准数学库stdlib
另外还有一些自定义的函数等,最后通过make连接在一起。我跟着书上的讲解,对着书把所有代码都敲了一遍。所有的操作都是在一���很老的IBM的ThinkPad T20上完成的,而且全部都在命令行中进行(当然,还在命令行里听着歌)。
这也是我第一次彻底被UNIX的哲学所折服的体验:
每个工具只做且做好一件事
工具可以协作起来
一切面向文本
下面是书中的Makefile脚本,通过简单的配置,就将一些各司其职的小工具协作起来,完成一个编程语言程序的预编译、编译、链接、二进制生成的动作。
YFLAGS = -d OBJS = hoc.o code.o init.o math.o symbol.o hoc5: $(OBJS) cc $(OBJS) -lm -o hoc5 hoc.o code.o init.o symbol.o: hoc.h code.o init.o symbol.o: x.tab.h x.tab.h: y.tab.h -cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h pr: hoc.y hoc.h code.c init.c math.c symbol.c @pr $? @ touch pr clean: rm -f $(OBJS) [xy].tab.[ch]
虽然现在来看,这本书的很多内容已经过期(特别是离它第一次出版已经过去了近30年),有兴趣的朋友可以读一读。这里有一个Lex/Yacc的小例子的小例子,有兴趣的朋友可以看看。
当然,如果你使用现在最先进的IDE(典型的GUI工具),其背后做的事情也是同样的原理:生成一个Makefile,然后在幕后调用它。
基础设施自动化
开发过程中,工程师还需要关注的一个问题是:软件运行的环境。我在学生时代刚开始学习Linux的时候,会在Windows机器上装一个虚拟机软件VMWare,然后在VMWare中安装一个Redhat Linux 9。
(图片来自:http://t.cn/R6IBSAu)
这样当我不小心把Linux玩坏了之后,只需要重装一下就行了,不影响我的其他数据(比如课程作业、文档之类)。不过每次重装也挺麻烦,需要找到iso镜像文件,再挂载到本地的虚拟光驱上,然后再用VMWare来安装。
而且这些动作都是在GUI里完成的,每次都要做很多重复的事情:找镜像文件,使用虚拟光驱软件挂载,启动VMWare,安装Linux,配置个人偏好,配置用户名/密码等等。熟练之后,我可以在30 - 60分钟内安装和配置好一个新的环境。
Vagrant
后来我就发现了Vagrant,它支持开发者通过配置的方式将机器描述出来,然后通过命令行的方式来安装并启动,比如下面这个配置:
VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "precise64" config.vm.network "private_network", :ip => "192.168.2.100" end
它定义了一个虚拟机,使用Ubuntu Precise 64的镜像,然后为其配置一个网络地址192.168.2.100,定义好之后,我只需要执行:
$ vagrant up
我的机器就可以在几分钟内装好,因为这个动作是命令行里完成的,我可以在持续集成环境里做同样的事情 – 只需要一条命令。定义好的这个文件可以在团队内共享,可以放入版本管理,团队里的任何一个成员都可以在几分钟内得到一个和我一样的环境。
Ansible
一般,对于一个软件项目而言,一个全新的操作系统基本上没有任何用处。为了让应用跑起来,我们还需要很多东西。比如Web服务器、Java环境、cgi路径等,除了安装一些软件之外,还有大量的配置工作要做,比如apache httpd服务器的文档根路径,JAVA_HOME环境变量等等。
(图片来自:http://t.cn/R6IBZKm)
这些工作做好了,一个环境才算就绪。我记得在上一个项目上,不小心把测试环境的Tomcat目录给删除了,结果害的另外一位同事花了三四个小时才把环境恢复回来(包括重新安装Tomcat,配置一些JAVA_OPTS,应用的部署等)。
不过好在我们有很多工具可以帮助开发者完成环境的自动化准备,比如:Chef、 Puppet、Ansible。只需要一些简单的配置,然后结合一个命令行应用,整个过程就可以自动化起来了:
- name: setup custom repo apt: pkg=python-pycurl state=present - name: enable carbon copy: dest=/etc/default/graphite-carbon content='CARBON_CACHE_ENABLED=true' - name: install graphite and deps apt: name= state=present with_items: packages - name: install graphite and deps pip: name= state=present with_items: python_packages - name: setup apache copy: src=apache2-graphite.conf dest=/etc/apache2/sites-available/default notify: restart apache - name: configure wsgi file: path=/etc/apache2/wsgi state=directory
上边的配置描述了安装graphite-carbon、配置apahce等很多手工的劳动,开发者现在只需要执行:
$ ansible
就可以将整个过程自动化起来。现在如果我不小心把Tomcat删了,只需要几分钟就可以重新配置一个全新的,当然整个过程还是自动的。这在GUI下完全无法想象,特别是在有如此多的定制内容的场景下。
持续集成/持续发布
日常开发任务中,除了实际的编码和环境配置之外,另一大部分内容就是持续集成/持续发布了。借助于命令行,这个动作也可以非常高效和自动化。
Jenkins
持续集成/持续发布已经是很多企业IT的基本配置了。各个团队通过持续集成环境来编译代码、静态检查、执行单元测试、端到端测试、生成报告、打包、部署到测试环境等等。
比如在Jenkins环境中,在最前的版本中,要配置一个构建任务需要很多的GUI操作,不过在新版本中,大部分操作都已经可以写成脚本。
这样的方式,使得自动化变成了可能,要复制一个已有的pipline,或者要修改一些配置、命令、变量等等,再也不需要用鼠标点来点去了。而且这些代码可以纳入项目代码库中,和其他代码一起被管理、维护,变更历史也更容易追踪和回滚(在GUI上,特别是基于Web的,回滚操作基本上属于不可能)。
node { def mvnHome stage('Preparation') { // for display purposes git 'http://ift.tt/2jAK0Df' mvnHome = tool 'M3' } stage('Build') { sh "'${mvnHome}/bin/mvn' -Dmaven.test.failure.ignore clean package" } stage('Results') { junit '*/target/surefire-reports/TEST-.xml' archive 'target/*.jar' } }
上面这段groovy脚本定义了三个阶段,每个阶段中分别有自己的命令,这种以代码来控制的方式显然比GUI编辑的方式更加高效,自动化也变成了可能。
运维工作
自动化监控
Graphite是一个功能强大的监控工具,不过其背后的理念倒是很简单:
存储基于时间线的数据
将数据渲染成图,并定期刷新
用户只需要将数据按照一定格式定期发送给Graphite,剩下的事情就交给Graphite了,比如它可以消费这样的数据:
instance.prod.cpu.load 40 1484638635 instance.prod.cpu.load 35 1484638754 instance.prod.cpu.load 23 1484638812
第一个字段表示数据的名称,比如此处instance.prod.cpu.load表示prod实例的CPU负载,第二个字段表示数据的值,最后一个字段表示时间戳。
这样,Graphite就会将所有同一名称下的值按照时间顺序画成图。
(图片来自:http://t.cn/R6IxKYL)
默认地,Graphite会监听一个网络端口,用户通过网络将信息发送给这个端口,然后Graphite会将信息持久化起来,然后定期刷新。简而言之,只需要一条命令就可以做到发送数据:
echo "instance.prod.cpu.load 23 date +%s" | nc -q0 graphite.server 2003
date +%s会生成当前时间戳,然后通过echo命令将其拼成一个完整的字符串,比如:
instance.prod.cpu.load 23 1484638812
然后通过管道|将这个字符串通过网络发送给graphite.server这台机器的2003端口。这样数据就被记录在graphite.server上了。
定时任务
如果我们要自动的将数据每隔几秒就发送给graphite.server,只需要改造一下这行命令:
获取当前CPU的load
获取当前时间戳
拼成一个字符串
发送给graphite.server的2003端口
每隔5分钟做重复一下1-4
获取CPU的load在大多数系统中都很容易:
ps -A -o %cpu
这里的参数:
-A表示统计所有当前进程
-o %cpu表示仅显示%cpu列的数值
这样可以得到每个进程占用CPU负载的数字:
%CPU 12.0 8.2 1.2 ...
下一步是将这些数字加起来。通过awk命令,可以很容易做到这一点:
$ awk '{s+=$1} END {print s}'
比如要计算1 2 3的和:
$ echo "1\n2\n3" | awk '{s+=$1} END {print s}' 6
通过管道可以讲两者连起来:
$ ps -A -o %cpu | awk '{s+=$1} END {print s}'
我们测试一下效果:
$ ps -A -o %cpu | awk '{s+=$1} END {print s}' 28.6
看来还不错,有个这个脚本,通过crontab来定期调用即可:
#!/bin/bash SERVER=graphite.server PORT=2003 LOAD=ps -A -o %cpu | awk '{s+=$1} END {print s}' echo "instance.prod.cpu.load ${LOAD} date +%s" | nc -q0 ${SERVER} ${PORT}
当然,如果使用Grafana等强调UI的工具,可以很容易的做的更加酷炫:
(图片来自:http://t.cn/R6IxsFu)
想想用GUI应用如何做到这些工作。
娱乐
命令行的MP3播放器
最早的时候,有一个叫做mpg123的命令行工具,用来播放MP3文件。不过这个工具是商用的,于是就有人写了一个工具,叫mpg321,基本上是mpg123的开源克隆。不过后来mpg123自己也开源了,这是后话不提。
将我的所有mp3文件的路径保存成一个文件,相当于我的歌单:
$ ls /Users/jtqiu/Music/*.mp3 > favorites.list $ cat favorites.list ... /Users/jtqiu/Music/Rolling In The Deep-Adele.mp3 /Users/jtqiu/Music/Wavin' Flag-K'Naan.mp3 /Users/jtqiu/Music/蓝莲花-许巍.mp3 ...
然后我将这个歌单交给mpg321去在后台播放:
$ mpg321 -q --list favorites.list & [1] 10268
这样我就可以一边写代码一边听音乐,如果听烦了,只需要将这个后台任务切换到前台fg,然后就可以关掉了:
$ fg [1] + 10268 running mpg321 -q --list favorites.list
小结
综上,优秀的程序员借助命令行的特性,可以成���(有时候是跨越数量级的)提高工作效率,从而有更多的时间进行思考、学习新的技能,或者开发新的工具帮助某项工作的自动化。这也是优秀的程序员之所以优秀的原因。而面向手工的、原始的图形界面会拖慢这个过程,很多原本可以自动化起来的工作被淹没在“简单的GUI”之中。
(图片来自:http://ift.tt/2np23iq
最后补充一点,本文的关键在于强调优秀的程序员与命令行的关系,而不在GUI程序和命令行的优劣对比。GUI程序当然有其使用场景,比如做3D建模、GIS系统、设计师的创作、图文并茂的字处理软件、电影播放器、网页浏览器等等。
应该说,命令行和优秀的程序员之间更多是关联关系,而不是因果关系。在程序员日常的工作中,涉及到的更多的是一些需要命令行工具来做支持的场景。如果走极端,在不适合的场景中强行使用命令行,而置效率于不顾,则未免有点矫枉过正,南辕北辙了。
为什么优秀的程序员喜欢命令行?,首发于文章 - 伯乐在线。
0 notes
Text
卷积神经网络初探
前言
目前为止我已经完整地学完了三个机器学习教程:包括“Stanford CS229”,”Machine Learning on Coursrea” 和 “Stanford UFLDL”,卷积神经网络是其中最抽象的概念。
维基百科对卷积的数学定义为:
由于卷积常用与信号处理,很多人基于“输入->系统->响应”这一模型来解释卷积的物理意义,这里转载一个非常通俗的版本:
比如说你的老板命令你干活,你却到楼下打台球去了,后来被老板发现,他非常气愤,扇了你一巴掌(注意,这就是输入信号,脉冲),于是你的脸上会渐渐地鼓起来一个包,你的脸就是一个系统,而鼓起来的包就是你的脸对巴掌的响应,好,这样就和信号系统建立起来意义对应的联系。下面还需要一些假设来保证论证的严谨:假定你的脸是线性时不变系统,也就是说,无论什么时候老板打你一巴掌,打在你脸的同一位置,你的脸上总是会在相同的时间间隔内鼓起来一个相同高度的包,并且假定以鼓起来的包的大小作为系统输出。好了,下面可以进入核心内容——卷积了!
如果你每天都到地下去打台球,那么老板每天都要扇你一巴掌,不过当老板打你一巴掌后,你5分钟就消肿了,所以时间长了,你甚至就适应这种生活了……如果有一天,老板忍无可忍,以0.5秒的间隔开始不间断的扇你的过程,这样问题就来了,第一次扇你鼓起来的包还没消肿,第二个巴掌就来了,你脸上的包就可能鼓起来两倍高,老板不断扇你,脉冲不断作用在你脸上,效果不断叠加了,这样这些效果就可以求和了,结果就是你脸上的包的高度随时间变化的一个函数了(注意理解);如果老板再狠一点,频率越来越高,以至于你都辨别不清时间间隔了,那么,求和就变成积分了。可以这样理解,在这个过程中的某一固定的时刻,你的脸上的包的鼓起程度和什么有关呢?和之前每次打你都有关!但是各次的贡献是不一样的,越早打的巴掌,贡献越小,所以这就是说,某一时刻的输出是之前很多次输入乘以各自的衰减系数之后的叠加而形成某一点的输出,然后再把不同时刻的输出点放在一起,形成一个函数,这就是卷积,卷积之后的函数就是你脸上的包的大小随时间变化的函数。本来你的包几分钟就可以消肿,可是如果连续打,几个小时也消不了肿了,这难道不是一种平滑过程么?反映到剑桥大学的公式上,f(a)就是第a个巴掌,g(x-a)就是第a个巴掌在x时刻的作用程度,乘起来再叠加就ok了,大家说是不是这个道理呢?我想这个例子已经非常形象了,你对卷积有了更加具体深刻的了解了吗?
这是一些尝试解释卷积的文章:
http://ift.tt/1rSdVcX
http://ift.tt/2o85Twt
http://ift.tt/1ZGvVDh
而在图像处理中通常使用离散形式的卷积,在下一节中介绍。
卷积特征提取(convolution)
卷积特征提取的过程
假设有一个稀疏自编码器 SAE,训练使用的是 3×3 的小图。将 SAE 用作深度网络的隐藏层时,它依然只接受 3×3 的数据作为输入,且假设这个隐藏层有 k 个单元(每个单元也被称为一个卷积核 – Convolution Kernel,由对应的权值向量 W 和 b 来体现)。
每个隐藏单元的输入是用自己的权值向量 W 与 3×3 的小图做内积,再与截距项相加得到的:
假如深度网络的输入是 5×5 的大图,SAE 要从中提取特征,必须将 5×5 的大图分解成若干 3×3 的小图并分别提取它们的特征。分解方法就是:从大图的 (1, 1)、(1, 2)、(1, 3)、… 、(3, 3)等 9 个点开始分别作为小图的左上角起点,依次截取 9 张带有重合区域的小图,然后分别提取这 9 张小图的特征:
所以,每个隐藏单元将有 9 个输入,不同于之前的 1 个。然后将所有输入分别导入激活函数计算相应的输出,卷积特征提取的工作就完成了。
对于本例,隐藏层所提取的特征共有 9×k 个;更一般化地,如果大图尺寸是 r×c,小图尺寸是 a×b,那么所提取特征的个数为:
卷积特征提取的原理
卷积特征提取利用了自然图像的统计平稳性(Stationary):
自然图像有其固有特性,也就是说,图像的一部分的统计特性与其他部分是一样的。这也意味着我们在这一部分学习的特征也能用在另一部分上,所以对于这个图像上的所有位置,我们都能使用同样的学习特征。
池化(Pooling)
池化过程
在完成卷积特征提取之后,对于每一个隐藏单元,它都提取到 (r-a+1)×(c-b+1)个特征,把它看做一个矩阵,并在这个矩阵上划分出几个不重合的区域,然后在每个区域上计算该区域内特征的均值或最大值,然后用这些均值或最大值参与后续的训练,这个过程就是【池化】。
池化的优点
显著减少了参数数量
池化单元具有平移不变性 (translation invariant)有一个 12×12 的 feature map (隐藏层的一个单元提取到的卷积特征矩阵),池化区域的大小为 6×6,那么池化后,feature map 的维度变为 2×2。
假设原 feature map 中灰色元素的值为 1,白色元素的值为 0。如果采用 max pooling,那么池化后左上角窗口的值为 1。如果将图像向右平移一个像素:
池化后左上角窗口的值还是 1。如果将图像缩小:
池化后左上角窗口的值依然是 1。
通常我们认为图像经过有限的平移、缩放、旋转,不���改变其识别结果,这就要求经过平移、缩放、旋转的图片所提取的特征与原图所提取的特征相同或相似,因此分类器才能把它们识别成同一类。
几种池化方式
比较主流的池化方式有如下几种:
一般池化(General Pooling): max pooling 和 average pooling现在已经知道了 max pooling 与 average pooling 的几何意义,还有一个问题需要思考:它们分别适用于那些场合?在不同的场合下,它们的表现有什么不一样?为什么不一样?
网络上有人这样区分 max pooling 和 average pooling:
“average对背景保留更好,max对纹理提取更好”。
限于篇幅以及我的理解还不深,就不展开讨论了,如果以后需要,我会深入研究一下。
重叠池化(Overlapping Pooling)重叠池化的相邻池化窗口之间会有重叠区域。
空间金字塔池化(Spatial Pyramid Pooling)空间金字塔池化拓展了卷积神经网络的实用性,使它能够以任意尺寸的图片作为输入。
下面列出一些研究池化的论文:http://ift.tt/1OUSPTN
http://ift.tt/1OUSPTS
http://ift.tt/1Ms6MpL
http://ift.tt/2o8aPBj
汇总
有 m 张彩色自然图片拿来训练一个神经网络,使它能够对图片中的物体做分类。训练过程可以大致分为以下几步:
从图片库中随机裁剪出相同尺寸的小图若干张,用来训练一个稀疏自编码器 C1;
以 C1 作为第一个卷积层,从原图中做卷积特征提取;
在 C1 下游添加一个池化层 S1,对 C1 所提取的特征做池化计算;
如果需要提取更加抽象的特征,在 S1 之后添加卷积层 C2,C2 是一个使用 S1 的数据进行训练的稀疏自编码器;
在 C2 下游添加一个池化层 S2,如果需要提取进一步抽象的特征,重复添加卷积层与池化层即可;
以最后一个池化层的输出作为数据训练分类器。
课后作业(Convolution and Pooling)
In this exercise you will use the features you learned on 8×8 patches sampled from images from the STL-10 dataset in the earlier exercise on linear decoders for classifying images from a reduced STL-10 dataset applying convolution and pooling. The reduced STL-10 dataset comprises 64×64 images from 4 classes (airplane, car, cat, dog).
这次作业依赖上一次“linear decoders”作业的代码,使用的数据是 STL-10 的一个子集,用来识别四种图像:飞机、汽车、猫和狗。
代码地址,由于 GIthub 有文件大小限制,所以这次没有上传数据文件。
Pooling 的代码比较简单,所以这里把计算卷积的代码详细注释后贴出来:
cnnConvolve.m
运行结果(识别的正确率):
使用 average pooling:
使用 max pooling:
卷积神经网络初探,首发于文章 - 伯乐在线。
1 note
·
View note
Text
如何建设高可用系统
面试的时候经常会问一个问题,如何建设高可用系统?大家可以一起探讨下。 “高可用性”(High Availabi […]
0 notes
Text
[時間技客-11] 莫法特休息法,面對高壓力工作的積極休息技巧
昨天我在「簡單把生活變成任務,就能贏得意想不到的成就感!」一文下方,收到大陸讀者的留言回饋:「我刚刚才仔细看看站长在2015年的帖子,才知道自己为什么“想放松”,然后又后悔的原因:时间都浪费了。」 我跟大家一樣,也會遇到下面這些問題點:想讀書但讀不下書,只要學習一下就覺得很累(但其實昨天晚上明明有充分睡眠);要動腦或枯躁的工作做不下去,做一下就覺得卡關或是想休息(但其實身體並不勞累);假日想說睡懶覺來補充上班日的疲勞,但疲勞卻幾乎沒有恢復,愈睡愈累,而且壓力持續累積。(可參考:善用週末假日「克服工作壓力」的四個有效方法) 「愈休息愈累,愈休息壓力愈大,一開始工作學習就覺得累」,這到底是什麼問題?有沒有解決的辦法呢?或許其中一個可能原因是:我想錯了休息方法。
為什麼愈休息愈累?
說到「休息」,我們通常想到的是睡覺、不工作、去玩、放鬆,這些是休息沒錯,但休息的方法其實不只這些,而我們因為缺乏對休息方法的想像力,常常沒有對症下藥的「用適合的方法休息」,才導致前述的問題。 休息的真正目的是什麼?無非是希望透過休息,讓下一個階段的某種行動更有動力、體力、精神力,也就是說「休息」不是只有體力回復而已:
很多時候休息要復原的其實是「動力」與「精神力」,而就像 RPG 角色需要補充動力與精神力時,就需要喝下不同的「休息藥水」,只喝體力藥水是沒用的。
所以,當我明明昨晚有準時睡覺起床,照理說我的體力應該很足夠,但為什麼我一學習就打瞌睡?一工作就覺得累?一到放假就算拚命放鬆、拚命玩也總覺得休息不夠? 因為這時候我的體力其實是滿格的,我需要的是「補充動力和精神力」的休息藥水,我需要的不是靜態休息,而是「積極休息」。
莫法特休息法:
什麼是「積極休息」?今天介紹大家很有趣也很有效的方法:「莫法特休息法」。 「莫法特休息法」背後有一個流傳很廣的故事:「據說《新約聖經》的翻譯者詹姆斯·莫法特在工作時會準備三張書桌,第一張書桌上擺著他的聖經譯稿,第二張書桌上擺著他正在寫的論文原稿,第三張書桌上則是他正在撰寫的偵探小說。而莫法特的工作方法就是翻譯累了,就換到第二張書桌寫論文,累了再換到第三張書桌寫小說。」 「莫法特休息法」的方法就是說,不一定要停止工作來休息,而可以「切換不同工作節奏」,尤其對腦力工作來說,切換不同的思考主題,其實對大腦是更好的「積極休息」,可以讓大腦因為思考不同主題而從前一個主題的壓力放鬆下來,但又能補回剛剛的精神力,甚至因為切換節奏而找到新的動力。
生理上我們需要休息,但精神上的休息,指的則是保持熱情、補充刺激。
所以我們之前的直接休息搞錯了什麼,導致我們學習累了(而且很快就累)跑去玩,但玩完回來面對學習一樣覺得累,為什麼?因為我們的熱情和刺激沒有回復。
進行高強度工作時,保持彈力的休息:
我自己就很喜歡實踐莫法特休息法,而且真的有用。 例如之前我在撰寫自己的專書《 Evernote 100個做筆記的好方法》,我在準備自己的「時間管理課程」,還同時處理我的正職工作任務時,我每一天在電腦玩物撰寫部落格文章的頻率並沒有降低。 有知道我的工作情況的朋友就很好奇問我, Esor 你怎麼可以在專案都快截止時,還是每天都可以寫文章?那時候我就說,因為寫部落格文章就是我的「休息」,聽到的朋友都覺得不可思議,寫文章怎麼會是休息? 但從上面莫法特休息法的例子我們就可以知道,當我壓力、腦力都集中在書籍撰寫時,我的動力與精神力急速消耗在書籍這件事情上,但這時候切換工作節奏到寫部落格文章,不同主題的思考與壓力會幫助我快速重新填滿精神值,���是再切換回書籍撰寫又能保持效率。 精神值跟體力不同,他需要的不是「不做事情」來慢慢回復,而是需要「做不一樣的事情」來保持「彈力」。 而且這個不一樣的事情不是放棄腦力思考,反而應該是繼續腦力思考,只是用不同方向、不同主題,去回復腦力的彈性。
面對難解的問題時,找到新刺激的休息:
另外還有一個故事,法國的啟蒙哲學家盧梭跟我們一樣,他也說自己只要用功的時間稍微長一點就會覺得疲倦(是不是感同身受?),而且盧梭說自己只要超過半個小時專注處理一個問題就開始覺得累,所以盧梭怎麼做呢?他就讓自己不斷的處理不同的問題,累了就換一個問題繼續思考,這讓他的大腦持續抱持輕鬆愉快,而事實上他研究的時間並沒有間斷。 這也是一種莫法特休息法,而且我常常運用在自己「讀書」這件事情上。(延伸閱讀:如何閱讀一本書?我的深讀、反芻、拆解三步驟) 有時候好不容易翻開一本書開始要閱讀了,讀了不到半小時就覺得累,因為卡在書中幾個問題一直苦思,這時候我就會先放下目前這個問題 ,跳到下一個章節繼續讀,於是有了「新章節的問題」來刺激我的大腦,我的大腦又開始恢復清醒了。 我也常常運用在我寫草稿上。(延伸閱讀:零碎時間最佳化:我如何堅持每天寫完一篇部落格文章?) 有時候我寫一篇草稿寫到卡住,開始覺得累,開始效率降低,我就換到另一篇草稿繼續寫,讓大腦重新恢復活力,而且這還常常會幫助我切換回原本的草稿時產生更多靈感。 也就是說,除了身體休息,大腦更需要「找到新刺激」的「積極休息」。
莫法特休息法如何實作:
前面說了我的經驗與莫法特休息法的方法,但具體實作上要怎麼才能做到呢? 我的經驗告訴我,首先我們必須擁有一個豐富的人生計劃,如果我的人生很單一,只有一種工作,那當然永遠逃不出這個工作的壓力,但實際上人生不需如此,人生應該有很多夢想、興趣、家庭、個人的目標可以實現,當然也有工作。 而當我們可以有更豐富的人生計劃時,莫法特休息法就能愈對我們起到作用,所以首先我建議大家可以根據我這篇文章的方法:「用心智圖法畫出一年目標與行動, 2017年從這個視覺技巧開始」,建立一個「多面向的人生規劃」。 然後可以在規劃每一天的行動清單時,注意到「多元性」,例如我的「135行動清單法則」中,就可以讓工作、家庭、個人興趣更平均安排在「3個必要任務」中,幫助我們在一天工作時可以透過切換而找到正確休息。 尤其對知識工作者、學習者來說,莫法特的積極休息法,相信會有所幫助。 延伸閱讀:
我的「不辦清單」:如何從不要做的犧牲,換回工作效率?
解除壓力與焦慮的大腦 SPA 筆記術,起碼每週做一次
我用來擺脫情緒枷鎖的十種方法
@轉貼本文時禁止修改,禁止商業使用,並且必須註明來自電腦玩物原創作者 esor huang(異塵行者),及附上原文連結與下方整個系列的文章連結。 @時間技客系列文章:
總論:幫你的時間管理方法健檢,如何挑選時間管理工具?
[時間技客-1] 做得到的每日待辦清單:1-3-5 專注法則
[時間技客-2]不想做就別做!感性自動聚焦時間管理法
[時間技客-3] 人生就是不斷的拖延,我如何用拖延對付拖延?
[時間技客-4] 完成這個線上測驗,幫你找出每天的時間黑洞
[時間技客-5] 改變上班打卡心態,開始學 SOHO 一樣精算工時
[時間技客-6] 用減法工作,每個人都能做到的減輕工作量秘訣
[時間技客-7] 重要緊急時間管理四象限在 Trello 活用與範本下載
[時間技客-8] 克服拖延不靠意志力,你需要這三個簡單可行技巧
[時間技客-9] 承諾日曆,幫你實現 2017 年願望的有效合約
[時間技客-10] 紅眼航班與優質時間
[時間技客-11] 莫法特休息法,面對高壓力工作的積極休息技巧
喜歡這篇文章嗎?歡迎追蹤我的Facebook、Twitter、Google+,獲得更多有趣、實用的數位科技消息,更歡迎直接透過社群聯繫我。
0 notes
Text
【Google 面試題庫大公開】求職者應徵 PM 職位,Google 人資佛心回覆應徵攻略
【Google 面試題庫大公開】求職者應徵 PM 職位,Google 人資佛心回覆應徵攻略
Posted on 2017/03/24
黃筱雯
想到 Google 當 PM 要具備什麼要的能力?
Business Insider 報導,一位求職者應徵了 Google 的 PM 職位,還在履歷審查階段時,就收到超佛心的 Google 人資寫的一封信,告訴他 Google 的 PM 需具備的能力。現在,就為你公開這封信的內容!
PM 工作概覽
Google 的 PM 工作就是要能夠做出對用戶友善也有商業價值的新產品及功能。PM 就是我們產品的經理,要領導各個團隊讓產品概念化,最後做出 Google 的下個好用產品。另外,Google 的 PM 也需要有我們「興奮」、「勇於挑戰」的企業精神。比起卡在一款已經存在的產品中,Google 的 PM 會更專注於開發新的產品概念與策略。
Google 的服務橫跨消費者、行動裝置、app、企業及公共基礎建設等等,所以我們的 PM 必須要是「通才」,能夠同時提供團隊不同意見。所以我們通常不僱用專才,而是尋找可以同時負責不同產品的人。
能力要求
以下是 Google PM 面試的五項要素:
產品設計:Google PM 要將使用者擺在第一位,必須能夠提供最好的使用者經驗。這就會需要對客戶有同理、對產品擁有熱情,而且能關注每個小細節。能以簡單幾個問題描繪出想法,並傳達給設計師。
分析能力:Google PM 要對數字有敏感度。能夠定義對的指標、並且能從 A/B 測試中讀懂成果並作出決定。也要不介意自己出手寫程式,從記錄中找出重要資訊。而且,要能從簡潔地從分析結果中闡述自己的想法。
融入企業文化:Google PM 要有做大夢的雄心壯志,他們要能夠領導、懂得提出歧見並完成任務。如果 Google PM 在別的地方工作,他們大概會是那間公司的 CEO。關於企業文化有幾個簡單的問題:
為什麼選擇 Google?
為什麼想當 PM?
技術能力:Google PM 要領導產品開發團隊,對於工程師來說,他們必須有一定可信任度跟影響力,因此也需要一些技術能力。在面試的最後一階段,工程師團隊的資深成員會來評估你的技術能力。所以要準備好一些 coding 相關問題。
策略能力:Google 的 PM 同時也要是好的商業領導人,他們要熟悉商業議題。雖然不需要有商業經驗或正式的商業課程訓練,但是 Google 希望 PM 具有商業敏感度及快速決斷的能力。以下是幾個相關面試題:
如果你是 Google CEO,你會擔心微軟的競爭嗎?
你認為 Google 需要推出 StubHub(編按:門票交易平台)的競爭產品嗎?
不用再準備「腦筋急轉彎」題
之前常有所謂 Google 面試題庫在網路上流傳,內容盡是一些相當刁鑽的問題。例如:「我擲了兩次骰子,第二次的數字比第一次大的機率是多少?」或「心算出 27×27 是多少��等。
最近 Google 人資部門發現,員工的工作表現與當初回答「腦筋急轉彎」的準確度實在沒什麼關聯性,所以最近 Google 面試幾乎不會再出現類似題目。
不過,還是要小心一些假設性問題。因為這些問題與工作內容是有關聯的,例如,你會如何設計一個可以從 USDA 取出數據並顯示在 Google nutrition 上?
如何準備
我推薦去讀些科技部落格,例如 Stratechery
產品設計:Google 面試官要衡量的是你的創造力,他們強調「偉大的點子」,所以要以有獨特、引人注目的點子吸引面試官興趣。在白板畫下你的線匡可以幫你釐清想法。你可以下載 Balsamiq 等工具來練習。別忘了多研究熱門網站跟行動裝置的設計。
技術能力:coding 的問題不像電話訪問那樣容易,但是如果你參加了線上面試,你就要準備編程相關的面試。技術面試官不會要求你的編程語法要很完美,但是要對技術概念有足夠的掌握度,才能與技術團隊溝通。我推薦去參加電腦科學的基本課程並且練習一些編程問題,這是我最推薦的資源:How to Ace the Software Engineering Interview。
分析能力: 準備一些數據預估問題,除此之外,也要精通產品相關數據及 A/B 測試,結果分析等。
策略能力: 要會用框架去建構策略,如果不熟悉思考框架的話,可以從「五力分析 」開始試試。
延伸閱讀
去 Google 工作真的沒那麼難!從醫生成功轉職 Google 工程師的大神親手撰寫攻略 好想去台灣 Google 工作!有這 11 個特質你才可能被錄取 【可惡羨慕】Google 公開軟體團隊工作日常,工程師每天都有 20%的自由活動時間
參考資料來源
Business Insider:Read the email a Google recruiter sent a job candidate to prepare him for the interview
(本文供合作夥伴轉載,首圖來源:Neon Tommy,CC licensed)
【TechOrange 徵才:社群編輯、程式設計】 如果你對數位行銷、Startup 趨勢、產業轉型、程式設計,以及新科技議題有興趣,不怕用與眾不同的面向,去衝撞一般思維,歡迎你加入 TO >> 詳細職缺訊息 意者請提供履歷自傳以及文字作品,寄至 [email protected] 來信主旨請註明:【應徵】TechOrange 職缺名稱:您的大名
點關鍵字看更多相關文章:
0 notes
Text
CSS 布局十八般武艺都在这里了
布局是CSS中一个重要部分,本文总结了CSS布局中的常用技巧,包括常用的水平居中、垂直居中方法,以及单列布局、多列布局的多种实现方式(包括传统的盒模型布局和比较新的flex布局实现),希望能给需要的小伙伴带来一些帮助。
The post CSS 布局十八般武艺都在这里了 appeared first on WEB前端 - 伯乐在线.
1 note
·
View note
Text
Ajax知识体系大梳理
Ajax 全称 Asynchronous JavaScript and XML, 即异步JS与XML. 它最早在IE5中被使用, 然后由Mozilla, Apple, Google推广开来. 典型的代表应用有 Outlook Web Access, 以及 GMail. 现代网页中几乎无ajax不欢. 前后端分离也正是建立在ajax异步通信的基础之上.
The post Ajax知识体系大梳理 appeared first on WEB前端 - 伯乐在线.
1 note
·
View note
Text
一周 10 小時,你也可以轉職 AI 工程師!fast.ai 的免費課程連 Google 都推
一周 10 小時,你也可以轉職 ai 工程師!fast.ai 的免費課程連 Google 都推
Posted on 2017/03/22
林子鈞
fast.ai 網站
深度學習一點都不難!會高中數學你就可以自己搞一個
最近有一個名為 fast.ai 的網站上線,他們提供了 20 小時的課程, 介紹如何簡單的建立屬於自己的基礎 AI 系統,而且完全免費。
他們的主張是:「不需要擁有研究生水準的數學,你也可以輕鬆建立屬於自己的 AI」。只要你跟著他們的課程一步一步自學,你就可以理解如何建立 GPU 服務器,並讓他在雲端不斷深化學習,並一直到創建最先進的,高度實用的計算機視覺,自然語言處理和推薦系統的模型。
他們課程的源頭是 南佛羅里達大學數據研究所 的第一期證書課程內容, 第二期預計會在 2017 年 2 月 27 日開始在該研究所授課,並在 2017 年 5 月上傳至這個網路平台 。
成立自學社群,矽谷大公司也來參一咖
這是他們的課程介面,左邊會有課程的影片,長度大概都落在 1 個半小時~2 小時之間。fast.ai 的學習建議是:「你應該計劃每週花費約 10 小時,為期 7 週來學習與練習這個課程。」
這個課程包含非常多的矽谷巨頭都參與了這項計畫,包含 Google、LinkIn、微軟、IBM 等等。
除了課程以外,他們還提供了非常大量的其他資源,其中包含可以針對每堂課提問的論壇,最多的討論串有 400 人以上在內,可以讓你針對每堂課提問,並會有人為你做出適當的解答,可以讓你的自學之路更為順遂。他們還建立了自己的維基百科,讓你在有問題的時候可以先上去查看。
fast.ai 論壇
fast.ai 維基百科
如果這兩項都不能解決你的學習問題的話,他們還開設了課程學員的 slack,不只可以讓你建立人脈,同時你也可以及時得到同學與教師的回饋。
畢業學長姐們的感想
程式後的下一個未來:人工智慧
我們可以發現過去矽谷巨頭,都非常專注地投入鼓勵更多人簡單的學習軟體,包含 facebook 提供資金與平台給有軟體創業 idea 的團隊,以及其他大公司開設的大量免費軟體學習課程與培訓。
現在這股風氣也燒到 AI 來了,這個免費深度學習教學的網站,可以讓未來有更多人懂得基礎的 GPU 伺服器該如何建立,也可以吸引更多人投入這個產業。或許能打造像今日程式語言一樣的榮景。
【TechOrange 徵才:社群編輯、程式設計】 如果你對數位行銷、Startup 趨勢、產業轉型、程式設計,以及新科技議題有興趣,不怕用與眾不同的面向,去衝撞一般思維,歡迎你加入 TO >> 詳細職缺訊息 意者請提供履歷自傳以及文字作品,寄至 [email protected] 來信主旨請註明:【應徵】TechOrange 職缺名稱:您的大名
點關鍵字看更多相關文章:
1 note
·
View note
Text
物理学家发现时钟越精确时间越模糊
物理学家结合两大物理学理论得出一个结论:时间不是普遍一致的,任何用来测量时间的时钟也会
模糊周围空间的时间流动
。维也纳大学和奥地利科学院的物理学家根据量子力学和广义相对论推断,增加同一空间的时钟测量精度也将增加时间扭曲。量子力学是描述微观宇宙的理论,其中一个著名的原理是海森堡的不确定原理,也就是粒子的速率和位置难以同时精确,位置精确���率就不精确反之亦然。研究人员认为时间也有同样的现象。他们认为更精确的测量时间将需要增加能量,从而导致计时工具周围的时间测量变得不精确。研究人员认为我们需要重新思考时间的性质。
0 notes
Text
【可惡羨慕】Google 公開軟體團隊工作日常,工程師每天都有 20%的自由活動時間
【我們為什麼挑選這篇文章】Google 是世上最成功的軟體公司之一,這篇文章完整的整理了 Google 的公司架構規劃與軟體計劃內容,或可給軟體領域的創業朋友提供非常好的榜樣。(責任編輯:林子鈞)
軟體發展
大部分的 Google 代碼都存在統一的原始程式碼庫中,可供 Google 內部所有工程師訪問。但是 Chrome 和 Android 則分別有單獨的代碼庫。
Google 的代碼庫,在 2015 年 1 月的統計中,共計 86T 資料,十幾億個檔,9 百萬個原始程式碼檔,其中包含了 20 億行代碼 。迄今為止共計 3500 萬次提交,每個工作日平均發生 4 萬次更新。
任何 Google 員工,都可以隨意的訪問所有代碼,並下載、編譯,可以在自己的環境下自行改寫,但 任何更改的提交,都需要通過代碼負責人的審批才可以 。
所有的開發都在資料庫的頭部進行。對代碼進行任何更改後,自動化系統將進行測試,並在 幾分鐘內通知開發者和代碼審查者,對更改的測試是否失敗 。
代碼庫中每個分支都有單獨的檔注明「代碼所有人」,只有代碼所有人才有權利審核提交的更改。 通常情況下,專案組的所有成員都是「代碼所有人」。
Google 使用分散式編譯系統,叫做 Blaze。Blaze 提供了標準的命令,用於編譯和測試庫中的所有代碼。Blaze 這種統一的編譯工具,讓 Google 公司的所有工程師都能隨時編譯和測試任何軟體,也都能跨專案工作。
程式師 需要撰寫「BUILD」檔,用來引導 Blaze 如何編譯軟體。在 Go 語言的代碼中,build 檔可以自動生成。
每個編譯步驟必須是「隔離」的,只依賴於聲明的輸入。為了實現編譯的分散式運行,必須強制要求正確輸入所有的依賴:只有聲明了的輸入才被發送到進行編譯的機器上去。
每個編譯步驟的結果是確定的。這樣保證了編譯系統可以緩存編譯結果。 軟體工程師 可以回退到老的版本號,並重新編譯,且得到完全一樣的二進位結果。
編譯結果緩存在雲端。包括中間結果,這樣當有別的編譯請求過來,系統直接應用緩存的結果。
增量的重新編譯非常快。編譯系統運行在記憶體中,當重新執行編譯任務時,它能夠分析檔上次編譯後發生的增量變化。
提交前檢查。Google 有專門的自動化工具,用來在發起代碼審查和準備提交更改到代碼庫時,進行一整套的標準檢查。
Google 開發了基於 Web 的代碼審查管理工具。程式師可以申請對代碼進行審查,審查者可以在流覽器上比較差異,並寫上評語。當寫代碼的人發起一次審查申請,則系統自動發郵件給審查者,並附上代碼查看頁的連結。
對原始程式碼的任何更改,必須經過最少一次審查。如果更改不是由「代碼所有人」做出,則還必須由所有人中的一位進行審查。
系統可以自動推薦合適的審查者。當然, 寫程式的人,可以自己選擇審查者。
Google 鼓勵工程師們,將每一次代碼更改控制在較小的規模上。 30-99 行的代碼更改,通常視為「中等」;300 行以上則標記為「大」; 1000-1999 行,則是「巨大」;
單元測試是必須的,在 Google 的開發中廣為採用。集成測試和回歸測試,也較為普及。Google 有一個自動化的工具,用來衡量測試覆蓋的範圍,這個結果也在代碼流覽器中可以查看。
部署前一定要做壓力測試。專案組要用表格或者圖示顯示關鍵參數,尤其是壓力之下的延遲和錯誤率。
Google 使用的 Bug 跟蹤工具叫 Buganizer。有的團隊,安排專人分配 Bug,有的團隊則在例行的會議中分配。
Google 內部有四大語言,一般都建議工程師在這四種裡挑選。四大語言是: C++,Java, Python, Go。不用多說, 減少語言數量,可以增加代碼複用,並提高內部協作。
每種語言都有代碼規範,保證風格統一。公司範圍內,還有針對“代碼可讀性”的培訓,由經驗豐富的老司機,對新人進行培訓。代碼審查,也需要對“可讀性”做專門的評審。
在不同語言之間的交交互操作,要通過 Protocol Buffers 來處理。Protocol Buffers 是 Google 公司開發的一種資料描述語言,類似於 XML 能夠將結構化資料序列化,可用於資料存儲、通信協定等方面
Google 的伺服器連接了很多資料庫,提供用於調試伺服器的工具。伺服器崩潰時,可以自動匯出堆疊軌跡到日誌檔。 還有 Web 介面用於調試,可以用來查看呼入和呼出的 RPC 調用、更改的命令列標誌值、資源消耗、性能分析等。
Google 的大部分專案組,都有固定的軟體工程師負責發 版。
大部分的軟體,發版比較頻繁。通常是周發版,或者每兩周發版,有些項目組甚至每天發。所以,自動化進行發版就是必須的了。頻繁發版有助於工程師們保持鬥志,提高整體速度,實現更多的反覆運算,從而也可以獲得更多的回饋,並做出更多有益的更正。
要上線任何更改,並對使用者可用,則需要專案組外很多人的審批。審批來自多個方面,包括法律合規、隱私保護、安全要求、可靠性、業務需求等等。
Google 有一個內部的上線審批工具,用來執行審查和上線審批。通過定制,這個工具,對不同的產品有不同的審查和審批流程。
發生了重大的服務事故後,相關人員要起草過錯總結報告。文檔描述事故細節,包括標題、概要、影響、時間段、原因、故障元件、行動。 總結的聚焦在於問題,以及未來如何解決,而不是聚焦在於人,也不是為了懲罰責任人。
Google 鼓勵頻繁的重寫代碼,任何軟體每隔幾年就重寫一遍。一來可以優化產品,採用最先進技術,去掉無用的功能,另外還可以轉移知識到新員工,並保持員工的鬥志。
專案管理
盡人皆知,google 的工程師擁有 20%的自由時間,可以隨意做感興趣的東西,而無需審批。這當然是為了 激發工程師的各種創意,同時也讓工程師們保持高效率 ,而不是窒息在必須完成的任務中。 另外,也考慮到,很多員工都會私下裡自己做一些東西,那麼還不如鼓勵大家將這些研究方向公開。
不論個人還是團隊,都要 明確的寫下自己的目標,並評估達成目標的進度 。每個季度的末尾,要根據關鍵結果,對目標達成情況進行打分,分數從 0 分到 1 分。這 OKR 分數是全公司內部公開的。但這並不直接用作個人績效評估的輸入。
平均得分是 0.65,但鼓勵大家將目標定的高一些,所以在可完成任務之上,再加 50% 的工作量是正常的。
對於項目立項審批,以及項目取消,Google 並沒有清楚定義的流程。即便是在 Google 做過 10 年的老經理,也不知道決策是如何做出的。很可能因為在公司範圍內,流程並不一致,經理們可以自行���斷並決策。有時候,決策是由下而上進行的,因為項目組的人都走光了。有時候,決策是自上而下的,老闆們決定哪些專案得到更多的預算,那些則必須關閉。
當關閉一個大項目時, 工程師們可以自行尋找新機會,加入新團隊 。有的時候,還會搞「去碎片化」行動,把瑣碎的分散的團隊合併,這個時間工程師也可以 自行選擇團隊和工作地點。
經常進行重組,有利於突破大公司的低效陷阱。
人的管理
Google 將「技術路線」和「管理路線」分開;將「技術領導」從「管理」中分出;將「研究」綜合到「工程」中;設置「產品經理」、「專案經理」、和「網站可靠性」來支持工程師們。
工程中主要的崗位包括:
這是工程序列中唯一的「人員管理」崗位。 軟體工程師也「可能」管理人,但工程經理「總是」管理人 。工程經理通常以前就是工程師,具備技術經驗,以及管理人的技能。
技術領導力與人員管理能力之間,是有區別的。
「工程經理」不一定帶領專案;項目通常由「技術組長」負責,當然「技術組長」也可能由「工程經理」擔任,但大多數情況下都是「工程師」。項目的「技術組長」對項目中的技術問題,具有決定權。
經理負責選擇「技術組長」,並監控團隊績效 。工程經理還負責職場發展的培訓及引領,進行績效評估,並部分負責薪酬制定。還要做一些招聘工作。
一般來說,工程經理管理 3 – 30 個人,普遍情況下是 8 – 12 人。
在 Google,「工程」和「管理」的職業發展路線是不同的。工程師可以管理下屬,但這不是必須的。在更高層次,領導力是必須的,但領導力不一定從對人的管理中來。比如, 開發了極具影響力的軟體,或者寫的代碼被很多工程師使用,也是一種領導力。
科學家的招聘的門檻更高,需要有學術上的論文發表能力和代碼能力。除了科學家需要論文和著作外,科學家和工程師沒有顯著的區別。在 Google,科學家和工程師一起工作,同樣研發產品,同在一個團隊。 這樣的安排為的將研究成果更好的導入產品中。
對系統的維護由軟體工程師團隊負責,而不是通常的系統管理員。 網站可靠性工程師的技能要求,比軟體發展工程師要稍低。
產品經理負責管理產品,他們 協調軟體工程師的工作,宣講功能特性,與其他團隊配合,跟蹤 bug 和進度 ,保證一切順暢運行以開發出高品質的產品。產品經理不寫代碼。
計畫經理有點類似產品經理, 但他們不管產品,而是管理專案、過程、或運營 。
工程師與產品經理、計畫經理的比例,一般非常高,大約在 4:1 到 30:1 之間 。
設施
Google 有很先進的各種設備,包括遊戲房、健身房,以及提供各種美食的免費餐廳,這一切都是為的將員工留在公司,多多工作。還可以帶朋友來蹭飯,這樣就增加了將朋友招聘進來的機會。
Google 的座位都是開放的,甚至有點擁擠,這有助於加強交流,但同時也影響了個人的專注,算是權衡之下的損失了。員工雖然有自己的座位,但每 6 -12 個月就要換一換,也是為了加強交流。
培訓
Google 的培訓有一下幾種:
新員工(Nooglers)都要參加一個入職培訓教程
技術員工要參加一個「Codelabs」,進行短期的線上培訓課程,其中還有編碼練習
許多線上和現場的培訓課程
對於參加外部機構的課程,Google 也給予支持
每個新員工,都被指派一名正式的「導師」和一名「搭檔」,以幫助他儘快上手。
換崗
鼓勵換崗流動,以在公司範圍內傳播知識,並提高跨組織的交流。在一個崗位工作 12 個月後,可以選擇其他項目,也可以選擇換個辦公室。
績效評估和獎勵
Google 非常歡迎互相評價。 工程師可以彼此互贈正面評價,一種是「同事獎金」,一種是「點贊」。每名員工,每年擁有兩次機會,給予其他員工以「同事獎金」提名,獎金是 100 美元。這種「同事獎金」是為了獎勵員工在職責之外幫助他人。「點贊」則僅僅是表揚,沒有現金獎勵。
經理可以發放獎金,包括一種在專案完成後的特殊獎金。Google 和其他公司一樣,也有年底績效獎和股權激勵。
績效優秀,可以晉升。而績效差的,則需要進行改進,但有意思的是 Google 很少開除員工。員工還要對經理的績效進行評估,以保證管理效率和管理品質。
【TechOrange 徵才:社群編輯、程式設計】 如果你對數位行銷、Startup 趨勢、產業轉型、程式設計,以及新科技議題有興趣,不怕用與眾不同的面向,去衝撞一般思維,歡迎你加入 TO >> 詳細職缺訊息 意者請提供履歷自傳以及文字作品,寄至 [email protected] 來信主旨請註明:【應徵】TechOrange 職缺名稱:您的大名
點關鍵字看更多相關文章:
1 note
·
View note
Text
美团点评移动网络优化实践
本文根据第16期美团点评技术沙龙“移动开发实践(上海站)”演讲内容整理而成。
第18期沙龙:高可用系统背后的基础架构(3月25日)火热来袭!快快点击报名吧。
为何要做网络优化
网络优化对于App产品的用户体验至关重要,与公司的运营和营收息息相关。这里列举两个公开的数据:
“页面加载超过3秒,57%的用户会离开。”
“Amazon页面加载延长1秒,一年就会减少16亿美金营收。”
在做网络优化前,我们首先要为网络通信质量设立一个标尺。
在美团点评,监控团队开发了基于端到端的客户端监控平台。这里要先解释一下“端到端”的含义:是指请求从客户端发出到服务端响应返回的整个过程。它区别于后台服务监控,是一种从用户角度观察到的真实体验监控。
监控页面如图所示: 通过基于命令字的、多维度、实时的监控工具,可以及时发现线上问题。
为了方便发现问题,我们在公司内统一了网络响应状态码的范围。通过状态码的分段范围,也可以迅速清晰地看到网络成败的原因和占比。 有了监控工具后,我们来讨论:移动网络请求过程中,出现了哪些最常见的问题?
首先是网络不可用的问题。主要由以下几种原因导致:
GFW的拦截,原因你懂的。
DNS的劫持,端口的意外封禁等。
偏远地区网络基础设施比较差。
其次是网络加载时间长。原因包括:
移动设备出于省电的目的,发出网络请求前需要先预热通信芯片。
网络请求需要跨网络运营商,物理路径长。
HTTP请求是基于Socket设计的,请求发起之前会经历三次握手,断开时又会进行四次挥手。
最后是HTTP协议的数据安全问题。原因有:
HTTP协议的数据容易被抓包。Post包体数据经过加密能够避免泄露,但协议中的URL和header部分还是会暴露给抓包软件。HTTPS也面临相似的问题。
运营商数据恶意篡改严重。如下图中,App的网页中就被运营商插入了广告。
基于短连的优化
面对上述网络问题,我们首先在HTTP短连请求中进行了一些优化尝试。
短连方案一、域名合并方案
随着开发规模逐渐扩大,各业务团队出于独立性和稳定性的考虑,纷纷申请了自己的三级域名。App中的API域名越来越多。如下所示: search.api.dianping.com ad.api.dianping.com tuangou.api.dianping.com waimai.api.dianping.com movie.api.dianping.com …
App中域名多了之后,将面临下面几个问题:
HTTP请求需要跟不同服务器建立连接。增加了网络的并发连接数量。
每条域名都需要经过DNS服务来解析服务器IP。
如果想将所有的三级域名都合并为一个域名,又会面临巨大的项目推进难题。因为不同业务团队当初正是出于独立性和稳定性的考虑才把域名进行拆分,现在再想把域名合并起来,势必会遭遇巨大的阻力。
所以我们面临的是:既要将域名合并,提升网络连接效率,又不能改造后端业务服务器。经过讨论,我们想到了一个折中的方案。 该方案的核心思想在于:保持客户端业务层代码编写的网络请求与后端业务服务器收到的请求��持一致,请求发出前,在客户端网络层对域名收编,请求送入后端,在SLB(Server Load Balancing)中对域名进行还原。
网络请求发出前,在客户端的网络底层将URL中的域名做简单的替换,我们称之为“域名收编”。
例如:URL "http://ift.tt/2mPHICH" 在网络底层被修改为 "http://ift.tt/2n5tGis" 。
这里,将域名"ad.api.dianping.com"替换成了"api.dianping.com",而紧跟在域名后的其后的"ad"表示了这是一条与广告业务有关的域名请求。
依此类推,所有URL的域名都被合并为"api.dianping.com"。子级域名信息被隐藏在了域名后的path中。
被改造的请求被送到网络后端,在SLB中,拥有与客户端网络层相反的一套域名反收编逻辑,称为“域名还原”。
例如:"http://ift.tt/2n5tGis" 在SLB中被还原为 "http://ift.tt/2mPHICH" 。 SLB的作用是将请求分发到不同的业务服务器,在经过域名还原之后,请求已经与客户端业务代码中原始请求一致了。
该方案具有如下优势:
域名得到了收编,减少了DNS调用次数,降低了DNS劫持风险。
针对同一域名,可以利用Keep-Alive来复用Http的连接。
客户端业务层不需要修改代码,后端业务服务也不需要进行任何修改。
短连方案二、IP直连方案
经过域名合并方案,我们已经将所有的域名都统一成了"api.dianping.com"。针对这唯一的域名,我们可以在客户端架设自己的DNS服务。
方案很简单:程序启动的时候拉取"api.dianping.com"对应的所有的IP列表;对所有IP进行跑马测试,找到速度最快的IP。后续所有的HTTPS请求都将域名更换为跑马最快的IP即可。 举个例子,假如:经过跑马测试发现域名"api.dianping.com"对应最快的IP是"1.23.456.789"。
URL"http://ift.tt/2n5ncQF"
IP直连方案有下面几大优势:
摒弃了系统DNS,减少外界干扰,摆脱DNS劫持困扰。
自建DNS更新时机可以控制。
IP列表更换方便。
此外,如果你的App域名没有经过合并,域名比较多,也建议可以尝试使用HttpDNS方案。参考:http://ift.tt/1RhS09G 对HTTPS中的证书处理:
HTTPS由于要求证书绑定域名,如果做IP直连方案可能会遇到一些麻烦,这时我们需要对客户端的HTTPS的域名校验部分进行改造,参见:http://ift.tt/2mPCNBD 。
经过域名合并加上IP直连方案改造后,HTTP短连的端到端成功率从95%提升到97.5%,网络延时从1500毫秒降低到了1000毫秒,可谓小投入大产出。
接下来要想进一步提升端到端成功率,就要开始进行长连通道建设了。
长连通道建设
提到长连通道建设,首先让人想到的应该是HTTP/2技术。它具有异步连接多路复用、头部压缩、请求响应管线化等众多优点。
如果查看HTTP/2的拓扑结构,其实非常简单: HTTP/2在客户端与服务器之间建立长连通道,将同一域名的请求都放在长连通道上进行。这种拓扑结构有如下一些缺点:
请求基于DNS,仍将面临DNS劫持风险。
不同域名的请求需要建立多条连接。
网络通道难以优化。客户端与服务器之间是公网链路。如果在多地部署服务器,成本消耗又会很大。
业务改造难度大。部署HTTP/2,需要对业务服务器进行改造,而且使用的业务服务器越多,需要改造的成本也越大。
网络协议可订制程度小。
与HTTP/2相区别,我们这里推荐另一种代理长连的模式。这种模式的拓扑图如下: 基本思路为:在客户端与业务服务器之间架设代理长连服务器,客户端与代理服务器建立TCP长连通道,客户端的HTTP请求被转换为了TCP通道上的二进制数据包。代理服务器负责与业务服务器进行HTTP请求,请求的结果通过长连通道送回客户端。
与HTTP/2模式对比,代理长连模式具有下面一些优势:
对DNS无依赖。客户端与代理服务器之间的长连通道是通过IP建立的,与DNS没有关系。客户端的HTTP请求被转换为二进制数据流送到代理服务器,也不需要进行DNS解析。代理服务器转发请求到业务服务器时,都处于同一内网,因此可以自己搭建DNS服务,减少对公网DNS服务的依赖。从这个层面上说,代理长连模式天生具有防DNS劫持的能力。
不同域名的请求可以复用同一条长连通道。
通道易优化。与部署业务服务器相比,部署代理长连服务器的代价就小了很多,可以在全国甚至全世界多地部署代理长连服务器。客户端在选择代理长连服务器时,可以通过跑马找到最快的服务器IP进行连接。另一方面,代理服务器与业务服务器之间的网络通道也可以进行优化,通过架设专线或者租用腾讯云等方式可以大大提升通道服务质量。
对业务完全透明。客户端的业务代码只要接入网络层的SDK即可,完全不用关心网络请求使用的是长连通道还是短连通道。代理服务器将客户端的请求还原为HTTP短连方式送到业务服务器,业务服务器不需要进行任何改造。
网络协议完全自定义。
在长连通道项目的早期,出于快速推进的目的,同时受限于建设代理长连服务器需要投入大量资金,我们首先接入使用了腾讯的维纳斯(WNS)服务(官网地址:http://ift.tt/2n5iS3W )。 WNS服务采用的也是代理长连模式,依托腾讯云的强大硬件建设,我们使用下来发现端到端成功率可以达到99.6%以上。(PS:这里的提到的端到端成功率与官网宣传的99.9%不同是由于统计口径的不同。)
由于腾讯WNS服务是面向公众的云服务,服务的客户远不止一家,无法完全满足我们公司技术需求的快速变更,因此还是需要进行自己的长连通道项目建设。
自建长连建设大概可以分为以下几个周期:
① 中转服务的开发和部署
作为开发的初级阶段,这一时期的任务主要是搭建代理中转服务器,并架设完整链路结构。
② 加密通道的建设
为了保护TCP通道上数据的安全性,客户端与代理长连服务器之间的二进制通信数据可以利用加密来保障数据安全。
③ 专线建设
在代理长连服务器与后台业务服务器之间建设专线。使用专线,可以大大降低公网环境的干扰,保障服务的稳定性。
④ 自动降级Failover建设
由于客户端的请求都放在TCP通道上进行,当代理长连服务器需要升级或者由于极端情况发生了故障时,将会造成客户端的整体网络服务不可用。为了解决这个问题,我们准备了Failover降级方案。当TCP通道无法建立或者发生故障时,可以使用UDP面向无连接的特性提供另一条请求通道,或者绕过代理长连服务器之间向业务服务器发起HTTP公网请求。本文的后面章节有展示Failover机制的实际效果。
⑤ 多地部署接入点
在全国多地部署代理长连接入点。客户端与接入点建立长连通道时,可以选择最快的服务器就近接入,从而大大降低通道连接速度并提升通信质量。 我们在近两年的网络优化实践中,将客户端的网络通道服务整理成了一个独立的SDK,SDK内除了包含了自建的长连通信服务,也包含了WNS等网络通道。
完整的网络通道拓扑图如下所示:
图中网络通道SDK包含了三大通信通道:
CIP通道:CIP通道就是上文中提到的自建代理长连通道。CIP是China Internet Plus的缩写,为美团点评集团的注册英文名称。App中绝大部分的请求通过CIP通道中的TCP子通道与长连服务器(CIP Connection Server)通信,长连服务器将收到的请求代理转发到业务服务器(API Server)。由于TCP子通道在一些极端情况下可能会无法工作,我们在CIP通道中额外部署了UDP子通道和HTTP子通道,其中HTTP子通道通过公网绕过长连服务器与业务服务器进行直接请求。CIP通道的平均端到端成功率目前已达99.7%,耗时平均在350毫秒左右。
WNS通道:出于灾备的需要,腾讯的WNS目前仍被包含在网络通道SDK中。当极端情况发生,CIP通道不可用时,WNS通道还可以作为备用的长连替代方案。
HTTP通道:此处的HTTP通道是在公网直接请求API Server的网络通道。出于长连通道重要性的考虑,上传和下载大数据包的请求如果放在长连上进行都有可能导致长连通道的拥堵,因此我们将CDN访问、文件上传和频繁的日志上报等放在公网利用HTTP短连进行请求,同时也减轻代理长连服务器的负担。
推送方案:在网络通道拓扑图的右上角,有个Push Server。它是考虑到TCP通道的双工特性,为网络通道SDK提供推送的能力。利用通知推送,可以在服务器数据发生变化时及时通知客户端。推送方案可以替换掉代码中常见的耗时低效的轮询方案。
案例展示
下图展示了某开机接口在接入长连后的端到端成功率对比:
上图中黑色曲线是某开机接口在短连通道下的成功率曲线。成功率平均只有81%,抖动的特别剧烈,说明网络服务稳定性不够。
蓝色曲线是同一接口在长连通道下的成功率曲线。成功率平均已达到99%,抖动大幅减小。
成功延时对比图:
上图中展示了同样情况下的成功延时曲线。蓝色线为长连延时曲线,黑色线为短连延时曲线。
接下来我们看Failover的效果展示图。
下图展示了2015年的一次长连服务器故障。
当时Android客户端采用了Failover方案,在长连不可用时Failover到短链或者UDP通道上。与未采用Failover方案的iOS客户端相比,Failover机制在维持网络整体可用性方面体现���了非常大的优势。
网络配置系统
网络通道SDK包含了CIP|WNS|HTTP三大通道,不同的通道具有各自的优缺点,控制各请求选择合适的网络通道成了迫在眉睫的重要课题。
为此我们开发了网络配置系统,通过下发指令,调整App中网络通道SDK中的通道选择策略,可以控制不同的API请求动态切换网络通道。
下图是某接口的线上通道切换示意图:
图中展示了某接口切换WNS通道的过程。图中的黑色线代表短连通道下的请求数量曲线,蓝色线代表WNS通道下的请求数量曲线。通过线上控制系统下发了通道切换指令后,绝大部分的短连请求在5分钟之内被切换成了WNS通道请求。
经验总结
在客户端开发过程中,我们发现:
长连通道建立越早,成功率越高。长连通道越早建立,越多的请求能够在长连通道上进行。特别是当App刚打开时,数量众多的请求同时需要发出。面对这种情况,我们采取的策略是首先建立长连通道,将众多请求放入等待发送队列中,待长连通道建立完毕后再将等待队列中的请求放在长连通道上依次送出。采用这种策略后,我们发现启动时的接口成功率平均提升了1.4%,延时平均降低了160毫秒。
TCP数据包越大,传输时间越长。如果长连通道未采用类似HTTP/2中的数据切片技术,大的数据包非常容易导致长连通道的堵塞。
底层SDK上线新功能一定要有线上降级手段。当新功能上线了发生故障时,可以通过开关或参数控制,或是采用ABTest方式等进行降级,防止故障扩大化。
iOS和Android系统网络库存在很多默认行为。例如系统网络库会在内部处理网络重定向,再比如请求头中如果没有填写Accept-Encoding或Content-Type等字段,系统网络库会自动填写默认值。
一个容易忽视的地方:HTTP的请求头键值对中的的键是允许相同和重复的。例如下图所示的"Set-Cookie"字段就是包含了多组的相同的键名称。与之类似的还有"Cookie"字段。在长连通信中,如果对header中的键值对用不加处理的字典方式保存和传输,就会造成数据的丢失。
对于正在成长中的创业公司,我们有如下改善网络状况的建议:
收拢网络底层。随着公司的成长,开发团队越来越多,不可避免的将会引入越来越多的网络库。网络库多了之后,再对网络请求进行集中管理就非常困难了。我们的建议是在网络库与业务代码之间架设自己的网络层,业务的网络请求全部经过网络层代码进行请求。这样未来进行底层网络库的更换,或者网络通道的优化将变得容易很多。
使用网络监控。引入网络监控机制,发现网络问题。这里推荐我公司开发的开源的Cat监控系统。Cat开源地址为http://ift.tt/1YJvUx2 。
尝试进行短连优化。前文中提到的域名合并和IP直连方案都是简单有效的手段。
可以尝试HTTP/2或腾讯WNS长连服务。
作者简介
周辉,美团点评资深移动架构师。所在团队负责整个集团客户端网络通道、监控、推送等底层SDK的开发和维护工作。
0 notes
Text
缓存那些事
本文已发表于《程序员》杂志2017年第3期,下面的版本又经过进一步的修订。
前言
一般而言,现在互联网应用(网站或App)的整体流程,可以概括如图1所示,用户请求从界面(浏览器或App界面)到网络转发、应用服务再到存储(数据库或文件系统),然后返回到界面呈现内容。
随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的,且技术变革是缓慢的,数据库每秒能接受的请求次数也是有限的(或者文件的读写也是有限的),如何能够有效利用有限的资源来提供尽可能大的吞吐量?一个有效的办法就是引入缓存,打破标准流程,每个环节中请求可以从缓存中直接获取目标数据并返回,从而减少计算量,有效提升响应速度,让有限的资源服务更多的用户。
如图1所示,缓存的使用可以出现在1~4的各个环节中,每个环节的缓存方案与使用各有特点。
图1 互联网应用一般流程
缓存特征
缓存也是一个数据模型对象,那么必然有它的一些特征:
命中率
命中率=返回正确结果数/请求缓存次数,命中率问题是缓存中的一个非常重要的问题,它是衡量缓存有效性的重要指标。命中率越高,表明缓存的使用率越高。
最大元素(或最大空间)
缓存中可以存放的最大元素的数量,一旦缓存中元素数量超过这个值(或者缓存数据所占空间超过其最大支持空间),那么将会触发缓存启动清空策略根据不同的场景合理的设置最大元素值往往可以一定程度上提高缓存的命中率,从而更有效的时候缓存。
清空策略
如上描述,缓存的存储空间有限制,当缓存空间被用满时,如何保证在稳定服务的同时有效提升命中率?这就由缓存清空策略来处理,设计适合自身数据特征的清空策略能有效提升命中率。常见的一般策略有:
FIFO(first in first out)
先进先出策略,最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时间。在数据实效性要求场景下可选择该类策略,优先保障最新数据可用。
LFU(less frequently used)
最少使用策略,无论是否过期,根据元素的被使用次数判断,清除使用次数较少的元素释放空间。策略算法主要比较元素的hitCount(命中次数)。在保证高频数据有效性场景下,可选择这类策略。
LRU(least recently used)
最近最少使用策略,无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素释放空间。策略算法主要比较元素最近一次被get使用时间。在热点数据场景下较适用,优先保证热点数据的有效性。
除此之外,还有一些简单策略比如:
根据过期时间判断,清理过期时间最长的元素;
根据过期时间判断,清理最近要过期的元素;
随机清理;
根据关键字(或元素内容)长短清理等。
缓存介质
虽然从硬件介质上来看,无非就是内存和硬盘两种,但从技术上,可以分成内存、硬盘文件、数据库。
内存:将缓存存储于内存中是最快的选择,无需额外的I/O开销,但是内存的缺点是没有持久化落地物理磁盘,一旦应用异常break down而重新启动,数据很难或者无法复原。
硬盘:一般来说,很多缓存框架会结合使用内存和硬盘,在内存分配空间满了或是在异常的情况下,可以被动或主动的将内存空间数据持久化到硬盘中,达到释放空间或备份数据的目的。
数据库:前面有提到,增加缓存的策略的目的之一就是为了减少数据库的I/O压力。现在使用数据库做缓存介质是不是又回到了老问题上了?其实,数据库也有很多种类型,像那些不支持SQL,只是简单的key-value存储结构的特殊数据库(如BerkeleyDB和Redis),响应速度和吞吐量都远远高于我们常用的关系型数据库等。
缓存分类和应用场景
缓存有各类特征,而且有不同介质的区别,那么实际工程中我们怎么去对缓存分类呢?在目前的应用服务框架中,比较常见的,时根据缓存雨应用的藕合度,分为local cache(本地缓存)和remote cache(分布式缓存):
本地缓存:指的是在应用中的缓存组件,其最大的优点是应用和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适;同时,它的缺点也是应为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。
分布式缓存:指的是与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存。
目前各种类型的缓存都活跃在成千上万的应用服务中,还没有一种缓存方案可以解决一切的业务场景或数据类型,我们需要根据自身的特殊场景和背景,选择最适合的缓存方案。缓存的使用是程序员、架构师的必备技能,好的程序员能根据数据类型、业务场景来准确判断使用何种类型的缓存,如何使用这种缓存,以最小的成本最快的效率达到最优的目的。
本地缓存
编程直接实现缓存
个别场景下,我们只需要简单的缓存数据的功能,而无需关注更多存取、清空策略等深入的特性时,直接编程实现缓存���是最便捷和高效的。
a. 成员变量或局部变量实现
简单代码示例如下:
public void UseLocalCache(){ //一个本地的缓存变量 Map<String, Object> localCacheStoreMap = new HashMap<String, Object>(); List<Object> infosList = this.getInfoList(); for(Object item:infosList){ if(localCacheStoreMap.containsKey(item)){ //缓存命中 使用缓存数据 // todo } else { // 缓存未命中 IO获取数据,结果存入缓存 Object valueObject = this.getInfoFromDB(); localCacheStoreMap.put(valueObject.toString(), valueObject); } } } //示例 private List<Object> getInfoList(){ return new ArrayList<Object>(); } //示例数据库IO获取 private Object getInfoFromDB(){ return new Object(); }
以局部变量map结构缓存部分业务数据,减少频繁的重复数据库I/O操作。缺点仅限于类的自身作用域内,类间无法共享缓存。
b. 静态变量实现
最常用的单例实现静态资源缓存,代码示例如下:
public class CityUtils { private static final HttpClient httpClient = ServerHolder.createClientWithPool(); private static Map<Integer, String> cityIdNameMap = new HashMap<Integer, String>(); private static Map<Integer, String> districtIdNameMap = new HashMap<Integer, String>(); static { HttpGet get = new HttpGet("http://ift.tt/2n5qbsn;); BaseAuthorizationUtils.generateAuthAndDateHeader(get, BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC, BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC); try { String resultStr = httpClient.execute(get, new BasicResponseHandler()); JSONObject resultJo = new JSONObject(resultStr); JSONArray dataJa = resultJo.getJSONArray("data"); for (int i = 0; i < dataJa.length(); i++) { JSONObject itemJo = dataJa.getJSONObject(i); cityIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name")); } } catch (Exception e) { throw new RuntimeException("Init City List Error!", e); } } static { HttpGet get = new HttpGet("http://ift.tt/2mPEtLG;); BaseAuthorizationUtils.generateAuthAndDateHeader(get, BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC, BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC); try { String resultStr = httpClient.execute(get, new BasicResponseHandler()); JSONObject resultJo = new JSONObject(resultStr); JSONArray dataJa = resultJo.getJSONArray("data"); for (int i = 0; i < dataJa.length(); i++) { JSONObject itemJo = dataJa.getJSONObject(i); districtIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name")); } } catch (Exception e) { throw new RuntimeException("Init District List Error!", e); } } public static String getCityName(int cityId) { String name = cityIdNameMap.get(cityId); if (name == null) { name = "未知"; } return name; } public static String getDistrictName(int districtId) { String name = districtIdNameMap.get(districtId); if (name == null) { name = "未知"; } return name; } }
O2O业务中常用的城市基础基本信息判断,通过静态变量一次获取缓存内存中,减少频繁的I/O读取,静态变量实现类间可共享,进程内可共享,缓存的实时性稍差。
为了解决本地缓存数据的实时性问题,目前大量使用的是结合ZooKeeper的自动发现机制,实时变更本地静态变量缓存:
美团点评内部的基础配置组件MtConfig,采用的就是类似原理,使用静态变量缓存,结合ZooKeeper的统一管理,做到自动动态更新缓存,如图2所示。
图2 Mtconfig实现图
这类缓存实现,优点是能直接在heap区内读写,最快也最方便;缺点同样是受heap区域影响,缓存的数据量非常有限,同时缓存时间受GC影响。主要满足单机场景下的小数据量缓存需求,同时对缓存数据的变更无需太敏感感知,如上一般配置管理、基础静态数据等场景。
Ehcache
Ehcache是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,是一个非常轻量级的缓存实现,我们常用的Hibernate里面就集成了相关缓存功能。
图3 Ehcache框架图
从图3中我们可以了解到,Ehcache的核心定义主要包括:
cache manager:缓存管理器,以前是只允许单例的,不过现在也可以多实例了。
cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口,这是一个真正使用的缓存实例;通过缓存管理器的模式,可以在单个应用中轻松隔离多个缓存实例,独立服务于不同业务场景需求,缓存数据物理隔离,同时需要时又可共享使用。
element:单条缓存数据的组成单位。
system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等,缓存就是从SOR中读取或者写入到SOR中去的。
在上层可以看到,整个Ehcache提供了对JSR、JMX等的标准支持,能够较好的兼容和移植,同时对各类对象有较完善的监控管理机制。它的缓存介质涵盖堆内存(heap)、堆外内存(BigMemory商用版本支持)和磁盘,各介质可独立设置属性和策略。Ehcache最初是独立的本地缓存框架组件,在后期的发展中,结合Terracotta服务阵列模型,可以支持分布式缓存集群,主要有RMI、JGroups、JMS和Cache Server等传播方式进行节点间通信,如图3的左侧部分描述。
整体数据流转包括这样几类行为:
Flush:缓存条目向低层次移动。
Fault:从低层拷贝一个对象到高层。在获取缓存的过程中,某一层发现自己的该缓存条目已经失效,就触发了Fault行为。
Eviction:把缓存条目除去。
Expiration:失效状态。
Pinning:强制缓存条目保持在某一层。
图4反映了数据在各个层之间的流转,同时也体现了各层数据的一个生命周期。
图4 缓存数据流转图(L1:本地内存层;L2:Terracotta服务节点层)
Ehcache的配置使用如下:
<ehcache> <!-- 指定一个文件目录,当Ehcache把数据写到硬盘上时,将把数据写到这个文件目录下 --> <diskStore path="java.io.tmpdir"/> <!-- 设定缓存的默认数据过期策略 --> <defaultCache maxElementsInMemory="10000" eternal="false" overflowToDisk="true" timeToIdleSeconds="0" timeToLiveSeconds="0" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"/> <!-- 设定具体的命名缓存的数据过期策略 cache元素的属性: name:缓存名称 maxElementsInMemory:内存中最大缓存对象数 maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大 eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。 diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。 diskPersistent:是否缓存虚拟机重启期数据 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒 timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态 timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,Ehcache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 --> <cache name="CACHE1" maxElementsInMemory="1000" eternal="true" overflowToDisk="true"/> <cache name="CACHE2" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="200" timeToLiveSeconds="4000" overflowToDisk="true"/> </ehcache>
整体上看,Ehcache的使用还是相对简单便捷的,提供了完整的各类API接口。需要注意的是,虽然Ehcache支持磁盘的持久化,但是由于存在两级缓存介质,在一级内存中的缓存,如果没有主动的刷入磁盘持久化的话,在应用异常down机等情形下,依然会出现缓存数据丢失,为此可以根据需要将缓存刷到磁盘,将缓���条目刷到磁盘的操作可以通过cache.flush()方法来执行,需要注意的是,对于对象的磁盘写入,前提是要将对象进行序列化。
主要特性:
快速,针对大型高并发系统场景,Ehcache的多线程机制有相应的优化改善。
简单,很小的jar包,简单配置就可直接使用,单机场景下无需过多的其他服务依赖。
支持多种的缓存策略,灵活。
缓存数据有两级:内存和磁盘,与一般的本地内存缓存相比,有了磁盘的存储空间,将可以支持更大量的数据缓存需求。
具有缓存和缓存管理器的侦听接口,能更简单方便的进行缓存实例的监控管理。
支持多缓存管理器实例,以及一个实例的多个缓存区域。
注意:Ehcache的超时设置主要是针对整个cache实例设置整体的超时策略,而没有较好的处理针对单独的key的个性的超时设置(有策略设置,但是比较复杂,就不描述了),因此,在使用中要注意过期失效的缓存元素无法被GC回收,时间越长缓存越多,内存占用也就越大,内存泄露的概率也越大。
Guava Cache
Guava Cache是Google开源的Java重用工具集库Guava里的一款缓存工具,其主要实现的缓存功能有:
自动将entry节点加载进缓存结构中;
当缓存的数据超过设置的最大值时,使用LRU算法移除;
具备根据entry节点上次被访问或者写入时间计算它的过期机制;
缓存的key被封装在WeakReference引用内;
缓存的Value被封装在WeakReference或SoftReference引用内;
统计缓存使用过程中命中率、异常率、未命中率等统计数据。
Guava Cache的架构设计灵感来源于ConcurrentHashMap,我们前面也提到过,简单场景下可以自行编码通过hashmap来做少量数据的缓存,但是,如果结果可能随时间改变或者是希望存储的数据空间可控的话,自己实现这种数据结构还是有必要的。
Guava Cache继承了ConcurrentHashMap的思路,使用多个segments方式的细粒度锁,在保证线程安全的同时,支持高并发场景需求。Cache类似于Map,它是存储键值对的集合,不同的是它还需要处理evict、expire、dynamic load等算法逻辑,需要一些额外信息来实现这些操作。对此,根据面向对象思想,需要做方法与数据的关联封装。如图5所示cache的内存数据模型,可以看到,使用ReferenceEntry接口来封装一个键值对,而用ValueReference来封装Value值,之所以用Reference命令,是因为Cache要支持WeakReference Key和SoftReference、WeakReference value。
图5 Guava Cache数据结构图
ReferenceEntry是对一个键值对节点的抽象,它包含了key和值的ValueReference抽象类,Cache由多个Segment组成,而每个Segment包含一个ReferenceEntry数组,每个ReferenceEntry数组项都是一条ReferenceEntry链,且一个ReferenceEntry包含key、hash、valueReference、next字段。除了在ReferenceEntry数组项中组成的链,在一个Segment中,所有ReferenceEntry还组成access链(accessQueue)和write链(writeQueue)(后面会介绍链的作用)。ReferenceEntry可以是强引用类型的key,也可以WeakReference类型的key,为了减少内存使用量,还可以根据是否配置了expireAfterWrite、expireAfterAccess、maximumSize来决定是否需要write链和access链确定要创建的具体Reference:StrongEntry、StrongWriteEntry、StrongAccessEntry、StrongWriteAccessEntry等。
对于ValueReference,因为Cache支持强引用的Value、SoftReference Value以及WeakReference Value,因而它对应三个实现类:StrongValueReference、SoftValueReference、WeakValueReference。为了支持动态加载机制,它还有一个LoadingValueReference,在需要动态加载一个key的值时,先把该值封装在LoadingValueReference中,以表达该key对应的值已经在加载了,如果其他线程也要查询该key对应的值,就能得到该引用,并且等待改值加载完成,从而保证该值只被加载一次,在该值加载完成后,将LoadingValueReference替换成其他ValueReference类型。ValueReference对象中会保留对ReferenceEntry的引用,这是因为在Value因为WeakReference、SoftReference被回收时,需要使用其key将对应的项从Segment的table中移除。
WriteQueue和AccessQueue :为了实现最近最少使用算法,Guava Cache在Segment中添加了两条链:write链(writeQueue)和access链(accessQueue),这两条链都是一个双向链表,通过ReferenceEntry中的previousInWriteQueue、nextInWriteQueue和previousInAccessQueue、nextInAccessQueue链接而成,但是以Queue的形式表达。WriteQueue和AccessQueue都是自定义了offer、add(直接调用offer)、remove、poll等操作的逻辑,对offer(add)操作,如果是新加的节点,则直接加入到该链的结尾,如果是已存在的节点,则将该节点链接的链尾;对remove操作,直接从该链中移除该节点;对poll操作,将头节点的下一个节点移除,并返回。
了解了cache的整体数据结构后,再来看下针对缓存的相关操作就简单多了:
Segment中的evict清除策略操作,是在每一次调用操作的开始和结束时触发清理工作,这样比一般的缓存另起线程监控清理相比,可以减少开销,但如果长时间没有调用方法的话,会导致不能及时的清理释放内存空间的问题。evict主要处理四个Queue:1. keyReferenceQueue;2. valueReferenceQueue;3. writeQueue;4. accessQueue。前两个queue是因为WeakReference、SoftReference被垃圾回收时加入的,清理时只需要遍历整个queue,将对应的项从LocalCache中移除即可,这里keyReferenceQueue存放ReferenceEntry,而valueReferenceQueue存放的是ValueReference,要从Cache中移除需要有key,因而ValueReference需要有对ReferenceEntry的引用,这个前面也提到过了。而对后面两个Queue,只需要检查是否配置了相应的expire时间,然后从头开始查找已经expire的Entry,将它们移除即可。
Segment中的put操作:put操作相对比较简单,首先它需要获得锁,然后尝试做一些清理工作,接下来的逻辑类似ConcurrentHashMap中的rehash,查找位置并注入数据。需要说明的是当找到一个已存在的Entry时,需要先判断当前的ValueRefernece中的值事实上已经被回收了,因为它们可以是WeakReference、SoftReference类型,如果已经被回收了,则将新值写入。并且在每次更新时注册当前操作引起的移除事件,指定相应的原因:COLLECTED、REPLACED等,这些注册的事件在退出的时候统一调用Cache注册的RemovalListener,由于事件处理可能会有很长时间,因而这里将事件处理的逻辑在退出锁以后才做。最后,在更新已存在的Entry结束后都尝试着将那些已经expire的Entry移除。另外put操作中还需要更新writeQueue和accessQueue的语义正确性。
Segment带CacheLoader的get操作:1. 先查找table中是否已存在没有被回收、也没有expire的entry,如果找到,并在CacheBuilder中配置了refreshAfterWrite,并且当前时间间隔已经操作这个事件,则重新加载值,否则,直接返回原有的值;2. 如果查找到的ValueReference是LoadingValueReference,则等待该LoadingValueReference加载结束,并返回加载的值;3. 如果没有找到entry,或者找到的entry的值为null,则加锁后,继续在table中查找已存在key对应的entry,如果找到并且对应的entry.isLoading()为true,则表示有另一个线程正在加载,因而等待那个线程加载完成,如果找到一个非null值,返回该值,否则创建一个LoadingValueReference,并调用loadSync加载相应的值,在加载完成后,将新加载的值更新到table中,即大部分情况下替换原来的LoadingValueReference。
Guava Cache提供Builder模式的CacheBuilder生成器来创建缓存的方式,十分方便,并且各个缓存参数的配置设置,类似于函数式编程的写法,可自行设置各类参数选型。它提供三种方式加载到缓存中。分别是:
在构建缓存的时候,使用build方法内部调用CacheLoader方法加载数据;
callable 、callback方式加载数据;
使用粗暴直接的方式,直接Cache.put 加载数据,但自动加载是首选的,因为它可以更容易的推断所有缓存内容的一致性。
build生成器的两种方式都实现了一种逻辑:从缓存中取key的值,如果该值已经缓存过了则返回缓存中的值,如果没有缓存过可以通过某个方法来获取这个值,不同的地方在于cacheloader的定义比较宽泛,是针对整个cache定义的,可以认为是统一的根据key值load value的方法,而callable的方式较为灵活,允许你在get的时候指定load方法。使用示例如下:
/** * CacheLoader */ public void loadingCache() { LoadingCache<String, String> graphs =CacheBuilder.newBuilder() .maximumSize(1000).build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("key:"+key); if("key".equals(key)){ return "key return result"; }else{ return "get-if-absent-compute"; } } }); String resultVal = null; try { resultVal = graphs.get("key"); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(resultVal); } /** * * Callable */ public void callablex() throws ExecutionException { Cache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(1000).build(); String result = cache.get("key", new Callable<String>() { public String call() { return "result"; } }); System.out.println(result); }
总体来看,Guava Cache基于ConcurrentHashMap的优秀设计借鉴,在高并发场景支持和线程安全上都有相应的改进策略,使用Reference引用命令,提升高并发下的数据……访问速度并保持了GC的可回收,有效节省空间;同时,write链和access链的设计,能更灵活、高效的实现多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等;编程式的build生成器管理,让使用者有更多的自由度,能够根据不同场景设置合适的模式。
分布式缓存
memcached缓存
memcached是应用较广的开源分布式缓存产品之一,它本身其实不提供分布式解决方案。在服务端,memcached集群环境实际就是一个个memcached服务器的堆积,环境搭建较为简单;cache的分布式主要是在客户端实现,通过客户端的路由处理来达到分布式解决方案的目的。客户端做路由的原理非常简单,应用服务器在每次存取某key的value时,通过某种算法把key映射到某台memcached服务器nodeA上,因此这个key所有操作都在nodeA上,结构图如图6、图7所示。
图6 memcached客户端路由图
图7 memcached一致性hash示例图
memcached客户端采用一致性hash算法作为路由策略,如图7,相对于一般hash(如简单取模)的算法,一致性hash算法除了计算key的hash值外,还会计算每个server对应的hash值,然后将这些hash值映射到一个有限的值域上(比如0~2^32)。通过寻找hash值大于hash(key)的最小server作为存储该key数据的目��server。如果找不到,则直接把具有最小hash值的server作为目标server。同时,一定程度上,解决了扩容问题,增加或删除单个节点,对于整个集群来说,不会有大的影响。最近版本,增加了虚拟节点的设计,进一步提升了可用性。
memcached是一个高效的分布式内存cache,了解memcached的内存管理机制,才能更好的掌握memcached,让我们可以针对我们数据特点进行调优,让其更好的为我所用。我们知道memcached仅支持基础的key-value键值对类型数据存储。在memcached内存结构中有两个非常重要的概念:slab和chunk。如图8所示。
图8 memcached内存结构图
slab是一个内存块,它是memcached一次申请内存的最小单位。在启动memcached的时候一般会使用参数-m指定其可用内存,但是并不是在启动的那一刻所有的内存就全部分配出去了,只有在需要的时候才会去申请,而且每次申请一定是一个slab。Slab的大小固定为1M(1048576 Byte),一个slab由若干个大小相等的chunk组成。每个chunk中都保存了一个item结构体、一对key和value。
虽然在同一个slab中chunk的大小相等的,但是在不同的slab中chunk的大小并不一定相等,在memcached中按照chunk的大小不同,可以把slab分为很多种类(class),默认情况下memcached把slab分为40类(class1~class40),在class 1中,chunk的大小为80字节,由于一个slab的大小是固定的1048576字节(1M),因此在class1中最多可以有13107个chunk(也就是这个slab能存最多13107个小于80字节的key-value数据)。
memcached内存管理采取预分配、分组管理的方式,分组管理就是我们上面提到的slab class,按照chunk的大小slab被分为很多种类。内存预分配过程是怎样的呢?向memcached添加一个item时候,memcached首先会根据item的大小,来选择最合适的slab class:例如item的大小为190字节,默认情况下class 4的chunk大小为160字节显然不合适,class 5的chunk大小为200字节,大于190字节,因此该item将放在class 5中(显然这里会有10字节的浪费是不可避免的),计算好所要放入的chunk之后,memcached会去检查该类大小的chunk还有没有空���的,如果没有,将会申请1M(1个slab)的空间并划分为该种类chunk。例如我们第一次向memcached中放入一个190字节的item时,memcached会产生一个slab class 2(也叫一个page),并会用去一个chunk,剩余5241个chunk供下次有适合大小item时使用,当我们用完这所有的5242个chunk之后,下次再有一个在160~200字节之间的item添加进来时,memcached会再次产生一个class 5的slab(这样就存在了2个pages)。
总结来看,memcached内存管理需要注意的几个方面:
chunk是在page里面划分的,而page固定为1m,所以chunk最大不能超过1m。
chunk实际占用内存要加48B,因为chunk数据结构本身需要占用48B。
如果用户数据大于1m,则memcached会将其切割,放到多个chunk内。
已分配出去的page不能回收。
对于key-value信息,最好不要超过1m的大小;同时信息长度最好相对是比较均衡稳定的,这样能够保障最大限度的使用内存;同时,memcached采用的LRU清理策略,合理甚至过期时间,提高命中率。
无特殊场景下,key-value能满足需求的前提下,使用memcached分布式集群是较好的选择,搭建与操作使用都比较简单;分布式集群在单点故障时,只影响小部分数据异常,目前还可以通过Magent缓存代理模式,做单点备份,提升高可用;整个缓存都是基于内存的,因此响应时间是很快,不需要额外的序列化、反序列化的程序,但同时由于基于内存,数据没有持久化,集群故障重启数据无法恢复。高版本的memcached已经支持CAS模式的原子操作,可以低成本的解决并发控制问题。
Redis缓存
Redis是一个远程内存数据库(非关系型数据库),性能强劲,具有复制特性以及解决问题而生的独一无二的数据模型。它可以存储键值对与5种不同类型的值之间的映射,可以将存储在内存的键值对数据持久化到硬盘,可以使用复制特性来扩展读性能,还可以使用客户端分片来扩展写性能。
图9 Redis数据模型图
如图9,Redis内部使用一个redisObject对象来标识所有的key和value数据,redisObject最主要的信息如图所示:type代表一个value对象具体是何种数据类型,encoding是不同数据类型在Redis内部的存储方式,比如——type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或是int,如果是int则代表世界Redis内部是按数值类型存储和表示这个字符串。
图9左边的raw列为对象的编码方式:字符串可以被编码为raw(一般字符串)或Rint(为了节约内存,Redis会将字符串表示的64位有符号整数编码为整数来进行储存);列表可以被编码为ziplist或linkedlist,ziplist是为节约大小较小的列表空间而作的特殊表示;集合可以被编码为intset或者hashtable,intset是只储存数字的小集合的特殊表示;hash表可以编码为zipmap或者hashtable,zipmap是小hash表的特殊表示;有序集合可以被编码为ziplist或者skiplist格式,ziplist用于表示小的有序集合,而skiplist则用于表示任何大小的有序集合。
从网络I/O模型上看,Redis使用单线程的I/O复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select。对于单纯只有I/O操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个I/O调度都是被阻塞住的,在这些特殊场景的使用中,需要额外的考虑。相较于memcached的预分配内存管理,Redis使用现场申请内存的方式来存储数据,并且很少使用free-list等方式来优化内存分配,会在一定程度上存在内存碎片。Redis跟据存储命令参数,会把带过期时间的数据单独存放在一起,并把它们称为临时数据,非临时数据是永远不会被剔除的,即便物理内存不够,导致swap也不会剔除任何非临时数据(但会尝试剔除部分临时数据)。
我们描述Redis为内存数据库,作为缓存服务,大量使用内存间的数据快速读写,支持高并发大吞吐;而作为数据库,则是指Redis对缓存的持久化支持。Redis由于支持了非常丰富的内存数据库结构类型,如何把这些复杂的内存组织方式持久化到磁盘上?Redis的持久化与传统数据库的方式差异较大,Redis一共支持四种持久化方式,主要使用的两种:
定时快照方式(snapshot):该持久化方式实际是在Redis内部一个定时器事件,每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件,如果满足则通过操作系统fork调用来创建出一个子进程,这个子进程默认会与父进程共享相同的地址空间,这时就可以通过子进程来遍历整个内存来进行存储操作,而主进程则仍然可以提供服务,当有写入时由操作系统按照内存页(page)为单位来进行copy-on-write保证父子进程之间不会互相影响。它的缺点是快照只是代表一段时间内的内存映像,所以系统重启会丢失上次快照与重启之间所有的数据。
基于语句追加文件的方式(aof):aof方式实际类似MySQl的基于语句的binlog方式,即每条会使Redis内存数据发生改变的命令都会追加到一个log文件中,也就是说这个log文件就是Redis的持久化数据。
aof的方式的主要缺点是追加log文件可能导致体积过大,当系统重启恢复数据时如果是aof的方式则加载数据会非常慢,几十G的数据可能需要几小时才能加载完,当然这个耗时并不是因为磁盘文件读取速度慢,而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写log,所以使用aof的方式,Redis的读写性能也会有所下降。
Redis的持久化使用了Buffer I/O,所谓Buffer I/O是指Redis对持久化文件的写入和读取操作都会使用物理内存的Page Cache,而大多数数据库系统会使用Direct I/O来绕过这层Page Cache并自行维护一个数据的Cache。而当Redis的持久化文件过大(尤其是快照文件),并对其进行读写时,磁盘文件中的数据都会被加载到物理内存中作为操作系统对该文件的一层Cache,而这层Cache的数据与Redis内存中管理的数据实际是重复存储的。虽然内核在物理内存紧张时会做Page Cache的剔除工作,但内核很可能认为某块Page Cache更重要,而让你的进程开始Swap,这时你的系统就会开始出现不稳定或者崩溃了,因此在持久化配置后,针对内存使用需要实时监控观察。
与memcached客户端支持分布式方案不同,Redis更倾向于在服务端构建分布式存储,如图10、11。
图10 Redis分布式集群图1
图11 Redis分布式集群图2
Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本,它没有中心节点,具有线性可伸缩的功能。如图11,其中节点与节点之间通过二进制协议进行通信,节点与客户端之间通过ascii协议进行通信。在数据的放置策略上,Redis Cluster将整个key的数值域分成4096个hash槽,每个节点上可以存储一个或多个hash槽,也就是说当前Redis Cluster支持的最大节点数就是4096。Redis Cluster使用的分布式算法也很简单:crc16( key ) % HASH_SLOTS_NUMBER。整体设计可总结为:
数据hash分布在不同的Redis节点实例上;
M/S的切换采用Sentinel;
写:只会写master Instance,从sentinel获取当前的master Instance;
读:从Redis Node中基于权重选取一个Redis Instance读取,失败/超时则轮询其他Instance;Redis本身就很好的支持读写分离,在单进程的I/O场景下,可以有效的避免主库的阻塞风险;
通过RPC服务访问,RPC server端封装了Redis客户端,客户端基于Jedis开发。
可以看到,通过集群+主从结合的设计,Redis在扩展和稳定高可用性能方面都是比较成熟的。但是,在数据一致性问题上,Redis没有提供CAS操作命令来保障高并发场景下的数据一致性问题,不过它却提供了事务的功能,Redis的Transactions提供的并不是严格的ACID的事务(比如一串用EXEC提交执行的命令,在执行中服务器宕机,那么会有一部分命令执行了,剩下的没执行)。但是这个Transactions还是提供了基本的命令打包执行的功能(在服务器不出问题的情况下,可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行)。Redis还提供了一个Watch功能,你可以对一个key进行Watch,然后再执行Transactions,在这过程中,如果这个Watched的值进行了修改,那么这个Transactions会发现并拒绝执行。在失效策略上,Redis支持多大6种的数据淘汰策略:
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰;
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 ;
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰;
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
no-enviction(驱逐):禁止驱逐数据。
个人总结了以下多种Web应用场景,在这些场景下可以充分的利用Redis的特性,大大提高效率。
在主页中显示最新的项目列表:Redis使用的是常驻内存的缓存,速度非常快。LPUSH用来插入一个内容ID,作为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。如果用户需要的检索的数据量超越这个缓存容量,这时才需要把请求发送到数据库。
删除和过滤:如果一篇文章被删除,可以使用LREM从缓存中彻底清除掉。
排行榜及相关问题:排行榜(leader board)按照得分进行排序。ZADD命令可以直接实现这个功能,而ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。
按照用户投票和时间排序:排行榜,得分会随着时间变化。LPUSH和LTRIM命令结合运用,把文章添加到一个列表中。一项后台任务用来获取列表,并重新计算列表的排序,ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。
过期项目处理:使用Unix时间作为关键字,用来保持列表能够按时间排序。对current_time和time_to_live进行检索,完成查找过期项目的艰巨任务。另一项后台任务使用ZRANGE…WITHSCORES进行查询,删除过期的条目。
计数:进行各种数据统计的用途是非常广泛的,比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易,通过原子递增保持计数;GETSET用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。
特定时间内的特定项目:这是特定访问者的问题,可以通过给每次页面浏览使用SADD命令来解决。SADD不会将已经存在的成员添加到一个集合。
Pub/Sub:在更新中保持用户对数据的映射是系统中的一个普遍任务。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令,让这个变得更加容易。
队列:在当前的编程中队列随处可见。除了push和pop类型的命令之外,Redis还有阻塞队列的命令,能够让一个程序在执行时被另一个程序添加到队列。
缓存实战
实际工程中,对于缓存的应用可以有多种的实战方式,包括侵入式硬编码,抽象服务化应用,以及轻量的注解式使用等。本文将主要介绍下注解式方式。
Spring注解缓存
Spring 3.1之后,引入了注解缓存技术,其本质上不是一个具体的缓存实现方案,而是一个对缓存使用的抽象,通过在既有代码中添加少量自定义的各种annotation,即能够达到使用缓存对象和缓存方法的返回对象的效果。Spring的缓存技术具备相当的灵活性,不仅能够使用SpEL(Spring Expression Language)来定义缓存的key和各种condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存集成。其特点总结如下:
少量的配置annotation注释即可使得既有代码支持缓存;
支持开箱即用,不用安装和部署额外的第三方组件即可使用缓存;
支持Spring Express Language(SpEL),能使用对象的任何属性或者方法来定义缓存的key和使用规则条件;
支持自定义key和自定义缓存管理者,具有相当的灵活性和可扩展性。
和Spring的事务管理类似,Spring Cache的关键原理就是Spring AOP,通过Spring AOP实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。而Spring Cache利用了Spring AOP的动态代理技术,即当客户端尝试调用pojo的foo()方法的时候,给它的不是pojo自身的引用,而是一个动态生成的代理类。
图12 Spring动���代理调用图
如图12所示,实际客户端获取的是一个代理的引用,在调用foo()方法的时候,会首先调用proxy的foo()方法,这个时候proxy可以整体控制实际的pojo.foo()方法的入参和返回值,比如缓存结果,比如直接略过执行实际的foo()方法等,都是可以轻松做到的。Spring Cache主要使用三个注释标签,即@Cacheable、@CachePut和@CacheEvict,主要针对方法上注解使用,部分场景也可以直接类上注解使用,当在类上使用时,该类所有方法都将受影响。我们总结一下其作用和配置方法,如表1所示。
表1
标签类型 作用 主要配置参数说明 @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 value:缓存的名称,在 Spring 配置文件中定义,必须指定至少一个; key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则默认按照方法的所有参数进行组合; condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 @CachePut 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个; key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则默认按照方法的所有参数进行组合; condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 @CacheEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空 value:缓存的名称,在 Spring 配置文件中定义,必须指定至少一个; key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则默认按照方法的所有参数进行组合; condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存; allEntries:是否清空所有缓存内容,默认为 false,如果指定为 true,则方法调用后将立即清空所有缓存; beforeInvocation:是否在方法执行前就清空,默认为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,默认情况下,如果方法执行抛出异常,则不会清空缓存
可扩展支持:Spring注解cache能够满足一般应用对缓存的需求,但随着应用服务的复杂化,大并发高可用性能要求下,需要进行一定的扩展,这时对其自身集成的缓存方案可能不太适用,该怎么办?Spring预先有考虑到这点,那么怎样利用Spring提供的扩展点实现我们自己的缓存,且在不改变原来已有代码的情况下进行扩展?是否在方法执行前就清空,默认为false,如果指定为true,则在方法还没有执行的时候就清空缓存,默认情况下,如果方法执行抛出异常,则不会清空缓存。
这基本能够满足一般应用对缓存的需求,但现实总是很复杂,当你的用户量上去或者性能跟不上,总需要进行扩展,这个时候你或许对其提供的内存缓存不满意了,因为其不支持高可用性,也不具备持久化数据能力,这个时候,你就需要自定义你的缓存方案了,还好,Spring也想到了这一点。
我们先不考虑如何持久化缓存,毕竟这种第三方的实现方案很多,我们要考虑的是,怎么利用Spring提供的扩展点实现我们自己的缓存,且在不改原来已有代码的情况下进行扩展。这需要简单的三步骤,首先需要提供一个CacheManager接口的实现(继承至AbstractCacheManager),管理自身的cache实例;其次,实现自己的cache实例MyCache(继承至Cache),在这里面引入我们需要的第三方cache或自定义cache;最后就是对配置项进行声明,将MyCache实例注入CacheManager进行统一管理。
酒店商家端自定义注解缓存
注解缓存的使用,可以有效增强应用代码的可读性,同时统一管理缓存,提供较好的可扩展性,为此,酒店商家端在Spring注解缓存基础上,自定义了适合自身业务特性的注解缓存。
主要使用两个标签,即@HotelCacheable、@HotelCacheEvict,其作用和配置方法见表2。
表2
标签类型 作用 主要配置参数说明 @HotelCacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 domain:作用域,针对集合场景,解决批量更新问题; domainKey:作用域对应的缓存key; key:缓存对象key 前缀; fieldKey:缓存对象key,与前缀合并生成对象key; condition:缓存获取前置条件,支持spel语法; cacheCondition:缓存刷入前置条件,支持spel语法; expireTime:超时时间设置 @HotelCacheEvict 主要针对方法配置,能够根据一定的条件对缓存进行清空 同上
增加作用域的概念,解决商家信息变更下,多重重要信息实时更新的问题。
图13 域缓存处理图
如图13,按���的方案,当cache0发送变化时,为了保持信息的实时更新,需要手动删除cache1、cache2、cache3等相关处的缓存数据。增加域缓存概念,cache0、cache1、cache2、cache3是以账号ID为基础,相互存在影响约束的集合体,我们作为一个域集合,增加域缓存处理,当cache0发送变化时,整体的账号ID domain域已发生更新,自动影响cache1、cache2、cache3等处的缓存数据。将相关联逻辑缓存统一化,有效提升代码可读性,同时更好服务业务,账号重点信息能够实时变更刷新,相关服务响应速度提升。
另外,增加了cacheCondition缓存刷入前置判断,有效解决商家业务多重外部依赖场景下,业务降级有损服务下,业务数据一致性保证,不因为缓存的增加影响业务的准确性;自定义CacheManager缓存管理器,可以有效兼容公共基础组件Medis、Cellar相关服务,在对应用程序不做改动的情况下,有效切换缓存方式;同时,统一的缓存服务AOP入口,结合接入Mtconfig统一配置管理,对应用内缓存做好降级准备,一键关闭缓存。几点建议:
上面介绍过Spring Cache的原理是基于动态生成的proxy代理机制来进行切面处理,关键点是对象的引用问题,如果对象的方法是类里面的内部调用(this引用)而不是外部引用的场景下,会导致proxy失败,那么我们所做的缓存切面处理也就失效了。因此,应避免已注解缓存的方法在类里面的内部调用。
使用的key约束,缓存的key应尽量使用简单的可区别的元素,如ID、名称等,不能使用list等容器的值,或者使用整体model对象的值。非public方法无法使用注解缓存实现。
总之,注释驱动的Spring Cache能够极大的减少我们编写常见缓存的代码量,通过少量的注释标签和配置文件,即可达到使代码具备缓存的能力,且具备很好的灵活性和扩展性。但是我们也应该看到,Spring Cache由于基于Spring AOP技术,尤其是动态的proxy技术,导致其不能很好的支持方法的内部调用或者非public方法的缓存设置,当然这些都是可以解决的问题。
作者简介
明辉,美团点评酒旅事业群酒店住宿研发团队B端商家业务平台负责人,主导构建商家业务平台系统,支撑美团点评酒店住宿业务的飞速发展需求。曾任职于联想集团、百度。
1 note
·
View note