微服务之 Dubbo+Zookeeper RPC 尝试
导航
一、单体应用和微服务
更好的解释:单体应用与微服务的比较
1. 单体应用架构
也就是所谓的 all in one ,将一个应用中的所有应用服务都封装在一个应用中。
例如一个系统,把数据库访问,web访问,等各个功能放到一个war包内。
若是开发一个不大的应用,单体应用架构总体上优于微服务架构。但是也有一些缺点,例如不够灵活、比微服务更高的耦合,团队开发效率不如微服务。
2. 微服务架构
将单体应用中的服务拆分出来,独立部署,各个模块独立开发,整个系统的耦合度降低,但也更为复杂,一个服务崩了,整个系统还会继续工作。
二、微服务架构解决方案以及分布式
微服务架构的四个问题:
- 客户端如何访问服务? api网关
- 服务之间如何进行通信? HTTP REST、RPC……
- 如何治理这些服务?Eureka、nacos、zookeeper……
- 服务挂了,如何解决?熔断机制、负载均衡……
解决这四个问题的方案就称为微服务的解决方案,如SpringCloud。
我们也可以自己组装一套解决方案,例如利用Dubbo进行服务间通信,利用zookeeper治理服务,用Flurry作为api网关,通过自带的服务降级来实现熔断机制,或是使用Hystrix 等组件。
分布式:
分布式服务顾名思义服务是分散部署在不同的机器上的,一个服务可能负责几个功能,是一种面向SOA架构的,服务之间也是通过rpc来交互或者是webservice来交互的。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用
得出结论: 微服务一定是分布式,分布式不一定是微服务。
接下来就用Dubbo+Zookeeper来体验一下阉割版微服务的hello world(体验一下RPC)。
三、Hello World
1. Dubbo架构
节点角色说明
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
2. zookeeper安装
直接到官网下载,3.5版本之后的下载带bin的版本。
https://dlcdn.apache.org/zookeeper/
需要修改配置文件/bin/zoo_sample.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper
clientPort=2181
注意端口就好。
将其重命名为zoo.cfg,双击运行bin目录下的zkServer.cmd。
3. Dubbo-Admin安装
下载:https://github.com/apache/dubbo-admin/tree/master
配置文件:dubbo-admin-server\src\main\resources\application.properties
需要添加一行:
server.port=7001
到dubbo-admin目录下:
打包:mvn clean package -Dmaven.test.skip=true
运行dubbo-admin-distribution/target/dubbo-admin-0.5.0.jar
java -jar ./dubbo-admin-distribution/target/dubbo-admin-0.5.0.jar
运行成功后,访问http://localhost:7001/即可登录监控后台,初始用户名密码皆为root。
在这里插入图片描述
4. SpringBoot + Dubbo + zookeeper
4.1 Provider
我们需要先创建一个服务。
新建项目,导入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--zookeeper客户端-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
写个服务:
接口:
public interface HelloService {
String sayHello();
}
实现:
@Service
@DubboService
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
System.out.println("Hello " + name + ", request from consumer: " + RpcContext.getContext().getRemoteAddress());
return "Hello " + name;
}
}
@DubboService表示这个服务由Dubbo管理。
配置:
dubbo:
registry:
address: zookeeper://127.0.0.1:2181
metadata-report:
address: zookeeper://127.0.0.1:2181
scan:
# 服务所在的包
base-packages: com/example/dubbozookeeper/Service/impl
application:
# 应用名称
name: DubboZookeeper_Provider
protocol:
# 注册在Dubbo上的端口
port: 20881
# 项目端口
server:
port: 8001
启动。
此时在Dubbo-admin中可以看到该服务;
4.2 Consumer
写个服务消费者:
新建项目,导入依赖(和之前一样)。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Dubbo-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--zookeeper客户端-->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置:
dubbo:
registry:
address: zookeeper://127.0.0.1:2181
application:
name: DubboZookeeper_Consumer
server:
port: 8002
调用方式有两种,本地调用或是线上调用。
4.3 本地调用
本地调用简便方法:直接复制需要调用的接口,到该项目目录下。
例如:
我们之前将 DubboZookeeper\src\main\java\com\example\dubbozookeeper\service\HelloService.java 注册到Zookeeper里了,现在只需要复制该文件到:DubboZookeeperConsumer\src\main\java\com\example\dubbozookeeper\service\HelloService.java
com\example\dubbozookeeper\service\HelloService.java部分需要一致。也就是groupId一致。
通过@DubboReference注入
写测试试一试:
@SpringBootTest
class DubboZookeeperConsumerApplicationTests {
@DubboReference
HelloService helloService;
@Test
void contextLoads() {
System.out.println(helloService.sayHello("Consumer"));
}
}
运行:
并且可以在Provider中看到如下信息:
4.4 线上调用
直接上管理后台,找到服务的url:
写个单元测试:
@Test
void text() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException {
// 接口
String serviceInterface = "com.example.dubbozookeeper.service.HelloService";
// 要调用的方法
String methodName = "sayHello";
// 替换成自己dubbo服务ip
String dubboURl = "dubbo://192.168.228.1:20881/com.example.dubbozookeeper.service.HelloService?anyhost=true&application=DubboZookeeper_Provider&background=false&deprecated=false&dubbo=2.0.2&dubbo.endpoints=[{\"port\":20881,\"protocol\":\"dubbo\"}]&dubbo.metadata-service.url-params={\"connections\":\"1\",\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"3.1.1\",\"side\":\"provider\",\"port\":\"20881\",\"protocol\":\"dubbo\"}&dubbo.metadata.revision=5263d1a3ac03c713d3de72860c2dd4e0&dubbo.metadata.storage-type=local&dynamic=true&generic=false&interface=com.example.dubbozookeeper.service.HelloService&ispuserver=true&methods=sayHello&release=3.1.1&service-name-mapping=true&side=provider×tamp=1666951237891";
// 创建调用配置
ReferenceConfig reference = new ReferenceConfig();
Class<?> forName = Class.forName(serviceInterface);
reference.setInterface(forName);
reference.setUrl(dubboURl);
// 获取dubbo服务链接
Object object = reference.get();;
// 找到指定方法
Method[] methods = forName.getMethods();
Method method = null;
for (Method m : methods) {
if (methodName.equals(m.getName())) {
method = m;
break;
}
}
// 指定方法,参数
Object invokeResult = method.invoke(object, "consumer");
// 调用结果
System.out.println(JSON.toJSONString(invokeResult));
}
运行结果:
在Provider控制台中可以看到: