Spring Cloud和Dubbo整合开发笔记(1)

一、需求背景:

  1. 公司内部老项目微服务技术栈使用Dubbo, 新项目技术栈使用主流的Spring Cloud相关组件开发,新旧项目涉及交互调用,无法直接通信数据传递。
  2. 老项目基于Dubbo,重构代码升级使用Spring Cloud,改造升级要求成本最低,不影响现有系统运行。

二、Dubbo和Spring Cloud 的比较

  首先Dubbo是一个分布式服务框架,以及SOA治理方案。它的功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等,它是著名的阿里服务治理的核心框架。Spring Cloud更加关心为开发人员提供开箱即用的一系列常见的分布式工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线),它是基于轻量级框架Spring家族,维护版本速度相对较快。

想深入了解的朋友,可以参考这篇文章专门分析了两者的区别:听听八年阿里架构师怎样讲述Dubbo和Spring Cloud微服务架构  

改造思路:Spring Cloud和Dubbo用于协调服务组件需要进行统一,使得Dubbo服务和Spring Cloud 服务能够相互感知。其次统一微服务之间的通信协议,Spring Cloud使用Http协议,Dubbo支持Dubbo,Hessian,rmi,http,webservice,thrift,redis,rest,memcached协议;数据传输载体或者说格式在网络中也是统一的,和调用的服务架构无关。改造前需要明确的是两种架构Spring Cloud和Dubbo在项目中的主次,不然容易造成开发人员使用API困惑,代码接口混乱,其次不要急于期望短期将架构统一,改造完整,特别是Spring Cloud带来了项目开发更多的环节,更多的组件(意味着有更多的坑)。

改造方案:

  • 传统方案:保留完整的Dubbo老系统,Dubbo服务不需要向SpringCloud组件注册服务,通过Ribbon/Fegin调用Dubbo服务暴露的Restful Api.缺点也明显,需要人工维护Dubbo服务和Spring Cloud服务的关系,当Dubbo服务较多时,不同的环境配置太多。
  • 传统方案:借助SideCar支持多语言的特性,连接Dubbo和Spring Cloud底层使用Sidecar交互,同时Dubbo也可以将信息传播到Eureka上面。缺点明显,需要每个Dubbo服务节点额外配置Sidecar服务节点,同时增加了链路的长度。

我的方案:Spring Cloud和Dubbo的服务中心选择阿里的Nacos,它是一个动态服务发现、配置管理和服务管理平台,为什么不选择使用Zookeeper,因为zookeeper是个CP系统,强一致性。如果其中master挂掉,此时zookeeper集群会进行重新选举,不能提供服务列表信息的服务,其次zookeeper通过tcp不能准确判断服务此时是否可用,数据库挂了,数据库连接池满了等也能提供TCP信息。通信协议我选择Http协议,Dubbo2.6之后支持http协议,数据格式使用Json,前端无需根据服务区分数据格式解析。

Nacos支持部署的模式有单机,集群,多集群模式,Nacos 0.8之后支持数据库持久化,可以方便看见服务信息的前后变化。单机模式很简单启动,下载最新版Nacos:https://github.com/alibaba/nacos/releases ,解压直接运行bin/startup.sh或者startup.cmd即可。

Nacos不仅提供服务发现和服务健康监测,它也提供控制台可视化页面进行动态配置服务,动态 DNS 服务,服务及其元数据管理等功能,Nacos0.8版本支持简单登录功能,默认用户名/密码为 nacos/nacos。:

相比Eureka提供的单一的看板页面,提供的管理功能可以说没得比,具体使用手册参考官方:https://nacos.io/zh-cn/docs/console-guide.html,这里不再赘述。

首先开发基于Spring Cloud+Nacos架构的微服务,nacos-discovery-provider为服务提供者,nacos-discovery-consumer为服务消费方, 

nacos-discovery-provider的pom.xml加入相关依赖:

 1 <dependencies>
 2         <dependency>
 3             <groupId>org.springframework.cloud</groupId>
 4             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 5             <version>0.2.1.RELEASE</version>
 6         </dependency>
 7         <dependency>
 8             <groupId>org.springframework.boot</groupId>
 9             <artifactId>spring-boot-starter-web</artifactId>
10             <version>2.0.6.RELEASE</version>
11         </dependency>
12         <dependency>
13             <groupId>org.springframework.boot</groupId>
14             <artifactId>spring-boot-starter-actuator</artifactId>
15             <version>2.0.6.RELEASE</version>
16         </dependency>
17     </dependencies>

application.properties的配置为:

1 server.port=18082
2 spring.application.name=service-provider
3 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848   
4 management.endpoints.web.exposure.include=*

其中spring.cloud.nacos.discovery.server-addr为配置的Nacos地址,management.endpoints.web.exposure.include=*表示对外暴露所有项目信息。

启动类的代码:

 1 package org.springframework.cloud.alibaba.cloud.examples;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 6 import org.springframework.web.bind.annotation.PathVariable;
 7 import org.springframework.web.bind.annotation.RequestMapping;
 8 import org.springframework.web.bind.annotation.RequestMethod;
 9 import org.springframework.web.bind.annotation.RequestParam;
10 import org.springframework.web.bind.annotation.RestController;
11 
12 @SpringBootApplication
13 @EnableDiscoveryClient
14 public class ProviderApplication {
15 
16     public static void main(String[] args) {
17         SpringApplication.run(ProviderApplication.class, args);
18     }
19 
20     @RestController
21     class EchoController {
22         @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
23         public String echo(@PathVariable String string) {
24             return "hello Nacos Discovery " + string;
25         }
26 
27         @RequestMapping(value = "/divide", method = RequestMethod.GET)
28         public String divide(@RequestParam Integer a, @RequestParam Integer b) {
29             return String.valueOf(a / b);
30         }
31     }
32 }

使用SpringCloud的原生注解@EnableDiscoveryClient 开启服务注册发现功能。

接下来创建服务消费方nacos-discovery-consumer进行服务消费:

pom.xml:

 1 <dependencies>
 2         <dependency>
 3             <groupId>org.springframework.boot</groupId>
 4             <artifactId>spring-boot-starter-web</artifactId>
 5             <version>2.0.6.RELEASE</version>
 6         </dependency>
 7         <dependency>
 8             <groupId>org.springframework.cloud</groupId>
 9             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
10             <version>0.2.1.RELEASE</version>
11         </dependency>
12         <dependency>
13             <groupId>org.springframework.boot</groupId>
14             <artifactId>spring-boot-starter-actuator</artifactId>
15             <version>2.0.6.RELEASE</version>
16         </dependency>
17         <dependency>
18             <groupId>org.springframework.cloud</groupId>
19             <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
20             <version>2.0.2.RELEASE</version>
21         </dependency>
22         <dependency>
23             <groupId>org.springframework.cloud</groupId>
24             <artifactId>spring-cloud-starter-openfeign</artifactId>
25             <version>2.0.2.RELEASE</version>
26         </dependency>
27         <dependency>
28             <groupId>org.springframework.cloud</groupId>
29             <artifactId>spring-cloud-alibaba-sentinel</artifactId>
30             <version>0.2.1.RELEASE</version>
31         </dependency>
32     </dependencies>

其中sentinel和传统的Spring Cloud组件Hystrix类似,提供熔断降级,系统负载保护等功能。application.properties的配置为:

 1 spring.application.name=service-consumer
 2 server.port=18083
 3 management.endpoints.web.exposure.include=*
 4 spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
 5 
 6 feign.sentinel.enabled=true
 7 
 8 spring.cloud.sentinel.transport.dashboard=localhost:8080
 9 spring.cloud.sentinel.eager=true
10 
11 spring.cloud.sentinel.datasource.ds1.file.file=classpath: flowrule.json
12 spring.cloud.sentinel.datasource.ds1.file.data-type=json
13 spring.cloud.sentinel.datasource.ds1.file.rule-type=flow

其中flowrule.json配置了限流降级的规则:

 1 [
 2   {
 3     "resource": "GET:http://service-provider/echo/{str}",
 4     "controlBehavior": 0,
 5     "count": 1,
 6     "grade": 1,
 7     "limitApp": "default",
 8     "strategy": 0
 9   }
10 ]

消费启动类ConsumerApplication.java:

 1 package org.springframework.cloud.alibaba.cloud.examples;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService;
 6 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 7 import org.springframework.cloud.client.loadbalancer.LoadBalanced;
 8 import org.springframework.cloud.openfeign.EnableFeignClients;
 9 import org.springframework.cloud.openfeign.FeignClient;
10 import org.springframework.context.annotation.Bean;
11 import org.springframework.web.bind.annotation.PathVariable;
12 import org.springframework.web.bind.annotation.RequestMapping;
13 import org.springframework.web.bind.annotation.RequestMethod;
14 import org.springframework.web.bind.annotation.RequestParam;
15 import org.springframework.web.client.RestTemplate;
16 
17 /**
18  * @author liujie037
19  */
20 @SpringBootApplication
21 @EnableDiscoveryClient
22 @EnableFeignClients
23 public class ConsumerApplication {
24 
25     @LoadBalanced
26     @Bean
27     public RestTemplate restTemplate() {
28         return new RestTemplate();
29     }
30 
31     public static void main(String[] args) {
32         SpringApplication.run(ConsumerApplication.class, args);
33     }
34 
35     @FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
36     public interface EchoService {
37         @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
38         String echo(@PathVariable("str") String str);
39 
40         @RequestMapping(value = "/divide", method = RequestMethod.GET)
41         String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b);
42 
43         @RequestMapping(value = "/notFound", method = RequestMethod.GET)
44         String notFound();
45     }
46 
47 }
48 
49 class FeignConfiguration {
50     @Bean
51     public EchoServiceFallback echoServiceFallback() {
52         return new EchoServiceFallback();
53     }
54 }
55 
56 class EchoServiceFallback implements EchoService {
57     @Override
58     public String echo(@PathVariable("str") String str) {
59         return "echo fallback";
60     }
61 
62     @Override
63     public String divide(@RequestParam Integer a, @RequestParam Integer b) {
64         return "divide fallback";
65     }
66 
67     @Override
68     public String notFound() {
69         return "notFound fallback";
70     }
71 }

通过 Spring Cloud 原生注解 @EnableDiscoveryClient 开启服务注册发现功能。给 RestTemplate 实例添加 @LoadBalanced 注解,开启 @LoadBalanced 与 Ribbon 的集成:

消费的接口TestController.java:

 1 package org.springframework.cloud.alibaba.cloud.examples;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.cloud.alibaba.cloud.examples.ConsumerApplication.EchoService;
 5 import org.springframework.cloud.client.discovery.DiscoveryClient;
 6 import org.springframework.web.bind.annotation.PathVariable;
 7 import org.springframework.web.bind.annotation.RequestMapping;
 8 import org.springframework.web.bind.annotation.RequestMethod;
 9 import org.springframework.web.bind.annotation.RequestParam;
10 import org.springframework.web.bind.annotation.RestController;
11 import org.springframework.web.client.RestTemplate;
12 
13 /**
14  * @author liujie037
15  */
16 @RestController
17 public class TestController {
18 
19     @Autowired
20     private RestTemplate restTemplate;
21     @Autowired
22     private EchoService echoService;
23 
24     @Autowired
25     private DiscoveryClient discoveryClient;
26 
27     @RequestMapping(value = "/echo-rest/{str}", method = RequestMethod.GET)
28     public String rest(@PathVariable String str) {
29         return restTemplate.getForObject("http://service-provider/echo/" + str,
30                 String.class);
31     }
32 
33     @RequestMapping(value = "/notFound-feign", method = RequestMethod.GET)
34     public String notFound() {
35         return echoService.notFound();
36     }
37 
38     @RequestMapping(value = "/divide-feign", method = RequestMethod.GET)
39     public String divide(@RequestParam Integer a, @RequestParam Integer b) {
40         return echoService.divide(a, b);
41     }
42 
43     @RequestMapping(value = "/echo-feign/{str}", method = RequestMethod.GET)
44     public String feign(@PathVariable String str) {
45         return echoService.echo(str);
46     }
47 
48     @RequestMapping(value = "/services/{service}", method = RequestMethod.GET)
49     public Object client(@PathVariable String service) {
50         return discoveryClient.getInstances(service);
51     }
52 
53     @RequestMapping(value = "/services", method = RequestMethod.GET)
54     public Object services() {
55         return discoveryClient.getServices();
56     }
57 }

访问Nacos控制台:

测试服务消费正常使用postman:

 下一篇中我将继续展示Dubbo服务创建和Spring Cloud 互相调用。

猜你喜欢

转载自www.cnblogs.com/liujie037/p/10311840.html