封面画师:adsuger     封面ID:79185730

1. Swagger

1.1 Swagger简介

在学习Swagger之前,我们需要先了解 前后端分离

前后端分离:

  • 后端:后端控制层,服务层,数据访问层
  • 前端:前端控制层,视图层
    • 使用JSON,伪造后端数据。因为伪造了数据,就不需要后端数据,前端工程就可以运行起来
  • 前后端如何交互?使用API接口
  • 前后端相对独立,实现低耦合
  • 一个项目的前后端甚至可以部署在不同的服务器上

使用了前后端分离,就会产生一个问题:前端人员与后端人员无法做到“及时协商,尽早解决”,最终导致问题集中式爆发。(就知道变需求,变变变,变NM,给爷死!😡)

这个时候就需要一种解决方法:

  • 首先指定Schema【计划的提纲】,实时更新最新API,降低集成的风险
  • 早些年:指定Word计划文档
  • 在前后端分离时代:
    • 前端测试后端接口:postman
    • 后端提供接口,需要实时更新最新的消息及改动!

这个时候,一个帅气的“男人登场了”——Swagger。😂

Swagger

  • Swagger号称世界上最流行的API框架
  • RestFul API 文档在线生成 ,实现 API 文档 与API 定义同步更新
  • 直接运行,在线测试API
  • 支持多种语言 (如:Java,PHP …)
  • 官网地址:Swagger

1.2 SpringBoot集成Swagger

在项目中使用Swagger需要springfox:

  • swagger2
  • swagger-ui

使用步骤

在创建的SpringBoot项目中导入下列依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>

编写一个简单的Controller,如:HelloController,确保页面能够正确跳转。

1
2
3
4
5
6
7
8
@RestController
public class HelloController {

@GetMapping(value = "/hello")
public String hello(){
return "hello";
}
}

编写配置类SwaggerConfig用于配置Swagger:

1
2
3
4
@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
}

然后访问:http://localhost:8080/swagger-ui.html,就可以看到Swagger的界面。

Swagger默认界面

1.3 配置Swagger

Swagger实例Bean是Docket,所以通过配置Docket实例来配置Swaggger,在配置类SwaggerConfig中编写:

1
2
3
4
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2);
}

通过apiInfo()属性配置文档信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//配置swagger信息=apiInfo
private ApiInfo apiInfo(){
//作者信息
Contact contact = new Contact("默烦", "https://space.bilibili.com/23658864", "cy.mofan@qq.com");

return new ApiInfo(
"Yang的Swagger日记",
"中二少年欢乐多!",
"v1.0",
"https://space.bilibili.com/23658864",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList()
);
}

然后将Docket实例关联apiInfo()

1
2
3
4
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}

然后我们可以再次运行项目,访问:http://localhost:8080/swagger-ui.html,再次查看Swagger的界面:

Swagger界面-配置Swagger

1.4 配置扫描接口及开关

现在有一个需求:我只希望我的Swagger在生产环境中使用,在发布的时候不使用。

解决方法:

  • 判断当前环境是否是生产环境 flag = false
  • 注入enable (flag)

编写配置文件application-dev.properties

1
server.port=8080

编写配置文件application-pro.properties

1
server.port=8083

编写主配置文件application.properties

1
2
# 激活项目某个环境
spring.profiles.active=dev

修改Docket实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//配置Swagger的Docket的bean实例
@Bean
public Docket docket(Environment environment){
//设置要显示的swagger环境
Profiles profiles = Profiles.of("dev","test");
//获取项目环境:spring.profiles.active=dev
//通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean flag = environment.acceptsProfiles(profiles);

return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(flag) //enable() 是否启动swagger,若为false,则swagger不能在浏览器中访问
.select()
//RequestHandlerSelectors 配置要扫描接口的方式
//basePackage 指定要扫描的包
//any():扫描全部
//none():都不扫描
//withClassAnnotation():扫描类上的注解,参数是一个注解的反射对象,eg:RestController.class
//withMethodAnnotation():扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("com.yang.swagger"))
//path() 过滤的路径
//.paths(PathSelectors.ant("/yang/**"))
.build();
}

1.5 配置API分组

配置单个API分组

只需要在Docket实例返回值中添加以下代码即可:

1
.groupName("Yang")

配置多个API分组

只需要在配置类SwaggerConfig中创建多个Docket实例并添加至容器即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("AAA");
}

@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("BBB");
}

@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("CCC");
}

然后我们可以运行项目,访问:http://localhost:8080/swagger-ui.html,查看Swagger的界面:

Swagger界面-多个API分组

1.6 配置实体

我们发现在界面中,我们的 Models 一项还没用到,Models可以用来配置实体。

配置实体方式

新建一个实体类:

1
2
3
4
5
6
7
8
9
10
11
//@Api(注释)
@ApiModel("账户实体类")
public class Account {

@ApiModelProperty("用户名")
public String username;
@ApiModelProperty(value = "余额",example = "1.50")
public float money;
@ApiModelProperty("密码")
public String password;
}

只需要请求接口的返回值是实体类型(即使是泛型),都能映射到 Models 中。在Controller中进行编写:

1
2
3
4
5
//只要我们的接口中,返回值存在实体类,它就会呗扫描到swagger中
@PostMapping(value = "/account")
public Account account(){
return new Account();
}

然后我们重启项目,访问:http://localhost:8080/swagger-ui.html,查看Swagger的界面并点开 Models 项:

Swagger界面-Models


注意:并不是因为@ApiModel这个注解让实体显示在 Models 里了,而是出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel@ApiModelProperty这两个注解只是为实体添加注释说明。

@ApiModel为类添加注释说明

@ApiModelProperty为类属性添加注释说明


这里还有个坑! 在实体类中整型、浮点型属性使用@ApiModelProperty注解时,记得给注解加属性example,否则会报错:java.lang.NumberFormatException: empty String。虽然不影响Swagger的运行,但是控制台打印个错误还是不爽。😂

Swagger-emptyString

1.7 常用注释

Swagger的注释可用于说明某个属性、方法、类、参数:

注解 注解位置
@Api 作用在模块类上
@ApiOperation 作用在接口方法上
@ApiModel 作用在模型类上:如VO、BO
@ApiModelProperty 作用在类方法和属性上,hidden属性设置为true可以隐藏该属性
@ApiParam 作用在参数、方法和字段上,类似@ApiModelProperty

在控制类中使用注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HelloController {
// ...

//Operation接口
@ApiOperation("Hello控制类")
@GetMapping(value = "/hello2")
public String hello2(@ApiParam("用户名") String username){
return "hello"+username;
}

@ApiOperation("post测试类")
@PostMapping(value = "/postt")
public Account postt(@ApiParam("用户名") Account account){
return account;
}
}

hello2注解说明:

Swagger界面-注解

1.8 设置Swagger皮肤

我们在最初使用Swagger时,导入了Swagger默认UI的依赖:

1
2
3
4
5
<dependency> 
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

这时候,UI的风格是Swagger的默认风格。我们可以导入不同的依赖来更换不同的主题。

Bootstrap风格:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>

LayerUI风格:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.github.caspar-chen/swagger-ui-layer -->
<dependency>
<groupId>com.github.caspar-chen</groupId>
<artifactId>swagger-ui-layer</artifactId>
<version>1.1.3</version>
</dependency>

我们只需要在 Maven仓库 中搜索关键词swagger-ui,就可以找到很多的主题了。😎

2. 任务

2.1 异步任务

所谓异步任务,就是异步处理的任务。比如,我们在网站上发邮件,后台就会执行发邮件的逻辑,但是发邮件这个操作需要一定的时间,不是立刻就发送成功的,在这期间,如果不使用异步处理,就会造成前台无响应,直到邮件发送成功,前台才有响应,这是十分不友好的。因此,我们对于一些任务需要进行异步处理,即:采用多线程的方式来处理这些任务。

异步任务操作

创建一个SpringBoot项目,在项目中创建一个名为service的包。

在service包下创建一个名为AsyncService的类。

AsyncService中编写一个方法,给线程进行休眠,模拟业务响应:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class AsyncService {
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("数据处理完毕");
}
}

然后再创建一个controller包,在包下创建一个名为AsyncController的控制类:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class AsyncController {

@Autowired
AsyncService asyncService;

@RequestMapping("/hello")
public String hello(){
asyncService.hello();
return "OK";
}
}

启动程序,访问http://localhost:8080/hello。这时,网页会卡顿三秒(转圈三秒),然后跳转界面并显示 “OK” 在界面上。

上面模拟的是同步等待的情况,在实际业务中,我们想让用户直接得到信息,可以在后台使用多线程的方法,但是每次自己手写显得过于麻烦,因此,SpringBoot给了我们一个封装的注解@Async

给业务处理方法加上注解@Async

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class AsyncService {
//告诉Spring这是一个异步方法
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("数据处理完毕");
}
}

还需要在主启动类上加上一个异步的注解@EnableAsync

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//开启异步注解
@EnableAsync
public class Springboot08TestApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot08TestApplication.class, args);
}

}

最后,我们再运行项目,访问http://localhost:8080/hello。这个时候,界面不会出现卡顿,而是直接跳转至消息界面,所对应的,后台正在进行处理:三秒后,控制台打印数据处理完毕

2.2 邮件发送

引入依赖:

1
2
3
4
5
<!--javax.mail:配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

从依赖名我们可以看出,这是SpringBoot官方的启动器。我们可以先点进去看一下,在这个依赖中可以看到:

1
2
3
4
5
6
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>1.6.4</version>
<scope>compile</scope>
</dependency>

这个依赖就是用来完成邮件发送的。

除此之外,我们可以全局搜索MailAutoConfiguration,看看SpringBoot有没有编写一个自动配置类。果不其然,我们发现了一个名为MailSenderAutoConfiguration的自动配置类。在这个类中,我们并没有发现注册的bean,我们可以看看它导入的类中有没有。点击导入的第一个类:

MailSenderAutoConfiguration

进入类后:

MailSenderJndiConfiguration

然后,我们可以看一下配置文件MailProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ConfigurationProperties(prefix = "spring.mail")
public class MailProperties {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private String host;
private Integer port;
private String username;
private String password;
private String protocol = "smtp";
private Charset defaultEncoding = DEFAULT_CHARSET;
private Map<String, String> properties = new HashMap<>();
private String jndiName;

// ...
}

通过阅读代码,我们得知:我们可以在主配置文件中进行关于邮件发送的配置,然后SpringBoot就会自动进行装配。

我们在此使用QQ邮箱做示范。为了不让我们的密码直接暴露在代码前,我们需要开启POP3和SMTP服务以获取授权码,进入QQ邮箱后,设置 – 账户 – 开启服务:

开启POP3和SMTP服务

然后,我们在主配置文件中进行配置:

1
2
3
4
5
spring.mail.username=cy.mofan@qq.com
spring.mail.password=开启服务后得到的授权码
spring.mail.host=smtp.qq.com
#开启加密验证
spring.mail.properties.mail.smtp.ssl.enable=true

最后,我们可以在测试类中编写测试代码:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@SpringBootTest
class Springboot08TestApplicationTests {

@Autowired
JavaMailSender mailSender;

@Test
void contextLoads() {

//一封简单的邮件
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
// 标题
simpleMailMessage.setSubject("Yang,你好!");
// 内容
simpleMailMessage.setText("hello boy");
// 发送给谁
simpleMailMessage.setTo("cy.mofan@qq.com");
// 发送者
simpleMailMessage.setFrom("cy.mofan@qq.com");
mailSender.send(simpleMailMessage);
}

@Test
void contextLoads2() throws MessagingException {

//一封复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装~
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
helper.setSubject("Yang");
helper.setText("<p style='color:red'>你在干嘛?</p>",true);

//附件 发送一张图片 第一个参数表示文件名
helper.addAttachment("1.png",new File("C:\\Users\\Administrator\\Desktop\\1.png"));

helper.setTo("cy.mofan@qq.com");
helper.setFrom("cy.mofan@qq.com");

mailSender.send(mimeMessage);
}

/**
* 邮件发送方法
* @param html 支持多文件上传
* @param subject 发送邮件标题
* @param text 邮件内容
* @param htmlTag 邮件内容内部是否开启html解析
* @param fileName 附件名
* @param filePath 附件地址
* @param recipient 收件人
* @param sender 发件人
* @throws MessagingException
* @Author mofan
*/
public void sendMail(Boolean html, String subject, String text, Boolean htmlTag,
String fileName, String filePath,
String recipient, String sender) throws MessagingException {

//一封复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装~
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,html);
helper.setSubject(subject);
helper.setText(text,htmlTag);

//附件
helper.addAttachment(fileName,new File(fileName));
helper.setTo(recipient);
helper.setFrom(sender);

mailSender.send(mimeMessage);
}

}

因为要测试发送邮件,所以 得联网 才能测试成功。

2.3 定时任务

所谓定时任务,就是在规定的时间上自动执行某个操作。比如:每天凌晨的时候,输出并分析前一天的日志信息,这就需要定时任务来完成。SpringBoot为我们异步执行任务调度提供了两个接口:

  • TaskExecutor接口 -----> 任务调度者
  • TaskScheduler接口 -----> 任务执行程序

同时,还提供了两个注解:

  • @EnableScheduling(表示开启定时功能的注解,位置在主启动类上)
  • @Scheduled(表示任务什么时候执行)

@Scheduled中,属性需要使用Cron表达式,来表示任务什么时候执行。

Cron表达式:Cron表达式在线工具

定时任务的使用

首先在service包下创建一个类ScheduledService ,在其中编写一个方法,这个方法需要定时执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class ScheduledService {

//在特定的时间执行这个方法
//cron表达式
//秒 分 时 日 月 周几
/**
30 15 10 * * ? 每天10点15分30秒执行一次
*/
@Scheduled(cron = "30 15 10 * * ?")
public void hello(){
System.out.println("hello,你被执行了......");
}
}

然后在主启动类上使用@EnableScheduling注解:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
//开启定时功能注解
@EnableScheduling
public class Springboot08TestApplication {

public static void main(String[] args) {
SpringApplication.run(Springboot08TestApplication.class, args);
}

}

3. 分布式系统

3.1 分布式系统理论

简单介绍一下分布式系统

《分布式系统原理与范式》一书中有:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”。

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

分布式系统是建立在网络之上的软件系统。

什么时候使用分布式?

只有当单个节点无法满足我们的需求,且硬件的提升已经远超过所带来的利益时才使用分布式系统。简单的、个人的系统,不用使用分布式系统,因为使用了分布式系统会引进很多单机系统没有的问题,为了解决这些问题需要已经各种协议或机制,从而带来更多的问题…

Dubbo文档

Dubbo官网:Dubbo

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

Dubbo

单一应用架构

当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。

垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

流动计算架构

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。

3.2 RPC

参考链接:RPC原理解析你应该知道的RPC原理

RPC【Remote Procedure Call】:远程过程调用,是一种进程间通信方式,是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数或方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数。

RPC的两个核心模块:通讯、序列化。

RPC基本原理

RPC基本原理

步骤解析

参考链接:Dubbo-

RPC步骤解析

(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;

(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);

(3) 客户端通过sockets将消息发送到服务端;

(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);

(5) 服务端存根( server stub)根据解码结果调用本地的服务;

(6) 本地服务执行并将结果返回给服务端存根( server stub);

(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);

(8) 服务端(server)通过sockets将消息发送到客户端;

(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);

(10) 客户端(client)得到最终结果。

RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。

注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。

3.3 搭建测试环境

Dubbo

Dubbo |ˈdʌbəʊ| 官网:Dubbo

参考链接:Dubbo-浅谈dubbo服务

Dubbo运行原理图:

Dubbo运行原理

服务提供者(Provider) :暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

服务消费者(Consumer) :调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

监控中心(Monitor) :服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

调用关系说明

  • 服务容器负责启动,加载,运行服务提供者。
  • 服务提供者在启动时,向注册中心注册自己提供的服务。
  • 服务消费者在启动时,向注册中心订阅自己所需的服务。
  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

Windows下安装ZK

下载地址:清华镜像下载 选择版本后,下载并解压。

解压完成后,进入解压的ZK目录下,再进入bin目录,运行zkServer.cmd,这个时候一般会闪退。我们可以编辑zkServer.cmd文件,在末尾处添加pause

zkServer.cmd

这样出错后就不会直接闪退,而是会打印错误信息:

ZK启动服务闪退错误信息

根据错误信息,我们得知:闪退是因为缺少zoo.cfg文件造成的。

前往conf目录,将zoo_sample.cfg复制一份在当前目录,然后改名为zoo.cfg

在这个文件中我们可以看到以下信息:

1
2
3
4
5
6
7
tickTime=2000
initLimit=10
syncLimit=5
# 临时数据存储的目录
dataDir=/tmp/zookeeper
# ZK默认端口号
clientPort=2181

完成简单的修改后,我们在启动服务。双击运行zkServer.cmd

ZK服务启动成功

表示启动成功!

然后我们运行zkCli.cmd,启动客户端。

注意:一定要先启动服务,并且保持服务的黑窗口一直运行!

1
2
3
WATCHER::

WatchedEvent state:SyncConnected type:None path:null

启动客户端后可能会出现上述情况,我们点击回车即可。


启动后,在客户端查看一下所有节点:

1
2
ls /
# 只显示zookeeper一个节点

我们还可以自己创建一个节点:

1
2
# 创建一个名为mofan,值为yang的节点
create -e /mofan yang

获取创建节点的值:

1
get /mofan

查看节点的值

最后,在查看一下所有节点,输入命令ls /,这时候会发现我们创建的节点也会显示出来。

安装dubbo-admin

Dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的Java程序连接到ZooKeeper,并利用ZooKeeper消费、提供服务。

为了让用户更好的管理监控众多的Dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,dubbo-admin只是一个后台监控系统,因此,也可以不使用它。

下载地址:Dubbo下载 (下载时,一般选择下载主节点,即:master节点的文件)

如果使用直接下载zip文件,而非Git clone下载,这时候需要解压文件。

解压文件后,进入dubbo-admin-master/dubbo-admin-master/dubbo-admin/src/main/resources,然后找到application.properties文件:

1
2
3
4
5
6
7
8
9
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

如果你修改了ZK的地址端口,这里也需要修改!

然后我们需要在 项目目录下 打包dubbo-admin,使用命令行,运行:

1
mvn clean package -Dmaven.test.skip=true

先启动ZK的服务!一定要先启动,因为dubbo依赖ZK!

再执行 dubbo-admin\target 下的dubbo-admin-xxx.jar:

1
java -jar dubbo-admin-xxx.jar

运行完成后,我们在浏览器地址栏输入http://localhost:7001/,再输入账号(root)和密码(root),就可以进入dubbo-admin的界面了:

dubbo-admin

至于为什么是7001端口,账号密码为什么都是root的原因也很简单,还记得前面说的application.properties文件吗?如果你想要改,在这里配置文件中修改就可以了。

4. SpringBoot-Dubbo-ZK

4.1 搭建架构

使用IDEA创建一个空项目,然后在项目中创建一个SpringBoot模块,选中Web依赖,取名为provider-server表示服务提供者模块。

然后导入依赖:

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
34
35
36
37
<!--导入依赖:Dubbo ZK-->
<!-- dubbo-spring-boot-starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--ZK Client-->
<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>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包,在包下创建TicketService接口:

1
2
3
public interface TicketService {
public String getTicket();
}

再在包下创建接口的实现类TicketServiceImpl

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

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

/**
* @author 默烦
* @date 2020/6/13
*/
// ZK:服务注册与发现
@Service // 导入Dubbo的@Service注解,可以被扫描到,在项目一启动就主动注册到注册中心,暴露服务
@Component // 使用Dubbo尽量不要@Service,用于区分
public class TicketServiceImpl implements TicketService{
@Override
public String getTicket() {
return "hello,provider-server";
}
}

我为什么要将导入的包也复制进来? 注意此处的@Service注解是Dubbo的。

在配置文件中进行配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
server.port=8001

# 服务应用名字
dubbo.application.name=provider-server
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

# dubbo.registry.protocol=zookeeper dubbo 注册协议名
# dubbo.protocol.name 指定通信协议
# dubbo.protocol.port 指定通信端口

# 哪些服务被注册
dubbo.scan.base-packages=com.yang.service

如果不在配置文件中设置 dubbo.scan.base-packages,那么就要在主启动类上加上注解 @EnableDubbo,开启基于注解的 Dubbo 功能。

最后,可以进行测试!

打开ZK服务 ,然后运行provider-server模块,运行完之后我们应该怎么看服务被注册呢?我们可以启动dubbo-admin,进监控后台查看。点击导航栏的 服务治理 — 提供者:

Dubbo提供者

点击机器IP:

Dubbo服务详情

4.2 服务消费者

完成架构搭建后,不要关闭ZK,不要关闭dubbo-admin,不要关闭提供者程序!

完成架构搭建后,不要关闭ZK,不要关闭dubbo-admin,不要关闭提供者程序!

完成架构搭建后,不要关闭ZK,不要关闭dubbo-admin,不要关闭提供者程序!

重要的事情说三遍!

首先,在项目中再创建一个SpringBoot模块,导入Web依赖,命名为consumer-server,表示消费者服务。

导入与提供者相同的依赖。

创建service包,将提供者的TicketService接口复制一份到这个包下,保证这个接口在消费者服务的目录结构与提供者的目录结构相同,方便待会使用。

再在包下创建一个UserService类:

consumer-server目录结构

在这个类中编写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.yang.service;

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

/**
* @author 默烦
* @date 2020/6/13
*/
@Service // 放到容器中
public class UserService {

// 获取provider提供的票,要去注册中心拿到服务
@Reference // 引用服务,pom坐标或定义路径相同的接口名
TicketService ticketService;

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

}

我为什么要将导入的包也复制进来? 注意此处的@Service注解是Spring的。

在配置文件中进行配置:

1
2
3
4
5
6
server.port=8002

# 消费者去哪里获取服务并暴露自己的名字
dubbo.application.name=consumer-server
# 注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181

编写测试类进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
class ConsumerServerApplicationTests {

@Autowired
UserService userService;

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

}

然后,运行consumer-server模块,在控制台中查看输出:

测试结果打印

Dubbo服务