在分布式系统中,服务与服务之间怎么通信是一个问题,目前主流的方式就是通过RPC或HTTP协议进行通信。像Spring Cloud就是通过http协议进行服务之间的通信,而dubbo是一个RPC框架,它实现了RPC调用。这两种方式对比起来的话,HTTP协议稍微简单点,但是由于它需要3次握手和4次挥手,性能较差,而dubbo实现的RPC,底层是用netty这种非阻塞I/O,速度会快很多。
性能:
编程过程中的使用方式:
那除了解决分布式系统之间的通信问题,dubbo还提供了一些其他的功能,:
服务自动注册与发现,服务熔断,服务降级,软负载均衡等,功能还在持续更新
由于Spring Cloud 是一个分布式一站式的解决方案,且代码都是通过上层接口去实现各个组件,所以可以直接在spring cloud中使用dubbo作为RPC通信框架,来解决Spring Cloud使用http协议的性能问题。
Spring Cloud Alibaba 集成了Alibaba的一些开源组件,使得Spring Cloud中的组件选择更加丰富。
官网上有:具体说明官网上有
连通性、健壮性、伸缩性、以及向未来架构的升级性。官方文档都有,我自己归纳整理了一下:
集成dubbo的方式有很多种,我们这里就用springboot来集成。
需要创建3个项目,
dubbo-spring-boot-starter 依赖有两个,一个是:
另一个是
看更新时间就能知道,apache.dubbo是最新的,且之后的dubbo-spring-boot-starter都是交给apache的。我们这里就使用apache.dubbo。
dubbo: https://github.com/apache/dubbo
dubbo-spring-boot地址:https://github.com/apache/dubbo-spring-boot-project
我的spring boot 版本是2.2.6.RELEASE,
dubbo-spring-boot-starter的版本是2.7.3
该项目只存放公共的资源,所以不需要使用spring boot,创建一个普通的maven工程即可
结构如下:
public interface UserService {
List getUserList();
}
创建一个springboot项目
org.springframework.boot
spring-boot-starter-web
com.tanfp.dubbo-study.dubbo-demo
gmall-interface
1.0.0
org.apache.dubbo
dubbo-spring-boot-starter
2.7.3
org.apache.curator
curator-framework
4.2.0
org.apache.curator
curator-recipes
4.2.0
io.netty
netty-all
4.1.48.Final
必须要引入curator-framework,curator-recipes这两个包,当你使用zookeeper作为注册中心时,它就会去者两个包中找一些类来操作zookeeper。
netty-all这个包当你遇到io/netty的问题再加吧,我这里其实不加也是可以正常访问的
// 注意引入包时,引入dubbo的Service包
@Service(version = "1.0")
public class UserServiceImpl implements UserService {
@Override
public List getUserList() {
List userList = new ArrayList<>();
User user = new User("张三z",1,new Date());
User user2 = new User("李四",2,new Date());
userList.add(user);
userList.add(user2);
return userList;
}
}
@Service表示对外暴露了这个类,即暴露了服务,可以让消费者来直接在本地调用UserserviceImpl类中的方法
启动类上添加@EnableDubbo注解
server.port=8082
#指定当前dubbo引用的名称
dubbo.application.name=gmall-user
#指定使用什么注册中心
dubbo.registry.protocol=zookeeper
#指定注册中心的地址
dubbo.registry.address=127.0.0.1:2181
#指定使用什么通信协议
dubbo.protocol.name=dubbo
创建一个spring boot项目,依赖与gmall-user项目一样
@RestController
public class OrderController {
@Autowired
private OrderServiceImpl orderService;
@GetMapping("/users")
public List getUserList() {
return orderService.getUserList();
}
}
@Service
public class OrderServiceImpl {
// 调用dubbo服务
@Reference(version = "1.0")
private UserService userService;
public List getUserList() {
return userService.getUserList();
}
}
启动类添加@EnableDubbo注解
server.port=8081
dubbo.application.name=gmall-order-web
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181
dubbo.protocol.name=dubbo
启动zookeeper集群,然后分别启动gmall-user(服务提供者),gmall-order(服务消费者)
访问http://localhost:8081/users
dubbo中具体的配置项,在官网中都有
除了dubbo:service和dubbo:reference,其他的基本上都有,这两个分别使用@Service,@Reference去配置
对应application.yml中就是:
dubbo.protocol.xxx=yyy
dubbo.registry.xxx=yyy
xxx表示具体的属性,yyy表示值
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
可以通过服务降级功能临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
一般都是在消费者的reference属性上添加mock值来判断,也只能消费方才能加,提供方加这个mock会报错。其实想想也能知道,它有一种是不进行调用,直接返回错误,如果放在提供服务方,还怎么直接返回?
mock的写法有以下几种:
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000)
@Reference(version = "1.0",mock = "force:return null",timeout = 2000)
private UserService userService;
自定义返回:
自定义返回的类放在公共接口的项目:gmall-interface下,有两点注意:
UserServiceMock:
public class UserServiceMock implements UserService {
public List getUserList() {
System.out.println("调用了Mock");
return new ArrayList();
}
}
消费方启用mock功能:
@Reference(version = "1.0",mock = "true",timeout = 2000)
private UserService userService;
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
一般来说,服务提供方和服务消费方都能设置,但是能在服务提供方进行配置的就尽量在服务提供方设置,可以更好的管理服务
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)。
在服务提供方添加重试次数。不设置cluster,则默认是Failover重试机制
@Service(version = "1.0",retries = 2, cluster="failsafe")
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
非幂等性表示多次调用结果都是一样的,比如查询,删除,更新
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
默认采用随机算法
使用:
@Service(version = "1.0",retries = 2, cluster="failsafe", loadbalance="roundrobin")
当一个接口有多种实现时,可以用 group 区分。
@Service(version = "1.0",retries = 2, group = "impl-1")
public class UserServiceImpl implements UserService {}
@Service(version = "1.0",retries = 2, group = "impl-2")
public class UserServiceImpl implements UserService {}
服务消费方引用的时候也加一个group属性执行即可,也可以使用*:表示使用任意一个实现类
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000,group = "impl-1")
// 使用任意组
@Reference(version = "1.0",mock = "fail:return null",timeout = 2000,group = "*")
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
可以按照以下的步骤进行版本迁移:
使用方式就是指定version版本号,服务消费者只能消费指定版本的服务
@Service(version = "2.0.0")
@Reference(version = "2.0.0")
如果消费方不区分服务版本号,那么消费方可以使用*
如果不需要区分版本,可以按照以下的方式配置 :
@Reference( version="*")
当服务消费方想在调用服务提供方前,去做一些事情,比如说注册,服务消费方可以先执行一部分逻辑,比如参数的校验,只有校验成功,我们再去调用服务提供方的注册方法,否则就直接返回错误。那这个具体怎么实现呢?
分两步:
一定要在与UserService接口同包的路径下去创建UserServiceStub,否则会找不到这个类
public class UserServiceStub implements UserService {
// 相当于是UserService的一个代理对象,当我们校验完成后,可以通过这个代理对象调用服务
private final UserService userService;
public UserServiceStub(UserService userService) {
this.userService = userService;
}
public List getUserList() {
System.out.println("校验参数中......");
if (true) {
// 校验成功,我们就继续调用服务
return userService.getUserList();
} else {
// 校验失败
return new ArrayList();
}
}
}
在这个版本中,如果你是以接口+Stub的这种形式创建的类,那么当你创建完成后,本地存根其实就默认开启了,但是最好还是显示声明一下,声明即可在服务提供方声明,也可以在服务消费方声明,但是我们最好在提供方去声明,统一管理服务。
设置stub为true即可
@Service(version = "1.0",stub = "true")
这个就是用服务降级来实现的,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回结果