Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于并发时连接数问题 #17

Closed
lawrence-peng opened this issue Aug 22, 2018 · 22 comments
Closed

关于并发时连接数问题 #17

lawrence-peng opened this issue Aug 22, 2018 · 22 comments

Comments

@lawrence-peng
Copy link

我这边测试程序用了 100 个并发,1秒发起一次,redis 连接池配置为 500 。不到一会连接池满,就会报 CSRedis.ConnectionPool.GetConnection 连接池获取超时(10秒)
使用的方式是用 RedisHelper.

@2881099
Copy link
Owner

2881099 commented Aug 22, 2018

每秒并发数100,链接池500
如果一秒内完成不了操作,确实会超时
redis服务时本地还是远程,本地不太会这样

@lawrence-peng
Copy link
Author

是本地的。其实50个并发也很快就满了。我的 redis 操作 主要是用的是 pub/sub .存储的数据是 base64 字符串,大小大概 5、6 K 左右。50并发不大,有什么解决方案吗

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

原来如此啊,Subscribe 不能回收链接的,多少都会满,尽可能重用 Subscribe
如:
Subscribe(
("chan1", msg => Console.WriteLine(msg.Body)),
("chan2", msg => Console.WriteLine(msg.Body))
)

如果是一次性订阅,RedisHelper.Subscribe 暂时还不支持

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

Task.Run(() => {
	try {
		subscr.Client.PSubscribe(chans);
	} catch (Exception ex) {
		var bgcolor = Console.BackgroundColor;
		Console.BackgroundColor = ConsoleColor.Yellow;
		Console.WriteLine($"模糊订阅出错(channel:{string.Join(",", chans)}{ex.Message},5秒后重连。。。");
		Console.BackgroundColor = bgcolor;
		Thread.CurrentThread.Join(1000 * 5);
		PSubscribe(channelPatterns, pmessage);
	}
});

RedisHelper.Subscribe 与 RedisHelper.PSubscribe 都是一根筋的设计,错误或断线重新订阅,不适合做一次性订阅,除非用完销毁这个redisClient实例,因为它已经有了对应的订阅事件,属性。。。这不符合连接池管理原则

redis-server本来是没有这种问题的

仍然推荐尽可能重用 Subscribe,不然只能自行现实了,参考文档:https://github.com/ctstone/csredis/blob/master/README.md

@lawrence-peng
Copy link
Author

我的场景不是一次性订阅的。程序有一个单例的类,在程序启动的时候就执行一次订阅(RedisHelper.Subcribe)。 每次用户提交请求时就 Push。下面是订阅的部分代码:
image

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

public partial class BaseController : Controller {
	public override void OnActionExecuting(ActionExecutingContext context) {
		RedisHelper.Publish("test", DateTime.Now.ToString("g"));
	}
}

//在 Startup.cs

RedisHelper.Subscribe(("test", msg => {
		Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(msg));
	}
));

image

运行结果,手工访问每个 BaseController 接口都有收到订阅消息

image

redisHelper 连接池数据,正常

正在下载 apache ab,等会发压力测试结果

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

image

Server Software: Kestrel
Server Hostname: localhost
Server Port: 5000

Document Path: /Tag/
Document Length: 4061 bytes

Concurrency Level: 50
Time taken for tests: 3.516 seconds
Complete requests: 5000
Failed requests: 0
Total transferred: 22575000 bytes
HTML transferred: 20305000 bytes
Requests per second: 1422.24 [#/sec] (mean)
Time per request: 35.156 [ms] (mean)
Time per request: 0.703 [ms] (mean, across all concurrent requests)
Transfer rate: 6270.93 [Kbytes/sec] received

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

CSRedis.ConnectionPool.GetConnection 连接池获取超时(10秒)的可能原因:

1、多次订阅,订阅长期占用连接,不会归还连接池,如上图 freeConnections: 49,allConnections: 50
2、借走连接池 redis 操作较耗时,未能及时完成(默认完成会自动归还)

连接池空的期间,若有借的需求,则 await 10秒,等待归还则马上激活(不用干等10秒),若10秒都无资源归还则抛出上述异常。

@lawrence-peng
Copy link
Author

我的程序也是这样的实现的,有差别的地方是,我存到 redis 的内容是 png 图片的 base64 字符 ,字符的 size 大概 6K 左右。我想应该是操作比较耗时导致。可以试着把你的测试例子 Push 一个 base64 图片字符串看看

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

image

正常的,复制这段代码,可以查看连接池状态

[HttpGet(@"connection/redis")]
public object GetRedisConnectionPool() {
	var ret = new Hashtable();
	foreach(var pool in RedisHelper.ClusterNodes) {
		List<Hashtable> list = new List<Hashtable>();
		foreach (var conn in pool.Value.AllConnections) {
			list.Add(new Hashtable() {
				{ "最后活动", conn.LastActive },
				{ "获取次数", conn.UseSum }
			});
		}
		ret.Add(pool.Key, new {
			FreeConnections = pool.Value.FreeConnections.Count,
			AllConnections = pool.Value.AllConnections.Count,
			GetConnectionQueue = pool.Value.GetConnectionQueue.Count,
			GetConnectionAsyncQueue = pool.Value.GetConnectionAsyncQueue.Count,
			List = list
		});
	}
	return ret;
}

@lawrence-peng
Copy link
Author

我在 redis-cli 用命令 info 查看过,确实是满的。 我想跟我的代码有关系,在 push 函数,我有用 async task 而 redis push 用的 RedisHelper.Push

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

push ? 还是 publish?我上面一直讲的是 Publish + Subscribe

push 是操作列表的

都是一个道理,使用方从连接池借走,操作耗时会归还不及时,导致阻塞后面的使用者,这个取决于连接池大小。

注: redis-server 是单线程的,多个连接不会提高效率,连接池的设计是借用其他库的思想,避免重复 open close 连接的开销。

@lawrence-peng
Copy link
Author

lawrence-peng commented Aug 23, 2018

Publish ,表达有误。这个是我在 Startup.cs 的代码
image

image

image

我的代码跟你的测试用例基本一致了。连接数还是会一直飙上去。

netcore 单例注入 Processor,在 startup.cs 的 processor.run() 就是 执行 Subscribe,所以在controller 里,我拿到实例应该是同一个,用这个实例 processor.push

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

_processor.Push 代码发出来看看

[HttpGet(@"connection/redis")]
public object GetRedisConnectionPool() {
	var ret = new Hashtable();
	foreach(var pool in RedisHelper.ClusterNodes) {
		List<Hashtable> list = new List<Hashtable>();
		foreach (var conn in pool.Value.AllConnections) {
			list.Add(new Hashtable() {
				{ "最后活动", conn.LastActive },
				{ "获取次数", conn.UseSum }
			});
		}
		ret.Add(pool.Key, new {
			FreeConnections = pool.Value.FreeConnections.Count,
			AllConnections = pool.Value.AllConnections.Count,
			GetConnectionQueue = pool.Value.GetConnectionQueue.Count,
			GetConnectionAsyncQueue = pool.Value.GetConnectionAsyncQueue.Count,
			List = list
		});
	}
	return ret;
}

把这段代码放进你的项目,请求截图发出来

@lawrence-peng
Copy link
Author

_processor.Push 的代码在上面回复中已贴了。下面是 Run 的代码
image

这是服务器的服务运行后 redis info 出来的信息。

qq 20180823154525

这是服务器的服务停止后 redis info 出来的信息

image

代码统计的图

image

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

_processor.Push 的代码在上面回复中已贴了


Run 的发了, _processor.Push 的没有发

@lawrence-peng
Copy link
Author

image

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

压力测试报错的时候,看 [HttpGet(@"connection/redis")] 接口的返回结果

@lawrence-peng
Copy link
Author

lawrence-peng commented Aug 23, 2018

我测试时,都把 async Task 去掉后没发现 10秒超时的报错了,但在redis-cli 看连接数还是满的。

@2881099
Copy link
Owner

2881099 commented Aug 23, 2018

异步可以增加吞吐量,async 可以,因为.net线程池大小没超过500,所以连接池500个用不完

@zsy619
Copy link

zsy619 commented Sep 23, 2018

如何在高并发的情况下,防止:
CSRedis.ConnectionPool.GetConnection 连接池获取超时(10秒)
设置poolSize大小多少为合适?

@2881099
Copy link
Owner

2881099 commented Sep 23, 2018

如何在高并发的情况下,防止:
CSRedis.ConnectionPool.GetConnection 连接池获取超时(10秒)
设置poolSize大小多少为合适?

异步方法,没有这个限制。

真实场景极少会这么大访问量,poolsize可以设置成同时刻访问次数/10,比如同时同时同时1000个操作,poolsize=100,具体还要看网络传输速度,如果redis-server是远程的,处理会稍慢。

poolsize好比道路的宽度,设大了浪费,设小了又堵车。所以先确定有多少台车,车速多少来决定。

@2881099 2881099 closed this as completed Sep 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants