Spring Cloud和Dubbo整合开发笔记(1)
一、需求背景:
- 公司内部老项目微服务技术栈使用Dubbo, 新项目技术栈使用主流的Spring Cloud相关组件开发,新旧项目涉及交互调用,无法直接通信数据传递。
- 老项目基于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 互相调用。
posted on 2019-01-24 00:51 liujie037 阅读(5948) 评论(0) 编辑 收藏 举报