Quantcast
Channel: IT瘾博客推荐
Viewing all 532 articles
Browse latest View live

GZIP、LZO、Zippy/Snappy压缩算法应用场景小结 - 大圆那些事 - 博客园

$
0
0

作者: 大圆那些事| 文章可以转载,请以超链接形式标明文章原始出处和作者信息

网址: http://www.cnblogs.com/panfeng412/archive/2012/12/24/applications-scenario-summary-of-compression-algorithms.html

GZIP、LZO、Zippy/Snappy是常用的几种压缩算法,各自有其特点,因此适用的应用场景也不尽相同。这里结合相关工程实践的情况,做一次小结。

压缩算法的比较

以下是Google几年前发布的一组测试数据(数据有些老了,有人近期做过测试的话希望能共享出来):

Algorithm% remainingEncodingDecoding
GZIP13.4%21 MB/s118 MB/s
LZO20.5%135 MB/s410 MB/s
Zippy/Snappy22.2%172 MB/s409 MB/s

 

 

 

 

注:来自《HBase: The Definitive Guide》

其中:

1)GZIP的压缩率最高,但是其实CPU密集型的,对CPU的消耗比其他算法要多,压缩和解压速度也慢;

2)LZO的压缩率居中,比GZIP要低一些,但是压缩和解压速度明显要比GZIP快很多,其中解压速度快的更多;

3)Zippy/Snappy的压缩率最低,而压缩和解压速度要稍微比LZO要快一些。

BigTable和HBase中压缩算法的选择

BigTable中采用的是Zippy算法,目标是达到尽可能快的压缩和解压速度,同时减少对CPU的消耗。

HBase中,在Snappy发布之前(Google 2011年对外发布Snappy),采用的LZO算法,目标和BigTable类似;在Snappy发布之后,建议采用Snappy算法(参考《HBase: The Definitive Guide》),具体可以根据实际情况对LZO和Snappy做过更详细的对比测试后再做选择。

实际项目中的实践经验

项目中使用clearspring公司开源的基数估计的概率算法: stream-lib,用于解决去重计算问题,如UV计算等,它的特点在于:

1)一个UV的计算,可以限制在一个固定大小的位图空间内完成(不同大小,对应不同的误差率),如8K,64K;

2)不同的位图可以进行合并操作,得到合并后的UV。

当系统中维护的位图越多的时候,不管是在内存中,还是在存储系统(MySQL、HBase等)中,都会占用相当大的存储空间。因此,需要考虑采取合适的算法来压缩位图。这里分为以下两类情况:

1)当位图在内存中时,此时压缩算法的选择,必须有尽可能快的压缩和解压速度,同时不能消耗过多CPU资源,因此,适合使用LZO或Snappy这样的压缩算法,做到快速的压缩和解压;

2)当位图存储到DB中时,更关注的是存储空间的节省,要有尽可能高的压缩率,因此,适合使用GZIP这样的压缩算法,同时在从内存Dump到DB的过程也可以减少网络IO的传输开销。

总结的话

以上是对GZIP、LZO、Zippy/Snappy压缩算法特点的概括比较,以及一些实践上的方法。如有不对之处,欢迎大家指正,讨论。

 


各大公司广泛使用的在线学习算法FTRL详解 - EE_NovRain - 博客园

$
0
0

 转载请注明本文链接:http://www.cnblogs.com/EE-NovRain/p/3810737.html 

  现在做在线学习和CTR常常会用到逻辑回归( Logistic Regression),而传统的批量(batch)算法无法有效地处理超大规模的数据集和在线数据流,google先后三年时间(2010年-2013年)从理论研究到实际工程化实现的 FTRL(Follow-the-regularized-Leader)算法,在处理诸如逻辑回归之类的带非光滑正则化项(例如1范数,做模型复杂度控制和稀疏化)的凸优化问题上性能非常出色,据闻国内各大互联网公司都第一时间应用到了实际产品中,我们的系统也使用了该算法。这里对FTRL相关发展背景和工程实现的一些指导点做一些介绍,凸优化的理论细节不做详细介绍,感兴趣可以去查阅相应paper,相关paper列表会在文后附上。机器学习并非本人在校时的专业方向,不过在校期间积累的基础不算太差,而且很多东西也是相通的,钻研一下基本意思都还能搞明白。当然,有不准确的地方欢迎大家讨论指正。

    本文主要会分三个部分介绍,如果对理论产生背景不感兴趣的话,可以直接看第3部分的工程实现(这一部分google13年那篇工程化的paper介绍得很详细):

  1. 相关背景:包括通用性的问题描述、批量算法、传统在线学习算法等
  2. 简单介绍与FTRL关系比较密切的Truncated Gradient、FOBOS以及RDA(Regularized Dual Averaging)等算法
  3. FTRL理论公式以及工程实现(对前因后果和理论方面不感兴趣的可以直接看这一小节的工程实现部分)

一、相关背景

  【问题描述】

     对于loss函数+正则化的结构风险最小化的优化问题(逻辑回归也是这种形式)有两种等价的描述形式,以1范数为例,分别是:

  a、无约束优化形式的soft regularization formulation:

      b、带约束项的凸优化问题convex constraint formulation:

       当合理地选择g时,二者是等价的。这里提这两种形式的问题描述,原因在于引出下面无约束优化和带约束优化问题的不同算法,对于不同的描述形式,会有一系列相关算法。

   【批量(batch)算法】

      批量算法中每次迭代对全体训练数据集进行计算(例如计算全局梯度),优点是精度和收敛还可以,缺点是无法有效处理大数据集(此时全局梯度计算代价太大),且没法应用于数据流做在线学习。这里分无约束优化形式和约束优化(与上面问题描述可以对应起来)两方面简单介绍一下一些传统批量算法。

      a、无约束优化形式:1、全局梯度下降 ,很常用的算法,就不细说了,每一步求一个目标函数的全局梯度,用非增学习率进行迭代;2、牛顿法(切线近似)、LBFGS(割线拟牛顿,用之前迭代结果近似Hessian黑塞矩阵的逆矩阵,BFGS似乎是几个人名的首字母的简称)等方法。牛顿和拟牛顿等方法一般对于光滑的正则约束项(例如2范数)效果很好,据说是求解2范数约束的逻辑回归类问题最好的方法,应用也比较广,但是当目标函数带L1非光滑、带不可微点的约束项后,牛顿类方法比较无力,理论上需要做修改。感兴趣的可以去查查无约束优化的相关数值计算的书,我也没有更深入研究相关细节,这里不做重点关注。

      b、不等式约束凸优化形式:1、传统的不等式约束优化算法内点法等;2、投影梯度下降(约束优化表示下),gt是subgradient,直观含义是每步迭代后,迭代结果可能位于约束集合之外,然后取该迭代结果在约束凸集合上的投影作为新的迭代结果(第二个公式中那个符号标识向X的投影):

 

   【在线算法】

  如上所述,批量算法有自身的局限性,而在线学习算法的特点是:每来一个训练样本,就用该样本产生的loss和梯度对模型迭代一次,一个一个数据地进行训练,因此可以处理大数据量训练和在线训练。常用的有在线梯度下降(OGD)和随机梯度下降(SGD)等,本质思想是对上面【问题描述】中的 未加和的单个数据的loss函数 L(w,zi)做梯度下降,因为每一步的方向并不是全局最优的,所以整体呈现出来的会是一个看似随机的下降路线。典型迭代公式如下:

这里使用混合正则化项: ,例如可能是1范数与2范数强凸项的混合 (后面会看到其实很多都是这种混合正则化的格式,而且是有一定直观含义的)。迭代公式中:gt是loss函数(单点的loss,未加和)的subgradient,与gt相加的那一项是混合正则化项中的第二项的梯度,投影集合C是约束空间(例如可能是1范数的约束空间),跟上面介绍的投影梯度下降类似的做法。

  梯度下降类的方法的优点是精度确实不错,但是不足相关paper主要提到两点:

  1、简单的在线梯度下降很难产生真正稀疏的解,稀疏性在机器学习中是很看重的事情,尤其我们做工程应用,稀疏的特征会大大减少predict时的内存和复杂度。这一点其实很容易理解,说白了,即便加入L1范数(L1范数能引入稀疏解的简单示例可以产看PRML那本书的第二章,我前面一篇blog的ppt里也大概提了),因为是浮点运算,训练出的w向量也很难出现绝对的零。到这里,大家可能会想说,那还不容易,当计算出的w对应维度的值很小时,我们就强制置为零不就稀疏了么。对的,其实不少人就是这么做的,后面的Truncated Gradient和FOBOS都是类似思想的应用;

  2、对于不可微点的迭代会存在一些问题,具体有什么问题,有一篇paper是这么说的:the iterates of the subgradient method are very rarely at the points of non-differentiability。我前后看了半天也没看明白,有熟悉的同学可以指导一下。

 

二、Truncated Gradient、FOBOS以及RDA(Regularized Dual Averaging)

  上面提到了,稀疏性在机器学习中是很重要的一件事情,下面给出常见的三种做稀疏解的途径:

   1)、简单加入L1范数

    –局限如上面所提,a+b两个float数很难绝对等于零,无法产生真正稀疏的特征权重
   2)、在1范数的基础上做截断,最直观没技术含量的思路,那就设定一个阈值,做截断来保证稀疏,可以结合L1范数
    –简单截断方法,每online训练K个数据截断一次,对OGD的迭代结果,每K步做一次截断置零:
    但是简单截断方法有问题:权重小,可能是确实是无用特征,还或者可能是该特征才刚被更新一次(例如训练刚开始的阶段、或者训练数据中包含该特征的样本数本来就很少),另外,简单rounding技术太aggressive了,可能会破坏在线训练算法的理论完备性。
    -简单截断基础上,不太aggressive的Truncated gradient (09年的工作),其实后面的FOBOS也可以归为这一类:
   3)、Black-box wrapper approaches:
    –黑盒的方法去除一些特征,然后重新训练的看被消去的特征是否有效。
    –需要在数据集上对算法跑多次,所以不太实用

      下面会提一下FOBOS(Forward-Backward Splitting method,其实应该叫FOBAS的,历史原因)以及RDA,因为后面的FTRL其实相当于综合了这两种算法的优点:

   a、FOBOS,google和伯克利09年的工作:

    –可以看作truncated gradient的一种特殊形式
    –基本思想:跟projected subgradient方法类似,不过将每一个数据的迭代过程,分解成一个经验损失梯度下降迭代和一个最优化问题。分解出的第二个最优化问题,有两项:第一项2范数那一项表示不能离第一步loss损失迭代结果太远,第二项是正则化项,用来限定模型复杂度抑制过拟合和做稀疏化等。这个最优化问题有一些特殊的性质,从而保证了最终结果的稀疏性和理论上的完备,具体细节感兴趣的可以查看对应paper。我这里更多关注直观含义和工程实现,忽略理论方面的内容。

   b、RDA(Regularized dual averaging),微软10年的工作,更加理论性一些,这里就直接略过去了,仅对其特点做一个简单介绍:

    –非梯度下降类方法,属于更加通用的一个primal-dual algorithmic schema的一个应用
 
    –克服了SGD类方法所欠缺的exploiting problem structure,especially for problems with explicit regularization。
 
    –能够更好地在精度和稀疏性之间做trade-off

  ok,背景和一些铺垫终于完成了,下面重点进入FTRL的部分。。。

 

三、FTRL (Follow-the-regularized-Leader)

【发展历程】

  FTRL的理论推进和工程应用首先要感谢这个人:H. Brendan McMahan, google这哥们儿护了三年的坑,直到13年工程性paper出来。发展历程和基本说明如下:

    –10年理论性paper,但未显式地支持正则化项迭代;11年证明regret bound以及引入通用的正则化项;11年另一篇的paper揭示OGD、FOBOS、RDA等算法与FTRL关系;13年的paper给出了工程性实现,并且附带了详细的伪代码,开始被大规模应用。

    –可以看作RDA和FOBOS的混合,但在L1范数或者其他非光滑的正则项下,FTRL比前两者更加有效
 
【基本思想及迭代公式】
 
 
  我简单画了个图:
  与其他在线算法的迭代公式的对比(其实OGD如何一步步到类似形式的迭代公式的过程,限于时间,这里就不细说了,最后我会附一篇自己做分享会时做的ppt,里面有,感兴趣的可以下载看看),不同的方法在这种统一的描述形式下,区别点仅在第二项和第三项的处理方式:
  
  –第一项:梯度或累积梯度;
  –第二项:L1正则化项的处理;
  –第三项:这个累积加和限定了新的迭代结果x不要离已迭代过的解太远(也即FTRL-Proximal中proximal的含义),或者离0太远(central),这一项其实也是low regret的需求
 
【工程实现】
  大家对上面那一大坨前因后果和公式都不感兴趣,ok,没关系,google非常贴心地在13年给出了一篇工程性很强的paper,其实大部分公司使用FTRL的,根本不会关心上面那一大段东西,直接按着伪代码写,调调参,看结果很不错就可以了。我们公司开始就是这么搞的,哈哈,不过人总是要有点儿好奇心的不是,深究一下前因后果和基本的理论公式感觉还是挺不同的。
  逻辑回归下的per-coordinate FTRL_Proximal的伪代码如下,在公式表达的基础上做了一些变换和实现上的trick,细节paper里有,大家在自己做实现的时候,可以在实际数据集上再并行加加速:
  四个参数的设定结合paper里的指导意见以及反复实验测试,找一组适合自己问题的参数就可以了。这里我想提一点,即上面所谓的 per-coordinate,其意思是 FTRL是对w每一维分开训练更新的,每一维使用的是不同的学习速率,也是上面代码中lamda2之前的那一项。与w所有特征维度使用统一的学习速率相比, 这种方法考虑了训练样本本身在不同特征上分布的不均匀性,如果包含w某一个维度特征的训练样本很少,每一个样本都很珍贵,那么该特征维度对应的训练速率可以独自保持比较大的值,每来一个包含该特征的样本,就可以在该样本的梯度上前进一大步,而不需要与其他特征维度的前进步调强行保持一致。
 
【工程实现中的memory saving策略】
  这里对google所提的一些节省内存的实现细节做一个介绍
  • Predict时的memory saving:
    –L1范数加策略,训练结果w很稀疏,在用w做predict的时候节省了内存,很直观,不细说了
 
  • Training时的memory saving:
  1. 在线丢弃训练数据中很少出现的特征(probabilistic feature inclusion),但是对于online set,对全数据进行pre-process查看哪些特征出现地很少、或者哪些特征无用,是代价很大的事情,所以要想训练的时候就做稀疏化,就要想一些在线的方法(FTRL分开更新的w各维度,每一维不同的步长,per-coordinate)

    1)Poisson Inclusion:对某一维度特征所来的训练样本,以p的概率接受并更新模型;

    2)Bloom Filter Inclusion:用bloom filter从概率上做某一特征出现k次才更新

  2. 浮点数重新编码

    1)  特征权重不需要用32bit或64bit的浮点数存储,存储浪费空间
    2)  16bit encoding,但是要注意处理rounding技术对regret带来的影响
  3. 训练若干相似model
    1)对同一份训练数据序列,同时训练多个相似的model
    2)这些model有各自独享的一些feature,也有一些共享的feature
    3)出发点:有的特征维度可以是各个模型独享的,而有的各个模型共享的特征,可以用同样的数据训练。
  4. Single Value Structure(据说有公司已经在实际中这么搞,大数据量下也能够保证不错的auc)
      1)多个model公用一个feature存储(例如放到cbase或redis中),各个model都更新这个共有的feature结构
      2)对于某一个model,对于他所训练的特征向量的某一维,直接计算一个迭代结果并与旧值做一个平均
  5.  使用正负样本的数目来计算梯度的和(所有的model具有同样的N和P)
  6. Subsampling Training Data
    
    1)在实际中,CTR远小于50%,所以正样本更加有价值。通过对训练数据集进行subsampling,可以大大减小训练数据集的大小
 
    2)正样本全部采(至少有一个广告被点击的query数据),负样本使用一个比例r采样(完全没有广告被点击的query数据)。但是直接在这种采样上进行训练,会导致比较大的biased prediction
    3)解决办法:训练的时候,对样本再乘一个权重。权重直接乘到loss上面,从而梯度也会乘以这个权重。
      先采样减少负样本数目,在训练的时候再用权重弥补负样本,非常不错的想法。
 
【References】
我大概标注了一下各篇paper的主要内容,感兴趣的可以有选择性地看一下,如果只关注工程实现,看标红的那篇就ok了:

[1] J. Langford, L. Li, and T. Zhang. Sparse online learning via truncated gradient.JMLR, 10, 2009. (截断梯度的paper)

[2] H. B. McMahan. Follow-the-regularized-leader and mirror descent: Equivalence theorems and L1 regularization. In AISTATS, 2011 (FOBOS、RDA、FTRL等各种方法对比的paper)

[3] L. Xiao. Dual averaging method for regularized stochastic learning and online optimization. In NIPS, 2009 (RDA方法)

[4] J. Duchi and Y. Singer. Efficient learning using forward-backward splitting. In Advances in Neural Information Processing Systems 22, pages 495{503. 2009. (FOBOS方法)

[5] H. Brendan McMahan, Gary Holt, D. Sculley, Michael Young, Dietmar Ebner, Julian Grady, Lan Nie, Todd Phillips, Eugene Davydov, Daniel Golovin, Sharat Chikkerur, Dan Liu, Martin Wattenberg, Arnar Mar Hrafnkelsson, Tom Boulos, Jeremy Kubica, Ad Click Prediction: a View from the Trenches, Proceedings of the 19th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining (KDD) (2013) (这篇是那篇工程性的paper)

[6] H. Brendan McMahan. A unied analysis of regular-ized dual averaging and composite mirror descent with implicit updates. Submitted, 2011 (FTRL理论发展,regret bound和加入通用正则化项)

[7] H. Brendan McMahan and Matthew Streeter. Adap-tive bound optimization for online convex optimiza-tion. InCOLT, 2010 (开始的那篇理论性paper)

 

后面附上我在组里分享时做的ppt,感兴趣的可以看看: http://pan.baidu.com/s/1eQvfo6e 

 

 

PMML模型文件在机器学习的实践经验 - CSDN博客

$
0
0

算法工程师和业务开发工程师,所掌握的技能容易在长期的工作中出现比较深的鸿沟,算法工程师辛辛苦苦调参的成果,业务工程师可能不清楚如何使用,如何为线上决策给予支持。本文介绍一种基于PMML的模型上线方法。

这种方案,在本次参加 QCon 大会时,Paypal的机器学习平台中也有所提及:

PMML

预测模型标记语言(Predictive Model Markup Language,PMML)是一种可以呈现预测分析模型的事实标准语言。标准东西的好处就是,各种开发语言都可以使用相应的包,把模型文件转成这种中间格式,而另外一种开发语言,可以使用相应的包导入该文件做线上预测。

不过,当训练和预测使用同一种开发语言的时候,PMML 就没有必要使用了,因为任何中间格式都会牺牲掉独有的优化。本文介绍的内容是采用Python语言做模型训练,线上采用 Java 载入模型做预测。在模型训练端,分别介绍了 python sk-learn 和 xgboost 训练模型。

正式开始之前,首先总结下模型训练和线上预测的流程图

离线部分负责模型训练和导出模型,线上导入模型并且做预测。当然特征工程部分主要做特征变换,例如 分桶,单值编码,归一化等。

SK-Learn

该开源项目支持 sk-learn模型转成PMML,项目地址: jpmml/sklearn2pmml,扩充了一个例子定义如何使用sk-learn导出带有特征工程的模型文件

heart_data=pandas.read_csv("heart.csv")#用Mapper定义特征工程mapper=DataFrameMapper([(['sbp'],MinMaxScaler()),(['tobacco'],MinMaxScaler()),('ldl',None),('adiposity',None),(['famhist'],LabelBinarizer()),('typea',None),('obesity',None),('alcohol',None),(['age'],FunctionTransformer(np.log)),])#用pipeline定义使用的模型,特征工程等pipeline=PMMLPipeline([('mapper',mapper),("classifier",linear_model.LinearRegression())])pipeline.fit(heart_data[heart_data.columns.difference(["chd"])],heart_data["chd"])#导出模型文件sklearn2pmml(pipeline,"lrHeart.xml",with_repr=True)

heart.csv定义结构如下:

sbp,tobacco,ldl,adiposity,famhist,typea,obesity,alcohol,age,chd
160,12,5.73,23.11,Present,49,25.3,97.2,52,1
144,0.01,4.41,28.61,Absent,55,28.87,2.06,63,1
118,0.08,3.48,32.28,Present,52,29.14,3.81,46,0
170,7.5,6.41,38.03,Present,51,31.99,24.26,58,1
134,13.6,3.5,27.78,Present,60,25.99,57.34,49,1
132,6.2,6.47,36.21,Present,62,30.77,14.14,45,0
142,4.05,3.38,16.2,Absent,59,20.81,2.62,38,0
114,4.08,4.59,14.6,Present,62,23.11,6.72,58,1
114,0,3.83,19.4,Present,49,24.86,2.49,29,0
132,0,5.8,30.96,Present,69,30.11,0,53,1
206,6,2.95,32.27,Absent,72,26.81,56.06,60,1
134,14.1,4.44,22.39,Present,65,23.09,0,40,1

模型文件导出后,可以把文件存储在公司内部文件存储,实在不行可以打包在 jar 包中,供线上调用。

Java端采用  jpmml/jpmml-evaluator项目,载入 PMML 文件,然后准备线上所需数据,示例代码如下:

//准备画像数据-key和原始特征一致即可
lrHeartInputMap.put("sbp", 142);
lrHeartInputMap.put("tobacco", 2);
lrHeartInputMap.put("ldl", 3);
lrHeartInputMap.put("adiposity", 30);
lrHeartInputMap.put("famhist", "Present");
lrHeartInputMap.put("typea", 83);
lrHeartInputMap.put("obesity", 23);
lrHeartInputMap.put("alcohol", 90);
lrHeartInputMap.put("age", 30);

//预测核心代码
public static void predictLrHeart() throws Exception {

    PMML pmml;
    //模型导入
    File file = new File("lrHeart.xml");
    InputStream inputStream = new FileInputStream(file);
    try (InputStream is = inputStream) {
        pmml = org.jpmml.model.PMMLUtil.unmarshal(is);

        ModelEvaluatorFactory modelEvaluatorFactory = ModelEvaluatorFactory.newInstance();
        ModelEvaluator<?> modelEvaluator = modelEvaluatorFactory.newModelEvaluator(pmml);
        Evaluator evaluator = (Evaluator) modelEvaluator;

        List<InputField> inputFields = evaluator.getInputFields();
        //过模型的原始特征,从画像中获取数据,作为模型输入
        Map<FieldName, FieldValue> arguments = new LinkedHashMap<>();
        for (InputField inputField : inputFields) {
            FieldName inputFieldName = inputField.getName();
            Object rawValue = lrHeartInputMap.get(inputFieldName.getValue());
            FieldValue inputFieldValue = inputField.prepare(rawValue);
            arguments.put(inputFieldName, inputFieldValue);
        }

        Map<FieldName, ?> results = evaluator.evaluate(arguments);
        List<TargetField> targetFields = evaluator.getTargetFields();
        //获得结果,作为回归预测的例子,只有一个输出。对于分类问题等有多个输出。
        for (TargetField targetField : targetFields) {
            FieldName targetFieldName = targetField.getName();
            Object targetFieldValue = results.get(targetFieldName);
            System.out.println("target: " + targetFieldName.getValue() + " value: " + targetFieldValue);
        }
    }
}

特征工程的说明

如流程图所示,对于原始样本数据,首先要做特征工程输入算法需要数据。特征工程可以线上线下,分开单独做,也可以用 DataFrameMapper 的方式实现特征工程,导出到模型文件中,这样线上就不需要再实现一次特征工程,但缺点是只可以使用Sklearn包中提供的方法,自拓展的方法无法支持(例如 Bucketizer)

支持的特征工程方法

Sklearn preprocessing 暂时不支持 分桶,该操作不久会被支持,已经有开源贡献者提交了 Merge Request,在Sk支持后,相信不久 JPMML项目也会支持

https://github.com/scikit-learn/scikit-learn/pull/9342

XGBoost

模型原理的理解可以参考这篇文章: GBDT的原理和应用

XGBoost 输入的文件格式是 SVMLib 文件格式。

1 3:1 10:1 11:1 21:1 30:1 34:1 36:1 40:1 41:1 53:1 58:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 105:1 117:1 124:1
0 3:1 10:1 20:1 21:1 23:1 34:1 36:1 39:1 41:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 120:1
0 1:1 10:1 19:1 21:1 24:1 34:1 36:1 39:1 42:1 53:1 56:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 106:1 116:1 122:1
1 3:1 9:1 19:1 21:1 30:1 34:1 36:1 40:1 42:1 53:1 58:1 65:1 69:1 77:1 86:1 88:1 92:1 95:1 102:1 105:1 117:1 124:1

第一列是 label,后面的是特征Id对应的值。和他一起使用的,有个特征文件,定义了特征工程方法。

0 cap-shape=bell i
1 cap-shape=conical i
2 cap-shape=convex i
3 cap-shape=flat i
4 cap-shape=knobbed i

i: indicator, 二值特征
q: quantitative, 数值特征,例如年龄等
int: int means this feature is integer value (when int is hinted, the decision boundary will be integer)

模型训练完毕后,导出模型文件 (.model) 。使用  jpmml/jpmml-xgboost 转模型文件成PMML格式。 可以和feature map文件作为输入,这样线上就不需要重新定义特征工程。

示例命令如下: java -jar target/converter-executable-1.2-SNAPSHOT.jar --model-input 0002.model --fmap-input featmap.txt --target-name mpg --pmml-output xgboost.pmml

线上使用的方法和前例基本一致,准备好原始特征值,过模型即可

xgBoostInputMap.put("mpg","1");
xgBoostInputMap.put("bruises?", "no");
xgBoostInputMap.put("odor", "creosote");
xgBoostInputMap.put("gill-spacing", "close");
xgBoostInputMap.put("gill-size", "narrow");
xgBoostInputMap.put("stalk-root", "club");
xgBoostInputMap.put("stalk-surface-below-ring", "smooth");
xgBoostInputMap.put("spore-print-color", "orange");

总结

因为PMML格式的通用性,所以会丧失特殊模型的特殊优化,例如上线XGBoost模型,也可以使用XGBoost4J,该包会链接一个本地环境编译的 .so 文件,C++实现的核心代码效率很高。不过PMML格式通用,在效率要求不高的场景可以发挥很大作用。

对于特征工程部分的计算,PMML对特征工程对支持有限,线上、线下可以单独实现,PMML文件只负责模型部分,这样既可以做丰富的特征工程,也实现了模型的共用。

传送门:https://zhuanlan.zhihu.com/p/30378213

基于DPI(深度报文解析)的应用识别 - CSDN博客

$
0
0

一、概述

1.DPI(Deep packet inspection,深度报文解析)

        所谓“深度”是和普通的报文分析层次相比较而言的,“普通报文检测”仅分析IP包4 层以下的内容,包括源地址、目的地址、源端口、目的端口以及协议类型,而DPI 除了对前面的层次分析外,还增加了应用层分析,识别各种应用及其内容,主要实现一下功能:

   1)应用分析——网络流量构成分析、性能分析、流向分析等;

   2)用户分析——用户群区分、行为分析、终端分析、趋势分析等;

   3)网元分析——根据区域属性(市、区、街道等)、基站负载情况进行分析等;

   4)流量管控——P2P限速、保证QoS、带宽保障、网络资源优化等;

   5)安全保障——DDoS攻击、数据广播风暴、防范恶意病毒攻击等。

2.网络应用的大致分类

           现在的上的应用不计其数,但大众常用的网络应用可以进行穷举。
       据我的了解应用识别做的最好的就是华为,号称能识别4000种应用。协议分析是很多防火墙公司(华为、网神、天融信、网康等)的基础模块,也是很重要的模块,支撑着其他的功能模块的实现,精准的应用识别,大大提高产品的性能和可靠性。像我现在在做的基于网络流量特征识别恶意软件的建模,精准的,大量的协议识别也是很重要的一环。公司出口的流量剔除掉常用应用的网络流量,剩下的流量占比会很少,更好的进行恶意软件的分析,报警。
根据我的经验将现有常用的应用根据作用进行分类:
     ps:根据个人对应用的理解进行应用分类,大家有什么好的建议欢迎留言提议
    1、电子邮件类
    2、视频类
    3、游戏类
    4、办公OA类
    5、软件更新类
    6、金融类(银行、支付宝)
    7、股票类
    8、社交通讯类(IM软件)
    9、Web浏览(借助url可能会更好的识别)
   10、下载工具类(网盘、P2P下载、BT类相关)

      P2P下载是很蛋疼的硬骨头!
      P2P下载相关:迅雷、Flashget-2.4/3.4、EasyMule-1.1.11、QQDownload、Vagaa-2.6.7.1、Baidu下吧-4.0.0.1
      BT类相关:BitComet/BitTorrent、gnutella、KAZAA、directconnect、ARES、SOUL、WINMX、APPLE、DC、MUTE、XDCC、WASTE

    3.传统的协议识别


      基于端口的协议识别


        网络中多种应用层协议可以同时运行在同一台计算机的同一个 IP 地址上。由于 IP 地址与网络应用程序的关系是一对多的关系,所以主机需要通过端口号来区分不同的网络服务。


      对于端口号的分配,有两种基本的方式:
       1)全局分配,即由一个公认的中央机构(LANA)统一进行分配。虽然这样容易确定应用程序和端口的对应关系,但不能适应大量且迅速变化的端口使用环境;
       2)本地分配,即动态分配。当某应用程序进程需要访问网络时,主机操作系统临时为该进程分配一个本地唯一的端口号。但本地分配方式使得其它主机无法获知分配情况,无法建立通信。

     Internet 同时采用上述两种分配方式,将端口分成两部分:
       1)保留端口,以全局方式(IANA)进行分配。这样每一个标准的服务器应用程序都拥有一个或多个全局的公认端口号;
       2)另一部分是自由端口,以本地方式进行分配。当某应用程序想要通过网络和远地程序通信之前,它会首先申请自由端口号与远地程序通信,主机操作系统临时为该进程分配一个本地唯一的端口号。

下图是常用的保留端口,部分还有一定的参考价值。



       传统上一直是基于端口映射机制对应用层协议进行识别。随着越来越多的网络协议不使用固定的端口进行通信,基于报文端口的协议识别受到很大限制,准确性受到很大挑战。但由于基于公知端口进行协议识别操作简单,识别速度快,现在的带宽越来越大,在识别 DNS、SMTP、POP3 等目前端口比较固定的传统协议时依然有一定的价值。

       基于测度的协议识别


       基于测度的协议识别根据各协议产生的流测度的差异识别应用层协议。
       基于测度识别协议无需分析报文体的内容,只要根据报文头中的域值、报文大小、报文间隙等特征分析流量所属的应用类型。例如,Web 浏览产生的流量一般为短流小报文,而各种 P2P 协议的流量一般为长流大报文。基于测度的协议识别一般采用机器学习的方式,利用已经按协议类型分类的报文来训练系统,使其把握该类应用的流测度特征以识别新的流量。

温超、郑雪峰等提出通过网络流量信息识别 P2P 协议。基于流量分析的 P2P协议识别方法,依据以下四个特征识别 P2P 协议:
        1)P2P 主机的上下行流量基本相当;
2)P2P 主机连接的其它主机的数量较多;
        3)P2P 主机既为服务器又为客户端;
4)P2P主机的监听端口的特点与其它协议不同。
虽然基于流量分析的识别方法只需对数据包的头部信息进行检测,不需要检测数据包的负载,具有简单、高效的特点。但是只能识别出网络流量是否为 P2P 协议,无法识别出具体是哪种 P2P 协议或是哪种非 P2P 协议,具有应用的局限性。PS:有些p2p下载应用也是可以识别的,后续的文章可以分析一下试试。

      基于负载的协议识别(基于正则表达式)


       基于负载的协议识别,对应用层协议交互过程中产生报文的内容进行分析,找出不同于其它协议的模式特征,根据各协议特有的模式特征确定流量所属协议类型。基于负载的协议识别主要有采用固定字符串和正则表达式来表示协议特征两种方式。这也是现在最常用的方式,大部门应用可以利用这种方式识别。

        采用固定字符串的方式过于笨拙,在这不在做介绍,其实可以把它理解成基于正则表达式的子集。正则表达式比固定字符串具有更强的表达能力和更好的灵活性,采用正则表达式代替固定字符串表示协议的特征成为研究的热点。

       正则表达式:又称正规表示法、常规表示法( 英语:Regular Expression,在代码中常简写为regex、regexp或RE)。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的 字符串

      正则表达式语法学习网站:http://msdn.microsoft.com/zh-cn/library/ae5bf541(VS.80).aspx





     上面说的相对比较全的正则表达式,下面这张表相对来说比较常用的:




杜绝假死,Tomcat容器做到自我保护,设置最大连接数(服务限流:tomcat请求数限制) - Ruthless - 博客园

$
0
0

为了确保服务不会被过多的http长连接压垮,我们需要对tomcat设定个最大连接数,超过这个连接数的请求会拒绝,让其负载到其它机器。达到保护自己的同时起到连接数负载均衡的作用。

一、解决方案:修改tomcat配置文件,修改最大连接数(增大)
修改server.xml配置文件,Connector节点中增加acceptCount和maxThreads这两个属性的值,并且使acceptCount大于等于maxThreads:

<Connectorport="8080"protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443"        maxConnections="800"acceptCount="500"maxThreads="400"/>

 

二、实验验证:
maxThreads:tomcat起动的最大线程数,即同时处理的任务个数,默认值为200
acceptCount:当tomcat起动的线程数达到最大时,接受排队的请求个数,默认值为100

这两个值(acceptCount+maxThreads)如何起作用,请看下面三种情况
情况1:接受一个请求,此时tomcat起动的线程数没有到达maxThreads,tomcat会起动一个线程来处理此请求。
情况2:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,tomcat会把此请求放入等待队列,等待空闲线程。
情况3:接受一个请求,此时tomcat起动的线程数已经到达maxThreads,等待队列中的请求个数也达到了acceptCount,此时tomcat会直接拒绝此次请求,返回connection refused

同时加上maxConnections
原来tomcat最大连接数取决于maxConnections这个值加上acceptCount这个值,在连接数达到了maxConenctions之后,tomcat仍会保持住连接,但是不处理,等待其它请求处理完毕之后才会处理这个请求。

三、总结:
tomcat能支持最大连接数由maxConnections加上acceptCount来决定。同时maxThreads如何设定?

一般的服务器操作都包括两方面:1计算(主要消耗cpu),2等待(io、数据库等)

第一种极端情况,如果我们的操作是纯粹的计算,那么系统响应时间的主要限制就是cpu的运算能力,此时maxThreads应该尽量设的小,降低同一时间内争抢cpu的线程个数,可以提高计算效率,提高系统的整体处理能力。

第二种极端情况,如果我们的操作纯粹是IO或者数据库,那么响应时间的主要限制就变为等待外部资源,此时maxThreads应该尽量设的大,这样 才能提高同时处理请求的个数,从而提高系统整体的处理能力。此情况下因为tomcat同时处理的请求量会比较大,所以需要关注一下tomcat的虚拟机内存设置和linux的open file限制。

现实应用中,我们的操作都会包含以上两种类型(计算、等待),所以maxThreads的配置并没有一个最优值,一定要根据具体情况来配置。

最好的做法是:在不断测试的基础上,不断调整、优化,才能得到最合理的配置。
acceptCount的配置,我一般是设置的跟maxThreads一样大,这个值应该是主要根据应用的访问峰值与平均值来权衡配置的。
如果设的较小,可以保证接受的请求较快响应,但是超出的请求可能就直接被拒绝
如果设的较大,可能就会出现大量的请求超时的情况,因为我们系统的处理能力是一定的。

Mosquitto搭建Android推送服务(三)Mosquitto集群搭建 - 梧桐雨的笑容 - 博客园

$
0
0

文章钢要:

1、进行双服务器搭建

2、进行多服务器搭建

一、Mosquitto的分布式集群部署

如果需要做并发量很大的时候就需要考虑做集群处理,但是我在查找资料的时候发现并不多,所以整理了一下,搭建简单的Mosquitto集群模式。

首先集群需要2台以上的Mosquitto服务器。安装方式同上。

先了解下Mosquitto集群模式的逻辑图,如下:

 

可以看出,无论在那台服务器中订阅了信息,无论在那台服务器上发布信息,订阅者都可以收到发布的信息。那么下一步我们着手搭建集群服务器,为了方便只演示2台服务器之间的集群搭建。

集群部署有一个专有名词叫做“桥接”,实现桥接的方式需要修改config.mk与mosquitto.conf文件。值得说明的是如果有10台服务器做Mosquitto集群,每台服务器上将桥连接打开,然后只需要更改一台服务器上的Mosquitto.conf文件即可,其他服务器的Mosquitto.conf文件不需要做任何改动。大大方便了集群的维护。如果有新的服务器加入或删除只需要修改主服务器的Mosquitto.conf即可。

1、开启服务器桥连接

进入安装目录

cd mosquitto-1.4.9/

打开config.mk文件

vi config.mk

找到WITH_BRIDGE:=yes 将签名的“#”号去掉开启桥连接模式。(默认是开启的,为了无误查看一下)

2、配置Mosquitto.conf的桥连接属性

进入etc目录,并且打开Mosquitto.conf文件

cd /etc/mosquitto/

vi mosquitto.conf

找到Bridges节点,在下面加入如下代码:

connection mytest
address 10.19.22.53:1883
topic room1/# both 2 sensor/ myhouse/
bridge_protocol_version mqttv311
notifications true
cleansession true
try_private true
start_type automatic

---------------------------------------------------------------------------------

connection 连接名称,可以随便取

address 连接的另外服务器地址和端口号,如果有多台服务器,可以写多个address

topic 主题名称,“#”为通配符,表示发布端可以在room1/后面接任何文字

both 服务质量,2代表只有一次(可以查看 第一篇博文对MQTT的详细介绍)

sensor/  本地前缀标识,可以随便命名

myhouse/ 远程前缀标识,可以随便命名

bridge_protocol_version mqttv311 桥连接协议版本MQTT3.11

notifications  是否发布桥接的状态信息

cleansession  桥接断开时,是否清除远程服务器中的消息

start_type 桥接模式,目前有三种:automatic、lazy、once

设置好之后保存退出。

 

3、开启服务器

第一步先确保从服务器先开启,第二步重新启动主服务器的Mosquitto服务。如果配置无误主服务器在开启的时候,会自动连接所有从服务器,显示如下:

Mytest实在Mosquitto.conf配置中设定的我的连接名称,后面是从服务器的地址与端口号

如上图所示,主服务器与从服务器已经桥接完成。

 

4、验证发布/订阅

集群的特点在任何服务器上都可以订阅与发布,并且订阅者可以收到在任何服务器中发送去信息。

测试场景:在从服务器中订阅一条信息,在主服务器中发布一条信息,从服务器的订阅者可以收到从主服务器中发布的消息。

(1)在从服务器中键入一下命令:

mosquitto_sub -t myhouse/room1/#
注意:myhouse/ 是编写Mosquitto.conf中topic的远程前缀。
room1/#是topic中的订阅主题
(2)在主服务器中键入一下命令:
mosquitto_pub -t sensor/room1/temperature -m '26.3'
注意:sensor/ 是编写Mosquitto.conf中topic的本地前缀。
room1/ 是topic中的订阅主题
temperature  相当与“#”通配符 
如果Mosquitto.conf配置无误,并且本地前缀与远程前缀拼写正确,那么会显示如下图信息,表示集群配置成功
在从服务器订阅,在主服务器发送,从服务器订阅者收到信息:
 

 

以上双集群配置完成。也比较简单。
下面对多集群配置进行阐述。

二、多集群部署

配置3台服务集群与3+n台理论一样,所以这里配置3台服务集群作为演示。

1、安装服务器

首先在上述2台服务器基础上,再增加一台服务器,配置步骤请参考 第二篇博文

2、配置服务器

假设有3台服务器分别是

192.168.0.53

192.168.0.88

192.168.0.89

其中53为主服务器,88与89为从服务器。

所以在88与89上只需要正常安装Mosquitto服务即可,其他不需要做任何配置。

重点还是在53的mosquitto.conf中配置。

依然打开mosquitto.conf,找到Bridge节点,重新复习一下节点中每个配置项的含义

#connection <name>
#address <host>[:<port>][<host>[:<port>]]
#topic <topic> [[[out | in | both] qos-level] local-prefix remote-prefix]

笔者一开始错误的认为红色字体部分是配置第二台服务器使用的,但是笔者错了。每一个connection只能有一个IP地址,address红色的部分是留有多个ip的保存。(貌似是对前地址的一个备份,如果前地址服务器挂了可以立马接手备用服务器,笔者尚未证实)

如果想增加一台服务器只需要重新添加connection、address、topic节点即可。因此Bridge节点变成下面形式:

connection mytest
address192.168.0.88:1883topic room1/# both2sensor/myhouse/connection mytest2
address192.168.0.89:1883topic room1/# both2sensor/ myhouse/bridge_protocol_version mqttv311
notifications true
cleansession true
try_private true
start_type automatic

红色部分为新增加的服务器。重启Mosquitto服务器即可。

3、测试订阅、发布

测试理论与第一节类似:

分别在88与89服务器中输入mosquitto_sub -t myhouse/room1/#  订阅信息

在53服务器中输入mosquitto_pub -t sensor/room1/temperature -m '26.3' 发布消息

同事88与89都会收到“26.3”这条信息。如果只有一台服务器收到说明配置有问题。

 

以上配置完成了对Mosquitto服务器的基础配置

下一步对服务器的用户登录与权限进行配置。

 

 

 

 

 

MQTT简介Mosquitto桥接及集群环境搭建 - CSDN博客

$
0
0

MQTT v5.0草案中文翻译: github.com/hui6075/mqtt_v5

原创文章如转载,请注明出处(http://blog.csdn.net/hui6075/)。

目录:
MQTT协议简介
Mosquitto桥接模式
Mosquitto集群模式

MQTT协议简介

MQTT是IBM为物联网等环境定义的一套应用层即时通信协议,通过消息中间件,提供订阅/发布方式通过“主题”为不同设备之间的通信提供解耦。

类似的协议还有XMPP、COAP等,但MQTT协议由于信令种类少、控制信息少、信息承载率高,因而对设备的处理能力和网络环境要求比较低。试想,让一个200MHz、64K内存的嵌入式模块去解析HTTP/JASON/XML字符串,可能还没等到数据包解析完,下一条数据就到了,哈哈。当然,也有很多人拿MQTT作为安卓推送协议,也是非常能提高用户体验的,比如MQTT协议里的保留消息,可以让手机收到开机之前系统或其他设备就已经发布的一些消息。

通常称消息中间件为broker,支持MQTT的broker有很多,开源的比如基于C语言的 Mosquitto,基于Erlang的 EMQRabbitMQ+插件,基于Java的 ActiveMQ,甚至基于Node.js的 Mosca。我自己尝试过Mosquitto和RabbitMQ,Mosquitto完全实现了MQTT协议的所有内容,而RabbitMQ对MQTT的支持并不完善,比如不支持QoS=2的消息以及保留消息。另外中国人自己开发的 EMQ也是非常棒的MQTT broker,支持集群,消息持久化。

Mosquitto桥接模式

我的生产环境使用的broker是 Mosquitto,老实说Mosquitto最初设计可能也是为了运行在嵌入式平台,使用的IO模型、定时器都比较简陋,通常需要优化才能支持大流量高并发的业务场景,而且流量太大的情况下Mosquitto也支持通过bridge(桥接)的方式连接多台broker,通常选取一台broker作为bridge节点,在其配置文件(mosquitto.conf)中添加桥接选项:

connection bridge1
address 192.168.1.102:1883
topic # both 0

connection bridge2
address 192.168.1.103:1883
topic /sensor/temperature in 1

其中bridge1/bridge2是为其他broker起的名字,地址是其他broker的IP和端口,topic指定了允许本地broker和对方broker之间传递的消息主题,配成#则所有主题消息都将被传递。both/in/out是指允许的消息方向,in是指此主题消息的转发方向只能是从对方broker到本地broker,不能是从本地broker到对方broker,这样如果有客户端连接到本地broker订阅/sensor/temperature这个主题的话,发布到对方broker上的/sensor/temperature主题消息本地客户端也可以收到,反之则收不到。

通过以上介绍,可以知道bridge方式下主题配置非常不灵活,特别是有些场景下消息发布客户端和订阅客户端之间通过MAC地址、UUID等作为主题名进行通信,根本无法手工为每对发布/订阅设备进行配置。

Mosquitto集群模式

Mosquitto也有基于二次开发的 集群实现

集群方式则是动态订阅,每个broker既是发布者也是订阅者,对于连到本地broker的客户端订阅的主题,本地broker会向集群中其他broker进行订阅,因此无需事先动态配置主题过滤器。此外,集群中所有broker都处于同等地位,因此不存在单点失效的问题。配置起来也非常简单,在每个机器的mosquitto.conf中:

node_name node1
node_address 192.168.1.101:1883

node_name node2
node_address192.168.1.102:1883

node_name node3
node_address 192.168.1.103:1883

编译mosquitto时把config.mk中的WITH_BRIDGE注释掉,把WITH_CLUSTER:=yes取消注释,然后make && make install安装mosquitto,并在所有机器启动mosquitto即可。

功能测试:

在主机1上订阅主题"clustermsg",在主机2上发布主题为"clustermsg"的消息,主机1上可以收到此消息。


benchmark:

在1台broker、2台broker、3台broker、4台broker、9台broker的情况下分别测试集群的整体吞吐率,从1台broker到3台broker时整体吞吐率逐渐增长,增长系数大概在0.8左右,broker超过3台时整体吞吐率由于压测工具的瓶颈而停止增长。


测试参数说明:消息长度固定1000字节,n10k指每个客户端发送10k个消息,c100指一共100个客户端,QoS选取为2。

集群的原理和更详细的性能测试报告,可见 mosquitto cluster@github项目的readme.md。

MQTT--topic(主题通配符)设计 - CSDN博客

$
0
0

参考博客: http://blog.csdn.net/amwha/article/details/74364175

 主题的设计是非常重要的,首先需要了解的就是MQTT主题过滤规则。


1、topic

 定阅与发布必须要有主题,只有当定阅了某个主题后,才能收到相应主题的payload,才能进行通信。

2、 主题层级分隔符—“/”

 主题层级分隔符使得主题名结构化。如果存在分隔符,它将主题名分割为多个主题层级。斜杠(‘/’ U+002F)用于分割主题的每个层级,为主题名提供一个分层结构。当客户端订阅指定的主题过滤器包含两种通配符时,主题层级分隔符就很有用了。主题层级分隔符可以出现在主题过滤器或主题名字的任何位置。相邻的主题层次分隔符表示一个零长度的主题层级。

如主题:

room212/electric
room212/tv/contrl/sensor
room212/tv/contrl/lightroom212/air/sensor

3、 多层通配符—-“#”

“#”是用于匹配主题中任意层级的通配符。多层通配符表示它的父级和任意数量的子层级。多层通配符必须位于它自己的层级或者跟在主题层级分隔符后面。不管哪种情况,它都必须是主题过滤器的最后一个字符 .

例如,如果客户端订阅主题 “china/xiangtan/#”,它会收到使用下列主题名发布的消息:

china/xiangtan
china/xiangtan/yuhu
china/xiangtan/yuetan/hnie
china/xiangtan/jiuhua/jiakao/kemusan

定阅主题示例

school/#//也匹配单独的 “school” ,因为 # 包括它的父级。#//是有效的,会收到所有的应用消息。school/teacher/#//有效的。school/teacher#//无效的。school/teacher/#/lever//无效的,必须是主题过滤器的最后一个字符

4、 单层通配符—-“+”

加号是只能用于单个主题层级匹配的通配符。在主题过滤器的任意层级都可以使用单层通配符,包括第一个和最后一个层级。然而它必须占据过滤器的整个层级 。可以在主题过滤器中的多个层级中使用它,也可以和多层通配符一起使用。

china/+ 只能匹配 china/guangzhou

china/+/+/zhongshanlu 能匹配china/guangzhou/tianhe/zhongshanlu和china/shenzhen/nanshan/zhongshanlu

5、 通配符 —-“$”

通配符“$”表示匹配一个字符,只要不是放在主题的最开头,即:

$xx/$xx/xx$

其它情况下都表示匹配一个字符。

如果客户端想同时接受以 “SYS/”开头主题的消息和不以 开头主题的消息, 它需要同时订阅 “#” 和 ““$SYS/#”。

6、 总结

  • 1、所有的主题名和主题过滤器必须至少包含一个字符
  • 2、主题名或主题过滤器以前置或后置斜杠 “/” 区分
  • 3、只包含斜杠 “/” 的主题名或主题过滤器是合法的
  • 4、主题名和主题过滤器是 UTF-8 编码字符串, 它们不能超过 65535 字节
  • 5、主题名和主题过滤器是区分大小写的

主题层级分隔符 / : 用于分割主题层级,/分割后的主题,这是消息主题层级设计中很重要的符号。 比方说: aaa/bbb和 aaa/bbb/ccc 和aaa/bbb/ccc/ddd ,这样的消息主题格式,是一个层层递进的关系,可通过多层通配符同时匹配两者,或者单层通配符只匹配一个。 这在现实场景中,可以应用到:公司的部门层级推送、国家城市层级推送等包含层级关系的场景。

单层通配符 +: 单层通配符只能匹配一层主题。比如: aaa/+ 可以匹配 aaa/bbb ,但是不能匹配aaa/bbb/ccc。 单独的+号可以匹配单层的所有推送

多层通配符 #: 多层通配符可以匹配于多层主题。比如: aaa/# 不但可以匹配aaa/bbb,还可以匹配aaa/bbb/ccc/ddd。 也就是说,多层通配符可以匹配符合通配符之前主题层级的所有子集主题。单独的#匹配所有的消息主题.

注: 单层通配符和多层通配符只能用于订阅(subscribe)消息而不能用于发布(publish)消息,层级分隔符两种情况下均可使用。


在Redis Sentinel环境下,jedis该如何配置 - iVictor - 博客园

$
0
0

在Redis主从复制架构中,如果master出现了故障,则需要人工将slave提升为master,同时,通知应用侧更新master的地址。这样方式比较低效,对应用侧影响较大。

 

为了解决这个问题,Redis 2.8中推出了自己的高可用方案Redis Sentinel。

 

Redis Sentinel架构图如下:

 

默认情况下,每个Sentinel节点会以每秒一次的频率对Redis节点和其它的Sentinel节点发送PING命令,并通过节点的回复来判断节点是否在线。

如果在down-after-millisecondes毫秒内,没有收到有效的回复,则会判定该节点为主观下线。

如果该节点为master,则该Sentinel节点会通过sentinel is-master-down-by-addr命令向其它sentinel节点询问对该节点的判断,如果超过<quorum>个数的节点判定master不可达,则该sentinel节点会将master判断为客观下线。

这个时候,各个Sentinel会进行协商,选举出一个领头Sentinel,由该领头Sentinel对master节点进行故障转移操作。

故障转移包含如下三个操作:

1. 在所有的slave服务器中,挑选出一个slave,并将其转换为master。

2. 让其它slave服务器,改为复制新的master。

3. 将旧master设置为新master的slave,这样,当旧的master重新上线时,它会成为新master的slave。

 

以上的所有操作对业务都是透明的,当新的master上线后,Sentinel会自动将这个变化实时通知给业务方。

 

那么,业务侧又该如何配置,才能扑捉到这个变化呢?

其实,这个主要取决于Redis客户端工具是否支持Redis Sentinel,对于支持的客户端工具来说,如Jedis,

只需将连接字符串设置为Sentinel地址即可。

 

下面,给出了一个测试代码,并模拟了master发生故障,业务侧是如何处理的?

 

代码如下:

复制代码
package com.victor_02;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;

public class JedisSentinelTest {

    public static void main(String[] args) throws Exception {
        // TODO Auto-generated method stub

        Set<String> sentinels = new HashSet<String>();
        sentinels.add("192.168.244.10:26379");
        sentinels.add("192.168.244.10:26380");
        sentinels.add("192.168.244.10:26381");

        JedisSentinelPool jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels);
        Jedis jedis = null;   
while (true) { Thread.sleep(1000); try { jedis = jedisSentinelPool.getResource(); Date now = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); String format_now = dateFormat.format(now); jedis.set("hello", "world"); String value = jedis.get("hello"); System.out.println(format_now + ' ' + value);
} catch (Exception e) { System.out.println(e); } finally { if (jedis != null) try { jedis.close(); } catch (Exception e) { System.out.println(e); } } } } }
复制代码

 

模拟故障:

# ./redis-cli -p 6380
127.0.0.1:6380> shutdown

 

上述代码的输出如下:

复制代码
四月 16, 2017 10:39:44 下午 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Trying to find master from available Sentinels...
四月 16, 2017 10:39:44 下午 redis.clients.jedis.JedisSentinelPool initSentinels
信息: Redis master running at 192.168.244.10:6380, starting Sentinel listeners...
四月 16, 2017 10:39:44 下午 redis.clients.jedis.JedisSentinelPool initPool
信息: Created JedisPool to master at 192.168.244.10:6380
2017/04/16 22:39:45 world
2017/04/16 22:39:46 world
2017/04/16 22:39:47 world
2017/04/16 22:39:48 world
2017/04/16 22:39:49 world
2017/04/16 22:39:50 world
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Software caused connection abort: recv failed
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
四月 16, 2017 10:40:21 下午 redis.clients.jedis.JedisSentinelPool initPool
信息: Created JedisPool to master at 192.168.244.10:6381
四月 16, 2017 10:40:21 下午 redis.clients.jedis.JedisSentinelPool initPool
信息: Created JedisPool to master at 192.168.244.10:6381
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
2017/04/16 22:40:22 world
2017/04/16 22:40:23 world
2017/04/16 22:40:24 world
2017/04/16 22:40:25 world
2017/04/16 22:40:26 world
复制代码

 

从上述输出可以看出,在master发生故障前,业务侧最后一次正常处理(22:39:50),到再次正常处理是(22:40:22),中间经过了32s。

而其中30s被用来判断master节点是否主观下线(由down-after-milliseconds来指定),整个切换的过程还是比较高效的。

Kylin 大数据时代的OLAP利器 - CSDN博客

$
0
0

Olap简介

OLAP的历史与基本概念

Olap全称为在线联机分析应用,是一种对于多维数据分析查询的解决方案。 典型的Olap应用场景包括销售、市场、管理等商务报表,预算决算,经济报表等等。

最早的Olap查询工具是发布于1970年的Express,然而完整的Olap概念是在1993年由关系数据库之父 Edgar F.Codd 提出,伴随而来的是著名的“twelve laws of online analytical processing”. 1998年微软发布 Microsoft Analysis Services, 并且在早一年通过OLE DB for OLAP API引入MDX查询语言,2001年微软和Hyperion发布的XML for Analysis 成为了事实上的OLAP查询标准。 如今,MDX已成为与SQL旗鼓相当的OLAP 查询语言,被各家OLAP厂商先后支持。

Olap Cube是一种典型的多维数据分析技术,Cube本身可以认为是不同维度数据组成的dataset,一个Olap Cube 可以拥有多个维度(Dimension),以及多个事实(Fact or Measure)。用户通过Olap工具从多个角度来进行数据的多为分析。通常认为Olap包括三种基本的分析操作: 上卷(rollup)、下钻(drill down)、切片切块(slicing and dicing),原始数据经过聚合以及整理后变成一个或多个维度的视图。

ROLAP和MOLAP

传统OLAP根据数据存储方式的不同分为ROLAP(relational olap)以及MOLAP(multi-dimension olap)

ROLAP 以关系模型的方式存储用作多为分析用的数据,优点在于存储体积小,查询方式灵活,然而缺点也显而易见,每次查询都需要对数据进行聚合计算,为了改善短板,ROLAP使用了列存、并行查询、查询优化、位图索引等技术

MOLAP 将分析用的数据物理上存储为多维数组的形式,形成CUBE结构。维度的属性值映射成多维数组的下标或者下标范围,事实以多维数组的值存储在数组单元中,优势是查询快速,缺点是数据量不容易控制,可能会出现维度爆炸的问题

大数据时代Olap的挑战

近二十年内,ROLAP技术随着MPP并行数据库技术的发展,尤其是列存技术的支持下,实现了分析能力大幅度的跨越提升,同时伴随着内存成本的进一步降低,单节点内存扩展性增强,集群单节点的查询性能实现了飞跃,内存数据库的实用性跨上了一个新台阶,这些技术进步共同作用的结果是类似的技术基本覆盖了TB级别的数据分析需求。 Hadoop以及相关大数据技术的出现提供了一个几近无限扩展的数据平台,在相关技术的支持下,各个应用的数据已突破了传统OLAP所能支持的容量上界。每天千万、数亿条的数据,提供若干维度的分析模型,大数据OLAP最迫切所要解决的问题就是大量实时运算导致的响应时间迟滞。

2. Apache Kylin 大数据下的Olap解决方案

Kylin的背景

Kylin 是一个Hadoop生态圈下的MOLAP系统,是ebay大数据部门从2014年开始研发的支持TB到PB级别数据量的分布式Olap分析引擎。其特点包括:

  • 可扩展的超快的OLAP引擎
  • 提供ANSI-SQL接口
  • 交互式查询能力
  • MOLAP Cube 的概念
  • 与BI工具可无缝整合

Kylin典型的应用场景如下:

*用户数据存在于Hadoop HDFS中,利用Hive将HDFS文件数据以关系数据方式存取,数据量巨大,在500G以上*每天有数G甚至数十G的数据增量导入*有10个以内较为固定的分析维度

Kylin的核心思想是利用空间换时间,在数据ETL导入OLAP引擎时提前计算各维度的聚合结果并持久化保存, 由于Kylin查询方面制定了多种灵活的策略,进一步提高空间的利用率,使得这样的平衡策略在应用中是值得采用的。

kylin的总体架构

Kylin 作为一个Olap引擎完成了从数据源抓取数据,ETL到自己的存储引擎,提供REST服务等一系列工作,其架构如图所示: Alt pic

Kylin 的生态圈包括:

  • Kylin Core: Kylin 引擎的框架,查询、任务、以及存储引擎都集中于此,除此之外还包括一个REST 服务器来响应各种客户端请求。
  • 扩展插件: 各种提供额外特性的插件,如安全认证、SSO等
  • 完整性组件: Job管理器,ETL、监控以及报警
  • 交互界面: 基于Kylin Core之上的用户交互界面
  • 驱动: 提供了JDBC以及ODBC的连接方式

kylin Cube 多维数据的计算

Kylin的多维计算主要是体现在OLAP Cube的计算。Cube由多个Cuboid组合而成,Cuboid上的数据是原始数据聚合的数据,因此创建Cube可以看作是在原始数据导入时做的一个预计算预处理的过程。Kylin的强大之处在于充分利用了Hadoop的MapReduce并行处理的能力,高效处理导入的数据。

Kylin的数据来自于Hive,并作为一个Hive的加速器希望最终的查询SQL类似于直接在Hive上查询。因此Kylin在建立Cube的时候需要从Hive获取Hive表的元数据。虽然有建立Cube的过程,但是并不想对普通的查询用户暴露Cube的存在。

Kylin创建Cube的过程如下:

Alt pic

  1. 根据Cube定义的事实表以及维度表,利用Hive创建一张宽表
  2. 抽取事实表上的维度的distinct值,将事实表上的维度以字典树方式压缩编码成目录,将维度表以字典树的方式编码
  3. 利用MapReduce从第一步得到的宽表文件作为输入,创建 N-Dimension cuboid,然后每次根据前一步的结果串行生成 N-1 cuboid, N-2 cuboid ... 0-Cuboid
  4. 根据生成的Cuboid数据量计算HTable的Region分割策略,创建HTable,将HFile导入进来

Kylin与传统的OLAP一样,无法应对数据Update的情况(更新数据会导致Cube的失效,需要重建整个Cube)。面对每天甚至每两个小时这样固定周期的增量数据,Kylin使用了一种增量Cubing技术来进行快速响应。

Kylin的Cube可以根据时间段划分成多个Segment。在Cube第一次Build完成之后会有一个Segment,在每次增量Build后会产生一个新的Segment。增量Cubing依赖已有的Cube Segments和增量的原始数据。增量Cubing的步骤和新建 Cube的步骤类似,Segment之间以时间段进行区分。

增量Cubing所需要面对的原始数据量更小,因此增量Cubing的速度时非常快的。然而随着Cube Segments的数目增加,一定程度上会影响到查询的进行,所以在Segments数目到一定数量后可能需要进行Cube Segments的合并操作,合并是一个异步操作,并不会影响到正常的查询服务。合并操作步骤如下:

  1. 遍历指定的Cube Segment
  2. 合并维度字典目录和维度表快照
  3. 利用MapReduce合并他们的 N-Dimension cuboid
  4. 将cuboid转换成HFile,生成新的HTable,替代原有的多个HTable 实际上merge cube是合成了一个新的大的Cube Segment来替代,Merge操作是一个异步的在线操作,不会对前端的查询业务产生影响。。

Kylin对传统MOLAP的改进

Kylin Cube的存储代价以及计算代价都是比较大的, 传统OLAP的维度爆炸的问题Kylin也一样会遇到。 Kylin提供给用户一些优化措施,包括减轻维度爆炸的问题,提高数据查询效率等:

Cube 建模优化:
  1. Hierachy Dimension(层级维度)
  2. Derived Dimension (衍生维度)
  3. Group Dimensions (维度组)

  4. Hierachy Dimension, 一系列具有层次关系的Dimension组成一个Hierachy, 比如年、月、日组成了一个Hierachy, 在Cube中,如果不设置Hierarchy, 会有 年、月、日、年月、年日、月日 6个cuboid, 但是设置了Hierarchy之后Cuboid增加了一个约束,希望低Level的Dimension一定要伴随高Level的Dimension 一起出现。设置了Hierachy Dimension 能使得需要计算的维度组合减少一半。

  5. Derived Dimension, 如果在某张维度表上有多个维度,那么可以将其设置为Derived Dimension, 在Kylin内部会将其统一用维度表的主键来替换,以此来达到降低维度组合的数目,当然在一定程度上Derived Dimension 会降低查询效率,在查询时,Kylin使用维度表主键进行聚合后,再通过主键和真正维度列的映射关系做一次转换,在Kylin内部再对结果集做一次聚合后返回给用户
  6. Group Dimension, 这是一个将维度进行分组,以求达到降低维度组合数目的手段。不同分组的维度之间不会进行组合计算。Group的优化措施与查询SQL紧密依赖,可以说是为了查询的定制优化。 维度组合从2的(k+m+n)次幂降低到 2的k次幂加2的m次幂加2的n次幂。如果查询的维度是夸Group的,那么Kylin需要以较大的代价从N-Cuboid中聚合得到所需要的查询结果
数据压缩:

Kylin针对维度字典以及维度表快照采用了特殊的压缩算法,保证存储在Hbase以及内存中的数据尽可能的小。 由于DataCube中会出现非常多的重复的维度值,因此直观的做法就是利用数据字典的方式将维度值映射成ID, Kylin中采用了Trie树的方式对维度值进行编码

Alt pic

distinct count聚合查询优化:

Kylin 采用了HypeLogLog的方式来计算Distinc Count。 好处是速度快,缺点是结果是一个近似值,会有一定的误差。 具体的算法可见Paper:  http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf 本文不再赘述。

kylin SQL查询的实现

Kylin支持标准的ANSI SQL, Kylin的SQL语法解析依赖于另一个开源数据管理框架 Apache Calcite, Calcite即之前的Optiq,可以说是一个没有存储模块的数据库,即不管理数据存储、不包含数据处理的算法,不包含元信息的存储。因此它非常适合来做一个应用到存储引擎之间的中间层。在Calcite的基础之上只要为存储引擎写一个专用的适配器(Adapter)即可形成一个功能丰富的支持DML的“类数据库”。目前Calcite已在Hive、Drill、Phoenix项目中被使用。

Kylin完成了一个针对性的Calcite Adapter,在Calcite完成SQL解析,形成语法树(AST)之后,Kylin定义语法树各个节点的执行规则,以及查询优化准则。Calcite在遍历语法树节点后生成一个Kylin描述查询模型的SQL Digest, Kylin会为此Digest去判断是否有匹配的Cube。如果有与查询匹配的Cube,即选择一个查询代价最小的Cube进行查询(Kylin Cube的查询代价计算目前是一个开放接口,可以根据维度数目,可以根据数据量大小来计算Cost)

Kylin目前的多维数据存储引擎是HBase, Kylin利用了HBase的Coprocessor机制在HBase的Region Server完成部分聚合以及过滤操作,在Hbase Scan时提前进行计算,利用HBase多个Region Server的计算能力加速Kylin的SQL查询。

再看kylin 与 RTOLAP

Kylin 可以说是与市面上流行的RTOLAP走了一条完全不同的道路。 Kylin在如何快速求得预计算结果,以及优化查询解析使得更多的查询能用上预计算结果方面在优化,后续Kylin的版本会优化预计算速度,使得Kylin可以变成一个近似实时的分析引擎。然而其缺点就是SQL支持方面可能在一定程度上会有所牺牲,存储开销也会比较大, 而像Presto,SparkSQL,Hawq等是着重于优化查询数据的过程环节,像一些其它的数据仓库一样,使用列存、压缩、并行查询等技术,优化查询。这种方案的好处就在于扩展性强、能适配更广泛的查询, 然而由于每次的聚合计算是 On Fly的,因此性能上相较Kylin还是有所不如。

3. Kylin在网易

Kylin服务化

在网易,Kylin作为大数据平台的Olap查询模块,可以为公司的各种分析类需求以及应用提供服务。所有数据存在Hadoop Hive 上的数据都能够通过Kylin Olap 引擎进行加速查询。在公司内部Kylin作为一个统一平台,与各产品的数据仓库进行接驳。

目前Kylin的部署架构如下:

Alt pic

Kylin集群由多个查询节点以及控制节点组成。 控制节点唯一,负责集群项目、任务调度与Cube增删查改。 多个查询节点前用Nginx做负载均衡,后段节点可按需水平扩容。前端可同时支持JDBC与ODBC的客户端查询

Kylin性能表现

在Kylin上线前,我们针对NRPT的报表业务进行过性能对比,对比内容在相同的数据下、Kylin查询与Mondrian 结合Oracle的查询比较。 我们选取了数据量较大的DataStream报表业务进行了测试:

Alt pic

再看Kylin的吞吐量,利用Haproxy进行请求转发后随着Kylin服务器的增加吞吐量的表现: Alt pic

根据测试结果可见,Kylin OLAP在性能上能达到秒级,并且在查询吞吐量可以通过增加查询服务器来达到线性扩展的目的

网易对Kylin的改进

原生的Kylin 是需要部署在一个统一底层的Hadoop、Hive、HBase集群之上的。而网易内部的大数据平台由于各种原因,分为了多个Hadoop集群、各应用会在不同的Hadoop集群上建立Hive数据仓库。 最原始而自然的想法就是在每一个Hadoop环境上部署一套Kylin服务来满足不同的需求,但是集群资源管理、计算资源调度、管理运维的复杂性都会是一个比较突出的问题。例如用户数据在A机房的Hive上,而A机房的Hadoop集群并没有足够的计算资源来保证Kylin Olap的高效运行。因此根据公司内部实际的大数据平台分布情况及机房建设情况,将Kylin打造成一个公司内统一的服务平台是一个更好的选择。Olap小组对开源版本的Kylin进行了二次开发,并将改进补丁提交了社区。目前的改进主要包括:

  1. Kylin对Kerberos认证的支持
  2. Kylin非Hadoop节点的部署支持
  3. 多数据源的支持

综合分析现实的场景之后,我们选择了公司内最大的hadoop集群作为Kylin Olap的计算引擎集群,保证有充足的存储以及计算资源。 HBase采用一个独立的集群,避免Hbase查询和Hadoop集群任务之间的互相干扰。数据源Hive允许用户自定义,目前已支持同Hadoop集群下不同Hive 以及不同Hadoop集群下的不同Hive节点使用Kylin Olap服务。根据用户数据仓库的实际配置情况可能会出现跨集群的数据源抽取计算, 由于公司同城机房有专线网络,数据仓库Hive里的源数据量也远小于Kylin实际的聚合后的数据存储(存于Hbase,数据量大小一般为数据源Hive中的10倍以上), 因此可认为这样的开销可以认为带来的影响不大,并且在我们的测试中得到了印证。

Kylin OLAP与猛犸以及有数的结合

为了让Kylin更快更好的融入到大数据平台中,OLAP小组已计划在不久之后全面与猛犸大数据平台进行打通和整合, Kylin Olap 将深度内嵌于猛犸,用户可以基于猛犸平台完成Kylin Olap的简化管理工作。猛犸平台对接控制节点,作为数据模型师的操作入口

  1. Kylin将利用猛犸的用户管理功能
  2. 猛犸将接管用户项目的创建以及Cube的管理
  3. 猛犸将原有的Hive数据源彻底与Kylin打通,便于Kylin管理用户的数据源

网易有数会成为Kylin Olap的一个重要的分析师入口,有数将Kylin Olap作为一个单独的数据源进行支持。已有的以及潜在的Hive查询客户可以轻松的将报表迁移到Kylin Olap,使得大数据量下的交互式报表分析称为可能。

  1. 有数能基于在猛犸上创建的Cube创建报表
  2. 有数会主动识别Kylin Cube定义的维度和度量
  3. 用户在Kylin Olap允许的范围内自由操作,完成报表的编辑和查询。

基于paho在android平台上实现MQTT Client间的简单通信 - CSDN博客

$
0
0

在之前的博文中,对MQTT和paho进行了简单的描述。paho为实现MQTT通信提供了接口。本篇将在android平台上,基于paho实现MQTT Client间的简单通信。broker选择公共的mosquitto broker.

(0)权限

<uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.INTERNET"/>

没有权限,寸步难行!

(1)引入paho lib

这里写图片描述

(2)Connection Options

paho通过MqttConnectOptions类,对broker与client间的连接进行需求设置。
例如,UserName和Password(不是所有client都可以连接某一broker,但如果无登录配置,则不用设置),Timeout时间,断开后是否自动连接,是否保留客户端的连接记录等。

privateMqttConnectOptionsinitMqttConnectionOptions(){
    MqttConnectOptions mOptions =newMqttConnectOptions();
    mOptions.setAutomaticReconnect(false);//断开后,是否自动连接mOptions.setCleanSession(true);//是否清空客户端的连接记录。若为true,则断开后,broker将自动清除该客户端连接信息mOptions.setConnectionTimeout(60);//设置超时时间,单位为秒mOptions.setUserName("Admin");//设置用户名。跟Client ID不同。用户名可以看做权限等级mOptions.setPassword("Admin");//设置登录密码mOptions.setKeepAliveInterval(60);//心跳时间,单位为秒。即多长时间确认一次Client端是否在线mOptions.setMaxInflight(10);//允许同时发送几条消息(未收到broker确认信息)mOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);//选择MQTT版本returnmOptions;
}

(3)设置回调函数

MqttCallbackExtended mqttCallback=newMqttCallbackExtended() {@OverridepublicvoidconnectComplete(booleanreconnect, String serverURI) {
        Log.i(TAG,"connect Complete"+ Thread.currentThread().getId());
    }@OverridepublicvoidconnectionLost(Throwable cause) {
        Log.i(TAG,"connection Lost ");
    }@OverridepublicvoidmessageArrived(String topic, MqttMessage message)throwsException {if(topic.equalsIgnoreCase("subscribe topic1")){
            Log.i(TAG,"messageArrived: "+newString(message.getPayload()));
            Message msg=newMessage();
            Bundle bindle=newBundle();
            bindle.putString("Content",newString(message.getPayload()));
            msg.what=MSG_TYPE_TO_B;
            msg.setData(bindle);
            mHandler.sendMessage(msg);
        }
    }@OverridepublicvoiddeliveryComplete(IMqttDeliveryToken token) {
        Log.i(TAG,"delivery Complete ");//即服务器成功delivery消息}

};

注:需要说明的是,不能在四个回调函数中进行UI操作。该回调函数在子线程中被调用。若掺杂UI操作,会引起MQTT连接断开,触发connectionLost回调函数。

(4)MQTT Client

mClient=initClient("tcp://test.mosquitto.org:1883","ClientA",mqttCallback,mOptions,newString[]{"topic1","topic2","topic3"});
privateMqttClient initClient(String serverURI, String clientId,MqttCallback callback, MqttConnectOptions options,String[] subscribeTopics){
    MqttClientclient=null;try{
        MemoryPersistence persistence =newMemoryPersistence();client=newMqttClient(serverURI,clientId,persistence);client.setCallback(callback);//设置回调函数client.connect(options);//连接brokerclient.subscribe(subscribeTopics);//设置监听的topic}catch(MqttException e) {
        e.printStackTrace();
    }returnclient;
}

创建Client时,需要指明:
(a)broker的URL:tcp://test.mosquitto.org:1883,即消息的中转站在哪儿。
(b)Client的ID:ClientA,即告诉broker我是谁。
(c)MemoryPersistence:用于存储两种消息( 可以设置为null,但与消息的发送质量有关。建议进行设置)。
(c.1)client尚未接收完毕的消息。
(c.2)client已发送完毕,但尚未得到broker确认的消息。
(d)Client的回调函数:mqttCallback,即如果连接成功,连接断开,消息发送成功,或接收到新的消息,该如何处理。
(e)Client的连接需求:mOptions,即对连接的设置。
(f)Client所监听的Topic:new String[]{“topic1”,”topic2”,”topic3”},即仅接收以上topic信息。

(6)发送消息

MqttMessage msg=newMqttMessage();StringmsgStr="Hello World";
msg.setPayload(msgStr.getBytes());//设置消息内容msg.setQos(2);//设置消息发送质量,可为0,1,2.msg.setRetained(false);//服务器是否保存最后一条消息,若保存,client再次上线时,将再次受到上次发送的最后一条消息。mClient.publish("my topic",msg);//设置消息的topic,并发送。

这里需要对发送质量进行再次说明。
(0)level0,最多一次的传输。不管消息是否到达broker,只要发出,client不再关心。
(1)level1,至少一次的传输。若消息到达broker,broker会回复client一个PUBACK消息,若未收到该回复消息,或超时,client将再次发送,直至收到broker响应。
(2)level2,这是最高级别的传输。在level1的基础上,保证重复消息不会被二次接收。

注:若想清空服务器保存的最后一条消息,可发送0字节的payload,对服务器进行清空。

(7)断开与broker的连接

try{if(mClient!=null){
        mClient.disconnect();
    }
}catch(MqttException e) {
    e.printStackTrace();
}

更多详情,可在paho官方文档中查找。
https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.javadoc.doc/WMQMQxrClasses/overview-summary.html

Demo实现效果
创建两个client,A和B,A监听TopicB,B监听TopicA,实现对话。效果如下:
这里写图片描述

Android APP必备高级功能,消息推送之MQTT - CSDN博客

$
0
0

本文已授权微信公众号《鸿洋》原创首发,转载请务必注明出处。

1. Android端实现消息推送的几种方式

  1. 轮询:客户端定时向服务器请求数据。伪推送。缺点:费电,费流量。
  2. 拦截短信消息。服务器需要向客户端发通知时,发送一条短信,客户端收到特定短信之后,先获取信息,然后拦截短信。伪推送。缺点:贵而且短信可能被安全软件拦截。
  3. 持久连接(Push)方式:客户端和服务器之间建立长久连接。真正的推送。
    1. Google的C2DM(Cloudto Device Messaging)。需要科学上网,国内大多数用户无法使用。
    2. XMPP。XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议。androidpn是一个基于XMPP协议的java开源Android push notification实现。它包含了完整的客户端和服务器端。
    3. MQTT。MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。

2. MQTT简介

MQTT官网: http://mqtt.org/

MQTT介绍: http://www.ibm.com

MQTT Android github: https://github.com/eclipse/paho.mqtt.android

MQTT API: http://www.eclipse.org/paho/files/javadoc/index.html

MQTT Android API: http://www.eclipse.org/paho/files/android-javadoc/index.html

建议时间充裕的同学有顺序的阅读上文五个链接内容,不充裕的同学请看下面简单的介绍(内容大多来自上面五条链接)。

MQTT 协议
客户机较小并且 MQTT 协议 高效地使用网络带宽,在这个意义上,其为轻量级。MQTT 协议支持可靠的传送和即发即弃的传输。 在此协议中,消息传送与应用程序脱离。 脱离应用程序的程度取决于写入 MQTT 客户机和 MQTT 服务器的方式。脱离式传送能够将应用程序从任何服务器连接和等待消息中解脱出来。 交互模式与电子邮件相似,但在应用程序编程方面进行了优化。

协议具有许多不同的功能:

  • 它是一种发布/预订协议。
  • 除提供一对多消息分发外,发布/预订也脱离了应用程序。对于具有多个客户机的应用程序来说,这些功能非常有用。
  • 它与消息内容没有任何关系。
  • 它通过 TCP/IP 运行,TCP/IP 可以提供基本网络连接。
  • 它针对消息传送提供三种服务质量:
    • “至多一次”
      消息根据底层因特网协议网络尽最大努力进行传递。 可能会丢失消息。
      例如,将此服务质量与通信环境传感器数据一起使用。 对于是否丢失个别读取或是否稍后立即发布新的读取并不重要。
    • “至少一次”
      保证消息抵达,但可能会出现重复。
    • “刚好一次”
      确保只收到一次消息。
      例如,将此服务质量与记帐系统一起使用。 重复或丢失消息可能会导致不便或收取错误费用。
  • 它是一种管理网络中消息流的经济方式。 例如,固定长度的标题仅 2 个字节长度,并且协议交换可最大程度地减少网络流量。
  • 它具有一种“遗嘱”功能,该功能通知订户客户机从 MQTT 服务器异常断开连接。请参阅“ 最后的消息”发布。

3. MQTT服务器搭建

  1. 点击 这里,下载Apollo服务器,解压后安装。
  2. 命令行进入安装目录bin目录下(例:E:>cd E:\MQTT\apache-apollo-1.7.1\bin)。
  3. 输入apollo create XXX(xxx为创建的服务器实例名称,例:apollo create mybroker),之后会在bin目录下创建名称为XXX的文件夹。XXX文件夹下etc\apollo.xml文件下是配置服务器信息的文件。etc\users.properties文件包含连接MQTT服务器时用到的用户名和密码,默认为admin=password,即账号为admin,密码为password,可自行更改。
  4. 进入XXX/bin目录,输入apollo-broker.cmd run开启服务器,看到如下界面代表搭建完成

success

之后在浏览器输入 http://127.0.0.1:61680/,查看是否安装成功。

4. MQTT Android客户端具体实现

基本概念:

  • topic:中文意思是“话题”。在MQTT中订阅了( subscribe)同一话题( topic)的客户端会同时收到消息推送。直接实现了“群聊”功能。
  • clientId:客户身份唯一标识。
  • qos:服务质量。
  • retained:要保留最后的断开连接信息。
  • MqttAndroidClient#subscribe():订阅某个话题。
  • MqttAndroidClient#publish(): 向某个话题发送消息,之后服务器会推送给所有订阅了此话题的客户。
  • userName:连接到MQTT服务器的用户名。
  • passWord :连接到MQTT服务器的密码。

添加依赖

repositories {
    maven {
        url"https://repo.eclipse.org/content/repositories/paho-releases/"}
}

dependencies {
    compile'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0'compile'org.eclipse.paho:org.eclipse.paho.android.service:1.1.0'}

添加限权

<uses-permissionandroid:name="android.permission.INTERNET"/><uses-permissionandroid:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permissionandroid:name="android.permission.WAKE_LOCK"/>

注册Service

<!-- Mqtt Service --><serviceandroid:name="org.eclipse.paho.android.service.MqttService"/><serviceandroid:name="com.dongyk.service.MQTTService"/>

Android端具体实现

packagecom.dongyk.service;importandroid.app.Service;
.../**
 * MQTT长连接服务
 *
 *@author一口仨馍 联系方式 : yikousamo@gmail.com
 *@version创建时间:2016/9/16 22:06
 */publicclassMQTTServiceextendsService{publicstaticfinalString TAG = MQTTService.class.getSimpleName();privatestaticMqttAndroidClient client;privateMqttConnectOptions conOpt;//    private String host = "tcp://10.0.2.2:61613";privateString host ="tcp://192.168.1.103:61613";privateString userName ="admin";privateString passWord ="password";privatestaticString myTopic ="topic";privateString clientId ="test";@OverridepublicintonStartCommand(Intent intent,intflags,intstartId) {
        init();returnsuper.onStartCommand(intent, flags, startId);
    }publicstaticvoidpublish(String msg){
        String topic = myTopic;
        Integer qos =0;
        Boolean retained =false;try{
            client.publish(topic, msg.getBytes(), qos.intValue(), retained.booleanValue());
        }catch(MqttException e) {
            e.printStackTrace();
        }
    }privatevoidinit() {// 服务器地址(协议+地址+端口号)String uri = host;
        client =newMqttAndroidClient(this, uri, clientId);// 设置MQTT监听并且接受消息client.setCallback(mqttCallback);

        conOpt =newMqttConnectOptions();// 清除缓存conOpt.setCleanSession(true);// 设置超时时间,单位:秒conOpt.setConnectionTimeout(10);// 心跳包发送间隔,单位:秒conOpt.setKeepAliveInterval(20);// 用户名conOpt.setUserName(userName);// 密码conOpt.setPassword(passWord.toCharArray());// last will messagebooleandoConnect =true;
        String message ="{\"terminal_uid\":\""+ clientId +"\"}";
        String topic = myTopic;
        Integer qos =0;
        Boolean retained =false;if((!message.equals("")) || (!topic.equals(""))) {// 最后的遗嘱try{
                conOpt.setWill(topic, message.getBytes(), qos.intValue(), retained.booleanValue());
            }catch(Exception e) {
                Log.i(TAG,"Exception Occured", e);
                doConnect =false;
                iMqttActionListener.onFailure(null, e);
            }
        }if(doConnect) {
            doClientConnection();
        }

    }@OverridepublicvoidonDestroy() {try{
            client.disconnect();
        }catch(MqttException e) {
            e.printStackTrace();
        }super.onDestroy();
    }/** 连接MQTT服务器 */privatevoiddoClientConnection() {if(!client.isConnected() && isConnectIsNomarl()) {try{
                client.connect(conOpt,null, iMqttActionListener);
            }catch(MqttException e) {
                e.printStackTrace();
            }
        }

    }// MQTT是否连接成功privateIMqttActionListener iMqttActionListener =newIMqttActionListener() {@OverridepublicvoidonSuccess(IMqttToken arg0) {
            Log.i(TAG,"连接成功 ");try{// 订阅myTopic话题client.subscribe(myTopic,1);
            }catch(MqttException e) {
                e.printStackTrace();
            }
        }@OverridepublicvoidonFailure(IMqttToken arg0, Throwable arg1) {
            arg1.printStackTrace();// 连接失败,重连}
    };// MQTT监听并且接受消息privateMqttCallback mqttCallback =newMqttCallback() {@OverridepublicvoidmessageArrived(String topic, MqttMessage message)throwsException {

            String str1 =newString(message.getPayload());
            MQTTMessage msg =newMQTTMessage();
            msg.setMessage(str1);
            EventBus.getDefault().post(msg);
            String str2 = topic +";qos:"+ message.getQos() +";retained:"+ message.isRetained();
            Log.i(TAG,"messageArrived:"+ str1);
            Log.i(TAG, str2);
        }@OverridepublicvoiddeliveryComplete(IMqttDeliveryToken arg0) {

        }@OverridepublicvoidconnectionLost(Throwable arg0) {// 失去连接,重连}
    };/** 判断网络是否连接 */privatebooleanisConnectIsNomarl() {
        ConnectivityManager connectivityManager = (ConnectivityManager)this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = connectivityManager.getActiveNetworkInfo();if(info !=null&& info.isAvailable()) {
            String name = info.getTypeName();
            Log.i(TAG,"MQTT当前网络名称:"+ name);returntrue;
        }else{
            Log.i(TAG,"MQTT 没有可用网络");returnfalse;
        }
    }@Nullable@OverridepublicIBinderonBind(Intent intent) {returnnull;
    }
}

首先初始化各个参数,之后连接服务器。连接成功之后在 http://127.0.0.1:61680/看到自动创建了名称为”topic”的 topic。这里我使用了一个真机和一个模拟器运行程序。 http://127.0.0.1:61680/服务端看到的是这个样子

serverPic

这里需要注意两个地方
1. 模拟器运行的时候 host = "tcp://10.0.2.2:61613",因为10.0.2.2 是模拟器设置的特定ip,是你电脑的别名。真机运行的时候 host = "tcp://192.168.1.103:61613"。192.168.1.103是我主机的IPv4地址,查看本机IP的cmd命令为 ipconfig/all
2. 两次运行时的 clientId不能一样(为了保证客户标识的唯一性)。

这里为了测试,在 MQTTService中暴露了一个公共方法 publish(String msg)MainActivity调用。代码如下

publicclassMainActivityextendsActivity{@OverrideprotectedvoidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);
        startService(newIntent(this, MQTTService.class));
        findViewById(R.id.publishBtn).setOnClickListener(newView.OnClickListener() {@OverridepublicvoidonClick(View view) {
                MQTTService.publish("CSDN 一口仨馍");
            }
        });
    }@Subscribe(threadMode = ThreadMode.MAIN)publicvoidgetMqttMessage(MQTTMessage mqttMessage){
        Log.i(MQTTService.TAG,"get message:"+mqttMessage.getMessage());
        Toast.makeText(this,mqttMessage.getMessage(),Toast.LENGTH_SHORT).show();
    }@OverrideprotectedvoidonDestroy() {
        EventBus.getDefault().unregister(this);super.onDestroy();
    }

}

这里使用了 EventBus3.0发送消息,感兴趣的可以看下 EventBus3.0使用及源码解析。当然,你也可以使用接口回调的方式甚至直接在Service中弹出 Toast。whatever,现在点击一个客户端 MainActivity中的 Button,两个客户端已经能同时弹出消息。已经 get到数据了。接下来,show time~

redis调优的实战经验 - 大叔据 - 博客园

$
0
0

本文根据redis的info命令查看redis的内存使用情况以及state状态,来观察redis的运行情况以及需要作出的相应优化。

inf    1.memory

used_memory:13409011624 #used_memory=实际缓存占用的内存+Redis自身运行所占用的内存(如元数据、lua)。
                        #这个值是由Redis使用内存分配器分配的内存,不包括内存碎片浪费的内存。
used_memory_rss:13740019719  #从操作系统上显示已经分配的内存总量。
used_memory_peak:13409011624  #内存使用的峰值大小
total_system_memory:33567678464  #系统总内存
used_memory_lua:37888  #Lua脚本引擎所使用的内存大小。
maxmemory:0  #最大可用内存(可配置,默认为total_system_memory)
maxmemory_policy:noeviction  #淘汰机制,noneviction为禁止淘汰数据
mem_fragmentation_ratio:1.02;  #内存碎片率
mem_allocator:jemalloc-4.0.3; #编译时指定的Redis内存分配器,可以是libc、jemalloc、tcmalloc。

2.stats
total_commands_processed:3500  #自启动起Redis服务处理命令的总数

1.used_memory 过大导致的问题

1.1.引发内存交换

  当Redis内存使用率超过可用内存(maxmemory可配置)的95%时,操作系统会进行内存与swap空间数据交换。 把内存中旧的或不再使用的内容写入硬盘上即Swap分区,以便腾出新的物理内存给新页或活动页(page)使用。 在硬盘上进行读写操作要比在内存上进行读写操作,时间上慢了近 5个数量级,内存是0.1μs单位、而硬盘是10ms。如果Redis进程上发生内存交换,那么Redis和依赖Redis上数据的应用会受到严重的性能影响。

1.2.rdb持久化风险

  在没有开启持久化的情况下, redis宕机或者内存使用率超过95%会有丢数据的风险。若使用快照(rdb)持久化,Redis会fork一个子进程把当前内存中的数据完全复制一份写入到硬盘上( fork使用的内存和redis当前使用的内存会一样多)。因此若是当前使用内存 超过可用内存的45%时触发快照功能,那么此时进行的内存交换会变的非常危险(可能会丢失数据)。 倘若在这个时候实例上有大量频繁的更新操作,问题会变得更加严重。

2.避免used_memory 过大

  • 尽可能的使用Hash数据结构

  因为Redis在储存小于100个字段的Hash结构上,其存储效率是非常高的。所以 在不需要集合(set)操作或list的push/pop操作的时候,尽可能的使用Hash结构。比如,在一个web应用程序中,需要存储一个对象表示用户信息,使用单个key表示一个用户,其每个属性存储在Hash的字段里,这样要比给每个属性单独设置一个key-value要高效的多。 通常情况下倘若有数据使用string结构,用多个key存储时,那么应该转换成单key多字段的Hash结构。 如上述例子中介绍的Hash结构应包含,单个对象的属性或者单个用户各种各样的资料。Hash结构的操作命令是HSET(key, fields, value)和HGET(key, field),使用它可以存储或从Hash中取出指定的字段。

  • 设置key的过期时间

  一个减少内存使用率的简单方法就是, 每当存储对象时确保设置key的过期时间。倘若key在明确的时间周期内使用或者旧key不大可能被使用时,就可以用Redis过期时间命令(expire,expireat, pexpire, pexpireat)去设置过期时间,这样Redis会在key过期时自动删除key。 假如你知道每秒钟有多少个新key-value被创建,那可以调整key的存活时间,并指定阀值去限制Redis使用的最大内存。

  • 回收key

  在Redis配置文件Redis.conf中, 通过设置“maxmemory”属性的值可以限制Redis最大使用的内存,修改后重启实例生效。 也可以使用客户端命令config set maxmemory 去修改值,这个命令是立即生效的,但会在重启后会失效,需要使用config rewrite命令去刷新配置文件。

  1. 若是启用了Redis快照功能,应该设置“maxmemory”值为系统可使用内存的45%,因为快照时需要一倍的内存来复制整个数据集,也就是说如果当前已使用45%,在快照期间会变成95%(45%+45%+5%),其中5%是预留给其他的开销。
  2. 如果没开启快照功能,maxmemory最高能设置为系统可用内存的95%。
  • 淘汰策略

  当内存使用 达到设置的最大阀值时,需要选择一种 key的回收策略,可在Redis.conf配置文件中修改“maxmemory-policy”属性值。 若是Redis数据集中的key都设置了过期时间,那么“volatile-ttl”策略是比较好的选择。但如果key在达到最大内存限制时没能够迅速过期,或者根本没有设置过期时间。那么设置为“allkeys-lru”值比较合适,它允许Redis从整个数据集中挑选最近最少使用的key进行删除(LRU淘汰算法)。

Redis还提供了一些其他淘汰策略,如下:

volatile-lru:使用LRU算法从已设置过期时间的数据集合中淘汰数据。
volatile-ttl:从已设置过期时间的数据集合中挑选即将过期的数据淘汰。
volatile-random:从已设置过期时间的数据集合中随机挑选数据淘汰。
allkeys-lru:使用LRU算法从所有数据集合中淘汰数据。
allkeys-random:从数据集合中任意选择数据淘汰
no-enviction:禁止淘汰数据。

  通过设置maxmemory为系统可用内存的45%或95%(取决于持久化策略)和设置“maxmemory-policy”为“volatile-ttl”或“allkeys-lru”(取决于过期设置),可以比较准确的限制Redis最大内存使用率, 在绝大多数场景下使用这2种方式可确保Redis不会进行内存交换。倘若你担心由于限制了内存使用率导致丢失数据的话,可以设置noneviction值禁止淘汰数据。

3. used_memory_rss 过大解决办法

  当mem_fragmentation_ratio远大于1时即used_memory_rss/used_memory(稍大于1正常),说明redis中存在大量的内存碎片,一个比较好的解决办法就是重启redis,这里需要注意的是 如果用的是aof持久化,那么重启之前要进行rewriteaof操作,否则会无效。还有可以指定Redis使用的内存分配器,一般管理员不推荐,麻烦而且要重新编译。

参考:

  1. redis官方文档
  2. 不错的英文文档

评论不能及时回复可直接加公众号提问或交流,知无不答,谢谢 。 

云纵持续交付环境管理进化历程 - 旁观者 - 博客园

$
0
0

破天(李进庄)、冬草(宋玥辉) 创作于2018-9-10

一个公司的运维能力强弱和你线上环境敲命令是有关的,
你越是喜欢上线敲命令,你的运维能力就越弱,
越是通过自动化来处理问题,你的运维能力就越强。
——陈皓,2017

持续集成(Continuous Integration, CI)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译、发布、自动化测试)来验证,从而尽早地发现集成错误。

持续部署(Continuous Deployment, CD)是通过自动化的构建、测试和部署,循环快速交付高质量的产品。某种程度上代表了一个开发团队工程化的程度,毕竟快速运转的互联网公司人力成本会高于机器,投资于机器优化开发流程也提高了人效。

这都说明了环境管理自动化对于互联网公司的重要性。

 

Docker自2014年以来非常火热,随着容器化的如日中天,另一个理念DevOps也在不断传播。大部分IT从业者对于DevOps本身也都有一定的了解和认识,然而企业内部要想根据DevOps思想实践一条自己的路,绝对不是一件简单的事。而我司自2014年11月份开始引入容器到现在为止,一直在DevOps的路上不断地探索中,在这期间业界也没有出现一套标准的开源工具集可以借鉴,因此我们只能摸着石头过河。以下为我司的一些经验分享。

我司的环境演化史

 

演进历史-云纵

 

2014年11月份在容器化启动之前,我司的开发、测试及线上环境都是部署在基于OpenStack的虚拟化服务器上,技术团队需要维护的环境划分得很细:开发环境、测试环境、特殊测试环境、紧急测试环境、镜像环境、线上环境。

2015年9月随着容器化技术的使用以及云纵业务调整,环境管理缩减到了四个。

到了2016年3月,我司启动了CloudEngine的开发,一个基于私有云的研发协作系统。基于CloudEngine,我司初步实现了DevOps的雏形。在之后,根据业务需求,我们先后实现私有云灰度发布功能、多机房混合云多活管理功能。

基于CloudEngine的基础环境管理架构如下图所示,CloudEngine架设在TouchStone(容器化管理平台)、SimpleWay(运维自动化平台)、iDB(数据库自动化运维平台)、Jenkins(注:开源产品)等基础设施系统之上,可以看作是一个元数据管理者和流程调度的发起者,同时支持虚拟机和容器资源申请等,等同于我们的私有云管理系统+项目需求开发测试跟踪系统的结合体。

 

基础管理体系-云纵

 

环境演化推动过程

CloudEngine引入背景

在CloudEngine引入之前,开发使用开发联调环境,测试使用测试环境,此时我们最大的痛点是多需求并发开发,或多需求并发测试,这个时候我们只有两个解决方案:

  1. 多准备几个开发联调环境和测试环境(这也是容器化之前维护很多环境的原因之一),每个需求版本使用自己单独的环境,但是环境维护困难,开发人员排查起来问题也困难。
  2. 代码变更要求向上兼容,但是向上兼容有时候是比较困难的,甚至有时候只能串行。

为了解决快速部署一套开发联调环境或者测试环境,我们很早就引入了容器化技术,但是即使通过容器化技术,环境维护也是比较困难的。

CloudEngine1.0实现CI/CD流水线管理

机缘巧合之下我们接触并深入了解了蚂蚁金服的研发协作平台,我们把庞大的蚂蚁金服研发支撑体系浓缩为四个平台:ACP(阿里协作平台)、九州资源管控平台、AQC(蚂蚁质量基础设施平台)、Zpass(蚂蚁集团发布部署平台)。ACP是总驱动,九州、AQC、Zpass作为基础设施,整个形成一套CI/CD流水线。

 

阿里研发协作-云纵

 

而此时,我司的持续集成体系如下:

项目管理、测试用例、缺陷管理我们有一套管理体系;

自动化编译、自动化部署、自动化测试我们有一套管理体系;

镜像管理、应用发布、应用监测我们有一套管理体系;

容器管理、集群管理我们也有一套管理体系。

 

 

我们发现私有云体系的基础系统我们已经基本具备,现在需要一个PAAS平台来把研发从环境申请到测试发布上线整个研发测试链路管理起来,因此我们(注:牵头人是田志全)提出了CloudEngine(下面简称CE)的建设解决方案,功能点如下:

 

CE架构-云纵

 

CE机器申请:当一个需求从提出进入研发阶段后,研发经理在CE中选择应用、环境、虚拟化方案、Git地址等信息就可以做到一键完成机器申请、编译构建、环境部署和环境监控。研发在Git开发分支上进行编码开发,当开发到一定阶段随时可以在服务申请列表中找到申请的机器进行重新部署,以达到CI(持续集成)的目的。

 

 

CE提测申请:当研发开发联调完成后,研发经理在CE中可以直接创建提测单,CE把多个应用打包到一起进行提测。测试人员直接使用研发申请的环境进行测试(不会创建新的测试环境)。

 

 

CE上线发布:当测试人员测试通过之后,测试经理在CE中可以直接创建上线工单,CE调用SimpleWay直接会把之前环境的的Docker镜像推送到线上环境(一包通用),如阿里云,如自建IDC机房,以达到CD(持续部署)的目的。

 

 

在整个CI/CD流水线管理中涉及两个核心技术点要解决:

  • 引入Stable环境概念

日常开发申请机器时,通常只需申请有代码变更的应用机器,所依赖的其他应用均来自于Stable环境。如下图所示,当我们订单服务有代码变更的时候,只需要申请一个Dev环境订单服务机器即可,购物车和支付服务还使用Stable环境的机器,即系统调用链路要能判断出是否有非Stable环境的应用,没有的话才会调用Stable环境应用。

 

稳定环境原理-云纵

 

我司应用之间的调用基本上使用Dubbo的RPC调用(而不是微服务的RestFull API,改造成本低一些),我们的解决方案是在容器申请时的ENV中打入环境标示,比如开发Stable环境标示为:SYS_ENV=ds,开发dev环境标示为:SYS_ENV=d,Dubbo在做服务调用时,优先选择非Stable环境的机器。

另外一个考虑点是MQ,我司使用的是自建Notify MQ,我们的Notify是按QueueName进行消息分组的,解决方案是不同的环境创建不同的QueueName即可,无需代码改造。

  • 一包通用解决方案

一包通用指测试和线上使用相同的镜像包,技术难点在于配置信息可根据不同环境自动拉取配置。这样做的好处是测试人员测试的结果能真实反映到线上环境,避免上线重新编译打包引起的各种不一致问题(之前我们经常遇到上线分支合并出现问题影响到了线上环境)。

我们的解决方案是使用Diamond分布式配置管理统一管理各个应用各个环境的配置信息,各个应用的镜像在启动的时候根据dockerfile和tpl.properties(应用配置模版)从Diamond中拉取自己的配置信息,写入app.properties(应用真实配置文件),之后再进行容器启动以适配具体环境。具体流程如下。

 

一镜到底一包通用-云纵

 

CloudEngine2.0实现灰度发布管理

2017年开始随着我司业务扩张,每周都有三到四次大的上线操作,为了不影响线上用户体验,每次上线都要求在0点之后进行,上线完成之后,测试人员要通跑一遍业务流程,随着业务不断的复杂化,每次上线都要求相关测试和研发进行一次通宵,有些时候遇到上线失败回滚,还有可能通宵两次。为了解决这个痛点,我司根据自己的私有云管理情况提出了一套灰度发布解决方案。

 

灰度发布原理-云纵

 

灰度解决方案主要支持流量比例规则和指定IP规则。外网第一层Nginx的Lua脚本根据灰度规则组配置进行请求流量筛选,符合灰度条件的请求会在header中打入灰度标示并转发到灰度Nginx,从这里开始环境进行了隔离,但是到了后台服务端因为有环境重合的可能,所以我们利用ThreadLocal实现了一套灰度上下文,Dubbo做服务分发调用的时候,根据灰度上下文中的标示做判断进行灰度环境调用,整个请求处理流程如上图所示。

这里我们也想过完全的环境隔离方案,即灰度单独使用一整套自己的环境,但是我们分析灰度的业务也是线上业务,我们的诉求是请求随时可以进入灰度,随时也可以退出灰度,所以灰度库和线上库应该是一套数据库,另外上线灰度环境的应用可能是任何一个或多个应用,所以最终我们选择了侵入性比较高的强控制方案。

CE3.0+SimpleWay2.0实现跨机房多活

2018年年初开始,随着我司业务继续扩张,尤其支付业务的重要性越来越高,本年3月份我司正式提出启动异地多活项目,着重解决双机房切换问题。

异地多活的实现目标:

  1. 双机房同时提供服务,流量按照指定规则打到不同机房;
  2. 当机房A发生灾难时,能将流量全部切换到机房B且不会压垮机房B的既有服务;
  3. 机房之间数据双向同步,数据同步在2秒内完成。不同机房短时间内不对同一行数据进行写操作;

异地多活的技术难点主要在数据跨机房同步和流量请求控制两方面,经过多次讨论分析我们最终确定的解决方案如下:

 

异地双活原理-云纵

 

  • 每个机房配备有一个管控系统、两套业务分片、一份全量MySQL节点,分片之间的应用是完全的网络隔离。
  • 流量按商户ID进行规则分流,不同商户分流到不同机房。
  • 每个机房有一个主分片和一个辅分片,主分片主要承接分到这个机房的流量,辅分片是另一个机房主分片的备用分片,当另一个机房挂掉,流程切到这个机房的辅分片上,主分片的业务不受影响。
  • 数据库独立于分片之外,每个机房一套完整的数据库,采用Otter服务实现双向数据同步。

跨机房数据同步需要注意的点:

跨机房同步类型主要有三种:不同步、单向同步、双向同步。其中不同步不必考虑,需要考虑的只有单向和双向两种:

 

跨机房同步注意事项-云纵

 

业务系统改造点(注意点)

业务改造点-云纵

 

其他补充点

多活解决方案的目标主要是做机房容灾,即当A机房发生不可抗拒灾难后,我们可以快速把流量切到B机房以减少灾难造成的影响,以目前我们的解决方案,在灾难发生的一瞬间,数据双机房备份如果没有同步完成也会有一些细微的影响,目前只能人工干预,在灾难结束后人工对受影响的数据进行订正。

未来的进化方向

息壤(质量管理)

2018年已进入下半场,为了继续提升我司的生产效率,保证我司工程发布质量和合规性,同时考虑我司私有云等各种云的操作复杂性,降低学习成本和犯错几率,需要继续完善我司的大研发协作平台,对标蚂蚁的AQC(质量基础设施平台),我司在质量自动化管控方面还存在一定短板,因此后续我们考虑构建我们自己的质量协作平台“息壤”,将CloudEngine、RAP、JMeter等流程串接起来实现质量管控的自动化管理。

取名息壤的意思是:这是一块能自己生长的土壤,元数据(如接口定义)录入后,随着产品迭代,它能自己慢慢生长,自动化单元测试,测试用例自动化执行,场景自动回归,日检等等。

 

 

总结

云纵是一个笃信工程师文化的公司,我们崇尚依靠技术(及自动化)而不是依靠线下流程(注:为了避免网友误会,此流程特指线下人工流程)和管理解决问题。

我们的体系架构宏大,我们持续投入资源(注:日常20%~30%的研发资源),虽然此体系绝非一朝一夕所能完成,但是我们秉承平凡人可做非凡事和日拱一卒功不唐捐的理念,一群信仰技术的工程师边开飞机边换引擎,我们终会有实现宏伟蓝图的那一天。

参考资源:

1,2018, #研发解决方案#异地多活让商户无感知

2,2017, 轻舟已过万重山——真正的技术派公司是怎么联调、测试和发布的?

3,2016, 私有云的难处—为什么需要CloudEngine?

4,2016, #研发解决方案#研发协作平台CloudEngine

-EOF-

/*敬请关注我的公众号:老兵笔记*/

redis 性能监控和排查 - babyblue - 博客园

$
0
0

    最近项目中接连遇到redis出现瓶颈的问题,现在把排查的一些经验记录下来备查,本篇只是思路的整理,不涉及具体的使用。

   大体的思路如下:

  1.通过slow log查看

     参考 http://www.cnblogs.com/onmyway20xx/p/5486604.html

  查看下是否有较为明显的慢查询?一般认为出现慢查询的话,redis性能瓶颈已经比较明显了

  2. 通过info 查看;

  info里面的信息比较多,通常关注以下几块

   # Memory    
    used_memory_human:795.13K  #redis现在占用的内存,有可能包括SWAP虚拟内存。
    used_memory_rss:18259968  #系统给redis分配的内存  
    used_memory_peak_human:9.51M  # Redis所用内存的峰值 
    mem_fragmentation_ratio:22.43 #used_memory_rss/used_memory ,当mem_fragmentation_ratio <1时,说明used_memory > used_memory_rss,

    这时Redis已经在使用SWAP,运行性能会受很大影响。

  3. 通过benchmark测试下当前服务器的性能;

  4. 通过MONITOR测算一次请求对redis操作的次数;

 

 

 

 

1. INFO

info指令返回服务器相关信息,包括:

  • server: General information about the Redis server

  • clients: Client connections section

  • memory: Memory consumption related information

  • persistence: RDB and AOF related information

  • stats: General statistics

  • replication: Master/slave replication information

  • cpu: CPU consumption statistics

  • commandstats: Redis command statistics

  • cluster: Redis Cluster section

  • keyspace: Database related statistics

其本身支持定制返回列表:

[root@~]# redis-cli info[root@~]# redis-cli info default[root@~]# redis-cli info all

详情请见: http://www.redis.cn/commands/info.html

2. MONITOR

MONITOR是一个调试命令,返回服务器处理的每一个命令,它能帮助我们了解在数据库上发生了什么操作。共有3种操作方法:

[root@~]# redis-cli monitor
OK1417532512.619715[0127.0.0.1:55043]"REPLCONF""ACK""6623624"[root@~]# telnet 127.0.0.1 6379
Trying127.0.0.1...
Connected to127.0.0.1.
Escape character is'^]'.
monitor
+OK
+1417532567.733458[0127.0.0.1:55043]"REPLCONF""ACK""6623708"
+1417532568.735936[0127.0.0.1:55043]"REPLCONF""ACK""6623708"
quit
+OK
Connection closed by foreign host.[root@~]# redis-cli127.0.0.1:6379> monitor
OK1417532590.785487[0127.0.0.1:55043]"REPLCONF""ACK""6623736"

由于MONITOR命令返回服务器处理的所有的命令, 所以在性能上会有一些消耗。使用官方的压测工具测试结果如下

在不运行MONITOR命令的情况下,benchmark的测试结果:

[root@~/software/redis-2.8.17]# src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE:51020.41 requests per second
PING_BULK:50607.29 requests per second
SET:37257.82 requests per second
GET:49800.80 requests per second
INCR:38699.69 requests per second
LPUSH:38910.51 requests per second
LPOP:39277.30 requests per second
SADD:54614.96 requests per second
SPOP:51948.05 requests per second
LPUSH(needed to benchmark LRANGE):38819.88 requests per second
LRANGE_100(first100 elements):20112.63 requests per second
LRANGE_300(first300 elements):9025.27 requests per second
LRANGE_500(first450 elements):6836.67 requests per second
LRANGE_600(first600 elements):5406.28 requests per second
MSET(10 keys):19394.88 requests persecond

在运行MONITOR命令的情况下,benchmark的测试结果: (redis-cli monitor > /dev/null):

[root@~/software/redis-2.8.17]# src/redis-benchmark -c 10 -n 100000 -q
PING_INLINE:42211.91 requests per second
PING_BULK:42936.88 requests per second
SET:26143.79 requests per second
GET:33990.48 requests per second
INCR:26553.37 requests per second
LPUSH:27337.34 requests per second
LPOP:27225.70 requests per second
SADD:30459.95 requests per second
SPOP:39494.47 requests per second
LPUSH(needed to benchmark LRANGE):26315.79 requests per second
LRANGE_100(first100 elements):22055.58 requests per second
LRANGE_300(first300 elements):8104.38 requests per second
LRANGE_500(first450 elements):6371.05 requests per second
LRANGE_600(first600 elements):5031.95 requests per second
MSET(10 keys):14861.05 requests persecond

可以看到各项指标基本都有所下降。

详情请见: http://www.redis.cn/commands/monitor.html

3. SLOWLOG

通过SLOWLOG可以读取慢查询日志。

使用SLOWLOG LEN就可以获取当前慢日志长度。

[root@~/software/redis-2.8.17]# redis-cli127.0.0.1:6379> slowlog len(integer)28

使用SLOWLOG GET就可以获取所有慢日志。

127.0.0.1:6379> slowlogget1)1)(integer)272)(integer)14175313203)(integer)246234)1)"info"

其中,各项指标表示:

  • A unique progressive identifier for every slow log entry.

  • The unix timestamp at which the logged command was processed.

  • The amount of time needed for its execution, in microseconds(注意,microseconds翻译成微秒,而不是毫秒).

  • The array composing the arguments of the command.

使用SLOWLOG GET N就可以获取最近N条慢日志。

127.0.0.1:6379>slowlogget21)1)(integer)272)(integer)14175313203)(integer)246234)1)"info"2)1)(integer)262)(integer)14175283793)(integer)213634)1)"get"2)"user:score"

使用SLOWLOG RESET命令重置慢日志。一旦执行,将丢失以前的所有慢日志。

127.0.0.1:6379> slowlog reset      
3. redis延迟时间排查

最近数据量越来越多,并发写操作很多的情况下,Redis出现响应慢的情况;

可以使用 Redis命令来测试一下redis的响应速度:

redis-cli --latency -h xxx  -p xxxx

这条命令会向Redis插入示例数据来检查平均延时。 Ctrl+C可以随时结束测试;

下面我们列一下会出现延时的可能:

  • 硬件,系统:硬件问题是所有问题最底层的问题了,如果硬件慢,例如CPU主频低,内存小,磁盘IO慢,这些会让所有运行在上面的系统响应慢;另外,使用虚拟机会让系统运行的性能太为下降;当然,有钱的话,这问题很容易解决;系统方面,Linux本身的系统资源调度也会产生一定的延时。这些一般不会很大,可以忽略不计;

  • 网络:如果客户端和redis在同一台服务器上,使用socket建立连接会比监听 TCP/IP 端口快很多;

  • Redis命令:一些时间复杂度比较高的命令,如 lrem,sort,sunion等命令会花比较长时间;另外,大量的重复连接也会造成延时,重用连接是一种很好的品质;如果有大量写操作,可以使用 pipeline 管道的方式(类似mysql事务),一次性提交,这样数据量也少了,连接次数也少了,不用每次都返回数据,速度自然会快很多;

  • 持久化:Redis持久化需要fork出一个进程来进行持久化操作,这本身就会引发延时,如果数据变化大,RDB配置时间短,那这个代价还是挺大的;再加上,硬盘这东西真有点不靠谱,如果还是虚拟机上的虚拟硬盘,如果还是NFS共享目录,那这延时会让你崩溃。所以,如果系统不需要持久化,关了吧。


Redis数据备份方案-Luffy的梦-51CTO博客

$
0
0

###只是为了查询方便,方法为借鉴网络的文章,文章贴在这里供参考:
http://blog.csdn.net/subuser/article/details/8157178

Redis提供了两种持久化选项,分别是RDB和AOF。
默认情况下60秒刷新到disk一次[save 60 10000 当有1w条keys数据被改变时],Redis的数据集保存在叫dump.rdb一个二进制文件,这种策略被称为快照。
也可以手动调用Save或BGSAVE命令的:
/usr/local/bin/redis-cli -h 127.0.0.1 -p 6379 -a pwd bgsave
快照易恢复,文件也小,但是如果遇到宕机等情况的时候快照的数据可能会不完整。此时可能需要启用另一种持久化方式AOF,在配置文件中打开[appendonly yes]。

AOF刷新日志到disk的规则:
appendfsync always #always 表示每次有写操作都进行同步,非常慢,非常安全。
appendfsync everysec #everysec表示对写操作进行累积,每秒同步一次
官方的建议的everysec,安全,就是速度不够快,如果是机器出现问题可能会丢失1秒的数据。
也可以手动执行bgrewriteaof进行AOF备份:
/usr/local/bin/redis-cli -h 127.0.0.1 -p 6379 -a pwd bgrewriteaof

我们现在的做法是一主(Master)多从(Slave),主库不开启AOF持久化,只是每天备份一下RDB[官方给的建议是每小时备份RDB文件,看你的策略了],而在从库上开启AOF备份;

当redis服务器挂掉时,重启时将按照以下优先级恢复数据到内存:
如果只配置AOF,重启时加载AOF文件恢复数据;
如果同时 配置了RBD和AOF,启动是只加载AOF文件恢复数据;
如果只配置RBD,启动是讲加载dump文件恢复数据。
恢复时需要注意,要是主库挂了不能直接重启主库,否则会直接覆盖掉从库的AOF文件,一定要确保要恢复的文件都正确才能启动,否则会冲掉原来的文件。

根据以上修改redis的备份脚本
#!/bin/sh
#
for i in `netstat -nlpt|grep redis|awk -F: '{print $2}'|awk '{print $1}'`
do

        idt=`redis-cli -p $i info |grep role|awk -F: '{print $2}'|tr -d '\r'`     
                                ### tr -d ‘\r’去掉换行符
  #根据redis的角色进行备份,master不开启AOF持久化,通过bgsave对RDB快照;slave开启AOF持久化,通过bgrewriteaof进行备份;
        case $idt in
        master)
                        redis-cli -h 127.0.0.1 -p $i -a pwd bgsave
                        ;;
        slvae)
                        redis-cli -h 127.0.0.1 -p $i -a pwd bgrewriteaof
                        ;;
        *)
                        exit 1
                        ;; 
        esac

done
sleep 600

tt=`date +"%Y%m%d-%H%M%S"`    ###按照日期时间格式进行备份
echo     "--------------------------$tt------------------------------" >>/data/backup/redis_backup.log    ##日志记录备份详情

#将备份的AOF或者RDB拷贝到备份目录下
for i in `find /data/ -name "*.aof"`
do
                echo cp $i /data/backup/`basename $i`.$tt >> /data/backup/redis_backup.log
                /bin/cp -f $i /data/backup/`basename $i`.$tt
done

for i in `find /data/ -name "dump*.rdb"`
do
                echo cp $i /data/backup/`basename $i`.$tt >>/data/backup/redis_backup.log
                /bin/cp -f $i /data/backup/`basename $i`.$tt
done

#因所有备份都是全库备,所以按日期删除4天以前的备份数据
cd /data/backup
find . \( -name "appendonly*" -o -name "dump*" \)    -mtime +4 -exec    rm -f {} \;    

IO设计模式:Actor、Reactor、Proactor - 梦想-现实 - 博客园

$
0
0

先看看 io模型

 
先介绍两种高性能服务器模型Reactor、Proactor

Reactor模型: 
1 向事件分发器注册事件回调 
2 事件发生 
4 事件分发器调用之前注册的函数 
4 在回调函数中读取数据,对数据进行后续处理 
Reactor模型实例:libevent,Redis、ACE

Proactor模型: 
1 向事件分发器注册事件回调 
2 事件发生 
3 操作系统读取数据,并放入应用缓冲区,然后通知事件分发器 
4 事件分发器调用之前注册的函数 
5 在回调函数中对数据进行后续处理 
Preactor模型实例:ASIO

 

reactor和proactor的主要区别:

主动和被动

以主动写为例: 
Reactor将handle放到select(),等待可写就绪,然后调用write()写入数据;写完处理后续逻辑; 
Proactor调用aoi_write后立刻返回,由内核负责写操作,写完后调用相应的回调函数处理后续逻辑;

可以看出,Reactor被动的等待指示事件的到来并做出反应;它有一个等待的过程,做什么都要先放入到监听事件集合中等待handler可用时再进行操作; 
Proactor直接调用异步读写操作,调用完后立刻返回;

实现

Reactor实现了一个被动的事件分离和分发模型,服务等待请求事件的到来,再通过不受间断的同步处理事件,从而做出反应;

Proactor实现了一个主动的事件分离和分发模型;这种设计允许多个任务并发的执行,从而提高吞吐量;并可执行耗时长的任务(各个任务间互不影响)

优点

Reactor实现相对简单,对于耗时短的处理场景处理高效; 
操作系统可以在多个事件源上等待,并且避免了多线程编程相关的性能开销和编程复杂性; 
事件的串行化对应用是透明的,可以顺序的同步执行而不需要加锁; 
事务分离:将与应用无关的多路分解和分配机制和与应用相关的回调函数分离开来,

Proactor性能更高,能够处理耗时长的并发场景;

缺点

Reactor处理耗时长的操作会造成事件分发的阻塞,影响到后续事件的处理;

Proactor实现逻辑复杂;依赖操作系统对异步的支持,目前实现了纯异步操作的操作系统少,实现优秀的如windows IOCP,但由于其windows系统用于服务器的局限性,目前应用范围较小;而Unix/Linux系统对纯异步的支持有限,应用事件驱动的主流还是通过select/epoll来实现;

适用场景

Reactor:同时接收多个服务请求,并且依次同步的处理它们的事件驱动程序; 
Proactor:异步接收和同时处理多个服务请求的事件驱动程序;

 

再说Actor模型: 

Actor模型被称为高并发事务的终极解决方案,
实体之通过消息通讯,各自处理自己的数据,能够实现这并行。 
actor模型实例:skynet,Erlang 

Actor模型是一个概念模型,用于处理并发计算。它定义了一系列系统组件应该如何动作和交互的通用规则,最著名的使用这套规则的编程语言是Erlang。

一个Actor指的是一个最基本的计算单元。它能接收一个消息并且基于其执行计算。

这个理念很像面向对象语言,一个对象接收一条消息(方法调用),然后根据接收的消息做事(调用了哪个方法)。

Actors一大重要特征在于actors之间相互隔离,它们并不互相共享内存。这点区别于上述的对象。也就是说,一个actor能维持一个私有的状态,并且这个状态不可能被另一个actor所改变。

 

思路方向:

其实无论是使用数据库锁 还是多线程,这里有一个共同思路,就是将数据喂给线程,就如同计算机是一套加工流水线,数据作为原材料投入这个流水线的开始,流水线出来后就是成品,这套模式的前提是数据是被动的,自身不复杂,没有自身业务逻辑要求。适合大数据处理或互联网网站应用等等。

但是如果数据自身要求有严格的一致性,也就是事务机制,数据就不能被动被加工,要让数据自己有行为能力保护实现自己的一致性,就像孩子小的时候可以任由爸妈怎么照顾关心都可以,但是如果孩子长大有自己的思想和要求,他就可能不喜欢被爸妈照顾,他要求自己通过行动实现自己的要求。

数据也是如此。

只有我们改变思路,让数据自己有行为维护自己的一致性,才能真正安全实现真正的事务。

我们可以看到

Actor模型=数据+行为+消息。

Actor模型内部的状态由自己的行为维护,外部线程不能直接调用对象的行为,必须通过消息才能激发行为,这样就保证Actor内部数据只有被自己修改。

 

参考:

reactor和proactor模式(epoll和iocp)

为什么Actor模型是高并发事务的终极解决方案?

 

MySQL高可用方案MHA的部署和原理 - iVictor - 博客园

$
0
0

MHA(Master High Availability)是一套相对成熟的MySQL高可用方案,能做到在0~30s内自动完成数据库的故障切换操作,在master服务器不宕机的情况下,基本能保证数据的一致性。

 

它由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。其中,MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave上。MHA Node则运行在每个mysql节点上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它自动将最新数据的slave提升为master,然后将其它所有的slave指向新的master。

 

在MHA自动故障切换过程中,MHA试图保存master的二进制日志,从而最大程度地保证数据不丢失,当这并不总是可行的,譬如,主服务器硬件故障或无法通过ssh访问,MHA就没法保存二进制日志,这样就只进行了故障转移但丢失了最新数据。可结合MySQL 5.5中推出的半同步复制来降低数据丢失的风险。

 

MHA软件由两部分组成:Manager工具包和Node工具包,具体说明如下:

MHA Manager:

1. masterha_check_ssh:检查MHA的SSH配置状况

2. masterha_check_repl:检查MySQL的复制状况

3. masterha_manager:启动MHA

4. masterha_check_status:检测当前MHA运行状态

5. masterha_master_monitor:检测master是否宕机

6. masterha_master_switch:控制故障转移(自动或手动)

7. masterha_conf_host:添加或删除配置的server信息

8. masterha_stop:关闭MHA

 

MHA Node:

save_binary_logs:保存或复制master的二进制日志

apply_diff_relay_logs:识别差异的relay log并将差异的event应用到其它slave中

filter_mysqlbinlog:去除不必要的ROLLBACK事件(MHA已不再使用这个工具)

purge_relay_logs:消除中继日志(不会堵塞SQL线程)

 

另有如下几个脚本需自定义:

1. master_ip_failover:管理VIP

2. master_ip_online_change:

3. masterha_secondary_check:当MHA manager检测到master不可用时,通过masterha_secondary_check脚本来进一步确认,减低误切的风险。

4. send_report:当发生故障切换时,可通过send_report脚本发送告警信息。

 

集群信息

角色                             IP地址                 ServerID      类型

Master                         192.168.244.10   1                 写入

Candicate master          192.168.244.20   2                 读

Slave                           192.168.244.30   3                 读

Monitor host                 192.168.244.40                      监控集群组

注:操作系统均为RHEL 6.7

其中,master对外提供写服务,备选master提供读服务,slave也提供相关的读服务,一旦master宕机,将会把备选master提升为新的master,slave指向新的master

 

一、在所有节点上安装MHA node

    1. 在MySQL服务器上安装MHA node所需的perl模块(DBD:mysql)

      # yum install perl-DBD-MySQL -y

    2. 在所有的节点上安装mha node

      下载地址为:https://code.google.com/p/mysql-master-ha/wiki/Downloads?tm=2

      由于该网址在国内被墙,相关文件下载后,放到了个人网盘中,http://pan.baidu.com/s/1boS31vT,有需要的童鞋可自行下载。

      # tar xvf mha4mysql-node-0.56.tar.gz

      # cd mha4mysql-node-0.56

      # perl Makefile.PL  

复制代码
Can't locate ExtUtils/MakeMaker.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at inc/Module/Install/Can.pm line 6.
BEGIN failed--compilation aborted at inc/Module/Install/Can.pm line 6.
Compilation failed in require at inc/Module/Install.pm line 283.
Can't locate ExtUtils/MakeMaker.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at inc/Module/Install/Makefile.pm line 4.
BEGIN failed--compilation aborted at inc/Module/Install/Makefile.pm line 4.
Compilation failed in require at inc/Module/Install.pm line 283.
Can't locate ExtUtils/MM_Unix.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at inc/Module/Install/Metadata.pm line 349.
复制代码

     通过报错可以看出,是相关依赖包没有安装。

     # yum install perl-ExtUtils-MakeMaker -y

     # perl Makefile.PL  

*** Module::AutoInstall version 1.03
*** Checking for Perl dependencies...
Can't locate CPAN.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at inc/Module/AutoInstall.pm line 277.

    # yum install perl-CPAN -y

    # perl Makefile.PL

复制代码
*** Module::AutoInstall version 1.03
*** Checking for Perl dependencies...
[Core Features]
- DBI        ...loaded. (1.609)
- DBD::mysql ...loaded. (4.013)
*** Module::AutoInstall configuration finished.
Checking if your kit is complete...
Looks good
Writing Makefile for mha4mysql::node
复制代码

     # make 

     # make install

    至此,MHA node节点安装完毕,会在/usr/local/bin下生成以下脚本文件

# ll /usr/local/bin/
total 44
-r-xr-xr-x 1 root root 16367 Jul 20 07:00 apply_diff_relay_logs
-r-xr-xr-x 1 root root  4807 Jul 20 07:00 filter_mysqlbinlog
-r-xr-xr-x 1 root root  8261 Jul 20 07:00 purge_relay_logs
-r-xr-xr-x 1 root root  7525 Jul 20 07:00 save_binary_logs

    

二、在Monitor host节点上部署MHA Manager

     # tar xvf mha4mysql-manager-0.56.tar.gz 

     # cd mha4mysql-manager-0.56

     # perl Makefile.PL

复制代码
*** Module::AutoInstall version 1.03
*** Checking for Perl dependencies...
[Core Features]
- DBI                   ...loaded. (1.609)
- DBD::mysql            ...loaded. (4.013)
- Time::HiRes           ...missing.
- Config::Tiny          ...missing.
- Log::Dispatch         ...missing.
- Parallel::ForkManager ...missing.
- MHA::NodeConst        ...missing.
==> Auto-install the 5 mandatory module(s) from CPAN? [y] y
*** Dependencies will be installed the next time you type 'make'.
*** Module::AutoInstall configuration finished.
Checking if your kit is complete...
Looks good
Warning: prerequisite Config::Tiny 0 not found.
Warning: prerequisite Log::Dispatch 0 not found.
Warning: prerequisite MHA::NodeConst 0 not found.
Warning: prerequisite Parallel::ForkManager 0 not found.
Warning: prerequisite Time::HiRes 0 not found.
Writing Makefile for mha4mysql::manager
复制代码

     # make

     # make install

    执行完毕后,会在/usr/local/bin下新增以下几个文件  

复制代码
# ll /usr/local/bin/
total 40
-r-xr-xr-x 1 root root 1991 Jul 20 00:50 masterha_check_repl
-r-xr-xr-x 1 root root 1775 Jul 20 00:50 masterha_check_ssh
-r-xr-xr-x 1 root root 1861 Jul 20 00:50 masterha_check_status
-r-xr-xr-x 1 root root 3197 Jul 20 00:50 masterha_conf_host
-r-xr-xr-x 1 root root 2513 Jul 20 00:50 masterha_manager
-r-xr-xr-x 1 root root 2161 Jul 20 00:50 masterha_master_monitor
-r-xr-xr-x 1 root root 2369 Jul 20 00:50 masterha_master_switch
-r-xr-xr-x 1 root root 5167 Jul 20 00:50 masterha_secondary_check
-r-xr-xr-x 1 root root 1735 Jul 20 00:50 masterha_stop
复制代码

 

三、配置SSH登录无密码验证

    1. 在manager上配置到所有Node节点的无密码验证

      # ssh-keygen

      一路按“Enter”

     # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.10

     # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.20

     # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.30

    2. 在Master(192.168.244.10)上配置

    # ssh-keygen

    # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.20

    # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.30

    3. 在Candicate master(192.168.244.20)上配置     

    # ssh-keygen

    # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.10

    # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.30

     4. 在Slave(192.168.244.30)上配置     

    # ssh-keygen

    # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.10

    # ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.244.20

 

四、搭建主从复制环境

     1. 在Master上执行备份

     # mysqldump --master-data=2 --single-transaction -R --triggers -A > all.sql

     其中,-R是备份存储过程,--triggers是备份触发器 -A代表全库

     2. 在Master上创建复制用户

mysql> grant replication slave on *.* to 'repl'@'192.168.244.%' identified by 'repl';
Query OK, 0 rows affected (0.09 sec)

    3. 查看备份文件all.sql中的CHANGE MASTER语句

      # head -n 30 all.sql

-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=120;

     4. 将备份文件复制到Candicate master和Slave上

     # scp all.sql 192.168.244.20:/root/

     # scp all.sql 192.168.244.30:/root/

     5. 在Candicate master上搭建从库

     # mysql < all.sql 

     设置复制信息

复制代码
mysql> CHANGE MASTER TO
    -> MASTER_HOST='192.168.244.10',
    -> MASTER_USER='repl',
    -> MASTER_PASSWORD='repl',
    -> MASTER_LOG_FILE='mysql-bin.000002',
    -> MASTER_LOG_POS=120;
Query OK, 0 rows affected, 2 warnings (0.19 sec)

mysql> start slave;
Query OK, 0 rows affected (0.02 sec)

mysql> show slave status\G
复制代码

       6. 在Slave上搭建从库

       7. slave服务器设置为read only

mysql> set global read_only=1;
Query OK, 0 rows affected (0.04 sec)

       8. 在Master中创建监控用户

mysql> grant all privileges on *.* to 'monitor'@'%' identified by 'monitor123';
Query OK, 0 rows affected (0.07 sec)

 

五、 配置MHA

     1. 在Monitor host(192.168.244.40)上创建MHA工作目录,并且创建相关配置文件

     # mkdir -p /etc/masterha

     # vim /etc/masterha/app1.cnf

复制代码
[server default]
manager_log=/masterha/app1/manager.log          //设置manager的日志
manager_workdir=/masterha/app1           //设置manager的工作目录
master_binlog_dir=/var/lib/mysql                  //设置master默认保存binlog的位置,以便MHA可以找到master的日志
master_ip_failover_script= /usr/local/bin/master_ip_failover    //设置自动failover时候的切换脚本
master_ip_online_change_script= /usr/local/bin/master_ip_online_change  //设置手动切换时候的切换脚本
user=monitor               // 设置监控用户
password=monitor123         //设置监控用户的密码
ping_interval=1         //设置监控主库,发送ping包的时间间隔,默认是3秒,尝试三次没有回应的时候进行自动failover
remote_workdir=/tmp     //设置远端mysql在发生切换时binlog的保存位置
repl_user=repl          //设置复制环境中的复制用户名
repl_password=repl    //设置复制用户的密码
report_script=/usr/local/bin/send_report    //设置发生切换后发送的报警的脚本
secondary_check_script= /usr/local/bin/masterha_secondary_check -s 192.168.244.20 -s 192.168.244.30 --user=root --master_host=192.168.244.10 --master_ip=192.168.244.10 --master_port=3306  //一旦MHA到master的监控之间出现问题,MHA Manager将会判断其它两个slave是否能建立到master_ip 3306端口的连接
shutdown_script=""      //设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机防止发生脑裂)
ssh_user=root           //设置ssh的登录用户名

[server1]
hostname=192.168.244.10
port=3306

[server2]
hostname=192.168.244.20
port=3306
candidate_master=1   //设置为候选master,如果设置该参数以后,发生主从切换以后将会将此从库提升为主库,即使这个主库不是集群中最新的slave
check_repl_delay=0   //默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master,因为对于这个slave的恢复需要花费很长时间,通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时,这个参数对于设置了candidate_master=1的主机非常有用,因为它保证了这个候选主在切换过程中一定是最新的master

[server3]
hostname=192.168.244.30
port=3306
复制代码

       注意:

      1> 在编辑该文件时,后面的注释切记要去掉,MHA并不会将后面的内容识别为注释。

      2> 配置文件中设置了master_ip_failover_script,secondary_check_script,master_ip_online_change_script,report_script,对应的文件见文章末 尾。

      2. 设置relay log清除方式(在每个Slave上)

mysql> set global relay_log_purge=0;
Query OK, 0 rows affected (0.00 sec)

      MHA在发生切换过程中,从库在恢复的过程中,依赖于relay log的相关信息,所以我们这里要将relay log的自动清楚设置为OFF,采用手动清楚relay log的方式。

      在默认情况下,从服务器上的中继日志会在SQL线程执行完后被自动删除。但是在MHA环境中,这些中继日志在恢复其它从服务器时可能会被用到,因此需要禁用中继日志的自动清除。改为定期手动清除SQL线程应用完的中继日志。

      在ext3文件系统下,删除大的文件需要一定的时间,这样会导致严重的复制延迟,所以在Linux中,一般都是通过硬链接的方式来删除大文件。

      3. 设置定期清理relay脚本

         MHA节点中包含了purge_relay_logs脚本,它可以为relay log创建硬链接,执行set global relay_log_purge=1,等待几秒钟以便SQL线程切换到新的中继日志,再执行set global relay_log_purge=0。

         下面看看脚本的使用方法:

         # purge_relay_logs --user=monitor --password=monitor123 -disable_relay_log_purge --workdir=/tmp/

复制代码
2017-04-24 20:27:46: purge_relay_logs script started.
 Found relay_log.info: /var/lib/mysql/relay-log.info
 Opening /var/lib/mysql/mysqld-relay-bin.000001 ..
 Opening /var/lib/mysql/mysqld-relay-bin.000002 ..
 Opening /var/lib/mysql/mysqld-relay-bin.000003 ..
 Opening /var/lib/mysql/mysqld-relay-bin.000004 ..
 Opening /var/lib/mysql/mysqld-relay-bin.000005 ..
 Opening /var/lib/mysql/mysqld-relay-bin.000006 ..
 Executing SET GLOBAL relay_log_purge=1; FLUSH LOGS; sleeping a few seconds so that SQL thread can delete older relay log files (if i
t keeps up); SET GLOBAL relay_log_purge=0; .. ok.2017-04-24 20:27:50: All relay log purging operations succeeded.
复制代码

        其中,

        --user:mysql用户名

        --password:mysql用户的密码

        --host: mysqlserver地址

        --workdir:指定创建relay log的硬链接的位置,默认的是/var/tmp。由于系统不同分区创建硬链接文件会失败,故需要指定具体的硬链接的位置。

        --disable_relay_log_purge:默认情况下,如果relay_log_purge=1,则脚本会直接退出。通过设置这个参数,该脚本会首先将relay_log_purge设置为1,清除掉relay log后,再将该参数设置为0。

        设置crontab来定期清理relay log

        MHA在切换的过程中会直接调用mysqlbinlog命令,故需要在环境变量中指定mysqlbinlog的具体路径。

        # vim /etc/cron.d/purge_relay_logs

0 4 * * * /usr/local/bin/purge_relay_logs --user=monitor --password=monitor123 -disable_relay_log_purge --workdir=/tmp/ >> /tmp/purge
_relay_logs.log 2>&1

       注意:最好是每台slave服务器在不同时间点执行该计划任务。

      4. 将mysqlbinlog的路径添加到环境变量中

 

六、 检查SSH的配置

       在Monitor host上执行

       # masterha_check_ssh --conf=/etc/masterha/app1.cnf

复制代码
Wed Jul 20 14:33:36 2016 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Jul 20 14:33:36 2016 - [info] Reading application default configuration from /etc/masterha/app1.cnf..
Wed Jul 20 14:33:36 2016 - [info] Reading server configuration from /etc/masterha/app1.cnf..
Wed Jul 20 14:33:36 2016 - [info] Starting SSH connection tests..
Wed Jul 20 14:33:51 2016 - [debug] 
Wed Jul 20 14:33:36 2016 - [debug]  Connecting via SSH from root@192.168.244.10(192.168.244.10:22) to root@192.168.244.20(192.168.244.20:22)..
Wed Jul 20 14:33:48 2016 - [debug]   ok.
Wed Jul 20 14:33:48 2016 - [debug]  Connecting via SSH from root@192.168.244.10(192.168.244.10:22) to root@192.168.244.30(192.168.244.30:22)..
Wed Jul 20 14:33:50 2016 - [debug]   ok.
Wed Jul 20 14:33:55 2016 - [debug] 
Wed Jul 20 14:33:37 2016 - [debug]  Connecting via SSH from root@192.168.244.30(192.168.244.30:22) to root@192.168.244.10(192.168.244.10:22)..
Wed Jul 20 14:33:49 2016 - [debug]   ok.
Wed Jul 20 14:33:49 2016 - [debug]  Connecting via SSH from root@192.168.244.30(192.168.244.30:22) to root@192.168.244.20(192.168.244.20:22)..
Wed Jul 20 14:33:54 2016 - [debug]   ok.
Wed Jul 20 14:33:55 2016 - [debug] 
Wed Jul 20 14:33:36 2016 - [debug]  Connecting via SSH from root@192.168.244.20(192.168.244.20:22) to root@192.168.244.10(192.168.244.10:22)..
Wed Jul 20 14:33:49 2016 - [debug]   ok.
Wed Jul 20 14:33:49 2016 - [debug]  Connecting via SSH from root@192.168.244.20(192.168.244.20:22) to root@192.168.244.30(192.168.244.30:22)..
Wed Jul 20 14:33:54 2016 - [debug]   ok.
Wed Jul 20 14:33:55 2016 - [info] All SSH connection tests passed successfully.
复制代码

 

七、查看整个集群的状态

     在Monitor host上执行

     # masterha_check_repl --conf=/etc/masterha/app1.cnf

复制代码
Wed Jul 20 14:44:30 2016 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Jul 20 14:44:30 2016 - [info] Reading application default configuration from /etc/masterha/app1.cnf..
Wed Jul 20 14:44:30 2016 - [info] Reading server configuration from /etc/masterha/app1.cnf..
Wed Jul 20 14:44:30 2016 - [info] MHA::MasterMonitor version 0.56.
Wed Jul 20 14:44:31 2016 - [info] GTID failover mode = 0
Wed Jul 20 14:44:31 2016 - [info] Dead Servers:
Wed Jul 20 14:44:31 2016 - [info] Alive Servers:
Wed Jul 20 14:44:31 2016 - [info]   192.168.244.10(192.168.244.10:3306)
Wed Jul 20 14:44:31 2016 - [info]   192.168.244.20(192.168.244.20:3306)
Wed Jul 20 14:44:31 2016 - [info]   192.168.244.30(192.168.244.30:3306)
Wed Jul 20 14:44:31 2016 - [info] Alive Slaves:
Wed Jul 20 14:44:31 2016 - [info]   192.168.244.20(192.168.244.20:3306)  Version=5.6.31 (oldest major version between slaves) log-bin:disabled
Wed Jul 20 14:44:31 2016 - [info]     Replicating from 192.168.244.10(192.168.244.10:3306)
Wed Jul 20 14:44:31 2016 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Jul 20 14:44:31 2016 - [info]   192.168.244.30(192.168.244.30:3306)  Version=5.6.31 (oldest major version between slaves) log-bin:disabled
Wed Jul 20 14:44:31 2016 - [info]     Replicating from 192.168.244.10(192.168.244.10:3306)
Wed Jul 20 14:44:31 2016 - [info] Current Alive Master: 192.168.244.10(192.168.244.10:3306)
Wed Jul 20 14:44:31 2016 - [info] Checking slave configurations..
Wed Jul 20 14:44:31 2016 - [warning]  log-bin is not set on slave 192.168.244.20(192.168.244.20:3306). This host cannot be a master.
Wed Jul 20 14:44:31 2016 - [warning]  log-bin is not set on slave 192.168.244.30(192.168.244.30:3306). This host cannot be a master.
Wed Jul 20 14:44:31 2016 - [info] Checking replication filtering settings..
Wed Jul 20 14:44:31 2016 - [info]  binlog_do_db= , binlog_ignore_db= 
Wed Jul 20 14:44:31 2016 - [info]  Replication filtering check ok.
Wed Jul 20 14:44:31 2016 - [error][/usr/local/share/perl5/MHA/MasterMonitor.pm, ln361] None of slaves can be master. Check failover configuration file or log-bin settings in my.cnf
Wed Jul 20 14:44:31 2016 - [error][/usr/local/share/perl5/MHA/MasterMonitor.pm, ln424] Error happened on checking configurations.  at /usr/local/bin/masterha_check_repl line 48.
Wed Jul 20 14:44:31 2016 - [error][/usr/local/share/perl5/MHA/MasterMonitor.pm, ln523] Error happened on monitoring servers.
Wed Jul 20 14:44:31 2016 - [info] Got exit code 1 (Not master dead).

MySQL Replication Health is NOT OK!
复制代码

    报错很明显,Candicate master和Slave都没有启动log-bin,如果没有启动的话,后续就无法提升为主

    设置log-bin后,重新执行:

复制代码
Wed Jul 20 15:49:58 2016 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Jul 20 15:49:58 2016 - [info] Reading application default configuration from /etc/masterha/app1.cnf..
Wed Jul 20 15:49:58 2016 - [info] Reading server configuration from /etc/masterha/app1.cnf..
Wed Jul 20 15:49:58 2016 - [info] MHA::MasterMonitor version 0.56.
Wed Jul 20 15:49:59 2016 - [info] GTID failover mode = 0
Wed Jul 20 15:49:59 2016 - [info] Dead Servers:
Wed Jul 20 15:49:59 2016 - [info] Alive Servers:
Wed Jul 20 15:49:59 2016 - [info]   192.168.244.10(192.168.244.10:3306)
Wed Jul 20 15:49:59 2016 - [info]   192.168.244.20(192.168.244.20:3306)
Wed Jul 20 15:49:59 2016 - [info]   192.168.244.30(192.168.244.30:3306)
Wed Jul 20 15:49:59 2016 - [info] Alive Slaves:
Wed Jul 20 15:49:59 2016 - [info]   192.168.244.20(192.168.244.20:3306)  Version=5.6.31-log (oldest major version between slaves) log-bin:enabled
Wed Jul 20 15:49:59 2016 - [info]     Replicating from 192.168.244.10(192.168.244.10:3306)
Wed Jul 20 15:49:59 2016 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Jul 20 15:49:59 2016 - [info]   192.168.244.30(192.168.244.30:3306)  Version=5.6.31-log (oldest major version between slaves) log-bin:enabled
Wed Jul 20 15:49:59 2016 - [info]     Replicating from 192.168.244.10(192.168.244.10:3306)
Wed Jul 20 15:49:59 2016 - [info] Current Alive Master: 192.168.244.10(192.168.244.10:3306)
Wed Jul 20 15:49:59 2016 - [info] Checking slave configurations..
Wed Jul 20 15:49:59 2016 - [info] Checking replication filtering settings..
Wed Jul 20 15:49:59 2016 - [info]  binlog_do_db= , binlog_ignore_db= 
Wed Jul 20 15:49:59 2016 - [info]  Replication filtering check ok.
Wed Jul 20 15:49:59 2016 - [info] GTID (with auto-pos) is not supported
Wed Jul 20 15:49:59 2016 - [info] Starting SSH connection tests..
Wed Jul 20 15:50:17 2016 - [info] All SSH connection tests passed successfully.
Wed Jul 20 15:50:17 2016 - [info] Checking MHA Node version..
Wed Jul 20 15:50:18 2016 - [info]  Version check ok.
Wed Jul 20 15:50:18 2016 - [info] Checking SSH publickey authentication settings on the current master..
Wed Jul 20 15:50:20 2016 - [info] HealthCheck: SSH to 192.168.244.10 is reachable.
Wed Jul 20 15:50:21 2016 - [info] Master MHA Node version is 0.56.
Wed Jul 20 15:50:21 2016 - [info] Checking recovery script configurations on 192.168.244.10(192.168.244.10:3306)..
Wed Jul 20 15:50:21 2016 - [info]   Executing command: save_binary_logs --command=test --start_pos=4 --binlog_dir=/var/lib/mysql --output_file=/tmp/save_binary_logs_test --manager_version=0.56 --start_file=mysqld-bin.000002 
Wed Jul 20 15:50:21 2016 - [info]   Connecting to root@192.168.244.10(192.168.244.10:22).. 
  Creating /tmp if not exists..    ok.
  Checking output directory is accessible or not..
   ok.
  Binlog found at /var/lib/mysql, up to mysqld-bin.000002
Wed Jul 20 15:50:23 2016 - [info] Binlog setting check done.
Wed Jul 20 15:50:23 2016 - [info] Checking SSH publickey authentication and checking recovery script configurations on all alive slave servers..
Wed Jul 20 15:50:23 2016 - [info]   Executing command : apply_diff_relay_logs --command=test --slave_user='monitor' --slave_host=192.168.244.20 --slave_ip=192.168.244.20 --slave_port=3306 --workdir=/tmp --target_version=5.6.31-log --manager_version=0.56 --relay_log_info=/var/lib/mysql/relay-log.info  --relay_dir=/var/lib/mysql/  --slave_pass=xxx
Wed Jul 20 15:50:23 2016 - [info]   Connecting to root@192.168.244.20(192.168.244.20:22).. 
  Checking slave recovery environment settings..
    Opening /var/lib/mysql/relay-log.info ... ok.
    Relay log found at /var/lib/mysql, up to mysqld-relay-bin.000004
    Temporary relay log file is /var/lib/mysql/mysqld-relay-bin.000004
    Testing mysql connection and privileges..Warning: Using a password on the command line interface can be insecure.
 done.
    Testing mysqlbinlog output.. done.
    Cleaning up test file(s).. done.
Wed Jul 20 15:50:28 2016 - [info]   Executing command : apply_diff_relay_logs --command=test --slave_user='monitor' --slave_host=192.168.244.30 --slave_ip=192.168.244.30 --slave_port=3306 --workdir=/tmp --target_version=5.6.31-log --manager_version=0.56 --relay_log_info=/var/lib/mysql/relay-log.info  --relay_dir=/var/lib/mysql/  --slave_pass=xxx
Wed Jul 20 15:50:28 2016 - [info]   Connecting to root@192.168.244.30(192.168.244.30:22).. 
  Checking slave recovery environment settings..
    Opening /var/lib/mysql/relay-log.info ... ok.
    Relay log found at /var/lib/mysql, up to mysqld-relay-bin.000008
    Temporary relay log file is /var/lib/mysql/mysqld-relay-bin.000008
    Testing mysql connection and privileges..Warning: Using a password on the command line interface can be insecure.
 done.
    Testing mysqlbinlog output.. done.
    Cleaning up test file(s).. done.
Wed Jul 20 15:50:32 2016 - [info] Slaves settings check done.
Wed Jul 20 15:50:32 2016 - [info] 
192.168.244.10(192.168.244.10:3306) (current master)
 +--192.168.244.20(192.168.244.20:3306)
 +--192.168.244.30(192.168.244.30:3306)

Wed Jul 20 15:50:32 2016 - [info] Checking replication health on 192.168.244.20..
Wed Jul 20 15:50:32 2016 - [info]  ok.
Wed Jul 20 15:50:32 2016 - [info] Checking replication health on 192.168.244.30..
Wed Jul 20 15:50:32 2016 - [info]  ok.
Wed Jul 20 15:50:32 2016 - [info] Checking master_ip_failover_script status:
Wed Jul 20 15:50:32 2016 - [info]   /usr/local/bin/master_ip_failover --command=status --ssh_user=root --orig_master_host=192.168.244.10 --orig_master_ip=192.168.244.10 --orig_master_port=3306 
Wed Jul 20 15:50:32 2016 - [info]  OK.
Wed Jul 20 15:50:32 2016 - [warning] shutdown_script is not defined.
Wed Jul 20 15:50:32 2016 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.
复制代码

    检查通过~

 

八、 检查MHA Manager的状态

# masterha_check_status --conf=/etc/masterha/app1.cnf 
app1 is stopped(2:NOT_RUNNING).  

        如果正常,会显示“PING_OK”,否则会显示“NOT_RUNNING”,代表MHA监控还没有开启。

 

九、开启MHA Manager监控

      # nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /masterha/app1/manager.log 2>&1 &

      其中,

      remove_dead_master_conf:该参数代表当发生主从切换后,老的主库的IP将会从配置文件中移除。

      ignore_last_failover:在默认情况下,MHA发生切换后将会在/masterha/app1下产生app1.failover.complete文件,下次再次切换的时候如果发现该目录下存在该文件且两次切换的时间间隔不足8小时的话,将不允许触发切换。除非在第一次切换后手动rm -rf /masterha/app1/app1.failover.complete。该参数代表忽略上次MHA触发切换产生的文件。

     查看MHA Manager监控是否正常

# masterha_check_status --conf=/etc/masterha/app1.cnf 
app1 (pid:1873) is running(0:PING_OK), master:192.168.244.10

 

十、 关闭MHA Manager监控

# masterha_stop --conf=/etc/masterha/app1.cnf 
Stopped app1 successfully.
[1]+  Exit 1                  nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /masterha/app1/manager.log 2>&1

至此,MHA部分配置完毕,下面,来配置VIP。

 

十一、VIP配置

VIP配置可以采用两种方式,一是通过引入Keepalived来管理VIP,另一种是在脚本中手动管理。

对于keepalived管理VIP,存在脑裂情况,即当主从网络出现问题时,slave会抢占VIP,这样会导致主从数据库都持有VIP,造成IP冲突,所以在网络不是很好的情况下,不建议采用keepalived服务。

在实际生产中使用较多的也是第二种,即在脚本中手动管理VIP,所以,对keepalived不感兴趣的童鞋可直接跳过第一种方式。

1. keepalived管理VIP

1> 安装keepalived

    因为我这里设置了Candicate master,故只在Master和Candicate master上安装。

    如果没有Candicate master,两个Slave的地位平等,则两个Slave上都需安装keepalived。

    # wget http://www.keepalived.org/software/keepalived-1.2.24.tar.gz

    # tar xvf keepalived-1.2.24.tar.gz

    # cd keepalived-1.2.24

    # ./configure --prefix=/usr/local/keepalived

    # make

    # make install

    # cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/rc.d/init.d/

    # cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/

    # mkdir /etc/keepalived

    # cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/

    # cp /usr/local/keepalived/sbin/keepalived /usr/sbin/

2> 为keepalived设置单独的日志文件(非必需)

     keepalived的日志默认是输出到/var/log/message中

     # vim /etc/sysconfig/keepalived

KEEPALIVED_OPTIONS="-D -d -S 0"

     设置syslog

     # vim /etc/rsyslog.conf

     添加如下内容:

local0.*           /var/log/keepalived.log

    # service rsyslog restart 

2> 配置keepalived

     在Master上修改

     # vim /etc/keepalived/keepalived.conf

复制代码
global_defs {
   notification_email {
     slowtech@qq.com 
   }
   notification_email_from root@localhost.localdomain 
   smtp_server 127.0.0.1 
   smtp_connect_timeout 30
   router_id MySQL-HA
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    nopreempt
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.244.188/24
    }
}
复制代码

    关于keepalived的参数的详细介绍,可参考:

     LVS+Keepalived搭建MyCAT高可用负载均衡集群

     keepalived工作原理和配置说明

    将配置文件scp到Candicate master上

    # scp /etc/keepalived/keepalived.conf 192.168.244.20:/etc/keepalived/

     只需将配置文件中的priority设置为90

    注意:我们为什么在这里设置keepalived为backup模式呢?

    在master-backup模式下,如果主库宕掉,VIP会自动漂移到Slave上,当主库修复,keepalived启动后,还会将VIP抢过来,即使设置了nopreempt(不抢占)的方

    式,该动作仍会发生。但在backup-backup模式下,当主库修改,并启动keepalived后,并不会抢占新主的VIP,即便原主的priority高于新主的。

3> 启动keepalived

     先在Master上启动

     # service keepalived start

env: /etc/init.d/keepalived: Permission denied

     # chmod +x /etc/init.d/keepalived

     # service keepalived start

     查看绑定情况

     # ip a

复制代码
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:c6:47:04 brd ff:ff:ff:ff:ff:ff
    inet 192.168.244.10/24 brd 192.168.244.255 scope global eth0
    inet 192.168.244.188/24 scope global secondary eth0
    inet6 fe80::20c:29ff:fec6:4704/64 scope link 
       valid_lft forever preferred_lft forever
复制代码

     可见,VIP(192168.244.188)已经绑定到Master的eth0网卡上了。

     启动Candicate master的keepalived

     # service keepalived start

4> MHA中引入keepalived

     编辑/usr/local/bin/master_ip_failover

     相对于原文件,修改地方为93-95行

复制代码
  1 #!/usr/bin/env perl
  2 
  3 #  Copyright (C) 2011 DeNA Co.,Ltd.
  4 #
  5 #  This program is free software; you can redistribute it and/or modify
  6 #  it under the terms of the GNU General Public License as published by
  7 #  the Free Software Foundation; either version 2 of the License, or
  8 #  (at your option) any later version.
  9 #
 10 #  This program is distributed in the hope that it will be useful,
 11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
 12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13 #  GNU General Public License for more details.
 14 #
 15 #  You should have received a copy of the GNU General Public License
 16 #   along with this program; if not, write to the Free Software
 17 #  Foundation, Inc.,
 18 #  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 19 
 20 ## Note: This is a sample script and is not complete. Modify the script based on your environment.
 21 
 22 use strict;
 23 use warnings FATAL => 'all';
 24 
 25 use Getopt::Long;
 26 use MHA::DBHelper;
 27 my (
 28   $command,        $ssh_user,         $orig_master_host,
 29   $orig_master_ip, $orig_master_port, $new_master_host,
 30   $new_master_ip,  $new_master_port,  $new_master_user,
 31   $new_master_password
 32 );
 33 
 34 GetOptions(
 35   'command=s'             => \$command,
 36   'ssh_user=s'            => \$ssh_user,
 37   'orig_master_host=s'    => \$orig_master_host,
 38   'orig_master_ip=s'      => \$orig_master_ip,
 39   'orig_master_port=i'    => \$orig_master_port,
 40   'new_master_host=s'     => \$new_master_host,
 41   'new_master_ip=s'       => \$new_master_ip,
 42   'new_master_port=i'     => \$new_master_port,
 43   'new_master_user=s'     => \$new_master_user,
 44   'new_master_password=s' => \$new_master_password,
 45 );
 46 
 47 exit &main();
 48 
 49 sub main {
 50   if ( $command eq "stop" || $command eq "stopssh" ) {
 51 
 52     # $orig_master_host, $orig_master_ip, $orig_master_port are passed.
 53     # If you manage master ip address at global catalog database,
 54     # invalidate orig_master_ip here.
 55     my $exit_code = 1;
 56     eval {
 57 
 58       # updating global catalog, etc
 59       $exit_code = 0;
 60     };
 61     if ($@) {
 62       warn "Got Error: $@\n";
 63       exit $exit_code;
 64     }
 65     exit $exit_code;
 66   }
 67   elsif ( $command eq "start" ) {
 68 
 69     # all arguments are passed.
 70     # If you manage master ip address at global catalog database,
 71     # activate new_master_ip here.
 72     # You can also grant write access (create user, set read_only=0, etc) here.
 73     my $exit_code = 10;
 74     eval {
 75       my $new_master_handler = new MHA::DBHelper();
 76 
 77       # args: hostname, port, user, password, raise_error_or_not
 78       $new_master_handler->connect( $new_master_ip, $new_master_port,
 79         $new_master_user, $new_master_password, 1 );
 80 
 81       ## Set read_only=0 on the new master
 82       $new_master_handler->disable_log_bin_local();
 83       print "Set read_only=0 on the new master.\n";
 84       $new_master_handler->disable_read_only();
 85 
 86       ## Creating an app user on the new master
 87       #print "Creating app user on the new master..\n";
 88       #FIXME_xxx_create_user( $new_master_handler->{dbh} );
 89       $new_master_handler->enable_log_bin_local();
 90       $new_master_handler->disconnect();
 91 
 92       ## Update master ip on the catalog database, etc
 93       my $cmd;
 94       $cmd = 'ssh '.$ssh_user.'@'.$orig_master_ip.' service keepalived stop';
 95       system($cmd);
 96       $exit_code = 0;
 97     };
 98     if ($@) {
 99       warn $@;
100 
101       # If you want to continue failover, exit 10.
102       exit $exit_code;
103     }
104     exit $exit_code;
105   }
106   elsif ( $command eq "status" ) {
107 
108     # do nothing
109     exit 0;
110   }
111   else {
112     &usage();
113     exit 1;
114   }
115 }
116 
117 sub usage {
118   print
119 "Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
120 }
复制代码

 

2. 通过脚本的方式管理VIP

   编辑/usr/local/bin/master_ip_failover

复制代码
#!/usr/bin/env perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.

use strict;
use warnings FATAL => 'all';

use Getopt::Long;
use MHA::DBHelper;
my (
  $command,        $ssh_user,         $orig_master_host,
  $orig_master_ip, $orig_master_port, $new_master_host,
  $new_master_ip,  $new_master_port,  $new_master_user,
  $new_master_password
);

my $vip = '192.168.244.188';
my $key = "2";
my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip/24";
my $ssh_stop_vip = "/sbin/ifconfig eth0:$key down";
my $ssh_send_garp = "/sbin/arping -U $vip -I eth0 -c 1";


GetOptions(
  'command=s'             => \$command,'ssh_user=s'            => \$ssh_user,'orig_master_host=s'    => \$orig_master_host,'orig_master_ip=s'      => \$orig_master_ip,'orig_master_port=i'    => \$orig_master_port,'new_master_host=s'     => \$new_master_host,'new_master_ip=s'       => \$new_master_ip,'new_master_port=i'     => \$new_master_port,'new_master_user=s'     => \$new_master_user,'new_master_password=s' => \$new_master_password,
);

exit &main();

sub main {
  if ( $command eq "stop" || $command eq "stopssh" ) {

    # $orig_master_host, $orig_master_ip, $orig_master_port are passed.
    # If you manage master ip address at global catalog database,
    # invalidate orig_master_ip here.
    my $exit_code = 1;
    eval {
      print "Disabling the VIP an old master: $orig_master_host \n";&stop_vip();
      $exit_code = 0;
    };
    if ($@) {
      warn "Got Error: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "start" ) {

    # all arguments are passed.
    # If you manage master ip address at global catalog database,
    # activate new_master_ip here.
    # You can also grant write access (create user, set read_only=0, etc) here.
    my $exit_code = 10;
    eval {
      
      my $new_master_handler = new MHA::DBHelper();

      # args: hostname, port, user, password, raise_error_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, 1 );

      ## Set read_only=0 on the new master
      $new_master_handler->disable_log_bin_local();
      print "Set read_only=0 on the new master.\n";
      $new_master_handler->disable_read_only();

      ## Creating an app user on the new master
      # print "Creating app user on the new master..\n";
      # FIXME_xxx_create_user( $new_master_handler->{dbh} );
      $new_master_handler->enable_log_bin_local();
      $new_master_handler->disconnect();

      print "Enabling the VIP $vip on the new master: $new_master_host \n";&start_vip();
      $exit_code = 0;
    };
    if ($@) {
      warn $@;

      # If you want to continue failover, exit 10.
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "status" ) {

    # do nothing
    exit 0;
  }
  else {
    &usage();
    exit 1;
  }
}

sub start_vip(){
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
    `ssh $ssh_user\@$new_master_host \" $ssh_send_garp \"`;
}

sub stop_vip(){   
return 0  unless  ($ssh_user); `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`; } sub usage { print "Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n"; }
复制代码

 

实际生产环境中,推荐这种方式来管理VIP,可有效防止脑裂情况的发生。

 

至此,MHA高可用环境基本搭建完毕。

 

关于MHA的常见操作,包括自动Failover,手动Failover,在线切换,可参考另一篇博客:

MHA在线切换的步骤和原理

MHA自动Failover与手动Failover的实践及原理

 

总结:

1. 可单独调试master_ip_failover,master_ip_online_change,send_report等脚本

 /usr/local/bin/master_ip_online_change --command=stop --orig_master_ip=192.168.244.10 --orig_master_host=192.168.244.10 --orig_master_port=3306 --orig_master_user=monitor --orig_master_password=monitor123 --orig_master_ssh_user=root --new_master_host=192.168.244.20 --new_master_ip=192.168.244.20 --new_master_port=3306 --new_master_user=monitor --new_master_password=monitor123 --new_master_ssh_user=root

 

/usr/local/bin/master_ip_failover --command=start --ssh_user=root --orig_master_host=192.168.244.10 --orig_master_ip=192.168.244.10 --orig_master_port=3306 --new_master_host=192.168.244.20 --new_master_ip=192.168.244.20 --new_master_port=3306 --new_master_user='monitor' --new_master_password='monitor123'

 

 2. 官方对于master_ip_failover,master_ip_online_change,send_report脚本,给出的只是sample,切换的逻辑需要自己定义。

     很多童鞋对perl并不熟悉,觉得无从下手,其实,完全可以调用其它脚本,譬如python,shell等。

     如:

复制代码
[root@node4 ~]# cat test.pl
#!/usr/bin/perl
use strict;
my $cmd='python /root/test.py';
system($cmd);

[root@node4 ~]# cat test.py
#!/usr/bin/python
print "hello,python"

[root@node4 ~]# perl test.pl
hello,python
复制代码

 

参考:

《深入浅出MySQL》 

 

附:

master_ip_online_change

复制代码
#!/usr/bin/env perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.

use strict;
use warnings FATAL => 'all';

use Getopt::Long;
use MHA::DBHelper;
use MHA::NodeUtil;
use Time::HiRes qw( sleep gettimeofday tv_interval );
use Data::Dumper;

my $_tstart;
my $_running_interval = 0.1;

my $vip = '192.168.244.188';
my $key = "2";
my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip/24";
my $ssh_stop_vip = "/sbin/ifconfig eth0:$key down";
my $ssh_send_garp = "/sbin/arping -U $vip -I eth0 -c 1";

my (
  $command,              $orig_master_is_new_slave, $orig_master_host,
  $orig_master_ip,       $orig_master_port,         $orig_master_user,
  $orig_master_password, $orig_master_ssh_user,     $new_master_host,
  $new_master_ip,        $new_master_port,          $new_master_user,
  $new_master_password,  $new_master_ssh_user,
);
GetOptions(
  'command=s'                => \$command,'orig_master_is_new_slave' => \$orig_master_is_new_slave,'orig_master_host=s'       => \$orig_master_host,'orig_master_ip=s'         => \$orig_master_ip,'orig_master_port=i'       => \$orig_master_port,'orig_master_user=s'       => \$orig_master_user,'orig_master_password=s'   => \$orig_master_password,'orig_master_ssh_user=s'   => \$orig_master_ssh_user,'new_master_host=s'        => \$new_master_host,'new_master_ip=s'          => \$new_master_ip,'new_master_port=i'        => \$new_master_port,'new_master_user=s'        => \$new_master_user,'new_master_password=s'    => \$new_master_password,'new_master_ssh_user=s'    => \$new_master_ssh_user,
);

exit &main();

sub start_vip(){
    `ssh $new_master_ssh_user\@$new_master_host \" $ssh_start_vip \"`;
    `ssh $new_master_ssh_user\@$new_master_host \" $ssh_send_garp \"`;
}

sub stop_vip(){
    `ssh $orig_master_ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}


sub current_time_us {
  my ( $sec, $microsec ) = gettimeofday();
  my $curdate = localtime($sec);
  return $curdate . " " . sprintf( "%06d", $microsec );
}

sub sleep_until {
  my $elapsed = tv_interval($_tstart);
  if ( $_running_interval > $elapsed ) {
    sleep( $_running_interval - $elapsed );
  }
}

sub get_threads_util {
  my $dbh                    = shift;
  my $my_connection_id       = shift;
  my $running_time_threshold = shift;
  my $type                   = shift;
  $running_time_threshold = 0 unless ($running_time_threshold);
  $type                   = 0 unless ($type);
  my @threads;

  my $sth = $dbh->prepare("SHOW PROCESSLIST");
  $sth->execute();

  while ( my $ref = $sth->fetchrow_hashref() ) {
    my $id         = $ref->{Id};
    my $user       = $ref->{User};
    my $host       = $ref->{Host};
    my $command    = $ref->{Command};
    my $state      = $ref->{State};
    my $query_time = $ref->{Time};
    my $info       = $ref->{Info};
    $info =~ s/^\s*(.*?)\s*$/$1/ if defined($info);
    next if ( $my_connection_id == $id );
    next if ( defined($query_time) && $query_time < $running_time_threshold );
    next if ( defined($command)    && $command eq "Binlog Dump" );
    next if ( defined($user)       && $user eq "system user" );
    next
      if ( defined($command)&& $command eq "Sleep"&& defined($query_time)&& $query_time >= 1 );

    if ( $type >= 1 ) {
      next if ( defined($command) && $command eq "Sleep" );
      next if ( defined($command) && $command eq "Connect" );
    }

    if ( $type >= 2 ) {
      next if ( defined($info) && $info =~ m/^select/i );
      next if ( defined($info) && $info =~ m/^show/i );
    }

    push @threads, $ref;
  }
  return @threads;
}

sub main {
  if ( $command eq "stop" ) {
    ## Gracefully killing connections on the current master
    # 1. Set read_only= 1 on the new master
    # 2. DROP USER so that no app user can establish new connections
    # 3. Set read_only= 1 on the current master
    # 4. Kill current queries
    # * Any database access failure will result in script die.
    my $exit_code = 1;
    eval {
      ## Setting read_only=1 on the new master (to avoid accident)
      my $new_master_handler = new MHA::DBHelper();

      # args: hostname, port, user, password, raise_error(die_on_error)_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, 1 );
      print current_time_us() . " Set read_only on the new master.. ";
      $new_master_handler->enable_read_only();
      if ( $new_master_handler->is_read_only() ) {
        print "ok.\n";
      }
      else {
        die "Failed!\n";
      }
      $new_master_handler->disconnect();

      # Connecting to the orig master, die if any database error happens
      my $orig_master_handler = new MHA::DBHelper();
      $orig_master_handler->connect( $orig_master_ip, $orig_master_port,
        $orig_master_user, $orig_master_password, 1 );

      ## Drop application user so that nobody can connect. Disabling per-session binlog beforehand
      $orig_master_handler->disable_log_bin_local();
      # print current_time_us() . " Drpping app user on the orig master..\n";
      #drop_app_user($orig_master_handler);

      ## Waiting for N * 100 milliseconds so that current connections can exit
      my $time_until_read_only = 15;
      $_tstart = [gettimeofday];
      my @threads = get_threads_util( $orig_master_handler->{dbh},
        $orig_master_handler->{connection_id} );
      while ( $time_until_read_only > 0 && $#threads >= 0 ) {
        if ( $time_until_read_only % 5 == 0 ) {
          printf"%s Waiting all running %d threads are disconnected.. (max %d milliseconds)\n",
            current_time_us(), $#threads + 1, $time_until_read_only * 100;
          if ( $#threads < 5 ) {
            print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . "\n"
              foreach (@threads);
          }
        }
        sleep_until();
        $_tstart = [gettimeofday];
        $time_until_read_only--;
        @threads = get_threads_util( $orig_master_handler->{dbh},
          $orig_master_handler->{connection_id} );
      }

      ## Setting read_only=1 on the current master so that nobody(except SUPER) can write
      print current_time_us() . " Set read_only=1 on the orig master.. ";
      $orig_master_handler->enable_read_only();
      if ( $orig_master_handler->is_read_only() ) {
        print "ok.\n";
      }
      else {
        die "Failed!\n";
      }

      ## Waiting for M * 100 milliseconds so that current update queries can complete
      my $time_until_kill_threads = 5;
      @threads = get_threads_util( $orig_master_handler->{dbh},
        $orig_master_handler->{connection_id} );
      while ( $time_until_kill_threads > 0 && $#threads >= 0 ) {
        if ( $time_until_kill_threads % 5 == 0 ) {
          printf"%s Waiting all running %d queries are disconnected.. (max %d milliseconds)\n",
            current_time_us(), $#threads + 1, $time_until_kill_threads * 100;
          if ( $#threads < 5 ) {
            print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . "\n"
              foreach (@threads);
          }
        }
        sleep_until();
        $_tstart = [gettimeofday];
        $time_until_kill_threads--;
        @threads = get_threads_util( $orig_master_handler->{dbh},
          $orig_master_handler->{connection_id} );
      }

      ## Terminating all threads
      print current_time_us() . " Killing all application threads..\n";
      $orig_master_handler->kill_threads(@threads) if ( $#threads >= 0 );
      print current_time_us() . " done.\n";
      $orig_master_handler->enable_log_bin_local();
      $orig_master_handler->disconnect();

      ## Droping the VIP     
      print "Disabling the VIP an old master: $orig_master_host \n";&stop_vip();

      ## After finishing the script, MHA executes FLUSH TABLES WITH READ LOCK
      $exit_code = 0;
    };
    if ($@) {
      warn "Got Error: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "start" ) {
    ## Activating master ip on the new master
    # 1. Create app user with write privileges
    # 2. Moving backup script if needed
    # 3. Register new master's ip to the catalog database

# We don't return error even though activating updatable accounts/ip failed so that we don't interrupt slaves' recovery.
# If exit code is 0 or 10, MHA does not abort
    my $exit_code = 10;
    eval {
      my $new_master_handler = new MHA::DBHelper();

      # args: hostname, port, user, password, raise_error_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, 1 );

      ## Set read_only=0 on the new master
      $new_master_handler->disable_log_bin_local();
      print current_time_us() . " Set read_only=0 on the new master.\n";
      $new_master_handler->disable_read_only();

      ## Creating an app user on the new master
      #print current_time_us() . " Creating app user on the new master..\n";
      # create_app_user($new_master_handler);
      print "Enabling the VIP $vip on the new master: $new_master_host \n";&start_vip();
      $new_master_handler->enable_log_bin_local();
      $new_master_handler->disconnect();

      ## Update master ip on the catalog database, etc
      $exit_code = 0;
    };
    if ($@) {
      warn "Got Error: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "status" ) {

    # do nothing
    exit 0;
  }
  else {
    &usage();
    exit 1;
  }
}

sub usage {
  print
"Usage: master_ip_online_change --command=start|stop|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
  die;
}
复制代码

 

master_ip_failover

复制代码
#!/usr/bin/env perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.

use strict;
use warnings FATAL => 'all';

use Getopt::Long;
use MHA::DBHelper;
my (
  $command,        $ssh_user,         $orig_master_host,
  $orig_master_ip, $orig_master_port, $new_master_host,
  $new_master_ip,  $new_master_port,  $new_master_user,
  $new_master_password
);

my $vip = '192.168.244.188';
my $key = "2";
my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip/24";
my $ssh_stop_vip = "/sbin/ifconfig eth0:$key down";
my $ssh_send_garp = "/sbin/arping -U $vip -I eth0 -c 1";


GetOptions(
  'command=s'             => \$command,'ssh_user=s'            => \$ssh_user,'orig_master_host=s'    => \$orig_master_host,'orig_master_ip=s'      => \$orig_master_ip,'orig_master_port=i'    => \$orig_master_port,'new_master_host=s'     => \$new_master_host,'new_master_ip=s'       => \$new_master_ip,'new_master_port=i'     => \$new_master_port,'new_master_user=s'     => \$new_master_user,'new_master_password=s' => \$new_master_password,
);

exit &main();

sub main {
  if ( $command eq "stop" || $command eq "stopssh" ) {

    # $orig_master_host, $orig_master_ip, $orig_master_port are passed.
    # If you manage master ip address at global catalog database,
    # invalidate orig_master_ip here.
    my $exit_code = 1;
    eval {
      print "Disabling the VIP an old master: $orig_master_host \n";&stop_vip();
      $exit_code = 0;
    };
    if ($@) {
      warn "Got Error: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "start" ) {

    # all arguments are passed.
    # If you manage master ip address at global catalog database,
    # activate new_master_ip here.
    # You can also grant write access (create user, set read_only=0, etc) here.
    my $exit_code = 10;
    eval {
      
      my $new_master_handler = new MHA::DBHelper();

      # args: hostname, port, user, password, raise_error_or_not
      $new_master_handler->connect( $new_master_ip, $new_master_port,
        $new_master_user, $new_master_password, 1 );

      ## Set read_only=0 on the new master
      $new_master_handler->disable_log_bin_local();
      print "Set read_only=0 on the new master.\n";
      $new_master_handler->disable_read_only();

      ## Creating an app user on the new master
      # print "Creating app user on the new master..\n";
      # FIXME_xxx_create_user( $new_master_handler->{dbh} );
      $new_master_handler->enable_log_bin_local();
      $new_master_handler->disconnect();

      print "Enabling the VIP $vip on the new master: $new_master_host \n";&start_vip();
      $exit_code = 0;
    };
    if ($@) {
      warn $@;

      # If you want to continue failover, exit 10.
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "status" ) {

    # do nothing
    exit 0;
  }
  else {
    &usage();
    exit 1;
  }
}

sub start_vip(){
    `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
    `ssh $ssh_user\@$new_master_host \" $ssh_send_garp \"`;
}

sub stop_vip(){
    return 0  unless  ($ssh_user);
    `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}

sub usage {
  print
"Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
}
复制代码

 

masterha_secondary_check

复制代码
#!/bin/env perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use strict;
use warnings FATAL => 'all';

use English qw(-no_match_vars);
use Getopt::Long;
use Pod::Usage;
use MHA::ManagerConst;

my @monitoring_servers;
my (
  $help,        $version,         $ssh_user,  $ssh_port,
  $ssh_options, $master_host,     $master_ip, $master_port,
  $master_user, $master_password, $ping_type
);
my $timeout = 5;

$| = 1;
GetOptions(
  'help'              => \$help,'version'           => \$version,'secondary_host=s'  => \@monitoring_servers,'user=s'            => \$ssh_user,'port=s'            => \$ssh_port,'options=s'         => \$ssh_options,'master_host=s'     => \$master_host,'master_ip=s'       => \$master_ip,'master_port=i'     => \$master_port,'master_user=s'     => \$master_user,'master_password=s' => \$master_password,'ping_type=s'       => \$ping_type,'timeout=i'         => \$timeout,
);

if ($version) {
  print "masterha_secondary_check version $MHA::ManagerConst::VERSION.\n";
  exit 0;
}

if ($help) {
  pod2usage(0);
}

unless ($master_host) {
  pod2usage(1);
}

sub exit_by_signal {
  exit 1;
}
local $SIG{INT} = $SIG{HUP} = $SIG{QUIT} = $SIG{TERM} = \&exit_by_signal;

$ssh_user    = "root" unless ($ssh_user);
$ssh_port    = 22     unless ($ssh_port);
$master_port = 3306   unless ($master_port);

if ($ssh_options) {
  $MHA::ManagerConst::SSH_OPT_CHECK = $ssh_options;
}
$MHA::ManagerConst::SSH_OPT_CHECK =~ s/VAR_CONNECT_TIMEOUT/$timeout/;

# 0: master is not reachable from all monotoring servers
# 1: unknown errors
# 2: at least one of monitoring servers is not reachable from this script
# 3: master is reachable from at least one of monitoring servers
my $exit_code = 0;

foreach my $monitoring_server (@monitoring_servers) {
  my $ssh_user_host = $ssh_user . '@' . $monitoring_server;
  my $command ="ssh $MHA::ManagerConst::SSH_OPT_CHECK -p $ssh_port $ssh_user_host \"perl -e "
    . "\\\"use IO::Socket::INET; my \\\\\\\$sock = IO::Socket::INET->new"
    . "(PeerAddr => \\\\\\\"$master_host\\\\\\\", PeerPort=> $master_port, "
    . "Proto =>'tcp', Timeout => $timeout); if(\\\\\\\$sock) { close(\\\\\\\$sock); "
    . "exit 3; } exit 0;\\\" \"";
  my $ret = system($command);
  $ret = $ret >> 8;
  if ( $ret == 0 ) {
    print"Monitoring server $monitoring_server is reachable, Master is not reachable from $monitoring_server. OK.\n";
    next;
  }
  if ( $ret == 3 ) {
    if ( defined $ping_type&& $ping_type eq $MHA::ManagerConst::PING_TYPE_INSERT )
    {
      my $ret_insert;
      my $command_insert ="ssh $MHA::ManagerConst::SSH_OPT_CHECK -p $ssh_port $ssh_user_host \'"
        . "/usr/bin/mysql -u$master_user -p$master_password -h$master_host "
        . "-e \"CREATE DATABASE IF NOT EXISTS infra; "
        . "CREATE TABLE IF NOT EXISTS infra.chk_masterha (\\`key\\` tinyint NOT NULL primary key,\\`val\\` int(10) unsigned NOT NULL DEFAULT '0'\) engine=MyISAM; "
        . "INSERT INTO infra.chk_masterha values (1,unix_timestamp()) ON DUPLICATE KEY UPDATE val=unix_timestamp()\"\'";
      my $sigalrm_timeout = 3;
      eval {
        local $SIG{ALRM} = sub {
          die "timeout.\n";
        };
        alarm $sigalrm_timeout;
        $ret_insert = system($command_insert);
        $ret_insert = $ret_insert >> 8;
        alarm 0;
      };
      if ( $@ || $ret_insert != 0 ) {
        print"Monitoring server $monitoring_server is reachable, Master is not writable from $monitoring_server. OK.\n";
        next;
      }
    }
    print "Master is reachable from $monitoring_server!\n";
    $exit_code = 3;
    last;
  }
  else {
    print "Monitoring server $monitoring_server is NOT reachable!\n";
    $exit_code = 2;
    last;
  }
}

exit $exit_code;

# ############################################################################
# Documentation
# ############################################################################

=pod

=head1 NAME

masterha_secondary_check - Checking master availability from additional network routes

=head1 SYNOPSIS

masterha_secondary_check -s secondary_host1 -s secondary_host2 .. --user=ssh_username --master_host=host --master_ip=ip --master_port=port

See online reference (http://code.google.com/p/mysql-master-ha/wiki/Parameters#secondary_check_script) for details.

=head1 DESCRIPTION

See online reference (http://code.google.com/p/mysql-master-ha/wiki/Parameters#secondary_check_script) for details.
复制代码

 

send_report

复制代码
#!/usr/bin/perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.

use strict;
use warnings FATAL => 'all';
use Mail::Sender;
use Getopt::Long;

#new_master_host and new_slave_hosts are set only when recovering master succeeded
my ( $dead_master_host, $new_master_host, $new_slave_hosts, $subject, $body );
my $smtp='smtp.126.com';
my $mail_from='slowtech@126.com';
my $mail_user='slowtech@126.com';
my $mail_pass='xxxxx';
my $mail_to=['slowtech@126.com'];
GetOptions('orig_master_host=s' => \$dead_master_host,'new_master_host=s'  => \$new_master_host,'new_slave_hosts=s'  => \$new_slave_hosts,'subject=s'          => \$subject,'body=s'             => \$body,
);
mailToContacts($smtp,$mail_from,$mail_user,$mail_pass,$mail_to,$subject,$body);

sub mailToContacts {
    my ( $smtp, $mail_from, $user, $passwd, $mail_to, $subject, $msg ) = @_;
    open my $DEBUG, "> /tmp/monitormail.log"
        or die "Can't open the debug      file:$!\n";
    my $sender = new Mail::Sender {
        ctype       => 'text/plain; charset=utf-8',
        encoding    => 'utf-8',
        smtp        => $smtp,
        from        => $mail_from,
        auth        => 'LOGIN',
        TLS_allowed => '0',
        authid      => $user,
        authpwd     => $passwd,
        to          => $mail_to,
        subject     => $subject,
        debug       => $DEBUG
    };

    $sender->MailMsg(
        {   msg   => $msg,
            debug => $DEBUG
        }
    ) or print $Mail::Sender::Error;
    return 1;
}



# Do whatever you want here
exit 0;
复制代码

 

基于Consul的数据库高可用架构 - yayun - 博客园

$
0
0

      几个月没有更新博客了,已经长草了,特意来除草。本次主要分享如何利用consul来实现redis以及mysql的高可用。以前的公司mysql是单机单实例,高可用MHA加vip就能搞定,新公司mysql是单机多实例,那么显然这个方案不适用,后来也实现了故障切换调用dns api来修改域名记录,但是还是没有利用consul来实现高可用方便,后面会说明优势。redis单机多实例最正常不过了,那么redis单机多实例高可用也不太好做,当然也可以利用sentinel来实现,当failover以后调用脚本调用dns api修改域名解析也是可以的。也不是那么的优雅,有人会说怎么不用codis,redis cluster,这些方案固然好,但不适合我们,这些方案不够灵活,不能很好的处理热点数据的问题。那么consul是什么呢,接下慢慢说:

consul是HashiCorp公司(曾经开发过vgrant) 推出的一款开源工具, 基于go语言开发, 轻量级, 用于实现分布式系统的服务发现与配置。 与其他类似产品相比, 提供更“一站式”的解决方案。 consul内置有KV存储, 服务注册/发现, 健康检查, HTTP+DNS API, Web UI等多种功能。官网: https://www.consul.io/其他同类服务发现与配置的主流开源产品有:zookeeper和ETCD。

consul的优势:

1. 支持多数据中心, 内外网的服务采用不同的端口进行监听。 多数据中心集群可以避免单数据中心的单点故障, zookeeper和 etcd 均不提供多数据中心功能的支持

2. 支持健康检查. etcd 不提供此功能.

3. 支持 http 和 dns 协议接口. zookeeper 的集成较为复杂,etcd 只支持 http 协议. 有DNS功能, 支持REST API

4. 官方提供web管理界面, etcd 无此功能.

5. 部署简单, 运维友好, 无依赖, go的二进制程序copy过来就能用了, 一个程序搞定, 可以结合ansible来推送。

Consul和其他服务发现工具的对比表:

 

  Consul 架构和角色

1. Consul Cluster由部署和运行了Consul Agent的节点组成。 在Cluster中有两种角色:Server和 Client。
2. Server和Client的角色和Consul Cluster上运行的应用服务无关, 是基于Consul层面的一种角色划分.
3. Consul Server: 用于维护Consul Cluster的状态信息, 实现数据一致性, 响应RPC请求。官方建议是: 至少要运行3个或者3个以上的Consul Server。 多个server之中需要选举一个leader, 这个选举过程Consul基于Raft协议实现. 多个Server节点上的Consul数据信息保持强一致性。 在局域网内与本地客户端通讯,通过广域网与其他数据中心通讯。Consul Client: 只维护自身的状态, 并将HTTP和DNS接口请求转发给服务端。
4. Consul 支持多数据中心, 多个数据中心要求每个数据中心都要安装一组Consul cluster,多个数据中心间基于gossip protocol协议来通讯, 使用Raft算法实现一致性

基础知识就介绍这么多了,更加详细的可以参考官网。下面我们来搭建一下consul,以及如何利用consul实现redis以及mysql的高可用。

测试环境(生产环境consul server部署3个或者5个):

consul server:192.168.0.10

consul client:192.168.0.20,192.168.0.30,192.168.0.40

 consul的安装非常容易,从 https://www.consul.io/downloads.html这里下载以后,解压即可使用,就是一个二进制文件,其他的都没有了。我这里使用的是0.92版本。文件下载以后解压放到/usr/local/bin。就可以使用了。不依赖任何东西。上面的4台服务器都安装。

4台机器都创建目录,分别是放配置文件,以及存放数据的。以及存放redis,mysql的健康检查脚本

mkdir/etc/consul.d/ -p &&mkdir/data/consul/ -p
mkidr /data/consul/shell -p

然后把相关配置参数写入配置文件,其实也可以不用写,直接跟在命令后面就行,那样不方便管理。
consul server(192.168.0.10)配置文件(具体参数的意思请查询官网或者文章给的参考链接):

[root@db-server-yayun-01~]#cat/etc/consul.d/server.json 
{"data_dir":"/data/consul","datacenter":"dc1","log_level":"INFO","server":true,"bootstrap_expect":1,"bind_addr":"192.168.0.10","client_addr":"192.168.0.10","ui":true}
[root@db-server-yayun-01~]#

consul client(192.168.0.20,192.168.0.30,192.168.0.40)

[root@db-server-yayun-02~]#cat/etc/consul.d/client.json 
{"data_dir":"/data/consul","enable_script_checks":true,"bind_addr":"192.168.0.20","retry_join": ["192.168.0.10"],"retry_interval":"30s","rejoin_after_leave":true,"start_join": ["192.168.0.10"]
}
[root@db-server-yayun-02~]#

3台服务器的配置文件差异不大,唯一有区别的就是 bind_addr地方,自行修改为你自己服务器的ip。我测试环境是虚拟机,有多快网卡,所以必须指定,否则可以绑定0.0.0.0。
下面我们先启动consul server:

nohup consul agent -config-dir=/etc/consul.d > /data/consul/consul.log &

 查看日志:

[root@db-server-yayun-01consul]#catconsul.log==> WARNING: BootstrapExpect Mode is specified as1; this is the same as Bootstrap mode.==> WARNING: Bootstrap mode enabled!Do not enable unless necessary==>Starting Consul agent...==> Consul agent running!Version:'v0.9.2'Node ID:'5e612623-ec5b-386c-19be-d38876a9a46f'Node name:'db-server-yayun-01'Datacenter:'dc1'Server:true(bootstrap:true)
       Client Addr:192.168.0.10(HTTP:8500, HTTPS: -1, DNS:8600)
      Cluster Addr:192.168.0.10(LAN:8301, WAN:8302)
    Gossip encrypt:false, RPC-TLS:false, TLS-Incoming:false==> Log data will now streaminas it occurs:2017/12/0909:49:53[INFO] raft: Initial configuration (index=1): [{Suffrage:Voter ID:192.168.0.10:8300Address:192.168.0.10:8300}]2017/12/0909:49:53[INFO] raft: Node at192.168.0.10:8300[Follower] entering Follower state (Leader:"")2017/12/0909:49:53[INFO] serf: EventMemberJoin: db-server-yayun-01.dc1192.168.0.102017/12/0909:49:53[INFO] serf: EventMemberJoin: db-server-yayun-01192.168.0.102017/12/0909:49:53[INFO] agent: Started DNS server192.168.0.10:8600(udp)2017/12/0909:49:53[INFO] consul: Adding LAN server db-server-yayun-01(Addr: tcp/192.168.0.10:8300) (DC: dc1)2017/12/0909:49:53[INFO] consul: Handled member-joineventforserver"db-server-yayun-01.dc1"inarea"wan"2017/12/0909:49:53[INFO] agent: Started DNS server192.168.0.10:8600(tcp)2017/12/0909:49:53[INFO] agent: Started HTTP server on192.168.0.10:85002017/12/0909:50:00[ERR] agent: failed tosyncremote state: No cluster leader2017/12/0909:50:00[WARN] raft: Heartbeat timeout from""reached, starting election2017/12/0909:50:00[INFO] raft: Node at192.168.0.10:8300[Candidate] entering Candidate stateinterm22017/12/0909:50:00[INFO] raft: Election won. Tally:12017/12/0909:50:00[INFO] raft: Node at192.168.0.10:8300[Leader] entering Leader state2017/12/0909:50:00[INFO] consul: cluster leadership acquired2017/12/0909:50:00[INFO] consul: New leader elected: db-server-yayun-012017/12/0909:50:00[INFO] consul: member'db-server-yayun-01'joined, marking health alive2017/12/0909:50:03[INFO] agent: Synced nodeinfo
View Code

 可以从日志中看到(HTTP: 8500, HTTPS: -1, DNS: 8600),http端口默认8500,在reload以及web ui会用到,dns端口是8600,在使用dns解析的时候会用到。还可以看到这台机器就是leader,consul: New leader elected: db-server-yayun-01。因为只有一台机器。所以生产环境一定要3个或者5个server。

下面启动3台client,3台client启动命令是一样的。然后查看其中一台client的日志:

nohup consul agent -config-dir=/etc/consul.d > /data/consul/consul.log &
[root@db-server-yayun-02consul]#cat/data/consul/consul.log==>Starting Consul agent...==>Joining cluster...
    Join completed. Synced with1initial agents==> Consul agent running!Version:'v0.9.2'Node ID:'0ec901ab-6c66-2461-95e6-50a77a28ed72'Node name:'db-server-yayun-02'Datacenter:'dc1'Server:false(bootstrap:false)
       Client Addr:127.0.0.1(HTTP:8500, HTTPS: -1, DNS:8600)
      Cluster Addr:192.168.0.20(LAN:8301, WAN:8302)
    Gossip encrypt:false, RPC-TLS:false, TLS-Incoming:false==> Log data will now streaminas it occurs:2017/12/0910:06:10[INFO] serf: EventMemberJoin: db-server-yayun-02192.168.0.202017/12/0910:06:10[INFO] agent: Started DNS server127.0.0.1:8600(udp)2017/12/0910:06:10[INFO] agent: Started DNS server127.0.0.1:8600(tcp)2017/12/0910:06:10[INFO] agent: Started HTTP server on127.0.0.1:85002017/12/0910:06:10[INFO] agent: (LAN) joining: [192.168.0.10]2017/12/0910:06:10[INFO] agent: Retryjoinis supportedfor: aws azure gce softlayer2017/12/0910:06:10[INFO] agent: Joining cluster...2017/12/0910:06:10[INFO] agent: (LAN) joining: [192.168.0.10]2017/12/0910:06:10[INFO] serf: EventMemberJoin: db-server-yayun-01192.168.0.102017/12/0910:06:10[INFO] agent: (LAN) joined:1Err: <nil>2017/12/0910:06:10[INFO] consul: adding server db-server-yayun-01(Addr: tcp/192.168.0.10:8300) (DC: dc1)2017/12/0910:06:10[INFO] agent: (LAN) joined:1Err: <nil>2017/12/0910:06:10[INFO] agent: Join completed. Synced with1initial agents2017/12/0910:06:10[INFO] agent: Synced nodeinfo
View Code

可以看到提示agent: Join completed. Synced with 1 initial agents,以及Server: false (bootstrap: false)。这也是client和server的区别。
我们继续执行命令看一下集群:

[root@db-server-yayun-02~]# consul members
Node                Address            Status  Type    Build  Protocol  DC
db-server-yayun-01192.168.0.10:8301alive   server0.9.22dc1
db-server-yayun-02192.168.0.20:8301alive   client0.9.22dc1
db-server-yayun-03192.168.0.30:8301alive   client0.9.22dc1
db-server-yayun-04192.168.0.40:8301alive   client0.9.22dc1
[root@db-server-yayun-02~]#
[root@db-server-yayun-02~]# consul operator raft list-peers
Node                ID                 Address            State   Voter  RaftProtocol
db-server-yayun-01192.168.0.10:8300192.168.0.10:8300leadertrue2[root@db-server-yayun-02~]#

我们看看web ui,consul自带的ui,非常轻便。访问: http://192.168.0.10:8500/ui/

到这来consul集群就搭建完成了,是不是很简单。对就是这么简单,但是从上面可以看到,client节点并没有注册服务,显示0 services。这也就是接下来需要讲解的。那么到底如何实现redis及mysql的高可用呢?正式开始:

Consul 使用场景一(redis sentinel)
(1)Redis 哨兵架构下,服务器部署了哨兵,但业务部门没有在app 层面,使用jedis 哨兵驱动来自动发现Redis master,而使用直连IP master。当master挂掉,其他redis节点担当新master后,应用需要手工修改配置,指向新master。
(2)Redis 客户端驱动,还没有读写分离的配置,若想slave的读负载均衡,暂时没好的办法。我们程序都是支持读写分离,所以没影响
(3)Consul 可以满足以上需求,配置两个DNS服务,一个是master的服务,利用consul自身的服务健康检查和探测功能, 自动发现新的master。 然后定义一个slave的服务,基于DNS本身, 能够对slave角色的redis IP做轮询。

架构图如下:

 

同样也可以对mysql做高可用,mha和sentinel的角色一样,架构图如下:

下面就说说redis高可用的实现过程,mysql的我就不说了,mysql用到的健康检查脚本我会贴出来。思路都是一样的。

Consul 服务定义(Redis)

上面已经搭建好了consul集群,server是192.168.0.10 client是20到40. 那么20我们就拿来当redis master,30,40拿来当redis slave。下面定义服务(20,30,40都要存在):

20,30,40的配置文件如下,除了address要修改为对应的服务器地址,其他一样。

[root@db-server-yayun-02consul.d]#pwd/etc/consul.d
[root@db-server-yayun-02consul.d]# ll
total12-rw-r--r--.1root root221Dec909:44client.json-rw-r--r--.1root root319Dec910:48r-6029-redis-test.json-rw-r--r--.1root root321Dec910:48w-6029-redis-test.json
[root@db-server-yayun-02consul.d]#

master的服务定义配置文件:

[root@db-server-yayun-02consul.d]#catw-6029-redis-test.json 
{"services": [
    {"name":"w-6029-redis-test","tags": ["master-test-6029"],"address":"192.168.0.20","port":6029,"checks": [
        {"script":"/data/consul/shell/check_redis_master.sh 6029","interval":"15s"}
      ]
    }
  ]
}

[root@db-server-yayun-02consul.d]#
View Code

slave的服务定义配置文件:

[root@db-server-yayun-02consul.d]#catr-6029-redis-test.json 
{"services": [
    {"name":"r-6029-redis-test","tags": ["slave-test-6029"],"address":"192.168.0.20","port":6029,"checks": [
        {"script":"/data/consul/shell/check_redis_slave.sh 6029","interval":"15s"}
      ]
    }
  ]
}

[root@db-server-yayun-02consul.d]#
View Code

每个agent都注册后, 对应有两个域名:
w-6029-redis-test.service.consul (对应唯一一个master IP)
r-6029-redis-test.service.consul  (对应两个slave IP, 客户端请求时, 随机分配一个)

其中"script": "/data/consul/shell/check_redis_slave.sh 6029 "代表对redis 6029端口进行健康检查,关于更多健康检查请查看官网介绍。

[root@db-server-yayun-03shell]#pwd/data/consul/shell
[root@db-server-yayun-03shell]# ll
total16-rwxr-xr-x.1root root480Dec910:56check_mysql_master.sh-rwxr-xr-x.1root root3004Dec910:55check_mysql_slave.sh-rwxr-xr-x.1root root254Dec910:51check_redis_master.sh-rwxr-xr-x.1root root379Dec910:51check_redis_slave.sh[root@db-server-yayun-03shell]#

/data/consul/shell目录下面有4个脚本,是对redis和mysql进行健康检查用的。脚本比较简单,大概就是如果只有一个master,那么读写都在master,如果有slave可用,那么读会在slave进行。如果slave复制不正常,或者复制延时,那么slave服务将不会注册。

[root@db-server-yayun-03shell]#catcheck_redis_master.sh#!/bin/bash
myport=$1auth=$2if[ ! -n"$auth"]thenauth='\"\"'ficomm="/usr/local/bin/redis-cli -p $myport -a $auth"role=`echo'INFO Replication'|$comm |grep-Ec'role:master'`echo'INFO Replication'|$commif[ $role -ne1]thenexit2fi[root@db-server-yayun-03shell]#
View Code
[root@db-server-yayun-03shell]#catcheck_redis_slave.sh#!/bin/bash
myport=$1auth=$2if[ ! -n"$auth"]thenauth='\"\"'ficomm="/usr/local/bin/redis-cli -p $myport -a $auth"role=`echo'INFO Replication'|$comm |grep-Ec'^role:slave|^master_link_status:up'`
single=`echo'INFO Replication'|$comm |grep-Ec'^role:master|^connected_slaves:0'`echo'INFO Replication'|$commif[ $role -ne2-a $single -ne2]thenexit2fi[root@db-server-yayun-03shell]#
View Code
[root@db-server-yayun-02shell]#catcheck_mysql_master.sh#!/bin/bash
port=$1user="root"passwod="123"comm="/usr/local/mysql/bin/mysql -u$user -h 127.0.0.1 -P $port -p$passwod"slave_info=`$comm -e"show slave status"|wc-l`
value=`$comm -Nse"select 1"`

# 判断是不是从库if[ $slave_info -ne0]thenecho"MySQL $port  Instance is Slave........"$comm-e"show slave status\G"|egrep-w"Master_Host|Master_User|Master_Port|Master_Log_File|Read_Master_Log_Pos|Relay_Log_File|Relay_Log_Pos|Relay_Master_Log_File|Slave_IO_Running|Slave_SQL_Running|Exec_Master_Log_Pos|Relay_Log_Space|Seconds_Behind_Master"exit2fi# 判断mysql是否存活if[ -z $value ]thenexit2fiecho"MySQL $port  Instance is Master........"$comm-e"select * from information_schema.PROCESSLIST where user='repl' and COMMAND like '%Dump%'"[root@db-server-yayun-02shell]#
View Code
[root@db-server-yayun-02shell]#catcheck_mysql_slave.sh#!/bin/bash
port=$1user="root"passwod="123"repl_check_user="root"repl_check_pwd="123"master_comm="/usr/local/mysql/bin/mysql -u$user -h 127.0.0.1 -P $port -p$passwod"slave_comm="/usr/local/mysql/bin/mysql -u$repl_check_user  -P $port -p$repl_check_pwd"# 判断mysql是否存活
value=`$master_comm -Nse"select 1"`if[ -z $value ]thenecho"MySQL Server is Down....."exit2figet_slave_count=0is_slave_role=0slave_mode_repl_delay=0master_mode_repl_delay=0master_mode_repl_dead=0slave_mode_repl_status=0max_delay=120get_slave_hosts=`$master_comm -Nse"select substring_index(HOST,':',1) from information_schema.PROCESSLIST where user='repl' and COMMAND like '%Binlog Dump%';"`
get_slave_count=`$master_comm -Nse"select count(1) from information_schema.PROCESSLIST where user='repl' and COMMAND like '%Binlog Dump%';"`
is_slave_role=`$master_comm -e"show slave status\G"|grep-Ewc"Slave_SQL_Running|Slave_IO_Running"`


### 单点模式(如果 get_slave_count=0and is_slave_role=0)functionsingle_mode
{if[ $get_slave_count -eq0-a $is_slave_role -eq0]thenecho"MySQL $port  Instance is Single Master........"exit0fi}

### 从节点模式(如果 get_slave_count=0and is_slave_role=2)functionslave_mode
{
#如果是从节点,必须满足不延迟,if[  $is_slave_role -ge2]thenecho"MySQL $port  Instance is Slave........"$master_comm-e"show slave status\G"|egrep-w"Master_Host|Master_User|Master_Port|Master_Log_File|Read_Master_Log_Pos|Relay_Log_File|Relay_Log_Pos|Relay_Master_Log_File|Slave_IO_Running|Slave_SQL_Running|Exec_Master_Log_Pos|Relay_Log_Space|Seconds_Behind_Master"slave_mode_repl_delay=`$master_comm -e"show slave status\G"|grep-w"Seconds_Behind_Master"|awk'{print $NF}'`
        slave_mode_repl_status=`$master_comm -e"show slave status\G"|grep-Ec"Slave_IO_Running: Yes|Slave_SQL_Running: Yes"`if[ X"$slave_mode_repl_delay"== X"NULL"]thenslave_mode_repl_delay=99999fiif[ $slave_mode_repl_delay !="NULL"-a $slave_mode_repl_delay -lt $max_delay -a $slave_mode_repl_status -ge2]thenexit0fifi}functionmaster_mode
{
###如果是主节点,必须满足从节点为延迟或复制错误。才可读if[ $get_slave_count -gt0-a $is_slave_role -eq0]thenecho"MySQL $port  Instance is Master........"$master_comm-e"select * from information_schema.PROCESSLIST where user='repl' and COMMAND like '%Dump%'"formy_slavein$get_slave_hostsdomaster_mode_repl_delay=`$slave_comm -h $my_slave -e"show slave status\G"|grep-w"Seconds_Behind_Master"|awk'{print $NF}'`
master_mode_repl_thread=`$slave_comm -h $my_slave -e"show slave status\G"|grep-Ec"Slave_IO_Running: Yes|Slave_SQL_Running: Yes"`if[ X"$master_mode_repl_delay"== X"NULL"]thenmaster_mode_repl_delay=99999fiif[ $master_mode_repl_delay -lt $max_delay -a $master_mode_repl_thread -ge2]thenexit2fidoneexit0fi} 

single_mode
slave_mode
master_mode
exit2[root@db-server-yayun-02shell]#
View Code

"name": "r-6029-redis-test",这个就是域名了,默认后缀是servers.consul,consul可以利用domain参数修改。配置文件生成以后安装redis,搭建主从复制(省略)。主从复制完成以后就可以重新reload consul了。redis info信息:

127.0.0.1:6029>inforeplication
# Replication
role:master
connected_slaves:2slave0:ip=192.168.0.40,port=6029,state=online,offset=6786,lag=0slave1:ip=192.168.0.30,port=6029,state=online,offset=6786,lag=1master_repl_offset:6786repl_backlog_active:1repl_backlog_size:67108864repl_backlog_first_byte_offset:2repl_backlog_histlen:6785127.0.0.1:6029>

reload consul(3台client,也就是20-40):

[root@db-server-yayun-02~]# consul reload
Configuration reload triggered
[root@db-server-yayun-02~]#

在其中一台服务器查看consul日志(20):

[root@db-server-yayun-02consul]#tail-f consul.log2017/12/0910:09:59[INFO] serf: EventMemberJoin: db-server-yayun-04192.168.0.402017/12/0911:14:55[INFO] Caught signal:  hangup2017/12/0911:14:55[INFO] Reloading configuration...2017/12/0911:14:55[INFO] agent: Synced service'r-6029-redis-test'2017/12/0911:14:55[INFO] agent: Synced service'w-6029-redis-test'2017/12/0911:14:55[INFO] agent: Synced check'service:w-6029-redis-test'2017/12/0911:15:00[WARN] agent: Check'service:r-6029-redis-test'is now critical2017/12/0911:15:15[WARN] agent: Check'service:r-6029-redis-test'is now critical2017/12/0911:15:30[WARN] agent: Check'service:r-6029-redis-test'is now critical2017/12/0911:15:45[WARN] agent: Check'service:r-6029-redis-test'is now critical

可以看到r-6029-redis-test,w-6029-redis-test服务都已经注册,但是只有w-6029-redis-test注册成功,也就是写的,因为服务器20上面的redis是master,slave的服务当然无法注册成功。我们通过web ui看看。

 可以看到3个client节点每个节点都已经注册了2个服务。还可以看到我们自定义的输出:

下面我们使用dns来解析看看,是否是我们想要的。我们注册两个服务。r-6029-redis-test,w-6029-redis-test,那么就是就产生了2个域名,分别是r-6029-redis-test.service.consul和w-6029-redis-test.service.consul。我们使用dig来看看:

[root@db-server-yayun-02~]# dig @192.168.0.10-p8600r-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600r-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:34508;; flags: qr aa rd; QUERY:1, ANSWER:2, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;r-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:
r-6029-redis-test.service.consul.0IN  A192.168.0.30r-6029-redis-test.service.consul.0IN  A192.168.0.40;; Querytime:1msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:26:382017;; MSG SIZE  rcvd:82[root@db-server-yayun-02~]#
View Code

我们可以看到读的域名r-6029-redis-test.service.consul解析到了两台服务器。那么我们就能够对从库进行负载均衡了。那么写的域名呢?

[root@db-server-yayun-02~]# dig @192.168.0.10-p8600w-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600w-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:7451;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;w-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:w-6029-redis-test.service.consul.0IN  A192.168.0.20;; Querytime:1msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:27:592017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]#
View Code

和我们预料的没错,解析在了20上面。那么我们如果关闭其中一个从库会是怎样的?

[root@db-server-yayun-03~]#ifconfigeth1 |grep-oP'(?<=inet addr:)\S+'192.168.0.30[root@db-server-yayun-03~]# pgrep -fl redis-server |awk'{print $1}'|xargskill[root@db-server-yayun-03~]#
127.0.0.1:6029>inforeplication
# Replication
role:master
connected_slaves:1slave0:ip=192.168.0.40,port=6029,state=online,offset=8200,lag=0master_repl_offset:8200repl_backlog_active:1repl_backlog_size:67108864repl_backlog_first_byte_offset:2repl_backlog_histlen:8199127.0.0.1:6029>

可以看到只有一个从了,我们再次dig 读域名看看:

[root@db-server-yayun-02~]# dig @192.168.0.10-p8600r-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600r-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:41984;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;r-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:
r-6029-redis-test.service.consul.0IN  A192.168.0.40;; Querytime:8msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:32:462017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]#
View Code

可以看到踢掉了另外一台机器。如果我再次关闭40这个从呢?

[root@db-server-yayun-04shell]#ifconfigeth1 |grep-oP'(?<=inet addr:)\S+'192.168.0.40[root@db-server-yayun-04shell]# pgrep -fl redis-server |awk'{print $1}'|xargskill[root@db-server-yayun-04shell]#

那么我们的redis就没有可用从库了,那么读写都将在master上面。

[root@db-server-yayun-02~]# dig @192.168.0.10-p8600r-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600r-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:58564;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;r-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:
r-6029-redis-test.service.consul.0IN  A192.168.0.20;; Querytime:4msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:35:112017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]# dig @192.168.0.10-p8600w-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600w-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:56965;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;w-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:w-6029-redis-test.service.consul.0IN  A192.168.0.20;; Querytime:5msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:35:162017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]#
View Code

这里测试的就差不多了,下面结合sentinel来实现高可用。我会恢复刚才的环境。也就是20是master,30,40是slave。10是sentinel。生产环境sentinel也要部署3个或5个。我的10上面已经有sentinel,端口是36029,我直接添加对20的6029监控。

127.0.0.1:36029> sentinel monitor my-test-6029192.168.0.2060291OK127.0.0.1:36029>
127.0.0.1:36029>infoSentinel
# Sentinel
sentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0master0:name=my-test-6029,status=ok,address=192.168.0.20:6029,slaves=2,sentinels=1127.0.0.1:36029>

再次看看读写域名是否正常了,我已经恢复环境:

[root@db-server-yayun-02~]# dig @192.168.0.10-p8600w-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600w-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:62669;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;w-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:w-6029-redis-test.service.consul.0IN  A192.168.0.20;; Querytime:2msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:43:042017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]# dig @192.168.0.10-p8600r-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600r-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:41305;; flags: qr aa rd; QUERY:1, ANSWER:2, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;r-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:
r-6029-redis-test.service.consul.0IN  A192.168.0.30r-6029-redis-test.service.consul.0IN  A192.168.0.40;; Querytime:2msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:43:082017;; MSG SIZE  rcvd:82[root@db-server-yayun-02~]#
View Code

可以看到已经正常,现在关闭redis master:

[root@db-server-yayun-02~]#ifconfigeth1 |grep-oP'(?<=inet addr:)\S+'192.168.0.20[root@db-server-yayun-02~]# pgrep -fl redis-server |awk'{print $1}'|xargskill

看看sentinel信息:

127.0.0.1:36029>infoSentinel
# Sentinel
sentinel_masters:1sentinel_tilt:0sentinel_running_scripts:0sentinel_scripts_queue_length:0master0:name=my-test-6029,status=ok,address=192.168.0.30:6029,slaves=2,sentinels=1127.0.0.1:36029>

可以看到master已经是30了,dig域名看看:

[root@db-server-yayun-02~]# dig @192.168.0.10-p8600w-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600w-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:55527;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;w-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:w-6029-redis-test.service.consul.0IN  A192.168.0.30;; Querytime:2msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:45:462017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]# dig @192.168.0.10-p8600r-6029-redis-test.service.consul

;<<>> DiG9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6_4.6<<>> @192.168.0.10-p8600r-6029-redis-test.service.consul
; (1server found)
;; global options:+cmd
;; Got answer:
;;->>HEADER<<- opcode: QUERY, status: NOERROR,id:11563;; flags: qr aa rd; QUERY:1, ANSWER:1, AUTHORITY:0, ADDITIONAL:0;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;r-6029-redis-test.service.consul. IN   A

;; ANSWER SECTION:
r-6029-redis-test.service.consul.0IN  A192.168.0.40;; Querytime:1msec
;; SERVER:192.168.0.10#8600(192.168.0.10)
;; WHEN: Sat Dec911:45:502017;; MSG SIZE  rcvd:66[root@db-server-yayun-02~]#
View Code

ok,可以看到已经是我们想要的结果了。最后说说dns的问题。

App端配置域名服务器IP来解析consul后缀的域名,DNS解析及跳转, 有三个方案:
1. 原内网dns服务器,做域名转发,consul后缀的,都转到consul server上(我们线上是采用这个)
2. dns全部跳到consul DNS服务器上,非consul后缀的,使用 recursors 属性跳转到原DNS服务器上
3. dnsmaq 转: server=/consul/10.16.X.X#8600 解析consul后缀的

我们内网dns是用的bind,对于bind的如何做域名转发consul官网也有栗子: https://www.consul.io/docs/guides/forwarding.html,另外也对consul的dns进行了压力测试,不存在性能问题:

 

参考资料:

https://www.consul.io/docs/

https://book-consul-guide.vnzmi.com/

http://www.liangxiansen.cn/2017/04/06/consul/

 

总结:

        对于单机多实例的mysql以及redis,利用consul能够很好的实现高可用,当然要结合mha或者sentinel,最大的好处是consul足够轻量,方便,简单。如果程序支持读写分离的,那么用起来更加方便。从挂掉一个或者多个也不会影响服务。

 

MHA+ProxySQL实现读写分离高可用 - yayun - 博客园

$
0
0

最近在研究ProxySQL,觉得还挺不错的,所以就简单的折腾了一下,ProxySQL目前也是Percona在推荐的一个读写分离的中间件。关于详细的介绍可以参考官方文档。 https://github.com/sysown/proxysql/wiki

本文主要介绍的是MHA+ProxySQL读写分离以及高可用,ProxySQL的细节请参考文档,目前已经有人写的非常详细了,文章最后会给出链接。当然和Group Replication,PXC搭配那是更完美的。关于MHA的配置参考我前面的文章,本文就不再介绍。下面来看看

MHA和ProxySQL搭配使用的架构图:

 

测试环境(1主2从):

M-> 192.168.0.20
S1-> 192.168.0.30
S2-> 192.168.0.40
proxysql -> 192.168.0.10

1. 检查主从复制是否正常:

复制代码
[root@db_server_yayun_04 ~]# masterha_check_repl --conf=/data/mha/3306/mha.cnf 
Thu Jun 15 17:45:32 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Jun 15 17:45:32 2017 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Thu Jun 15 17:45:32 2017 - [info] Updating application default configuration from /usr/local/bin/load_cnf..
Thu Jun 15 17:45:32 2017 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Thu Jun 15 17:45:32 2017 - [info] Setting max_ping_errors to 10, ping_interval to 3.
Thu Jun 15 17:45:32 2017 - [info] MHA::MasterMonitor version 0.57.
Thu Jun 15 17:45:32 2017 - [info] GTID failover mode = 1
Thu Jun 15 17:45:32 2017 - [info] Dead Servers:
Thu Jun 15 17:45:32 2017 - [info] Alive Servers:
Thu Jun 15 17:45:32 2017 - [info]   192.168.0.20(192.168.0.20:3306)
Thu Jun 15 17:45:32 2017 - [info]   192.168.0.30(192.168.0.30:3306)
Thu Jun 15 17:45:32 2017 - [info]   192.168.0.40(192.168.0.40:3306)
Thu Jun 15 17:45:32 2017 - [info] Alive Slaves:
Thu Jun 15 17:45:32 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Thu Jun 15 17:45:32 2017 - [info]     GTID ON
Thu Jun 15 17:45:32 2017 - [info]     Replicating from 192.168.0.20(192.168.0.20:3306)
Thu Jun 15 17:45:32 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Jun 15 17:45:32 2017 - [info]   192.168.0.40(192.168.0.40:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Thu Jun 15 17:45:32 2017 - [info]     GTID ON
Thu Jun 15 17:45:32 2017 - [info]     Replicating from 192.168.0.20(192.168.0.20:3306)
Thu Jun 15 17:45:32 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Jun 15 17:45:32 2017 - [info] Current Alive Master: 192.168.0.20(192.168.0.20:3306)
Thu Jun 15 17:45:32 2017 - [info] Checking slave configurations..
Thu Jun 15 17:45:32 2017 - [info] Checking replication filtering settings..
Thu Jun 15 17:45:32 2017 - [info]  binlog_do_db= , binlog_ignore_db= 
Thu Jun 15 17:45:32 2017 - [info]  Replication filtering check ok.
Thu Jun 15 17:45:32 2017 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Thu Jun 15 17:45:32 2017 - [info] Checking SSH publickey authentication settings on the current master..
Thu Jun 15 17:45:33 2017 - [info] HealthCheck: SSH to 192.168.0.20 is reachable.
Thu Jun 15 17:45:33 2017 - [info] 
192.168.0.20(192.168.0.20:3306) (current master)
 +--192.168.0.30(192.168.0.30:3306)
 +--192.168.0.40(192.168.0.40:3306)

Thu Jun 15 17:45:33 2017 - [info] Checking replication health on 192.168.0.30..
Thu Jun 15 17:45:33 2017 - [info]  ok.
Thu Jun 15 17:45:33 2017 - [info] Checking replication health on 192.168.0.40..
Thu Jun 15 17:45:33 2017 - [info]  ok.
Thu Jun 15 17:45:33 2017 - [warning] master_ip_failover_script is not defined.
Thu Jun 15 17:45:33 2017 - [warning] shutdown_script is not defined.
Thu Jun 15 17:45:33 2017 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.
[root@db_server_yayun_04 ~]# 
复制代码

2. 配置后端MySQL。登入ProxySQL,把MySQL主从的信息添加进去。将主库master也就是做写入的节点放到HG 100中,salve节点做读放到HG 1000。在proxysql输入命令:

mysql -uadmin -padmin -h127.0.0.1 -P6032
复制代码
[admin@127.0.0.1][(none)]> insert into mysql_servers(hostgroup_id,hostname,port,weight,max_connections,max_replication_lag,comment) values(100,'192.168.0.20',3306,1,1000,10,'test my proxysql');
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> insert into mysql_servers(hostgroup_id,hostname,port,weight,max_connections,max_replication_lag,comment) values(1000,'192.168.0.30',3306,1,1000,10,'test my proxysql');
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> insert into mysql_servers(hostgroup_id,hostname,port,weight,max_connections,max_replication_lag,comment) values(1000,'192.168.0.40',3306,1,1000,10,'test my proxysql'); 
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> select * from mysql_servers;
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| hostgroup_id | hostname     | port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment          |
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| 100          | 192.168.0.20 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 0              | test my proxysql |
| 1000         | 192.168.0.30 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 0              | test my proxysql |
| 1000         | 192.168.0.40 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 0              | test my proxysql |
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
3 rows in set (0.00 sec)
复制代码

3. 配置后端MySQL用户。这个用户需要先在后端MySQL(20,30,40)里真实存在,一个是监控账号、一个是程序账号。
(1) 监控账号(用来监控后端mysql是否存活以及read_only变量):

GRANT SUPER, REPLICATION CLIENT ON *.* TO 'proxysql'@'192.168.0.10' IDENTIFIED BY 'proxysql';

(2) 程序账号(这里为了后面测试方便给了all权限):

GRANT all ON *.* TO 'yayun'@'192.168.0.10' identified by 'yayun';

4. 在后端MySQL里添加完之后再配置ProxySQL:这里需要注意,default_hostgroup需要和上面的对应。(proxysql)

复制代码
[admin@127.0.0.1][(none)]> insert into mysql_users(username,password,active,default_hostgroup,transaction_persistent) values('yayun','yayun',1,100,1);
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> select * from mysql_users;
+----------+----------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+
| username | password | active | use_ssl | default_hostgroup | default_schema | schema_locked | transaction_persistent | fast_forward | backend | frontend | max_connections |
+----------+----------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+
| yayun    | yayun    | 1      | 0       | 100               | NULL           | 0             | 1                      | 0            | 1       | 1        | 10000           |
+----------+----------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+
1 row in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

5. 设置健康监测账号(proxysql):

复制代码
[admin@127.0.0.1][(none)]> set mysql-monitor_username='proxysql';
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> set mysql-monitor_password='proxysql';
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

6. 加载配置和变量:因为修改了servers、users和variables,所以加载的时候要执行:

复制代码
[admin@127.0.0.1][(none)]> load mysql servers to runtime;
Query OK, 0 rows affected (0.01 sec)

[admin@127.0.0.1][(none)]> load mysql users to runtime;
Query OK, 0 rows affected (0.00 sec)

[admin@127.0.0.1][(none)]> load mysql variables to runtime;
Query OK, 0 rows affected (0.00 sec)

[admin@127.0.0.1][(none)]> save mysql servers to disk;
Query OK, 0 rows affected (0.05 sec)

[admin@127.0.0.1][(none)]> save mysql users to disk;
Query OK, 0 rows affected (0.02 sec)

[admin@127.0.0.1][(none)]> save mysql variables to disk;
Query OK, 74 rows affected (0.01 sec)

[admin@127.0.0.1][(none)]> 
复制代码

7. 连接数据库,通过proxysql的客户端接口访问(6033):(我这里从40从库上面发起连接)

 mysql -uyayun -pyayun -h192.168.0.10 -P6033
复制代码
[root@db_server_yayun_04 ~]# mysql -uyayun -pyayun -h192.168.0.10 -P6033               
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.5.30 (ProxySQL)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

[yayun@192.168.0.10][(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| yayun              |
+--------------------+
5 rows in set (0.01 sec)

[yayun@192.168.0.10][(none)]> 
复制代码

 8. 创建表并且写入数据进行查询:

复制代码
[yayun@192.168.0.10][(none)]> use yayun
Database changed, 2 warnings
[yayun@192.168.0.10][yayun]> create table t1 ( id int);
Query OK, 0 rows affected (0.08 sec)

[yayun@192.168.0.10][yayun]> insert into t1 select 1;
Query OK, 1 row affected (0.05 sec)
Records: 1  Duplicates: 0  Warnings: 0

[yayun@192.168.0.10][yayun]> select * from t1;
+------+
| id   |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

[yayun@192.168.0.10][yayun]> 
复制代码

可以看到创建了表,并且插入了数据,查询也没问题。proxysql有个类似审计的功能,可以查看各类SQL的执行情况。在proxysql执行SQL查看。

复制代码
[admin@127.0.0.1][(none)]> select * from stats_mysql_query_digest;
+-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
| hostgroup | schemaname         | username | digest             | digest_text                      | count_star | first_seen | last_seen  | sum_time | min_time | max_time |
+-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
| 100       | yayun              | yayun    | 0xA9518ABEA63705E6 | create table t1 ( id int)        | 1          | 1497577980 | 1497577980 | 79733    | 79733    | 79733    |
| 100       | yayun              | yayun    | 0x3765930C7143F468 | select * from t1                 | 1          | 1497577997 | 1497577997 | 1537     | 1537     | 1537     |
| 100       | yayun              | yayun    | 0x4BBB5CD4BC2CFD94 | insert into t1 select ?          | 1          | 1497577986 | 1497577986 | 33350    | 33350    | 33350    |
| 100       | information_schema | yayun    | 0x620B328FE9D6D71A | SELECT DATABASE()                | 1          | 1497577955 | 1497577955 | 4994     | 4994     | 4994     |
| 100       | information_schema | yayun    | 0x594F2C744B698066 | select USER()                    | 1          | 1497577951 | 1497577951 | 0        | 0        | 0        |
| 100       | information_schema | yayun    | 0x226CD90D52A2BA0B | select @@version_comment limit ? | 1          | 1497577951 | 1497577951 | 0        | 0        | 0        |
+-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
6 rows in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

9. 可以看到读写都发送到了组100上面,组100是主库,说明没有读写分离。那是因为还有配置没有完成,我们需要自己定义规则。
定义路由规则,如:除select * from tb for update的select全部发送到slave,其他的的语句发送到master。

复制代码
[admin@127.0.0.1][(none)]> INSERT INTO mysql_query_rules(active,match_pattern,destination_hostgroup,apply) VALUES(1,'^SELECT.*FOR UPDATE$',100,1);
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> INSERT INTO mysql_query_rules(active,match_pattern,destination_hostgroup,apply) VALUES(1,'^SELECT',1000,1);
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> load mysql query rules to runtime;
Query OK, 0 rows affected (0.00 sec)

[admin@127.0.0.1][(none)]> save mysql query rules to disk;
Query OK, 0 rows affected (0.02 sec)

[admin@127.0.0.1][(none)]> select rule_id,active,match_pattern,destination_hostgroup,apply from runtime_mysql_query_rules;
+---------+--------+----------------------+-----------------------+-------+
| rule_id | active | match_pattern        | destination_hostgroup | apply |
+---------+--------+----------------------+-----------------------+-------+
| 1       | 1      | ^SELECT.*FOR UPDATE$ | 100                   | 1     |
| 2       | 1      | ^SELECT              | 1000                  | 1     |
+---------+--------+----------------------+-----------------------+-------+
2 rows in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

清理掉统计信息,再次进行测试。

select * from stats_mysql_query_digest_reset;

再次运行读写,然后再查看,发现已经实现读写分离。

复制代码
[admin@127.0.0.1][(none)]> select * from stats_mysql_query_digest;                                                                    
+-----------+------------+----------+--------------------+-------------------------+------------+------------+------------+----------+----------+----------+
| hostgroup | schemaname | username | digest             | digest_text             | count_star | first_seen | last_seen  | sum_time | min_time | max_time |
+-----------+------------+----------+--------------------+-------------------------+------------+------------+------------+----------+----------+----------+
| 1000      | yayun      | yayun    | 0x3765930C7143F468 | select * from t1        | 1          | 1497578494 | 1497578494 | 21751    | 21751    | 21751    |
| 100       | yayun      | yayun    | 0x4BBB5CD4BC2CFD94 | insert into t1 select ? | 1          | 1497578492 | 1497578492 | 54852    | 54852    | 54852    |
+-----------+------------+----------+--------------------+-------------------------+------------+------------+------------+----------+----------+----------+
2 rows in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

重点来了,如何配合MHA实现高可用呢?那么需要利用到proxysql里面的mysql_replication_hostgroups表。mysql_replication_hostgroups 表的主要作用是监视指定主机组中所有服务器的read_only值,并且根据read_only的值将服务器分配给写入器或读取器主机组,定义 hostgroup 的主从关系。ProxySQL monitor 模块会监控 HG 后端所有servers 的 read_only 变量,如果发现从库的 read_only 变为0、主库变为1,则认为角色互换了,自动改写 mysql_servers 表里面 hostgroup 关系,达到自动 Failover 效果。

我们看看mysql_replication_hostgroups表结构:

复制代码
[admin@127.0.0.1][(none)]> show create table mysql_replication_hostgroups\G
*************************** 1. row ***************************
       table: mysql_replication_hostgroups
Create Table: CREATE TABLE mysql_replication_hostgroups (
    writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY,
    reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND reader_hostgroup>0),
    comment VARCHAR,
    UNIQUE (reader_hostgroup))
1 row in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

3个字段很明显,写主机组,读主机组,备注。那么我们现在插入数据。我们的100是写,1000是读。

复制代码
[admin@127.0.0.1][(none)]> insert into  mysql_replication_hostgroups (writer_hostgroup,reader_hostgroup,comment)values(100,1000,'测试我的读写分离高可用');
Query OK, 1 row affected (0.00 sec)

[admin@127.0.0.1][(none)]> load mysql servers to runtime;
Query OK, 0 rows affected (0.01 sec)

[admin@127.0.0.1][(none)]> save mysql servers to disk;
Query OK, 0 rows affected (0.03 sec)

[admin@127.0.0.1][(none)]> select * from runtime_mysql_replication_hostgroups;
+------------------+------------------+-----------------------------------+
| writer_hostgroup | reader_hostgroup | comment                           |
+------------------+------------------+-----------------------------------+
| 100              | 1000             | 测试我的读写分离高可用            |
+------------------+------------------+-----------------------------------+
1 row in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

我们用vc-mysql-sniffer在从库抓一下。看看能看见什么。

复制代码
[root@db_server_yayun_03 ~]# ./vc-mysql-sniffer 
# Time: 061617 10:26:45
# User@Host: unknown_user[unknown_user] @ 192.168.0.10:36371 []
# Query_time: 0.005198
SHOW GLOBAL VARIABLES LIKE 'read_only';
# Time: 061617 10:26:46
# User@Host: unknown_user[unknown_user] @ 192.168.0.10:36372 []
# Query_time: 0.000564
SHOW SLAVE STATUS;
# Time: 061617 10:26:46
# User@Host: unknown_user[unknown_user] @ 192.168.0.10:36372 []
# Query_time: 0.004008
SHOW GLOBAL VARIABLES LIKE 'read_only';
# Time: 061617 10:26:48
# User@Host: unknown_user[unknown_user] @ 192.168.0.10:36371 []
# Query_time: 0.004923
SHOW GLOBAL VARIABLES LIKE 'read_only';
# Time: 061617 10:26:49
# User@Host: unknown_user[unknown_user] @ 192.168.0.10:36372 []
# Query_time: 0.003617
SHOW GLOBAL VARIABLES LIKE 'read_only';
复制代码

可以看见在检查read_only变量。我们先看看现在主机组的关系:

复制代码
[admin@127.0.0.1][(none)]> select * from runtime_mysql_servers;
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| hostgroup_id | hostname     | port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment          |
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| 100          | 192.168.0.20 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.30 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.40 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
3 rows in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

可以看见100主机组是主库,1000主机组是从库。下面使用MHA在线变换主从关系。我们把从库40提升为主库。

复制代码
[root@db_server_yayun_04 ~]# masterha_master_switch --master_state=alive --conf=/data/mha/3306/mha.cnf --new_master_host=192.168.0.40 --new_master_port=3306 --orig_master_is_new_slave 
Fri Jun 16 10:30:39 2017 - [info] MHA::MasterRotate version 0.57.
Fri Jun 16 10:30:39 2017 - [info] Starting online master switch..
Fri Jun 16 10:30:39 2017 - [info] 
Fri Jun 16 10:30:39 2017 - [info] * Phase 1: Configuration Check Phase..
Fri Jun 16 10:30:39 2017 - [info] 
Fri Jun 16 10:30:39 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Jun 16 10:30:39 2017 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:30:39 2017 - [info] Updating application default configuration from /usr/local/bin/load_cnf..
Fri Jun 16 10:30:39 2017 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:30:39 2017 - [info] Setting max_ping_errors to 10, ping_interval to 3.
Fri Jun 16 10:30:39 2017 - [info] GTID failover mode = 1
Fri Jun 16 10:30:39 2017 - [info] Current Alive Master: 192.168.0.20(192.168.0.20:3306)
Fri Jun 16 10:30:39 2017 - [info] Alive Slaves:
Fri Jun 16 10:30:39 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:30:39 2017 - [info]     GTID ON
Fri Jun 16 10:30:39 2017 - [info]     Replicating from 192.168.0.20(192.168.0.20:3306)
Fri Jun 16 10:30:39 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:30:39 2017 - [info]   192.168.0.40(192.168.0.40:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:30:39 2017 - [info]     GTID ON
Fri Jun 16 10:30:39 2017 - [info]     Replicating from 192.168.0.20(192.168.0.20:3306)
Fri Jun 16 10:30:39 2017 - [info]     Primary candidate for the new Master (candidate_master is set)

It is better to execute FLUSH NO_WRITE_TO_BINLOG TABLES on the master before switching. Is it ok to execute on 192.168.0.20(192.168.0.20:3306)? (YES/no): y
Fri Jun 16 10:30:40 2017 - [info] Executing FLUSH NO_WRITE_TO_BINLOG TABLES. This may take long time..
Fri Jun 16 10:30:40 2017 - [info]  ok.
Fri Jun 16 10:30:40 2017 - [info] Checking MHA is not monitoring or doing failover..
Fri Jun 16 10:30:40 2017 - [info] Checking replication health on 192.168.0.30..
Fri Jun 16 10:30:40 2017 - [info]  ok.
Fri Jun 16 10:30:40 2017 - [info] Checking replication health on 192.168.0.40..
Fri Jun 16 10:30:40 2017 - [info]  ok.
Fri Jun 16 10:30:40 2017 - [info] 192.168.0.40 can be new master.
Fri Jun 16 10:30:40 2017 - [info] 
From:
192.168.0.20(192.168.0.20:3306) (current master)
 +--192.168.0.30(192.168.0.30:3306)
 +--192.168.0.40(192.168.0.40:3306)

To:
192.168.0.40(192.168.0.40:3306) (new master)
 +--192.168.0.30(192.168.0.30:3306)
 +--192.168.0.20(192.168.0.20:3306)

Starting master switch from 192.168.0.20(192.168.0.20:3306) to 192.168.0.40(192.168.0.40:3306)? (yes/NO): yes
Fri Jun 16 10:30:42 2017 - [info] Checking whether 192.168.0.40(192.168.0.40:3306) is ok for the new master..
Fri Jun 16 10:30:42 2017 - [info]  ok.
Fri Jun 16 10:30:42 2017 - [info] 192.168.0.20(192.168.0.20:3306): SHOW SLAVE STATUS returned empty result. To check replication filtering rules, temporarily executing CHANGE MASTER to a dummy host.
Fri Jun 16 10:30:42 2017 - [info] 192.168.0.20(192.168.0.20:3306): Resetting slave pointing to the dummy host.
Fri Jun 16 10:30:42 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Fri Jun 16 10:30:42 2017 - [info] 
Fri Jun 16 10:30:42 2017 - [info] * Phase 2: Rejecting updates Phase..
Fri Jun 16 10:30:42 2017 - [info] 
Fri Jun 16 10:30:42 2017 - [info] Executing master ip online change script to disable write on the current master:
Fri Jun 16 10:30:42 2017 - [info]   /usr/local/bin/master_ip_online_change --command=stop --orig_master_host=192.168.0.20 --orig_master_ip=192.168.0.20 --orig_master_port=3306 --orig_master_user='root' --orig_master_password='123' --new_master_host=192.168.0.40 --new_master_ip=192.168.0.40 --new_master_port=3306 --new_master_user='root' --new_master_password='123' --orig_master_ssh_user=root --new_master_ssh_user=root   --orig_master_is_new_slave
Fri Jun 16 10:30:42 2017 783184 Set read_only on the new master.. ok.
Fri Jun 16 10:30:42 2017 786894 Drpping app user on the orig master..
Fri Jun 16 10:30:42 2017 787716 Waiting all running 3 threads are disconnected.. (max 1500 milliseconds)
{'Time' => '0','Command' => 'Sleep','db' => undef,'Id' => '282','Info' => undef,'User' => 'proxysql','State' => '','Host' => '192.168.0.10:54482'}
{'Time' => '1620','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '296','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.30:53451'}
{'Time' => '874','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '310','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.40:53136'}
Fri Jun 16 10:30:43 2017 294399 Waiting all running 2 threads are disconnected.. (max 1000 milliseconds)
{'Time' => '1621','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '296','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.30:53451'}
{'Time' => '875','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '310','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.40:53136'}
Fri Jun 16 10:30:43 2017 800400 Waiting all running 2 threads are disconnected.. (max 500 milliseconds)
{'Time' => '1621','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '296','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.30:53451'}
{'Time' => '875','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '310','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.40:53136'}
Fri Jun 16 10:30:44 2017 306937 Set read_only=1 on the orig master.. ok.
Fri Jun 16 10:30:44 2017 311939 Waiting all running 3 queries are disconnected.. (max 500 milliseconds)
{'Time' => '0','Command' => 'Sleep','db' => undef,'Id' => '281','Info' => undef,'User' => 'proxysql','State' => '','Host' => '192.168.0.10:54481'}
{'Time' => '1622','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '296','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.30:53451'}
{'Time' => '876','Command' => 'Binlog Dump GTID','db' => undef,'Id' => '310','Info' => undef,'User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Host' => '192.168.0.40:53136'}
Fri Jun 16 10:30:44 2017 810421 Killing all application threads..
Fri Jun 16 10:30:44 2017 816513 done.
SIOCSIFFLAGS: Cannot assign requested address
Fri Jun 16 10:30:45 2017 - [info]  ok.
Fri Jun 16 10:30:45 2017 - [info] Locking all tables on the orig master to reject updates from everybody (including root):
Fri Jun 16 10:30:45 2017 - [info] Executing FLUSH TABLES WITH READ LOCK..
Fri Jun 16 10:30:45 2017 - [info]  ok.
Fri Jun 16 10:30:45 2017 - [info] Orig master binlog:pos is mysql-bin.000001:1610.
Fri Jun 16 10:30:45 2017 - [info]  Waiting to execute all relay logs on 192.168.0.40(192.168.0.40:3306)..
Fri Jun 16 10:30:45 2017 - [info]  master_pos_wait(mysql-bin.000001:1610) completed on 192.168.0.40(192.168.0.40:3306). Executed 0 events.
Fri Jun 16 10:30:45 2017 - [info]   done.
Fri Jun 16 10:30:45 2017 - [info] Getting new master's binlog name and position..
Fri Jun 16 10:30:45 2017 - [info]  mysql-bin.000002:194
Fri Jun 16 10:30:45 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='192.168.0.40', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Fri Jun 16 10:30:45 2017 - [info] Executing master ip online change script to allow write on the new master:
Fri Jun 16 10:30:45 2017 - [info]   /usr/local/bin/master_ip_online_change --command=start --orig_master_host=192.168.0.20 --orig_master_ip=192.168.0.20 --orig_master_port=3306 --orig_master_user='root' --orig_master_password='123' --new_master_host=192.168.0.40 --new_master_ip=192.168.0.40 --new_master_port=3306 --new_master_user='root' --new_master_password='123' --orig_master_ssh_user=root --new_master_ssh_user=root   --orig_master_is_new_slave
Fri Jun 16 10:30:45 2017 744690 Set read_only=0 on the new master.
Fri Jun 16 10:30:45 2017 745124 Creating app user on the new master..
Fri Jun 16 10:30:45 2017 - [info]  ok.
Fri Jun 16 10:30:45 2017 - [info] 
Fri Jun 16 10:30:45 2017 - [info] * Switching slaves in parallel..
Fri Jun 16 10:30:45 2017 - [info] 
Fri Jun 16 10:30:45 2017 - [info] -- Slave switch on host 192.168.0.30(192.168.0.30:3306) started, pid: 9457
Fri Jun 16 10:30:45 2017 - [info] 
Fri Jun 16 10:30:45 2017 - [info] Log messages from 192.168.0.30 ...
Fri Jun 16 10:30:45 2017 - [info] 
Fri Jun 16 10:30:45 2017 - [info]  Waiting to execute all relay logs on 192.168.0.30(192.168.0.30:3306)..
Fri Jun 16 10:30:45 2017 - [info]  master_pos_wait(mysql-bin.000001:1610) completed on 192.168.0.30(192.168.0.30:3306). Executed 0 events.
Fri Jun 16 10:30:45 2017 - [info]   done.
Fri Jun 16 10:30:45 2017 - [info]  Resetting slave 192.168.0.30(192.168.0.30:3306) and starting replication from the new master 192.168.0.40(192.168.0.40:3306)..
Fri Jun 16 10:30:45 2017 - [info]  Executed CHANGE MASTER.
Fri Jun 16 10:30:45 2017 - [info]  Slave started.
Fri Jun 16 10:30:45 2017 - [info] End of log messages from 192.168.0.30 ...
Fri Jun 16 10:30:45 2017 - [info] 
Fri Jun 16 10:30:45 2017 - [info] -- Slave switch on host 192.168.0.30(192.168.0.30:3306) succeeded.
Fri Jun 16 10:30:45 2017 - [info] Unlocking all tables on the orig master:
Fri Jun 16 10:30:45 2017 - [info] Executing UNLOCK TABLES..
Fri Jun 16 10:30:45 2017 - [info]  ok.
Fri Jun 16 10:30:45 2017 - [info] Starting orig master as a new slave..
Fri Jun 16 10:30:45 2017 - [info]  Resetting slave 192.168.0.20(192.168.0.20:3306) and starting replication from the new master 192.168.0.40(192.168.0.40:3306)..
Fri Jun 16 10:30:45 2017 - [info]  Executed CHANGE MASTER.
Fri Jun 16 10:30:46 2017 - [info]  Slave started.
Fri Jun 16 10:30:46 2017 - [info] All new slave servers switched successfully.
Fri Jun 16 10:30:46 2017 - [info] 
Fri Jun 16 10:30:46 2017 - [info] * Phase 5: New master cleanup phase..
Fri Jun 16 10:30:46 2017 - [info] 
Fri Jun 16 10:30:46 2017 - [info]  192.168.0.40: Resetting slave info succeeded.
Fri Jun 16 10:30:46 2017 - [info] Switching master to 192.168.0.40(192.168.0.40:3306) completed successfully.
复制代码

可以看见已经成功切换。我们再来看看运行主机组的状态:

复制代码
[admin@127.0.0.1][(none)]> select * from runtime_mysql_servers;
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| hostgroup_id | hostname     | port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment          |
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| 100          | 192.168.0.40 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.30 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.40 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.20 | 3306 | ONLINE | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
+--------------+--------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
4 rows in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

可以看见40自动设置成了主机组100,也就是master。我们进行读写测试。

复制代码
[admin@127.0.0.1][(none)]> select * from stats_mysql_query_digest;
+-----------+------------+----------+--------------------+-----------------------------+------------+------------+------------+----------+----------+----------+
| hostgroup | schemaname | username | digest             | digest_text                 | count_star | first_seen | last_seen  | sum_time | min_time | max_time |
+-----------+------------+----------+--------------------+-----------------------------+------------+------------+------------+----------+----------+----------+
| 1000      | yayun      | yayun    | 0x3765930C7143F468 | select * from t1            | 1          | 1497580568 | 1497580568 | 34920    | 34920    | 34920    |
| 100       | yayun      | yayun    | 0x5A680F86B3D8FB2B | select * from t1 for update | 1          | 1497580565 | 1497580565 | 9609     | 9609     | 9609     |
| 100       | yayun      | yayun    | 0x4BBB5CD4BC2CFD94 | insert into t1 select ?     | 1          | 1497580557 | 1497580557 | 133003   | 133003   | 133003   |
| 100       | yayun      | yayun    | 0x99531AEFF718C501 | show tables                 | 1          | 1497580544 | 1497580544 | 2051     | 2051     | 2051     |
| 100       | yayun      | yayun    | 0x74A739578E179F19 | show processlist            | 1          | 1497580532 | 1497580532 | 4335     | 4335     | 4335     |
+-----------+------------+----------+--------------------+-----------------------------+------------+------------+------------+----------+----------+----------+
5 rows in set (0.00 sec)

[admin@127.0.0.1][(none)]> 
复制代码

可以看见除了for update语句和insert发送到主库,查询是发送到了从库。当然虽然1000主机组里面有40,也就是会有非常少量的查询才会发送到主库。这个没法避免。
下面进行主库宕机测试,启动mha监控,关闭主库。mha切换日志,可以看见30当选主库:

复制代码
[root@db_server_yayun_04 log]# cat manager.log 
Fri Jun 16 10:38:59 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Jun 16 10:38:59 2017 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:38:59 2017 - [info] Updating application default configuration from /usr/local/bin/load_cnf..
Fri Jun 16 10:38:59 2017 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:38:59 2017 - [info] Setting max_ping_errors to 10, ping_interval to 3.
Fri Jun 16 10:39:53 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Jun 16 10:39:53 2017 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:39:53 2017 - [info] Updating application default configuration from /usr/local/bin/load_cnf..
Fri Jun 16 10:39:53 2017 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:39:53 2017 - [info] Setting max_ping_errors to 10, ping_interval to 3.
nfo]   192.168.0.20(192.168.0.20:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:38:59 2017 - [info]     GTID ON
Fri Jun 16 10:38:59 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:38:59 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:38:59 2017 - [info]     GTID ON
Fri Jun 16 10:38:59 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:38:59 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:38:59 2017 - [info] Current Alive Master: 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:38:59 2017 - [info] Checking slave configurations..
Fri Jun 16 10:38:59 2017 - [info] Checking replication filtering settings..
Fri Jun 16 10:38:59 2017 - [info]  binlog_do_db= , binlog_ignore_db= 
Fri Jun 16 10:38:59 2017 - [info]  Replication filtering check ok.
Fri Jun 16 10:38:59 2017 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Fri Jun 16 10:38:59 2017 - [info] Checking SSH publickey authentication settings on the current master..
Fri Jun 16 10:38:59 2017 - [info] HealthCheck: SSH to 192.168.0.40 is reachable.
Fri Jun 16 10:38:59 2017 - [info] 
192.168.0.40(192.168.0.40:3306) (current master)
 +--192.168.0.20(192.168.0.20:3306)
 +--192.168.0.30(192.168.0.30:3306)

Fri Jun 16 10:38:59 2017 - [warning] master_ip_failover_script is not defined.
Fri Jun 16 10:38:59 2017 - [warning] shutdown_script is not defined.
Fri Jun 16 10:38:59 2017 - [info] Set master ping interval 3 seconds.
Fri Jun 16 10:38:59 2017 - [warning] secondary_check_script is not defined. It is highly recommended setting it to check master reachability from two or more routes.
Fri Jun 16 10:38:59 2017 - [info] Starting ping health check on 192.168.0.40(192.168.0.40:3306)..
Fri Jun 16 10:38:59 2017 - [info] Ping(SELECT) succeeded, waiting until MySQL doesn't respond..
Fri Jun 16 10:39:26 2017 - [warning] Got error on MySQL select ping: 2006 (MySQL server has gone away)
Fri Jun 16 10:39:26 2017 - [info] Executing SSH check script: exit 0
Fri Jun 16 10:39:27 2017 - [info] HealthCheck: SSH to 192.168.0.40 is reachable.
Fri Jun 16 10:39:29 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:29 2017 - [warning] Connection failed 2 time(s)..
Fri Jun 16 10:39:32 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:32 2017 - [warning] Connection failed 3 time(s)..
Fri Jun 16 10:39:35 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:35 2017 - [warning] Connection failed 4 time(s)..
Fri Jun 16 10:39:38 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:38 2017 - [warning] Connection failed 5 time(s)..
Fri Jun 16 10:39:41 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:41 2017 - [warning] Connection failed 6 time(s)..
Fri Jun 16 10:39:44 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:44 2017 - [warning] Connection failed 7 time(s)..
Fri Jun 16 10:39:47 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:47 2017 - [warning] Connection failed 8 time(s)..
Fri Jun 16 10:39:50 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:50 2017 - [warning] Connection failed 9 time(s)..
Fri Jun 16 10:39:53 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Fri Jun 16 10:39:53 2017 - [warning] Connection failed 10 time(s)..
Fri Jun 16 10:39:53 2017 - [warning] Master is not reachable from health checker!
Fri Jun 16 10:39:53 2017 - [warning] Master 192.168.0.40(192.168.0.40:3306) is not reachable!
Fri Jun 16 10:39:53 2017 - [warning] SSH is reachable.
Fri Jun 16 10:39:53 2017 - [info] Connecting to a master server failed. Reading configuration file /etc/masterha_default.cnf and /data/mha/3306/mha.cnf again, and trying to connect to all servers to check server status..
Fri Jun 16 10:39:53 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Jun 16 10:39:53 2017 - [info] Reading application default configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:39:53 2017 - [info] Updating application default configuration from /usr/local/bin/load_cnf..
Fri Jun 16 10:39:53 2017 - [info] Reading server configuration from /data/mha/3306/mha.cnf..
Fri Jun 16 10:39:53 2017 - [info] Setting max_ping_errors to 10, ping_interval to 3.
Fri Jun 16 10:39:53 2017 - [info] GTID failover mode = 1
Fri Jun 16 10:39:53 2017 - [info] Dead Servers:
Fri Jun 16 10:39:53 2017 - [info]   192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:53 2017 - [info] Alive Servers:
Fri Jun 16 10:39:53 2017 - [info]   192.168.0.20(192.168.0.20:3306)
Fri Jun 16 10:39:53 2017 - [info]   192.168.0.30(192.168.0.30:3306)
Fri Jun 16 10:39:53 2017 - [info] Alive Slaves:
Fri Jun 16 10:39:53 2017 - [info]   192.168.0.20(192.168.0.20:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:53 2017 - [info]     GTID ON
Fri Jun 16 10:39:53 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:53 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:53 2017 - [info]     GTID ON
Fri Jun 16 10:39:53 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:53 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:39:53 2017 - [info] Checking slave configurations..
Fri Jun 16 10:39:53 2017 - [info] Checking replication filtering settings..
Fri Jun 16 10:39:53 2017 - [info]  Replication filtering check ok.
Fri Jun 16 10:39:53 2017 - [info] Master is down!
Fri Jun 16 10:39:53 2017 - [info] Terminating monitoring script.
Fri Jun 16 10:39:53 2017 - [info] Got exit code 20 (Master dead).
Fri Jun 16 10:39:53 2017 - [info] MHA::MasterFailover version 0.57.
Fri Jun 16 10:39:53 2017 - [info] Starting master failover.
Fri Jun 16 10:39:53 2017 - [info] 
Fri Jun 16 10:39:53 2017 - [info] * Phase 1: Configuration Check Phase..
Fri Jun 16 10:39:53 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] GTID failover mode = 1
Fri Jun 16 10:39:54 2017 - [info] Dead Servers:
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info] Checking master reachability via MySQL(double check)...
Fri Jun 16 10:39:54 2017 - [info]  ok.
Fri Jun 16 10:39:54 2017 - [info] Alive Servers:
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.20(192.168.0.20:3306)
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.30(192.168.0.30:3306)
Fri Jun 16 10:39:54 2017 - [info] Alive Slaves:
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.20(192.168.0.20:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:39:54 2017 - [info] Starting GTID based failover.
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 2: Dead Master Shutdown Phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] Forcing shutdown so that applications never connect to the current master..
Fri Jun 16 10:39:54 2017 - [warning] master_ip_failover_script is not set. Skipping invalidating dead master IP address.
Fri Jun 16 10:39:54 2017 - [warning] shutdown_script is not set. Skipping explicit shutting down of the dead master.
Fri Jun 16 10:39:54 2017 - [info] * Phase 2: Dead Master Shutdown Phase completed.
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 3: Master Recovery Phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 3.1: Getting Latest Slaves Phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] The latest binary log file/position on all slaves is mysql-bin.000002:497
Fri Jun 16 10:39:54 2017 - [info] Retrieved Gtid Set: 900f20f2-f48c-11e6-8d78-000c2930a8b9:1
Fri Jun 16 10:39:54 2017 - [info] Latest slaves (Slaves that received relay log files to the latest):
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.20(192.168.0.20:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:39:54 2017 - [info] The oldest binary log file/position on all slaves is mysql-bin.000002:497
Fri Jun 16 10:39:54 2017 - [info] Retrieved Gtid Set: 900f20f2-f48c-11e6-8d78-000c2930a8b9:1
Fri Jun 16 10:39:54 2017 - [info] Oldest slaves:
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.20(192.168.0.20:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 3.3: Determining New Master Phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] Searching new master from slaves..
Fri Jun 16 10:39:54 2017 - [info]  Candidate masters from the configuration file:
Fri Jun 16 10:39:54 2017 - [info]   192.168.0.30(192.168.0.30:3306)  Version=5.7.17-log (oldest major version between slaves) log-bin:enabled
Fri Jun 16 10:39:54 2017 - [info]     GTID ON
Fri Jun 16 10:39:54 2017 - [info]     Replicating from 192.168.0.40(192.168.0.40:3306)
Fri Jun 16 10:39:54 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Jun 16 10:39:54 2017 - [info]  Non-candidate masters:
Fri Jun 16 10:39:54 2017 - [info]  Searching from candidate_master slaves which have received the latest relay log events..
Fri Jun 16 10:39:54 2017 - [info] New master is 192.168.0.30(192.168.0.30:3306)
Fri Jun 16 10:39:54 2017 - [info] Starting master failover..
Fri Jun 16 10:39:54 2017 - [info] 
From:
192.168.0.40(192.168.0.40:3306) (current master)
 +--192.168.0.20(192.168.0.20:3306)
 +--192.168.0.30(192.168.0.30:3306)

To:
192.168.0.30(192.168.0.30:3306) (new master)
 +--192.168.0.20(192.168.0.20:3306)
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 3.3: New Master Recovery Phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info]  Waiting all logs to be applied.. 
Fri Jun 16 10:39:54 2017 - [info]   done.
Fri Jun 16 10:39:54 2017 - [info]  Replicating from the latest slave 192.168.0.20(192.168.0.20:3306) and waiting to apply..
Fri Jun 16 10:39:54 2017 - [info]  Waiting all logs to be applied on the latest slave.. 
Fri Jun 16 10:39:54 2017 - [info]  Resetting slave 192.168.0.30(192.168.0.30:3306) and starting replication from the new master 192.168.0.20(192.168.0.20:3306)..
Fri Jun 16 10:39:54 2017 - [info]  Executed CHANGE MASTER.
Fri Jun 16 10:39:54 2017 - [info]  Slave started.
Fri Jun 16 10:39:54 2017 - [info]  Waiting to execute all relay logs on 192.168.0.30(192.168.0.30:3306)..
Fri Jun 16 10:39:54 2017 - [info]  master_pos_wait(mysql-bin.000001:1879) completed on 192.168.0.30(192.168.0.30:3306). Executed 4 events.
Fri Jun 16 10:39:54 2017 - [info]   done.
Fri Jun 16 10:39:54 2017 - [info]   done.
Fri Jun 16 10:39:54 2017 - [info] Getting new master's binlog name and position..
Fri Jun 16 10:39:54 2017 - [info]  mysql-bin.000002:459
Fri Jun 16 10:39:54 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='192.168.0.30', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Fri Jun 16 10:39:54 2017 - [info] Master Recovery succeeded. File:Pos:Exec_Gtid_Set: mysql-bin.000002, 459, 56195f28-36e2-11e7-991b-000c29e3f5ab:1-6,
900f20f2-f48c-11e6-8d78-000c2930a8b9:1
Fri Jun 16 10:39:54 2017 - [warning] master_ip_failover_script is not set. Skipping taking over new master IP address.
Fri Jun 16 10:39:54 2017 - [info] Setting read_only=0 on 192.168.0.30(192.168.0.30:3306)..
Fri Jun 16 10:39:54 2017 - [info]  ok.
Fri Jun 16 10:39:54 2017 - [info] ** Finished master recovery successfully.
Fri Jun 16 10:39:54 2017 - [info] * Phase 3: Master Recovery Phase completed.
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 4: Slaves Recovery Phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 4.1: Starting Slaves in parallel..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] -- Slave recovery on host 192.168.0.20(192.168.0.20:3306) started, pid: 9905. Check tmp log /data/mha/3306/log/192.168.0.20_3306_20170616103953.log if it takes time..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] Log messages from 192.168.0.20 ...
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info]  Resetting slave 192.168.0.20(192.168.0.20:3306) and starting replication from the new master 192.168.0.30(192.168.0.30:3306)..
Fri Jun 16 10:39:54 2017 - [info]  Executed CHANGE MASTER.
Fri Jun 16 10:39:54 2017 - [info]  Slave started.
Fri Jun 16 10:39:54 2017 - [info]  gtid_wait(56195f28-36e2-11e7-991b-000c29e3f5ab:1-6,
900f20f2-f48c-11e6-8d78-000c2930a8b9:1) completed on 192.168.0.20(192.168.0.20:3306). Executed 0 events.
Fri Jun 16 10:39:54 2017 - [info] End of log messages from 192.168.0.20.
Fri Jun 16 10:39:54 2017 - [info] -- Slave on host 192.168.0.20(192.168.0.20:3306) started.
Fri Jun 16 10:39:54 2017 - [info] All new slave servers recovered successfully.
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] * Phase 5: New master cleanup phase..
Fri Jun 16 10:39:54 2017 - [info] 
Fri Jun 16 10:39:54 2017 - [info] Resetting slave info on the new master..
Fri Jun 16 10:39:54 2017 - [info]  192.168.0.30: Resetting slave info succeeded.
Fri Jun 16 10:39:54 2017 - [info] Master failover to 192.168.0.30(192.168.0.30:3306) completed successfully.
Fri Jun 16 10:39:54 2017 - [info] 

----- Failover Report -----

mha: MySQL Master failover 192.168.0.40(192.168.0.40:3306) to 192.168.0.30(192.168.0.30:3306) succeeded

Master 192.168.0.40(192.168.0.40:3306) is down!

Check MHA Manager logs at db_server_yayun_04:/data/mha/3306/log/manager.log for details.

Started automated(non-interactive) failover.
Selected 192.168.0.30(192.168.0.30:3306) as a new master.
192.168.0.30(192.168.0.30:3306): OK: Applying all logs succeeded.
192.168.0.20(192.168.0.20:3306): OK: Slave started, replicating from 192.168.0.30(192.168.0.30:3306)
192.168.0.30(192.168.0.30:3306): Resetting slave info succeeded.
Master failover to 192.168.0.30(192.168.0.30:3306) completed successfully.
复制代码

再来看看proxysql里面现在主机组的情况:

复制代码
[admin@127.0.0.1][(none)]> select * from runtime_mysql_servers;
+--------------+--------------+------+---------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| hostgroup_id | hostname     | port | status  | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment          |
+--------------+--------------+------+---------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
| 100          | 192.168.0.30 | 3306 | ONLINE  | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.30 | 3306 | ONLINE  | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.40 | 3306 | SHUNNED | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
| 1000         | 192.168.0.20 | 3306 | ONLINE  | 1      | 0           | 1000            | 10                  | 0       | 100            | test my proxysql |
+--------------+--------------+------+---------+--------+-------------+-----------------+---------------------+---------+----------------+------------------+
4 rows in set (0.01 sec)

[admin@127.0.0.1][(none)]> 
复制代码

可以看见40已经是SHUNNED状态,这个已经自动被踢出。30当选主库,自动被设置在主机组100. 再次运行读写测试:

复制代码
[admin@127.0.0.1][(none)]> select * from stats_mysql_query_digest;
+-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
| hostgroup | schemaname         | username | digest             | digest_text                      | count_star | first_seen | last_seen  | sum_time | min_time | max_time |
+-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
| 100       | yayun              | yayun    | 0x4BBB5CD4BC2CFD94 | insert into t1 select ?          | 1          | 1497581038 | 1497581038 | 114743   | 114743   | 114743   |
| 1000      | information_schema | yayun    | 0x620B328FE9D6D71A | SELECT DATABASE()                | 1          | 1497581026 | 1497581026 | 31128    | 31128    | 31128    |
| 100       | information_schema | yayun    | 0x594F2C744B698066 | select USER()                    | 1          | 1497581025 | 1497581025 | 0        | 0        | 0        |
| 1000      | yayun              | yayun    | 0x3765930C7143F468 | select * from t1                 | 1          | 1497581045 | 1497581045 | 3283     | 3283     | 3283     |
| 100       | information_schema | yayun    | 0x226CD90D52A2BA0B | select @@version_comment limit ? | 1          | 1497581025 | 1497581025 | 0        | 0        | 0        |
+-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
5 rows in set (0.00 sec)
复制代码

可以看见依然没有问题。到这里本文就结束了,上面是1主2从,如果是1主1从,那么从挂了怎么办呢? 需要额外一条规则,那就是在mysql_servers的hostgroup 1000 里面要插一条主库的记录,然后把weight设小,当读不到从库,回去主库查询

关于ProxySQL比较详细的文章可以参考如下:


ProxySQL之安装及配置详解

ProxySQL之读写分离与分库路由演示

 

作者:Atlas

出处:Atlas的博客  http://www.cnblogs.com/gomysql

您的支持是对博主最大的鼓励,感谢您的认真阅读。本文版权归作者所有,欢迎转载,但请保留该声明。如果您需要技术支持,本人亦提供有偿服务。


Viewing all 532 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>