BitShares API 服务器架设指南 - 公共API篇

接上一篇个人API服务器的架设,这一篇探讨一下公共API服务器的架设。目前暂时还没有最佳实践的规范,行业内很多比特股技术爱好者和先行者都在摸索尝试,也希望这篇文章能引起大家的关注,一起来探讨如何能够最有效的架设公共API服务器,为更多用户提供数据支持服务,提升用户体验,从而推动比特股生态的发展。

公共API服务器一般架设在Linux服务器上,也面向专业技术用户,所以文中更多用命令或者代码来阐述,少了些描述性文字。欢迎技术同好轻拍。因为我目前架设的服务器主要面向国内用户,所以服务器的选择,域名解析的选择,包括本文用中文写,多偏向国内的使用。

我们这里谈到的 API 服务器,实际上有以下几种用法,不同的用法,硬件要求及配置上有很大不同:

  1. 个人使用
    主要是个人用户在使用轻钱包或者网页钱包时,使用运行在本地,独占使用的API服务器。现代的个人电脑配置基本就足够了。请参见个人篇

  2. 公共 API 服务器

    提供一个公共的API服务器,向公众开放服务。一般需要是托管在IDC的服务器或者从云服务提供商那里租赁的VPS,对配置带宽都有一定要求。

配置公共API服务器

配置一个运行在公网上,面向普通用户的API服务器,包括以下几个步骤。

  1. 域名
  2. 租用服务器
  3. SSL证书
  4. 配置站点首页
  5. 配置并架设节点
  6. 配置nginx负载均衡
  7. 架设钱包网站(非必须)
  8. 架设水龙头(非必须)

我们举例使用 dexhub.io 这个新域名,最后要实现的目标是:

域名

这部分就简化了,大概人人都会,可以去godaddy注册,比较便宜,选择也多。国内的域名注册商也是可以。不过需要注意到是,如果租用国内的服务器,域名必须完成实名制和备案。否则会造成域名访问被屏蔽。

我一般喜欢注册完后用 dnspod 来做域名解析。在国内解析效果不错,很稳定,如果升级到付费套餐的话,提供更多的智能线路解析,还有A记录,CNANME记录的负载均衡选项。比如在南方机房配置了服务器,也在北方机房配置了服务器,通过域名解析自动指向离用户最近的服务器,这样可以在第一个环节提升用户访问时的速度。当然还有个因素是如果站点访问量比较小,DNS解析量少,local dns就几乎没有缓存,理论上解析速度就慢,反之亦然。

国内其他的域名解析商提供的服务之间的比较我没有深入研究过,比如阿里云的DNS,口碑似乎还不错,欢迎各位补充,这个话题可以聊得很深。

域名解析记录略过。

服务器租用

怎么租用也不废话了,我这里用的是阿里云的服务,购买了服务器,我们需要讨论下配置和架构。

我们现在知道API服务器在提供服务时,以下几个要素需要特别关注:服务器带宽、内存、CPU。经过测试,我觉得下面的架构比较合适。前置一台nginx服务器进行负载均衡,将wss请求分配到后段的多台节点服务器,可以根据监控使用状况,或者增加后端节点服务器,或者增加nginx服务器的带宽是配合实际需求。

在测试例子中,我在阿里云上租用了2个ECS示例,配置都是8核32G,硬盘空间不算贵,各挂了200G的云磁盘,好在IO读写不吃紧,问题不大,所以没用SSD;1个RDS示例,配置可以最小的,为水龙头服务准备,非必须。其中一台仅作为节点服务器,运行witness_node,另一台充当多个角色,即作为节点服务器运行了witness_node,也运行了nginx,充当web服务器。喜欢干干净的话最好各个角色独立出来。

所以目前架构图大致如下

architecture

ECS A 和 ECS B是2个ECS示例,其中ECS B上运行了2个witness_node节点,ECS A上运行了一个 witness_node,并且运行了nginx,通过nginx将 wss 请求均衡转发到后端的对它来讲可用的3个节点上。nginx并负责SSL、网页钱包等资源服务。

两台服务器的内网IP假设分别为10.10.10.110.10.10.2

在nginx服务器上先安装nginx,以及SSL证书。

sudo apt-get update
sudo apt-get install nginx

我们并且在2台服务器上创建了用户deploy,用户目录位于/home/deploy,用户有sudo权限。

# 当前是root登录的系统,创建用户 deploy
adduser deploy
adduser deploy sudo

以后我们就用deploy用户通过 ssh 访问系统了。这里最好配置 sshd 以后不允许 root登录,也不允许使用密码登录,只允许用PublicKey登录,摘要如下。

# /etc/ssh/sshd_config

RSAAuthentication yes
PubkeyAuthentication yes
PermitEmptyPasswords no
PasswordAuthentication no
PermitRootLogin no

所以,后面的代码例子都是以deploy用户执行的,必要时才进行sudo进行提权。这里只是最基本的安全设置,再怎么强调安全都不为过,如果是生产服务器的话,请进一步加强安全方面的配置。

SSL证书

数字证书可以认证服务器身份,防止钓鱼网站攻击;同时对用户浏览器与服务器之间的数据传输进行双向加密,防止传输数据被泄露和篡改,抵御中间人攻击。所以,提供公共API服务,请务必给你的站点加上SSL证书,并让服务运行在https(wss)上。wss是Websocket Secure的缩写,是加密版的Websocket协议。

SSL证书没有所谓的“品质”和“等级”之分,只有三种不同的认证类型。SSL证书需要向国际公认的证书证书认证机构(简称CA,Certificate Authority)申请。

根据验证级别大致分以下几种,不同的CA叫法可能不同:

  • DV (Domain Validation):域名认证。信任等级普通,只需验证网站的真实性便可颁发证书保护网站
  • OV(Orgnization Validation):信任等级强,须要验证企业的身份,审核严格,安全性更高
  • EV(Extended Validation):增强型,信任等级最高,审核严格,安全性最高,同时可以激活绿色网址栏。

根据支持的域名数量,大致有:

  • 单域名:只支持单一域名,比如 www.acme.com (含 acme.com )
  • 多域名:只支持有限数量的子域名,比如3个,a.acme.com, b.acme.com, c.acme.com 用完即止
  • 通配符:支持无限数量子域名,*.acme.com

显然通配符证书更灵活,常常开始时,你只想着我只需要一个网站就好了,渐渐发现还需要api域名,静态资源域名,后端中台域名等等,如果使用前2种就比较尴尬。

SSL证书有免费的,也有付费的。

国内的主要云服务商都有提供SSL证书签发服务,有免费的,国外 StartSSLLet's Encrypt (教程) 并提到比较多,我自己喜欢用 RapidSSL 的这个通配符版的,价格还合适。

不同的服务商申请时细节或有不同,可参考服务商网站上的详细帮助,但是基本上都包含以下步骤,以购买RapidSSL® Wildcard Certificate,支持通配符,为例。

1 购买证书:访问上面网址,选择证书年限,比如2年,到期需要续约。加入购物车后填写基本信息完成支付。

2 完成支付的订单,会要求生成一个certificate,提供common name,就是你的根域名,比如dexhub.io。选择验证方式,邮件或者网页。我选择了网页,也就是到时候需要根据要求在网站根目录下放置一个文件,能被读到,那就证明了我对这个域名的网站是有管理权的,通过这个方式来实现域名认证。

request certificate

3 下一步就是要根据提示生成CSR(Certificate Signing Request)文件了,这个文件的意思是要在你的服务器上使用服务器端私钥签署一个包含了网站、组织信息、域名等信息的文件,来证明证书请求真实有效。将来最后发行的证书需要服务器端私钥配合才能工作,如果私钥丢失了,这个证书也就等于失效了。所以务必备份。

那么生成CSR,我们首先要生成服务器端私钥,我们只需要在nginx所在的服务器上生成私钥,前面讲到的架构中,由nginx和后端节点服务器进行沟通,他们之间不使用secure协议。

登录ECS A nginx 服务器

sudo mkdir -p /etc/nginx/ssl
cd /etc/nginx/ssl

#生成private key 
sudo openssl genrsa -des3 -out server.key 2048 

#这里问你输入一个passphrase,选择一个容易记得,下一步会需要输入。 
#生成 CSR 
sudo openssl req -new -key server.key -out server.csr 

Country Name (2 letter code) [AU]:CN #国家代码 
State or Province Name (full name) [Some-State]:Shanghai #省份 
Locality Name (eg, city) []:SH #城市 
Organization Name (eg, company) [Internet Widgits Pty Ltd]: DexHub Inc #公司名称 
Organizational Unit Name (eg, section) []: #部门名称 
Common Name (e.g. server FQDN or YOUR name) []: *.dexhub.io 
Email Address []: admin@dexhub.io #管理员邮箱
Challedge Password: #留空

现在 /etc/nginx/ssl 目录下有两个文件了,server.keyserver.csr,前者是有密码保护的服务器端私钥;后者是CSR文件。我们需要把CSR文件的内容复制黏贴提交到RapidSSL网站上要求我们输入的页面中去,如下图:

CSR

点击 Continue 后进入下一页,显示CSR中decode出来的信息,你可以再审视一遍,确认后提交。如果成功,会出来下一步提示,要你在域名根目录下创建文件,包含特定内容,确保能被访问的,从而被验证。

fileauth

那这里我们就快速创建文件,并且简单配置下nginx一下。

# 设定 /home/deploy/www/dexhub 为我们站点的根目录
mkdir -p /home/deploy/www/dexhub/.well-known/pki-validation
echo "20170619075914rcoo8ydy274hz1as0afn5sc5cxyfs4suu7554ch0fejqv9lqae" > /home/deploy/www/dexhub/.well-known/pki-validation/fileauth.txt

# 新建dexhub.io的站点配置文件
sudo touch /etc/nginx/sites-enabled/dexhub.io.conf

最简单的nginx配置文件

server {
  listen 80;
  
  server_name dexhub.io www.dexhub.io;
  
  location / {
    root /home/deploy/www/dexhub;
    
    index index.html;
  }
}

别忘了reload nginx配置文件 sudo /etc/init.d/nginx reload

测试一下 curl http://www.dexhub.io/.well-known/pki-validation/fileauth.txt看看内容正确否。

然后就等着吧,快的话几分钟后就能收到邮件,说是Approved了,这个是应该很快的,因为都是自动的。然后根据邮件里的提示进行了。还是那句话,不同的服务商指示略有不同,照着提示做就可以了。

这里我就略过了,最终拿到的是一个 server.crt 文件,我们把它放到 /etc/nginx/ssl目录下。现在目录下有3个文件:

  • 服务器私钥:server.key

  • 数字证书: server.crt

  • CSR文件:server.csr 这个文件后面就不再需要了,可以保留可以删除。

配置站点首页

这一步,我们要实现用户可以通过 https://dexhub.io 来访问。也就是第一步,确认数字证书能够正常工作。

之前我们生成的server.key是有密码保护的,我们现在去掉密码,这样nginx就可以自由自在的启动了。

cd /etc/nginx/ssl
sudo cp server.key server.key.org # 保留一份原始的
sudo openssl rsa -in server.key.org -out server.key

修改 dexhub.io.conf变成这样。

# 重定向 http 请求到 https,保持路径
server {
   listen 80;
   server_name dexhub.io www.dexhub.io;
   rewrite ^(.*) https://$host$1 permanent;
   
   access_log /dev/null;
   error_log  /dev/null;
}

# https 站点定义
server {
  listen 443 ssl;
  
  server_name dexhub.io www.dexhub.io;
  root   /home/deploy/www/dexhub;
  index  index.html index.htm;

  include ssl_conf;
 
  location / {
    try_files $uri $uri/ /index.html;
  }  
}

这里我们将 http 请求强制重定向到 https 上。在 /home/deploy/www/dexhub/ 就是我们的根目录,index.html就是首页了,可自由发挥,制作精美站点。我们这里就放个高大上的 hello world 就模拟了。

echo 'Hello world, BitShares' > /home/deploy/www/dexhub/index.html

这里有个 ssl_conf 文件被include了,这是什么呢,我们把SSL相关的配置都放在这个文件里,内容如下:

# path: /etc/nginx/ssl_conf
ssl on;
ssl_certificate             /etc/nginx/ssl/server.crt; #指向证书
ssl_certificate_key         /etc/nginx/ssl/server.key; #指向服务器私钥
ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers               RC4:HIGH:!aNULL:!MD5;
# ssl_ciphers               AES-128-GCM:AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
ssl_ciphers                 ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK;
ssl_prefer_server_ciphers   on;
ssl_session_cache           shared:SSL:10m;
ssl_session_timeout         10m;

一切就绪,我们 reload nginx 后测试访问以下地址。

# 我们知道http会redirect,所以加上-L去follow
curl -L http://dexhub.io
Hello world, BitShares

curl https://dexhub.io
Hello world, Bitshares

很好,SSL证书正确安装了。下面我们把witness_node节点搭建起来。

配置并架设节点

我们现在ECS B 专用节点服务器上进行配置,登录ECS B。

    # Ubuntu 14.04 操作系统上
    # 下载BitShares源码并编译
    # 来源: https://github.com/bitshares/bitshares-core/wiki/BUILD_UBUNTU
    
    # 安装依赖
    sudo apt-get install -y cmake make libbz2-dev \
    libdb++-dev libdb-dev libssl-dev openssl \
    libreadline-dev autoconf libtool git ntp
    
    # 安装Boost
    BOOST_ROOT=$HOME/opt/boost_1_57_0
    mkdir -p $HOME/src
    cd $HOME/src
    sudo apt-get update
    sudo apt-get install -y autotools-dev build-essential \
    g++ libbz2-dev libicu-dev python-dev
    wget -c 'http://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.bz2/download' -O boost_1_57_0.tar.bz2
    [ $( sha256sum boost_1_57_0.tar.bz2 | cut -d ' ' -f 1 ) == "910c8c022a33ccec7f088bd65d4f14b466588dda94ba2124e78b8c57db264967" ] || ( echo 'Corrupt download' ; exit 1 )
    tar xjf boost_1_57_0.tar.bz2
    cd boost_1_57_0/
    ./bootstrap.sh "--prefix=$BOOST_ROOT"
    ./b2 install
    
    # 下载 BitShares 源码并编译
    # 这里,我们假设将目的地目录设置在 $HOME/src/bts_source,可根据需要修改
    cd $HOME/src/bts_source
    # 获取源码
    git clone https://github.com/bitshares/bitshares-core
    cd bitshares-core
    
    # 这里默认是master分支,有时候太过edge有不可知bug,
    # 所以最好checkout到最近的一个release,相对稳定很多
    git checkout `git describe --tags`
    
    # 获取依赖的子模块代码
    git submodule update --init --recursive
    cmake -DBOOST_ROOT="$BOOST_ROOT" -DCMAKE_BUILD_TYPE=Release .
    make

编译完成后会生成以下几个程序,有的我们会用到,有的适用其他场景

  • programs/witness_node/witness_node: 见证节点,运行公共API节点只需要它
  • programs/cli_wallet/cli_wallet: 命令行钱包,只有在我们需要提供水龙头服务时需要用它进行注册账户操作
  • programs/delayed_node/delayed_node: 延时节点,只包括不可逆交易,适用交易所。大约比 witness_node见证人数量 * 2/3 个区块。
  • 还有些其他调试等工具类程序,这里不聊。

上面编译的操作并不需要在每一台服务器上都进行,一般来说配置的节点服务器的配置和环境是一样的话,那么选择其中一台进行即可,完成后可以将编译出来程序直接scp到其他节点服务器上去即可。

我们在 $HOME/src/bitshares-core目录下创建一个 copy_to_build.sh 文件,负责将编译后的程序复制到 build目录下。虽然在 make 的时候可以直接指向目的地过去,但是在过去的版本中曾经有bug导致失败,所以就用笨一点的办法达到相同的目的。这样以后BitShares有版本更新,编译完直接执行下就好了。另一个原因是我们要在一台服务器上跑多个节点,这样复制起来更方便些。

#!/usr/bin/env bash

SRC_PATH=$HOME/src/bitshares-core/programs
BUILD_PATH=$HOME/build

# 创建2个节点目录,分别放置 witness_node 和 cli_wallet

for node in node1 node2; do
  mkdir -p $BUILD_PATH/$node
  
  for bin in witness_node cli_wallet; do
    cp $SRC_PATH/$bin/$bin $BUILD_PATH/$node/
  done
done

所以,到现在,我们的目录结构大概是这个样子的

└── deploy
    ├── www
    │   ├── dexhub
    │   │   ├── index.html
    │   ├── dexhub_wallet #这个目录现在还没有,后面的步骤中会创建
    ├── build
    │   ├── node1
    │   │   ├── cli_wallet
    │   │   └── witness_node
    │   └── node2
    │       ├── cli_wallet
    │       └── witness_node
    └── src
        ├── bitshares-core
        ├── boost_1_57_0
        └── boost_1_57_0.tar.bz2

启动witness_node节点

witness_node节点的运行要求当前的服务器器校准时间,在之前安装依赖的时候有安装ntp,请确保没有遗漏。

sudo apt-get install -y ntp

配置并启动

我们先启动以下witness_node,然后Ctrl+C终止它,目的是让它生成配置文件。

cd $HOME/build/node1
./witness_node -d node_data

PRESS CTRL+C

这里使用了-d node_data意思是将区块链数据以及配置文件放到node_data子目录下,不指定的话会创建一个叫witness_node_data_dir的目录来存放,我不喜欢这个名字,所以就用这个,纯属个人怪癖。

启动并终止后,多了个node_data的目录,里面有不少东西,其中config.ini是配置文件。命令行下的参数基本上这里面也都可以设置。

我们再来看一下witness_node启动时支持哪些参数

# 注意我们的下载和编译路径
cd $HOME/build

# -h 参数返回witness_node程序启动时支持的运行参数
./witness_node -h

# 其中这几条比较重要,大致解释如下,还有其他更多参数,请阅读帮助

# 指定数据及配置文件存储的目录,默认witness_node_data_dir.
-d [ --data-dir ] arg (="witness_node_data_dir") 

# 重发所有已下载的区块并重建索引,非常耗时。当意外中断后重启会强制进行,所以尽量不要强制中断,按了Ctrl+C之后稍等一会儿等程序完成收尾工作后优雅退出。
--replay-blockchain

# 删除所有已下载数据,重新同步区块链。
--resync-blockchain

# Websocket RPC侦听地址及端口
--rpc-endpoint [=arg(=127.0.0.1:8090)]

Options for plugin account_history:
# 追踪指定Account ID的交易历史(可多次设置)
--track-account arg

# 允许只装载部分Op数据旗标
--partial-operations arg

# 每账户保留历史Op数据上限
--max-ops-per-account

-d参数设置数据及配置存储的目录。

--track-account 参数的意思是我们只关心特别指定的账户的历史交易信息,其他账户的历史交易信息我不需要。这样就可以大大节省内存开支。这在架设供个人使用的API服务器时非常必要,但对于公共API服务器则不适合,所以千万不要设置该参数。

而对于节省内存开支,以下两个参数非常重要,他们是 partial-operationsmax-ops-per-account。如果不设置,目前状态下,一个全节点公共API服务器大约要占据24G的内存,太厉害了,配置了这两个参数,将大大降低,目前测试情况下大概在5G左右,当然这也和访问量有关系,但大大降低是肯定的。

--partial-operations 这个参数指示只需要部分的数据。这个参数可以在启动命令行加入,也可以在config.ini中设置。

--max-ops-per-account 这个参数的意思是在内存中对访问的每个账户保持最多n条op记录,如果不设置则是全部。而一般用户使用时,很少需要看到很早以前的历史记录,所以,可以设置一个较小的值,比如500.

所以,可以在config.ini中加入下面这两行。

max-ops-per-account = 500
partial-operations = true

另外--rpc-endpoint这个设置是节点对外暴露Websocket RPC服务器的地址和端口,默认的设置是 127.0.0.1:8090 也就是只在本地的8090端口提供服务。--rpc-endpoint后面可以不填内容,这时就采用默认值;但是--rpc-endpoint必须要写,否则节点启动后就不开放Websocket RPC服务了。

我们不打算并在localhost上,而是绑在内网IP上,原因是,我们打算让nginx通过反向代理来访问,而节点服务器和nginx并不在一台服务器上(当然,我们前面架构图中,有一个节点和nginx在一起,它就可以并在localhost上)。

所以,我们可以把启动命令都放到一个脚本中,不需要再在config.ini中设置了,这样方便运行和迁移,当然config.ini 其中还有很多选项值得探索,有的并不支持在命令行中使用。

我们创建一个 run.sh,以后就用它来启动。

#!/usr/bin/env/bash

# 文件位置 $HOME/build/node1/run.sh
# 启动 node1
./witness_node -d ./node_data \
--partial-operations true \
--max-ops-per-account 500 \
--rpc-endpoint 10.10.10.2:8090

应该注意到,我们目前是在node1目录下进行配置,那么在node2目录下是一样的,可以将run.sh复制过去,但是端口需要修改一下,比如设置成18090

#!/usr/bin/env/bash

# 文件位置 $HOME/build/node2/run.sh
# 启动 node2
./witness_node -d ./node_data \
--partial-operations true \
--max-ops-per-account 500 \
--rpc-endpoint 10.10.10.2:18090

下面我们就要启动节点了,可以使用upstart来控制。这里我们先用screen来模拟2个终端,让任务持续执行。

sudo apt-get install -y screen

# 启动2个screen终端会话,并给他们命名s1和s2,在各自的会话中运行启动节点的脚本
screen -dmS node1 $HOME/build/node1/run.sh
screen -dmS node2 $HOME/build/node2/run.sh

# 查看终端
screen -ls

There are screens on:
    92480.s1    (Detached)
    92799.s2    (Detached)

# 接入s1,会看到很多节点程序运行时的输出
screen -rd s1

# 其它screen终端中常用的命令
CTRL+a d #暂时断开screen会话
CTRL+a [ #进入拷贝/回滚模式,可使用CTRL+b和CTRL+b上下翻屏

2个节点启动后,就需要耐心等待区块同步。在未完成同步之前,如果尝试使用客户端进行连接,也能连,但是会出现“区块数据陈旧或时钟不准”的错误提示。

有一点注意,不要为了节省磁盘空间,想着让2个节点共享数据目录哦,2个独立的进程都在写数据,会发生很混乱的状况。目前的已同步的区块目录磁盘占用不超过20G。

测试节点

我们可以尝试通过命令行来测试连接

# 使用curl命令来测试,向localhost:8090发出请求,获取#1号block摘要
curl http://10.10.10.2:8090 -d '{"jsonrpc": "2.0", "method": "get_block", "params": [1], "id": 1}'

# 应该返回
{"id":1,"jsonrpc":"2.0","result":{"previous":"0000000000000000000000000000000000000000","timestamp":"2015-10-13T14:12:24","witness":"1.6.8","transaction_merkle_root":"0000000000000000000000000000000000000000","extensions":[],"witness_signature":"1f53542bb60f1f7a653bac70d6b1613e73b9adc952031e30e591e601dd60d493ba5c9a832e155ff0c40ea1dd53512e9f93bf65a8191497ea67d701bc2502f93af7","transactions":[]}}

# 这就表示我们的节点能够正常地在指定端口提供数据服务了
# 注意也要同样测试下node2的18090端口是否也正常

同样道理,我们在ECS A nginx 服务器上也跑一个节点来做测试,毕竟内存配得多了,闲着也是闲着。启动命令中ip和端口我们就设置成 --rpc-endpoint 127.0.0.1:8090 用默认的即可。

防火墙

我们希望ECS A是唯一对外的服务器,ECS B则躲在后面,我们也只对ECS A开放809018090两个端口。我们在ECS B上用ufw来创建几个规则。

sudo apt-get install -y ufw

sudo ufw allow 22
sudo ufw allow from 10.10.10.1 to any port 8090
sudo ufw allow from 10.10.10.1 to any port 18090

其他安全措施,请参照最佳实践。

配置nginx负载均衡

现在回到ECS A上,配置nginx了。我们希望服务地址是 wss://wallet.dexhub.io/ws,而且wallet.dexhub.io是要运行一个钱包的,所以创建一个新的站点配置。

# 创建配置文件
sudo touch /etc/nginx/sites-enabled/wallet.dexhub.io.conf

# 创建目录放置钱包网页资源
mkdir /home/deploy/www/dexhub_wallet

wallet.dexhub.io.conf 内容如下:

# https://www.nginx.com/blog/websocket-nginx/
map $http_upgrade $connection_upgrade {
  default upgrade;
  '' close;
}

# http://nginx.org/en/docs/http/ngx_http_upstream_module.html
upstream nodes {
  ip_hash; #根据IP分配节点,并保持
  server 127.0.0.1:8090; #weight=1
  server 10.10.10.2:8090; #weight=2
  server 10.10.10.2:18090; #max_fails=3 fail_timeout=6s;
  server 10.10.10.2:28090; #backup
}

server {
  listen 80;
  server_name wallet.dexhub.io;
  rewrite ^(.*) https://$host$1 permanent;

  access_log /dev/null;
  error_log  /dev/null;
}

server {
  listen 443 ssl;
  
  server_name wallet.dexhub.io;
  root   /home/deploy/www/dexhub_wallet;
  index  index.html index.htm;

  include ssl_conf;
 
  location / {
    try_files $uri $uri/ /index.html;
  }  
  
  location ~* (\.js|\.css|\.dat) {
    gzip_static on; # to serve pre-gzipped version
    if ( -f $request_filename) {
      expires 7d;
      add_header Cache-Control public;
      # Some browsers still send conditional-GET requests if there's a
      # Last-Modified header or an ETag header even if they haven't
      # reached the expiry date sent in the Expires header.
      # add_header Last-Modified "";
      # add_header ETag "";
      access_log off;
      break;
    }
  }
  
  location /ws {
    proxy_pass http://nodes; # upstream nodes
    
    # 参见顶部的map块,升级协议支持wss
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
  }
}

大部分比较容易看明白,其中对js/css/dat的处理是考虑钱包的都是静态资源,对他们设置些缓存选项。

/ws部分,则通过发向代理去到 upstream nodes部分。upsteam 部分维护了一组节点列表,分别是2个指向ECS B和一个指向本地的节点。后面注释掉的是可选用的选项。ip_hash很重要。因为websock是持久连接,可不希望客户端在连接时一直不停的在后端不同节点间跳跃。所以ip_hash的作用就是nginx维护一个根据客户端ip和分配的节点的映射,让同一ip地址的客户端始终连到同一个后端节点。

OK,现在我们载入配置可以测试一下了。

sudo /etc/nginx/init.d/nginx reload

我们可以在自己的电脑上进行一下远程测试:

# 安装 wscat
npm install -g wscat

# 如果能出现 > 输入提示符,那就是连上啦
wscat -c wss://wallet.dexhub.io/ws
> {"id":1, "method":"call", "params":[0,"get_accounts",[["1.2.0"]]]}
< {"id":1,"jsonrpc":"2.0","result":[{"id":"1.2.0","membership_expiration_date":"1969-12-31T23:59:59","registrar":"1.2.0","referrer":"1.2.0","lifetime_referrer":"1.2.0","network_fee_percentage":2000,"lifetime_referrer_fee_percentage":8000,"referrer_rewards_percentage":0,"name":"committee-account","owner":{"weight_threshold":1,"account_auths":[],"key_auths":[],"address_auths":[]},"active":{"weight_threshold":157334,"account_auths":[["1.2.121",38841],["1.2.159",19685],["1.2.282",30547],["1.2.3284",21581],["1.2.8891",33018],["1.2.12376",39050],["1.2.25010",36649],["1.2.35939",29899],["1.2.97845",28497],["1.2.130258",15652],["1.2.158781",21247]],"key_auths":[],"address_auths":[]},"options":{"memo_key":"BTS1111111111111111111111111111111114T1Anm","voting_account":"1.2.5","num_witness":0,"num_committee":0,"votes":[],"extensions":[]},"statistics":"2.6.0","whitelisting_accounts":[],"blacklisting_accounts":[],"whitelisted_accounts":[],"blacklisted_accounts":[],"owner_special_authority":[0,{}],"active_special_authority":[0,{}],"top_n_control_flags":0}]}

到现在,实际上我们的公共API服务器已经完成啦,可以去告诉小伙伴们来试用 wss://wallet.dexhub.io/ws作为钱包接入点了。

架设钱包

我们这里就简单的复制官方钱包来伺服就好。当然最好是下载了钱包源码自己编译,或者做些自定义修改更佳,那就是另外一个话题了,或许以后另开一篇讨论。

cd $HOME/src
git clone https://github.com/bitshares/bitshares.github.io

cp -r bitshares.github.io/wallet/* $HOME/www/dexhub_wallet

之前nginx我们已经配置了wallet站点了,现在就可以去试试看访问了 https://wallet.dexhub.io,我们发现能看到首页,但是不能正常进入,仔细一看,原来官网的钱包是放置在 /wallet路径下来访问的,所以资源都取不到,那我们就rewrite一下跑起来再说。在wallet.dexhub.io.conf 中增加:

rewrite /wallet(.*) /$1 permanent;

# 加在这上面就行
location / {
  ...
}

再访问就行了,钱包网页打开后,可以手动添加 wss://wallet.dexhub.io/ws作为接入点。最好当然是我们的接入点可以在默认列表里,那如果API服务稳定了,就去github提交pull request加入默认接入点列表为更多人服务吧。

架设水龙头

写得有点太长了,有点累了,这一章节好在不是必须,留给以后写吧。

总结

运行了一段时间了,每个witness_node程序平均占用大约4.5G左右的内存,实际上目前在ECS B上跑了3个节点,ECS A上跑了1个节点,也就是说 nginx 后面有4个节点在服务,比较稳定。

过去几周间,经过了yoyow和OracleChain的两个ICO,BitShares的访问量增加很多,观察下来似乎是带宽的瓶颈更大些,在带宽足够的情况下这个架构可以支持更多用户的使用。

最早带宽只有5M,开始卡顿之后扩充到10M,再扩充到20M。每次扩充完毕马上快起来。

流量

目前还没有使用专门工具进行压力测试,只是主观感受。等回头做些更详细的测试之后再来详细分析。

写得有点冗长,能看到这里的估计都是真爱了,谢谢各位看官,也感谢 @abit 协助审校。受个人学识和经验限制,文中不免或有错漏,欢迎大家指正。架构优化是条没有尽头的路,面对并发100/1,000/10,000用户的架构和程序设计是完全不一样的,并没有一招通吃的路数,在资源有限的情况下。我也不赞同过度优化,随着用户的增加逐渐演进是更有效合理的。上面的设计针对目前的需求应该还是有一定的可拓展性的。

这是 BitShares API 服务器架设指南 文章系列中的第二篇,公共API篇。上一篇是关于个人使用API的的搭建。

如果你喜欢这篇教程,请为我的见证人投票,在 BitShares 网络上,我的见证人叫 mr.agsexplorer,我同时维护着一个公共网页钱包 https://bitshares.dacplay.org 以及一组公共 API 服务器 wss://bitshares.dacplay.org/ws,示例中使用的 dexhub.io 是随着文章撰写架设起来的真实存在的站点,因为在国内还没有备案,所以 http://dexhub.io 暂时指向国外服务器的一个临时站点,没有启用 https,不过 https://wallet.dexhub.io 以及 wss://wallet.dexhub.io/ws 是可用的,我打算之后可能的话继续这个站点的服务。

tags: bitshares cn api infrastructure programming

H2
H3
H4
3 columns
2 columns
1 column
84 Comments