All posts by dotte

垃圾回收原来是这么回事

最近想复习一下JVM的知识。然后发现网上不少文章在写JVM的垃圾回收算法时,都比较偏向于具体实现,而少有站在更高角度来看垃圾回收算法的文章。而我本人想对垃圾回收算法有个全景的认识,所以,就找到了这本《垃圾回收的算法与实现》(以下简称《垃圾回收》)。本篇博客就是尝试对“全景”的总结。

以下为方便讨论,垃圾回收缩写成GC。

  为什么要有GC

我时而听到C++程序员说我们是被GC惯坏了的一代。的确是这样的,我本人在学习GC算法时,大脑里第一问题就是为什么需要GC这样的东西。说明我已经认为GC是理所当然了。

总的一句话:没有GC的世界,我们需要手动进行内存管理,而手动内存管理是纯技术活,又容易出错。

既然我们写的大多程序都是为了解决现实业务问题,那么,我们为什么不把这种纯技术活自动化呢?但是自动化,也是有代价的。 这是我的个人理解,不代表John McCarthy本人的理解

  “垃圾”的定义

首先,我们要给个“垃圾”的定义,才能进行回收吧。书中给出的定义:把分配到堆中那些不能通过程序引用的对象称为非活动对象,也就是死掉的对象,我们称为“垃圾”。

  GC的定义

因为我们期望让内存管理变得自动(只管用内存,不管内存的回收),我们就必须做两件事情: 1. 找到内存空间里的垃圾;2. 回收垃圾,让程序员能再次利用这部分空间 。(《垃圾回收》 P2)只要满足这两项功能的程序,就是GC,不论它是在JVM中,还是在Ruby的VM中。

但这只是两个需求,并没有说明GC应该何时找垃圾,何时回收垃圾等等更具体的问题,各类GC算法就是在这些更具体问题的处理方式上施展手脚。

  GC的历史

John McCarthy身为Lisp之父和人工智能之父,同时,他也是GC之父。1960年,他在其 论文中首次发布了GC算法(其实是委婉的提出)。

《垃圾回收》的作者认为:

从50年前GC算法首次发布以来,众多研究者对其进行了各种各样的研究,因此许多GC算法也得以发布。但事实上,这些算法只不过是把前文中提到的三种算法进行组合或应用。也可以这么说,1963年GC复制算法诞生时,GC的根本性内容就已经完成了。(《垃圾回收》 P4)

那我们常常听说的分代垃圾回收又是怎么回事?作者是这样说的:人们从众多程序案例中总结出了一个经验:“大部分的对象在生成后马上就变成了垃圾,很少有对象能活得很久”。分代垃圾回收利用该经验,在对象中导入了“年龄”的概念,经历过一次GC后活下来的对象年龄为1岁。(垃圾回收》 P141)

分代垃圾回收中把对象分类成几代,针对不同的代使用不同的GC算法,我们把刚生成的对象称为新生代对象,到达一定年龄的对象则称为老年代对象。(《垃圾回收》 P142)

好了,这下我总算知道为什么要分代了,我的总结是: 将对象根据存活概率进行分类,对存活时间长一些的对象,可以减少扫描“垃圾”的时间,以减少GC频率和时长。 根本思路就是对对象进行分类,才能针对各个分类采用不同的垃圾回收算法,以对各算法进行扬长避短。

留一个问题给读者:我们知道分代垃圾回收所采用的堆结构是:

为什么新生代空间要分成“生成空间”和“幸存空间”,而幸存空间又分成两块大小相等的幸存空间1,幸存空间2?

  这些GC算法共同解决的问题

上面我们说了,GC的定义只给出了需求,三种算法都为实现这个需求,那么它们总会遇到共同要解决的问题吧? 我尝试总结出:

  • 如何分辨出什么是垃圾?
  • 如何、何时搜索垃圾?
  • 如何、何时清除垃圾?

这样,只要涉及到垃圾回收,我就可以从这2点需求,3个共同问题(两点三共)出发来讨论、学习。

  如何评价GC算法?

如果没有评价标准,我们当然无法评估这些GC算法的性能。作者给出了4个标准:

  • 吞吐量: 单位时间内的处理能力
  • 最大暂停时间:GC执行过程中,应用暂停的时长。较大的吞吐量和较短的最大暂停时间不可兼得
  • 堆的使用效率:就是堆空间的利用率。可用的堆越大,GC运行越快;相反,越想有效地利用有限的堆,GC花费的时间就越长。
  • 访问的局部性:把具有引用关系的对象安排在堆中较近的位置,就能提高在缓存中读取到想利用的数据的概率。

好吧。两点三共,四标~

  小结

  搞清楚为什么要GC,要实现GC都要解决什么问题,而各类算法又是怎么解决的,最后怎么评价这些算法。GC原来是这么回事。

但是这不是GC的全部。但是提供我一个思考GC的思考框架。

以上就是《垃圾回收的算法与实现》的读书笔记。如果想更深入,可以阅读《垃圾回收算法手册:自动内存管理的艺术》。

from:http://kb.cnblogs.com/page/562433/

扛住100亿次请求?我们来试一试

作者:ppmsn2005#gmail.com
项目: https://github.com/xiaojiaqi/10billionhongbaos
wiki: https://github.com/xiaojiaqi/10billionhongbaos/wiki/扛住100亿次请求?我们来试一试

1. 前言

前几天,偶然看到了 《扛住100亿次请求——如何做一个“有把握”的春晚红包系统”》(url)一文,看完以后,感慨良多,收益很多。正所谓他山之石,可以攻玉,虽然此文发表于2015年,我看到时已经是2016年末,但是其中的思想仍然是可以为很多后端设计借鉴,。同时作为一个工程师,看完以后又会思考,学习了这样的文章以后,是否能给自己的工作带来一些实际的经验呢?所谓纸上得来终觉浅,绝知此事要躬行,能否自己实践一下100亿次红包请求呢?否则读完以后脑子里能剩下的东西 不过就是100亿 1400万QPS整流 这样的字眼,剩下的文章将展示作者是如何以此过程为目标,在本地环境的模拟了此过程。

实现的目标: 单机支持100万连接,模拟了摇红包和发红包过程,单机峰值QPS 6万,平稳支持了业务。

注:本文以及作者所有内容,仅代表个人理解和实践,过程和微信团队没有任何关系,真正的线上系统也不同,只是从一些技术点进行了实践,请读者进行区分。因作者水平有限,有任何问题都是作者的责任,有问题请联系 ppmsn2005#gmail.com. 全文内容 扛住100亿次请求?我们来试一试

2. 背景知识

QPS: Queries per second 每秒的请求数目

PPS:Packets per second 每秒数据包数目

摇红包:客户端发出一个摇红包的请求,如果系统有红包就会返回,用户获得红包

发红包:产生一个红包里面含有一定金额,红包指定数个用户,每个用户会收到红包信息,用户可以发送拆红包的请求,获取其中的部分金额。

3. 确定目标

在一切系统开始以前,我们应该搞清楚我们的系统在完成以后,应该有一个什么样的负载能力。

3.1 用户总数:

通过文章我们可以了解到接入服务器638台, 服务上限大概是14.3亿用户, 所以单机负载的用户上限大概是14.3亿/638台=228万用户/台。但是目前中国肯定不会有14亿用户同时在线,参考 http://qiye.qianzhan.com/show/detail/160818-b8d1c700.html 的说法,2016年Q2 微信用户大概是8亿,月活在5.4 亿左右。所以在2015年春节期间,虽然使用的用户会很多,但是同时在线肯定不到5.4亿。

3.2. 服务器数量:

一共有638台服务器,按照正常运维设计,我相信所有服务器不会完全上线,会有一定的硬件冗余,来防止突发硬件故障。假设一共有600台接入服务器。

3.3 单机需要支持的负载数:

每台服务器支持的用户数:5.4亿/600 = 90万。也就是平均单机支持90万用户。如果真实情况比90万更多,则模拟的情况可能会有偏差,但是我认为QPS在这个实验中更重要。

3.4. 单机峰值QPS:

文章中明确表示为1400万QPS.这个数值是非常高的,但是因为有600台服务器存在,所以单机的QPS为 1400万/600= 约为2.3万QPS, 文章曾经提及系统可以支持4000万QPS,那么系统的QPS 至少要到4000万/600 = 约为 6.6万, 这个数值大约是目前的3倍,短期来看并不会被触及。但是我相信应该做过相应的压力测试。

3.5. 发放红包:

文中提到系统以5万个每秒的下发速度,那么单机每秒下发速度50000/600 =83个/秒,也就是单机系统应该保证每秒以83个的速度下发即可。
最后考虑到系统的真实性,还至少有用户登录的动作,拿红包这样的业务。真实的系统还会包括聊天这样的服务业务。

最后整体的看一下 100亿次摇红包这个需求,假设它是均匀地发生在春节联欢晚会的4个小时里,那么服务器的QPS 应该是10000000000/600/3600/4.0=1157. 也就是单机每秒1000多次,这个数值其实并不高。如果完全由峰值速度1400万消化 10000000000/(1400*10000) = 714秒,也就是说只需要峰值坚持11分钟,就可以完成所有的请求。可见互联网产品的一个特点就是峰值非常高,持续时间并不会很长。

总结:

从单台服务器看.它需要满足下面一些条件
1. 支持至少100万连接用户
2. 每秒至少能处理2.3万的QPS,这里我们把目标定得更高一些 分别设定到了3万和6万。
3. 摇红包:支持每秒83个的速度下发放红包,也就是说每秒有2.3万次摇红包的请求,其中83个请求能摇到红包,其余的2.29万次请求会知道自己没摇到。当然客户端在收到红包以后,也需要确保客户端和服务器两边的红包数目和红包内的金额要一致。因为没有支付模块,所以我们也把要求提高一倍,达到200个红包每秒的分发速度
4. 支持用户之间发红包业务,确保收发两边的红包数目和红包内金额要一致。同样也设定200个红包每秒的分发速度为我们的目标。

想完整模拟整个系统实在太难了,首先需要海量的服务器,其次需要上亿的模拟客户端。这对我来说是办不到,但是有一点可以确定,整个系统是可以水平扩展的,所以我们可以模拟100万客户端,在模拟一台服务器 那么就完成了1/600的模拟。

和现有系统区别:
和大部分高QPS测试的不同,本系统的侧重点有所不同。我对2者做了一些对比。

常见高QPS系统压力测试 本系统压力测试
连接数 一般<1000 (几百以内) 1000000 (1百万)
单连接吞吐量 非常大 每个连接几十M字节吞吐 非常小 每个连接每次几十个字节
需要的IO次数 不多 非常多

4. 基础软件和硬件

4.1软件:

Golang 1.8r3 , shell, python (开发没有使用c++ 而是使用了golang, 是因为使用golang 的最初原型达到了系统要求。虽然golang 还存在一定的问题,但是和开发效率比,这点损失可以接受)
服务器操作系统:
Ubuntu 12.04
客户端操作系统:
debian 5.0

4.2硬件环境

服务端: dell R2950。 8核物理机,非独占有其他业务在工作,16G内存。这台硬件大概是7年前的产品,性能应该不是很高要求。
服务器硬件版本:
machine
服务器CPU信息:
cpu

客户端: esxi 5.0 虚拟机,配置为4核 5G内存。一共17台,每台和服务器建立6万个连接。完成100万客户端模拟

5. 技术分析和实现

5.1) 单机实现100万用户连接

这一点来说相对简单,笔者在几年前就早完成了单机百万用户的开发以及操作。现代的服务器都可以支持百万用户。相关内容可以查看   github代码以及相关文档。
https://github.com/xiaojiaqi/C1000kPracticeGuide
系统配置以及优化文档:
https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn

5.2) 3万QPS

这个问题需要分2个部分来看客户端方面和服务器方面。

客户端QPS

因为有100万连接连在服务器上,QPS为3万。这就意味着每个连接每33秒,就需要向服务器发一个摇红包的请求。因为单IP可以建立的连接数为6万左右, 有17台服务器同时模拟客户端行为。我们要做的就保证在每一秒都有这么多的请求发往服务器即可。
其中技术要点就是客户端协同。但是各个客户端的启动时间,建立连接的时间都不一致,还存在网络断开重连这样的情况,各个客户端如何判断何时自己需要发送请求,各自该发送多少请求呢?

我是这样解决的:利用NTP服务,同步所有的服务器时间,客户端利用时间戳来判断自己的此时需要发送多少请求。
算法很容易实现:
假设有100万用户,则用户id 为0-999999.要求的QPS为5万, 客户端得知QPS为5万,总用户数为100万,它计算 100万/5万=20,所有的用户应该分为20组,如果 time() % 20 == 用户id % 20,那么这个id的用户就该在这一秒发出请求,如此实现了多客户端协同工作。每个客户端只需要知道 总用户数和QPS 就能自行准确发出请求了。
(扩展思考:如果QPS是3万 这样不能被整除的数目,该如何办?如何保证每台客户端发出的请求数目尽量的均衡呢?)

服务器QPS

服务器端的QPS相对简单,它只需要处理客户端的请求即可。但是为了客观了解处理情况,我们还需要做2件事情。
第一: 需要记录每秒处理的请求数目,这需要在代码里埋入计数器。
第二: 我们需要监控网络,因为网络的吞吐情况,可以客观的反映出QPS的真实数据。为此,我利用python脚本 结合ethtool 工具编写了一个简单的工具,通过它我们可以直观的监视到网络的数据包通过情况如何。它可以客观的显示出我们的网络有如此多的数据传输在发生。
工具截图: 工具截图

5.3) 摇红包业务

摇红包的业务非常简单,首先服务器按照一定的速度生产红包。红包没有被取走的话,就堆积在里面。服务器接收一个客户端的请求,如果服务器里现在有红包就会告诉客户端有,否则就提示没有红包。
因为单机每秒有3万的请求,所以大部分的请求会失败。只需要处理好锁的问题即可。
我为了减少竞争,将所有的用户分在了不同的桶里。这样可以减少对锁的竞争。如果以后还有更高的性能要求,还可以使用 高性能队列——Disruptor来进一步提高性能。

注意,在我的测试环境里是缺少支付这个核心服务的,所以实现的难度是大大的减轻了。另外提供一组数字:2016年淘宝的双11的交易峰值仅仅为12万/秒,微信红包分发速度是5万/秒,要做到这点是非常困难的。(http://mt.sohu.com/20161111/n472951708.shtml)

5.4) 发红包业务

发红包的业务很简单,系统随机产生一些红包,并且随机选择一些用户,系统向这些用户提示有红包。这些用户只需要发出拆红包的请求,系统就可以随机从红包中拆分出部分金额,分给用户,完成这个业务。同样这里也没有支付这个核心服务。

5.5)监控

最后 我们需要一套监控系统来了解系统的状况,我借用了我另一个项目(https://github.com/xiaojiaqi/fakewechat) 里的部分代码完成了这个监控模块,利用这个监控,服务器和客户端会把当前的计数器内容发往监控,监控需要把各个客户端的数据做一个整合和展示。同时还会把日志记录下来,给以后的分析提供原始数据。 线上系统更多使用opentsdb这样的时序数据库,这里资源有限,所以用了一个原始的方案

监控显示日志大概这样
监控日志

6. 代码实现及分析

在代码方面,使用到的技巧实在不多,主要是设计思想和golang本身的一些问题需要考虑。
首先golang的goroutine 的数目控制,因为至少有100万以上的连接,所以按照普通的设计方案,至少需要200万或者300万的goroutine在工作。这会造成系统本身的负担很重。
其次就是100万个连接的管理,无论是连接还是业务都会造成一些心智的负担。
我的设计是这样的:

架构图

首先将100万连接分成多个不同的SET,每个SET是一个独立,平行的对象。每个SET 只管理几千个连接,如果单个SET 工作正常,我只需要添加SET就能提高系统处理能力。
其次谨慎的设计了每个SET里数据结构的大小,保证每个SET的压力不会太大,不会出现消息的堆积。
再次减少了gcroutine的数目,每个连接只使用一个goroutine,发送消息在一个SET里只有一个gcroutine负责,这样节省了100万个goroutine。这样整个系统只需要保留 100万零几百个gcroutine就能完成业务。大量的节省了cpu 和内存
系统的工作流程大概如下:
每个客户端连接成功后,系统会分配一个goroutine读取客户端的消息,当消息读取完成,将它转化为消息对象放至在SET的接收消息队列,然后返回获取下一个消息
在SET内部,有一个工作goroutine,它只做非常简单而高效的事情,它做的事情如下,检查SET的接受消息,它会收到3类消息

1, 客户端的摇红包请求消息

2, 客户端的其他消息 比如聊天 好友这一类

3, 服务器端对客户端消息的回应

对于 第1种消息 客户端的摇红包请求消息 是这样处理的,从客户端拿到摇红包请求消息,试图从SET的红包队列里 获取一个红包,如果拿到了就把红包信息 返回给客户端,否则构造一个没有摇到的消息,返回给对应的客户端。
对于第2种消息 客户端的其他消息 比如聊天 好友这一类,只需简单地从队列里拿走消息,转发给后端的聊天服务队列即可,其他服务会把消息转发出去。
对于第3种消息 服务器端对客户端消息的回应。SET 只需要根据消息里的用户id,找到SET里保留的用户连接对象,发回去就可以了。

对于红包产生服务,它的工作很简单,只需要按照顺序在轮流在每个SET的红包产生对列里放至红包对象就可以了。这样可以保证每个SET里都是公平的,其次它的工作强度很低,可以保证业务稳定。

见代码
https://github.com/xiaojiaqi/10billionhongbaos

7实践

实践的过程分为3个阶段

阶段1:

分别启动服务器端和监控端,然后逐一启动17台客户端,让它们建立起100万的链接。在服务器端,利用ss 命令 统计出每个客户端和服务器建立了多少连接。
命令如下:
Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print \$8}” | sort | uniq –c’

结果如下: 100万连接建立

阶段2:

利用客户端的http接口,将所有的客户端QPS 调整到3万,让客户端发出3W QPS强度的请求。
运行如下命令:
启动脚本

观察网络监控和监控端反馈,发现QPS 达到预期数据
网络监控截图
3万qps

在服务器端启动一个产生红包的服务,这个服务会以200个每秒的速度下发红包,总共4万个。此时观察客户端在监控上的日志,会发现基本上以200个每秒的速度获取到红包。

摇红包

等到所有红包下发完成后,再启动一个发红包的服务,这个服务系统会生成2万个红包,每秒也是200个,每个红包随机指定3位用户,并向这3个用户发出消息,客户端会自动来拿红包,最后所有的红包都被拿走。

发红包

阶段3

利用客户端的http接口,将所有的客户端QPS 调整到6万,让客户端发出6W QPS强度的请求。

6wqps

如法炮制,在服务器端,启动一个产生红包的服务,这个服务会以200个每秒的速度下发红包。总共4万个。此时观察客户端在监控上的日志,会发现基本上以200个每秒的速度获取到红包。
等到所有红包下发完成后,再启动一个发红包的服务,这个服务系统会生成2万个红包,每秒也是200个,每个红包随机指定3位用户,并向这3个用户发出消息,客户端会自动来拿红包,最后所有的红包都被拿走。

最后,实践完成。

8 分析数据

在实践过程中,服务器和客户端都将自己内部的计数器记录发往监控端,成为了日志。我们利用简单python 脚本和gnuplt 绘图工具,将实践的过程可视化,由此来验证运行过程。

第一张是 客户端的QPS发送数据
客户端qps
这张图的横坐标是时间,单位是秒,纵坐标是QPS,表示这时刻所有客户端发送的请求的QPS。
图的第一区间,几个小的峰值,是100万客户端建立连接的, 图的第二区间是3万QPS 区间,我们可以看到数据 比较稳定的保持在3万这个区间。最后是6万QPS区间。但是从整张图可以看到QPS不是完美地保持在我们希望的直线上。这主要是以下几个原因造成的

  1. 当非常多goroutine 同时运行的时候,依靠sleep 定时并不准确,发生了偏移。我觉得这是golang本身调度导致的。当然如果cpu比较强劲,这个现象会消失。
  2. 因为网络的影响,客户端在发起连接时,可能发生延迟,导致在前1秒没有完成连接。
  3. 服务器负载较大时,1000M网络已经出现了丢包现象,可以通过ifconfig 命令观察到这个现象,所以会有QPS的波动。

第二张是 服务器处理的QPS图
服务器qps

和客户端的向对应的,服务器也存在3个区间,和客户端的情况很接近。但是我们看到了在大概22:57分,系统的处理能力就有一个明显的下降,随后又提高的尖状。这说明代码还需要优化。

整体观察在3万QPS区间,服务器的QPS比较稳定,在6万QSP时候,服务器的处理就不稳定了。我相信这和我的代码有关,如果继续优化的话,还应该能有更好的效果。

将2张图合并起来 qps

基本是吻合的,这也证明系统是符合预期设计的。

这是红包生成数量的状态变化图
生成红包

非常的稳定。

这是客户端每秒获取的摇红包状态
获取红包

可以发现3万QPS区间,客户端每秒获取的红包数基本在200左右,在6万QPS的时候,以及出现剧烈的抖动,不能保证在200这个数值了。我觉得主要是6万QPS时候,网络的抖动加剧了,造成了红包数目也在抖动。

最后是golang 自带的pprof 信息,其中有gc 时间超过了10ms, 考虑到这是一个7年前的硬件,而且非独占模式,所以还是可以接受。
pprof

总结:

按照设计目标,我们模拟和设计了一个支持100万用户,并且每秒至少可以支持3万QPS,最多6万QPS的系统,简单模拟了微信的摇红包和发红包的过程。可以说达到了预期的目的。
如果600台主机每台主机可以支持6万QPS,只需要7分钟就可以完成 100亿次摇红包请求。

虽然这个原型简单地完成了预设的业务,但是它和真正的服务会有哪些差别呢?我罗列了一下

区别 真正服务 本次模拟
业务复杂 更复杂 非常简单
协议 Protobuf 以及加密 简单的协议
支付 复杂
日志 复杂
性能 更高
用户分布 用户id分散在不同服务器,需要hash以后统一, 复杂。 用户id 连续,很多优化使代码简单 非常高效
安全控制 复杂
热更新及版本控制 复杂
监控 细致 简单

Refers:

mysql性能优化-慢查询分析、优化索引和配置

目录

一、优化概述

二、查询与索引优化分析

1性能瓶颈定位

Show命令

慢查询日志

explain分析查询

profiling分析查询

2索引及查询优化

三、配置优化

1)      max_connections

2)      back_log

3)      interactive_timeout

4)      key_buffer_size

5)      query_cache_size

6)      record_buffer_size

7)      read_rnd_buffer_size

8)      sort_buffer_size

9)      join_buffer_size

10)    table_cache

11)    max_heap_table_size

12)    tmp_table_size

13)    thread_cache_size

14)    thread_concurrency

15)    wait_timeout

一、 优化概述

MySQL 数据库是常见的两个瓶颈是CPU和I/O的瓶颈,CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候。磁盘I/O瓶颈发生在装入数据远大于 内存容量的时候,如果应用分布在网络上,那么查询量相当大的时候那么平瓶颈就会出现在网络上,我们可以用mpstat, iostat, sar和vmstat来查看系统的性能状态。

除了服务器硬件的性能瓶颈,对于MySQL系统本身,我们可以使用工具来优化数据库的性能,通常有三种:使用索引,使用EXPLAIN分析查询以及调整MySQL的内部配置。

二、查询与索引优化分析

在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有慢查询日志,EXPLAIN 分析查询,profiling分析以及show命令查询系统状态及系统变量,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

1 性能瓶颈定位Show命令

我们可以通过show命令查看MySQL状态及变量,找到系统的瓶颈:

Mysql> show status ——显示状态信息(扩展show status like ‘XXX’)

Mysql> show variables ——显示系统变量(扩展show variables like ‘XXX’)

Mysql> show innodb status ——显示InnoDB存储引擎的状态

Mysql> show processlist ——查看当前SQL执行,包括执行状态、是否锁表等

Shell> mysqladmin variables -u username -p password——显示系统变量

Shell> mysqladmin extended-status -u username -p password——显示状态信息

查看状态变量及帮助:

Shell> mysqld –verbose –help [|more #逐行显示]

比较全的Show命令的使用可参考: http://blog.phpbean.com/a.cn/18/

慢查询日志

慢查询日志开启:

在配置文件my.cnf或my.ini中在[mysqld]一行下面加入两个配置参数

log-slow-queries=/data/mysqldata/slow-query.log

long_query_time=2

注:log-slow-queries参数为慢查询日志存放的位置,一般这个目录要有mysql的运行帐号的可写权限,一般都将这个目录设置为mysql的数据存放目录;

long_query_time=2中的2表示查询超过两秒才记录;

在my.cnf或者my.ini中添加log-queries-not-using-indexes参数,表示记录下没有使用索引的查询。

log-slow-queries=/data/mysqldata/slow-query.log

long_query_time=10

log-queries-not-using-indexes

慢查询日志开启方法二:

我们可以通过命令行设置变量来即时启动慢日志查询。由下图可知慢日志没有打开,slow_launch_time=# 表示如果建立线程花费了比这个值更长的时间,slow_launch_threads 计数器将增加

设置慢日志开启

MySQL后可以查询long_query_time 的值 。

为了方便测试,可以将修改慢查询时间为5秒。

慢查询分析mysqldumpslow

我们可以通过打开log文件查看得知哪些SQL执行效率低下

[root@localhost mysql]# more slow-query.log

# Time: 081026 19:46:34

# User@Host: root[root] @ localhost []

# Query_time: 11 Lock_time: 0 Rows_sent: 1 Rows_examined: 6552961

select count(*) from t_user;

从日志中,可以发现查询时间超过5 秒的SQL,而小于5秒的没有出现在此日志中。

如果慢查询日志中记录内容很多,可以使用mysqldumpslow工具(MySQL客户端安装自带)来对慢查询日志进行分类汇总。mysqldumpslow对日志文件进行了分类汇总,显示汇总后摘要结果。

进入log的存放目录,运行

[root@mysql_data]#mysqldumpslow  slow-query.log

Reading mysql slow query log from slow-query.log

Count: 2 Time=11.00s (22s) Lock=0.00s (0s) Rows=1.0 (2), root[root]@mysql

select count(N) from t_user;

mysqldumpslow命令

/path/mysqldumpslow -s c -t 10 /database/mysql/slow-query.log

这会输出记录次数最多的10条SQL语句,其中:

-s, 是表示按照何种方式排序,c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序,ac、at、al、ar,表示相应的倒叙;

-t, 是top n的意思,即为返回前面多少条的数据;

-g, 后边可以写一个正则匹配模式,大小写不敏感的;

例如:

/path/mysqldumpslow -s r -t 10 /database/mysql/slow-log

得到返回记录集最多的10个查询。

/path/mysqldumpslow -s t -t 10 -g “left join” /database/mysql/slow-log

得到按照时间排序的前10条里面含有左连接的查询语句。

使 用mysqldumpslow命令可以非常明确的得到各种我们需要的查询语句,对MySQL查询语句的监控、分析、优化是MySQL优化非常重要的一步。 开启慢查询日志后,由于日志记录操作,在一定程度上会占用CPU资源影响mysql的性能,但是可以阶段性开启来定位性能瓶颈。

explain分析查询

使用 EXPLAIN 关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。通过explain命令可以得到:

– 表的读取顺序

– 数据读取操作的操作类型

– 哪些索引可以使用

– 哪些索引被实际使用

– 表之间的引用

– 每张表有多少行被优化器查询

EXPLAIN字段:

ØTable:显示这一行的数据是关于哪张表的

Øpossible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句

Økey:实际使用的索引。如果为NULL,则没有使用索引。MYSQL很少会选择优化不足的索引,此时可以在SELECT语句中使用USE INDEX(index)来强制使用一个索引或者用IGNORE INDEX(index)来强制忽略索引

Økey_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好

Øref:显示索引的哪一列被使用了,如果可能的话,是一个常数

Ørows:MySQL认为必须检索的用来返回请求数据的行数

Øtype:这是最重要的字段之一,显示查询使用了何种类型。从最好到最差的连接类型为system、const、eq_reg、ref、range、index和ALL

nsystem、const:可以将查询的变量转为常量.  如id=1; id为 主键或唯一键.

neq_ref:访问索引,返回某单一行的数据.(通常在联接时出现,查询使用的索引为主键或惟一键)

nref:访问索引,返回某个值的数据.(可以返回多行) 通常使用=时发生

nrange:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西,并且该字段上建有索引时发生的情况(注:不一定好于index)

nindex:以索引的顺序进行全表扫描,优点是不用排序,缺点是还要全表扫描

nALL:全表扫描,应该尽量避免

ØExtra:关于MYSQL如何解析查询的额外信息,主要有以下几种

nusing index:只用到索引,可以避免访问表.

nusing where:使用到where来过虑数据. 不是所有的where clause都要显示using where. 如以=方式访问索引.

nusing tmporary:用到临时表

nusing filesort:用到额外的排序. (当使用order by v1,而没用到索引时,就会使用额外的排序)

nrange checked for eache record(index map:N):没有好的索引.

profiling分析查询

通过慢日志查询可以知道哪些SQL语句执行效率低下,通过explain我们可以得知SQL语句的具体执行情况,索引使用等,还可以结合show命令查看执行状态。

如果觉得explain的信息不够详细,可以同通过profiling命令得到更准确的SQL执行消耗系统资源的信息。

profiling默认是关闭的。可以通过以下语句查看

 

打开功能: mysql>set profiling=1; 执行需要测试的sql 语句:

mysql> show profiles\G; 可以得到被执行的SQL语句的时间和ID

mysql>show profile for query 1; 得到对应SQL语句执行的详细信息

Show Profile命令格式:

SHOW PROFILE [type [, type] … ]

    [FOR QUERY n]

    [LIMIT row_count [OFFSET offset]]

type:

    ALL

  | BLOCK IO

  | CONTEXT SWITCHES

  | CPU

  | IPC

  | MEMORY

  | PAGE FAULTS

  | SOURCE

  | SWAPS

以 上的16rows是针对非常简单的select语句的资源信息,对于较复杂的SQL语句,会有更多的行和字段,比如converting HEAP to MyISAM 、Copying to tmp table等等,由于以上的SQL语句不存在复杂的表操作,所以未显示这些字段。通过profiling资源耗费信息,我们可以采取针对性的优化措施。

测试完毕以后 ,关闭参数:mysql> set profiling=0

2     索引及查询优化

索引的类型

Ø 普通索引:这是最基本的索引类型,没唯一性之类的限制。

Ø 唯一性索引:和普通索引基本相同,但所有的索引列值保持唯一性。

Ø 主键:主键是一种唯一索引,但必须指定为”PRIMARY KEY”。

Ø 全文索引:MYSQL从3.23.23开始支持全文索引和全文检索。在MYSQL中,全文索引的索引类型为FULLTEXT。全文索引可以在VARCHAR或者TEXT类型的列上创建。

大多数MySQL索引(PRIMARY KEY、UNIQUE、INDEX和FULLTEXT)使用B树中存储。空间列类型的索引使用R-树,MEMORY表支持hash索引。

单列索引和多列索引(复合索引)

索引可以是单列索引,也可以是多列索引。对相关的列使用索引是提高SELECT操作性能的最佳途径之一。

多列索引:

MySQL可以为多个列创建索引。一个索引可以包括15个列。对于某些列类型,可以索引列的左前缀,列的顺序非常重要。

多列索引可以视为包含通过连接索引列的值而创建的值的排序的数组。一般来说,即使是限制最严格的单列索引,它的限制能力也远远低于多列索引。

最左前缀

多列索引有一个特点,即最左前缀(Leftmost Prefixing)。假如有一个多列索引为key(firstname lastname age),当搜索条件是以下各种列的组合和顺序时,MySQL将使用该多列索引:

firstname,lastname,age

firstname,lastname

firstname

也就是说,相当于还建立了key(firstname lastname)和key(firstname)。

索引主要用于下面的操作:

Ø 快速找出匹配一个WHERE子句的行。

Ø 删除行。当执行联接时,从其它表检索行。

Ø 对 具体有索引的列key_col找出MAX()或MIN()值。由预处理器进行优化,检查是否对索引中在key_col之前发生所有关键字元素使用了 WHERE key_part_# = constant。在这种情况下,MySQL为每个MIN()或MAX()表达式执行一次关键字查找,并用常数替 换它。如果所有表达式替换为常量,查询立即返回。例如:

SELECT MIN(key2), MAX (key2)  FROM tb WHERE key1=10;

Ø 如果对一个可用关键字的最左面的前缀进行了排序或分组(例如,ORDER BY key_part_1,key_part_2),排序或分组一个表。如果所有关键字元素后面有DESC,关键字以倒序被读取。

Ø 在一些情况中,可以对一个查询进行优化以便不用查询数据行即可以检索值。如果查询只使用来自某个表的数字型并且构成某些关键字的最左面前缀的列,为了更快,可以从索引树检索出值。

SELECT key_part3 FROM tb WHERE key_part1=1

有 时MySQL不使用索引,即使有可用的索引。一种情形是当优化器估计到使用索引将需要MySQL访问表中的大部分行时。(在这种情况下,表扫描可能会更快 些)。然而,如果此类查询使用LIMIT只搜索部分行,MySQL则使用索引,因为它可以更快地找到几行并在结果中返回。例如:

合理的建立索引的建议:

(1)  越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。

(2)  简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址。

(3)  尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值

这部分是关于索引和写SQL语句时应当注意的一些琐碎建议和注意点。

1. 当结果集只有一行数据时使用LIMIT 1

2. 避免SELECT *,始终指定你需要的列

从表中读取越多的数据,查询会变得更慢。他增加了磁盘需要操作的时间,还是在数据库服务器与WEB服务器是独立分开的情况下。你将会经历非常漫长的网络延迟,仅仅是因为数据不必要的在服务器之间传输。

3. 使用连接(JOIN)来代替子查询(Sub-Queries)

       连接(JOIN).. 之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

4. 使用ENUMCHAR 而不是VARCHAR,使用合理的字段属性长度

5. 尽可能的使用NOT NULL

6. 固定长度的表会更快

7. 拆分大的DELETE INSERT 语句

8. 查询的列越小越快

 Where条件

在查询中,WHERE条件也是一个比较重要的因素,尽量少并且是合理的where条件是很重要的,尽量在多个条件的时候,把会提取尽量少数据量的条件放在前面,减少后一个where条件的查询时间。

有些where条件会导致索引无效:

Ø where子句的查询条件里有!=,MySQL将无法使用索引。

Ø where子句使用了Mysql函数的时候,索引将无效,比如:select * from tb where left(name, 4) = ‘xxx’

Ø 使用LIKE进行搜索匹配的时候,这样索引是有效的:select * from tbl1 where name like ‘xxx%’,而like ‘%xxx%’ 时索引无效

三、    配置优化

安 装MySQL后,配置文件my.cnf在 /MySQL安装目录/share/mysql目录中,该目录中还包含多个配置文件可供参考,有my-large.cnf ,my-huge.cnf,  my-medium.cnf,my-small.cnf,分别对应大中小型数据库应用的配置。win环境下即存在于MySQL安装目录中的.ini文 件。

下面列出了对性能优化影响较大的主要变量,主要分为连接请求的变量和缓冲区变量。

1.   连接请求的变量:

1)     max_connections

MySQL 的最大连接数,增加该值增加mysqld 要求的文件描述符的数量。如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量,当然这建立在机器能支撑的情况下,因为如果连接数越多, 介于MySQL会为每个连接提供连接缓冲区,就会开销越多的内存,所以要适当调整该值,不能盲目提高设值。

数值过小会经常出现ERROR 1040: Too many connections错误,可以过’conn%’通配符查看当前状态的连接数量,以定夺该值的大小。

show variables like ‘max_connections’ 最大连接数

show  status like ‘max_used_connections’响应的连接数

如下:

mysql> show variables like ‘max_connections‘;

+———————–+——-+

| Variable_name | Value |

+———————–+——-+

| max_connections | 256  |

+———————–+——-+

mysql> show status like ‘max%connections‘;

+———————–+——-+

| Variable_name       | Value |

+—————————-+——-+

| max_used_connections | 256|

+—————————-+——-+

max_used_connections / max_connections * 100% (理想值≈ 85%)

如果max_used_connections跟max_connections相同 那么就是max_connections设置过低或者超过服务器负载上限了,低于10%则设置过大。

2)     back_log

MySQL 能暂存的连接数量。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用。如果MySQL的连接数据达到 max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过 back_log,将不被授予连接资源。

back_log值指出在MySQL暂时停止回答新请求之前的短时间内有多少个请求可以被存在堆栈中。只有如果期望在一个短时间内有很多连接,你需要增加它,换句话说,这值对到来的TCP/IP连接的侦听队列的大小。

当 观察你主机进程列表(mysql> show full processlist),发现大量264084 | unauthenticated user | xxx.xxx.xxx.xxx | NULL | Connect | NULL | login | NULL 的待连接进程时,就要加大back_log 的值了。

默认数值是50,可调优为128,对于Linux系统设置范围为小于512的整数。

3)     interactive_timeout

一个交互连接在被服务器在关闭前等待行动的秒数。一个交互的客户被定义为对mysql_real_connect()使用CLIENT_INTERACTIVE 选项的客户。

默认数值是28800,可调优为7200。

2.   缓冲区变量

全局缓冲:

4)     key_buffer_size

key_buffer_size 指定索引缓冲区的大小,它决定索引处理的速度,尤其是索引读的速度。通过检查状态值Key_read_requests和Key_reads,可以知道 key_buffer_size设置是否合理。比例key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000更好(上述状态值可以使用SHOW STATUS LIKE ‘key_read%’获得)。

key_buffer_size只对MyISAM表起作用。即使你不使用MyISAM表,但是内部的临时磁盘表是MyISAM表,也要使用该值。可以使用检查状态值created_tmp_disk_tables得知详情。

举例如下:

mysql> show variables like ‘key_buffer_size‘;

+——————-+————+

| Variable_name | Value      |

+———————+————+

| key_buffer_size | 536870912 |

+———— ———-+————+

key_buffer_size为512MB,我们再看一下key_buffer_size的使用情况:

mysql> show global status like ‘key_read%‘;

+————————+————-+

| Variable_name   | Value    |

+————————+————-+

| Key_read_requests| 27813678764 |

| Key_reads   |  6798830      |

+————————+————-+

一共有27813678764个索引读取请求,有6798830个请求在内存中没有找到直接从硬盘读取索引,计算索引未命中缓存的概率:

key_cache_miss_rate =Key_reads / Key_read_requests * 100%,设置在1/1000左右较好

默认配置数值是8388600(8M),主机有4GB内存,可以调优值为268435456(256MB)。

5)     query_cache_size

使用查询缓冲,MySQL将查询结果存放在缓冲区中,今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。

通 过检查状态值Qcache_*,可以知道query_cache_size设置是否合理(上述状态值可以使用SHOW STATUS LIKE ‘Qcache%’获得)。如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,如果Qcache_hits的值也 非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小;如果Qcache_hits的值不大,则表明你的查询重复率很低,这种情况下使用查询缓冲反 而会影响效率,那么可以考虑不用查询缓冲。此外,在SELECT语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲。

与查询缓冲有关的参数还有query_cache_type、query_cache_limit、query_cache_min_res_unit。

query_cache_type指定是否使用查询缓冲,可以设置为0、1、2,该变量是SESSION级的变量。

query_cache_limit指定单个查询能够使用的缓冲区大小,缺省为1M。

query_cache_min_res_unit 是在4.1版本以后引入的,它指定分配缓冲区空间的最小单位,缺省为4K。检查状态值Qcache_free_blocks,如果该值非常大,则表明缓冲 区中碎片很多,这就表明查询结果都比较小,此时需要减小query_cache_min_res_unit。

举例如下:

mysql> show global status like ‘qcache%‘;

+——————————-+—————–+

| Variable_name                  | Value        |

+——————————-+—————–+

| Qcache_free_blocks        | 22756       |

| Qcache_free_memory     | 76764704    |

| Qcache_hits           | 213028692 |

| Qcache_inserts         | 208894227   |

| Qcache_lowmem_prunes   | 4010916      |

| Qcache_not_cached | 13385031    |

| Qcache_queries_in_cache | 43560 |

| Qcache_total_blocks          | 111212      |

+——————————-+—————–+

mysql> show variables like ‘query_cache%‘;

+————————————–+————–+

| Variable_name            | Value      |

+————————————–+———–+

| query_cache_limit         | 2097152     |

| query_cache_min_res_unit      | 4096    |

| query_cache_size         | 203423744 |

| query_cache_type        | ON           |

| query_cache_wlock_invalidate | OFF   |

+————————————–+—————+

查询缓存碎片率= Qcache_free_blocks / Qcache_total_blocks * 100%

如果查询缓存碎片率超过20%,可以用FLUSH QUERY CACHE整理缓存碎片,或者试试减小query_cache_min_res_unit,如果你的查询都是小数据量的话。

查询缓存利用率= (query_cache_size – Qcache_free_memory) / query_cache_size * 100%

查询缓存利用率在25%以下的话说明query_cache_size设置的过大,可适当减小;查询缓存利用率在80%以上而且Qcache_lowmem_prunes > 50的话说明query_cache_size可能有点小,要不就是碎片太多。

查询缓存命中率= (Qcache_hits – Qcache_inserts) / Qcache_hits * 100%

示例服务器查询缓存碎片率=20.46%,查询缓存利用率=62.26%,查询缓存命中率=1.94%,命中率很差,可能写操作比较频繁吧,而且可能有些碎片。

每个连接的缓冲

6)    record_buffer_size

每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,你可能想要增加该值。

默认数值是131072(128K),可改为16773120 (16M)

7)     read_rnd_buffer_size

随 机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySQL会首先扫描一遍该缓冲,以避免磁盘搜 索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。

一般可设置为16M

8)     sort_buffer_size

每个需要进行排序的线程分配该大小的一个缓冲区。增加这值加速ORDER BY或GROUP BY操作。

默认数值是2097144(2M),可改为16777208 (16M)。

9)     join_buffer_size

联合查询操作所能使用的缓冲区大小

record_buffer_size,read_rnd_buffer_size,sort_buffer_size,join_buffer_size为每个线程独占,也就是说,如果有100个线程连接,则占用为16M*100

10)  table_cache

表高速缓存的大小。每当MySQL访问一个表时,如果在表缓冲区中还有空间,该表就被打开并放入其中,这样可以更快地访问表内容。通过检查峰值时间的状态值Open_tablesOpened_tables,可以决定是否需要增加table_cache的值。如 果你发现open_tables等于table_cache,并且opened_tables在不断增长,那么你就需要增加table_cache的值了 (上述状态值可以使用SHOW STATUS LIKE ‘Open%tables’获得)。注意,不能盲目地把table_cache设置成很大的值。如果设置得太高,可能会造成文件描述符不足,从而造成性能 不稳定或者连接失败。

1G内存机器,推荐值是128-256。内存在4GB左右的服务器该参数可设置为256M或384M。

11)  max_heap_table_size

用户可以创建的内存表(memory table)的大小。这个值用来计算内存表的最大行数值。这个变量支持动态改变,即set @max_heap_table_size=#

这个变量和tmp_table_size一起限制了内部内存表的大小。如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。

12)  tmp_table_size

通过设置tmp_table_size选项来增加一张临时表的大小,例如做高级GROUP BY操作生成的临时表。如果调高该值,MySQL同时将增加heap表的大小,可达到提高联接查询速度的效果,建议尽量优化查询,要确保查询过程中生成的临时表在内存中,避免临时表过大导致生成基于硬盘的MyISAM表

mysql> show global status like ‘created_tmp%‘;

+——————————–+———+

| Variable_name             | Value |

+———————————-+———+

| Created_tmp_disk_tables | 21197  |

| Created_tmp_files   | 58  |

| Created_tmp_tables  | 1771587 |

+——————————–+———–+

每 次创建临时表,Created_tmp_tables增加,如果临时表大小超过tmp_table_size,则是在磁盘上创建临时 表,Created_tmp_disk_tables也增加,Created_tmp_files表示MySQL服务创建的临时文件文件数,比较理想的配 置是:

Created_tmp_disk_tables / Created_tmp_tables * 100% <= 25%比如上面的服务器Created_tmp_disk_tables / Created_tmp_tables * 100% =1.20%,应该相当好了

默认为16M,可调到64-256最佳,线程独占,太大可能内存不够I/O堵塞

13)  thread_cache_size

可以复用的保存在中的线程的数量。如果有,新的线程从缓存中取得,当断开连接的时候如果有空间,客户的线置在缓存中。如果有很多新的线程,为了提高性能可以这个变量值。

通过比较 Connections和Threads_created状态的变量,可以看到这个变量的作用。

默认值为110,可调优为80。

14)  thread_concurrency

推荐设置为服务器 CPU核数的2倍,例如双核的CPU, 那么thread_concurrency的应该为4;2个双核的cpu, thread_concurrency的值应为8。默认为8

15)  wait_timeout

指定一个请求的最大连接时间,对于4GB左右内存的服务器可以设置为5-10。

3.    配置InnoDB的几个变量

innodb_buffer_pool_size

对于InnoDB表来说,innodb_buffer_pool_size的作用就相当于key_buffer_size对于MyISAM表的作用一样。InnoDB使用该参数指定大小的内存来缓冲数据和索引。对于单独的MySQL数据库服务器,最大可以把该值设置成物理内存的80%。

根据MySQL手册,对于2G内存的机器,推荐值是1G(50%)。

innodb_flush_log_at_trx_commit

主 要控制了innodb将log buffer中的数据写入日志文件并flush磁盘的时间点,取值分别为0、1、2三个。0,表示当事务提交时,不做日志写入操作,而是每秒钟将log buffer中的数据写入日志文件并flush磁盘一次;1,则在每秒钟或是每次事物的提交都会引起日志文件写入、flush磁盘的操作,确保了事务的 ACID;设置为2,每次事务提交引起写入日志文件的动作,但每秒钟完成一次flush磁盘操作。

实际测试发现,该值对插入数据的速度影响非常大,设置为2时插入10000条记录只需要2秒,设置为0时只需要1秒,而设置为1时则需要229秒。因此,MySQL手册也建议尽量将插入操作合并成一个事务,这样可以大幅提高速度。

根据MySQL手册,在允许丢失最近部分事务的危险的前提下,可以把该值设为0或2。

innodb_log_buffer_size

log缓存大小,一般为1-8M,默认为1M,对于较大的事务,可以增大缓存大小。

可设置为4M或8M。

innodb_additional_mem_pool_size

该参数指定InnoDB用来存储数据字典和其他内部数据结构的内存池大小。缺省值是1M。通常不用太大,只要够用就行,应该与表结构的复杂度有关系。如果不够用,MySQL会在错误日志中写入一条警告信息。

根据MySQL手册,对于2G内存的机器,推荐值是20M,可适当增加。

innodb_thread_concurrency=8

推荐设置为 2*(NumCPUs+NumDisks),默认一般为8

refer:

mysql索引攻略

from:http://awen7916.iteye.com/blog/2266320

分布式系统中唯一 ID 的生成方法

本文主要介绍在一个分布式系统中, 怎么样生成全局唯一的 ID

一, 问题描述

在分布式系统存在多个 Shard 的场景中, 同时在各个 Shard 插入数据时, 怎么给这些数据生成全局的 unique ID?

在单机系统中 (例如一个 MySQL 实例), unique ID 的生成是非常简单的, 直接利用 MySQL 自带的自增 ID 功能就可以实现.

但在一个存在多个 Shards 的分布式系统 (例如多个 MySQL 实例组成一个集群, 在这个集群中插入数据), 这个问题会变得复杂, 所生成的全局的 unique ID 要满足以下需求:

  1. 保证生成的 ID 全局唯一
  2. 今后数据在多个 Shards 之间迁移不会受到 ID 生成方式的限制
  3. 生成的 ID 中最好能带上时间信息, 例如 ID 的前 k 位是 Timestamp, 这样能够直接通过对 ID 的前 k 位的排序来对数据按时间排序
  4. 生成的 ID 最好不大于 64 bits
  5. 生成 ID 的速度有要求. 例如, 在一个高吞吐量的场景中, 需要每秒生成几万个 ID (Twitter 最新的峰值到达了 143,199 Tweets/s, 也就是 10万+/秒)
  6. 整个服务最好没有单点

如果没有上面这些限制, 问题会相对简单, 例如:

  1. 直接利用 UUID.randomUUID() 接口来生成 unique ID (http://www.ietf.org/rfc/rfc4122.txt). 但这个方案生成的 ID 有 128 bits, 另外, 生成的 ID 中也没有带 Timestamp
  2. 利用一个中心服务器来统一生成 unique ID. 但这种方案可能存在单点问题; 另外, 要支持高吞吐率的系统, 这个方案还要做很多改进工作 (例如, 每次从中心服务器批量获取一批 IDs, 提升 ID 产生的吞吐率)
  3. Flickr 的做法 (http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/). 但他这个方案 ID 中没有带 Timestamp, 生成的 ID 不能按时间排序

在要满足前面 6 点要求的场景中, 怎么来生成全局 unique ID 呢?

Twitter 的 Snowflake 是一种比较好的做法. 下面主要介绍 Twitter Snowflake, 以及它的变种

二, Twitter Snowflake

https://github.com/twitter/snowflake

Snowflake 生成的 unique ID 的组成 (由高位到低位):

  • 41 bits: Timestamp (毫秒级)
  • 10 bits: 节点 ID (datacenter ID 5 bits + worker ID 5 bits)
  • 12 bits: sequence number

一共 63 bits (最高位是 0)

unique ID 生成过程:

  • 10 bits 的机器号, 在 ID 分配 Worker 启动的时候, 从一个 Zookeeper 集群获取 (保证所有的 Worker 不会有重复的机器号)
  • 41 bits 的 Timestamp: 每次要生成一个新 ID 的时候, 都会获取一下当前的 Timestamp, 然后分两种情况生成 sequence number:
  • 如果当前的 Timestamp 和前一个已生成 ID 的 Timestamp 相同 (在同一毫秒中), 就用前一个 ID 的 sequence number + 1 作为新的 sequence number (12 bits); 如果本毫秒内的所有 ID 用完, 等到下一毫秒继续 (这个等待过程中, 不能分配出新的 ID)
  • 如果当前的 Timestamp 比前一个 ID 的 Timestamp 大, 随机生成一个初始 sequence number (12 bits) 作为本毫秒内的第一个 sequence number

整个过程中, 只是在 Worker 启动的时候会对外部有依赖 (需要从 Zookeeper 获取 Worker 号), 之后就可以独立工作了, 做到了去中心化.

异常情况讨论:

  • 在获取当前 Timestamp 时, 如果获取到的时间戳比前一个已生成 ID 的 Timestamp 还要小怎么办? Snowflake 的做法是继续获取当前机器的时间, 直到获取到更大的 Timestamp 才能继续工作 (在这个等待过程中, 不能分配出新的 ID)

从这个异常情况可以看出, 如果 Snowflake 所运行的那些机器时钟有大的偏差时, 整个 Snowflake 系统不能正常工作 (偏差得越多, 分配新 ID 时等待的时间越久)

从 Snowflake 的官方文档 (https://github.com/twitter/snowflake/#system-clock-dependency) 中也可以看到, 它明确要求 “You should use NTP to keep your system clock accurate”. 而且最好把 NTP 配置成不会向后调整的模式. 也就是说, NTP 纠正时间时, 不会向后回拨机器时钟.

三, Snowflake 的其他变种

Snowflake 有一些变种, 各个应用结合自己的实际场景对 Snowflake 做了一些改动. 这里主要介绍 3 种.

1. Boundary flake

http://boundary.com/blog/2012/01/12/flake-a-decentralized-k-ordered-unique-id-generator-in-erlang/

变化:

  • ID 长度扩展到 128 bits:
  • 最高 64 bits 时间戳;
  • 然后是 48 bits 的 Worker 号 (和 Mac 地址一样长);
  • 最后是 16 bits 的 Seq Number
  • 由于它用 48 bits 作为 Worker ID, 和 Mac 地址的长度一样, 这样启动时不需要和 Zookeeper 通讯获取 Worker ID. 做到了完全的去中心化
  • 基于 Erlang

它这样做的目的是用更多的 bits 实现更小的冲突概率, 这样就支持更多的 Worker 同时工作. 同时, 每毫秒能分配出更多的 ID

2. Simpleflake

http://engineering.custommade.com/simpleflake-distributed-id-generation-for-the-lazy/

Simpleflake 的思路是取消 Worker 号, 保留 41 bits 的 Timestamp, 同时把 sequence number 扩展到 22 bits;

Simpleflake 的特点:

  • sequence number 完全靠随机产生 (这样也导致了生成的 ID 可能出现重复)
  • 没有 Worker 号, 也就不需要和 Zookeeper 通讯, 实现了完全去中心化
  • Timestamp 保持和 Snowflake 一致, 今后可以无缝升级到 Snowflake

Simpleflake 的问题就是 sequence number 完全随机生成, 会导致生成的 ID 重复的可能. 这个生成 ID 重复的概率随着每秒生成的 ID 数的增长而增长.

所以, Simpleflake 的限制就是每秒生成的 ID 不能太多 (最好小于 100次/秒, 如果大于 100次/秒的场景, Simpleflake 就不适用了, 建议切换回 Snowflake).

3. instagram 的做法

先简单介绍一下 instagram 的分布式存储方案:

  • 先把每个 Table 划分为多个逻辑分片 (logic Shard), 逻辑分片的数量可以很大, 例如 2000 个逻辑分片
  • 然后制定一个规则, 规定每个逻辑分片被存储到哪个数据库实例上面; 数据库实例不需要很多. 例如, 对有 2 个 PostgreSQL 实例的系统 (instagram 使用 PostgreSQL); 可以使用奇数逻辑分片存放到第一个数据库实例, 偶数逻辑分片存放到第二个数据库实例的规则
  • 每个 Table 指定一个字段作为分片字段 (例如, 对用户表, 可以指定 uid 作为分片字段)
  • 插入一个新的数据时, 先根据分片字段的值, 决定数据被分配到哪个逻辑分片 (logic Shard)
  • 然后再根据 logic Shard 和 PostgreSQL 实例的对应关系, 确定这条数据应该被存放到哪台 PostgreSQL 实例上

instagram unique ID 的组成:

  • 41 bits: Timestamp (毫秒)
  • 13 bits: 每个 logic Shard 的代号 (最大支持 8 x 1024 个 logic Shards)
  • 10 bits: sequence number; 每个 Shard 每毫秒最多可以生成 1024 个 ID

生成 unique ID 时, 41 bits 的 Timestamp 和 Snowflake 类似, 这里就不细说了.

主要介绍一下 13 bits 的 logic Shard 代号 和 10 bits 的 sequence number 怎么生成.

logic Shard 代号:

  • 假设插入一条新的用户记录, 插入时, 根据 uid 来判断这条记录应该被插入到哪个 logic Shard 中.
  • 假设当前要插入的记录会被插入到第 1341 号 logic Shard 中 (假设当前的这个 Table 一共有 2000 个 logic Shard)
  • 新生成 ID 的 13 bits 段要填的就是 1341 这个数字

sequence number 利用 PostgreSQL 每个 Table 上的 auto-increment sequence 来生成:

  • 如果当前表上已经有 5000 条记录, 那么这个表的下一个 auto-increment sequence 就是 5001 (直接调用 PL/PGSQL 提供的方法可以获取到)
  • 然后把 这个 5001 对 1024 取模就得到了 10 bits 的 sequence number

instagram 这个方案的优势在于:

  • 利用 logic Shard 号来替换 Snowflake 使用的 Worker 号, 就不需要到中心节点获取 Worker 号了. 做到了完全去中心化
  • 另外一个附带的好处就是, 可以通过 ID 直接知道这条记录被存放在哪个 logic Shard 上

同时, 今后做数据迁移的时候, 也是按 logic Shard 为单位做数据迁移的, 所以这种做法也不会影响到今后的数据迁移

from:http://blog.jobbole.com/110064/

消息系统在微服务间通讯的数据一致性

微服务是当下的热门话题,今天来聊下微服务中的一个敏感话题:如何保证微服务的数据一致性。谈到分布式事务,就避免不了CAP理论。

 

CAP理论是指对于一个分布式计算系统来说,不可能同时满足以下三点:

1. 一致性(Consistence) (等同于所有节点访问同一份最新的数据副本)

2. 可用性(Availability)(对数据更新具备高可用性)

3. 容忍网络分区(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。)

根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。以上关于CAP的理论介绍来自维基百科。同理,如何保证微服务间的数据一致性也一直是一个持续的话题,其实就是 如何在这三者中做一个权衡

就之前此公众号已经有一系列的文章来讨论,关于微服务架构下的事务一致性的话题,包括 BASE理论,两阶段提交,三阶段提交,可靠事件模式,TCC模式,补偿模式等,想进一步了解的话可以参考这里:微服务架构下的数据一致性保证(一),微服务架构下的数据一致性保证(二),微服务架构下的数据一致性保证(三)。今天只是谈一谈其中的一种场景:使用消息系统进行微服务间通讯,如何来保证微服务间的数据一致性。

1. 问题的引出:

微服务架构之数据一致性问题

这里我们先以下面的一个例子来引出问题:以 公有云市场1中的一个部署产品来说,当用户想要部署一个公有云中已有的产品,比如Redis产品,用户会先去公有云市场中找到对应的Redis产品,当用户点击发布时,市场中会进行相应的记录,同时后台有真正负责部署的模块,此处我们叫部署模块。当产品部署成功后,部署模块和市场都会进行最终状态的同步。

1、公有云市场:此处指一个简单的模型,类似阿里云的云镜像市场或亚马逊aws中的镜像市场。在云镜像市场中,用户会选择其中感兴趣的产品比如mysql,然后进行付费,发布,这样省去了用户在自己的云平台环境中手动下载安装包,然后安装,配置,启动等一系列繁琐的过程。在云平台市场中,用户所需要做的只是一些必要的配置,然后点击启动就能完成一个产品的发布。一般都是这样一个先购买镜像后启动实例的过程。

以上都是在理想的情况下进行的,大致流程如下图:

此时,市场和部署模块都是是独立的微服务,当平台用户申请开通产品后,公有云市场会先进行一系列的初始化工作,并向部署模块中发送部署请求,当部署模块部署成功或者失败后,会进行相应的记录,市场也会将状态记录到本地数据库。由于市场和部署都是以微服务形式存在,都有自己的本地事务,此时,我们已经无法通过本地事务的控制来保证操作的原子性了。那么问题就随之而来:

假如市场模块在向部署模块发送完请求之后,市场微服务出现了数据库的连接异常(比如连接数据库的网络异常,数据库漂移等),此时市场会向前端报错,提示部署过程中出错,导致部署失败,但实际上部署模块已经在后台默默的为用户开通了实例。
同样的问题也会出现在,当向部署模块发送完请求后市场微服务出现了宕机等意外情况,市场微服务的数据库中干脆直接没有保存用户的此次开通的请求,但实际上部署模块却已经在这个过程中开通过了产品实例。

如果公有云平台对用户资源的实例限制是5个,即一个用户(比如试用版的用户)最多只能开通5个产品实例,则用户此时在市场中最多只能开4个,因为有一个实例在部署模块成功部署,但是市场模块却并不清楚,此时就出现了数据不一致的严重问题。那么该如何解决此类问题呢?如何解决这类业务前后不一致的问题呢?

2. 引入消息框架,解决数据不一致问题

这里我们采用了 消息通信框架Kafka,通过事件机制来完成相应的需求。

在采用Kafka来完成消息的投递的同时,不可避免地也会面对消息的丢失的意外情况。这里我们先来看一下我们实现的主场景,然后在后面我们会接着探讨,如何在业务层面保证消息的绝对投递和消费。

消息发送方的处理

流程处理如下:

我们来分析一下此种设计如何能够满足我们的需求:

市场模块操作Product和Event是在本地事务进行,保证了本地操作的一致性。

如果开通产品时,市场领域在事件发布之前就发生了异常,宕机或者数据库无法连接,根据设计,事件发布定时器和开通产品的Service是分离操作,此时发生宕机等意外事件,并不会影响数据库中的数据,并 在下次服务器正常后事件发布定时器会去Event表中查找尚未发布的数据进行发布并更新消息状态为PUBLISHED.

如果是在更新库中的状态时发生了意外呢?此时消息已经发出到Kafka broker,则下次服务正常时,会将这些消息重新发送,但是因为有了Key的唯一性,部署模块判断这些是重复数据,直接忽略即可。

当产品部署成功后,Market事件监听器收到通知,准备更新数据库时发生了意外宕机等,下次服务正常启动后事件监听器会从上次的消息偏移量处进行监听并更新Event表。

消息接收方的处理

下面我们来看一下消息的接收方部署模块如何处理从Kafka Broker接收到的消息呢?

以下是部署模块对消息处理的流程图,此处部署模块的部署过程使用了简略的示意图。实际的场景中,部署动作以及更新状态是一个复杂的过程,同时可能依赖轮询来完成操作。

部署模块的事件监听器,在收到通知后,直接调用部署的Service,更新Deploy_table表中的业务逻辑,同时更新Event_Table中的消息状态。另一方面, 部署模块的 Event定时器,也会定时从Event_Table中读取信息并将结果发布到Kafka Broker, 市场模块收到通知后进行自己的业务操作。

至于采用这种模式的原理以及原因和市场领域类似,这里不再赘述。

3.引入补偿+幂等机制,

保证消息投递的可靠性

刚才也谈到,Kafka等市面上的大多数消息系统本身是无法保证消息投递的可靠性的。所以,我们也必须要从业务上对消息的意外情况进行保证。下面,我们探讨一下如何从业务上来保证消息投递的绝对可靠呢?

这里,我们就要引入 补偿机制+幂等操作,我们在前面的步骤中已经将Event进行了数据库持久化,我们还需要以下几个步骤来从业务上对消息的绝对可靠进行保证:

一、完善事件表字段

我们在Event表中增加两个新的字段count和updateTime,用来标识此消息发送或者重试的次数。正常情况下,count为1,表示只发送一次。

二、定时补偿加错误重试

同时 使用异常事件发布定时器,每隔2分钟(此时间只是一个示例,实际应用中应大于业务中正常业务处理逻辑的时间)去Event表中查询状态为PUBLISHED的消息,如果对应的消息记录更新时间为两分钟之前的时间,我们就悲观的认为此消息丢失,进行消息的重发,同时更新字段updateTime并将count计数加1。

三、最后一道防线:对账记录,人工干预

如果发现重发次数已经大于5,则认为此时已经无法依靠消息系统来完成此消息的投递,需要最后的一道保障,就是记录下来并在日常进行的人工对账中人工审核。

 

四、幂等去重

何为幂等呢?因为存在重试和错误补偿机制,不可避免的在系统中存在重复收到消息的场景,接口的幂等性能提高数据的一致性.在编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

由于我们的定时补偿机制,消息的消费端也应该保证部署服务的操作是幂等的,即针对同一条消息多次发送的情况,我们应该保证这个消息实际上只会执行一次。这里如果发现消息是重复发送的,则直接将数据库中的执行结果读出并将结果推送到broker中,从而保证了消息的幂等性。

现在我们来分析一下此种策略如何保证的消息的绝对投递:

每条消息的产生都会在数据库中进行记录,保证消息的不丢失。

异常消息发布定时器会定时去Event表中查看异常消息,发现没有回应的数据则认为消息丢失,进行消息补偿,重新发送,如果连续5次依然失败则认为发生了异常,进行记录并人工干预对账。

对于部署模块(消息的消费端),如果消息丢失,则市场模块就无法收到回应(对应的Event表记录中的状态也不会修改),最终效果也会同#2情况,市场模块进行消息重发,如果重发次数超出了限制则会触发对账记录的业务逻辑。

4. 总结

本文通过采用消息系统进行微服务间的通信,加上一些设计上的变更,既保证了正常情况下(99.9%以上的情况)的逻辑的正确执行,也保证了极端情况下的数据一致性,满足了我们的业务需求,同时依赖市面上消息中间件强大的功能,极大的提高了系统的吞吐量。

针对Kafka等本身不可靠的问题,我们又通过修改业务场景的设计来保证了在极端情况下消息丢失时消息的可靠性,对应的也保证了业务的可靠性。此处只是以Kafka举例,如果是顾虑Kafka的本身消息不可靠的限制,可以考虑使用RabbitMQ或RocketMQ等市面上流行的消息通信框架。

概括来说,此方案主要保证了以下4个维度的一致性:

本地事务保证了业务持久化与消息持久化的一致性。

定时器保证了消息持久与消息投递的一致性。

消息中间件保证了消息的投递和消费的一致性。

业务补偿+幂等保证了消息失败下的一致性。

使用此种方案的弊端就是编码会大幅增加,为不同的微服务间增加不少额外的工作量,同时会产生较多的中间状态。对于业务中时间要求苛刻的场景,此方案不合适。(此处却符合本文中举例的场景,因为产品的开通,需要对容器进行操作,本身就是一个耗时的过程。)

数据一致性是微服务架构设计中唯恐避之不及却又不得不考虑的话题。通过保证最终数据的一致性,也是对CAP理论的一个折衷妥协方案,关于此方案的优劣,也不能简单的一言而概之,而是应该根据场景定夺,适合的才是最好的。

所以,我们在对微服务进行业务划分的时候就尽可能的避免“可能会产生一致性问题”的设计。如果这种设计过多,也许是时候考虑改改设计了。

from:http://windpoplar.iteye.com/blog/2353205