Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
以上是官方的解释,我个人的理解,dubbo作为一个RPC服务开发框架,除了满足我微服务之间远程调用的目的之外,还能有以下几个重要的点。
1.和nacos等注册中心,完成dubbo接口服务的服务发现,消费方调用接口无需关注服务端的ip、端口
2.和sentinel集成,可以完成限流和熔断的目的
3.dubbo实现了多个负载均衡算法,只需yml文件配置即可。
4.dubbo和seata可以很方便的集成,轻松达到业务数据一致性的目的。
@EnableDubbo:创建Springboot启动类,需添加@EnableDubbo注解,开启Dubbo自动配置功能
@DubboService:Dubbo会将对应的服务注册到spring, 在spring启动后调用对应的服务导出方法,将服务注册到注册中心, 这样Consumer端才能发现我们发布的服务并调用(与spring的@Service注解作用类似)
@DubboReference:通过@DubboReference注解对需要调用的服务进行引入。即可像调用本地方法一样调用远程服务了。(和spring的@Autowired功能类似)
*注:@DubboService和@DubboReference的version参数和group参数的值必须一致。这两个参数可以确定一个实现类,接口一般可以有多个实现类的,多个实现类可以通过version和group参数进行区分。
示例需要三个工程,一个接口定义、一个服务端,一个消费端。
maven依赖的版本号取决于项目使用的springboot版本及springcloud版本,我使用的版本如下:
org.springframework.boot
spring-boot-dependencies
2.6.11
pom
import
org.springframework.cloud
spring-cloud-dependencies
2021.0.1
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2021.0.4.0
pom
import
org.apache.dubbo
dubbo-bom
3.0.9
pom
import
工程名称:dubbo-demo-interface
实体类必须实现序列化接口,因为在远程调用时,实体类数据是序列化后传输的
package com.jc.shop.dubbo.demo.domain;
/**
* 用户表
*/
public class User implements java.io.Serializable{
/**
* 主键ID
*/
private long id;
/**
* 用户名称
*/
private String name;
/**
* 所属部门
*/
private long deptId;
/**
* 岗位
*/
private String post;
private Dept dept;
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getDeptId() {
return deptId;
}
public void setDeptId(long deptId) {
this.deptId = deptId;
}
public String getPost() {
return post;
}
public void setPost(String post) {
this.post = post;
}
}
接口定义:
package com.jc.shop.dubbo.demo.service;
import com.jc.shop.dubbo.demo.domain.User;
/**
* 业务接口
*/
public interface IUserService {
public int insert(User user);
}
服务端工程名:dubbo-demo-provider
jar包依赖,maven
org.apache.dubbo
dubbo-spring-boot-starter
com.alibaba.nacos
nacos-client
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.cloud
spring-cloud-starter-bootstrap
com.jc
dubbo-demo-interface
1.0-SNAPSHOT
yml配置
dubbo:
application:
name: dubbo-provider
protocol: #dubbo协议信息
name: dubbo
host: 127.0.0.1
port: 20881
registry:
address: nacos://localhost:8848 #使用nacos作为注册中心
服务层的代码实现:
package com.jc.shop.dubbo.demo.service.impl;
import com.jc.shop.dubbo.demo.domain.User;
import com.jc.shop.dubbo.demo.mapper.UserMapper;
import com.jc.shop.dubbo.demo.service.IUserService;
import io.seata.core.context.RootContext;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 业务层接口,@Service注解不用加,因为我在当前工程的controller中需要调用,所以加了@Service注解
*/
@Service
@DubboService(version = "1.0.0",loadbalance = "leastactive")//负载策略为“最少活跃优先 + 加权随机”
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper mapper;
@Override
public int insert(User user) {
//以下打印信息是为了集成seata框架的,可忽略,本次不涉及seata框架内容
System.out.println("用户新增的事务ID为:"+ RootContext.getXID());
return mapper.insert(user);
}
}
在当前工程的启动类中,添加@EnableDubbo注解,开启dubbo配置的自动配置功能:
package com.jc;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class DubboProviderApplication {
public static void main(String[] args) {
//以下4个变量的设置是为了避免同一台机器,启动多个dubbo服务,缓存默认使用的同一个地址会报错。
System.setProperty("dubbo.meta.cache.filePath","/media/zhangzz/localDisk/home/zhangzz/dubbo/provider/");
System.setProperty("dubbo.meta.cache.fileName","provider");
System.setProperty("dubbo.mapping.cache.filePath","/media/zhangzz/localDisk/home/zhangzz/dubbo/provider/");
System.setProperty("dubbo.mapping.cache.fileName","provider1");
SpringApplication.run(DubboProviderApplication.class,args);
}
}
消费端工程名:dubbo-demo-consumer
maven依赖配置:
org.apache.dubbo
dubbo-spring-boot-starter
3.2.7
com.alibaba.nacos
nacos-client
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
org.springframework.cloud
spring-cloud-starter-bootstrap
yml配置:
dubbo:
application:
name: dubbo-consumer
protocol: #dubbo协议信息
name: dubbo
port: -1 # -1表示端口随机
registry:
address: nacos://localhost:8848 #nacos地址
业务层代码调用:
package com.jc.shop.dubbo.demo.service.impl;
import com.jc.shop.dubbo.demo.domain.User;
import com.jc.shop.dubbo.demo.service.IConsumerService;
import com.jc.shop.dubbo.demo.service.IUserService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Service;
@Service
public class ConsumerServiceImpl implements IConsumerService {
@DubboReference(version = "1.0.0") //版本号需和服务端一致,若有group,也需保持一致
private IUserService userService;
@Override
public int insertUser(User user) {
int j = userService.insert(user);
System.out.println("用户新增,影响行数:"+j);
return j;
}
}
controller层代码:
package com.jc.shop.dubbo.demo.controller;
import com.jc.core.domain.AjaxResult;
import com.jc.shop.dubbo.demo.domain.User;
import com.jc.shop.dubbo.demo.service.IConsumerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private IConsumerService service;
@PostMapping("/insert")
public AjaxResult insert(@RequestBody User user){
int i = service.insertUser(user);
if(i>0) {
return AjaxResult.success("success");
}else{
return AjaxResult.error();
}
}
}
应用启动类:
package com.jc;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class DubboConsumerApplication {
public static void main(String[] args) {
System.setProperty("dubbo.meta.cache.filePath","/media/zhangzz/localDisk/home/zhangzz/dubbo/consumer/");
System.setProperty("dubbo.meta.cache.fileName","consumer");
System.setProperty("dubbo.mapping.cache.filePath","/media/zhangzz/localDisk/home/zhangzz/dubbo/consumer/");
System.setProperty("dubbo.mapping.cache.fileName","consumer1");
SpringApplication.run(DubboConsumerApplication.class,args);
}
}
启动之后,访问http://localhost:8082/demo/insert地址
集成的配置可参考《sentinel-单机流量控制》或《sentinel-集群流量控制》,此处不再赘述。此处只说明下限流、熔断的资源名称如何定义:
以我的代码为例,规则定义如下:
FlowRule flowRule = new FlowRule(IUserService.class.getName())
.setCount(10)
.setGrade(RuleConstant.FLOW_GRADE_QPS);
FlowRuleManager.loadRules(Collections.singletonList(flowRule));
由以上代码可以看出,资源名称是类的全路径“com.jc.shop.dubbo.demo.service.IUserService”,一般我们的流控规则都是在nacos中配置,由代码动态加载。nacos中的配置如下:
[
{
"resource":"com.jc.shop.dubbo.demo.service.IUserService",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
官方文档传送门:dubbo与Sentinel集成的限流示例
目前 Dubbo 内置了如下负载均衡算法,用户可直接配置使用:
算法 | 特性 | 备注 | 配置值 |
---|---|---|---|
Weighted Random LoadBalance | 加权随机 | 默认算法,默认权重相同 | random (默认) |
RoundRobin LoadBalance | 加权轮询 | 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同, | roundrobin |
LeastActive LoadBalance | 最少活跃优先 + 加权随机 | 背后是能者多劳的思想 | leastactive |
Shortest-Response LoadBalance | 最短响应优先 + 加权随机 | 更加关注响应速度 | shortestresponse |
ConsistentHash LoadBalance | 一致性哈希 | 确定的入参,确定的提供者,适用于有状态请求 | consistenthash |
P2C LoadBalance | Power of Two Choice | 随机选择两个节点后,继续选择“连接数”较小的那个节点。 | p2c |
Adaptive LoadBalance | 自适应负载均衡 | 在 P2C 算法基础上,选择二者中 load 最小的那个节点 | adaptive |
@DubboService(loadbalance = "leastactive")
@DubboReference(loadbalance = "leastactive")
服务端方法级别的负载配置:
@DubboService(method={@Method(name="insert",loadbalance = "leastactive")})
消费端方法级别的负载配置:
@DubboReference(method={@Method(name="insert",loadbalance = "leastactive")})