关于优化:记一次对代码优化的经历

1. 复杂度不会隐没,只会转移新建工具类文件,把一些判断的逻辑抽离进来2. 形象,就是像做数学题一样总结一种办法把具备雷同作用的逻辑提取出他们的公式先拆分,后重构在传参为空时,给出默认值和校验提醒,避免一些调用引发的BUG3. 默认值,数据类型校验和try/catch/finally语句,是爱护程序平安运行的三剑客4. 文件放入文件夹,分类排好,把代码依照性能拆散,放入相应的文件夹中就像拾掇衣服一样,把不同节令的打底衣服,外套,内衣裤分凋谢5.缩小因为版本更迭产生的反复而无用的逻辑,代码,变量和死循环依据热力学第二定律,孤立零碎的熵永不主动缩小,熵在可逆过程中不变,在不可逆过程中减少。整个我的项目只调用一次的函数形象应该和调用者函数合并6.编写文档和正文7.在重构时就会领会到面向对象比照面向流程的长处一但流程变动,整个链条都会断掉知乎答主justjavac的一句话解决了我脑中始终以来的困惑:“优化的实质就是给V8写代码。如果是业务代码,要优雅,写给人看;如果是库代码,要高效,写给V8去运行。”原话起源:https://zhuanlan.zhihu.com/p/...

January 30, 2022 · 1 min · jiezi

关于优化:屏幕后处理效果系列之常见后处理效果篇

1.1 前言在系列第一篇中,咱们学习了用于图像含糊的相干算法,在探索这些算法效率高下的同时,深刻了解了这些算法的核心思想,这些思维将会被利用于许多后处理成果中。接下来,咱们将学习一些常见的屏幕后处理成果的实现形式。 1.2 基础知识 Bloom(Glow)特效是游戏中利用最广泛的一种屏幕后处理特效。体现为高光物体带有泛光成果,使画面的光影体现更加优良。Bloom通常会搭配HDR和ToneMapping来失去更好的成果。 首先咱们须要简略理解一下什么是HDR和ToneMapping。电子设备能够显示的亮度范畴无限,对于RGB三个通道,每个通道8bit,由此以来能够显示(2^8)^3=16777216种颜色,这个范畴被称为低动静范畴(Low-Dynamic Range)。这个范畴是一个教训亮度范畴,在这个范畴内不会对人眼产生挫伤,而超出这个范畴的色彩空间被称为高动静范畴(High-Dynamic Range)。现实生活中的亮度是没有范畴限度的,想要表白更多的亮度,RGB每个通道须要裁减到12位乃至更多。运算实现后,在进行输入时须要通过ToneMapping将HDR空间中的色彩映射到LDR空间中,再在显示器上进行显示。 现实生活中,Bloom产生的起因是人眼晶状体的散射,例如在强光照耀的时候,会有看不清的状况产生。于是应用Bloom来体现高光溢出的成果从而模仿这种景象。 对于HDR 、 LDR、ToneMapping的更多知识点能够学习《漫谈HDR和色调治理》。 通用的Bloom特效的制作形式是将图像高亮的局部通过卷积进行含糊之后再叠加到原图像上。在《屏幕后处理成果系列之图像含糊算法篇》课程中,咱们介绍了各种图像含糊的算法,抉择应用效率最高的Dual Blur的形式进行含糊解决。 1.3 Unity实现 首先设定阈值,采样图片,对于图片中超过阈值的像素被断定为高亮局部。高亮局部,保留色彩,其余部分归0,存储在一张RT中。 BloomMaterial.SetFloat("_Threhold", threshold);BloomMaterial.SetFloat("_Intensity", intensity);RenderTexture HighLighrRT=RenderTexture.GetTemporary(src.descriptor);Graphics.Blit(src, HighLighrRT, BloomMaterial, 0);Boom.shader:Pass{ CGPROGRAM #pragma vertex vert_img #pragma fragment frag fixed4 frag(v2f_img input) : SV_Target { float4 color = tex2D(_MainTex, input.uv); return max(color - _Threhold, 0) * _Intensity; } ENDCG}接着,将失去的高亮局部进行含糊解决后叠加在原图上: Pass{ CGPROGRAM #pragma vertex vert_img #pragma fragment frag sampler2D _CompositeTex; float4 _CompositeColor; fixed4 frag(v2f_img input) : SV_Target{ float4 mainColor = tex2D(_MainTex,input.uv); float4 compositeColor = tex2D(_CompositeTex, input.uv); return saturate(mainColor + compositeColor);} ENDCG}失去成果如图: ...

August 30, 2021 · 1 min · jiezi

关于优化:两行代码显存翻倍2080Ti也能当V100来用这个炼丹神器真牛

| 作者:邓哲也 | 旷视 MegEngine 基础架构组工程师 在近年来的深度学习畛域,许多钻研机构和研究者通过增大模型的参数量来晋升模型的体现,获得了十分显著的成绩,一次次令业界称奇。这主观上使得“扩充模型的尺寸”简直一度成为各家竞相追赶的惟一指标。几年间,最先进的模型的参数量已减少了成千盈百倍,但每张 GPU 的显存大小却简直没有增长。这导致大模型的训练往往依赖于巨量的 GPU 卡数。于是,很多想法杰出、有钻研激情的研究者单纯因为资金不足,难以持续从事深度学习的钻研,而近年里的重要科研成果也简直都是被几家头部钻研机构所垄断。从长远看来,这种趋势未必有利于深度学习这门迷信的倒退提高。 作为深度学习训练框架的开发者,咱们除了帮忙用户在一个训练任务中利用更多的 GPU 卡(即分布式训练)之外,还采纳了各种技术手段,以减少每张 GPU 上显存的利用效率,升高研究者的资金老本。减少显存利用效率的常见办法有: 生命周期不重叠的算子共享显存;通过额定的数据传输缩小显存占用;通过额定的计算缩小显存占用。目前已有的办法中大多都要求计算图是动态的,随着越来越多的框架反对动态图模式,是否在动态图训练时最大水平地利用无限的显存资源,成为了评估深度学习框架性能的重要指标。MegEngine 在近期公布的v1.4 版本中,通过引入 DTR[[1]](https://arxiv.org/abs/2006.09... 技术并进行进一步的工程优化,提供了一种通过额定计算缩小显存占用的路径,从而让小显存也能训练大模型,享受更大 batch size 所带来的训练收益。在 2080Ti 上,ResNet-50、ShuffleNet 等网络的最大 batch size 能够达到原来的 3 倍以上。本篇文章将从工程实现的角度重点介绍在 MegEngine 中如何应用 DTR 技术对动态图显存进行优化的。 一、背景介绍1.1 计算图在深度学习畛域,神经网络模型实质上都能够用一个计算图来示意。它的训练过程能够分为三个局部:前向流传,反向流传,参数更新。 以 y=wx+b 为例,它的前向计算过程为输出 x 和参数 w 首先通过乘法运算失去两头后果 p,接着 p 和参数 b 通过加法运算,失去右侧最终的输入 y。 反向流传需要求出 y 对于 w 与 b 的导数,首先求出 y 对于 p 的导数是 1,p 对于 w的导数是 x,应用链式法则就能够失去 y对于 w 的导数是 x。 ...

August 3, 2021 · 3 min · jiezi

关于优化:性能优化提升

1.节流 从滚动条监听的例子说起: 监听浏览器滚动事件,返回以后滚条与顶部的间隔 function showTop() { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log("滚动条地位: " + scrollTop);}window.onscroll = showTop;在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么水平呢?以chrome为例,咱们能够点击选中一个页面的滚动条,而后点击一次键盘的【向下方向键】,会发现函数执行了8-9次! 然而实际上咱们并不需要如此高频的反馈,毕竟浏览器的性能是无限的,不应该节约在这里,所以接着探讨如何优化这种场景。 防抖(debounce) 在第一次触发事件时,不立刻执行函数,而是给出一个期限值,比方200ms,而后, 如果在200ms内没有再次触发滚动事件,那么就执行函数如果200ms内再次触发滚动事件,那么以后的计时勾销,重现开始计时成果:实现如果短时间内触发对立事件,那么只会执行一次函数实现:既然后面都提到了计时,那实现的要害就在于setTimeout这个函数,因为还须要一个变量来保留计时,思考保护全局污浊,能够借助闭包来实现: /** * fn: function 须要防抖的函数 * delay: number 防抖期限值 */function debounce(fn, delay) { let timer = null; // 借助闭包 return function () { if(timer) { clearTimeout(time); } timer = setTimeout(fn, delay); }}window.onscroll = debounce(showTop, 1000);window.onscroll = debounce(showTop, 1000);此时会发现,必须在进行滚动1秒当前,才会打印出滚动条地位。 到这里,曾经把防抖实现了,当初给出定义: 对于短时间内间断触发的事件(下面的滚动事件),防抖的含意就是让某个工夫期限(如下面的1000毫秒)内,事件处理函数只执行一次。 2.节流(throttle) 持续思考,应用下面的防抖计划来解决问题的后果是: 如果在限定时间段内,一直触发滚动事件(比方某个用户闲着无聊,按住滚动一直的拖来拖去),只有不进行触发,实践上就永远不会输入以后间隔顶部的间隔。 然而如果产品同学的冀望解决计划是:即便用户一直拖动滚动条,也能在某个工夫距离之后给出反馈呢? 咱们能够设计一种相似管制阀门一样定期凋谢的函数,也就是让函数执行一次后,在某个时间段内临时生效,过了这段时间后再从新激活 成果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的工夫期限内不再工作,直至过了这段时间才从新失效。 ...

July 22, 2021 · 1 min · jiezi

关于优化:多维分析后台实践-2数据类型优化

【摘要】用实例、分步骤,具体解说多维分析(OLAP)的实现。点击理解多维分析后盾实际 2:数据类型优化 实际指标本期指标是练习将数据库读出的数据,尽可能转换为有利于性能优化的数据类型,例如:小整数和浮点数。 实际的步骤: 1、 筹备根底宽表:批改上期的代码,实现数据类型优化存为组表文件。 2、 拜访根底宽表:批改上期的代码,在传入参数放弃不变的前提下,查问数据转换之后的组表文件,后果集也要返回原有的数据显示值。对于这个要求,SQL 是无奈实现传入参数和后果集的转换的,所以拜访宽表的代码以 SPL 为例。 本期样例宽表不变,仍然为 customer 表。从 Oracle 数据库中取出宽表数据的 SQL 语句是 select * from customer。执行后果如下图: 其中字段包含: CUSTOMER_ID NUMBER(10,0), 客户编号 FIRST_NAME VARCHAR2(20), 名 LAST_NAME VARCHAR2(25), 姓 PHONE_NUMBER VARCHAR2(20), 电话号码 BEGIN_DATE DATE, 开户日期 JOB_ID VARCHAR2(10), 职业编号 JOB_TITLE VARCHAR2(32), 职业名称 BALANCE NUMBER(8,2), 余额 EMPLOYEE_ID NUMBER(4,0), 开户雇员编号 DEPARTMENT_ID NUMBER(4,0), 分支机构编号 DEPARTMENT_NAME VARCHAR2(32), 分支构造名称 FLAG1 CHAR(1), 标记 1 FLAG2 CHAR(1), 标记 2 FLAG3 CHAR(1), 标记 3 FLAG4 CHAR(1), 标记 4 FLAG5 CHAR(1), 标记 5 ...

January 22, 2021 · 3 min · jiezi

关于优化:2020年度大赏-UWA问答精选

UWA每周推送的知识型栏目《厚积薄发 | 技术分享》曾经随同大家走过了252个工作周。精选了2020年十大精彩问答分享给大家,期待2021年UWA问答持续有您的陪伴。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) Q1:IL2CPP的内存问题最近看问答下面有个对于IL2CPP和Mono的比照,看到IL2CPP内存冲高会降落。对于这个,我问了Unity的官网技术,答复是:你好,Unity有本人的GC机制,为了防止频繁向操作系统申请/开释内存,Reserved Mono值会放弃在肯定区间内,达到某些条件或在某些非凡状况才会触发GC。 有人说是:“内存池治理逻辑都是一样的,属于下层治理一样。它们只是两头语言不一样而已,也是只涨不降。”也有其余大佬说是IL2CPP冲高会降落。当初很困惑,求解答。 A:看下来题主说的内存冲高不降,波及两个指标,一个是Profiler里的Reserved Mono,一个是设施内存(PSS)。目前的确没有权威的文档阐明这一点,所以上面通过实在数据来阐明一下。 先说第一个(Reserved Mono)。 在Script Backend是Mono的状况下,如果抉择的是旧版本里的Mono 2.x,或者新版本里的 .Net 3.5(Runtime Version),那么这个值是只升不降的。比方这个数据,Unused曾经很高了,但也不会降落: 同样在Script Backend是Mono的状况下,如果抉择的是.Net 4.x (Runtime Version),那么这个值是能够降落的(但不确定具体是从哪个版本开始的)。比方这个数据,能够看出尽管会降落,也并不是频繁执行降落操作的: 最初Script Backend是IL2CPP的状况下,那么这个值也是能够降落的。比方这个数据,看上去和下面的状况相差不是太大: 而对于第二个,设施内存。这个就和安卓零碎的内存管理机制无关了,即便Unity把Reserved Mono升高了,缩小了本身的内存占用,零碎也不肯定会立刻会把这块内存开释,所以这里的行为就很难说分明了。 该答复由UWA提供 Q2:加载配置内存过大问题配置表太多占用内存过大时,除了采纳Sqlite,还有什么好的解决办法没有,有没有大佬是否指导下。FlatBuffer不必全副进内存吗?如果不全副进内存,访问速度如何呢? A1:第一问题参考如下: 能够针对反复数据进行剔除,尤其是一些字符串的配置。在配置导出时把这样的数据提取一份,其余用到的中央只是援用,会节俭不少。数据类型要正当。能够应用相似FlatBuffer/ZeroFormatter的提早加载的思路,在真正应用时再去反序列化。一次游戏过程中理论用到的配置量比拟无限,应用这种策略能够尽可能的缩小不必要数据的加载。第二个问题参考如下:咱们上个我的项目也是到前期优化时遇到相似问题,只是参考了这种思路,并没有进行齐全替换。咱们过后在打包时,会对配置以行为单位,进行Offset和Length的计算,在Runtime阶段,初始加载只会加载每行的ID,对应的这一行的Offset和Length,而后后续逻辑调用配置表接口拿数据的时候,如果发现没有反序列化过,就依据Offset和Length再去构建一下相应的数据提供给下层。访问速度的话必定不如开始间接全副加载好,但咱们测下来影响不大。 感激范君@UWA问答社区提供了答复 A2:字符串吃内存不说了,尽量少用或者复用。表格中比拟多的会是那种:攻打-1000;进攻-2000;血量-3000,每个int都是4个字节,数量多了会顶不住。这种能够思考用一个int32/int64/uint32/uint64 去存多个数值。感激萧小俊@UWA问答社区提供了答复 Q3:Instruments如何看Mono内存调配例如在调配了一个10MB数组,对应在Unity Profiler中会看到开拓了至多10MB大小的Mono内存。 那么在Instruments中,如何查看调配的内存信息呢?Allocations中的信息是此过程中调配的所有内存信息吗,尝试调配过100MB内存,Allocations中的统计没有任何增长。 A:我这边也做了测试: 创立了100MB大小的int数组,Size理论应该是400MB。 而后到Profiler察看: 能够看到ManagedHeap正确调配了这400MB的空间。 而后打包iOS后到xCode运行,运行前首先吧Run这个Scheme的Malloc Stack勾上: Run当前点选Memory并导出Memory Graph来察看: 因为应用程序的内存都是在VirtualMemory空间调配的,因而查看VM Regions的VM_ALLOCATE局部。 于是就可已发现128X3+16刚好400MB的调配。调用堆栈也很好确定: 正式咱们的测试代码。 而后咱们来看Instruments。首先是Allocations局部,有一点要留神,该栏的下部有一些选项: 留神最初一个选项,如果抉择第一个:All Heap & Anonymous VM,All Heap对应App理论调配的物理空间,不蕴含VM, Anonymous VM的官网解释是:interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions。 ...

January 11, 2021 · 3 min · jiezi

关于优化:2020年度大赏-UWA问答精选

UWA每周推送的知识型栏目《厚积薄发 | 技术分享》曾经随同大家走过了252个工作周。精选了2020年十大精彩问答分享给大家,期待2021年UWA问答持续有您的陪伴。 UWA 问答社区:answer.uwa4d.comUWA QQ群2:793972859(原群已满员) Q1:IL2CPP的内存问题最近看问答下面有个对于IL2CPP和Mono的比照,看到IL2CPP内存冲高会降落。对于这个,我问了Unity的官网技术,答复是:你好,Unity有本人的GC机制,为了防止频繁向操作系统申请/开释内存,Reserved Mono值会放弃在肯定区间内,达到某些条件或在某些非凡状况才会触发GC。 有人说是:“内存池治理逻辑都是一样的,属于下层治理一样。它们只是两头语言不一样而已,也是只涨不降。”也有其余大佬说是IL2CPP冲高会降落。当初很困惑,求解答。 A:看下来题主说的内存冲高不降,波及两个指标,一个是Profiler里的Reserved Mono,一个是设施内存(PSS)。目前的确没有权威的文档阐明这一点,所以上面通过实在数据来阐明一下。 先说第一个(Reserved Mono)。 在Script Backend是Mono的状况下,如果抉择的是旧版本里的Mono 2.x,或者新版本里的 .Net 3.5(Runtime Version),那么这个值是只升不降的。比方这个数据,Unused曾经很高了,但也不会降落: 同样在Script Backend是Mono的状况下,如果抉择的是.Net 4.x (Runtime Version),那么这个值是能够降落的(但不确定具体是从哪个版本开始的)。比方这个数据,能够看出尽管会降落,也并不是频繁执行降落操作的: 最初Script Backend是IL2CPP的状况下,那么这个值也是能够降落的。比方这个数据,看上去和下面的状况相差不是太大: 而对于第二个,设施内存。这个就和安卓零碎的内存管理机制无关了,即便Unity把Reserved Mono升高了,缩小了本身的内存占用,零碎也不肯定会立刻会把这块内存开释,所以这里的行为就很难说分明了。 该答复由UWA提供 Q2:加载配置内存过大问题配置表太多占用内存过大时,除了采纳Sqlite,还有什么好的解决办法没有,有没有大佬是否指导下。FlatBuffer不必全副进内存吗?如果不全副进内存,访问速度如何呢? A1:第一问题参考如下: 能够针对反复数据进行剔除,尤其是一些字符串的配置。在配置导出时把这样的数据提取一份,其余用到的中央只是援用,会节俭不少。数据类型要正当。能够应用相似FlatBuffer/ZeroFormatter的提早加载的思路,在真正应用时再去反序列化。一次游戏过程中理论用到的配置量比拟无限,应用这种策略能够尽可能的缩小不必要数据的加载。第二个问题参考如下:咱们上个我的项目也是到前期优化时遇到相似问题,只是参考了这种思路,并没有进行齐全替换。咱们过后在打包时,会对配置以行为单位,进行Offset和Length的计算,在Runtime阶段,初始加载只会加载每行的ID,对应的这一行的Offset和Length,而后后续逻辑调用配置表接口拿数据的时候,如果发现没有反序列化过,就依据Offset和Length再去构建一下相应的数据提供给下层。访问速度的话必定不如开始间接全副加载好,但咱们测下来影响不大。 感激范君@UWA问答社区提供了答复 A2:字符串吃内存不说了,尽量少用或者复用。表格中比拟多的会是那种:攻打-1000;进攻-2000;血量-3000,每个int都是4个字节,数量多了会顶不住。这种能够思考用一个int32/int64/uint32/uint64 去存多个数值。感激萧小俊@UWA问答社区提供了答复 Q3:Instruments如何看Mono内存调配例如在调配了一个10MB数组,对应在Unity Profiler中会看到开拓了至多10MB大小的Mono内存。 那么在Instruments中,如何查看调配的内存信息呢?Allocations中的信息是此过程中调配的所有内存信息吗,尝试调配过100MB内存,Allocations中的统计没有任何增长。 A:我这边也做了测试: 创立了100MB大小的int数组,Size理论应该是400MB。 而后到Profiler察看: 能够看到ManagedHeap正确调配了这400MB的空间。 而后打包iOS后到xCode运行,运行前首先吧Run这个Scheme的Malloc Stack勾上: Run当前点选Memory并导出Memory Graph来察看: 因为应用程序的内存都是在VirtualMemory空间调配的,因而查看VM Regions的VM_ALLOCATE局部。 于是就可已发现128X3+16刚好400MB的调配。调用堆栈也很好确定: 正式咱们的测试代码。 而后咱们来看Instruments。首先是Allocations局部,有一点要留神,该栏的下部有一些选项: 留神最初一个选项,如果抉择第一个:All Heap & Anonymous VM,All Heap对应App理论调配的物理空间,不蕴含VM, Anonymous VM的官网解释是:interesting VM regions such as graphics- and Core Data-related. Hides mapped files, dylibs, and some large reserved VM regions。 ...

January 11, 2021 · 3 min · jiezi

关于优化:性能优化技巧前半有序时的排序

一、  问题背景与实用场景在对数据集进行排序运算时,有时会遇到这样一种场景:数据集T曾经按字段a有序,而字段b无序,当初咱们要将T按a、b排序,咱们称之为前半有序(a有序)的排序。此时咱们能想到一种优化的排序办法:从T中先取出a值雷同的一组记录,再这一组内对b排序。而后再顺次取出下一组a值雷同的记录,反复这个动作,直到实现T中所有记录的排序。这种办法的益处是不须要对T中所有记录进行大排序,一次只需取出一小组,对内存容量要求大大减低,只需能装下每个小组即可。 遗憾的是SQL并不反对这种优化算法,只能所有记录进行大排序,而SPL提供了对这种算法的反对,上面咱们实例测试一下,并与Oracle作比照。 二、  测试环境与工作测试机有两个Intel2670 CPU,主频2.6G,共16核,内存64G,SSD固态硬盘。在此机上安装虚拟机来测试,设置虚拟机为16核、8G内存。 在虚拟机上创立数据表salesman1,共两个字段:area(字符串)、salesman(字符串),生成数据记录4亿行,按area升序排列,area不同值共2000个,每个area对应salesman为20万个。将此表数据导入Oracle数据库,同时用它生成集算器SPL组表来进行测试。 再建另一张表salesman2作大数据量测试,数据表构造不变,总数据记录20亿行,area值4000个,每个area对应50万个salesman。 测试工作都是要对表依照area、salesman排序。 三、  小数据量测试1.  Oracle测试编写测试SQL如下: select area, salesman from salesman1 order by area, salesman 原本只需这一句简略的SQL即可,不过这个排序后果的输入工夫却十分长,为了缩小输出量,只统计排序过程的用时,咱们不输入排序后的全副后果,而只输入两头地位的一行,也就是行号为2亿的那一行,所以SQL语句改写如下: select area, salesman from (        select area, salesman, rownum rn from (               select area, salesman from salesman1 order by area, salesman        ) ) where rn=200000000; 要多说一句,这个查问其实没有什么业务意义,纯正是为了迫使数据库大排序且防止统计输入工夫的 。 2.  SPL测试编写SPL脚本如下: group@qs中选项s示意对数据集只排序,不分组;选项q示意数据集对分号前的分组表达式(area)是有序的,申请应用前半有序时的排序办法按分号后的表达式(salesman)排序。 四、  大数据量测试1.  Oracle测试编写测试SQL如下: select area, salesman from (        select area, salesman, rownum rn from (               select area, salesman from salesman2 order by area, salesman ...

December 3, 2020 · 1 min · jiezi

关于优化:技术分享-优化案例

作者:杨奇龙网名“北在北方”,目前任职于杭州有赞科技 DBA,次要负责数据库架构设计和运维平台开发工作,善于数据库性能调优、故障诊断。本文起源:原创投稿*爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。前言在数据库表构造变更公布之前,咱们会和开发沟通索引设计是否正当,发现局部开发同学对于索引设计还是有一些常识盲区。本文把常见的案例记录下来,做个剖析,抛砖引玉。 区分度和过滤性先看一段和开发同学日常 sql review 的对话: 下面的对话其实在工作中比拟常见(同时也阐明咱们培训没有到位 T_T),这样的想法会导致开发疏忽选择性比拟低的字段,sql 的执行打算应用 using where 匹配更多的数据行,后果执行耗时比拟,性能就不现实。大部分开发会理解这样的《开发标准》:创立索引要抉择区分度高的字段。他们会认为区分度低的字段不适宜创立索引或者不适宜增加到组合索引外面。然而这样的操作会导致很多慢查。举例来说: select * from tab where a=1 and b=2;场景 1 合乎 a=1 的记录数有 10w 条记录 ,b=2 有 1000 条记录。如果只是创立 idx_a(a),sql 申请通过索引 idx_a 拜访 10w 条件记录,而后还要逐个匹配 10w 条记录中的 status,找到合乎 b=2 的记录。这个动作会导致慢查。如果创立组合索引 idx_ab(a,b),sql 申请通过索引 idx_ab 能够间接定位到 1000 条记录,无需额定的过滤。这样缩小拜访 9900 条记录的工夫,晋升查问速度。 场景 2 合乎 a=1 的有 100 条记录,status=2 有 10 条记录。其实场景 2 因为数据量比拟少,间接拜访 100 条记录和定位到 10 条记录的工夫耗费相差不大,质变不足以引发量变,能够疏忽了。 Tips:创立索引的目标是通过索引尽可能找到匹配 where 条件的行,缩小不必要的回表,进步查问效率;须要辩证地对待区分度比拟低的字段在组合索引中的作用。在组合索引的状况下,咱们不能只是单纯地看字段的区分度,而是要看符合条件的记录数是多少。符合条件的记录越少,性能越好。索引的有序性在优化业务 sql 的过程中,常常发现开发将 order by 的字段增加到组合索引外面,然而仍然有 file sort 产生,导致慢查。这是为什么呢?索引自身是有序的,之所以产生 file sort 阐明组合索引中存在字段在索引中存储的程序和order by 字段的程序不统一,不是严格正相干导致 MySQL 依据后果从新排序。order by 语句利用索引的有序性是有比拟高要求的,组合索引中 order by 之前的字段必须是等值查问,不能是 in、between、<、> 等范畴查问,explain 的 type 是 range 的 sql 都会导致 order by 不能失常利用索引的有序性。入手实际一下,初始化一张表 x ...

November 26, 2020 · 2 min · jiezi

关于优化:什么你的电脑太渣这几招包你搞定-Win10优化教程

每当你们在编程或者工作的时候,就须要关上很多软件,就像这样:而后,你的(渣机)电脑就变成了这样:而后,你的电脑不是变成这样:就是这样:而后,你就会:https://www.bilibili.com/vide...所以,这个时候,咱们就须要搞机对电脑优化一下了。首先,我要介绍两款咱们的优化软件,它们别离是:www.sysinternals.com(RAMMap)和https://coderbag.com/assets/downloads/cpm/currentversion/QuickCpuSetup64.zip(qiuckCPU)在RAMMap中,你只需找到empty这一项(在右上角)点开它,把外面的每一项不反复的点一下你的电脑就变成了这样:再关上qiuckCPU点击向上的箭头(也在右上角)再点击确定就能够了。另外,如果在工作之余,想要玩游戏的人能够……………………期待下一期了。而后,开始咱们的其余教程。首先,关上设置,搜寻告诉与操作,把全副选项都敞开,再把专一助手敞开。找到平板模式,调成不询问,不切换选项,再关上游戏模式(本人找)把游戏DVR和true play敞开(如果你有的话),把多任务处理外面的全副选项敞开,把上面的两个选项改成仅限我只能在应用的桌面,敞开远程桌面。好的,咱们的电脑优化教程就完结了,(制作不易,求打赏)打赏国20光速更新游戏优化教程。咱们下次再见,88.

November 7, 2020 · 1 min · jiezi

关于优化:将-20M-文件从-30-秒压缩到-1-秒我是如何做到的

作者:不学有数的程序员,原文链接:https://urlify.cn/7NRrea压缩20M文件从30秒到1秒的优化过程有一个需要须要将前端传过来的10张照片,而后后端进行解决当前压缩成一个压缩包通过网络流传输入去。之前没有接触过用Java压缩文件的,所以就间接上网找了一个例子改了一下用了,改完当前也能应用,然而随着前端所传图片的大小越来越大的时候,消耗的工夫也在急剧减少,最初测了一下压缩20M的文件居然须要30秒的工夫。压缩文件的代码如下。 这里找了一张2M大小的图片,并且循环十次进行测试。打印的后果如下,工夫大略是30秒。 第一次优化过程-从30秒到2秒进行优化首先想到的是利用缓冲区 BufferInputStream。在FileInputStream中read() 办法每次只读取一个字节。源码中也有阐明。 这是一个调用本地办法与原生操作系统进行交互,从磁盘中读取数据。每读取一个字节的数据就调用一次本地办法与操作系统交互,是十分耗时的。例如咱们当初有30000个字节的数据,如果应用FileInputStream那么就须要调用30000次的本地办法来获取这些数据,而如果应用缓冲区的话(这里假如初始的缓冲区大小足够放下30000字节的数据)那么只须要调用一次就行。因为缓冲区在第一次调用read()办法的时候会间接从磁盘中将数据间接读取到内存中。随后再一个字节一个字节的缓缓返回。 BufferedInputStream外部封装了一个byte数组用于存放数据,默认大小是8192优化过后的代码如下 输入 能够看到相比拟于第一次应用FileInputStream效率曾经晋升了许多了 第二次优化过程-从2秒到1秒应用缓冲区buffer的话曾经是满足了我的需要了,然而秉着学以致用的想法,就想着用NIO中常识进行优化一下。 应用Channel 为什么要用Channel呢?因为在NIO中新出了Channel和ByteBuffer。正是因为它们的构造更加合乎操作系统执行I/O的形式,所以其速度相比拟于传统IO而言速度有了显著的进步。Channel就像一个蕴含着煤矿的矿藏,而ByteBuffer则是派送到矿藏的卡车。也就是说咱们与数据的交互都是与ByteBuffer的交互。 在NIO中可能产生FileChannel的有三个类。别离是FileInputStream、FileOutputStream、以及既能读又能写的RandomAccessFile。 源码如下 咱们能够看到这里并没有应用ByteBuffer进行数据传输,而是应用了transferTo的办法。这个办法是将两个通道进行直连。 这是源码上的形容文字,大略意思就是应用transferTo的效率比循环一个Channel读取进去而后再循环写入另一个Channel好。操作系统可能间接传输字节从文件系统缓存到指标的Channel中,而不须要理论的copy阶段。 copy阶段就是从内核空间转到用户空间的一个过程能够看到速度相比拟应用缓冲区曾经有了一些的进步。 内核空间和用户空间 那么为什么从内核空间转向用户空间这段过程会慢呢?首先咱们需理解的是什么是内核空间和用户空间。在罕用的操作系统中为了爱护零碎中的外围资源,于是将零碎设计为四个区域,越往里权限越大,所以Ring0被称之为内核空间,用来拜访一些关键性的资源。Ring3被称之为用户空间。 用户态、内核态:线程处于内核空间称之为内核态,线程处于用户空间属于用户态那么咱们如果此时应用程序(应用程序是都属于用户态的)须要拜访外围资源怎么办呢?那就须要调用内核中所暴露出的接口用以调用,称之为零碎调用。例如此时咱们应用程序须要拜访磁盘上的文件。此时应用程序就会调用零碎调用的接口open办法,而后内核去拜访磁盘中的文件,将文件内容返回给应用程序。大抵的流程如下 间接缓冲区和非间接缓冲区既然咱们要读取一个磁盘的文件,要废这么大的周折。有没有什么简略的办法可能使咱们的利用间接操作磁盘文件,不须要内核进行直达呢?有,那就是建设间接缓冲区了。 非间接缓冲区:非间接缓冲区就是咱们下面所讲内核态作为中间人,每次都须要内核在两头作为直达。 间接缓冲区:间接缓冲区不须要内核空间作为直达copy数据,而是间接在物理内存申请一块空间,这块空间映射到内核地址空间和用户地址空间,应用程序与磁盘之间数据的存取通过这块间接申请的物理内存进行交互。 既然间接缓冲区那么快,咱们为什么不都用间接缓冲区呢?其实间接缓冲区有以下的毛病。间接缓冲区的毛病: 不平安耗费更多,因为它不是在JVM中间接开拓空间。这部分内存的回收只能依赖于垃圾回收机制,垃圾什么时候回收不受咱们管制。数据写入物理内存缓冲区中,程序就丢失了对这些数据的治理,即什么时候这些数据被最终写入从磁盘只能由操作系统来决定,应用程序无奈再干预。综上所述,所以咱们应用transferTo办法就是间接开拓了一段间接缓冲区。所以性能相比而言进步了许多应用内存映射文件NIO中新出的另一个个性就是内存映射文件,内存映射文件为什么速度快呢?其实起因和下面所讲的一样,也是在内存中开拓了一段间接缓冲区。与数据间接作交互。源码如下 打印如下 能够看到速度和应用Channel的速度差不多的。 应用PipeJava NIO 管道是2个线程之间的单向数据连贯。Pipe有一个source通道和一个sink通道。其中source通道用于读取数据,sink通道用于写入数据。能够看到源码中的介绍,大略意思就是写入线程会阻塞至有读线程从通道中读取数据。如果没有数据可读,读线程也会阻塞至写线程写入数据。直至通道敞开。 Whether or not a thread writing bytes to a pipe will block until another thread reads those bytes我想要的成果是这样的。源码如下: 源码地址https://github.com/modouxiansheng/Doraemon ...

September 14, 2020 · 1 min · jiezi

关于优化:UWA-五周年-我们的长期主义刚刚开始

转眼之间,UWA步入了第六个年头。 UWA,从一个石破天惊的产品,缓缓成长为业界略有出名的服务商。借这个有留念意义的日子,和大家分享一些咱们的故事。 UWA的倒退经验几个过程:从0到0.1,通过工具化解决技术层面的性能问题;从0.1到1,通过体系化优化流程层面的问题。将来,UWA也将继续深耕于优化方面,把一米宽的市场做到一百米深。 从0到0.1:解决上线前的优化问题五年前的UWA,在上海某个园区里的一间小小办公室起航。过后的守业思路很简略,用咱们所把握的技术,去帮忙天下开发者解决上线前后的性能优化问题。那时候Unity引擎在国内的发展势头大增,优化需要正是强烈,咱们在短期工夫内疾速帮到了一群敢于尝鲜的开发者,而当初这群人经常会聊起这句话:哎呀,咱们当年也是见证UWA成长起来的呢。 UWA是一家技术服务型公司,UWA人的价值观之一是用户第一。如何做到?咱们给本人定的规范是:快、精准、有温度。 “快”和“精准”是把产品打造得简略易用、业余精确。咱们置信应用的形式越容易、反馈的效率越高效、剖析和提供的解决方案越精准,用户解决问题就会越简略。 “有温度”是提供人与人沟通的平台,而不是凉飕飕的数字。咱们心愿用户能感触到UWA产品中的温度:敌对性、专业性和亲和性。 从那时起,大家开始逐渐晓得咱们UWA是做优化的,如果性能不达标,那去UWA上提个包测一测。用得好的,降级个VIP,相当于众筹了咱们下个月的研发经费;如果感觉囊中羞涩,每个月的收费提测来用,平时帮咱们打个小广告,就当交个敌人。就这样,咱们交到了越来越多的敌人,UWA的口碑和品牌开始逐渐在行业里累积了起来。 随后,咱们针对过后的外围产品:线上真人真机测试服务做了继续的优化:加了性能简报、行业排名、剖析倡议、优化队列,反对了iOS平台、PC平台、反对了IL2CPP版本... 这里分享一个数据:超过2000多我的项目在咱们平台上提测大于等于5次,并且均匀晋升了20%的性能。 很快,咱们累积了一批外围用户,他们是十分可恶的群体,大部分都十分敌对。在帮忙他们解决问题的过程中,不仅他们感触到了UWA的“有温度”,咱们UWA员工本人也感觉到真正地参加进这个我的项目中,一句“谢谢”都有种世间值得的满足。 从0.1到1:把优化市场做到一百米深如果维持过后的产品服务不去拓展,或者咱们能够轻松很多。但事实是:更多需要向你扑来的时候,你只会感觉本人的能力太局限了。咱们享受到了一些市场红利,但市场号召咱们去做更多有意义的事件。于是咱们开始了从0.1向1的迈进和摸索。 深刻到各大团队中,咱们发现,对于大部分团队来说,优化解决的是短期内的需要,但越是短期的需要,就越须要和工夫赛跑,这种需要的意义其实越低,所以咱们还须要更深刻地把握对于优化的更深层的货色,正如同咱们的一位开发敌人说的,比优化代码更重要的是优化思维,这就要与工夫成为敌人,缓缓积淀累积,工夫越长、价值越高。 过来的两年,咱们提出了性能保障体系,绝对于过来头疼医头脚疼医脚的优化形式,咱们倡议开发团队改善优化流程,随时发现问题、随时解决问题。咱们的产品体系也从刚开始仅有的真人真机测试倒退为残缺的性能品质保障体系,陆续推出了GPM、GOT Online、自动化测试和Pipeline等服务,在不同的研发阶段为开发者的优化之路构筑起多道防线。 在咱们前几年的UWA DAY大会上就有不少团队分享了他们的最佳实际: 喔,原来优化能够这样执行; 原来性能能够做到这样的水平; 甚至有公司会说,喔,原来咱们团队的组织架构能够更加正当... 从0到0.1,是优化性能问题; 从0.1到1, 是优化优化的流程。 从优化到保障,咱们实现了1。 所有的优化,实质上是人思维的扭转解决了产品的技术问题,咱们再来说下人的问题。因为纵然有不错的工具,实质上还是须要人的了解能力更好地执行:为什么要这么优化、实质的起因是什么、可能会有怎么的局限性、下次呈现雷同的问题,咱们是否也能相似地解决...所有的优化引起的提高,实质上都是思维的降级。 咱们所处的时代正在以前所未有的速度在向前一直推动,整个世界近30年来的变动甚至要比再之前300年来的变动还要大。每年甚至每月都会有大量的新技术、新教训不断涌现。常识在爆炸性地增长,但咱们的工夫却并没有减少。所以,无论是行业新人,还是技术牛人,咱们该如何去学习、吸取有用的常识来补充本人的常识体系,是咱们所有人都在面对的难题。 最近两年,咱们继续在UWA学堂上投入精力,初衷很简略,解决企业用人的问题。 问答、学堂、开源库和博客都隶属于常识服务的局部。 开源库帮忙大家更快捷、不便地找到须要的开源解决方案;问答解决的是具象的问题,寻找到更适合的解决答案;学堂旨在流传游戏行业的进阶学习内容,让常识共享有收益,让学习成长有体系;学堂训练营是UWA推出的技术晋升培训课,旨在流传零碎的实践和实用的技能,通过短期内高强度培训和实际,实现学以致用。过来,咱们UWA是常识的搬运工,而当初,咱们更多的是和开发者独特发明常识。常识服务是UWA策略倒退中极为要害的一环,在向指标一直靠近、一步一步实现的过程中,岂但能够促成游戏生态的一直改善,让开发者受害,也能让咱们本人在整个游戏产业中表演更重要的角色,承当更重要的责任。 举重若轻,源于长期保持“利他”二字守业的过程中少不了各种抉择、各种纠结,正如《创业维艰》这本书里写的:“只有3天是逆境,剩下8年全都举步维艰”。 **然而,有2个字能够让咱们举重若轻,那就是“利他”。UWA是一家天生有着To B基因的公司,咱们有着很强的“利他”精力,这就是咱们UWA的基因。 所谓基因,不是外表上所做的业务,而是外在的价值观和做事逻辑。** 当遇到一个超出咱们服务范畴的问题,咱们技术工程师不会间接Say No,而会说“兄弟,你先等等,你们这个我的项目工程过于奢华,咱们再钻研下看看怎么解决”;当遇到用户不分明该买咱们哪个服务的时候,咱们商务共事会说“你们问题次要集中在这里,你买咱们这个就能够了,没必要节约钱多买。就当咱们交给敌人”。 UWA置信,不论什么时代,为客户发明价值是指标,赚钱是后果和奖赏。优良的公司赚取利润,平凡的公司博得人心。 时值UWA五周年之际,UWA品牌形象更新,此次更新代表着UWA产品和服务的大众化、社区化。 浅色形象代表了开发者,与开办uwa4d.com时的愿景4d(for developer)相响应。UWA从成立起始终置信,只有开发者受害,咱们才受害。 往年产生了很多事,很多咱们不违心承受的事件。但宏观是咱们必须承受的,宏观才是咱们能够有所作为的。咱们十分荣幸且将继续成为整个行业后退的后驱车。 咱们将秉承UWA的价值观,保持长期主义,把一米宽的市场做到一百米深。 既往不恋,尽情向前。

August 19, 2020 · 1 min · jiezi

关于优化:发布-Optuna-20

公布 Optuna 2.0咱们很快乐公布 Optuna 的第二个次要版本,Optuna 是一个用 Python 写的的超参数优化(HPO)框架,当初你曾经能够通过 PyPI 和 conda-forge 来获取它。 自往年一月的第一个次要版本公布以来,咱们见证了社区在提出 pull request, issue 和一些不属于常见HPO的用例等方面做出的微小致力。而该框架也已倒退到能够包容大量的新性能,其中包含用于评估超参数重要性的个性,用以节俭计算资源的简单剪枝算法,以及与 LightGBM 的严密集成。本文将向你介绍这些更新,并展现本我的项目自上一个版本起的倒退过程。同时,咱们还将分享将来的布局,以期让你先一步理解将来会引入的新个性。。 如果你曾应用过 Optuna, 则能很快了解上面将介绍的内容。如果你想先理解对本框架,请参考咱们之前的博客(https://medium.com/optuna/optuna-v1-86192cd09be5),它解释了 Optuna 背地的各种概念。 新个性 超参数重要性性能晋升Hyperband Pruning新的 CMA-ES 采样与第三方框架的集成 (Integration)文档与 PyTorch 合作将来布局贡献者新个性上面介绍的是本次公布的版本中一些十分重要的个性。 超参数重要性尽管 Optuna 的设计能够解决任意多的超参数,但通常状况下,咱们倡议放弃尽量少的参数个数,以缩小搜寻空间的维度。因为实际上,在许多状况下,只有很少的参数在确定模型的整体性能中起主导作用。而从 2.0 版开始,咱们 引入了一个新模块 optuna.importance. 该模块能够评估每个超参数对整体性能的重要性,`optuna.importances.get_param_importances`. 该函数承受一个 study 作为参数,返回一个字典,该字典将不同的超参数映射到其各自的重要性数值上,这个数值的浮动范畴为 0.0 到 1.0, 值越高则越重要。同时,你也能够通过批改evaluator 参数来尝试不同的超参数重要性评估算法,其中包含 fANOVA,这是一种基于随机森林的简单算法。因为各种算法对重要性的评估形式不同,因而咱们打算在当前的发行版中减少可选算法的数量。 study.optimize(...)importances = optuna.importance.get_param_importances(study)# Specify which algorithm to use.importances.optuna.importance.get_param_importances( study, evaluator=optuna.importance.FanovaImportanceEvaluator())你不必本人解决这些重要性数据,Optuna 曾经提供了同 `optuna.importance.get_param_importances` 具备雷同接口的函数 `optuna.visualization.plot_param_importances`。它将返回一个 Plotly 图表,这对于可视化剖析很有帮忙。 fig = optuna.visualization.plot_param_importances(study) ...

August 14, 2020 · 2 min · jiezi

HBase-优化

HBase 优化JVM调优内存调优一般安装好的HBase集群,默认配置是给Master和RegionServer 1G的内存,而Memstore默认占0.4,也就是400MB。显然RegionServer给的1G真的太少了。 export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -Xms2g -Xmx2g"export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g"这里只是举例,并不是所有的集群都是这么配置。==要牢记至少留10%的内存给操作系统来进行必要的操作== 如何给出一个合理的JVM 内存大小设置,举一个ambari官方提供的例子吧。 比如你现在有一台16GB的机器,上面有MapReduce服务、 RegionServer和DataNode(这三位一般都是装在一起的),那么建议按 照如下配置设置内存: 2GB:留给系统进程。8GB:MapReduce服务。平均每1GB分配6个Map slots + 2个Reduce slots。4GB:HBase的RegionServer服务1GB:TaskTracker1GB:DataNode如果同时运行MapReduce的话,RegionServer将是除了MapReduce以外使用内存最大的服务。如果没有MapReduce的话,RegionServer可以调整到大概一半的服务器内存。 Full GC调优由于数据都是在RegionServer里面的,Master只是做一些管理操作,所以一般内存问题都出在RegionServer上。 JVM提供了4种GC回收器: 串行回收器(SerialGC)。并行回收器(ParallelGC),主要针对年轻带进行优化(JDK 8 默认策略)。并发回收器(ConcMarkSweepGC,简称CMS),主要针对年老带进 行优化。G1GC回收器,主要针对大内存(32GB以上才叫大内存)进行优化。一般会采取两种组合方案 ParallelGC和CMS的组合方案export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g -XX:+UseParNewGC -XX:+UseConMarkSweepGC" G1GC方案export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=100" 怎么选择呢? 一般内存很大(32~64G)的时候,才会去考虑用G1GC方案。如果你的内存小于4G,乖乖选择第一种方案吧。如果你的内存(4~32G)之间,你需要自行测试下两种方案,孰强孰弱靠实践。测试的时候记得加上命令-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy MSLAB和In Memory Compaction(HBase2.X才有)HBase自己实现了一套以Memstore为最小单元的内存管理机制,称为 MSLAB(Memstore-Local Allocation Buffers) 跟MSLAB相关的参数是: hbase.hregion.memstore.mslab.enabled:设置为true,即打开 MSLAB,默认为true。hbase.hregion.memstore.mslab.chunksize:每个chunk的大 小,默认为2048 * 1024 即2MB。hbase.hregion.memstore.mslab.max.allocation:能放入chunk 的最大单元格大小,默认为256KB,已经很大了。hbase.hregion.memstore.chunkpool.maxsize:在整个memstore 可以占用的堆内存中,chunkPool占用的比例。该值为一个百分 比,取值范围为0.0~1.0。默认值为0.0。 hbase.hregion.memstore.chunkpool.initialsize:在 RegionServer启动的时候可以预分配一些空的chunk出来放到 chunkPool里面待使用。该值就代表了预分配的chunk占总的 chunkPool的比例。该值为一个百分比,取值范围为0.0~1.0,默认值为0.0。在HBase2.0版本中,为了实现更高的写入吞吐和更低的延迟,社区团队对MemStore做了更细粒度的设计。这里,主要指的就是In Memory Compaction。 开启的条件也很简单。 ...

October 16, 2019 · 2 min · jiezi

搜索场景下的智能推荐演变之路

摘要:传统的推荐手段主要还是深度挖掘用户行为和内容本身相似性的价值,包括但不限于协同过滤,内容表征+向量召回,以及各式各样的点击率预估模型,然后这样的推荐行为缺乏内在的逻辑性和可解释性,有一种知其然,不知所以然的体感。本文中,阿里巴巴高级算法专家王悦就为大家分享了搜索场景下的智能推荐演变之路。以下内容根据演讲视频以及PPT整理而成。 点击查看阿里巴巴AI智能专场直播 演讲嘉宾简介:王跃(跃神),阿里巴巴高级算法专家。浙江大学硕士毕业,阿里巴巴高级算法专家,加入阿里巴巴以来一直致力于研究搜索推荐相关技术,相关工作包括自然语言处理,查询词分析技术研究,知识图谱数据构建,实体推荐等多个不同方向。当前是夸克浏览器智能推荐业务业务负责人,致力于推动推荐从传统的用户行为推荐向知识化推荐的升级,从而提升用户信息获取信息的边界,加快信息决策的效率。 本次分享将首先介绍神马搜索在推荐领域有哪些应用场景,之后为大家分享在神马搜索的推荐系统中所做的召回和排序相关的工作。 一、概览场景介绍首先为大家介绍神马搜索的推荐场景有哪些,比如大家在向搜索框输入内容之前,搜索框就会提供一些预置的搜索词,这属于没有搜索Query的推荐。其次,如果大家点击网页之后返回结果,神马搜索会在URL下面提供一些相关的Query,这是与URL本身相关的推荐。再次,还有Query推荐和相关搜索,这中推荐的主要目的是引流,国内的搜索引擎基本上都是商业化的产品,因此通过这样的推荐方法就能够很好地吸引一些流量进来。此外,还有体感比较好的实体推荐,以及在内容消费页面所做的相关推荐。 推荐大致可以分为三个阶段,首先在输入之前,神马搜索引擎会基于用户画像以及其他的一些相关推荐技术将一些内容推荐给用户;第二个阶段就是在搜索的结果页进行推荐;最后一个阶段就是在内容页面上做一些相关推荐。从另外一个维度上来看,推荐也可以分为三个部分,分别为没有Query的推荐、有Query的推荐以及基于URL的推荐。 技术大图正如下图所展示的,推荐的业务应用场景非常多,因此无论是从横向还是纵向上进行划分,都可以将推荐划分为多个视角。而如果对于每种推荐都从头到尾搭建一套系统,那么成本将会非常高,而UC团队有一套比较通用的技术体系来支撑如下图所示的推荐相关业务。搜索场景下智能推荐的技术大图可以大致分为三个部分,最底层是数据以及数据相关的梳理;其上层就是通过召回以及排序等手段对于数据进行一定的处理;最上面一层就是使用处理好的数据来支撑业务。 对于上层大部分的推荐场景而言,所采用的召回方法基本都是相同的,而所采用的排序方法往往不同。比如对于预置词这种业务而言,它是没有Query的,因此在做模型设计的时候就无法利用这些信息。 二、召回接下来为大家整体地介绍一下推荐系统中的召回体系,在本次分享中只会涉及其中比较通用的4种召回方法,但实际上召回体系远远不止这4种,一些比较通用的召回方法没有在本文中列出。 用户行为召回在召回部分介绍的第一种方法就是用户行为召回,也就是去深挖用户行为的价值。用户行为的挖掘是搜索引擎推荐的重要环节,这部分会针对于用户行为做两件事情。第一件事情就是从Session的角度来分析哪些Query经常会出现在一起,这样分析也会遇到一些问题,比如首先要去区分Session里面不同的Query类型,在搜索引擎里面可以自己主动地发起一次搜索,也可以自己去点击一些推荐结果。但是这两种行为存在一定的区别,比如主动搜索和被动通过推荐来搜索是不同的,主动搜索行为往往会获得较高的分数,如果在比较靠后的位置点击了推荐结果和在相对比较靠前的位置点击了推荐结果的行为也是不同的。因此,在这里需要对于不同类型的行为做一些权重计算,同时做一些比较机器化的规则,比如在某一个Session里面,某一个Query是用户最后一次搜索,此时就需要去考虑这个Query是不是已经满足了用户需求,因此会对于这些Query加一定的权重。 第二个问题就是时效性优化问题,对于一些头部的Query而言,可能一天之内就能达到几万甚至十万的量级。对于这样的Query,通常的做法就是拉一个时间窗口去看所有Session里面Query的情况如何。但实际上对于这些头部的Query没有任何意义,因为其一天的数据就足够分析了,因此在这种情况下会做一些采样;对于一些长尾的Query则会做一些时间窗口的拉长操作。第三个问题是稀疏优化,对于前面所提到的基于URL的推荐而言,通常的做法就是收集用户点击了URL之后又搜索了哪些Query的行为,但是这种情况下点击的URL往往是很稀疏的,因此会使用URL下面本身的一些与Title相似的Doc共享推荐的List实现基于文本的泛化,或者通过相似Query共享推荐List实现基于行为的泛化,这样一来推荐的效果和覆盖率都会有极大的提升。 行为分析下图展示的是协同过滤算法,但是经典的协同过滤算法往往存在一些问题,比如同一个Item权重的分配而言,在行为非常丰富的用户和行为较少的用户之间,可能更加倾向于前者。 但是这样的做法并不一定合理,因此我们复用了集团的一些成果,做了两点主要的改进,第一个就是尽量地降低行为特别丰富的用户的比重,使得其相对比较平滑。第二个就是构建如上图所示的菱形结构,进而达到闭环的效果,使得推荐的理由更加强烈一些。综上所述,可以从入度出度、行为丰富度不同等闭环的结构上面做优化,来提升整体协同过滤类算法的效果。 标签召回基于标签的召回与基于用户画像的召回非常类似,对于用户画像而言,现在业界比较传统的做法就是在用户身上打上各种各样的标签,比如性别、年龄以及爱好等。因此,这里将基于标签的召回和基于用户画像的召回合在一起讲解。这里列举了一个例子就是在做APP推荐时如何去分析偏长尾的标签,比如搜索“什么软件拍照带耳朵?”时能够发现非常丰富的问答数据,并且发现Faceu这款APP在答案里面。而如果其他的问答网站里面反馈出了其他的APP,就能计算出Faceu和其他拍照APP之间存在非常强大的相关性,这样一来可以做一些关联的推荐,并且可以标注出其推荐者。 标签召回主要包括两个步骤,第一步就是建立比较完整的标签体系,将标签归纳到比较稀疏的链路下面去。在定义好这些链路体系之后,第二步就可以分门别类地去进行挖掘,这里的挖掘相对而言还是比较传统的,比如先分取一些Query,然后去判断有哪些数据,并对于已有的数据进行一些标注,做一些标签的识别,之后进一步扩大。当我们累积到一定量之后,就可以尝试借助有监督的方法实现进一步的泛化。 知识图谱召回基于知识图谱的召回是最近一段时间内在学术界比较火的方法。UC团队在基于知识图谱的召回方面也做了大量的尝试,大致分析了一下有这样几类算法,比如文本建模算法DLA和Doc2vec,知识表示算法tranE、transH、transD以及transR,网络关系算法DeepWalk、Node2Vec以及SNDE等。文本建模算法基本上都是无监督学习,因此没有办法很好地利用关系网络,主要是利用文本信息;知识表示算法对于关系的稠密度要求非常高,如果关系稠密度没有达到要求,那么采样效果就会非常差;基于深度学习的网络关系算法即可以结合文本信息也可以融合关系网络。综上所述,基于深度学习的网络关系算法相对而言比较中庸一点,能够同时利用文本和网络信息,整体效果也会相对好一些。 UC团队主要针对Node2vec的基础版本做了一些优化。之所以优化Node2vec是因为其具有深度优先和广度优先的机制,能够使得其整个训练过程和方向变得可控。Node2vec的过程主要可以分为3部分,主要就是以知识图谱这个图关系网络为基础做随机游走,并且控制随机游走需要深度优先还是广度优先,深度优先会更加关注全局信息,而广度优先则会更加关注Doc信息。UC团队在Node2vec上面主要做了两方面优化,一个是数据增广,也就是增加了用户行为数据以及百科数据和超链接数据,将这些数据抽取出来实现层级化,这样就能够在一定程度上解决网络稀疏的问题。第二个优化点就是利用深度学习中一个比较好的方法,也就是利用文本信息做embedding,比如在知识图谱里面某一个人物有相应的描述,可以对于这些描述信息进行切词并embedding到网络中来。 向量召回基于向量的召回也是最近几年在学术界和工业界中比较热门的方法。向量召回的出发点就是分析输入的Query或者用户与候选的推荐Query之间的文本语义匹配问题。这个模型是YouTube在2016年发的一篇论文中提出的,UC团队在此基础上进行了改进,比如对于Query以不同的粒度进行切词。此外,Query还会有一些文本特征,比如检索切词、语义切词等,还会将用户画像的特征以及实时信息特征一起训练来提升模型的性能。 下图所展示的是向量召回的效果图,左边的第一列是训练的特征,第二列是召回的数据,第三列是真实的搜索Query。对于向量召回方法而言,有一些优化的方法,比如线上存在真实的排序情况,那么可以将线上真实情况和线下召回的情况做一个比较,从而大致了解向量召回的优势情况以及准确率如何。 三、排序基础相关性在排序部分首先介绍基础相关性。下图中展示了一个Query例子“泰勒级数展开公式”。在线上首先会对于这个Query做切词,切词完成之后,每个Token都会召回一系列的候选Doc,此时会出现一系列的问题,因为已经将Query切成Token了,所以极有可能产生的Doc结果和原始的Query是不相关的,因为切分之后无法得到足够的Query信息。此时,需要借助相关性模型大致地控制所获取的文本与原始Query的相关性,将相关性特别低的候选Doc在这一步过滤掉。在模型设计时也会考虑一些应用的场景,比如在做实体推荐时就会将Query里面实体的信息引入进来,进而实现共享网络。 如果将Query分类信息引入进来就能很好地解决一些歧义的问题。 CTR预估UC团队在两年前做了CTR预估的相关工作,那个时候其他的一些方法还没有成熟,因此这部分做的相对比较简单,主要的工作集中在样本的选择以及特征的选择上面。对于样本选择而言,通常会在一个推荐序列里面将点击过的结果作为正样本,将没有被点击过的结果作为负样本。在模型设计方面,比较重要的是CTR类特征,如果这个特征不佳就会使得整个模型的特征打一个比较大的折扣。而UC团队所实现的CTR预估模型能够达到小时级更新,保证线上的效果。 MABMAB的意思就是“多臂老虎机”,比如一个老虎机有多种可以玩的方法,我们一开始不知道哪种方法才能获胜,因此需要逐个实验每种玩法获胜的几率是多少,最终去确定应该以什么顺序来玩。这和排序是非常相关的,因为在推荐时如果直接使用CTR排序可能导致一些比较好的潜在的推荐Item因为刚刚出来,没有被很多用户点击过,就会导致其永远无法排在前面。此时就需要借助一个探索机制来缓解这样的问题,也就是当使用CTR排序完成之后,并不完全按照CTR去提供排序结果,而是使得所有的推荐候选项都有一定的概率被选中。如果经常性地进行探测,那么推荐结果也会逐渐地收敛。 小结这里简单做一个总结,在本文中已经介绍了大部分的推荐算法。对于召回而言,从精准到泛化基本上可以分为基于检索的召回、基于标签的召回、协同过滤、基于知识图谱的召回以及基于向量的召回。对于排序而言,也介绍了基础相关性、语义相关性以及CTR预估和MAB。 本文作者:游客be77vkb76molw阅读原文 本文为云栖社区原创内容,未经允许不得转载。

September 6, 2019 · 1 min · jiezi

SQL优化一

首先从索引上下功夫在sql前面加上“explain” ,查看性能如何查看表中是否使用了索引show index from tableName 添加索引ALTER TABLE tableName ADD INDEX (表字段);删除索引DROP INDEX 索引名(key_name) ON tableName 从sql语句中优化最好用left join, inner join或者exists。用IN查询速度慢一直以来认为 exists 比 in 效率高的说法是不准确的。如果查询的两个表大小相当,那么用in和exists差别不大。如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:inner join(left / right)SELECT t.t_advert_id, IFNULL( sum( t.success_target_account ), 0 ) AS account_num, IFNULL( sum( t.success_target_group ), 0 ) AS group_num FROM t_task_detail t INNER JOIN t_device d ON d.device_id = t.device_id INNER JOIN r_device_device_tag tag ON tag.t_device_id = d.t_device_id WHERE 1 = 1 AND tag.t_device_tag_id = 9 GROUP BY t.t_advert_idexistsSELECT t.t_advert_id, IFNULL( sum( t.success_target_account ), 0 ) AS account_num, IFNULL( sum( t.success_target_group ), 0 ) AS group_num FROM t_task_detail t WHERE 1 = 1 AND EXISTS (SELECT d.device_id FROM t_device_tag dt INNER JOIN r_device_device_tag ddt ON ddt.t_device_tag_id = dt.t_device_tag_id INNER JOIN t_device d ON d.t_device_id = ddt.t_device_id WHERE dt.t_device_tag_id =9 AND t.device_id = d.device_id ) GROUP BY t.t_advert_idinSELECT t.t_advert_id, IFNULL( sum( t.success_target_account ), 0 ) AS account_num, IFNULL( sum( t.success_target_group ), 0 ) AS group_num FROM t_task_detail t WHERE 1 = 1 AND t.device_id IN (SELECT d.device_id FROM t_device_tag dt INNER JOIN r_device_device_tag ddt ON ddt.t_device_tag_id = dt.t_device_tag_id INNER JOIN t_device d ON d.t_device_id = ddt.t_device_id WHERE dt.t_device_tag_id = 9 ) GROUP BY t.t_advert_id

August 19, 2019 · 1 min · jiezi

Go使用压缩文件优化io-二

原文链接: https://blog.thinkeridea.com/... 上一篇文章《使用压缩文件优化io (一)》中记录了日志备份 io 优化方案,使用文件流数据压缩方案优化 io 性能,效果十分显著。这篇文章记录数据分析前置清洗、格式化数据的 io 优化方案,我们有一台专用的日志前置处理服务器,所有业务日志通过这台机器从 OSS 拉取回来清洗、格式化,最后进入到数据仓储中便于后续的分析。 随着业务扩展这台服务器压力越来越大,高峰时数据延迟越来越厉害,早期也是使用 Python 脚本 + awk 以及一些 shell 命令完成相关工作,在数据集不是很大的时候这种方案很好,效率也很高,随着数据集变大,发现服务器负载很高,经过分析是还是 io 阻塞,依旧采用对数据流进行处理的方案优化io,以下记录优化的过程。 背景介绍服务器配置:4 核 8G; 磁盘:1T分析前置服务会根据业务不同分为十分钟、一小时两个阶段拉取分析日志,每隔一个阶段会去 OSS 拉取日志回到服务器进行处理,处理过程因 io 阻塞,导致 CPU 和 load 异常高,且处理效率严重下降,这次优化主要就是降低 io 阻塞,提升 CPU 利用率 (处理业务逻辑而不是等待 io) 和处理效率。 后文中会详细描述优化前后的方案,并用 go 编写测试,使用一台 2 核4G的服务器进行测试,测试数据集大小为: 文件数量:432个压缩文件:17G解压后文件:63G压缩方案:lzoGoroutine 数量:20优化前优化前日志处理流程: 获取待处理文件列表拉取 OSS 日志到本地磁盘 (压缩文件)解压缩日志文件读取日志数据业务处理……导入到数据仓储中导致 io 阻塞的部分主要是: 拉取 OSS 日志、解压缩日志文件及读取日志数据,优化也主要从这三块着手。 这里有一段公共的日志读取方法,该方法接收一个 io.Reader, 并按行读取日志,并简单切分日志字段,并没有实质的处理日志数据,后面的优化方案也将使用这个方法读取日志。 package mainimport ( "bufio" "bytes" "io" "github.com/thinkeridea/go-extend/exbytes")func Read(r io.Reader) { rawBuffer := make([]byte, 512) buf := bufio.NewReader(r) for { line, ok, err := readLine(buf, rawBuffer) if err == io.EOF { return } if err != nil { panic(nil) } if ok { rawBuffer = line } c := bytes.Count(line, []byte{'\x01'}) if c != 65 { panic("无效的行") } }}func readLine(r *bufio.Reader, rawBuffer []byte) ([]byte, bool, error) { var ok bool line, err := r.ReadSlice('\n') if (err == bufio.ErrBufferFull || len(line) < 3 || exbytes.ToString(line[len(line)-3:]) != "\r\r\n") && err != io.EOF { rawBuffer = append(rawBuffer[:0], line...) for (err == bufio.ErrBufferFull || len(line) < 3 || exbytes.ToString(line[len(line)-3:]) != "\r\r\n") && err != io.EOF { line, err = r.ReadSlice('\n') rawBuffer = append(rawBuffer, line...) } line = rawBuffer ok = true } if len(line) > 0 && err == io.EOF { err = nil } return line, ok, err}日志按 \r\r\n 分隔行,使用 \x01 切分字段,读取方法使用 bufio.ReadSlice 方法,避免内存分配,且当 bufio 缓冲区满之后使用 rwaBuffer 作为本地可扩展缓冲,每次扩展之后会保留最大的扩展空间,因为业务日志每行大小差不多,这样可以极大的减少内存分配,效率是 bufio.ReadLine 方法的好几倍。 ...

July 8, 2019 · 6 min · jiezi

译21-项优化-React-App-性能的技术

原文:21 Performance Optimization Techniques for React Apps作者:Nishant译者:博轩 介绍在 React 内部,React 会使用几项巧妙的小技术,来优化计算更新 UI 时,所需要的最少的更新 DOM 的操作。在大多数情况下,即使你没有针对性能进行专项优化,React 依然很快,但是仍有一些方法可以加速 React 应用程序。本文将介绍一些可用于改进 React 代码的有效技巧。 1.使用不可变数据结构数据不变性不是一种架构或者设计模式,它是一种编程思想。它会强制您考虑如何构建应用程序的数据流。在我看来,数据不变性是一种符合严格单项数据流的实践。 数据不变性,这一来自函数式编程的概念,可应用于前端应用程序的设计。它会带来很多好处,例如: 零副作用不可变的数据对象更易于创建,测试,和使用;利于解耦;更加利于追踪变化;在 React 环境中,我们使用 Component 的概念来维护组件内部的状态,对状态的更改可以导致组建的重新渲染。 React 构建并在内部维护呈现的UI(Virtual DOM)。当组件的 props 或者 state 发生改变时,React 会将新返回的元素与先前呈现的元素进行比较。当两者不相等时,React 将更新 DOM。因此,在改变状态时,我们必须要小心。 让我们考虑一个用户列表组件: state = { users: []}addNewUser = () =>{ /** * OfCourse not correct way to insert * new user in user list */ const users = this.state.users; users.push({ userName: "robin", email: "email@email.com" }); this.setState({users: users});}这里的关注点是,我们正在将新的用户添加到变量 users ,这里它对应的引用是 this.state.users。 ...

July 6, 2019 · 7 min · jiezi

深度学习深度学习常用优化方法

作者:LogM 本文原载于 https://segmentfault.com/u/logm/articles ,不允许转载~ 文章中的数学公式若无法正确显示,请参见:正确显示数学公式的小技巧 1. SGD(随机梯度下降)$$g_t = \bigtriangledown_{\theta_{t-1}} f(\theta_{t-1})$$ $$\Delta\theta_t = -\eta*g_t$$ 需要手动选取合适的learning_rate稀疏特征更新问题(不常出现的特征更新慢,如word2vec的embedding矩阵)易被困局部最优2. momentum$$m_t = \mu*m_{t-1}+g_t$$ $$\Delta\theta_t = -\eta*m_t$$ 引入惯量缓解局部最优问题未解决稀疏特征更新问题3. AdaGrad$$\nu_t = \nu_{t-1} + g_t*g_t$$ $$\Delta\theta_t = \frac{g_t}{\sqrt{\nu_t+\epsilon}} * \eta$$ 解决稀疏特征更新问题,不常出现的特征更新快局部最优问题没有解决随着 $\nu_t$ 的累加,学习率不断衰减4. RMSprop$$\nu_t = \mu * \nu_{t-1} + (1-\mu) * g_t*g_t$$ $$\Delta\theta_t = \frac{g_t}{\sqrt{\nu_t+\epsilon}} * \eta$$ 解决稀疏特征更新问题,不常出现的特征更新快局部最优问题没有解决RMSprop 是 AdaGrad 的升级版,区别是 $\nu_t$ 的计算方式:RMSprop 是移动平均,而 AdaGrad 是累加,越加越大。5. Adadelta$$\nu_t = \mu * \nu_{t-1} + (1-\mu) * g_t*g_t$$ ...

July 6, 2019 · 1 min · jiezi

Go使用压缩文件优化io-一

原文连接:https://blog.thinkeridea.com/... 最近遇到一个日志备份 io 过高的问题,业务日志每十分钟备份一次,本来是用 Python 写一个根据规则扫描备份日志问题不大,但是随着业务越来越多,单机上的日志文件越来越大,文件数量也越来越多,导致每每备份的瞬间 io 阻塞严重, CPU 和 load 异常的高,好在备份速度很快,对业务影响不是很大,这个问题会随着业务增长,越来越明显,这段时间抽空对备份方式做了优化,效果十分显著,整理篇文章记录一下。 背景说明服务器配置:4 核 8G; 磁盘:500G每十分钟需要上传:18 个文件,高峰时期约 10 G 左右 业务日志为了保证可靠性,会先写入磁盘文件,每10分钟切分日志文件,然后在下十分钟第一分时备份日志到 OSS,数据分析服务会从在备份完成后拉取日志进行分析,日志备份需要高效快速,在最短的时间内备份完,一般备份均能在几十秒内完成。 备份的速度和效率并不是问题,足够的快,但是在备份时 io 阻塞严重导致的 CPU 和 load 异常,成为业务服务的瓶颈,在高峰期业务服务仅消耗一半的系统资源,但是备份时 CPU 经常 100%,且 iowait 可以达到 70 多,空闲资源非常少,这样随着业务扩展,日志备份虽然时间很短,却成为了系统的瓶颈。 后文中会详细描述优化前后的方案,并用 go 编写测试,使用一台 2 核4G的服务器进行测试,测试数据集大小为: 文件数:336原始文件:96G压缩文件:24G压缩方案:lzoGoroutine 数量:4优化前优化前日志备份流程: 根据备份规则扫描需要备份的文件使用 lzop 命令压缩日志上传压缩后的日志到 OSS下面是代码实现,这里不再包含备份文件规则,仅演示压缩上传逻辑部分,程序接受文件列表,并对文件列表压缩上传至 OSS 中。 .../pkg/aliyun_oss 是我自己封装的基于阿里云 OSS 操作的包,这个路径是错误的,仅做演示,想运行下面的代码,OSS 交互这部分需要自己实现。 package mainimport ( "bytes" "fmt" "os" "os/exec" "path/filepath" "sync" "time" ".../pkg/aliyun_oss")func main() { var oss *aliyun_oss.AliyunOSS files := os.Args[1:] if len(files) < 1 { fmt.Println("请输入要上传的文件") os.Exit(1) } fmt.Printf("待备份文件数量:%d\n", len(files)) startTime := time.Now() defer func(startTime time.Time) { fmt.Printf("共耗时:%s\n", time.Now().Sub(startTime).String()) }(startTime) var wg sync.WaitGroup n := 4 c := make(chan string) // 压缩日志 wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() for file := range c { cmd := exec.Command("lzop", file) cmd.Stderr = &bytes.Buffer{} err := cmd.Run() if err != nil { panic(cmd.Stderr.(*bytes.Buffer).String()) } } }() } for _, file := range files { c <- file } close(c) wg.Wait() fmt.Printf("压缩耗时:%s\n", time.Now().Sub(startTime).String()) // 上传压缩日志 startTime = time.Now() c = make(chan string) wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() for file := range c { name := filepath.Base(file) err := oss.PutObjectFromFile("tmp/"+name+".lzo", file+".lzo") if err != nil { panic(err) } } }() } for _, file := range files { c <- file } close(c) wg.Wait() fmt.Printf("上传耗时:%s\n", time.Now().Sub(startTime).String())}程序运行时输出: ...

June 30, 2019 · 6 min · jiezi

优雅的防止用户重复点击某个按钮

前言:想必前端小伙伴都遇到过一个问题:点击某个按钮时如果点击的比较快,可能会触发多次。如果查询操作影响还不大,如果是提交操作,那就会有问题了。接下来为大家介绍几种防止重复点击的小妙招。基础:给请求添加loading效果。这个是网站必备的装逼特效,既能告诉用户系统已经在帮用户搞事情了,又可以防止用户在此期间做其他操作。进阶(方法1): 使用防抖。 防抖: 在一定时间内,动作只会执行一次(大家可以使用loadsh的debounce方法,也可以自己写)。举个例子:我在网上买了很多东西,今天很多快递都会到,时不时的就会有快递小哥的电话,我不想来回去取快递,就每隔1个小时取一次,如果1个小时内没有快递,就不下楼拿快递了。 建议:第一次点击立即执行,后面的点击每隔一段时间执行一次。(debounce的leading设置为true) 进阶(方法2):变量控制。 如果按钮和事件处理在一个组件中,那么我们可以使用变量来控制,以react为例: 建议使用防抖的方式,写法简单,可维护性高。如果您还有其他比较好的方法,欢迎留言。 过几天会写防抖的文章,欢迎关注。

June 27, 2019 · 1 min · jiezi

一个mysql死锁场景分析

最近遇到一个mysql在RR级别下的死锁问题,感觉有点意思,研究了一下,做个记录。涉及知识点:共享锁、排他锁、意向锁、间隙锁、插入意向锁、锁等待队列 场景隔离级别:Repeatable-Read表结构如下 create table t ( id int not null primary key AUTO_INCREMENT, a int not null default 0, b varchar(10) not null default '', c varchar(10) not null default '', unique key uniq_a_b(a,b), unique key uniq_c(c));初始化数据 insert into t(a,b,c) values(1,'1','1');有A/B两个session,按如下顺序执行两个事务 结果是 B执行完4之后还是一切正常A执行5的时候,被blockB接着执行6,B报死锁,B回滚,A插入数据show engine innodb status中可以看到死锁信息,这里先不贴,先解释几种锁的概念,再来理解死锁过程 共享(S)锁/互斥(X)锁共享锁允许事务读取记录互斥锁允许事务读写记录这两种其实是锁的模式可以和行锁、间隙锁混搭,多个事务可以同时持有S锁,但是只有一个事务能持有X锁 意向锁一种表锁(也是一种锁模式),表明有事务即将给对应表的记录加S或者X锁。SELECT ... LOCK IN SHARE MODE会在给记录加S锁之前先给表加IS锁,SELECT ... FOR UPDATE会在给记录加X锁之前给表加IX锁。这是一种mysql的锁优化策略,并不是很清楚意向锁的优化点在哪里,求大佬指教 两种锁的兼容情况如下 行锁很简单,给对应行加锁。比如update、select for update、delete等都会给涉及到的行加上行锁,防止其他事务的操作 间隙锁在RR隔离级别下,为了防止幻读现象,除了给记录本身,还需要为记录两边的间隙加上间隙锁。比如列a上有一个普通索引,已经有了1、5、10三条记录,select * from t where a=5 for update除了会给5这条记录加行锁,还会给间隙(1,5)和(5,10)加上间隙锁,防止其他事务插入值为5的数据造成幻读。当a上的普通索引变成唯一索引时,不需要间隙锁,因为值唯一,select * from t where a=5 for update不可能读出两条记录来。 ...

May 18, 2019 · 4 min · jiezi

从实现角度看redis-lazy-free的使用和注意事项

众所周知,redis对外提供的服务是由单线程支撑,通过事件(event)驱动各种内部逻辑,比如网络IO、命令处理、过期key处理、超时等逻辑。在执行耗时命令(如范围扫描类的keys, 超大hash下的hgetall等)、瞬时大量key过期/驱逐等情况下,会造成redis的QPS下降,阻塞其他请求。近期就遇到过大容量并且大量key的场景,由于各种原因引发的redis内存耗尽,导致有6位数的key几乎同时被驱逐,短期内redis hang住的情况 耗时命令是客户端行为,服务端不可控,优化余地有限,作者antirez在4.0这个大版本中增加了针对大量key过期/驱逐的lazy free功能,服务端的事情还是可控的,甚至提供了异步删除的命令unlink(前因后果和作者的思路变迁,见作者博客:Lazy Redis is better Redis - <antirez>) lazy free的功能在使用中有几个注意事项(以下为个人观点,有误的地方请评论区交流): lazy free不是在遇到快OOM的时候直接执行命令,放后台释放内存,而是也需要block一段时间去获得足够的内存来执行命令lazy free不适合kv的平均大小太小或太大的场景,大小均衡的场景下性价比比较高(当然,可以根据业务场景调整源码里的宏,重新编译一个版本)redis短期内其实是可以略微超出一点内存上限的,因为前一条命令没检测到内存超标(其实快超了)的情况下,是可以写入一个很大的kv的,当后续命令进来之后会发现内存不够了,交给后续命令执行释放内存操作如果业务能预估到可能会有集中的大量key过期,那么最好ttl上加个随机数,匀开来,避免集中expire造成的blocking,这点不管开不开lazy free都一样具体分析请见下文 参数redis 4.0新加了4个参数,用来控制这种lazy free的行为 lazyfree-lazy-eviction:是否异步驱逐key,当内存达到上限,分配失败后lazyfree-lazy-expire:是否异步进行key过期事件的处理lazyfree-lazy-server-del:del命令是否异步执行删除操作,类似unlinkreplica-lazy-flush:replica client做全同步的时候,是否异步flush本地db以上参数默认都是no,按需开启,下面以lazyfree-lazy-eviction为例,看看redis怎么处理lazy free逻辑,其他参数的逻辑类似 源码分析命令处理逻辑int processCommand(client *c)是redis处理命令的主方法,在真正执行命令前,会有各种检查,包括对OOM情况下的处理 int processCommand(client *c) { // ... if (server.maxmemory && !server.lua_timedout) { // 设置了maxmemory时,如果有必要,尝试释放内存(evict) int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR; // ... // 如果释放内存失败,并且当前将要执行的命令不允许OOM(一般是写入类命令) if (out_of_memory && (c->cmd->flags & CMD_DENYOOM || (c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) { flagTransaction(c); // 向客户端返回OOM addReply(c, shared.oomerr); return C_OK; } } // ... /* Exec the command */ if (c->flags & CLIENT_MULTI && c->cmd->proc != execCommand && c->cmd->proc != discardCommand && c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) { queueMultiCommand(c); addReply(c,shared.queued); } else { call(c,CMD_CALL_FULL); c->woff = server.master_repl_offset; if (listLength(server.ready_keys)) handleClientsBlockedOnKeys(); } return C_OK;内存释放(淘汰)逻辑内存的释放主要在freeMemoryIfNeededAndSafe()内进行,如果释放不成功,会返回C_ERR。freeMemoryIfNeededAndSafe()包装了底下的实现函数freeMemoryIfNeeded() ...

May 18, 2019 · 2 min · jiezi

mpvue性能优化实战技巧

最近一直在折腾mpvue写的微信小程序的性能优化,分享下实战的过程。先上个优化前后的图:可以看到打包后的代码量从813KB减少到387KB,Audits体验评分从B到A,效果还是比较明显的。其实这个指标说明不了什么,而且轻易就可以做到,更重要的是优化小程序运行过程中的卡顿感,请耐心往下看。常规优化常规的Web端优化方法在小程序中也是适用的,而且不可忽视。一、压缩图片这一步最简单,但是容易被忽视。在tiny上在线压缩,然后下载替换即可。我这项目的压缩率高达72%,可以说打包后的代码从813KB降到387KB大部分都是归功于压缩图片了。二、移除无用的库我之前在项目中使用了Vant Weapp,在static目录下引入了整个库,但实际上我只使用了button,field,dialog等几个组件,实在是没必要。所以干脆移除掉了,微信小程序自身提供的button,wx.showModal等一些组件基本可以满足需求,自己手写一下样式也不用花什么时间。在这里建议大家,在微信小程序中,尽量避免使用过多的依赖库。不要贪图方便而引入一些比较大的库,小程序不同于Web,限制比较多,能自己写一下就尽量自己写一下吧。小程序的优化咱们首先得看一下官方优化建议,大多是围绕这个建议去做。一、开启Vue.config._mpTrace = true这个是mpvue性能优化的一个黑科技啊,可能大多数同学都不知道这个,我在官方文档都没有搜到到这个配置,我真的是服了。我能找到这个配置也是Google机缘巧合下看到的,出处:mpvue重要更新,页面更新机制进行全面升级具体做法是在/src/main.js添加Vue.config._mpTrace = true,如:Vue.config._mpTrace = trueVue.config.productionTip = falseApp.mpType = ‘app’添加了Vue.config._mpTrace属性,这样就可以看到console里会打印每500ms更新的数据量。如图:如果数据更新量很大,会明显感觉小程序运行卡顿,反之就流畅。因此我们可以根据这个指标,逐步找出性能瓶颈并解决掉。二、精简data1. 过滤api返回的冗余数据后端的api可能是需要同时为iOS,Android,H5等提供服务的,往往会有些冗余的数据小程序是用不到的。比如api返回的一个文章列表数据有很多字段:this.articleList = [ { articleId: 1, desc: ‘xxxxxx’, author: ‘fengxianqi’, time: ‘xxx’, comments: [ { userId: 2, conent: ‘xxx’ } ] }, { articleId: 2 // … }, // …]假设我们在小程序中只需要用到列表中的部分字段,如果不对数据做处理,将整个articleList都setData进去,是不明智的。小程序官方文档: 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。可以看出,内存是很宝贵的,当articleList数据量非常大超过1M时,某些机型就会爆掉(我在iOS中遇到过很多次)。因此,需要将接口返回的数据剔除掉不需要的,再setData,回到我们上面的articleList例子,假设我们只需要用articleId和author这两个字段,可以这样:import { getArticleList } from ‘@/api/article’export default { data () { return { articleList: [] } } methods: { getList () { getArticleList().then(res => { let rawList = res.list this.articleList = this.simplifyArticleList(rawList) }) }, simplifyArticleList (list) { return list.map(item => { return { articleId: item.articleId, author: item.author // 需要哪些字段就加上哪些字段 } }) } }}这里我们将返回的数据通过simplifyArticleList 来精简数据,此时过滤后的articleList中的数据类似:[ {articleId: 1, author: ‘fengxianqi’}, {articleId: 2, author: ‘others’} // …]当然,如果你的需求中是所有数据都要用到(或者大部分数据),就没必要做一层精简了,收益不大。毕竟精简数据的函数中具体的字段,是会增加维护成本的。PS: 在我个人的实际操作中,做数据过滤虽然增加了维护的成本,但一般收益都很大,因次这个方法比较推荐。2. data()中只放需要的数据import xx from ‘xx.js’export default { data () { return { xx, otherXX: ‘2’ } }}有些同学可能会习惯将import的东西都先放进data中,再在methods中使用,在小程序中可能是个不好的习惯。因为通过Vue.config._mpTrace = true在更新某个数据时,我对比放进data和不放进data中的两种情况会有差别。所以我猜测可能是data是会一起更新的,比如只是想更新otherXX时,会同时将xx也一起合起来setData了。3. 静态图片放进static这个问题和上面的问题其实是一样的,有时候我们会通过import的方式引入,比如这样:<template> <img :src=“UserIcon”></template><script>import UserIcon from ‘@/assets/images/user_icon.png’export default { data () { return { UserIcon } }}</script>这样会导致打包后的代码,图片是base64形式(很长的一段字符串)存放在data中,不利于精简data。同时当该组件多个地方使用时,每个组件实例都会携带这一段很长的base64代码,进一步导致数据的冗余。因此,建议将静态图片放到static目录下,这样引用:<template> <img src="/static/images/user_icon.png"></template>代码也更简洁清爽。看一下做了上面操作的前后对比图,使用体验上也流畅了很多。三、swiper优化小程序自身提供的swiper组件性能上不是很好,使用时要注意。参考着两个思路:【优化】解决swiper渲染很多图片时的卡顿想请教一下小程序swiper组件的问题在我使用时,由于需求原因,动态删掉swiper-item的思路不可行(手滑时会造成抖动)。因此只能作罢。但仍然可以优化一下:将未显示的swiper-item中的图片用v-if隐藏到,当判断到current时才显示,防止大量图片的渲染导致的性能问题。四、vuex使用注意事项我之前写过的一篇mpvue开发音频类小程序踩坑和建议里面有讲如何在小程序中使用vuex。但遇到了个比较严重的性能问题。1. 问题描述我开发的是一个音频类的小程序,所以需要将播放列表playList,当前索引currentIndex和当前时长currentTime放进state.js中:const state = { currentIndex: 0, // playList当前索引 currentTime: 0, // 当前播放的进度 playList: [], // {title: ‘’, url: ‘’, singer: ‘’}}每次用户点击播放音频时,都会先加载音频的播放列表playList,然后播放时更新当前时长currentTime,发现有时候播音频时整个小程序非常卡顿。注意到,音频需每秒就得更新一次currentTime,即每秒就做一次setData操作,稍微有些卡顿是可以理解的。但我发现是播放列表数据比较多时会特别卡,比如playList的长度是100条以上时。2. 问题原因我开启Vue.config._mpTrace = true后发现一个规律:当palyList数据量小时,console显示造成的数据量更新数值比较小;当playList比较大时,console显示造成的数据量更新数值比较大。PS: 我曾尝试将playList数据量增加到200条,每500ms的数据量更新达到800KB左右。到这里基本可以确定一个事实就是:更新state中的任何一个字段,将导致整个state全量一起setData。在我这里的例子,虽然我每次只是更新currentTime这个字段的值,但依然导致将state中的其他字段如playList,currentIndex都一起做了一次setData操作。3. 解决问题有两个思路:精简state中保存的数据,即限制playList的数据不能太多,可将一些数据暂存在storage中vuex采用Module的写法能改善这个问题,虽然使用时命名空间造成一定的麻烦。vuex传送门一般情况下,推荐使用后者。我在项目中尝试使用了前者,同样能达到很好的效果,请继续看下面的分享。五、善用storage1.为什么说要善用storage由于小程序的内存非常宝贵,占用内存过大会非常卡顿,因此最好尽可能少的将数据放到内存中,即vuex存的数据要尽可能少。而小程序的storage支持单个 key允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。所以可以将一些相对取用不频繁的数据放进storage中,需要时再将这些数据放进内存,从而缓解内存的紧张,有点类似Windows中虚拟内存的概念。2.storage换内存的实例这个例子讲的会有点啰嗦,真正能用到的朋友可以详细看下。上面讲到playList数据量太多,播放一条音频时其实只需要最多保证3条数据在内存中即可,即上一首,播放中的,下一首,我们可以将多余的播放列表存放在storage中。PS: 为了保证更平滑地连续切换下一首,我们可以稍微保存多几条,比如我这里选择保存5条数据在vuex中,播放时始终保证当前播放的音频前后都有两条数据。// 首次播放背景音频的方法async function playAudio (audioId) { // 拿到播放列表,此时的playList最多只有5条数据。getPlayList方法看下面 const playList = await getPlayList(audioId) // 当前音频在vuex中的currentIndex const currentIndex = playList.findIndex(item => item.audioId === audioId) // 播放背景音频 this.audio = wx.getBackgroundAudioManager() this.audio.title = playList[currentIndex].title this.audio.src = playList[currentIndex].url // 通过mapActions将播放列表和currentIndex更新到vuex中 this.updateCurrentIndex(index) this.updatePlayList(playList) // updateCurrentIndex和updatePlayList是vuex写好的方法}// 播放音频时获取播放列表的方法,将所有数据存在storage,然后返回当前音频的前后2条数据,保证最多5条数据import { loadPlayList } from ‘@/api/audio’async function getPlayList (courseId, currentAudioId) { // 从api中请求得到播放列表 // loadPlayList是api的方法, courseId是获取列表的参数,表示当前课程下的播放列表 let rawList = await loadPlayList(courseId) // simplifyPlayList过滤掉一些字段 const list = this.simplifyPlayList(rawList) // 将列表存到storage中 wx.setStorage({ key: ‘playList’, data: list }) return subPlayList(list, currentAudioId)}重点是subPlayList方法,这个方法保证了拿到的播放列表是最多5条数据。function subPlayList(playList, currentAudioId) { let tempArr = […playList] const count = 5 // 保持vuex中最多5条数据 const middle = parseInt(count / 2) // 中点的索引 const len = tempArr.length // 如果整个原始的播放列表本来就少于5条数据,说明不需要裁剪,直接返回 if (len <= count) { return tempArr } // 找到当前要播放的音频的所在位置 const index = tempArr.findIndex(item => item.audioId === currentAudioId) // 截取当前音频的前后两条数据 tempArr = tempArr.splice(Math.max(0, Math.min(len - count, index - middle)), count) return tempArr}tempArr.splice(Math.max(0, index - middle), count)可能有些同学比较难理解,需要仔细琢磨一下。假设playList有10条数据:当前音频是列表中的第1条(索引是0),截取前5个:playList.splice(0, 5),此时currentAudio在这5个数据的索引是0,没有上一首,有4个下一首当前音频是列表中的第2条(索引是1),截取前5个:playList.splice(0, 5),此时currentAudio在这5个数据的索引是1,有1个上一首,3个下一首当前音频是列表中的第3条(索引是2),截取前5个:playList.splice(0, 5),此时currentAudio在这5个数据的索引是2,有2个上一首,2个下一首当前音频是列表中的第4条(索引是3),截取第1到6个:playList.splice(1, 5),此时currentAudio在这5个数据的索引是2,有2个上一首,2个下一首当前音频是列表中的第5条(索引是4),截取第2到7个:playList.splice(2, 5),此时currentAudio在这5个数据的索引是2,有2个上一首,2个下一首…当前音频是列表中的第9条(索引是8),截取后5个:playList.splice(4, 5),此时currentAudio在这5个数据的索引是3,有3个上一首,1个下一首当前音频是列表中的最后1条(索引是9),截取后的5个:playList.splice(4, 5),此时currentAudio在这5个数据的索引是4,有4个上一首,没有下一首有点啰嗦,感兴趣的同学仔细琢磨下,无论当前音频在哪,都始终保证了拿到当前音频前后的最多5条数据。接下来就是维护播放上一首或下一首时保证当前vuex中的playList始终是包含当前音频的前后2条。播放下一首function playNextAudio() { const nextIndex = this.currentIndex + 1 if (nextIndex < this.playList.length) { // 没有超出数组长度,说明在vuex的列表中,可以直接播放 this.audio = wx.getBackgroundAudioManager() this.audio.src = this.playList[nextIndex].url this.audio.title = this.playList[nextIndex].title this.updateCurrentIndex(nextIndex) // 当判断到已经到vuex的playList的边界了,重新从storage中拿数据补充到playList if (nextIndex === this.playList.length - 1 || nextIndex === 0) { // 拿到只有当前音频前后最多5条数据的列表 const newList = getPlayList(this.playList[nextIndex].courseId, this.playList[nextIndex].audioId) // 当前音频在这5条数据中的索引 const index = newList.findIndex(item => item.audioId === this.playList[nextIndex].audioId) // 更新到vuex this.updateCurrentIndex(index) this.updatePlayList(newList) } }}这里的getPlayList方法是上面讲过的,本来是从api中直接获取的,为了避免每次都从api直接获取,所以需要改一下,先读storage,若无则从api获取:import { loadPlayList } from ‘@/api/audio’async function getPlayList (courseId, currentAudioId) { // 先从缓存列表中拿 const playList = wx.getStorageSync(‘playList’) if (playList && playList.length > 0 && courseId === playList[0].courseId) { // 命中缓存,则从直接返回 return subPlayList(playList, currentAudioId) } else { // 没有命中缓存,则从api中获取 const list = await loadPlayList(courseId) wx.setStorage({ key: ‘playList’, data: list }) return subPlayList(list, currentAudioId) }}播放上一首也是同理,就不赘述了。PS: 将vuex中的数据精简后,我所做的小程序在播放音频时刷其他页面已经非常流畅啦,效果非常好。六、动画优化这个问题在mpvue开发音频类小程序踩坑和建议已经讲过了,感兴趣的可以移步看一眼,这里只写下概述:如果要使用动画,尽量用css动画代替wx.createAnimation使用css动画时建议开启硬件加速最后大致总结一下上面所讲的几个要点:开发时打开Vue.config._mpTrace = true。谨慎引入第三方库,权衡收益。添加数据到data中时要克制,能精简尽量精简。图片记得要压缩,图片在显示时才渲染。vuex保持数据精简,必要时可先存storage。性能优化是一个永不止步的话题,我也还在摸索,不足之处还请大家指点和分享。欢迎关注,会持续分享前端实战中遇到的一些问题和解决办法。 ...

April 17, 2019 · 3 min · jiezi

回到基础:用循环优化 JavaScript 程序

翻译:疯狂的技术宅https://medium.freecodecamp.o…本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章对于提高 JavaScript 程序的性能这个问题,最简单同时也是很容易被忽视的方法就是学习如何正确编写高性能循环语句。本文将会帮你解决这个问题。我们将看到 JavaScript 中主要的循环类型,以及如何针对它们进行高效编码。现在开始!循环性能谈到循环性能,争论的焦点始终会集中到关于应该使用哪种循环,哪个是速度最快、性能最好的?事实上,在 JavaScript 提供的四种循环类型中,只有一种比其他循环慢得多 —— for-in 循环。 对循环类型的选择应基于你的需求而不是性能问题。有两个主要因素有助于改善循环性能 —— 每次迭代完成的工作和迭代次数。在下面的内容中,我们将会看到通过对这两点的优化,可以对循环的整体性能产生积极的影响。For 循环在 ECMA-262(定义JavaScript的基本语法和行为的规范)第三版中,定义了四种循环类型。第一个是标准的 for 循环,它与其他类 C 语言的语法相同:for (var i = 0; i < 10; i++){ //循环体}这可能是最常用的 JavaScript 循环结构。要了解应该怎样对其进行优化,需要先进行一些分析。解析for 循环由四部分组成:初始化,预测试条件,循环体和后执行。它的工作方式如下:首先,执行初始化代码(var i = 0;)。然后是预测试条件(i <10;)。如果预测试条件的计算结果为 true,则执行循环体。之后运行后执行代码(i ++)。优化要优化循环中的工作量,第一步是最小化对象成员和数组项查找的数量。还可以通过反转顺序来提高循环的性能。在 JavaScript 中,反转循环对循环的性能提升不大,除非你消除了额外的操作。// 原始循环for (var i = 0; i < items.length; i++){ process(items[i]);}// 最小化属性查找for (var i = 0, len = items.length; i < len; i++){ process(items[i]);}// 最小化属性查找并反序for (var i = items.length; i–; ){ process(items[i]);}While 循环第二种是 while 循环。下面是一个简单的预测试循环,由预测试条件和循环体组成。var i = 0;while(i < 10){ //循环体 i++;}解析如果预测试条件的计算结果为 true,则执行循环体。如果不是 —— 它就会被跳过。每个 while 循环都可以用 for 替换,反之亦然。优化// 原始循环var j = 0;while (j < items.length){ process(items[j++]);}// 最小化属性查找var j = 0, count = items.length;while (j < count){ process(items[j++]);}// 最小化属性查找和反序var j = items.length;while (j–){ process(items[j]);}Do-While 循环do-while 是第三种循环,它是 JavaScript 中唯一的后测试循环。由循环体和后测试条件组成:var i = 0;do { //循环体} while (i++ < 10);解析在这种类型的循环中,循环体总是至少执行一次。然后评估测试后的条件,如果它是true,则执行另一个循环周期。优化// 原始循环var k = 0;do { process(items[k++]);} while (k < items.length);// 最小化属性查找var k = 0, num = items.length;do { process(items[k++]);} while (k < num);// 最小化属性查找和反序var k = items.length - 1;do { process(items[k]);} while (k–);For-In 循环最后一种是 for-in 循环。它有一个非常特殊的用途 —— 枚举 JavaScript 对象的命名属性。 它的语法如下:for (var prop in object){ //loop body}解析它的名称与 for 循环类似。但是工作方式完全不同。而这种差异使它比另外三种循环慢得多,后者具有相同的性能特征,所以争论哪个循环最快是没有用的。每次循环执行时,变量 prop 会得到 object 的一个属性。它将会不断执行,直到返回所有属性为止。这些是对象自身的以及通过其原型链继承的属性。注意事项永远不要用“ for-in ”来迭代数组成员。这种循环的每次迭代都会在实例或原型上进行属性查找,这使得 for-in 循环比其它循环要慢得多。对于相同次数的迭代,可能会比其它循环慢七倍。结论for , while 和 do-while 循环都有类似的性能特征,因此没有哪种类型比其他的更快或更慢。避免使用 for-in 循环,除非你需要对大量未知对象属性进行迭代。提高循环性能的最佳方法是减少每次迭代完成的工作量并减少循环迭代次数。???? 希望这对你有用,感谢阅读! ????资源高性能 JavaScript - Nicholas C. Zakas本文首发微信公众号:前端先锋欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目必须要会的 50 个React 面试题世界顶级公司的前端面试都问些什么11 个最好的 JavaScript 动态效果库CSS Flexbox 可视化手册从设计者的角度看 React过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!CSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从14个最好的 JavaScript 数据可视化库8 个给前端的顶级 VS Code 扩展插件Node.js 多线程完全指南把HTML转成PDF的4个方案及实现 ...

April 16, 2019 · 2 min · jiezi

react性能优化归纳简述

合理善用componentShouldUpdate;组件传递props的时候,只传递需要的props,尽量少用{…props},这样会增加虚拟DOM的比较负担;如果要用到bind方法,尽量全部放到constructror里面,这样bind只会在组件初始化的时候执行一次,如果放到render里面,每一次的render都会执行bind,造成性能上的浪费;相对复杂的页面尽量不要在一个组件里面写完,细粒度的管理组件有助于提高渲染效率;map渲染的组件要使用key,而且key必须是确定的,不要使用index这类的无法确定标识一条数据的key;尽量少用refs或者DOM之类不可控操作;如果可以尽量使用无状态组件(函数组件);React 官方提供了 PureRenderMixin 插件;新版本里直接提供React.PureComponent的基础类(进行的是浅比较,通过使用Immutable.js能够解决对象深比较的问题);

March 28, 2019 · 1 min · jiezi

加入B_树与hash | 自己动手写一个Redis

最近学习了Redis,对其内部结构较为感兴趣,为了进一步了解其运行原理,我打算自己动手用C++写一个redis。这是我第一次造轮子,所以纪念一下 ^ _ ^。源码github链接,项目现在实现了客户端与服务器的链接与交互,以及一些Redis的基本命令,下面是测试结果:(左边是服务端,右边是客户端)上节已经实现了小型Redis的基本功能,为了完善其功能并且锻炼一下自己的数据结构与算法,我打算参考《Redis设计与实现》一书优化其中的数据结构与算法从而完善自己的项目。本章讲解的是项目中B树与hash的引入。B树的引入在上一章中,我们的数据库使用的是原生的map结构,为了提高数据库的增删改查效率,这里我将其改为使用B_树这一数据结构。B树的具体实现方法如下:其中主要函数为(1)void insert(int k,string stt) 向B_树中插入一个关键字以及该关键字对应的value的值。(2)string getone(int k) 通过关键字获取其对应的value的值。// A BTree nodeclass BTreeNode{ int keys; // An array of keys string strs;//value的类型使用string数组 int t; // Minimum degree (defines the range for number of keys) BTreeNode **C; // An array of child pointers int n; // Current number of keys bool leaf; // Is true when node is leaf. Otherwise false public: BTreeNode(int _t, bool _leaf); // Constructor string getOne(int k); // A function to traverse all nodes in a subtree rooted with this node void traverse(); // A function to search a key in subtree rooted with this node. BTreeNode search(int k); // returns NULL if k is not present. // A function that returns the index of the first key that is greater // or equal to k int findKey(int k); // A utility function to insert a new key in the subtree rooted with // this node. The assumption is, the node must be non-full when this // function is called void insertNonFull(int k,string stt); // A utility function to split the child y of this node. i is index // of y in child array C[]. The Child y must be full when this // function is called void splitChild(int i, BTreeNode y); // A wrapper function to remove the key k in subtree rooted with // this node. void remove(int k); // A function to remove the key present in idx-th position in // this node which is a leaf void removeFromLeaf(int idx); // A function to remove the key present in idx-th position in // this node which is a non-leaf node void removeFromNonLeaf(int idx); // A function to get the predecessor of the key- where the key // is present in the idx-th position in the node int getPred(int idx); // A function to get the successor of the key- where the key // is present in the idx-th position in the node int getSucc(int idx); // A function to fill up the child node present in the idx-th // position in the C[] array if that child has less than t-1 keys void fill(int idx); // A function to borrow a key from the C[idx-1]-th node and place // it in C[idx]th node void borrowFromPrev(int idx); // A function to borrow a key from the C[idx+1]-th node and place it // in C[idx]th node void borrowFromNext(int idx); // A function to merge idx-th child of the node with (idx+1)th child of // the node void merge(int idx); // Make BTree friend of this so that we can access private members of // this class in BTree functions friend class BTree;}; class BTree{ BTreeNode root; // Pointer to root node int t; // Minimum degreepublic: // Constructor (Initializes tree as empty) BTree(int _t) { root = NULL; t = _t; } void traverse() { if (root != NULL) root->traverse(); } // function to search a key in this tree //查找这个关键字是否在树中 BTreeNode search(int k) { return (root == NULL)? NULL : root->search(k); } // The main function that inserts a new key in this B-Tree void insert(int k,string stt); // The main function that removes a new key in thie B-Tree void remove(int k); string getone(int k){ string ss=root->getOne(k); return ss; } }; BTreeNode::BTreeNode(int t1, bool leaf1){ // Copy the given minimum degree and leaf property t = t1; leaf = leaf1; // Allocate memory for maximum number of possible keys // and child pointers keys = new int[2t-1]; strs= new string[2t-1]; C = new BTreeNode [2t]; // Initialize the number of keys as 0 n = 0;} // A utility function that returns the index of the first key that is// greater than or equal to k//查找关键字的下标int BTreeNode::findKey(int k){ int idx=0; while (idx<n && keys[idx] < k) ++idx; return idx;}string BTreeNode::getOne(int k){ int idx = findKey(k); string s=strs[idx]; //cout<<“idx:"<<idx<<endl; return s;}// A function to remove the key k from the sub-tree rooted with this nodevoid BTreeNode::remove(int k){ int idx = findKey(k); cout<<idx<<endl; cout<<keys[idx]<<endl; // The key to be removed is present in this node if (idx < n && keys[idx] == k) { // If the node is a leaf node - removeFromLeaf is called // Otherwise, removeFromNonLeaf function is called if (leaf) removeFromLeaf(idx); else removeFromNonLeaf(idx); } else { // If this node is a leaf node, then the key is not present in tree if (leaf) { cout << “The key “<< k <<” is does not exist in the tree\n”; return; } // The key to be removed is present in the sub-tree rooted with this node // The flag indicates whether the key is present in the sub-tree rooted // with the last child of this node bool flag = ( (idx==n)? true : false ); // If the child where the key is supposed to exist has less that t keys, // we fill that child if (C[idx]->n < t) fill(idx); // If the last child has been merged, it must have merged with the previous // child and so we recurse on the (idx-1)th child. Else, we recurse on the // (idx)th child which now has atleast t keys if (flag && idx > n) C[idx-1]->remove(k); else C[idx]->remove(k); } return;} // A function to remove the idx-th key from this node - which is a leaf nodevoid BTreeNode::removeFromLeaf (int idx){ // Move all the keys after the idx-th pos one place backward for (int i=idx+1; i<n; ++i){ keys[i-1] = keys[i]; strs[i-1]=strs[i]; } // Reduce the count of keys n–; return;} // A function to remove the idx-th key from this node - which is a non-leaf nodevoid BTreeNode::removeFromNonLeaf(int idx){ int k = keys[idx]; // If the child that precedes k (C[idx]) has atleast t keys, // find the predecessor ‘pred’ of k in the subtree rooted at // C[idx]. Replace k by pred. Recursively delete pred // in C[idx] if (C[idx]->n >= t) { int pred = getPred(idx); keys[idx] = pred; C[idx]->remove(pred); } // If the child C[idx] has less that t keys, examine C[idx+1]. // If C[idx+1] has atleast t keys, find the successor ‘succ’ of k in // the subtree rooted at C[idx+1] // Replace k by succ // Recursively delete succ in C[idx+1] else if (C[idx+1]->n >= t) { int succ = getSucc(idx); keys[idx] = succ; C[idx+1]->remove(succ); } // If both C[idx] and C[idx+1] has less that t keys,merge k and all of C[idx+1] // into C[idx] // Now C[idx] contains 2t-1 keys // Free C[idx+1] and recursively delete k from C[idx] else { merge(idx); C[idx]->remove(k); } return;} // A function to get predecessor of keys[idx]int BTreeNode::getPred(int idx){ // Keep moving to the right most node until we reach a leaf BTreeNode *cur=C[idx]; while (!cur->leaf) cur = cur->C[cur->n]; // Return the last key of the leaf return cur->keys[cur->n-1];} int BTreeNode::getSucc(int idx){ // Keep moving the left most node starting from C[idx+1] until we reach a leaf BTreeNode *cur = C[idx+1]; while (!cur->leaf) cur = cur->C[0]; // Return the first key of the leaf return cur->keys[0];} // A function to fill child C[idx] which has less than t-1 keysvoid BTreeNode::fill(int idx){ // If the previous child(C[idx-1]) has more than t-1 keys, borrow a key // from that child if (idx!=0 && C[idx-1]->n>=t) borrowFromPrev(idx); // If the next child(C[idx+1]) has more than t-1 keys, borrow a key // from that child else if (idx!=n && C[idx+1]->n>=t) borrowFromNext(idx); // Merge C[idx] with its sibling // If C[idx] is the last child, merge it with with its previous sibling // Otherwise merge it with its next sibling else { if (idx != n) merge(idx); else merge(idx-1); } return;} // A function to borrow a key from C[idx-1] and insert it// into C[idx]void BTreeNode::borrowFromPrev(int idx){ BTreeNode *child=C[idx]; BTreeNode *sibling=C[idx-1]; // The last key from C[idx-1] goes up to the parent and key[idx-1] // from parent is inserted as the first key in C[idx]. Thus, the loses // sibling one key and child gains one key // Moving all key in C[idx] one step ahead for (int i=child->n-1; i>=0; –i){ child->keys[i+1] = child->keys[i]; child->strs[i+1]=child->strs[i]; } // If C[idx] is not a leaf, move all its child pointers one step ahead if (!child->leaf) { for(int i=child->n; i>=0; –i) child->C[i+1] = child->C[i]; } // Setting child’s first key equal to keys[idx-1] from the current node child->keys[0] = keys[idx-1]; child->strs[0]=strs[idx-1]; // Moving sibling’s last child as C[idx]’s first child if (!leaf) child->C[0] = sibling->C[sibling->n]; // Moving the key from the sibling to the parent // This reduces the number of keys in the sibling keys[idx-1] = sibling->keys[sibling->n-1]; strs[idx-1] = sibling->strs[sibling->n-1]; child->n += 1; sibling->n -= 1; return;} // A function to borrow a key from the C[idx+1] and place// it in C[idx]void BTreeNode::borrowFromNext(int idx){ BTreeNode *child=C[idx]; BTreeNode *sibling=C[idx+1]; // keys[idx] is inserted as the last key in C[idx] child->keys[(child->n)] = keys[idx]; child->strs[(child->n)] = strs[idx]; // Sibling’s first child is inserted as the last child // into C[idx] if (!(child->leaf)) child->C[(child->n)+1] = sibling->C[0]; //The first key from sibling is inserted into keys[idx] keys[idx] = sibling->keys[0]; strs[idx] = sibling->strs[0]; // Moving all keys in sibling one step behind for (int i=1; i<sibling->n; ++i) sibling->strs[i-1] = sibling->strs[i]; // Moving the child pointers one step behind if (!sibling->leaf) { for(int i=1; i<=sibling->n; ++i) sibling->C[i-1] = sibling->C[i]; } // Increasing and decreasing the key count of C[idx] and C[idx+1] // respectively child->n += 1; sibling->n -= 1; return;} // A function to merge C[idx] with C[idx+1]// C[idx+1] is freed after mergingvoid BTreeNode::merge(int idx){ BTreeNode *child = C[idx]; BTreeNode sibling = C[idx+1]; // Pulling a key from the current node and inserting it into (t-1)th // position of C[idx] child->keys[t-1] = keys[idx]; child->strs[t-1] = strs[idx]; int i; // Copying the keys from C[idx+1] to C[idx] at the end for (i=0; i<sibling->n; ++i){ child->strs[i+t] = sibling->strs[i]; } // Copying the child pointers from C[idx+1] to C[idx] if (!child->leaf) { for(i=0; i<=sibling->n; ++i) child->C[i+t] = sibling->C[i]; } // Moving all keys after idx in the current node one step before - // to fill the gap created by moving keys[idx] to C[idx] for (i=idx+1; i<n; ++i){ keys[i-1] = keys[i]; strs[i-1] = strs[i]; } // Moving the child pointers after (idx+1) in the current node one // step before for (i=idx+2; i<=n; ++i) C[i-1] = C[i]; // Updating the key count of child and the current node child->n += sibling->n+1; n–; // Freeing the memory occupied by sibling delete(sibling); return;} // The main function that inserts a new key in this B-Treevoid BTree::insert(int k,string stt){ // If tree is empty if (root == NULL) { // Allocate memory for root root = new BTreeNode(t, true); root->keys[0] = k; // Insert key root->strs[0]=stt; root->n = 1; // Update number of keys in root } else // If tree is not empty { // If root is full, then tree grows in height if (root->n == 2t-1) { // Allocate memory for new root BTreeNode s = new BTreeNode(t, false); // Make old root as child of new root s->C[0] = root; // Split the old root and move 1 key to the new root s->splitChild(0, root); // New root has two children now. Decide which of the // two children is going to have new key int i = 0; if (s->keys[0] < k) i++; s->C[i]->insertNonFull(k,stt); // Change root root = s; } else // If root is not full, call insertNonFull for root root->insertNonFull(k,stt); }} // A utility function to insert a new key in this node// The assumption is, the node must be non-full when this// function is calledvoid BTreeNode::insertNonFull(int k,string stt){ // Initialize index as index of rightmost element int i = n-1; // If this is a leaf node if (leaf == true) { // The following loop does two things // a) Finds the location of new key to be inserted // b) Moves all greater keys to one place ahead while (i >= 0 && keys[i] > k) { keys[i+1] = keys[i]; strs[i+1] = strs[i]; i–; } // Insert the new key at found location keys[i+1] = k; strs[i+1]=stt; n = n+1; } else // If this node is not leaf { // Find the child which is going to have the new key while (i >= 0 && keys[i] > k) i–; // See if the found child is full if (C[i+1]->n == 2t-1) { // If the child is full, then split it splitChild(i+1, C[i+1]); // After split, the middle key of C[i] goes up and // C[i] is splitted into two. See which of the two // is going to have the new key if (keys[i+1] < k) i++; } C[i+1]->insertNonFull(k,stt); }} // A utility function to split the child y of this node// Note that y must be full when this function is calledvoid BTreeNode::splitChild(int i, BTreeNode y){ // Create a new node which is going to store (t-1) keys // of y BTreeNode z = new BTreeNode(y->t, y->leaf); z->n = t - 1; int j; // Copy the last (t-1) keys of y to z for (j = 0; j < t-1; j++){ z->keys[j] = y->keys[j+t]; z->strs[j] = y->strs[j+t]; } // Copy the last t children of y to z if (y->leaf == false) { for (int j = 0; j < t; j++) z->C[j] = y->C[j+t]; } // Reduce the number of keys in y y->n = t - 1; // Since this node is going to have a new child, // create space of new child for (j = n; j >= i+1; j–) C[j+1] = C[j]; // Link the new child to this node C[i+1] = z; // A key of y will move to this node. Find location of // new key and move all greater keys one space ahead for (j = n-1; j >= i; j–){ strs[j+1] = strs[j]; } // Copy the middle key of y to this node keys[i] = y->keys[t-1]; strs[i] = y->strs[t-1]; // Increment count of keys in this node n = n + 1;} // Function to traverse all nodes in a subtree rooted with this nodevoid BTreeNode::traverse(){ // There are n keys and n+1 children, travers through n keys // and first n children int i; for (i = 0; i < n; i++) { // If this is not leaf, then before printing key[i], // traverse the subtree rooted with child C[i]. if (leaf == false) C[i]->traverse(); cout << " " << keys[i]; } // Print the subtree rooted with last child if (leaf == false) C[i]->traverse();} // Function to search key k in subtree rooted with this nodeBTreeNode BTreeNode::search(int k){ // Find the first key greater than or equal to k int i = 0; while (i < n && k > keys[i]) i++; // If the found key is equal to k, return this node if (keys[i] == k) return this; // If key is not found here and this is a leaf node if (leaf == true) return NULL; // Go to the appropriate child return C[i]->search(k);} void BTree::remove(int k){ if (!root) { cout << “The tree is empty\n”; return; } // Call the remove function for root root->remove(k); // If the root node has 0 keys, make its first child as the new root // if it has a child, otherwise set root as NULL if (root->n==0) { BTreeNode tmp = root; if (root->leaf) root = NULL; else root = root->C[0]; // Free the old root delete tmp; } return;}hash的引入由于客户端传入的是键值对,考虑到B_树的性质以及数据库的效率,我将作为键key的字符串的值hash后作为B_树中的关键字进行存储,并且仿照关键字数组开辟了一个字符串数组存储值value的值。因此get和set命令的实现做了如下的改动int DJBHash(string str){ unsigned int hash = 5381; for(int i=0;i<str.length();i++) { hash += (hash << 5) + str[i]; } return (hash & 0x7FFFFFFF)%1000;}//get命令void getCommand(Serverserver,Clientclient,string key,string&value){ //取值的时候现将key hash一下,然后再进行取值 int k=DJBHash(key); string ss=client->db->getone(k); if(ss==”"){ cout<<“get null”<<endl; }else{ value=ss; }}//set命令void setCommand(Serverserver,Clientclient,string key,string&value){ //client->db.insert(pair<string,string>(key,value)); //需要将key进行hash转成int int k=DJBHash(key); client->db->insert(k,value);} ...

February 26, 2019 · 13 min · jiezi

造个轮子 | 自己用C++实现Redis

最近学习了Redis,对其内部结构较为感兴趣,为了进一步了解其运行原理,我打算自己动手用C++写一个redis。这是我第一次造轮子,所以纪念一下 ^ _ ^。源码github链接,项目现在实现了客户端与服务器的链接与交互,以及一些Redis的基本命令,下面是测试结果:(左边是服务端,右边是客户端)为了完善其功能并且锻炼一下自己的数据结构与算法,我下一阶段打算根据《Redis设计与实现》一书优化数据结构与算法从而完善自己的项目。基本结构介绍基本流程介绍首先是对服务端的初始化,包括数据库的初始化以及命令集合的初始化。在客户端连接之后,开始创建客户端对其进行初始化,并且将其与服务端对应的数据库进行连接。在客户端发送命令之后,服务端接受命令,对命令的合法性进行判断,然后在命令集合中查找相关命令并执行,最后返回执行结果给客户端。

February 20, 2019 · 1 min · jiezi

记一次vue-webpack项目优化实践

项目现状项目是一个数据监测平台,引入了ehcart和three.js 负责项目的数据可视化;打包后,体积高达2.1M,这个体积相比于我的项目规模来说就显得稍有笨重了使用webpack-bundle-analyzer分析了一下各个文件所占用的比例:整个项目文件分布大体清晰了,现在开始优化走起!优化思路根据 wba的显示,第三方插件是大部头,包括three.js echart组件和elementUI组件。three.js优化空间不大,主要关注另外两个上面。echarts根据我的项目需求,echart主要用到的是linechart,其他图表不需要。而在开发过程中,我把整个echart都引用进来,其实是很没有必要的。ehcart整体引用方式import echarts from (“echarts”)vue.prototype.$echarts = echarts更改为:import echarts from “echarts/lib/echarts.js"import “echarts/lib/chart/line"import ’echarts/lib/component/tooltip’import ’echarts/lib/component/title’import ’echarts/lib/component/legend’import ’echarts/lib/component/legendScroll’import “echarts/lib/component/dataZoom"Vue.prototype.$echarts = echartselementUI同理echart,elementUI同样按需求导入,替换之前的整体引入。elementUI按需引入需要安装 babel-plugin-component包,在babelrc文件中进行如下修改:“plugins”: [ … [“component”, { “libraryName”: “element-ui”, “styleLibraryName”: “theme-chalk” }] ]优化后:经过对第三方插件的优化,打包后的文件缩小了近30%。目前为止,项目打包后的大部头就是three.js,这个目前的优化空间较小。而对echart改造给打包体积上带来的收益还是很明显的。后记这次的优化比较简单,主要是通过对自己项目的优化,熟悉webpack-bundle-analyzer的操作和使用这个插件的来优化webpack打包文件的方法和思路;算是简单的练手记录一下吧。当然,从整体优化的大维度上来说优化的点还有很多,这个文章继续更新下去。

February 15, 2019 · 1 min · jiezi

mariadb 内存占用优化

本文由云+社区发表作者:工程师小熊摘要:我们在使用mariadb的时候发现有时候不能启动起来,在使用过程中mariadb占用的内存很大,在这里学习下mariadb与内存相关的配置项,对mariadb进行调优。查询最高内存占用使用以下命令可以知道mysql的配置使用多少 RAMSELECT ( @@key_buffer_size+ @@query_cache_size+ @@innodb_buffer_pool_size+ @@innodb_additional_mem_pool_size+ @@innodb_log_buffer_size+ @@max_connections * ( @@read_buffer_size+ @@read_rnd_buffer_size+ @@sort_buffer_size+ @@join_buffer_size+ @@binlog_cache_size+ @@thread_stack+ @@tmp_table_size)) / (1024 * 1024 * 1024) AS MAX_MEMORY_GB;可以使用mysql计算器来计算内存使用下面是理论,可以直接到推荐配置如何调整配置key_buffer_size(MyISAM索引用)指定索引缓冲区的大小,它决定索引处理的速度,尤其是索引读的速度。为了最小化磁盘的 I/O , MyISAM 存储引擎的表使用键高速缓存来缓存索引,这个键高速缓存的大小则通过 key-buffer-size 参数来设置。如果应用系统中使用的表以 MyISAM 存储引擎为主,则应该适当增加该参数的值,以便尽可能的缓存索引,提高访问的速度。怎么设show global status like ‘key_read%’;+————————+————-+| Variable_name | Value |+————————+————-+| Key_read_requests | 27813678764 || Key_reads | 6798830 |——————— key_buffer_size通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。比例key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000更好。show global status like ‘%created_tmp_disk_tables%’;key_buffer_size只对MyISAM表起作用。即使你不使用MyISAM表,但是内部的临时磁盘表是MyISAM表,也要使用该值。可以使用检查状态值created_tmp_disk_tables得知详情。对于1G内存的机器,如果不使用MyISAM表,推荐值是16M(8-64M)另一个参考如下show global status like ‘key_blocks_u%’;+————————+————-+| Variable_name | Value |+————————+————-+| Key_blocks_unused | 0 || Key_blocks_used | 413543 |+————————+————-+Key_blocks_unused表示未使用的缓存簇(blocks)数,Key_blocks_used表示曾经用到的最大的blocks数,比如这台服务器,所有的缓存都用到了,要么增加key_buffer_size,要么就是过渡索引了,把缓存占满了。比较理想的设置:可以根据此工式来动态的调整Key_blocks_used / (Key_blocks_unused + Key_blocks_used) * 100% ≈ 80%show engines;查询存储引擎innodb_buffer_pool_size (innodb索引用)这个参数和MyISAM的key_buffer_size有相似之处,但也是有差别的。这个参数主要缓存innodb表的索引,数据,插入数据时的缓冲。为Innodb加速优化首要参数。 该参数分配内存的原则:这个参数默认分配只有8M,可以说是非常小的一个值。如果是专用的DB服务器,且以InnoDB引擎为主的场景,通常可设置物理内存的50%,这个参数不能动态更改,所以分配需多考虑。分配过大,会使Swap占用过多,致使Mysql的查询特慢。如果是非专用DB服务器,可以先尝试设置成内存的1/4,如果有问题再调整query_cache_size(查询缓存)缓存机制简单的说就是缓存sql文本及查询结果,如果运行相同的sql,服务器直接从缓存中取到结果,而不需要再去解析和执行sql。如果表更改了,那么使用这个表的所有缓冲查询将不再有效,查询缓存值的相关条目被清空。更改指的是表中任何数据或是结构的改变,包括INSERT、UPDATE、DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE等,也包括那些映射到改变了的表的使用MERGE表的查询。显然,这对于频繁更新的表,查询缓存是不适合的,而对于一些不常改变数据且有大量相同sql查询的表,查询缓存会节约很大的性能。注意:如果你查询的表更新比较频繁,而且很少有相同的查询,最好不要使用查询缓存。因为这样会消耗很大的系统性能还没有任何的效果要不要打开?先设置成这样跑一段时间query_cache_size=128M query_cache_type=1 看看命中结果来进行进一步的判断mysql> show status like ‘%Qcache%’;+————————-+———–+| Variable_name | Value |+————————-+———–+| Qcache_free_blocks | 669 || Qcache_free_memory | 132519160 || Qcache_hits | 1158 || Qcache_inserts | 284824 || Qcache_lowmem_prunes | 2741 || Qcache_not_cached | 1755767 || Qcache_queries_in_cache | 579 || Qcache_total_blocks | 1853 |+————————-+———–+8 rows in set (0.00 sec)Qcache_free_blocks:表示查询缓存中目前还有多少剩余的blocks,如果该值显示较大,则说明查询缓存中的内存碎片过多了,可能在一定的时间进行整理。Qcache_free_memory:查询缓存的内存大小,通过这个参数可以很清晰的知道当前系统的查询内存是否够用,是多了,还是不够用,DBA可以根据实际情况做出调整。Qcache_hits:表示有多少次命中缓存。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大,缓存效果越理想。Qcache_inserts: 表示多少次未命中然后插入,意思是新来的SQL请求在缓存中未找到,不得不执行查询处理,执行查询处理后把结果insert到查询缓存中。这样的情况的次数,次数越多,表示查询缓存应用到的比较少,效果也就不理想。当然系统刚启动后,查询缓存是空的,这很正常。Qcache_lowmem_prunes:该参数记录有多少条查询因为内存不足而被移除出查询缓存。通过这个值,用户可以适当的调整缓存大小。Qcache_not_cached: 表示因为query_cache_type的设置而没有被缓存的查询数量。Qcache_queries_in_cache:当前缓存中缓存的查询数量。Qcache_total_blocks:当前缓存的block数量。我们可以看到现网命中1158,未缓存的有1755767次,说明我们这个系统命中的太少了,表变动比较多,不什么开启这个功能涉及参数query_cache_limit:允许 Cache 的单条 Query 结果集的最大容量,默认是1MB,超过此参数设置的 Query 结果集将不会被 Cachequery_cache_min_res_unit:设置 Query Cache 中每次分配内存的最小空间大小,也就是每个 Query 的 Cache 最小占用的内存空间大小query_cache_size:设置 Query Cache 所使用的内存大小,默认值为0,大小必须是1024的整数倍,如果不是整数倍,MySQL 会自动调整降低最小量以达到1024的倍数query_cache_type:控制 Query Cache 功能的开关,可以设置为0(OFF),1(ON)和2(DEMAND)三种,意义分别如下: 0(OFF):关闭 Query Cache 功能,任何情况下都不会使用 Query Cache 1(ON):开启 Query Cache 功能,但是当 SELECT 语句中使用的 SQL_NO_CACHE 提示后,将不使用Query Cache 2(DEMAND):开启 Query Cache 功能,但是只有当 SELECT 语句中使用了 SQL_CACHE 提示后,才使用 Query Cachequery_cache_wlock_invalidate:控制当有写锁定发生在表上的时刻是否先失效该表相关的 Query Cache,如果设置为 1(TRUE),则在写锁定的同时将失效该表相关的所有 Query Cache,如果设置为0(FALSE)则在锁定时刻仍然允许读取该表相关的 Query Cache。innodb_additional_mem_pool_size(InnoDB内部目录大小)InnoDB 字典信息缓存主要用来存放 InnoDB 存储引擎的字典信息以及一些 internal 的共享数据结构信息,也就是存放Innodb的内部目录,所以其大小也与系统中所使用的 InnoDB 存储引擎表的数量有较大关系。这个值不用分配太大,通常设置16M够用了,默认8M,如果设置的内存大小不够,InnoDB 会自动申请更多的内存,并在 MySQL 的 Error Log 中记录警告信息。innodb_log_buffer_size (日志缓冲)表示InnoDB写入到磁盘上的日志文件时使用的缓冲区的字节数,默认值为16M。一个大的日志缓冲区允许大量的事务在提交之前不用写日志到磁盘,所以如果有更新,插入或删除许多行的事务,则使日志缓冲区更大一些可以节省磁盘IO通常最大设为64M足够max_connections (最大并发连接)MySQL的max_connections参数用来设置最大连接(用户)数。每个连接MySQL的用户均算作一个连接,max_connections的默认值为100。这个参数实际起作用的最大值(实际最大可连接数)为16384,即该参数最大值不能超过16384,即使超过也以16384为准;增加max_connections参数的值,不会占用太多系统资源。系统资源(CPU、内存)的占用主要取决于查询的密度、效率等;该参数设置过小的最明显特征是出现”Too many connections”错误mysql> show variables like ‘%max_connect%’;+———————–+——-+| Variable_name | Value |+———————–+——-+| extra_max_connections | 1 || max_connect_errors | 100 || max_connections | 2048 |+———————–+——-+3 rows in set (0.00 sec)mysql> show status like ‘Threads%’;+——————-+———+| Variable_name | Value |+——————-+———+| Threads_cached | 0 || Threads_connected | 1 || Threads_created | 9626717 || Threads_running | 1 |+——————-+———+4 rows in set (0.00 sec)可以看到此时的并发数也就是Threads_connected=1,还远远达不到2048mysql> show variables like ‘open_files_limit’;+——————+——-+| Variable_name | Value |+——————+——-+| open_files_limit | 65535 |+——————+——-+1 row in set (0.00 sec)max_connections 还取决于操作系统对单进程允许打开最大文件数的限制也就是说如果操作系统限制单个进程最大可以打开100个文件那么 max_connections 设置为200也没什么用MySQL 的 open_files_limit 参数值是在MySQL启动时记录的操作系统对单进程打开最大文件数限制的值可以使用 show variables like ‘open_files_limit’; 查看 open_files_limit 值ulimit -n65535或者直接在 Linux 下通过ulimit -n命令查看操作系统对单进程打开最大文件数限制 ( 默认为1024 )connection级内存参数(线程独享)connection级参数,是在每个connection第一次需要使用这个buffer的时候,一次性分配设置的内存。排序性能mysql对于排序,使用了两个变量来控制sort_buffer_size和 max_length_for_sort_data, 不象oracle使用SGA控制. 这种方式的缺点是要单独控制,容易出现排序性能问题.mysql> SHOW GLOBAL STATUS like ‘%sort%’;+—————————+——–+| Variable_name | Value |+—————————+——–+| Sort_merge_passes | 0 || Sort_priority_queue_sorts | 1409 || Sort_range | 0 || Sort_rows | 843479 || Sort_scan | 13053 |+—————————+——–+5 rows in set (0.00 sec)如果发现Sort_merge_passes的值比较大,你可以考虑增加sort_buffer_size 来加速ORDER BY 或者GROUP BY 操作,不能通过查询或者索引优化的。我们这为0,那就没必要设置那么大。读取缓存read_buffer_size = 128K(默认128K)为需要全表扫描的MYISAM数据表线程指定缓存read_rnd_buffer_size = 4M:(默认256K)首先,该变量可以被任何存储引擎使用,当从一个已经排序的键值表中读取行时,会先从该缓冲区中获取而不再从磁盘上获取。大事务binlogmysql> show global status like ‘binlog_cache%’;+———————–+———-+| Variable_name | Value |+———————–+———-+| Binlog_cache_disk_use | 220840 || Binlog_cache_use | 67604667 |+———————–+———-+2 rows in set (0.00 sec)Binlog_cache_disk_use表示因为我们binlog_cache_size设计的内存不足导致缓存二进制日志用到了临时文件的次数Binlog_cache_use 表示 用binlog_cache_size缓存的次数当对应的Binlog_cache_disk_use 值比较大的时候 我们可以考虑适当的调高 binlog_cache_size 对应的值如上图,现网是32K,我们加到64Kjoin语句内存影响如果应用中,很少出现join语句,则可以不用太在乎join_buffer_size参数的设置大小。如果join语句不是很少的话,个人建议可以适当增大join_buffer_size到1MB左右,如果内存充足可以设置为2MB。线程内存影响Thread_stack:每个连接线程被创建时,MySQL给它分配的内存大小。当MySQL创建一个新的连接线程时,需要给它分配一定大小的内存堆栈空间,以便存放客户端的请求的Query及自身的各种状态和处理信息。mysql> show status like ‘%threads%’;+————————-+———+| Variable_name | Value |+————————-+———+| Delayed_insert_threads | 0 || Slow_launch_threads | 0 || Threadpool_idle_threads | 0 || Threadpool_threads | 0 || Threads_cached | 0 || Threads_connected | 1 || Threads_created | 9649301 || Threads_running | 1 |+————————-+———+8 rows in set (0.00 sec)mysql> show status like ‘connections’;+—————+———+| Variable_name | Value |+—————+———+| Connections | 9649311 |+—————+———+1 row in set (0.00 sec)如上:系统启动到现在共接受到客户端的连接9649311次,共创建了9649301个连接线程,当前有1个连接线程处于和客户端连接的状态。而在Thread Cache池中共缓存了0个连接线程(Threads_cached)。Thread Cache 命中率:Thread_Cache_Hit = (Connections - Threads_created) / Connections * 100%;一般在系统稳定运行一段时间后,Thread Cache命中率应该保持在90%左右才算正常。内存临时表tmp_table_size 控制内存临时表的最大值,超过限值后就往硬盘写,写的位置由变量 tmpdir 决定 max_heap_table_size 用户可以创建的内存表(memory table)的大小.这个值用来计算内存表的最大行数值。Order By 或者Group By操作多的话,加大这两个值,默认16Mmysql> show status like ‘Created_tmp_%’;+————————-+——-+| Variable_name | Value |+————————-+——-+| Created_tmp_disk_tables | 0 || Created_tmp_files | 626 || Created_tmp_tables | 3 |+————————-+——-+3 rows in set (0.00 sec)如上图,写入硬盘的为0,3次中间表,说明我们的默认值足够用了mariadb 推荐配置注意这里只推荐innodb引擎内存配置只关注有注释的行[mysqld]datadir=/var/lib/mysqlsocket=/var/lib/mysql/mysql.sockdefault-storage-engine=INNODBcharacter-set-server=utf8collation-server=utf8_general_ciuser=mysqlsymbolic-links=0# global settingstable_cache=65535table_definition_cache=65535max_allowed_packet=4Mnet_buffer_length=1Mbulk_insert_buffer_size=16Mquery_cache_type=0 #是否使用查询缓冲,0关闭query_cache_size=0 #0关闭,因为改表操作多,命中低,开启消耗cpu# sharedkey_buffer_size=8M #保持8M MyISAM索引用innodb_buffer_pool_size=4G #DB专用mem50%,非DB专用mem15%到25%myisam_sort_buffer_size=32Mmax_heap_table_size=16M #最大中间表大小tmp_table_size=16M #中间表大小# per-threadsort_buffer_size=256K #加速排序缓存大小read_buffer_size=128k #为需要全表扫描的MYISAM数据表线程指定缓存read_rnd_buffer_size=4M #已排序的表读取时缓存,如果比较大内存就到6Mjoin_buffer_size=1M #join语句多时加大,1-2Mthread_stack=256k #线程空间,256K or 512Kbinlog_cache_size=64K #大事务binlog# big-tablesinnodb_file_per_table = 1skip-external-lockingmax_connections=2048 #最大连接数skip-name-resolve# slow_query_logslow_query_log_file = /var/log/mysql-slow.loglong_query_time = 30group_concat_max_len=65536# according to tuning-primer.shthread_cache_size = 8thread_concurrency = 16# set variablesconcurrent_insert=2运行时修改使用以下命令来修改变量set global {要改的key} = {值}; (立即生效重启后失效)set @@{要改的key} = {值}; (立即生效重启后失效)set @@global.{要改的key} = {值}; (立即生效重启后失效)试验mysql> set @@global.innodb_buffer_pool_size=4294967296;ERROR 1238 (HY000): Variable ‘innodb_buffer_pool_size’ is a read only variablemysql> set @@global.thread_stack=262144;ERROR 1238 (HY000): Variable ’thread_stack’ is a read only variablemysql> set @@global.binlog_cache_size=65536;Query OK, 0 rows affected (0.00 sec)mysql> set @@join_buffer_size=1048576;Query OK, 0 rows affected (0.00 sec)mysql> set @@read_rnd_buffer_size=4194304;Query OK, 0 rows affected (0.00 sec)mysql> set @@sort_buffer_size=262144;Query OK, 0 rows affected (0.00 sec)mysql> set @@read_buffer_size=131072;Query OK, 0 rows affected (0.00 sec)mysql> set global key_buffer_size=8388608;Query OK, 0 rows affected (0.39 sec)我们可以看到innodb_buffer_pool_size和thread_stack报错了,他们只能改配置文件,在运行时是只读的。 以下直接复制使用set @@global.binlog_cache_size=65536;set @@join_buffer_size=1048576;set @@read_rnd_buffer_size=4194304;set @@sort_buffer_size=262144;set @@read_buffer_size=131072;set global key_buffer_size=8388608;引用记一次Mysql占用内存过高的优化过程mysql 优化技巧心得一(key_buffer_size设置)mysql内存计算mysql计算器mariadb官网此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 23, 2019 · 3 min · jiezi

一次简单的项目优化

前言最近在帮同组的兄弟项目做需求,这几天差不多业务代码写完了。本着搞事情的原则, 简单分析之后, 决定搞一波优化。先简单看一下优化成果。先睹为快优化前:优化后:???????? 30%⬆️ ????????前:后:效果还是有一点的, 下面就看看具体的步骤。都做了啥step1 - 分析瓶颈第一步当然是先找影影响最大的因素, 这次主要是处理打包和加载的一些问题,所以还是先整体分析一些项目的构成,具体的渲染问题不在此篇范围。首先, 祭出一个包分析工具: webpack-bundle-analyzer.具体的使用方法:// webpack.config.jsconst BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin;// …pluginsnew BundleAnalyzerPlugin({ analyzerMode: ‘server’, generateStatsFile: true, statsOptions: { source: false }})安装, 然后重启一下server, 然后就可以在http://127.0.0.1:8888/ 看到这个分析了。简单分析一下之后发现几个问题:node-modules 都打在了一个包里,体积达到了 4.52M.有的包使用姿势不对, 没有被tree-shaking, 比如那一大坨lodash 和 vue-awesome.有个炒鸡大的css文件了解上述信息后, 就可以开始逐个击破了。step2: 对症下药1. Bundle Split修改之前: optimization: { splitChunks: { cacheGroups: { commons: { test: /[\/]node_modules[\/]/, name: ‘vendors’, chunks: ‘all’ } } } },修改之后: optimization: { runtimeChunk: ‘single’, splitChunks: { chunks: ‘all’, maxInitialRequests: Infinity, minSize: 0, minChunks: 1, cacheGroups: { commons: { test: /[\/]node_modules[\/]/, name(module) { const packageName = module.context.match(/[\/]node_modules\//)[1]; return modules.${packageName.replace('@', '')}; } } } } },https://webpack.js.org/plugin…2: 按需加载vue-awesome:lodash:eg:3:公共资源放CDN回头去看那个css 文件, 原来是个主题包, 其他项目也在用, 这就好办了, 拿出来放CDN上, 直接饮用就好了, 没必要打在包里。模块按需加载react 有 Loadable 这样的工具, 原理其实都是一样的。https://github.com/jamiebuild…可以在适当的节点异步加载所需的模块, 避免一股脑的打在一起。比如你可以单独加载某个页面, 比如:const Hello = () => import(’./components/Hello.vue’);Vue.component(‘xxx’, Hello);这样每一个模块就会被单独打包:与此同时, 你还需要另一个工具去合并这些小包产生的HTTP请求:https://webpack.js.org/plugin…大概就是这些, 当然可能处理的细节还有很多, 这里就不赘述了.结语优化是个一步一步逐步深入的过程,有时候也可能需要妥协, 但是每做一步优化, 心情都会是愉悦的, 也是能带来成就感的, 或许还能拯救你的KPI,哈哈。希望上面的一些文字能给你带来一些些启发。才疏学浅,难免会有疏漏,还请指正,谢谢。推荐阅读:https://segmentfault.com/a/11… ...

January 16, 2019 · 1 min · jiezi

从 10 秒到 2 秒!ElasticSearch 性能调优

大家好,我是皮蛋二哥。“ELK”是 ElasticSearch、Logstash、Kibana 三门技术的简称。如今 ELK 技术栈在互联网行业数据开发领域使用率越来越高,做过数据收集、数据开发、数据存储的同学相信对这个简称并不陌生,而ElasticSearch(以下简称 ES)则在 ELK 栈中占着举足轻重的地位。前一段时间,我亲身参与了一个 ES 集群的调优,今天把我所了解与用到的调优方法与大家分享,如有错误,请大家包涵与指正。系统层面的调优系统层面的调优主要是内存的设定与避免交换内存。ES 安装后默认设置的堆内存是 1GB,这很明显是不够的,那么接下来就会有一个问题出现:我们要设置多少内存给 ES 呢?其实这是要看我们集群节点的内存大小,还取决于我们是否在服务器节点上还是否要部署其他服务。如果内存相对很大,如 64G 及以上,并且我们不在 ES 集群上部署其他服务,那么我建议 ES 内存可以设置为 31G-32G,因为这里有一个 32G 性能瓶颈问题,直白的说就是即使你给了 ES 集群大于 32G 的内存,其性能也不一定会更加优良,甚至会不如设置为 31G-32G 时候的性能。以我调优的集群为例,我所调优的服务器节点内存为 64G,服务器节点上也基本不跑其他服务,所以我把 ES 集群内存大小设置为了 31G,以充分发挥集群性能。设置 ES 集群内存的时候,还有一点就是确保堆内存最小值(Xms)与最大值(Xmx)的大小是相同的,防止程序在运行时改变堆内存大小,这是一个很耗系统资源的过程。还有一点就是避免交换内存,可以在配置文件中对内存进行锁定,以避免交换内存(也可以在操作系统层面进行关闭内存交换)。对应的参数:bootstrap.mlockall: true分片与副本分片 (shard):ES 是一个分布式的搜索引擎, 索引通常都会分解成不同部分, 分布在不同节点的部分数据就是分片。ES 自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节。创建索引时默认的分片数为 5 个,并且一旦创建不能更改。副本 (replica):ES 默认创建一份副本,就是说在 5 个主分片的基础上,每个主分片都相应的有一个副本分片。额外的副本有利有弊,有副本可以有更强的故障恢复能力,但也占了相应副本倍数的磁盘空间。那我们在创建索引的时候,应该创建多少个分片与副本数呢?对于副本数,比较好确定,可以根据我们集群节点的多少与我们的存储空间决定,我们的集群服务器多,并且有足够大多存储空间,可以多设置副本数,一般是 1-3 个副本数,如果集群服务器相对较少并且存储空间没有那么宽松,则可以只设定一份副本以保证容灾(副本数可以动态调整)。对于分片数,是比较难确定的。因为一个索引分片数一旦确定,就不能更改,所以我们在创建索引前,要充分的考虑到,以后我们创建的索引所存储的数据量,否则创建了不合适的分片数,会对我们的性能造成很大的影响。对于分片数的大小,业界一致认为分片数的多少与内存挂钩,认为 1GB 堆内存对应 20-25 个分片,而一个分片的大小不要超过 50G,这样的配置有助于集群的健康。但是我个人认为这样的配置方法过于死板,我个人在调优 ES 集群的过程中,根据总数据量的大小,设定了相应的分片,保证每一个分片的大小没有超过 50G(大概在 40G 左右),但是相比之前的分片数查询起来,效果并不明显。之后又尝试了增加分片数,发现分片数增多之后,查询速度有了明显的提升,每一个分片的数据量控制在 10G 左右。查询大量小分片使得每个分片处理数据速度更快了,那是不是分片数越多,我们的查询就越快,ES 性能就越好呢?其实也不是,因为在查询过程中,有一个分片合并的过程,如果分片数不断的增加,合并的时间则会增加,而且随着更多的任务需要按顺序排队和处理,更多的小分片不一定要比查询较小数量的更大的分片更快。如果有多个并发查询,则有很多小碎片也会降低查询吞吐量。如果现在你的场景是分片数不合适了,但是又不知道如何调整,那么有一个好的解决方法就是按照时间创建索引,然后进行通配查询。如果每天的数据量很大,则可以按天创建索引,如果是一个月积累起来导致数据量很大,则可以一个月创建一个索引。如果要对现有索引进行重新分片,则需要重建索引,我会在文章的最后总结重建索引的过程。参数调优下面我会介绍一些 ES 关键参数的调优。有很多场景是,我们的 ES 集群占用了多大的 cpu 使用率,该如何调节呢。cpu 使用率高,有可能是写入导致的,也有可能是查询导致的,那要怎么查看呢?可以先通过 GET _nodes/{node}/hot_threads 查看线程栈,查看是哪个线程占用 cpu 高,如果是 elasticsearch[{node}][search][T#10] 则是查询导致的,如果是 elasticsearch[{node}][bulk][T#1] 则是数据写入导致的。我在实际调优中,cpu 使用率很高,如果不是 SSD,建议把 index.merge.scheduler.max_thread_count: 1 索引 merge 最大线程数设置为 1 个,该参数可以有效调节写入的性能。因为在存储介质上并发写,由于寻址的原因,写入性能不会提升,只会降低。还有几个重要参数可以进行设置,各位同学可以视自己的集群情况与数据情况而定。index.refresh_interval:这个参数的意思是数据写入后几秒可以被搜索到,默认是 1s。每次索引的 refresh 会产生一个新的 lucene 段, 这会导致频繁的合并行为,如果业务需求对实时性要求没那么高,可以将此参数调大,实际调优告诉我,该参数确实很给力,cpu 使用率直线下降。indices.memory.index_buffer_size:如果我们要进行非常重的高并发写入操作,那么最好将 indices.memory.index_buffer_size 调大一些,index buffer 的大小是所有的 shard 公用的,一般建议(看的大牛博客),对于每个 shard 来说,最多给 512mb,因为再大性能就没什么提升了。ES 会将这个设置作为每个 shard 共享的 index buffer,那些特别活跃的 shard 会更多的使用这个 buffer。默认这个参数的值是 10%,也就是 jvm heap 的 10%。translog:ES 为了保证数据不丢失,每次 index、bulk、delete、update 完成的时候,一定会触发刷新 translog 到磁盘上。在提高数据安全性的同时当然也降低了一点性能。如果你不在意这点可能性,还是希望性能优先,可以设置如下参数:“index.translog”: { “sync_interval”: “120s”, –sync间隔调高 “durability”: “async”, -– 异步更新 “flush_threshold_size”:“1g” –log文件大小 }这样设定的意思是开启异步写入磁盘,并设定写入的时间间隔与大小,有助于写入性能的提升。还有一些超时参数的设置:discovery.zen.ping_timeout 判断 master 选举过程中,发现其他 node 存活的超时设置discovery.zen.fd.ping_interval 节点被 ping 的频率,检测节点是否存活discovery.zen.fd.ping_timeout 节点存活响应的时间,默认为 30s,如果网络可能存在隐患,可以适当调大discovery.zen.fd.ping_retries ping 失败/超时多少导致节点被视为失败,默认为 3其他建议还有一些零碎的优化建议喔。插入索引自动生成 id:当写入端使用特定的 id 将数据写入 ES 时,ES 会检查对应的索引下是否存在相同的 id,这个操作会随着文档数量的增加使消耗越来越大,所以如果业务上没有硬性需求建议使用 ES 自动生成的 id,加快写入速率。避免稀疏索引:索引稀疏之后,会导致索引文件增大。ES 的 keyword,数组类型采用 doc_values 结构,即使字段是空值,每个文档也会占用一定的空间,所以稀疏索引会造成磁盘增大,导致查询和写入效率降低。我的调优下面说一说我的调优:我的调优主要是重建索引,更改了现有索引的分片数量,经过不断的测试,找到了一个最佳的分片数量,重建索引的时间是漫长的,在此期间,又对 ES 的写入进行了相应的调优,使 cpu 使用率降低下来。附上我的调优参数。index.merge.scheduler.max_thread_count:1 # 索引 merge 最大线程数indices.memory.index_buffer_size:30% # 内存index.translog.durability:async # 这个可以异步写硬盘,增大写的速度index.translog.sync_interval:120s #translog 间隔时间discovery.zen.ping_timeout:120s # 心跳超时时间discovery.zen.fd.ping_interval:120s # 节点检测时间discovery.zen.fd.ping_timeout:120s #ping 超时时间discovery.zen.fd.ping_retries:6 # 心跳重试次数thread_pool.bulk.size:20 # 写入线程个数 由于我们查询线程都是在代码里设定好的,我这里只调节了写入的线程数thread_pool.bulk.queue_size:1000 # 写入线程队列大小index.refresh_interval:300s #index 刷新间隔关于重建索引在重建索引之前,首先要考虑一下重建索引的必要性,因为重建索引是非常耗时的。ES 的 reindex api 不会去尝试设置目标索引,不会复制源索引的设置,所以我们应该在运行_reindex 操作之前设置目标索引,包括设置映射(mapping),分片,副本等。第一步,和创建普通索引一样创建新索引。当数据量很大的时候,需要设置刷新时间间隔,把 refresh_intervals 设置为-1,即不刷新,number_of_replicas 副本数设置为 0(因为副本数可以动态调整,这样有助于提升速度)。{ “settings”: { “number_of_shards”: “50”, “number_of_replicas”: “0”, “index”: { “refresh_interval”: “-1” } } “mappings”: { }}第二步,调用 reindex 接口,建议加上 wait_for_completion=false 的参数条件,这样 reindex 将直接返回 taskId。POST _reindex?wait_for_completion=false{ “source”: { “index”: “old_index”, //原有索引 “size”: 5000 //一个批次处理的数据量 }, “dest”: { “index”: “new_index”, //目标索引 }}第三步,等待。可以通过 GET _tasks?detailed=true&actions=*reindex 来查询重建的进度。如果要取消 task 则调用_tasks/node_id:task_id/_cancel。第四步,删除旧索引,释放磁盘空间。更多细节可以查看 ES 官网的 reindex api。那么有的同学可能会问,如果我此刻 ES 是实时写入的,那咋办呀?这个时候,我们就要重建索引的时候,在参数里加上上一次重建索引的时间戳,直白的说就是,比如我们的数据是 100G,这时候我们重建索引了,但是这个 100G 在增加,那么我们重建索引的时候,需要记录好重建索引的时间戳,记录时间戳的目的是下一次重建索引跑任务的时候不用全部重建,只需要在此时间戳之后的重建就可以,如此迭代,直到新老索引数据量基本一致,把数据流向切换到新索引的名字。POST /_reindex{ “conflicts”: “proceed”, //意思是冲突以旧索引为准,直接跳过冲突,否则会抛出异常,停止task “source”: { “index”: “old_index” //旧索引 “query”: { “constant_score” : { “filter” : { “range” : { “data_update_time” : { “gte” : 123456789 //reindex开始时刻前的毫秒时间戳 } } } } } }, “dest”: { “index”: “new_index”, //新索引 “version_type”: “external” //以旧索引的数据为准 }}以上就是我在 ES 调优上的一点总结,希望能够帮助到对 ES 性能有困惑的同学们,谢谢大家。——我是皮蛋,我喂自己袋盐。文 / 皮蛋二哥“一直以为只要保持低调,就没人知道其实我是一名作家”编 / 荧声本文由创宇前端作者授权发布,版权属于作者,创宇前端出品。 欢迎注明出处转载本文。文章链接:https://knownsec-fed.com/2019…想要订阅更多来自知道创宇开发一线的分享,请搜索关注我们的微信公众号:创宇前端(KnownsecFED)。欢迎留言讨论,我们会尽可能回复。欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。感谢您的阅读。 ...

January 16, 2019 · 2 min · jiezi

编写可维护的代码

编写可维护的代码前言我们在修改他人代码的时候,阅读他人代码所花的时间经常比实现功能的时间还要更多如果程序结构不清晰,代码混乱 。牵一发而动全身。那维护起来就更难维护了可读性可理解性:他人可以接手代码并理解它直观性 : 代码逻辑清晰可调试性 :出错时,方便定位问题所在如何提高可读性代码格式化适当添加注释函数与方法大段代码注释需有意义如何优化代码找出代码的坏味道使用重构手法将其解决掉代码的坏味道在我们的程序中,可以闻到很多的坏味道。主要有以下这些点命名不规范或无意义命名存在使用缩写、不规范、无意义例子:var a = xxx,b = xxx重复代码相同(或相似)的代码在项目中出现了多次,如果需求发生更改,则需要同时修改多个地方过长函数程序越长越难理解,一个函数应该只完成一个功能过长的类一个类的职责过多,一个类应该是一个独立的整体。过长参数列表太长的参数列表难以理解,不易使用。当需要修改的时候,会更加容易出错数据泥团有些数据项总是成群结队的待在一起。例如两个类中相同的字段、许多函数签名相同的参数。这些都应该提炼到一个对象中,将很多参数列缩短,简化函数调用类似的函数整体上实现的功能差不多,但是由于有一点点区别。所以写成了多个函数重构手法提炼函数针对一个比较长的函数,提炼成一个个完成特定功能的函数。例子// 提炼前function test11() { var day = $(‘day’); var yearVal = ‘2016’; var monthVal = ‘10’; var dayVal = ‘10’; day.val(dayVal); switch (monthVal) { case 4: case 6: case 9: case 11: if (dayVal > 30) { day.val(30); } break; case 2: if ( yearVal % 4 == 0 && (yearVal % 100 != 0 || yearVal % 400 == 0) && monthVal == 2 ) { if (dayVal > 29) { day.val(29); } } else { if (dayVal > 28) { day.val(28); } } break; default: if (dayVal > 31) { day.val(31); } }}// 提炼后function test12() { var day = $(‘day’); var yearVal = ‘2016’; var monthVal = ‘10’; var dayVal = ‘10’; var maxDay = getMaxDay(yearVal, monthVal); if (dayVal > maxDay) { day.val(maxDay); } else { day.val(dayVal); }}function getMaxDay(year, month) { var maxDay = 0; switch (month) { case 4: case 6: case 9: case 11: maxDay = 30; break; case 2: if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) { maxDay = 29; } else { maxDay = 28; } break; default: maxDay = 31; } return maxDay;}例子中,提炼前的代码,需要很费劲的看完整个函数,才会明白做了什么处理,提炼后的代码。只需要稍微看一下,就知道 getMaxDay 是获取当前月份的最大天数优点:如果每个函数的粒度都很小,那么函数被复用的机会就更大;这会使高层函数读起来就想一系列注释;如果函数都是细粒度,那么函数的覆写也会更容易些内联函数有时候,一个函数的本体与函数名一样简单易懂,就要用到这种手法。这种手法用于处理优化过度的问题举个例子:function biggerThanZero(num) { return num > 0;}function test() { var num = 10; if (biggerThanZero(num)) { //do something }}//内联后function test() { var num = 10; if (num > 0) { //do something }}引入解释性变量当表达式比较复杂难以阅读的时候,就可以通过临时变量来帮助你将表达式分解为容易管理的形式有些时候,运用提炼函数会更好一点举两个简单的例子:// 例子 1// beforefunction test2() { if ( platform.toUpperCase().indexOf(‘MAC’) > -1 && browser.toUpperCase().indexOf(‘IE’) > -1 && wasInitialized() && resize > 0 ) { // do something }}// afterfunction test2() { var isMacOs = platform.toUpperCase().indexOf(‘MAC’) > -1; var isIEBrowser = browser.toUpperCase().indexOf(‘IE’) > -1; var wasResized = resize > 0; if (isMacOs && isIEBrowser && wasInitialized() && wasResized) { // do something }}// ————————————————–// 例子2// beforefunction caluPrice(quantity, itemPrice) { return ( quantity * itemPrice - Math.max(0, quantity - 500) * itemPrice * 0.05 + Math.min(quantity * itemPrice * 0.1, 100) );}// afterfunction caluPrice(quantity, itemPrice) { var basePrice = quantity * itemPrice; var discount = Math.max(0, quantity - 500) * itemPrice * 0.05; var shiping = Math.min(basePrice * 0.1, 100); return basePrice - discount + shiping;}在两个例子中,引入解释性的变量之后,可读性大大增加。函数的意图就比较明显,单看变量命名就已经能大概知道具体的实现分解临时变量除了 for 循环里用来收集结果的变量,其他的临时变量都应该只被赋值一次。因为被赋值超过一次,就意味着他在函数中承担了多个责任。一个变量承担多个责任。会令代码看起来容易迷惑举个例子:// 分解临时变量// beforefunction test3() { var temp = 2 * (width + height); console.log(temp); // do something temp = height * width; // do something console.log(temp);}// afterfunction test4() { var perimeter = 2 * (width + height); console.log(perimeter); // do something var area = height * width; // do something console.log(area);}在这个例子中,temp 分别被赋予了两次,如果代码块较长的情况,会增加风险,因为你不知道他在哪里被改掉了替换算法当你重构的时候,发现实现同样的功能有一个更清晰的方式,就应该将原有的算法替换成你的算法。举个例子:// 替换算法// beforefunction getWeekDay() { var weekStr = ‘’; switch (date.format(’d’)) { case 0: weekStr = ‘日’; break; case 1: weekStr = ‘一’; break; case 2: weekStr = ‘二’; break; case 3: weekStr = ‘三’; break; case 4: weekStr = ‘四’; break; case 5: weekStr = ‘五’; break; case 6: weekStr = ‘六’; break; } return weekStr;}// afterfunction getWeekDay() { var weekDays = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’]; return weekDays[date.format(’d’)];}以字面常量取代魔法数(eg:状态码)在计算机科学中,魔法数是历史最悠久的不良现象之一。魔法数是指程序中莫名其妙的数字。拥有特殊意义,却又不能明确表现出这种意义的数字举个例子:// beforefunction test5(x) { if (x == 1) { console.log(‘完成’); } else if (x == 2) { console.log(‘上传中’); } else if (x == 3) { console.log(‘上传失败’); } else { console.log(‘未知的错误’); }}function test6(x) { if (x == 3) { // do something }}// aftervar UploadStatus = { START: 0, UPLOADING: 1, SUCCESS: 2, ERROR: 3, UNKNOWN: 4};function test7(x) { if (x == UploadStatus.START) { console.log(‘未开始’); } else if (x == UploadStatus.UPLOADING) { console.log(‘上传中’); } else if (x == UploadStatus.SUCCESS) { console.log(‘上传成功’); } else if (x == UploadStatus.ERROR) { console.log(‘上传失败’); } else { console.log(‘未知的错误’); }}function test8(x) { if (x == UploadStatus.ERROR) { // do something }}对于魔法数,应该用一个枚举对象或一个常量来赋予其可见的意义。这样,你在用到的时候,就能够明确的知道它代表的是什么意思而且,当需求变化的时候,只需要改变一个地方即可分解条件表达式复杂的条件逻辑是导致复杂度上升的地点之一。因为必须编写代码来处理不同的分支,很容易就写出一个相当长的函数将每个分支条件分解成新函数可以突出条件逻辑,更清楚表明每个分支的作用以及原因举个例子:// 分解条件表达式// 商品在冬季和夏季单价不一样// beforevar SUMMER_START = ‘06-01’;var SUMMER_END = ‘09-01’;function test9() { var quantity = 2; var winterRate = 0.5; var winterServiceCharge = 9; var summerRate = 0.6; var charge = 0; if (date.before(SUMMER_START) || date.after(SUMMER_END)) { charge = quantity * winterRate + winterServiceCharge; } else { charge = quantity * summerRate; } return charge;}// afterfunction test9() { var quantity = 2; return notSummer(date) ? winterCharge(quantity) : summerCharge(quantity);}function notSummer(date) { return date.before(SUMMER_START) || date.after(SUMMER_END);}function summerCharge(quantity) { var summerRate = 0.6; return quantity * summerRate;}function winterCharge(quantity) { var winterRate = 0.5; var winterServiceCharge = 9; return quantity * winterRate + winterServiceCharge;}合并条件表达式当发现一系列的条件检查,检查条件不一样,但是行为却一致。就可以将它们合并为一个条件表达式举个例子:// 合并条件表达式// beforefunction test10(x) { var isFireFox = ‘xxxx’; var isIE = ‘xxxx’; var isChrome = ‘xxxx’; if (isFireFox) { return true; } if (isIE) { return true; } if (isChrome) { return true; } return false;}// afterfunction test10(x) { var isFireFox = ‘xxxx’; var isIE = ‘xxxx’; var isChrome = ‘xxxx’; if (isFireFox || isIE || isChrome) { return true; } return false;}合并后的代码会告诉你,实际上只有一个条件检查,只是有多个并列条件需要检查而已合并重复的条件片段条件表达式上有着相同的一段代码,就应该将它搬离出来// 合并重复片段// beforefunction test11(isSpecial) { var total, price = 1; if (isSpecial) { total = price * 0.95; // 这里处理一些业务 } else { total = price * 0.8; // 这里处理一些业务 }}// afterfunction test12(isSpecial) { var total, price = 1; if (isSpecial) { total = price * 0.95; } else { total = price * 0.8; } // 这里处理一些业务}在不同的条件里面做了同样的事情,应该将其抽离出条件判断。这样代码量少而且逻辑更加清晰以卫语句取代嵌套条件表达式如果某个条件较为罕见,应该单独检查该条件,并在该条件为真时立即从函数中返回。这样的检查就叫卫语句举个例子:// 以卫语句取代嵌套条件表达式// beforefunction getPayMent() { var result = 0; if (isDead) { result = deadAmount(); } else { if (isSepartated) { result = separtedAmount(); } else { if (isRetired) { result = retiredAmount(); } else { result = normalPayAmount(); } } } return result;}// afterfunction getPayMent() { if (isDead) { return deadAmount(); } if (isSepartated) { return separtedAmount(); } if (isRetired) { return retiredAmount(); } return normalPayAmount();}函数改名(命名)当函数名称不能表达函数的用途,就应该改名变量和函数应使用合乎逻辑的名字。eg:获取产品列表 -> getProductList()变量名应为名词,因为变量名描述的大部分是一个事物。eg: 产品 -> product函数名应为动词开始,因为函数描述的是一个动作eg:获取产品列表 -> getProductList()将查询函数和修改函数分开如果某个函数只向你提供一个值,没有任何副作用。这个函数就可以任意的调用。这样的函数称为纯函数如果遇到一个既有返回值,又有副作用的函数。就应该将查询与修改动作分离出来举个例子:// beforefunction test13(people) { for (var i = 0, len = people.length; i < len; i++) { if (people[i].name == ‘andy’) { // do something 例如进行DOM 操作之类的 return ‘andy’; } if (people[i].name == ‘ChunYang’) { // do something 例如进行DOM 操作之类的 return ‘ChunYang’; } }}// afterfunction test14(people) { var p = find(people); // do something 例如进行DOM 操作之类的 // doSomeThing(p);}function find(people) { for (var i = 0, len = people.length; i < len; i++) { if (people[i].name == ‘andy’) { return ‘andy’; } if (people[i].name == ‘ChunYang’) { return ‘ChunYang’; } }}令函数携带参数如果发现两个函数,做着类似的工作。区别只在于其中几个变量的不同。就可以通过参数来处理。这样可以去除重复的代码,提高灵活性关键点: 找出不同的地方和重复的地方。推荐书籍《重构 改善既有代码的设计 》 基于 java 的《代码大全》相关链接个人博客代码片段 ...

December 20, 2018 · 5 min · jiezi

前端技术演进(六):前端项目与技术实践

这个来自之前做的培训,删减了一些业务相关的,参考了很多资料(参考资料列表),谢谢前辈们,么么哒 ????任何五花八门的技术,最终还是要在实践中落地。现代的软件开发,大部分讲求的不是高难度高精尖,而是效率和质量。这里主要来说说现代前端技术在项目中的实践。开发规范开发规范是开发工程师之间交流的另一种语言,它在一定程度上决定了代码是否具有一致性和易维护性,统一的开发规范常常可以降低代码的出错概率和团队开发的协作成本。就拿命名规范来说,如果没有规范,你会经常看到这样的代码:var a1,a2,temp1,temp2,woshimt;开发规范制定的重要性不言而喻,使用怎样的规范又成为了另一个问题,因为编程规范并不唯一。通俗地讲,规范的差别很多时候只是代码写法的区别,不同的规范都有各自的特点,大部分没有优劣之分。一般在选择时没必要纠结于使用哪一种规范, 只要团队成员都认可并达成一致就行。实际上,我们平时所说的开发规范更多时候指的是狭义上的编码规范,广义上的开发规范包括实际项目开发中可能涉及的所有规范,如项目技术选型规范、组件规范、接口规范、模块化规范等。由于每个团队使用的项目技术实现不一样,规范也可能千差万别,但无论是哪一种规范, 在一个团队中尽可能保持统一。这里是一个规范的例子:https://guide.aotu.io/docs/index.html如果使用框架,各个框架会有自己的最佳实践,一般来说参考官方的最佳实践,结合自己团队的习惯即可。比如Vue:https://cn.vuejs.org/v2/style-guide/自动化构建在现代软件开发中,自动化构建已经成为一个不可缺少的部分。对于编译型语言来说,一般都会通过命令行或者IDE先进行编译,然后在不同平台上安装运行。而前端代码不需要软件编译,Javascript算是解释型语言,浏览器变解析边执行,所以前端的自动化构建和传统语言略有不同。前端自动化构建目的前端构建工具的作用主要是对项目源文件或资源进行文件级处理,将文件或资源处理成需要的最佳输出结构和形式。在处理过程中,我们可以对文件进行模块化引入、依赖分析、资源合并、压缩优化、文件嵌入、路径替换、生成资源包等多种操作,这样就能完成很多原本需要手动完成的事情,极大地提高开发效率。前端自动化构建工具在没有自动化构建工具之前,前端在上线前的处理一般是这样的:HTML代码语法检查HTML去掉注释CSS代码去掉注释,添加版权信息CSS代码语法检查CSS文件添加兼容性属性CSS文件压缩合并JS文件语法检查JS文件去掉注释,添加版权信息JS文件压缩图片压缩、合并各个文件名称添加唯一hash修改HTML文件引用路径区分线上和开发环境整个过程每个步骤会用到相应的工具,比如:CSSLint、JSLint、Uglyfy、HTMLMin、CssMinify、imagemin等,繁琐且浪费时间。而且还有一些附加的构建要求,比如代码一旦修改就要自动校验,自动测试,刷新浏览器等,这种在几年前基本上无法实现。渐渐地,出现了一些自动化构建的工具。GruntGrunt 是比较早期的工具,它通过安装插件和配置任务,来执行自动化构建。比如:module.exports = function(grunt) { grunt.initConfig({ jshint: { files: [‘Gruntfile.js’, ‘src//*.js’, ’test//.js’], options: { globals: { jQuery: true } } }, watch: { files: [’<%= jshint.files %>’], tasks: [‘jshint’] } }); grunt.loadNpmTasks(‘grunt-contrib-jshint’); grunt.loadNpmTasks(‘grunt-contrib-watch’); grunt.registerTask(‘default’, [‘jshint’]);};这里就是监控js文件的变化,一旦改版,就执行jshint,也就是语法校验。Grunt有很强的生态,但是它运用配置的思想来写打包脚本,一切皆配置,所以会出现比较多的配置项,诸如option,src,dest等等。而且不同的插件可能会有自己扩展字段,导致认知成本的提高,运用的时候要搞懂各种插件的配置规则。Grunt的速度也比较慢,他是一个任务一个任务依次执行,会有很多IO操作。现在基本上用的人比较少了。GulpGulp 用代码方式来写打包脚本,并且代码采用流式的写法,只抽象出了gulp.src, gulp.pipe, gulp.dest, gulp.watch 接口,运用相当简单,使用 Gulp 的代码量能比 Grunt 少一半左右。var gulp = require(‘gulp’);var pug = require(‘gulp-pug’);var less = require(‘gulp-less’);var minifyCSS = require(‘gulp-csso’);var concat = require(‘gulp-concat’);var sourcemaps = require(‘gulp-sourcemaps’);gulp.task(‘html’, function(){ return gulp.src(‘client/templates/.pug’) .pipe(pug()) .pipe(gulp.dest(‘build/html’))});gulp.task(‘css’, function(){ return gulp.src(‘client/templates/.less’) .pipe(less()) .pipe(minifyCSS()) .pipe(gulp.dest(‘build/css’))});gulp.task(‘js’, function(){ return gulp.src(‘client/javascript/.js’) .pipe(sourcemaps.init()) .pipe(concat(‘app.min.js’)) .pipe(sourcemaps.write()) .pipe(gulp.dest(‘build/js’))});gulp.task(‘default’, [ ‘html’, ‘css’, ‘js’ ]);Gulp 基于并行执行任务的思想,通过一个pipe方法,以数据流的方式处理打包任务,中间文件只生成于内存,不会产生多余的IO操作,所以 Gulp 比 Grunt 要快很多。WebpackGrunt 和 Gulp 可以算是第一代的自动化构建工具。现在前端主要使用的是 Webpack。其实对比 Gulp 来说,Webpack 并不是一个完全的替代平,Gulp 是任务运行工具,它只是一个自动执行可重复活动的应用程序,它的用途更加的广泛,因为自动任务的范围更广。相对Gulp来说, Webpack是一个静态模块打包器(static module bundler),主要目的是帮助程序模块及其依赖构建静态资源。但是因为前端自动化构建的主要任务其实就是静态资源的构建,所以Webpack基本都可以完成。因此 Gulp 现在的使用比较少了。其实 Webpack 之所以流行,是因为之前的工具对模块化的支持不足,以前的工具大部分是以文件为单位的,而现代JS开发,都是基于模块的,模块依赖的识别是需要语法语义分析的,像 Gulp 之类的工具,只是一个自动执行的工具,没法很好的识别所有的模块依赖,所以继续使用会限制书写的方式和项目结构,配置起来也更加繁琐。Webpack 把所有的代码或图片都当做资源,它会从一个或多个入口文件开始找起,找到所有的资源依赖,然后做语法分析,去除掉不用的或重复的,最终按照配置要求生成处理过的文件。一个典型的Webpack配置文件:var webpack = require(‘webpack’);var path = require(‘path’);var HtmlWebpackPlugin = require(‘html-webpack-plugin’)var CleanWebpackPlugin = require(‘clean-webpack-plugin’)var ExtractTextPlugin = require(’extract-text-webpack-plugin’)var OptimizeCSSPlugin = require(‘optimize-css-assets-webpack-plugin’)const VENOR = [ “lodash”, “react”, “redux”,]module.exports = { entry: { bundle: ‘./src/index.js’, vendor: VENOR }, // 如果想修改 webpack-dev-server 配置,在这个对象里面修改 devServer: { port: 8081 }, output: { path: path.join(_dirname, ‘dist’), filename: ‘[name].[chunkhash].js’ }, module: { rules: [{ test: /.js$/, use: ‘babel-loader’ }, { test: /.(png|jpe?g|gif|svg)(?.)?$/, use: [{ loader: ‘url-loader’, options: { limit: 10000, name: ‘images/[name].[hash:7].[ext]’ } }] }, { test: /.css$/, loader: ExtractTextPlugin.extract({ fallback: ‘style-loader’, use: [{ // 这边其实还可以使用 postcss 先处理下 CSS 代码 loader: ‘css-loader’ }] }) }, ] }, plugins: [ // 抽取共同代码 new webpack.optimize.CommonsChunkPlugin({ name: [‘vendor’, ‘manifest’], minChunks: Infinity }), // 删除不需要的hash文件 new CleanWebpackPlugin([‘dist/.js’], { verbose: true, dry: false }), new HtmlWebpackPlugin({ template: ‘index.html’ }), // 生成全局变量 new webpack.DefinePlugin({ “process.env.NODE_ENV”: JSON.stringify(“process.env.NODE_ENV”) }), // 分离 CSS 代码 new ExtractTextPlugin(“css/[name].[contenthash].css”), // 压缩提取出的 CSS,并解决ExtractTextPlugin分离出的 JS 重复问题 new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }), // 压缩 JS 代码 new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ]};打包后生成:Rollup最近,React,Vue、Ember、Preact、D3、Three.js、Moment 等众多知名项目都使用了 Rollup 这个构建工具。Rollup 可以使用 ES2015的语法来写配置文件,而 Webpack 不行:// rollup.config.jsimport babel from ‘rollup-plugin-babel’;export default { input: ‘./src/index.js’, output: { file: ‘./dist/bundle.rollup.js’, format: ‘cjs’ }, plugins: [ babel({ presets: [ [ ’es2015’, { modules: false } ] ] }) ]}// webpack.config.jsconst path = require(‘path’);const webpack = require(‘webpack’);module.exports = { entry: { ‘index.webpack’: path.resolve(’./src/index.js’) }, output: { libraryTarget: “umd”, filename: “bundle.webpack.js”, }, module: { rules: [ { test: /.js$/, exclude: /node_modules/, loader: ‘babel-loader’, query: { presets: [’es2015’] } } ] }};举个简单的例子,两个文件://some-file.jsexport default 10;// index.jsimport multiplier from ‘./some-file.js’;export function someMaths() { console.log(multiplier); console.log(5 * multiplier); console.log(10 * multiplier);}通过 Rollup 和 Webpack 打包之后,分别长成下面这样:// bundle.rollup.js — ~245 bytes’use strict’;Object.defineProperty(exports, ‘esModule’, { value: true });var multiplier = 10;function someMaths() { console.log(multiplier); console.log(5 * multiplier); console.log(10 * multiplier);}exports.someMaths = someMaths;// bundle.webpack.js — ~4108 bytesmodule.exports = // (function(modules) { // webpackBootstrap // // The module cache // var installedModules = {}; // // // The require function // function webpack_require(moduleId) { // // // Check if module is in cache // if(installedModules[moduleId]) { // return installedModules[moduleId].exports; // } // // Create a new module (and put it into the cache) // var module = installedModules[moduleId] = { // i: moduleId, // l: false, // exports: {}………可以看到 Webpack 打包后的代码基本上不具备可读性,尺寸也有些大。所以对于主要是给其他人使用的纯JS库或框架来说,Rollup 比 Webpack 更适合。性能优化前端性能优化是一个很宽泛的概念,不过最终目的都是提升用户体验,改善页面性能。性能优化是个很有意思的事情,很多人常常竭尽全力进行前端页面优化,但却忽略了这样做的效果和意义。通常前端性能可以认为是用户获取所需要页面数据或执行某个页面动作的一个实时性指标,一般以用户希望获取数据的操作到用户实际获得数据的时间间隔来衡量。例如用户希望获取数据的操作是打开某个页面,那么这个操作的前端性能就可以用该用户操作开始到屏幕展示页面内容给用户的这段时间间隔来评判。用户的等待延时可以分成两部分:可控等待延时和不可控等待延时。可控等待延时可以理解为能通过技术手段和优化来改进缩短的部分,例如减小图片大小让请求加载更快、减少HTTP请求数等。不可控等待延时则是不能或很难通过前后端技术手段来改进优化的,例如鼠标点击延时、CPU计算时间延时、ISP ( Internet Service Provider,互联网服务提供商)网络传输延时等。前端中的所有优化都是针对可控等待延时这部分来进行的。前端性能测试Performance Timing APIPerformance Timing API是一个支持Internet Explorer9以上版本及WebKit内核浏览器中用于记录页面加载和解析过程中关键时间点的机制,它可以详细记录每个页面资源从开始加载到解析完成这一过程中具体操作发生的时间点,这样根据开始和结束时间戳就可以计算出这个过程所花的时间了。之前我们介绍 Chrome 网络面板的时候说过一个请求的生命周期:可以通过 Performance Timing API 捕获到各个阶段的时间,通过计算各个属性的差值来评测性能,比如:var timinhObj = performance.timing;DNS查询耗时 :domainLookupEnd - domainLookupStartTCP链接耗时 :connectEnd - connectStartrequest请求耗时 :responseEnd - responseStart解析dom树耗时 : domComplete - domInteractive白屏时间 :responseStart - navigationStartdomready时间 :domContentLoadedEventEnd - navigationStartonload时间 :loadEventEnd - navigationStartProfile 工具之前有说过,使用 Chrome 开发者工具的 Audit 面板或者 Performance 面板,可以评估性能。埋点计时在关键逻辑之间手动埋点计时,比如:let timeList = []timeList.push({ tag: ‘xxxBegin’, time: +new Date })…timeList.push({ tag: ‘xxxEnd’, time: +new Date })这种方式常常在移动端页面中使用,因为移动端浏览器HTML解析和JavaScript执行相对较慢,通常为了进行性能优化,需要找到页面中执行JavaScript 耗时的操作,如果将关键JavaScript的执行过程进行埋点计时并上报,就可以轻松找出JavaScript 执行慢的地方,并有针对性地进行优化。资源时序图可以通过 Chrome 的网络面板,或者 Fiddler 之类的工具查看时序图,来分析页面阻塞:前端优化策略前端优化的策略非常多,主要的策略大概可以归为几大类:网络加载类减少HTTP资源请求次数在前端页面中,通常建议尽可能合并静态资源图片、JavaScript或CSS代码,减少页面请求数和资源请求消耗,这样可以缩短页面首次访问的用户等待时间。减小HTTP请求大小应尽量减小每个HTTP请求的大小。如减少没必要的图片、JavaScript、 CSS及HTML代码,对文件进行压缩优化,或者使用gzip压缩传输内容等都可以用来减小文件大小,缩短网络传输等待时延。将CSS或JavaScript放到外部文件中,避免使用 script 标签直接引入在HTML文件中引用外部资源可以有效利用浏览器的静态资源缓存。避免使用空的href和src当 link 标签的 href 属性为空,或script、 img、iframe标签的src属性为空时,浏览器在渲染的过程中仍会将href属性或src属性中的空内容进行加载,直至加载失败,这样就阻塞了页面中其他资源的下载进程,而且最终加载到的内容是无效的,因此要尽量避免。为HTML指定Cache-Control或Expires为HTML内容设置Cache-Control或Expires可以将HTML内容缓存起来,避免频繁向服务器端发送请求。前面讲到,在页面Cache-Control或Expires头部有效时,浏览器将直接从缓存中读取内容,不向服务器端发送请求。比如:<meta http-equiv=“Cache -Control” content=“max-age=7200” />合理设置Etag和Last-Modified合理设置Etag和Last-Modified使用浏览器缓存,对于未修改的文件,静态资源服务器会向浏览器端返回304,让浏览器从缓存中读取文件,减少Web资源下载的带宽消耗并降低服务器负载。减少页面重定向页面每次重定向都会延长页面内容返回的等待延时,一次重定向大约需要600毫秒的时间开销,为了保证用户尽快看到页面内容,要尽量避免页面重定向。使用静态资源分域存放来增加下载并行数浏览器在同一时刻向同一个域名请求文件的并行下载数是有限的,因此可以利用多个域名的主机来存放不同的静态资源,增大页面加载时资源的并行下载数,缩短页面资源加载的时间。通常根据多个域名来分别存储JavaScript、CSS和图片文件。比如京东:使用静态资源CDN来存储文件如果条件允许,可以利用CDN网络加快同一个地理区域内重复静态资源文件的响应下载速度,缩短资源请求时间。使用CDN Combo下载传输内容CDN Combo是在CDN服务器端将多个文件请求打包成一个文件的形式来返回的技术,这样可以实现HTTP连接传输的一次性复用,减少浏览器的HTTP请求数,加快资源下载速度。比如://g.alicdn.com/??kissy/k/6.2.4/seed-min.js,tbc/global/0.0.8/index-min.js,tms/tb-init/6.1.0/index-min.js,sea/sitenav-global/0.5.2/global-min.js使用可缓存的AJAX对于返回内容相同的请求,没必要每次都直接从服务端拉取,合理使用AJAX缓存能加快AJAX响应速度并减轻服务器压力。比如:const cachedFetch = (url, options) => { let cacheKey = url let cached = sessionStorage.getItem(cacheKey) if (cached !== null) { let response = new Response(new Blob([cached])) return Promise.resolve(response) } return fetch(url, options).then(response => { if (response.status === 200) { let ct = response.headers.get(‘Content-Type’) if (ct && (ct.match(/application/json/i) || ct.match(/text//i))) { response.clone().text().then(content => { sessionStorage.setItem(cacheKey, content) }) } } return response })}使用GET来完成AJAX请求使用XMLHttpRequest时,浏览器中的POST方法发送请求首先发送文件头,然后发送HTTP正文数据。而使用GET时只发送头部,所以在拉取服务端数据时使用GET请求效率更高。减少Cookie的大小并进行Cookie隔离HTTP请求通常默认带上浏览器端的Cookie一起发送给服务器,所以在非必要的情况下,要尽量减少Cookie来减小HTTP请求的大小。对于静态资源,尽量使用不同的域名来存放,因为Cookie默认是不能跨域的,这样就做到了不同域名下静态资源请求的Cookie隔离。缩小favicon.ico并缓存这样有利于favicon.ico的重复加载,因为一般一个Web应用的favicon.ico是很少改变的。推荐使用异步JavaScript资源异步的JavaScript 资源不会阻塞文档解析,所以允许在浏览器中优先渲染页面,延后加载脚本执行。比如:<script src=“main.js” defer></script><script src=“main.js” async></script>使用async时,加载和渲染后续文档元素的过程和main.js的加载与执行是并行的。使用defer 时,加载后续文档元素的过程和main.js的加载也是并行的,但是main.js的执行要在页面所有元素解析完成之后才开始执行。使用异步Javascript,加载的先后顺序被打乱,要注意依赖问题。消除阻塞渲染的CSS及JavaScript对于页面中加载时间过长的CSS或JavaScript文件,需要进行合理拆分或延后加载,保证关键路径的资源能快速加载完成。避免使用CSS import引用加载CSSCSS中的@import可以从另一个样式文件中引入样式,但应该避免这种用法,因为这样会增加CSS资源加载的关键路径长度,带有@import的CSS样式需要在CSS文件串行解析到@import时才会加载另外的CSS文件,大大延后CSS渲染完成的时间。首屏数据请求提前,避免JavaScript 文件加载后才请求数据针对移动端,为了进一步提升页面加载速度,可以考虑将页面的数据请求尽可能提前,避免在JavaScript加载完成后才去请求数据。通常数据请求是页面内容渲染中关键路径最长的部分,而且不能并行,所以如果能将数据请求提前,可以极大程度.上缩短页面内容的渲染完成时间。首屏加载和按需加载,非首屏内容滚屏加载,保证首屏内容最小化由于移动端网络速度相对较慢,网络资源有限,因此为了尽快完成页面内容的加载,需要保证首屏加载资源最小化,非首屏内容使用滚动的方式异步加载。一般推荐移动端页面首屏数据展示延时最长不超过3秒。目前中国联通3G的网络速度为338KB/s (2.71Mb/s), 不能保证客户都是流畅的4G网络,所以推荐首屏所有资源大小不超过1014KB,即大约不超过1MB。模块化资源并行下载在移动端资源加载中,尽量保证JavaScript资源并行加载,主要指的是模块化JavaScript资源的异步加载,使用并行的加载方式能够缩短多个文件资源的加载时间。inline 首屏必备的CSS和JavaScript通常为了在HTML加载完成时能使浏览器中有基本的样式,需要将页面渲染时必备的CSS和JavaScript通过 style 内联到页面中,避免页面HTML载入完成到页面内容展示这段过程中页面出现空白。比如百度:<!Doctype html><html xmlns=http://www.w3.org/1999/xhtml><head><meta http-equiv=Content-Type content=“text/html;charset=utf-8”><meta http-equiv=X-UA-Compatible content=“IE=edge,chrome=1”><meta content=always name=referrer><link rel=“shortcut icon” href=/favicon.ico type=image/x-icon><link rel=icon sizes=any mask href=//www.baidu.com/img/baidu_85beaf5496f291521eb75ba38eacbd87.svg><title>百度一下,你就知道 </title><style id=“style_super_inline”>body,h1,h2,h3,h4,h5,h6,hr,p,blockquote,dl,dt,dd,ul,ol,li,pre,form,fieldset,legend,button,input,textarea,th,td{margin:0;padding:0}html{color:#000;overflow-y:scroll;overflow:-moz-scrollbars}body,button,input,select,textarea{font:12px arial}…meta dns prefetch设置DNS预解析设置文件资源的DNS预解析,让浏览器提前解析获取静态资源的主机IP,避免等到请求时才发起DNS解析请求。通常在移动端HTML中可以采用如下方式完成。<!– cdn域名预解析–><meta http-equiv=“x-dns-prefetch-control” content=“on”><link rel=“dns-prefetch” href="//cdn.domain.com">资源预加载对于移动端首屏加载后可能会被使用的资源,需要在首屏完成加载后尽快进行加载,保证在用户需要浏览时已经加载完成,这时候如果再去异步请求就显得很慢。合理利用MTU策略通常情况下,我们认为TCP网络传输的最大传输单元(Maximum Transmission Unit, MTU)为1500B,即一个RTT ( Round-Trip Time,网络请求往返时间)内可以传输的数据量最大为1500字节。因此,在前后端分离的开发模式中,尽量保证页面的HTML内容在1KB以内,这样整个HTML的内容请求就可以在一个RTT内请求完成,最大限度地提高HTML载入速度。页面渲染类把CSS资源引用放到HTML文件顶部一般推荐将所有CSS资源尽早指定在HTML文档中, 这样浏览器可以优先下载CSS并尽早完成页面渲染。JavaScript资源引用放到HTML文件底部JavaScript资源放到HTML文档底部可以防止JavaScript的加载和解析执行对页面渲染造成阻塞。由于JavaScript资源默认是解析阻塞的,除非被标记为异步或者通过其他的异步方式加载,否则会阻塞HTML DOM解析和CSS渲染的过程。不要在HTML中直接缩放图片在HTML中直接缩放图片会导致页面内容的重排重绘,此时可能会使页面中的其他操作产生卡顿,因此要尽量减少在页面中直接进行图片缩放。减少DOM元素数量和深度HTML中标签元素越多,标签的层级越深,浏览器解析DOM并绘制到浏览器中所花的时间就越长,所以应尽可能保持DOM元素简洁和层级较少。尽量避免使用table、iframe等慢元素table 内容的渲染是将table的DOM渲染树全部生成完并一次性绘制到页面上的,所以在长表格渲染时很耗性能,应该尽量避免使用它,可以考虑使用列表元素 ul 代替。尽量使用异步的方式动态添加iframe,因为iframe内资源的下载进程会阻塞父页面静态资源的下载与CSS及HTML DOM的解析。避免运行耗时的JavaScript长时间运行的JavaScript会阻塞浏览器构建DOM树、DOM渲染树、渲染页面。所以,任何与页面初次渲染无关的逻辑功能都应该延迟加载执行,这和JavaScript资源的异步加载思路是一致的。避免使用CSS表达式或CSS滤镜CSS表达式或CSS滤镜的解析渲染速度是比较慢的,在有其他解决方案的情况下应该尽量避免使用。缓存类合理利用浏览器缓存除了上面说到的使用Cache-Control、Expires、 Etag 和Last-Modified来设置HTTP缓存外,在移动端还可以使用localStorage 等来保存AJAX返回的数据,或者使用localStorage保存CSS或JavaScript静态资源内容,实现移动端的离线应用,尽可能减少网络请求,保证静态资源内容的快速加载。静态资源离线方案对于移动端或Hybrid应用,可以设置离线文件或离线包机制让静态资源请求从本地读取,加快资源载入速度,并实现离线更新。图片类图片压缩处理在移动端,通常要保证页面中一切用到的图片都是经过压缩优化处理的,而不是以原图的形式直接使用的,因为那样很消耗流量,而且加载时间更长。使用较小的图片,合理使用base64内嵌图片在页面使用的背景图片不多且较小的情况下,可以将图片转化成base64编码嵌入到HTML页面或CSS文件中,这样可以减少页面的HTTP请求数。需要注意的是,要保证图片较小,一般图片大小超过2KB就不推荐使用base64嵌入显示了。使用更高压缩比格式的图片使用具有较高压缩比格式的图片,如webp 等。在同等图片画质的情况下,高压缩比格式的图片体积更小,能够更快完成文件传输,节省网络流量。不过注意 webp 的兼容性,除了Chrome其他浏览器支持不好。图片懒加载为了保证页面内容的最小化,加速页面的渲染,尽可能节省移动端网络流量,页面中的图片资源推荐使用懶加载实现,在页面滚动时动态载入图片。比如京东首页滚动。使用Media Query或srcset 根据不同屏幕加载不同大小图片介绍响应式时说过,针对不同的移动端屏幕尺寸和分辨率,输出不同大小的图片或背景图能保证在用户体验不降低的前提下节省网络流量,加快部分机型的图片加载速度,这在移动端非常值得推荐。使用iconfont代替图片图标在页面中尽可能使用iconfont 来代替图片图标,这样做的好处有以下几个:使用iconfont体积较小,而且是矢量图,因此缩放时不会失真;可以方便地修改图片大小尺寸和呈现颜色。但是需要注意的是,iconfont引用不同webfont格式时的兼容性写法,根据经验推荐尽量按照以下顺序书写,否则不容易兼容到所有的浏览器上。@font-face { font-family: iconfont; src: url("./iconfont.eot") ; src: url("./iconfont.eot?#iefix") format(“eot”), url("./iconfont.woff") format(“woff”), url("./iconfont.ttf") format(“truetype”);}定义图片大小限制加载的单张图片一般建议不超过30KB,避免大图片加载时间长而阻塞页面其他资源的下载,因此推荐在10KB以内。如果用户,上传的图片过大,建议设置告警系统 。脚本类脚本类涉及到代码的优化,这里只简单列一些:尽量使用id选择器合理缓存DOM对象页面元素尽量使用事件代理,避免直接事件绑定使用touchstart代替click避免touchmove、scroll 连续事件处理,设置事件节流推荐使用ECMAScript 6的字符串模板连接字符串尽量使用新特性渲染类使用Viewport固定屏幕渲染,可以加速页面渲染内容在移动端设置Viewport可以加速页面的渲染,同时可以避免缩放导致页面重排重绘。比如:<meta name=“viewport” content=“width=device-width, initial-scale=1.0, maximum-scale=1.0,user-scalable=no”>避免各种形式重排重绘页面的重排重绘很耗性能,所以一定要尽可能减少页面的重排重绘。使用CSS3动画,开启GPU加速使用CSS3动画时可以设置 transform: translateZ(0) 来开启移动设备浏览器的GPU图形处理,加速,让动画过程更加流畅。合理使用Canvas和requestAnimationFrame选择Canvas或requestAnimationFrame等更高效的动画实现方式,尽量避免使用setTimeout、setInterval 等方式来直接处理连续动画。SVG代替图片部分情况下可以考虑使用SVG代替图片实现动画,因为使用SVG格式内容更小,而且SVG DOM结构方便调整。不滥用float在DOM渲染树生成后的布局渲染阶段,使用float的元素布局计算比较耗性能,推荐使用固定布局或flex-box弹性布局的方式来实现页面元素布局。架构协议类尝试使用SPDY和HTTP 2在条件允许的情况下可以考虑使用SPDY协议来进行文件资源传输,利用连接复用加快传输过程,缩短资源加载时间。HTTP2在未来也是可以考虑尝试的。使用后端数据渲染使用后端数据渲染的方式可以加快页面内容的渲染展示,避免空白页面的出现,同时可以解决移动端页面SEO的问题。如果条件允许,后端数据渲染是一个很不错的实践思路。使用NativeView代替DOM的性能劣势可以尝试使用Native View等来避免HTML DOM性能慢的问题,目前使用React Native、Weex等已经可以将页面内容渲染体验做到接近客户端Native应用的体验了。这里列举了一部分优化的策略,世界上没有十全十美的事情,在做到了极致优化的同时也会付出很大的代价,这也是前端优化的一个问题。理论上这些优化都是可以实现的,但是作为工程师,要懂得权衡。优化提升了用户体验,使数据加载更快,但是项目代码却可能打乱,异步内容要拆分出来,首屏的一个雪碧图可能要分成两个,页面项目代码的数量和维护成本可能成倍增加,项目结构也可能变得不够清晰。任何一部分优化都可以做得很深入,但不一定都值得,在优化的同时也要尽量考虑性价比,这才是处理前端优化时应该具有的正确思维。用户数据分析在现代互联网产品的开发迭代中,对前端用户数据的统计分析严重影响着最终产品的成败。谈到前端数据,涉及的方面就比较广了。网站用户数据统计分析通常可以反映出网站的用户规模、用户使用习惯、用户的内容偏好等,了解了这些就能帮助我们调整产品策略、改进产品需求、提高产品质量,除此之外用户数据的统计甚至也会直接和广告收入相关联。用户访问统计通常页面上用户访问统计主要包括PV(Page View)、UV(Unique Visitor)、VV (Visit View)、IP(访问站点的不同IP数)等。PVPV一般指在一天时间之内页面被所有用户访问的总次数,即每一次页面刷新都会增加一次PV。PV作为单个页面的统计量参数,通常用来统计获取关键入口页面或临时推广性页面的访问量或推广效果,由于PV的统计一般是不做任何条件限制的,可以人为地刷新来提升统计量,所以单纯靠PV是无法反应页面被用户访问的具体情况的。UVUV是指在一天时间之访问内页的不同用户个数,和PV不同的是,如果一个页面在同一天内被某个相同用户多次访问,只计算一次UV。UV可以认为是前端页面统计中一个最有价值的统计指标,因为其直接反应页面的访问用户数。目前有较多站点的UV是按照一天之内访问目标页面的IP数来计算的,因此也可以根据UV来统计站点的周活跃用户量和月活跃用户量。严格来讲,根据一天时间内访问目标页面的IP数来计算UV是不严谨的,因为在办公区或校园局域网的情况下,多个用户访问互联网网站的IP可能是同一个,但实际上的访问用户却有很多。所以为了得到更加准确的结果,除了根据IP,还需要结合其他的辅助信息来识别统计不同用户的UV,比如有两种常用的方式:根据浏览器Cookie 和IP统计:在目标页面每次打开时向浏览器中写入唯一的某个Cookie信息,再结合IP一起上报统计,就可以精确统计出一天时间内访问页面的用户数。存在的问题是如果用户手动清除了Cookie再进入访问,页面被重新访问时就只能算第二次。结合用户浏览器标识userAgent和IP统计:由于使用Cookie统计存在可能被手动清除的问题,所以推荐结合浏览器标识userAgent 来统计。这样可以在一定程度上区分同IP下的不同用户,但也不完全准确,IP和浏览器标识userAgent相同的情况也很常见,但仍却只能计算一次。由此可见,虽然UV是网站统计的一个很重要的统计量,但一般情况下是无法用于精确统计的,所以通常需要结合PV、UV来一起分析网站被用户访问的情况。此外,我们还可以对站点一天的新访客数、新访客比率等进行统计,计算第一次访问网站的新用户数和比例,这对判断网站用户增长也是很有意义的。VVPV和UV更多是针对单页面进行的统计,而VV则是用户访问整个网站的统计指标。例如用户打开站点,并在内部做了多次跳转操作,最后关闭该网站所有的页面,即为一次VV。IPIP是一天时间内访问网页或网站的独立IP数,一般服务器端可以直接获取用户访问网站时的独立IP,统计也比较容易处理。需要注意IP统计与UV统计的区别和联系。用户行为分析对于较小的项目团队来说,或许得到页面或网站的PV、UV、VV、IP这些基本的统计数据就可以了。其实相对于访问量的统计,用户行为分析才是更加直接反映网页内容是否受用户喜欢或满足用户需求的一个重要标准,用户在页面上操作的行为有很多种,每种操作都可能对应页面上不同的展示内容。如果我们能知道用户浏览目标页面时所有的行为操作,一定程度上就可以知道用户对页面的哪些内容感兴趣,对哪些内容不感兴趣,这对产品内容的调整和改进是很有意义的。一般用于分析用户行为的参数指标主要包括:页面点击量、用户点击流、用户访问路径、用户点击热力图、用户转换率、用户访问时长分析和用户访问内容分析等。页面点击量页面点击量用来统计用户对于页面某个可点击或可操作区域的点击或操作次数。以点击的情况为例,统计页面上某个按钮被点击的次数就可以通过该方法来计算,这样通过统计的结果可以分析出页面上哪些按钮对应的内容是用户可能感兴趣的。用户点击流分析点击流用来统计用户在页面中发生点击或操作动作的顺序,可以反映用户在页面上的操作行为。所以统计上报时需要在浏览器上先保存记录用户的操作顺序,例如在关键的按钮中埋点,点击时向localStorage中记录点击或操作行为的唯一id,在用户一次VV结束或在下一次VV开始时进行点击流上报,然后通过后台归并统计分析。用户访问路径分析用户访问路径和用户点击流有点类似,不过用户访问路径不针对用户的可点击或操作区域埋点,而是针对每个页面埋点记录用户访问不同页面的路径。上报信息的方法和用户点击流上报相同,常常也是在一次VV结束或下一次VV开始时,上报用户的访问路径。用户点击热力图用户点击热力图是为了统计用户的点击或操作发生在整个页面哪些区域位置的一种分析方法,一般是统计用户操作习惯和页面某些区域内容是否受用户关注的一种方式。这种统计方法获取上报点的方式主要是捕获鼠标事件在屏幕中的坐标位置进行上报,然后在服务端进行计算归类分析并绘图。用户转化率与导流转化率对用户转化率的分析一 般在一些临时推广页面或拉取新用户宜传页面上比较常用,这里统计也很简单,例如要统计某个新产品推广页面的用户转化率,通过计算经过该页面注册的用户数相对于页面的PV比例就可以得出。用户转化率 = 通过该页面注册的用户数 / 页面PV相对来说,用户转化率分析的应用场景比较单一。还有另一种导流的页面统计分析和该页面的功能类似,不过其作用是将某个页面的用户访问流量引导到另一个页面中,导流转化率可以用通过源页面导入的页面访问PV相对于源页面的总PV比例来表示。导流转化率 = 通过源页面导入的页面访问PV / 源页面PV本质上,关键的统计分析仍是对现有页面访问量进行对比和计算而得出的,并不是统计出来的。用户访问时长、内容分析用户访问时长和内容分析则是统计分析用户在某些关键内容页面的停留时间,来判断用户对该页面的内容是否感兴趣,从而分析出用户对网站可能感兴趣的内容,方便以后精确地向该用户推荐他们感兴趣的内容。前端日志上报后端开发一般在程序运行出现异常时可以通过写服务器日志的方式来记录错误的信息,然后下载服务器日志打开查看是哪里的问题并进行修复。但是如果是前端页面运行出现了问题,我们却不能打开用户浏览器的控制台记录来查看代码中到底出现了什么错误。一般情况下,在前端开发中,前端工程师按照需求完成页面开发,通过产品体验确认和测试,页面就可以上线了。但不幸的是,产品很快就收到了用户的投诉。用户反映页面点击按钮没反应,我们自己试了一下却一切正常,于是追问用户所用的环境,最后结论是用户使用了一个非常小众的浏览器打开页面,因为该浏览器不支持某个特性,因此页面报错,整个页面停止响应。在这种情况下,用户反馈的投诉花掉了我们很多时间去定位问题,然而这并不是最可怕的,更让我们担忧的是更多的用户遇到这种场景后便会直接抛弃这个有问题的“垃圾产品”。这个问题唯一的解决办法就是在尽量少的用户遇到这样的场景时就把问题即时修复掉,保证尽量多的用户可以正常使用。首先需要在少数用户使用产品出错时知道有用户出错,而且尽量定位到是什么错误。由于用户的运行环境是在浏览器端的,因此可以在前端页面脚本执行出错时将错误信息上传到服务器,然后打开服务器收集的错误信息进行分析来改进产品的质量。要实现这个过程,我们必须考虑下面几个问题。怎样获取错误日志浏览器提供了try.. .catch和window. onerror的两种机制来帮助我们获取用户页面的脚本错误信息。window.onerror = function (msg, url, lineNo, columnNo, error) { // … handle error … return false;}怎样将错误信息上传到服务器如果捕获到了具体的错误或栈信息,就可以将错误信息进行上报了,如出错信息、错误行号、列号、用户浏览器信息等,通过创建HTTP请求的方式即可将它们发送到日志收集服务器。当然错误信息上报设计时需要注意一点:页面的访问量可能很大,如果到达百万级、千万级,那么就需要按照一定的条件上报,例如根据一定的概率进行上报,否则大量的错误信息上报请求会占用日志收集服务器的很多资源和流量。怎样通过高效的方式来找到问题为了方便查看收集到的这些信息,我们通常可以建立一个简单的内容管理系统(Content Management System,CMS)来管理查看错误日志,对同一类型的错误做归并统计,也可以建立错误量实时统计来查看错误量的即时变化情况。当某个版本发布后,如果收到的错误量明显增加,就需要格外注意。另外一点要注意的是,上报错误信息机制是用来辅助产品质量改进的,不能因为在页面中添加了错误信息收集和上报而影响了原有的业务模块功能。文件加载失败监控如果要进一步完善地检测页面的异常信息,可以尝试对静态资源文件加载失败的情况进行监控。例如在CDN网络中,可能因为部分机器故障,导致用户加载不到<img>、<script>等静态资源,但是开发者不一定能复现,而且无法第一时间知道静态资源加载失败了。这种情况下这就需要在页面上自动捕获文件加载失败的异常来进行处理,可以对<img>或<script>标签元素的readyChange进行是否加载成功的判断。不幸的是,只有部分IE浏览器支持<img>或<script>的readyState,因此一般还需要结合其他方式,如onload,针对不同浏览器分开处理。前端性能分析上报开发者怎样知道用户端打开页面时的性能如何呢,一个可行的方法就是将页面性能数据进行上报统计,例如将PerformanceTiming 数据、开发者自己埋点的性能统计数据通过页面JavaScript统一上报到远程服务器,在服务器端统计计算性能数据的平均值来评判前端具体页面的性能情况。以上介绍的是前端页面数据统计和分析的主要内容,在实际项目中可以根据产品或开发需要来进行调整。需要注意的是,不要过度设计,例如对于访问量很少的网站进行大量的用户行为分析可能就得不偿失了。搜索引擎优化搜索引擎优化简称SEO。对于很多网站来说,搜索引擎是最重要的入口,提升自然排名相当于提升网站的曝光度,作为前端工程师,了解搜索引擎优化方面的相关知识是很重要的。title、keywords、description 的优化title. keywords、 description 是可以在HTML的<meta>标签内定义的,有助于搜索引擎抓取到网页的内容。要注意的是,一般title的权重是最高的,也是最重要的。keywords 相对权重较低,可以作为页面的辅助关键词搜索。description的描述一般会直接显示在搜索结果的介绍中,可以使用户快速了解页面内容的描述文字,所以要尽量让这段文字能够描述整个页面的内容,增加用户进入页面的概率。title的优化一般title的设置要尽量能够概括页面的内容,可以使用多个title关键字组合的形式,并用分隔符连接起来。分隔符一般有 “”、“-”、“ ”、“,”等,其中“”分隔符比较容易被百度搜索引擎检索到,“-”分隔符则容易被谷歌搜索引擎检索到,“,” 则在英文站点中使用比较多,可以使用空格。title 的长度在桌面浏览器端一般建议控制在 30个字以内,在移动端控制在20个字以内,若长度超出时浏览器会默认截断并显示省略号。关于title格式的优化设置可以遵循以下规则:每个网页都应该有独一无二的标题,切忌所有的页面都使用同样的默认标题。标题主题明确,应该包含网页中最重要的信息。简明精练,不应该罗列与网页内容不相关的信息。用户浏览通常从左到右的,建议将重要的内容放到title靠前的位置。使用用户所熟知的语言描述,如果有中、英文两种网站名称,尽量使用用户熟知的语言作为标题描述。对于网站不同页面title的定义可以设置如下:首页:网站名称提供服务介绍或产品介绍列表页:列表名称_网站名称文章页:文章标题_文章分类_网站名称如果文章标题不是很长,还可以增加部分关键词来提高网页的检索量,如文章title 关键词_网站名称例如某个博客的名称为极限前端,那么其首页的title就可以如下编写:<!– 不好的title设置–><title>极限前端</title><title>极限前端 front end</title><!– 良好的title设置–><title>极限前端_首页 前端技术知识_某某某的博客</title>keywords的优化keywords是目前用于页面内容检索的辅助关键字信息,容易被搜索引擎检索到,所以恰当的设置页面keywords内容对于页面的SEO也是很重要的,而且keywords本身的使用也比较简单。description优化在搜索引擎检索结果中,description 更重要的作用是作为搜索结果的描述,而不是作为权值计算的重要参考因素。description 的长度在桌面浏览器页面中一般为78个中文字符,移动端为50个,超过则会自动截断并显示省略号。如下定义title、keywords、description 比较合适:<!–不好的title. keywords、 description优化设置–><title>极限前端</title><meta name=“keywords” content=“极限前端”><meta name=“description” content=“极限前端”><!–良好的title. keywords、 description优化设置–><title>前端搜索引擎优化基础_极限前端_前端技术知识_某某某的博客</title><meta name=“keywords” content=“现代前端技术, 前端页面SEO优化, 极限前端, 某某某的博客”><meta name=“description” content=“本章讲述了前端搜索引擎优化基础实践技术。">语义化标签的优化title、keywords. description 的设置对页面SEO具有重要意义,但除了页面title、keywords、description外,还有页面结构语义化设计,因为搜索引擎分析页面内容时可以解析语义化的标签来获取内容,并赋予相关的权重,因此语义化结构的页面就比全部为<div>标签元素布局的页面更容易被检索到。使用具有语义化的HTML5标签结构如果页面兼容性条件允许,尽量使用HTML5语义化结构标签。使用<header>、 <nav>、 <aside>、 <article>、 <footer>等标签增加页面的语义化内容,可以让搜索引擎更容易获取页面的结构内容。唯一的H1标题建议每个页面都有一个唯一的<h1>标题, 但一般<h1>内容并不是网站的标题。<h1>作为页面最高层级的标题能够更容易被搜索引擎收录,并赋予页面相对较高权重的内容描述。一般设置首页的<h1>标题为站点名称,其他内页的<h1>标题则可以为各个内页的标题,如分类页用分类的名字、详情页用详情页标题等。因为SEO的需要,应该尽量保证搜索引擎抓取到的页面是有内容的,但是以AJAX技术实现的SPA应用在SEO上不具有优势,因此要尽量避免这样的页面实现方式。<img>添加alt属性一般要求<img>标签必须设置 alt属性,这样更有利于搜索引擎检索出图片的描述信息。URL规范化统一网站的地址链接:http://www.domain.comhttp://domain.comhttp://www.domain.com/index.htmlhttp://domain. com/index.html以上四个地址都可以表示跳转到同一个站点的首页,虽然不会对用户访问造成什么麻烦,但对于搜索引擎来说是四条网址并且内容相同。这种情况有可能会被搜索引擎误认为是作弊手段,另外当搜索引擎要规范化网址时,需要从这些选择中挑一个作为代表,但是挑的这个不一定是最好的,因此我们最好统一搜索引擎访问页面的地址,否则可能影响网站入口搜索结果的权重。301跳转如果URL发生改变,一定要使旧的地址301指向新的页面,否则搜索引擎会把原有的这个URL当作死链处理,之前完成的页面内容收录权重的工作就都失效了。canonical当该页面有不同参数传递的时候,标签属性也可以起到标识页面唯一性的作用,例如以下三个地址。domain.com/index.htmldomain.com/index.html?from=123domain.com/index.html?from=456在搜索引擎中,以上三个地址分别表示三个页面,但其实后面两个一般表示页面跳转的来源,所以为了确保这三个地址为同一个页面,往往在<head>上加上canonical声明,告诉搜索引擎在收录页面时可以按照这个href提供的页面地址去处理,而不是将每个地址都独立处理。<link rel=“cononical” href=”//:domain. com/index.html" />robotsrobots.txt是网站站点用来配置搜索引擎抓取站点内容路径的一种控制方式,放置于站点根目录下。搜索引擎爬虫访问网站时会访问robots.txt文件,robots.txt可以指导搜索引擎爬虫禁止抓取网站某些内容或只允许抓取哪些内容,这就保证了搜索引擎不抓取站点中临时或不重要的内容,保证网站的主要内容被搜索引擎收录。sitemapsitemap格式一般分为HTML和XML两种,命名可以为sitemap.html或sitemap.xml,作用是列出网站所有的URL地址,方便搜索引擎去逐个抓取网站的页面,增加网站页面在搜索引擎中的的曝光量。关于SEO的内容有很多,这里只是简单提了一些实际开发中可能涉及的部分。关于内外链、权重、内容结构、内容建设等和编码基本没啥关系的,就没有说了。前端协作前端技术涉及UI界面、数据展示、用户交互等实现,因此不可避免地要和团队其他成员进行协作沟通,如产品经理、UI设计师、交互设计师、后台工程师、运维工程师等。前端主要协作的内容有:和产品经理:主要关注需求是否明确,技术方案是否可行,需求性价比是否高,是否有简单的可接受的替代方案,需求变更的影响等。和后端工程师:主要关注数据接口定义,线上问题定位,接口调试等。和UI设计师:主要关注设计图是否容易实现,使用什么样的组件,操作过程中的交互和动画效果等。和运维工程师:主要关注如何上线,环境配置等。注意在协作过程中不要殴打同事。 ...

December 14, 2018 · 4 min · jiezi

关于回流与重绘优化的探索

前言杭州下雪了,冷到不行,在家躺在床上玩手机,打开微信进入前端交流群里日常吹水,看到大佬在群里发了一篇文章你应该要知道的重绘与重排,文章里有一段骚操作,就是为了减少重绘与重排,合并样式操作,这个骚操作成功的引起了我的注意,然后开启了我的探索。正文前言中描述的合并样式的骚操作是如下:var el = document.querySelector(‘div’);el.style.borderLeft = ‘1px’;el.style.borderRight = ‘2px’;el.style.padding = ‘5px’;原文描述的大概意思是这段代码多次对 DOM 的修改和对样式的修改,页面会进行多次回流或者重绘,应该进行如下优化:var el = document.querySelector(‘div’);el.style.cssText = ‘border-left: 1px; border-right: 1px; padding: 5px;‘这样的优化在以前我刚开始学习前端的时候,经常也在一些相关的性能优化的文章里看到,因为一直没有探究过,概念里一直觉得自己应该把多次 DOM 的样式的修改合并在一起,这样效率会更高,直到后来,自己对浏览器的进程与线程慢慢有了了解,曾经也写过一篇博客,浅谈浏览器多进程与JS线程,其中有一个概念是,JS线程与GUI渲染线程是互斥关系,大概的意思就是当js引擎在执行js代码的时候,浏览器的渲染引擎是被冻结了的,无法渲染页面的,必须等待js引擎空闲了才能渲染页面。这个概念,JS线程与GUI渲染线程是互斥关系与上面描述的骚操作似乎有点冲突,也就是当我们对el.style进行一系列赋值的时候,渲染引擎是被冻结的状态,怎么会进行多次重绘或者回流?带着这样的疑问,写了一个小demo,代码如下。<!DOCTYPE html><html><head> <title>测试页</title> <style> #box { width: 109px; height: 100px; background-color: lightsteelblue; border-style: solid; } </style></head><body> <div id=“box”></div></body><script>var box = document.getElementById(‘box’);var toggle = 0;var time = 500;function toggleFun() { var borderWidth = toggle ? 20 : 0; var borderColor = toggle ? ‘coral’ : ’transparent’; if (toggle) { box.style.borderWidth = ‘50px’; box.style.borderWidth = borderWidth + ‘px’; box.style.borderColor = borderColor; } else { box.style.cssText = ‘border: ’ + borderWidth + ‘px solid’ + borderColor; } toggle = toggle ? 0 : 1;}setInterval(toggleFun, time)</script></html>代码大概的意思就是定时以两种操作设置样式,收集浏览器的回流或者重绘次数。打开chrome的开发者工具,切换到Performance选项卡,点击左上角的圆 ○,开始record,等几秒后stop,点击Frames查看Event log选项卡,内容如下:大概可以看到,Recalculate Style -> Layout -> Update Layer Tree -> Paint -> Composite Layers 这个过程在循环进行,触发的目标代码是第25行代码合29行代码,也就是box.style.borderWidth = ‘50px’;和box.style.cssText = ‘border: ’ + borderWidth + ‘px solid’ + borderColor;。首先回顾一下浏览器渲染页面的流程:请求拿到html报文。同时解析生成CSS规则树和DOM树。合并CSS规则树和DOM树,生成render树。渲染进程根据render树进行Layout。绘制paint页面。然后在看看上面的过程,可以容易看出,首先,Recalculate Style,重新计算css规则树。进行Layout,这里的Layout可以理解成回流,重新计算每个元素的位置。Update Layer Tree,字面意思理解,更新层级树。Paint,绘制页面,在这里可以理解成重绘。Composite Layers,字面意思理解,合并层级。由上面过程得到结果,当在同一执行任务里面对DOM的样式进行多次操作的时候,只会进行一次回流或者重绘,也就是说,只要我们的js引擎时候忙碌的,渲染引擎是冻结的时候,无论对DOM样式进行多少次操作,都只会进行一次回流或者重绘,也就是说前面说的合并样式优化是无效的。这个时候,我对上面过程又产生了新的疑问,为什么要Paint之后在Composite Layers呢?为什么不把所有层合并完了在绘制页面呢?…………………….(看搜索相关资料去了)翻看资料结束后,我得到以下理解。首先理解layer概念,可以理解成PS里面的图层,我们知道PS文件最后保存层PSD文件,当图层越多的时候,PSD文件就越大,在我们的浏览器里面也是一样的,我们的layer越多,所占的内存就越大。然后理解Paint真正做的事情,paint的任务大概就是把所有的layer绘制到页面中,这个绘制与canvas的绘制不一样,canvas的绘制相当于在画布里把像素直接绘制成指定颜色,然后我们直接看到的东西就直接是像素颜色,而我们这里说的Paint只是把图层丢到页面中,最后的绘制,需要交给Composite线程处理。最后是Composite Layers,由composite线程进行,这个线程在浏览器的Renderer进程中,任务是把Paint时候丢上页面的图层转化成位图,最终生成我们肉眼可以看到的图像,所以,真正的绘制,应该是Composite Layers过程进行的。由于paint与composite解耦,浏览器对每一个layer都有一个标识,这个标识用来标识该layer是否需要重绘,在有CSS规则树变化的时候,浏览器只会对这些被标识的layer进行重绘,用这样的方式提高浏览器的渲染性能。最后前端大法博大精深,越往下学越觉得自己不适合前端!!!仿佛看到自己在从入门到跑路这条路上快走到了终点。。。参考Chrome 性能调优简介浏览器解析过程GPU:合成加速 ...

December 9, 2018 · 1 min · jiezi

预防cdn链接失效,无缝切换本地文件

如今的前端项目追求的不仅仅是能用能看的程度,而是愈发追求项目的性能,对用户体验的影响。而现在的开发工具在性能优化方面也替我们做很大一部分的工作,想必大家对CDN的使用都是轻车熟路了,但是大家有没有考虑过,万一我们使用的CDN服务器,地址啥的出现了问题,导致我们引用的CDN文件都拿不到了,那么我们的项目崩溃了,天了噜,崩溃了。因此我们得考虑下,引用CDN的文件拿不到了,应该有相应的处理方案,而不会直接导致我们项目崩溃。想必大家都能想到的处理方案就是:如果引用CDN的文件出错了,拿不到,那么我们就引用本地相对应的文件。有人肯定会说,这样处理会导致项目体积变大的。对,是的,没有错,会使项目体积变大,但是这种处理总不会直接导致项目崩溃的,给用户的体验能更好一点,牺牲这么一点点是值得的。很多事情都是没有绝对的情况,都是相对的,这就需要我们自己去权衡了。话不多说,直接进入正题。下面以Vue项目为列:当然是我们项目的启动页/index.html <script src=“https://cdn.bootcss.com/vue/2.5.15/vue.min.js"></script> <script>!window.Vue && document.write(unescape(’%3Cscript src=”./static/js/vue.min.js"%3E%3C/script%3E’))</script> <script src=“https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script> <script>!window.VueRouter && document.write(unescape(’%3Cscript src=”./static/js/vue-router.min.js"%3E%3C/script%3E’))</script> <script src=“https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script> <script>!window.axios && document.write(unescape(’%3Cscript src=”./static/js/axios.min.js"%3E%3C/script%3E’))</script> <script src=“https://cdn.bootcss.com/element-ui/2.4.9/index.js"></script> <script>!window.Element && document.write(unescape(’%3Cscript src=”./static/js/element-ui-index.js"%3E%3C/script%3E’))</script> <script>!window.Element && document.write(unescape(’%3Clink href="./static/css/element-ui-index.css" rel=“stylesheet”%3E%3C/link%3E’))</script> <script src=“https://cdn.bootcss.com/qs/6.5.2/qs.min.js"></script> <script>!window.Qs && document.write(unescape(’%3Cscript src=”./static/js/qs.js"%3E%3C/script%3E’))</script> <script src=“https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script> <script>!window.Vuex && document.write(unescape(’%3Cscript src=”./static/js/vuex.min.js"%3E%3C/script%3E’))</script>下面就在啰嗦几点,看懂的慢走,不送啦。不太懂的再瞅瞅。1)、不用担心会全部加载,出现重复的情况,不信邪的自己去NetWork里验证。2)、!window.Vue、!window.VueRouter、!window.axios、!window.Element、!window.Qs、!window.Vuex这些都是些什么鬼? 那是第三方库暴露出来的方法名。也可以去看看我之前 “配置webpack中externals来减少打包后vendor.js的体积” 这篇文章。3)、对js文件的处理都是这样的,但是对css文件应该怎么处理呢。 眼尖的已经看到了,上面那段中已经写出来。4)、心细善于思考的你,还会发现,其实对css文件的处理还是存在问题的。 因为难以直接判断出CDN上的css文件是否成功加载,所以直接用js文件加载完成的判断方式,打包一起处理。这样如果CDN上的整个Element-UI出现问题,那js和css都可以加载本地的。最坏的情况是,CDN上的css文件很不巧的出现问题了呢? 那就让凉吧。没办法。。。哈哈哈因为对于css文件的处理,暂时没有想到好一点的处理方案,所以目前只能先这样统一处理。不可否认对css处理这里是存在问题的。如果你有更好的处理方案,欢迎评论告知我,谢谢。 当然文中有不当的地方,也欢迎大家指正,谢谢。

November 30, 2018 · 1 min · jiezi

服务器小白的我,是如何成功将 node+mongodb 项目部署在服务器上并进行性能优化的

前言本文讲解的是:做为前端开发人员,对服务器的了解还是小白的我,是如何一步步将 node+mongodb 项目部署在阿里云 centos 7.3 的服务器上,并进行性能优化,达到页面 1 秒内看到 loading ,3 秒内看到首屏内容的。搭建的项目是采用了主流的前后端分离思想的,这里只讲 服务器环境搭建与性能优化。效果请看 http://乐趣区.cn/main.html1. 流程开发好前端与后端程序。购买服务器与域名服务器上安装所需环境(本项目是 node 和 mongodb )服务器上开放端口与设置规则用 nginx、apache 或者tomcat 来提供HTTP服务或者设置代理上传项目代码 或者 用码云或者 gihub 来拉取你的代码到服务器上启动 express 服务器优化页面加载2. 内容细节2.1 开发好前端与后端程序开发好前端与后端程序,这个没什么好说的,就是开发!开发!开发!再开发!2.2 购买服务器与域名本人一直觉得程序员应该有一个自己的个人网站,拥有自己的域名与服务器。学知识或者测试项目的时候可以用来测试。阿里云有个专供学生的云翼计划 阿里云学生套餐,入门级的云服务器原价1400多,学生认证后只要114一年,非常划算。还是学生的,直接购买;不是学生了,有弟弟、妹妹的,可以用他们的大学生身份,购买,非常便宜实用(我购买的就是学生优惠套餐)。当然阿里云服务器在每年双 11 时都有很大优惠,也很便宜,选什么配置与价格得看自己的用处。服务器预装环境可以选择 CentOS 或者 windows server,,为了体验和学习 linux 系统,我选择了CentOS。再次是购买域名 阿里域名购买,本人也是在阿里云购买的。域名是分 国际域名与国内域名的,国际域名是不用备案的,但是国内的域名是必须 ICP备案的 阿里云ICP代备案管理系统,不然不能用,如果是国内域名,如何备案域名,请自己上网查找教程。当然如果你的网站只用来自己用的话,可以不用买域名,因为可以通过服务器的公网 ip 来访问网站内容的。如果购买了域名了,还要设置域名映射到相应的公网 ip ,不然也不能用。3. 服务器上安装所需环境(本项目是 node 和 mongodb )3.1 登录服务器因本人用的是 MacBook Pro ,所以直接打开 mac 终端,通过下面的命令行连接到服务器。root 是阿里云服务器默认的账号名,连接时候会叫你输入密码,输入你购买时设置的或者后来设置的密码。ssh root@47.106.20.666 //你的服务器公网 ip,比如 47.106.20.666如图:window 系统的,请用 Putty 或 Xshell 来登录,可以参考一下这篇文章 把 Node.js 项目部署到阿里云服务器(CentOs)一般在新服务器创建后,建议先升级一下 CentOS:yum -y update常用的 Linux 命令cd 进入目录cd .. 返回上一个目录ls -a 查看当前目录mkdir abc 创建abc文件夹mv 移动或重命名rm 删除一个文件或者目录3.2 安装 node升级常用库文件, 安装 node.js 需要通过 g++ 进行编译。yum -y install gcc gcc-c++ autoconf跳转到目录:/usr/local/src,这个文件夹通常用来存放软件源代码:cd /usr/local/src下载 node.js 源码,也可以使用 scp 命令直接上传,因为下载实在太慢了:下载地址:Downloads,请下载最新的相应版本的源码进行下载,本人下载了 v10.13.0 版本的。https://nodejs.org/dist/v10.13.0/node-v10.13.0.tar.gz下载完成后解压:tar -xzvf node-v10.13.0.tar.gz进入解压后的文件夹:cd node-v10.13.0执行配置脚本来进行预编译处理:./configure编译源代码,这个步骤花的时间会很长,大概需要 5 到 10 分钟:make编译完成后,执行安装命令,使之在系统范围内可用:make install安装 express 推荐 global 安装npm -g install express建立超级链接, 不然 sudo node 时会报 “command not found"sudo ln -s /usr/local/bin/node /usr/bin/nodesudo ln -s /usr/local/lib/node /usr/lib/nodesudo ln -s /usr/local/bin/npm /usr/bin/npmsudo ln -s /usr/local/bin/node-waf /usr/bin/node-waf通过指令查看 node 及 npm 版本:node -vnpm -vnode.js 到这里就基本安装完成了。3.2 安装 mongodb下载地址:mongodb下载时,请选对相应的环境与版本,因为本人的服务器是 CentOS ,其实本质就是 linux 系统,所以选择了如下图环境与目前最新的版本。mongodb :软件安装位置:/usr/local/mongodb数据存放位置:/home/mongodb/data数据备份位置:/home/mongodb/bak日志存放位置:/home/mongodb/logs下载安装包> cd /usr/local> wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.4.tgz解压安装包,重命名文件夹为 mongodbtar zxvf mongodb-linux-x86_64-4.0.4.tgzmv mongodb-linux-x86_64-4.0.4 mongodb在 var 文件夹里建立 mongodb 文件夹,并分别建立文件夹 data 用于存放数据,logs 用于存放日志mkdir /var/mongodbmkdir /var/mongodb/datamkdir /var/mongodb/logs打开 rc.local 文件,添加 CentOS 开机启动项:vim /etc/rc.d/rc.local// 不懂 vim 操作的请自行查看相应的文档教程,比如: vim 模式下,要 按了 i 才能插入内容,输入完之后,要按 shift 加 :wq 才能保存退出。将 mongodb 启动命令追加到本文件中,让 mongodb 开机自启动:/usr/local/mongodb/bin/mongod –dbpath=/var/mongodb/data –logpath /var/mongodb/logs/log.log -fork启动 mongodb/usr/local/mongodb/bin/mongod –dbpath=/var/mongodb/data –logpath /var/mongodb/logs/log.log -fork看到如下信息说明已经安装完成并成功启动:forked process: 18394all output going to: /var/mongodb/logs/log.logmongodb 默认的端口号是 27017。如果你数据库的连接要账号和密码的,要创建数据库管理员,不然直接连接即可。在 mongo shell 中创建管理员及数据库。切换到 admin 数据库,创建超级管理员帐号use admindb.createUser({ user: “用户名”, pwd:“登陆密码”, roles:[{ role: “userAdminAnyDatabase”, db: “admin” }] })切换到要使用的数据库,如 taodb 数据库,创建这个数据库的管理员帐号use taodbdb.createUser({ user: “用户名”, pwd:“登陆密码”, roles:[ { role: “readWrite”, db: “taodb” }] //读写权限 })重复按两下 control+c ,退出 mongo shell。到这里 mongodb 基本已经安装设置完成了。备份与恢复 请看这篇文章:MongoDB 备份(mongodump)与恢复(mongorestore)安装 node 与 mongodb 也可以参考这篇文章:CentOs搭建NodeJs服务器—Mongodb安装3.3 服务器上开放端口与设置安全组规则如果你只放静态的网页,可以参考这个篇文章 通过云虚拟主机控制台设置默认首页但是我们是要部署后台程序的,所以要看以下的内容:安全组规则是什么鬼授权安全组规则可以允许或者禁止与安全组相关联的 ECS 实例的公网和内网的入方向和出方向的访问。 阿里云安全组应用案例文档80 端口是为 HTTP(HyperText Transport Protocol) 即超文本传输协议开放的,浏览器 HTTP 访问 IP 或域名的 80 端口时,可以省略 80 端口号如果我们没有开放相应的端口,比如我们的服务要用到 3000 ,就要开放 3000 的端口,不然是访问不了的;其他端口同理。端口都配置对了,以为能用公网 IP 进行访问了么 ? 小兄弟你太天真了 …还有 防火墙 这一关呢,如果防火墙没有关闭或者相关的端口没有开放,也是不能用公网 IP 进行访问网站内容的。和安全组端口同理,比如我们的服务要用到的是 3000 端口,就要开放 3000 的端口,不然是访问不了的;其他端口同理。出于安全考虑还是把防火墙开上,只开放相应的端口最好。怎么开放相应的端口 ? 看下面两篇文章足矣,这里就不展开了。1. 将nodejs项目部署到阿里云ESC服务器,linux系统配置80端口,实现公网IP访问2. centos出现“FirewallD is not running”怎么办3.4 用 nginx、apache 或者 tomcat 来提供 HTTP 服务或者设置代理我是用了 nginx 的,所以这里只介绍 nginx 。安装 nginx 请看这两篇文章:1. Centos7安装Nginx实战2. 阿里云Centos7安装Nginx服务器实现反向代理开启 ngnx 代理进入到目录位置cd /usr/local/nginx在 nginx 目录下有一个 sbin 目录,sbin 目录下有一个 nginx 可执行程序。./nginx关闭 nginx./nginx -s stop重启./nginx -s reload基本的使用就是这样子了。如下给出我的 nginx 代理的设置:我的两个项目是放在 /home/blog/blog-react/build/; 和 /home/blog/blog-react-admin/dist/; 下的,如果你们的路径不是这个,请修改成你们的路径。#user nobody;worker_processes 1;#error_log logs/error.log;#error_log logs/error.log notice;#error_log logs/error.log info;#pid logs/nginx.pid;events { worker_connections 1024;}http { include mime.types; default_type application/octet-stream; #log_format main ‘$remote_addr - $remote_user [$time_local] “$request” ’ # ‘$status $body_bytes_sent “$http_referer” ’ # ‘"$http_user_agent” “$http_x_forwarded_for”’; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; # 如果port_in_redirect为off时,那么始终按照默认的80端口;如果该指令打开,那么将会返回当前正在监听的端口。 port_in_redirect off; # 前台展示打开的服务代理 server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; #root /home/blog; location / { root /home/blog/blog-react/build/; index index.html; try_files $uri $uri/ @router; autoindex on; } location @router{ rewrite ^.$ /index.html last; } location /api/ { proxy_set_header X-Real-IP $remote_addr; proxy_pass http://47.106.136.114:3000/ ; } gzip on; gzip_buffers 32 4k; gzip_comp_level 6; gzip_min_length 200; gzip_types text/css text/xml application/javascript; gzip_vary on; #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } # HTTPS server # 管理后台打开的服务代理 server { listen 4444; server_name localhost; # charset koi8-r; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; location / { root /home/blog/blog-react-admin/dist/; index index.html index.htm; try_files $uri $uri/ @router; autoindex on; } location @router{ rewrite ^.$ /index.html last; } location /api/ { proxy_set_header X-Real-IP $remote_addr; proxy_pass http://47.106.136.114:3000/ ; } gzip on; gzip_buffers 32 4k; gzip_comp_level 6; gzip_min_length 200; gzip_types text/css text/xml application/javascript; gzip_vary on; error_page 500 502 503 504 /50x.html; }}我是开了两个代理的:前台展示打开的服务代理和管理后台打开的服务代理,这个项目是分开端口访问的。比如:我的公网 ip 是 47.106.20.666,那么可以通过 http://47.106.20.666 即可访问前台展示,http://47.106.20.666:4444 即可访问管理后台的登录界面。至于为什么要写这样的配置:try_files $uri $uri/ @router;location @router{ rewrite ^.*$ /index.html last; }因为进入到文章详情时或者前端路由变化了,再刷新浏览器,发现浏览器出现 404 。刷新页面时访问的资源在服务端找不到,因为 react-router 设置的路径不是真实存在的路径。所以那样设置是为了可以刷新还可以打到对应的路径的。刷新出现 404 问题,可以看下这篇文章 react,vue等部署单页面项目时,访问刷新出现404问题3.5 上传项目代码,或者用码云、 gihub 来拉取你的代码到服务器上我是创建了码云的账号来管理项目代码的,因为码云上可以创建免费的私有仓库,我在本地把码上传到 Gitee.com 上,再进入服务器用 git 把代码拉取下来就可以了,非常方便。具体请看:码云(Gitee.com)帮助文档 V1.2git 的安装请看: CentOS 7.4 系统安装 git如果不想用 git 进行代码管理,请用其他可以连接服务器上传文件的软件,比如 FileZilla。3.6 启动 express 服务启动 express 服务,我用了 pm2, 可以永久运行在服务器上,且不会一报错 express 服务就挂了,而且运行中还可以进行其他操作。安装:npm install -g pm2切换当前工作目录到 express 应用文件夹下,执行 pm2 命令启动 express 服务:pm2 start ./bin/www比如我操作项目时的基本操作:cd /home/blog/blog-nodepm2 start ./bin/www // 开启pm2 stop ./bin/www // 关闭pm2 list //查看所用已启动项目:3.7 页面加载优化再看刚刚的 nginx 的一些配置:server { gzip on; gzip_buffers 32 4k; gzip_comp_level 6; gzip_min_length 200; gzip_types text/css text/xml application/javascript; gzip_vary on; }这个就是利用 ngonx 开启 gzip,亲测开启之后,压缩了接近 2/3 的文件大小,本来要 1M 多的文件,开启压缩之后,变成了 300k 左右。还有其他的优化请看这篇文章 React 16 加载性能优化指南,写的很不错,我的一些优化都是参考了这个篇文章的。做完一系列的优化处理之后,在网络正常的情况下,页面首屏渲染由本来是接近 5 秒,变成了 3 秒内,首屏渲染之前的 loading 在 1 秒内可见了。4. 项目地址本人的个人博客项目地址:前台展示: https://github.com/乐趣区/blog-react管理后台:https://github.com/乐趣区/blog-react-admin后端:https://github.com/乐趣区/blog-nodeblog:https://github.com/乐趣区/blog本博客系统的系列文章:react + node + express + ant + mongodb 的简洁兼时尚的博客网站react + Ant Design + 支持 markdown 的 blog-react 项目文档说明基于 node + express + mongodb 的 blog-node 项目文档说明服务器小白的我,是如何将node+mongodb项目部署在服务器上并进行性能优化的5. 最后对 全栈开发 有兴趣的朋友,可以扫下方二维码,关注我的公众号,我会不定期更新有价值的内容。微信公众号:乐趣区分享 前端、后端开发 等相关的技术文章,热点资源,全栈程序员的成长之路。关注公众号并回复 福利 便免费送你视频资源,绝对干货。福利详情请点击: 免费资源分享–Python、Java、Linux、Go、node、vue、react、javaScript ...

November 26, 2018 · 4 min · jiezi

[盘点]项目中可以怎么优化图片

看似平常的事物,往往会蕴含的巨大的智慧。把看似平常的事物简单做好,可能很正常。如果能把平常的事物做精,做细,这个不平常。1.前言每一个开发者在开发项目中,不可避免要和图片打交道,优化图片似乎也成了一个必修课。图片优化也不仅仅是性能上的优化,还要进行体验上的优化。至于怎么优化图片,没有固定的方式,只能具体场景,具体分析,选择合适的方案。不多说,下面也简单介绍下自己处理过,了解过的一些方式。如果大家有补充,建议。欢迎在评论区留言,交流学习下。2.概念用法‘概念用法’这个词是自己乱起的,可能不太准确,是因为词穷了,不知道怎样形容。总得来说,这部分介绍的处理方式,就是讲一下就知道怎么用的方式,不需要怎么放代码,运行图等。只需要笼统的介绍一下,大家都会懂的一些方式。2-1.图片压缩这个没有隐含的意思,就是把图片的大小进行压缩。目前自己用的比较多的两个压缩网站是TinyPng和智图。使用比较方便,品质也基本保持一致。2-2.base64代替小图标一些比较小的图标,使用 base64 编码代替可以减少 http 请求。但是有一个缺点就是转成 base64 后,编码会比原图更大,图片越大,差别就越大。1K左右的图标,转码出来的 base64 大概是 1.1K-2K。如果是 8K 的图片,转码出来的 base64 可能超过10K。就自己项目开发而言,只有小于 4K 的图标,才会进行转码。2-3.icon-font代替图标由于 icon-font 看着是图片,实际上是字体。优点:就是在于可以矢量缩放,大小图标都可以使用,也可以改变颜色,使用也不麻烦。缺点:需要引入的文件不少(.svg,.ttf,.woff,.eot )。文件大小也比较大。建议是项目的图标要达到一定量才使用 icon-font,如果是几个图标,还是用图片吧。如果需要引入的图标多,就建议使用 icon-font。上面说的 icon-font 由于是字体,所以不支持多色图标。有了解到,现在 icon-font 可以支持多色图标了(symbol引用)。只是兼容性不好。2-4.雪碧图雪碧图就是把很多小的图整合到一起,制作成一张比较大的图,然后作为元素的背景图片使用,定位到相应的图片即可。优点:减少了大量的 http 请求。缺点:背景定位和在移动端适配大小有点麻烦。除此之外,使用雪碧图,有两个个注意地方1.不要把页面所有的图片都合并,比如把 logo 整合会破坏 html 的语义结构。图像复杂的 banner 也不要合并2.尽量只把颜色相近的图标整合在一张图片上,如果图片颜色相差太大,合并出来的图片可能会很大。2-5.响应式图片比如页面上有一张尺寸是 100100 的图片,但是图片的实际尺寸是 10001000 的。这样的情况建议在多准备一张 100*100 的图片。不然可能会造成资源浪费。2-6.混合模式代替变色的图标如下例子,比如页面有这个图标在特定情况下会是下面这个颜色。同一个图标,在不同的时候是不同的颜色。icon-font 可以通过改变 color 实现。或者用两张图片。除了这两个方法,用 CSS3 的混合模式,一样可以实现。两行代码搞定。<!DOCTYPE html><html> <head> <meta charset=“UTF-8”> <title></title> <style> div{ /容器必须有背景/ background: #09f; display: inline-block; } img{ width: 100px; vertical-align: top; } img:hover{ /设置混合模式/ mix-blend-mode: lighten; } </style> </head> <body> <div><img src=“images/icon-good.jpg” class=“u-icon”/></div> <div><img src=“images/icon-good.png” class=“u-icon”/></div> </body></html>运行效果展示完 mix-blend-mode,顺便提下 background-blend-mode 。用法基本一致,只是 mix-blend-mode 作用于 html 元素的混合模式,background-blend-mode 作用于元素背景的混合模式。<!DOCTYPE html><html> <head> <meta charset=“UTF-8”> <title></title> <style> div{ display: inline-block; width: 100px; height: 100px; /设置背景/ background: url(images/icon-good.jpg) no-repeat center,#09f; background-size:100%; /设置背景混合模式/ background-blend-mode: lighten; } </style> </head> <body> <div></div> </body></html>注意事项1.图片必须是白底纯色图标2.现代的浏览器,支持这个属性的浏览器如果图片是透明纯色背景,得到的结果会是这样受限篇幅影响,混合模式暂时就介绍这么多,以后发现好玩的再写文章。有兴趣可以看下面的参考资料。两行 CSS 代码实现图片任意颜色赋色技术不可思议的颜色混合模式 mix-blend-mode不可思议的混合模式 background-blend-mode2-7.简单图标使用 CSS 画有一些简单的图标,可以使用 CSS 代替。比如下面这些自己而言,项目上画的最多的就是各种箭头<!DOCTYPE html><html> <head> <meta charset=“UTF-8”> <title></title> <style> .icon-arrow-bottom { width: 0; height: 0; border: 100px solid #000; border-color: #000 transparent transparent transparent; } .icon-arrow-top { width: 0; height: 0; border: 100px solid #000; border-color: transparent transparent #000 transparent; } </style> </head> <body> <div class=“icon-arrow-bottom”></div> <div class=“icon-arrow-top”</div> </body></html>优点:矢量缩放,颜色可变,不需要发送请求缺点:只适合用简单图形,1-5行 CSS 代码可以搞定的才建议用,超过的不建议。想得痛苦,写也麻烦,花时间也多,效果未必比其它方案好。建议还是图片 base64,或者 icon-font。这里就简单举个例子,需要知道 css3 还可以画什么图形。看参考资料。纯CSS制作的图形效果奇妙的 CSS shapes(CSS图形)【CSS】用CSS绘制图标(图标大全)3.隐式预加载1.从这里开始。下面的demo,有些会用到 ecDo 这个库(自己写的一个常用函数库,欢迎star)。之前的文章有介绍过,这里就不再重复。大家不知道的时候点开看下相应的 API ,运行下,调试下就好。2.为方便展示,下面的demo,除了懒加载,都有在 network 把网速调至了慢速的3G。有些项目图片比较多,如果一次性加载,用户等待时间会过久,可能会造成体验效果很差,甚至导致用户流失,很多网站用到的一个体验优化方式是隐式预加载。等待首屏加载,在用户看首屏(第一张大图)的时候,悄悄的加载其它图片(这里为了展示效果,在项目上其他的小图片不应在第一屏)。<body> <p><img src=“lawyerOtherImg.jpg”/></p> <p>这是预加载的图片</p> <div> <img data-src=“https://materialdb.zhaoyl.com/201809/106796.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/105567.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/103097.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/10205.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/001.jpg" class=“load-img” width=“100” height=“100”/> </div></body>//测试请先清空缓存window.onload = function () { ecDo.loadImg(’load-img’, function () { console.log(‘加载完毕’) });}注意事项:1.大概预测,用户看首屏的时候,很大概率会往下面看。2.该方式,用户等待的时间比较短。但是图片超大,要慎重考虑。因为该方式无法保证用户在浏览的时候,能把下一屏(比如浏览第一屏的时候,要加载第二屏)的图片加载完毕,让用户无感知。如果切换的下一屏还没加载完毕,也可能会影响体验。demo:https://github.com/chenhuiYj/…4.显式预加载告诉用户正在加载,等到加载完了再一次性渲染在页面上。<style> div{ display: none; }</style><body> <p id=“p”>显示预加载进行中</p> <div id=“div”> <img data-src=“https://materialdb.zhaoyl.com/201809/106796.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/105567.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/103097.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/10205.jpg" class=“load-img” width=“100” height=“100”/><img data-src=“https://materialdb.zhaoyl.com/201809/001.jpg" class=“load-img” width=“100” height=“100”/> </div></body>let oP1=document.getElementById(‘p’);let oDiv=document.getElementById(‘div’);//测试请先清空缓存window.onload = function () { ecDo.loadImg(’load-img’, function () { oDiv.style.display=‘block’; oP1.style.display=‘none’; });}注意事项:1.大概预测,用户看首屏的时候,很大概率会往下面看。2.该方式好处在于加载完毕之后,就所有图片都加载完毕了,体验比较好。如果图片全部过大,加载时间会比较长,loading 的时间也会很长,会影响体验。demo地址:https://github.com/chenhuiYj/…5.懒加载这个大家应该很熟悉了,简单点说就是图片一开始不加载,当用户浏览到什么位置的时候,相应位置得图片就加载出来。<body> <p><img data-src=“lawyerOtherImg.jpg” class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“lawyerOtherImg.jpg” class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“lawyerOtherImg.jpg” class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“https://materialdb.zhaoyl.com/201809/105567.jpg" class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“https://materialdb.zhaoyl.com/201809/106796.jpg" class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“https://materialdb.zhaoyl.com/201809/103097.jpg" class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“https://materialdb.zhaoyl.com/201809/10205.jpg" class=“load-img” width=‘528’ height=‘304’/></p> <p><img data-src=“https://materialdb.zhaoyl.com/201809/001.jpg" class=“load-img” width=‘528’ height=‘304’/></p></body>window.onload = function () { //根据load-img 这个 class 遍历,元素距离页面底部 100像素的时候就开始加载,加载错误就显示error.jpg ecDo.delayFn(ecDo.lazyLoadImg(’load-img’, 100, ’error.jpg’),100,200); window.onscroll = function () { ecDo.delayFn(ecDo.lazyLoadImg(’load-img’, 100, ’error.jpg’),100,200); }}demo:https://github.com/chenhuiYj/…6.图片没加载出来显示默认图片这个例子,当网速比较慢的时候,想要加载的图片没有马上出来。或者图片路径错误,这个时候页面可能会出现一部分空白的地方,或者页面布局会出现错乱,比较常用的做法是先显示一张 loading 图或者是 logo 图。告诉用户,这里是图片,正在加载,体验上会好很多,比如下面这个例子。下面也简单的实现一下。比如网站上有这样的图片<p><img src=“error.jpg” data-src=“https://materialdb.zhaoyl.com/201809/105567.jpg" width=“264”/></p><p><img src=“error.jpg” data-src=“https://materialdb.zhaoyl.com/201809/106796.jpg" width=‘264’/></p><p><img src=“error.jpg” data-src=“https://materialdb.zhaoyl.com/201809/1067961.jpg" width=‘264’/></p>在 network 把网速调至了慢速的3G,以方便调试。//测试前请先清空缓存window.onload = function () { let oImg=document.getElementsByTagName(‘img’); for(let i=0;i<oImg.length;i++){ ecDo.aftLoadImg({ dom:oImg[i], url:oImg[i].dataset.src, errorUrl:oImg[i].src }) }}可以看到,一开始显示的是一张默认图片,等需要加载的图片,加载完了之后,再加载需要加载的图片。(最后一张图片,是故意把路径写错,所以出来的图片是之前的图片)demo:https://github.com/chenhuiYj/…7.小结关于项目上,优化图片的各种方式,自己用过的,听过的,大概就在这里了。实现方案,也不敢说是最好。如果大家有更好的想法,建议,欢迎在评论区留言。————————-华丽的分割线——————–想了解更多,和我交流,内推职位,请添加我微信。或者关注我的微信公众号:乐趣区 ...

November 26, 2018 · 2 min · jiezi

Android如何实现超级棒的沉浸式体验

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由brzhang发表于云+社区专栏做APP开发的过程中,有很多时候,我们需要实现类似于下面这种沉浸式的体验。沉浸式体验一开始接触的时候,似乎大家都会觉这种体验实现起来,会比较困难。难点在于:头部的背景图在推上去的过程中,慢慢的变得不可见了,整个区域的颜色变成的暗黑色,然后标题出现了。StatusBar变的透明,且空间可以被利用起来,看我们的图片就顶到了顶 了。我们的viewpager推到actionbar的下方的时候,就固定在了actionbar的下方,不能在往上面推了。底部有一个控件,随着列表的向上滑动,它退出视角范围,以便于给出更多的空间来展示列表,其实整个沉浸式体验都是为了给列表留出更多的空间来展示。好,总结起来以上就是我们的问题,也是需要解决的,一个一个解决了,这种需求也就实现了,那么,我们如何去一步一步来解决以上的问题呢?1、头部背景和标题的渐隐渐现首先,我们来分析第一个问题,头部的背景图在推上去的过程中,慢慢的变得不可见了,这种听起来好像是某种collapse,因此,很容易让人想到CollapsingToolbarLayout,如果你想要比较容易的了解CollapsingToolbarLayout应用,建议看这位兄台的文章,他给也给了一个动画,比较详细的介绍了这个的应用,例如:CollapsingToolbarLayout对于里面的用法,我这里不作讲解了,但是如果你不了解这个布局的应用,我强烈建议你好好了解一下,才能继续下面走,只是想说明一下,走到这里,你有一个坑需要去填,那就是我们的标题动画可以不是这样的,而且,还是标题还是居中的,注意,这里的实现,标题不是居中的,是靠左的,这本来是Android设计规范,但是设计师偏偏不买Android规范的账,因此,我们必须躺过这个坑,然后,从Stack Overflow上了解到一个issue:<android.support.v7.widget.Toolbar android:id="@+id/toolbar_top" android:layout_height=“wrap_content” android:layout_width=“match_parent” android:minHeight="?attr/actionBarSize" android:background="@color/action_bar_bkgnd" app:theme="@style/ToolBarTheme" > <TextView android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:text=“Toolbar Title” android:layout_gravity=“center” android:id="@+id/toolbar_title" /></android.support.v7.widget.Toolbar>假设,这个方式是可行的,那么要解决居中的问题后,把返回按钮改为我们的按钮样式,然后,在耍点小诡计,让title开始是透明的,并且改变返回按钮的图片:collapsingToolbarLayout.setCollapsedTitleTextColor(Color.WHITE);//collapsingToolbarLayout.setExpandedTitleColor(Color.WHITE);collapsingToolbarLayout.setExpandedTitleColor(Color.TRANSPARENT);然而,假设,始终只是一个假设,实际上,这个假设不成立,我在尝试的时候,发现Toolbar中的TextView根本就不能使用android:layout_gravity=“center"这种属性好吧,即使强行加上,效果也是靠左的。那么,如何做,我的解决方式是这样的<android.support.design.widget.AppBarLayout android:id=”@+id/appbarlayout" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:elevation=“0dp”> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:contentScrim="@color/b_G6" app:expandedTitleMarginEnd=“10dp” app:expandedTitleMarginStart=“10dp” app:layout_scrollFlags=“scroll|exitUntilCollapsed|snap”> <android.support.constraint.ConstraintLayout android:layout_width=“match_parent” android:layout_height=“match_parent”> <ImageView android:id="@+id/igame_arena_rank_class_header_bg" android:layout_width=“match_parent” android:layout_height=“0dp” android:scaleType=“centerCrop” android:src="@drawable/bg_arena_rank_class" app:layout_constraintDimensionRatio=“375:156” /> ……… </android.support.constraint.ConstraintLayout> <android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:minHeight="?android:attr/actionBarSize" android:visibility=“visible” app:contentInsetLeft=“0dp” app:contentInsetStart=“0dp” app:layout_collapseMode=“pin”> <include layout="@layout/igame_common_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:layout_gravity=“center” /> </android.support.v7.widget.Toolbar> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout>然后,include里面的布局是这样的<?xml version=“1.0” encoding=“utf-8”?><LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android" xmlns:tools=“http://schemas.android.com/tools" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:orientation=“vertical”>//请注意这个View**/// <View android:id=”@+id/common_index_activity_view_status_bar” android:layout_width=“match_parent” android:layout_height=“0dp” /> <RelativeLayout android:layout_width=“match_parent” android:layout_height=“50dp”> <TextView android:id="@+id/tv_toolbar_bg" android:layout_width=“match_parent” android:layout_height=“50dp” android:layout_centerInParent=“true” tools:background="@color/b_G6" /> <TextView android:id="@+id/common_index_header_tv_title" android:layout_width=“wrap_content” android:layout_height=“wrap_content” android:layout_centerInParent=“true” android:gravity=“center” android:textColor="@color/b_G99" android:textSize="@dimen/igame_textsize_xl" tools:text=“这里是标题” /> <RelativeLayout android:id="@+id/common_index_header_rl_back" android:layout_width=“48dp” android:layout_height=“48dp” android:layout_centerVertical=“true” android:layout_gravity=“center_vertical” android:visibility=“visible”> <ImageView android:layout_width=“match_parent” android:layout_height=“match_parent” android:layout_centerInParent=“true” android:contentDescription="@string/image_desc" android:scaleType=“centerInside” android:src="@drawable/igame_actionbar_arrow_left" /> </RelativeLayout> </RelativeLayout></LinearLayout>效果就是这样当然,这时候,标题是需要你自己设置渐隐渐现的。那么,我们依据什么呢?appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { mTitle.setAlpha(-verticalOffset * 1.0f / appBarLayout.getTotalScrollRange()); } });依据的就是对appBarLayout的监听。2、将statusBar变为透明,且利用他的空间来放我们的布局内容。 /** * 使状态栏透明,并覆盖状态栏,对API大于19的显示正常,但小于的界面扩充到状态栏,但状态栏不为透明 / @TargetApi(Build.VERSION_CODES.KITKAT) public static void transparentAndCoverStatusBar(Activity activity) { //FLAG_LAYOUT_NO_LIMITS这个千万别用,带虚拟按键的机型会有特别多问题// //FLAG_TRANSLUCENT_STATUS要求API大于19// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);// //FLAG_LAYOUT_NO_LIMITS对API没有要求// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = activity.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(Color.TRANSPARENT); window.setNavigationBarColor(Resources.getSystem().getColor(android.R.color.background_dark)); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Window window = activity.getWindow(); window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); } }这里是在网上找的一个方法,直接调用即可,但是API需要大于19,相信目前基本上都满足吧。请注意,我的AppBarLayout中并没有这个属性android:fitsSystemWindows=“true"如果你加了这个属性,嘿嘿,statusbar虽然空间可以利用,但是有一个你挥之不去的颜色覆盖在上面,然后,你还记得上面那个布局中//请注意这个View*/// <View android:id=”@+id/common_index_activity_view_status_bar" android:layout_width=“match_parent” android:layout_height=“0dp” />这个作用可大了,就是为了对status_bar原始空间做偏移的,在代码中,需要动态的改变这个View的高度为statusBar的高度,怎么获取:/** * 获取状态栏高度 * * @param context context * @return 状态栏高度 */ public static int getStatusBarHeight(Context context) { // 获得状态栏高度 int resourceId = context.getResources().getIdentifier(“status_bar_height”, “dimen”, “android”); return context.getResources().getDimensionPixelSize(resourceId); }完了之后,还需要设置我们自己塞进去的那个toolbar的高度为toolbar的高度加上StatusBar的高度。3、ViewPager推到actionbar下面就不让在推了这个其实需要你CollapsingToolbarLayout里面有一个子view是要使用pin模式的,那么这个子view是谁,显然就是那个toolbar了<android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:minHeight="?android:attr/actionBarSize" android:visibility=“visible” app:contentInsetLeft=“0dp” app:contentInsetStart=“0dp” app:layout_collapseMode=“pin”> <include layout="@layout/igame_common_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:layout_gravity=“center” /> </android.support.v7.widget.Toolbar>4、底部控件随着列表的滑动渐渐隐藏可以看到,底部的控件是覆盖在列表上的,列表向上滑动的时候,把他隐藏,就可以空出更多的控件看列表。那么,如何做呢?既然,我们是包裹在CoordinatorLayout中,那么,显然,最好的方式是使用layout_behavior了,我这里实现了一个BottomBehavior:public class BottomBehavior extends CoordinatorLayout.Behavior { private int id; private float bottomPadding; private int screenWidth; private float designWidth = 375.0f;//设计视图的宽度,通常是375dp, public BottomBehavior() { super(); } public BottomBehavior(Context context, AttributeSet attrs) { super(context, attrs); screenWidth = getScreenWidth(context); TypedArray typedArray = context.getResources().obtainAttributes(attrs, R.styleable.BottomBehavior); id = typedArray.getResourceId(R.styleable.BottomBehavior_anchor_id, -1); bottomPadding = typedArray.getFloat(R.styleable.BottomBehavior_bottom_padding, 0f); typedArray.recycle(); } @Override public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) { params.dodgeInsetEdges = Gravity.BOTTOM; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { return dependency.getId() == id; } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { child.setTranslationY(-(dependency.getTop() - (screenWidth * bottomPadding / designWidth))); Log.e(“BottomBehavior”, “layoutDependsOn() called with: parent = [” + dependency.getTop()); return true; } public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); Display display = null; if (wm != null) { display = wm.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int width = size.x;// int height = size.y; return width; } return 0; }}这个里面有两个自定义属性,id,bottomPadding,id表示基于哪个控件的相对位置改变,我这打算基于viewpager这个控件,看源码可以知道,只有当onDependentViewChanged返回ture时,layoutDependsOn才会被回调。bottomPadding是表示一个初始的偏移,因为viewpager本身不是顶在屏幕顶端的(开始被图片占据了一部分控件),因此,需要扣除这部分占有。同理,加入让你实现一个悬浮在左侧,右侧,滑动隐藏,停止显示的,也都可以参考类似Behavior的方式,减少代码耦合。总结最后整个布局是这样子的<?xml version=“1.0” encoding=“utf-8”?><com.tencent.igame.view.common.widget.IGameRefreshLayout xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto” android:id="@+id/igame_competition_detail_fragment_refresh" android:layout_width=“match_parent” android:layout_height=“match_parent”> <android.support.design.widget.CoordinatorLayout android:layout_width=“match_parent” android:layout_height=“match_parent”> <android.support.design.widget.AppBarLayout android:id="@+id/appbarlayout" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:elevation=“0dp”> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” app:contentScrim="@color/b_G6" app:expandedTitleMarginEnd=“10dp” app:expandedTitleMarginStart=“10dp” app:layout_scrollFlags=“scroll|exitUntilCollapsed|snap”> <android.support.constraint.ConstraintLayout android:layout_width=“match_parent” android:layout_height=“match_parent”> <ImageView android:id="@+id/igame_arena_rank_class_header_bg" android:layout_width=“match_parent” android:layout_height=“0dp” android:scaleType=“centerCrop” android:src="@drawable/bg_arena_rank_class" app:layout_constraintDimensionRatio=“375:156” /> ………… </android.support.constraint.ConstraintLayout> <android.support.v7.widget.Toolbar android:id="@+id/common_index_activity_tb_title" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:minHeight="?android:attr/actionBarSize" android:visibility=“visible” app:contentInsetLeft=“0dp” app:contentInsetStart=“0dp” app:layout_collapseMode=“pin”> <include layout="@layout/igame_common_tool_bar" android:layout_width=“match_parent” android:layout_height=“wrap_content” android:layout_gravity=“center” /> </android.support.v7.widget.Toolbar> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <com.tencent.igame.widget.viewpager.IgameViewPager android:id="@+id/igame_arena_rank_class_vp_content" android:layout_width=“match_parent” android:layout_height=“match_parent” app:layout_behavior="@string/appbar_scrolling_view_behavior" /> <android.support.constraint.ConstraintLayout android:layout_width=“match_parent” android:layout_height=“60dp” android:layout_gravity=“bottom” android:background="@color/b_G6" android:paddingLeft=“12dp” android:paddingRight=“12dp” app:anchor_id="@+id/igame_arena_rank_class_vp_content" app:bottom_padding=“156.0” app:layout_behavior=“com.tencent.igame.common.widget.BottomBehavior”>……….底部布局 </android.support.constraint.ConstraintLayout> </android.support.design.widget.CoordinatorLayout></com.tencent.igame.view.common.widget.IGameRefreshLayout>注:IGameRefreshLayout实际上就是封装的PullToRefreshView,IgameViewPager是我们封装的Viewpager,减少每次写Viewpager的套路代码。按照这个框架来,相信你很容易写出这个样子的布局。相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 8, 2018 · 3 min · jiezi

图解浏览器缓存,教你提高用户体验

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由前端林子发表于云+社区专栏浏览器缓存,是浏览器端保存数据,用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和加快页面速度,从而提高用户体验。一 强缓存1.1 区分Expires和Cache-Control以一个接口返回的响应头为例:这里我画了张思维导图,对Expires和Cache-Control做比较:具体介绍Expires和Cache-Control:Expires:(1)Expires是HTTP1.0的东西,现在默认浏览器均默认使用HTTP 1.1,所以它的作用基本忽略;(2)Expires规定了缓存失效时间(Date为当前时间),是绝对时间。由于Expires返回的是一个绝对时间,在服务器时间与客户端时间相差较大的时候,缓存命中不准确;Cache-Control:(1)Cache-Control是HTTP1.1的(2)Cache-Control的max-age规定了缓存有效时间(2552s),是相对时间;(3)若响应头Expires和Cache-Control同时存在,Cache-Control优先级高于ExpiresCache-Control的常用指令:no-cache:不使用本地缓存,需要使用协商缓存,也就是先与服务器确认缓存是否可用。no-store:禁用缓存。用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。public:其他用户也可使用缓存,适用于公共缓存服务器的情况。private:只有特定用户才能使用缓存,适用于公共缓存服务器的情况。max-age:客户机可以接收生存期不大于指定时间(以秒为单位)的响应。min-fresh客户机可以接收响应时间小于当前时间加上指定时间的响应。max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。注意:no-cache指令并不是不缓存,no-cache的意思是可以缓存,但每次用应该去向服务器验证缓存是否可用。no-store才是不缓存内容。1.2 强缓存的过程强缓存:浏览器直接从本地缓存中获取数据,不与服务器进行交互。· 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response的header会加上Expires/Cache-Control的header;· 浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,比较Expires或Cache-Control的max-age字段值做比较, 如果在有效期内,则读取缓存内容;若缓存已过期,则重新向服务器发送请求;· header在重新加载的时候会被更新这里我画了两张图,浏览器第一次请求:浏览器第一次请求浏览器再次请求:强缓存对于强缓存,chrome浏览器的状态码:200 OK(from disk cache)或是200 OK (from memory cache)例如:请求某个图片后,当浏览器再次访问这个图片时,发现有这个图片的缓存,且缓存没过期,所以就使用缓存。当浏览器发现缓存过期后,缓存并不一定不能使用了。比如文件虽然过了有效期,但内容并没有发生改变,还是可以用缓存数据。所以,这个时候需要与服务器协商,让服务器判断本地缓存是否还能使用。那么又怎么判断服务端文件有没有更新呢?主要有两种方式:Last-Modified,If-Modified-since。二 协商缓存2.1 区分Last-Modified和If-Modified-Since以一个返回的接口为例:Last-Modified的格式:Last-Modified: Mon, 17 Sep 2018 12:06:18 GMTIf-Modified-Since的格式:If-Modified-Since: Mon, 17 Sep 2018 12:06:18 GMT2.2 Etag是什么web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。2.3 协商缓存的过程浏览器第一次请求:浏览器第一次缓存浏览器再一次请求:协商缓存Last-Modified、If-Modified-Since:· 浏览器第一次向服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Last-Modified字段,表示该资源在服务器上的最后修改时间;· 浏览器再次向服务器请求这个资源时,在request的header上加上If-Modified-Since字段,这个值就是上一次请求时返回的Last-Modified的值;·服务器收到资源请求时,比较If-Modified-Since字段值和被请求资源的最后修改时间,若资源最后修改时间较旧,则说明文件没有修改,返回304 Not Modified, 浏览器从缓存中加载资源;若不相同,说明文件被更新,浏览器直接从服务器加载资源, 返回200;·重新加载资源时更新Last-Modified HeaderEtag、If-None-Match· 浏览器第一次向服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上ETag字段;·浏览器再次跟服务器请求这个资源时,在request的header上加上If-None-Match,这个值就是上一次请求时返回的ETag的值;·服务器再次收到资源请求时,再根据资源生成一个新的ETag,与浏览器传过来If-None-Match比较,如果这两个值相同,则说明资源没有变化,返回304 Not Modified, 浏览器从缓存中加载资源,否则返回200 资源内容。与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化2.4 为什么有了Last-Modified,还要用Etag呢?HTTP1.1中ETag的出现主要是为了解决几个Last-Modified比较难解决的问题:·一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;·某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);·某些服务器不能精确的得到文件的最后修改时间。对于上述情景,利用ETag能够更加准确的控制缓存,因为ETag是服务器自动生成的资源在服务器端的唯一标识符,资源每次变动,都会生成新的ETag值。Last-Modified与ETag是可以一起使用的,但服务器会优先验证ETag。2.5 比较强缓存和协商缓存基于上文对强缓存和协商缓存过程的解释,这里我把强缓存和协商缓存绘制在一张图里,方便比较,具体过程可以参照上文:http缓存三 小结本文主要通过图解介绍了http的缓存,具体包括强缓存和协商缓存。如有问题,欢迎指正。相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识

November 1, 2018 · 1 min · jiezi

Go 语言编译器的 //go: 详解

前言C 语言的 #include一上来不太好说明白 Go 语言里 //go: 是什么,我们先来看下非常简单,也是几乎每个写代码的人都知道的东西:C 语言的 #include。我猜,大部分人第一行代码都是 #include 吧。完整的就是#include <stdio.h>。意思很简单,引入一个 stdio.h。谁引入?答案是编译器。那么,# 字符的作用就是给 编译器 一个 指示,让编译器知道接下来要做什么。编译指示在计算机编程中,编译指示(pragma)是一种语言结构,它指示编译器应该如何处理其输入。指示不是编程语言语法的一部分,因编译器而异。这里 Wiki 详细介绍了它,值得你看一下。Go 语言的编译指示官方文档 https://golang.org/cmd/compil…形如 //go: 就是 Go 语言编译指示的实现方式。相信看过 Go SDK 的同学对此并不陌生,经常能在代码函数声明的上一行看到这样的写法。有同学会问了,// 这不是注释吗?确实,它是以注释的形式存在的。编译器源码 这里可以看到全部的指示,但是要注意,//go: 是连续的,// 和 go 之间并没有空格。常用指示详解//go:noinlinenoinline 顾名思义,不要内联。Inline 内联Inline,是在编译期间发生的,将函数调用调用处替换为被调用函数主体的一种编译器优化手段。Wiki:Inline 定义使用 Inline 有一些优势,同样也有一些问题。优势:减少函数调用的开销,提高执行速度。复制后的更大函数体为其他编译优化带来可能性,如 过程间优化消除分支,并改善空间局部性和指令顺序性,同样可以提高性能。问题:代码复制带来的空间增长。如果有大量重复代码,反而会降低缓存命中率,尤其对 CPU 缓存是致命的。所以,在实际使用中,对于是否使用内联,要谨慎考虑,并做好平衡,以使它发挥最大的作用。简单来说,对于短小而且工作较少的函数,使用内联是有效益的。内联的例子func appendStr(word string) string { return “new " + word}执行 GOOS=linux GOARCH=386 go tool compile -S main.go > main.S 我截取有区别的部分展出它编译后的样子: 0x0015 00021 (main.go:4) LEAL “”..autotmp_3+28(SP), AX 0x0019 00025 (main.go:4) PCDATA $2, $0 0x0019 00025 (main.go:4) MOVL AX, (SP) 0x001c 00028 (main.go:4) PCDATA $2, $1 0x001c 00028 (main.go:4) LEAL go.string.“new “(SB), AX 0x0022 00034 (main.go:4) PCDATA $2, $0 0x0022 00034 (main.go:4) MOVL AX, 4(SP) 0x0026 00038 (main.go:4) MOVL $4, 8(SP) 0x002e 00046 (main.go:4) PCDATA $2, $1 0x002e 00046 (main.go:4) LEAL go.string.“hello”(SB), AX 0x0034 00052 (main.go:4) PCDATA $2, $0 0x0034 00052 (main.go:4) MOVL AX, 12(SP) 0x0038 00056 (main.go:4) MOVL $5, 16(SP) 0x0040 00064 (main.go:4) CALL runtime.concatstring2(SB)可以看到,它并没有调用 appendStr 函数,而是直接把这个函数体的功能内联了。那么话说回来,如果你不想被内联,怎么办呢?此时就该使用 go//:noinline 了,像下面这样写://go:noinlinefunc appendStr(word string) string { return “new " + word}编译后是: 0x0015 00021 (main.go:4) LEAL go.string.“hello”(SB), AX 0x001b 00027 (main.go:4) PCDATA $2, $0 0x001b 00027 (main.go:4) MOVL AX, (SP) 0x001e 00030 (main.go:4) MOVL $5, 4(SP) 0x0026 00038 (main.go:4) CALL “".appendStr(SB)此时编译器就不会做内联,而是直接调用 appendStr 函数。//go:nosplitnosplit 的作用是:跳过栈溢出检测。栈溢出是什么?正是因为一个 Goroutine 的起始栈大小是有限制的,且比较小的,才可以做到支持并发很多 Goroutine,并高效调度。stack.go 源码中可以看到,_StackMin 是 2048 字节,也就是 2k,它不是一成不变的,当不够用时,它会动态地增长。那么,必然有一个检测的机制,来保证可以及时地知道栈不够用了,然后再去增长。回到话题,nosplit 就是将这个跳过这个机制。优劣显然地,不执行栈溢出检查,可以提高性能,但同时也有可能发生 stack overflow 而导致编译失败。//go:noescapenoescape 的作用是:禁止逃逸,而且它必须指示一个只有声明没有主体的函数。逃逸是什么?Go 相比 C、C++ 是内存更为安全的语言,主要一个点就体现在它可以自动地将超出自身生命周期的变量,从函数栈转移到堆中,逃逸就是指这种行为。请参考我之前的文章,逃逸分析。优劣最显而易见的好处是,GC 压力变小了。因为它已经告诉编译器,下面的函数无论如何都不会逃逸,那么当函数返回时,其中的资源也会一并都被销毁。不过,这么做代表会绕过编译器的逃逸检查,一旦进入运行时,就有可能导致严重的错误及后果。//go:noracenorace 的作用是:跳过竞态检测我们知道,在多线程程序中,难免会出现数据竞争,正常情况下,当编译器检测到有数据竞争,就会给出提示。如:var sum intfunc main() { go add() go add()}func add() { sum++}执行 go run -race main.go 利用 -race 来使编译器报告数据竞争问题。你会看到:==================WARNING: DATA RACERead at 0x00000112f470 by goroutine 6: main.add() /Users/sxs/Documents/go/src/test/main.go:15 +0x3aPrevious write at 0x00000112f470 by goroutine 5: main.add() /Users/sxs/Documents/go/src/test/main.go:15 +0x56Goroutine 6 (running) created at: main.main() /Users/sxs/Documents/go/src/test/main.go:11 +0x5aGoroutine 5 (finished) created at: main.main() /Users/sxs/Documents/go/src/test/main.go:10 +0x42==================Found 1 data race(s)说明两个 goroutine 执行的 add() 在竞争。优劣使用 norace 除了减少编译时间,我想不到有其他的优点了。但缺点却很明显,那就是数据竞争会导致程序的不确定性。总结 我认为绝大多数情况下,无需在编程时使用 //go: Go 语言的编译器指示,除非你确认你的程序的性能瓶颈在编译器上,否则你都应该先去关心其他更可能出现瓶颈的事情。 ...

October 21, 2018 · 2 min · jiezi

吃鸡决赛圈直播却卡屏的我心好痛,立马找来开发刚了一波

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦本文由腾讯云视频发表于云+社区专栏关注公众号“腾讯云视频”,一键获取 技术干货 | 优惠活动 | 视频方案本是一名佛性型吃鸡选手,自从被三个妹子带着躺尸吃鸡之后,便立志要成为一名吃鸡高手,一大早便沉迷于各大网站的吃鸡直播中,正看到决赛圈激动人心的时刻,直播花屏了?然后游戏结束了?我的天,我是谁?我在哪?我错过了什么?作为一名有强迫症的IT小哥哥,怎能让直播花屏现象存在呢?一方面,为了自己能成为一名吃鸡高手。另一方面,不能错过每一个升职加薪的机会。就这样开始了一段漫长的长征之路……对于直播业务,“秒开、卡顿、时延、进房成功率"是我们经常关注的几个指标,这些指标可以说是从"一个用户能够优雅地进入直播间"的角度来考量的,然而进入直播间后"用户究竟看到的什么内容"也是很关键的一环,内容上除了涉及安全的一些指标外,还可能会有内容是否有花屏、绿屏等其他异常内容,这时我们便会考虑如何衡量和发现外网的花屏情况。本文主要针对花屏提出了一种基于CNN网络的检测方案。01花屏检测能力构建 无论是视频还是直播,都是由一帧帧图像组成的,之所以会以一种动态的形式展现到我们眼前,是因为了人类的视觉暂留现象。物体在快速运动时, 当人眼所看到的影像消失后,人眼仍能继续保留其影像0.1-0.4秒左右的图像,这种现象被称为视觉暂留现象。既然如此,检测直播中是否存在花屏,其实可以转换为检测直播中的帧画面是否是花屏的画面,即一个图像识别问题。那么如何识别一个图像是否是花屏呢?通常图像识别总是以特征为基础的,我们会先根据所设定的目标来提取相应的特征,用于我们后面来制定策略。不过好在现在的深度学习卷积神经网络CNN将提取特征和制定决策策略都帮我们完成了。而使用深度学习CNN网络则绕不开数据集和模型训练两大块1.1数据集准备困难要使用深度学习网络,一个门槛是需要足够的带有标签的数据集,否则学习出的网络很容易过拟合,从而泛化能力不强。说其是门槛是因为实际业务中更多情况是缺少数据集,就以现在的花屏为例,目前直播发生花屏的案例非常少,想要通过实际案例来收集足够的花屏图片作为训练集显得异常困难。因此必须探寻其他的路子来收集训练集。人类之所以能够分辨出花屏,是因为人类眼睛能够找到花屏图像的特征,虽然这些特征我们可能用语言都描述不出来,事实上,如果我们能用语言描述出特征,我们也很容易将其翻译成代码来找到花屏图像的特征。制作训练集机器学习其实也是通过特征来工作的,既然如此,我们可以制作一些花屏图像出来,让CNN网络找到它们区别于正常图片的特征,从而学习到花屏图片的检测能力。在使用YUVviewer工具时,发现当设置错误的分辨率来播放视频文件时会出现花屏情况。灵感来源于此,我们完全可以通过使用错误的分辨率从YUV文件中抽取帧,从而拿到花屏图片。整体流程如下:这里需要了解YUV文件的存储格式,从而根据格式来进行抽取对应的帧:YUV,分为三个分量,“Y”表示明亮度(Luminance或Luma),也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。YUV采样样式有一下几种,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量,一般情况下我们都使用的是YUV420格式 YUV420格式存储方式如下所示:按照上面存储方式编写代码来提取帧,代码如下:def get_frames_from_YUV(filename, dims, numfrm, startfrm, frmstep): "”" 从给定的YUV文件中抽取对应的帧数据,帧数据格式仍然为YUV :param filename: YUV文件路径 :param dims: YUV文件的分辨率 :param numfrm: 要提取帧的数量 :param startfrm: 从哪一帧开始提取 :param frmstep: 抽取帧的帧间隔,即每隔几帧抽一帧 :return: 返回抽取帧的Y列表,U列表,V列表 """ filesize = os.path.getsize(filename) fp = open(filename, ‘rb’) blk_size = prod(dims) * 3 / 2 # 计算每帧大小 if (startfrm+1+(numfrm-1)*frmstep)*blk_size > filesize: numfrm = (filesize/blk_size - 1 - startfrm)/frmstep +1 util.log(‘文件读取越界–修改为%d’%numfrm) fp.seek(blk_size * startfrm, 0) # 跳转到指定开始帧 Y, U, V= [],[],[] d00 = dims[0] / 2 d01 = dims[1] / 2 for i in range(numfrm): util.log(‘文件读取第%d帧’ % i) Yt = zeros((dims[1], dims[0]), uint8, ‘C’) Ut = zeros((d01, d00), uint8, ‘C’) Vt = zeros((d01, d00), uint8, ‘C’) for m in range(dims[1]): for n in range(dims[0]): # print m,n Yt[m, n] = ord(fp.read(1)) for m in range(d01): for n in range(d00): Ut[m, n] = ord(fp.read(1)) for m in range(d01): for n in range(d00): Vt[m, n] = ord(fp.read(1)) Y = Y + [Yt] U = U + [Ut] V = V + [Vt] fp.seek(blk_size * (frmstep - 1), 1) # 跳出间隔帧 fp.close() return (Y, U, V)这里对分辨率错误的多种情况也做了下研究,发现如下规律:1)分辨率正确2)分辨率width+1情况3)分辨率width+n情况4)分辨率width-1情况5)分辨率width-n情况上面只是针对图片宽来进行错误干扰,可以看出宽变小花屏条纹方向是左下的,宽变大花屏条纹方向时右下的。所以我们队宽和高分别设置不同的错误值,会造成不同类型的花屏,于是可以用这种策略构造大量的花屏。我们用800多个视频,每个视频以一定的间隔来抽10帧,获得了8000多张花屏图片。这些图片标签为花屏,也就是我们的正样本,负样本可选取实际直播中的正常截图。至此,数据集准备差不多了。1.2 模型和训练模型上使用网上一些知名模型即可,这里我们使用了轻量的mobilenet模型,模型结构如下图所示:训练采用基于用imagenet已经训练好的模型来进行finetune训练,最后一层使用随机超参数来训练(这一层也无法读取pretrained模型的超参数,因为分类数不一致)。训练可以快速收敛,因为特征太明显了,而且测试集准确率很高。其实这里训练是一个不断迭代的过程,因为机器学习模型是一张白纸,它要具有怎样的能力完全是你教它的,而教的方式就是通过训练集(数据和标签),而想要让它能够应对更多的情况,你的训练集就要尽可能涵盖各种情况。而我们的训练集总是不足的,你总会有care不到的地方。训练集不足的情况会怎样?举个例子你训练个识别飞机的模型,而大部分关于飞机的图片都有天空,这样你给张天空的图片到模型,它也可能会认为是飞机,因为其实模型很可能学到的是天空的特征。而怎么让模型学到飞机的特征呢,当然需要调整训练集,让训练集里不仅包含背景为天空的飞机,还有陆地上的飞机等等。通过不断低迭代调优,我们在空间场景下检测准确率已经可以达到94%,NOW直播场景检测准确率也已达到90%。02直播检测方案 检测能力具备后想要真正地接入直播业务,还需要将直播流进行分帧,然后把对应的帧图片进行花屏检测。后台整体框架采用分帧截屏的手段,如果每天需要检测2000万张截图,即10s会有一张截图需要检测,而考虑花屏总是出现在很长的一段时间内,因此这里希望能够以更长的时间来抽样从而避免浪费算力。附一张目前业务检测花屏结果的截图:作为一名热爱工作的IT小哥哥,花了一个星期的时间,总算把基于CNN网络的直播花屏检测的工作告一段落了。工作使我开心,游戏使我快乐,终于可以再次流畅的游走在各大网址的吃鸡直播中啦问答游戏体系结构相关阅读团战开黑必备“良药”了解一下!再也不用担心网吧开黑队友听不清了!3行代码,为QQ轻游戏加上语音互动能力 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 12, 2018 · 1 min · jiezi

猫头鹰的深夜翻译:如何优化MYSQL查询

在所有用于where,order by和group by的列上添加索引索引除了能够确保唯一的标记一条记录,还能是MySQL服务器更快的从数据库中获取结果。索引在排序中的作用也非常大。Mysql的索引可能会占据额外的空间,并且会一定程度上降低插入,删除和更新的性能。但是,如果你的表格有超过10行数据,那么索引就能极大的降低查找的执行时间。强烈建议使用“最坏情况的数据样本”来测试MySql查询,从而更清晰的了解查询在生产中的行为方式。假设你正在一个超过500行的数据库表中执行如下的查询语句:mysql>select customer_id, customer_name from customers where customer_id=‘345546’上述查询会迫使Mysql服务器执行一个全表扫描来获得所查找的数据。型号,Mysql提供了一个特别的Explain语句,用来分析你的查询语句的性能。当你将查询语句添加到该关键词后面时,MySql会显示优化器对该语句的所有信息。如果我们用explain语句分析一下上面的查询,会得到如下的分析结果:mysql> explain select customer_id, customer_name from customers where customer_id=‘140385’;+—-+————-+———–+————+——+—————+——+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+———–+————+——+—————+——+———+——+——+———-+————-+| 1 | SIMPLE | customers | NULL | ALL | NULL | NULL | NULL | NULL | 500 | 10.00 | Using where |+—-+————-+———–+————+——+—————+——+———+——+——+———-+————-+可以看到,优化器展示出了非常重要的信息,这些信息可以帮助我们微调数据库表。首先,MySql会执行一个全表扫描,因为key列为Null。其次,MySql服务器已经明确表示它将要扫描500行的数据来完成这次查询。为了优化上述查询,我们只需要在customer_id这一列上添加一个索引m即可:mysql> Create index customer_id ON customers (customer_Id);Query OK, 0 rows affected (0.02 sec)Records: 0 Duplicates: 0 Warnings: 0如果我们再次执行explain语句,会得到如下结果:mysql> Explain select customer_id, customer_name from customers where customer_id=‘140385’;+—-+————-+———–+————+——+—————+————-+———+——-+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+———–+————+——+—————+————-+———+——-+——+———-+——-+| 1 | SIMPLE | customers | NULL | ref | customer_id | customer_id | 13 | const | 1 | 100.00 | NULL |+—-+————-+———–+————+——+—————+————-+———+——-+——+———-+——-+从上述的输出结果,显然MySQL服务器会使用索引customer_id来查询表格。可以看需要扫描的行数为1。虽然我只是在一个行数为500的表格中执行这条查询语句,索引在检索一个更大的数据集的时候优化程度更加明显。2. 用Union优化Like语句有时候,你可能需要在查询中使用or操作符进行比较。当or关键字在where子句中使用频率过高的时候,它可能会使MySQL优化器错误的选择全表扫描来检索记录。union子句可以是查询执行的更快,尤其是当其中一个查询有一个优化索引,而另一个查询也有一个优化索引的时候。比如,在first_name和last_name上分别存在索引的情况下,执行如下查询语句:mysql> select * from students where first_name like ‘Ade%’ or last_name like ‘Ade%‘上述查询和下面使用union合并两条充分利用查询语句的查询相比,速度慢了许多。mysql> select * from students where first_name like ‘Ade%’ union all select * from students where last_name like ‘Ade%’ 3. 避免使用带有前导通配符的表达式当查询中存在前导通配符时,Mysql无法使用索引。以上面的student表为例,如下的查询会导致MySQL执行全表扫描,及时first_name字段上加了索引。mysql> select * from students where first_name like ‘%Ade’使用explain分析得到如下结果:mysql> explain select * from students where first_name like ‘%Ade’ ;+—-+————-+———-+————+——+—————+——+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+———-+————+——+—————+——+———+——+——+———-+————-+| 1 | SIMPLE | students | NULL | ALL | NULL | NULL | NULL | NULL | 500 | 11.11 | Using where |+—-+————-+———-+————+——+—————+——+———+——+——+———-+————-+如上所示,Mysql将扫描全部500行数据,这将使得查询极其缓慢。4. 充分利用MySQL的全文检索如果你正面临着使用通配符查询数据,但是并不想降低数据库的性能,你应当考虑使用MySQL的全文检索(FTS),因为它比通配符查询快得多。除此以外,FTS还能够返回质量更好的相关结果。添加一个全文检索索引到student样表上的语句如下:mysql> alter table students add fulltext(first_name, last_name)’;mysql> select * from students where match(first_name, last_name) against (‘Ade’);在上面的例子中,我们针对搜索关键字Ade指定了想要匹配的列(first_name, last_name)。如果查询优化器如上语句的执行情况,将得到下面的结果:mysql> explain Select * from students where match(first_name, last_name) AGAINST (‘Ade’);+—-+————-+———-+————+———-+—————+————+———+——-+——+———-+——————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+———-+————+———-+—————+————+———+——-+——+———-+——————————-+| 1 | SIMPLE | students | NULL | fulltext | first_name | first_name | 0 | const | 1 | 100.00 | Using where; Ft_hints: sorted |+—-+————-+———-+————+———-+—————+————+———+——-+——+———-+——————————-+5. 优化数据库架构规范化首先,规范化所有数据库表,即使可能会有些损失。比如,如果你需要创建两张表分别用来记录customers和orders数据,你应当在order表上用顾客id引用顾客,而不是反过来。下图显示了没有任何数据冗余而设计的数据库架构。除此以外,对相似的值使用同一种数据类型类存储。使用最佳数据类型MySQL支持各种数据类型,包括integer,float,double,date,datetime,varchar,text等。当设计数据库表时,应当尽可能使用能够满足特性的最短的数据类型。比如,如果你在设计一个系统用户表,而该用户数量不会超过100个人,你就应该对user_ud使用’TINYINT’类型,该类型的取值范围为-128至128。如果一个字段需要存储date型值,使用datetime类型比较好,因为在查询的时候无需进行复杂的类型转换。当值全为数字类型时,使用Integer。在进行计算时,Integer类型的值比文本类型的值速度更快。避免NULLNULL指该列没有任何值。你应当尽可能的避免这类型的值因为他们会损害数据库结果。比如你需要获得数据库中所有订单金额的和,但是某个订单记录中金额为null,如果不注意空指针,很有可能导致计算结果出现异常。在某些情况下,你可能需要为列定义一个默认值。

October 5, 2018 · 2 min · jiezi

完成需求很重要,优化好更重要

总喜欢在前端啰嗦一些…如题,在日常紧急开发中,完成需求固然排在前面(毕竟产品会追着你打????),但是需求完成之后还是很有必要去优化我们的项目的!花一两个月去做了一个项目,需求评审、原型评审、开发排期、测试用例评审、设计图评审(感觉不断的在开会)、开发、联调、提测、上线…大功告成,告辞!当然这期间的流程每个公司都会不同,可能还会有需求变更什么的…我们今天只谈上线之后…上线之后,各种bug如期而至,正如你所料,需求所涉及的功能都做了,为什么还遭到用户投诉,这里不好那里又有问题?对于用户而言,你的首页加载速度,很考验他(她)的耐心,so,首页加载优化得做最简单易见的就是图片了,关于图片的优化:图片优化(一)——大图片的优化压缩上传,已附链接,还蛮好用的,去各大网站看一下,首页总共的资源x宝x东可以看到,电商类的首页才只有不到2M的图片,还有你会发现,图片懒加载和预加载也起了至关重要的作用,预加载就是在网页全部加载之前提前加载图片。当用户需要查看时可直接从本地缓存中渲染以提供给用户更好的体验减少等待的时间,懒加载就是滑到可视区再加载(或满足条件再加载)图片优化(二)——小图片的优化(icon)精灵图 (请css喝雪碧),老生常谈了,就是将很多的小图标集合到一个图片中去,目的是减少资源请求(网页的缓存机制是会略去本地已经有的资源),这个让设计师把一些简单的小图标集成到一张图给到前端就好了,前端设置 background-image: url(’../../img/jlt.png’); background-size: 4.34rem;//根据大图算出来的 background-position: 0.2rem 0.3rem;//位置拿photoshop量或者手动试也可以(????)还有一些小icon,要会变色(不是根据眼睛变化那个..),根据网站主题色去变(或者就是迭代需求只是变色,图标不变),那再让设计师把带颜色的加到精灵图中,相信你俩都不爽,这时候可以考虑用svgsvg明显能满足变色需求,改fill属性就好了,不管你是主题色还是需求变更,而且svg是矢量级的图标,占位也很小,你要是变换3个颜色,精灵图里面就得给你三个不同颜色图标,所以从空间上也有优势@font-face还有一个比较棒的网站,阿里矢量图标库,里面存放了很多小的icon,格式支持也很多,jpg、png、svg等等让设计师建个项目,把对应的小图标上传到这里,然后会生成一个unicode,就是font-face的线上地址,复制到项目里就好用法和本地下载好字体库是一样的,@font-face { font-family: ‘icon’; src: url(‘https://。。。.eot’);}.icon{ font-family:“iconfont” !important; font-style:normal;}具体页面使用加上icon这个类名,改颜色,就是直接改color,也是灰常的好用静态资源的抽取项目中依赖的一些json、css、js等都抽取到cdn浏览器是根据域(Domain)来缓存内容资源的,只要域不一样,即使是同一个资源,也需要重复下载,且使用同样的方式缓存起来,这需要需要占用带宽和本地缓存空间,cdn详解路由懒加载(vue+webpack)前提是vue+webpack都知道,如果不做,在首页会加载所有的页面资源,于是出了个解决方案:到哪个页面再去加载对应的资源写法一:const abc = r => require.ensure([], () => r(require(’../pages/acb/abc.vue’)), ‘abc’);写法二:{ path: ‘abc’, component: () => import(’@/pages/abc/abc.vue’), meta: { title: ‘abc’ } },把网速调慢,会返现,第一种写法会有很长的白屏时间(页面title变了,页面未变),而且第二种写法也更简洁公有组件抽取开发中,可能有点小摩擦,‘你的组件比较偏向于你,我要自己从新写一个’组件的设计一定要满足大众口味,这样才能节省一些空间出来比如load、弹框等等,主题结构复用,style在个人页面调整还有一些大而广的东西:减少请求(只能说尽量,业务需要该少的少不了)减少DOM操作(现在都是虚拟DOM,还在讲DOM操作…)避免使用CSS表达式(很少有人写吧,尽量用rem替换calc吧)函数防抖和函数节流(小红书函数部分明确有些,load也可以替代功能)首屏服务端渲染(还不错,接口直接返回page)…总结:只有真正的在完成需求后,才会遇到种种问题,才能想到去优化哪些东西,所以实践真的是检验真理的唯一标准有时间新建个demo实际操作一波还是很不错的选择,毕竟听别人叨叨似懂非懂的,换成自己的东西还能吹上一番

September 30, 2018 · 1 min · jiezi