容器服务化方向的一些探索

本文参考使用Docker打造自己的云平台编写 本文基于 Docker Swarm Mode 实现容器化,虽然目前 k8s 更火一些,但实在是太重了,以后再折腾。 使用 traefik 来实现反向代理、负载均衡,traefik 还自带了服务发现、后端断路器、健康检查等,相当于是自带服务发现的 nginx。当然它还支持其他的容器编排工具如,服务发现工具如 Consul。 使用 Portainer 来管理 Docker 容器,可以兼容 Docker Swarm 模式。 Docker 的安装就不说了。装完初始化 Swarm 模式。 $ docker swarm init 先设置一下环境变量。 $ export DOCKER_DEV_PATH=/usr/local/src/docker-dev 日志文件都会统一存到 ${DOCKER_DEV_PATH}/logs。 traefik 新建一个编排文件 traefik.yml。 version: '3.3' services: reverse-proxy: image: traefik:1.7-alpine command: --web --docker --docker.domain=cloud-labs.io --docker.watch --docker.swarmmode=true --loglevel=INFO --accesslog --accesslog.filepath=/logs/access.log --traefiklog --traefiklog.filepath=/logs/traefik.log deploy: mode: replicated replicas: 1 labels: - traefik.enable=true - traefik.backend=traefik - traefik.frontend.rule=Host:monitor.cloud-labs.io - traefik.port=8080 - traefik.docker.network=traefik_proxy networks: - proxy ports: - 8081:80 volumes: - /var/run/docker.sock:/var/run/docker.sock - ${DOCKER_DEV_PATH}/logs/traefik:/logs - /dev/null:/traefik.toml networks: proxy: 简单解释下 labels 中配置的含义 ...

August 28, 2019 · 2 min · 361 words · Nick

PHP7 数组排序函数源码解析

今天来看看经常使用的数组排序函数如 sort, rsort, asort, arsort, ksort, krsort 。话不多说直接找 sort 函数吧。 在 php7.3 源码中搜索 PHP_FUNCTION(sort) 可以搜到如下 其中 .h 文件是C语言的头文件,直接打开 .c 文件。 sort 函数如下,其中我加了一点注释。 PHP_FUNCTION(sort) { zval *array; zend_long sort_type = PHP_SORT_REGULAR; // 默认的排序规则 compare_func_t cmp; // 这里开始接请求参数 ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_ARRAY_EX(array, 0, 1) Z_PARAM_OPTIONAL Z_PARAM_LONG(sort_type) ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); // 根据排序规则获取使用的排序函数 cmp = php_get_data_compare_func(sort_type, 0); // 进行排序 if (zend_hash_sort(Z_ARRVAL_P(array), cmp, 1) == FAILURE) { RETURN_FALSE; } RETURN_TRUE; } 不但 rsort, asort, arsort, ksort, krsort 这些函数在 array.c 文件中,PHP数组相关的也都在其中。 先说下 rsort, asort, arsort, ksort, krsort 函数内容与 sort 只有细微的差别。 ksort、krsort 是根据键排序所以排序规则获取排序函数用的是 php_get_key_compare_func 参数与 php_get_data_compare_func 是一样的。 php_get_data_compare_func、php_get_key_compare_func 函数第二个参数意思是是否降序排列,rsort、arsort、krsort 第二个参数都是1。 进行排序时 zend_hash_sort(Z_ARRVAL_P(array), cmp, 1) 第三个参数意思是是否重新排列索引, sort、rsort 传的都是1。 做个表格看下 ...

May 22, 2019 · 5 min · 935 words · Nick

JetBrains IDE 文件对比功能

之前用Beyond Compare对比文件,功能非常强大。但是试用期到了就用不了了,而且授权非常的贵,很多功能一般也用不到。 一般文件对比直接用系统自带的diff命令就可以了。但是,如果临时复制粘贴不同版本文件对比,又不想为了对比而创建文件,JetBrains家IDE对比功能就派上用场了。 Mac下按 ⇧⌘A ,搜索 open diff ,回车。 左右两边可以随意复制粘贴对比了。 ⇧⌘A这其实是一个功能搜索快捷键,可以搜索任何功能。也可以快速开关一个功能。

May 7, 2019 · 1 min · 11 words · Nick

编写Go的TCP服务来替代PHP的Swoole

背景说明 目前公司采用微服务架构,主要开发语言为PHP,通过Swoole开启TCP服务供业务端调用。通过公司内部编写的PHP扩展封装客户端调用逻辑。 需求 暂定使用Go语言开发新的业务,并提供TCP服务。其中老的PHP项目要通过原有的客户端扩展实现无修改调用。 解决方案 通过阅读客户端扩展源码了解调用逻辑。编写简单的测试如下。 <?php $_client = new \swoole_client(SWOOLE_SOCK_TCP | SWOOLE_KEEP); $_client->set([ 'open_length_check' => true, 'package_length_type' => 'N', 'package_length_offset' => 0, 'package_body_offset' => 4, 'package_max_length' => 24657920, ]); if (false == $_client->connect("127.0.0.1", 8880)) { printf("err_msg: %s err_code: %s" . PHP_EOL, var_export($_client->errMsg, true), var_export($_client->errCode, true)); } // 随便测试个请求参数 $data = [ 'api' => 'getUserInfo', 'params' => [ 'user_id' => 123 ] ]; $data = json_encode($data); $data = gzcompress($data, 9); $_client->send(pack("N", strlen($data)) . $data); $res = $_client->recv(); $end = getTime(); $data = json_decode($res, true); 其中前4个字节是head,表示body长度,采用二进制大端字节序编码。body先进行json编码再进行了zlib压缩。这都是编写Go的TCP服务时需要处理的。 写个简单的Go TCP服务试试,先不考虑过多的错误边界处理。 package main import ( "bytes" "compress/zlib" "encoding/binary" "fmt" "io" "net" ) func main() { ln, err := net.Listen("tcp", ":8880") if err != nil { fmt.Printf("%s", err) } for { conn, err := ln.Accept() if err != nil { fmt.Printf("accept err:%s", err) } go handleConnection(conn) } } func handleConnection(conn net.Conn) { fmt.Println("on conn") var err error headLen := 4 head := make([]byte, headLen) if _, err = conn.Read(head); err != nil { fmt.Println(err.Error()) return } // 解码大端字节序获取body长度 bodyLen := binary.BigEndian.Uint32(head) allBody := make([]byte, 0) readLen := 0 for bodyLen > 0 { body := make([]byte, bodyLen) readLen, err = conn.Read(body) if err != nil { fmt.Println(err.Error()) return } bodyLen = bodyLen - uint32(readLen) allBody = append(allBody, body[:readLen]...) } // 解压zlib压缩的数据 RFC 1950 b := bytes.NewReader(allBody) uncompressRead, err := zlib.NewReader(b) if err != nil { fmt.Printf("uncompress data err:%s", err) } var uncompressData bytes.Buffer io.Copy(&uncompressData, uncompressRead) // 解出的json字符串 fmt.Printf("Received:%s", uncompressData.Bytes()) // 路由调用实际业务逻辑处理 ... // conn.Write() conn.Close() return } 运行Go的TCP服务,跑一个PHP请求测试。 ...

April 30, 2019 · 2 min · 247 words · Nick

PHP5下的Error错误处理及问题定位

背景说明 来说说当PHP出现E_ERROR级别致命的运行时错误的问题定位方法。例如像Fatal error: Allowed memory size of内存溢出这种。当出现这种错误时会导致程序直接退出,PHP的error log中会记录一条错误日志说明报错的具体文件和代码行数,其它的任何信息都没有了。如果是PHP7的话还可以像捕获异常一样捕获错误,PHP5的话就不行了。 一般想到的方法就是看看报错的具体代码,如果报错文件是CommonReturn.class.php像下面这个样子。 <?php /** * 公共返回封装 * Class CommonReturn */ class CommonReturn { /** * 打包函数 * @param $params * @param int $status * * @return mixed */ static public function packData($params, $status = 0) { $res['status'] = $status; $res['data'] = json_encode($params); return $res; } } 其中json_encode那一行报错了,然后你查了下packData这个方法,有很多项目的类中都有调用,这时要怎么定位问题呢? 场景复现 好,首先我们复现下场景。假如实际调用的程序bug.php如下 <?php require_once './CommonReturn.class.php'; $res = ini_set('memory_limit', '1m'); $res = []; $char = str_repeat('x', 999); for ($i = 0; $i < 900 ; $i++) { $res[] = $char; } $get_pack = CommonReturn::packData($res); // something else 运行bug.php PHP错误日志中会记录 ...

January 8, 2019 · 2 min · 381 words · Nick

RabbitMQ 消息队列:队列的参数与消息的属性

队列的参数即声明Queues时的Arguments。 消息的属性即向Exchange发布消息时的Properties。 Queues Arguments Message TTL 消息的存活时间,写入队列后被消费前可以存活的时间单位毫秒,通过x-message-ttl属性设置。 Auto expire 队列的存活时间,指定时间内没有consumer或get方式请求队列消息则会自动删除,通过x-expires属性设置。 Dead letter exchange 死信消息的exchange,通过x-dead-letter-exchange属性设置。 Dead letter routing key 死信消息的路由键,通过x-dead-letter-routing-key属性设置。 Maximum priority 消息支持的最大优先级,可实现优先级消息队列,通过x-max-priority属性设置。 Max length 队列的最大消息数,通过x-max-length属性设置。 Max length bytes 队列的消息的最大字节数,通过x-max-length-bytes属性设置。 Lazy mode 懒惰队列模式,将队列内容移动到磁盘上,当消息者请求时加载入内存,这样可以支持非常长的队列,通过x-queue-mode属性设置。 Master locator 在RabbitMQ的高可用镜像模式中,队列消息首先会写入主节点再依次备份至从节点,通过x-queue-master-locator属性设置队列的主节点选择策略。 选择承担主节点最少的节点min-masters 选择声明队列客户端连接到的节点client-local 随机挑选一个节点random 死信相关 当消息满足以下三种情况之一时会当做死信来处理: 消息被拒绝 (basic.reject or basic.nack) 消息未消费超时 超出队列长度限制 队列最大长度相关 通过x-max-length、x-max-length-bytes来设置队列的最大长度。一个可以控制队列消息的个数,一个可以控制队列占用的空间。如果两个都设置任何一个触发都会执行队列溢出行为,默认的队列溢出后会从队列头开始丢弃消息或进行死信处理。通过x-overflow可以设置队列溢出后的行为,值为drop-head(默认值)或reject-publish(拒绝新消息)。 Message Properties 属性 说明 content_type MIME类型 content_encoding MIME编码 priority 消息优先级 correlation_id 业务应用关联标识 reply_to 回复队列名称 expiration 过期时间 message_id 业务应用消息标识 timestamp 消息写入时间戳 type 消息类型 user_id 用户id app_id 应用id cluster_id 集群id

January 31, 2018 · 1 min · 76 words · Nick

RabbitMQ 消息队列:浅谈

简介 消息队列用来解决不同项目间通信、业务解耦。消息队列有很多种比如用Redis实现的轻量级消息队列。RabbitMQ是消息队列的一种,基于AMQP协议,用Erlang语言编写,属于一种消息队列中间件。 消息机制 Connection 链接 真实的TCP链接 Channel 信道 基于Connection创建的Channel,所有的数据传输都是基于Channel的。大家都知道TCP链接需要三次握手,业务频繁每次开TCP链接对性能也会有不小的损耗,TCP链接数也有限制。Channel的存在主要是为了复用TCP链接。 Exchange 交换机 消息生产者生产的所有消息都会先写入Exchange中,再路由到Queues中,如果Exchange没有绑定或未匹配到Queues则消息会被丢弃。 Queues 队列 生产者生产的消息会从Exchange中路由到Queues中,消费者从Queues中消费消息。 Binding 绑定 Binding将Exchange与Queues关联起来。 Routing key 路由键 路由键是消息从Exchange进入到某个Queues的规则。 在将Queues绑定至Exchange时会设置消息从Exchange路由至Queues的绑定路由键规则。 在将消息写入Exchange时会附带消息的路由键。 Exchange类型为direct或topic时,消息的投递会根据消息的路由键和绑定的路由键进行匹配,Exchange将消息投递给所有匹配上的Queues。 路由键写法 路由键可以由. 进行分隔,如computer.mac或computer.win。 路由键还支持通配符来模糊匹配,*与# *匹配一个分隔的单词 #匹配多个或零个分隔的单词 应用举例 Exchange Routing key Queues X *.*.imac apple X apple.# apple X *.phone.* phone 此时写入一条消息路由键为apple.phone.iphone4会被投递到队列apple与phone。 再写入一条消息路由键为apple.computer.imac仅会被投递到apple,此时apple队列匹配了两个路由键,但是也只会投递到apple一次。 再写入一条消息路由键为micro.computer.win因为没有匹配的路由键队列该消息会被丢弃。 Exchange Types 交换机类型 fanout 广播 fanout类型的Exchange会无视路由键,将消息投递给所有绑定到该Exchange上的Queues。 direct 单播 direct类型的Exchange会将消息投递给路由键完全匹配的Queues中。 topic 组播 topic类型的Exchange会将消息投递给路由键模糊匹配的Queues中。 headers headers类型的Exchange也会无视路由键,会根据headers中的属性来进行匹配。该类型应用较少,不过在RabbitMQ内部还是有使用。

December 28, 2017 · 1 min · 62 words · Nick