Docker Swarm 模型配合 traefik 实现集群sticky实践

背景

为什么要用Docker Swarm, 相比 Kubernetes 有什么好处

Docker Swarm可以看作是docker自带的一个简化版的Kubernetes, 拥有Kubernetes的基本功能如:

  • 更新
  • 回滚
  • 动态扩容
  • 分布式部署

Docker Swarm 的优势在于:

  • Docker 自带, 无需另外安装。学习成本低
  • 单机也能很好的使用,也可以很方便的进行实例扩容与设备扩容
  • Swarm 只有2层网络封装,而 Kubernetes 有5层网络封装
  • Swarm 本身占用的内存只有100M左右,而 Kubernetes 的简化版 k3s 也需要512M的内存空间,对小资源机器友好

Traefik 是什么, 为什么要使用它

Traefik 是一个反向代理软件,类似 Nginx但对于微服务有很好的优化。可以搭配各种分布式发现服务而无需另外配置。在本篇文章中我们会使用 Docker Provider 作为服务发现。

虽然 Docker Swarm 自带了http请求的分发,但是无法实现 sticky 功能(即同一用户的请求会分发到同一后端实例),因此需要Traefik 作为请求的中间件来分发请求

搭建 Docker Swarm 环境

Docker Swarm 环境非常好搭建,因为已经集成到Docker中了。我们安装好Docker以后就可以直接使用了

依赖:

  • Docker 1.12+
1
2
# 初始化swarm, 并将当前节点作为manager
$ docker swarm init
1
2
3
# 或者使用--advertise-addr 和 --listen-addr参数来指定使用哪个IP作为沟通IP
# 两个参数一般保持一致即可
$ docker swarm init --advertise-addr 10.0.0.1:2377 --listen-addr 10.0.0.1:2377

使用Docker快速搭建Traefik

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3'

services:
reverse-proxy:
# The official v2.0 Traefik docker image
image: traefik:v2.0
# Enables the web UI and tells Traefik to listen to docker
command: --api.insecure=true --providers.docker.endpoint=tcp://127.0.0.1:2377 --providers.docker.swarmMode=true
ports:
# The HTTP port
- "80:80"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
volumes:
# So that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock
  • api.insecure参数用于打开WEB UI. 可以访问127.0.0.1:8080/api/rawdata获取当前可以连接到的服务的相关信息
  • providers.docker.endpoint指向docker的沟通端口。默认端口为2377
  • providers.docker.swarmMode 表示为swarm模式

创建测试服务

此处使用 whoami 镜像提供的HTTP服务用于打印出集群连接相关信息

将以下文件保存为docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3'

services:
whoami:
# A container that exposes an API to show its IP address
image: containous/whoami
deploy:
replicas: 3
labels:
- "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
- "traefik.http.services.whoami.loadbalancer.sticky=true"
- "traefik.http.services.whoami.loadbalancer.sticky.cookie.name=foosession"

启动集群:

1
docker stack deploy -c ./docker-compose.yml whoami

测试命令:

1
$ curl -vs -c cookie.txt -b cookie.txt -H "Host: whoami.docker.localhost" http://127.0.0.1

注意需要带上 header Host 这样才能成功反向代理

Unicode编码解明

简介

本文简述了Unicode编码解码相关知识。假设读者已经对此有一定的基本了解

从Base64开始

为了更好说明变长字符编码,我们从Base64编码开始。因为Base64是每个字节进行编码

编码如下字符串:

1
你好, 世界!

得到如下编码:

1
5L2g5aW9LCDkuJbnlYwh

将这段编码放入浏览器控制台中进行不进行任何处理的解密

1
atob('5L2g5aW9LCDkuJbnlYwh');

得到如下结果:

1
你好, 世界!

可以阴影约约看到逗号和感叹号已经被解释出来,而中文没有被正确处理。因为英文的逗号和感叹号在128位ascii表中。而其他的中文字符就无法正确翻译了。

为了能正确翻译中文。我们需要对数据进行一些特殊处理。即实现utf8的编码规范来将二进制转换成一个unicode码,使其能对应上一个具体的字符。(unicode表是一个巨大的map, 每个数字都能对应一个具体的字符, 至于字符的具体渲染由系统提供的字体显示)

atob
atob 是一个很基础的base64转二进制的方法。他只会单独的去处理每个字节而不管其具体的编码实现

UTF8解码

我们先处理第一个字符ä
很明显。这是因为浏览器错误的处理了这个字节的翻译。我们需要将其转换成二进制

1
2
3
4
'ä'.charCodeAt(0); // 228

// 将其转换成二进制
(228).toString(2); // 11100100

我们查看一下 utf8的编码规范 。首字符前n个1表示由n个字节组成.以0表示收尾与分割。因此我们提取出其描述的二进制位为1110表示这个字由3个字节组成。那么我们继续提取接下来的2个字符

1
2
'½'.charCodeAt(0).toString(2); // 10111101
' '.charCodeAt(0).toString(2); // 10100000

其中最前的二位10是描述位,是无效的。我们将这三个字节的有效的二进制位提取出来可得: 0100 111101 100000。将其转换成16进制

1
(0b0100111101100000).toString(16); // 4f60

简单验证一下结果。直接在控制台输入:

1
'\u4f60' // 你

我们成功提取出了第一个中文字符。那么剩下的中文字符也很简单了

而编码就是其逆过程

UTF8编码

1
2
3
4
5
6
7
8
// 将 你好, 世界! 编码成16进制字符
const str = '你好, 世界!'

str
.split("")
.map(char => char.charCodeAt(0).toString(16))
.map(hex => "\\u" + hex.padStart(4, "0"))
.join(""); // \u4f60\u597d\u002c\u0020\u4e16\u754c\u0021

直接将输出的字符串复制到控制台可以看到自动转换出的中文

\u表示的是只后面跟的是一个unicode。长度为4字节

Git骚操作之从一个分支中批量将离散的commit 迁移到另一个分支

背景

因为某些原因。分支A与分支B在某个点分叉了。且分叉出来的分支B拥有很多乱七八糟的commit。现在希望将分支B中的代码迁移到分支A中。但因为分支B中有很多其他的commit。因此希望把分支B舍弃,只保留想要的一些commit。

本例是指仅作者为我自己的commit

方案

使用git log将自己的commit选出来。然后切换到分支A上。将选出来的commit cherry-pick。

首先根据某些条件选出想要迁移的commit。最终输出成空格分割的hash号

bash语句如下:

1
2
3
# 本例假设起始commit为4524cb34ea4
# 当前所在分支: 分支B
git log --author moonrailgun --oneline 4524cb34ea4^1...HEAD | awk '{print $1}' | sed '1!G;h;$!d' | xargs echo

语句解释:

  • git log --author moonrailgun --oneline 4524cb34ea4^1...HEAD 获取范围4524cb34ea4(包含该commit)~当前commit的commit中作者是(包含)moonrailgun的列并用单行显示
  • awk '{print $1}' 获取每行中以空格分割的第一列
  • sed '1!G;h;$!d' 将输入按行倒序输出(因为git log输出的最后一行在最上面)
  • xargs echo 将输入的行变成一行

由此可以得到一串用空格分割的hash字符串

然后git checkout A切换到分支A。执行git cherry-pick <此处输入刚刚得到的字符串>
如有冲突,解决冲突后git cherry-pick --continue即可

结论

  • 不会丢失代码
  • 若有冲突能马上解决
  • 保留commit细节
  • 解放生产力

Let's Encrypt免费通配符证书申请

依赖

1
git clone https://github.com/certbot/certbot.git

命令

示例:

1
./certbot-auto certonly  -d *.moonrailgun.com --manual --preferred-challenges dns --server https://acme-v02.api.letsencrypt.org/directory

参数说明:

  • certonly 表示安装模式,Certbot 有安装模式和验证模式两种类型的插件。
  • --manual 表示手动安装插件,Certbot 有很多插件,不同的插件都可以申请证书,用户可以根据需要自行选择
  • -d 为那些主机申请证书,如果是通配符,输入 *.yourdomain.com
  • --preferred-challenges dns 使用 DNS 方式校验域名所有权
  • --server Let’s Encrypt ACME v2 版本使用的服务器不同于 v1 版本,需要显示指定。

参考文章

MySQL 查询优化笔记

概述

正确的查询方式与正确的索引可以极大增加数据的查询效率,能极大提升服务器响应时间。对于较大的并发服务来说一点点提升也有比较大的收益。

sql调优

一条sql语句的执行过程:

mysql分为server层和存储引擎层两个部分;
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。
现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

使用show create table 语句查看一张表的DDL

show create table user

使用desc或explain调优sql语句

e.g. : desc select * from user

即在要执行的语句前可以加上desc或explain查看该语句的查询操作。(两者操作是等价的)

该命令会列出以下项:

  • id
  • select_type
  • table
  • partitions
  • type
  • possible_key
  • key
  • key_len
  • ref
  • rows
  • filterd
  • Extra

其中我们主要需要关心的有三列: key, rowsfiltered

其中:
key主要是表示当前查询用到的键
rows表示查询后返回的行数
filtered表示当前查询过滤了多少。 100表示完全没有进行过滤,是最佳的情况。因为where操作是很消耗性能的

简单的,如select count(1) from employees where gender = 'M'
我们可以通过简单的增加一个索引来实现优化: alter table employees add index(gender)
select count(1) 表示仅返回数据量而不关心数据值 减少其他变量影响结果

这样当我们使用desc语句时可以看到key变为了gender, rows数量为返回的数量,filtered变为了100。可以注意到的是,Extra的值从Using where变为Using index。说明我们这条语句使用了索引

而对于多条件的查询。我们可以通过联合索引来进行优化
如以下查询:
select count(1) from employees where gender = 'F' and birth_date > '1964-01-01'
则可以通过增加联合索引来进行优化
alter table employees add key (gender, birth_date)
注意: 联合索引的顺序很重要,如果顺序不对则无法进行优化

如果我们想在select时统计别的数据,如以下查询:
select count(distinct birth_date) from employees where gender = 'F'
我们也可以使用联合索引进行调优
alter table employees add key (birth_date, gender)
同样的。需要注意顺序, 不过要注意比如是(birth_date, gender), 虽然都是filtered: 100但是执行效率仍有区别
区别如下:

  • (birth_date, gender): key_len为4, 即用到了两个键(gender是tiny,长度1,birth_date是日期,长度4)。rows为4788,Extra为Using where; Using index for group-by
  • (gender, birth_date): key_len为1, 只用到了gender键。rows为149734,Extra为Using index

而对于一些无法优化的。如双向like操作,也可以通过联合索引来应用一部分索引增加部分速度(虽然只会应用一部分)
select count(1) from employees where gender = 'F' and first_name like '%a%' and last_name like '%b%';
可以增加联合索引实现索引下推功能:
alter table employees drop key gender, add key (gender, first_name, last_name);

优化总结

  • 每个索引都是一个BTree(MySQL一般是B+Tree)
  • 什么时候加索引:搜索条件固定,数据分布不均(即需要全表搜索)
  • 双向like是没法优化的
  • 联合索引受顺序影响。而且如果最左索引是范围的话无法使用后面的
  • 使用索引下推,可以减少从主键树上取数据的时间
  • 大多数复杂情况or查询是无法优化的, 但是一些简单查询可以优化
  • 尽量使用join查询而不是子查询,因为join查询能被优化而子查询不行

参考文档

Linux 小资源服务器使用经验总结

善用交换内存

有些时候。作为个人用的服务器我们往往不会去购买一些性能很好的服务器。但是在某些情况下我们却要临时去使用一个比较高的内存去执行某个程序,但是我们当前服务器的资源却无法执行。因为linux为了防止自身系统崩溃,引入了一个OOM Killer机制。即当一个进程占用过多内存时,系统会直接kill掉这个进程并抛出Out of memory错误。
我们可以通过grep "Out of memory" /var/log/messages来查看相关日志。
这个时候我们就可以使用交换内存来用磁盘空间换内存了。

第一步: 创建一个空白文件

使用命令dd if=/dev/zero of=/opt/swapfile bs=1M count=1024来创建一个1GB的空白文件到/opt/swapfile。同样的可以按照这个方法创建其他大小的空白文件。我一般会创建和内存一样大小的交换空间

第二步: 创建交换空间

使用命令mkswap /opt/swapfile来将这个空白文件变成一个swap文件。只有该文件才能将对应的磁盘空间作为一个临时内存。

第三部: 应用交换文件

使用命令swapon /opt/swapfile将该交换空间应用到系统中。此时执行free -h可以看到swap一行多出了1GB空间

交换空间应该设定多大?

实际内存 推荐交换空间 推荐交换空间(开启休眠模式)
⩽ 2GB 2倍 3倍
2GB - 8GB 1倍 2倍
8GB - 64GB 至少4GB 1.5倍
> 64GB 至少4GB 不推荐

参考文章:

其他

  • swapoff -a 移除所有的swap内存空间

玩PUBG时运行VMware被禁止进入游戏的解决方案

起因

因为需要安装一些mysql之类的东西而不想在本机(主要用于娱乐)上安装这些东西。因此想到了在VMware上安装一个linux环境。然后当我完成了VMware的安装时。悲剧发生了。。我无法进行游戏

反外挂系统给我提出的警告是。
发现不允许应用程序VMware

尝试

我看到上面的信息感觉很诧异。。因为BE在我打完了一局正在打第二局的时候把我踢出去了。看来BE的扫描是有个延时特性的。可能是为了防止电脑卡。那么这样的话。我关闭VMware不就好了?

结果是失败。。

一开始我还以为是BE的问题。然后经过一系列问题排查发现。是VMware会在后台不断得启动一系列VMware开头的后台进程。对用户来说没有什么影响,但是BE会检测到并禁止你进入游戏。而且就算在任务管理器中退出了进程他也会自动重启

解决方案

在window服务中将VMware的服务设为手动即可

修改完毕后需要重启电脑后生效

后续

作为代价。当使用VMware时会出现网络异常。只需要在用的时候手动找到服务将其打开即可(就是麻烦了一点)

TRPG Engine开发手册——数据库框架迁移

为什么要迁移

TRPG Engine一开始设计是基于node-orm设计的数据库框架,对于一个后台系统来说。更换数据库框架是一件非常需要勇气的事情,其代价相当于项目重构。但是经过长时间的考虑,我还是决定将数据库框架从node-orm迁移到sequelize

迁移的理由:

  • sequelize具有更加规范化的文档,便于学习
  • sequelize天生支持Promise,而node-orm4版本以后虽然也支持Promise但是因为原函数为回调形式因此若要使用findPromise版本需要使用findAsync
  • sequelize对连接池的支持更加好,并能很大程度上减少数据库连接释放的问题。
  • sequelize天生支持事务,但node-orm需要第三方插件。
  • sequelize天生继承了一些中大型后台项目需要的一些特性如timestamp, migrations。而node-orm只有第三方开发者提供的插件,并没有官方支持。
  • node-orm是一个很老的项目了,开发者的维护已经不在提供维护了,对于一些问题可能没法得到很好地解决。而sequelize的开发社区依旧非常活跃
  • sequelize的star数是所有node的orm框架中最高,同类的orm还有bookshelf, persistencejs

迁移手册

注册方式

注册模型的方式还是使用原先的设计。通过传输一个orm对象与一个db对象来将定义的模型注册到数据库实例中。不同的是需要修改模型字段的类型定义。

连接方式

首先sequelize是没有connect的概念的。它不像node-orm一样是需要在访问前先连接连接池。而是直接在操作连接实例上注册后操作数据库model即可。因此需要变更的部分为:

  • 去除所有的connect方法
  • 调整model的注册方式
  • 直接返回给各个event app.storage.db, 而不是创建连接后的值。

关联方式

sequelize框架和node-orm一样有一对一、一对多、多对多的关联关系
但是node-orm将其设定为两个接口,即hasOne和hasMany。其中hasOne同时可以是一对一关系和一对多关系
迁移到sequelize时需要手动改写相对的关系,这一点无法借助重写方法的实现,因为sequelize同样有hasOne和hasMany接口且其意义不同。因此为了防止二义性,应当手动改写其方法。

Hooks

sequelize框架定义的hooks参数是(instance, options)。而node-orm则是(next)。给this设定了当前实例。这是由于两者的设计思路决定的。为了防止二义性应当手动修改hooks的定义

数据库模型实例方法

node-orm中模型的实例方法是在定义时的第二个参数中传入一个methods中的对象实现的,而sequelize是通过注册到模型对象的原型链prototype中实现。因此可以进行一个别名操作,即在注册数据库对象时重新定义define方法。将第二个参数的method提取出来并进行统一赋值。

zsh插件推荐

依赖

需要已安装zshoh-my-zsh。如果没有安装请自行查找安装文档
oh-my-zsh官方文档

快速安装指令

  • 通过curl: $ sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
  • 通过wget: $ sh -c "$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"

优化显示

效率提升

  • zsh-autosuggestions 命令自动补全

    git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions