Little eagle - SpringBoot实战管理系统(六)分布式

前言

本部分所涉及到的分布式系统理论和RPC的概念这块较干,尽量以通熟易懂的例子为引子展开表述。便于理解~

分布式系统理论

分布式可以理解通过多个计算性能弱的服务器来完成计算资源需求大的计算任务

分布式和集群的概念很容易混淆

弹幕看到一个浅显易懂的例子可以帮助理解什么是分布式↓

小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群

为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群

简单理解,分布式是一个系统,而集群是一个模块

分布式系统概念:建立在网络上的软件系统。由一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统,利用廉价、普通机器完成单个计算机无法完成的计算存储任务。其目的是利用更多的机器,处理更多的数据。

RPC

什么是RPC(remote procedure call)中一个例子很好的诠释了什么是远程调用:

一个阳光明媚的早晨,老婆又在翻看我订阅的技术杂志。

“老公,什么是RPC呀,为什么你们程序员那么多黑话!”,老婆还是一如既往的好奇。 “RPC,就是Remote Procedure Call的简称呀,翻译成中文就是远程过程调用嘛”,我一边看着书,一边漫不经心的回答着。 “啥?你在说啥?谁不知道翻译成中文是什么意思?你个废柴,快给我滚去洗碗!” “我去。。。”,我如梦初醒,我对面坐着的可不是一个程序员,为了不去洗碗,我瞬间调动起全部脑细胞,星辰大海在我脑中汇聚,灵感涌现……

“是这样,远程过程调用,自然是相对于本地过程调用来说的嘛。” “嗯哼,那先给老娘讲讲,本地过程调用是啥子?” “本地过程调用,就好比你现在在家里,你要想洗碗,那你直接把碗放进洗碗机,打开洗碗机开关就可以洗了。这就叫本地过程调用。”

“哎呦,我可不干,那啥是远程过程调用?” “远程嘛,那就是你现在不在家,跟姐妹们浪去了,突然发现碗还没洗,打了个电话过来,叫我去洗碗,这就是远程过程调用啦”,多么通俗易懂的解释,我真是天才!

“哦!我明白了”,说着,老婆开始收拾包包。 “你这是干啥去哦” “我?我要出门浪去呀,待会记得接收我的远程调用哦,哦不,咱们要专业点,应该说,待会记得接收我的RPC哦!” ……

RPC远程方法调用,首先要理解什么是远程方法调用,区分于本地方法调用。

例如在本地程序中A方法中调用了B方法为本地方法调用。

而如果要调用的B方法在另一台机器上呢,这时跨机器的方法调用即为远程方法调用。

而跨机器的方法调用依赖于网络实现,所以相比于本地方法调用,远程方法调用的难点就在于网络不可靠

okok,带着以上对远程方法调用的初步理解,来看看RPC的定义:

RPC是指远程过程调用,是一种进程间通信方式,RPC是一种技术思想,而不是一种硬性规范,其实现方式具有一定的灵活性。RPC允许程序调用另一个地址空间(通常是共享网络上的另一台机器)上的过程或函数。对于编程人员来说,无论调用本地还是远程函数,应该是不可感知的,即调用代码基本相同。不用显式编码远程调用细节

步骤解析:

RPC的两个核心模块:

  • 通讯
  • 序列化(用于机器间的对象传输)

Dubbo

概念

RPC介绍中了解到其为一种思想,而Dubbo即为该思想的一个具体实现,Dubbo是一种高可用的RPC框架。

Dubbo官网

什么是Dubbo?Apache Dubbo是一款高性能、轻量级的开源Java RPC框架,其三大核心能力:

  • 面向接口的远程方法调用
  • 智能容错和负载均衡
  • 服务自动注册和发现

其整体框架如↓

框架表明仅invoke为同步操作

Provider:暴露服务的服务提供方,服务提供方在启动时,向注册中心注册自身所提供的服务

Consumer:调用服务的消费方,启动时向注册中心订阅服务,Provider从地址列表中,基于软负载均衡算法,选一个Provider调用,如调用失败切换一台继续调用

Registry:注册中心返回Provider列表给Consumer,如有变动,注册中心将基于长连接推送变更数据给Consumer

Monitor:Consumer和Provider,在内存中累计调用次数和时间,定时每分钟发送一次统计数据到Monitor

环境搭建

zookeeper-控制中心

利用zookeeper提供服务注册与发现(即Registry)

下载zookeeper并解压,以管理员身份!!!运行bin目录下的zkserver.cmd启动服务(如遇闪退,出门右拐:Windows下Zookeeper启动zkServer.cmd闪退问题的解决方案

运行zkcli.cmd客户端(需要先运行zkserver.cmd开启服务才能连接成功)

安装dubbo-admin

dubbo-admin是一个监控管理后台,能够查看注册了哪些服务以及哪些服务被消费了,配置过程如下

  1. 下载并解压dubbo-admin项目包

  2. 修改dubbo-admin-master-0.2.0-admin.properties指定zookeeper地址

1
2
3
4
5
6
7
8
9
10
11
server.port=7001
spring.velocity.cache=false
spring.velocity.charset=UTF-8
spring.velocity.layout-url=/templates/default.vm
spring.messages.fallback-to-system-locale=false
spring.messages.basename=i18n/message
spring.root.password=root
spring.guest.password=guest

dubbo.registry.address=zookeeper://127.0.0.1:2181

  1. 项目目录下打包dubbo-admin

    1
    mvn clean package -D maven.test.skip=true

    如提示mvn不是内部或外部命令,查看Maven环境配置及介绍

打包完成后在dubbo-admin-master-0.2.0-admin-admin-0.0.1-SNAPSHOT.jar包,将其放入zookeeper根目录下方便后续使用。双击启动jar包

访问localhost:7071

默认账号root密码root

登陆成功↓

服务注册发现实战

新建一个empty项目

project-server项目下新建一个spring initalizr模块provider-server

模拟一个买票服务,新建service/TicketService及其实现类TicketServiceImpl

1
2
3
4
5
package com.sliu.providerserver.service;

public interface TicketService {
public String getTicket();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.sliu.providerserver.service;

import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

@Service
@Component // 项目启动时将该服务注册到注册中心
public class TicketServiceImpl implements TicketService {

@Override
public String getTicket() {
return "《程序员评测室》";
}
}

服务端搞定,注册中心要注册的服务即为TicketService

新建一个客户端spring initalizr模块consumer-server

分别在两个模块的application.properties下设置端口号,consumer-server设置为8001,provider-server设置为8002

1
2
3
4
//consumer-server
server.port=8001
//provider-server
server.port=8002

provider-server下pom.xml下导入dubbo&zookeeper依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!--        导入dubbo和zookeeper依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!-- 排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

application.properties下对dubbo进行配置

1
2
3
4
5
6
7
8
server.port=8002

#服务应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#哪些路径下的服务被注册
dubbo.scan.base-packages=com.sliu.providerserver.service

配置完成后开启ProviderServerApplication,启动后自动注册provicerserver

http://localhost:7001/governance/providers 下可以观察到注册服务列表

该服务详细信息↓

OK!到此服务注册完毕,provider端配置完成。

接下来编码Consumer端

pom.xml下导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!--        导入dubbo和zookeeper依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!-- 排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

新建service/UserService用于获取provider提供的ticket

为了实现服务请求,需要将provider-server模块下service/TicketService拷贝到consumer-server模块下service目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.sliu.consumerserver.service;

import jdk.nashorn.internal.ir.annotations.Reference;
import org.springframework.stereotype.Service;

@Service //放到容器中,区别于dubbo包下的service用于标识注册到服务中心
public class UserService {
//拿到Provider-server提供的Ticket
@Reference //引用, pom坐标,可以定义路径相同的接口名
TicketServiceImpl ticketService;

public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println("注册中心获取=====>"+ticket);
}

}

新建test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.sliu.consumerserver;

import com.sliu.consumerserver.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ConsumerServerApplicationTests {

@Autowired
UserService userService;

@Test
void contextLoads() {
userService.buyTicket();
}

}

运行test下contextLoads方法,搞定!

梳理一下服务注册与发现流程,大体如下:

前提:zookeeper服务已开启

  1. provider提供服务
    • 导入依赖
    • 配置注册中心信息
    • 要注册的服务上添加注解@Service
  2. consumer消费
    • 导入依赖
    • 配置注册中心信息
    • 从远程注入服务@Reference

总结

该part后,狂神springboot练手项目告一段落

撒花完结!

Dubbo+ZooKeeper源码自取:

https://github.com/codersliu/project-server