wrk — 小巧轻盈的 http 性能测试工具.

via: http://zjumty.iteye.com/blog/2221040

测试先行是软件系统质量保证的有效手段. 在单元测试方面, 我们有非常成熟的 xUnit 方案. 在集成测试方面, 我们 selenium 等自动化方案. 在性能测试方面也有很多成熟的工具, 比如 LoadRunner, Jmeter 等. 但是很多工具都是给专门的性能测试人员使用的, 功能虽然强大, 但是安装和操作不太方便. 作为开发人员, 我们有些时候想快速验证我们的解决方案是不是存在性能问题, 或者在并发情况下是否有意想不到的问题.  安装 LoadRunner 这样工具, 录制脚本很麻烦, 用起来就像在用大炮打蚊子.

wrk 是一个很简单的 http 性能测试工具. 也可以叫做 http benchmark 工具. 只有一个命令行, 就能做很多基本的 http 性能测试.

wrk 的开源的, 代码在 github 上. https://github.com/wg/wrk

首先要说的一点是: wrk 只能运行在 Unix 类的系统上. 比如 linux, mac, solaris 等. 也只能在这些系统上编译.

这里不得不说一下, 为什么很多人说 mac 是最好的开发环境. 不是因为使用 mac 逼格有多高. 而是你可以同时得到 windows 和 linux 的好处. 多数 linux 下的开发工具都可以在 mac 上使用. 很多都是预编译好的, 有些只需要编译一下就能用.

wrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原因是它使用了一些操作系统特定的高性能 io 机制, 比如 select, epoll, kqueue 等. 其实它是复用了 redis 的 ae 异步事件驱动框架.  确切的说 ae 事件驱动框架并不是 redis 发明的, 它来至于 Tcl的解释器 jim, 这个小巧高效的框架, 因为被 redis 采用而更多的被大家所熟知.

要用 wrk, 首先要编译 wrk.
你的机器上需要已经安装了 git 和基本的c编译环境. wrk 本身是用 c 写的. 代码很少. 并且没有使用很多第三方库.  所以编译基本不会遇到什么问题.

git clone https://github.com/wg/wrk.git  
cd wrk  
make

就 ok了.
make 成功以后在目录下有一个 wrk 文件. 就是它了. 你可以把这个文件复制到其他目录, 比如 bin 目录. 或者就这个目录下执行.

如果编译过程中出现:

src/wrk.h:11:25: fatal error: openssl/ssl.h: No such file or directory  
 #include <openssl/ssl.h>

是因为系统中没有安装openssl的库.

sudo apt-get install libssl-dev

sudo yum install  openssl-devel

我们先来做一个简单的性能测试:

wrk -t12 -c100 -d30s http://www.baidu.com

30秒钟结束以后可以看到如下输出:

Running 30s test @ http://www.baidu.com  
  12 threads and 100 connections  
  Thread Stats   Avg      Stdev     Max   +/- Stdev  
    Latency   538.64ms  368.66ms   1.99s    77.33%  
    Req/Sec    15.62     10.28    80.00     75.35%  
  5073 requests in 30.09s, 75.28MB read  
  Socket errors: connect 0, read 5, write 0, timeout 64  
Requests/sec:    168.59  
Transfer/sec:      2.50MB

先解释一下输出:
12 threads and 100 connections
这个能看懂英文的都知道啥意思: 用12个线程模拟100个连接.
对应的参数 -t 和 -c 可以控制这两个参数.

一般线程数不宜过多. 核数的2到4倍足够了. 多了反而因为线程切换过多造成效率降低. 因为 wrk 不是使用每个连接一个线程的模型, 而是通过异步网络 io 提升并发量. 所以网络通信不会阻塞线程执行. 这也是 wrk 可以用很少的线程模拟大量网路连接的原因. 而现在很多性能工具并没有采用这种方式, 而是采用提高线程数来实现高并发. 所以并发量一旦设的很高, 测试机自身压力就很大. 测试效果反而下降.

下面是线程统计:

Thread Stats   Avg      Stdev     Max   +/- Stdev  
  Latency   538.64ms  368.66ms   1.99s    77.33%  
  Req/Sec    15.62     10.28    80.00     75.35%

Latency: 可以理解为响应时间, 有平均值, 标准偏差, 最大值, 正负一个标准差占比.
Req/Sec: 每个线程每秒钟的完成的请求数, 同样有平均值, 标准偏差, 最大值, 正负一个标准差占比.

一般我们来说我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大.

接下来:

5073 requests in 30.09s, 75.28MB read  
  Socket errors: connect 0, read 5, write 0, timeout 64  
Requests/sec:    168.59  
Transfer/sec:      2.50MB

30秒钟总共完成请求数和读取数据量.
然后是错误统计, 上面的统计可以看到, 5个读错误, 64个超时.
然后是所以线程总共平均每秒钟完成168个请求. 每秒钟读取2.5兆数据量.

可以看到, 相对于专业性能测试工具. wrk 的统计信息是非常简单的. 但是这些信息基本上足够我们判断系统是否有问题了.

wrk 默认超时时间是1秒. 这个有点短. 我一般设置为30秒. 这个看上去合理一点.
如果这样执行命令:

/wrk -t12 -c100 -d30s -T30s http://www.baidu.com

可以看到超时数就大大降低了, Socket errors 那行没有了:

Running 30s test @ http://www.baidu.com  
  12 threads and 100 connections  
  Thread Stats   Avg      Stdev     Max   +/- Stdev  
    Latency     1.16s     1.61s   14.42s    86.52%  
    Req/Sec    22.59     19.31   108.00     70.98%  
  4534 requests in 30.10s, 67.25MB read  
Requests/sec:    150.61  
Transfer/sec:      2.23MB

通过 -d 可以设置测试的持续时间. 一般只要不是太短都是可以的. 看你自己的忍耐程度了.
时间越长样本越准确. 如果想测试系统的持续抗压能力, 采用 loadrunner 这样的专业测试工具会更好一点.

想看看响应时间的分布情况可以加上–latency参数:

wrk -t12 -c100 -d30s -T30s --latency http://www.baidu.com
Running 30s test @ http://www.baidu.com  
  12 threads and 100 connections  
  Thread Stats   Avg      Stdev     Max   +/- Stdev  
    Latency     1.22s     1.88s   17.59s    89.70%  
    Req/Sec    14.47      9.92    98.00     77.06%  
  Latency Distribution  
     50%  522.18ms  
     75%    1.17s  
     90%    3.22s  
     99%    8.87s  
  3887 requests in 30.09s, 57.82MB read  
  Socket errors: connect 0, read 2, write 0, timeout 0  
Requests/sec:    129.19  
Transfer/sec:      1.92MB

可以看到50%在0.5秒以内, %75在1.2s 以内. 看上去还不错.

看到这里可能有人会说了, HTTP 请求不会总是这么简单的, 通常我们会有 POST,GET 等多个 method, 会有 Header, 会有 body 等.

在我第一次知道有 wrk 这个工具的时候他确实还不太完善, 要想测试一些复杂的请求还有点难度. 现在 wrk 支持 lua 脚本. 在这个脚本里你可以修改 method, header, body, 可以对 response 做一下自定义的分析. 因为是 lua 脚本, 其实这给了你无限的可能. 但是这样一个强大的功能如果不谨慎使用, 会降低测试端的性能, 测试结果也受到影响.

一般修改method, header, body不会影响测试端性能, 但是操作 request, response 就要格外谨慎了.

我们通过一些测试场景在看看怎么使用 lua 脚本.

POST + header + body.

首先创建一个 post.lua 的文件:

wrk.method = "POST"  
wrk.body   = "foo=bar&baz=quux"  
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

就这三行就可以了, 当然 headers 可以加入任意多的内容.
然后执行:

wrk -t12 -c100 -d30s -T30s --script=post.lua --latency http://www.baidu.com

当然百度可能不接受这个 post 请求.

对 wrk 对象的修改全局只会执行一次.
通过 wrk 的源代码可以看到 wrk 对象的源代码有如下属性:

local wrk = {  
   scheme  = "http",  
   host    = "localhost",  
   port    = nil,  
   method  = "GET",  
   path    = "/",  
   headers = {},  
   body    = nil,  
   thread  = nil,  
}

schema, host, port, path 这些, 我们一般都是通过 wrk 命令行参数来指定.

wrk 提供的几个 lua 的 hook 函数:

setup 函数
这个函数在目标 IP 地址已经解析完, 并且所有 thread 已经生成, 但是还没有开始时被调用. 每个线程执行一次这个函数.
可以通过thread:get(name),  thread:set(name, value)设置线程级别的变量.

init 函数
每次请求发送之前被调用.
可以接受 wrk 命令行的额外参数. 通过 — 指定.

delay函数
这个函数返回一个数值, 在这次请求执行完以后延迟多长时间执行下一个请求. 可以对应 thinking time 的场景.

request函数
通过这个函数可以每次请求之前修改本次请求的属性. 返回一个字符串. 这个函数要慎用, 会影响测试端性能.

response函数
每次请求返回以后被调用. 可以根据响应内容做特殊处理, 比如遇到特殊响应停止执行测试, 或输出到控制台等等.

function response(status, headers, body)  
   if status ~= 200 then  
      print(body)  
      wrk.thread:stop()  
   end  
end

done函数
在所有请求执行完以后调用, 一般用于自定义统计结果.

done = function(summary, latency, requests)  
   io.write("------------------------------\n")  
   for _, p in pairs({ 50, 90, 99, 99.999 }) do  
      n = latency:percentile(p)  
      io.write(string.format("%g%%,%d\n", p, n))  
   end  
end

下面是 wrk 源代码中给出的完整例子:

local counter = 1  
local threads = {}  
  
function setup(thread)  
   thread:set("id", counter)  
   table.insert(threads, thread)  
   counter = counter + 1  
end  
  
function init(args)  
   requests  = 0  
   responses = 0  
  
   local msg = "thread %d created"  
   print(msg:format(id))  
end  
  
function request()  
   requests = requests + 1  
   return wrk.request()  
end  
  
function response(status, headers, body)  
   responses = responses + 1  
end  
  
function done(summary, latency, requests)  
   for index, thread in ipairs(threads) do  
      local id        = thread:get("id")  
      local requests  = thread:get("requests")  
      local responses = thread:get("responses")  
      local msg = "thread %d made %d requests and got %d responses"  
      print(msg:format(id, requests, responses))  
   end  
end

测试复合场景时, 也可以通过 lua 实现访问多个 url.
例如这个复杂的 lua 脚本, 随机读取 paths.txt 文件中的 url 列表, 然后访问.:

counter = 1  
  
math.randomseed(os.time())  
math.random(); math.random(); math.random()  
  
function file_exists(file)  
  local f = io.open(file, "rb")  
  if f then f:close() end  
  return f ~= nil  
end  
  
function shuffle(paths)  
  local j, k  
  local n = #paths  
  for i = 1, n do  
    j, k = math.random(n), math.random(n)  
    paths[j], paths[k] = paths[k], paths[j]  
  end  
  return paths  
end  
  
function non_empty_lines_from(file)  
  if not file_exists(file) then return {} end  
  lines = {}  
  for line in io.lines(file) do  
    if not (line == '') then  
      lines[#lines + 1] = line  
    end  
  end  
  return shuffle(lines)  
end  
  
paths = non_empty_lines_from("paths.txt")  
  
if #paths <= 0 then  
  print("multiplepaths: No paths found. You have to create a file paths.txt with one path per line")  
  os.exit()  
end  
  
print("multiplepaths: Found " .. #paths .. " paths")  
  
request = function()  
    path = paths[counter]  
    counter = counter + 1  
    if counter > #paths then  
      counter = 1  
    end  
    return wrk.format(nil, path)  
end

关于 cookie
有些时候我们需要模拟一些通过 cookie 传递数据的场景. wrk 并没有特殊支持, 可以通过 wrk.headers[“Cookie”]=”xxxxx”实现.
下面是在网上找的一个离职, 取 Response的cookie作为后续请求的cookie

function getCookie(cookies, name)  
  local start = string.find(cookies, name .. "=")  
  
  if start == nil then  
    return nil  
  end  
  
  return string.sub(cookies, start + #name + 1, string.find(cookies, ";", start) - 1)  
end  
  
response = function(status, headers, body)  
  local token = getCookie(headers["Set-Cookie"], "token")  
    
  if token ~= nil then  
    wrk.headers["Cookie"] = "token=" .. token  
  end  
end

wrk 本身的定位不是用来替换 loadrunner 这样的专业性能测试工具的. 其实有这些功能已经完全能应付平时开发过程中的一些性能验证了.

MacBook 提升电池效率的一些设置

虽然说Macbook电池能够撑爆很长时间,但是,对于电池来说,永远不够的。 下面一些设置,可减少合起盖子的时候的耗电。当然,你的其他部分功能会缺失。

查看电源管理设置

使用 pmset 命令可以查看系统电源的管理设置

$ pmset -g
Active Profiles:
Battery Power       1
AC Power        2*
Currently in use:
 standbydelay         4200
 standby              1
 womp                 1
 halfdim              1
 hibernatefile        /var/vm/sleepimage
 darkwakes            1
 networkoversleep     0
 disksleep            10
 sleep                10 (sleep prevented by AddressBookSour)
 autopoweroffdelay    14400
 hibernatemode        3
 autopoweroff         1
 ttyskeepawake        1
 displaysleep         10
 acwake               0
 lidwake              1

停用 AddressBookSour

看到这句 (sleep prevented by AddressBookSour), 于是怀疑会不会是 AddressBook 应用引起的系统无法进入休眠状态.

查找了一些关于 AddressBook 的一些资料, 这其实是 Mac OS 用来同步联系人信息的工具, 同时因为这个工具出现很多系统问题.

这个工具对我来说也没有任何意义, 将其停用完事.

/System/Library/Frameworks/ 目录下进入 AddressBook.framework, 然后删除 Helpers 目录下的 AddressBookSourceSync.app 即可. (不要将整个 AddressBook.framework 删除, 不然会导致 Finder 等应用无法正常工作)

$ cd /System/Library/Frameworks/AddressBook.framework/Helpers
$ sudo mv AddressBookSourceSync.app AddressBookSourceSync.bak

上面操作过于不友好,调整为以下方法:

launchctl unload -w /System/Library/LaunchAgents/com.apple.AddressBook.SourceSync.plist
#如果你后悔了,可用这样操作
#launchctl load -w /System/Library/LaunchAgents/com.apple.AddressBook.SourceSync.plist

现在执行 pmset -g 命令 (sleep prevented by AddressBookSour) 这句提示已经消失了:

$ pmset -g
Active Profiles:
Battery Power       1
AC Power        2*
Currently in use:
 standbydelay         4200
 standby              1
 womp                 1
 halfdim              1
 hibernatefile        /var/vm/sleepimage
 darkwakes            1
 networkoversleep     0
 disksleep            10
 sleep                10
 autopoweroffdelay    14400
 hibernatemode        3
 autopoweroff         1
 ttyskeepawake        1
 displaysleep         10
 acwake               0
 lidwake              1

修改休眠模式

停用 AddressBookSour 后, 发现合盖后系统还是无法立即进入休眠状态, 问题就出在休眠模式(hibermode)上.

$ man pmset
We do not recommend modifying hibernation settings.
Any changes you make are not supported.
If you choose to do so anyway, we recommend using one of these three settings.
For your sake and mine, please don't use anything other 0, 3, or 25.

hibernatemode = 0 (binary 0000) by default on supported desktops.
The system will not back memory up to persistent storage.
The system must wake from the contents of memory;
the system will lose context on power loss.
This is, historically, plain old sleep.

hibernatemode = 3 (binary 0011) by default on supported portables.
The system will store a copy of memory to persistent storage (the disk),
and will power memory during sleep.
The system will wake from memory,
unless a power loss forces it to restore from hibernate image.

hibernatemode = 25 (binary 0001 1001) is only settable via pmset.
The system will store a copy of memory to persistent storage (the disk),
and will remove power to memory.
The system will restore from disk image.
If you want "hibernation" - slower sleeps, slower wakes, and better battery life,
you should use this setting.

设置系统 hibernatemode:

$ sudo pmset -a hibernatemode 25 # setting hibernatemode to 25

这样合盖后系统就能立即进入休眠状态了.

修改Standby的时间

缩短上面的 standbydelay 时间,终端输入命令: sudo pmset -a standbydelay [你希望的秒数]。我改成了一个小时,因为我如果合上盖子之后一个多小时没有打开,我一般都是有事出去了,所以电脑两个小时就可以休眠了。

via: http://blog.fourcels.me/2015/01/05/mac-os-hibernate.html
https://www.v2ex.com/t/280420#reply13

微信钱包功能。

事情是这样的,微信钱包功能我相信大部分人都能正常使用。但是对于一部分国外的朋友来说,如果你用国外手机号码注册之类的,你没有“钱包” 功能。无论如何,如果你的微信,里面没有钱包功能。

只有优惠卡券,没有钱包选项。尝试很多方法,依然没有出来的话,微信没有钱包功能,那么试一下以下方法:

添加朋友,搜索“微信红包 ” 公众号,等一下下,就可以开启了。

请不知道的朋友,广而告之。

via ;https://www.v2ex.com/t/255571

Tmux的配置。

# cat << ?_? > /dev/null
# (?●?●)> released under the WTFPL v2 license, by Gregory Pakosz (@gpakosz)


# -- general -------------------------------------------------------------------

set -g default-terminal "screen-256color" # colors!
setw -g mode-keys vi
set -s escape-time 0                      # fastest command sequences
set -sg repeat-time 600                   # increase repeat timeout
set -s quiet on                           # disable various messages
set -g status-utf8 on

#unbind C-b
#set -g prefix C-a
set -g prefix2 C-a                        # GNU-Screen compatible prefix
bind C-a send-prefix -2

set -g history-limit 5000                 # boost history

# reload configuration
bind r source-file ~/.tmux.conf \; display '~/.tmux.conf sourced'

# -- display -------------------------------------------------------------------
bind-key * list-clients
set -g base-index 1         # start windows numbering at 1
setw -g pane-base-index 1   # make pane numbering consistent with windows

setw -g automatic-rename on # rename window to reflect current program
# renumber windows when a window is closed
set -g renumber-windows on

set -g set-titles on                        # set terminal title
set -g set-titles-string '#h ? #S ● #I #W'

set -g display-panes-time 800 # slightly longer pane indicators display time
set -g display-time 1000      # slightly longer status messages display time

set -g status-interval 10     # redraw status line every 10 seconds

# 24 hour clock
setw -g clock-mode-style 24

# clear both screen and history
bind -n C-l send-keys C-l \; run 'tmux clear-history'

# activity
set -g monitor-activity on
set -g visual-activity off


# -- navigation ----------------------------------------------------------------

# find session
bind C-f command-prompt -p find-session 'switch-client -t %%'

# pane navigation
bind -r h select-pane -L  # move left
bind -r j select-pane -D  # move down
bind -r k select-pane -U  # move up
bind -r l select-pane -R  # move right
bind > swap-pane -D       # swap current pane with the next one
bind < swap-pane -U       # swap current pane with the previous one

# key bindings for horizontal and vertical panes
unbind %
bind | split-window -h      # 使用|竖屏,方便分屏
unbind '"'
bind - split-window -v      # 使用-横屏,方便分屏

# maximize current pane


# pane resizing
bind -r H resize-pane -L 2
bind -r J resize-pane -D 2
bind -r K resize-pane -U 2
bind -r L resize-pane -R 2

# window navigation
unbind n
unbind p
bind -r C-h previous-window # select previous window
bind -r C-l next-window     # select next window
bind Tab last-window        # move to last active window

# toggle mouse

setw -g mode-mouse on
set -g mouse-select-pane on
set -g mouse-resize-pane on
set -g mouse-select-window on

# -- list choice ---------------------------------------------------------------

bind -t vi-choice h tree-collapse
bind -t vi-choice l tree-expand
run -b 'tmux bind -t vi-choice K start-of-list 2> /dev/null'
run -b 'tmux bind -t vi-choice J end-of-list 2> /dev/null'
bind -t vi-choice H tree-collapse-all
bind -t vi-choice L tree-expand-all
bind -t vi-choice Escape cancel


# -- edit mode -----------------------------------------------------------------

# the following vi-copy bindings match my vim settings
#   see https://github.com/gpakosz/.vim.git
bind -ct vi-edit H start-of-line
bind -ct vi-edit L end-of-line
bind -ct vi-edit q cancel
bind -ct vi-edit Escape cancel


# -- copy mode -----------------------------------------------------------------

bind Enter copy-mode # enter copy mode
bind -t vi-copy v begin-selection
bind -t vi-copy y copy-selection
unbind p
bind p pasteb
setw -g mode-keys vi      # Vi风格选择文本

# zoom pane <-> window
#http://tmux.svn.sourceforge.net/viewvc/tmux/trunk/examples/tmux-zoom.sh
bind ^z run "tmux-zoom"

 

TCP 连接的 TIME_WAIT 过多 导致 Tomcat 假死

via: http://www.cnblogs.com/digdeep/

最近系统二次开发之后,发现使用的 Tomcat 7 会经常假死。前端点击页面无任何反应,打开firebug,很多链接一直在等待服务器的反应。查看服务器的状态,CPU占用很少,最多不超过10%,一般只有2%,3%左右,内存占用倒是接近80, 90%。一开始怀疑是tomcat内存配置不够,但是打开 jvisualvm.exe 分析,发现Tomcat 占用的堆内存没有什么问题。因为是假死,所以最后怀疑到 tomcat的 链接数和 数据库的链接数的配置估计太小了。netstat -na 结果页显示很多time_wait.

查看各种状态的网络连接的数量:

1)Linux 使用命令:netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’

上面的命令可以查出各种状态的网络连接的数量
2)windows使用命令:

netstat -n |find /i “time_wait” /c

netstat -n |find /i “close_wait” /c

netstat -n |find /i “established” /c

windows下没有awk,所以要一个一个状态的统计它们的数量。

结果是:

1)TIME_WAIT: 状态的连接达到了 709

sql server占用的TIME_WAIT最多,还有nginx, tomcat都有一些处于 TIME_WAIT状态。

2)并且最大的端口达到了 65327 ,六万多,几乎接近端口的最大值 65535.

因为是 Windows server 2008,不同Linux下的TCP的调优。

解决方法:将 TcpTimedWaitDelay 调到 30S,让 TIME_WAIT 状态的维持最多30S,默认是4分钟。

如何查看或设置TcpTimedWaitDelay

cmd中运行 regedit 命令,找到 HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/ Services/TCPIP/Parameters 注册表子键

看看有没有  TcpTimedWaitDelay 项,有的话直接修改,没有的话创建一个并创建名为 TcpTimedWaitDelay 的新 REG_DWORD 值。 将此值设置为十进制 30,其为十六进制 0x0000001e。该值将等待时间设置为 30 秒。 停止并重新启动系统。 缺省值:0xF0,它将等待时间设置为 240 秒(4 分钟)。 建议值:最小值为 0x1E,它将等待时间设置为 30 秒。

修改之后,重启系统,在观察,TIME_WAIT在100左右徘徊。效果还是立竿见影的。几天来一直再也没有出现Tomcat假死的情况。

 

当然也可以同时 增大 MaxUserPort 的数值(2008最大值好像是 65535):

MaxUserPort :确定在应用程序从系统请求可用用户端口时,TCP/IP 可指定的最高端口号。默认是65535,可以调到10万.

如何查看或设置: 使用 regedit 命令访问 HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/ Services/TCPIP/Parameters 注册表子键并创建名为 MaxUserPort 的新 REG_DWORD 值,比如设置成200000。

参考:http://www.cnblogs.com/tianzhiliang/articles/2400176.html

 

TIME_WAIT 相关的网络原理,参见:http://www.cnblogs.com/digdeep/p/4869010.html

java enum的使用以及字符串其字符串之间的转换

via:http://blog.csdn.net/xieyuooo/

文章简单,相信在很多网站都能搜索到Java enum枚举的使用方式;可能有些东西我当时在刚开始用的时候没找到,所以我写了这篇文章,例如:

大多数地方写的枚举都是给一个枚举然后例子就开始switch,可是我想说,我代码里头来源的数据不太可能就是枚举,通常是字符串或数字,比如一个SQL我解析后首先判定SQL类型,通过截取SQL的token,截取出来可能是SELECT、DELETE、UPDATE、INSERT、ALTER等等,但是都是字符串,此时我想用枚举就不行了,我要将字符串转换成枚举怎么转呢,类似的情况还有从数据库取出数据根据一些类型做判定,从页面传入数据,根据不同的类型做不同的操作,但是都是字符串,不是枚举,悲剧的是我很少看到有人写到这个东西;所以我把它写下来,希望有人能用到。

首先为什么要用枚举?我们在什么时候用枚举比较好,用枚举有啥优势?

我认为哈,当你在一些一个范畴类,并可列举,不变化的类型,用以指导程序向不同的地方路由,用枚举是较好的选择;

听起来有点绕,不过有个例子也许可以明白,例如:

我们可以列举下日常工作日所做的事情:

上班、开会、吃饭、睡觉等

我们可以列举医院五官科需要检查人的部位:

眼睛、鼻子、耳朵、嘴巴等

这些都是可以被列举的,且每种事情我们要用不同的方式去做

当然你可以说:

1、可以用动态方法分派,通过配置文件或annotation;

2、可以使用常量来达到类似的效果;

3、直接通过字符串的equals来表达,用if else来表达

如果用配置加方法分派来做,是灵活,便于修改;但是如果在很多不经常修改的参数上,我们用这中方式往往增加配置的负担,并且当你需要看系统逻辑的时候,需要需要一遍看配置一遍看代码;不过,如果参数是可动态变换的信息,用配置是正确的选择;

而常量的使用,通常在switch case的时候都是数字,字符串在java中是不能做switch case的,使用常量的目的比case 1、case 2 …这种增加了可读性;但是字符串数据也麻烦,除非再映射一次,那没那个必要,其实枚举也差不多是帮你映射了一次,只是它将代码封装了而已吧了,既然他弄好了,而且语法上支持,干嘛不用呢!其次,常量虽然增加了可读性,不过他没有范畴和管理类型的概念,即一个枚举的定义会定义个范畴,可以很好的将这个范围所需要的东西列举出来,而常量通常是些自己定义的一些池,放在一些公共类中或随机定义,都是比较零散的,并且枚举在switch的时候就明确定义好了就在锁列举的范围内case,既可以控制好系统,增加可读性,并且可以随时查看这个范畴的枚举信息到底有那些,达到类似看配置文件的作用;不过还是回到那句话,如果参数是可变的,那么就不适合做枚举,枚举是一定是可列举的,或者说当前系统考虑范围是可以被枚举的,例如上面的医院五官科,可能还有很多没有列举到,但是当前医院只处理几个部位,不处理其他的,就是这个道理;什么是可变的呢,例如URL参数来分派到对应方法,不可能大家加一段逻辑就去加一个枚举,加一个case,此时用【配置 动态方法分派】更好,当然配置可以用文件或annotation而已。

还有最土的就是,通过字符串equals,用if else来实现,呵呵,这个并没有什么不好,只是这个写比较零散,其次,字符串匹配的equals每次匹配都需要对比每个字符,如果你的代码中大量循环,性能并不是很好,其余的看看上面的描述就更加清楚了;

其次,枚举提供一种类型管理的组件,让面向对象的体系更加完善,使得一些类型的管理既可配置化,并可以管理,在使用枚举的地方都可以沿着枚举的定义找到那些有处理过,那些没处理过,而上述几种很难做到;例如,数据库的操作类型定义了10种,那么再判定的过程中就可以讲枚举像配置文件一样看待,而又非常简单的来管理。

最后,枚举绝对是单例的,对比的性能和数字性能相当,既可以得到可读性,也可以得到性能。

我们先定义个简单枚举(这里只是个例子,就简单定义3个变量了):

public enum SqlTypeEnum {  
    INSERT ,   
    UPDATE ,  
    DELETE ,  
    SELECT  
}

此时解析SQL后,获取出来一个token,我们要获取这个token的枚举怎么获取呢?

这样获取:

String token = "select";  
SqlTypeEnum sqlTypeEnum = SqlTypeEnum.valueOf(token.toUpperCase());

如果没获取到,java会抛出一个异常哦:IllegalArgumentException No enum const class SqlTypeEnum.XXX

我做大写处理的原因是因为枚举也是大写的(当然如果你的枚举是小写的,那你就小写,不过混写比较麻烦哈),其实valueOf就是调用了枚举的底层映射:

java enum的使用以及字符串其字符串之间的转换

调用的时候会调用这个方法:

java enum的使用以及字符串其字符串之间的转换

所以内部也是一个HashMap,呵呵!

拿到这个信息后,就可以做想要的操作了:

switch(sqlTypeEnum) {  
  case INSERT:处理insert逻辑;break;  
  case DELETE:处理delete逻辑;break;  
 ....  
}

OK,有些时候可能我们不想直接用INSERT、UPDATE这样的字符串在交互中使用,因为很多时候命名规范的要求;

例如定义一些用户操作类型:

1、保存用户信息

2、通过ID获取用户基本信息

3、获取用户列表

4、通过ID删除用户信息

等等

我们可能定义枚举会定义为:

public enum UserOptionEnum {  
    SAVE_USER,  
    GET_USER_BY_ID,  
    GET_USER_LIST,  
    DELETE_USER_BY_ID  
}

但是系统的方法和一些关键字的配置,通常会写成:

saveUser、getUserById、getUserById、deleteUserById

当然各自有各自的规则,不过中间这层映射,你不想做,就一方面妥协,要么枚举名称全部换掉,貌似挺奇怪的,要么方法名称全部换掉,更加奇怪,要么自己做映射,可以,稍微麻烦点,其实也不麻烦?

我们首先写个将枚举下划线风格的数据转换为驼峰的方法,放在一个StringUtils里面:

public static String convertDbStyleToJavaStyle(String dbStyleString , boolean firstUpper) {  
        dbStyleString = dbStyleString.toLowerCase();  
        String []tokens = dbStyleString.split("_");  
        StringBuilder stringBuilder = new StringBuilder(128);  
        int length = 0;  
        for(String token : tokens) {  
            if(StringUtils.isNotBlank(token)) {  
                if(length == 0 && !firstUpper) {  
                    stringBuilder.append(token);  
                }else {  
                    char c = token.charAt(0);  
                    if(c >= \'a\' || c <= \'z\') c = (char)(c - 32);  
                    stringBuilder.append(c);  
                    stringBuilder.append(token.substring(1));  
                }  
            }  
              length;  
        }  
        return stringBuilder.toString();  
    }

重载一个方法:

public static String convertDbStyleToJavaLocalStyle(String dbStyleString) {  
        return convertDbStyleToJavaStyle(dbStyleString , false);  
    }

然后定义枚举:

public enum UserOptionEnum {  
    SAVE_USER,  
    GET_USER_BY_ID,  
    GET_USER_LIST,  
    DELETE_USER_BY_ID;  
  
    private final static Map ENUM_MAP = new HashMap<String, UserOptionEnum>(64);  
  
  
    static {  
        for(UserOptionEnum v : values()) {  
            ENUM_MAP.put(v.toString() , v);   
        }  
    }  
  
    public staticUserOptionEnum fromString(String v) {  
        UserOptionEnum userOptionEnum = ENUM_MAP.get(v);  
        return userOptionEnum == null ? DEFAULT :userOptionEnum;  
    }  
  
    public String toString() {  
        String stringValue = super.toString();  
        return StringUtil.convertDbStyleToJavaLocalStyle(stringValue);  
    }  
}

OK,这样传递一个event参数让如果是:saveUser,此时就用:

String event = "saveUser";//假如这里得到参数  
UserOptionEnum enum = UserOptionEnum.fromString(event);

其实就是自己做了一个hashMap,我这加了一个fromString,因为枚举有一些限制,有些方法不让你覆盖,比如valueOf方法就是这样。

其实没啥好讲的了,非要说,再说说枚举加一些自定义变量吧,其实枚举除了是单例的外,其余的和普通类也相似,它也可以有构造方法,只是默认情况下不是而已,也可以提供自定义的变量,然后获取set、get方法,但是如果有set的话,线程不是安全的哦,要注意这点;所以一般是构造方法就写好了:

public enum SqlTypeEnum {  
   INSERT("insert into"),  
   DELETE("delete from")  
   ......省略;  
  
   private String name;//定义自定义的变量  
  
   private SqlTypeEnum(String name) {  
      this.name = name;  
   }  
  
   public String getName() {  
       return name;  
   }  
  
   public String toString() {  
       return name   " 我靠";//重写toString方法  
  }  
  //一般不推荐  
  public void setName(String name) {  
        this.name = name;  
  }  
}

 

调用下:

SqlTypeEnum sqlTypeEnum = SqlTypeEnum.valueOf("INSERT");  
System.out.println(sqlTypeEnum);  
System.out.println(sqlTypeEnum.getName());

不推荐也调用下:

sqlTypeEnum.setName("我靠");  

在另一个线程:

SqlTypeEnum sqlTypeEnum = SqlTypeEnum.valueOf("INSERT");  
System.out.println(sqlTypeEnum);  
System.out.println(sqlTypeEnum.getName());

发现结果被改了,呵呵!

 

怎么通过java去调用并执行shell脚本以及问题总结

背景

我们在开发过程中,大部分是java开发, 而在文本处理过程中,主要就是脚本进行开发。 java开发的特点就是我们可以很早地进行TDDL, METAQ 等等地对接; 而脚本开发的特点就是在进行批处理的时候非常方便。 前阵子我遇到这么一个需求场景: 对抓取的数据进行打包, 后来又遇到我要通过脚本进行抓取,比如nodejs下基于phantomjs的casperjs爬虫。

解决方法

对于第一个问题:java抓取,并且把结果打包。
那么比较直接的做法就是,java接收各种消息(db,metaq等等),然后借助于jstorm集群进行调度和抓取。 最后把抓取的结果保存到一个文件中,并且通过调用shell打包, 回传。 也许有同学会问, 为什么不直接把java调用odps直接保存文件,答案是,我们的集群不是hz集群,直接上传odps速度很有问题,因此先打包比较合适。(这里不纠结设计了,我们回到正题)

java调用shell的方法

通过ProcessBuilder进行调度

这种方法比较直观,而且参数的设置也比较方便, 比如我在实践中的代码(我隐藏了部分业务代码):

ProcessBuilder pb = new ProcessBuilder("./" + RUNNING_SHELL_FILE, param1,
                                               param2, param3);
        pb.directory(new File(SHELL_FILE_DIR));
        int runningStatus = 0;
        String s = null;
        try {
            Process p = pb.start();
            try {
                runningStatus = p.waitFor();
            } catch (InterruptedException e) {
            }

        } catch (IOException e) {
        }
        if (runningStatus != 0) {
        }
        return;

这里有必要解释一下几个参数:
RUNNING_SHELL_FILE:要运行的脚本
SHELL_FILE_DIR:要运行的脚本所在的目录; 当然你也可以把要运行的脚本写成全路径。
runningStatus:运行状态,0标识正常。 详细可以看java文档。
param1, param2, param3:可以在RUNNING_SHELL_FILE脚本中直接通过1,2,$3分别拿到的参数。

直接通过系统Runtime执行shell

这个方法比较暴力,也比较常用, 代码如下:

p = Runtime.getRuntime().exec(SHELL_FILE_DIR + RUNNING_SHELL_FILE + " "+param1+" "+param2+" "+param3);
p.waitFor();

我们发现,通过Runtime的方式并没有builder那么方便,特别是参数方面,必须自己加空格分开,因为exec会把整个字符串作为shell运行。

可能存在的问题以及解决方法

如果你觉得通过上面就能满足你的需求,那么可能是要碰壁了。你会遇到以下情况。

没权限运行

这个情况我们团队的朱东方就遇到了, 在做DTS迁移的过程中,要执行包里面的shell脚本, 解压出来了之后,发现执行不了。 那么就按照上面的方法授权吧

java进行一直等待shell返回

这个问题估计更加经常遇到。 原因是, shell脚本中有echo或者print输出, 导致缓冲区被用完了! 为了避免这种情况, 一定要把缓冲区读一下, 好处就是,可以对shell的具体运行状态进行log出来。 比如上面我的例子中我会变成:

ProcessBuilder pb = new ProcessBuilder("./" + RUNNING_SHELL_FILE, keyword.trim(),
                                               taskId.toString(), fileName);
        pb.directory(new File(CASPERJS_FILE_DIR));
        int runningStatus = 0;
        String s = null;
        try {
            Process p = pb.start();
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
            BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream()));
            while ((s = stdInput.readLine()) != null) {
                LOG.error(s);
            }
            while ((s = stdError.readLine()) != null) {
                LOG.error(s);
            }
            try {
                runningStatus = p.waitFor();
            } catch (InterruptedException e) {
            }

记得在start()之后, waitFor()之前把缓冲区读出来打log, 就可以看到你的shell为什么会没有按照预期运行。 这个还有一个好处是,可以读shell里面输出的结果, 方便java代码进一步操作。

也许你还会遇到这个问题,明明手工可以运行的命令,java调用的shell中某一些命令居然不能执行,报错:命令不存在!

比如我在使用casperjs的时候,手工去执行shell明明是可以执行的,但是java调用的时候,发现总是出错。 通过读取缓冲区就能发现错误日志了。 我发现即便自己把安装的casperjs的bin已经加入了path中(/etc/profile, 各种bashrc中)还不够。 比如:

export NODE_HOME="/home/admin/node"
export CASPERJS_HOME="/home/admin/casperjs"
export PHANTOMJS_HOME="/home/admin/phantomjs"
export PATH=$PATH:$JAVA_HOME/bin:/root/bin:$NODE_HOME/bin:$CASPERJS_HOME/bin:$PHANTOMJS_HOME/bin

原来是因为java在调用shell的时候,默认用的是系统的/bin/下的指令。特别是你用root权限运行的时候。 这时候,你要在/bin下加软链了。针对我上面的例子,就要在/bin下加软链:

ln -s /home/admin/casperjs/bin/casperjs casperjs;
ln -s /home/admin/node/bin/node node;
ln -s /home/admin/phantomjs/bin/phantomjs phantomjs;

这样,问题就可以解决了。

如果是通过java调用shell进行打包,那么要注意路径的问题了

因为shell里面tar的压缩和解压可不能直接写:

tar -zcf /home/admin/data/result.tar.gz /home/admin/data/result

直接给你报错,因为tar的压缩源必须到路径下面, 因此可以写成

tar -zcf /home/admin/data/result.tar.gz -C /home/admin/data/ result

如果我的shell是在jar包中怎么办?

答案是:解压出来。再按照上面指示进行操作。
(1)找到路径

String jarPath = findClassJarPath(ClassLoaderUtil.class);
        JarFile topLevelJarFile = null;
        try {
            topLevelJarFile = new JarFile(jarPath);
            Enumeration<JarEntry> entries = topLevelJarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (!entry.isDirectory() && entry.getName().endsWith(".sh")) {
                    对你的shell文件进行处理
                }
            }

对文件处理的方法就简单了,直接touch一个临时文件,然后把数据流写入,代码:

FileUtils.touch(tempjline);
tempjline.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tempjline);
IOUtils.copy(ClassLoaderUtil.class.getResourceAsStream(r), fos);
fos.close();

有这个这个东东,相信大家会减少踩坑,而且大胆地使用java和脚本之间的交互吧。 java可以调用shell,那么shell再调用其他就方便了。 记得一点, 不要过度地依赖缓冲区进行线程之间的通信。原因自己去学习吧。

via: https://yq.aliyun.com/articles/2362

Ubuntu自动更新相关设置

  1. 设置自动更新
apt-get install unattended-upgrades

安装好unattended-upgrades后,需要配置:

/etc/apt/apt.conf.d/50unattended-upgrades 
Unattended-Upgrade::Allowed-Origins {
//      "${distro_id}:${distro_codename}-security";
//      "${distro_id}:${distro_codename}-updates";
//      "${distro_id}:${distro_codename}-proposed";
//      "${distro_id}:${distro_codename}-backports";
};
 
注释掉相关选项。
然后,编辑文件:/etc/apt/apt.conf.d/10periodic

APT::Periodic::Update-Package-Lists "1";   //显示更新包列表  0表示停用设置
APT::Periodic::Download-Upgradeable-Packages "1"; //下载更新包  0表示停用设置
APT::Periodic::AutocleanInterval "7"; // 7日自动删除 
APT::Periodic::Unattended-Upgrade "1"; //启用自动更新 0表示停用自动更新

或者直接使用: dpkg-reconfigure -plow unattended-upgrades 进行相关更新

  1. 禁用相关的包更新,例如Linux-Kernel.
sudo apt-mark hold <package_name>
sudo apt-mark hold linux-image-generic linux-headers-generic

 

tomcat 异常-java.util.prefs.BackingStoreException

今天Linux部署Tomcat的时候,tail 日志的时候,发现频繁有下面的错误出来:

Couldn’t flush system prefs: java.util.prefs.BackingStoreException: Couldn’t get file lock.

解决办法是: 在执行程序的用户的home目录下:

#注意切换到执行程序的用户下执行下面的操作
mkdir -p ~/.java/.systemPrefs
mkdir ~/.java/.userPrefs
chmod -R 755 ~/.java

然后在执行程序之前增加如下参数:

JAVA_OPTS="-Djava.util.prefs.systemRoot=/home/user/.java Djava.util.prefs.userRoot=/home/user/.java/.userPrefs"

例如tomcat的话,可以建立setenv.sh ,并且添加上述参数 执行即可

参考:

https://confluence.atlassian.com/confkb/could-not-lock-user-prefs-unix-error-code-2-670958391.html

http://stackoverflow.com/questions/23960451/java-system-preferences-under-different-users-in-linux

CentOS 7 64bit Minimal安装后的初步10项优化和配置

1. 更新系统并安装必备的组件

安装这些之后会大大方便今后安装其他应用是碰到的依赖包问题。其中net-tools是为了提供dig, nslookup, ipconfig等命令,方便配置CentOS 7初始化网络环境。如果不安装这个,在CentOS 7中,可以使用ip addr命令来代替ipconfig进行当前ip地址查询。

2. 添加源(repository)REMI & EPEL

yum安装时,要想安装比较新的版本软件,可以试试这两个源。都有一些国内镜像,我添加的EPEL是阿里云镜像的。

这是适合CentOS 6的源

cd /tmp && wget http://rpms.famillecollet.com/enterprise/remi-release-6.rpm && wget http://mirrors.yun-idc.com/epel/6/x86_64/epel-release-6-8.noarch.rpm && rpm -Uvh remi-release-6.rpm epel-release-6-8.noarch.rpm

 

真正适合CentOS 7的epel和remi源

rpm -Uvh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

如果国外没法用,那用国内的镜像

#中科大镜像源
rpm -Uvh http://mirrors.ustc.edu.cn/centos/7.0.1406/extras/x86_64/Packages/epel-release-7-5.noarch.rpm
 
#浙大源
rpm -Uvh http://mirrors.zju.edu.cn/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
 
#上海交大源
rpm -Uvh http://ftp.sjtu.edu.cn/fedora/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
 
#sohu镜像源,更新比较慢
rpm -Uvh http://mirrors.sohu.com/fedora-epel/7/x86_64/e/epel-release-7-2.noarch.rpm

使用方法:

yum --enablerepo=remi install php mysql php-mysql mysql-server phpmyadmin
 
或者
 
yum --enablerepo=epel install php mysql php-mysql mysql-server phpmyadmin

 

3. FQDN配置,全称Fully Qualified Domain Name

有些软件,特别是邮件系统对这个要求比较高。

vi /etc/hosts
 
127.0.0.1 localhost.localdomain localhost geeker
::1 localhost.localdomain localhost geeker
 
vi /etc/sysconfig/network
HOSTNAME=geeker
 
设置好之后,查询是否完整
 
hostname -f
  1. 关闭Selinux

这是Centos系统的安装机制,单单往往导致很多软件无法正常安装,让我们关掉它吧!

/etc/selinux/config
在 SELINUX=enforcing 前面加个#号注释掉它
#SELINUX=enforcing
然后新加一行
SELINUX=disabled
#SELINUXTYPE=targeted #注释掉这行

保存,退出,重启系统,搞定。

不想重启,可以使用

setenforce 0 #使配置立即生效

 

5. CentOS 7的防火墙关闭和iptables安装

CentOS 7.0默认使用的是firewall作为防火墙,但可能一下子很难适应,让我们先改回原先的iptables防火墙吧!

关闭CentOS 7的firewall

systemctl stop firewalld.servic #停止firewall
systemctl disable firewalld.service #禁止firewall开机启动

安装iptables防火墙

启动iptables防火墙

systemctl restart iptables.service #最后重启防火墙使配置生效
systemctl enable iptables.service #设置防火墙开机启动

6. 本地SMTP邮件发送功能(

很多软件和服务可以用到这个功能给用户发送通知邮件,需要配置一下。

最好加上一个认证,使用Postfix + Saslauthd

yum remove sendmail   #如果有原先的sendmail,先移除
yum install postfix  
vi /etc/postfix/main.cf   #编辑postfix主配置文件
useradd itgeeker   #增加用户
passwd itgeeker   #设置用户密码
yum install cyrus-sasl*
 
/bin/systemctl restart saslauthd.service && /bin/systemctl restart postfix.service   #启动postfix和saslauth服务

最好用telnet测试一下,前面安装的telnet就发挥作用了

telnet localhost smtp
ehlo localhost
mail from:
rcpt to:<alanljj@qq.com>
data
Welcome to itgeeker mail server
.
quit
#查看邮件内容
less /var/log/maillog
cd /root/Maildir/new #注意M要大写
ll
cat ***** #*代表列出的文件名,可以查看新的邮件内容
vi /var/log/maillog
Tips小技巧:
有时候telnet登陆后就退不出来了ctrl+c也不管用此时可以使用ctl+] 切换,然后quit退出。

7.  CentOS 7时间同步及更改

和之前基本一样:

date
 
yum install ntpdate -y
ntpdate time.windows.com && hwclock -w
 
#连网更新时间,如果成功,将系统时间,写入BOIS
 
hwclock -w 或 hwclock --systohc
 
date -s 20150119
date -s 17:28:00

8. Shell登陆操作显示中文乱码问题(和CentOS 6一样,问题还是存在)

方法一:

vi /etc/sysconfig/i18n 文件中修改LANG的设置为:
#LANG="en_US.UTF-8"
#SYSFONT="latarcyrheb-sun16"
LANG="zh_CN.GBK"
LANGUAGE="zh_CN.GBK:zh_CN.GB18030:zh_CN.GB2312:zh_CN"
SUPPORTED="zh_CN.GB18030:zh_CN:zh:en_US.UTF-8:en_US:en"
SYSFONT="lat0-sun16"
 
然后在/etc/profile文件中增加export LC_ALL=zh_CN.GBK内容。使得全部的LC*都统一了。
 
重启主机

方法二: 更改shell的显示语言

ITGeeker技术奇客使用的是xshell,直接在当前链接的属性-终端-选择UTF-8为编码即可。如果你经常使用变换使用shell,那就用第一种方法吧。

9. FTP服务安装(vsftpd安装)

为主机开通FTP服务还是非常有必要的,我们为主机快速安装vsftpd吧。

可以参考详细教程 CentOS6.5 64bit如何安装配置FTP服务(vsftpd

yum install vsftpd -y
vi /etc/vsftpd/vsftpd.conf
 
#记得CentOS 7启动命令有所不同
 
systemctl restart vsftpd
systemctl enable vsftpd