打造用户喜爱甚至欲罢不能的用户体验,通常意味着你需要给予用户他们想要的东西,还要给他们一些他们意料之外但是又非常需要的东西,并且平衡两者的比例。那些大家耳熟能详的用户体验设计的技巧和规则能让你搞定绝大多数的用户体验设计的问题,但是还有一些容易忽略、处于盲点的设计技巧,能帮你做好另一部分的用户体验设计。1、专注于20%你可曾听说过80/20法则?简而言之,80%的用户通常只会使用你网站内容和功能的20%,绝大多数的用户只是在扫视网站内容,并且只会挑选真正感兴趣的浏览。同时这也意味着,剩余的20%就相当重要了。大多数的点击将源自于这一区域,它也是近乎完美的内容和互动区域。你可以借助数据分析来决定哪个部分是整个网站最被关注的那20%。对于刚刚上线的网站,这个数据搜集的过程可能长达好几周,随后再进行调整。当然,你也可以通过引导将用户引流到你希望用户去的那20%的区域。借助视觉引导和行动召唤设计来引流,用有趣和有意思的设计来营造令人有兴趣的区域,让他们乐于点击,从而达到目的。2、架构式的思维设计一个网站和搭建一所房子其实差不多。首先,它需要一个坚实的基础,然后是能够承载所有内容的框架,做好后要好好装饰一下。同样的思维模式可以套用在网站的设计上。更重要的是,你并不需要为此创造出过去从未在框架中出现过的东西,换言之,框架内的素材、组件和我们常见的并无二致。就像导航设计模式一样,现在的设计都趋于一致,因为这样的导航好用。当网站的整体结构搭建起来之后,可以将相同的思路套用到内容体系的构建上来。主要的正文内容是整个网站内容的基础,辅以吸引人的标题、图片,配合上其他的次要元素,整个内容体系可以很快搭建起来。3、不要要求太多现如今的世界可以说是由数据所驱动,越来越多的应用开始要求用户注册,要用户提供更多的权限,更多的信息,以期为用户提供更加个性化的体验。但是从体验的角度上来看,要尽量规避这一点。站在用户的角度稍加思考你就明白了。找到一个令你感兴趣的炫酷网站,如果要深入了解更多内容,就需要内容,而如果要注册的话,网站却需要你提供下面10个类型的信息:姓名、邮件、国家、地区、城市、电话、Twitter权限、个人网站、工作职位、以及如何发现这个网站的。那么接下来,你会怎么做?绝大多数的用户会直接转身离开,这个注册信息填写起来太费劲,体验太差了。那么,如果你当你点击注册的时候,只需要一键从Facebook或者twitter授权即可,那么你会不会立刻点击呢?至少从目前已有的数据来看,绝大多数的用户会这么选择。4、令人愉悦的微交互可能很多时候你都没注意到,你和各种微交互进行的互动一直都存在。·谷歌日历弹出框提示你每周例会要开始了·短信提醒·午睡的闹钟·微博上的新粉丝和转发提醒这样的例子我们可以连续不断地说上一个小时。这些带有微交互的提醒和动作会推动用户进行下一步操作,带来愉悦的体验,它们不能设计得非常醒目,但是又需要适当地吸引用户注意。这些有趣的微交互的加入让用户从中获得好处。而你需要思考的是,有哪些东西是你的网站或者APP当中,用户想要立刻知道和获得、想被提醒的?5、甚至幼儿都能轻松使用如果要给小孩子设计产品,那么它应该是什么样子?你可能会更专注于色彩的使用,让每一个区块都可以轻松点击,明显的标签,加上拼图式的连接方式。所以,当我们在设计网站或者APP的时候,我们会说这个产品要足够易用,那么怎么才算得上“足够”呢?让小孩子都可以轻松使用,这就叫足够易用。换句话说,即使是不经常使用网站和APP的成人,也不会存在明显的使用障碍。超大的设计元素和标签是设计的关键因素。这些视觉线索是帮助用户引导用户的核心,是整体体验设计中最重要的部分。大胆的色彩选择,会鼓励用户点击和探索。如果它足够易用,用户会继续尝试使用和探索。而难于理解操作不便的导航自然会被用户所嫌弃。如果网站包含太过复杂的媒体和内容,那么不妨从一个设计简单的首页开始,几个简单的导航点击将用户引导到对应的位置。在深入到更复杂的页面之前,用梳理清晰、简单明了的分页让用户感到舒适,这是带来好的浏览体验的不错解决方案。6、轻拍(Tap)还是点击(Click)?这一点要说的并不是设计问题,而是一个常见的开发代码的问题。虽然Tap和Click两者都能在点击的时候触发,但是在移动端网页上使用Click事件的时候,会有200ms到300ms的延迟,所以在移动端上最好替换为Tap事件。在进行响应式网页设计的时候,一个事件从头用到尾的错误出现得很普遍,但是有太多的移动端网页在这样的设计下会有明显延迟,更恶劣的情况是无法识别,这样会直接损害到整个页面的易用性和功能本身。此外,移动端上使用Tap事件的时候,它所对应的按钮应当相对更大一些,便于小屏上进行交互。7、像用户一样思考我们一直在说:“像用户一样思考”。但是实际的情况往往是,我们很难走出设计者和开发者的思维方式,因为我们的思维方式压根就和用户不同,面对每一个交互、每一个元素的下意识反应是不一样的。所以,还是同设计圈、开发圈和产品圈以外的人去聊聊吧,看看他们对于网站和APP的真实反应到底是怎样的。你可能会在观察中发现,他们对于产品、对于交互、对于界面的反应和你的预期完全不同。将用户的真实反馈记录下来,反馈给项目组,这样可以帮你打造更好的用户体验,创造更优秀的产品。以上就是对如何提升网站用户体验及可能你从未注意过的7个用户体验设计细节介绍,更多内容请继续关注站三界导航!
前言随着大数据时代的到来,机器学习成为解决问题的一种重要且关键的工具。不管是工业界还是学术界,机器学习都是一个炙手可热的方向,但是学术界和工业界对机器学习的研究各有侧重,学术界侧重于对机器学习理论的研究,工业界侧重于如何用机器学习来解决实际问题。我们结合美团在机器学习上的实践,进行一个实战(InAction)系列的介绍(带“机器学习InAction系列”标签的文章),介绍机器学习在解决工业界问题的实战中所需的基本技术、经验和技巧。本文主要结合实际问题,概要地介绍机器学习解决实际问题的整个流程,包括对问题建模、准备训练数据、抽取特征、训练模型和优化模型等关键环节;另外几篇则会对这些关键环节进行更深入地介绍。下文分为1)机器学习的概述,2)对问题建模,3)准备训练数据,4)抽取特征,5)训练模型,6)优化模型,7)总结共7个章节进行介绍。机器学习的概述:###什么是机器学习?随着机器学习在实际工业领域中不断获得应用,这个词已经被赋予了各种不同含义。在本文中的“机器学习”含义与wikipedia上的解释比较契合,如下:Machinelearningisascientificdisciplinethatdealswiththeconstructionandstudyofalgorithmsthatcanlearnfromdata.机器学习可以分为无监督学习(unsupervisedlearning)和有监督学习(supervisedlearning),在工业界中,有监督学习是更常见和更有价值的方式,下文中主要以这种方式展开介绍。如下图中所示,有监督的机器学习在解决实际问题时,有两个流程,一个是离线训练流程(蓝色箭头),包含数据筛选和清洗、特征抽取、模型训练和优化模型等环节;另一个流程则是应用流程(绿色箭头),对需要预估的数据,抽取特征,应用离线训练得到的模型进行预估,获得预估值作用在实际产品中。在这两个流程中,离线训练是最有技术挑战的工作(在线预估流程很多工作可以复用离线训练流程的工作),所以下文主要介绍离线训练流程。###什么是模型(model)?模型,是机器学习中的一个重要概念,简单的讲,指特征空间到输出空间的映射;一般由模型的假设函数和参数w组成(下面公式就是LogisticRegression模型的一种表达,在训练模型的章节做稍详细的解释);一个模型的假设空间(hypothesisspace),指给定模型所有可能w对应的输出空间组成的集合。工业界常用的模型有LogisticRegression(简称LR)、GradientBoostingDecisionTree(简称GBDT)、SupportVectorMachine(简称SVM)、DeepNeuralNetwork(简称DNN)等。模型训练就是基于训练数据,获得一组参数w,使得特定目标最优,即获得了特征空间到输出空间的最优映射,具体怎么实现,见训练模型章节。###为什么要用机器学习解决问题?目前处于大数据时代,到处都有成T成P的数据,简单规则处理难以发挥这些数据的价值;廉价的高性能计算,使得基于大规模数据的学习时间和代价降低;廉价的大规模存储,使得能够更快地和代价更小地处理大规模数据;存在大量高价值的问题,使得花大量精力用机器学习解决问题后,能获得丰厚收益。###机器学习应该用于解决什么问题?目标问题需要价值巨大,因为机器学习解决问题有一定的代价;目标问题有大量数据可用,有大量数据才能使机器学习比较好地解决问题(相对于简单规则或人工);目标问题由多种因素(特征)决定,机器学习解决问题的优势才能体现(相对于简单规则或人工);目标问题需要持续优化,因为机器学习可以基于数据自我学习和迭代,持续地发挥价值。对问题建模本文以DEAL(团购单)交易额预估问题为例(就是预估一个给定DEAL一段时间内卖了多少钱),介绍使用机器学习如何解决问题。首先需要:收集问题的资料,理解问题,成为这个问题的专家;拆解问题,简化问题,将问题转化机器可预估的问题。深入理解和分析DEAL交易额后,可以将它分解为如下图的几个问题:###单个模型?多个模型?如何来选择?按照上图进行拆解后,预估DEAL交易额就有2种可能模式,一种是直接预估交易额;另一种是预估各子问题,如建立一个用户数模型和建立一个访购率模型(访问这个DEAL的用户会购买的单子数),再基于这些子问题的预估值计算交易额。不同方式有不同优缺点,具体如下:选择哪种模式?1)问题可预估的难度,难度大,则考虑用多模型;2)问题本身的重要性,问题很重要,则考虑用多模型;3)多个模型的关系是否明确,关系明确,则可以用多模型。如果采用多模型,如何融合?可以根据问题的特点和要求进行线性融合,或进行复杂的融合。以本文问题为例,至少可以有如下两种:###模型选择对于DEAL交易额这个问题,我们认为直接预估难度很大,希望拆成子问题进行预估,即多模型模式。那样就需要建立用户数模型和访购率模型,因为机器学习解决问题的方式类似,下文只以访购率模型为例。要解决访购率问题,首先要选择模型,我们有如下的一些考虑:主要考虑1)选择与业务目标一致的模型;2)选择与训练数据和特征相符的模型。训练数据少,HighLevel特征多,则使用“复杂”的非线性模型(流行的GBDT、RandomForest等);训练数据很大量,LowLevel特征多,则使用“简单”的线性模型(流行的LR、Linear-SVM等)。补充考虑1)当前模型是否被工业界广泛使用;2)当前模型是否有比较成熟的开源工具包(公司内或公司外);3)当前工具包能够的处理数据量能否满足要求;4)自己对当前模型理论是否了解,是否之前用过该模型解决问题。为实际问题选择模型,需要转化问题的业务目标为模型评价目标,转化模型评价目标为模型优化目标;根据业务的不同目标,选择合适的模型,具体关系如下:通常来讲,预估真实数值(回归)、大小顺序(排序)、目标所在的正确区间(分类)的难度从大到小,根据应用所需,尽可能选择难度小的目标进行。对于访购率预估的应用目标来说,我们至少需要知道大小顺序或真实数值,所以我们可以选择AreaUnderCurve(AUC)或MeanAbsoluteError(MAE)作为评估目标,以Maximumlikelihood为模型损失函数(即优化目标)。综上所述,我们选择spark版本GBDT或LR,主要基于如下考虑:1)可以解决排序或回归问题;2)我们自己实现了算法,经常使用,效果很好;3)支持海量数据;4)工业界广泛使用。准备训练数据深入理解问题,针对问题选择了相应的模型后,接下来则需要准备数据;数据是机器学习解决问题的根本,数据选择不对,则问题不可能被解决,所以准备训练数据需要格外的小心和注意:###注意点:待解决问题的数据本身的分布尽量一致;训练集/测试集分布与线上预测环境的数据分布尽可能一致,这里的分布是指(x,y)的分布,不仅仅是y的分布;y数据噪音尽可能小,尽量剔除y有噪音的数据;非必要不做采样,采样常常可能使实际数据分布发生变化,但是如果数据太大无法训练或者正负比例严重失调(如超过100:1),则需要采样解决。###常见问题及解决办法待解决问题的数据分布不一致:1)访购率问题中DEAL数据可能差异很大,如美食DEAL和酒店DEAL的影响因素或表现很不一致,需要做特别处理;要么对数据提前归一化,要么将分布不一致因素作为特征,要么对各类别DEAL单独训练模型。数据分布变化了:1)用半年前的数据训练模型,用来预测当前数据,因为数据分布随着时间可能变化了,效果可能很差。尽量用近期的数据训练,来预测当前数据,历史的数据可以做降权用到模型,或做transferlearning。y数据有噪音:1)在建立CTR模型时,将用户没有看到的Item作为负例,这些Item是因为用户没有看到才没有被点击,不一定是用户不喜欢而没有被点击,所以这些Item是有噪音的。可以采用一些简单规则,剔除这些噪音负例,如采用skip-above思想,即用户点过的Item之上,没有点过的Item作为负例(假设用户是从上往下浏览Item)。采样方法有偏,没有覆盖整个集合:1)访购率问题中,如果只取只有一个门店的DEAL进行预估,则对于多门店的DEAL无法很好预估。应该保证一个门店的和多个门店的DEAL数据都有;2)无客观数据的二分类问题,用规则来获得正/负例,规则对正/负例的覆盖不全面。应该随机抽样数据,进行人工标注,以确保抽样数据和实际数据分布一致。###访购率问题的训练数据收集N个月的DEAL数据(x)及相应访购率(y);收集最近N个月,剔除节假日等非常规时间(保持分布一致);只收集在线时长>T且访问用户数>U的DEAL(减少y的噪音);考虑DEAL销量生命周期(保持分布一致);考虑不同城市、不同商圈、不同品类的差别(保持分布一致)。抽取特征完成数据筛选和清洗后,就需要对数据抽取特征,就是完成输入空间到特征空间的转换(见下图)。针对线性模型或非线性模型需要进行不同特征抽取,线性模型需要更多特征抽取工作和技巧,而非线性模型对特征抽取要求相对较低。通常,特征可以分为HighLevel与LowLevel,HighLevel指含义比较泛的特征,LowLevel指含义比较特定的特征,举例来说: DEALA1属于POIA,人均50以下,访购率高; DEALA2属于POIA,人均50以上,访购率高; DEALB1属于POIB,人均50以下,访购率高; DEALB2属于POIB,人均50以上,访购率底;基于上面的数据,可以抽到两种特征,POI(门店)或人均消费;POI特征则是LowLevel特征,人均消费则是HighLevel特征;假设模型通过学习,获得如下预估:如果DEALx属于POIA(LowLevelfeature),访购率高;如果DEALx人均50以下(HighLevelfeature),访购率高。所以,总体上,LowLevel比较有针对性,单个特征覆盖面小(含有这个特征的数据不多),特征数量(维度)很大。HighLevel比较泛化,单个特征覆盖面大(含有这个特征的数据很多),特征数量(维度)不大。长尾样本的预测值主要受HighLevel特征影响。高频样本的预测值主要受LowLevel特征影响。对于访购率问题,有大量的HighLevel或LowLevel的特征,其中一些展示在下图:非线性模型的特征1)可以主要使用HighLevel特征,因为计算复杂度大,所以特征维度不宜太高;2)通过HighLevel非线性映射可以比较好地拟合目标。线性模型的特征1)特征体系要尽可能全面,HighLevel和LowLevel都要有;2)可以将HighLevel转换LowLevel,以提升模型的拟合能力。###特征归一化特征抽取后,如果不同特征的取值范围相差很大,最好对特征进行归一化,以取得更好的效果,常见的归一化方式如下:Rescaling:归一化到[0,1]或[-1,1],用类似方式:Standardization:设为x分布的均值,为x分布的标准差;Scalingtounitlength:归一化到单位长度向量###特征选择特征抽取和归一化之后,如果发现特征太多,导致模型无法训练,或很容易导致模型过拟合,则需要对特征进行选择,挑选有价值的特征。Filter:假设特征子集对模型预估的影响互相独立,选择一个特征子集,分析该子集和数据Label的关系,如果存在某种正相关,则认为该特征子集有效。衡量特征子集和数据Label关系的算法有很多,如Chi-square,InformationGain。Wrapper:选择一个特征子集加入原有特征集合,用模型进行训练,比较子集加入前后的效果,如果效果变好,则认为该特征子集有效,否则认为无效。Embedded:将特征选择和模型训练结合起来,如在损失函数中加入L1Norm,L2Norm。训练模型完成特征抽取和处理后,就可以开始模型训练了,下文以简单且常用的LogisticRegression模型(下称LR模型)为例,进行简单介绍。设有m个(x,y)训练数据,其中x为特征向量,y为label,;w为模型中参数向量,即模型训练中需要学习的对象。所谓训练模型,就是选定假说函数和损失函数,基于已有训练数据(x,y),不断调整w,使得损失函数最优,相应的w就是最终学习结果,也就得到相应的模型。###模型函数1)假说函数,即假设x和y存在一种函数关系:2)损失函数,基于上述假设函数,构建模型损失函数(优化目标),在LR中通常以(x,y)的最大似然估计为目标:###优化算法梯度下降(GradientDescent)即w沿着损失函数的负梯度方向进行调整,示意图见下图,的梯度即一阶导数(见下式),梯度下降有多种类型,如随机梯度下降或批量梯度下降。随机梯度下降(StochasticGradientDescent),每一步随机选择一个样本,计算相应的梯度,并完成w的更新,如下式,批量梯度下降(BatchGradientDescent),每一步都计算训练数据中的所有样本对应的梯度,w沿着这个梯度方向迭代,即牛顿法(Newton’sMethod)牛顿法的基本思想是在极小点附近通过对目标函数做二阶Taylor展开,进而找到L(w)的极小点的估计值。形象地讲,在wk处做切线,该切线与L(w)=0的交点即为下一个迭代点wk+1(示意图如下)。w的更新公式如下,其中目标函数的二阶偏导数,即为大名鼎鼎的Hessian矩阵。拟牛顿法(Quasi-NewtonMethods):计算目标函数的二阶偏导数,难度较大,更为复杂的是目标函数的Hessian矩阵无法保持正定;不用二阶偏导数而构造出可以近似Hessian矩阵的逆的正定对称阵,从而在"拟牛顿"的条件下优化目标函数。BFGS:使用BFGS公式对H(w)进行近似,内存中需要放H(w),内存需要O(m2)级别;L-BFGS:存储有限次数(如k次)的更新矩阵,用这些更新矩阵生成新的H(w),内存降至O(m)级别;OWLQN:如果在目标函数中引入L1正则化,需要引入虚梯度来解决目标函数不可导问题,OWLQN就是用来解决这个问题。CoordinateDescent对于w,每次迭代,固定其他维度不变,只对其一个维度进行搜索,确定最优下降方向(示意图如下),公式表达如下:优化模型经过上文提到的数据筛选和清洗、特征设计和选择、模型训练,就得到了一个模型,但是如果发现效果不好?怎么办?【首先】反思目标是否可预估,数据和特征是否存在bug。【然后】分析一下模型是Overfitting还是Underfitting,从数据、特征和模型等环节做针对性优化。###Underfitting&Overfitting所谓Underfitting,即模型没有学到数据内在关系,如下图左一所示,产生分类面不能很好的区分X和O两类数据;产生的深层原因,就是模型假设空间太小或者模型假设空间偏离。所谓Overfitting,即模型过渡拟合了训练数据的内在关系,如下图右一所示,产生分类面过好地区分X和O两类数据,而真实分类面可能并不是这样,以至于在非训练数据上表现不好;产生的深层原因,是巨大的模型假设空间与稀疏的数据之间的矛盾。在实战中,可以基于模型在训练集和测试集上的表现来确定当前模型到底是Underfitting还是Overfitting,判断方式如下表:###怎么解决Underfitting和Overfitting问题?总结综上所述,机器学习解决问题涉及到问题建模、准备训练数据、抽取特征、训练模型和优化模型等关键环节,有如下要点:理解业务,分解业务目标,规划模型可预估的路线图。数据:y数据尽可能真实客观;训练集/测试集分布与线上应用环境的数据分布尽可能一致。特征:利用DomainKnowledge进行特征抽取和选择;针对不同类型的模型设计不同的特征。模型:针对不同业务目标、不同数据和特征,选择不同的模型;如果模型不符合预期,一定检查一下数据、特征、模型等处理环节是否有bug;考虑模型Underfitting和Qverfitting,针对性地优化。
#摘要序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中;另一方面,它们会以其他更容易理解的概念出现,例如加密、持久化。然而,序列化和反序列化的选型却是系统设计或重构一个重要的环节,在分布式、大数据量系统设计里面更为显著。恰当的序列化协议不仅可以提高系统的通用性、强健性、安全性、优化系统性能,而且会让系统更加易于调试、便于扩展。本文从多个角度去分析和讲解“序列化和反序列化”,并对比了当前流行的几种序列化协议,期望对读者做序列化选型有所帮助。简介文章作者服务于美团推荐与个性化组,该组致力于为美团用户提供每天billion级别的高质量个性化推荐以及排序服务。从Terabyte级别的用户行为数据,到Gigabyte级别的Deal/Poi数据;从对实时性要求毫秒以内的用户实时地理位置数据,到定期后台job数据,推荐与重排序系统需要多种类型的数据服务。推荐与重排序系统客户包括各种内部服务、美团客户端、美团网站。为了提供高质量的数据服务,为了实现与上下游各系统进行良好的对接,序列化和反序列化的选型往往是我们做系统设计的一个重要考虑因素。本文内容按如下方式组织:第一部分给出了序列化和反序列化的定义,以及其在通讯协议中所处的位置。第二部分从使用者的角度探讨了序列化协议的一些特性。第三部分描述在具体的实施过程中典型的序列化组件,并与数据库组建进行了类比。第四部分分别讲解了目前常见的几种序列化协议的特性,应用场景,并对相关组件进行举例。最后一部分,基于各种协议的特性,以及相关benchmark数据,给出了作者的技术选型建议。#一、定义以及相关概念互联网的产生带来了机器间通讯的需求,而互联通讯的双方需要采用约定的协议,序列化和反序列化属于通讯协议的一部分。通讯协议往往采用分层模型,不同模型每层的功能定义以及颗粒度不同,例如:TCP/IP协议是一个四层协议,而OSI模型却是七层协议模型。在OSI七层协议模型中展现层(PresentationLayer)的主要功能是把应用层的对象转换成一段连续的二进制串,或者反过来,把二进制串转换成应用层的对象--这两个功能就是序列化和反序列化。一般而言,TCP/IP协议的应用层对应与OSI七层协议模型的应用层,展示层和会话层,所以序列化协议属于TCP/IP协议应用层的一部分。本文对序列化协议的讲解主要基于OSI七层协议模型。序列化:将数据结构或对象转换成二进制串的过程反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程数据结构、对象与二进制串不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。数据结构和对象:对于类似Java这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。在Java语言中最接近数据结构的概念,就是POJO(PlainOldJavaObject)或者Javabean--那些只有setter/getter方法的类。而在C++这种半面向对象的语言中,数据结构和struct对应,对象和class对应。二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C++语言具有内存操作符,所以二进制串的概念容易理解,例如,C++语言的字符串可以直接被传输层使用,因为其本质上就是以'\0'结尾的存储在内存中的二进制串。在Java语言里面,二进制串的概念容易和String混淆。实际上String是Java的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在Java里面所指的是byte[],byte是Java的8中原生数据类型之一(Primitivedatatypes)。#二、序列化协议特性每种序列化协议都有优点和缺点,它们在设计之初有自己独特的应用场景。在系统设计的过程中,需要考虑序列化需求的方方面面,综合对比各种序列化协议的特性,最终给出一个折衷的方案。通用性通用性有两个层面的意义:第一、技术层面,序列化协议是否支持跨平台、跨语言。如果不支持,在技术层面上的通用性就大大降低了。第二、流行程度,序列化和反序列化需要多方参与,很少人使用的协议往往意味着昂贵的学习成本;另一方面,流行度低的协议,往往缺乏稳定而成熟的跨语言、跨平台的公共包。强健性/鲁棒性以下两个方面的原因会导致协议不够强健:第一、成熟度不够,一个协议从制定到实施,到最后成熟往往是一个漫长的阶段。协议的强健性依赖于大量而全面的测试,对于致力于提供高质量服务的系统,采用处于测试阶段的序列化协议会带来很高的风险。第二、语言/平台的不公平性。为了支持跨语言、跨平台的功能,序列化协议的制定者需要做大量的工作;但是,当所支持的语言或者平台之间存在难以调和的特性的时候,协议制定者需要做一个艰难的决定--支持更多人使用的语言/平台,亦或支持更多的语言/平台而放弃某个特性。当协议的制定者决定为某种语言或平台提供更多支持的时候,对于使用者而言,协议的强健性就被牺牲了。可调试性/可读性序列化和反序列化的数据正确性和业务正确性的调试往往需要很长的时间,良好的调试机制会大大提高开发效率。序列化后的二进制串往往不具备人眼可读性,为了验证序列化结果的正确性,写入方不得同时撰写反序列化程序,或提供一个查询平台--这比较费时;另一方面,如果读取方未能成功实现反序列化,这将给问题查找带来了很大的挑战--难以定位是由于自身的反序列化程序的bug所导致还是由于写入方序列化后的错误数据所导致。对于跨公司间的调试,由于以下原因,问题会显得更严重:第一、支持不到位,跨公司调试在问题出现后可能得不到及时的支持,这大大延长了调试周期。第二、访问限制,调试阶段的查询平台未必对外公开,这增加了读取方的验证难度。如果序列化后的数据人眼可读,这将大大提高调试效率,XML和JSON就具有人眼可读的优点。性能性能包括两个方面,时间复杂度和空间复杂度:第一、空间开销(Verbosity),序列化需要在原有的数据上加上描述字段,以为反序列化解析之用。如果序列化过程引入的额外开销过高,可能会导致过大的网络,磁盘等各方面的压力。对于海量分布式存储系统,数据量往往以TB为单位,巨大的的额外空间开销意味着高昂的成本。第二、时间开销(Complexity),复杂的序列化协议会导致较长的解析时间,这可能会使得序列化和反序列化阶段成为整个系统的瓶颈。可扩展性/兼容性移动互联时代,业务系统需求的更新周期变得更快,新的需求不断涌现,而老的系统还是需要继续维护。如果序列化协议具有良好的可扩展性,支持自动增加新的业务字段,而不影响老的服务,这将大大提供系统的灵活度。安全性/访问限制在序列化选型的过程中,安全性的考虑往往发生在跨局域网访问的场景。当通讯发生在公司之间或者跨机房的时候,出于安全的考虑,对于跨局域网的访问往往被限制为基于HTTP/HTTPS的80和443端口。如果使用的序列化协议没有兼容而成熟的HTTP传输层框架支持,可能会导致以下三种结果之一:第一、因为访问限制而降低服务可用性。第二、被迫重新实现安全协议而导致实施成本大大提高。第三、开放更多的防火墙端口和协议访问,而牺牲安全性。#三、序列化和反序列化的组件典型的序列化和反序列化过程往往需要如下组件:IDL(Interfacedescriptionlanguage)文件:参与通讯的各方需要对通讯的内容需要做相关的约定(Specifications)。为了建立一个与语言和平台无关的约定,这个约定需要采用与具体开发语言、平台无关的语言来进行描述。这种语言被称为接口描述语言(IDL),采用IDL撰写的协议约定称之为IDL文件。IDLCompiler:IDL文件中约定的内容为了在各语言和平台可见,需要有一个编译器,将IDL文件转换成各语言对应的动态库。Stub/SkeletonLib:负责序列化和反序列化的工作代码。Stub是一段部署在分布式系统客户端的代码,一方面接收应用层的参数,并对其序列化后通过底层协议栈发送到服务端,另一方面接收服务端序列化后的结果数据,反序列化后交给客户端应用层;Skeleton部署在服务端,其功能与Stub相反,从传输层接收序列化参数,反序列化后交给服务端应用层,并将应用层的执行结果序列化后最终传送给客户端Stub。Client/Server:指的是应用层程序代码,他们面对的是IDL所生存的特定语言的class或struct。底层协议栈和互联网:序列化之后的数据通过底层的传输层、网络层、链路层以及物理层协议转换成数字信号在互联网中传递。序列化组件与数据库访问组件的对比数据库访问对于很多工程师来说相对熟悉,所用到的组件也相对容易理解。下表类比了序列化过程中用到的部分组件和数据库访问组件的对应关系,以便于大家更好的把握序列化相关组件的概念。#四、几种常见的序列化和反序列化协议互联网早期的序列化协议主要有COM和CORBA。COM主要用于Windows平台,并没有真正实现跨平台,另外COM的序列化的原理利用了编译器中虚表,使得其学习成本巨大(想一下这个场景,工程师需要是简单的序列化协议,但却要先掌握语言编译器)。由于序列化的数据与编译器紧耦合,扩展属性非常麻烦。CORBA是早期比较好的实现了跨平台,跨语言的序列化协议。COBRA的主要问题是参与方过多带来的版本过多,版本之间兼容性较差,以及使用复杂晦涩。这些政治经济,技术实现以及早期设计不成熟的问题,最终导致COBRA的渐渐消亡。J2SE1.3之后的版本提供了基于CORBA协议的RMI-IIOP技术,这使得Java开发者可以采用纯粹的Java语言进行CORBA的开发。这里主要介绍和对比几种当下比较流行的序列化协议,包括XML、JSON、Protobuf、Thrift和Avro。一个例子如前所述,序列化和反序列化的出现往往晦涩而隐蔽,与其他概念之间往往相互包容。为了更好了让大家理解序列化和反序列化的相关概念在每种协议里面的具体实现,我们将一个例子穿插在各种序列化协议讲解中。在该例子中,我们希望将一个用户信息在多个系统里面进行传递;在应用层,如果采用Java语言,所面对的类对象如下所示:JavaCode复制内容到剪贴板class Address { private String city; private String postcode; private String street; } public class UserInfo { private Integer userid; private String name; private List address; } XML&SOAPXML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。XML历史悠久,其1.0版本早在1998年就形成标准,并被广泛使用至今。XML的最初产生目标是对互联网文档(Document)进行标记,所以它的设计理念中就包含了对于人和机器都具备可读性。但是,当这种标记文档的设计被用来序列化对象的时候,就显得冗长而复杂(VerboseandComplex)。XML本质上是一种描述语言,并且具有自我描述(Self-describing)的属性,所以XML自身就被用于XML序列化的IDL。标准的XML描述格式有两种:DTD(DocumentTypeDefinition)和XSD(XMLSchemaDefinition)。作为一种人眼可读(Human-readable)的描述语言,XML被广泛使用在配置文件中,例如O/Rmapping、SpringBeanConfigurationFile等。SOAP(SimpleObjectAccessprotocol)是一种被广泛应用的,基于XML为序列化和反序列化协议的结构化消息传递协议。SOAP在互联网影响如此大,以至于我们给基于SOAP的解决方案一个特定的名称--Webservice。SOAP虽然可以支持多种传输层协议,不过SOAP最常见的使用方式还是XML+HTTP。SOAP协议的主要接口描述语言(IDL)是WSDL(WebServiceDescriptionLanguage)。SOAP具有安全、可扩展、跨语言、跨平台并支持多种传输层协议。如果不考虑跨平台和跨语言的需求,XML的在某些语言里面具有非常简单易用的序列化使用方法,无需IDL文件和第三方编译器,例如Java+XStream。自我描述与递归SOAP是一种采用XML进行序列化和反序列化的协议,它的IDL是WSDL.而WSDL的描述文件是XSD,而XSD自身是一种XML文件。这里产生了一种有趣的在数学上称之为“递归”的问题,这种现象往往发生在一些具有自我属性(Self-description)的事物上。IDL文件举例采用WSDL描述上述用户基本信息的例子如下: 复制代码代码如下:典型应用场景和非应用场景SOAP协议具有广泛的群众基础,基于HTTP的传输协议使得其在穿越防火墙时具有良好安全特性,XML所具有的人眼可读(Human-readable)特性使得其具有出众的可调试性,互联网带宽的日益剧增也大大弥补了其空间开销大(Verbose)的缺点。对于在公司之间传输数据量相对小或者实时性要求相对低(例如秒级别)的服务是一个好的选择。由于XML的额外空间开销大,序列化之后的数据量剧增,对于数据量巨大序列持久化应用常景,这意味着巨大的内存和磁盘开销,不太适合XML。另外,XML的序列化和反序列化的空间和时间开销都比较大,对于对性能要求在ms级别的服务,不推荐使用。WSDL虽然具备了描述对象的能力,SOAP的S代表的也是simple,但是SOAP的使用绝对不简单。对于习惯于面向对象编程的用户,WSDL文件不直观。JSON(JavascriptObjectNotation)JSON起源于弱类型语言Javascript,它的产生来自于一种称之为"Associativearray"的概念,其本质是就是采用"Attribute-value"的方式来描述对象。实际上在Javascript和PHP等弱类型语言中,类的描述方式就是Associativearray。JSON的如下优点,使得它快速成为最广泛使用的序列化协议之一:1、这种Associativearray格式非常符合工程师对对象的理解。2、它保持了XML的人眼可读(Human-readable)的优点。3、相对于XML而言,序列化后的数据更加简洁。来自于的以下链接的研究表明:XML所产生序列化之后文件的大小接近JSON的两倍。http://www.codeproject.com/Articles/604720/JSON-vs-XML-Some-hard-numbers-about-verbosity4、它具备Javascript的先天性支持,所以被广泛应用于Webbrowser的应用常景中,是Ajax的事实标准协议。5、与XML相比,其协议比较简单,解析速度比较快。6、松散的Associativearray使得其具有良好的可扩展性和兼容性。IDL悖论JSON实在是太简单了,或者说太像各种语言里面的类了,所以采用JSON进行序列化不需要IDL。这实在是太神奇了,存在一种天然的序列化协议,自身就实现了跨语言和跨平台。然而事实没有那么神奇,之所以产生这种假象,来自于两个原因:第一、Associativearray在弱类型语言里面就是类的概念,在PHP和Javascript里面Associativearray就是其class的实际实现方式,所以在这些弱类型语言里面,JSON得到了非常良好的支持。第二、IDL的目的是撰写IDL文件,而IDL文件被IDLCompiler编译后能够产生一些代码(Stub/Skeleton),而这些代码是真正负责相应的序列化和反序列化工作的组件。但是由于Associativearray和一般语言里面的class太像了,他们之间形成了一一对应关系,这就使得我们可以采用一套标准的代码进行相应的转化。对于自身支持Associativearray的弱类型语言,语言自身就具备操作JSON序列化后的数据的能力;对于Java这强类型语言,可以采用反射的方式统一解决,例如Google提供的Gson。典型应用场景和非应用场景JSON在很多应用场景中可以替代XML,更简洁并且解析速度更快。典型应用场景包括:1、公司之间传输数据量相对小,实时性要求相对低(例如秒级别)的服务。2、基于Webbrowser的Ajax请求。3、由于JSON具有非常强的前后兼容性,对于接口经常发生变化,并对可调式性要求高的场景,例如Mobileapp与服务端的通讯。4、由于JSON的典型应用场景是JSON+HTTP,适合跨防火墙访问。总的来说,采用JSON进行序列化的额外空间开销比较大,对于大数据量服务或持久化,这意味着巨大的内存和磁盘开销,这种场景不适合。没有统一可用的IDL降低了对参与方的约束,实际操作中往往只能采用文档方式来进行约定,这可能会给调试带来一些不便,延长开发周期。由于JSON在一些语言中的序列化和反序列化需要采用反射机制,所以在性能要求为ms级别,不建议使用。IDL文件举例以下是UserInfo序列化之后的一个例子: 复制代码代码如下:{"userid":1,"name":"messi","address":[{"city":"北京","postcode":"1000000","street":"wangjingdonglu"}]}ThriftThrift是Facebook开源提供的一个高性能,轻量级RPC服务框架,其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。但是,Thrift并不仅仅是序列化协议,而是一个RPC框架。相对于JSON和XML而言,Thrift在空间开销和解析性能上有了比较大的提升,对于对性能要求比较高的分布式系统,它是一个优秀的RPC解决方案;但是由于Thrift的序列化被嵌入到Thrift框架里面,Thrift框架本身并没有透出序列化和反序列化接口,这导致其很难和其他传输层协议共同使用(例如HTTP)。典型应用场景和非应用场景对于需求为高性能,分布式的RPC服务,Thrift是一个优秀的解决方案。它支持众多语言和丰富的数据类型,并对于数据字段的增删具有较强的兼容性。所以非常适用于作为公司内部的面向服务构建(SOA)的标准RPC框架。不过Thrift的文档相对比较缺乏,目前使用的群众基础相对较少。另外由于其Server是基于自身的Socket服务,所以在跨防火墙访问时,安全是一个顾虑,所以在公司间进行通讯时需要谨慎。另外Thrift序列化之后的数据是Binary数组,不具有可读性,调试代码时相对困难。最后,由于Thrift的序列化和框架紧耦合,无法支持向持久层直接读写数据,所以不适合做数据持久化序列化协议。IDL文件举例 复制代码代码如下:structAddress{1:requiredstringcity;2:optionalstringpostcode;3:optionalstringstreet;}structUserInfo{1:requiredstringuserid;2:requiredi32name;3:optionallistaddress;}ProtobufProtobuf具备了优秀的序列化协议的所需的众多典型特征:1、标准的IDL和IDL编译器,这使得其对工程师非常友好。2、序列化数据非常简洁,紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。3、解析速度非常快,比对应的XML快约20-100倍。4、提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。Protobuf是一个纯粹的展示层协议,可以和各种传输层协议一起使用;Protobuf的文档也非常完善。但是由于Protobuf产生于Google,所以目前其仅仅支持Java、C++、Python三种语言。另外Protobuf支持的数据类型相对较少,不支持常量类型。由于其设计的理念是纯粹的展现层协议(PresentationLayer),目前并没有一个专门支持Protobuf的RPC框架。典型应用场景和非应用场景Protobuf具有广泛的用户基础,空间开销小以及高解析性能是其亮点,非常适合于公司内部的对性能要求高的RPC调用。由于Protobuf提供了标准的IDL以及对应的编译器,其IDL文件是参与各方的非常强的业务约束,另外,Protobuf与传输层无关,采用HTTP具有良好的跨防火墙的访问属性,所以Protobuf也适用于公司间对性能要求比较高的场景。由于其解析性能高,序列化后数据量相对少,非常适合应用层对象的持久化场景。它的主要问题在于其所支持的语言相对较少,另外由于没有绑定的标准底层传输层协议,在公司间进行传输层协议的调试工作相对麻烦。IDL文件举例 复制代码代码如下:messageAddress{requiredstringcity=1;optionalstringpostcode=2;optionalstringstreet=3;}messageUserInfo{requiredstringuserid=1;requiredstringname=2;repeatedAddressaddress=3;}AvroAvro的产生解决了JSON的冗长和没有IDL的问题,Avro属于ApacheHadoop的一个子项目。Avro提供两种序列化格式:JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美,JSON格式方便测试阶段的调试。Avro支持的数据类型非常丰富,包括C++语言里面的union类型。Avro支持JSON格式的IDL和类似于Thrift和Protobuf的IDL(实验阶段),这两者之间可以互转。Schema可以在传输数据的同时发送,加上JSON的自我描述属性,这使得Avro非常适合动态类型语言。Avro在做文件持久化的时候,一般会和Schema一起存储,所以Avro序列化文件自身具有自我描述属性,所以非常适合于做Hive、Pig和MapReduce的持久化数据格式。对于不同版本的Schema,在进行RPC调用的时候,服务端和客户端可以在握手阶段对Schema进行互相确认,大大提高了最终的数据解析速度。典型应用场景和非应用场景Avro解析性能高并且序列化之后的数据非常简洁,比较适合于高性能的序列化服务。由于Avro目前非JSON格式的IDL处于实验阶段,而JSON格式的IDL对于习惯于静态类型语言的工程师来说不直观。IDL文件举例 复制代码代码如下:protocolUserservice{recordAddress{stringcity;stringpostcode;stringstreet;}recordUserInfo{stringname;intuserid;arrayaddress=[];}}所对应的JSONSchema格式如下:JavaScriptCode复制内容到剪贴板{ "protocol" : "Userservice", "namespace" : "org.apache.avro.ipc.specific", "version" : "1.0.5", "types" : [ { "type" : "record", "name" : "Address", "fields" : [ { "name" : "city", "type" : "string" }, { "name" : "postcode", "type" : "string" }, { "name" : "street", "type" : "string" } ] }, { "type" : "record", "name" : "UserInfo", "fields" : [ { "name" : "name", "type" : "string" }, { "name" : "userid", "type" : "int" }, { "name" : "address", "type" : { "type" : "array", "items" : "Address" }, "default" : [ ] } ] } ], "messages" : { } } #五、Benchmark以及选型建议##Benchmark以下数据来自https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking解析性能序列化之空间开销从上图可得出如下结论:1、XML序列化(Xstream)无论在性能和简洁性上比较差。2、Thrift与Protobuf相比在时空开销方面都有一定的劣势。3、Protobuf和Avro在两方面表现都非常优越。选型建议以上描述的五种序列化和反序列化协议都各自具有相应的特点,适用于不同的场景:1、对于公司间的系统调用,如果性能要求在100ms以上的服务,基于XML的SOAP协议是一个值得考虑的方案。2、基于Webbrowser的Ajax,以及Mobileapp与服务端之间的通讯,JSON协议是首选。对于性能要求不太高,或者以动态类型语言为主,或者传输数据载荷很小的的运用场景,JSON也是非常不错的选择。3、对于调试环境比较恶劣的场景,采用JSON或XML能够极大的提高调试效率,降低系统开发成本。4、当对性能和简洁性有极高要求的场景,Protobuf,Thrift,Avro之间具有一定的竞争关系。5、对于T级别的数据的持久化应用场景,Protobuf和Avro是首要选择。如果持久化后的数据存储在Hadoop子项目里,Avro会是更好的选择。6、由于Avro的设计理念偏向于动态类型语言,对于动态语言为主的应用场景,Avro是更好的选择。7、对于持久层非Hadoop项目,以静态类型语言为主的应用场景,Protobuf会更符合静态类型语言工程师的开发习惯。8、如果需要提供一个完整的RPC解决方案,Thrift是一个好的选择。9、如果序列化之后需要支持不同的传输层协议,或者需要跨防火墙访问的高性能场景,Protobuf可以优先考虑。
eMarketer最近在美国市场所做的一项调查显示:70%的美国网民表示一个公司的网站是影响他们作出购买决策的主要因素。当然这并非美国市场的特例,在其他国家同样适用。 peitu1 网页的设计和功能列表理所当然成为了与受众粘性直接联系的因素。早前eMarketer的几项研究数据曾揭示了大部分用户在浏览网站时关注的一些网站项目,这里便借助这些研究为大家列出建网站时应首要考虑的问题以优化网站效果,当然这既要考虑网站设计元素,也要确保你的网站没有任何错误。 移动端设计优化 peitu2 如果你的网站移动友好度不是很高的话,很可能你将无法接触到相当一部分受众。而随着移动端用户的爆炸式增长,响应式设计将是大部分品牌的最佳选择。这种设计确保你的网站在移动设备端口得到恰当展示,并且能够跨越所有你能想得到的平台。此外,39.6%的移动端用户表示移动端网页浏览不顺畅成为了他们不在智能手机上完成购买的最主要原因。 当用户在移动端浏览网页的时候,他们需要专门为小屏设备设计的移动端页面。页面上的电话号码应该能够点击,这样用户便能够在几秒钟内实现与品牌的在线客服代表的无缝对接。 同时,类似配送信息或付款信息等格式化表格也应该进行移动端优化,这样用户便能方便输入相关信息,也不需要在输入过程中过分放大或缩小页面。任何影响用户移动端体验,妨碍用户与品牌联系的细节都将导致品牌丧失一次销售机会。因此,这种情况绝对不能发生! 重点关注内容 peitu4 关注网站内容建设不仅能够通过在网站中整合关键词来提升SEO效果,还有助于向用户证明你的价值。不管是通过博文还是首页和加载页面的信息展示,你应该尽可能多地向用户提供信息,告诉他们不管是你的产品还是服务都值得他们花时间去了解,花钱去体验。跟以前比起来,用户越来越依赖自己的研究,并且通过品牌来比较不同的产品。 peitu5 品牌应多花些时间来创造出与产品与服务相关的有价值的内容。有价值的内容对于用户来说格外重要,并且通常是将潜在消费者留在购买漏斗内的关键要素。一旦你能向用户提供足够的内容,你便能够利用其他营销技术,如电子邮件或付费广告等进行更密切的营销。内容通常是引起用户最初的品牌兴趣的因素,因此不是你想略过就能略过的。 测试,测试,再测试 在内容发布前需要进行大量的测试、校对工作以及整体预览以检查是否存在错误,因为网站上存在的错误将延展为品牌可信度的缺失。91%的美国网民在调查中指出“网站中包含错误”成为他们不信任品牌官网的最主要原因。 赶紧行动起来吧,用一双仔细的眼睛去检查网站的每一个部分以保证网站质量。 peitu6 有时候你仅仅改变了网站上某一区域不起作用的按钮却很可能反过来影响到另一个区域。因此,在发布前应确保每一个细节都处于最佳运行状态。 如果你向用户展示的是质量较低的网站,那么用户会合理地认为你的产品及服务肯定也不怎么样。 peitu7 如果你还没有安装网站分析的工具,那么你应该安装一个。因为你的网站一旦上线,这些工具将非常有用,你可以利用它们定期做一个全面的分析。如果你的网站的某些领域仍然存在较高的跳出率,也许这便是你检查的一个很好的突破口,你需要重新检查一下内容、功能或图像方面是否存在错误。 简单的页面导航 尽管设计美学对于建站来说极为重要,但功能性和实用性则更为宝贵。美国91%B2B的买家表示搜索和导航工具是供应商网站拥有的最重要的标志。 peitu9 页面上的导航必须简单易使用,同时能够反映出你希望用户在网站内实现的浏览路径。也许你不能完全控制用户在网站进行导航的顺序,但通过在导航指示背后进行一些推理便能够最大限度地控制用户的浏览行为。 如果你的网站导航设计比较复杂,用户难以使用的话,他们将离开你的页面去寻找更方便浏览的页面。设计网站的过程中应关注清晰度,而这些从受众的角度来说也是有意义的,而其它一些在你和你员工看来是第二天性的导航设计从网站新访客的角度来看可能并不那么重要。因此,在进行导航设计时,你的终极目标便是创建一个工具,而这个工具从访客角度来看也几乎属于第二天性。 例如,在导航设计领域内众所周知的“汉堡”菜单 peitu8 这种三线图标的表现形式通常能够引起用户的共鸣并得到广泛认可。 为下一次网站设计时刻准备着 peitu10 总而言之,在开始设计一个新网站前需要进行适当的规划,这样才能确定出最佳的策略以更好达成目标。通过识别对于品牌至关重要的功能、特征,以及关注整个决策制定过程中的数据将有助于形成网站设计
在美团商家数据中心(MDC),有超过100w的已校准审核的POI数据(我们一般将商家标示为POI,POI基础信息包括:门店名称、品类、电话、地址、坐标等)。如何使用这些已校准的POI数据,挖掘出有价值的信息,本文进行了一些尝试:利用机器学习方法,自动标注缺失品类的POI数据。例如,门店名称为“好再来牛肉拉面馆”的POI将自动标注“小吃”品类。机器学习解决问题的一般过程:本文将按照:1)特征表示;2)特征选择;3)基于NaiveBayes分类模型;4)分类预测,四个部分顺序展开。特征表示我们需要先将实际问题转换成计算机可识别的形式。对于POI而言,反应出POI品类的一个重要特征是POI门店名称,那么问题转换成了根据POI门店名称判别POI品类。POI名称字段属于文本特征,传统的文本表示方法是基于向量空间模型(VSM模型)[1]:空间向量模型需要一个“字典”,这个字典可以在样本中产生,也可以从外部导入。上图中的字典就是[好,宾馆,海底,拉面,冰雪,.......,馆]。我们对已校准的POI,先利用Lucene的中文分词工具SmartCn[2]对POI名称做预分词处理,提取特征词,作为原始粗糙字典集合。有了字典后便可以量化地表示出某个文本。先定义一个与字典长度相同的向量,向量中的每个位置对应字典中的相应位置的单词。然后遍历这个文本,对应文本中的出现某个单词,在向量中的对应位置,填入“某个值”(即特征词的权重,包括BOOL权重,词频权重,TFIDF权重)。考虑到一般的POI名称都属于短文本,本文采用BOOL权重。在产生粗糙字典集合时,我们还统计了校准POI中,每个品类(type_id),以及特征词(term)在品类(type_id)出现的次数(文档频率)。分别写入到表category_frequency和term_category_frequency,表的部分结果如下:category_frequency表:term_category_frequency表:分别记: A(i,j)=特征词term(i)在品类为type_id(j)出现的次数count T(j)=品类为type_id(j)在样本集出现的次数 N=已校准POI数据集的数量这些统计量,将在后续的计算中发挥它们的作用。特征选择现在,我们得到了一个“预输入字典”:包括了所有已校准POI名称字段的特征词,这些特征词比如:“88”、“11”,“3”、“auyi”、“中心”、“中国”、“酒店”、“自助餐”、“拉面”等。直观感觉,“88”、“11”,“3”、“auyi”、“中国”这些词对判断品类并没有多大帮助,但“酒店”、“自助餐”、“拉面”对判断一个POI的品类却可能起到非常重要作用。那么问题来了,如何挑选出有利于模型预测的特征呢?这就涉及到了特征选择。特征选择方法可以分成基于领域知识的规则方法和基于统计学习方法。本文使用统计机器学习方法,辅助规则方法的特征选择算法,挑选有利于判断POI品类的特征词。基于统计学习的特征选择算法基于统计学习的特征选择算法,大体可以分成两种:1.基于相关性度量(信息论相关)2.特征空间表示(典型的如PCA)文本特征经常采用的基于信息增益方法(IG)特征选择方法[3]。某个特征的信息增益是指,已知该特征条件下,整个系统的信息量的前后变化。如果前后信息量变化越大,那么可以认为该特征起到的作用也就越大。那么,如何定义信息量呢?一般采用熵的概念来衡量一个系统的信息量:当我们已知该特征时,从数学的角度来说就是已知了该特征的分布,系统的信息量可以由条件熵来描述:该特征的信息增益定义为:信息增益得分衡量了该特征的重要性。假设我们有四个样本,样本的特征词包括“火锅”、“米粉”、“馆”,我们采用信息增益判断不同特征对于决策影响:整个系统的最原始信息熵为:分别计算每个特征的条件熵:利用整个系统的信息熵减去条件熵,得到每个特征的信息增益得分排名(“火锅”(1)>“米粉”(0.31)>“馆”(0)),按照得分由高到低挑选需要的特征词。本文采用IG特征选择方法,选择得分排名靠前的N个特征词(Top30%)。我们抽取排名前20的特征词:[酒店,宾馆,火锅,摄影,眼镜,美容,咖啡,ktv,造型,汽车,餐厅,蛋糕,儿童,美发,商务,旅行社,婚纱,会所,影城,烤肉]。这些特征词明显与品类属性相关联具有较强相关性,我们将其称之为品类词。基于领域知识的特征选择方法基于规则的特征选择算法,利用领域知识选择特征。目前很少单独使用基于规则的特征选择算法,往往结合统计学习的特征选择算法,辅助挑选特征。本文需要解决的是POI名称字段短文本的自动分类问题,POI名称字段一般符合这样的规则,POI名称=名称核心词+品类词。名称核心词对于实际的品类预测作用不大,有时反而出现”过度学习“起到负面作用。例如”好利来牛肉拉面馆“,”好利来“是它的名称核心词,在用学习算法时学到的很有可能是一个”蛋糕“品类(”好利来“和”蛋糕“品类的关联性非常强,得到错误的预测结论)。本文使用该规则在挑选特征时做了一个trick:利用特征选择得到的特征词(绝大部分是品类词),对POI名称字段分词,丢弃前面部分(主要是名称核心词),保留剩余部分。这种trick从目前的评测结果看有5%左右准确率提升,缺点是会降低了算法覆盖度。#分类模型##建模完成了特征表示、特征选择后,下一步就是训练分类模型了。机器学习分类模型可以分成两种:1)生成式模型;2)判别式模型。可以简单认为,两者区别生成式模型直接对样本的联合概率分布进行建模:生成式模型的难点在于如何去估计类概率密度分布p(x|y)。本文采用的朴素贝叶斯模型,其"Naive"在对类概率密度函数简化上,它假设了条件独立:根据对p(x|y)不同建模形式,NaiveBayes模型主要分成:Muti-variateBernoulliModel(多项伯努利模型)和Multinomialeventmodel(多项事件模型)[4]。一次伯努利事件相当于一次投硬币事件(0,1两种可能),一次多项事件则相当于投色子(1到6多种可能)。我们结合传统的文本分类解释这两类模型:多项伯努利模型已知类别的条件下,多项伯努利对应样本生X成过程:遍历字典中的每个单词(t1,t2...t|V|),判断这个词是否在样本中出现。每次遍历都是一次伯努利实验,|V|次遍历:其中1(condition)为条件函数,该函数表示当条件成立是等于1,不成立时等于0;|V|则表示字典的长度。多项事件模型已知类别的条件下,多项事件模型假设样本的产生过程:对文本中第k个位置的单词,从字典中选择一个单词,每个位置k产生单词对应于一次多项事件。样本X=(w1,w2...ws)的类概率密度:采用向量空间模型表示样本时,上式转成:其中N(ti,X)表示特征词i在样本X出现的次数。##参数估计好啦,一大堆无聊公式的折磨后,我们终于要见到胜利的曙光:模型参数预估。一般的方法有最大似然估计、最大后验概率估计等。本文使用的是多项伯努利模型,我们直接给出多项伯努利模型参数估计结论:还记得特征表示一节中统计的term_category_frequency和category_frequency两张表吗?此时,就要发挥它的作用了!我们,只需要查询这两张表,就可以完成参数的估计了,是不是很happy?过程虽然有点曲折,但是结果是美好的~具体参数意义可以参见特征表示一节。接下来的coding的可能需要关注的两个点:参数平滑在计算类概率密度p(X|Cj)时,如果在类Cj下没有出现特征ti,p(ti|Cj)=0,类概率密度连乘也将会等于0,额,对于一个样本如果在某条件下某个特征没有出现,便认为她产生的可能等于零,这样的结论实在是过武断,解决方法是加1平滑:其中,|C|表示样本的类别数据。小数溢出在计算类概率密度时多个条件概率(小数)连乘,会存在着超过计算机能够表示的最小数可能,为避免小数溢出问题,一般将类概率密度计算转成成对数累和的形式。另外,如果在计算p(ti|Cj)时过小,取对数后将会得到一个负无穷的值,需要对p(ti|Cj)截断处理:小于某个阈值(如1E-6)时,采用该阈值替代。算法预测本节将结合前面三节内容,给出算法具体的计算预测过程。为简化问题,我们假设字典为:[拉面,七天,牛肉,馆],并且只有火锅和快餐两个品类,两类样本的数量均为8个。以“好利来牛肉拉面馆为例”:对测试样本做中文分词,判断”牛肉“属于品类词,丢弃品类词”牛肉“前面的部分,并提取样本的特征词集合得到:[牛肉拉面馆]根据字典,建立向量空间模型:x=[1,0,1,1]利用NaiveBayes模型分类预测,我们给出火锅和快餐两类样本的term_category_frequency统计:样本属于快餐的概率高于属于火锅概率4倍,预测样本属于快餐置信度明显高于火锅概率。算法随机抽取2000条未校准的POI数据进行评测,算法的评测指标有两个:覆盖度和准确率。覆盖度是指算法可预测的样本数量在整个测试样本集中的比例。由于采用特征选择后,一些POI名称因不包含特征词集合而无法预测,算法的评测的覆盖度为84%。算法的准确率是指,可预测正确样本在整个测试样本集中的比例,算法评测的正确率为91%。#总结机器学习解决问题最关键的一步是找准问题:这种问题能否用机器学习算法解决?是否存在其他更简单的方法?简单的如字符串匹配,利用正则就可以简单解决,才机器学习方法反而很麻烦,得不偿失。如果能机器学习算法,如何去表示这个机器学习问题,如何抽取特征?又可能归类哪类机器模式(分类、聚类、回归?)找准问题后,可以先尝试一些开源的机器学习工具,验证算法的有效性。如果有必要,自己实现一些机器算法,也可以借鉴一些开源机器学习算法实现。
前言推荐系统并不是新鲜的事物,在很久之前就存在,但是推荐系统真正进入人们的视野,并且作为一个重要的模块存在于各个互联网公司,还是近几年的事情。随着互联网的深入发展,越来越多的信息在互联网上传播,产生了严重的信息过载。如果不采用一定的手段,用户很难从如此多的信息流中找到对自己有价值的信息。解决信息过载有几种手段:一种是搜索,当用户有了明确的信息需求意图后,将意图转换为几个简短的词或者短语的组合(即query),然后将这些词或短语组合提交到相应的搜索引擎,再由搜索引擎在海量的信息库中检索出与query相关的信息返回给用户;另外一种是推荐,很多时候用户的意图并不是很明确,或者很难用清晰的语义表达,有时甚至连用户自己都不清楚自己的需求,这种情况下搜索就显得捉襟见肘了。尤其是近些年来,随着电子商务的兴起,用户并非一定是带着明确的购买意图去浏览,很多时候是去“逛”的,这种情景下解决信息过载,理解用户意图,为用户推送个性化的结果,推荐系统便是一种比较好的选择。美团作为国内发展较快的o2o网站,有着大量的用户和丰富的用户行为,这些为推荐系统的应用和优化提供了不可或缺的条件,接下来介绍美团在推荐系统的构建和优化过程中的一些做法,与大家共享。框架从框架的角度看,推荐系统基本可以分为数据层、触发层、融合过滤层和排序层。数据层包括数据生成和数据存储,主要是利用各种数据处理工具对原始日志进行清洗,处理成格式化的数据,落地到不同类型的存储系统中,供下游的算法和模型使用。候选集触发层主要是从用户的历史行为、实时行为、地理位置等角度利用各种触发策略产生推荐的候选集。候选集融合和过滤层有两个功能,一是对出发层产生的不同候选集进行融合,提高推荐策略的覆盖度和精度;另外还要承担一定的过滤职责,从产品、运营的角度确定一些人工规则,过滤掉不符合条件的item。排序层主要是利用机器学习的模型对触发层筛选出来的候选集进行重排序。同时,对与候选集触发和重排序两层而言,为了效果迭代是需要频繁修改的两层,因此需要支持ABtest。为了支持高效率的迭代,美团对候选集触发和重排序两层进行了解耦,这两层的结果是正交的,因此可以分别进行对比试验,不会相互影响。同时在每一层的内部,美团会根据用户将流量划分为多份,支持多个策略同时在线对比。数据应用数据乃算法、模型之本。美团作为一个交易平台,同时具有快速增长的用户量,因此产生了海量丰富的用户行为数据。当然,不同类型的数据的价值和反映的用户意图的强弱也有所不同。用户主动行为数据记录了用户在美团平台上不同的环节的各种行为,这些行为一方面用于候选集触发算法(在下一部分介绍)中的离线计算(主要是浏览、下单),另外一方面,这些行为代表的意图的强弱不同,因此在训练重排序模型时可以针对不同的行为设定不同的回归目标值,以更细地刻画用户的行为强弱程度。此外,用户对deal的这些行为还可以作为重排序模型的交叉特征,用于模型的离线训练和在线预测。负反馈数据反映了当前的结果可能在某些方面不能满足用户的需求,因此在后续的候选集触发过程中需要考虑对特定的因素进行过滤或者降权,降低负面因素再次出现的几率,提高用户体验;同时在重排序的模型训练中,负反馈数据可以作为不可多得的负例参与模型训练,这些负例要比那些展示后未点击、未下单的样本显著的多。用户画像是刻画用户属性的基础数据,其中有些是直接获取的原始数据,有些是经过挖掘的二次加工数据,这些属性一方面可以用于候选集触发过程中对deal进行加权或降权,另外一方面可以作为重排序模型中的用户维度特征。通过对UGC数据的挖掘可以提取出一些关键词,然后使用这些关键词给deal打标签,用于deal的个性化展示。策略触发上文中美团提到了数据的重要性,但是数据的落脚点还是算法和模型。单纯的数据只是一些字节的堆积,美团必须通过对数据的清洗去除数据中的噪声,然后通过算法和模型学习其中的规律,才能将数据的价值最大化。在本节中,将介绍推荐候选集触发过程中用到的相关算法。##1.协同过滤提到推荐,就不得不说协同过滤,它几乎在每一个推荐系统中都会用到。基本的算法非常简单,但是要获得更好的效果,往往需要根据具体的业务做一些差异化的处理。清除作弊、刷单、代购等噪声数据。这些数据的存在会严重影响算法的效果,因此要在第一步的数据清洗中就将这些数据剔除。合理选取训练数据。选取的训练数据的时间窗口不宜过长,当然也不能过短。具体的窗口期数值需要经过多次的实验来确定。同时可以考虑引入时间衰减,因为近期的用户行为更能反映用户接下来的行为动作。user-based与item-based相结合。尝试不同的相似度计算方法。在实践中,美团采用了一种称作loglikelihoodratio[1]的相似度计算方法。在mahout中,loglikelihoodratio也作为一种相似度计算方法被采用。下表表示了EventA和EventB之间的相互关系,其中:k11:EventA和EventB共现的次数k12:EventB发生,EventA未发生的次数k21:EventA发生,EventB未发生的次数k22:EventA和EventB都不发生的次数、则logLikelihoodRatio=2*(matrixEntropy-rowEntropy-columnEntropy)其中rowEntropy=entropy(k11,k12)+entropy(k21,k22)columnEntropy=entropy(k11,k21)+entropy(k12,k22)matrixEntropy=entropy(k11,k12,k21,k22)(entropy为几个元素组成的系统的香农熵)##2.location-based对于移动设备而言,与PC端最大的区别之一是移动设备的位置是经常发生变化的。不同的地理位置反映了不同的用户场景,在具体的业务中可以充分利用用户所处的地理位置。在推荐的候选集触发中,美团也会根据用户的实时地理位置、工作地、居住地等地理位置触发相应的策略。根据用户的历史消费、历史浏览等,挖掘出某一粒度的区域(比如商圈)内的区域消费热单和区域购买热单区域消费热单区域购买热单当新的线上用户请求到达时,根据用户的几个地理位置对相应地理位置的区域消费热单和区域购买热单进行加权,最终得到一个推荐列表。此外,还可以根据用户出现的地理位置,采用协同过滤的方式计算用户的相似度。##3.query-based搜索是一种强用户意图,比较明确的反应了用户的意愿,但是在很多情况下,因为各种各样的原因,没有形成最终的转换。尽管如此,美团认为,这种情景还是代表了一定的用户意愿,可以加以利用。具体做法如下:对用户过去一段时间的搜索无转换行为进行挖掘,计算每一个用户对不同query的权重。计算每个query下不同deal的权重。当用户再次请求时,根据用户对不同query的权重及query下不同deal的权重进行加权,取出权重最大的TopN进行推荐。##4.graph-based对于协同过滤而言,user之间或者deal之间的图距离是两跳,对于更远距离的关系则不能考虑在内。而图算法可以打破这一限制,将user与deal的关系视作一个二部图,相互间的关系可以在图上传播。Simrank[2]是一种衡量对等实体相似度的图算法。它的基本思想是,如果两个实体与另外的相似实体有相关关系,那它们也是相似的,即相似性是可以传播的。Lets(A,B)denotethesimilaritybetweenpersonsAandB,forA!=BLets(c,d)denotethesimilaritybetweenitemscandd,forc!=dO(A),O(B):thesetofout-neighborsfornodeAornodeBI(c),I(d):thesetofin-neighborsfornodecornodedsimrank的计算(采用矩阵迭代的方式)计算得出相似度矩阵后,可以类似协同过滤用于线上推荐。##5.实时用户行为目前美团的业务会产生包括搜索、筛选、收藏、浏览、下单等丰富的用户行为,这些是美团进行效果优化的重要基础。美团当然希望每一个用户行为流都能到达转化的环节,但是事实上远非这样。当用户产生了下单行为上游的某些行为时,会有相当一部分因为各种原因使行为流没有形成转化。但是,用户的这些上游行为对美团而言是非常重要的先验知识。很多情况下,用户当时没有转化并不代表用户对当前的item不感兴趣。当用户再次到达美团的推荐展位时,美团根据用户之前产生的先验行为理解并识别用户的真正意图,将符合用户意图的相关deal再次展现给用户,引导用户沿着行为流向下游行进,最终达到下单这个终极目标。目前引入的实时用户行为包括:实时浏览、实时收藏。##6.替补策略虽然美团有一系列基于用户历史行为的候选集触发算法,但对于部分新用户或者历史行为不太丰富的用户,上述算法触发的候选集太小,因此需要使用一些替补策略进行填充。热销单:在一定时间内销量最多的item,可以考虑时间衰减的影响等。好评单:用户产生的评价中,评分较高的item。城市单:满足基本的限定条件,在用户的请求城市内的。子策略融合为了结合不同触发算法的优点,同时提高候选集的多样性和覆盖率,需要将不同的触发算法融合在一起。常见的融合的方法有以下几种[3]:加权型:最简单的融合方法就是根据经验值对不同算法赋给不同的权重,对各个算法产生的候选集按照给定的权重进行加权,然后再按照权重排序。分级型:优先采用效果好的算法,当产生的候选集大小不足以满足目标值时,再使用效果次好的算法,依此类推。调制型:不同的算法按照不同的比例产生一定量的候选集,然后叠加产生最终总的候选集。过滤型:当前的算法对前一级算法产生的候选集进行过滤,依此类推,候选集被逐级过滤,最终产生一个小而精的候选集合。目前美团使用的方法集成了调制和分级两种融合方法,不同的算法根据历史效果表现给定不同的候选集构成比例,同时优先采用效果好的算法触发,如果候选集不够大,再采用效果次之的算法触发,依此类推。候选集重排序如上所述,对于不同算法触发出来的候选集,只是根据算法的历史效果决定算法产生的item的位置显得有些简单粗暴,同时,在每个算法的内部,不同item的顺序也只是简单的由一个或者几个因素决定,这些排序的方法只能用于第一步的初选过程,最终的排序结果需要借助机器学习的方法,使用相关的排序模型,综合多方面的因素来确定。##1.模型非线性模型能较好的捕捉特征中的非线性关系,但训练和预测的代价相对线性模型要高一些,这也导致了非线性模型的更新周期相对要长。反之,线性模型对特征的处理要求比较高,需要凭借领域知识和经验人工对特征做一些先期处理,但因为线性模型简单,在训练和预测时效率较高。因此在更新周期上也可以做的更短,还可以结合业务做一些在线学习的尝试。在美团的实践中,非线性模型和线性模型都有应用。非线性模型目前美团主要采用了非线性的树模型AdditiveGroves[4](简称AG),相对于线性模型,非线性模型可以更好的处理特征中的非线性关系,不必像线性模型那样在特征处理和特征组合上花费比较大的精力。AG是一个加性模型,由很多个Grove组成,不同的Grove之间进行bagging得出最后的预测结果,由此可以减小过拟合的影响。每一个Grove有多棵树组成,在训练时每棵树的拟合目标为真实值与其他树预测结果之和之间的残差。当达到给定数目的树时,重新训练的树会逐棵替代以前的树。经过多次迭代后,达到收敛。线性模型目前应用比较多的线性模型非LogisticRegression莫属了。为了能实时捕捉数据分布的变化,美团引入了onlinelearning,接入实时数据流,使用google提出的FTRL[5]方法对模型进行在线更新。主要的步骤如下:在线写特征向量到HBaseStorm解析实时点击和下单日志流,改写HBase中对应特征向量的label通过FTRL更新模型权重将新的模型参数应用于线上##2.数据采样:对于点击率预估而言,正负样本严重不均衡,所以需要对负例做一些采样。负例:正例一般是用户产生点击、下单等转换行为的样本,但是用户没有转换行为的样本是否就一定是负例呢?其实不然,很多展现其实用户根本没有看到,所以把这样样本视为负例是不合理的,也会影响模型的效果。比较常用的方法是skip-above,即用户点击的item位置以上的展现才可能视作负例。当然,上面的负例都是隐式的负反馈数据,除此之外,美团还有用户主动删除的显示负反馈数据,这些数据是高质量的负例。去噪:对于数据中混杂的刷单等类作弊行为的数据,要将其排除出训练数据,否则会直接影响模型的效果。##3.特征在美团目前的重排序模型中,大概分为以下几类特征:deal(即团购单,下同)维度的特征:主要是deal本身的一些属性,包括价格、折扣、销量、评分、类别、点击率等user维度的特征:包括用户等级、用户的人口属性、用户的客户端类型等user、deal的交叉特征:包括用户对deal的点击、收藏、购买等距离特征:包括用户的实时地理位置、常去地理位置、工作地、居住地等与poi的距离对于非线性模型,上述特征可以直接使用;而对于线性模型,则需要对特征值做一些分桶、归一化等处理,使特征值成为0~1之间的连续值或01二值。总结以数据为基础,用算法去雕琢,只有将二者有机结合,才会带来效果的提升。对美团而言,以下两个节点是美团优化过程中的里程碑:将候选集进行融合:提高了推荐的覆盖度、多样性和精度引入重排序模型:解决了候选集增加以后deal之间排列顺序的问题以上是美团在实践中的一点总结,当然美团还有还多事情要做。wearestillontheway!注:本文为美团推荐与个性化团队集体智慧的结晶,感谢为此辛苦付出的每一个成员。同时,团队长期招聘算法工程师与平台研发工程师,感兴趣的同学请联系hr.tech@meituan.com,邮件标题注明“应聘推荐系统工程师”。
如果在平时的工作中有留意的话,你会发现一个问题,同行业的网站在设计上都是雷同的,没有自己的个性。这样的网站建设就只是别人的影子,用户也不大喜欢这样的网站。一些网站设计的很漂亮,有动画,有图片,但是内容却很少,这种网站的用户体验也不好。网站需要的不仅是精美的设计,还需要有充实的内容。一个网站想要有良好的用户体验,最起码要有自己的主题,并且坚持更新用户喜欢的内容,网站的价值才能逐渐体现出来,用户对网站的体验也会上升。那么,究竟要怎样从产品层面上提升用户体验呢?来看看吧!第一、怎样才可以证明网站有着优秀的用户体验呢?我们可以举一个例子,如果苹果手机的用户非常少,那么即使苹果手机再好,可以证明的人太少了,不足以证明它的好。判断用户体验的好坏,是由大多数人决定的。比较受欢迎的,不一定是好的,但是可以证明使用它的用户对它都比较有好感,评价都不错,受众用户多了,网站的用户体验就比较良好。如果用户多了,用户体验好了,就代表着网站被用户喜爱,用户对于喜欢的东西都会自动宣传,将网站传给更多的人知道。所以,想要证明网站的用户体验,首先要让更多的用户到网站访问。第二、用户体验以及用户访问习惯要如何选择呢?就好比一件新产品推出的时候,肯定会受到旧产品的冲击,因为要用户重新适应新产品。而很多用户在面对新产品的时候,都会思考一个问题,就是对新产品的学习成本值不值得。我们可以从团购网站上找到答案。为什么团购网站可以在短时间内被那么多人喜欢呢?团购网站最核心的建设就是为用户带来了优惠,用户认为自己得到了一些利益,那么就会去尝试,尝试过后,原来真的可以享受到优惠,自然就会经常使用。随着团购网站的发展,团购网站已经成为了用户日常最重要的一个工具。就好像百度糯米团购提出的“随便退”的功能,让更多用户去尝试,用户体验也不断提升。而用户的习惯则是推出新产品时遇到的最大的障碍。例如office2007刚推出的那一段时期,因为用户对旧版本的office在使用上已经形成了习惯,一时间很难改变过来,而且office2007的改变太大了,推出的时候,遭到了很多用户的拒绝。但是office对工作实在太重要了,就算再不习惯也还是要使用的,最终用户也开始慢慢习惯了。甚至对于有些人,觉得2007版本使用起来更加得心应手。所以,用户对产品的使用习惯也是用户体验的一个方面。一个网站想要推出新产品,新产品一定要符合用户的需求,并且要满足用户的使用习惯,这样一款新产品才会更畅销,用户体验也会更加好,同样放注在网站建设上面也是一样的。
问题背景搜索关键字智能提示是一个搜索应用的标配,主要作用是避免用户输入错误的搜索词,并将用户引导到相应的关键词上,以提升用户搜索体验。美团CRM系统中存在数以百万计的商家,为了让用户快速查找到目标商家,我们基于solrcloud实现了商家搜索模块。用户在查找商家时主要输入商户名、商户地址进行搜索,为了提升用户的搜索体验和输入效率,本文实现了一种基于solr前缀匹配查询关键字智能提示(Suggestion)实现。需求分析1.支持前缀匹配原则在搜索框中输入“海底”,搜索框下面会以海底为前缀,展示“海底捞”、“海底捞火锅”、“海底世界”等等搜索词;输入“万达”,会提示“万达影城”、“万达广场”、“万达百货”等搜索词。2.同时支持汉字、拼音输入由于中文的特点,如果搜索自动提示可以支持拼音的话会给用户带来更大的方便,免得切换输入法。比如,输入“haidi”提示的关键字和输入“海底”提示的一样,输入“wanda”与输入“万达”提示的关键字一样。3.支持多音字输入提示比如输入“chongqing”或者“zhongqing”都能提示出“重庆火锅”、“重庆烤鱼”、“重庆小天鹅”。4.支持拼音缩写输入对于较长关键字,为了提高输入效率,有必要提供拼音缩写输入。比如输入“hd”应该能提示出“haidi”相似的关键字,输入“wd”也一样能提示出“万达”关键字。基于用户的历史搜索行为,按照关键字热度进行排序为了提供suggest关键字的准确度,最终查询结果,根据用户查询关键字的频率进行排序,如输入[重庆,chongqing,cq,zhongqing,zq]—>[“重庆火锅”(f1),“重庆烤鱼”(f2),“重庆小天鹅”(f3),…],查询频率f1>f2>f3。解决方案1.关键字收集当用户输入一个前缀时,碰到提示的候选词很多的时候,如何取舍,哪些展示在前面,哪些展示在后面?这就是一个搜索热度的问题。用户在使用搜索引擎查找商家时,会输入大量的关键字,每一次输入就是对关键字的一次投票,那么关键字被输入的次数越多,它对应的查询就比较热门,所以需要把查询的关键字记录下来,并且统计出每个关键字的频率,方便提示结果按照频率排序。搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。2.汉字转拼音用户输入的关键字可能是汉字、数字,英文,拼音,特殊字符等等,由于需要实现拼音提示,我们需要把汉字转换成拼音,java中考虑使用pinyin4j组件实现转换。3.拼音缩写提取考虑到需要支持拼音缩写,汉字转换拼音的过程中,顺便提取出拼音缩写,如“chongqing”,"zhongqing"--->"cq",”zq”。4.多音字全排列要支持多音字提示,对查询串转换成拼音后,需要实现一个全排列组合,字符串多音字全排列算法如下:JavaCode复制内容到剪贴板public static List getPermutationSentence(List> termArrays,int start) { if (CollectionUtils.isEmpty(termArrays)) return Collections.emptyList(); int size = termArrays.size(); if (start = size) { return Collections.emptyList(); } if (start == size-1) { return termArrays.get(start); } List strings = termArrays.get(start); List permutationSentences = getPermutationSentence(termArrays, start + 1); if (CollectionUtils.isEmpty(strings)) { return permutationSentences; } if (CollectionUtils.isEmpty(permutationSentences)) { return strings; } List result = new ArrayList(); for (String pre : strings) { for (String suffix : permutationSentences) { result.add(pre+suffix); } } return result; } 索引与前缀查询方案一Trie树+TopK算法Trie树即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie是一颗存储多个字符串的树。相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串。和普通树不同的地方是,相同的字符串前缀共享同一条分支。例如,给出一组单词inn,int,at,age,adv,ant,我们可以得到下面的Trie:从上图可知,当用户输入前缀i的时候,搜索框可能会展示以i为前缀的“in”,“inn”,”int"等关键词,再当用户输入前缀a的时候,搜索框里面可能会提示以a为前缀的“ate”等关键词。如此,实现搜索引擎智能提示suggestion的第一个步骤便清晰了,即用trie树存储大量字符串,当前缀固定时,存储相对来说比较热的后缀。TopK算法用于解决统计热词的问题。解决TopK问题主要有两种策略:hashMap统计+排序、堆排序hashmap统计:先对这批海量数据预处理。具体方法是:维护一个Key为Query字串,Value为该Query出现次数的HashTable,即hash_map(Query,Value),每次读取一个Query,如果该字串不在Table中,那么加入该字串,并且将Value值设为1;如果该字串在Table中,那么将该字串的计数加一即可,最终在O(N)的时间复杂度内用Hash表完成了统计。堆排序:借助堆这个数据结构,找出TopK,时间复杂度为N‘logK。即借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此,维护一个K(该题目中是10)大小的小根堆,然后遍历300万的Query,分别和根元素进行对比。所以,我们最终的时间复杂度是:O(N)+N'*O(logK),(N为1000万,N’为300万)。该方案存在的问题是:建索引和查询的时候都要把汉字转换成拼音,查询完成后还得把拼音转换成汉字显示,且需要考虑数字和特殊字符。需要维护拼音、缩写两棵Trie树。方案二Solr自带Suggest智能提示Solr作为一个应用广泛的搜索引擎系统,它内置了智能提示功能,叫做Suggest模块。该模块可选择基于提示词文本做智能提示,还支持通过针对索引的某个字段建立索引词库做智能提示。(详见solr的wiki页面http://wiki.apache.org/solr/Suggester)该方案存在的问题是:返回的结果是基于索引中字段的词频进行排序,不是用户搜索关键字的频率,因此不能将一些热门关键字排在前面。拼音提示,多音字,缩写还是要另外加索引字段。方案三Solrcloud建立单独的collection,利用solr前缀查询实现如前所述,以上两个方案在实施起来都存在一些问题,Trie树+TopK算法,在处理汉字suggest时不是很优雅,且需要维护两棵Trie树,实施起来比较复杂;Solr自带的suggest智能提示组件存在问题是使用freq排序算法,返回的结果完全基于索引中字符的出现次数,没有兼顾用户搜索词语的频率,因此无法将一些热门词排在更靠前的位置。于是,我们继续寻找一种解决这个问题更加优雅的方案。至此,我们考虑专门为关键字建立一个索引collection,利用solr前缀查询实现。solr中的copyField能很好解决我们同时索引多个字段(汉字、pinyin,abbre)的需求,且field的multiValued属性设置为true时能解决同一个关键字的多音字组合问题。配置如下:schema.xml:XML/HTMLCode复制内容到剪贴板 ------------------multiValued表示字段是多值的-------------------------------------XML/HTMLCode复制内容到剪贴板kw suggest 说明:kw为原始关键字pinyin和abbre的multiValued=true,在使用solrj建此索引时,定义成集合类型即可:如关键字“重庆”的pinyin字段为{chongqing,zhongqing},abbre字段为{cq,zq}kwfreq为用户搜索关键的频率,用于查询的时候排序-------------------------------------------------------XML/HTMLCode复制内容到剪贴板 ------------------suggest_text----------------------------------XML/HTMLCode复制内容到剪贴板 KeywordTokenizerFactory:这个分词器不进行任何分词!整个字符流变为单个词元。String域类型也有类似的效果,但是它不能配置文本分析的其它处理组件,比如大小写转换。任何用于排序和大部分Faceting功能的索引域,这个索引域只有能一个原始域值中的一个词元。前缀查询构造:JavaCode复制内容到剪贴板private SolrQuery getSuggestQuery(String prefix, Integer limit) { SolrQuery solrQuery = new SolrQuery(); StringBuilder sb = new StringBuilder(); sb.append(“suggest:").append(prefix).append("*"); solrQuery.setQuery(sb.toString()); solrQuery.addField("kw"); solrQuery.addField("kwfreq"); solrQuery.addSort("kwfreq", SolrQuery.ORDER.desc); solrQuery.setStart(0); solrQuery.setRows(limit); return solrQuery; } 效果如下图所示:
美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流。美团的日志收集系统基于Flume设计和搭建而成。《基于Flume的美团日志收集系统》将分两部分给读者呈现美团日志收集系统的架构设计和实战经验。第一部分架构和设计,将主要着眼于日志收集系统整体的架构设计,以及为什么要做这样的设计。第二部分改进和优化,将主要着眼于实际部署和使用过程中遇到的问题,对Flume做的功能修改和优化等。1日志收集系统简介日志收集是大数据的基石。许多公司的业务平台每天都会产生大量的日志数据。收集业务日志数据,供离线和在线的分析系统使用,正是日志收集系统的要做的事情。高可用性,高可靠性和可扩展性是日志收集系统所具有的基本特征。目前常用的开源日志收集系统有Flume,Scribe等。Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,目前已经是Apache的一个子项目。Scribe是Facebook开源的日志收集系统,它为日志的分布式收集,统一处理提供一个可扩展的,高容错的简单方案。2常用的开源日志收集系统对比下面将对常见的开源日志收集系统Flume和Scribe的各方面进行对比。对比中Flume将主要采用Apache下的Flume-NG为参考对象。同时,美团将常用的日志收集系统分为三层(Agent层,Collector层和Store层)来进行对比。3美团日志收集系统架构美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流。美团的日志收集系统基于Flume设计和搭建而成。目前每天收集和处理约T级别的日志数据。下图是美团的日志收集系统的整体框架图。a.整个系统分为三层:Agent层,Collector层和Store层。其中Agent层每个机器部署一个进程,负责对单机的日志收集工作;Collector层部署在中心服务器上,负责接收Agent层发送的日志,并且将日志根据路由规则写到相应的Store层中;Store层负责提供永久或者临时的日志存储服务,或者将日志流导向其它服务器。b.Agent到Collector使用LoadBalance策略,将所有的日志均衡地发到所有的Collector上,达到负载均衡的目标,同时并处理单个Collector失效的问题。c.Collector层的目标主要有三个:SinkHdfs,SinkKafka和SinkBypass。分别提供离线的数据到Hdfs,和提供实时的日志流到Kafka和Bypass。其中SinkHdfs又根据日志量的大小分为SinkHdfs_b,SinkHdfs_m和SinkHdfs_s三个Sink,以提高写入到Hdfs的性能,具体见后面介绍。d.对于Store来说,Hdfs负责永久地存储所有日志;Kafka存储最新的7天日志,并给Storm系统提供实时日志流;Bypass负责给其它服务器和应用提供实时日志流。下图是美团的日志收集系统的模块分解图,详解Agent,Collector和Bypass中的Source,Channel和Sink的关系。a.模块命名规则:所有的Source以src开头,所有的Channel以ch开头,所有的Sink以sink开头;b.Channel统一使用美团开发的DualChannel,具体原因后面详述;对于过滤掉的日志使用NullChannel,具体原因后面详述;c.模块之间内部通信统一使用Avro接口;4架构设计考虑下面将从可用性,可靠性,可扩展性和兼容性等方面,对上述的架构做细致的解析。4.1可用性(availablity)对日志收集系统来说,可用性(availablity)指固定周期内系统无故障运行总时间。要想提高系统的可用性,就需要消除系统的单点,提高系统的冗余度。下面来看看美团的日志收集系统在可用性方面的考虑。4.1.1Agent死掉Agent死掉分为两种情况:机器死机或者Agent进程死掉。对于机器死机的情况来说,由于产生日志的进程也同样会死掉,所以不会再产生新的日志,不存在不提供服务的情况。对于Agent进程死掉的情况来说,确实会降低系统的可用性。对此,美团有下面三种方式来提高系统的可用性。首先,所有的Agent在supervise的方式下启动,如果进程死掉会被系统立即重启,以提供服务。其次,对所有的Agent进行存活监控,发现Agent死掉立即报警。最后,对于非常重要的日志,建议应用直接将日志写磁盘,Agent使用spooldir的方式获得最新的日志。4.1.2Collector死掉由于中心服务器提供的是对等的且无差别的服务,且Agent访问Collector做了LoadBalance和重试机制。所以当某个Collector无法提供服务时,Agent的重试策略会将数据发送到其它可用的Collector上面。所以整个服务不受影响。4.1.3Hdfs正常停机美团在Collector的HdfsSink中提供了开关选项,可以控制Collector停止写Hdfs,并且将所有的events缓存到FileChannel的功能。4.1.4Hdfs异常停机或不可访问假如Hdfs异常停机或不可访问,此时Collector无法写Hdfs。由于美团使用DualChannel,Collector可以将所收到的events缓存到FileChannel,保存在磁盘上,继续提供服务。当Hdfs恢复服务以后,再将FileChannel中缓存的events再发送到Hdfs上。这种机制类似于Scribe,可以提供较好的容错性。4.1.5Collector变慢或者Agent/Collector网络变慢如果Collector处理速度变慢(比如机器load过高)或者Agent/Collector之间的网络变慢,可能导致Agent发送到Collector的速度变慢。同样的,对于此种情况,美团在Agent端使用DualChannel,Agent可以将收到的events缓存到FileChannel,保存在磁盘上,继续提供服务。当Collector恢复服务以后,再将FileChannel中缓存的events再发送给Collector。4.1.6Hdfs变慢当Hadoop上的任务较多且有大量的读写操作时,Hdfs的读写数据往往变的很慢。由于每天,每周都有高峰使用期,所以这种情况非常普遍。对于Hdfs变慢的问题,美团同样使用DualChannel来解决。当Hdfs写入较快时,所有的events只经过MemChannel传递数据,减少磁盘IO,获得较高性能。当Hdfs写入较慢时,所有的events只经过FileChannel传递数据,有一个较大的数据缓存空间。4.2可靠性(reliability)对日志收集系统来说,可靠性(reliability)是指Flume在数据流的传输过程中,保证events的可靠传递。对Flume来说,所有的events都被保存在Agent的Channel中,然后被发送到数据流中的下一个Agent或者最终的存储服务中。那么一个Agent的Channel中的events什么时候被删除呢?当且仅当它们被保存到下一个Agent的Channel中或者被保存到最终的存储服务中。这就是Flume提供数据流中点到点的可靠性保证的最基本的单跳消息传递语义。那么Flume是如何做到上述最基本的消息传递语义呢?首先,Agent间的事务交换。Flume使用事务的办法来保证event的可靠传递。Source和Sink分别被封装在事务中,这些事务由保存event的存储提供或者由Channel提供。这就保证了event在数据流的点对点传输中是可靠的。在多级数据流中,如下图,上一级的Sink和下一级的Source都被包含在事务中,保证数据可靠地从一个Channel到另一个Channel转移。其次,数据流中Channel的持久性。Flume中MemoryChannel是可能丢失数据的(当Agent死掉时),而FileChannel是持久性的,提供类似mysql的日志机制,保证数据不丢失。4.3可扩展性(scalability)对日志收集系统来说,可扩展性(scalability)是指系统能够线性扩展。当日志量增大时,系统能够以简单的增加机器来达到线性扩容的目的。对于基于Flume的日志收集系统来说,需要在设计的每一层,都可以做到线性扩展地提供服务。下面将对每一层的可扩展性做相应的说明。4.3.1Agent层对于Agent这一层来说,每个机器部署一个Agent,可以水平扩展,不受限制。一个方面,Agent收集日志的能力受限于机器的性能,正常情况下一个Agent可以为单机提供足够服务。另一方面,如果机器比较多,可能受限于后端Collector提供的服务,但Agent到Collector是有LoadBalance机制,使得Collector可以线性扩展提高能力。4.3.2Collector层对于Collector这一层,Agent到Collector是有LoadBalance机制,并且Collector提供无差别服务,所以可以线性扩展。其性能主要受限于Store层提供的能力。4.3.3Store层对于Store这一层来说,Hdfs和Kafka都是分布式系统,可以做到线性扩展。Bypass属于临时的应用,只对应于某一类日志,性能不是瓶颈。4.4Channel的选择Flume1.4.0中,其官方提供常用的MemoryChannel和FileChannel供大家选择。其优劣如下:MemoryChannel:所有的events被保存在内存中。优点是高吞吐。缺点是容量有限并且Agent死掉时会丢失内存中的数据。FileChannel:所有的events被保存在文件中。优点是容量较大且死掉时数据可恢复。缺点是速度较慢。上述两种Channel,优缺点相反,分别有自己适合的场景。然而,对于大部分应用来说,美团希望Channel可以同提供高吞吐和大缓存。基于此,美团开发了DualChannel。DualChannel:基于MemoryChannel和FileChannel开发。当堆积在Channel中的events数小于阈值时,所有的events被保存在MemoryChannel中,Sink从MemoryChannel中读取数据;当堆积在Channel中的events数大于阈值时,所有的events被自动存放在FileChannel中,Sink从FileChannel中读取数据。这样当系统正常运行时,美团可以使用MemoryChannel的高吞吐特性;当系统有异常时,美团可以利用FileChannel的大缓存的特性。4.5和scribe兼容在设计之初,美团就要求每类日志都有一个category相对应,并且Flume的Agent提供AvroSource和ScribeSource两种服务。这将保持和之前的Scribe相对应,减少业务的更改成本。4.6权限控制在目前的日志收集系统中,美团只使用最简单的权限控制。只有设定的category才可以进入到存储系统。所以目前的权限控制就是category过滤。如果权限控制放在Agent端,优势是可以较好地控制垃圾数据在系统中流转。但劣势是配置修改麻烦,每增加一个日志就需要重启或者重载Agent的配置。如果权限控制放在Collector端,优势是方便进行配置的修改和加载。劣势是部分没有注册的数据可能在Agent/Collector之间传输。考虑到Agent/Collector之间的日志传输并非系统瓶颈,且目前日志收集属内部系统,安全问题属于次要问题,所以选择采用Collector端控制。4.7提供实时流美团的部分业务,如实时推荐,反爬虫服务等服务,需要处理实时的数据流。因此美团希望Flume能够导出一份实时流给Kafka/Storm系统。一个非常重要的要求是实时数据流不应该受到其它Sink的速度影响,保证实时数据流的速度。这一点,美团是通过Collector中设置不同的Channel进行隔离,并且DualChannel的大容量保证了日志的处理不受Sink的影响。5系统监控对于一个大型复杂系统来说,监控是必不可少的部分。设计合理的监控,可以对异常情况及时发现,只要有一部手机,就可以知道系统是否正常运作。对于美团的日志收集系统,美团建立了多维度的监控,防止未知的异常发生。5.1发送速度,拥堵情况,写Hdfs速度通过发送给zabbix的数据,美团可以绘制出发送数量、拥堵情况和写Hdfs速度的图表,对于超预期的拥堵,美团会报警出来查找原因。下面是FlumeCollectorHdfsSink写数据到Hdfs的速度截图:下面是FlumeCollector的FileChannel中拥堵的events数据量截图:5.2flume写hfds状态的监控Flume写入Hdfs会先生成tmp文件,对于特别重要的日志,美团会每15分钟左右检查一下各个Collector是否都产生了tmp文件,对于没有正常产生tmp文件的Collector和日志美团需要检查是否有异常。这样可以及时发现Flume和日志的异常.5.3日志大小异常监控对于重要的日志,美团会每个小时都监控日志大小周同比是否有较大波动,并给予提醒,这个报警有效的发现了异常的日志,且多次发现了应用方日志发送的异常,及时给予了对方反馈,帮助他们及早修复自身系统的异常。通过上述的讲解,美团可以看到,基于Flume的美团日志收集系统已经是具备高可用性,高可靠性,可扩展等特性的分布式服务。改进和优化下面,美团将会讲述在实际部署和使用过程中遇到的问题,对Flume的功能改进和对系统做的优化。1Flume的问题总结在Flume的使用过程中,遇到的主要问题如下:a.Channel“水土不服”:使用固定大小的MemoryChannel在日志高峰时常报队列大小不够的异常;使用FileChannel又导致IO繁忙的问题;b.HdfsSink的性能问题:使用HdfsSink向Hdfs写日志,在高峰时间速度较慢;c.系统的管理问题:配置升级,模块重启等;2Flume的功能改进和优化点从上面的问题中可以看到,有一些需求是原生Flume无法满足的,因此,基于开源的Flume美团增加了许多功能,修改了一些Bug,并且进行一些调优。下面将对一些主要的方面做一些说明。2.1增加Zabbixmonitor服务一方面,Flume本身提供了http,ganglia的监控服务,而美团目前主要使用zabbix做监控。因此,美团为Flume添加了zabbix监控模块,和sa的监控服务无缝融合。另一方面,净化Flume的metrics。只将美团需要的metrics发送给zabbix,避免zabbixserver造成压力。目前美团最为关心的是Flume能否及时把应用端发送过来的日志写到Hdfs上,对应关注的metrics为:Source:接收的event数和处理的event数Channel:Channel中拥堵的event数Sink:已经处理的event数2.2为HdfsSink增加自动创建index功能首先,美团的HdfsSink写到hadoop的文件采用lzo压缩存储。HdfsSink可以读取hadoop配置文件中提供的编码类列表,然后通过配置的方式获取使用何种压缩编码,美团目前使用lzo压缩数据。采用lzo压缩而非bz2压缩,是基于以下测试数据:其次,美团的HdfsSink增加了创建lzo文件后自动创建index功能。Hadoop提供了对lzo创建索引,使得压缩文件是可切分的,这样HadoopJob可以并行处理数据文件。HdfsSink本身lzo压缩,但写完lzo文件并不会建索引,美团在close文件之后添加了建索引功能。JavaCode复制内容到剪贴板 /** * Rename bucketPath file from .tmp to permanent location. */ private void renameBucket() throws IOException, InterruptedException { if(bucketPath.equals(targetPath)) { return; } final Path srcPath = new Path(bucketPath); final Path dstPath = new Path(targetPath); callWithTimeout(new CallRunner() { @Override public Object call() throws Exception { if(fileSystem.exists(srcPath)) { // could block LOG.info("Renaming " + srcPath + " to " + dstPath); fileSystem.rename(srcPath, dstPath); // could block //index the dstPath lzo file if (codeC != null && ".lzo".equals(codeC.getDefaultExtension()) ) { LzoIndexer lzoIndexer = new LzoIndexer(new Configuration()); lzoIndexer.index(dstPath); } } return null; } }); } 2.3增加HdfsSink的开关美团在HdfsSink和DualChannel中增加开关,当开关打开的情况下,HdfsSink不再往Hdfs上写数据,并且数据只写向DualChannel中的FileChannel。以此策略来防止Hdfs的正常停机维护。2.4增加DualChannelFlume本身提供了MemoryChannel和FileChannel。MemoryChannel处理速度快,但缓存大小有限,且没有持久化;FileChannel则刚好相反。美团希望利用两者的优势,在Sink处理速度够快,Channel没有缓存过多日志的时候,就使用MemoryChannel,当Sink处理速度跟不上,又需要Channel能够缓存下应用端发送过来的日志时,就使用FileChannel,由此美团开发了DualChannel,能够智能的在两个Channel之间切换。其具体的逻辑如下:JavaCode复制内容到剪贴板/*** * putToMemChannel indicate put event to memChannel or fileChannel * takeFromMemChannel indicate take event from memChannel or fileChannel * */ private AtomicBoolean putToMemChannel = new AtomicBoolean(true); private AtomicBoolean takeFromMemChannel = new AtomicBoolean(true); void doPut(Event event) { if (switchon && putToMemChannel.get()) { //往memChannel中写数据 memTransaction.put(event); if ( memChannel.isFull() || fileChannel.getQueueSize() > 100) { putToMemChannel.set(false); } } else { //往fileChannel中写数据 fileTransaction.put(event); } } Event doTake() { Event event = null; if ( takeFromMemChannel.get() ) { //从memChannel中取数据 event = memTransaction.take(); if (event == null) { takeFromMemChannel.set(false); } } else { //从fileChannel中取数据 event = fileTransaction.take(); if (event == null) { takeFromMemChannel.set(true); putToMemChannel.set(true); } } return event; } 2.5增加NullChannelFlume提供了NullSink,可以把不需要的日志通过NullSink直接丢弃,不进行存储。然而,Source需要先将events存放到Channel中,NullSink再将events取出扔掉。为了提升性能,美团把这一步移到了Channel里面做,所以开发了NullChannel。2.6增加KafkaSink为支持向Storm提供实时数据流,美团增加了KafkaSink用来向Kafka写实时数据流。其基本的逻辑如下:JavaCode复制内容到剪贴板public class KafkaSink extends AbstractSink implements Configurable { private String zkConnect; private Integer zkTimeout; private Integer batchSize; private Integer queueSize; private String serializerClass; private String producerType; private String topicPrefix; private Producer producer; public void configure(Context context) { //读取配置,并检查配置 } @Override public synchronized void start() { //初始化producer } @Override public synchronized void stop() { //关闭producer } @Override public Status process() throws EventDeliveryException { Status status = Status.READY; Channel channel = getChannel(); Transaction tx = channel.getTransaction(); try { tx.begin(); //将日志按category分队列存放 Map topic2EventList = new HashMap(); //从channel中取batchSize大小的日志,从header中获取category,生成topic,并存放于上述的Map中; //将Map中的数据通过producer发送给kafka tx.commit(); } catch (Exception e) { tx.rollback(); throw new EventDeliveryException(e); } finally { tx.close(); } return status; } } 2.7修复和scribe的兼容问题Scribed在通过ScribeSource发送数据包给Flume时,大于4096字节的包,会先发送一个Dummy包检查服务器的反应,而Flume的ScribeSource对于logentry.size()=0的包返回TRY_LATER,此时Scribed就认为出错,断开连接。这样循环反复尝试,无法真正发送数据。现在在ScribeSource的Thrift接口中,对size为0的情况返回OK,保证后续正常发送数据。3.Flume系统调优经验总结3.1基础参数调优经验HdfsSink中默认的serializer会每写一行在行尾添加一个换行符,美团日志本身带有换行符,这样会导致每条日志后面多一个空行,修改配置不要自动添加换行符;lc.sinks.sink_hdfs.serializer.appendNewline=false调大MemoryChannel的capacity,尽量利用MemoryChannel快速的处理能力;调大HdfsSink的batchSize,增加吞吐量,减少hdfs的flush次数;适当调大HdfsSink的callTimeout,避免不必要的超时错误;3.2HdfsSink获取Filename的优化HdfsSink的path参数指明了日志被写到Hdfs的位置,该参数中可以引用格式化的参数,将日志写到一个动态的目录中。这方便了日志的管理。例如美团可以将日志写到category分类的目录,并且按天和按小时存放:lc.sinks.sink_hdfs.hdfs.path=/user/hive/work/orglog.db/%{category}/dt=%Y%m%d/hour=%HHdfsSink中处理每条event时,都要根据配置获取此event应该写入的Hdfspath和filename,默认的获取方法是通过正则表达式替换配置中的变量,获取真实的path和filename。因为此过程是每条event都要做的操作,耗时很长。通过美团的测试,20万条日志,这个操作要耗时6-8s左右。由于美团目前的path和filename有固定的模式,可以通过字符串拼接获得。而后者比正则匹配快几十倍。拼接定符串的方式,20万条日志的操作只需要几百毫秒。3.3HdfsSink的b/m/s优化在美团初始的设计中,所有的日志都通过一个Channel和一个HdfsSink写到Hdfs上。美团来看一看这样做有什么问题。首先,美团来看一下HdfsSink在发送数据的逻辑:JavaCode复制内容到剪贴板//从Channel中取batchSize大小的events for (txnEventCount = 0; txnEventCount
网站出现了500错误,找了半天原因,发现是服务器密码变了,导致网站出现了500错误。在解决方法中,给我印象最深刻的是我修改了服务器密码后网站打不开了,我记得当时改了3个钟头,所有的方法都试过了,还是不行,最后终于找到原因,原来是改变了服务器密码,却没有改变网站授权密码,因此网站出现500错误。要想解决问题,就要打开IIS,找到出现问题的网站,然后找到IIS右边的基本设置。情况一:点击基本设置,我们会看到下图所示的界面。点击测试连接,我们会发现该网站没有通过身份验证和授权,没有通过授权没关系,没有通过身份验证网站可就打不来了。点击【连接为】找到【特定用户】,点击【设置】,输入服务器新修改的用户名和密码。点击确定,我们会发现我们通过了身份验证和授权,然后就可以打开我们的网站了。情况二:网站开启,而应用程序池没有开启。按照常规,IIS上网站开启,应用程序池自然开启,可有时确实例外,因此在检查网站的时候也要检查应用程序池。情况三:织梦网站根目录的index.php文件被人有意或无意篡改,如果是这种情况找一个好的文件替换一下就OK了。情况四:IIS绑定网站的路径错误,我们应该绑定12345,却绑定了123456,这也会造成网站500错误。情况五:IIS关闭,如果IIS关闭,服务器也会报500错误的,这时候我们需要重启IIS,重启之后,网站打开成功。