本文主要介绍了阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

网上很多关于杜博非正常统一处理的博文,90%都是照搬照抄的。大部分都是先在dubbo统一处理异常的原代码,然后一堆(甚至12345,五个)不靠谱的方案,最后ldquo本文使用方案4;,然后写了一段所谓的方案四,最后说不清!!!

这个解决方案不会那么啰嗦,不会贴dubbo源码来凑字数。我来解释一下这个dubbo统一处理异常是如何在刚刚结束的双11保卫战性能全链路优化中,直接从我们面向10万TPS的解决方案中抽取代码进行处理的!


1. 为什么要抛异常?

不同的开发团队很容易追踪异常的来源和定位问题的需要。

实际开发中的架构通常是这样的:


dubbo微服务架构示意图

不同层的开发者是不同的人或者不同的人群;

无状态API层(一组Tomcat的API暴露给Nginx Web层)是一组开发团队;

微服Dubbo层是另一组开发团队;

在调试、测试、上线之后,我们经常会出现各种各样的异常。这时候这些不同的开发团队就会互相扯皮,互相争斗,大家都要忙着定位这个异常发生的楼层,甚至需要追溯异常发生的stackTrace。

服务层中数据库事务的一致性问题必须抛出异常。

我们都知道spring中的服务层必须抛出运行时异常,否则,如果有涉及数据库的修改,服务层的方法不会回滚。


2. 给出解决方案

事实上,有两种解决方案:

provider向远程consumer层直接抛RuntimeException即可;provider端把所有的Exception进行统一包装,向consumer端返回json报文体的类似message:xxx,code:500,data{xxx:xxx,xxx:xxx}这样的消息而在provider端进行ldquo;logger.errorrdquo;的记录即可;

本文实现了这两种方式。直接通过给我看代码来说吧。


3. 两种抛异常的实例解说

环境建设

nacos1.1.4

我们这里不用dubbo admin,因为dubbo admin太老了,用起来不方便,缺少很多管理微服务所需的基本功能。而dubbo从2.6开始就将dubbo admin从其主项目中分离出来,dubbo2.6开始支持nacos注册表。

目前nacos是最方便、最高效、最强大的微服务发现组件(甚至支持spring cloud)。

下载地址在这里(请盖章):阿里nacos最新下载地址。

下载后直接解压,然后配置nacos。


编辑这个application.properties文件,我们在自己的开发环境中将nacos自动服务发现管理终端连接到mysql。

# springspring.datasource.platform=mysqlserver.contextPath=/nacosserver.servlet.contextPath=/nacosserver.port=8848db.num=1db.url.0=jdbc:mysql://192.168.56.101:3306/nacosuseUnicode=truecharacterEncoding=utf-8useSSL=falsedb.user=nacosdb.password=111111

配置完成后,双击startup.cmd启动nacos。


在登录界面使用nacos/nacos进行登录。


登录后可以看到nacos管理界面,说明nacos配置和启动成功。接下来,我们将开始编写dubbo的提供者端和消费者端。

杜博工程建设公司

nacos-父项目

我已经把整个项目放在git上。请戳这里地址:nacos-dubbo-demo

项目的依赖结构如下:


由于dubbo和springboot结合的项目并不多,而且网上很多博客充斥着未经验证的随意复制加载代码,大部分网友通过网上的文字和短语拼凑出来的项目很难在本地运行,不是maven包冲突就是这个或那个包缺失。项目的父pom文件如下所示。


lt;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 https://maven.apache.org/xsd/maven-4.0.0.xsd"gt;lt;modelVersiongt;4.0.0lt;/modelVersiongt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;nacos-parentlt;/artifactIdgt;lt;versiongt;0.0.1-SNAPSHOTlt;/versiongt;lt;packaginggt;pomlt;/packaginggt;lt;descriptiongt;Demo project for Spring Boot Dubbo Nacoslt;/descriptiongt;lt;modulesgt;lt;/modulesgt; lt;propertiesgt;lt;java.versiongt;1.8lt;/java.versiongt;lt;spring-boot.versiongt;1.5.15.RELEASElt;/spring-boot.versiongt;lt;dubbo.versiongt;2.7.3lt;/dubbo.versiongt;lt;curator-framework.versiongt;4.0.1lt;/curator-framework.versiongt;lt;curator-recipes.versiongt;2.8.0lt;/curator-recipes.versiongt;lt;druid.versiongt;1.1.20lt;/druid.versiongt;lt;guava.versiongt;27.0.1-jrelt;/guava.versiongt;lt;fastjson.versiongt;1.2.59lt;/fastjson.versiongt;lt;dubbo-registry-nacos.versiongt;2.7.3lt;/dubbo-registry-nacos.versiongt;lt;nacos-client.versiongt;1.1.4lt;/nacos-client.versiongt;lt;mysql-connector-java.versiongt;5.1.46lt;/mysql-connector-java.versiongt;lt;disruptor.versiongt;3.4.2lt;/disruptor.versiongt;lt;aspectj.versiongt;1.8.13lt;/aspectj.versiongt;lt;nacos-service.versiongt;0.0.1-SNAPSHOTlt;/nacos-service.versiongt;lt;skycommon.versiongt;0.0.1-SNAPSHOTlt;/skycommon.versiongt;lt;maven.compiler.sourcegt;${java.version}lt;/maven.compiler.sourcegt;lt;maven.compiler.targetgt;${java.version}lt;/maven.compiler.targetgt;lt;compiler.plugin.versiongt;3.8.1lt;/compiler.plugin.versiongt;lt;war.plugin.versiongt;3.2.3lt;/war.plugin.versiongt;lt;jar.plugin.versiongt;3.1.2lt;/jar.plugin.versiongt;lt;project.build.sourceEncodinggt;UTF-8lt;/project.build.sourceEncodinggt;lt;project.reporting.outputEncodinggt;UTF-8lt;/project.reporting.outputEncodinggt;lt;/propertiesgt;lt;dependencyManagementgt;lt;dependenciesgt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-weblt;/artifactIdgt;lt;versiongt;${spring-boot.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-dependencieslt;/artifactIdgt;lt;versiongt;${spring-boot.version}lt;/versiongt;lt;typegt;pomlt;/typegt;lt;scopegt;importlt;/scopegt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbo-spring-boot-starterlt;/artifactIdgt;lt;versiongt;${dubbo.version}lt;/versiongt;lt;exclusionsgt;lt;exclusiongt;lt;groupIdgt;org.slf4jlt;/groupIdgt;lt;artifactIdgt;slf4j-log4j12lt;/artifactIdgt;lt;/exclusiongt;lt;/exclusionsgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbolt;/artifactIdgt;lt;versiongt;${dubbo.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.curatorlt;/groupIdgt;lt;artifactIdgt;curator-frameworklt;/artifactIdgt;lt;versiongt;${curator-framework.version}lt;/versiongt;lt;/dependencygt; lt;dependencygt;lt;groupIdgt;org.apache.curatorlt;/groupIdgt;lt;artifactIdgt;curator-recipeslt;/artifactIdgt;lt;versiongt;${curator-recipes.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;mysqllt;/groupIdgt;lt;artifactIdgt;mysql-connector-javalt;/artifactIdgt;lt;versiongt;${mysql-connector-java.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibabalt;/groupIdgt;lt;artifactIdgt;druidlt;/artifactIdgt;lt;versiongt;${druid.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.lmaxlt;/groupIdgt;lt;artifactIdgt;disruptorlt;/artifactIdgt;lt;versiongt;${disruptor.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.google.guavalt;/groupIdgt;lt;artifactIdgt;guavalt;/artifactIdgt;lt;versiongt;${guava.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibabalt;/groupIdgt;lt;artifactIdgt;fastjsonlt;/artifactIdgt;lt;versiongt;${fastjson.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbo-registry-nacoslt;/artifactIdgt;lt;versiongt;${dubbo-registry-nacos.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibaba.nacoslt;/groupIdgt;lt;artifactIdgt;nacos-clientlt;/artifactIdgt;lt;versiongt;${nacos-client.version}lt;/versiongt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.aspectjlt;/groupIdgt;lt;artifactIdgt;aspectjweaverlt;/artifactIdgt;lt;versiongt;${aspectj.version}lt;/versiongt;lt;/dependencygt; lt;/dependenciesgt;lt;/dependencyManagementgt;lt;buildgt;lt;pluginsgt;lt;plugingt;lt;groupIdgt;org.apache.maven.pluginslt;/groupIdgt;lt;artifactIdgt;maven-compiler-pluginlt;/artifactIdgt;lt;versiongt;${compiler.plugin.version}lt;/versiongt;lt;configurationgt;lt;sourcegt;${java.version}lt;/sourcegt;lt;targetgt;${java.version}lt;/targetgt;lt;/configurationgt;lt;/plugingt;lt;plugingt;lt;groupIdgt;org.apache.maven.pluginslt;/groupIdgt;lt;artifactIdgt;maven-war-pluginlt;/artifactIdgt;lt;versiongt;${war.plugin.version}lt;/versiongt;lt;/plugingt;lt;plugingt;lt;groupIdgt;org.apache.maven.pluginslt;/groupIdgt;lt;artifactIdgt;maven-jar-pluginlt;/artifactIdgt;lt;versiongt;${jar.plugin.version}lt;/versiongt;lt;/plugingt;lt;/pluginsgt;lt;/buildgt;lt;/projectgt;

演示用数据库(mySQL5.7)建表的语句

CREATE TABLE `t_product` ( `product_id` int(11) NOT NULL AUTO_INCREMENT, `product_name` varchar(45) DEFAULT NULL, PRIMARY KEY (`product_id`));CREATE TABLE `t_stock` ( `stock_id` int(11) NOT NULL AUTO_INCREMENT, `stock` int(11) DEFAULT NULL, `product_id` int(11) NOT NULL, PRIMARY KEY (`stock_id`));

它建立了两个表,t_product表和t_stock表。这两个表将用于演示在dubbo provider中数据库一致性插入期间遇到异常时如何处理回滚的场景。

nacos-服务工程施工说明

先去pom.xml(非常重要,这里的依赖是正确的springboot+dubbo+nacos客户端的完整配置)

lt;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 https://maven.apache.org/xsd/maven-4.0.0.xsd"gt;lt;modelVersiongt;4.0.0lt;/modelVersiongt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;nacos-servicelt;/artifactIdgt;lt;versiongt;0.0.1-SNAPSHOTlt;/versiongt;lt;namegt;nacos-servicelt;/namegt;lt;descriptiongt;服务者 Demo project for Spring Boot dubbo nacoslt;/descriptiongt;lt;parentgt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;nacos-parentlt;/artifactIdgt;lt;versiongt;0.0.1-SNAPSHOTlt;/versiongt;lt;/parentgt; lt;dependenciesgt; lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-jdbclt;/artifactIdgt;lt;exclusionsgt;lt;exclusiongt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-logginglt;/artifactIdgt;lt;/exclusiongt;lt;/exclusionsgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbolt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.curatorlt;/groupIdgt;lt;artifactIdgt;curator-frameworklt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.curatorlt;/groupIdgt;lt;artifactIdgt;curator-recipeslt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;mysqllt;/groupIdgt;lt;artifactIdgt;mysql-connector-javalt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibabalt;/groupIdgt;lt;artifactIdgt;druidlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-testlt;/artifactIdgt;lt;scopegt;testlt;/scopegt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.spockframeworklt;/groupIdgt;lt;artifactIdgt;spock-corelt;/artifactIdgt;lt;scopegt;testlt;/scopegt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.spockframeworklt;/groupIdgt;lt;artifactIdgt;spock-springlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-configuration-processorlt;/artifactIdgt;lt;optionalgt;truelt;/optionalgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-data-redislt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-log4j2lt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-weblt;/artifactIdgt;lt;exclusionsgt;lt;exclusiongt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-logginglt;/artifactIdgt;lt;/exclusiongt;lt;/exclusionsgt;lt;exclusiongt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-tomcatlt;/artifactIdgt;lt;/exclusiongt;lt;/dependencygt; lt;dependencygt;lt;groupIdgt;org.aspectjlt;/groupIdgt;lt;artifactIdgt;aspectjweaverlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.lmaxlt;/groupIdgt;lt;artifactIdgt;disruptorlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;redis.clientslt;/groupIdgt;lt;artifactIdgt;jedislt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.google.guavalt;/groupIdgt;lt;artifactIdgt;guavalt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibabalt;/groupIdgt;lt;artifactIdgt;fastjsonlt;/artifactIdgt;lt;/dependencygt;lt;!-- Dubbo Registry Nacos --gt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbo-registry-nacoslt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibaba.nacoslt;/groupIdgt;lt;artifactIdgt;nacos-clientlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;skycommonlt;/artifactIdgt;lt;versiongt;${skycommon.version}lt;/versiongt;lt;/dependencygt;lt;/dependenciesgt;lt;buildgt;lt;sourceDirectorygt;src/main/javalt;/sourceDirectorygt;lt;testSourceDirectorygt;src/test/javalt;/testSourceDirectorygt;lt;pluginsgt;lt;plugingt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-maven-pluginlt;/artifactIdgt;lt;/plugingt;lt;/pluginsgt;lt;resourcesgt;lt;resourcegt;lt;directorygt;src/main/resourceslt;/directorygt;lt;/resourcegt;lt;resourcegt;lt;directorygt;src/main/webapplt;/directorygt;lt;targetPathgt;META-INF/resourceslt;/targetPathgt;lt;includesgt;lt;includegt;**/**lt;/includegt;lt;/includesgt;lt;/resourcegt;lt;resourcegt;lt;directorygt;src/main/resourceslt;/directorygt;lt;filteringgt;truelt;/filteringgt;lt;includesgt;lt;includegt;application.propertieslt;/includegt;lt;includegt;application-${profileActive}.propertieslt;/includegt;lt;/includesgt;lt;/resourcegt;lt;/resourcesgt;lt;/buildgt;lt;/projectgt;

然后,我们设置application.properties文件的内容。

dubbo在这里的部分配置是相对于我的虚拟仿真环境4C CPU和4GB内存设置的。更具体的设置参数请直接参考dubbo的官方文档。

spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driverClassName=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://192.168.56.101:3306/mkuseUnicode=truecharacterEncoding=utf-8useSSL=falsespring.datasource.username=mkspring.datasource.password=111111 server.port=8080server.tomcat.max-connections=300server.tomcat.max-threads=300server.tomcat.uri-encoding=UTF-8server.tomcat.max-http-post-size=0 #Dubbo provider configurationdubbo.application.name=nacos-service-demodubbo.registry.protocol=dubbodubbo.registry.address=nacos://127.0.0.1:8848dubbo.protocol.name=dubbodubbo.protocol.port=20880dubbo.protocol.threads=200dubbo.protocol.queues=100dubbo.protocol.threadpool=cacheddubbo.provider.retries = 3dubbo.provider.threadpool = cacheddubbo.provider.threads = 200dubbo.provider.connections = 100dubbo.scan.base-packages=org.sky.service logging.config=classpath:log4j2.xml

我们可以看到,要将dubbo与nacos连接起来,只需要在pom.xml文件中引入即可。

lt;!-- Dubbo Registry Nacos --gt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbo-registry-nacoslt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibaba.nacoslt;/groupIdgt;lt;artifactIdgt;nacos-clientlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbolt;/artifactIdgt;lt;/dependencygt;

和application.properties文件一样,对应的dubbo协议仍然使用dubbo。这是因为在dubbo2.6中已经引入了nacos-registry,所以有必要将dubbo.registry.address设置为指向您的本机nacos启动实例(默认为端口8848)。

dubbo.registry.protocol=dubbodubbo.registry.address=nacos://127.0.0.1:8848

Application.java spring boot的启动代码

package org.sky; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.ComponentScan;import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableDubbo@EnableAutoConfiguration@ComponentScan(basePackages = { "org.sky" })@EnableTransactionManagement public class Application { public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

有两个重要的注意事项。

@EnableDubbo声明该项目启用了Dubbo的自动标注;

@EnableTransactionManagement声明项目将使用数据库事务;

将项目连接到数据库。

我们使用druid作为数据库的连接池。

package org.sky.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.boot.web.servlet.ServletRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary; import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.support.http.StatViewServlet;import com.alibaba.druid.support.http.WebStatFilter; @Configuration@EnableAutoConfigurationpublic class DruidConfig { @ConfigurationProperties(prefix = "spring.datasource")@Beanpublic DruidDataSource dataSource() { return new DruidDataSource(); }}

创建一个自定义全局异常DemorpCruntimeeException。

放在共同项目里。

package org.sky.exception; import java.io.Serializable; public class DemoRpcRunTimeException extends RuntimeException implements Serializable {public DemoRpcRunTimeException() {} public DemoRpcRunTimeException(String msg) {super(msg);} public DemoRpcRunTimeException(Throwable cause) {super(cause);} public DemoRpcRunTimeException(String message, Throwable cause) {super(message, cause);} }

制作AOP,DemorpCruntimeexceptionHandler

用于包装自定义异常,位于nacos-service项目中,将由AOP注入。


package org.sky.config; import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Aspect;import org.sky.exception.DemoRpcRunTimeException;import org.springframework.stereotype.Component; @Aspect@Componentpublic class DemoRpcRuntimeExceptionHandler {protected Logger logger = LogManager.getLogger(this.getClass()); /** * service层的RuntimeException统一处理器 * 可以将RuntimeException分装成RpcRuntimeException抛给调用端处理 或自行处理 * * @param exception */@AfterThrowing(throwing = "exception", pointcut = "execution(* org.sky.service.*.*(..))")public void afterThrow(Throwable exception) {if (exception instanceof RuntimeException) {logger.error("DemoRpcRuntimeExceptionHandler side-gt;exception occured: " + exception.getMessage(),exception);throw new DemoRpcRunTimeException(exception);}// logger.error("DemoRpcRuntimeExceptionHandler side-gt;exception occured: " +// exception.getMessage(), exception);}}

开始核心提供商服务的生产。

产品服务接口

我们把它放在公共项目中,这样消费者项目就可以通过nacos的注册表找到这个接口名,然后通过spring的invoke调用远程的用于实现服务逻辑的xxxServiceImpl类。

package org.sky.service; import org.sky.exception.DemoRpcRunTimeException;import org.sky.platform.util.DubboResponse;import org.sky.vo.ProductVO; public interface ProductService {public DubboResponse addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException;}

特定业务逻辑实现类,ProductServiceImpl

阶级应该做这样的事:

1)插入t_product表数据

2)插入t_stock表数据

在插入两个表时,如果有一点错误,整个插入的事务都会回滚,否则就成功了。这里需要注意的是:

springboot service只有接到RuntimeException才会回滚;要把RuntimeException从provider远程传递到consumer端,包括把stackTrace这些信息也远程传递到consumer端,那么这个exception必须是serializable的;暴露成dubbo provider service的service方法必须加上@Service注解,这个Service可不是spring annotation的service而是ali dubbo的service,在2.7.3开始变成了org.apache.dubbo包了。它配合着springboot的主启动文件中的@EnableDubbo来启作用,它在启动后会通过application.properties中的dubbo.scan.base-packages中所指的路径把这个路径下所有的类寻找是否带有@Service注解,如有那么就把它通过nacos-registry给注册到nacos中去;


ProductServiceImpl.java

package org.sky.service; import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.SQLException; import org.apache.dubbo.config.annotation.Service;import org.sky.exception.DemoRpcRunTimeException;import org.sky.platform.util.DubboResponse;import org.sky.vo.ProductVO;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpStatus;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.PreparedStatementCreator;import org.springframework.jdbc.support.GeneratedKeyHolder;import org.springframework.jdbc.support.KeyHolder;import org.springframework.transaction.annotation.Transactional; @Service(version = "1.0.0", interfaceClass = ProductService.class, timeout = 120000)public class ProductServiceImpl extends BaseService implements ProductService {@AutowiredJdbcTemplate jdbcTemplate; @Override@Transactionalpublic DubboResponselt;ProductVOgt; addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException {DubboResponselt;ProductVOgt; response = null;int newProdId = 0;String prodSql = "insert into t_product(product_name)values()";String stockSql = "insert into t_stock(product_id,stock)values(,)";try {if (prod != null) {KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() {@Overridepublic PreparedStatement createPreparedStatement(Connection connection) throws SQLException {PreparedStatement ps = connection.prepareStatement(prodSql, new String[] { "id" });ps.setString(1, prod.getProductName());return ps;}}, keyHolder);newProdId = keyHolder.getKey().intValue();logger.info("======gt;insert into t_product with product_id:" + newProdId);if (newProdId gt; 0) {jdbcTemplate.update(stockSql, newProdId, prod.getStock());logger.info("======gt;insert into t_stock with successful");ProductVO returnData = new ProductVO();returnData.setProductId(newProdId);returnData.setProductName(prod.getProductName());returnData.setStock(prod.getStock());response = new DubboResponse(HttpStatus.OK.value(), "success", returnData);//throw new Exception("Mk throwed exception to enforce rollback[insert into t_stock]");return response;} } else {throw new DemoRpcRunTimeException("error occured on ProductVO is null");}} catch (Exception e) {logger.error("error occured on Dubbo Service Side: " + e.getMessage(), e);throw new DemoRpcRunTimeException("error occured on Dubbo Service Side: " + e.getMessage(), e);}return response;} }

这个班目前处于正常状态。让我们先将一个普通的提供者调用到服务端,然后演示如何将异常远程传递到消费者端。

nacos-消费者项目建设说明

先去pom.xml

lt;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 https://maven.apache.org/xsd/maven-4.0.0.xsd"gt;lt;modelVersiongt;4.0.0lt;/modelVersiongt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;nacos-consumerlt;/artifactIdgt;lt;versiongt;0.0.1-SNAPSHOTlt;/versiongt;lt;namegt;nacos-servicelt;/namegt;lt;descriptiongt;消费者 Demo project for Spring Boot dubbo nacoslt;/descriptiongt;lt;parentgt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;nacos-parentlt;/artifactIdgt;lt;versiongt;0.0.1-SNAPSHOTlt;/versiongt;lt;/parentgt; lt;dependenciesgt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbolt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.curatorlt;/groupIdgt;lt;artifactIdgt;curator-frameworklt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.apache.curatorlt;/groupIdgt;lt;artifactIdgt;curator-recipeslt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-testlt;/artifactIdgt;lt;scopegt;testlt;/scopegt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.spockframeworklt;/groupIdgt;lt;artifactIdgt;spock-corelt;/artifactIdgt;lt;scopegt;testlt;/scopegt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.spockframeworklt;/groupIdgt;lt;artifactIdgt;spock-springlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-configuration-processorlt;/artifactIdgt;lt;optionalgt;truelt;/optionalgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-log4j2lt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-weblt;/artifactIdgt;lt;exclusionsgt;lt;!-- 去掉默认配置 --gt;lt;exclusiongt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-starter-logginglt;/artifactIdgt;lt;/exclusiongt;lt;/exclusionsgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.lmaxlt;/groupIdgt;lt;artifactIdgt;disruptorlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.google.guavalt;/groupIdgt;lt;artifactIdgt;guavalt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibabalt;/groupIdgt;lt;artifactIdgt;fastjsonlt;/artifactIdgt;lt;/dependencygt;lt;!-- Dubbo Registry Nacos --gt;lt;dependencygt;lt;groupIdgt;org.apache.dubbolt;/groupIdgt;lt;artifactIdgt;dubbo-registry-nacoslt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;com.alibaba.nacoslt;/groupIdgt;lt;artifactIdgt;nacos-clientlt;/artifactIdgt;lt;/dependencygt;lt;dependencygt;lt;groupIdgt;org.aspectjlt;/groupIdgt;lt;artifactIdgt;aspectjweaverlt;/artifactIdgt;lt;/dependencygt;lt;!-- import sky common package --gt;lt;dependencygt;lt;groupIdgt;org.sky.demolt;/groupIdgt;lt;artifactIdgt;skycommonlt;/artifactIdgt;lt;versiongt;${skycommon.version}lt;/versiongt;lt;/dependencygt;lt;/dependenciesgt;lt;buildgt;lt;sourceDirectorygt;src/main/javalt;/sourceDirectorygt;lt;testSourceDirectorygt;src/test/javalt;/testSourceDirectorygt;lt;pluginsgt;lt;plugingt;lt;groupIdgt;org.springframework.bootlt;/groupIdgt;lt;artifactIdgt;spring-boot-maven-pluginlt;/artifactIdgt;lt;/plugingt;lt;/pluginsgt;lt;resourcesgt;lt;resourcegt;lt;directorygt;src/main/resourceslt;/directorygt;lt;/resourcegt;lt;resourcegt;lt;directorygt;src/main/webapplt;/directorygt;lt;targetPathgt;META-INF/resourceslt;/targetPathgt;lt;includesgt;lt;includegt;**/**lt;/includegt;lt;/includesgt;lt;/resourcegt;lt;resourcegt;lt;directorygt;src/main/resourceslt;/directorygt;lt;filteringgt;truelt;/filteringgt;lt;includesgt;lt;includegt;application.propertieslt;/includegt;lt;includegt;application-${profileActive}.propertieslt;/includegt;lt;/includesgt;lt;/resourcegt;lt;/resourcesgt;lt;/buildgt;lt;/projectgt;

nacos消费者端的应用程序属性

server.port=8082server.tomcat.max-connections=50server.tomcat.max-threads=50server.tomcat.uri-encoding=UTF-8server.tomcat.max-http-post-size=0 #Dubbo provider configurationdubbo.application.name=nacos-consumerdubbo.registry.address=nacos://127.0.0.1:8848#dubbo.consumer.time=120000 logging.config=classpath:log4j2.xml

类似地,消费者端也需要连接到本地nacos实例。

更何况,不要在消费端做什么dubbo通信超时或者一些个性化的dubbo参数设置。因为dubbo有三个核心参数集,提供者,协议,消费者。但是,由于三者的优先级,在使用者端进行的设置将覆盖提供者端的设置。如果是在大规模的微服务开发场景下,每个消费者自己进行个性化设置,整体上不利于系统性能的集中统一控制。因此,公司的架构师需要在提供者端统一控制这些规范,必须尽量避免在消费者端设置一些本应属于中央(提供者)端的参数。

消费者的Application.java

package org.sky; import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.ComponentScan; @EnableDubbo@ComponentScan(basePackages = { "org.sky" })@EnableAutoConfigurationpublic class Application { public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

在提供商端和naco-service区别不大。关注@EnableDubbo,否则spring不会在项目启动的时候把消费端给在nacos注册的注册中心。

消费者侧的控制器

这个消费者就是我第一张图中的无状态API层。这一层会有一堆类似tomcat/netty/jboss的东西。他们做的是路由API,以json格式返回给客户端(手机、网页、小程序)。这一层不会和DB、NOSQL、cache之类的打交道,他们要做的就是调用ldquo后端rdquo微服的Dubbo服务,所以我们这边基本上把精力放在spring的控制器上。

为了让消费者调用提供者的服务方法,必须在注入时添加@Reference注释,这样dubbos的消费者就注册在ldquo注册中心rdquo,如:nacos,你知道你要找哪个提供商的服务(-stub)(寻址功能)。

package org.sky.controller; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.RequestMapping; import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; import javax.annotation.Resource; import org.apache.dubbo.config.annotation.Reference;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.sky.platform.util.AppConstants;import org.sky.platform.util.DubboResponse;import org.sky.platform.util.ResponseResult;import org.sky.platform.util.ResponseStatusEnum;import org.sky.platform.util.ResponseUtil;import org.sky.service.HelloNacosService;import org.sky.service.ProductService;import org.sky.vo.ProductVO;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.MediaType;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody; @RestController@RequestMapping("nacosconsumer")public class DemoDubboConsumer extends BaseController { @Reference(version = "1.0.0",loadbalance="roundrobin")private HelloNacosService helloNacosService; @Reference(version = "1.0.0")private ProductService productService; @PostMapping(value = "/sayHello", produces = "application/json")public ResponseEntitylt;Stringgt; sayHello(@RequestBody String params) throws Exception {ResponseEntitylt;Stringgt; response;HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON_UTF8);Maplt;String, Objectgt; resultMap = new HashMaplt;gt;();JSONObject requestJsonObj = JSON.parseObject(params);try {String name = getHelloNameFromJson(requestJsonObj);String answer = helloNacosService.sayHello(name);logger.info("answer======gt;" + answer);Maplt;String, Stringgt; result = new HashMaplt;gt;();result.put("result", answer);String resultStr = JSON.toJSONString(result);response = new ResponseEntitylt;gt;(resultStr, headers, HttpStatus.OK);} catch (Exception e) {logger.error("dubbo-clinet has an exception occured: " + e.getMessage(), e);String resultStr = e.getMessage();response = new ResponseEntitylt;gt;(resultStr, headers, HttpStatus.EXPECTATION_FAILED);}return response; } @PostMapping(value = "/addProductAndStock", produces = "application/json")public ResponseEntitylt;Stringgt; addProduct(@RequestBody String params) throws Exception {ResponseEntitylt;Stringgt; response = null;DubboResponselt;ProductVOgt; dubboResponse;String returnResultStr;HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON_UTF8);JSONObject requestJsonObj = JSON.parseObject(params);Maplt;String, Objectgt; result = new HashMaplt;gt;();try {ProductVO inputProductPara = getProductFromJson(requestJsonObj);dubboResponse = productService.addProductAndStock(inputProductPara);ProductVO returnData = dubboResponse.getData();if (returnData != null  dubboResponse.getCode() == HttpStatus.OK.value()) {result.put("code", HttpStatus.OK.value());result.put("message", "add a new product successfully");result.put("productid", returnData.getProductId());result.put("productname", returnData.getProductName());result.put("stock", returnData.getStock());returnResultStr = JSON.toJSONString(result);response = new ResponseEntitylt;gt;(returnResultStr, headers, HttpStatus.OK);} else {result.put("message", "dubbo service ProductService get nullpoint exception");returnResultStr = JSON.toJSONString(result);response = new ResponseEntitylt;gt;(returnResultStr, headers, HttpStatus.EXPECTATION_FAILED);}} catch (Exception e) {logger.error("add a new product with error: " + e.getMessage(), e);result.put("message", "add a new product with error: " + e.getMessage());returnResultStr = JSON.toJSONString(result);response = new ResponseEntitylt;gt;(returnResultStr, headers, HttpStatus.EXPECTATION_FAILED);}return response;} private String getHelloNameFromJson(JSONObject requestObj) {String helloName = requestObj.getString("name");return helloName;} private ProductVO getProductFromJson(JSONObject requestObj) {String productName = requestObj.getString("productname");int stock = requestObj.getIntValue("stock");ProductVO prod = new ProductVO();prod.setProductName(productName);prod.setStock(stock);return prod;} }

这个消费者挺单纯的。直接通过远程接口调用dubbo获得回报。

运行示例

确保我们的nacos1.1.4在那里运行。

然后先运行nacos-service的Application.java,再运行nacos-consumer的Application.java。

nacos服务的运行示例:


nacos-消费者的运行示例:


然后我们去nacos的管理界面查看,可以发现提供商和消费者都注册成功了。


然后我们使用postman向消费者发送一个json请求。


得到以下结果。


看数据库。



这表明我们的dubbo+nacos构建工作得非常完美。接下来,我们将演示两个异常的抛出。

类型1:直接从提供者向使用者抛出RuntimeException。

在提供者方面,我们对ProductServiceImpl做了如下的小修改:


我们写了一句话:

throw new Exception("Mk throwed exception to enforce rollback[insert into t_stock]");

如前所述,必须在提供者的服务中抛出RuntimeException来回滚数据库事务,但我们不必担心。请记住,我们已经在nacos-service中注入了一个名为ldquo的aop拦截器;demorpcruntimeexceptionhandlerrd quo;真的吗?

它的功能是停止所有的异常,并把它们变成RuntimeException。

好了,我们加上这句话后再依次重新运行nacose-service和nacos-consumer。然后也通过postman访问http://localhost:8082/nacosconsumer/addproductandstock,然后我们使用新的产品名称。post请求正文中的消息如下:

{"productname":"coffee","stock":10000}

看,在我们的请求之后,什么会直接出现在回应中?


我们来看看nacos-service的日志。这是我们在提供商处手动抛出的日志:


查看nacos-consumer的日志,我们可以看到提供者的异常甚至它的堆栈跟踪信息都被传递给了消费者:


这样,消费者端的开发者就会跑到dubbo开发团队那里大喊:“嘿,生产中有个bug。看,这是你们提供商那边扔的。换一个!”

为了确保我们的ExceptionHandler成功拦截,让我们看看数据库端:

T_product表没有插咖啡的记录。


T_stock表没有插入到相关咖啡的库存中。


解释异常确实被转换成RuntimeException,被spring框架捕获,然后回滚。

类型2:将所有异常打包成json返回消息,不向消费者输出具体的异常信息。

我们希望将提供者端的异常封装到一个json消息中,如下所示:

{ "message" : "exception", "code" : "500", "add new product failed", "productid" : xxx, "productname" : xxx, "stock" : xxx}

转到:

异常堆栈跟踪以日志的形式记录在提供者端。当出现问题时,提供者端的开发人员可以通过日志来查询和定位问题。

为什么会有这种做法?

很简单,因为stackTrace是异常追踪,jvm的栈中的信息被调用。这是ldquo重rdquo一件作品。我们通过提供者和消费者抛出一堆异常。本来我们用dubbo做微服务,处理大规模并发请求,让系统灵活冗余。你还在用这么大的stackTrace二话不说两端传来传去,上传的时候还要加序列化,接收的时候还要加反序列化。这不是增加了系统的开销吗?

直接给我看下面的代码,在nacos-service的org.sky.config添加一个名为ldquo的aopServiceExceptionHandlerrdquo,代码如下:

package org.sky.config; import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.sky.platform.util.DubboResponse;import org.springframework.stereotype.Component; import com.google.common.base.Throwables; @Component@Aspectpublic class ServiceExceptionHandler {protected Logger logger = LogManager.getLogger(this.getClass()); /** * 返回值类型为Response的Service */@Pointcut(value = "execution(* org.sky.service.*.*(..))")private void servicePointcut() {} /** * 任何持有@Transactional注解的方法 */@Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")private void transactionalPointcut() {} /** * 异常处理切面 将异常包装为Response,避免dubbo进行包装 * * @param pjp 处理点 * @return Object */@Around("servicePointcut()  !transactionalPointcut()")public Object doAround(ProceedingJoinPoint pjp) {Object[] args = pjp.getArgs();try {return pjp.proceed();} catch (Exception e) {processException(pjp, args, e);return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());} catch (Throwable throwable) {processException(pjp, args, throwable);return DubboResponse.error("exception occured on dubbo service side: " + throwable.getMessage());}} /** * 任何持有@Transactional注解的方法异常处理切面 将自定义的业务异常转为RuntimeException: * 1.规避dubbo的包装,让customer可以正常获取message 2.抛出RuntimeException使事务可以正确回滚 其他异常不处理 * * @param pjp 处理点 * @return Object */@Around("servicePointcut()  transactionalPointcut()")public Object doTransactionalAround(ProceedingJoinPoint pjp) throws Throwable {try {return pjp.proceed();} catch (Exception e) {Object[] args = pjp.getArgs();// dubbo会将异常捕获进行打印,这里就不打印了processException(pjp, args, e);// logger.error("service with @Transactional exception occured on dubbo service// side: " + e.getMessage(), e);throw new RuntimeException(e.getMessage(), e);}} /** * 处理异常 * * @param joinPoint 切点 * @param args 参数 * @param throwable 异常 */private void processException(final ProceedingJoinPoint joinPoint, final Object[] args, Throwable throwable) {String inputParam = "";if (args != null  args.length gt; 0) {StringBuilder sb = new StringBuilder();for (Object arg : args) {sb.append(",");sb.append(arg);}inputParam = sb.toString().substring(1);}logger.error("n 方法: {}n 入参: {} n 错误信息: {}", joinPoint.toLongString(), inputParam,Throwables.getStackTraceAsString(throwable));}}

它的功能是:

把一切Exception使用一个叫DubboResponse的请求体来返回provider端的service报文;如果provider端出错,那么也把错误的系统code与系统messageldquo;包rdquo;在DubboResponse内

等等等等。。。。。。出事了!不全在这里。为什么?

一切都例外吗?这样的话,包完之后服务层不就有异常抛出了吗?如果服务方法涉及数据库操作而没有抛出RuntimeException,那么数据库事务如何回滚?

这就是为什么我们在这个处理程序类中有这样一段内容。它的功能是抛出quot对于所有带有@Transactional注释的服务方法。RuntimeExceptionquot对于其他的,则包装在DubboResponse中返回给调用者(如下:非事务性服务方法异常的统一包装):

@Around("servicePointcut()  !transactionalPointcut()")public Object doAround(ProceedingJoinPoint pjp) {Object[] args = pjp.getArgs();try {return pjp.proceed();} catch (Exception e) {processException(pjp, args, e);return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());} catch (Throwable throwable) {processException(pjp, args, throwable);return DubboResponse.error("exception occured on dubbo service side: " + throwable.getMessage());}}

好的,那么让我们现在重新启动我们的系统。让我们看看下面的运行示例。。。。。。

等等!

忘了一件事,我给你以下信息位于ldquocommonrdquo第一,写博文不喜欢ldquoRdquo单手。

ProductVO.java

package org.sky.vo; import java.io.Serializable; public class ProductVO implements Serializable { private int stock = 0; public int getStock() {return stock;} public void setStock(int stock) {this.stock = stock;} public String getProductName() {return productName;} public int getProductId() {return productId;} public void setProductId(int productId) {this.productId = productId;} public void setProductName(String productName) {this.productName = productName;} private int productId = 0;private String productName = "";}

DubboResponse.java

package org.sky.platform.util; import java.io.Serializable; import org.springframework.http.HttpStatus; import com.alibaba.fastjson.JSON; public class DubboResponselt;Tgt; implements Serializable {/** * */private static final long serialVersionUID = 1L; /** * 状态码 */private int code; /** * 返回信息 */private String message; /** * * 返回json对象 */private T data; public DubboResponse(int code, String message) {this.code = code;this.message = message;} public DubboResponse(int code, String message, T data) {this.code = code;this.message = message;this.data = data;} public T getData() {return data;} public void setData(T data) {this.data = data;} public static lt;Tgt; DubboResponse success(String message, T data) {String resultStr = JSON.toJSONString(data);return new DubboResponse(HttpStatus.OK.value(), message, data);} public static DubboResponse success(String message) {return success(message, null);} public static DubboResponse error(String message) {return new DubboResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), message, null);} public static DubboResponse error(int code, String message) {return new DubboResponse(code, message, null);} public int getCode() {return code;} public void setCode(int code) {this.code = code;} public String getMessage() {return message;} public void setMessage(String message) {this.message = message;}}

ldquoRdquo根据@Transactional的服务方法,抛出一个异常演示:

现在让我们运行nacose-service和nacos-consumer来看看效果,尝试插入一个新的产品:


获取:


看看nacos-服务,nacos-消费者和数据库。





可以看到,提供者和使用者都正确地抛出了错误,并且数据库中没有插入值。

ldquo异常rdquo不包含事务性的(普通)服务方法会引发异常,并被封装用于演示:

现在让我们做一些小事,让我们把ldquo放在提供者端;addProductAndStock(product VO prod)rdquo;@Transactional在方法上把它拿走看看效果。

@Overridepublic DubboResponselt;ProductVOgt; addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException {DubboResponselt;ProductVOgt; response = null;int newProdId = 0;String prodSql = "insert into t_product(product_name)values()";String stockSql = "insert into t_stock(product_id,stock)values(,)";try {if (prod != null) {KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() {@Override

请像上面那样结束代码芯片。

让我们在nacos-consumer端做一个小的修改,如下图所示,这样消费者端直接组装提供者端{ quot消息quot;:quot电影站...quot}以ldquo显示;前端rdquo(一切通过nginx访问消费者,消费者通过provider调用数据库。这里,我们用邮递员)。


然后让我们运行它,看看效果:

我们可以看到,这一次,在移除了@Transactional注释之后,当服务方法抛出错误时,请求者得到的是我们打包的DubboResponse中的内容。


提供者包装由普通服务抛出的异常的核心代码:

@Around("servicePointcut()  !transactionalPointcut()")public Object doAround(ProceedingJoinPoint pjp) {Object[] args = pjp.getArgs();try {return pjp.proceed();} catch (Exception e) {processException(pjp, args, e);return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());} catch (Throwable throwable) {processException(pjp, args, throwable);return DubboResponse.error("exception occured on dubbo service side: " + throwable.getMessage());}}

让我们看看我们的供应商。正是通过上面代码catch(异常e)中的这一段,它记错了服务器端的日志,并将错误打包返回给了消费者端。是下面两句话:

processException(pjp, args, e);return DubboResponse.error("exception occured on dubbo service side: " + e.getMessage());

看看nacos服务端的日志输出:


看看nacos消费者端的日志输出:


哈哈,这次nacos-消费者端没有抛出任何错误,因为错误已经被提供者端包装好了。

当然,当我们查看我们的DB端时,可以肯定的是有一个成功的数据插入。

因为如前所述,对于没有@Transactional注释的方法,我们的aop handler类会把所有错误ldquo吃rdquo,只在后台做记录然后把正常的返回结果打包给消费方,那么提供方的服务方法既不抛出RuntimeException,那么怎么回滚呢?

当然是插成功了!

t _产品表


t _股票表



总结所以在dubbo的provider端的RuntimeExeption并且是quot;implements Serializablequot;的就可以连着stackTrace抛到远程的consumer端;实际项目中dubbo的provider(dubbo群)与dubbo的consumer(一堆无状态的tomcat为容器布署的api controller)间如果只是为了传stackTrace来消耗网络硬件等资源只是为了ldquo;排查定位问题rdquo;方便,这么做是不值的,那么就要包一层,包时不要包的太过了,记得涉及数据库事务的方法一定要抛RuntimeException,要不然就是插进去一堆脏数据;

关于阿里nacos+springboot+dubbo2.7.3统一异常处理的两种方式的文章到此为止,关于nacos+springboot+dubbo统一异常处理的更多信息