{"posts":[{"title":"Croc文件传输工具","content":"什么是croc croc是一种允许任意两台计算机简单安全地传输文件和文件夹的工具。AFAIK,croc是唯一可以执行以下所有操作的CLI 文件传输工具: croc适用场景 允许任意两台计算机传输数据(使用中继) 提供端到端加密(使用 PAKE) 实现轻松的跨平台传输(Windows、Linux、Mac) 允许多个文件传输 允许恢复被中断的传输 不需要本地服务器或端口转发 ipv6 优先和 ipv4 回退 可以使用代理,比如tor 使用方法 croc源码分析 文件传输过程 我们可能会好奇,为什么我执行了croc xxx 就能接收到其他机器发送的文件呢 经过源码分析发现,传输过程主要有三个角色,sender,relay, receive sender 当我执行croc send hosts的时候本地会启动一个sender,sender内部会启动对外服务的端口,创建一个room, 一个room只允许存在两个连接,一个发送者,一个接收者,此时还未出现接收者,所以room内需要保持发送者的连接,同时会生成一个room的code与password(可选项),并且将room外网地址, code 发送至 relay服务中,如果未设置relay服务,默认使用 croc.schollz.com:9009(可自行搭建) relay 存储着room的信息,能让接收者通过code查找到连接地址 receive croc code 可以添加--relay参数指定relay, 如果未指定默认使用croc.schollz.com:9009, relay查找到code对应的sender的连接地址,reveive连接上sender的指定room, 输入password(可选),进入room后此时存在两个连接了,sender开始将文件数据传输给receive,结束后断开连接,关闭room 如何实现断点续传 上传过程中记录读取块位置,使用固定块传输,类似dd命令,进行文件传输 ","link":"https://greatewei.github.io/post/croc-wen-jian-chuan-shu-gong-ju/"},{"title":"AWS lambda实现s3图片资源数据万象","content":"详细步骤: 00x1 什么是数据万象 前端可通过原有的cdn链接后添加相应的图片操作参数即可实现动态的进行图片资源操作(图片缩放,裁剪,质量缩放,水印,文件类型转换) eg: https://www.greatwei.com/1.png => https://www.greatwei.com/1.png@200h_300w_20x20x100x100c.jpg 这样的链接即可获取1.png 高200, 宽300, 左上角20,20位置开始裁剪裁剪100x100大小的图片,并且图片类型转换成jpg的图片 00x2 如何实现 Cdn请求过程 如图,cdn请求过程存在四条链路,我们可以根据自己的需求在指定路径挂载lambda函数触发,本次方案采用的是在路径3设置图片处理函数(aws支持nodejs, go, python来实现函数), 基本过程,路径3接收获取资源失败的相关信息,解析文件路径获取原图,进行图片资源操作,将处理后的数据同步返回cdn,异步上传处理后的图片到s3 注意点 (1) 处理后图片返回cdn,应该设置reponse Etag,使用http的缓存策略 (2) 异步上传到s3需要给资源打上tag,并且在aws管理后台设置tag的声明周期,防止资源占用,需要进行删除,只需要保留原图 (3) @200h_300w_20x20x100x100c.jpg 参数容易被恶意修改,导致s3产生大量资源内容,可以将改规则进行对称加密防止用户发现规则,或者给资源进行sign计算,虽然图片资源比例暴露,但是用户无法进行修改 (4) 资源过大的图片可以进行忽略,通过资源meta信息判断文件大小,超过指定大小跳过处理 00x3 优化空间 (1) 大文件处理 (2) 图片处理速度 ","link":"https://greatewei.github.io/post/aws-lambda-shi-xian-s3-tu-pian-zi-yuan-shu-ju-wan-xiang/"},{"title":"Go日志转发服务设计","content":"服务描述 日志转发服务主要是通过读取filebeat发送到redis的数据内容进行加工处理后,写入数据库的服务。 服务需求 (1) 能够快速读取redis数据,快速写入指定数据库。 (2) 空闲时,不能够长时间占用cpu资源。 (3) 保证数据完整性。 服务设计 数据库 由于这个服务处理的只要是日志数据,写多读少,所以需要寻找一个写性能优秀的数据,本服务使用的是cassandra数据库,单机写入性能能达到3w TPS, redis操作list的单机速度也差不多是5w TPS, 刚好适合,生产速度和消费速度接近。 逻辑设计 \b由于redis读取的速度大于cs数据库写入速度,如果是边读编写,容易导致cs数据库处理不过来,所以我们可使用channel,将读取出来的数据放入channel,如果channel满了,生产者就阻塞住了,这样就可以控制生产速度,接下来需要开启多个work读取channel将数据写入cs数据库。 注意点: (1) 读取redis采用批量读取,当数据为空的时候,需要改成阻塞获取。 (2) 写入cs采用批量写入。 开发过程遇到的坑 (1) 由于go的协程非常优秀,于是做了一件蠢事,在读取channel一定数据后,开启一个协程进行写入,但是数据量实在太多了,导致产生了很多协程,内存占用很高,为了解决这个问题,又采用了go的协程池防止无限增长的协程数,但是当协程池满后,出现了阻塞,由于没有新的协程使用,导致channel数据消费不过来,之后选择启用多个work,每个work都轮询读取channel解决了以上问题。 服务性能 2w TPS写入能力,cpu 内存使用情况正常。 ","link":"https://greatewei.github.io/post/go-ri-zhi-zhuan-fa-fu-wu-she-ji/"},{"title":"Kafka磁盘扩容","content":"起因 由于业务日志量不断变的庞大,当前2台kafka机器的磁盘占用过高,很快就不能正常使用,有两个解决方案。 (1) 添加机器 (2) 新增磁盘 由于考虑到目前使用的机器磁盘本身就比较小只有500G,于是我们选择了方案2,将磁盘扩容至1t,如果后期依旧不够使用,就需要考虑新增机器。 扩容步骤 (1) 迁移zookeeper(如果zk和kafka不再同一机器,无需考虑)。 (2) 选择扩容kafka集群中非controller节点,将历史数据拷贝到新磁盘中(使用rsync进行同步,考虑到历史数据过大,应该先进行拷贝,最后停止kafka节点,进行增量数据拷贝)。 (3) 重新扩容后的节点需要等到数据的同步成功后(可使用kafka_drop观察),继续操作下一台机器节点。 zookeeper迁移步骤: (1) 关闭zk, ps -ef|grep zk|awk '{print $2}'|xargs kill (2) zk数据迁移到新磁盘(使用rsync进行同步) (3) 重启zk (4) 检查zookeeper是否正常, ./zkServer.sh status kafka迁移步骤: (1) 进行数据拷贝到新磁盘 rsync -avPh /data/kafka /kafka_data/kafka。 (2) 关闭kafka 非controller 节点。 (3) 继续进行数据拷贝到新磁盘rsync -avPh /data/kafka /kafka_data/kafka 只需要copy kafka数据。 (4) 重启kafka节点(restart-server.sh)。 (5) 观察数据同步是否达到100%(可使用kafka_drop观察)。 (6) 继续下一台机器。 总结 迁移过程还算顺利,客户端无感知迁移。 ","link":"https://greatewei.github.io/post/kafka-ci-pan-kuo-rong/"},{"title":"Prometheus监控告警系统","content":"什么是prometheus Prometheus是由SoundCloud开发的开源监控报警系统和时序列数据库(TSDB)。Prometheus使用Go语言开发,是Google BorgMon监控系统的开源版本。 2016年由Google发起Linux基金会旗下的原生云基金会(Cloud Native Computing Foundation), 将Prometheus纳入其下第二大开源项目。 Prometheus目前在开源社区相当活跃。 Prometheus和Heapster(Heapster是K8S的一个子项目,用于获取集群的性能数据。)相比功能更完善、更全面。Prometheus性能也足够支撑上万台规模的集群。 prometheus的特点 多维度数据模型。 灵活的查询语言。 不依赖分布式存储,单个服务器节点是自主的。 通过基于HTTP的pull方式采集时序数据。 可以通过中间网关进行时序列数据推送。 通过服务发现或者静态配置来发现目标服务对象。 支持多种多样的图表和界面展示,比如Grafana等。 基本原理 Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的集成过程。这样做非常适合做虚拟化环境监控系统,比如VM、Docker、Kubernetes等。输出被监控组件信息的HTTP接口被叫做exporter 。目前互联网公司常用的组件大部分都有exporter可以直接使用,比如Varnish、Haproxy、Nginx、MySQL、Linux系统信息(包括磁盘、内存、CPU、网络等等)。 服务过程 Prometheus Daemon负责定时去目标上抓取metrics(指标)数据,每个抓取目标需要暴露一个http服务的接口给它定时抓取。Prometheus支持通过配置文件、文本文件、Zookeeper、Consul、DNS SRV Lookup等方式指定抓取目标。Prometheus采用PULL的方式进行监控,即服务器可以直接通过目标PULL数据或者间接地通过中间网关来Push数据。 Prometheus在本地存储抓取的所有数据,并通过一定规则进行清理和整理数据,并把得到的结果存储到新的时间序列中。 Prometheus通过PromQL和其他API可视化地展示收集的数据。Prometheus支持很多方式的图表可视化,例如Grafana、自带的Promdash以及自身提供的模版引擎等等。Prometheus还提供HTTP API的查询方式,自定义所需要的输出。 PushGateway支持Client主动推送metrics到PushGateway,而Prometheus只是定时去Gateway上抓取数据。 Alertmanager是独立于Prometheus的一个组件,可以支持Prometheus的查询语句,提供十分灵活的报警方式。 docker-stack 配置服务 其中kafka_exporter与webhook-dingding 是可选服务,这里主要是用于kafka集群的监控与告警使用。启动命令: docker stack deploy prom --compose-file=docker-stack.yml 配置 prometheus 配置文件(prometheus.yml) 这里主要配置一些exporter服务,主要用来提供数据,用于分析监控。 prometheus 配置文件(alter.rules) 以上是自定义的各种告警指标。 alertmanager配置文件(config.yml) 这里将会将告警信息发送到指定告警api,可自定义webhook服务,处理告警消息内容。 效果图 ","link":"https://greatewei.github.io/post/prometheus-jian-kong-gao-jing-xi-tong/"},{"title":"MQTT协议 简介","content":"MQTT是什么? MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(Publish/Subscribe)模式的轻量级通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布,目前最新版本为v3.1.1。MQTT最大的优点在于可以以极少的代码和有限的带宽,为远程设备提供实时可靠的消息服务。做为一种低开销、低带宽占用的即时通讯协议,MQTT在物联网、小型设备、移动应用等方面有广泛的应用。 当然,在物联网开发中,MQTT不是唯一的选择,与MQTT互相竞争的协议有XMPP和CoAP协议等,文章末尾会有一个比较和说明。 MQTT是哪一层协议? 众所周知,TCP/IP参考模型可以分为四层:应用层、传输层、网络层、链路层。TCP和UDP位于传输层,应用层常见的协议有HTTP、FTP、SSH等。MQTT协议运行于TCP之上,属于应用层协议,因此只要是支持TCP/IP协议栈的地方,都可以使用MQTT。 MQTT消息格式 每条MQTT命令消息的消息头都包含一个固定的报头,有些消息会携带一个可变报文头和一个负荷。消息格式如下: 固定报文头 | 可变报文头 | 负荷 固定报文头(Fixed Header) MQTT固定报文头最少有两个字节,第一字节包含消息类型(Message Type)和QoS级别等标志位。第二字节开始是剩余长度字段,该长度是后面的可变报文头加消息负载的总长度,该字段最多允许四个字节。 剩余长度字段单个字节最大值为二进制0b0111 1111,16进制0x7F。也就是说,单个字节可以描述的最大长度是127字节。为什么不是256字节呢?因为MQTT协议规定,单个字节第八位(最高位)若为1,则表示后续还有字节存在,第八位起“延续位”的作用。 例如,数字64,编码为一个字节,十进制表示为64,十六进制表示为0×40。数字321(65+2*128)编码为两个字节,重要性最低的放在前面,第一个字节为65+128=193(0xC1),第二个字节是2(0x02),表示2×128。 由于MQTT协议最多只允许使用四个字节表示剩余长度(如表1),并且最后一字节最大值只能是0x7F不能是0xFF,所以能发送的最大消息长度是256MB,而不是512MB。 可变报文头(Variable Header) 可变报文头主要包含协议名、协议版本、连接标志(Connect Flags)、心跳间隔时间(Keep Alive timer)、连接返回码(Connect Return Code)、主题名(Topic Name)等,后面会针对主要部分进行讲解。 有效负荷(Payload) Payload直译为负荷,可能让人摸不着头脑,实际上可以理解为消息主体(body)。 当MQTT发送的消息类型是CONNECT(连接)、PUBLISH(发布)、SUBSCRIBE(订阅)、SUBACK(订阅确认)、UNSUBSCRIBE(取消订阅)时,则会带有负荷。 MQTT的主要特性 固定报文头中的第一个字节包含连接标志(Connect Flags),连接标志用来区分MQTT的消息类型。MQTT协议拥有14种不同的消息类型(如表2),可简单分为连接及终止、发布和订阅、QoS 2消息的机制以及各种确认ACK。至于每一个消息类型会携带什么内容,这里不多阐述。 消息质量(Qos) MQTT消息质量有三个等级,QoS 0,QoS 1和 QoS 2。 QoS 0:最多分发一次。消息的传递完全依赖底层的TCP/IP网络,协议里没有定义应答和重试,消息要么只会到达服务端一次,要么根本没有到达。 QoS 1:至少分发一次。服务器的消息接收由PUBACK消息进行确认,如果通信链路或发送设备异常,或者指定时间内没有收到确认消息,发送端会重发这条在消息头中设置了DUP位的消息。 QoS 2:只分发一次。这是最高级别的消息传递,消息丢失和重复都是不可接受的,使用这个服务质量等级会有额外的开销。 通过下面的例子可以更深刻的理解上面三个传输质量等级。 比如目前流行的共享单车智能锁,智能锁可以定时使用QoS level 0质量消息请求服务器,发送单车的当前位置,如果服务器没收到也没关系,反正过一段时间又会再发送一次。之后用户可以通过App查询周围单车位置,找到单车后需要进行解锁,这时候可以使用QoS level 1质量消息,手机App不断的发送解锁消息给单车锁,确保有一次消息能达到以解锁单车。最后用户用完单车后,需要提交付款表单,可以使用QoS level 2质量消息,这样确保只传递一次数据,否则用户就会多付钱了。 遗愿标志 (Will Flag) 在可变报文头的连接标志位字段(Connect Flags)里有三个Will标志位:Will Flag、Will QoS和Will Retain Flag,这些Will字段用于监控客户端与服务器之间的连接状况。如果设置了Will Flag,就必须设置Will QoS和Will Retain标志位,消息主体中也必须有Will Topic和Will Message字段。 那遗愿消息是怎么回事呢?服务器与客户端通信时,当遇到异常或客户端心跳超时的情况,MQTT服务器会替客户端发布一个Will消息。当然如果服务器收到来自客户端的DISCONNECT消息,则不会触发Will消息的发送。 因此,Will字段可以应用于设备掉线后需要通知用户的场景。 连接保活心跳机制(Keep Alive Timer) MQTT客户端可以设置一个心跳间隔时间(Keep Alive Timer),表示在每个心跳间隔时间内发送一条消息。如果在这个时间周期内,没有业务数据相关的消息,客户端会发一个PINGREQ消息,相应的,服务器会返回一个PINGRESP消息进行确认。如果服务器在一个半(1.5)心跳间隔时间周期内没有收到来自客户端的消息,就会断开与客户端的连接。心跳间隔时间最大值大约可以设置为18个小时,0值意味着客户端不断开。 MQTT其他特点 异步发布、订阅实现 发布/订阅模式解耦了发布消息的客户(发布者)与订阅消息的客户(订阅者)之间的关系,这意味着发布者和订阅者之间并不需要直接建立联系。 这个模式有以下好处: 发布者与订阅者只需要知道同一个消息代理即可; 发布者和订阅者不需要直接交互; 发布者和订阅者不需要同时在线。 由于采用了发布/订阅实现,MQTT可以双向通信。也就是说MQTT支持服务端反向控制设备,设备可以订阅某个主题,然后发布者对该主题发布消息,设备收到消息后即可进行一系列操作。 二进制格式实现 MQTT基于二进制实现而不是字符串,比如HTTP和XMPP都是基于字符串实现。由于HTTP和XMPP拥有冗长的协议头部,而MQTT固定报文头仅有两字节,所以相比其他协议,发送一条消息最省流量。 MQTT的安全 由于MQTT运行于TCP层之上并以明文方式传输,这就相当于HTTP的明文传输,使用Wireshark可以完全看到MQTT发送的所有消息,消息指令一览无遗 !()[https://img-blog.csdn.net/20170725101215295] 这样可能会产生以下风险: 设备可能会被盗用; 客户端和服务端的静态数据可能是可访问的(可能会被修改); 协议行为可能有副作用(如计时器攻击); 拒绝服务攻击; 通信可能会被拦截、修改、重定向或者泄露; 虚假控制报文注入。 作为传输协议,MQTT仅关注消息传输,提供合适的安全功能是开发者的责任。安全功能可以从三个层次来考虑——应用层、传输层、网络层。 应用层:在应用层上,MQTT提供了客户标识(Client Identifier)以及用户名和密码,可以在应用层验证设备。 传输层:类似于HTTPS,MQTT基于TCP连接,也可以加上一层TLS,传输层使用TLS加密是确保安全的一个好手段,可以防止中间人攻击。客户端证书不但可以作为设备的身份凭证,还可以用来验证设备。 网络层:如果有条件的话,可以通过拉专线或者使用VPN来连接设备与MQTT代理,以提高网络传输的安全性。 总结 MQTT基于异步发布/订阅的实现解耦了消息发布者和订阅者,基于二进制的实现节省了存储空间及流量,同时MQTT拥有更好的消息处理机制,可以替代TCP Socket一部分应用场景。相对于HTTP和XMPP,MQTT可以选择用户数据格式,解析复杂度低,同时MQTT也可用于手机推送等领域。手机作为与人连接的入口,正好建立了人与物的连接,可谓一箭双雕。当然,其他协议也可以作为一个辅助的存在,HTTP可以为只需定时上传数据的设备服务,CoAP则更适用于非常受限的移动通信网络。 ","link":"https://greatewei.github.io/post/mqtt-xie-yi-xiang-jie/"},{"title":"美图长连接平台 简介","content":"美图长连接服务 随着科技的飞速发展,技术的日新月异,长连接的运用场景日益增多。不仅在后端服务中被广泛运用,比较常见的有数据库的访问、服务内部状态的协调等,而且在 App 端的消息推送、聊天信息、直播弹字幕等场景长连接服务也是优选方案。长连接服务的重要性也在各个场合被业界专家不断提及,与此同时也引起了更为广泛地关注和讨论,各大公司也开始构建自己的长连接服务。 美图公司于2016 年初开始构建长连接服务,与此同时, Go 在编程语言领域异军突起,考虑到其丰富的编程库,完善的工具链,简单高效的并发模型等优势,使我们最终选择 Go 去作为实现长连接服务的语言。在通信协议的选择上,考虑到 MQTT 协议的轻量、简单、易于实现的优点,选择了 MQTT 协议作为数据交互的载体。其整体的架构会在下文中做相应地介绍。 美图长连接服务(项目内部代号为bifrost )已经历时三年,在这三年的时间里,长连接服务经过了业务的检验,同时也经历了服务的重构,存储的升级等,长连接服务从之前支持单机二十几万连接到目前可以支撑单机百万连接。在大多数长连接服务中存在一个共性问题,那就是内存占用过高,我们经常发现单个节点几十万的长连接,内存却占用十几G 甚至更多,有哪些手段能降低内存呢? 本文将从多个角度介绍长连接服务在内存优化路上的探索,首先会先通过介绍当前服务的架构模型,Go 语言的内存管理,让大家清晰地了解我们内存优化的方向和关注的重要数据。后面会重点介绍我们在内存优化上做的一些尝试以及具体的优化手段,希望对大家有一定的借鉴意义。 架构模型 从架构图中我们可以清晰地看到由7 个模块组成,分别是:conf 、grpcsrv 、mqttsrv、session、pubsub、packet、util ,每个模块的作用如下: conf :配置管理中心,负责服务配置的初始化,基本字段校验。 grpcsrv :grpc 服务,集群内部信息交互协调。 mqttsrv :mqtt 服务,接收客户端连接,同时支持单进程多端口 MQTT 服务。 session :会话模块,管理客户端状态变化,MQTT 信息的收发。 pubsub :发布订阅模块,按照 Topic 维度保存 session 并发布 Topic 通知给 session。 packet:协议解析模块,负责 MQTT 协议包解析。 util :工具包,目前集成监控、日志、grpc 客户端、调度上报四个子模块。 Go的内存管理 ... 业务优化 session模块主要用户处理消息的收发,在实现时考虑到在通常场景中业务的消息生产大于客户端的消费速度的情况,为了缓解这种状况,设计时引入消息的缓冲队列,这种做法同样也有助于做客户端消息的流控。 缓冲消息队列借助chan 实现 ,chan 大小根据经验将初始化默认配置为 128 。但在目前线上推送的场景中,我们发现,消息的生产一般小于消费的速度,128 缓冲大小明显偏大,因此我们把长度调整为 16 ,减少内存的分配。 在设计中按照topic 对客户端进行分组管理的算法中,采用空间换时间的方式,组合 map 和 list 两种数据结构对于客户端集合操作提供O(1)的删除、O(1)的添加、O(n)的遍历。数据的删除采用标记删除方式,使用辅助 slice 结构进行记录,只有到达预设阈值才会进行真正的删除。虽然标记删除提高了遍历和添加的性能,但也同样带来了内存损耗问题。 大家一定好奇什么样的场景需要提供这样的复杂度,在实际中其场景有以下两种情况: 在实际的网络场景中,客户端随时都可能由于网络的不稳定断开或者重新建联,因此集合的增加和删除需要在常数范围内。 在消息发布的流程中,采用遍历集合逐一发布通知方式,但随着单个topic 上的用户量的增加,经常会出现单个 topic 用户集合消息过热的问题,耗时太久导致消息挤压,因此针对集合的遍历当然也要求尽量快。 通过benchamrk 数据分析,在标记回收 slice 长度在 1000 时,可以提供最佳的性能,因此默认配置阈值为 1000。在线上服务中,无特殊情况都是采用默认配置。但在当前推送服务的使用中,发现标记删除和延迟回收机制好处甚微,主要是因为 topic 和客户端为 1 : 1 方式,也就是不存在客户端集合,因此调整回收阈值大小为 2,减少无效内存占用。 上述所有优化,只要简单调整配置后服务灰度上线即可,在设计实现时通过conf 模块动态配置,降低了服务的开发和维护成本。通过监控对比优化效果如下表,在优化后在线连接数比优化的在线连接更多的情况下, heap 使用内存使用数量由原来的 4.16G 下降到了 3.5G ,降低了约 0.66 G。 golang 代码优化 在实现上面展示的架构的时候发现在session 模块 和 mqttsrv 模块之间存在很多共享变量,目前实现方式都是采用指针或者值拷贝的,由于 session的数量和客户端数据量成正比也就导致消耗大量内存用于共享数据,这不仅仅增加 GC 压力,同样对于内存的消耗也是巨大的。就此问题思考再三,参考系统的库 context 的设计在架构中也抽象 context 包负责模块之间交互信息传递,统一分配内存。此外还参考他人减少临时变量的分配的优化方式,提高系统运行效率。主要优化角度参考如下: 在频繁申请内存的地方,使用pool 方式进行内存管理 小对象合并成结构体一次分配,减少内存分配次数 缓存区内容一次分配足够大小空间,并适当复用 slice 和 map 采 make 创建时,预估大小指定容量 调用栈避免申请较多的临时对象 减少[]byte 与 string 之间转换,尽量采用 []byte 来字符串处理 目前系统具被完备的单元测试、集成测试,因此经过一周的快速的开发重构后灰度上线监控数据对比如下表:在基本相同的连接数上,heap 使用内存约占用降低 0.27G,stack 申请内存占用降低 3.81G。为什么 stack 会大幅度降低呢? 通过设置stackDebug 重新编译程序追查程序运行过程,优化前 goroutine 栈的大多数在内存为 16K,通过减少临时变量的分配,拆分大函数处理逻辑,有效的减少触发栈的内存扩容(详细分析见参考文章),优化后 goroutine 栈内存降低到 8 K。一个连接需要启动两个 goroutine 负责数据的读和写,粗略计算一个连接减少约 16 K 的内存,23 w 连接约降低 3.68 G 内存。 网络模型优化 在Go 语言的网络编程中经典的实现都是采用同步处理方式,启动两个 goroutine 分别处理读和写请求,goroutine 也不像 thread ,它是轻量级的。但对于一百万连接的情况,这种设计模式至少要启动两百万的 goroutine,其中一个 goroutine 使用栈的大小在 2 KB 到 8KB, 对于资源的消耗也是极大的。在大多数场景中,只有少数连接是有数据处理,大部分 goroutine 阻塞 IO 处理中。在因此可以借鉴 C 语言的设计,在程序中使用 epoll 模型做事件分发,只有活跃连接才会启动 goroutine 处理业务,基于这种思想修改网络处理流程。 网络模型修改测试完成后开始灰度上线,通过监控数据对比如下表:在优化后比优化前的连接数多10 K的情况下,heap 使用内存降低 0.33 G,stack 申请内存降低 2.34 G,优化效果显著。 ","link":"https://greatewei.github.io/post/mei-tu-chang-lian-jie-ping-tai/"},{"title":"长连接平台架构","content":" Go实现IM 长连接平台: 分为四个部分,rev,sender,registor,online(接受者,发送者,注册中心,\b在线列表) im:负责处理业务逻辑 rev负责数据的接收发送 架构设计 \b\b主进程负责接收消息(epoll)将消息放入阻塞队列,主进程开启多个消费者(work协程)读取阻塞队列消息处理,向用户中心获取产品注册的队列(队列1,队列2),将处理后的消息发送到对应队列(消费队列,异常队列)。 im负责消息业务处理 功能 将消息从队列(队列1,队列2)获取后进行业务处理,将需要返回的消息内容发送到接收队列(sender接收队列)。 sender负责数据转发 架构设计 主进程开启多个(消费者work协程)消费task(任务消息),消息内容包含要发送的目的rev信息,调用rpc方法将消息发送到指定rev,开启多个(生产者work协程)订阅接收队列(sender接收队列)中将消息封装成task供(消费者work协程)进行消费。 registor负责注册各种服务 功能 注册中心负责记录长连平台各个服务的信息,以便各个服务相互之间能找到对方。 各个服务的注册信息存放在redis中。 各个服务使用redis的发布订阅模式获取注册中心的所有信息 。 online 在线列表 功能 online模块负责记录成功登陆长连平台的用户信息。主要是用户是再哪个recv登陆的。 ","link":"https://greatewei.github.io/post/go-shi-xian-im/"},{"title":"Swoole实现IM","content":"0X01 swoole_table 共享内存表 在swoole_server->start()之前创建swoole_table对象。并存入全局变量或者类静态变量/对象属性中。 在worker/task进程中获取table对象,并使用 注意点:只有在swoole_server->start()之前创建的table对象才能在子进程中使用 swoole_table构造方法中指定了最大容量,一旦超过此数据容量将无法分配内存导致set操作失败。所以使用swoole_table之前一定要规划好数据容量,实现进程之间的数据共享。 0X02 swoole_websocket_server WebSocket服务器是建立在Http服务器之上的长连接服务器,客户端首先会发送一个Http的请求与服务器进行握手。握手成功后会触发onOpen事件,表示连接已就绪,onOpen函数中可以得到$request对象,包含了Http握手的相关信息,如GET参数、Cookie、Http头信息等。 建立连接后客户端与服务器端就可以双向通信了。 0X02 swoole_process swoole增加了多进程管理模块来替代PHP的pcntl,它相比pcntl的不同点是: swoole_process提供了pcntl没有的进程间通信 swoole_process支持重定向标准输入和输出,在子进程内echo或者读键盘输入可以被重定向为从管道中取数据 子进程可以异步化 0X03 实现IM 创建一个swoole_table共享内存表,用于记录连接的客户端信息 记录: 用户id, 设备id, 设备型号, 登录状态,数据接收池,客户端ip,客户端端口,上一次在线时间。 创建一个websocket_service 启动之前编写一个协议类IM,负责处理业务逻辑。 swoole_process 创建一个进程专门用于处理定时器,websocket_service.addprocess() 加载配置文件 回调函数内需要编写的内容 connect: 客户端连接时触发,\b将用户信息记录到swoole_table中,并且调用IM协议类中的方法,创建一个定时器,等待用户进行登录操作,swoole_timer_after 指定时间后自动关闭当前用户连接(主动关闭用户连接会触发回调函数close),如果用户在指定时间内登录,清除定时器。 handShake: 服务端握手时触发,有需要可以自己进行处理(可选项)。 message: 服务端接收到数据时触发,判断当前接收到是的数据是否完整(数据传输过程发生了拆包,swoole_frame->finish判断是否完整),如果数据不完整将数据存储到内存表中的当前用户的数据接收池,请求结束。否则将当前用户数据接收池数据获取出来与当前数据进行组和,情况数据接收池,获得到完整数据进行业务逻辑处理。调用IM协议类的方法进行相应处理。 close 主动断开客户端连接时触发,清除swoole_table用户相关数据。 start websocket服务启动触发,有需要可以自行修改 managerStart manager进程启动时触发,有需要可以自行修改 workerStart worker进程启动时触发 shutdown 服务关闭时触发,清除远端用户在线列表信息(redis)。 workerError work异常触发,释放work资源,调用使用类的析构函数。 workerStop work停止触发,释放work资源,调用使用类的析构函数。 task task启动时触发 IM协议类 接收消息 解析接收的消息,判断当前消息是否正常,检查内存表中用户是否存在信息。 每次接收到消息重置心跳定时器。 检查用户是否有在线信息,如果有更新在线时间。 客户端需要定时发送ping包 用户登录 用户登录后清除登录定时器,更新本地在线信息,远端在线信息。 ","link":"https://greatewei.github.io/post/swoole-shi-xian-im/"},{"title":"Swoole架构","content":"Swoole:Master/Reactor/Manager/Worker/TaskWorker(Task) Master进程 master进程为主进程,该进程会创建Manager进程和Reactor线程等工作进/线程 swoole的主进程,是个多线程的程序. 主进程内的回调函数: onStart onShutdown onMasterConnect onMasterClose onTimer ... Reactor线程 Reactor线程是真正处理TCP连接,收发数据的线程。 Swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,并由这个线程负责监听此socket。在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端. Manager进程 Manager进程是管理进程,该进程是为了创建管理所有的woker进程和TaskWorker进程,swoole中worker/task进程都是由Manager进程Fork并管理的。(master主进程为多线程进程,不能安全的执行fork操作) 子进程结束运行时,manager进程负责回收此子进程,避免成为僵尸进程。并创建新的子进程 服务器关闭时,manager进程将发送信号给所有子进程,通知子进程关闭服务 服务器reload时,manager进程会逐个关闭/重启子进程 管理进程内的回调函数 onManagerStart onManagerStop Worker进程 worker进程是工作进程,所有的业务逻辑都在该进程中进行,当Reactor线程接收到来自客户端的数据后,会将数据打包通过管道发送给某个Worker进程. Swoole提供了完善的进程管理机制,当Worker进程异常退出,如发生PHP的致命错误、被其他程序误杀,或达到max_request次数之后正常退出。主进程会重新拉起新的Worker进程。 Worker进程内可以像普通的apache+php或者php-fpm中写代码。不需要像Node.js那样写异步回调的代码。 Worker进程内的回调函数 onWorkerStart onWorkerStop onConnect onClose onReceive onTimer onFinish TaskWorker进程 Swoole的业务逻辑部分是同步阻塞运行的,如果遇到一些耗时较大的操作,例如访问数据库、广播消息等,就会影响服务器的响应速度。因此Swoole提供了Task功能,将这些耗时操作放到另外的进程去处理,当前进程继续执行后面的逻辑 task进程必须是同步阻塞的,task进程支持定时器 TaskWorker进程内的回调函数 onTask onWorkerStart 职责功能 Reactor线程 负责维护客户端TCP连接、处理网络IO、处理协议、收发数据 完全是异步非阻塞的模式 全部为C代码,除Start/Shudown事件回调外,不执行任何PHP代码将 TCP客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包 Reactor以多线程的方式运行 Worker进程 接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据生成响应数据并发给 Reactor线程,由Reactor线程发送给TCP客户端 可以是异步非阻塞模式,也可以是同步阻塞模式 Worker以多进程的方式运行 TaskWorker进程 接受由Worker进程通过swoole_server->task/taskwait方法投递的任务 处理任务,并将结果数据返回(swoole_server->finish)给Worker进程 完全是同步阻塞模式 TaskWorker以多进程的方式运行 关系 可以理解为Reactor就是nginx,Worker就是php-fpm。Reactor线程异步并行地处理网络请求,然后再转发给Worker进程中去处理。Reactor和Worker间通过UnixSocket进行通信。在php-fpm的应用中,经常会将一个任务异步投递到Redis等队列中,并在后台启动一些php进程异步地处理这些任务。Swoole提供的TaskWorker是一套更完整的方案,将任务的投递、队列、php任务处理进程管理合为一体。通过底层提供的API可以非常简单地实现异步任务的处理。另外TaskWorker还可以在任务执行完成后,再返回一个结果反馈到Worker。Swoole的Reactor、Worker、TaskWorker之间可以紧密的结合起来,提供更高级的使用方式。一个更通俗的比喻,假设Server就是一个工厂,那Reactor就是销售,接受客户订单。而Worker就是工人,当销售接到订单后,Worker去工作生产出客户要的东西。而TaskWorker可以理解为行政人员,可以帮助Worker干些杂事,让Worker专心工作。 底层会为Worker进程、TaskWorker进程分配一个唯一的ID不同的Worker和TaskWorker进程之间可以通过sendMessage接口进行通信 ","link":"https://greatewei.github.io/post/swoolemasterreactormanagerworkertaskworkertask/"},{"title":"Zookeeper集群 + canal服务简介","content":"\b\b\b zookeeper简介 顾名思义 zookeeper 就是动物园管理员,他是用来管 hadoop(大象)、Hive(蜜蜂)、pig(小 猪)的管理员, Apache Hbase 和 Apache Solr 的分布式集群都用到了 zookeeper;Zookeeper: 是一个分布式的、开源的程序协调服务,是 hadoop 项目下的一个子项目。他提供的主要功 能包括:配置管理、名字服务、分布式锁、集群管理 zookeeper作用 公共配置管理 服务器共用hosts,名字服务 分布式锁 集群管理,竞选策略 本文章,主要讲解搭建内容,理论方面不再阐述。 单机多节点搭建要点 需要修改每个节点存储日志目录(/usr/local/var/run/zookeeper/data/zoo-1/),每个节点需要创建myid。 创建多个节点copy多分zoo.cfg文件,修改配置内容,主要配置其他节点的地址,与客户端端口。 每个节点依次启动 服务端:zkServer start zoo-1.cfg 客户端可连接任意端口:zkCli -server ip:port(客户端端口)\b canal简介 canal是阿里巴巴开源的mysql数据库binlog的增量订阅&消费组件。 ","link":"https://greatewei.github.io/post/zookeeper-ji-qun-da-jian/"},{"title":"Http ","content":"基础概念 uri uri包好url 和urn 请求和响应报文 请求报文 响应报文 GET 获取资源 当前网络请求中,绝大部分使用的是GET方法。 HEAD 获取报文首部 和GET方法类似,但是不返回报文实体主体部分。 主要用于确认URL的有效性以及资源更新的日期时间等。 POST 传输实体主体 POST主要用来传输数据,而GET主要用来获取资源。 PUT 上传文件 由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。 PATCH 对资源进行部分修改 PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。 \b\b\bDELETE 删除文件 与PUT功能相反,并且同样不带验证机制。 OPTIONS 查询支持的方法 查询指定URL能够支持的方法 CONNECT 要求在与代理服务器通信时简历隧道 使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 CONNECT www.example.com:443 HTTP/1.1 TRACE 服务器会将通信路径返回给客户端。 发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。 通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。 HTTP状态码 服务器返回的 响应报文 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。 1xx信息 100 Continue:表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 2xx成功 200 ok 204 No Content :请求已经成功处理,但是响应报文不包含实体的主题部分。 一般只需要从客户端往服务器发送信息,而不逊要返回数据时使用。 206 Partial Content:表示客户端进行了范围请求,响应报文报文包含 Content-Range 指定范围的实体内容。 3xx重定向 301 Moved Permanmently : 永久重定向 302 Found : 临时性重定向 303 See Other: 和302有着相同的功能,但是303明确客户端应该采用GET方法获取资源 304 Not Modified: 如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 307 Temporary Redirect: 临时重定向, 与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 4xx 客户端错误 400 Bad Request :请求报文中存在语法错误。 401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 403 Forbidden :请求被拒绝。 404 Not Found 5xx 服务端错误 500 Internal Server Error :服务器正在执行请求时发生错误。 503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 HTTP首部 实体首部 请求首部 响应首部 具体应用 连接管理 短连接与长连接 当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问的 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。 长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 Connection : close; 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 Connection : Keep-Alive。 流水线 默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。 流水线是在同一条长连接上连续发出请求,而不用等待响应返回,这样可以减少延迟。 cookie HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。 用途 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) 个性化设置(如用户自定义设置、主题等) 浏览器行为跟踪(如跟踪分析用户行为等) 创建过程 服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。 HTTP/1.0 200 OK Content-type: text/html Set-Cookie: yummy_cookie=choco Set-Cookie: tasty_cookie=strawberry [page content] 客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。 GET /sample_page.html HTTP/1.1 Host: www.example.org Cookie: yummy_cookie=choco; tasty_cookie=strawberry 分类 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。 持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie。 Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; 作用域 Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。 Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配: /docs /docs/Web/ /docs/Web/HTTP JavaScript 浏览器通过 document.cookie 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。 document.cookie = "yummy_cookie=choco"; document.cookie = "tasty_cookie=strawberry"; console.log(document.cookie); HttpOnly 标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 document.cookie API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。 Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly Secure 标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。 Session 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 使用 Session 维护用户登录状态的过程如下: 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 浏览器禁用Cookie 此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。 Cookie 与 Session 选择 Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选 Session; Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。 缓存 优点 缓解服务器压力; 降低客户端获取资源的延迟:缓存通常位于内存中,读取缓存的速度更快。并且缓存服务器在地理位置上也有可能比源服务器来得近,例如浏览器缓存。 实现方法 让代理服务器进行缓存; 让客户端浏览器进行缓存。 Cache-Control HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。 禁止进行缓存 no-store 指令规定不能对请求或响应的任何一部分进行缓存。 Cache-Control: no-store 强制确认缓存 no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效时才能使用该缓存对客户端的请求进行响应。 Cache-Control: no-cache 私有缓存和公共缓存 private 指令规定了将资源作为私有缓存,只能被单独用户使用,一般存储在用户浏览器中。 Cache-Control: private public 指令规定了将资源作为公共缓存,可以被多个用户使用,一般存储在代理服务器中。 Cache-Control: public 缓存过期机制 max-age 指令出现在请求报文,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。 max-age 指令出现在响应报文,表示缓存资源在缓存服务器中保存的时间。 Cache-Control: max-age=31536000 Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。 Expires: Wed, 04 Jul 2012 08:26:05 GMT 在 HTTP/1.1 中,会优先处理 max-age 指令; 在 HTTP/1.0 中,max-age 指令会被忽略掉。 缓存验真 需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 http://www.google.com/ 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。 ETag: "82e22293907ce725faf67773957acd12" 可以将缓存资源的 ETag 值放入 If-None-Match 首部,服务器收到该请求后,判断缓存资源的 ETag 值和资源的最新 ETag 值是否一致,如果一致则表示缓存资源有效,返回 304 Not Modified。 If-None-Match: "82e22293907ce725faf67773957acd12" Last-Modified 首部字段也可以用于缓存验证,它包含在源服务器发送的响应报文中,指示源服务器对资源的最后修改时间。但是它是一种弱校验器,因为只能精确到一秒,所以它通常作为 ETag 的备用方案。如果响应首部字段里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为 200 OK。如果请求的资源从那时起未经修改,那么返回一个不带有实体主体的 304 Not Modified 响应报文。 Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT 内部协商 通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 ","link":"https://greatewei.github.io/post/http/"},{"title":"Crontab ","content":"问题: crontab设置定时任务时,* * * * * php 1.php, 找不到php环境变量,导致任务执行失败。 解决方法:输入完整php路径。 * * * * * /usr/local/php 1.php ","link":"https://greatewei.github.io/post/cr/"},{"title":"Linux ","content":"sync 为了加快对磁盘文件的读写速度,位于内存中的文件数据不会立即同步到磁盘,因此关机之前需要先进行 sync 同步操作。 PATH 可以在环境变量 PATH 中声明可执行文件的路径,路径之间用 : 分隔。 磁盘的文件名 Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘接口类型进行命名,常见磁盘的文件名如下: IDE 磁盘:/dev/hd[a-d] SATA/SCSI/SAS 磁盘:/dev/sd[a-p] 其中文件名后面的序号的确定与系统检测到磁盘的顺序有关,而与磁盘所插入的插槽位置无关。 分区 磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。 MBR MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。 分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区来记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。 文件系统 分区与文件系统 对分区进行格式化是为了在分区上建立文件系统,一个分区通常只能格式化为一个文件系统,但是磁盘阵列等技术可以将一个分区格式化为多个文件系统。 组成 最主要的几个组成部分如下: innode:一个文件占用一个inode,记录文件的属性,同时记录此文件的内容所在block编号。 block:记录文件内容,文件太大时,会占用多个block. 除此之外还包括 superblock:记录文件系统的整体信息,包括inode和block的总量,剩余量,使用量,以及文件系统的格式与相关信息等; block bitmap:记录block是否被使用的位图。 文件读取 对于EXT2文件系统,当要读取一个文件的内容时,先在inode中查找文件内容所在的所有block,然后把所有的内容读出来。 而对于FAT文件系统,它没有inode,每个block中存储着下一个block的编号。 磁盘碎片 \b☞一个文件内容所在的block过于分散,导致磁盘磁头移动距离过大,从而降低磁盘的读写性能。 block 在Ext2文件系统中所支持的block大小有1k,2k及4k三种,不同的大小限制了单个文件和文件系统的最大大小。 一个block只能被一个文件所使用,未使用的部分直接浪费了。因此如果需要存储大量的小文件,那么最好选用比较小的block。 inode inode具体包含以下信息: 权限、拥有者/群组、容量、建立或状态改变时间、最近读取时间、最近修改时间、定义文件特性的旗标、改文件真正内容的指向。 inode具有以下特点: 每个inode大小均固定为128bytes (新的ext4 与 xfs可设定到256bytes) \b每个文件都仅会占用一个inode inode 中记录了文件内容所在的 block 编号,但是每个 block 非常小,一个大文件随便都需要几十万的 block。而一个 inode 大小有限,无法直接引用这么多 block 编号。因此引入了间接、双间接、三间接引用。间接引用让 inode 记录的引用 block 块记录引用信息。 目录 建立一个目录时,会分配一个inode与至少一个block,block记录的内容是目录下所有文件的inode编号以及文件名。 可以看到文件的inode本身不记录文件名,文件名记录在目录中,因此新增文件,删除文件,更改文件名这些操作与目录的写权限有关。 日志 如果突然断电,那么文件系统会发生错误,例如断电前只修改了block bitmap,而还没有将数据真正写入block中。ext3/ext4文件系统引入了日志功能,可以利用日志来修复文件系统。 挂载 挂载利用目录作为文件系统的进入点,也就是说,进入目录之后就可以读取文件系统的数据。 目录配置 为了使不同linux发行版本的目录结构保持一致性,FHS规定了Linux的目录结构。最基础的三个目录如下: /root /usr /var 文件 文件属性 用户分为三种:文件拥有者、群组以及其它人,对不同的用户有不同的文件权限。 使用 ls 查看一个文件时,会显示一个文件的信息,例如 drwxr-xr-x 3 root root 17 May 6 00:14 .config,对这个信息的解释如下: drwxr-xr-x:文件类型以及权限,第 1 位为文件类型字段,后 9 位为文件权限字段 3:链接数 root:文件拥有者 root:所属群组 17:文件大小 May 6 00:14:文件最后被修改的时间 .config:文件名 常见的文件类型及其含义有: d:目录 -:文件 l:链接文件 9 位的文件权限字段中,每 3 个为一组,共 3 组,每一组分别代表对文件拥有者、所属群组以及其它人的文件权限。一组权限中的 3 位分别为 r、w、x 权限,表示可读、可写、可执行。 文件时间有以下三种: modification time (mtime):文件的内容更新就会更新; status time (ctime):文件的状态(权限、属性)更新就会更新; access time (atime):读取文件时就会更新。 文件与目录的基本操作 .... 链接 实体链接 在目录下创建一个条目,记录着文件名与 inode 编号,这个 inode 就是源文件的 inode。 删除任意一个条目,文件还是存在,只要引用数量不为 0。 有以下限制:不能跨越文件系统、不能对目录进行链接。 ln /etc/crontab . ll -i /etc/crontab crontab 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 crontab 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab 符号链接 符号链接文件保存着源文件所在的绝对路径,在读取时会定位到源文件上,可以理解为 Windows 的快捷方式。 当源文件被删除了,链接文件就打不开了。 因为记录的是路径,所以可以为目录建立符号链接。 ll -i /etc/crontab /root/crontab2 34474855 -rw-r--r--. 2 root root 451 Jun 10 2014 /etc/crontab 53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab 进程管理 查看进程: ps -l 查看自己的进程 ps aux 查看系统所有进程 ps aux | grep php 查看特定进程 pstree -A 查看所有进程树 top 实时显示进程信息 top -d 2 两秒刷新一次 netstat 查看占用端口的进程 netstat -anp | grep port 进程状态 SIGCHLD 当一个子进程改变了它的状态时(停止运行,继续运行或者退出),有两件事会发生在父进程中: 得到 SIGCHLD 信号; waitpid() 或者 wait() 调用会返回。 其中子进程发送的 SIGCHLD 信号包含了子进程的信息,比如进程 ID、进程状态、进程使用 CPU 的时间等。 在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。 wait() 父进程调用 wait() 会一直阻塞,直到收到一个子进程退出的 SIGCHLD 信号,之后 wait() 函数会销毁子进程并返回。 如果成功,返回被收集的子进程的进程 ID;如果调用进程没有子进程,调用就会失败,此时返回 -1,同时 errno 被置为 ECHILD。 参数 status 用来保存被收集的子进程退出时的一些状态,如果对这个子进程是如何死掉的毫不在意,只想把这个子进程消灭掉,可以设置这个参数为 NULL。 waitpid() 作用和 wait() 完全相同,但是多了两个可由用户控制的参数 pid 和 options。 pid 参数指示一个子进程的 ID,表示只关心这个子进程退出的 SIGCHLD 信号。如果 pid=-1 时,那么和 wait() 作用相同,都是关心所有子进程退出的 SIGCHLD 信号。 options 参数主要有 WNOHANG 和 WUNTRACED 两个选项,WNOHANG 可以使 waitpid() 调用变成非阻塞的,也就是说它会立即返回,父进程可以继续执行其它任务。 孤儿进程 一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。 孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。 由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。 僵尸进程 一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。 僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。 系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。 要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 进程所收养,这样 init 进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。 系统诊断命令 free 需要学会查看系统内存 未使用的内容 = free + buffers + cached -m 单位转换M -g 单位转换为G vmstat 分析系统问题 r (running) 列表运行和等待的进程数量,如果长期大于1说明cpu不足,需要cpu。 b 列表等待资源的进程数,如等待I/O,或者内存交换等。 cpu使用状态 us 表示用户进程cpu时间占比,如果长期大于50%,需要考虑优化用户程序。 sy 显示了内核进程cpu时间占比,如果us + sy 大于80%说明可能存在CPU不足。 wa 显示了IO等待所占用的CPU时间的百分比,说明IO等待严重,磁盘大量随机访问造成的,也可能是磁盘或者磁盘访问控制器带宽瓶颈造成的 id cpu处于空闲的百分比 system显示采集间隔中观察到的每秒设备中断数。 in 列表在某一时间间隔中观察到的每秒设备中断数 cs 表示每秒产生的上下文切换次数 memory 内存情况 swpd 切换到内存交换区的内存数量,如果swpd的值不为0,或者比较大,只要si,so的长期为0,系统性能还是正常 free 当前的空闲页面列表中内存数量(k表示) buff 作为buffer cache的内存数量,一般对块设备的读写才需要缓冲。 cache 作为page cache的内存数量,一般作为文件系统的cache,如果cache较大,说明用到cache的文件较多,如果此时IO中bi比较小,说明文件系统效率比较好。 swap si 由内存进入内存交换区数量 so 有内存交换区进入内存数量 IO bi 从块设备读入数据的总量(读磁盘)(每秒kb) bo 块设备写入数据的总量(写磁盘)(每秒kb) 这里我们设置的bi+bo参考值为1000,如果超过1000,而且wa值较大应该考虑均衡磁盘负载,可以结合iostat输出来分析。 dd 模拟读写磁盘操作 top 系统 watch more /proc/net/dev 用于定位丢包,错报情况,以便看望了瓶颈 netstat 查看端口 ping traceroute ip 路由追踪 dig 查看域名解析 dmesg 查看系统日志 df 查看磁盘剩余空间 du 查看磁盘使用空间 ps 查看进程 tcpdump 抓包 traceroute 路由追踪 ","link":"https://greatewei.github.io/post/linux/"},{"title":"计算机操作系统-链接","content":"编译系统 以下是一个hello.c程序 在Unix系统上,由于编译器把源文件转换为目标文件。 这个过程大致如下: 预处理阶段:处理以 # 开头的预处理命令; 编译阶段:翻译成汇编文件; 汇编阶段:将汇编文件翻译成可重定位目标文件; 链接阶段:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。 静态链接 静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务: 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。 动态链接 静态库有以下两个问题: 当静态库更新时那么整个程序都要重新进行链接; 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。 共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点: 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中; 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。 ","link":"https://greatewei.github.io/post/ji-suan-ji-cao-zuo-xi-tong-lian-jie/"},{"title":"计算机操作系统-设备管理","content":"磁盘结构 盘面(Platter):一个磁盘有多个盘面; 磁道(Track):盘面上的圆形带状区域,一个盘面可以有多个磁道; 扇区(Track Sector):磁道上的一个弧段,一个磁道可以有多个扇区,它是最小的物理储存单位,目前主要有 512 bytes 与 4 K 两种大小; 磁头(Head):与盘面非常接近,能够将盘面上的磁场转换为电信号(读),或者将电信号转换为盘面的磁场(写); 制动手臂(Actuator arm):用于在磁道之间移动磁头; 主轴(Spindle):使整个盘面转动。 磁盘调度算法 读写一个磁盘块的时间的影响因素有: 旋转时间(主轴转动盘面,使得磁头移动到适当的扇区上) 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上) 实际的数据传输时间 其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。 先来先服务 按照磁盘请求的顺序进行调度。 优点是公平和简单。缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。 最短寻道时间优先 优先调度与当前磁头所在磁道距离最近的磁道。 虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两端的磁道请求更容易出现饥饿现象。 电梯算法 电梯总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。 电梯算法(扫描算法)和电梯的运行过程类似,总是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,然后改变方向。 因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了 SSTF 的饥饿问题。 ","link":"https://greatewei.github.io/post/ji-suan-ji-cao-zuo-xi-tong-she-bei-guan-li/"},{"title":"计算机操作系统-内存管理","content":"虚拟内存 虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存,从而让程序获得更多的可用内存。 为了更好的管理内存,操作系统将内存抽象成地址空间,每一个程序拥有自己的地址空间,这个地址空间被分割成多个块。每一块称为一页,这些页被映射到物理内存,但不需要所有页都必须在物理内存中。当程序引用到不再物理内存的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。 从上面描述中可以看出,虚拟内存运行程序不用将地址空间的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序称为可能。例如有一台计算机可以产生16位地址,那么一个程序的地址空间范围是0~64k,该计算机只有32KB的物理内存,虚拟内存技术允许该计算机运行一个64k大小的程序。 分页系统地址映射 内存管理单元(MMU)管理着地址空间和物理内存的转换,其中的页表(Page table)存储着页(程序地址空间)和页框(物理内存空间)的映射表。 一个虚拟地址分为两部分,一部分存储页面号,一部分存储偏移量。 页面置换算法 在程序运行过程中,如果要访问的页面不存在内存中,就发生缺页中断从而将该页调入内存中,此时如果内存已无空闲空间,系统必须从内存调出一个页面到磁盘对换区中腾出空间。 页面置换算法和淘汰策略类似,可以将内存看成磁盘的缓存。在缓存系统中,缓存的大小有限,当有新的缓存到达时,需要淘汰一部分已经存在的缓存,这样才有空间存放新的缓存数据。 页面置换算法的主要目的是使置换频率最低,也可以说缺页率最低。 最佳 所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。 是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。 举例: 开始运行时,先将7,0,1三个页面装入内存。当程序要访问页面2时,产生缺页中断,会将页面7换出,应为页面7再次被访问的时间最长。 最近最久未使用 虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。 为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。 因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。 最近未使用 每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类: R=0,M=0 R=0,M=1 R=1,M=0 R=1,M=1 当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。 NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。 先进先出 选择换出的页面是最先进入的页面。 该算法会将那些经常被访问的页面也被换出,从而使缺页率升高。 第二次机会算法 \bFIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改: 当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。 时钟 第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。 分段 虚拟内存采用的是分页技术,也就是将地址空间划分成固定大小的页,每一页再与内存进行映射。 下图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。 分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。 段页式 程序的地址空间划分成多个拥有独立地址空间的段,每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护,又拥有分页系统的虚拟内存功能。 分页与分段的比较 对程序员的透明性:分页透明,但是分段需要程序员显式划分每个段。 地址空间的维度:分页是一维地址空间,分段是二维的。 大小是否可以改变:页的大小不可变,段的大小可以动态改变。 出现的原因:分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。 ","link":"https://greatewei.github.io/post/ji-suan-ji-cao-zuo-xi-tong-nei-cun-guan-li/"},{"title":"计算机操作系统-死锁","content":"产生死锁的必要条件 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。 占有和等待:已经得到了某个资源的进程可以再请求新的资源。 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。 处理方法 主要有以下四种方法: 鸵鸟策略 死锁检测与死锁恢复 死锁预防 死锁避免 鸵鸟策略 把头埋在沙子里,假装根本没发生问题。 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。 当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。 大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。 死锁检测与死锁恢复 不试图阻止死锁,而是当检测到死锁发送时,采取措施进行恢复。 每种类型一个资源的死锁检测 上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。 图 a 可以抽取出环,如图 b,它满足了环路等待条件,因此会发生死锁。 每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。 死锁恢复 利用抢占恢复 利用回滚恢复 通过杀死进程恢复 死锁预防 在程序运行之前预防发生死锁。 破坏互斥条件 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。 破坏占有和等待条件 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。 破坏不可抢占条件 破坏环路等待 给资源统一编号,进程只能按编号顺序来请求资源。 死锁避免 在程序运行时避免发送死锁。 ","link":"https://greatewei.github.io/post/ji-suan-ji-cao-zuo-xi-tong-si-suo/"},{"title":"RabbitMQ 简介","content":" Beanstalkd \b\b\bBeantalkd 一个轻量级消息中间件,他的最大特点是将自己定位为基于管道 (tube) 和任务 (job) 的工作队列 (work-queue) 有以下特点: 任务优先级 (priority): 任务 (job) 可以有 0~2^32 个优先级, 0 代表最高优先级。 beanstalkd 采用最大最小堆 (Min-max heap) 处理任务优先级排序, 任何时刻调用 reserve 命令的消费者总是能拿到当前优先级最高的任务, 时间复杂度为 O(logn)。 最大堆与最小堆 堆树是一种二叉树,堆树中某个节点的值总是不大于或不小于其孩子节点的值。 当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆。 当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆。 延时任务 (delay): 有两种方式可以延时执行任务 (job): 生产者发布任务时指定延时,或者当任务处理完毕后, 消费者再次将任务放入队列延时执行 (RELEASE with )。 任务超时重发 (time-to-run): Beanstalkd 把任务返回给消费者以后:消费者必须在预设的 TTR (time-to-run) 时间内发送 delete / release/ bury 改变任务状态;否则 Beanstalkd 会认为消息处理失败,然后把任务交给另外的消费者节点执行。如果消费者预计在 TTR (time-to-run) 时间内无法完成任务, 也可以发送 touch 命令, 它的作用是让 Beanstalkd 从系统时间重新计算 TTR (time-to-run)。 任务预留 (buried): 如果任务因为某些原因无法执行, 消费者可以把任务置为 buried 状态让 Beanstalkd 保留这些任务。管理员可以通过 peek buried 命令查询被保留的任务,并且进行人工干预。简单的, kick 能够一次性把 n 条被保留的任务踢回队列。 消息持久化 通过日志实现消息的持久化。 速度优势 Beanstalkd协议基于tcp上。客户端连接服务器并发送指令和数据,然后等待响应并关闭连接。对于每个连接,服务器按照接收命令的序列依次处理并响应。 场景优势 延时系统,比如延迟20分钟发送短信,******,在投放的时候就设定一定的延迟时间值,让任务在延迟时间到了之后进入ready队列,等待worker预订处理。 轮询系统,如下图,一个被投放的任务,在延迟时间过后需要再检查一遍状态,如果不符合,继续释放(release with delay)为延迟投放状态(DELAYED),直到时间过期之后,再次进入ready队列,被worker预订,进行一些逻辑判断,比如微信银行卡退款是否成功,如果成功,删除该任务,如果没成功,继续释放(release with delay)为延迟投放状态。 缺陷 Beanstalk单点部署,不支持集群,当服务发送故障造成服务不可用,任务积压问题,不能够灵活的设置消息持久化。 RabbitMQ Rabbitmq特性 可靠性:持久化存储、ACK消息确认、发布confirm、事务支持。 灵活的路由:交换机功能。交换机类型:direct,topic,headers,fanout。 镜像,master-slave 多协议支持,集群节点 多语言客户端支持:java、c#、ruby、Python、php、c、scale、nodejs、go、erlang… 管理界面功能丰富、命令行rabbitmqctl、RPC远程调度 AMQP高级消息协议 一个AMQP服务器类似于邮件服务器,exchange类似于消息传输代理(email里的概念),message queue类似于邮箱。Binding定义了每一个传输代理中的消息路由表,发布者将消息发给特定的传输代理,然后传输代理将这些消息路由到邮箱中,消费者从这些邮箱中取出消息。 AMQP术语 Channel(信道): 在AMQP模型中,我们不需要通过建立太多的TCP连接来实现。假如针对每一个AMQP连接都建立一个TCP连接的话,会占用大量的系统资源。对此,AMQP提供了通道(channel)机制。即,共享一个TCP连接,可创建多个通道。 ​ 在多线程/进程的应用程序中正确做法是,对于每一个线程/进程,应分别建立一个通道,而不是多个线程/进程之间去共享一个通道。 Exchange(交换器):用于接受、分配消息;可以有好几种模式、相当于邮箱 Queue(队列):用于存储生产者的消息; RoutingKey(路由键):用于把生成者的数据分配到交换器上; BindingKey(绑定键):用于把交换器的消息绑定到队列上; Broker(消息代理):消息代理会接收来自生产者(publishers/producers)生产的消息,并将它们路由(route,可以理解成按指定规则转发)给相应的消费者(consumers)手中。 VHOST(虚拟主机):为了在一个单独的代理上实现多个隔离的环境(用户、用户组、交换机、队列 等),AMQP 提供了一个虚拟主机(virtual hosts - vhosts)的概念。这跟 Web servers 虚拟主机概念非常相似,这为 AMQP 实体提供了完全隔离的环境。当连接被建立的时候,AMQP 客户端来指定使用哪个虚拟主机。 模拟器 hello world 生产者生产消息,消费者消费消息,消息存储在队列中。 生产者: 消费者: 工作队列 一个工作队列,该队列将用于在多个工作人员之间分配耗时的任务,默认情况下,RabbitMQ将按顺序将每个消费者都会收到相同数量的消息,采用循环发送的方式。 消息确认 执行任务可能需要几秒钟。您可能想知道,如果其中一个消费者开始一项漫长的任务并仅部分完成而死掉,会发生什么情况。使用我们当前的代码,RabbitMQ一旦向消费者发送了一条消息,便立即将其标记为删除。在这种情况下,如果您杀死一个工人,我们将丢失正在处理的消息。我们还将丢失所有发送给该特定工作人员但尚未处理的消息。但是我们不想丢失任何任务。如果一个工人死亡,我们希望将任务交付给另一个工人。 为了确保消息永不丢失,RabbitMQ支持 消息确认。消费者发送回一个确认(acknowledgement),以告知RabbitMQ已经接收,处理了特定的消息,并且RabbitMQ可以自由删除它。 如果使用者死了(其通道已关闭,连接已关闭或TCP连接丢失)而没有发送确认,RabbitMQ将了解消息未完全处理,并将重新排队。如果同时有其他消费者在线,它将很快将其重新分发给另一位消费者。这样,即使工人偶尔死亡,您也可以确保不会丢失任何消息。没有任何消息超时;消费者死亡时,RabbitMQ将重新传递消息。 如果当我们的消费者大量的消息内容忘记ack后,会照成rabbitMq将会消耗越来越多的内存,因为它无法释放任何未确认的消息。 rabbitmqctl list_queues name messages_ready messages_unacknowledged 以上命令可以查询队列中的为确认消息数量。 消息持久性 \b以上方法,已经能够解决消费者死亡,任务也不会丢失,但是,如果RabbitMQ服务器停止,我们的任务仍然会丢失,要确保消息不会丢失,需要做两件事,将队列与消息都是标记为持久性。 将消息标记持久化并不能完全保证不会丢失消息,应为RabbitMQ接收消息但尚未保存至磁盘中时还是有很短的时间,另外,RabbitMQ不会对每条消息都执行fsync(2),可能只是保存到缓存中,而没有真正写入磁盘,持久性保证并不强,但是对于简单的任务队列而已,已经足够了,如果需要更强有力的保证,则可以使用发布者确认。 公平派遣 您可能已经注意到,调度仍然无法完全按照我们的要求进行。例如,在有两名工人的情况下,当所有奇怪的消息都很重,甚至消息都很轻时,一位工人将一直忙碌而另一位工人将几乎不做任何工作。好吧,RabbitMQ对此一无所知,并且仍将平均分配消息。发生这种情况是因为RabbitMQ在消息进入队列时 为了克服这一点,我们可以将basic_qos方法与 prefetch_count = 1设置一起使用。这告诉RabbitMQ一次不要给工人一个以上的消息。换句话说,在处理并确认上一条消息之前,不要将新消息发送给工作人员。而是将其分派给尚不繁忙的下一个工作人员。 发布订阅 一次发布,订阅的队列都能接收到消息进行处理,将繁琐的业务逻辑进行解耦。 \b\b每个订阅者就是一个队列,虽然这场景也可以通过一个队列进行所有业务操作,但是一旦一个复杂的业务发生故障,容易服务不可用任务积压,如果能够将业务拆分的更加小,每个队列处理自己的业务逻辑,能够很好解决这一问题,采用发布订阅模式,发布者不需要关系哪些队列关注这个任务,只要订阅了,自动回接收到任务。 有几种交换类型:direct,topic,headers,fanout。 生产者: 消费者: 路由 在发布订阅的基础上,有些队列并不需要接收所有消息,而是只对自己感兴趣的内容进行订阅,这就是路由的功能,生成消息时可以添加routing-key进行绑定,发送到指定队列。 生产者: 消费者: 主题 \b\b路由功能虽然解决了,订阅者只接收自己订阅的内容,但是如果用户可能想要接收到,某一类相关的消息,就需要同topic功能, 主题交流不具有任意routing-key,routing-key必须是单词列表以"."分隔,单词可以使用"*"通配符表示,"#"绑定表示接收所有消息,和直接订阅效果一样。 生产者: 消费者: RPC 有关RPC的说明 尽管RPC是计算中非常普遍的模式,但它经常受到批评。当程序员不知道函数调用是本地的还是缓慢的RPC时,就会出现问题。这样的混乱会导致系统变幻莫测,并给调试增加了不必要的复杂性。滥用RPC可能会导致无法维护的意大利面条代码,而不是简化软件。 牢记这一点,请考虑以下建议: 确保明显的是哪个函数调用是本地的,哪个是远程的。 记录您的系统。明确组件之间的依赖关系。 处理错误案例。RPC服务器长时间关闭后,客户端应如何反应? 如有疑问,请避免使用RPC。如果可以的话,应该使用异步管道-代替类似RPC的阻塞,将结果异步推送到下一个计算阶段。 客户端启动时,它将创建一个匿名排他回调队列。 对于RPC请求,客户端发送一条消息,该消息具有两个属性: reply_to(设置为回调队列)和correlation_id(设置为每个请求的唯一值)。 该请求被发送到rpc_queue队列。 RPC工作程序(又名:服务器)正在等待该队列上的请求。出现请求时,它将使用reply_to字段中的队列来完成工作,并将消息和结果发送回客户端。 客户端等待回调队列上的数据。出现消息时,它将检查correlation_id属性。如果它与请求中的值匹配,则将响应返回给应用程序。 客户端将请求发送到rpc_queue队列,需要传递请求唯一id,请求的回调队列。 rpc_service服务端,消费rpc_queue队列,接收到消息后进行逻辑处理,再将数据放入回调队列中。 rpc_client客户端,需要生产消息到rpc_queue,同时消费回调队列的结果correlation_id相同时返回结果。 集群 RabbitMQ始终记录的四种类型的内部元数据: 队列元数据 - 队列名称和他们的属性 交换器元数据 - 交换器名称、类型和属性 绑定元数据 - 一张简单的表格展示了如何将消息路由到队列 vhost元数据 - 为vhost内的队列、交换器、绑定提供命名空间和安全属性。 单机多节点部署 RABBITMQ_NODE_PORT:指定rmq端口 RABBITMQ_NODENAME:指定节点名称与域名 rabbitmq-server -detached 后台运行 RABBITMQ_SERVER_START_ARGS:启动服务参数 -rabbitmq_management listener [{port,15673}] 管理后台监听端口 -rabbitmq_stomp tcp_listeners [61614] rabbitmq_stomp tcp服务使用端口 -rabbitmq_mqtt tcp_listeners [1884]rabbitmq_mqtt tcp服务使用端口 第一个节点: 命令:RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit@localhost rabbitmq-server -detached http://localhost:15674/#/ 地址能够进入对应节点管理后台。 第二个节点: 注意:在单机部署多节点的时候,命令会有所不同,需要添加一些参数修改对应服务使用的端口,否则会出现端口冲突,节点无法启动。 RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}] -rabbitmq_stomp tcp_listeners [61614] -rabbitmq_mqtt tcp_listeners [1884]" RABBITMQ_NODENAME=rabbit2@localhost rabbitmq-server -detached 第三个节点: RABBITMQ_NODE_PORT=5674 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15674}] -rabbitmq_stomp tcp_listeners [61615] -rabbitmq_mqtt tcp_listeners [1885]" RABBITMQ_NODENAME=rabbit2@localhost rabbitmq-server -detached 添加节点步骤: rabbitmqctl -n rabbit1@localhost stop_app //暂停节点服务 rabbitmqctl -n rabbit1@localhost reset //节点重置,将节点所属的cluster都删除。 rabbitmqctl -n rabbit1@localhost join_cluster rabbit@localhost //将rabbit1节点加入集群 默认是disk 磁盘节点模式 --ram 可以指定为内存节点模式 rabbitmqctl -n rabbit1@localhost start_app //启动节点 rabbitmqctl cluster_status -n rabbit@localhost //查看集群情况 rabbit1只需要加入这个集群中任意一台节点就可以加入集群。 删除节点步骤: 方法一: rabbitmqctl -n rabbit1@localhost stop_app //暂停节点服务 rabbitmqctl -n rabbit1@localhost reset //节点重置,将节点所属的cluster都删除。 方法二: rabbitmqctl -n rabbit1@localhost stop_app //暂停节点服务 rabbitmqctl forget_cluster_node rabbit1@localhost //集群中移除节点 内存节点与磁盘节点的区别 网上说的云里雾里的,这边总结一下,只要队列和消息指定持久化,都会落地到磁盘中,内存节点和磁盘节点的区别就是将元数据放在了内存还是硬盘,仅此而已,当在集群中声明队列、交换器和绑定 ,这些操作会同步元数据到所有节点,所以一个集群至少要有一个磁盘节点来同步元数据。 元数据必须至少保存在一个硬盘上,内存节点重启会去磁盘节点下载当前集群元数据拷贝,磁盘节点全挂了,那么集群就无法创建新的东西了,但是还能继续使用已有的东西。 默认情况下,队列只会保存在一个节点上,其他只是保存元数据,当然消息也会投递到这个队列所在的机器上 所以我们才有了创建镜像队列的需求,镜像队列则需要队列适配了策略,当一个节点挂掉后,其他节点都会有这个节点的镜像队列,选择其中一个节点作为新的队列master。 rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all", "ha-sync-mode":"automatic"}' 将所有队列都是设置为镜像队列并且自动同步,是否需要自动同步可以根据自己需求进行设置, 也可以设置需要镜像同步的节点数,个人感觉一个master节点一个mirror节点就足够了。 负载均衡 HAProxy 提供高可用性、负载均衡以及基于 TCP 和 HTTP 应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。 配置项: haproxy -f /usr/local/haproxy/conf/haproxy.cnf 镜像队列 \b交换器和绑定始终可视为在所有节点上,队列可以选择性的跨多个节点进行镜像。每个镜像队列由一个master和一个或者多个mirrors组成,主节点位于一个通常称为master的节点,每个队列都有自己的主节点。 延迟队列 通过死信队列与消息的过期时间实现延时队列。 生产者: 消费者: 死信队列 \b一个消息在满足如下条件下,会进死信交换机,记住这里是交换机而不是队列,一个交换机可以对应很多队列。 2.1 消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。 2.2 上面的消息的TTL到了,消息过期了。 2.3 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。 死信交换机就是普通的交换机,只是因为我们把过期的消息扔进去,所以叫死信交换机,并不是说死信交换机是某种特定的交换机 优先级队列 发布消息时可以设置优先级。 使用插件的延时队列 生产者: 消费者: 思维脑图 脑图 ","link":"https://greatewei.github.io/post/rabbitmq-ji-zhu-diao-yan/"},{"title":"计算机操作系统-进程管理","content":" 进程与线程 进程是资源分配的基本单位。 进程控制块(PCB)创建进程,撤销进程的操作都是由PCB进行控制。 下图显示了4个程序创建了4个进程,这4个进程能够并发的执行。 线程是独立调度的基本单位。 一个进程里可以有多个线程,他们共享资源,就像QQ与浏览器属于两个进程,但是浏览器进程中的包含事件响应线程,渲染线程等线程。 区别 进程拥有资源,但是线程不拥有资源,但是线程可以访问进程的资源,统一进程中线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程的线程中会引起进程切换,进程的创建和销毁都需要系统进行分配和回收资源,如内存空间,I/O设备等,所付出的开销远大于线程的创建和销毁,线程之间的通信可以通过读取同一进程的数据进行通信,但是进程通信需要借助IPC。 进程状态的切换 就绪状态(ready):等待被调度 运行状态(running) 阻塞状态:等待资源 只有就绪态和运行态可以相互转换,其他都是单向转换。就绪状态的进程通过调度算法从而获得cpu时间,转为运行状态,而运行状态的进程,在分配给它的CPU时间片用完之后就会转为就绪状态,等待下一次调度。 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括CPU时间,缺少CPU时间会从运行状态转换为就绪状态。 进程调度算法 不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。 批处理系统 批处理系统里面没有太多的用户操作,在该系统中,调度算法是保证吞吐量和周转时间。 先来先服务(FCFS) 非抢占式的调度算法,安装请求的顺序进行调度,有利于长作业,不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。 短作业优先(SJF) 非抢占式的调度算法,按估计运行时间最短的顺序进行调度,长作业可能饿死,处于一直等待短作业执行完毕状态。 最短剩余时间优先(SRTN) 最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度,当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较,如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。 交互式系统 交互式系统有大量的用户交换操作,在该系统中的调度算法的目标是快速地进行响应。 时间片轮转 将所有就绪进程按FCFS的原则排成一个队列,每次调度时,把CPU时间分配给对首进程,该进程可以执行一个时间片,当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送到就绪队列的末尾,同时继续把CPU时间分配给队首的进程。 时间片轮转算法的效率和时间片大小有很大关系: 因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。 而如果时间片过长,那么实用性就不能够得到保证。 优先级调度 为每一个进程分配一个优先级,按优先级进行调度。 为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。 多级反馈队列 一个进程需要执行100个时间片,如果采用时间片轮转调度算法,那么需要交换100次。 多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同。 实时系统 实时系统要求一个请求在一个确定的时间内得到响应。 分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。 进程同步 临界区 对临界资源进行访问的那段代码称为临界区,为了互斥访问临界资源,每个进程进入临界区之前,需要先进行检查。 同步与互斥 同步:多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系。 互斥:多个进程在同一时刻只有一个进程能进入临界区。 信号量 信号量是一个整形变量,可以对其执行down和up操作,也就是常见的P和V操作。 down如果信号量大于0.执行-1操作,如果信号量等于0,进入睡眠,等待信号量大于0; up对信号量执行+1操作,唤醒睡眠的进程,让其完成down操作。 down和up操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。 如果信号量的取值只能为0和1,那么就成了互斥量,0表示临界区已经加锁,1表示临界区解锁。 进程通信 进程同步与进程通信很容易混淆,它们的区别在于: 进程同步:控制多个进程按一定顺序执行; 进程通信:进程间传输信息。 进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。 管道 管道是通过调用 pipe 函数创建的,fd[0] 用于读,fd[1] 用于写。 只支持半双工通信(单向交替传输); 只能在父子进程或者兄弟进程中使用。 FIFO 也称为命名管道,去除看管道只能在父子进程中使用的限制。 FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。 消息队列 相比于 FIFO,消息队列具有以下优点: 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难; 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法; 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。 信号量 它是一个计数器,用于为多个进程提供对共享数据对象的访问。 共享存储 允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。 需要使用信号量用来同步对共享存储的访问。 多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件,而是使用内存的匿名段。 套接字 与其它通信机制不同的是,它可用于不同机器间的进程通信。 ","link":"https://greatewei.github.io/post/ji-suan-ji-cao-zuo-xi-tong-jin-cheng-guan-li/"},{"title":"浅谈Mysql索引","content":" 何为索引 索引其实就是一种优化查询的数据结构。比如mysql的索引就是使用B+树来实现的,而B+树就是一种数据结构,可以优化查询速度,所以可以利用索引来优化慢查询,索引的定义就是优化查询的数据结构。 有哪些可以优化查询的数据结构 优化查询的数据结构有哈希表,完全平衡二叉树,B树,B+树。其中Mysql使用最多的是B+树。 hash表 存储数据,需要先获取hashcode,再将数据存储到hashTable[hashcode],这种方式容易产生hash冲突,多个数据同时落到相同下标,可以采用hashTable[hashcode]存储的数据结构变化成链表,里面存储着这些公共hashcode的数据,这种方法叫做拉链法。实现过程 优点: 适合查询单一数据,速度快。 缺点:不适合查询范围数据,相当要遍历索引。 完全平衡二叉树 每个节点最多只能有左右两个子节点,左子树 < 节点 < 右子数,左右子数的层级不能超过 1 层。 实现过程 插入过程,判断当前数与根节点大小进行判断,如果小于根节点,向左子树继续寻找,否则向右子树继续寻找,直到叶子节点后,回溯判断,左右层级是否超过了1层,继续进行树的结构变化。 优点:支持范围查询,因为二叉树是有序的。而且也可以提高查询效率,还是因为它是有序的,而且高度相比其它二叉树更平衡,通过二分法查询。 缺点:二叉树这个定义的本身就限制了它,即一个节点只能有两个子节点,所以当插入的数据非常多时,树的深度就会非常高,树的深度非常高的话就会影响查询效率,所以没有使用二叉树来当索引的。 B树 简单介绍一下就是可以一个节点可以存储多个节点的搜索树。这样就没有了二叉树的两个节点的限制,同时带有有序的特点。所以图中有个参数:MAX.Degree,即一个节点存储的最大节点数,这里设置的是3,看得比较明显。这样的话一层就可以存储很多的数据了。 实现过程 优点:它有二分查找可以快速定位数据的所在位置,而且每层可以存放大量数据,所以树的高度低。感觉还是很不错的。 缺点:范围查询效率太低,因为比一个数据大的话,虽说肯定会在右子树,但是上层数据和其它子树的数据不好对比。 B+树 实现过程 我们对比一下B+树和B树,发现了什么?图中是有重复元素的,仔细一看,所有的非叶子节点都在叶子节点中出现了备份。也就是说,最下面的一层,也就是所有的叶子节点就包含了所有的数据。这就和前面的所有结构都不一样的地方了。然后,比起B树而言,叶子节点这层还有指向后面的指针,也就是多了指向。这就能很好地进行了范围查询,很好地解决了B树的问题。这样定位到数据后,直接在这层的指针遍历即可。 经过上述的讨论,我总结了一下,衡量一种mysql索引好不好有三个原则: 1.能不能快速定位到元素所在位置。 2.能不能较好的进行范围查询。 3.树的高度是低还是高。 局部性原理与磁盘预读 计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。 所以操作系统为了提高效率,读取数据时往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,操作系统也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这里的-定长度叫做页,也就是操作系统操作磁盘时的基本单位。一般操作系统中 一页的大小是4Kb。 从上面的原理我们也能知道,固态硬盘比机械硬盘快的最根本最简单的原因就是:固态硬盘使用的电路进行读写,而机械硬盘使用的机械运动。 其实不管是机械硬盘还是固态硬盘都是存储介质,真正控制读写的是操作系统。 这部分属于计算机原理和操作系统方面的知识,感觉理解一下还是很有必要的。 所以,回到我们的问题,B+树中一个节点到底存多少个元素合适?,其实也可以换个角度来思考B+树中一个节点到底多大合适? 答案是: B+树中一个节点为一页或页的倍数最为合适。因为如果一个节点的大小小于1页,那么读取这个节点的时候其实也会读出1页,造成资源的浪费;如果一个节点的大小大于1页,比如1.2页,那么读取这个节点的时候会读出2页,也会造成资源的浪费;所以为了不造成浪费,所以最后把一个节点的大小控制在1页、2页、3页、4页等倍数页大小最为合适。 那么,Mysql中B+树的一个节点大小为多大呢? 这个问题的答案是“1页"”,这里说的页"是Mysq自定义的单位(其实和操作系统类似),Mysql的Innodb引擎中一页的默认大小是16k (如果操作系统中-页大小是4k,那么Mysql中1 页=操作系统中4页),可以使用命令SHOW GLOBAL STATUS like 'Innodb_ page size';查看。 并且还可以告诉你的是,一个节点为1页就够了。 为什么一个节点为一页(16kb)就够了呢? 先来看看mylsam和innodb使用B+树的情况: 通常我们认为B+树的非叶子节点不存储数据,只有叶子节点才存储数据。而B树的非叶子节点和叶子节点都会存储数据,这会导致非叶子节点存储的索引值更少,树的高度相对会比B+树高,平均的io效率会比较低,所以使用B+树作为索引的数据结构,再加上B+树的叶子节点之间会有指针相连,便于范围查询。上图的data区域两个存储引擎会有所不同,也就是聚族和非聚族索引的区别。后面详细讲解。 (这里说一下我对这里的为啥B树的高度相对B+树高的理解:就如上图所见,一个节点指的是 这才是一个节点,而不是单纯的15,或者加上旁边的一个指针。这样的话,前面已知一个节点是16kb大小,那么在这固定大小中,B树的非叶子节点还要存储数据,而B+树只存储值和指针。具体一点就是,假设数据大小2kb,值大小1kb,指针大小1kb。那么B树里只能有4个值,而B+树里则有8个值,这样的话,整个索引存储相同数量的值的话,B+树明显就比B树低,这样就提高了磁盘的io效率) 前面我们提到数据区域存储的东西,现在来进行详细解释: myisam中,叶子节点的数据区域存储的是数据记录的地址。这也叫非聚族索引。 下面是主键索引: 下面是辅助索引: 从图中看得出来,叶子节点中只存储着地址(也就是此值对应的记录的所在地址),找到对应的地址,然后去地址中取出数据。并且主键索引和辅助索引并没有太多区别。 然后再来看innodb中的B+树: 下面是主键索引: 下面是辅助索引: innodb的主键索引和实际数据是绑定在一起的也就是说innodb的表一定要有一个主键索引,如果一个表没有手动创建一个主键索引,innodb会查看有没有唯一索引,如果有,则选用唯一索引作为主键,如果连唯一索引也没有,则会默认建立一个隐藏的主键索引(用户不可见) 另外,innodb的主键索引要比myisam的的主键索引查询效率较高(少一次磁盘io),并且比辅助索引也要高很多。(这里不是很理解。。。。) 所以,我们在使用innodb作为存储引擎时,要注意: 1.手动建立一个主键索引 2.尽量利用主键索引进行查询 默默表示,看到这里对主键索引,辅助索引,聚族索引,非聚族索引有点理解不过来了。。。 后面慢慢缓冲。。 回到我们的问题:为什么一个节点为1页(16k) 就够了? 对着上面Mysql中Innodb中对B+树的实际应用(主要看主键索引),我们可以发现,B+树中的一个节点存储的内容是: 非叶子节点 : 主键+指针 叶子节点 : 数据 那么,假设我们一行数据大小为1K,那么一页就能存16条数据,也就是一个叶子节点能存16条数据; 再看非叶子节点,假设主键ID为bigint类型, 那么长度为8B,指针大小在Innodb源码中为6B,-共就是14B,那么一页里就可以存储16K/14=1170个(主键+指针),那么一颗高度 为2的B+树能存储的数据为: 117016=18720条,一 颗高度为3的B+树能存储的数据为: 11701170*16=21902400 (千万级条)。所以在InnoDB中B+树高度一般为1-3层, 它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。所以也就回答了我们的问题,1 页=16k这么设置是比较合适的,是适用大多数的企业的,当然这个值是可以修改的,所以也能根据业务的时间情况进行调整。 接着,我们来联系这次学到的索引底层原理再来看看我常常见到的最左前辍原则: 比如有下面这个B+树索引,我们建立了一个联合索引,顺序是emp_no,title,from_data.既然是联合索引,那么它们按理说应该是放在一起连续存放的。 我们判断一 个查询条件能不能用到索引,我们要分析这个查询条件能不能利用某个索引缩小查询范围 对于select * from employees.titles where emp_ no = 1是能用到索引的,因为它能利用上面的索引缩小所有查询范围,首先和第一个节点"4-r-01"比较,1<4, 所以可以直接确定结果在左子树,同理,依次按顺序进行比较,逐步可以缩小查询范围。 对于select * from employees. titles where title =‘1’是不能用到索引的,因为它不能用到上面的索引,和第一节点进行比较时,没有emp_ no这个字段的值,不能确定到底该去左子树还是右子树继续进行查询。 对于select * from employees.titles where title =‘1’and emp_ no = 1是能用到索引,按照我们的上面的分析,先用ttle='1 这个条件和第一个节点进行比较,是没有结果的,但是mysql会对这个sql进行优化,优化之后会将emp_ no=1这个条件放到第一位,从而可以利用索引。这里是使用了mysql的查询优化器。 Mysq总结 B+树可以更好的结合磁盘IO原理提高查询效率 Innodb一 定要有主键,没有主键会以唯一索引为主键, 否则会建立一个隐藏主键 Innodb的数据是和主键索引存在一起的(数据在叶子节点中,MyISAM中的叶子节点存储的数据地址) 4.建立索引时要考虑已有索引,一个SQL语句只会选择花费最低的一个索引执行 5.索引是一种有序的数据结构(B+树) ,一个节点可以存多个有序的元素,所以要利用好最左前缀原则 6.真实场景中一颗B+树的高度通常为3 ","link":"https://greatewei.github.io/post/qian-tan-mysql-suo-yin/"},{"title":"图解Tcp/Ip","content":"最近看了《图解TCP/IP》这本书,感觉不错,这里做了一个简单知识点概览。 第一章 网络基础知识 计算机网络出现的背景 计算机的普及与多样化 计算机自诞生以来,经历了一系列演变与发展,大型通用计算机,超级计算机,小型机,个人电脑,工作站,便携式电脑以及如今的智能手机终端等都是这一系列过程的产物,它们的性能逐年增强,价格却逐年下降,机体规模也正在逐渐变小。 从独立模式到网络互连模式 1.什么是独立模式? 一个完整的业务逻辑需要执行A,B,C三个步骤才能够完成,然而每一个步骤只能在一台机器完成,如果当前有人使用其中一个业务,你只能等待,这就是独立模式(类似银行业务的【取票,排队,柜台处理】)。 2.什么是网络互连方式? 还是完整A,B,C三个步骤,现在完成的流程都可以在一台服务器完成,这时候用户只要使用自己的电脑连接到服务器进行业务处理,进行业务的随意切换无需等待。 3.计算机网络按照规模可区分哪几类? WAN(广域网,由多个LAN构成),LAN(局域网,一栋楼或者大学校园中有限的,狭小的,区域内网络)。 从计算机通信到信息通信 最初,由管理员将特定的几台计算机相连在一起形成计算机网络,形成一种私有的网络。人们开始将这些私有网络连接在一起形成了更大的私有网络,最后形成了现在的综合信息通信网络。 计算机网络的作用 信息网络如同空气般,触手可及,在信息爆炸的时代,有利有弊。 计算机与网络发展的7个阶段 批处理 1.什么是批处理? 所谓批处理,是指实现将用户程序和数据装入卡带或者磁带,并由计算按照一定的顺序读取,使用户所要执行的这些程序和数据能够一并批量得到处理的方式。批处理时代的计算机主要用于大规模计算或处理,因为那时候的计算机不是一个便于普通人使用的工具。 分时系统 1.什么是分时系统? 分时系统(TSS)它是指多个终端与同一计算机连接,允许多个用户同时使用一台计算系统,实现了“一人一机”的目的,让用户感觉好像自己使用一台计算机一样,这也体现了分时系统的一个重要特性——独占性。 计算机之间的通信 在计算机之间的通信技术诞生之前,想要将一台计算机的数据转移到另一台机器,需要通过存储介质,计算机通信技术诞生后,计算机之间的数据传输可通过通信线路传输。 计算机网络的产生 20世纪70年代初期,人们开始实验基于分组交换技术的计算机网络,并且研究这各个计算机厂商之间的网络通信的技术,其中窗口系统的产生使人们人们可以通过一台计算机就可以享受网络各种丰富的资源。 互联网的普及 形成了“一人一机”的环境。 以互联网技术为中心的时代 随着互联网的发展,其地位也开始为IP网取代,而IP网(WWW,移动通讯网,Voip,iSCSI)本身就是互联网的产物。 从“单纯建立连接”到”安全建立连接“ 互联网让世纪各地的电脑连接一起,然而现在已不再满足于”单纯的建立连接“,而是追求”安全建立连接“为目的而发展。 手握金刚钻的TCP/IP TCP/IP是通讯协议的统称。 协议 随处可见的协议 io,icmp,tcp,udp,http,telent,snmp,smtp... 协议的必要性 两台计算机事先约定一规则,进行建立通信。 分组交换协议 1.什么是分组交换协议? 分组交换是指将大数据分割为一个个叫做包(packet)的较小单位进行传输的方法,这里所说的包,如同我们平常在邮局见到的邮包,分组交换就是将这些大数据封装成一个个这样的邮包交给对方。 ##协议由谁规定 国际标准OSI(Open System Intercon-nection) 协议分层与OSI参考模型 协议的分层 OSI将通信协议中必要的功能分成了7层,通过这些分层将那些复杂的网络协议更加简单化。上下层进行的交互时所遵守的约定叫做接口。同一层之间交互所遵循的约定叫做协议。 ...... ","link":"https://greatewei.github.io/post/tu-jie-tcpip/"},{"title":"思维锻炼","content":"\b\b\b 去除数组重复值 在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中唯一个重复的数字。 Input: 0 1 2 4 2 5 思路一: 使用空间换时间,使用hashMap,开辟长度为n的数组空间,对输入的数作为index就行叠加,hashMap[2] = 1,第二次遇到2,hashMap[2]++ = 2, 说明重复的数字为2。 时间复杂度O(n),空间复杂度O(n) 思路二: 遍历Input,将值与Input[值] 进行swap,如果交换过程中出现两次值相同则说明,当前数字是重复的。 时间复杂度O(n),空间复杂度O(1) 二维数组中的查找 给定一个(n * m)二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。 Input: [ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30] ] 思路一: 采用二分思想,当这个数在左下角,大于这个数的都在右边,小于这个数的都在上边。当这个数在右上角,大于这个数的都在下边,小于这个数的都在左边,所以只要从这两端进行查找速度会比较快。 时间复杂度O(n + m),空间复杂度O(1) 矩形覆盖 我们可以用 21 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 21 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法? Input: n = 5 思路一: 动态规划思想,当n=1, 覆盖的方法 = 1, 当n=2, 覆盖的方法 = 2,当n=3,可以把这个2 * 3的大局限拆分成 2 *1 与 2 * 2 的两块区域矩形,覆盖方法为 1 + 2 = 3。f(n) = f(n -1) + f(n - 2),所有结果应该是1 2 3 5 8 ,n 为5时有 8种方法,动态规划思想,将分体拆分为一个个小问题。 变态跳台阶 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 Input: n = 5 思路一 动态规划思想,将问题拆分为登上最后一个楼梯可以采用1 - n级的方法,所以f(n) = f(n-1) + f(n - 2) + ... + f(1) => f(n) = 2 * f(n - 1)。f(1) = 1, f(2) = 2, f(3) = 2^(n - 1), f(5) = 2 ^ 4 = 16。 求100!的结果有多少0 思路一: 首先分析阶层的计算都是通过乘法进行的,所以只要计算出有多少个10进行相乘就知道有多少个0了,而10能够通过2 * 5 得到,2的数量一定是足够的,所以只要计算出5的个数就能知道有多少个0,将每个数字进行质因数分解得出结果 (100/5 + 20/5 + 4/5) = 24。 二进制中1的个数 输入一个整数,输出该数二进制表示中 1 的个数。 思路一 整数转二进制过程中统计1的个数。 时间复杂度O(n),空间复杂度O(1)。 思路二 100010001 与 100010000进行 & 运算的结果每次都消耗一个1,所以只需要判断进行了多少次&运算就能够知道有多少个1了。 寻找重复数个数 输入一组递增数组arr = [1,2,2,2,2,4,5,5,7,7,8]求重复的2出现的次数,复杂度小于O(n) 思路一 \b使用二分算法。cnt = 0; 第一次: left = 0, right = 10, mid =( left + right)/ 2 = 5 arr[5] > 2 => left = 0, right = mid - 1 = 4 第二次:left = 0, right = 4, mid = 2 arr[2] == 2 => cnt++, 这时候不清楚arr[2] 左右两端与2之间的关系。 如何实现一个高效单项链表逆序输出? head->1->2->3->4->NULL 思路一 遍历链表入栈,输出栈。 已知sqrt(2) 约等于1.414,不用数学库,求sqrt(2)精确到小数位后10位 思路一 sqrt(2)的结果是在1.4 与 1.5 之间,采用二分法,不断扩大精度,获取结果。 给定一个二叉搜索树(BST),找到树中第K小的节点 思路一 BST左子树小于根节点,右节点大于根节点,采用中序遍历即可。 设计一个LRU 缓存机制 思路一 新数据插入使用头插法在链表首部插入数据,更新数据将旧节点移动到首部,当链表满了淘汰链表末尾数据,保留头部与尾部指针。 寻找字符串最大不重复子串 思路一 使用滑动窗口,不断的向字符串右侧移动,当出现重复字符,将字符从左侧移除,边移动边记录。 寻找字符串最大回文子串 思路一 暴力枚举每一个字符作为回文串中心向两侧判断。 思路二 动态规划,最大回文串状态转换公式f[i][j] = f[i + 1][j - 1] ^ (s[i] == s[j]) 思路三 Manacher 算法,这里不多介绍。 寻找n个柱子,2个柱子之间的最大积水量 思路一 双指针法,分别从左,右两端向内移动,判断当前位置小的节点不断向中间移动,边移动边计算最大积水量。 多个字符串最长公共前缀 思路一 暴力横向遍历或者纵向遍历。 思路二 \b取第一个字符串,不断二分左边部分的字符串进行前缀匹配。 思路三 分而治之,将两辆个字符串判断的最长公共前缀,不断进行公共最长前缀匹配,计算出最后结果。 删除链表倒数第N个节点 思路一 定义两个指针,先让其中一个向后移动N节点,再同时移动,当其中一个节点到达尾部,另一个节点就是倒数第N个节点。 ","link":"https://greatewei.github.io/post/si-wei-duan-lian/"},{"title":"Go Tcp客户端/服务端","content":"服务端 客户端 简单的语法 ","link":"https://greatewei.github.io/post/go-yu-yan-xue-xi/"},{"title":"Phabricator作为Code Review工具","content":"概述 Phabricator是一套基于Web的软件开发协作工具,包括代码审查工具Differential,资源库浏览器Diffusion,变更监测工具Herald,Bug跟踪工具Maniphest和维基工具Phriction。Phabricator可与Git、Mercurial、Subversion集成使用。 Phabricator是开源软件,可在Apache许可证第2版下作为自由软件分发。 Phabricator最初是Facebook的一个内部工具,主要开发者为Evan Priestley。Evan Priestley离开Facebook后,在名为Phacility的新公司继续Phabricator的开发。 搭建 暂未实践 ","link":"https://greatewei.github.io/post/phabricator-zuo-wei-code-review-gong-ju/"},{"title":"工具","content":"常用: Grok正则解析工具 Sql语法转换Dsl查询语法 作图工具 ","link":"https://greatewei.github.io/post/gong-ju-hui-zong/"},{"title":"Elasticsearch","content":"架构 如有异常请指出 常见问题 内存不足查询失败: 在公司搭建的ELK日志分析系统,在一个月内稳定的运行中,突然有一天,日志分析工具不好使了。 发现问题是由于,ES的内存不足了,导致查询失败。 解决方法: 对索引与数据进行物理删除,所以在使用这类需要占用大量磁盘,内存的程序,都应该定时监控清理。 索引写入失败: 当服务器磁盘占用达到95%,es会自动将索引设置成只读模式。 解决方法: 单个索引过大,查询失败 由于索引过大,需要大量内存加载数据导致搜索失败,记得定时清理无用的日志信息。 其它问题 前辈走过的坑 排查elasticsearch的cpu居高不下,查询慢的问题 ","link":"https://greatewei.github.io/post/es-chang-jian-wen-ti/"},{"title":"Elk","content":"pm2 安装 elasticsearch-7.3.1安装 logstash-7.3.0安装 kibana-7.3.0-linux-x86_64安装 filebeat-7.4.2-linux-x86_64安装 ","link":"https://greatewei.github.io/post/elk-ri-zhi-fen-xi-xi-tong-da-jian/"},{"title":"Nginx","content":"Nginx介绍 Nginx是lgor Sysoev为俄罗斯访问量第二的rambler.ru站点设计开发的。从2004年发布至今,凭借开源的力量,已经接近成熟与完善。 Nginx功能丰富,可作为HTTP服务器,也可作为反向代理服务器,邮件服务器。支持FastCGI、SSL、Virtual Host、URL Rewrite、Gzip等功能。并且支持很多第三方的模块扩展。 负载均衡 Nginx提供的负载均衡策略有2种: 1.内置策略:轮询,加权轮询,Ip hash。 2.扩展策略:各种方式都有 3.负载均衡理解 web缓存 Nginx可以对不同的文件做不同的缓存处理,配置灵活,并且支持FastCGI_Cache,主要用于对FastCGI的动态程序进行缓存。配合着第三方的ngx_cache_purge,对制定的URL缓存内容可以的进行增删管理。 Nginx配置文件结构 Nginx的配置分为全局块、events块、http块和server块。在nginx.conf文件中只包含了全局块、events块和http块的内容,server块的配置需要自己定义。每一个server块都可以当做一个虚拟主机,一个http块可以包含多个server块,每一个server块的配置都是独立的,不会影响到其他server块。http全局块的配置对server块有效,但是如果server块中和http全局块的配置冲突,则采用就近原则,以server块的配置为准。 原文 ","link":"https://greatewei.github.io/post/nginx-jie-shao/"},{"title":"Mysql","content":"数据库基础 \b基础概念定义 SQL语言划分4个部分: DDL(Data Definition Language) ,用来定义数据库对象,数据库表 DML(Data Manipulation Language), 数据库操作语言,操作数据库的相关数据,比如增删改 DCL(Data Control Language), 数据库控制语言,用来定义访问权限和安全等级 if grande等 DQL(Data Control Language), 数据查询语言, 用来查询数据select SQL查询过程(Mysql8.0以下,如果设置了查询缓存,可能出现权限不足依然查询到数据情况) Mysql架构组成 连接池组件(Connection Poll) 管理服务和工具组件(Enterprise Management Services & Utilities) SQL接口组件(SQL Interface) 查询优化器(Parser) 优化组件(Optimizer) 缓存组件(Cache & Buffer) 插件式存储引擎(Pluggable Storage Engines) 物理文件(File System, Files & Logs) 存储引擎 InnoDB 存储引擎:Mysql 5.5版本后默认的存储引擎,优点是支持事务,行级锁(只作用在索引),外键约束,支持崩溃后的安全恢复; MyISAM 存储引擎:不支持事务和外键,支持全文索引(但只对英文有效),特点是查询速度快; Memory 存储引擎:数据放在内存当中(类似memcache)以便得到更快的响应速度,但是崩掉的话数据会丢失; NDB 存储引擎:主要用于Mysql Cluster分布式集群; Archive 存储引擎:有很好的压缩机制,用于文件归档,写入时会进行压缩; 事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。 ACID:原子性,一致性,隔离性,持续性 四种隔离级别 可重复读(可以查看其他事务已提交的插入数据,会导致幻读) 可串行化(最高隔离级别当事务读取相同数据需要按照事务执行顺序执行) 读未提交(可以查看到其他事务未提交的数据内容,导致脏读) 读已提交(可以查看到其他事务提交的数据内容,导致不可重复读) 架构图 架构图:如有问题请指出 数据库迁移命令 Mysql版本导致的sql查询异常 sql 查询索引失效 在日常开发中,有许多问题是由于数据库索引没有建立好,与sql写法问题导致索引失效,当我们服务出现了接口压力时,分析原因,如果是由于数据库耗时原因的话,我们应该分析一下sql,获取日志的sql后,执行explain “需要执行的sql” 根据KEY字段判断索引是否有效。 多张分表联合查询出现bug 上线代码过程中,发现日志一直对一段正常的逻辑代码报错,排查后发现填充的字段数据有问题,于是去数据库查询后发现数据并没有问题,逻辑也是正常的,不断的排查过程中,发现是sql查询的结果有问题,copy sql执行后,发现查询出来的数据的确有问题,其中有一个字段的数据与其它字段数据进行了交换,之后排查发现两张分表的表字段顺序是不相同的,导致发生了这样的一个bug。 处理方法 desc tableName md5处理后分表之间进行表结构对比,发现出现问题的表结构及时处理。 ","link":"https://greatewei.github.io/post/mysql-chang-jian-wen-ti/"}]}