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 互相调用。