├── .gitignore ├── linux └── linux.md ├── golang ├── sources │ ├── images │ │ ├── 1_range_map.png │ │ ├── 2_func_reload.png │ │ └── 1_range_map_res.png │ └── code │ │ ├── range_map │ │ └── range_map.go │ │ └── func_reload │ │ └── func_reload.go └── golang.md ├── interview ├── sources │ └── images │ │ ├── index_三次握手.png │ │ ├── index_二叉树.png │ │ ├── index_关系表.png │ │ ├── index_分代回收.png │ │ ├── index_分段机制.png │ │ ├── index_分页机制.png │ │ ├── index_协议表.png │ │ ├── index_四次挥手.png │ │ ├── index_异步IO.png │ │ ├── index_阻塞IO.png │ │ ├── index_url历程.png │ │ ├── index_xpath语法.png │ │ ├── index_伯乐网络结构.png │ │ ├── index_多路复用IO.png │ │ ├── index_排序复杂度.jpg │ │ ├── index_数据库分类.png │ │ ├── index_非阻塞IO.png │ │ ├── index_xpath_谓语.png │ │ ├── index_xpath语法_.png │ │ └── index_redis与memcached.png └── index.md ├── k8sandkubeflow ├── sources │ └── images │ │ ├── cuda1.jpg │ │ ├── cuda2.jpg │ │ ├── cudnn1.png │ │ ├── cudnn2.png │ │ ├── cudnn3.png │ │ ├── nvida_web.png │ │ ├── notebook_view.png │ │ └── 企业微信截图_15553274233731.png └── k8sandkubeflow.md ├── deeplearning ├── deeplearning.md └── deeplearning.html └── databases ├── 从零开始写数据库.md └── 从零开始写数据库.html /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /linux/linux.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /golang/sources/images/1_range_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/golang/sources/images/1_range_map.png -------------------------------------------------------------------------------- /golang/sources/images/2_func_reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/golang/sources/images/2_func_reload.png -------------------------------------------------------------------------------- /interview/sources/images/index_三次握手.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_三次握手.png -------------------------------------------------------------------------------- /interview/sources/images/index_二叉树.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_二叉树.png -------------------------------------------------------------------------------- /interview/sources/images/index_关系表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_关系表.png -------------------------------------------------------------------------------- /interview/sources/images/index_分代回收.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_分代回收.png -------------------------------------------------------------------------------- /interview/sources/images/index_分段机制.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_分段机制.png -------------------------------------------------------------------------------- /interview/sources/images/index_分页机制.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_分页机制.png -------------------------------------------------------------------------------- /interview/sources/images/index_协议表.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_协议表.png -------------------------------------------------------------------------------- /interview/sources/images/index_四次挥手.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_四次挥手.png -------------------------------------------------------------------------------- /interview/sources/images/index_异步IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_异步IO.png -------------------------------------------------------------------------------- /interview/sources/images/index_阻塞IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_阻塞IO.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/cuda1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/cuda1.jpg -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/cuda2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/cuda2.jpg -------------------------------------------------------------------------------- /golang/sources/images/1_range_map_res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/golang/sources/images/1_range_map_res.png -------------------------------------------------------------------------------- /interview/sources/images/index_url历程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_url历程.png -------------------------------------------------------------------------------- /interview/sources/images/index_xpath语法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_xpath语法.png -------------------------------------------------------------------------------- /interview/sources/images/index_伯乐网络结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_伯乐网络结构.png -------------------------------------------------------------------------------- /interview/sources/images/index_多路复用IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_多路复用IO.png -------------------------------------------------------------------------------- /interview/sources/images/index_排序复杂度.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_排序复杂度.jpg -------------------------------------------------------------------------------- /interview/sources/images/index_数据库分类.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_数据库分类.png -------------------------------------------------------------------------------- /interview/sources/images/index_非阻塞IO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_非阻塞IO.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/cudnn1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/cudnn1.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/cudnn2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/cudnn2.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/cudnn3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/cudnn3.png -------------------------------------------------------------------------------- /interview/sources/images/index_xpath_谓语.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_xpath_谓语.png -------------------------------------------------------------------------------- /interview/sources/images/index_xpath语法_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_xpath语法_.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/nvida_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/nvida_web.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/notebook_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/notebook_view.png -------------------------------------------------------------------------------- /interview/sources/images/index_redis与memcached.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/interview/sources/images/index_redis与memcached.png -------------------------------------------------------------------------------- /k8sandkubeflow/sources/images/企业微信截图_15553274233731.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JK-97/my_note/HEAD/k8sandkubeflow/sources/images/企业微信截图_15553274233731.png -------------------------------------------------------------------------------- /deeplearning/deeplearning.md: -------------------------------------------------------------------------------- 1 | --- 2 | export_on_save: 3 | html: true 4 | html: 5 | toc: true 6 | offline: true 7 | toc: 8 | depth_from: 1 9 | depth_to: 3 10 | ordered: false 11 | 12 | --- 13 | # 深度学习 -------------------------------------------------------------------------------- /golang/golang.md: -------------------------------------------------------------------------------- 1 | # golang 2 | 3 | 4 | ##### 1.golang range map 获取的是值的备份,都是同一个指针 5 | ![1_range_map.png ](https://jk-97.github.io/my_note/golang/sources/images/1_range_map.png) 6 | **result:** 7 | ![1_range_map_res.png ](https://jk-97.github.io/my_note/golang/sources/images/1_range_map_res.png) 8 | **solve** 9 | 需要在range体内声明一个变量来存储value,再将指针赋予map -------------------------------------------------------------------------------- /golang/sources/code/range_map/range_map.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type student struct { 8 | Name string 9 | Age int 10 | } 11 | 12 | func main() { 13 | m := make(map[string]*student) 14 | stus := []student{ 15 | {Name: "zhou", Age: 24}, 16 | {Name: "li", Age: 23}, 17 | {Name: "wang", Age: 22}, 18 | } 19 | for _, stu := range stus { 20 | m[stu.Name] = &stu 21 | } 22 | fmt.Println(m) 23 | 24 | m["zhou"] = &stus[0] 25 | m["li"] = &stus[1] 26 | m["wang"] = &stus[2] 27 | fmt.Println(m) 28 | } 29 | -------------------------------------------------------------------------------- /golang/sources/code/func_reload/func_reload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type People struct{} 6 | 7 | func (p *People) ShowA() { 8 | fmt.Println("showA") 9 | p.ShowB() 10 | } 11 | func (p *People) ShowB() { 12 | fmt.Println("showB") 13 | } 14 | 15 | type Teacher struct { 16 | People 17 | } 18 | 19 | func (t *Teacher) ShowB() { 20 | fmt.Println("teacher showB") 21 | } 22 | 23 | func main() { 24 | t := Teacher{} 25 | t.ShowA() 26 | x := "Aaaaaa" 27 | for index, key := range x { 28 | fmt.Println(index, key) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /databases/从零开始写数据库.md: -------------------------------------------------------------------------------- 1 | --- 2 | export_on_save: 3 | html: true 4 | html: 5 | toc: true 6 | offline: true 7 | toc: 8 | depth_from: 1 9 | depth_to: 3 10 | ordered: false 11 | 12 | --- 13 | 14 | # 从零开始----nosql数据库(golang ) 15 | ## 数据库的需求 16 | * 需要有索引,快速地找到数据 17 | * 需要满足ACID 18 | 19 | 20 | ## 学习路线 21 | 我们首先不先讲什么理论知识,这些东西对于一点都没有概念的人来说会感觉很抽象。现在的开源社区这么多的项目,这些项目就是很好的学习资源。这里先由我通过分析开源项目的源码,来一步一步剖析原理,编写一个简单的数据库需要什么准备。 22 | ## nustdb源码分析 23 | **https://github.com/xujiajun/nutsdb** 24 | 这个一个用golang编写的k/v数据库,该仓库的的社区还持续更新着。 25 | 我们先把这个repo克隆下来,跟着开源作者的思路来一步一步构建数据库的概念。chekout第一次提交。 26 | 27 | ```shell 28 | git clone https://github.com/xujiajun/nutsdb.git 29 | git checkout 7c0a5b6 30 | ``` 31 | 那我们就可以看到以下目录结构(将无关的文件忽略掉了),先大概讲下这里的文件的作用 32 | ```go 33 | ├── bptree.go # 写了一些B+树的操作 34 | ├── datafile.go # 写了存放数据文件抽象 35 | ├── db.go # 数据库的抽象 36 | ├── entry.go # 数据包装 37 | ├── options.go # 数据库的配置选项 38 | ├── tx.go # 事务的包装 39 | └── utils.go 40 | ``` 41 | 那么我们知道的了它们的调用关系 42 | ```bash 43 | options -> db -> tx -> entry -> datafile 44 | -> bptree 45 | ``` 46 | 在这个commit的文件中,我们可以看到example文件夹,我们选择 example/basic/main.go作为分析的大门。 47 | 函数 :db.update() 48 | ```go 49 | func put() { 50 | if err := db.Update( 51 | func(tx *nutsdb.Tx) error { 52 | key := []byte("name1") 53 | val := []byte("val1") 54 | if err := tx.Put(bucket, key, val, 0); err != nil { 55 | return err 56 | } 57 | return nil 58 | }); err != nil { 59 | log.Fatal(err) 60 | } 61 | } 62 | ``` 63 | 函数 :db.Update -> db.managed() ;看到这段代码我 们就大概知道nutsdb数据库写操作时的思路了。看下面的代码注释,主要分为了3步: 64 | * 创建事务 65 | * 预执行 66 | * 提交修改 67 | ```go 68 | // managed calls a block of code that is fully contained in a transactio 69 | func (db *DB) managed(writable bool, fn func(tx *Tx) error) (err error) { 70 | # writeable 是否可写 ,需要对数据库进行修改时 传参为true 71 | var tx *Tx 72 | tx, err = db.Begin(writable) 73 | # 1->创建事务,并且加锁,保证当前事务独占 74 | if err != nil { 75 | return 76 | } 77 | 78 | if err = fn(tx); err != nil { 79 | # 2->执行数据库的操作,还没写入文件,这时执行put操作,用一个切片进行保存 80 | err = tx.Rollback() 81 | # 操作失败,进行回滚 82 | return 83 | } 84 | 85 | if err = tx.Commit(); err != nil { 86 | # 3->提交修改,将切片的数据写入文件,并创建索引 87 | err = tx.Rollback() 88 | return 89 | } 90 | 91 | return 92 | } 93 | ``` 94 | 下面们分析下这3个步里具体做了什么? 95 | 96 | __1--> db.Begin()__ 97 | ```go 98 | # tx的结构题 ->代表事务 99 | type Tx struct { 100 | id uint64 # 事务id 101 | db *DB # 事务所执行的db 102 | writable bool # 是否有写操作 103 | pendingWrites []*Entry # 等待写入的数据 104 | } 105 | 106 | func (db *DB) Begin(writable bool) (tx *Tx, err error) { 107 | tx, err = newTx(db, writable) 108 | # 创建事务实例 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | tx.lock() 114 | # 加上事务锁 115 | if db.closed { 116 | tx.unlock() 117 | return nil, ErrDBClosed 118 | } 119 | 120 | return 121 | } 122 | ``` 123 | __2--> db.Put()__ 124 | 第二步是参数传入的函数,即db.update() 里的函数。主要就是创建一对键值数据,然后调用db.Put()函数。 125 | ```go 126 | func (tx *Tx) put(bucket string, key, value []byte, ttl uint32, flag uint16, timestamp uint64) error { 127 | # bucket 相当于mongodb 的collection ,可称表名 128 | # key,value 即将存入的数据 129 | # ttl 过期时间 130 | # timestamp 时间戳 131 | if err := tx.checkTxIsClosed(); err != nil { 132 | return err 133 | } 134 | 135 | if !tx.writable { 136 | return ErrTxNotWritable 137 | } 138 | 139 | if len(key) == 0 { 140 | return ErrKeyEmpty 141 | } 142 | # entry 是每一条数据的包装,将准备要写的数据其append到tx.pendingWrites 待写 143 | tx.pendingWrites = append(tx.pendingWrites, &Entry{ 144 | Key: key, 145 | Value: value, 146 | Meta: &MetaData{ 147 | keySize: uint32(len(key)), 148 | valueSize: uint32(len(value)), 149 | timestamp: timestamp, 150 | Flag: flag, 151 | TTL: ttl, 152 | bucket: []byte(bucket), 153 | bucketSize: uint32(len(bucket)), 154 | status: UnCommitted, 155 | txId: tx.id, 156 | }, 157 | }) 158 | 159 | return nil 160 | } 161 | ``` 162 | 163 | ```go 164 | // 5. Unlock the database and clear the db field. 165 | func (tx *Tx) Commit() error { 166 | # 在函数进行写入操作 167 | var e *Entry 168 | 169 | if tx.db == nil { 170 | return ErrDBClosed 171 | } 172 | 173 | writesLen := len(tx.pendingWrites) 174 | 175 | if writesLen == 0 { 176 | tx.unlock() 177 | tx.db = nil 178 | return nil 179 | } 180 | 181 | for i := 0; i < writesLen; i++ { 182 | entry := tx.pendingWrites[i] 183 | entrySize := entry.Size() 184 | if entrySize > tx.db.opt.SegmentSize { 185 | # 判断每条数据的大小是否超过设定的段大小 186 | return ErrKeyAndValSize 187 | } 188 | 189 | if tx.db.ActiveFile.ActualSize+entrySize > tx.db.opt.SegmentSize { 190 | # 如果当前存储的.dat文件超过大小,则需要进行rotate操作,换一个文件写入 191 | if err := tx.rotateActiveFile(); err != nil { 192 | return err 193 | } 194 | } 195 | 196 | if i == writesLen-1 { 197 | # 更改数据的状态 198 | entry.Meta.status = Committed 199 | } 200 | 201 | off := tx.db.ActiveFile.writeOff 202 | # 当前写入文件的末尾byte数 203 | if _, err := tx.db.ActiveFile.WriteAt(entry.Encode(), off); err != nil { 204 | # 写入当前数据库文件的末尾 205 | return err 206 | } 207 | 208 | tx.db.ActiveFile.ActualSize += entrySize 209 | tx.db.ActiveFile.writeOff += entrySize 210 | # 更新数据库文件的大小信息 , 末尾位置 211 | 212 | if tx.db.opt.EntryIdxMode == HintAndRAMIdxMode { 213 | entry.Meta.status = Committed 214 | e = entry 215 | } else { 216 | e = nil 217 | } 218 | 219 | countFlag := CountFlagEnabled 220 | if tx.db.opt.IsMerging { 221 | countFlag = CountFlagDisabled 222 | } 223 | bucket := string(entry.Meta.bucket) 224 | if _, ok := tx.db.HintIdx[bucket]; !ok { 225 | # 若当前表么有建立索引,则建立一个 226 | tx.db.HintIdx[bucket] = NewTree() 227 | } 228 | _ = tx.db.HintIdx[bucket].Insert(entry.Key, e, &Hint{ 229 | # 插入索引,将数据的位置记录到b+树的数据结构。中 230 | fileId: tx.db.ActiveFile.fileId, 231 | # 数据库源文件的id,对应哪个文件 232 | key: entry.Key, 233 | meta: entry.Meta, 234 | dataPos: uint64(off), 235 | # 直接指向记录在数据库文件的位置,加快了检索速度 236 | }, countFlag) 237 | tx.db.KeyCount++ 238 | } 239 | 240 | tx.unlock() 241 | 242 | tx.db = nil 243 | 244 | return nil 245 | } 246 | ``` 247 | 248 | 249 | ## 小结(编写数据库需要什么知识) 250 | 通过上面的分析,我们可以得出,编写一个最简单数据库需要一些最基本的知识 251 | 252 | * 索引数据结构(b+树,hash索引,lsm树) 253 | 254 | 255 | ## 未来优化的方向,以及所需的知识 256 | * 目前是不能保证并发时ACID性,会出现脏读幻读等错误。 257 | * 随机读写时,速度会相对顺序读写要慢很多,所以需要了解一些硬盘读写的基本知识,或其他的数据结构(当前比较就行的就是LSM树)。 258 | * 存储的数据没有进行压缩,导致很冗余数据会占用大量的存储空间。 -------------------------------------------------------------------------------- /deeplearning/deeplearning.html: -------------------------------------------------------------------------------- 1 | 2 | deeplearning 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 262 | 263 | 264 |
265 |

深度学习

266 | 267 | 268 |
269 |
272 |
273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /interview/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | export_on_save: 3 | html: true 4 | html: 5 | toc: true 6 | offline: true 7 | toc: 8 | depth_from: 1 9 | depth_to: 3 10 | ordered: false 11 | 12 | --- 13 | # 欢迎来到我的笔记 14 | --- 15 | # 1.python语言 16 | 17 | 18 | ## 1.1.python语言特点 19 | **动态语言** 20 | * 编译器还是运行期确定类型 21 | * python是在运行期确定类型的 22 | 23 | **强类型** 24 | * 会不会发生隐式转换 25 | * python是强类型语言 26 | 27 | **优缺点** 28 | * 胶水语言,轮子多,应用广泛 29 | * 语言灵活,生产力高 30 | * 性能问题,代码维护,python2/3差异 31 | 32 | **拥有自省功能** 33 | * 在运行时能够获得对象的类型 34 | * type(),判断对象类型 35 | * dir(), 带参数时获得该对象的所有属性和方法;不带参数时,返回当前范围内的变量、方法和定义的类型列表 36 | * isinstance(),判断对象是否是已知类型 37 | * hasattr(),判断对象是否包含对应属性 38 | * getattr(),获取对象属性 39 | * setattr(), 设置对象属性 40 | 41 | **猴子补丁** 42 | * 猴子补丁是程序在本地扩展或修改支持系统软件的方式(仅影响程序的运行实例) 43 | * 所谓mankey patch 就是运行时替换 44 | * 比如gevent库需要修改内置的socket 45 | * from gevent import monkey; 46 | monkey.patch_socket() 47 | 将阻塞soket替换成非阻塞socket 48 | 49 | **鸭子类型** 50 | * 如果里看到一个鸟,走起来像鸭子,叫起来像鸭子,那么它就是鸭子 51 | * 更关注接口 52 | --- 53 | ## 1.2.python2/3差异 54 | **不同** 55 | * print成为了函数 56 | * 不再有Unicode,默认str就是nuicode 57 | * 除法,除号返回浮点数 58 | * 优化super函数 59 | ```python 60 | retru super(C,self).func()#py2 61 | return super().func()#py3 62 | ``` 63 | * keyword only argument限定关键字参数 64 | ```python 65 | def add(a,b,*,c): 66 | pass 67 | def add(**kwargs): 68 | pass 69 | ``` 70 | * 高级解包操作,a,b,*rest = range(10) 71 | * 类型注解:type hint:def hello(name:str) ->str: 72 | * chaied exception, py3重新抛出异常不会丢失栈信息 73 | * 一切返回迭代器 range,zip,map,dict,values,etc,are all iterators 74 | 75 | **新增** 76 | * yeld from 连接 77 | * asyncio,async/wait 原生协程支持异步编程 78 | * 新增enum,mock,asyncio,ipaddress,concurrent.futures 79 | * 生成的pyc文件统一放到__pycache__ 80 | * 内置库修改,urllib,selector 81 | 82 | **兼容2/3的代码** 83 | * six 84 | * 2to3等工具转换代码格式 85 | * \_\_future__ 86 | 87 | **可变/不可变对象** 88 | * 不可变对象:bool/int/float/tuple/str/frozenset 89 | * 可变对象:list/set/dict 90 | 91 | 92 | 93 | 94 | --- 95 | # 2.算法与数据结构 96 | 97 | ## 2.1.排序算法 98 | **快速排序算法** 99 | 100 | ```python {.line-numbers} 101 | def quicksort(array): 102 | if len(array) < 2: 103 | return array 104 | else: 105 | pivot_index = 0 #第一个元素作为主元 106 | pivot = array[pivot_index] 107 | less_part = [i for i in array[pivot_index + 1:] if i <= pivot] 108 | great_part = [i for i in array[pivot_index + 1:] if i > pivot] 109 | return quicksort(less_part) + [pivot] + quicksort(great_part) 110 | def test_quicksort(): 111 | import random 112 | l1 = [range(10)] 113 | random.shuffle(l1) 114 | assert quicksort(l1) == sorted(l1) 115 | 116 | 117 | test_quicksort() 118 | ``` 119 | --- 120 | **归并排序算法算法** 121 | ```python {.line-numbers} 122 | 123 | def merge_sorted_seq(sorted_a, sorted_b): 124 | length_a, length_b = len(sorted_a), len(sorted_b) 125 | a = b = 0 126 | new_sorted_seq = [] 127 | while a < length_a and b < length_b: 128 | if sorted_a[a] < sorted_b[b]: 129 | new_sorted_seq.append(sorted_a[a]) 130 | a += 1 131 | else: 132 | new_sorted_seq.append(sorted_b[b]) 133 | b += 1 134 | if a < length_a: 135 | new_sorted_seq.extend(sorted_a[a:]) 136 | else: 137 | new_sorted_seq.extend(sorted_b[b:]) 138 | return new_sorted_seq 139 | 140 | 141 | def test_merge_sorted_seq(): 142 | a = [1, 2, 5] 143 | b = [0, 3, 4, 8] 144 | print(merge_sorted_seq(a, b)) 145 | 146 | 147 | def merge_sort(array): 148 | if len(array) <= 1: 149 | return array 150 | else: 151 | mid = int(len(array) / 2) 152 | left_array = merge_sort(array[:mid]) 153 | right_array = merge_sort(array[mid:]) 154 | return merge_sorted_seq(left_array, right_array) 155 | 156 | 157 | def test_merge_sort(): 158 | import random 159 | l1 = list(range(10)) 160 | random.shuffle(l1) 161 | assert merge_sort(l1) == sorted(l1) 162 | 163 | 164 | test_merge_sort() 165 | 166 | ``` 167 | > 由于一次面试没答出来,回来恶补了一下 168 | > ![排序算法复杂度](https://jk-97.github.io/my_note/interview/sources/images/index_排序复杂度.jpg) 169 | 170 | --- 171 | ## 2.2.数据结构 172 | 173 | **链表** 174 | ```python 175 | class Solution: 176 | def reverseList(self, head: ListNode) -> ListNode: 177 | pre =None 178 | cur = head 179 | while cur: 180 | nextnode = cur.next 181 | cur.next = pre 182 | pre = cur 183 | cur = nextnode 184 | return pre 185 | 186 | ``` 187 | **队列** 188 | ```python 189 | from collections import deque 190 | 191 | 192 | class Queue: 193 | def __init__(self): 194 | self.item = deque() 195 | 196 | def append(self, val): 197 | return self.item.append(val) 198 | 199 | def pop(self): 200 | return self.item.popleft() 201 | 202 | def empty(self): 203 | return len(self.item) == 0 204 | 205 | ``` 206 | **栈** 207 | 208 | ```python 209 | class MinStack: 210 | 211 | def __init__(self): 212 | """ 213 | initialize your data structure here. 214 | """ 215 | self.stack=[] 216 | 217 | def push(self, x: int) -> None: 218 | self.stack.append(x) 219 | def pop(self) -> None: 220 | self.stack.pop() 221 | 222 | def top(self) -> int: 223 | return self.stack[-1] 224 | 225 | def getMin(self) -> int: 226 | return min(self.stack) 227 | 228 | 229 | # Your MinStack object will be instantiated and called as such: 230 | # obj = MinStack() 231 | # obj.push(x) 232 | # obj.pop() 233 | # param_3 = obj.top() 234 | # param_4 = obj.getMin() 235 | ``` 236 | 237 | 238 | **树** 239 | ```python 240 | 241 | # Definition for a binary tree node. 242 | # class TreeNode: 243 | # def __init__(self, x): 244 | # self.val = x 245 | # self.left = None 246 | # self.right = None 247 | 248 | class Solution: 249 | def maxDepth(self, root: TreeNode) -> int: 250 | stack = [] 251 | if root is not None: 252 | stack.append((1, root)) 253 | 254 | depth = 0 255 | while stack != []: 256 | current_depth, root = stack.pop() 257 | if root is not None: 258 | depth = max(depth, current_depth) 259 | stack.append((current_depth + 1, root.left)) 260 | stack.append((current_depth + 1, root.right)) 261 | 262 | return depth 263 | ``` 264 | # 3.编程范式 265 | ## 3.1.装饰器 266 | **函数装饰器** 267 | ```python 268 | import time 269 | 270 | 271 | def log_time(func): 272 | def _log(*args, **kwargs): 273 | beg = time.time() 274 | res = func(*args, **kwargs) 275 | print('use time: {}'.format(time.time() - beg)) 276 | return res 277 | 278 | return _log 279 | 280 | 281 | @log_time 282 | def mysleep(): 283 | time.sleep(1) 284 | 285 | 286 | mysleep() 287 | ``` 288 | **类装饰器** 289 | ```python 290 | import time 291 | 292 | class LogTime: 293 | def __call__(self,func): 294 | def _log(*args, **kwargs): 295 | beg = time.time() 296 | res = func(*args, **kwargs) 297 | print('use time: {}'.format(time.time() - beg)) 298 | return res 299 | 300 | return _log 301 | 302 | 303 | @LogTime() 304 | def mysleep(): 305 | time.sleep(1) 306 | 307 | 308 | mysleep() 309 | ``` 310 | 311 | ## 3.2.面向对象 312 | **概念** 313 | * 把对象作为基本单元,把对象抽象成类 314 | * 数据封装,继承,多态 315 | 316 | **举个例子:** 317 | ```python 318 | class person(object): #py3可以直接class person 319 | def __init__(self,name,age): 320 | self.name = name 321 | self.age = age 322 | 323 | def print_name(self): 324 | print("my name is {}".format(self.name)) 325 | ``` 326 |   方法中我们可以看到方法的参数中都带有self这个关键字,这么的意义是指,这些方法都只能通过类创建的对象调用,self代表对象自己。我们称之为”方法“。而那些如self.attr_name的,我们成为实例属性 327 | **组合与继承** 328 | * 组合是使用其他的类实例作为自己的一个属性(Has-a关系) 329 | * 子类继承父类的属性和方法(Is-a) 330 | * 优先使用组合保持代码简单 331 | 332 | **类变量和实例变量** 333 | * 类变量由所有实例共享 334 | * 实例变量单独享有,不同实例之间不影响 335 | * 当我们需要在一个类的不同实例之间共享变量的时候使用类变量 336 | 337 | **方法装饰器** 338 | * 都可以通过Class.method()的方式使用 339 | * classmethod第一个参数是cls,可以引用类变量 340 | * staticmethod使用起来和普通函数,只不过放在类里去组织 341 | 342 | **元类** 343 | 344 | ***元类是创建类的类*** 345 | * 元类允许我们控制类的生成,比如修改类的属性 346 | * 使用type来定义元类 347 | * 也可使用metaclass属性来替换元类 348 | * 元类最常见的一个使用场景就是ORM框架 349 | ```python 350 | #观看文档这两种定义是等价的。 351 | >>> class X: 352 | ... a = 1 353 | ... 354 | >>> X = type('X', (object,), dict(a=1)) 355 | ``` 356 | 357 | ```python 358 | class Base: 359 | pass 360 | 361 | class Child(Base): 362 | pass 363 | 364 | #等价定义注意Base后要加逗号否证就不是tuple了 365 | SameChild = type('Child',(Base,)m|{}) 366 | #此操作与上无区别 367 | 368 | #加上方法 369 | class ChildWithMethod(Base): 370 | bar = True 371 | 372 | def hello(self): 373 | print('hello') 374 | def hello(): 375 | print('hello') 376 | 377 | type('ChildWithMethod',(Base,),{'bar':Ture,'hello':hello}) 378 | ``` 379 | 380 | --- 381 | # 4.网络协议 382 | 383 | 384 | ## 4.1.基本概念 385 | **协议表** 386 | > ![index_协议表 ](https://jk-97.github.io/my_note/interview/sources/images/index_协议表.png) 387 | 388 | **浏览器输入一个url中间经历的过程** 389 | > ![index_url历程](https://jk-97.github.io/my_note/interview/sources/images/index_url历程.png) 390 | 391 | **TCP三次握手** 392 | > ![index_三次握手](https://jk-97.github.io/my_note/interview/sources/images/index_三次握手.png) 393 | 394 | **TCP四次挥手** 395 | > ![index_四次挥手 ](https://jk-97.github.io/my_note/interview/sources/images/index_四次挥手.png) 396 | 397 | [reference:https://www.cnblogs.com/huangjianping/p/7998067.html==](https://www.cnblogs.com/huangjianping/p/7998067.html) 398 | 399 | ## 4.2.HTTP协议 400 |   在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以: 401 | 402 | * 是一种用来定义网页的文本,会HTML,就可以编写网页; 403 | * 是在网络上传输HTML的协议,用于浏览器和服务器的通信。 404 | > 405 | > ***步骤1***:浏览器首先向服务器发送HTTP请求,请求包括: 406 | > 方法:GET还是POST,GET仅请求资源,POST会附带用户数据; 407 | > 路径:/full/url/path; 408 | > 域名:由Host头指定:Host: www.sina.com.cn 409 | > 以及其他相关的Header; 410 | > 如果是POST,那么请求还包括一个Body,包含用户数据。 411 | > 412 | > ***步骤2***:服务器向浏览器返回HTTP响应,响应包括: 413 | > 响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误; 414 | > 响应类型:由Content-Type指定; 415 | > 以及其他相关的Header; 416 | > 通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。 417 | > 418 | > ***步骤3***:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。 419 | > Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP请求中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源。 420 | 421 | **什么是长链接?** 422 | * 短链接: 连接请求——数据传输——断开连接 423 | * 长连接: 保持一段时间不断开tcp连接 424 | * 告诉他长度 conten—length 425 | 426 | **cookies和sesion区别** 427 | * Session 一般是服务器生成后给客户端 428 | * Cookie 是实现session的一种机制,通过HTTP cookie字段实现 429 | * Session 通过在服务器保存sessionid 识别用户,cookie 存储在客户端 430 | 431 | **HTTP1.0和HTTP1.1的一些区别** 432 | * TTP1.1则引入了更多的缓存控制策略 433 | * 支持断点续传功能,解决了浪费资源的问题 434 | * 请求消息和响应消息都应支持Host头 435 | 436 | **HTTPS与HTTP的一些区别** 437 | * HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。 438 | * HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。 439 | 440 | * HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。 441 | 442 | * HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。 443 | 444 | 445 | 446 | **HTTP请求方法** 447 | * 根据HTTP标准,HTTP请求可以使用多种请求方法。 448 | 449 | * HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。 450 | 451 | * HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。 452 | 453 | column0 | column1 | column2 454 | ------- | ------- | ------- 455 | 序号 | 方法 | 描述 456 | 1 | GET | 请求指定的页面信息,并返回实体主体。 457 | 2 | HEAD | 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 458 | 3 | POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 459 | 4 | PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 460 | 5 | DELETE | 请求服务器删除指定的页面。 461 | 6 | CONNECT | HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 462 | 7 | OPTIONS | 允许客户端查看服务器的性能。 463 | 8 | TRACE | 回显服务器收到的请求,主要用于测试或诊断。 464 | --- 465 | # 5.Linux相关 466 | 467 | ## 5.1.Linux储备 468 | **常考察方向** 469 | * 在Linux服务器上操作 470 | * 了解Linux工作原理和常用工具 471 | * 需要了解查看文件,进程,内存相关的一些命令,用来调试 472 | 473 | **如何查询linux命令用法** 474 | * 使用man命令查询用法。但是man手册比较晦涩 475 | * 工具自带dehelp 476 | * man的替代品tldr 477 | 478 | **文件/目录操作命令** 479 | * chown/chmode/chgrp 480 | * ls/rm/cd/mv/touch/rename/ln(软链接和硬链接)等 481 | * locate/find/grep定位查找搜索 482 | 483 | **文件查看** 484 | * 编辑器vi/nano 485 | * cat/head/tail查看文件 486 | * more/less交互式查看文件 487 | 488 | **进程操作命令** 489 | * ps查看进程 490 | * kill杀死进程 491 | * top/htop监控进程 492 | 493 | **内存操作命令** 494 | * free查看可用内存 495 | * 了解每一列的具体含义 496 | * 排查内存泄漏问题 497 | 498 | **网络操作命令** 499 | * ifconfig查看网卡信息 500 | * lsof/netstat查看端口信息 501 | * ssh/scp远程登录复制 502 | * tcpdump抓包 503 | 504 | **用户/组操作命令** 505 | * useradd/usermod 506 | * groupadd/groundmod 507 | --- 508 | 509 | 510 | ## 5.2.操作系统内存管理机制 511 | 512 | **什么是分页机制** 513 | 操作系统为了搞笑管理内存,减少碎片,逻辑地址和物理地址分离的内存分配管理方案 514 | * 程序的逻辑地址划分为固定大小的页(Page) 515 | * 物理地址划分为同样大小的帧(Frame) 516 | * 通过页表对应逻辑地址和物理地址 517 | > ![分页](https://jk-97.github.io/my_note/interview/sources/images/index_分页机制.png) 518 | 519 | **什么是分段机制** 520 | 分段式为了满足代码的一些逻辑需求 521 | * 数据共享,数据保护,动态链接库等 522 | * 通过段表实现逻辑地址和物理地址的映射关系 523 | * 每个段类不式连续内存分配,段和段直接式离散分配的(每个段是出于实现相同的一个功能来进行分配) 524 | > ![分段](https://jk-97.github.io/my_note/interview/sources/images/index_分段机制.png) 525 | 526 | **分页和分段的区别** 527 | * 页是出于内存利用率的角度提出离散分配机制 528 | * 段是出于用户角度,出于用户数据保护,数据隔离等用途的管理机制 529 | * 页的大小是固定的,操作系统决定;段的大小不确定,用户程序决定 530 | 531 | **什么是虚拟内存** 532 | 通过把一部分了暂时不用的的内存信息放到硬盘上 533 | * 局部性原理,程序运行时候只有部分必要的信息转入内存 534 | * 内存中暂时不需要的内容放到硬盘上 535 | * 系统似乎提供了比实际内存大得很多的内存容量,称之为虚拟内存 536 | 537 | **什么是内存抖动** 538 | * 本质是频繁的页调度行为 539 | * 频繁的页调度,进程不断产生缺页中断 540 | * 置换一个页,又不断再次需要这个页 541 | * 运行程序太多;分页替换策略不好。终止进程或者增加物理内存 542 | 543 | **Python的垃圾回收机制原理** 544 | * 引用计数为主(缺点:循环引用无法解决) 545 | * 引用标记清楚和分代回收解决引用计数的问题 546 | * 引用计数为主+标记清除和分代回收为辅 547 | 548 | **引用计数** 549 | ```python 550 | a = [1] #ref 1 551 | b = a #ref 2 552 | b=None #ref 1 553 | del a #ref 0 回收 554 | ``` 555 | **标记清除** 556 | ```python 557 | a = [1] #a ref 1 558 | b = [2] #b ref 1 559 | a.append(b) #b ref 2 560 | b.append(a) #a ref 2 561 | del a #a ref 1 562 | del b #b ref 1 无法归零回收 563 | ``` 564 | > ![标记清楚](https://jk-97.github.io/my_note/interview/sources/images/index_分代回收.png) 565 | > 通过root节点搜索可以达到的节点,不可达到的点标为灰色,回收 566 | 567 | ***分代回收*** 568 | * 给对象记录下一个age,随着每一次垃圾回收,这个age会增加; 569 | * 给不同age的对象分配不同的堆内内存空间,称为某一代; 570 | * 对某一代的空间,有适合其的垃圾回收算法; 571 | * 对每代进行不同垃圾回收,一般会需要一个额外的信息:即每代中对象被其他代中对象引用的信息。这个引用信息对于当前代来说,扮演与"root"一样的角色,也是本代垃圾回收的起点。 572 | 573 | ## 5.3.操作系统线程和进程常考题 574 | **进程和线程对比** 575 | * 进程是对运行时程序的封装,时系统资源调度的基本单位 576 | * 线程是进程的子任务,cpu调度和分配的基本单位,实现进程内并发,并行 577 | * 并行是真正的多核运行 578 | * python由于GIL不能真正的并行,看似并发 579 | * 一个进程都可以包含多个线程,线程依赖进程存在,并共享进程内存 580 | 581 | **什么是线程安全** 582 | * 一个操作可以再多线程环境中安全使用,获取正确的结果 583 | * 线程安全的操作好比线程是顺序执行而不是并发执行的(i+=1) 584 | * 一般如果涉及到写操作需要考虑如何让多个线程安全访问数据 585 | 586 | **线程同步方式** 587 | * 互踩量:通过互斥机制防止多个线程同时访问公共资源 588 | * 信号量(Semphare):threading.Semphare(valuse = 1)控制同一时刻多个线程访问同一个资源的线程数 589 | * 事件(信号):通过通知的方式保持多个线程同步 590 | 591 | **进程间通信的方式(IPC)** 592 | * 管道/匿名管道/有名管道(pipe) 593 | * 信号(Signal):比如用户使用Ctrl+c产生SIGINT程序终止信号 594 | * 消息队列(Message) 595 | * 共享内存(share memory) 596 | * 信号量(Semaphore) 597 | * 套接字(socket):最常用的方式,我们的web应用都是这种方式 598 | 599 | **Python使用多线程**(python开发适用于I/O密集型的应用) 600 | * threading.Thread类来创建线程 601 | * start()方法启动进程 602 | * 可以用join()等待线程结束 603 | 604 | **Python使用多进程程**(python开发使用于计算密集型的应用) 605 | * mltiprocessing多进程模块 606 | * Multiprocessing.Process类实现对进程 607 | * 避免GIL的影响 608 | --- 609 | 610 | # 6.数据库 611 | 612 | ## 6.1.数据库种类 613 | **如图:** 614 | > ![index_数据库分类 ](https://jk-97.github.io/my_note/interview/sources/images/index_数据库分类.png) 615 | > 616 | **关系型数据库介绍** 617 |   618 | 619 | 1. ***关系型数据库的由来*** 620 |   虽然网状数据库和层次数据库已经很好的解决了数据的集中和共享问题,但是在数据库独立性和抽象级别上扔有很大欠缺。用户在对这两种数据库进行存取时,仍然需要明确数据的存储结构,指出存取路径。而关系型数据库就可以较好的解决这些问题。 621 | 622 | 2. ***关系型数据库介绍*** 623 |   关系型数据库模型是把复杂的数据结构归结为简单的二元关系(即二维表格形式)。在关系型数据库中,对数据的操作几乎全部建立在一个或多个关系表格上,通过对这些关联的表格分类、合并、连接或选取等运算来实现数据库的管理。 624 | 625 |   关系型数据库诞生40多年了,从理论产生发展到现实产品,例如:Oracle和MySQL,Oracle在数据库领域上升到霸主地位,形成每年高达数百亿美元的庞大产业市场。 626 | 627 | 3. **关系型数据库表格之间的关系举例** 628 | ![index_关系表 ](https://jk-97.github.io/my_note/interview/sources/images/index_关系表.png) 629 |   630 | 631 | **非关系型数据库介绍** 632 |   633 | 634 | 1. ***非关系型数据库诞生背景*** 635 |   NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSql数据库在特定的场景下可以发挥出难以想象的高效率和高性能,它是作为对传统关系型数据库的一个有效的补充。 636 | 637 |   NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨。NoSQL的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。 638 | 639 | 2. ***非关系型数据库种类*** 640 | 641 | (1)键值存储数据库(key-value) 642 |   键值数据库就类似传统语言中使用的哈希表。可以通过key来添加、查询或者删除数据库,因为使用key主键访问,所以会获得很高的性能及扩展性。 643 | 键值数据库主要使用一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。Key/value模型对于IT系统来说的优势在于简单、易部署、高并发。 644 | 典型产品:Memcached、Redis、MemcacheDB 645 | 646 | (2)列存储(Column-oriented)数据库 647 |   列存储数据库将数据存储在列族中,一个列族存储经常被一起查询的相关数据,比如人类,我们经常会查询某个人的姓名和年龄,而不是薪资。这种情况下姓名和年龄会被放到一个列族中,薪资会被放到另一个列族中。 648 | 649 | 这种数据库通常用来应对分布式存储海量数据。 650 | 651 | 典型产品:Cassandra、HBase 652 | 653 | (3)面向文档(Document-Oriented)数据库 654 | 655 |   文档型数据库的灵感是来自于Lotus Notes办公软件,而且它同第一种键值数据库类似。该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。 656 | 657 |   面向文档数据库会将数据以文档形式存储。每个文档都是自包含的数据单元,是一系列数据项的集合。每个数据项都有一个名词与对应值,值既可以是简单的数据类型,如字符串、数字和日期等;也可以是复杂的类型,如有序列表和关联对象。数据存储的最小单位是文档,同一个表中存储的文档属性可以是不同的,数据可以使用XML、JSON或JSONB等多种形式存储。 658 | 659 | 典型产品:MongoDB、CouchDB 660 | 661 | (4)图形数据库 662 | 663 |   图形数据库允许我们将数据以图的方式存储。实体会被作为顶点,而实体之间的关系则会被作为边。比如我们有三个实体,Steve Jobs、Apple和Next,则会有两个“Founded by”的边将Apple和Next连接到Steve Jobs。 664 | 665 | 典型产品:Neo4J、InforGrid 666 | 667 | 668 | > [reference:https://blog.csdn.net/qq_27565769/article/details/80731213 ](https://blog.csdn.net/qq_27565769/article/details/80731213 ) 669 | 670 | --- 671 | 672 | ## 6.2.MYSQL 673 | ### 6.2.1.MYSQL概念 674 | **常考察点** 675 | * 事务原理,特性,事务的并发控制 676 | * 常用字段,含义,区别 677 | * 常用数据库引擎的区别 678 | 679 | **事务 Transaction** 680 | * 事务是数据库并发控制的基本单位 681 | * 事务可以看作是一系列SQL语句的集合 682 | * 事务必须要么全部执行成功,要么全部执行失败 683 | 684 | **特性ACID** 685 | * 原子性(Atomicity):一个事务所有操作全部完成或失败 686 | * 一致性(Consistency):事务开始和结束后数据的完整性没有被破坏 687 | * 隔离性(Isolation):允许多个事务同时对数据库修改和读写 688 | * 持久性(Durability):事务结束后,修改时永久不会丢失的 689 | 690 | **事务的并发可能会产生的四种异常情况** 691 | * 幻读(phanton read):一个事务第二次查出第一次没有的结果 692 | * 非重复读(nonrepeatable read): 一个事务重复读两次得到不同结果 693 | * 脏读(dirty read):一个事务读取到另外一个事务没有提交的修改 694 | * 丢失修改(lost update):并发写入造成其中一些修改丢失 695 | 696 | **四种事务隔离级别** 697 | * 读取提交(read uncommitted):别的事务可以读取到未提交改变 698 | * 读已提交(read committed):只能读取已提交的数据 699 | * 可重复读(repeatable read):同一个事务先后查询结果一样(Mysql InoDB默认实现可重复读级别) 700 | * 串行化(Serialzavle):事务串行化的执行,隔离级别最高,执行效率最低 701 | 702 | **如何解决并发场景下的插入重复** 703 | * 使用数据库的唯一索引(一般情况用不了,一般项目会建库建表) 704 | * 使用队列异步写入 705 | * 使用redis等实现分布式锁 706 | 707 | **乐观锁和悲观锁** 708 | * 悲观锁是先获取在进行操作。一锁二查三更新select for update 709 | * 乐观锁先修改,更新的时候发现数据已经变了就回滚(测check and set) 710 | * 根据响应速度,冲突频率,重试代价来判断选择哪种 711 | 712 | **MYSQL数据类型** 713 | 1. 字符串 714 | CHAR:存储定长字符串 715 | VARCHAR:存储不定长字符串 716 | TEXT:存储较长的文章 717 | 2. 数值 718 | TINTINT,INT,BIGINT,DOUBLE等 719 | 3. 日期和时间 720 | DATE:YYYY-MM-DD 721 | DATETIME:YYYY-MM-DD HH:MM:SS 722 | 723 | **Mysql常用引擎** 724 | * MyISAM不支持事务,InnoDB支持事务 725 | * MyISAM不支持外键,InnoDB支持外键 726 | * MyISAM只支持表锁,InnoDB支持表锁和行锁 727 | 728 | 729 | ### 6.2.1.索引原理以及优化 730 | * 索引的原理,类型,结构 731 | * 创建索引的注意事项,使用原则 732 | * 创建排查和消除慢查询 733 | 734 | **什么是索引?** 735 | * 索引是数据表中一个或多个列进行排序的数据结构 736 | * 索引能够大幅度提升检索速度(回顾下查找结构:二叉搜索树,平衡数,多路平衡数) 737 | * 创建,更新索引本身也会消耗空间和时间 738 | 739 | **查找结构进化史** 740 | * 线性查找:一个一个找,实现简单,速度慢 741 | * 二分查找:简单,查找快,但要求是有序的,插入特别慢 742 | * HASH:查询快,占用空间,不太适合存储大规模的数据 743 | * 二叉查找树:插入和查询很快(log(n)),无法存大规模数据,复杂退化问题 744 | * 平衡数:解决bst退化的问题,树是平衡的;节点非常多的时候,树依然很高 745 | * 多路查找树:一个父亲多个孩子节点,书不会特别深 746 | * 多路平衡查找树:B-Tree 747 | 748 | > [数据结构可视化网站](https://:www.cs.usfca.edu/~galles/visualization/Algorithms.html) 749 | 750 | **什么是B-Tree?** 751 | * 多路平衡查找树(每个节点最多m(m>=2)个孩子,称为m阶或者度) 752 | * 叶节点具有相同深度 753 | * 节点中的数据key从做到右四递增的 754 | 755 | **什么是B+Tree** 756 | * Mysql实际使用的是B+Tree作为索引的数据结构 757 | * 只在叶子节点带有指向的指针,可以增加书的度 758 | * 叶子节点通过指针相连,实现范围查询 759 | 760 | **Mysql索引类型** 761 | * 普通索引 762 | * 唯一索引 763 | * 多列索引 764 | * 主键索引 765 | * 全文索引InnoDB不支持 766 | 767 | **什么时候创建索引** 768 | * 经常用作查询条件的字段(WHERE条件) 769 | * 经常用锁表连接的字段 770 | * 经常出现order by,ground by之后的字段 771 | 772 | **创建索引右那些需要注意的** 773 | * 非空字典NOT NULL,Mysql很难多空值查询优化 774 | * 区分度高,离散度大,作为索引的字段值尽量不要右大量相同值 775 | * 索引长度不要太长(比较耗费时间) 776 | 777 | **索引什么时候失效** 778 | * 模糊匹配:以%开头的LIKE语句,模糊搜索 779 | * 类型隐转:出现隐式类型转换(在python这种动态语言中查询需要特别注意) 780 | * 没有满足最左前缀原则 781 | 782 | **什么式聚集索引和非聚集索引** 783 | * 聚集还是非聚集指的式B+Tree叶节点的是指针还是数据记录 784 | * MyISAM索引和数据分离,使用的是非聚集索引 785 | * InnoDB数据文件就是索引文件,主键索引就是聚集索引 786 | 787 | **如何排查慢查询** 788 | * 慢查询通常是缺少索引,索引不合理或业务逻辑代码实现导致 789 | * slow_query_log_file开启并且查询了慢查询日志 790 | * 通过explain排查索引问题 791 | * 调整数据修改索引;业务代码层限制不合理访问 792 | ---- 793 | ### 6.2.2.Mysql语句常考题 794 | 795 | **SQL语句已考察各种各种连接为重点** 796 | * 内链接(INNER JOIN):两个表存在匹配时,才会返回匹配行 797 | * 将左表和右表能关联起来的数据连接后返回 798 | * 类似于求两个表的“交集” 799 | * select * from A innner join B on a .id =v .id 800 | * 外连接(LEFT/RIGHT JOIN):返回一个表的行,即使另外一个没有匹配 801 | * 左连接返回坐标中所有记录,几时右表中没有匹配的记录 802 | * 左连接返回右表中所有记录,几时坐标中没有匹配的记录 803 | * 没有匹配的字段会设置成NULL 804 | * Mysql中使用left join和right jion实现 805 | * 全链接(FULL JOIN):只要某一个表存在匹配就返回 806 | * 只要某一个表存在匹配,就返回行 807 | * 类似求两个表的“并集” 808 | * 但是Mysql不支持,可以用left jion,union,right join联合使用模拟 809 | 810 | ### 6.2.3.Mysql思考题 811 | 812 | * 为什么Mysql数据库的主键使用自增的增数比较好? 813 |   对于这个问题需要从MySQL的索引以及存储引擎谈起: 814 |   InnoDB的primary key为cluster index,除此之外,不能通过其他方式指定cluster index,如果InnoDB不指定primary key,InnoDB会找一个unique not null的field做cluster index,如果还没有这样的字段,则InnoDB会建一个非可见的系统默认的主键---row_id(6个字节长)作为cluster_index。 815 |   建议使用数字型auto_increment的字段作为cluster index。不推荐用字符串字段做cluster index (primary key) ,因为字符串往往都较长, 会导致secondary index过大(secondary index的叶子节点存储了primary key的值),而且字符串往往是乱序。cluster index乱序插入容易造成插入和查询的效率低下。 816 | 817 | * 使用uuid可以?为什么? 818 | * 自增ID节省一半磁盘空间 819 | * 单个数据走索引查询,自增id和uuid相差不大 820 | * 范围like查询,自增ID性能优于UUID 821 | * 写入测试,自增ID是UUID的4倍 822 | * 备份和恢复,自增ID性能优于UUID 823 | 824 | * 如果是分布式系统下怎么生成数据库的自增? 825 | 分布式架构,意味着需要多个实例中保持一个表的主键的唯一性。这个时候普通的单表自增ID主键就不太合适,因为多个mysql实例上会遇到主键全局唯一性问题。 826 | * 自增ID主键+步长,适合中等规模的分布式景 827 |   在每个集群节点组的master上面,设置(auto_increment_increment),让目前每个集群的起始点错开 1,步长选择大于将来基本不可能达到的切分集群数,达到将 ID 相对分段的效果来满足全局唯一的效果。 828 |   优点是:实现简单,后期维护简单,对应用透明。 829 |   缺点是:第一次设置相对较为复杂,因为要针对未来业务的发展而计算好足够的步长; 830 | * UUID,适合小规模的分布式环境 831 |   对于InnoDB这种聚集主键类型的引擎来说,数据会按照主键进行排序,由于UUID的无序性,InnoDB会产生巨大的IO压力,而且由于索引和数据存储在一起,字符串做主键会造成存储空间增大一倍。 832 |   在存储和检索的时候,innodb会对主键进行物理排序,这对auto_increment_int是个好消息,因为后一次插入的主键位置总是在最后。但是对uuid来说,这却是个坏消息,因为uuid是杂乱无章的,每次插入的主键位置是不确定的,可能在开头,也可能在中间,在进行主键物理排序的时候,势必会造成大量的 IO操作影响效率,在数据量不停增长的时候,特别是数据量上了千万记录的时候,读写性能下降的非常厉害。 833 | 优点:搭建比较简单,不需要为主键唯一性的处理。 834 | 缺点:占用两倍的存储空间(在云上光存储一块就要多花2倍的钱),后期读写性能下降厉害。 835 | * 雪花算法自造全局自增ID,适合大数据环境的分布式场景。由twitter公布的开源的分布式id算法snowflake 836 | --- 837 | ## 6.3.Redis 838 | ### 6.3.1.Redis概念 839 | 840 | * 为什么使用缓存?使用场景? 841 | * 常用的内存缓存有Redis和Memcached 842 | * 缓存关系数据库并访问的压力:热点数据 843 | * 减少响应时间:内存IO速度必磁盘快 844 | * 提升吞吐量:Redis等内存数据库单机可以支撑很大并发 845 | 846 | > ![index_redis与memcached](https://jk-97.github.io/my_note/interview/sources/images/index_redis与memcached.png) 847 | 848 | * Redis的常用数据类型,使用方式 849 | * Sring:用来实现简单的KV键值对,比如计数器 850 | * List:实现双向链表,比如用户的关注,粉丝列表 851 | * Hash:用来存储彼此相关的键值对,HSET key filed value 852 | * Set:存储不重复元素,比如用户的关注者 853 | * Sorted Set:实时信息排行榜 854 | 855 | * Redis内置实现 856 | * C语言底层实现 857 | * String:整数或者sds(Simple Dynamic String) 858 | * List:ziplist或者double linked list 859 | * Hash:ziplist或者hashtable 860 | * Set:intset或者hashtable 861 | * SortedSet:skiplist 跳跃表 862 | 863 | * Redis有哪些持久化方式? 864 | * 快照方式:把数据快照放在磁盘二进制文件中,dump.rdb,指定时间间隔把Redis数据库状态保存到一个压缩的二进制文件中,缺点:若宕机,间隔内的数据全部丢失 865 | * AOF(Append Only File):每一个写命令保存到appendonly.aof中。缺点,虽然不会丢失大量数据,但文件比较大,恢复速度比较慢 866 | 867 | * Redis事务 868 | * 将多个请求打包,一次性,按序执行多个命令的机制 869 | * Rdis通过MULTI,EXEC,WATCH等命令实现事务功能 870 | * Python redis-py pipeline = conn.pipeline(transaction =True) 871 | 872 | * Redis如何实现分布式锁 873 | * 使用setnx实现加锁,可以同时通过expire添加超时时间 874 | * 锁的valuse值可以使用一个随机的uuid或者待定的命名 875 | * 释放锁的时候,通过uuid判断是否是该锁,是则执行delete释放锁 876 | 877 | [Redis分布式锁的实现原理看这篇就够了](https://blog.csdn.net/gupao123456/article/details/84327254) 878 | 879 | * 使用缓存的模式? 880 | * Cache Aside:同时更新缓存和数据库(先更新数据库后更新缓存,并发写操作可能导致缓存读取的是脏数据,一般先更新数据库然后删除缓存,下次读取时再重建缓存) 881 | * Read/Write Throught:先更新缓存,缓存负责同步更新数据库 882 | * Write Behind Caching:先更新缓存,缓存顶起异步更新数据库 883 | * 缓存使用问题:数据一致性问题;缓存穿透,击穿,雪崩 884 | * 缓存穿透:大量查询不到的数据请求落到后端数据库,数据库压力增大(很多无脑爬虫通过自增id的方式爬取网站,网站查不到相关id的数据) 885 | * 原因:由于大量缓存查不到就去数据库取,数据库也没有要差的数据 886 | * 解决:对于没查到返回为None的数据也缓存 887 | * 插入数据的时候删除相应缓存,或者设置较短的超时时间 888 | * 缓存击穿:某些非常热点的数据key过期,大量请求打到后端数据库 889 | * 原因:热点数据key失效导致大量请求打到数据库增加数据库压力 890 | * 解决:分布式锁:获取锁的线程从数据库拿去数据更新缓存,其他线程等待。异步后台更新:后代任务针对过期的key自动刷新 891 | * 缓存雪崩:缓存不可用或者大量缓存key同时失效,大量请求直接打到数据库 892 | * 解决:多级缓存:不同级别的key设置不同的超时时间。随机超时:key的超时时间随机设置,防止同时超时。架构层:提升系统可用性,监控,报警完善 893 | 894 | --- 895 | 896 | ### 6.3.2.Redis分布式锁应用 897 | * 请里基于redis编写实现一个简单的分布式锁(要求支持超时参数) 898 | * 如果Redis单个节点宕机了,如何处理?还有其他业界的方案实现分布式锁么? 899 | 900 | --- 901 | # 7.爬虫 902 | ## 7.1.技术储备 903 | ### 7.1.1.开发环境 904 | * pycharm 905 | * mysql+redis+etri 906 | 907 | **技术选型** 908 | * scrapy vs requests + beatifulsoup 909 | * request 和beatifulsoup都是库,scrapy是框架 910 | * scrapy框架加入 911 | * scrapy基于twsted,性能最大的优势 912 | * scrapy方便拓展,提供了很对内置的功能 913 | * scrapy内置的css和xpath selector非常方便,beautifulsoup最大的缺点就是慢 914 | 915 | **网页分类** 916 | * 静态网页 917 | * 动态网页 918 | * webservice(restapi) 919 | 920 | ### 7.1.2.正则表达式 921 | 字符 | 描述 922 | ------------ | ------------- 923 | \cx |匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。 924 | \f |匹配一个换页符。等价于 \x0c 和 \cL。 925 | \n |匹配一个换行符。等价于 \x0a 和 \cJ。 926 | \r |匹配一个回车符。等价于 \x0d 和 \cM。 927 | \s |匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。注意 Unicode 正则表达式会匹配全角空格符。 928 | \S |匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 929 | \t |匹配一个制表符。等价于 \x09 和 \cI。 930 | \v |匹配一个垂直制表符。等价于 \x0b 和 \cK。 931 | 932 | --- 933 | 字符|描述 934 | ------------- | ------------- 935 | $ | 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 \$ 也匹配 '\n' 或 '\r'。要匹配 $ 字符本身,请使用 \$。 936 | ( ) |标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 \\( 和 \\)。 937 | * |匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \\*。 938 | + |匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \\+。 939 | . |匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。 940 | [ |标记一个中括号表达式的开始。要匹配 [,请使用 \\[。 941 | ? |匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。 942 | \ |将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\\\' 匹配 "\",而 '\\(' 则匹配 "("。 943 | ^ |匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。 944 | { |标记限定符表达式的开始。要匹配 {,请使用 \\{。 945 | \| |指明两项之间的一个选择。要匹配 \|,请使用 \\\|。 946 | 947 | --- 948 | 字符|描述 949 | ------------- | ------------- 950 | * | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。 951 | + |匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 952 | ? |匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。 953 | {n} |n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 954 | {n,} |n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。 955 | {n,m} |m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。 956 | 957 | 958 | 959 | ```python 960 | 961 | import re 962 | line = 'marshenmmm' #.为任意字符 * 与 +为修饰次数的限定词 963 | regek_str1 = '.*(m.*m).*' #贪婪匹配 964 | regek_str2 = '.*?(m.*?m).*' #非贪婪匹配 965 | regek_str2 = '.*?(m.+?m).*' #至少中间有一个字符 966 | res1 = re.match(regek_str1,line) 967 | res2 = re.match(regek_str2,line) 968 | print(res1.group(1)) 969 | print(res2.group(1)) 970 | """ 971 | 执行结果: 972 | mm 973 | marshenm 974 | mmm 975 | """ 976 | 977 | ``` 978 | 979 | 980 | ### 7.1.3.深度优先与广度邮箱 981 | > ![index_伯乐网络结构 ](https://jk-97.github.io/my_note/interview/sources/images/index_伯乐网络结构.png) 982 | 我们可以观看网站的结构非常地与数据结构的树相似 983 | > ![index_二叉树 ](https://jk-97.github.io/my_note/interview/sources/images/index_二叉树.png) 984 | * 深度优先 985 | ```python 986 | # ABDEICFGH(递归实现) 987 | def depth_tree(tree_node): 988 | if tree_node is not None: 989 | print(tree_node.val) 990 | if tree_node._left is not None: 991 | return depth_tree(tree_node._left) 992 | if tree_node._right is not None: 993 | return depth_tree(tree_node._right) 994 | ``` 995 | * 广度优先 996 | 997 | ```python 998 | # ABCDEFGHI(队列实现) 999 | def level_queue(root): 1000 | if root is None: 1001 | return 1002 | my_queue = [] 1003 | my_queue.append(root) 1004 | while my_queue: 1005 | node = my_queue.pop() 1006 | print(node.elem) 1007 | if node.lchild is not None: 1008 | my_queue.append(node.lchild) 1009 | if node.rchild is not None: 1010 | my_queue.appnd(node.rchild) 1011 | ``` 1012 | **爬虫去重策略** 1013 | 由于网站中链接会有相互跳转的情况,如我们不处理,那么就有可能会进入无限的循环。我们就需要进行网站的除重。常用的除虫有以下策略: 1014 | * 将访问的url保存到数据库中 1015 | * 将访问的url报错到set中,只需要o(1)代价就可以查询url 1016 | * url经过md5等方法哈希保存到set 1017 | * 用bitmap方法,将访问的url通过hash函数映射到某一位 1018 | * bloomfilter方法对bitmap进行改进,对重hash函数降低冲突 1019 | 1020 | ## 7.2.scrapy的安装 1021 | 虚拟环境安装好后,在windows系统中还需安装pypiwin32库,安装好后,在cmd中输入 1022 | ```c 1023 | scrapy startproject ArticleSpider #创建项目 1024 | scrapy genspider jobbloe web.jobble.com #在项目于目录下,创建爬虫 1025 | ``` 1026 | 由于pycharm中没有scrapy的模板,需要创建主脚本启动调试爬虫,在主目录下创建 1027 | ```python 1028 | from scrapy.cmdline import execute 1029 | import sys 1030 | import os 1031 | 1032 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 1033 | execute(['scrapy','crawl','jobbole']) #相当于在cmd窗口执行 scrapy crawl jobble 1034 | ``` 1035 | 1036 | **xpath** 1037 | 1038 | 简介 1039 | 使用路径表达式在xml和html中进行导航 1040 | xpath包含标准函数库 1041 | xpath时一个w3c标准 1042 | 1043 | 节点关系 1044 | 父节点 1045 | 子节点 1046 | 同胞节点 1047 | 先辈节点 1048 | 后代节点 1049 | 1050 | xpath语法 1051 | > ![xpath ](https://jk-97.github.io/my_note/interview/sources/images/index_xpath语法.png) 1052 | > ![xpath ](https://jk-97.github.io/my_note/interview/sources/images/index_xpath语法_.png) 1053 | > ![xpath ](https://jk-97.github.io/my_note/interview/sources/images/index_xpath_谓语.png) 1054 | 1055 | 1056 | 1057 | 1058 | 在伯乐在线选取一篇网站,使用浏览器开发者工具复制xpth路径填入 1059 | 火狐浏览器和chrome浏览器的可能不一样,是因为火狐的复制xpath是浏览器运行网页代码之后生成的,其中有js生成的元素。 1060 | 0 1061 | ```python 1062 | #jobbole.py 1063 | start_urls = ['http://blog.jobbole.com/114666/'] 1064 | ··· 1065 | strs = '//*[@id="post-114666"]/div[1]/h1/text()' 1066 | re_selector = response.xpath(strs) 1067 | xpath("//span[contains(@class,'xxxx')]") 1068 | ``` 1069 | 1070 | 1071 | 1072 | 1073 | --- 1074 | 1075 | 1076 | # 8.框架语言 1077 | 1078 | 1079 | ## 8.1.什么是WSGI 1080 | * python web server gateway interface 1081 | * 解决了python webserver乱象 mode——python,CGI。fastCGI 1082 | * 描述了web server 如何与web框架交互,web框架如何请求处理 1083 | 1084 | ```python 1085 | #一个简单的wsgi应用 1086 | def myapp(environ, start_resopnce): 1087 | status = '200 OK' 1088 | header = [('Conten-Typr', 'text/html;charset=utf-8')] 1089 | start_resopnce(status, header) 1090 | return [b'

Hello world

'] 1091 | 1092 | 1093 | if __name__ == "__main__": 1094 | from wsgiref.simple_server import make_server 1095 | httpd = make_server('127.0.0.1', 8888, myapp) 1096 | httpd.serve_forever() 1097 | 1098 | 1099 | ``` 1100 | 1101 | ## 8.2.常用pythonweb框架 1102 | 1103 | ## 8.3.web框架组成 1104 | MVC 1105 | 1106 | ## 8.4.RESTful 1107 | * 前后端分离的意义和方式 1108 | * 什么是RESTful 1109 | * 如何设计RESTful API 1110 | 1111 | 前后端解耦,接口复用,减少开发量 1112 | 各司其职,前后端同步开发,提升工作效率,定义好接口规范 1113 | 更有利于调试(mock)测试和运维部署 1114 | 1115 | **representtational state transfer** 1116 | 表现层状态转移,由HTTP协议的主要设计者RoyFielding提出 1117 | 资源(resources),表现层(representation),状态转化(statr transfer) 1118 | 是一种以资源为为中心的web软件架构风格,可以用ajax和resful web服务构建应用 1119 | * resources:使用url指向一个实体 1120 | * representation:资源的表现形式,比如图片,HTML文本等 1121 | * statr transfer状态转化:get,post,putdelete http动词来操作资源,实现资源的改变 1122 | 1123 | **resful的准则** 1124 | 所有思维u抽象围殴至于那,资源对应唯一的标识 1125 | 资源通过接口进行操作实现状态转移,操作本身无状态 1126 | 对之u按的操作不会改变资源的标识 1127 | 1128 | **restful api** 1129 | 通过get,post,put delete http 获取/新疆/更新/删除 1130 | 一般使用json格式返回数据 1131 | 一般web框架都有相应的插件支持resfulapi 1132 | 1133 | **什么是https** 1134 | * https和http的区别是什么 1135 | * 什么是对称加密和非对称加密 1136 | 1137 | --- 1138 | 1139 | 1140 | 1141 | # 9.web高并发、分布式技术 1142 | 1143 | 1144 | 1145 | ## 9.1.什么是微服务? 1146 | 1147 | **单体式开发的缺点** 1148 | * 设计、开发、部署为一个单独的单元。会变得越来越复杂,最后导致维护、升级、新增功能变得异常困难。很难以敏捷研发模式进行开发和发布部分更新,都需要重新部署整个应用 1149 | 1150 | * 水平扩展:必须以应用为单位进行扩展,在资源需求有冲突时扩展变得比较困难(部分服务需要更多的计算资源,部分需要更多内存资源) 1151 | 1152 | * 可用性:一个服务的不稳定会导致整个应用出问题 1153 | 1154 | * 创新困难:很难引入新的技术和框架,所有的功能都构建在同质的框架之上 1155 | 1156 | * 运维困难:变更或升级的影响分析困难,任何一个小修改都可能导致单体应用整体运行出现故障。 1157 | 1158 | **微服务的出现** 1159 | 随着用户群体的增多,导致了web服务器的压力增大,还有一些高并发的时间端的冲击。我们并不能保证服务器的安全平稳运行。一旦宕机,若不尽快的修复就会带来难以弥补的损失。 1160 | 1161 | **微服务的设计原则** 1162 | 职责单一原则(Single Responsibility Principle):把某一个微服务的功能聚焦在特定业务或者有限的范围内会有助于敏捷开发和服务的发布。 1163 | 设计阶段就需要把业务范围进行界定。 1164 | 1165 | 1166 | 1167 | ## 9.2.网络IO 1168 | ==本节图片摘自《UNIX网络编程》== 1169 | **poll,select,epoll** 1170 | 1171 | * poll是Linux中的字符设备驱动中的一个函数。Linux 2.5.44版本后,poll被epoll取代。和select实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列。 1172 | 1173 | * Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。 1174 | 可是使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。 1175 | 1176 | * epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。==另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了==。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。 1177 | 1178 | **BIO(同步阻塞)** 1179 | 阻塞式I/O模型是最基本的I/O模型。在默认情况下,所有套接字都是阻塞的,以数据报(在python中常用monkey pack替换成非阻塞socket) 1180 | 1181 | > ![index_阻塞IO ](https://jk-97.github.io/my_note/interview/sources/images/index_阻塞IO.png) 1182 | 1183 | 1. 收到一个IO请求,首先调用recvfrom系统调用 1184 | 2. 不能立即获得数据,从磁盘读取数据到内核内存(wait for data) 1185 | 3. 数据准备完毕,从内核内存复制到用户程序内存 1186 | 4. 返回OK 1187 | 1188 | 当发生错误时recvfrom会返回出错。recvfrom只有接收或者出错时才会返回,其余时间都是阻塞的。 1189 | 1190 | > 在用户量有一定规模的情况下,可以使用: 1191 | > * 在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。 1192 | > * 但是开启多进程或都线程的方式,在遇到要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,==降低系统对外界响应效率,而且线程与进程本身也更容易进入假死状态。(内存抖动)== 1193 | > * ==线程池”旨在减少创建和销毁线程的频率==,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统。 1194 | > * “线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。而且,==所谓“池”始终有其上限,当请求大大超过上限时==,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。 1195 | 1196 | **NIO(同步非阻塞)** 1197 | 1198 | > ![index_非阻塞IO ](https://jk-97.github.io/my_note/interview/sources/images/index_非阻塞IO.png) 1199 | 1200 | 1201 | 1. 收到一个IO请求,首先调用recvfrom系统调用 1202 | 2. 系统立即返回一个ERROR说明数据没准备好 1203 | 3. 在这一阶段可以继续执行其他操作 1204 | 4. 继续回到步骤2继续执行,直到数据表准备完成 1205 | 5. 操作系统将数据表复制到用户程序内存(这一阶段也是阻塞状态的) 1206 | 6. 返回ok 1207 | 1208 | 也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。 1209 | 1210 | > 优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。 1211 | > 缺点: 1212 | > * 循环调用recv()将大幅度推高CPU占用率;这也是我们在代码中留一句time.sleep(2)的原因,否则在低配主机下极容易出现卡机情况 1213 | > * 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。 1214 | 1215 | ==此外,在这个方案中recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如select()多路复用模式,可以一次检测多个连接是否活跃。== 1216 | 1217 | 1218 | **多路复用** 1219 | IO multiplexing这个词可能有点陌生,但是如果我说select/epoll,大概就都能明白了。有些地方也称这种IO方式为事件驱动IO(event driven IO)。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图: 1220 | > ![index_多路复用IO ](https://jk-97.github.io/my_note/interview/sources/images/index_多路复用IO.png) 1221 | 1222 | 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 1223 | 这个图和blocking IO的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而blocking IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个connection。 1224 | 1225 | 强调: 1226 | 1227 | 1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。 1228 | 1229 | 2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。 1230 | 1231 | ==结论: select的优势在于可以处理多个连接,不适用于单个连接== 1232 | 1233 | 1234 | > 优点: 1235 | > 相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。 1236 | > 缺点: 1237 | > 首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要实现更高效的服务器程序,类似epoll这样的接口更被推荐。遗憾的是不同的操作系统特供的epoll接口有很大差异,所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难。 1238 | > 其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。 1239 | **AIO(异步非阻塞)** 1240 | 1241 | 1242 | > ![index_异步IO ](https://jk-97.github.io/my_note/interview/sources/images/index_异步IO.png) 1243 | 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。 1244 | 1245 | **通俗的例子** 1246 | 老张爱喝茶,废话不说,煮开水。 1247 | 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。 1248 | 1. 老张把水壶放到火上,立等水开。(同步阻塞) 1249 | 老张觉得自己有点傻 1250 | 2. 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 1251 | 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 1252 | 3. 老张把响水壶放到火上,立等水开。(异步阻塞) 1253 | 老张觉得这样傻等意义不大 1254 | 4. 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 1255 | 老张觉得自己聪明了。 1256 | 1257 | * 所谓同步异步,只是对于水壶而言。 1258 | 普通水壶,同步;响水壶,异步。 1259 | 虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。 1260 | 同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。 1261 | 1262 | * 所谓阻塞非阻塞,仅仅对于老张而言。 1263 | 立等的老张,阻塞;看电视的老张,非阻塞。 1264 | 情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。 1265 | -------------------------------------------------------------------------------- /k8sandkubeflow/k8sandkubeflow.md: -------------------------------------------------------------------------------- 1 | --- 2 | export_on_save: 3 | html: true 4 | html: 5 | toc: true 6 | offline: true 7 | toc: 8 | depth_from: 1 9 | depth_to: 3 10 | ordered: false 11 | 12 | --- 13 | 14 | 15 | 16 | 17 | # 一、前期准备 18 | k8s有很多种搭建方式,google上查找的大部分教程都是基于AWS和GCP的,而网上搭建本地的集群的教程极为零散。 19 | 那么接下就开始搭建之路吧! 20 | 示例环境 21 | master 192.168.0.105 22 | node 192.168.0.115 23 | ## ip与dns的分配 24 | 由于k8s在新建pod的时候,会在 /etc/network/interface 的配置文件中获取nameserver等信息,所以为了kubeflow再spwan一个新的pod时能够解析域名,就需要配置此文件。 25 | 注意:不能再ubuntu的图形界面配置,这是ubuntu中一个槽点 26 | ``` 27 | # interfaces(5) file used by ifup(8) and ifdown(8) 28 | 29 | auto lo 30 | iface lo inet loopback 31 | 32 | auto enp0s31f6 33 | iface enp0s31f6 inet static 34 | address 10.54.0.1 35 | netmask 255.255.255.0 36 | gateway 10.54.0.253 37 | dns-nameservers 192.168.0.66 38 | 39 | 40 | ``` 41 | 42 | 43 | ## 已经适配的版本: 44 | * kubernets 45 | kubeadm kubelet kubectl 全部要统一版本v1.13.6 46 | ``` 47 | docker : 48 | Client: 49 | Version: 18.06.1-ce 50 | API version: 1.38 51 | Go version: go1.10.3 52 | Git commit: e68fc7a 53 | Built: Tue Aug 21 17:24:56 2018 54 | OS/Arch: linux/amd64 55 | Experimental: false 56 | 57 | Server: 58 | Engine: 59 | Version: 18.06.1-ce 60 | API version: 1.38 (minimum version 1.12) 61 | Go version: go1.10.3 62 | Git commit: e68fc7a 63 | Built: Tue Aug 21 17:23:21 2018 64 | OS/Arch: linux/amd64 65 | Experimental: false 66 | ``` 67 | * nvidia-docker 68 | ``` 69 | NVIDIA Docker: 2.0.3 70 | Client: 71 | Version: 18.06.1-ce 72 | API version: 1.38 73 | Go version: go1.10.3 74 | Git commit: e68fc7a 75 | Built: Tue Aug 21 17:24:56 2018 76 | OS/Arch: linux/amd64 77 | Experimental: false 78 | 79 | Server: 80 | Engine: 81 | Version: 18.06.1-ce 82 | API version: 1.38 (minimum version 1.12) 83 | Go version: go1.10.3 84 | Git commit: e68fc7a 85 | Built: Tue Aug 21 17:23:21 2018 86 | OS/Arch: linux/amd64 87 | Experimental: false 88 | 89 | 90 | ``` 91 | 92 | 相关命令 93 | ```shell 94 | $ kubeadm version 95 | $ docker version 96 | $ nvidia-docker version 97 | ``` 98 | ## 第一步:安装docker 99 | ```shell 100 | # 安装最新版本 101 | $ sudo apt-get update 102 | 103 | $ sudo apt-get install \ 104 | apt-transport-https \ 105 | ca-certificates \ 106 | curl \ 107 | gnupg-agent \ 108 | software-properties-common 109 | 110 | $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 111 | 112 | $ sudo apt-key fingerprint 0EBFCD88 113 | 114 | $ sudo add-apt-repository \ 115 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 116 | $(lsb_release -cs) \ 117 | stable" 118 | # 获取docker的repo 119 | 120 | $ sudo apt-get update 121 | 122 | $ sudo apt-get install docker-ce docker-ce-cli containerd.io 123 | # 直接安装是安装最新版本的 124 | ``` 125 | 126 | ```shell 127 | # 该教程使用的就是 17.12.1~ce-0~ubuntu 版本 128 | # 安装指定版本,紧接上一段倒数第二句命令 129 | $ apt-cache madison docker-ce 130 | docker-ce | 5:18.09.1~3-0~ubuntu-xenial | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages 131 | docker-ce | 5:18.09.0~3-0~ubuntu-xenial | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages 132 | docker-ce | 18.06.1~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages 133 | docker-ce | 18.06.0~ce~3-0~ubuntu | https://download.docker.com/linux/ubuntu xenial/stable amd64 Packages 134 | ····· 135 | 136 | $ sudo apt-get install docker-ce= containerd.io 137 | # eg. sudo apt-get install docker-ce=17.12.1~ce-0~ubuntu containerd.io 138 | ``` 139 | 这样就完成了docker的安装 140 | ## 第二步:安装nvidia-docker 141 | ```shell 142 | $ docker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f 143 | 144 | $ sudo apt-get purge -y nvidia-docker 145 | # 卸载旧版的nvidia-docker,之前没安装就跳过 146 | 147 | $ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - 148 | 149 | $ distribution=$(. /etc/os-release;echo $ID$VERSION_ID) 150 | 151 | $ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list 152 | deb https://nvidia.github.io/libnvidia-container/ubuntu18.04/$(ARCH) / 153 | deb https://nvidia.github.io/nvidia-container-runtime/ubuntu18.04/$(ARCH) / 154 | deb https://nvidia.github.io/nvidia-docker/ubuntu18.04/$(ARCH) / 155 | 156 | 157 | $ sudo apt-get update 158 | 159 | $ sudo apt-get install -y nvidia-docker2 160 | # 直接装是最新版本,会自动升级docker到最新版本,一般情况下我们不这么做 161 | # 162 | ``` 163 | 164 | ```shell 165 | # 安装指定版本 166 | $ apt-cache madison nvidia-docker2 167 | nvidia-docker2 | 2.0.3+docker18.09.5-3 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 168 | nvidia-docker2 | 2.0.3+docker18.09.5-2 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 169 | nvidia-docker2 | 2.0.3+docker18.09.4-1 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 170 | nvidia-docker2 | 2.0.3+docker18.09.3-1 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 171 | nvidia-docker2 | 2.0.3+docker18.09.2-1 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 172 | nvidia-docker2 | 2.0.3+docker18.09.1-1 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 173 | nvidia-docker2 | 2.0.3+docker18.09.0-1 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 174 | nvidia-docker2 | 2.0.3+docker18.06.2-2 | https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64 Packages 175 | nvidia-docker2 | 2.0.3+docker18.06.2-1 | https://nvidia.github.io/ 176 | ···· 177 | # 获取到版本号后,直接装也是不行的 178 | # 他会提示你要有新的依赖,需要安装最新的nvidia-container-runtime,实际是不需要的 179 | # 所以安装还要带上nvidia-container-runtime并且指定一个版本 180 | 181 | $ apt-cache madison nvidia-container-runtime 182 | nvidia-container-runtime | 2.0.0+docker18.09.5-3 | https://nvidia.github.io/nvidia-container-runtime/ubuntu16.04/amd64 Packages 183 | nvidia-container-runtime | 2.0.0+docker18.09.5-1 | https://nvidia.github.io/nvidia-container-runtime/ubuntu16.04/amd64 Packages 184 | nvidia-container-runtime | 2.0.0+docker18.09.4-1 | https://nvidia.github.io/nvidia-container-runtime/ubuntu16.04/amd64 Packages 185 | nvidia-container-runtime | 2.0.0+docker18.09.3-1 | https://nvidia.github.io/nvidia-container-runtime/ubuntu16.04/amd64 Packages 186 | nvidia-container-runtime | 2.0.0+docker18.09.2-1 | https://nvidia.github.io/nvidia-container-runtime/ubuntu16.04/amd64 Packages 187 | nvidia-container-runtime | 2.0.0+docker18.09.1-1 | https://nvidia.github.io/nvidia-container-runtime/ubuntu16.04/amd64 Packages 188 | ···· 189 | # 查看版本对应的docker版本 190 | 191 | $ sudo apt-get install -y nvidia-docker2=2.0.3+docker17.12.1-1 nvidia-container-runtime=2.0.0+docker17.12.1-1 192 | # 最终选择这样匹配的版本 193 | ``` 194 | ```shell 195 | # 卸载docker 196 | $ apt autoremove docker-ce containerd.io 197 | 198 | ``` 199 | 200 | ## 第三步:配置显卡 201 | ```shell 202 | # 需要修改docker 的 203 | $ vim /etc/docker/daemon.json 204 | # 写入以下内容 205 | { 206 | "registry-mirrors": ["https://registry.docker-cn.com"], 207 | "default-runtime": "nvidia", 208 | "runtimes": { 209 | "nvidia": { 210 | "path": "nvidia-container-runtime", 211 | "runtimeArgs": [] 212 | } 213 | } 214 | } 215 | 216 | $ sudo pkill -SIGHUP dockerd 217 | 218 | $ docker run --runtime=nvidia --rm nvidia/cuda:9.0-base nvidia-smi 219 | +-----------------------------------------------------------------------------+ 220 | | NVIDIA-SMI 418.39 Driver Version: 418.39 CUDA Version: 10.1 | 221 | |-------------------------------+----------------------+----------------------+ 222 | | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | 223 | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | 224 | |===============================+======================+======================| 225 | | 0 GeForce RTX 208... Off | 00000000:19:00.0 Off | N/A | 226 | | 22% 35C P8 1W / 250W | 1MiB / 10989MiB | 0% Default | 227 | +-------------------------------+----------------------+----------------------+ 228 | | 1 GeForce RTX 208... Off | 00000000:1A:00.0 Off | N/A | 229 | | 22% 37C P8 17W / 250W | 1MiB / 10989MiB | 0% Default | 230 | +-------------------------------+----------------------+----------------------+ 231 | | 2 GeForce RTX 208... Off | 00000000:67:00.0 Off | N/A | 232 | | 22% 38C P8 7W / 250W | 1MiB / 10989MiB | 0% Default | 233 | +-------------------------------+----------------------+----------------------+ 234 | | 3 GeForce RTX 208... Off | 00000000:68:00.0 On | N/A | 235 | | 24% 43C P8 11W / 250W | 239MiB / 10981MiB | 0% Default | 236 | +-------------------------------+----------------------+----------------------+ 237 | 238 | +-----------------------------------------------------------------------------+ 239 | | Processes: GPU Memory | 240 | | GPU PID Type Process name Usage | 241 | |=============================================================================| 242 | +-----------------------------------------------------------------------------+ 243 | 244 | # 出现结果说明docker可以调用GPU了 245 | ``` 246 | 247 | 248 | 249 | ## 第四步:装kubeadm kubelet kubectl 250 | ``` shell 251 | # 安装最新版本kubernet管理套件 252 | $ apt-get update && apt-get install -y apt-transport-https 253 | 254 | $ curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - 255 | 256 | $ cat </etc/apt/sources.list.d/kubernetes.list 257 | deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main 258 | EOF 259 | 260 | $ apt-get update 261 | 262 | $ apt-get install -y kubelet kubeadm kubectl 263 | # 直接安装获取最新版本 264 | ``` 265 | ```shell 266 | # 获取指定的版本 267 | $ apt-cache madison kubeadm 268 | kubeadm | 1.14.1-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 269 | kubeadm | 1.14.0-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 270 | kubeadm | 1.13.5-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 271 | kubeadm | 1.13.4-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 272 | kubeadm | 1.13.3-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 273 | kubeadm | 1.13.2-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 274 | kubeadm | 1.13.1-00 | https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial/main amd64 Packages 275 | ···· 276 | apt-get install -y kubelet=1.12.8-00 kubeadm=1.12.8-00 kubectl=1.12.8-00 277 | ``` 278 | 279 | 280 | ```shell 281 | # 卸载 282 | apt autoremove kubeadm kubelet kubectl kubernetes-cni 283 | 284 | ``` 285 | # 二、k8s搭建 286 | ## 第一步:将子节点录入 287 | ```shell 288 | # 以下操作除开特别说明处都在master 192.168.0.105上执行 289 | 290 | $ kubeadm init --pod-network-cidr=10.244.0.0/16 291 | # 同kubeadm管理工具搭建kubernets,拉取与kubeadm版本一致的的kubernets镜像创建 292 | # --pod-network-cidr=10.244.0.0/16 集群内部网段 293 | kubeadm join 192.168.0.105:6443 --token aoanr5.geidnr74gvp5xrlc --discovery-token-ca-cert-hash sha256:beb198cf8a70ff17c96b387b06de16d6973f9b8cacb1a8e1586b52ff5f84db0c 294 | # 会在最后生成这个tocken 295 | 296 | $ mkdir -p $HOME/.kube 297 | $ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config 298 | 299 | $ sudo chown $(id -u):$(id -g) $HOME/.kube/config 300 | # 这3句命令使kubectl获取权限。用户配置,hash验证等信息,都在 $HOME/.kube/config 文件中 301 | 302 | 303 | ---------------------- 304 | # 这时在ndoe 192.168.0.115 操作,复制master上的tocken 305 | $ kubeadm join 192.168.0.105:6443 --token aoanr5.geidnr74gvp5xrlc --discovery-token-ca-cert-hash sha256:beb198cf8a70ff17c96b387b06de16d6973f9b8cacb1a8e1586b52ff5f84db0c 306 | --------------------- 307 | 308 | 309 | $ kubectl get nodes 310 | NAME STATUS ROLES AGE VERSION 311 | jiang-pc NoReady 21h v1.13.6 312 | jiangxing-pc NoReady master 21h v1.13.6 313 | # 就可以在master上看到所有的node机子了,但是还是noready状态。 314 | # 因为他们还不具备网段通讯的基础,所以需要安装网段策略。晚上 315 | ``` 316 | 317 | ## 第二步:安装网段策略 318 | * canal网段策略 319 | ```shell 320 | 321 | 322 | $ kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/rbac.yaml 323 | clusterrole.rbac.authorization.k8s.io "calico" created 324 | clusterrole.rbac.authorization.k8s.io "flannel" created 325 | clusterrolebinding.rbac.authorization.k8s.io "canal-flannel" created 326 | clusterrolebinding.rbac.authorization.k8s.io "canal-calico" create 327 | 328 | $ kubectl apply -f https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/canal.yaml 329 | configmap "canal-config" created 330 | daemonset.extensions "canal" created 331 | customresourcedefinition.apiextensions.k8s.io "felixconfigurations.crd.projectcalico.org" created 332 | customresourcedefinition.apiextensions.k8s.io "bgpconfigurations.crd.projectcalico.org" created 333 | customresourcedefinition.apiextensions.k8s.io "ippools.crd.projectcalico.org" created 334 | customresourcedefinition.apiextensions.k8s.io "clusterinformations.crd.projectcalico.org" created 335 | customresourcedefinition.apiextensions.k8s.io "globalnetworkpolicies.crd.projectcalico.org" created 336 | customresourcedefinition.apiextensions.k8s.io "networkpolicies.crd.projectcalico.org" created 337 | serviceaccount "canal" created 338 | # 配置内部网段的策略 339 | 340 | $ kubectl get pod -n kube-system -o wide 341 | NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE 342 | canal-hnbdf 3/3 Running 0 20h 192.168.0.105 jiangxing-pc 343 | canal-p9t59 3/3 Running 0 20h 192.168.0.115 jiang-pc 344 | coredns-576cbf47c7-6d58x 1/1 CrashLoopBackOff 45 21h 10.244.0.2 jiangxing-pc 345 | coredns-576cbf47c7-q6629 1/1 CrashLoopBackOff 46 21h 10.244.0.3 jiangxing-pc 346 | etcd-jiangxing-pc 1/1 Running 2 21h 192.168.0.105 jiangxing-pc 347 | kube-apiserver-jiangxing-pc 1/1 Running 4 21h 192.168.0.105 jiangxing-pc 348 | kube-controller-manager-jiangxing-pc 1/1 Running 2 21h 192.168.0.105 jiangxing-pc 349 | kube-proxy-bqms2 1/1 Running 2 21h 192.168.0.105 jiangxing-pc 350 | kube-proxy-vz9f8 1/1 Running 2 21h 192.168.0.115 jiang-pc 351 | kube-scheduler-jiangxing-pc 1/1 Running 2 21h 192.168.0.105 jiangxing-pc 352 | # 大概等待2分钟,需抓取镜像,时间看网速,一般1min,查看canal的安装状态 353 | # 我们看到coredns是启动不起来的,这个东西很重要,先吧不用着急,后面会讲解 354 | 355 | 356 | $ kubectl get nodes 357 | NAME STATUS ROLES AGE VERSION 358 | jiang-pc Ready 21h v1.12.8 359 | jiangxing-pc Ready master 21h v1.12.8 360 | # 然后你就可以看到节点都在Ready状态 361 | ``` 362 | * flannel网段策略 363 | ``` 364 | $ kubectl apply -f wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml 365 | 366 | $ kubectl get pod -n kube-sytem -o wide 367 | 368 | $ kubectl get nodes 369 | ``` 370 | ## 第三步:解决coredns pod CrashLoopBackOff 371 | ```shell 372 | # 为什么? 373 | # 因为ubuntu系统会不断地刷新/etc/resolv.conf的文件,所以一直导致coredns会缺少文件,孤儿不断的地 CrashLoopBackOff 374 | 375 | # 解决办法有2种, 376 | 377 | # 1.其中一种是编辑coredns的configmap配置 378 | $ kubectl -n kube-system edit configmap coredns 379 | # 在所有的节点都要操作 380 | # 然后删除显示 loop 的行,并保存配置。 381 | # k8s可能需要几分钟才能将配置更改传播到coredns pod 382 | #下面是删除后的样子,保存退出 383 | # Please edit the object below. Lines beginning with a '#' will be ignored, 384 | # and an empty file will abort the edit. If an error occurs while saving this file will be 385 | # reopened with the relevant failures. 386 | # 387 | apiVersion: v1 388 | data: 389 | Corefile: | 390 | .:53 { 391 | errors 392 | health 393 | kubernetes cluster.local in-addr.arpa ip6.arpa { 394 | pods insecure 395 | upstream 396 | fallthrough in-addr.arpa ip6.arpa 397 | } 398 | prometheus :9153 399 | proxy . /etc/resolv.conf 400 | cache 30 401 | reload 402 | loadbalance 403 | } 404 | kind: ConfigMap 405 | metadata: 406 | creationTimestamp: 2019-05-05T08:22:27Z 407 | name: coredns 408 | namespace: kube-system 409 | resourceVersion: "9385" 410 | selfLink: /api/v1/namespaces/kube-system/configmaps/coredns 411 | uid: eb157cfb-6f0e-11e9-92a9-0492264b2d9d 412 | 413 | 414 | # 2.关闭bunutu系统的network-manager 415 | $ sudo stop network-manager 416 | ``` 417 | 418 | # 三、kubeflow搭建 419 | 420 | ## 第一步:安装NVIDIA GPU device plugin 421 | ```shell 422 | # 自从k8s 1.8版本开始,官方开始推荐使用device plugin的方式来调用GPU使用。截至目前,Nvidia和AMD都推出了相#应的设备插件,使得k8s调用GPU变得容易起来。因为我们是Nvidia的显卡,所以需要安装NVIDIA GPU device plugin 423 | 424 | # 前面我们已经设置好了/etc/docker/daemon.json配置 425 | 426 | # 通过kubeadm部署的Kubernetes cluster,需要打开kubeadm 的systemd unit文件,位于 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 然后添加下面的参数作为环境变量 427 | # 同样在所有节点都需要配置 428 | $ vim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 429 | # 添加下面所示的行 430 | Environment="KUBELET_GPU_ARGS=--feature-gates=DevicePlugins=true" 431 | 432 | 433 | # 完整文件如下 434 | # Note: This dropin only works with kubeadm and kubelet v1.11+ 435 | [Service] 436 | Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" 437 | Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" 438 | Environment="KUBELET_GPU_ARGS=--feature-gates=DevicePlugins=true" 439 | # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically 440 | EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env 441 | # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use 442 | # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. 443 | EnvironmentFile=-/etc/default/kubelet 444 | ExecStart= 445 | ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS 446 | 447 | 448 | $ sudo systemctl daemon-reload 449 | $ sudo systemctl restart kubelet 450 | # 重新载入配置文件,然后重新启动服务 451 | 452 | # 完成所有的GPU节点的选项启用,然后就可以在在Kubernetes中启用GPU支持,通过安装Nvidia提供的Daemonset服务来实现,方法如下(2选1,推荐使用v1.11): 453 | $ kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.11/nvidia-device-plugin.yml 454 | $ kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.10/nvidia-device-plugin.yml 455 | 456 | 457 | 458 | # 查看nvidia-device-plugin 的 pod是否启动,会拉取镜像,时间看网速 459 | $ kubectl get pod -n kube-system -o wide 460 | NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE 461 | canal-hnbdf 3/3 Running 0 21h 192.168.0.105 jiangxing-pc 462 | canal-p9t59 3/3 Running 0 21h 192.168.0.115 jiang-pc 463 | coredns-576cbf47c7-6d58x 1/1 Running 45 22h 10.244.0.2 jiangxing-pc 464 | coredns-576cbf47c7-q6629 1/1 Running 46 22h 10.244.0.3 jiangxing-pc 465 | etcd-jiangxing-pc 1/1 Running 2 22h 192.168.0.105 jiangxing-pc 466 | kube-apiserver-jiangxing-pc 1/1 Running 4 22h 192.168.0.105 jiangxing-pc 467 | kube-controller-manager-jiangxing-pc 1/1 Running 2 22h 192.168.0.105 jiangxing-pc 468 | kube-proxy-bqms2 1/1 Running 2 22h 192.168.0.105 jiangxing-pc 469 | kube-proxy-vz9f8 1/1 Running 2 22h 192.168.0.115 jiang-pc 470 | kube-scheduler-jiangxing-pc 1/1 Running 2 22h 192.168.0.105 jiangxing-pc 471 | nvidia-device-plugin-daemonset-1.12-8s2vt 1/1 Running 0 21h 10.244.1.3 jiang-pc 472 | nvidia-device-plugin-daemonset-99w5k 1/1 Running 0 21h 10.244.1.2 jiang-pc 473 | 474 | 475 | # 可以用以下命令测试device-plugin在节点上是否生效 476 | $ docker run --security-opt=no-new-privileges --cap-drop=ALL --network=none -it -v /var/lib/kubelet/device-plugins:/var/lib/kubelet/device-plugins nvidia/k8s-device-plugin:1.11 477 | # 出现以下信息代表没生效 478 | 2018/11/08 02:58:17 Loading NVML 479 | 2018/11/08 02:58:17 Failed to initialize NVML: could not load NVML library. 480 | 2018/11/08 02:58:17 If this is a GPU node, did you set the docker default runtime to nvidia? 481 | 2018/11/08 02:58:17 You can check the prerequisites at: https://github.com/NVIDIA/k8s-device-plugin#prerequisites 482 | 2018/11/08 02:58:17 You can learn how to set the runtime at: https://github.com/NVIDIA/k8s-device-plugin#quick-start 483 | 484 | # 出现以下信息代表生效了 485 | 2018/11/08 02:58:46 Loading NVML 486 | 2018/11/08 02:58:46 Fetching devices. 487 | 2018/11/08 02:58:46 Starting FS watcher. 488 | 2018/11/08 02:58:46 Starting OS watcher. 489 | 2018/11/08 02:58:46 Starting to serve on /var/lib/kubelet/device-plugins/nvidia.sock 490 | 2018/11/08 02:58:46 Registered device plugin with Kubelet 491 | 492 | 493 | 494 | # 安装了device-plugin后,device-plugin Pod正常启动,但是依旧无法调用GPU资源,查看Node节点信息发现GPU处数量为0 495 | # 当时遇见该问题的过程为:修改Docker配置,安装device-plugin,无法启动,重启Docker,device-plugin启动正常,但GPU无法调用 496 | # 原因就在于启动device-plugin的时候Docker配置没有生效,虽然后续重启Docker了,但是device-plugin的Pod没有更新,所以依旧捕获不到GPU信息。解决办法是杀掉device-plugin的Pod,让其重新生成 497 | 498 | 499 | $ kubectl describe nodes 500 | # 查看节点的信息,就会看到节点信息中存在 nvidia.com/gpu 501 | ··· 502 | Addresses: 503 | InternalIP: 192.168.0.115 504 | Hostname: jiang-pc 505 | Capacity: 506 | cpu: 20 507 | ephemeral-storage: 245016792Ki 508 | hugepages-1Gi: 0 509 | hugepages-2Mi: 0 510 | memory: 32600796Ki 511 | nvidia.com/gpu: 4 512 | pods: 110 513 | Allocatable: 514 | cpu: 20 515 | ephemeral-storage: 225807475134 516 | hugepages-1Gi: 0 517 | hugepages-2Mi: 0 518 | memory: 32498396Ki 519 | nvidia.com/gpu: 4 520 | pods: 110 521 | ··· 522 | 523 | 524 | ``` 525 | ## 第二步:安装ksonet与kfctl 526 | ```shell 527 | # 什么是ksonnet? 528 | # 他是一个kubernet的快速部署应用的一个工具,迁移一个应用时就会用到他,所以安装kubeflow时就会用到他,因为kubeflow其实是多个子应用组成的综合性服务 529 | 530 | 531 | # 先下载ksonnet安装包,下载地址 https://github.com/ksonnet/ksonnet/releases,拿到最新版本的链接 532 | # 教程使用的版本是ks_0.13.1_linux_amd64.tar.gz 533 | $ wget https://github.com/ksonnet/ksonnet/releases/download/v0.13.1/ks_0.13.1_linux_amd64.tar.gz 534 | 535 | $ tar -xvf ks_0.13.1_linux_amd64.tar.gz 536 | 537 | $ cp ks_0.13.1_linux_amd64/* /usr/local/bin/ 538 | 539 | 540 | 541 | # 什么是kfctl? 542 | # 他是一个部署kubeflow的脚本工具 543 | # 下载地址https://github.com/kubeflow/kubeflow/releases/ 544 | # 教程使用的版本时kfctl_v0.5.0_linux.tar.gz 545 | $ wget https://github.com/kubeflow/kubeflow/releases/download/v0.5.0/kfctl_v0.5.0_linux.tar.gz 546 | 547 | $ tar -zxvf kfctl_v0.5.0_linux.tar.gz 548 | 549 | $ cp kfctl /usr/bin/ 550 | ``` 551 | 552 | ## 第三步:部署kubeflow 553 | ```shell 554 | $ export KFAPP=mykfapp 555 | # export KFAPP=<你的kubeflow app 名字> 556 | 557 | $ kfctl init ${KFAPP} 558 | # 初始化 559 | 560 | $ cd ${KFAPP} 561 | # 进入生成的mykfapp命令 562 | # 有文件app.yaml 563 | $ kfctl generate k8s -V 564 | 565 | $ kfctl apply k8s -V 566 | # 有文件app.yaml ks_app 567 | ``` 568 | ## 第四步:安装seldon 569 | ```shell 570 | 571 | # 目前Kubeflow使用kso​​nnet管理包 572 | # kfctl在kubeflow应用程序中创建一个名为ks_app的ksonnet应用程序。随附kubeflow的Ksonnet软件包可以通过运行ks pkg install kubeflow / [package_name]进入ks_app目录来安装。 573 | 574 | $ cd ks_app 575 | 576 | $ ks pkg install kubeflow/seldon 577 | 578 | $ ks pkg list 579 | # ks_app目录下执行 580 | # 观看安装的包 581 | REGISTRY NAME VERSION INSTALLED ENVIRONMENTS 582 | ======== ==== ======= ========= ============ 583 | kubeflow application master 584 | kubeflow argo * 585 | kubeflow argo master 586 | kubeflow automation master 587 | kubeflow chainer-job master 588 | kubeflow common * 589 | kubeflow common master 590 | kubeflow examples * 591 | kubeflow examples master 592 | kubeflow gcp * 593 | kubeflow gcp master 594 | kubeflow jupyter * 595 | kubeflow jupyter master 596 | kubeflow katib * 597 | kubeflow katib master 598 | kubeflow kubebench master 599 | kubeflow metacontroller * 600 | kubeflow metacontroller master 601 | kubeflow modeldb * 602 | kubeflow modeldb master 603 | kubeflow mpi-job * 604 | kubeflow mpi-job master 605 | kubeflow mxnet-job master 606 | kubeflow new-package-stub master 607 | kubeflow nvidia-inference-server master 608 | kubeflow openvino master 609 | kubeflow pachyderm master 610 | kubeflow pipeline * 611 | kubeflow pipeline master 612 | kubeflow profiles master 613 | kubeflow pytorch-job * 614 | kubeflow pytorch-job master 615 | kubeflow seldon * 616 | kubeflow seldon master 617 | kubeflow tensorboard * 618 | kubeflow tensorboard master 619 | kubeflow tf-batch-predict master 620 | kubeflow tf-serving * 621 | kubeflow tf-serving master 622 | kubeflow tf-training * 623 | kubeflow tf-training master 624 | 625 | $ ks generate seldon seldon 626 | 627 | 628 | $ ks component list 629 | # 您应该在此列表中看到seldon,如果不是,请仔细检查您的生成是否成功。 630 | COMPONENT 631 | ========= 632 | ambassador 633 | application 634 | argo 635 | centraldashboard 636 | cert-manager 637 | cloud-endpoints 638 | iap-ingress 639 | jupyter 640 | katib 641 | metacontroller 642 | notebooks 643 | openvino 644 | pipeline 645 | profiles 646 | pytorch-operator 647 | seldon 648 | spartakus 649 | tf-job-operator 650 | 651 | $ ks apply default -c seldon 652 | ``` 653 | ## 第五步:观察kubeflow是否成功部署 654 | 655 | ```shell 656 | $ kubectl get pod --all-namespaces 657 | # 通过观看pod节点的的运行状态 658 | # 刚开始时会有很多pod节点都在CrashLoopBackOff状态,是因为都需要拿去镜像建立 659 | # 大概等待30分钟,就可以进入running 660 | # 一定要进入running状态的是 661 | # 下面是pod信息 662 | NAMESPACE NAME READY STATUS RESTARTS AGE 663 | kube-system pod/canal-hnbdf 3/3 Running 0 22h 664 | kube-system pod/canal-p9t59 3/3 Running 0 22h 665 | kube-system pod/coredns-576cbf47c7-6d58x 1/1 Running 45 23h 666 | kube-system pod/coredns-576cbf47c7-q6629 1/1 Running 46 23h 667 | kube-system pod/etcd-jiangxing-pc 1/1 Running 2 23h 668 | kube-system pod/kube-apiserver-jiangxing-pc 1/1 Running 4 23h 669 | kube-system pod/kube-controller-manager-jiangxing-pc 1/1 Running 2 23h 670 | kube-system pod/kube-proxy-bqms2 1/1 Running 2 23h 671 | kube-system pod/kube-proxy-vz9f8 1/1 Running 2 23h 672 | kube-system pod/kube-scheduler-jiangxing-pc 1/1 Running 2 23h 673 | kube-system pod/nvidia-device-plugin-daemonset-1.12-8s2vt 1/1 Running 0 22h 674 | kube-system pod/nvidia-device-plugin-daemonset-99w5k 1/1 Running 0 22h 675 | kubeflow pod/ambassador-7b8477f667-4bjbq 1/1 Running 34 22h 676 | kubeflow pod/ambassador-7b8477f667-kzn8g 1/1 Running 15 22h 677 | kubeflow pod/ambassador-7b8477f667-x2l6p 1/1 Running 23 22h 678 | kubeflow pod/argo-ui-9cbd45fdf-vd4xp 1/1 Running 0 22h 679 | kubeflow pod/centraldashboard-796c755dcf-cqmcn 1/1 Running 0 22h 680 | kubeflow pod/jupyter-web-app-6866fc55f9-d9rjb 1/1 Running 0 22h 681 | kubeflow pod/katib-ui-7c6997fd96-zzwkr 1/1 Running 0 22h 682 | kubeflow pod/metacontroller-0 1/1 Running 0 22h 683 | kubeflow pod/minio-594df758b9-p2wp7 0/1 Pending 0 22h 684 | kubeflow pod/ml-pipeline-59bc76b9cf-nvwr4 1/1 Running 163 22h 685 | kubeflow pod/ml-pipeline-persistenceagent-6b47685656-8mt5c 0/1 CrashLoopBackOff 178 22h 686 | kubeflow pod/ml-pipeline-scheduledworkflow-75bf95745d-77xkm 1/1 Running 0 22h 687 | kubeflow pod/ml-pipeline-ui-7f7bb7df6d-gzb66 1/1 Running 0 22h 688 | kubeflow pod/ml-pipeline-viewer-controller-deployment-5bd64877d8-b778r 1/1 Running 0 22h 689 | kubeflow pod/mysql-5d5b5475c4-mhz8l 0/1 Pending 0 22h 690 | kubeflow pod/notebooks-controller-685db44f8c-5kz5g 1/1 Running 0 22h 691 | kubeflow pod/pytorch-operator-9996bcb49-6c6l8 1/1 Running 0 22h 692 | kubeflow pod/seldon-redis-6c867f7c9d-n7cqm 1/1 Running 0 22h 693 | kubeflow pod/seldon-seldon-cluster-manager-7fd685c95-h6kwm 1/1 Running 0 22h 694 | kubeflow pod/sencond-0 1/1 Running 0 6h13m 695 | kubeflow pod/shenmingjie-0 1/1 Running 0 5h9m 696 | kubeflow pod/studyjob-controller-57cb6746ff-jvcv5 1/1 Running 0 22h 697 | kubeflow pod/tensorboard-76dffc9ffc-kcjbs 1/1 Running 0 22h 698 | kubeflow pod/tf-job-dashboard-84bdddd5cc-rlqd5 1/1 Running 0 22h 699 | kubeflow pod/tf-job-operator-8486555578-hjmft 1/1 Running 0 22h 700 | kubeflow pod/vizier-core-bcc86677d-mn786 0/1 CrashLoopBackOff 417 22h 701 | kubeflow pod/vizier-core-rest-68c7577f84-dqc7j 1/1 Running 0 22h 702 | kubeflow pod/vizier-db-54f46c46c6-bbzn6 0/1 Pending 0 22h 703 | kubeflow pod/vizier-suggestion-bayesianoptimization-97f4f76dd-7mng6 1/1 Running 0 22h 704 | kubeflow pod/vizier-suggestion-grid-6f94f98f9d-56ch7 1/1 Running 0 22h 705 | kubeflow pod/vizier-suggestion-hyperband-68f4cc7f5d-8g66j 1/1 Running 0 22h 706 | kubeflow pod/vizier-suggestion-random-6ff5b8f6d8-9sw6z 1/1 Running 0 22h 707 | kubeflow pod/workflow-controller-d5cb6468d-gxsct 1/1 Running 0 22h 708 | 709 | ``` 710 | 711 | ## 第六步:kubeflow web ui 712 | ```shell 713 | # 在master上配置ambassador映射ip 714 | $ kubectl -n kubeflow edit svc ambassador 715 | # 文中加入externalIPs + 你master的ip,譬如: 716 | spec: 717 | clusterIP: 10.107.127.1 718 | externalIPs: # Newly added 719 | - 192.168.0.105 # Newly added 720 | externalTrafficPolicy: Cluster 721 | 722 | $ export NAMESPACE=kubeflow 723 | $ kubectl port-forward svc/ambassador -n ${NAMESPACE} 8080:80 724 | # 映射到80端口 725 | # 就可以访问了http://localhost:8080/ 726 | ``` 727 | 728 | # 四、持久化 729 | 配置kubelfow,我们会将dockers中得出来的结果,与代码需要保存到本地,以防机子意外关机导致数据全部丢失 730 | 由于挂在的持久卷是基于nfs服务的,所以我们需要在集群中先安装nfs服务。 731 | 732 | 733 | ### 生命周期 734 | pv和pvc遵循以下生命周期: 735 | 736 | 供应准备。通过集群外的存储系统或者云平台来提供存储持久化支持。 737 | - 静态提供:管理员手动创建多个PV,供PVC使用。 738 | - 动态提供:动态创建PVC特定的PV,并绑定。 739 | 绑定。用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。 740 | 使用。用户可在pod中像volume一样使用pvc。 741 | 释放。用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。 742 | 743 | 回收(Reclaiming)。pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。 744 | - 保留策略:允许人工处理保留的数据。 745 | - 删除策略:将删除pv和外部关联的存储资源,需要插件支持。 746 | - 回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。 747 | 748 | ## 第一步:安装nfs服务 749 | ``` 750 | # 在master中执行: 751 | $ sudo apt-get update && sudo apt-get install -y nfs-server 752 | $ sudo mkdir /nfs-data/kubeflow-pv1 753 | # 修改/etc/exports文件,设置vol的权限 754 | /nfs-data/kubeflow-pv1 *(rw,sync,no_root_squash,no_subtree_check) 755 | 756 | /mnt/sda/work-vol-1 *(rw,sync,no_root_squash,no_subtree_check) 757 | /mnt/sda/work-vol-2 *(rw,sync,no_root_squash,no_subtree_check) 758 | /mnt/sda/work-vol-3 *(rw,sync,no_root_squash,no_subtree_check) 759 | /mnt/sda/data-vol-1 *(rw,sync,no_root_squash,no_subtree_check) 760 | /mnt/sda/data-vol-2 *(rw,sync,no_root_squash,no_subtree_check) 761 | /mnt/sda/data-vol-3 *(rw,sync,no_root_squash,no_subtree_check 762 | /mnt/sdb/work-vol-4 *(rw,sync,no_root_squash,no_subtree_check) 763 | /mnt/sdb/work-vol-5 *(rw,sync,no_root_squash,no_subtree_check) 764 | /mnt/sdb/work-vol-6 *(rw,sync,no_root_squash,no_subtree_check) 765 | /mnt/sdb/data-vol-4 *(rw,sync,no_root_squash,no_subtree_check) 766 | /mnt/sdb/data-vol-5 *(rw,sync,no_root_squash,no_subtree_check) 767 | /mnt/sdb/data-vol-6 *(rw,sync,no_root_squash,no_subtree_check) 768 | /mnt/sdc/work-vol-7 *(rw,sync,no_root_squash,no_subtree_check) 769 | /mnt/sdc/work-vol-8 *(rw,sync,no_root_squash,no_subtree_check) 770 | /mnt/sdc/work-vol-9 *(rw,sync,no_root_squash,no_subtree_check) 771 | /mnt/sdc/data-vol-7 *(rw,sync,no_root_squash,no_subtree_check) 772 | /mnt/sdc/data-vol-8 *(rw,sync,no_root_squash,no_subtree_check) 773 | /mnt/sdc/data-vol-9 *(rw,sync,no_root_squash,no_subtree_check) 774 | 775 | # 重启服务读入配置 776 | $ sudo /etc/init.d/nfs-kernel-server restart 777 | ``` 778 | ``` 779 | # 在node节点,安装nfs的client 780 | $ sudo apt-get update && sudo apt-get install -y nfs-common 781 | ``` 782 | ## 第二步:创建pv 783 | ``` 784 | # 创建pv 785 | # 使用如下yaml 786 | apiVersion: v1 787 | kind: PersistentVolume 788 | metadata: 789 | name: {yourname} 790 | namespace: {yournamespaces}(kubeflow) 791 | spec: 792 | capacity: 793 | storage: {yourstorge}(20GI) 794 | accessModes: 795 | - ReadWriteOnce 796 | nfs: 797 | server: {your nfs server ip} 798 | path: {your vol path} 799 | ``` 800 | ## 第三步:创建pvc 801 | ``` 802 | # 创建pvc 803 | # 使用如下yaml 804 | kind: PersistentVolumeClaim 805 | apiVersion: v1 806 | metadata: 807 | name: {yourname} 808 | namespace: {yournamesapaces}(kuebflow) 809 | spec: 810 | accessModes: 811 | - ReadWriteOnce 812 | resources: 813 | requests: 814 | storage: {yourstorge}(5GI) 815 | ``` 816 | 817 | 818 | 819 | 820 | 821 | ## 第四步:结果 822 | ``` 823 | $ kubectl get pv -n kubelfow 824 | NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE 825 | data-vol-1 450Gi RWO Retain Bound kubeflow/data-vol-1 7d16h 826 | data-vol-2 450Gi RWO Retain Bound kubeflow/data-vol-2 7d16h 827 | data-vol-3 450Gi RWO Retain Bound kubeflow/data-vol-3 7d16h 828 | data-vol-4 450Gi RWO Retain Bound kubeflow/data-vol-4 7d16h 829 | data-vol-5 450Gi RWO Retain Bound kubeflow/data-vol-5 7d16h 830 | data-vol-6 450Gi RWO Retain Bound kubeflow/data-vol-6 7d16h 831 | data-vol-7 450Gi RWO Retain Bound kubeflow/data-vol-7 7d16h 832 | data-vol-8 450Gi RWO Retain Bound kubeflow/data-vol-8 7d16h 833 | data-vol-9 450Gi RWO Retain Bound kubeflow/data-vol-9 7d16h 834 | pv1 20Gi RWO Retain Bound kubeflow/minio-pvc 11d 835 | pv2 20Gi RWO Retain Bound kubeflow/katib-mysql 11d 836 | vizier-pv 20Gi RWO Retain Bound kubeflow/mysql-pv-claim 11d 837 | work-vol-1 100Gi RWO Retain Bound kubeflow/work-vol-1 7d16h 838 | work-vol-2 100Gi RWO Retain Bound kubeflow/work-vol-2 7d16h 839 | work-vol-3 100Gi RWO Retain Bound kubeflow/work-vol-3 7d16h 840 | work-vol-4 100Gi RWO Retain Bound kubeflow/work-vol-4 7d16h 841 | work-vol-5 100Gi RWO Retain Bound kubeflow/work-vol-5 7d16h 842 | work-vol-6 100Gi RWO Retain Bound kubeflow/work-vol-6 7d16h 843 | work-vol-7 100Gi RWO Retain Bound kubeflow/work-vol-7 7d16h 844 | work-vol-8 100Gi RWO Retain Bound kubeflow/work-vol-8 7d16h 845 | work-vol-9 100Gi RWO Retain Bound kubeflow/work-vol-9 7d16h 846 | 847 | 848 | 849 | $ kubectl get pvc -n kubeflow 850 | NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE 851 | data-vol-1 Bound data-vol-1 450Gi RWO 7d16h 852 | data-vol-2 Bound data-vol-2 450Gi RWO 7d16h 853 | data-vol-3 Bound data-vol-3 450Gi RWO 7d16h 854 | data-vol-4 Bound data-vol-4 450Gi RWO 7d16h 855 | data-vol-5 Bound data-vol-5 450Gi RWO 7d16h 856 | data-vol-6 Bound data-vol-6 450Gi RWO 7d16h 857 | data-vol-7 Bound data-vol-7 450Gi RWO 7d16h 858 | data-vol-8 Bound data-vol-8 450Gi RWO 7d16h 859 | data-vol-9 Bound data-vol-9 450Gi RWO 7d16h 860 | katib-mysql Bound pv2 20Gi RWO 11d 861 | minio-pvc Bound pv1 20Gi RWO 32d 862 | mysql-pv-claim Bound vizier-pv 20Gi RWO 32d 863 | work-vol-1 Bound work-vol-1 100Gi RWO 7d16h 864 | work-vol-2 Bound work-vol-2 100Gi RWO 7d16h 865 | work-vol-3 Bound work-vol-3 100Gi RWO 7d16h 866 | work-vol-4 Bound work-vol-4 100Gi RWO 7d16h 867 | work-vol-5 Bound work-vol-5 100Gi RWO 7d16h 868 | work-vol-6 Bound work-vol-6 100Gi RWO 7d16h 869 | work-vol-7 Bound work-vol-7 100Gi RWO 7d16h 870 | work-vol-8 Bound work-vol-8 100Gi RWO 7d16h 871 | work-vol-9 Bound work-vol-9 100Gi RWO 7d16h 872 | ``` 873 | 874 | 875 | ## 过程: 876 | * 创建pod的时候,kuebflow的pod节点会在pvc中搜索对应名字的 pvc 877 | * 先创建对应名字的pvc,pvc会搜索空间足够的pv 878 | * 如果没有,那么我们就需要创建一个pv,他们会自动的进行绑定 879 | 880 | 881 | # 五、使用方法 882 | 1. 登录到网站 883 | 2. 左侧栏选择notebooks 884 | ![notebook界面](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/notebook_view.png) 885 | 3. 点击newserver 886 | 4. 创建名字,选镜像 887 | 5. 配置CPU与MEM 888 | 6. 配置Volume(暂时不可以使用,不然server会创建不了) 889 | 7. 选择GPU数量 890 | 资源是独占的 所以最多4个服务可以用显卡, 暂时不知道什么原因 2张1080ti没显示出来,4张2080ti可以用。 891 | 892 | 893 | # 六、其他 894 | ## GPU驱动cuda与cudnn 895 | 本教程主要针对-新装机-进行配置,使用过一段时间的机子可能会在 步骤4安装***.run 报错误 896 | 897 | 步骤1: 898 | 899 | * 首先先安装ubuntu16.04 900 | tips:在选择是否安装第三方驱动和更新时,不选择,直接跳过。 901 | 902 | 步骤2: 903 | * 进入系统,打开浏览器先下载cuda,选择.run格式的驱动(cuda_10.1.105_418.39_linux.run),将文件复制到home目录下 904 | tips:由于cuda里自带了驱动,所以我们可以跳过安装驱动的步骤 905 | ![nvida_web](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/nvida_web.png) 906 | 907 | 步骤3: 908 | * 打开终端(vim没安装的 需要先安装) 909 | 910 | ``` 911 | sudo apt-get install vim 912 | ``` 913 | 914 | * 将nouveau禁用 915 | ``` 916 | sudo vim /etc/modprobe.d/blacklist.conf 917 | ``` 918 | * 在vim中进入编辑状态,下拉到文件末尾,添加下面内容: 919 | 920 | ``` 921 | blacklist nouveau 922 | options nouveau modeset=0 923 | ``` 924 | * 保存退出 925 | * 更新刚才的配置 926 | 927 | ``` 928 | sudo update-initramfs -u 929 | ``` 930 | * 重启 931 | 932 | ``` 933 | sudo reboot 934 | ``` 935 | 步骤4 936 | * 打开终端,关闭图形界面 937 | 938 | ``` 939 | sudo service lightdm stop 940 | ``` 941 | tips:会进入黑屏,正常情况 942 | * 键入Ctrl +Alt+F1,输入用户名,输入密码。发现进入home目录 943 | * 修改文件的读写权限 944 | 945 | ``` 946 | sudo chmod 777 *.run 947 | ``` 948 | * 安装驱动与cuda 949 | ``` 950 | sudo ./cuda_10.1.105_418.39_linux.run 951 | 952 | ``` 953 | ![cuda1](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/cuda1.jpg) 954 | ![cuda2](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/cuda2.jpg) 955 | 步骤5 956 | * 等待30s左右出现选择界面 957 | * 只需全部选择即可 958 | * 安装完成后重启 959 | * 进入终端输入,出现显卡信息和cuda的版本信息就说明驱动已经安装完成 960 | ``` 961 | nvidia-smi 962 | ``` 963 | 步骤6 964 | * 在终端输入 965 | ``` 966 | sudo gedit ~/.bashrc 967 | ``` 968 | * 在末尾添加 969 | tips:若安装不一样的版本,请更改好对应的路径名 970 | ``` 971 | export PATH=/usr/local/cuda-10.1/bin:$PATH 972 | export LD_LIBRARY_PATH=/usr/local/cuda-10.1/lib64:$LD_LIBRARY_PATH 973 | ``` 974 | * 终端输入,出现cuda的信息说明cuda安装完成 975 | ``` 976 | source ~/.bashrc 977 | nvcc --version 978 | ``` 979 | 步骤7 980 | * cuda安装好,还需要配置cudnn 981 | * 到官网下载cudnn,点击download,会让你先登录账号,先注册 982 | ![cudnn](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/cudnn1.png) 983 | * 注册勾选同意协议会出现如下界面 984 | ![cudnn](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/cudnn2.png) 985 | * 选择对应cuda版本的cudnn,本教程使用的时cuda10.1,所以选择第一个,在选择cuDNN Library for Linux 986 | ![cudnn](https://jk-97.github.io/my_note/k8sandkubeflow/sources/images/cudnn3.png) 987 | * 解压到当前目录,会有cuda这个文件夹 988 | * 在终端执行以下命令 989 | tips:若安装不一样的版本,请更改好对应的路径名 990 | ``` 991 | sudo cp cuda/include/cudnn.h /usr/local/cuda-10.1/include 992 | sudo cp cuda/lib64/libcudnn* /usr/local/cuda-10.1/lib64 993 | sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda-10.1/lib64/libcudnn* 994 | source ~/.bashrc 995 | 996 | ``` 997 | * 检查cudnn是否配置完成 998 | ``` 999 | nvcc --version 1000 | cd /usr/local/cuda-10.0/samples/1_Utilities/deviceQuery 1001 | sudo make 1002 | ./deviceQuery 1003 | ``` 1004 | * 出现pass说明环境搭建好了 1005 | -------------------------------------------------------------------------------- /databases/从零开始写数据库.html: -------------------------------------------------------------------------------- 1 | 2 | 从零开始写数据库 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 263 | 264 | 265 |
266 |

从零开始----nosql数据库(golang )

267 | 268 |

数据库的需求

269 | 270 |
    271 |
  • 需要有索引,快速地找到数据
  • 272 |
  • 需要满足ACID
  • 273 |
274 |

学习路线

275 | 276 |

我们首先不先讲什么理论知识,这些东西对于一点都没有概念的人来说会感觉很抽象。现在的开源社区这么多的项目,这些项目就是很好的学习资源。这里先由我通过分析开源项目的源码,来一步一步剖析原理,编写一个简单的数据库需要什么准备。

277 |

nustdb源码分析

278 | 279 |

https://github.com/xujiajun/nutsdb
280 | 这个一个用golang编写的k/v数据库,该仓库的的社区还持续更新着。
281 | 我们先把这个repo克隆下来,跟着开源作者的思路来一步一步构建数据库的概念。chekout第一次提交。

282 |
git clone https://github.com/xujiajun/nutsdb.git
283 | git checkout 7c0a5b6
284 | 

那我们就可以看到以下目录结构(将无关的文件忽略掉了),先大概讲下这里的文件的作用

285 |
├── bptree.go                     # 写了一些B+树的操作
286 | ├── datafile.go                   # 写了存放数据文件抽象
287 | ├── db.go                         # 数据库的抽象
288 | ├── entry.go                      # 数据包装
289 | ├── options.go		              # 数据库的配置选项
290 | ├── tx.go                         # 事务的包装
291 | └──  utils.go
292 | 

那么我们知道的了它们的调用关系

293 |
options -> db -> tx -> entry -> datafile
294 |                              -> bptree
295 | 

在这个commit的文件中,我们可以看到example文件夹,我们选择 example/basic/main.go作为分析的大门。
296 | 函数 :db.update()

297 |
func put() {
298 | 	if err := db.Update(
299 | 		func(tx *nutsdb.Tx) error {
300 | 			key := []byte("name1")
301 | 			val := []byte("val1")
302 | 			if err := tx.Put(bucket, key, val, 0); err != nil {
303 | 				return err
304 | 			}
305 | 			return nil
306 | 		}); err != nil {
307 | 		log.Fatal(err)
308 | 	}
309 | }
310 | 

函数 :db.Update -> db.managed() ;看到这段代码我 们就大概知道nutsdb数据库写操作时的思路了。看下面的代码注释,主要分为了3步:

311 |
    312 |
  • 创建事务
  • 313 |
  • 预执行
  • 314 |
  • 提交修改
  • 315 |
316 |
// managed calls a block of code that is fully contained in a transactio
317 | func (db *DB) managed(writable bool, fn func(tx *Tx) error) (err error) {
318 | 	# writeable 是否可写 ,需要对数据库进行修改时 传参为true
319 | 	var tx *Tx
320 | 	tx, err = db.Begin(writable)
321 | 	# 1->创建事务,并且加锁,保证当前事务独占
322 | 	if err != nil {
323 | 		return
324 | 	}
325 | 
326 | 	if err = fn(tx); err != nil {
327 | 	#  2->执行数据库的操作,还没写入文件,这时执行put操作,用一个切片进行保存
328 | 		err = tx.Rollback()
329 | 		# 操作失败,进行回滚
330 | 		return
331 | 	}
332 | 
333 | 	if err = tx.Commit(); err != nil {
334 | 	#  3->提交修改,将切片的数据写入文件,并创建索引
335 | 		err = tx.Rollback()
336 | 		return
337 | 	}
338 | 
339 | 	return
340 | }
341 | 

下面们分析下这3个步里具体做了什么?

342 |

1--> db.Begin()

343 |
# tx的结构题 ->代表事务
344 | type Tx struct {
345 | 	id            uint64    # 事务id
346 | 	db            *DB       # 事务所执行的db
347 | 	writable      bool      # 是否有写操作
348 | 	pendingWrites []*Entry  # 等待写入的数据 
349 | }
350 | 
351 | func (db *DB) Begin(writable bool) (tx *Tx, err error) {
352 | 	tx, err = newTx(db, writable)
353 | 	# 创建事务实例
354 | 	if err != nil {
355 | 		return nil, err
356 | 	}
357 | 
358 | 	tx.lock()
359 | 	# 加上事务锁
360 | 	if db.closed {
361 | 		tx.unlock()
362 | 		return nil, ErrDBClosed
363 | 	}
364 | 
365 | 	return
366 | }
367 | 

2--> db.Put()
368 | 第二步是参数传入的函数,即db.update() 里的函数。主要就是创建一对键值数据,然后调用db.Put()函数。

369 |
func (tx *Tx) put(bucket string, key, value []byte, ttl uint32, flag uint16, timestamp uint64) error {
370 | 	# bucket      相当于mongodb 的collection ,可称表名
371 | 	# key,value   即将存入的数据
372 | 	# ttl         过期时间
373 | 	# timestamp   时间戳
374 | 	if err := tx.checkTxIsClosed(); err != nil {
375 | 		return err
376 | 	}
377 | 
378 | 	if !tx.writable {
379 | 		return ErrTxNotWritable
380 | 	}
381 | 
382 | 	if len(key) == 0 {
383 | 		return ErrKeyEmpty
384 | 	}
385 | 	# entry 是每一条数据的包装,将准备要写的数据其append到tx.pendingWrites 待写
386 | 	tx.pendingWrites = append(tx.pendingWrites, &Entry{
387 | 		Key:   key,
388 | 		Value: value,
389 | 		Meta: &MetaData{
390 | 			keySize:    uint32(len(key)),
391 | 			valueSize:  uint32(len(value)),
392 | 			timestamp:  timestamp,
393 | 			Flag:       flag,
394 | 			TTL:        ttl,
395 | 			bucket:     []byte(bucket),
396 | 			bucketSize: uint32(len(bucket)),
397 | 			status:     UnCommitted,
398 | 			txId:       tx.id,
399 | 		},
400 | 	})
401 | 
402 | 	return nil
403 | }
404 | 
// 5. Unlock the database and clear the db field.
405 | func (tx *Tx) Commit() error {
406 | 	# 在函数进行写入操作
407 | 	var e *Entry
408 | 
409 | 	if tx.db == nil {
410 | 		return ErrDBClosed
411 | 	}
412 | 
413 | 	writesLen := len(tx.pendingWrites)
414 | 
415 | 	if writesLen == 0 {
416 | 		tx.unlock()
417 | 		tx.db = nil
418 | 		return nil
419 | 	}
420 | 
421 | 	for i := 0; i < writesLen; i++ {
422 | 		entry := tx.pendingWrites[i]
423 | 		entrySize := entry.Size()
424 | 		if entrySize > tx.db.opt.SegmentSize {
425 | 			# 判断每条数据的大小是否超过设定的段大小
426 | 			return ErrKeyAndValSize
427 | 		}
428 | 
429 | 		if tx.db.ActiveFile.ActualSize+entrySize > tx.db.opt.SegmentSize {
430 | 			# 如果当前存储的.dat文件超过大小,则需要进行rotate操作,换一个文件写入
431 | 			if err := tx.rotateActiveFile(); err != nil {
432 | 				return err
433 | 			}
434 | 		}
435 | 
436 | 		if i == writesLen-1 {
437 | 			# 更改数据的状态
438 | 			entry.Meta.status = Committed
439 | 		}
440 | 
441 | 		off := tx.db.ActiveFile.writeOff
442 | 		# 当前写入文件的末尾byte数
443 | 		if _, err := tx.db.ActiveFile.WriteAt(entry.Encode(), off); err != nil {
444 | 			# 写入当前数据库文件的末尾
445 | 			return err
446 | 		}
447 | 
448 | 		tx.db.ActiveFile.ActualSize += entrySize
449 | 		tx.db.ActiveFile.writeOff += entrySize
450 | 		# 更新数据库文件的大小信息 , 末尾位置
451 | 
452 | 		if tx.db.opt.EntryIdxMode == HintAndRAMIdxMode {
453 | 			entry.Meta.status = Committed
454 | 			e = entry
455 | 		} else {
456 | 			e = nil
457 | 		}
458 | 
459 | 		countFlag := CountFlagEnabled
460 | 		if tx.db.opt.IsMerging {
461 | 			countFlag = CountFlagDisabled
462 | 		}
463 | 		bucket := string(entry.Meta.bucket)
464 | 		if _, ok := tx.db.HintIdx[bucket]; !ok {
465 | 			# 若当前表么有建立索引,则建立一个
466 | 			tx.db.HintIdx[bucket] = NewTree()
467 | 		}
468 | 		_ = tx.db.HintIdx[bucket].Insert(entry.Key, e, &Hint{
469 | 			# 插入索引,将数据的位置记录到b+树的数据结构。中
470 | 			fileId:  tx.db.ActiveFile.fileId,
471 | 			# 数据库源文件的id,对应哪个文件
472 | 			key:     entry.Key,
473 | 			meta:    entry.Meta,
474 | 			dataPos: uint64(off),
475 | 			# 直接指向记录在数据库文件的位置,加快了检索速度
476 | 		}, countFlag)
477 | 		tx.db.KeyCount++
478 | 	}
479 | 
480 | 	tx.unlock()
481 | 
482 | 	tx.db = nil
483 | 
484 | 	return nil
485 | }
486 | 

小结(编写数据库需要什么知识)

487 | 488 |

通过上面的分析,我们可以得出,编写一个最简单数据库需要一些最基本的知识

489 |
    490 |
  • 索引数据结构(b+树,hash索引,lsm树)
  • 491 |
492 |

未来优化的方向,以及所需的知识

493 | 494 |
    495 |
  • 目前是不能保证并发时ACID性,会出现脏读幻读等错误。
  • 496 |
  • 随机读写时,速度会相对顺序读写要慢很多,所以需要了解一些硬盘读写的基本知识,或其他的数据结构(当前比较就行的就是LSM树)。
  • 497 |
  • 存储的数据没有进行压缩,导致很冗余数据会占用大量的存储空间。
  • 498 |
499 | 500 |
501 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 534 | 535 | 536 | --------------------------------------------------------------------------------