-
Notifications
You must be signed in to change notification settings - Fork 48
使用手册 中文版
你所负责的产品,业务量和代码量是否面临爆炸式增长的问题? 包括:
模块划分粒度粗,分层混乱。
代码耦合度高,缺少重用,存在代码搬运现象,开发效率低,影响了迭代速度。
系统理解困难,可维护性差。
系统划分不合理,致使各系统之间关系复杂。
系统可扩展性差,性能受到限制。
为了更好的服务于产品(这是我们的立足之本、核心使命,架构为业务服务),同时作为开发人员能够惬意、高效地进行日常开发升级,你的系统是否急需要一次大的诊断并手术,这就是“重构”,只有重构才能精简代码,避免重复拷贝,提高开发效率,进而提高团队生产力,因此在重构的背景下,可以选择了引入“服务化”,二者会起到协同效应的作用,一方面模块化,另一方面服务化,对于业务稍复杂的系统来说,这可以说是一种最佳实践。
Navi就是在服务化的背景下产生的,作为未来服务化发展的核心基础框架。
- 服务提供者(Provider): 暴露服务的服务提供方,也叫做服务端。
- 服务容器:服务运行容器,由spring负责管理,在servlet容器中运行。
- 服务消费者: 调用远程服务的服务消费方,也就是调用方。
- 服务注册中心(Zookeeper):服务注册与发现的注册中心,由zookeeper实现。
- 启动:服务容器负责启动,加载,运行服务提供者。
- 注册:服务提供者在启动时,向注册中心注册自己提供的服务。
- 订阅:服务消费者在启动时,向注册中心订阅自己所需的服务。
- 通知:注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 调用:服务消费者,从提供者地址列表中,基于软负载均衡算法(轮训或者随机),选一台提供者进行调用(可采用FAILOVER重试或者FAILFAST失败退出策略)。
注册中心采用zookeeper实现,Zookeeper 作为一个分布式的服务框架,是Apache Hadoop下的一个子项目,主要用来解决分布式集群中应用系统的一致性问题。
它能提供基于类似于文件系统的目录节点树方式的数据存储。但是 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控存储数据的状态变化。
通过监控这些数据状态的变化,从而可以达到基于数据的集群管理。
每个子目录项都被称作为 znode,znode按照path下来是可以唯一定位的。znode 可以有子节点目录,并且每个 znode 可以存储数据,节点类型可以为PERSISTENT或者EPHEMERAL类型。
所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。这也是利用zookeeper来监听服务提供者是否在线的原理。
服务注册到zookeeper的path,以及客户端订阅的时候需要有一个统一的规则来规定各个path的层级关系,有个规则,那么服务提供者、消费者就可以做到“言语”统一,方便管理。
Navi规则见下图,
解释如下,
-
Root: zookeeper根路径。
-
根命名空间(global namespace): 服务化用的路径,与它并列还有其他需要用到zookeeper的项目,例如北斗的fountain项目。
-
项目命名空间(app namespace): 项目命名空间,可以采用多路径配置,znode数量随意,只需要在服务提供者、消费者的配置文件中配置正确即可。推荐采用服务名称+版本号方式,例如/my-service/main表示自己的第一个服务的全流量main版本。
-
服务(services): 这一层就是app暴露的服务详细了,默认一个znode + 一个“IP:PORT”命名的znode,前者表示服务名称,后者表示提供服务的地址。
其中Global namespace在代码中默认设置为/navi_rpc,该节点必须存在于zookeeper中;如果想设置自己的公共默认路径请配置ZOOKEEPER_BASE_PATH#NaviCommonConstant.java
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互。
- 注册中心、服务提供者、服务消费者三者之间均为长连接。
- 注册中心通过长连接感知服务提供者的存在,服务提供者down掉,注册中心将立即推送事件通知消费者。
- 注册中心全部down掉,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。
- 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者。
- 注册中心使用对等zookeeper集群,任意一台宕掉后,将自动切换到另一台,一般采用2+1策略,即3台里允许一台down掉,zookeeper服务不受影响。
- 服务提供者无状态,任意一台宕掉后,不影响使用,同时可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者,从而达到了水平扩展,扩容的目的。
- 注册中心上可动态增加或减少服务,所有消费者将自动发现新的注册服务。
为了快速了解navi如何使用,我们来做一个简单的hello world。
服务提供者、消费者均需要依赖navi。项目中的navi-demo-server即是一个服务提供者模块,navi-demo-client即是一个服务消费者模块。
服务提供者和消费者模块都需要依赖navi模块,该模块用maven进行源代码管理,在pom.xml中加入dependency:
<dependency>
<groupId>com.baidu.beidou</groupId>
<artifactId>navi-rpc</artifactId>
<version>1.1.0</version>
</dependency>
通过mvn dependency:tree命令分析,navi依赖以下三方库:
[INFO] +- org.springframework:spring-beans:jar:3.1.2.RELEASE:compile
[INFO] +- org.springframework:spring-context:jar:3.1.2.RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:3.1.2.RELEASE:compile
[INFO] | +- org.springframework:spring-expression:jar:3.1.2.RELEASE:compile
[INFO] | \- org.springframework:spring-asm:jar:3.1.2.RELEASE:compile
[INFO] +- org.springframework:spring-core:jar:3.1.2.RELEASE:compile
[INFO] | \- commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- org.springframework:spring-web:jar:3.1.2.RELEASE:compile
[INFO] | \- aopalliance:aopalliance:jar:1.0:compile
[INFO] +- org.codehaus.jackson:jackson-mapper-asl:jar:1.9.9:compile
[INFO] +- org.codehaus.jackson:jackson-core-asl:jar:1.9.9:compile
[INFO] +- com.dyuproject.protostuff:protostuff-runtime:jar:1.0.7:compile
[INFO] +- com.dyuproject.protostuff:protostuff-api:jar:1.0.7:compile
[INFO] +- com.dyuproject.protostuff:protostuff-collectionschema:jar:1.0.7:compile
[INFO] +- com.dyuproject.protostuff:protostuff-core:jar:1.0.7:compile
[INFO] +- org.apache.zookeeper:zookeeper:jar:3.4.0:compile
[INFO] | +- org.slf4j:slf4j-api:jar:1.6.1:compile
[INFO] | +- org.slf4j:slf4j-log4j12:jar:1.6.1:compile
[INFO] | +- log4j:log4j:jar:1.2.15:compile
[INFO] | | \- javax.mail:mail:jar:1.4:compile
[INFO] | | \- javax.activation:activation:jar:1.1:compile
[INFO] | +- jline:jline:jar:0.9.94:compile
[INFO] | \- org.jboss.netty:netty:jar:3.2.2.Final:compile
[INFO] +- javax.servlet:servlet-api:jar:2.5:provided
推荐使用logback作为日志组件,在logback.xml中加入如下配置,默认level="INFO",如想打开navi的debug模式可以设置level="DEBUG"。
<logger name="com.baidu.beidou.navi" level="INFO" additivity="true">
</logger>
如果使用slf4j+log4j组合,则在pom中去掉相应的依赖,使用log4j配置即可。
log4j.rootLogger=INFO, console
log4j.logger.com.baidu.beidou.navi=INFO
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.encoding=gbk
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%p]\t%d\t[%t]\t%c{3}\t(%F:%L)\t-%m%n
服务端和客户端更加详细的日志格式详细见附录部分。
服务端demo的所有代码可以参考navi-demo-server模块。
Navi使用HTTP Servlet作为传输通道来进行RPC调用,因此服务端代码需要运行在servelt容器中,例如jetty、tomcat中。
Navi同时使用spring来管理处理请求的bean,因此需要spring容器的依赖,推荐使用spring3.0+以上版本,以下demo均为基于使用spring3.0+版本,同时兼容spring2.5,同时依赖于servlet-api,maven依赖为provided的scope,可以使用2.5也可以使用3.0版本。
首先创建一个war工程,配置webapp/WEB-INF/web.xml,常规spring配置省略,重点介绍需要加入一个核心的servlet配置。
<servlet>
<display-name>navi rpc servlet</display-name>
<servlet-name>naviRpcServlet</servlet-name>
<servlet-class>com.baidu.beidou.navi.server.NaviRpcServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>naviRpcServlet</servlet-name>
<url-pattern>/service_api/*</url-pattern>
</servlet-mapping>
工程配置在tomcat或者jetty等容器的ROOT path上,也就是说不可以带相对路径,这对大多数应用都是适用的,线上的运维要求也是一个运行实例对一个应用,否则客户端将无法正确访问到服务端。
在classpath中加入rpc-server.properties,默认启用zookeeper来进行服务的注册、订阅、更新操作,因此该配置文件中所有包含为*ZK*的均为zookeeper相关配置。
如果不想利用注册中心的zookeeper来订阅服务端地址,服务端可以关闭服务注册发布功能,只需要设置ENABLE_ZK_REGISTRY为false,那么客户端就自己直接定义访问地址,详见4.2.8小节。
################################################
#
# 是否启用zookeeper进行服务注册、发布
# 当ZK_SERVER_LIST为空时,该值忽略直接为false
# true:启用
# false:不启用
#
################################################
ENABLE_ZK_REGISTRY=true
################################################
#
# zookeeper集群地址IP:PORT列表,按逗号分隔
#
################################################
ZK_SERVER_LIST=127.0.0.1:8701
################################################
#
# zookeeper访问控制密串,留空表示不启用访问控制
#
################################################
ZK_DIGEST_AUTH=
################################################
#
# 启用zookeeper注册后,所发布服务的命名空间
# 按照"/产品线/服务名称/版本"的格式定义
#
################################################
ZK_REGISTRY_NAMESPACE=/beidou/test-service/online
################################################
#
# zookeeper nio连接seesion超时时间(单位ms),一般设置为30s
#
################################################
ZK_DEFAULT_SESSION_TIMEOUT_MILLS=30000
################################################
#
# 启动zookeeper连接时的超时时间(单位ms)
#
################################################
ZK_CONNECTION_TIMEOUT_MILLS=30000
################################################
#
# *选择性配置*
#
# 启用zookeeper注册服务时,需要提前配置端口号。
# (1) 在JPaaS1.0上环境,自动替换为环境变量中的
# $VCAP_APP_PORT或者JVM启动参数中的
# -Dport.http.nonssl。
# (2) 在JPaaS2.0上环境,自动替换为环境变量中的
# $JPAAS_HOST为注册IP,
# $JPAAS_HTTP_PORT为访问端口。
# (3) 如果不满足(1),(2)则直接取该配置值,因此通常在
# 开发环境或者测试环境中需要设置此值为tomcat或者jetty端口。
#
################################################
SERVER_PORT=8080
更多关于rpc-server.properties的配置说明见配置说明。
配置加载到spring容器中,需要在applicationContext.xml中加入如下配置:
<context:annotation-config />
<context:component-scan base-package="com.baidu" />
<context:property-placeholder location="classpath*:/rpc-server.properties" />
<bean class="com.baidu.beidou.navi.conf.RpcServerConf">
<property name="ENABLE_ZK_REGISTRY">
<value>${ENABLE_ZK_REGISTRY}</value>
</property>
<property name="ZK_SERVER_LIST">
<value>${ZK_SERVER_LIST}</value>
</property>
<property name="ZK_DIGEST_AUTH">
<value>${ZK_DIGEST_AUTH}</value>
</property>
<property name="ZK_DEFAULT_SESSION_TIMEOUT_MILLS">
<value>${ZK_DEFAULT_SESSION_TIMEOUT_MILLS}</value>
</property>
<property name="ZK_CONNECTION_TIMEOUT_MILLS">
<value>${ZK_CONNECTION_TIMEOUT_MILLS}</value>
</property>
<property name="ZK_REGISTRY_NAMESPACE">
<value>${ZK_REGISTRY_NAMESPACE}</value>
</property>
<property name="SERVER_PORT">
<value>${SERVER_PORT}</value>
</property>
</bean>
推荐使用这种方式来开发服务。
Navi提供的服务接口暴露粒度为“类”级别,也就是说当需要暴露服务时,也就是把该类下面的所有方法当做服务接口暴露出去了,支持类的继承以及接口的继承。
服务参数及返回值都必需是byValue的,而不能是byRef的,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,navi不支持引用远程对象
例如,我们要开发一个公司信息服务,提供公司信息的CRUD操作,首先创建一个Company类,该类可以不实现Serialization接口,POJO类建议存在默认无参构造函数,否则使用jackson-json序列化时会报错。Company类可以看做是BO(Biz Object),可以具有复杂结构,包括继承、引用其他类型泛型的属性等,但是不支持内部类(Inner class)实现。
它的服务CompanyMgr接口定义如下:
public interface CompanyMgr {
public Company get(int id);
public List<Company> getAll();
public Company add(Company company);
public void delete(int id);
public void update(Company company);
}
其实现类CompanyMgrImpl加入注解
@NaviRpcService(serviceInterface = CompanyMgr.class)
例如,
@Service
@NaviRpcService(serviceInterface = CompanyMgr.class)
public class CompanyMgrImpl implements CompanyMgr {
//… 实现方法
}
其中类注解@service为Spring bean的注解标示,表示该实现bean被Spring容器托管;类注解@NaviRpcService表示暴露该实现bean为navi-rpc服务,参数serviceInterface表示该bean实现的接口,客户端可以通过rpc调用方式访问通信。
服务端可以发布只包含VO/BO/接口/异常类的sdk jar,供客户端依赖使用。详细见配置说明-第4部分。
服务端异常传递详细见配置说明-第8部分。
服务端启动权限控制,可以加入如下注解:
@NaviRpcAuth({ @Authority(appId = "beidou", token = "123456"), @Authority(appId = "ssp", token = "111111") })
例如,这样客户端就必须配置appId+token才可以访问。
@Service
@NaviRpcService(serviceInterface = AuthService.class)
@NaviRpcAuth({ @Authority(appId = "beidou", token = "123456"), @Authority(appId = "ssp", token = "111111") })
public class AuthServiceImpl implements AuthService {
@Override
public String sayHello() {
return "hi!";
}
}
如上所述的CompanyMgr,用XML方式暴露服务的配置如下
配置加载到spring容器中,需要在applicationContext.xml中加入如下配置:
<!-- service bean begin -->
<bean id="companyMgr" class="com.baidu.xxx.CompanMgrImpl">
</bean>
<!-- service bean end -->
<!-- NaviRpcExporter begin -->
<bean class="com.baidu.beidou.navi.server.NaviRpcExporter"
scope="singleton">
<property name="serviceInterfaceName" value="com.baidu.xxx.CompanMgr"/>
<property name="serviceBean">
<ref bean="companyMgr"/>
</property>
</bean>
<!-- NaviRpcExporter end -->
两种方式目前都支持服务端代理,可以将bean交由spring管理,配置成由cglib、aspectj织入的proxy。
至此,所有服务的配置工作已经结束,不存在任何API侵入,简单易用。
服务的启动方式即为常规的war启动方式,可以通过jetty或者tomcat方式启动。
启动完毕后,访问服务的如下URL:
http://${IP}:${PORT}/service_api/
可以看到该服务所暴露的所有接口:
点击任意接口可以看到该接口的定义:
点击类定义看到接口内所有的参数类型及其父类的详细信息:
点击“Zookeeper configuration”可以查看目前所有注册到zookeeper注册中心的服务、数量、机器、host信息等:
推荐一般项目使用这种客户端调用方式,依赖服务端发布的SDK jar,可以做到只依赖其暴露的VO/BO/接口/异常信息等,减少和服务端的耦合。 使用JDK动态代理技术,发起rpc调用,JDK动态代理的对象Bean,可以交由Spring容器托管,无缝和客户端其他代码融合,便于开发管理业务逻辑。 客户端demo的所有代码可以参考navi-demo-client模块。
服务消费者可以是war也可以是jar,其代码依赖的服务提供者可以是war也可以是jar,并无强者要求。
具体开发过程中,往往会存在服务依赖与服务的需求,也可以理解为war依赖于war,为了避免这种不友好的方式,我们可以通过maven编译出一个war包的sdk jar包,只包含接口和需要的VO,这样就可以做到两个模块减少耦合,界限明晰,详见配置说明。
在classpath中加入rpc-client.properties,默认启用zookeeper来进行服务的注册、订阅、更新操作,因此该配置文件中所有包含为*ZK*的均为zookeeper相关配置。
如果不想利用注册中心的zookeeper来订阅服务端地址,客户端可以关闭服务注册发布功能,只需要设置ENABLE_ZK_REGISTRY为false,那么就使用SERVER_LIST来直接调用服务端。
################################################
#
# 客户端是否启用zookeeper进行服务发现
# 当ZK_SERVER_LIST为空时,该值忽略直接为false
# true:启用
# false:不启用
#
################################################
ENABLE_ZK_REGISTRY=true
################################################
#
# zookeeper集群地址IP:PORT列表,按逗号分隔
#
################################################
ZK_SERVER_LIST=127.0.0.1:8080
################################################
#
# zookeeper访问控制密串,留空表示不启用权限控制
#
################################################
ZK_DIGEST_AUTH=
################################################
#
# 客户端订阅zookeeper的path列表,
# 可以调用多个不同的服务。
#
################################################
ZK_WATCH_NAMESPACE_PATH1=/group-service/online
ZK_WATCH_NAMESPACE_PATH2=/plan-service/online
ZK_WATCH_NAMESPACE_PATH3=/creative-service/online
################################################
#
# zookeeper nio连接seesion超时时间(单位ms),一般设置为30s
#
################################################
ZK_DEFAULT_SESSION_TIMEOUT_MILLS=30000
################################################
#
# 启动zookeeper连接时的超时时间(单位ms)
#
################################################
ZK_CONNECTION_TIMEOUT_MILLS=30000
################################################
#
# rpc连接超时时间(单位ms)
#
################################################
RPC_CONNECTION_TIMEOUT=6000
################################################
#
# rpc读超时时间(单位ms)
#
################################################
RPC_READ_TIMEOUT=30000
################################################
#
# rpc调用失败重试次数
#
################################################
RPC_RETRY_TIMES=2
更多关于rpc-client.properties的配置说明见配置说明。
配置加载到spring容器中,需要在applicationContext.xml中加入如下配置:
<context:property-placeholder location="classpath*:/rpc-client.properties" />
<bean class="com.baidu.beidou.navi.conf.RpcClientConf">
<property name="ENABLE_ZK_REGISTRY">
<value>${ENABLE_ZK_REGISTRY}</value>
</property>
<property name="ZK_SERVER_LIST">
<value>${ZK_SERVER_LIST}</value>
</property>
<property name="ZK_DIGEST_AUTH">
<value>${ZK_DIGEST_AUTH}</value>
</property>
<property name="ZK_DEFAULT_SESSION_TIMEOUT_MILLS">
<value>${ZK_DEFAULT_SESSION_TIMEOUT_MILLS}</value>
</property>
<property name="ZK_CONNECTION_TIMEOUT_MILLS">
<value>${ZK_CONNECTION_TIMEOUT_MILLS}</value>
</property>
<property name="ZK_WATCH_NAMESPACE_PATHS">
<!-- 多个服务端地址按照逗号分隔,此处表示需要利用Zookeeper监控这些服务的地址以及端口变化 -->
<value>${ZK_WATCH_NAMESPACE_PATH1},${ZK_WATCH_NAMESPACE_PATH2},${ZK_WATCH_NAMESPACE_PATH3}</value>
</property>
<property name="RPC_CONNECTION_TIMEOUT">
<value>${RPC_CONNECTION_TIMEOUT}</value>
</property>
<property name="RPC_READ_TIMEOUT">
<value>${RPC_READ_TIMEOUT}</value>
</property>
<property name="RPC_RETRY_TIMES">
<value>${RPC_RETRY_TIMES}</value>
</property>
</bean>
Spring3.0以后,XML 配置方式不再是 Spring IoC 容器的唯一配置方式,如今使用注解,配置信息的载体由 XML 文件转移到了 Java 类中。通常将用于存放配置信息的类的类名以 “Config” 结尾,比如 AppDaoConfig.java、AppServiceConfig.java 等等。需要在用于指定配置信息的类上加上 @Configuration 注解,以明确指出该类是 Bean 配置的信息源。
可以采用如下的方式配置一个Navi-rpc Config类:
@Configuration
public class RpcConfig {
///**************************************************************
//
// Navi Rpc bean proxy are listed below
//
//***************************************************************
@Bean
public CompanyMgr companyMgr() throws Exception {
return (CompanyMgr) (new NaviProxyFactoryBean("/navi-demo-server/main/", CompanyMgr.class, NaviProtocol.PROTOSTUFF, NaviFailStrategy.FAILOVER, NaviSelectorStrategy.RANDOM).getObject());
}
}
该类中定义了一个特殊的方法,companyMgr(),该方法的作用是当某个类需要注入名字为companyMgr的bean时,该对象即由RpcConfig类的指定companyMgr()方法提供。
提供的对象利用的JDK Proxy技术,提供远程RPC调用的底层实现。
注意到如下参数,可选
NaviProtocol.PROTOSTUFF, //表示使用protostuff序列化协议,也可以指定为protobuf协议
NaviFailStrategy.FAILOVER, //表示使用failover策略,失败重试,也可以指定为立即退出
NaviSelectorStrategy.RANDOM //表示使用随机访问,也可指定为round robin软负载均衡策略
参数的详细信息见配置说明。
当不想利用服务注册中心来订阅服务地址的时候,可以去掉NaviProxyFactoryBean的构造函数的第一个参数,即可通过直连的方式调用。
如上所述的companyMgr代理,用xml方式配置服务的配置如下,配置了两个接口服务(此处的service1实际代表某个服务暴露的接口,在Java代码层次中属于service层而已,因此通常命名为service)。 配置加载到Spring容器中,需要在applicationContext.xml中加入如下配置,protocol、failStrategy、selectorStrategy均可选:
<bean id="service1" class="com.baidu.beidou.navi.client.NaviProxyFactoryBean">
<property name="zkNodePath">
<value>${ZK_WATCH_NAMESPACE_PATH1}</value>
</property>
<property name="directCallServers">
<value>cache.beidou.baidu.com</value>
</property>
<property name="serviceInterface">
<value>com.baidu.ub.beidou.app.exporter.AppService</value>
</property>
</bean>
<bean id="service2" class="com.baidu.beidou.navi.client.NaviProxyFactoryBean">
<property name="zkNodePath">
<value>${ZK_WATCH_NAMESPACE_PATH2}</value>
</property>
<property name="directCallServers">
<value>10.57.80.44:8090,10.49.57.123:8090</value>
</property>
<property name="serviceInterface">
<value>com.baidu.beidou.period.api.PeriodReportAPI</value>
</property>
<property name="appId">
<value>beidou</value>
</property>
<property name="token">
<value>123456</value>
</property>
<property name="protocol">
<value type="com.baidu.beidou.navi.server.protocol.NaviProtocol">PROTOBUF</value>
</property>
<property name="failStrategy">
<value type="com.baidu.beidou.navi.client.selector.NaviFailStrategy">FAILOVER</value>
</property>
<property name="selectorStrategy">
<value type="com.baidu.beidou.navi.client.selector.NaviSelectorStrategy">RANDOM</value>
</property>
</bean>
至此,所有服务的配置工作已经结束,不存在任何API侵入,简单易用。
在前两个小节配置的服务代理bean,可以用spring的原生方式注入到任何类中,例如通过@Resource、或者@Autowire注解
例如,一个spring的controller需要调用company接口或者所有公司信息:
@Controller
public class CompanyController extends BaseController {
@Resource
private CompanyMgr companyMgr;
@RequestMapping(value = "/getAll", method = RequestMethod.GET)
@ResponseBody
public JsonObject getAll() {
return buildSuccess("company", companyMgr.getAll());
}
}
如果启用了服务注册中心,那么服务端需要将一个NaviRpcServerListListener放到spring容器中,该listern是一个zookeeper的watcher,用来订阅rpc-client.properties中指定的watched namespaces,一旦服务其下注册的service的地址发生变更,即更新客户端的地址,默认该地址放到一个客户端的cache中,异步更新无缝切换,不会阻塞调用。配置需加入到applicationContext.xml中。
如果启用注册中心,那么该listener必须添加到spring容器中。
<bean class="net.neoremind.navi.client.NaviRpcServerListListener">
</bean>
当服务提供者down掉,或者切换实例IP后,那么最新的服务会将自己更新注册到zookeeper中,同时由于在上面一节配置过NaviRpcServerListListener,因此变更后的服务地址会直接更新到客户端的本地缓存中。一般断开后会经过rpc-server.propertis中定义的ZK_DEFAULT_SESSION_TIMEOUT_MILLS时间后通知到客户端。 服务更新后,将会看到客户端日志中打印如下:
2014-12-28 06:08:55.998 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
Receive watched event?WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/navi_rpc/beido
u/period-report/online/PeriodReportAPI
2014-12-28 06:08:55.998 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
********************************************************
2014-12-28 06:08:55.998 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
* Zookeeper service registry changes detected! *
2014-12-28 06:08:55.998 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
********************************************************
2014-12-28 06:08:55.998 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
Changed path is /navi_rpc/beidou/period-report/online/PeriodReportAPI
2014-12-28 06:08:55.998 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
Last update time:2014-12-27 23:32:22
2014-12-28 06:08:56.000 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
Zookeeper global root path is /navi_rpc
2014-12-28 06:08:56.000 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
Zookeeper watched name spaces are [/beidou/bd-service-cache/online, /beidou/period-report/online]
2014-12-28 06:08:56.009 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
======>Find 1 interfaces under service path - /navi_rpc/beidou/period-report/online
2014-12-28 06:08:56.011 [main-EventThread] INFO c.b.beidou.navi.client.NaviRpcServerListListener -
3 servers available for /navi_rpc/beidou/period-report/online/PeriodReportAPI, server list = [10.57.
201.41:63469, 10.52.124.56:61207, 10.57.201.37:63666]
Object[] args = new Object[] { 88 };
SimpleNaviRpcClient client = new SimpleNaviRpcClientBuilder().setIpPort(IPPORT)
.setServiceName("CompanyService").setConnectTimeout(5000).setReadTimeout(5000)
.build();
Company result = client.transport("get", args);
System.out.println(result);
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.SimpleHttpConnectionManager;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.junit.Test;
import com.baidu.beidou.navi.codec.Codec;
import com.baidu.beidou.navi.codec.protobuf.ProtobufCodec;
public class SimpleHTTPInvocationTest {
@Test
public void testHttpRpc() throws Exception {
PostMethod postMethod = null;
try {
//1. 构造请求体
Codec codec = new ProtobufCodec();
RequestDTO request = new RequestDTO();
request.setId(123);
request.setMethod("get");
request.setParameters(new Object[] { 100 });
//2. 设置调用的HTTP客户端以及填充请求体
String serviceUrl = "http://127.0.0.1:8080/service_api/CompanyMgr";
postMethod = new PostMethod(serviceUrl);
postMethod.setRequestHeader("content-type", "application/baidu.protobuf");
postMethod.setRequestEntity(new ByteArrayRequestEntity(codec.encode(RequestDTO.class, request)));
//3. 发送请求
HttpConnectionManagerParams connectionParams = new HttpConnectionManagerParams();
connectionParams.setConnectionTimeout(5 * 1000);
connectionParams.setSoTimeout(5 * 1000);
HttpConnectionManager manager = new SimpleHttpConnectionManager();
manager.setParams(connectionParams);
HttpClient client = new HttpClient(manager);
int state = client.executeMethod(postMethod);
//4. 解析请求返回
if (state == HttpStatus.SC_OK) {
ResponseDTO response = codec.decode(ResponseDTO.class, postMethod.getResponseBody());
System.out.println(response.getId() + "\t" + response.getStatus() + "\t" + response.getResult());
} else {
System.err.println("Invocation failed " + serviceUrl + ", HTTP status=" + state);
}
} catch (Exception e) {
System.err.println("Failed to invoke rpc. " + e.getMessage());
e.printStackTrace();
} finally {
if (postMethod != null) {
try {
postMethod.releaseConnection();
} catch (Exception e) {
System.err.println("Failed to release connection. " + e.getMessage());
}
}
}
}
}
/**
* Request
*
* @author Zhang Xu
*/
public class RequestDTO {
private long id;
private String method;
private Object[] parameters;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public Object[] getParameters() {
return parameters;
}
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
}
/**
* Response
*
* @author Zhang Xu
*/
public class ResponseDTO {
private long id;
private Object result;
/**
* @see com.baidu.beidou.navi.constant.NaviStatus
*/
private int status;
private Throwable error;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Throwable getError() {
return error;
}
public void setError(Throwable error) {
this.error = error;
}
}
由于navi基于HTTP协议进行通信,因此可以支持“伪”RESTful调用,并不是严格意义上的REST,只不过可以利用HTTP这一通用的协议以及无状态特性,使客户更灵活的接入系统。下面展示了利用wget或者curl等命令进行的一次调用。
wget http://cache.beidou.baidu.com/service_api/UnionSiteService --post-data '{"method":"getAll4WebData"}' --header 'Content-Type:application/baidu.json' -O unionSite.json
wget http://smartideaconf.beidou.baidu.com/service_api/SiConfService --post-data
'{"method":"getTplConf", "parameters":[150001], "parameterTypes":["long"]}' --
header 'Content-Type:application/baidu.json' -O getTplConf`date -d today +"%Y%m%
d-%H%M"`.json
请求结果格式为一段Json数据,例如:
{
"result":
{
"siteurl": "wagnsp.com",
"firstTradeId": 1,
"secondTradeId": 101,
"srchs": 4
}
]
}
对于服务器端的access日志,在独立主机上可以单独配置打印到指定的文件,navi-rpc使用slf4j为日志实现接口,以下是以logback为例的一个配置:
<appender name="accessLogger"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>../logs/access.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>365</maxHistory>
</rollingPolicy>
<encoder charset="GBK">
<pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
<Encoding>GBK</Encoding>
</appender>
<logger name="com.baidu.beidou.navi.util.AccessLogSupport" level="INFO" additivity="true">
<appender-ref ref="accessLogger" />
</logger>
每次调用会打印如下信息:
[INFO] 2015-08-08 23:37:46,450 [pool-1-thread-1] navi.util.AccessLogSupport (AccessLogSupport.java:116) -192.168.31.142 BookMgr getAll application/baidu.protostuff 1682566910 {"traceId":1682566910,"method":"getAll","parameters":null,"paramterTypes":[],"appId":null,"token":null} 38 0 [{"id":1,"name":"xxxx","pageNum":123,"publishDate":973872000000},{"id":1,"name":"yyyy","pageNum":123,"publishDate":973872000000},{"id":12345,"name":"zzzz","pageNum":789,"publishDate":1212076800000}] 373
日志按照tab分隔字段,含义如下:
字段位置 | 含义 |
---|---|
1 | 客户端IP |
2 | 服务名 |
3 | 接口名 |
4 | 序列化协议 |
5 | trace id |
6 | 请求信息,仅展示部分 |
7 | 请求信息大小(bytes) |
8 | 请求结果状态码。0:成功,1:失败。通常是框架造成的,例如序列化失败等,2:系统内部错误。通常是业务逻辑造成的,不属于框架问题 |
9 | 请求结果,仅展示部分或者出错的异常信息 |
10 | 服务器处理耗时(ms) |
打印日志如下,共分两条,第一条为call start,第二条为call end:
[INFO] 2015-08-08 23:37:45,954 [main] navi.client.SimpleNaviRpcClient (SimpleNaviRpcClient.java:322) -Navi rpc call start - 1682566910 http://192.168.31.142:8080/service_api/BookMgr getAll application/baidu.protostuff 38
[INFO] 2015-08-08 23:37:46,407 [main] navi.client.SimpleNaviRpcClient (SimpleNaviRpcClient.java:336) -Navi rpc call end - 1682566910 http://192.168.31.142:8080/service_api/BookMgr getAll application/baidu.protostuff 38 231 584
日志按照tab分隔字段,含义如下:
字段位置 | 含义 |
---|---|
1 | trace id |
2 | 调用服务地址 |
3 | 接口名 |
4 | 序列化协议 |
5 | 请求信息大小(bytes) |
6(optinal) | 响应信息大小(bytes) |
7(optinal) | 从发起请求到得到响应耗时,包括服务处理时间以及网络通信,客户端接收并且反序列化(ms) |