树莓派 GSM 语音和短信报警

树莓派 GSM 语音短信报警

  1. 树莓派系统安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1. 购买树莓派
    2. 购买无线网卡,读卡器,内存卡,HDMI转VGA线,USB转RS232串口连接转换线,相应的电源线
    3. 下载树莓派系统
    4. 将系统安装到内存卡中
    5. 接通电源,启动服务
    6. 通过无线网卡,连接WIFI
    7. 启动ssh服务
    root@raspberrypi:~# /etc/init.d/ssh start
    root@raspberrypi:~# update-rc.d ssh defaults
    root@raspberrypi:~# update-rc.d -f ssh remove
  2. 树莓派基本操作

    1
    2
    3
    4
    5
    6
    7
    pi@raspberrypi:~ $ sudo su -
    root@raspberrypi:~# apt-get update
    root@raspberrypi:~# apt-get upgrade
    root@raspberrypi:~# apt-get install vim ntpdate
    root@raspberrypi:~# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

    root@raspberrypi:~# raspi-config 初始化一些配置
  3. 树莓派工具 密码:yb3k


树莓派 调用 GSM模块 发送短信 和 语音

  1. 给单个用户发送短信

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    pi@raspberrypi:~/gsm $ cat send_sms.py
    #!/usr/bin/env python3
    #coding:utf8

    import RPi.GPIO as GPIO
    import serial
    import time, sys
    import datetime
    import binascii

    P_BUTTON = 24

    def setup():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP)

    def Unicode2HexStr(Unicde_Str):
    Hex_Str = ""
    for i in range(0,len(Unicde_Str)):
    Hex_Str += (hex(ord(Unicde_Str[i])).replace('0x','').zfill(4))
    return Hex_Str

    INFO = Unicode2HexStr('hi,刘朋')

    AA = binascii.a2b_hex('1A')

    SERIAL_PORT = "/dev/ttyUSB0"

    ser = serial.Serial(SERIAL_PORT, baudrate = 9600, timeout = 5)
    setup()

    ser.write('AT+CMGDA="DEL ALL"\r'.encode())
    print(ser.read(ser.inWaiting()))
    time.sleep(1)

    ser.write("AT+CMGF=1\r".encode())
    print(ser.read(ser.inWaiting()))
    time.sleep(1)

    ser.write('AT+CSMP=17,167,2,25\r'.encode())
    time.sleep(3)
    print(ser.read(ser.inWaiting()))

    ser.write('AT+CSCS="UCS2"\r'.encode())
    time.sleep(3)
    print(ser.read(ser.inWaiting()))

    ser.write('AT+CMGS="00310035003200300031003600380034003200320033"\r'.encode())
    time.sleep(2)
    ser.write(INFO.encode())
    time.sleep(1)
    ser.write(AA)
    print(ser.read(ser.inWaiting()))
    time.sleep(1)

    ser.close()
  2. 给单个用户打电话,语音报警

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    pi@raspberrypi:~/gsm $ cat send_gms.py
    #!/usr/bin/env python3
    #coding:utf8

    import serial
    import time, sys
    import datetime

    def Unicode2HexStr(Unicde_Str):
    Hex_Str = ""
    for i in range(0,len(Unicde_Str)):
    Hex_Str += (hex(ord(Unicde_Str[i])).replace('0x','').zfill(4))
    return Hex_Str

    INFO = Unicode2HexStr('你在干什么!!! 我一直在盯着你!!! 你给我小心点!!!')
    SEND_INFO = 'AT+CTTS=1,' + '"' + INFO + '"'
    PHONE_NUM = 'ATD' + '{手机号}' + ';'


    SERIAL_PORT = "/dev/ttyUSB0"

    ser = serial.Serial(SERIAL_PORT, baudrate = 115200, timeout = 5)
    time.sleep(1)

    ser.write("ATE1;\r".encode())
    time.sleep(1)
    print(ser.read(ser.inWaiting()))

    ser.write("AT+COLP=1\r".encode())
    time.sleep(1)
    print(ser.read(ser.inWaiting()))

    ser.write("AT+CTTSRING=0\r".encode())
    time.sleep(1)
    print(ser.read(ser.inWaiting()))

    ser.write("AT+CTTSPARAM=20,0,50,75,1\r".encode())
    time.sleep(1)
    print(ser.read(ser.inWaiting()))

    ser.write((PHONE_NUM + '\r').encode())
    time.sleep(1)

    ABC = 0
    CBA = 0
    while ABC != 1:
    AA = ser.read(ser.inWaiting())
    BB = AA.decode()
    if '+COLP' in BB and 'OK' in BB:
    ser.write((SEND_INFO + '\r').encode())
    time.sleep(10)
    ser.write("AT+CTTS=0\r".encode())
    time.sleep(1)
    ABC = 1
    CBA += 1
    print(CBA)
    if CBA == 50:
    ABC = 1
    time.sleep(1)

    ser.write("ATH\r".encode())
    ser.close()

树莓派 python 模块

  1. pyserial

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    Python中用于读串口的模块是"pySerial"
    读取串口时首先导入包"import serial",其次设置读取哪一个口,波特率,数据位,停止位

    例如:
    class serial.Serial
    __init__( port=None,
    baudrate=9600,
    bytesize=EIGHTBITS,
    parity=PARITY_NONE,
    stopbits=STOPBITS_ONE,
    timeout=None,
    xonxoff=False,
    rtscts=False,
    writeTimeout=None,
    dsrdtr=False,
    interCharTimeout=None )


    baudrate:设置波特率
    bytesize:数据位
    stopbits:停止位
    timeout:超时时间
    timeout = None: 长时间等待
    timeout = 0: 不阻塞形式 (读完之后就返回)
    timeout = x: x秒后超时 (float allowed)

    例2:
    import serial //导入模块
    ser = serial.Serial(0) //打开第一个串口
    print ser.portstr //能看到第一个串口的标识,windows下是COM1
    ser.write("hello") //往串口里面写数据
    ser.close() //关闭ser表示的串口
    ser.open() //打开这个串口
    ser = serial.Serial('COM1', 115200) //来设置波特率,当然还有专门的函数
    data = ser.read() //可以读1个字符
    data = ser.read(20) //可以读20个字符
    data = ser.readline() //是读一行,以/n结束,要是没有/n就一直读,阻塞
    data = ser.readlines()和ser.xreadlines() //都需要设置超时时间
    ser.baudrate = 9600 //设置波特率
    ser //来查看当前串口的状态
    ser.isOpen() //看看这个串口是否已经被打开
  2. RPi.GPIO

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态-是高电平或是低电平
    用户可以通过GPIO口和硬件进行数据交互(如UART),控制硬件工作(如LED、蜂鸣器等),读取硬件的工作状态信号(如中断信号)等
    掌握了GPIO,差不多相当于掌握了操作硬件的能力

    例如:
    import RPi.GPIO as GPIO //导入RPi.GPIO模块
    或者:
    try:
    import RPi.GPIO as GPIO
    except RuntimeError:
    print("引入错误")

    在RPi.GPIO中,同时支持树莓派上的两种GPIO引脚编号:
    第一种编号是BOARD编号,这和树莓派电路板上的物理引脚编号相对应.使用这种编号的好处是,你的硬件将是一直可以使用的,不用担心树莓派的版本问题.因此,在电路板升级后,你不需要重写连接器或代码
    第二种编号是BCM规则,是更底层的工作方式,它和Broadcom的片上系统中信道编号相对应.在使用一个引脚时,你需要查找信道号和物理引脚编号之间的对应规则.对于不同的树莓派版本,编写的脚本文件也可能是无法通用的

    GPIO.setmode(GPIO.BOARD) //指定BOARD编号
    或者
    GPIO.setmode(GPIO.BCM) //指定BCM规则

    mode = GPIO.getmode() //返回设置的引脚编号

    GPIO.setwarnings(False) //禁用警告

    GPIO.setup(channel, GPIO.IN) //将引脚设置为输入模式
    GPIO.setup(channel, GPIO.OUT) //将引脚设置为输出模式
    GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH) //为输出的引脚设置默认值

    GPIO.cleanup() //释放脚本中的使用的引脚(注意,GPIO.cleanup()只会释放掉脚本中使用的GPIO引脚,并会清除设置的引脚编号规则)

    GPIO.output(channel, state) //设置引脚的输出状态(状态可以设置为0,GPIO.LOW,False,1,GPIO.HIGH,True.如果编码规则为"GPIO.BOARD",那么channel就是对应引脚的数字)

    一次性设置多个引脚:
    chan_list = [11,12]
    GPIO.output(chan_list, GPIO.LOW)
    GPIO.output(chan_list, (GPIO.HIGH, GPIO.LOW))

    你还可以使用Input()函数读取一个输出引脚的状态并将其作为输出值:
    GPIO.output(12, not GPIO.input(12))

    我们也常常需要读取引脚的输入状态,获取引脚输入状态如下代码:
    GPIO.input(channel)
    注意:低电平返回0,GPIO.LOW,False;高电平返回1,GPIO.HIGH.True

    如果输入引脚处于悬空状态,引脚的值将是漂动的.换句话说,读取到的值是未知的,因为它并没有被连接到任何的信号上,直到按下一个按钮或开关.由于干扰的影响,输入的值可能会反复的变化.解决方法:
    GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    注意: 上面的读取代码只是获取当前一瞬间的引脚输入信号

    如果需要实时监控引脚的状态变化,可以有两种办法:
    最简单原始的方式是每隔一段时间检查输入的信号值,这种方式被称为轮询.如果你的程序读取的时机错误,则很可能会丢失输入信号.轮询是在循环中执行的,这种方式比较占用处理器资源.
    另一种响应GPIO输入的方式是使用中断(边缘检测),这里的边缘是指信号从高到低的变换(下降沿)或从低到高的变换(上升沿)

    轮询方式:
    while GPIO.input(channel) == GPIO.LOW:
    time.sleep(0.01) # wait 10 ms to give CPU chance to do other things

    边缘检测:
    边缘是指信号状态的改变,从低到高(上升沿)或从高到低(下降沿).通常情况下,我们更关心于输入状态的该边而不是输入信号的值.这种状态的该边被称为事件
    先介绍两个函数:
    * wait_for_edge() 函数
    wait_for_edge()被用于阻止程序的继续执行,直到检测到一个边沿.也就是说,上文中等待按钮按下的实例可以改写为:
    channel = GPIO.wait_for_edge(channel, GPIO_RISING, timeout=5000)
    if channel is None:
    print('Timeout occurred')
    else:
    print('Edge detected on channel', channel)

    * add_event_detect() 函数
    该函数对一个引脚进行监听,一旦引脚输入状态发生了改变,调用event_detected()函数会返回true,如下代码:
    GPIO.add_event_detect(channel, GPIO.RISING) # add rising edge detection on a channel
    do_something()
    // 下面的代码放在一个线程循环执行。
    if GPIO.event_detected(channel):
    print('Button pressed')

    设置多个回调函数:
    def my_callback_one(channel):
    print('Callback one')
    def my_callback_two(channel):
    print('Callback two')
    GPIO.add_event_detect(channel, GPIO.RISING)
    GPIO.add_event_callback(channel, my_callback_one)
    GPIO.add_event_callback(channel, my_callback_two)
    注意:回调触发时,并不会同时执行回调函数,而是根据设置的顺序调用它们

    例如: 点亮LED灯
    import RPi.GPIO as GPIO //引入函数库
    import time

    RPi.GPIO.setmode(GPIO.BOARD) //设置引脚编号规则
    RPi.GPIO.setup(11, RPi.GPIO.OUT) //将11号引脚设置成输出模式

    while True
    GPIO.output(channel, 1) //将引脚的状态设置为高电平,此时LED亮了
    time.sleep(1) //程序休眠1秒钟,让LED亮1秒
    GPIO.output(channel, 0) //将引脚状态设置为低电平,此时LED灭了
    time.sleep(1) //程序休眠1秒钟,让LED灭1秒

    GPIO.cleanup() //程序的最后别忘记清除所有资源

00310035003200300031003600380034003200320033
00480065006C006C006F002C5218670B

RabbitMQ working principle

RabbitMQ 工作原理

  1. RabbitMQ 简介

    1
    2
    3
    4
    5
    6
    * 开源AMQP实现,Erlang语言编写,支持多种客户端
    * 分布式,高可用,持久化,可靠,安全
    * 支持多种协议: AMQP,STOMP,MQTT,HTTP
    * RabbitMQ主要概念对象: 生产者,消费者,交换机,队列
    * 业务解耦: 解决多系统,易购系统间的数据交换,解耦生产者和消费者
    * 适用场景: 批量数据异步处理,并行任务串行化,高负载任务负载均衡
  2. RabbitMQ 核心概念
    AMQP基本概念图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    * 颗粒度
    * Broker
    接收和分发消息的应用,RabbitMQ Server就是Message Broker

    * Virtual host
    虚拟主机(虚拟组),一个Broker可以开设多个vhost.因为RabbitMQ当中,用户只能在虚拟主机的粒度进行权限控制.
    当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange和queue等

    * Exchange 消息交换机
    message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发消息到queue中去
    常用的类型有:direct(point-to-point),topic(publish-subscribe),fanout(multicast)

    * Queue 消息队列载体
    消息最终被送到这里等待consumer取走.一个message可以被同时拷贝到多个queue中


    * 消息流转
    * Binding 绑定,根据路由规则绑定Exchange和Queue
    exchange和queue之间的虚拟连接,binding中可以包含routing key
    Binding信息被保存到exchange中的查询表中,用于message的分发依据

    * Routing Key 路由关键字

    * Connection 连接
    Publisher/Consumer和broker之间的TCP连接
    断开连接的操作只会在client端进行,Broker不会断开连接,除非出现网络故障或broker服务出现问题

    * Channel 消息通道,每个连接可建立多个channel
    如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低.Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了"channel id"帮助客户端和"message broker"识别channel,所以channel之间是完全隔离的.Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销


    * 关联对象
    * Producter 消息生产者
    * Consumer 消息消费者
  3. RabbitMQ典型生产消费消息模型

    1
    生产者发送消息到broker server(RabbitMQ).在Broker内部,用户创建Exchange/Queue,通过Binding规则将两者联系在一起.Exchange分发消息,根据类型/binding的不同,分发策略有所区别.消息最后来到Queue中,等待消费者取走

RabbitMQ典型生产消费消息模型

  1. RabbitMQ 交换机类型- Direct Exchange
    1
    Direct Exchange 路由机制: 通过精确匹配消息的路由关键字,将消息路由到零个或者多个队列中,绑定关键字用来将队列和交换器绑定在一起

Exchange.Direct

  1. RabbitMQ 交换机类型- Topic Exchange
    1
    Topic Exchange 路由机制: 通过消息的路由关键字和绑定关键字的模式匹配,将消息路由到被绑定的队列中.这种路由器类型可以被用来支持经典的发布/订阅消息传输模式

Exchange.Topic

  1. RabbitMQ 交换机类型- Fanout Exchange
    1
    Fanout Exchange 路由机制: 不论消息的路由关键字是什么,这条信息都会被路由到所有与该交换机绑定的队列中

Exchange.Fanout

  1. RabbitMQ 交换机类型- Headers Exchange
    1
    Headers Exchange 路由机制: 键值对匹配路由

Exchange.Headers

  1. 操作流程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    (1)  消费者创建消息队列
    (2) 消费者定义消息队列
    (3) 消费者定义特定类型的交换机
    (4) 消费者设定绑定规则
    (5) 等待消息
    (6) 生产者创建消息
    (7) 生产者将消息投递至信息通道中
    (8) 交换机获取消息
    (9) 消费者获取并处理消息,并发送反馈
    (10) 结束,关闭通道和连接
  2. 实践保障和规划

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //保障
    * 复用connection,复用channel
    * 如果需要可靠业务,需要持久化和ack机制
    * 如果希望高吞吐,可以采取非持久化,noack,自动删除机制
    * 稳定性保障: 生产者异常保障,消费者异常保障

    //规划
    * 生产者面对Exchange,消费者面对queue
    * 一个queue只有一个消费者(可多个副本)进行处理
    * 命名规划:
    - exchange: ex_{业务对象}_{业务场景}
    - routekey: {事件,某个业务动作}
    - queue: ex_{业务对象}_{业务场景}_{事件}
    * 串行或并行业务方案
    - 并行方案: 一个事件发生后,多个消费者相互间没有依赖关系,可由Exchange分发消息到多个队列,由各队列的消费者并行进行处理
    - 串行方案: 一个事件发生后,多个消费者有先后依赖关系,可以由先执行的消费者处理事情,处理完成后再次发送消息到exchange,由后续的队列进行处理
  3. 应用场景-异步处理
    场景说明: 用户注册后,需要发注册邮件和注册短信,传统的做法有两种:

    1. 串行的方式
      将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端
      这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西.
      异步处理串行
    2. 并行的方式
      将注册信息写入数据库后,发送邮件的同时发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间
      异步处理并行
    3. 引入消息队列
      引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理.用户的响应时间就等于写入数据库的时间+写入消息队列的时间,响应时间是串行的3倍,是并行的2倍
      异步处理消息队列
  4. 应用场景-应用解耦
    场景说明: 双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.这种做法有一个缺点,当库存系统出现故障时,订单就会失败
    应用解耦传统模式
    引入消息队列,订单系统(用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功),库存系统(订阅下单的消息,获取下单消息,进行库操作),就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失
    应用解耦引入消息队列

  5. 应用场景-流量削峰
    场景说明: 流量削峰一般在秒杀活动中应用广泛.秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列.一方面可以控制活动人数,超过此一定阀值的订单直接丢弃;另一方面可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
    引入消息队列后,用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面.秒杀业务根据消息队列中的请求信息,再做后续处理.
    流量消峰引入消息队列

  6. RabbitMQ 系统架构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    * Round-robin dispathching 循环分发
    RabbbitMQ的分发机制非常适合扩展,而且它是专门为并发程序设计的,如果现在load加重,那么只需要创建更多的Consumer来进行任务处理

    * Message acknowledgment 消息确认
    为了保证数据不被丢失,RabbitMQ支持消息确认机制,为了保证数据能被正确处理,而不仅仅是被Consumer收到,那么我们不能采用no-ack,而应该是在处理完数据之后发送ack.
    在处理完数据之后发送ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以安全的删除它了.如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer,这样就保证在Consumer异常退出情况下数据也不会丢失.
    RabbitMQ它没有用到超时机制.RabbitMQ仅仅通过Consumer的连接中断来确认该Message并没有正确处理,也就是说RabbitMQ给了Consumer足够长的时间做数据处理.如果忘记ack,那么当Consumer退出时,Mesage会重新分发,然后RabbitMQ会占用越来越多的内存.

    * Message durability 消息持久化
    要持久化队列(queue)的持久化需要在声明时指定"durable=True;"
    这里要注意,队列的名字一定要是Broker中不存在的,不然不能改变此队列的任何属性.

    队列和交换机有一个创建时候指定的标志durable,durable的唯一含义就是具有这个标志的队列和交换机会在重启之后重新建立,它不表示说在队列中的消息会在重启后恢复

    消息持久化包括3部分:
    1.exchange持久化,在声明时指定"durable => true"
    2.queue持久化,在声明时指定"durable => true"
    3.消息持久化,在投递时指定"delivery_mode => 2" (1是非持久化)
    注意:
    * 如果exchange和queue都是持久化的,那么它们之间的binding也是持久化的
    * 如果exchange和queue两者之间有一个持久化,一个非持久化,则不允许建立绑定
    * 一旦创建了队列和交换机,就不能修改其标志了.例如,创建了一个non-durable的队列,然后想把它改变成durable的,唯一的办法就是删除这个队列然后重现创建

    * Fair dispath 公平分发
    分发机制默认状态下不是那么优雅: RabbitMQ将第N个Message分发给第N个Consumer.N是取余后的,它不管Consumer是否还有"unacked Message",只是按照这个默认的机制进行分发.如果有个Consumer工作比较重,那么就会导致有的Consumer基本没事可做,有的Consumer却毫无休息的机会

    解决方法:
    公平分发: 通过basic.qos方法设置"prefetch_count=1",这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message,换句话说,在接收到该Consumer的ack前,它不会将新的Message分发给它
    注意: 这种方法可能会导致queue爆满.当然,这种情况下你可能需要添加更多的Consumer,或者创建更多的virtual Host来细化你的设计

    * 分发到多个Consumer
    重温交换机类型:
    * Direct Exchange
    直接匹配,通过Exchange名称+Rounting Key来发送与接收消息
    * Fanout Exchange
    广播订阅,向所有的消费者发布消息,但是只有消费者将队列绑定到该路由器才能收到消息,忽略Routing Key
    * Topic Exchange
    主题匹配订阅,这里的主题指的是Routing Key,Routing Key可以采用通配符,如:*或#,RoutingKey命名采用"."来分隔多个词,只有消息这将队列绑定到该路由器且指定Routing Key符合匹配规则时才能收到消息
    * Headers Exchange
    消息头订阅,消息发布前,为消息定义一个或多个键值对的消息头,然后消费者接收消息同时需要定义类似的键值对请求头:(如:x-mactch=all或者x_match=any),只有请求头与消息头匹配,才能接收消息,忽略RoutingKey.
    * 默认的exchange
    如果用空字符串去声明一个exchange,那么系统就会使用"amq.direct"这个exchange.我们创建一个queue时,默认的都会有一个和新建queue同名的routing Key绑定到这个默认的exchange上去

    如果有两个接收程序都是用了同一个的queue和相同的routingKey去绑定direct exchange的话,分发的行为是负载均衡的,也就是说第一个是程序1收到,第二个是程序2收到,以此类推
    如果有两个接收程序用了各自的queue,但使用相同的routingKey去绑定direct exchange的话,分发的行为是复制的,也就是说每个程序都会收到这个消息的副本.行为相当于fanout类型的

    * 消息序列化
    RabbitMQ使用ProtoBuf序列化消息,它可作为RabbitMQ的Message的数据格式进行传输,由于是结构化的数据,这样就极大的方便了Consumer的数据高效处理,当然也可以使用XML,与XML相比,ProtoBuf有优势.

RabbitMQ high availability

RabbitMQ 高可用方案

  1. Rabbit集群方案一: 普通模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    普通模式:
    exchange,buindling在所有的节点上都会保存一份,但queue只会存储在其中的一个节点上,同时所有的节点还都会存储一份queue的meta信息.
    因为这样有两个好处:
    1.存储空间
    如果每一个节点上都有全部的消息,有多少个节点就会有多少个消息总量的"拷贝".
    假如一个队列的消息占用的空间是1G,那么三个节点就是3G
    2.性能
    * 消息需要在节点之间传输会有很大的网络开销
    * 如果消息设置了durable(即,持久化),还会增加很大的磁盘负载

    队列存储的节点 取决于 创建队列的客户端当时所连接的节点.如果生产者连接的是另外一个节点,将会把消息转发到存储该队列的节点上.如果消费者连接了非存储队列的节点取数据,消费者从存储消息的节点拉去数据.
    所以:
    1.创建队列都连到了一个节点上,所有的队列都存储在一个节点上
    2.存消息的节点挂掉了,consumer只能等到节点恢复后才能读到消息
    3.设A,B节点,queue数据在A上,可以向A或B生产或消费消息.一旦往B生产消息时A挂了,client不会收到任何错误信息并继续发送,而实际上消息是被丢弃了.而此时如果client也挂了,再连接B时会报节点A不存在而失败.在B读也是类似的,在client批量取到的数据读完之前是不会知道A有没有挂掉,等到读取下一批数据时一旦A挂掉会报错
    所以这种集群方法的特点是:高吞吐量,非高可用
  2. Rabbit集群方案一: 镜像模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    镜像模式:

    镜像模式和普通模式的区别:
    队列的数据都镜像了一份到所有的节点上.这样任何一个节点失效,不会影响整个集群的使用

    在实现上,镜像队列内部有一套选举算法,会选出一个master和若干个slaver.master和slaver通过相互间不断发送心跳来检查是否连接断开.可以通过指定net_ticktime来控制心跳检查频率.注意一个单位时间net_ticktime实际上做了4次交互,故当超过net_ticktime(± 25%)秒没有响应的话则认为节点挂掉.另外注意修改net_ticktime时需要所有节点都一致:
    配置举例:
    [
    {rabbit, [{tcp_listeners, [5672]}]},
    {kernel, [{net_ticktime, 120}]}
    ].

    consumer,任意连接一个节点,若连上的不是master,请求会转发给master,为了保证消息的可靠性,consumer回复ack给master后,master删除消息并广播所有的slaver去删除.

    publisher,任意连接一个节点,若连上的不是master,则转发给master,由master存储并转发给其他的slaver存储.如果master挂掉,则从slaver中选择消息队列最长的为master,在这种情况下可以存在消息未同步,给ack消息未同步的情况,会造成消息重发(默认是异步同步的).
    总共有以下几件事情发生:
    1.1个最老的(队列最长的)的slaver提升为master,如果没有一个slaver是和master同步的则会造成消息丢失
    2.要提升为master的slaver会认为以前所有连接挂掉的master的消费者都断开了连接.那么存在clinet发送了ack的消息但还在路上是master挂掉的情况,或者master收到了ack但是在广播给slaver的时候master挂掉的情况,所以新的master别无选择,只能认为消息没有被确认.他会requeue他认为没有ack的消息.那么client可能就收到了重复的消息,并要再次发送ack
    3.从镜像队列中消费的client支持了consumer Cancellation通知的,将收到通知并订阅的mirrored-queue被取消了,这是因为该mirrored-queue升级成了master,这是client需要重现去找mirrored-queue上消费,这样就避免了client继续发送ack到老的挂掉的master上.避免收到新的master发送的相同的消息.
    4.如果noAck=true,且在mirrored-queue上消费,那么在切换时由于服务器是先ack然后发送到noAck=true的消费者,这时连接断开可能导致该数据丢失

    如果slaver挂掉,则集群的节点状态没有任何变化.只要client没有连到这个节点上,也不会给client发送失败的通知.在检测到slaver挂掉的期间publish消息会有延迟.如果配置了高可用策略是自动同步,当slaver起来后,队列中有大量的消息需要同步,将会整个集群阻塞长时间的不能读写直到同步结束.

    这两个挂掉的情况都需要客户端镜像容错,比如在连接断开的时候进行重连(官方的Java和.net 客户端提供了callback方法在监听到链接失败的时候调用.Java在Connection和channel类中提供了ShutdownListener 的callback方法,.net client在IConnecton中提供了ConnectionShuedown在Imodel中提供了ImodelShutdown事件供调用).也可以在client和server之间加入LoadBalancer.比如haproxy做负载均衡

    指定mirror策略:
    有三种策略:
    * all
    队列将mirrored到所有集群中的节点中,当新节点添加进来时也会mirrored到新的节点
    * exactly(需指定count)
    如果节点数小于count数,则队列将mirrored到所有的节点.如果节点数大于count,新的节点将不再创建队列的mirror(即使原来已创建mirror的节点挂掉也不会创建)
    * nodes
    对指定的节点进行mirror.如果没有一个指定的节点在运行中,那么只有client连接的那个节点才会声明queue(这里有个迁移策略:假如queue是在[A,B]上且A为master,若给定的新的策略为nodes[C,D],那么为了防止数据丢失,在迁移中会同时存在[A,C,D]直到C,D已经同步好以后,A才会关闭)

    配置举例:
    设置queue的名称为ha.的为高可用:
    linux:rabbitmqctl set_policy ha-all "^ha\." '{"ha-mode":"all"}'
    win:rabbitmqctl set_policy ha-all "^ha\." "{""ha-mode"":""all""}"
    http api:PUT /api/policies/%2f/ha-all {"pattern":"^ha\.", "definition":{"ha-mode":"all"}}
    web ui:
    1:Navigate to Admin > Policies > Add / update a policy.
    2:Enter "ha-all" next to Name, "^ha\." next to Pattern, and "ha-mode" = "all" in the first line next to Policy.
    3:Click Add policy.

    举例2:
    rabbitmqctl set_policy ha-two "^two\." \
    '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

    自动或手动同步:
    你可以查看哪些slave已经同步好了:
    rabbitmqctl list_queues name slave_pids synchronised_slave_pids

    你可以手动同步(默认手动同步):
    rabbitmqctl sync_queue name

    你可以取消自动同步:
    rabbitmqctl cancel_sync_queue name
    一个没有同步的mirror,它仍然会同步后续插入队列的数据,但是队列前面的数据却没有。但是随着队列的不断消费,导致空缺的部分的消息被消费掉了,此时mirror也可以是同步了的。
  3. Rabbit集群方案一: 主备模式

    1
    2
    主备模式:
    主备方式(active,passive)只有一个节点处于服务状态,可以结合pacemaker和ARBD,shovel简单从一个broker的一个队列中消费消息,且转发该消息到另一个broker的交换机.

SSL certificate

SSL 证书相关信息

  1. SSL专用名词

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    * HTTPS
    HTTPS=HTTP+SSL,是 HTTP 基于 SSL 协议的网站加密传输协议,网站安装 SSL 证书后,使用 HTTPS 加密协议访问,可激活客户端浏览器到网站服务器之间的"SSL 加密通道"(SSL 协议),实现高强度双向加密传输,以保护传输数据的隐私性与完整性

    * SSL
    SSL(Secure Sockets Layer)即安全套接层,利用数据加密(Encryption)技术,可确保数据在网络上传输过程中不会被截取.SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持

    * SSL 证书
    SSL 证书就是遵守 SSL 协议的服务器数字证书,由受信任的数字证书颁发机构CA颁发,具有服务器身份验证和数据传输加密等功能

    * CA
    数字证书授权机构(CA,Certificate Authority)是负责发放和管理数字证书的权威机构

    * RSA
    RSA 公钥加密算法是 1977 年由 Ron Rivest、Adi Shamir、Leonard Adleman 一起提出的,它是第一个能同时用于加密和数字签名的算法,从提出到现今经历了各种攻击的考验,能够抵抗到目前为止已知的绝大多数密码攻击,已被 ISO 推荐为公钥数据加密标准

    * ECC
    ECC(Elliptic Curves Cryptography,椭圆曲线加密算法)也是一种公钥加密算法,与主流的 RSA 算法相比,ECC 算法可以使用较短的密钥达到相同的安全程度,其安全性更高、处理速度更快
  2. SSL域名证书类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    * DV
    信任等级一般只需验证网站的真实性便可颁发证书保护网站 普通加锁标记(一般免费)
    适用于个人网站

    * OV
    信任等级强须要验证企业的身份,审核严格,安全性更高 普通加锁标记(收费)
    适用于企业官网,一般网店,电商

    * EV
    信任等级最高须要验证企业的身份,审核严格,安全性最高 绿色安全地址栏(收费)
    适用于金融行业

    补充:
    标准域名: www.qiniu.com;qiniu.com;abc.qiniu.com
    泛域名: *.qiniu.com;.abc.qiniu.com
  3. SSL域名证书验证方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    * 邮箱验证
    系统会向你选择的管理员邮箱 发送验证邮件,能够收到验证邮件,并点击邮件中验证链接 即可完成验证
    域名管理员邮箱须符合以下任意规则:
    1.域名 whois 管理联系人邮箱
    2.域名 whois 技术联系人邮箱
    3.默认管理员前缀的邮箱:
    admin@domain.com
    administrator@domain.com
    hostmaster@domain.com
    webmaster@domain.com
    postmaster@domain.com

    * DNS验证
    通过解析指定的DNS记录验证域名所有权
    例如: sr5jtl17ng5dfzjvx2lg9fxm90dygdps.domain.com ---CName--> s20150112110645.domain.com
    系统会定时检查 sr5jtl17ng5dfzjvx2lg9fxm90dygdps.domain.com 的CName纪录,若能检测到 并且与指定的值匹配 即可完成域名所有权验证

    * 文件验证
    通过在域名根目录下创建指定的文件验证域名所有权
    例如:创建文件 irmvo302.htm 文件内容 NtdutxbskfhWvP3OffXW
    系统会定时尝试访问 http://xxx.domain/irmvo302.htm 这个文件,若能访问到 并且内容匹配即可完成域名所有权验证

smokeping install

smokeping install

  1. smokeping 简介

    1
    Smokeping 是一款用于网络性能监测的监控软件,通过它可以检测IDC的网络状况(如:延时,丢包率,是否BGP多线等),通过rrdtool制图方式,图形化地展示网络的时延情况,进而能够清楚的判断出网络的即时通信情况.
  2. smokeping install

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    //基础环境安装
    [root@localhost ~]# yum -y install epel-release
    [root@localhost ~]# yum -y install gcc gcc-c++ make cmake bison libtool autoconf automake zip unzip bzip2 zlib zlib-devel openssl openssl-devel pcre pcre-devel bison-devel ncurses-devel tcl tcl-devel perl-Digest-SHA1 GeoIP GeoIP-devel gperftools gperftools-devel libatomic_ops-devel gtest gtest-devel glibc-devel unixODBC-devel fop libperl libpython readline readline-devel python2-pip readline readline-devel readline-static openssl openssl-devel openssl-static sqlite-devel bzip2-devel bzip2-libs openldap-devel
    [root@localhost ~]# yum -y install git lftp ntpdate vim wget telnet dstat tree lrzsz net-tools nmap-ncat nmap sysstat

    //perl版本
    [root@localhost ~]# perl -v|head -2|tail -1
    This is perl 5, version 16, subversion 3 (v5.16.3) built for x86_64-linux-thread-multi

    //curl版本
    [root@zabbix-server-company ~]# curl -V
    curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.15.4 zlib/1.2.7 libidn/1.28 libssh2/1.4.3

    //ssh版本
    [root@localhost ~]# ssh -V
    OpenSSH_6.4p1, OpenSSL 1.0.1e-fips 11 Feb 2013

    //fping安装
    [root@localhost ~]# yum -y install fping

    //echoping安装
    [root@localhost app]# yum -y install popt popt-devel libidn libidn-devel
    [root@localhost app]# wget https://fossies.org/linux/misc/old/echoping-6.0.2.tar.gz
    [root@localhost app]# tar xzf echoping-6.0.2.tar.gz
    [root@localhost app]# cd echoping-6.0.2
    [root@localhost echoping-6.0.2]# ./configure --prefix=/mnt/app/echoping
    [root@localhost echoping-6.0.2]# make
    [root@localhost echoping-6.0.2]# make install
    [root@localhost echoping-6.0.2]# echo "ECHOPING_HOME=/mnt/app/echoping" |tee /etc/profile.d/echoping.sh
    [root@localhost echoping-6.0.2]# echo "ECHOPING_BIN=\${ECHOPING_HOME}/bin" |tee -a /etc/profile.d/echoping.sh
    [root@localhost echoping-6.0.2]# echo "export PATH=\${ECHOPING_BIN}:\$PATH" |tee -a /etc/profile.d/echoping.sh
    [root@localhost echoping-6.0.2]# source /etc/profile.d/echoping.sh

    //dig安装
    [root@localhost ~]# yum install bind-utils //dig命令

    //rrdtool安装
    [root@localhost ~]# yum -y install rrdtool rrdtool-perl

    //httpd安装
    [root@localhost ~]# yum -y install httpd

    //smokeping安装
    [root@localhost ~]# yum -y install smokeping
  3. smokeping config

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    [root@localhost ~]# useradd -s /sbin/nologin smokeping
    [root@localhost ~]# vim /etc/smokeping/config
    *** General ***
    owner = {姓名}
    contact = {邮箱}
    mailhost = {邮箱服务器地址}
    sendmail = /usr/sbin/sendmail
    imgcache = /mnt/data/smokeping/images
    imgurl = /smokeping/images
    datadir = /mnt/data/smokeping/rrd
    piddir = /var/run/smokeping
    cgiurl = http://{192.168.13.202}/smokeping/sm.cgi
    smokemail = /etc/smokeping/smokemail
    tmail = /etc/smokeping/tmail
    syslogfacility = local0
    *** Alerts ***
    to = {接收邮箱地址}
    from = {发送邮箱地址}
    +someloss
    type = loss
    pattern = >0%,*12*,>0%,*12*,>0%
    comment = loss 3 times in a row
    *** Database ***
    step = 60
    pings = 20
    AVERAGE 0.5 1 1008
    AVERAGE 0.5 12 4320
    MIN 0.5 12 4320
    MAX 0.5 12 4320
    AVERAGE 0.5 144 720
    MAX 0.5 144 720
    MIN 0.5 144 720
    *** Presentation ***
    template = /etc/smokeping/basepage.html
    charset = utf-8
    + charts
    menu = Charts
    title = The most interesting destinations
    ++ stddev
    sorter = StdDev(entries=>4)
    title = Top Standard Deviation
    menu = Std Deviation
    format = Standard Deviation %f
    ++ max
    sorter = Max(entries=>5)
    title = Top Max Roundtrip Time
    menu = by Max
    format = Max Roundtrip Time %f seconds
    ++ loss
    sorter = Loss(entries=>5)
    title = Top Packet Loss
    menu = Loss
    format = Packets Lost %f
    ++ median
    sorter = Median(entries=>5)
    title = Top Median Roundtrip Time
    menu = by Median
    format = Median RTT %f seconds
    + overview
    width = 600
    height = 50
    range = 10h
    + detail
    width = 600
    height = 200
    unison_tolerance = 2
    "Last 3 Hours" 3h
    "Last 30 Hours" 30h
    "Last 10 Days" 10d
    "Last 400 Days" 400d
    *** Probes ***
    + FPing
    binary = /usr/sbin/fping
    *** Slaves ***
    secrets=/etc/smokeping/smokeping_secrets
    +boomer
    display_name=boomer
    color=0000ff
    +slave2
    display_name=another
    color=00ff00
    *** Targets ***
    probe = FPing
    menu = Top
    title = Network Latency Grapher
    remark = Welcome to the SmokePing website of <b>众荟</b>. \
    Here you will learn all about the latency of our network.
    + Ping
    menu = {XX机房}
    title = {}监控统计}
    ++ AliCloud
    menu = 阿里云网络监控
    title = 阿里云网络监控列表
    host = /Ping/AliCloud/{xx1} /Ping/AliCloud/{xx2}
    +++ {xx1}
    menu = {xx1}
    title = {xx1}
    alerts = someloss
    host = {xx1 服务器地址}
    +++ {xx2}
    menu = {xx2}
    title = {xx2}
    alerts = someloss
    host = {xx2 服务器地址}
    [root@localhost ~]# mkdir -p /mnt/data/smokeping/{images,rrd}
    [root@localhost ~]# chown -R apache /mnt/data/smokeping/images
  4. smokeping 中文显示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [root@localhost ~]# yum -y install wqy-zenhei-fonts
    [root@localhost ~]# vim /etc/smokeping/config
    *** Presentation ***
    charset = utf-8
    [root@localhost ~]# vim /usr/share/smokeping/Smokeping/Graphs.pm
    在第147行下边插入下边一行:
    '--font TITLE:20:"WenQuanYi Zen Hei Mono"',
    例如:
    my ($graphret,$xs,$ys) = RRDs::graph
    ("dummy",
    '--start', $tasks[0][1],
    '--end', $tasks[0][2],
    '--font TITLE:20:"WenQuanYi Zen Hei Mono"',
    "DEF:maxping=$cfg->{General}{datadir}${host}.rrd:median:AVERAGE",
    'PRINT:maxping:MAX:%le' );
  5. httpd配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    [root@zabbix-server-company ~]# cat /etc/httpd/conf.d/smokeping.conf
    <Directory "/usr/share/smokeping" >
    Require local
    Require ip 0.0.0.0
    # Require host example.org
    Options Indexes FollowSymLinks
    Options +ExecCGI
    AllowOverride None
    DirectoryIndex smokeping.fcgi
    Require all granted
    </Directory>

    <Directory "/mnt/data/smokeping" >
    Require local
    Require ip 0.0.0.0
    # Require host example.org
    AllowOverride None
    Require all granted
    </Directory>

    # .fcgi : smokeping by mod_fcgid aka fastcgi
    # _cgi : plain old fashion cgi
    ScriptAlias /smokeping/sm.cgi /usr/share/smokeping/cgi/smokeping.fcgi
    #ScriptAlias /smokeping/sm.cgi /usr/share/smokeping/cgi/smokeping_cgi

    Alias /smokeping/images /mnt/data/smokeping/images
    Alias /smokeping /usr/share/smokeping/htdocs


    注意:
    1.httpd 版本是2.4
    2.每次更新完smokeping的配置文件conf后,需要重启httpd服务
  6. 做软连接

    1
    [root@localhost ~]# ln -s /usr/share/smokeping/cgi/smokeping_cgi /usr/share/smokeping/htdocs/smokeping.fcgi
  7. 启动服务

    1
    2
    [root@localhost ~]# systemctl start smokeping
    [root@localhost ~]# systemctl restart httpd

zabbix optimization

zabbix 优化

  1. zabbix Poller 调整

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    * Option: StartPollers              处理监控项
    * Option: StartIPMIPollers 处理IPMI,暂时未用,设置为0
    * Option: StartPollersUnreachable 获取数据遇到不可到达时,交给这些进程处理
    * Option: StartHTTPPollers 监控HTTP,WEB,设置为10
    * Option: StartJavaPollers 监控JAVA专用进程,设置为10
    * Option: StartProxyPollers 处理代理的进程,设置为30
    * Option: StartDiscoverers 处理自动发现的,设置为30
    * Option: StartPingers 如果用了ICMP PING那个模板的,这个值要调大一些

    注意:
    StartPollers,然配置文件写是可用范围是0-1000,但这个1000是所有的进程的值,也就是说上面那些进程的总数不能超过1000,设的时候要注意一下这点
  2. 关于Cache的调整

    1
    2
    3
    4
    5
    6
    7
    8
    * Option: CacheSize               zabbix初始化时占用多少系统共享内存用于存储配置信息(HOST,ITEM,TRIGGER数据,视监控主机数量和监控项调整)
    * Option: CacheUpdateFrequency zabbix更新操作系统CACHE频率,若管理页面操作不频繁,可以考虑加大参数值
    * Option: HistoryCacheSize 用于设置划分多少系统共享内存用于存储采集的历史数据,此数值越大,数据库读压力越小
    * Option: TrendCacheSize 用于设置划分多少系统共享内存用于存储计算出来的趋势数据,此参数值从一定程度上可影响数据库读压力
    * Option: HistoryIndexCacheSize 历史数据索引缓存
    * Option: ValueCacheSize 划出系统多少共享内存用于已请求的存储监控项信息,若监控项较多,建议加大此数值

    注意: 关于内存的参数有这么多,都是要根据机器数量和item数量的增加而增加,这些内存的值不能大于系统内核的kernel.shmall这个值,否则申请不了内存程序启动不了
  3. 内核共享内存优化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [root@zabbix-server-mysql ~]# echo kernel.shmmax=15461882265 | tee -a /etc/sysctl.conf
    [root@zabbix-server-mysql ~]# echo kernel.shmall=3774873 | tee -a /etc/sysctl.conf
    [root@zabbix-server-mysql ~]# echo kernel.msgmax=65535 | tee -a /etc/sysctl.conf
    [root@zabbix-server-mysql ~]# echo kernel.msgmnb=65535 | tee -a /etc/sysctl.conf

    说明:
    shmall是全部允许使用的共享内存大小,shmmax是单个段允许使用的大小.这两个可以设置为内存的90%
    例如:16G内存,shmmax的大小为 16*1024*1024*1024*90% = 15461882265,shmall的大小为 15461882265/4k(getconf PAGESIZE可得到) = 3774873

    msgmax该文件指定了从一个进程发送到另一个进程的消息的最大长度(bytes).进程间的消息传递是在内核的内存中进行的,不会交换到磁盘上,所以如果增加该值,则将增加操作系统所使用的内存数量
    msgmnb该文件指定一个消息队列的最大长度(bytes)
  4. 数据库配置参数优化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    [root@zabbix-server-mysql ~]# cat /mnt/app/mysql/conf/3306.M.cnf
    [mysqld]
    user = mysql
    port = 3306
    basedir = /mnt/app/mysql
    datadir = /mnt/data/mysql/3306
    socket = /mnt/data/mysql/3306/mysql.sock
    default-storage-engine = InnoDB
    character_set_server = utf8
    max_connections = 5000
    sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

    server-id = 210
    max_allowed_packet = 32m
    max_heap_table_size = 128m
    read_rnd_buffer_size = 8m
    sort_buffer_size = 4m
    join_buffer_size = 4m
    query_cache_type = 1
    query_cache_size = 512m
    query_cache_limit = 4m
    innodb_open_files = 3000
    innodb_buffer_pool_size = 12G
    innodb_file_per_table = 1

    [mysqld_safe]
    log-error=/mnt/log/mysql/3306/mysqld.log
    pid-file =/mnt/app/mysql/mysqld.3306.pid

    [client]
    default-character-set=utf8
    socket=/mnt/data/mysql/3306/mysql.sock

    [mysql]
    default-character-set=utf8
    socket=/mnt/data/mysql/3306/mysql.sock

    [root@zabbix-server-mysql ~]# /etc/init.d/mysql.3306M restart
  5. zabbix mysql 表分区优化

    1
    2
    3
    4
    5
    机器数量多的时候,mysql里面的history表就会越来越大,虽然zabbix本身有删除功能(就是那个housekeeper的功能),但这东西太影响性能,所以网上的做法都是关闭这个东西,用mysql的表分区功能来实现清理历史数据还可以提升mysql的性能

    补充: zabbix 3.2 版本数据库不需要重新执行创建索引过程;2.0,3.0版本都需要
    mysql> Alter table history_text drop primary key, add index (id), drop index history_text_2, add index history_text_2 (itemid, id);
    mysql> Alter table history_log drop primary key, add index (id), drop index history_log_2, add index history_log_2 (itemid, id);
  6. 创建存储过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    [root@zabbix-server-mysql data]# vim partition_call.sql
    DELIMITER $$
    CREATE PROCEDURE `partition_create`(SCHEMANAME varchar(64), TABLENAME varchar(64), PARTITIONNAME varchar(64), CLOCK int)
    BEGIN
    /*
    SCHEMANAME = The DB schema in which to make changes
    TABLENAME = The table with partitions to potentially delete
    PARTITIONNAME = The name of the partition to create
    */
    /*
    Verify that the partition does not already exist
    */

    DECLARE RETROWS INT;
    SELECT COUNT(1) INTO RETROWS
    FROM information_schema.partitions
    WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND partition_description >= CLOCK;

    IF RETROWS = 0 THEN
    /*
    1. Print a message indicating that a partition was created.
    2. Create the SQL to create the partition.
    3. Execute the SQL from #2.
    */
    SELECT CONCAT( "partition_create(", SCHEMANAME, ",", TABLENAME, ",", PARTITIONNAME, ",", CLOCK, ")" ) AS msg;
    SET @sql = CONCAT( 'ALTER TABLE ', SCHEMANAME, '.', TABLENAME, ' ADD PARTITION (PARTITION ', PARTITIONNAME, ' VALUES LESS THAN (', CLOCK, '));' );
    PREPARE STMT FROM @sql;
    EXECUTE STMT;
    DEALLOCATE PREPARE STMT;
    END IF;
    END$$
    DELIMITER ;

    DELIMITER $$
    CREATE PROCEDURE `partition_drop`(SCHEMANAME VARCHAR(64), TABLENAME VARCHAR(64), DELETE_BELOW_PARTITION_DATE BIGINT)
    BEGIN
    /*
    SCHEMANAME = The DB schema in which to make changes
    TABLENAME = The table with partitions to potentially delete
    DELETE_BELOW_PARTITION_DATE = Delete any partitions with names that are dates older than this one (yyyy-mm-dd)
    */
    DECLARE done INT DEFAULT FALSE;
    DECLARE drop_part_name VARCHAR(16);

    /*
    Get a list of all the partitions that are older than the date
    in DELETE_BELOW_PARTITION_DATE. All partitions are prefixed with
    a "p", so use SUBSTRING TO get rid of that character.
    */
    DECLARE myCursor CURSOR FOR
    SELECT partition_name
    FROM information_schema.partitions
    WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND CAST(SUBSTRING(partition_name FROM 2) AS UNSIGNED) < DELETE_BELOW_PARTITION_DATE;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    /*
    Create the basics for when we need to drop the partition. Also, create
    @drop_partitions to hold a comma-delimited list of all partitions that
    should be deleted.
    */
    SET @alter_header = CONCAT("ALTER TABLE ", SCHEMANAME, ".", TABLENAME, " DROP PARTITION ");
    SET @drop_partitions = "";

    /*
    Start looping through all the partitions that are too old.
    */
    OPEN myCursor;
    read_loop: LOOP
    FETCH myCursor INTO drop_part_name;
    IF done THEN
    LEAVE read_loop;
    END IF;
    SET @drop_partitions = IF(@drop_partitions = "", drop_part_name, CONCAT(@drop_partitions, ",", drop_part_name));
    END LOOP;
    IF @drop_partitions != "" THEN
    /*
    1. Build the SQL to drop all the necessary partitions.
    2. Run the SQL to drop the partitions.
    3. Print out the table partitions that were deleted.
    */
    SET @full_sql = CONCAT(@alter_header, @drop_partitions, ";");
    PREPARE STMT FROM @full_sql;
    EXECUTE STMT;
    DEALLOCATE PREPARE STMT;

    SELECT CONCAT(SCHEMANAME, ".", TABLENAME) AS `table`, @drop_partitions AS `partitions_deleted`;
    ELSE
    /*
    No partitions are being deleted, so print out "N/A" (Not applicable) to indicate
    that no changes were made.
    */
    SELECT CONCAT(SCHEMANAME, ".", TABLENAME) AS `table`, "N/A" AS `partitions_deleted`;
    END IF;
    END$$
    DELIMITER ;

    DELIMITER $$
    CREATE PROCEDURE `partition_maintenance`(SCHEMA_NAME VARCHAR(32), TABLE_NAME VARCHAR(32), KEEP_DATA_DAYS INT, HOURLY_INTERVAL INT, CREATE_NEXT_INTERVALS INT)
    BEGIN
    DECLARE OLDER_THAN_PARTITION_DATE VARCHAR(16);
    DECLARE PARTITION_NAME VARCHAR(16);
    DECLARE OLD_PARTITION_NAME VARCHAR(16);
    DECLARE LESS_THAN_TIMESTAMP INT;
    DECLARE CUR_TIME INT;

    CALL partition_verify(SCHEMA_NAME, TABLE_NAME, HOURLY_INTERVAL);
    SET CUR_TIME = UNIX_TIMESTAMP(DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00'));

    SET @__interval = 1;
    create_loop: LOOP
    IF @__interval > CREATE_NEXT_INTERVALS THEN
    LEAVE create_loop;
    END IF;

    SET LESS_THAN_TIMESTAMP = CUR_TIME + (HOURLY_INTERVAL * @__interval * 3600);
    SET PARTITION_NAME = FROM_UNIXTIME(CUR_TIME + HOURLY_INTERVAL * (@__interval - 1) * 3600, 'p%Y%m%d%H00');
    IF(PARTITION_NAME != OLD_PARTITION_NAME) THEN
    CALL partition_create(SCHEMA_NAME, TABLE_NAME, PARTITION_NAME, LESS_THAN_TIMESTAMP);
    END IF;
    SET @__interval=@__interval+1;
    SET OLD_PARTITION_NAME = PARTITION_NAME;
    END LOOP;

    SET OLDER_THAN_PARTITION_DATE=DATE_FORMAT(DATE_SUB(NOW(), INTERVAL KEEP_DATA_DAYS DAY), '%Y%m%d0000');
    CALL partition_drop(SCHEMA_NAME, TABLE_NAME, OLDER_THAN_PARTITION_DATE);

    END$$
    DELIMITER ;

    DELIMITER $$
    CREATE PROCEDURE `partition_verify`(SCHEMANAME VARCHAR(64), TABLENAME VARCHAR(64), HOURLYINTERVAL INT(11))
    BEGIN
    DECLARE PARTITION_NAME VARCHAR(16);
    DECLARE RETROWS INT(11);
    DECLARE FUTURE_TIMESTAMP TIMESTAMP;

    /*
    * Check if any partitions exist for the given SCHEMANAME.TABLENAME.
    */
    SELECT COUNT(1) INTO RETROWS
    FROM information_schema.partitions
    WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND partition_name IS NULL;

    /*
    * If partitions do not exist, go ahead and partition the table
    */
    IF RETROWS = 1 THEN
    /*
    * Take the current date at 00:00:00 and add HOURLYINTERVAL to it. This is the timestamp below which we will store values.
    * We begin partitioning based on the beginning of a day. This is because we don't want to generate a random partition
    * that won't necessarily fall in line with the desired partition naming (ie: if the hour interval is 24 hours, we could
    * end up creating a partition now named "p201403270600" when all other partitions will be like "p201403280000").
    */
    SET FUTURE_TIMESTAMP = TIMESTAMPADD(HOUR, HOURLYINTERVAL, CONCAT(CURDATE(), " ", '00:00:00'));
    SET PARTITION_NAME = DATE_FORMAT(CURDATE(), 'p%Y%m%d%H00');

    -- Create the partitioning query
    SET @__PARTITION_SQL = CONCAT("ALTER TABLE ", SCHEMANAME, ".", TABLENAME, " PARTITION BY RANGE(`clock`)");
    SET @__PARTITION_SQL = CONCAT(@__PARTITION_SQL, "(PARTITION ", PARTITION_NAME, " VALUES LESS THAN (", UNIX_TIMESTAMP(FUTURE_TIMESTAMP), "));");

    -- Run the partitioning query
    PREPARE STMT FROM @__PARTITION_SQL;
    EXECUTE STMT;
    DEALLOCATE PREPARE STMT;
    END IF;
    END$$
    DELIMITER ;


    [root@zabbix-server-mysql data]# mysql -S /mnt/data/mysql/3306/mysql.sock zabbix < partition_call.sql
  7. 创建调用存储过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [root@zabbix-server-mysql data]# vim partition_call_call.sql
    DELIMITER $$
    CREATE PROCEDURE `partition_maintenance_all`(SCHEMA_NAME VARCHAR(32))
    BEGIN
    CALL partition_maintenance(SCHEMA_NAME, 'history', 30, 24, 14);
    CALL partition_maintenance(SCHEMA_NAME, 'history_log', 30, 24, 14);
    CALL partition_maintenance(SCHEMA_NAME, 'history_str', 30, 24, 14);
    CALL partition_maintenance(SCHEMA_NAME, 'history_text', 30, 24, 14);
    CALL partition_maintenance(SCHEMA_NAME, 'history_uint', 30, 24, 14);
    CALL partition_maintenance(SCHEMA_NAME, 'trends', 730, 24, 14);
    CALL partition_maintenance(SCHEMA_NAME, 'trends_uint', 730, 24, 14);
    END$$
    DELIMITER ;

    说明:
    mysql> CALL partition_maintenance('<zabbix_db_name>', '<table_name>', <days_to_keep_data>, <hourly_interval>, <num_future_intervals_to_create>)
    说明:
    zabbix_db_name 库名
    table_name 表名
    days_to_keep_data 保存多少天的数据
    hourly_interval 每隔多久生成一个分区
    num_future_intervals_to_create 本次一共生成多少个分区
  8. 执行调用存储过程

    1
    [root@zabbix-server-mysql data]# mysql -S /mnt/data/mysql/3306/mysql.sock zabbix -e "CALL partition_maintenance_all('zabbix');"
  9. 将”执行调用存储过程”命令,加入计划任务

    1
    2
    [root@zabbix-server-mysql data]# crontab -l
    5 1 * * * /mnt/app/mysql/bin/mysql -S /mnt/data/mysql/3306/mysql.sock zabbix -e "CALL partition_maintenance_all('zabbix');"
  10. 关掉Zabbix的HouseKeeper功能

    1
    administration -> general -> HouseKeeping -> "取消勾选框,保存"
  11. 解决表依赖关系

    1
    2
    3
    mysql> SET foreign_key_checks=0;
    mysql> truncate table events;
    mysql> SET foreign_key_checks=1;

angular install

angular install

  1. nodejs install

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@dev app]# xz -d node-v8.1.3-linux-x64.tar.xz
    [root@dev app]# tar xf node-v8.1.3-linux-x64.tar
    [root@dev app]# mv node-v8.1.3-linux-x64 /mnt/app/node
    [root@dev app]# chown -R root.root /mnt/app/node

    [root@dev app]# echo 'NODE_HOME=/mnt/app/node' |tee /etc/profile.d/node.sh
    [root@dev app]# echo 'NODE_PATH=${NODE_HOME}/lib/node_modules' |tee -a /etc/profile.d/node.sh
    [root@dev app]# echo 'export PATH=${PATH}:${NODE_HOME}/bin' |tee -a /etc/profile.d/node.sh
    [root@dev app]# source /etc/profile

    [root@dev app]# node -v
    v8.1.3
  2. 修改npm源

    1
    2
    3
    [root@dev app]# echo 'registry = https://registry.npm.taobao.org' > ~/.npmrc
    [root@dev app]# npm -v
    5.0.3
  3. angular install

    1
    [root@dev app]# npm install -g @angular/cli
  4. angular 创建新项目

    1
    [root@dev ~]# ng new my-app
  5. angular 启动开发服务

    1
    2
    3
    4
    [root@dev ~]# cd my-app/
    [root@dev my-app]# ng server --open --host 192.168.13.208 --port 4000

    浏览器中输入:http://192.168.13.208:4000/

sass install

sass install

  1. ruby install

    1
    2
    3
    4
    5
    6
    7
    8
    [root@dev ~]# gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
    [root@dev ~]# curl -sSL https://get.rvm.io | bash -s stable
    [root@dev ~]# source /etc/profile.d/rvm.sh
    [root@dev ~]# sed -i 's!ftp.ruby-lang.org/pub/ruby!cache.ruby-china.org/pub/ruby!g' $rvm_path/config/db

    [root@dev ~]# rvm install 2.4
    [root@dev ~]# rvm --default use 2.4
    [root@dev ~]# gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
  2. sass install

    1
    2
    3
    4
    5
    6
    7
    [root@dev ~]# gem install sass
    [root@dev ~]# gem install compass

    [root@dev ~]# sass -v
    Sass 3.4.25 (Selective Steve)
    [root@dev ~]# compass -v
    Compass 1.0.3 (Polaris)
  3. sass 编译

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@dev ~]# sass input.scss output.css  //单文件转换命令
    [root@dev ~]# sass --watch input.scss:output.css //单文件监听命令
    [root@dev ~]# sass --watch app/sass:public/stylesheets //sass监听整个目录

    [root@dev ~]# sass --watch input.scss:output.css --style compact //编译格式
    [root@dev ~]# sass --watch input.scss:output.css --sourcemap //编译添加调试map
    [root@dev ~]# sass --watch input.scss:output.css --style expanded --sourcemap //选择编译格式并添加调试map
    [root@dev ~]# sass --watch input.scss:output.css --debug-info //开启debug信息

    注意:
    1.sass内置有四种编译格式:nested``expanded``compact``compressed
    2.-sourcemap表示开启sourcemap调试.开启sourcemap调试后,会生成一个后缀名为.css.map文件

ruby install

Ruby install

  1. rvm安装

    1
    2
    3
    [root@dev ~]# gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
    [root@dev ~]# curl -sSL https://get.rvm.io | bash -s stable
    [root@dev ~]# source /etc/profile.d/rvm.sh
  2. 修改rvm源

    1
    2
    3
    [root@dev ~]# grep ftp.ruby-lang.org $rvm_path/config/db
    ruby_url_fallback_1=https://ftp.ruby-lang.org/pub/ruby
    [root@dev ~]# sed -i 's!ftp.ruby-lang.org/pub/ruby!cache.ruby-china.org/pub/ruby!g' $rvm_path/config/db
  3. Ruby install

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [root@dev ~]# rvm list known
    [root@dev ~]# rvm install 2.4
    [root@dev ~]# rvm docs generate-ri
    [root@dev ~]# rvm --default use 2.4 //指定默认版本

    [root@dev ~]# ruby -v
    ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]
    [root@dev ~]# gem -v
    2.6.12

    补充:
    [root@dev ~]# rvm list //列出所有ruby版本
    [root@dev ~]# rvm list default //列出默认版本
    [root@dev ~]# rvm reset //恢复系统默认设置
    [root@dev ~]# rvm reinstall 2.4 //重新安装
    [root@dev ~]# rvm uninstall 2.4 //卸载,但保留源数据
    [root@dev ~]# rvm remove 2.4 //卸载,删除源数据,比uninstall更彻底
  4. 修改gem源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [root@dev ~]# gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
    [root@dev ~]# gem sources -l
    https://gems.ruby-china.org/

    补充:
    [root@dev ~]# gem update --system //更新gem
    [root@dev ~]# gem update //更新所有gem包

    [root@dev ~]# gem list --local //显示本地安装的gem包

    [root@dev ~]# gem uninstall rake //卸载旧的gem包,保留最新
    [root@dev ~]# gem uninstall rake --version={version} //卸载指定gem包

    [root@dev ~]# gem cleanup //清除所有包旧版本,保留最新版本

    [root@dev ~]# gem search -r {关键字} //从远程服务器上查找包含"关键字"的gem包
    [root@dev ~]# gem search {关键字} --remoter //从远程服务器上查找包含"关键字"的gem包
    [root@dev ~]# gem search {关键字} --both //从本地和远程服务器上查找包含"关键字"的gem包

    [root@dev ~]# gem install {gem_package} //安装最新版本的gem包(本地+远程)
    [root@dev ~]# gem install {gem_package} --remoter //安装最新版本的gem包(远程)
    [root@dev ~]# gem install {gem_package} -v {version} //安装指定版本的gem包(本地+远程)

    [root@dev ~]# gem query -n ''[0-9]'' --local //查找本地含有数字的gem包

    [root@dev ~]# gem build rake.gemspec //把rake.gemspec编译成rake.gem
    [root@dev ~]# gem check -v pkg/rake-0.4.0.gem #检测rake是否有效

    [root@dev ~]# gem contents rake //显示rake包中所包含的文件
    [root@dev ~]# gem dependency rails -v 0.10.1 //列出与rails相互依赖的包
    [root@dev ~]# gem environment //查看gem的环境