You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
8.2 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 25 | 答疑(二):特权进程的权限到底是什么?
你好,我是温铭。
专栏更新到现在OpenResty第二版块 OpenResty API 篇,我们就已经学完了。恭喜你没有掉队,仍然在积极学习和实践操作,并且热情地留下了你的思考。
很多留言提出的问题很有价值大部分我都已经在App里回复过一些手机上不方便回复的或者比较典型、有趣的问题我专门摘了出来作为今天的答疑内容集中回复。另一方面也是为了保证所有人都不漏掉任何一个重点。
下面我们来看今天的这 6 个问题。
## 第一问,特权进程的权限
Q我想请问下特权进程是怎么回事如果启动 OpenResty 的本身就是普通用户如何获取root权限呢另外老师可以介绍下特权进程的使用场景有哪些吗
A其实特权进程的权限和 master 进程的权限保持一样。如果你用普通用户身份启动 OpenResty那么 master 就是普通用户的权限,这时候特权进程也就没有什么特权了。
这一点应该还是很好理解的,普通用户启动的进程,无论如何也不会有 root 权限。
至于特权进程的使用场景,我们一般用特权进程来处理的是清理日志、重启 OpenResty 自身等需要高权限的任务。你需要注意的是,不要把 worker 进程的任务交给特权进程来处理。这并非因为特权进程不能做到,而是其存在安全隐患。
我见到过一个开发者,他把定时器的任务都交给了特权进程来处理。他为什么这么做呢?因为特权进程只有一个,这样 timer 就不会重复启动。
是不是觉得这看上去很聪明呀,不用 worker.id 这种笨方法就做到了。但是,别忘了,如果定时器的任务和用户的输入有关,这不就等于留了一个后门吗?显然是非常危险的。
## 第二问,阶段和调试
Q老师是不是无论在哪个阶段运行`ngx.say('hello')`OpenResty都会在执行完本阶段的剩余代码后直接响应给客户端而不会继续执行其他阶段了呢我测试出来是这样的。
A事实上并非如此我们可以来看下它的执行阶段的[顺序图](https://github.com/moonbingbing/openresty-best-practices/blob/master/images/openresty_phases.png)
![](https://static001.geekbang.org/resource/image/71/bf/71b24c95f042f0bf79ac34211e2dd0bf.png)
你可以做个测试,先在 content 里面 `ngx.say`;然后,在 log 或者 body filter 阶段使用 `ngx.log` 来打印下日志试试。
在专栏中,我并没有专门提到在 OpenResty 中做代码调试的问题,这也是开发者经常困惑的地方,我正好顺着这个问题在答疑中聊一下。
其实OpenResty 中的代码调试,并没有断点这些高级功能(相应有一些付费的插件,但我并没有使用过),只能用 `ngx.say` 和`ngx.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老师你好我有下面几个问题想请教一下。
1. 前面讲过,`ngx.var`变量的作用域在nginx C和lua-nginx-module模块之间。这个我不太理解从请求的角度来看是指一个工作进程中的单个请求吗
2. 我的理解是在我们操作模块内的变量时如果两个操作之间有阻塞操作可能会出现竞争。那么如果两个操作之间没有阻塞操作恰好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函数恢复堆栈阶段吗
ANginx 是以性能优先作为设计理念的,它会把时间缓存下来。这一点,我们从 `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` 的调用都出现在事件循环中,这个问题也就明白了吧。
通过这个问题你应该也能发现,开源项目的好处就是,你可以根据蛛丝马迹,在源码中寻找答案,颇有一种破案的感觉。
今天主要解答这几个问题。最后,欢迎你继续在留言区写下你的疑问,我会持续不断地解答。希望可以通过交流和答疑,帮你把所学转化为所得。也欢迎你把这篇文章转发出去,我们一起交流、一起进步。