开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

数据结构与算法(三)线性表的顺序存储结构

发表于 2021-11-23

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

首先,我们大概先了解下什么是线性表。

线性表:零个或多个数据元素的有限数列。

数据元素 1 对 1的关系,这种关系是位置关系。

线性表元素个数n(n>=0)定义为线性表的长度,当n=0时,称为空表。

然后我们了解一下数据类型和抽象数据类型

一.数据类型

先看看为什么会有不同的数据类型呢?很简单,很多东西不能一概而论,而是需要更精确的划分。计算机计算1+1并不需要多么大的空间,但是计算10000000000+1000000000就得需要有个比较大的空间来放。还有有时候会计算小数,小数的位数不一样,需要的空间也就不一样。数字1和字母a也需要区分啊,于是开发者就想出了“数据类型”这一招,用来描述不同的数据的集合。

我记得最早接触的数据类型就是int了。当初一个int a;就把我看得神魂颠倒,不知所以。像这种类型,就是一个基本的数据类型。以前总以为数据类型就是一个描述数据到底是什么玩意儿的东东,现在再去看,倒是有点儿浅了。数据类型学术点呢,是一个值的集合和定义在这个值集合的一组操作的总称。一种数据类型也可以看成是一种已经实现了的“数据结构”。

按“值”是否可分解,将其分为两类:

1.原子类型: 其值不可分解,通常由语言直接提供,像C++中的int,float,double等等。

2.结构类型: 其值可以分解为若干部分(分量),是程序员自定义的,比如结构体,类等等。

ps:对于什么是“原子”,经常会看到什么“原子操作”,“原子类型”,一般就是指不可再分的。


二.抽象的数据类型

抽象数据类型(abstract data type,ADT)只是一个数学模型以及定义在模型上的一组操作。通常是对数据的抽象,定义了数据的取值范围以及对数据操作的集合。

其实,数据类型和抽象数据类型可以看成一种概念。比如,各种计算机都拥有的整数类型就是一个抽象数据类型,尽管实现方法不同,但他们的数学特性相同。

抽象数据类型的特征是实现与操作分离,从而实现封装。

看到有人举出了“超级玛丽”例子,觉得写得很不错,如下:

就像“超级玛丽”这个经典的任天堂游戏,里面的游戏主角是马里奥,我们给他定义了基本操作,前进、后退、跳、打子弹等。这就是一个抽象数据类型,定义了一个数据对象、对象中各元素之间的关系及对数据元素的操作。至于,到底是哪些操作,这只能由设计者根据实际需要来定。像马里奥可能开始只能走和跳,后来发现应该增加一种打子弹的操作,再后来又有了按住打子弹键后前进就有跑的操作。这都是根据实际情况来定的。

事实上,抽象数据类型体现了程序设计中问题分解和信息隐藏的特征。它把问题分解为多个规模较小且容易处理的问题,然后把每个功能模块的实现为一个独立单元,通过一次或多次调用来实现整个问题。

最后进重点,线性表的顺序存储结构

u=3642763191,618991523&fm=214&gp=0.jpg

1:定义

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

2:顺序存储属性

我觉得,举一个比较恰当的列子就是强语法语言中的数组。一个萝卜一个坑,每个元素都有自己固定的位置。

(1):存储空间的起始位置

(2):线性表的最大存储容量

(3):线性表的当前长度

3:地址计算方法

这里需要注意一下,线性表是从1开始的。

我们的下标是从0开始的。

下边我们写一个线性表顺醋存储结构的小例子:我这里使用的是C#。

文末有实例,可下载。

首先定义一个线性表顺序存储结构的接口类:ILineTotal.cs

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
csharp复制代码/// <summary>
    /// 泛型接口
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    interface ILineTotal<T>
    {
        /// <summary>
        /// 是否为空
        /// </summary>
        /// <returns></returns>
        bool IsEmptyTableLine();
        /// <summary>
        /// 根据下标获取数据
        /// </summary>
        T GetTableData(int index);
        /// <summary>
        ///  在中间插入数据
        /// </summary>
        bool InsertTableData(T data, int Index);
        /// <summary>
        /// 是否是最大长度
        /// </summary>
        /// <returns>true/false</returns>
        bool IsMaxSize();
        /// <summary>
        /// 在末尾插入数据
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        bool InsertTableEndData(T data);
        /// <summary>
        ///
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        bool DeleteTableData(int index);
        /// <summary>
        /// 清空表
        /// </summary>
        /// <returns></returns>
        bool ClearTableData();
        /// <summary>
        /// 翻转数组
        /// </summary>
        /// <returns></returns>
        bool FlipTableLine();
        /// <summary>
        /// 返回元素索引,如果这个元素在线性表中
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        int ReturnDataIndex(T item);
    }

线性表顺序存储结构类:tableLine.cs

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
csharp复制代码/// <summary>
    /// 线性表顺序存储结构类(泛型类)
    /// </summary>
    class tableLine<T> :ILineTotal<T>
    {
        /// <summary>
        /// 定义一个数组
        /// </summary>
        public T[] TArray = null;
        /// <summary>
        /// 数组最大下标
        /// </summary>
        public int MaxSize = 0;
        /// <summary>
        /// 最后一个数据索引
        /// </summary>
        public int lastDataIndex = 0;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="lenght">线性表长度</param>
        public tableLine(int length)
        {
            MaxSize = length - 1;
            // 定义一个线性表
            TArray = new T[length];
        }
        /// <summary>
        /// 是否为空的线性表
        /// </summary>
        /// <returns></returns>
        public bool IsEmptyTableLine()
        {
            try
            {
                if (TArray.GetLength(0) > 0)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            catch (Exception)
            {
                Console.WriteLine("IsEmptyTableLine出错");
                return false;
            }
        }
        /// <summary>
        /// 根据下标获取数据
        /// </summary>
        public T GetTableData(int index)
        {
            T data = TArray[index];
            return data;
        }
        /// <summary>
        /// 在中间插入数据
        /// 一个萝卜一个坑,有人插队,其余数据下标后移
        /// </summary>
        /// <param name="data">要插入的数据</param>
        /// <param name="Index">插入的下标</param>
        public bool InsertTableData(T data, int Index)
        {
            try
            {
                // 如果数组超出最大长度
                if (IsMaxSize())
                {
                    Console.WriteLine("数组已满");
                    return false;
                }
                lastDataIndex++;
                // 现将这个索引本身及其之后的数据向后挪一位(最大下标加一)
                for (int i = lastDataIndex + 1; i > Index; i--)
                {
                    TArray[i] = TArray[i - 1];
                }
                // 插入
                TArray[Index] = data;
                return true;
            }
            catch (Exception)
            {
                Console.WriteLine("在中间插入数据出错");
                return false;
            }
        }
        /// <summary>
        /// 是否是最大长度
        /// </summary>
        /// <returns></returns>
        public bool IsMaxSize()
        {
            if (lastDataIndex >= MaxSize)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        /// <summary>
        /// 在末尾插入数据
        /// </summary>
        public bool InsertTableEndData(T data)
        {
            try
            {
                // 如果数组超出最大长度
                if (IsMaxSize())
                {
                    return false;
                }
                lastDataIndex++;
                TArray[lastDataIndex] = data;
                return true;
            }
            catch (Exception)
            {
                Console.WriteLine("在末尾插入数据出错");
                return false;
            }
           
        }
        /// <summary>
        /// 删除指定下标数据
        /// </summary>
        public bool DeleteTableData(int index)
        {
            // 数据长度
            int dataLength = TArray.Length;
            if (index > dataLength - 1)
            {
                Console.WriteLine("传入下标超出了数组范围");
                return false;
            }
            for (int i = index; i < lastDataIndex; i++)
            {
                TArray[i] = TArray[i + 1];
            }
            TArray[lastDataIndex] = default(T);
            lastDataIndex--;
            return true;
        }
        /// <summary>
        /// 清空表
        /// </summary>
        public bool ClearTableData()
        {
            try
            {
                if (TArray.Length <= 0)
                {
                    Console.WriteLine("数据表为空");
                }
                Array.Clear(TArray, 0, TArray.Length);
                return true;
            }
            catch (Exception)
            {
                Console.WriteLine("清空线性表出错");
                return false;
            }
           
        }
        /// <summary>
        /// 翻转线性表
        /// </summary>
        public bool FlipTableLine()
        {
            Array.Reverse(TArray);
            return true;
        }
        /// <summary>
        /// 返回元素索引,如果这个元素在线性表中
        /// </summary>
        public int ReturnDataIndex(T item)
        {
            int index = Array.IndexOf(TArray, item); // 这里的1就是你要查找的值
            return index;
        }
    }

客户端调用:Program.cs

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
arduino复制代码static void Main(string[] args)
        {
            // 最后一个数据的索引
            int lastDataIndex = 0;
            // 实例化一个线性表
            tableLine<int> tableLineObj = new tableLine<int>(10);
            lastDataIndex = tableLineObj.TArray.Length - 3;
            for (int i = 0; i < tableLineObj.TArray.Length - 2; i++)
            {
                tableLineObj.TArray[i] = i + 1;
                Console.WriteLine(tableLineObj.TArray[i]);
            }
            // 最后一个下标赋值
            tableLineObj.lastDataIndex = lastDataIndex;
            // 查看线性表是否为空
            bool isEmpty =  tableLineObj.IsEmptyTableLine();
            Console.WriteLine("线性表为空:"+ isEmpty);
            Console.WriteLine("=================================================================");
 
            int fiveData = tableLineObj.GetTableData(4);
            Console.WriteLine("第五个数为:"+ fiveData);
            Console.WriteLine("=================================================================");
 
            bool insertRes = tableLineObj.InsertTableData(100,4);
            Console.WriteLine("中间插入数据:"+ insertRes);
 
            for (int i = 0; i < tableLineObj.TArray.Length; i++)
            {
                Console.WriteLine(tableLineObj.TArray[i]);
            }//*/
            Console.WriteLine("=================================================================");
 
            bool insertEnd = tableLineObj.InsertTableEndData(50);
            Console.WriteLine("末尾追加插入数据:" + insertEnd);
            for (int i = 0; i < tableLineObj.TArray.Length; i++)
            {
                Console.WriteLine(tableLineObj.TArray[i]);
            }
            Console.WriteLine("=================================================================");
 
            bool deleteRes = tableLineObj.DeleteTableData(3);
            Console.WriteLine("删除指定下标数据:" + deleteRes);
            for (int i = 0; i < tableLineObj.TArray.Length; i++)
            {
                Console.WriteLine(tableLineObj.TArray[i]);
            }
            Console.WriteLine("=================================================================");
 
            bool flipRes = tableLineObj.FlipTableLine();
            Console.WriteLine("反转线性表:" + flipRes);
            for (int i = 0; i < tableLineObj.TArray.Length; i++)
            {
                Console.WriteLine(tableLineObj.TArray[i]);
            }
            Console.WriteLine("=================================================================");
 
            int dataIndex = tableLineObj.ReturnDataIndex(100);
            Console.WriteLine("100对应的下标:" + dataIndex);
            Console.WriteLine("=================================================================");
 
            /*bool clearRes = tableLineObj.ClearTableData();
            Console.WriteLine("清空线性表:" + clearRes);
            for (int i = 0; i < tableLineObj.TArray.Length; i++)
            {
                Console.WriteLine(tableLineObj.TArray[i]);
            }
Console.WriteLine("=================================================================");//*/
            Console.ReadKey();
        }

大概例子就是这样。

最后说下顺序存储结构的优缺点:

**优点:

1:无须为表中元素之间的逻辑关系而增加额外的存储空间。

2:可以快速的存取表中任一位置的元素。**

缺点:

1:插入和删除操作需要移动大量元素

2:当线性表长度变化较大时,难以确定存储空间的容量

3:造成存储空间的“碎片”

有好的建议,请在下方输入你的评论。

欢迎访问个人博客
guanchao.site

欢迎访问小程序:

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

操作系统综合实验——动态高优先权优先进程调度算法 导读 一、

发表于 2021-11-23

「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」

导读

PS:最近有很多小伙伴问我怎么准备算法竞赛和面试的问题,还有一些刚入门的新同学不知道怎么学习编程,肥学给大家准备了一些专栏有兴趣的可以去看看哦!

好了今天我们给大家准备了操纵系统的期末综合实验大家可以参考参考!源码可以在我的主页上找到。

一、实验目的

通过对进程调度算法的模拟,进一步理解进程的基本概念,加深对进程运行状态和进程调度过程、调度算法的理解。

二、设备与环境

  1. 硬件设备:PC机一台
  2. 软件环境:安装Windows操作系统,并安装相关的程序开发环境,如C \C++\Java 等编程语言环境。

三、实验内容

实验采用了java语言编程模拟N个进程采用动态高优先权优先进程调度算法。该算法就是按照优先权的大小运行进程,如果一个时间片内未运行完,则将优先权数减3后再插入到链表中按priority的顺序进行排序找到最大的priority作为下一个运行进程且在就绪队列里面的进程priority会加1。

主模块:

在这里插入图片描述

动态priority排序模块:
用来保证头部永远最大

在这里插入图片描述

到达时间进入就绪状态模块:

在这里插入图片描述

计算周转时间和带权周转时间:

在这里插入图片描述

四、实验结果及分析

输入的信息
初始权值都为100,needtime为还需要的时间
进程 到达时刻 服务时间

1
2
3
4
5
java复制代码A	0	3
B 2 6
C 4 4
D 6 5
E 8 2

在这里插入图片描述

在这里插入图片描述

以A为例:

在这里插入图片描述

最后结束时:

在这里插入图片描述

部分代码展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码//创建结构
class PCB{
String ID;
int priority=-1;
int cputime;//服务的时间
int needtime;//进程还需的时间
String state="wait";
int arrivetime;
PCB next;
public PCB(String ID,int priority,int cputime,int arrivetime){
this.ID=ID;
this.priority=priority;
this.cputime=cputime;
this.arrivetime=arrivetime;
}
}
1
2
3
4
5
java复制代码判断是否到达,进程进入时间
for(int i=0;i<arr.size();i++){
if(h==arr.get(i).arrivetime){
Dynamic_priority.sort(arr.get(i));
arr.remove(i);}}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码//将进程转为就绪态并排序
public static void sort(PCB pcb){
PCB tmp=null;
if(ready==null){//当头结点为空
ready=pcb;
tail=pcb;}
else {if(pcb.priority>ready.priority){//如果这个结点priority大于头priority
pcb.next=ready;
ready=pcb;}
else {
boolean m=false;
tmp=ready;//q
while (m==false){
if(tail.priority>=pcb.priority){//插入尾端
tail.next=pcb;
tail=pcb;
pcb.next=null;
m=true; }
else {
if(tmp.priority>=pcb.priority&&pcb.priority>tail.priority){//逐渐遍历插到tmp前
pcb.next=tmp.next;
tmp.next=pcb;
m=true;
} else { tmp=tmp.next;

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JVM 参数介绍

发表于 2021-11-23

这是我参与11月更文挑战的第19天,活动详情查看:2021最后一次更文挑战。

JVM 参数类型

JVM 参数类型大致分为以下几类:

  • 标准参数(-),即在 JVM 的各个版本中基本不变的,相对比较稳定的参数,向后兼容;
  • 非标准参数(-X),变化比较小的参数,默认 JVM 实现这些参数的功能,但是并不保证所有 JVM 实现都满足,且不保证向后兼容;
  • 非Stable参数(-XX),此类参数各个 JVM 实现会有所不同,将来可能会随时取消,需要慎重使用;

标准参数

通过命令 java 即可查看

  • -version:输出 java 的版本信息,比如 jdk 版本、vendor、model
  • -help:输出 java 标准参数列表及其描述
  • -showversion:输出 java 版本信息(与-version相同)之后,继续输出 java 的标准参数列表及其描述,相当于java -verion 和 java -help
  • -client:设置 jvm 使用 client 模式,特点是启动速度比较快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或者PC应用开发和调试
  • -server:设置 jvm 使 server 模式,特点是启动速度比较慢,但运行时性能和内存管理效率很高,适用于生产环境。在具有64位能力的 jdk 环境下将默认启用该模式,而忽略 -client 参数
  • -agentlib:libname[=options]:用于装载本地 lib 包。其中 libname 为本地代理库文件名,默认搜索路径为环境变量 PATH 中的路径,options 为传给本地库启动时的参数,多个参数之间用逗号分隔
  • -agentpath:pathname[=options]:按全路径装载本地库,不再搜索PATH中的路径;其他功能和 agentlib相同
  • -Dproperty=value 设置系统属性名/值对,运行在此jvm之上的应用程序可用System.getProperty(“property”)得到value的值。 如果value中有空格,则需要用双引号将该值括起来,如-Dname=”space string”。 该参数通常用于设置系统级全局变量值,如配置文件路径,以便该属性在程序中任何地方都可访问
  • -verbose:[class|gc|jni]:输出每个加载类|gc|jni 的信息

X 参数

非标准参数又称为扩展参数,通过命令 java -X 查看:

  • -Xint:设置 jvm 以解释模式运行,所有的字节码将被直接执行,而不会编译成本地码
  • -Xmixed:混合模式,JVM自己来决定是否编译成本地代码,默认使用的就是混合模式
  • -Xbatch:关闭后台代码编译,强制在前台编译,编译完成之后才能进行代码执行。 默认情况下,jvm 在后台进行编译,若没有编译完成,则前台运行代码时以解释模式运行
  • -Xbootclasspath:bootclasspath:让 jvm 从指定路径(可以是分号分隔的目录、jar、或者zip)中加载bootclass,用来替换 jdk 的 rt.jar;若非必要,一般不会用到
  • -Xbootclasspath/a:path :将指定路径的所有文件追加到默认 bootstrap 路径中
  • -Xfuture:让jvm对类文件执行严格的格式检查(默认 jvm 不进行严格格式检查),以符合类文件格式规范,推荐开发人员使用该参数。
  • -Xincgc:开启增量 gc(默认为关闭),这有助于减少长时间GC时应用程序出现的停顿,但由于可能和应用程序并发执行,所以会降低CPU对应用的处理能力
  • -Xloggc:file: 与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。若与 verbose 命令同时出现在命令行中,则以 -Xloggc 为准
  • -Xms:指定 jvm 堆的初始大小,默认为物理内存的1/64,最小为1M,可以指定单位,比如k、m,若不指定,则默认为字节
  • -Xmx:指定 jvm 堆的最大值,默认为物理内存的 1/4或者1G,最小为2M;单位与-Xms一致
  • -Xprof:跟踪正运行的程序,并将跟踪数据在标准输出输出;适合于开发环境调试
  • -Xss: 设置单个线程栈的大小,一般默认为 512k

xx 参数

  • -XX:+PrintFlagsInitial
+ 主要查看初始默认值
+ java -XX:+PrintFlagsInitial
+ java -XX:+PrintFlagsInitial -version
1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码       
C:\Users>java -XX:+PrintFlagsInitial
[Global flags]
int ActiveProcessorCount = -1 {product} {default}
uintx AdaptiveSizeDecrementScaleFactor = 4 {product} {default}
uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} {default}
uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} {default}
uintx AdaptiveSizePolicyInitializingSteps = 20 {product} {default}
uintx AdaptiveSizePolicyOutputInterval = 0 {product} {default}
uintx AdaptiveSizePolicyWeight = 10 {product} {default}
uintx AdaptiveSizeThroughPutPolicy = 0 {product} {default}
......

等号前有冒号 := 说明 jvm 参数有人为修改过或者 JVM加载修改

false 说明是Boolean 类型 参数,数字说明是 KV 类型参数

  • -XX:+PrintFlagsFinal
+ 主要查看修改更新
+ java -XX:+PrintFlagsFinal
+ java -XX:+PrintFlagsFinal -version
+ 运行java命令的同时打印出参数 java -XX:+PrintFlagsFinal -XX:MetaspaceSize=512m Hello.java
  • -XX:+PrintCommondLineFlags
+ 打印命令行参数
+ java -XX:+PrintCommandLineFlags -version
+ 可以方便的看到垃圾回收器

xx 参数主要分为 Boolean 类型参数和 KV 类型参数,我们一一介绍下

Boolean 类型

  • 公式: -xx:+ 或者 - 某个属性值(+表示开启,- 表示关闭)
  • Case
+ 是否打印 GC 收集细节


    - `-XX:+PrintGCDetails`
    - `-XX:-PrintGCDetails`添加如下参数后,重新查看,发现是 + 号了
+ 是否使用串行垃圾回收器


    - `-XX:-UseSerialGC`
+ `-XX:+UseSerialGC`

KV 设值类型

  • 公式: -XX:属性key=属性value
  • Case:
+ `-XX:MetaspaceSize=128m`
+ `-xx:MaxTenuringThreshold=15`
+ 我们常见的 -Xm s和 -Xmx 也属于 KV 设值类型


    - `-Xms` 等价于 `-XX:InitialHeapSize`
    - `-Xmx` 等价于 `-XX:MaxHeapSize`
  • jinfo 举例,如何查看当前运行程序的配置
+ jps -l
+ jinfo -flag [配置项] 进程编号
+ jinfo **-flags** 1981(打印所有)
+ jinfo -flag PrintGCDetails 1981
+ jinfo -flag MetaspaceSize 2044

这些都是命令级别的查看,我们也可以在程序运行中查看

1
2
3
4
5
java复制代码long totalMemory = Runtime.getRuntime().totalMemory();
long maxMemory = Runtime.getRuntime().maxMemory();

System.out.println("total_memory(-xms)="+totalMemory+"字节," +(totalMemory/(double)1024/1024)+"MB");
System.out.println("max_memory(-xmx)="+maxMemory+"字节," +(maxMemory/(double)1024/1024)+"MB");

常用配置

参数名称 含义 默认值 说明
-Xms 初始堆大小 物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制.
-Xmx 最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn 年轻代大小(1.4or lator) 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。 整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-XX:NewSize 设置年轻代大小(for 1.3/1.4)
-XX:MaxNewSize 年轻代最大值(for 1.3/1.4)
-XX:PermSize 设置持久代(perm gen)初始值 物理内存的1/64
-XX:MaxPermSize 设置持久代最大值 物理内存的1/4
-Xss 每个线程的堆栈大小 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.更具应用的线程所需内存大小进行 调整.在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右 一般小的应用, 如果栈不是很深, 应该是128k够用的 大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。(校长) 和threadstacksize选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:”” -Xss is translated in a VM flag named ThreadStackSize” 一般设置这个值就可以了。
-XX:ThreadStackSize Thread Stack Size (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]
-XX:NewRatio 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代) -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5 Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
-XX:SurvivorRatio Eden区与Survivor区的大小比值 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:LargePageSizeInBytes 内存页的大小不可设置过大, 会影响Perm的大小 =128m
-XX:+UseFastAccessorMethods 原始类型的快速优化
-XX:+DisableExplicitGC 关闭System.gc() 这个参数需要严格的测试
-XX:MaxTenuringThreshold 垃圾最大年龄 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效.
-XX:+AggressiveOpts 加快编译
-XX:+UseBiasedLocking 锁机制的性能改善
-Xnoclassgc 禁用垃圾回收
-XX:SoftRefLRUPolicyMSPerMB 每兆堆空闲空间中SoftReference的存活时间 1s softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap
-XX:PretenureSizeThreshold 对象超过多大是直接在旧生代分配 0 单位字节 新生代采用Parallel Scavenge GC时无效 另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象.
-XX:TLABWasteTargetPercent TLAB占eden区的百分比 1%
-XX:+CollectGen0First FullGC时是否先YGC false

并行收集器相关参数

-XX:+UseParallelGC Full GC采用parallel MSC (此项待验证) 选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集.(此项待验证)
-XX:+UseParNewGC 设置年轻代为并行收集 可与CMS收集同时使用 JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值
-XX:ParallelGCThreads 并行收集器的线程数 此值最好配置与处理器数目相等 同样适用于CMS
-XX:+UseParallelOldGC 年老代垃圾收集方式为并行收集(Parallel Compacting) 这个是JAVA 6出现的参数选项
-XX:MaxGCPauseMillis 每次年轻代垃圾回收的最长时间(最大暂停时间) 如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.
-XX:+UseAdaptiveSizePolicy 自动选择年轻代区大小和相应的Survivor区比例 设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开.
-XX:GCTimeRatio 设置垃圾回收时间占程序运行时间的百分比 公式为1/(1+n)
-XX:+ScavengeBeforeFullGC Full GC前调用YGC true Do young generation GC prior to a full GC. (Introduced in 1.4.1.)

CMS相关参数

-XX:+UseConcMarkSweepGC 使用CMS内存收集 测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明.所以,此时年轻代大小最好用-Xmn设置.???
-XX:+AggressiveHeap 试图是使用大量的物理内存 长时间大内存使用的优化,能检查计算资源(内存, 处理器数量) 至少需要256MB内存 大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提升)
-XX:CMSFullGCsBeforeCompaction 多少次后进行内存压缩 由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生”碎片”,使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理.
-XX:+CMSParallelRemarkEnabled 降低标记停顿
-XX+UseCMSCompactAtFullCollection 在FULL GC的时候, 对年老代的压缩 CMS是不会移动内存的, 因此, 这个非常容易产生碎片, 导致内存不够用, 因此, 内存的压缩这个时候就会被启用。 增加这个参数是个好习惯。 可能会影响性能,但是可以消除碎片
-XX:+UseCMSInitiatingOccupancyOnly 使用手动定义初始化定义开始CMS收集 禁止hostspot自行触发CMS GC
-XX:CMSInitiatingOccupancyFraction=70 使用cms作为垃圾回收 使用70%后开始CMS收集 92 为了保证不出现promotion failed(见下面介绍)错误,该值的设置需要满足以下公式**CMSInitiatingOccupancyFraction计算公式 (opens new window)**
-XX:CMSInitiatingPermOccupancyFraction 设置Perm Gen使用到达多少比率时触发 92
-XX:+CMSIncrementalMode 设置为增量模式 用于单CPU情况
-XX:+CMSClassUnloadingEnabled

辅助信息

-XX:+PrintGC 输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails 输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps
-XX:+PrintGC:PrintGCTimeStamps 可与-XX:+PrintGC -XX:+PrintGCDetails混合使用 输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationStoppedTime 打印垃圾回收期间程序暂停的时间.可与上面混合使用 输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:+PrintGCApplicationConcurrentTime 打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用 输出形式:Application time: 0.5291524 seconds
-XX:+PrintHeapAtGC 打印GC前后的详细堆栈信息
-Xloggc:filename 把相关日志信息记录到文件以便分析. 与上面几个配合使用
-XX:+PrintClassHistogram garbage collects before printing the histogram.
-XX:+PrintTLAB 查看TLAB空间的使用情况
XX:+PrintTenuringDistribution 查看每次minor GC后新的存活周期的阈值 Desired survivor size 1048576 bytes, new threshold 7 (max 15) new threshold 7即标识新的存活周期的阈值为7。

GC性能方面的考虑

再谈 JVM 参数设置

经过前面对 JVM 参数的介绍及相关例子的实验,相信大家对 JVM 的参数有了比较深刻的理解,接下来我们再谈谈如何设置 JVM 参数

  1. 首先 Oracle 官方推荐堆的初始化大小与堆可设置的最大值一般是相等的,即 Xms = Xmx,因为起始堆内存太小(Xms),会导致启动初期频繁 GC,起始堆内存较大(Xmx)有助于减少 GC 次数
  2. 调试的时候设置一些打印参数,如 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log,这样可以从 gc.log 里看出一些端倪出来
  3. 系统停顿时间过长可能是 GC 的问题也可能是程序的问题,多用 jmap 和 jstack 查看,或者 killall -3 Java,然后查看 Java 控制台日志,能看出很多问题
  4. 采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿
  5. 仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的 HashMap 不应该无限制长,建议采用 LRU 算法的 Map 做缓存,LRUMap 的最大长度也要根据实际情况设定

要设置好各种 JVM 参数,还可以对 server 进行压测, 预估自己的业务量,设定好一些 JVM 参数进行压测看下这些设置好的 JVM 参数是否能满足要求

工作中常用配置

docs.oracle.com/javacompone…

参数不懂,推荐直接去看官网

  • -Xms
+ 初始大小内存,默认为物理内存1/64
+ 等价于 -XX:InitialHeapSize
  • -Xmx
+ 最大分配内存,默认为物理内存的1/4
+ 等价于 -XX:MaxHeapSize
  • -Xss
+ 设置单个线程的大小,一般默认为 512k~1024k
+ 等价于 -XX:ThreadStackSize
+ 如果通过 `jinfo ThreadStackSize 线程 ID` 查看会显示为 0,指的是默认出厂设置
  • -Xmn
+ 设置年轻代大小(一般不设置)
  • -XX:MetaspaceSize
+ 设置元空间大小。元空间的本质和永久代类似,都是对 JMM 规范中方法区的实现。不过元空间与永久代最大的区别是,元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制
+ 但是元空间默认也很小,频繁 new 对象,也会 OOM
+ -Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
  • -XX:+PrintGCDetails
+ 输出详细的 GC 收集日志信息
+ 测试时候,可以将参数调到最小,


`-Xms10m -Xmx10m -XX:+PrintGCDetails`


定义一个大对象,撑爆堆内存,



1
2
3
4
5
6
7
8
9
java复制代码public static void main(String[] args) throws InterruptedException {
System.out.println("==hello gc===");

//Thread.sleep(Integer.MAX_VALUE);

//-Xms10m -Xmx10m -XX:PrintGCDetails

byte[] bytes = new byte[11 * 1024 * 1024];
}
  • -XX:SurvivorRatio
+ 设置新生代中 eden 和S0/S1空间的比例
+ 默认 -XX:SurvivorRatio=8,Eden:S0:S1=8:1:1
+ SurvivorRatio值就是设置 Eden 区的比例占多少,S0/S1相同,如果设置 -XX:SurvivorRatio=2,那Eden:S0:S1=2:1:1
  • -XX:NewRatio
+ 配置年轻代和老年代在堆结构的比例
+ 默认 -XX:NewRatio=2,新生代 1,老年代 2,年轻代占整个堆的 1/3
+ NewRatio值就是设置老年代的占比,如果设置-XX:NewRatio=4,那就表示新生代占 1,老年代占 4,年轻代占整个堆的 1/5
  • -XX:MaxTenuringThreshold
+ 设置垃圾的最大年龄(java8 固定设置最大 15)

最后

参数不懂,推荐直接去看官网,

docs.oracle.com/javase/8/do…

docs.oracle.com/javacompone…

docs.oracle.com/javase/8/

Java SE Tools Reference for UNIX](docs.oracle.com/javase/8/do…)

参考:

www.cnblogs.com/duanxz/p/34…

www.javatt.com/p/48544

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Cloud Gateway限流实战

发表于 2021-11-23

欢迎访问我的GitHub

github.com/zq2599/blog…

内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等;

本篇概览

  • 本文是《Spring Cloud Gateway实战》系列的第八篇,经过前面的学习,咱们对过滤器已了解得差不多,今天来补全过滤器的最后一个版块:限流(RequestRateLimiter )
  • 默认的限流器是基于redis实现的,限流算法是大家熟悉的令牌桶(Token Bucket Algorithm),关于令牌捅的原理就不在此展开了,聪明的您看一眼下图应该就懂了:装令牌的桶容量有限,例如最多20个,令牌进入桶的速度恒定(注意,这里是和漏桶算法的区别),例如每秒10个,底部每个请求能拿到令牌才会被处理:

在这里插入图片描述

RequestRateLimiter基本套路

  • 使用RequestRateLimiter过滤器的步骤非常简单:
  1. 准备可用的redis
  2. maven或者gradle中添加依赖org.springframework.boot:spring-boot-starter-data-redis-reactive
  3. 确定按照什么维度限流,例如按照请求中的username参数限流,这是通过编写KeyResolver接口的实现来完成的
  4. 配置application.yml文件,添加过滤器
  • 以上就是使用RequestRateLimiter过滤器的套路了,简单么?接下来,咱们先编码再验证

源码下载

  • 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示(github.com/zq2599/blog…%EF%BC%9A)
名称 链接 备注
项目主页 github.com/zq2599/blog… 该项目在GitHub上的主页
git仓库地址(https) github.com/zq2599/blog… 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在spring-cloud-tutorials文件夹下,如下图红框所示:

在这里插入图片描述

  • spring-cloud-tutorials文件夹下有多个子工程,本篇的代码是gateway-requestratelimiter,如下图红框所示:

在这里插入图片描述

准备工作

  • 为了更好的演示Gateway的效果,在服务提供者provider-hello的代码(Hello.java)中新增一个web接口,可以接受一个入参:
1
2
3
4
java复制代码    @GetMapping("/userinfo")
public String userInfo(@RequestParam("username") String username) {
return Constants.HELLO_PREFIX + " " + username + ", " + dateStr();
}
  • 后面的测试咱们就用上述接口;

编码

  • 在父工程spring-cloud-tutorials之下新增子工程gateway-requestratelimiter,其pom.xml内容如下,重点是org.springframework.boot:spring-boot-starter-data-redis-reactive:
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
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-tutorials</artifactId>
<groupId>com.bolingcavalry</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>gateway-requestratelimiter</artifactId>

<dependencies>
<dependency>
<groupId>com.bolingcavalry</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
</dependencies>
</project>
  • 配置文件application.yml,请注意RequestRateLimiter的几个参数,已经用中文添加了详细的注释:
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
yml复制代码server:
#服务端口
port: 8081
spring:
application:
name: circuitbreaker-gateway
# redis配置
redis:
host: 192.168.50.43
port: 6379

cloud:
gateway:
routes:
- id: path_route
uri: http://127.0.0.1:8082
predicates:
- Path=/hello/**
filters:
- name: RequestRateLimiter
args:
# 令牌入桶的速度为每秒100个,相当于QPS
redis-rate-limiter.replenishRate: 100
# 桶内能装200个令牌,相当于峰值,要注意的是:第一秒从桶内能去200个,但是第二秒只能取到100个了,因为入桶速度是每秒100个
redis-rate-limiter.burstCapacity: 200
# 每个请求需要的令牌数
redis-rate-limiter.requestedTokens: 1
  • 指定限流维度的代码CustomizeConfig.java,这里是根据请求参数username的值来限流的,假设真实请求中一半请求的username的等于Tom,另一半的username的等于Jerry,按照application.yml的配置,Tom的请求QPS为10,Jerry的QPS也是10:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码package com.bolingcavalry.gateway.config;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import java.util.Objects;

@Configuration
public class CustomizeConfig {
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
}
  • 毫无营养的启动类RequestRateLimiterApplication.java:
1
2
3
4
5
6
7
8
9
10
11
java复制代码package com.bolingcavalry.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RequestRateLimiterApplication {
public static void main(String[] args) {
SpringApplication.run(RequestRateLimiterApplication.class,args);
}
}
  • 代码写完了,接下来开始验证;

验证(桶容量等于入桶速度)

  • 首先验证的是桶容量等于入桶速度时的效果,请修改gateway-requestratelimiter应用的application.yml中文件,使得redis-rate-limiter.replenishRate和redis-rate-limiter.burstCapacity的值都等于100,也就是说桶的大小等于100,每秒放入的令牌数也是100
  • 确保redis已经启动,并且与application.yml中的配置保持一直
  • 启动nacos(provider-hello依赖)
  • 启动服务提供者provider-hello
  • 启动gateway-requestratelimiter
  • 为了模拟web请求,我这里使用了Apache Benchmark,windows版本的下载地址:
    www.apachelounge.com/download/VS…
  • 上述文件下载解压后即可使用,在控制台进入Apache24\bin后执行以下命令,意思是向指定地址发送10000个请求,并发数为2:
1
shell复制代码ab -n 10000  -c 2 http://localhost:8081/hello/userinfo?username=Tom
  • 控制台输出如下,可见不到八秒的时间,只成功了800个,证明限流符合预期:

在这里插入图片描述

验证(桶容量大于入桶速度)

  • 接下来试试桶容量大于入桶速度时的限流效果,这对于我们控制峰值响应有很重要的参考价值
  • 请修改gateway-requestratelimiter应用的application.yml中文件,redis-rate-limiter.replenishRate维持100不变,但是redis-rate-limiter.burstCapacity改成200,也就是说每秒放入的令牌数还是100,但桶的容量翻倍了
  • 重启应用gateway-requestratelimiter
  • 再次执行以下命令,意思是向指定地址发送10000个请求,并发数为2:
1
shell复制代码ab -n 10000  -c 2 http://localhost:8081/hello/userinfo?username=Tom
  • 测试结果如下图,可见符合预期,可以将桶内令牌全部用掉,以支撑峰值超过QPS的场景:

在这里插入图片描述

验证(根据username的维度限流)

  • 接下来验证限流的维度,究竟是不是按照请求参数username的值来限流的
  • 咱们打开两个命令行,同时发送请求(动作要快),第一个的username等于Tom,第二个等于Jerry,理论上推测,如果都是8秒内完成,那么每个命令都有900个请求能成功
  • 测试结果如下图,可见符合预期,每个username用的是自己的令牌:

在这里插入图片描述

  • 至此,Spring Cloud Gateway限流实战已经完成,如此简单易用的限流方案,希望能给您的学习和使用带来参考

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界…
github.com/zq2599/blog…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Javaweb重要知识点总结(二)Http 协议 1 ht

发表于 2021-11-23

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

  1. http 的长连接和短连接

HTTP 协议有 HTTP/1.0 版本和 HTTP/1.1 版本。HTTP1.1 默认保持长连接(HTTP persistent connection,也翻译为持久连接),数据传输完成了保持 TCP 连接不断开(不发 RST 包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接。

在 HTTP/1.0 中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。从 HTTP/1.1 起,默认使用的是长连接,用以保持连接特性。

  1. HTTP/1.1 与HTTP/1.0 的区别

1 可扩展性

a)HTTP/1.1 在消息中增加版本号,用于兼容性判断。

b)HTTP/1.1 增加了 OPTIONS 方法,它允许客户端获取一个服务器支持的方法列表。

为了与未来的协议规范兼容,HTTP/1.1 在请求消息中包含了 Upgrade 头域,通过该头域,客户端可以让服务器知道它能够支持的其它备用通信协议,服务器可以据此进行协议切换,使用备用协议与客户端进行通信。

2 缓存

在 HTTP/1.0 中,使用 Expire 头域来判断资源的 fresh 或 stale,并使用条件请求(conditional request)来判断资源是否仍有效。HTTP/1.1 在 1.0 的基础上加入了一些 cache 的新特性,当缓存对象的 Age 超过 Expire 时变为

stale 对象,cache 不需要直接抛弃 stale 对象,而是与源服务器进行重新激活(revalidation)。

3 带宽优化

HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了。例如,客户端只需要显示一个文档的部分内容,又比如下载大文件时需要支持断点续传功能,而不是在发生断连后不得不重新 下载完整的包。

HTTP/1.1 中在请求消息中引入了 range 头域,它允许只请求资源的某个部分。在响应消息中 Content-Range 头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码为 206

(Partial Content),它可以防止 Cache 将响应误以为是完整的一个对象。

另外一种情况是请求消息中如果包含比较大的实体内容,但不确定服务器是否能够接收该请求(如是否有权限), 此时若贸然发出带实体的请求,如果被拒绝也会浪费带宽。

HTTP/1.1 加入了一个新的状态码 100(Continue)。客户端事先发送一个只带头域的请求,如果服务器因为权限拒绝了请求,就回送响应码 401(Unauthorized);如果服务器接收此请求就回送响应码 100,客户端就可以继续发送带实体的完整请求了。注意,HTTP/1.0 的客户端不支持 100 响应码。但可以让客户端在请求消息中加入 Expect 头域,并将它的值设置为 100-continue。

节省带宽资源的一个非常有效的做法就是压缩要传送的数据。Content-Encoding 是对消息进行端到端(end-to- end)的编码,它可能是资源在服务器上保存的固有格式(如 jpeg 图片格式);在请求消息中加入 Accept-Encoding 头域,它可以告诉服务器客户端能够解码的编码方式。

4 长连接

HTTP/1.0 规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个 TCP 连接,服务器完成请求处理后立即断开 TCP 连接,服务器不跟踪每个客户也不记录过去的请求。此外,由于大多数网页的流量都比较小,一次 TCP 连接很少能通过 slow-start 区,不利于提高带宽利用率。

HTTP 1.1 支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个 TCP 连接上可以传送多个 HTTP 请求和响应,减少了建立和关闭连接的消耗和延迟。例如:一个包含有许多图像的网页文件的多个请求和应答可以在一个连接中传输,但每个单独的网页文件的请求和应答仍然需要使用各自的连接。

HTTP 1.1 还允许客户端不用等待上一次请求结果返回,就可以发出下一次请求,但服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间。

5 消息传递

HTTP 消息中可以包含任意长度的实体,通常它们使用 Content-Length 来给出消息结束标志。但是,对于很多动态产生的响应,只能通过缓冲完整的消息来判断消息的大小,但这样做会加大延迟。如果不使用长连接,还可以通过连接关闭的信号来判定一个消息的结束。

HTTP/1.1 中引入了 Chunkedtransfer-coding 来解决上面这个问题,发送方将消息分割成若干个任意大小的数据块,每个数据块在发送时都会附上块的长度,最后用一个零长度的块作为消息结束的标志。这种方法允许发送方只缓 冲消息的一个片段,避免缓冲整个消息带来的过载。

在 HTTP/1.0 中,有一个 Content-MD5 的头域,要计算这个头域需要发送方缓冲完整个消息后才能进行。而HTTP/1.1 中,采用 chunked 分块传递的消息在最后一个块(零长度)结束之后会再传递一个拖尾(trailer),它包含一个或多个头域,这些头域是发送方在传递完所有块之后再计算出值的。发送方会在消息中包含一个 Trailer 头域告诉接收方这个拖尾的存在。

6 Host 头域

在HTTP1.0 中认为每台服务器都绑定一个唯一的IP 地址,因此,请求消息中的URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。

HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request) 。 此 外 , 服 务 器 应 该 接 受 以 绝 对 路 径 标 记 的 资 源 请 求 。

7 错误提示

HTTP/1.0 中只定义了 16 个状态响应码,对错误或警告的提示不够具体。HTTP/1.1 引入了一个 Warning 头域, 增加对错误或警告信息的描述。

此外,在 HTTP/1.1 中新增了 24 个状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突; 410(Gone)表示服务器上的某个资源被永久性的删除。

  1. http 常见的状态码有哪些?

200 OK //客户端请求成功

301Moved Permanently(永久移除),请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置

302found 重定向

400Bad Request //客户端请求有语法错误,不能被服务器所理解

401Unauthorized //请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用403 Forbidden //服务器收到请求,但是拒绝提供服务

404 Not Found //请求资源不存在,eg:输入了错误的 URL

500 Internal Server Error //服务器发生不可预期的错误

503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

  1. GET 和 POST 的区别?

从表面现像上面看 GET 和 POST 的区别:

1.GET 请求的数据会附在 URL 之后(就是把数据放置在 HTTP 协议头中),以?分割 URL 和传输数据,参数之间以&相连,如:login.action?name=zhagnsan&password=123456。POST 把提交的数据则放置在是 HTTP 包的包体中。

2.GET 方式提交的数据最多只能是 1024 字节,理论上 POST 没有限制,可传较大量的数据。其实这样说是错误的,不准确的:

“GET 方式提交的数据最多只能是 1024 字节”,因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟

URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对URL 长度的限制是2083 字节(2K+35)。对于其他浏览器,如Netscape、

FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。

3.POST 的安全性要比 GET 的安全性高。注意:这里所说的安全性和上面 GET 提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的 Security 的含义,比如:通过 GET 提交数据,用户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。

Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中,Method 默认为”GET”,实质上,GET 和 POST 只是发送机制不同,并不是一个取一个发!

  1. http 中重定向和请求转发的区别?

本质区别:转发是服务器行为,重定向是客户端行为。

重定向特点:两次请求,浏览器地址发生变化,可以访问自己 web 之外的资源,传输的数据会丢失。

请求转发特点:一次强求,浏览器地址不变,访问的是自己本身的 web 资源,传输的数据不会丢失。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java 中 Vector 和 SynchronizedLi

发表于 2021-11-23

「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」

本文被《从小工到专家的 Java 进阶之旅》收录。

你好,我是看山。

本文还是折腾 Java 中的队列,上次比较了 Vector、ArrayList、CopyOnWriteArrayList、SynchronizedList,当时感觉挺明白,后来想想又有些不理解的地方,所以今天在重新翻出来研究一下,我承认我钻了牛角尖了。

Vector虽然种种问题,但是都属于设计上的问题,为什么不在后续版本中进行优化呢?HashMap就优化了好几次。而SynchronizedList这个内部类(也就是通过Collections.synchronizedList(new ArrayList())创建的),也是采用了和Vector类似的同步方式(区别是一个在方法体、一个在方法块上,差别不大),为什么大家还是舍弃Vector呢?

其实,在 JDK 中,Vector一直没有被标记为Deprecated,也就是说,虽然外界传说Vector有各种问题,但是从 JDK 官方,从没有认为这个亲儿子没用。

所以,大家不用Vector的原因就剩下两种:

  1. 其他队列比Vector更加适合,优中选优
  2. 大家都说Vector不好用,那我也不用了【个人感觉这种概率更大】

因为Vector主要是数组结构,所以下面大部分的对比都是比较的是针对ArrayList的同步封装。

有了Vector为什么还要有SynchronizedList

这个问题的答案是从 StackOverflow 中找到的。

在 JDK 1.2 之前,Collections是独立类库,不是 JDK/JRE 中的一部分。当时synchronized性能特别差,很多场景不需要使用同步方式,所以,独立类库的开发者删除了同步操作,这个应该就是ArrayList的前身。但是,少部分场景还是需要使用同步,于是就有了SynchronizedList,一个可以包装所有List子类的包装类,这个类在几乎所有方法上都加上了synchronized同步,这个设计与Vector相似。

古人说“文人相轻”,其实在编码界也是有鄙视链的。在这里就是:虽然我的设计和你的设计类似,但是我的设计就是比你的好。不过,Collections确实设计更优。

一个SynchronizedList实现所有List的同步

SynchronizedList定位是包装类,可以包装所有List的子类。也就是说,无论是ArrayList还是LinkedList都能过实现同步,完全不会修改底层数据结构,既实现的同步,又保留了底层接口的优点。比如LinkedList的插入、删除效率,ArrayList的顺序读取。而且,一个包装类就解决所有List子类的同步需求,完全不需要重复实现一遍。

相对而言,Vector就比较霸道了,任何想要同步的队列,都需要转换为Vector的数组结构。大家都知道,数组存储需要连续空间,顺序读取效率表现优秀,但是插入和删除效率就比较差了。

将迭代器的同步权利交给用户

同步方法中SynchronizedList和Vector很类似,不过迭代器方法有了不同想法。

看源码就知道,SynchronizedList中的iterator和listIterator方法都没有实现同步,所以在获取迭代器的时候不会阻塞。

1
2
3
4
5
6
7
8
9
10
11
java复制代码public Iterator<E> iterator() {
    return list.iterator(); // Must be manually synched by user!
}

public ListIterator<E> listIterator() {
    return list.listIterator(); // Must be manually synched by user
}

public ListIterator<E> listIterator(int index) {
    return list.listIterator(index); // Must be manually synched by user
}

如果需要迭代的话,直接用synchronized包一下队列对象就可以了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码final List<String> list = Collections.synchronizedList(new ArrayList());
list.add("A");
list.add("B");
list.add("C");
final Iterator<String> iterator = list.iterator();

synchronized (list) {
    while (iterator.hasNext()) {
        final String next = iterator.next();
        System.out.println(next);
    }
}

我们再看下Vector迭代器实现:

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
java复制代码/**
    * An optimized version of AbstractList.Itr
    */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        // Racy but within spec, since modifications are checked
        // within or after synchronization in next/previous
        return cursor != elementCount;
    }

    public E next() {
        synchronized (Vector.this) {
            checkForComodification();
            int i = cursor;
            if (i >= elementCount)
                throw new NoSuchElementException();
            cursor = i + 1;
            return elementData(lastRet = i);
        }
    }

    public void remove() {
        if (lastRet == -1)
            throw new IllegalStateException();
        synchronized (Vector.this) {
            checkForComodification();
            Vector.this.remove(lastRet);
            expectedModCount = modCount;
        }
        cursor = lastRet;
        lastRet = -1;
    }

    // 此处省略一些方法
}

Vector的迭代器用synchronized (Vector.this)加锁,其实也是对当前类实例加锁,和我们自己实现的加锁方式一致。当然,从这点上来说,Vector能够保证在开发人员无意识的情况下,避免为同步造成的错误,这也是Vector的一个优点。

Vector不完全一无是处

虽然Vector在其他地方败给了Collections,但是在扩容这方面,还有一个可取之处。先看看Vector的扩容方法:

1
2
3
4
5
6
7
8
9
10
11
java复制代码private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                        capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

在计算新数组长度的时候,会检查capacityIncrement是否大于 0,如果是,就扩容capacityIncrement的大小。就是说,在Vector中可以指定扩容大小,如果没有指定,默认扩容到原来的 2 倍;而ArrayList只能扩容到 1.5 倍,没有办法自定义扩容大小。

仔细想想,这点并没有什么用处。

文末总结

  1. Vector内部结构是数组,与Collections.synchronizedList(new ArrayList())类似。
  2. Vector可以指定扩容大小,默认是扩容到原数组长度的 2 倍;ArrayList不能指定扩容大小,直接扩容到原数组大小的 1.5 倍。
  3. SynchronizedList是一个包装类,可以将List子类都包装为同步队列,从非线程安全队列转为线程安全队列,没有性能延迟,直接包装即可;Vector是一个基于数组的同步队列,其他队列想要转换为Vector,需要有数据拷贝。
  4. SynchronizedList的迭代器没有做同步,需要用户自己实现;Vector的迭代器做好了同步,开发人员不需要关心同步。
  5. Vector至今未标记Deprecated,而且随着 JDK 发布,也在更新实现。虽然 JDK 承诺兼容,但是一直没有标记过期,其用意不得而知。

推荐阅读

  • JDK中居然也有反模式接口常量
  • java import 导入包时,我们需要注意什么呢?
  • Java 并发基础(一):synchronized 锁同步
  • Java 并发基础(二):主线程等待子线程结束
  • Java 并发基础(三):再谈 CountDownLatch
  • Java 并发基础(四):再谈 CyclicBarrier
  • Java 并发基础(五):面试实战之多线程顺序打印
  • 如果非要在多线程中使用ArrayList会发生什么?
  • 如果非要在多线程中使用 ArrayList 会发生什么?(第二篇)
  • 重新认识 Java 中的队列
  • Java 中 Vector 和 SynchronizedList 的区别
  • 一文掌握 Java8 Stream 中 Collectors 的 24 个操作
  • 一文掌握 Java8 的 Optional 的 6 种操作

你好,我是看山。游于码界,戏享人生。如果文章对您有帮助,请点赞、收藏、关注。欢迎关注公众号「看山的小屋」,发现不一样的世界。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

小码农教你玩真正的数码管 教你玩真正的数码管

发表于 2021-11-23

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

教你玩真正的数码管

我们最终目标就是偷懒,一起工作是看你认真工作,我偷懒就是享受,所以为了偷懒框架式编程是必会的,以后我的博客里面希望人人都是框架式编程,那种无规则的就认真勤奋吧,我们太懒不配你们来看:joy:,记住我们是懒人族,就应该学懒的东西。

本次不玩虚的,没有什么数码管的概念了,我太懒不想找了,直接上干货

image-20211002094753239

上面那副图也就让你先大概了解一下,详细看下面

首先上面的文件你先创建好,然后再看代码与电路我说过只给懒人看,那写文件都没建好的你适合勤奋

image-20210930214127001

image-20211002095445294

image-20211002095902628

我这套板子个数码管芯片是SN74LS244(SN是厂家名字),

74LS244是三态八缓冲器

逻辑图

image-20211002100232513

这款芯片是用来驱动的,一句话他的驱动能力要比单片机IO驱动能力要大,也就是8位数码管同时工作的时候依旧生龙活虎

他用了很多IO口,但是方便了代码,也就是我们不怎么需要学这个芯片的代码,不需要看时序图,这对那些不太喜欢看时序图的是非常友好的,这个和HC595这款芯片有很大区别,我们老师那款就是这个芯片

为了你可以更懒我找一下那个逻辑图和时序图来给你们看看 串行输入转并行输出

逻辑图

image-20211002102342522

时序图

image-20211002102023787

代码

image-20211002102716173

要是不知道IO口原理可以看这篇文章,他是我哥哥写的 单片机IO口.

SN74LS244_Drive.c

1
2
3
4
5
6
7
8
9
10
11
12
c复制代码#include "all.h"

u8 SN74LS244_Write_Buffer[2] = {0};
void SN74LS244_IO_Mode()
{
P4M1 = 0;
P4M0 = 0;
//P4 = SN74LS244_Write_Buffer[0];//位选
P7M1 = 0;
P7M0 = 0;
//P7 = SN74LS244_Write_Buffer[1];//段选
}

SN74LS244_Drive.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
c复制代码#ifndef SN74LS244_Drive
#define SN74LS244_Drive


//外部声明
extern u8 SMG_Write_Buffer[8];
extern u8 SN74LS244_Write_Buffer[2];


extern void SN74LS244_IO_Mode();
//extern void SN74LS244_Write_Data_Drive();


#endif

实际上上面代码如果all.h文件没有问题是不会报错的,但是all.h有问题的话报错是报SN74LS244_Drive.h的错误,以后我会详细讲解这种类型的错误,今天只玩数码管

这时也就到了中层部分服务层

image-20211002112241885

image-20211002111825484

image-20211002112048871

image-20211002123940101

image-20211002130748018

image-20211002131403005

代码

SMG_Ser.c

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
c复制代码#include "all.h"
u8 SMG_Write_Buffer[8] = {0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff};//数码管缓存
//数码管段选
u8 code SMG_SEG[] = {0xC0,0xF9,0xA4,0xB0, //0 1 2 3
0x99,0x92,0x82,0xF8, //4 5 6 7
0x80,0x90,0x88,0x83, //8 9 a b
0xC6,0xA1,0x86,0x8E}; //c d e f
//数码管位选
u8 code SMG_GRID[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};


void delay()
{
u8 i = 0;
for(i = 0;i<100;i++);
}

//数码管显示服务
void SMG_Display_Ser()
{
static u8 count = 0;
switch(count)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
SN74LS244_Write_Buffer[1] = ~SMG_GRID[count];
SN74LS244_Write_Buffer[0] = SMG_SEG[SMG_Write_Buffer[count]];
P4 = SN74LS244_Write_Buffer[1];
P7 = SN74LS244_Write_Buffer[0];
delay();
break;
}
count++;
count = count%8;

}

SMG_Ser.h

1
2
3
4
5
6
7
8
9
c复制代码#ifndef SMG_Ser
#define SMG_Ser

//外部声明
extern u8 code SMG_SEG[];
extern void SMG_Display_Ser();


#endif

最后就到了主文件了

image-20211002132711660

刚开始时

运行一段时间后

上面就是死等的垃圾之处,就好像傲娇女一样,看见就来气

代码

main.c

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
c复制代码#include "all.h"
sbit LED1 = P3^2;
sbit LED2 = P3^3;

//数码管分配
void SMG_Allot()//数码管需要进入服务层很快,
//但数组是没必要一直刷,所以我们可以降低,刷新次数
{
static xdata u16 count = 0;//放到外部RAM上面,降低内部ROM的空间
count++;
if(count>200)
{
count = 0;
SMG_Write_Buffer[0] = 9;
SMG_Write_Buffer[1] = 8;
SMG_Write_Buffer[2] = 7;
SMG_Write_Buffer[3] = 6;
SMG_Write_Buffer[4] = 5;
SMG_Write_Buffer[5] = 4;
SMG_Write_Buffer[6] = 3;
SMG_Write_Buffer[7] = 2;
}
SMG_Display_Ser();
}
//void SN74LS244__Allot()
//{
// static xdata u8 count = 0;
// count++;
// if(count>200)
// {
// count = 0;
// SN74LS244_Write_Buffer[0] = 0xf0;
// }
//}
//看门狗
void WDT_CONTR_Allot()
{
static xdata u16 count = 0;
count++;
if(count>1000)
{
count = 0;//超过1000计数器清零
WDT_CONTR=0x34; //启动看门狗和喂狗
}
}

void main()
{
SN74LS244_IO_Mode();
WKTCH = 0xff;//掉电唤醒定时器高字节
WKTCL = 0xff;//掉电唤醒定时器低字节
PCON |= 0x02;

while(1)
{
SMG_Allot();
//WDT_CONTR_Allot();
}
}

项目脊梁

image-20211001013324843

代码

all.h

1
2
3
4
5
6
7
8
9
10
c复制代码#include <STC15.h>


typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;


#include "SN74LS244_Drive.h"
#include "SMG_Ser.h"

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

孤尽T31训练营11权限管理笔记

发表于 2021-11-23

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

这次课程主要是实践,这里记录一下搭建auth-center的过程

准备:生成密钥证书

第1步:生成密钥证书

下面的命令生成密钥证书,采用RSA算法,每个证书包含公钥和私钥

1
2
3
4
5
6
7
8
shell复制代码-- 创建一个文件夹,在该文件夹下执行如下命令
keytool -genkeypair -alias kaikeba -keyalg RSA -keypass kaikeba -keystore kaikeba.jks -storepass kaikeba

-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名
-storepass:密钥库的访问密码

image-20211122222036588.png

生成证书文件kaikeba.jks,搭建授权服务器时用

第2步:证书管理

1
2
3
4
5
shell复制代码-- 查询证书信息
keytool -list -keystore kaikeba.jks

-- 删除别名
keytool -delete -alias kaikeba -keystore kaikeba.jsk

image-20211122222243394.png

第3步:导出公钥

1
shell复制代码keytool -list -rfc --keystore kaikeba.jks | openssl x509 -inform pem -pubkey

将公钥保存到public.key文件,搭建资源服务器时用

搭建授权中心

第1步:创建授权中心模块

t31-auth-center

第2步:添加依赖

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
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>t31-parent</artifactId>
<groupId>com.kaikeba</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>t31-auth-center</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!--oauth2-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>com.kaikeba</groupId>
<artifactId>t31-admin-instance</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>

</project>

第3步:配置

application.yml

1
2
3
4
5
yaml复制代码spring:
application:
name: auth-center
profiles:
active: dev

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yaml复制代码spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
extension-configs[0]:
data-id: common.yaml
refresh: true
extension-configs[1]:
data-id: db.yaml
refresh: true
# 多个接口上的@FeignClient(“相同服务名”)会报错,overriding is disabled。
# 设置 为true ,即 允许 同名
main:
allow-bean-definition-overriding: true

生成的密钥证书放到resources下

第4步:启动器

AuthApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java复制代码package com.kaikeba.t31.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
* 授权中心
*
* @author yangdc
* @date 2021/11/22
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class AuthApplication {

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

第5步:OAuth2配置

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
java复制代码package com.kaikeba.t31.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;


/**
* OAuth2配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

@Autowired
@Qualifier("authenticationManagerBean")
AuthenticationManager authenticationManager;

@Autowired
private DataSource dataSource;

@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}

@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
//证书路径和密钥库密码
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("kaikeba.jks"), "kaikeba".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//密钥别名
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("kaikeba"));
return converter;
}

@Bean
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//配置通过表oauth_client_details,读取客户端数据
clients.withClientDetails(clientDetailsService());
}

/**
* 配置token service和令牌存储方式(tokenStore
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//tokenStore
endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager);

//tokenService
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
tokenServices.setSupportRefreshToken(false);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
// 30天
tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
endpoints.tokenServices(tokenServices);
}

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许表单认证
security.allowFormAuthenticationForClients()
//放行oauth/token_key(获得公钥)
.tokenKeyAccess("permitAll()")
//放行 oauth/check_token(验证令牌)
// .checkTokenAccess("isAuthenticated()");
.checkTokenAccess("permitAll()");
}
}

第6步:Spring Security配置

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
java复制代码package com.kaikeba.t31.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
* Security配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

/**
* 注入自定义UserDetailService,读取rbac数据
*/
@Autowired
private UserDetailsService userDetailsService;

@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
// 开放/oauth/开头的所有请求
http.requestMatchers().anyRequest()
.and().authorizeRequests().antMatchers("/oauth/**").permitAll();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 注入自定义的UserDetailsService,采用BCrypt加密
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
}

第7步:UserDetailService

UserDetailService作用是从数据库中读取rbac数据,用户、密码、角色数据返回UserDetails,交给Spring Security框架匹配验证,颁发令牌等操作

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
java复制代码package com.kaikeba.t31.auth.service.impl;

import com.kaikeba.t31.admin.po.Role;
import com.kaikeba.t31.auth.client.UserClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
* 用户详情服务实现
* 作用:读取数据库用户角色数据
*
* @author yangdc
* @date 2021/11/22
*/
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {

@Autowired
UserClient userClient;

@Autowired
PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 使用OpenFeign调用admin-service,得到用户角色数据
com.kaikeba.t31.admin.po.User user = userClient.getByUsername(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if (user != null) {
log.debug("current user = " + user);
// 获取用户的授权
List<Role> roles = userClient.selectRoleByUserId(user.getId());
// 声明授权文件
for (Role role : roles) {
if (role != null && role.getName() != null) {
// 封闭用户角色信息,必须ROLE_开头
// spring Security中权限名称必须满足ROLE_XXX
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getName());
grantedAuthorities.add(grantedAuthority);
}
}
}
log.debug("granted authorities = " + grantedAuthorities);
// 返回Spring Security框架的User对象
return new User(user.getUserName(), user.getPassword(), grantedAuthorities);
}
}

读取数据的UserFeign如下

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
java复制代码package com.kaikeba.t31.auth.client;

import com.kaikeba.t31.admin.po.Role;
import com.kaikeba.t31.admin.po.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

/**
* 用户客户端
*
* @author yangdc
* @date 2021/11/22
*/
@FeignClient(name = "admin-service", fallback = UserClient.UserClientFallback.class)
public interface UserClient extends UserApi {

@Slf4j
@Component
//这个可以避免容器中requestMapping重复
@RequestMapping("/fallback")
class UserClientFallback implements UserClient {

@Override
public User getByUsername(String username) {
log.info("异常发生,进入fallback方法");
return null;
}

@Override
public List<Role> selectRoleByUserId(Long id) {
log.info("异常发生,进入fallback方法");
return null;
}

}
}

搭建资源中心

t31-admin-service服务

第0步:配置

先前public.key复制到resources下

第1步:Jwt配置

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
java复制代码package com.kaikeba.t31.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.util.FileCopyUtils;

import java.io.IOException;

/**
* Jwt配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
public class JwtConfig {

public static final String public_cert = "public.key";

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Bean
@Qualifier("tokenStore")
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter);
}

@Bean
protected JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource(public_cert);

String publicKey;
try {
publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
}catch (IOException e) {
throw new RuntimeException(e);
}

// 设置校验公钥
converter.setVerifierKey(publicKey);

// 设置证书签名密码,否则报错
converter.setSigningKey("kaikeba");

return converter;
}
}

第2步:资源服务器配置

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
java复制代码package com.kaikeba.t31.admin.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;

/**
* 资源服务器配置
*
* @author yangdc
* @date 2021/11/22
*/
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Autowired
private TokenStore tokenStore;

@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/**").permitAll();
.antMatchers("/user/**").permitAll()
// 用于测试
.antMatchers("/book/**").hasRole("ADMIN")
.antMatchers("/**").authenticated();
}

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
}

第3步:security.yml

所有资源中心都要配置oauth/token_key,用来验证公钥,启动时验证

resources/security.yml

1
2
3
4
5
6
yaml复制代码security:
oauth2:
resource:
jwt:
#如果使用JWT,可以获取公钥用于 token 的验签
key-uri: http://localhost:9098/oauth/token_key

第4步:权限控制说明

Spring Security中定义了四个支持权限控制的表达式注解,分别是

  • @PreAuthorize
  • @PostAuthorize
  • @PreFilter
  • @PostFilter

其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤.在需要控制权限的方法上, 我们可以添加@PreAuthorize注解,用于方法执行前进行权限检查,校验用户当前角色是否能访问该方法.

1 开启EnableGlobalMethodSecurity

使用上述注解控制权限需要设置EnableGlobalMethodSecurity —–见第2步资源服务器配置

2 如何使用注解

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
java复制代码package com.kaikeba.t31.admin.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

/**
* @author yangdc
* @date 2021/11/22
*/
@Slf4j
@RestController
public class TestController {

@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
return "product id : " + id;
}

@GetMapping("/order/{id}")
public String getOrder(@PathVariable String id) {
return "order id : " + id;
}

@GetMapping("/book/{id}")
public String getBook(@PathVariable String id) {
return "book id : " + id;
}

@GetMapping("/anno/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String getAnno(@PathVariable String id) {
return "admin id :" + id;
}

@RequestMapping("/hello")
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
public String hello() {
return "hello you ...";
}


@GetMapping("/getPrinciple")
public OAuth2Authentication getPrinciple(OAuth2Authentication oAuth2Authentication, Principal principal, Authentication authentication) {
log.info(oAuth2Authentication.getUserAuthentication().getAuthorities().toString());
log.info(oAuth2Authentication.toString());
log.info("principal.toString() " + principal.toString());
log.info("principal.getName() " + principal.getName());
log.info("authentication: " + authentication.getAuthorities().toString());

return oAuth2Authentication;
}

}

3 控制权限的方式—全局代码控制

image-20211122230658799.png

4 控制权限的方式—注解控制

image-20211122230740959.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1 开始Kubernetes k8s 一、发展经历 二

发表于 2021-11-23

这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战」

目录内容:

1. 发展经历

2. 知识图谱

3. 组件说明

  本节目标: 要求会画bolg系统和kubernetes系统的架构图, 并且知道架构每一部分的作用.


一、发展经历

对于云计算来说, 他会有一些发展标准

1. Infrastructure as a Service : 简称IAAS, 基础设施及服务. 国内典型的厂家是阿里云. 国际最大的厂商是AWS.

2. platform as a Service: 简称PAAS, 平台及服务.

3. Software as a Service: 简称SSAS, 软件设施及服务.

  比如: 我想用office套件, 不在需要像以前一下,需要1小时的安装, 我只需要通过b/s结构, 也就是浏览器端访问到他的网页即可. 完成office的文件的创建,修改

下面来详细说说PAAS, 平台及服务.

新浪有一个老的平台SAA, 以前是用户可以免费申请这样一个平台, 运行代码项目, 比如java, php等.

  比如: 有一个用户, 申请了一个SAA服务的平台. 然后新浪的运维在后台看到这个订单, 开始部署环境. 这时候, 运维特别苦逼. 来一个单子, 部署一套环境.

  后来有了一些自动部署的运维工具, 让部署自动化所谓的环境的创建, 可以能是java的环境, 可能是php的环境. 直到后来, docker股份有限公司开发了docker. 这个公司主要就是做PAAS平台的. docker在他们公司的主要地位就是自动的构建这些环境的封装体. Docker成了PAAS下一代的标准. 随之也会带来一些问题.

  在传统的服务中, 在一台物理机上, 运行多个tomcat, 多个数据库, 组成一个大的集群. 这是没有问题的, 但是一旦容器化以后, 就有问题了.

  假设: 有6台物理机, 让这6台物理机组成一个集群. 比如一台nginx, 3台tomcat, 2台mysql. 他们之间访问的方式是ip+端口号, 进行联通.

  

但是使用容器以后, 我们就发现,他们之间的映射关系就比较困难了. 比如, 将nginx安装在docker上. 首先要将docker中nginx的端口号映射到物理机上, tomcat需要吧8080映射到主机的8080. mysql也是.  而且一台服务器不可能只安装1个tomcat, 还会有很多其他的应用, 然后每台机器都会映射很多端口. 这样越来越多, 太杂,太乱了. 于是衍生出了新的产品. 这个产品叫资源管理器.

那么资源管理器又经过了哪些过程呢?

1. Apache 的mesos: mesos是apache下开源的分布式管理框架, 由加州大学的伯克利分校开发出来的. 2019年5月, 弃用mesos, 全部改为k8s.

2. docker swarm: 设置docker的母公司诞生的一款资源管理器. docker和swarm已经组成了一个体系的功能. 这个东西很轻量, 只消耗几十M的资源, 对于集群管理来说, 消耗几十M是非常小的, 通常都是要几十G的. 他这么好,为什么不用他呢? 根本原因, 还是相对于k8s来说, swarm的功能还太少了. 比如我想实现一个滚动更新和回滚等操作. 在swarm里实现是非常困难的. 我们需要手工定义这个流程, 太费事. 2019年7月 阿里云宣布 Docker swarm 剔除

3. kubernetes: google, 在10年就已经进行容器化基础架构管理了. 系统的名字叫borg(伯格系统), 当时很多人都想要这套系统, 但是google不差钱, 不卖. 后来随着docker的盛行, 市场都开始研究资源管理器. 这是google站出来说话了. 他让其内部工程师, 使用go语言, 重新翻写了borg系统. 就是现在的k8s. 成为了当前的标准.

 kubernetes的特点:

    1. 轻量级: go语言开发, 运行效率高, 消耗资源少.

    2. 开源: 能够被推广的主要原因

    3. 弹性伸缩: 公司运行个几年就上市了, 资源不够用了, 从5台—>一下升级到10台. 我们还可以由主节点将某些节点调度剥离出去, 有原来的8台缩减为5台. 可以释放资源, 减少资金消耗

    4. 负载均衡: kubernetes已经实现了模块之间的负载均衡, 不需要我们在使用调度器实现. 由kubernetes本机进行. 并且, kubernetes采用了最近版本的负载均衡框架IPVS. IPVS是我们国内开发的, 是国人的骄傲, 在负载均衡界也是老大, no1.

二. 知识图谱

了解, 都有哪些知识点

三. 组件说明

本节目标: 要求会画bolg系统和kubernetes系统的架构图, 并且知道架构每一部分的作用.

Kubernetes的前身是borg系统. 所以,我们先来看borg系统的架构

3.1 Borg系统的系统架构

1. 首先可以看到, 这里有两大块: BorgMaster和Borglet.

  • BorgMaster: 负责请求的分发 , 是整个集群的大脑. BorgMaster为了避免单节点故障, 所以, 会有很多副本, 通常副本数都是奇数3, 5, 7, 9…..方便选举.
  • Borglet: 真正工作的系统是Borglet, Borglet会提供一些计算能力.

2. 访问BorgMaster有三种方式

  • web browsers: 浏览器
  • command-line tools: 命令行
  • config file: 文件

在Kubernetes里面也会有这三种调度管理的方式.

请求通过三种方式到达BorgMaster系统以后, master会对请求进行分发, 分发给不同的Borglet进行处理. 比如, 有活来了, 领导要把活分给不同的小伙伴, 那么怎么分呢? 就有一个scheduler调度器.

由调度器来决定将任务分配给哪一个Borglet来做. 这里调度去Scheduler并不直接给Borglet分配工作, 而是, 将任务写入到数据库Paxos, 进行持久化存储起来. Paxos是谷歌的键值对数据库. Borglet会监听Paxos数据库, 看看是不是有我的任务来了. 如果来了, 那就消费.

以上是borg系统的架构, Kubernetes和borg是类似的, 下面来看Kubernetes架构

3.2 Kubernetes架构

这是k8s的架构图. 主要分为两大部分: 中间包含三个绿色包的是master服务器. 下面是node节点.

  • Master: master中有哪些东西

+ **scheduler: 任务调度器, 负责调度任务, 选择合适的节点执行任务.** 任务来了, 还是通过scheduler进行任务调度分发至不同的node. 在Borg系统中, scheduler是将任务写入到Paxos系统中, 而这里不同的是, scheduler会将任务交给api server, 由api server将任务写入到etcd, 也就是说scheduler不会直接和etcd交互
+ **replication controller: 简称rc, 控制器, 用来维护副本的期望数目**. 举个例子, 我想让容器运行几个副本, 就是由rc来控制的. 一旦副本数不符合我们的期望值, rc就要改写副本数或者申请到我们的期望值. 也就是创建对应的Pod或者删除对应的Pod
+ **api server: 所有服务访问的统一入口.**  就是一起访问的入口. 从上图可以看出. Master中scheduler需要和api server交互, rc要和api server交互, kubectl(客户端)也要和api sever交互, web UI也要和api server交互, etcd也要和api server交互. apiserver是非常繁忙的.
  • etcd: 键值对数据库, 存储K8s集群的所有重要信息(持久化). etcd就类似于在Borg系统中的Paxos键值对数据库. 在Kubernetes集群中起到的了持久化的作用. 对于etcd有两点说明:

+ etcd官方将其定位为一个可信赖的分布式键值存储服务, 它能够为整个分布式集群存储一些关键数据, 协助分布式集群的正常运转.
+ etcd的版本

      

      etcd现在有两个版本, v2和v3版本, v2版本将数据保存到内存, v3版本将数据保存到数据库. 正常我们都选择使用v3版本, 但Kubernetes v1.11版本之前使用的是v2版本.

    • etcd内部架构图

      • http Server: 这里采用的是使用http进行构建的c/s服务, k8s也是采用的http协议进行c/s服务的开发. 为什么要这么做呢? 因为http天生支持一系列的操作. 例如: get ,post, put, delete, 授权认证等. 所以, 没有必要再去采用标准的tcp协议. 开发一系列的认证流程, 所以, 直接采用http协议即可.
        • Raft是读写的信息, 所有的读写信息都被存在Raft里面, 而且, 为了防止这些信息出现损坏, 他还有一个WAL预写日志
        • WAL: 预写日志, 如果要对数据进行更改, 那么先写入一条日志, 然后定时的对日志进行完整的备份. 也就是完整+临时. 比如: 我先备份一个大版本, 备份以后, 还会有1个子版本, 两个子版本….., 然后将这些版本再次进行一个完整备份,把它变成一个大版本. 这样做的好处, 我们不能始终进行完整备份, 因为消耗的数据量太大. 为什么还要在一定时间内进行完整的备份呢?防止增量备份太多, 还原的时候太费事. 并且, Raft还会实时的把这些数据和日志存入到本地磁盘进行持久化.
        • Store: 试试把WAL中的日志和数据, 写入磁盘进行持久化.
  • Node节点: 从图中可以看出, Node节点包含三个组件 ,kubelet, kube proxy, 以及container. 也就是说我们在node节点需要安装三个软件: kebelet, kebu proxy, docker

    

    • kubelet的作用: 直接跟容器交互, 实现容器的生命周期管理. 他会和CRI, C是容器, R是runtime, I是interface. CRI就是docker的操作形式. kubelet会和docker交互, 创建需要的容器. kubelet会维持Pod的生命周期.
      • kube proxy的作用: 负责写入规则至IPTABLES, IPVS实现服务映射访问. 之前说过svc, 可以进行负载操作, 负责的操作就是通过kube proxy完成的. 怎么实现Pod与Pod之间的访问, 以及负载均衡. 默认操作是操作防火墙, 去实现Pod的映射. 新版本还支持IPVS.

其他重要的插件

  • COREDNS: 可以为集群中的SVC创建一个域名IP对应的关系解析. 也就是说,我们在集群中访问其他Pod的时候, 完全不需要通过Pod的ip地址, 通过CoreDns给他生成的域名去实现访问. 他是集群中的重要重要组件, 也是实现负载均衡的其中一项功能.
  • DASHBOARD: 给K8S集群提供一个 B/S结构访问体系.
  • Ingress Controller: 官方只为我们实现了四层代理. Ingress可以实现七层代理, 也就是可以根据组件名和域名进行负载均衡.
  • Federation: 提供一个可以跨集群中心多K8s统一集群管理功能.
  • Prometheus(普罗米修斯): 提供K8S集群的监控能力.
  • ELK: 提供k8s集群日志统一接入平台

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

快速排序算法之双边循环法

发表于 2021-11-23

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

欢迎关注公众号OpenCoder,来和我做朋友吧~❤😘😁🐱‍🐉👀

前言:

之前我们学习了冒泡排序,有没有比冒泡排序更快的排序算法呢?当然有,例如快速排序,归并排序,堆排序。接下来即将介绍的快速排序就是由冒泡排序演变而来的。

一.快速排序介绍

由快速排序演变而来的快速排序也是沿用了交换排序,通过元素之间的比较和交换位置来达到排序的目的。不同的是快速排序使用了分治法。冒泡排序在每一轮只把1个元素冒泡到数列一端,而快速排序则先在每一轮挑选1个基准元素,并让其他比他大的元素移动到数列一端,比他小的元素移动到数列另一端,从而把数列拆成两个部分。

二.逻辑推演

每一轮挑选1个基准元素,并让其他比他大的元素移动到数列一端,比他小的元素移动到数列另一端,从而把数列拆成两个部分,这种操作方式被称为分治法。

分治法:

如图示:

橙色:挑选的基准元素

蓝色:小于基准元素的数

粉色:大于基准元素的数

屏幕快照 2021-10-17 上午10.45.47

假设给我8个数的数列,按照冒泡排序通常需要比较7轮,每一轮确定一个元素移动到数列的某一端,时间复杂度为O(n^2)。而快速排序采用分治法如图:

屏幕快照 2021-10-17 上午11.11.03

屏幕快照 2021-10-17 上午11.18.05

如图所示:

在分治法思想下,原数列在每一轮都被拆分成两部分,每一部分在下一轮又分别被拆分成两部分,直到不可分为止。

每一轮的比较和交换,需要把数组全部元素都遍历一次,时间复杂度为O(n)。这样的遍历需要多少轮?假设元素个数为n,那平均情况下需要logn轮,因此快速排序算法总体的平均时间复杂度为O(nlogn)。

基准元素选择:

在分治过程中,以基准元素为中心,把其他元素移动到它的左右两边。分治法的第一环节就是确定基准元素,那么基准元素如何产生?

最简单的方式是选择数列的第1个元素,作为基准元素。

屏幕快照 2021-10-17 上午11.39.12

这种选择在绝大多数情况下,没有问题。但是假如有一个原本逆序的数列,期望排序成顺序数列,那就会出现这样的情况:

屏幕快照 2021-10-17 上午11.43.47

屏幕快照 2021-10-17 上午11.45.56

第4轮…

第5轮…

整个数列并没有被分成两半,每一轮都只是确定了基准元素的位置。这种情况下,数列的第1个元素要么数列的最小值,要么是数列的最大值,完全无法发挥分治法的优势。在这样的极端情况下,快速排序需要进行n轮,时间复杂度退化成了O(n^2)。

那么如何避免这样的情况发生呢?

其实很简单,我们可以随机选择一个元素作为基准元素,并让基准元素和数列首元素交换位置。

例如:随机得到元素4作为基准,然后将4与首位8进行交换。

屏幕快照 2021-10-17 上午11.54.02

这样的话,即使在数列完全逆序的情况下,也可以有效的将数列分成两部分了。

不过,即使是随机选择元素作为基准元素,也会有小概率选到数列的最大或者最小值,同样会影响分治的效果。因此虽然快速排序的平均时间复杂度为O(nlogn),但是最坏情况下时间复杂度是O(n^2)。

元素的交换:

选定了基准元素以后,我们要做的就是把其他元素中小于基准元素的都交换到基准元素的一边,大于基准元素的都交换到基准元素的另外一边。

具体如何实现?有两种方法。

1.双边循环法

2.单边循环法

三.快速排序之双边循环法

分析:

何谓双边循环法?下面看看详细过程。

原始数列如下,要求对其从小到大排序。

屏幕快照 2021-10-17 下午2.31.25

1.首先,选定基准元素4,并且设置两个指针left和right,指向数列的最左和最右两个元素。

屏幕快照 2021-10-17 下午2.39.18

2.接下来进行第1次循环,从right指针开始,让指针所指向的元素和基准元素做比较。如果大于或等于基准元素,则指针向左移动;如果小于基准元素,则right指针停止移动,切换到left指针。

当前数列中,1<4,所以right指针直接停止移动,换到left指针,进行下一步行动。

3.轮到left指针行动,让指针所指向的元素和基准元素做比较。如果小于或等于基准元素,则指针向右移动,如果大于基准元素,则left指针停止移动。

当前数列中,left指针指向的就是基准元素所以相等,则left右移1位。

屏幕快照 2021-10-17 下午2.46.04

4.由于7>4,left指针在元素7的位置处停下来。这时候,让left和right指针指向的元素进行交换,即数字7和数字1进行交换。

屏幕快照 2021-10-17 下午2.52.06

5.接下来进行第2次循环,重新切换到right指针,向左移动。right指针先移动到8,8>4,继续向左移动。由于2<4,right指针停止在2的位置。

按照这个思路,后续步骤如图所示:

第2次循环right指针停在2的位置,left指针停在6的位置。

屏幕快照 2021-10-17 下午4.25.17

元素2和6进行交换

屏幕快照 2021-10-17 下午4.26.24

第3次循环,right指针在3的位置,left指针在5的位置,3和5发生元素交换

屏幕快照 2021-10-17 下午4.28.10

第4次循环,right指针停在3的位置和left指针重合。

屏幕快照 2021-10-17 下午4.30.50

最后把基准元素4和重合点3进行交换,这一轮宣告结束,数列分成两部分。。

屏幕快照 2021-10-17 下午4.33.46

代码实现:

下面通过递归的方式完成双边循环法的快速排序实现

初始数列为:4,4,6,5,3,2,8,1

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
java复制代码    public static void main(String[] args) {
       int[] arr = {4,4,6,5,3,2,8,1};
       quickSort(arr,0,arr.length-1);
       System.out.println(Arrays.toString(arr));
  }
​
   private static void quickSort(int[] arr, int startIndex, int endIndex) {
       //递归结束条件:startIndex>=endIndex时
       if(startIndex>=endIndex){
           return;
      }
       //得到基准元素的位置
      int pivotIndex = partition(arr,startIndex,endIndex);
       //根据基准元素,分成两部分进行递归排序
       quickSort(arr,startIndex,pivotIndex-1);
       quickSort(arr,pivotIndex+1,endIndex);
  }
​
   private static int partition(int[] arr, int startIndex, int endIndex) {
       //取第1个位置(也可以选择随机位置)的元素作为基准元素
       int pivot = arr[startIndex];
       int left = startIndex;
       int right = endIndex;
       while (left != right){
           //控制right指针比较并右移
           while(left<right && arr[right]>pivot){
               right--;
          }
           //控制left指针比较并右移
           while (left<right && arr[left]<=pivot){
               left++;
          }
           //交换left和right指针所指向的元素
           if(left<right){
               int p = arr[left];
               arr[left]=arr[right];
               arr[right]=p;
          }
​
      }
       //pivot和指针重合点交换
       arr[startIndex] = arr[left];
       arr[left] = pivot;
       return left;
  }

小结:

在上述代码中,quickSort方法通过递归的方式,实现了分而治之的思想。

partition方法则实现了元素的交换,让数列中的元素依据自身大小,分别交换到基准元素的左右两边。这样的交换方式被称为双边循环法。

四.总结

partition方法实现挺复杂的,在一个大循环中还嵌套了两个小循环。不过以双边循环法作为基础,又延伸出来了单边循环法,单边循环法就要简单得多,只从数组的一遍对元素进行遍历和交换。下一章,我们将探寻单边循环法。

欢迎关注公众号OpenCoder,来和我做朋友吧~❤😘😁🐱‍🐉👀

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…226227228…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%