├── Golang └── golang_interview.md ├── Mysql └── Mysql.md ├── OS └── os.md ├── README.md ├── Redis └── Redis.md └── 计网 └── 计网.md /Golang/golang_interview.md: -------------------------------------------------------------------------------- 1 | ## 1.make和new的区别 2 | 3 | **不同点** 4 | 5 | 1. 作用变量的类型不同,new可以给任何类型分配内存;make用于给slice,map,channel分配内存。 6 | 2. 返回的类型不同,new返回变量的指针,make则返回变量本身。 7 | 3. new只是分配内存并清零,并没有初始化内存;make即分配内存,也初始化内存。 8 | 9 | **相同点** 10 | 11 | 1. 都会在栈上分配内存。 12 | 2. 给变量分配内存。 13 | 14 | ## 2.array和slice的区别 15 | 16 | **相同点** 17 | 18 | 1. 不论是array还是slice都只能存储一组相同类型的数据结构。 19 | 2. 都是通过下标来访问,并且有长度和容量,长度通过len获取,容量通过cap获取。 20 | 3. 访问不能越界。 21 | 22 | **不同点** 23 | 24 | 1. array是定长,一旦申请则不能变化;slice是边长,可以动态变化。 25 | 2. array是值类型,slice是引用类型。 26 | 27 | ## 3.slice 28 | 29 | ### 3.1slice底层数据结构 30 | 31 | 1. ~~~go 32 | type slice struct{ 33 | array unsafe.Pointer 34 | len int 35 | cap int 36 | } 37 | ~~~ 38 | 39 | 2. 使用方式 40 | 41 | 1. 使用make创建 42 | 2. 使用array创建 43 | ### 3.2slice扩容 44 | 45 | **slice扩容遵循以下原则** 46 | 47 | 1. 如果旧的容量*2=1024,newcap=oldcap*1.25 51 | 52 | ### 3.3slice深拷贝和浅拷贝 53 | 54 | **深拷贝** 55 | 56 | 拷贝的是数据本身,创建新对象与旧对象不共享内存,对新对象的操作不会影响到旧对象。实现方式有: 57 | 58 | 1. copy(slice2,slice1) 59 | 2. append赋值 60 | 61 | **浅拷贝** 62 | 63 | 拷贝的是数据地址,只复制指针,新对象和旧对象的内存地址是一样的,会同时修改新旧对象。实现方式有: 64 | 65 | 1. slice2:=slice1 66 | 67 | ## 4.map 68 | 69 | ### 4.1map底层数据结构 70 | 71 | ~~~go 72 | // A header for a Go map. 73 | type hmap struct { 74 | count int 75 | // 代表哈希表中的元素个数,调用len(map)时,返回的就是该字段值。 76 | flags uint8 77 | // 状态标志(是否处于正在写入的状态等) 78 | B uint8 79 | // buckets(桶)的对数 80 | // 如果B=5,则buckets数组的长度 = 2^B=32,意味着有32个桶 81 | noverflow uint16 82 | // 溢出桶的数量 83 | hash0 uint32 84 | // 生成hash的随机数种子 85 | buckets unsafe.Pointer 86 | // 指向buckets数组的指针,数组大小为2^B,如果元素个数为0,它为nil。 87 | oldbuckets unsafe.Pointer 88 | // 如果发生扩容,oldbuckets是指向老的buckets数组的指针,老的buckets数组大小是新的buckets的1/2;非扩容状态下,它为nil。 89 | nevacuate uintptr 90 | // 表示扩容进度,小于此地址的buckets代表已搬迁完成。 91 | extra *mapextra 92 | // 存储溢出桶,这个字段是为了优化GC扫描而设计的,下面详细介绍 93 | } 94 | 95 | ~~~ 96 | 97 | ~~~go 98 | // A bucket for a Go map. 99 | type bmap struct { 100 | tophash [bucketCnt]uint8 101 | // len为8的数组 102 | // 用来快速定位key是否在这个bmap中 103 | // 一个桶最多8个槽位,如果key所在的tophash值在tophash中,则代表该key在这个桶中 104 | } 105 | 106 | ~~~ 107 | 108 | ~~~go 109 | type bmap struct{ 110 | tophash [8]uint8 111 | keys [8]keytype 112 | // keytype 由编译器编译时候确定 113 | values [8]elemtype 114 | // elemtype 由编译器编译时候确定 115 | overflow uintptr 116 | // overflow指向下一个bmap,overflow是uintptr而不是*bmap类型,保证bmap完全不含指针,是为了减少gc,溢出桶存储到extra字段中 117 | } 118 | ~~~ 119 | 120 | ![image-20221026092616764](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221026092616764.png) 121 | 122 | ![image-20221026092709764](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221026092709764.png) 123 | 124 | ### 4.2map的创建方式 125 | 126 | 1. 通过make创建 127 | 2. 字面量创建 128 | 129 | ### 4.3map什么时候会发生扩容 130 | 131 | 1. 当达到负载因子(load factor)的最大极限时。 132 | 2. 溢出桶(overflow buckets)过多时。 133 | 134 | ### 4.4map扩容的类型 135 | 136 | 1. **等量扩容**:数据不多但是溢出桶太多了(主要目的是对buckets中的数据进行整理),所谓等量扩容,实际上并不是扩大容量,buckets数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对 重新排列一次,以使bucket的使用率更高,进而保证更快的存取。 137 | 2. **翻倍扩容**:数据过多,当负载因子过大时,就新建一个bucket,新的bucket长度是原来的2倍,然后旧bucket数据搬迁到新的bucket。 考虑到如果map存储了数以亿计的key-value,一次性搬迁将会造成比较大的延时,Go采用逐步搬迁策略,即每次访 问map时都会触发一次搬迁,每次搬迁2个键值对。 138 | 139 | ### 4.5map扩容步骤 140 | 141 | **步骤1** 142 | 143 | 1. 创建一组新桶 144 | 2. oldbuckets指向原有的数组。 145 | 3. buckets 指向新数组。 146 | 4. 将map标记为扩容状态。 147 | 148 | **步骤2** 149 | 150 | 1. 将所有的数据从旧桶驱逐到新桶。 151 | 2. 采用渐进式驱逐。 152 | 3. 每次操作一个旧桶时,将旧桶数据驱逐到新桶。 153 | 4. 读取时不进行驱逐,只判断是读取新桶还是旧桶。 154 | 155 | **步骤3** 156 | 157 | 1. 等所有的旧桶驱逐完成后 158 | 2. 将oldbuckets进行回收。 159 | 160 | ### 4.6map的并发问题 161 | 162 | 1. map的读写具有并发问题 163 | 2. A协程在桶中读数据过程中,B协程驱逐了这个桶,则此时A协程会读到错误的数据或找不到数据。 164 | 165 | ### 4.7map并发问题的解决方案 166 | 167 | 1. 给map加锁(开销大,性能低,不提倡) 168 | 2. 使用sync.Map 169 | 1. ![image-20221026125204660](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221026125204660.png) 170 | 171 | 172 | **Map总结** 173 | 174 | 1. map在扩容时会有并发问题。 175 | 2. sync.Map使用了两个map,分离了读写问题。 176 | 3. 不会引发扩容的操作(查、改)使用 **read map** 177 | 4. 可能引发扩容的操作(新增) 使用 **dirty map** 178 | 179 | ## 5.G0,M0 180 | 181 | **M0** M0表示进程启动的第一个线程,也叫主线程。它和其他的m没有什么区别,要说区别的化,它是由进程启动直接通过汇编直接复制给M0的,这个M0是全局变量,而其他的M都是runtime内自己创建的,一个g0进程只有一个m0。 182 | 183 | **g0** 首先需要明确是每一个m都一个g0,因为每个线程都有一个系统堆栈,g0虽然也是g的结构,但和普通的g还是有区别的,最主要的差别就是在栈上的差别,g0上的栈是系统分配的栈,Linux默认是8mb,不能扩展,也不能缩小,而普通g一开始只有2kb大小,可扩展。g0上也没有任何任务函数,也没有任务状态,并且它不能被调度程序抢占。因为调度就是在g0上跑的。 184 | 185 | **总结** 186 | 187 | m0代表主线程,g0代表了线程的堆栈。调度都是在系统堆栈上的,也就是一定要跑在g0上。 188 | 189 | ## 6.GC什么会触发? 190 | 191 | **内存分配量达到阈值触发GC** 192 | 193 | 每次内存分配时都会检查当前内存分配量是否达到阈值,如果达到阈值则立即启动GC 194 | 195 | **定期触发GC** 196 | 197 | 默认情况,golang2min触发一次GC。 198 | 199 | **手动触发** 200 | 201 | 通过runtime.gc来手动触发GC。 202 | 203 | ## 7.逃逸分析 204 | 205 | 所谓逃逸分析是指由编译器决定内存分配的位置,不需要程序员指定,函数中申请一个新的对象 206 | 207 | * 如果分配在堆上,则函数执行结束交给GC来进行处理。 208 | * 如果分配在栈上,则函数执行结束可自动回收。 209 | 210 | **逃逸策略** 211 | 212 | 每当函数中申请新的对象,编译器会根据该对象是否被函数外部引用来决定是否逃逸: 213 | 214 | * 如果函数外部没有引用,则优先放在栈中。 215 | * 如果函数外部存在引用,则必定放在堆中 216 | 217 | **逃逸场景** 218 | 219 | 1. 指针逃逸 220 | 2. 栈空间不足逃逸 221 | 3. 动态类型逃逸 222 | 4. 闭包引用对象逃逸 223 | 224 | **总结** 225 | 226 | * 栈上分配要比堆上更有效率,因为栈上分配欸大不需要gc来处理,而堆上的需要。 227 | * 逃逸分析的目的是决定内存分配在栈上还是堆上。 228 | * 逃逸分析是在编译阶段完成。 229 | 230 | ## Go中常用的并发控制手段 231 | 232 | **Channel** 233 | 234 | 1. 优点:实现简单,清晰易懂。 235 | 1. 缺点:当需要大量创建协程,就需要同样数量的channel,而且对子协程派生出的协程不方便控制。 236 | 237 | **WaitGroup** 238 | 239 | ​ 子协程个数动态可调整,执行过程如下: 240 | 241 | 1. 启动goroutine前将计数器通过Add()将计数器设置为待启动的goroutine个数。 242 | 2. 启动goroutine后,使用Wait()方法阻塞自己,等待计数器变为0。 243 | 3. 每个goroutine执行结束通过Done()方法将计数器减1。 244 | 4. 计数器变为0后,阻塞的goroutine被唤醒。 245 | 246 | **Context** 247 | 248 | ​ 对子协程派生出来的孙子协程的控制方便。 249 | 250 | ## 为什么使用通信来共享内存? 251 | 252 | 1. 避免协程之间的竞争和数据的冲突 253 | 2. 更高级的抽象、降低开发难度、增加程序可读性 254 | 3. 模块之间更容易解耦合 255 | 256 | ## Channel发送的情形 257 | 258 | 1. 直接发送 259 | 2. 放入缓存 260 | 3. 休眠等待 261 | 262 | ![image-20221025133358600](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025133358600.png) 263 | 264 | ## 265 | 266 | **Channel接受的情形** 267 | 268 | 1. 有等待的G,从G接收 269 | 2. 有等待的G,从缓存接收 270 | 3. 接收缓存 271 | 4. 阻塞接收 272 | 273 | **锁的基础** 274 | 275 | 1. atomic操作 276 | 1. 原子操作是一种硬件层面加锁的机制,可以用于操作一个变量的时候,其他协程/线程没法访问,但只能用于简单变量的操作 277 | 2. sema锁(信号量锁) 278 | 1. 核心是一个uint32值,含义是同时可以并发的数量 279 | 2. 每一个sema锁对应一个SemaRoot结构体 280 | 3. 每个SemaRoot中有一个平衡二叉树用于协程排队 281 | 3. ![image-20221025135617317](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025135617317.png) 282 | 283 | 284 | 285 | **Muetex(互斥锁)** 286 | 287 | ![image-20221025135807502](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025135807502.png) 288 | 289 | 290 | 291 | ![image-20221025135907103](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025135907103.png) 292 | 293 | 294 | 295 | ![image-20221025135923549](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025135923549.png) 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | ![image-20221025140150927](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025140150927.png) 304 | 305 | 306 | 307 | 308 | 309 | ![image-20221025140207356](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025140207356.png) 310 | 311 | 312 | 313 | ![image-20221025140345961](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025140345961.png) 314 | 315 | **RWMutex(读写锁)** 316 | 317 | ![image-20221025140749588](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025140749588.png) 318 | 319 | **WaitGroup** 320 | 321 | ![image-20221025141030879](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025141030879.png) 322 | 323 | ![image-20221025141047773](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025141047773.png) 324 | 325 | **协程的底层结构** 326 | 327 | ![image-20221025141816407](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025141816407.png) 328 | 329 | **线程的抽象** 330 | 331 | ![image-20221025142011765](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025142011765.png) 332 | 333 | **单线程循环** 334 | 335 | ![image-20221025142057914](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025142057914.png) 336 | 337 | ![image-20221025142618586](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025142618586.png) 338 | 339 | ![image-20221025142819716](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221025142819716.png) 340 | -------------------------------------------------------------------------------- /Mysql/Mysql.md: -------------------------------------------------------------------------------- 1 | ## 一、如何优化Mysql 2 | 3 | ### sql语句的优化 4 | 5 | 1. 尽力避免使用子查询 6 | 2. 用In 来代替or 7 | 3. 禁止不必要的order by排序 8 | 4. 只返回必要的列,用具体的字段列表来代替select * 语句 9 | 10 | ### 索引的优化 11 | 12 | 1. 最佳左前缀法则 13 | 2. 尽量使用覆盖索引 14 | 3. 在组合/联合索引中,将有区分度的索引放在前面 15 | 4. 使用前缀索引 16 | 17 | ### 表结构的优化 18 | 19 | 1. 使得数据表的结构符合三大范式 20 | 21 | ### 分库分表 22 | 23 | **分库分表怎么设计** 24 | 25 | 分库分表方案、分库分表中间件、可能遇到的问题。 26 | 27 | ### 分库分表方案 28 | 29 | * 水平分库:以字段为依据,按照一定策略,将一个库中的数据拆分到多个库中。 30 | * 水平分表:以字段为依据,按照一定策略,将一个表中的数据拆分到多个表中。 31 | * 垂直分库:以表为依据,按照业务归属不同,将不同的表拆分到不同的库中。 32 | * 垂直分表:以字段为依据,按照字段的活跃性,将表中字段拆到不同的表(主表和扩展表)中。 33 | 34 | ### 常用分库分表中间件 35 | 36 | * sharding-jdbc(当当) 37 | * Mycat 38 | * TDDL(淘宝) 39 | * Oceanus(58同城数据库中间件) 40 | * vitess(谷歌开发的数据库中间件) 41 | * Atlas(Qihoo 360) 42 | 43 | ### 分库分表可能遇到的问题 44 | 45 | * 事务问题:需要用到分布式事务。 46 | * 数据迁移、容量规划、扩容。 47 | * 跨节点Join的问题:可以通过分两次查询实现。 48 | * 跨节点的count、order by、group by以及聚合函数问题:分别在各个节点上将得到的结果在应用程序端进行合并。 49 | 50 | ## 二、InnoDB与MyISAM的区别 51 | 52 | * InnoDB支持事务,MyISAM不支持事务。 53 | * InnoDB支持外键,MyISAM不支持外键。 54 | * InnoDB支持MVCC,MyISAM不支持。 55 | * InnoDB支持表、行级锁,MyISAM只支持表锁。 56 | * InnoDB必须有主键,MyISAM可以没有主键。 57 | * nnodb表需要更多的内存和存储,而MyISAM可被压缩,存储空间较小。 58 | * Innodb按主键大小有序插入,MyISAM记录插入顺序是,按记录插入顺序保存。 59 | 60 | ## 三、数据库索引的原理,为什么要用 B+树,为什么不用二叉树? 61 | 62 | 可以从多个维度去看这个问题: 63 | 64 | 1. 查询是否够快?是否稳定? 65 | 2. 存储数据多少,查找磁盘次数。 66 | 3. 为什么不是二叉树?为什么不是二叉平衡树?为什么不是B树,为什么不是红黑树 ,而偏偏是B+树。 67 | 68 | **为什么不是一般二叉树?** 69 | 70 | 如果使用二叉树,在最坏的情况下会退化为链表形态,变成了[顺序查找](https://so.csdn.net/so/search?q=顺序查找&spm=1001.2101.3001.7020),遍历整个链。 71 | 当数据量大的情况下,层数会不受控的增多,导致效率低下。![在这里插入图片描述](https://img-blog.csdnimg.cn/d59ad88c6ca8481aaed422bc1554b1f2.png) 72 | 73 | **为什么不是平衡二叉树**? 74 | 75 | 平衡二叉树可是每个节点只存储一个键值和数据的,会导致树的深度越来越深,意味着读取磁盘的次数越多,严重影响效率。 76 | 77 | **为什么不是红黑树** 78 | 79 | 红黑树但是一种特殊的[平衡二叉树](https://so.csdn.net/so/search?q=平衡二叉树&spm=1001.2101.3001.7020),他不会出现二叉树中的最坏情节,他会进行平衡。 80 | 但是也有一个问题,就是由于他还是属于二叉树,一个父节点只能跟着2个子节点。如果存在几千万数据,那么红黑树会非常的深同样需要大量io操作,这样一句无法快速的查找到我们想要的数据了。 81 | 82 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/eb580ac93fbb470eb3968a651f32d6c0.png) 83 | 84 | **为什么不是B树而是B+树**? 85 | 86 | 1. B树不仅存键值,也存数据;B+树非叶子节点是不存数据的,仅存储键值,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。 87 | 2. B+树索引的所有数据均存储在叶子节点,而且数据是按顺序排列的,通过双向链表相连,从而使得范围查找,排序查找,等等变得更加有效、快捷。 88 | 89 | ## 四、索引 90 | 91 | **聚簇索引和非聚簇索引** 92 | 93 | 在InnoDB,索引B+树的叶子节点存储了整行数据的是主键索引,也被称为聚簇索引,即将数据存储和索引放在了一起,找到了索引就找到了数据。 94 | 95 | 而索引B+ Tree的叶子节点存储了主键的值的是非主键索引,也被称之为非聚簇索引、二级索引。 96 | 97 | 聚簇索引和非聚簇索引的区别: 98 | 99 | 1. 非聚簇索引的叶子节点不存储表中的数据,而是存储该列对应的主键。 100 | 2. 对于InnoDB来说,想要查找数据还需要根据主键再去聚簇索引里进行查找,这个过程称为回表。 101 | 3. 通常情况,聚簇索引只会查一次,而非聚簇索引会查多次。 102 | 4. 聚簇索引中键值的逻辑顺序决定了表中相应行的物理顺序。 103 | 5. 非聚集索引,索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。 104 | 105 | MyISAM无论主键索引还是二级索引都是非聚簇索引,而InnoDB的主键索引是聚簇索引,二级索引是非聚簇索引。我们自己建的索引基本都是非聚簇索引。 106 | 107 | **非聚簇索引一定会回表查询吗?** 108 | 109 | 不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。一个索引包含(覆盖)所有需要查询字段的值,被称之为"覆盖索引"。 110 | 111 | **联合索引是什么?为什么需要主要联合索引中的顺序?** 112 | 113 | MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。 114 | 115 | 具体原因为: 116 | 117 | MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。 118 | 119 | **Mysql的最左前缀原则?** 120 | 121 | 最左前缀原则就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。 mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。 122 | 123 | **前缀索引** 124 | 125 | 因为可能我们索引的字段非常长,这既占内存空间,也不利于维护。所以我们就想,如果只把很长字段的前面的公共部分作为一个索引,就会产生超级加倍的效果。但是,我们需要注意,order by不支持前缀索引 。 126 | 127 | **怎么查看mysql语句中有没有用到索引?** 128 | 129 | explain 130 | 131 | **为什么官方建议使用自增长主键作为索引??** 132 | 133 | 结合B+Tree的特点,自增主键是连续的,可以在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率。 134 | 135 | **如何创建索引?** 136 | 137 | 1. 在执行CREATE TABLE时创建索引。 138 | 2. 使用ALTER TABLE命令去增加索引。 139 | 3. 使用CREATE INDEX命令创建。 140 | 141 | **创建索引时需要注意什么?** 142 | 143 | 可以从三个维度回答这个问题:索引哪些情况会失效,索引不适合哪些场景,索引规则。 144 | 145 | **索引那些情况会失效** 146 | 147 | * 查询条件包含or,可能导致索引失效 148 | * 如何字段类型是字符串,where时一定用引号括起来,否则索引失效 149 | * like通配符可能导致索引失效。 150 | * 联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。 151 | 152 | **索引不适合哪些场景?** 153 | 154 | * 数据量少的情况 155 | * 更新比较频繁的也不适合加索引 156 | * 区分度低的字段不适合加索引 157 | 158 | **建索引的原则有哪些?** 159 | 160 | 1. 最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。 161 | 2. =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。 162 | 3. 尽量选择区分度高的列作为索引。 163 | 4. 索引列不能参与计算。 164 | 5. 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 165 | 166 | **索引的一些潜规则** 167 | 168 | * 覆盖索引. 169 | * 回表. 170 | * 索引数据结构(b+树) 171 | * 最左前缀原则. 172 | * 索引下推. 173 | 174 | **索引的种类** 175 | 176 | 1. 从存储结构上分为 btree索引、hash索引、full-index索引 177 | 2. 从应用层来分:普通、唯一、符合 178 | 3. 根据数据的物理存储与键值的逻辑(索引) 聚簇索引、非聚簇索引 179 | 180 | ## 五、MySQL主从复制 181 | 182 | **为什么需要主从复制** 183 | 184 | 随着业务的增长,一台数据服务器已经满足不了需求了,负载过重。这个时候就需要减压了,实现负载均衡读写分离,一主一丛或一主多从。 185 | 186 | 主服务器只负责写,而从服务器只负责读,从而提高了效率减轻压力。 187 | 188 | 主从复制可以分为: 189 | 190 | - 主从同步:当用户写数据主服务器必须和从服务器同步了才告诉用户写入成功,等待时间比较长。 191 | - 主从异步:只要用户访问写数据主服务器,立即返回给用户。 192 | - 主从半同步:当用户访问写数据主服务器写入并同步其中一个从服务器就返回给用户成功。 193 | 194 | **形式** 195 | 196 | 一主一从、一主多从、多主一从、双主复制 197 | 198 | **原理** 199 | 200 | MySQL 的主从复制依赖于 binlog ,也就是记录 MySQL 上的所有变化并以二进制形式保存在磁盘上。复制的过程就是将 binlog 中的数据从主库传输到从库上。 201 | 202 | **工作过程** 203 | 204 | MySQL 的主从复制工作过程大致如下: 205 | 206 | 1. 从库生成两个线程,一个 I/O 线程,一个 SQL 线程; 207 | 2. I/O 线程去请求主库的 binlog,并将得到的 binlog 日志写到 relay log(中继日志) 文件中; 208 | 3. 主库会生成一个 log dump 线程,用来给从库 I/O 线程传 binlog; 209 | 4. SQL 线程会读取 relay log 文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致; 210 | 211 | ![img](https://dl-harmonyos.51cto.com/images/202204/5132b2c63a1331515f505679557dcf5971e8bc.png) 212 | 213 | 214 | 215 | ![MySQL 主从复制过程](https://cdn.xiaolincoding.com/gh/xiaolincoder/mysql/how_update/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E8%BF%87%E7%A8%8B.drawio.png?image_process=watermark,text_5YWs5LyX5Y-377ya5bCP5p6XY29kaW5n,type_ZnpsdHpoaw,x_10,y_10,g_se,size_20,color_0000CD,t_70,fill_0) 216 | 217 | 218 | 219 | ## MySQL优化了解吗?说一下从哪些方面可以做到性能优化? 220 | 221 | * 为搜索字段创建索引 222 | * 避免使用select*,列出需要查询的字段 223 | * 分表 224 | * 选择正确的存储引擎 225 | 226 | ## 数据库为什么要进行分库和分表呢?都放在一个库或者一张表中不可以吗? 227 | 228 | 单个数据库实例能够承载的并发访问量和数据量是有限的,当系统的并发访问量或者数据量超过这个限制时,就需要考虑使用分库分表来优化系统的架构设计,使其能够承载更大的并发量和数据量,并给用户提供良好的使用体验。当然,分库和分表其实是两个事情,两者并不是都会同时出现。 229 | 230 | ### 1. 分库解决了什么问题 231 | 232 | 分库主要解决的是**并发量大**的问题,因为单个数据库实例能够提供的连接数是有限的,当系统并发量不断增加,我们势必需要增加更多的微服务实例来承载更多的业务请求,而每个微服务实例都会占用一定量的数据库连接,因此,当数据库连接数不够用了,就只能通过增加数据库实例的方式来提供更多的数据库连接,进而提升系统整体的并发度。 233 | 234 | ### 2. 分表解决了什么问题 235 | 236 | 分表主要解决的是**数据量大**的问题,因为单表的数据量很大时,即使并发访问量不大,但单表的存储和查询的性能已经遭遇了瓶颈,通过索引优化等手段虽然能够一定程度上提升效率,但当单表数据量超过 500 万行或者单表存储容量超过 2GB(经验值,实际要看业务的具体情况) 之后,分表就应该提上日程了。一般都是将数据拆分到多张表中,来减少单表的数据量,从而提升查询的速度 237 | 238 | ## 主键索引和唯一索引的区别? 239 | 240 | * 普通索引:无任何约束作用,就是用来提高查询效率 241 | * 唯一索引:在普通索引的基础上,增加了唯一性约束,要求索引列的值唯一,但可以为空,一张表可以存在多个唯一索引。 242 | * 主键索引: 在唯一索引的基础上又增加了不能为空的约束,而且一张表最多只能有一个主键索引,但一个主键索引可以包含多个字段。 243 | * 全文索引:不经常用的 244 | 245 | ## Mysql如何实现acid? 246 | 247 | ### 何为事务? 248 | 249 | **一系列操作组成,要么全部成功,要么全部失败。它具备ACID四大特性,在并发下,可能存在脏读、幻读、不可重复读的并发问题,于是又引出了四大隔离级别** 250 | 251 | 事务具有ACID四个特性,那么事务是如何保证这4个特性的呢? 252 | 253 | 1. 原子性(A) 一系列操作要么全部成功,要么全部失败。 254 | 2. 隔离性(I) 事务的结果只有提交了其他事务才可见 255 | 3. 一致性(C) 数据库总是从一个一致状态变到另一个一致状态 256 | 4. 持久性(D) 提交事务后,对数据的修改是永久的。 257 | 258 | **如何保证原子性** 259 | 260 | 主要依靠回滚日志(undo),既可以用来实现MVCC,也可以用来保证原子性,实现原子性的关键,就在于事务执行undo操作可以撤销所有成功执行的sql语句 261 | 262 | undo log作用:undolog记录事务开始前老版本数据,用于实现回滚,保证原子性,实现MVCC,会将数据修改前的旧版本保存在undolog,然后行记录有个隐藏字段回滚指针指向老版本。 263 | 264 | **如何保证持久性** 265 | 266 | redo log 是一种物理日志,我们再更新数据库时,先将更新操作记录在redo log日志,等redo log满了或则MYSQL空闲了再刷盘。其实就是MySQL里经常说到的WAL技术,WAL的全称是Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先装小推车,等不忙的时候再装库。 267 | 268 | **如何保证隔离性** 269 | 270 | 说到隔离性,我们都知道MYSQL有四种隔离级别,用来解决存在的并发问题。脏读、幻读、不可重复读。那么不同隔离级别,隔离性是怎样实现的呢?具体实现原理是怎样的呢? 271 | 272 | 一句话:**锁+mvcc** 273 | 274 | 总之,MYSQL的隔离性便是由MVCC+锁来保证,各个隔离级别实现原理我做了一下归纳总结: 275 | 276 | 隔离级别原理及解决问题分析: 277 | 278 | 1. 读未提交:原理:直接读取数据,不能解决任何并发问题 279 | 2. 读已提交:读操作不加锁,写操作加排他锁,解决了脏读。原理:利用MVCC实现,每一句语句执行前都会生成Read View(一致性视图) 280 | 3. 可重复读:MVCC实现,只有事务开始时会创建Read View,之后事务里的其他查询都用这个Read View。解决了脏读、不可重复读,快照读(普通查询,读取历史数据)使用MVCC解决了幻读,当前读(读取最新提交数据)通过间隙锁解决幻读(lock in share mode、for update、update、detete、insert),间隙锁在可重复读下才生效。(**默认隔离级别**) 281 | 4. 可串行化:原理:使用锁,读加共享锁,写加排他锁,串行执行 282 | 283 | 总结:读已提交和可重复读实现原理就是MVCC Read View不同的生成时机。可重复读只在事务开始时生成一个Read View,之后都用的这个;读已提交每次执行前都会生成Read View 284 | 285 | **索引** 286 | 287 | **如何创建索引?** 288 | 289 | alter table 290 | 291 | create index 292 | 293 | **如何设计索引?** 294 | 295 | 一句话看性价比,总的来说就是创建索引的目的就是在尽可能占用少量内存的情况下去涉及一个合适的索引使得查询速度更快。 296 | 297 | 1. 一般建在 `where` 字段匹配条件后,为了让创建索引所带来的好处大于其坏处,我们一定要在数据量大,也就是**基数大**的情况下才考虑索引。” 298 | 2. 为了使得效率更高,应该选择**区分度大**,**匹配度高**的字段建立索引 。而且索引**不适合于频繁更新**的数据,因为操作数据同事需要维护索引又得花费时间。” 299 | 3. 觉得我们创建索引方面,我们应该尽量扩展索引,而不是创建新的索引,可以合理利用联合索引,如(a)->(a,b) 。” 300 | 301 | **索引失效** 302 | 303 | 不恰当的使用索引,不仅没有提高性能,反而占额外内存空间,影响效率 304 | 305 | 1. 比如,我们在写模糊查询时,如果以 `%`开头,索引会因此失效,为什么? 306 | 1. 我们通常用的索引数据结构是`B+树`,而索引是**有序排列**的;索引的排列顺序是根据比较字符串的首字母排序的,如果首字母相同,就根据比较第二个字母进行排序,以此类推 307 | 2. 因此如果把 `%` 放在了前面,最左的 n 个字母便是模糊不定的,无法根据索引的`有序性` 准确的定位到某一个索引,只能进行**全表扫描**,找出符合条件的数据 308 | 309 | **如何优化索引?从那些角度出发?** 310 | 311 | 索引+sql语句+数据库结构优化+优化器优化+架构优化 312 | 313 | **索引为啥不用hash实现** 314 | 315 | hash 底层是哈希表实现,等值查询,可以快速定位,一般情况效率很高,不稳定,无法用于排序分组,无法模糊查询 316 | 317 | **覆盖索引** 就是索引字段覆盖了查询语句涉及的字段,直接通过索引文件就可以返回查询所需的数据,不必通过回表操作。” 318 | 319 | **回表** 通过索引去找到主键,再根据主键 ID 去主键索引查,`InnoDB` 使用聚簇索引,它的二级索引就是这样一个模式 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | -------------------------------------------------------------------------------- /OS/os.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 记录自己学习go的过程,以及一些常见的八股文。 2 | -------------------------------------------------------------------------------- /Redis/Redis.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | 3 | ## 1. Redis是什么?简述它的优缺点? 4 | 5 | Redis本质上是一个Key-Value类型的内存数据库,整个数据库加载在内存当中操作,定期通过异步操作把数据库中的数据flush到硬盘上进行保存,因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value 数据库。 6 | 7 | **优点**: 8 | 9 | * 读写性能高 10 | * 支持数据持久化(AOF、RDB) 11 | * 支持事务 12 | * 数据结构丰富 13 | * 支持主从复制 14 | 15 | **缺点**: 16 | 17 | - 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。 18 | - 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。 19 | 20 | ## 2. Redis为什么这么快? 21 | 22 | * 内存存储:Reids是基于内存存储,没有磁盘IO的开销。 23 | * 单线程实现(6.0以前):Redis使用单个线程来处理请求,避免了多个线程之间线程切换和锁资源的开销(注意:单线程是指的是在核心网络模型中,网络请求模块使用一个线程来处理,即一个线程处理所有网络请求。) 24 | * 优化的数据结构:Redis有诸多可以直接应用的优化数据结构的实现,应用层可以直接使用原生的数据结构提升性能。 25 | * 使用底层模型不同:Redis直接自己构建了 VM (虚拟内存)机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 26 | 27 | ## 3. 为什么要用 Redis 做缓存? 28 | 29 | **从高并发上来说:** 30 | 31 | - 直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。 32 | 33 | **从高性能上来说:** 34 | 35 | - 用户第一次访问数据库中的某些数据。 因为是从硬盘上读取的所以这个过程会比较慢。将该用户访问的数据存在缓存中,下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据。 36 | 37 | ## 4. Redis的数据类型有哪些?应用场景? 38 | 39 | Redis 提供了丰富的数据类型,常见的有五种数据类型:**String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)**。 40 | 41 | - String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。 42 | - List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。 43 | - Hash 类型:缓存对象、购物车等。 44 | - Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。 45 | - Zset 类型:排序场景,比如排行榜、电话和姓名排序等。 46 | 47 | ## 5. Redis的数据类型底层实现? 48 | 49 | **String** string 类型的底层的数据结构实现主要是 int 和 SDS(简单动态字符串)。 50 | 51 | **List** List 类型的底层数据结构是由**双向链表或压缩列表**实现的: 52 | 53 | **Hash** Hash 类型的底层数据结构是由**压缩列表或哈希表**实现的: 54 | 55 | **Set** Set 类型的底层数据结构是由**哈希表或整数集合**实现的: 56 | 57 | **Zset** Zset 类型的底层数据结构是由**压缩列表或跳表**实现的: 58 | 59 | # 持久化 60 | 61 | ## 6. Redis持久化机制? 62 | 63 | 为了能够重用Redis数据,或者防止系统故障,我们需要将Redis中的数据写入到磁盘空间中,即持久化。 64 | 65 | Redis提供了两种不同的持久化方法可以将数据存储在磁盘中,一种叫快照`RDB`,另一种叫只追加文件`AOF`。 66 | 67 | **RDB** 68 | 69 | 在指定的时间间隔内将内存中的数据集快照写入磁盘(`Snapshot`),它恢复时是将快照文件直接读到内存里。 70 | 71 | **优势**:适合大规模的数据恢复;对数据完整性和一致性要求不高 72 | 73 | **劣势**:在一定间隔时间做一次备份,所以如果Redis意外`down`掉的话,就会丢失最后一次快照后的所有修改。 74 | 75 | **AOF** 76 | 77 | 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。 78 | 79 | AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时, Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.。 80 | 81 | **优势** 82 | 83 | - 每修改同步:`appendfsync always` 同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好 84 | - 每秒同步:`appendfsync everysec` 异步操作,每秒记录,如果一秒内宕机,有数据丢失 85 | - 不同步:`appendfsync no` 从不同步 86 | 87 | **劣势** 88 | 89 | - 相同数据集的数据而言`aof`文件要远大于`rdb`文件,恢复速度慢于`rdb` 90 | - `aof`运行效率要慢于`rdb`,每秒同步策略效率较好,不同步效率和`rdb`相同 91 | 92 | ## 7. 如何选择合适的持久化方式 93 | 94 | - 如果是数据不那么敏感,且可以从其他地方重新生成补回的,那么可以关闭持久化。 95 | - 如果是数据比较重要,不想再从其他地方获取,且可以承受数分钟的数据丢失,比如缓存等,那么可以只使用RDB。 96 | - 如果是用做内存数据库,要使用Redis的持久化,建议是RDB和AOF都开启,或者定期执行bgsave做快照备份,RDB方式更适合做数据的备份,AOF可以保证数据的不丢失。 97 | 98 | # 过期键的删除策略、淘汰策略 99 | 100 | ## 8. Redis过期键的删除策略 101 | 102 | **Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。** 103 | 104 | **惰性删除**:惰性删除不会去主动删除数据,而是在访问数据的时候,再检查当前键值是否过期,如果过期则执行删除并返回 null 给客户端,如果没有过期则返回正常信息给客户端。它的优点是简单,不需要对过期的数据做额外的处理,只有在每次访问的时候才会检查键值是否过期,缺点是删除过期键不及时,造成了一定的空间浪费。 105 | 106 | **定期删除**:Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。 107 | 108 | ## 9. Redis内存淘汰策略 109 | 110 | Redis是不断的删除一些过期数据,但是很多没有设置过期时间的数据也会越来越多,那么Redis内存不够用的时候是怎么处理的呢?答案就是淘汰策略。此类的 111 | 112 | 当Redis的内存超过最大允许的内存之后,Redis会触发内存淘汰策略,删除一些不常用的数据,以保证Redis服务器的正常运行。 113 | 114 | # 缓存异常 115 | 116 | 缓存异常有四种类型,分别是缓存和数据库的数据不一致、缓存雪崩、缓存击穿和缓存穿透。 117 | 118 | ## 10.如何保证缓存与数据库双写时的数据一致性? 119 | 120 | 使用到缓存,无论是本地内存做缓存还是使用 Redis 做缓存,那么就会存在数据同步的问题,因为配置信息缓存在内存中,而内存时无法感知到数据在数据库的修改。这样就会造成数据库中的数据与缓存中数据不一致的问题。 121 | 122 | 共有四种方案: 123 | 124 | 1. 先更新数据库,后更新缓存 125 | 2. 先更新缓存,后更新数据库 126 | 3. 先删除缓存,后更新数据库 127 | 4. 先更新数据库,后删除缓存 128 | 129 | 第一种和第二种方案,没有人使用的,因为第一种方案存在问题是:并发更新数据库场景下,会将脏数据刷到缓存。 130 | 131 | 第二种方案存在的问题是:如果先更新缓存成功,但是数据库更新失败,则肯定会造成数据不一致。 132 | 133 | 目前主要用第三和第四种方案。 134 | 135 | ## 11.什么是缓存击穿? 136 | 137 | 我们的业务通常会有几个数据会被频繁地访问,比如秒杀活动,这类被频地访问的数据被称为热点数据。 138 | 139 | 如果缓存中的**某个热点数据过期**了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是**缓存击穿**的问题。 140 | 141 | 应对缓存击穿可以采取前面说到两种方案: 142 | 143 | - 互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。 144 | - 不给热点数据设置过期时间,由后台异步更新缓存,或者在热点数据准备要过期前,提前通知后台线程更新缓存以及重新设置过期时间; 145 | 146 | ## 12.什么是缓存雪崩? 147 | 148 | 当**大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机**时,如果此时有大量的用户请求,都无法在 Redis 中处理,于是全部请求都直接访问数据库,从而导致数据库的压力骤增,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃,这就是**缓存雪崩**的问题。 149 | 150 | 可以看到,发生缓存雪崩有两个原因: 151 | 152 | - 大量数据同时过期; 153 | - Redis 故障宕机; 154 | 155 | ### 大量数据同时过期 156 | 157 | 针对大量数据同时过期而引发的缓存雪崩问题,常见的应对方法有下面这几种: 158 | 159 | - 均匀设置过期时间; 160 | - 互斥锁; 161 | - 双 key 策略; 162 | - 后台更新缓存; 163 | 164 | ### Redis 故障宕机 165 | 166 | 针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种: 167 | 168 | - 服务熔断或请求限流机制; 169 | - 构建 Redis 缓存高可靠集群; 170 | 171 | ## 13. 什么是缓存穿透? 172 | 173 | 当发生缓存雪崩或击穿时,数据库中还是保存了应用要访问的数据,一旦缓存恢复相对应的数据,就可以减轻数据库的压力,而缓存穿透就不一样了。 174 | 175 | 当用户访问的数据,**既不在缓存中,也不在数据库中**,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是**缓存穿透**的问题。 176 | 177 | ### 1.Redis 崩溃时,如何保证数据不丢失? 178 | 179 | Redis 是一个内存键值对数据库,如果服务器进程挂掉,内存中的数据就会丢失,为了避免数据丢失,Redis 提供了三种持久化方案: 180 | 181 | •RDB 持久化:Redis DataBase,将内存中的数据以快照(二进制)的形式保存到磁盘上,是 Redis 默认的持久化方式。执行完 RDB 持久操作后,会在指定的目录中生成一个 `dump.rdb` 文件,在 Redis 重启时,会加载 `dump.rdb` 文件来恢复数据到内存中。RDB 持久化可以通过手动和自动两种方式触发: 182 | 183 | •手动方式:同步方式 save,会阻塞 Redis 主线程;异步方式 bgsave,会 fork 一个子进程,由子进程负责 RDB 文件的操作,避免阻塞 Redis 服务主进程•自动方式:save m n,当 m 秒内数据集发生 n 次修改时,自动触发 bgsave 184 | 185 | •AOF 持久化:Append Only File,基于日志来记录 Redis 的每个写操作,每个操作会追加到文件的末尾。Redis 默认不开启 AOF。需要注意的是,AOF 是在执行完 Redis 命令才记录日志的,而不是执行之前,因为 Redis 是不会对输入的命令进行语法检查的,因此,只有真正执行完命令后,才能避免将非法的命令写入 AOF 文件中。AOF 持久化方案有三种日志写回策略 `appendfsync`: 186 | 187 | •always:同步执行日志写回,也就是在每个命令执行完之后,立即将日志写入 AOF 文件末尾•everysec:每隔一秒将 AOF 内存缓冲区中的日志刷新到磁盘中•no:Redis 只负责将日志写入到 AOF 内存缓冲区中,由操作系统的刷盘机制决定什么时候写入磁盘 188 | 189 | •混合持久化:RDB 方式的优点是文件相比 AOF 小,数据恢复快,适合大规模数据恢复场景,例如数据备份等;AOF 的优点是数据一致性和完整性相比 RDB 高,通常使用 everysec 写回策略保证只有秒级的数据丢失。为了中和两者的优缺点,Redis 4.0 引入了混合持久化,也就是在两次 RDB 持久化中间,会增加 AOF 操作来记录这段时间的日志。 190 | -------------------------------------------------------------------------------- /计网/计网.md: -------------------------------------------------------------------------------- 1 | ### HTTP 常见的状态码有哪些? 2 | 3 | `2xx` 类状态码表示服务器**成功**处理了客户端的请求,也是我们最愿意看到的状态。 4 | 5 | `3xx` 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是**重定向**。 6 | 7 | - 「**301 Moved Permanently**」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。 8 | - 「**302 Found**」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。 9 | 10 | `4xx` 类状态码表示客户端发送的**报文有误**,服务器无法处理,也就是错误码的含义。 11 | 12 | - 「**403 Forbidden**」表示服务器禁止访问资源,并不是客户端的请求出错。 13 | - 「**404 Not Found**」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。 14 | 15 | `5xx` 类状态码表示客户端请求报文正确,但是**服务器处理时内部发生了错误**,属于服务器端的错误码。 16 | 17 | - 「**501 Not Implemented**」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。 18 | - 「**502 Bad Gateway**」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。 19 | - 「**503 Service Unavailable**」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。 20 | 21 | ### 说一下GET和POST的区别? 22 | 23 | **Get方法**是请求**从服务器获取资源**,这个资源可以是静态的文本、页面、图片视频等。(获取数据) 24 | 25 | **Post方法**是相反操作,它是向**URL**指定的资源提交数据,数据方法在报文的body里。(修改数据) 26 | 27 | **本质区别**: 28 | 29 | **Get方法是安全且幂等**。因为它是只读操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。 30 | 31 | Post因为是**新增或提交数据**的操作,会修改服务器上的资源,所以是**不安全**的,且多次提交数据就会创建多个资源,所以不是幂等的。 32 | 33 | 拓展知识: 34 | 35 | 在 HTTP 协议⾥,所谓的「安全」是指请求⽅法不会「破坏」服务器上的资源。 36 | 37 | 所谓的「幂等」,意思是多次执⾏相同的操作,结果都是「相同」的。 38 | 39 | ## 在浏览器中输入url地址后显示主页的过程? 40 | 41 | - 根据域名,进行DNS域名解析; 42 | - 拿到解析的IP地址,建立TCP连接; 43 | - 向IP地址,发送HTTP请求; 44 | - 服务器处理请求; 45 | - 返回响应结果; 46 | - 关闭TCP连接; 47 | - 浏览器解析HTML; 48 | - 浏览器布局渲染; 49 | 50 | ## TCP和UDP的区别 51 | 52 | 1. TCP面向连接,UDP无连接 53 | 2. TCP提供可靠的服务(无差错,不丢失,不重复),UDP尽最大努力交付(不可靠) 54 | 3. TCP面向字节流、UDP面向报文。 55 | 4. TCP只能是点到点的一对一,而UDP是一对多,多对一、多对多 56 | 5. TCP开销大,UDP开销小。 57 | 58 | ## TCP对应的应用层协议 59 | 60 | * FTP:定义了文件传输协议 21 61 | * Telnet:它是一种用于远程登陆的端口,23端口 62 | * SMTP:定义了简单邮件传送协议,服务器开放的是25号端口 63 | * POP3:它是和SMTP对应,POP3用于接收邮件 64 | 65 | ## UDP对应的应用层协议 66 | 67 | * DNS:用于域名解析服务,用的是53号端口 68 | * SNMP:简单网络管理协议,使用161号端口 TFTP 69 | 70 | ## 数据链路层常见协议?可以说一下吗? 71 | 72 | | 协议 | 名称 | 作用 | 73 | | ---- | ---------------- | ------------------------------------------------------------ | 74 | | ARP | 地址解析协议 | 根据IP地址获取物理地址 | 75 | | RARP | 反向地址转换协议 | 根据物理地址获取IP地址 | 76 | | PPP | 点对点协议 | 主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案 | 77 | 78 | ## HTTP长连接和短连接的区别 79 | 80 | 短连接:客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。 81 | 82 | ``` 83 | 短连接的操作步骤是: 84 | 建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接 85 | ``` 86 | 87 | 长连接:当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。 88 | 89 | ``` 90 | 建立连接——数据传输...(保持连接)...数据传输——关闭连接 91 | ``` 92 | 93 | **长连接**可以**省去较多的TCP建立和关闭的操作,减少浪费,节约时间**。 94 | 95 | **短连接**对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。 96 | 97 | ## 什么是SSL/TLS ? 98 | 99 | SSL代表安全套接字层。它是一种用于加密和验证应用程序(如浏览器)和Web服务器之间发送的数据的协议。 100 | 101 | ## 说一下一次完整的HTTP请求过程包括哪些内容? 102 | 103 | 域名解析 --> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求,浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户。 104 | 105 | ## DNS的工作原理? 106 | 107 | 将主机域名转换为ip地址,属于应用层协议,使用UDP传输。 108 | 109 | ![image-20220920164819984](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220920164819984.png) 110 | 111 | ## HTTPS和HTTP的区别 112 | 113 | 1、HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全, HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。 114 | 115 | 2、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。 116 | 117 | 3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。 118 | 119 | ## 为何需要把 TCP/IP 协议栈分成 5 层(或7层)?开放式回答 120 | 121 | 1. 灵活性好 122 | 2. 易于实现和维护 123 | 3. 层与层之间是独立的 124 | 125 | ## Session是什么? 126 | 127 | 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 128 | 129 | Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 130 | 131 | ## Session知识大总结 132 | 133 | 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 134 | 135 | Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。 136 | 137 | 使用 Session 维护用户登录状态的过程如下: 138 | 139 | 1. 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; 140 | 2. 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID; 141 | 3. 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中; 142 | 4. 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。 143 | 144 | session 的工作原理是客户端登录完成之后,服务器会创建对应的 session,session 创建完之后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 之后,在内存找到与之对应的 session 这样就可以正常工作了。 145 | 146 | ## Cookie与Session的对比 147 | 148 | TCP三次握手 149 | 150 | ![image-20221026091956184](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221026091956184.png) 151 | 152 | TCP四次挥手 153 | 154 | ![image-20221026091859708](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221026091859708.png) 155 | --------------------------------------------------------------------------------