采用java 动静分离离架构.请问怎样的架构来更

一分钟了解互联网动静分离架构
一、静态页面
静态页面,是指互联网架构中,几乎不变的页面(或者变化频率很低),例如:
首页等html页面
js/css等样式文件
jpg/apk等资源文件
静态页面,有与之匹配的技术架构来加速,例如:
squid/varnish
二、动态页面
动态页面,是指互联网架构中,不同用户不同场景访问,都不一样的页面,例如:
百度搜索结果页
淘宝商品列表页
速运个人订单中心页
这些页面,不同用户,不同场景访问,大都会动态生成不同的页面。
动态页面,有与之匹配的技术架构,例如:
服务化架构
数据库,缓存架构
三、互联网动静分离架构
动静分离是指,静态页面与动态页面分开不同系统访问的架构设计方法。
一般来说:
静态页面访问路径短,访问速度快,几毫秒
动态页面访问路径长,访问速度相对较慢(数据库的访问,网络传输,业务逻辑计算),几十毫秒甚至几百毫秒,对架构扩展性的要求更高
静态页面与动态页面以不同域名区分
四、页面静态化
既然静态页面访问快,动态页面生成慢,有没有可能,将原本需要动态生成的站点提前生成好,使用静态页面加速技术来访问呢?
这就是互联网架构中的“页面静态化”优化技术。
举例,如下图,58同城的帖子详情页,原本是需要动态生成的:
浏览器发起http请求,访问/detail/x.shtml 详情页
web-server层从RESTful接口中,解析出帖子id是
service层通过DAO层拼装SQL语句,访问数据库
最终获取数据,拼装html返回浏览器
而“页面静态化”是指,将帖子ID为的帖子x.shtml提前生成好,由静态页面相关加速技术来加速:
这样的话,将极大提升访问速度,减少访问时间,提高用户体验。
五、页面静态化的适用场景
页面静态化优化后速度会加快,那能不能所有的场景都使用这个优化呢?哪些业务场景适合使用这个架构优化方案呢?
一切脱离业务的架构设计都是耍流氓,页面静态化,适用于:总数据量不大,生成静态页面数量不多的业务。例如:
58速运的城市页只有几百个,就可以用这个优化,只需提前生成几百个城市的“静态化页面”即可
一些二手车业务,只有几万量二手车库存,也可以提前生成这几万量二手车的静态页面
像58同城这样的信息模式业务,有几十亿的帖子量,就不太适合于静态化(碎片文件多,反而访问慢)
“页面静态化”是一种将原本需要动态生成的站点提前生成静态站点的优化技术。
总数据量不大,生成静态页面数量不多的业务,非常适合于“页面静态化”优化。
一分钟不长,希望逻辑清晰,希望大伙有收获。
责任编辑:
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
今日搜狐热点 上传我的文档
 上传文档
 下载
 收藏
该文档贡献者很忙,什么也没留下。
 下载此文档
平台网站架构设计说明书
下载积分:0
内容提示:平台网站架构设计说明书
文档格式:DOCX|
浏览次数:275|
上传日期: 18:00:52|
文档星级:
全文阅读已结束,此文档免费下载
下载此文档
该用户还上传了这些文档
平台网站架构设计说明书
关注微信公众号架构 | 我爱互联网
http://mp.weixin.qq.com/s/8iaO_uW0V8MIpLdg3kmd8g
工作也有几多年了,无论是身边遇到的还是耳间闻到的,多多少少也积攒了自己的一些经验和思考,当然,博主并没有太多接触高大上的分布式架构实践,相对比较零碎,随时补充(附带架构装逼词汇)。
俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的,当然对于我们开发人员来说,一个好的架构也不是一蹴而就的。
开始的开始,就是各种框架一搭,然后扔到Tomcat容器中跑就是了,这时候我们的文件,数据库,应用都在一个服务器上。
随着系统的的上线,用户量也会逐步上升,很明显一台服务器已经满足不了系统的负载,这时候,我们就要在服务器还没有超载的时候,提前做好准备。
由于我们是单体架构,优化架构在短时间内是不现实的,增加机器是一个不错的选择。这时候,我们可能要把应用和数据库服务单独部署,如果有条件也可以把文件服务器单独部署。
为了提升服务处理能力,我们在Tomcat容器前加一个代理服务器,我一般使用Nginx,当然你如果更熟悉apache也未尝不可。
用户的请求发送给反向代理,然后反向代理把请求转发到后端的服务器。
严格意义上来说,Nginx是属于web服务器,一般处理静态html、css、js请求,而Tomcat属于web容器,专门处理JSP请求,当然Tomcat也是支持html的,只是效果没Nginx好而已。
反向代理的优势,如下:
隐藏真实后端服务
负载均衡集群
高可用集群
缓存静态内容实现动静分离
静态文件压缩
解决多个服务跨域问题
合并静态请求(HTTP/2.0后已经被弱化)
SSL以及http2
基于以上Nginx反向代理,我们还可以实现动静分离,静态请求如html、css、js等请求交给Nginx处理,动态请求分发给后端Tomcat处理。
Nginx 升级到1.9.5+可以开启HTTP/2.0时代,加速网站访问。
当然,如果公司不差钱,CDN也是一个不错的选择。
在这分布式微服务已经普遍流行的年代,其实我们没必要踩过多的坑,就很容易进行拆分。市面上已经有相对比较成熟的技术,比如阿里开源的Dubbo(官方明确表示已经开始维护了),spring家族的spring cloud,当然具体如何去实施,无论是技术还是业务方面都要有很好的把控。
SpringCloud
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关——Netflix Zuul
分布式配置——Spring Cloud Config
微服务与轻量级通信
同步通信和异步通信
远程调用RPC
持续集成部署
服务拆分以后,随着而来的就是持续集成部署,你可能会用到以下工具。
Docker、Jenkins、Git、Maven
图片源于网络,基本拓扑结构如下所示:
整个持续集成平台架构演进到如下图所示:
Linux集群主要分成三大类( 高可用集群, 负载均衡集群,科学计算集群)。其实,我们最常见的也是生产中最常接触到的就是负载均衡集群。
负载均衡实现
DNS负载均衡,一般域名注册商的dns服务器不支持,但博主用的阿里云解析已经支持
四层负载均衡(F5、LVS),工作在TCP协议下
七层负载均衡(Nginx、haproxy),工作在Http协议下
分布式session
大家都知道,服务一般分为有状态和无状态,而分布式sessoion就是针对有状态的服务。
分布式Session的几种实现方式
基于数据库的Session共享
基于resin/tomcat web容器本身的session复制机制
基于oscache/Redis/memcached 进行 session 共享。
基于cookie 进行session共享
分布式Session的几种管理方式
Session Replication 方式管理 (即session复制)
简介:将一台机器上的Session数据广播复制到集群中其余机器上
使用场景:机器较少,网络流量较小
优点:实现简单、配置较少、当网络中有机器Down掉时不影响用户访问
缺点:广播式复制到其余机器有一定廷时,带来一定网络开销
Session Sticky 方式管理
简介:即粘性Session、当用户访问集群中某台机器后,强制指定后续所有请求均落到此机器上
使用场景:机器数适中、对稳定性要求不是非常苛刻
优点:实现简单、配置方便、没有额外网络开销
缺点:网络中有机器Down掉时、用户Session会丢失、容易造成单点故障
缓存集中式管理
简介:将Session存入分布式缓存集群中的某台机器上,当用户访问不同节点时先从缓存中拿Session信息
使用场景:集群中机器数多、网络环境复杂
优点:可靠性好
缺点:实现复杂、稳定性依赖于缓存的稳定性、Session信息放入缓存时要有合理的策略写入
目前生产中使用到的
基于tomcat配置实现的MemCache缓存管理session实现(麻烦)
基于OsCache和shiro组播的方式实现(网络影响)
基于spring-session+redis实现的(最适合)
负载均衡策略
负载均衡策略的优劣及其实现的难易程度有两个关键因素:一、负载均衡算法,二、对网络系统状况的检测方式和能力。
1、rr 轮询调度算法。顾名思义,轮询分发请求。
优点:实现简单
缺点:不考虑每台服务器的处理能力
2、wrr 加权调度算法。我们给每个服务器设置权值weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。
优点:考虑了服务器处理能力的不同
3、sh 原地址散列:提取用户IP,根据散列函数得出一个key,再根据静态映射表,查处对应的value,即目标服务器IP。过目标机器超负荷,则返回空。
4、dh 目标地址散列:同上,只是现在提取的是目标地址的IP来做哈希。
优点:以上两种算法的都能实现同一个用户访问同一个服务器。
5、lc 最少连接。优先把请求转发给连接数少的服务器。
优点:使得集群中各个服务器的负载更加均匀。
6、wlc 加权最少连接。在lc的基础上,为每台服务器加上权值。算法为:(活动连接数*256+非活动连接数)÷权重 ,计算出来的值小的服务器优先被选择。
优点:可以根据服务器的能力分配请求。
7、sed 最短期望延迟。其实sed跟wlc类似,区别是不考虑非活动连接数。算法为:(活动连接数+1)*256÷权重,同样计算出来的值小的服务器优先被选择。
8、nq 永不排队。改进的sed算法。我们想一下什么情况下才能“永不排队”,那就是服务器的连接数为0的时候,那么假如有服务器连接数为0,均衡器直接把请求转发给它,无需经过sed的计算。
9、LBLC 基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近被使用的服务器,把请求转发之,若该服务器超载,最采用最少连接数算法。
10、LBLCR 带复制的基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近使用的“服务器组”,注意,并不是具体某个服务器,然后采用最少连接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最少连接数算法,在集群的非本服务器组的服务器中,找出一台服务器出来,加入本服务器组,然后把请求转发之。
MySql主从配置,读写分离并引入中间件,开源的MyCat,阿里的DRDS都是不错的选择。
如果是对高可用要求比较高,但是又没有相应的技术保障,建议使用阿里云的RDS或者Redis相关数据库,省事省力又省钱。
如果有搜索业务需求,引入solr或者elasticsearch也是一个不错的选择,不要什么都塞进关系型数据库。
引入缓存无非是为了减轻后端数据库服务的压力,防止其”罢工”。
常见的缓存服务有,Ehcache、OsCache、MemCache、Redis,当然这些都是主流经得起考验的缓存技术实现,特别是Redis已大规模运用于分布式集群服务中,并证明了自己优越的性能。
异步通知:比如短信验证,邮件验证这些非实时反馈性的逻辑操作。
流量削锋:应该是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。
日志处理:系统中日志是必不可少的,但是如何去处理高并发下的日志确是一个技术活,一不小心可能会压垮整个服务。工作中我们常用到的开源日志ELK,为嘛中间会加一个Kafka或者redis就是这么一个道理(一群人涌入和排队进的区别)。
消息通讯:点对点通信(个人对个人)或发布订阅模式(聊天室)。
消息队列中提到的组间对于中小型创业供公司是一个不错的选择。
以上种种,没有安全做保证可能都会归于零。
阿里云的VPN虚拟专有网络以及安全组配置
自建机房的话,要自行配置防火墙安全策略
相关服务访问,比如Mysql、Redis、Solr等如果没有特殊需求尽量使用内网访问并设置鉴权
尽量使用代理服务器,不要对外开放过多的端口
https配合HTTP/2.0也是个不错的选择
架构装逼必备词汇
负载均衡(负载均衡算法)
服务降级(自动优雅降级)
分布式缓存
分布式事务
二阶段提交(强一致)
三阶段提交(强一致)
消息中间件(最终一致性),推荐阿里的RocketMQ
单体垂直扩容
单体水平扩容
数据库拆分
数据库分库分表
分布式任务
拒绝服务(DoS,Denial of Service)攻击
架构装逼必备工具
Linux(必备)、某软的
DNS、F5、LVS、Nginx、HAproxy、负载均衡SLB(阿里云)
分布式框架
Dubbo、Motan、Spring-Could
数据库中间件
DRDS (阿里云)、Mycat、360 Atlas、Cobar (不维护了)
RabbitMQ、ZeroMQ、Redis、ActiveMQ、Kafka
Zookeeper、Redis
Redis、Oscache、Memcache、Ehcache
Docker、Jenkins、Git、Maven
OSS、NFS、FastDFS、MogileFS
MySql、Redis、MongoDB、PostgreSQL、Memcache、HBase
专用网络VPC、弹性公网IP、CDN
文·blogchong
本文接上一篇,不清楚前面剧情的童鞋可以先看看。
这篇文章重点在于解决“数据虫巢官网”的底层数据问题,即那些分析数据的原始数据的来源。
结论很明显,当然是爬过来的,所以这篇我们将重点讲讲如何进行数据爬取,并且以虫巢官网的底层数据爬取代码为例子进行讲解。
当然,其中会一些常规的防爬机制破解以及应对的话题,整体来说这篇会偏重互联网公开数据集的爬取,即爬虫。
此外,整个数据虫巢官网的站点源代码,目前已经整理到github上咯,先放上github的链接:github.com/blogchong/mite8-com。
这是一个完整的项目,这部分代码包括以下部分:
1 整个数据虫巢数据处理后端框架。
2 前端每个页面JSP代码部分,以及涉及数据可视化渲染部分。
3 几个重点数据源的爬虫逻辑,以及定期更新爬虫数据的入口逻辑。
4 数据处理中涉及到的NLP部分,有几个侧重点,包括重构加工的分词工具,以及简单的情感分析,并且提供了分词的一个工具接口。
PS:如果感兴趣,clone之前别忘了给个star,哈哈。
爬虫框架 – Webcollector
回到主题,说到爬虫,由于我之前对于Python并不是很熟悉,而Java则是我的拿手好戏,并且目前市面上封装的爬虫工具很多,所以,我的考虑就是Java语言封装的Webcollector。
简单说一下这个框架,大伙儿感兴趣的可以去开源中国搜一下他的主页,对于Java不熟悉的朋友,其实也无所谓的,使用其他的Python框架一样是可以的,那这部分关于框架这块的就可以略过啦。
Webcollector支持各种自定义的遍历策略,这种在于路径规则不明确的时候使用是很有用的,比如我当时在爬取各大主流招聘网站的JD数据时,就通过这种模式做的,但如果是目的明确的,其实就是按照自己的业务逻辑去固定路径一次性获取数据了。
Webcollector对于Cookie、请求头之类的信息,提供了设置接口,可以很方便的伪装成浏览器,以及登录状态去爬取数据。
Webcollector集成了传统的JDBC持久化策略,可以很方便的将爬取的数据进行MySQL落地,以及MongDB落地等。
使用上也很方面,集成在Maven中,并且更新还是蛮及时的,所以需要集成到自己的Java代码中,只需要引入Jar包即可开整。
除此之外,Webcollector内部封装了selenium,对于动态加载的JS数据来说,也可以很轻松的拿到相关的数据。
其实上面基本都是它的一些特性,对于新手来说都太模糊,这个框架最好的地方在于作者提供了大量的博客实例,来解释各种特性,以及各种简单的爬虫实例可供参考,简直就是初学者的福音。
具体不再多说,想了解更多的,可以搜索然后进入进行学习。
不同的网站对于数据的展现以及输出方式可能都有点不同,静态的网页数据是最好获取的,比如我之前爬取一些偏传统的招聘网站的数据,直接通过入口就可以拿到数据,基本不设防。
代码例子:
CrawlDatum crawlDatum = new CrawlDatum(listUrl).putMetaData(“method”, “POST”);
HttpRequest request = new HttpRequest(crawlDatum.getUrl());
request.setMethod(crawlDatum.getMetaData(“method”));
HttpResponse httpResponse = request.getResponse();
Page page = new Page(crawlDatum, httpResponse);
我们拿到了HttpResponse对象,并且封装成Page对象,通过Page对象提供的Html解析方法,进行数据拆解。
其实Page底层的实现依然是Jsoup,一种很常规的Html结构解析包,我们来看一下具体的使用:
page.select(“div[class=review-content clearfix]”).text()
这是一个很常见的解析过程语法,在page对象中查找class名为“review-content clearfix ”的div,并且调用text方法,将内容转换为String。
静态页面,基本上会上门两招就够了,访问页面数据,然后解析数据,将非结构化的数据转换为结构化数据,当然具体怎么入库,在Java里方式就很多了。
除了静态页面之外,还有其他形式的数据获取。
比如现在很流行的一种做法,那就是前后端进行分离,即后端数据由额外的请求进行获取,再通过前端进行异步渲染。
其实这种做法也是有理由的,因为后端数据的请求跟前端其他部分渲染效率是不同,所以一般做成异步请求,这样在整个页面在后端效率不高时不会造成整个页面等待,提升用户效率。
这个时候,你单纯的看页面源码已经不行啦,你需要会使用浏览器的元素审查,把这些异步请求的链接给逮出来。
我在做雾霾影响分析报告时,基础原始数据是京东的口罩购买数据,并且是评论数据,其评论就是异步加载获取的。
通过F12做元素审查,找到评论数据的真正调用接口,一般异步操作都是放到JS里,并且接口在命名上有一定的提示,如上图就是京东商品的评论数据接口。
大概链接长这样子:
https://sclub.jd.com/comment/productPageComments.action?productId=2582352&score=3&sortType=3&page=0&pageSize=10&isShadowSku=0&callback=fetchJSON_comment98vv47364
里头有控制翻页的参数,我们控制部分参数就可以愉快的获取到数据啦,我们再把callback参数去掉,就是实打实的JSON数据了,连清洗数据的活都省了。
除此之外,还有一个需要注意的点就是,控制访问频度,不管你是单机爬着玩也好,或者是工作大范围爬用代理池也好,频度是一个很基础的防爬机制。
具体的虫巢涉及的代码呢,我就不一一列出来了,这里列一下开源出来的代码,涉及到爬虫的部分,做个备注,有兴趣的可以去我github上clone下来,然后按下面的路径去分析分析逻辑,克隆完了记得给个star哟。
mite8-com开源项目涉及到爬虫的部分:
1 京东雾霾相关的爬虫逻辑:package com.mite8.Insight.jd_
2 电影《长城》相关的爬虫逻辑:package com.mite8.Insight.movie_great_
3 政务舆情相关的爬虫逻辑:package com.mite8.jx.gz.dn. //service下对应的几个子目录,下面的utils,入口是OptXXX类。
防爬的一些机制,以及对应的破解之道
在这里再说一些玩爬虫时,会遇到的一些常见的防爬手段,以及对应的破解之道。
由于俺不是专业的爬虫,所以这部分这么完善的东西显然不是出自我之手,是我团队里爬虫大神在内部技术分享时总结的,俺只是个搬运工。
第一种,伪装成合法的浏览器
在一般情况下,我们会对请求头进行伪装,最重点的key就是user-agent,这部分信息就是浏览器的内核信息。
由于很多公司,甚至是大楼都是用同一个对外IP,所以单纯的使用频度进行防爬封锁,这种情况很容易造成误杀,这也是目标网站不愿意看到的情况。
但是这种情况下,一般不同的电脑其浏览器是不同的,包括内核版本等等,防爬时会分析这个user-agent是不是一样的,或者说非法的字符。
因为很多爬虫框架,或者进程方位URL时会有默认的标志,通过分析这个频度可以明显知道是不是机器在访问页面。
所以,我们通常会获取一批正常的user-agent做随机封装,去获取数据,这种措施会导致上面说的那种防爬机制时效。
第二种,IP频度封锁
在一个IP过于频繁的访问页面时,网站根据一定的判定策略,会判断这个IP是非法的机器,进行IP封锁,导致这个IP无法访问目标页面。
这个时候,我们可以控制访问频度避免被封,但很多时候我们爬取的量很大,控制频度很难完成任务,那么我们就需要使用代理池来做了。
通过代理池的IP,进行IP伪装,这样就破解了频度的控制。
通常代理池分免费与收费,一般免费的代理池都是被人用烂了的,里头的IP都是在各大主流网站的黑名单里。
最后,至于说每个网站的频度是什么样子的,以及控制力度(禁封几分钟,或者是一天等等),就需要自己多测试尝试了。
第三种,用户验证机制
用户验证,这是个很常见的东西,很多页面只有用户登录之后可以访问。
一般通常的做法都是cookie验证,所以,关键是我们如何获取这个cookie。
一次性爬取比较容易,直接把cookie帖进去,做访问即可,但是遇到自动化的时候,我们就需要研究用户登录的过程了,使用POST做表单提交,获取cookie,后面的流程就通啦。
第四种,验证码
很多操作是需要验证码才能下一步操作的,这个时候除了破解验证码无法可破。
不错对于简单的验证码,或者说自己技术犀利的话,写个图像识别的东东,做图像识别,识别验证码也行,但是,目前验证码设计的都很变态,详情参考12306,所以这个方法打折的厉害。
还有一种手段,购买付费的打码平台服务,直接完破之,就是费钱而已。
第五种,动态页面
所谓动态页面,即很多时候数据是通过js动态加载出来的,或者JS加密的,这个时候,直接访问是拿不到数据。
也有破解之道,使用JS引擎做JS解析,目前不管是Python的还是Java的,有不少这种引擎可以供调用。
最后一种方法,使用浏览器内核去访问这个链接,就跟真正的浏览器访问页面没有什么差别啦,Java中经典的selenium就是其中一种。
据闻,技术高端点的公司还有更变态的,通过机器学习来学习真实用户的访问轨迹,通过算法来判断这种访问轨迹是否是机器造成的,然后再做判断是否做禁封。
好吧,玩高深的爬虫,其实就一部防爬与反爬的斗争史,其乐无穷。
下一篇,接着话题,我们讲讲述云平台搭建,服务器部署,环境配置相关的东西:《如何打造类似数据虫巢官网系列教程之三:服务器》。
最后,再贴一遍,数据虫巢官网(www.mite8.com)的开源代码地址(可以随意fork、star 哈哈):github.com/blogchong/mite8-com。
相关阅读:
广告Time:
要不要一起探讨大数据的相关的话题,是不是想要跨界大数据,进一步了解、讨论mite-com的开源代码,欢迎加入“数据虫巢读者私密群”,=&&。
https://sort.hust.cc/
罗列了大型网站架构涉及到的概念,附上了简单说明
本文是对《大型网站架构设计》(李智慧 著)一书的梳理,类似文字版的“思维导图”
全文主要围绕“性能,可用性,伸缩性,扩展性,安全”这五个要素
性能,可用性,伸缩性这几个要素基本都涉及到应用服务器,缓存服务器,存储服务器这几个方面
三个纬度:演化、模式、要素
五个要素: 性能,可用性,伸缩性,扩展性,安全
图例可参考 :
初始阶段的网站架构:一台服务器,上面同时拥有应用程序,数据库,文件,等所有资源。例如 LAMP 架构
应用和数据服务分离:三台服务器(硬件资源各不相同),分别是应用服务器,文件服务器和数据库服务器
使用缓存改善网站性能:分为两种,缓存在应用服务器上的本地缓存和缓存在专门的分布式缓存服务器的远程缓存
使用应用服务器集群改善网站并发处理能力:通过负载均衡调度服务器来将访问请求分发到应用服务器集群中的任何一台机器
数据库读写分离:数据库采用主从热备,应用服务器在写数据时访问主数据库,主数据库通过主从复制机制将数据更新同步到从数据库。应用服务器使用专门的数据访问模块从而对应用透明
使用反向代理和 CDN 加速网站响应:这两者基本原理都是缓存。反向代理部署在网站的中心机房,CDN 部署在网络提供商的机房
使用分布式文件系统和分布式数据库系统:数据库拆分的最后手段,更常用的是业务分库
使用 NoSQL 和搜索引擎:对可伸缩的分布式有更好的支持
业务拆分:将整个网站业务拆分成不同的应用,每个应用独立部署维护,应用之间通过超链接建立联系/消息队列进行数据分发/访问同一数据存储系统
分布式服务:公共业务提取出来独立部署
演化的价值观
大型网站架构的核心价值是随网站所需灵活应对
驱动大型网站技术发展的主要力量是网站的业务发展
一味追随大公司的解决方案
为了技术而技术
企图用技术解决所有问题
模式的关键在于模式的可重复性
分层:横向切分
分割:纵向切分
分布式:分层和分割的主要目的是为了切分后的模块便于分布式部署。常用方案:
分布式应用和服务
分布式静态资源
分布式数据和存储
分布式计算
分布式配置,分布式锁,分布式文件,等等
集群:多台服务器部署相同的应用构成一个集群,通过负载均衡设备共同对外提供服务
缓存:将数据放距离计算最近的位置加快处理速度,改善性能第一手段,可以加快访问速度,减小后端负载压力。使用缓存 两个前提条件 :1.数据访问热点不均衡;2.数据某时段内有效,不会很快过期
分布式缓存
异步:旨在系统解耦。异步架构是典型的消费者生产者模式,特性如下:
提高系统可用性
加快网站访问速度
消除并发访问高峰
冗余:实现高可用。数据库的冷备份和热备份
自动化:包括发布过程自动化,自动化代码管理,自动化测试,自动化安全检测,自动化部署,自动化监控,自动化报警,自动化失效转移,自动化失效恢复,自动化降级,自动化分配资源
安全:密码,手机校验码,加密,验证码,过滤,风险控制
架构是“最高层次的规划,难以改变的规定”。主要关注五个要素:
可用性(Availability)
伸缩性(Scalability)
扩展性(Extensibility)
下面依次对这五个要素进行归纳
性能的测试指标主要有:
响应时间:指应用执行一个操作需要的时间
并发数:指系统能够同时处理请求的数目
吞吐量:指单位时间内系统处理的请求数量
性能计数器:描述服务器或者操作系统性能的一些数据指标
性能测试方法:
稳定性测试
性能优化,根据网站分层架构,可以分为三大类:
Web 前端性能优化
浏览器访问优化
减少 http 请求
使用浏览器缓存
CSS 放在页面最上面,JavaScript 放在页面最下面
减少 Cookie 传输
CDN 加速:本质是一个缓存,一般缓存静态资源
保护网站安全
通过配置缓存功能加速 Web 请求
实现负载均衡
应用服务器性能优化:主要手段有 缓存、集群、异步
分布式缓存(网站性能优化第一定律:优化考虑使用缓存优化性能)
异步操作(消息队列,削峰作用)
多线程(设计为无状态,使用局部对象,并发访问资源使用锁)
资源复用(单例,对象池)
存储服务器性能优化
机械硬盘 vs. 固态硬盘
B+ 树 vs. LSM 树
RAID vs. HDFS
高可用的网站架构:目的是保证服务器硬件故障时服务依然可用、数据依然保存并能够被访问,主要手段数据和服务的冗余备份及失效转移
高可用的应用:显著特点是应用的无状态性
通过负载均衡进行无状态服务的失效转移
应用服务器集群的 Session 管理
Session 复制
Session 绑定
利用 Cookie 记录 Session
Session 服务器
高可用的服务:无状态的服务,可使用类似负载均衡的失效转移策略,此外还有如下策略
幂等性设计
高可用的数据:主要手段是数据备份和失效转移机制
数据一致性(Consisitency)
数据可用性(Availibility)
分区耐受性(Partition Tolerance)
冷备:缺点是不能保证数据最终一致和数据可用性
热备:分为异步热备和同步热备
失效转移:由以下三部分组成
高可用网站的软件质量保证
自动化测试
预发布验证
主干开发、分支发布
分支开发、主干发布
自动化发布
网站运行监控
监控数据采集
用户行为日志采集(服务器端和客户端)
服务器性能监控
运行数据报告
自动优雅降级
大型网站的“大型”是指:
用户层面:大量用户及大量访问
功能方面:功能庞杂,产品众多
技术层面:网站需要部署大量的服务器
伸缩性的分为如下几个方面
网站架构的伸缩性设计
不同功能进行物理分离实现伸缩
纵向分离(分层后分离)
横向分离(业务分割后分离)
单一功能通过集群规模实现伸缩
应用服务器集群的伸缩性设计
HTTP 重定向负载均衡
DNS 域名解析负载均衡
反向代理负载均衡(在 HTTP 协议层面,应用层负载均衡)
IP 负载均衡(在内核进程完成数据分发)
数据链路层负载均衡(数据链路层修改 mac 地址,三角传输模式,LVS)
负载均衡算法
轮询(Round Robin, RR)
加权轮询(Weighted Round Robin, WRR)
随机(Random)
最少链接(Least Connections)
源地址散列(Source Hashing)
分布式缓存集群的伸缩性设计
Memcached 分布式缓存集群的访问模型
Memcached 客户端(包括 API,路由算法,服务器列表,通信模块)
Memcached 服务器集群
Memcached 分布式缓存集群的伸缩性挑战
分布式缓存的一致性 Hash 算法(一致性 Hash 环,虚拟层)
数据存储服务集群的伸缩性设计
关系数据库集群的伸缩性设计
NoSQL 数据库的伸缩性设计
系统架构设计层面的“开闭原则”
构建可扩展的网站架构
利用分布式消息队列降低耦合性
事件驱动架构(Event Driven Architecture)
分布式消息队列
利用分布式服务打造可复用的业务平台
Web Service 与企业级分布式服务
大型网站分布式服务的特点
分布式服务框架设计(Thrift, Dubbo)
可扩展的数据结构(如 ColumnFamily 设计)
利用开放平台建设网站生态圈
网站的安全架构
XSS 攻击和 SQL 注入攻击是构成网站应用攻击最主要的两种手段,此外还包括 CSRF,Session 劫持等手段。
攻击与防御
XSS 攻击:跨站点脚本攻击(Cross Site Script)
XSS 防御手段
消毒(即对某些 html 危险字符转义)
SQL 注入攻击
OS 注入攻击
避免被猜到数据库表结构信息
CSRF 攻击:跨站点请求伪造(Cross Site Request Forgery)
CSRF 防御:主要手段是识别请求者身份
表单 Token
Referer Check
其他攻击和漏洞
Error Code
Web 应用防火墙(ModSecurity)
网站安全漏洞扫描
信息加密技术及密钥安全管理
单向散列加密:不同输入长度的信息通过散列计算得到固定长度的输出
不可逆,非明文
可加盐(salt)增加安全性
输入的微小变化会导致输出完全不同
对称加密:加密和解密使用同一个密钥
非对称加密
信息传输:公钥加密,私钥解密
数字签名:私钥加密,公钥解密
密钥安全管理:信息安全传输是靠密钥保证的,改善手段有:
把密钥和算法放在一个独立的服务器上
将加解密算法放在应用系统中,密钥放在独立服务器
信息过滤与反垃圾
作者更多文章: |
伴随业务的增长,系统压力也在不断增加,再加上机房机架趋于饱和,无法更加有效应对各种突发事件。在这样的情况下,PC主站升级为PHP 7,有哪些技术细节可以分享?
新浪微博在2016年Q2季度公布月活跃用户(MAU)较上年同期增长33%,至2.82亿;日活跃用户(DAU)较上年同期增长36%,至1.26亿,总注册用户达8亿多。PC主站作为重要的流量入口,承载部分用户访问和流量落地,其中我们提供的部分服务(如:头条文章)承担全网所有流量。
随着业务的增长,系统压力也在不断的增加。峰值时,服务器Hits达10W+,CPU使用率也达到了80%,远超报警阈值。另外,当前机房的机架已趋于饱和,遇到突发事件,只能对非核心业务进行降低,挪用这些业务的服务器来进行临时扩容,这种方案只能算是一种临时方案,不能满足长久的业务增长需求。再加上一年一度的三节(圣诞、元旦、春节),系统需预留一定的冗余来应对,所以当前系统面临的问题非常严峻,解决系统压力的问题也迫在眉急。
相关厂商内容
面对当前的问题,我们内部也给出两套解决方案同步进行。
方案一:申请新机房,资源统一配置,实现弹性扩容。
方案二:对系统进行优化,对性能做进一步提升。
针对方案一,通过搭建与新机房之间的专线与之打通,高峰时,运用内部自研的混合云DCP平台,对所有资源进行调度管理,实现了真正意义上的弹性扩容。目前该方案已经在部分业务灰度运行,随时能对重点业务进行小流量测试。
针对方案二,系统层面,之前做过多次大范围的优化,比如:
将Apache升级至Nginx
应用框架升级至Yaf
CPU计算密集型的逻辑扩展化
弃用smarty
并行化调用
优化效果非常明显,如果再从系统层面进行优化,性能可提升的空间非常有限。好在业界传出了两大福音,分别为HHVM和PHP7。
在PHP7还未正式发布时,我们也研究过HHVM(HipHop Virtual Machine),关于HHVM更多细节,这里就不再赘述,可参考官方说明。下面对它提升性能的方式进行一个简单的介绍。
默认情况下,Zend引擎先将PHP源码编译为opcode,然后Zend解析引擎逐条执行。这里的opcode码,可以理解成C语言级的函数。而HHVM提升性能方式为替代Zend引擎将PHP代码转换成中间字节码(HHVM自己的中间字节码,通常称为中间语言),然后在运行时通过即时(JIT)编译器将这些字节码转换成x64的机器码,类似于Java的JVM。
HHVM为了达到最佳优化效果,需要将PHP的变量类型固定下来,而不是让编译器去猜测。Facebook的工程师们就定义一种Hack写法,进而来达到编译器优化的目的,写法类似如下:
class point {
public float $x, $y;
function __construct(float $x, float $y) {
$this-&x = $x;
$this-&y = $y;
通过前期的调研,如果使用HHVM解析器来优化现有业务代码,为了达到最佳的性能提升,必须对代码进行大量修改。另外,服务部署也比较复杂,有一定的维护成本,综合评估后,该方案我们也就不再考虑。
当然,PHP7的开发进展我们也一直在关注,通过官方测试数据以及内部自己测试,性能提升非常明显。
令人兴奋的是,在去年年底(日),官方终于正式发布了PHP7,并且对原生的代码几乎可以做到完全兼容,性能方面与PHP5比较能提升达一倍左右,和HHVM相比已经是不相上下。
无论从优化成本、风险控制,还是从性能提升上来看,选择PHP7无疑是我们的最佳方案。
系统现状以及升级风险
微博PC主站从日发布第一版开始,先后经历了6个大的版本,系统架构也随着需求的变化进行过多次重大调整。截止目前,系统部分架构如下。
从系统结构层面来看,系统分应用业务层、应用服务层,系统所依赖基础数据由平台服务层提供。
从服务部署层面来看,业务主要部署在三大服务集群,分别为Home池、Page池以及应用服务池。
为了提升系统性能,我们自研了一些PHP扩展,由于PHP5和PHP7底层差别太大,大部分Zend API接口都进行了调整,所有扩展都需要修改。
所以,将PHP5环境升级至PHP7过程中,主要面临如下风险:
使用了自研的PHP扩展,目前这些扩展只有PHP5版本,将这些扩展升级至PHP7,风险较大。
PHP5与PHP7语法在某种程度上,多少还是存在一些兼容性的问题。由于涉及主站代码量庞大,业务逻辑分支复杂,很多测试范围仅仅通过人工测试是很难触达的,也将面临很多未知的风险。
软件新版本的发布,都会面临着一些未知的风险和版本缺陷。这些问题,是否能快速得到解决。
涉及服务池和项目较多,基础组件的升级对业务范围影响较大,升级期间出现的问题、定位会比较复杂。
对微博这种数亿用户级别的系统的基础组件进行升级,影响范围将非常之大,一旦某个环节考虑不周全,很有可能会出现比较严重的责任事故。
PHP7升级实践
1. 扩展升级
一些常用的扩展,在发布PHP7时,社区已经做了相应升级,如:Memcached、PHPRedis等。另外,微博使用的Yaf、Yar系列扩展,由于鸟哥(laruence)的支持,很早就全面支持了PHP7。对于这部分扩展,需要详细的测试以及现网灰度来进行保障。
PHP7中,很多常用的API接口都做了改变,例如HashTable API等。对于自研的PHP扩展,需要做升级,比如我们有个核心扩展,升级涉及到代码量达1500行左右。
新升级的扩展,刚开始也面临着各式各样的问题,我们主要通过官方给出的建议以及测试流程来保证其稳定可靠。
在PHP7下编译你的扩展,编译错误与警告会告诉你绝大部分需要修改的地方。
在DEBUG模式下编译与调试你的扩展,在run-time你可以通过断言捕捉一些错误。你还可以看到内存泄露的情况。
首先通过扩展所提供的单元测试来保证扩展功能的正确性。
其次通过大量的压力测试来验证其稳定性。
然后再通过业务代码的自动化测试来保证业务功能的可用性。
最后再通过现网流量灰度来确保最终的稳定可靠。
整体升级过程中,涉及到的修改比较多,以下只简单列举出一些参数变更的函数。
(1)addassocstringl参数4个改为了3个。
add_assoc_stringl(parray, key, value, value_len);
add_assoc_stringl(parray, key, value);
(2)addnextindex_stringl 参数从3个改为了2个。
add_assoc_stringl(parray, key, value, value_len);
add_assoc_stringl(parray, key, value);
(3)RETURN_STRINGL 参数从3个改为了2个。
RETURN_STRINGL(value, length,dup);
RETURN_STRINGL(value, length);
(4)变量声明从堆上分配,改为栈上分配。
zval* sarray_l;
ALLOC_INIT_ZVAL(sarray_l);
array_init(sarray_l);
zval sarray_l;
array_init(&sarray_l);
(5)zendhashgetcurrentkey_ex参数从6个改为4个。
ZEND_API int ZEND_FASTCALL zend_hash_get_current_key_ex (
HashTable* ht,
char** str_index,
uint* str_length,
ulong* num_index,
zend_bool duplicate,
HashPosition* pos);
ZEND_API int ZEND_FASTCALL zend_hash_get_current_key_ex(
const HashTable *ht,
zend_string **str_index,
zend_ulong *num_index,
HashPosition *pos);
更详细的说明,可参考官方PHP7扩展迁移文档:https://wiki.PHP.net/PHPng-upgrading。
2. PHP代码升级
整体来讲,PHP7向前的兼容性正如官方所描述那样,能做到99%向前兼容,不需要做太多修改,但在整体迁移过程中,还是需要做一些兼容处理。
另外,在灰度期间,代码将同时运行于PHP5.4和PHP7环境,现网灰度前,我们首先对所有代码进行了兼容性修改,以便同一套代码能同时兼容两套环境,然后再按计划对相关服务进行现网灰度。
同时,对于PHP7的新特性,升级期间,也强调不允许被使用,否则代码与低版本环境的兼容性会存在问题。
接下来简单介绍下升级PHP7代码过程中,需要注意的地方。
(1)很多致命错误以及可恢复的致命错误,都被转换为异常来处理,这些异常继承自Error类,此类实现了 Throwable 接口。对未定义的函数进行调用,PHP5和PHP7环境下,都会出现致命错误。
undefine_function();
错误提示:
PHP Fatal error:
Call to undefined function
undefine_function() in /tmp/test.PHP on line 4
在PHP7环境下,这些致命的错误被转换为异常来处理,可以通过异常来进行捕获。
undefine_function();
catch (Throwable $e) {
Error: Call to undefined function undefine_function() in /tmp/test.PHP:5 Stack trace:
(2)被0除,PHP 7 之前,被0除会导致一条 E_WARNING 并返回 false 。一个数字运算返回一个布尔值是没有意义的,PHP 7 会返回如下的 float 值之一。
var_dump(42/0);
// float(INF)
+ E_WARNING
var_dump(-42/0); // float(-INF) + E_WARNING
var_dump(0/0);
// float(NAN)
+ E_WARNING
当使用取模运算符( % )的时候,PHP7会抛出一个 DivisionByZeroError 异常,PHP7之前,则抛出的是警告。
echo 42 % 0;
PHP5输出:
PHP Warning:
Division by zero in /tmp/test.PHP on line 4
PHP7输出:
PHP Fatal error:
Uncaught DivisionByZeroError: Modulo by zero in /tmp/test.PHP:4 Stack trace: #
thrown in /tmp/test.PHP on line 4
PHP7环境下,可以捕获该异常:
echo 42 % 0;
} catch (DivisionByZeroError $e) {
echo $e-&getMessage();
Modulo by zero
(3)pregreplace() 函数不再支持 “\e” (PREGREPLACEEVAL). 使用 pregreplace_callback() 替代。
$content = preg_replace("/#([^#]+)#/ies", "strip_tags('#\\1#')", $content);
$content = preg_replace_callback("/#([^#]+)#/is", "self::strip_str_tags", $content);
public static function strip_str_tags($matches){
return "#".strip_tags($matches[1]).'#';
(4)以静态方式调用非静态方法。
class foo { function bar() { echo ‘I am not static!’; } } foo::bar();
以上代码PHP7会输出:
PHP Deprecated:
Non-static method foo::bar() should not be called statically in /tmp/test.PHP on line 10
I am not static!
(5)E_STRICT 警告级别变更。
原有的 ESTRICT 警告都被迁移到其他级别。 ESTRICT 常量会被保留,所以调用 errorreporting(EALL|E_STRICT) 不会引发错误。
关于代码兼容PHP7,基本上是对代码的规范要求更严谨。以前写的不规范的地方,解析引擎只是输出NOTICE或者WARNING进行提示,不影响对代码上下文的执行,而到了PHP7,很有可能会直接抛出异常,中断上下文的执行。
如:对0取模运行时,PHP7之前,解析引擎只抛出警告进行提示,但到了PHP7则会抛出一个DivisionByZeroError异常,会中断整个流程的执行。
对于警告级别的变更,在升级灰度期间,一定要关注相关NOTICE或WARNING报错。PHP7之前的一个NOTICE或者WARNING到了PHP7,一些报警级变成致命错误或者抛出异常,一旦没有对相关代码进行优化处理,逻辑被触发,业务系统很容易因为抛出的异常没处理而导致系统挂掉。
以上只列举了PHP7部分新特性,也是我们在迁移代码时重点关注的一些点,更多细节可参考官方文档http://PHP.net/manual/zh/migration70.PHP。
3. 研发流程变更
一个需求的开发到上线,首先我们会通过统一的开发环境来完成功能开发,其次经过内网测试、仿真测试,这两个环境测试通过后基本保证了数据逻辑与功能方面没有问题。然后合并至主干分支,并将代码部署至预发环境,再经过一轮简单回归,确保合并代码没有问题。最后将代码发布至生产环境。
为了确保新编写的代码能在两套环境(未灰度的PHP5.4环境以及灰度中的PHP7环境)中正常运行,代码在上线前,也需要在两套环境中分别进行测试,以达到完全兼容。
所以,在灰度期间,对每个环节的运行环境除了现有的PHP5.4环境外,我们还分别提供了一套PHP7环境,每个阶段的测试中,两套环境都需要进行验证。
4. 灰度方案
之前有过简单的介绍,系统部署在三大服务池,分别为Home池、Page池以及应用服务池。
在准备好安装包后,先是在每个服务池分别部署了一台前端机来灰度。运行一段时间后,期间通过错误日志发现了不少问题,也有用户投诉过来的问题,在问题都基本解决的情况下,逐渐将各服务池的机器池增加至多台。
经过前期的灰度测试,主要的问题得到基本解决。接下是对应用服务池进行灰度,陆续又发现了不少问题。前后大概经历了一个月左右,完成了应用服务池的升级。然后再分别对Home池以及Page池进行灰度,经过漫长灰度,最终完成了PC主站全网PHP7的升级。
虽然很多问题基本上在测试或者灰度期间得到了解决,但依然有些问题是全量上线后一段时间才暴露出来,业务流程太多,很多逻辑需要一定条件才能被触发。为此BUG都要第一时间同步给PHP7升级项目组,对于升级PHP引起的问题,要求必须第一时间解决。
5. 优化方案
(1)启用Zend Opcache,启用Opcache非常简单, 在PHP.ini配置文件中加入:
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1"
(2)使用GCC4.8以上的编译器来编译安装包,只有GCC4.8以上编译出的PHP才会开启Global Register for opline and execute_data支持。
(3)开启HugePage支持,首先在系统中开启HugePages, 然后开启Opcache的hugecodepages。
关于HugePage
操作系统默认的内存是以4KB分页的,而虚拟地址和内存地址需要转换, 而这个转换要查表,CPU为了加速这个查表过程会内建TLB(Translation Lookaside Buffer)。 显然,如果虚拟页越小,表里的条目数也就越多,而TLB大小是有限的,条目数越多TLB的Cache Miss也就会越高, 所以如果我们能启用大内存页就能间接降低这个TLB Cache Miss。
PHP7与HugePage
PHP7开启HugePage支持后,会把自身的text段, 以及内存分配中的huge都采用大内存页来保存, 减少TLB miss, 从而提高性能。相关实现可参考Opcache实现中的accel_move_code_to_huge_pages()函数。
以CentOS 6.5为例, 通过命令:
sudo sysctl vm.nr_hugepages=128
分配128个预留的大页内存。
$ cat /proc/meminfo | grep Huge
AnonHugePages:
HugePages_Total:
HugePages_Free:
HugePages_Rsvd:
HugePages_Surp:
Hugepagesize:
然后在PHP.ini中加入
opcache.huge_code_pages=1
6. 关于负载过高,系统CPU使用占比过高的问题
当我们升级完第一个服务池时,感觉整个升级过程还是比较顺利,当灰度Page池,低峰时一切正常,但到了流量高峰,系统CPU占用非常高,如图:
系统CPU的使用远超用户程序CPU的使用,正常情况下,系统CPU与用户程序CPU占比应该在1/3左右。但我们的实际情况则是,系统CPU是用户CPU的2~3倍,很不正常。
对比了一下两个服务池的流量,发现Page池的流量正常比Home池高不少,在升级Home池时,没发现该问题,主要原因是流量没有达到一定级别,所以未触发该问题。当单机流量超过一定阈值,系统CPU的使用会出现一个直线的上升,此时系统性能会严重下降。
这个问题其实困扰了我们有一段时间,通过各种搜索资料,均未发现任何升级PHP7会引起系统CPU过高的线索。但我们发现了另外一个比较重要的线索,很多软件官方文档里非常明确的提出了可以通过关闭Transparent HugePages(透明大页)来解决系统负载过高的问题。后来我们也尝试对其进行了关闭,经过几天的观察,该问题得到解决,如图:
什么是Transparent HugePages(透明大页)
简单的讲,对于内存占用较大的程序,可以通过开启HugePage来提升系统性能。但这里会有个要求,就是在编写程序时,代码里需要显示的对HugePage进行支持。
而红帽企业版Linux为了减少程序开发的复杂性,并对HugePage进行支持,部署了Transparent HugePages。Transparent HugePages是一个使管理Huge Pages自动化的抽象层,实现方案为操作系统后台有一个叫做khugepaged的进程,它会一直扫描所有进程占用的内存,在可能的情况下会把4kPage交换为Huge Pages。
为什么Transparent HugePages(透明大页)对系统的性能会产生影响
在khugepaged进行扫描进程占用内存,并将4kPage交换为Huge Pages的这个过程中,对于操作的内存的各种分配活动都需要各种内存锁,直接影响程序的内存访问性能。并且,这个过程对于应用是透明的,在应用层面不可控制,对于专门为4k page优化的程序来说,可能会造成随机的性能下降现象。
怎么关闭Transparent HugePages(透明大页)
(1)查看是否启用透明大页。
[root@venus153 ~]# cat
/sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
使用命令查看时,如果输出结果为[always]表示透明大页启用了,[never]表示透明大页禁用。
(2)关闭透明大页。
echo never & /sys/kernel/mm/transparent_hugepage/enabled
echo never & /sys/kernel/mm/transparent_hugepage/defrag
(3)启用透明大页。
echo always &
/sys/kernel/mm/transparent_hugepage/enabled
echo always & /sys/kernel/mm/transparent_hugepage/defrag
(4)设置开机关闭。
修改/etc/rc.local文件,添加如下行:
if test -f /sys/kernel/mm/redhat_transparent_hugepage/ then
echo never & /sys/kernel/mm/transparent_hugepage/enabled
echo never & /sys/kernel/mm/transparent_hugepage/defrag
由于主站的业务比较复杂,项目较多,涉及服务池达多个,每个服务池所承担业务与流量也不一样,所以我们在对不同的服务池进行灰度升级,遇到的问题也不尽相同,导致整体升级前后达半年之久。庆幸的是,遇到的问题,最终都被解决掉了。最让人兴奋的是升级效果非常好,基本与官方一致,也为公司节省了不少成本。
以下简单地给大家展示下这次PHP7升级的成果。
(1)PHP5与PHP7环境下,分别对我们的某个核心接口进行压测(压测数据由QA团队提供),相关数据如下:
同样接口,分别在两个不现的环境中进行测试,平均TPS从95提升到220,提升达130%。
(2)升级前后,单机CPU使用率对比如下。
升级前后,1小时流量情况变化:
升级前后,1小时CPU使用率变化:
升级前后,在流量变化不大的情况下,CPU使用率从45%降至25%,CPU使用率降低44.44%。
(3)某服务集群升级前后,同一时间段1小时CPU使用对比如下。
PHP5环境下,集群近1小时CPU使用变化:
PHP7环境下,集群近1小时CPU使用变化:
升级前后,CPU变化对比:
升级前后,同一时段,集群CPU平均使用率从51.6%降低至22.9%,使用率降低56.88%。
以上只简单从三个维度列举了一些数据。为了让升级效果更加客观,我们实际的评估维度更多,如内存使用、接口响应时间占比等。最终综合得出的结论为,通过本次升级,PC主站整体性能提升在48.82%,效果非常好。团队今年的职能KPI就算是提前完成了。
整体升级从准备到最终PC主站全网升级完成,时间跨度达半年之久,无论是扩展编写、准备安装脚本、PHP代码升级还是全网灰度,期间一直会出现各式各样的问题。最终在团队的共同努力下,这些问题都彻底得到了解决。
一直以来,对社区的付出深怀敬畏之心,也是因为他们对PHP语言性能极限的追求,才能让大家的业务坐享数倍性能的提升。同时,也让我们更加相信,PHP一定会是一门越来越好的语言。
侯青龙,微博主站研发负责人。2010年加入新浪微博,先后参与过微博主站V2版至V6版的研发,主导过主站V6版以及多机房消息同步系统等重大项目的架构设计工作。致力于提升产品研发效率以及优化系统性能。
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博(,),微信(微信号:)关注我们。
作者:IT程序狮
链接:https://zhuanlan.zhihu.com/p/
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在过去的一年里,软件开发行业继续大踏步地向前迈进。回顾 2016 年,我们看到了更多新兴的流行语言、框架和工具,它们改变着我们的工作方式,让我们看到更多的可能。但在这个行业,紧随潮流是很难的。所以在每年年底,我们都会给你提供一些建议,它涉及什么是最重要的,以及你在未来一年中应该学习什么。
渐进式 Web Apps
在 2016 年里,我们见证了
概念的蓬勃兴起。它意味着 Web 应用程序可以离线工作,并能提供原生移动应用的体验。它们可以添加到你的智能设备的主屏幕上,甚至可以给你发送推送通知,从而弥补与原生移动应用程序的差距。我们认为,在 2017 年,渐进式 Web Apps 将变得更加重要,也值得我们去探究。在这里。
聊天机器人
从运行聊天机器人的平台到构建其的框架,现在每个人都在谈论它。而社区里也正忙于此活动。()机器人是一款新兴的移动应用程序,它让我们感到兴奋。如果你快点的话,还可以赶得上这波浪潮。然而一旦新鲜感消失,那么它只会承担一些无聊的角色,例如自动化的客服支持。但是,相信我们可以实现梦想。
前端框架的合并
在 JavaScript 社区,随着令人难以置信的框架和工具的混合,每周都会出现新的东西。直到最近,人们希望旧工具将被新工具所取代,但这不是 2016 年我们所想看到的。相反,我们看到了流行框架交换的想法,以及纳入新诞生框架中的创新元素。所以在 2017 年,你该选择哪个 JS 框架无关紧要,因为它们的功能大多是可以比较的。
就目前的形势看,众多的公司与开发者们都在积极地拥抱“云”。云是可根据不同的需求,并通过控制面板来完全配置的虚拟化计算机基础设施。目前三大云提供商为亚马逊 AWS、Google Cloud 和 微软 Azure. 由于它们的竞争价格一直在下跌,使得小公司和个人开发者也可以将云纳入其预算中,所以熟悉云工作流程将是 2017 年的一笔不错的投资。
机器学习(ML)在去年一年中呈现爆炸式的增长。三月份 ,也让它成为了焦点。从原始数据中学习的智能计算机系统,正在改变我们与移动设备的交互方式。看样子,机器学习将在 2017 年成为更大的影响因素。
JavaScript 继续迈着令人难以置信的创新步伐在前进。由于 Web 浏览器的快速发布计划,JS 的标准定为了每年更新。故“”预计将在 2017 年中期完成,它也将带来 JS 开发者梦寐以求的新特性——用于处理异步函数的аsync/аwait。同时要感谢
,因为你现在可以在每个浏览器中编写 ES2017 了。
TypeScript 2.1 于 2016 年年底,它将为旧浏览器带来Async/Await异步解决方案,并改进了类型推断。TypeScript 是一种编译为纯 JavaScript 的静态类型语言。它增强了经典的 OOP 模型和可选的静态类型,使大代码库更易于维护。同时,它也是编写 Angular 2 应用程序的首选语言,我们建议你可以尝试下。 这是关于它的。
C#7.0 预计在 2017 年发布,作为一门优秀的编程语言,它也将得到更大的改进。当微软推出开源的 Visual Studio 代码编辑器和 .Net Core 时,这一举动让众人都感到惊讶万分。它们不仅可以在 Linux、Windows 和 macOS 操作系统中运行,而且你可以在 C# 中编写快速、高效的应用程序(阅读更多)。同时,这两种工具也都形成了充满活力的社区。相信,它们将在 2017 年会给我们带来更多的惊喜。
Python 3.6 版本将于 12 月。它正在巩固自身在开发人员、IT 专业人员和科学家在脚本语言选择中的地位。它适用于自动化、Web开发、机器学习和科学计算。虽然 Python 2.X 与 3.X 版本的割裂,对于社区来说是一个长达数年的斗争,但是就目前而言,你可以自信地选择 Python 3 并享受完整的库支持。而对于那些需要额外性能的朋友,建议你们看看 ,一个可启用 Python 运行时 JIT 的替代品。
Ruby 2.3 已在今年早些时候了,并带来了一些性能上的改进。同时,Ruby 也是学习通用脚本语言的一个好选择,但是只有当它和 Rails 相配合的时候才能发挥出其最大的功效。伴随 Ruby 3×3 计划的宣布,也促使了即将到来的 Ruby 3 版本比当前版本的运行速度快 3 倍。而你也可以在更多的情景中,打开使用 Ruby 的大门。
PHP 7.1 版本已在 12 月,并对该语言进行了小范围的增强。这个版本基于了去年 7.0 版本主要性能的改进,将 PHP 转变为构建 Web 应用程序的快速平台。如果你打算学习,我们推荐你看看 中的最佳实践。
Java 9 预计在 2017 年发布,它将带来一些备受开发者们所欢迎的新功能,例如评估代码的 repl、HTTP 2.0 的支持以及一些新的 API . 对于有才能的 Java 开发人员和广泛使用该语言进行项目研发的人来说,他们对这些新特性是有强烈需求的。如果 Java 不是你的“菜”,这里还有一些基于 JVM 的编程语言,像
和 ,你也可以了解下。
Swift 3 已经在今年早些时候发布了。简化 iOS 和 MacOS 上应用程序的开发,是苹果公司对现代编程语言的愿景。由于 Swift 是的,所以也涌现了大量的社区。Swift 4 计划于 2017 年发布,此版本将会改进语言并引入服务器 API,致力使其成为编写 Web 应用程序和后端的不错选择。
如果你在寻找一些让你感到兴奋的东西,你可以尝试下
和 。它们都拥有类似与 Ruby 的友好语法以及卓越的性能,或者你也可以看看类似于
这类函数式语言。另外两种快速编程语言,我们推荐给你
挑一个或多个学习: JS (ES2017)、TypeScript、C#、Python、Ruby、PHP7、Java/Kotlin/Scala.
近期 Web 平台取得了两个重大的进展: 字节码技术和
技术。它们打开了快速、高效的 Web 应用程序的大门,并且有效的弥补了编译本地应用上的差距。Service Workers 是针对渐进式 Web App 的启动技术,它为 Web 平台提供了通知上的支持,将来也会有更多的 API.
Angular.js 2 在今年也已经了。该框架由 Google 进行维护,受到了众多企业和大公司的青睐。它所具备众多的功能,也为从网络到桌面以及移动应用程序中编写任何东西成为了可能。而它的框架也是用 TypeScript 所编写的,这也是写应用程序推荐的编程语言。虽然学习它还需要阅读更多的内容,但我们认为在 2017 年学习 Angular 2 将是一个很不错的投资。
在今年我们也看到了 Vue.js,它借鉴了 Angular,React 和 Ember 中好的想法,并且比前两个框架更轻量、更快速。我们建议你今年要试一试,你可以从我们的 开始。
是 JavaScript 框架的另一个不错的选择。它支持数据双向绑定,并能够自动更新模板、组件以及服务器端渲染。与其他竞争者相比,使用它的好处是它更加成熟与稳定,而其框架的重大更改频率之低,社区重视向后的兼容性,也使得此框架成为开发较长生命周期的应用程序的不二之选。
另外两个值得一提的框架是
和 。在过去的一年里 React 的生态系统变得越来越复杂,因此很难推荐给初学者。但经验丰富的开发者可以将库与 、、 和
组合成一个全面完整的全栈解决方案。
的前端终归是不完整的。而 Bootstrap 4 目前也正处于 Alpha 阶段,预计在 2017 年发布。值得关注的变化是新的通用卡片组件和 Flexbox 网格(查看与),这使得框架更加现代化,并且让用户使用它进行工作时更加得舒心。
仍然是当前最流行的两种 CSS 预处理器。尽管 Vanilla CSS 已经实现了对变量的支持,但对 mixins、函数和代码组织上的支持,SASS 和 LESS 依然更胜一筹。如果您还没有了解它们,可以看看我们的
快速入门指南。
挑一个或多个学习:Angular 2、Vue.js、Ember、Bootstrap、LESS/SASS
后端有众多的选择,但所有的选择都取决于你对编程语言或特定性能需求的偏好上。Web 开发中的一个持续趋势是远离后端的业务逻辑,并将该层转换为由前端和移动应用程序使用的 API 上。但一个全栈的框架通常是能够更简单、快速的应用于开发,并且它仍然是 Web 应用程序最有效的选择。
Node.js 是在浏览器之外运行 JS 的主要方式。在今年,我们也看到了它发布了许多新的版本。除了提升了性能外,也添加了对整个 ES6 规范的覆盖。Node 具有构建快速 API、服务器、桌面应用程序甚至机器人的框架,同时它可以创建想象到的各种模块的庞大社区。这里有一些你可能想研究的框架:、、、.
PHP 是一种拥有大量 Web 框架可供你选择的 Web 开发语言。由于其拥有出色的文档和功能, 已建成了一个活跃的社区。Zend Framework 发布了,这标志着面向业务框架的巨大升级。在今年,我们也看到了
发行了很多新的版本,使它成为了全栈解决方案中更好的选择。
对于 Ruby 来说,Rails 框架是首选的。 版本已于今年发布,并为 Web Sockets、API 模型等方面提供了支持。对于小型应用程序而言, 也是一个不错的选择,Sinatra 2.0版本预计在 2017 年发布。
Python 有着以
为组合的全栈/迷你型框架。Django 1.10 已在今年 8 月了,它为 Postgres 引入了全文搜索和一个重大修改的中间件层。
Java 的生态系统中,依旧有很多流行的 Web 框架可供你选择。 和
便是两个必备的选择,同时它们也可以与 Scala 一起使用。
对于编程爱好者来说,你还可以选择 ,它是用 Elixir 编写的,它试图成为一个具有卓越的性能,并能完整替代 Rails 功能的框架。如果 Elixir 是你想在 2017 年学习的语言之一,不妨尝试下 Phoenix .
学习其中之一:全栈后端框架、一个微框架
PostgreSQL 在今年已经发行了两个完整的版本——和.它们带来了我们从 MySQL 就开始期盼的 UPSERT (aka ON DUPLICATE KEY UPDATE)功能,以及更好的全文搜索和速度改进功能,这多亏了并行查询,更高效的复制、聚合、索引和排序。Postgres 适用于大规模、TB 级规模的数据集以及繁忙的 Web Apps,这些优化都是很受欢迎的。
将是数据库的下一个主要版本。预计在 2017 年发布,它将给系统带来更多的改进。MySQL 仍然是最受欢迎的数据库管理系统,整个行业都受益于这些新的版本。
对于 NoSQL 的粉丝们,我们推荐 。它是一个快速、可扩展的 JSON 存储系统,同时公开了一个 REST-ful HTTP API.此数据库易于使用,同时性能卓越。与 CouchDB 对应的是
,它可以完全在浏览器中工作,并且可以与 Couch 同步数据。所以你可以在离线应用程序上使用 PouchDB ,联网后它会自动同步数据。
是我们最喜欢的键-值存储型数据库。它体积小、快速并且有丰富的特性。作为 NoSQL 数据存储或进程消息和同步通道,你可以使用它作为智能分布式高速缓存系统的可替代方案。它提供了大量的数据结构可供选择,并且在即将到来的 4.0 版本中会有一个模块系统,并将改进复制功能。
学习其中之一:Postgres、MySQL、CouchDB、Redis.
是由 Facebook 开发的 Node.js 包管理器。它是对 npm 命令行工具的升级,并提供了更快速地安装,更好的安全性以及确定性的构建。它仍然使用 npm 包注册表作为其后端,因此您甚至可以访问同一个 JavaScript 模块的生态系统。Yarn 与 npm 使用的 package.json 格式是兼容的,区别在于前者能实现快速安装。
作为两个最受开发者欢迎的开源代码编辑器—— 和
,在过去一年中,我们看到了它们进行了很多不可思议的创新。这两个项目都是使用 Web 技术构建的,社区中也吸引了大量的粉丝。编辑器具备高扩展,提供了诸如语法检查、linting 和重构工具的相关插件。
作为最流行的源代码版本控制系统, 当之无愧。虽然它无服务器,但你可以将计算机上的任何文件夹转换为存储库。如果你想共享代码,像 、 和 都是不错的选择。在 2017 年,我们建议你,因为它会比您想象的更加方便。
桌面应用程序依然没有消失。即使 Web App 变得越来越强大,有时你依然会需要强大的功能和 API,这是 Web 平台无法提供的。你可以使用诸如 Electron 和 NW.js 之类的工具,利用 Web 技术来创建桌面应用程序,同时你也可以完全访问操作系统和 npm 可用的广度模块。要了解这些工具的更多信息,请阅读有关
软件开发团队的最新趋势是让开发人员负责自己软件项目中的部署,也称为 DevOps.这能产生更快地发布和更迅速地修复生产中出现的问题。而具有运维经验的开发人员将得到公司的高度重视,因此从现在开始熟悉能够实现这一目标的技术,将对你来说是一个巨大的提升。我们推荐的工具是
。同时,具备 Linux 命令行和基本系统管理技能,也将为你的职场生涯大大的加分。
尝试一个或多个学习:Yarn、Git、Visual Studio Code、Electron、Ansible、Docker.
随着大型公司数据中心的关闭,并调整其整体的基础设施到云上,我们可以看到云已经赢得了整个软件行业。目前三个主要的平台是 ,
和 。这三大平台都有着强大的功能,同时不断地扩展其功能集,涉及虚拟机、数据库托管、机器学习服务等。由于价格的迅速下降,小公司和个人开发者也都可以接触到云。对于 2017 年,在云上部署一个业余项目将是一个很好的学习积累。
人工智能是 2016 年的流行词。语音识别和图像分类只是该技术在面向用户应用程序的两个部分,人工智能设备的性能达到甚至超越了人类的水平。当下众多的创业公司也将 AI 和机器学习应用到其新的领域,同时许多相关的开源项目也已经发布,例如谷歌的
和微软的 。机器学习是一个与数学非常相关的主题,对于刚刚开始的人,这里有全面的供你学习。
虚拟现实(VR)和增强现实(AR)已经存在了一段时间,而最终该技术已经成熟到足以提供引人注目的体验。Facebook(),Google()和 Microsoft()都有欢迎第三方开发者加入的虚拟现实平台。然而 VR 穿戴设备依然面临着艰巨的挑战。例如如何消除穿戴者恶心的感觉,以及脱离了游戏圈,又如何创造令人信服的使用案例。
挑一种学习:云部署、机器学习库、VR 开发
如果觉得文章不错,不妨点个赞。^_^
若有翻译不当之处,还请大家多多指正,我会及时修改;
本文版权归原作者所有。如需转载译文,烦请注明出处,谢谢!
英文原文:
作者:Martin Angelov
译文源自:
首先来聊聊往事吧~~两年前就职于一家传统金融软件公司,为某交易所开发一套大型交易系统,交易标的的价格为流式数据,采用价格触发成交方式,T+0交易制度(类似炒股,只是炒的不是股票而是其他标的物,但可以随时开平仓)。鉴于系统需要记录大量价格数据、交易信息及订单流水,且系统对性能要求极高(敏感度达毫秒级),因此需要避免日志服务成为系统性能瓶颈。通过对几个通用型日志(如log4j、logback)的性能压测,以及考虑到它们作为通用型日志相对比较臃肿,就决定自个儿写个日志工具以支撑系统功能和性能所需。当时的做法只是简单的将日志的实现作为一个 util 类写在项目中,只有几百行的代码量。
系统上线两个月后日均成交额200亿RMB,最高达440亿RMB,峰值成交4000笔/秒。系统非常庞大,但几百行的代码却完美支撑住了重要的日志服务!
鉴于其优秀的表现,就花了一点点时间把它抽取出来作为一个独立的日志组件,取名叫 FLogger,代码几乎没有改动,现已托管到GitHub(),有兴趣的童鞋可以clone下来了解并改进,目前它的实现是非常简(纯)单(粹)的。
以上就是 FLogger 的诞生背景。好吧,下面进入正题。
虽然 FLogger 只有几百行的代码,但是麻雀虽小五脏俱全,它可是拥有非常丰富的特性呢:
双缓冲队列
多种刷盘机制,支持时间触发、缓存大小触发、服务关闭强制触发等刷盘方式
多种 RollingFile 机制,支持文件大小触发、按天触发等 Rolling 方式
多日志级别,支持 debug、info、warn、error和 fatal 等日志级别
热加载,由日志事件触发热加载
超轻量,不依赖任何第三方库
性能保证,成功用于日交易额百亿级交易系统
既然是个超轻量级日志,使用肯定要很简单。为最大程度保持用户的使用习惯,Flogger 提供了与 log4j 几乎一样的日志 API。你只需要先获取一个实例,接下来的使用方式就非常简单了:
FLogger logger = FLogger.getInstance();
logger.info("Here is your message...");
logger.writeLog(Constant.INFO, "Here is your customized level message...");
logger.writeLog("error", Constant.ERROR, "Here is your customized log file and level message...");
使用前你需要在项目根路径下创建 log.properties 文件,配置如下:
########## 公共环境配置 ##########
CHARSET_NAME = UTF-8
########## 日志信息配置 ##########
# 日志级别
0:调试信息
1:普通信息
2:警告信息
3:错误信息
4:严重错误信息
LOG_LEVEL = 0,1,2,3,4
# 日志文件存放路径
LOG_PATH =./log
# 日志写入文件的间隔时间(默认为1000毫秒)
WRITE_LOG_INV_TIME = 1000
# 单个日志文件的大小(默认为10M)
SINGLE_LOG_FILE_SIZE =
# 单个日志文件缓存的大小(默认为10KB)
SINGLE_LOG_CACHE_SIZE = 10240
当然,为了提供最大程度的便捷性,日志内部针对所有配置项都提供了默认值,你大可不必担心缺少配置文件会抛出异常。
至此,你可能很好奇使用 FLogger 打印出来的日志格式到底是怎样的,会不会杂乱无章无法理解,还是信息不全根本无法判断上下文呢?好吧,你多虑了,FLogger 提供了非常规范且实用的日志格式,能使让你很容易理解且找到相关上下文。
先来看看上面的 demo 代码打印出来的结果:
21:07:32:840 [main] Here is your message...
21:07:32:842 [main] Here is your customized level message...
21:07:32:842 [main] Here is your customized log file and level message...
从上面可以看到,你可以很清楚的分辨出日志的级别、时间和内容等信息。到这其实很明了了,日志由以下几个元素组成:
[日志级别] 精确到毫秒的时间 [当前线程名] 日志内容
当然,处于便捷性的考虑,FLogger 目前并不支持用户定义日志格式,毕竟它的目的也不是要做成一个通用性或者可定制性非常高的日志来使用。
上面这么多都是围绕如何使用进行说明,下面就针对 FLogger 的特性进行实现逻辑的源码解析。
双缓冲队列
FLogger 在内部采用双缓冲队列,那何为双缓冲队列呢?它的作用又是什么呢?
FLogger 为每个日志文件维护了一个内部对象 LogFileItem ,定义如下:
public class LogFileItem {
public String logFileName = "";
public String fullLogFileName = "";
public long currLogSize = 0;
public char currLogBuff = 'A';
public ArrayList&StringBuffer& alLogBufA = new ArrayList&StringBuffer&();
public ArrayList&StringBuffer& alLogBufB = new ArrayList&StringBuffer&();
public long nextWriteTime = 0 ;
public String lastPCDate = "";
public long currCacheSize = 0;
在每次写日志时,日志内容作为一个 StringBuffer 添加到当前正在使用的 ArrayList&StringBuffer& 中,另一个则空闲。当内存中的日志输出到磁盘文件时,会将当前使用的 ArrayList&StringBuffer& 与空闲的 ArrayList&StringBuffer& 进行角色交换,交换后之前空闲的 ArrayList&StringBuffer& 将接收日志内容,而之前拥有日志内容的 ArrayList&StringBuffer& 则用来输出日志到磁盘文件。这样就可以避免每次刷盘时影响日志内容的接收(即所谓的 stop-the-world 效应)及多线程问题。流程如下:
关键代码如下:
日志接收代码
synchronized(lfi){
    if(lfi.currLogBuff == 'A'){
        lfi.alLogBufA.add(logMsg);
    }else{
        lfi.alLogBufB.add(logMsg);
    lfi.currCacheSize += CommUtil.StringToBytes(logMsg.toString()).
日志刷盘代码:
ArrayList&StringBuffer& alWrtLog = null;
synchronized(lfi){
    if(lfi.currLogBuff == 'A'){
        alWrtLog = lfi.alLogBufA;
        lfi.currLogBuff = 'B';
    }else{
        alWrtLog = lfi.alLogBufB;
        lfi.currLogBuff = 'A';
    lfi.currCacheSize = 0;
createLogFile(lfi);
int iWriteSize = writeToFile(lfi.fullLogFileName,alWrtLog);
lfi.currLogSize += iWriteS
多刷盘机制
FLogger 支持多种刷盘机制:
刷盘时间间隔触发
内存缓冲大小触发
退出强制触发
下面就来一一分析。
刷盘时间间隔触发
配置项如下:
# 日志写入文件的间隔时间(默认为1000毫秒)
WRITE_LOG_INV_TIME = 1000
当距上次刷盘时间超过间隔时间,将执行内存日志刷盘。
内存缓冲大小触发
配置项如下:
# 单个日志文件缓存的大小(默认为10KB)
SINGLE_LOG_CACHE_SIZE = 10240
当内存缓冲队列的大小超过配置大小时,将执行内存日志刷盘。
退出强制触发
FLogger 内部注册了 JVM 关闭钩子 ShutdownHook ,当 JVM 正常关闭时,由钩子触发强制刷盘,避免内存日志丢失。相关代码如下:
public FLogger(){
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
        @Override
        public void run() {
            close();
        }
    }));
当 JVM 异常退出时无法保证内存中的日志全部落盘,但可以通过一种妥协的方式来提高日志刷盘的实时度:设置 SINGLE_LOG_CACHE_SIZE = 0 或者 WRITE_LOG_INV_TIME = 0 。
刷盘代码如下:
public void run(){
    int i = 0 ;
    while(bIsRun){
        try{
            
            flush(false);
            
            if(i++ % 100 == 0){
                Constant.CFG_LOG_LEVEL = CommUtil.getConfigByString("LOG_LEVEL","0,1,2,3,4");
                i = 1;
            }
        }catch(Exception e){
            System.out.println("开启日志服务错误...");
            e.printStackTrace();
        }
public void close(){
    bIsRun = false;
    try{
        flush(true);
    }catch(Exception e){
        System.out.println("关闭日志服务错误...");
        e.printStackTrace();
private void flush(boolean bIsForce) throws IOException{
    long currTime = System.currentTimeMillis();
    Iterator&String& iter = logFileMap.keySet().iterator();
    while(iter.hasNext()){
        LogFileItem lfi = logFileMap.get(iter.next());
        if(currTime &= lfi.nextWriteTime || SINGLE_LOG_CACHE_SIZE &= lfi.currCacheSize || bIsForce == true){
            
            ArrayList&StringBuffer& alWrtLog = null;
            synchronized(lfi){
                if(lfi.currLogBuff == 'A'){
                    alWrtLog = lfi.alLogBufA;
                    lfi.currLogBuff = 'B';
                }else{
                    alWrtLog = lfi.alLogBufB;
                    lfi.currLogBuff = 'A';
                }
                lfi.currCacheSize = 0;
            }
            
            createLogFile(lfi);
            
            int iWriteSize = writeToFile(lfi.fullLogFileName,alWrtLog);
            lfi.currLogSize += iWriteS
        }
多 RollingFile 机制
同 log4j/logback,FLogger 也支持多种 RollingFile 机制:
按文件大小 Rolling
按天 Rolling
其中按文件大小 Rolling,配置项为:
# 单个日志文件的大小(默认为10M)
SINGLE_LOG_FILE_SIZE =
即当文件大小超过配置大小时,将创建新的文件记录日志,同时重命名旧文件为”日志文件名_日期_时间.log”(如 info_105.log)。
按天 Rolling 即每天产生不同的文件。
产生的日志文件列表可参考如下:
info_105.log
info_010.log
info_110.log
info_010.log
当前正在写入的日志文件为 info.log。
关键代码如下:
* 创建日志文件
* @param lfi
private void createLogFile(LogFileItem lfi){
    String currPCDate = TimeUtil.getPCDate('-');
    if(lfi.fullLogFileName != null && lfi.fullLogFileName.length() & 0 && lfi.currLogSize &= LogManager.SINGLE_LOG_FILE_SIZE ){
        File oldFile = new File(lfi.fullLogFileName);
        if(oldFile.exists()){
            String newFileName = Constant.CFG_LOG_PATH + "/" + lfi.lastPCDate + "/" + lfi.logFileName + "_" + TimeUtil.getPCDate() + "_"+ TimeUtil.getCurrTime() + ".log";
            File newFile = new File(newFileName);
            boolean flag = oldFile.renameTo(newFile);
            System.out.println("日志已自动备份为 " + newFile.getName() + ( flag ? "成功!" : "失败!" ) );
            lfi.fullLogFileName = "";
            lfi.currLogSize = 0;
        }
    if ( lfi.fullLogFileName == null || lfi.fullLogFileName.length() &= 0 || lfi.lastPCDate.equals(currPCDate) == false ){
        String sDir = Constant.CFG_LOG_PATH + "/" + currPCD
        File file = new File(sDir);
        if(file.exists() == false){
            file.mkdir();
        }
        lfi.fullLogFileName = sDir + "/" + lfi.logFileName + ".log";
        lfi.lastPCDate = currPCD
        file = new File(lfi.fullLogFileName);
        if(file.exists()){
            lfi.currLogSize = file.length();
        }else{
            lfi.currLogSize = 0;
        }
多日志级别
FLogger 支持多种日志级别:
FLogger 为每个日志级别都提供了简易 API,在此就不再赘述了。
打印 error 和 fatal 级别日志时,FLogger 默认会将日志内容输出到控制台。
FLogger 支持热加载,FLogger 内部并没有采用事件驱动方式(即新增、修改和删除配置文件时产生相关事件通知 FLogger 实时热加载),而是以固定频率的方式进行热加载,具体实现就是每执行完100次刷盘后才进行热加载(频率可调),关键代码如下:
int i = 0;
while(bIsRun){
    try{
        
        Thread.sleep(200);
        
        flush(false);
        
        if(i++ % 100 == 0){
            Constant.CFG_LOG_LEVEL = CommUtil.getConfigByString("LOG_LEVEL","0,1,2,3,4");
            
            i = 1;
        }
    }catch(Exception e){
        System.out.println("开启日志服务错误...");
        e.printStackTrace();
这么做完全是为了保持代码的精简和功能的纯粹性。事件驱动热加载无疑是更好的热加载方式,但需要新建额外的线程并启动对配置文件的事件监听,有兴趣的童鞋可自行实现。
FLogger 成功支撑了日交易额百亿级交易系统的日志服务,它的性能是经历过考验的。下面我们就来拿 FLogger 跟 log4j 做个简单的性能对比。
测试环境:Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz
4.00 GB Memory
64位操作系统
测试场景:单条记录72byte
共1000000条
写单个日志文件
FLogger 配置如下:
# 日志写入文件的间隔时间
WRITE_LOG_INV_TIME = 0
# 单个日志文件的大小
SINGLE_LOG_FILE_SIZE =
# 单个日志文件缓存的大小
SINGLE_LOG_CACHE_SIZE = 0
以上配置保证所有日志写入到单个文件,且尽量保证每一条记录不在内存中缓存,减少测试误差。
测试代码:
FLogger logger = FLogger.getInstance();
String record = "Performance Testing about log4j and cyfonly customized java project log.";
long st = System.currentTimeMillis();
for(int i=0; i&1000000; i++){
   logger.info(record);
long et = System.currentTimeMillis();
System.out.println("FLogger/log4j write 1000000 records with each record 72 bytes, cost :" + (et - st) + " millseconds");
日志内容:
[INFO] 2016-12-06 21:40:06:842 [main] Performance Testing about log4j and cyfonly customized java project log.
[INFO ]2016-12-06 21:41:12,852, [main]Log4jTest:12, Performance Testing about log4j and cyfonly customized java project log.
测试结果(执行10次取平均值):
FLogger write 1000000 records with each record 72 bytes, cost :2144 millseconds
log4j write 1000000 records with each record 72 bytes, cost :cost :12691 millseconds
说明:测试结果为日志全部刷盘成功的修正时间,加上各种环境的影响,有少许误差,在此仅做简单测试,并不是最严格最公平的测试对比。有兴趣的童鞋可进行精确度更高的测试。欢迎私下探讨,本人QQ:。
如果觉得文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
本文版权归作者和博客园共有,欢迎转载,未经同意须保留此段声明,且在文章页面明显位置给出原文连接。欢迎指正与交流。
http://mp.weixin.qq.com/s/VHHK3Mt4FKOMUst3NJpRfw
从七月份开始一直到九月底才看完设计模式,在这个过程中我不敢说我已经掌握了那本书里面的内容,或者说1/5,没能力说也没有资格说。但是结果不重要,重要的是这个过程我的收获!主要包括如下几个方面:
1、认识了这么多设计模式。刚刚接触java没多久就在学长那里听过设计模式的大名,但是由于能力有限,一直不敢触碰。而今有幸将其都认识了。
2、开始有设计的理论了。在接触设计模式之前没有怎么想过设计方面东东,看到问题就立马动手解决,没有想到怎么样来设计更好,如何来是这块更加优化、漂亮。
3、开始考虑系统的可扩展性了。
4、在遇到问题后开始想有那个设计模式会适用这个场景。
5、对面向对象有了更深一步的了解。
鄙人天资不聪慧,既不是聪明人,更不是那种天才,所有顿悟有限!!!闲话过多,先看如下两幅图片
设计模式之间的关系:
设计模式总概况:
一、设计原则
1、单一职责原则
一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。from:百度百科
2、开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
3、里氏代换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。from:百度百科
4、依赖倒转原则(Dependence Inversion Principle)
所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。 from:百度百科
5、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
6、合成复用原则(Composite Reuse Principle)
合成复用原则就是指在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用其已有功能的目的。简言之:要尽量使用组合/聚合关系,少用继承。
7、迪米特法则(最少知道原则)(Demeter Principle)
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。也就是说一个软件实体应当尽可能少的与其他实体发生相互作用。这样,当一个模块修改时,就会}

我要回帖

更多关于 nginx tomcat动静分离 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信