`

京东活动系统--openresty实践之路

 
阅读更多

背景

     先来说下今天的主角openresty,它是一个基于 Nginx Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。可以使用Lua编写脚本,然后部署到Nginx Web容器中运行。利用nginx的高并发处理能力,轻松构建自己的高性能的Web服务。

    但个人觉得,使用lua编写一些复杂业务逻辑不是它的专长,编写成本也比较高。现有业务的web系统,大多还是以javac#等为主。如要完全改为lua实现,几乎不可能完成。

    上一篇分享《京东活动系统亿级流量架构应对之术》的核心,其实是活动页面的浏览与页面的渲染的异步化。即:活动页面在被浏览时,view工程直接从redis或者硬盘获取已经在engine工程被渲染好的页面返回。简而言之,就是通过engine工程提前渲染好页面,实现页面全静态化。保证view工程每次请求都直接获取静态页面返回,无需等待页面渲染。

 

    这样复杂的业务逻辑已经被完全剥离到engine工程。view工程可以采用nginx+lua+redis+硬盘实现自己的高性能web服务。最终架构如下:



 

 

其中,硬盘和redis互为主备,做一个开关相互切换。

 

lua+redis 字符串压缩

engine工程采用java把静态html字符串放入redisview工程再采用luaredis中获取。这里本身没有问题,但是由于活动页面的内容较多,一个静态页面html动辄几百kb,大的甚至几兆。存取速度势必会很慢,很自然的会想到压缩。

 

 

view工程没有引入openresty之前,我们所有的活动页面都是采用的javagzipGZIPInputStreamGZIPOutputStream来进行压缩和解压,也收到很好的收益,并且待压缩内容越长压缩效果越是明显。存入redis前先用GZIPInputStream压缩,把压缩后的字节数组存入redis(在engine中完成)。view工程从redis中取出,再用GZIPOutputStream解压,还原成正常的活动页面静态html字符串返回。如下:



 

现在要把view工程改为用openresty实现,难点就在于用luaredis中取出字符串的内容是用gzip压缩后的,直接返回是乱码。通过查找资料,lua可以调用zlib来实现对字符串的压缩和解压(参考

https://github.com/brimworks/lua-zlib)。

 

engine工程压缩端把gzip压缩方式改成zlib压缩、并存入redisjava zlib压缩可以参考http://snowolf.iteye.com/blog/465433)。view工程就可以直接从redis中获取,再通过zlib解压即可。但由于redis已存在大量gzip压缩的活动页,这种方式没办法实现平滑过渡。

 

通过分析gzipzlib,可以发现他们本质上都是deflate算法进行压缩。只是gzip相对于zlib会多一个描述头部,如果直接用lua-zlib解压gzip压缩的内容,头部会多出一段乱码,其余内容完全一样。我们直接去掉这部分乱码头部即可:



 

 

lua实现如下:

nginx.conf配置如下:

   

location  ~ ^/act/(\w+)\.html{
      default_type "text/html";
      charset utf-8,gbk;
      set $shortUrl $1;
      content_by_lua_file /export/app/view/page_view_redis.lua;
    }
 

 

/export/app/view/page_view_redis.lua代码如下:

 

local redis_pool = require "redis_pool" -- lua redis缓存池封装
local zlib = require "zlib"
local stream = zlib.inflate()
local key = "pc-page-cache-key-"..ngx.var.shortUrl
local red = redis_pool:new()
local res, err = red:get(key)
if res ~= nil then
    -- 解压, page内容为:"xxxxxx<html><header></header><body>test page</body></html>"其中xxxxx即为gzip压缩的乱码头
    local page=stream(res);
    -- 去掉乱码头
    local h1,h2=string.find(r, "<html>")
 
page= string.sub(page,h1)
    -- 返回页面内容
    ngx.say(page)
end
ngx.say("页面不存在")
ngx. ngx.exit(200)
 

 

 

lua+硬盘

先说下,静态活动页面存硬盘:

1engine:渲染页面完成;

2engine:页面内容存入redis

3engine:根据活动url hash规则,向指定的view服务器发送数据推送请求;

4viewview服务器收到请求,从redis中获取页面内容存到指定目录下。

 

步骤4中,从redis中取出,还是要先进行解压,再存储到指定目录,lua代码如下:

nginx.conf配置:

 

# engine 发起的保存页面到本地硬盘请求
    location  ~ ^/save/(\w+)\.html{
      default_type "text/html";
      charset utf-8,gbk;
      set $key $1;
      content_by_lua_file /export/app/view/save_disk.lua;
    }
 

 

/export/app/view/save_disk.lua代码内容如下:

 

local redis_pool = require "redis_pool" -- lua redis缓存池封装
local util_tools = require "util_tools"
local zlib = require "zlib"
local stream = zlib.inflate()
 
local key = "pc-page-cache-key-"..ngx.var.shortUrl
local red = redis_pool:new();
local res, err = red:get(key)
if res ~= nil then
    -- 解压, page内容为:"xxxxxx<html><header></header><body>test page</body></html>"其中xxxxx即为gzip压缩的乱码头
    local page=stream(res);
    -- 去掉乱码头
    local h1,h2=string.find(page, "<html>")
    page= string.sub(page,h1)
 
    -- 通过md5(uri),构造存储目录和文件名
    local pageUri="/act/"..ngx.var.shortUrl..".html"
    local filePath,pageName = util_tools:get_abpath(pageUri)
    local file,err=io.open(filePath)
    if not file then  --如果文件目录不存在,就先创建
        os.execute("mkdir -p "..filePath)
    end
    local f = assert(io.open(filePath..pageName,'w')) --写入硬盘文件中
    f:write(page) -- 存储页面
    f:close()
end
ngx.exit(200)
 

 

 

util_tools.lua代码内容:

 

local rootPath="/export/static/page/"
local uri_pre = "page"
 
local util_tools = {}
 
-- get static html page
-- 如md5(url)=xxxxxxabc,最终的页面内容存放到:"/export/static/page/c/ab/xxxxxxabc",拼装的新uri为:/page/c/ab/xxxxxxabc
function util_tools:get_path(url)
    local md5path = ngx.md5(url)
    local pathlen = string.len(md5path)
    local path1 = string.sub(md5path,-1)
    local path2 = string.sub(md5path,pathlen-2,-2)
    local abPathFile = uri_pre.."/"..path1.."/"..path2.."/"..md5path
    return abPathFile
end
 
-- get static html page path and name
--类似nginx proxy cache对文件的存储结构,把链接先md5,取后三位字符串,拼成文件存放目录
--存取文件:如md5(url)=xxxxxxabc,最终的页面内容存放到:"/export/static/page/c/ab/xxxxxxabc"
function util_tools:get_abpath(url)
    local md5path = ngx.md5(url)
    local pathlen = string.len(md5path)
    local path1 = string.sub(md5path,-1)
    local path2 = string.sub(md5path,pathlen-2,-2)
    local abPathFile = rootPath.."/"..path1.."/"..path2.."/"
    return abPathFile,md5path
end
return util_tools
 

 

步骤3中的url hash规则讲解:由于京东活动页面较多,所有在线的活动静态页面大小已经超过10G。如果把如此大量的数据存放到每台sale服务上,是不可取的。

首先不方便扩展,随着京东业务快速增长,活动数据也会快速增加,而硬盘大小始终有限。

其次,在用lua读取静态页面时,从如此大量的静态文件中查找也会增加耗时。通过活动url规则进行hash,把这些活动页面内容平均分配到不同sale服务器分组,是我们目前采取的方式。其中还涉及到sale服务器主备分组,这里就不再详细讲解,后面单独再做一次分享。

 

再说下从硬盘获取,根据url拼装出文件存放路径,直接从本地硬盘获取即可。代码如下:

 

location  ~ ^/act/(\w+)\.html{
      default_type "text/html";
      charset utf-8,gbk;
      set $shortUrl $1;
      content_by_lua_file /export/app/view/page_view_redis.lua;
    }
/export/app/view/page_view_redis.lua代码内容:
local util_tools = require "util_tools"
-- 如果链接为http://sale.jd.com/act/Ok70VSmKFZo1Weca.html,pageUri为:
-- /act/Ok70VSmKFZo1Weca.html
local pageUri="/act/"..ngx.var.key..".html"
local abpath = util_tools:get_abpath(pageUri)
--abpath相对路径 规则为:/page/c/ab/xxxxxxabc
local resSta = ngx.location.capture(abpath)
if resSta.status == ngx.HTTP_OK then  --读取到硬盘
    if resSta.body ~= nil and resSta.body ~= "nil" and resSta.body ~= "" then
        ngx.say(resSta.body) --返回页面内容
        return ngx.exit(200)
    end
end
 

 

view 工程代码结构(lua

 

再来看下代码结构:

|--export

|-----|app

|---------|view

|--------------| page_view_redis.lua

|--------------| page_view_disk.lua

|--------------| save_disk.lua

|--------------| util_tools.lua

|-----|pro

|--------|nginx

|------------|conf

|----------------|nginx.conf

 

比起java来是不是简洁多了。页面浏览可以读redis和硬盘,可以在lua_shared_dict中存放一个切换开关,根据开关值选择执行page_view_redis.lua或者page_view_disk.lua

 

    好了就写这么多了吧,回顾下这次讲解的主要内容:luaredislua-zlibgzip压缩字符串、lua读硬盘等。

 

  • 大小: 91.2 KB
  • 大小: 53.1 KB
  • 大小: 49.6 KB
分享到:
评论

相关推荐

    OpenResty-Best-Practices(openresty最佳实践)

    openresy最佳实践 Lua 入门 Nginx LuaCjsonLibrary PostgresNginxModule LuaNginxModule LuaRestyDNSLibrary LuaRestyLock 测试

    lua-resty-mongol_openresty连接mongoldb的lua库

    **标题详解:**"lua-resty-mongol_openresty连接mongoldb的lua库" “lua-resty-mongol”是专为OpenResty设计的一个lua库,用于在OpenResty环境中与MongoDB数据库进行交互。这个库的创建是为了在高性能的Web服务中...

    taotao-weblog-analysis基于openresty kafka hadoop hive 离线网站日志点击流数据分

    1、资源内容:taotao-weblog-analysis基于openresty kafka hadoop hive 离线网站日志点击流数据分 2、代码特点:内含运行结果,不会运行可私信,参数化编程、参数可方便更改、代码编程思路清晰、注释明细,都经过...

    lua-resty-shell, 用于OpenResty应用服务器的微型子进程/shell 库.zip

    lua-resty-shell, 用于OpenResty应用服务器的微型子进程/shell 库 简介当你需要执行子进程( 或者 shell 命令) 时,这个小型库是用于与OpenResty应用程序一起使用的。 除了 non completely完全不阻塞外,即使是完全不...

    打破基于OpenResty的WEB安全防护(CVE-2018-9230)1

    《OpenResty WEB安全防护漏洞详解:CVE-2018-9230》 OpenResty,作为一款基于Nginx与Lua...对于OpenResty用户来说,定期更新和维护是最基本的安全实践,同时结合有效的输入验证和过滤策略,可以大大增强系统的安全性。

    taotao-weblog-analysis基于openresty kafka hadoop hive 日志点击流数据分析

    【标题】"taotao-weblog-analysis基于openresty kafka hadoop hive 日志点击流数据分析"涉及的关键技术点包括OpenResty、Kafka、Hadoop和Hive,这些都是大数据处理和分析领域的重要组件。 OpenResty是基于Nginx与...

    z-blog-openresty:程序员技术之旅-OpenResty

    z-博客-openresty依赖 :基于Nginx的高性能Web服务器 :适用Nginx和OpenResty的HTML模板渲染引擎 : PostgreSQL的Lua驱动 :基于Woothee的User-Agent解析器 : OpenResty和ngx_lua中的HTTP客户端 :自动注册和更新...

    lua-nginx-openresty-redis 详细案例源码

    Nginx 高并发系统内核优化 nginx 并发数问题思考:worker_connections,worker_processes与 max clients 如何在工作中提高Ngixn服务器性能?达到高效 并发 = 同步/异步/阻塞/非阻塞/进程/线程 The Complete NGINX...

    lua-resty-session:OpenResty的会话库–灵活且安全

    lua-resty-session是OpenResty的安全,灵活的会话库。 Hello World与lua-resty-session worker_processes 1 ; events { worker_connections 1024 ; } http { server { listen 8080 ; server_name localhost; ...

    OpenResty(openresty-1.21.4.1.tar.gz)

    OpenResty(openresty-1.21.4.1.tar.gz) OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的...

    openresty 在直播领域中的应用.pdf

    OpenResty 的高性能和可扩展性使其成为直播服务的不二之选。 体系结构 ---------- OpenResty 的体系结构主要包括前端的 Nginx 服务器、后端的云存储、RTMP 服务器和 CDN 节点。Nginx 服务器负责处理用户的请求和...

    Windows上nginx-openresty添加rtmp模块

    网上查找nginx-openresty添加rtmp模块的方法基本都是在Linux上的。但由于项目需要在Windows上使用nginx,无奈只好...本资源是Windows上生成好的openresty-1.19.3.1+nginx-rtmp-module的包,同时包含win32、win64版本。

    openresty(nginx-lua-module-zh-wiki)中文文档.pdf

    Web 应用服务器,Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,更主要的是在性能方面,OpenResty可以 快速构造出足以胜任 10K 以上并发连接响应的超高性能 Web 应用系统。 360,UPYUN...

    《OpenResty最佳实践》 .pdf

    《OpenResty最佳实践》这本书籍,旨在向读者介绍OpenResty的使用方法和最佳实践,从而让读者能够充分利用OpenResty进行高效、安全的Web开发。 书籍涵盖了多个知识点,从最基础的Lua脚本语言学习,到OpenResty的高级...

    lua-resty-mlcache:OpenResty的分层缓存库

    快速,自动的OpenResty分层缓存。 结合 API和,可以将该库作为键/值存储缓存标量Lua类型和表,从而提供了一种性能且灵活的缓存解决方案。 特征: 使用TTL进行缓存和否定缓存。 通过内置的互斥可以防止在缓存未...

    heroku-buildpack-openresty-template:Heroku 上 openresty 的模板项目

    heroku-openresty-buildpack-template 这是使用的模板。 对不起,我听到了满嘴的名字,但至少它是确定的。 用法: git clone ...

    openresty源码包,包含pcre zlib openssl等依赖

    1. 解压所有文件,例如:`tar -zxvf openssl-1.1.0j.tar.gz`,`tar -zxvf openresty-1.13.6.2.tar.gz`,等等。 2. 配置和编译OpenSSL,根据系统需求设置合适的选项,如`./config --prefix=/usr/local/openssl && ...

    lua-resty-waf:基于OpenResty堆栈的高性能WAF

    lua-resty-waf作为OpenResty生态系统的一部分,为Nginx提供了强大的WAF能力,它的轻量级设计和高效的Lua集成使得它成为保护Web服务安全的理想选择。通过深入理解和定制lua-resty-waf,开发者可以构建出更健壮、更...

    Openresty_For_Windows_1.7.10.zip

    OpenResty 是一个通过扩展 nginx 的快速 Web 应用服务器。 Nginx Openresty For Windows (NOW) 是带有 Openresty 的 Windows 版本中的 Nginx。 它有一些特点: 高性能 并发两万多个连接 多进程 支持共享内存 支持...

    lua-resty-limit-traffic, 在 openresty/ngx_lua中,用于限制和控制流量的Lua库.zip

    lua-resty-limit-traffic, 在 openresty/ngx_lua中,用于限制和控制流量的Lua库 电子邮件名称lua-resty-limit-traffic - 用于限制和控制 openresty/ngx_lua中流量的Lua库目录名称状态概要说明描述安装工具社区服务...

Global site tag (gtag.js) - Google Analytics