分类目录归档:产品研发

w3cschool,HTML,CSS,PHP,DOM,JavaScript,jQuery,XML,AJAX,ASP.NET,W3C,MySQL,SQL,jquery mobile,bootstrap,Python,jquery easyui,jquery ui,angularjs::::演道网的w3cschool,HTML,CSS,PHP,DOM,JavaScript,jQuery,XML,AJAX,ASP.NET,W3C,MySQL,SQL,jquery mobile,bootstrap,Python,jquery easyui,jquery ui,angularjs等技术都在这里。

网站性能优化(二)——应用服务器性能优化策略总结-演道网

接上篇,本篇讲的是网站性能优化在应用服务器端的优化。此处的应用服务器指的是处理网站业务的服务器,网站的业务代码都在这里,所以这里的优化也是网站性能优化中最复杂的一块优化

应用服务器的优化的首选依然是使用缓存,依次是代码优化,异步操作,使用集群。

一 使用缓存

1.1 缓存的原理

缓存是指将信息存储在系统能以相对较快的速度访问到的介质中(如内存),缓存优化速度的原因一方面是访问的速度快,一方面是存在缓存中的数据一般是计算后的的数据所以使用的时候无需重复计算。

缓存主要缓存读写比很高,但是很少变化的数据,比如说网站的菜单,热门词的搜索列表。访问的过程是到缓存中读取,如果读不到或者缓存已经失效,再去数据库中读取并且把新数据更新到缓存中。网站的数据访问经常遵循2,8定律,即80%的访问集中在20%的数据上,如果我们能把这20%的数据做一下缓存会大大提高系统的性能。

1.2 使用缓存的注意事项

1.2.1 缓存中不要存频繁修改的数据

缓存中保存频繁修改的数据就会出现缓存写入还来不及读出使用就已经过期的了情况,浪费缓存资源,徒增系统负担。一般来说缓存至少存一次使用两次才算有价值,道理显而易见。

1.2.2 没有热点的数据无须进缓存

没有热点的数据即用户很少进行读写的数据,因为缓存的数据存在内存中,内存资源成本高,因此将没有热点的数据放在缓存中是一种很大的浪费。

1.2.3 使用缓存会带来数据不一致以及脏读问题

一般来说使用缓存的时候会设置缓存过期时间,所以在缓存过期时间内用户修改了数据,那么缓存中的数据和数据库中的数据会产生不一致的情况导致脏读,大部分的数据可以容忍短期内的数据不一致,但是有的数据是绝对要实时的如电商系统中的库存和价格,读写频率很高但对实时性的要求也非常高,这个时候我们就要综合考虑了,可以采用数据库更新即更新缓存的策略,但这样会增加比较大的系统开销也会带来数据的事务一致性问题。

1.2.4 要保证缓存的可用性

像淘宝,京东,微博这样的大站,倘若有一天他们的缓存服务器突然都挂掉不能用了后果会怎么样?我猜许多高并发大流量的核心数据库服务器会因为一时间无法承受骤增的压力而倒塌。所以缓存一旦用上,数据库服务器已经习惯在有缓存层保护的环境下工作,我们就要尽可能保证缓存服务器的可用性。提高可用性的有效手段是使用缓存服务器集群,一台缓存服务器宕机,我们可以将访问转移到另一台。

1.2.5 要防范缓存穿透问题

假设我们有个功能是统计业务地区的的每日营业额,我们的业务地区集中在上海,北京,天津这几个大城市,这个时候我们只在缓存中缓存这几个地区的数据靠不靠谱?仔细想是不靠谱的,假设你们公司来了个新人,他对业务地区不熟悉,多配置了个湖南,显然按之前的设计湖南是不可能出现在缓存中的,所以这段业务代码会一直去读数据库。如果这样的漏洞被黑客发现了,那就更惨了,写一段恶意攻击代码不断地访问你没缓存的数据,这样会对数据库造成持续性的压力。

改善这种问题的对策是在缓存中将不存在的数据也缓存起来,值设为null。

1.2.6 缓存初建时要预热

缓存中的热点数据按理来说是通过LRU(最近最久未用算法)通过对不断访问的数据筛选出来的,这个过程要耗费较久的时间,而我们的数据库服务器可能扛不了那么久。所以在大系统的缓存层的设计中,最开始一般会手动将热点数据建立。这个过程称为缓存预热。

二 代码优化

我们在工作中经常听到编程菜鸟,编程大神这样的称谓,同样的一个功能何谓大神级实现什么时候又会被吐槽成菜鸟呢?这个问题的答案可能多种多样,但现在放在性能的这个角度来说,优良的代码就是使用更少的资源用更快的速度去处理任务。实现角度如下:

2.1 使用多线程

从资源利用的角度上看,使用多线程的原因有两个:IO阻塞和多CPU。最理想的系统load状态是既没有线程(进程)等待,,也没有CPU空闲。

2.2 资源复用

从编程的角度上来看的资源复用主要是指单例模式和对象池。单例模式是一个系统共用一个对象,对象池也是通过复用对象,来减少对象的创建和资源消耗。比如说对于数据库连接,每次创建数据库服务端都需要创建专门的资源来应对,因此频繁地创建数据连接关闭数据库连接对数据库服务器造成的压力是灾难性的。因此在大型系统中数据库连接往往通过数据池的方式,系统将创建好的数据库连接对象放入数据池,应用服务器需要连接数据库的时候就从对象池中获取一个空闲的连接,使用完毕之后再放回应用池。

2.3 数据结构

这个问题老生常谈了,每次面试基本上都会问的问题,可见他的重要程度。所谓程序就是数据结构+算法,良好的数据结构和算法在节省系统资源和提高程序效率都有裨益。至于如何评判数据结构和算法的优良又是另一个大话题的,此处不赘述。

2.4 垃圾回收

每种语言都有自己的垃圾回收机制,理解垃圾回收机制可以帮助程序优化和参数调优,并且可以减少写出内存泄漏代码的机率。这一方面的知识笔者也仍处于学习理解状态,不做详述,以免误导大家。

三 使用异步

做网站开发的时候有一条原则是可以晚一点做的事情就晚一点做。常见的如用户注册,用户下订单的email以及短信的发送。使用消息队列将调用异步化一方面可以提高网站的扩展性,另一方面可以起到很好的削峰作用以提高网站的响应速度即提高性能

四 使用集群

在网站高并发的情景下使用负载均衡技术搭建由多台服务器组成的集群,通过计算将用户的访问合理分配到多台服务器,这样可以避免单台服务器因压力过大而访问延迟。

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

美团点评 Docker 容器管理平台-演道网

本文是郑坤根据第14期美团点评技术沙龙“你不知道的美团云”演讲内容整理而成,已发表在《程序员》杂志2017年1月刊。

美团点评容器平台简介

本文介绍美团点评的Docker容器集群管理平台(以下简称“容器平台”)。该平台始于2015年,是基于美团云的基础架构和组件而开发的Docker容器集群管理平台。目前该平台为美团点评的外卖、酒店、到店、猫眼等十几个事业部提供容器计算服务,承载线上业务数百个,日均线上请求超过45亿次,业务类型涵盖Web、数据库、缓存、消息队列等。

为什么要开发容器管理平台

作为国内大型的O2O互联网公司,美团点评业务发展极为迅速,每天线上发生海量的搜索、推广和在线交易。在容器平台实施之前,美团点评的所有业务都是运行在美团私有云提供的虚拟机之上。随着业务的扩张,除了对线上业务提供极高的稳定性之外,私有云还需要有很高的弹性能力,能够在某个业务高峰时快速创建大量的虚拟机,在业务低峰期将资源回收,分配给其他的业务使用。美团点评大部分的线上业务都是面向消费者和商家的,业务类型多样,弹性的时间、频度也不尽相同,这些都对弹性服务提出了很高的要求。在这一点上,虚拟机已经难以满足需求,主要体现以下两点。

第一,虚拟机弹性能力较弱。使用虚拟机部署业务,在弹性扩容时,需要经过申请虚拟机、创建和部署虚拟机、配置业务环境、启动业务实例这几个步骤。前面的几个步骤属于私有云平台,后面的步骤属于业务工程师。一次扩容需要多部门配合完成,扩容时间以小时计,过程难以实现自动化。如果可以实现自动化“一键快速扩容”,将极大地提高业务弹性效率,释放更多的人力,同时也消除了人工操作导致事故的隐患。

第二,IT成本高。由于虚拟机弹性能力较弱,业务部门为了应对流量高峰和突发流量,普遍采用预留大量机器和服务实例的做法。即先部署好大量的虚拟机或物理机,按照业务高峰时所需资源做预留,一般是非高峰时段资源需求的两倍。资源预留的办法带来非常高的IT成本,在非高峰时段,这些机器资源处于空闲状态,也是巨大的浪费。

由于上述原因,美团点评从2015年开始引入Docker,构建容器集群管理平台,为业务提供高性能的弹性伸缩能力。业界很多公司的做法是采用Docker生态圈的开源组件,例如Kubernetes、Docker Swarm等。我们结合自身的业务需求,基于美团云现有架构和组件,实践出一条自研Docker容器管理平台之路。我们之所以选择自研容器平台,主要出于以下考虑。

快速满足美团点评的多种业务需求

美团点评的业务类型非常广泛,几乎涵盖了互联网公司所有业务类型。每种业务的需求和痛点也不尽相同。例如一些无状态业务(例如Web),对弹性扩容的延迟要求很高;数据库,业务的master节点,需要极高的可用性,而且还有在线调整CPU,内存和磁盘等配置的需求。很多业务需要SSH登陆访问容器以便调优或者快速定位故障原因,这需要容器管理平台提供便捷的调试功能。为了满足不同业务部门的多种需求,容器平台需要大量的迭代开发工作。基于我们所熟悉的现有平台和工具,可以做到“多快好省”地实现开发目标,满足业务的多种需求。

从容器平台稳定性出发,需要对平台和Docker底层技术有更高的把控能力

容器平台承载美团点评大量的线上业务,线上业务对SLA可用性要求非常高,一般要达到99.99%,因此容器平台的稳定性和可靠性是最重要的指标。如果直接引入外界开源组件,我们将面临3个难题:1. 我们需要摸熟开源组件,掌握其接口、评估其性能,至少要达到源码级的理解;2. 构建容器平台,需要对这些开源组件做拼接,从系统层面不断地调优性能瓶颈,消除单点隐患等;3. 在监控、服务治理等方面要和美团点评现有的基础设施整合。这些工作都需要极大的工作量,更重要的是,这样搭建的平台,在短时间内其稳定性和可用性都难以保障。

避免重复建设私有云

美团私有云承载着美团点评所有的在线业务,是国内规模最大的私有云平台之一。经过几年的经营,可靠性经过了公司海量业务的考验。我们不能因为要支持容器,就将成熟稳定的私有云搁置一旁,另起炉灶再重新开发一个新的容器平台。因此从稳定性、成本考虑,基于现有的私有云来建设容器管理平台,对我们来说是最经济的方案。

美团点评容器管理平台架构设计

我们将容器管理平台视作一种云计算模式,云计算的架构同样适用于容器。如前所述,容器平台的架构依托于美团私有云现有架构,其中私有云的大部分组件可以直接复用或者经过少量扩展开发。容器平台架构如下图所示。

美团点评容器管理平台架构
美团点评容器管理平台架构

可以看出,容器平台整体架构自上而下分为业务层、PaaS层、IaaS控制层及宿主机资源层,这与美团云架构基本一致。

业务层:代表美团点评使用容器的业务线,他们是容器平台的最终用户。
PaaS层:使用容器平台的HTTP API,完成容器的编排、部署、弹性伸缩,监控、服务治理等功能,对上面的业务层通过HTTP API或者Web的方式提供服务。
IaaS控制层:提供容器平台的API处理、调度、网络、用户鉴权、镜像仓库等管理功能,对PaaS提供HTTP API接口。
宿主机资源层:Docker宿主机集群,由多个机房,数百个节点组成。每个节点部署HostServer、Docker、监控数据采集模块,Volume管理模块,OVS网络管理模块,Cgroup管理模块等。
容器平台中的绝大部分组件是基于美团私有云已有组件扩展开发的,例如API,镜像仓库、平台控制器、HostServer、网络管理模块,下面将分别介绍。

API

API是容器平台对外提供服务的接口,PaaS层通过API来创建、部署云主机。我们将容器和虚拟机看作两种不同的虚拟化计算模型,可以用统一的API来管理。即虚拟机等同于set(后面将详细介绍),磁盘等同于容器。这个思路有两点好处:1. 业务用户不需要改变云主机的使用逻辑,原来基于虚拟机的业务管理流程同样适用于容器,因此可以无缝地将业务从虚拟机迁移到容器之上;2. 容器平台API不必重新开发,可以复用美团私有云的API处理流程
创建虚拟机流程较多,一般需要经历调度、准备磁盘、部署配置、启动等多个阶段,平台控制器和Host-SRV之间需要很多的交互过程,带来了一定量的延迟。容器相对简单许多,只需要调度、部署启动两个阶段。因此我们对容器的API做了简化,将准备磁盘、部署配置和启动整合成一步完成,经简化后容器的创建和启动延迟不到3秒钟,基本达到了Docker的启动性能。

Host-SRV

Host-SRV是宿主机上的容器进程管理器,负责容器镜像拉取、容器磁盘空间管理、以及容器创建、销毁等运行时的管理工作。

镜像拉取:Host-SRV接到控制器下发的创建请求后,从镜像仓库下载镜像、缓存,然后通过Docker Load接口加载到Docker里。

容器运行时管理:Host-SRV通过本地Unix Socker接口与Docker Daemon通信,对容器生命周期的控制,并支持容器Logs、exec等功能。

容器磁盘空间管理:同时管理容器Rootfs和Volume的磁盘空间,并向控制器上报磁盘使用量,调度器可依据使用量决定容器的调度策略。

Host-SRV和Docker Daemon通过Unix Socket通信,容器进程由Docker-Containerd托管,所以Host-SRV的升级发布不会影响本地容器的运行。

镜像仓库

容器平台有两个镜像仓库:

  • Docker Registry: 提供Docker Hub的Mirror功能,加速镜像下载,便于业务团队快速构建业务镜像;
  • Glance: 基于Openstack组件Glance扩展开发的Docker镜像仓库,用以托管业务部门制作的Docker镜像。

镜像仓库不仅是容器平台的必要组件,也是私有云的必要组件。美团私有云使用Glance作为镜像仓库,在建设容器平台之前,Glance只用来托管虚拟机镜像。每个镜像有一个UUID,使用Glance API和镜像UUID,可以上传、下载虚拟机镜像。Docker镜像实际上是由一组子镜像组成,每个子镜像有独立的ID,并带有一个Parent ID属性,指向其父镜像。我们稍加改造了一下Glance,对每个Glance镜像增加Parent ID的属性,修改了镜像上传和下载的逻辑。经过简单扩展,使Glance具有托管Docker镜像的能力。通过Glance扩展来支持Docker镜像有以下优点:

  • 可以使用同一个镜像仓库来托管Docker和虚拟机的镜像,降低运维管理成本;
  • Glance已经十分成熟稳定,使用Glance可以减少在镜像管理上踩坑;
  • 使用Glance可以使Docker镜像仓库和美团私有云“无缝”对接,使用同一套镜像API,可以同时支持虚拟机和Docker镜像上传、下载,支持分布式的存储后端和多租户隔离等特性;
  • Glance UUID和Docker Image ID是一一对应的关系,利用这个特性我们实现了Docker镜像在仓库中的唯一性,避免冗余存储。

可能有人疑问,用Glance做镜像仓库是“重新造轮子”。事实上我们对Glance的改造只有200行左右的代码。Glance简单可靠,我们在很短的时间就完成了镜像仓库的开发上线,目前美团点评已经托管超过16,000多个业务方的Docker镜像,平均上传和下载镜像的延迟都是秒级的。

高性能、高弹性的容器网络

网络是十分重要的,又有技术挑战性的领域。一个好的网络架构,需要有高网络传输性能、高弹性、多租户隔离、支持软件定义网络配置等多方面的能力。早期Docker提供的网络方案比较简单,只有None、Bridge、Container和Host这四种网络模式,也没有用户开发接口。2015年Docker在1.9版本集成了Libnetwork作为其网络的解决方案,支持用户根据自身需求,开发相应的网络驱动,实现网络功能自定义的功能,极大地增强了Docker的网络扩展能力。

从容器集群系统来看,只有单宿主机的网络接入是远远不够的,网络还需要提供跨宿主机、机架和机房的能力。从这个需求来看,Docker和虚拟机来说是共通的,没有明显的差异,从理论上也可以用同一套网络架构来满足Docker和虚拟机的网络需求。基于这种理念,容器平台在网络方面复用了美团云网络基础架构和组件。

美团点评容器平台网络架构
美团点评容器平台网络架构

数据平面: 我们采用万兆网卡,结合OVS-DPDK方案,并进一步优化单流的转发性能,将几个CPU核绑定给OVS-DPDK转发使用,只需要少量的计算资源即可提供万兆的数据转发能力。OVS-DPDK和容器所使用的CPU完全隔离,因此也不影响用户的计算资源。

控制平面: 我们使用OVS方案。该方案是在每个宿主机上部署一个自研的软件Controller,动态接收网络服务下发的网络规则,并将规则进一步下发至OVS流表,决定是否对某网络流放行。

MosBridge

在MosBridge之前,我们配置容器网络使用的是None模式。所谓None模式也就是自定义网络的模式,配置网络需要如下几步:

  1. 在创建容器时指定—net=None,容器创建启动后没有网络;
  2. 容器启动后,创建eth-pair;
  3. 将eth-pair一端连接到OVS Bridge上;
  4. 使用nsenter这种Namespace工具将eth-pair另一端放到容器的网络Namespace中,然后改名、配置IP地址和路由。

然而,在实践中,我们发现None模式存在一些不足:

  • 容器刚启动时是无网络的,一些业务在启动前会检查网络,导致业务启动失败;
  • 网络配置与Docker脱离,容器重启后网络配置丢失;
  • 网络配置由Host-SRV控制,每个网卡的配置流程都是在Host-SRV中实现的。以后网络功能的升级和扩展,例如对容器添加网卡,或者支持VPC,会使Host-SRV越来越难以维护。

为了解决这些问题,我们将眼光投向Docker Libnetwork。Libnetwork为用户提供了可以开发Docker网络的能力,允许用户基于Libnetwork实现网络驱动来自定义其网络配置的行为。就是说,用户可以编写驱动,让Docker按照指定的参数为容器配置IP、网关和路由。基于Libnetwork,我们开发了MosBridge – 适配美团云网络架构的Docker网络驱动。在创建容器时,需要指定容器创建参数—net=mosbridge,并将IP地址、网关、OVS Bridge等参数传给Docker,由MosBridge完成网络的配置过程。有了MosBridge,容器创建启动后便有了网络可以使用。容器的网络配置也持久化在MosBridge中,容器重启后网络配置也不会丢失。更重要的是,MosBridge使Host-SRV和Docker充分解耦,以后网络功能的升级也会更加方便。

解决Docker存储隔离性的问题

业界许多公司使用Docker都会面临存储隔离性的问题。就是说Docker提供的数据存储的方案是Volume,通过mount bind的方式将本地磁盘的某个目录挂载到容器中,作为容器的“数据盘”使用。这种本地磁盘Volume的方式无法做到容量限制,任何一个容器都可以不加限制地向Volume写数据,直到占满整个磁盘空间。

LVM-Volume方案
LVM-Volume方案

针对这一问题,我们开发了LVM Volume方案。该方案是在宿主机上创建一个LVM VG作为Volume的存储后端。创建容器时,从VG中创建一个LV当作一块磁盘,挂载到容器里,这样Volume的容量便由LVM加以强限制。得益于LVM机强大的管理能力,我们可以做到对Volume更精细、更高效的管理。例如,我们可以很方便地调用LVM命令查看Volume使用量,通过打标签的方式实现Volume伪删除和回收站功能,还可以使用LVM命令对Volume做在线扩容。值得一提地是,LVM是基于Linux内核Devicemapper开发的,而Devicemapper在Linux内核的历史悠久,早在内核2.6版本时就已合入,其可靠性和IO性能完全可以信赖。

适配多种监控服务的容器状态采集模块

容器监控是容器管理平台极其重要的一环,监控不仅仅要实时得到容器的运行状态,还需要获取容器所占用的资源动态变化。在设计实现容器监控之前,美团点评内部已经有了许多监控服务,例如Zabbix、Falcon和CAT。因此我们不需要重新设计实现一套完整的监控服务,更多地是考虑如何高效地采集容器运行信息,根据运行环境的配置上报到相应的监控服务上。简单来说,我们只需要考虑实现一个高效的Agent,在宿主机上可以采集容器的各种监控数据。这里需要考虑两点:

  1. 监控指标多,数据量大,数据采集模块必须高效率;
  2. 监控的低开销,同一个宿主机可以跑几十个,甚至上百个容器,大量的数据采集、整理和上报过程必须低开销。
    监控数据采集方案
    监控数据采集方案

针对业务和运维的监控需求,我们基于Libcontainer开发了Mos-Docker-Agent监控模块。该模块从宿主机proc、CGroup等接口采集容器数据,经过加工换算,再通过不同的监控系统driver上报数据。该模块使用GO语言编写,既可以高效率,又可以直接使用Libcontainer。而且监控的数据采集和上报过程不经过Docker Daemon,因此不会加重Daemon的负担。

在监控配置这块,由于监控上报模块是插件式的,可以高度自定义上报的监控服务类型,监控项配置,因此可以很灵活地适应不同的监控场景的需求。

支持微服务架构的设计

近几年,微服务架构在互联网技术领域兴起。微服务利用轻量级组件,将一个大型的服务拆解为多个可以独立封装、独立部署的微服务实例,大型服务内在的复杂逻辑由服务之间的交互来实现。

美团点评的很多在线业务是微服务架构的。例如美团点评的服务治理框架,会为每一个在线服务配置一个服务监控Agent,该Agent负责收集上报在线服务的状态信息。类似的微服务还有许多。对于这种微服务架构,使用Docker可以有以下两种封装模式。

  1. 将所有微服务进程封装到一个容器中。但这样使服务的更新、部署很不灵活,任何一个微服务的更新都要重新构建容器镜像,这相当于将Docker容器当作虚拟机使用,没有发挥出Docker的优势。
  2. 将每个微服务封装到单独的容器中。Docker具有轻量、环境隔离的优点,很适合用来封装微服务。不过这样可能产生额外的性能问题。一个是大型服务的容器化会产生数倍的计算实例,这对分布式系统的调度和部署带来很大的压力;另一个是性能恶化问题,例如有两个关系紧密的服务,相互通信流量很大,但被部署到不同的机房,会产生相当大的网络开销。

对于支持微服务的问题,Kubernetes的解决方案是Pod。每个Pod由多个容器组成,是服务部署、编排、管理的最小单位,也是调度的最小单位。Pod内的容器相互共享资源,包括网络、Volume、IPC等。因此同一个Pod内的多个容器相互之间可以高效率地通信。

我们借鉴了Pod的思想,在容器平台上开发了面向微服务的容器组,我们内部称之为set。一个set逻辑示意如下图所示。

Set逻辑示意图
Set逻辑示意图

set是容器平台的调度、弹性扩容/缩容的基本单位。每个set由一个BusyBox容器和若干个业务容器组成, BusyBox容器不负责具体业务,只负责管理set的网络、Volume和IPC配置。

set的配置json
set的配置json

set内的所有容器共享网络,Volume和IPC。set配置使用一个JSON描述(如图6所示),每一个set实例包含一个Container List,Container的字段描述了该容器运行时的配置,重要的字段有:

  • Index,容器编号,代表容器的启动顺序;
  • Image,Docker镜像在Glance上的name或者ID;
  • Options,描述了容器启动时的参数配置。其中CPU和MEM都是百分比,表示这个容器相对于整个set在CPU和内存的分配情况(例如,对于一个4核的set而言,容器CPU:80,表示该容器将最多使用3.2个物理核)。

通过set,我们将美团点评的所有容器业务都做了标准化,即所有的线上业务都是用set描述,容器平台内只有set,调度、部署、启停的单位都是set。
对于set的实现上我们还做了一些特殊处理:

  • Busybox具有Privileged权限,可以自定义一些sysctl内核参数,提升容器性能。
  • 为了稳定性考虑,用户不允许SSH登陆Busybox,只允许登陆其他业务容器。
  • 为了简化Volume管理,每一个set只有一个Volume,并挂载到Busybox下,每个容器相互共享这个Volume。

很多时候一个set内的容器来自不同的团队,镜像更新频度不一,我们在set基础上设计了一个灰度更新的功能。该功能允许业务只更新set中的部分容器镜像,通过一个灰度更新的API,即可将线上的set升级。灰度更新最大的好处是可以在线更新部分容器,并保持线上服务不间断。

Docker稳定性和特性的解决方案:MosDocker

众所周知,Docker社区非常火热,版本更新十分频繁,大概2~4个月左右会有一个大版本更新,而且每次版本更新都会伴随大量的代码重构。Docker没有一个长期维护的LTS版本,每次更新不可避免地会引入新的Bug。由于时效原因,一般情况下,某个Bug的修复要等到下一个版本。例如1.11引入的Bug,一般要到1.12版才能解决,而如果使用了1.12版,又会引入新的Bug,还要等1.13版。如此一来,Docker的稳定性很难满足生产场景的要求。因此十分有必要维护一个相对稳定的版本,如果发现Bug,可以在此版本基础上,通过自研修复,或者采用社区的BugFix来修复。

除了稳定性的需求之外,我们还需要开发一些功能来满足美团点评的需求。美团点评业务的一些需求来自于我们自己的生产环境,而不属于业界通用的需求。对于这类需求,开源社区通常不会考虑。业界许多公司都存在类似的情况,作为公司基础服务团队就必须通过技术开发来满足这种需求。

基于以上考虑,我们从Docker 1.11版本开始,自研维护一个分支,我们称之为MosDocker。之所以选择从版本1.11开始,是因为从该版本开始,Docker做了几项重大改进:
Docker Daemon重构为Daemon、Containerd和runC这3个Binary,并解决Daemon的单点失效问题;

  • 支持OCI标准,容器由统一的rootfs和spec来定义;
  • 引入了Libnetwork框架,允许用户通过开发接口自定义容器网络;
  • 重构了Docker镜像存储后端,镜像ID由原来的随即字符串转变为基于镜像内容的Hash,使Docker镜像安全性更高。

到目前为止,MosDocker自研的特性主要有:

  1. MosBridge,支持美团云网络架构的网络驱动, 基于此特性实现容器多IP,VPC等网络功能;
  2. Cgroup持久化,扩展Docker Update接口,可以使更多的CGroup配置持久化在容器中,保证容器重启后CGroup配置不丢失。
  3. 支持子镜像的Docker Save,可以大幅度提高Docker镜像的上传、下载速度。

总之,维护MosDocker使我们可以将Docker稳定性逐渐控制在自己手里,并且可以按照公司业务的需求做定制开发。

在实际业务中的推广应用

在容器平台运行的一年多时间里,已经接入了美团点评多个大型业务部门的业务,业务类型也是多种多样。通过引入Docker技术,为业务部门带来诸多好处,典型的好处包括以下两点。

  • 快速部署,快速应对业务突发流量。由于使用Docker,业务的机器申请、部署、业务发布一步完成,业务扩容从原来的小时级缩减为秒级,极大地提高了业务的弹性能力。
  • 节省IT硬件和运维成本。Docker在计算上效率更高,加之高弹性使得业务部门不必预留大量的资源,节省大量的硬件投资。以某业务为例,之前为了应对流量波动和突发流量,预留了32台8核8G的虚拟机。使用容器弹性方案,即3台容器+弹性扩容的方案取代固定32台虚拟机,平均单机QPS提升85%, 平均资源占用率降低44-56%(如图7,8所示)。
  • Docker在线扩容能力,保障服务不中断。一些有状态的业务,例如数据库和缓存,运行时调整CPU、内存和磁盘是常见的需求。之前部署在虚拟机中,调整配置需要重启虚拟机,业务的可用性不可避免地被中断了,成为业务的痛点。Docker对CPU、内存等资源管理是通过Linux的CGroup实现的,调整配置只需要修改容器的CGroup参数,不必重启容器。
某业务虚拟机和容器平均单机QPS
某业务虚拟机和容器平均单机QPS
某业务虚拟机和容器资源使用量
某业务虚拟机和容器资源使用量

结束语

本文介绍了美团点评Docker的实践情况。经过一年的推广实践,从部门内部自己使用,到覆盖公司大部分业务部门和产品线;从单一业务类型到公司线上几十种业务类型,证明了Docker这种容器虚拟化技术在提高运维效率,精简发布流程,降低IT成本等方面的价值。

目前Docker平台还在美团点评深入推广中。在这个过程中,我们发现Docker(或容器技术)本身存在许多问题和不足,例如,Docker存在IO隔离性不强的问题,无法对Buffered IO做限制;偶尔Docker Daemon会卡死,无反应的问题;容器内存OOM导致容器被删除,开启OOM_kill_disabled后可能导致宿主机内核崩溃等问题。因此Docker技术,在我们看来和虚拟机应该是互补的关系,不能指望在所有场景中Docker都可以替代虚拟机,因此只有将Docker和虚拟机并重,才能满足用户的各种场景对云计算的需求。

不想错过技术博客更新?想给文章评论、和作者互动?第一时间获取技术沙龙信息?

请关注我们的官方微信公众号“美团点评技术团队”。现在就拿出手机,扫一扫:

公众号二维码

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

2017 年,你应该这样学习 Web 编程—— 内置索引 + 外置搜索引擎-演道网

如果你不会使用 Google 去搜索,那么你是一个新手。而如果你只学会如何使用 Google,但是不知道搜索什么,那么你也仍是一个新手。

最初我对这个问题的思考,来自于一年前的一篇相关的文章《程序员的内置索引与外置的Google》。当时,文章的主要对比点是,门户网站与 Google。两者有一些明显的区别:

  • 门户网站更适合那些什么都不知道,从头开始探索互联网的人。

  • 搜索引擎更适合你知道相似的东西,但是忘记具体的细节。

也因此,学习应用型技术变成了一项相当简单的事。你只需要知道它有什么(索引),然后去了解怎么用(搜索)即可。

从怎么学到学什么

开始之前,先让我介绍一下,我的学习框架、语言的方式:

  • 买本中文书或者找个教程、官方的 Guide,花个十几分钟了解一下目录。

  • 直接找个官方的示例,运行一下 Demo。

  • 上手写写应用。

  • 查看官方文档,看看自己是不是漏掉了什么重要的东西。

首先,你有了一份入门资料了,并且也已经有官方的文档了。然后你只需要一步步去做就可以了,不会的地方你就可以搜索到。难怪,程序员被喻为新蓝领工人

你拿上一份框架的说明书、一份需求文档、一个搜索引擎,就可以很容易地制造出一个产品。唯一的门槛是,你需要会读懂这些内容。这有点像新的知识阶级,只是门槛不再是识字与否,而在于是否能懂编程的知识。

将学习编程与门户网站、搜索引擎相比,就是:

  • 当你是一个新手程序员的时候,你需要一本书、一份指南、一个教程来作为索引,并学习上面的一个个内容。

  • 当你是一个有经验的程序员时,你只需要一个搜索引擎,因为你的脑子里已经有了整个世界。

当你不会使用 Google 时,你可能会这样去搜索资料(参见:英国老人坚持用敬语谷歌搜索 成网红被怒赞):please translate these roman numerals mcmxcviii, thank you。

这种感觉就好像是,你在使用机器人“娇娇”,背后有一个人一样:

可惜,机器人都是晚期直男癌,喜欢单刀直入。

当你只会使用 Google 时,你只能去知乎、SegmentFault 或者 StackOverflow 提个问题:

过去,我花了相当长的时间,在探索学习什么的问题。毕竟学习是相当简单的一件事,你只需要抽点时间、找个空间、研究个点就可以了。在这其中,最难的地方是研究一个点。因为你根本不知道,需要学习什么?并非所有的人,都能找到合适的路线。

索引与图谱

当你在某个领域拥有多年的经验时,你就可以将它整理为各式各样的图谱、技能树等等。如:

这样的图谱,就像门房网站一样,在上面列好了一个个的知识点。

它按照不同的类别,一一的归类。稍有区别的是,这些类别都会相应的内容与之对应。而你在技能汇总上是看不到的,这也就是为什么像技能树这样的工具,也会相当的受欢迎。

人们需要的,不仅仅是一张简单的地图,还需要导航功能。技能图谱、技能汇总等等类似的图谱,它们都只是一些简单的工具。你还需要辅助相应的内容,如文章,视频、教程等等的资料。

在这个时候,或者你需要的是一个 Awesome-xx 的项目,上面不仅仅有目录,还有各式各样的资料。点击到相应的链接,你可以看到代码、应用。

初学的时候,你只需要找到一份合适的索引。学到一定程度的时候,你就可以和我一样创造相应的索引,还有各种资料,如 Growth(https://github.com/phodal/growth)。随后,你就可以对比不同的索引,来完善自己的知识休系。

不断的更新索引

小学的时候,你学会了基本的数学知识,如加法,除法,乘法。你开始在初中的时候,开始解决各种复杂的二次、三次方程、图形计算等等的问题。

从哇哇坠地在医院,到初高中毕业,学习的大部分知识都是别人觉得重要的。学习的时候,教育者们出于某种目的,已经为你规划好了一个个的路线。

工作的时候,领导们仍然会出于某种目的,为你规划好一个学习路线。你并不需要知道自己需要去什么,你像游戏中的国王一样,按别人的规划一步步地往前走。

如果别人为你规则学习的路线是合理的,那么学习起来就会很轻松。反之,你就会很痛苦,开始质疑合理性。

知识本身具有连贯性——这就是《技术发展的本质》一书所阐述的。你把一个现代的智能手机,交给 20 年前的用户,他都不知道怎么用。

如果你是经历过手机 20 年的更新换代,那么你就很容易地就可以上手各种手机。与些同时,你并需要从 20 年前的大哥大开始用起。这也是大部分开发者的学习过程,但是并意味着你需要从头学起。你仍然可以忽略过很多东西,然后学习最新技术。

令人遗憾的是,知识本身不是静止的,而是一个不断发展的过程。就连吃饭,你都要学习使用不同的工具,如西餐。只有基础本身是静止(相对)的,一旦涉及到应用都是变化的——你学习的 A 技术,经过一段时间,都会被市场淘汰。

这时,你需要像爬虫一样,不断地去抓取新的网页,新的知识点。



转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

一群黑客聚在一起开会是什么场面?-演道网

按照我们团队的内部会议评定标准和个人参会演讲经历讲一讲吧。这几年大概讲过这些比较大的会议,先讲大陆外的。

Tier One(以下排名不分先后):BlackHat USA

工业界第一会议,毋庸置疑。规模最大,研究方向众多,参会人数最多,业界关注度最高。Blackhat USA 作为全球顶尖的安全技术会议,是全球安全圈每一位研究者都梦想登上的舞台,也是全球安全界的盛大集会。Blackhat 还没开始前邮箱里已经有五六个 Party/Meetup 的邀请了,包括来自于 Google, Microsoft, Qualcomm, Facebook 和 BASec 这样的华人大拿聚会。

BlackHat USA 上有哪些东西?

Blackhat 按日程分为三个部分:最早开始的是 training。这一般是业界大佬来作为讲师,对某个方向做手把手的深入指导。当然学费也相当不菲,几千刀小意思,其中有很大一部分是作为了 Blackhat 主办方的分成。当然现在随着各大公司对信息安全的重视程度提高,很多人也以公费留学的形式来参加培训,这次来我也看到了很多 3BATH 的人员公费派来学习,挂着 Training 牌子。早些年信息安全还没这么火的时候,现在业界一些顶级大牛曾经不远万里自费来 training 学习,这种动力或许也是他们如今能站在技术之巅的原因。

Training 之后就是万众瞩目的 Briefings 了。Arsenal 也和 Briefings 同时进行,同时在 Business Area 还有各大安全厂商的现场宣讲活动(无论是招人还是给产品做广告。相对来讲招人的会比较含蓄,卖产品的则五花八门招数应有尽有)。 Briefings 分为 25 分钟和 50 分钟,还有 100 分钟的 Workshop。其中 25 分钟今年开始提供机票但不提供住宿,50 分钟则是全包,讲师费也多一倍。50 分钟的 Topic 一般投的人会更多些,也相对更难中选,毕竟主办方要出更多的钱,而 25 分钟 slot 比较多,也相对好中一些。像国内的盘古团队是年年都中 50 分钟,非常厉害。今年投稿人数再创新高,根据官方消息中选率约在 1/7,总共 700 多篇投稿选中了 100 多篇。

Briefings 一旦中选则会给所有的 Speaker 提供门票,所以往年也有一个 Session 下面挂了七八个演讲者的情况。Blackhat 虽然不赞许这种做法但是最后也容忍了。 主办方会根据 Topic 的热门程度决定演讲的房间大小。最大最炫酷的当然是 Keynote 所在的地方,也就是 Jeff Moss(Blackhat 创始人)开场让大家 high 起来的地方,可以坐下几千人也就这一个房间有跟踪拍摄实时投影,简直不能更帅。

附几张现场图片,有兴趣的同学可以看我去年去演讲的时候写过的专栏:

(知乎专栏: 白帽赌城演讲记:Trend, Tech, Team,还有打枪)

去年还在那边顺便参加了 Googe 和 Qualcomm 的 researcher party,Google 请了一顿牛排,送了一个 Pixel C 笔记本,一个 Nexus 5X,用这两个设备又挖了不少漏洞 233.

在现场还有 Pwnie Award 颁奖典礼,也是业界关注的一件大事。可惜现场主持人大佬们老喜欢开美式笑话,中国人理解不能。

拉斯维加斯也是个非常适宜于娱乐的地方,吃喝 X 赌打枪一应俱全,X 这个请老司机来解答,我什么都不知道,其他的例如找个靶场实弹射击还是很 high 的。在那边打了 M16, AK47, Glock, SCAR Red dot.

乱入,打枪的时候碰到的美女 233

学术界四大安全会议: Oakland, CCS, USENIX, NDSS

这四个会议是信息安全学术界的顶级会议。和国内一些安全学术界固步自封于密码学、合规之类的不同,国外安全圈还是有很多实干型研究的。ROP, ASLR, ret2dir, Control Flow Guard, 这些引领一时风骚的攻击技术都是在这些学术会议上最先被公布的。

2015 年我们团队的 Wen Xu(前交大 ACM 班,Oops 前副队长,现 Georgia Tech PhD candidate) 通过 Pingpong Root (CVE-2015-3636)的杰出研究中选 CCS,该成果也发表在了 BlackHat USA 2015,得到了广泛认可。

不过国内工业界的人士一般不太会有机会去这种会议,但建议把这些会议的论文都仔细读一读,非常有价值。

Tier Two:CanSecWest

在加拿大温哥华举办,老牌安全会议,主要关注二进制安全,包含 Training 和 Briefings。随 CanSecWest 一起举办的 Pwn2Own 更是全球黑客顶级赛事。

(Pwn2Own 上每一次攻破都激动人心)

去年我们在 CanSecWest 发表了对 Apple Graphics 的研究成果。

会场比 BlackHat 的小,但是大拿还是非常多。温哥华也是一个很漂亮的城市,有很多华人,Stanley Park 也是会后散心好去处,downtown 的越南米粉也是相当好吃。(去年我们打 Pwn2Own 之前在酒店通宵了两天,饭都是 711 盒饭解决,比赛打完才出去看了看风景)

RECON (RECON.CX)

北美顶尖二进制安全峰会,在加拿大蒙特利尔举办,偏向于底层(操作系统,硬件,固件安全)。我们去年在会上分享了 Pwn2Own 2016 中我们使用的内核漏洞。国外的会议和国内的会议有一个很大的不同点是,国外会议不会有赠票。想有票要么去投稿中了当 Speaker,自然有门票。要么老老实实买票。所以在国外会议上,基本不会有国内这种人在上面讲,下面听的都跑出去唠嗑的现象。我们甚至在会场发现一个残疾人士依然坐着轮椅来现场听讲,令人感慨。

我们在现场和 Alex Ionescu (Windows 圣经 Windows Internals 作者, Windows 内核第一专家,传闻微软专门把 Windows 源代码开放给了他)的合影。

蒙特利尔也是一个很美丽的地方,风景空气都让人心旷神怡,感叹下天朝…

DEFCON

DEFCON 和 BlackHat USA 一般都是前后脚举办。相对于 BlackHat 的逼格高大上,DEFCON 则更像是一个安全大 party,全民运动。在会场既可以看到白发苍苍的穿着 DEFCON 4 纪念 Tshirt 的老大爷(DEFCON 4 举办于 1996 年),也可以看到穿着 DEFCON kids/girls 衣服的被家长带去各个 village 黑客从小学抓起的小朋友们。Village 里什么都搞,开锁,Car Hacking,etc…

Briefings 里分享的话题也各式各样,但实战的话题永远最受人欢迎。我们去年分享了关于各种 Sandbox bypass 的技巧和原理,下来之后朋友告诉我一直到结束外面还有人在排队没挤进去。。

Badge 别具一格

今年的 DARPA CGC 环节引入到了 DEFCON CTF 中,以 Rise of the machine 为主题,力图考察自动化挖掘和利用漏洞的研究,虽然最终机器由于比赛协议问题没能取得佳绩,但是还是大大推动了漏洞挖掘技术的发展。

DEFCON CTF 比赛中的蓝莲花,友情出镜我浙学弟 zyf 清华大佬 MaskRay

CGC 比赛现场

Qualcomm Security Summit

高通举办的一年一度的移动安全会议,因为高通的号召力,基本业内所有安卓大佬都集齐了。会上干货满满,会后还会组织打 paint ball,也是保留节目。我们去年分享了 sandbox bypass 和 rooting 两个议题。

Android Security 的老大:

我们团队的 retme 和 jfang

高通园区和他们自己搞的一个自动行走机器人。

待续:

BlackHat Europe

BlackHat 在欧洲的分会,在阿姆斯特丹或者伦敦举办。在每年 11 月份左右举办。相对于 USA 逊色一些,但依然是欧洲地区顶级会议之一。我们团队的 retme 和 jfang 今年在 BlackHat EU 发表了 关于 Android Rooting 的演讲,主要涉及到厂商定制的 Android 内核中 wireless extension 代码引发的漏洞。

阿姆斯特丹有两个东西世界闻名, 去那里开会的时候可以体验一下。

Infiltrate

这个会议国内知名度比较低,但事实上也是北美地区的顶会之一,每年都是各种大佬云集。在 Miami 主办方包 5 天 4 夜的住宿,风景宜人,冲浪也是好去处。这个会议一个特点是选稿使用投票制,主办方初审投稿之后会公布出来,由大家投票决定。

因为老外们一般都比较直接,一些不太好的议题如果也在投票列表中,会被评论喷的很惨,实在是悲剧啊。

Zeronights

老毛子办的会,主要面向欧洲区域。我们团队的 Peter,Marco,聂博都在 Zeronights 发表过演讲。

BlackHat Asia

Blackhat 在新加坡的亚洲分会场,一般在每年 3、4 月份举行。会场相对于 USA 主会场的奢华小了不

少,亚太地区的人参加的比较多。算是 BlackHat 三个区域中最小的一个了。去年我在会上分享了 android binder 攻击的议题。

可能唯一的好处就是主办方提供的酒店是著名的新加坡 Marina Bay Sands, 演讲之余可以体验下空中之船和空中泳池。


HITB (Hack in the box, Singapore & Amsterdam)

HITB 也是老牌会议,每年举办两次。其中 HITB Singapore 也是投票 CFP 制度,和 Infiltrate 有些类似。不过 Amsterdam 那场最近受 BlackHat EU 冲击有点大。

POC (Power of community)

韩国的新兴会议,最近几年质量稳步提升,也在承办 Pwn2Own 类似的比赛。韩国人的个性还是很认真务实的,会议质量也比较高。去年我和同事分享了 Mobile Pwn2Own 用到的一些研究成果。

主办方的韩国妹子。

Tier Three:HITCON

HITCON 之前分为 community 和 enterprise 两个分会场,类似于 DEFCON 和 BlackHat 的组合。但今年的 HITCON 因为政治因素,导致很多演讲者都没去成,逊色了不少,成色有所下降。在之前堪称大中华区第一安全会议,吸引了很多国际演讲者。

2015 年我在 HITCON 做过演讲,还是体验很好的。那一年 geohot 在现场做了 keynote speech,介绍了他新开发的利器实时程序追踪框架 qira,适合逆向和编写 exp 时的调试,现场参加的人也非常多,会议整体氛围不输于国外会议。

适合普通开发者入门的会议:

现在一般大的软件开发会议也都有安全分会场,例如 QCON,会比较浅显易懂,适合开发者了解和入门。议题一般注重安全开发,安全产品和安全响应体系建设等. 2014 年我在 QCON 分享了基于程序分析的 Android App 漏洞自动化检测系统的技术要点.

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

MySQL 如何存储大数据?-演道网

最近,在工作中遇到了MySQL中如何存储长度较长的字段类型问题,于是花了一周多的时间抽空学习了一下,并且记录下来。

MySQL大致的逻辑存储结构在这篇文章中有介绍,做为基本概念:InnoDB 逻辑存储结构

注:文中所指的大数据指的是长度较长的数据字段,包括varchar/varbinay/text/blob。

Compact行格式

我们首先来看一下行格式为Compact是如何存储大数据的:

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.1.73    |
+-----------+
1 row in set (0.01 sec)

mysql> show table status like 'row'\G;
*************************** 1. row ***************************
           Name: row
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 1
 Avg_row_length: 81920
    Data_length: 81920
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2017-01-04 21:46:02
    Update_time: NULL
     Check_time: NULL
      Collation: latin1_swedish_ci
       Checksum: NULL
 Create_options: 
        Comment: 
1 row in set (0.00 sec)

我们建立一张测试表,插入数据:

CREATE TABLE `row` (
  `content` varchar(65532) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1

mysql> insert into row(content) select repeat('a',65532);
Query OK, 1 row affected (0.03 sec)
Records: 1  Duplicates: 0  Warnings: 0

我们使用py_innodb_page_info.py工具来查看表中的页分布:

[root@localhost mysql]# python py_innodb_page_info.py -v com/row.ibd 
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000004, page type <Uncompressed BLOB Page>
page offset 00000005, page type <Uncompressed BLOB Page>
page offset 00000006, page type <Uncompressed BLOB Page>
page offset 00000007, page type <Uncompressed BLOB Page>
Total number of page: 8:
Insert Buffer Bitmap: 1
Uncompressed BLOB Page: 4
File Space Header: 1
B-tree Node: 1
File Segment inode: 1

可以看出,第4页的, page level <0000>格式为数据页,存放着MySQL的行数据。可以理解为MySQL存放大数据的地方,暂且叫作外部存储页。Compact格式没有将大数据全部放在数据页中,而是将一部分数据放在了外部存储页中。那么,是全部数据在外部存储页中,还是一部分数据。假如是一部分数据,这一部分是多少呢?

我们使用hexdump -Cv row.ibd查看一下数据页, page level <0000>,也就是第4页:

3073 0000c000  8c 25 17 57 00 00 00 03  ff ff ff ff ff ff ff ff  |.%.W....????????|
3074 0000c010  00 00 00 00 00 07 3a b8  45 bf 00 00 00 00 00 00  |......:?E?......|
3075 0000c020  00 00 00 00 00 02 00 02  03 a6 80 03 00 00 00 00  |.........?......|
3076 0000c030  00 7f 00 05 00 00 00 01  00 00 00 00 00 00 00 00  |................|
3077 0000c040  00 00 00 00 00 00 00 00  00 13 00 00 00 02 00 00  |................|
3078 0000c050  00 02 00 f2 00 00 00 02  00 00 00 02 00 32 01 00  |...?.........2..|
3079 0000c060  02 00 1c 69 6e 66 69 6d  75 6d 00 02 00 0b 00 00  |...infimum......|
3080 0000c070  73 75 70 72 65 6d 75 6d  14 c3 00 00 10 ff f1 00  |supremum.?...??.|
3081 0000c080  00 00 00 04 03 00 00 00  00 13 12 80 00 00 00 2d  |...............-|
3082 0000c090  01 10 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |..aaaaaaaaaaaaaa|
3083 0000c0a0  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
3084 0000c0b0  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
3085 0000c0c0  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
....
....
3128 0000c370  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
3129 0000c380  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
3130 0000c390  61 61 00 00 00 02 00 00  00 04 00 00 00 26 00 00  |aa...........&..|
3131 0000c3a0  00 00 00 00 fc fc 00 00  00 00 00 00 00 00 00 00  |....??..........|
3132 0000c3b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
3133 0000c3c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
3134 0000c3d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
...
4093 0000ffc0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
4094 0000ffd0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
4095 0000ffe0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
4096 0000fff0  00 00 00 00 00 70 00 63  01 a1 6c 2b 00 07 3a b8  |.....p.c.?l+..:?|

我们可以看出,数据页中存储了一部分数据,算下来一共是768字节,然后剩余部分存储在外部存储页中。那么数据页与外部存储页、外部存储页与外部存储页是如何连接在一起的呢?

我们观察这一行:

3130 0000c390  61 61 00 00 00 02 00 00  00 04 00 00 00 26 00 00  |aa...........&..|
3131 0000c3a0  00 00 00 00 fc fc 00 00  00 00 00 00 00 00 00 00  |................|

这一行是前缀768字节的结尾。注意最后的20个字节:

  • 00 00 00 02:4字节,代表外部存储页所在的space id
  • 00 00 00 04:4字节,代表第一个外部页的Page no
  • 00 00 00 26:4字节,值为38,指向blob页的header
  • 00 00 00 00 00 00 fc fc:8字节,代表该列存在外部存储页的总长度。此处的值为64764,加上前缀768正好是65532。(注意一点,虽然表示BLOB长度的是8字节,实际只有4个字节能使用,所有对于BLOB字段,存储数据的最大长度为4GB。)

验证下第一个外部存储页的头部信息:

4097 00010000  cd c3 b6 8e 00 00 00 04  00 00 00 00 00 00 00 00  |?ö.............|
4098 00010010  00 00 00 00 00 06 b8 a2  00 0a 00 00 00 00 00 00  |......??........|
4099 00010020  00 00 00 00 00 02 00 00  3f ca 00 00 00 05 61 61  |........??....aa|
4100 00010030  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
...
...

前38个字节为File Header(关于InnoDB数据页的详细结构请参见《MySQL技术内幕 InnoDB存储引擎》4.4),这个简单提一下:

  • cd c3 b6 8e:4字节,该页的checksum。
  • 00 00 00 04:4字节,页偏移,此页为表空间中的第5个页。
  • 00 00 00 00:4字节,当前页的上一个页。此页为,所以没有上一页。
  • 00 00 00 00:4字节,当前页的下一个页。此页为,所以没有下一页。
  • 00 00 00 00 00 06 b8 a2:8字节,该页最后被修改的日志序列位置LSN。
  • 00 0a:2字节,页类型,0x000A代表BLOB页。
  • 00 00 00 00 00 00 00 00:8字节,略过。
  • 00 00 00 02:页属于哪个表空间,此处指表空间的ID为2。

之后是4字节的00 00 3f ca,这里的值为16330,代表此BLOB页的有效数据的字节数。00 00 00 05代表下一个BLOB页的page number。

我们看最后一个,第8个页:

7169 0001c000  fa 78 9b 27 00 00 00 07  00 00 00 00 00 00 00 00  |?x.'............|
7170 0001c010  00 00 00 00 00 07 3a b8  00 0a 00 00 00 00 00 00  |......:?........|
7171 0001c020  00 00 00 00 00 02 00 00  3d 9e ff ff ff ff 61 61  |........=.????aa|
7172 0001c030  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
7173 0001c040  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
...
...

最后一页的有效数据大小为0x00003d9e=15774,768+16330*3+15774 = 65532字节,符合初始插入数据的大小。
由于这是最后一个,所以指向下一个的指针为ff ff ff ff。

由此我们可以很清晰的看出数据页与BLOB页的连接关系(引用淘宝数据库月报上的一张图):
image

我们来再看一个比较有意思的例子。:

CREATE TABLE `testblob` (
  `blob1` blob NOT NULL,
  `blob2` blob NOT NULL,
  `blob3` blob NOT NULL,
  `blob4` blob NOT NULL,
  `blob5` blob NOT NULL,
  `blob6` blob NOT NULL,
  `blob7` blob NOT NULL,
  `blob8` blob NOT NULL,
  `blob9` blob NOT NULL,
  `blob10` blob NOT NULL,
  `blob11` blob NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

mysql> insert into testblob select repeat('a',1000),repeat('b',1000),repeat('c',1000),repeat('d',1000),repeat('e',1000),repeat('f',1000),repeat('g',1000),repeat('h',1000),repeat('i',1000),repeat('j',1000),repeat('k',1000);
ERROR 1030 (HY000): Got error 139 from storage engine

我们建立一张新表,有11个blob字段。然后向每个字段插入1000字节的数据,MySQL会提示ERROR 1030 (HY000): Got error 139 from storage engine,什么意思呢?

InnoDB是以B+树来组织数据的,假如每一行数据都占据一整个Page页,那么B+树将退化为单链表,所以InnoDB规定了一个Page必须包含两行数据。也就是一行数据存储在Page上的大小大概为8000字节。
而上面的例子,一行数据有11个1000字节的数据,Page层肯定放不下,所以在Page层留下768*11=8448字节,已经超过了8000字节,所以MySQL会提示ERROR 1030 (HY000): Got error 139 from storage engine。我们很轻松的定义一个字段,来存储11000个字节,但是却无法将他们分成11个字段来存储,有点意思!

那么如何解决上面的问题呢?

  • 将行格式转为接下来要说的Dynamic格式。此种格式只用20字节指向外部存储空间。
  • 将多个blob字段转为一个blob字段。多个字段可以用数组存储,然后json_encode打包进blob。

我们向表中插入一条有效记录:

mysql>  insert into testblob(blob1,blob2,blob3,blob4,blob5,blob6,blob7,blob8,blob9) select repeat('a',8000),repeat('b',8000),repeat('c',8000),repeat('d',8000),repeat('e',8000),repeat('f',8000),repeat('g',8000),repeat('h',8000),repeat('i',8000);
Query OK, 1 row affected (0.12 sec)
Records: 1  Duplicates: 0  Warnings: 0
[root@localhost mysql]# python py_innodb_page_info.py -v com/testblob.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000004, page type <Uncompressed BLOB Page>
page offset 00000005, page type <Uncompressed BLOB Page>
page offset 00000006, page type <Uncompressed BLOB Page>
page offset 00000007, page type <Uncompressed BLOB Page>
page offset 00000008, page type <Uncompressed BLOB Page>
page offset 00000009, page type <Uncompressed BLOB Page>
page offset 0000000a, page type <Uncompressed BLOB Page>
page offset 0000000b, page type <Uncompressed BLOB Page>
page offset 0000000c, page type <Uncompressed BLOB Page>
Total number of page: 13:
Insert Buffer Bitmap: 1
Uncompressed BLOB Page: 9
File Space Header: 1
B-tree Node: 1
File Segment inode: 1

我们可以看出这一行数据有9个外部存储页,而我们一共就插入了9列数据,是不是当每一列的数据在page页放不下,都单独申请一个外部存储页,而互相之前不共享外部存储页。我们看一下page页的结构就知道了:

 3130 0000c390  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61  |aaaaaaaaaaaaaaaa|
 3131 0000c3a0  61 61 61 61 00 00 00 05  00 00 00 04 00 00 00 26  |aaaa...........&|
...
...
 3180 0000c6b0  62 62 62 62 62 62 62 62  00 00 00 05 00 00 00 05  |bbbbbbbb........|
 3181 0000c6c0  00 00 00 26 00 00 00 00  00 00 1c 40 63 63 63 63  |...&.......@cccc|
...
...
 3229 0000c9c0  63 63 63 63 63 63 63 63  63 63 63 63 00 00 00 05  |cccccccccccc....|
 3230 0000c9d0  00 00 00 06 00 00 00 26  00 00 00 00 00 00 1c 40  |.......&.......@|
...
...

根据前面的分析,我们现在可以看出,外部存储页是不共享的,即使一个列的数据多出一个字节,这一个字节也是独占一个16KB空间的大小,这很浪费存储空间。(当然,这对现代计算机可能不是问题,呵呵)。

说了这么多,总结下Compact格式存储大数据的缺点:

  • 由于存在768字节的前缀在Page页,所以会存在能定义一个字段,存储11000字节,但是不能定义11个字段,每个字段存储1000字节的”bug”。
  • 外部存储页不共享,即使多余一个字节也是独享16KB的页面。

Dynamic行格式

接着我们首先看一下行格式为Dynamic是如何存储大数据的:

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.14    |
+-----------+
1 row in set (0.00 sec)

mysql> show table status like 'row'\G;
*************************** 1. row ***************************
           Name: row
         Engine: InnoDB
        Version: 10
     Row_format: Dynamic
           Rows: 0
 Avg_row_length: 0
    Data_length: 16384
Max_data_length: 0
   Index_length: 0
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2017-01-03 22:45:16
    Update_time: NULL
     Check_time: NULL
      Collation: latin1_swedish_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

创建和compact格式一样的表:

CREATE TABLE `row` (
  `content` varchar(65532) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1

insert into row(content) select repeat('a',65532);
Query OK, 1 row affected (0.03 sec)
Records: 1  Duplicates: 0  Warnings: 0

看下页分布:

[root@localhost mysql]# python py_innodb_page_info.py -v row.ibd 
page offset 00000000, page type 
page offset 00000001, page type 
page offset 00000002, page type 
page offset 00000003, page type , page level <0000>
page offset 00000004, page type 
page offset 00000005, page type 
page offset 00000006, page type 
page offset 00000007, page type 
page offset 00000008, page type 
Total number of page: 9:
Insert Buffer Bitmap: 1
Uncompressed BLOB Page: 5
File Space Header: 1
B-tree Node: 1
File Segment inode: 1

第4页是数据页,第5-9页是二进制页。我们直接看磁盘中第4页的数据:

3073 0000c000  dc 2d b0 f5 00 00 00 03  ff ff ff ff ff ff ff ff  |.-..............|
3074 0000c010  00 00 00 00 00 a3 4b 59  45 bf 00 00 00 00 00 00  |......KYE.......|
3075 0000c020  00 00 00 00 00 36 00 02  00 a6 80 03 00 00 00 00  |.....6..........|
3076 0000c030  00 7f 00 05 00 00 00 01  00 00 00 00 00 00 00 00  |................|
3077 0000c040  00 00 00 00 00 00 00 00  00 64 00 00 00 36 00 00  |.........d...6..|
3078 0000c050  00 02 00 f2 00 00 00 36  00 00 00 02 00 32 01 00  |.......6.....2..|
3079 0000c060  02 00 1c 69 6e 66 69 6d  75 6d 00 02 00 0b 00 00  |...infimum......|
3080 0000c070  73 75 70 72 65 6d 75 6d  14 c0 00 00 10 ff f1 00  |supremum........|
3081 0000c080  00 00 00 02 00 00 00 00  00 07 07 a7 00 00 01 1b  |................|
3082 0000c090  01 10 00 00 00 36 00 00  00 04 00 00 00 26 00 00  |.....6.......&..|
3083 0000c0a0  00 00 00 00 ff fc 00 00  00 00 00 00 00 00 00 00  |................|
3084 0000c0b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
3085 0000c0c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
3086 0000c0d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
3087 0000c0e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
...
...

和Compact格式有着明显的不同,当大数据在Page页存放不下时,Dynamic行格式不会留768字节在Page页,并且将全部大数据都放在外部存储页。具体的数据页和外部存储页的连接关系同Compact格式一样。

我们再看看Dynamic格式的外部存储页是不是每一个列独享外部存储空间,还是同Compact格式实验过程一样:

CREATE TABLE `testblob` (
  `blob1` blob NOT NULL,
  `blob2` blob NOT NULL,
  `blob3` blob NOT NULL,
  `blob4` blob NOT NULL,
  `blob5` blob NOT NULL,
  `blob6` blob NOT NULL,
  `blob7` blob NOT NULL,
  `blob8` blob NOT NULL,
  `blob9` blob NOT NULL,
  `blob10` blob NOT NULL,
  `blob11` blob NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

mysql>   insert into testblob(blob1,blob2,blob3,blob4,blob5,blob6,blob7,blob8,blob9,blob10,blob11) select repeat('a',8000),repeat('b',8000),repeat('c',8000),repeat('d',8000),repeat('e',8000),repeat('f',8000),repeat('g',8000),repeat('h',8000),repeat('i',8000),repeat('j',8000),repeat('k',8000);
Query OK, 1 row affected (0.10 sec)
Records: 1  Duplicates: 0  Warnings: 0

看一下外部存储页数据:

 4599 00011f60  61 61 61 61 61 61 61 61  61 61 61 61 61 61 00 00  |aaaaaaaaaaaaaa..|
 4600 00011f70  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

好的,可以不用向下看其他列的了,Dynamic的外部存储页也不是共享的。

但是MySQL为什么要这么设计呢?可能是为了实现简单吧,沿着链表通过有效数据大小就能读取blob的全部数据。假如多个字段的blob混在一起,可能设计更复杂,要更新每个字段的偏移量之类的,更新的话页数据管理也比较麻烦。我的个人猜测,呵呵。

总结下Dynamic格式存储大数据的特点:

  • 当数据页放不下时,MySQL会将大数据全部放在外部存储页,数据页只留指向外部存储页的指针。
  • 外部存储页不共享,即使多余一个字节也是独享16KB的页面。

将列放入外部存储页的标准

当一行中的数据不能在数据页中放下,需要申请外部存储页时,MySQL需要决定将哪一列的数据放到外部存储页,遵循的规则如下:

  • 长度固定的字段不会被放到外部存储页(int、char(N)等)
  • 长度小于20字节的字段不会被放到外部存储页。(假如放到外部存储页,不仅会单独占据16KB,还要额外的20字节指针,没有必要)
  • 对于Compact和REDUNDANT格式的行数据,长度小于768字节的字段不会被放到外部存储页。(这个原因很显然,本来就不够768字节的前缀,总不能生搬硬凑吧)。

当有多个大数据字段满足上面条件,需要被放到外部存储页时(比如一个7000字节,一个6000字节,需要选择一个字段放到外部存储页时),MySQL会优先选择大的字段放到外部存储页,因为这样可以最大限度的省下数据页的空间,使得更多的字段能够被放到数据页。

由于有较多的实验过程,所以显得比较乱,建议看到这篇文章人自己实践一遍,毕竟自己动手会思考更多的问题与细节,理解的也比较深刻,哈哈哈。

参考资料:http://dev.mysql.com/doc/refman/5.7/en/column-count-limit.html
http://mysqlserverteam.com/externally-stored-fields-in-innodb/
https://www.percona.com/blog/2010/02/09/blob-storage-in-innodb/
http://mysql.taobao.org/monthly/2016/02/01/

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

前端基础进阶:变量对象详解,教你如何高逼格地解释变量提升-演道网

在JavaScript中,我们肯定不可避免的需要声明变量和函数,可是JS解析器是如何找到这些变量的呢?我们还得对执行上下文有一个进一步的了解。

在上一篇文章中,我们已经知道,当调用一个函数时(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。

  • 创建阶段
    在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向
  • 代码执行阶段
    创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

执行上下文生命周期

从这里我们就可以看出详细了解执行上下文极为重要,因为其中涉及到了变量对象,作用域链,this等很多人没有怎么弄明白,但是却极为重要的概念,因此它关系到我们能不能真正理解JavaScript。在后面的文章中我们会一一详细总结,这里我们先重点了解变量对象。

变量对象(Variable Object)

变量对象的创建,依次经历了以下几个过程。

  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。
  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
  3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。

我知道有的人不喜欢看文字

根据这个规则,理解变量提升就变得十分简单了。在很多文章中虽然提到了变量提升,但是具体是怎么回事还真的很多人都说不出来,以后在面试中用变量对象的创建过程跟面试官解释变量提升,保证瞬间提升逼格。

在上面的规则中我们看出,function声明会比var声明优先级更高一点。为了帮助大家更好的理解变量对象,我们结合一些简单的例子来进行探讨。

// demo01
function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}

test();

在上例中,我们直接从test()的执行上下文开始理解。全局作用域中运行test()时,test()的执行上下文开始创建。为了便于理解,我们用如下的形式来表示

创建过程
testEC = {
    // 变量对象
    VO: {},
    scopeChain: {},
    this: {}
}

// 因为本文暂时不详细解释作用域链和this,所以把变量对象专门提出来说明

// VO 为 Variable Object的缩写,即变量对象
VO = {
    arguments: {...},
    foo:   // 表示foo的地址引用
    a: undefined
}

未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。

这样,如果再面试的时候被问到变量对象和活动对象有什么区别,就又可以自如的应答了,他们其实都是同一个对象,只是处于执行上下文的不同生命周期。

// 执行阶段
VO ->  AO   // Active Object
AO = {
    arguments: {...},
    foo: ,
    a: 1
}

因此,上面的例子demo1,执行顺序就变成了这样

function test() {
    function foo() {
        return 2;
    }
    var a;
    console.log(a);
    console.log(foo());
    a = 1;
}

test();

再来一个例子,巩固一下我们的理解。

// demo2
function test() {
    console.log(foo);
    console.log(bar);

    var foo = 'Hello';
    console.log(foo);
    var bar = function () {
        return 'world';
    }

    function foo() {
        return 'hello';
    }
}

test();
// 创建阶段
VO = {
    arguments: {...},
    foo: ,
    bar: undefined
}
// 这里有一个需要注意的地方,因为var声明的变量当遇到同名的属性时,会跳过而不会覆盖
// 执行阶段
VO -> AO
VO = {
    arguments: {...},
    foo: 'Hello',
    bar: 
}

需要结合上面的知识,仔细对比这个例子中变量对象从创建阶段到执行阶段的变化,如果你已经理解了,说明变量对象相关的东西都已经难不倒你了。

全局上下文的变量对象

以浏览器中为例,全局对象为window。
全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也同样适用,this也是指向window。

// 以浏览器中为例,全局对象为window
// 全局上下文
windowEC = {
    VO: window,
    scopeChain: {},
    this: window
}

除此之外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。其他所有的上下文环境,都能直接访问全局上下文的属性。

如有疑问,请在评论中提出。

如果你在本文学到了知识,点个赞可好 ^_^

目录
参考

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

代码编写工具中常用的10种字体_www.websbook.com_网页设计手册-演道网

字体并不是网页或设计的特例,在日常程序编写中,使用的各种编辑器里也会用到一些并非常用的字体,这让程序员在编写代码时感觉更良好,本文将推荐10种常用于程序开发编辑器的英文字体。

日复一日的编写代码,有没有感到审美疲劳?也许些许的改变就能让我们感到生活更美好。

换一种编程字体吧!体验一种新的代码感觉。

下面我眼中的十大编程字体:

10. Courier

基本上所有的系统都自带了此种字体,有时候它又以Courier New的名字出现。不幸的是很多终端和编辑器都默认使用此种字体,虽然这不会影响使用,但这会影响心情,它太枯燥了。以前看到这样一句评论:久不见牡丹会以仙人掌为美。这句评论形Courier字体非常合适。所以如果你还有其他选择的话,请勿使用此字体。更不幸的事情是最后你还是会继续使用它,那3秒只能强烈建议你调整一下字体大小并消除锯齿。

9. Andale Mono

稍微比Courier好一点的字体,有些时候它也被用作默认字体。3秒给它的定义是:一个软件不自带就不会有人去专门下载使用的一种字体。

8. Monaco

使用苹果Mac的人们对它不陌生,它是Mac的默认字体,3秒的经验是:使用它时,把字体设置为9号或者10号时会更好,这样看起来就相对不寒酸了。

7. Profont

Profont是一种类Monaco的位图字体,你能够在Mac, Windows和Linux上面使用,Mac平台的ProFontX就是它的修改版,当然两个字体并非出自同一作者之手。如果使用它,你把字体调小一点为好。而且如果你是Mac平台,它是Monaco的最佳替代者。如果你喜欢微小字体或者喜欢眼疲劳,你可以考虑一下它。

6. Monofur

Monofur是一种独特的等宽字体,各种大小看起来都非常不错,前提是你已经设置为消除锯齿。这种字体的外观比较独特,看着它容易让你想起上世纪八十年代Sun的Solaris(SunOS)上的OPENLOOK窗口管理器。如果你喜欢新鲜的东西,你可以试一下这种字体,再次提示一下:消除锯齿。

5. Proggy

Proggy是一种很干净的等宽字体,貌似受到很多Windows用户的青睐,在Mac上它同样工作正常。使用它一般把字体调小点,不要消除锯齿。

4. Droid Sans Mono

Droid 字体家族 适合手机等小屏幕的移动平台,比如Android。它在Apache许可证下授权。伟大的编程字体,在我列出的等宽字体中它是最突出的一个。

3. Deja Vu Sans Mono

Deja Vu 是我最喜欢的免费字体家族之一,基于Vera字体家族。Deja Vu已经能够支持更多的字符了,并保持了Vera的外观和感觉。适于任何大小,只要你消除锯齿。

2. Consolas

Consolas是商业字体,它是Luc(as) de Groot为微软ClearType字体家族设计的,与微软很多产品绑定在一起,所以幸运的是可能你的系统上已经有它的存在了。如果你在不消除锯齿的情况下使用它,那还不如使用Courier吧!

1. Inconsolata

Inconsolata是我最喜欢的等宽字体,而且是免费的。在发现它之后,3秒迅速改变了Deja Vu Sans Mono作为我默认编程字体的情况。从终端窗口到代码编辑器,我让它无处不在。它的风格非常独特。设计它的时候就已经把锯齿消除了,就算是非常小的时候也很清晰—真正的情况是它适合于任何大小。感谢Raph Levien创造了Inconsolata,并让它免费。

这些字体中大部分是免费的,可以自由下载。有些是是商业软件的一部分,比如Consolas。

你感觉怎么样?如果感觉相见恨晚,不妨去尝试一下。

原文链接:http://3seconds.cn/2010/01/20/10-programming-fonts.html

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

前端基础进阶:详细图解JavaScript执行上下文-演道网

先随便放张图

我们在JS学习初期或者面试的时候常常会遇到考核变量提升的思考题。比如先来一个简单一点的。

console.log(a);   // 这里会打印出什么?
var a = 20;

暂时先不管这个例子,我们先引入一个JavaScript中最基础,但同时也是最重要的一个概念执行上下文(Execution Context)

每次当控制器转到可执行代码的时候,就会进入一个执行上下文。执行上下文可以理解为当前代码的执行环境,它会形成一个作用域。JavaScript中的运行环境大概包括三种情况。

  • 全局环境:JavaScript代码运行起来会首先进入该环境
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码
  • eval

因此在一个JavaScript程序中,必定会产生多个执行上下文,在我的上一篇文章中也有提到,JavaScript引擎会以堆栈的方式来处理它们。栈底永远都是全局上下文,而栈顶就是当前正在执行的上下文。

当代码在执行过程中,遇到以上三种情况,都会生成一个执行上下文,放入栈中,而处于栈顶的上下文执行完毕之后,就会自动出栈。为了更加清晰的理解这个过程,根据下面的例子,结合图示给大家展示。

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();

我们用ECStock来表示处理执行上下文组的堆栈。我们很容易知道,第一步,首先是全局上下文入栈。

第一步:全局上下文入栈

全局上下文入栈之后,其中的可执行代码开始执行,直到遇到了changeColor(),这一句激活函数changeColor创建它自己的执行上下文,因此第二步就是changeColor的执行上下文入栈。

第二步:changeColor的执行上下文入栈

changeColor的上下文入栈之后,控制器开始执行其中的可执行代码,遇到swapColors()之后又激活了一个执行上下文。因此第三步是swapColors的执行上下文入栈。

第三步:swapColors的执行上下文入栈

在swapColors的可执行代码中,再没有遇到其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapColors的上下文从栈中弹出。

第四步:swapColors的执行上下文出栈

swapColors的执行上下文弹出之后,继续执行changeColor的可执行代码,也没有再遇到其他执行上下文,顺利执行完毕之后弹出。这样,ECStack中就只身下全局上下文了。

第五步:changeColor的执行上下文出栈

全局上下文在浏览器窗口关闭后出栈。

注意:函数中,遇到return能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。

整个过程

详细了解了这个过程之后,我们就可以对执行上下文总结一些结论了。

  • 单线程
  • 同步执行,只有栈顶的上下文处于执行中,其他上下文需要等待
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

为了巩固一下执行上下文的理解,我们再来绘制一个例子的演变过程,这是一个简单的闭包例子。

function f1(){
    var n=999;
    function f2(){
        alert(n); 
    }
    return f2;
}
var result=f1();
result(); // 999

因为f1中的函数f2在f1的可执行代码中,并没有被调用执行,因此执行f1时,f2不会创建新的上下文,而直到result执行时,才创建了一个新的。具体演变过程如下。

上例演变过程

下一篇文章继续总结执行上下文的创建过程与变量对象,求持续关注与点赞,谢谢大家。

本系列目录


正在为成为简书签约作者而努力。
如果你觉得我的文章写得还不错,跪求一赞!求关注求助攻,谢谢!^ ^

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

日志易服务初体验201612-演道网

友情提示: 本评测报告时间为2016年12月份

『适用性建议』

企业规模 场景 成本 推荐度
小企业(团队) nginx/apache日志实时监控,运维日志监控,接口性能监控 1G天*300元/月 ★★★☆☆
基于日志的日常统计分析 1G天*300元/月 ★★★☆☆
基于日志的大数据智能分析 1G天*300元/月 ★☆☆☆☆
大企业  nginx/apache日志实时监控,运维日志监控,接口性能监控 1G天*300元/月 ★★★★☆
基于日志的日常统计分析 1G天*300元/月 ★★★★☆
基于日志的大数据智能分析 1G天*300元/月 ★★★☆☆

附(具体价格表):

套餐 限额 存储时间 价格 备注
免费 0.5 GB/天 7天 0 每天最大上传日志量500MB ,可搜索分析7天内的日志
收费 1 GB/天 7天 300元/月 每天最大上传日志量1GB ,可搜索分析7天内的日志  (参考10万pv/天的应用日志量)
2 GB/天 7天 600元/月 每天最大上传日志量2GB ,可搜索分析7天内的日志
5 GB/天 7天 1500元/月 每天最大上传日志量5GB ,可搜索分析7天内的日志
10 GB/天 7天 3000元/月 每天最大上传日志量10GB ,可搜索分析7天内的日志
15 GB/天 7天 4500元/月 每天最大上传日志量15GB ,可搜索分析7天内的日志
20 GB/天 7天 6000元/月 每天最大上传日志量20GB ,可搜索分析7天内的日志
自选 以上为标准价格体系 ,如每天日志处理量超过20GB ,或对存储天数由更长需求 ,请致电 010-64785156 或联系 sales@yottabyte.cn

『学习成本★★★★☆』

        将nginx日志传到日志易服务后,日志易系统会自动识别。如果是应用日志则需要配置相应的字段拆分规则(规则设置)。

日志易引入日志搜索,统计分析语言 SPL,本次评测直接从项目需求出发,希望通过日志易服务完成如下任务
1. 获取访问首页的国外用户的访问状态
2. 希望做到http code 为500的请求比例到达一定程度触发短信报警,频度做到秒级 案例详情
3. 希望通过分析nginx 日志的请求用时,分析得到一天平均请求用时大于 1s的 uri 列表  案例详情

目标:经过学习 和尝试 SPL 搜索语言,能判断出以上需求是否可以完成,并掌握 日志易 核心产品规则为准。人员为 一名中高级工程师,耗时8小时左右, 初次接触学习成本约 1500 – 2000 元。

附:SPL语言注意事项

『成熟度★★★☆☆』

2015年初 – 2016年底,整体成熟度评级为 可用阶段

『稳定性☆☆☆☆☆』

暂无参考数据

『客服响应度★☆☆☆☆』

初次接触的客户,日志易会分配一个技术支持约15分钟左右,之后任何问题都不会再响应,估计是技术客服压力很大。如图:

 

笔者在测试过程中遇到:
        “产品修改密码功能失败”
       “SPL语法报错咨询若干”
       “短信报警设置”
       “日志是否为秒级传输”
“安装脚本出错需客户自行解决”


        等问题,客服和技术支持均不作响应,预计是响应成本过高,整体客服响应度低。从客户案例分析,应该对大企业客户会有好的客服响应,小企业需要自己承担这部分成本。

『最佳/差体验』

最佳:日志自动识别,并附赠网站pv,uv访客统计;一站式搜索统计功能灵活性强大

最差:SPL语言无语法提示,50%比例出错影响心情

最坑:日志只保留7天,务必自己做好日志备份

加我的个人微信 35816146在线支持

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn

分布式架构实战–FastDFS分布式文件系统集群安装-演道网

样例项目实战参考:http://www.roncoo.com/course/view/85d6008fe77c4199b0cdd2885eaeee53   46-48节


文档修订记录

face/tTKsrKXG6QAYwd6P6pZahWRisX4sRrG5.jpg


跟踪服务器:

Tracker Server1 IP:  10.10.16.201 FASTDFS-TRACKER-01

Tracker Server2 IP:  10.10.16.202 FASTDFS-TRACKER-02

 

存储服务器:

Storage Server1 IP:  10.10.16.203 FASTDFS-STORAGE-01

Storage Server2 IP:  10.10.16.204 FASTDFS-STORAGE-02

 

 

集群中各操作系统环境设置:

(1) fastdfs安装目录:/usr/local/fastdfs

[root@FASTDFS-TRACKER-01 local]# mkdir /usr/local/fastdfs

[root@FASTDFS-TRACKER-02 local]# mkdir /usr/local/fastdfs

[root@FASTDFS-STORAGE-01 local]# mkdir /usr/local/fastdfs

[root@FASTDFS-STORAGE-02 local]# mkdir /usr/local/fastdfs

 

(2)创建fastdfs用户组及fastdfs用户(四台机同时配置):

[root@xxx]# groupadd fastdfs

[root@xxx]# useradd -g fastdfs fastdfs

[root@xxx]# passwd fastdfs

## 设置fastdfs用户密码为gzzyzz.com (上生产前要改)

 

(3) 创建数据存储目录:

#创建tracker目录保存运行日志(Tracker服务器)

[fastdfs@FASTDFS-TRACKER-01 ~]$ mkdir -p /home/fastdfs/tracker

[fastdfs@FASTDFS-TRACKER-02 ~]$ mkdir -p /home/fastdfs/tracker

#创建Storage目录保存运行日志及其data数据(Storage服务器)

[fastdfs@FASTDFS-STORAGE-01 ~] $ mkdir -p /home/fastdfs/storage

[fastdfs@FASTDFS-STORAGE-02 ~] $ mkdir -p /home/fastdfs/storage

 

 

注:因为要源码编译安装软件,因而各系统要事先安装好:make cmake gcc gcc-c++

[root@FASTDFS-TRACKER-01 yum.repos.d]# yum install make cmake gcc gcc-c++

 

1、 首先安装libevent(集群中所有服务器都执相同的安装):

fastdfs在编译源程序时fastdfs内部调用libevent的处理机制,需要用到libevent一些依赖文件,否则编译fastdfs会出错。

# 卸载系统自带libevent,自带版本过低,要不然安装fastdfs会出错

[root@FASTDFS-TRACKER-01 fastdfs]# rpm -qa|grep libevent

libevent-1.4.13-4.el6.x86_64

(由上可以系统自带了libevent-1.4.13-4.el6.x86_64

卸载:

[root@FASTDFS-TRACKER-01 fastdfs]# rpm -e –nodeps libevent-1.4.13-4.el6.x86_64

 

 

#下载安装libevent

进入/usr/local/src目录

# cd /usr/local/src/

[root@FASTDFS-TRACKER-01 src]#

wget http://cloud.github.com/downloads/libevent/libevent/libevent-2.0.19-stable.tar.gz

 

[root@FASTDFS-TRACKER-01 src]# tar -zxvf libevent-2.0.19-stable.tar.gz

[root@FASTDFS-TRACKER-01 src]# cd libevent-2.0.19-stable

#make clean;

[root@FASTDFS-TRACKER-01 libevent-2.0.19-stable]$ ./configure –prefix=/usr/local/libevent

[root@FASTDFS-TRACKER-01 libevent-2.0.19-stable]# make

[root@FASTDFS-TRACKER-01 libevent-2.0.19-stable]# make && make install

#为libevent创建软链接到/lib库下,64位系统对应/lib64

[root@FASTDFS-TRACKER-01 libevent-2.0.19-stable]#

ln -s /usr/local/libevent/lib/libevent-2.0.so.5 /usr/lib/libevent-2.0.so.5

[root@FASTDFS-TRACKER-01 libevent-2.0.19-stable]#

ln -s /usr/local/libevent/lib/libevent-2.0.so.5 /usr/lib64/libevent-2.0.so.5

 

   

2、 安装FastDFS(集群中所有服务器都执相同的安装):

进入/usr/local/src目录

[root@FASTDFS-TRACKER-01 src]#

wget https://fastdfs.googlecode.com/files/FastDFS_v4.06.tar.gz

[root@FASTDFS-TRACKER-01 src]# tar -zxvf FastDFS_v4.06.tar.gz

[root@FASTDFS-TRACKER-01 src]# cd FastDFS

 

#由于定义/usr/local/fastdfs为fastdfs安装目录,所以需要修改make.sh

[gw@register2 FastDFS]$ vi make.sh

# /etc/fdfs 全部替换为 /usr/local/fastdfs/conf

# TARGET_PREFIX=/usr/local 修改为 /usr/local/fastdfs

# TARGET_CONF_PATH=/etc/fdfs 修改为 /usr/local/fastdfs/conf

附件:

 

#安装

[root@FASTDFS-TRACKER-01 FastDFS]$

./make.sh C_INCLUDE_PATH=/usr/local/libevent/include LIBRARY_PATH=/usr/local/libevent/lib

# 切换到超级管理员

[root@FASTDFS-TRACKER-01 FastDFS]# ./make.sh install

 

注意:如果安装时提示找不到命令,请查看是不是没有装perl安装环境

wget  http://www.cpan.org/src/5.0/perl-5.18.2.tar.gz

tar -zxvf  perl-5.18.2.tar.gz

cd perl-5.18.2

./Configure

make

make install

 

3、 配置Tracker Server(10.10.16.201,10.10.16.202):

进入/usr/local/fastdfs/conf

#修改tracker.conf配置

[root@FASTDFS-TRACKER-01 conf]# vi /usr/local/fastdfs/conf/tracker.conf

绑定IP:

bind_addr=10.10.16.201 和 bind_addr=10.10.16.202

 

#设置日志目录

由base_path=/home/yuqing/fastdfs 改为 base_path=/home/fastdfs/tracker

store_group=group1

run_by_group= 改为 run_by_group=fastdfs

run_by_user= 改为 run_by_user=fastdfs

trunk_create_file_space_threshold = 20G

#开启自trunk_create_file_space_threshold定义server ID取代ip形式,方便内部网络服务器更换ip#**此方式要重点理解,4.0以后新特性

use_storage_id = true #使用server ID作为storage server标识

storage_ids_filename = storage_ids.conf  #

id_type_in_filename = id #文件名反解析中包含server ID,以前是ip

 

复制storage_ids.conf文件

[root@FASTDFS-TRACKER-01 conf]#

cp /usr/local/src/FastDFS/conf/storage_ids.conf /usr/local/fastdfs/conf/

#编辑storage服务器ID与IP地址的对应关系

[root@register1 conf]# vi /usr/local/fastdfs/conf/storage_ids.conf

#

100001  group1        10.10.16.203

100002  group1        10.10.16.204

 

防火墙打开8080和22122端口:

# vi /etc/sysconfig/iptables

增加:

-A INPUT -m state –state NEW -m tcp -p tcp –dport 8080 -j ACCEPT

-A INPUT -m state –state NEW -m tcp -p tcp –dport 22122 -j ACCEPT

重启防火墙:

# /etc/init.d/iptables restart

 

自定义tracker服务启动、关闭、重启脚本:

# su fastdfs

切换回fastdfs用户后

$ cd /home/fastdfs/tracker/

(1)启动脚本:

[fastdfs@FASTDFS-TRACKER-01 tracker]$ vi start_tracker.sh

/usr/local/fastdfs/bin/fdfs_trackerd /usr/local/fastdfs/conf/tracker.conf

 

(2)关闭脚本(使用FastDFS自带的stop.sh脚本):

[fastdfs@FASTDFS-TRACKER-01 tracker]$ vi stop_tracker.sh

/usr/local/fastdfs/bin/stop.sh /usr/local/fastdfs/bin/fdfs_trackerd /usr/local/fastdfs/conf/tracker.conf

 

(3)重启脚本(使用FastDFS自带的restart.sh脚本):

[fastdfs@FASTDFS-TRACKER-01 tracker]$ vi restart_tracker.sh

/usr/local/fastdfs/bin/restart.sh /usr/local/fastdfs/bin/fdfs_trackerd /usr/local/fastdfs/conf/tracker.conf

 

注意:千万不要使用kill -9参数强杀进程,否则可能会导致binlog数据丢失的问题

 

给自定义脚本赋予可执行权限:

[fastdfs@FASTDFS-TRACKER-01 tracker]$ chmod +x *_tracker.sh

 

启动tracker服务:

[fastdfs@FASTDFS-TRACKER-01 tracker]$ ./start_tracker.sh

查看是否启用成功:

[fastdfs@FASTDFS-TRACKER-01 tracker]$ ps -ef | grep fastdfs

root     27253 26511  0 10:03 pts/0    00:00:00 su fastdfs

fastdfs  27254 27253  0 10:03 pts/0    00:00:00 bash

fastdfs  27580     1  0 10:25 ?        00:00:00 /usr/local/fastdfs/bin/fdfs_trackerd /usr/local/fastdfs/conf/tracker.conf

fastdfs  27588 27254  5 10:25 pts/0    00:00:00 ps -ef

fastdfs  27589 27254  0 10:25 pts/0    00:00:00 grep fastdfs

[fastdfs@FASTDFS-TRACKER-01 tracker]$

(首次正常启动后会在/home/fastdfs/tracker目录下会产生data目录和logs目录)

[2014-01-12 16:33:54] INFO – local_host_ip_count: 2,  127.0.0.1  10.10.16.201

[2014-01-12 16:34:11] INFO – file: tracker_service.c, line: 920, the tracker leader is 10.10.16.202:22122

停用tracker服务:

[fastdfs@FASTDFS-TRACKER-01 tracker]$ ./stop_tracker.sh

 

重启tracker服务:

[fastdfs@FASTDFS-TRACKER-01 tracker]$ ./restart_tracker.sh

 

 

 

4、 配置Storage Server(10.10.16.203、10.10.16.204,两台机执行相同的配置操作):

# cd /usr/local/fastdfs/conf

# vi storage.conf

绑定IP分别为:

bind_addr=10.10.16.203 和 bind_addr=10.10.16.204

 

base_path=/home/yuqing/fastdfs 改为 base_path=/home/fastdfs/storage

store_path0=/home/yuqing/fastdfs 改为 store_path0=/home/fastdfs/storage

tracker_server=192.168.209.121:22122 改为

tracker_server=10.10.16.201:22122

tracker_server=10.10.16.202:22122

(这里我们配了两个跟踪器服务)

run_by_group= 改为 run_by_group=fastdfs

run_by_user= 改为 run_by_user=fastdfs

# 与Nginx端口相同

http.server_port=80

 

防火墙打开23000和8888端口:

# vi /etc/sysconfig/iptables

增加:

-A INPUT -m state –state NEW -m tcp -p tcp –dport 23000 -j ACCEPT

-A INPUT -m state –state NEW -m tcp -p tcp –dport 8888 -j ACCEPT

重启防火墙:

# /etc/init.d/iptables restart

 

 

自定义storage服务启动、关闭、重启脚本:

# su fastdfs

切换回fastdfs用户后

$ cd /home/fastdfs/storage/

(1)启动脚本:

[fastdfs@FASTDFS-STORAGE-01 storage]$ vi start_storage.sh

/usr/local/fastdfs/bin/fdfs_storaged /usr/local/fastdfs/conf/storage.conf

 

(2)关闭脚本(使用FastDFS自带的stop.sh脚本):

[fastdfs@FASTDFS-STORAGE-01 storage]$ vi stop_tracker.sh

/usr/local/fastdfs/bin/stop.sh /usr/local/fastdfs/bin/fdfs_storaged /usr/local/fastdfs/conf/storage.conf

 

(3)重启脚本(使用FastDFS自带的restart.sh脚本):

[fastdfs@FASTDFS-STORAGE-01 storage]$ vi restart_storage.sh

/usr/local/fastdfs/bin/restart.sh /usr/local/fastdfs/bin/fdfs_storaged /usr/local/fastdfs/conf/storage.conf

 

注意:千万不要使用kill -9参数强杀进程,否则可能会导致binlog数据丢失的问题

 

给自定义脚本赋予可执行权限:

[fastdfs@FASTDFS-STORAGE-01 storage]$ chmod +x *_storage.sh

 

 

启动Storage Server:

[fastdfs@FASTDFS-STORAGE-01 storage]$ ./start_storage.sh

data path: /home/fastdfs/storage/data, mkdir sub dir…

mkdir data path: 00 …

mkdir data path: 01 …

mkdir data path: 02 …

mkdir data path: 03 …

(首次成功启动会初始化数据存储目录)

 

重启storage服务:

[fastdfs@FASTDFS-STORAGE-01 storage]$ ./restart_storage.sh

 

停止storage服务:

[fastdfs@FASTDFS-STORAGE-01 storage]$ ./stop_storage.sh

 


Storage Server10.10.16.203和10.10.16.204)安装Nginx:

#安装Nginx使用fastdfs用户

#创建nginx日志目录

# mkdir -p /home/fastdfs/nginx/logs

# chmod a+w /home/fastdfs/nginx/logs

# chown -R fastdfs:fastdfs /home/fastdfs/nginx/logs

 

 

安装nginx1.4.4:

# cd /usr/local/src

#下载 nginx

[root@FASTDFS-STORAGE-01 src]#

wget http://nginx.org/download/nginx-1.4.4.tar.gz

解压:

# tar -zxvf nginx-1.4.4.tar.gz

# cd nginx-1.4.4

 

# ./configure –user=fastdfs –group=fastdfs –prefix=/usr/local/nginx –with-http_stub_status_module

 

如果产生如下错误提示:

checking for PCRE library … not found

checking for PCRE library in /usr/local/ … not found

checking for PCRE library in /usr/include/pcre/ … not found

checking for PCRE library in /usr/pkg/ … not found

checking for PCRE library in /opt/local/ … not found

 

./configure: error: the HTTP rewrite module requires the PCRE library.

You can either disable the module by using –without-http_rewrite_module

option, or install the PCRE library into the system, or build the PCRE library

statically from the source with nginx by using –with-pcre=

 

(大概意思是:HTTP重定向模块的安装需要PCRE库,网上搜了一下,是需要安装pcre-developenssl-devel

# yum install pcre-devel openssl openssl-devel

(RHEL6.3 x64 桌面版中已自带以上包)

 

再重新执行:

# ./configure –user=fastdfs –group=fastdfs –prefix=/usr/local/nginx –with-http_stub_status_module

 

#–with-http_stub_status_module 用来监控nginx的当前状态

Configuration summary

  + using system PCRE library

  + OpenSSL library is not used

  + md5: using system crypto library

  + sha1: using system crypto library

  + using system zlib library

 

  nginx path prefix: “/usr/local/nginx”

  nginx binary file: “/usr/local/nginx/sbin/nginx”

  nginx configuration prefix: “/usr/local/nginx/conf”

  nginx configuration file: “/usr/local/nginx/conf/nginx.conf”

  nginx pid file: “/usr/local/nginx/logs/nginx.pid”

  nginx error log file: “/usr/local/nginx/logs/error.log”

  nginx http access log file: “/usr/local/nginx/logs/access.log”

  nginx http client request body temporary files: “client_body_temp”

  nginx http proxy temporary files: “proxy_temp”

  nginx http fastcgi temporary files: “fastcgi_temp”

  nginx http uwsgi temporary files: “uwsgi_temp”

  nginx http scgi temporary files: “scgi_temp”

 

执行安装:# make && make install

安装fastdfs-nginx-module插件:

# cd /usr/local/src

# wget http://fastdfs.googlecode.com/files/fastdfs-nginx-module_v1.15.tar.gz

# tar -zxvf fastdfs-nginx-module_v1.15.tar.gz

 

#修改插件配置文件

# vi /usr/local/src/fastdfs-nginx-module/src/config

ngx_addon_name=ngx_http_fastdfs_module

HTTP_MODULES=”$HTTP_MODULES ngx_http_fastdfs_module”

NGX_ADDON_SRCS=”$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_fastdfs_module.c”

CORE_INCS=”$CORE_INCS /usr/local/fastdfs/include/fastdfs /usr/local/fastdfs/include/fastcommon/

CORE_LIBS=”$CORE_LIBS -L/usr/local/fastdfs/lib -lfastcommon -lfdfsclient

CFLAGS=”$CFLAGS -D_FILE_OFFSET_BITS=64 -DFDFS_OUTPUT_CHUNK_SIZE=’256*1024′ -DFDFS_MOD_CONF_FILENAME='”/usr/local/fastdfs/conf/mod_fastdfs.conf”‘

 

#复制mod_fastdfs.conf/usr/local/fastdfs/conf/目录下

# cp /usr/local/src/fastdfs-nginx-module/src/mod_fastdfs.conf /usr/local/fastdfs/conf/

 

上面编译时使用的动态链接库:

#启动nginx报错

#./nginx: error while loading shared libraries: libfastcommon.so: cannot open shared object file: No such file or directory

#解决办法 —> /usr/local/fastdfs/lib 加入系统文件/etc/ld.so.conf

# vi /etc/ld.so.conf

include ld.so.conf.d/*.conf

/usr/local/fastdfs/lib

 

#更新库文件缓存ld.so.cache

# /sbin/ldconfig -v

 

编译fastdfs-nginx-module模块:

# 重新编译安装nginx

# cd /usr/local/src/nginx-1.4.4

# ./configure –add-module=/usr/local/src/fastdfs-nginx-module/src

 

# make && make install

 

修改mod_fastdfs.conf配置:

# vi /usr/local/fastdfs/conf/mod_fastdfs.conf

 

 

# connect timeout in seconds

# default value is 30s

connect_timeout=30

 

# the base path to store log files

base_path=/tmp

 

# if load FastDFS parameters from tracker server

# since V1.12

# default value is false

load_fdfs_parameters_from_tracker=true

 

# if use storage ID instead of IP address

# same as tracker.conf

# valid only when load_fdfs_parameters_from_tracker is false

# default value is false

# since V1.13

use_storage_id = true

 

# FastDFS tracker_server can ocur more than once, and tracker_server format is

#  “host:port”, host can be hostname or ip address

# valid only when load_fdfs_parameters_from_tracker is true

tracker_server=10.10.16.201:22122

tracker_server=10.10.16.202:22122

 

# the port of the local storage server

# the default value is 23000

storage_server_port=23000

 

# the group name of the local storage server

group_name=group1

 

# if the url / uri including the group name

# set to false when uri like /M00/00/00/xxx

# set to true when uri like ${group_name}/M00/00/00/xxx, such as group1/M00/xxx

# default value is false

url_have_group_name = true

 

# path(disk or mount point) count, default value is 1

# must same as storage.conf

store_path_count=1

 

# store_path#, based 0, if store_path0 not exists, it’s value is base_path

# the paths must be exist

# must same as storage.conf

store_path0=/home/fastdfs/storage

 

# set the log filename, such as /usr/local/apache2/logs/mod_fastdfs.log

# empty for output to stderr (apache and nginx error_log file)

log_filename=/home/fastdfs/nginx/logs/mod_fastdfs.log

 

 

 注意, 下载时如发现老报404. nginx.conf第一行 usr nobody 修改为 user root 重新启动后解决.

 

storage 的存储目录做一个软连接

# ln -s /home/fastdfs/storage/data /home/fastdfs/storage/data/M00

 

访问路径带group名(storage对应有多个group的情况),如/group1/M00/00/00/xxx:
      location ~ /group([0-9])/M00 {
            ngx_fastdfs_module;
        }

 

 

nginx配置简洁版本:

# vi /usr/local/nginx/conf/nginx.conf

user root root;

worker_processes  2;

error_log  /home/fastdfs/nginx/logs/error.log  notice;

pid        /home/fastdfs/nginx/logs/nginx.pid;

worker_rlimit_nofile 5120;

 

events {

    use epoll;

    worker_connections  5120;

}

 

http {

    include       mime.types;

    default_type  application/octet-stream;

    sendfile        on;

    tcp_nopush     on;

    keepalive_timeout  65;

    tcp_nodelay on;

    server {

        listen       80;

        server_name  localhost;

        #charset koi8-r;

        location ~/group([0-9])/M00 {

            #alias /home/fastdfs/storage/data;

            ngx_fastdfs_module;

        }

    }

}

 

 

#启动nginx

# /usr/local/nginx/sbin/nginx

 

#重启nginx

# /usr/local/nginx/sbin/nginx -s reload

 

 

用Java客户端上传一个文件:

浏览


face/ASNGBn74BGS5aypebfs2y4x7ydRzHRST.png

 

face/yAeQkkjTrfs2rDjP6WTc6rFi5pynY26F.png

 

文件的访问通过硬件做均衡负载

VIP:10.10.16.209 (做负载均衡的虚拟机网关要设为10.10.16.8)

face/Z4HRRkipn3GsaXwTH4T2aP2i8FHdZiDy.png

 

5、 a

6、 a

7、 a

8、 a

9、 a

关注微信:

face/JQ8mbPNjdrZyCKGxNhNfcQnBcnpYs6Fd.png

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn