Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 分布式服务框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。她最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo 采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。
节点角色说明
节点
角色说明
Provider
暴露服务的服务提供方
Consumer
调用远程服务的服务消费方
Registry
服务注册与发现的注册中心
Monitor
统计服务的调用次数和调用时间的监控中心
Container
服务运行容器
调用关系说明
父工程ApacheDubbo的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.qiang</groupId>
<artifactId>ApacheDubbo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>apache-dubbo-dependencies</module>
<module>apache-dubbo-provider</module>
<module>apache-dubbo-consumer</module>
</modules>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.qiang</groupId>
<artifactId>apache-dubbo-dependencies</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring-javaformat.version>0.0.12</spring-javaformat.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-javaformat.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/Abstract*.java</exclude>
</excludes>
<systemPropertyVariables>
<java.security.egd>file:/dev/./urandom</java.security.egd>
<java.awt.headless>true</java.awt.headless>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-rules</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>commons-logging:*:*</exclude>
</excludes>
<searchTransitive>true</searchTransitive>
</bannedDependencies>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
<inherited>true</inherited>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
统一依赖管理apache-dubbo-dependencies
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qiang</groupId>
<artifactId>apache-dubbo-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<dubbo.version>2.7.3</dubbo.version>
<dubbo-actuator.version>2.7.1</dubbo-actuator.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
<spring-cloud-alibaba.verion>2.1.0.RELEASE</spring-cloud-alibaba.verion>
<alibaba-spring-context-support.version>1.0.2</alibaba-spring-context-support.version>
<guava.version>15.0</guava.version>
</properties>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.verion}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
<version>${dubbo-actuator.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
<version>${alibaba-spring-context-support.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestone</id>
<name>Spring Milestone</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshot</id>
<name>Spring Snapshot</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
由于我们已经有了 Nacos 注册中心,Sentinel 熔断限流控制中心,所以我们不再使用 Zookeeper 和 Dubbo Admin 来管理我们的 Dubbo 应用程序,Dubbo 仅当作我们微服务中的 RPC 通信框架,真正实现对内 RPC,对外 REST。
服务提供者apache-dubbo-provider
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qiang</groupId>
<artifactId>ApacheDubbo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>apache-dubbo-provider</artifactId>
<packaging>pom</packaging>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<modules>
<!-- 专门暴露接口的模块 -->
<module>apache-dubbo-provider-api</module>
<!-- 专门实现接口的模块 -->
<module>apache-dubbo-provider-service</module>
</modules>
</project>
创建服务提供者接口模块apache-dubbo-provider-api
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qiang</groupId>
<artifactId>apache-dubbo-provider</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>apache-dubbo-provider-api</artifactId>
<packaging>jar</packaging>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
</project>
定义接口
package com.qiang.apache.dubbo.provider.api;
/**
* @author: 小强崽
* @create: 2020-08-29 07:07
* @description: 定义接口
*/
public interface EchoService {
/**
* 输出字符串
* @param string
* @return
*/
String echo(String string);
}
创建服务提供者接口实现apache-dubbo-provider-service
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qiang</groupId>
<artifactId>apache-dubbo-provider</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>apache-dubbo-provider-service</artifactId>
<packaging>jar</packaging>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>com.qiang</groupId>
<artifactId>apache-dubbo-provider-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>
<!-- sentinel数据持久化到nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>compile</scope>
</dependency>
<!-- 这里只是引入springcloud包作为nacos和sentinel连接的桥梁 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.qiang.apache.dubbo.provider.ProviderApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
主要增加了 Dubbo 包扫描路径和 Nacos Server 配置。
spring:
application:
name: dubbo-provider
main:
allow-bean-definition-overriding: true
dubbo:
scan:
base-packages: com.qiang.apache.dubbo.provider.service
protocol:
name: dubbo
port: 20880
registry:
address: nacos://172.25.0.12:8848
provider:
loadbalance: roundrobin
Application
package com.qiang.apache.dubbo.provider;
import com.qiang.apache.dubbo.provider.configuration.DubboSentinelConfiguration;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author: 小强崽
* @create: 2020-08-29 07:16
* @description:
*/
@SpringBootApplication(scanBasePackageClasses = {EchoServiceFallback.class, DubboSentinelConfiguration.class})
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
Service
package com.qiang.apache.dubbo.provider.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;
/**
* @author: 小强崽
* @create: 2020-08-29 07:18
* @description:
*/
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {
@Value("${dubbo.protocol.port}")
private String port;
@Override
@SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
public String echo(String string) {
if ("hello".equals(string)){
throw new IllegalArgumentException("invalid arg");
}
return "Echo Hello Dubbo " + string + " i am from port: " + port;
}
}
验证是否成功
访问http://172.25.0.12:8848/nacos
apache-dubbo-consumer
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qiang</groupId>
<artifactId>ApacheDubbo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>apache-dubbo-consumer</artifactId>
<packaging>jar</packaging>
<licenses>
<license>
<name>Apache 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.spring</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>com.qiang</groupId>
<artifactId>apache-dubbo-provider-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.qiang.apache.dubbo.consumer.ConsumerApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
application.yml
主要增加了 Dubbo 包扫描路径、健康检查以及 Nacos Server 配置。
spring:
application:
name: dubbo-consumer
main:
allow-bean-definition-overriding: true
dubbo:
scan:
base-packages: com.qiang.apache.dubbo.consumer.controller
protocol:
name: dubbo
port: -1
registry:
address: nacos://172.25.0.12:8848
server:
port: 8080
endpoints:
dubbo:
enabled: true
management:
health:
dubbo:
status:
defaults: memory
extras: threadpool
endpoints:
web:
exposure:
include: "*"
Application
package com.qiang.apache.dubbo.consumer;
/**
* @author: 小强崽
* @create: 2020-08-30 01:21
* @description:
*/
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
Controller
通过 org.apache.dubbo
包下的 @Reference
注解像调用本地服务一样调用远程服务,轻松实现透明的远程过程调用。
package com.qiang.apache.dubbo.consumer.controller;
import com.qiang.apache.dubbo.provider.api.EchoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: 小强崽
* @create: 2020-08-30 01:23
* @description:
*/
@RestController
public class EchoController {
@Reference(version = "1.0.0")
private EchoService echoService;
@GetMapping(value = "/echo/{string}")
public String echo(@PathVariable String string) {
return echoService.echo(string);
}
}
验证是否成功
访问http://172.25.0.12:8848/nacos
Echo Hello Dubbo hi i am from port: 20880
服务端点检查
http://localhost:8080/actuator/health
{
"status": "UP"
}
修改 dubbo-provider
项目的负载均衡策略,默认的负载均衡策略是 随机,我们修改为 轮循,可配置的值分别是:random
,roundrobin
,leastactive
,consistenthash
dubbo:
provider:
loadbalance: roundrobin
修改 dubbo-provider
的协议端口为 20880 和 20881,并启动多个实例,IDEA 中依次点击 Run -> Edit Configurations 并勾选 Allow parallel run 以允许 IDEA 多实例运行项目。
Nacos Server 控制台可以看到 dubbo-provider
有 2 个实例。
修改 dubbo-provider
项目的 EchoServiceImpl
中的测试方法。
package com.qiang.apache.dubbo.provider.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;
/**
* @author: 小强崽
* @create: 2020-08-29 07:18
* @description:
*/
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {
@Value("${dubbo.protocol.port}")
private String port;
@Override
@SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
public String echo(String string) {
if ("hello".equals(string)){
throw new IllegalArgumentException("invalid arg");
}
return "Echo Hello Dubbo " + string + " i am from port: " + port;
}
}
重启服务,通过浏览器访问 http://localhost:8080/echo/hi ,反复刷新浏览器,浏览器交替显示。
我们以 dubbo-consumer 项目为例,修改 pom.xml ,引入 Nacos Config Starter。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
Controller
完成上述两步后,应用会从 Nacos Config 中获取相应的配置,并添加在 Spring Environment 的 PropertySources 中。这里我们使用 @Value
注解来将对应的配置注入到 EchoController
的 username
字段,并添加 @RefreshScope
打开动态刷新功能。
package com.qiang.apache.dubbo.consumer.controller;
import com.qiang.apache.dubbo.provider.api.EchoService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: 小强崽
* @create: 2020-08-30 01:23
* @description:
*/
@RefreshScope
@RestController
public class EchoController {
@Reference(version = "1.0.0")
private EchoService echoService;
@GetMapping(value = "/echo/{string}")
public String echo(@PathVariable String string) {
return echoService.echo(string);
}
}
使用控制台发布配置。
spring:
application:
name: dubbo-consumer
main:
allow-bean-definition-overriding: true
dubbo:
scan:
base-packages: com.qiang.apache.dubbo.consumer.controller
protocol:
name: dubbo
port: -1
# 高速序列化
serialization: kryo
registry:
address: nacos://172.25.0.12:8848
server:
port: 8080
endpoints:
dubbo:
enabled: true
management:
health:
dubbo:
status:
defaults: memory
extras: threadpool
endpoints:
web:
exposure:
include: "*"
user:
name: "傻狗"
修改客户端配置
创建名为 bootstrap.properties
的配置文件并删除之前创建的 application.yml
配置文件。
spring.application.name=dubbo-consumer-config
spring.cloud.nacos.config.server-addr=172.25.0.12:8848
spring.cloud.nacos.config.file-extension=yaml
通过浏览器访问 http://localhost:8080/echo/hi ,浏览器输出如下。
Echo Hello Dubbo hi i am from port: -1 傻狗
动态刷新配置
在 Nacos Server 控制台修改配置文件,将 user.name
属性修改为 大傻狗
,此时观察控制台日志,你会发现我们已经成功刷新了配置。
验证是否成功
通过浏览器访问 http://localhost:8080/echo/hi ,浏览器输出如下。
Echo Hello Dubbo hi i am from port: -1 大傻狗
可以使用 spring.cloud.nacos.config.refresh.enabled=false
来关闭动态刷新。
docker-compose.yaml
version: '3.0'
services:
sentinel:
image: 172.25.0.15/xiaoqiangzai/bladex/sentinel-dashboard:latest
container_name: sentinel
network_mode: "host"
restart: always
privileged: true
ports:
- '8858:8858'
environment:
- auth.username=sentinel
- auth.password=sentinel
jar包
https://github.com/alibaba/Sentinel/releases
启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中 -Dserver.port=8080
用于指定 Sentinel 控制台端口为 8080
,从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的 登录 功能,默认用户名和密码都是 sentinel。
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为 sentinel。-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel。-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟。使用时需要引入 3 个依赖。
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-apache-dubbo-adapter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>
依赖说明
sentinel-apache-dubbo-adapter
:Sentinel 提供的 Apache Dubbo 适配模块 (注意:sentinel-dubbo-adapter
是未毕业版本的 Dubbo 适配模块)。sentinel-transport-simple-http
: 用于暴露一个特定的端口,Sentinel Dashboard 通过 HTTP 的形式进行数据推送,客户端接收后将规则保存在本地内存中。sentinel-annotation-aspectj
:Sentinel 提供了 @SentinelResource
注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException
等。在启动时,需要在 JVM 中添加以下启动参数。
-Djava.net.preferIPv4Stack=true
-Dcsp.sentinel.api.port=8720
-Dproject.name=dubbo-provider
-Dcsp.sentinel.dashboard.server=172.25.0.12:8858
参数说明
-Dcsp.sentinel.api.port=客户端端口,用于上报信息,默认 8720 即可,Sentinel 发现端口冲突会自动递增
-Dproject.name=显示在控制台上的应用名称
-Dcsp.sentinel.dashboard.server=控制台地址
我们需要使用 Spring AOP 的方式显示的注册 SentinelResourceAspect
为一个 Bean。
package com.qiang.apache.dubbo.provider.configuration;
import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: 小强崽
* @create: 2020-08-30 02:29
* @description: 仅 Dubbo 服务需要该配置,Spring Cloud Alibaba 无需加载该配置
*/
@Configuration
public class DubboSentinelConfiguration {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
加载配置
配置单独封装至 configuration-sentinel
项目中,方便其它 Dubbo 服务依赖,所有 Dubbo 服务提供者都需要调用该模块并指定加载配置类,修改 Application
代码如下。
package com.qiang.apache.dubbo.provider;
import com.qiang.apache.dubbo.provider.configuration.DubboSentinelConfiguration;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author: 小强崽
* @create: 2020-08-29 07:16
* @description:
*/
@SpringBootApplication(scanBasePackageClasses = {EchoServiceFallback.class, DubboSentinelConfiguration.class})
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
可以为需要熔断降级和限流的方法创建一个专门的熔断类,在需要使用熔断和限流的方法上使用 @SentinelResource
注解指定该类中的方法即可实现熔断降级功能。
注意: 熔断方法必须是 static
函数。
package com.qiang.apache.dubbo.provider.service.fallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author: 小强崽
* @create: 2020-08-30 02:32
* @description: 服务熔断后处理的方法
*/
public class EchoServiceFallback {
private static final Logger logger = LoggerFactory.getLogger(EchoServiceFallback.class);
public static String getEchoServiceFallback(String username, Throwable ex) {
logger.warn("Invoke getEchoServiceFallback: " + ex.getClass().getTypeName());
ex.printStackTrace();
return "服务熔断";
}
}
package com.qiang.apache.dubbo.provider.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;
/**
* @author: 小强崽
* @create: 2020-08-29 07:18
* @description: 熔断器的使用
* {@link SentinelResource#value()} 对应的是 Sentinel 控制台中的资源,可用作控制台设置【流控】和【降级】操作。
* {@link SentinelResource#fallback()} 对应的是 {@link EchoServiceFallback#getEchoServiceFallback(String, Throwable)},并且必须为 `static`。
* 如果不设置 {@link SentinelResource#fallbackClass()},则需要在当前类中创建一个 `Fallback` 函数,函数签名与原函数一致或加一个 {@link Throwable} 类型的参数。
*/
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {
@Value("${dubbo.protocol.port}")
private String port;
@Override
@SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
public String echo(String string) {
if ("hello".equals(string)){
throw new IllegalArgumentException("invalid arg");
}
return "Echo Hello Dubbo " + string + " i am from port: " + port;
}
}
在 Sentinel 控制台配置流控规则,需要至少调用一次服务,才能在控制台看到监控效果。
流控规则说明
@SentinelResource
注解中的 value
属性。排队等待模式下 QPS 不要超过 1000(请求间隔 1 ms)。
在出现异常时会自动启动熔断,增加测试代码如下。
package com.qiang.apache.dubbo.provider.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.qiang.apache.dubbo.provider.api.EchoService;
import com.qiang.apache.dubbo.provider.service.fallback.EchoServiceFallback;
import org.apache.commons.io.FileExistsException;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Value;
/**
* @author: 小强崽
* @create: 2020-08-29 07:18
* @description:
*/
@Service(version = "1.0.0")
public class EchoServiceImpl implements EchoService {
@Value("${dubbo.protocol.port}")
private String port;
@Override
@SentinelResource(value = "getByUsername", fallback = "getEchoServiceFallback", fallbackClass = EchoServiceFallback.class)
public String echo(String string) {
if ("hello".equals(string)){
throw new IllegalArgumentException("invalid arg");
}
return "Echo Hello Dubbo " + string + " i am from port: " + port;
}
}
触发异常时,控制台 (dubbo-provider
) 打印日志如下。
java.lang.IllegalArgumentException: invalid arg
at com.qiang.apache.dubbo.provider.service.EchoServiceImpl.echo(EchoServiceImpl.java:25)
at com.qiang.apache.dubbo.provider.service.EchoServiceImpl$$FastClassBySpringCGLIB$$9193b731.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)
at com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect.invokeResourceWithSentinel(SentinelResourceAspect.java:57)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.qiang.apache.dubbo.provider.service.EchoServiceImpl$$EnhancerBySpringCGLIB$$555ba878.echo(<generated>)
at org.apache.dubbo.common.bytecode.Wrapper0.invokeMethod(Wrapper0.java)
at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:47)
at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:84)
at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:56)
at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
at org.apache.dubbo.rpc.filter.ExceptionFilter.invoke(ExceptionFilter.java:55)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at com.alibaba.csp.sentinel.adapter.dubbo.SentinelDubboProviderFilter.invoke(SentinelDubboProviderFilter.java:71)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:92)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.filter.TimeoutFilter.invoke(TimeoutFilter.java:48)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.protocol.dubbo.filter.TraceFilter.invoke(TraceFilter.java:81)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.filter.ContextFilter.invoke(ContextFilter.java:96)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.filter.GenericFilter.invoke(GenericFilter.java:148)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.filter.ClassLoaderFilter.invoke(ClassLoaderFilter.java:38)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.filter.EchoFilter.invoke(EchoFilter.java:41)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:82)
at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$CallbackRegistrationInvoker.invoke(ProtocolFilterWrapper.java:157)
at org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol$1.reply(DubboProtocol.java:152)
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.handleRequest(HeaderExchangeHandler.java:102)
at org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.received(HeaderExchangeHandler.java:193)
at org.apache.dubbo.remoting.transport.DecodeHandler.received(DecodeHandler.java:51)
at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
限流效果
com.alibaba.csp.sentinel.slots.block.flow.FlowException
2020-08-31 04:30:01.853 WARN 10932 --- [:20881-thread-7] c.q.a.d.p.s.f.EchoServiceFallback : Invoke getEchoServiceFallback: com.alibaba.csp.sentinel.slots.block.flow.FlowException
2020-08-31 04:30:46.078 WARN 10932 --- [:20881-thread-8] c.q.a.d.p.s.f.EchoServiceFallback : Invoke getEchoServiceFallback: java.lang.IllegalArgumentException
分布式是促使 RPC 诞生的领域,RPC 是一种编程模型,并没有规定你具体要怎样实现,无论使用 HTTP 或是 RMI 都是可以的。
假设你有一个计算器接口,Calculator,以及它的实现类 CalculatorImpl,那么在系统还是 单体应用 时,你要调用 Calculator 的 add 方法来执行一个加运算,直接 new 一个 CalculatorImpl,然后调用 add 方法就行了,这其实就是非常普通的 本地函数调用,因为在 同一个地址空间,或者说在同一块内存,所以通过方法栈和参数栈就可以实现。
现在,基于高性能和高可靠等因素的考虑,你决定将系统改造为分布式应用,将很多可以共享的功能都单独拎出来,比如上面说到的计算器,你单独把它放到一个服务里头,让别的服务去调用它。
这下问题来了,服务 A 里头并没有 CalculatorImpl 这个类,那它要怎样调用服务 B 的 CalculatorImpl 的 add 方法呢?
RPC 要解决的两个问题
实际情况下,RPC 很少用到 HTTP 协议来进行数据传输,毕竟我只是想传输一下数据而已,何必动用到一个文本传输的应用层协议呢,我为什么不直接使用二进制传输?比如直接用 Java 的 Socket 协议进行传输?
不管你用何种协议进行数据传输,一个完整的 RPC 过程,都可以用下面这张图来描述。
以左边的 Client 端为例,Application 就是 RPC 的调用方,Client Stub 就是我们上面说到的代理对象,也就是那个看起来像是 Calculator 的实现类,其实内部是通过 RPC 方式来进行远程调用的代理对象,至于 Client Run-time Library,则是实现远程调用的工具包,比如 JDK 的 Socket,最后通过底层网络实现实现数据的传输。
这个过程中最重要的就是 序列化 和 反序列化 了,因为数据传输的数据包必须是二进制的,你直接丢一个 Java 对象过去,人家可不认识,你必须把 Java 对象序列化为二进制格式,传给 Server 端,Server 端接收到之后,再反序列化为 Java 对象。
RPC 是面向过程,Restful 是面向资源,并且使用了 HTTP 动词。从这个维度上看,Restful 风格的 URL 在表述的精简性、可读性上都要更好。
CAP
有个思考,从 CAP 角度考虑,服务注册中心是 CP 系统还是 AP 系统呢?
调用 node1 的服务,顶多就是负载均衡时不会有流量打到 ip3,然后等 node1 同步回 ip3 后,又一致了,这对服务其实没什么太大影响。所以,推测出服务注册中心应该是个 AP 系统。
Zookeeper 是个 CP 系统,强一致性
Zookeeper 是通过 TCP 的心跳判断服务是否可用,但 TCP 的活性并不代表服务是可用的,如:连接池已满,DB 挂了等。
理想的注册中心
问题:
java.lang.NoClassDefFoundError: com/alibaba/nacos/client/naming/utils/StringUtils
at org.apache.dubbo.registry.nacos.NacosRegistryFactory.putPropertyIfAbsent(NacosRegistryFactory.java:104) ~[dubbo-2.7.2.jar:2.7.2]
at org.apache.dubbo.registry.nacos.NacosRegistryFactory.setProperties(NacosRegistryFactory.java:94) ~[dubbo-2.7.2.jar:2.7.2]
at org.apache.dubbo.registry.nacos.NacosRegistryFactory.buildNacosProperties(NacosRegistryFactory.java:73) ~[dubbo-2.7.2.jar:2.7.2]
解决:
这里版本号nacos跟dubbo冲突,修改dubbo的版本即可
<dubbo.version>2.7.3</dubbo.version>
作者(Author):小强崽
来源(Source):https://www.wuduoqiang.com/archives/ApacheDubbo
协议(License):署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
版权(Copyright):商业转载请联系作者获得授权,非商业转载请注明出处。 For commercial use, please contact the author for authorization. For non-commercial use, please indicate the source.
手机扫一扫
移动阅读更方便
你可能感兴趣的文章