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

善用交换内存

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

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

切换到root权限

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

设置一下权限chmod 600 /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

git使用方法简明教程 - 团队篇

简述

首先,如果你没有看过前文。我推荐你先看一下我的上一篇文章git使用方法简明教程 - 个人篇,这样你能对git这个程序员必用的软件能够有一定的了解。

工作流

团队使用代码进行协作编程与个人使用不太一样。因为它一般来说是在多个分支上进行操作。这里涉及到一个分支(branch的概念,我稍后再讲)

一般来说一个团队的代码仓库至少会有2个以上的分支。有一个生产环境的master (或者release) 分支,一个用于内部内部开发的dev分支。
有两种模式,一种是小团队的快速开发。多个人在同一个分支开发。通过不断地进行push和pull进行代码间的同步。另一种是以开发分支为基础。每个人分出一个自己的分支,完成开发后合并到主开发分支。这种情况可以根据实际需求选择一个人一个个人分支或者一个人开发一个任务就分出一个分支两种情况。

分支

分支,英文名branch。可以通过git branch命令查看本地的分支。
使用git checkout [分支名]的方式来切换不同分支。
我们在使用git log命令查看提交简述的时候也能看到分支所在位置。

分支分为本地分支和远程分支。远程分支是远程代码仓库在本地的映射。我们可以通过git fetch命令进行同步本地的远程分支。通过git branch -r命令可以查看所有的远程分支。
我们正常操作都是操作的本地分支。

通过git checkout -b [新分支名]就可以从当前位置创建一个新的分支。并且切换到新的分支上来。
在完成代码编辑的时候我们可以通过git merge命令进行分支的合并操作。
举例:
我有两个分支masterdev.分别是主分支和开发分支。我在开发分支上完成了一些代码的提交后。想要把dev分支的变更合并到主分支

1
2
$ git checkout master
$ git merge dev

如果有代码冲突的情况我们需要解决冲突,然后使用git commit命令提交合并
如果在代码冲突的过程中想要中断代码合并的过程,使用git merge --abort命令

代码冲突

代码冲突是多人协作时常见的问题,因为你没法保证在你进行编辑的时候其他的不会对代码进行修改。在大多数情况下git会自动处理代码变更。但如果两个开发者共同修改了同一处代码时。git无法自动处理。那么就需要开发者手动解决冲突。
当git出现无法处理的冲突时,它会告知你冲突的文件,并修改文件共同修改处的代码变更为:

1
2
3
4
5
<<<<<<<head
这里是当前代码
=======
这里是远程代码
>>>>>>>xxxxxxxxxx(发生冲突的hash值)

当出现这种情况时,作为开发者你需要做的是仔细比较两边的代码,并把多余的东西删除。然后把所有的冲突全部解决完毕后,提交代码。

rebase

NOTE:rebase是一种破坏性的行为, 请尽量不要在公共分支上进行操作

当我们在自己的分支提交了一系列代码后,需要把完成的功能提交到公共开发分支,那么我们要做的第一步是先把公共分支的代码同步到我们自己的分支。简单的,我们当然可以使用git merge命令。但是,这样会留下一个git生成的merge commit点,而这个commit是无意义的。对于code review(代码审查)来说是一个比较影响的东西。当然了,merge commit 点的好处是所有的操作都是线性的。在进行合并个人分支到公共分支的过程中最好使用merge,而把公共分支同步到个人分支的时候,我建议使用git rebase, 他能让你看上去是从当前公共分支创建出来的个人分支进行往前编辑
rebase的原理是把往前一段距离的代码提交重新打开,然后重新提交一遍。因为是一种破坏性的行为,因此无法与远程服务器同步,需要使用git push -f命令强制覆盖远程分支。

举例:
我有一个个人开发的分支moonrailgun, 然后有一个开发分支dev。在经过一段时间的开发后,两个分支各向前跑了几个commit。现在我希望能够把我的moonrailgun合并到dev分支。为了保证冲突能现在我的个人分支解决,我需要先把dev的变更同步到moonrailgun分支

1
2
# 当前在moonrailgun分支
$ git rebase dev

如果出现冲突。需要解决冲突,然后git add以后使用git rebase --continue命令继续操作。
如果想要中断rebase的过程,使用git rebase --abort

然后你就能很方便的把moonrailgun分支合并到dev分支上了,并且代码的历史线会很干净。

P.S.: 如果团队在分支上进行代码操作,那么常常会出现代码不同步的问题。那么为了保证代码分支线的干净。最好不要使用git pull命令(因为这会留下一个合并的commit点)。推荐的操作如下:

1
2
3
4
$ git fetch
$ git log origin/master
# 查看最新的远程commit的hash值。复制下来
$ git rebase <hash>

这样可以完美的处理单分支多用户进行代码提交。

应用patch和使用patch

patch是一种线下通过文件传输同步变更的方式

基于历史原因, git有两套使用patch的方式

旧的:

1
2
3
git diff > 1.patch # 生成patch
git apply --check 1.patch # 检查patch是否可用,如果没有任何输出说明可以正常使用
git apply 1.patch # 应用patch

新的:

1
2
git format-patch HEAD^ # 等价于git format-patch -1
git am <patch file>

在一般情况下推荐使用新的方式,基于commit来控制。但是如果出现特殊情况,比如未提交的代码想要交换的话,可以考虑使用旧的patch方式。

这也是为什么git没有把旧方式移除的原因

Vue学习笔记

概述

本章内容主要记录一些在Vue开发过程中比较实用的一些工具,写法之类的东西

本章内容可能会不断、不定期的进行更新。

工具

好用的UI框架: elementUI

写法

根据字符串动态生成不同组件:is属性
通过is属性我们可以做一些比较特殊的定制化操作。比如我们可能定义一系列的控件,然后根据后台返回的表单对象自由组合成对应的表单。

状态管理

除了常见的Vuex以外。Vue+RxJS+VueRx 这套技术栈也是一个不错的选择

如何实现方法既能实现函数式编程又能获取结果进行比较——实现形如add(1)(2)(3)...(n)的方法

面试的时候面试官出了一道题目很有意思。问的是这样的。假如我有一个方法add。如何实现形如add(1)(2)调用返回3
这道题答案很简单,如下:

1
2
3
4
5
function add (x) {
return function (y) {
return x + y;
}
}

但我当时第一时间的反应是这样的: 如何实现形如add(1)(2)(3)...(n)这样的链式调用。
首先这个问题有两个难点:

  • 1.如何解决累加结果的存储问题
  • 2.如何解决他返回的方法可以作为结果来使用

首先这第一个问题是好解决的。只需要在内部存储计数器即可。而第二个问题是无法完全解决的。因为如果要实现链式调用那么返回必须是函数。而函数的typeof值是function, 永远不能作为一个number使用。但是如果放宽限制。我们可以用过重写方法的toString函数来实现对一些隐式调用转换为字符串的匹配。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var sum = 0;
function add(x) {
sum += x;
return add;
}
add.toString = function() {
return sum;
}

var result = add(1)(2)(3);
console.log(typeof result); // 返回function
console.log(result + ''); // 返回6
console.log(result == 6); // 返回true

当然,这里有个问题。就是封装性不好。会污染全局变量。那么我们把add方法做一下闭包处理进行优化一下:

1
2
3
4
5
6
7
8
9
10
11
function add(x) {
var sum = x;
function tmp(y) {
sum += y;
return tmp;
}
tmp.toString = function() {
return sum;
}
return tmp;
}

Firefox、safari的session无法匹配的原因

场景

在测试服务器上出现一个这样奇怪的场景:在Firefox和Safari浏览器上出现session无法正确被识别,而Chrome浏览器能够正常被服务器识别session。

原因

服务端时间与客户端时间不匹配,客户端时间在服务端时间后并差距大于session过期时间(如session过期时间为30分钟, 那么客户端时间往后调30分钟以上或者服务端时间往前调30分钟以上都会出现这个问题)

分析

首先我们看一下以下两张图:

[图1]


[图2]

其中图1是我在火狐浏览器发起的一次http请求的请求头,图2是我在chrome浏览器中发起的http请求的请求头
可以见当cookie过期的时候火狐浏览器发送的请求头中会自动过滤掉已经过期的cookie,而Chrome浏览器则不会。而我们知道服务端的session是根据浏览器发送的cookie进行匹配的。因此火狐浏览器当客户端时间与服务端时间差距过大的时候会出现session无法正常匹配的情况

同理,Safari也会过滤掉失效的cookie。

解决方案

暂时没有比较好的解决方案,只能确保服务端的时间是正常的即可。

nginx使用笔记

简介

主要记录一些使用nginx的一些特殊的技巧。常用的不会记录在内

技巧

使用rewrite重定向时保留referer网站来源

首先我们要知道referer是浏览器主动发起时带入的。跟服务端无关。而浏览器在进行rewrite重定向时并不会带入referer,而目前没有成熟的解决方案。这里记录一个骚操作: 通过cookie来进行同域之间的referer传递

比如我有一个页面www.moonrailgun.com,我想检测当用户是手机端访问时跳转到m.moonrailgun.com
那么要这么做:

1
2
3
4
5
6
7
8
#在www.moonrailgun.com把referer数据写入cookie:
add_header Set-Cookie "referer=$http_referer;Domain=moonrailgun.com";

#在m.moonrailgun.com把referer数据根据cookie信息重新构造出来:
if ($http_cookie ~* "referer=(.+?)(?=;|$)") {
set $referer_cookie $1;
}
proxy_set_header Referer $referer_cookie;

ReactNative踩坑记录与学习笔记

原生交互

  • 在iOS9之后,网络请求默认为Https请求,如需支持Http,修改info.plist文件添加键值对设置允许http访问
    /images/react/001.png
  • 编译后抛出错误$export is not a function,原因: react-native 无法正常使用babel的runtime-transform插件,原因不明
  • react-native安卓端允许的最大长计时器时间为60000ms,而socket.io默认ping计时器为85000ms.为了解决这个警告你需要在服务端设置pingInterval(默认25000)pingTimeout(默认60000)使两者之和小于等于60000ms

路由react-navigation

Github

  • 路由插件解析需要依赖babel-preset-react-native插件:确保.babelrc文件中有"presets": ["react-native"]。否则会抛出语法错误
  • 当使用redux嵌套多个Navigator的时候。如果外面是一个StackNavigator然后子路由是一个DrawerNavigatorTabNavigator会抛出异常Cannot read property 'undefined' of undefined。解决方案:react-navigation#issues#1919

FlatList

不要把Array.reverse()和inverted一起使用! 更新时会有问题! 选一个即可

编译打包

安卓: cd android && ./gradlew assembleRelease

  • 当使用64位linux系统打包时抛出找不到aapt, 如果该路径下有aapt文件的话那么则是64位系统的问题。apktool需要32位编译环境。安装ia32-libs即可解决问题,如为centos则使用命令yum install libstdc++.i686 glibc.i686 zlib.i686
  • 如出现:app:bundleReleaseJsAndAssets 错误。可能是由于系统配置过低导致的编译文件超时的问题。解决方案是手动编译js文件后再打包(使用-x ":app:bundleReleaseJsAndAssets"参数跳过)
    1
    2
    3
    mkdir -p android/app/build/intermediates/assets/release
    mkdir -p android/app/build/intermediates/res/merged
    node node_modules/react-native/local-cli/cli.js bundle --platform android --dev false --reset-cache --entry-file src/app/index.js --bundle-output android/app/build/intermediates/assets/release/index.android.bundle --assets-dest android/app/build/intermediates/res/merged/release

测试环境使用独立包名(Android)

android/app/build.gradle

1
2
3
4
5
6
7
android {
buildTypes {
debug {
applicationIdSuffix ".test"
}
}
}

即在debug模式下。为包名增加后缀.test

在gradle.properties中保存敏感信息

因为gradle.properties可能会同时存在项目需要的信息与敏感信息。为了不把敏感数据上传到版本控制仓库,可以考虑使用git的命令

1
git update-index --assume-unchanged android/gradle.properties

使用方式是先提交需要上传的部分,然后执行上述命令即可

撤销标识

1
git update-index --no-assume-unchanged android/gradle.properties

升级到新版本

使用自动迁移脚本

1
npx react-native upgrade [version]

手动迁移

查看迁移网站https://react-native-community.github.io/upgrade-helper/