haproxy 配置基础

  1. mode health
    该模式用于健康检查,tcp层面的,只返回OK,如下:
  2. stats
    1. 通过http页面查看状态:
    2.  通过tcp socket查看状态:


      这个socket也可以是一个unix socket:(或者同时写两个都是可以的)


      使用nc也行:

      tcp(或unix模式)有很多命令可以使用
    3. 一个一般的配置

       

 

 

 

 

参考资料: http://www.ttlsa.com/linux/haproxy-study-tutorial/

haproxy sni

目标:

让haproxy实现sniproxy的功能;sniproxy可以通过简单配置允许访问任意的https,但是haproxy针对每个要访问的server进行明确的配置,不过这个已经满足我们的需求了;目前对我们来讲,haproxy中的resolvers 指令是非常需要的

配置一:

最初配置时少了tcp-request 的两个(或者任意一个选项),会导致偶尔请求失败,因为if条件没有起作用,使得总是轮训

配置方式二:

最初配置时少了tcp-request 的两个(或者任意一个选项),使得无法找到一个合适的后端,于是就SSL connect error

第三种写法:

这里的tcp-request 选项写在backend中是也是可以的,如下:

一个包含443和80的配置:

 

参考资料: http://blog.haproxy.com/2012/04/13/enhanced-ssl-load-balancing-with-server-name-indication-sni-tls-extension/

基于sni的https负载均衡

ssl(会话层)的负载均衡介于http(应用层)负载均衡和tcp(传输层)负载均衡之间;

对于tcp(传输层)的负载均衡只能基于端口来做,访问同一个端口的请求就意味着转发到相同的后端服务器;

而对于http(应用层)的负载均衡可以介于应用层的很多东西来做,如:host、sessionid等;

对于https来讲,一般会将https证书放在负载均衡服务器上,对应用层数据解包之后再负载均衡;如果负载均衡不在自己管辖范围(亦或是处于别的原因考虑,如: 性能)而不想将证书放在负载均衡上,如何让一个负载均衡服务于多个服务呢?

这时候就可以考虑,通过sni来解析到servername之后,直接将请求通过tcp的方式转发到指定的服务,这样不需要对数据包进行解密操作,提高了性能,也更不需要将对应的证书放在负载均衡上了。

举个栗子: http://blog.haproxy.com/2012/04/13/enhanced-ssl-load-balancing-with-server-name-indication-sni-tls-extension/

神秘的loadavg

一般来讲,cpu使用率和iowait都会导致系统的loadavg很高,而且很多人也只限于cpu使用率和iowait导致系统的loadavg很高,于是,发现loadavg很高时,赶紧看看是cpu导致的还是iowait导致的,一般来讲都能很快定位问题;但是当发现cpu使用率几乎为0,iowait几乎为0,但是loadavg奇高便目瞪口呆了。

其实,不仅io等待会影响loadavg,其它资源的等待(如: 内存)也会导致loadavg的升高,只是很少遇到而已;一般来讲,进程在申请资源的时候,进程状态标识为D,如果很多进程状态为D,或者某些进程长时间处于状态D,都将导致loadavg的升高,而且进程的这种状态是非可中断的,试图发现阻塞在哪里的时候,发现的却是,strace的时候,strace没有响应,ltrace的时候,ltrace没有响应,pstack的时候,pstack没有响应,甚至ctrl+c都无法终止,幸运的话,ctrl+z可以推到后台并停止,然后kill  之

一般来讲,问题只要容易重现,就容易解决,不容易重现,就比较麻烦;尤其,等待内存资源的情况不太常见,如果真的没有内存了,你还能继续调试吗?而且默认情况下,当没有内存的时候,系统便会祭出oom来大开杀戒了;那么如何模拟呢?

docker给我们提供了限制内存使用(其实是cgroup)功能,而且可以设置禁用oom,那么,这就够了;在禁用oom的情况下,限制内存使用1g(注意最好禁用swap),然后启动容器,运行一个程序试图在容器里面申请2g的内存,我们将会发现进程申请1g内存之后卡住不动,系统cpu不高、iowait不高,loadavg在慢慢攀升

 

一个真实的情景:

在不晓得docker默认存储空间107G的情况下,把存储空间用满了,然后iowait很高,系统的loadavg也很高

docker之 docker ps无响应

现象:

docker exec无法进入容器,docker ps没有响应,strace跟踪docker daemon进程未发现明显异常(基本是水平不够,所以看不出来),但是能知道docker ps之所以没有响应,是和锁有关系,应该是有一个什么操作阻塞所致。

解决过程:

  1. docker daemon的事情基本和所启动的容器有关系,可以肯定一点的是,重启daemon就能好,但是该daemon下启动着几十个虚拟机,都将会停止,影响面比较大,而且该问题将来必然还会出现;所以,重启daemon是下下策
  2. 如果能找到有问题的容器,然后杀掉该容器,或者也许能找到问题根源;但是似乎比较难
  3. 庆幸的是,从日志上基本可以知道开始出现问题的时候刚好是启动某个容器的时候,那么,很有可能是该容器导致的,不妨试一试
  4. 所有容器的1号进程都一样,子进程都相像,docker destroy、docker ps 、docker inspect都没有响应,如何杀掉指定的容器?
  5. 办法: grep 所有容器的config.json,根据容器名确定哪个config.json 是有问题的容器的,然后,该config.json 中有该容器1号进程的pid,然后取出而杀之
  6. 问题解决

其他想到的:

  1. 一般来讲,每个容器都有一个hostname,默认是容器id的前面一部分,如果能知道这个问题都好办;但是我们的hostname被自定义了,那么,在知道hostname的情况下,找容器1号进程pid非常方便,因为hostname是进程环境变量的一部分(HOSTNAME),所以,可以:
    grep -l the_hostname /proc/*/envrion
    其中就包含容器1号进程的pid
  2. 如果能在1号进程名上区分开来会更加方便,我们的docker容器的1号进程都是/sbin/init,如果能添加一个进程/sbin/init 不会在意的多余的参数作为标识,便是极好的(一般来讲是可以的),但是测试发现给/sbin/init 添加的参数在ps的时候都是不给显示的(大概这里的init是entrypoint,不是cmd)
  3. 在创建容器的时候,给容器指定一个具有标识性的环境变量,也将有助于找到进程的pid
  4. docker top的时候,我们可以看到容器里面的进程信息,但是这里看到的进程id是容器里面的私有pid,每个私有pid都在宿主机上对应一个全局的pid,根据全局的pid也可以通过 “pstree -p 全局的pid ”来查看容器里面的所有进程,然后,还可以做到不进入容器就能strace容器里面的进程的pid
  5. nsenter和docker的exec都能进入容器里面,但是docker的exec依赖docker daemon进程,如果docker daemon进程无法响应,就不好使了;而nsenter却不依赖docker daemon进程,直接就可以进入容器内部(我没有试过哦)

stunnel vs spipe vs shadowsocks

shadowsocks ~= spipe + socket5_proxy

即:

shadowsocks_client   —–基于共享密码的加密数据—-> shadowsocks_server(解密数据,并将解密后的数据作为socket5协议来处理)

spipe和stunnel只是简单的数据的加密传输

如果client支持socket5代理,则使用shadowsocks比较方便;如果client不支持socket5代理,则需要在shadowsocks_client 前面添加一个支持socket5代理的proxy,如:(polipo)

如果client不支持任何代理设置,则可以通过一个自建的dns+stunnel+sniproxy来实现:

  1. 将需要代理的域名,通过dns解析到stunnel
  2. stunnel_client对数据进行加密传输给stunnel_server,stunnel_server解密数据并转给sniproxy
  3. sniproxy进行代理

spipe 是基于共享秘钥加密的,没有认证功能;也有其他基于ssl的实现方式,具有认证功能,如: https://github.com/dchest/spipe

stunnel是通过ssl实现的,具有认证功能

基于ssl握手认证的实现需要配置证书相关的东西,相对共享秘钥来讲麻烦一些

centos7之systemctl

Centos7之前,我们都通过service命令来管理(启动、停止、重启)服务,通过chkconfig来管理服务的自启动; 然而,这两个命令并不属于同一个软件包, service命令属于initscripts软件包,chkconfig是一个单独的chkconfig软件包。

service命令非常好用,但是服务名和操作的顺序不太符合一些人的口味,如: service mysql start ; 有些人就经常写做: service start mysql

而,chkconfig虽然可以添加、查看服务,但是添加的服务脚本也有一点苛刻,脚本中需要添加:

别以为这是注释,没有的话,chkconfig add是不认的,如果不chkconfig add的话,chkconfig也list不出来你的服务的。

Centos7中服务的管理使用的是systemctl, 他是systemd软件包中的一个命令,该命令不仅仅管理服务,包括关机都能管

几个关键路径:

  1. 服务脚本目录: /usr/lib/systemd/system/
  2. 服务自动启动目录:/etc/systemd/system/
    systemctl enable xxx的时候就在这个目录下添加一个软连接到实际的脚本

参考资料: http://www.linuxidc.com/Linux/2015-04/116648.htm

 

systemd-vs-sysVinit-cheatsheet

 

数据结构 之 树

概述

树的章节一般分两大部分: 一部分将树,一部分将二叉树;虽然二叉树也是树,但是二叉树足够特殊,足够有用,所以重点来讲;或者说,如果不是二叉树,树的家族也不会如此的德高望重。

在二叉树中,又有一些特殊性质的二叉树,可能没法用树的结构来描述他们之间的关系;比如: 满二叉树一定是完全二叉树;完全二叉树和二叉排序树直接却没有从属关系;完全二叉树和二叉排序树是从不同的维度定义出来的特殊的二叉树。

二叉排序树(也叫二叉查找树)在树的家族中是一颗耀眼的明星,但是在树的章节中没有被介绍,大概因为这是二叉树的实际应用,而和树本身的形态没有直接关系;还有一些特殊的树,如:红黑树、B+、B-树,稍后再研究,有些数据结构的书是没有提及的,大概因为这些东西可以自学,不需要教吧。

树的逻辑结构

树的定义

树是n(n>=0)个结点的有限集合。当 n= 0 时,称为空树;任意一颗非空树满足一下两个条件:

  1. 有且只有一个特定的称为根的结点
  2. 当 n > 1 时,除根结点之外的其余结点被分成m(m>0)个互不相交的有限集合T1, T2, …, Tm,其中每个集合又是一棵树,并称为这个根结点的子树

树的基本术语

  1. 结点的度,树的度
  2. 叶子结点,分支结点
  3. 孩子结点,双亲结点,兄弟结点
  4. 路径、路径长度
  5. 祖先、子孙
  6. 结点的层数、树的深度(高度)
  7. 层序编号
  8. 有序树、无序树
  9. 森林
  10. 同构

树的表示形式

一般有四种表示形式:

  1. 树形图
  2. 嵌套图
  3. 凹凸表
  4. 广义表

树的遍历

  1. 前(根)序遍历
  2. 后(根)序遍历
  3. 层序遍历
    注: 这里说的是树,不是二叉树,所以没有中序遍历(如果有的话,三个子树的树根应该放哪里?)

树的存储结构

  1. 双亲表示法
    1. 思想: 每个结点都记住自己双亲结点的位置(即可保证该树是唯一的)
    2. 缺点: 要找到一个结点的所有孩子是比较麻烦的
    3. 使用数组存储还是比较方便的
  2. 孩子表示法
    1. 思想: 每个结点都记住自己孩子的位置(即可保证该树是唯一的)
    2. 缺点: 要找到结点的双亲结点比较麻烦
    3.  两种形式:
      1.  多重链表标识
        1. 思想: 父亲那N个绳拉住自己的N个孩子
        2. 关于拿几根绳?两种办法:
          1. 有几个孩子拿几根绳
            1. 需要有一个地方记录自己的绳子数目(就是该结点的度)
          2. 孩子最多的父亲拿几根绳子大家就都拿几根绳子
            1. 没人的绳子数目是一样的,不需要各自记录
      2. 孩子链表
        1. 思想:所有节点维护在一个数组中;然后,父亲拿一根绳子牵着老大,然后老大牵着老二;依次类推
  3. 孩子双亲表示法
    1. 思想:孩子表示法中,添加一个双亲节点的指针
  4. 孩子兄弟表示法
    1. 思想: 每个节点都左手拉着自己孩子,右手拉着自己的弟弟妹妹

二叉树

概述

二叉树是一种最简单的树结构,其存储结构更具有规范性和确定性,在一系列条件的约束下,使得二叉树具有很多的性质,方便我们研究和使用二叉树。

二叉树的定义

二叉树的5种基本形态

  1. 空二叉树
  2. 只有一个根结点
  3. 根结点只有左子树
  4. 根结点只有右子树
  5. 根结点既有左子树又有右子树

特殊二叉树

  1. 斜树
    1. 左斜树
    2. 右斜树
  2. 满二叉树
  3. 完全二叉树
    1. 从满二叉树的最后面的结点去掉n (n >= 0)个结点都是完全二叉树
    2. 满二叉树是一种特殊的完全二叉树

二叉树的性质

二叉树有5个重要的性质,他们主要讨论了树的深度、结点数等之间的关系

  1. 在二叉树的第i层上至多有2i-1个结点 (i >= 1)
  2. 深度为k的二叉树至多有2k-1个结点 (k >=1)
  3. 对任何一颗二叉树T,如果其叶子结点数为n0,度为2的结点数为n2,则:n0=n2+1
  4. 具有n个结点的完全二叉树的深度为log2n+1
  5. 如果有一颗有n个节点的完全二叉树的节点按层次序编号,对任一层的节点i(1<=i<=n)有1.如果i=1,则节点是二叉树的根,无双亲,如果i>1,则其双亲节点为[i/2],向下取整

    2.如果2i>n那么节点i没有左孩子,否则其左孩子为2i

    3.如果2i+1>n那么节点没有右孩子,否则右孩子为2i+1

二叉树的遍历

  1. 前序遍历
  2. 中序遍历
  3. 后序遍历

二叉树的存储

顺序表

思想: 将一棵树通过添加“虚节点”的方式完善成完全二叉树,然后存储

缺点: 空间浪费严重,只适合存储完全二叉树的情况

链式存储

  1. 二叉链表
    1. 思想: 每个结点包含数据域和左右孩子两个指针域
    2. 缺点: 寻找双亲结点不方便
  2. 三叉链表
    1. 思想: 在二叉链表的基础上添加双亲结点指针域

线索链表

实际问题: select * from tb where id < N limit 2; 在这种情况下,我们不仅要查到id=2的结点,还要找到他附近的一些结点;即: 需要访问二叉树中的结点在某种遍历序列中的前驱和后继;

于是: 在存储结构中应该保存结点在某种遍历序列中前驱和后继的信息。

思想: 根据二叉树的性质可知,二叉树中有n+1个指针域为空,可以想办法利用起来;通过添加标记来区分是孩子指针还是前驱(或后继)指针;注意: 挨着自己的孩子在线索化中未必就挨着自己,但是要找到挨着自己的那个结点并不难,对于中序线索链表,如果自己子树的深度为k,则,找到自己的前驱或后继的时间复杂度为log2k

由于二叉树的遍历次序有三种,因此有三种意义上的前驱和后继,相应的也有三种线索链表:前序线索链表、中序线索链表、后序线索链表。中序线索链表看起来更加直观一些

中序线索链表上求结点前驱
中序线索链表上求结点后继
中序线索链表上遍历