前文
选题一:Session 管理策略及其优化
数据库在处理大量的并发查询时,会创建和管理众多的 Session,这是维持数据库操作顺利进行的关键环节。会话管理不仅涉及到用户连接的创建、维持和断开,还包括资源分配、权限验证和状态监控等多个方面。在高并发的环境下,有效的会话管理策略是保证数据库性能和稳定性的核心。本选题旨在探讨基于 NebulaGraph 当前的 Session 管理功能,分析其在实际应用中的表现,并提出可行的优化的使用方案
作为一个服务端基础工具,数据库Session 管理方式非常重要。当客户端与服务端建立连接,我们想像公民出国签证与外国建立联系,国家【服务端】是如何对这些外国人【客户端】进行管理的?
session的技术生命周期
创建使用:当客户首次访问服务端时,服务端会为其创建一个唯一的Session对象,并分配一个Session ID。
Session ID传递:客户端通过密码认证与服务端确认,服务端将Session ID发送给客户端,客户端端会自动将此Session ID与服务端建立会话。
授权访问:服务端接收到带有Session ID的请求后,会使用这个ID查找对应的Session对象,为客户端提供个性化服务。
session存储:Session数据可以存储在服务端的内存中,也可以通过分布式缓存、数据库等方式持久化存储,以便于集群环境中各个服务器节点能够共享Session数据。
销毁删除: 服务端销毁唯一的Session对象,客户端如果想使用,必须要重新创建
资源调试上,NebulaGraph与MySQL一样,采用的是Thread-Pool的方式, 线程处理的最小单位是statement(语句),一个线程可以处理多个连接的请求。在保证充分利用硬件资源情况下(合理设置线程池大小),可避免瞬间连接数暴增导致的服务器抖动
与MySQL一样,NebulaGraph也是一个单进程架构的数据库,主要靠线程进行资源调度和网络通讯。 不过NebulaGraph还多了一个分布式标签,它下面的meta模块服务、graphd模块服务、storag模块服务都占用系统一个进程,然后进程通过线程跨网络访问,
客户端-服务端会话过程
NebulaGraph的Session 生命过程图如下
- NebulaGraph客户端 向NebulaGraph服务端发起请求【connection-pool.init】
- NebulaGraph服务端同意,并建立两端会话,把NebulaGraph客户端登记注册进入会话里面。
- NebulaGraph客户端 执行任务后,向NebulaGraph服务端销毁会话【connection-pool.release】
测试方法
测试环境
单机 | CPU | 内存 | 版本 |
---|---|---|---|
是 | i7 | 8G | nebula3.4.0 |
硬件
- CPU核数设置为8核, 观测nebula-graphd的 graph-netio线程个数。
- CPU核数设置为12核,观测nebula-graphd的 graph-netio线程个数。
- CPU核数设置为16核,观测nebula-graphd的 graph-netio线程个数。
软件
- 客户端模拟多个用户, 每个用户对应一个session,并执行大任务【插入1亿条数据到nebugraph】,最后释放session
- 客户端模拟多个用户, 每个用户对应一个session,并执行大任务【1亿条数据有错,不能写入到nebugraph,写入到日志里面】,最后释放session
- 客户端模拟多个用户, 每个用户对应一个session,并执行大任务【插入1亿条数据到nebugraph】,没有释放session
- 客户端模拟多个用户, 每个用户对应一个session,并执行大任务【1亿条数据有错,不能写入到nebugraph,写入到日志里面】,没有释放session
- 客户端模拟多个用户, 每个用户对应一个session,并执行小任务【小任务是指1条数据】,包括释放和没有释放session的状况,超过服务端预置 的session个数和没有超过的session个数。
伪代码
connection_pool = ConnectionPool()
assert connection_pool.init([('xxxx.128', 9669)], config)
threads = list()
#模拟16个会话
for i in range(0,16):
threads.append(
threading.Thread(target=main_test, name='thread{}'.format(i))
)
for thread in threads:
thread.start()
for thread in threads:
thread.join()
# close connect pool
#是否注释决定会话有没有关
connection_pool.close()
测试过程
Session的极限承值
#测试过程中报错,错因指向无法创建新的Session
nebula3.Exception.AuthFailedException: b"Create Session failed: Too many sessions created from ::ffff:192.168.153.1 by user root. the threshold is 300. You can change it by modifying 'max_sessions_per_ip_per_user' in nebula-graphd.conf"
Closing a connection that is in use
Closing a connection that is in use
Closing a connection that is in use
Process finished with exit code 0
进入nebula的窗品确认下session是不是到达极限,果然超过了
解决的方法是把session kill掉,然后把max_sessions_per_ip_per_user
设置大一点,重启nebula
错误数据写入到日志里面
写入数据到nebula 分为两种情况,一种是数据合规将会写入到storaged里面,一种是数据不合规写入日志里面。两个情况调用的graph-netio线程不同,写入到storaged里面将会依照当前CPU核数 启动相当数量的graph-netio,写入到日志则会启动6到8个graph-netio,但是写入日志的速度会很快。
记录一下客户端发起的会话,写入错误数据到日志,服务端产生的graph-netio线程个数
16个CPU核,客户端发起8个会话,服务端产生 8个graph-netio线程
16个CPU核,客户端发起12个会话,服务端产生8个graph-netio线程
16个CPU核,客户端发起10个会话,服务端产生8个graph-netio线程
12个CPU核,客户端发起8个会话,服务端产生6个graph-netio线程
12个CPU核,客户端发起8个会话,服务端产生6个graph-netio线程
正确数据写入到storaged里面
记录设置不同的CPU核数【16、8,4】,客户端不同的session个数 【32、16、8】,发起1亿条数据写入的大任务
16个CPU核个数,服务端产生 16个graph-netio线程
8个CPU核个数,服务端产生8个graph-netio线程
4个CPU核个数,服务端产生 4个graph-netio线程
客户端发起的连接个数【32、16、8】,将会形成对应【32、16、8】session
测试总结
- 理想正常的处理过程,客户端与服务端建立session,客户执行操作任务,当任务执行结束,session会话释放。
- 大任务中途宕掉导致的异常情况,客户端 处理的是1亿大任务处理过程 ,处理时间实在过长,如果中途把客户端程序中,你会发现服务端的session因此驻留无法释放,最终造成session负荷数量过多。
- 小任务导致无法建立服务端的异常情况, 服务端默认只支持300个会话,如果客户端发起300个会话,服务端是能够处理的,如果客户端发起301个会话,会出现
Closing a connection that is in use
, 服务端只限制有300个位置,超出的位置没有把访问的客户排成队列依照顺序分派,所以必要时要调大服务端的服务个数。 - 代码没有关闭导致的异常情况,如果客户端写的代码太烂 ,在处理任务完后,没有把session销毁,也会发现服务端的session因此驻留无法释放,最终造成session负荷数量过多。
- 服务端在会话管理上,支持kill session id的单个操作方式 ,删除多个会话或全部会话,可以参照这里 终止会话 - NebulaGraph Database 手册
- 针对空连接的session,超时的机制让会话退出,参考
session_idle_timeout_secs
和client_idle_timeout_secs
- 建立会话后,假设任务的工作量很大并且数据将写入storag,服务端将会根据当前所有的CPU核个数对请求进行处理,一般会打满CPU,例如一共16有CPU核,那么将会启动16个
graph-netio
线程处理。如果数据有错将会写到NebulaGraph的错误日志,这时调用的资源不会打满CPU,只有6个线程启动工作。工作结束后,会把graph-netio线程回收,会话可以根据任务的工作调度资源处理。