凡 若

初心 读书 知新 生活

题目

给定一个整数类型的数组 nums,请编写一个能够返回数组“中心索引”的方法。
我们是这样定义数组中心索引的:数组中心索引的左侧所有元素相加的和等于右侧所有元素相加的和。
如果数组不存在中心索引,那么我们应该返回 -1。如果数组有多个中心索引,那么我们应该返回最靠近左边的那一个。
难易程度:easy

阅读全文 »

背景

部分 worker 进程 CPU 使用率达到100%整个 worker 不可用。

原因

诱因

上游长连接服务上线,由于服务发现服务生效时间长,导致 Nginx 连接上游失败,在释放连接时 Nginx worker 进程可能在持有读锁时出现问题,从而导致读写锁的状态和实际情况不一致,最终读锁永远没法释放,而写锁会死锁。

主因

通过火焰图分析,发现整个进程的耗时都在 ngx_rwlock_wlock 这个函数上。

通 GDB 查看该进程的堆栈信息,执行下面的命令(121606 是对应 CPU 打满进程的进程号 ),查看进程调用栈。

1
2
3
4
5
6
# 先执行下面命令进入 GDB
/opt/compiler/gcc-4.8.2/bin/gdb -nx /proc/121606/exe 121606


# 执行 bt
> bt

上述命令执行的结果如下图,可以看出 worker 进程在释放连接时出现问题,最终死锁在 ngx_rwlock_wlock 函数上。

阅读全文 »

背景

由于业务需求我们需要根据client_ip对部分接口做一个简单的 ab test,将部分流量导新的服务上。基于之前做 WAF 的经验,制定了大体方案:根据 client_ip 做 hash, 根据 hash 值来确定是请求新后端还是原来的后端服务。
在说明详细方案前,先说明一下现状。当前的配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location ~ ^/api/rest/v\d+/Login {
rewrite ^(.*)$ /loginServer$1 break;

proxy_http_version 1.1;
proxy_next_upstream error timeout invalid_header;
proxy_redirect off;

proxy_set_header Host $http_host;
proxy_set_header Clientip $http_clientip;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";

proxy_pass http://test.fanruo.net;
}

其中,/loginServer是当前服务路径前缀,/loginServerNew是新服务路径前缀。所以,具体需求可以描述为,10% 的用户使用/loginServerNew服务,90% 的用户使用/loginServer服务。

具体方案

  1. 根据字符串 client_ip 转成长整数 ip_num;
  2. 对 ip_num 按 100 取模,取最后两位 hash;
  3. 当 hash < 10 时走/loginServerNew服务, 设置 tag="New", 否则 tag=""
  4. 改造rewrite指令为 rewrite ^(.*)$ /loginServer$tag$1 break;

对应配置为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
location ~ ^/api/rest/v\d+/Login {
set $tag "";
access_by_lua_file conf/lua/abtest.lua;
rewrite ^(.*)$ /loginServer$tag$1 break;

proxy_http_version 1.1;
proxy_next_upstream error timeout invalid_header;
proxy_redirect off;

proxy_set_header Host $http_host;
proxy_set_header Clientip $http_clientip;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";

proxy_pass http://test.fanruo.net;
}

对应 lua 脚本(conf/lua/btest.lua):

1
2
3
4
5
6
7
8
9
local client_ip = ngx.var.http_clientip or ngx.var.remote_addr
local o1,o2,o3,o4 = client_ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" )
local ip_num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4
local hash = ip_num % 100

ngx.var.tag = ""
if hash > 10 then
ngx.var.tag = "New"
end
阅读全文 »

背景

随着业务发展会出现较大的 post body 数据,按照nginx + lua 开发中过程中 post body 过大返回 4xx提到的方式修改后,大部分情况下 post body 正常接收并处理落日志。但会偶现空日志的情况。

问题分析

经过多轮本地和沙盒压测,复现了问题。由于在出现空日志情况是 error 日志并没留下相关信息,随后做了如下处理:

  1. 把 error 日志级别调到 debug,当问题复现时,error.log 中会有客户端过早断开连接类似的日子打出。
  2. 在 access 日志中添加 request_time, status,等信息,发现出现空日志时,status=408,request_time 都比较长。

因此,可以明确出现该问题是客户端链接超时造成的。

解决方案

为解决该问题,做如下优化:

调整超时时间,和 buffer

1
2
3
client_body_timeout 10s;
client_header_timeout 10s;
client_body_in_single_buffer on; #这个 directive 让 Nginx 将所有的 request body 存储在一个缓冲当中,它的默认值是 off。启用它可以优化读取 $request_body 变量时的 I/O 性能
阅读全文 »

背景

基于 OpenResty 提供 post 接口,调用方调用该接口 post 数据,该接口接收 post 过来的数据,复用 Nginx access 日志落盘。

问题

当用户的 body 体过大时,ngx.req.get_body_data() 读请求体,会出现读取不到直接返回 nil 的情况。

问题原因

究其原因,主要是 Nginx 诞生之初主要是为了解决负载均衡情况,而这种情况,是不需要读取 body 就可以决定负载策略的,所以这个点对于 API Server 和 Web Application 开发的同学有点怪。

解决办法

  1. 如果你只是某个接口需要读取 body(并非全局行为),那么这时候也可以显示调用 ngx.req.read_body() 接口
  2. 如果想全局生效的话需要使用命令lua_need_request_body on;

当选择上述其中一种,甚至两种方案都使用了,依旧还解决不了问题,这时候,需要坚持 body 体是不是太大了。这是需要设置如下两个命令:

1
2
client_body_buffer_size 256k; #默认8k|16k
client_max_body_size 256k; #默认1m
阅读全文 »

通过前两章的学习大体掌握了lua的基本语法,这部分语法和其他语言大体类似,可以说这是一门语言语法的最最基础的部分了。在接下来的部分,将学习一下带有lua特性的语法知识。

返回值

通常一个函数会返回一个返回值,比如C/C++等,也有一些语言会返回多个返回值,比如python。lua也支持多返回值。以标准库中的string.find()函数为例,该函数会返回匹配字符串在搜索字符串中的起始位置索引,如果没匹配成功则返回nil

1
2
3
#!/usr/bin/bua
s, e = string.find("hello world", "he")
print(s, e)

对于返回值,lua有一个特性——尽可能的调整返回值的数量以适应调用环境,具体表现为一下规则:

  1. 当作为调用表达式最后一个参数或者仅有的一个参数时,根据变量的数量尽可能多的返回值,当变量数目大余于返回值数目时补nil,当变量数小于返回值数目时从左到右依此返回,舍弃右边多余的返回值。
  2. 其他情况下,函数只返回第一个返回值。
    总之,函数做作为最后一个参数时尽可能多返回返回值,其他情况只返回第一个返回值。
    上述规则同样适用于以下情况:
  3. 作为表达式赋值给其他变量
  4. 作为其他函数的参数
  5. 函数调用在表构造初始化时
  6. ruturn 参数
    函数调用用在表构造初始化时需要特别说明一下:
    1
    2
    3
    4
    5
    6
    7
    8
    function 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
    2
    function fun_3() return fun_2() end  -- "b", "c"
    function fun_4() return (fun_2()) end -- "b"

函数的定义

lua中函数是一等变量,可以通过关键字function来进行定义。

1
2
3
4
5
6
7
8
9
-- 和php等语言一样使用function关键字声明一个函数
local function func_a()
...
end

-- 在lua中函数也是一等变量,可以通过如下方式来声明一个函数
local func_b = function()
...
end

参数

阅读全文 »

和其他语言一样lua也提供对应的跳出关键字,不过lua不提供continue关键字。

break

语句break用于跳出循环,终止forrepeatwhile 三种循环的执行,并跳出当前循环体,继续执行当前循环之后的语句,在循环外部不可用。

return

return 只能写在语句块的最后,一旦执行了return 语句,该语句之后的所有语句都不会再执行。若要写在函数中间,则只能写在一个显式的语句块内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local function add(x, y)
return x + y
--print("add: I will return the result " .. (x + y))
--因为前面有个return,若不注释该语句,则会报错
end
local function is_positive(x)
if x > 0 then
return x .. " is positive"
else
return x .. " is non-positive"
end
--由于return只出现在前面显式的语句块,所以此语句不注释也不会报错
--,但是不会被执行,此处不会产生输出
print("function end!")
end

sum = add(10, 20)
print("The sum is " .. sum) -->output:The sum is 30
answer = is_positive(-10)
print(answer) -->output:-10 is non-positive

有时候,为了调试方便,我们可以想在某个函数的中间提前 return ,以进行控制流的短路。此时我们可以将 return 放在一个 do ... end 代码块中:

1
2
3
4
5
local function foo()
print("before")
do return end
print("after") -- 这一行语句永远不会执行到
end

特别注意:上述实例中return如果不放在do ... end中将会保存,因为return只能放在函数的最后。

阅读全文 »

全局变量

创建一个全局变量

全局变量不需要声明,给一个变量赋值即创建了一个全局变量,访问一个没有初始化的变量(默认是全局变量,即,lua的变量默认是全局变量,特别注意)也不会出错,会返回nil

1
2
3
print(var_a) -- nil
var_a = 10
print(var_a) -- 10

在命令模式中执行如下:

1
2
3
4
Lua 5.3.4  Copyright (C) 1994-2017 Lua.org, PUC-Rio
> print(var_a)
nil
>

删除一个全局变量

删除一个变量很简单,直接将改变量赋值为nil;如下面的例子:b是全局变量,当赋值为nil之后,再调用print就会返回nil

1
2
3
4
5
Lua 5.3.4  Copyright (C) 1994-2017 Lua.org, PUC-Rio
> b = 1; print("b=" .. b); b = nil; print(b);
b=1
nil
>

总之,可以这么理解:当一个变量被赋值为nil,这个变量就变得像从来没出现过一样,换句话说,只有当一个变量的值不是nil这个变量才是存在的

阅读全文 »

流程控制语句对于程序设计来说特别重要,它可以用于设定程序的逻辑结构。一般需要与条件判断语句结合使用。Lua 语言提供的控制结构有 if,while,repeat,for,并提供 break 关
键字来满足更丰富的需求。

if/else

if-else 是我们熟知的一种控制结构。Lua 跟其他语言一样,提供了 if-else 的控制结构。语法上更接近shell的语言,逻辑结构上和其他语言没有较大的区别,直接上实例,一看便知。

单分支if-end

1
2
3
4
5
x = 10
if x > 0 then
print("x is a positive number")
end
--运行输出:x is a positive number

双分支if-else-end

1
2
3
4
5
6
7
x = 10
if x > 0 then
print("x is a positive number")
else
print("x is a non-positive number")
end
--运行输出:x is a positive number

多分支if-elseif-else-end

1
2
3
4
5
6
7
8
9
10
score = 90
if score == 100 then
print("Very good!Your score is 100")
elseif score >= 60 then
print("Congratulations, you have passed it,your score greater or equal to 60")
--此处可以添加多个elseif
else
print("Sorry, you do not pass the exam! ")
end
--运行输出:Congratulations, you have passed it,your score greater or equal to 60

**特别注意|**与 C 语言的不同之处是 else 与 if 是连在一起的,若将 else 与 if 写成 “else if” 则相当于在else 里嵌套另一个 if 语句,如下代码:

阅读全文 »
0%