avatar


20.Dubbo和Zookeeper

引例

假设现在有这么一个需求,需要在已有的服务上新增一个功能,但是该功能非常的消耗资源,甚至会影响已有的服务,导致已有服务的其他功能也不可用。

SOA-1

那么,有一个解决办法是,将需要新增的功能,抽成一个独立的服务,并部署在独立的机器上,已有的服务,只负责转发。

SOA-2

这便是SOA思想的体现。SOA(Service Oriented Architecture),面向服务的架构模式。
已有的服务去调用新服务的过程,就是RPC(Remote Procedure Call),远程过程调用。

根据上述设计,我们需要把新服务的地址配置在已有的服务上。但如果我们服务很多的话,这个会很繁琐,而且会导致耦合度很高。
我们如何更好的管理这些服务呢?
这就是本章要讨论的内容。

Dubbo

简介

Dubbo,一个RPC框架,可以和Spring框架无缝集成。

根据上文的讨论,我们知道,RPC并不是一个具体的技术,指的就是"远程调用过程"。
Dubbo就是一个RPC框架。

Dubbo官网:https://dubbo.apache.org

Dubbo的三大核心能力:

  1. 面向接口的远程方法调用
  2. 智能容错和负载均衡
  3. 服务自动注册和发现

Dubbo最初是阿里巴巴的,在2017年,正式移交给了Apache。

架构

Dubbo架构

节点说明:

  • Provider:暴露服务的 服务生产者
  • Consumer:调用远程服务的 服务消费者
  • Registry:服务注册与发现的 注册中心
  • Monitor:统计服务的调用次数和调用时间的 监控中心
  • Container:服务运行容器。

过程说明:

  • 0:服务容器启动,加载服务生产者。
  • 1:服务生产者在启动时,向注册中心注册自己提供的服务。
  • 2:服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 3:注册中心返回服务生产者的地址列表给服务消费者,如果有变更,注册中心将基于长连接推送变更数据给服务消费者。
  • 4:服务消费者从服务生产者地址列表中,基于软负载均衡算法,选一台服务生产者进行调用,如果调用失败,再选另一台服务生产者调用。
  • 5:服务消费者和服务生产者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Zookeeper

通过上文的Dubbo架构图可以看到,Registry(服务注册中心)在其中起着至关重要的作用。
Dubbo官方推荐使用Zookeeper作为服务注册中心。

(Kafka的注册中心,一般也采用Zookeeper。关于Kafka,我们在《分布式事件流平台Kafka》有更详细的讨论。)

简介

Zookeeper,注册中心,负责服务地址的注册与查找,相当于一个目录服务,服务生产者和服务消费者只在启动时与注册中心交互,注册中心不转发请求。

官网:https://zookeeper.apache.org

Zookeeper树型目录服务

如图,Zookeeper采用一种树型的目录服务。

  • 服务提供者(Provider)启动时,向/dubbo/com.foo.BarService/providers目录下写入自己的URL地址。
  • 服务消费者(Consumer)启动时,订阅/dubbo/com.foo.BarService/providers目录下的服务提供者URL地址。并向/dubbo/com.foo.BarService/consumers目录下写入自己的URL地址。
  • 监控中心(Monitor)启动时,订阅/dubbo/com.foo.BarService目录下的所有服务提供者和服务消费者URL地址。

安装

安装JDK

Zookeeper需要运行在JDK上。

下载并解压

可以通过官网下载Zookeeper,或者直接在Linux服务器上通过wget的方式下载。

1
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz

解压:

1
tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz

配置

  1. 在目录apache-zookeeper-3.7.1-bin下创建data目录。
  2. 复制conf目录下的zoo_sample.cfg,新文件名为zoo.cfg
  3. 修改zoo.cfg的dataDir属性
    1
    dataDir=/Users/kaka/Documents/apache-zookeeper-3.7.1-bin/data
    如果是Windows机器,需要注意转义字符。如,需要写成如下的形式
    1
    dataDir=D:\\apache-zookeeper-3.7.1-bin\\data
    或者
    1
    dataDir=D:/apache-zookeeper-3.7.1-bin/data

(默认端口是2181,可以通过"clientPort"进行修改。)

有些资料会建议修改bin目录下的"zkEnv"。
例如,对于zkEnv.cmd

1
set ZOOCFG=%ZOOCFGDIR%\zoo.cfg
改为
1
set ZOOCFG=%ZOOCFGDIR%\zoo_sample.cfg

实际上,这绝不是一个好办法。
更好的方法是复制zoo_sample.cfg,新文件名为zoo.cfg。

启动和停止

在Zookeeper的bin目录

  • 启动:./zkServer.sh start
  • 停止:./zkServer.sh stop
  • 查看:./zkServer.sh status

集群

也正因为Zookeeper如此重要,所以在实际中,Zookeeper不会是单台,通常是集群。

搭建集群

整体过程和单台差不多,多了两步:

  1. 配置每台服务的ID
  2. 配置集群服务器IP列表

配置每台服务的ID

在配置文件dataDir定义的目录(在本文是data目录)下创建一个myid文件,内容分别是123,记录每个服务器的ID。

1
echo 1 > myid
1
echo 2 > myid
1
echo 3 > myid

配置集群服务器IP列表

zoo.cfg配置客户端集群服务器IP列表。

1
2
3
server.1=172.23.2.79:2287:3387
server.2=172.23.2.80:2287:3387
server.3=172.23.2.81:2287:3387

格式:server.N =YYY:A:B

  • N:服务器编号
  • YYY:服务器地址
  • A:flower和leader的通信端口,即服务端内部通信的端口(默认2287)
  • B:选举端口(默认是3387)

启动集群

启动集群就是分别启动每个实例。

启动后我们查询一下每个实例的运行状态。

示例代码:

1
./zkServer.sh status

运行结果:

1
2
3
4
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.7.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: leader

示例代码:

1
./zkServer.sh status

运行结果:

1
2
3
4
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.7.1-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
  • Mode为follower表示是 跟随者 (从)
  • Mode为leader表示是 领导者 (从)

集群异常

  • 三台服务,如果只挂了一台从服务,集群正常。
  • 三台服务,如果挂了两台从服务,主服务也无法运行,因为可运行的机器没有超过集群总数量的半数。
  • 当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader。

(具体实验过程略)

问题处理

名字带有"bin"的包

3.5版本以后的包分成了两种,我们需要使用名字带有"bin"的包,例如:apache-zookeeper-3.7.1-bin.tar.gz

问题排查

如果启动失败,我们可以通过命令./zkServer.sh start-foreground,查看日志的报错详细信息,这样有助于我们判断错误的原因。

示例代码:

1
./zkServer.sh start-foreground

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

【部分运行结果略】

2022-12-22 08:37:06,839 [myid:] - ERROR [main:ZooKeeperServerMain@86] - Unable to start AdminServer, exiting abnormally
org.apache.zookeeper.server.admin.AdminServer$AdminServerException: Problem starting AdminServer on address 0.0.0.0, port 8080 and command URL /commands
at org.apache.zookeeper.server.admin.JettyAdminServer.start(JettyAdminServer.java:188)
at org.apache.zookeeper.server.ZooKeeperServerMain.runFromConfig(ZooKeeperServerMain.java:155)
at org.apache.zookeeper.server.ZooKeeperServerMain.initializeAndRun(ZooKeeperServerMain.java:113)
at org.apache.zookeeper.server.ZooKeeperServerMain.main(ZooKeeperServerMain.java:68)
at org.apache.zookeeper.server.quorum.QuorumPeerMain.initializeAndRun(QuorumPeerMain.java:141)
at org.apache.zookeeper.server.quorum.QuorumPeerMain.main(QuorumPeerMain.java:91)
Caused by: java.io.IOException: Failed to bind to /0.0.0.0:8080
at org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:349)
at org.eclipse.jetty.server.ServerConnector.open(ServerConnector.java:310)
at org.eclipse.jetty.server.AbstractNetworkConnector.doStart(AbstractNetworkConnector.java:80)
at org.eclipse.jetty.server.ServerConnector.doStart(ServerConnector.java:234)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
at org.eclipse.jetty.server.Server.doStart(Server.java:401)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
at org.apache.zookeeper.server.admin.JettyAdminServer.start(JettyAdminServer.java:179)
... 5 more
Caused by: java.net.BindException: Address already in use
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:438)
at sun.nio.ch.Net.bind(Net.java:430)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:225)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:74)
at org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:344)
... 12 more
Unable to start AdminServer, exiting abnormally
2022-12-22 08:37:06,840 [myid:] - INFO [main:ZKAuditProvider@42] - ZooKeeper audit is disabled.
2022-12-22 08:37:06,841 [myid:] - ERROR [main:ServiceUtils@48] - Exiting JVM with code 4

这是因为3.5版本以后,都会自动把8080端口给占用了,所以启动不成功。

  • 可以为"admin"指定端口,在zoo.cfg添加admin.serverPort=10086
  • 可以直接禁用"admin",在zoo.cfg添加admin.enableServer=false

在Windows下

在Windows下,通过命令zkServer.cmd start可能会报错

1
java.lang.NumberFormatException: For input string:

原因暂时未知,解决方法为修改命令为zkServer.cmd,不加 start

入门案例

《18.SSM》的项目为例,将其改造成Dubbo形式的。

Service接口

我们将Service接口层抽成一个单独的jar包,因为之后我们在Provider和Consumer都会用到该接口。
抽成单独的jar包,这样我们在Provider和Consumer只需要引入jar包,而不必重复编码。

具体抽取步骤略,可以参考《18.SSM》的"模块化"部分。

Provider

pom.xml

  • 需要引入Dubbo的包:org.apache.dubbo.dubbo
  • 以及Zookeeper的客户端:
    • org.apache.curator.curator-framework
    • org.apache.curator.curator-recipes

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.19</version>
</dependency>

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.4.0</version>
</dependency>

<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.4.0</version>
</dependency>

在本文中,我们需要将服务生产者打成一个war包,所以

1
<packaging>war</packaging>

DubboConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.kakawanyifan.config;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;

@DubboComponentScan("com.kakawanyifan.service.impl")
public class DubboConfig {

@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("ssm-provider");
return applicationConfig;
}

@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181");
return registryConfig;
}

@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20800);
return protocolConfig;
}
}
  • 如果是Zookeeper不是集群,地址形如zookeeper://172.23.2.79:2181,即不配置备节点。

我们还需要在SpringConfig中ImportDubboConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kakawanyifan.config;

import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(value = "com.kakawanyifan",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = Controller.class
))
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class, DubboConfig.class})
@EnableTransactionManagement
public class SpringConfig {

}

@DubboService

将Service的实现类,抽出来。注意,这里用的注解是@DubboService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.pojo.Book;
import com.kakawanyifan.service.BookService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
@DubboService
@Transactional
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;

public boolean save(Book book) {
bookDao.save(book);
return true;
}

public boolean update(Book book) {
bookDao.update(book);
return true;
}

public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}

public Book getById(Integer id) {
return bookDao.getById(id);
}

public List<Book> getAll() {
return bookDao.getAll();
}
}

XML方式

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>

applicationContext.xml

  • xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
  • http://dubbo.apache.org/schema/dubbo
  • https://dubbo.apache.org/schema/dubbo/dubbo.xsd
    • 注意,此处是https,而不是http
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://dubbo.apache.org/schema/dubbo
https://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!--配置注解扫描-->
<context:component-scan base-package="com.kakawanyifan">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<context:property-placeholder location="classpath:jdbc.properties"/>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<bean id="SqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>

<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.kakawanyifan.dao"></property>
</bean>

<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 事务增强配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- 事务的AOP增强 -->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.kakawanyifan.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

<!-- 使用dubbo发布服务 -->
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="ssm-provider" />
<dubbo:registry address="zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:annotation package="com.kakawanyifan.service.impl" />

</beans>

内容很多,重点关注:

1
2
3
4
5
6
7
<!-- 使用dubbo发布服务 -->
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="ssm-provider" />
<dubbo:registry address="zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:annotation package="com.kakawanyifan.service.impl" />

Consumer

pom.xml

需要引入的jar包和Provider一样,打包方式也一样。

DubboConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.kakawanyifan.config;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@DubboComponentScan("com.kakawanyifan.controller")
public class DubboConfig {

@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("ssm-consumer");
return applicationConfig;
}

@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181");
return registryConfig;
}

}

同样,我们需要在SpringMvcConfig中ImportDubboConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(value = {"com.kakawanyifan.controller"})
@Import(DubboConfig.class)
@EnableWebMvc
public class SpringMvcConfig {

}

@DubboReference

将Controller抽出来。注意,这里用的注解是@DubboReference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.kakawanyifan.controller;

import com.kakawanyifan.pojo.Book;
import com.kakawanyifan.service.BookService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/books")
public class BookController {

@DubboReference
private BookService bookService;

@PostMapping
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}

@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}

@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return bookService.delete(id);
}

@GetMapping("/{id}")
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}

@GetMapping
public List<Book> getAll() {
return bookService.getAll();
}
}

XML方式

spring-mvc.xml:

  • xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
  • http://dubbo.apache.org/schema/dubbo
  • https://dubbo.apache.org/schema/dubbo/dubbo.xsd
    • 注意,此处是https,而不是http
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://dubbo.apache.org/schema/dubbo
https://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!--配置注解扫描-->
<context:component-scan base-package="com.kakawanyifan.controller"/>
<mvc:annotation-driven/>

<!-- 引用dubbo服务 -->
<dubbo:application name="ssm-consumer"/>
<dubbo:registry address="zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181" />
<dubbo:annotation package="com.kakawanyifan.controller"/>

</beans>

配置

启动时检查

1
<dubbo:consumer check="false"/>

该配置需要配置在服务消费者一方,Dubbo会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成。默认为true

包扫描

在上文,我们使用的是包扫描的方式。

1
@DubboComponentScan("com.kakawanyifan.service.impl")
1
<dubbo:annotation package="com.kakawanyifan.service.impl" />

如果不使用包扫描,也可以通过如下配置的方式来发布服务:

1
2
<bean id="helloService" class="com.kakawanyifan.service.impl.HelloServiceImpl" />
<dubbo:service interface="com.kakawanyifan.service.HelloService" ref="helloService" />
1
<dubbo:reference interface="com.kakawanyifan.service.HelloService" id="helloService"/>

如果有多个服务,这种方式就比较繁琐了。推荐使用包扫描方式。

协议

在上文,我们使用的都是Dubbo协议。

1
2
3
4
5
6
7
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20800);
return protocolConfig;
}
1
<dubbo:protocol name="dubbo" port="20880"/>

Dubbo框架支持很多协议,有:dubbormihessianhttpwebservicerestredis等。
但是一般在工作中,通常我们只会使用其duboo协议。需要注意的是,该协议的使用场景:

  1. 传入传出参数数据包较小(建议小于100K)。
  2. 不要用dubbo协议传输大文件或超大字符串。
  3. 消费者比提供者个数多。

如果涉及到,传入传出参数数据包较大,提供者比消费者个数多。可以考虑hessian

在最新的Dubbo3中,还支持Triple协议,这也是Dubbo3推出的主力协议。

我们在同一个工程中配置多个协议,不同服务可以使用不同的协议,例如:

1
2
3
4
5
6
7
<!-- 多协议配置 -->
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:protocol name="rmi" port="1099" />
<!-- 使用dubbo协议暴露服务 -->
<dubbo:service interface="com.kakawanyifan.service.HelloService" ref="helloService" protocol="dubbo" />
<!-- 使用rmi协议暴露服务 -->
<dubbo:service interface="com.kakawanyifan.service.DemoService" ref="demoService" protocol="rmi" />

超时与重试

配置方法

可以消费者这一端进行配置。

全局配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.kakawanyifan.config;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;

@DubboComponentScan("com.kakawanyifan.controller")
public class DubboConfig {

【部分代码略】

@Bean
public ConsumerConfig consumerConfig(){
ConsumerConfig consumerConfig = new ConsumerConfig();
consumerConfig.setTimeout(3000);
consumerConfig.setRetries(3);
return consumerConfig;
}

}

分别配置:

1
@DubboReference(timeout = 3000,retries = 3)

可以在生产者这一端进行配置。
全局配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.kakawanyifan.config;

import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;

@DubboComponentScan("com.kakawanyifan.service.impl")
public class DubboConfig {

【部分代码略】

@Bean
public ProviderConfig providerConfig(){
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.setTimeout(3000);
providerConfig.setRetries(3);
return providerConfig;
}
}

分别配置:

1
@DubboService(timeout = 3000,retries = 3)

分别配置的优先级比全局配置的优先级更高。

XML方式

1
<dubbo:consumer timeout="3000" retries="3"></dubbo:consumer>
1
<dubbo:provider timeout="3000" retries="3"></dubbo:provider>
1
2
<bean id="helloService" class="com.kakawanyifan.service.impl.HelloServiceImpl" />
<dubbo:service interface="com.kakawanyifan.service.HelloService" ref="helloService" timeout="3000" retries="3"/>
1
<dubbo:reference interface="com.kakawanyifan.service.HelloService" id="helloService"  timeout="3000" retries="3" />

多版本

配置关键词version,例如:

1
@DubboReference(version = "1.0")
1
@DubboService(version = "1.0")

负载均衡

Dubbo的负载均衡策略有4种:

  • Random:按权重随机,默认值。按权重设置随机概率。
  • RoundRobin:按权重轮询。
  • LeastActive:最少活跃调用数,相同活跃数的随机。
  • ConsistentHash:一致性Hash,相同参数的请求总是发到同一提供者。

负载均衡策略的关键词是loadbalance,例如:

1
@DubboReference(loadbalance = LoadbalanceRules.RANDOM)

权重的关键词是weight,例如:

1
@DubboService(weight = 100)

集群容错

配置关键词:cluster,例如:

1
@DubboReference(cluster = ClusterRules.FAIL_OVER)

集群容错模式:

  • failover:失败重试。默认值。当出现失败,重试其它服务器 ,默认重试2次,使用retries配置,一般用于读操作。
  • failfast:快速失败,只发起一次调用,失败立即报错。通常用于写操作。
  • failsafe:失败安全,出现异常时,直接忽略。返回一个空结果。
  • failback:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  • forking:并行调用多个服务器,只要一个成功即返回。
  • broadcast:广播调用所有提供者,逐个调用,任意一台报错则报错。

dubbo-admin

简介

正如我们上文的Dubbo架构图,有一部分是Monitor,即监控中心。但是Dubbo官方也表示Monitor不够好。所以我们这里讨论的是另外一个监控中心,dubbo-admin。

dubbo-admin,是图形化的服务管理,从注册中心中获取到所有的提供者/消费者进行配置管理,有:路由规则、动态配置、服务降级、访问控制、权重调整、负载均衡等管理功能。

部署

下载dubbo-admin

修改配置文件

dubbo-admin/dubbo-admin-server/src/main/resources目录下,找到application.properties

将如下的三个地址,改成我们自己的地址

1
2
3
admin.registry.address=zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181
admin.config-center=zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181
admin.metadata-report.address=zookeeper://39.101.64.102:2181?backup=8.130.39.106:2181,8.130.47.203:2181

打包并启动

在配置完成后,我们通过maven进行打包。

根据maven的规则,在打包之前都会进行测试。我们可以通过跳过测试。
具体跳过测试的方法,我们在[《18.SSM》]有过讨论。

打包完成之后,我们会在dubbo-admin-servertarget目录找到dubbo-admin-server-0.5.0-SNAPSHOT.jar

1
nohup java -jar dubbo-admin-server-0.5.0-SNAPSHOT.jar &

访问http://localhost:38080,用户名密码都是root

有些资料说前端需要单独打包,单独启动,其实并不需要。

2.7版本的Dubbo确实是前后端分离的项目,但是并不意味着部署的时候,也需要前后端分离。

使用

如图,是我们发布的服务。

服务查询

我们可以查看服务详情。

服务详情

还可以直接对服务进行测试。

服务测试

Q&A

两个机器传输数据,如何传输Java对象?

dubbo内部已经将序列化和反序列化的过程内部封装了,我们只需要在定义pojo类时实现Serializable接口即可。
一般会定义一个公共的pojo模块,让生产者和消费者都依赖该模块。

如果注册中心集群都挂掉,发布者和订阅者之间还能通信么?

可以通信的,启动dubbo时,消费者会从zookeeper拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。但如果要调用新的服务,不能通信。

dubbo通信协议dubbo协议为什么不能传大包?

因为dubbo协议采用单一长连接,传大包的话,容易导致网络传输方面成为瓶颈。

例如,包有20MB,网速1MB每秒,那么可能会阻塞后续连接。

dubbo通信协议dubbo协议为什么采用异步单一长连接?

因为dubbo的应用场景大都是服务提供者少,服务的消费者多。
通过单一连接,保证单一消费者不会压垮提供者;长连接,减少连接握手验证等。

文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/10820
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区