Linux网络命令必知必会之瑞士军刀-ncnetcat

本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。nc,全名叫 netcat,它可以用来完成很多的网络功能,譬如端口扫描、建立TCP/UDP连接,数据传输、网络调试等等,因此,它也常被称为网络工具的 瑞士军刀 。 使用方式我们可以这样来使用它: nc [-46DdhklnrStUuvzC] [-i interval] [-p source_port] [-s source_ip_address] [-T ToS] [-w timeout] [-X proxy_protocol] [-x proxy_address[:port]] [hostname] [port[s]]常用选项: -4:只使用 IPv4 地址-6:只使用 IPv6 地址-l:启动本地监听-n:不使用 DNS 解析-p:指定源端口-s:指定源 IP 地址-u:使用 UDP,默认是 TCP-v:显示详细信息-w:设定超时时间(只适合用在 Client 端)-d:禁止从标准输入读取数据,也就是客户端输入数据不会发送到服务端-k:让服务端保持连接,不断开Linux 系统默认没有安装 nc,可以用下面的方法安装: # centosyum install nc# ubuntuapt-get install netcat建立 C/S 聊天室nc 的本质是在两台机器之间建立连接,之后就可以基于这个连接做很多事情,数据传输是其中一个最为基本的。我们下面就使用 nc 来建立一个 C/S 的聊天室。 模拟 Server 端: # -v :输出详细信息# -l :指定监听地址和端口nc -v -l 127.0.0.1 6000模拟 Client 端: ...

May 21, 2019 · 2 min · jiezi

4种极大提升学习效率的工具

    本文首发于cartoon的博客    转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/assorted/4%E7%A7%8D%E6%9E%81%E5%A4%A7%E6%8F%90%E5%8D%87%E5%AD%A6%E4%B9%A0%E6%95%88%E7%8E%87%E7%9A%84%E5%B7%A5%E5%85%B7/     近段时间发现身边很多人都在用TUDO List,但是我觉得TUDO List的效率还是不够高,所以就写一下提升学习效率的工具。 TUDO List    因为这阵子真的很多东西做,持续的时间比较长。而且会有时候(大概率)忘掉做某几件事,所以学习效率偏低,直到遇到了TUDO List。     TUDO List,可以理解为一个任务清单,它包含了你一天所有要做的事情,而且过一段时间你会发现自己已经依赖上TUDO List管理你的日程。     市面上TUDO List的APP很多,有小黄条,TickTick,Mircosoft To-Do,Google Keep(好像是叫这个名字),还有很多很多,但是我还是喜欢TickTick。     下面是我TickTick的收集箱。 <center>(嗯,我知道我还有很多要做)</center>     我觉得TickTick比其他TUDO List好的主要有三点 几乎全平台的支持 安卓端是离线app加上联网更新形式,断网都可以继续用桌面端通过chrome插件提供服务苹果家的设备就不清楚了(穷)在界面舒适度与易用性取得平衡 小黄条的界面,我就是看了一眼简介的截图就没有下载的欲望Mircosoft家的,我不知道界面怎么样,但是我体验过小娜,我觉得不会好用Google家的,好的,根本不能正常用自带番茄时钟 我觉得现代人都摆脱不了手机(包括我),想认真看书的时候总是想看手机。所以番茄时钟能有效监督学习自带统计视图,能随时看到当周完成的情况    有利也有弊,弊是有些功能需要付费使用,一个月10来块。始终还是要恰饭的嘛,能理解能理解。付费功能包括TUDO Task的统计,番茄时钟的统计建议。     我对免费的功能已经满足了,所以我不是付费用户,付费解锁功能戳不到我的需求点。 思维导图    不知道有多少读者是在做纸质笔记的,我的话已经很久没有做纸质笔记了。那么平常我在看书的时候,是怎么做笔记的呢。     思维导图,我第一次接触已经爱上它了。     下面是我学习帅张的git相关书籍做的思维导图,虽然过了三个月了,但是我还是可以根据思维导图讲出原书的大概内容。     因为我是比较怕写字的(包括打字),所以我现在只会用思维导图做笔记了。     市面上思维导图的软件很多,有XMind(开源,全平台支持),百度脑图(只有web端),MindMaster(苹果专属),MindLine(国人开发,支持云同步)等等。     我说说我在用的XMind吧,优点有很多 模板简洁而且漂亮。即使免费版也内置很多模板,ZEN版本跟Pro版本会更多操作简单。桌面端新建子分支或者兄弟分支会使用到快捷键(我比较少用,但是上手了非常方便),移动端(我常用)主要操作底下四颗按钮完成日常功能支持导出多格式。免费版只支持导出图片以及pdf,PRO版支持更多,但是我觉得这方面日常使用免费版已经足够了。    下面到缺点的部分了。     其实在我看来缺点只有一个,就是不支持云同步,确实有点反人类。     我记得上次我双清平板忘记备份思维导图了(因为平板上独占数据只有思维导图),结果除了之前上传到OneDrive的几个之外,全都没了。。。大概10个上面图片的规模的思维导图吧。。。。     我断断续续找了一个月,找到一个MindLine符合我使用场景之余是支持云同步的,但是界面说实话还有待改进,所以我只能继续用XMind。 Markdown    如果不习惯用思维导图但是又想摆脱纸张的限制,我也可以提供选择。     可能有人已经注意到,这篇文章的风格有点怪怪的。确实,是有点朴素(打死都不承认是怪怪的),因为我是用markdown去写的,具体原因看第二条推文。     效果不截图了,整篇文章都是效果,或者可以去我的博客逛一下,有时间我也会写搭建博客的教程放在博客上。     首先先说一下,markdown是一种标记语言,不是什么软件。具体介绍可以点击查看     在我看来,markdown浑身都是优点 操作简单。只需要掌握特定语法就可以写出比word整洁很多的文章,而且可以完全摆脱鼠标(纯文字文章的话)花样很多。markdown原生支持html语言,就是如果你懂html,可以像写网页那样写文章可选编辑器很多。微软家的VSCode,GitHub家的Sublime(实际上也是微软家的),Typora(我在用的,支持实时渲染),有道云笔记,印象笔记等等将主要精力放在思考上。整体风格更加符合思考的习惯,而且摆脱了鼠标的使用,可以将主要精力放在思考上。缺点嘛,也有两个,但是都是面对新手的。 上手复杂。因为需要特定语法,所以上手需要一个阵痛期,但是practice makes prefect。习惯了之后效率会飞涨。我觉得这个教程不错插入图片比较麻烦,markdown支持三种图片插入方式 (不推荐)插入相对目录下的图片,但是需要确保路径的正确(还行,但是不推荐)插入转码后的图片。这个比较麻烦,首先需要将图片转换成Base64字符串,再将字符串复制到markdown底部(转码后的字符串会很长),再利用语法引用。(推荐)引用图床图片。图床这个不解释了,可以将它理解为一个网络的图片仓库。图床主要有三种 免费的公有图床。百度一搜会很多。不推荐,容易出现图片丢失免费的私人图床。我所知道的只有微博图床,但是操作上有点麻烦,还行,但是不推荐。收费的私人图床。简单来说就是自己用OSS搭一个图床,本地上传到云端OSS。七牛云(一定容量免费),阿里云(我在用),腾讯云。七牛云就算了,需要拍身份证。个人推荐阿里云(9块/年)。云盘    可能很多人bb我:云盘谁没有,某度云盘几个T。     是的,某度云盘确实占领了天朝大部分的市场。但是我想说的是,免费的是最贵的。想想某度的广告,想想几十K的下载速度,我不想吐槽了。     云盘为什么能提升效率呢。     我个人需求是 平板是Wifi版的,需要经常带出去,出去的地方大多没有网络或者网络很差平常数据种类多而且量级比较大,而且需要多设备同步需要在线或缓存中阅读pdf    我日常使用情况 电脑下载需要看的电子书,直接放在OneDrive文件夹上,Win10自动上传平板脱机缓存电子书,网络环境不允许时可以随时使用电子书在平板上做好XMind之后,上传到OneDrive,需要再继续时下载    所以我OneDrive是这样子的。     通过OneDrive,我的数据能随时同步而且不丢失(再次为我丢失的思维导图伤心。。。)     市面上有很多很多云盘。有我们能用到的,也有我们用不到的:OneDrive,某度云盘,亚马逊,iCloud,Google Drive,坚果云等等。      苹果党,iCloud;     Win10党,OneDrive(虽然免费空间只有5G,但是好用,我愿意付费);     有特殊手段的安卓党,Google Drive;     数据量大且不常使用,某度还是可以选择一下的;     以上几款软件都是开源或者免费+付费形式的。免费的部分基本上够用了,付费也在承受范围之内(真希望XMind出一个云同步的功能,我绝对付费)。 题外话:     这段时间在混帅张的星球,受到了很多启发。关于软件跟知识是否付费的争论,我是站在付费的那一边。     曾经在星球上面看到一个观点:免费是最贵的。软件都是程序猿一块块砖搬出来的,也是有成本的。免费使用,意味着在某些方面软件开发方是赚钱的,广告,流量,会员等等。 ...

May 18, 2019 · 1 min · jiezi

工具系列简单水印watermarkdom和算法水印频域方式图片合并实现

一、简单水印(watermark-dom)阿里巴巴内网的不可见水印用的是什么算法?据说月饼事件截图的那位员工也被开除了? 下面的只是简单的加一个很浅的水印,实现起来很容易。 1、看看水印的效果随便找一个网站,比如就找掘金的个人首页, (1)F12检查模式, (2)在console里粘贴下面的代码, (function(watermark){window.watermarkdivs=[];var loadMark=function(settings){var defaultSettings={watermark_txt:"text",watermark_x:20,watermark_y:20,watermark_rows:0,watermark_cols:0,watermark_x_space:50,watermark_y_space:50,watermark_color:'#000000',watermark_alpha:0.005,watermark_fontsize:'18px',watermark_font:'微软雅黑',watermark_width:150,watermark_height:100,watermark_angle:15,watermark_bg_alpha:0.5};if(arguments.length===1&&typeof arguments[0]==="object"){var src=arguments[0]||{};for(key in src){if(src[key]&&defaultSettings[key]&&src[key]===defaultSettings[key])continue;else if(src[key])defaultSettings[key]=src[key]}}var oTemp=document.createDocumentFragment();if(window.watermarkdivs&&window.watermarkdivs.length>0){document.body.removeChild(document.getElementById("otdivid"));window.watermarkdivs=[]}var page_width=Math.max(document.body.scrollWidth,document.body.clientWidth);var page_height=Math.max(document.body.scrollHeight,document.body.clientHeight);var otdiv=document.getElementById("otdivid");if(defaultSettings.watermark_cols==0||(parseInt(defaultSettings.watermark_x+defaultSettings.watermark_width*defaultSettings.watermark_cols+defaultSettings.watermark_x_space*(defaultSettings.watermark_cols-1))>page_width)){defaultSettings.watermark_cols=parseInt((page_width-defaultSettings.watermark_x+defaultSettings.watermark_x_space)/(defaultSettings.watermark_width+defaultSettings.watermark_x_space));defaultSettings.watermark_x_space=parseInt((page_width-defaultSettings.watermark_x-defaultSettings.watermark_width*defaultSettings.watermark_cols)/(defaultSettings.watermark_cols-1))}if(defaultSettings.watermark_rows==0||(parseInt(defaultSettings.watermark_y+defaultSettings.watermark_height*defaultSettings.watermark_rows+defaultSettings.watermark_y_space*(defaultSettings.watermark_rows-1))>page_height)){defaultSettings.watermark_rows=parseInt((defaultSettings.watermark_y_space+page_height-defaultSettings.watermark_y)/(defaultSettings.watermark_height+defaultSettings.watermark_y_space));defaultSettings.watermark_y_space=parseInt(((page_height-defaultSettings.watermark_y)-defaultSettings.watermark_height*defaultSettings.watermark_rows)/(defaultSettings.watermark_rows-1))}var x;var y;for(var i=0;i<defaultSettings.watermark_rows;i++){y=defaultSettings.watermark_y+(defaultSettings.watermark_y_space+defaultSettings.watermark_height)*i;for(var j=0;j<defaultSettings.watermark_cols;j++){x=defaultSettings.watermark_x+(defaultSettings.watermark_width+defaultSettings.watermark_x_space)*j;var mask_div=document.createElement('div');mask_div.id='mask_div'+i+j;mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));mask_div.style.webkitTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.MozTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.msTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.OTransform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.transform="rotate(-"+defaultSettings.watermark_angle+"deg)";mask_div.style.visibility="";mask_div.style.position="absolute";mask_div.style.left=x+'px';mask_div.style.top=y+'px';mask_div.style.overflow="hidden";mask_div.style.zIndex="9";mask_div.style.pointerEvents="none";mask_div.style.opacity=defaultSettings.watermark_alpha;mask_div.style.fontSize=defaultSettings.watermark_fontsize;mask_div.style.fontFamily=defaultSettings.watermark_font;mask_div.style.color=defaultSettings.watermark_color;mask_div.style.textAlign="center";mask_div.style.width=defaultSettings.watermark_width+'px';mask_div.style.height=defaultSettings.watermark_height+'px';mask_div.style.display="block";mask_div.style.fontWeight="900";oTemp.appendChild(mask_div)}};document.body.appendChild(oTemp)};watermark.load=function(settings){window.onload=function(){loadMark(settings)};window.onresize=function(){loadMark(settings)}};watermark.load({watermark_txt:"测试水印,saucxs,测试水印,songEagle,工号等"})})(window.watermark={});(3)改变一下页面窗口大小,然后就可以看到我首页出现如下图的水印,一层浅浅的水印 它的作用是在当前页面上增加了一个透明度只有0.005的很多的水印。水印内容“测试水印,saucxs,测试水印,songEagle,工号等”。 2、水印的优化当然是需要使用者不知道这个页面有水印,保证一些信息的安全性以及泄露之后可以追踪到是谁在泄露机密信息,他没有发觉到有水印,所以需要将水印调成透明度很低,这样使用者看不到水印,但是一旦使用者截图将图片发布到互联网上,这时候只需要将图片进行一些简单操作就可以让水印重新显现出来。 只要使用者不知道有水印存在,这样就是从根本上加了水印,信息的源头上加了水印,确保信息的安全,以及泄露之后的追踪。 我们还是拿掘金的个人首页作为试验田。这回我们将水印的透明度调成0.004,水印字体颜色调成页面背景颜色(掘金的是#f4f5f5),然后截图(这回看不出来有水印吧) 把图片放到PS,我使用的是ps cs6里面,建一个空白图层在上面,填充为黑色,操作:如图所示背景色选择黑色,然后按shift+f5,选择背景色进行填充。 混合模式选择正片叠底这一类的(也就是让亮的更亮,暗的更暗),一个个试。当我试到“实色混合”和“颜色加深”的时候,水印就显示出来了。 哇,吓到我了,原来可以这么玩。 3、watermark-dom水印原理分析通过js向html中一次性添加dom元素,所以我取名叫做watermark-dom,如果一旦知道有这个水印插件,使用者是可以手动将当前页面的水印dom删掉,这样也就是开发人员知道怎么弄,对于其他人员还是不知道如何去掉页面水印的。 3.1关键元素1--pointer-events:none这个归功于css的属性,准确的说是css3的一个属性pointer-events,本来这个属性的而设计之初是为了--真正意义上的禁用元素,因为设置值为none的时候,这个元素是使用鼠标或者触摸感知不到的,可以称pointer-events为“元素虚化”。 支持浏览器:目前FireFox浏览器,Chrome都支持。Opera以及IE不支持。 该属性的缺点: 1、pointer-events:none影响触屏设备的滚动,如无线端页面等; 2、如果子元素设置了pointer-eventes: auto会导致滚动的时候页面闪动 3.2关键元素2--opacity属性为什么这么说opacity属性,因为这个属性是对元素进行透明的设置,因为是水印,希望尽量不影响到正常页面的视觉体验。 支持浏览器:所有浏览器都支持 opacity 属性。注意:IE8以及更早的版本支持替代的filter属性。例如:filter:Alpha(opacity=50)。 在设置这个透明度的时候,经过测试发现,水印透明度,要求设置在大于等于0.005,因为比这个低的话,使用ps显现的时候效果不明显。 如果需要去尽量影藏水印,可以把水印的字体颜色和页面的背景颜色调成一致。 如果就要正常显示,尽量设置opactity的时候设置为大于等于0.005,一个性能和显示的平衡点。 4、附上watermark-dom完整代码如果后续更新和优化的时候,源码请看这个watermark-dom里的watermark.js文件 5、一个具体的例子引入这个watermark.js的代码。 html代码 <html> <meta charset="utf-8"> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <script type="text/javascript" src="watermark.js"></script> <script type="text/javascript"> watermark.load({ watermark_txt: "测试水印,1021002301,测试水印,SDAHJDBJJdjsfsc" }); </script> <body> <div style="width:300px;height:300px;background-color: red; opacity:0.98;" onclick="alert(1);">test</div> <div style="width:300px;height:300px;background-color: blue; opacity:0.9;" onclick="alert(2);">test</div> <a href="www.test.com"> baidu</a> </body></html>效果如下图: ...

May 16, 2019 · 2 min · jiezi

Linux 网络命令必知必会之 tcpdump,一份完整的抓包指南请查收!

本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。[TOC] 这篇文章我总结得比较详尽,可以当字典查,建议收藏,不过别光顾着收藏,点赞什么的鼓励我一下,这能让我更有动力给大家输出更好的内容。 01 简介tcpdump 是一款 Linux 平台的抓包工具。它可以抓取涵盖整个 TCP/IP 协议族的数据包,支持针对网络层、协议、主机、端口的过滤,并提供 and、or、not 等逻辑语句来过滤无用的信息。 tcpdump 是一个非常复杂的工具,掌握它的方方面面实属不易,也不推荐,能够用它来解决日常工作问题才是关系。 02 tcpdump 命令选项tcpdump 有很多命令选项,想了解所有选项可以 Linux 命令行输入 tcpdump -h,man tcpdump 查看每个选项的意思。 [root@by ~]# tcpdump -htcpdump version 4.9.2libpcap version 1.5.3OpenSSL 1.0.2k-fips 26 Jan 2017Usage: tcpdump [-aAbdDefhHIJKlLnNOpqStuUvxX#] [ -B size ] [ -c count ] [ -C file_size ] [ -E algo:secret ] [ -F file ] [ -G seconds ] [ -i interface ] [ -j tstamptype ] [ -M secret ] [ --number ] [ -Q|-P in|out|inout ] [ -r file ] [ -s snaplen ] [ --time-stamp-precision precision ] [ --immediate-mode ] [ -T type ] [ --version ] [ -V file ] [ -w file ] [ -W filecount ] [ -y datalinktype ] [ -z postrotate-command ] [ -Z user ] [ expression ]下面列举一些常用选项: ...

April 22, 2019 · 4 min · jiezi

网络分析利器wireshark命令版:tshark使用示例(2)

tshark是wireshark网络分析工具下的一个分支,主要用于命令行环境进行抓包、分析,尤其对协议深层解析时,tcpdump难以胜任的场景中。本系列文章将整理介绍tshark相关内容。基本用法常用命令查看tshark版本tshark -v列出当前存在的网络接口tshark -D网卡描述依据OS有不同的编号方式,在不了解网络设备及编号情况下,一般先用“tshark -D”查看网络接口的编号以供-i参数使用。注: linux可以结合ifconfig命令查看tshark对指定网卡监听,抓包sudo tshark -i <interface>抓取网卡eth0的流量并写入capture123.pcaptshark -i eth0 -w capture123.pcap读取之前的文件capture123.pcaptshark -i eth0 -r capture123.pcap抓取网卡eth0的流量10分钟tshark -i eth0 -a duration:600注: 默认时间单位为秒抓取网卡eth0的10000个数据包tshark -c 10000 -i eth0抓取网卡eth0涉及192.168.1.1的流量报文tshark -i eth0 -f “host 192.168.1.1”注: 与wireshark、tcpdump一致,均使用BPF过滤表达式抓取网卡eth0指定协议的流量报文tshark -i eth0 -f “<协议名>”协议名可以为: tcp, udp, dns, icmp, http等案例实时打印当前mysql查询语句tshark -s 512 -i eth1 -n -f ’tcp dst port 3306’ -R ‘mysql.query’ -T fields -e mysql.query说明:-s 512 :只抓取前512个字节数据-i eth0 :监听eth0网卡-n :禁止域名解析-f ‘tcp dst port 3306’ :只捕捉协议为tcp,目的端口为3306的数据包-R ‘mysql.query’ :过滤出mysql.query查询语句的报文-T fields -e mysql.query :打印mysql查询语句实时打印当前http请求的url(包括域名)tshark -s 512 -i eth1 -n -f ’tcp dst port 8000’ -R ‘http.host and http.request.uri’ -T fields -e http.host -e http.request.uri -l | tr -d ’t’说明:-s 512 :只抓取前512个字节数据-i eth1 :监听eth1网卡-n :禁止网络对象名称解析-f ‘tcp dst port 8000’ :只捕捉协议为tcp,目的端口为8000的数据包-R ‘http.host and http.request.uri’ :过滤出http.host和http.request.uri-T fields -e http.host -e http.request.uri :打印http.host和http.request.uri-l :输出到标准输出读取之前抓包文件进行报文数据分析需要从抓包的文件evidence04.pcap中提取出报文相关数据信息,如时间、源IP、目的IP、协议名、源Port、標Port、包大小等信息,最后输出到csv文件。tshark -r evidence.pcap -T fields -e frame.time_relative -e ip.src -e ip.dst -e ip.proto -e tcp.srcport -e tcp.dstport -e frame.len -E header=n -E separator=, -E quote=n -E occurrence=f > output.csv说明:-r evidence.pcap 需要分析的报文记录文件(pcap格式)-T fields 输出格式,选fields按字段,也可以选json等其他格式,需结合-e 及 -E使用-e frame.time_relative 取出封包的相对时间-e ip.src 提取源IP-e ip.dst 提取目的IP-e ip.proto 提取协议名-e tcp.srcport 提取源Port-e tcp.dstport 提取目的Port-e frame.len 提取数据帧大小-E header=n 是否输出字段名称(cvs的第1行)-E separator=, 指定分割符,/t是tab,/s是一格空格-E quote=n 指定是否对字段用引号,d是双引号,s是单引号,n是不用-E occurrence=f 多值时是否保留,f是第一个值,l是最后一个值,a是所有值都列出,默认全部output.csv 输出文件路径及名称DNS报文过滤使用tshark过滤dns cap包中源ip、目的ip、request请求tshark -r test.cap -T fields -e frame.time -e ip.src -e ip.dst -e dns.qry.name -R ‘udp.dstport==53 || dns’说明:-r test.pcap 需要分析的报文记录文件(pcap格式)-T fields 输出格式,选fields按字段,也可以选json等其他格式,需结合-e 及 -E使用-e frame.time 提取数据帧时间-e ip.src 提取源IP-e ip.dst 提取目的IP-e dns.qry.name 提取dns查询的域名信息-R ‘udp.dstport==53 || dns’ 显示过滤,仅对udp目标端口为53或者dns协议的报文进行处理默认直接显示在终端上,不记录文件。常见问题tshark: Only read filters, not capture filters, can be specified when reading a capture file.tshark -r 20190409.pcap -f ‘udp’ -w udp-20190409.pcap读取文件时只能使用显示过滤,也就是只能使用-Y或-2 -R过滤tshark: -R without -2 is deprecated. For single-pass filtering use -Y.tshark -r 20190409.pcap -R ‘udp’ -w udp-20190409.pcap显示过滤-R参数需要和-2一起使用,或使用-Y参考:tshark很快,但要怎麼用?ethereal-filter使用tshark 和 shell脚本分析 DNS pcap包 系列文章: 网络分析利器wireshark命令版:tshark简介(1)网络分析利器wireshark命令版:tshark使用示例(2) ...

April 16, 2019 · 2 min · jiezi

网络分析利器wireshark命令版:tshark简介(1)

tshark是网络分析工具wireshark下的一个工具,主要用于命令行环境进行抓包、分析,尤其对协议深层解析时,tcpdump难以胜任的场景中。本系列文章将整理tshark相关:简介安装在linux下安装(debian或ubuntu)sudo apt-get install tshark安装完成后在抓包之前,可以先检查版本、查看帮助等了解tshark初步了解安装后检查按照安装wireshark的方式,安装完成后,启动“终端”(命令提示符):tshark -vtshark参数命令解析查看帮助如下:➜ ~ tshark -hTShark (Wireshark) 3.0.1 (v3.0.1-0-gea351cd8)Dump and analyze network traffic.See https://www.wireshark.org for more information.Usage: tshark [options] …主要参数说明接口报文捕捉 -i <interface> 接口名或网卡编号 (默认: 第一个非环回接口) -f <capture filter> 使用libpcap过滤表达式进行包过滤 -s <snaplen> 设置每个抓包的大小,默认为65535。 (相当于tcpdump的-s,tcpdump默认抓包的大小仅为68) -p 不使用混杂模式抓捕报文(即只抓取与本机有关的流量) -I 如果支持则启用镜像模式 -B <buffer size> 内核缓存大小 (默认2MB) -y <link type> 链路层类型 (默认为找到的第一个协议) –time-stamp-type <type> 接口时间戳类型 -D 列出所有接口并退出 -L 列出所有接口链路层类型并退出(供-y参数使用) –list-time-stamp-types 列出所有接口时间戳类型并退出(供–time-stamp参数使用)捕获终止条件 -c <packet count> 捕获到n个包时停止 (默认不限,持续捕获) -a <autostop cond.> … duration:NUM - 捕获进行NUM后停止 filesize:NUM - 输出文件大于NUM KB后停止 files:NUM - 输出超过NUM个文件后停止捕获输出 -b <ringbuffer opt.> … duration:NUM - 在NUM秒后写入下一个文件(文件名由-w参数决定) interval:NUM - create time intervals of NUM secs filesize:NUM - 在文件大于NUM KB后写入下一个文件 files:NUM - 循环缓存: 在NUM个文件后替换早前的读取文件-r <infile|-> 设置需要读取的文件名及路径(或’-‘表示标准输入,从终端输入)分析处理 -2 执行two-pass分析 -M <packet count> 执行会话自动重置 -R <read filter> 包读取过滤使用wireshark显示过滤表达式(配合-2参数) -Y <display filter> 包显示过滤使用wireshark显示过滤表达式 -n 不进行名称解析 (def: all enabled) -N <name resolve flags> 启用指定的地址名字解析: “mnNtdv” (“m”代表MAC层,“n”代表网络层,“t”代表传输层,“N”代表当前异步DNS查找。) -d <layer_type>==<selector>,<decode_as_protocol> … “解析为”,详见man帮助页面。例: tcp.port==8888,http (注意选择子和解包协议之间不能留空格) -H <hosts file> 读取主机列表文件,将被写入捕获的文件(Implies -W n) –enable-protocol <proto_name> 启用协议报文解析 –disable-protocol <proto_name> 不对指定协议报文解析 –enable-heuristic <short_name> 启用协议报文启发式解析 –disable-heuristic <short_name> 不对指定协议报文启发式解析输出 -w <outfile|-> 使用pcapng格式将报文写入"outfile"文件 (或’-‘表示标准输出,直接显示在终端) -C <config profile> 启动时使用指定的配置文件 -F <output file type> 设置输出文件格式类型, 默认为pcapng格式 “-F"留空则列出所有的文件类型 -V 输出中增加报文层次树(包详细信息) -O <protocols> 仅显示以下协议的详细信息,逗号分割 -P 每写入一个文件后进行包情况汇总 -S <separator> 数据包之间的行分割符 -x 输出中增加16进制和ascii字符信息(报文按字节显示) -T pdml|ps|psml|json|jsonraw|ek|tabs|text|fields|? 文本输出格式 (默认文本:text) -j <protocolfilter> 当-T ek|pdml|json 设置时协议层过滤 (例:“ip ip.flags text”, 过滤不展开的所有字节点,除非过滤中有指定的子节点) -J <protocolfilter> 当 -T ek|pdml|json 选项设置时进行顶层协议过滤, (例: “http tcp”, 过滤展开的所有字节点) -e <field> 当 -T fields 设置时打印字段 (如tcp.port,_ws.col.Info) 此选项可以多个用于打印多个字段 -E<fieldsoption>=<value> 当-Tfields选项启用时用于输出配置: bom=y|n 打印UTF-8 BOM header=y|n 选择首行是否输出字段名(类似表头) separator=/t|/s|<char> 选择字段采用tab、空格、指定可打印字符为分割符 occurrence=f|l|a 打印第一个、最后一个或全部出现的数值(默认全部) aggregator=,|/s|<char> 选择字段采用逗号、空格、指定可打印字符聚合字段 quote=d|s|n 选择对数值采用双引号、单引号、不用引号 -t a|ad|d|dd|e|r|u|ud|? 输出格式化的时间戳(默认r: rel. 优先) -u s|hms 输出格式化秒(默认s:秒) -l 每个包之后就刷新标准输出 -q 向终端输出少量信息 (e.g. 当使用统计) -Q 仅向stderr输出确切错误信息(比-q信息更少) -g 启用组用户读取输出文件 -W n 如果支持,保存额外信息到文件中 n = 写入网络地址解析信息 -X <key>:<value> 扩展选项,详见man页面 -U tap_name PDUs专家模式, 详见man帮助页面 -z <statistics> 大量统计,详见man帮助页面 –capture-comment <comment> 在最新创建的输出文件中增加捕获注释(仅支持pcapng格式) –export-objects <protocol>,<destdir> 保存指定导出协议对象到指定目录 –color 在输出的文本格式中支持类似Wireshark图形界面的色彩,终端需要支持24位彩色 同时支持pdml、psml的色彩属性(注:这两张属性非标准) –no-duplicate-keys 如果-T json设置, 合并重复键,将多个值归并在一个键下的数组中 –elastic-mapping-filter <protocols> 如果指定-G elastic-mapping,设置仅mapping文件中指定的协议杂项 -h 显示帮助 -v 显示版本 -o <name>:<value> … 覆盖配置项 -K <keytab> 使用keytab文件用于解密kerberos -G [report] 生成一份或多份报告,默认report=“fields” 使用”-G help"获取更多信息补充说明支持包输出格式可用作不同格式转化工具,推荐使用默认的pcapngtshark -Ftshark: option requires an argument – Ftshark: The available capture file types for the “-F” flag are:… pcap - Wireshark/tcpdump/… - pcap pcapng - Wireshark/… - pcapng…捕获过滤(Capture Filter)抓包前在网络接口上设置过滤的,使用BPF表达式(tcpdump、libpcap通用)。通常用于抓取指定的ip、协议,可定位到3层,可以通过简单字符进行。不建议作过多过滤(规则复杂可能影响抓包性能)读取(显示)过滤(Display Filter)用于抓包之后,能够对报文可识别的协议字段进行过滤,可对2~7层过滤,可使用逻辑表达式,多个条件结合使用。注意:建议在使用时,先在wireshare过滤栏调试,具有字段提示,表达式合规会显示绿色参考:tshark linux 命令tshark官方文档 ...

April 16, 2019 · 2 min · jiezi

Linux 虚拟网络设备详解之 “vRouter”

本文首发于我的公众号 cloud_dev,专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。这篇文章继上篇文章 Linux 虚拟网络设备详解之 Bridge,上篇发表之后,有读者问我这样一个问题:我的回答基本上是一句废话,因为只要你知道点网络的基础知识,肯定知道这种情况要走三层路由。但知道归知道,不实践永远不知道自己是不是真的知道(有点绕),我相信那位读者也是希望我能讲讲这其中具体是怎么路由的,今天这篇文章就来说说这个。Linux 本身就是一台路由器前面的文章我们学习了多种虚拟的网络设备,包括网卡、交换机等,也了解了怎么用工具来操作这些设备,那么,回到今天的主题,路由器有没有对应的虚拟设备,能不能也用相关工具来操作呢,这个答案如果要深究的话,也是有的,比如 OpenStack 的 DVR、一些开源的虚拟路由器实现等等。不过我们不做那么深究的讨论,简化问题,Linux 系统实际上没有实现相关的虚拟路由器设备,自然也没有工具可以操作路由器,因为 Linux 本身就是一台路由器。(这也是我文章的标题对 vRouter 打双引号的原因)Linux 提供一个开关来操作路由功能,就是 /proc/sys/net/ipv4/ip_forward,默认这个开关是关的,打开只需:echo 1 > /proc/sys/net/ipv4/ip_forward但这种打开方式只是临时的,如果要一劳永逸,可以修改配置文件 /etc/sysctl.conf,添加或修改项 net.ipv4.ip_forward 为:net.ipv4.ip_forward = 1即可。实践为了降低大家实践的难度,我们就不创建虚拟机了,直接使用 namespace,一条 ip 命令就可以搞定所有的操作。我们按照下面的图示进行操作(NS1 和 NS2 分布在不同网段):创建两个 namespace:ip netns add ns1ip netns add ns2创建两对 veth-pair,一端分别挂在两个 namespace 中:ip link add v1 type veth peer name v1_rip link add v2 type veth peer name v2_rip link set v1 netns ns1ip link set v2 netns ns2分别给两对 veth-pair 端点配上 IP 并启用:ip a a 10.10.10.1/24 dev v1_rip l s v1_r upip a a 10.10.20.1/24 dev v2_rip l s v2_r upip netns exec ns1 ip a a 10.10.10.2/24 dev v1ip netns exec ns1 ip l s v1 upip netns exec ns2 ip a a 10.10.20.2/24 dev v2ip netns exec ns2 ip l s v2 up验证一下: v1 ping v2,结果不通。看下 ip_forward 的值:[root@by ~]# cat /proc/sys/net/ipv4/ip_forward0没开路由怎么通,改为 1 再试,还是不通。看下 ns1 的路由表:[root@by ~]# ip netns exec ns1 route -nKernel IP routing tableDestination Gateway Genmask Flags Metric Ref Use Iface10.10.10.0 0.0.0.0 255.255.255.0 U 0 0 0 v1只有一条直连路由,没有去往 10.10.20.0/24 网段的路由,怎么通?那就给它配一条:[root@by ~]# ip netns exec ns1 route add -net 10.10.20.0 netmask 255.255.255.0 gw 10.10.10.1[root@by ~]# ip netns exec ns1 route -nKernel IP routing tableDestination Gateway Genmask Flags Metric Ref Use Iface10.10.10.0 0.0.0.0 255.255.255.0 U 0 0 0 v110.10.20.0 10.10.10.1 255.255.255.0 UG 0 0 0 v1同理也给 ns2 配上去往 10.10.10.0/24 网段的路由。最后再 ping,成功了![root@by ~]# ip netns exec ns1 ping 10.10.20.2PING 10.10.20.2 (10.10.20.2) 56(84) bytes of data.64 bytes from 10.10.20.2: icmp_seq=1 ttl=63 time=0.071 ms64 bytes from 10.10.20.2: icmp_seq=2 ttl=63 time=0.070 ms^C— 10.10.20.2 ping statistics —2 packets transmitted, 2 received, 0% packet loss, time 1000msrtt min/avg/max/mdev = 0.070/0.070/0.071/0.008 ms总结Linux 本身是一台路由器。上面的实验使用 namespace 效果和使用虚拟机是一样的,关键是知道有这个功能,知道怎么用就差不多了。我的公众号 cloud_dev,号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 20, 2019 · 2 min · jiezi

Linux 虚拟网络设备详解之 Bridge 网桥

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。前面几篇文章介绍了 tap/tun、veth-pair,今天这篇来看看 Bridge。Bridge 是什么同 tap/tun、veth-pair 一样,Bridge 也是一种虚拟网络设备,所以具备虚拟网络设备的所有特性,比如可以配置 IP、MAC 等。除此之外,Bridge 还是一个交换机,具有交换机所有的功能。对于普通的网络设备,就像一个管道,只有两端,数据从一端进,从另一端出。而 Bridge 有多个端口,数据可以从多个端口进,从多个端口出。Bridge 的这个特性让它可以接入其他的网络设备,比如物理设备、虚拟设备、VLAN 设备等。Bridge 通常充当主设备,其他设备为从设备,这样的效果就等同于物理交换机的端口连接了一根网线。比如下面这幅图通过 Bridge 连接两个 VM 的 tap 虚拟网卡和物理网卡 eth0。VM 同主机通信以这个图来简单说明下,借助 Bridge 来完成同主机两台 VM 的之间的通信流程。首先准备一个 centos 或 ubuntu 虚拟机,然后创建一个 bridge:ip link add br0 type bridgeip link set br0 up然后通过 virt-manager 创建两个 kvm 虚拟机:kvm1 和 kvm2(前提得支持嵌套虚拟化),将它们的 vNIC 挂到 br0 上,如下图:kvm 虚机会使用 tap 设备作为它的虚拟网卡,我们验证下:# ps -ef | grep kvm1libvirt+ 3549 1 87 ? 00:22:09 qemu-system-x86_64 -enable-kvm -name kvm1 … -netdev tap,fd=26,id=hostnet0,vhost=on,vhostfd=28 …可以看到,其中网络部分参数,-netdev tap,fd=26 表示的就是连接主机上的 tap 设备。创建的 fd=26 为读写 /dev/net/tun 的文件描述符。使用 lsof -p 3549 验证下:# lsof -p 3549COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME…qemu-system 3549 libvirt-qemu 26u CHR 10,200 0t107 135 /dev/net/tun…可以看到,PID 为 3549 的进程打开了文件 /dev/net/tun,分配的文件描述符 fd 为 26。因此,我们可以得出以下结论:在 kvm 虚机启动时,会向内核注册 tap 虚拟网卡,同时打开设备文件 /dev/net/tun,拿到文件描述符 fd,然后将 fd 和 tap 关联,tap 就成了一端连接着用户空间的 qemu-kvm,一端连着主机上的 bridge 的端口,促使两者完成通信。下面分别给两虚机配上 IP:10.1.1.2/24 和 10.1.1.3/24,ping 一下:在 bridge 上抓个包看看:可以看到,br0 上抓到 ping 的 ICMP echo 包和 ARP 包。Bridge 常用使用场景Bridge 设备通常就是结合 tap/tun、veth-pair 设备用于虚拟机、容器网络里面。这两种网络,在数据传输流程上还有些许不同,我们简单来看下:首先是虚拟机网络,虚拟机一般通过 tap/tun 设备将虚拟机网卡同宿主机里的 Bridge 连接起来,完成同主机和跨主机的通信。如下图所示:【图片来源于网络,侵权必删】虚拟机发出的数据包通过 tap 设备先到达 br0,然后经过 eth0 发送到物理网络中,数据包不需要经过主机的的协议栈,效率是比较高的。其次是容器网络(容器网络有多种引申的形式,这里我们只说 Bridge 网络),容器网络和虚拟机网络类似,不过一般是使用 veth-pair 来连接容器和主机,因为在主机看来,容器就是一个个被隔离的 namespace,用 veth-pair 更有优势。如下图所示:【图片来源于网络,侵权必删】容器的 Bridge 网络通常配置成内网形式,要出外网需要走 NAT,所以它的数据传输不像虚拟机的桥接形式可以直接跨过协议栈,而是必须经过协议栈,通过 NAT 和 ip_forward 功能从物理网卡转发出去,因此,从性能上看,Bridge 网络虚拟机要优于容器。总结Linux Bridge 是虚拟交换机,功能和物理交换机一样,用于连接虚拟机和容器。虚拟机网络和容器网络的区别。Bridge 是偏低级的工具,更高级的工具是 Open vSwitch,这个工具后面再详说。我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 19, 2019 · 1 min · jiezi

一文掌握 Linux 性能分析之内存篇

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。前面我们已经学习了 CPU 篇,这篇来看下内存篇。01 内存信息同样在分析内存之前,我们得知到怎么查看系统内存信息,有以下几种方法。1.1 /proc/meminfo这个文件记录着比较详细的内存配置信息,使用 cat /proc/meminfo 查看。我们比较关心的是下面几个字段:MemTotal:系统总内存,由于 BIOS、内核等会占用一些内存,所以这里和配置声称的内存会有一些出入,比如我这里配置有 2G,但其实只有 1.95G 可用。MemFree:系统空闲内存。MemAvailable:应用程序可用内存。有人会比较奇怪和 MemFree 的区别,可以从两个层面来区分,MemFree 是系统层面的,而 MemAvailable 是应用程序层面的。系统中有些内存虽然被使用了但是有一部分是可以回收的,比如 Buffers、Cached 及 Slab 这些内存,这部分可以回收的内存加上 MemFree 才是 MemAvailable 的内存值,这是内核通过特定算法算出来的,是一个估算值。Buffers:缓冲区内存Cached:缓存上面信息没有 MemUsed 的值,虽然可以用现有的值大致估算出来,但是我们想一步到位,就用下面的 free 命令。1.2 free这个命令估计用的人就多了(我一般都是用这个命令)。这里存在一个计算公式:MemTotal = used + free + buff/cache(单位 K)几个字段和上面 /proc/meminfo 的字段是对应的。还有个 shared 字段,这个是多进程的共享内存空间,不常用。我们注意到 free 很小,buff/cache 却很大,这是 Linux 的内存设计决定的,Linux 的想法是内存闲着反正也是闲着,不如拿出来做系统缓存和缓冲区,提高数据读写的速率。但是当系统内存不足时,buff/cache 会让出部分来,非常灵活的操作。要看比较直观的值,可以加 -h 参数:1.3 dmidecode同样可以使用这个命令,对于内存,可以使用 dmidecode -t memory 查看:1.4 vmstat这个命令也是非常常用了。但对于内存,显示信息有限。它更多是用于进行系统全局分析和 CPU 分析。详细可以看 CPU 分析一文。02 进程内存使用情况分析最常用的两个命令 ps 和 top,虽然很简单的两个命令,但还是有不少学问的。2.1 top/htoptop 命令运行时默认是按照 CPU 利用率进行排序的,如果要按照内存排序,该怎么操作呢?两种方法,一种直接按 “M”(相应的按 “P” 是 CPU),另外一种是在键入 top 之后,按下 “F”,然后选择要排序的字段,再按下 “s” 确认即可。可以看到,我按照 “%MEM” 排序的结果。这个结果对于查看系统占用内存较多的哪些进程是比较有用的。然后这里我们会重点关注几个地方,上面横排区,和前面几个命令一样可以查看系统内存信息,中间标注的横条部分,和内存相关的有三个字段:VIRT、RES、SHR。VIRT:virtual memory usage,进程占用的虚拟内存大小。RES:resident memory usage,进程常驻内存大小,也就是实际内存占用情况,一般我们看进程占用了多少内存,就是看的这个值。SHR:shared memory,共享内存大小,不常用。2.2 psps 同样可以查看进程占用内存情况,一般常用来查看 Top n 进程占用内存情况,如:ps aux –sort=rss | head -n,表示按 rss 排序,取 Top n。这里也关注三个字段:%MEM:进程使用物理内存所占百分比。VSZ:进程使用虚拟内存大小。RSS:进程使用物理内存大小,我们会重点关注这个值。2.3 pmap这个命令用于查看进程的内存映像信息,能够查看进程在哪些地方用了多少内存。 常用 pmap -x pid 来查看。可以看到该进程内存被哪些库、哪些文件所占用,据此我们定位程序对内存的使用。几个字段介绍一下:Address:占用内存的文件的内存起始地址。Kbytes:占用内存的字节数。RSS:实际占用内存大小。Dirty:脏页大小。Mapping:占用内存的文件,[anon] 为已分配的内存,[stack] 为程序堆栈最后的 total 为统计的总值。我们可以使用 pmap -x pid | tail -1 这样只显示最后一行,循环显示最后一行,达到监控该进程的目的。使用:while true; do pmap -x pid | tail -1; sleep 1; doneOK,以上工具都是 Linux 自带的,当然还有很多高阶的工具,比如 atop、memstat 等等,对于内存泄漏有一个比较常用的检测工具 Valgrind,更多干货可以关注我的公众号。通过以上手段,我们基本上就能定位内存问题所在了,究竟是内存太小,还是进程占用内存太多,有哪些进程占用较多,这些进程又究竟有哪些地方占用较多,这些问题通过以上方法都能解决。最后简单总结下,以上不少工具可能有人会犯选择困难症了。对于我来说,查看系统内存用 free -h,分析进程内存占用用 ps 或者 top(首选 ps),深入分析选择 pmap,就酱。参考:Linux下查看内存使用情况的多种方法 http://stor.51cto.com/art/201…我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 18, 2019 · 1 min · jiezi

一文掌握 Linux 性能分析之网络篇

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。这是 Linux 性能分析系列的第四篇,前三篇在这里:一文掌握 Linux 性能分析之 CPU 篇一文掌握 Linux 性能分析之内存篇一文掌握 Linux 性能分析之 I/O 篇比较宽泛地讲,网络方向的性能分析既包括主机测的网络配置查看、监控,又包括网络链路上的包转发时延、吞吐量、带宽等指标分析。包括但不限于以下分析工具:ping:测试网络连通性ifconfig:接口配置ip:网络接口统计信息netsat:多种网络栈和接口统计信息ifstat:接口网络流量监控工具netcat:快速构建网络连接tcpdump:抓包工具sar:统计信息历史traceroute:测试网络路由pathchar:确定网络路径特征dtrace:TCP/IP 栈跟踪iperf / netperf / netserver:网络性能测试工具perf 性能分析神器本文先来看前面 7 个。pingping 发送 ICMP echo 数据包来探测网络的连通性,除了能直观地看出网络的连通状况外,还能获得本次连接的往返时间(RTT 时间),丢包情况,以及访问的域名所对应的 IP 地址(使用 DNS 域名解析),比如:我们 ping baidu.com,-c 参数指定发包数。可以看到,解析到了 baidu 的一台服务器 IP 地址为 220.181.112.244。RTT 时间的最小、平均、最大和算术平均差分别是 40.732ms、40.762ms、40.791ms 和 0.248。ifconfigifconfig 命令被用于配置和显示 Linux 内核中网络接口的统计信息。通过这些统计信息,我们也能够进行一定的网络性能调优。1)ifconfig 显示网络接口配置信息其中,RX/TX packets 是对接收/发送数据包的情况统计,包括错误的包,丢掉多少包等。RX/TX bytes 是接收/发送数据字节数统计。其余还有很多参数,就不一一述说了,性能调优时可以重点关注 MTU(最大传输单元) 和 txqueuelen(发送队列长度),比如可以用下面的命令来对这两个参数进行微调:ifconfig eth0 txqueuelen 2000ifconfig eth0 mtu 1500 2)网络接口地址配置ifconfig 还常用来配置网口的地址,比如:为网卡配置和删除IPv6地址:ifconfig eth0 add 33ffe:3240:800:1005::2/64 #为网卡eth0配置IPv6地址ifconfig eth0 del 33ffe:3240:800:1005::2/64 #为网卡eth0删除IPv6地址修改MAC地址:ifconfig eth0 hw ether 00:AA:BB:CC:dd:EE配置IP地址:ifconfig eth0 192.168.2.10ifconfig eth0 192.168.2.10 netmask 255.255.255.0ifconfig eth0 192.168.2.10 netmask 255.255.255.0 broadcast 192.168.2.255IPip 命令用来显示或设置 Linux 主机的网络接口、路由、网络设备、策略路由和隧道等信息,是 Linux 下功能强大的网络配置工具,旨在替代 ifconfig 命令,如下显示 IP 命令的强大之处,功能涵盖到 ifconfig、netstat、route 三个命令。netstatnetstat 可以查看整个 Linux 系统关于网络的情况,是一个集多钟网络工具于一身的组合工具。常用的选项包括以下几个:默认:列出连接的套接字-a:列出所有套接字的信息-s:各种网络协议栈统计信息-i:网络接口信息-r:列出路由表-l:仅列出有在 Listen 的服务状态-p:显示 PID 和进程名称各参数组合使用实例如下:netstat -at 列出所有 TCP 端口netstat -au 列出所有 UDP 端口netstat -lt 列出所有监听 TCP 端口的 socketnetstat -lu 列出所有监听 UDP 端口的 socketnetstat -lx 列出所有监听 UNIX 端口的 socketnetstat -ap | grep ssh 找出程序运行的端口netstat -an | grep ‘:80’ 找出运行在指定端口的进程1)netstat 默认显示连接的套接字数据整体上来看,输出结果包括两个部分:Active Internet connections :有源 TCP 连接,其中 Recv-Q 和 Send-Q 指的是接收队列和发送队列,这些数字一般都是 0,如果不是,说明请求包和回包正在队列中堆积。Active UNIX domain sockets:有源 UNIX 域套接口,其中 proto 显示连接使用的协议,RefCnt 表示连接到本套接口上的进程号,Types 是套接口的类型,State 是套接口当前的状态,Path 是连接到套接口的进程使用的路径名。2)netstat -i 显示网络接口信息接口信息包括网络接口名称(Iface)、MTU,以及一系列接收(RX-)和传输(TX-)的指标。其中 OK 表示传输成功的包,ERR 是错误包,DRP 是丢包,OVR 是超限包。这些参数有助于我们对网络收包情况进行分析,从而判断瓶颈所在。3)netstat -s 显示所有网络协议栈的信息可以看到,这条命令能够显示每个协议详细的信息,这有助于我们针对协议栈进行更细粒度的分析。4)netstat -r 显示路由表信息这条命令能够看到主机路由表的一个情况。当然查路由我们也可以用 ip route 和 route 命令,这个命令显示的信息会更详细一些。ifstatifstat 主要用来监测主机网口的网络流量,常用的选项包括:-a:监测主机所有网口-i:指定要监测的网口-t:在每行输出信息前加上时间戳-b:以 Kbit/s 显示流量数据,而不是默认的 KB/s-delay:采样间隔(单位是 s),即每隔 delay 的时间输出一次统计信息-count:采样次数,即共输出 count 次统计信息比如,通过以下命令统计主机所有网口某一段时间内的流量数据:可以看出,分别统计了三个网口的流量数据,前面输出的时间戳,有助于我们统计一段时间内各网口总的输入、输出流量。netcatnetcat,简称 nc,命令简单,但功能强大,在排查网络故障时非常有用,因此它也在众多网络工具中有着“瑞士军刀”的美誉。它主要被用来构建网络连接。可以以客户端和服务端的方式运行,当以服务端方式运行时,它负责监听某个端口并接受客户端的连接,因此可以用它来调试客户端程序;当以客户端方式运行时,它负责向服务端发起连接并收发数据,因此也可以用它来调试服务端程序,此时它有点像 Telnet 程序。常用的选项包括以下几种:-l:以服务端的方式运行,监听指定的端口。默认是以客户端的方式运行。-k:重复接受并处理某个端口上的所有连接,必须与 -l 一起使用。-n:使用 IP 地址表示主机,而不是主机名,使用数字表示端口号,而不是服务名称。-p:当以客户端运行时,指定端口号。-s:设置本地主机发出的数据包的 IP 地址。-C:将 CR 和 LF 两个字符作为结束符。-U:使用 UNIX 本地域套接字通信。-u:使用 UDP 协议通信,默认使用的是 TCP 协议。-w:如果 nc 客户端在指定的时间内未检测到任何输入,则退出。-X:当 nc 客户端与代理服务器通信时,该选项指定它们之间的通信协议,目前支持的代理协议包括 “4”(SOCKS v.4),“5”(SOCKS v.5)和 “connect” (HTTPs Proxy),默认使用 SOCKS v.5。-x:指定目标代理服务器的 IP 地址和端口号。下面举一个简单的例子,使用 nc 命令发送消息:首先,启动服务端,用 nc -l 0.0.0.0 12345 监听端口 12345 上的所有连接。然后,启动客户端,用 nc -p 1234 127.0.0.1 12345 使用 1234 端口连接服务器 127.0.0.1::12345。接着就可以在两端互发数据了。这里只是抛砖引玉,更多例子大家可以多实践。tcpdump最后是 tcpdump,强大的网络抓包工具。虽然有 wireshark 这样更易使用的图形化抓包工具,但 tcpdump 仍然是网络排错的必备利器。tcpdump 选项很多,我就不一一列举了,大家可以看文章末尾的引用来进一步了解。这里列举几种 tcpdump 常用的用法。1)捕获某主机的数据包比如想要捕获主机 200.200.200.100 上所有收到和发出的所有数据包,使用:tcpdump host 200.200.200.1002)捕获多个主机的数据包比如要捕获主机 200.200.200.1 和主机 200.200.200.2 或 200.200.200.3 的通信,使用:tcpdump host 200.200.200.1 and (200.200.200.2 or )同样要捕获主机 200.200.200.1 除了和主机 200.200.200.2 之外所有主机通信的 IP 包。使用:tcpdump ip host 200.200.200.1 and ! 200.200.200.23)捕获某主机接收或发出的某种协议类型的包比如要捕获主机 200.200.200.1 接收或发出的 Telnet 包,使用:tcpdump tcp port 23 host 200.200.200.14)捕获某端口相关的数据包比如捕获在端口 6666 上通过的包,使用:tcpdump port 66665)捕获某网口的数据包比如捕获在网口 eth0 上通过的包,使用:tcpdump -i eth0下面还是举个例子,抓取 TCP 三次握手的包:(具体抓包的过程请移步到我的公众号进一步了解,那里阅读体验好一点,谢谢。)总结:本文总结了几种初级的网络工具,基本的网络性能分析,通过组合以上几种工具,基本都能应付,但对于复杂的问题,以上工具可能就无能为力了。更多高阶的工具将在下文送上,敬请期待。参考:基础 http://kuring.me/post/linux_n… 讲 ip 和 ifconfig 很强大的一篇文章:https://blog.csdn.net/freekin…https://www.alibabacloud.com/…性能之巅:Linux网络性能分析工具http://www.infoq.com/cn/artic…抓包工具tcpdump用法说明 https://www.cnblogs.com/f-ck-...https://www.shiyanlou.com/cou...http://linuxtools-rst.readthe...https://www.shiyanlou.com/cou…我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 15, 2019 · 2 min · jiezi

一个在线日期计算器

春日里,携妹子的手登上高山,看万物复苏,看繁华绽放,此时微风吹过,你将她顺势轻轻拉入怀中。妹子轻轻地问你:亲爱的,我们相恋多久了?刹那间你:&%^@#$(@@&……避免出现这样的尴尬,写了一个日期计算器,先计算好,一切尽在掌握。可以计算日期间的天数差,可以通过天数差推算出日期,顺便展示下这一年已经过去多少,距离下一个假日还有几天……点击可以体验下 日期计算器一个没有啥技术的小工具,just for fun & just for love .

March 14, 2019 · 1 min · jiezi

【原创】一文掌握 Linux 性能分析之 I/O 篇

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。一文掌握 Linux 性能分析之 CPU 篇一文掌握 Linux 性能分析之内存篇这是 Linux 性能分析系列的第三篇,前两篇分别讲了 CPU 和 内存,本篇来看 IO。IO 和 存储密切相关,存储可以概括为磁盘,内存,缓存,三者读写的性能差距非常大,磁盘读写是毫秒级的(一般 0.1-10ms),内存读写是微妙级的(一般 0.1-10us),cache 是纳秒级的(一般 1-10ns)。但这也是牺牲其他特性为代价的,速度快的,价格越贵,容量也越小。IO 性能这块,我们更多关注的是读写磁盘的性能。首先,先了解下磁盘的基本信息。磁盘基本信息fdisk查看磁盘信息,包括磁盘容量,扇区大小,IO 大小等信息,常用 fdisk -l 查看:可以看到 /dev/ 下有一个 40G 的硬盘,一共 8K 多万个扇区,每个扇区 512字节,IO 大小也是 512 字节。df查看磁盘使用情况,通常看磁盘使用率:磁盘性能分析主要分析磁盘的读写效率(IOPS:每秒读写的次数;吞吐量:每秒读写的数据量),IO 繁忙程度,及 IO 访问对 CPU 的消耗等性能指标。vmstat第一个较为常用的还是这个万能的 vmstat:对于 IO,我们常关注三个部分:b 值:表示因为 IO 阻塞排队的任务数bi 和 bo 值:表示每秒读写磁盘的块数,bi(block in)是写磁盘,bo(block out)是读磁盘。wa 值:表示因为 IO 等待(wait)而消耗的 CPU 比例。一般这几个值偏大,都意味着系统 IO 的消耗较大,对于读请求较大的服务器,b、bo、wa 的值偏大,而写请求较大的服务器,b、bi、wa 的值偏大。iostatvmstat 虽然万能,但是它分析的东西有限,iostat 是专业分析 IO 性能的工具,可以方便查看 CPU、网卡、tty 设备、磁盘、CD-ROM 等等设备的信息,非常强大,总结下来,共有以下几种用法:1)iostat -c 查看部分 CPU 使用情况:这里显示的是多个 CPU 的平均值,每个字段的含义我就不多解释了,我一般会重点关注 %iowait 和 %idle,分别表示 CPU 等待 IO 完成时间的百分比和 CPU 空闲时间百分比。如果 %iowait 较高,则表明磁盘存在 IO 瓶颈,如果 %idle 较高,则 CPU 比较空闲,如果两个值都比较高,则有可能 CPU 在等待分配内存,瓶颈在内存,此时应该加大内存,如果 %idle 较低,则此时瓶颈在 CPU,应该增加 CPU 资源。2)iostat -d 查看磁盘使用情况,主要是显示 IOPS 和吞吐量信息(-k : 以 KB 为单位显示,-m:以 M 为单位显示):其中,几个参数分别解释如下:tps:设备每秒的传输次数(transfers per second),也就是读写次数。kB_read/s:每秒读磁盘的数据量kB_wrtn/s:每秒写磁盘的数据量kB_read:读取磁盘的数据总量kB_wrtn:写入磁盘的数据总量3)iostat -x 查看磁盘详细信息:其中,几个参数解释如下;rrqm/s 和 wrqm/s:分别每秒进行合并的读操作数和写操作数,这是什么意思呢,合并就是说把多次 IO 请求合并成少量的几次,这样可以减小 IO 开销,buffer 存在的意义就是为了解决这个问题的。r/s 和 w/s:每秒磁盘读写的次数。这两个值相加就是 tps。rkB/s 和 wkB/s:每秒磁盘读写的数据量,这两个值和上面的 kB_read/s、kB_wrnt/s 是一个意思。avgrq-sz:平均每次读写磁盘扇区的大小。avgqu-sze:平均 IO 队列长度。队列长度越短越好。await:平均每次磁盘读写的等待时间(ms)。svctm:平均每次磁盘读写的服务时间(ms)。%util:一秒钟有百分之多少的时间用于磁盘读写操作。以上这些参数太多了,我们并不需要每个都关注,可以重点关注两个:1)%util:衡量 IO 的繁忙程度这个值越大,说明产生的 IO 请求较多,IO 压力较大,我们可以结合 %idle 参数来看,如果 %idle < 70% 就说明 IO 比较繁忙了。也可以结合 vmstat 的 b 参数(等待 IO 的进程数)和 wa 参数(IO 等待所占 CPU 时间百分比)来看,如果 wa > 30% 也说明 IO 较为繁忙。2)await:衡量 IO 的响应速度通俗理解,await 就像我们去医院看病排队等待的时间,这个值和医生的服务速度(svctm)和你前面排队的人数(avgqu-size)有关。如果 svctm 和 await 接近,说明磁盘 IO 响应时间较快,排队较少,如果 await 远大于 svctm,说明此时队列太长,响应较慢,这时可以考虑换性能更好的磁盘或升级 CPU。4)iostat 1 2 默认显示 cpu 和 吞吐量信息,1 定时 1s 显示,2 显示 2 条信息进程 IO 性能分析有了以上两个命令,基本上能对磁盘 IO 的信息有个全方位的了解了。但如果要确定具体哪个进程的 IO 开销较大,这就得借助另外的工具了。iotop这个命令类似 top,可以显示每个进程的 IO 情况,有了这个命令,就可以定位具体哪个进程的 IO 开销比较大了。OK,最后还是总结下,fdisk -l 和 df 查看磁盘基本信息,iostat -d 查看磁盘 IOPS 和吞吐量,iostat -x 结合 vmstat 查看磁盘的繁忙程度和处理效率。参考:http://linuxtools-rst.readthe…http://rdc.hundsun.com/portal…我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 14, 2019 · 1 min · jiezi

一文掌握 Linux 性能分析之内存篇

本文首发于我的公众号 CloudDeveloper(ID: cloud_dev),专注于干货分享,号内有大量书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫。前面我们已经学习了 CPU 篇,这篇来看下内存篇。01 内存信息同样在分析内存之前,我们得知到怎么查看系统内存信息,有以下几种方法。1.1 /proc/meminfo这个文件记录着比较详细的内存配置信息,使用 cat /proc/meminfo 查看。我们比较关心的是下面几个字段:MemTotal:系统总内存,由于 BIOS、内核等会占用一些内存,所以这里和配置声称的内存会有一些出入,比如我这里配置有 2G,但其实只有 1.95G 可用。MemFree:系统空闲内存。MemAvailable:应用程序可用内存。有人会比较奇怪和 MemFree 的区别,可以从两个层面来区分,MemFree 是系统层面的,而 MemAvailable 是应用程序层面的。系统中有些内存虽然被使用了但是有一部分是可以回收的,比如 Buffers、Cached 及 Slab 这些内存,这部分可以回收的内存加上 MemFree 才是 MemAvailable 的内存值,这是内核通过特定算法算出来的,是一个估算值。Buffers:缓冲区内存Cached:缓存上面信息没有 MemUsed 的值,虽然可以用现有的值大致估算出来,但是我们想一步到位,就用下面的 free 命令。1.2 free这个命令估计用的人就多了(我一般都是用这个命令)。这里存在一个计算公式:MemTotal = used + free + buff/cache(单位 K)几个字段和上面 /proc/meminfo 的字段是对应的。还有个 shared 字段,这个是多进程的共享内存空间,不常用。我们注意到 free 很小,buff/cache 却很大,这是 Linux 的内存设计决定的,Linux 的想法是内存闲着反正也是闲着,不如拿出来做系统缓存和缓冲区,提高数据读写的速率。但是当系统内存不足时,buff/cache 会让出部分来,非常灵活的操作。要看比较直观的值,可以加 -h 参数:1.3 dmidecode同样可以使用这个命令,对于内存,可以使用 dmidecode -t memory 查看:1.4 vmstat这个命令也是非常常用了。但对于内存,显示信息有限。它更多是用于进行系统全局分析和 CPU 分析。详细可以看 CPU 分析一文。02 进程内存使用情况分析最常用的两个命令 ps 和 top,虽然很简单的两个命令,但还是有不少学问的。2.1 top/htoptop 命令运行时默认是按照 CPU 利用率进行排序的,如果要按照内存排序,该怎么操作呢?两种方法,一种直接按 “M”(相应的按 “P” 是 CPU),另外一种是在键入 top 之后,按下 “F”,然后选择要排序的字段,再按下 “s” 确认即可。可以看到,我按照 “%MEM” 排序的结果。这个结果对于查看系统占用内存较多的哪些进程是比较有用的。然后这里我们会重点关注几个地方,上面横排区,和前面几个命令一样可以查看系统内存信息,中间标注的横条部分,和内存相关的有三个字段:VIRT、RES、SHR。VIRT:virtual memory usage,进程占用的虚拟内存大小。RES:resident memory usage,进程常驻内存大小,也就是实际内存占用情况,一般我们看进程占用了多少内存,就是看的这个值。SHR:shared memory,共享内存大小,不常用。2.2 psps 同样可以查看进程占用内存情况,一般常用来查看 Top n 进程占用内存情况,如:ps aux –sort=rss | head -n,表示按 rss 排序,取 Top n。这里也关注三个字段:%MEM:进程使用物理内存所占百分比。VSZ:进程使用虚拟内存大小。RSS:进程使用物理内存大小,我们会重点关注这个值。2.3 pmap这个命令用于查看进程的内存映像信息,能够查看进程在哪些地方用了多少内存。 常用 pmap -x pid 来查看。可以看到该进程内存被哪些库、哪些文件所占用,据此我们定位程序对内存的使用。几个字段介绍一下:Address:占用内存的文件的内存起始地址。Kbytes:占用内存的字节数。RSS:实际占用内存大小。Dirty:脏页大小。Mapping:占用内存的文件,[anon] 为已分配的内存,[stack] 为程序堆栈最后的 total 为统计的总值。我们可以使用 pmap -x pid | tail -1 这样只显示最后一行,循环显示最后一行,达到监控该进程的目的。使用:while true; do pmap -x pid | tail -1; sleep 1; doneOK,以上工具都是 Linux 自带的,当然还有很多高阶的工具,比如 atop、memstat 等等,对于内存泄漏有一个比较常用的检测工具 Valgrind,更多干货可以关注我的公众号。通过以上手段,我们基本上就能定位内存问题所在了,究竟是内存太小,还是进程占用内存太多,有哪些进程占用较多,这些进程又究竟有哪些地方占用较多,这些问题通过以上方法都能解决。最后简单总结下,以上不少工具可能有人会犯选择困难症了。对于我来说,查看系统内存用 free -h,分析进程内存占用用 ps 或者 top(首选 ps),深入分析选择 pmap,就酱。参考:Linux下查看内存使用情况的多种方法 http://stor.51cto.com/art/201…我的公众号 CloudDeveloper(ID: cloud_dev),号内有大量书籍和视频资源,后台回复「1024」即可领取,分享的内容包括但不限于云计算虚拟化、容器、OpenStack、K8S、雾计算、网络、工具、SDN、OVS、DPDK、Linux、Go、Python、C/C++编程技术等内容,欢迎大家关注。 ...

March 13, 2019 · 1 min · jiezi

关于文本编辑器我为什么墙裂推荐你使用Notepad++

关于文本编辑器,我使用过好几种,比如记事本、editplus、Notepad++、emeditor、sublime text等等。每个都有它自己的特点,我也不能说哪个好或者不好,只能说自己使用起来最顺手的是哪一个,想必大家已经知道我为什么墙裂推荐Notepad++了,因为我觉得它使用起来忒方便(因人而异),下面我为大家说说他的优点吧。首先,它的第一个优点就是免费我们去官网下载最新的Notepad++点击Notepad++ 7.6.4 released后鼠标下滑找到下图的[ ](https://notepad-plus-plus.org…然后找对应自己系统的版本,这里下载64位Notepad++ Installer 64-bit x64下载完成后,双击安装包,选择语言下来就一直下一步直到安装完成。点击完成,启动Notepad++如下图:第二个优点就是字体大小调节方便,只需按住atrl键,鼠标滚轮上下滚动可以调节字体大小(是不是很方便)第三个优点就是无需插件,多种主题供你选择接下来,我们根据自己的喜好配置notepad++,点击设置,再点击语言格式设置然后选择自己喜欢的主题如果习惯从左侧列表选择文件的话,可以设置将文件列表显示OK,文件列表已经显示出来。第四个优点就是可以同时打开多个文件,并且当你编辑文件后即使没保存,关闭notepad++再重新打开,各文件还是你关闭前的编辑状态。第五个优点就是可以根据文件选择对应的语言,下面以C语言文件为例这是编辑的代码就会高亮显示第六个优点就是文件编码切换方便第七个优点就是给代码文件指定好环境后可以直接运行今天给大家分享的是一些很常用的功能,它的优点还有很多很多,如果你现在还没有一个得心应手的文本编辑器,不妨试试notepad++。

March 10, 2019 · 1 min · jiezi

【Linux系统编程】快速查找errno错误码信息

我们都知道,errno整型变量被普遍应用于*NIX C的异常处理中,其记录了最近一次的错误码。通过判断错误码的值,以此执行不同的错误处理,这是C语言典型的异常处理方式。其错误名称,比如EAGAIN、EWOULDBLOCK等,都通过宏定义,头文件是errno.h;错误码对应的描述,可以通过strerror输出。如果我们想知道错误名称对应的错误码的值,只需要简单的print("%d",EAGAIN);就能知道;如果想知道错误码的描述,调用strerror即可。但毕竟要写程序,相对不方便,那有没有现成的工具帮我们做到这些呢?Linux有一款errno命令行程序可以很方便的解决以上的问题,而且还能提供更丰富的功能。安装在Debian可通过apt-get install moreutils安装,这个软件包里包含很多的命令程序,有时间可以挖掘一下。功能说明errno程序选项很少,通过man errno一屏就可展开。下面说主要功能:通过错误名称查错误码和错误描述$ errno EWOULDBLOCKEWOULDBLOCK 11 Resource temporarily unavailable通过错误码查错误名称和错误描述$ errno 11EAGAIN 11 Resource temporarily unavailable列举所有errno变量所有错误情况使用errno -l 或 errno -ls通过错误描述里的关键字(大小写不敏感)查对应的错误情况$ errno -s supportEPROTONOSUPPORT 93 Protocol not supportedESOCKTNOSUPPORT 94 Socket type not supportedEOPNOTSUPP 95 Operation not supportedEPFNOSUPPORT 96 Protocol family not supportedEAFNOSUPPORT 97 Address family not supported by protocolENOTSUP 95 Operation not supported 请关注我的公众号哦。

February 28, 2019 · 1 min · jiezi

CAP 一致性协议及应用解析

一、一致性1.1 CAP 理论C 一致性:分布式环境中,一致性是指多个副本之间,在同一时刻能否有同样的值A 可用性:系统提供的服务必须一直处于可用的状态。即使集群中一部分节点故障。P 分区容错性:系统在遇到节点故障,或者网络分区时,任然能对外提供一致性和可用性的服务。以实际效果而言,分区相当于通信的时限要求。系统如果不能在一定实现内达成数据一致性,也就意味着发生了分区的情况。必须就当前操作在 C 和 A 之前作出选择1.2 CAP不能同时满足的证明假设系统中有 5 个节点,n1n5。n1,n2,n3 在A物理机房。n4,n5 在 B 物理机房。现在发生了网络分区,A 机房和 B 机房网络不通。保证一致性:此时客户端在 A 机房写入数据,不能同步到B机房。写入失败。此时失去了可用性。保证可用性:数据在 A 机房的 n1n3 节点都写入成功后返回成功。数据在 B 机房的 n4n5 节点也写入数据,返回成功。同一份数据在 A 机房和 B 机房出现了数据不一致的情况。聪明如你,可以想到 zookeeper,当一个节点 down 掉,系统会将其剔出节点,然其它一半以上的节点写入成功即可。是不是 zookeeper 同时满足了 CAP 呢。其实这里有一个误区,系统将其剔出节点。有一个隐含的条件是,系统引入了一个调度者,一个踢出坏节点的调度者。当调度者和 zookeeper 节点出现网络分区,整个系统还是不可用的。1.3 常见场景CA without P: 在分布式环境中,P 是不可避免的,天灾(某软公司的Azure被雷劈劈中)人祸(某里公司 A 和 B 机房之间的光缆被挖断)都能导致PCP without A:相当于每个写请求都须在Server之前强一致。P (分区)会导致同步时间无限延长。这个是可以保证的。例如数据库的分布式事务,两阶段提交,三阶段提交等AP without C: 当网络分区发生,A 和 B 集群失去联系。为了保证高可用,系统在写入时,系统写入部分节点就会返回成功,这会导致在一定时间之内,客户端从不同的机器上面读取到的是数据是不一样的。例如 redis 主从异步复制架构,当 master down 掉,系统会切换到 slave,由于是异步复制,salve 不是最新的数据,会导致一致性的问题。二、一致性协议2.1 两阶段提交协议(2PC)二阶段提交( Two-phaseCommit )是指,在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法( Algorithm )。通常,二阶段提交也被称为是一种协议( Protocol )。在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的 ACID 特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。2.1.1 两种角色协调者参与者2.1.2处理阶段询问投票阶段:事务协调者给每个参与者发送 Prepare 消息,参与者受到消息后,,要么在本地写入 redo 和 undo 日志成功后,返回同意的消息,否者一个终止事务的消息。执行初始化(执行提交):协调者在收到所有参与者的消息后,如果有一个返回终止事务,那么协调者给每个参与者发送回滚的指令。否者发送 commit 消息2.1.3 异常情况处理协调者故障:备用协调者接管,并查询参与者执行到什么地址参与者故障:协调者会等待他重启然后执行协调者和参与者同时故障:协调者故障,然后参与者也故障。例如:有机器 1,2,3,4。其中 4 是协调者,1,2,3是参与者 4 给1,2 发完提交事务后故障了,正好3这个时候也故障了,注意这是 3 是没有提交事务数据的。在备用协调者启动了,去询问参与者,由于3死掉了,一直不知道它处于什么状态(接受了提交事务,还是反馈了能执行还是不能执行 3 个状态)。面对这种情况,2PC,是不能解决的,要解决需要下文介绍的 3PC。2.1.4缺点同步阻塞问题:由于所有参与的节点都是事务阻塞型的,例如update table set status=1 where current_day=20181103,那么参与者table表的current_day=20181103的记录都会被锁住,其他的要修改current_day=20181103行的事务,都会被阻塞单点故障阻塞其他事务:协调者再执行提交的阶段 down 掉,所有的参与者出于锁定事务资源的状态中。无法完成相关的事务操作。参与者和协调者同时 down 掉:协调者在发送完 commit 消息后 down 掉,而唯一接受到此消息的参与者也 down 掉了。新协调者接管,也是一个懵逼的状态,不知道此条事务的状态。无论提交或者回滚都是不合适的。这个是两阶段提交无法改变的2.2 三阶段提交协议(3PC)2PC 当时只考虑如果单机故障的情况,是可以勉强应付的。当遇到协调者和参与者同时故障的话,2PC 的理论是不完善的。此时 3PC 登场。3PC 就是对 2PC 漏洞的补充协议。主要改动两点在 2PC 的第一阶段和第二阶段插入一个准备阶段,做到就算参与者和协调者同时故障也不阻塞,并且保证一致性。在协调者和参与者之间引入超时机制2.2.1 处理的三个阶段事务询问阶段( can commit 阶段):协调者向参与者发送 commit 请求,然后等待参与者反应。这个和 2PC 阶段不同的是,此时参与者没有锁定资源,没有写 redo,undo,执行回滚日志。回滚代价低事务准备阶段 (pre commit):如果参与者都返回ok,那么就发送Prepare消息,参与者本地执行redo和undo日志。否者就向参与者提交终止(abort)事务的请求。如果再发送Prepare消息的时候,等待超时,也会向参与者提交终止事务的请求。执行事务阶段(do commit):如果所有发送Prepare都返回成功,那么此时变为执行事务阶段,向参与者发送commit事务的消息。否者回滚事务。在此阶段参与者如果在一定时间内没有收到docommit消息,触发超时机制,会自己提交事务。此番处理的逻辑是,能够进入此阶段,说明在事务询问阶段所有节点都是好的。即使在提交的时候部分失败,有理由相信,此时大部分节点都是好的。是可以提交的2.2.2 缺点不能解决网络分区的导致的数据不一致的问题:例如 15 五个参与者节点,1,2,3 个节点在A机房,4,5 节点在 B 机房。在pre commit阶段,15 个节点都收到 Prepare 消息,但是节点1执行失败。协调者向15个节点发送回滚事务的消息。但是此时A,B机房的网络分区。13 号节点会回滚。但是 45 节点由于没收到回滚事务的消息,而提交了事务。待网络分区恢复后,会出现数据不一致的情况。不能解决 fail-recover 的问题:由于 3PC 有超时机制的存在,2PC 中未解决的问题,参与者和协调者同时 down 掉,也就解决了。一旦参与者在超时时间内没有收到协调者的消息,就会自己提交。这样也能避免参与者一直占用共享资源。但是其在网络分区的情况下,不能保证数据的一致性2.3 Paxos协议像 2PC 和 3PC 都需要引入一个协调者的角色,当协调者 down 掉之后,整个事务都无法提交,参与者的资源都出于锁定的状态,对于系统的影响是灾难性的,而且出现网络分区的情况,很有可能会出现数据不一致的情况。有没有不需要协调者角色,每个参与者来协调事务呢,在网络分区的情况下,又能最大程度保证一致性的解决方案呢。此时 Paxos 出现了。Paxos 算法是 Lamport 于 1990 年提出的一种基于消息传递的一致性算法。由于算法难以理解起初并没有引起人们的重视,Lamport在八年后重新发表,即便如此Paxos算法还是没有得到重视。2006 年 Google 的三篇论文石破天惊,其中的 chubby 锁服务使用Paxos 作为 chubbycell 中的一致性,后来才得到关注。2.3.1 解决了什么问题Paxos 协议是一个解决分布式系统中,多个节点之间就某个值(提案)达成一致(决议)的通信协议。它能够处理在少数节点离线的情况下,剩余的多数节点仍然能够达成一致。即每个节点,既是参与者,也是决策者2.3.2 两种角色(两者可以是同一台机器)Proposer:提议提案的服务器Acceptor:批准提案的服务器由于 Paxos 和下文提到的 zookeeper 使用的 ZAB 协议过于相似,详细讲解参照下文,Zookeeper原理部分2.4 Raft协议Paxos 是论证了一致性协议的可行性,但是论证的过程据说晦涩难懂,缺少必要的实现细节,而且工程实现难度比较高广为人知实现只有 zk 的实现 zab 协议。然后斯坦福大学RamCloud项目中提出了易实现,易理解的分布式一致性复制协议 Raft。Java,C++,Go 等都有其对应的实现2.4.1 基本名词节点状态Leader(主节点):接受 client 更新请求,写入本地后,然后同步到其他副本中Follower(从节点):从 Leader 中接受更新请求,然后写入本地日志文件。对客户端提供读请求Candidate(候选节点):如果 follower 在一段时间内未收到 leader 心跳。则判断 leader 可能故障,发起选主提议。节点状态从 Follower 变为 Candidate 状态,直到选主结束termId:任期号,时间被划分成一个个任期,每次选举后都会产生一个新的 termId,一个任期内只有一个 leader。termId 相当于 paxos 的 proposalId。RequestVote:请求投票,candidate 在选举过程中发起,收到 quorum (多数派)响应后,成为 leader。AppendEntries:附加日志,leader 发送日志和心跳的机制election timeout:选举超时,如果 follower 在一段时间内没有收到任何消息(追加日志或者心跳),就是选举超时。2.4.2 特性Leader 不会修改自身日志,只会做追加操作,日志只能由Leader转向Follower。例如即将要down掉的Leader节点已经提交日志1,未提交日志 2,3。down 掉之后,节点 2 启动最新日志只有 1,然后提交了日志 4。好巧不巧节点 1 又启动了。此时节点 2 的编号 4 日志会追加到节点 1 的编号 1 日志的后面。节点 1 编号 2,3 的日志会丢掉。不依赖各个节点物理时序保证一致性,通过逻辑递增的 term-id 和 log-id 保证。2.4.3 选主契机在超时时间内没有收到 Leader 的心跳启动时2.4.4 选主过程如图raft-2所示,Raft将时间分为多个 term(任期),term 以连续的整数来标识,每个 term 表示一个选举的开始。例如Follower 节点 1。在 term1 和 term2 连接处的时间,联系不到Leader,将currentTerm编号加1,变成2,进入了到term2任期,在term2的蓝色部分选举完成,绿色部分正常工作。当然一个任期不一定能选出Leader,那么会将currentTerm继续加1,然后继续进行选举,例如图中的t3。选举的原则是,每一轮选举每个选民一张选票,投票的请求先到且选民发现候选人节点的日志id大于等于自己的,就会投票,否者不会投票。获得半数以上的票的节点成为主节点(注意这并不是说选出来的事务id一定是最大的,。例如下图raft-1af六个节点(正方形框里面的数字是选举的轮数term)。在第四轮选举中,a先发出投票,六台机器中,ae都会投a,即使f不投a,a也会赢得选举。)。如果没有事务id(如刚启动时),就遵循投票请求先来先头。然后Leader将最新的日志复制到各个节点,再对外提供服务。当然除了这些选举限制,还会有其他的情况。如commit限制等保证,Leader选举成功一定包含所有的commit和log2.4.5 日志复制过程raft日志写入过程,主节点收到一个x=1的请求后,会写入本地日志,然后将x=1的日志广播出去,follower如果收到请求,会将日志写入本地 log ,然后返回成功。当 leader 收到半数以上的节点回应时,会将此日志的状态变为commit,然后广播消息让 follwer 提交日志。节点在 commit 日志后,会更新状态机中的 logindex 。firstLogIndex/lastLogIndex 为节点中开始和结束的索引位置(包含提交,未提交,写入状态机)commitIndex:已提交的索引。applyIndex:已写入状态机中的索引日志复制的本质是让 follwer 和 Leader 的已提交的日志顺序和内容都完全一样,用于保证一致性。具体的原则就是原则1:两个日志在不同的 raft 节点中,如果有两个相同的 term 和 logIndex,则保证两个日志的内容完全一样。原则2:两段日志在不同的 raft 节点中,如果起始和终止的的 term,logIndex 都相同,那么两段日志中日志内容完全一样。如何保证第一个原则只需要在创建 logIndex 的时候使用新的 logIndex,保证 logIndex 的唯一性。而且创建之后不去更改。那么在 leader 复制到 follwer 之后,logIndex,term 和日志内容都没变。第二个原则,在 Leader 复制给 Follower 时,要传递当前最新日志 currenTermId 和currentLogIndex,以及上一条日志 preCurrentTermId 和 preCurrentLogIndex。如图raft-1,在 d 节点,term7,logIndex12。在给节点节点 a 同步时,发送(term7,logIndex11),(term7,logIndex12),a 节点没有找到(term7,logIndex11)的日志,会让Leader,d 节点重新发送。d 节点会重新发(term6,logIndex10)(term7,logIndex11),还是没有(term6,logIndex10)的日志,依然会拒绝同步。接着发(term6,logIndex9)(term6,logIndex10)。现在a节点有了(term6,logIndex9)。那么 leader节点就会将(term6,logIndex9) ~ (term7,logIndex11)日志内容给节点 a,节点 a 将会和节点d有一样的日志。三、Zookeeper 原理3.1 概述Google 的粗粒度锁服务 Chubby 的设计开发者 Burrows 曾经说过:“所有一致性协议本质上要么是 Paxos 要么是其变体”。Paxos 虽然解决了分布式系统中,多个节点就某个值达成一致性的通信协议。但是还是引入了其他的问题。由于其每个节点,都可以提议提案,也可以批准提案。当有三个及以上的 proposer 在发送 prepare 请求后,很难有一个 proposer 收到半数以上的回复而不断地执行第一阶段的协议,在这种竞争下,会导致选举速度变慢。所以 zookeeper 在 paxos 的基础上,提出了 ZAB 协议,本质上是,只有一台机器能提议提案(Proposer),而这台机器的名称称之为 Leader 角色。其他参与者扮演 Acceptor 角色。为了保证 Leader 的健壮性,引入了 Leader 选举机制。ZAB协议还解决了这些问题在半数以下节点宕机,依然能对台提供服务客户端所有的写请求,交由 Leader 来处理。写入成功后,需要同步给所有的 follower 和 observerleader 宕机,或者集群重启。需要确保已经再 Leader 提交的事务最终都能被服务器提交,并且确保集群能快速回复到故障前的状态3.2 基本概念基本名词数据节点(dataNode):zk 数据模型中的最小数据单元,数据模型是一棵树,由斜杠( / )分割的路径名唯一标识,数据节点可以存储数据内容及一系列属性信息,同时还可以挂载子节点,构成一个层次化的命名空间。事务及 zxid:事务是指能够改变 Zookeeper 服务器状态的操作,一般包括数据节点的创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每个事务请求,zk 都会为其分配一个全局唯一的事务 ID,即 zxid,是一个 64 位的数字,高 32 位表示该事务发生的集群选举周期(集群每发生一次 leader 选举,值加 1),低 32 位表示该事务在当前选择周期内的递增次序(leader 每处理一个事务请求,值加 1,发生一次 leader 选择,低 32 位要清 0)。事务日志:所有事务操作都是需要记录到日志文件中的,可通过 dataLogDir 配置文件目录,文件是以写入的第一条事务 zxid 为后缀,方便后续的定位查找。zk 会采取“磁盘空间预分配”的策略,来避免磁盘 Seek 频率,提升 zk 服务器对事务请求的影响能力。默认设置下,每次事务日志写入操作都会实时刷入磁盘,也可以设置成非实时(写到内存文件流,定时批量写入磁盘),但那样断电时会带来丢失数据的风险。事务快照:数据快照是 zk 数据存储中另一个非常核心的运行机制。数据快照用来记录 zk 服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中,可通过 dataDir 配置文件目录。可配置参数 snapCount,设置两次快照之间的事务操作个数,zk 节点记录完事务日志时,会统计判断是否需要做数据快照(距离上次快照,事务操作次数等于snapCount/2snapCount 中的某个值时,会触发快照生成操作,随机值是为了避免所有节点同时生成快照,导致集群影响缓慢)。核心角色leader:系统刚启动时或者 Leader 崩溃后正处于选举状态;follower:Follower 节点所处的状态,Follower 与 Leader 处于数据同步阶段;observer:Leader 所处状态,当前集群中有一个 Leader 为主进程。节点状态LOOKING:节点正处于选主状态,不对外提供服务,直至选主结束;FOLLOWING:作为系统的从节点,接受主节点的更新并写入本地日志;LEADING:作为系统主节点,接受客户端更新,写入本地日志并复制到从节点3.3 常见的误区写入节点后的数据,立马就能被读到,这是错误的。 zk 写入是必须通过 leader 串行的写入,而且只要一半以上的节点写入成功即可。而任何节点都可提供读取服务。例如:zk,有 15 个节点,写入了一个最新的数据,最新数据写入到节点 13,会返回成功。然后读取请求过来要读取最新的节点数据,请求可能被分配到节点 45 。而此时最新数据还没有同步到节点45。会读取不到最近的数据。如果想要读取到最新的数据,可以在读取前使用 sync 命令。zk启动节点不能偶数台,这也是错误的。zk 是需要一半以上节点才能正常工作的。例如创建 4 个节点,半数以上正常节点数是 3。也就是最多只允许一台机器 down 掉。而 3 台节点,半数以上正常节点数是 2,也是最多允许一台机器 down 掉。4 个节点,多了一台机器的成本,但是健壮性和 3 个节点的集群一样。基于成本的考虑是不推荐的3.4 选举同步过程3.4.1 发起投票的契机节点启动节点运行期间无法与 Leader 保持连接,Leader 失去一半以上节点的连接3.4.2 如何保证事务ZAB 协议类似于两阶段提交,客户端有一个写请求过来,例如设置 /my/test 值为 1,Leader 会生成对应的事务提议(proposal)(当前 zxid为 0x5000010 提议的 zxid 为Ox5000011),现将set /my/test 1(此处为伪代码)写入本地事务日志,然后set /my/test 1日志同步到所有的follower。follower收到事务 proposal ,将 proposal 写入到事务日志。如果收到半数以上 follower 的回应,那么广播发起 commit 请求。follower 收到 commit 请求后。会将文件中的 zxid ox5000011 应用到内存中。上面说的是正常的情况。有两种情况。第一种 Leader 写入本地事务日志后,没有发送同步请求,就 down 了。即使选主之后又作为 follower 启动。此时这种还是会日志会丢掉(原因是选出的 leader 无此日志,无法进行同步)。第二种 Leader 发出同步请求,但是还没有 commit 就 down 了。此时这个日志不会丢掉,会同步提交到其他节点中。3.4.3 服务器启动过程中的投票过程现在 5 台 zk 机器依次编号 15节点 1 启动,发出去的请求没有响应,此时是 Looking 的状态节点 2 启动,与节点 1 进行通信,交换选举结果。由于两者没有历史数据,即 zxid 无法比较,此时 id 值较大的节点 2 胜出,但是由于还没有超过半数的节点,所以 1 和 2 都保持 looking 的状态节点 3 启动,根据上面的分析,id 值最大的节点 3 胜出,而且超过半数的节点都参与了选举。节点 3 胜出成为了 Leader节点 4 启动,和 1~3 个节点通信,得知最新的 leader 为节点 3,而此时 zxid 也小于节点 3,所以承认了节点 3 的 leader 的角色节点 5 启动,和节点 4 一样,选取承认节点 3 的 leader 的角色3.4.4 服务器运行过程中选主过程1.节点 1 发起投票,第一轮投票先投自己,然后进入 Looking 等待的状态2.其他的节点(如节点 2 )收到对方的投票信息。节点 2 在 Looking 状态,则将自己的投票结果广播出去(此时走的是上图中左侧的 Looking 分支);如果不在 Looking 状态,则直接告诉节点 1 当前的 Leader 是谁,就不要瞎折腾选举了(此时走的是上图右侧的 Leading/following 分支)3.此时节点 1,收到了节点 2 的选举结果。如果节点 2 的 zxid 更大,那么清空投票箱,建立新的投票箱,广播自己最新的投票结果。在同一次选举中,如果在收到所有节点的投票结果后,如果投票箱中有一半以上的节点选出了某个节点,那么证明 leader 已经选出来了,投票也就终止了。否则一直循环zookeeper 的选举,优先比较大 zxid,zxid 最大的节点代表拥有最新的数据。如果没有 zxid,如系统刚刚启动的时候,则比较机器的编号,优先选择编号大的3.5 同步的过程在选出 Leader 之后,zk 就进入状态同步的过程。其实就是把最新的 zxid 对应的日志数据,应用到其他的节点中。此 zxid 包含 follower 中写入日志但是未提交的 zxid 。称之为服务器提议缓存队列 committedLog 中的 zxid。同步会完成三个 zxid 值的初始化。peerLastZxid:该 learner 服务器最后处理的 zxid。minCommittedLog:leader服务器提议缓存队列 committedLog 中的最小 zxid。maxCommittedLog:leader服务器提议缓存队列 committedLog 中的最大 zxid。系统会根据 learner 的peerLastZxid和 leader 的minCommittedLog,maxCommittedLog做出比较后做出不同的同步策略3.5.1 直接差异化同步场景:peerLastZxid介于minCommittedLogZxid和maxCommittedLogZxid间此种场景出现在,上文提到过的,Leader 发出了同步请求,但是还没有 commit 就 down 了。 leader 会发送 Proposal 数据包,以及 commit 指令数据包。新选出的 leader 继续完成上一任 leader 未完成的工作。例如此刻Leader提议的缓存队列为 0x20001,0x20002,0x20003,0x20004,此处learn的peerLastZxid为0x20002,Leader会将0x20003和0x20004两个提议同步给learner3.5.2 先回滚在差异化同步/仅回滚同步此种场景出现在,上文提到过的,Leader写入本地事务日志后,还没发出同步请求,就down了,然后在同步日志的时候作为learner出现。例如即将要 down 掉的 leader 节点 1,已经处理了 0x20001,0x20002,在处理 0x20003 时还没发出提议就 down 了。后来节点 2 当选为新 leader,同步数据的时候,节点 1 又神奇复活。如果新 leader 还没有处理新事务,新 leader 的队列为,0x20001, 0x20002,那么仅让节点 1 回滚到 0x20002 节点处,0x20003 日志废弃,称之为仅回滚同步。如果新 leader 已经处理 0x30001 , 0x30002 事务,那么新 leader 此处队列为0x20001,0x20002,0x30001,0x30002,那么让节点 1 先回滚,到 0x20002 处,再差异化同步0x30001,0x30002。3.5.3 全量同步peerLastZxid小于minCommittedLogZxid或者leader上面没有缓存队列。leader直接使用SNAP命令进行全量同步四、使用 Raft + RocksDB 有赞分布式 KV 存储服务当前开源的缓存 kv 系统,大都是 AP 系统,例如设置主从同步集群 redis,master 异步同步到 slave。虽然在 master 停止服务后,slave 会顶上来。但是在 master 写入了数据,但是还没来得及同步到 slave 就 down 了,然后 slave 被选为主节点继续对外提供服务的情况下,会丢失部分数据。这对于要求强一致性的系统来说是不可接受的。例如很多场景下 redis 做分布式锁,有天然的缺陷在里面,如果 master 停止服务,这个锁不很不可靠的,虽然出现的几率很小,但一旦出现,将是致命的错误。为了实现 CP 的 KV 存储系统,且要兼容现有的 redis 业务。有赞开发了 ZanKV(先已开源ZanRedisDB)。底层的存储结构是 RocksDB(底层采用 LSM 数据结构)。一个set x=1的会通过 redis protocol 协议传输,内容会通过 Raft 协议,同步写入到其他的节点的 RocksDB。有了Raft 理论的加持,RocksDB优秀的存储性能,即使遇到网络分区,master 节点 down 掉, slave 节点 down 掉,等一系列异常情况,其都能轻松应对。在扩容方面,系统用选择维护映射表的方式来建立分区和节点的关系,映射表会根据一定的算法并配合灵活的策略生成,来达到方便扩容。具体原理可参见使用开源技术构建有赞分布式KV存储服务五、总结本文从三个方面介绍了一致性,首先是描述分布架构中的核心理论-CAP,以及其简单的证明。第二部分介绍了 CAP 里面协议,重点介绍了 Raft 协议。第三部分,重点介绍了常用的 zookeeper 原理。为了保证数据 commit 之后不可丢,系统都会采用(WAL write ahead log)(在每次修改数据之前先写操作内容日志,然后再去修改数据。即使修改数据时异常,也可以通过操作内容日志恢复数据)分布式存储系统中,是假设机器是不稳定,随时都有可能 down 掉的情况下来设计的。也就是说就算机器 down 掉了,用户写入的数据也不能丢,避免单点故障。为此每一份写入的数据,需要在多个副本中同时存放。例如 zk 节点数据复制,etcd 的数据复制。而复制数据给节点又会带来一致性的问题,例如主节点和从节点数据不一致改如何去同步数据。也会带来可用性的问题,如 leader 节点 down 掉,如何快速选主,恢复数据等。好在已有成熟的理论如 Paxos 协议,ZAB 协议 Raft 协议等做为支撑。参考文章/书籍《从 paxos 到 Zookeeper 分布式一致性原理与实践》使用开源技术构建有赞分布式KV存储服务关于分布式事务、两阶段提交协议、三阶提交协议zookeeper leader 和 learner 的数据同步浅析Zookeeper的一致性原理图解分布式协议- Raft [Raft协议详解](https://zhuanlan.zhihu.com/p/… ...

February 25, 2019 · 4 min · jiezi

Druid 在有赞的实践

一、Druid介绍Druid 是 MetaMarket 公司研发,专为海量数据集上的做高性能 OLAP (OnLine Analysis Processing)而设计的数据存储和分析系统,目前Druid 已经在Apache基金会下孵化。Druid的主要特性:交互式查询( Interactive Query ): Druid 的低延迟数据摄取架构允许事件在它们创建后毫秒内查询,因为 Druid 的查询延时通过只读取和扫描有必要的元素被优化。Druid 是列式存储,查询时读取必要的数据,查询的响应是亚秒级响应。高可用性( High Available ):Druid 使用 HDFS/S3 作为 Deep Storage,Segment 会在2个 Historical 节点上进行加载;摄取数据时也可以多副本摄取,保证数据可用性和容错性。可伸缩( Horizontal Scalable ):Druid 部署架构都可以水平扩展,增加大量服务器来加快数据摄取,以及保证亚秒级的查询服务并行处理( Parallel Processing ): Druid 可以在整个集群中并行处理查询丰富的查询能力( Rich Query ):Druid支持 Scan、 TopN、 GroupBy、 Approximate 等查询,同时提供了2种查询方式:API 和 SQLDruid常见应用的领域:网页点击流分析网络流量分析监控系统、APM数据运营和营销BI分析/OLAP二、为什么我们需要用 Druid有赞作为一家 SaaS 公司,有很多的业务的场景和非常大量的实时数据和离线数据。在没有是使用 Druid 之前,一些 OLAP 场景的场景分析,开发的同学都是使用 SparkStreaming 或者 Storm 做的。用这类方案会除了需要写实时任务之外,还需要为了查询精心设计存储。带来问题是:开发的周期长;初期的存储设计很难满足需求的迭代发展;不可扩展。 在使用 Druid 之后,开发人员只需要填写一个数据摄取的配置,指定维度和指标,就可以完成数据的摄入;从上面描述的 Druid 特性中我们知道,Druid 支持 SQL,应用 APP 可以像使用普通 JDBC 一样来查询数据。通过有赞自研OLAP平台的帮助,数据的摄取配置变得更加简单方便,一个实时任务创建仅仅需要10来分钟,大大的提高了开发效率。2.1、Druid 在有赞使用场景系统监控和APM:有赞的监控系统(天网)和大量的APM系统都使用了 Druid 做数据分析数据产品和BI分析:有赞 SaaS 服务为商家提供了有很多数据产品,例如:商家营销工具,各类 BI 报表实时OLAP服务:Druid 为风控、数据产品等C端业务提供了实时 OLAP 服务三、Druid的架构Druid 的架构是 Lambda 架构,分成实时层( Overlord、 MiddleManager )和批处理层( Broker 和 Historical )。主要的节点包括(PS: Druid 的所有功能都在同一个软件包中,通过不同的命令启动):Coordinator 节点:负责集群 Segment 的管理和发布,并确保 Segment 在 Historical 集群中的负载均衡Overlord 节点:Overlord 负责接受任务、协调任务的分配、创建任务锁以及收集、返回任务运行状态给客户端;在Coordinator 节点配置 asOverlord,让 Coordinator 具备 Overlord 功能,这样减少了一个组件的部署和运维MiddleManager 节点:负责接收 Overlord 分配的索引任务,创建新启动Peon实例来执行索引任务,一个MiddleManager可以运行多个 Peon 实例Broker 节点:负责从客户端接收查询请求,并将查询请求转发给 Historical 节点和 MiddleManager 节点。Broker 节点需要感知 Segment 信息在集群上的分布Historical 节点:负责按照规则加载非实时窗口的SegmentRouter 节点:可选节点,在 Broker 集群之上的API网关,有了 Router 节点 Broker 不在是单点服务了,提高了并发查询的能力四、有赞 OLAP 平台的架构和功能解析4.1 有赞 OLAP 平台的主要目标:最大程度的降低实时任务开发成本:从开发实时任务需要写实时任务、设计存储,到只需填写配置即可完成实时任务的创建提供数据补偿服务,保证数据的安全:解决因为实时窗口关闭,迟到数据的丢失问题提供稳定可靠的监控服务:OLAP 平台为每一个 DataSource 提供了从数据摄入、Segment 落盘,到数据查询的全方位的监控服务4.2 有赞 OLAP 平台架构有赞 OLAP 平台是用来管理 Druid 和周围组件管理系统,OLAP平台主要的功能:Datasource 管理Tranquility 配置和实例管理:OLAP 平台可以通过配置管理各个机器上 Tranquility 实例,扩容和缩容数据补偿管理:为了解决数据迟延的问题,OLAP 平台可以手动触发和自动触发补偿任务Druid SQL查询: 为了帮助开发的同学调试 SQL,OLAP 平台集成了 SQL 查询功能监控报警4.2 Tranquility 实例管理OLAP 平台采用的数据摄取方式是 Tranquility工具,根据流量大小对每个 DataSource 分配不同 Tranquility 实例数量; DataSource 的配置会被推送到 Agent-Master 上,Agent-Master 会收集每台服务器的资源使用情况,选择资源丰富的机器启动 Tranquility 实例,目前只要考虑服务器的内存资源。同时 OLAP 平台还支持 Tranquility 实例的启停,扩容和缩容等功能。4.3 解决数据迟延问题——离线数据补偿功能流式数据处理框架都会有时间窗口,迟于窗口期到达的数据会被丢弃。如何保证迟到的数据能被构建到 Segment 中,又避免实时任务窗口长期不能关闭。我们研发了 Druid 数据补偿功能,通过 OLAP 平台配置流式 ETL 将原始的数据存储在 HDFS 上,基于 Flume 的流式 ETL 可以保证按照 Event 的时间,同一小时的数据都在同一个文件路径下。再通过 OLAP 平台手动或者自动触发 Hadoop-Batch 任务,从离线构建 Segment。基于 Flume 的 ETL 采用了 HDFS Sink 同步数据,实现了 Timestamp 的 Interceptor,按照 Event 的时间戳字段来创建文件(每小时创建一个文件夹),延迟的数据能正确归档到相应小时的文件中。4.4 冷热数据分离随着接入的业务增加和长期的运行时间,数据规模也越来越大。Historical 节点加载了大量 Segment 数据,观察发现大部分查询都集中在最近几天,换句话说最近几天的热数据很容易被查询到,因此数据冷热分离对提高查询效率很重要。Druid 提供了Historical 的 Tier 分组机制与数据加载 Rule 机制,通过配置能很好的将数据进行冷热分离。 首先将 Historical 群进行分组,默认的分组是"_default_tier",规划少量的 Historical 节点,使用 SATA 盘;把大量的 Historical 节点规划到 “hot” 分组,使用 SSD 盘。然后为每个 DataSource 配置加载 Rule :rule1: 加载1份最近30天的 Segment 到 “hot” 分组;rule2: 加载2份最近6个月的 Segment 到 “_default_tier” 分组;rule3: Drop 掉之前的所有 Segment(注:Drop 只影响 Historical 加载 Segment,Drop 掉的 Segment 在 HDFS 上仍有备份){“type”:“loadByPeriod”,“tieredReplicants”:{“hot”:1}, “period”:“P30D”} {“type”:“loadByPeriod”,“tieredReplicants”:{"_default_tier":2}, “period”:“P6M”} {“type”:“dropForever”}提高 “hot"分组集群的 druid.server.priority 值(默认是0),热数据的查询都会落到 “hot” 分组。4.5 监控与报警Druid 架构中的各个组件都有很好的容错性,单点故障时集群依然能对外提供服务:Coordinator 和 Overlord 有 HA 保障;Segment 是多副本存储在HDFS/S3上;同时 Historical 加载的 Segment 和 Peon 节点摄取的实时部分数据可以设置多副本提供服务。同时为了能在节点/集群进入不良状态或者达到容量极限时,尽快的发出报警信息。和其他的大数据框架一样,我们也对 Druid 做了详细的监控和报警项,分成了2个级别:基础监控包括各个组件的服务监控、集群水位和状态监控、机器信息监控业务监控业务监控包括:实时任务创建、数据摄取 TPS、消费迟延、持久化相关、查询 RT/QPS 等的关键指标,有单个 DataSource 和全局的2种不同视图;同时这些监控项都有设置报警项,超过阈值触发报警提醒。业务指标的采集是大部分是通过 Druid 框架自身提供的 Metrics 和 Alerts 信息,然后流入到 Kafka / OpenTSDB 等组件,通过流数据分析获得我们想要的指标。4.6 部署架构Historical 集群的部署和4.4节中描述的数据冷热分离相对应,用 SSD 集群存储最近的N天的热数据(可调节 Load 的天数),用相对廉价的 Sata 机型存储更长时间的历史冷数据,同时充分利用 Sata 的 IO 能力,把 Segment Load到不同磁盘上;在有赞有很多的收费业务,我们在硬件层面做隔离,保证这些业务在查询端有足够的资源;在接入层,使用 Router 做路由,避免了 Broker 单点问题,也能很大的程度集群查询吞吐量;在 MiddleManager 集群,除了部署有 Index 任务(内存型任务)外,我们还混合部署了部分流量高 Tranquility 任务(CPU型任务),提高了 MiddleManager 集群的资源利用率。4.7 贡献开源社区在有赞业务查询方式一般是 SQL On Broker/Router,我们发现一旦有少量慢查询的情况,客户端会出现查询不响应的情况,而且连接越来越难获取到。登录到Broker 的服务端后发现,可用连接数量急剧减少至被耗尽,同时出现了大量的 TCP Close_Wait。用 jstack 工具排查之后发现有 deadlock 的情况,具体的 Stack 请查看 ISSUE-6867。经过源码排查之后发现,DruidConnection为每个 Statement 注册了回调。在正常的情况下 Statement 结束之后,执行回调函数从 DruidConnection 的 statements 中 remove 掉自己的状态;如果有慢查询的情况(超过最长连接时间或者来自客户端的Kill),connection 会被强制关闭,同时关闭其下的所有 statements ,2个线程(关闭connection的线程和正在退出 statement 的线程)各自拥有一把锁,等待对方释放锁,就会产生死锁现象,连接就会被马上耗尽。// statement线程退出时执行的回调函数final DruidStatement statement = new DruidStatement( connectionId, statementId, ImmutableSortedMap.copyOf(sanitizedContext), () -> { // onClose function for the statement synchronized (statements) { log.debug(“Connection[%s] closed statement[%s].”, connectionId, statementId); statements.remove(statementId); } });// 超过最长连接时间的自动killreturn connection.sync( exec.schedule( () -> { log.debug(“Connection[%s] timed out.”, connectionId); closeConnection(new ConnectionHandle(connectionId)); }, new Interval(DateTimes.nowUtc(), config.getConnectionIdleTimeout()).toDurationMillis(), TimeUnit.MILLISECONDS ));在排查清楚问题之后,我们也向社区提了 PR-6868 。目前已经成功合并到 Master 分支中,将会 0.14.0 版本中发布。如果读者们也遇到这个问题,可以直接把该PR cherry-pick 到自己的分支中进行修复。五、挑战和未来的展望5.1 数据摄取系统目前比较常用的数据摄取方案是:KafkaIndex 和 Tranquility 。我们采用的是 Tranquility 的方案,目前 Tranquility 支持了 Kafka 和 Http 方式摄取数据,摄取方式并不丰富;Tranquility 也是 MetaMarket 公司开源的项目,更新速度比较缓慢,不少功能缺失,最关键的是监控功能缺失,我们不能监控到实例的运行状态,摄取速率、积压、丢失等信息。目前我们对 Tranquility 的实例管理支持启停,扩容缩容等操作,实现的方式和 Druid 的 MiddleManager 管理 Peon 节点是一样的。把 Tranquility 或者自研摄取工具转换成 Yarn 应用或者 Docker 应用,就能把资源调度和实例管理交给更可靠的调度器来做。5.2 Druid 的维表 JOIN 查询Druid 目前并不没有支持 JOIN查询,所有的聚合查询都被限制在单 DataSource 内进行。但是实际的使用场景中,我们经常需要几个 DataSource 做 JOIN 查询才能得到所需的结果。这是我们面临的难题,也是 Druid 开发团队遇到的难题。5.3 整点查询RT毛刺问题对于 C 端的 OLAP 查询场景,RT 要求比较高。由于 Druid 会在整点创建当前小时的 Index 任务,如果查询正好落到新建的 Index 任务上,查询的毛刺很大,如下图所示: 我们已经进行了一些优化和调整,首先调整 warmingPeriod 参数,整点前启动 Druid 的 Index 任务;对于一些 TPS 低,但是 QPS 很高的 DataSource ,调大 SegmentGranularity,大部分 Query 都是查询最近24小时的数据,保证查询的数据都在内存中,减少新建 Index 任务的,查询毛刺有了很大的改善。尽管如此,离我们想要的目标还是一定的差距,接下去我们去优化一下源码。5.4 历史数据自动Rull-Up现在大部分 DataSource 的 Segment 粒度( SegmentGranularity )都是小时级的,存储在 HDFS 上就是每小时一个Segment。当需要查询时间跨度比较大的时候,会导致Query很慢,占用大量的 Historical 资源,甚至出现 Broker OOM 的情况。如果创建一个 Hadoop-Batch 任务,把一周前(举例)的数据按照天粒度 Rull-Up 并且 重新构建 Index,应该会在压缩存储和提升查询性能方面有很好的效果。关于历史数据 Rull-Up 我们已经处于实践阶段了,之后会专门博文来介绍。最后打个小广告,有赞大数据团队基础设施团队,主要负责有赞的数据平台 (DP), 实时计算 (Storm, Spark Streaming, Flink),离线计算 (HDFS, YARN, HIVE, SPARK SQL),在线存储(HBase),实时 OLAP (Druid) 等数个技术产品,欢迎感兴趣的小伙伴联系 zhaojiandong@youzan.com ...

February 25, 2019 · 3 min · jiezi

windows下使用nginx调试简介

安装使用nginx是一个反向代理服务器,在web开发调试中经常用到,写一个简单的使用说明和总结。1. 下载点击官网下载地址 下载对应版本的nginx并解压2. 配置在解压的目录下找到conf/nginx.conf文件添加所需监听和代理的server# 项目名称 server { listen 80; server_name example.com;//监听的host #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_set_header host $host; proxy_pass http://127.0.0.1:8092; // 要代理的地址 } }3. 常用命令start nginx // 启动nginxnginx -s stop // 快速停止nginx,可能并不保存相关信息nginx -s quit // 有序的停止nginx,并保存相关信息nginx -s reload // 重新载入配置,修改完配置文件后使用此命令重启原理简析在说明使用nginx的优点之前,先说说另一种常用的调试方案:修改hosts文件。修改host的原理是:对主机添加特定dns解析规则,将指定域名解析至某个特定ip。例如:在hosts文件里写了这么一行:127.0.0.1 example.com,接下来在浏览器输入www.example.com后:主机检测hosts文件发现有对应规则(如果没有对应规则就走正常dns解析服务),于是向IP127.0.0.1:80(默认端口)发送http请求,请求报文上GET / HTTP/1.1,host:example.com。如果此时本地80端口确实启动了某个服务,则处理该对应的请求。如果我们正好是在本地的80端口运行这个要调试项目,那自然没有问题。但是可能有意外情况:本地项目不跑在80端口要监听的host项目不在80端口,例如一般线上正式环境的项目使用的是https协议,请求默认端口就是443,此时就需要nginx来解决问题,nginx的作用用一句话描述:将发送至特定host的请求转发到指定的代理地址上。举一个实际的例子,首先在hosts添加一行127.0.0.1 construct-courses.pre1.web.nd后,可以在浏览器看到这样的请求:首先看图上Grnrral部分的remote address字段,即前文的第一步骤完成,本地的80端口接收到一个http请求,host为construct-courses.pre1.web.nd。接着在nginx的配置文件中增加:server { listen 80; server_name construct-courses.pre1.web.nd; location / { proxy_set_header host $host; proxy_pass http://127.0.0.1:8092; } }在conf中配置如上图代码并启动nginx后,nginx检查到server_name construct-courses.pre1.web.nd接收到了请求,根据匹配规则,将请求转发到指定的 proxy_pass 即 http://127.0.0.1:8092上。至此,请求example.com实际变成了请求http://127.0.0.1:8092在本地8092端口启动实际项目,就可以发现在浏览器访问construct-courses.pre1.web.nd时,运行的正是我们的实际项目整个过程至此结束惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址

February 14, 2019 · 1 min · jiezi

写个 Go 时间交并集小工具

示例代码(含测试)在这里需求在甘特图的场景下,我们经常会遇到这种情况,五位员工A, B, C, D, E,可能他们的工作都是并行的,我们需要计算某段时间内他们总的工作时长。我们不能简单得把五个人的工作时间都加起来,因为当中会有重叠的部分。所以这时候我们就需要一个计算时间交并集的工具。思路将一组离散的时间段按照开始时间,从小到大排序。像这样[{2 7} {4 11} {10 19} {10 30} {16 18} {19 29} {23 35} {24 42} {25 30} {27 49}]我这里将时间用十分小的秒来代替,方便理解。循环排序后的数组,如果下一个时间段开始时间介于上个时间段的开始时间和结束时间之间,那么就进行合并,否则就分离。可以看到我们这里有两个关键动作,合并,分离,而这个就是我们要实现的核心代码。实现一段连续的工作时间都会有两个点,开始时间和结束时间。所以我们可以把这个时间结构设计成:type T struct { Start int64 End int64}而一个人的工作时间是由多个 T 组成的,所以我们在定义一个切片类型type TSlice []T为了能顺序合并时间,我们需要将TSlice进行排序。我们知道 Go 中有个 sort 包,我们只需要实现 sort 类型的接口,就能实现 TSlice 的排序了。我们实现下:func (t TSlice) Len() int { return len(t) }func (t TSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }func (t TSlice) Less(i, j int) bool { return t[i].Start < t[j].Start }三个方法分别是,长度、交换位置、比小。这样一来,我们就能直接用 sort.Stable() 稳定排序,对我们的时间段切片排序了。好,接下来我们实现并集的方法,我们取名为 Union:func (t TSlice) Union() TSlice { // 新建一个空的时间切片 var s TSlice // 如果有至少两个是时间段,我们才排序,否则直接返回 if len(t) > 1 { // @todo 合并逻辑 } return s}Union 方法将会返回一个同样的 TSlice 时间切片,只不过是经过并集处理的。一旦 t 中的时间段个数大于1,我们就要执行处理逻辑了:if len(t) > 1 { sort.Stable(t) s = append(s, t[0]) // @todo 循环比较合并}我们先对时间切片进行排序,然后把第一个时间段作为第一个元素放进我们的结果 TSlice 中,好让我们开始进行循坏的比较。if len(t) > 1 { sort.Stable(t) s = append(s, t[0]) for k, v := range t { // 如果开始时间大于结束时间,那其实是错误数据,但是我们这里正常返回 // 你可以根据自己的需要定制错误处理逻辑 if v.Start > v.End { return s } // 第一组元素我们不做任何操作 if k == 0 { continue } // 当开始时间介于上一个时间段的开始时间和结束时间之间 if v.Start >= s[len(s)-1].Start && v.Start <= s[len(s)-1].End { // 合并 if v.End > s[len(s)-1].End { s[len(s)-1].End = v.End } // 如果大于上一个时间段的结束时间 } else if v.Start > s[len(s)-1].End { // 分离 inner := T{Start: v.Start, End: v.End} s = append(s, inner) } } }来张图其实就清楚了:可以看到最后输出的也是一个 TSlice 类型。上面就是 union,求并集的过程,那交集的?其实交集也很简单,如果两个时间段相交,我们只要判断:开始时间取最大的那个,结束时间取两个时间段中最小的那个。func (t TSlice) Intersect() TSlice { var s TSlice if len(t) > 1 { sort.Stable(t) s = append(s, t[0]) for k, v := range t { if v.Start > v.End { return s } if k == 0 { continue } // 两个时间段相交 if v.Start >= s[0].Start && v.Start <= s[0].End { // 开始时间取最大的那个 s[0].Start = v.Start // 结束时间取最小的那个 if v.End <= s[0].End { s[0].End = v.End } } else { return s[:0] } } } return s}一样,我们来个图:需要注意的是,这个求交集的结果是全相交–只有当所有时间段都有共同时间才会有结果。这样的需求在实际过程中用到的是不是不太多??所以我想是不是能够实现:一次相交,两次相交…的条件筛选。看看效果我们随机生成了一组时间切片func makeTimes(t int) TSlice { var set TSlice rand.Seed(time.Now().Unix()) for i := 0; i < t; i++ { randStart := rand.Int63n(50) randEnd := randStart + rand.Int63n(25) + 1 set = append(set, T{Start: randStart, End: randEnd}) } return set}testSet := makeTimes(10) // 生成10个时间段的时间切片res := testSet.Union() // 直接调用 Union() 或者 Intersect()输入数据为:[{10 21} {34 52} {49 54} {18 31} {26 44} {24 27} {43 51} {41 53} {20 41} {48 67}]输出结果:[{10 67}]结果还行~额外我发现在求并集的过程中,会要求求最终的时间之和,所以我们为 TSlice 加一个 Sum() 方法,就是简单的循环求和:func (t TSlice) Sum() (sum int64) { for i := 0; i < len(t); i++ { sum += t[i].End - t[i].Start } return sum} ...

February 1, 2019 · 2 min · jiezi

Anka——渐进式小程序开发工具集

渐进式小程序开发工具集。提供通用的开发函数库及组件,我们正努力使小程序开发过程变得愉快。如何使用安装CLIAnkaCLI 能在原生小程序语法之上提供 npm 包支持、管理页面与组件、在 Parser 的加持下还能在小程序项目中使用 Sass、PostCSS、TypeScript。甚至,你能通过编写插件实现一些特殊功能,关于如何编写插件请看这里。通过 npm 安装 AnkaCLI:$ npm install @anka-dev/cli -g关联文档如何使用 AnkaCLI功能示例及项目模板Github地址你可能需要的anka-brush —— 小程序 Canvas 操作anka-tracker —— 小程序打点库更多工具问题Anka 是小程序开发框架吗?Anka 是小程序开发工具集,AnkaCLI 是其中之一。我们可以根据实际需求引入 Anka 集合内的工具,比如 Canvas库 anka-brush、打点工具 anka-tracker。不同于 mpvue、wepy 和 Taro,Anka 不提供任何特殊的小程序开发语法,在保持原生状态的条件下引入所需的工具。当然,通过编写插件和解析器也能实现特殊功能。关于 AnkaCLI 如何工作请看这里。可以不安装 CLI 而单独使用其他工具吗?完全可以。Anka 集合下的工具都能单独使用,他们之间没有任何依赖(如果有则会特别指出)。问题反馈及建议我们非常乐意看见你将使用过程中遇见的缺陷反馈到 issue 区。也任何新的想法或需求也可以通过相同方式提出并列入讨论。Have a nice day ???? !

January 28, 2019 · 1 min · jiezi

CornerStone使用教程

前言不管你在什么公司,对我们程序员来说,都要使用源码版本控制工具,否则多人开发就成了很大的问题,而且源码一般都保存在公司的远程仓库中,这样也能保证写好的代码不会丢失。现在源码版本控制工具有两种:git和svn,使用git的团队应该是最多的,但是也有使用svn的,windows中svn客户端一般使用TortoiseSVN,mac中比较好用的当属CornerStone了,接下来主要介绍CornerStone的使用。使用教程安装由于Cornerstone是收费的,因此你可以去网上下载破解版,直接安装即可。配置远程仓库首先,打开CornerStone,在界面左下角出有一个“+”,点击后选择“Add Repository”。然后出现以下界面,选中SVN Server。其中,Server填写svn的服务器地址。如果主机后有端口路径,则Port填写相对应的端口。Path填写仓库访问路径。Title为名称,会显示在侧边栏中。Name和Password为你访问的用户名和密码。填写完后点击Add添加即可。注意:填写完后检查一下Path下面的合成的svn地址是不是 “svn://用户名@主机地址:端口号/路径” 格式。上传文件如果你想要将本地文件上传到svn仓库的话,可以直接将本地文件拖到repository的子文件夹中,或者点击软件上方的Import按钮,选择文件后点击import,填写提交信息即可。下载文件下载文件分为两种:Export和Check Out,Export后的项目不会与repository中的源文件相关联,是一个独立的版本。Check Out下来的文件会创建一个working copy,此文件与repository中源文件相关联,当有别人修改或是自己修改时,working copy会显示修改数量,白色数量为别人修改数量,灰色数量为自己修改数量,所以如果你是项目中的开发人员,可以选择check out。版本控制作为开发人员,我们要对代码进行版本控制,这时候就要将代码check out到本地。然后,当你要提交代码的时候,记得先要update代码,直到working copy不再显示白色圈,然后再commit自己的代码。暂时先写这些,以后要是遇到问题再添加。参考CornerStone的使用Cornerstone的使用

January 25, 2019 · 1 min · jiezi

优秀的命令行工具整理(三)

原作者:Darren Burns 授权 LeanCloud 翻译,作者:weakish@LeanCloud本文是「优秀的命令行工具」系列的第三篇。在这篇文章中,我将展示五个命令行工具,这些工具能助你更方便地解决常见问题。tig 交互式地浏览 git 仓库有了 tig,无需离开命令行,就能交互式地浏览 git 仓库。tig 的使用简单直观,提供了 stash、staging、log 等视图。感谢 Renato Suero [@renatosuero] 在 DEV 上向我推荐 tig。安装 tigmacOS (Homebrew): brew install tigPathPicker (fpp) 快速选择文件PathPicker 是由 Facebook 推出的命令行文件速选库。下面的动画摘自 PathPicker 文档。摘自PathPicker 官网:PathPicker 接受各种各样的输入 —— git 命令的输出,grep 结果,搜索 —— 几乎任何输入都支持。解析输入后,PathPicker 呈现出供你选择文件的美观界面。选中文件后,可以用你偏爱的编辑器打开,或执行任意命令。感谢 Nikolay Dubina (@nikolayid)推荐这一工具。安装 PathPickermacOS (Homebrew): brew install fpptldr 使用命令行工具的实用示例tldr 助你快速查看使用命令行工具的实际示例。简而言之,「tl;dr」版本的 man 页面。tldr 的例子由社区维护,存储于 tldr 的 GitHub 仓库。安装 tldr推荐使用 npm 安装:npm install -g tldrmacOS (Homebrew): brew install tldrgron 查看 JSONgron 将 JSON 文本转换为离散的赋值语句,以便查找。我特别喜欢组合 fzf 使用(我在本系列的第一篇文章中提到过这个工具),这样可以交互式地查看 API:gron 也可以用来转换 JSON 对象(例子)。不过这不是 gron 的主要使用场景,使用 [jq] 这样的专门工具解决这类任务大概更合适。安装 gronmacOS (Homebrew): brew install gronthefuck 快速修正命令行手误输入命令时拼错了,输入 fuck ,thefuck 会给出一些候选的正确命令。很不幸,这个命令的名称稍微有点黄暴,你也许想要起个别名。安装 thefuckmacOS (Homebrew): brew install thefuck额外福利:explainshell,解释命令如果你手头有一行很复杂的命令,想要了解它做了什么,又不想一个个查 man 或 tldr,那么你可以使用 explainshell:结语希望你对这篇文章中的工具感兴趣!想看更多类似内容,欢迎在 Twitter 和 DEV 上关注 @_darrenburns。 ...

January 23, 2019 · 1 min · jiezi

IntelliJ IDEA 18 周岁,吐血推进珍藏已久的必装插件

IntelliJ IDEA是目前最好最强最智能的Java IDE,前几天,他刚刚年满18岁。本文,给大家推荐几款我私藏已久的,自己经常使用的,可以提升代码效率的插件。IDEA插件简介常见的IDEA插件主要有如下几类:常用工具支持Java日常开发需要接触到很多常用的工具,为了便于使用,很多工具也有IDEA插件供开发使用,其中大部分已经在IDEA中默认集成了。例如maven、git、svn、tomcat、jetty、jrebel、Gradle等。功能增强还有些插件提供了一些IDE中不具有的功能,比如静态代码扫描、代码自动生成等。框架集成集成框架主要是为了提供框架定制的代码和配置的生成,以及快速的访问框架提供的功能。例如集成Spring框架,Mybatis框架等。UI定制化及优化UI定制化相关的插件主要提供一下个性化需求定制,例如修改编辑区的背景图片插件、修改代码颜色等。其他编程语言支持IDEA主要支持Java,为了使用其他语言,可以使用一些支持其他语言的插件,通过这些插件可以实现语法分析,配色主题,代码格式化和提示等功能。例如Go语言的支持的插件。个人或者公司特殊需求公司内部插件我的插件一览这里简单介绍一些笔者日常开发中使用到的插件。 以下是我的IDEA中自己安装的插件列表。这些插件都是我经过很长时间的使用之后最终保留下来的,都是值得安装的一些插件。其中被我打码的就是公司内部插件。IDEA插件安装IDEA的插件安装非常简单,对于很多插件来说,只要你知道插件的名字就可以在IDEA里面直接安装。Preferences—>Plugins—>查找所需插件—>Install或者Preferences—>Plugins—>Install plug from disk —>选择下载好的插件安装安装之后重启IDEA即可生效IDEA插件仓库IntelliJ IDEA激发了许多Java开发人员编写插件, IntelliJ IDEA Plugins(https://plugins.jetbrains.com… )中目前包含3000+个插件并且还在不断增长。基本功能作者目前使用的IntelliJ IDEA版本是专业版2018.2.4(Ultimate Edition)很多插件中提供的功能在这个版本中都已经集成进来了,这里简单说几个可能比较常用的,这几种功能就无需加装插件了。背景图片目前,IDEA支持设置背景图片。这对于广大程序员来说无疑是个好功能。整日对着枯燥的代码实在是会让人疲乏。要是可以设置一张美女图片的话。。。设置方法Ctrl+Shift+A(或者help -> find action)调用弹窗后输入Set Background Image在里面设定要设置为Image的图片,透明度调到15左右,保存即可。效果如下内置terminal目前IntelliJ IDEA已经有一个内置的terminal工具,可以方便的使用shell命令。内置support目前很多新版本的IntelliJ IDEA中,已经内置了很多support插件,比如我们常用的markdown support 、UML support 以及 android support等。PS:由于作者使用的是专业版,不知道社区版是否也会内置这些插件,如果没有内置的话,读者可以根据需要自行下载相关插件并安装。有了markdowm support插件以后,就可以直接预览md语法的内容了。必备插件Maven Helper目前,Java开发很多都在使用maven进行项目管理和自动构建。日常开发中,可能经常会遇到jar包冲突等问题,就需要通过查看maven依赖树来查查看依赖情况。这种方式不是很高效,这里推荐一个插件,安装之后,直接打开pom文件,即可查看依赖数,还能自动分析是否存在jar包冲突。一旦安装了Maven Helper插件,只要打开pom文件,就可以打开该pom文件的Dependency Analyzer视图(在文件打开之后,文件下面会多出这样一个tab)。进入Dependency Analyzer视图之后有三个查看选项,分别是Conflicts(冲突)、All Dependencies as List(列表形式查看所有依赖)、All Dependencies as Tree(树结构查看所有依赖)。并且这个页面还支持搜索。FindBugs-IDEAFindBugs很多人都并不陌生,Eclipse中有插件可以帮助查找代码中隐藏的bug,IDEA中也有这款插件。使用方法很简单,就是可以对多种级别的内容进行finbugs分析完之后会有一个视图进行提示,详细的说明是哪种问题。 按照提示解决完问题之后再执行findbug查看情况即可。阿里巴巴代码规约检测2017年10月14日杭州云栖大会,Java代码规约扫描插件全球首发仪式正式启动,规范正式以插件形式公开走向业界,引领Java语言的规范之路。Java代码规约扫描插件以今年年初发布的《阿里巴巴Java开发规约》为标准,作为Eclipse、IDEA的插件形式存在,检测JAVA代码中存在不规范得位置然后给予提示。规约插件是采用kotlin语言开发的,感兴趣的同学可以去开看插件源码。阿里巴巴规约插件包含三个子菜单:编码规约扫描、关闭试试检测功能。并且,该插件支持在编写代码的同时进行提示,这款插件,真的可以很大程度上提升代码质量,一定要安装。GsonFormatJava开发中,经常有把json格式的内容转成Object的需求,比如项目开始时,合作方给你提供了一个json格式request/response,这时候你就需要将其定义成一个Java类,GsonFormat这款插件可以实现该功能。Lombok plugin在Java中,我们经常会定义很多JavaBean,这些Bean需要有getter、setter、toString、equals和hashCode等方法。通常情况下,我们可以使用IDEA的快捷键生成这些代码,但是自动生成的代码后,如果bean中的属性一旦有修改,需要重新生成,给代码维护增加了一定的负担。有一款很好的插件,可以帮助开发者节省这部分工作。那就是Lombok。只要在IDEA中安装了该插件,只需要在JavaBean中添加一行注解代码,插件就会自动帮我们生成getter、setter、toString、equals和hashCode等方法。当然,这些方法不止在IDE中的代码调用中需要用到,在真正线上部署的时候也需要有,所以,还需要使用maven引入一个lombok的包。<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version></dependency>/** * @author Hollis */public class HollisLab { public static void main(String[] args) { Wechat wechat = new Wechat(); wechat.setId(“Hollis”); wechat.setDesc(“每日更新Java技术文章”); System.out.println(wechat); }}@Dataclass Wechat { private String id; private String desc;}输出结果:Wechat(id=Hollis, desc=每日更新Java技术文章)我们在Wechat类上面添加了@Data注解,插件就自动帮我们添加了getter/setter和toString方法。String Manipulation字符串日常开发中经常用到的,但是不同的字符串类型在不同的地方可能有一些不同的规则,比如类名要用驼峰形式、常量需要全部大写等,有时候还需要进行编码解码等。这里推荐一款强大的字符串转换工具——String Manipulation。它强大到什么程度,看下他的功能列表你就知道了:文本转换操作切换样式(camelCase, hyphen-lowercase, HYPHEN-UPPERCASE, snake_case, SCREAMING_SNAKE_CASE, dot.case, words lowercase, Words Capitalized, PascalCase)转换为SCREAMING_SNAKE_CASE (或转换为camelCase)转换为 snake_case (或转换为camelCase)转换为dot.case (或转换为camelCase)转换为hyphen-case (或转换为camelCase)转换为hyphen-case (或转换为snake_case)转换为camelCase (或转换为Words)转换为camelCase (或转换为lowercase words)转换为PascalCase (或转换为camelCase)选定文本大写样式反转Un/EscapeUn/Escape 选中的 java 文本Un/Escape 选中的 javascript 文本Un/Escape 选中的 HTML 文本Un/Escape 选中的 XML 文本Un/Escape 选中的 SQL 文本Un/Escape 选中的 PHP 文本将 diacritics(accents) 转换为 ASCII将非ASCII 转换为转义的Unicode将转义的Unicode转换为字符串Encode/DecodeEncode 选中的文本为 MD5 Hex16De/Encode 选中的文本为 URLDe/Encode 选中的文本为 Base64递增/递减递增/递减所有找到的数字复制行并且递增/递减所有找到的数字创建序列:保持第一个数字,递增替换所有其他数字递增重复的数字按自然顺序排序按行倒序按行随机排序区分大小写A-z排序区分大小写z-A排序不区分大小写A-Z排序不区分大小写Z-A排序按行长度排序通过子选择行排序:每行仅处理一个选择/插入符号对齐通过选定的分隔将选定的文本格式化为列/表格将文本对齐为左/中/右过滤/删除/移除grep选定的文本,所有行不匹配输入文字将被删除。 (不能在列模式下工作)移除选定的文本移除选定文本中的所有空格删除选定文本中的所有空格删除重复的行只保留重复的行删除空行删除所有换行符其他交换字符/选择/线/标记切换文件路径分隔符:Windows < - > UNIX安装好插件后,选中需要处理的内容后,按快捷键Alt+m,即可弹出工具功能列表。很好很强大的一款字符串处理工具。.ignore目前很多开发都在使用git做版本控制工具,但是有些时候有些代码我们是不想提到到我们的代码仓库中的,比如ide自动生成的一些配置文件,或者是我们打包生成的一些jar文件等,这时候就需要编写一个.ignore文件,来排除那些不想被版本管理的文件。这里推荐一个好用的插件.ignore,他可以帮我们方便的生成各种ignore文件。安装插件后,选中项目,右键新建的时候,会多出一个.ignore文件的选项,可以通过这个选项创建ignore文件。在弹出的对话框中,可以自动帮我们生成一份.ignore文件,这里我们让其帮忙自动排除所有和idea有关的文件。 Mybatis plugin目前ORM框架中,Mybatis非常受欢迎。但是,同时给很多开发带来困扰的就是Mybatis需要很多xml的配置文件,有的时候很难去进行修改。这里推荐一款神器,可以让你像编辑java代码一样编辑mybatis的文件。Intellij Idea Mybatis插件主要功能:提供Mapper接口与配置文件中对应SQL的导航编辑XML文件时自动补全根据Mapper接口, 使用快捷键生成xml文件及SQL标签ResultMap中的property支持自动补全,支持级联(属性A.属性B.属性C)快捷键生成@Param注解XML中编辑SQL时, 括号自动补全XML中编辑SQL时, 支持参数自动补全(基于@Param注解识别参数)自动检查Mapper XML文件中ID冲突自动检查Mapper XML文件中错误的属性值支持Find Usage支持重构从命名支持别名自动生成ResultMap属性 (图源:https://www.oschina.net/p/int…)但是这款插件是收费的,但是不影响他确实是一个很实用,可以很大程度上提升开发效率的插件。读者可以考虑使用Free Mybatis plugin(这款插件我没用过,具体是否好用有待考证)。Key promoter X对于很多刚刚开始使用IDEA的开发者来说,最苦恼的就是不知道快捷键操作是什么。使用IDEA,如果所有操作都使用鼠标,那么说明你还不是一个合格的程序员。这里推荐一款可以进行快捷键提示的插件Key promoter X。Key Promoter X 是一个提示插件,当你在IDEA里面使用鼠标的时候,如果这个鼠标操作是能够用快捷键替代的,那么Key Promoter X会弹出一个提示框,告知你这个鼠标操作可以用什么快捷键替代。当我使用鼠标查看一个方法都被哪些类使用的时候,就会提示:记住这个快捷键以后,就可以使用快捷键代替鼠标啦。AceJump前面介绍了一款可以通过使用快捷键来代替鼠标操作的插件,这里再介绍一款可以彻底摆脱鼠标的插件,即AceJumpAceJump允许您快速将光标导航到编辑器中可见的任何位置,只需点击“ctrl +;”,然后输入一个你想要跳转到的字符,之后键入匹配的字符就跳转到你想要挑战的地方了。如以上代码,我想在这个类中,跳转到println那个位置,只需要按下快捷键:“ctrl +;”,然后输入println,即可定位到目标位置。上图中,我输入了pri三个字母后,页面提示三个可选项,分别用字母G、H、D标注,这时候只需要按下对应字母,即可快速定位到指定位置,是不是很方便。activate-power-mode最后,介绍一款程序员很好的在妹子面前装X的插件——activate-power-mode 。安装了这款插件之后,你写代码的时候,就会附加一些狂拽炫酷屌炸天的效果:总结本文一共介绍了11款可以提升程序员开发效率、提升代码质量、提升编码心情的软件。欢迎大家安装尝试一下。 ...

January 22, 2019 · 1 min · jiezi

python数据库连接工具DBUtils

DBUtils是一个允许在多线程python应用和数据库之间安全及高效连接的python模块套件。模块DBUtils套件包含两个模块子集,一个适用于兼容DB-API 2接口的模块,一个适用于PyGreSQL的模块。Universal DB-API 2 variant该子集下的模块依赖关系如图:Classic PyGreSQL variant该子集下的模块依赖关系如图:SimplePooledDBDBUtils.SimplePooledDB是池化数据库连接中非常基础的一种实现。相较于PooledDB,它并不那么复杂,且缺少failover机制。SimplePooledDB应视为一种概念演示,不要直接在生产环境使用。SteadyDBDBUtils.SteadyDB基于兼容DB-API 2接口的数据库模块创建的普通连接,实现了"加强"连接。具体指当数据库连接关闭、丢失或使用频率超出限制时,将自动重新获取连接。典型的应用场景如下:在某个维持了某些数据库连接的程序运行时重启了数据库,或在某个防火墙隔离的网络中访问远程数据库时重启了防火墙。PersistentDBDBUtils.PersistentDB实现了稳定,线程仿射(thread-affine),持久化的数据库连接。下图显式了使用PersistentDB进行连接时涉及的连接层:某个线程第一次开启一个数据库连接时,该连接将用于此特定线程。即使在线程中关闭连接,连接也会保持打开状态,以便同一个线程的下一次连接请求直接使用。线程结束时该连接会自动关闭。简而言之:PersistentDB会回收数据库连接从而在整体上增加多线程应用的数据库访问性能,它确保线程之间永远不会共享连接。因此即使底层的DB-API模块不是connection级别的线程安全,PersistentDB也可以完美实现线程安全,避免在其他线程更改数据库会话或执行跨多个SQL指令的事务时出现问题。要使用PersistentDB模块,首先传递以下参数创建PersistentDB实例:creator:兼容DB-API 2的数据库模块或返回DB-API 2连接的任意函数maxusage:单个连接的最大重用次数(0或None表示无重用次数限制),达到该限制后自动关闭并重新打开连接setsession:设置连接会话的sql指令列表,比如[“set wait_timeout = 100”, …]failures:异常类或异常类元组。在默认的(OperationalError, InternalError)不能处理连接failover机制时使用ping:如果ping()方法可用,该值表示何时使用ping()方法检查连接(0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always, and all other bit combinations of these values)closeable:如果设置为True,将允许手动close()连接,默认为False,忽略关闭连接的操作,只在线程终止时自动关闭threadlocal:表示thread-local数据的类。设置值为threading.local可能获取连接的速度更快,但不一定适用于所有情况(例如,mod_wsgi会清空requests之间的threading.local数据)传递给creator参数值创建connection对象的参数,如host, database等import pymysqlfrom DBUtils.PersistentDB import PersistentDBpersist = PersistentDB(creator=pymysql, user=“root”, passwd=“123456”, db=“test”)# conn的使用和常规DB-API 2接口类似conn = persist.connection()NOTE:需要在连接上调用begin()方法明确开启事务。这可以确保a.只在事务完成时才重新打开连接b.连接被同一个线程重用时回滚。PooledDBDBUtils.PooledDB实现了稳定、线程安全的缓存连接池。下图显式了使用PooledDB进行连接时涉及的连接层:使用正整数的maxshared参数和connection级别的线程安全的creator参数创建连接池时,连接池中的连接默认是线程间共享的。但仍可以请求非线程共享的专用数据库连接。除了共享连接池外,还可以创建至少mincached个,至多maxcached个连接的空闲连接池,在共享连接池未满(不太理解)或线程请求专用数据库连接时使用。当某个线程关闭不再共享的连接时,该连接将回收到到空闲连接池以便再次。如果底层的DB-API 2模块非线程安全,将使用线程锁确保PooledDB连接是线程安全的。但对于线程专用的连接,要小心更改数据库会话或执行跨多个SQL指令的事务带来的不良影响。要使用PoolDB模块,首先传递以下参数创建PoolDB实例:creator:同PersistentDBmincached:连接池中创建的初始连接数(0表示初始不创建)maxcached:连接池中允许的最大空闲连接数(0或None表示无限制)maxshared:允许的最大共享连接数(0或None表示所有连接都是专用的),When this maximum number is reached, connections are shared if they have been requested as shareablemaxconnections:允许的最大连接数(0或None表示无限制)blocking:查过最大值是否阻塞。True表示将阻塞直到释放新的连接,默认False表示抛出异常maxusage:同PersistentDBsetsession:同PersistentDBreset:返回连接池时应该怎样重置连接(False或None将只回滚明确调用了begin()开启的事务,默认值为True,出于安全考虑总是会回滚)failures:同PersistentDBping:同PersistentDB传递给creator参数值创建connection对象的参数,如host, database等import pymysqlfrom DBUtils.PooledDB import PooledDBpool = PooledDB(creator=pymysql, 5, user=“root”, passwd=“123456”, db=“test”)# conn的使用和常规DB-API 2接口类似conn = pool.connection()对于线程共享的连接池,可以用以下方式获取线程专用连接:conn = pool.connection(shareable=False)# 或者conn = pool.dedicated_connection()对于不再使用的连接,调用close()方法回收到连接池。在多线程环境中,不要写以下代码,这会导致连接过早释放并被其他线程重用,如果连接非线程安全可能导致程序出现严重错误:pool.connection().cursor().execute(…)NOTE:需要在连接上调用begin()方法明确开启事务。这可以确保a.只在事务完成时才重新打开连接b.连接在返回连接池之前执行回滚c.连接不会被其他线程共享如何选择PooledDB和PersistentDB都通过回收数据库连接,且即使数据库连接中断也能保持稳定性的方式从而达到提升数据库访问性能的目的。在现实场景中应该如何选择呢?对于保持常量线程数且频繁使用数据库的应用,使用PersistentDB;对于频繁开启、结束线程的应用,使用PooledDB。其他如果程序中使用了ORM框架,如SQLObject或SQLAlchemy,不需要使用DBUtils,因为这些框架自身维护了连接池。 ...

January 19, 2019 · 1 min · jiezi

优秀的命令行工具整理(二)

原文作者: Darren Burns 翻译:weakish@LeanCloud原文链接:Power Up Your Command Line本文是「命令行威力提升」系列的第二篇,这一系列展示一些出色的非标准工具,这些工具能让命令行用起来更容易、更享受。peco 交互式过滤将任何命令的输出通过管道传给 peco,便能交互式地过滤输出,查找所需信息。你可以把它想成交互式 grep,随着输入实时更新结果,让搜索过程更直观。你可以使用上下键选择搜索结果,然后用回车键确认。按下回车后,peco 会输出结果。安装 pecomacOS (Homebrew): brew install pecohexyl 十六进制查看器检查二进制文件时通常查看文件的十六进制表示。hexyl 是个命令行下的十六进制查看器。界面分为三栏:偏移量 当前所在的字节数十六进制 文件的十六进制表示(自身又分为两栏,不过在上面的 gif 例子中不可见)表示 尝试将文件显示为文本(同样分为两栏,gif 例子中未显示)hexyl 显示的每个字节的颜色取决于其类型(NULL、ASCII、non-ASCII等),这很有助于可读性。小窍门 :查看二进制文件或大的文本文件时,输出经常会超出屏幕,所以你可以把 hexyl 的输出传给 bat 或 less,以支持分页。如果用 less,需要加上 –raw-control-chars/-r 参数以正确显示颜色。hexyl 由 David Peter 使用 Rust 编写,他也是 bat、fd、hyperfine 的作者,我在本系列的第一篇文章中介绍过这些工具。安装 hexylmacOS (Homebrew): brew install hexylpomo 番茄钟计时器番茄工作法是提升生产效率的好方法。如果你还没听说过,那么它大概是这样的:你心无旁骛地工作 25 分钟放松 5 分钟,做任何你想做的事情(只要不是工作 ????)重复以上步骤 4 次(根据需要调整这个数字),接着休息 15 分钟根据番茄工作法这一理论,遵循这一计划能让你在相对较短的时间内完成相对较多的事情。这也许不适用于每个人,但我个人验证了它的有效性!pomo 是一个简单的命令行工具,助你依照番茄工作法管理时间。安装 pomo在 macOS 上安装:从 GitHub 下载二进制文件:curl -L -o pomo https://github.com/kevinschoon/pomo/releases/download/0.6.0/pomo-0.6.0-darwin-amd64设置权限:chmod +x pomo加入 PATH:mv pomo /usr/local/bin初始化数据库:pomo initncdu 分析、清理磁盘空间如果你的计算机上有很多项目,最近也没有清理过磁盘。那么你几乎一定能找到一个占用大量磁盘的文件夹(我发现旧项目的 node_modules 文件夹特别容易占用大量空间)。ncdu是我最爱的修复工具。事实上,在创建下面的 ncdu 演示例子时,我清理了 10 GiB 的磁盘空间!只需运行 ncdu 即可使用。它会扫描当前目录下的所有子目录,所以如果在家目录运行 ncdu,也许需要较长时间扫描。ncdu 的 ncurses 界面可以使用方向键,也可以使用 vim 风格的快捷键。安装 ncdumacOS (Homebrew): brew install ncduHTTPie curl 的现代替代品HTTPie 是一个更简单(不像 curl,每次使用都要 Google 下用法)、功能更多、更美观的 curl 替代品,可以在命令行调用 HTTP 的 API。到目前为止,我介绍的工具中,它是最流行的,而且有很精良的文档。http 命令的输出足够与 cURL 区分开来。输出的 JSON 响应带语法高亮,十分美观,可读性要好很多。如果你偏爱图形 UI,那么你也许会喜欢 Insomnia、Postman 或 Paw (Paw 需要购买许可,并且只适用于 macOS)。安装 HTTPiemacOS (Homebrew): brew install httpie结语感谢阅读!在这一系列的下一篇文章中,还有一些工具值得一提。如果你有任何建议,欢迎联系我!如果你对更多类似内容感兴趣,可以在 Twitter 上关注我。 ...

January 18, 2019 · 1 min · jiezi

WakaTime数据同步展示工具

从16年知道 WakaTime 后就开始使用它来记录自己的编码时间,但作为免费版只能查看最近两周的数据,于是很久之前就写了一个程序同步自己的历史数据,不过一直没找到合适的图表插件像官网那样展示数据信息。前些日子咨询了一下同事,得知了用 antv 可以绘制对应的图表数据,刚好现在也处于失业阶段,就花了几天时间又重新做了一个项目用于同步和展示 WakaTime 数据。WakaTimeWakaTime简介WakaTime 是一款可以记录你的编码时间的工具,目前支持绝大部分主流的 IDE 以及 Chrome 浏览器。使用步骤注册 WakaTime 账号;在官网找到对应的 IDE 插件,按照步骤安装 WakaTime 插件(下图中灰色表示目前官方还不支持):在个人设置页面复制 Secret API Key ,填入对应的 WakaTime 插件中;过一段时间后,你就可以在 WakaTime 网站上看到你的编码情况,如下图所示:wakatime-sync项目项目简介WakaTime 提供了丰富多样的图表可以多维度地查看自己的编码时间情况。不过作为免费用户,最多只能查看自己最近14天的数据;如果要查看全部的数据,需要 $9/月的订阅费用。还好 WakaTime 提供了 API 接口,通过接口可以获取到编码时间统计情况的原始数据(作为免费用户还是有只能查看最近14天数据的限制)。本项目通过 WakaTime 提供的 API 接口,可以把自己的 WakaTime 数据保存在的数据库中,然后利用图表插件展示出来,目前已完成三种类型的图表展示:每日项目持续时间图:时间范围内活动情况:每日编码耗时日历图:项目地址GITHUB: https://github.com/wf2311/wakatime-sync码云: https://gitee.com/wf2311/wakatime-sync所用技术后端:JDK1.8、SpringBoot、Jodd-HTTP、Thymeleaf;数据库:MySQL 5.7;前端:Moment.js、ElementUI、AntV-G2 、Echarts;通知服务:Server酱、钉钉机器人项目逻辑比较简单,就是每天会定时通过 WakaTime 的 API 抓取并保存前一天的数据,再通过图标插件展示出来。之后还会完善接口缓存、同步通知等功能;同时使用了 AntV-G2 和 Echarts 的原因是因为持续时间图可以用 AntV-G2 实现,但日历图用 AntV-G2 实现过于复杂,就采用了用 Echarts 实现日历图;另外本项目最初是的数据库是 MongoDB ,但是考虑到通用性和易用性后来又换成了 MySQL。MongoDB 版本的代码也实现了相关的同步展示逻辑,代码在 mongodb 分支中。使用方法替换或设置好src/main/resources/application.yml配置文件中的wakatime.app.key和spring.datasource.* 相关数据库配置,采用 maven 打包的方式安装即可,支持 Docker 方式安装。数据库建库脚本位于sql/wakatime_sync.sql中。数据库使用 MongoDB 的版本位于分支 mongdb 中。示例地址:https://wakatime.wangfeng.pro/。消息通知系统中有一个定时任务,会在每天早上09:00会根据配置信息想钉钉或微信发送上一天的编码时间信息;需要在application.yml配置对应的参数:Server酱微信通知:按照Server酱网站说明获得一个SCKEY,设置成wakatime.ftqq-key的值;钉钉机器人通知:在要获得提醒的钉钉群里面生成一个自定义机器人,将机器人的 Hook 地址中的 access_token 的值设置成wakatime.dingding-key的值;如果不想使用对应的消息通知,请将application.yml中对应的参数注释掉或将值置为空可能会遇到的问题由于本项目采用的是SpringBoot 2,对应的 mysql-connector-java 驱动使用的是MySQL服务端的时区,如果你使用的MySQL的时区和你程序中的时区以及你在 WakaTime 个人设置中的时区不一致,就会导致保存的相关数据中时间不准,解决办法就是首先调整好 WakaTime 个人设置里的时区,再调整 MySQL 数据库的时区,或者是使用 5.X版本的mysql-connector-java驱动。如果你一直在使用 WakaTime ,如果想使用本项目同步你所有的历史数据,可以在官网上试用团队版的方式获得1个月(还是半个月?)的付费版功能或者是订阅一个月的付费版,然后通过本项目来同步所有的历史数据:POST /api/v1/sync 或参见项目中的测试方法。使用测试方法进行时不能同时使用太多的线程去同时调用 API 接口,会被限流。TODO查询接口缓存;可以对项目名称设置别名展示;结语如果本项目对你有用的话,欢迎在 GITHUB 或码云上 star,也欢迎对项目提出修改意见和建议。 ...

January 18, 2019 · 1 min · jiezi

mybatis增强工具MyBatis-plus

如果你正在用mybatis,那MyBatis-plus你不能错过,配合使用可极大简化开发、提高效率!简介MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。愿景我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。特性无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )支持关键词自动转义:支持数据库关键词(order、key……)自动转义,还可自定义关键词内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击其中两大点可极大提高开发效率:代码生成器:采用代码或者 Maven 插件可快速生成 Dao、 Model 、 Service 、 Controller 层,Mapper.xml等代码,一键生成,无需重复编码强大的 CRUD 操作:内置通用 Mapper、通用 Service,无需编写其他代码,即用期做CRUD操作,以及自带分页插件,配置一下即可使用以下为CRUD 操作例子public interface FileTypeService extends IService<FileType> {}//注意:FileTypeService中,未写任何代码 //继承了IService通用Service public void addTest(){ //新增 FileType fileType = new FileType(); fileType.setName(“测试4”); fileTypeService.insert(fileType); } public void deleteTest(){ //根据id删除 fileTypeService.deleteById(1); //自定义条件删除 fileTypeService.delete(new EntityWrapper<FileType>().eq(“name”,“测试3”)); } public void getOneTest(){ //查询单个实体 条件为name为测试3 fileTypeService.selectOne(new EntityWrapper<FileType>().eq(“name”,“测试3”)); } public void listTest(){ //查询列表 只查"name"列 fileTypeService.selectList(new EntityWrapper<FileType>().setSqlSelect(“name”)); } public void selectPage(){ //分页查询 //参数Map Map<String,Object> param = new HashMap<>(); //当前页数 param.put(“page”,1); //每页显示 param.put(“limit”,20); //根据id字段倒序排序 param.put(“field”,“id”); param.put(“order”,“desc”); //分页查询 Page<FileType> page = fileTypeService.selectPage(new MapQuery(param).getPage()); }一行代码即可实现CRUD,单仅支持单表查询,如果需要关联多个表,还是得用以前的方法,写sql实现了支持SpringMvc和SpringBoot集成,具体使用请移步官网哈~ THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单! ...

January 9, 2019 · 1 min · jiezi

响应式架构与 RxJava 在有赞零售的实践

随着有赞零售业务的快速发展,系统和业务复杂度也在不断提升。如何解决系统服务化后,多个系统之间的耦合,提升业务的响应时间与吞吐量,有效保证系统的健壮性和稳定性,是我们面临的主要问题。结合目前技术体系和业务特点的思考,我们在业务中实践了响应式架构以及RxJava框架,来解决系统与业务复杂所带来的问题。实践响应式架构响应式架构是指业务组件和功能由事件驱动,每个组件异步驱动,可以并行和分布式部署及运行。响应式架构可以带来以下优势:大幅度降低应用程序内部的耦合性事件传递形式简化了并行程序的开发工作,使开发人员无须与并发编程基础元素打交道,同时可以解决许多并发编程难题,如死锁等。响应式架构能够大幅度提高调用方法的安全性和速度。对复杂业务系统的领域建模,响应式架构可以天然支持。每个系统组件就可以对应到一个业务实体,业务实体之间通过接收事件来完成一次业务操作。我们使用响应式架构主要是为解决多个系统间的多次远程调用带来的分布式问题,尤其在长任务场景中,响应式架构显得尤其必要。有赞连锁出现后,随着连锁商家经营规模的扩张,会在系统中创建新的门店。创建新门店会引发一系列业务初始化工作,例如店铺、员工、仓库、商品、库存等业务域,并且各业务域之间存在一定的依赖关系(如图1所示),例如商品依赖仓库初始化完成。图1 连锁新建分店系统依赖关系商家新增门店时,在店铺初始化完成后,连锁系统发送店铺初始化成功消息,相应系统对事件进行响应,处理完成(成功/失败)后将回执给连锁系统,连锁系统根据相关业务的反馈,决定是继续通知下游业务,还是结束整个过程。新建门店部分流程如图2所示。在创建门店业务中,每个系统响应连锁系统发出的消息,处理完成后进行回执。通过这种模式,业务系统本身不关心其他系统是否成功或失败,只需对通知的事件进行处理,整体初始化进度与异常处理由连锁系统来控制。这种设计使得各业务系统之间没有直接耦合并保持相互独立。图2 连锁体系新增分店消息驱动图上面的案例介绍了在复杂业务场景下系统间对响应式架构的实践,系统内部同样会遇到复杂业务场景。下面介绍下在系统内部应对复杂业务的实践。RxJava在有赞零售实践Rxjava是用来编写异步和基于消息的程序的类库。RxJava在Android有着广泛的使用,主要应用在用户界面绘制与服务端通讯等场景。RxJava的核心思想是响应式编程以及事件、异步这两个特点。响应式编程是一种通过异步和事件流来构建程序的编程模型。在复杂的业务开发中,最棘手的问题就是如何清晰直观的展现复杂的业务逻辑,并且方便后续的业务维护与扩展。响应式编程使得复杂业务逻辑更清晰有赞零售的业务场景中有着复杂的业务逻辑,有赞目前提供多种产品供商家选择,商家在不同产品进行切换时,为了商家更好的体验,不同业务的切换会进行数据初始化与处理。例如有赞微商城转换到有赞零售。这里拿着微商城升级零售的业务场景给大家举例。微商城升级为零售时需要对商品进行转换。首先初始化店铺基础信息。然后读取商品流,将微商城的商品类型转换成零售支持的商品类型。最后读取规格,为规格创建供应链商品库,创建门店商品与添加网店商品的供应链商品关联关系。整体转换流程如图3所示。图中也画出了可以并发处理的场景。图3 微商城升级有赞零售流程如果单纯使用设计模式来解决上面这种场景单一、但业务逻辑特别复杂的场景,是很难做到的。也可以看到除了初始化信息那一步,后面的商品模型转化自始至终在业务中流转的事件都是商品,这里就可以使用RxJava来优化业务代码使得处理流程可以并发,加快升级速度。最终我们按照图3的流程处理升级逻辑,其中的并发场景,比如保存完零售商品后,并发处理库存、和销售渠道,使用rxjava封装的方法帮助我们进行并发操作。如下所示代码结构清晰,对外屏蔽了复杂的并发处理逻辑。Observable.zip( callAsync(()->处理库存相关操作), callAsync(()->更新商品库门店销售渠道), callAsync(()->创建商品库与网店商品关联关系), (sku1,sku2,sku3)-> sku).blockingFirst();最终我们的整体的代码UpgradeItem.listItems(manager, shop) .flatMap(item-> fromCallable(()->更新为零售商品类型)) .flatMap(item-> fromCallable(()->并发处理商品操作), true) .flatMap(item-> 商品流转化为sku流, true) .flatMap(sku-> fromCallable(()->保存零售商品)) .flatMap(sku-> fromCallable(()->并发处理保存商品后续操作, true) .subscribeOn(Schedulers.io());整个商品处理流程就是上面这段代码,一目了然,后面扩展可以自己在中间加入处理流程,也可以在对应业务方法中修改逻辑。多服务、数据源组合随着微服务架构兴起,我们将不同的业务域拆分成不同的系统。这样方便了系统的维护,提升了系统的扩展性,但是给上层业务系统也带来了很多麻烦。往往我们为了展示一个页面会涉及到2-3个或更多的应用,而多次的分布式调用不但使得系统的rt增加,也使得核心页面的出错风险更高。降低rt:在假设第三方接口已经达到性能顶点的情况下,并发是解决多次分布式调用降低rt的常用方法。自动降级:传统编程方法中,自动降级处理,意味着我们代码中会出现一大堆try/catch,而使用rxjava,我们可以直接定义当流处理异常时,程序需要怎么做,这样的代码看起来非常简洁。商品搜索作为商品管理的核心入口,根据不同场景聚合商品、优惠、库存等信息。由于商品列表页展示的信息涉及到多服务数据的整合,一方面需要保证整个接口的rt,另一方面不希望由于一个商品数据或外部服务的异常影响到整个商品列表的加载。因此该场景非常适用于RxJava。最终我们的代码1.根据入参获取商品加载器//只有包含的merger才会加载List<SkuAttrMerger> validMergers = Observable.fromIterable(skuAttrMergers).filter(loader -> request.getAttributes().contains(loader.supportAttribute().getValue())).toList().blockingGet();2.根据es结果获取商品各个属性详情并加载到SkuAttrContext中(某类属性加载失败则忽略)//调用load并发加载数据到商品属性上下文中Observable.fromIterable(商品信息加载器列表).flatMap(商品信息加载器-> Observable.fromCallable(() ->异步加载商品信息)).onErrorResumeNext(Observable.empty())//如果失败则忽略.subscribeOn(Schedulers.io()),false,线程数(为加载器数 量)).blockingSubscribe();3.组装搜索结果(如果某个sku组装失败则直接忽略)//调用merge将数据合并到目标对象商品搜索返回结果列表 = Observable.fromIterable(商品id列表) .map(商品id->初始化商品搜索结果返回对象) .flatMap(商品搜索结果返回对象-> { val observables=Observable.fromIterable(商品加载器列表) .map(loader -> Observable.fromCallable(() ->合并每个sku的不同属性)).toList().blockingGet(); return Observable.zipIterable(observables, (a) -> sku, false, 线程数) .onErrorResumeNext(Observable.empty()); //如果失败则忽略 }, false, 1) .toList() .blockingGet();后记本文主要介绍了响应式架构与RxJava在有赞零售的使用场景。目前我们对响应式架构的实践方式是:在系统间使用消息中间件来进行实现,在系统内则使用RxJava实现异步化和响应式编程。对于响应式架构的思想,我们也在探索阶段,并在部分业务场景进行实践。未来面对越来越复杂的零售业务场景,会用响应式架构全面实现系统业务的异步化。总的来说响应式架构思想为提升复杂业务系统健壮性、灵活性提供了强有力的支撑。后面大家如果想更多的讨论响应式架构与编程的实践,欢迎联系我们。

January 9, 2019 · 1 min · jiezi

软件推荐:Microsoft To-Do

不知道大家是不是和我一样,遇到一样自己用过特别棒的东西,总想分享安利给别人,而且这种行为是很纯粹的,不以利益为目的。或许我的性格里自带分享的属性值吧~大众都在用的其实都不需要推广分享,产品本身就足够让使用者主动去分享了。但难免有一些优秀的实用软件会被埋没在沙子里,可能只需要我这一阵风将沙子吹开一些,大家就能看到它了!这次我想分享的是一款实用的 To-Do 小工具:Microsoft To-Do。一听名字,那必然知道这是微软的产品,至少品质是有保障的。讲句真心话,自从微软换了CEO(Satya Nadella),从事编程行业的人应该都知道微软对软件的态度发生了很大的改变,尤其是对开源这件事。总之,微软变得越来越好了。我之前也用过很多款 To-Do 类的产品,体验比较好的是 Pomotodo——也就是熟知的番茄闹钟,和 Google 的 Google Tasks。在用了一段时间的 Pomotodo 后,其实不是怎么喜欢他们的 UI 设计,然后有一天看到了 Google Tasks,瞬间被它的“颜值”征服了。其实 Google Tasks 很早以前就有了,但并没有推出官方的 App,而且 UI 丑到爆。但是,在国内因为众所周知的原因,用起来很不方便。无奈弃坑。幸运的是最后遇到了Microsoft To-Do,颜值够,功能很精简够用,使用起来很顺畅。除却基本的 To-Do 功能,首先它有步骤的概念,也就是给某一个 To-Do 添加子 To-Do,这就满足了我们需要有计划的做一件事的需求;其次它有提醒时间,截止时间,重复三个功能,能很好的帮助你完成你的 To-Do 清单;还有一个我比较喜欢的功能,类似可以将书签放进不同的书签夹里,我们也可以新建清单然后将不同类型的 To-Do 分门别类的放进各自的清单里。不过,最后还是要提醒大家:To-Do 的最终目的是为了更高效的帮助我们解决事情,是为了让事情越做越少,千万不要本末倒置,不停的往 To-Do 里面塞,但又不去解决,这样只会越积越多。

January 6, 2019 · 1 min · jiezi

推介几款 windows 下非常好用的工具

在下工具控一枚,平时会留意收集各种各样给我们生活生产带来便捷的工具,毕竟人生苦短;下面介绍一些 Windows 系统上发现的一些好用的工具,并且将一笔带过主要特点,详细用法可以搜一下,相关帖子挺多的,每个都展开介绍的话那就太长啦Listary啥都憋说了,Listary 必须排在第一个,用过 Everything,觉得还是 Listary 更胜一筹;它不仅可以在本地非常快速的搜索,还可以打开网站、在搜索引擎中搜索、随时随地打开快捷菜单、文件快速定位、快速打开cmd窗口等等优秀的功能,轻量、简洁、随时随地;比如输入cmd打开cmd窗口,输入cmda使用管理员权限打开cmd窗口,输入wyyyy打开网易云音乐,焦点在某个文件的时候Enter直接打开,Ctrl + Enter 是打开文件所在文件夹;值得一提的是搜索关键词功能,让我们可以非常便捷的打开相应网站或用搜索引擎搜索,比如输入gg 我的存款呢?就可以直接打开默认浏览器在谷歌搜索中搜索,还可以自定义输入其他关键字,只需把搜索链接中的关键字换成{query} DittoDitto 是一款免费开源的 indows 剪切板管理工具,作为Ctrl C V工程师,复制粘贴少不了,更厉害的是,它可以批量的复制,Ctrl+C一堆别人的代码,一次性全粘上,岂不美哉;使用快捷键打开剪切板历史,然后Ctrl / Shift来选择你希望粘贴的内容,Enter即可选择性的粘贴多行内容;另外剪切板历史还可以搜索,快速找到复制内容;只需设置寥寥几个快捷键,就可以很方便的操作剪切板,带来极大幸福Winsnap看到上边的截图没,旁边都有很骚包的阴影,怎么做到的?不需要各种高大上的图片处理软件,使用 Winsnap ,它可以在截图的时候自动帮你加上背景阴影,然后帮你自动复制到剪切板;它可以使用全屏、应用程序、窗口、对象等捕捉模式,更牛的是它还可以在截图的时候同时选择和捕捉多个对象,按住Ctrl或Shift选择多个窗口或对象…这个就比较厉害了,试试看?CmderCmder 是一个美观又实用的命令行工具,它支持大部分Linux命令,支持ssh连Linux,还可以在它的窗口中新建cmd和powershell,更多玩法等你来战比较方便的是在安装目录下 \config\user-aliases.cmd 设置 alias 别名,比如参见的 Git 操作:ga=git add $*gb=git branch $*gc=git commit $*gch=git checkout $*gd=git diff $*gl=git log $*gs=git status $*还可以将cmder配置到右键菜单,快捷在当前目录打开cmder,方法是先把这个地址加到系统的path环境变量里面,比如我的是D:cmder,然后右键Cmder.exe属性-兼容性-以管理员身份运行此程序,再重新打开Cmder.exe输入Cmder.exe /REGISTER ALL就行了记得安装完在配置Setting-Startup-Environment里面加上set LANG=zh_CN.UTF8,否则输出的一些中文会乱码;Typora使用过很多 Markdown 编辑器,最后选择了 Typora,与主流编辑器一边编辑一边预览的形式不同,Typora 是将编辑和预览合并到一起,简洁大方,目光也不需要在编辑区和预览区中来回切换了,只有当焦点移入的时候才显示 Markdown 语法;另外 Typora 还支持 Latex、[TOC]动态目录、拖拽图片自动生成本地预览链接、自定义主题等方便的功能;Quick LookQuickLook 是在 Microsoft Store 里面下载的一个速览工具,有时候打开一个PDF、视频之类的需要等关联程序启动半天,有了它之后只要选中目标文件,按空格就可以快速预览了,速度非常快,支持图片、视频、音频、压缩包、PDF、文本文件、Markdown、HTML等格式;用它来看一些代码什么的,甚至不需要 SublimeVSCode 启动就可以直接看了,如果只是速览一下的场景的话非常适合。Myper SplashMyper Splash 也是可以在 Microsoft Store 里面下载的一款高质量壁纸库,所有壁纸来源 Unsplash 网站,均无版权可以免费使用,再加上简洁美观的UI/UX设计,让你体验一见钟情的感觉。另外 MyperSplash 可以设置自动每天自动更换壁纸或锁屏,每天早晨来到办公室点亮屏幕就可以看到 Awesome 的锁屏或壁纸,让你带着好心情开启一天的工作。GifCam / ScreenToGif相信大家都有过需要截一个 Gif 的时候,这里有两个免费 Gif 屏幕录制工具都很不错,小而美的 GifCam 和开源强大的 ScreenToGif ;GifCam 小巧便捷,如果希望快速录屏分享,那么它是不二选择,可以选择录屏帧率,录制的过程可以调整窗口大小和位置,也可以暂停和继续,足以满足大部分的使用场景;SceenToGif 的编辑功能更为强大,可以单独操作录制的帧,删除、加速或修改都可以,试试看吧Free Download ManageFree Download Manage (FDM) 是一款免费的下载工具,如果你已经受够了国内一些软件的广告和限速,那么 FDM 是一个不错的选择,另外多线程、断点续传、计划任务等功能让 FDM 值得推介。SourcetreeSourcetree 是跨平台免费的 Git 客户端管理工具,如果受够了手打各种 Git 操作命令,那么 Sourcetree 是一个不错的选择;Sourcetree 可以大大简化你的代码操作,特别是对于一些不甚熟悉 Git 命令的人来说灰常实用;一些对 Git 操作比较熟练的用户也可以用它来提升效率,减少出错。 ...

January 4, 2019 · 1 min · jiezi

工作中那些有用的工具

项目maven jar构建 git上的后悔药视频编辑IMOVIE使用数据处理pandas处理文本的方便之处Linux命令Linux上的方便命令

December 31, 2018 · 1 min · jiezi

忘掉数据线,Android下使用无线调试

Android开发过程中,大部分时候我们需要连接usb线通过adb做调试,adb实际上可以设置通过网络来连接,这个设置是在手机端,而不是在pc端,掌握几个小技巧,基本上可以永久抛弃数据线。初始设置要想设置adb走无线网络,有几种方法:root后的设备,可以在手机端直接通过App设置,这样当然比较方便,并且完全不需要usb线,可惜需要root,很多手机不具备这个条件。有些手机的ROM,在开发人员选项中,可以打开adb wifi,这样也算方便,但是大部分手机的ROM也没有这个选项。最后一个方法是对所有手机都适用的方法,就是通过pc侧的adb命令去设置,先通过usb连接手机,然后执行adb tcpip 5555, 手机侧的adb就处于无线模式,然后就可以拔出数据线了。无论上述哪种方法,一旦设置无线模式之后,只要手机不重启,手机将永远处于这种模式,也就是不需要再用到数据线了,但是记得及时充电,防止手机掉电重启,一旦重启过,上面的方法需要再执行一次。平常使用只要手机没有重启过,任何时候想调试手机,需要先执行adb connect命令就可以了,这个命令很简单adb connect phone-ip上面的phone-ip就是手机的ip,显然我们要让手机和pc处于同一个wifi局域网中,才能访问,通过手机端wifi设置的页面,可以看到这个ip,如下图:但是这样每次来看ip,明显比较繁琐,有个简单的App,可以把这个ip通过一个桌面部件放在桌面上,就简单多了,下图就是(qq图标左侧的小部件):chrome远程调试涉及前端类型的开发,比如cordova,javascript等,很多时候需要做chrome远程调试,chrome的远程调试一样可以通过无线,上述adb连接建立好之后,在chrome的地址栏输入chrome://inspect,就可以对手机进行远程调试,下图就是通过无线连接adb后,chrome远程调试的界面,其中Remote target下,我们可以看到了通过无线连接的远程设备:adb远程控制通过adb,有一个非常有用的工具,叫做scrcpy,可以对安卓设备远程控制,同样的,这个工具也一样可以在无线模式下工作,但在无线模式下,网络速度可能不如usb直线快,所以需要在scrcpy启动的时候,增加几个参数,控制scrcpy的屏幕分辨率和压缩率,需要两个参数:scrcpy -m 600 -b 1m这两个参数分别控制屏幕分辨率和压缩码率,根据你自己的无线网速来调整就好,这样也可以愉快的通过无线使用scrcpy的远程控制功能了。总结通过上述几个工具和方法,我们可以半彻底的丢弃usb线,对android的开发还是方便很多的。

December 30, 2018 · 1 min · jiezi

有赞全链路压测引擎的设计与实现

一年以前,有赞准备在双十一到来之前对系统进行一次性能摸底,以便提前发现并解决系统潜在性能问题,好让系统在双十一期间可以从容应对剧增的流量。工欲善其事,必先利其器,我们拿什么工具来压测呢?我们做了很多前期调研和论证,最终决定基于 Gatling 开发有赞自己的分布式全链路压测引擎 —— MAXIM。一年多来,我们使用 Maxim 对系统做了很多次的性能压测,在提升系统性能、稳定性的同时,也得益于历次压测的实践经验逐步改进 Maxim。1、前期调研1.1、技术选型的核心考量由于时间或成本关系,我们打算基于开源软件做二次开发,而以下就是我们技术选型时的核心考量:将请求编排成业务场景以用户下单这个场景为例,用户完成一笔订单,可能需要打开商品主页-加入购物车-选择收货地址-下单支付这些步骤,而串起这一系列的请求就是所谓的将请求编排成业务场景流量控制流量控制可以是纵向的,如上述下单场景中,各个步骤的请求量逐渐减少,整体呈现一个漏斗模型;也可以是横向的,比如用户正在浏览 A 商品的商品详情页,然后看到了 B 商品的推荐,转而浏览 B 商品的商品详情页压力控制指压测时并发用户数、吞吐量(RPS / TPS)的控制数据跟请求参数的绑定压测往往涉及大量的测试数据,而如何绑定数据和请求参数是我们需要考量的对分布式测试的支持因为是全链路压测,自然需要多台施压机共同协作施压,自然而然的需要分布式支持测试报告良好的测试报告是我们分析性能问题的必备条件二次开发的成本由于时间或人力关系,我们也需要考虑二次开发成本1.2、4 个主流开源性能测试框架对比我们调研了以下 4 个主流开源性能测试框架:ApacheBenchApache 服务器自带,简单易用,但不支持场景编排、不支持分布式,二次开发难度较大JMeterJMeter 支持上述很多特性,如分布式、良好的压测报告等,但其基于 GUI 的使用方式,使得当我们的压测场景非常复杂并包含很多请求时,使用上不够灵活;此外在流量控制方面的支持也一般nGrinder基于 Grinder 二次开发的开源项目,支持分布式,测试报告良好,但和 JMeter 一样,在场景编排和流量控制方面支持一般Gatling支持场景编排、流量控制、压力控制,测试报告良好,且提供了强大的 DSL(领域特定语言)方便编写压测脚本,但不支持分布式,且使用 Scala 开发,有一定开发成本以上,我们最终选择基于 Gatling 做二次开发。2、Maxim 新增的特性Maxim 在 Gatling 基础上开发了很多新特性:支持分布式一个控制中心(Control Center,负责调度) + 多个压力注入器(指施压机)提供 GUI,并对用户隐藏压测过程的复杂性高效地创建、运行(手动/定期)测试任务管理测试资源测试资源包括压测脚本、数据集(为压测请求提供测试数据,由数据块构成的一个集合,数据块是大量测试数据的最小分割单元)、压力注入器支持压测脚本参数化Maxim 中并发用户数、RPS、持续时间等都可以通过 GUI 动态注入压测脚本支持压力注入器系统状态监控实时监控压力注入器的 CPU、内存、I/O 等指标自动生成压测报告,保留历史压测报告采集多个压力注入器的压测日志,自动汇总生成压测报告,并保留历史压测报告3、Maxim 的技术架构3.1、Maxim 的总体架构Maxim 架构的主要构成:Maxim ConsoleMaxim Console 主要衔接 GUI 和 Maxim Control Center,负责创建、运行测试任务,接收压力控制参数等Maxim Control CenterMaxim 的控制中心,这里主要负责压测任务的调度、读取数据集、上传脚本和数据以及读取日志并生成压测报告Load Injector Cluster压力注入器集群,主要分为 Agent 和 Gatling 两部分,Agent 负责接收 Maxim 控制中心的调度指令以及向控制中心反馈本压力注入器压测情况,而 Gatling 则是真正发起压测请求的地方,并将压测日志写入 InfluxDBData Factory压测数据首先会在大数据平台通过 MapReduce 任务生成,而数据工厂负责为控制中心读取这些数据并返回数据集Cloud Storage云存储,Maxim 控制中心会将压测脚本和压测数据上传到云存储,当 Agent 收到控制中心的任务执行指令时,会从云存储下载压测脚本和对应的数据块。设计云存储的目的主要是为了模拟真实用户环境在公网发起压测请求,但有赞目前都是从内网发起压测请求,所以云存储的功能也可以以其他方式实现,比如 Agent 直接从大数据平台下载数据集InfluxDB所有压力注入器产生的日志都会统一写入 InfluxDB,方便生成压测报告Maxim的调度算法 控制中心会根据当前测试任务使用的压力注入器数量,将数据集中的数据块平均分配给每个压力注入器,让每个压力注入器只下载对应的那些数据块。此外,并发用户数、RPS 也会被平均切分给每个压力注入器。这样,每个压力注入器的负载基本是一致的。3.2、Maxim 的领域抽象TestJob - JobExecution - JobSliceExecution当压测任务开始执行,首先会在控制中心生成 JobExecution,监控本次压测任务的整体执行状态。控制中心又会根据上述调度算法为每个压力注入器生成任务分片 JobSliceExecution 并下发到各个压力注入器,其中包含了脚本、数据集等信息TestScript压测脚本DataSet和DataChunk数据集和组成数据集的数据块单元,目前单次压测任务已支持多数据集,为多个场景提供不同的压测数据,即混合场景压测LoadProfile从 GUI 接收动态参数,主要包括压力注入器数量、并发用户数、RPS、持续时间等ExecPlan执行计划,包括按需执行和周期执行两种执行方式ExecutionStatus关于状态机下一节会详细介绍3.3、Maxim的状态机Maxim 状态机是 Maxim 分布式的核心,控制中心和各个 Agent 的行为都受状态机变化的影响。创建任务并开始执行以后,各个任务分片(JobSliceExecution)首先会进入 preparing 状态,各个 Agent 会从云存储下载压测脚本和各自对应的那些数据块,下载完成后再将这些数据块合并成一个 Json 数据文件作为压测脚本的数据输入。如果下载失败则会重试,即 Prepare。如果所有 Agent 都成功下载了脚本和数据,则各个 JobSliceExecution 会相继进入 prepared 状态,等所有 JobSliceExecution 进入 prepared 状态后,JobExecution 也会进入 prepared 状态,并向各个 Agent 发起执行指令,各个 JobSliceExecution 进入 running 状态,等所有 Agent 执行完成且各个 JobSliceExecution 变成 completed 状态之后,JobExecution 也会进入 completed 状态,此时压测任务执行完成并生成压测报告。如果各个任务分片在 preparing、prepared 或 running 过程中有任何一个出错,则出错的分片会进入 failed 状态并通知控制中心,控制中心则控制其他分片中止正在执行的任务并进入 Stopping 状态,等这些分片中止成功并都变成 stopped 状态后,JobExecution 会被置成 failed 状态。当然了,也可以手动停止压测任务,这时候 JobSliceExecution 和 JobExecution 都会被置成 stopping->stopped 状态。3.4、Maxim 控制中心的技术架构Maxim 控制中心采用六边形架构(也叫端口与适配器模式),核心服务只处理核心业务逻辑(如调度算法),其他功能如与 Agent 通信、脚本存储、数据存储、压测报告等都是通过适配层调用特定实现的 API 实现。具体技术的话,与 Agent 通信使用 grpc 实现,其他功能则是通过 SPI 技术实现,我们把这一层叫做接缝层(Seam)。这样设计最大层度的解耦了核心业务逻辑和其他功能的特定实现,我们在保持接缝层 API 不变的情况下,可以自由选择技术方案实现相应的功能。比如数据服务这块强依赖了有赞的大数据平台,假设我们开源了 Maxim,外部团队就可以选择他们自己的技术方案实现数据服务,或者为了测试目的 Mock 掉。4、改造 Gatling原生 Gatling 是将压测日志写入本地日志文件的,而在分布式中,如果每个压力注入器都把日志写在本地,则为了基于所有日志分析生成压测报告,我们需要首先收集分散在各个压力注入器中的日志文件,这样显然是低效的。所以我们改造了 Gatling ,将所有日志都写到同一个 InfluxDB 数据库。需要生成压测报告时,控制中心从 InfluxDB 数据库读入本次压测任务的所有压测日志并保存为一个日志文件,再交由 Gatling 的日志处理模块来生成压测报告。5、扩展 Gatling原生 Gatling 不支持 Dubbo 压测,所以我们扩展 Gatling,实现并开源了 gatling-dubbo压测插件,具体实现方法详见 Dubbo压测插件的实现——基于Gatling6、Maxim 的未来展望Maxim 目前还是个单打独斗的产品,未来我们希望与大数据平台、运维平台等系统打通,让 Maxim 逐渐进化为一个一站式的压测平台,并引入更多新特性,如压测过程和压测报告的实时计算和展示等等。我的系列博客 混沌工程 - 软件系统高可用、弹性化的必由之路 异步系统的两种测试方法 Dubbo压测插件的实现——基于Gatling我的其他测试相关开源项目 捉虫记:方便产品、开发、测试三方协同自测的管理工具 gatling-dubbo:扩展自Gatling的Dubbo性能测试插件招聘 有赞测试组在持续招人中,大量岗位空缺,只要你来,就能帮你点亮全栈开发技能树,有意向换工作的同学可以发简历到 sunjun【@】youzan.com ...

December 24, 2018 · 1 min · jiezi

Dubbo压测插件的实现——基于Gatling

Dubbo 压测插件已开源,本文涉及代码详见gatling-dubboGatling 是一个开源的基于 Scala、Akka、Netty 实现的高性能压测框架,较之其他基于线程实现的压测框架,Gatling 基于 AKKA Actor 模型实现,请求由事件驱动,在系统资源消耗上低于其他压测框架(如内存、连接池等),使得单台施压机可以模拟更多的用户。此外,Gatling 提供了一套简单高效的 DSL(领域特定语言)方便我们编排业务场景,同时也具备流量控制、压力控制的能力并提供了良好的压测报告,所以有赞选择在 Gatling 基础上扩展分布式能力,开发了自己的全链路压测引擎 MAXIM。全链路压测中我们主要模拟用户实际使用场景,使用 HTTP 接口作为压测入口,但有赞目前后端服务中 Dubbo 应用比重越来越高,如果可以知道 Dubbo 应用单机水位将对我们把控系统后端服务能力大有裨益。基于 Gatling 的优势和在有赞的使用基础,我们扩展 Gatling 开发了 gatling-dubbo 压测插件。插件主要结构实现 Dubbo 压测插件,需实现以下四部分内容:Protocol 和 ProtocolBuild协议部分,这里主要定义 Dubbo 客户端相关内容,如协议、泛化调用、服务 URL、注册中心等内容,ProtocolBuild 则为 DSL 使用 Protocol 的辅助类Action 和 ActionBuild执行部分,这里的作用是发起 Dubbo 请求,校验请求结果并记录日志以便后续生成压测报告。ActionBuild 则为 DSL 使用 Action 的辅助类Check 和 CheckBuild检查部分,全链路压测中我们都使用Json Path检查请求结果,这里我们实现了一样的检查逻辑。CheckBuild 则为 DSL 使用 Check 的辅助类DSLDubbo 插件的领域特定语言,我们提供了一套简单易用的 API 方便编写 Duboo 压测脚本,风格上与原生 HTTP DSL 保持一致Protocol协议部分由 5 个属性组成,这些属性将在 Action 初始化 Dubbo 客户端时使用,分别是:protocol协议,设置为dubbogeneric泛化调用设置,Dubbo 压测插件使用泛化调用发起请求,所以这里设置为true,有赞优化了泛化调用的性能,为了使用该特性,引入了一个新值result_no_change(去掉优化前泛化调用的序列化开销以提升性能)urlDubbo 服务的地址:dubbo://IP地址:端口registryProtocolDubbo 注册中心的协议,设置为ETCD3registryAddressDubbo 注册中心的地址如果是测试 Dubbo 单机水位,则设置 url,注册中心设置为空;如果是测试 Dubbo 集群水位,则设置注册中心(目前支持 ETCD3),url 设置为空。由于目前注册中心只支持 ETCD3,插件在 Dubbo 集群上使用缺乏灵活性,所以我们又实现了客户端层面的负载均衡,如此便可抛开特定的注册中心来测试 Dubbo 集群水位。该特性目前正在内测中。object DubboProtocol { val DubboProtocolKey = new ProtocolKey { type Protocol = DubboProtocol type Components = DubboComponents def protocolClass: Class[io.gatling.core.protocol.Protocol] = classOf[DubboProtocol].asInstanceOf[Class[io.gatling.core.protocol.Protocol]] def defaultProtocolValue(configuration: GatlingConfiguration): DubboProtocol = throw new IllegalStateException(“Can’t provide a default value for DubboProtocol”) def newComponents(system: ActorSystem, coreComponents: CoreComponents): DubboProtocol => DubboComponents = { dubboProtocol => DubboComponents(dubboProtocol) } }}case class DubboProtocol( protocol: String, //dubbo generic: String, //泛化调用? url: String, //use url or registryProtocol: String, //use registry registryAddress: String //use registry) extends Protocol { type Components = DubboComponents}为了方便 Action 中使用上面这些属性,我们将其装进了 Gatling 的 ProtocolComponents:case class DubboComponents(dubboProtocol: DubboProtocol) extends ProtocolComponents { def onStart: Option[Session => Session] = None def onExit: Option[Session => Unit] = None}以上就是关于 Protocol 的定义。为了能在 DSL 中配置上述 Protocol,我们定义了 DubboProtocolBuilder,包含了 5 个方法分别设置 Protocol 的 protocol、generic、url、registryProtocol、registryAddress 5 个属性。object DubboProtocolBuilderBase { def protocol(protocol: String) = DubboProtocolBuilderGenericStep(protocol)}case class DubboProtocolBuilderGenericStep(protocol: String) { def generic(generic: String) = DubboProtocolBuilderUrlStep(protocol, generic)}case class DubboProtocolBuilderUrlStep(protocol: String, generic: String) { def url(url: String) = DubboProtocolBuilderRegistryProtocolStep(protocol, generic, url)}case class DubboProtocolBuilderRegistryProtocolStep(protocol: String, generic: String, url: String) { def registryProtocol(registryProtocol: String) = DubboProtocolBuilderRegistryAddressStep(protocol, generic, url, registryProtocol)}case class DubboProtocolBuilderRegistryAddressStep(protocol: String, generic: String, url: String, registryProtocol: String) { def registryAddress(registryAddress: String) = DubboProtocolBuilder(protocol, generic, url, registryProtocol, registryAddress)}case class DubboProtocolBuilder(protocol: String, generic: String, url: String, registryProtocol: String, registryAddress: String) { def build = DubboProtocol( protocol = protocol, generic = generic, url = url, registryProtocol = registryProtocol, registryAddress = registryAddress )}ActionDubboAction 包含了 Duboo 请求逻辑、请求结果校验逻辑以及压力控制逻辑,需要扩展 ExitableAction 并实现 execute 方法。DubboAction 类的域 argTypes、argValues 分别是泛化调用请求参数类型和请求参数值,需为 Expression[] 类型,这样当使用数据 Feeder 作为压测脚本参数输入时,可以使用类似 ${args_types}、${args_values}这样的表达式从数据 Feeder 中解析对应字段的值。execute 方法必须以异步方式执行 Dubbo 请求,这样前一个 Dubbo 请求执行后但还未等响应返回时虚拟用户就可以通过 AKKA Message 立即发起下一个请求,如此一个虚拟用户可以在很短的时间内构造大量请求。请求方式方面,相比于泛化调用,原生 API 调用需要客户端载入 Dubbo 服务相应的 API 包,但有时候却拿不到,此外,当被测 Dubbo 应用多了,客户端需要载入多个 API 包,所以出于使用上的便利性,Dubbo 压测插件使用泛化调用发起请求。异步请求响应后会执行 onComplete 方法,校验请求结果,并根据校验结果记录请求成功或失败日志,压测报告就是使用这些日志统计计算的。 为了控制压测时的 RPS,则需要实现 throttle 逻辑。实践中发现,高并发情况下,泛化调用性能远不如原生 API 调用性能,且响应时间成倍增长(如此不能表征 Dubbo 应用的真正性能),导致 Dubbo 压测插件压力控制不准,解决办法是优化泛化调用性能,使之与原生 API 调用的性能相近,请参考dubbo 泛化调用性能优化。class DubboAction( interface: String, method: String, argTypes: Expression[Array[String]], argValues: Expression[Array[Object]], genericService: GenericService, checks: List[DubboCheck], coreComponents: CoreComponents, throttled: Boolean, val objectMapper: ObjectMapper, val next: Action) extends ExitableAction with NameGen { override def statsEngine: StatsEngine = coreComponents.statsEngine override def name: String = genName(“dubboRequest”) override def execute(session: Session): Unit = recover(session) { argTypes(session) flatMap { argTypesArray => argValues(session) map { argValuesArray => val startTime = System.currentTimeMillis() val f = Future { try { genericService.$invoke(method, argTypes(session).get, argValues(session).get) } finally { } } f.onComplete { case Success(result) => val endTime = System.currentTimeMillis() val resultMap = result.asInstanceOf[JMap[String, Any]] val resultJson = objectMapper.writeValueAsString(resultMap) val (newSession, error) = Check.check(resultJson, session, checks) error match { case None => statsEngine.logResponse(session, interface + “.” + method, ResponseTimings(startTime, endTime), Status(“OK”), None, None) throttle(newSession(session)) case Some(Failure(errorMessage)) => statsEngine.logResponse(session, interface + “.” + method, ResponseTimings(startTime, endTime), Status(“KO”), None, Some(errorMessage)) throttle(newSession(session).markAsFailed) } case FuFailure(e) => val endTime = System.currentTimeMillis() statsEngine.logResponse(session, interface + “.” + method, ResponseTimings(startTime, endTime), Status(“KO”), None, Some(e.getMessage)) throttle(session.markAsFailed) } } } } private def throttle(s: Session): Unit = { if (throttled) { coreComponents.throttler.throttle(s.scenario, () => next ! s) } else { next ! s } }}DubboActionBuilder 则是获取 Protocol 属性并初始化 Dubbo 客户端:case class DubboActionBuilder(interface: String, method: String, argTypes: Expression[Array[String]], argValues: Expression[Array[Object]], checks: List[DubboCheck]) extends ActionBuilder { private def components(protocolComponentsRegistry: ProtocolComponentsRegistry): DubboComponents = protocolComponentsRegistry.components(DubboProtocol.DubboProtocolKey) override def build(ctx: ScenarioContext, next: Action): Action = { import ctx._ val protocol = components(protocolComponentsRegistry).dubboProtocol //Dubbo客户端配置 val reference = new ReferenceConfig[GenericService] val application = new ApplicationConfig application.setName(“gatling-dubbo”) reference.setApplication(application) reference.setProtocol(protocol.protocol) reference.setGeneric(protocol.generic) if (protocol.url == “”) { val registry = new RegistryConfig registry.setProtocol(protocol.registryProtocol) registry.setAddress(protocol.registryAddress) reference.setRegistry(registry) } else { reference.setUrl(protocol.url) } reference.setInterface(interface) val cache = ReferenceConfigCache.getCache val genericService = cache.get(reference) val objectMapper: ObjectMapper = new ObjectMapper() new DubboAction(interface, method, argTypes, argValues, genericService, checks, coreComponents, throttled, objectMapper, next) }}LambdaProcessBuilder 则提供了设置 Dubbo 泛化调用入参的 DSL 以及接下来要介绍的 Check 部分的 DSLcase class DubboProcessBuilder(interface: String, method: String, argTypes: Expression[Array[String]] = _ => Success(Array.empty[String]), argValues: Expression[Array[Object]] = _ => Success(Array.empty[Object]), checks: List[DubboCheck] = Nil) extends DubboCheckSupport { def argTypes(argTypes: Expression[Array[String]]): DubboProcessBuilder = copy(argTypes = argTypes) def argValues(argValues: Expression[Array[Object]]): DubboProcessBuilder = copy(argValues = argValues) def check(dubboChecks: DubboCheck*): DubboProcessBuilder = copy(checks = checks ::: dubboChecks.toList) def build(): ActionBuilder = DubboActionBuilder(interface, method, argTypes, argValues, checks)}Check全链路压测中,我们都使用Json Path校验 HTTP 请求结果,Dubbo 压测插件中,我们也实现了基于Json Path的校验。实现 Check,必须实现 Gatling check 中的 Extender 和 Preparer:package object dubbo { type DubboCheck = Check[String] val DubboStringExtender: Extender[DubboCheck, String] = (check: DubboCheck) => check val DubboStringPreparer: Preparer[String, String] = (result: String) => Success(result)}基于Json Path的校验逻辑:trait DubboJsonPathOfType { self: DubboJsonPathCheckBuilder[String] => def ofType[X: JsonFilter](implicit extractorFactory: JsonPathExtractorFactory) = new DubboJsonPathCheckBuilder[X](path, jsonParsers)}object DubboJsonPathCheckBuilder { val CharsParsingThreshold = 200 * 1000 def preparer(jsonParsers: JsonParsers): Preparer[String, Any] = response => { if (response.length() > CharsParsingThreshold || jsonParsers.preferJackson) jsonParsers.safeParseJackson(response) else jsonParsers.safeParseBoon(response) } def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) = new DubboJsonPathCheckBuilder[String](path, jsonParsers) with DubboJsonPathOfType}class DubboJsonPathCheckBuilder[X: JsonFilter]( private[check] val path: Expression[String], private[check] val jsonParsers: JsonParsers)(implicit extractorFactory: JsonPathExtractorFactory) extends DefaultMultipleFindCheckBuilder[DubboCheck, String, Any, X]( DubboStringExtender, DubboJsonPathCheckBuilder.preparer(jsonParsers) ) { import extractorFactory._ def findExtractor(occurrence: Int) = path.map(newSingleExtractor[X](_, occurrence)) def findAllExtractor = path.map(newMultipleExtractor[X]) def countExtractor = path.map(newCountExtractor)}DubboCheckSupport 则提供了设置 jsonPath 表达式的 DSLtrait DubboCheckSupport { def jsonPath(path: Expression[String])(implicit extractorFactory: JsonPathExtractorFactory, jsonParsers: JsonParsers) = DubboJsonPathCheckBuilder.jsonPath(path)}Dubbo 压测脚本中可以设置一个或多个 check 校验请求结果,使用 DSL check 方法*DSLtrait AwsDsl提供顶层 DSL。我们还定义了 dubboProtocolBuilder2DubboProtocol、dubboProcessBuilder2ActionBuilder 两个 Scala 隐式方法,以自动构造 DubboProtocol 和 ActionBuilder。 此外,泛化调用中使用的参数类型为 Java 类型,而我们的压测脚本使用 Scala 编写,所以这里需要做两种语言间的类型转换,所以我们定义了 transformJsonDubboData 方法trait DubboDsl extends DubboCheckSupport { val Dubbo = DubboProtocolBuilderBase def dubbo(interface: String, method: String) = DubboProcessBuilder(interface, method) implicit def dubboProtocolBuilder2DubboProtocol(builder: DubboProtocolBuilder): DubboProtocol = builder.build implicit def dubboProcessBuilder2ActionBuilder(builder: DubboProcessBuilder): ActionBuilder = builder.build() def transformJsonDubboData(argTypeName: String, argValueName: String, session: Session): Session = { session.set(argTypeName, toArray(session(argTypeName).as[JList[String]])) .set(argValueName, toArray(session(argValueName).as[JList[Any]])) } private def toArray[T:ClassTag](value: JList[T]): Array[T] = { value.asScala.toArray }}object Predef extends DubboDslDubbo 压测脚本和数据 Feeder 示例压测脚本示例:import io.gatling.core.Predef._import io.gatling.dubbo.Predef._import scala.concurrent.duration._class DubboTest extends Simulation { val dubboConfig = Dubbo .protocol(“dubbo”) .generic(“true”) //直连某台Dubbo机器,只单独压测一台机器的水位 .url(“dubbo://IP地址:端口”) //或设置注册中心,压测该Dubbo应用集群的水位,支持ETCD3注册中心 .registryProtocol("") .registryAddress("") val jsonFileFeeder = jsonFile(“data.json”).circular //数据Feeder val dubboScenario = scenario(“load test dubbo”) .forever(“repeated”) { feed(jsonFileFeeder) .exec(session => transformJsonDubboData(“args_types1”, “args_values1”, session)) .exec(dubbo(“com.xxx.xxxService”, “methodName”) .argTypes("${args_types1}") .argValues("${args_values1}") .check(jsonPath("$.code").is(“200”)) ) } setUp( dubboScenario.inject(atOnceUsers(10)) .throttle( reachRps(10) in (1 seconds), holdFor(30 seconds)) ).protocols(dubboConfig)}data.json 示例:[ { “args_types1”: [“com.xxx.xxxDTO”], “args_values1”: [{ “field1”: “111”, “field2”: “222”, “field3”: “333” }] }]Dubbo 压测报告示例我的系列博客 混沌工程 - 软件系统高可用、弹性化的必由之路 异步系统的两种测试方法我的其他测试相关开源项目 捉虫记:方便产品、开发、测试三方协同自测的管理工具招聘 有赞测试组在持续招人中,大量岗位空缺,只要你来,就能帮你点亮全栈开发技能树,有意向换工作的同学可以发简历到 sunjun【@】youzan.com ...

December 24, 2018 · 5 min · jiezi

「 神器 」资源管理神器Clover,风一样的效率

开开心心地上班,这时你得打开我的电脑,点进D盘,打开某个项目;然后还得打开XX文档,还有….最后的最后,你的桌面便成了这个样子每天你都得天打开多个文件夹,切换时找文件找的晕头转向而烦恼。每天层层深入的找某个文件而浪费时间。今天为大家推荐一款资源管理神器“Colver”,Colver可快速找到文件,打开多个文件夹,桌面还是一样的整洁方便的Tab页功能,像浏览器一样打开文件夹要掌握功能强大,操作简单的标签页,只需记住Ctrl+T新开页面,Ctrl+W关闭页面,Ctrl+Tab切换页面,工作效率提高何止一倍!操作系统无缝集成Clover 通过插件的形式集成到 Windows Explorer,保留您通常的使用习惯,无需学习新的文件管理操作,马上就可以使用啦。快如闪电的书签栏按Ctrl+D添加当前路径,或者直接将文件夹拖入书签栏。再也不用到处寻找要访问的文件夹了,瞬间到达,何等痛快!管理书签设置快捷键如果你的文件存放位置杂乱无章的话,再推荐一款神器——Everything,让你瞬间找到你想要的文件,比系统自带的搜索功能不知道强大N倍。当然平时养成一个整理文件的好习惯很重要,再结合Clover,开发效率会提升不少,快去试试吧。获取方式</article>长按二维码,回复:资源管理坚持日更:39天您的点赞、转发是对我最大的支持! THANDKSEnd -一个立志成大腿而每天努力奋斗的年轻人伴学习伴成长,成长之路你并不孤单!

November 30, 2018 · 1 min · jiezi

有赞透明多级缓存解决方案(TMC)

一、引子1-1. TMC 是什么TMC ,即“透明多级缓存( Transparent Multilevel Cache )”,是有赞 PaaS 团队给公司内应用提供的整体缓存解决方案。TMC 在通用“分布式缓存解决方案(如 CodisProxy + Redis ,如有赞自研分布式缓存系统 zanKV )”基础上,增加了以下功能:应用层热点探测应用层本地缓存应用层缓存命中统计以帮助应用层解决缓存使用过程中出现的热点访问问题。1-2. 为什么要做 TMC使用有赞服务的电商商家数量和类型很多,商家会不定期做一些“商品秒杀”、“商品推广”活动,导致“营销活动”、“商品详情”、“交易下单”等链路应用出现 缓存热点访问 的情况:活动时间、活动类型、活动商品之类的信息不可预期,导致 缓存热点访问 情况不可提前预知;缓存热点访问 出现期间,应用层少数 热点访问 key 产生大量缓存访问请求:冲击分布式缓存系统,大量占据内网带宽,最终影响应用层系统稳定性;为了应对以上问题,需要一个能够 自动发现热点 并 将热点缓存访问请求前置在应用层本地缓存 的解决方案,这就是 TMC 产生的原因。1-3. 多级缓存解决方案的痛点基于上述描述,我们总结了下列 多级缓存解决方案 需要解决的需求痛点:热点探测:如何快速且准确的发现 热点访问 key ?数据一致性:前置在应用层的本地缓存,如何保障与分布式缓存系统的数据一致性?效果验证:如何让应用层查看本地缓存命中率、热点 key 等数据,验证多级缓存效果?透明接入:整体解决方案如何减少对应用系统的入侵,做到快速平滑接入?TMC 聚焦上述痛点,设计并实现了整体解决方案。以支持“热点探测”和“本地缓存”,减少热点访问时对下游分布式缓存服务的冲击,避免影响应用服务的性能及稳定性。二、 TMC 整体架构TMC 整体架构如上图,共分为三层:存储层:提供基础的kv数据存储能力,针对不同的业务场景选用不同的存储服务( codis / zankv / aerospike );代理层:为应用层提供统一的缓存使用入口及通信协议,承担分布式数据水平切分后的路由功能转发工作;应用层:提供统一客户端给应用服务使用,内置“热点探测”、“本地缓存”等功能,对业务透明;本篇聚焦在应用层客户端的“热点探测”、“本地缓存”功能。三、 TMC 本地缓存3-1. 如何透明TMC 是如何减少对业务应用系统的入侵,做到透明接入的?对于公司 Java 应用服务,在缓存客户端使用方式上分为两类:基于spring.data.redis包,使用RedisTemplate编写业务代码;基于youzan.framework.redis包,使用RedisClient编写业务代码;不论使用以上那种方式,最终通过JedisPool创建的Jedis对象与缓存服务端代理层做请求交互。TMC 对原生jedis包的JedisPool和Jedis类做了改造,在JedisPool初始化过程中集成TMC“热点发现”+“本地缓存”功能Hermes-SDK包的初始化逻辑,使Jedis客户端与缓存服务端代理层交互时先与Hermes-SDK交互,从而完成 “热点探测”+“本地缓存”功能的透明接入。对于 Java 应用服务,只需使用特定版本的 jedis-jar 包,无需修改代码,即可接入 TMC 使用“热点发现”+“本地缓存”功能,做到了对应用系统的最小入侵。3-2. 整体结构3-2-1. 模块划分TMC 本地缓存整体结构分为如下模块:Jedis-Client: Java 应用与缓存服务端交互的直接入口,接口定义与原生 Jedis-Client 无异;Hermes-SDK:自研“热点发现+本地缓存”功能的SDK封装, Jedis-Client 通过与它交互来集成相应能力;Hermes服务端集群:接收 Hermes-SDK 上报的缓存访问数据,进行热点探测,将热点 key 推送给 Hermes-SDK 做本地缓存;缓存集群:由代理层和存储层组成,为应用客户端提供统一的分布式缓存服务入口;基础组件: etcd 集群、 Apollo 配置中心,为 TMC 提供“集群推送”和“统一配置”能力;3-2-2. 基本流程1) key 值获取Java 应用调用 Jedis-Client 接口获取key的缓存值时,Jedis-Client 会询问 Hermes-SDK 该 key 当前是否是 热点key;对于 热点key ,直接从 Hermes-SDK 的 热点模块 获取热点 key 在本地缓存的 value 值,不去访问 缓存集群 ,从而将访问请求前置在应用层;对于非 热点key ,Hermes-SDK 会通过Callable回调 Jedis-Client 的原生接口,从 缓存集群 拿到 value 值;对于 Jedis-Client 的每次 key 值访问请求,Hermes-SDK 都会通过其 通信模块 将 key访问事件 异步上报给 Hermes服务端集群 ,以便其根据上报数据进行“热点探测”;2)key值过期Java 应用调用 Jedis-Client 的set() del() expire()接口时会导致对应 key 值失效,Jedis-Client 会同步调用 Hermes-SDK 的invalid()方法告知其“ key 值失效”事件;对于 热点key ,Hermes-SDK 的 热点模块 会先将 key 在本地缓存的 value 值失效,以达到本地数据强一致。同时 通信模块 会异步将“ key 值失效”事件通过 etcd集群 推送给 Java 应用集群中其他 Hermes-SDK 节点;其他Hermes-SDK节点的 通信模块 收到 “ key 值失效”事件后,会调用 热点模块 将 key 在本地缓存的 value 值失效,以达到集群数据最终一致;3)热点发现Hermes服务端集群 不断收集 Hermes-SDK上报的 key访问事件,对不同业务应用集群的缓存访问数据进行周期性(3s一次)分析计算,以探测业务应用集群中的热点key列表;对于探测到的热点key列表,Hermes服务端集群 将其通过 etcd集群 推送给不同业务应用集群的 Hermes-SDK 通信模块,通知其对热点key列表进行本地缓存;4)配置读取Hermes-SDK 在启动及运行过程中,会从 Apollo配置中心 读取其关心的配置信息(如:启动关闭配置、黑白名单配置、etcd地址…);Hermes服务端集群 在启动及运行过程中,会从 Apollo配置中心 读取其关心的配置信息(如:业务应用列表、热点阈值配置、 etcd 地址…);3-2-3. 稳定性TMC本地缓存稳定性表现在以下方面:数据上报异步化:Hermes-SDK 使用rsyslog技术对“ key 访问事件”进行异步化上报,不会阻塞业务;通信模块线程隔离:Hermes-SDK 的 通信模块 使用独立线程池+有界队列,保证事件上报&监听的I/O操作与业务执行线程隔离,即使出现非预期性异常也不会影响基本业务功能;缓存管控:Hermes-SDK 的 热点模块 对本地缓存大小上限进行了管控,使其占用内存不超过 64MB(LRU),杜绝 JVM 堆内存溢出的可能;3-2-4. 一致性TMC 本地缓存一致性表现在以下方面:Hermes-SDK 的 热点模块 仅缓存 热点key 数据,绝大多数非热点 key 数据由 缓存集群 存储;热点key 变更导致 value 失效时,Hermes-SDK 同步失效本地缓存,保证 本地强一致;热点key 变更导致 value 失效时,Hermes-SDK 通过 etcd集群 广播事件,异步失效业务应用集群中其他节点的本地缓存,保证 集群最终一致;四、TMC热点发现4-1. 整体流程TMC 热点发现流程分为四步:数据收集:收集 Hermes-SDK 上报的 key访问事件;热度滑窗:对 App 的每个 Key ,维护一个时间轮,记录基于当前时刻滑窗的访问热度;热度汇聚:对 App 的所有 Key ,以<key,热度>的形式进行 热度排序汇总;热点探测:对 App ,从 热Key排序汇总 结果中选出 TopN的热点Key ,推送给 Hermes-SDK;4-2. 数据收集Hermes-SDK 通过本地rsyslog将 key访问事件 以协议格式放入 kafka ,Hermes服务端集群 的每个节点消费 kafka 消息,实时获取 key访问事件。访问事件协议格式如下:appName:集群节点所属业务应用uniqueKey:业务应用 key访问事件 的 keysendTime:业务应用 key访问事件 的发生时间weight:业务应用 key访问事件 的访问权值Hermes服务端集群 节点将收集到的 key访问事件 存储在本地内存中,内存数据结构为Map<String, Map<String, LongAdder>>,对应业务含义映射为Map< appName , Map< uniqueKey , 热度 >>。4-3. 热度滑窗4-3-1. 时间滑窗Hermes服务端集群 节点,对每个App的每个 key ,维护了一个 时间轮:时间轮中共10个 时间片,每个时间片记录当前 key 对应 3 秒时间周期的总访问次数;时间轮10个时间片的记录累加即表示当前 key 从当前时间向前 30 秒时间窗口内的总访问次数;4-3-2. 映射任务Hermes服务端集群 节点,对每个 App 每3秒 生成一个 映射任务 ,交由节点内 “缓存映射线程池” 执行。映射任务 内容如下:对当前 App ,从Map< appName , Map< uniqueKey , 热度 >>中取出 appName 对应的Map Map< uniqueKey , 热度 >>;遍历Map< uniqueKey , 热度 >>中的 key ,对每个 key 取出其热度存入其 时间轮 对应的时间片中;4-4. 热度汇聚完成第二步“热度滑窗”后,映射任务 继续对当前 App 进行“热度汇聚”工作:遍历 App 的 key ,将每个 key 的 时间轮 热度进行汇总(即30秒时间窗口内总热度)得到探测时刻 滑窗总热度;将 < key , 滑窗总热度 > 以排序集合的方式存入 Redis存储服务 中,即 热度汇聚结果;4-5. 热点探测在前几步,每3秒 一次的 映射任务 执行,对每个 App 都会产生一份当前时刻的 热度汇聚结果 ;Hermes服务端集群 中的“热点探测”节点,对每个 App ,只需周期性从其最近一份 热度汇聚结果 中取出达到热度阈值的 TopN 的 key 列表,即可得到本次探测的 热点key列表;TMC 热点发现整体流程如下图:4-6. 特性总结4-6-1. 实时性Hermes-SDK基于rsyslog + kafka 实时上报 key访问事件。映射任务 3秒一个周期完成“热度滑窗” + “热度汇聚”工作,当有 热点访问场景 出现时最长3秒即可探测出对应 热点key。4-6-2. 准确性key 的热度汇聚结果由“基于时间轮实现的滑动窗口”汇聚得到,相对准确地反应当前及最近正在发生访问分布。4-6-3.扩展性Hermes服务端集群 节点无状态,节点数可基于 kafka 的 partition 数量横向扩展。“热度滑窗” + “热度汇聚” 过程基于 App 数量,在单节点内多线程扩展。五、TMC实战效果5-1. 快手商家某次商品营销活动有赞商家通过快手直播平台为某商品搞活动,造成该商品短时间内被集中访问产生访问热点,活动期间 TMC 记录的实际热点访问效果数据如下:5-1-1. 某核心应用的缓存请求&命中率曲线图上图蓝线为应用集群调用get()方法访问缓存次数上图绿线为获取缓存操作命中 TMC 本地缓存的次数上图为本地缓存命中率曲线图可以看出活动期间缓存请求量及本地缓存命中量均有明显增长,本地缓存命中率达到近 80% (即应用集群中 80% 的缓存查询请求被 TMC 本地缓存拦截)。5-1-2. 热点缓存对应用访问的加速效果上图为应用接口QPS曲线上图为应用接口RT曲线可以看出活动期间应用接口的请求量有明显增长,由于 TMC 本地缓存的效果应用接口的 RT 反而出现下降。5-2. 双十一期间部分应用 TMC 效果展示5-2-1. 商品域核心应用效果5-2-2. 活动域核心应用效果六、TMC功能展望在有赞, TMC 目前已为商品中心、物流中心、库存中心、营销活动、用户中心、网关&消息等多个核心应用模块提供服务,后续应用也在陆续接入中。TMC 在提供“热点探测” + “本地缓存”的核心能力同时,也为应用服务提供了灵活的配置选择,应用服务可以结合实际业务情况在“热点阈值”、“热点key探测数量”、“热点黑白名单”维度进行自由配置以达到更好的使用效果。最后, TMC 的迭代还在持续进行中… ...

November 26, 2018 · 2 min · jiezi

【源码阅读方法论】

周生政valgrind查看调用关系在学习开源代码时,我们希望有个工具能够给我们全局的视角而不过早的陷入细节的泥淖中。读书可以跳读,读代码也是可以跳读的。valgrind可以生成整个调用关系链。该关系链指导我们,迅速定位到我们关心的细节。安装ubuntu系统1 apt-get install valgrind2 apt-get install kcachegrindmac系统1 brew install qcachegrind –with-graphviz使用使用valgrind生成调用关系1 valgrind –tool=callgrind –trace-children=yes –callgrind-out-file=/data/opt/callgrind.out.1111 ./nginx使用qcachegrind查看调用关系qcachegrindhttps://raw.githubusercontent…可能遇到的问题Error: can not open cache simulation output file保证写的目录有写的权限,可以创建一个目录,赋值为777,在该目录下启动命令valgrind –tool=callgrind –trace-children=yes /data/server/nginx/sbin/nginx, 使用kill终止程序kill SIGINT pid

November 13, 2018 · 1 min · jiezi

有赞容器化实践

前言容器化已经成为一种趋势,它可以解决很多运维中的痛点,比如效率、成本、稳定性等问题,而接入容器的过程中往往也会碰到很多问题和不便。在有赞最开始做容器化是为了快速交付开发测试环境,在容器化的过程中,我们碰到过容器技术、运维体系适配、用户使用习惯改变等各种问题,本文主要介绍有赞容器化过程中碰到的问题以及采取的方案。有赞容器化的初衷在有赞同时会有很多个项目、日常在并行开发,环境的抢占问题严重影响了开发、测试和上线的效率,我们需要给每个项目提供一套开发联调(daily)、测试环境(qa),并且随着项目、日常的生命周期项目环境也会随着创建和销毁,我们最早的容器化需求就是怎么解决环境快速交付的问题。[有赞环境]上面是有赞大致的研发流程,在标准流程中我们有四套稳定环境,分别是 Daily 环境、Qa 环境、预发环境和测试环境。我们的开发、测试、联调工作一般并不会直接在稳定环境中进行,而是会拉一套独立的项目环境出来,随着代码经过开发、测试、预发验收最终发布到生产环境后再同步回 Daily/Qa 的稳定环境中。[项目环境]我们提供了一套以最小的资源投入满足最大项目并行度的环境交付方案,在 Daily/Qa 稳定环境的基础上,隔离出N个项目环境,在项目环境里只需要创建该项目所涉及应用的计算资源,其它缺失的服务调用由稳定环境提供,在项目环境里,我们大量使用了容器技术。[持续交付]后面我们又在项目环境快速交付的解决方案的基础上实现了持续交付流水线,目前已经有超过 600 套项目/持续交付环境,加上 Daily/Qa 稳定环境,涉及计算实例四五千个,这些计算实例无论是 cpu 还是内存使用率都是非常低的,容器化可以非常好的解决环境交付的效率问题,以及提高资源使用率来节省成本的投入。有赞容器化方案我们的容器化方案基于 kubernetes(1.7.10)和 docker(1.12.6)、docker(1.13.1),下面介绍一下我们在各个方面遇到的问题以及解决方案。网络有赞后端主要是 java 应用,采用定制的 dubbo 服务化方案,过程中无法做到整个单元全量容器化,和原有集群在网络路由上互通也就成了刚需,由于我们无法解决公有云上 overlay 网络和公有云网络的互通问题,所以一开始我们放弃了 overlay 网络方案,采用了托管网络下的 macvlan 方案,这样既解决了网络互通的问题也不存在网络性能问题,但是也就享受不到公有云弹性资源的优势了。随着有赞多云架构的发展以及越来越多的云厂商支持容器 overlay 网络和 vpc 网络打通,弹性资源的问题才得到了缓解。隔离性容器的隔离主要利用内核的 namespace 和 cgroup 技术,在进程、cpu、内存、IO等资源隔离限制上有比较好的表现,但其他方面和虚拟机相比存在着很多的不足,我们在使用过程中碰到最多的问题是容器里看到的 cpu 数和内存大小不准确,因为/proc文件系统无法隔离,导致容器里的进程"看到"的是物理机的 cpu 数以及内存大小。内存问题我们的 java 应用会根据服务器的内存大小来决定 jvm 参数应该怎么配置,我们是采用 lxcfs 方案来规避的。CPU 数的问题因为我们有超卖的需求以及 kubernetes 默认也是采用 cpu share 来做 cpu 限制,虽然我们使用了 lxcfs,CPU 数还是不准的。jvm 以及很多 Java sdk 都会根据系统的 CPU 数来决定创建多少线程,导致 java 应用在线程数和内存使用上都比虚拟机多的多,严重影响运行,其他类型的应用也有类似的问题。我们会根据容器的规格内置一个环境变量 NUM_CPUS,然后比如 nodejs 应用就会按照这个变量来创建它的 worker 进程数。在解决 java 类应用的问题时,我们索性通过 LD_PRELOAD 将 JVM_ActiveProcessorCount 函数覆盖掉,让它直接返回 NUM_CPUS 的值[1]。应用接入在容器化之前,有赞的应用已经全部接入到发布系统,在发布系统里已经标准化了应用的打包、发布流程,所以在应用接入方面成本还是比较小的,业务方无需提供 Dockerfile。nodejs, python,php-soa 等用 supervisord 托管的应用,只需要在 git 仓库里提供 app.yaml 文件定义运行需要的 runtime 和启动命令即可。java 标准化启动的应用业务方无需改动java 非标准化的应用需要做标准化改造镜像集成容器镜像我们分了三层,依次为 stack 层(os),runtime 层(语言环境),应用层(业务代码和一些辅助agent),应用以及辅助 agent 由 runit 来启动。由于我们的配置还没有完全分离,在应用层目前还是每个环境独立打包,镜像里除了业务代码之外,我们还会根据业务的语言类型放一些辅助的 agent。我们一开始也想将各种 agent 拆成多个镜像,然后每个 pod 运行多个容器,后来因为解决不了 pod 里容器的启动顺序(服务启动有依赖)问题,就把所有服务都扔到一个容器里去运行了。我们的容器镜像集成过程也是通过 kubernetes 来调度的(会调度到指定的打包节点上),在发布任务发起时,管控系统会在集群中创建一个打包的 pod,打包程序会根据应用类型等参数编译代码、安装依赖,并且生成 Dockerifile,然后在这个 pod 中使用 docker in docker 的方式来集成容器镜像并推送到仓库。为了加速应用的打包速度,我们用 pvc 缓存了 python 的 virtualenv,nodejs 的 node_modules,java 的 maven 包等文件。另外就是 docker 早的版本里,Dockerfile ADD 指令是不支持指定文件属主和分组的,这样会带来一个问题就是需要指定文件属主时(我们的应用是以 app 账号运行的)需要多运行一次 RUN chown,这样镜像也就多了一层数据,所以我们打包节点的 docker 版本采用了官方比较新的 ce 版本,因为新版本支持 ADD –chown 特性。负载均衡(ingress)有赞的应用内部调用有比较完善的服务化和 service mesh 方案,集群内的访问不用过多考虑,负载均衡只需要考虑用户和系统访问的 http 流量,在容器化之前我们已经自研了一套统一接入系统,所以在容器化负载均衡上我们并没有完整按照 ingress 的机制来实现 controller,ingress 的资源配置是配在统一接入里的,配置里面转发的 upstream 会和 kubernetes 里的 service 关联,我们只是做了一个 sync 程序 watch kube-api,感知 service 的变化来实时更新统一接入系统中 upstream 的服务器列表信息。容器登录和调试在容器化接入过程中开发会反馈是控制台比较难用,虽然我们优化了多次,和 iterm2 等的体验还是有所不足,最终我们还是放开了项目/持续交付环境这种需要频繁登陆调试的 ssh 登陆权限。另外一个比较严重的问题是,当一个应用启动后健康检查有问题会导致 pod 一直在重新调度,而在开发过程中开发肯定是希望看到失败现场的,我们提供了调试发布模式,让容器不做健康检查。日志有赞有专门的日志系统,我们内部叫天网,大部分日志以及业务监控数据都是通过 sdk 直接打到天网里去了,所以容器的标准输出日志仅仅作为一种辅助排查问题的手段。我们容器的日志收集采用的是 fluentd,经过 fluentd 处理后按照天网约定的日志格式打到 kafka,最终由天网处理进入 es 做存储。灰度发布我们涉及到灰度发布的流量主要包含三部分:用户端的 http 访问流量应用之间的 http 调用应用之间的 dubbo 调用首先,我们在入口的统一接入上统一打上灰度需要用的各种维度的标签(比如用户、店铺等),然后需要对统一接入、http client 以及 dubbo client 做改造,目的是让这些标签能够在整个调用链上透传。我们在做容器灰度发布时,会发一个灰度的 deployment,然后在统一接入以及灰度配置中心配置灰度规则,整个链路上的调用方都会感知这些灰度规则来实现灰度发布。标准环境容器化标准环境的出发点和项目环境类似,标准稳定环境中的 daily,qa,pre 以及 prod 中超过一半运行在低水位的服务器的资源非常浪费。因为成本考虑 daily,qa,pre 里都是以单台虚拟机运行的,这样一旦需要发布稳定环境将会造成标准稳定环境和项目环境的短暂不可用。虚拟机交付速度比较慢,使用虚拟机做灰度发布也比较复杂。虚拟机往往会存在几年甚至更长的时间,运行过程中操作系统以及基础软件版本的收敛非常麻烦。标准环境容器化推进经过之前项目/持续交付的上线和迭代,大部分应用本身已经具备了容器化的条件。不过对于上线来说,需要整个运维体系来适配容器化,比如监控、发布、日志等等。目前我们生产环境容器化准备基本完成,生产网已经上了部分前端 nodejs 应用,其他应用也在陆续推动中,希望以后可以分享更多生产环境中的容器化经验。结束语以上是有赞在容器化上的应用,以及在容器化过程中碰到的一些问题和解决方案,我们生产环境的容器化还处于开始阶段,后面还会碰到各种个样的问题,希望能够和大家互相学习,后面能够有更多的经验分享给大家。参考文献[1] https://github.com/fabianenar… ...

September 29, 2018 · 1 min · jiezi

有赞搜索系统的架构演进

有赞搜索平台是一个面向公司内部各项搜索应用以及部分 NoSQL 存储应用的 PaaS 产品,帮助应用合理高效的支持检索和多维过滤功能,有赞搜索平台目前支持了大大小小一百多个检索业务,服务于近百亿数据。在为传统的搜索应用提供高级检索和大数据交互能力的同时,有赞搜索平台还需要为其他比如商品管理、订单检索、粉丝筛选等海量数据过滤提供支持,从工程的角度看,如何扩展平台以支持多样的检索需求是一个巨大的挑战。我是有赞搜索团队的第一位员工,也有幸负责设计开发了有赞搜索平台到目前为止的大部分功能特性,我们搜索团队目前主要负责平台的性能、可扩展性和可靠性方面的问题,并尽可能降低平台的运维成本以及业务的开发成本。ElasticsearchElasticsearch 是一个高可用分布式搜索引擎,一方面技术相对成熟稳定,另一方面社区也比较活跃,因此我们在搭建搜索系统过程中也是选择了 Elasticsearch 作为我们的基础引擎。架构1.0时间回到 2015 年,彼时运行在生产环境的有赞搜索系统是一个由几台高配虚拟机组成的 Elasticsearch 集群,主要运行商品和粉丝索引,数据通过 Canal 从 DB 同步到 Elasticsearch,大致架构如下:通过这种方式,在业务量较小时,可以低成本的快速为不同业务索引创建同步应用,适合业务快速发展时期,但相对的每个同步程序都是单体应用,不仅与业务库地址耦合,需要适应业务库快速的变化,如迁库、分库分表等,而且多个 canal 同时订阅同一个库,也会造成数据库性能的下降。另外 Elasticsearch 集群也没有做物理隔离,有一次促销活动就因为粉丝数据量过于庞大导致 Elasticsearch 进程 heap 内存耗尽而 OOM,使得集群内全部索引都无法正常工作,这给我上了深深的一课。架构 2.0我们在解决以上问题的过程中,也自然的沉淀出了有赞搜索的 2.0 版架构,大致架构如下:首先数据总线将数据变更消息同步到 mq,同步应用通过消费 mq 消息来同步业务库数据,借数据总线实现与业务库的解耦,引入数据总线也可以避免多个 canal 监听消费同一张表 binlog 的虚耗。高级搜索(Advanced Search)随着业务发展,我们也逐渐出现了一些比较中心化的流量入口,比如分销、精选等,这时普通的 bool 查询并不能满足我们对搜索结果的细粒率排序控制需求,将复杂的 function_score 之类专业性较强的高级查询编写和优化工作交给业务开发负责显然是个不可取的选项,这里我们考虑的是通过一个高级查询中间件拦截业务查询请求,在解析出必要的条件后重新组装为高级查询交给引擎执行,大致架构如下:这里另外做的一点优化是加入了搜索结果缓存,常规的文本检索查询 match 每次执行都需要实时计算,在实际的应用场景中这并不是必须的,用户在一定时间段内(比如 15 或 30 分钟)通过同样的请求访问到同样的搜索结果是完全可以接受的,在中间件做一次结果缓存可以避免重复查询反复执行的虚耗,同时提升中间件响应速度,对高级搜索比较感兴趣的同学可以阅读另外一篇文章《有赞搜索引擎实践(工程篇)》,这里不再细述。大数据集成搜索应用和大数据密不可分,除了通过日志分析来挖掘用户行为的更多价值之外,离线计算排序综合得分也是优化搜索应用体验不可缺少的一环,在 2.0 阶段我们通过开源的 es-hadoop 组件搭建 hive 与 Elasticsearch 之间的交互通道,大致架构如下:通过 flume 收集搜索日志存储到 hdfs 供后续分析,也可以在通过 hive 分析后导出作为搜索提示词,当然大数据为搜索业务提供的远不止于此,这里只是简单列举了几项功能。问题这样的架构支撑了搜索系统一年多的运行,但是也暴露出了许多问题,首当其冲的是越发高昂的维护成本,除去 Elasticsearch 集群维护和索引本身的配置、字段变更,虽然已经通过数据总线与业务库解耦,但是耦合在同步程序中的业务代码依旧为团队带来了极大的维护负担。消息队列虽然一定程序上减轻了我们与业务的耦合,但是带来的消息顺序问题也让不熟悉业务数据状态的我们很难处理。这些问题我总结在之前写过的一篇文章。除此之外,流经 Elasticsearch 集群的业务流量对我们来说呈半黑盒状态,可以感知,但不可预测,也因此出现过线上集群被内部大流量错误调用压到CPU占满不可服务的故障。目前的架构 3.0针对 2.0 时代的问题,我们在 3.0 架构中做了一些针对性调整,列举主要的几点:通过开放接口接收用户调用,与业务代码完全解耦;增加 proxy 用来对外服务,预处理用户请求并执行必要的流控、缓存等操作;提供管理平台简化索引变更和集群管理这样的演变让有赞搜索系统逐渐的平台化,已经初具了一个搜索平台的架构:Proxy作为对外服务的出入口,proxy 除了通过 ESLoader 提供兼容不同版本 Elasticsearch 调用的标准化接口之外,也内嵌了请求校验、缓存、模板查询等功能模块。请求校验主要是对用户的写入、查询请求进行预处理,如果发现字段不符、类型错误、查询语法错误、疑似慢查询等操作后以 fast fail 的方式拒绝请求或者以较低的流控水平执行,避免无效或低效能操作对整个 Elasticsearch 集群的影响。缓存和 ESLoader 主要是将原先高级搜索中的通用功能集成进来,使得高级搜索可以专注于搜索自身的查询分析和重写排序功能,更加内聚。我们在缓存上做了一点小小的优化,由于查询结果缓存通常来说带有源文档内容会比较大,为了避免流量高峰频繁访问导致 codis 集群网络拥堵,我们在 proxy 上实现了一个简单的本地缓存,在流量高峰时自动降级。这里提一下模板查询,在查询结构(DSL)相对固定又比较冗长的情况下,比如商品类目筛选、订单筛选等,可以通过模板查询(search template)来实现,一方面简化业务编排DSL的负担,另一方面还可以通过编辑查询模板 template,利用默认值、可选条件等手段在服务端进行线上查询性能调优。管理平台为了降低日常的索引增删、字段修改、配置同步上的维护成本,我们基于 Django 实现了最初版本的搜索管理平台,主要提供一套索引变更的审批流以及向不同集群同步索引配置的功能,以可视化的方式实现索引元数据的管理,减少我们在平台日常维护上的时间成本。由于开源 head 插件在效果展示上的不友好,以及暴露了部分粗暴功能:如图,可以通过点按字段使得索引按指定字段排序展示结果,在早期版本 Elasticsearch 会通过 fielddata 加载需要排序的字段内容,如果字段数据量比较大,很容易导致 heap 内存占满引发 full gc 甚至 OOM,为了避免重复出现此类问题,我们也提供了定制的可视化查询组件以支持用户浏览数据的需求。ESWriter由于 es-hadoop 仅能通过控制 map-reduce 个数来调整读写流量,实际上 es-hadoop 是以 Elasticsearch 是否拒绝请求来调整自身行为,对线上工作的集群相当不友好。为了解决这种离线读写流量上的不可控,我们在现有的 DataX 基础上开发了一个 ESWriter 插件,能够实现记录条数或者流量大小的秒级控制。挑战平台化以及配套的文档体系完善降低了用户的接入门槛,随着业务的快速增长,Elasticsearch 集群本身的运维成本也让我们逐渐不堪,虽然有物理隔离的多个集群,但不可避免的会有多个业务索引共享同一个物理集群,在不同业务间各有出入的生产标准上支持不佳,在同一个集群内部署过多的索引也是生产环境稳定运行的一个隐患。另外集群服务能力的弹性伸缩相对困难,水平扩容一个节点都需要经历机器申请、环境初始化、软件安装等步骤,如果是物理机还需要更长时间的机器采购过程,不能及时响应服务能力的不足。未来的架构 4.0当前架构通过开放接口接受用户的数据同步需求,虽然实现了与业务解耦,降低了我们团队自身的开发成本,但是相对的用户开发成本也变高了,数据从数据库到索引需要经历从数据总线获取数据、同步应用处理数据、调用搜索平台开放接口写入数据三个步骤,其中从数据总线获取数据与写入搜索平台这两个步骤在多个业务的同步程序中都会被重复开发,造成资源浪费。这里我们目前也准备与 PaaS 团队内自研的DTS(Data Transporter,数据同步服务)进行集成,通过配置化的方式实现 Elasticsearch 与多种数据源之间的自动化数据同步。要解决共享集群应对不同生产标准应用的问题,我们希望进一步将平台化的搜索服务提升为云化的服务申请机制,配合对业务的等级划分,将核心应用独立部署为相互隔离的物理集群,而非核心应用通过不同的应用模板申请基于 k8s 运行的 Elasticsearch 云服务。应用模板中会定义不同应用场景下的服务配置,从而解决不同应用的生产标准差异问题,而且云服务可以根据应用运行状况及时进行服务的伸缩容。小结本文从架构上介绍了有赞搜索系统演进产生的背景以及希望解决的问题,涉及具体技术细节的内容我们将会在本系列的下一篇文章中更新。 ...

September 5, 2018 · 1 min · jiezi