DNS预解析dns-prefetch提升页面载入速度优化前端性能

当浏览器请求一个URL的时候,通过firebug我们可以发现大概有以下几个过程:阻挡、域名解析、建立连接、发送请求、等待响应、接收数据。后面四个跟用户的网络情况和你的服务器处理速度有关,本文重点说说前两个。

1、阻挡:解决方案——提高浏览器并发连接数

阻挡:不同的浏览器对单个域名的最大并发连接数有一定的限制,HTTP/1.0和HTTP/1.1也不相同。比如HTTP/1.1协议下,IE6的并发连接数限制是2个;而在HTTP/1.0下,IE6的并发连接数可以达到4个。在其它浏览器也有类似的限制,一般是4~8个。这个时候,如果浏览器同时对某一域名发起多个请求,超过了限制就会出现等待,也就是阻挡。

那么为了解决阻挡这一问题,我们可以对某些URL的域名分散处理,比如我们的图片域名,一般用类似img.guoweiwei.com的域名,当一个页面包含20多张图片的时候,那至少有10几个请求会被阻挡,而如果我们分散到img0.guoweiwei.com/img1.guoweiwei.com/img2.guoweiwei.com/…等不同域名的时候,至少这20个图片请求会并发进行,网站打开速度会明显提升很多。类似的,可以对一些css/js的域名同样处理。

2、域名解析:解决方案——DNS预解析

域名解析:从域名查询IP的过程,这个过程一般都很快的,但也会引起延迟。一般浏览器会适当的对解析结果缓存,并对页面中出现的新域名进行预解析,但并不是所有的浏览器都会这么做,为了帮助其它浏览器对某些域名进行预解析,你可以在页面的html标签中添加dns-prefetch告诉浏览器对指定域名预解析,如下:

<link rel="dns-prefetch" href="//domain.com">

如果细心一点,你会在淘宝的网站发现这两个现象,淘宝有很多类似img0.tbcdn.cn这样的域名。

再另外提一点优化,

3、cookie隔离

那就是为什么用img0.tbcdn.cn这个域名,而不是img0.taobao.com呢?这个得从cookie说起,淘宝的cookie已经非常大了,据说曾接近1K,如果用后面的域名,那每次请求图片都会带上长长的cookie,后果可想而知,不仅使得网络请求变慢,而且还浪费了带宽,而淘宝图片服务器并不需要这些cookie。这就是所说的cookie污染,为了解决这一问题,单独的域名是很有必要的。

下面重点介绍下:

4、DNS预解析解决方案

DNS预解析是浏览器试图在用户访问链接之前解析域名,这是计算机的正常DNS解析机制。

域名解析后,如果用户确实访问该域名,那么DNS解析时间将不会有延迟。

最明显的例子,DNS预解析在某个页面中包含非常多的域名非常有效,如搜索结果页。遇到网页中的超链接,DNS prefetching从中提取域名并将其解析为IP地址,这些工作在用户浏览网页时,使用最少的CPU和网络在后台进行解析。当用户点击这些已经预解析的域名,可以平均减少200毫秒耗时(假设用户最近还未访问过该域名),更重要的是用户不会遇到DNS解析最坏的情况(往往超过1秒)。

DNS Prefetch,即DNS预获取,是前端优化的一部分。一般来说,在前端优化中与 DNS 有关的有两点:

一个是减少DNS的请求次数,

  另一个就是进行DNS预获取 。

DNS 作为互联网的基础协议,其解析的速度似乎很容易被网站优化人员忽视。现在大多数新浏览器已经针对DNS解析进行了优化,典型的一次DNS解析需要耗费 20-120 毫秒,减少DNS解析时间和次数是个很好的优化方式。

DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能 减少用户的等待时间,提升用户体验 。

浏览器对网站第一次的域名DNS解析查找流程依次为:浏览器缓存——系统缓存——路由器缓存——ISP DNS缓存——递归搜索

默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取,那么就要使用显示的 DNS Prefetch 了。

目前大多数浏览器已经支持此属性,支持版本如下:

  • – Safari: 5+
  • – Chrome: All
  • – Firefox: 3.5+
  • – Opera: Unknown
  • – IE: 9+ (called “Pre-resolution” on blogs.msdn.com)

其中 Chrome 和 Firefox 3.5+ 内置了 DNS Prefetching 技术并对DNS预解析做了相应优化设置。所以,即使不设置此属性,Chrome 和 Firefox 3.5+ 也能自动在后台进行预解析 。

目前很多大型站点也应用了这一优化,例如:

  淘宝:

  支付宝:

  网易:

  DNS Prefetch 应该尽量的放在网页的前面,推荐放在 <meta charset="UTF-8"> 后面。具体使用方法如下:

<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//www.zhix.net">
<link rel="dns-prefetch" href="//api.share.zhix.net">
<link rel="dns-prefetch" href="//bdimg.share.zhix.net">

预解析的实现:

1、用meta信息来告知浏览器, 当前页面要做DNS预解析:<meta http-equiv=”x-dns-prefetch-control” content=”on” />

  2、在页面header中使用link标签来强制对DNS预解析: <link rel=”dns-prefetch” href=”http://bdimg.share.baidu.com” />

注:dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数。

需要注意的是,虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出 禁用DNS 预读取能节省每月100亿的DNS查询 。

如果需要禁止隐式的 DNS Prefetch,可以使用以下的标签:

<meta http-equiv="x-dns-prefetch-control" content="off">

ZooKeeper是什么?

本文转自:http://developer.51cto.com/art/201809/583184.htm

相信大家对 ZooKeeper 应该不算陌生,但是你真的了解 ZooKeeper 是什么吗?如果别人/面试官让你讲讲 ZooKeeper 是什么,你能回答到哪个地步呢?

我本人曾经使用过 ZooKeeper 作为 Dubbo 的注册中心,另外在搭建 Solr 集群的时候,我使用到了 ZooKeeper 作为 Solr 集群的管理工具。

前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?

想了半天,脑海中只是简单的能浮现出几句话:

  • Zookeeper 可以被用作注册中心。
  • Zookeeper 是 Hadoop 生态系统的一员。
  • 构建 Zookeeper 集群的时候,使用的服务器最好是奇数台。

可见,我对于 Zookeeper 的理解仅仅是停留在了表面。所以,通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。

如果没有学过 ZooKeeper,那么本文将会是你进入 ZooKeeper 大门的垫脚砖;如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。

最后,本文只涉及 ZooKeeper 的一些概念,并不涉及 ZooKeeper 的使用以及 ZooKeeper 集群的搭建。

网上有介绍 ZooKeeper 的使用以及搭建 ZooKeeper 集群的文章,大家有需要可以自行查阅。

什么是 ZooKeeper

ZooKeeper 的由来

下面这段内容摘自《从 Paxos 到 ZooKeeper 》第四章第一节的某段内容,推荐大家阅读一下:

Zookeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。

所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。

关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。

时任研究院的首席科学家 Raghu Ramakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”

此话一出,大家纷纷表示就叫动物园管理员吧,因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了。

而 Zookeeper 正好要用来进行分布式环境的协调,于是,Zookeeper 的名字也就由此诞生了。

ZooKeeper 概览

ZooKeeper 是一个开源的分布式协调服务,ZooKeeper 框架最初是在“Yahoo!”上构建的,用于以简单而稳健的方式访问他们的应用程序。

后来,Apache ZooKeeper 成为 Hadoop,HBase 和其他分布式框架使用的有组织服务的标准。

例如,Apache HBase 使用 ZooKeeper 跟踪分布式数据的状态。

ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

原语: 操作系统或计算机网络用语范畴。它是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。

ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

ZooKeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。

服务生产者将自己提供的服务注册到 ZooKeeper 中心,服务的消费者在进行服务调用的时候先到 ZooKeeper 中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。

如下图所示,在 Dubbo 架构中 ZooKeeper 就担任了注册中心这一角色。

Dubbo 架构图

结合个人使用讲一下 ZooKeeper

在我自己做过的项目中,主要使用到了 ZooKeeper 作为 Dubbo 的注册中心(Dubbo 官方推荐使用 ZooKeeper 注册中心)。

另外在搭建 Solr 集群的时候,我使用  ZooKeeper 作为 Solr 集群的管理工具。

这时,ZooKeeper 主要提供下面几个功能:

  • 集群管理:容错、负载均衡。
  • 配置文件的集中管理。
  • 集群的入口。

我个人觉得在使用 ZooKeeper 的时候,最好是使用集群版的 ZooKeeper 而不是单机版的。

官网给出的架构图就描述的是一个集群版的 ZooKeeper 。通常 3 台服务器就可以构成一个  ZooKeeper 集群了。

为什么最好使用奇数台服务器构成 ZooKeeper 集群?

我们知道在 ZooKeeper 中 Leader 选举算法采用了 Zab 协议。Zab 核心思想是当多数 Server 写成功,则任务数据写成功:

  • 如果有 3 个 Server,则最多允许 1 个 Server 挂掉。
  • 如果有 4 个 Server,则同样最多允许 1 个 Server 挂掉。

既然 3 个或者 4 个 Server,同样最多允许 1 个 Server 挂掉,那么它们的可靠性是一样的。

所以选择奇数个 ZooKeeper Server 即可,这里选择 3 个 Server。

关于 ZooKeeper  的一些重要概念

重要概念总结

关于 ZooKeeper  的一些重要概念:

  • ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
  • 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。
  • ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 Znode 中存储的数据量较小的进一步原因)。
  • ZooKeeper 是高性能的。在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
  • ZooKeeper 有临时节点的概念。当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。

而当会话终结时,瞬时节点被删除。持久节点是指一旦这个 ZNode 被创建了,除非主动进行 ZNode 的移除操作,否则这个 ZNode 将一直保存在 Zookeeper 上。

  • ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务。

下面关于会话(Session)、 Znode、版本、Watcher、ACL 概念的总结都在《从 Paxos 到 ZooKeeper 》第四章第一节以及第七章第八节有提到,感兴趣的可以看看!

会话(Session)

Session 指的是 ZooKeeper  服务器与客户端会话。在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接。

客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。

通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 Zookeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watch 事件通知。

Session 的 sessionTimeout 值用来设置一个客户端会话的超时时间。

当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在 sessionTimeout 规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 sessionID。

由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的。

因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。

Znode

在谈到分布式的时候,我们通常说的“节点”是指组成集群的每一台机器。

然而,在 ZooKeeper 中,“节点”分为两类:

  • 第一类同样是指构成集群的机器,我们称之为机器节点。
  • 第二类则是指数据模型中的数据单元,我们称之为数据节点一ZNode。

ZooKeeper 将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个 Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。

在 Zookeeper 中,Node 可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个 ZNode 被创建了,除非主动进行 ZNode 的移除操作,否则这个 ZNode 将一直保存在 ZooKeeper 上。

而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。

另外,ZooKeeper 还允许用户为每个节点添加一个特殊的属性:SEQUENTIAL。

一旦节点被标记上这个属性,那么在这个节点被创建的时候,ZooKeeper 会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。

版本

在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个 ZNode,Zookeeper 都会为其维护一个叫作 Stat 的数据结构。

Stat 中记录了这个 ZNode 的三个数据版本,分别是:

  • version(当前 ZNode 的版本)
  • cversion(当前 ZNode 子节点的版本)
  • aversion(当前 ZNode 的 ACL 版本)

Watcher

Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。

ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。

ACL

ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于  UNIX 文件系统的权限控制。

ZooKeeper 定义了 5 种权限,如下图:

其中尤其需要注意的是,CREATE 和 DELETE 这两种权限都是针对子节点的权限控制。

ZooKeeper 特点

ZooKeeper 有哪些特点呢?具体如下:

  • 顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
  • 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
  • 单一系统映像:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
  • 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。

ZooKeeper 设计目标

简单的数据模型

ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调,这与标准文件系统类似。

名称空间由 ZooKeeper 中的数据寄存器组成,称为 Znode,这些类似于文件和目录。

与为存储设计的典型文件系统不同,ZooKeeper 数据保存在内存中,这意味着 ZooKeeper 可以实现高吞吐量和低延迟。

可构建集群

为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。

客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 连接来使用服务。

客户端使用这个 TCP 链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。

ZooKeeper 官方提供的架构图:

上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。

集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。

顺序访问

对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号。

这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。这个编号也叫做时间戳—zxid(ZooKeeper Transaction Id)。

高性能

ZooKeeper 是高性能的。在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)

ZooKeeper 集群角色介绍

最典型集群模式:Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。

但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三种角色。

如下图所示:

ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器。

Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和  Observer 都只能提供读服务。

Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。

ZooKeeper & ZAB 协议 & Paxos 算法

ZAB 协议 & Paxos 算法

Paxos 算法可以说是  ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。

另外,在 ZooKeeper 的官方文档中也指出,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 ZooKeeper 设计的崩溃可恢复的原子消息广播算法。

ZAB 协议介绍

ZAB(ZooKeeper Atomic Broadcast 原子广播)协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。

在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

ZAB 协议两种基本的模式

ZAB 协议包括两种基本的模式,分别是崩溃恢复和消息广播。

当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。

当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。

其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。

当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。

当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播。

那么新加入的服务器就会自觉地进人数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

正如上文介绍中所说的,ZooKeeper 设计成只允许唯一的一个 Leader 服务器来进行事务请求的处理。

Leader 服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议。

而如果集群中的其他机器接收到客户端的事务请求,那么这些非 Leader 服务器会首先将这个事务请求转发给 Leader 服务器。

关于 ZAB 协议 & Paxos 算法需要讲和理解的东西太多了,推荐阅读下面两篇文章:

  • 图解 Paxos 一致性协议:

http://blog.xiaohansong.com/2016/09/30/Paxos/

  • Zookeeper ZAB 协议分析:

http://blog.xiaohansong.com/2016/08/25/zab/

关于如何使用 ZooKeeper 实现分布式锁,可以查看下面这篇文章:

  • Zookeeper ZAB 协议分析:

https://blog.csdn.net/qiangcuo6087/article/details/79067136

总结

通过阅读本文,想必大家已从以下这七点了解了 ZooKeeper:

  • ZooKeeper 的由来
  • ZooKeeper 到底是什么
  • ZooKeeper 的一些重要概念(会话(Session)、Znode、版本、Watcher、ACL)
  • ZooKeeper 的特点
  • ZooKeeper 的设计目标
  • ZooKeeper 集群角色介绍(Leader、Follower 和 Observer 三种角色)
  • ZooKeeper & ZAB 协议 & Paxos 算法

参考文章:

  • 《从Paxos到Zookeeper 》
  • https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription
  • https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index
  • https://www.cnblogs.com/raphael5200/p/5285583.html
  • https://zhuanlan.zhihu.com/p/30024403

TP5 事务支持

使用事务之前,先确保数据库的存储引擎支持事务操作。

  • MyISAM:不支持事务,主要用于读数据提高性能
  • InnoDB:支持事务、行级锁和并发


ThinkPHP5 使用事务常用方法:
$model->startTrans(); // 开启事务
$model->rollback(); // 事务回滚
$model->commit(); // 提交事务

public function transaction()
{
    $modelA = model('A');
    $modelA->startTrans();             // 开启事务A
    $result = $modelA->save($data1);
    if($result === false){
        $modelA->rollBack();        // 事务A回滚
        $this->error('添加A信息失败');
    }
 
    $modelB = model('B');
    $modelB->startTrans();             // 开启事务B
    $result = $modelB->save($data2);
    if($result === false){
        $modelB->rollBack();        // 事务B回滚
        $modelA->rollBack();        // 事务A回滚
        $this->error('添加B信息失败');
    }
 
    $modelC = model('C');
    $modelC->startTrans();             // 开启事务C
    $result = $modelC->save($data3);
    if($result === false){
        $modelC->rollBack();        // 事务C回滚
        $modelB->rollBack();        // 事务B回滚
        $modelA->rollBack();        // 事务A回滚
        $this->error('添加C信息失败');
    }
 
    // 提交事务
    $modelC->commit();
    $modelB->commit();
    $modelA->commit();
 
    $this->success('添加成功');
}

互联网项目中MySQL应该选什么事务隔离级别

本文来自微信公众号:孤独烟

引言

开始我们的内容,相信大家一定遇到过下面的一个面试场景

面试官:“讲讲mysql有几个事务隔离级别?”  

你:“读未提交,读已提交,可重复读,串行化四个!默认是可重复读”

面试官:“为什么mysql选可重复读作为默认的隔离级别?”   

(你面露苦色,不知如何回答!)   

面试官:”你们项目中选了哪个隔离级别?为什么?”   

你:“当然是默认的可重复读,至于原因。。呃。。。”   

(然后你就可以回去等通知了!)

为了避免上述尴尬的场景,请继续往下阅读!

Mysql默认的事务隔离级别是可重复读(Repeatable Read),那互联网项目中Mysql也是用默认隔离级别,不做修改么?

OK,不是的,我们在项目中一般用读已提交(Read Commited)这个隔离级别!

what!居然是读已提交,网上不是说这个隔离级别存在不可重复读幻读问题么?不用管么?好,带着我们的疑问开始本文!

正文

我们先来思考一个问题,在Oracle,SqlServer中都是选择读已提交(Read Commited)作为默认的隔离级别,为什么Mysql不选择读已提交(Read Commited)作为默认隔离级别,而选择可重复读(Repeatable Read)作为默认的隔离级别呢?

Why?Why?Why?

这个是有历史原因的,当然要从我们的主从复制开始讲起了!

主从复制,是基于什么复制的?

是基于binlog复制的!这里不想去搬binlog的概念了,就简单理解为binlog是一个记录数据库更改的文件吧~

binlog有几种格式?

OK,三种,分别是

  • statement:记录的是修改SQL语句
  • row:记录的是每行实际数据的变更
  • mixed:statement和row模式的混合

那Mysql在5.0这个版本以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!

接下来,就要说说当binlog为STATEMENT格式,且隔离级别为读已提交(Read Commited)时,有什么bug呢?如下图所示,在主(master)上执行如下事务

此时在主(master)上执行下列语句

select * from test

输出如下

+---+
| b |
+---+
| 3 |
+---+
1 row in set

但是,你在此时在从(slave)上执行该语句,得出输出如下

Empty set

这样,你就出现了主从不一致性的问题!原因其实很简单,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!从(slave)同步的是binglog,因此从机执行的顺序和主机不一致!就会出现主从不一致!

如何解决?

解决方案有两种!

(1)隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住!

(2)将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!

那么,当我们了解完mysql选可重复读(Repeatable Read)作为默认隔离级别的原因后,接下来我们将其和读已提交(Read Commited)进行对比,来说明为什么在互联网项目为什么将隔离级别设为读已提交(Read Com

对比

ok,我们先明白一点!项目中是不用读未提交(Read UnCommitted)串行化(Serializable)两个隔离级别,原因有二

  • 采用读未提交(Read UnCommitted),一个事务读到另一个事务未提交读数据,这个不用多说吧,从逻辑上都说不过去!
  • 采用串行化(Serializable),每个次读操作都会加锁,快照读失效,一般是使用mysql自带分布式事务功能时才使用该隔离级别!(笔者从未用过mysql自带的这个功能,因为这是XA事务,是强一致性事务,性能不佳!互联网的分布式方案,多采用最终一致性的事务解决方案!)

也就是说,我们该纠结都只有一个问题,究竟隔离级别是用读已经提交呢还是可重复读?

接下来对这两种级别进行对比,讲讲我们为什么选读已提交(Read Commited)作为事务隔离级别!

假设表结构如下

 CREATE TABLE `test` (
`id` int(11NOT NULL,
`color` varchar(20NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB

数据如下

+----+-------+
| id | color |
+----+-------+
|  1 |  red  |
|  2 | white |
|  5 |  red  |
|  7 | white |
+----+-------+

为了便于描述,下面将

  • 可重复读(Repeatable Read),简称为RR;
  • 读已提交(Read Commited),简称为RC;

缘由一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!

此时执行语句

select * from test where id <3 for update;

在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据!
而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!

ps:在RC隔离级别下并不是不会出现死锁,只是出现几率比RR低而已!

缘由二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
此时执行语句

update test set color = 'blue' where color = 'red'

在RC隔离级别下,其先走聚簇索引,进行全部扫描。加锁如下:

但在实际中,MySQL做了优化,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录放锁。

实际加锁如下然而,在RR隔离级别下,走聚簇索引,进行全部扫描,最后会将整个表锁上,如下所示

缘由三:在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性!

在5.1.15的时候,innodb引入了一个概念叫做“semi-consistent”,减少了更新同一行记录时的冲突,减少锁等待。

所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!

具体表现如下:

此时有两个Session,Session1和Session2!

Session1执行

update test set color = 'blue' where color = 'red'

先不Commit事务!

与此同时Ssession2执行

update test set color = 'blue' where color = 'white'

session 2尝试加锁的时候,发现行上已经存在锁,InnoDB会开启semi-consistent read,返回最新的committed版本(1,red),(2,white),(5,red),(7,white)。MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!

而在RR隔离级别下,Session2只能等待!

两个疑问

在RC级别下,不可重复读问题需要解决么?

不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle的默认隔离级别就是RC,你们改过Oracle的默认隔离级别么?

在RC级别下,主从复制用什么binlog格式?

OK,在该隔离级别下,用的binlog为row格式,是基于行的复制!Innodb的创始人也是建议binlog使用该格式!

总结

本文啰里八嗦了一篇文章只是为了说明一件事,互联网项目请用:读已提交(Read Commited)这个隔离级别!

Laravel文件上传

Laravel文件系统是基于Frank de Jonge的Flysystem扩展包
提供了简单的接口,可以操作本地端空间、AmazonS3、Rackspace Cloud Storage
可以非常简单的切换不同的保存方式,但仍使用相同的API操作
支持 local ftp s3 rackspace
默认是local 就是本地
s3是亚马逊的配置,在国内用的很少,国内可以弄个阿里云OSS 七牛存储
本人laravel版本是5.2

然后我们新建一个控制器方法,然后添加对应的路由

Route::any('/upload',"StudentController@upload");

public function upload(Request $request){
   if($request->isMethod('POST')){
      //var_dump($_FILES);
      //exit;
      $file = $request->file('files');
      //dd($file);
      //判断文件是否上传成功
      if($file->isValid())
      {
         //原文件名
         $originalName = $file->getClientOriginalName();
         //文件扩展名
         $ext = $file->getClientOriginalExtension();
         //文件MImeType
         $type = $file->getClientMimeType();
         //文件临时绝对路径
         $realPath = $file->getRealPath();
         //为了避免重名
         $filename = date('YmdHis').'-'.uniqid().'.'.$ext;
         echo $filename;
         $bool = Storage::disk('uploads')->put($filename,file_get_contents($realPath));
         //var_dump($bool);
      }
      echo 'ok';
   }
   return view('student.upload');
}

模板文件是根据auth下面login.blade.php修改的如下:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="panel panel-default">
                <div class="panel-heading">Login</div>
                <div class="panel-body">
                    <form class="form-horizontal" role="form" method="POST" action="{{ url('/login') }}">
                        {{ csrf_field() }}

                        <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                            <label for="email" class="col-md-4 control-label">E-Mail Address</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control" name="email" value="{{ old('email') }}">

                                @if ($errors->has('email'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                            <label for="password" class="col-md-4 control-label">Password</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control" name="password">

                                @if ($errors->has('password'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <div class="checkbox">
                                    <label>
                                        <input type="checkbox" name="remember"> Remember Me
                                    </label>
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="col-md-6 col-md-offset-4">
                                <button type="submit" class="btn btn-primary">
                                    <i class="fa fa-btn fa-sign-in"></i> Login
                                </button>

                                <a class="btn btn-link" href="{{ url('/password/reset') }}">Forgot Your Password?</a>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

OK,完成了,测试下

软件开发模式对比(瀑布、迭代、螺旋、敏捷)

本文转自:https://www.cnblogs.com/tianguook/p/4004726.html

1、瀑布模型是由W.W.Royce在1970年最初提出的软件开发模型, 瀑布式开发是一种老旧的计算机软件开发方法。
瀑布模型式是最典型的预见性的方法,严格遵循预先计划的需求分析、设计、编码、集成、测试、维护的步骤顺序进行。
步骤成果作为衡量进度的方法,例如需求规格,设计文档,测试计划和代码审阅等等。

瀑布式的主要的问题是它的严格分级导致的自由度降低,项目早期即作出承诺导致对后期需求的变化难以调整,
代价高昂。瀑布式方法在需求不明并且在项目进行过程中可能变化的情况下基本是不可行的。

2、迭代式开发也被称作迭代增量式开发迭代进化式开发,是一种与传统的瀑布式开发相反的软件开发过程,它弥补了传统开发方式中的一些弱点,具有更高的成功率和生产率。
什么是迭代式开发?
每次只设计和实现这个产品的一部分,
逐步逐步完成的方法叫迭代开发,
每次设计和实现一个阶段叫做一个迭代.

在迭代式开发方法中,整个开发工作被组织为一系列的短小的、
固定长度(如3周)的小项目,被称为一系列的迭代。
每一次迭代都包括了需求分析、设计、实现与测试。
采用这种方法,开发工作可以在需求被完整地确定之前启动,
并在一次迭代中完成系统的一部分功能或业务逻辑的开发工作。
再通过客户的反馈来细化需求,并开始新一轮的迭代。

迭代式开发的优点:
1、降低风险
2、得到早期用户反馈
3、持续的测试和集成
4、使用变更
5、提高复用性

螺旋开发,1988年,巴利·玻姆(Barry Boehm)正式发表了软件系统开发的“螺旋模型”,它将瀑布模型和快速原型模型结合起来,强调了其他模型所忽视的风险分析,特别适合于大型复杂的系统。
“螺旋模型”刚开始规模很小,当项目被定义得更好、更稳定时,逐渐展开。

“螺旋模型”的核心就在于您不需要在刚开始的时候就把所有事情都定义的清清楚楚。您轻松上阵,定义最重要的功能,实现它,然后听取客户的意见,之后再进入到下一个阶段。如此不断轮回重复,直到得到您满意的最终产品。
(1)制定计划:确定软件目标,选定实施方案,弄清项目开发的限制条件;

(2)风险分析:分析评估所选方案,考虑如何识别和消除风险;

(3)实施工程:实施软件开发和验证;

(4)客户评估:评价开发工作,提出修正建议,制定下一步计划。
螺旋模型很大程度上是一种风险驱动的方法体系,因为在每个阶段之前及经常发生的循环之前,都必须首先进行风险评估。

敏捷软件开发又称敏捷开发, 是一种从1990年代开始逐渐引起广泛关注的一些新型软件开发方法,是一种应对快速变化的需求的一种软件开发能力。它们的具体名称、理念、过程、术语都不 尽相同,相对于“非敏捷”,更强调程序员团队与业务专家之间的紧密协作、面对面的沟通(认为比书面的文档更有效)、频繁交付新的软件版本、紧凑而自我组织 型的团队、能够很好地适应需求变化的代码编写和团队组织方法,也更注重软件开发中人的作用。

  • 人和交互 重于过程和工具。
  • 可以工作的软件 重于求全而完备的文档。
  • 客户协作重于合同谈判。
  • 随时应对变化重于循规蹈矩。

其中位于右边的内容虽然也有其价值,但是左边的内容最为重要。
人员彼此信任 人少但是精干 可以面对面的沟通

项目的敏捷开发:
敏捷开发小组主要的工作方式可以归纳为:作为一个整体工作; 按短迭代周期工作; 每次迭代交付一些成果;
关注业务优先级; 检查与调整。

最重要的因素恐怕是项目的规模。规模增长,面对面的沟通就愈加困难,
因此敏捷方法更适用于较小的队伍,40、30、20、10人或者更少。
大规模的敏捷软件开发尚处于积极研究的领域。

四者对比区别:

传统的瀑布式开发,也就是从需求到设计,从设计到编码,从编码到测试,从测试到提交大概这样的流程,要求每一个开发阶段都要做到最好。
特别是前期阶段,设计的越完美,提交后的成本损失就越少。

迭代式开发,不要求每一个阶段的任务做的都是最完美的,而是明明知道还有很多不足的地方,却偏偏不去完善它,而是把主要功能先搭建起来为目的,以最短的时间,
最少的损失先完成一个“不完美的成果物”直至提交。然后再通过客户或用户的反馈信息,在这个“不完美的成果物”上逐步进行完善。

螺旋开发,很大程度上是一种风险驱动的方法体系,因为在每个阶段之前及经常发生的循环之前,都必须首先进行风险评估。

敏捷开发,相比迭代式开发两者都强调在较短的开发周期提交软件,但是,敏捷开发的周期可能更短,并且更加强调队伍中的高度协作。
敏捷方法有时候被误认为是无计划性和纪律性的方法,实际上更确切的说法是敏捷方法强调适应性而非预见性。

适应性的方法集中在快速适应现实的变化。当项目的需求起了变化,团队应该迅速适应。这个团队可能很难确切描述未来将会如何变化.

mysql 索引

今天整理下mysql索引直接上截图

表比较简单 结构和索引如下

LIKE 相关:

有疑问的地方:

总结:对于单值索引,尽量选择过滤更好的索引

组合索引,过滤性好的索引尽量放在前面

选择组合索引,尽量选择可以包含where更多的字段索引,不确定的时候多explain吧,分析调整到合适的SQL

安装php pcntl扩展

服务器上是php 5.6.40的版本,去php.net下载一下源文件

解压出来 tar -zxvf php-5.6.40.tar.gz

cd hp-5.6.40/ext/pcntl

我的phpize 在/usr/bin/下面

执行:/usr/bin/phpize ./configure –with-php-config=/usr/bin/php-config

然后  ./configure  -》 make-》  make install

看到生成了一个so文件

vim /etc/php.ini 加入 extension=pcntl.so

然后重启php-fpm

看下phpinfo()

OK 搞定!