随着互联网日新月异的发展,传统的软件架构已经满足不了庞大的用户需要和服务可靠性以及稳定性,SOA概念提出后,很多公司都在考虑做服务化,按照业务模块划分,每一个模块都是一个独立的工程,并且能够提供服务,热点模块也可以部署多个(支付场景)来提高服务可靠性和稳定性,目前比较常见的有dubbo(阿里开源),motan(新浪微博开源),
gRpc(谷歌)以及国外比较流行的springcloud等。每个框架都有其优势和不足,对于其他rpc框架暂时不做赘述,今天就国内比较流行的开源rpc框架dubbo,做一下分析和使用.
一、新建maven工程&添加依赖
1. 新建工程,结构如下图:
dubbo-server:聚合工程,主要用来管理工程和提供一些通用依赖
dubbo-server-interface:接口工程,定义一些接口和数据传输类;如果其他模块要依赖该模块服务,代码层面要依赖该接口工程
dubbo-server-provider:服务提供工程,提供服务具体实现以及服务暴露
2. 添加基本的依赖
(I) dubbo-server-interface工程添加dubbo依赖:
<!-- dubbo依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
<exclusions>
<exclusion>
<artifactId>netty</artifactId>
<groupId>org.jboss.netty</groupId>
</exclusion>
<exclusion>
<artifactId>spring</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
排除dubbo中的netty和spring依赖(dubbo依赖的包版本过低,排除后我们自己引入相关依赖)
(II) dubbo-server-provider添加最简依赖(同时依赖dubbo-server-interface包):
<!-- netty依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>3.10.6.Final</version>
</dependency>
<!-- mysql connector -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- druid jdbcpool -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.2</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
<!-- 接口依赖 -->
<dependency>
<groupId>com.typhoon</groupId>
<artifactId>dubbo-server-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
二、添加配置和服务暴露
- 添加配置文件,如下图:
jdbc.properties:数据库访问配置文件
server.properties:服务配置文件,目前主要提供服务host和端口配置
dubbo-provider.xml:服务暴露配置文件(dubbo服务暴露可以使用注解,但是有 些注解名称和spring注解相同,容易混淆,所以dubbo服务暴露和消费都使用配置的方式)
spring-root.xml:项目的主配文件
2. 编写服务和服务暴露
编写一个简单的用户查询接口和实现:
(I)在dubbo-server-interface中定义接口和数据传输类
UserDto:
/**
* 用户信息数据传输类
*
* @author Typhoon
*
*/
public class UserDto implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3059861301424720410L;
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 创建时间
*/
private Date createTime;
//省略get和set方法
}
UserService:
public interface UserService {
UserDto queryByPK(Long id);
}
(II)编写服务实现
User:
/**
* 用户信息实体类
*
* @author Typhoon
*
*/
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3059861301424720410L;
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 创建时间
*/
private Date createTime;
//省略set和get方法
}
UserServiceImpl:
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
UserDao userDao;
@Override
public UserDto queryByPK(Long id) {
User user = this.userDao.queryByPK(id);
if(null == user) {
return null;
}
UserDto dto = new UserDto();
dto.setId(user.getId());
dto.setName(user.getName());
dto.setCreateTime(user.getCreateTime());
return dto;
}
}
UserDao:
@Repository("userDao")
public class UserDao {
JdbcTemplate jdbcTemplate = DataSourceUtils.getJdbcTemplate();
public User queryByPK(Long id) {
String sql = " select * from user where id = ? limit 1 ";
List<User> list = this.jdbcTemplate.query(sql, BeanPropertyRowMapper.newInstance(User.class), id);
if(null == list || list.isEmpty()) {
return null;
}
return list.get(0);
}
}
(III)配置服务暴露
在dubbo-provider.xml中添加一下配置:
<dubbo:application name="dubbo-provider"/>
<dubbo:registry register="false" />
<!-- <dubbo:registry protocol="zookeeper" address="
<!-- <dubbo:provider timeout="${dubbo.timeout}" retries="0"></dubbo:provider> -->
<dubbo:protocol name="dubbo" port="
<dubbo:service interface="org.dubbo.server.service.UserService" ref="userService" protocol="dubbo" />
<dubbo:application />用来配置当前模块的名称,主要用来做调用分析
<dubbo:registry />用来做配置中心(比如zookeeper),该实例中我们暂不是用注册中心,所以配置register=false(如果没有<dubbo:registry />节点,服务无法启动)
<dubbo:protocol />用来配置服务协议,常用的有dubbo协议,hessian协议,此处我们使用dubbo协议,协议中需要配置服务暴露主机和端口
<dubbo:service />改节点才是我们具体服务的暴露,interface需要配置接口,ref配置spring容器注入的bean引用,protocol就是我们上述的协议配置.
然后把dubbo-provider.xml导入到项目主配文件spring-root.xml中,这样工程启动的时候会加载dubbo-provider.xml中配置
(IV)编写服务启动门面类&启动服务
经过上述步骤,我们的dubbo服务已经编写和配置完成,为了能够独立启动服务,还需要一个门面类用来启动dubbo服务(部署到linux环境的时候使用shell脚本运行该类来启动服务),代码如下:
public class ProviderServer {
public static void main(String[] args) throws Exception {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-root.xml");
context.start();
CountDownLatch count = new CountDownLatch(1);
count.await();
context.close();
}
}
然后运行该门面类,看到如下结果:
没有看到异常,我们暂且认为服务启动成功了(其实了解过netty的朋友,可以从最后一行看出来,nettserver绑定成功,服务暴露在本机20289端口)
三、编写单元测试&消费服务
经过上述步骤,dubbo服务已经启动了,接下来我们就需要模拟消费者去消费服务.
1. 编写单元测试消费服务
编写dubbo单元测试基类,代码如下:
/**
* 免配置dubbo服务单元测试
*
* @author Typhoon
* @date 2017-08-03 17:35 Thursday
* @since V1.3.1
* @param <T>
*/
@SuppressWarnings("unchecked")
public abstract class AbstractBaseTest<T> {
private ApplicationConfig application = null;
private Class <T> clazz = (Class <T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
private static final String appName = "dubbo-consumer";
public AbstractBaseTest() {
application = new ApplicationConfig();
application.setName(appName);
//registry.setAddress(zkUrl);
// 注意:ReferenceConfig为重对象,内部封装了与注册中心的连接,以及与服务提供方的连接
}
T getService() {
// 引用远程服务
ReferenceConfig<T> reference = new ReferenceConfig<T>(); // 此实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
reference.setApplication(application);
reference.setUrl(getDubboUrl());
//reference.setFilter("consumerFilter");
reference.setInterface(clazz);
reference.setTimeout(getTimeout());
// 和本地bean一样使用xxxService
return reference.get(); // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
};
abstract String getDubboUrl();
/**
* 默认超时时间30秒
*
* @return
*/
int getTimeout() {
return 30000;
}
}
编写单元测试类,代码如下:
public class UserServiceTest extends AbstractBaseTest<UserService> {
UserService userService = this.getService();
@Override
String getDubboUrl() {
return "dubbo://localhost:20289";
}
@Test
public void testQueryByPK() {
UserDto userDto = this.userService.queryByPK(1L);
System.out.println("响应:" userDto);
}
}
运行单元测试,可以看到如下结果:
可以看出,使用单元测试模拟消费者已经成功消费了dubbo服务,可能会有人觉得这是不是本地调用而不是dubbo服务调用,为了验证我们的结论,我们下一步将使用真实的项目去依赖和消费服务
2. 编写单元测试消费服务
(I)新建消费者工程
在dubbo-consumer-provider中添加dubbo-consumer-interface、dubbo-server-interface以及其他dubbo和spring相关依赖:
<!-- netty依赖 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>3.10.6.Final</version>
</dependency>
<!-- spring依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<!-- commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>com.typhoon</groupId>
<artifactId>dubbo-consumer-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.typhoon</groupId>
<artifactId>dubbo-server-interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
(II)在dubbo-consumer-interface和dubbo-consumer-provider中服务消费接口和实现.
ImitateService:
public interface ImitateService {
void imitateQueryUserById(Long id);
}
ImitateServiceImpl:
@Service("imitateService")
public class ImitateServiceImpl implements ImitateService {
@Resource
UserService userService;
@Override
public void imitateQueryUserById(Long id) {
UserDto dto = this.userService.queryByPK(id);
System.out.println("远程调用userService查询结果:" dto);
}
}
注意:使用@Resource标注UserService后,我们可以像调用本地服务一样调用rpc服务
(III) 添加服务依赖配置
src/main/resources目录下添加dubbo-consumer.xml配置,内容如下:
<dubbo:application name="dubbo-consumer"/>
<!-- 用户信息服务依赖 -->
<dubbo:reference interface="org.dubbo.server.service.UserService" url="dubbo://localhost:20289" id="userService" protocol="dubbo" timeout="30000" />
<dubbo:application />和上述服务提供者一样.
<dubbo:reference />定义dubbo服务依赖,本质上是将dubbo服务本地化
然后将dubbo-consumer.xml导入到spring-root.xml中
(IV)编写测试验证
在dubbo-consumer-provider中新建RpcConsumer:
public class RpcConsumer {
@SuppressWarnings("resource")
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("spring-root.xml");
context.start();
ImitateService imitateService = SpringContextUtil.getBean("imitateService", ImitateService.class);
imitateService.imitateQueryUserById(1L);
}
}
然后以debug方式运行RpcConsumer:
跟进去具体服务,逐步运行看到如下结果:
终于大功告成,得到了我们想要的结果!
总结
经过一番波折,我们实现了dubbo服务的搭建、启动和消费,但是可以发现上述的服务是点到点直连,如果server挂掉,consumer就无法调用了,如果我们使用了zk注册中心,server启动成功后注册到zk,consumer消费的时候先去zk订阅,如果有可用节点,zk会根据配置的调用策略(轮训、最少访问等)提供一个节点给consumer,然后consumer使用拿到的节点url去连接对应的服务提供者节点.
PS:原创不易,多多支持!