OpenResty(nginx+lua) 开发入门

OpenResty 官网:http://openresty.org/  OpenResty® - 中文官方站 http://openresty.org/cn/

OpenResty 是一个nginx和它的各种三方模块的一个打包而成的软件平台。最重要的一点是它将lua/luajit打包了进来,使得我们可以使用lua脚本来进行web的开发。有了lua,我们可以借助于nginx的异步非阻塞的功能,达到使用 lua 异步并发访问后端的 MySQL, PostgreSQL, Memcached, Redis等等服务。特别是特有的 ngx.location.capture_multi 功能让人印象深刻,其可以达到极大的减少浏览器的http连接数量,并且可以异步并发的访问后台 Java/PHP/Python 等等接口。OpenResty 架构的web可以轻松超越Node.js的性能,并且对后端语言没有限制,你可以使用Java/PHP/Python等等各种语言。OpenResty(nginx+lua)可以替代node.js的前端渲染的功能。

火云邪神语录:天下武功,无坚不破,唯快不破!Nginx的看家本领就是速度,Lua的拿手好戏亦是速度,这两者的结合在速度上无疑有基因上的优势。

最先将Nginx,Lua组合到一起的是OpenResty,它有一个ngx_lua模块,将Lua嵌入到了Nginx里面;随后Tengine也包含了ngx_lua模块。至于二者的区别:OpenResty是Nginx的Bundle;而Tengine则是Nginx的Fork。值得一提的是,OpenResty和Tengine均是国人自己创建的项目,前者主要由春哥和晓哲开发,后者主要由淘宝打理。

至于OpenResty和Tengine孰优孰劣,留给大家自己判断,如下资料可供参考:

ngx_openresty: an Nginx ecosystem glued by Lua

淘宝网Nginx应用、定制与开发实战

推荐看看春哥在Tech-Club上关于『由Lua粘合的Nginx生态环境』的演讲实录,有料!

安装

需要最新版的Nginx,LuaJIT,ngx_devel_kit,ngx_lua等安装文件。

安装Lua或者LuaJIT都是可以的,但是出于效率的考虑,推荐安装LuaJIT。

详细参考:Nginx与Lua | 火丁笔记 https://huoding.com/2012/08/31/156

OpenResty (aka. ngx_openresty) is a full-fledged web application server by bundling the standard Nginx core, lots of 3rd-party Nginx modules, as well as most of their external dependencies.

By taking advantage of various well-designed Nginx modules, OpenResty effectively turns the nginx server into a powerful web app server, in which the web developers can use the Lua programming language to script various existing nginx C modules and Lua modules and construct extremely high-performance web applications that are capable to handle 10K+ connections.

OpenResty aims to run your server-side web app completely in the Nginx server, leveraging Nginx's event model to do non-blocking I/O not only with the HTTP clients, but also with remote backends like MySQL, PostgreSQL, Memcached, and Redis.

1. 安装OpenResty

先安装依赖:yum install readline-devel pcre-devel openssl-devel gcc

解压: tar zxvf ngx_openresty-1.9.3.1.tar.gz

建立一个软连接:ln -s ngx_openresty-1.9.3.1 openresty

进入目录:cd openresty

编译:

./configure \

            --with-cc-opt="-I/usr/local/include" \

            --with-ld-opt="-L/usr/local/lib" \

            --prefix=/opt/openresty

... ...

Configuration summary

  + using system PCRE library

  + using system OpenSSL library

  + md5: using OpenSSL library

  + sha1: using OpenSSL library

  + using system zlib library

  nginx path prefix: "/opt/openresty/nginx"

  nginx binary file: "/opt/openresty/nginx/sbin/nginx"

  nginx configuration prefix: "/opt/openresty/nginx/conf"

  nginx configuration file: "/opt/openresty/nginx/conf/nginx.conf"

  nginx pid file: "/opt/openresty/nginx/logs/nginx.pid"

  nginx error log file: "/opt/openresty/nginx/logs/error.log"

  nginx http access log file: "/opt/openresty/nginx/logs/access.log"

  nginx http client request body temporary files: "client_body_temp"

  nginx http proxy temporary files: "proxy_temp"

  nginx http fastcgi temporary files: "fastcgi_temp"

  nginx http uwsgi temporary files: "uwsgi_temp"

  nginx http scgi temporary files: "scgi_temp"

其中 --prefix=/opt/openresty 指定了安装目录,不指定的话默认会安装到 /usr/local/openresty 目录下。

编译安装: make && make install

[root@localhost src]# cd /opt/openresty/

[root@localhost openresty]# ls

bin  luajit  lualib  nginx

可以看到 /opt/openresty 目录下四个文件夹,其中包括了 luajit,nginx。

启动openresty: /opt/openresty/nginx/sbin/nginx -c /opt/openresty/nginx/conf/nginx.conf -p /opt/openresty/nginx/

[root@localhost src]# ps -elf|grep nginx

1 S root      2076    1  0  80  0 - 34999 -      21:24 ?        00:00:00 nginx: master process /opt/openresty/nginx/sbin/nginx -c /opt/openresty/nginx/conf/nginx.conf -p /opt/openresty/nginx/

5 S nobody    2077  2076  0  80  0 - 35045 -      21:24 ?        00:00:00 nginx: worker process                                   

0 S root      2079  1678  0  80  0 -  1088 -      21:24 pts/1    00:00:00 grep nginx

验证可以访问: curl 127.0.0.1

2. content_by_lua 和 content_by_lua_file

nginx 如何嵌入 lua 脚本。方法就是在nginx的配置文件nginx.conf 中使用 content_by_lua 或者 cotent_by_lua_file 指令:

1) content_by_lua 一般在很简单的lua脚本时使用:

        location /lua {

                set $test "hello, world.";

                content_by_lua '

                        ngx.header.content_type = "text/plain";

                        ngx.say(ngx.var.test);

                ';

        }

访问 http://localhost/lua 可以看到输出到页面的  hello, world.

2)cotent_by_lua_file 适应于复杂的 lua 脚本,专门放入一个文件中:

        location /lua2 {

            #lua_code_cache off;

            content_by_lua_file lua/hello.lua;

        }

路径相对于 /opt/openresty/nginx

[root@localhost lua]# pwd

/opt/openresty/nginx/lua

[root@localhost lua]# cat hello.lua

ngx.say('hello ngx_lua!!!!');

本例子中 hello.lua 只包含一句: ngx.say('hello ngx_lua!!!!');

访问 /lua2 :

[root@localhost lua]# curl localhost/lua

hello ngx_lua!!!!

可以看到访问成功。

在 nginx.conf 文件的 server {.. ...} 中加入 lua_code_cache off; 可以方便调试lua脚本,修改lua脚本之后,不需要 reload nginx.

openresty 中的 nginx 嵌入 luajit 的原理:

每一个nginx的进程中都嵌入了一个 luajit的虚拟机,来执行lua脚本。nginx将lua脚本的执行交给了luajit vm.

3. ngx_lua 的指令 和 API

上面我们说到 nginx 嵌入 lua 脚本可以使用 content_by_lua 和 content_by_lua_file,它们其实是指令(Directives),类似的指令还有很多,

具体参见:https://www.nginx.com/resources/wiki/modules/lua/#directives

这些指令都是 nginx 访问 lua 脚本的入口。

ngx_lua API:

指令是 nginx 访问 lua 脚本的入口。那么lua脚本如何调用nginx中的函数呢?就是通过 ngx_lua 的API 。

具体介绍参见:https://www.nginx.com/resources/wiki/modules/lua/#nginx-api-for-lua

The various *_by_lua and *_by_lua_file configuration directives serve as gateways to the Lua API within the nginx.conf file. The NGINX Lua API described below can only be called within the user Lua code run in the context of these configuration directives.

The API is exposed to Lua in the form of two standard packages ngx and ndk. These packages are in the default global scope within ngx_lua and are always available within ngx_lua directives.

其实nginx和Lua的交互开发主要就是指令和API,当然还有lua脚本的语法。指令是nginx访问lua的入口,API是lua调用nginx的函数,lua是脚本编程语言。

指令其实很简单,所以主要就是熟悉ngx_lua的 API 和Lua语法。

4. lua 访问 redis

lua-resty-redis 模块:https://github.com/openresty/lua-resty-redis (有文档可以参考)

在nginx.conf中加入:

        location /redis_test{

            content_by_lua_file lua/redis_test.lua;

        }

redis_test.lua 内容:

[root@localhost lua]# cat redis_test.lua

local redis = require "resty.redis"

local red = redis:new()

red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)

if not ok then

        ngx.say("failed to connect: ", err)

        return

end

ngx.say("set result: ", ok)

local res, err = red:get("dog")

if not res then

        ngx.say("failed to get doy: ", err)

        return

end

if res == ngx.null then

        ngx.say("dog not found.")

        return

end

ngx.say("dog: ", res)

[root@localhost lua]#

访问:

[root@localhost lua]# curl localhost/redis_test

set result: 1

dog: an animal

[root@localhost lua]#

我们看到访问成功。

5. lua 访问mysql

openresty的mysql模块:lua-resty-mysql :https://github.com/openresty/lua-resty-mysql(有文档可以参考)

在nginx.conf加入如下配置:

        location /mysql_test {

            content_by_lua_file lua/mysql_test.lua;

        }

mysql_test.lua脚本内容:

[root@localhost lua]# pwd

/opt/openresty/nginx/lua

[root@localhost lua]# cat mysql_test.lua

local mysql = require "resty.mysql"

local db, err = mysql:new()

if not db then

        ngx.say("failed to instantiate mysql: ", err)

        return

end

db:set_timeout(1000)

local ok, err, errno, sqlstate = db:connect{

        host = "127.0.0.1",

        port = 3306,

        database = "ngx_lua",

        user = "root",

        password="digdeep",

        max_packet_size = 1024 * 1024

}

if not ok then

        ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)

        return

end

ngx.say("connected to mysql.")

local res, err, errno, sqlstate = db:query("drop table if exists cats")

if not res then

        ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")

        return

end

res, err, errno, sqlstate = db:query("create table cats " .. "(id int not null primary key auto_increment, "

                                        .. "name varchar(30))")

if not res then

        ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")

        return

end

ngx.say("table cats created.")

res, err, errno, sqlstate = db:query("insert into cats(name) " .. "values (\'Bob\'),(\'\'),(null)")

if not res then

        ngx.say("bad request: ", err, ": ", errno, ": ", sqlstate, ".")

        return

end

ngx.say(res.affected_rows, " rows inserted into table cats ", "(last insert id: ", res.insert_id, ")")

res, err, errno, sqlstate = db:query("select * from cats order by id asc", 10)

if not res then

        ngx.say("bad result ", err, ": ", errno, ": ", sqlstate, ".")

        return

end

local cjson = require "cjson"

ngx.say("result: ", cjson.encode(res))

local ok, err = db:set_keepalive(1000, 100)

if not ok then

        ngx.say("failed to set keepalive: ", err)

        return

end

测试:

[root@localhost lua]# curl localhost/mysql_test

connected to mysql.

table cats created.

3 rows inserted into table cats (last insert id: 1)

result: [{"name":"Bob","id":1},{"name":"","id":2},{"name":null,"id":3}]

测试通过。

5. lua 的 capture 和 capture_multi(子查询)

capture_multi 是 openresty 一个十分强大的功能。它能极大的减少前端浏览器发送的http请求的数量,突破了浏览器对于同一个服务器并发请求数量的限制,因为他可以将前端的多个http请求减少为只要一个http请求到nginx,然后nginx使用capture_multi特性,对后端发起多个异步并发请求,然后统一将结果返回给前端。下面看一个例子:

首先在nginx.conf中加入下面的 location 配置,并且配置好 nginx 访问 php 的配置:

        location /capture {

            content_by_lua_file lua/capture.lua;

            #access_by_lua_file lua/capture.lua;

        }

        location ~ \.php$ {

            root          html;

            fastcgi_pass  127.0.0.1:9000;

            fastcgi_index  index.php;

            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;

            include        fastcgi_params;

        }

capture.lua 的代码如下:

[root@localhost lua]# pwd

/opt/openresty/nginx/lua

[root@localhost lua]# cat capture.lua

local res1,res2,res3,res4 = ngx.location.capture_multi{

        {"/mysql_test", {args="t=1&id=1"}},

        {"/redis_test", {args="t=2&id=2"}},

        {"/lua", {args="t=3&id=3"}},

        {"/index.php", {args="t=3&id=3"}},

}

ngx.header.content_type="text/plain"

ngx.say(res1.body)

ngx.say(res2.body)

ngx.say(res3.body)

ngx.say(res4.truncated)

ngx.say(res4.status)

ngx.say(res4.header["Set-Cookie"])

--ngx.say(res4.body)

index.php 代码:

[root@localhost html]# pwd

/opt/openresty/nginx/html

[root@localhost html]# cat index.php

<?php

        echo phpinfo();

?>

访问:

[root@localhost html]# curl localhost/capture

connected to mysql.

table cats created.

3 rows inserted into table cats (last insert id: 1)

result: [{"name":"Bob","id":1},{"name":"","id":2},{"name":null,"id":3}]

set result: 1

dog: an animal

hello ngx_lua!!!!

false

200

nil

可以看到访问成功了。/mysql_test,/redis_test, /lua, /index.php 四个请求的结果都输出了。

注意:

ngx.location.capture_multi{... ...} 中的多个异步并发请求可以是 nginx.conf 中配置的 location(比如 /mysql_test, /redis_test, /lua),也可以不是 location配置的路径,比如 index.php 就不是。index.php 就是一个简单的后台php 脚本。当然也可以是一个 java 实现的后台接口。

6. openresty的缓存 lua_shared_dict

定义一个缓存:

在nginx的配置文件 nginx.conf 的 http 端下面加入指令:

lua_shared_dict ngx_cache 128m;

就定义了一个 名称为 ngx_cache 大小为128m的内存用于缓存,注意该缓存是所有nginx work process所共享的。

在lua脚本中访问缓存:

local ngx_cache = ngx.shared.ngx_cache

local value = ngx_cache:get(key)

local succ, err, forcible = ngx_cache:set(key, value, exptime)

下面测试一下,首先在 nginx.conf的server端中加入:

        location /cache {

            content_by_lua_file lua/cache.lua;

        }

然后编写 cache.lua 脚本:

[root@localhost lua]# cat cache.lua

local redis = require "resty.redis"

local red = redis:new()

function set_to_cache(key, value, exptime)

        if not exptime then

                exptime = 0

        end

        local ngx_cache = ngx.shared.ngx_cache

        local succ, err, forcible = ngx_cache:set(key, value, exptime)

        return succ

end

function get_from_cache(key)

        local ngx_cache = ngx.shared.ngx_cache;

        local value = ngx_cache:get(key)

        if not value then

                value = get_from_redis(key)

                set_to_cache(key, value)

                return value

        end

        ngx.say("get from cache.")

        return value

end

function get_from_redis(key)

        red:set_timeout(1000)

        local ok, err = red:connect("127.0.0.1", 6379)

        if not ok then

                ngx.say("failed to connect: ", err)

                return

        end

        local res, err = red:get(key)

        if not res then

                ngx.say("failed to get doy: ", err)

                return ngx.null

        end

        ngx.say("get from redis.")

        return res

end

function set_to_redis(key, value)

        red:set_timeout(1000)

        local ok, err = red:connect("127.0.0.1", 6379)

        if not ok then

                ngx.say("failed to connect: ", err)

                return

        end

        local ok, err = red:set(key, value)

        if not ok then

                ngx.say("failed to set to redis: ", err)

                return

        end

        return ok

end

set_to_redis('dog', "Bob")

local rs = get_from_cache('dog')

ngx.say(rs)

测试:

[root@localhost ~]# curl localhost/cache

get from redis.

Bob

[root@localhost ~]# curl localhost/cache

get from cache.

Bob

[root@localhost ~]# curl localhost/cache

get from cache.

Bob

第一次从 redis中获取,以后每次都从cache中获取。

可以使用 ab 测试一下rps(Requests per second):

ab -n 1000 -c 100 -k http://127.0.0.1/cache

7. 解决缓存失效风暴 lua-resty-lock

缓存失效风暴是指缓存因为时间过期而失效时,会导致所有的请求都去访问 后台的redis或者mysql,而导致CPU性能即刻增长的现象。所以关键是当缓存失效时,用lock保证只有一个线程去访问后台的redis或者mysql,然后更新缓存。需要使用到 lua-resty-lock 模块的加锁、解锁功能。

lua-resty-lock 文档:https://github.com/openresty/lua-resty-lock

首先在nginx.conf 的 http 端下面加入指令:

lua_shared_dict ngx_cache 128m;    # cache

lua_shared_dict cache_lock 100k;    # lock for cache

然后在nginx.conf的server端中加入:

        location /cache_lock {

            content_by_lua_file lua/cache_lock.lua;

        }

cache_lock.lua代码:

cat cache_lock.lua

local redis = require "resty.redis"

local resty_lock = require "resty.lock"

local red = redis:new()

function set_to_cache(key, value, exptime)

    if not exptime then

        exptime = 0

    end

    local ngx_cache = ngx.shared.ngx_cache

    local succ, err, forcible = ngx_cache:set(key, value, exptime)

    return succ

end

function get_from_cache(key)

    local ngx_cache = ngx.shared.ngx_cache;

    local value = ngx_cache:get(key)

    if not value then

        value = get_from_redis(key)

        set_to_cache(key, value)

        return value

    end

    ngx.say("get from cache.")

    return value

end

function get_from_redis(key)

    red:set_timeout(1000)

    -- lock , then get cache

    local lock, err = resty_lock:new("cache_lock")

    if not lock then

        return fail("failed to create lock: ", err)

    end

    local elapsed, err = lock:lock("lock_cache_key")

    if not elapsed then

        ngx.log(log.ERR, "failed to acquire the lock: ", err)

    end

    ngx.sleep(5)

    local ok, err = red:connect("127.0.0.1", 6379)

    if not ok then

        ngx.say("failed to connect: ", err)

        --unlock

        local ok, err = lock:unlock()

        if not ok then

            return fail("failed to unlock: ", err)

        end

        return

    end

    local res, err = red:get(key)

    if not res then

        ngx.say("failed to get doy: ", err)

        --unlock

        local ok, err = lock:unlock()

        if not ok then

            return fail("failed to unlock: ", err)

        end

        return ngx.null

    end

    --unlock

    local ok, err = lock:unlock()

    if not ok then

        return fail("failed to unlock: ", err)

    end

    ngx.say("get from redis.")

    return res

end

function set_to_redis(key, value)

    red:set_timeout(1000)

    local ok, err = red:connect("127.0.0.1", 6379)

    if not ok then

        ngx.say("failed to connect: ", err)

        return

    end

    local ok, err = red:set(key, value)

    if not ok then

        ngx.say("failed to set to redis: ", err)

        return

    end

    return ok

end

local rs = get_from_cache('dog')

ngx.say(rs)

测试:

[root@localhost lua]# curl localhost/cache_lock

get from cache.

Bob

[root@localhost lua]# curl localhost/cache_lock

get from cache.

Bob

7. openresty 执行阶段

nginx的执行阶段分成了很多个阶段,所以第三方模块就可以在某个适当的阶段加入一些处理。openresty进行了简化成了7个阶段:

7个阶段的执行顺序如下:

set_by_lua: 流程分支判断,判断变量初始哈

rewrite_by_lua: 用lua脚本实现nginx rewrite

access_by_lua: ip准入,是否能合法性访问,防火墙

content_by_lua: 内存生成

header_filter_by_lua:过滤http头信息,增加头信息

body_filter_by_lua: 内容大小写,内容加密

log_by_lua: 本地/远程记录日志

但是其实我们可以只用 content_by_lua,所有功能都在该阶段完成,也是可以的。

总结:

OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

参考 组件 可以知道 OpenResty® 中包含了多少软件。

参考 上路 学习如何从最简单的 hello world 开始使用 OpenResty® 开发 HTTP 业务,或前往 下载 直接获取 OpenResty® 的源代码包开始体验。

---------------------

作者:天府云创

来源:CSDN

原文:https://blog.csdn.net/enweitech/article/details/78519398

版权声明:本文为博主原创文章,转载请附上博文链接!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342