本篇会把连接(CONNECT)、心跳(PINGREQ/PINGRESP)、确认(CONNACK)、断开连接(DISCONNECT)和在一起进行介绍。

在阅读本文之前推荐大家先读一下我的前两篇文章:MQTT协议及应用场景 MQTT协议之头部信息介绍

CONNECT

  MQTT v3.1.1有关字符串部分采用的修改版的UTF-8编码,CONNECT可变头部中协议名称、消息体都是采用修改版的UTF-8编码。前面基本上可变头部内容不多,下面是一个较为完整的CONNECT消息结构:

mqtt connect消息结构

协议变化
MQTT 3.1.1中CONNECT消息可变头部协议名称MQIsdp被改为MQTT。
MQTT 3.1.1中所有字符串明确规定使用UTF-8编码,包括客户端标识符(Client Identifier)。
MQTT 3.1.1中针对客户端标识符( client identifier)限制由原来的23个字节上限改为65535个字节。
MQTT 3.1.1中CONNECT消息可变头部协议版本号,由0x03变成了0x04 QoS 0类型PUBLISH消息DUP标记必须被设置为0
MQTT 3.1.1中MQTT Over WebSocket 被定义,互联网地址编码分配机构(Internet Assigned Numbers Authority)分配标识符为mqtt。虽在MQTT 3.1规范通篇没有提到WebSocket,但因其二进制属性可以很容易的在WebSocket通道传输。


术语变化

  • MQTT代理 -> MQTT服务器(MQTT Broker is now MQTT Server)
  • 消息ID -> 包ID(Message ID is now Packet ID)
  • 消息类型 -> 包类型(Message Type is now Packet Type)
  • 主题路径 -> 主题名称(Subscribe and Unsubscribe take Topic Paths, rather than Topic names)
  • 以前在固定头部,现在在包类型中( Flags in the fixed header are now specific to the packet type
  • 0字节保留信息需要清除 (A zero byte retained message MUST NOT be stored as a retained message on the Server )

可变头部

  协议名称和协议版本都是固定的。

连接标志(Connect Flags)

  一个字节表示,除了第1位是保留未使用,其它7位都具有不同含义。业务上很重要,对消息总体流程影响很大,需要牢记。

Clean Session

  0,表示如果订阅的客户机断线了,要保存为其要推送的消息(QoS为1和QoS为2),若其重新连接时,需将这些消息推送(若客户端长时间不连接,需要设置一个过期值)。 1,断线服务器即清理相关信息,重新连接上来之后,会再次订阅。

Will Flag

  定义了客户端(没有主动发送DISCONNECT消息)出现网络异常导致连接中断的情况下,服务器需要做的一些措施。
  简而言之,就是客户端预先定义好,在自己异常断开的情况下,所留下的最后遗愿(Last Will),也称之为遗嘱(Testament)。 这个遗嘱就是一个由客户端预先定义好的主题和对应消息,附加在CONNECT的可变头部中,在客户端连接出现异常的情况下,由服务器主动发布此消息。
  只有在Will Flag位为1时,Will Qos和Will Retain才会被读取,此时消息体payload中要出现Will Topic和Will Message具体内容,否则,Will QoS和Will Retain值会被忽略掉。 

Will Qos

  两位表示,和PUBLISH消息固定头部的QoS level含义一样。这里先略过,到PUBLISH消息再回过头来看看,会更明白些。若标识了Will Flag值为1,那么Will QoS就会生效,否则会被忽略掉。

Will Retain

  如果设置Will Flag,Will Retain标志就是有效的,否则它将被忽略。当客户端意外断开服务器发布其Will Message之后,服务器是否应该继续保存。这个属性和PUBLISH固定头部的RETAIN标志含义一样,这里先略过。

User name 和 password Flag

  用于授权,两者要么为0要么为1,否则都是无效。都为0,表示客户端可自由连接/订阅,都为1,表示连接/订阅需要授权。

Payload/消息体

  消息体定义的消息顺序(如上表所示),约定俗成,不得更改,否则将可能引起混乱。
  若Will Flag值为0,那么在payload中,Client Identifer后面就不会存在Will Topic和Will Message内容。
  若User Name和Password都为0,意味着Payload/消息体中,找不到User Name和password的值,就算有,也是无效。标志决定着是否读取与否。

心跳时间(Keep Alive timer)

  以秒为单位,定义服务器端从客户端接收消息的最大时间间隔。一般应用服务会在业务层次检测客户端网络是否连接,不是TCP/IP协议层面的心跳机制(比如开启SOCKET的SO_KEEPALIVE选项)。 一般来讲,在一个心跳间隔内,客户端发送一个PINGREQ消息到服务器,服务器返回PINGRESP消息,完成一次心跳交互,继而等待下一轮。若客户端没有收到心跳反馈,会关闭掉TCP/IP端口连接,离线。 16位两个字节,可看做一个无符号的short类型值。最大值,2^16-1 = 65535秒 = 18小时。最小值可以为0,表示客户端不断开。一般设为几分钟,比如微信心跳周期为300秒。

Will Message编码

  Will Message在CONNECT Payload/息体中,使用UTF-8编码。假设内容为“abcd”,大概如下:
mqtt connect will message

注:PUBLISH的Payload/消息体中以二进制编码保存

  在某一时间点客户端异常关闭会触发服务器PUBLISH此消息。那么服务器会直接把byte3-byte6之间字符取出,保存为二进制,附加到PUBLISH消息体中,大概存储如下:
mqtt publish will message

连接异常中断通知机制

  CONNECT消息一旦设置在可变头部设置了Will flag标记,那就启用了Last-Will-And-Testament特性,此特性很赞。
  一旦客户端出现异常中断,便会触发服务器发布Will Message消息到Will Topic主题上去,通知Will Topic订阅者,对方因异常退出。

CONNACK

  收到CONNECT消息之后,服务器应该返回一个CONNACK消息作为响应:

  • 若客户端绕过CONNECT消息直接发送其它类型消息,服务器应关闭此非法连接 若客户端发送CONNECT之后未收到CONNACT,需要关闭当前连接,然后重新连接
  • 相同Client ID客户端已连接到服务器,先前客户端必须断开连接后,服务器才能完成新的客户端CONNECT连接 客户端发送无效非法CONNECT消息,服务器需要关闭。
      一个完整的CONNACK消息大致如下:
    connack
      可变头部第一个字节为保留,无甚用处。第二个字节为连接握手返回码:
    connack response code
      只有0-5目前被使用到,其他值有待日后使用。一般返回值为0x00,表示连接建立。非法的请求,需要返回相应的数值。
      从上面看出,一个CONNACK,四个字节表示。一个正常的CONNACK消息实际内容可能如下:
    0x20 0x02 0x00 0x00 
      若是在私有协议中,两个字节就足够了。很多时候,客户端和服务器端在没有消息传递时,会一直保持着连接。虽然不能依靠TCP心跳机制(比如SO_KEEPALIVE选项),业务层面定义心跳机制,会让连接状态检测、控制更为直观。

PINGREQ

  由客户端发送到服务器端,证明自己还在一直连接着呢。两个字节,固定值。
PINGREQ
  客户端会在一个心跳周期内发送一条PINGREQ消息到服务器端。心跳频率在CONNECT可变头部“Keep Alive Timer”中定义时间,单位为秒,无符号16位short表示。

PINGRESP

  服务器收到PINGREQ请求之后,会立即响应一个两个字节固定格式的PINGRESP消息。

PINGRESP
  服务器一般若在1.5倍的心跳周期内接收不到客户端发送的PINGREQ,可考虑关闭客户端的连接描述符。此时的关闭连接的行为和接收到客户端发送DISCONNECT消息的处理行为一致,但对客户端的订阅不会产生影响(不会清除客户端订阅数据),这个需要牢记。
  若客户端发送PINGREQ之后的一个心跳周期内接收不到PINGRESP消息,可考虑关闭TCP/IP套接字连接。

DISCONNECT

客户端主动发送到服务器端,表明即将关闭TCP/IP连接。此时要求服务器要完整、干净的进行断开处理,不能仅仅类似于关闭连接描述符类似草草处理之。 需要两个字节,值固定。
DISCONNECT
  服务器要根据先前此客户端在发送CONNECT消息可变头部Connect flag中的“Clean session flag”所设置值。
复习一下:

  1. 值为0,服务器必须在客户端断开之后继续存储/保持客户端的订阅状态。这些状态包括:
    • 存储订阅的消息QoS1和QoS2消息
    • 正在发送消息期间连接丢失导致发送失败的消息。
    • 以便当客户端重新连接时以上消息可以被重新传递。
  • 值为1,服务器需要立刻清理连接状态数据。

注意:服务器在接收到客户端发送的DISCONNECT消息之后,需要主动关闭TCP/IP连接。

前言

  MQTT(Message Queue Telemetry Transport),遥测传输协议,提供订阅/发布模式,更为简约、轻量,易于使用,针对受限环境(带宽低、网络延迟高、网络通信不稳定),可以简单概括为物联网打造,官方总结特点如下:

1
2
3
4
5
6
7
8
9
1.使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
2. 对负载内容屏蔽的消息传输。
3. 使用 TCP/IP 提供网络连接。
4. 有三种消息发布服务质量:
“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
“至少一次”,确保消息到达,但消息重复可能会发生。
“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
5. 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量。
6. 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。

  在沉寂了四年之后, MQTT 3.1.1规范 于2014年10月30号正式发布,与此同时MQTT 3.1.1已成为OASIS(结构化信息标准促进组织)开放物联网消息传递协议标准( 连接1 连接2 ),换种说法就是MQTT 3.1.1已升级为国际物联网标准。
  细心的朋友一定会发现,原来MQTT 3.1规范是IBM的协议,到了MQTT 3.1.1规范 后就变成了OASIS的标准。
  中文版的文档查看以下的链接:
  MQTT协议v3.1中文版
  MQTT协议v3.1.1中文版

协议介绍

固定头部

  固定头部,使用两个字节,共16位:

mqtt 固定头部
Byte 1 消息类型和标志字段,消息类型(4-7),使用4位二进制表示,可代表16种消息类型。
Byte 2 剩余长度字段(至少1个字节,最多4个字节),采用big-endian模式存储。

消息类型

mqtt 消息类型

除去0和15位置属于保留待用,共14种消息事件类型。

DUP flag(打开标志)

保证消息可靠传输,默认为0,只占用一个字节,表示第一次发送。不能用于检测消息重复发送等。只适用于客户端或服务器端尝试重发PUBLISH, PUBREL, SUBSCRIBE 或 UNSUBSCRIBE消息,注意需要满足以下条件:

1
2
当 QoS > 0
消息需要回复确认

此时,在可变头部需要包含消息ID。当值为1时,表示当前消息先前已经被传送过。

QoS(Quality of Service,服务质量)

  使用两个二进制表示PUBLISH类型消息:

mqtt 服务质量

RETAIN(保持)

  仅针对PUBLISH消息。不同值,不同含义:
  1:表示发送的消息需要一直持久保存(不受服务器重启影响),不但要发送给当前的订阅者,并且以后新来的订阅了此Topic name的订阅者会马上得到推送。

备注:新来乍到的订阅者,只会取出最新的一个RETAIN flag = 1的消息推送。

  0:仅仅为当前订阅者推送此消息。
假如服务器收到一个空消息体(zero-length payload)、RETAIN = 1、已存在Topic name的PUBLISH消息,服务器可以删除掉对应的已被持久化的PUBLISH消息。

Remaining Length(剩余长度)

  在当前消息中剩余的byte(字节)数,包含可变头部和负荷(称之为内容/body,更为合适)。单个字节最大值:01111111,16进制:0x7F,10进制为127。单个字节为什么不能是11111111(0xFF)呢?因为MQTT协议规定,第八位(最高位)若为1,则表示还有后续字节存在。
  同时MQTT协议最多允许4个字节表示剩余长度。那么最大长度为:0xFF,0xFF,0xFF,0x7F。
二进制表示为:11111111,11111111,11111111,01111111
十进制:268435455 byte=261120KB=256MB=0.25GB
四个字节之间值的范围:

mqtt 剩余长度

可变头部

  固定头部仅定义了消息类型和一些标志位,一些消息的元数据,需要放入可变头部中。可变头部内容字节长度 + Playload/负荷字节长度 = 剩余长度,这个是需要牢记的。可变头部,包含了协议名称,版本号,连接标志,用户授权,心跳时间等内容,这部分和后面要讲到的CONNECT消息类型,有重复,暂时略过。

Playload/消息体/负荷

  消息体主要是为配合固定/可变头部命令(比如CONNECT可变头部User name标记若为1则需要在消息体中附加用户名称字符串)而存在。
  CONNECT/SUBSCRIBE/SUBACK/PUBLISH等消息有消息体。PUBLISH的消息体以二进制形式对待。
  请记住MQTT协议只允许在PUBLISH类型消息体中使用自定义特性,在固定/可变头部想加入自定义私有特性,就免了吧。这也是为了协议免于流于形式,变得很分裂也为了兼顾现有客户端等。比如支持压缩等,那就可以在Playload中定义数据支持,在应用中进行读取处理。这部分会在后面的文章中详细论述。

消息标识符/消息ID

  固定头中的QoS level标志值为1或2时才会在:PUBLISH,PUBACK,PUBREC,PUBREL,PUBCOMP,SUBSCRIBE,SUBACK,UNSUBSCRIBE,UNSUBACK等消息的可变头中出现。
  一个16位无符号位的short类型值(值不能为 0,0做保留作为无效的消息ID),仅仅要求在一个特定方向(服务器发往客户端为一个方向,客户端发送到服务器端为另一个方向)的通信消息中必须唯一。比如客户端发往服务器,有可能存在服务器发往客户端会同时存在重复,但不碍事。
  可变头部中,需要两个字节的顺序是MSB(Most Significant Bit) LSB(Last/Least Significant Bit),翻译成中文就是,最高有效位,最低有效位。最高有效位在最低有效位左边/上面,表示这是一个大端字节/网络字节序,符合人的阅读习惯,高位在最左边。

mqtt 消息标识符

小结

  掌握固定头部的QoS level、RETAIN标记、可变头部的Connect flags作用和意义,对总体理解MQTT作用很大。下面列举了一些常用的操作:
CONNECT
  TCP连接建立完毕后,Client向Server发出一个Request。
如果一段时间内接收不到Server的Response,则关闭socket,重新建立一个session连接。
  如果一个ClientID已经与服务器连接,则持有同样ClientID的旧有连接必须由服务器关闭后,新建立才能建立。
CONNACK
  Server发出Response响应。

1
2
3
4
5
6
0x00 Connection Accepted
0x01 Connection Refused: unacceptable protocol version
0x02 Connection Refused: identifier rejected
0x03 Connection Refused: server unavailable
0x04 Connection Refused: bad user name or password
0x05 Connection Refused: not authorized

PUBLISH 发布消息
  Client/Servier均可以进行PUBLISH。
  publish message 应该包含一个TopicName(Subject/Channel),即订阅关键词。

关于Topic通配符

  • /:用来表示层次,比如a/b,a/b/c。
  • #:表示匹配>=0个层次,比如a/#就匹配a/,a/b,a/b/c。
    单独的一个#表示匹配所有。不允许 a#和a/#/c。
  • +:表示匹配一个层次,例如a/+匹配a/b,a/c,不匹配a/b/c。
    单独的一个+是允许的,a+不允许,a/+/b不允许

PUBACK 发布消息后的确认
  QoS=1时,Server向Client发布该确认(Client收到确认后删除),订阅者向Server发布确认。
PUBREC / PUBREL / PUBCOMP
  QoS=2时
  1. Server->Client发布PUBREC(已收到);
  2. Client->Server发布PUBREL(已释放);
  3. Server->Client发布PUBCOMP(已完成),Client删除msg;订阅者也会向Server发布类似过程确认。
PINGREQ / PINGRES 心跳
  Client有责任发送KeepAliveTime时长告诉给Server。在一个时长内,发送PINGREQ,Server发送PINGRES确认。
  Server在1.5个时长内未收到PINGREQ,就断开连接。
  Client在1个时长内未收到PINGRES,断开连接。
  一般来说,时长设置为几个分钟。最大18小时,0表示一直未断开。
Clean Session
  如果为false(flag=0),Client断开连接后,Server应该保存Client的订阅信息。
  如果为true(flag=1),表示Server应该立刻丢弃任何会话状态信息。

背景

  近来公司需要做一个即时通讯工具,选型用MQTT协议来做。于是仔细搜集MQTT相关的了一些资料,并分享出来供大家参考。

MQTT简介

  MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,它是一种轻量级的、基于代理的“发布/订阅”模式的消息传输协议。其具有协议简洁、小巧、可扩展性强、省流量、省电等优点,而且已经有PHP,JAVA,Python,C,C#,Go等多个语言版本,基本可以使用在任何平台上,几乎可以把所有联网物品和外部连接起来,所以特别适合用来当做物联网的通信协议。

MQTT特点

  MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:

  1. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;
  • 对负载内容屏蔽的消息传输;
  • 使用 TCP/IP 提供网络连接;
  • 有三种消息发布服务质量:
    • “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    • “至少一次”,确保消息到达,但消息重复可能会发生。
    • “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
  • 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;
  • 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制;

MQTT协议特征

  1. 消息模型
      MQTT是一种基于代理的发布/订阅的消息协议。提供一对多的消息分发,解除应用程序耦合。一个发布者可以对应多个订阅者,当发布者发生变化的时候,他可以将消息一一通知给所有的订阅者。这种模式提供了更大的网络扩展性和更动态的网络拓扑。
    MQTT 消息传输模型
  • 消息质量
    MQTT提供三种质量的服务:
    1. 至多一次,可能会出现丢包的现象。使用在对实时性要求不高的情况。这一级别可应用于如下情景,如环境传感器数据,丢失一次读记录无所谓,因为很快下一次读记录就会产生。
    • 至少一次,保证包会到达目的地,但是可能出现重包。
    • 正好一次,保证包会到达目的地,且不会出现重包的现象。这一级别可用于如计费系统等场景,在计费系统中,消息丢失或重复可能会导致生成错误的费用。
  • 主题名称
      主题名称(Topic name)用来标识已发布消息的信息的渠道。订阅者用它来确定接收到所关心的信息。它是一个分层的结构,用斜线“/”作为分隔符。有两种通配符可以在主题发布、订阅时使用:“#”和“+”。前者可以通配多层结构,而后者只能通配一层结构。例如一个topic : “a/b/c”,则“a/+/c”和“a/#”都可以和它相等。发布不支持模糊匹配,必须是确定的主题。
  • 遗属
      当一个客户端断开连接的时候,它希望客户端可以发送它指定的消息。该消息和普通消息的结构相同。通过设置该位并填入和信息相关的内容即可。
  • 消息类型
消息类型 类型编码 说明
reserved 0 保留
connect 1 客户端到服务端的连接请求
connACK 2 服务端对连接请求的响应
publish 3 发布消息
puback 4 对发布消息的回应
pubRec 5 收到发布消息(保证传输part1)
pubRel 6 释放发布消息(保证传输part2)
pubComp 7 完成发布消息(保证传输part3)
subscribe 8 客户端订阅请求
subBack 9 订阅请求的回应
unsubscribe 10 停止订阅请求
unsubBack 11 停止订阅请求响应
pingReq 12 Ping请求(保持连接)
pingResp 13 Ping响应
disconnect 14 客户端正在断开
reserved 15 保留

开发一个MQTT库需要提供如下命令:
Connect :当一个TCP/IP套接字在服务器端和客户端连接建立时需要使用的命令。
publish : 是由客户端向服务端发送,告诉服务器端自己感兴趣的Topic。每一个publishMessage 都会与一个Topic的名字联系在一起。
pubRec: 是publish命令的响应,只不过使用了2级QoS协议。它是2级QoS协议的第二条消息
pubRel: 是2级QoS协议的第三条消息
publComp: 是2级QoS协议的第四条消息
subscribe: 允许一个客户端注册自已感兴趣的Topic 名字,发布到这些Topic的消息会以publish Message的形式由服务器端发送给客户端。
unsubscribe: 从客户端到服务器端,退订一个Topic。
Ping: 有客户端向服务器端发送的“are you alive”的消息。
disconnect:断开这个TCP/IP协议。

MQTT服务端和客户端

MQTT协议服务端:https://github.com/mqtt/mqtt.github.io/wiki/servers
MQTT协议类库:https://github.com/mqtt/mqtt.github.io/wiki/libraries
MQTT协议官网:http://mqtt.org/

应用场景

推送

  再给大家普及下“推送”这个概念,推送这个词大部分人都会有印象的。比如PC端的推送广告,比如安卓的推送服务,还有一些即时通信软件如微信、易信等也是采用的推送技术。
  现在的推送实现的方式已经非常多了,主流的有C2DM服务(Google Cloud Messaging),基于XML协议的通讯协议XMPP协议(Openfire + Spark + Smack), 轻量级的、基于代理的“发布/订阅”模式的消息传输协议MQTT协议,还可以通过嵌入SDK使用第三方提供的推送服务,如百度云推送极光推送智游推送腾讯信鸽等。
  推送利用的也是类似于上面提到的代理技术,从而将数据源源不断地推向客户机。笔者对其它协议了解不多所以也不敢多做妄言,但是现在国内很多企业都已经广泛使用MQTT作为Android手机客户端与服务器端推送消息的协议。其中Sohu,Cmstop手机客户端中均有使用到MQTT作为消息推送消息。

背景

  上次写的我在Deepin Linux 上使用ssh问题集锦日记中提到可以用config来方便管理ssh会话,现补充上来。

介绍

  通常利用 ssh 连接远程服务器,一般都要输入以下类似命令:

1
ssh user@hostname -p port

  如果拥有多个 ssh 账号,特别是像我这种喜欢在终端里直接 ssh 登陆,又不用 PuTTYSecureCRT之类的 ssh 客户端的,要记住每个ssh 账号的参数,比较浪费精力、时间。
  还好 ssh 提供一种优雅且灵活的方式来解决这个问题,就是利用 ssh 的用户配置文件 config 管理 ssh 会话。ssh 的用户配置文件是放在当前用户根目录下的 .ssh 文件夹里(~/.ssh/config,不存在则新创建一个),其配置写法如下:

Host 别名
HostName 主机名
Port 端口
User 用户名
IdentityFile 密钥文件的路径
IdentitiesOnly 只接受SSH key 登录
PreferredAuthentications 强制使用Public Key验证

提示:可以通过 man ssh_config,查看~/.ssh/config的语法。


进入相关的配置后,就可以这样用 ssh 登陆服务器了:

1
ssh 别名

配置

1
2
3
4
5
6
7
8
➜ tonny@tonny-pc  ~/ssh  cp 115.29.240.144.pem ~/.ssh
➜ tonny@tonny-pc ~/ssh vi ~/.ssh/config
# snails
Host snails
HostName 115.29.240.144
Port 63210
IdentityFile ~/.ssh/115.29.240.144.pem
➜ tonny@tonny-pc ~/ssh ssh snails

延伸阅读

github/gitlab/bitbucket 管理多个ssh key

  以前只使用一个 ssh key 在github上提交代码,由于工作原因,需要再添加一个ssh key在公司的 gitlab上提交代码,下面记录下配置过程,防止遗忘。

  1. 针对github及gitlab分别生成公钥和私钥
    1
    2
    ➜ tonny@tonny-pc  ~/ssh  ssh-keygen -t rsa -C "luohoufu@company.com" -f ~/.ssh/id_rsa.gitlab
    ➜ tonny@tonny-pc ~/ssh ssh-keygen -t rsa -C "luohoufu@163.com" -f ~/.ssh/id_rsa.github
  • 添加私钥
    1
    2
    3
    ➜ tonny@tonny-pc  ~/ssh ssh-add ~/.ssh/id_rsa.gitlab
    ➜ tonny@tonny-pc ~/ssh ssh-add ~/.ssh/id_rsa.github
    ➜ tonny@tonny-pc ~/ssh ssh-add -l

ssh-add命令参考: ssh-add

  • 修改配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ➜ tonny@tonny-pc  ~/ssh  vi ~/.ssh/config
    # gitlab
    Host gitlab.com
    HostName gitlab.com
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/id_rsa.gitlab
      
    # github
    Host github.com
    HostName github.com
    PreferredAuthentications publickey
    IdentityFile ~/.ssh/id_rsa.github
  • github密钥配置

    1
    ➜ tonny@tonny-pc  ~/ssh xclip -sel clip < ~/.ssh/id_rsa.github.pub

  登录github.com,并点击左侧的SSH and GPG keys;然后点击“New SSH key” 并粘贴剪切板中的公钥;最后点击“Add SSH key”保存公钥。

  • 测试
    1
    2
    3
    ➜ tonny@tonny-pc  ~/ssh ssh -T git@github.com
    Hi luohoufu! You've successfully authenticated, but GitHub does not provide shell access.
    #表示成功的连上github了,然后再在gitlab上进行同样的操作即可。

  近期在Deepin Linux上使用ssh远程连接其他Linux服务器时,发现有些问题并逐步解决了;特记录下来供大家参考。

环境

  • 个人电脑 Deepin Linux 15.2
  • 远程服务器 CentOS 7

SSH连接慢

在用ssh连接远程服务器时,发现要等待一定时间。排查过程如下:

  1. 将ssh连接过程信息输出
    1
    ➜ tonny@tonny-pc  ~  ssh -v root@115.29.240.144

然后就会输出一大堆debug,通过debug信息就可以看到连接到什么地方被耽搁了比如会显示如下信息:

1
2
debug1: Next authentication method: gssapi-with-mic
debug1: Unspecified GSS failure. Minor code may provide more informationNo credentials cache found

  • 检测连接时间
    1
    ➜ tonny@tonny-pc  ~ time ssh root@115.29.240.144 exit

解决办法

  1. 修改服务器端配置(修改参数值或去掉注释)
    1
    2
    3
    4
    5
    [root@snails ~]# vi /etc/ssh/sshd_config
    UseDNS=no #关闭DNS反向解析
    GSSAPIAuthentication no #关闭SERVER上的GSS认证
    IgnoreRhosts yes #打开SERVER上的IgnoreRhosts参数
    [root@snails ~]# systemctl restart sshd
  • 修改本机配置(考虑到没必要去每台服务器上修改)
    1
    2
    ➜ tonny@tonny-pc  ~  sudo vi /etc/ssh/ssh_config
    GSSAPIAuthentication no #关闭SERVER上的GSS认证

超时自动断开连接

  在离开办公桌一段时间后,发现原来的ssh连接已被断开。提示信息如下:

1
packet_write_wait: Connection to 115.29.240.144 port 22: Broken pipe

解决办法

  1. 修改服务器端配置(修改参数值或去掉注释)
    1
    2
    [root@snails ~]# echo "ClientAliveInterval 60">>/etc/ssh/sshd_config
    [root@snails ~]# systemctl restart sshd

  重启OPENSSH服务器后该项设置会生效。每一个连接到此服务器上的客户端都会受其影响。应注意启用该功能后,安全性会有一定下降(比如忘记登出时……),所以 建议用客户端设置。
如果您只想让当前的 ssh 保持连接,可以使用以下的命令:

1
➜ tonny@tonny-pc  ~  ssh -o ServerAliveInterval=60 user@sshserver

  • 修改本机配置
    1
    [root@snails ~]# echo "ServerAliveInterval 60">>/etc/ssh/ssh_config

采用spawn自动登录服务器(示例)

1
2
3
4
5
6
7
8
9
10
11
➜ tonny@tonny-pc  ~ vi ssh_115.29.240.144
#!/usr/bin/expect -f
set timeout -1
set passwd "THIS@IS@PASS"
spawn ssh -p22 root@115.29.240.144
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$passwd\r" }
}
interact
➜ tonny@tonny-pc ~ chmod +x ssh_115.29.240.144

补充说明

  服务器安全是个永恒的话题,并且安全一直都是相对的。长期检查服务器登录日志、记录登录用户操作等等都是运维人员必须要做的日常事务。当然一开始就加强安全设置会更好。下面的内容主要介绍 如何配置ssh使用密钥登录,禁止口令登录

创建用户并赋予root权限

1
2
3
4
5
6
7
8
9
10
[root@snails ~]# adduser tonny
[root@snails ~]# passwd tonny
[root@snails ~]# ll /home/
drwx------ 2 tonny tonny 4096 7月 20 16:09 tonny
[root@snails ~]# id tonny
uid=1002(tonny) gid=1002(tonny) 组=1002(tonny)
[root@snails ~]# visudo
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
tonny ALL=(ALL) ALL

编辑sshd配置文件,修改一些默认选项

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@snails ~]# vi /etc/ssh/sshd_config
#使用不常用端口
Port 63210
#仅使用SSH2协议
Protocol 2
#修改密钥生成强度
ServerKeyBits 1024
#禁止root账户通过ssh登录
PermitRootLogin no
#禁止使用常规的用户名密码方式登录,此项慎用#在没有生成好Key,并且成功使用之前,要设置为yes
PasswordAuthentication no
#禁止空密码登录
PermitEmptyPasswords no

生成个人的公钥与私钥

1
2
3
4
5
6
7
8
[root@snails ~]# su - tonny
[root@snails ~]$ ssh-keygen -t rsa -P '' -C "tonny"
#改名为sshd支持的默认公钥文件名
[tonny@snails ~]$ mv ~/.ssh/id_rsa.pub ~/.ssh/authorized_keys
#设置公钥文件名,如不设置为400,会无法登录
[tonny@snails ~]$ chmod 400 ~/.ssh/authorized_keys
#取出私钥文件(通过sftp下载)
[tonny@snails ~]$ rm -rf ~/.ssh/id_rsa

利用私钥文件登录

1
2
3
4
#自己的电脑,删除原有的公钥信息
➜ tonny@tonny-pc ~ ssh-keygen -R 115.29.240.144
➜ tonny@tonny-pc ~/ssh mv id_rsa 115.29.240.144.pem
➜ tonny@tonny-pc ~/ssh ssh -p 63210 tonny@115.29.240.144 -i 115.29.240.144.pem

验证

1
2
3
4
5
6
7
#服务器
[root@snails ~]# systemctl restart sshd
#自己的电脑
➜ tonny@tonny-pc ~/ssh ssh -p 63210 tonny@115.29.240.144
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
➜ tonny@tonny-pc ~/ssh ssh -p 63210 root@115.29.240.144 -i 115.29.240.144.pem
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
  1. 为什么说是安全的SSH链接呢?关闭了空密码链接、禁止常规的ssh用户名密码方式登录,只有在服务器上生成过key文件的用户方可访问,key文件+密钥,保护更完善一些。
  • 友情提示: PasswordAuthentication no配置内容,一定要在已经生成好了至少一个key并可从远程登录之后再使用,否则会导致直接无法登录。

附录:
  经常登录远程服务器操作,可以根据自己的喜好进行快捷配置
例如采用spawnaliasoh-my-zsh支持的custom脚本config配置

Zookeeper简介

  Zookeeper 分布式服务框架是 Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等等。

Zookeeper基本概念

zk角色

  Zookeeper中的角色主要有以下三类,如下表所示:
zookeeper角色

zk service网络结构

  Zookeeper的工作集群可以简单分成两类,一个是Leader,唯一一个,其余的都是follower,如何确定Leader是通过内部选举确定的。
zookeeper服务

  1. Leader和各个follower是互相通信的,对于zk系统的数据都是保存在内存里面的,同样也会备份一份在磁盘上。
      对于每个zk节点而言,可以看做每个zk节点的命名空间是一样的,也就是有同样的数据。(可查看下面的树结构)
  • 如果Leader挂了,zk集群会重新选举,在毫秒级别就会重新选举出一个Leaer。
  • 集群中除非有一半以上的zk节点挂了,zk service才不可用。

zk命名空间结构

  Zookeeper的命名空间就是zk应用的文件系统,它和linux的文件系统很像,也是树状,这样就可以确定每个路径都是唯一的,对于命名空间的操作必须都是绝对路径操作。与linux文件系统不同的是,linux文件系统有目录和文件的区别,而zk统一叫做znode,一个znode节点可以包含子znode,同时也可以包含数据。
zookeeper树结构

提示:
比如/Nginx/conf,/是一个znode,/Nginx是/的子znode,/Nginx还可以包含数据,数据内容就是所有安装Nginx的机器IP,/Nginx/conf是/Nginx子znode,它也可以包含内容,数据就是Nginx的配置文件内容。在应用中,我们可以通过这样一个路径就可以获得所有安装Nginx的机器IP列表,还可以获得这些机器上Nginx的配置文件。

zk读写数据

zookeeper读写数据

  • 写数据,但一个客户端进行写数据请求时,会指定zk集群中节点,如果是follower接收到写请求,就会把请求转发给Leader,Leader通过内部的Zab协议进行原子广播,直到所有zk节点都成功写了数据后(内存同步以及磁盘更新),这次写请求算是完成,然后zk service就会给client发回响应
  • 读数据,因为集群中所有的zk节点都呈现一个同样的命名空间视图(就是结构数据),上面的写请求已经保证了写一次数据必须保证集群所有的zk节点都是同步命名空间的,所以读的时候可以在任意一台zk节点上

    ps:其实写数据的时候不是要保证所有zk节点都写完才响应,而是保证一半以上的节点写完了就把这次变更更新到内存,并且当做最新命名空间的应用。所以在读数据的时候可能会读到不是最新的zk节点,这时候只能通过sync()解决。这里先不考虑了,假设整个zk service都是同步meta信息的,后面的文章再讨论。

zk znode类型

  Zookeeper中znode的节点创建时候是可以指定类型的,主要有下面几种类型。

  1. PERSISTENT:持久化znode节点,一旦创建这个znode点存储的数据不会主动消失,除非是客户端主动的delete。
    SEQUENCE:顺序增加编号znode节点,比如ClientA去zk service上建立一个znode名字叫做/Nginx/conf,指定了这种类型的节点后zk会创建/Nginx/conf0000000000,ClientB再去创建就是创建/Nginx/conf0000000001,ClientC是创建/Nginx/conf0000000002,以后任意Client来创建这个znode都会得到一个比当前zk命名空间最大znode编号+1的znode,也就说任意一个Client去创建znode都是保证得到的znode是递增的,而且是唯一的。
  • EPHEMERAL:临时znode节点,Client连接到zk service的时候会建立一个session,之后用这个zk连接实例创建该类型的znode,一旦Client关闭了zk的连接,服务器就会清除session,然后这个session建立的znode节点都会从命名空间消失。总结就是,这个类型的znode的生命周期是和Client建立的连接一样的。比如ClientA创建了一个EPHEMERAL的/Nginx/conf0000000011的znode节点,一旦ClientA的zk连接关闭,这个znode节点就会消失。整个zk service命名空间里就会删除这个znode节点。
  • PERSISTENT|SEQUENTIAL:顺序自动编号的znode节点,这种znoe节点会根据当前已近存在的znode节点编号自动加 1,而且不会随session断开而消失。
  • EPHEMERAL|SEQUENTIAL:临时自动编号节点,znode节点编号会自动增加,但是会随session消失而消失

Zookeeper设计目的

  1. 最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。
  • 可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。
  • 实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。
  • 等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。
  • 原子性:更新只能成功或者失败,没有中间状态。
  • 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。

Zookeeper工作原理

  Zookeeper 的核心是广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。
  Zab协议有两种模式,它们分别是恢复模式(选主)和广播 模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后, 恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。为了保证事务的顺序一致性,zookeeper采用了递增的事务id号 (zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch用 来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。

每个Server在工作过程中有三种状态:

  • LOOKING:当前Server不知道leader是谁,正在搜寻。
  • LEADING:当前Server即为选举出来的leader。
  • FOLLOWING:leader已经选举出来,当前Server与之同步。

    选主流程

      当 leader崩溃或者leader失去大多数的follower,这时候zk进入恢复模式,恢复模式需要重新选举出一个新的leader,让所有的 Server都恢复到一个正确的状态。
    Zookeeper的选举算法有两种:
      一种是基于basic paxos实现的,另外一种是基于fast paxos算法实现的。
    系统默认的选举算法为fast paxos。
    basic paxos流程:
    1. 选举线程由当前Server发起选举的线程担任,其主要功能是对投票结果进行统计,并选出推荐的Server;
    • 选举线程首先向所有Server发起一次询问(包括自己);
    • 选举线程收到回复后,验证是否是自己发起的询问(验证zxid是否一致),然后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些信息存储到当次选举的投票记录表中;
    • 收到所有Server回复以后,就计算出zxid最大的那个Server,并将这个Server相关信息设置成下一次要投票的Server;
    • 线程将当前zxid最大的Server设置为当前Server要推荐的Leader,如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置自己的状态,否则,继续这个过程,直到leader被选举出来。通 过流程分析我们可以得出:要使Leader获得多数Server的支持,则Server总数必须是奇数2n+1,且存活的Server的数目不得少于 n+1.每个Server启动后都会重复以上流程。在恢复模式下,如果是刚从崩溃状态恢复的或者刚启动的server还会从磁盘快照中恢复数据和会话信 息,zk会记录事务日志并定期进行快照,方便在恢复时进行状态恢复。
      选举的具体流程图如下所示:

zk basic paxos选举
fast paxos流程:
  在选举过程中,某Server首先向所有Server提议自己要成为leader,当其它Server收到提议以后,解决epoch和 zxid的冲突,并接受对方的提议,然后向对方发送接受提议完成的消息,重复这个流程,最后一定能选举出Leader。
选举的具体流程图如下所示:

zk fast paxos选举

同步流程

选完leader以后,zk就进入状态同步过程。

  1. leader等待server连接;
  • Follower连接leader,将最大的zxid发送给leader;
  • Leader根据follower的zxid确定同步点;
  • 完成同步后通知follower 已经成为uptodate状态;
  • Follower收到uptodate消息后,又可以重新接受client的请求进行服务了。
    同步的具体流程图如下所示:

zk同步流程

工作流程

Leader工作流程

  1. 恢复数据;
  • 维持与Learner的心跳,接收Learner请求并判断Learner的请求消息类型;
  • Learner的消息类型主要有PING消息、REQUEST消息、ACK消息、REVALIDATE消息,根据不同的消息类型,进行不同的处理。

    PING 消息是指Learner的心跳信息;
    REQUEST消息是Follower发送的提议信息,包括写请求及同步请求;
    ACK消息是Follower的对提议 的回复,超过半数的Follower通过,则commit该提议;
    REVALIDATE消息是用来延长SESSION有效时间。

Leader的工作流程简图具体如下所示:

Leader工作流程

Follower工作流程

Follower主要有四个功能:

  1. 向Leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
  • 接收Leader消息并进行处理;
  • 接收Client的请求,如果为写请求,发送给Leader进行投票;
  • 返回Client结果。
    Follower的消息循环处理如下几种来自Leader的消息:
  1. PING消息: 心跳消息;
  • PROPOSAL消息:Leader发起的提案,要求Follower投票;
  • COMMIT消息:服务器端最新一次提案的信息;
  • UPTODATE消息:表明同步完成;
  • REVALIDATE消息:根据Leader的REVALIDATE结果,关闭待revalidate的session还是允许其接受消息;
  • SYNC消息:返回SYNC结果到客户端,这个消息最初由客户端发起,用来强制得到最新的更新。
    Follower的工作流程简图具体如下所示:

Follower的工作流程

应用篇

  分布式系统的运行是很复杂的,因为涉及到了网络通信还有节点失效等不可控的情况。下面介绍在最传统的master-workers模型,主要可以会遇到什么问题,传统方法是怎么解决以及怎么用zookeeper解决。

Master节点管理

  集群当中最重要的是Master,所以一般都会设置一台Master的Backup。
Backup会定期向Master获取Meta信息并且检测Master的存活性,一旦Master挂了,Backup立马启动,接替Master的工作自己成为Master,分布式的情况多种多样,因为涉及到了网络通信的抖动,针对下面的情况:

  1. Backup检测Master存活性传统的就是定期发包,一旦一定时间段内没有收到响应就判定Master Down了,于是Backup就启动,如果Master其实是没有down,Backup收不到响应或者收到响应延迟的原因是因为网络阻塞的问题呢?Backup也启动了,这时候集群里就有了两个Master,很有可能部分workers汇报给Master,另一部分workers汇报给后来启动的Backup,这下子服务就全乱了。
  • Backup是定期同步Master中的meta信息,所以总是滞后的,一旦Master挂了,Backup的信息必然是老的,很有可能会影响集群运行状态。
    解决问题:
    Master节点高可用,并且保证唯一。
    Meta信息的及时同步。
    Zookeeper Master选举
      Zookeeper会分配给注册到它上面的客户端一个编号,并且zk自己会保证这个编号的唯一性和递增性,N多机器中只需选出编号最小的Client作为Master就行,并且保证这些机器的都维护一个一样的meta信息视图,一旦Master挂了,那么这N机器中编号最小的胜任Master,Meta信息是一致的。

集群worker管理

  集群中的worker挂了是很可能的,一旦worker A挂了,如果存在其余的workers互相之间需要通信,那么workers必须尽快更新自己的hosts列表,把挂了的worker剔除,从而不在和它通信,而Master要做的是把挂了worker上的作业调度到其他的worker上。同样的,这台worker重新恢复正常了,要通知其他的workers更新hosts列表。传统的作法都是有专门的监控系统,通过不断去发心跳包(比如ping)来发现worker是否alive,缺陷就是及时性问题,不能应用于在线率要求较高的场景
解决问题:
集群worker监控。
Zookeeper监控集群
  利用zookeeper建立znode的强一致性,可以用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器变化作出响应。

分布式锁

  在一台机器上要多个进程或者多个线程操作同一资源比较简单,因为可以有大量的状态信息或者日志信息提供保证,比如两个A和B进程同时写一个文件,加锁就可以实现。但是分布式系统怎么办?需要一个三方的分配锁的机制,几百台worker都对同一个网络中的文件写操作,怎么协同?还有怎么保证高效的运行?
解决问题:
高效分布式的分布式锁
Zookeeper分布式锁
  分布式锁主要得益于ZooKeeper为我们保证了数据的强一致性,zookeeper的znode节点创建的唯一性和递增性能保证所有来抢锁的worker的原子性。

配置文件管理

  集群中配置文件的更新和同步是很频繁的,传统的配置文件分发都是需要把配置文件数据分发到每台worker上,然后进行worker的reload,这种方式是最笨的方式,结构很难维护,因为如果集群当中有可能很多种应用的配置文件要同步,而且效率很低,集群规模一大负载很高。还有一种就是每次更新把配置文件单独保存到一个数据库里面,然后worker端定期pull数据,这种方式就是数据及时性得不到同步。
解决问题:
统一配置文件分发并且及时让worker生效
Zookeeper发布与订阅模型
  发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,服务式服务框架的服务地址列表等就非常适合使用。

背景介绍

  本文介绍ZooKeeper-3.4.8版本的分布式安装,力求提供细致、精确的安装指导。本文的安装环境是64位的CentOS 7。

约定

  将ZooKeeper安装在/opt/zookeeper目录,其中/opt/zookeeper是指向/opt/zookeeper-3.4.8的软链接。ZooKeeper的数据目录设置为/opt/zookeeper/data。

机器列表

机器IP 主机名
10.160.233.44 zoo1
10.160.233.45 zoo2
10.160.233.46 zoo3

安装前准备

分别登录3台机器进行主机名设置、hosts设置、ssh免密登录设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
### ssh root@10.160.233.44 登录到目标机器 ###
[root@zoo1 ~]# hostnamectl set-hostname zoo1
### ssh root@10.160.233.45 登录到目标机器 ###
[root@zoo2 ~]# hostnamectl set-hostname zoo2
### ssh root@10.160.233.46 登录到目标机器 ###
[root@zoo3 ~]# hostnamectl set-hostname zoo3
### 下面以zoo1为例,实际需要分别在3台机器上进行以下操作 ###
[root@zoo1 ~]# cat /etc/sysconfig/network
[root@zoo1 ~]# cat >> /etc/hosts << EOF

10.160.233.44 zoo1
10.160.233.45 zoo2
10.160.233.46 zoo3
EOF
[root@zoo1 ~]# ssh-keygen -t rsa -P ''
[root@zoo1 ~]# ls -l /root/.ssh/
[root@zoo1 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@zoo2
[root@zoo1 ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub root@zoo3
[root@zoo1 ~]# ssh root@zoo2
[root@zoo1 ~]# ssh root@zoo3

设置myid

  在Zookeeper配置文件中dataDir指定的数据目录(/opt/zookeeper/data)下,创建文件myid,文件内容为一个正整数值,用来唯一标识当前机器,因此不同机器的数值不能相同,建议从1开始递增标识,以方便记忆和管理。本文约定如下:

机器IP myid
10.160.233.44 1
10.160.233.45 2
10.160.233.46 3

可以使用echo命令直接写进去

1
2
3
4
5
[root@zoo1 ~]# mkdir -p /opt/zookeeper/{data,logs}
[root@zoo1 ~]# ls -ltr /opt/zookeeper/
[root@zoo1 ~]# echo 1 > /opt/zookeeper/data/myid
[root@zoo2 ~]# echo 2 > /opt/zookeeper/data/myid
[root@zoo3 ~]# echo 3 > /opt/zookeeper/data/myid

下载Zookeeper并配置

  Zookeeper是java应用,因此需要java运行环境,对于java环境的配置可以参考我以前的文章在CentOS7上用systemctl配置tomcat 8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@zoo1 ~]# wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.8/zookeeper-3.4.8.tar.gz -P /opt
[root@zoo1 ~]# cd /opt/
[root@zoo1 opt]# scp -r zookeeper-3.4.8.tar.gz root@zoo2:/opt
[root@zoo1 opt]# scp -r zookeeper-3.4.8.tar.gz root@zoo3:/opt
[root@zoo1 opt]# tar -zxvf zookeeper-3.4.8.tar.gz
[root@zoo1 ~]# ln -s /opt/zookeeper-3.4.8 zookeeper
[root@zoo1 opt]# cat > /opt/zookeeper/conf/zoo.cfg << EOF
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/opt/zookeeper/data
dataLogDir=/opt/zookeeper/logs
clientPort=2181
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
maxClientCnxms=500
minSessionTimeout=30000
maxSessionTimeout=60000
EOF
[root@zoo1 opt]# scp -r /opt/zookeeper/conf/zoo.cfg root@zoo2:/opt/zookeeper/conf
[root@zoo1 opt]# scp -r /opt/zookeeper/conf/zoo.cfg root@zoo3:/opt/zookeeper/conf

说明:
server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

设置环境变量、启动ZooKeeper集群

1
2
3
### 下面以zoo1为例,实际需要分别在3台机器上进行以下操作 ###
[root@zoo1 opt]# echo -e '\n\nexport PATH=/opt/zookeeper/bin:$PATH\n' >> /etc/profile && source /etc/profile
[root@zoo1 opt]# zkServer.sh start

安装验证

  脚本zkServer.sh不但可以用来启动ZooKeeper,还可以用来查看状态、停止。使用方式为带一个status参数、stop参数。

1
2
3
4
[root@zoo1 opt]# zkServer.sh status
JMX enabled by default
Using config: /opt/zookeeper/bin/../conf/zoo.cfg
Mode: leader

基本命令

1
[root@zoo1 opt]# zkCli.sh -server localhost:2181
  1. 查看当前节点列表
    1
    2
    [zk: localhost:2181(CONNECTED) 1] ls /
    [zookeeper]
  • 创建节点

    1
    2
    3
    4
    [zk: localhost:2181(CONNECTED) 2] create /test "test"
    Created /test
    [zk: localhost:2181(CONNECTED) 3] ls /
    [zookeeper, test]
  • 查看节点数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [zk: localhost:2181(CONNECTED) 4] get /test
    "test"
    cZxid = 0x300000007
    ctime = Thu Sep 24 05:54:51 PDT 2015
    mZxid = 0x300000007
    mtime = Thu Sep 24 05:54:51 PDT 2015
    pZxid = 0x300000007
    cversion = 0
    dataVersion = 0
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 6
    numChildren = 0
  • 设置节点数据

    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
    [zk: localhost:2181(CONNECTED) 7] set /test "111111" 
    cZxid = 0x300000007
    ctime = Thu Sep 24 05:54:51 PDT 2015
    mZxid = 0x300000008
    mtime = Thu Sep 24 05:57:40 PDT 2015
    pZxid = 0x300000007
    cversion = 0
    dataVersion = 1
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 8
    numChildren = 0
    [zk: localhost:2181(CONNECTED) 8] get /test
    "111111"
    cZxid = 0x300000007
    ctime = Thu Sep 24 05:54:51 PDT 2015
    mZxid = 0x300000008
    mtime = Thu Sep 24 05:57:40 PDT 2015
    pZxid = 0x300000007
    cversion = 0
    dataVersion = 1
    aclVersion = 0
    ephemeralOwner = 0x0
    dataLength = 8
    numChildren = 0
  • 删除节点

    1
    2
    3
    [zk: localhost:2181(CONNECTED) 9] delete /test
    [zk: localhost:2181(CONNECTED) 10] ls /
    [zookeeper]

zookeeper四字命令的使用

zookeeper四字命令 功能描述
conf 输出相关服务配置的详细信息
cons 列出所有连接到服务器的客户端的完全的连接 /会话的详细信息。包括“接受 / 发送”的包数量、会话id 、操作延迟、最后的操作执行等等信息。
dump 列出未经处理的会话和临时节点
envi 输出关于服务环境的详细信息(区别于 conf命令)。
reqs 列出未经处理的请求
ruok 测试服务是否处于正确状态。如果确实如此,那么服务返回“imok ”,否则不做任何相应。
stat 输出关于性能和连接的客户端的列表。
wchs 列出服务器 watch的详细信息。
wchc 通过 session列出服务器 watch的详细信息,它的输出是一个与watch相关的会话的列表。
wchp 通过路径列出服务器 watch的详细信息。它输出一个与 session相关的路径。

查看连接到结点上所有的client信息,被选作leader还是 follower

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@zoo2 ~]# yum -y install nc
[root@zoo2 ~]# echo stat|nc 127.0.0.1 2181
Zookeeper version: 3.4.8-1, built on 02/06/2016 03:18 GMT
Clients:
/10.41.51.7:59655[1](queued=0,recved=34413,sent=34417)
/127.0.0.1:55594[0](queued=0,recved=1,sent=0)
/10.41.51.7:59603[1](queued=0,recved=34426,sent=34431)
/10.41.51.7:59436[1](queued=0,recved=34502,sent=34518)
Latency min/avg/max: 0/0/45
Received: 7768148
Sent: 7771067
Connections: 4
Outstanding: 0
Zxid: 0xb0000c4ec
Mode: follower
Node count: 227

结束语

至此,ZooKeeper分布式安装大告成功!更多细节,请浏览官方文档:http://zookeeper.apache.org/doc/trunk/zookeeperStarted.html

MySQL 5.7主要特性

  1. 更好的性能
    对于多核CPU、固态硬盘、锁有着更好的优化,每秒100W QPS已不再是MySQL的追求,下个版本能否上200W QPS才是用户更关心的。
  • 更好的InnoDB存储引擎
  • 更为健壮的复制功能
    复制带来了数据完全不丢失的方案,传统金融客户也可以选择使用。MySQL数据库。此外,GTID在线平滑升级也变得可能。
  • 更好的优化器
    优化器代码重构的意义将在这个版本及以后的版本中带来巨大的改进,Oracle官方正在解决MySQL之前最大的难题。
  • 原生JSON类型的支持
  • 更好的地理信息服务支持
    InnoDB原生支持地理位置类型,支持GeoJSON,GeoHash特性
  • 新增sys库
    以后这会是DBA访问最频繁的库MySQL 5.7

安装准备

安装依赖包

1
[root@snails ~]# yum -y install gcc gcc-c++ ncurses ncurses-devel cmake bison

下载相应源码包

1
2
[root@snails ~]# wget https://sourceforge.net/projects/boost/files/boost/1.59.0/boost_1_59_0.tar.gz
[root@snails ~]# wget http://cdn.mysql.com/Downloads/MySQL-5.7/mysql-5.7.13.tar.gz

也可以使用官方下载链接 进行下载。

新建MySQL用户和用户组

1
[root@snails ~]# groupadd -r mysql && useradd -r -g mysql -s /sbin/nologin -M mysql

预编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@snails ~]# tar -zxvf boost_1_59_0.tar.gz
[root@snails data]# md5sum mysql-5.7.13.tar.gz
8fab75dbcafcd1374d07796bff88ae00 mysql-5.7.13.tar.gz
[root@snails ~]# tar -zxvf mysql-5.7.13.tar.gz
[root@snails data]# mkdir -p /data/mysql
[root@snails data]# cd mysql-5.7.13
[root@snails data]# cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
-DMYSQL_DATADIR=/data/mysql \
-DWITH_BOOST=../boost_1_59_0 \
-DSYSCONFDIR=/etc \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_PARTITION_STORAGE_ENGINE=1 \
-DWITH_FEDERATED_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_MYISAM_STORAGE_ENGINE=1 \
-DENABLED_LOCAL_INFILE=1 \
-DENABLE_DTRACE=0 \
-DDEFAULT_CHARSET=utf8mb4 \
-DDEFAULT_COLLATION=utf8mb4_general_ci \
-DWITH_EMBEDDED_SERVER=1

编译安装

1
2
3
[root@snails mysql-5.7.13]# make -j `grep processor /proc/cpuinfo | wc -l`
#编译很消耗系统资源,小内存可能编译通不过make install
[root@snails mysql-5.7.13]# make install

设置启动脚本,开机自启动

1
2
3
4
5
6
[root@snails mysql-5.7.13]# ls -lrt /usr/local/mysql
[root@snails mysql-5.7.13]# cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld
[root@snails mysql-5.7.13]# chmod +x /etc/init.d/mysqld
[root@snails mysql-5.7.13]# systemctl enable mysqld
mysqld.service is not a native service, redirecting to /sbin/chkconfig.
Executing /sbin/chkconfig mysqld on

配置文件

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
/etc/my.cnf,仅供参考 
[root@snails mysql-5.7.13]# cat > /etc/my.cnf << EOF
[client]
port = 3306
socket = /dev/shm/mysql.sock
[mysqld]
port = 3306
socket = /dev/shm/mysql.sock
basedir = /usr/local/mysql
datadir = /data/mysql
pid-file = /data/mysql/mysql.pid
user = mysql
bind-address = 0.0.0.0
server-id = 1
init-connect = 'SET NAMES utf8mb4'
character-set-server = utf8mb4
#skip-name-resolve
#skip-networking
back_log = 300
max_connections = 1000
max_connect_errors = 6000
open_files_limit = 65535
table_open_cache = 128
max_allowed_packet = 4M
binlog_cache_size = 1M
max_heap_table_size = 8M
tmp_table_size = 16M
read_buffer_size = 2M
read_rnd_buffer_size = 8M
sort_buffer_size = 8M
join_buffer_size = 8M
key_buffer_size = 4M
thread_cache_size = 8
query_cache_type = 1
query_cache_size = 8M
query_cache_limit = 2M
ft_min_word_len = 4
log_bin = mysql-bin
binlog_format = mixed
expire_logs_days = 30
log_error = /data/mysql/mysql-error.log
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /data/mysql/mysql-slow.log
performance_schema = 0
explicit_defaults_for_timestamp
#lower_case_table_names = 1
skip-external-locking
default_storage_engine = InnoDB
#default-storage-engine = MyISAM
innodb_file_per_table = 1
innodb_open_files = 500
innodb_buffer_pool_size = 64M
innodb_write_io_threads = 4
innodb_read_io_threads = 4
innodb_thread_concurrency = 0
innodb_purge_threads = 1
innodb_flush_log_at_trx_commit = 2
innodb_log_buffer_size = 2M
innodb_log_file_size = 32M
innodb_log_files_in_group = 3
innodb_max_dirty_pages_pct = 90
innodb_lock_wait_timeout = 120
bulk_insert_buffer_size = 8M
myisam_sort_buffer_size = 8M
myisam_max_sort_file_size = 10G
myisam_repair_threads = 1
interactive_timeout = 28800
wait_timeout = 28800
[mysqldump]
quick
max_allowed_packet = 16M
[myisamchk]
key_buffer_size = 8M
sort_buffer_size = 8M
read_buffer = 4M
write_buffer = 4M
EOF

添加mysql的环境变量

1
[root@snails mysql-5.7.13]# echo -e '\n\nexport PATH=/usr/local/mysql/bin:$PATH\n' >> /etc/profile && source /etc/profile

初始化数据库

1
[root@snails mysql-5.7.13]# mysqld --initialize-insecure --user=mysql --basedir=/usr/local/mysql --datadir=/data/mysql

注:

  • MySQL之前版本mysql_install_db是在mysql_basedir/script下
  • MySQL 5.7直接放在了mysql_install_db/bin目录下。
  • “–initialize”已废弃,生成一个随机密码(~/.mysql_secret)
  • “–initialize-insecure”不会生成密码
  • “–datadir”目录下不能有数据文件

启动数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@snails mysql-5.7.13]# systemctl start mysqld
[root@snails mysql-5.7.13]# systemctl status mysqld
● mysqld.service - LSB: start and stop MySQL
Loaded: loaded (/etc/rc.d/init.d/mysqld)
Active: active (running) since 一 2016-07-18 11:15:35 CST; 8s ago
Docs: man:systemd-sysv-generator(8)
Process: 23927 ExecStart=/etc/rc.d/init.d/mysqld start (code=exited, status=0/SUCCESS)
CGroup: /system.slice/mysqld.service
├─23940 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data/mysql --pid-file=/data/mysql/mysql.pid
└─24776 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/data/mysql/mysql-err...

7月 18 11:15:32 snails systemd[1]: Starting LSB: start and stop MySQL...
7月 18 11:15:35 snails mysqld[23927]: Starting MySQL..[ OK ]
7月 18 11:15:35 snails systemd[1]: Started LSB: start and stop MySQL.

查看MySQL服务进程和端口

1
2
3
4
5
[root@snails mysql-5.7.13]# ps -ef | grep mysql
root 23940 1 0 11:15 ? 00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/data/mysql --pid-file=/data/mysql/mysql.pid
mysql 24776 23940 0 11:15 ? 00:00:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/data/mysql --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/data/mysql/mysql-error.log --open-files-limit=65535 --pid-file=/data/mysql/mysql.pid --socket=/dev/shm/mysql.sock --port=3306
[root@snails mysql-5.7.13]# netstat -tunpl | grep 3306
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 24776/mysqld

设置数据库root用户密码

  MySQL和Oracle数据库一样,数据库也默认自带了一个 root 用户(这个和当前Linux主机上的root用户是完全不搭边的),我们在设置好MySQL数据库的安全配置后初始化root用户的密码。配制过程中,一路输入 y 就行了。这里只说明下MySQL5.7.13版本中,用户密码策略分成低级 LOW 、中等 MEDIUM 和超强 STRONG 三种,推荐使用中等 MEDIUM 级别!

1
[root@snails mysql-5.7.13]# mysql_secure_installation

常用操作

将MySQL数据库的动态链接库共享至系统链接库

  一般MySQL数据库还会被类似于PHP等服务调用,所以我们需要将MySQL编译后的lib库文件添加至当前Linux主机链接库 /etc/ld.so.conf.d/
下,这样MySQL服务就可以被其它服务调用了。

1
2
3
4
5
6
7
8
9
10
11
12
 [root@snails mysql-5.7.13]# ldconfig |grep mysql
[root@snails mysql-5.7.13]# echo "/usr/local/mysql/lib" > /etc/ld.so.conf.d/mysql.conf
[root@snails mysql-5.7.13]# ldconfig
[root@snails mysql-5.7.13]# ldconfig -v |grep mysql
ldconfig: 无法对 /libx32 进行 stat 操作: 没有那个文件或目录
ldconfig: 多次给出路径“/usr/lib”
ldconfig: 多次给出路径“/usr/lib64”
ldconfig: 无法对 /usr/libx32 进行 stat 操作: 没有那个文件或目录
/usr/lib64/mysql:
libmysqlclient.so.18 -> libmysqlclient.so.18.0.0
/usr/local/mysql/lib:
libmysqlclient.so.20 -> libmysqlclient.so.20.3.0

创建其它MySQL数据库用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@snails mysql-5.7.13]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.13-log Source distribution

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.
mysql>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mysql>CREATE DATABASE `tonnydb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.01 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| tonnydb |
+--------------------+
5 rows in set (0.00 sec)
mysql> grant all privileges on tonnydb.* to 'tonny@%' identified by 'Hi.Tonny@888';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)

mysql> exit
Bye

背景

  我是一名java工程师,一直以来都以Eclipse进行java开发。从2015年底开始在Linux上工作,期间安装过Ubuntu\Deepin\CentOS等系统,最终确定在Deepin下工作是被其界面吸引。
  最开始用的Deepin还是基于Ubuntu开发的,从15版本开始是基于Debian上定制开发。尝试请下载:下载地址

下面说说我常用的软件及配置:

基础工具

  • 以下是我常用的一些工具
    1
    sudo apt-get install git xclip htop guake tree tmux

git 用来管理源代码的版本及分支
xclip 用来操作剪贴板
htop top加强管理工具
guake 可以后台运行的终端,F12可在后台放音乐。
tree 用来显示目录
tmux 用来管理终端连接

系统自带软件

  • CrossOver 用于运行widows相关的软件,如QQ
  • WPS 用于文档、演示文稿、电子表格操作
  • 深度商店 用于快速安装仓库自带软件
  • 深度音乐/深度影院/深度截图 用于娱乐、截图
  • Gedit 用于文本编辑
  • 深度终端 用于命令操作,可调整为zsh,并用oh-my-zsh加强
  • 文件管理器 用于文件浏览、查看、删除
  • 搜狐拼音/五笔拼音 用于输入法
  • Chrome浏览器 用于上网及网页开发调试

常用工具软件

  • 有道词典 翻译软件,用于专业词汇查找。
  • 为知笔记 记录软件,用于工作总结。
  • VirtualBox 虚拟机软件,用于Windos虚拟、Android虚拟。
  • Wireshark 抓包软件,用于协议分析、调试

附加软件

  • 微信网页版
  • 钉钉
  • Firefox

JAVA开发环境

IntelliJ IDEA

长期使用Eclipse后再用IDEA就很少用回Eclipse了,毕竟IDEA功能比Eclipse强大很多。
  • 集成了Maven环境,可以修改配置路径指向自己下载的maven。
  • 集成了Git环境,方便进行分支切换、代码提交。
  • 调试功能强大,用Visual Studio的同学都会佩服其调试功能,IDEA也是如此。
  • 更高的开发效率,默认深色主题,重构、跳转定义、删除/复制行方便。
  • 集成命令行终端,相当多的快捷键,并可配合终端操作。

Dbeaver

用于数据库操作

Sublime Text 3

用于代码查看及编译

Visual Studio Code

用于golang开发

环境准备

安装java环境

1
2
3
4
5
[root@snails ~]# yum -y install java
[root@snails local]# java -version
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)

下载tomcat并解压

1
2
3
4
5
6
wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.0.36/bin/apache-tomcat-8.0.36.tar.gz -P /usr/local
[root@snails local]# cd /usr/local/
[root@snails local]# tar -zxvf apache-tomcat-8.0.36.tar.gz > /dev/nul
[root@snails local]# ln -s /usr/local/apache-tomcat-8.0.36 /usr/local/tomcat
[root@snails local]# rm -rvf apache-tomcat-8.0.36.tar.gz
已删除"apache-tomcat-8.0.36.tar.gz"

设置环境变量

1
2
3
4
5
6
7
[root@snails local]# vi /etc/profile
export JAVA_HOME=/usr/lib/jvm/jre-1.8.0-openjdk
export CLASS_PATH=.:$JAVA_HOME/lib
export CATALINA_HOME=/usr/local/tomcat
export CATALINA_BASE=/usr/local/tomcat
export PATH=$PATH:$JAVA_HOME/bin:$CATLINA_HOME:/bin
[root@snails ~]# source /etc/profile

centos7 使用 systemctl 替换了 service命令

1
2
3
4
5
6
7
[root@snails ~]# systemctl list-unit-files --type service  #查看全部服务命令
[root@snails ~]# systemctl status name.service #查看服务命令
[root@snails ~]# systemctl start name.service #启动服务
[root@snails ~]# systemctl stop name.service #停止服务
[root@snails ~]# systemctl restart name.service #重启服务
[root@snails ~]# systemctl enable name.service #增加开机启动
[root@snails ~]# systemctl disable name.service #删除开机启动

tips: 其中.service 可以省略。

tomcat增加启动参数

tomcat 需要增加一个pid文件

在tomca/bin 目录下面,增加 setenv.sh 配置,catalina.sh启动的时候会调用,同时配置java内存参数。

1
2
3
4
5
[root@snails ~]# vi /usr/local/tomcat/bin/setenv.sh
#add tomcat pid
CATALINA_PID="$CATALINA_BASE/tomcat.pid"
#add java opts
JAVA_OPTS="-server -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=1024m -Xms512M -Xmx1024M -XX:MaxNewSize=256m"

创建tomcat启动用户并授权

1
2
3
[root@snails ~]# getent group tomcat || groupadd -r tomcat
[root@snails ~]# getent passwd tomcat || useradd -r -d /opt -s /bin/nologin -g tomcat tomcat
[root@snails ~]# chown -R tomcat:tomcat /usr/local/apache-tomcat-8.0.36

增加systemd-tomcat.service

在/usr/lib/systemd/system目录下增加systemd-tomcat.service,目录必须是绝对目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@snails ~]# cd /usr/lib/systemd/system
[root@snails ~]# cat >systemd-tomcat.service <<EOF
[Unit]
Description=Apache Tomcat 8
After=syslog.target network.target

[Service]
Type=forking
PIDFile=/usr/local/tomcat/tomcat.pid
ExecStart=/usr/local/tomcat/bin/startup.sh
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target
EOF

使用systemd-tomcat.service

1
2
3
4
5
6
7
8
9
10
[root@snails ~]# systemctl enable systemd-tomcat
[root@snails ~]# systemctl start systemd-tomcat
[root@snails ~]# ps aux |grep tomcat
tomcat 1304 34.8 2.8 3570504 102908 ? Sl 14:36 0:03 /usr/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=1024m -Xms512M -Xmx1024M -XX:MaxNewSize=256m -Djdk.tls.ephemeralDHKeySize=2048 -Djava.endorsed.dirs=/usr/local/tomcat/endorsed -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
[root@snails system]# curl -I localhost:8080
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 14 Jul 2016 06:39:13 GMT

因为配置pid,在启动的时候会再tomcat根目录生成tomcat.pid文件,停止之后删除。
同时tomcat在启动时候,执行start不会启动两个tomcat,保证始终只有一个tomcat服务在运行。