Openresty Redis正确使用连接池(set_keepalive)

本文探讨了在OpenResty项目中遇到的高并发下Redis连接爆炸问题,提出了两种解决方案。首先尝试在init阶段创建连接并在access阶段复用,但这种方法在Redis故障时会导致功能不可用,且TCP连接可能中断。然后转向使用连接池(set_keepalive)进行管理,初期出现错误,原因是误将set_keepalive用在已关闭的连接上。最终修正了使用方法,确保在每次操作后正确设置连接池,成功解决了并发连接问题。在实际测试中,连接数与并发量相关,达到了预期效果。

最近在做一个openresty项目,每次访问需要通过openresty读取redis,判断是否可以允许访问。

问题:

如果每次访问就要与redis建立连接,当并发量大时,连接数会直接爆炸。影响性能和系统。

方案一:

在init_by_lua中先创建redis连接,在access_by_lua中直接使用:

init.lua:

local redis = require "redis"
client = redis.connect('127.0.0.1', 6379)

filter.lua(演示:简单的黑名单功能,如果request的remoteip在redis中存在key,就返回403):

client = red;

function redisHasKey(client,keyname)
	if(client:get(keyname))
	then
		return true;
	else
		return false;
	end
end

local keyname = ngx.var.remote_addr;
if(pcall(redisHasKey,client,keyname))then
	if(redisHasKey(client,keyname))
	then
		ngx.exit(403);
	else
		return
	end
else
	return
end

在nginx启动时,就创建了连接,并只使用这个连接。就不会出现连接数过多问题。

方案一问题:

1、在使用中redis故障会导致功能不可用,即使重启redis还需要重启nginx才能重新获取连接。

2、linux tcp连接不能保证长时间不中断。中断后不能修复。

3、单连接在高并发多请求情况下,会阻塞请求,多个请求会等待连接空闲后再进行操作,甚至直接报错。

方案二:

连接池,针对连接数管理

openresty官方本身有连接池管理(set_keepalve)

语法:syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size)

我就来试着用下,这次只使用filter.lua:

local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000)
red:set_keepalive(1000, 20)
red:connect("127.0.0.1", 6379)
client = red;

function redisHasKey(client,keyname)
	if(client:get(keyname))
	then
		return true;
	else
		return false;
	end
end

local keyname = ngx.var.remote_addr;
if(pcall(redisHasKey,client,keyname))then
	if(redisHasKey(client,keyname))
	then
		ngx.exit(403);
	else
		return
	end
else
	return
end

问题:

使用之后发现更大的问题,连接数量没有被连接池控制,同时出现大量报错:

2021/05/19 13:57:57 [error] 2734932#0: *2019 attempt to send data on a closed socket: u:00000000401F79D0, c:0000000000000000, ft:0 eof:0, client: 127.0.0.1, server: , request: "GET / HTTP/1.0", host: "127.0.0.1:8082"
2021/05/19 13:57:57 [error] 2734932#0: *2019 attempt to send data on a closed socket: u:00000000401F79D0, c:0000000000000000, ft:0 eof:0, client: 127.0.0.1, server: , request: "GET / HTTP/1.0", host: "127.0.0.1:8082"
2021/05/19 13:57:57 [alert] 2734932#0: *2019 socket() failed (24: Too many open files), client: 127.0.0.1, server: , request: "GET / HTTP/1.0", host: "127.0.0.1:8082"
2021/05/19 13:57:57 [error] 2734932#0: *2019 attempt to send data on a closed socket: u:00000000401F6290, c:0000000000000000, ft:0 eof:0, client: 127.0.0.1, server: , request: "GET / HTTP/1.0", host: "127.0.0.1:8082"

连接在被使用的时候已经中断了。

解决:

看下官方对于set_keepalive的用法说明

set_keepalive

syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size)

Puts the current Redis connection immediately into the ngx_lua cosocket connection pool.

You can specify the max idle timeout (in ms) when the connection is in the pool and the maximal size of the pool every nginx worker process.

In case of success, returns 1. In case of errors, returns nil with a string describing the error.

Only call this method in the place you would have called the close method instead. Calling this method will immediately turn the current redis object into the closed state. Any subsequent operations other than connect() on the current object will return the closed error.

简单来说,set_keepalive使用时是用来替代close的,如果使用了set_keepalive之后再对连接进行任何除了connect的操作都会报错。

看来看官方文档很重要啊!

改进下:

local redis = require "resty.redis"
local red = redis:new()
--red:set_timeouts(1000, 1000, 1000)
--red:set_keepalive(1000, 20)
red:connect("127.0.0.1", 6379)
client = red;

function redisHasKey(client,keyname)
	if(client:get(keyname))
	then
		return true;
	else
		return false;
	end
end

local keyname = ngx.var.remote_addr;
if(pcall(redisHasKey,client,keyname))then
	if(redisHasKey(client,keyname))
	then
		red:set_keepalive(1000, 200)
		ngx.exit(403);
	else
		red:set_keepalive(1000, 200)
		return
	end
else
	red:set_keepalive(1000, 200)
	return
end

虽然用起来还挺恶心,但确实解决了我们的问题。

在使用ab测试时连接数虽然不会被严格控制在set_keepalive的pool_size数值内,但能够与测试的并发量相关。

### OpenResty 配置 Redis 连接密码示例 在 OpenResty 中,通过 `lua-resty-redis` 模块可以实现与 Redis 的连接。如果 Redis 服务器设置了密码保护,则需要在连接后调用 `auth` 方法进行身份验证[^2]。以下是完整的配置示例: ```lua local redis = require "resty.redis" -- 创建 Redis 实例 local red = redis:new() -- 设置超时时间(单位:毫秒) red:set_timeouts(1000, 1000, 1000) -- 连接到 Redis 服务器 local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) return end -- 如果 Redis 有密码保护,则需要进行身份验证 local password = "your_redis_password" -- 替换为实际的 Redis 密码 local auth_ok, auth_err = red:auth(password) if not auth_ok then ngx.say("failed to authenticate: ", auth_err) red:close() return end -- 执行 Redis 命令(例如设置一个键值对) local res, cmd_err = red:set("mykey", "myvalue") if not res then ngx.say("failed to set mykey: ", cmd_err) red:close() return end -- 将连接返回到连接池 local pool_ok, pool_err = red:set_keepalive(10000, 100) -- 设置连接池的最大空闲时间和连接数 if not pool_ok then ngx.say("failed to set keepalive: ", pool_err) return end ngx.say("success") ``` 上述代码展示了如何在 OpenResty 中配置 Redis 的连接密码。具体步骤包括: - 引入 `resty.redis` 模块。 - 创建 Redis 实例并设置超时时间。 - 使用 `connect` 方法连接到 Redis 服务器。 - 调用 `auth` 方法进行身份验证。 - 执行 Redis 命令。 - 使用 `set_keepalive` 方法将连接返回到连接池中。 如果 Redis 未设置密码,则可以省略 `auth` 方法的调用。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值