极客时间已完结课程限时免费阅读

25 | 答疑(二):特权进程的权限到底是什么?

25 | 答疑(二):特权进程的权限到底是什么?-极客时间

25 | 答疑(二):特权进程的权限到底是什么?

讲述:温铭

时长08:42大小7.96M

你好,我是温铭。
专栏更新到现在,OpenResty 第二版块 OpenResty API 篇,我们就已经学完了。恭喜你没有掉队,仍然在积极学习和实践操作,并且热情地留下了你的思考。
很多留言提出的问题很有价值,大部分我都已经在 App 里回复过,一些手机上不方便回复的或者比较典型、有趣的问题,我专门摘了出来,作为今天的答疑内容,集中回复。另一方面,也是为了保证所有人都不漏掉任何一个重点。
下面我们来看今天的这 6 个问题。

第一问,特权进程的权限

Q:我想请问下,特权进程是怎么回事,如果启动 OpenResty 的本身就是普通用户,如何获取 root 权限呢?另外,老师可以介绍下,特权进程的使用场景有哪些吗?
A:其实,特权进程的权限和 master 进程的权限保持一样。如果你用普通用户身份启动 OpenResty,那么 master 就是普通用户的权限,这时候特权进程也就没有什么特权了。
这一点应该还是很好理解的,普通用户启动的进程,无论如何也不会有 root 权限。
至于特权进程的使用场景,我们一般用特权进程来处理的是清理日志、重启 OpenResty 自身等需要高权限的任务。你需要注意的是,不要把 worker 进程的任务交给特权进程来处理。这并非因为特权进程不能做到,而是其存在安全隐患。
我见到过一个开发者,他把定时器的任务都交给了特权进程来处理。他为什么这么做呢?因为特权进程只有一个,这样 timer 就不会重复启动。
是不是觉得这看上去很聪明呀,不用 worker.id 这种笨方法就做到了。但是,别忘了,如果定时器的任务和用户的输入有关,这不就等于留了一个后门吗?显然是非常危险的。

第二问,阶段和调试

Q:老师,是不是无论在哪个阶段运行ngx.say('hello'),OpenResty 都会在执行完本阶段的剩余代码后,直接响应给客户端,而不会继续执行其他阶段了呢?我测试出来是这样的。
A:事实上并非如此,我们可以来看下它的执行阶段的顺序图
你可以做个测试,先在 content 里面 ngx.say;然后,在 log 或者 body filter 阶段使用 ngx.log 来打印下日志试试。
在专栏中,我并没有专门提到在 OpenResty 中做代码调试的问题,这也是开发者经常困惑的地方,我正好顺着这个问题在答疑中聊一下。
其实,OpenResty 中的代码调试,并没有断点这些高级功能(相应有一些付费的插件,但我并没有使用过),只能用 ngx.sayngx.log 来看输出。我知道的开发者,包括 OpenResty 的作者和贡献者们,都是这样来做 debug 的。所以,你需要有强有力的测试案例和调试日志来作为保证。

第三问,ngx.exit 和动手实验

Q:老师,文中的这句话,“OpenResty 的 HTTP 状态码中,有一个特别的常量:ngx.OK。当 ngx.exit(ngx.OK) 时,请求会退出当前处理阶段,进入下一个阶段,而不是直接返回给客户端。”
我记得,ngx.OK应该不能算是 HTTP 状态码,它对应的值是 0。我的理解是:
ngx.exit(ngx.OK)ngx.exit(ngx.ERROR)ngx.exit(ngx.DECLINED)时,请求会退出当前处理阶段,进入下一个阶段;
而当ngx.exit(ngx.HTTP_*)ngx.HTTP_*的各种 HTTP 状态码作为参数时,会直接响应给客户端。
不知道这样想对不对呢?
A:关于你的第一个问题,ngx.ok 确实不是 http 状态码,它是 OpenResty 中的一个常量,值是 0。
至于第二个问题,ngx.exit 的官方文档其实正好可以解答:
When status >= 200 (i.e., ngx.HTTP_OK and above), it will interrupt the execution of the current request and return status code to nginx.
When status == 0 (i.e., ngx.OK), it will only quit the current phase handler (or the content handler if the content_by_lua* directive is used) and continue to run later phases (if any) for the current request.
不过,文档里并没有提到, OpenResty 对于ngx.exit(ngx.ERROR)ngx.exit(ngx.DECLINED)是如何处理的,我们可以自己来做个测试,比如下面这样:
location /lua {
rewrite_by_lua "ngx.exit(ngx.ERROR)";
echo hello;
}
显然,访问这个 location,你可以看到 http 响应码为空,响应体也是空,并没有进入下一个执行阶段。
其实,还是那句话,在 OpenResty 的学习过程中,随着你逐步深入,一定会在某个阶段发现,文档和测试案例都无法回答你的问题。这时候,就需要你自己构建测试案例来验证你的想法了。你可以手动测试,也可以添加在 test::nginx 搭建的测试案例集里面。

第四问,变量和竞争

Q:老师,你好,我有下面几个问题想请教一下。
前面讲过,ngx.var变量的作用域在 nginx C 和 lua-nginx-module 模块之间。这个我不太理解,从请求的角度来看,是指一个工作进程中的单个请求吗?
我的理解是,在我们操作模块内的变量时,如果两个操作之间有阻塞操作,可能会出现竞争。那么,如果两个操作之间没有阻塞操作,恰好 CPU 时间到了后,当前进程进入就绪队列,这样可能产生竞争吗?
A:我们依次来看这几个问题。
第一,关于ngx.var 变量的问题,你的理解是正确的。实际上,ngx.var 的生命周期和请求一致,请求结束它也就消失了。但它的优势,是数据可以在 C 模块和 Lua 代码中传递。这是其他几种方式都无法做到的。
第二,关于变量竞争的问题,其实,只要两个操作之间有 yield 操作,就可能出现竞争,而不是阻塞操作;有阻塞操作时是不会出现竞争的。换句话说,只要你不把主动权交给 Nginx 的事件循环,就不会有竞争。

第五问,共享字典操作是否需要加锁呢?

Q:老师,如果多个 worker 并发存储数据,是不是需要加锁呢?比如下面这个例子:
resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
local lock= ngx.xxxx.lock
lock.lock()
dogs:set("Jim", 8)
lock.unlock()
local v = dogs:get("Jim")
ngx.say(v)
'
A:其实这里不用你自己加锁,共享字典(shared dict)的操作都是原子性的,不管是 get 还是 set。这种类似加锁的处理,OpenResty 已经帮你考虑到了。

第六问,OpenResty 中如何更新时间?

Q:ngx.now()取时间,是发生在 resume 函数恢复堆栈阶段吗?
A:Nginx 是以性能优先作为设计理念的,它会把时间缓存下来。这一点,我们从 ngx.now 的源码中就可以得到印证:
static int
ngx_http_lua_ngx_now(lua_State *L)
{
ngx_time_t *tp;
tp = ngx_timeofday();
lua_pushnumber(L, (lua_Number) (tp->sec + tp->msec / 1000.0L));
return 1;
}
可以看出,ngx.now()这个获取当前时间函数的背后,隐藏的其实是 Nginx 的 ngx_timeofday 函数。而ngx_timeofday 函数,其实是一个宏定义:
#define ngx_timeofday() (ngx_time_t *) ngx_cached_time
这里ngx_cached_time 的值,只在函数 ngx_time_update 中会更新。
所以,这个问题就简化成了, ngx_time_update什么时候会被调用?如果你在 Nginx 的源码中去跟踪它的话,就会发现, ngx_time_update 的调用都出现在事件循环中,这个问题也就明白了吧。
通过这个问题你应该也能发现,开源项目的好处就是,你可以根据蛛丝马迹,在源码中寻找答案,颇有一种破案的感觉。
今天主要解答这几个问题。最后,欢迎你继续在留言区写下你的疑问,我会持续不断地解答。希望可以通过交流和答疑,帮你把所学转化为所得。也欢迎你把这篇文章转发出去,我们一起交流、一起进步。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 3

提建议

上一篇
24 | 实战:处理四层流量,实现Memcached Server
下一篇
26 | 代码贡献者的拦路虎:test::nginx 简介
unpreview
 写留言

精选留言(4)

  • HelloBug
    2019-07-22
    老师,你好,关于共享内存加锁及竞争条件有个疑问~ 假设有这样的场景,所有的工作进程都可以执行到如下操作序列,dict.get, dict.set。进程1执行了dict.get之后,进程2这时获得了共享内存锁,这个时候执行了dict.set,然后进程1再次获得了共享内存锁,执行dict.set之前,看到的其实已经是共享内存中比较老的数据了,然后执行了dict.set操作,覆盖了进程2的操作。这里等待获得共享内存锁的操作,应该是个阻塞操作,按照文中的说法,阻塞操作应该不会产生竞争。可是这里应该是产生了竞争了是吧?难道说这里涉及到把主动权交给nginx的事件循环了吗?
    展开

    作者回复: 我的理解哈,多个 worker 共享了同一个 shared dict,你这里的描述更像是数据库里面的事务,要达到这个效果有两个方法: 1. 如果用 incr 能够满足你的需求的话,就不要用 set; 2. 否则就需要你自己去手工加锁。 如果是 lrucache 的 get 和 set 操作就不会有这个问题,因为它只存在于一个 worker 内。

    1
  • HelloTalk
    2019-11-02
    特权进程的权限和 master 进程的权限保持一样。 这句话的意思是说 特权进程 不是master进程吗? 如在 worker数量为4的情况 nginx nginx: worker process nginx: worker process nginx: worker process nginx: worker process nginx: cache manager process nginx: master process /usr/local/openresty/nginx/sbin/nginx -c 总共就这6个进程,master 就是特权进程吧
    展开
  • 高远
    2019-07-23
    断点调试别忘了lua-resty-repl呀~😁

    作者回复: 置顶:)

  • wusiration
    2019-07-22
    受益良多,谢谢老师的解答