最近发布
  • 当心Local数据——XSS安全 XSS是一种注入类的攻击。本文忽略了我们日常碰到的xss问题和解决方案,而是针对前端页面Local数据的分析。 HTML获取数据的方式有三种,1)调用后端接口;2)调用第三方接口;3)读取本地数据。实际上就是remote数据和local数据。Local数据有两种:Cookie和LocalStorage。 攻击 前端进行xss需要两个条件: 有权限往数据存储端写数据读取被写入的数据 为了预防这种攻击,可以通过HTTPS+HSTS,在Http header中使用includeSubDomains可以防止所有的行为。但是,不是所有的网站都采用了这种方式。这里举一个例子来说明没有采用这种防治方式的网站,结果被攻击。 假设你是一个对安全特别上心的用户,一天,你在星巴克连接了他们的免密wifi。你知道在公共网络环境下,不应该输入敏感信息,比如,登陆网站的账号和密码。喝完咖啡回家以后,你连接到了安全的环境(家里的wifi),然后再访问某网站,并且输入了账号和密码。几天后,可能你的账号还是被盗了。一个例子 上面的例子中,黑客可能就采用了Local Persistence XSS Attack的方式。下面是你在公开网络下被攻击的过程。 连接不安全的网络准备访问“A网站”(不输入账号密码)访问Http连接被劫持被劫持的网页弹出广告,诱骗用户点击用户点击后,通过JS,注入攻击代码到A网站下的LocalStorage。用户断开不安全网络。连接信任的网络(比如Home)访问“A网站”,A网站运行JS代码,读取LocalStorage。攻击代码运行,启动键盘录屏,用户信息泄漏。 比如,A网站的代码可能会是这样: // 打开页面时,根据缓存的用户姓名,提示内容 function loadWelcomeInfo(){ var v = localStorage.getItem("userName"); document.write("<span>欢迎您回来!" + v…
  • 如何读论文 这篇文章的内容是从《极客时间》上,“左耳听风”的课(第90课)中摘录并整理的。整理的内容均是转载,已附上了原文链接。转载原因是防止原文莫名其妙的消失了。如果你想转载或者收藏,建议收藏原文。
  • 高可用:异地多活 有状态服务 后台服务可以划分为两类,有状态和无状态。高可用对于无状态的应用来说是比较简单的,无状态的应用,只需要通过F5或者任何代理的方式就可以很好的解决。后文描述的主要是针对有状态的服务进行分析。服务端进行状态维护主要是通过磁盘或内存进行保存,比如MySQL数据库,redis等内存数据库。除了这两种类型的维护方式,还有jvm的内存的状态维持,但jvm的状态生命周期通常很短。 高可用的一些解决方案 高可用,从发展来看,大致经过了这几个过程: 冷备双机热备同城双活异地双活异地多活 在聊异地多活的时候,还是先看一些其他的方案,这有利于我们理解很多设计的缘由。 冷备 冷备,通过停止数据库对外服务的能力,通过文件拷贝的方式将数据快速进行备份归档的操作方式。简而言之,冷备,就是复制粘贴,在linux上通过cp命令就可以很快完成。可以通过人为操作,或者定时脚本进行。有如下好处: 简单快速备份(相对于其他备份方式)快速恢复。只需要将备份文件拷贝回工作目录即完成恢复过程(亦或者修改数据库的配置,直接将备份的目录修改为数据库工作目录)。更甚,通过两次mv命令就可瞬间完成恢复。可以按照时间点恢复。比如,几天前发生的拼多多优惠券漏洞被人刷掉很多钱,可以根据前一个时间点进行还原,“挽回损失”。 以上的好处,对于以前的软件来说,是很好的方式。但是对于现如今的很多场景,已经不好用了,因为: 服务需要停机。n个9肯定无法做到了。然后,以前我们的停机冷备是在凌晨没有人使用的时候进行,但是现在很多的互联网应用已经是面向全球了,所以,任何时候都是有人在使用的。数据丢失。如果不采取措施,那么在完成了数据恢复后,备份时间点到还原时间内的数据会丢失。传统的做法,是冷备还原以后,通过数据库日志手动恢复数据。比如通过redo日志,更甚者,我还曾经通过业务日志去手动回放请求恢复数据。恢复是极大的体力活,错误率高,恢复时间长。冷备是全量备份。全量备份会造成磁盘空间浪费,以及容量不足的问题,只能通过将备份拷贝到其他移动设备上解决。所以,整个备份过程的时间其实更长了。想象一下每天拷贝几个T的数据到移动硬盘上,需要多少移动硬盘和时间。并且,全量备份是无法定制化的,比如只备份某一些表,是无法做到的。 如何权衡冷备的利弊,是每个业务需要考虑的。 双机热备 热备,和冷备比起来,主要的差别是不用停机,一边备份一边提供服务。但还原的时候还是需要停机的。由于我们讨论的是和存储相关的,所以不将共享磁盘的方式看作双机热备。 Active/Standby模式 相当于1主1从,主节点对外提供服务,从节点作为backup。通过一些手段将数据从主节点同步到从节点,当故障发生时,将从节点设置为工作节点。数据同步的方式可以是偏软件层面,也可以是偏硬件层面的。偏软件层面的,比如mysql的master/slave方式,通过同步binlog的方式;sqlserver的订阅复制方式。偏硬件层面,通过扇区和磁盘的拦截等镜像技术,将数据拷贝到另外的磁盘。偏硬件的方式,也被叫做数据级灾备;偏软件的,被叫做应用级灾备。后文谈得更多的是应用级灾备。 双机互备 本质上还是Active/Standby,只是互为主从而已。双机互备并不能工作于同一个业务,只是在服务器角度来看,更好的压榨了可用的资源。比如,两个业务分别有库A和B,通过两个机器P和Q进行部署。那么对于A业务,P主Q从,对于B业务,Q主P从。整体上看起来是两个机器互为主备。这种架构下,读写分离是很好的,单写多读,减少冲突又提高了效率。 其他的高可用方案还可以参考各类数据库的多种部署模式,比如mysql的主从、双主多从、MHA;redis的主从,哨兵,cluster等等。 同城双活 前面讲到的几种方案,基本都是在一个局域网内进行的。业务发展到后面,有了同城多活的方案。和前面比起来,不信任的粒度从机器转为了机房。这种方案可以解决某个IDC机房整体挂掉的情况(停电,断网等)。 同城双活其实和前文提到的双机热备没有本质的区别,只是“距离”更远了,基本上还是一样(同城专线网速还是很快的)。双机热备提供了灾备能力,双机互备避免了过多的资源浪费。 在程序代码的辅助下,有的业务还可以做到真正的双活,即同一个业务,双主,同时提供读写,只要处理好冲突的问题即可。需要注意的是,并不是所有的业务都能做到。 业界更多采用的是两地三中心的做法。远端的备份机房能更大的提供灾备能力,能更好的抵抗地震,恐袭等情况。双活的机器必须部署到同城,距离更远的城市作为灾备机房。灾备机房是不对外提供服务的,只作为备份使用,发生故障了才切流量到灾备机房;或者是只作为数据备份。原因主要在于:距离太远,网络延迟太大。 图1…
  • Kafka丢消息的处理 一个示意图 Kafka存在丢消息的问题,消息丢失会发生在Broker,Producer和Consumer三种。 Broker Broker丢失消息是由于Kafka本身的原因造成的,kafka为了得到更高的性能和吞吐量,将数据异步批量的存储在磁盘中。消息的刷盘过程,为了提高性能,减少刷盘次数,kafka采用了批量刷盘的做法。即,按照一定的消息量,和时间间隔进行刷盘。这种机制也是由于linux操作系统决定的。将数据存储到linux操作系统种,会先存储到页缓存(Page cache)中,按照时间或者其他条件进行刷盘(从page cache到file),或者通过fsync命令强制刷盘。数据在page cache中时,如果系统挂掉,数据会丢失。 Broker在linux服务器上高速读写以及同步到Replica 上图简述了broker写数据以及同步的一个过程。broker写数据只写到PageCache中,而pageCache位于内存。这部分数据在断电后是会丢失的。pageCache的数据通过linux的flusher程序进行刷盘。刷盘触发条件有三: 主动调用sync或fsync函数可用内存低于阀值dirty data时间达到阀值。dirty是pagecache的一个标识位,当有数据写入到pageCache时,pagecache被标注为dirty,数据刷盘以后,dirty标志清除。 Broker配置刷盘机制,是通过调用fsync函数接管了刷盘动作。从单个Broker来看,pageCache的数据会丢失。 Kafka没有提供同步刷盘的方式。同步刷盘在RocketMQ中有实现,实现原理是将异步刷盘的流程进行阻塞,等待响应,类似ajax的callback或者是java的future。下面是一段rocketmq的源码。 GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes()); service.putRequest(request); boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());…
  • IBM红皮书:Java微服务最佳实践(译) 本文是个人翻译,文中可能存在很多错误,翻译出于学习目的。正文中截图来自于英文原文中的截图,并添加了水印。 侵删。原文:https://www.redbooks.ibm.com/abstracts/sg248357.html。 作者:Michael Hofmann, Erin Schnabel, Katherine Stanley。 This article is a translation. Because this article is not for ANY commercial use, just for study,…

当心Local数据——XSS安全

XSS是一种注入类的攻击。本文忽略了我们日常碰到的xss问题和解决方案,而是针对前端页面Local数据的分析。 HTML获取数据的方式有三种,1)调用后端接口;2)调用第三方接口;3)读取本地数据。实际上就是remote数据和local数据。Local数据有两种:Cookie和LocalStorage。 攻击 前端进行xss需要两个条件: 有权限往数据存储端写数据 读取被写入的数据 为了预防这种攻击,可以通过HTTPS+HSTS,在Http header中使用includeSubDomains可以防止所有的行为。但是,不是所有的网站都采用了这种方式。这里举一个例子来说明没有采用这种防治方式的网站,结果被攻击。 假设你是一个对安全特别上心的用户,一天,你在星巴克连接了他们的免密wifi。你知道在公共网络环境下,不应该输入敏感信息,比如,登陆网站的账号和密码。喝完咖啡回家以后,你连接到了安全的环境(家里的wifi),然后再访问某网站,并且输入了账号和密码。几天后,可能你的账号还是被盗了。 一个例子 上面的例子中,黑客可能就采用了Local Persistence XSS Attack的方式。下面是你在公开网络下被攻击的过程。 连接不安全的网络 准备访问“A网站”(不输入账号密码) 访问Http连接被劫持 被劫持的网页弹出广告,诱骗用户点击 用户点击后,通过JS,注入攻击代码到A网站下的LocalStorage。 用户断开不安全网络。连接信任的网络(比如Home) 访问“A网站”,A网站运行JS代码,读取LocalStorage。攻击代码运行,启动键盘录屏,用户信息泄漏。 比如,A网站的代码可能会是这样: 假设,被攻击以后,黑客在你的storage写的是这个内容: 看到了吧,localStorage并不一定是你自己写进去的。 防御 对于开发人员 对于文本类信息,在读取localStorage数据,并渲染DOM时,进行Encoding,或者过滤拦截。对于结构化数据,使用JSON.parse,而不要使用eval。或者,使用DOMPurity之类的工具,移除所有js代码。 总之,前端开发人员不光是不能相信后端的数据,连自己的数据,最好都不要过于相信。 对于用户 不要随便点击广告 在公共环境下,不要随便输入敏感信息。如果要用,可以考虑用隐身模式,或者使用完毕后,清理掉使用痕迹(比如清理最近一小时的使用痕迹)。

如何读论文

这篇文章的内容是从《极客时间》上,“左耳听风”的课(第90课)中摘录并整理的。整理的内容均是转载,已附上了原文链接。转载原因是防止原文莫名其妙的消失了。如果你想转载或者收藏,建议收藏原文。

高可用:异地多活

有状态服务 后台服务可以划分为两类,有状态和无状态。高可用对于无状态的应用来说是比较简单的,无状态的应用,只需要通过F5或者任何代理的方式就可以很好的解决。后文描述的主要是针对有状态的服务进行分析。服务端进行状态维护主要是通过磁盘或内存进行保存,比如MySQL数据库,redis等内存数据库。除了这两种类型的维护方式,还有jvm的内存的状态维持,但jvm的状态生命周期通常很短。 高可用的一些解决方案 高可用,从发展来看,大致经过了这几个过程: 冷备 双机热备 同城双活 异地双活 异地多活 在聊异地多活的时候,还是先看一些其他的方案,这有利于我们理解很多设计的缘由。 冷备 冷备,通过停止数据库对外服务的能力,通过文件拷贝的方式将数据快速进行备份归档的操作方式。简而言之,冷备,就是复制粘贴,在linux上通过cp命令就可以很快完成。可以通过人为操作,或者定时脚本进行。有如下好处: 简单 快速备份(相对于其他备份方式) 快速恢复。只需要将备份文件拷贝回工作目录即完成恢复过程(亦或者修改数据库的配置,直接将备份的目录修改为数据库工作目录)。更甚,通过两次mv命令就可瞬间完成恢复。 可以按照时间点恢复。比如,几天前发生的拼多多优惠券漏洞被人刷掉很多钱,可以根据前一个时间点进行还原,“挽回损失”。 以上的好处,对于以前的软件来说,是很好的方式。但是对于现如今的很多场景,已经不好用了,因为: 服务需要停机。n个9肯定无法做到了。然后,以前我们的停机冷备是在凌晨没有人使用的时候进行,但是现在很多的互联网应用已经是面向全球了,所以,任何时候都是有人在使用的。 数据丢失。如果不采取措施,那么在完成了数据恢复后,备份时间点到还原时间内的数据会丢失。传统的做法,是冷备还原以后,通过数据库日志手动恢复数据。比如通过redo日志,更甚者,我还曾经通过业务日志去手动回放请求恢复数据。恢复是极大的体力活,错误率高,恢复时间长。 冷备是全量备份。全量备份会造成磁盘空间浪费,以及容量不足的问题,只能通过将备份拷贝到其他移动设备上解决。所以,整个备份过程的时间其实更长了。想象一下每天拷贝几个T的数据到移动硬盘上,需要多少移动硬盘和时间。并且,全量备份是无法定制化的,比如只备份某一些表,是无法做到的。 如何权衡冷备的利弊,是每个业务需要考虑的。 双机热备 热备,和冷备比起来,主要的差别是不用停机,一边备份一边提供服务。但还原的时候还是需要停机的。由于我们讨论的是和存储相关的,所以不将共享磁盘的方式看作双机热备。 Active/Standby模式 相当于1主1从,主节点对外提供服务,从节点作为backup。通过一些手段将数据从主节点同步到从节点,当故障发生时,将从节点设置为工作节点。数据同步的方式可以是偏软件层面,也可以是偏硬件层面的。偏软件层面的,比如mysql的master/slave方式,通过同步binlog的方式;sqlserver的订阅复制方式。偏硬件层面,通过扇区和磁盘的拦截等镜像技术,将数据拷贝到另外的磁盘。偏硬件的方式,也被叫做数据级灾备;偏软件的,被叫做应用级灾备。后文谈得更多的是应用级灾备。 双机互备 本质上还是Active/Standby,只是互为主从而已。双机互备并不能工作于同一个业务,只是在服务器角度来看,更好的压榨了可用的资源。比如,两个业务分别有库A和B,通过两个机器P和Q进行部署。那么对于A业务,P主Q从,对于B业务,Q主P从。整体上看起来是两个机器互为主备。这种架构下,读写分离是很好的,单写多读,减少冲突又提高了效率。 其他的高可用方案还可以参考各类数据库的多种部署模式,比如mysql的主从、双主多从、MHA;redis的主从,哨兵,cluster等等。 同城双活 前面讲到的几种方案,基本都是在一个局域网内进行的。业务发展到后面,有了同城多活的方案。和前面比起来,不信任的粒度从机器转为了机房。这种方案可以解决某个IDC机房整体挂掉的情况(停电,断网等)。 同城双活其实和前文提到的双机热备没有本质的区别,只是“距离”更远了,基本上还是一样(同城专线网速还是很快的)。双机热备提供了灾备能力,双机互备避免了过多的资源浪费。 在程序代码的辅助下,有的业务还可以做到真正的双活,即同一个业务,双主,同时提供读写,只要处理好冲突的问题即可。需要注意的是,并不是所有的业务都能做到。 业界更多采用的是两地三中心的做法。远端的备份机房能更大的提供灾备能力,能更好的抵抗地震,恐袭等情况。双活的机器必须部署到同城,距离更远的城市作为灾备机房。灾备机房是不对外提供服务的,只作为备份使用,发生故障了才切流量到灾备机房;或者是只作为数据备份。原因主要在于:距离太远,网络延迟太大。 如上图,用户流量通过负载均衡,将服务A的流量发送到IDC1,服务器集A;将服务B的流量发送到IDC2,服务器B;同时,服务器集a和b分别从A和B进行同城专线的数据同步,并且通过长距离的异地专线往IDC3进行同步。当任何一个IDC当机时,将所有流量切到同城的另一个IDC机房,完成了failover。当城市1发生大面积故障时,比如发生地震导致IDC1和2同时停止工作,则数据在IDC3得以保全。同时,如果负载均衡仍然有效,也可以将流量全部转发到IDC3中。不过,此时IDC3机房的距离非常远,网络延迟变得很严重,通常用户的体验的会受到严重影响的。 上图是一种基于Master-Slave模式的两地三中心示意图。城市1中的两个机房作为1主1从,异地机房作为从。也可以采用同城双主+keepalived+vip的方式,或者MHA的方式进行failover。但城市2不能(最好不要)被选择为Master。 异地双活 同城双活可以应对大部分的灾备情况,但是碰到大面积停电,或者自然灾害的时候,服务依然会中断。对上面的两地三中心进行改造,在异地也部署前端入口节点和应用,在城市1停止服务后将流量切到城市2,可以在降低用户体验的情况下,进行降级。但用户的体验下降程度非常大。 所以大多数的互联网公司采用了异地双活的方案。 上图是一个简单的异地双活的示意图。流量经过LB后分发到两个城市的服务器集群中,服务器集群只连接本地的数据库集群,只有当本地的所有数据库集群均不能访问,才failover到异地的数据库集群中。 在这种方式下,由于异地网络问题,双向同步需要花费更多的时间。更长的同步时间将会导致更加严重的吞吐量下降,或者出现数据冲突的情况。吞吐量和冲突是两个对立的问题,你需要在其中进行权衡。例如,为了解决冲突,引入分布式锁/分布式事务;为了解决达到更高的吞吐量,利用中间状态、错误重试等手段,达到最终一致性;降低冲突,将数据进行恰当的sharding,尽可能在一个节点中完成整个事务。 对于一些无法接受最终一致性的业务,饿了么采用的是下图的方式: 对于个别一致性要求很高的应用,我们提供了一种强一致的方案(Global Zone),Globa Zone是一种跨机房的读写分离机制,所有的写操作被定向到一个 Master 机房进行,以保证一致性,读操作可以在每个机房的 Slave库执行,也可以 bind 到 Master 机房进行,这一切都基于我们的数据库访问层(DAL)完成,业务基本无感知。 《饿了么异地多活技术实现(一)总体介绍》 也就是说,在这个区域是不能进行双活的。采用主从而不是双写,自然解决了冲突的问题。 实际上,异地双活和异地多活已经很像了,双活的结构更为简单,所以在程序架构上不用做过多的考虑,只需要做传统的限流,failover等操作即可。但其实双活只是一个临时的步骤,最终的目的是切换到多活。因为双活除了有数据冲突上的问题意外,还无法进行横向扩展。 异地多活…

Kafka丢消息的处理

Kafka存在丢消息的问题,消息丢失会发生在Broker,Producer和Consumer三种。 Broker Broker丢失消息是由于Kafka本身的原因造成的,kafka为了得到更高的性能和吞吐量,将数据异步批量的存储在磁盘中。消息的刷盘过程,为了提高性能,减少刷盘次数,kafka采用了批量刷盘的做法。即,按照一定的消息量,和时间间隔进行刷盘。这种机制也是由于linux操作系统决定的。将数据存储到linux操作系统种,会先存储到页缓存(Page cache)中,按照时间或者其他条件进行刷盘(从page cache到file),或者通过fsync命令强制刷盘。数据在page cache中时,如果系统挂掉,数据会丢失。 上图简述了broker写数据以及同步的一个过程。broker写数据只写到PageCache中,而pageCache位于内存。这部分数据在断电后是会丢失的。pageCache的数据通过linux的flusher程序进行刷盘。刷盘触发条件有三: 主动调用sync或fsync函数 可用内存低于阀值 dirty data时间达到阀值。dirty是pagecache的一个标识位,当有数据写入到pageCache时,pagecache被标注为dirty,数据刷盘以后,dirty标志清除。 Broker配置刷盘机制,是通过调用fsync函数接管了刷盘动作。从单个Broker来看,pageCache的数据会丢失。 Kafka没有提供同步刷盘的方式。同步刷盘在RocketMQ中有实现,实现原理是将异步刷盘的流程进行阻塞,等待响应,类似ajax的callback或者是java的future。下面是一段rocketmq的源码。 也就是说,理论上,要完全让kafka保证单个broker不丢失消息是做不到的,只能通过调整刷盘机制的参数缓解该情况。比如,减少刷盘间隔,减少刷盘数据量大小。时间越短,性能越差,可靠性越好(尽可能可靠)。这是一个选择题。 为了解决该问题,kafka通过producer和broker协同处理单个broker丢失参数的情况。一旦producer发现broker消息丢失,即可自动进行retry。除非retry次数超过阀值(可配置),消息才会丢失。此时需要生产者客户端手动处理该情况。那么producer是如何检测到数据丢失的呢?是通过ack机制,类似于http的三次握手的方式。 The number of acknowledgments the producer requires the leader to have received before considering a request complete. This controls the durability of records that are sent. The following settings are allowed: acks=0 If set to zero then the producer will not wait…

IBM红皮书:Java微服务最佳实践(译)

本文是个人翻译,文中可能存在很多错误,翻译出于学习目的。正文中截图来自于英文原文中的截图,并添加了水印。 侵删。原文:https://www.redbooks.ibm.com/abstracts/sg248357.html。 作者:Michael Hofmann, Erin Schnabel, Katherine Stanley。 This article is a translation. Because this article is not for ANY commercial use, just for study, there maybe some errors in it. write to me if you find any. 正文开始了…

在https的情况下客户端需要加密吗?

https本质上是http+ssl,是在http和tcp层之间添加了一个ssl层。加密和解密是对编码来说几乎是透明的。复杂度低。在开发javaweb时,通常https的做法是通过在tomcat或者nginx中添加相应的证书和配置进行。也可以在调用apacha http或者其他 的http组件时,进行配置。下面针对上述情况依次分析。 https https的加密是由http client执行,解密是由http服务进行解密。比如,nginx解密,httpd解密,tomcat解密。这几种解密分为下面几个图: 上图中的三种模式的安全性和复杂性是不一样的。安全性上,1>2>3。1中数据的明文出现在tomcat容器中。2的明文出现在nginx之后。假如在机器上针对80端口抓包,则可以获取明文。3的安全性最低,整个内网环境中,全部是明文。在这种情况下,任何一个机器被攻破,都可能拿到整个网络的明文信息。 客户端加密 客户端加密和解密的过程,是可控程度最高的。很多文章中关于安全的讲解,都会提到“尽早加密,最迟解密”,只有当真正需要使用数据时才进行解密。采用这种方式,应用的安全性是最高的。比如,在https的过程中的任何环节进行抓包,拿到的都是密文。但方案的复杂度也更高,管理成本也更高。 可以简单认为,加密要求越高,复杂度越高。数据是否进行加密,需要根据业务特性和数据特性,采取不同的加密手段。比如,对于支付密码这种信息,是需要在编码过程中进行加密的。在支付宝和微信支付的sdk中,也是采取的这种做法。 在实际的开发过程中,https的加密信息其实是不太可靠的。因为程序和代码的安全级别,是开发人员才能知道的,而运维人员并不知道。但是在进行网络环境搭建和部署的时候,是运维人员执行的,很有可能采取的架构是通过统一的nginx机器进行https协议转http协议的操作。如果该nginx是部署到外网环境,问题就更严重了。所以,对于特别敏感的数据,仍然建议在编码的过程中进行加密。

通过jstack与jmap分析一次线上故障(转自cnblogs)

转自: kingszelda 文章所有内容转自cnblogs,侵删 一、发现问题 下面是线上机器的cpu使用率,可以看到从4月8日开始,随着时间cpu使用率在逐步增高,最终使用率达到100%导致线上服务不可用,后面重启了机器后恢复。 二、排查思路 简单分析下可能出问题的地方,分为5个方向: 1.系统本身代码问题 2.内部下游系统的问题导致的雪崩效应 3.上游系统调用量突增 4.http请求第三方的问题 5.机器本身的问题 三、开始排查 1.查看日志,没有发现集中的错误日志,初步排除代码逻辑处理错误。 2.首先联系了内部下游系统观察了他们的监控,发现一起正常。可以排除下游系统故障对我们的影响。 3.查看provider接口的调用量,对比7天没有突增,排除业务方调用量的问题。 4.查看tcp监控,TCP状态正常,可以排除是http请求第三方超时带来的问题。 5.查看机器监控,6台机器cpu都在上升,每个机器情况一样。排除机器故障问题。 即通过上述方法没有直接定位到问题。 四、解决方案 1.重启了6台中问题比较严重的5台机器,先恢复业务。保留一台现场,用来分析问题。2.查看当前的tomcat线程pid。 3.查看该pid下线程对应的系统占用情况。top -Hp 384 4.发现pid 4430 4431 4432 4433 线程分别占用了约40%的cpu 5.将这几个pid转为16进制,分别为114e 114f 1150 1151 6.下载当前的java线程栈 sudo -u tomcat jstack -l 384>/1.txt 7.查询5中对应的线程情况,发现都是gc线程导致的 8.dump java堆数据 sudo -u tomcat jmap -dump:live,format=b,file=/dump201612271310.dat 384 9.使用MAT加载堆文件,可以看到javax.crypto.JceSecurity对象占用了95%的内存空间,初步定位到问题。 MAT下载地址:http://www.eclipse.org/mat/ 10.查看类的引用树,看到BouncyCastleProvider对象持有过多。即我们代码中对该对象的处理方式是错误的,定位到问题。 五、代码分析 我们代码中有一块是这样写的…

故障复盘:数据库连接池

本文主要描述几次我司碰到的由于数据库连接池遇到的故障。上半部分描述了几个具体的案例,最后对数据库连接池导致的故障问题进行了总结,以便未来应对类似问题能够快速定位。 故障1:防火墙 本次故障发生于18年初。故障表现为,在生产环境中,某些应用偶尔会出现服务停止的现象。重启以后问题解决,但比较长的一段时间后又出现了该问题。为了重现该问题,必须等到系统度过了一个没有加班的周末可能才会出现。耗费了团队数周的时间。问题解决小组一度没有思绪。 问题追踪:采用了以下方法进行故障的追踪 * jstat,jinfo,jmap等命令,查看堆栈信息,查看线程,定位代码 * 查看错误日志 * 查看系统配置 * 查看cat异常日志 通过java bin中的小工具,只查到了系统在数据库访问的线程中存在挂起状态,无法定位到某些固定的方法中,任意选取了一个报错的方法,走查代码没有发现任何问题。怀疑问题【不是】由于某一段业务代码或者dao层代码的逻辑导致,而是由于连接池配置导致。进而,查找了相关数据库连接池(druid)的官方文档,按照官方文档和搜索到的通用解决方案,进行了druid的参数配置,包括testOnReturn等各种参数的尝试。修改完成后,系统上线。 一周以后,问题又出来了。进一步检查各个业务代码,发现公司除了使用druid连接池以外,还使用了tomcat的连接池或者其他的连接池。作为架构组的一员,顿时就怀疑是业务组胡乱使用连接池(赶紧甩锅!),导致引入了连接池本身的bug造成了故障(并没有通过连接池源代码进行分析)。于是乎,大面积替换了各个业务线原有的连接池组件,替换为druid。并且进一步咨询DBA,参数配置是否合理,DBA也掏出他线上稳定运行的项目进行了参数的对比,确认无误,于是满怀细心的又部署上服务器了。 几天以后,问题又出来了。于是乎,怀疑是mysql的wait_timeout参数问题,该参数在mysql中默认是8小时,一旦达到8小时主动杀连接,于是乎,尝试修改该参数到7天,查看问题是否仍然存在。 一周以后,问题又出来了。故障上升了一个层次,由公司某领导过问,于是乎,DBA,开发,架构,测试,运维成立了问题小组,针对该问题进行复现和定位。不解决不回家。于是乎,我配合测试团队进行该故障的压力测试,尝试进行问题的重现。同时,dba查看mysql连接信息,运维排查服务器各项指标和网络联通。这样持续了几个小时以后… 问题依然没有重现!大家都要疯了。吃完几块饼干,挑灯夜战了。停止了压力测试,重新开始分析问题(其实还是在蒙)。又经过一轮的连接池参数调节,日志追踪,jvisualvm等,各种手段的折腾,又蒙了一版,准备部署上去看。这个时候测试人员习惯性的调用了一次接口。“太好了!挂了!”于是乎,又是一波骚操作,但是很可惜还是没有找到问题。解决的方法还是那些方法,各种jvm折腾,监控折腾,撸代码撸日志,远程debug打断点。各种搞不定。 最后,新来的一个DBA小哥提出一个他曾经碰到的坑,是由于他们公司环境的防火墙还有一个超时没有流量自动屏蔽连接信息(类似于移除iptables的操作),此时,应用服务器到数据库服务器的连通性出现问题,导致连接被断掉。大家一听,有道理,于是乎给服务器加了一个心跳。 问题解决了。解决方法还是靠蒙的,并没有什么真正定位到问题:) 故障2:mysql8小时故障 由于我们公司的业务特性,并不会有太多流量持续在跑,druid连接池的配置按照常规配置。但是仍然有的应用运行几天以后,出现了数据库连接超时的问题。查看日志,发现刚好8小时。直接就“蒙”问题原因是mysql的wait_timeout参数和druid的参数在某些情况下刚好出现了死连接的问题。由于数据库连接池中,每次只会死掉一部分连接,在开发的过程中,第一次失败了,第二次可能就成功了,所以扛到了连接池中出现了大量死连接才暴露出问题。 我们的解决方法是通过调节druid参数可以解决。当然也可以通过修改mysql的wait_timeout参数配置临时解决。其实还可以通过修改druid的minIdel参数,这个参数其实是可以非常小的,我们的业务场景中并不需要进行连接的“预热”操作,没有连接了创建一个就好了,偶尔创建连接的开销在我们的低效代码下,不是问题瓶颈啦。 故障3:nodejs的连接池的坑 公司的rap2是nodejs开发的,数据库连接池采用的nodejs的orm框架sequelize自带的pool。现象时运行一段时间,就有同事反馈rap2非常之慢。于是我马上打开一个网页,多次刷新,发现时而超时,时而正常。通过加日志,升级rap2,升级sequelize等方法后,重新构建部署。问题依然存在。此时,重启应用以后,问题解决,速度飞起。 一周以后,问题又出来了。但是通过刷新以后,页面通常就正常了,所以给大家的解决方案是:多次尝试刷新页面。但是我是知道问题仍然存在的…于是rap2就在大家不停的刷新下,艰难的服务着。 最终将问题反馈给rap2作者,并且阅读了sequelize相关的所有配置和参数,尝试进行了修复,无奈问题依然没有解决。确实不知道nodejs中如何像druid这样配置参数。至今问题依然存在,每过几周就需要找运维帮忙杀掉pod重启应用。只是尝试了将mysql的wait_timeout修改为3天,问题可以有所缓解,但是依然只能无止尽的reboot。如果你有该方面的解决方案,欢迎联系我。 进一步分析该问题,有可能是因为引入了redis缓存,大量的请求并不会将流量发到数据库中。后期可能会针对定期清理redis缓存、修改数据库连接池的minIdel参数等曲线救国的方法处理。 总结 总结起来数据库连接池的坑还是在于连接池组件本身。当数据库连接的中断不是由连接池组件发起时,连接池无法知晓该状态,最终导致了“连接泄漏”的情况。 数据库中断可能在多个环节产生:mysql,服务器,路由,防火墙,网络等等。任何环节都可能主动杀掉连接。 mysql8小时wait_timeout是一个经常碰到问题。 公司的底层框架针对该问题,尝试进行了处理:当任何一个连接失败后,控制连接池组件主动断掉所有的连接。 如果你还碰到其他的关于数据库方面的故障,欢迎你留言或者邮件我:mail@dongguochao.com 

树形结构的查询优化

(文中图片点击可以查看大图) 场景 公司部门结构。部门之间是上下级关系。员工属于某一个部门(一个员工能属于多个部门)。 结构如图 常用操作 查询某个组织及其下级组织的所有人员列表 查询某个人的同级职务的人员列表 变更组织结构,将org3(及其下属部门)合并到org1部门中,作为org1的子部门 假设员工张三,同时属于部门org1和org23,获取张三拥有的所有下属组织列表 传统设计 传统设计中,树形结构通过parent进行表的自关联。查询过程中,进行递归的操作,获取完整的树形信息。递归分为下面几种 单条sql递归(oracle有函数,mysql可以从网上找到function) java递归多条sql查询 java查询出全量db数据到内存,通过hashmap等数据结构进行组装,然后进行内存的递归 上面集中操作,代码复杂程度上来说,第二种是新人容易写出的实现。但是sql发起存在递归调用逻辑中,会发起大量的sql,当数据量大时会崩。 第一种是比较常见的一种实现,性能上比第二种高,sql语句逻辑可能会比较复杂,当数据量大(树形层次深,结构复杂)时,性能也会存在问题。 第三种性能比较好,但是需要在java代码中进行逻辑的拼装,java代码复杂度较高,维护性较差,当数据量太大时可能会出现oom(一次性读取全表数据)。 Code硬编码 如上图所示,可以观察到,组织的code是有规律的,有一定的含义。可以通过单个code知晓部分树形的结构。 — 查询org22的同级节点列表 select * from org where org.code like “org2_” — 查询org231的所有父节点(在java中拆分好所有的code列表) select * from org where org.code in (“org”, “org2”, “org23”) — 查询org22的所有直接子节点 select * from org where org.code like “org22_” — 查询org2的所有子节点(包含多级的子节点) select *…

高扇出类应用在发送消息时的设计

本文得益于《数据密集型应用系统设计》,第一章,可扩展性,描述负载中,对于twitter的分析。 场景是,twitter的消息设计,难点在于twitter的“关注”关系导致的“消息”的巨大扇出:每个人都会关注很多人,也会被很多人关注。被关注以后,自己发的消息别人可以看到。比如,A被B关注,A发推以后,B打开twitter,可以看到A发的消息。 方案有两种 方案一:利用关系型数据关的关联关系,做联表查询实时数据 SELECT tweets.*, users.* FROM tweets  JOIN users ON tweets.sender_id = users.id  JOIN follows ON follows.followee_id = users.id  WHERE follows.follower_id = current_user 方案二:每个用户发推时,将该消息同时写入到他所有关注着的“收件箱”中。有点类似于数据库的Master和Slave,Master在执行操作后,将binlog同步到Slave下。本方案每个用户登录以后,只需要查询自己的“收件箱”。 SELECT * from my_tweets where follower.id = current_user   显而易见,方案二的查询性能上比方案一好很多。方案一无论是做分库分表还是做任何优化,都始终存在巨大的复杂度和性能损失。但方案二还是存在另外一个问题,在创建消息的时候会产生大量的写操作。比如少数明星,会有几千万的粉丝,如果进行上千万的“insert”操作,性能和IO消耗也非常大,可能会造成“发推”需要比较长的时间。为解决该问题,可以同时采用方法1和2。标注出“明星”用户,明星用户的发推动作,只将消息写到自己的“发件箱”中,其他用户发消息,仍然采用方法2。 SELECT my_tweets.* FROM my_tweets WHERE follower.id = current_user  UNION ALL  SELECT stars_tweets.* FROM stars_tweets  WHERE stars_tweets.follower_id = current_user 上述方法2,由于明星属于极少部分用户,也可以理解为一种“消息中间件”的方式:明星发布消息到消息中间件,粉丝消费该消息。…