第一节 NCLocator了解吗,客户端与服务端如何进行通讯
在平时的开发中,如果我们想要在客户端调用服务端部署好的服务,一般我们是这样在客户端通过以下方式:
1 | IxxxService service = NCLocator.getInstance().lookup(IxxxService.class); |
获取到service这样一个接口对象,然后通过这个service调用对应的method就能实现与服务端的通讯,但是诸位有没有想过为什么这样就可以进行client到server的网络IO通讯,其实这里就是通过了jdk的动态代理来实现的,jdk动态代理是javase高级知识点,没接触过的可以去看看相关知识点,在java高级知识介绍中我会用一篇日记来仔细讲解,这里就不做详细介绍。继续讲我们在客户端获取到的service对象就是一个IxxxService接口的动态代理对象,这里看一下动态代理是如何获取的,如下代码:
1 | Proxy.newProxyInstance(classloader, interfaces, InvocationHanddler); |
有没有发现上图中h=RemoteInvocationHandler很眼熟,这就是一个InvocationHanddler的实现方式
这里我通过下面一个通用接口来进行详细说明:
1 | IBillcodeManage lookup = NCLocator.getInstance().lookup(IBillcodeManage.class); |
从上图我们看到在客户端拿到的lookup对象确实就是一个动态代理类,并非对象本身那么简单,换句话说我们可以理解,
NCLocator.getInstance()其实就是获取到客户端jvm中的一个hashmap容器,
key是IxxxService.class,value就是这个接口类的一个动态代理实例,
这样说或许会比较好容易理解,但事实上也确实如此,只是具体实现上面会复杂很多,好了
接下来就去看看这个客户端获取到的动态代理是如何往服务端发送请求的
看到这里是不是有种拨开云雾的赶脚,没错,你没有眼花,我们的NC客户端采用的就是最原始最古老的BIO通讯模型
,不是说这个BIO模型不好,而是因为他存在有很大的局限性,比如同步、阻塞、无法处理高并发访问等,但是用在
erp系统中我觉得是够用的,这里我在整理总结一下当你使用客户端接口的动态代理调用方法时具体发生了什么:
- 1、进入RemoteInvocationHandler的invoke方法
- 2、获取调用的方法名称和参数
- 3、Tcp三次握手与服务端建立连接
- 4、网络io发送调用方法的相关信息和参数信息到服务端
- 5、同步等待服务端返回结果(这里就是一个所谓同步调用的概念)
这里再多解释一句,NC服务端启动的时候会有一个ServerSocket.accept()的方法专门用来处理客户端
发送来的连接请求,这也是BIO模型的精髓所在<同步与阻塞>。
这里还有非常一个值得一提的地方见下图,客户端并不是无限制的创建Socket,而是设计了一个Socket连接池来进行Socket复用,原理类似数据库连接池;
Socket s = (Socket)sq.poll();
这就是通过有界阻塞队列来实现Socket容器。
第二节 事务与日志(NC系统的事务型组件)
上一节我们讲到了客户端接口类的动态代理发送请求到服务端,那么我们NC的服务端又是如何处理请求,
又是如何将请求发送回客户端的了?我们慢慢来讲,跟第一章一样,我们先通过:
1 |
|
在服务端获取到lookup的动态代理对象,注意了这里我们是在服务端(private)获取了,不是在客户端(client)了哈,重要的事情说三遍:
- 不是在客户端(client)
- 不是在客户端(client)
- 不是在客户端(client)
见下图:
是不是发现了异样,同样的方法在客户端和服务端拿到的lookup对象是完全不一样的!因为服务端获取到的是具体实现类的动态代理,是要去实实在在做事情的了,这也就解释了NC是分服务端容器和客户端两大容器的!见下图:
有没有,看了图p2是不是就感觉发现了新大陆,这就是NC所谓的事务型组件的设计原理:根据接口设计中的方法名后缀来进行判断决定采用何种事务的传播属性,另外强调一下
NC的事务传播属性并没有很多种,从源码层面来看NC只支持最基本的Requires和equiresNew,并非像UAP红皮书中说的还支持mandatory、support以及never,至少我在源码层面是没有发现支持
与spring的7大事务传播属性相差甚远,至于这两种传播属性具体有什么区别,我会在我的第二篇UAP文章中进行剖析,好了这里我再总结一下服务端接收到客户端请求后都做了一些什么操作,见图p11、p12、p13
- 1、服务端bioserver开启SocketServer等待客户端请求到来
- 2、服务端接受到请求后解析请求参数,通过servicename去服务端的服务实现类动态代理容器中寻找对应的服务类动态代理
- 3、最后通过反射机制,mothod.invoke(obj)来实现事务控制以及日志记录以及实际的业务处理
- 4、返回结果到客户端,至此一次bio完整的请求通讯结束
第三节 系统是如何保证一次请求中的多次数据库交互拿到的是同一个Connection
其实这是一个扩展思考的问题,当时也是因为这个事务问题笨人就多思考了一点,就是说你如果要保证事务有效,
前提条件你必须让你的一次请求过程中拿到的conn是同一个对象对吧,
这个问题我并不想描述的太多,因为这是一个很基础的并发编程问题–>如何让一个变量(conn)成为一个线程的独享变量,
从而达到解决线程安全的问题,这里的解决方案就是ThreadLocal;
我提供几个关键截图,朋友们可以自行去了解一下ThreadLocal的原理再回过头来就看这几行代码,
就能很好的理解其设计原理
上图中的connRef对象就是一个ThreadLocal变量。用于获取当前线程的conn,但是conn对象并非是存在这个变量中的,而是存储在每一个Thread的一个ThreadLocalMap的变量中的,这个ThreadLocalMap中key就是connRef的softreference形式,value就是conn对象,这里有点绕,所以我的建议是先了解原理再看代码;
前前后后写了三四个小时,终于写完了本人的第一篇博文,后续还会更新更多…
Slogan:技术界一枚不起眼的迷途小书童