网络寻租

Programmer, Gamer, Hacker

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。不过这种发布方式我没有研究清楚,等研究过之后再比较吧。

Slack介绍-企业级实时通讯工具

| Comments

最近我在团队里面推广了slack,它是一个文字实时通讯工具,为什么要用它呢? 比起QQ,skype等通用实时文字通讯工具,有几个好处:

  • 文字交流内容可以在团队之间分享。
  • 交流按照channel主题分组,比如项目1,服务器管理,客服等。
  • 可以存档,以及搜索历史记录。
  • 安装了手机应用或者桌面应用了之后,可以实时收到提醒。
  • 可以和其他各种系统整合,比如redmine,gitlab,或者程序员撰写提醒插件(比如监控服务器性能,登录,应用发布,定期任务等)
  • 聊天机器人:机器人可以通过监控聊天室的内容,执行特定操作:
    • 这样团队成员可以控制机器人做一些事情(查看系统状态,执行特定任务等),这些控制指令可以在手机上面发出。
    • 机器人监控各种提醒,执行操作。

因为这些好处,我觉得slack这种工具值得所有的技术团队使用。

不过同时slack也有一些其他问题:

  • 聊天分组是按照channel来的,不能进一步区分,比如服务器管理下面还有系统性能,系统重启,系统登录等子分组。这样用户可以关注特定的子分组,而不会被大分组的其他内容刷屏。
  • 通讯不够实时,有的时候可能好几分钟才能响应,一些紧急时间几分钟是很长的时间了。

比特币介绍

| Comments

大家应该对比特币不陌生了吧,不过弄懂比特币原理的人应该不是很多。 这里有一篇中文介绍,讲得非常清楚。

主要的原理:

  • 比特币简称BTC,一个比特币可以拆分到10的-8次方。
  • 用户拥有钱包,钱包由公钥私钥组成。这样用户可以给交易做签名。
  • 比特币网络维护一个全局的账本,账本里面记录的是交易:一个用户转多少BTC给另外一个用户,所以BTC本质上是通过账本计算得到一个用户到底有多少钱。
  • 一次交易就是交易双方广播一条交易记录到比特币网络里面,被大多数人接受了,那么这个交易就成立了。

那么比特币如何产生?交易需要写入全局账本才算正式成立,全局账本拆分成一个个block,任何人都可以收集交易,创建block。每创建一个block,创建者可以在里面记录自己获得若干BTC,这是比特币产生的唯一途径。比特币网络每个小时只能接受若干数量的block,这样保证比特币产生的数量稳定。创建block的人要互相PK,谁运算量更大就更有可能被接受。创建block的过程叫做挖矿,挖矿可以获得比特币,同时也为大家服务:创建新的账本。

比特币协议规定,随着时间的增长,每个block获得的BTC会变少,总体来说,比特币是通货紧缩的(如果更多交易用到比特币的话)。开始的人更容易获得比特币,大家更愿意把比特币囤积起来一段时间。同时币值完全取决于市场,大家对比特币多有信心,它就值多少钱。比特币的用户数量,真实交易频率可以作为它生命力的象征。

我觉得比特币的实际用处在于交易本身。因为比特币是去中心化,以及不需要用户信息的,可以利用它进行匿名跨国交易,比如快速从中国转钱到国外或者相反,以及各种黑市交易。有了比特币,资金交易可以真正做到匿名安全。从这个角度看,它是相当有价值的。

因为账本是全局的,我们可以看到有史以来所有的比特币交易记录,比如这一条,里面列出来创建者获得25BTC,以及各种交易记录。我看了一下最开始的记录,应该是比特币的发明者自己留的,他留了至少100万个比特币,按照现在的币值,价值2亿美元。

初始创建者获利甚多,于是就有无数的山寨币出来。但是同时货币的去中心化,匿名特性,让它变得无保障,经常会有黑客盗窃,网上交易所诈骗等现象,用户同时也没有办法维权。加上币值容易被操作,个人觉得比特币不适合持有,而是用来进行货币交易转换。

采用node-webkit进行C/S架构客户端设计

| Comments

2014年我在医院信息科工作, 医院需要一个通知系统用来进行信息的传达,于是我设计了一个客户端的信息通知系统, 同时整合有医院各大系统的启动器。

选型

我希望能够做把通知系统做成医院的统一化信息化入口,同时带有信息通讯,通知传达,监控等功能。 医院上上下下有无数的科室,科室里面的电脑操作系统各异(虽然都是windows),性能也一般(有很多老的电脑)。

我选择采用C/S架构而不是B/S架构,因为很多操作(开启另外的应用程序,后台监控)需要系统权限,不能用系统自带的浏览器完成。 考虑了各种GUI框架:包括Qt,PyQt,winform,MFC,html,最后选择了node-webkit,几个原因:

  • 我对前端这一块比较熟悉,用html用来进行内容展示比较容易实现。
  • 客户端电脑只有IE,并且版本不一,node-webkit可以统一浏览器环境,方便后续基于浏览器的应用导入。
  • node-webkit可以执行相当多的本地操作,包括开启应用程序,执行后台脚本,窗口控制等。
  • node-webkit经过测试,可以跑在我们医院各种旧电脑上,不会有很大的性能问题。

设计

启动器:客户端带有程序启动器的功能,限制用户必须采用该系统启动各种医院信息系统(原先都是用桌面快捷方式的方案)。 用了启动器,才能强制用户看到通知,另外也把各种启动的方法都汇总起来。 启动器可以自动化安装:点击了某信息系统,如果发现本地没有安装,就可以连接到服务器上面,拷贝下来自动化安装。 autoit可以实现自动化安装的功能。 启动器也可以提供一些帮助功能:比如显示电脑的IP地址,开启远程桌面,链接到文件服务器等,方便系统管理员处理问题。

通知系统:通知系统整合到启动器中,内嵌一个网页,链接到远程的Rails服务器,展示通知。 有需要发放通知的科室,可以根据信息科发放的账户,登录到服务器中,撰写通知,发布。各个客户端会定时轮询远端服务器, 如果有新的通知,就会弹出窗口。这样可以达到全院信息传达的目的。 同时通知历史都存储在服务器上面,所有客户端都可以查看历史通知,以及下载通知带有的附件文件。

启动性能:医院的部分信息系统采用delphi开发,使用者已经习惯系统的瞬间开启。 新一代的软件基本上都达不到这样的开启速度,更别说是node-webkit了。我让客户端开机启动,驻留在系统中, 用户点击开启触发程序窗口,达到快速开启的效果。

更新:客户端软件的更新是一个大问题。我采用的方法:定时监控一台远程服务器上面的某个文件, 如果发现远端有更新(根据版本判断),后台下载新的程序文件,自动重启。因为windows上面可执行文件不能修改自己, 客户端首先运行一个加载小程序:如果发现本地有更新程序,拷贝更新程序,再执行node-webnit程序。 这个加载小程序我用前面说的autoit编写,启动速度很快。

本地缓存:考虑到客户端同时具有系统启动器的功能,当远端的服务器出现问题,本地要能够保持正常使用。 启动器功能都整合在客户端之中,启动器的每个项目,用json格式描述,方便拓展。

心得

系统开发过程中,我应用了node-webkit和autoit,发现node-webkit性能还是不够好(webkit内存占用还是太高,开启速度不够), 以及用html/js开发会有一些不稳定因素(开启页面有的时候会不能正常工作)。autoit是很简陋的编程语言,不是很方便使用。

客户端导入过程:我跑了医院无数科室安装,零零散散耗时一个多星期。最后客户端的稳定性我还是很满意的。 最担心的问题是自动更新系统工作不正常造成所有客户端不能访问,需要重新安装。通知系统设计得简洁,用户使用上面没有什么问题。

还有后话:我在医院待了半年多就离职了,考虑到整体系统的维护成本,离职后我们就把这个系统废弃掉不再使用了。

Docker介绍

| Comments

我们知道虚拟化的层次有:CPU指令集级别虚拟化,操作系统级别虚拟化, 还有一种进程级别虚拟化(container),底层都采用linux内核,在内核之上的CPU资源,内存,文件系统,设备,都用虚拟化技术隔离出来。 docker就是这样的一个工具。

docker用linux内核的资源控制功能(LXC)来隔离和控制资源,用AUFS来管理虚拟的文件系统,用docker可以提供一个轻量级的虚拟机,它的特性有:

  • 快速启动,一个进程起来可能只需要几秒钟。
  • 轻量级文件系统虚拟化,带有版本控制的文件系统,改动只保存变更,存储占用非常小。
  • 进程间隔离,保证进程之间不会互相干扰。

用docker可以把应用集装箱化,快速搭建,移动,复制,分发。对于应用开发,它可以让开发和发布环境归一起来, 开发阶段测试完毕之后可以直接装箱进入发布流程。

同时docker也有一些不适合的场景,比如有状态应用(存储),资源占用比较大,或者高性能网络应用,虚拟化会带来性能损耗。

docker以及container思想对于现在系统架构的冲击比较大,现在有一个CoreOS发行版,所有发布的应用都存放在container里面执行。