网络寻租

Programmer, Gamer, Hacker

什么通用数据交换格式更好

| Comments

考虑可以同时用来应对程序和人都需要阅读状况的通用数据格式, 几个层面:

首先是数据格式,需要能够支持:string, bool, number, time。 它们都应该是标准化了的。考虑不增加symbol,因为和string有概念上面的重复, 使用上会产生困扰。

然后是数据的组织方式。因为组织本身是不可变的,用map和list能够覆盖所有类型的数据组合方式, 并且是完备的。tuple这种冗余不需要。

最重要的是数据的表示方式。 人工阅读的数据应该容易阅读,所以数据要有换行和缩进,没有xml这种尾部冗余。 数据组织之间的切割应该用分割符,而不是换行。类似yaml这种换行分割会带来更多的问题。 数据应该同时有两种表示方式,一种文本格式,用utf8编码,方便人阅读, 一种二进制格式,机器处理更快,两种方式能够互相转换。

关于xml,如果说json是类型的组合,那么xml就是物件的组合,默认物件拥有属性和子项, 所以xml我觉得是一种非通用的格式,本质模型不适合进行通用数据交换。

根据上述考虑,我觉得合适的方案是:格式化之后(换行缩进)的json,结合bson来用。 比如RESTful的服务,返回的json默认增加缩进,同时也提供bson格式的返回,不考虑其他格式。

局域网时代下科学上网

| Comments

(2015-08-29更新SS方法)

我们知道,最近各大运营商都提速了,大家随便就可以买到100M以上的网络。 但是国外出口没有配套提升,这样的结果就是,分到每个人的国际出口带宽就少了, 访问国外网站就成为一个非常痛苦的过程。中国的网络就此成为一个名副其实的局域网。

要解决访问国外网站的问题,需要两步:

  • 要有一个保障国际出口带宽的国内服务。
  • 要有一个国外流量中转服务来绕过我们都知道的防火墙干扰。

用户连接国内VPS的方案可以是VPN,可以是代理。我偏好VPN,因为全局翻墙下各种命令行工具也是可以用的。对于普通人来说只需要访问网页,各种代理协议也可以。

国内VPS连接国外VPS的方法最好是常见的VPN,为什么呢,因为常见VPN各大国内外企或者国内互联网公司也在用,防火墙不太会干扰这些主流业务。 如果用ssh,或者其它代理或者不知名的协议,一方面会被防火墙干扰,另外一方面包协议没有足够好的优先级,性能会差一些。

我觉得最好的解决方案是:国内一台有良好国际出口带宽的VPS和一台国外VPS结合,提供pptp/l2tp/anyconnect VPN服务。

这种方案我没有配置成功,因为不知道如何让国内VPS来做VPN中继。只能用其他的方案了: 结合微林的云涟服务以及ssh代理连到我买的国外VPS上网。

云涟是一个提供良好国外出口流量的服务,方法是用openvpn或者anyconnect连接到一个阿里云里面CN2网络的VPS上面。 有了好的出口带宽,之后用本地电脑命令行的ssh连到国外VPS,提供本地的一个sock5代理,再在浏览器上面设置代理访问网站。

具体操作:

  • 注册微林,按照教程,本地电脑连接上VPN。
  • 购买一台便宜的国外VPS,比如DigitalOcean
  • 如果你是linux或者mac,在命令行执行 ssh -ND 1080 user@digital_ocean_vps_ip。
  • 设置浏览器的代理,用sock5,地址是127.0.0.1,端口1080。

我现在用这种方法,访问国外网站非常顺畅,速度可以达到1MB/s。 不过,成本会高一些,云涟的费用是1块钱每G,国内流量的费用是比较高的。 国外VPS就便宜了,DigitalOcean是5美元一个月。

期待有科学上网服务商提供这样的整体方案,省得大家自己弄了。

2015-08-29更新:

因为ssh提供sock5代理的性能问题,以及有协议特征容易被防火墙干扰,我建议大家换用shadowsocks。 它是一个python写的客户端/服务器分离的sock5代理(国外服务器跑server端,client端和server端加密通讯,client端再启动一个sock5服务)。 现在作者被喝茶,官方项目已经废掉了,不过还有其他的镜像

客户端和服务器双方预先保存密码,没有登录等操作,直接用密码加密通讯, 同时可以选择多种加密方式,这样通讯没有协议特征,防火墙没有办法干扰。 不过还是会有流量特征,防火墙如果开启智能学习的话,会把通讯劣化掉。不过现在好像没有进行这样的干扰。 同时因为跑的是非主流协议,运营商会降低通讯的优先级,造成性能差一些。

使用方法:

  • 购买一台便宜的国外VPS,比如DigitalOcean
  • 在VPS上面安装SS,然后执行ssserver -p 4431 -k password。记得修改-k的密码参数。
  • 注册微林,在vxTrans里面创建一个端口映射,指向你的VPS服务器的4431端口。通过微林的CN2精品网络保证出国带宽。
  • 在你本地机器上面安装SS,执行sslocal -s 微林服务器地址 -p 微林服务器分配给你的端口号 -k password -l 1080,这样连接到微林的端口转接,同时在本地启动了一个1080的sock5服务。
  • 设置浏览器的sock5代理,指向本地服务器的1080端口。
  • 如果你希望在命令行下面翻墙,可以安装proxychains-ng,OSX下面可以用brew安装。

是否要用装修公司

| Comments

最近忙装修,装修公司挑了无数家。在我们这种四线城市, 很多人不会去找装修公司,而是自己找装修队伍,我不是很认同这种方法, 诚然,自己找装修队可以节省好几万的费用,但是钱不是这样省的。

装修过程有几个方面:

开始的格局设计:考虑什么房间做什么事情,里面的家具如何放置,动线,通风,采光什么的。 没有在这个阶段深入思考,装修出来发现问题再改就来不及了。某朋友自己装修, 完了才发现一个房间空调没有地方放,再打洞就会毁坏掉房间的装潢,很麻烦。 我们购买的是复式楼,在找设计公司的时候,群策群力,分别提出了各种的方案,比如考虑楼梯的位置,就有三种选择。 每种选择都有不同的功能区域划分,然后功能区还需要细化。如果不找装修公司,自己是想不到这么多方案的。

硬装:包括水电,木工泥工什么的。工程方面有很多很多的门道,自己可以去研究,弄清每道工艺, 但是再怎么研究,也比不上有经验的施工人员。施工的过程中也会出现一些突发状况, 需要有施工经验的人帮助参考。某朋友自己找施工队,楼顶敲出来三处,结果防水没有做好,返工了好几次, 损失的钱都可以找装修公司了。

软装:我们去过一些朋友家,自己装修和找人装修的效果区别巨大,很明显就认出谁的家是设计过的。 装修很花钱,效果好不好却很大程度体现在软装上面,一般人对空间色彩不够敏感,也没有很多的设计可以选择。自己装修,花大笔钱,最后却没有一个好的样子。 除非自己是设计领域的,软装最好还是找人把关,自己乱买东西最后搭出来一个古怪的风格。

总而言之,专业的事情交给专业的人员。不要什么事情都自己做。 大脑容量是有限的,多充实自己的专业知识赚更多的钱购买服务更划算。

至于行业鱼龙混杂,找不到好的装修公司,或者找到装修公司乱开价的问题,就是“战术”层面的问题了。

我的看法是:根据开销,选择对应档次的装修公司。普通人也不需要顶级的设计师和装修公司, 我觉得满足要求的就可以了(当然满足基本要求也需要挑好久)。最好是原先有经验的设计师出来单干开新公司,价格上优惠,人员也是有经验的。

至于装修公司乱开价问题,这种非标准化服务,没有办法横向比较,只能自己多研究,可以找一些连锁的公司,管理规范一些。 收费方面每个项目自己都要弄明白,这样才不容易被蒙骗。装修不能做甩手掌柜。

另外装修公司也是希望把项目做满意的,项目满意名气就更大,名气大了来的人多,收费也可以更高。 走上合作的道路对双方都有好处。

在项目过程中,顺利的沟通非常重要。 设计师能够提供专业领域的把关,但是生活方式和房子的风格还是只有自己清楚, 需要让设计师弄清楚自己的需求,而不是顺着设计师的思路,弄出一套样板房,自己住着不舒服。

更好的命令行

| Comments

命令行大家应该都熟悉了,但是命令行其实是很烂的, 输入和输出都采用字符串的方式,而不是一个规整的数据结构。 这样的后果是,每个程序都需要自己解析输入,以及提供一个特殊结构的输出, 很容易出现解析上面的问题。

从本质上面说,命令行就是一个函数执行的接口,终端就是一个状态机, 每一个命令就是一个函数。那么为什么不像通用的编程语言的函数一样来定义?

比如这样来写:

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
# 普通操作
$> date
=> "Sat May 23 09:18:38 CST 2015"
$> rm('/fdas/fdas/fda', directory: true, force: true)
=> true

# 输出结构化
$> ls
=> [{mode: 'drwxr-xr-x', user: 'halida', group: 'halida', size: 123, name: 'Gemfile.lock'}]
$> ls(columns: [:name, :size])
=> {name: 'Gemfile.lock', size: 123}

# 管道
$> history >> select{|row| row[:cmd] =~ /mina/ }
=> [cmd: {'mina init'}, run_at: '2014-02-01 10:00:21']
$> ls >> filter(:name)
=> ['Gemfile.lock']

# 很多命令应该用面向对象的方式来执行,更容易理解
$> master = git-get-commit('master')
$> master.files.count
=>3
$> master.committer
{name: 'James', email: 'james@gmail.com'}

# 输出日志就算是以文字的方式体现,也最好结构化起来,方便解析
$> log = log("Process file (filename) finished, spend time (spendtime)", filename: "xxx.mov", spendtime: 220.seconds)
$> log.spendtime
=> 220.seconds

网上搜索了一下,看起来没有什么好的解决方案,有时间我可以开发一个看看。

Rails导出数据经验整理

| Comments

最近做了一些rails数据导出的工作,就是把一些特定的ActiveRecord数据挖出来,保存到表格里面。 需要注意几件事:执行速度,内存消耗,以及调试速度。

执行速度

导数据的程序基本上就是一个循环体,外部获取数据集,内部把一条数据转换成表格。 在内部,往往需要通过一条记录作为主体,通过数据库逻辑关系顺藤摸瓜挖出一批数据, 这样会形成一批短查询,因为是在循环体里面,会带来很大时间上的消耗,比如:

1
2
3
4
out = []
Unit.where("active_at" < Time.now).each do |unit|
  out << [unit.company.name]
end

可以先把这些查询汇总起来,一次查询掉,然后在循环体内部筛选出对应的数据:

1
2
3
4
5
6
out = []
units = Unit.where("active_at" < Time.now)
company_names = Company.where(id: units.pluck(:company_id).uniq).pluck(:id, :name).to_h
units.each do |unit|
  out << [company_names[unit.company_id]]
end

内存消耗

查询大量数据的时候,可以首先查所有的ID,然后分批查询,这样防止序列化大量的数据库对象:

1
2
3
4
5
6
7
unit_ids = Unit.where("active_at" < Time.now).pluck(:id)
group_size = 100
unit_ids.in_groups_of(group_size, false) do |ids|
  Unit.where(id: ids).each do |unit|
    ...
  end
end

数据全部缓存在一个array中的话,会占用大量内存,最好是通过数据流的方式一个个输出处理,用后即丢:

1
2
3
4
5
6
7
8
9
def export
  Unit.where("active_at" < Time.now).each do |unit|
    yield([unit.name])
  end
end

CSV.open('out.csv', wb) do |csv|
  export { |row| csv << row }
end

在循环体内部,尽量用局部变量,不用的资源会更早释放。

跑数据导出的时候,最好同时注意一下服务器的剩余内存。不要把其它服务搞挂了。

调试速度

导数据最花费时间的往往还是调试过程。

调试的时候,可以只返回几条数据,检查完毕之后再全部跑。

1
2
3
4
5
6
7
debug = true
index = 0
Unit.where("active_at" < Time.now).each do |unit|
  yield([unit.name])
  index += 1
  break if debug and index >= 10
end

需要考虑数据并不是很规整,做好预防性编程。

1
2
3
Unit.where("active_at" < Time.now).each do |unit|
  yield([unit.company.try(:name)]) # 公司可能不存在
end

很多时候难免特定数据不符合预设状况,最好循环体内部记录log,出现问题可以跟踪。 比如下面的例子,unit没有oldest_driver的时候,会报错,记录了日志,就知道在哪条上面出现了问题。

1
2
3
4
5
6
7
logs = []
Unit.where("active_at" < Time.now).each do |unit|
  logs << "unit: #{unit.id}"
  oldest_driver = unit.drivers.order('age desc').first
  # 
  yield([ordest_driver.name])
end

导出数据可以生成两个版本,一个给客户,另外一个加上一些debug数据方便自己分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def export
  Unit.where("active_at" < Time.now).each do |unit|
    yield([unit.name, 'debug', unit.id])
  end
end

CSV.open('out.csv', wb) do |csv|
  CSV.open('out_debug.csv', wb) do |debug_csv|
    export { |row|
      debug_csv << row
      csv << row[1..1]
    }
  end
end

架构设计

代码架构上面最重要的是职责清晰。导出数据的逻辑比较简单,分离出导数据类,以及处理数据类就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module Exporter
  def iterator_to_csv(filename, iterator)
    CSV.open(filename, wb) do |csv|
    iterator.call { |row| csv << row }
  end
end

class ExportActiveUnits
  attr_accessor :time
  def export
    Unit.where("active_at" < time).each do |unit|
      yield([unit.name])
    end
  end
end

eau = ExportActiveUnits.new
eau.time = Time.now
Exporter.iterator_to_csv "out.csv", eau.method(:export)

学习ASP.NET心得

| Comments

因为现在公司的一个网站项目是用ASP.NET写的,我周末抽时间学习了一下相关的概念,对于ASP.NET有了一定的了解。 学习过程就是看一遍W3School的教程。 我没有下载Visual Studio实际编写,因为只需要能够看懂项目代码就可以了。

总体来说,我觉得微软的技术有这些特点:

复杂:微软为了让普通程序员能够上手,做了大量的工作,集成在Visual Studio里面。 你用微软的集成环境,按照教程一步步做,就可以跑出来一个东西。但是在这个过程中,你会发现接触到一堆微软特有的概念,以及用到大量微软设计出来的工具。 可能有的人认为微软的东西比较简单,刚刚毕业的大学生就可以拖拉出来一个东西。但是仔细看看,就会发现微软的东西非常复杂,每一步都隐含了大量的复杂度。 微软有自己的设计思路和概念,但是感觉挺别扭,要么是离本质有点远,要么是文档讲了一堆,你还是搞不清楚它到底是什么。

既定路线:如果按照微软设计的路线走,可以很快开发出来一个东西,但是如果需要定制,就会面对重重阻力:工具和库假设你会按照这条路走。 如果说这条路线很顺也就罢了,但是我感觉微软的设计总是不够好,感觉有些偏门,不是“大道”。

依赖:学习一个东西,牵连出来需要学习一整块的东西,而不是单独学习一个内容。同时用了微软的一个工具,就要用微软的一套工具。 这是微软的赚钱战略,如果只生存在这个环境里面就没有什么问题,但是眼界宽一些,感到被限制住就有点难受了。

学习曲线:使用微软提供的解决方案,能够很快进入状态,写一个东西出来,比单独组合工具学习曲线低。 但是再更深一步就很困难,学习曲线就陡峭起来。这里面就有一个平衡:选择更强大的工具,还是更容易理解的工具。 更强大的工具能够更快,但是带来了更多的复杂度,复杂度过高,人就无法控制了。

现在用微软解决方案的人越来越少,这是好事。作为程序员其实自主选择容易被大环境控制,其他人如果都用一个烂工具,很难独善其身。 你不可能采用一个很少有其他人会用的技术,一个是技术成熟度需要程序员的时间去堆,另外一个是项目不是一个人的事情,要有其他人一起做,或者至少能够找到维护人员。

微软的操作系统也是,国内基于微软的生态系统乱七八糟,作为程序员自己可以用linux或者apple的生态圈,但是总是免不了被其他人拉回来, 重装系统,解决问题。看到操作系统差劲的用户体验,漫天飞的木马病毒,大大小小的流氓软件,死活找不到东西的搜索引擎,不由觉得普通人真辛苦。

用chef对少量服务器进行配置管理

| Comments

原先介绍了chef,现在需要面对一个实际的问题:如何用chef管理少量的服务器。

我希望:

  • 能够对几台或者十几台服务器进行配置管理。
  • 针对每台服务器,写yml格式的配置文件,执行一个命令之后,就可以配置好这台服务器,同时源码管控这个yml文件的变更。
  • 支持复杂的服务器配置,包括启动项目管理,自动告警,日志归总等。
  • 不需要管理服务器,比如chef-server这样的东西,只需要留有本地的配置文件。

我采用的解决方案:

  • littlechef,这个项目可以把chef-solo,一个本地跑chef的方法,部署到远端服务器上面去,同时拷贝本地的recipe和配置文件到远端,执行需要的操作。
  • 写recipe,让部署能够通过写node配置文件进行配置,比如启动服务,日志归总,服务器管理员用户,自动重启更新等。
  • 用yml格式撰写node配置文件,以及datatag配置文件,然后用自动化脚本转换成json格式。手动写json太反人类了。
  • 远端的脚本用ruby写,脚本里面的参数不是用erb渲染出来的,而是把配置序列化成yml,ruby脚本再读取它们。这样远端服务器上面的执行代码是规整的,人可以阅读。

都弄好之后,可以写这样的服务器配置文件:

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
---
  # 服务器名字
  name: "test"
  # 基础配置的recipe
  base-system:
    # 是否跑软件包更新
    update: false
    # 是否设置自动更新
    auto-upgrade: true

    # base-system会自动创建一个deployer管理员用户,谁可以通过公钥的方式访问这个用户
    authorized_keys:
      - "linjunhalida"

    # 写在databags里面的配置参数,可以连接到slack发提醒
    notify: 'test'
    # 是否每天判断服务器是否需要重启或者更新
    check:
      need_reboot: true
      need_upgrade: true
    # 设置服务器定期重启
    schedule-reboot:
      minute: '26'
      hour: '8'
      weekday: '2'
    # 设置服务器自动升级安全更新
    schedule-upgrade:
      reboot: true
      minute: '1'
      hour: '0'
      weekday: '1'
    # 服务器启动的时候跑的应用
    start:
      - name: "railsapp"
        user: 'deployer'
        pwd: '/home/deployer/apps/railsapp/current'
        cmds:
          - 'bundle exec thin restart -C config/thin.yml'
          - 'RAILS_ENV=staging bundle exec rake resque:restart_workers'
          - 'RAILS_ENV=staging bundle exec rake ts:restart'

  run_list:
    - "recipe[chef-solo-search]" # so chef-solo search can work
    - "recipe[base-system]"

chef部署之后,会创建目录/etc/base-system,里面存放各种配置和执行脚本,同时安装到crontab,/etc/rc.local等各种地方。 服务器的关键操作,比如重启,安全更新结果,都会通过notify功能汇报到slack上面。 我可以通过node配置文件,清晰看到每台服务器是如何配置的。并且这个系统可以演化,更换一种配置方式,只需要重新跑一下部署。

不过还是有一些难办的问题:

  • littlechef项目成熟度不高,使用起来不是很舒服。
  • 架构复杂:chef已经很复杂了,远端还要部署一个ruby环境,架构复杂带来调试和理解上面的难度。
  • 学习成本高:维护者需要弄懂chef,远端编织起来的ruby框架,之后才能配置服务器,最后可能只是需要加上一个小东西。
  • 维护成本:又多了一个项目了。

正确使用社交网络的方法

| Comments

我们常用的社交网络有:微信,微博,QQ,豆瓣,它们消耗了我们大量的时间,其中绝大部分都被浪费了。

我们在无聊的时候会拿出手机,刷一下动态。刷动态的时候,大脑通过接受信息,获得了奖励:感觉到和他人建立了联系,学到了新的知识,享受到了快感。 这个过程强化了神经反射:我们有了冲动,经常拿出手机来刷一下,期望能够获得奖励。这是上瘾症状。

我们需要反省一下,应该如何更好地使用社交网络。它对我们的用途有:

  • 娱乐:从其他人分享的内容上面取乐。
  • 关注认识的人:获取动态,能够保持联系。
  • 关注有意思的人:获取资讯和洞见。

在满足这些用途的前提下,我期望能够更少消耗时间,更好和他人保持联系,获得真正重要的信息。所以这里列了一下更好的使用方式:

  • 社交网络核心的用法是记录人:记录认识的人的联系方式,收集有趣的人的讯息。
  • 我不建议把社交网络作为娱乐方式,因为:娱乐正是给我们大脑奖励的主要手段,娱乐性内容是成瘾的主要一环,娱乐要么正正经经地玩个痛快,而不是一点点的享受,欲求不满容易刺激上瘾。
  • 和他人的沟通和联系,尽量用语音聊天或者见面聊,时间成本更低,收益更高。
  • 保持联系和关注认识的人:每天最多过一遍认识人的动态,尽量回复,而不是点赞,这样他人对你印象更深刻。觉得无关注价值的人,就不用看他的资讯了,记录联系方式即可。
  • 获得洞见:大多数的信息是碎片化的,不成体系没有阅读价值,建议通过看书获得。最新资讯可以关注一些高质量的讯息来源。
  • 只利用碎片时间查看和处理动态:没其他事情可做,以及有明确截止时间。比如:等车,等饭,上厕所的时间。整块的时间里,千万不要打开这些手机应用。

Rails容灾恢复策略

| Comments

我们可以把一个运行的rails环境,根据处置的方式,拆分成以下几个部分:

  • 系统环境:跑rails必须的服务器系统环境,比如特定版本的ruby,各种第三方库,第三方工具等。
  • 项目配置:和当前运行环境相关的变量,比如数据库帐号,各种第三方API和服务的连接方式。
  • 项目代码:除去和运行环境相关的rails应用代码。
  • 应用数据:比如数据库,本地一些需要持久化的文件等。

拆分的原因是因为以上几个部分有不同的特性,在构建,备份,恢复的过程中需要用不同的方式来对待。 特性包括是否经常发生变化,变化的频率,是否和运行环境相关等。

首先看构建过程。构建过程往往非常繁琐,需要能够自动化进行。拆分成以上几个部分后,构建方案如下:

  • chef构建系统环境。
  • cap把项目配置和项目代码构建出应用的执行环境。
  • backup Gem备份和恢复应用数据。

发布新版本,因为只改动了项目代码,只需要重新用cap重新构建执行环境。 需要增加配置,或者修改应用数据,也不复杂,执行一些特定脚本即可。

系统恢复:可以重头构建,但是时间消耗太长,更好的方案是针对服务器做镜像,也可以用docker做应用级别的镜像。 另外也需要备份好项目配置,以及隔离和备份出来应用数据。 出现问题的时候从镜像创建环境,修改配置,恢复备份的应用数据,然后发布最新的代码。

Rails项目的发布

| Comments

capistrano/mina可以用来发布rails项目,它们是应用发布的最佳实践。

关于应用发布,需要满足以下要求:

  • 发布的版本确保可以运行之后,才替代现有版本。
  • 留存有旧的版本,必要的时候可以恢复。
  • 发布版本不应该带有版本管控信息,防止不必要的信息泄漏。
  • 配置文件,数据文件和代码分离:另外存放,不和发布的代码混在一起。

capistrano/mina的解决方案:

  • 发布的各个项目版本放在releases目录下面,各个版本的目录名称按照1,2,3的顺序递增。
  • current是真正跑的版本,是指向release的软链接,当新项目发布成功的时候,再修改软链接。
  • scm目录存放带有项目管控的代码。
  • shared目录存放配置文件,数据文件,按照需要软链接到各个发布版本里面去。

配置:

capistrano(以下简称cap)写一个config/deploy.rb,里面定义了一系列的rake任务,以及一系列的role(角色,比如数据库,应用服务器,网站服务器), rake任务定义了在什么role上面执行什么命令。各种配置环境写在config/deploy/文件夹里面,命名producton, staging等, 当需要发布项目的时候,执行cap production deploy,就根据配置环境和deploy脚本执行操作。

执行任务过程如下:

  • 创建目标环境releases/n
  • scm获取最新的项目,然后根据配置中指定的版本号,拷贝代码到releases/n
  • 初始化releases/n
  • 把app/release/23链接到app/current,然后重启服务
  • 清理releases/目录,只保留最新的几个版本

cap在服务器上面执行代码的方式,是通过维护一个ssh连接实现的,每次执行任务都要上传命令,返回结果,如果ssh连接比较慢的话,整体消耗时间就很长。 mina它的原理是生成一个bash脚本,上传到服务器上面执行,这样执行效率比cap高太多,大家可以考虑作为替代使用。

这种方式是传统的编译发布,另外有直接发布环境的方法,比如用docker。不过这种发布方式我没有研究清楚,等研究过之后再比较吧。