【leetcode】2. 数组-寻找数组的中心索引
接入层 Nginx 部分 worker 进程死锁解决方案
背景
部分 worker 进程 CPU 使用率达到100%整个 worker 不可用。
原因
诱因
上游长连接服务上线,由于服务发现服务生效时间长,导致 Nginx 连接上游失败,在释放连接时 Nginx worker 进程可能在持有读锁时出现问题,从而导致读写锁的状态和实际情况不一致,最终读锁永远没法释放,而写锁会死锁。
主因
通过火焰图分析,发现整个进程的耗时都在 ngx_rwlock_wlock 这个函数上。
通 GDB 查看该进程的堆栈信息,执行下面的命令(121606 是对应 CPU 打满进程的进程号 ),查看进程调用栈。
1 | # 先执行下面命令进入 GDB |
上述命令执行的结果如下图,可以看出 worker 进程在释放连接时出现问题,最终死锁在 ngx_rwlock_wlock 函数上。
openresty 执行阶段问题踩坑
背景
由于业务需求我们需要根据client_ip
对部分接口做一个简单的 ab test,将部分流量导新的服务上。基于之前做 WAF 的经验,制定了大体方案:根据 client_ip
做 hash, 根据 hash 值来确定是请求新后端还是原来的后端服务。
在说明详细方案前,先说明一下现状。当前的配置如下:
1 | location ~ ^/api/rest/v\d+/Login { |
其中,/loginServer
是当前服务路径前缀,/loginServerNew
是新服务路径前缀。所以,具体需求可以描述为,10% 的用户使用/loginServerNew
服务,90% 的用户使用/loginServer
服务。
具体方案
- 根据字符串 client_ip 转成长整数 ip_num;
- 对 ip_num 按 100 取模,取最后两位 hash;
- 当 hash < 10 时走
/loginServerNew
服务, 设置tag="New"
, 否则tag=""
- 改造
rewrite
指令为rewrite ^(.*)$ /loginServer$tag$1 break;
对应配置为
1 | location ~ ^/api/rest/v\d+/Login { |
对应 lua 脚本(conf/lua/btest.lua):
1 | local client_ip = ngx.var.http_clientip or ngx.var.remote_addr |
nginx + lua 开发中过程中 post body 过大返回 4xx (续)
背景
随着业务发展会出现较大的 post body 数据,按照nginx + lua 开发中过程中 post body 过大返回 4xx提到的方式修改后,大部分情况下 post body 正常接收并处理落日志。但会偶现空日志的情况。
问题分析
经过多轮本地和沙盒压测,复现了问题。由于在出现空日志情况是 error 日志并没留下相关信息,随后做了如下处理:
- 把 error 日志级别调到 debug,当问题复现时,error.log 中会有客户端过早断开连接类似的日子打出。
- 在 access 日志中添加 request_time, status,等信息,发现出现空日志时,status=408,request_time 都比较长。
因此,可以明确出现该问题是客户端链接超时造成的。
解决方案
为解决该问题,做如下优化:
调整超时时间,和 buffer
1 | client_body_timeout 10s; |
nginx + lua 开发中过程中 post body 过大返回 4xx
背景
基于 OpenResty 提供 post 接口,调用方调用该接口 post 数据,该接口接收 post 过来的数据,复用 Nginx access 日志落盘。
问题
当用户的 body 体过大时,ngx.req.get_body_data()
读请求体,会出现读取不到直接返回 nil
的情况。
问题原因
究其原因,主要是 Nginx 诞生之初主要是为了解决负载均衡情况,而这种情况,是不需要读取 body 就可以决定负载策略的,所以这个点对于 API Server 和 Web Application 开发的同学有点怪。
解决办法
- 如果你只是某个接口需要读取 body(并非全局行为),那么这时候也可以显示调用
ngx.req.read_body()
接口 - 如果想全局生效的话需要使用命令
lua_need_request_body on;
当选择上述其中一种,甚至两种方案都使用了,依旧还解决不了问题,这时候,需要坚持 body 体是不是太大了。这是需要设置如下两个命令:
1 | client_body_buffer_size 256k; #默认8k|16k |
3.1 lua 函数
通过前两章的学习大体掌握了lua的基本语法,这部分语法和其他语言大体类似,可以说这是一门语言语法的最最基础的部分了。在接下来的部分,将学习一下带有lua特性的语法知识。
返回值
通常一个函数会返回一个返回值,比如C/C++等,也有一些语言会返回多个返回值,比如python。lua也支持多返回值。以标准库中的string.find()
函数为例,该函数会返回匹配字符串在搜索字符串中的起始位置索引,如果没匹配成功则返回nil
。
1 | #!/usr/bin/bua |
对于返回值,lua有一个特性——尽可能的调整返回值的数量以适应调用环境,具体表现为一下规则:
- 当作为调用表达式最后一个参数或者仅有的一个参数时,根据变量的数量尽可能多的返回值,当变量数目大余于返回值数目时补
nil
,当变量数小于返回值数目时从左到右依此返回,舍弃右边多余的返回值。 - 其他情况下,函数只返回第一个返回值。
总之,函数做作为最后一个参数时尽可能多返回返回值,其他情况只返回第一个返回值。
上述规则同样适用于以下情况: - 作为表达式赋值给其他变量
- 作为其他函数的参数
- 函数调用在表构造初始化时
- ruturn 参数
函数调用用在表构造初始化时需要特别说明一下:注意:如果1
2
3
4
5
6
7
8function fun_0() end
function fun_1() return "a" end
function fun_2() return "b", "c" end
a = {fun_0()} -- a = {}
b = {fun_1()} -- b = {"a"}
c = {fun_2()} -- c = {"b", "c"}
d = {fun_0(), fun_1(), fun_2(), "d"} -- d = {nil, "a", "b", "d"}return
语句中将返回值用括号包裹起来也会导致返回一个值:1
2function fun_3() return fun_2() end -- "b", "c"
function fun_4() return (fun_2()) end -- "b"
函数的定义
lua中函数是一等变量,可以通过关键字function
来进行定义。
1 | -- 和php等语言一样使用function关键字声明一个函数 |
参数
2.6 lua break 与 return
和其他语言一样lua
也提供对应的跳出关键字,不过lua
不提供continue
关键字。
break
语句break
用于跳出循环,终止for
、repeat
、while
三种循环的执行,并跳出当前循环体,继续执行当前循环之后的语句,在循环外部不可用。
return
return 只能写在语句块的最后,一旦执行了return 语句,该语句之后的所有语句都不会再执行。若要写在函数中间,则只能写在一个显式的语句块内。
1 | local function add(x, y) |
有时候,为了调试方便,我们可以想在某个函数的中间提前 return
,以进行控制流的短路。此时我们可以将 return
放在一个 do ... end
代码块中:
1 | local function foo() |
特别注意:上述实例中return
如果不放在do ... end
中将会保存,因为return
只能放在函数的最后。
2.5 lua 变量
全局变量
创建一个全局变量
全局变量不需要声明,给一个变量赋值即创建了一个全局变量,访问一个没有初始化的变量(默认是全局变量,即,lua的变量默认是全局变量,特别注意)也不会出错,会返回nil
。
1 | print(var_a) -- nil |
在命令模式中执行如下:
1 | Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio |
删除一个全局变量
删除一个变量很简单,直接将改变量赋值为nil
;如下面的例子:b是全局变量,当赋值为nil
之后,再调用print
就会返回nil
。
1 | Lua 5.3.4 Copyright (C) 1994-2017 Lua.org, PUC-Rio |
总之,可以这么理解:当一个变量被赋值为nil
,这个变量就变得像从来没出现过一样,换句话说,只有当一个变量的值不是nil
这个变量才是存在的。
2.4 lua 控制结构
流程控制语句对于程序设计来说特别重要,它可以用于设定程序的逻辑结构。一般需要与条件判断语句结合使用。Lua 语言提供的控制结构有 if,while,repeat,for,并提供 break 关
键字来满足更丰富的需求。
if/else
if-else 是我们熟知的一种控制结构。Lua 跟其他语言一样,提供了 if-else 的控制结构。语法上更接近shell的语言,逻辑结构上和其他语言没有较大的区别,直接上实例,一看便知。
单分支if-end
1 | x = 10 |
双分支if-else-end
1 | x = 10 |
多分支if-elseif-else-end
1 | score = 90 |
**特别注意|**与 C 语言的不同之处是 else 与 if 是连在一起的,若将 else 与 if 写成 “else if” 则相当于在else 里嵌套另一个 if 语句,如下代码: