Ethereum devcon4: part2

Swarm

在Devcon4的前一天去参加了Swarm的一个专场,他们和Ethereum magicians共同在Smichov的National House组织的一个mini-summit,在大会之前的这些暖场活动都是Status赞助或组织的,Status也在那几天组织过一个黑客马拉松。

Swarm是以太坊生态重要的基础服务。在Gavin和Vitalik早先构想的Web3的技术栈底层: 由以太坊的智能合约实现去中心化的运行逻辑;Swarm提供去中心化的存储;Whisper则提供去中心化的消息服务。但这几年的发展进度不那么乐观,IPFS的势头也盖过了Swarm。

《Data Structures on Swarm》

《Deep SQL and NoSQL Blockchains for Provable Database Storage》
这个是 Wolk 的演讲,Wolk 是基于SWARM的去中心数据库服务平台。他们属于Layer3,锚定的Layer2 是Plasma Cash并提供Provable Storage。

另外的几个分享有《Cooperative protocol evolution with virtualization layers》《Privacy & Access Control 》《Observability in Swarm》 这个相对简单,主要是讲swarm的tracing机制,同一般互联网公司的分布式链路跟踪系统。

《Building Unstoppable Chat》
这个是 mainframe的CTO做的一个演讲,他为了增加大家对他的记忆,穿了一个独角兽的衣服,带着Vitalik的面具来做的演讲,博得了不少眼球。利用 Swarm 的 Mutable Resource (swarm feeds) 的能力,实现了一个区中心化的不可阻挡的(unstoppable)聊天工具。但跟现实中的IM比起来,聊天的过程还是明显偏慢。

《Swarm feeds》这是一个非常有趣的演讲,feeds在之前被称为 "Mutable Resource Updates" (可变资源更新) 它可以当作一个 KV store甚至microblogging platform,可以对某个话题发帖,订阅其他人的更新。
有这种基础设置之后,可以创造出各种不同的应用。甚至可以跟IoT结合,现场演示了一个气温检测的例子。

在第二天Devcon的第一天大会上Swarm团队也去做了状态汇报,项目负责人Viktor Tron在最后汇报了一下整体情况和后续工作。他似乎受过很重的腰伤,走路有明显的问题,站着的时候身体也有些发抖。

Whisper

Whisper在最近一年发布了v6,后续还有很多事情要做。

Ethereum 2.0

Vitalik的分享ethfans已经做过翻译,参考Devcon4 | ETH 2.0 in 30 minutes,Part-1:回顾Devcon4 | ETH 2.0 in 30 minutes,Part-2:展望

Prysmatic Labs分享了sharding的一些实现过程。Prysm是他们用go实现的Ethereum 2.0 client,使用的技术栈提到了bazel, grpc, libp2p, boltdb等

Ethereum 2.0没有给出具体的日期,VDF芯片的开发可能就要18个月;Sharding有7个阶段,现在还处于phase0或phase1阶段?

  • Phase 0: PoS beacon chain without shards
  • Phase 1: Basic sharding without EVM
  • Phase 2: EVM state transition function
  • Phase 3: Light client state protocol
  • Phase 4: Cross-shard transactions: see here and more.
  • Phase 5: Tight coupling with main chain security: here and more.
  • Phase 6: Super-quadratic or exponential sharding

Vlad对2.0里的PoS共识 Casper CBC做了分享,很难理解。

Libp2p也有分享,可以参考ethfans的这篇:Serenity 中的 P2P 网络

客户端&库

ConsenSys下的PegaSys推出了企业级的 Pantheon客户端,用Java实现,后续将增加iBFT共识(i是istanbul的缩写,BFT的一个变种,企业私有链采用的一种共识)。

Slock.it的incubed客户端,用于Slock.it的物联网设备的服务器节点网络

Turbo-Geth 对 Geth的优化。

web3j 4.0 的一些特性,大会演讲ppt的时候还未发布,这几天刚刚发布。

ethereumjs也演示了ethereumjs-client

基础设施&运维

AirSwap 讲了他们在亚马逊上节点运维的一些经验,云硬盘有支持Burst特性

  • Magnetic / spinning disk drives don't work – you are I/O bound
  • provisioned IOPS doesn't work due over allocation of IOPS – costs too much
  • Burst-based IOPS requires 3TB drives and cap at 10000 IOPS
  • EFS network overhead is too slow
  • NVMe drives WORK PERFECTLY

还有一个workshop是基于k8s搭建基础设置的。

其他

这是一个关于避免交易被 front-running 的技巧,是基于这篇To Sink Frontrunners, Send in the Submarines,它基于EIP-86里的CREATE2操作码,目前还未被采用。

AvalancheLabs的Emin Gün Sirer(Emin是康奈尔大学的教授,Avalanche字面意思是雪崩的意思,可以参考 Snowflake to Avalanche:一种新型的亚稳态共识协议族)分享了Pos的例子。他提到硬件也是一种特殊的权益(hardware is just a specialized kind of stake),在指出pow的各种问题时还调侃了一下JihanWu。

大会也有一个专门讨论安全的topic,里面有不少关于隐私、零知识证明方面的讨论。

Ethereum devcon4: part1

编程语言与工具

Solidity

那天主会场Erik Kundt分享了Solidity的最新状况,对0.5.0的新特性做了一些介绍。在写这篇总结时solidity0.5.0也终于发布了: https://github.com/ethereum/solidity/releases/tag/v0.5.0

当时Solidity的核心团队几个人也都在现场,回答听众提问的时候看到solidity项目负责人Christian Reitwiessner在台下做了回复。

对于0.5.0新特性主要围绕下面几点:

  • Explicit types
  • Explicit visibility
  • Explicit data locations
  • Scoping rules for function local variables
  • New constructor syntax
  • emit for events
  • address payable

总体而言就是更加严格了,增加了更多约束,让编译器能提前发现问题。这些例子我没有一一记录,感兴趣的话等youtube上视频出来了大家可以再去看一下视频。

函数的可见性变成了强制的,如下几个function的可见性修饰符是必须的:

contract Test{
  address owner;
  function Test() public {
    initialize();
  }
  function initialize() internal {
    owner = msg.sender;
  }
  function withdraw() public {
    //...
  }
}

实际上在0.4.24版本下对于可见性修饰符已经给出编译时警告了,对构造函数也建议使用constructor替代跟合约同名的函数了,只不过不是强制。

在0.5.0版本里 var已经不再允许使用,因为它可能在类型推导时产生问题。在0.4版本里它不需要SMTChecker编译器就已给出warning了:

 ➜  cat a.sol
pragma solidity ^0.4.24;

contract C {

  function f() public pure returns (uint) {
    for ( var i = 0; i < 2000; i++) {
      //...
    }
  }
}

 ➜  solc a.sol
a.sol:6:11: Warning: Use of the "var" keyword is deprecated.
    for ( var i = 0; i < 2000; i++) {
          ^---^
a.sol:6:11: Warning: The type of this variable was inferred as uint8, which can hold values between 0 and 255. This is probably not desired. Use an explicit type to silence this warning.
    for ( var i = 0; i < 2000; i++) {
          ^-------^

但对这个例子稍作改变,不使用var而是现实的类型声明,但在类型值边界存在判断问题的话就必须通过SMTChecker才能发现了:

 ➜  cat b.sol
pragma solidity ^0.4.24;
pragma experimental SMTChecker;

contract C {

  function f() public pure returns (uint) {
    for ( uint i = 200; i >= 0; i--) {
      //...
    }
  }
}

 ➜  solc b.sol
b.sol:7:25: Warning: For loop condition is always true.
    for ( uint i = 200; i >=0; i--) {
                        ^---^
b.sol:7:32: Warning: Underflow (resulting value less than 0) happens here for:
  value = (- 1)
  i = 0

    for ( uint i = 200; i >=0; i--) {
                               ^-^

对于存储引用必须初始化。

Yul之前也被称为JuliaIulia是为了编译到各种不同后端而设计的中间语言,各类合约语言都先编译为Yul,这样不管后端是EVM还是eWASM都能很平滑。为Yul构建高级的优化器阶段也将会很容易。Yul作为通用的中间语言,本身不提供操作符,如果目标平台是EVM则操作码将作为内置函数提供,如果后端平台发生了变化可以重新实现它们。参看它的文档

SMTChecker

在第二天有一个专门介绍SMTChecker的小会场,看一个例子:

 ➜  cat c.sol
pragma solidity ^0.4.24;
pragma experimental SMTChecker;

contract C {

  uint c;
  function f(uint x) public pure returns (uint) {
    return x * 42;
  }

  function g(uint a, uint b) public view {
    require(a >= b);
    require(b >= c);
    assert(f(a) >= f(c));
  }
}

上面的例子如果不使用SMTChecker编译过程不会给出任何警告,开启后则会看到详细的警告:

 ➜  solc c.sol
c.sol:8:12: Warning: Overflow (resulting value larger than 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) happens here for:
  value = 0x01000000000000000000000000000000000000000000000000000000000000001a
  x = 0x0618618618618618618618618618618618618618618618618618618618618619
  c = 0

    return x * 42;
           ^----^
c.sol:14:12: Warning: Internal error: Expression undefined for SMT solver.
    assert(f(a) >= f(c));
           ^--^
c.sol:14:20: Warning: Internal error: Expression undefined for SMT solver.
    assert(f(a) >= f(c));
                   ^--^
c.sol:14:5: Warning: Assertion violation happens here for:
  a = 0
  b = 0
  c = 0

    assert(f(a) >= f(c));
    ^------------------^

SMTChecker最大优势是Solidity编译器直接集成了它。

AxLang

Athanasios Konstantinidis分享了Axoni公司的 AxLang 语言。它是一门基于Scala的带有形式化验证的智能合约语言,将scala语法树转换为中间语言,做健壮性分析和优化,然后再编译到后端EVM。我之前没有听说过,这家公司好像主要是面向金融领域。他们在devcon4的视频有人贴在了这里,感兴趣可以自己去看。

S-gram

S-gram是一个用于智能合约的Statistical Linter,这个topic是由清华大学的刘浛分享的。会后我们交流了一下,他当前在清华读博士后,主要方向在系统和安全领域。

Formality

Formality是一门新的函数式、无垃圾回收、面向EVM和GPU的形式验证语言,号称很快。因为给自己贴的标签太多,对他们的发展和进度不是很乐观。

ZeppelinOS

ZepplinOS 团队分享了一些合约开发的实用技巧,对于合约的升级问题可以借鉴。

其他

有一家名叫trail of bits的安全公司做了一个《区块链的验尸报告:selfdestructs分析》的分享,视角比较独特,他们在devcon4上有好几个安全相关的分享,感兴趣可以去找找他们的ppt

Vyper 语言的开发者也有做一个分享,这个python风格的合约语言似乎欢迎度挺高,不过没有去这个会场,无法给出太多的信息。

面向合约的编程语言这几年百花齐放,毕竟还未形成一家独大的局面。另外之前看到devcon1上有一个《Monadic Design Patterns for the Blockchain》的分享也很有趣,可以在youtube上找到。

状态通道与Layer2

在devcon4的前一天晚上,Nervos在一家支付比特币的酒吧里组织过一个Layer2的聚会,参与的人也很多,其中有不少是投资人。

因为时差那天晚上没怎么听进去,celer的董沫主持,Jan做了一个简单的分享,介绍了Nervos是专为Layer2设计的 Layer1。在到布拉格之前从杭州到上海的大巴里碰到过Jan,当时简单聊过一些Nervos;他们设计的很独特,Layer1做的事情比较少,鼓励用户把计算放到Layer2上去;在Layer1的存储总量是有上限的,通过经济模型来调节在Layer1上的存储使用成本;共识方面仍采用POW,数据采用UTXO模型。

当时坐在我傍边的是loom network的开发者,一边很快捷的在tmux下敲打命令,一边去github他们的产品issue下回答问题。我去查了一下才知道loom是一套正式上线的以太坊第2层扩展解决方案。是一个DPoS侧链网络,允许高度可扩展的游戏和面向用户的 DApp 在其之上运行,同时仍然受到以太坊的安全支持。似乎在游戏领域很有名,在medium上有一个中文的博客:https://medium.com/@loomnetworkcn

引用节点研究中心蔡晨曦的这篇关于状态通道的分析里对各个项目方的一个介绍。基本上这次devcon4这些状态通道的项目方都来了,看得出是Layer2是当下寻求突破的共识,项目方们也都在相互追赶发力,不排除在可预期的时间内会有不错的效果。

SpankChain

SpankChain的几个人都染着红头发,很容易辨识他们。讲了他们被黑客攻击的过程,以及怎么又把钱给要了回来。支付通道这块主要是与Connext合作的,在后续计划里SpankChain打算集成Gnosis的多重签名钱包。

Perun

在波兰语里Perun是闪电之神,项目方是个学院派背景。他们的主要技术是虚拟通道(virtual channels)采用证明驱动(proof-driven)的方式实现。Virtual channels在Ledger state channels(一种双向支付通道)基础之上,在中心辐射(hub-and-spoke)拓扑下对两个节点(spoke)之间创建一个虚拟通道使得它们之间可以不依赖中心节点(hub)而进行交易。相对于Ledger state channel在创建和关闭的时候都在链上执行,只有在执行的时候在链下,virtual state channel这三个阶段都在链下执行。

FunFair

FunFair是面向博彩游戏的,他们在状态通道的基础上整合了随机数生成器。他们称之为“命运通道”(Fate Channels),“命运通道”的证明机制可以逐步生成一个确定的且不可预测的随机数序列。更多信息可以通过他们的白皮书了解。

Celer

在Celer的主页上引用阿西莫夫的解释,celeritas是拉丁文速度的意思。Celer是分层架构最底层是通道(cChannel),中间是路由层(cRouter),上层是面向应用(cOS)。团队的背景比较豪华,核心成员基本都是名校博士,有博弈论和系统架构方面的工程背景。看到他们在路由层做了很多的事情,正是他们擅长的领域。在经济激励设计上,一方面是对流动性的激励,设计了多种激励模式(包含Proof of Liquidity Commitment和Liquidity Backing Auction);另一方面是对状态守护(State Guardian)提供激励,这也是Layer2普遍采用的机制,让参与方参与检查或校验来获取激励。在会场碰到了Celer的co-founder李小舟,简单交流了一下。上周Celer团队来杭州时董沫和其他团队成员也过来给我们做了一个分享。

其他

光环最显著的plasma项目,因为时间冲突这次没有去听,loom也有个基于plasma cash的分享。当前二层网络是亟待突破的一个阶段,大家都在为产品的用户体验寻求突破,这也成了整个生态当下最显著的痛点,逼迫着Layer2加快发展。我们也在密切关注这个方向的技术进展。

一些方便系统诊断的bash函数

这段脚本包含100多个bash函数,是我几年前方便自己调试和诊断问题写的。贴出来给有需要的人,因为比较懒怎么使用这些函数就不写说明了。其中以下划线开头的是表示私有函数,以cf_开头的表示公共函数,可当做命令使用。

# check current os is linux
function cf_is_linux() {
  [[ "$OSTYPE" = *linux* ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is mac/darwin
function cf_is_darwin() {
  [[ "$OSTYPE" = *darwin* ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current os is windows/cygwin
function cf_is_cygwin() {
  [[ "$OSTYPE" = *cygwin* ]] && echo "true" && return 0
  echo "false" && return 1
}

function cf_is_gnu_date() {
  date --version >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function cf_is_gnu_sed() {
  sed --version >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function cf_is_gnu_awk() {
  awk --version | grep GNU >/dev/null && echo "true" && return 0
  echo "false" && return 1
}

function cf_is_gnu_grep() {
  grep --version | grep GNU >/dev/null && echo "true" && return 0
  echo "false" && return 1
}

# java style startsWith
function cf_starts_with() {
  local str=$1
  local pre=$2
  [[ "$str" ==  ${pre}* ]]
}

# java style substring
function cf_substring() {
  local str=$1
  local begin=$2
  local end=$3
  if [ -z "$end" ]; then
    echo ${str:$begin} 
  else
    local len=`expr $end - $begin`
    echo ${str:$begin:$len}
  fi
}

# get current shell name
function cf_shell_name() {
  local name=$( ps -ocommand= -p $$ | awk '{print $1}')
  if cf_starts_with $name "-"; then
    cf_substring $name 1
  else
    echo $name
  fi
}

# check current shell is bash
function cf_is_bash() {
  [[ `cf_shell_name` = "-bash" || `basename $(cf_shell_name)` = "bash" ]] && echo "true" && return 0
  echo "false" && return 1
}

# check current shell is zsh
function cf_is_zsh() {
  [[ `cf_shell_name` = "-zsh" || `basename $(cf_shell_name)` = "zsh" ]] && echo "true" && return 0
  echo "false" && return 1
}

function _script_dir() {
  if cf_is_bash >/dev/null; then
    cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd -P 
  elif cf_is_zsh >/dev/null; then
    cd "$( dirname "${(%):-%N}" )" && pwd -P 
  else
    echo "unsupported shell" && return 1
  fi
}

function _script_file() {
  if cf_is_bash >/dev/null; then
    basename "${BASH_SOURCE[0]}" 
  elif cf_is_zsh >/dev/null; then
    basename "${(%):-%N}" 
  else
    echo "unsupported shell" && return 1
  fi
}

# colorful grep. private function
function _get_colorful_grep() {
  cf_is_gnu_grep >/dev/null && echo "grep --color" && return 0
  export GREP_OPTIONS='--color=always'
  export GREP_COLOR='1;35;40'
  echo "grep" 
}

# list all common functions
function cf_functions() {
  if cf_is_bash >/dev/null; then
    declare -F | awk '{print $NF}' | grep "cf_" | sort
  elif cf_is_zsh >/dev/null; then
    print -l ${(ok)functions} | grep "cf_" | sort
  else
    echo "unsupported shell" && return 1
  fi
}

# get total memory (MB)
function cf_mem_total() {
  if cf_is_linux >/dev/null; then
    free -m | awk '/^Mem/{print $2"M"}'
  elif cf_is_darwin >/dev/null; then
    sysctl hw.memsize | awk '{print $2/1024/1024"M"}'
  else
    echo "unsupported os" && return 1
  fi 
}

# decimal to hexadecimal
function cf_dec2Hex() {
  printf "%x" $1
}

# decimal to octal 
function cf_dec2Oct() {
  printf "%o" $1
}

# decimal to binary
function cf_dec2Bin() {
  echo "obase=2; $1" | bc
}

# hexadecimal to decimal 
function cf_hex2Dec() {
  echo $((16#$1))
}

# octal to decimal 
function cf_oct2Dec() {
  echo $((8#$1))
}

# binary to decimal 
function cf_bin2Dec() {
  echo $((2#$1))
}

function cf_calc() {
  local exp="$1"
  echo "$exp" | bc -l | awk '{printf "%.2f", $0}'
}

# warning and exit, not for interactive shell
function cf_die() {
  local msg="$1"
  local code=${2:-1}
  echo "$msg" && exit $code
}

# highlight key words from file or pipeline
function cf_highlight() {
  local keyword="$1"
  local cgrep="$(_get_colorful_grep)"
  if [ -p /dev/stdin ]; then
    # from pipeline
    while IFS='' read line; do
      echo $line | eval "$cgrep -E \"${keyword}|$\""
    done
  else
    local file="$2"
    eval "$cgrep -E \"${keyword}|$\"" "$file"
  fi
}

function cf_ps_env() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  if cf_is_linux >/dev/null; then
    xargs --null --max-args=1 < /proc/$pid/environ
  elif cf_is_darwin >/dev/null; then
    ps -wwE -p $pid
  else
    echo "unsupported os" && return 1
  fi
}

# get bash(current shell) major version
function cf_bash_major_ver() {
  echo ${BASH_VERSINFO[0]}
}

# get bash(current shell) minor version
function cf_bash_minor_ver() {
  echo ${BASH_VERSINFO[1]}
}

# get kernel version
function cf_kernel_ver() {
  if cf_is_linux >/dev/null; then
    uname -r | cut -d'-' -f1
  elif cf_is_darwin >/dev/null; then
    uname -r | cut -d'-' -f1
  else
    echo "unsupported os" && return 1
  fi
}

# get kernel major version
function cf_kernel_major_ver() {
  if cf_is_linux >/dev/null; then
    uname -r | awk -F'.' '{print $1"."$2}' 
  elif cf_is_darwin >/dev/null; then
    uname -r | awk -F'.' '{print $1"."$2}' 
  else
    echo "unsupported os" && return 1
  fi
}

# get kernel minor version
function cf_kernel_minor_ver() {
  if cf_is_linux >/dev/null; then
    uname -r | awk -F'.' '{print $3}'
  elif cf_is_darwin >/dev/null; then
    uname -r | awk -F'.' '{print $3}'
  else
    echo "unsupported os" && return 1
  fi
}

# get value from config file such as app.properties
function cf_get_property() {
  local file="$1"
  local key="$2"
  grep "^${key}=" "$file" | tr -d '\r' | cut -d'=' -f2 | cf_trim
}

# get command path, eg: `cf_command_path ls` output /usr/bin/ls
function cf_command_path() {
  local cmd=$1
  cf_is_bash && builtin type -P $cmd && return $?

  if [ -x /usr/bin/which ]; then
    local p=$( /usr/bin/which $1 | head -1 )
    [ ! -z "$p" ] && echo $p && return 0
    return 1
  else
    local p=$( which $1 | grep "^/" | head -1 )
    [ ! -z "$p" ] && echo $p && return 0
    return 1
  fi
}

# get all ip addresses
function cf_ip_list() {
  if [ -x /sbin/ip ]; then
    local list=$(/sbin/ip -o -4 addr list | awk '{print $4}' | cut -d'/' -f1 | tr '\n' ',')
  else
    local list=$(/sbin/ifconfig | grep "inet " | awk '{print $2}' | sed 's/addr://' | tr '\n' ',')
  fi
  echo ${list%,}
}

function cf_stdio() {
  local pid=$1
  /usr/sbin/lsof -a -p $pid -d 0,1,2
}

function cf_stdout() {
  local pid=$1
  if cf_is_linux >/dev/null; then
    readlink -f /proc/$pid/fd/1
  elif cf_is_darwin >/dev/null; then
    /usr/sbin/lsof -a -p $pid -d 1 | awk 'NR>1{print $NF}'
  else
    echo "unsupported os" && return 1
  fi
}

# get file last modification time
function cf_last_modification() {
  local file="$1"
  if [[ $OSTYPE == *linux* ]];then
    date +%Y%m%d%H%M%S -r $file
  elif [[ $OSTYPE == *darwin* ]];then
    stat -f "%Sm" -t "%Y%m%d%H%M%S" $file
  fi
}

# check current user is root 
function cf_is_root() {
  [ `whoami` = "root" ] && echo "true" && return 0
  echo "false" && return 1
}

# check current shell is interactive
function cf_is_interactive_shell() {
  if cf_is_bash >/dev/null; then
    [[ "$-" = *i* ]] && echo "true" && return 0
  elif cf_is_zsh >/dev/null; then
    [[ -o interactive ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

# check current shell is login shell
function cf_is_login_shell() {
  if cf_is_bash >/dev/null; then
    shopt -q login_shell && echo "true" && return 0
  elif cf_is_zsh >/dev/null; then
    [[ -o login ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

# check command is exists
function cf_is_command_exists() {
  local cmd=$1
  if [ -x /usr/bin/which ]; then
    /usr/bin/which $cmd >/dev/null 2>&1 && echo "true" && return 0
  else
    which $cmd >/dev/null 2>&1 && echo "true" && return 0 
  fi
  echo "false" && return 1
}

# check file name globbing flag
function cf_is_glob_enabled() {
  if cf_is_bash >/dev/null; then 
    [[ $- != *f* ]] && echo "true" && return 0
  elif cf_is_zsh >/dev/null; then
    [[ -o glob ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

# enable file name globbing
function cf_enable_glob() {
  cf_is_bash >/dev/null && set +f && return 0
  cf_is_zsh >/dev/null && set -o glob && return 0
  echo "unsupported shell" && return 1
}

# disable file name globbing
function cf_disable_glob() {
  cf_is_bash >/dev/null && set -f && return 0
  cf_is_zsh >/dev/null && set -o noglob && return 0
  echo "unsupported shell" && return 1
}

# check extglob flag
function cf_is_extglob_enabled() {
  if cf_is_bash >/dev/null; then 
    shopt -q extglob && echo "true" && return 0
  elif cf_is_zsh >/dev/null; then
    [[ -o kshglob ]] && echo "true" && return 0
  else
    echo "unsupported shell" && return 1
  fi
  echo "false" && return 1
}

# enable extglob 
function cf_enable_extglob() {
  cf_is_bash >/dev/null && shopt -s extglob && return 0
  cf_is_zsh >/dev/null && set -o kshglob && return 0
  echo "unsupported shell" && return 1
}

# disable extglob 
function cf_disable_extglob() {
  cf_is_bash >/dev/null && shopt -u extglob && return 0
  cf_is_zsh >/dev/null && unsetopt kshglob && return 0
  echo "unsupported shell" && return 1
}

# check pid is exists
function cf_is_pid_exists() {
  local pid=$1
  [ -z "$pid" ] && echo "false" && return 1
  kill -0 $pid >/dev/null 2>&1 && echo "true" && return 0
  echo "false" && return 1
}

function cf_is_java() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  ps -ocommand= -p$pid | awk '$1~/java$/' > /dev/null && echo "true" && return 0
  echo "false" && return 1
}

function cf_is_available_port() {
  local port=$1
  if [[ "$OSTYPE" = *linux* ]];then
    local r=$( netstat -ant | awk '$6=="LISTEN" && $4~":'$port'$"' )
  elif [[ "$OSTYPE" = *darwin* ]];then
    local r=$( netstat -ant | awk '$6=="LISTEN"' | grep "\.$port " )
  else
    echo "unknown system" && return 1
  fi

  [ -z "$r" ] && echo "true" && return 0;
  echo "false" && return 1 # port has been used
}

function cf_defined() {
  if cf_is_bash >/dev/null; then
    [[ ${!1-X} == ${!1-Y} ]]
  elif cf_is_zsh >/dev/null; then
    [[ ${(P)1-X} == ${(P)1-Y} ]]
  else
    echo "unsupported shell" && return 1
  fi
}

function cf_has_value() {
    cf_defined $1 || return 1
    if cf_is_bash >/dev/null; then
      [[ -n ${!1} ]] && return 0
    elif cf_is_zsh >/dev/null; then
      [[ -n ${(P)1} ]] && return 0
    fi
    return 1
}

function cf_has_sudo_privilege() {
  # do not need password
  sudo -n echo >/dev/null 2>&1
}

function cf_timestamp() {
  date +%F-%T | tr ':-' '_' #2015_12_01_22_15_22
}

function cf_length() {
  echo ${#1}
}

# trim string
function cf_trim() {
  if [ -p /dev/stdin ]; then
    while IFS='' read line; do
      _trim "$line"
    done
  else
    _trim "$1"
  fi
}

# private function
function _trim() {
  local str="$1"
  local extglob=$(cf_is_extglob_enabled)
  if cf_is_bash >/dev/null || cf_is_zsh >/dev/null; then
    [ $extglob = "false" ] && cf_enable_extglob
    str="${str##*( )}"
    str="${str%%*( )}"
    [ $extglob = "false" ] && cf_disable_extglob
  else
    echo "unsupported shell" && return 1
  fi
  echo $str
}

function cf_lower() {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function cf_upper() {
  echo "$1" | tr '[:lower:]' '[:upper:]'
}

function cf_ps_name() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  if cf_is_java $pid >/dev/null; then
    local main=$(cf_ps_java_main $pid)
    echo "java($main)"
  else
    ps -ocommand= -p $pid | awk '{print $1}'
  fi
}

function cf_ppid() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  ps -oppid= -p $pid
}

function cf_ps_java_main() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  ps -ocommand= -p $pid | tr ' ' '\n' | awk '/-classpath|-cp/{getline;next};/^-/{next}1' | awk 'NR==2'
}

function cf_ps_time() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  local elapsed="$(ps -oetime= -p $pid | cf_trim)"
  local started="$(ps -olstart= -p $pid | cf_trim)"
  if [ `cf_is_gnu_date` = "true" ]; then
    started=$(date +'%Y-%m-%d %H:%M:%S' -d "$started")
  fi
  local cpu_time=$(ps -otime= -p $pid | cf_trim)
  echo "started from: $started, elapsed: $elapsed, cumulative cpu time: $cpu_time"
}

function cf_ps_zombies() {
  ps -opid,state,command -e | awk 'NR==1 || $2=="Z"'
}

function cf_connection_topology() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  /usr/sbin/lsof -Pan -iTCP -p $pid > /tmp/.$pid.lsof
  grep -o "[0-9.:]*->[0-9.:]*" /tmp/.$pid.lsof > /tmp/.$pid.conns
  grep "LISTEN" /tmp/.$pid.lsof | awk '$9~/*/{print substr($9,3)}' > /tmp/.$pid.ports

  echo "-------------- downstream -------------"
  for port in $(cat /tmp/.$pid.ports); do
    cf_connection_list_by_port $port | awk '$6=="ESTABLISHED" {print $5}' | cut -d':' -f1 | sort | uniq -c | awk '{print $2"-->localhost:"'$port'" ("$1")"}'
  done

  echo "-------------- upstream ---------------"
  local portsExpr=$(cat /tmp/.$pid.ports | sed -e 's/^/:/' -e 's/$/->/' | xargs | sed 's/ /|/g')
  grep -Ev "$portsExpr" /tmp/.$pid.conns > /tmp/.$pid.out
  awk -F'->' '{print $2}' /tmp/.$pid.out | sort | uniq -c | sort -nrk1 | awk '{print "localhost-->"$2" ("$1")"}'
  rm -f /tmp/.$pid.lsof /tmp/.$pid.conns /tmp/.$pid.ports
}

function cf_connection_list_by_pid() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  /usr/sbin/lsof -Pan -iTCP -p $pid
}

function cf_connection_list_by_port() {
  local port=$1
  netstat -ant| awk '$4~/[:.]'"$port"'$/'
}

function cf_connection_stat_by_pid() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  local interval=${2:-1}
  /usr/sbin/lsof -Pan -iTCP -p $pid -r $interval
}

function cf_connection_stat_by_port() {
  local port=$1
  netstat -ant -c| awk '$1=="Proto"{print "\n"$0};$4~/[:.]'"$port"'$/'
}

function cf_listening_sockets() {
  #lsof -Pnl -i4TCP -sTCP:LISTEN #low version unsupported -sTCP params
  if cf_is_linux >/dev/null || cf_is_darwin >/dev/null; then
    if cf_has_sudo_privilege; then
      sudo /usr/sbin/lsof -Pnl -i4TCP | grep LISTEN
    else
      /usr/sbin/lsof -Pnl -i4TCP | grep LISTEN
    fi
  else
    netstat -plnt 2>/dev/null | grep -v tcp6
  fi
}

function cf_traffic_by_eth() {
  local eth=${1:-"eth0"}
  if cf_is_linux >/dev/null; then
    [ ! -d /sys/class/net/$eth ] && echo "network interface not exists." && return 1
    while true; do
      local r1=`cat /sys/class/net/$eth/statistics/rx_bytes`
      local t1=`cat /sys/class/net/$eth/statistics/tx_bytes`
      sleep 1
      local r2=`cat /sys/class/net/$eth/statistics/rx_bytes`
      local t2=`cat /sys/class/net/$eth/statistics/tx_bytes`
      local rkbps=`cf_calc "( $r2 - $r1 ) / 1024"`
      local tkbps=`cf_calc "( $t2 - $t1 ) / 1024"`
      echo "$eth: RX $rkbps kB/s TX $tkbps kB/s"
    done
  elif cf_is_darwin >/dev/null; then
    # `netstat -I eth0 -w 1` or `nettop -n -m tcp`
    declare -a tuple
    local _i1=0
    cf_is_zsh >/dev/null && _i1=1
    local _i2=1
    cf_is_zsh >/dev/null && _i1=2
    while true; do
      tuple=( $(netstat -nbi -I $eth | tail -1 | awk '{print $7,$10}') )
      local r1=${tuple[$_i1]}
      local t1=${tuple[$_i2]}
      sleep 1
      tuple=( $(netstat -nbi -I $eth | tail -1 | awk '{print $7,$10}') )
      local r2=${tuple[$_i1]}
      local t2=${tuple[$_i2]}
      local rkbps=`cf_calc "( $r2 - $r1 ) / 1024"`
      local tkbps=`cf_calc "( $t2 - $t1 ) / 1024"`
      echo "$eth: RX $rkbps kB/s TX $tkbps kB/s"
    done
  else
    echo "unsupported os" && return 1
  fi
}

function cf_traffic_by_pid() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1

  # kernel 2.6.18 not support, must 2.6.32 or later?
  local pf="/proc/$pid/net/netstat"
  [ ! -f $pf ] && echo "$pf not found!" && return 1

  declare -a tuple
  local _i1=0
  cf_is_zsh >/dev/null && _i1=1
  local _i2=1
  cf_is_zsh >/dev/null && _i1=2
  local pname="$(cf_ps_name $pid)"
  while true; do
    tuple=( $(grep "IpExt: " $pf | awk 'NR==2{print $8,$9}') )
    local r1=${tuple[$_i1]}
    local t1=${tuple[$_i2]}
    sleep 1
    tuple=( $(grep "IpExt: " $pf | awk 'NR==2{print $8,$9}') )
    local r2=${tuple[$_i1]}
    local t2=${tuple[$_i2]}
    local rkbps=`cf_calc "( $r2 - $r1 ) / 1024"`
    local tkbps=`cf_calc "( $t2 - $t1 ) / 1024"`
    echo "$pname: IN $rkbps kB/s OUT $tkbps kB/s"
  done
}

function cf_iotop() {
  sudo iotop -bod1
}

function cf_check_sum() {
  local dir=${1:-$PWD}
  local dirsum=0
  for sum  in $(find ${dir} -type f -print0 | xargs -0 cksum | awk '{print $1}')
  do
    dirsum=$(( ${sum} + ${dirsum} ))
  done
  echo ${dirsum}
}

function cf_java_classpath_check() {
  [ $# -eq 0 ] && echo "please enter classpath dir" && return 1
  [ ! -d "$1" ] && echo "not a directory" && return 1

  local tmpfile="/tmp/.cp$(date +%s)"
  local tmphash="/tmp/.hash$(date +%s)"
  local verbose="/tmp/cp-verbose.log"

  if cf_is_zsh >/dev/null;then
    local -a files
    local begin=1
  elif cf_is_bash >/dev/null;then
    declare -a files
    local begin=0
  else 
    echo "unsupported shell" && return 1
  fi
  files=(`find "$1" -name "*.jar"`)

  for f in $files; do
    jarName=`basename $f`
    list=`unzip -l $f | awk -v fn=$jarName '/\.class$/{print $NF,fn}'`
    size=`echo "$list" | wc -l`
    echo $jarName $size >> $tmphash
    echo "$list"
  done | sort | awk 'NF{ a[$1]++;m[$1]=m[$1]","$2}END{for(i in a) if(a[i] > 1) print i,substr(m[i],2)}' > $tmpfile

  awk '{print $2}' $tmpfile | awk -F',' '{i=1;for(;i<=NF;i++) for(j=i+1;j<=NF;j++) print $i,$j}' | sort | uniq -c | sort -nrk1 | 
  while read line; do
    local dup=${line%% *}
    local jars=${line#* }
    local jar1=${jars% *}
    local jar2=${jars#* }
    local len_jar1=`grep -F "$jar1" $tmphash | grep ^"$jar1" | awk '{print $2}'`
    local len_jar2=`grep -F "$jar2" $tmphash | grep ^"$jar2" | awk '{print $2}'`
    local len=$(($len_jar1 > $len_jar2 ? $len_jar1 : $len_jar2))
    local per=$(echo "scale=2; $dup/$len" | bc -l)
    echo ${per/./} $dup $jar1 $jar2
  done | sort -nr -k1 -k2 | awk 'NR==1{print "Similarity DuplicateClasses File1 File2"}{print "%"$0}'| column -t

  sort $tmpfile | awk '{print $1,"\n\t\t",$2}' > $verbose
  echo "See $verbose for more details."

  rm -f $tmpfile
  rm -f $tmphash
}

function cf_java_class_find() {
  local libdir=$1
  local name=$2
  local glob=$(cf_is_glob_enabled)
  [ $glob = "false" ] && cf_enable_glob
  builtin pushd $libdir >/dev/null
  for j in *.jar; do
    unzip -l $j | grep $name && echo $j;
  done
  builtin popd >/dev/null
  [ $glob = "false" ] && cf_disable_glob
}

function cf_java_pids() {
  ps x | grep "jav[a]" | awk '{print $1}'
}

function cf_java_infos() {
  for p in `cf_java_pids`; do
    echo "java pid: $p"
    info=`ps -opid=,command= -p $p | tr ' ' '\n' | awk '/-classpath|-cp/{getline;next};/-Xmx|-Dcatalina.base/{print};/^-/{next};1' | xargs`
    echo "  $info"
    time=`cf_ps_time $p`
    echo "  $time"
  done
}

function cf_java_threads() {
  local pid=$1
  local vm_threads="GC task|VM |CompilerThread|Finalizer|Reference Handler|Signal Dispatcher"
  "$JAVA_HOME"/bin/jstack $pid | grep "^\"" | grep -Ev "$vm_threads"
}

function cf_java_sysprops() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  "$JAVA_HOME"/bin/jinfo -sysprops $pid
}

function cf_jstack_series() {
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  local count=${2:-5}  # defaults 5 times
  local delay=${3:-0.5} # defaults 0.5 seconds

  local logdir=${LOG_DIR:-"/tmp"}
  while [ $count -gt 0 ]; do
    if cf_is_gnu_date >/dev/null; then 
      local suffix=$(date +%H%M%S.%N)
    else
      local suffix=$(date +%H%M%S)"."$count
    fi
    "$JAVA_HOME"/bin/jstack $pid > $logdir/jstack.$pid.$suffix
    sleep $delay
    let count--
    echo -n "."
  done
}

function cf_dmesg() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1

  dmesg -T "$@" 2>/dev/null
  [ $? -eq 0 ] && return 0

  dmesg "$@" | perl -w -e 'use strict;
  my ($uptime) = do { local @ARGV="/proc/uptime";<>}; ($uptime) = ($uptime =~ /^(\d+)\./);
  foreach my $line (<>) {
    printf( ($line=~/^\[\s*(\d+)\.\d+\](.+)/) ? ( "[%s]%s\n", scalar localtime(time - $uptime + $1), $2 ) : $line )
  }'
}

function cf_trace_http_request() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e read -s 2000 -qftp $pid 2>&1 | grep " HTTP/1[.][01][\]r[\]n" 
}

function cf_trace_http_response() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e write -s 2000 -qftp $pid 2>&1 | grep "HTTP/1[.][01] " 
}

function cf_trace_http_req_header() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e read -s 2000 -qftp $pid 2>&1 | grep " HTTP/1[.][01][\]r[\]n" | sed  's/\\r\\n/\n/g'
}

function cf_trace_http_resp_header() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e write -s 2000 -qftp $pid 2>&1 | grep "HTTP/1[.][01] " | sed 's/\\r\\n/\n/g'
}

function cf_trace_http_invoke() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e sendto -s 2000 -qftp $pid 2>&1 | grep " HTTP/1[.][01][\]r[\]n" 
}

function cf_trace_connect() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e connect -s 2000 -qftp $pid 2>&1 | grep "port"
}

function cf_trace_socket() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e connect,socket,close -s 2000 -qftp $pid 2>&1 | grep "port"
}

function cf_trace_sql_select() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e sendto,write -s 2000 -qftp $pid 2>&1 | grep -i "[\]3select"
}

function cf_trace_sql_update() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e sendto,write -s 2000 -qftp $pid 2>&1 | grep -i "[\]3update"
}

function cf_trace_sql_insert() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e sendto,write -s 2000 -qftp $pid 2>&1 | grep -i "[\]3insert"
}

function cf_trace_redis_command() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  local cmd=$2
  strace -e sendto,write -s 2000 -qftp $pid 2>&1 | grep -i "$cmd[\]r[\]n"
}

function cf_trace_dubbo_request() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e read -s 2000 -qftp $pid 2>&1 | grep -i "[\]tinterface"
}

function cf_trace_dubbo_invoke() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  strace -e write -s 2000 -qftp $pid 2>&1 | grep -i "[\]tinterface"
}

function cf_trace_system_call() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  local pid=$1
  ! cf_is_pid_exists >/dev/null $pid && echo "pid:$pid not exists" && return 1
  local time=${2:-5}

  local outfile="/tmp/.sys-call.$pid"
  strace -cqftp $pid -o $outfile & 
  local spid=$!
  while [ $time -gt 0 ]; do
    sleep 1
    let time--
    echo -n "."
  done
  echo ""
  kill $spid && echo "ok"
  # if strace process still exists
  cf_is_pid_exists $spid >/dev/null 2>&1 && kill -9 $spid
  cat $outfile && rm -f $outfile
}

function cf_random_entropy_stat() {
  ! cf_is_linux >/dev/null && echo "only works in linux" && return 1
  while true; do
    echo "entropy available:" `cat /proc/sys/kernel/random/entropy_avail`
    sleep 1
  done
}

function cf_json_fmt() {
  python -mjson.tool
}

function cf_http_server() {
  local port=${1:-8000}
  python -mSimpleHTTPServer $port 2>/dev/null
}

狂暴的欢愉必将有狂暴的结局

狂暴的欢愉必将有狂暴的结局,这句话是从《西部世界》里听到的,用来形容我对这一波数字化货币行情的感受再切合不过了。

并不是在这几天得知政府会将所有数字化货币交易关闭的消息后才有这样的感受。我是在这一波经历的末端才参与的,当时高潮已过,但一些八九月份ICO的项目仍存在极大投机性,便投了一点钱,目前当然是亏损了很多,但投入量很小,损失也在预期之内。

在2016年初的时候,开始关注到区块链相关的技术,当时这个概念还没有那么普及,但几乎不到半年时间这个概念被迅速放大并普及。大概也是2016年二三月份的时候还去上海静安寺附近的一家咖啡馆参加过一次以太坊的技术聚会,但那个时候完全是出于对其技术方面的好奇。与会的一半是区块链领域开发者,一半是投机者。有不少人现在已经在这个圈子较有影响力,比如后来的imtoken钱包的创始人何斌、秘猿科技的创始人Jan、暴走恭亲王、还有一些其他区块链创业公司的核心开发者等等。坐在我身边的好像还有果仁宝CTO,以及一位做智能跑步机的创业者(他在考虑将用户的跑步积分与挖矿虚拟货币兑换,当时令我很惊讶)。

只可惜那次聚会除了对一些技术概念熟悉了些,因为后续没有应用场景,就没有再对区块链进行关注。以太坊之后经历过被黑客攻击以及分叉等曲折的故事。等我再次关注关注到它的时候,已经是2017年的六七月,这时以太坊的价格已经增长了近一百倍。

并没有过懊悔什么,这个机会并不属于没有准备的人。我之前的好奇只是关注它的技术层面,从没有想到在投机性方面还存在如此巨大的可能,对它背后的经济学理论也懵懵懂懂、半信半疑。所以这一波行情跟我没什么关系。只是仍没有经住ICO的一些诱惑,尽管对行情走势看跌,但万一ICO的项目撞上一个爆发的呢?

蛮荒时代必有超常规的手段,政府的干预让这个事情变得如何还不一定,当我看着那些T恤上印着“一切才刚刚开始”或“Decentralize Everything”的九零后小伙子目光中充满笃信的神态时,迟疑自己是否有些老了,或许DAO时代真的可能会到来?

继续招聘

我们在招Java/Scala的资深工程师(或技术专家),你将有机会跟我们共同参与一些挖财基础设施的开发。整体上我们算是偏中庸的一个技术团队,形成一套适合自己的技术栈,相对务实。这次招聘的岗位有面向中间件岗位的开发(消息、分布式事务、存储等),以及面向信贷业务的开发。

对于中间件开发岗位,如果你有Akka的应用经验更好,不过掌握Scala/Akka并不是必要条件,基本的要求是功底扎实,对分布式有一定的了解。至少有三年以上的工作经验,熟悉Java/Scala/C++其中之一。

对于业务开发岗位,希望你有较扎实的基本功和责任心,熟悉Spring生态和SOA,善于沟通和团队协作,有支付或互联网金融相关的工作背景会有加分。

工作地点在杭州。对职位感兴趣的请发邮件到: cuodao(at)wacai.com

本站已支持https访问

几个月前已经配置了https,已经支持使用https方式访问本站,否则你可能在访问时遇到运营商广告劫持的情况,类似下图右下角的这个广告(宽带运营商篡改了http植入了广告):

不过以前的blog里有些图片使用的url仍是http的,所以在chrome浏览器里可能看到有图片内容的文章时,并没有在地址栏展示一个绿色Secure锁的标记。

Akka的RoundRobinRouting实现存在bug

Akka的RoundRobinRouting实现存在bug,当累计执行超过Long最大值之后(变为负数),会导致分散不均,最后一个节点永远分配不上,直到累计值再次变为正数才恢复均匀。代码如下(2.4.9版本):

final class RoundRobinRoutingLogic extends RoutingLogic {
  val next = new AtomicLong

  override def select(message: Any, routees: immutable.IndexedSeq[Routee]): Routee =
    if (routees.nonEmpty) {
      val size = routees.size
      val index = (next.getAndIncrement % size).asInstanceOf[Int]
      routees(if (index < 0) size + index - 1 else index)
    } else NoRoutee

}

上面select函数里当index为负数时,返回size + index - 1存在问题,用一段java代码验证一下:

import java.util.concurrent.atomic.AtomicLong;

public class Test {

    static final AtomicLong next = new AtomicLong(-2003);
    static final int size = 4;

    static int getNext() {
        int index = (int) (next.getAndIncrement() % size);
        if (index < 0)
            return size + index - 1;
        else
            return index;
    }

    public static void main(String[] args) {
        for (int n = 0; n < 8; n++) {
            System.out.println(getNext());
        }
    }
}

// 运行后输出:
0
1
2
0
0
1
2
0

上面的代码假设有4个节点,当next为负数后,一直在"0,1,2"这三个节点上分配,不会分配给最后一个节点(即索引为3的),修正的方式是当index为负数后,返回其绝对值(即 -index)即可。

spring-boot 1.4.x遇到的cpu高的问题

如果你的spring-boot应用里tomcat线程耗cpu较高,并主要耗在做读取jar的操作上(堆栈类似下面),可能跟我们遇到同样的问题。

    CRC32.update(byte[], int, int) line: 76
    JarInputStream(ZipInputStream).read(byte[], int, int) line: 200
    JarInputStream.read(byte[], int, int) line: 207
    JarInputStream(ZipInputStream).closeEntry() line: 140
    JarInputStream(ZipInputStream).getNextEntry() line: 118
    JarInputStream.getNextEntry() line: 142
    JarInputStream.getNextJarEntry() line: 179
    JarWarResourceSet.getArchiveEntries(boolean) line: 112
    JarWarResourceSet(AbstractArchiveResourceSet).getResource(String) line: 256
    StandardRoot.getResourceInternal(String, boolean) line: 280
    CachedResource.validateResource(boolean) line: 95
    Cache.getResource(String, boolean) line: 69
    StandardRoot.getResource(String, boolean, boolean) line: 215
    StandardRoot.getResource(String) line: 205
    Mapper.internalMapWrapper(Mapper$ContextVersion, CharChunk, MappingData) line: 1027
    Mapper.internalMap(CharChunk, CharChunk, String, MappingData) line: 842
    Mapper.map(MessageBytes, MessageBytes, String, MappingData) line: 698
    CoyoteAdapter.postParseRequest(Request, Request, Response, Response) line: 672
    CoyoteAdapter.service(Request, Response) line: 344
    Http11Processor.service(SocketWrapperBase<?>) line: 784
    Http11Processor(AbstractProcessorLight).process(SocketWrapperBase<?>, SocketEvent) line: 66
    AbstractProtocol$ConnectionHandler<S>.process(SocketWrapperBase<S>, SocketEvent) line: 802
    NioEndpoint$SocketProcessor.doRun() line: 1410
    NioEndpoint$SocketProcessor(SocketProcessorBase<S>).run() line: 49
    ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1142
    ThreadPoolExecutor$Worker.run() line: 617
    TaskThread$WrappingRunnable.run() line: 61
    TaskThread(Thread).run() line: 745  

这种情况只发生在 spring-boot 1.4.x版本(及1.3.x版本,更早的没有确认),1.5.x已经没有这个问题。

主要的改变在org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory的内部类StoreMergedWebXmlListeneronStart方法:

// TomcatEmbeddedContext 启动时触发了该监听器
private void onStart(Context context) {
    ServletContext servletContext = context.getServletContext();
    if (servletContext.getAttribute(MERGED_WEB_XML) == null) {
        servletContext.setAttribute(MERGED_WEB_XML, getEmptyWebXml());
    }
    // 注意最后这句,1.5.3版本已经去掉了这句,它导致变慢
    TomcatResources.get(context).addClasspathResources(); 
}

addClasspathResources方法里对于jar资源的处理,不同的tomcat版本方式有所不同,spring-boot 中如果使用嵌入式的 tomcat8 的话这些jar资源会记录到StandardRoot里的jarResources集合里,它们会被定时清理。

tomcat容器的后台线程(ContainerBackgroundProcessor)会触发StandardRoot里的清理逻辑

    public void backgroundProcess() {
        cache.backgroundProcess();
        gc();
    }

    public void gc() {
        for (List<WebResourceSet> list : allResources) {
            for (WebResourceSet webResourceSet : list) {
                webResourceSet.gc();
            }
        }
    }
    
    // JarWarResourceSet里的gc方法
    public void gc() {
        synchronized (archiveLock) {
            if (archive != null && archiveUseCount == 0) {
                try {
                    archive.close();
                } catch (IOException e) {
                    // Log at least WARN
                }
                archive = null;
                archiveEntries = null;
            }
        }
    }

请求过来时,Mapper阶段会根据请求路径去找映射的资源,Cache不管命中还是未命中,都会对资源进行validate,在validateResource时要去遍历WebResourceRoot里所有的资源(包括所有的jar资源),若应用依赖的jar比较多时,会导致cpu较高。

spring-boot 1.5 版本里不会再将 BOOT-INF/lib 下的所有jar添加到tomcat的WebResourceRoot里,升级到1.5.3后这个情况没有再发生。