mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
[docs improve]seo优化&文章标题优化
This commit is contained in:
parent
839aacfc28
commit
a600b8d8ca
@ -198,10 +198,6 @@ If you have not touched Java Web development, you can first look at my summary o
|
||||
|
||||
[MyBatis Common Interview Questions Summary](docs/system-design/framework/mybatis/mybatis-interview.md)
|
||||
|
||||
#### Spring Cloud
|
||||
|
||||
[Getting Started with Spring Cloud in Plain English](docs/system-design/framework/springcloud/springcloud-intro.md)
|
||||
|
||||
### Security
|
||||
|
||||
#### Certification Authorization
|
||||
|
@ -244,10 +244,6 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle
|
||||
|
||||
[MyBatis 常见面试题总结](docs/system-design/framework/mybatis/mybatis-interview.md)
|
||||
|
||||
#### Spring Cloud
|
||||
|
||||
[大白话入门 Spring Cloud](docs/system-design/framework/springcloud/springcloud-intro.md)
|
||||
|
||||
### 安全
|
||||
|
||||
#### 认证授权
|
||||
|
@ -160,11 +160,18 @@ export const sidebarConfig = defineSidebarConfig({
|
||||
icon: "network",
|
||||
collapsable: true,
|
||||
children: [
|
||||
"osi&tcp-ip-model",
|
||||
"http&https",
|
||||
"http1.0&http1.1",
|
||||
"http-status-codes",
|
||||
"other-network-questions",
|
||||
{
|
||||
text: "重要知识点",
|
||||
icon: "star",
|
||||
collapsable: true,
|
||||
children: [
|
||||
"osi&tcp-ip-model",
|
||||
"http&https",
|
||||
"http1.0&http1.1",
|
||||
"http-status-codes",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -320,7 +327,6 @@ export const sidebarConfig = defineSidebarConfig({
|
||||
},
|
||||
"mybatis/mybatis-interview",
|
||||
"netty",
|
||||
"springcloud/springcloud-intro",
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -336,20 +342,11 @@ export const sidebarConfig = defineSidebarConfig({
|
||||
icon: "basic",
|
||||
collapsable: true,
|
||||
children: [
|
||||
"RESTfulAPI",
|
||||
"naming",
|
||||
"refactoring",
|
||||
{
|
||||
text: "RESTful API",
|
||||
link: "RESTfulAPI",
|
||||
},
|
||||
{
|
||||
text: "编码命名",
|
||||
link: "naming",
|
||||
},
|
||||
{
|
||||
text: "重构",
|
||||
link: "refactoring",
|
||||
},
|
||||
{
|
||||
text: "单元测试",
|
||||
text: "单元测试指南",
|
||||
link: "unit-test",
|
||||
},
|
||||
],
|
||||
|
@ -1,3 +1,4 @@
|
||||
$theme-color: #2980B9;
|
||||
$sidebar-width: 20rem;
|
||||
$sidebar-mobile-width: 16rem;
|
||||
$content-width: 60em;
|
||||
|
@ -59,64 +59,5 @@ export default defineThemeConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
// pwa: {
|
||||
// favicon: "/favicon.ico",
|
||||
// cachePic: true,
|
||||
// apple: {
|
||||
// icon: "/assets/icon/apple-icon-152.png",
|
||||
// statusBarColor: "black",
|
||||
// },
|
||||
// msTile: {
|
||||
// image: "/assets/icon/ms-icon-144.png",
|
||||
// color: "#ffffff",
|
||||
// },
|
||||
// manifest: {
|
||||
// icons: [
|
||||
// {
|
||||
// src: "/assets/icon/chrome-mask-512.png",
|
||||
// sizes: "512x512",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/chrome-mask-192.png",
|
||||
// sizes: "192x192",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/chrome-512.png",
|
||||
// sizes: "512x512",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/chrome-192.png",
|
||||
// sizes: "192x192",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// ],
|
||||
// shortcuts: [
|
||||
// {
|
||||
// name: "Guide",
|
||||
// short_name: "Guide",
|
||||
// url: "/guide/",
|
||||
// icons: [
|
||||
// {
|
||||
// src: "/assets/icon/guide-maskable.png",
|
||||
// sizes: "192x192",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/guide-monochrome.png",
|
||||
// sizes: "192x192",
|
||||
// purpose: "monochrome",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 计算机网络常见知识点&面试题(补充)
|
||||
title: 计算机网络常见面试题总结
|
||||
category: 计算机基础
|
||||
tag:
|
||||
- 计算机网络
|
||||
|
@ -4,12 +4,14 @@ category: 计算机基础
|
||||
tag:
|
||||
- 操作系统
|
||||
- Linux
|
||||
head:
|
||||
- - meta
|
||||
- name: description
|
||||
content: 简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
|
||||
---
|
||||
|
||||
简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
|
||||
|
||||
_如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!笔芯!_
|
||||
|
||||
## 从认识操作系统开始
|
||||
|
||||

|
||||
|
@ -3,6 +3,12 @@ title: 操作系统常见面试题总结
|
||||
category: 计算机基础
|
||||
tag:
|
||||
- 操作系统
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: 操作系统,进程,进程通信方式,死锁,操作系统内存管理,块表,多级页表,虚拟内存,页面置换算法
|
||||
- name: description
|
||||
content: 很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如进程管理、内存管理、虚拟内存等等。
|
||||
---
|
||||
|
||||
很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如**进程管理**、**内存管理**、**虚拟内存**等等。
|
||||
|
@ -1,24 +1,30 @@
|
||||
---
|
||||
title: Shell 编程入门
|
||||
title: Shell 编程基础知识总结
|
||||
category: 计算机基础
|
||||
tag:
|
||||
- 操作系统
|
||||
- Linux
|
||||
head:
|
||||
- - meta
|
||||
- name: description
|
||||
content: Shell 编程在我们的日常开发工作中非常实用,目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。这篇文章我会简单总结一下 Shell 编程基础知识,带你入门 Shell 编程!
|
||||
---
|
||||
|
||||
# Shell 编程入门
|
||||
Shell 编程在我们的日常开发工作中非常实用,目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。
|
||||
|
||||
这篇文章我会简单总结一下 Shell 编程基础知识,带你入门 Shell 编程!
|
||||
|
||||
## 走进 Shell 编程的大门
|
||||
|
||||
### 为什么要学Shell?
|
||||
### 为什么要学 Shell?
|
||||
|
||||
学一个东西,我们大部分情况都是往实用性方向着想。从工作角度来讲,学习 Shell 是为了提高我们自己工作效率,提高产出,让我们在更少的时间完成更多的事情。
|
||||
|
||||
很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做Linux运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是shell编程也是我们必须要掌握的!
|
||||
很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做 Linux 运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是 Shell 编程也是我们必须要掌握的!
|
||||
|
||||
目前Linux系统下最流行的运维自动化语言就是Shell和Python了。
|
||||
目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。
|
||||
|
||||
两者之间,Shell几乎是IT企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,shell是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过web访问等。Shell是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。
|
||||
两者之间,Shell 几乎是 IT 企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,shell 是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过 web 访问等。Shell 是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。
|
||||
|
||||
另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。
|
||||
|
||||
@ -26,22 +32,20 @@ tag:
|
||||
|
||||
### 什么是 Shell?
|
||||
|
||||
简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。
|
||||
简单来说“Shell 编程就是对一堆 Linux 命令的逻辑化处理”。
|
||||
|
||||
W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。
|
||||
W3Cschool 上的一篇文章是这样介绍 Shell 的,如下图所示。
|
||||

|
||||
|
||||
|
||||
### Shell 编程的 Hello World
|
||||
|
||||
学习任何一门编程语言第一件事就是输出HelloWorld了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。
|
||||
学习任何一门编程语言第一件事就是输出 HelloWorld 了!下面我会从新建文件到 shell 代码编写来说下 Shell 编程如何输出 Hello World。
|
||||
|
||||
|
||||
(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了)
|
||||
(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh 代表 Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了)
|
||||
|
||||
(2) 使脚本具有执行权限:`chmod +x helloworld.sh`
|
||||
|
||||
(3) 使用 vim 命令修改helloworld.sh文件:`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。))
|
||||
(3) 使用 vim 命令修改 helloworld.sh 文件:`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按 i 进入编辑模式----->编辑文件 ------->按 Esc 进入底行模式----->输入:wq/q! (输入 wq 代表写入内容并退出,即保存;输入 q!代表强制退出不保存。))
|
||||
|
||||
helloworld.sh 内容如下:
|
||||
|
||||
@ -51,39 +55,37 @@ helloworld.sh 内容如下:
|
||||
echo "helloworld!"
|
||||
```
|
||||
|
||||
shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中,除了bash shell以外,还有很多版本的shell, 例如zsh、dash等等...不过bash shell还是我们使用最多的。**
|
||||
|
||||
shell 中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在 linux 中,除了 bash shell 以外,还有很多版本的 shell, 例如 zsh、dash 等等...不过 bash shell 还是我们使用最多的。**
|
||||
|
||||
(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)
|
||||
|
||||

|
||||
|
||||
|
||||
## Shell 变量
|
||||
|
||||
### Shell 编程中的变量介绍
|
||||
|
||||
|
||||
**Shell编程中一般分为三种变量:**
|
||||
**Shell 编程中一般分为三种变量:**
|
||||
|
||||
1. **我们自己定义的变量(自定义变量):** 仅在当前 Shell 实例中有效,其他 Shell 启动的程序不能访问局部变量。
|
||||
2. **Linux已定义的环境变量**(环境变量, 例如:`PATH`, `HOME` 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。
|
||||
3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行
|
||||
2. **Linux 已定义的环境变量**(环境变量, 例如:`PATH`, `HOME` 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而 set 命令既可以查看环境变量也可以查看自定义变量。
|
||||
3. **Shell 变量** :Shell 变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行
|
||||
|
||||
**常用的环境变量:**
|
||||
> PATH 决定了shell将到哪些目录中寻找命令或程序
|
||||
HOME 当前用户主目录
|
||||
HISTSIZE 历史记录数
|
||||
LOGNAME 当前用户的登录名
|
||||
HOSTNAME 指主机的名称
|
||||
SHELL 当前用户Shell类型
|
||||
LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量
|
||||
MAIL 当前用户的邮件存放目录
|
||||
PS1 基本提示符,对于root用户是#,对于普通用户是$
|
||||
|
||||
> PATH 决定了 shell 将到哪些目录中寻找命令或程序
|
||||
> HOME 当前用户主目录
|
||||
> HISTSIZE 历史记录数
|
||||
> LOGNAME 当前用户的登录名
|
||||
> HOSTNAME 指主机的名称
|
||||
> SHELL 当前用户 Shell 类型
|
||||
> LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量
|
||||
> MAIL 当前用户的邮件存放目录
|
||||
> PS1 基本提示符,对于 root 用户是#,对于普通用户是\$
|
||||
|
||||
**使用 Linux 已定义的环境变量:**
|
||||
|
||||
比如我们要看当前用户目录可以使用:`echo $HOME`命令;如果我们要看当前用户Shell类型 可以使用`echo $SHELL`命令。可以看出,使用方法非常简单。
|
||||
比如我们要看当前用户目录可以使用:`echo $HOME`命令;如果我们要看当前用户 Shell 类型 可以使用`echo $SHELL`命令。可以看出,使用方法非常简单。
|
||||
|
||||
**使用自己定义的变量:**
|
||||
|
||||
@ -94,21 +96,19 @@ hello="hello world"
|
||||
echo $hello
|
||||
echo "helloworld!"
|
||||
```
|
||||

|
||||
|
||||

|
||||
|
||||
**Shell 编程中的变量名的命名的注意事项:**
|
||||
|
||||
|
||||
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,但是可以使用下划线(_)开头。
|
||||
- 中间不能有空格,可以使用下划线(_)。
|
||||
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,但是可以使用下划线(\_)开头。
|
||||
- 中间不能有空格,可以使用下划线(\_)。
|
||||
- 不能使用标点符号。
|
||||
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
|
||||
|
||||
- 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。
|
||||
|
||||
### Shell 字符串入门
|
||||
|
||||
字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和Java中有所不同。
|
||||
字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和 Java 中有所不同。
|
||||
|
||||
**单引号字符串:**
|
||||
|
||||
@ -118,6 +118,7 @@ name='SnailClimb'
|
||||
hello='Hello, I am '$name'!'
|
||||
echo $hello
|
||||
```
|
||||
|
||||
输出内容:
|
||||
|
||||
```
|
||||
@ -139,7 +140,6 @@ echo $hello
|
||||
Hello, I am SnailClimb!
|
||||
```
|
||||
|
||||
|
||||
### Shell 字符串常见操作
|
||||
|
||||
**拼接字符串:**
|
||||
@ -161,7 +161,6 @@ echo $greeting_2 $greeting_3
|
||||
|
||||

|
||||
|
||||
|
||||
**获取字符串长度:**
|
||||
|
||||
```shell
|
||||
@ -175,6 +174,7 @@ expr length "$name";
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
10
|
||||
10
|
||||
@ -186,6 +186,7 @@ expr length "$name";
|
||||
expr 5+6 // 直接输出 5+6
|
||||
expr 5 + 6 // 输出 11
|
||||
```
|
||||
|
||||
对于某些运算符,还需要我们使用符号`\`进行转义,否则就会提示语法错误。
|
||||
|
||||
```shell
|
||||
@ -197,7 +198,6 @@ expr 5 \* 6 // 输出30
|
||||
|
||||
简单的字符串截取:
|
||||
|
||||
|
||||
```shell
|
||||
#从字符串第 1 个字符开始往后截取 10 个字符
|
||||
str="SnailClimb is a great man"
|
||||
@ -225,8 +225,7 @@ s5=${var##*/} #linux-shell-variable.html
|
||||
|
||||
### Shell 数组
|
||||
|
||||
bash支持一维数组(不支持多维数组),并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。
|
||||
|
||||
bash 支持一维数组(不支持多维数组),并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
@ -246,12 +245,11 @@ unset array; # 删除数组中的所有元素
|
||||
for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容
|
||||
```
|
||||
|
||||
|
||||
## Shell 基本运算符
|
||||
|
||||
> 说明:图片来自《菜鸟教程》
|
||||
|
||||
Shell 编程支持下面几种运算符
|
||||
Shell 编程支持下面几种运算符
|
||||
|
||||
- 算数运算符
|
||||
- 关系运算符
|
||||
@ -273,14 +271,13 @@ val=`expr $a + $b`
|
||||
echo "Total value : $val"
|
||||
```
|
||||
|
||||
|
||||
### 关系运算符
|
||||
|
||||
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
|
||||
|
||||

|
||||
|
||||
通过一个简单的示例演示关系运算符的使用,下面shell程序的作用是当score=100的时候输出A否则输出B。
|
||||
通过一个简单的示例演示关系运算符的使用,下面 shell 程序的作用是当 score=100 的时候输出 A 否则输出 B。
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
@ -315,7 +312,6 @@ echo $a;
|
||||
|
||||
### 布尔运算符
|
||||
|
||||
|
||||

|
||||
|
||||
这里就不做演示了,应该挺简单的。
|
||||
@ -337,6 +333,7 @@ else
|
||||
echo "a 不等于 b"
|
||||
fi
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
@ -349,7 +346,7 @@ a 不等于 b
|
||||
|
||||
使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。
|
||||
|
||||
## shell流程控制
|
||||
## shell 流程控制
|
||||
|
||||
### if 条件语句
|
||||
|
||||
@ -376,7 +373,7 @@ fi
|
||||
a 小于 b
|
||||
```
|
||||
|
||||
相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。
|
||||
相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。
|
||||
|
||||
### for 循环语句
|
||||
|
||||
@ -401,9 +398,9 @@ do
|
||||
done
|
||||
```
|
||||
|
||||
**输出1到5:**
|
||||
**输出 1 到 5:**
|
||||
|
||||
通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要,下面来看一个例子:
|
||||
通常情况下 shell 变量调用需要加 \$,但是 for 的 (()) 中不需要,下面来看一个例子:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
@ -412,7 +409,6 @@ for((i=1;i<=5;i++));do
|
||||
done;
|
||||
```
|
||||
|
||||
|
||||
### while 语句
|
||||
|
||||
**基本的 while 循环语句:**
|
||||
@ -427,7 +423,7 @@ do
|
||||
done
|
||||
```
|
||||
|
||||
**while循环可用于读取键盘信息:**
|
||||
**while 循环可用于读取键盘信息:**
|
||||
|
||||
```shell
|
||||
echo '按下 <CTRL-D> 退出'
|
||||
@ -477,7 +473,6 @@ echo "-----函数执行完毕-----"
|
||||
-----函数执行完毕-----
|
||||
```
|
||||
|
||||
|
||||
### 有返回值的函数
|
||||
|
||||
**输入两个数字之后相加并返回结果:**
|
||||
|
@ -1,9 +1,15 @@
|
||||
---
|
||||
title: MySQL知识点&面试题总结
|
||||
title: MySQL常见面试题总结
|
||||
category: 数据库
|
||||
tag:
|
||||
- MySQL
|
||||
- 大厂面试
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: MySQL基础,MySQL基础架构,MySQL存储引擎,MySQL查询缓存,MySQL事务,MySQL锁等内容。
|
||||
- name: description
|
||||
content: 一篇文章总结MySQL常见的知识点和面试题,涵盖MySQL基础、MySQL基础架构、MySQL存储引擎、MySQL查询缓存、MySQL事务、MySQL锁等内容。
|
||||
---
|
||||
|
||||
## MySQL 基础
|
||||
|
@ -1,11 +1,11 @@
|
||||
---
|
||||
title: 缓存基础
|
||||
title: 缓存基础常见面试题总结
|
||||
category: 数据库
|
||||
tag:
|
||||
- Redis
|
||||
---
|
||||
|
||||
**缓存基础** 为我的[知识星球](https://www.yuque.com/docs/share/8a30ffb5-83f3-40f9-baf9-38de68b906dc)专属内容,已经整理到了[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)中。
|
||||
**缓存基础常见知识点&面试题总结** 为我的[知识星球](https://www.yuque.com/docs/share/8a30ffb5-83f3-40f9-baf9-38de68b906dc)专属内容,已经整理到了[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)中。
|
||||
|
||||
[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7) 的部分内容展示如下,你可以将其看作是 [JavaGuide](https://javaguide.cn/#/) 的补充完善,两者可以配合使用。
|
||||
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Redis知识点&面试题总结
|
||||
title: Redis常见面试题总结
|
||||
category: 数据库
|
||||
tag:
|
||||
- Redis
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Redis基础,Redis常见数据结构,Redis线程模型,Redis内存管理,Redis事务,Redis性能优化
|
||||
- name: description
|
||||
content: 一篇文章总结Redis常见的知识点和面试题,涵盖Redis基础、Redis常见数据结构、Redis线程模型、Redis内存管理、Redis事务、Redis性能优化等内容。
|
||||
---
|
||||
|
||||
## Redis 基础
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Dubbo常见知识点&面试题总结
|
||||
title: Dubbo常见面试题总结
|
||||
category: 分布式
|
||||
tag:
|
||||
- rpc
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: RPC基础常见知识点&面试题总结
|
||||
title: RPC基础常见面试题总结
|
||||
category: 分布式
|
||||
tag:
|
||||
- rpc
|
||||
|
@ -1,4 +1,13 @@
|
||||
# 负载均衡
|
||||
---
|
||||
title: 负载均衡
|
||||
category: 高性能
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: 客户端负载均衡,服务负载均衡,Nginx,负载均衡算法,七层负载均衡,DNS解析
|
||||
- name: description
|
||||
content: 负载均衡指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力。负载均衡可以简单分为服务端负载均衡和客户端负载均衡 这两种。服务端负载均衡涉及到的知识点更多,工作中遇到的也比较多,因为,我会花更多时间来介绍。
|
||||
---
|
||||
|
||||
**负载均衡** 相关的面试题为我的[知识星球](https://www.yuque.com/docs/share/8a30ffb5-83f3-40f9-baf9-38de68b906dc)专属内容,已经整理到了[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)中。
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
|
||||
# Kafka知识点&面试题总结
|
||||
---
|
||||
title: Kafka常见面试题总结
|
||||
category: 高性能
|
||||
tag:
|
||||
- 消息队列
|
||||
---
|
||||
|
||||
### Kafka 是什么?主要应用场景有哪些?
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
# 消息队列知识点&面试题总结
|
||||
---
|
||||
title: 消息队列基础常见面试题总结
|
||||
category: 高性能
|
||||
tag:
|
||||
- 消息队列
|
||||
---
|
||||
|
||||
“RabbitMQ?”“Kafka?”“RocketMQ?”...在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。
|
||||
|
||||
|
@ -1,5 +1,10 @@
|
||||
|
||||
# RabbitMQ 入门总结
|
||||
---
|
||||
title: RabbitMQ基础知识总结
|
||||
category: 高性能
|
||||
tag:
|
||||
- RabbitMQ
|
||||
- 消息队列
|
||||
---
|
||||
|
||||
## 一 RabbitMQ 介绍
|
||||
|
||||
|
@ -1,8 +1,12 @@
|
||||
# RocketMQ入门总结
|
||||
---
|
||||
title: RocketMQ基础知识总结
|
||||
category: 高性能
|
||||
tag:
|
||||
- RocketMQ
|
||||
- 消息队列
|
||||
---
|
||||
|
||||
> 文章很长,点赞再看,养成好习惯😋😋😋
|
||||
>
|
||||
> [本文由 FrancisQ 老哥投稿!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485969&idx=1&sn=6bd53abde30d42a778d5a35ec104428c&chksm=cea245daf9d5cccce631f93115f0c2c4a7634e55f5bef9009fd03f5a0ffa55b745b5ef4f0530&token=294077121&lang=zh_CN#rd)
|
||||
> [本文由 FrancisQ 投稿!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485969&idx=1&sn=6bd53abde30d42a778d5a35ec104428c&chksm=cea245daf9d5cccce631f93115f0c2c4a7634e55f5bef9009fd03f5a0ffa55b745b5ef4f0530&token=294077121&lang=zh_CN#rd)
|
||||
|
||||
## 消息队列扫盲
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
# RocketMQ常见问题
|
||||
---
|
||||
title: RocketMQ常见面试题总结
|
||||
category: 高性能
|
||||
tag:
|
||||
- RocketMQ
|
||||
- 消息队列
|
||||
---
|
||||
|
||||
本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。
|
||||
> 本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。
|
||||
|
||||
## 1 单机版消息中心
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
# 读写分离&分库分表
|
||||
|
||||
大家好呀!今天和小伙伴们聊聊读写分离以及分库分表。
|
||||
---
|
||||
title: 数据库读写分离和分库分表
|
||||
category: 高性能
|
||||
head:
|
||||
- - meta
|
||||
- name: description
|
||||
content: 相信很多小伙伴们对于读写分离和分库分表这两个概念已经比较熟悉了,这篇文章全程都是大白话的形式,希望能够给你带来不一样的感受。
|
||||
---
|
||||
|
||||
相信很多小伙伴们对于这两个概念已经比较熟悉了,这篇文章全程都是大白话的形式,希望能够给你带来不一样的感受。
|
||||
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java基础常见知识&面试题总结(上)
|
||||
title: Java基础常见面试题总结(上)
|
||||
category: Java
|
||||
tag:
|
||||
- Java基础
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: JVM,JDK,JRE,字节码详解,Java 基本数据类型,装箱和拆箱
|
||||
- name: description
|
||||
content: 全网质量最高的Java基础常见知识点和面试题总结,希望对你有帮助!
|
||||
---
|
||||
|
||||
## 基础概念与常识
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java基础常见知识&面试题总结(中)
|
||||
title: Java基础常见面试题总结(中)
|
||||
category: Java
|
||||
tag:
|
||||
- Java基础
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: 面向对象,构造方法,接口,抽象类,String,Object
|
||||
- name: description
|
||||
content: 全网质量最高的Java基础常见知识点和面试题总结,希望对你有帮助!
|
||||
---
|
||||
|
||||
## 面向对象基础
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java基础知识&面试题总结(下)
|
||||
title: Java基础常见面试题总结(下)
|
||||
category: Java
|
||||
tag:
|
||||
- Java基础
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Java异常,泛型,反射,IO,注解
|
||||
- name: description
|
||||
content: 全网质量最高的Java基础常见知识点和面试题总结,希望对你有帮助!
|
||||
---
|
||||
|
||||
## 异常
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java集合常见知识点&面试题总结(上)
|
||||
title: Java集合常见面试题总结(上)
|
||||
category: Java
|
||||
tag:
|
||||
- Java集合
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: Collection,List,Set,Queue,Deque,PriorityQueue,
|
||||
- name: description
|
||||
content: Java集合常见知识点和面试题总结,希望对你有帮助!
|
||||
---
|
||||
|
||||
## 集合概述
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java集合常见知识点&面试题总结(下)
|
||||
title: Java容器常见面试题总结(下)
|
||||
category: Java
|
||||
tag:
|
||||
- Java集合
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: HashMap,ConcurrentHashMap,Hashtable,List,Set
|
||||
- name: description
|
||||
content: Java集合常见知识点和面试题总结,希望对你有帮助!
|
||||
---
|
||||
|
||||
## Map 接口
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java 并发常见知识点&面试题总结(基础篇)
|
||||
title: Java 并发常见面试题总结(上)
|
||||
category: Java
|
||||
tag:
|
||||
- Java并发
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: 线程和进程,并发和并行,多线程,死锁,线程的生命周期
|
||||
- name: description
|
||||
content: Java并发常见知识点和面试题总结(含详细解答),希望对你有帮助!
|
||||
---
|
||||
|
||||
## 什么是线程和进程?
|
||||
|
@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Java 并发常见知识点&面试题总结(进阶篇)
|
||||
title: Java 并发常见面试题总结(下)
|
||||
category: Java
|
||||
tag:
|
||||
- Java并发
|
||||
head:
|
||||
- - meta
|
||||
- name: keywords
|
||||
content: 多线程,死锁,synchronized,ReentrantLock,volatile,ThreadLocal,线程池,cas,aqs
|
||||
- name: description
|
||||
content: Java并发常见知识点和面试题总结(含详细解答),希望对你有帮助!
|
||||
---
|
||||
|
||||
## synchronized 关键字
|
||||
|
@ -1,6 +1,11 @@
|
||||
# MyBatis 常见面试总结
|
||||
---
|
||||
title: MyBatis常见面试题总结
|
||||
category: 框架
|
||||
tag:
|
||||
- MyBatis
|
||||
---
|
||||
|
||||
> 本篇文章是 JavaGuide 收集自网络,原出处不明。
|
||||
> 本篇文章由 JavaGuide 收集自网络,原出处不明。
|
||||
|
||||
MyBatis 技术内幕系列博客,从原理和源码角度,介绍了其内部实现细节,无论是写的好与不好,我确实是用心写了,由于并不是介绍如何使用 MyBatis 的文章,所以,一些参数使用细节略掉了,我们的目标是介绍 MyBatis 的技术架构和重要组成部分,以及基本运行原理。
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Netty 知识点&面试题总结
|
||||
title: Netty常见面试题总结
|
||||
category: 框架
|
||||
---
|
||||
|
||||
|
@ -5,10 +5,6 @@ tag:
|
||||
- SpringBoot
|
||||
---
|
||||
|
||||
#
|
||||
|
||||
> 本文已经收录进 Github 95k+ Star 的Java项目JavaGuide 。JavaGuide项目地址 : https://github.com/Snailclimb/JavaGuide 。
|
||||
>
|
||||
> 作者:[Miki-byte-1024](https://github.com/Miki-byte-1024) & [Snailclimb](https://github.com/Snailclimb)
|
||||
|
||||
每次问到 Spring Boot, 面试官非常喜欢问这个问题:“讲述一下 SpringBoot 自动装配原理?”。
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Spring&Spring Boot 常用注解总结!
|
||||
title: Spring&SpringBoot常用注解总结
|
||||
category: 框架
|
||||
tag:
|
||||
- SpringBoot
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Spring常见问题总结
|
||||
title: Spring常见面试题总结
|
||||
category: 框架
|
||||
tag:
|
||||
- Spring
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: SpringBoot 常见问题总结
|
||||
title: SpringBoot常见面试题总结
|
||||
category: 框架
|
||||
tag:
|
||||
- Spring
|
||||
|
@ -1,621 +0,0 @@
|
||||
# Spring Cloud 入门
|
||||
|
||||
> 本文基于 Spring Cloud Netflix 。Spring Cloud Alibaba 也是非常不错的选择哦!
|
||||
>
|
||||
> 授权转载自:https://juejin.im/post/5de2553e5188256e885f4fa3
|
||||
|
||||
首先我给大家看一张图,如果大家对这张图有些地方不太理解的话,我希望你们看完我这篇文章会恍然大悟。
|
||||
|
||||

|
||||
|
||||
## 什么是 Spring Cloud
|
||||
|
||||
> 构建分布式系统不仅复杂而且容易出错。Spring Cloud 为最常见的分布式系统模式提供了一种简单且易于接受的编程模型,帮助开发人员构建有弹性的、可靠的、协调的应用程序。Spring Cloud 构建于 Spring Boot 之上,使得开发者很容易入手并快速应用于生产中。
|
||||
|
||||
官方果然官方,介绍都这么有板有眼的。
|
||||
|
||||
我所理解的 `Spring Cloud` 就是微服务系统架构的一站式解决方案,在平时我们构建微服务的过程中需要做如 **服务发现注册** 、**配置中心** 、**消息总线** 、**负载均衡** 、**断路器** 、**数据监控** 等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。
|
||||
|
||||
## Spring Cloud 的版本
|
||||
|
||||
当然这个只是个题外话。
|
||||
|
||||
`Spring Cloud` 的版本号并不是我们通常见的数字版本号,而是一些很奇怪的单词。这些单词均为英国伦敦地铁站的站名。同时根据字母表的顺序来对应版本时间顺序,比如:最早 的 `Release` 版本 `Angel`,第二个 `Release` 版本 `Brixton`(英国地名),然后是 `Camden`、 `Dalston`、`Edgware`、`Finchley`、`Greenwich`、`Hoxton`。
|
||||
|
||||
## Spring Cloud 的服务发现框架——Eureka
|
||||
|
||||
> `Eureka`是基于`REST`(代表性状态转移)的服务,主要在 `AWS` 云中用于定位服务,以实现负载均衡和中间层服务器的故障转移。我们称此服务为`Eureka`服务器。Eureka 还带有一个基于 `Java` 的客户端组件 `Eureka Client`,它使与服务的交互变得更加容易。客户端还具有一个内置的负载平衡器,可以执行基本的循环负载平衡。在 `Netflix`,更复杂的负载均衡器将 `Eureka` 包装起来,以基于流量,资源使用,错误条件等多种因素提供加权负载均衡,以提供出色的弹性。
|
||||
|
||||
总的来说,`Eureka` 就是一个服务发现框架。何为服务,何又为发现呢?
|
||||
|
||||
举一个生活中的例子,就比如我们平时租房子找中介的事情。
|
||||
|
||||
在没有中介的时候我们需要一个一个去寻找是否有房屋要出租的房东,这显然会非常的费力,一你找凭一个人的能力是找不到很多房源供你选择,再者你也懒得这么找下去(找了这么久,没有合适的只能将就)。**这里的我们就相当于微服务中的 `Consumer` ,而那些房东就相当于微服务中的 `Provider` 。消费者 `Consumer` 需要调用提供者 `Provider` 提供的一些服务,就像我们现在需要租他们的房子一样。**
|
||||
|
||||
但是如果只是租客和房东之间进行寻找的话,他们的效率是很低的,房东找不到租客赚不到钱,租客找不到房东住不了房。所以,后来房东肯定就想到了广播自己的房源信息(比如在街边贴贴小广告),这样对于房东来说已经完成他的任务(将房源公布出去),但是有两个问题就出现了。第一、其他不是租客的都能收到这种租房消息,这在现实世界没什么,但是在计算机的世界中就会出现 **资源消耗** 的问题了。第二、租客这样还是很难找到你,试想一下我需要租房,我还需要东一个西一个地去找街边小广告,麻不麻烦?
|
||||
|
||||
那怎么办呢?我们当然不会那么傻乎乎的,第一时间就是去找 **中介** 呀,它为我们提供了统一房源的地方,我们消费者只需要跑到它那里去找就行了。而对于房东来说,他们也只需要把房源在中介那里发布就行了。
|
||||
|
||||

|
||||
|
||||
那么现在,我们的模式就是这样的了。
|
||||
|
||||

|
||||
|
||||
但是,这个时候还会出现一些问题。
|
||||
|
||||
1. 房东注册之后如果不想卖房子了怎么办?我们是不是需要让房东 **定期续约** ?如果房东不进行续约是不是要将他们从中介那里的注册列表中 **移除** 。
|
||||
2. 租客是不是也要进行 **注册** 呢?不然合同乙方怎么来呢?
|
||||
3. 中介可不可以做 **连锁店** 呢?如果这一个店因为某些不可抗力因素而无法使用,那么我们是否可以换一个连锁店呢?
|
||||
|
||||
针对上面的问题我们来重新构建一下上面的模式图
|
||||
|
||||

|
||||
|
||||
好了,举完这个:chestnut:我们就可以来看关于 `Eureka` 的一些基础概念了,你会发现这东西理解起来怎么这么简单。:punch::punch::punch:
|
||||
|
||||
**服务发现**:其实就是一个“中介”,整个过程中有三个角色:**服务提供者(出租房子的)、服务消费者(租客)、服务中介(房屋中介)**。
|
||||
|
||||
**服务提供者**: 就是提供一些自己能够执行的一些服务给外界。
|
||||
|
||||
**服务消费者**: 就是需要使用一些服务的“用户”。
|
||||
|
||||
**服务中介**: 其实就是服务提供者和服务消费者之间的“桥梁”,服务提供者可以把自己注册到服务中介那里,而服务消费者如需要消费一些服务(使用一些功能)就可以在服务中介中寻找注册在服务中介的服务提供者。
|
||||
|
||||
**服务注册 Register**:
|
||||
|
||||
官方解释:当 `Eureka` 客户端向 `Eureka Server` 注册时,它提供自身的**元数据**,比如 IP 地址、端口,运行状况指示符 URL,主页等。
|
||||
|
||||
结合中介理解:房东 (提供者 `Eureka Client Provider`)在中介 (服务器 `Eureka Server`) 那里登记房屋的信息,比如面积,价格,地段等等(元数据 `metaData`)。
|
||||
|
||||
**服务续约 Renew**:
|
||||
|
||||
官方解释:**`Eureka` 客户会每隔 30 秒(默认情况下)发送一次心跳来续约**。 通过续约来告知 `Eureka Server` 该 `Eureka` 客户仍然存在,没有出现问题。 正常情况下,如果 `Eureka Server` 在 90 秒没有收到 `Eureka` 客户的续约,它会将实例从其注册表中删除。
|
||||
|
||||
结合中介理解:房东 (提供者 `Eureka Client Provider`) 定期告诉中介 (服务器 `Eureka Server`) 我的房子还租(续约) ,中介 (服务器`Eureka Server`) 收到之后继续保留房屋的信息。
|
||||
|
||||
**获取注册列表信息 Fetch Registries**:
|
||||
|
||||
官方解释:`Eureka` 客户端从服务器获取注册表信息,并将其缓存在本地。客户端会使用该信息查找其他服务,从而进行远程调用。该注册列表信息定期(每 30 秒钟)更新一次。每次返回注册列表信息可能与 `Eureka` 客户端的缓存信息不同, `Eureka` 客户端自动处理。如果由于某种原因导致注册列表信息不能及时匹配,`Eureka` 客户端则会重新获取整个注册表信息。 `Eureka` 服务器缓存注册列表信息,整个注册表以及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。`Eureka` 客户端和 `Eureka` 服务器可以使用 JSON / XML 格式进行通讯。在默认的情况下 `Eureka` 客户端使用压缩 `JSON` 格式来获取注册列表的信息。
|
||||
|
||||
结合中介理解:租客(消费者 `Eureka Client Consumer`) 去中介 (服务器 `Eureka Server`) 那里获取所有的房屋信息列表 (客户端列表 `Eureka Client List`) ,而且租客为了获取最新的信息会定期向中介 (服务器 `Eureka Server`) 那里获取并更新本地列表。
|
||||
|
||||
**服务下线 Cancel**:
|
||||
|
||||
官方解释:Eureka 客户端在程序关闭时向 Eureka 服务器发送取消请求。 发送请求后,该客户端实例信息将从服务器的实例注册表中删除。该下线请求不会自动完成,它需要调用以下内容:`DiscoveryManager.getInstance().shutdownComponent();`
|
||||
|
||||
结合中介理解:房东 (提供者 `Eureka Client Provider`) 告诉中介 (服务器 `Eureka Server`) 我的房子不租了,中介之后就将注册的房屋信息从列表中剔除。
|
||||
|
||||
**服务剔除 Eviction**:
|
||||
|
||||
官方解释:在默认的情况下,**当 Eureka 客户端连续 90 秒(3 个续约周期)没有向 Eureka 服务器发送服务续约,即心跳,Eureka 服务器会将该服务实例从服务注册列表删除**,即服务剔除。
|
||||
|
||||
结合中介理解:房东(提供者 `Eureka Client Provider`) 会定期联系 中介 (服务器 `Eureka Server`) 告诉他我的房子还租(续约),如果中介 (服务器 `Eureka Server`) 长时间没收到提供者的信息,那么中介会将他的房屋信息给下架(服务剔除)。
|
||||
|
||||
下面就是 `Netflix` 官方给出的 `Eureka` 架构图,你会发现和我们前面画的中介图别无二致。
|
||||
|
||||

|
||||
|
||||
当然,可以充当服务发现的组件有很多:`Zookeeper` ,`Consul` , `Eureka` 等。
|
||||
|
||||
更多关于 `Eureka` 的知识(自我保护,初始注册策略等等)可以自己去官网查看,或者查看我的另一篇文章 [深入理解 Eureka](https://juejin.im/post/5dd497e3f265da0ba7718018)。
|
||||
|
||||
## 负载均衡之 Ribbon
|
||||
|
||||
### 什么是 `RestTemplate`?
|
||||
|
||||
不是讲 `Ribbon` 么?怎么扯到了 `RestTemplate` 了?你先别急,听我慢慢道来。
|
||||
|
||||
我不听我不听我不听:hear_no_evil::hear_no_evil::hear_no_evil:。
|
||||
|
||||
我就说一句!**`RestTemplate`是`Spring`提供的一个访问 Http 服务的客户端类**,怎么说呢?就是微服务之间的调用是使用的 `RestTemplate` 。比如这个时候我们 消费者 B 需要调用 提供者 A 所提供的服务我们就需要这么写。如我下面的伪代码。
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
// 这里是提供者A的ip地址,但是如果使用了 Eureka 那么就应该是提供者A的名称
|
||||
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";
|
||||
|
||||
@PostMapping("/judge")
|
||||
public boolean judge(@RequestBody Request request) {
|
||||
String url = SERVICE_PROVIDER_A + "/service1";
|
||||
return restTemplate.postForObject(url, request, Boolean.class);
|
||||
}
|
||||
```
|
||||
|
||||
如果你对源码感兴趣的话,你会发现上面我们所讲的 `Eureka` 框架中的 **注册**、**续约** 等,底层都是使用的 `RestTemplate` 。
|
||||
|
||||
### 为什么需要 Ribbon?
|
||||
|
||||
`Ribbon` 是 `Netflix` 公司的一个开源的负载均衡 项目,是一个客户端/进程内负载均衡器,**运行在消费者端**。
|
||||
|
||||
我们再举个:chestnut:,比如我们设计了一个秒杀系统,但是为了整个系统的 **高可用** ,我们需要将这个系统做一个集群,而这个时候我们消费者就可以拥有多个秒杀系统的调用途径了,如下图。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/秒杀系统-ribbon.jpg" style="zoom:50%;" />
|
||||
|
||||
如果这个时候我们没有进行一些 **均衡操作** ,如果我们对 `秒杀系统1` 进行大量的调用,而另外两个基本不请求,就会导致 `秒杀系统1` 崩溃,而另外两个就变成了傀儡,那么我们为什么还要做集群,我们高可用体现的意义又在哪呢?
|
||||
|
||||
所以 `Ribbon` 出现了,注意我们上面加粗的几个字——**运行在消费者端**。指的是,`Ribbon` 是运行在消费者端的负载均衡器,如下图。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/秒杀系统-ribbon2.jpg" style="zoom:50%;" />
|
||||
|
||||
其工作原理就是 `Consumer` 端获取到了所有的服务列表之后,在其**内部**使用**负载均衡算法**,进行对多个系统的调用。
|
||||
|
||||
### Nginx 和 Ribbon 的对比
|
||||
|
||||
提到 **负载均衡** 就不得不提到大名鼎鼎的 `Nignx` 了,而和 `Ribbon` 不同的是,它是一种**集中式**的负载均衡器。
|
||||
|
||||
何为集中式呢?简单理解就是 **将所有请求都集中起来,然后再进行负载均衡**。如下图。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/nginx-vs-ribbon1.jpg" style="zoom:50%;" />
|
||||
|
||||
我们可以看到 `Nginx` 是接收了所有的请求进行负载均衡的,而对于 `Ribbon` 来说它是在消费者端进行的负载均衡。如下图。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/nginx-vs-ribbon2.jpg" style="zoom:50%;" />
|
||||
|
||||
> 请注意 `Request` 的位置,在 `Nginx` 中请求是先进入负载均衡器,而在 `Ribbon` 中是先在客户端进行负载均衡才进行请求的。
|
||||
|
||||
### Ribbon 的几种负载均衡算法
|
||||
|
||||
负载均衡,不管 `Nginx` 还是 `Ribbon` 都需要其算法的支持,如果我没记错的话 `Nginx` 使用的是 轮询和加权轮询算法。而在 `Ribbon` 中有更多的负载均衡调度算法,其默认是使用的 `RoundRobinRule` 轮询策略。
|
||||
|
||||
- **`RoundRobinRule`**:轮询策略。`Ribbon` 默认采用的策略。若经过一轮轮询没有找到可用的 `provider`,其最多轮询 10 轮。若最终还没有找到,则返回 `null`。
|
||||
- **`RandomRule`**: 随机策略,从所有可用的 `provider` 中随机选择一个。
|
||||
- **`RetryRule`**: 重试策略。先按照 `RoundRobinRule` 策略获取 `provider`,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
|
||||
|
||||
🐦🐦🐦 还有很多,这里不一一举:chestnut:了,你最需要知道的是默认轮询算法,并且可以更换默认的负载均衡算法,只需要在配置文件中做出修改就行。
|
||||
|
||||
```yaml
|
||||
providerName:
|
||||
ribbon:
|
||||
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
|
||||
```
|
||||
|
||||
当然,在 `Ribbon` 中你还可以**自定义负载均衡算法**,你只需要实现 `IRule` 接口,然后修改配置文件或者自定义 `Java Config` 类。
|
||||
|
||||
## 什么是 Open Feign
|
||||
|
||||
有了 `Eureka` ,`RestTemplate` ,`Ribbon`, 我们就可以愉快地进行服务间的调用了,但是使用 `RestTemplate` 还是不方便,我们每次都要进行这样的调用。
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
// 这里是提供者A的ip地址,但是如果使用了 Eureka 那么就应该是提供者A的名称
|
||||
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";
|
||||
|
||||
@PostMapping("/judge")
|
||||
public boolean judge(@RequestBody Request request) {
|
||||
String url = SERVICE_PROVIDER_A + "/service1";
|
||||
// 是不是太麻烦了???每次都要 url、请求、返回类型的
|
||||
return restTemplate.postForObject(url, request, Boolean.class);
|
||||
}
|
||||
```
|
||||
|
||||
这样每次都调用 `RestRemplate` 的 `API` 是否太麻烦,我能不能像**调用原来代码一样进行各个服务间的调用呢?**
|
||||
|
||||
:bulb::bulb::bulb:聪明的小朋友肯定想到了,那就用 **映射** 呀,就像域名和 IP 地址的映射。我们可以将被调用的服务代码映射到消费者端,这样我们就可以 **“无缝开发” **啦。
|
||||
|
||||
> `OpenFeign` 也是运行在消费者端的,使用 `Ribbon` 进行负载均衡,所以 `OpenFeign` 直接内置了 `Ribbon`。
|
||||
|
||||
在导入了 `Open Feign` 之后我们就可以进行愉快编写 `Consumer` 端代码了。
|
||||
|
||||
```java
|
||||
// 使用 @FeignClient 注解来指定提供者的名字
|
||||
@FeignClient(value = "eureka-client-provider")
|
||||
public interface TestClient {
|
||||
// 这里一定要注意需要使用的是提供者那端的请求相对路径,这里就相当于映射了
|
||||
@RequestMapping(value = "/provider/xxx",
|
||||
method = RequestMethod.POST)
|
||||
CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);
|
||||
}
|
||||
```
|
||||
|
||||
然后我们在 `Controller` 就可以像原来调用 `Service` 层代码一样调用它了。
|
||||
|
||||
```java
|
||||
@RestController
|
||||
public class TestController {
|
||||
// 这里就相当于原来自动注入的 Service
|
||||
@Autowired
|
||||
private TestClient testClient;
|
||||
// controller 调用 service 层代码
|
||||
@RequestMapping(value = "/test", method = RequestMethod.POST)
|
||||
public CommonResponse<List<Plan>> get(@RequestBody planGetRequest request) {
|
||||
return testClient.getPlans(request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 必不可少的 Hystrix
|
||||
|
||||
### 什么是 Hystrix 之熔断和降级
|
||||
|
||||
> 在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix 是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
|
||||
|
||||
总体来说 `Hystrix` 就是一个能进行 **熔断** 和 **降级** 的库,通过使用它能提高整个系统的弹性。
|
||||
|
||||
那么什么是 熔断和降级 呢?再举个:chestnut:,此时我们整个微服务系统是这样的。服务 A 调用了服务 B,服务 B 再调用了服务 C,但是因为某些原因,服务 C 顶不住了,这个时候大量请求会在服务 C 阻塞。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/Hystrix1.jpg" style="zoom:50%;" />
|
||||
|
||||
服务 C 阻塞了还好,毕竟只是一个系统崩溃了。但是请注意这个时候因为服务 C 不能返回响应,那么服务 B 调用服务 C 的的请求就会阻塞,同理服务 B 阻塞了,那么服务 A 也会阻塞崩溃。
|
||||
|
||||
> 请注意,为什么阻塞会崩溃。因为这些请求会消耗占用系统的线程、IO 等资源,消耗完你这个系统服务器不就崩了么。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/Hystrix2.jpg" style="zoom:50%;" />
|
||||
|
||||
这就叫 **服务雪崩**。妈耶,上面两个 **熔断** 和 **降级** 你都没给我解释清楚,你现在又给我扯什么 **服务雪崩** ?:tired_face::tired_face::tired_face:
|
||||
|
||||
别急,听我慢慢道来。
|
||||
|
||||

|
||||
|
||||
不听我也得讲下去!
|
||||
|
||||
所谓 **熔断** 就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 **断路器** 直接将此请求链路断开。
|
||||
|
||||
也就是我们上面服务 B 调用服务 C 在指定时间窗内,调用的失败率到达了一定的值,那么 `Hystrix` 则会自动将 服务 B 与 C 之间的请求都断了,以免导致服务雪崩现象。
|
||||
|
||||
其实这里所讲的 **熔断** 就是指的 `Hystrix` 中的 **断路器模式** ,你可以使用简单的 `@HystrixCommand` 注解来标注某个方法,这样 `Hystrix` 就会使用 **断路器** 来“包装”这个方法,每当调用时间超过指定时间时(默认为 1000ms),断路器将会中断对这个方法的调用。
|
||||
|
||||
当然你可以对这个注解的很多属性进行设置,比如设置超时时间,像这样。
|
||||
|
||||
```java
|
||||
@HystrixCommand(
|
||||
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
|
||||
)
|
||||
public List<Xxx> getXxxx() {
|
||||
// ...省略代码逻辑
|
||||
}
|
||||
```
|
||||
|
||||
但是,我查阅了一些博客,发现他们都将 **熔断** 和 **降级** 的概念混淆了,以我的理解,**降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复**。这也就对应着 `Hystrix` 的 **后备处理** 模式。你可以通过设置 `fallbackMethod` 来给一个方法设置备用的代码逻辑。比如这个时候有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过 id 去查询新闻的详情,但是因为这条新闻太火了(比如最近什么\*易对吧),大量用户同时访问可能会导致系统崩溃,那么我们就进行 **服务降级** ,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。
|
||||
|
||||
```java
|
||||
// 指定了后备方法调用
|
||||
@HystrixCommand(fallbackMethod = "getHystrixNews")
|
||||
@GetMapping("/get/news")
|
||||
public News getNews(@PathVariable("id") int id) {
|
||||
// 调用新闻系统的获取新闻api 代码逻辑省略
|
||||
}
|
||||
//
|
||||
public News getHystrixNews(@PathVariable("id") int id) {
|
||||
// 做服务降级
|
||||
// 返回当前人数太多,请稍后查看
|
||||
}
|
||||
```
|
||||
|
||||
### 什么是 Hystrix 之其他
|
||||
|
||||
我在阅读 《Spring 微服务实战》这本书的时候还接触到了一个 **舱壁模式** 的概念。在不使用舱壁模式的情况下,服务 A 调用服务 B,这种调用默认的是 **使用同一批线程来执行** 的,而在一个服务出现性能问题的时候,就会出现所有线程被刷爆并等待处理工作,同时阻塞新请求,最终导致程序崩溃。而舱壁模式会将远程资源调用隔离在他们自己的线程池中,以便可以控制单个表现不佳的服务,而不会使该程序崩溃。
|
||||
|
||||
具体其原理我推荐大家自己去了解一下,本篇文章中对 **舱壁模式** 不做过多解释。当然还有 **`Hystrix` 仪表盘**,它是**用来实时监控 `Hystrix` 的各项指标信息的**,这里我将这个问题也抛出去,希望有不了解的可以自己去搜索一下。
|
||||
|
||||
## 微服务网关——Zuul
|
||||
|
||||
> ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups(亚马逊自动缩放组,亚马逊的一种云计算方式) 的能力
|
||||
|
||||
在上面我们学习了 `Eureka` 之后我们知道了 _服务提供者_ 是 _消费者_ 通过 `Eureka Server` 进行访问的,即 `Eureka Server` 是 _服务提供者_ 的统一入口。那么整个应用中存在那么多 _消费者_ 需要用户进行调用,这个时候用户该怎样访问这些 _消费者工程_ 呢?当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而 Zuul 就是这样的一个对于 _消费者_ 的统一入口。
|
||||
|
||||
> 如果学过前端的肯定都知道 Router 吧,比如 Flutter 中的路由,Vue,React 中的路由,用了 Zuul 你会发现在路由功能方面和前端配置路由基本是一个理。:smile: 我偶尔撸撸 Flutter。
|
||||
|
||||
大家对网关应该很熟吧,简单来讲网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行**鉴权**、**限流**、 **路由**、**监控**等功能。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/zuul-sj22o93nfdsjkdsf.jpg" style="zoom:50%;" />
|
||||
|
||||
没错,网关有的功能,`Zuul` 基本都有。而 `Zuul` 中最关键的就是 **路由和过滤器** 了,在官方文档中 `Zuul` 的标题就是
|
||||
|
||||
> Router and Filter : Zuul
|
||||
|
||||
### Zuul 的路由功能
|
||||
|
||||
#### 简单配置
|
||||
|
||||
本来想给你们复制一些代码,但是想了想,因为各个代码配置比较零散,看起来也比较零散,我决定还是给你们画个图来解释吧。
|
||||
|
||||
> 请不要因为我这么好就给我点赞 :thumbsup: 。 疯狂暗示。
|
||||
|
||||
比如这个时候我们已经向 `Eureka Server` 注册了两个 `Consumer` 、三个 `Provicer` ,这个时候我们再加个 `Zuul` 网关应该变成这样子了。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/zuul-sj22o93nfdsjkdsf2312.jpg" style="zoom:50%;" />
|
||||
|
||||
emmm,信息量有点大,我来解释一下。关于前面的知识我就不解释了:neutral_face:。
|
||||
|
||||
首先,`Zuul` 需要向 `Eureka` 进行注册,注册有啥好处呢?
|
||||
|
||||
你傻呀,`Consumer` 都向 `Eureka Server` 进行注册了,我网关是不是只要注册就能拿到所有 `Consumer` 的信息了?
|
||||
|
||||
拿到信息有什么好处呢?
|
||||
|
||||
我拿到信息我是不是可以获取所有的 `Consumer` 的元数据(名称,ip,端口)?
|
||||
|
||||
拿到这些元数据有什么好处呢?拿到了我们是不是直接可以做**路由映射**?比如原来用户调用 `Consumer1` 的接口 `localhost:8001/studentInfo/update` 这个请求,我们是不是可以这样进行调用了呢?`localhost:9000/consumer1/studentInfo/update` 呢?你这样是不是恍然大悟了?
|
||||
|
||||
> 这里的 url 为了让更多人看懂所以没有使用 restful 风格。
|
||||
|
||||
上面的你理解了,那么就能理解关于 `Zuul` 最基本的配置了,看下面。
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 9000
|
||||
eureka:
|
||||
client:
|
||||
service-url:
|
||||
# 这里只要注册 Eureka 就行了
|
||||
defaultZone: http://localhost:9997/eureka
|
||||
```
|
||||
|
||||
然后在启动类上加入 `@EnableZuulProxy` 注解就行了。没错,就是那么简单:smiley:。
|
||||
|
||||
#### 统一前缀
|
||||
|
||||
这个很简单,就是我们可以在前面加一个统一的前缀,比如我们刚刚调用的是 `localhost:9000/consumer1/studentInfo/update`,这个时候我们在 `yaml` 配置文件中添加如下。
|
||||
|
||||
```yaml
|
||||
zuul:
|
||||
prefix: /zuul
|
||||
```
|
||||
|
||||
这样我们就需要通过 `localhost:9000/zuul/consumer1/studentInfo/update` 来进行访问了。
|
||||
|
||||
#### 路由策略配置
|
||||
|
||||
你会发现前面的访问方式(直接使用服务名),需要将微服务名称暴露给用户,会存在安全性问题。所以,可以自定义路径来替代微服务名称,即自定义路由策略。
|
||||
|
||||
```yaml
|
||||
zuul:
|
||||
routes:
|
||||
consumer1: /FrancisQ1/**
|
||||
consumer2: /FrancisQ2/**
|
||||
```
|
||||
|
||||
这个时候你就可以使用 ``localhost:9000/zuul/FrancisQ1/studentInfo/update` 进行访问了。
|
||||
|
||||
#### 服务名屏蔽
|
||||
|
||||
这个时候你别以为你好了,你可以试试,在你配置完路由策略之后使用微服务名称还是可以访问的,这个时候你需要将服务名屏蔽。
|
||||
|
||||
```yaml
|
||||
zuul:
|
||||
ignore-services: "*"
|
||||
```
|
||||
|
||||
#### 路径屏蔽
|
||||
|
||||
`Zuul` 还可以指定屏蔽掉的路径 URI,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。
|
||||
|
||||
```yaml
|
||||
zuul:
|
||||
ignore-patterns: **/auto/**
|
||||
```
|
||||
|
||||
这样关于 auto 的请求我们就可以过滤掉了。
|
||||
|
||||
> \*\* 代表匹配多级任意路径
|
||||
>
|
||||
> \*代表匹配一级任意路径
|
||||
|
||||
#### 敏感请求头屏蔽
|
||||
|
||||
默认情况下,像 `Cookie`、`Set-Cookie` 等敏感请求头信息会被 `zuul` 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。
|
||||
|
||||
### Zuul 的过滤功能
|
||||
|
||||
如果说,路由功能是 `Zuul` 的基操的话,那么**过滤器**就是 `Zuul`的利器了。毕竟所有请求都经过网关(Zuul),那么我们可以进行各种过滤,这样我们就能实现 **限流**,**灰度发布**,**权限控制** 等等。
|
||||
|
||||
#### 简单实现一个请求时间日志打印
|
||||
|
||||
要实现自己定义的 `Filter` 我们只需要继承 `ZuulFilter` 然后将这个过滤器类以 `@Component` 注解加入 Spring 容器中就行了。
|
||||
|
||||
在给你们看代码之前我先给你们解释一下关于过滤器的一些注意点。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/zuul-sj22o93nfdsjkdsf2312244.jpg" style="zoom:50%;" />
|
||||
|
||||
过滤器类型:`Pre`、`Routing`、`Post`。前置`Pre`就是在请求之前进行过滤,`Routing`路由过滤器就是我们上面所讲的路由策略,而`Post`后置过滤器就是在 `Response` 之前进行过滤的过滤器。你可以观察上图结合着理解,并且下面我会给出相应的注释。
|
||||
|
||||
```java
|
||||
// 加入Spring容器
|
||||
@Component
|
||||
public class PreRequestFilter extends ZuulFilter {
|
||||
// 返回过滤器类型 这里是前置过滤器
|
||||
@Override
|
||||
public String filterType() {
|
||||
return FilterConstants.PRE_TYPE;
|
||||
}
|
||||
// 指定过滤顺序 越小越先执行,这里第一个执行
|
||||
// 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行
|
||||
// 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return 0;
|
||||
}
|
||||
// 什么时候该进行过滤
|
||||
// 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
// 如果过滤器允许通过则怎么进行处理
|
||||
@Override
|
||||
public Object run() throws ZuulException {
|
||||
// 这里我设置了全局的RequestContext并记录了请求开始时间
|
||||
RequestContext ctx = RequestContext.getCurrentContext();
|
||||
ctx.set("startTime", System.currentTimeMillis());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// lombok的日志
|
||||
@Slf4j
|
||||
// 加入 Spring 容器
|
||||
@Component
|
||||
public class AccessLogFilter extends ZuulFilter {
|
||||
// 指定该过滤器的过滤类型
|
||||
// 此时是后置过滤器
|
||||
@Override
|
||||
public String filterType() {
|
||||
return FilterConstants.POST_TYPE;
|
||||
}
|
||||
// SEND_RESPONSE_FILTER_ORDER 是最后一个过滤器
|
||||
// 我们此过滤器在它之前执行
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
|
||||
}
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
return true;
|
||||
}
|
||||
// 过滤时执行的策略
|
||||
@Override
|
||||
public Object run() throws ZuulException {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
HttpServletRequest request = context.getRequest();
|
||||
// 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔
|
||||
Long startTime = (Long) context.get("startTime");
|
||||
// 这里我可以获取HttpServletRequest来获取URI并且打印出来
|
||||
String uri = request.getRequestURI();
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("uri: " + uri + ", duration: " + duration / 100 + "ms");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面就简单实现了请求时间日志打印功能,你有没有感受到 `Zuul` 过滤功能的强大了呢?
|
||||
|
||||
没有?好的、那我们再来。
|
||||
|
||||
#### 令牌桶限流
|
||||
|
||||
当然不仅仅是令牌桶限流方式,`Zuul` 只要是限流的活它都能干,这里我只是简单举个:chestnut:。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/zuui-令牌桶限流.jpg" alt="令牌桶限流" style="zoom:50%;" />
|
||||
|
||||
我先来解释一下什么是 **令牌桶限流** 吧。
|
||||
|
||||
首先我们会有个桶,如果里面没有满那么就会以一定 **固定的速率** 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。很简单吧,啊哈哈、
|
||||
|
||||
下面我们就通过 `Zuul` 的前置过滤器来实现一下令牌桶限流。
|
||||
|
||||
```java
|
||||
package com.lgq.zuul.filter;
|
||||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
import com.netflix.zuul.ZuulFilter;
|
||||
import com.netflix.zuul.context.RequestContext;
|
||||
import com.netflix.zuul.exception.ZuulException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RouteFilter extends ZuulFilter {
|
||||
// 定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求
|
||||
private static final RateLimiter RATE_LIMITER = RateLimiter.create(2);
|
||||
@Override
|
||||
public String filterType() {
|
||||
return FilterConstants.PRE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int filterOrder() {
|
||||
return -5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object run() throws ZuulException {
|
||||
log.info("放行");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldFilter() {
|
||||
RequestContext context = RequestContext.getCurrentContext();
|
||||
if(!RATE_LIMITER.tryAcquire()) {
|
||||
log.warn("访问量超载");
|
||||
// 指定当前请求未通过过滤
|
||||
context.setSendZuulResponse(false);
|
||||
// 向客户端返回响应码429,请求数量过多
|
||||
context.setResponseStatusCode(429);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样我们就能将请求数量控制在一秒两个,有没有觉得很酷?
|
||||
|
||||
### 关于 Zuul 的其他
|
||||
|
||||
`Zuul` 的过滤器的功能肯定不止上面我所实现的两种,它还可以实现 **权限校验**,包括我上面提到的 **灰度发布** 等等。
|
||||
|
||||
当然,`Zuul` 作为网关肯定也存在 **单点问题** ,如果我们要保证 `Zuul` 的高可用,我们就需要进行 `Zuul` 的集群配置,这个时候可以借助额外的一些负载均衡器比如 `Nginx` 。
|
||||
|
||||
## Spring Cloud 配置管理——Config
|
||||
|
||||
### 为什么要使用进行配置管理?
|
||||
|
||||
当我们的微服务系统开始慢慢地庞大起来,那么多 `Consumer` 、`Provider` 、`Eureka Server` 、`Zuul` 系统都会持有自己的配置,这个时候我们在项目运行的时候可能需要更改某些应用的配置,如果我们不进行配置的统一管理,我们只能**去每个应用下一个一个寻找配置文件然后修改配置文件再重启应用**。
|
||||
|
||||
首先对于分布式系统而言我们就不应该去每个应用下去分别修改配置文件,再者对于重启应用来说,服务无法访问所以直接抛弃了可用性,这是我们更不愿见到的。
|
||||
|
||||
那么有没有一种方法**既能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件呢?**
|
||||
|
||||
那就是我今天所要介绍的 `Spring Cloud Config` 。
|
||||
|
||||
> 能进行配置管理的框架不止 `Spring Cloud Config` 一种,大家可以根据需求自己选择(`disconf`,阿波罗等等)。而且对于 `Config` 来说有些地方实现的不是那么尽人意。
|
||||
|
||||
### Config 是什么
|
||||
|
||||
> `Spring Cloud Config` 为分布式系统中的外部化配置提供服务器和客户端支持。使用 `Config` 服务器,可以在中心位置管理所有环境中应用程序的外部属性。
|
||||
|
||||
简单来说,`Spring Cloud Config` 就是能将各个 应用/系统/模块 的配置文件存放到 **统一的地方然后进行管理**(Git 或者 SVN)。
|
||||
|
||||
你想一下,我们的应用是不是只有启动的时候才会进行配置文件的加载,那么我们的 `Spring Cloud Config` 就暴露出一个接口给启动应用来获取它所想要的配置文件,应用获取到配置文件然后再进行它的初始化工作。就如下图。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/config-ksksks.jpg" style="zoom:50%;" />
|
||||
|
||||
当然这里你肯定还会有一个疑问,如果我在应用运行时去更改远程配置仓库(Git)中的对应配置文件,那么依赖于这个配置文件的已启动的应用会不会进行其相应配置的更改呢?
|
||||
|
||||
答案是不会的。
|
||||
|
||||
什么?那怎么进行动态修改配置文件呢?这不是出现了 **配置漂移** 吗?你个渣男:rage:,你又骗我!
|
||||
|
||||
别急嘛,你可以使用 `Webhooks` ,这是 `github` 提供的功能,它能确保远程库的配置文件更新后客户端中的配置信息也得到更新。
|
||||
|
||||
噢噢,这还差不多。我去查查怎么用。
|
||||
|
||||
慢着,听我说完,`Webhooks` 虽然能解决,但是你了解一下会发现它根本不适合用于生产环境,所以基本不会使用它的。
|
||||
|
||||

|
||||
|
||||
而一般我们会使用 `Bus` 消息总线 + `Spring Cloud Config` 进行配置的动态刷新。
|
||||
|
||||
## 引出 Spring Cloud Bus
|
||||
|
||||
> 用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(例如配置更改事件)。
|
||||
|
||||
你可以简单理解为 `Spring Cloud Bus` 的作用就是**管理和广播分布式系统中的消息**,也就是消息引擎系统中的广播模式。当然作为 **消息总线** 的 `Spring Cloud Bus` 可以做很多事而不仅仅是客户端的配置刷新功能。
|
||||
|
||||
而拥有了 `Spring Cloud Bus` 之后,我们只需要创建一个简单的请求,并且加上 `@ResfreshScope` 注解就能进行配置的动态修改了,下面我画了张图供你理解。
|
||||
|
||||
<img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/springcloud-bus-s213dsfsd.jpg" style="zoom:50%;" />
|
||||
|
||||
## 总结
|
||||
|
||||
这篇文章中我带大家初步了解了 `Spring Cloud` 的各个组件,他们有
|
||||
|
||||
- `Eureka` 服务发现框架
|
||||
- `Ribbon` 进程内负载均衡器
|
||||
- `Open Feign` 服务调用映射
|
||||
- `Hystrix` 服务降级熔断器
|
||||
- `Zuul` 微服务网关
|
||||
- `Config` 微服务统一配置中心
|
||||
- `Bus` 消息总线
|
||||
|
||||
如果你能这个时候能看懂文首那张图,也就说明了你已经对 `Spring Cloud` 微服务有了一定的架构认识。
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 认证授权基础
|
||||
title: 认证授权基础概念详解
|
||||
category: 系统设计
|
||||
tag:
|
||||
- 安全
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: JWT 基本概念详解
|
||||
title: JWT 基础概念详解
|
||||
category: 系统设计
|
||||
tag:
|
||||
- 安全
|
||||
|
@ -1,9 +1,9 @@
|
||||
---
|
||||
title: 系统设计常见面试总结
|
||||
title: 系统设计常见面试题总结
|
||||
category: Java面试指北
|
||||
---
|
||||
|
||||
**系统设计常见面试总结** 为我的[知识星球](https://www.yuque.com/docs/share/8a30ffb5-83f3-40f9-baf9-38de68b906dc)专属内容,已经整理到了[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)中。
|
||||
**系统设计** 相关的面试题为我的[知识星球](https://www.yuque.com/docs/share/8a30ffb5-83f3-40f9-baf9-38de68b906dc)专属内容,已经整理到了[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)中。
|
||||
|
||||
[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7) 的部分内容展示如下,你可以将其看作是 [JavaGuide](https://javaguide.cn/#/) 的补充完善,两者可以配合使用。
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user