作者:刘海龙
微博:[http://weibo.com/liuhailong2008]
博客:[http://blog.csdn.net/stationxp]2015-02-17 天园小区
计划是这样的:先把Duboo所有文档看一遍;然后启动起来,玩一把;然后看源码汲取营养;最后再把文档过一遍。
用户指南部分篇幅较长,本次涉及:协议参考、注册中心参考、Telnet命令参考、Maven、基准测试工具包
服务容器是一个standalone的启动程序,因为后台服务不需要Tomcat或JBoss等Web容器的功能,如果硬要用Web容器去加载服务提供方,增加复杂性,也浪费资源。
服务容器只是一个简单的Main方法,并加载一个简单的Spring容器,用于暴露服务。
服务容器的加载内容可以扩展,内置了spring, jetty, log4j等加载,可通过Container扩展点进行扩展,参见:Container。
(缺省只加载spring)
java com.alibaba.dubbo.container.Main
(通过main函数参数传入要加载的容器)
java com.alibaba.dubbo.container.Main spring jetty log4j
java com.alibaba.dubbo.container.Main -Ddubbo.container=spring,jetty,log4j
ReferenceConfig实例很重,封装了与注册中心的连接以及与提供者的连接,需要缓存,否则重复生成ReferenceConfig可能造成性能问题并且会有内存和连接泄漏。API方式编程时,容易忽略此问题。
Dubbo 2.4.0+版本,提供了简单的工具类ReferenceConfigCache用于缓存ReferenceConfig实例。
使用方式如下:
ReferenceConfig reference = new ReferenceConfig();
reference.setInterface(XxxService.class);
reference.setVersion(“1.0.0”);
……
ReferenceConfigCache cache = ReferenceConfigCache.getCache();
XxxService xxxService = cache.get(reference); // cache.get方法中会Cache Reference对象,并且调用ReferenceConfig.get方法启动ReferenceConfig
// 注意! Cache会持有ReferenceConfig,不要在外部再调用ReferenceConfig的destroy方法,导致Cache内的ReferenceConfig失效!
// 使用xxxService对象
xxxService.sayHello();
基于JSR303实现的,用户只需标识JSR303标准的验证Annotation,并通过声明filter来实现验证。
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.Future;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class ValidationParameter implements Serializable {
private static final long serialVersionUID = 7158911668568000392L;
@NotNull // 不允许为空
@Size(min = 1, max = 20) // 长度或大小范围
private String name;
@NotNull(groups = ValidationService.Save.class) // 保存时不允许为空,更新时允许为空 ,表示不更新该字段
@Pattern(regexp = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$")
private String email;
@Min(18) // 最小值
@Max(100) // 最大值
private int age;
@Past // 必须为一个过去的时间
private Date loginDate;
@Future // 必须为一个未来的时间
private Date expiryDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getLoginDate() {
return loginDate;
}
public void setLoginDate(Date loginDate) {
this.loginDate = loginDate;
}
public Date getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(Date expiryDate) {
this.expiryDate = expiryDate;
}
}
验证异常信息的处理:
try {
parameter = new ValidationParameter();
validationService.save(parameter);
System.out.println("Validation ERROR");
} catch (RpcException e) { // 抛出的是RpcException
ConstraintViolationException ve = (ConstraintViolationException) e.getCause(); // 里面嵌了一个ConstraintViolationException
Set> violations = ve.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
System.out.println(violations);
}
需要封装一个工具方法,判断RpcException中是否包含 ConstraintViolationException和violation的个数。
lru 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
threadlocal 当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。
jcache 与JSR107集成,可以桥接各种缓存实现。
缓存类型可扩展,参见:CacheFactory扩展点
泛接口调用方式主要用于客户端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的服务测试框架,可通过GenericService调用所有服务实现。
GenericService barService = (GenericService) applicationContext.getBean(“barService”);
Object result = barService.$invoke(“sayHello”, new String[] { “java.lang.String” }, new Object[] { “World” });
回声测试用于检测服务是否可用,回声测试按照正常请求流程执行,能够测试整个调用是否通畅,可用于监控。
所有服务自动实现EchoService接口,只需将任意服务引用强制转型为EchoService,即可使用。
String status = echoService.$echo(“OK”); // 回声测试可用性
assert(status.equals(“OK”))
boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 本端是否为提供端,这里会返回true
String clientIP = RpcContext.getContext().getRemoteHost(); // 获取调用方IP地址
String application = RpcContext.getContext().getUrl().getParameter(“application”); // 获取当前服务配置信息,所有配置信息都将转换为URL的参数
// …
yyyService.yyy(); // 注意:每发起RPC调用,上下文状态会变化
boolean isProviderSide = RpcContext.getContext().isProviderSide(); // 此时本端变成消费端,这里会返回false
// …
path,group,version,dubbo,token,timeout几个key有特殊处理,请使用其它key值。
适用于特别耗时的操作。
本地调用,使用了Injvm协议,是一个伪协议,它不开启端口,不发起远程调用,只在JVM内直接关联,但执行Dubbo的Filter链。
好好考察一下效率。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="simple-monitor" />
<dubbo:registry address="127.0.0.1:9090" />
<dubbo:protocol port="7070" />
<dubbo:service interface="com.alibaba.dubbo.monitor.MonitorService" ref="monitorService" />
<bean id="monitorService" class="com.alibaba.dubbo.monitor.simple.SimpleMonitorService" />
beans>
暴露一个简单监控中心服务,但不注册到注册中心: (如果是用安装包,不需要自己写这个配置,如果是自己实现监控中心,则需要)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="simple-monitor" />
<dubbo:protocol port="7070" />
<dubbo:service interface="com.alibaba.dubbo.monitor.MonitorService" ref="monitorService" registry="N/A" />
<bean id="monitorService" class="com.alibaba.dubbo.monitor.simple.SimpleMonitorService" />
beans>
直连监控中心服务:
不需要启动任何中心节点,只需要广播地址一样,就可以互相发现。
组播地址段: 224.0.0.0 - 239.255.255.255
提供方启动时广播自己的地址。
消费方启动时广播订阅请求。
提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了unicast=false,则广播给订阅者。
消费方收到提供方地址时,连接该地址进行RPC调用。
安装方式参见: Zookeeper安装手册,只需搭一个原生的Zookeeper服务器,并将Quick Start中Provider和Consumer里的conf/dubbo.properties中的dubbo.registry.addrss的值改为zookeeper://127.0.0.1:2181即可使用。
zookeeper实现:https://github.com/sgroschupf/zkclient 。
http://repo1.maven.org/maven2/com/github/sgroschupf/zkclient
只需搭一个原生的Redis服务器,并将Quick Start中Provider和Consumer里的conf/dubbo.properties中的dubbo.registry.addrss的值改为redis://127.0.0.1:6379即可使用。
Redis过期数据
通过心跳的方式检测脏数据,服务器时间必须相同,并且对服务器有一定压力。
数据结构:
使用Redis的Key/Map结构存储数据。
主Key为服务名和类型。
Map中的Key为URL地址。
Map中的Value为过期时间,用于判断脏数据,脏数据由监控中心删除。(注意:服务器时间必需同步,否则过期检测会不准确)
使用Redis的Publish/Subscribe事件通知数据变更。
通过事件的值区分事件类型:register, unregister, subscribe, unsubscribe。
普通消费者直接订阅指定服务提供者的Key,只会收到指定服务的register, unregister事件。
监控中心通过psubscribe功能订阅/dubbo/*,会收到所有服务的所有变更事件。
调用过程:
服务提供方启动时,向Key:/dubbo/com.foo.BarService/providers下,添加当前提供者的地址。
并向Channel:/dubbo/com.foo.BarService/providers发送register事件。
服务消费方启动时,从Channel:/dubbo/com.foo.BarService/providers订阅register和unregister事件。
并向Key:/dubbo/com.foo.BarService/providers下,添加当前消费者的地址。
服务消费方收到register和unregister事件后,从Key:/dubbo/com.foo.BarService/providers下获取提供者地址列表。
服务监控中心启动时,从Channel:/dubbo/*订阅register和unregister,以及subscribe和unsubsribe事件。
服务监控中心收到register和unregister事件后,从Key:/dubbo/com.foo.BarService/providers下获取提供者地址列表。
服务监控中心收到subscribe和unsubsribe事件后,从Key:/dubbo/com.foo.BarService/consumers下获取消费者地址列表。
选项:
可通过设置redis中key的前缀,缺省为dubbo。
可通过设置redis集群策略,缺省为failover。
failover: 只写入和读取任意一台,失败时重试另一台,需要服务器端自行配置数据同步。
replicate: 在客户端同时写入所有服务器,只读取单台,服务器端不需要同步,注册中心集群增大,性能压力也会更大。
Dogfooding
注册中心本身就是一个普通的Dubbo服务,可以减少第三方依赖,使整体通讯方式一致。
适用性说明
此SimpleRegistryService只是简单实现,不支持集群,可作为自定义注册中心的参考,但不适合直接用于生产环境。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsdhttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<dubbo:application name="simple-registry" />
<dubbo:protocol port="9090" />
<dubbo:service interface="com.alibaba.dubbo.registry.RegistryService" ref="registryService" registry="N/A" ondisconnect="disconnect" callbacks="1000">
<dubbo:method name="subscribe"><dubbo:argument index="1" callback="true" />dubbo:method>
<dubbo:method name="unsubscribe"><dubbo:argument index="1" callback="false" />dubbo:method>
dubbo:service>
<bean id="registryService" class="com.alibaba.dubbo.registry.simple.SimpleRegistryService" />
beans>
支持telnet对服务进行调试、管理,还可以扩展。
mvn duboo:registry -Dport=x
benchmark-2..214.tar.gz