当我们写完代码,发布之后,服务器上到底发生了什么。目前我们的代码都是部署在tomcat上,tomcat运行在虚拟机上,虚拟机运行在物理服务器上。下面是服务运行的整体结构,分析每层都发生了什么,有助于我们了解服务是怎么运行的。
tomcat
tomcat是一个开源的应用服务器,它可以运行Java web应用。先看下tomcat的结构:
- server: 表示这个tomcat 容器
- service: 一个或多个connector和一个engine
- connector: 用户监听端口请求,并将请求发给engine处理,下面是2个典型的connector
- 监听8080端口的connector,用于接收http请求
- 监听8009端口的connector,用户接收其他web server发来的请求
- engine: engine下可配置多个虚拟主机(virtual host),每个虚拟主机有一个域名。有一个默认的host,如果无法处理请求时,则直接将请求交给默认host
- context: 一个context对应一个web application, 一个web application 由一个或多个servlet组成,它在创建的时候会根据web.xml载入servlet类,当获取到请求的时候,将在自己的映射表中寻找servlet类
业务代码
我们的项目可以看成是一个web项目,它部署在tomcat容器中
那么就很困惑,为什么我们不是发http请求给我们的service,而是通过Pigeon发起了远程调用。在讨论调用之前,我们先分析下我们的项目结构。
以xxx-xxx-service为例,一个典型的Spring服务端项目是由一个api层,一个service层,和一个biz层构成的,其中,api层包含了一堆接口,service层包含这些接口的实现类,biz层包含了一些具体的实现等等。不像spring mvc项目,这里没有web项目的相关配置,所以我们项目代码本身是无法提供http服务的。
那么我们的项目是怎么提供服务的呢?
spring项目最离不开的就是bean了,一种比较通用的实现方式是,一个接口类,一个实现类,以及这两个类对应的bean。我们平时所说的调接口,其实就是调这个bean实例,让这个bean实例执行某个方法。
项目启动后会创建一个spring容器,spring容器中包含了很多bean,我们所有的功能都是依附于一个个bean实现的。在一个类中引用了另一个类的bean,然后就可以调用这个bean提供的方法。可是如果我想调用别人的服务呢?这里就用到了远程调用。
远程调用
我们公司的主流RPC框架是pigeon,这里我们不分析pigeon实现的细节,主要看pigeon是啥,怎么实现远程调用的。没有找到Pigeon详细的架构图,下图为一般rpc框架的架构,pigeon与它基本类似。
客户端
下面是pigeon发送的执行流程图,可以把流程简略的分为几步:调用远程服务的一个方法 → 从zookeeper获取远程服务所在的ip等信息 → netty发送请求 → 等待回应
netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP, UDP和文件传输的支持。这里netty做的其实是发tcp请求。
服务端
服务端的功能是接收客户端发来的请求,执行完后将结果再返回给客户端。首先第一个问题,客户端是怎么知道服务端信息的
打开项目中的appcontext-remote.xml,我们会发现一个com.dianping.dpsf.spring.ServiceRegistry的bean配置。这个bean的几个参数包括 services(它是一个map,其中key为服务url表示,value-ref为项目中的bean id), port(端口)。它的init-method 是ServiceRegistry.init()。
逐步看ServiceRegistry.init(),这里将当前机器的信息构建成ProviderConfig,然后启动服务。启动服务包含了一下操作:
- 检查com.dianping.dpsf.spring.ServiceRegistry中设置的services以及每个bean对应的方法,准备好服务
- 将服务使用netty绑定到指定端口,监听tcp请求
- 将当前服务的ip、port、group(当前环境)等信息注册到zookeeper,表明本台机器开始提供 xxx 服务
当netty收到一个tcp请求,做了如下操作:
举例说明
虚拟机
分析完业务代码是怎么运行之后