sayi.github.com icon indicating copy to clipboard operation
sayi.github.com copied to clipboard

Dubbo 2.5.3+

Open Sayi opened this issue 9 years ago • 0 comments

1. 关于Dubbo、原理、负载均衡

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 官网地址 http://dubbo.io GitHub地址 https://github.com/alibaba/dubbo Dubbo 是阿里巴巴的一个开源产品,淘宝内部使用的HSF并未开源。 关于Dubbo的架构可以参考官网的示例图

http://dubbo.io/dubbo-architecture.jpg-version=1&modificationDate=1330892870000.jpg

当整个系统越来越庞大的时候,基础的分布式服务越来越多,这些基础服务的负载均衡,服务URL配置管理,服务的协调都显得复杂,dubbo就是这样一个分布式服务的协调和治理框架。

与Hessian比较,dubbo具有如下优势:

  • 服务URL地址的管理,服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者
  • Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
  • Dubbo提供了多种均衡策略,缺省为random随机调用。

Random LoadBalance随机,按权重设置随机概率。 RoundRobin LoadBalance轮循,按公约后的权重设置轮循比率。。 LeastActive LoadBalance最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 ConsistentHash LoadBalance 一致性Hash,相同参数的请求总是发到同一提供者。

2. Dubbo的几个概念:注册中心、服务提供者、服务消费者、协议(dubbo、http、hessian)

在架构图中,可以看到有以下几个节点:

  • Provider: 暴露服务的服务提供方。
  • Consumer: 调用远程服务的服务消费方。
  • Registry: 服务注册与发现的注册中心。
  • Monitor: 统计服务的调用次调和调用时间的监控中心。
  • Container: 服务运行容器。
  1. 注册中心 注册中心负责服务地址的注册与查找,相当于目录服务。服务提供者向注册中心注册自己的服务,服务消费者向注册中心发现自己的服务,当服务有变更时,注册中心通知到消费者。消费者发现所需服务列表后,将根据软负载均衡,选择一台提供者进行调用。注册中心,服务提供者,服务消费者三者之间均为长连接。 注册中心推荐使用Zookeeper,还可以使用Redis、数据库等客户端。如<dubbo:registry address="zookeeper://127.0.0.1:7181" />

  2. 服务提供者 暴露服务一方,每个接口都应定义版本号,为后续不兼容升级提供可能。由于默认使用hessian二进制序列化,所以接口的参数和返回值必须实现序列化接口。如<dubbo:service interface="com.deepoove.XxxService" version="1.0" />

  3. 服务消费者 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者。<dubbo:reference id="xxxService" interface="com.deepoove.XxxService" />

  4. 协议 http://dubbo.io/dubbo-protocol.jpg-version=1&modificationDate=1331068241000.jpg 服务调用涉及到网络通讯,缺省协议为dubbo://,dubbo协议采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。对于大数据量不建议采用dubbo协议,可以考虑短连接协议,比如rmi、http。dubbo协议默认序列化方式为hessian2,对性能有更高要求可以使用dubbo序列化。 <dubbo:protocol name=“dubbo” port=“9090” server=“netty” client=“netty” codec=“dubbo” serialization=“hessian2” charset=“UTF-8” threadpool=“fixed” threads=“100” queues=“0” iothreads=“9” buffer=“8192” accepts=“1000” payload=“8388608” />

  • Serialization枚举值:dubbo, hessian2, java, json dubbo协议说明:
  • 连接个数:单连接
  • 连接方式:长连接
  • 传输协议:TCP
  • 传输方式:NIO异步传输
  • 序列化:Hessian二进制序列化
  • 适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,尽量不要用dubbo协议传输大文件或超大字符串。
  • 适用场景:常规远程服务方法调用

除了默认dubbo协议,还有rmi、http、hessian(可以与原生hessian服务互操作)等协议。

3. 注册中心Zookeeper

Zookeeper是一个针对分布式应用程序的高性能协调服务,提供的功能有:命名、配置管理、同步、组服务等。Because coordinating distributed systems is a Zoo。 dubbo默认为zkclient,推荐使用Java客户端Curator,中文意思为馆长。

单机安装Zookeeper,下载tar包:

~/sayi/zookeeper/zookeeper-3.5.2-alpha$ cd conf/
~/sayi/zookeeper/zookeeper-3.5.2-alpha/conf$ cp zoo_sample.cfg zoo.cfg
~/sayi/zookeeper/zookeeper-3.5.2-alpha/conf$ vi zoo.cfg
~/sayi/zookeeper/zookeeper-3.5.2-alpha/conf$ cd ../bin/
~/sayi/zookeeper/zookeeper-3.5.2-alpha/bin$ ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/sayi/zookeeper/zookeeper-3.5.2-alpha/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

至此,Zookeeper已经启动完毕,其中zoo.cfg内容如下:

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial 
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between 
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just 
# example sakes.
dataDir=~/sayi/zookeeper/data/zookeeper
# the port at which the clients will connect
clientPort=7181

客户端连接Zookeeper,连接成功后可以通过help查看可用命令: ./zkCli.sh -server 127.0.0.1:7181

Dubbo使用Zookeeper作为注册中心,目录结构如下:

---dubbo
-----|---com.deepoove.XxxService
------------|-----configurators
------------|-----routers
------------|-----consumers
------------|-----providers
----------------------|--dubbo://172.16.11.18:20880/com.deepoove.XxxService?XXX

流程说明:
1.服务提供者启动时
  向/dubbo/com.deepoove.XxxService/providers目录下写入自己的URL地址。
2.服务消费者启动时
  订阅/dubbo/com.deepoove.XxxService/providers目录下的提供者URL地址。
  并向/dubbo/com.deepoove.XxxService/consumers目录下写入自己的URL地址。
3.监控中心启动时
  订阅/dubbo/com.deepoove.XxxService目录下的所有提供者和消费者URL地址。

4. Dubbo Example 一个示例

Dubbo版本为2.5.3,Zookeeper版本3.5.2。服务提供方[项目dubbo_example]采用jetty(tomcat)作为服务容器,服务消费端[项目dubbo_example_consumer]只是一个简单的Main方法,并加载一个简单的Spring容器,用于消费服务,服务接口[项目dubbo_example_api](该接口需单独打包,在服务提供方和消费方共享)不依赖任何jar包。项目基于Gradle进行构建。

1. 服务接口dubbo_example_api

服务接口需要单独打包,供服务提供方和消费方共享。定义一个简单的接口如下:

package com.deepoove.dubboexample.api.service;
import com.deepoove.dubboexample.api.pojo.User;

public interface UserService {
	User getUser(String id);
}

定义简单的类User:

package com.deepoove.dubboexample.api.pojo;
import java.io.Serializable;

public class User implements Serializable{

	private static final long serialVersionUID = -1169812613737118557L;
	private String id;
	private String name;
	private String site;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getSite() {
		return site;
	}

	public void setSite(String site) {
		this.site = site;
	}
}

2. 服务提供方dubbo_example

项目除了依赖dubbo、spring、Zookeeper、zkclient,还需要加上服务接口的依赖,在settings.gradle文件中,包含服务接口项目:

rootProject.name = 'dubbo_example'

include ':dubbo_example_api'
project(':dubbo_example_api').projectDir = new File(settingsDir, '../dubbo_example_api')

由于采用logback打印日志,同时使用jetty作为容器,所以我也加上了logback的依赖和jetty gradle插件,最终build.gradle文件内容如下:


apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'jetty'

repositories {
    mavenLocal()
    mavenCentral()
    jcenter()
}

dependencies {
    compile project(":dubbo_example_api")
    compile group: 'org.springframework', name: 'spring-webmvc', version: '4.1.5.RELEASE'
    compile group: 'org.springframework', name: 'spring-test', version: '4.0.5.RELEASE'
    compile group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
    
    compile (group: 'com.alibaba', name: 'dubbo', version: '2.5.3'){
    	exclude group: 'org.springframework'
    }
    compile group: 'org.apache.zookeeper', name: 'zookeeper', version: '3.5.2-alpha'
    compile group: 'com.github.sgroschupf', name: 'zkclient', version: '0.1'

    compile group: 'ch.qos.logback', name: 'logback-core', version: '1.1.7'
    compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.7'
    testCompile 'junit:junit:4.12'
}

jettyRun{
	contextPath = "demo"
	httpPort = 8077
}

定义服务接口的实现,实现类如下:

package com.deepoove.dubboexample.provider;

import com.deepoove.dubboexample.api.pojo.User;
import com.deepoove.dubboexample.api.service.UserService;

public class UserServiceImpl implements UserService {

	@Override
	public User getUser(String id) {
		User user = new User();
		user.setId("sayi");
		user.setName("sayi");
		user.setSite("http://www.deepoove.com");
		return user;
	}

}

服务接口实现好之后,接下来就可以配置dubbo,在src/main/resources下新增logback.xml日志配置文件,在src/main/resources/application下新增配置文件remote-provider.xml,同时在web.xml中配置路径。

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:application/*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

remote-provider.xml中的典型配置如下,我们通过dubbo:application配置提供方应用信息名称,通过dubbo:registry向注册中心暴露服务地址,通过dubbo:protocol配置暴露服务的协议和端口,通过dubbo:service申明要暴露的服务接口。

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

	<dubbo:application name="dubbo-example-app" />

	<dubbo:registry address="zookeeper://127.0.0.1:7181" />

	<dubbo:protocol name="dubbo" port="20880" />

	<dubbo:service interface="com.deepoove.dubboexample.api.service.UserService"
		ref="userRemoteService" />

	<bean id="userRemoteService" class="com.deepoove.dubboexample.provider.UserServiceImpl" />

</beans>

运行grade jettyrun后,可以通过查看Zookeeper的目录或者lsof -i:20880查看服务提供的接口是否使用来确认服务是否提供成功。

3. 服务消费端dubbo_example_consumer

消费方式一个Main方法,加载了spring容器,同时依赖服务接口项目。定义文件remote-consumer.xml,通过dubbo:application配置消费方应用名,通过dubbo:reference生成远程服务代理,可以和本地bean一样使用,内容如下:

    <dubbo:application name="consumer-of-dubbo-example-app" />
    <dubbo:registry address="zookeeper://115.29.10.121:7181" />
    <dubbo:reference id="userService"
		interface="com.deepoove.dubboexample.api.service.UserService" />

Main方法中加载此xml文件,远程调用服务方提供的接口:

package com.deepoove.dubbo_example_consumer;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.deepoove.dubboexample.api.pojo.User;
import com.deepoove.dubboexample.api.service.UserService;

public class Consumer {

	public static void main(String[] args) throws Exception {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
				new String[] { "remote-consumer.xml" });
		context.start();

		UserService userService = (UserService) context.getBean("userService"); // 获取远程服务代理
		User sayi = userService.getUser("sayi"); // 执行远程方法

		System.out.println(sayi); // 显示调用结果

		System.in.read(); // 按任意键退出
		
		context.close();
	}
}

5. Dubbo:检测服务是否提供成功、验证是否可用

在实际使用中,在服务提供者启动完毕后,通常会检测服务是否发布成功,或者对服务的某个接口进行调用测试。大体上有以下三个方法:

  • 检测服务提供者监听端口号
lsof -i:20880
COMMAND  PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    4675  Sai  217u  IPv6 0x1ca3042d017b5851      0t0  TCP *:20880 (LISTEN)

linux上也可以用命令netstat -apn | grep 20880

  • 查看注册中心是否注册服务 以Zookeeper为例,可以查看在providers目录下是否暴露对应服务,同时,我们也可以提供一些连接Zookeeper的dubbo工具,如dubbo-admin、dubbo-monitor。
[zk: 127.0.0.1:7181(CONNECTED) 0] ls /
[dubbo, zookeeper]
[zk: 127.0.0.1:7181(CONNECTED) 1] ls /dubbo
[com.alibaba.dubbo.monitor.MonitorService, com.deepoove.dubboexample.api.service.UserService]
[zk: 127.0.0.1:7181(CONNECTED) 2] ls /dubbo/com.
com.alibaba.dubbo.monitor.MonitorService            com.deepoove.dubboexample.api.service.UserService   
[zk: 127.0.0.1:7181(CONNECTED) 2] ls /dubbo/com.deepoove.dubboexample.api.service.UserService
[configurators, consumers, providers, routers]
[zk: 127.0.0.1:7181(CONNECTED) 3] ls /dubbo/com.deepoove.dubboexample.api.service.UserService/providers
[dubbo%3A%2F%2F172.16.11.18%3A20880%2Fcom.deepoove.dubboexample.api.service.UserService%3Fanyhost%3Dtrue%26application%3Ddubbo-example-app%26dubbo%3D2.5.3%26interface%3Dcom.deepoove.dubboexample.api.service.UserService%26methods%3DgetUser%26pid%3D4675%26revision%3Ddubbo_example_api%26side%3Dprovider%26timestamp%3D1478840366117]
  • 模拟服务消费者调用服务 对于http协议可以通过浏览器进行模拟调用,但是由于缺省采用的是dubbo协议,所以我们需要写个简单的Main方法,参加服务消费端dubbo_example_consumer,如果不考虑具体方法的调用,可以采用回声测试:所有服务自动实现EchoService接口,只需将任意服务引用强制转型为EchoService。
    UserService demoService = (UserService) context.getBean("userService"); // 获取远程服务代理
    EchoService echoService = (EchoService) demoService; // 强制转型为EchoService
    Object status = echoService.$echo("OK"); // 回声测试可用性
    assert (status.equals("OK"));

6. Dubbo admin 和 Dubbo monitor

  • Dubbo admin 获取dubbo-admin.war,修改文件WEB-INF/dubbo.properties文件,配置Zookeeper地址:
dubbo.registry.address=zookeeper://127.0.0.1:7181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest

服务启动完毕后,浏览器访问控制台:127.0.0.1:8080/,输入密码进入管理界面。

  • Dubbo monitor 监控中心也是一个标准的dubbo服务,所以也需要配置dubbo.protocol.port端口。
dubbo.container=log4j,spring,registry,jetty
dubbo.application.name=simple-monitor
dubbo.application.owner=
dubbo.registry.address=zookeeper://127.0.0.1:7181
dubbo.protocol.port=7077
dubbo.jetty.port=8099
dubbo.jetty.directory=/Users/Sai/Sayi/source/dubbo/monitordirectory/monitor
dubbo.charts.directory=${dubbo.jetty.directory}/charts
dubbo.statistics.directory=/Users/Sai/Sayi/source/dubbo/monitordirectory/monitor/statistics
dubbo.log4j.file=/Users/Sai/Sayi/source/dubbo/monitordirectory/logs/dubbo-monitor-simple.log
dubbo.log4j.level=WARN

通过命令bin/start.sh启动监控中心服务,浏览器访问127.0.0.1:8099/,进入监控界面。

7. 安全:关于Zookeeper的ACL

Zookeeper使用ACL控制节点的访问,ACL不具有继承性,即对父节点设置的Access Control不应用到子节点上。ACL可以理解成为scheme : id : permission的组合,其中permission为:CREATE、READ、WRITE、DELETE、ADMIN。Zookeeper支持pluggable authentication schemes.

  • world has a single id, anyone, that represents anyone.
  • auth doesn't use any id, represents any authenticated user.
  • digest uses a username:password string to generate MD5 hash which is then used as an ACL ID identity. Authentication is done by sending the username:password in clear text. When used in the ACL the expression will be the username: base64 encoded SHA1 password digest(sha1加密后base64编码) .
  • host uses the client host name as an ACL ID identity. The ACL expression is a hostname suffix. For example, the ACL expression host:corp.com matches the ids host:host1.corp.com and host:host2.corp.com, but not host:host1.store.com.
  • ip uses the client host IP as an ACL ID identity. The ACL expression is of the form addr/bits where the most significant bits of addr are matched against the most significant bits of the client host IP.

下面的示例演示通过zkcli操作ACL,setAcl可以设置权限,getAcl获取权限列表:

[zk: 127.0.0.1:7181(CONNECTED) 14] ls /
[dubbo, zookeeper]
[zk: 127.0.0.1:7181(CONNECTED) 15] create /sayi "hello,data"
Created /sayi
[zk: 127.0.0.1:7181(CONNECTED) 16] get /sayi
hello,data
[zk: 127.0.0.1:7181(CONNECTED) 17] getAcl /sayi
'world,'anyone
: cdrwa
[zk: 127.0.0.1:7181(CONNECTED) 18] addauth digest sayi:123456
[zk: 127.0.0.1:7181(CONNECTED) 19] setAcl /sayi auth:sayi:123456:cdrwa
[zk: 127.0.0.1:7181(CONNECTED) 20] getAcl /sayi
'digest,'sayi:8n7aQg/S4IP6WJGOwf3DGTLg4iY=
: cdrwa
[zk: 127.0.0.1:7181(CONNECTED) 21] 

8. 服务参数校验、服务异常捕获、请求日志、服务文档

Sayi avatar Nov 08 '16 16:11 Sayi