springboot2教程系列
SpringBoot与dubbo整合的三种方式
1)导入dubbo-starter,在application.properties配置属性,使用@Service【暴露服务】使用@Reference【引用服务】,使用@EnableDubbo注解。但是没有提供dubbo:method标签的对应注解。
2)保留dubbo xml配置文件,导入dubbo-starter,使用@ImportResource导入dubbo的配置文件即可。不再使用@Service【暴露服务】使用@Reference【引用服务】,不使用@EnableDubbo注解。
3)使用注解API的方式,将每一个组件手动创建到容器中,让dubbo来扫描其他的组件,使用@Service【暴露服务】使用@Reference【引用服务】,使用@EnableDubbo注解。
第一种实现
添加maven依赖
<!--dubbo-springBoot依赖-->
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.8</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
提供者
定义接口
public interface RemoteUserService {
String sayHello(String name);
}
接口实现
@Component
@Service(version = "2.6.0",timeout = 10000,interfaceClass = RemoteUserService.class)
public class RemoteUserServiceImpl implements RemoteUserService {
@Value("${server.port}")
private Integer port;
@Override
public String sayHello(String name) {
System.out.println("provider");
return "hi!server port:"+port+";name="+name;
}
}
启动类添加@EnableDubboConfiguration
消费者
@RestController
public class RemoteUserController {
@Reference(version = "2.7.0",async=true,sent=false)
private RemoteUserService remoteUser;
}
启动类添加@EnableDubboConfiguration
配置文件
# Tomcat
server:
tomcat:
max-threads: 1000
min-spare-threads: 10
port: 8091
spring:
dubbo:
appname: consumer
registry: zookeeper://10.10.2.138:2181?backup=10.10.2.139:2181,10.10.2.137:2181
protocol:
name: dubbo
port: 20883
#启动时检查(check),关闭所有服务的启动时检查 @Reference(check = false)
spring.dubbo.consumer.check: false
#超时(timeout,默认为1000), @Reference(timeout=XXX)
spring.dubbo.consumer.timeout: 10000
#超时后,重试次数(retries) @Reference(retries=XXX)
spring.dubbo.consumer.retries: 1
#是否注册服务
spring.dubbo.server: false
spring.application.name: dubbo-consumer
第二种实现
启动类
@SpringBootApplication
@ImportResource(value = {"classpath:dubbo.xml"})
@EnableDubbo
public class DubboProviderXMLApplication {
public static void main(String[] args){
SpringApplication app = new SpringApplication(DubboProviderXMLApplication.class);
// app.setWebEnvironment(false);
app.run(args);
}
}
dubbo.xml放到resources目录下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 配置可参考 http://dubbo.io/User+Guide-zh.htm -->
<!-- 服务提供方应用名,用于计算依赖关系 -->
<dubbo:application name="dubbo-provider" owner="dubbo-provider" />
<!-- 定义 zookeeper 注册中心地址及协议 -->
<dubbo:registry protocol="zookeeper" address="zookeeper://10.10.2.138:2181?backup=10.10.2.139:2181,10.10.2.137:2181" client="zkclient" />
<!-- 定义 Dubbo 协议名称及使用的端口,dubbo 协议缺省端口为 20880,如果配置为 -1 或者没有配置 port,则会分配一个没有被占用的端口 -->
<dubbo:protocol name="dubbo" port="20889" />
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="cn.myframe.service.RemoteUserService"
ref="user2Service" timeout="10000" />
</beans>
第三种实现
提供者
@Component
public class ProviderDubboConfig {
@Autowired(required = false)
RemoteUserService userService;
@Autowired(required = false)
DubboProperties dubboProperties;
@PostConstruct
public void userServiceConfig(){
ServiceConfig<RemoteUserService> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(RemoteUserService.class);
serviceConfig.setRef(userService);
serviceConfig.setVersion("2.6.0");
serviceConfig.setApplication(dubboProperties.getApplication());
serviceConfig.setRegistry(dubboProperties.getRegistry());
serviceConfig.setProtocol(dubboProperties.getProtocol());
// serviceConfig.setProtocols(dubboProperties.getProtocols());
//配置每一个method的信息
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("sayHello");
methodConfig.setTimeout(1000);
//将method的设置关联到service配置中
List<MethodConfig> methods = new ArrayList<>();
methods.add(methodConfig);
serviceConfig.setMethods(methods);
serviceConfig.export();
// return serviceConfig;
}
}
消息者
@Autowired
DubboProperties dubboProperties;
@Bean
public RemoteUserService remoteUserService(){
ReferenceConfig<RemoteUserService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setInterface(RemoteUserService.class);
referenceConfig.setTimeout(5000);
referenceConfig.setVersion("2.6.0");
referenceConfig.setRetries(3);
referenceConfig.setCheck(false);
referenceConfig.setRegistry(dubboProperties.getRegistry());
referenceConfig.setApplication(dubboProperties.getApplication());
return referenceConfig.get();
}
常用配置
启动时检查
spring.dubbo.consumer.check:设置为false时,启动时检查(check),关闭所有服务的启动时检查。设置true时,如果服务没启动,启动会报错。也可以单独给某个接口设置@Reference(check = false)
,而且覆盖application.yml中的spring.dubbo.consumer.check
配置。
集群容错模式
spring.dubbo.consumer.Cluster: 可以自行扩展集群容错策略,@Reference(cluster = "failover ")
。
failover
失败自动切换 ,默认策略。当出现失败,重试其它服务器 ,spring.dubbo.consumer.retries
设置重试次数(不含第一次) 。
failfast
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录
failsafe
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
failback
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
forking
并行调用多个服务器,只要一个成功即返回。
broadcast
广播调用所有提供者,逐个调用,任意一台报错则报错 。
负载均衡策略
spring.dubbo.consumer.loadbalance: 均衡策略 。@Reference(loadbalance = "roundrobin")
random
随机,按权重设置随机概率 ,缺省
roundrobin
轮询,按公约后的权重设置轮询比率。存在慢的提供者累积请求的问题
leastactive
最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差 。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大
consistenthash
一致性 Hash,相同参数的请求总是发到同一提供者 ,缺省只对第一个参数 Hash
直连提供者
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者 。
spring.dubbo.consumer.url: dubbo://localhost:20881
,或者@Reference(url = "dubbo://localhost:20881")
多协议
application.yml配置,可能配置协议有rmi
http
spring:
dubbo:
appname: provider
registry: zookeeper://10.10.2.138:2181?backup=10.10.2.139:2181,10.10.2.137:2181
protocols:
- name: rmi
port: 7885
hose: 127.0.0.1
- name: dubbo
port: 28802
hose: 127.0.0.1
- name: http
port: 8500
server: servlet #默认是jetty
同时协议有使用才会有效
@Service(version = "2.7.0",interfaceClass = RemoteUserService.class,protocol={"http","rmi"})
服务分组
当一个接口有多种实现时,可以用 group 区分。
生产者
@Service(version = "2.7.0",group = "service2",timeout = 10000,interfaceClass = RemoteUserService.class)
public class RemoteUserService2Impl implements RemoteUserService {...}
@Service(version = "2.7.0",group = "service1",timeout = 10000,interfaceClass = RemoteUserService.class)
public class RemoteUserServiceImpl implements RemoteUserService {...}
消费者
@Reference(version = "2.7.0",group = "*") #任意组
多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
生产者
@Service(version = "2.6.0",timeout = 10000,interfaceClass = RemoteUserService.class)
public class RemoteUserService2Impl implements RemoteUserService {...}
@Service(version = "2.7.0",timeout = 10000,interfaceClass = RemoteUserService.class)
public class RemoteUserServiceImpl implements RemoteUserService {...}
消费者
@Reference(version = "2.7.0") #version="*" 表示任意版本
结果缓存
-
lru
基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。 -
threadlocal
当前线程缓存,比如一个页面渲染,用到很多 portal,每个 portal 都要去查用户信息,通过线程缓存,可以减少这种多余访问。 -
jcache
与 JSR107 集成,可以桥接各种缓存实现。
lru
的缺省cache.size为1000
@Reference(version = "2.7.0",cache = "lru")
异步调用
@Reference(version = "2.7.0",async = true)
-
sent="true"
等待消息发出,消息发送失败将抛出异常。 -
sent="false"
不等待消息发出,将消息放入 IO 队列,即刻返回。
@Reference(version = "2.7.0",async = true,sent = true)
隐式参数
消费者
@RequestMapping(value="/dubbo/attachment/{name}")
public String attachment(@PathVariable("name") String name){
RpcContext.getContext().setAttachment("name", "1");
String result=remoteUser.attachment();
return result;
}
注意:path, group, version, dubbo, token, timeout 几个 key 是保留字段,请使用其它值
参数回调
参数回调方式与调用本地 callback 或 listener 相同,只需要在 Spring 的配置文件中声明哪个参数是 callback 类型即可。Dubbo 将基于长连接生成反向代理,这样就可以从服务器端调用客户端逻辑 。
服务接口示例
public interface CallbackService {
void addListener(String key, CallbackListener listener);
}
public interface CallbackListener {
void changed(String msg);
}
@Component("callbackService")
public class CallbackServiceImpl implements CallbackService{
public final static Map<String, CallbackListener> listeners =
new ConcurrentHashMap<String, CallbackListener>();
public void addListener(String key, CallbackListener listener) {
listeners.put(key, listener);
listener.changed("key:"+key+",调用回调方法"); // 发送变更通知
}
}
服务提供者配置
@PostConstruct
public void callBackrServiceConfig(){
ServiceConfig<CallbackService> serviceConfig = new ServiceConfig<>();
serviceConfig.setInterface(CallbackService.class);
serviceConfig.setRef(callbackService);
serviceConfig.setVersion("2.6.0");
serviceConfig.setApplication(dubboProperties.getApplication());
serviceConfig.setRegistry(dubboProperties.getRegistry());
serviceConfig.setProtocol(dubboProperties.getProtocol());
serviceConfig.setCallbacks(100); //设置回调参数个数
//配置每一个method的信息
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("addListener");
ArgumentConfig argumentConfig = new ArgumentConfig();
argumentConfig.setIndex(1);
argumentConfig.setCallback(true);
methodConfig.setArguments(Arrays.asList(argumentConfig));
//将method的设置关联到service配置中
List<MethodConfig> methods = new ArrayList<>();
methods.add(methodConfig);
serviceConfig.setMethods(methods);
serviceConfig.export();
}
触发回调
@GetMapping("/listener")
public String listener(){
for(Map.Entry<String, CallbackListener> entry : CallbackServiceImpl.listeners.entrySet()) {
entry.getValue().changed("key:" + entry.getKey() + ",调用回调方法");
}
return "success";
}
服务消费者配置示例
@Reference(version = "2.6.0")
private CallbackService callbackService;
服务消费者调用
@RequestMapping(value="/dubbo/listener/{name}")
public void listener(@PathVariable("name") String name){
callbackService.addListener(name,new CallbackListener(){
public void changed(String msg) {
System.out.println("callback1:" + msg);
}
});
}
事件通知
在调用之前、调用之后、出现异常时,会触发 oninvoke
、onreturn
、onthrow
三个事件,可以配置当事件发生时
服务提供者与消费者共享服务接口
public interface IDemoService {
public Project get(String projectName);
}
服务提供者实现
@Component
@Service(version = "2.7.0",timeout = 10000,interfaceClass = IDemoService.class)
public class NormalDemoService implements IDemoService{
@Override
public Project get(String projectName) {
return new Project(projectName);
}
}
服务消费者 Callback 接口和实现
public interface Notify {
public void onreturn(Project project, String name);
public void onthrow(Throwable ex, String name);
}
@Component
public class NotifyImpl implements Notify {
public Map<String, Project> ret = new HashMap<String, Project>();
public Map<String, Throwable> errors = new HashMap<String, Throwable>();
public void onreturn(Project project, String name) {
System.out.println("onreturn:" + project+",name:"+name);
ret.put(name, project);
}
public void onthrow(Throwable ex, String name) {
errors.put(name, ex);
}
}
服务消费者 Callback 配置
@Configuration
public class DubboConfig {
@Autowired
NotifyImpl notify;
@Autowired
DubboProperties dubboProperties;
@Bean
public IDemoService demoService(){
ReferenceConfig<IDemoService> referenceConfig = new ReferenceConfig<>();
referenceConfig.setInterface(IDemoService.class);
referenceConfig.setTimeout(5000);
referenceConfig.setVersion("2.7.0");
referenceConfig.setRetries(3);
referenceConfig.setRegistry(dubboProperties.getRegistry());
referenceConfig.setApplication(dubboProperties.getApplication());
//配置每一个method的信息
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("get");
methodConfig.setTimeout(1000);
methodConfig.setAsync(true);
methodConfig.setOnreturn(notify);
methodConfig.setOnreturnMethod("onreturn");
//将method的设置关联到service配置中
List<MethodConfig> methods = new ArrayList<>();
methods.add(methodConfig);
referenceConfig.setMethods(methods);
return referenceConfig.get();
}
}
callback
与 async
功能正交分解,async=true
表示结果是否马上返回,onreturn
表示是否需要回调。
两者叠加存在以下几种组合情况 [2]:
- 异步回调模式:
async=true onreturn="xxx"
- 同步回调模式:
async=false onreturn="xxx"
- 异步无回调 :
async=true
- 同步无回调 :
async=false
测试代码
@Autowired
private IDemoService demoService;
@RequestMapping(value="/dubbo/notity/{name}")
public String notity(@PathVariable("name") String name){
demoService.get(name);
return "success";
}
本地伪装(服务降级)
通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
@Reference(version = "2.7.0",mock = "cn.myframe.service.MockUserServicImpl")
提供 Mock 实现
public class MockUserServicImpl implements RemoteUserService {
@Override
public String sayHello(String name) {
return "error";
}
}
调用
@RequestMapping(value="/dubbo/sayhello/{name}")
public String sayHello(@PathVariable("name") String name){
String result=remoteUser.sayHello(name);
return result;
}
当提供者服务不可用时,返回error
并发控制
@Service(version = "2.7.0",interfaceClass = RemoteUserService.class,executes=10)
服务器端并发执行(或占用线程池线程数)不能超过 10 个
@Service(version = "2.7.0",interfaceClass = RemoteUserService.class,actives = 10)
每客户端并发执行(或占用连接的请求数)不能超过 10 个
粘滞连接
粘滞连接用于有状态服务,尽可能让客户端总是向同一提供者发起调用,除非该提供者挂了,再连另一台。
@Reference(sticky = true)