工作日志
1.golang 安放依赖包
先运行 go get -u -ldflags -H=windowsgui github.com/nsf/gocode 会在 GOPATH 下建立 src/github.com/nsf/gocode 文件夹,在无法访问外网情况下,目录为空。离线下载后将文件放入此目录,再运行 go install 即可安装 gocode
2.golang 编译
go build 后面可以接文件, 此时,从当前目录出发(从 GoPath 出发不作数),一定要能保证找得到那个文件。 go build 后面可以接目录名(看起来像是包名但其实是目录). 从当前目录出发,或者 GoPath 下有这个目录名即可。 go install 与 go build 相同,也可以接文件和目录名,与 go build 规则一样。 当位于一个目录下时,可以直接使用 go build ,不带参数,表示编译此目录下所有的文件。 当一个目录下存在引入不同包的 go 文件,使用 go build 会报错。 注意,当 go build/install 的参数是目录时,build/install 的逻辑是非递归的,它只会处理所指目录下的文件,它里面的目录不会递归处理。 补充一条规则:当 go build/install 接目录时,而当前 shell 所处的目录不在 GOPATH 里面,则上面的命令报错,因为 go 命令是全局式的,它找目录的方式永远是从 GOPATH 开始找。 GOPATH 下的目录下的 src 目录可以被自动忽略,这是因为, go 语言假设所有工程的源码都放于 src 文件夹下。 包名与文件夹可以任意安排,但是要注意,package 的是包名,import 里面的是路径名,import 成功后在代码中引用其它包中的函数时,形式是 包名.函数名
3.批处理
在批处理是若使用了 !n! ,则一定要加上: setlocal enabledelayedexpansion
bat 比较两个变量是否相等,对于整数来说,使用 equ ,字符串使用 ==,另外需要注意的是,需要这样的形式: if "%value%" 即需要用双引号包围
十分注意, if 和 else 后面要有空格,因为有可能导致括号与变量或值被解析为一个整体,这会运行出错
if 体内不能是空块,如果是空,加一点注释即可。
()中无法写注释,因为bat把它()当成一行语句,这样注释就相当于一行中语句一部分。
4.Idea IntelJ 编译
idea --> Run --> Configuraions -->JRE 这个是设置整个工程的 jre,而非所有 modules 在 Moudle 上右键 --> open Moudle settings --> Dependencies 设置 jdk 或者 File --> Project Structure --> Modules -->Dependencies 设置 jdk
5. http 代理
对于不能设置网络代理的程序,可以使用 charles 在本地开启一个代理服务器(如果有需要就设置 external proxy)。最后确认 charles 是否已经设置了 windows proxy。所有的事情就搞定了,任何程序都会把请求发送到 charles
6.学习看机器码
简单地看机器码 https://www.cnblogs.com/guocai/archive/2012/10/18/2730048.html
7.图像与机器学习
libsvm 解决的问题是,利用特征数据进行训练得到模型,之后进行预测 caffe 可以用来提供图处的特征; 如何理解 caffe,https://www.zhihu.com/question/27982282
图像学习之如何理解方向梯度直方图(Histogram Of Gradient) https://www.leiphone.com/news/201708/ZKsGd2JRKr766wEd.html
汉字常用特征的提取方法详解 http://blog.csdn.net/jy02660221/article/details/52012543
计算图像一阶微分参见这里:http://blog.csdn.net/jia20003/article/details/7562092
特征提取; http://blog.csdn.net/qq_25543287/article/details/71296100
8.opencv2
下载的版本是 2.4.9 ,已经编译好的几个版本是:VC10, VC11, VC12,而自己只有 VC9,在此情况下,需要自己编译出 VC9 的版本。下载 CMake 工具,按教程重新编译。完了之后 Debug 和 Release 各编译出一个版本。 生成的 dll ,名字后面带 d 的是 debug 版本,不带 d 的是 release 版本。相应模式的应用程序在使用 opencv2 库时要注意选择正确。
9.imgSearch 研究日志
- 使用 kmeans 算法得到汉字特征
- 要把参加 kmeans 点的个数提上来,才有意义
- 根据汉字在图片中的规律,使用窗口扫描灰度化后的图片,若这个窗口内含有的像素点超过一定的数目则这个窗口中心可以成为一个中心
- 若参加聚类的像素点数目存在量级差别,则不是一张图?
- 搜索窗口中的点个数,优化为:二分查找
- 函数传递参数时,优化为使用指针传递
- 如果初始的质心有较大不同,则说明两个文字有较大可能不同
- 是否可以不识别文字,直接人眼来分类
- 如何提取汉字特征?
- 只识别其中一个字,就可以识别整个词语?
- 是否可以记录大图(不含文字)的特征,直接记录答案
- 当前解决:相同的 imgindex 不同照片(存在完全相同的大图)
- 可能需要对每个 clip 有多个数据采集区
- 为什么生成的图像的 width 和 height 反了: New3DSlice 的第一个参数是 height,第二个是 width
- 发现了两个 bug: 1. imgo 的计算灰度图算法,应该取 rgb 三色平均,而实际上它取的是 rga; 2. goleveldb iterator 返回的 key 不是复制出来的,而是缓存地址,这可能导致误用。
- 判断字形作为特征:左右结构,上下结构,封装结构,等等,
- 验证了相同的大图出自于缓存,而非固定存储。 -- 可能缓存的周期是每天,则每天下载一次数据,验证大图的总数量。
- 挖掘小图的协同出现关系:同一主题下的小图伴随出现的概率应该远大于非同一主题。
- 存在一个 bug : 图片的 id 是使用字母+数字形式的,而 leveldb 默认的遍历顺序是字典序的:A99 > A200。这会导致遍历顺序出乎意料。
- leveldb OPenOPtion 中的 BlockSize 设置多少存储图片较合适
- 各个 iterator 有自己的缓存吗 ?如果共享一下,在多个迭代器分别访问时,存在竞争,且换入换出的代价
- leveldb 性能调优:设置布隆过滤器, 设置缓存大小
- goleveldb 一个坑:生成 iterator 之后,有时需要手动调用 iter.First ,后续 iter.Valid 才会成功
- 局部敏感 hash 或 全局 hash:小图非常非常相似,二进制数据也非常相近,目前的方法,采用顺序取样得到小图索引,结合 leveldb 数值型的索引存储,则取出时,两个相近的小图可能相隔的非常远(比如采样数据的第一个字节就相差较大)。这是一个可以优化的方向
- 待解决的问题本质上是:如何将相似的小图,邻近存放
- golang 中 map 类型的键若是复合类型,怎么办
- 上面的疑问的答案:由于 golang 无继承及强制实现接口,故无法限制结构体必须有某个方法(hash 和 equals),所以无法将结构作为 map 的 key
- 问题: golang 返回函数内部变量的地址,函数调用结束后,是否可以访问那个变量
- 待做:clip index 算法优化:1.分多段建立多个索引 2.局部敏感 hash 3.容错 hash: 像素点数值阶段化。 clip 协同关系分析
- clip 图片使用图像转换软件转换试试
- 已经可以输出 dbId, mainImgId, which 对应的 clip 的 index ,可适当分析
- 优化 imgId, clipId 编码
- 目前皆是明文编码: imgId 是 8 个字节存放
- 多个 iterator 同时对 leveldb ,可能导致读取的效率低:缓存不停的换入换出,所以要“集中读”,
- DB::NewIterator 实现: DB::NewIterator 每次调用会生成以下迭代器: 1.mem->NewIterator 2.imm->NewIterator 3.每个第 0 层文件构成一个迭代器 4.从第1层开始,每层文件构成一个迭代器 mem 和 imm 皆是内存中的 skiplist,整个数据库只有这两个,生成 iterator 并不会有复制器出来。 每个第 0 层的文件构成的迭代器,均放在 Version 对象的 tablecache 中:若某个 sst 文件不在内存中,则读取 sst 文件的 footer 和 indexblock 到内存(注意,此时并未读取 data_block )。后续在查找时,若某个 Block 不在内存中,则加到全局的 block 缓存器中:options 中定义的:Cache* block_cache;,默认的大小是 8<<20 即 8 M。 从第 1 层开始的每层一个迭代器,也是依托在 table_cache 的:每层是一个 TwoLevelIterator,先通过第一个迭代器, 定位 user_key 到某层某文件,再通过第二个迭代器,定位某层某文件所在的位置(如缓存的位置),最后,在那个位置查找 user_key 的值。 整个数据库只有一个 Version,从而也只有一个 table_cache。 table_cache 的组织方式是: 1.使用一个 LRUCahce 对象控制和缓存打开的文件信息(table_cache.cc 中 ,默认最多只能 1000-10,即 990 个) 2.使用一个 LRUCache 缓存了所有打开的 Block(table.cc 中 BlockReader 函数) LRUCache 的行为是,淘汰最近最久未使用的缓存。 由以上可知,当有多个线程同时进行顺序访问时,由于 block_cache 较小,全局唯一的 block_cache 会经常被换入换出,导致性能降低。 若每个 block 块大小为 2M,当有 N 个线程时,比较适当的一种设置方式是,block_cache 大小应该为至少 N*2 兆比较合适:这可以避免某个线程加载了 2M 数据到缓存,不会立即被换出。
- leveldb 迭代器依序访问效率低,会不会是命中了太多次 compaction,改日要研究一下 compaction 的触发规则
- idea 编辑器在运行 go 程序时,貌似会抢占到 go 编译器,导致在编辑器外面使用 go build 编译 go 文件陷入等待。
- 扩增 img_key 时要注意一点,同一个线程所属图片,一定要存储在相领的块。否则会导致后面的读取缓存被换入换出
- 同时需要注意,多个线程同时写数据库,可能使得相同 threadId 的数据不在同一个块。可能是离散的。
- golang 坑: golang 坑:笔误导致非常难追踪的错 全局定义 var threadFinished chan int 在创建线程处,定义: threadFinished:=make(chan int, 4); 此时使用的并非那个全局变量,而是局部的。 尤其是此全局变量被两波使用,各波都创建了一堆线程,这将导致很难追踪的问题。 另外,golang 并发执行时一定不要忘记了 go 关键字
- golang 顺序地使用 Iterator.Next 可能速度较慢,即使逻辑上的有序性与物理存储有序性一致,但是由于 Iterator 结构的复杂性,在并行场景下要注意缓存换入换出的问题,而且 Next 的实现内部包装了多个迭代器。可能这是较慢的原因。 实际上,最大的一个导致遍历速度慢的原因是,触发了 comparation。 如果知道存储内容的有序性,可以直接使用 Seek ,自己计算出每一次遍历的 key,这样可以提速
- []byte 转换为 string 时,会有内存的拷贝:使用 str := (*string)(unsafe.Pointer(&bytes)) 则可以避免内存拷贝
- leveldb 写入数据完成后,造成记得要手动 CloseDB,否则在写入缓存区中的数据可能不会被 Flush 到磁盘。
- 估计还有一种可能会大幅度降低写入速度:当写入的键与已存在的键重复度较大时,一是会导致有大量的无用记录(leveldb 并不会立即对提交的删除作存储层面的删除,而是会插入一条相同 user_key 的记录,但 seqencenumber 更大,即更新,这会导致后续的访问会得到“删除”:无值。逻辑上是正确的,但是这些存在的无用的记录会降低遍历速度。),这些相同 user_key 的旧版本记录被删除只可能发生在 compact 且它们必须要同时处于同一个 compact 单元:这是非常难以满足的。 另一方面,由于 leveldb 快照功能的存在,若使用了快照这个功能,会使得同一 user_key 被保留很多份,不能被删除。 综上,为了提高“高频率碰撞”写入时的速度,需要使用 leveldb.Batch,将邻近的写入打包到同一单元,然后再一起写入。可以避免并发环境下各线程连续写入,相互打散写入,导致相同 user_key 的多个版本分布在不同的 sst 文件,甚至不同的 level 中,这便降低了它们合并的概率。 另外,前面有提到,相领的键之记录,最好要保存到同一个块,这可以大幅度提高顺序访问效率。
- clipmain 过程可能有错误,求原value时,应该参考在缓存中的值。
- download 是 imgid 分散的来源,可以考虑使用缓存。各个线程分别批量写入。++++++然而这样也有问题,leveldb底层对于并发的批量写,可能也会被打散,需要更多考虑
- 可否拆开为多个 clip,分而治之-------可能没有意义:处理一个库时 cpu 占用已经接近100%了
- 打印clip索引长度,是否已经归十化?是否相邻的 clip index 对应的照片一致呢?
- edit hash 改进:hash 前面几个字节的容错性应该更加好:前几个字节分支hash
- map本质上是一个字典指针
- 非常重要的一点是, golang 弱化了程序员需要关注栈与堆的行为。分配在堆与栈上,完全由 golang 编译器决定。但它们都指示了一个正确的行为:基于对象的引用法则:需要保证引用有效。当一个对象只在函数内被使用,则可以在栈上分配,否则,函数调用完毕后仍然需要使用此对象,则只能在堆上分配。
- 但 golang 比较自作聪明的地方是,若一个对象逃逸出了函数作用域,但逃不远,有可能还是会在栈上分配:在调用者的栈上分配即可。
- 为一个没有成员的结构体生成对象时,它们都指向同样的一个对象。这一点通过打印它们的地址得知:0x55b988 这是一个较“短”的地址,可能是常量区?正常的对象的地址类似:0xc04204c0c0。 当结构体中加入一个变量时,则立即体现不同了。
- c++ 中的 this,暗含的指向当前对象的指针,不可以被增值。这是显然的。而在 golang 中,没有 this,不受此限制: 以上说明, 指针作为值传递,函数作用域内的修改,对之后的外围作用域没有影响。
- golang 的 Mutex 有一个需要特别注意的是,即使是同一个 go routine, 两次调用 lock 后会死锁。原因是, mutex 的实现与 goroutine id 无关,因此,mutex 没有记录当前是哪个线程持有了锁。其实现的原因是,基于 compareAndSwap 的自旋锁。当一个 go routine 获得锁后,mutex 内部的数据会记录已上锁,故,即使是同一个 go routine , 调用 lock 是获取不到锁的(因为 Mutex 的状态显示,已上锁了),故会等待。
- 1.cpu 占用率为什么不是 99% 了; 2.lastldealedkey 没有起作用?3.cache 占用的内存过大,是不是没有释放掉; 4.cache 好像有重复?5.map 的键类型是 string,效率可能是个问题
- 上面 1~5 的问题已解决:原因在于,所写的 TwolevelCache 底层没有释放掉缓存,越使用越多。而且这也将导致数据上的逻辑错误,它们会被多次 flush,进一步将导致 leveldb 多次 compaction
- 重构了 cache 结构,加入二级缓存,并且使用后,在计算 clip index 时发现性能有大幅度下降。 二级缓存的作用是为了汇集各个线程的缓存,将它们集中写,以提高 leveldb 写效率并且维持数据在连续的块,以便以后提高读的效率。但由于使用了 mutex 去同步合并各个线程的缓存,在高密集的计算中,这个锁会导致性能下降了近一半,直观的感受是 cpu (8核,开启 16 个 goroutine)由 99% 占用率下降到了 50%。 因此,在高密集度计算中放弃了二级缓存,而且由于 clip index 是散列的,本不需要维护在相同块中。 相同地,计算 img index 也不需要使用二级缓存。
- 一张大图产生的 clip index 数据:8 张子图,每张子图 8 个采样,每个采样 2 个像素点,每个像素点 3 个通道。每个索引前4位分支:16 倍。即:8 8 2 3 16 = 6144 个条目。每个条目的 key 的长度是 48。 value 长度是 6 n,n 表示子图出现在哪些大图里面。最少时:6144(48+6) = 331776 字节,即 324 KB。当保存 2000 个条目时,大小为:2000 54 = 106 KB
- leveldb 的 CompactionL0Trigger 参数,若设置的过大则会导致后面查询效率低:L0 文件是无序的,查找只能依次遍历 L0 所有的文件。即使 leveldb 库用于随机写,依次读的场景:不需要查询。也会存在效率低,因为在使用 Iterator 去依次读,为了保持逻辑上的有序性,对 L0 所有文件的遍历在所难免。所以,尽量不要使 L0 过大。
- 之前发现了一个 leveldb Iterator.Key() 和 Value() 两个方法返回的是其内部维护的内存,而非拷贝。而 leveldb.batch 同样也有这个问题:当一边遍历一个迭代器,一边往 batch 中加入 Key(), Value() 时,会发现,最终的结果难以致信:只有一个值。这是由上一个问题导致的
- golang 提供的 map 键仅支持基础类型及 string。自实现了一个 []byte 作为键的 MyMap,与 java 里面 String 作为 HashMap的键实现类型:手动计算 []byte 的 hashCode,作为内里 map 的键。
- 在对 MyMap 性能测试时,发现性能很低,后来发现最耗时的是在测试循环中的 random.Intn,
- 建立子图索引时,不需要使用 SURF 算法去提取子图的特征。这个算法用于需要高鲁棒性的场景:旋转,噪声等较明显的图片。而我们的场景,子图只有少量的干扰,并没有一些特意的变换,旋转等。所以直接取固定地方点,建立索引即可。我们需要的是,如何建立抗微弱噪声的索引
- 目前 Collaborate 中求协同关系,查询某个 indexBytes 对应的 clipIdents ,使用的库不正确,应该要使用全局的 indexToClip 库。
- 目前努力的方向是,将相邻 indexBytes 合并,并标记为同一张子图。由于现在前面 2 个字节表示分支,每个字节的取值范围是 0, 10, 20, 30 .... 250 中的某个:26 种可能性。所以总共有 26*26 种可能性。可以以此为依据去并行。
- golang 缺点:1.没有使用的包,报错,这会导致注释一些暂时不用但以后又需要的代码时,还必须手动删除这些包的引入,而之后恢复代码时又需要加回来; 2.不支持隐式类型转换,需要多写很多代码。例如,math.Abs 方法,传入的类型要求是 float64, 不能传入 int 等。不支持重载函数,这也需要多写一些代码。
- 为了解决 clip index 对应的 clip ident 追加问题,使用二级缓存,这样会使得写库时只有一个线程在写,可以边写边验证
- 另外一个需要优化的是,如果一张大图已经处理过,则不再处理.
- 如何为 leveldb 加上布隆过滤器
- leveldb bloomfilter. bits_per_key 默认是 10,表示可能会有 1% 的误判率,bits_per_key 越大,误判率越小,但也会占用更多的 memory 和 space amplification。
- 解决了 clip ident/ img ident 追加的问题。优化了写入 index 库的效率问题 1.不使用 cache.Visit 去遍历,加入 Batch 再统一写入的方式,这样会使得占用内存十分多; 2.写 index 库时优化了内存分配问题,触诊了 Batch.Put 会发生内存拷贝,而不是维持引用,所以在计算 index/ident 时不需要分配内存,只需要使用引用,直接加入 Batch, 让其拷贝。这样做的好处是,减少了少内存的分配,能大规模减少 gc; 3.优化逻辑:写入 index 库前判断库是否为空,如果空,则不需要去频繁地查询原值。直接插入,而不需要计算覆盖值。
- 关于 golang walk 界面库: 0.rsrc.syso 文件是资源文件编译后生成的文件,并非是 manifest 的编译生成文件. rsrc.syso 文件中包括了图片资源及其它资源的路径; 1.rsrc.syso 必须要和应用程序在同一目录下. 此外它申明的资源也必须能找得到; 2.编译程序时,若只编译我们的主程序文件: go build test.go: 得到的 exe 将无法运行. 要在目录下使用 go build 编译,将资源等文件一起纳入编译; 3.使用 idea 调试界面程序时,基于上面 2 的原因,要设置 Debug 里面的属性: Run kind 要设为 Package: 如 /ImgTrain/src,方可一起编译目录下的文件,才可正常运行。
- 1.最外层 TabWidget,里面 使用 Tab 对应 Page 再容纳一个 ImageViewer,再旋转一个图片。TabWidget 响应鼠标事件。然而点击图片时,事件响应函数没有被回调:这是因为图片占据的 ImageViewer 挡住了后面的 TabWidget,所以事件响应函数无法被回调
- 关于 leveldb 心得: 由于 leveldb 是 key-value 的,当需要一个 key 对应多个 value ,同时需要按 key 来索引时,可以考虑按如下格式构建库: (key|value) --> ... 即,将 value 直接放于 key 后面作为键,需要关联的信息作为值,或者维持 nil 也可。
- 待做: 1. 在线的 12306 验证码识别,也即一个在线的训练程序 2. 子图自动归类程序
- 坑:某当前要计算的大图中有两张相同的子图,则只要此子图出现的大图,都算作支持这两个子图联合出现。这是不合理的。
- 由于是以图片库为遍历对象,一个一个遍历,一个一个处理,当大图 A 和 B 有共同的子图时,则处理 A 时会处理一次,处理 B 时又会处理一次。
- 协同检验结果:8000 张图片,检测出协同关系。在20 组里面抽样,正确率约为 80%+
- 协同检验结果:8000 张图片,检测出协同关系共 228 组。
- 待优化:手动 compaction index 库,这是为了提升读取的效率
- 目前存储协同计算结果的形式是两个库:tag|clipBranchIndexes --> support。clipBranchIndexes|tag --> support。是否有必要合并结果呢?举例说明:两幅大图 A, B, A1, A4 及 B3, B7 分别是 A 和 B 的子图,而且 A1 与 B3, A4 与 B7 实际上是相同的子图。A1, A4 同时出现在 A, C, D 三幅图中,B3, B7 同时出现在 A, C, D 三幅图中,目前的架构,在依次计算 A, B 时,我们会将 A1, A4 绑定为 tag1, B3 B7 绑定为 tag2,但实际上这两个 tag 应该要一致。目前我们会多冗余存储非常多的 tag。更深层次的问题是,在判断一幅大图 X 中各个子图联合出现的支持度时,会得到一个更大的支持度,这正是由于重复存储导致的。现在我们尝试这种存储方式:branchIndex1 | branchIndex2 | vtag --> support
- goleveldb: WriteBufferSize 直接影响打开 leveldb 时占用的空间大小. 当设置它为 320 M 时,打开 14 个库,内存峰值为 3G 多,但是待 GC 后(使用腾讯管家内存回收球),空间回退。
- 发现一个现象,如果参考库过多,噪声会增加,这样会使得将本不是同主题的子图划分为一个主题。反而,若只使用与图片库一致的参考索引库,得到的结果却准确多了。 -- 这一点,可能仅是规律
- 计算协同关系算法太慢了,每挖掘一个大概 5 秒
- 使用 pprof 优化的点:IsSameClipBranchIndex 中判断是否为相同分支索引时,计算欧式距离并比较差值,优化为计算欧式距离平方比较差值。这样可以优化掉 pow 带来的计算成本。
- 使用 pprof 优化的点:另外一个导致速度慢的原因是,leveldb 的 iterator.Next ,顺序遍历库。
- 使用 pprof 优化的点:另外一个耗费高的操作是,occInImgs -> GetDBIndexOfClips -> GetDBIndexOfClipsBySrcData -> FromImageFlatBytesToStructBytes -> imgo.Read
- pow 的另一个优化思路,因为计算 pow 的输入值范围是 0 ` 255, 因此可以先计算好,并把结果使用一个数组保存起来,之后直接查询。注意,不要使用 map 去存放,效率要低于数组 O(1)。
- leveldb 应该可以有一个优化:当我们只是读取一个库,无修改/增加时,并且我们是顺序遍历时,应该可以顺序从排好序的 sst 文件中挨个读取,而不是融入 leveldb 的查找体系(memtable, imemtable, L0, files ),这应该可以提速很多。
- 这可能需要 leveldb 暴露无遗两个功能:1.手动将 L0 文件有序化,与其它层的文件保持顺序一致性; 2.在各层文件中查找键(可能是真实键的前缀),得到一个区间,而这个区间必然是物理上(当然也是逻辑上)连续的。如此,我们直接在连续的 sst 文件中读取记录即可。
- 目前 leveldb 没有提供上述的功能。如库中存在这样的键:
edgarlli12345
edgarlli12354
edgarlli12435
edgarlli12453
edgarlli12534
edgarlli12543
等等
我们需要查找所有键以 "edgarlli" 为前缀的记录,目前较高效的做法如下:
region := util.Range{Start:[]byte("edgarlli"), Limit[]byte("edgarlliZZZZZZ")}
iter := db.DBPtr.NewIterator(region, &db.ReadOptions)
iter.First()
for iter.Valid(){
...
}
当数据量巨大时,这个做法很低效:
具体原因是,leveldb 的顺序读并不是物理上的顺序读,只是逻辑上的。leveldb 的查找体系较复杂:block cache,memtable, immtable, L0 files, other files 。在并行环境上, leveldb 会存在缓存换入换出的情况,这都加重了查找的负担。
以上为背景,所以我们需要做的优化是: clipIndexToIdent 库,key 是 clipIndex,更改为 clipIndex分支 | 均值分支 | 标准差分支 | 欧式距离分支
内存优化:计算 clip index 后,由于某些数据保存在结构 ImgIndex.SubImgIndex 中,此结构中其它的数据本应该是可删除,但由于有部分数据还在使用中,这将导致 ImgIndex.SubImgIndex 的内存不能被回收。这点可以优化。
为了搜索索引相似的子图,需要保存 index -> ident 库。
由于 leveldb 是 key-value 库,只能容许 key 单一地存在。因此,可以边写入,边查找,当 key 已存在时,合并新老旧值。但由于 leveldb 查找是一项较耗时的操作,而 key 碰撞的概率大致上来说是较小的,如果为了执行较少次数的合并,而对所有插入都执行“查找后插入”,无疑是相当不划算的。
做如下优化:
将 key -> value 转化为 key|value -> nil 写入, 此时键是 key 和 value 的拼凑: key
。等待过程完成,再做库转换:顺序访问库,若前 len(key) 个字节相同则合并后面的字节作为值,key
[len(key)] 作为键。 顺序遍历效率比较低,但是总好过边插入边查找:每个键只需要被查找一次。 目前决定 clip 索引格式为: clipIndexToIdentMiiddle : branchIndex|idents --> nil clipIndexToIdent : branchIndex --> idents clipStatIndexToIdentMiddle: statIndex|idents --> nil clipStatIndexToIdent : statIndex --> idents
clipIdentToIndex : ident --> source index 准确度待提高 -> 建立支持度 -> 高效的查找键值对设计 协同计算效率待提高 -> 高效的查找键值对设计 ------ 待验证 发现一个现象,如果参考库过多,噪声会增加,这样会使得将本不是同主题的子图划分为一个主题。反而,若只使用与图片库一致的参考索引库,得到的结果却准确多了。 -- 这一点,可能仅是规律
- 如果只使用平均数+方差的方法,误差较大,会把很多原本和目标子图不相同的子图误认为相同。
- CoordinateMain.exe --cpuprofile=monitor.prof go tool pprof ClipCoordinateMain.exe monitor.prof
- 目前计算 clip index 速度大概是 1000 个 10 秒。
- 平均数 + 方差的形式,忽略了序列的顺序性。
- 协同库的格式: indexToTagMiddle : statIndex1 | statIndex2 | clipIndex1 | clipIdent1 | clipIndex2 | clipIdent2 | vtag --> support tagToIndexMiddle : vtag | clipIndex1 | clipIdent1 | clipIndex2 | clipIdent2 --> support
- 平均数 + 方差的形式,忽略了序列的顺序性。 -- 是否还可以优化,比如带有位置信息的统计索引 ?
- 待优化:选择了多个库作为参考库时,效率很慢,为什么
- 对于“可重复性”的值,需要最大程度地减轻 库中 value 的重复性!特别要注意 left-right, right-left 这样的重复!
- leveldb 不支持 insert 时 update on duplicated key 逻辑,这点很麻烦: 需要自己先取出值, 手动合并新旧值再写入. 比较麻烦。
- 上面的方案的运行时耗费过高: 每插入一条值,都要先查一次。考虑, 若往 leveldb 插入键相同的 1000 条记录, 需要查找这个键 1000 次, 且作了 1000 次合并后写入。 我们采用的方案是中间表: 构建一张中间表,合并原表的键和值: key | value 作为新表的键,值设置为 nil,这样写入时直接写入. 写入完毕后,再顺序遍历中间表,在内存中处理分裂,归并逻辑,最终一次性写入。 这样做的好处有几点: 1.写入时直接写入,不需查找。众所周知, leveldb 写入的性能远高于查询。这样可以提高运行时效率。 2.相同键的记录在物理上会存储在一起,后面顺序遍历时,借助于缓存,较少需要缓存换入换出,因此效率也是较高的。 3.内存中的分裂,合并,支持并行处理。
- golang 指针和对象可以直接调用方法,在这点上无差异。map 的指针不对直接使用 mapptr[i] 去访问键为 i 的元素,必须要使用 (*mapptr)[i] 才能访问。
- 需要将自己写的 golang 工程 A,在 src 目录下 go install ,这样别的项目可以使用。 实际上,如果设置别的工程 B 的 go path, 能够找到 A,也会报错,因为在编译 B 时,也会编译 A。 A 作为一个整体,内部的相互依赖 import 应该不会写出全路径: import "edgarlli.imgSearch.options",一般只会写出 import "options",在编译B时,联合编译A,会使得 A 编译出错,因为此时无法找到 "options",它并不在 B 的目录下。
- 所以需要将 A go install 才可以。
- 上面关于 AB 工程编译依赖的理解可能有误,可能只是由于 A 工程里面的目录有 src 的干扰?
- 计算协同关系时效率低,有一个压倒性的因素。先看协同计算的步骤: 1.通过各个子图的 stat index 找到 clipIdents 后,进一步得到 imgIdents,它们是子图出现的大图 2.查询各个 imgIdent 的索引,去重,得到各个子图出现的大图 3.找各个子图的大图的索引,找出重复的,即是协同出现的子图 其中第2步,由于 imgIdent 会很多,因此需要查询很多次imgIndex。 此函数 getImgIndexFromClipIdents 会被调用很多次。实际上,这也是协同计算时占据最多耗费的一个调用。
- 然而这个调用很多情况下应该是可以避免掉的,因为第一步中仅仅是根据 stat index 查到了 clipIdent ,并没有后续判断,到底这些 clipIdent 是否真的与给定的子图是同一个子图?注意, 不同的子图的 stat index 可能会相同 。
- 因此决定这样优化: 写入 statIndexToClip 库,值为 clipIndex | clipIdent, 可重复。在之后的查询时可以快速先判断,给定的子图是否与查出来的 clip 是同一个子图。
- 同时,注意保持 statIndexToClip 值的纯净性:在对中间表分裂,归并时,要加入一个逻辑,如果一个子图的 clipIndex | clipIdent 已经存在于值中,则不加入,注意应该使用 IsSameClip 判断,不是使用 clipIdent 判断。
- 再次深入优化: 前面提到过,stat index 在一定程度上具有区分度,但是它丢失了序列的顺序性。因此,我们可以优化 stat index 的计算: 将原序列分为 N 个部分,分别计算各部分的平均值,方差,最终归并作为最后的 stat index。这样做的好处是,兼顾了顺序性。
- golang 循环闭包注意以下的陷阱:闭包引用的内存作用域在闭包外,则此内存会在堆上分配。对于匿名函数而言, i 和 j 都会在堆上分配。但是 i 只会分配一次,所以最后在执行匿名函数时,i 的值是固定的,10。而 j 会被分配 10 次,值依次是 0 到 9.
- 想到一个优化的思路:IsSameClip 的计算中没有必要计算两个序列的欧拉距离,考虑到场景的特殊性,直接挨个比较像素值,看是否落在某个区间上即可。
- 发现一个较大的可以优化的点:考虑到我们存储的数据都是二进制的,因此压缩可能耗费了时间,但并没有减少空间,而且还降低查询时的效率。
- 目前每计算一张大图中存在的协同关系, 大概需要根据 stat index 查询 db 55 次左右。此处可以优化为:将查询结果缓存起来。
- 1.leveldb 使用优化(减少 compaction, 读取效率优化, 如何建立联合索引, ) 2.协同算法 3.图片处理(下载, 转化, 修复) 4.图片索引计算与搜索(分支索引, ) 5.缓存(支持字节数组的 map, 进一步支持并发, ) 6.单元测试 7.回调(数据库遍历, 缓存遍历, 图片训练器 Ui 线程与业务线程交互) 8.kmeans 算法
- 现在思考如何将所有子图分类。
10. hive工作日志
- https://www.cnblogs.com/judylucky/p/3713774.html lateral view用于和split、explode等UDTF一起使用的,能将一行数据拆分成多行数据,在此基础上可以对拆分的数据进行聚合,lateral view首先为原始表的每行调用UDTF,UDTF会把一行拆分成一行或者多行,lateral view在把结果组合,产生一个支持别名表的虚拟表。
- 两个表join的时候,不支持两个表的字段 非相等 操作,>= 等也不支持,需要放到 where 中
- docker :1.一个母机有多个 ip,每个 ip 可以分配给多个不同的容器使用(只要容器的端口不同即可)。2.一个容器一个应用,对外提供可访问的端口暴露出来,对内 docker 控制的端口不暴露。3.dockerfile 更改时,要重新生成镜像。4.应用发布到母机上后,其真实存在于容器中,而它的目录是被包装之后的,真实存在于某个隐藏的目录。
- 动态分区配置是针对会话的,不针对表,也不是全局的。set hive.exec.dynamic.partition=true; set hive.exec.dynamic.partition.mode=nonstrict;
- hive 有一种情况会造成堆内存耗尽:select collect_list(xxx)[0] from table group by yy; 若 xx 在 table 中重复度极高,最终合并时,是在一台 jvm 里面完成的,则 collect_list 占据的内存将会很吓人,直接 heap error。
- 写 markdown 的利器 :typora
- 冒泡排序:第一次将最大的数放到最后面。第二次将第二大的数放在倒数第二个位置,直到最小的元素在第一个位置。
- 直接选择排序:第一次将最大的数放到最后面。第二次将第二大的数放在倒数第二个位置,直到最小的元素在第一个位置。与冒泡排序思路是一致的,第 i 次处理(倒数)第 i 大的数字,放到最终的位置。惟一的不同是,冒泡排序是通过相邻数据互换以达到将元素放到最终位置的结果。而直接选择排序是标记第 i 大元素的位置,直接与最终位置的元素互换。
- 十大经典排序算法:https://www.cnblogs.com/onepixel/articles/7674659.html
- 住房公积金:6214857555078205
- 若调头处的路不宽则可以多开过去一点再调头。收费处挂空档,手刹后,伸出头(若没有空档手刹会滑车)接单,找零。变道时一点一点挤进去,若挨的近则速度要慢。
- 汇车时没有看到右侧来车。变道时没有打转向灯。连续变道。心理上惧怕大车。开车时没有注意到地图/报音。
- hive 插入常数到表时,类似: insert into table xxx parition(ds) select date_sub(current_date,1) as yesterday, current_date; 通不过编译。此时,可能会想到使用以下方式: insert into table xxx parition(ds) select date_sub(current_date,1) as yesterday, current_date from xxx limit 1; 这个语句可以通过编译,但是执行出来的结果可能不正确:当 xxx 表没有数据时, select date_sub(current_date,1) as yesterday, current_date from xxx limit 1; 查询结果为空,所以没有任何东西会写入到 xxx ,这不是我们想要的。此时可以这样解决这个问题: insert into table xxx parition(ds) select * from( select date_sub(current_date,1) as yesterday, current_date ) tmp;
- hive select 语句后面的 order by 字段必须要出现在 select 字段里面。
- sql 注入的本质在于 sql 的运行时编译再运行:数据和指令混淆,数据越权成为代码,执行结果当然不符合预期。 有几种本质上可以防止注入的方法: 1.对于纯文本式的指令,基本没有办法可以杜绝注入,只能在外面套壳,进行词法分析,然而该方法太过复杂且一般来说有漏洞,不可靠。 2.不使用数据库的 SQL 文本查询,使用它提供的参数化查询接口。存储过程即是一种参数化查询接口。另外,数据库一般也提供了 SQL 语句预编译,查询参数动态绑定的接口。
- 由于 hive 没有提供 delete 行的功能,所以使用下面的方法去删除:使用 insert overwrite table select not target 的方法。 然而这种做法有坑:当 select not target 查询结果为 NULL 时,则 insert overwrite 不会生效:没有任何数据会被删除。 所以我们要先 insert into (注意非覆盖式) 插入一行占位记录,之后再运用 insert overwirte select not target 的方式删除 target,最后再删除占位记录即可。
- RWWRITE 实现的关键除上上面的算法,还有以下关键点:
- rlock 和 wlock,以及 unlock 三个接口。
- rlock 和 wlock 这两个接口的实现都在同一个逻辑 CriticalSection 。任意时刻只有一个线程会执行。
- 维护以下计数: rlockedCounter, wlockedCounter 分别表示读或写已持有锁的计数,必须要调用数量相同的 unlock ; rlockWaitCount, wlockWaitCount 分别表示正阻塞着的读和写。读会被阻塞是因为其它系统已持有写锁,或者虽无线程持有写锁但有正被阻塞着的写(此时读不可直接进入,这是为了不让写线程饥饿);state 表示当前锁的状态:空闲/ 读 / 写 4.当读或写线程需要阻塞时,让线程阻塞在一个 Event 上。 5.当调用 unlock 时,根据当前锁的状态,变化计数,然后再发射信号,触发 Event 。
- 条件变量解决的核心问题在于: 以统一的方案解决了这个模式:先释放锁再无缝地阻塞等待。 更通俗地:如果释放锁后,另一线程抢占到了锁,之后执行了 signal -- 这一动作发生在当前线程阻塞等待之前。则当前线程会陷入死等待。 条件变量解决这个问题的方法是:在释放锁之前将当前线程注册等待者,在 signal 中去检查这个等待队列,如果存在则设置一个标志,这将影响当前线程中的 wait 中的行为:不再陷入等待,直接返回。因而就解决了这个问题。
- 有键事件:keyed event,用于解决在内存不足时创建事件的问题。有键事件会在系统启动时被创建:系统中惟一一个实例。
- lock free数据结构一般来说拥有比基于lock实现的数据结构更高的性能,但是其实现比基于lock的实现更为复杂,需要处理的难题包括预防ABA问题,内存如何重用和回收等。
- 这篇文章把 hazard-pointer 原理和使用讲解的比较清楚,精短有力:http://www.it610.com/article/1618145.htm http://ifeve.com/hazard-pointer/
- @Service, @Conroller, @Repository 三个分别应用于服务层,逻辑层,持久层。实际上功能一样,皆是继承自 @Component 功能:这个是最底层的生成一个 Bean 的注解。
- pagehelper 是一个分页插件,其原理是在 sqlSession 层面对每个提出需要分页的查询(如参数中带了 RowBound 或使用 PageHelper 静态方法设置了线程变量 offset, limit ),拦截并改写查询语句,对于 mysql 而言直接加上 limit 语句,对于 oracle 则需要嵌套查询实现分页。 在这个大的原理下, PageHelper 提供了一些配置以扩展功能,如 resasonable 以应对分页参数越界,count=countSql 使用用户的条件查询总数目,等等。
- 项目经验:不要把事情做复杂,尤其是团队只有你一个人知道细节的情况下,哪怕它是一个好的解决方案。此方案没有人帮你评审,所以一旦发生了问题只有一个人解决。 4月17日刷卡金大数据版本,使用了自己实现的 HiveToMysql 从 hive 推数到 mysql,有以下两点致命的问题: 1.在生产服务器上动态使用了 javac 命令编译 java 文件再运行。 2.编译 java 文件和运行 class 文件均直接使用的系统环境变量指向的 java,而没有自定义编译/运行的 java 版本。 作了发布并在 tss 上试运行,发现 HiveToMysql 没有导数成功,但是 tss 里面也没有任何错误日志--准确地说是没有任何日志。 费九牛二虎之力发现原因:HiveToMysql.java 依赖于 secure.jar 这个文件,而它是使用 jdk8 编译的。生产上两台服务器的 JAVA_HOME 环境变量不一致,一台是 jdk7 一台是 jdk8,而编译脚本直接使用了系统默认的,这就导致了编译,运行出错。如果仅仅是出错我们就能立即发现了。但是由于整个编译,运行环境是由 tss 调度的,它与 java 之间没有错误信息交换,TSS 无法感知到错误,因此无声无息地,实际上 HiveToMysql 没有得到执行。 其实,伴随着第 1 个致命问题,还有一个问题:在编译脚本中,判断了 HiveToMysql.class 是否存在,如果不存在则编译,否则不编译。这有潜在的问题:如果生产升级,更改了 HiveToMysql.java 文件,但是生产发布时,没有删除 class 文件,则新的 java 得不到编译,生产也就无声无息地没有生效。这一点本质上是自己做了一个有致命缺陷的版本管理。 以上。 经过与其他同事交流,大数据脚本有统一的形式,但是事先没有征求意见,这招来了大数据运维同事的鄙视
- 协程的本质在于,包装 [线程上下文和堆栈] 为一个一个的整体,通过切换它们运行,达到避免阻塞,充分使线程有效地运作。一个线程里面也可以模拟 N 多个线程。 有两个最关键的地方:
- 保存,替换,恢复 当前线程上下文,堆栈。
- 调度方面:侦测阻塞的调用,绕开它,使调度顺畅。 1 是根本。如果没有满足 2:当前协程执行阻塞住了,显然真正的执行体线程也阻塞了,这样就会妨碍其它协程的调度。 协程阻塞针对两种情况有不同的处理: a). 当程序发生 system call,为了避免当前线程阻塞,此时会唤起(或创建)一个新的线程去执行当前的协程,新线程会被阻塞住。当前的线程去它自己的协程列表里面继续调度。那个阻塞调用完成后,新线程中的协程会被放到全局的可调度协程队列里面等待下一次调度。 b) 协程主动放弃执行: 调用了 channel call,此时协程状态变为 waitting,当这个协程被另外的协程使用 channel call 唤醒时则可以再次被线程运行起来。 --------------------- 更多细节 一般协程库会 hook 系统调用。如微信的 libco, golang 的协程,均对系统调用包裹了一些东西:在系统调用前后增加一些代码,如唤起或新创建线程来执行系统调用等。
- 缺点:规划能力。体现在以下几个方面: 个人生涯规划:学的东西较杂,涉及太广,没有重点(网络服务器,异步 IO,数据库,算法研究,c++/go原理等),需要在一个领域沉淀,运用自己良好的基础,突破自我技术的瓶颈,向大神看齐。 包装自我:写了很多文章但不善于推销,有一些自我感觉比较好的开源项目不知道扩大影响力。
- csp: 使用通信来共享内存,而不是通过共享内存来通信。channel 就是 csp 的典范:使用通信机制唤醒阻塞中的协程,去取数据。
- 一致性 hash ,“一致性”的本质包括以下两点: 1.绝大部分情况下已 hash 的元素不会因为(动态增加一个分区,或者删除一个分区)而改变其 hash 分区。 2.一个分区失效,不会导致大部分元素发生 cache missing。 实现的方式有两步: A.将节点映射到一个 2^32-1 的环上。 B.将数据映射到上面的环上,顺时针方向上首个节点即是此数据应该被放置到的节点。 由上两点可知,在某两个节点 a,b 的区段上增加一个节点 c 时,只会改变 c-b 区段中的元素的放置。而假设原本顺时针方向上有 a, b , c 三个节点,b 节点失效时,只会影响 b-c 区间的元素放置。 以上就是一致性 hash 算法的基本。其实还有一个问题: 两个映射过程:节点与数据是不同维度的两个数据,如何保证它们映射后都是均匀的呢?答案是,无法保证。特别是当节点数目较少时,会存在数据分布不均匀。为解决这个问题,一致性 hash 引入虚拟节点的概念:在实践中,如果发现数据倾斜的现象(分布不均匀),则可以将数据堆积较多的区段安插一些虚拟节点,占位以代其它数据少的节点行事:实际上这些虚拟节点可以看成其它节点的软链接
- channel 实现原理是,维护 n 个“读写双工缓存区”,各个协程可以写,可以读。如果缓存区空,则读出阻塞,如果缓存区满则写入阻塞。在读写此缓存区时,均要锁住此缓存区。当一个协程写入完成时,使用条件变量唤醒因缓存空阻塞的读协程。当一个协程读完成时,使用条件变量唤醒因缓存满阻塞的写协程。
- hash 冲突(若 hash 表中值已被占) 使用以下开放地址法可以解决:
- 线性探测:线性向后查找返回首个没被占用的值
- 二次探查:以 i 的平方的步长(i 顺序加 1)向后探查首个空位
- 双重 hash:增加的步长是另一个 hash 函数的值。每次增加此步长循环式的在表中查找首个空位。注意步长必须要与表长度互素且不为零,这样可以探测到表中所有位置。 另外可以用的是链地址法:即将相同 hash 的元素都接在同一个桶里
- bloom filter: 使用 k 个 hash 算法,根据 n 个初始元素,生成长为 m 个标志位的串,用于后续的元素存在性判断。有以下结论:
- k = ln2 * (m/n) 时错误率最小
- 要想保持错误率低,最好让位数组有一半还空着
leveldb 中过滤器的实现原理如下: 创建:
- 给定 n 个元素和平均每个元素多少占位,得出 m。 ln2 * (n/m) 即是 hash 函数个数 k
- 对每个元素使用 k 个 hash 函数计算: k 个 hash 函数乃模拟的:实际上是 k 次 hash:第 i 次的 hash 计算的值经以下运算成为 i+1 次的输入:h + (h >> 17 | h << 15)。第 0 次的计算结果是原始 hash 函数的输出。
- golang 的 fallthrough : golang 默认 case 语句后面不需要加 break。如果要实现 c 语言里面不加 break 功能,则需要在 case 语句后面加上 fallthrough。 首个与 switch 条件命中的 case 在执行完毕后,如果后面有 fallthrough 则会掉入它后面的一个 case 去执行,而不会管它的条件是否满足。如果一直有 fallthrough ,则一直往下掉
- 一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数) 如果是CPU密集型应用,则线程池大小设置为N+1 如果是IO密集型应用,则线程池大小设置为2N+1
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 因为很显然,线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
- golang 非匿名组合时,外类必须通过被组合的对象去访问其成员或方法:注意如果被组合类和外类不在同一个包下则不能访问私有成员或方法(小写的变量名,方法名)。 当匿名组合时,外类可以直接访问被组合类的成员及方法:注意不同包时不能访问非公开的成员及方法。 当匿名组合了多个类,而这些类有同名方法,若代码中有不经被组合类访问的同名方法,在编译期会报告:ambiguous selector。 注意:匿名组合产生的效果:外类可以直接访问组合类的成员/方法或许是一个语法糖,但是它做了更多:外类因匿名组合增加的方法,也会产生多态的效果。
- c++ x 中的 concept: 用意在于从语言级别抽象模板,不仅仅让程序员可以从更高层次去编程,也可以减少编译器报的太底层,太细节化,看起来无关的错误。也可以让 IDE 代码提示更智能。 concept:https://blog.csdn.net/pongba/article/details/1726031
- 有折/联名户 消费关键性能指标: 在一台 6 核,2593 MHZ,4M cache, 14.5 G 内存 机器上的测试结果:
- 有折消费 120 笔/s 左右. 并发达到 400 左右到达峰值(经分析耗时:调用下游核心单笔转账接口耗时占比将近71.5%;调用下游安全检查接口耗时占比为16.5%;)
- 联名户消费 300 笔/s 左右. 并发到达 400 左右到达性能峰值,线程池最大最小均是 280,队列 10000。当设置最大最小为 150且队列1000时, tps 在并发 400 左右到达峰值,为 230 笔/s
其它性能指标: 1.拉取二维码, 400笔/s 2.刷卡金余额查询 3400 笔/s 3.交易明细查询 2500 笔/s 4.订单撤销/退款 160 笔/s
- 线程池大小经验上为 CPU 核数的 4 到 6 倍为佳。 数据库连接池大小为线程池大小的 1.5 到 2 倍为佳。 注意数据库连接池的几个配置: spring.datasource.test-on-borrow 设置为 false 可提高性能. 其意思是每从线程池中拿出一个连接则使用 select 1 查询连接是否有效。该项判断绝大部分时候不需要因为 spring.datasource.test-while-idle 已设置为 true 了. 云哥建议 tomcat 的数据库连接池性能最佳,使用的配置是: spring.datasource.tomcat.initial-size=25 spring.datasource.tomcat.max-active=50 spring.datasource.tomcat.max-idle=50 spring.datasource.tomcat.min-idle=25 spring.datasource.tomcat.test-on-borrow=false spring.datasource.tomcat.test-on-return=false spring.datasource.tomcat.test-while-idle=true spring.datasource.tomcat.validation-query=SELECT 1 spring.datasource.tomcat.validation-interval=34000 spring.datasource.tomcat.time-between-eviction-runs-millis=34000 spring.datasource.tomcat.min-evictable-idle-time-millis=34000 spring.datasource.tomcat.remove-abandoned=false spring.datasource.tomcat.log-abandoned=true spring.datasource.tomcat.remove-abandoned-timeout=54 spring.datasource.tomcat.jdbc-interceptors=ConnectionState;StatementFinalizer;SlowQueryReport(threshold=200)
- 做过的项目/开发微众有折 遇到的较大的问题/生产事故:
- 并发增加时,数据库连接池的响应不及时导致获得不到数据库连接,业务代码显示:数据库超时。通过增加数据库连接池的大小解决。
- 并发急增,消息队列深度太小,导致消息丢失。通过增加深度和调整 unack 值解决。
- 消息队列上的消息重发。需要在业务逻辑里面防重(一般通过系统调用流水号防重, 但具体场景要具体分析)。
- 分库分表应提前考虑: 微众有折项目目前客户两百多万,订单表,转账流水表,刷卡金发放表,使用明细表等已接近亿级别。严重影响线上交易性能。目前暂时的解决办法是,备份冷数据(超过三个月)到大数据,在大数据上提供查询功能,并且刷卡金消费方案也已优化为大数据离线核销。这样的弊端是,系统复杂化,流程太长,运维工作加剧。上面的暂时方案治标不治本。分库分表才是较好的方案。
- 流程中要严格区分出需要旁路的分支,将它们旁路出去,或异步,或同步但 catch 包住所有异常。有折 2016 年底活动是由于应该旁路的分支 -- 支付结果通知给安全,没有充分旁边,导致高并发时压爆了深度较浅的安全接口,导致已经成功的客户的交易仍返回给第三方失败。
- 版本规范:(版本控制,差异化配置的管理,发布模板的管理,灰度评估及回滚分析,大数据开发的流程要标准化,不可以在生产上编译 java 文件再运行,这难以做到有效的版本控制)
- mysql 表没有建索引, 使用 delete from... where xxx in(1,2,3,4,5…)的形式从主库删除多行记录。但是主库在向备库同步时,使用的是 binlog, 等价于在备库执行 n 条以下语句: delete from... where xxx =1; delete from... where xxx =2; delete from... where xxx =3; …… 在没有索引的情况下,备库都执行的时间会是主库的 n 倍。在实际案例中, n 是 100 w,这直接导致了备库卡死。
- 4月27日生产事故简要回顾 1.大数据平台上的余额返现模块,给客户多发了一份刷卡金,多发的那部分的类型是预期之外的。涉及总金额2.5块。 2.余额返现的逻辑是,查询到各卡号余额,根据他所属商户,找到这个商户使用的最新的类型为3的刷卡金模版,给这个客户发放余额比例的刷卡金。 3.经排查,原因是sql的bug。找出刷卡金模版表中各商户下类型为3的最新一个模版时的bug。语句如下: select c.* from config c inner join (select merchant,max(create_time)as mtime from config where type=3 group by merchant)mc on mc.merchant=c.merchant and mc.create_time=mtime 很隐蔽的错误是,当一个商户类型为3的模版与它下面类型为4的模版创建时间一样,外围的关联将导致最终查出这个商户的两条记录,类型为3和4。 4.解决办法是在外围的sql加上where c.type=3
- 关于信息熵通俗易懂一文: https://zhuanlan.zhihu.com/p/26486223 一然话总结:信息熵与概率有关,它表示当一个/多个 事件发生时,我们可以收获的信息量之大小。如太阳从东边升起这一事件是确定的,概率为 1,我们收获的信息量是 0:我们并不能在常识的基础上获得其它信息。所以初步可以判断:概率越大信息熵越小。 设两个相互独立事件 A, B,发生的概率为 p , q。它们同时给我们带来的信息量应该是线性叠加的:信息熵的定义应该有一项 logp。考虑到 logp 小于等于0 (因 p<=1),所以要加负号。 综上,所以信息熵的定义如下:
- 信息熵指的是信息的不确定程度。
- C4.5算法:是一个分类算法,基于 ID3 算法的优化。基于已有数据构建一个决策树。决策树内任何一条分支上不会有重复的属性,有向线段是属性之值,叶子节点是类别标签。
- 对于给定的输入数据,走访决策树,直到叶节点,可知其类型。
算法的核心以及难点在于,构建决策树的各个结点时,选择哪个节点:很明显被先选择的节点享有优先过滤权,因此判定输入的类别时影响也就最大。
ID3 算法选择属性的思路是:在每一步都选择“最大程度上能减少不确定性的属性”。比如数据集有 A, B, C, S 四个属性,S 是最终分类属性。现在考虑决策树根节点是 A, B, C 中哪个。设 A, B, C 不确定性是 x, y, z,若 x > z > y,则显然 B更具有确定性,它能最大程度上减少不确定性,应该选择 B 作为当前的属性。
前面已经指出,不确定性度量即是信息熵,B 的信息熵 H(B)。整个数据集的信息熵即是 S 的信息熵 H(S), 把 H(S)-H(B) 称为 B 属性的信息增益(即 B 属性减少的不确定性)。类似地,当前属性为 B, 取值为 b1,要从 A, C 中决定它下面的属性时,计算 A, C 以 B 作为先手条件时的信息熵,取最小的那个即是 B 下面的属性。
下面给出计算某属性的信息熵的方法: 属性 A是颜色,S 表示属性甜,输入样本对于A, S 关联关系的支持度为以下: 绿色 - 不甜 - 2 绿色 - 甜 - 1 黄色 - 甜 - 3 黄色 - 不甜 - 2 红色 - 甜 - 4 则计算 H(A) = 3/12H(A, 绿色) + 5/12H(A, 黄色) + 4/12H(A, 红色),各分项: H(A, 绿色) = -(1/3log1/3 + 2/3log2/3) H(A, 黄色) = -(3/5log3/5 + 2/5*log2/5)H(A, 红色) = 0
决定分支 H(A, 绿色) 下面的属性时,以 A 为绿色的所有样本去计算 H(B),H(C),比较 H(A, 绿色)-H(B) 和 H(A, 绿色)-H(C) 的较大值,(也即 H(B) 和 H(C) 较小值)。 总之,ID3 算法是递归的。 考虑属性的信息熵计算方法是 H(A) = p1H(A, a1) + p2H(A, a2) + p3H(A, a3),ID3 算法的缺点是,某属性分支值越多,较大确定性的分支也就越多,则属性的信息熵值可能很小:这将导致增益很大,则决策树很有可能选择了这个属性。考虑“编号”属性与 S 的支持度关系: 编号1 - 甜 -1 编号2 - 甜 -1 编号3 - 不甜 -1 编号4 - 不甜 -1 编号5 - 甜 -1 编号6 - 不甜 -1 编号7 - 甜 -1 编号8 - 甜 -1 编号9 - 甜 -1 编号10 - 不甜 -1 编号11 - 甜 -1 编号12 - 甜 -1 每一个子项的信息熵是:1/12 0, 则总的信息熵 H(编号)=0,增益无疑是所有属性中最大的,它将被作为决策树的根节点。然而,显然这个属性不应该放到决策树如此靠前的位置。这就引出了 C4.5 算法:替代 ID3 算法的增益算法为增益率算法: Gain_raion = Gain(B) / IV(B),其中 Gain(B) = H(S)-H(B), IV(B) 被定义为: p1logp1 + p2logp2 + p3*logp3 + ..., IV(B) 与属性的值个数正相关,它将压制值较多的属性的增益。 - golang 的 interface 多态的实现与 c++ 多态实现的思路不一样。c++ 是由将各个对象中增加了一个虚表指针。而 golang 是将虚表指针放置于 interface 中,完全不一样的思路。golang 的实现的优点在于,在不需要多态的场景下,对象不会多出一个指针空间的浪费。
- mysql 锁与事务控制系列好文:http://www.cnblogs.com/crazylqy/p/7611069.html
- TextMinning 算法亮点: 1.使用了 effective rmq/二阶 rmq: 结合 st 算法,将数组线性分为 m 块,计算各块的最值,对 m 个最值再构建一个 brmq, 使用 brmq 对外提供服务。各个块内部仍使用基本的 rmq 去构建。设元素总个数为 N,块长度为 L 则总的时间复杂度为: 一阶 rmq 构建时间 N/L LlgL 二阶 rmq 构建时间 N/Llg(N/L) 在线查询时间 O(1) 2.为计算频繁序列,要计算各个排序相邻的后缀的公共子串。先计算出 height 数组(从第一个后缀与排名在它前面的后缀的公共串开始计算,设公共串长度 k,则第二个后缀与排名在它前面的那个后缀的公共串的长度必定大于等于 k-1)。 3.在 height 数组上使用二分的 effectiveRmq 算法,可得频繁序列。另外需要对一些频繁序列进行的处理: 递归时相同序列不要处理,
- 知乎 live 参考:https://www.zhihu.com/question/50185864?from=profile_question_card
- 观察者设计模式:即是发布/订阅模式。本质在于,发布者提供一组接口用来增加订阅者,当事件发生时,发布者通过调用订阅者的接口去告之(push 模式), 或发布者触发订阅者事件让订阅者来取(pull 模式)。
- 装饰者设计模式:装饰者组合一个类之对象,不同的装饰者有自身不同的装饰效果,通过被装饰者的接口对外提供服务。装饰者和被装饰者都实现自同一接口(即提供真实服务的接口)。
- 策略模式:context 类提供接口用以设置策略对象,context 提供统一接口对外服务,内里行为调用策略对象。
- linux: 1.daemon 进程:守护进程,运行在后台,无终端接口,通过配置文件影响行为,日志文件观察其行为。daemon 进程用来处理“后台业务”,如网络数据转发,任务调度,等等;2.僵尸进程:父进程 fork 的子进程挂了,父进程没有通过 wait 去获得这一事件,因此父进程的进程空间仍留有那个已经死亡的子进程的信息;3.孤儿进程:父进程死亡子进程仍存活,这些孤儿进程会被 init 进程收养,用以在这些孤儿进程死亡时清理相关信息;4.信号: SIGINT 中断信号:终断一个前台进程(如 shell 里启动的进程。可以显式地通过 Ctrl+C 发射此信号) SIGTERM 终止信号:向一个后台进程提出终止的请求。进程可以捕获到这一信号并且根据情况自行处理 SIGQUIT 退出信号:提醒程序当前有错,生成一个 core 文件,然后终止运行 SIGKILL 强制杀死:进程捕获不到此信号,必须死亡 SIGSTOP 暂停执行
- 同步&异步用来描述外在结果: 1) A 调用 B 是同步的:B 内部要处理好并准备数据才会返回。B 内部不一定阻塞 2) A 调用 C 是异步的:C 直接返回并后续处理好再将结果通知给 A。C 内部也不一定非阻塞。 总之,同步&异步 是两个系统通信外在表现出来的结果,与阻塞/非阻塞并无关系。 阻塞&非阻塞是底层限制:A 请求 B 这个设备 IO,B 只支持阻塞处理再返回。但这并不妨碍我们对 B 提供一个异步接口:操作系统可以加一个中间层:操作系统同步收 A 的请求异步转发 B 的结果:这就是异步的行为。若 B 天生支持非阻塞且后续通知的方式,则不再需要操作系统中间层额外处理,这意味着更高效。此时 B 外在看起来就是一个异步 IO,因为代码以异步的方式读写它。否则 B 是一个同步 IO。 对于异步 IO ,不会阻塞/挂起线程,驱动程序操作完成会时通知线程(windows 下四种方式:触发设备内核对象,触发事件内核对象,在发起请求的线程的 APC 队列中增加一项,IOCP)。 epoll 是通过多路复用机制提高线程对于 IO 的处理效率的。一个 epoll_event 内核对象关联多个 io 描述符。只需要在一个线程里面阻塞(epoll_wait),当发生事件时线程重新调度,处理。线程接收到事件后,仍然需要调用阻塞的系统调用去收发数据。 结合以上, epoll 机制实际上是同步阻塞的:同步阻塞 accept, read, write。它的先进之处在于 IO 多路复用,只需要阻塞一个线程,就可以读写多个 IO。 iocp 也是多路复用,只不过,它是 M-N 模型:通过 N 个线程的线程池来处理 M 个 IO。而且它更为先进的是,操作系统实现了异步 IO,所以它的底层是非阻塞的。而由于 iocp 框架下,我们不需要写出去调用同步系统调用的代码,故它外在看起来是异步的。故 iocp 可以认为是异步非阻塞的。 然而需要注意的,这并不是一定说明 iocp 性能就强于 epoll:本质上,IO 必然都是阻塞的。性能要看具体场景:对于异步来说,节省出的用户线程是否有效的被利用到了。
托管代码被编译器编译为中间语言,运行在公共语言运行库(CLR)上,包括 visual Basic, C#。托管代码享受 CLR 提供的安全检测,内存回收等功能。 非托管代码被编译为机器码,直接运行在 CPU 上。 JIT 是 .NET 的即时编译器:首次运行时编译为本地代码执行,之后不再需要。故效率高于 JVM。jvm 将字节码载入内存,是一个 interpreter,在运行时模拟为 x86 的指令。
- 《分布式频控系统的设计和优化》(来自手机KM):https://km.tencent.com/openkm/url/afgzer
- reinterpret_cast 用来转化函数指针。
- 拥塞控制,流量控制:https://www.zhihu.com/question/38749788
- 零拷贝技术: 一般地,当需要通过 socket 传输大文件时,应用程序先读取文件内容(从内核拷贝文件内容到进程空间),再调用 send 函数,将内容从应用空间拷贝到内核,内核再发送出去。sendfile 减少内核态和用户态的拷贝,直接从内核态发送。这样效率大大提高。 splice 系统调用更为抽象,通过指定两个文件描述符,直接实现大块内容的拷贝。可以是文件,可以是网络。该系统调用可以设置为阻塞或者非阻塞(之后通过接收信号感知完成)。
- reactor 模式和 proactor 模式都是IO多路复用的设计模式。 reactor 模式的步骤大概是: 1.注册就绪事件和相应的事件处理器 2.事件分离器等待事件。线程被挂起。 3.分离器被激活,线程调度,调用事件处理器 4.事件处理器完成读写。可以注册新的事件,然后返回到事件分离器,线程被挂起。
proactor 的不同之处在于,当事件分离器被激活时,IO 已经完成,而不是 reactor 模式的已就绪。从高层次来讲,proactor 更像是一个异步 IO 的行为:线程提交 IO 请求时直接返回,IO 完成时被通知。 reactor 模式更像是一个同步 IO 的优化:线程提交 IO 请求时直接返回,IO 就绪时被通知,再去向 IO 取数,同步的意义在于,“同步得知 IO 的状态,之后再做某事”。
- explain select ... 分析其执行计划。对于查询时使用的索引类型, type = All - 扫描所有记录 index - 扫描所有索引值 range - 扫描索引的某个范围的值 ref - 扫描非唯一索引的值或其索引值前缀是否等于某值 eq_ref - 对于每个索引值,只有一条记录(注意索引的类型不是唯一索引) const/system - 查询的记录不可能超过一行。如根据 primary key/ unique index 的查询 NULL - 不需要访问索引,直接返回数据。如 select 1;
show profiles 列出所有最近查询(各查询一个标志 id),再通过 show profile for query id 显示查询执行中各步的时间耗费。一般 sending data 是最耗时的。 edgarlli(李志浩) 2018-05-08 15:30:00 ICP:index condition pushdown 索引是在存储引擎层中实现的,而非服务层。 查询条件 where a=? and b > ?,其中 a 是 索引,b 不是。非 ICP 的查询步骤是,使用 a 去索引层过滤记录,再回表查询,根据 b 条件过滤。最终返回。 ICP 的优化为,用 a 条件在索引层圈定范围后,联合 b 条件过滤,提前过滤提高效率。
- 不会用到索引的情况:
- % LIKE查询 2.where 条件存在隐式类型转换,特别是索引字段为字符串类型,但使用一个整数去查询 3.复合索引,查询时并未使用复合索引的最左索引 4.若执行计划分析到使用索引更慢,则不用索引。如查询某字段以 S 开头的记录,由于记录行太多,还不如直接全表扫描 5.where 中 or 条件存在字段无索引(不论顺序)
- 查看 mysql 全局索引的使用情况: Handler_read_key 值越高表示查询中索引次数使用越高 Handler_read_key_next 越高则表示越多次去存储文件中扫描,说明应该要适当建立索引
- 使用 optimize 命令优化 mysql 表的存储:表中可变长度的行(varchar, Text, blob)若更改较多,则存在很多存储碎片,使用 optimize 可减少硫化,合并。
- mysql sort 的优化宗旨是尽量通过索引直接返回有序数据,减少额外的排序(file sort)。 select * from ... order by a; 若 a 是索引但由于 select 了其它字段,将导致回表关联。这就无法直接返回有序数据,需要有序地去关联,而这还不如直接在表中 sort by a。 此例 select a, b from ... order by b; 若 (a,b)是联合索引,返回虽然覆盖索引,但是 order by b ,这需要在索引上面排序。故还是会存在 file sort。 file sort 指宽泛的排序,而非一定在 table 上执行的排序。 filesort 的步骤是,通过相应的排序算法,在 sort_buffer_size 指定的内存排序区上排序,如果内存装载不了,则会将磁盘上的数据分块,使用归并排序。注意 sort_buffer_size 是指各线程一次排序最多使用多少空间排序,而非全局共享的排序区。 优化的手段: where 和 order by 的字段都要使用相同的索引,且 order by 的顺序和索引的顺序要一样(不可颠倒字段顺序),且 order by 的字段都为升序或降序(一致有序性)。否则必然会 filesort。 当 fielsort 避免不掉时,要尽可能再优化:
- 增加 sort_buffer_size 尽量在内存中排序(根据实际资源考虑) 2.filesort 分为一次扫描排序和两次扫描排序。后者是,仅取出排序字段和记录指针进行排序,之后再根据指针回表得到记录。当查询的字段太多时会使用两次扫描排序。阈值是 max_length_for_sort_data 控制。(根据实际资源可调整此阈值。)
注意 group by a,b 默认将记录按 a,b 排序。若不想排序,则可在 group by 查询最后加上 order by NULL 禁止排序来提升效率。
- 优化 limit m, n 1.在索引上完成排序分页的操作,再根据索引指示的主键回表关联得到其它字段。 select a, b, c from t order by a limit 1000, 10; 改写为: select t.a,t.b,t.c from t inner join (select a from t order by a limit 1000, 10) tt on t.a=tt.a; 2.将 limit m,n 转化为 limit n。前者需要只查 n 行记录,但是从 m 开始这个条件的限制,必须要全表扫描。而 limit n 则不然。 这一优化需要调用者记录每次查询的记录的主键和该记录按某字段排序后的偏移量的对应关系。每次查询时,多给定这个主键为条件。
- 流量控制拥塞控制:https://www.zhihu.com/question/38749788;mysql 加锁 http://www.cnblogs.com/crazylqy/p/7611069.html
- 分布式事务两阶段提交:第一阶段:各个资源管理器 (RMS) 做完事务分支动作后向事务管理器(TM) 发送 prepare;第二阶段:TM 综合评估,所有的 RMS 一齐回滚或提交。当只有一个事务资源时则会退化为一阶段提交。
- 网络字节序采用大端。 大端是高位在低内存地址处。相反。 小端是高位在高内存地址处。相同。 htonl , htons 用来将本地字节序转为网络字节序。前者转化 IP,后者转换 PORT。 AF_INET 表示地址簇,PF_INET 表示协议簇。设想是一个协议簇包括多个地址簇。目前的实现这两个值是一样的。都可用于 socket 系统调用的 domain 参数:用于网络通信的域/选择信息协议簇。
- SOCK_DGRAM 用于 UDP 协议。SOCK_STREAM 用于 TCP 协议。
- udp 也可以带 connect
- 使用 select 来避免 UDP 中的 recvfrom 无穷等待的方法: select 去监视这一个 socketfd,发生事件则说明有数据,而超时到了还没有数据事件,则可以结束等待。 https://www.cnblogs.com/liang-hk/archive/2012/04/28/2475199.html
- 二问题: 1.分布式事务一致性 两阶级提交 -> 2.消息队列可靠性保证及实现 至少一次模式 -> 持久化 + 超时重传队列 + 死信队列 3.mysql 分布式数据库一致性 两阶段提交;存在的问题,一个节点 prepare,但是断电。 4.你怎么看待多重继承 空间布局复杂,接口行为复杂。不如组合。
- 如何实现一个单例对象
- 无锁编程 & 自旋锁应用的场景
- 高性能 C++ 网络框架
- leveldb 的底层数据结构 skiplist,为什么要使用 skiplist,有什么优势。为什么不用 hash 作为存储结构? 查找效率高。逻辑简单,易于实现。底层数据结构易于存储为线性结构:除第0层外各层文件均是有序的。而 hash 只适用于精确查找,不能大于小于,或者部分匹配,且存储的形式非顺序存储,读取时容易发生内存 missing。
- 局部静态变量是如何实现只初始化一次的呢 https://www.cnblogs.com/xuxm2007/p/4652944.html
- 静态局部变量的初始化:
- 若静态局部变量类型是基础类型则它是在 main 函数执行之前初始化的,与静态全局变量一样。
- 若静态局部变量类型是自定义类型则它是在第一次被访问到时初始化的。做法是使用一个变量去守护它,并且通过锁机制来实现。
- io 多路复用的 select/poll 机制与 condVar(条件变量) 有惊群效应:唤醒所有线程,但并非所有线程都有资格处理,或者唤醒所有线程,但条件并未满足。
- 条件变量为什么要传入一个 mutex https://www.zhihu.com/question/24116967
- 深入静态局部变量的初始化: 在并行情况下,为了保证静态局部变量只被初始化一次, 使用一个变量来守护它:guard. 第一个字节表示已经初始化,第二个字节表示正在初始化。多个线程对这个变量的读取/存储使用 acquire 和 release 语义:保证读取最新的值。而且,当第二个字节指示了正在初始化,则其它线程会使用条件变量在用户态自旋等待。
- 当基类没有虚函数但我们想让它成为虚类时,只需要将它的析构函数声明为纯虚,并且给它一个实现即可。
- 定义一个类的对象数组,C cs[10],只有当 C 有默认构造函数,或者构造函数的参数全有默认值时,编译成功。当是后者时,编译器会在暗地里生成一个无参的默认构造函数去调用默认参数的构造函数。
- 不要试图去判断一个对象是否分配在堆上: 1.若使用地址比较法:基于栈的地址高于堆。但全局的对象所在区域是在静态区,非栈非堆,它会影响判断。该法可能不具备移植性。 2.若重载 static operator new 函数,通过 static 变量标记当前对象被分配在堆上,然后在构造函数里面判断此静态变量的值,进行分支处理。此法可能也有纰漏: 通过 new C[100] 的方法一次性在堆上构建 100 个对象,它调用的是 static operator new[]:我们也得如重载此函数:然而问题是,此函数构建的是 100 个对象,却只有一个静态变量标志,后面的 99 个对象就懵了。--- 这实现不了我们的功能。 因此,我们尽量不要去判断一个对象是否在栈还是堆上。若非要,我们可以继承自 HeapTraked 类实现我们的功能,此类使用 list 记录了每一次分配出去的堆内存地址。
- 禁止类的对象产生在堆上:将 static operator new 和 static operator new [] 函数声明为 private 且不实现。 禁止类的对象产生在栈上:将析构函数声明为 private ,且提供一个替代者给类析构,如 destroy 函数。
- 数组无多态行为,且以数组处理多态行为往往是错误的: Base * bs = new Derived[10]; 考虑 bs 和 bs+1 相隔的是 sizeof(Base) 还是 sizeof(Derived) 就明白了。
- MVCC(多版本控制协议)解决的问题是:读写不冲突。读快照,写加排他锁。每行记录有两个字段表示版本:创建时间和删除时间(实际上存储的是事务序列号)。每次更新都产生一个新的版本。读时只能读取到创建时间小于当前事务,删除时间为空或大于当前事务的记录。 select .... lock in share mode 加共享锁。
- mysql 使用两阶段锁协议:同一个事务中,所有的 lock 操作均在 unlock 操作之前。 《事务处理:概念与技术》中证明,只有上述条件满足,才能有: 如果事务是良构的且是两阶段的,那么任何一个合法的调度都是隔离的。 在事务中只有提交(commit)或者回滚(rollback)时才是解锁阶段, 其余时间为加锁阶段。
- mysql 死锁的本质是,两个事务请求的锁被已经对方占有。而原因往往是加锁的顺序不一致。
- 幻读和不可重复读的区别: 不可重复读是指同一个事务中,先后两次相同条件的查询得到的结果不一样。这发生于 RC 级别下。 可重复读与此相反,RR 级别解决了"不可重复读"的问题,使相同条件的多次查询结果一致。 对于可重复读,依然可能出现幻读,原因是,A 事务中修改的记录不会在 B 事务中可见。但是当 B 事务中的查询(一般是隐式查询,如 update/delete) 相关于 A 事务中的修改,则此时发生幻读。 如有以下执行时序 :
- B 事务 select count(0) from t where id = 2; //结果为 0
- A 事务 insert into t(id, name) values (2, 'A insert')
- B 事务 select count(0) from t where id = 2; //结果依然为 0
- B 事务 insert into t(id, name) values (2, 'B insert')// error, duplicated key insertion 第 4 句发生幻读。可见,"可重复读" 和 "幻读"没有直接关系。可重复读隔离级别下也会发生 "幻读",此时要通过显式地加锁来解决: select ... for update
edgarlli(李志浩) 2018-05-11 11:48:56 function 文件夹中的类有配置: drdps.hbase.listener.namespace drdps_mbp_ccs edgarlli(李志浩) 2018-05-11 15:09:52 腾讯海量服务之道: http://baozh.github.io/2015-12/tencent-massive-service-discipline/ edgarlli(李志浩) 2018-05-11 16:45:45 消息队列的特点: -- 消息传递的模式 RabbitMQ : 使用 Erlang 实现,点对点模式(一经消费便从队列中移除),但也能通过设置交换器类型实现发布/订阅模式(广播模式)。 KafKa 是发布订阅模式。限制消费组只有一个消费者即是点对点模式 消息消费模式。均有 pull模式(延迟) 和 push模式(实时)。但要保证 push 模式不会压垮消费端。pull 模式对消费端限流天然支持。 内部队列。优先级队列(当 broker 消息有堆积时此功能有意义)或先入先出队列;延迟队列(broker 堆积消息,超时后才推送给消费者. 较优的做法是维持不同超时时间的队列, 5s, 10s, 30s,各个队列依次处理,省去为每个消息排序的耗费);死信队列(消息投递失败且重试 N 次依然无 ack,将此消息发置在死信队列中用以后续排查问题或手动重试); 重试队列(没有收到消费者的 ack,则放入重试队列,标记重试次数,已重试次数越大则下一次重试的间隔时间越长。直到次数超限则放入死信队列)。 消息堆积+持久化 消息适量堆积用于减轻消费者的压力,使消费者的交易流量消峰(突然拥入大量交易)。若内存中堆积的消息过多则刷入磁盘。 Kafka 所有的消息均堆积在磁盘。 消息路由与追踪 消息队列必须要支持消息的路由:类似某些服务发现协议。并且之后需要追踪出消息的传输路径。
- mysql binlog 几种形式: http://blog.itpub.net/15498/viewspace-2125085/
- statement(SBR), 语句模式, 每条修改数据的 sql 被保存. 优点是 binlog 小且易于追溯, 审计; 缺点是可能由于随机函数, 用户定义函数, 存储过程, 触发器等导致数据不一致。此外 sql 的追溯很可能会导致在表中加更多但不必要的锁。
- row(RBR), 记录每行数据的变化: 当要修改的数据很多时将导致 binlog 巨大。
- 混合模式: 结合上面两种, 一般情况下使用 statement, 当无使用 sql 追溯出数据时(如随机函数, UDF, )使用 row。
- zookeeper 原理简介:https://www.cnblogs.com/wuxl360/p/5817471.html
- zoopker 本质上提供了分布式同步/协调服务。实现的原理: 类似于文件系统的层次的命名空间。每个节点为 ZNode,各个叶子节点对应于一个服务器节点。各个 ZNode 上定义了一些原语,用于原子性地访问,或更改 ZNode 数据(状态信息,绑定的数据,子节点)。ZNode 可能绑定一些 watcher,当发生事件时时会触发对应的节点(客户端)。客户端也可主动去查询 zookeeper 某些信息。 zookeeper 可感知客户端节点的状态(增加,修改),并据此广播/通知给 watcher。
- CAP:一致性,可用性,允许存在网络分区(网络可能被划分为不连通的多个块)
- ACID:原子性,一致性,隔离性,持久性
- 分布式 CAP 和 BASE: https://blog.csdn.net/zhangyuan19880606/article/details/51143628
- zookeeper 可以用于节点监控,选举,服务发现等场景。
- 动画演示的 raft 协议:http://thesecretlivesofdata.com/raft/
- raft 协议
- 三角色: followers(初始状态/正常节点状态),candicate, leader
- leader 选举: 各个 follower 节点有一个超时器, 一旦它接受到了 leader 的心跳包则回包且超时器重新开始计时(每次超时值均是随机的 150ms - 300ms), 超时值到了则它成为 candicate, 广播发出 "request for vote" 信息, 进行投票。半数以上(含自己, 注意这个半数并非是全局的全数)同意则它可为当前网络分区中的 leader。 当两个 follower 同时变为了 candicate 则发生"split vote": 递归此过程直到某个 candicate 票数过半(由于随机的超时值不可能永远一样)。 当存在多个网络分区:A, B 分区1以 A 为 leader 。C, D, E 分区2以 C 为 leader, 分区1比较老, term 比较小, 所以当网络可达时,C 为作 leader 将不变同时 A 变为 followers。
- log replication leader 负责和外界交流。对于传入的写/更新操作,leader 加入它的 log entries,然后在下一次的心跳包里面带上此写/更新讯息,若 leader 收到了超半数 followers (此处的半数是全局的半数)的 Ack 则 a). 该写入被提交到 leader b). 返回给写入者成功 c). 发送 response 给所有 followers -- 各 followers 提交更新
- 限流算法: 令牌捅算法, 每隔 1/n 秒往桶里放置 1 个令牌, 满则不放. 这可实现平滑的限流: 每秒 n 笔交易. 滑动窗口算法: 每次统计窗口里面有多少在处理中的交易。为提高精度,可将窗口分隔为多个连续的子窗口,每过一段时间移动一格。此模型可应对尖锋交易。 redis 限流(基于 redis 全局计数器是全局唯一的), 维护 redis 两个变量: count, time, 每到一段时间就清空 count,同时保证 count 有上限即可达到限流。
- C++ 11 nullptr是一个对象,类型是 std::nullptr_t,
- C++库中提供了这些智能指针auto_ptr, unique_ptr, shared_ptr and weak_ptr. 左值引用是通过 const 指针实现的,在栈上有一个 const 指针指向变量。 右值引用:
- 右值引用可以重新 rebind
- 右值引用 bind 的对象是右值(常数,临时变量,没有其它用户的变量),而非左值(变量)。左值引用是永久的,不可 rebind,而右值引用否。
- 右值引用被用于移动拷贝或移动赋值的场景,以用于加速效率。本质上是由于利用局部对象去构造对象时,有些成员不只需要浅拷贝即可。 https://www.cnblogs.com/ldlchina/p/6608154.html 右值引用的本质是,充分利用临时变量,减少拷贝/赋值时的效率。
- 分布式问题: 事务一致性,容灾,流量控制,尖峰交易处理,负载均衡(一致性 hash 算法),水平扩容,选举算法,服务发现,分布式数据库高可用,缓存, 具体应用:rpc,MQ,分布式数据库,缓存,
- kafka 分区: 对于每个 topic 上的消息分区存放。本质上有几个原因: 1.为了减少消息顺序写和顺序读时的锁。若只有一个分区,则消息读和写存在竞争。使用物理层的分区以减轻竞争。
- 容灾,多个分区数据不会一起丢失。
- 分区可以在不同的服务器上,这增加了可存储的消息的总量。 下面是有四个节点的 kafka 集群,每个节点是一个 borker,topic 有四个分区,各有两个副本。 考虑负载均衡,各个 broker 应至少含有某 topic 的一个 leader。partition 和 broker 对应的算法是:parition i 的 leader 放到 broker i 上,副本依次放到 broker i+1,broker i+2 .... 上,超出范围则用模除。下图是原来有 broker1,2,3,4 后面增加 5,6 的 parttion 分布。 producer 可指定消息发送的分区。 每个分区是一个 leader 加一组 followers 的分布式结构,用以提高消息可靠性。 每个分区是一个文件夹,包括多个 segement。每个 segement 是一个二元组(索引文件+消息数据文件)。offset 用于描述索引文件中的偏移。 参考: http://leaver.me/2015/09/04/kafka%E4%B8%AD%E7%9A%84partition%E5%92%8Coffset/?utm_source=tuicool&utm_medium=referral
- hbase 中的列是可变集合。每个列被称为 family,可以有多个子列: qualifier。即: family + qualifier 表示一个惟一的“列”。 hbase 内部使用毫秒级时间戳作为数据版本,插入数据时可以手动指定 timestamp 以区分数据的版本。版本低的数据不会被查出来。
- 敏捷开发概念及实践 价值观
- 敏捷开发是一组价值观和原则。
价值观明确了哪些事情比哪些事情更为重要。 个体和互动 高于 流程和工具 工作的软件 高于 详尽的文档 客户合作 高于 合同谈判
响应变化 高于 遵循计划
基于价值观产生 12 条原则,用以指导我们在软件开发中的决策: 1.我们最重要的目标,是通过持续不断地及早交付有价值的软件使客户满意。 2.欣然面对需求变化,即使在开发后期也一样。为了客户的竞争优势,敏捷过程掌控变化。 3.经常地交付可工作的软件,相隔几星期或一两个月,倾向于采取较短的周期。 4.业务人员和开发人员必须相互合作,项目中的每一天都不例外。 5.激发个体的斗志,以他们为核心搭建项目。提供所需的环境和支援,辅以信任,从而达成目标。 6.不论团队内外,传递信息效果最好效率也最高的方式是面对面的交谈。 7.可工作的软件是进度的首要度量标准。 8.敏捷过程倡导可持续开发。责任人、开发人员和用户要能够共同维持其步调稳定延续。 9.坚持不懈地追求技术卓越和良好设计,敏捷能力由此增强。 10.以简洁为本,它是极力减少不必要工作量的艺术。 11.最好的架构、需求和设计出自自组织团队。 12.团队定期地反思如何能提高成效,并依此调整自身的举止表现。