rails中strack loop too deep 问题的解决

在将一个Rails3.1.2的应用升级到Rails4.0后,突然发现以前运行的好好的程序无法使用了,抛出”Stack loop too deep”这个异常。
雅美蝶!!!怎么可能,我这个地方又没使用递归,我瞬间打开IDE,找到了出问题的地方。

1
2
3
4
5
6
7
def transcation_in (detail, operator='系统')
self.money = self.money + detail.money
detail.account_type = STATE[:in]
detail.updateby = operator
detail.save
self.account_details << detail
end

如代码所见,逻辑很简单,就是对一个Model对象的简单保存,并加入到关联对象中。这哪里会出现堆栈太深呢。

随后我加入了调试日志

1
2
3
4
5
6
7
8
9
10
11
12
13
def transcation_in (detail, operator='系统')
puts 'start transcation_in'
begin
self.money = self.money + detail.money
detail.account_type = STATE[:in]
detail.updateby = operator
detail.save
self.account_details << detail
rescue Exception => e
puts "====#{e}"
end
puts 'end transcation_in'
end

查看了日志,异常是在执行“detail.save”这个代码的是报的异常。这明明很正常呢。。。。日怪了。

调试日志看不出东西来,那我就进入了debugger模式,发现执行到这一步时,进入了active_record.transactions.rb这个类里面的save方法,但我没开启事务呢。难道是因为方法名字的问题???这有点扯蛋了。。。

我查看了我的Mysql数据表,确实是MyISAM引擎,改成InnoDB试试?尼玛,成功了!!!这里果然用了事务,当数据库表不支持事务时,就抛出了异常!!!

这错误异常信息也太扯蛋了嘛,提示和事务一点关系也没有!

希望这篇文章能为遇到同类问题的人提供解决办法,但问题的根源我还是没找到,为什么会用到事务了呢。

varnish 4.0 安装与配置

Varnish简介

Varnish是一款高性能且开源的反向代理服务器和HTTP加速器,其采用全新的软件体系结构,和现在的硬件体系紧密结合,与传统的Squid相比,varnish具有性能更高,速度更快,管理更加方便等诸多优点。

安装

我们这里只将如何以源码在CentOS上安装,其他方式请参考官方文档

1、下载源代码

1
git clone git://git.varnish-cache.org/varnish-cache

2、安装必须的依赖包

1
yum install libtool automake autoconf groff libedit-devel ncurses-devel pcre-devel pkgconfig python-docutils

3、编译安装

1
2
3
4
5
cd varnish-cache
sh autogen.sh
sh configure --prefix=/usr/local/varnish
make
make install

4、启动

1
2
3
4
5
6
./sbin/varnishd -f var/varnish/default.vcl -s malloc,1G -T 127.0.0.1:2000 -a 0.0.0.0:8080

-f 指定加载配置文件
-s 指定内存库和大小
-T 指定管理端IP和端口
-a 监听的业务IP和端口,这里指监听所有IP

5、查看日志

1
bin/varnishlog

这样就安装好了Varnish,下面就是配置了

配置

Varnish的配置是使用它自定义的VCL语法的,在Varnish启动时,会将配置文件编译为C语言。

  • 内置函数

    vcl_recv:用于接收和处理请求;当请求到达并成功接收后被调用,通过判断请求的数据来决定如何处理请求;

    vcl_pipe:此函数在进入pipe模式时被调用,用于将请求直接传递至后端主机,并将后端响应原样返回客户端;

    vcl_pass:此函数在进入pass模式时被调用,用于将请求直接传递至后端主机,但后端主机的响应并不缓存直接返回客户端;

    vcl_hit:在执行 lookup 指令后,在缓存中找到请求的内容后将自动调用该函数;

    vcl_miss:在执行 lookup 指令后,在缓存中没有找到请求的内容时自动调用该方法,此函数可用于判断是否需要从后端服务器获取内容;

    vcl_hash:在vcl_recv调用后为请求创建一个hash值时,调用此函数;此hash值将作为varnish中搜索缓存对象的key;

    vcl_purge:pruge操作执行后调用此函数,可用于构建一个响应;

    vcl_deliver:将在缓存中找到请求的内容发送给客户端前调用此方法;

    vcl_backend_fetch:向后端主机发送请求前,调用此函数,可修改发往后端的请求;

    vcl_backend_response:获得后端主机的响应后,可调用此函数;

    vcl_backend_error:当从后端主机获取源文件失败时,调用此函数;

    vcl_init:VCL加载时调用此函数,经常用于初始化varnish模块(VMODs)

    vcl_fini:当所有请求都离开当前VCL,且当前VCL被弃用时,调用此函数,经常用于清理varnish模块;

  • 变量类型详解

    req:The request object,请求到达时可用的变量

    bereq:The backend request object,向后端主机请求时可用的变量

    beresp:The backend response object,从后端主机获取内容时可用的变量

    resp:The HTTP response object,对客户端响应时可用的变量

    obj:存储在内存中时对象属性相关的可用的变量

  • 示例

    首先VCL文件头必须要写 ‘vcl 4.0’,强制说明这个配置文件是4.0版本使用的。’import directors’是用于代理后端是多机的情况,比如如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
vcl 4.0;
import directors;

backend web1 {
.host = "localhost";
.port = "8080";
}
backend web2 {
.host = "localhost";
.port = "8080";
}
#web3是由web1和web2共同组成的,同时web3是使用的轮询的方式来选择是路由到web1还是web2。
sub vcl_init {
new web3 = directors.round_robin();
web3.add_backend(web1);
web3.add_backend(web2);
}

sub vcl_recv {
set req.backend_hint = web3.backend();
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For =
req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE") {
return (pipe);
}
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
if (req.method == "PURGE") {
if (client.ip ~ local) {
return(purge);
} else {
return(synth(403, "Access denied."));
}
}
}

性能测试

  • 使用Varnish,请求流媒体资源,并发20个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ab -n 200 -c 20 http://192.168.1.101:8080/data/ring/20090925/47541/1289369.mp3
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.101 (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software: nginx/0.8.46
Server Hostname: 192.168.1.101
Server Port: 8080

Document Path: /data/ring/20090925/47541/1289369.mp3
Document Length: 874370 bytes

Concurrency Level: 20
Time taken for tests: 97.471 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 174934225 bytes
HTML transferred: 174874000 bytes
Requests per second: 2.05 [#/sec] (mean)
Time per request: 9747.065 [ms] (mean)
Time per request: 487.353 [ms] (mean, across all concurrent requests)
Transfer rate: 1752.67 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 14 67 62.7 47 242
Processing: 3390 9448 2377.2 9474 16366
Waiting: 8 75 108.1 44 753
Total: 3437 9516 2382.5 9576 16421

Percentage of the requests served within a certain time (ms)
50% 9576
66% 10462
75% 10881
80% 11306
90% 12746
95% 13703
98% 14877
99% 15983
100% 16421 (longest request)
  • 使用Varnish请求流媒体资源,并发30个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ab -n 200 -c 30 http://192.168.1.101:8080/data/ring/20090925/47541/1289369.mp3
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.101 (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software: nginx/0.8.46
Server Hostname: 192.168.1.101
Server Port: 8080

Document Path: /data/ring/20090925/47541/1289369.mp3
Document Length: 874370 bytes

Concurrency Level: 30
Time taken for tests: 86.679 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 174934106 bytes
HTML transferred: 174874000 bytes
Requests per second: 2.31 [#/sec] (mean)
Time per request: 13001.908 [ms] (mean)
Time per request: 433.397 [ms] (mean, across all concurrent requests)
Transfer rate: 1970.87 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 9 83 100.5 45 771
Processing: 6518 12533 3048.6 12224 21697
Waiting: 12 61 100.9 40 979
Total: 6548 12616 3062.2 12429 21723

Percentage of the requests served within a certain time (ms)
50% 12429
66% 13628
75% 14365
80% 14881
90% 17125
95% 18631
98% 19900
99% 20743
100% 21723 (longest request)
  • 不使用Varnish请求流媒体,并发20个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ab -n 200 -c 20 http://192.168.1.101/1289369.mp3
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.101 (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software: nginx/1.4.7
Server Hostname: 192.168.1.101
Server Port: 80

Document Path: /1289369.mp3
Document Length: 874370 bytes

Concurrency Level: 20
Time taken for tests: 95.333 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 174921600 bytes
HTML transferred: 174874000 bytes
Requests per second: 2.10 [#/sec] (mean)
Time per request: 9533.339 [ms] (mean)
Time per request: 476.667 [ms] (mean, across all concurrent requests)
Transfer rate: 1791.84 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 5 55 55.5 35 538
Processing: 3906 9288 2493.6 9128 17836
Waiting: 2 61 77.9 43 785
Total: 3943 9343 2493.9 9201 17888

Percentage of the requests served within a certain time (ms)
50% 9201
66% 10175
75% 10619
80% 10935
90% 12902
95% 14188
98% 16024
99% 17397
100% 17888 (longest request)
  • 不使用Varnish请求流媒体,并发30个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ab -n 200 -c 30 http://192.168.1.101/1289369.mp3
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.1.101 (be patient)
Completed 100 requests
Completed 200 requests
Finished 200 requests


Server Software: nginx/1.4.7
Server Hostname: 192.168.1.101
Server Port: 80

Document Path: /1289369.mp3
Document Length: 874370 bytes

Concurrency Level: 30
Time taken for tests: 102.131 seconds
Complete requests: 200
Failed requests: 0
Write errors: 0
Total transferred: 174921600 bytes
HTML transferred: 174874000 bytes
Requests per second: 1.96 [#/sec] (mean)
Time per request: 15319.686 [ms] (mean)
Time per request: 510.656 [ms] (mean, across all concurrent requests)
Transfer rate: 1672.57 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 18 62 65.2 49 507
Processing: 4651 14863 4008.9 14227 28418
Waiting: 5 83 117.1 48 1091
Total: 4715 14925 4012.8 14322 28482

Percentage of the requests served within a certain time (ms)
50% 14322
66% 16154
75% 17140
80% 18276
90% 20387
95% 22312
98% 25391
99% 26251
100% 28482 (longest request)

可以看出,使用Varnish后,性能有所提升。

部署rails生产环境

  • 1、添加Gem
1
2
gem 'unicorn'
gem 'execjs'
  • 2、添加unicorn的配置文件
1
2
3
4
5
6
7
8
9
10
11
12
touch config/unicorn.conf
vi config/unicorn.conf
rails_env = ENV['RAILS_ENV'] || 'production'
user 'root'
worker_processes 2
preload_app true
timeout 30
listen 3009
working_directory "/www/htdocs/weixin_backend"
pid "/www/htdocs/weixin_backend/unicorn.pid"
stderr_path "/www/htdocs/weixin_backend/log/unicorn.stderr.log"
stdout_path "/www/htdocs/weixin_backend/log/unicorn.stdout.log"
  • 3、添加启动脚本
1
unicorn_rails -E production -c config/unicorn.rb -D
  • 4、启动之前需要先执行
1
rake assets/precompile
  • 5、配置nginx
1
2
3
4
location /assets 
{
root /www/htdocs/weixin_backend/public;
}

开发微信服务器

最近做了一个微信的商户平台,有些心得,和大家分享一下。

一、申请帐号

首先肯定需要注册一个公众平台帐号了,现在注册就能免费成为订阅号,能使用一些基本的接口功能,如:接收消息,发送消息等。而高级的功能,必须是认证的商户,并且缴纳300元/年的认证费(有点坑啊,一年交一次)。具体的接口文档,可以去查看微信公众平台开发者文档

二、接入

1、申请消息接口

在公众平台网站的高级功能 – 开发模式页,点击“成为开发者”按钮,填写URL和Token,其中URL是开发者用来接收微信服务器数据的接口URL。Token可由开发者任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。

2、验证URL有效性

微信服务器会对你刚才填的URL做GET请求,并带上下面4个参数

参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。

1
2
3
4
加密/校验流程如下:
1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

检验signature的Java示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

/**
* 签名校验工具类
* Created by karidyang on 14-2-23.
*/
public class SignUtils {
public static final String TOKEN = "";

/**
* 验证签名
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机码
* @return 校验是否成功
*/
public static boolean checkSign(String signature, String timestamp, String nonce) {
if (signature == null) {
return false;
}
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");

String[] arr = new String[] { TOKEN, timestamp, nonce };
Arrays.sort(arr);
String str = StringUtils.join(arr,"");
byte[] digest = messageDigest.digest(str.getBytes());
String tmpStr = byteToStr(digest);
return tmpStr != null && tmpStr.equals(signature.toUpperCase());

} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}

return false;
}

/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}

/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];

String s = new String(tempArr);
return s;
}
}
3、成为开发者

验证URL有效性成功后即接入生效,成为开发者。如果公众号类型为服务号(订阅号只能使用普通消息接口),可以在公众平台网站中申请认证,认证成功的服务号将获得众多接口权限,以满足开发者需求。

此后用户每次向公众号发送消息、或者产生自定义菜单点击事件时,响应URL将得到推送。

公众号调用各接口时,一般会获得正确的结果,具体结果可见对应接口的说明。返回错误时,可根据返回码来查询错误原因。全局返回码说明

用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。

此外请注意,微信公众号接口只支持80接口。

三、设置菜单

微信设置菜单的方式比较简单,就是POST一个JSON格式的菜单数据到微信服务器就行了,只是不同的菜单按钮对应不同的功能,比如可点击的,或者执行某项请求的,或者是跳转的。

使用Nginx的一些心得体会

1、安装Nginx

在linux上安装nginx,可以使用源码安装,也可以使用包安装器安装,如 apt-get, yum等,使用包安装器安装比较简单,所以,这里我们介绍源码安装。

首先需要下载源码包,可以在这里下载nginx

下载之后就需要安装。

tar -zxvf nginx-1.4.2.tar.gz
cd nginx-1.4.2
./configure --prefix=/usr/local/nginx_8080 --user=www --group=www  --with-http_stub_status_module --without-http_fastcgi_module --without-http_autoindex_module --without-http_ssi_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --without-http_uwsgi_module --without-http_scgi_module  --without-http_memcached_module
make
make  install

说明:

–prefix : 安装目录

–user: linux中的用户

–group: linux中的用户组

–with-http_stub_status_module : 安装http_status模块

–without-http_fastcgi_module : 如不需要后端接入php、asp等可以不安装fastcgi模块

–without-http_autoindex_module : 不安装文件目录索引模块

–without-http_ssi_module : 不安装https模块

–without-mail_pop3_module –without-mail_imap_module –without-mail_smtp_module : 不安装mail模块

–without-http_memcached_module : 不安装memcached模块

编译以后,会在安装目录下找到conf目录,我们后面的主要配置就是编辑这个目录下的nginx.conf文件。

2、配置

使用ImageMagick处理图片

在处理图片的过程中,一般都会使用ImageMagick,下面我来介绍下如何在CentOS系统中安装ImageMagick。

首先必须查看下系统是否已安装了ImageMagick

identify -version

如果已安装,那么先卸载掉旧版本

yum remove ImageMagick

然后下载安装源文件

cd /usr/local/src/; wget ftp://mirror.aarnet.edu.au/pub/imagemagick/    ImageMagick-6.8.7-9.tar.gz

解压

tar zxvf ImageMagick-6.8.7-9.tar.gz

编译

cd ImageMagick-6.8.7-9
./configure
make 
make install
ldconfig /usr/local/lib

最后使用命令测试

convert logo: logo.gif

######在Java中的使用

在java中要使用ImageMagick的话,必须要引入一个jar包,它就是im4java-1.4.0.jar,使用maven的同学,可以直接使用

<dependency>
    <groupId>org.im4java</groupId>
    <artifactId>im4java</artifactId>
    <version>1.4.0</version>
</dependency>

直接上代码说明比较方便

public class ImageUtils {
    private ConvertCmd cmd = new ConvertCmd();
        private ImageUtils() {
        // Windows下需要以下配置
        // ImageMagick在windows中的安装目录
        String im = "C:\\Program Files\\ImageMagick-6.8.7-Q16"
        ProcessStarter.setGlobalSearchPath(im);
    }

    //缩放
    public void resize(Integer width, Integer height, String src, String dst) throws Exception {
        IMOperation imop = new IMOperation();
        imop.addImage(src);
        imop.resize(width, height);
        imop.addImage(dst);
        cmd.run(imop);
    
    }

    //剪裁
    public void crop(String dst,Integer srcWidth, Integer srcHeight, Integer newWidth, Integer newHeight) throws Exception {
        IMOperation imop = new IMOperation();
        imop.addImage(dst);
        imop.crop(srcWidth, srcHeight, newWidth, newHeight);
        imop.addImage(dst);
        cmd.run(imop);
    
    }

更多功能还请参考官方文档.

优化Jetty

调整Linux内核参数

调整Tcp Buffer Sizes
1
2
3
4
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 16384 4194304

调整Queue Sizes

1
2
3
4
5
net.core.somaxconn = 4096

net.core.netdev_max_backlog = 16385
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_syncookies = 1

调整Ports

1
2
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_recycle = 1

调整JVM参数

调整Jetty配置

调整Acceptors数量

大于等于1,小于等于CPU数量

1
<Set name="Acceptors">4</Set>

为线程池的阻塞队列设置默认大小

默认大小的值以每分钟的请求数来计算

1
2
3
4
5
6
7
8
9
10
11
12
13
<Set name="ThreadPool">
<!-- Default queued blocking threadpool -->
<New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
<Arg>
<New class="java.util.concurrent.ArrayBlockingQueue">
<Arg type="int">6000</Arg>
</New>
</Arg>
<Set name="minThreads">10</Set>
<Set name="maxThreads">500</Set>
<Set name="detailedDump">false</Set>
</New>
</Set>