前言
大家好,今天开始给大家分享 — Dubbo 专题之 Dubbo 令牌验证和优雅停机。在前面的章节中我们介绍了 Dubbo 延迟和粘滞连接,了解了什么是延迟和粘滞连接以及日常的使用场景和实现原理,同时我们知道延迟连接是在使用实例对象的时候才创建通讯连接,粘滞连接是尽可能的使用已经创建的连接,它们都有类似减少连接创建的作用。那本章节讨论一些轻松的话题就是令牌验证和优雅停机,那什么是令牌呢?以及它的作用是什么呢?。那就让我们快速开始吧!
1. 令牌验证和优雅停机简介
首先介绍什么是令牌,我们通过一个生活中的小例子讨论什么是令牌。例如:我们小伙伴经常坐火车出去玩那得首先购买火车票吧,然后我们拿着火车票到安检口进行检票,检票成功我们就可以乘坐火车,如果检票失败则不能乘坐。这里的火车票就是我们所说的令牌,只有拿到有效的令牌我们才有权限或资格做后续的事情。
那什么是优雅停机呢?简单的理解就是在应用正常处理完成过后才退出应用,但是如果我们通过kill -9 PID
等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID
时,才会执行。在我的 Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机。
2. 配置方式
Dubbo 主要通过 XML 、注解、配置文件的方式配置:
2.1 令牌验证配置方式
XML 配置方式:
- 全局设置
<!--随机token令牌,使用UUID生成-->
<dubbo:provider token="true" />
或者
<!--固定token令牌,相当于密码-->
<dubbo:provider token="123456" />
- 服务级别设置
<!--随机token令牌,使用UUID生成-->
<dubbo:service interface="com.muke.dubbocourse.common.api.BookFacade" token="true" />
或者
<!--固定token令牌,相当于密码-->
<dubbo:service interface="com.muke.dubbocourse.common.api.BookFacade" token="123456" />
注解方式:服务级别设置
@DubboService(token = "true")
//@Service(token = "true")
配置文件:全局配置
dubbo.provider.token=true
2.2 优雅停机配置方式
Dubbo 中主要通过属性配置和编码方式:
属性配置:
# dubbo.properties
dubbo.service.shutdown.wait=15000
编码方式:
DubboShutdownHook.destroyAll();
**Tips:**使用 tomcat 等容器部署的场景,建议通过扩展 ContextListener 等自行调用以下代码实现优雅停机
3. 使用场景
下面我们看看 Dubbo 中令牌验证和优雅停机的使用场景。首先 Dubbo 中的场景描述是令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者。优雅停机在 Dubbo 中注意也是用于资源的回收处理等。下面我们讨论常用的使用场景:
-
令牌验证使用场景:从 Dubbo 令牌验证场景描述中我们可以知道可以通过令牌对消费者访问接口进行授权访问,即我们可以通过自行拓展对注册信息进行动态调整以支持接口权限动态访问控制。
-
优雅停机使用场景:在 Dubbo 应用退出时关闭底层的网络连接资源、缓存操作资源等等。
4. 示例演示
下面我们以获取图书列表为例进行实例演示主要演示令牌验证。项目结构如下:
下面我们看看服务消费端的配置dubbo-provider-xml.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-provider" metadata-type="remote"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<bean id="bookFacade" class="com.muke.dubbocourse.tokenverify.provider.BookFacadeImpl"/>
<!--暴露本地服务为Dubbo服务-->
<dubbo:service interface="com.muke.dubbocourse.async.api.BookFacade" ref="bookFacade" token="12345"/>
</beans>
上面我们开启服务级别的 token
配置 token="12345"
设置 token
固定值。下面我们看看服务消费端配置文件dubbo-consumer-xml.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="demo-consumer" logger="log4j"/>
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!--1.通过注册中心获取服务引用-->
<!-- <dubbo:reference id="bookFacade" interface="com.muke.dubbocourse.common.api.BookFacade">-->
<!-- </dubbo:reference>-->
<!--2.url直接指定服务提供者所做机器以及端口-->
<dubbo:reference id="bookFacade"
interface="com.muke.dubbocourse.common.api.BookFacade" url="dubbo://localhost:20890">
</dubbo:reference>
</beans>
上面的 XML 中如果使用第一种方式可以正常访问我们的bookFacade
服务,如果使用第二种直连的方式绕开注册中心则会得到以下错误:
Caused by: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: /192.168.101.8:20890Caused by: java.net.ConnectException: Connection refused at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
因为我们的服务开启 token
验证如果绕开注册中心直接调用则 token
会验证不通过。
5. 原理分析
下面我们主要分析 Dubbo 中的令牌验证源码。
token 的解析过程:
当我们的服务服务端的服务暴露时会调用方法org.apache.dubbo.config.ServiceConfig #doExportUrlsFor1Protocol
其核心代码如下:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
//...
//获取在服务提供端配置的 token 值 ,例如:token="123456"
if(ConfigUtils.isEmpty(token) && provider != null) {
token = provider.getToken();
}
if (!ConfigUtils.isEmpty(token)) {
//如果配置值为:true 或 default 则产生一个随机的token值
if (ConfigUtils.isDefault(token)) {
map.put(TOKEN_KEY, UUID.randomUUID().toString());
} else {
//设置为配置的token值
map.put(TOKEN_KEY, token);
}
}
//init serviceMetadata attachments
serviceMetadata.getAttachments().putAll(map);
//...
}
在上面的代码中我们可以看到从我们的配置中获取token
值并注册到注册中心,如果配置值为:true
或 default
则产生一个随机的token
值,否则使用配置的token
值。
token使用过程:
在服务消费端调用请求到达服务提供端前会经过一些了的过滤器,其中就包括了对token
的校验过滤器。核心代码如下:
public class TokenFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation inv)
throws RpcException {
//获取token参数值
String token = invoker.getUrl().getParameter(TOKEN_KEY);
if (ConfigUtils.isNotEmpty(token)) {
Class<?> serviceType = invoker.getInterface();
Map<String, String> attachments = inv.getAttachments();
String remoteToken = (attachments == null ? null : attachments.get(TOKEN_KEY));
//验证消费者携带的token与服务提供端的token是否相等
if (!token.equals(remoteToken)) {
throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost());
}
}
return invoker.invoke(inv);
}
}
通过上面的过滤器对token
进行验证,如果验证通过则调用后面的执行器,如果验证失败则抛出异常。
6. 小结
在本小节中我们主要学习了 Dubbo 中的令牌验证和优雅停机,以及常见的使用场景和使用方式。同时我们也通过示例演示和源码分析对 Dubbo 的令牌验证原理进行解析。其中令牌验证核心思想就是通过服务提供端提供的token
或者随机产生的token
放入注册中心进行管理,然后服务消费端获取token
令牌并且在调用服务提供端时携带 token
,服务提供端根据消费端携带的token
进行验证。在优雅停机方面 Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机。
本节课程的重点如下:
-
理解 Dubbo 中令牌验证和优雅停机
-
了解令牌验证和优雅停机使用方式
-
了解令牌验证和优雅停机使用场景
-
了解 Dubbo 中令牌验证原理
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。