nebula-java-1.1.0客户端多线程调用 报NPE问题

  • nebula服务器 版本:nebula-1.2.0
  • 部署方式: 分布式
    -nebula-java 版本:1.1.0

你好,请教一个关于nebula-java 的多线程链接问题。
在nebula-java-1.1.0版本中,GraphClientImpl 类中,使用ThreadLocal来存储具体的连接。
private ThreadLocal<GraphService.Client> client。

在阅读了github的问答之后:https://github.com/vesoft-inc/nebula-java/issues/171
我知道使用ThreadLocal的目的,是因为GraphClient支持 switchSpace操作,为了保证每个线程安全。

我现在的情形是这样的:
1.目前能够保证关于图数据的读写,都是在同一个space中进行。
2.自己使用apache的common-pool2 来实现连接池。

问题出在,不同的线程是调用 同一个GraphClient 的对象,去执行 NGQL的时候,因为ThreadLocal的原理,只会有一个线程能够拿到有效的client,并且执行成功。对于其它线程来说,拿到的client,都是null。

想请教的是,要我能够保证在同一个space图数据,
我是不是可以修改 GraphClientImpl类的实现,移除使用ThreadLocal的方式,修改成普通的成员变量的方式,就可以实现同一个GraphClient,在多个线程中访问,可以正常执行呢?
谢谢 以及 期待您的回答。

你好,首先GraphService.Client是不支持多线程访问的,而ThreadLocal的目的就是确保不同线程内不同公用同一个GraphService.Client, 和switchSpace无关的。
不同线程调用的是不同GraphService.client的对象,你在使用的时候应该没在子线程执行connect操作,导致子线程的LocalMap中没有对应的client,所以拿到的是null。

谢谢您的回复,因为不是很明白,所以我先进一步咨询下,谢谢。

首先看下我自己封装的,简陋的连接工厂。对创建GraphClient对象的时候,就进行connect()与switchSpace操作,固定了space:

再来是 NGQL语句的执行:
首先从链接池获取对象之后,执行完成之后,归还。

我自己本地写了一个多线程的样例。

得到报错的代码日志:

具体报错的代码:

从目前调试的结果来看,
一个线程从 连接池中 拿到了GraphClient对象之后,第一次可以成功执行,然后该对象放回到连接池中。
另外一个线程 从连接池中,拿到这个GraphClient对象,在执行execute的时候,在调用 client.get()的时候返回了空指针。

谢谢您耐心地看完上面啰嗦的描述!

我的问题是:
1.这个NPE的问题,是不是因为 ThreadLocal 类型的GraphClient.client 不能在两个线程中 都拿到?
2. 您上面的回复中说,【ThreadLocal的目的就是确保不同线程内不共用一个 GraphClient.client】,这个原因是什么呢?
3. 因为每一次请求都新建GraphClient对象,然后进行connect(),再进行switchSpace() 是非常耗时的。
目前有没有办法(可以是修改源代码)改造成 类似 dbcp 的链接池那样呢?

可以参考 nebula 2.0 java api支持异步吗 - #6 由 xrfinbj

一次多实例几个client 不同的线程取到 client后执行完 放回去

  1. ThreadLocal 你可以将其看做一个map,key是当前线程,value是线程独享的对象或数据。 你在线程1可以执行成功是因为ThreadLocal中存在<线程1,连接对象>这个Entry,线程2 调用client.get()时因为ThreadLocal中不存在<线程2,连接对象>这个Entry,所以会NPE。 你可以断点在134行代码出,看下ThreadLocal内容。
  2. 因为GraphClient是不支持多线程的,所以client1.0会采用ThreadLocal用来变相实现多线程使用,本质是每个线程内都维护了各自的GraphService.Client,即真正的thrift连接还是单独使用,没有在多线程内复用。

好的,非常明白,谢谢您的回复。

【因为GraphClient是不支持多线程】

请问这个类不支持多线程的原因,想请您具体的描述下,我这边学习下。

那每次在使用 client执行的NGQL的时候,都要再手动进行一次connetc() 与 switchspace() 进行链接,从而保证当前线程能够拿到属于当前线程的 GraphClient.client, 对不?

不用每次执行ngql时, 只需要在子线程内做一次connect就好。 因为向ThreadLocal中set一次可以多次get的。

我们Client与服务端的通信使用的fbthrift。thrift client是非线程安全,多线程下使用可能导致server和客户端崩溃。client的每次远程方法调用其实是多次调用socket写操作,非原子操作,多线程交错使用会出现错乱。

@nicole
今天突然想到,因为上文您这边回复【client的每次远程方法调用其实是多次调用socket写操作,非原子操作,多线程交错使用会出现错乱。】,那我是不是只要保证,每一次就只有一个线程使用该链接(通过apache-pool2 连接池对象保证),这样我就可以重写 GraphClientImpl类,将【ThreadLocal<GraphService.Client> client】修改成 【GraphService.Client client】,就可以在多个线程中先后使用该 GraphService.Client 对象,而不需要在线程中每次都进行connect操作。

是的,去掉ThreadLocal可以。