月度归档:2015年11月

博客全面迁移并启用https链接

博客现在已经迁移到了阿里云青岛9.9学生服务器。

使用StartCom StartSSL证书用于https加密链接。

附上nginx通过ssllabs A+测试的配置文件

server {
    listen       80;
    listen	 443 ssl;
    server_name  www.summershrimp.com summershrimp.com;

    ssl on;
    ssl_certificate      /home/ubuntu/.ssl_cert/www/cert.pem;
    ssl_certificate_key  /home/ubuntu/.ssl_cert/www/cert.key;
    ssl_trusted_certificate /home/ubuntu/.ssl_cert/www/cert.pem;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    resolver 114.114.114.114;
    ssl_stapling on;
    ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
    ssl_prefer_server_ciphers on;
    ssl_dhparam /home/ubuntu/.ssl_cert/dhparam.pem;

    error_page 497 =307 @https;
    location @https {
        rewrite ^(.*)$ https://$host$1 permanent;
    }
    add_header Strict-Transport-Security "max-age=63072000; preload";

    root /opt/blog/;
    index index.php index.htm index.html;
    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    if (!-e $request_filename){
        rewrite (.*) /index.php;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        fastcgi_pass   unix:/var/run/php5-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\.ht {
        deny  all;
    }
}

 

SSL Server Test- summershrimp.com (Powered by Qualys SSL Labs)

HttpLuaModule在Coding WebIDE中的应用

0x00 前言

HttpLuaModule又名ngx_lua,由国人大神agentzh(章奕春)开发。ngx_lua将lua脚本语言嵌入nginx中,并用lua封装了部分nginx的API,使nginx开发不再需要繁琐的C语言进行。目前,ngx_lua在阿里cdn,又拍云cdn中均发挥了极大的作用。

Coding作为一个技术导向的创业公司,也在Coding WebIDE混合架构中使用了ngx_lua。

0x01 ngx_lua介绍

ngx_lua 通过在nginx的处理阶段中使用lua或luajit(推荐)插入lua脚本,对当前阶段的请求进行处理,使nginx具有更复杂的逻辑功能。由于lua的紧凑、快速以及内建协程,所以在保证高并发服务能力的同时极大地降低了业务逻辑实现成本。

推荐阅读浅谈 ngx_lua 在 UPYUN 的应用

0x02 应用背景

Coding WebIDE是国内第一个基于Web的集成开发环境(IDE),目前提供给用户一定的代码储存空间和一个完整的Ubuntu Terminal用于在线调试

由于IDE在使用的过程中存在状态,因此每个用户的每个Workspace必须存放在某台固定的机器上。这就要求Balancer将用户每次分配到相同的机器上。

在引入ngx_lua前,Service会在用户创建Workspace时将用户所在的机器名返回给Web UI,Web UI在每次Ajax请求的时候会带上`X-Space-Key` 和 `X-Sharding-Group`两个HTTP Header,nginx在请求中根据X-Sharding-Group的值来选择对应的Service。这里将后端的机器名暴露给了用户,带来了安全隐患。

Coding WebIDE还有一个生成访问URL的功能,可将用户在Terminal中启动的Http Application暴露在公网中,供用户调试使用。

在引入ngx_lua前,每次对访问URL的请求都会进入Service,由Service找到对应Container的IP,并通过`X-Accel-Redirect`的方式通知每个Service中的nginx,去返回目标Container中的服务。而在这个过程中,用户的请求会两次经过Service层的nginx,造成较大的延时。

0x03 ngx_lua的应用

Balancing

我们使用ngx_lua将原本需要在X-Sharding-Group头中的backend地址去掉,通过ngx_lua在数据库和缓存中检索X-Space-Key来找到对应的Service,并将upstream设为对应机器,杜绝了请求中带有Service机器名带来的隐患。同时,Redis作为缓存的加入并不会导致性能有太大的降低。

if ngx.var.backend_upstream ~= "" then
	ngx.log(ngx.ALERT, ngx.var.backend_upstream, " From Header")
	return
end

local spaceKey = ngx.req.get_headers()['X-Space-Key']
targetGroup = config.defaultGroup
redisCli = init_redis()
mysqlConn = init_mysql()
if spaceKey ~= nil then
	local shardingGroup, err = 在缓存中查询
	if shardingGroup ~= nil and shardingGroup ~= ngx.null then
		targetGroup = shardingGroup
		ngx.log(ngx.ALERT, shardingGroup, " From Redis")
	else
		res, err, errno, sqlstate = 在数据库中查询
		if not res then
			ngx.log(ngx.ERR, err)
			ngx.exit(500)
		else
			shardingGroup = res;
			if shardingGroup ~= nil then
				缓存数据
				targetGroup = shardingGroup
			end
		end
	end
end

ngx.var.backend_upstream = targetGroup
ngx.log(ngx.ALERT, "Workspace ", spaceKey, " final upstream: ",targetGroup)

close_cosock(mysqlConn)
close_cosock(redisCli)

 

 

Access URL

Access URL在引入ngx_lua后,性能得到了极大的提升。在用户生成了Access URL后,Service会将相关数据缓存入redis,在用户访问URL时,将由Frontend Balancer直接寻找对应container的信息,并直接要求Backend Service返回对应的请求。

redisCli = init_redis()

local upstream = ""

local host = ngx.req.get_headers()['host']
local spaceKey, port, token = string.match(host, "^([^-]+)-([^-]+)-([^.]+)[.]") --匹配spaceKey, port, token sdciqw-58647-eidsae.box.io ->  sdciqw, 58647, eidsae

local redisKey = spaceKey..":"..port
local jsonStr, err = redisCli:get(redisKey)
if jsonStr == nil or jsonStr == ngx.null then
	ngx.log(ngx.ERR, "access a non-exist http forwarding upstream")
else
    local luahf = cjson.decode(jsonStr)
    if luahf.token == token and luahf.ip ~= cjson.null  then
        upstream = luahf.host.."/"..luahf.ip.."/"..port..ngx.var.request_uri
    end
end
close_cosock(redisCli)
ngx.log(ngx.ALERT, "Http forwarding upstream: ", upstream)
if upstream == "" then
	ngx.exit(502)
else
	ngx.var.upstream = upstream
end

原本需要nginx转发两次的请求现在只需要一次就可以到达。大大提升了用户的体验。

WebSocket

由于WebSocket不能自定义Header,所以使用了类似于Access URL的方法进行Balancing。Web UI在建立WebSocket时所需的握手时间也有一定的降低。

0x04 ngx_lua的踩坑经历

cosocket在不同阶段的可用性

resty.mysql和resty.redis均采用了nginx中提供的cosocket进行socket链接。但cosocket并不是在每个nginx的访问阶段都可用。在我们第一版测试的时候使用了set_by_lua直接对nginx变量进行赋值。但是在这个阶段只能使用redis_lua和luasql.mysql进行访问。但这两个模块使用了lua原生的socket库,并不能复用nginx的socket链接。而且由于ngx_lua的特殊性,无法在外部模块中使用连接池,导致链接开销过大,速度降低等问题。

因此,我们将原在set_by_lua中执行的任务使用access_by_lua的方式重写,使用了复用cosocket的openresty系列库。

sql和redis的连接池

由于ngx_lua的特殊性,无法使用传统意义上的连接池。但是openresty提供了基于cosocket的连接池,可以减少每次重连造成的开销。

function close_cosock(cosock)
	if not cosock then
		return
	end
	local ok, err = cosock:set_keepalive(config.pool.idle_time, config.pool.size)
	if not ok then
		ngx.say("set keepalive error : ", err)
	end
end

使用如上代码关闭`resty.redis`和`resty.mysql`的链接便可将cosocket链接放入连接池,等待下次connect。

生产和开发环境不一致

根据官方文档,生产环境需要打开lua_code_cache。但是开发环境可以不打开lua_code_cache。当不打开code cache时,每次请求都会重新加载lua文件,这使得lua文件可以获得及时的更新。

在我们的测试中,当生产环境打开code cache后,部分

0x05 总结

综上,集成lua的nginx可以完成很多之前需要在Backend Service中完成的功能,可以减少不同模块之间的耦合度,还能一定程度上提升应用性能。

lua的开发周期和成本也比用C开发nginx模块要低得多,便于快速上线和迭代开发。因此,不妨尝试将部分业务放入ngx_lua中完成。