阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式

目录
  • 1.为什么要抛异常?
  • 2.给出解决方案
  • 3.两种抛异常的实例解说
    • dubbo工程搭建

在网上很多关于dubbo异常统一处理的博文,90%都是抄来抄去。大多都是先上一段dubbo中对于异常的统一处理的原码,然后说一堆的(甚至有12345,五种)不靠谱方案,最后再说“本篇使用的是方案4”,然后再对所谓的方案4写了一段文字,最后还说不清!!!

本篇解决方案不会那么罗里吧嗦也不会贴dubbo源码来凑字数,我就直接从刚结束不久的双11保卫战性能全链路优化中我们的面对10万级别TPS的方案中提取的代码来说明这个dubbo统一处理异常是怎么个处理方式吧!

1. 为什么要抛异常?

不同开发团队间便于追溯异常的来源以及为了便于定位问题的需要

往往实际开发中的架构是这么一个样子的:

dubbo微服务架构简图

不同层的开发人员都是不同的人或者是不同的几波人马;

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

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

在调试、测试、上线后我们经常会发生各种Exception,此时这几个不同的开发团队间会互相扯皮、打架,并且大家都要忙于定位这个Exception到底是发生在哪一层,甚至需要追溯Exception发生在哪个点(stackTrace)。

Service层有数据库事务一致性的问题必须抛出异常

我们都知道在spring中的Service层必须抛出Runtime Exception,否则Service层的方法如果有涉及数据库的修改操作是不会回滚的。

2. 给出解决方案

其实解决方案真正的无外乎就2种:

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

本文把这2种实现方式都给实现了,下面开始直接show me the code的方式来说话吧。

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

环境搭建

nacos1.1.4

我们这边不用dubbo admin,因为dubbo admin太老且使用不方便,缺少了很多管理微服务所需要的基本功能。并且dubbo从2.6开始已经把dubbo admin从它的主工程里分离了出去,同时dubbo2.6开始支持nacos registry了。

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

下载地址在这里(请戳):阿里nacos最新下载地址

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

编辑这个application.properties文件,我们把nacos自动服务发现管理端连上自己开发环境上的mysql。

# spring
spring.datasource.platform=mysql
server.contextPath=/nacos
server.servlet.contextPath=/nacos
server.port=8848
db.num=1
db.url.0=jdbc:mysql://192.168.56.101:3306/nacos?useUnicode=true&characterEncoding=utf-8&useSSL=false
db.user=nacos
db.password=111111

配完后直接双击:startup.cmd启动nacos

登录界面中使用nacos/nacos即可进行登录了。

登录后看到nacos管理界面就说明nacos配置和启动成功了。接下来我们就要开始书写dubbo的provider端与consumer端了。

dubbo工程搭建

nacos-parent工程

整个工程我已经放在git上了,地址请戳这里:nacos-dubbo-demo

工程的依赖结构如下:

由于dubbo与springboot结合的项目不多,很多网上有的博客也充斥着乱抄、自己都没有验证过就上代码的,因此大多网友们通过网上之言片语拼凑起来的项目在本地很难运行起来,不是maven包冲突就是少这个、那个包。下面给出工程的parent pom文件。

<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">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.sky.demo</groupId>
	<artifactId>nacos-parent</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>
	<description>Demo project for Spring Boot Dubbo Nacos</description>
	<modules>
	</modules>

	<properties>
		<java.version>1.8</java.version>
		<spring-boot.version>1.5.15.RELEASE</spring-boot.version>
		<dubbo.version>2.7.3</dubbo.version>
		<curator-framework.version>4.0.1</curator-framework.version>
		<curator-recipes.version>2.8.0</curator-recipes.version>
		<druid.version>1.1.20</druid.version>
		<guava.version>27.0.1-jre</guava.version>
		<fastjson.version>1.2.59</fastjson.version>
		<dubbo-registry-nacos.version>2.7.3</dubbo-registry-nacos.version>
		<nacos-client.version>1.1.4</nacos-client.version>
		<mysql-connector-java.version>5.1.46</mysql-connector-java.version>
		<disruptor.version>3.4.2</disruptor.version>
		<aspectj.version>1.8.13</aspectj.version>
		<nacos-service.version>0.0.1-SNAPSHOT</nacos-service.version>
		<skycommon.version>0.0.1-SNAPSHOT</skycommon.version>
		<maven.compiler.source>${java.version}</maven.compiler.source>
		<maven.compiler.target>${java.version}</maven.compiler.target>
		<compiler.plugin.version>3.8.1</compiler.plugin.version>
		<war.plugin.version>3.2.3</war.plugin.version>
		<jar.plugin.version>3.1.2</jar.plugin.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	</properties>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-web</artifactId>
				<version>${spring-boot.version}</version>
			</dependency>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring-boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo-spring-boot-starter</artifactId>
				<version>${dubbo.version}</version>
				<exclusions>
					<exclusion>
						<groupId>org.slf4j</groupId>
						<artifactId>slf4j-log4j12</artifactId>
					</exclusion>
				</exclusions>
			</dependency>
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo</artifactId>
				<version>${dubbo.version}</version>
			</dependency>
			<dependency>
				<groupId>org.apache.curator</groupId>
				<artifactId>curator-framework</artifactId>
				<version>${curator-framework.version}</version>
			</dependency>

			<dependency>
				<groupId>org.apache.curator</groupId>
				<artifactId>curator-recipes</artifactId>
				<version>${curator-recipes.version}</version>
			</dependency>
			<dependency>
				<groupId>mysql</groupId>
				<artifactId>mysql-connector-java</artifactId>
				<version>${mysql-connector-java.version}</version>
			</dependency>
			<dependency>
				<groupId>com.alibaba</groupId>
				<artifactId>druid</artifactId>
				<version>${druid.version}</version>
			</dependency>
			<dependency>
				<groupId>com.lmax</groupId>
				<artifactId>disruptor</artifactId>
				<version>${disruptor.version}</version>
			</dependency>
			<dependency>
				<groupId>com.google.guava</groupId>
				<artifactId>guava</artifactId>
				<version>${guava.version}</version>
			</dependency>
			<dependency>
				<groupId>com.alibaba</groupId>
				<artifactId>fastjson</artifactId>
				<version>${fastjson.version}</version>
			</dependency>
			<dependency>
				<groupId>org.apache.dubbo</groupId>
				<artifactId>dubbo-registry-nacos</artifactId>
				<version>${dubbo-registry-nacos.version}</version>
			</dependency>
			<dependency>
				<groupId>com.alibaba.nacos</groupId>
				<artifactId>nacos-client</artifactId>
				<version>${nacos-client.version}</version>
			</dependency>
			<dependency>
				<groupId>org.aspectj</groupId>
				<artifactId>aspectjweaver</artifactId>
				<version>${aspectj.version}</version>
			</dependency>

		</dependencies>
	</dependencyManagement>
	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>${compiler.plugin.version}</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-war-plugin</artifactId>
				<version>${war.plugin.version}</version>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-jar-plugin</artifactId>
				<version>${jar.plugin.version}</version>
			</plugin>
		</plugins>
	</build>
</project>

演示用数据库(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中对于数据库一致性插入时在碰到Exception时怎么处理回滚的场景。

nacos-service工程搭建说明

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

<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">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.sky.demo</groupId>
	<artifactId>nacos-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>nacos-service</name>
	<description>服务者 Demo project for Spring Boot dubbo nacos</description>
	<parent>
		<groupId>org.sky.demo</groupId>
		<artifactId>nacos-parent</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-core</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-spring</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
			<exclusion>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-tomcat</artifactId>
			</exclusion>
		</dependency>

		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
		</dependency>
		<dependency>
			<groupId>com.lmax</groupId>
			<artifactId>disruptor</artifactId>
		</dependency>
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
		</dependency>
		<!-- Dubbo Registry Nacos -->
		<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>org.sky.demo</groupId>
			<artifactId>skycommon</artifactId>
			<version>${skycommon.version}</version>
		</dependency>
	</dependencies>
	<build>
		<sourceDirectory>src/main/java</sourceDirectory>
		<testSourceDirectory>src/test/java</testSourceDirectory>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
			<resource>
				<directory>src/main/webapp</directory>
				<targetPath>META-INF/resources</targetPath>
				<includes>
					<include>**/**</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>application.properties</include>
					<include>application-${profileActive}.properties</include>
				</includes>
			</resource>
		</resources>
	</build>
</project>

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

这边dubbo的部分配置是相对于我虚拟出来的模拟环境4C CPU,4GB内存来设的,具体更多设置参数可以直接参照于dubbo官方文档。

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.56.101:3306/mk?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=mk
spring.datasource.password=111111

server.port=8080
server.tomcat.max-connections=300
server.tomcat.max-threads=300
server.tomcat.uri-encoding=UTF-8
server.tomcat.max-http-post-size=0

#Dubbo provider configuration
dubbo.application.name=nacos-service-demo
dubbo.registry.protocol=dubbo
dubbo.registry.address=nacos://127.0.0.1:8848
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
dubbo.protocol.threads=200
dubbo.protocol.queues=100
dubbo.protocol.threadpool=cached
dubbo.provider.retries = 3
dubbo.provider.threadpool = cached
dubbo.provider.threads = 200
dubbo.provider.connections = 100
dubbo.scan.base-packages=org.sky.service

logging.config=classpath:log4j2.xml

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

		<!-- Dubbo Registry Nacos -->
		<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>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
		</dependency>

以及在application.properties文件中把相应的dubbo协议依旧使用dubbo,这是因为dubbo2.6中已经带入了nacos-registry了,因此就必须把dubbo.registry.address设成指向你本机的nacos启动实例(默认为8848端口)即可。

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

springboot的启动代码,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;
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
@EnableAutoConfiguration
public class DruidConfig {

	@ConfigurationProperties(prefix = "spring.datasource")
	@Bean
	public DruidDataSource dataSource() {

		return new DruidDataSource();

	}
}

制作一个自定义的全局Exception,DemoRpcRunTimeException

把它放置于common项目内

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
@Component
public 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->exception occured: " + exception.getMessage(),
					exception);
			throw new DemoRpcRunTimeException(exception);
		}
		// logger.error("DemoRpcRuntimeExceptionHandler side->exception occured: " +
		// exception.getMessage(), exception);
	}
}

开始进入核心provider Service端的制作。

ProductService接口

我们把它放置于common工程,这样consumer工程也就可以通过nacos的注册中心找到这个接口名,然后通过spring的invoke来对于远程的用于具体实现service逻辑的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 {
	@Autowired
	JdbcTemplate jdbcTemplate;

	@Override
	@Transactional
	public DubboResponse<ProductVO> addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException {
		DubboResponse<ProductVO> 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
					public 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("======>insert into t_product with product_id:" + newProdId);
				if (newProdId > 0) {
					jdbcTemplate.update(stockSql, newProdId, prod.getStock());
					logger.info("======>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;
	}

}

这个类目前是正常状态,我们先调用一把正常的provider到service端的过程然后接下来就来演示如何把exception远程传递到consumer端。

nacos-consumer工程搭建说明

先上pom.xml

<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">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.sky.demo</groupId>
	<artifactId>nacos-consumer</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>nacos-service</name>
	<description>消费者 Demo project for Spring Boot dubbo nacos</description>
	<parent>
		<groupId>org.sky.demo</groupId>
		<artifactId>nacos-parent</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.apache.dubbo</groupId>
			<artifactId>dubbo</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-framework</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.curator</groupId>
			<artifactId>curator-recipes</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-core</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.spockframework</groupId>
			<artifactId>spock-spring</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<exclusions><!-- 去掉默认配置 -->
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>com.lmax</groupId>
			<artifactId>disruptor</artifactId>
		</dependency>
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
		</dependency>
		<!-- Dubbo Registry Nacos -->
		<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>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
		</dependency>
		<!-- import sky common package -->
		<dependency>
			<groupId>org.sky.demo</groupId>
			<artifactId>skycommon</artifactId>
			<version>${skycommon.version}</version>
		</dependency>
	</dependencies>
	<build>
		<sourceDirectory>src/main/java</sourceDirectory>
		<testSourceDirectory>src/test/java</testSourceDirectory>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
		<resources>
			<resource>
				<directory>src/main/resources</directory>
			</resource>
			<resource>
				<directory>src/main/webapp</directory>
				<targetPath>META-INF/resources</targetPath>
				<includes>
					<include>**/**</include>
				</includes>
			</resource>
			<resource>
				<directory>src/main/resources</directory>
				<filtering>true</filtering>
				<includes>
					<include>application.properties</include>
					<include>application-${profileActive}.properties</include>
				</includes>
			</resource>
		</resources>
	</build>
</project>

nacos-consumer端的application.properties

server.port=8082
server.tomcat.max-connections=50
server.tomcat.max-threads=50
server.tomcat.uri-encoding=UTF-8
server.tomcat.max-http-post-size=0

#Dubbo provider configuration
dubbo.application.name=nacos-consumer
dubbo.registry.address=nacos://127.0.0.1:8848
#dubbo.consumer.time=120000

logging.config=classpath:log4j2.xml

同样,consumer端也需要连上本地的nacos实例。

另外多说一点的是,不要在consumer端去做类似dubbo通讯超时或者是一些个性化的dubbo参数设置。因为dubbo有3个核心参数集,provider, protocol, consumer。而在consumer做的设置由于这3者的优先级问题,它是会覆盖掉provider端的设置。如果是在大规模微服务开发场景中,每个consumer都做自己的个性化设置,这不利于全局上对系统性能进行集中统一的管控,因此这需要公司的架构师对这些规范进行provider端的统一管控,一定尽量避免在consumer端去设置本该属于central(provider)端的一些参数。

consumer端的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" })
@EnableAutoConfiguration
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

和provider端的naco-service没多少区别,注意要@EnableDubbo,要不然spring不会在项目启动时把consumer端给注册到nacos的注册中心去。

consumer端的Controller

这个consumer端正是我第一张图中的无状态的API层,这一层会有一堆tomcat/netty/jboss一类的东西,它们做的事就是路由API,以json格式向客户端(手机、网页、小程序)进行返回。这一层是不会去和DB、NOSQL、缓存一类的打交道的,它们要做的就是调用“后端”微服务的dubbo服务,因此我们在这一端基本以spring中的controller为主。

为了让consumer端可以调用provider端的service方法,必须在注入时加上@Reference注解,这样dubbos的consumer在注册进“注册中心”,如:nacos这一类东西时就知道要找哪个provider的service(残根-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 ResponseEntity<String> sayHello(@RequestBody String params) throws Exception {
		ResponseEntity<String> response;
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		Map<String, Object> resultMap = new HashMap<>();
		JSONObject requestJsonObj = JSON.parseObject(params);
		try {
			String name = getHelloNameFromJson(requestJsonObj);
			String answer = helloNacosService.sayHello(name);
			logger.info("answer======>" + answer);
			Map<String, String> result = new HashMap<>();
			result.put("result", answer);
			String resultStr = JSON.toJSONString(result);
			response = new ResponseEntity<>(resultStr, headers, HttpStatus.OK);
		} catch (Exception e) {
			logger.error("dubbo-clinet has an exception occured: " + e.getMessage(), e);
			String resultStr = e.getMessage();
			response = new ResponseEntity<>(resultStr, headers, HttpStatus.EXPECTATION_FAILED);
		}
		return response;

	}

	@PostMapping(value = "/addProductAndStock", produces = "application/json")
	public ResponseEntity<String> addProduct(@RequestBody String params) throws Exception {
		ResponseEntity<String> response = null;
		DubboResponse<ProductVO> dubboResponse;
		String returnResultStr;
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
		JSONObject requestJsonObj = JSON.parseObject(params);
		Map<String, Object> result = new HashMap<>();
		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 ResponseEntity<>(returnResultStr, headers, HttpStatus.OK);
			} else {
				result.put("message", "dubbo service ProductService get nullpoint exception");
				returnResultStr = JSON.toJSONString(result);
				response = new ResponseEntity<>(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 ResponseEntity<>(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;
	}

}

这个consumer相当的简单,直接通过远程接口调用dubbo得到一个返回。

运行例子

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

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

nacos-service运行示例:

nacos-consumer运行示例:

然后我们去nacos的管理界面查看一下,就能发现provider和consumer都注册成功了。

接着我们使用postman对consumer发一个json请求

得到返回如下所示

再看数据库中

这说明我们的dubbo+nacos搭建完全运行正常,接下来就要演示两种Exception的抛出了。

第1种:直接从provider端抛RuntimeException到consumer端

在provider端我们对ProductServiceImpl进行一个小修改如下:

我们写了一句:

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

我们前文说过,在provider端的service里一定要抛出RuntimeException才会让数据库事物回滚,但是我们也不用担心,还记得我们在nacos-service中已经注入了一个aop的拦截器叫“DemoRpcRuntimeExceptionHandler”吗?

它的作用就是拦住一切Exception然后把它转化成RuntimeException。

好,我们加完这一句话后重新依次运行nacose-service和nacos-consumer。然后同样通过postman来访问http://localhost:8082/nacosconsumer/addProductAndStock,然后我们使用新的产品品名,post请求体内的报文如下所 示:

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

看,我们这次请求过去后直接在response中出现的是什么?

来看nacos-service端的日志,这是我们在provider端人为手工抛出的一条日志:

来看nacos-consumer端的日志,我们可以看到provider端的异常甚至包括它的stackTrace信息都已经传递到了consumer端了:

这样的话consumer端的开发人员一看传过来了这个错误就会跑到dubbo开发团队处吼一下:喂,生产上有一个bug,你看这就是你们provider端抛出来的,改吧!

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

t_product表没有插入coffee的记录

t_stock表也没有插入相关coffee的库存

说明Exception确实是被转成了RuntimeException并被spring框架所捕捉然后进行了一次回滚。

第2种:把一切Exception包装成json返回报文不向consumer端输出异常具体信息

我们希望把provider端的Exception包装成如下这种json报文:

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

转而把:

异常的stackTrace以log方式记录在provider端,在出了问题让provider端的开发人员通过日志查询和定位问题即可。

为什么还有这种做法?

很简单,因为stackTrace是异常追溯,调用到了jvm的栈内信息了,这个是“很重”的一件活 。我们把一堆的异常Exception通过provider和consumer端抛来抛去,本来我们用dubbo就是用来做微服务的、就是为了应对大规模的并发请求的、就是为了做系统的弹性伸缩和高冗余的,你还在用这么大一陀stackTrace在两端传来传去不说,还要加上传时序列化、接到时反序列化,这不是增加了系统的开销吗?

下面直接show me the code,在nacos-service的org.sky.config处增加一个aop叫“ServiceExceptionHandler”,代码如下:

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
@Aspect
public 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 > 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与系统message“包”在DubboResponse内

等等等等。。。。。。出问题了!此处还没全完,为什么?

一切Exception?这样一来那么包完后在Service层岂不是没有Exception被抛出了?如果Service方法涉及到数据库操作没有抛RuntimeException时数据库事务怎么回滚?

这才有了我们在这个handler类中有这么一段内容,它的作用就是对一切有@Transactional注解的Service方法在其出错时,还是照样要抛"RuntimeException",对于其它的就都包成DubboResponse返回给调用者了(如下对于非事务型Service方法的异常的统一包装):

	@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());
		}
	}

好了,然后我们现在重新启动我们的系统,我们再来看下面的运行示例。。。。。。

等!

忘记一件事,下面我给出位于“common”工程中的ProductVO和DubboResponse这两个类的结构先,我写博文不喜欢“藏”一手。

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 DubboResponse<T> 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 <T> 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;
	}
}

“正常”据有@Transactional的Service方法抛异常演示:

现在我们把nacose-service和nacos-consumer运行起来看效果,试图插入一个新的prouct:

得到返回:

再来看nacos-service端、nacos-consumer端以及数据库

可以看到provider与consumer端都正确抛错且数据库中没有插进去值。

“不正常”的不含有Transactional的(普通)Service方法抛异常被封装演示:

我们现在做点小手脚,我们把provider端的“addProductAndStock(ProductVO prod)”方法上的@Transactional拿走来看看效果。

	@Override
	public DubboResponse<ProductVO> addProductAndStock(ProductVO prod) throws DemoRpcRunTimeException {
		DubboResponse<ProductVO> 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-consume端做一个小小的修改,如下所示,让consumer端直接把provider端组装好的{ "message" : "xxxx..."}显示在“最前端”(一切通过 nginx端来访问consumer,consumer再通过provider调用数据库,在这边我们使用的是postman)。

然后我们来运行起来看一下效果:

我们可以看到,这一次在去除了@Transactional注解后,当Service方法抛错时,请求端拿到的是我们经过包装过的DubboResponse内的东西

provider端包装普通Service抛出的异常的核心代码:

	@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());
		}
	}

我们查看我们的Provider端,它正是通过上述代码catch(Exception e)中的这一段来进行服务端日志的记错和把错误包装后返回给到consumer端的,就是下面这两句:

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

来看看nacos-service端的日志输出

来看看nacos-consumer端的日志输出

哈哈,这次nacos-consumer端无任何抛错,因为错误已经被provider端包装起来了。

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

因为前文说了,对于无@Transactional注解的方法,我们的aop handler类会把一切错误 “吃掉”,在后台仅作记录然后包成正常返回结果给到consumer端的,那么provider端的Service方法既无RuntimeException抛出,何来回滚?

当然是插入成功的!

t_product表

t_stock表

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

到此这篇关于阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式的文章就介绍到这了,更多相关nacos+springboot+dubbo统一处理异常内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot集成SSM、Dubbo、Redis、JSP的案例小结及思路讲解

    1.思路讲解 这个案例其实就是SpringBoot集成SSM.Dubbo.Redis.JSP,看起来集成了一大堆,感觉挺麻烦的,但实际上并不是很麻烦,下面我来说一下我的思路: 接口工程:存放实体bean和业务接口 服务提供者:它是一个SpringBoot框架web项目,集成MyBatis.Redis 1)pom文件中添加依赖:MyBatis.MySQL驱动.Dubbo.zookeeper.redis.接口工程. 2)配置springboot核心配置文件(连接数据库.连接redis.dubbo.内

  • springboot集成dubbo注解版的示例代码

    工作中用springboot搭建项目,用dubbo做远程调用.springboot提倡注解配置和java配置,本文是基于dubbo最新版本2.6.3,使用注解方式的示例. 本文假定你已经有springboot和dubbo的使用经验. dubbo简介 dubbo是阿里巴巴开源的分布式服务框架,一般使用dubbo的RPC调用.但2016年停止维护,现在使用的2.8.4版本其实是当当维护的dubbox.2017年8月阿里又重启维护dubbo,并从2.5.7版本开始支持注解配置. 准备 此示例使用gra

  • springboot2.2.2集成dubbo的实现方法

    最近在学习dubbo,想着作一些笔记,从来没有在csdn上面写过博客,今天献出第一次,哈哈,直接上代码 一.创建父工程 <?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" xs

  • 详解SpringBoot+Dubbo集成ELK实战

    前言 一直以来,日志始终伴随着我们的开发和运维过程.当系统出现了Bug,往往就是通过Xshell连接到服务器,定位到日志文件,一点点排查问题来源. 随着互联网的快速发展,我们的系统越来越庞大.依赖肉眼分析日志文件来排查问题的方式渐渐凸显出一些问题: 分布式集群环境下,服务器数量可能达到成百上千,如何准确定位? 微服务架构中,如何根据异常信息,定位其他各服务的上下文信息? 随着日志文件的不断增大,可能面临在服务器上不能直接打开的尴尬. 文本搜索太慢.无法多维度查询等 面临这些问题,我们需要集中化的

  • SpringBoot系列教程之dubbo和Zookeeper集成方法

    今日学习新的内容:dubbo   dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,一款高性能.轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. zookeeper   zooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护.域名服务.分布式同步.组服务等. SpringBoot整合dubbo和Zookeeper 1.了解Dubbo基本

  • 详解Springboot整合Dubbo之代码集成和发布

    本文介绍了Springboot整合Dubbo之代码集成和发布,分享给大家,具体如下: 1. boot-dubbo-api相关 打开boot-dubbo-api项目,正在src/main/java下创建一个包,并创建你需要dubbo暴露的接口TestService.java,并创建一个实体类用于测试User.java.如下图所示: 创建文件和包结构 User.java package com.boot.domain; import lombok.Data; import java.io.Seria

  • 阿里nacos+springboot+dubbo2.7.3统一处理异常的两种方式

    目录 1.为什么要抛异常? 2.给出解决方案 3.两种抛异常的实例解说 dubbo工程搭建 在网上很多关于dubbo异常统一处理的博文,90%都是抄来抄去.大多都是先上一段dubbo中对于异常的统一处理的原码,然后说一堆的(甚至有12345,五种)不靠谱方案,最后再说“本篇使用的是方案4”,然后再对所谓的方案4写了一段文字,最后还说不清!!! 本篇解决方案不会那么罗里吧嗦也不会贴dubbo源码来凑字数,我就直接从刚结束不久的双11保卫战性能全链路优化中我们的面对10万级别TPS的方案中提取的代码

  • 阿里云服务器手动实现mysql双机热备的两种方式

    一.概念 1.热备份和备份的区别 热备份指的是:High Available(HA)即高可用,而备份指的是Backup,数据备份的一种.这是两种不同的概念,应对的产品也是两种功能上完全不同的产品.热备份主要保障业务的连续性,实现的方法是故障点的转移.而备份,主要目的是为了防止数据丢失,而做的一份拷贝,所以备份强调的是数据恢复而不是应用的故障转移. 2.什么是双机热备? 双机热备从广义上讲,就是对于重要的服务,使用两台服务器,互相备份,共同执行同一服务.当一台服务器出现故障时,可以由另一台服务器承

  • springboot项目部署在linux上运行的两种方式小结

    springboot部署项目在linux的两种方式 可以选择 war包方式或者jar包方式(个人推荐使用jar方式) 1.springboot的jar包方式 因为idea默认就是jar打包方式所以直接使用maven工具按照步骤点击就可以直接打包 打包之前别忘了修改好你的配置文件,别到时候端口号冲突启动不了(多个同样的端口号),假如要使用linux上的数据库也要提前修改好密码 然后控制台就会输出执行过程,不用管,最后结束了就会这如图红框处找到输出路径. 找到这个文件把他扔到你的linux虚拟机里,

  • SpringBoot 在项目启动之后执行自定义方法的两种方式小结

    目录 SpringBoot 项目启动之后执行自定义方法的两种方式 方式一 实现 CommandLineRunner 接口 方式二 实现 ApplicationRunner 接口 二者区别 Springboot 项目启动后执行某些自定义代码 SpringBoot 项目启动之后执行自定义方法的两种方式 在测试配置中心的配置时,想在项目启动成功之后打印配置项,然后需要执行自定义的类 一般项目中也会在这个地方进行初始化数据的一些操作 方式一 实现 CommandLineRunner 接口 自定义类并实现

  • SpringBoot实现全局和局部跨域的两种方式

    前言 在如今前后端分离的开发模式下,跨域是一个非常经典的问题,解决的方式也有很多,比如代理服务器,使用JSONP 我之前也写过一篇解决跨域问题的文章,感兴趣的可以参考:解决Vue前后端跨域问题的多种方式 上面两种解决跨域的办法都是在前端角度的,这次站在后端角度,从全局和局部两个方面解决跨域问题 什么是跨域 所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样.这里的域指的是这样的一个概念:我们认为若协议 + 域名 + 端口号均相同,那么就是同域. 解决跨域 后端解决跨域,主要借

  • springboot全局日期格式化的两种方式

    方式一是配置参数 参数配置的方式就是在json序列化的时候,当字段为日期类型的时候的format类型,就相当于在所有日期字段上加了一个注解 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss"),但是每个字段都加注解太麻烦,所以直接使用全局配置来实现 参数配置也分为两种配置 第一种是yml的配置 spring: jackson: #参数意义: #JsonInclude.Include.A

  • 详解Springboot之接收json字符串的两种方式

    第一种方式.通过关键字段@RequestBody,标明这个对象接收json字符串.还有第二种方式,直接通过request来获取流.在spring中,推荐使用. 代码地址 https://gitee.com/yellowcong/springboot-demo/tree/master/springboot-json 项目结构 其实项目里面没啥类容,就是一个控制器和pom.xml配置 配置fastjson 添加fastjson的依赖到pom.xml中 <dependency> <groupI

  • 部署springboot项目到云服务器的两种方式(jar+war)

    目录 方式一.以jar文件运行 添加maven依赖 将项目打包 将jar文件放到服务器 在服务器运行jar文件 浏览器访问 2.以war方式部署 修改打包方式 移除嵌入式tomcat插件 打包 部署 浏览器访问 总结 springboot版本:2.0.3.RELEASE 云服务器:阿里云ECS CentOS 7.3 64位 IDE:IntelliJ IDEA 服务器远程连接工具:Xshell 5 方式一.以jar文件运行 添加maven依赖 Spring Boot 默认以jar包方式运行, 可以

  • SpringBoot用实体接收Get请求传递过来的多个参数的两种方式

    目录 一.Controller层不带任何注解接收参数 二.Controller层通过@ModelAttribute接收参数 最近项目中Controller层查询接口需要通过实体来接受前端传过来的多个参数(Get请求),这个问题困扰了我很久,之前在第二家公司的时候,就因为我后端是Get请求,并且是通过实体去接收前端参数的,导致我当天上线搞到半夜没搞好,这次又遇到,势必解决它. 一年前,通过查看大量的坑爹文章,发现网上没有一篇有效的,这次通过阿里主管的协助,成功的通过实体接收到了Get请求传递过来的

  • springboot处理异常的5种方式

    目录 1.自定义错误页面 2.@ExceptionHandle 注解处理异常 3.@ControllerAdvice+@ExceptionHandler 注解处理异常 4.配置 SimpleMappingExceptionResolver 处理异常 5.自定义 HandlerExceptionResolver 类处理异常 通用异常处理: 1.自定义异常枚举类 2.自定义异常类 3.自定义异常结果处理类 4.全局异常处理 程序的异常:Throwable 严重错误问题:Error     我们不处理

随机推荐