diff --git a/_config.yml b/_config.yml index a73bb33..0c4468f 100644 --- a/_config.yml +++ b/_config.yml @@ -10,12 +10,10 @@ kramdown: plugins: - jekyll-paginate +title: 悟空小饭 +description: 通过 Jekyll 和 Github Pages 搭建的个人 Blog ,记录一些日常生活以及杂七杂八的技术文章,如有不足不吝赐教,如果喜欢欢迎转发。 production_url: 'https://jin-yang.github.io' + example_repository: 'https://github.com/Jin-Yang/examples/tree/master' aspire_repository: 'https://github.com/Jin-Yang/aspire/tree/master' kernel_docs_url: 'https://www.kernel.org/doc' - -description: > # this means to ignore newlines until "baseurl:" - Write an awesome description for your new site here. You can edit this - line in _config.yml. It will appear in your document head meta (for - Google search results) and in your feed.xml site description. diff --git a/_drafts/2012-03-06-iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii_init.md b/_drafts/2012-03-06-iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii_init.md new file mode 100644 index 0000000..76941c3 --- /dev/null +++ b/_drafts/2012-03-06-iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii_init.md @@ -0,0 +1,7681 @@ +--- +title: Kernel 内存映射 +layout: post +comments: true +language: chinese +category: [linux] +keywords: linux,内存,memory,映射 +description: +--- + + + + + +## iomem VS. ioports + +Linux 在 proc 目录下有 iomem 和 ioports 文件,主要描述了系统的内存和 IO 端口资源分布,两个地址空间分别编制,均是从 0 开始,如果硬件支持 MMIO,port 地址也可以映射到 memory 空间去。 + +### IO 地址映射 + +对于外设的访问控制,最终都是通过读写设备上的寄存器实现的,而寄存器不外乎控制寄存器、状态寄存器和数据寄存器,这些外设寄存器也称为 "IO端口",并且一个外设的寄存器通常是连续编址。不同的 CPU 体系对外设 IO 端口物理地址的编址方式也不同,分为 IO 映射方式 (IO-Mapped) 和内存映射方式 (Memory-Mapped)。 + +x86 为外设专门实现有单独的地址空间,称为 "IO地址空间",这是独立于 CPU 和 RAM 的物理地址空间,它将所有外设的 IO 端口均在这一空间进行编址。CPU 通过设立专门的 IN/OUT 指令来访问这一空间中的地址单元,也就是 IO-Mapped ,其地址空间一般只有 64KB(```0000~FFFF```)。 + +Linux 设计了一个通用的 ```struct resource``` 来描述各种 IO 资源,包括 IO 端口、外设内存、DMA 和 IRQ 等,通过树形结构来管理每一类 IO 资源。 + + + + +## 用户虚拟地址到物理地址转换 + +查看当前进程的虚拟地址映射 ```cat /proc/self/maps```, + +通过命令你给 ```ps aux | less``` 查看内存相关参数时,会发现两个比较有疑问的指标 RSS、VSZ (单位是KB),通过 ```man 1 ps``` 查看对应的解释如下: + + * RSS resident set size, the non-swapped physical memory that a task has used (in kiloBytes). (alias rssize, rsz). + * VSZ virtual memory size of the process in KiB (1024-byte units). Device mappings are currently excluded; this is subject to change. (alias vsize). + +简言之,RSS 就是这个进程实际占用的物理内存;VSZ 就是进程的虚拟内存,包括了还未发生缺页异常加载映射到内存中。如果通过上述的两个指标来评估实际内存使用量,那么是错误的 !!! + +ps 显示的统计结果实际上有个前提假设:如果只有这一个进程在运行。在现在的内核中,显然是不可能的,很多进程会共享一些内存,例如 libc 。 + + + + +## 其它 + +### 刷新内存 + +可以通过手动执行 sync 命令刷新内存,以确保文件系统的完整性,sync 命令将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件。 + +{% highlight text %} +# sync; echo 1 > /proc/sys/vm/drop_caches // 仅清除页面缓存PageCache +# sync; echo 2 > /proc/sys/vm/drop_caches // 清除目录项和inode +# sync; echo 3 > /proc/sys/vm/drop_caches // 清除页面缓存,目录项和inode +{% endhighlight %} + +其中第一个是比较安全的,可以在生产环境中使用;最后一个操作比较危险,不建议在生产环境中使用,除非你自己清楚在做什么,源码实现在 fs/drop_caches.c 中。实际上,上述的操作通常针对 IO 的基准测试;否则不建议清除缓存。 + +可以通过如下命令清除掉交换区的空间。 + +{% highlight text %} +# swapoff -a && swapon -a +{% endhighlight %} + + + + + + + +### buddyinfo + +是 Linux Buddy 系统管理物理内存的 debug 信息,为了解决物理内存的碎片问题,其把所有空闲的内存,以 2 的幂次方的形式,分成 11 个块链表,分别对应为 1、2、4、8、16、32、... ... 1024 个页块。 + +Linux 支持 NUMA 技术,NUMA 系统的结点通常是由一组 CPU 和本地内存组成,每个节点都有相应的本地内存,z在 buddyinfo 中通过 Node N 表示一个 NUMA 节点,如果硬件不支持 NUMA,则只有 Node 0。 + +而每个节点下的内存设备,又可以划分为多个内存区域 (zone),因此下面的显示中,对于 Node 0 的内存,又划分类 DMA、DMA32、Normal,部分还有 HighMem 区域。 + +而每行的数字表示上述 11 个链表连续空闲的区域的大小,例如 Normal 中的第二列表示连续两个内存的大小为 ```3506*2*PAGE_SIZE```。 + +{% highlight text %} +# cat /proc/buddyinfo +Node 0, zone DMA 2 2 2 1 3 2 0 0 1 1 3 +Node 0, zone DMA32 118 156 996 790 254 88 47 13 0 0 0 +Node 0, zone Normal 2447 3506 613 31 0 0 0 0 0 0 0 +{% endhighlight %} + + + + + + + + + +在 Linux 内核启动时,会打印类似如下的内存初始化信息,由于此时内核的引导过程未完成,initrd 以及初始化占用的内存未释放,所以最终内核可用的内存要更大一些。 + +{% highlight text %} +----- 查看内核打印的内存信息,包括释放的内存 +$ dmesg | grep -E "(Memory:|Freeing)" +[ 0.000000] Memory: 5428672k/8970240k available (6765k kernel code, + 686640k absent, 309176k reserved, 4432k data, 1680k init) +[ 0.008667] Freeing SMP alternatives: 28k freed +[ 0.728176] Freeing initrd memory: 27652k freed +[ 2.757835] Freeing unused kernel memory: 1680k freed + +----- 计算上述打印的可用内存 +$ echo "5428672 + 28 + 27652 + 1680" | bc +5458032 + +----- 通过free命令查看当前内存 +$ free -k + total used free shared buff/cache available +Mem: 8070604 2425256 714864 778268 4930484 4515144 + +5428672k/8970240k available + 分母表示可用物理内存的大小; + 分子表示可供kernel分配的free memory的大小; + +absent: + 不可用的物理内存大小,包括了 BIOS 保留、Kernel 不可用的物理内存; +{% endhighlight %} + + + +物理内存可以通过 ```dmidecode --type 17``` 查看,包括了很多详细的物理信息,不过对于虚机来说不支持。 + + +## 其它 + +### sysrq + +Linux 中,有一个非常强大的 sysrq 功能,通过该功能可以查看一些内核的当前运行信息,该信息会打印信息到内核的环形缓冲并输出到系统控制台,一般也会通过 syslog 输出到 ```/var/log/messages``` 。 + +有些发行版本该功能是关闭的,可以通过如下方式打开。 + +{% highlight text %} +# echo 1 > /proc/sys/kernel/sysrq // 打开 +# echo 0 > /proc/sys/kernel/sysrq // 关闭 +# vi /etc/sysctl.conf // 设置永久生效 +kernel.sysrq = 1 +{% endhighlight %} + +一些常见的功能可以参考如下内容。 + +{% highlight text %} +# echo "b" > /proc/sysrq-trigger // 立即重新启动计算机 +# echo "o" > /proc/sysrq-trigger // 立即关闭计算机 +# echo "m" > /proc/sysrq-trigger // 导出内存分配的信息到demsg +# echo "p" > /proc/sysrq-trigger // 导出当前CPU寄存器信息和标志位的信息 +# echo "t" > /proc/sysrq-trigger // 导出线程状态信息 +# echo "c" > /proc/sysrq-trigger // 故意让系统崩溃 +# echo "s" > /proc/sysrq-trigger // 立即重新挂载所有的文件系统 +# echo "u" > /proc/sysrq-trigger // 立即重新挂载所有的文件系统为只读 +{% endhighlight %} + + +### mtrace + +该工具可以用来协助定位内存泄露,默认没有安装,在 CentOS 中可以通过如下方式安装。 + +{% highlight text %} +# yum install glibc-utils +{% endhighlight %} + +假设有如下的测试代码。 + + +{% highlight text %} +$ cat main.c +#include +#include +#include +int main(int argc, char **argv) +{ + setenv("MALLOC_TRACE", "taoge.log", 1); + mtrace(); + + malloc(2 * sizeof(int)); + + return 0; +} + +$ gcc -Wall -g -o foobar main.c +$ ./foobar +$ mtrace foobar trace.log +{% endhighlight %} + +可以看到,有内存泄露,且正确定位到了代码的行数。实际上,mtrace 原理很简单,就是记录每一对 malloc/free 的调用情况,然后检查是否有内存没有释放。 + + + + + + + + + + + + +ptmalloc + + + + + + + +15600615832 + + +## 内存分配 + + + +## Page Allocator + + + +### alloc_page() + +通过 ```alloc_page()``` 函数完成页的分配,在使用时需要通过 ```page_address()``` 完成线性地址转换,详细可以参考 [alloc.c]({{ site.example_repository }}/linux/LKM/memory/alloc.c) 示例程序。 + +内核通过 struct page 描述一个页,所有的页描述符存放在全局 mem_map 数组中,每个 page 代表一个物理页面,整个数组代表着系统中的全部物理页面,数组的下标为页框号 (pfn),代表了 page 结构对应第几个物理页面。 + +页面表项的高 20 位对于软件和 MMU 硬件有着不同的意义,对于软件,这是一个物理页面的序号,将这个序号用作下标就可以从 mem_map 找到代表这个物理页面的 page 数据结构,对于硬件,则 (在低位补上12个0后) 就是物理页面的起始地址。 + +根据是否配置了 ```CONFIG_NUMA``` 会调用不同的函数,一般来说很多发行版本都会配置该选项。 + +{% highlight c %} +#ifdef CONFIG_NUMA +static inline struct page * +alloc_pages(gfp_t gfp_mask, unsigned int order) +{ + return alloc_pages_current(gfp_mask, order); +} +#else +#define alloc_pages(gfp_mask, order) \ + alloc_pages_node(numa_node_id(), gfp_mask, order) +#endif +{% endhighlight %} + +接下来看看实现的详细细节。 + +{% highlight text %} +alloc_pages() + |-alloc_pages_current() + |-get_task_policy() 获取内存的分配策略 + |-__alloc_pages_nodemask() buddy内存分配系统的核心部分 + |-first_zones_zonelist() + |-get_page_from_freelist() 快速分配内存 + | |-zone_watermark_ok() 判断设置的内存水位 + | |-buffered_rmqueue() 从zone中分配order阶的页帧 + | |-rmqueue_bulk() 对于单页分配 + | |-__rmqueue() 从伙伴系统中分配指定的页 + | | |-__rmqueue_smallest() + | | | |-expand() 处理请求页小于当前页的情况 + | | | |-list_add() 将页一分为二,并添加到链表中 + | | |-__rmqueue_fallback() + | |-__mod_zone_page_state() 更新页的统计状态 + | + |-__alloc_pages_slowpath() 慢速分配 +{% endhighlight %} + + + + + + + +## Buddy System + +## Slub + +## 缺页异常 + +可以通过 ```ps -o majflt,minflt -p PID``` 查看进程从启动开始缺页异常的次数,两个参数分别代表了 ```major fault``` 和 ```minor fault``` 。 + +发成缺页中断后,会进入内核空间,然后执行如下操作。 + +1. 检查要访问的虚拟地址是否合法。 +2. 查找/分配一个物理页。 +3. 填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)。 +4. 建立映射关系(虚拟地址到物理地址)。 +5. 重新执行发生缺页中断的那条指令 + +如果第 3 步填充空间时,需要读取磁盘,那么这次缺页中断就是 ```majflt```,否则就是 ```minflt``` 。 + + +## page cache + +page cache 也就是页高速缓存器,其大小为一页,通常为 4K ,在 Linux 读写文件时,用于缓存文件的逻辑内容,从而加快对磁盘上映像和数据的访问。 + + + +三、page cache的管理 + +在Linux内核中,文件的每个数据块最多只能对应一个page cache项,它通过两个数据结构来管理这些cache项,一个是radix tree,另一个是双向链表。 +Radix tree是一种搜索树,Linux内核利用这个数据结构,快速查找脏的(dirty)和回写的(writeback)页面,得到其文件内偏移,从而对page cache进行快速定位。图1是radix tree的一个示意图,该radix tree的分叉为4(22),树高为4,用来快速定位8位文件内偏移。 +另一个数据结构是双向链表,Linux内核为每一片物理内存区域(zone)维护active_list和 inactive_list两个双向链表,这两个list主要用来实现物理内存的回收。这两个链表上除了文件Cache之外,还包括其它匿名 (Anonymous)内存,如进程堆栈等。 + +四、page cache相关API及其实现 +Linux内核中与文件Cache操作相关的API有很多,按其使用方式可以分成两类:一类是以拷贝方式操作的相关接口,如read/write/sendfile等,其中sendfile在2.6系列的内核中已经不再支持;另一类是以地址映射方式操作的相关接口,如mmap等。 +第一种类型的API在不同文件的Cache之间或者Cache与应用程序所提供的用户空间buffer之间拷贝数据,其实现原理如图2所示。 +第二种类型的API将Cache项映射到用户空间,使得应用程序可以像使用内存指针一样访问文件,Memory map访问Cache的方式在内核中是采用请求页面机制实现的,其工作过程如图3所示。 + +首先,应用程序调用mmap(图中1),陷入到内核中后调用do_mmap_pgoff(图中2)。该函数从应用程序的地址空间中分配一段区域作为映射的内存地址,并使用一个VMA(vm_area_struct)结构代表该区域,之后就返回到应用程序(图中3)。当应用程序访问mmap所返回的地址指针时(图中4),由于虚实映射尚未建立,会触发缺页中断(图中5)。之后系统会调用缺页中断处理函数(图中6),在缺页中断处理函数中,内核通过相应区域的 VMA结构判断出该区域属于文件映射,于是调用具体文件系统的接口读入相应的Page Cache项(图中7、8、9),并填写相应的虚实映射表。经过这些步骤之后,应用程序就可以正常访问相应的内存区域了。 + + +add_page_to_hash_queue /*加入pache cache hash表*/ +add_page_to_inode_queue /*加入inode queue即address_space*/ +remove_page_from_inode_queue +remove_page_from_hash_queue +__remove_inode_page /*离开inode queue和hash 表*/ +remove_inode_page /*同上*/ +add_to_page_cache_locked /*加入inode queue,hash 和lru cache*/ +__add_to_page_cache /*同上*/ +仅罗列函数add_page_to_hash_queue,以示完整: +static void add_page_to_hash_queue(struct page * page, struct page **p) +{ + struct page *next = *p; + *p = page; /* page->newNode */ + page->next_hash = next; /* +-----+ */ + page->pprev_hash = p; /* p--> |hashp|-->|oldNode| */ + if (next) /* next----+ */ + next->pprev_hash = &page->next_hash; + if (page->buffers) + PAGE_BUG(page); /*证明page 不会同时存在于page cache + 和 buffer cache*/ + /*2.6 已经与此不同了*/ + atomic_inc(&page_cache_size); +}> BufferCache >> PageCache >> 应用程序进程空间 + 写文件:PageCache, BufferCache >> 磁盘 + +SwapCache + 交换空间。 +{% endhighlight %} + +### Swap Cache + +其作用不是为了提高磁盘的 IO 效率,而是为了解决页面在 swap in 和 swap out 时的同步问题,也就是在进行 swap out (将页面内容写入磁盘分区时) 进程如果发起了对换出页面的访问,系统如何对其处理。 + +由于存在 swap cache ,如果页面的数据还没有完全写入磁盘,这个 page frame 是在 swap cache 中的;等数据完全写入磁盘后,且没有进程对 page frame 访问时,才会真正释放 page frame,然后将其交给 buddy system 。 + + + +## 常用函数 + +### SetPageReserved() + +随着 Linux 的长时间运行,空闲页面会越来越少,为此内核采用页面回收算法 (PFRA) 从用户进程和内核高速缓存中回收内存页框,并根据需要把要回收页框的内容交换到磁盘上的交换区。 + + + + + + + + + + + + + + + + + + + + + +MySQL 5.7 在进行恢复的时候,一般情况下需要进行最多 3 次的 redo-log 扫描: + +第一次redo log的扫描,主要是查找MLOG_CHECKPOINT,不进行redo log的解析,如果没有找到MLOG_CHECKPOINT,则说明InnoDB不需要进行recovery,后面的两次扫描可以省略,如果找到了MLOG_CHECKPOINT,则获取MLOG_FILE_NAME到指定列表,后续只需打开该链表中的表空间即可。 + +第二次扫描是在第一次找到MLOG_CHECKPOINT基础之上进行的,该次扫描会把redo log解析到哈希表中,如果扫描完整个文件,哈希表还没有被填满,则不需要第三次扫描,直接进行recovery就结束 + +第三次扫描是在第二次基础上进行的,第二次扫描把哈希表填满后,还有redo log剩余,则需要循环进行扫描,哈希表满后立即进行recovery,直到所有的redo log被apply完为止。 + +redo log全部被解析并且apply完成,整个InnoDB recovery的第一阶段也就结束了,在该阶段中,所有已经被记录到redo log但是没有完成数据刷盘的记录都被重新落盘。然而,InnoDB单靠redo log的恢复是不够的,这样还是有可能会丢失数据(或者说造成主从数据不一致),因为在事务提交过程中,写binlog和写redo log提交是两个过程,写binlog在前而redo提交在后,如果MySQL写完binlog后,在redo提交之前发生了宕机,这样就会出现问题:binlog中已经包含了该条记录,而redo没有持久化。binlog已经落盘就意味着slave上可以apply该条数据,redo没有持久化则代表了master上该条数据并没有落盘,也不能通过redo进行恢复。这样就造成了主从数据的不一致,换句话说主上丢失了部分数据,那么MySQL又是如何保证在这样的情况下,数据还是一致的?这就需要进行第二阶段恢复。 + + + + + + +在函数 ```recv_recovery_from_checkpoint_start()``` 执行完成之后,实际上就可以开始处理用户的事务了。 + + + +MLOG_FILE CHECKPOINT 如何写入???如何使用???kkkk + + + + + +mlog 如何记录的那些 ibd 时需要读取的。 + + + + + +InnoDB 在 MySQL 启动的时候,会对 redo-log 进行日志回放,通过 recv_sys_t 结构来进行数据恢复和控制的,它的结构如下: + +{% highlight text %} +struct recv_sys_t { + +mutex_t mutex; /*保护锁*/ +ibool apply_log_recs; /*正在应用log record到page中*/ +ibool apply_batch_on; /*批量应用log record标志*/ + +dulint lsn; +ulint last_log_buf_size; + +byte* last_block; /*恢复时最后的块内存缓冲区*/ +byte* last_block_buf_start; /*最后块内存缓冲区的起始位置,因为last_block是512地址对齐的,需要这个变量记录free的地址位置*/ + +byte* buf; // 从redo-log文件中读取的日志 + +ulint len; /*buf有效的日志数据长度*/ + +dulint parse_start_lsn; /*开始parse的lsn*/ +dulint scanned_lsn; /*已经扫描过的lsn序号*/ + +ulint scanned_checkpoint_no; /*恢复日志的checkpoint 序号*/ +ulint recovered_offset; /*恢复位置的偏移量*/ + +dulint recovered_lsn; /*恢复的lsn位置*/ +dulint limit_lsn; /*日志恢复最大的lsn,暂时在日志重做的过程没有使用*/ + +ibool found_corrupt_log; /*是否开启日志恢复诊断*/ + +log_group_t* archive_group; + +mem_heap_t* heap; /*recv sys的内存分配堆,用来管理恢复过程的内存占用*/ + + hash_table_t* addr_hash; // 以(space_id+page_no)为KEY的Hash表 + +ulint n_addrs; /*addr_hash中包含recv_addr的个数*/ + +}; +{% endhighlight %} + +在这个结构中,比较复杂的是addr_hash这个哈希表,这个哈希表是用sapce_id和page_no作为hash key,里面存储有恢复时对应的记录内容。 + +恢复日志在从日志文件中读出后,进行解析成若干个recv_t并存储在哈希表当中。在一个读取解析周期过后,日志恢复会对hash表中的recv_t中的数据写入到ibuf和page中。这里为什么要使用hash表呢?个人觉得是为了同一个page的数据批量进行恢复的缘故,这样可以page减少随机插入和修改。 以下是和这个过程相关的几个数据结构: + + +{% highlight text %} +/*对应页的数据恢复操作集合*/ +struct recv_addr_t { + ulint state; /*状态,RECV_NOT_PROCESSED、RECV_BEING_PROCESSED、RECV_PROCESSED*/ + ulint space; /*space的ID*/ + ulint page_no; /*页序号*/ + UT_LIST_BASE_NODE_T(recv_t) rec_list; // 该页对应的log records地址 + hash_node_t addr_hash; +}; + +/*当前的记录操作*/ +struct recv_t { + byte type; /*log类型*/ + ulint len; /*当前记录数据长度*/ + recv_data_t* data; /*当前的记录数据list*/ + + lsn_t start_lsn; // mtr起始LSN + lsn_t end_lsn; // mtr结尾LSN + UT_LIST_NODE_T(recv_t) rec_list; // 该页对应的log records +}; + +struct recv_data_t { + recv_data_t* next; // 指向下个结构体,该地址之后为一大块内存,用于存储log record消息体 +}; +{% endhighlight %} + +他们的内存关系结构图如下: \ +2.重做日志推演过程的LSN关系 +除了这个恢复的哈希表以外,recv_sys_t中的各种LSN也是和日志恢复有非常紧密的关系。以下是各种lsn的解释: parse_start_lsn 本次日志重做恢复起始的lsn,如果是从checkpoint处开始恢复,等于checkpoint_lsn。 scanned_lsn 在恢复过程,将恢复日志从log_sys->buf解析块后存入recv_sys->buf的日志lsn. recovered_lsn 已经将数据恢复到page中或者已经将日志操作存储addr_hash当中的日志lsn; 在日志开始恢复时: +parse_start_lsn = scanned_lsn = recovered_lsn = 检查点的lsn。 +在日志完成恢复时: +parse_start_lsn = 检查点的lsn +scanned_lsn = recovered_lsn = log_sys->lsn。 +在日志推演过程中lsn大小关系如下: +\ +3.日志恢复的主要接口和流程 +恢复日志主要的接口函数: recv_recovery_from_checkpoint_start 从重做日志组内的最近的checkpoint开始恢复数据 +recv_recovery_from_checkpoint_finish 结束从重做日志组内的checkpoint的数据恢复操作 +recv_recovery_from_archive_start 从归档日志文件中进行数据恢复 +recv_recovery_from_archive_finish 结束从归档日志中的数据恢复操作 +recv_reset_logs 截取重做日志最后一段作为新的重做日志的起始位置,可能会丢失数据。 +重做日志恢复数据的流程(checkpoint方式) 1.当MySQL启动的时候,先会从数据库文件中读取出上次保存最大的LSN。 +2.然后调用recv_recovery_from_checkpoint_start,并将最大的LSN作为参数传入函数当中。 +3.函数会先最近建立checkpoint的日志组,并读取出对应的checkpoint信息 +4.通过checkpoint lsn和传入的最大LSN进行比较,如果相等,不进行日志恢复数据,如果不相等,进行日志恢复。 +5.在启动恢复之前,先会同步各个日志组的archive归档状态 +6.在开始恢复时,先会从日志文件中读取2M的日志数据到log_sys->buf,然后对这2M的数据进行scan,校验其合法性,而后将去掉block header的日志放入recv_sys->buf当中,这个过程称为scan,会改变scanned lsn. +7.在对2M的日志数据scan后,innodb会对日志进行mtr操作解析,并执行相关的mtr函数。如果mtr合法,会将对应的记录数据按space page_no作为KEY存入recv_sys->addr_hash当中。 +8.当对scan的日志数据进行mtr解析后,innodb对会调用recv_apply_hashed_log_recs对整个recv_sys->addr_hash进行扫描,并按照日志相对应的操作进行对应page的数据恢复。这个过程会改变recovered_lsn。 +9.如果完成第8步后,会再次从日志组文件中读取2M数据,跳到步骤6继续相对应的处理,直到日志文件没有需要恢复的日志数据。 +10.innodb在恢复完成日志文件中的数据后,会调用recv_recovery_from_checkpoint_finish结束日志恢复操作,主要是释放一些开辟的内存。并进行事务和binlog的处理。 +上面过程的示意图如下: + + + + + +GTID的全称为 global transaction identifier  , 可以翻译为全局事务标示符,GTID在原始master上的事务提交时被创建。GTID需要在全局的主-备拓扑结构中保持唯一性,GTID由两部分组成: +GTID = source_id:transaction_id + +source_id用于标示源服务器,用server_uuid来表示,这个值在第一次启动时生成,并写入到配置文件data/auto.cnf中 +transaction_id则是根据在源服务器上第几个提交的事务来确定。 + +一个GTID的生命周期包括: +1.事务在主库上执行并提交 +给事务分配一个gtid(由主库的uuid和该服务器上未使用的最小事务序列号),该GTID被写入到binlog中。 +2.备库读取relaylog中的gtid,并设置session级别的gtid_next的值,以告诉备库下一个事务必须使用这个值 +3.备库检查该gtid是否已经被其使用并记录到他自己的binlog中。slave需要担保之前的事务没有使用这个gtid,也要担保此时已分读取gtid,但未提交的事务也不恩呢过使用这个gtid. +4.由于gtid_next非空,slave不会去生成一个新的gtid,而是使用从主库获得的gtid。这可以保证在一个复制拓扑中的同一个事务gtid不变。 + +由于GTID在全局的唯一性,通过GTID,我们可以在自动切换时对一些复杂的复制拓扑很方便的提升新主库及新备库,例如通过指向特定的GTID来确定新备库复制坐标。 + +当然,使用GTID也有一些限制: +1.事务中的更新包含非事务性存储引擎,这可能导致多个GTID分配给同一个事务。 +2. create table…select语句不被支持,因为该语句会被拆分成create table 和insert两个事务,并且这个两个事务被分配了同一个GTID,这会导致insert被备库忽略掉。 +3.不支持CREATE/DROP临时表操作 + +可以看到,支持GTID的复制对一些语句都有一些限制,MySQL也提供了一个选项disable-gtid-unsafe-statements以禁止这些语句的执行。 + + + + +### ibd页损坏 +Page损坏的情况比较多:二级索引页损坏(可以通过OPTIMIZE TABLE恢复);聚集索引页损坏;表字典损坏; +----- 使用VIM编译 +vim -b titles.ibd +----- 通过外部xxd程序改变,注意修改完之后一定要通过-r选项恢复为二进制格式 +:%!xxd +:%!xxd -r +当执行check table titles;检查表是否正常时,就会由于页面错误退出,报错信息如下。 + +buf_page_io_complete() +2017-03-09T08:58:34.750125Z 4 [ERROR] InnoDB: Database page corruption on disk or a failed file read of page [page id: space=NUM, page number=NUM]. You may have to recover from a backup. +len 16384; hex ... ... +InnoDB: End of page dump +InnoDB: Page may be an index page where index id is 48 +2017-03-09T08:58:34.804632Z 4 [ERROR] [FATAL] InnoDB: Aborting because of a corrupt database page in the system tablespace. Or,  there was a failure in tagging the tablespace  as corrupt. +2017-03-09 16:58:34 0x7fe6e87b7700  InnoDB: Assertion failure in thread 140629719611136 in file ut0ut.cc line 916 + +实际上是可以正常重启的,但是一旦查询到该页时,仍然会报错,接下来看看如何恢复其中还完好的数据。 + +修改my.ini中的innodb_force_recovery参数,默认是0,此时可以修改为1-6,使mysqld在启动时跳过部分恢复步骤,在启动后将数据导出来然后重建数据库;当然,不同的情况可能恢复的数据会有所不同。 + +1. SRV_FORCE_IGNORE_CORRUPT: 忽略检查到的corrupt页;** +2. SRV_FORCE_NO_BACKGROUND): 阻止主线程的运行,在srv_master_thread()中处理;** + +3. SRV_FORCE_NO_TRX_UNDO):不执行事务回滚操作。 +4. SRV_FORCE_NO_IBUF_MERGE):不执行插入缓冲的合并操作。 +5. SRV_FORCE_NO_UNDO_LOG_SCAN):不查看重做日志,InnoDB存储引擎会将未提交的事务视为已提交。 + +6. SRV_FORCE_NO_LOG_REDO): 不执行redo log。 + +另外,需要注意,设置参数值大于0后,可以对表进行select,create,drop操作,但insert,update或者delete这类操作是不允许的。 + +再次执行check table titles;时,会报如下的错误。 ++------------------+-------+----------+---------------------------------------------------+ +| Table            | Op    | Msg_type | Msg_text                                          | ++------------------+-------+----------+---------------------------------------------------+ +| employees.titles | check | Warning  | InnoDB: The B-tree of index PRIMARY is corrupted. | +| employees.titles | check | error    | Corrupt                                           | ++------------------+-------+----------+---------------------------------------------------+ +2 rows in set (2.47 sec) + +SELECT * FROM titles INTO OUTFILE '/tmp/titles.csv' +   FIELDS TERMINATED BY ',' ENCLOSED BY '"' +   LINES TERMINATED BY '\r\n'; +注意上述在使用LOAD DATA INFILE或者SELECT INTO OUTFILE操作时,需要在配置文件中添加secure_file_priv=/tmp配置项,或者配置成secure_file_priv="/"不限制导入和导出路径。 +如何确认哪些数据丢失了???? + +http://www.runoob.com/mysql/mysql-database-export.html + +如下是导致MySQL表毁坏的常见原因: +1、 服务器突然断电或者强制关机导致数据文件损坏; +2、 mysqld进程在修改表时被强制杀掉,例如kill -9; +3、 磁盘故障、服务器宕机等硬件问题无法恢复; +4、 使用myisamchk的同时,mysqld也在操作表; +5、 MySQL、操作系统、文件系统等软件的bug。 + + + + +表损坏的典型症状: +     1 、当在从表中选择数据之时,你得到如下错误:  + +      Incorrect key file for table: '...'. Try to repair it + +     2 、查询不能在表中找到行或返回不完全的数据。 + +     3 、Error: Table 'p' is marked as crashed and should be repaired 。 + +     4 、打开表失败: Can’t open file: ‘×××.MYI’ (errno: 145) 。 + +  ER_NOT_FORM_FILE  Incorrect information in file: +  ER_NOT_KEYFILE    Incorrect key file for table 'TTT'; try to repair it +  ER_OLD_KEYFILE    Old key file for table 'TTT'; repair it! +  ER_CANT_OPEN_FILE Can't open file: 'TTT' (errno: %d - %s) +  + +  3.预防 MySQL 表损坏 + +   可以采用以下手段预防mysql 表损坏:  + +    1 、定期使用myisamchk 检查MyISAM 表(注意要关闭mysqld ),推荐使用check table 来检查表(不用关闭mysqld )。 + +    2 、在做过大量的更新或删除操作后,推荐使用OPTIMIZE TABLE 来优化表,这样既减少了文件碎片,又减少了表损坏的概率。 + +    3 、关闭服务器前,先关闭mysqld (正常关闭服务,不要使用kill -9 来杀进程)。 + +    4 、使用ups 电源,避免出现突然断电的情况。 + +    5 、使用最新的稳定发布版mysql ,减少mysql 本身的bug 导致表损坏。 + +    6 、对于InnoDB 引擎,你可以使用innodb_tablespace_monitor来检查表空间文件内文件空间管理的完整性。 + +    7 、对磁盘做raid ,减少磁盘出错并提高性能。 + +    8 、数据库服务器最好只跑mysqld 和必要的其他服务,不要跑其他业务服务,这样减少死机导致表损坏的可能。 + +    9 、不怕万一,只怕意外,平时做好备份是预防表损坏的有效手段。 + +  4. MySQL 表损坏的修复 + +  MyISAM 表可以采用以下步骤进行修复 : + +    1、  使用 reapair table 或myisamchk 来修复。  + +    2、  如果上面的方法修复无效,采用备份恢复表。 + + + +  具体可以参考如下做法: + +  阶段1 :检查你的表 + +    如果你有很多时间,运行myisamchk *.MYI 或myisamchk -e *.MYI 。使用-s (沉默)选项禁止不必要的信息。  + +    如果mysqld 服务器处于宕机状态,应使用--update-state 选项来告诉myisamchk 将表标记为' 检查过的' 。 + +    你必须只修复那些myisamchk 报告有错误的表。对这样的表,继续到阶段2 。 + +    如果在检查时,你得到奇怪的错误( 例如out of memory 错误) ,或如果myisamchk 崩溃,到阶段3 。 + +  阶段2 :简单安全的修复 + +    注释:如果想更快地进行修复,当运行myisamchk 时,你应将sort_buffer_size 和Key_buffer_size 变量的值设置为可用内存的大约25% 。 + +    首先,试试myisamchk -r -q tbl_name(-r -q 意味着“ 快速恢复模式”) 。这将试图不接触数据文件来修复索引文件。如果数据文件包含它应有的一切内容和指向数据文件内正确地点的删除连接,这应该管用并且表可被修复。开始修复下一张表。否则,执行下列过程: + +    在继续前对数据文件进行备份。 + +    使用myisamchk -r tbl_name(-r 意味着“ 恢复模式”) 。这将从数据文件中删除不正确的记录和已被删除的记录并重建索引文件。 + +    如果前面的步骤失败,使用myisamchk --safe-recover tbl_name 。安全恢复模式使用一个老的恢复方法,处理常规恢复模式不行的少数情况( 但是更慢) 。 + +    如果在修复时,你得到奇怪的错误( 例如out of memory 错误) ,或如果myisamchk 崩溃,到阶段3 。  + +  阶段3 :困难的修复 + +    只有在索引文件的第一个16K 块被破坏,或包含不正确的信息,或如果索引文件丢失,你才应该到这个阶段。在这种情况下,需要创建一个新的索引文件。按如下步骤操做: + +    把数据文件移到安全的地方。 + +    使用表描述文件创建新的( 空) 数据文件和索引文件: + +    shell> mysql db_name + +    mysql> SET AUTOCOMMIT=1; + +    mysql> TRUNCATE TABLE tbl_name; + +    mysql> quit  + +    如果你的MySQL 版本没有TRUNCATE TABLE ,则使用DELETE FROM tbl_name 。 + +    将老的数据文件拷贝到新创建的数据文件之中。(不要只是将老文件移回新文件之中;你要保留一个副本以防某些东西出错。) + +    回到阶段2 。现在myisamchk -r -q 应该工作了。(这不应该是一个无限循环)。 + +    你还可以使用REPAIR TABLE tbl_name USE_FRM ,将自动执行整个程序。 + +  阶段4 :非常困难的修复 + +    只有.frm 描述文件也破坏了,你才应该到达这个阶段。这应该从未发生过,因为在表被创建以后,描述文件就不再改变了。 + +     从一个备份恢复描述文件然后回到阶段3 。你也可以恢复索引文件然后回到阶段2 。对后者,你应该用myisamchk -r 启动。 + +    如果你没有进行备份但是确切地知道表是怎样创建的,在另一个数据库中创建表的一个拷贝。删除新的数据文件,然后从其他数据库将描述文件和索引文件移到破坏的数据库中。这样提供了新的描述和索引文件,但是让.MYD 数据文件独自留下来了。回到阶段2 并且尝试重建索引文件。 +Log Sequence Number, LSN + +Sharp Checkpoint 是一次性将 buffer pool 中的所有脏页都刷新到磁盘的数据文件,同时会保存最后一个提交的事务LSN。 + + +fuzzy checkpoint就更加复杂了,它是在固定的时间点发生,除非他已经将所有的页信息刷新到了磁盘,或者是刚发生过一次sharp checkpoint,fuzzy checkpoint发生的时候会记录两次LSN,也就是检查点发生的时间和检查点结束的时间。但是呢,被刷新的页在并不一定在某一个时间点是一致的,这也就是它为什么叫fuzzy的原因。较早刷入磁盘的数据可能已经修改了,较晚刷新的数据可能会有一个比前面LSN更新更小的一个LSN。fuzzy checkpoint在某种意义上可以理解为fuzzy checkpoint从redo  log的第一个LSN执行到最后一个LSN。恢复以后的话,REDO LOG就会从最后一个检查点开始时候记录的LSN开始。 + +一般情况下大家可能fuzzy checkpoint的发生频率会远高于sharp checkpoint发生的频率,这个事毫无疑问的。不过当数据库关闭,切换redo日志文件的时候是会触发sharp checkpoint,一般情况是fuzzy checkpoint发生的更多一些。 + +一般情况下,执行普通操作的时候将不会发生检查点的操作,但是,fuzzy checkpoint却要根据时间推进而不停的发生。刷新脏页已经成为了数据库的一个普通的日常操作。 + +INNODB维护了一个大的缓冲区,以保证被修改的数据不会被立即写入磁盘。她会将这些修改过的数据先保留在buffer pool当中,这样在这些数据被写入磁盘以前可能会经过多次的修改,我们称之为写结合。这些数据页在buffer pool当中都是按照list来管理的,free list会记录那些空间是可用的,LRU list记录了那些数据页是最近被访问到的。flush list则记录了在LSN顺序当中的所有的dirty page信息,最近最少修改信息。 + +这里着重看一下flush list,我们知道innodb的缓存空间是有限的。如果buffer pool空间使用完毕,再次读取新数据就会发生磁盘读,也就是会发生flush操作,所以说就要释放一部分没有被使用的空间来保证buffer pool的可用性。由于这样的操作是很耗时的,所以说INNODB是会连续按照时间点去执行刷新操作,这样就保证了又足够的clean page来作为交换,而不必发生flush操作。每一次刷新都会将flush list的最老的信息驱逐,这样才能够保证数据库缓冲命中率是很高的一个值。这些老数据的选取是根据他们在磁盘的位置和LSN(最后一次修改的)号来确认数据新旧。 + +MySQL数据的日志都是混合循环使用的,但是如果这些事物记录的页信息还没有被刷新到磁盘当中的话是绝对不会被覆盖写入的。如果还没被刷新入磁盘的数据被覆盖了日志文件,那数据库宕机的话岂不是所有被覆盖写入的事物对应的数据都要丢失了呢。因此,数据修改也是有时间限制的,因为新的事物或者正在执行的事物也是需要日志空间的。日志越大,限制就越小。而且每次fuzzy checkpoint都会将最老最不被访问的数据驱逐出去,这也保证了每次驱逐的都是最老的数据,在下次日志被覆盖写入的时候都是已经被刷盘的数据的日志信息。最后一个老的,不被访问的数据的事物的LSN就是事务日志的 low-water标记,INNODB一直想提高这个LSN的值以保证buffer pool又足够的空间刷入新的数据,同时保证了数据库事务日志文件可以被覆盖写入的时候有足够的空间使用。将事务日志设置的大一些能够降低释放日志空间的紧迫性,从而可以大大的提高性能。 + +当innodb刷新 dirty page落盘的时候,他会找到最老的dirty  page对应的LSN并且将其标记为low-water,然后将这些信息记录到事物日志的头部,因此,每次刷新脏页都是要从flush  list的头部进行刷新的。在推进最老的LSN的标记位置的时候,本质上就是做了一次检查点。 + +当INNODB宕机的时候,他还要做一些额外的操作,第一:停止所有的数据更新等操作,第二:将dirty page in  buffer 的数据刷新落盘,第三:记录最后的LSN,因为我们上面也说到了,这次发生的是sharp checkpoint,并且,这个LSN会写入到没一个数据库文件的头部,以此来标记最后发生检查点的时候的LSN位置。 + +我们知道,刷新脏页数据的频率如果越高的话就代表整个数据库的负载很大,越小当然代表数据库的压力会小一点。将LOG 文件设置的很大能够再检查点发生期间减少磁盘的IO,总大小最好能够设置为和buffer pool大小相同,当然如果日志文件设置太大的话MySQL就会再crash recovery的时候花费更多的时间(5.5之前)。 + + +http://mysqlmusings.blogspot.com/2011/04/crash-safe-replication.html + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://mysql.taobao.org/monthly/2015/06/01/ + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +https://gold.xitu.io/entry/5841225561ff4b00587ec651 + +https://yq.aliyun.com/articles/64677?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&utm_content=m_7935 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=696 + +http://mysqllover.com/?p=213 + +http://mysql.taobao.org/monthly/2016/02/03/ + +http://mysql.taobao.org/monthly/2016/08/07/ + +http://mysql.taobao.org/monthly/2015/12/01/ + +http://mysqllover.com/?p=834 + +http://mysqllover.com/?p=1087 + +http://www.xuchunyang.com/2016/01/13/deak_lock/ + +http://mysqllover.com/?p=1119 + +http://www.askmaclean.com/archives/mysql-recover-innodb.html + +http://www.askmaclean.com/archives/mysql%e4%b8%ad%e6%81%a2%e5%a4%8d%e4%bf%ae%e5%a4%8dinnodb%e6%95%b0%e6%8d%ae%e5%ad%97%e5%85%b8.html + +http://www.thinkphp.cn/code/430.html + +http://www.cnblogs.com/liuhao/p/3714012.html + +http://louisyang.blog.51cto.com/8381303/1360394 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://hamilton.duapp.com/detail?articleId=34 + +https://twindb.com/undrop-tool-for-innodb/ + +https://twindb.com/tag/stream_parser/ + +http://jishu.y5y.com.cn/aeolus_pu/article/details/60143284 + +http://hedengcheng.com/?p=148 + +read_view + +http://www.cnblogs.com/chenpingzhao/p/5065316.html + +http://kabike.iteye.com/blog/1820553 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://mysqllover.com/?p=696 + +http://imysql.com/2014/08/13/mysql-faq-howto-shutdown-mysqld-fulgraceful.shtml + +http://11879724.blog.51cto.com/11869724/1872928 + +http://coolnull.com/3145.html + +http://mysqllover.com/?p=594 + +http://mysqllover.com/?p=87 + +http://mysqllover.com/?p=581 + +http://keithlan.github.io/2016/06/23/gtid/ + +http://mysqlmusings.blogspot.com/2011/04/crash-safe-replication.html + +http://www.orczhou.com/index.php/2010/12/more-about-mysql-innodb-shutdown/ + +https://www.slideshare.net/frogd/inno-db-15344119 + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://github.com/zhaiwx1987/innodb_ebook/blob/master/innodb_adaptive_hash.md + + + + +隔离级别 + +详细可以查看row_search_for_mysql()中的实现,实际上也就是row_search_mvcc()函数的实现。 + +row_search_for_mysql()    + |-row_search_no_mvcc()       # 对于MySQL内部使用的表(用户不可见),不需要MVCC机制 + |-row_search_mvcc() + +row_search_no_mvcc()用于MySQL的内部表使用,通常是一些作为一个较大任务的中间结果存储,所以希望其可以尽快处理,因此不需要MVCC机制。 + +事务的隔离级别在trx->isolation_level中定义,其取值也就是如下的宏定义。 + +#define TRX_ISO_READ_UNCOMMITTED        0 +#define TRX_ISO_READ_COMMITTED          1 +#define TRX_ISO_REPEATABLE_READ         2 +#define TRX_ISO_SERIALIZABLE            3 + + +在不同的隔离级别下,可见性的判断有很大的不同。 + +READ-UNCOMMITTED +在该隔离级别下会读到未提交事务所产生的数据更改,这意味着可以读到脏数据,实际上你可以从函数row_search_mvcc中发现,当从btree读到一条记录后,如果隔离级别设置成READ-UNCOMMITTED,根本不会去检查可见性或是查看老版本。这意味着,即使在同一条SQL中,也可能读到不一致的数据。 + +    READ-COMMITTED +    在该隔离级别下,可以在SQL级别做到一致性读,当事务中的SQL执行完成时,ReadView被立刻释放了,在执行下一条SQL时再重建ReadView。这意味着如果两次查询之间有别的事务提交了,是可以读到不一致的数据的。 + +    REPEATABLE-READ +    可重复读和READ-COMMITTED的不同之处在于,当第一次创建ReadView后(例如事务内执行的第一条SEELCT语句),这个视图就会一直维持到事务结束。也就是说,在事务执行期间的可见性判断不会发生变化,从而实现了事务内的可重复读。 + +    SERIALIZABLE +    序列化的隔离是最高等级的隔离级别,当一个事务在对某个表做记录变更操作时,另外一个查询操作就会被该操作堵塞住。同样的,如果某个只读事务开启并查询了某些记录,那么另外一个session对这些记录的更改操作是被堵塞的。内部的实现其实很简单: +        对InnoDB表级别加LOCK_IS锁,防止表结构变更操作 +        对查询得到的记录加LOCK_S共享锁,这意味着在该隔离级别下,读操作不会互相阻塞。而数据变更操作通常会对记录加LOCK_X锁,和LOCK_S锁相冲突,InnoDB通过给查询加记录锁的方式来保证了序列化的隔离级别。 + +注意不同的隔离级别下,数据具有不同的隔离性,甚至事务锁的加锁策略也不尽相同,你需要根据自己实际的业务情况来进行选择。 + + + + + +SELECT count_star, sum_timer_wait, avg_timer_wait, event_name FROM events_waits_summary_global_by_event_name WHERE count_star > 0 AND event_name LIKE "wait/synch/%" ORDER BY sum_timer_wait DESC LIMIT 20; + + + + +最新的事务ID通过trx_sys_get_new_trx_id()函数获取,每次超过了TRX_SYS_TRX_ID_WRITE_MARGIN次数后,都会调用trx_sys_flush_max_trx_id()函数刷新磁盘。 + + + + + + +innodb_force_recovery变量对应源码中的srv_force_recovery变量, +  + + +当innodb_fast_shutdown设置为0时,会导致purge一直工作近两个小时。????? + +从5.5版本开始,purge任务从主线程中独立出来;5.6开始支持多个purge线程,可以通过innodb_purge_threads变量控制。 + +purge后台线程的最大数量可以有32个,包括了一个coordinator线程,以及多个worker线程。 + +在innobase_start_or_create_for_mysql()函数中,会创建srv_purge_coordinator_thread以及srv_worker_thread线程。 + + +srv_purge_coordinator_thread() + |-srv_purge_coordinator_suspend()   如果不需要purge或者上次purge记录数为0,则暂停 + |-srv_purge_should_exit()           判断是否需要退出;fast_shutdown=0则等待所有purge操作完成 + |-srv_do_purge()                    协调线程的主要工作,真正调用执行purge操作的函数 + | + |-trx_purge()                       防止上次循环结束后又新的记录写入,此处不再使用worker线程 + | + |-trx_purge()                       最后对history-list做一次清理,确保所有worker退出 + +srv_worker_thread() + + +最后一次做trx_purge()时,为了防止执行时间过程,批量操作时不再采用innodb_purge_batch_size(300)指定的值,而是采用20。 + + +InnoDB的数据组织方式采用聚簇索引,也就是索引组织表,而二级索引采用(索引键值,主键键值)组合来唯一确定一条记录。 +无论是聚簇索引,还是二级索引,每条记录都包含了一个DELETED-BIT位,用于标识该记录是否是删除记录;除此之外,聚簇索引还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR,分别表示产生当前记录项的事务ID以及指向当前记录的undo信息。 + + + +从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断???? + + +InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下: + +class ReadView { +private: +  trx_id_t        m_low_limit_id;  // +}; + + +ReadView::prepare() + + +copy_trx_ids +mtr_commit(struct mtr_t*)                 提交一个mini-transaction,调用mtr_t::commit() + |-mtr_t::Command::execute()              写redo-log,将脏页添加到flush-list,并释放占用资源 +   |-mtr_t::Command::prepare_write()      准备写入日志 +   | |-fil_names_write_if_was_clean() +   |-mtr_t::Command::finish_write()   + + +测试场景#1 Drop Database (innodb_file_per_table=ON) +OFF代表MySQL是共享表空间,也就是所有库的数据都存放在一个ibdate1文件中;ON代表每个表的存储空间都是独立的。 + +ibd是MySQL数据文件、索引文件,二进制文件无法直接读取;frm是表结构文件,可以直接打开。如果innodb_file_per_table 无论是ON还是OFF,都会有这2个文件,区别只是innodb_file_per_table为ON的时候,数据时放在 .idb中,如果为OFF则放在ibdata1中。 + + + + + + +  + + + + + + + + +#######redo-log文件 +redo-log保存在innodb_log_group_home_dir参数指定的目录下,文件名为ib_logfile*;undo保存在共享表空间ibdata*文件中。 + +InnoDB的redo log可控制文件大小以及文件个数,分别通过innodb_log_file_size和innodb_log_files_in_group控制,总大小为两者之积。日志顺序写入,而且文件循环使用。 + +简单来说,InnoDB中的两个核心参数innodb_buffer_pool_size、innodb_log_file_size,分别定义了数据缓存和redo-log的大小,而后者的大小也决定了可以允许buffer中可以有多少脏页。当然,也不能因此就增大redo-log文件的大小,如果这样,可能会导致系统启动时Crash Recovery时间增大。 + + + + +LSN对应了日志文件的偏移量,为了减小故障恢复时间,引入了Checkpoint机制, + +InnoDB在启动时会自动检测InnoDB数据和事务日志是否一致,是否需要执行相应的操作???保证数据一致性;当然,故障恢复时间与事务日志的大小相关。 + + +checkpoint会将最近写入的LSN + + + + +主线程主要完成 purge、checkpoint、dirty pages flush 等操作。 + + + +Database was not shutdown normally! # InnoDB开始Crash Recovery{recv_init_crash_recovery_spaces()} +Starting crash recovery. + + + +1. 读取Checkpoint LSN +2. 从Checkpoint LSN开始向前遍历Redo Log File + 重做从Checkpoint LSN开始的所有Redo日志 +3. 重新构造系统崩溃时的事务 + Commit事务,等待Purge线程回收 + Prepare事务,由MySQL Server控制提交或者回滚(与Binlog 2PC相关) + Active事务,回滚 +4. 新建各种后台线程,Crash Recovery完成返回 + + +正常关闭时,会在flush redo log和脏页后,做一次完全同步的checkpoint,并将checkpoint的LSN写到第一个ibdata文件的第一个page中,详细可以参考fil_write_flushed_lsn()。 + + + + +innobase_start_or_create_for_mysql() + |-log_group_init() + |-log_calc_max_ages() + + + log_sys->log_group_capacity = smallest_capacity; + + log_sys->max_modified_age_async = margin + - margin / LOG_POOL_PREFLUSH_RATIO_ASYNC; + log_sys->max_modified_age_sync = margin + - margin / LOG_POOL_PREFLUSH_RATIO_SYNC; + + log_sys->max_checkpoint_age_async = margin - margin + / LOG_POOL_CHECKPOINT_RATIO_ASYNC; + log_sys->max_checkpoint_age = margin; + + + + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://tech.uc.cn/?p=716 + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=620 + +http://apprize.info/php/effective/6.html + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +数据库内核分享 + +https://www.slideshare.net/frogd/inno-db-15344119 + +检查保存到磁盘的最大checkpoint LSN与redo-log的LSN是否一致; + + +MySQL · 引擎特性 · InnoDB 崩溃恢复过程 + +http://mysql.taobao.org/monthly/2015/06/01/ + + + + + + + + + + + + + +https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in + + + +http://mysql.taobao.org/monthly/2015/12/01/ +http://hedengcheng.com/?p=148 +read_view +http://www.cnblogs.com/chenpingzhao/p/5065316.html +http://kabike.iteye.com/blog/1820553 +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ +http://mysqllover.com/?p=696 + +隔离级别 +详细可以查看row_search_mvcc()中的实现 + + +row_search_for_mysql() + |-row_search_no_mvcc() # 对于MySQL内部使用的表(用户不可见),不需要MVCC机制 + |-row_search_mvcc() + +row_search_no_mvcc()用于MySQL的内部表使用,通常是一些作为一个较大任务的中间结果存储,所以希望其可以尽快处理,因此不需要MVCC机制。 + + +innodb_force_recovery变量对应源码中的srv_force_recovery变量, + + + + +当innodb_fast_shutdown设置为0时,会导致purge一直工作近两个小时。????? + +从5.5版本开始,purge任务从主线程中独立出来;5.6开始支持多个purge线程,可以通过innodb_purge_threads变量控制。 + +purge后台线程的最大数量可以有32个,包括了一个coordinator线程,以及多个worker线程。 + +在innobase_start_or_create_for_mysql()函数中,会创建srv_purge_coordinator_thread以及srv_worker_thread线程。 + + +srv_purge_coordinator_thread() + |-srv_purge_coordinator_suspend() 如果不需要purge或者上次purge记录数为0,则暂停 + |-srv_purge_should_exit() 判断是否需要退出;fast_shutdown=0则等待所有purge操作完成 + |-srv_do_purge() 协调线程的主要工作,真正调用执行purge操作的函数 + | + |-trx_purge() 防止上次循环结束后又新的记录写入,此处不再使用worker线程 + | + |-trx_purge() 最后对history-list做一次清理,确保所有worker退出 + +srv_worker_thread() + + +最后一次做trx_purge()时,为了防止执行时间过程,批量操作时不再采用innodb_purge_batch_size(300)指定的值,而是采用20。 + + +InnoDB的数据组织方式采用聚簇索引,也就是索引组织表,而二级索引采用(索引键值,主键键值)组合来唯一确定一条记录。 +无论是聚簇索引,还是二级索引,每条记录都包含了一个DELETED-BIT位,用于标识该记录是否是删除记录;除此之外,聚簇索引还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR,分别表示产生当前记录项的事务ID以及指向当前记录的undo信息。 + + + +从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断???? + + +InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下: + +class ReadView { +private: + trx_id_t m_low_limit_id; // +}; + + +mtr_commit(struct mtr_t*) 提交一个mini-transaction,调用mtr_t::commit() + |-mtr_t::Command::execute() 写redo-log,将脏页添加到flush-list,并释放占用资源 + |-mtr_t::Command::prepare_write() 准备写入日志 + | |-fil_names_write_if_was_clean() + |-mtr_t::Command::finish_write() + + + + + + + + + + + + +妈的文件整理文件 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://www.cnblogs.com/liuhao/p/3714012.html + + +持续集成 https://www.zhihu.com/question/23444990 + + + + +buf_flush_batch + + + + + + + + + + + + + +FAQ系列 | 如何避免ibdata1文件大小暴涨 + +0、导读 + + 遇到InnoDB的共享表空间文件ibdata1文件大小暴增时,应该如何处理? + +1、问题背景 + +用MySQL/InnoDB的童鞋可能也会有过烦恼,不知道为什么原因,ibdata1文件莫名其妙的增大,不知道该如何让它缩回去,就跟30岁之后男人的肚腩一样,汗啊,可喜可贺的是我的肚腩还没长出来,hoho~ + +正式开始之前,我们要先知道ibdata1文件是干什么用的。 + +ibdata1文件是InnoDB存储引擎的共享表空间文件,该文件中主要存储着下面这些数据: + + data dictionary + double write buffer + insert buffer/change buffer + rollback segments + undo space + Foreign key constraint system tables + +另外,当选项 innodb_file_per_table = 0 时,在ibdata1文件中还需要存储 InnoDB 表数据&索引。ibdata1文件从5.6.7版本开始,默认大小是12MB,而在这之前默认大小是10MB,其相关选项是 innodb_data_file_path,比如我一般是这么设置的: + + innodb_data_file_path = ibdata1:1G:autoextend + +当然了,无论是否启用了 innodb_file_per_table = 1,ibdata1文件都必须存在,因为它必须存储上述 InnoDB 引擎所依赖&必须的数据,尤其是上面加粗标识的 rollback segments 和 undo space,它俩是引起 ibdata1 文件大小增加的最大原因,我们下面会详细说。 +2、原因分析 + +我们知道,InnoDB是支持MVCC的,它和ORACLE类似,采用 undo log、redo log来实现MVCC特性的。在事务中对一行数据进行修改时,InnoDB 会把这行数据的旧版本数据存储一份在undo log中,如果这时候有另一个事务又要修改这行数据,就又会把该事物最新可见的数据版本存储一份在undo log中,以此类推,如果该数据当前有N个事务要对其进行修改,就需要存储N份历史版本(和ORACLE略有不同的是,InnoDB的undo log不完全是物理block,主要是逻辑日志,这个可以查看 InnoDB 源码或其他相关资料)。这些 undo log 需要等待该事务结束后,并再次根据事务隔离级别所决定的对其他事务而言的可见性进行判断,确认是否可以将这些 undo log 删除掉,这个工作称为 purge(purge 工作不仅仅是删除过期不用的 undo log,还有其他,以后有机会再说)。 + +那么问题来了,如果当前有个事务中需要读取到大量数据的历史版本,而该事务因为某些原因无法今早提交或回滚,而该事务发起之后又有大量事务需要对这些数据进行修改,这些新事务产生的 undo log 就一直无法被删除掉,形成了堆积,这就是导致 ibdata1 文件大小增大最主要的原因之一。这种情况最经典的场景就是大量数据备份,因此我们建议把备份工作放在专用的 slave server 上,不要放在 master server 上。 + +另一种情况是,InnoDB的 purge 工作因为本次 file i/o 性能是在太差或其他的原因,一直无法及时把可以删除的 undo log 进行purge 从而形成堆积,这是导致 ibdata1 文件大小增大另一个最主要的原因。这种场景发生在服务器硬件配置比较弱,没有及时跟上业务发展而升级的情况。 + +比较少见的一种是在早期运行在32位系统的MySQL版本中存在bug,当发现待 purge 的 undo log 总量超过某个值时,purge 线程直接放弃抵抗,再也不进行 purge 了,这个问题在我们早期使用32位MySQL 5.0版本时遇到的比较多,我们曾经遇到这个文件涨到100多G的情况。后来我们费了很大功夫把这些实例都迁移到64位系统下,终于解决了这个问题。 + +最后一个是,选项 innodb_data_file_path 值一开始就没调整或者设置很小,这就必不可免导致 ibdata1 文件增大了。Percona官方提供的 my.cnf 参考文件中也一直没把这个值加大,让我百思不得其解,难道是为了像那个经常被我吐槽的xx那样,故意留个暗门,好方便后续帮客户进行优化吗?(我心理太阴暗了,不好不好~~) + +稍微总结下,导致ibdata1文件大小暴涨的原因有下面几个: + + 有大量并发事务,产生大量的undo log; + 有旧事务长时间未提交,产生大量旧undo log; + file i/o性能差,purge进度慢; + 初始化设置太小不够用; + 32-bit系统下有bug。 + +稍微题外话补充下,另一个热门数据库 PostgreSQL 的做法是把各个历史版本的数据 和 原数据表空间 存储在一起,所以不存在本案例的问题,也因此 PostgreSQL 的事务回滚会非常快,并且还需要定期做 vaccum 工作(具体可参见PostgreSQL的MVCC实现机制,我可能说的不是完全正确哈) +3、解决方法建议 + +看到上面的这些问题原因描述,有些同学可能觉得这个好办啊,对 ibdata1 文件大小进行收缩,回收表空间不就结了吗。悲剧的是,截止目前,InnoDB 还没有办法对 ibdata1 文件表空间进行回收/收缩,一旦 ibdata1 文件的肚子被搞大了,只能把数据先备份后恢复再次重新初始化实例才能恢复原先的大小,或者把依次把各个独立表空间文件备份恢复到一个新实例中,除此外,没什么更好的办法了。 + +当然了,这个问题也并不是不能防范,根据上面提到的原因,相应的建议对策是: + + 升级到5.6及以上(64-bit),采用独立undo表空间,5.6版本开始就支持独立的undo表空间了,再也不用担心会把 ibdata1 文件搞大; + 初始化设置时,把 ibdata1 文件至少设置为1GB以上; + 增加purge线程数,比如设置 innodb_purge_threads = 8; + 提高file i/o能力,该上SSD的赶紧上; + 事务及时提交,不要积压; + 默认打开autocommit = 1,避免忘了某个事务长时间未提交; + 检查开发框架,确认是否设置了 autocommit=0,记得在事务结束后都有显式提交或回滚。 + + + +关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :) + +最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠 + + + + +MySQL-5.7.7引入的一个系统库sys-schema,包含了一系列视图、函数和存储过程,主要是一些帮助MySQL用户分析问题和定位问题,可以方便查看哪些语句使用了临时表,哪个用户请求了最多的io,哪个线程占用了最多的内存,哪些索引是无用索引等。 + +其数据均来自performance schema和information schema中的统计信息。 + +MySQL 5.7.7 and higher includes the sys schema, a set of objects that helps DBAs and developers interpret data collected by the Performance Schema. sys schema objects can be used for typical tuning and diagnosis use cases. + +MySQL Server blog中有一个很好的比喻: + +For Linux users I like to compare performance_schema to /proc, and SYS to vmstat. + +也就是说,performance schema和information schema中提供了信息源,但是,没有很好的将这些信息组织成有用的信息,从而没有很好的发挥它们的作用。而sys schema使用performance schema和information schema中的信息,通过视图的方式给出解决实际问题的答案。 + +查看是否安装成功 +select * from sys.version; +查看类型 +select * from sys.schema_object_overview where db='sys'; +当然,也可以通过如下命令查看 +show full tables from sys +show function status where db = 'sys'; +show procedure status where db = 'sys' + +user/host资源占用情况 +SHOW TABLES FROM `sys` WHERE + `Tables_in_sys` LIKE 'user\_%' OR + `Tables_in_sys` LIKE 'host\_%' +IO资源使用,包括最近IO使用情况latest_file_io +SHOW TABLES LIKE 'io\_%' +schema相关,包括表、索引使用统计 +SHOW TABLES LIKE 'schema\_%' +等待事件统计 +SHOW TABLES LIKE 'wait%' +语句查看,包括出错、全表扫描、创建临时表、排序、空闲超过95% +SHOW TABLES LIKE 'statement%' +当前正在执行链接,也就是processlist +其它还有一些厂家的帮助函数,PS设置。 +https://www.slideshare.net/Leithal/the-mysql-sys-schema +http://mingxinglai.com/cn/2016/03/sys-schema/ +http://www.itpub.net/thread-2083877-1-1.html + +x$NAME保存的是原始数据,比较适合通过工具调用;而NAME表更适合阅读,比如使用命令行去查看。 + + +select digest,digest_text from performance_schema.events_statements_summary_by_digest\G +CALL ps_trace_statement_digest('891ec6860f98ba46d89dd20b0c03652c', 10, 0.1, TRUE, TRUE); +CALL ps_trace_thread(25, CONCAT('/tmp/stack-', REPLACE(NOW(), ' ', '-'), '.dot'), NULL, NULL, TRUE, TRUE, TRUE); + +优化器调优 +https://dev.mysql.com/doc/internals/en/optimizer-tracing.html + + +MySQL performance schema instrumentation interface(PSI) + +struct PFS_instr_class {}; 基类 + + +通过class page_id_t区分页, + +class page_id_t { +private: + ib_uint32_t m_space; 指定tablespace + ib_uint32_t m_page_no; 页的编号 + + + + + +buf_page_get_gen() 获取数据库中的页 + |-buf_pool_get() 所在buffer pool实例 + |-buf_page_hash_lock_get() + |-buf_page_hash_get_low() 尝试从bp中获取页 + |-buf_read_page() + |-buf_read_page_low() + |-buf_page_init_for_read() 初始化bp + |-buf_LRU_get_free_block() 如果没有压缩,则直接获取空闲页 + |-buf_LRU_add_block() + | + |-buf_buddy_alloc() 压缩页,使用buddy系统 + |-fil_io() + |-buf_block_get_state() 根据页的类型,判断是否需要进一步处理,如ZIP + |-buf_read_ahead_random() + +buf_read_ahead_linear() + +http://www.myexception.cn/database/511937.html +http://blog.csdn.net/taozhi20084525/article/details/17613785 +http://blogread.cn/it/article/5367 +http://mysqllover.com/?p=303 +http://www.cnblogs.com/chenpingzhao/p/5107480.html ??? +https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/innodb-recovery-tablespace-discovery.html +http://mysqllover.com/?p=1214 + + + +[mysqld] +innodb_data_file_path = ibdata1:12M;ibdata2:12M:autoextend + + + +


文件 IO 操作

+在 InnoDB 中所有需要持久化的信息都需要文件操作,例如:表文件、重做日志文件、事务日志文件、备份归档文件等。InnoDB 对文件 IO 操作可以是煞费苦心,主要包括两方面:A) 对异步 IO 的实现;B) 对文件操作管理和 IO 调度的实现。

+ +其主要实现代码集中在 os_file.* + fil0fil.* 文件中,其中 os_file.* 是实现基本的文件操作、异步 IO 和模拟异步 IO;fil0fil.* 是对文件 IO 做系统的管理和 space 结构化。

+ +Innodb 的异步 IO 默认使用 libaio。 +

+ + +其中数据刷盘的主要代码在 innodb/buf/buf0flu.c 中。 +
+buf_flush_batch()
+ |-buf_do_LRU_batch()                         根据传入的type决定调用函数
+ |-buf_do_flush_list_batch()
+   |-buf_flush_page_and_try_neighbors()
+     |-buf_flush_try_neighbors()
+       |-buf_flush_page()                     刷写单个page
+          |-buf_flush_write_block_low()       实际刷写单个page
+
+    buf_flush_write_block_low调用buf_flush_post_to_doublewrite_buf (将page放到double write buffer中,并准备刷写)
+
+    buf_flush_post_to_doublewrite_buf 调用 fil_io ( 文件IO的封装)
+
+    fil_io 调用 os_aio (aio相关操作)
+
+    os_aio 调用 os_file_write (实际写文件操作)
+
+
+ + +其中buf_flush_batch 只有两种刷写方式: BUF_FLUSH_LIST 和 BUF_FLUSH_LRU 两种方式的方式和触发时机简介如下: + +BUF_FLUSH_LIST: innodb master线程中 1_second / 10 second 循环中都会调用。触发条件较多(下文会分析) + +BUF_FLUSH_LRU: 当Buffer Pool无空闲page且old list中没有足够的clean page时,调用。刷写脏页后可以空出一定的free page,供BP使用。 + +从触发频率可以看到 10 second 循环中对于 buf_flush_batch( BUF_FLUSH_LIST ) 的调用是10秒一次IO高负载的元凶所在。 + +我们再来看10秒循环中flush的逻辑: + + 通过比较过去10秒的IO次数和常量的大小,以及pending的IO次数,来判断IO是否空闲,如果空闲则buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) ); + + 如果脏页比例超过70,则 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) ); + + 否则 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(10) ); + +可以看到由于SSD对于随机写的请求响应速度非常快,导致IO几乎没有堆积。也就让innodb误认为IO空闲,并决定全力刷写。 + +其中PCT_IO(N) = innodb_io_capacity *N% ,单位是页。因此也就意味着每10秒,innodb都至少刷10000个page或者刷完当前所有脏页。 + +updated on 2013/10/31: 在5.6中官方的adaptive flush算法有所改变,但是空闲状态下innodb_io_capacity对于刷写page数量的影响仍然不改变。 +UNIQUE 索引 IO 与聚簇索引 IO 完全一致,因为二者都必须读取页面,不能进行 Insert Buffer 优化。 +
+buf_page_get_gen()
+ |-buf_page_hash_lock_get()                 # 判断所需的页是否在缓存中
+ |-buf_read_page()                          # 如果不存在则直接从文件读取的buff_pool中
+   |-buf_read_page_low()                    # 实际底层执行函数
+     |-fil_io()
+        |-os_aio()                          # 实际是一个宏定义,最终调用如下函数
+        | |-os_aio_func()                   # 其入参包括了mode,标识同步/异步
+        |   |-os_file_read_func()           # 同步读
+        |   | |-os_file_pread()
+        |   |   |-pread()
+        |   |
+        |   |-os_file_write_func()          # 同步写
+        |   | |-os_file_pwrite()
+        |   |   |-pwrite()
+        |   |
+        |   |-... ...                       # 对于异步操作,不同的mode其写入array会各不相同 #A
+        |   |-os_aio_array_reserve_slot()   # 从相应队列中选取一个空闲slot,保存需要读写的信息
+        |   | |
+        |   | |-local_seg=... ...           # 1. 首先在任务队列中选择一个segment #B
+        |   | |
+        |   | |-os_mutex_enter()            # 2. 对队列加锁,遍历该segement,选择空闲的slot,如果没有则等待
+        |   | |
+        |   | |                             # 3. 如果array已经满了,根据是否使用AIO决定具体策略
+        |   | |-os_aio_simulated_wake_handler_threads()    # 非native AIO,模拟唤醒
+        |   | |-os_wait_event(array->not_full)             # native aio 则等待not_full信号
+        |   | |
+        |   | |-os_aio_array_get_nth_slot() # 4. 已经确定是有slot了,选择空闲的slot
+        |   | |
+        |   | |-slot... ...                 # 5. 将文件读写请求信息保存在slot,如目标文件、偏移量、数据等
+        |   | |
+        |   | |                             # 6. 对于Win AIO、Native AIO采取不同策略
+        |   | |-ResetEvent(slot->handle)        # 对于Win调用该接口
+        |   | |-io_prep_pread()                 # 而Linux AIO则根据传入的type,决定执行读或写
+        |   | |-io_prep_pwrite()
+        |   |
+        |   |                               # 执行IO操作
+        |   |-WriteFile()                       # 对于Win调用该函数
+        |   |-os_aio_linux_dispatch()           # 对于LINUX_NATIVE_AIO需要执行该函数,将IO请求分发给内核层
+        |   | |-io_submit()                 # 调用AIO接口函数发送
+        |   |
+        |   |-os_aio_windows_handle()       # Win下如果AIO_SYNC调用则通过该函数等待AIO结束
+        |     |-... ...                     # 根据传入的array判断是否为sync_array
+        |     |-WaitForSingleObject()           # 是则等待指定的slot aio操作完成
+        |     |-WaitForMultipleObjects()        # 否则等待array中所有的aio操作完成
+        |     |-GetOverlappedResult()       # 获取AIO的操作结果
+        |     |-os_aio_array_free_slot()    # 最后释放当前slot
+        |
+ |      |-fil_node_complete_io()            # 如果是同步IO,则会等待完成,也就是确保调用os_aio()已经完成了IO操作
+ |-buf_read_ahead_random()                  # 同时做预读
+
+fil_aio_wait()
+ |-os_aio_linux_handle()
+
+os_aio_linux_handle
+
+    分析完os_aio_windows_handle函数,接着分析Linux下同样功能的函数:os_aio_linux_handle
+        无限循环,遍历array,直到定位到一个完成的I/O操作(slot->io_already_done)为止
+        若当前没有完成的I/O,同时有I/O请求,则进入os_aio_linux_collect函数
+            os_aio_linux_collect:从kernel中收集更多的I/O请求
+                调用io_getevents函数,进入忙等,等待超时设置为OS_AIO_REAP_TIMEOUT
+
+            /** timeout for each io_getevents() call = 500ms. */
+
+            #define OS_AIO_REAP_TIMEOUT    (500000000UL)
+                若io_getevents函数返回ret > 0,说明有完成的I/O,进行一些设置,最主要是将slot->io_already_done设置为TRUE
+
+                slot->io_already_done = TRUE;
+                若系统I/O处于空闲状态,那么io_thread线程的主要时间,都在io_getevents函数中消耗。
+
+
+log_buffer_flush_to_disk()
+ |-log_write_up_to()
+
+ + + +
  1. + +在这步中会选择不同的 array,包括了 os_aio_sync_array、os_aio_read_array、os_aio_write_array、os_aio_ibuf_array、os_aio_log_array。每个 aio array 在系统启动时调用 os0file.c::os_aio_init() 初始化。 +
    +innobase_start_or_create_for_mysql() {
    +    ... ...
    +    os_aio_init(io_limit,            // 每个线程可并发处理pending IO的数量
    +        srv_n_read_io_threads,       // 处理异步read IO线程的数量
    +        srv_n_write_io_threads,      // 处理异步write IO线程的数量
    +        SRV_MAX_N_PENDING_SYNC_IOS); // 同步IO array的slots个数,
    +    ... ...
    +}
    +
    +io_limit:
    +   windows = SRV_N_PENDING_IOS_PER_THREAD = 32
    +     linux = 8 * SRV_N_PENDING_IOS_PER_THREAD = 8 * 32 = 256
    +
    +srv_n_read_io_threads:
    +    通过innobase_read_io_threads/innodb_read_io_threads参数控制
    +    因此可并发处理的异步read page请求为:io_limit * innodb_read_io_threads
    +
    +srv_n_write_io_threads:
    +    通过innobase_write_io_threads/innodb_write_io_threads参数控制
    +    因此可并发处理的异步write请求为:io_limit * innodb_write_io_threads
    +    注意,当超过此限制时,必须将已有的异步IO部分写回磁盘,才能处理新的请求
    +
    +SRV_MAX_N_PENDING_SYNC_IOS:
    +    同步IO不需要处理线程log thread、ibuf thread个数均为1
    +
    +接下来是创建 array 。 +
    +os_aio_init()
    + |-os_aio_array_create()
    +
    +异步 IO 主要包括两大类:A) 预读page,需要通过异步 IO 方式进行;B) 主动merge,Innodb 主线程对需要 merge 的 page 发出异步读操作,在read_thread 中进行实际 merge 处理。

  2. + + + +选择 segment 时,是根据偏移量来计算 segment 的,从而可以尽可能的将相邻的读写请求放到一起,从而有利于 IO 层的合并操作。 +
+ +

+ + + + + + +## 参考 + +XtraDB: The Top 10 enhancements +https://www.percona.com/blog/2009/08/13/xtradb-the-top-10-enhancements/ + +https://forums.cpanel.net/threads/innodb-corruption-repair-guide.418722/ + +http://www.itpub.net/thread-2083877-1-1.html + + + + + + + + + + + + + + + + + + + + +innodb_adaptive_flushing +innodb_adaptive_flushing_lwm 百分比,配置自适应flush机制的低水位(low water mark),超过该限制之后,即使没有通过上述参数开启AF,仍然执行AF +innodb_io_capacity +innodb_io_capacity_max redo 刷盘的最大值,如果刷盘落后很多,那么IO可能会超过innodb_io_capacity而小于max +innodb_max_dirty_pages_pct 刷脏时,需要保证没有超过该值;注意,该值是一个目标,并不会影响刷脏的速率。 +innodb_max_dirty_pages_pct_lwm 脏页的低水位,用于决定什么时候开启pre-flush操作,从而保证不会超过上面配置的百分比 +innodb_flushing_avg_loops 决定了利用上述的值循环多少次之后重新计算dirty page和LSN,次数越少对外部的动态变化就越敏感 + + +要刷新多少page和lsn主要代码在af_get_pct_for_dirty()和af_get_pct_for_lsn()中,其中主要控制adaptive flush的代码位于后者函数中。 + +http://www.cnblogs.com/Amaranthus/p/4450840.html + +1.先判断redo log的容量是否到了innodb_adaptive_flushing_lwm低水位阀值。 +2.是否配置了adaptive flush或者age超过了异步刷新的阀值。 +3.lsn_age_factor=age占异步刷新阀值的比例。 +4.要被刷新的比率=innodb_io_capacity_max/innodb_io_capacity*lsn_age_factor* sqrt(innodb_io_capacity)/7.5 + + +定义BP中的页。 +class buf_page_t { +public: + buf_page_state state; + + UT_LIST_NODE_T(buf_page_t) list; 根据state的不同值决定了list的类型 + + UT_LIST_NODE_T(buf_page_t) LRU; + + - BUF_BLOCK_NOT_USED: free, withdraw + - BUF_BLOCK_FILE_PAGE: flush_list + - BUF_BLOCK_ZIP_DIRTY: flush_list + - BUF_BLOCK_ZIP_PAGE: zip_clean + +struct buf_pool_t{ + + +https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb *** + +page_cleaner线程负责刷脏,基本上是基于如下的两个因素: +1. 最近最少(the least recently used pages )使用的页将会从LRU_list上移除; +2. the oldest modified non-flushed pages从flush_list上移除; + +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb *** +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_6_multi_threaded +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_5_innodb_adaptive +https://blogs.oracle.com/mysqlinnodb/entry/introducing_page_cleaner_thread_in + +MySQL 5.6.2引入了一个新的后台线程page_cleaner, + +https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html +http://mysql.taobao.org/monthly/2015/07/01/ + +系统表空间包括了 InnoDB data dictionary(InnoDB相关的元数据)、doublewrite buffer、the change buffer、undo logs. + +innodb_data_file_path +https://www.slideshare.net/Leithal/mysql-monitoring-mechanisms + + + + + + + + + + + + + + + + +当事务执行速度大于刷脏速度时,Ckp age和Buf age (innodb_flush_log_at_trx_commit!=1时) 都会逐步增长,当达到 async 点的时候,强制进行异步刷盘或者写 Checkpoint,如果这样做还是赶不上事务执行的速度,则为了避免数据丢失,到达 sync 点的时候,会阻塞其它所有的事务,专门进行刷盘或者写Checkpoint。 + +因此从理论上来说,只要事务执行速度大于脏页刷盘速度,最终都会触发日志保护机制,进而将事务阻塞,导致MySQL操作挂起。 + +class MVCC { +private: + view_list_t m_views; +}; + +buf_flush_wait_batch_end() + + +#define PCT_IO(p) ((ulong) (srv_io_capacity * ((double) (p) / 100.0))) + + + +buf_flush_page_cleaner_coordinator() 该函数基本上由page_cleaner每隔1s调用一次 + |-buf_flush_page_cleaner_coordinator() + |-page_cleaner_flush_pages_recommendation() + |-af_get_pct_for_dirty() 需要刷新多个页 + | |-buf_get_modified_ratio_pct() + | |-buf_get_total_list_len() + | + |-af_get_pct_for_lsn() 计算是否需要进行异步刷redo log + |-log_get_max_modified_age_async() + + + +af_get_pct_for_lsn()计算方法涉及变量 +srv_adaptive_flushing_lwm + +srv_flushing_avg_loops + +storage/innobase/log/log0log.cc + +max_modified_age_sync + + |log_write_up_to() + |-log_write_flush_to_disk_low() + |-fil_flush() + + +#####FLUSH_LRU_LIST Checkpoint +srv_LRU_scan_depth + + +#####Async/Sync Flush Checkpoint +log_free_check() 用户线程调用 + |-log_check_margins() + |-log_flush_margin() + | |-log_write_up_to() + |-log_checkpoint_margin() 执行sync操作,尝试空出足够的redo空间,避免checkpoint操作,可能会执行刷脏操作 + |-log_buf_pool_get_oldest_modification() 获取BP中最老的lsn,也就是LSN4 + | |-buf_pool_get_oldest_modification() 遍历各个BP实例,找出最大lsn,如果刚初始化完成则返回sys->lsn + | 计算log->lsn-oldest_lsn,如果超过了max_modified_age_sync值,则执行sync操作 + +log_checkpoint_margin 核心函数,用于判断当前age情况,是否需要执行异步甚至是同步刷新。 + +buff async/sync是在前面,因为redo的刷新成本更低 + +buf_pool_resize() BP调整大小时的操作 + |-buf_pool_withdraw_blocks() + + +innodb_adaptive_flushing +innodb_adaptive_flushing_lwm 百分比,配置自适应flush机制的低水位(low water mark),超过该限制之后,即使没有通过上述参数开启AF,仍然执行AF +innodb_io_capacity +innodb_io_capacity_max redo 刷盘的最大值,如果刷盘落后很多,那么IO可能会超过innodb_io_capacity而小于max +innodb_max_dirty_pages_pct 刷脏时,需要保证没有超过该值;注意,该值是一个目标,并不会影响刷脏的速率。 +innodb_max_dirty_pages_pct_lwm 脏页的低水位,用于决定什么时候开启pre-flush操作,从而保证不会超过上面配置的百分比 +innodb_flushing_avg_loops 决定了利用上述的值循环多少次之后重新计算dirty page和LSN,次数越少对外部的动态变化就越敏感 + + +要刷新多少page和lsn主要代码在af_get_pct_for_dirty()和af_get_pct_for_lsn()中,其中主要控制adaptive flush的代码位于后者函数中。 + +http://www.cnblogs.com/Amaranthus/p/4450840.html + +1.先判断redo log的容量是否到了innodb_adaptive_flushing_lwm低水位阀值。 +2.是否配置了adaptive flush或者age超过了异步刷新的阀值。 +3.lsn_age_factor=age占异步刷新阀值的比例。 +4.要被刷新的比率=innodb_io_capacity_max/innodb_io_capacity*lsn_age_factor* sqrt(innodb_io_capacity)/7.5 + + +定义BP中的页。 +class buf_page_t { +public: + buf_page_state state; + + UT_LIST_NODE_T(buf_page_t) list; 根据state的不同值决定了list的类型 + + UT_LIST_NODE_T(buf_page_t) LRU; + + - BUF_BLOCK_NOT_USED: free, withdraw + - BUF_BLOCK_FILE_PAGE: flush_list + - BUF_BLOCK_ZIP_DIRTY: flush_list + - BUF_BLOCK_ZIP_PAGE: zip_clean + +struct buf_pool_t{ + + + +https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html +http://mysql.taobao.org/monthly/2015/07/01/ + +系统表空间包括了 InnoDB data dictionary(InnoDB相关的元数据)、doublewrite buffer、the change buffer、undo logs. + +innodb_data_file_path +https://www.slideshare.net/Leithal/mysql-monitoring-mechanisms +https://www.percona.com/blog/2014/11/18/mysqls-innodb_metrics-table-how-much-is-the-overhead/ +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb + +http://www.cnblogs.com/digdeep/p/4947694.html ****** +http://hedengcheng.com/?p=220 *** +http://blog.itpub.net/30496894/viewspace-2121517/ + + +Purge 实际上就是一个垃圾回收策略,简单来说,对于类似 "DELETE FROM t WHERE c = 1;" 的 DML,InnoDB 实际上并不会直接就删除,主要原因是为了回滚以及MVCC机制,简述如下: +1. 在记录的控制标志位中,标记该行已经删除; +2. 将修改列的前镜像保存到UNDO log中; +3. 修改聚集索引中的DB_TRX_ID、DB_ROLL_PTR系统列,前者标示最近一次修改的事务信息,后者则指向undo log中的记录,而 undo log 可能会存在同样的两列指向其历史记录。 +另外,B+Tree的合并操作比较耗时,通过后台的异步线程可以避免阻塞用户的事务。 + +当事务已经提交,而且其它事务也不再依赖该记录了,那么就可以删除掉相应的记录,当然,也包括了二级索引对应的记录;这也就是 purge 线程的工作。 + +接下来,看看 purge 是如何工作的? + + +trx_purge是purge任务调度的核心函数,包含三个参数: +* n_purge_threads —>使用到的worker线程数 +* batch_size —-> 由innodb_purge_batch_size控制,表示一次Purge的记录数 +* truncate —>是否truncate history list + +trx_purge() + |-trx_purge_dml_delay() 计算是否需要对dml延迟 + | ### 持有purge_sys->latch的x锁 + |-clone_oldest_view() 复制当前的view,也就是Class MVCC:m_views链表的中尾部 + |-trx_purge_attach_undo_recs() 获取需要清理的undo记录 + | + | ### 多线程 + |-que_fork_scheduler_round_robin() 根据是否是单线程 + |-srv_que_task_enqueue_low() 将线程添加到队列中 + |-que_run_threads() 协调线程也会运行执行一个任务 + |-trx_purge_wait_for_workers_to_complete() 等待任务执行完成 + | + | ### 单线程 + | + |-trx_purge_truncate() 如果需要删除 + http://mysqllover.com/?p=696 + +purge 会复制一份系统中最老的 view,通过这一结构体,可以断定哪些回滚段需要回收。 + + +mysql> show variables like 'innodb%purge%'; ++-----------------------------------------+-------+ +| Variable_name | Value | ++-----------------------------------------+-------+ +| innodb_max_purge_lag | 0 | 如果purge操作比较慢,可以通过该参数设置dml操作的延迟时间 +| innodb_max_purge_lag_delay | 0 | 最大延迟不会超过该参数 +| innodb_purge_batch_size | 300 | 一次处理多少页 +| innodb_purge_rseg_truncate_frequency | 128 | +| innodb_purge_run_now | OFF | +| innodb_purge_stop_now | OFF | +| innodb_purge_threads | 4 | 并发线程数 +| innodb_trx_purge_view_update_only_debug | OFF | ++-----------------------------------------+-------+ +8 rows in set (0.00 sec) +Changes in 5.5 + +In 5.5 there is an option innodb-purge-threads=[0,1] to create a dedicated thread that purges asynchronously if there are UNDO logs that need to be removed. We also introduced another option innodb-purge-batch-size that can be used to fine tune purge operations. The batch size determines how many UNDO log pages purge will parse and process in one pass. + + + The default setting is 20, this is the same as the hard coded value that is in previous InnoDB releases. An interesting side effect of this value is that it also determines when purge will free the UNDO log pages after processing them. It is always after 128 passes, this magic value of 128 is the same as the number of UNDO logs in the system tablespace, now that 5.5 has 128 rollback segments. By increasing the innodb-purge-batch-size the freeing of the UNDO log pages behaviour changes, it will increase the number of UNDO log pages that it removes in a batch when the limit of 128 is reached. This change was seen as necessary so that we could reduce the cost of removing the UNDO log pages for the extra 127 rollback segments that were introduced in 5.5. Prior to this change iterating over the 128 rollback segments to find the segment to truncate had become expensive. + +Changes in 5.6 + +In 5.6 we have the same parameters as 5.5 except that innodb-purge-threads can now be between 0 and 32. This introduces true multi threaded purging. If the value is greater than 1 then InnoDB will create that many purge worker threads and a dedicated purge coordinator thread. The responsibility of the purge coordinator thread is to parse the UNDO log records and parcel out the work to the worker threads. The coordinator thread also purges records, instead of just sitting around and waiting for the worker threads to complete. The coordinator thread will divide the innodb-purge-batch-size by innodb-purge-threads and hand that out as the unit of work for each worker thread. + +对于单表来说,会阻塞在 dict_index_t::lock 中,除非使用分区;对于多表来说是可以并发的。 + + + + + +####### 崩溃恢复(Crash Recovery) +Crash Recovery的起点,Checkpoint LSN存储位置? +InnoDB如何完成Redo日志的重做? +InnoDB如何定位哪些事务需要Rollback? +Crash Recovery需要等待Rollbach完成吗? +InnoDB各版本,在Crash Recovery流程上做了哪些优化? +mysqld_safe是否存在自动重启功能? + +ha_recover + + +af_get_pct_for_lsn() + |-log_get_max_modified_age_async() +   +MySQL · 引擎特性 · InnoDB 崩溃恢复过程 +http://mysql.taobao.org/monthly/2015/06/01/ + +Database was not shutdown normally!   # InnoDB开始Crash Recovery{recv_init_crash_recovery_spaces()} +Starting crash recovery. + + + + +Doing recovery: scanned up to log sequence number 0            # 扫描redolog日志recv_scan_log_recs() + + + +Starting an apply batch of log records to the database...      # 开始应用redolog recv_apply_hashed_log_recs()    +InnoDB: Progress in percent: 2 3 4 5 ... 99 +Apply batch completed + + +1. 读取Checkpoint LSN +2. 从Checkpoint LSN开始向前遍历Redo Log File +   重做从Checkpoint LSN开始的所有Redo日志 +3. 重新构造系统崩溃时的事务 +   Commit事务,等待Purge线程回收 +   Prepare事务,由MySQL Server控制提交或者回滚(与Binlog 2PC相关) +   Active事务,回滚 +4. 新建各种后台线程,Crash Recovery完成返回 + + +正常关闭时,会在flush redo log和脏页后,做一次完全同步的checkpoint,并将checkpoint的LSN写到第一个ibdata文件的第一个page中,详细可以参考fil_write_flushed_lsn()。 + + +innodb_counter_info[] 定义了监控计数器 + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://tech.uc.cn/?p=716 + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=620 + +http://apprize.info/php/effective/6.html + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +数据库内核分享 + +https://www.slideshare.net/frogd/inno-db-15344119 + +检查保存到磁盘的最大checkpoint LSN与redo-log的LSN是否一致; + + +事务源码 +崩溃恢复 +ReadView +Undo-Redo + + +https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in + + + + + + + + + +######################################################################### + + +如果 InnoDB 没有正常关闭,会在服务器启动的时候执行崩溃恢复 (Crash Recovery),这一流程比较复杂,涉及到了 redo log、undo log 甚至包括了 binlog 。 + +在此简单介绍下 InnoDB 崩溃恢复的流程。 + + + +## 崩溃恢复 + +InnoDB 的数据恢复是一个很复杂的过程,在其恢复过程中,需要 redolog、binlog、undolog 等参与,接下来具体了解下整个恢复的过程。 + +{% highlight text %} +innobase_init() + |-innobase_start_or_create_for_mysql() +   | +   |-recv_sys_create()   创建崩溃恢复所需要的内存对象 +   |-recv_sys_init() +   | +   |-srv_sys_space.check_file_spce()                检查系统表空间是否正常 +   |-srv_sys_space.open_or_create()              1. 打开系统表空间,并获取flushed_lsn +   | |-read_lsn_and_check_flags() +   |   |-open_or_create()                           打开系统表空间 +   |   |-read_first_page()                          读取第一个page +   |   |-buf_dblwr_init_or_load_pages()             将双写缓存加载到内存中,如果ibdata日志损坏,则通过dblwr恢复 +   |   |-validate_first_page()                      校验第一个页是否正常,并读取flushed_lsn +   |   | |-mach_read_from_8() 读取LSN,偏移为FIL_PAGE_FILE_FLUSH_LSN +   |   |-restore_from_doublewrite()                 如果有异常,则从dblwr恢复 +   | +   |-log_group_init() redo log的结构初始化 +   |-srv_undo_tablespaces_init()                    对于undo log表空间恢复 +   | +   |-recv_recovery_from_checkpoint_start()       2. 从redo-log的checkpoint开始恢复;注意,正常启动也会调用 +   | |-buf_flush_init_flush_rbt()                   创建一个红黑树,用于加速插入flush list +   | | 通过force_recovery判断是否大于SRV_FORCE_NO_LOG_REDO +   | |-recv_find_max_checkpoint() 查找最新的checkpoint点,在此会校验redo log的头部信息 +   | | |-log_group_header_read() 读取512字节的头部信息 +   | | |-mach_read_from_4() 读取redo log的版本号LOG_HEADER_FORMAT +   | | |-recv_check_log_header_checksum() 版本1则校验页的完整性 +   | | | |-log_block_get_checksum() 获取页中的checksum,也就是页中的最后四个字节 +   | | | |-log_block_calc_checksum_crc32() 并与计算后的checksum比较 +   | | |-recv_find_max_checkpoint_0() +   | | |-log_group_header_read() +   | | +   | |-recv_group_scan_log_recs() 3. 从checkpoint-lsn处开始查找MLOG_CHECKPOINT +   | | |-log_group_read_log_seg() +   | | |-recv_scan_log_recs() + | | |-recv_parse_log_recs() +   | |-recv_group_scan_log_recs() +   | | ##如果flushed_lsn和checkponit lsn不同则恢复 +   | |-recv_init_crash_recovery() +   | |-recv_init_crash_recovery_spaces() +   | | +   | |-recv_group_scan_log_recs() +   | +   |-trx_sys_init_at_db_start() +   | +   |-recv_apply_hashed_log_recs()                    当页LSN小于log-record中的LSN时,应用redo日志 +   | |-recv_recover_page()                           实际调用recv_recover_page_func() + | |-recv_parse_or_apply_log_rec_body() +   | +   |-recv_recovery_from_checkpoint_finish()          完成崩溃恢复 + +fil_op_write_log() 些日志 + + + +fil_names_write() 写入MLOG_FILE_NAME +fil_name_write() +fil_op_write_log() + +{% endhighlight %} + +MLOG_FILE CHECKPOINT 如何写入???如何使用???kkkk + +{% highlight text %} +flushed_lsn + 只有在系统表空间的第一页存在,偏移量为FIL_PAGE_FILE_FLUSH_LSN(26),至少在此LSN之前的页已经刷型到磁盘; + 该LSN通过fil_write_flushed_lsn()函数写入; +{% endhighlight %} + +1. 从系统表空间中读取flushed_lsn,每次刷脏时都会写入系统表空间的第一页,而且为了防止写入异常会使用Double Write Buffer; +2. 从redo log头部中读取两个checkpoint值,并比较获取最新的checkpoint信息; + + + + +mlog 如何记录的那些 ibd 时需要读取的。 + + + + + +InnoDB 在 MySQL 启动的时候,会对 redo-log 进行日志回放,通过 recv_sys_t 结构来进行数据恢复和控制的,它的结构如下: + +{% highlight text %} +struct recv_sys_t { + +mutex_t mutex; /*保护锁*/ +ibool apply_log_recs; /*正在应用log record到page中*/ +ibool apply_batch_on; /*批量应用log record标志*/ + +dulint lsn; +ulint last_log_buf_size; + +byte* last_block; /*恢复时最后的块内存缓冲区*/ +byte* last_block_buf_start; /*最后块内存缓冲区的起始位置,因为last_block是512地址对齐的,需要这个变量记录free的地址位置*/ +byte* buf; /*从日志块中读取的重做日志信息数据*/ +ulint len; /*buf有效的日志数据长度*/ + +dulint parse_start_lsn; /*开始parse的lsn*/ +dulint scanned_lsn; /*已经扫描过的lsn序号*/ + +ulint scanned_checkpoint_no; /*恢复日志的checkpoint 序号*/ +ulint recovered_offset; /*恢复位置的偏移量*/ + +dulint recovered_lsn; /*恢复的lsn位置*/ +dulint limit_lsn; /*日志恢复最大的lsn,暂时在日志重做的过程没有使用*/ + +ibool found_corrupt_log; /*是否开启日志恢复诊断*/ + +log_group_t* archive_group; + +mem_heap_t* heap; /*recv sys的内存分配堆,用来管理恢复过程的内存占用*/ + + hash_table_t* addr_hash; // 以(space_id+page_no)为KEY的Hash表 + +ulint n_addrs; /*addr_hash中包含recv_addr的个数*/ + +}; +{% endhighlight %} + +在这个结构中,比较复杂的是addr_hash这个哈希表,这个哈希表是用sapce_id和page_no作为hash key,里面存储有恢复时对应的记录内容。 + +恢复日志在从日志文件中读出后,进行解析成若干个recv_t并存储在哈希表当中。在一个读取解析周期过后,日志恢复会对hash表中的recv_t中的数据写入到ibuf和page中。这里为什么要使用hash表呢?个人觉得是为了同一个page的数据批量进行恢复的缘故,这样可以page减少随机插入和修改。 以下是和这个过程相关的几个数据结构: + + +{% highlight text %} +/*对应页的数据恢复操作集合*/ +struct recv_addr_t { + ulint state; /*状态,RECV_NOT_PROCESSED、RECV_BEING_PROCESSED、RECV_PROCESSED*/ + ulint space; /*space的ID*/ + ulint page_no; /*页序号*/ + UT_LIST_BASE_NODE_T(recv_t) rec_list; // 该页对应的log records地址 + hash_node_t addr_hash; +}; + +/*当前的记录操作*/ +struct recv_t { + byte type; /*log类型*/ + ulint len; /*当前记录数据长度*/ + recv_data_t* data; /*当前的记录数据list*/ + + lsn_t start_lsn; // mtr起始LSN + lsn_t end_lsn; // mtr结尾LSN + UT_LIST_NODE_T(recv_t) rec_list; // 该页对应的log records +}; + +struct recv_data_t { + recv_data_t* next; // 指向下个结构体,该地址之后为一大块内存,用于存储log record消息体 +}; +{% endhighlight %} + +他们的内存关系结构图如下: \ +2.重做日志推演过程的LSN关系 +除了这个恢复的哈希表以外,recv_sys_t中的各种LSN也是和日志恢复有非常紧密的关系。以下是各种lsn的解释: parse_start_lsn 本次日志重做恢复起始的lsn,如果是从checkpoint处开始恢复,等于checkpoint_lsn。 scanned_lsn 在恢复过程,将恢复日志从log_sys->buf解析块后存入recv_sys->buf的日志lsn. recovered_lsn 已经将数据恢复到page中或者已经将日志操作存储addr_hash当中的日志lsn; 在日志开始恢复时: +parse_start_lsn = scanned_lsn = recovered_lsn = 检查点的lsn。 +在日志完成恢复时: +parse_start_lsn = 检查点的lsn +scanned_lsn = recovered_lsn = log_sys->lsn。 +在日志推演过程中lsn大小关系如下: +\ +3.日志恢复的主要接口和流程 +恢复日志主要的接口函数: recv_recovery_from_checkpoint_start 从重做日志组内的最近的checkpoint开始恢复数据 +recv_recovery_from_checkpoint_finish 结束从重做日志组内的checkpoint的数据恢复操作 +recv_recovery_from_archive_start 从归档日志文件中进行数据恢复 +recv_recovery_from_archive_finish 结束从归档日志中的数据恢复操作 +recv_reset_logs 截取重做日志最后一段作为新的重做日志的起始位置,可能会丢失数据。 +重做日志恢复数据的流程(checkpoint方式) 1.当MySQL启动的时候,先会从数据库文件中读取出上次保存最大的LSN。 +2.然后调用recv_recovery_from_checkpoint_start,并将最大的LSN作为参数传入函数当中。 +3.函数会先最近建立checkpoint的日志组,并读取出对应的checkpoint信息 +4.通过checkpoint lsn和传入的最大LSN进行比较,如果相等,不进行日志恢复数据,如果不相等,进行日志恢复。 +5.在启动恢复之前,先会同步各个日志组的archive归档状态 +6.在开始恢复时,先会从日志文件中读取2M的日志数据到log_sys->buf,然后对这2M的数据进行scan,校验其合法性,而后将去掉block header的日志放入recv_sys->buf当中,这个过程称为scan,会改变scanned lsn. +7.在对2M的日志数据scan后,innodb会对日志进行mtr操作解析,并执行相关的mtr函数。如果mtr合法,会将对应的记录数据按space page_no作为KEY存入recv_sys->addr_hash当中。 +8.当对scan的日志数据进行mtr解析后,innodb对会调用recv_apply_hashed_log_recs对整个recv_sys->addr_hash进行扫描,并按照日志相对应的操作进行对应page的数据恢复。这个过程会改变recovered_lsn。 +9.如果完成第8步后,会再次从日志组文件中读取2M数据,跳到步骤6继续相对应的处理,直到日志文件没有需要恢复的日志数据。 +10.innodb在恢复完成日志文件中的数据后,会调用recv_recovery_from_checkpoint_finish结束日志恢复操作,主要是释放一些开辟的内存。并进行事务和binlog的处理。 +上面过程的示意图如下: + + + + +####### 崩溃恢复(Crash Recovery) +Crash Recovery的起点,Checkpoint LSN存储位置? +InnoDB如何完成Redo日志的重做? +InnoDB如何定位哪些事务需要Rollback? +Crash Recovery需要等待Rollbach完成吗? +InnoDB各版本,在Crash Recovery流程上做了哪些优化? +mysqld_safe是否存在自动重启功能? + +ha_recover + + +af_get_pct_for_lsn() + |-log_get_max_modified_age_async() +   +MySQL · 引擎特性 · InnoDB 崩溃恢复过程 +http://mysql.taobao.org/monthly/2015/06/01/ + +Database was not shutdown normally!   # InnoDB开始Crash Recovery{recv_init_crash_recovery_spaces()} +Starting crash recovery. + + + + +Doing recovery: scanned up to log sequence number 0            # 扫描redolog日志recv_scan_log_recs() + + + + +Starting an apply batch of log records to the database...      # 开始应用redolog recv_apply_hashed_log_recs()    +InnoDB: Progress in percent: 2 3 4 5 ... 99 +Apply batch completed + + +1. 读取Checkpoint LSN +2. 从Checkpoint LSN开始向前遍历Redo Log File +   重做从Checkpoint LSN开始的所有Redo日志 +3. 重新构造系统崩溃时的事务 +   Commit事务,等待Purge线程回收 +   Prepare事务,由MySQL Server控制提交或者回滚(与Binlog 2PC相关) +   Active事务,回滚 +4. 新建各种后台线程,Crash Recovery完成返回 + + +正常关闭时,会在flush redo log和脏页后,做一次完全同步的checkpoint,并将checkpoint的LSN写到第一个ibdata文件的第一个page中,详细可以参考fil_write_flushed_lsn()。 + + +innodb_counter_info[] 定义了监控计数器 + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://tech.uc.cn/?p=716 + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=620 + +http://apprize.info/php/effective/6.html + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +数据库内核分享 + +https://www.slideshare.net/frogd/inno-db-15344119 + +检查保存到磁盘的最大checkpoint LSN与redo-log的LSN是否一致; + + +事务源码 +崩溃恢复 +ReadView +关闭过程 +Undo-Redo + + +https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in + + + + +GTID的全称为 global transaction identifier  , 可以翻译为全局事务标示符,GTID在原始master上的事务提交时被创建。GTID需要在全局的主-备拓扑结构中保持唯一性,GTID由两部分组成: +GTID = source_id:transaction_id + +source_id用于标示源服务器,用server_uuid来表示,这个值在第一次启动时生成,并写入到配置文件data/auto.cnf中 +transaction_id则是根据在源服务器上第几个提交的事务来确定。 + +一个GTID的生命周期包括: +1.事务在主库上执行并提交 +给事务分配一个gtid(由主库的uuid和该服务器上未使用的最小事务序列号),该GTID被写入到binlog中。 +2.备库读取relaylog中的gtid,并设置session级别的gtid_next的值,以告诉备库下一个事务必须使用这个值 +3.备库检查该gtid是否已经被其使用并记录到他自己的binlog中。slave需要担保之前的事务没有使用这个gtid,也要担保此时已分读取gtid,但未提交的事务也不恩呢过使用这个gtid. +4.由于gtid_next非空,slave不会去生成一个新的gtid,而是使用从主库获得的gtid。这可以保证在一个复制拓扑中的同一个事务gtid不变。 + +由于GTID在全局的唯一性,通过GTID,我们可以在自动切换时对一些复杂的复制拓扑很方便的提升新主库及新备库,例如通过指向特定的GTID来确定新备库复制坐标。 + +当然,使用GTID也有一些限制: +1.事务中的更新包含非事务性存储引擎,这可能导致多个GTID分配给同一个事务。 +2. create table…select语句不被支持,因为该语句会被拆分成create table 和insert两个事务,并且这个两个事务被分配了同一个GTID,这会导致insert被备库忽略掉。 +3.不支持CREATE/DROP临时表操作 + +可以看到,支持GTID的复制对一些语句都有一些限制,MySQL也提供了一个选项disable-gtid-unsafe-statements以禁止这些语句的执行。 + + + + +### ibd页损坏 +Page损坏的情况比较多:二级索引页损坏(可以通过OPTIMIZE TABLE恢复);聚集索引页损坏;表字典损坏; +----- 使用VIM编译 +vim -b titles.ibd +----- 通过外部xxd程序改变,注意修改完之后一定要通过-r选项恢复为二进制格式 +:%!xxd +:%!xxd -r +当执行check table titles;检查表是否正常时,就会由于页面错误退出,报错信息如下。 + +buf_page_io_complete() +2017-03-09T08:58:34.750125Z 4 [ERROR] InnoDB: Database page corruption on disk or a failed file read of page [page id: space=NUM, page number=NUM]. You may have to recover from a backup. +len 16384; hex ... ... +InnoDB: End of page dump +InnoDB: Page may be an index page where index id is 48 +2017-03-09T08:58:34.804632Z 4 [ERROR] [FATAL] InnoDB: Aborting because of a corrupt database page in the system tablespace. Or,  there was a failure in tagging the tablespace  as corrupt. +2017-03-09 16:58:34 0x7fe6e87b7700  InnoDB: Assertion failure in thread 140629719611136 in file ut0ut.cc line 916 + +实际上是可以正常重启的,但是一旦查询到该页时,仍然会报错,接下来看看如何恢复其中还完好的数据。 + +修改my.ini中的innodb_force_recovery参数,默认是0,此时可以修改为1-6,使mysqld在启动时跳过部分恢复步骤,在启动后将数据导出来然后重建数据库;当然,不同的情况可能恢复的数据会有所不同。 + +1. SRV_FORCE_IGNORE_CORRUPT: 忽略检查到的corrupt页;** +2. SRV_FORCE_NO_BACKGROUND): 阻止主线程的运行,在srv_master_thread()中处理;** + +3. SRV_FORCE_NO_TRX_UNDO):不执行事务回滚操作。 +4. SRV_FORCE_NO_IBUF_MERGE):不执行插入缓冲的合并操作。 +5. SRV_FORCE_NO_UNDO_LOG_SCAN):不查看重做日志,InnoDB存储引擎会将未提交的事务视为已提交。 +6. SRV_FORCE_NO_LOG_REDO):不执行redo log。 + +另外,需要注意,设置参数值大于0后,可以对表进行select,create,drop操作,但insert,update或者delete这类操作是不允许的。 + +再次执行check table titles;时,会报如下的错误。 ++------------------+-------+----------+---------------------------------------------------+ +| Table            | Op    | Msg_type | Msg_text                                          | ++------------------+-------+----------+---------------------------------------------------+ +| employees.titles | check | Warning  | InnoDB: The B-tree of index PRIMARY is corrupted. | +| employees.titles | check | error    | Corrupt                                           | ++------------------+-------+----------+---------------------------------------------------+ +2 rows in set (2.47 sec) + +SELECT * FROM titles INTO OUTFILE '/tmp/titles.csv' +   FIELDS TERMINATED BY ',' ENCLOSED BY '"' +   LINES TERMINATED BY '\r\n'; +注意上述在使用LOAD DATA INFILE或者SELECT INTO OUTFILE操作时,需要在配置文件中添加secure_file_priv=/tmp配置项,或者配置成secure_file_priv="/"不限制导入和导出路径。 +如何确认哪些数据丢失了???? + +http://www.runoob.com/mysql/mysql-database-export.html + +如下是导致MySQL表毁坏的常见原因: +1、 服务器突然断电或者强制关机导致数据文件损坏; +2、 mysqld进程在修改表时被强制杀掉,例如kill -9; +3、 磁盘故障、服务器宕机等硬件问题无法恢复; +4、 使用myisamchk的同时,mysqld也在操作表; +5、 MySQL、操作系统、文件系统等软件的bug。 + + + + +表损坏的典型症状: +     1 、当在从表中选择数据之时,你得到如下错误:  + +      Incorrect key file for table: '...'. Try to repair it + +     2 、查询不能在表中找到行或返回不完全的数据。 + +     3 、Error: Table 'p' is marked as crashed and should be repaired 。 + +     4 、打开表失败: Can’t open file: ‘×××.MYI’ (errno: 145) 。 + +  ER_NOT_FORM_FILE  Incorrect information in file: +  ER_NOT_KEYFILE    Incorrect key file for table 'TTT'; try to repair it +  ER_OLD_KEYFILE    Old key file for table 'TTT'; repair it! +  ER_CANT_OPEN_FILE Can't open file: 'TTT' (errno: %d - %s) +  + +  3.预防 MySQL 表损坏 + +   可以采用以下手段预防mysql 表损坏:  + +    1 、定期使用myisamchk 检查MyISAM 表(注意要关闭mysqld ),推荐使用check table 来检查表(不用关闭mysqld )。 + +    2 、在做过大量的更新或删除操作后,推荐使用OPTIMIZE TABLE 来优化表,这样既减少了文件碎片,又减少了表损坏的概率。 + +    3 、关闭服务器前,先关闭mysqld (正常关闭服务,不要使用kill -9 来杀进程)。 + +    4 、使用ups 电源,避免出现突然断电的情况。 + +    5 、使用最新的稳定发布版mysql ,减少mysql 本身的bug 导致表损坏。 + +    6 、对于InnoDB 引擎,你可以使用innodb_tablespace_monitor来检查表空间文件内文件空间管理的完整性。 + +    7 、对磁盘做raid ,减少磁盘出错并提高性能。 + +    8 、数据库服务器最好只跑mysqld 和必要的其他服务,不要跑其他业务服务,这样减少死机导致表损坏的可能。 + +    9 、不怕万一,只怕意外,平时做好备份是预防表损坏的有效手段。 + +  4. MySQL 表损坏的修复 + +  MyISAM 表可以采用以下步骤进行修复 : + +    1、  使用 reapair table 或myisamchk 来修复。  + +    2、  如果上面的方法修复无效,采用备份恢复表。 + + + +  具体可以参考如下做法: + +  阶段1 :检查你的表 + +    如果你有很多时间,运行myisamchk *.MYI 或myisamchk -e *.MYI 。使用-s (沉默)选项禁止不必要的信息。  + +    如果mysqld 服务器处于宕机状态,应使用--update-state 选项来告诉myisamchk 将表标记为' 检查过的' 。 + +    你必须只修复那些myisamchk 报告有错误的表。对这样的表,继续到阶段2 。 + +    如果在检查时,你得到奇怪的错误( 例如out of memory 错误) ,或如果myisamchk 崩溃,到阶段3 。 + +  阶段2 :简单安全的修复 + +    注释:如果想更快地进行修复,当运行myisamchk 时,你应将sort_buffer_size 和Key_buffer_size 变量的值设置为可用内存的大约25% 。 + +    首先,试试myisamchk -r -q tbl_name(-r -q 意味着“ 快速恢复模式”) 。这将试图不接触数据文件来修复索引文件。如果数据文件包含它应有的一切内容和指向数据文件内正确地点的删除连接,这应该管用并且表可被修复。开始修复下一张表。否则,执行下列过程: + +    在继续前对数据文件进行备份。 + +    使用myisamchk -r tbl_name(-r 意味着“ 恢复模式”) 。这将从数据文件中删除不正确的记录和已被删除的记录并重建索引文件。 + +    如果前面的步骤失败,使用myisamchk --safe-recover tbl_name 。安全恢复模式使用一个老的恢复方法,处理常规恢复模式不行的少数情况( 但是更慢) 。 + +    如果在修复时,你得到奇怪的错误( 例如out of memory 错误) ,或如果myisamchk 崩溃,到阶段3 。  + +  阶段3 :困难的修复 + +    只有在索引文件的第一个16K 块被破坏,或包含不正确的信息,或如果索引文件丢失,你才应该到这个阶段。在这种情况下,需要创建一个新的索引文件。按如下步骤操做: + +    把数据文件移到安全的地方。 + +    使用表描述文件创建新的( 空) 数据文件和索引文件: + +    shell> mysql db_name + +    mysql> SET AUTOCOMMIT=1; + +    mysql> TRUNCATE TABLE tbl_name; + +    mysql> quit  + +    如果你的MySQL 版本没有TRUNCATE TABLE ,则使用DELETE FROM tbl_name 。 + +    将老的数据文件拷贝到新创建的数据文件之中。(不要只是将老文件移回新文件之中;你要保留一个副本以防某些东西出错。) + +    回到阶段2 。现在myisamchk -r -q 应该工作了。(这不应该是一个无限循环)。 + +    你还可以使用REPAIR TABLE tbl_name USE_FRM ,将自动执行整个程序。 + +  阶段4 :非常困难的修复 + +    只有.frm 描述文件也破坏了,你才应该到达这个阶段。这应该从未发生过,因为在表被创建以后,描述文件就不再改变了。 + +     从一个备份恢复描述文件然后回到阶段3 。你也可以恢复索引文件然后回到阶段2 。对后者,你应该用myisamchk -r 启动。 + +    如果你没有进行备份但是确切地知道表是怎样创建的,在另一个数据库中创建表的一个拷贝。删除新的数据文件,然后从其他数据库将描述文件和索引文件移到破坏的数据库中。这样提供了新的描述和索引文件,但是让.MYD 数据文件独自留下来了。回到阶段2 并且尝试重建索引文件。 +Log Sequence Number, LSN + +Sharp Checkpoint 是一次性将 buffer pool 中的所有脏页都刷新到磁盘的数据文件,同时会保存最后一个提交的事务LSN。 + + +fuzzy checkpoint就更加复杂了,它是在固定的时间点发生,除非他已经将所有的页信息刷新到了磁盘,或者是刚发生过一次sharp checkpoint,fuzzy checkpoint发生的时候会记录两次LSN,也就是检查点发生的时间和检查点结束的时间。但是呢,被刷新的页在并不一定在某一个时间点是一致的,这也就是它为什么叫fuzzy的原因。较早刷入磁盘的数据可能已经修改了,较晚刷新的数据可能会有一个比前面LSN更新更小的一个LSN。fuzzy checkpoint在某种意义上可以理解为fuzzy checkpoint从redo  log的第一个LSN执行到最后一个LSN。恢复以后的话,REDO LOG就会从最后一个检查点开始时候记录的LSN开始。 + +一般情况下大家可能fuzzy checkpoint的发生频率会远高于sharp checkpoint发生的频率,这个事毫无疑问的。不过当数据库关闭,切换redo日志文件的时候是会触发sharp checkpoint,一般情况是fuzzy checkpoint发生的更多一些。 + +一般情况下,执行普通操作的时候将不会发生检查点的操作,但是,fuzzy checkpoint却要根据时间推进而不停的发生。刷新脏页已经成为了数据库的一个普通的日常操作。 + +INNODB维护了一个大的缓冲区,以保证被修改的数据不会被立即写入磁盘。她会将这些修改过的数据先保留在buffer pool当中,这样在这些数据被写入磁盘以前可能会经过多次的修改,我们称之为写结合。这些数据页在buffer pool当中都是按照list来管理的,free list会记录那些空间是可用的,LRU list记录了那些数据页是最近被访问到的。flush list则记录了在LSN顺序当中的所有的dirty page信息,最近最少修改信息。 + +这里着重看一下flush list,我们知道innodb的缓存空间是有限的。如果buffer pool空间使用完毕,再次读取新数据就会发生磁盘读,也就是会发生flush操作,所以说就要释放一部分没有被使用的空间来保证buffer pool的可用性。由于这样的操作是很耗时的,所以说INNODB是会连续按照时间点去执行刷新操作,这样就保证了又足够的clean page来作为交换,而不必发生flush操作。每一次刷新都会将flush list的最老的信息驱逐,这样才能够保证数据库缓冲命中率是很高的一个值。这些老数据的选取是根据他们在磁盘的位置和LSN(最后一次修改的)号来确认数据新旧。 + +MySQL数据的日志都是混合循环使用的,但是如果这些事物记录的页信息还没有被刷新到磁盘当中的话是绝对不会被覆盖写入的。如果还没被刷新入磁盘的数据被覆盖了日志文件,那数据库宕机的话岂不是所有被覆盖写入的事物对应的数据都要丢失了呢。因此,数据修改也是有时间限制的,因为新的事物或者正在执行的事物也是需要日志空间的。日志越大,限制就越小。而且每次fuzzy checkpoint都会将最老最不被访问的数据驱逐出去,这也保证了每次驱逐的都是最老的数据,在下次日志被覆盖写入的时候都是已经被刷盘的数据的日志信息。最后一个老的,不被访问的数据的事物的LSN就是事务日志的 low-water标记,INNODB一直想提高这个LSN的值以保证buffer pool又足够的空间刷入新的数据,同时保证了数据库事务日志文件可以被覆盖写入的时候有足够的空间使用。将事务日志设置的大一些能够降低释放日志空间的紧迫性,从而可以大大的提高性能。 + +当innodb刷新 dirty page落盘的时候,他会找到最老的dirty  page对应的LSN并且将其标记为low-water,然后将这些信息记录到事物日志的头部,因此,每次刷新脏页都是要从flush  list的头部进行刷新的。在推进最老的LSN的标记位置的时候,本质上就是做了一次检查点。 + +当INNODB宕机的时候,他还要做一些额外的操作,第一:停止所有的数据更新等操作,第二:将dirty page in  buffer 的数据刷新落盘,第三:记录最后的LSN,因为我们上面也说到了,这次发生的是sharp checkpoint,并且,这个LSN会写入到没一个数据库文件的头部,以此来标记最后发生检查点的时候的LSN位置。 + +我们知道,刷新脏页数据的频率如果越高的话就代表整个数据库的负载很大,越小当然代表数据库的压力会小一点。将LOG 文件设置的很大能够再检查点发生期间减少磁盘的IO,总大小最好能够设置为和buffer pool大小相同,当然如果日志文件设置太大的话MySQL就会再crash recovery的时候花费更多的时间(5.5之前)。 + + +http://mysqlmusings.blogspot.com/2011/04/crash-safe-replication.html + +http://apprize.info/php/effective/6.html + +http://mysqllover.com/?p=620 + +http://tech.uc.cn/?p=716 + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://mysql.taobao.org/monthly/2015/06/01/ + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +https://gold.xitu.io/entry/5841225561ff4b00587ec651 + +https://yq.aliyun.com/articles/64677?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&utm_content=m_7935 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=696 + +http://mysqllover.com/?p=213 + +http://mysql.taobao.org/monthly/2016/02/03/ + +http://mysql.taobao.org/monthly/2016/08/07/ + +http://mysql.taobao.org/monthly/2015/12/01/ + +http://mysqllover.com/?p=834 + +http://mysqllover.com/?p=1087 + +http://www.xuchunyang.com/2016/01/13/deak_lock/ + +http://mysqllover.com/?p=1119 + +http://www.askmaclean.com/archives/mysql-recover-innodb.html + +http://www.askmaclean.com/archives/mysql%e4%b8%ad%e6%81%a2%e5%a4%8d%e4%bf%ae%e5%a4%8dinnodb%e6%95%b0%e6%8d%ae%e5%ad%97%e5%85%b8.html + +http://www.thinkphp.cn/code/430.html + +http://www.cnblogs.com/liuhao/p/3714012.html + +http://louisyang.blog.51cto.com/8381303/1360394 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://hamilton.duapp.com/detail?articleId=34 + +https://twindb.com/undrop-tool-for-innodb/ + +https://twindb.com/tag/stream_parser/ + +http://jishu.y5y.com.cn/aeolus_pu/article/details/60143284 + +http://hedengcheng.com/?p=148 + +read_view + +http://www.cnblogs.com/chenpingzhao/p/5065316.html + +http://kabike.iteye.com/blog/1820553 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://mysqllover.com/?p=696 + +http://imysql.com/2014/08/13/mysql-faq-howto-shutdown-mysqld-fulgraceful.shtml + +http://11879724.blog.51cto.com/11869724/1872928 + +http://coolnull.com/3145.html + +http://mysqllover.com/?p=594 + +http://mysqllover.com/?p=87 + +http://mysqllover.com/?p=581 + +http://keithlan.github.io/2016/06/23/gtid/ + +http://mysqlmusings.blogspot.com/2011/04/crash-safe-replication.html + +https://dbarobin.com/2015/08/29/mysql-optimization-under-ssd/ + +http://www.orczhou.com/index.php/2010/12/more-about-mysql-innodb-shutdown/ + +https://www.slideshare.net/frogd/inno-db-15344119 + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://github.com/zhaiwx1987/innodb_ebook/blob/master/innodb_adaptive_hash.md + + + + +隔离级别 + +详细可以查看row_search_for_mysql()中的实现,实际上也就是row_search_mvcc()函数的实现。 + +row_search_for_mysql()    + |-row_search_no_mvcc()       # 对于MySQL内部使用的表(用户不可见),不需要MVCC机制 + |-row_search_mvcc() + +row_search_no_mvcc()用于MySQL的内部表使用,通常是一些作为一个较大任务的中间结果存储,所以希望其可以尽快处理,因此不需要MVCC机制。 + +事务的隔离级别在trx->isolation_level中定义,其取值也就是如下的宏定义。 + +#define TRX_ISO_READ_UNCOMMITTED        0 +#define TRX_ISO_READ_COMMITTED          1 +#define TRX_ISO_REPEATABLE_READ         2 +#define TRX_ISO_SERIALIZABLE            3 + + +在不同的隔离级别下,可见性的判断有很大的不同。 + +READ-UNCOMMITTED +在该隔离级别下会读到未提交事务所产生的数据更改,这意味着可以读到脏数据,实际上你可以从函数row_search_mvcc中发现,当从btree读到一条记录后,如果隔离级别设置成READ-UNCOMMITTED,根本不会去检查可见性或是查看老版本。这意味着,即使在同一条SQL中,也可能读到不一致的数据。 + +    READ-COMMITTED +    在该隔离级别下,可以在SQL级别做到一致性读,当事务中的SQL执行完成时,ReadView被立刻释放了,在执行下一条SQL时再重建ReadView。这意味着如果两次查询之间有别的事务提交了,是可以读到不一致的数据的。 + +    REPEATABLE-READ +    可重复读和READ-COMMITTED的不同之处在于,当第一次创建ReadView后(例如事务内执行的第一条SEELCT语句),这个视图就会一直维持到事务结束。也就是说,在事务执行期间的可见性判断不会发生变化,从而实现了事务内的可重复读。 + +    SERIALIZABLE +    序列化的隔离是最高等级的隔离级别,当一个事务在对某个表做记录变更操作时,另外一个查询操作就会被该操作堵塞住。同样的,如果某个只读事务开启并查询了某些记录,那么另外一个session对这些记录的更改操作是被堵塞的。内部的实现其实很简单: +        对InnoDB表级别加LOCK_IS锁,防止表结构变更操作 +        对查询得到的记录加LOCK_S共享锁,这意味着在该隔离级别下,读操作不会互相阻塞。而数据变更操作通常会对记录加LOCK_X锁,和LOCK_S锁相冲突,InnoDB通过给查询加记录锁的方式来保证了序列化的隔离级别。 + +注意不同的隔离级别下,数据具有不同的隔离性,甚至事务锁的加锁策略也不尽相同,你需要根据自己实际的业务情况来进行选择。 + + + + + +SELECT count_star, sum_timer_wait, avg_timer_wait, event_name FROM events_waits_summary_global_by_event_name WHERE count_star > 0 AND event_name LIKE "wait/synch/%" ORDER BY sum_timer_wait DESC LIMIT 20; + + + + +最新的事务ID通过trx_sys_get_new_trx_id()函数获取,每次超过了TRX_SYS_TRX_ID_WRITE_MARGIN次数后,都会调用trx_sys_flush_max_trx_id()函数刷新磁盘。 + + + + + + +innodb_force_recovery变量对应源码中的srv_force_recovery变量, +  + + +当innodb_fast_shutdown设置为0时,会导致purge一直工作近两个小时。????? + +从5.5版本开始,purge任务从主线程中独立出来;5.6开始支持多个purge线程,可以通过innodb_purge_threads变量控制。 + +purge后台线程的最大数量可以有32个,包括了一个coordinator线程,以及多个worker线程。 + +在innobase_start_or_create_for_mysql()函数中,会创建srv_purge_coordinator_thread以及srv_worker_thread线程。 + + +srv_purge_coordinator_thread() + |-srv_purge_coordinator_suspend()   如果不需要purge或者上次purge记录数为0,则暂停 + |-srv_purge_should_exit()           判断是否需要退出;fast_shutdown=0则等待所有purge操作完成 + |-srv_do_purge()                    协调线程的主要工作,真正调用执行purge操作的函数 + | + |-trx_purge()                       防止上次循环结束后又新的记录写入,此处不再使用worker线程 + | + |-trx_purge()                       最后对history-list做一次清理,确保所有worker退出 + +srv_worker_thread() + + +最后一次做trx_purge()时,为了防止执行时间过程,批量操作时不再采用innodb_purge_batch_size(300)指定的值,而是采用20。 + + +InnoDB的数据组织方式采用聚簇索引,也就是索引组织表,而二级索引采用(索引键值,主键键值)组合来唯一确定一条记录。 +无论是聚簇索引,还是二级索引,每条记录都包含了一个DELETED-BIT位,用于标识该记录是否是删除记录;除此之外,聚簇索引还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR,分别表示产生当前记录项的事务ID以及指向当前记录的undo信息。 + + + +从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断???? + + +InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下: + +class ReadView { +private: +  trx_id_t        m_low_limit_id;  // +}; + + +ReadView::prepare() + + +copy_trx_ids +mtr_commit(struct mtr_t*)                 提交一个mini-transaction,调用mtr_t::commit() + |-mtr_t::Command::execute()              写redo-log,将脏页添加到flush-list,并释放占用资源 +   |-mtr_t::Command::prepare_write()      准备写入日志 +   | |-fil_names_write_if_was_clean() +   |-mtr_t::Command::finish_write()   + + +测试场景#1 Drop Database (innodb_file_per_table=ON) +OFF代表MySQL是共享表空间,也就是所有库的数据都存放在一个ibdate1文件中;ON代表每个表的存储空间都是独立的。 + +ibd是MySQL数据文件、索引文件,二进制文件无法直接读取;frm是表结构文件,可以直接打开。如果innodb_file_per_table 无论是ON还是OFF,都会有这2个文件,区别只是innodb_file_per_table为ON的时候,数据时放在 .idb中,如果为OFF则放在ibdata1中。 + + + + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + +#######redo-log文件 +redo-log保存在innodb_log_group_home_dir参数指定的目录下,文件名为ib_logfile*;undo保存在共享表空间ibdata*文件中。 + +InnoDB的redo log可控制文件大小以及文件个数,分别通过innodb_log_file_size和innodb_log_files_in_group控制,总大小为两者之积。日志顺序写入,而且文件循环使用。 + +简单来说,InnoDB中的两个核心参数innodb_buffer_pool_size、innodb_log_file_size,分别定义了数据缓存和redo-log的大小,而后者的大小也决定了可以允许buffer中可以有多少脏页。当然,也不能因此就增大redo-log文件的大小,如果这样,可能会导致系统启动时Crash Recovery时间增大。 + + + + +LSN对应了日志文件的偏移量,为了减小故障恢复时间,引入了Checkpoint机制, + +InnoDB在启动时会自动检测InnoDB数据和事务日志是否一致,是否需要执行相应的操作???保证数据一致性;当然,故障恢复时间与事务日志的大小相关。 + + +checkpoint会将最近写入的LSN + + +主线程主要完成 purge、checkpoint、dirty pages flush 等操作。 + + + +Database was not shutdown normally! # InnoDB开始Crash Recovery{recv_init_crash_recovery_spaces()} +Starting crash recovery. + + + +1. 读取Checkpoint LSN +2. 从Checkpoint LSN开始向前遍历Redo Log File + 重做从Checkpoint LSN开始的所有Redo日志 +3. 重新构造系统崩溃时的事务 + Commit事务,等待Purge线程回收 + Prepare事务,由MySQL Server控制提交或者回滚(与Binlog 2PC相关) + Active事务,回滚 +4. 新建各种后台线程,Crash Recovery完成返回 + + +正常关闭时,会在flush redo log和脏页后,做一次完全同步的checkpoint,并将checkpoint的LSN写到第一个ibdata文件的第一个page中,详细可以参考fil_write_flushed_lsn()。 + + + + + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://tech.uc.cn/?p=716 + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=620 + +http://apprize.info/php/effective/6.html + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +数据库内核分享 + +https://www.slideshare.net/frogd/inno-db-15344119 + +检查保存到磁盘的最大checkpoint LSN与redo-log的LSN是否一致; + + +MySQL · 引擎特性 · InnoDB 崩溃恢复过程 + +http://mysql.taobao.org/monthly/2015/06/01/ + + + + + + + + + + + + + +https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in + + + +http://mysql.taobao.org/monthly/2015/12/01/ +http://hedengcheng.com/?p=148 +read_view +http://www.cnblogs.com/chenpingzhao/p/5065316.html +http://kabike.iteye.com/blog/1820553 +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ +http://mysqllover.com/?p=696 + +隔离级别 +详细可以查看row_search_mvcc()中的实现 + + +row_search_for_mysql() + |-row_search_no_mvcc() # 对于MySQL内部使用的表(用户不可见),不需要MVCC机制 + |-row_search_mvcc() + +row_search_no_mvcc()用于MySQL的内部表使用,通常是一些作为一个较大任务的中间结果存储,所以希望其可以尽快处理,因此不需要MVCC机制。 + + +innodb_force_recovery变量对应源码中的srv_force_recovery变量, + + + + +当innodb_fast_shutdown设置为0时,会导致purge一直工作近两个小时。????? + +从5.5版本开始,purge任务从主线程中独立出来;5.6开始支持多个purge线程,可以通过innodb_purge_threads变量控制。 + +purge后台线程的最大数量可以有32个,包括了一个coordinator线程,以及多个worker线程。 + +在innobase_start_or_create_for_mysql()函数中,会创建srv_purge_coordinator_thread以及srv_worker_thread线程。 + + +srv_purge_coordinator_thread() + |-srv_purge_coordinator_suspend() 如果不需要purge或者上次purge记录数为0,则暂停 + |-srv_purge_should_exit() 判断是否需要退出;fast_shutdown=0则等待所有purge操作完成 + |-srv_do_purge() 协调线程的主要工作,真正调用执行purge操作的函数 + | + |-trx_purge() 防止上次循环结束后又新的记录写入,此处不再使用worker线程 + | + |-trx_purge() 最后对history-list做一次清理,确保所有worker退出 + +srv_worker_thread() + + +最后一次做trx_purge()时,为了防止执行时间过程,批量操作时不再采用innodb_purge_batch_size(300)指定的值,而是采用20。 + + +InnoDB的数据组织方式采用聚簇索引,也就是索引组织表,而二级索引采用(索引键值,主键键值)组合来唯一确定一条记录。 +无论是聚簇索引,还是二级索引,每条记录都包含了一个DELETED-BIT位,用于标识该记录是否是删除记录;除此之外,聚簇索引还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR,分别表示产生当前记录项的事务ID以及指向当前记录的undo信息。 + + + +从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断???? + + +InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下: + +class ReadView { +private: + trx_id_t m_low_limit_id; // +}; + + +mtr_commit(struct mtr_t*) 提交一个mini-transaction,调用mtr_t::commit() + |-mtr_t::Command::execute() 写redo-log,将脏页添加到flush-list,并释放占用资源 + |-mtr_t::Command::prepare_write() 准备写入日志 + | |-fil_names_write_if_was_clean() + |-mtr_t::Command::finish_write() + + + + + + + + + + + + +妈的文件整理文件 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://www.cnblogs.com/liuhao/p/3714012.html + + +持续集成 https://www.zhihu.com/question/23444990 + + + + +buf_flush_batch + + + + + + + + + +FAQ系列 | 如何避免ibdata1文件大小暴涨 + +0、导读 + + 遇到InnoDB的共享表空间文件ibdata1文件大小暴增时,应该如何处理? + +1、问题背景 + +用MySQL/InnoDB的童鞋可能也会有过烦恼,不知道为什么原因,ibdata1文件莫名其妙的增大,不知道该如何让它缩回去,就跟30岁之后男人的肚腩一样,汗啊,可喜可贺的是我的肚腩还没长出来,hoho~ + +正式开始之前,我们要先知道ibdata1文件是干什么用的。 + +ibdata1文件是InnoDB存储引擎的共享表空间文件,该文件中主要存储着下面这些数据: + + data dictionary + double write buffer + insert buffer/change buffer + rollback segments + undo space + Foreign key constraint system tables + +另外,当选项 innodb_file_per_table = 0 时,在ibdata1文件中还需要存储 InnoDB 表数据&索引。ibdata1文件从5.6.7版本开始,默认大小是12MB,而在这之前默认大小是10MB,其相关选项是 innodb_data_file_path,比如我一般是这么设置的: + + innodb_data_file_path = ibdata1:1G:autoextend + +当然了,无论是否启用了 innodb_file_per_table = 1,ibdata1文件都必须存在,因为它必须存储上述 InnoDB 引擎所依赖&必须的数据,尤其是上面加粗标识的 rollback segments 和 undo space,它俩是引起 ibdata1 文件大小增加的最大原因,我们下面会详细说。 +2、原因分析 + +我们知道,InnoDB是支持MVCC的,它和ORACLE类似,采用 undo log、redo log来实现MVCC特性的。在事务中对一行数据进行修改时,InnoDB 会把这行数据的旧版本数据存储一份在undo log中,如果这时候有另一个事务又要修改这行数据,就又会把该事物最新可见的数据版本存储一份在undo log中,以此类推,如果该数据当前有N个事务要对其进行修改,就需要存储N份历史版本(和ORACLE略有不同的是,InnoDB的undo log不完全是物理block,主要是逻辑日志,这个可以查看 InnoDB 源码或其他相关资料)。这些 undo log 需要等待该事务结束后,并再次根据事务隔离级别所决定的对其他事务而言的可见性进行判断,确认是否可以将这些 undo log 删除掉,这个工作称为 purge(purge 工作不仅仅是删除过期不用的 undo log,还有其他,以后有机会再说)。 + +那么问题来了,如果当前有个事务中需要读取到大量数据的历史版本,而该事务因为某些原因无法今早提交或回滚,而该事务发起之后又有大量事务需要对这些数据进行修改,这些新事务产生的 undo log 就一直无法被删除掉,形成了堆积,这就是导致 ibdata1 文件大小增大最主要的原因之一。这种情况最经典的场景就是大量数据备份,因此我们建议把备份工作放在专用的 slave server 上,不要放在 master server 上。 + +另一种情况是,InnoDB的 purge 工作因为本次 file i/o 性能是在太差或其他的原因,一直无法及时把可以删除的 undo log 进行purge 从而形成堆积,这是导致 ibdata1 文件大小增大另一个最主要的原因。这种场景发生在服务器硬件配置比较弱,没有及时跟上业务发展而升级的情况。 + +比较少见的一种是在早期运行在32位系统的MySQL版本中存在bug,当发现待 purge 的 undo log 总量超过某个值时,purge 线程直接放弃抵抗,再也不进行 purge 了,这个问题在我们早期使用32位MySQL 5.0版本时遇到的比较多,我们曾经遇到这个文件涨到100多G的情况。后来我们费了很大功夫把这些实例都迁移到64位系统下,终于解决了这个问题。 + +最后一个是,选项 innodb_data_file_path 值一开始就没调整或者设置很小,这就必不可免导致 ibdata1 文件增大了。Percona官方提供的 my.cnf 参考文件中也一直没把这个值加大,让我百思不得其解,难道是为了像那个经常被我吐槽的xx那样,故意留个暗门,好方便后续帮客户进行优化吗?(我心理太阴暗了,不好不好~~) + +稍微总结下,导致ibdata1文件大小暴涨的原因有下面几个: + + 有大量并发事务,产生大量的undo log; + 有旧事务长时间未提交,产生大量旧undo log; + file i/o性能差,purge进度慢; + 初始化设置太小不够用; + 32-bit系统下有bug。 + +稍微题外话补充下,另一个热门数据库 PostgreSQL 的做法是把各个历史版本的数据 和 原数据表空间 存储在一起,所以不存在本案例的问题,也因此 PostgreSQL 的事务回滚会非常快,并且还需要定期做 vaccum 工作(具体可参见PostgreSQL的MVCC实现机制,我可能说的不是完全正确哈) +3、解决方法建议 + +看到上面的这些问题原因描述,有些同学可能觉得这个好办啊,对 ibdata1 文件大小进行收缩,回收表空间不就结了吗。悲剧的是,截止目前,InnoDB 还没有办法对 ibdata1 文件表空间进行回收/收缩,一旦 ibdata1 文件的肚子被搞大了,只能把数据先备份后恢复再次重新初始化实例才能恢复原先的大小,或者把依次把各个独立表空间文件备份恢复到一个新实例中,除此外,没什么更好的办法了。 + +当然了,这个问题也并不是不能防范,根据上面提到的原因,相应的建议对策是: + + 升级到5.6及以上(64-bit),采用独立undo表空间,5.6版本开始就支持独立的undo表空间了,再也不用担心会把 ibdata1 文件搞大; + 初始化设置时,把 ibdata1 文件至少设置为1GB以上; + 增加purge线程数,比如设置 innodb_purge_threads = 8; + 提高file i/o能力,该上SSD的赶紧上; + 事务及时提交,不要积压; + 默认打开autocommit = 1,避免忘了某个事务长时间未提交; + 检查开发框架,确认是否设置了 autocommit=0,记得在事务结束后都有显式提交或回滚。 + + + +关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :) + +最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠 + + + + +MySQL-5.7.7引入的一个系统库sys-schema,包含了一系列视图、函数和存储过程,主要是一些帮助MySQL用户分析问题和定位问题,可以方便查看哪些语句使用了临时表,哪个用户请求了最多的io,哪个线程占用了最多的内存,哪些索引是无用索引等。 + +其数据均来自performance schema和information schema中的统计信息。 + +MySQL 5.7.7 and higher includes the sys schema, a set of objects that helps DBAs and developers interpret data collected by the Performance Schema. sys schema objects can be used for typical tuning and diagnosis use cases. + +MySQL Server blog中有一个很好的比喻: + +For Linux users I like to compare performance_schema to /proc, and SYS to vmstat. + +也就是说,performance schema和information schema中提供了信息源,但是,没有很好的将这些信息组织成有用的信息,从而没有很好的发挥它们的作用。而sys schema使用performance schema和information schema中的信息,通过视图的方式给出解决实际问题的答案。 + +查看是否安装成功 +select * from sys.version; +查看类型 +select * from sys.schema_object_overview where db='sys'; +当然,也可以通过如下命令查看 +show full tables from sys +show function status where db = 'sys'; +show procedure status where db = 'sys' + +user/host资源占用情况 +SHOW TABLES FROM `sys` WHERE + `Tables_in_sys` LIKE 'user\_%' OR + `Tables_in_sys` LIKE 'host\_%' +IO资源使用,包括最近IO使用情况latest_file_io +SHOW TABLES LIKE 'io\_%' +schema相关,包括表、索引使用统计 +SHOW TABLES LIKE 'schema\_%' +等待事件统计 +SHOW TABLES LIKE 'wait%' +语句查看,包括出错、全表扫描、创建临时表、排序、空闲超过95% +SHOW TABLES LIKE 'statement%' +当前正在执行链接,也就是processlist +其它还有一些厂家的帮助函数,PS设置。 +https://www.slideshare.net/Leithal/the-mysql-sys-schema +http://mingxinglai.com/cn/2016/03/sys-schema/ +http://www.itpub.net/thread-2083877-1-1.html + +x$NAME保存的是原始数据,比较适合通过工具调用;而NAME表更适合阅读,比如使用命令行去查看。 + + +select digest,digest_text from performance_schema.events_statements_summary_by_digest\G +CALL ps_trace_statement_digest('891ec6860f98ba46d89dd20b0c03652c', 10, 0.1, TRUE, TRUE); +CALL ps_trace_thread(25, CONCAT('/tmp/stack-', REPLACE(NOW(), ' ', '-'), '.dot'), NULL, NULL, TRUE, TRUE, TRUE); + +优化器调优 +https://dev.mysql.com/doc/internals/en/optimizer-tracing.html + + +MySQL performance schema instrumentation interface(PSI) + +struct PFS_instr_class {}; 基类 + + +通过class page_id_t区分页, + +class page_id_t { +private: + ib_uint32_t m_space; 指定tablespace + ib_uint32_t m_page_no; 页的编号 + + + + + + +http://www.myexception.cn/database/511937.html +http://blog.csdn.net/taozhi20084525/article/details/17613785 +http://blogread.cn/it/article/5367 +http://mysqllover.com/?p=303 +http://www.cnblogs.com/chenpingzhao/p/5107480.html ??? +https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/innodb-recovery-tablespace-discovery.html +http://mysqllover.com/?p=1214 + + + +[mysqld] +innodb_data_file_path = ibdata1:12M;ibdata2:12M:autoextend + + + + + + + + + +http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html +http://www.maomao365.com/?p=3043 + +UNIX 域套接字用于在同一台机器上进程之间通信,与因特网域套接字类似,不过 UNIX Socket 更加高效,只需要复制数据,不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。 + +提供了流和数据报两种接口,可以使用面向网络的域套接字接口,也可使用 socketpair() 函数。 +https://akaedu.github.io/book/ch37s04.html +http://www.cnblogs.com/nufangrensheng/p/3569416.html +http://blog.csdn.net/jnu_simba/article/details/9079359 +http://liulixiaoyao.blog.51cto.com/1361095/533469/ +http://www.infoq.com/cn/news/2012/12/twemproxy + +typesdb 中如果不存在则会直接报 `Dataset not found` 错,然后退出,通过 type 做作为 key 保存在 AVL 中。 + +twemproxy后端支持多个server pool,为每个server pool分配一个监听端口用于接收客户端的连接。 + +客户端和 proxy 建立连接 (CLI_CONN),然后发送请求,proxy 读取数据并放入到 req_msg 中,消息的 owner 为 (CLI_CONN); +Proxy 根据策略从 server pool 中选取一个 server 并且建立连接 (SVR_CONN) 然后准备开始转发; +将 req_msg 的指针放入 CLI_CONN 的 output 队列中,同时放入 SVR_CONN 的 input 队列中,然后触发 SVR_CONN 的写事件; +SVR_CONN 的写回调函数从 input 队列中取出 req_msg 发送给对应的后端 server,并将 req_msg 放入 server_conn 的 output 队列; +收到从后端服务器发送的响应消息 rsp_msg 后,依次调用 rsp_filter(判断消息是否为空、是否消息可以不用回复等)和rsp_forward; +将 req_msg 从 SVR_CONN 的 output 队列中取出,建立 req_msg 和 rsp_msg 的对应关系(通过msg的peer字段),通过req_msg的owner找到client_conn,然后启动client_conn的写事件; +client_conn的写回调函数从client_conn的output队列中取出req_msg,然后通过peer字段拿到对应的rsp_msg,将其发出去。 +http://www.cnblogs.com/foxmailed/p/3623817.html +http://www.yeolar.com/note/topics/twemproxy/ +http://blog.sina.com.cn/s/blog_4f8ea2ef0101iill.html +vim-minimal不需要删除 http://www.madirish.net/294 http://blog.itpub.net/28588485/viewspace-755403/ + + +#include +int main(int argc, char *argv[]) +{ + unsigned long long x = 0; + printf("g: %g, f: %f\n", x, x); + printf("sizeof: float(%d), double(%d), unsigned long long(%d)\n", + sizeof(float), sizeof(double), sizeof(unsigned long long)); + return 0; +} +for (int i = 0; i < ds->ds_num; ++i) { + INFO(">>>>>>> %s %s %s %f\n", vl->type, vl->type_instance, ds->ds[i].name, vl->values[i].gauge); +} + + + + +惊群问题讨论 +http://www.voidcn.com/blog/liujiyong7/article/p-377809.html +Heap数据结构(栈) +http://www.cnblogs.com/gaochundong/p/binary_heap.html +http://www.cnblogs.com/skywang12345/p/3610187.html + + +AVL数 +https://courses.cs.washington.edu/courses/cse373/06sp/handouts/lecture12.pdf +https://www.cise.ufl.edu/~nemo/cop3530/AVL-Tree-Rotations.pdf +http://www.cnblogs.com/zhoujinyi/p/6497231.html + +https://dev.mysql.com/doc/refman/5.7/en/backup-policy.html +https://dev.mysql.com/doc/refman/5.7/en/point-in-time-recovery.html + +https://www.unixhot.com/page/ops + + + + + + +非常经典的《Linux平台下的漏洞分析入门 》 +https://github.com/1u4nx/Exploit-Exercises-Nebula +原文在这里 +https://www.mattandreko.com/ + +http://hustcat.github.io/iostats/ +http://ykrocku.github.io/blog/2014/04/11/diskstats/ +http://www.udpwork.com/item/12931.html + +FIXME: + linux-monitor-io.html +/proc/diskstats 中包括了主设备号、次设备号和设备名称,剩余的各个字段的含义简单列举如下,详细可以查看内核文档 [I/O statistics fields](https://www.kernel.org/doc/Documentation/iostats.txt) 。 + +可以通过 grep diskstats 找到对应内核源码实现在 diskstats_show()@block/genhd.c 中。 + +获取源码 diskstats_show() + struct disk_stats 。 + +可以看到是通过 part_round_stats() 函数获取每个磁盘的最新统计信息,通过 struct hd_struct 中的 struct disk_stats *dkstats 结构体保存,然后利用 part_stat_read() 函数统计各个 CPU 的值 (如果是多核)。 + + +在 Bash 编程时,经常需要切换目录,可以通过 pushd、popd、dirs 命令切换目录。 + +pushd 切换到参数指定的目录,并把原目录和当前目录压入到一个虚拟的堆栈中,不加参数则在最近两个目录间切换; +popd 弹出堆栈中最近的目录; +dirs 列出当前堆栈中保存的目录列表; + -v 在目录前添加编号,每行显示一个目录; + -c 清空栈; + +切换目录时,会将上次目录保存在 $OLDPWD 变量中,与 "-" 相同,可以通过 cd - 切换回上次的目录。 + + + + +安全渗透所需的工具 +https://wizardforcel.gitbooks.io/daxueba-kali-linux-tutorial/content/2.html + + + + + + + + + + +http://fengyuzaitu.blog.51cto.com/5218690/1616268 +http://www.runoob.com/python/os-statvfs.html +http://blog.csdn.net/papiping/article/details/6980573 +http://blog.csdn.net/hepeng597/article/details/8925506 + + + + + + + + +shell版本号比较 +http://blog.topspeedsnail.com/archives/3999 +https://www.netfilter.org/documentation/HOWTO/NAT-HOWTO-6.html +man 3 yum.conf 确认下YUM配置文件中的变量信息 +https://unix.stackexchange.com/questions/19701/yum-how-can-i-view-variables-like-releasever-basearch-yum0 + + +int lt_dlinit (void); + 初始化,在使用前调用,可以多次调用,正常返回 0 ; +const char * lt_dlerror (void); + 返回最近一次可读的错误原因,如果没有错误返回 NULL; +void * lt_dlsym (lt_dlhandle handle, const char *name); + 返回指向 name 模块的指针,如果没有找到则返回 NULL 。 +lt_dlhandle lt_dlopen (const char *filename); + 加载失败返回 NULL,多次加载会返回相同的值; +int lt_dlclose (lt_dlhandle handle); + 模块的应用次数减一,当减到 0 时会自动卸载;成功返回 0 。 + +https://github.com/carpedm20/awesome-hacking +http://jamyy.us.to/blog/2014/01/5800.html + + + + +Page Cache +https://www.thomas-krenn.com/en/wiki/Linux_Page_Cache_Basics + +Page Cache, the Affair Between Memory and Files + +http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/ + +https://www.quora.com/What-is-the-major-difference-between-the-buffer-cache-and-the-page-cache + +从free到page cache +http://www.cnblogs.com/hustcat/archive/2011/10/27/2226995.html + + + +getaddrinfo() +http://www.cnblogs.com/cxz2009/archive/2010/11/19/1881693.html + + +#define unlikely(x) __builtin_expect((x),0) +http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html + + +libcurl使用 +http://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html + + + + +https://lwn.net/Articles/584225/ +https://en.wikipedia.org/wiki/Stack_buffer_overflow#Stack_canaries +https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html +https://outflux.net/blog/archives/2014/01/27/fstack-protector-strong/ +-pipe + 从源码生成可执行文件一般需要四个步骤,并且还会产生中间文件,该参数用于配置实用PIPE,一些平台会失败,不过 GNU 不受影响。 +-fexceptions + 打开异常处理,该选项会生成必要的代码来处理异常的抛出和捕获,对于 C++ 等会触发异常的语言来说,默认都会指定该选项。所生成的代码不会造成性能损失,但会造成尺寸上的损失。因此,如果想要编译不使用异常的 C++ 代码,可能需要指定选项 -fno-exceptions 。 +-Wall -Werror -O2 -g --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic -DLT_LAZY_OR_NOW="RTLD_LAZY|RTLD_GLOBAL" + + + + +关于Linux内核很不错的介绍 +http://duartes.org/gustavo/blog/ + + + + + + +编程时常有 Client 和 Server 需要各自得到对方 IP 和 Port 的需求,此时就可以通过 getsockname() 和 getpeername() 获取。 + +python -c ' +import sys +import socket +s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect(("www.host.com", 80)) +print s.getsockname() +s.close()' + + +Python判断IP有效性 +https://gist.github.com/youngsterxyf/5088954 + + + +安全渗透工具集 +https://wizardforcel.gitbooks.io/daxueba-kali-linux-tutorial/content/2.html + +hostname获取方式,在启动时通过 1) global_option_get() 配置文件获取;2) gethostname();3) getaddrinfo()。 + +#include +int gethostname(char *name, size_t len); + 返回本地主机的标准主机名;正常返回 0 否则返回 -1,错误码保存在 errno 中。 + +#include +#include +struct hostent *gethostbyname(const char *name); + 用域名或主机名获取IP地址,注意只支持IPv4;正常返回一个 struct hostent 结构,否则返回 NULL。 + +#include +int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result); + hostname: 一个主机名或者地址串,IPv4的点分十进制串或者IPv6的16进制串; + service : 服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等; + hints : 可以为空,用于指定返回的类型信息,例如,服务支持 TCP/UDP 那么,可以设置 ai_socktype 为 SOCK_DGRAM 只返回 UDP 信息; + result : 返回的结果。 + 返回 0 成功。 + +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; // IP地址,需要通过inet_ntop()转换为IP字符串 + char *ai_canonname; // 返回的主机名 + struct addrinfo *ai_next; +}; +http://blog.csdn.net/a_ran/article/details/41871437 + + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); + 将类型为af的网络地址结构src,转换成主机序的字符串形式,存放在长度为cnt的字符串中。返回指向dst的一个指针。如果函数调用错误,返回值是NULL。 + + + +对于不信任的组件建议使用后者,因为 ldd 可能会加载后显示依赖的库,从而导致安全问题。 + +----- 查看依赖的库 +$ ldd /usr/bin/ssh +$ objdump -p /usr/bin/ssh | grep NEEDED + +----- 运行程序加载的库 +# pldd $(pidof mysqld) + + +# pldd $(pidof uagent) + + +VIRT, Virtual Memory Size @ + 该任务的总的虚拟内存,包括了 code、data、shared libraries、换出到磁盘的页、已经映射但是没有使用的页。 +USED, Memory in Use @ + 包括了已使用的物理内存 RES ,以及换出到磁盘的内存 SWAP。 +%MEM, Memory Usage(RES) @ + 当前任务使用的内存与整个物理内存的占比。 +CODE, Code Size + 可执行代码占用的物理内存数,也被称为 Text Resident Set, TRS。 +DATA, Data+Stack Size + 除了代码之外的物理内存占用数,也就是 Data Resident Set, DRS 。 +RES, Resident Memory Size @ + 驻留在物理内存中的使用量。 +SHR, Shared Memory Size @ + 包括了共享内存以及共享库的数据。 + +SWAP, Swapped Size + 换出到磁盘的内存。 +nMaj, nMin, nDRT + + +RES = CODE + DATA???? DATA太大了,为什么 + +====== ps +DRS, Data Resident Set <=> top(DATA) !!! + 除了代码之外的物理内存占用数。 +RSS, Resident Set Size <=> top(RES) + 物理内存使用数。 +TRS, Text Resident Set <=> top(CODE) !!! + 代码在内存中的占用数。 +VSZ, Virtual Memory Size <=> top(VIRT) <=> pmap -d(mapped) + 虚拟内存的大小。 + +RES(top) 和 RSS(ps) 实际上读取的是 /proc/$(pidof process)/stat 或者 /proc/$(pidof process)/status statm。 +pmap -d $(pidof uagent) +pmap -x $(pidof uagent) +ps -o pid,pmem,drs,trs,rss,vsz Hp `pidof uagent` + +另外,cgtop 中显示的内存与什么相关???? +ps(TRS) 和 top(CODE) 的值不相同。 + +http://blog.csdn.net/u011547375/article/details/9851455 +https://stackoverflow.com/questions/7594548/res-code-data-in-the-output-information-of-the-top-command-why +https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/36669.pdf +https://landley.net/kdocs/ols/2010/ols2010-pages-245-254.pdf +LDD 跟 PMAP 加载的库不同? + +awk 'BEGIN{sum=0};{if($3~/x/) {sum+=$2}};END{print sum}' /tmp/1 + +procs_refresh + +Top用于查看Linux系统下进程信息,有时候需要选择显示那些列,以及按照某一列进行排序。查询整理如下: + + +top 除了默认的列之外,可以选择需要显示的列,操作如下: + +----- 选择需要显示的列 +1) 按 f 键进入选择界面;2) 方向键选择需要的列;3) 通过空格选择需要显示的列。 + +列显示位置调整: +执行top命令后,按 o 键,选择要调整位置的列(如K:CUP Usageage),按动一下大写K则显示位置往上调整,按动一下小写K则显示位置往下调整。 + +列排序: +执行top命令后,按 shift + f(小写),进入选择排序列页面,再按要排序的列的代表字母即可; + +注册信号处理函数 + +setsid() +pidfile_create() + +https://www.ibm.com/support/knowledgecenter/zh/ssw_aix_61/com.ibm.aix.genprogc/ie_prog_4lex_yacc.htm + +flex 通过 yylval 将数据传递给 yacc;如果在 yacc 中使用了 ```%union``` ,那么各个条件的目的变量使用 yyval 。 + +https://www.howtoforge.com/storing-files-directories-in-memory-with-tmpfs + + + +meminfo详解 +https://lwn.net/Articles/28345/ + +ps的SIZE以及RSS不含部分的内存统计,所以要比pmap -d统计的RSS小。 +The SIZE and RSS fields don't count some parts of a process including the page tables, kernel stack, struct thread_info +https://techtalk.intersec.com/2013/07/memory-part-2-understanding-process-memory/ +http://tldp.org/LDP/tlk/mm/memory.html +http://tldp.org/LDP/khg/HyperNews/get/memory/linuxmm.html +https://lwn.net/Articles/230975/ +https://gist.github.com/CMCDragonkai/10ab53654b2aa6ce55c11cfc5b2432a4 +https://yq.aliyun.com/ziliao/75375 +http://elinux.org/Runtime_Memory_Measurement +https://access.redhat.com/security/vulnerabilities/stackguard +http://events.linuxfoundation.org/sites/events/files/slides/elc_2016_mem_0.pdf +http://blog.csdn.net/lijzheng/article/details/23618365 +https://yq.aliyun.com/articles/54405 +https://stackoverflow.com/questions/31328349/stack-memory-management-in-linux +/post/mysql-parser.html +yyset_in() 设置入口 + + +pmap -d $(pidof uagent) +pmap -x $(pidof uagent) + +top -Hp $(pidof uagent) +ps -o pid,pmem,drs,trs,rss,vsz Hp `pidof uagent` +/proc/$(pidof uagent)/stat +/proc/$(pidof uagent)/status +/proc/$(pidof uagent)/maps +VmRSS、VmSize + +$ ps aux|grep /usr/bin/X|grep -v grep | awk '{print $2}' # 得出X server 的 pid ... +1076 +$ cat /proc/1076/stat | awk '{print $23 / 1024}' +139012 +$ cat /proc/1076/status | grep -i vmsize +VmSize: 106516 kB +VmSize = memory + memory-mapped hardware (e.g. video card memory). + +kmap 是用来建立映射的,映射后返回了被映射的高端内存在内核的线性地址 +https://www.zhihu.com/question/30338816 +http://blog.csdn.net/gatieme/article/details/52705142 +http://www.cnblogs.com/zhiliao112/p/4251221.html +http://way4ever.com/?p=236 +awk统计Linux最常用命令 +http://www.ha97.com/3980.html +awk使用技巧 +http://blog.csdn.net/ultrani/article/details/6750434 +http://blog.csdn.net/u011204847/article/details/51205031 ***** +http://ustb80.blog.51cto.com/6139482/1051310 + + +关于smaps的详细介绍 +https://jameshunt.us/writings/smaps.html +$ cat /proc/self/smaps 相比maps显示更详细信息 +$ cat /proc/self/maps +address perms offset dev inode pathname +7f571af7a000-7f571af7d000 ---p 00000000 00:00 0 +7f571af7d000-7f571b080000 rw-p 00000000 00:00 0 [stack:4714] +7f571b0ac000-7f571b0ad000 r--p 00021000 08:01 1838227 /usr/lib/ld-2.21.so +7ffe49dbd000-7ffe49dbf000 r-xp 00000000 00:00 0 [vdso] + +列说明: + starting address - ending address + permissions + r : read + w : write + x : execute + s : shared + p : private (copy on write) + offset : 如果不是file,则为0; + device : 如果是file,则是file所在device的major and monior device number,否则为00:00; + inode : 如果是file,则是file的inode number,否则为0; + pathname : 有几种情况; + file absolute path + [stack] the stack of the main process + [stack:1001] the stack of the thread with tid 1001 + [heap] + [vdso] - virtual dynamic shared object, the kernel system call handler + 空白 -通常都是mmap创建的,用于其他一些用途的,比如共享内存 + + +df -h +ls /dev/XXX -alh +echo $((0x0a)) + +print "Backed by file:\n"; +print " RO data r-- $mapped_rodata\n"; +print " Unreadable --- $mapped_unreadable\n"; 共享库同时存在???? +print " Unknown $mapped_unknown\n"; +print "Anonymous:\n"; +print " Writable code (stack) rwx $writable_code\n"; +print " Data (malloc, mmap) rw- $data\n"; +print " RO data r-- $rodata\n"; +print " Unreadable --- $unreadable\n"; +print " Unknown $unbacked_unknown\n"; + +16进制求和,都是16进制 +awk --non-decimal-data '{sum=($1 + $2); printf("0x%x %s\n", sum,$3)}' +strtonum("0x" $1) +echo $(( 16#a36b )) +echo "obase=2;256"|bc ibase base + + +print "Backed by file:\n"; +print " Unreadable --- $mapped_unreadable\n"; 共享库同时存在???? +print " Unknown $mapped_unknown\n"; +print "Anonymous:\n"; +print " Unreadable --- $unreadable\n"; +print " Unknown $unbacked_unknown\n"; + +代码 +r-x 代码,包括程序(File)、共享库(File)、vdso(2Pages)、vsyscall(1Page) +rwx 没有,Backed by file: Write/Exec (jump tables); Anonymous: Writable code (stack) +r-- 程序中的只读数据,如字符串,包括程序(File)、共享库(File) +rw- 可读写变量,如全局变量;包括程序(File)、共享库(File)、stack、heap、匿名映射 + +静态数据、全局变量将保存在 ELF 的 .data 段中。 +与smaps相关,以及一些实例 +https://jameshunt.us/writings/smaps.html + + +各共享库的代码段,存放着二进制可执行的机器指令,是由kernel把该库ELF文件的代码段map到虚存空间; +各共享库的数据段,存放着程序执行所需的全局变量,是由kernel把ELF文件的数据段map到虚存空间; + +用户代码段,存放着二进制形式的可执行的机器指令,是由kernel把ELF文件的代码段map到虚存空间; +用户数据段之上是代码段,存放着程序执行所需的全局变量,是由kernel把ELF文件的数据段map到虚存空间; + +用户数据段之下是堆(heap),当且仅当malloc调用时存在,是由kernel把匿名内存map到虚存空间,堆则在程序中没有调用malloc的情况下不存在; +用户数据段之下是栈(stack),作为进程的临时数据区,是由kernel把匿名内存map到虚存空间,栈空间的增长方向是从高地址到低地址。 + +https://wiki.wxwidgets.org/Valgrind_Suppression_File_Howto + + +另外,可以通过 ldd 查看对应的映射地址,在实际映射到物理内存时,会添加随机的变量,不过如上的各个共享库的地址是相同的。 + +可以通过 echo $(( 0x00007f194de48000 - 0x00007f194dc2c000)) 计算差值。 + + +maps 文件对应了内核中的 show_map() + +show_map() + |-show_map_vma() + +address perms offset dev inode pathname + +http://duartes.org/gustavo/blog/post/how-the-kernel-manages-your-memory/ + + +主要是anon中的rw属性导致 +cat /proc/$(pidof uagent)/maps | grep stack | wc -l + + + +Clean_pages 自从映射之后没有被修改的页; +Dirty_pages 反之; +RSS 包括了共享以及私有,Shared_Clean+Shared_Dirty、Private_Clean+Private_Dirty +PSS (Proportional set size) 包括了所有的私有页 (Private Pages) 以及共享页的平均值。例如,一个进程有100K的私有页,与一个进程有500K的共享页,与四个进程有500K的共享页,那么 PSS=100K+(500K/2)+(500K/5)=450K +USS (Unique set size) 私有页的和。 + +awk -f test.awk /proc/$(pidof uagent)/maps +#! /bin/awk -f +BEGIN { + mapped_executable = 0 + mapped_wrexec = 0 + mapped_rodata = 0 + mapped_rwdata = 0 + mapped_unreadable = 0 + mapped_unknown = 0 + writable_code = 0 + data = 0 + rodata = 0 + unreadable = 0 + vdso = 0 + unbacked_unknown = 0 +} + +{ + split($1, addr, "-") + pages = (strtonum("0x" addr[2]) - strtonum("0x" addr[1]))/4096 + if ( $4 == "00:00") { + if ( $2 ~ /rwx/ ) { writable_code += pages } + else if ( $2 ~ /rw-/ ) { data += pages } + else if ( $2 ~ /r-x/ ) { vdso += pages } + else if ( $2 ~ /r--/ ) { rodata += pages } + else if ( $2 ~ /---/ ) { unreadable += pages } + else { unbacked_unknown += pages } + } else { + if ( $2 ~ /rwx/ ) { mapped_wrexec += pages } + else if ( $2 ~ /rw-/ ) { mapped_rwdata += pages } + else if ( $2 ~ /r-x/ ) { mapped_executable += pages } + else if ( $2 ~ /r--/ ) { mapped_rodata += pages } + else if ( $2 ~ /---/ ) { mapped_unreadable += pages } + else { mapped_unknown += pages } + } +} +END { + printf ("Backed by file:\n") + printf (" Write/Exec (jump tables) rwx %d\n", mapped_wrexec) + printf (" Data rw- %d\n", mapped_rwdata) + printf (" Executable r-x %d\n", mapped_executable) + printf (" RO data r-- %d\n", mapped_rodata) + printf (" Unreadable --- %d\n", mapped_unreadable) + printf (" Unknown %d\n", mapped_unknown) + printf ("Anonymous:\n") + printf (" Writable code (stack) rwx %d\n", writable_code) + printf (" Data (malloc, mmap) rw- %d\n", data) + printf (" vdso, vsyscall r-x %d\n", vdso) + printf (" RO data r-- %d\n", rodata) + printf (" Unreadable --- %d\n", unreadable) + printf (" Unknown %d\n", unbacked_unknown) +} + +pmap -x $(pidof uagent) > /tmp/1 +awk -f test.awk /tmp/1 +#! /bin/awk -f +BEGIN { + lib_dirty_rx = 0 + lib_dirty_rw = 0 + lib_dirty_r = 0 + lib_dirty_unknown = 0 + lib_rss_rx = 0 + lib_rss_rw = 0 + lib_rss_r = 0 + lib_rss_unknown = 0 + + uagent_dirty_rx = 0 + uagent_dirty_rw = 0 + uagent_dirty_r = 0 + uagent_dirty_unknown = 0 + uagent_rss_rx = 0 + uagent_rss_rw = 0 + uagent_rss_r = 0 + uagent_rss_unknown = 0 + + anon_dirty_rw = 0 + anon_dirty_rx = 0 + anon_dirty_unknown = 0 + anon_dirty_r = 0 + anon_rss_rw = 0 + anon_rss_rx = 0 + anon_rss_unknown = 0 + anon_rss_r = 0 + + count = 0 + actual = 0 +} + +$NF ~ /(^lib|^ld)/ { + if ( $5 ~ /r-x/ ) { + lib_rss_rx += $3 + lib_dirty_rx += $4 + } else if ( $5 ~ /rw-/ ) { + lib_rss_rw += $3 + lib_dirty_rw += $4 + } else if ( $5 ~ /r--/ ) { + lib_rss_r += $3 + lib_dirty_r += $4 + } else { + lib_rss_unknown += $3 + lib_dirty_unknown += $4 + } + count += $3 +} +$NF ~ /([a-zA-Z]+\.so$|^uagent\>)/ { + if ( $5 ~ /r-x/ ) { + uagent_rss_rx += $3 + uagent_dirty_rx += $4 + } else if ( $5 ~ /rw-/ ) { + uagent_rss_rw += $3 + uagent_dirty_rw += $4 + } else if ( $5 ~ /r--/ ) { + uagent_rss_r += $3 + uagent_dirty_r += $4 + } else { + uagent_rss_unknown += $3 + uagent_dirty_unknown += $4 + } + count += $3 +} +$NF ~ /^]$/ { + if ( $5 ~ /r-x/ ) { + anon_rss_rx += $3 + anon_dirty_rx += $4 + } else if ( $5 ~ /rw-/ ) { + anon_rss_rw += $3 + anon_dirty_rw += $4 + } else if ( $5 ~ /r--/ ) { + anon_rss_r += $3 + anon_dirty_r += $4 + } else { + anon_rss_unknown += $3 + anon_dirty_unknown += $4 + } + count += $3 +} +$1 ~ /^total\>/ { + actual = $4 +} +END { + printf ("Libraries info:\n") + printf (" Perm RSS Dirty\n") + printf (" r-x %5d %5d\n", lib_rss_rx, lib_dirty_rx) + printf (" rw- %5d %5d\n", lib_rss_rw, lib_dirty_rw) + printf (" r-- %5d %5d\n", lib_rss_r, lib_dirty_r) + printf (" Unknown %5d %5d\n", lib_rss_unknown, lib_dirty_unknown) + + printf ("Uagent info:\n") + printf (" Perm RSS Dirty\n") + printf (" r-x %5d %5d\n", uagent_rss_rx, uagent_dirty_rx) + printf (" rw- %5d %5d\n", uagent_rss_rw, uagent_dirty_rw) + printf (" r-- %5d %5d\n", uagent_rss_r, uagent_dirty_r) + printf (" Unknown %5d %5d\n", uagent_rss_unknown, uagent_dirty_unknown) + + printf ("Anon info:\n") + printf (" Perm RSS Dirty\n") + printf (" r-x %5d %5d\n", anon_rss_rx, anon_dirty_rx) + printf (" rw- %5d %5d\n", anon_rss_rw, anon_dirty_rw) + printf (" r-- %5d %5d\n", anon_rss_r, anon_dirty_r) + printf (" Unknown %5d %5d\n", anon_rss_unknown, anon_dirty_unknown) + + printf ("\nCount: %d Actual: %d\n", count, actual) +} + + + + + + +可以通过mprotect设置内存的属性 +https://linux.die.net/man/2/mprotect +Memory protection keys +https://lwn.net/Articles/643797/ +Memory Protection and ASLR on Linux +https://eklitzke.org/memory-protection-and-aslr + +ES Collectd插件 +https://www.elastic.co/guide/en/logstash/current/plugins-codecs-collectd.html + + + + +真随机数等生成 +http://www.cnblogs.com/bigship/archive/2010/04/04/1704228.html + +在打印时,如果使用了 size_t 类型,那么通过 ```%d``` 打印将会打印一个告警,可以通过如下方式修改,也就是添加 ```z``` 描述。 + +size_t x = ...; +ssize_t y = ...; +printf("%zu\n", x); // prints as unsigned decimal +printf("%zx\n", x); // prints as hex +printf("%zd\n", y); // prints as signed decimal + +/proc/iomem 保存物理地址的映射情况,每行代表一个资源 (地址范围和资源名),其中可用物理内存的资源名为 "System RAM" ,在内核中通过 insert_resource() 这个API注册到 iomem_resource 这颗资源树上。 + +例如,如下的内容: + +01200000-0188b446 : Kernel code +0188b447-01bae6ff : Kernel data +01c33000-01dbbfff : Kernel bss + +这些地址范围都是基于物理地址的,在 ```setup_arch()@arch/x86/kernel/setup.c``` 中通过如下方式注册。 + +max_pfn = e820_end_of_ram_pfn(); + code_resource.start = __pa_symbol(_text); + code_resource.end = __pa_symbol(_etext)-1; + insert_resource(&iomem_resource, &code_resource); + +linux虚拟地址转物理地址 +http://luodw.cc/2016/02/17/address/ +Linux内存管理 +http://gityuan.com/2015/10/30/kernel-memory/ +/proc/iomem和/proc/ioports +http://blog.csdn.net/ysbj123/article/details/51088644 +port地址空间和memory地址空间是两个分别编址的空间,都是从0地址开始 +port地址也可以映射到memory空间中来,前提是硬件必须支持MMIO +iomem—I/O映射方式的I/O端口和内存映射方式的I/O端口 +http://www.cnblogs.com/b2tang/archive/2009/07/07/1518175.html + + +协程 +https://github.com/Tencent/libco + + +#if FOO < BAR +#error "This section will only work on UNIX systems" +#endif +http://hbprotoss.github.io/posts/cyu-yan-hong-de-te-shu-yong-fa-he-ji-ge-keng.html +https://linuxtoy.org/archives/pass.html +https://en.m.wikipedia.org/wiki/Padding_(cryptography) + + + + + + sed -e 's/collectd/\1/' * +sed只取匹配部分 +http://mosquito.blog.51cto.com/2973374/1072249 +http://blog.sina.com.cn/s/blog_470ab86f010115kv.html + +通过sed只显示匹配行或者某些行 +----- 显示1,10行 +$ sed -n '1,10p' filename +----- 显示第10行 +$ sed -n '10p' filename +----- 显示某些匹配行 +$ sed -n '/This/p' filename +sed -n '/\/p' * +sed -i 's/\fe_req_per_sec proxy_inc_fe_req_ctr() 请求速率 + req_rate_max 请求限制速率 + req_tot 目前为止总的请求数 +Response: (只对HTTP代理有效) + 'hrsp_1xx': ('response_1xx', 'derive'), + 'hrsp_2xx': ('response_2xx', 'derive'), + 'hrsp_3xx': ('response_3xx', 'derive'), + 'hrsp_4xx': ('response_4xx', 'derive'), + 'hrsp_5xx': ('response_5xx', 'derive'), + 'hrsp_other': ('response_other', 'derive'), + +>>>>>backend<<<<< +Time: + qtime (v1.5+) 过去1024个请求在队里中的平均等待时间 + rtime (v1.5+) 过去1024个请求在队里中的平均响应时间 + + + + + + + + + +http://savannah.nongnu.org/projects/nss-mysql +https://github.com/NigelCunningham/pam-MySQL +http://lanxianting.blog.51cto.com/7394580/1767113 +https://stackoverflow.com/questions/7271939/warning-ignoring-return-value-of-scanf-declared-with-attribute-warn-unused-r +https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html +https://stackoverflow.com/questions/30813452/how-to-ignore-all-warnings-in-c +http://www.jianshu.com/p/7e84a33b46e9 +https://github.com/flike/kingshard/blob/master/doc/KingDoc/kingshard_admin_api.md 微博 +__attribute__((warn_unused_result)) + + +协程 +http://blog.163.com/shu_wang/blog/static/1721704112016114113331412 +https://stackoverflow.com/questions/28977302/how-do-stackless-coroutines-differ-from-stackful-coroutines +http://www.infoq.com/cn/articles/CplusStyleCorourtine-At-Wechat +https://www.iamle.com/archives/1865.html +https://github.com/mcxiaoke/mqtt +http://www.infoq.com/cn/articles/fine-tuned-haproxy-to-achieve-concurrent-ssl-connections?utm_campaign=rightbar_v2&utm_source=infoq&utm_medium=articles_link&utm_content=link_text + + + +JMX(Java Management Extensions) 是一个为应用程序植入管理功能的框架,是一套标准的代理和服务,实际上,用户可以在任何 Java 应用程序中使用这些代理和服务实现管理。 +http://blog.csdn.net/derekjiang/article/details/4532375 +http://tomcat.apache.org/tomcat-6.0-doc/monitoring.html#Enabling_JMX_Remote +http://comeonbabye.iteye.com/blog/1463104 +https://visualvm.github.io/ +http://blog.csdn.net/kingzone_2008/article/details/50865350 + + +Buddy 和 Slub 是 Linux 内核中的内存管理算法。Buddy 防止内存的 "外碎片",即防止内存块越分越小,而不能满足大块内存分配的需求。Slub 防止内存的 "内碎片",即尽量按请求的大小分配内存块,防止内存块使用上的浪费。https://github.com/chenfangxin/buddy_slub + +https://stackoverflow.com/questions/9873061/how-to-set-the-source-port-in-the-udp-socket-in-c +http://www.binarytides.com/programming-udp-sockets-c-linux/ +https://www.cyberciti.biz/faq/linux-unix-open-ports/ +https://www.cs.cmu.edu/afs/cs/academic/class/15213-f99/www/class26/udpclient.c +https://www.cs.rutgers.edu/~pxk/417/notes/sockets/udp.html +https://www.digitalocean.com/community/tutorials/how-to-use-netcat-to-establish-and-test-tcp-and-udp-connections-on-a-vps + + + + + +http://my.fit.edu/~vkepuska/ece3551/ADI_Speedway_Golden/Blackfin%20Speedway%20Manuals/LwIP/socket-api/setsockopt_exp.html + +socktop(systap使用) + + + + +Socket INTR的处理 +http://blog.csdn.net/SUKHOI27SMK/article/details/43021081 + +http://www.tldp.org/HOWTO/html_single/C++-dlopen/ + +UDP-based Data Transfer Protocol +http://udt.sourceforge.net/ +https://github.com/securesocketfunneling/udt +http://blog.leanote.com/post/caasi/Reliable-UDP-3 + +https://github.com/lsalzman/enet + +https://askubuntu.com/questions/714503/regular-expressions-vs-filename-globbing + + +1. #注释 +2. 变量:使用set命令显式定义及赋值,在非if语句中,使用${}引用,if中直接使用变量名引用;后续的set命令会清理变量原来的值; +3. command (args ...) #命令不分大小写,参数使用空格分隔,使用双引号引起参数中空格 +4. set(var a;b;c) <=> set(var a b c) #定义变量var并赋值为a;b;c这样一个string list +5. Add_executable(${var}) <=> Add_executable(a b c) #变量使用${xxx}引用 + +----- 条件语句 +if(var) # 其中空 0 N No OFF FALSE 视为假,NOT 后为真 +else()/elseif() +endif(var) + +7. 循环语句 + +Set(VAR a b c) + +Foreach(f ${VAR}) …Endforeach(f) + +8. 循环语句 + +WHILE() … ENDWHILE() + + + +INCLUDE_DIRECTORIES(include) # 本地的include目录设置 +LINK_LIBRARIES('m') # 添加库依赖,等价于命令行中的-lm参数 + + + +GTest 实际上不建议直接使用二进制文件,而是建议从源码开始编译。https://github.com/google/googletest/blob/master/googletest/docs/FAQ.md +如果要使用二进制包,那么可以使用如下方式进行配置。 +find_package(PkgConfig) +pkg_check_modules(GTEST REQUIRED gtest>=1.7.0) +pkg_check_modules(GMOCK REQUIRED gmock>=1.7.0) + +include_directories( + ${GTEST_INCLUDE_DIRS} + ${GMOCK_INCLUDE_DIRS} +) +http://www.yeolar.com/note/2014/12/16/cmake-how-to-find-libraries/ +http://blog.csdn.net/netnote/article/details/4051620 + +find_package(Threads REQUIRED) # 使用内置模块查找thread库支持 + + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(uagent) + +ADD_SUBDIRECTORY(librudp ) +INCLUDE_DIRECTORIES(include) + +option(WITH_UNIT_TESTS "Compile with unit tests" OFF) + + + +https://github.com/sohutv/cachecloud Redis监控管理 +https://github.com/apache/incubator-superset 牛掰的项目管理 +https://github.com/huichen/wukong 悟空搜索引擎 +https://github.com/sylnsfar/qrcode 动态二维码生成 +https://github.com/hellysmile/fake-useragent 伪装浏览器身份 +https://github.com/jwasham/coding-interview-university 谷歌面试题 +https://github.com/Tencent/libco 腾讯协程库 +https://github.com/xtaci/kcptun 最快的UDP传输 +https://github.com/reorx/httpstat 图形显示http处理耗时 +https://github.com/ajermakovics/jvm-mon JVM监控 +https://github.com/stampery/mongoaudit MongoDB审计 +https://github.com/alexazhou/VeryNginx +https://github.com/helloqingfeng/Awsome-Front-End-learning-resource +https://github.com/shimohq/chinese-programmer-wrong-pronunciation +https://github.com/egonelbre/gophers +https://github.com/dbcli/mycli +https://github.com/nextcloud/server +https://github.com/SpaceVim/SpaceVim +https://github.com/nlohmann/json +https://github.com/alisaifee/flask-limiter +https://github.com/nicolargo/glances +https://github.com/nonstriater/Learn-Algorithms +https://github.com/ZuzooVn/machine-learning-for-software-engineers +https://github.com/jumpserver/jumpserver +https://github.com/FredWe/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md +https://github.com/drduh/macOS-Security-and-Privacy-Guide +https://github.com/chrislgarry/Apollo-11 +https://github.com/taizilongxu/interview_python +https://github.com/FallibleInc/security-guide-for-developers +https://github.com/SamyPesse/How-to-Make-a-Computer-Operating-System +https://github.com/yiminghe/learning-react + + +FIXME: + /post/python-modules.html + Python 包管理 + http://www.jianshu.com/p/9acc85d0ff16 + http://xiaorui.cc/2014/09/20/%E4%BD%BF%E7%94%A8hashring%E5%AE%9E%E7%8E%B0python%E4%B8%8B%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7hash/ +360 +https://github.com/flike/kingshard +https://github.com/Qihoo360/Atlas +https://tech.meituan.com/dbproxy-pr.html +http://www.cnblogs.com/wunaozai/p/3955156.html +https://tech.meituan.com/ + +http://blog.csdn.net/factor2000/article/details/3929816 +http://www.tldp.org/HOWTO/html_single/C++-dlopen/ + + +schema_integrity_check_failed +一般是由于 mnesia 数据库的问题导致,简单粗暴的方式是直接删除。 + + + +MTU +Maximum Transmission Unit + ifconfig eth0 mtu number +/etc/sysconfig/network-scripts/ifcfg-eth0 +MTU=1000 +IPV6_MTU=1000 +http://www.361way.com/linux-mtu-jumbo-frames/4055.html +http://www.microhowto.info/howto/change_the_mtu_of_a_network_interface.html +http://www.cnblogs.com/liu-yao/p/5678161.html +http://blog.csdn.net/anzhsoft/article/details/19563091 + + + + + +首先通过 crontab(crontab.c) 完成任务的编辑,然后通过 poke_daemon() 通知 crond 程序,实际上就是通过 utime() 修改 SPOOL_DIR 目录的访问和修改时间。而在 crond(cron.c) 程序中,会通过 inotify 机制接收,然后进行更新。 + +http://blog.csdn.net/rain_qingtian/article/details/11008779 + +https://github.com/DaveGamble/cJSON + +语法规则可以参考 [JSON: The Fat-Free Alternative to XML](yuhttp://www.json.org/fatfree.html) 。 + +parse_value() 正式的语法解析 + +https://github.com/staticlibs/ccronexpr + +American Fuzzy Lop, AFL 是一种开源的模糊测试器,由谷歌的 Michal Zalewski 开发。可以在源码编译时添加,或者使用 QEMU 模式,也就是 QEMU-(User Mode) ,在执行时注入部分代码进行测试。http://lcamtuf.coredump.cx/afl/ +https://github.com/google/syzkaller +https://github.com/xxg1413/fuzzer/tree/master/iFuzz +https://stfpeak.github.io/2017/06/12/AFL-Cautions/ +http://bobao.360.cn/news/detail/3354.html +http://www.jianshu.com/p/015c471f5a9d +http://ele7enxxh.com/Use-AFL-For-Stagefright-Fuzzing-On-Linux.html +http://www.freebuf.com/articles/system/133210.html +http://www.hackdig.com/07/hack-24522.htm +http://aes.jypc.org/?p=38207 +https://fuzzing-project.org/tutorial3.html +afl-fuzz -i afl_in -o afl_out ./binutils/readelf -a @@ +afl-gcc afl-clang-fast + +http://blog.codingnow.com/2017/07/float_inconsistence.html#more + + +:verbose imap +http://www.cnblogs.com/acbingo/p/4757275.html +http://blog.guorongfei.com/2016/12/03/vim-ultisnipt-google-c-cpp-header-gurad/ +http://vimzijun.net/2016/10/30/ultisnip/ +http://www.linuxidc.com/Linux/2016-11/137665.htm +https://segmentfault.com/q/1010000000610373 + +http://liulixiaoyao.blog.51cto.com/1361095/814329 + + + + + + + + + + + + + +https://casatwy.com/pthreadde-ge-chong-tong-bu-ji-zhi.html +http://blog.csdn.net/willib/article/details/32942189 + +https://github.com/beego/beedoc/blob/master/zh-CN/module/grace.md +https://www.nginx.com/resources/wiki/start/topics/tutorials/commandline/ +http://blog.csdn.net/brainkick/article/details/7192144 +http://shzhangji.com/cnblogs/2012/12/23/nginx-live-upgrade/ + +http://www.freebuf.com/sectool/119680.html +http://tonybai.com/2011/04/21/apply-style-check-to-c-code/ +https://github.com/dspinellis/cqmetrics + +VGC、RATS、Source Insight + + +测试版本 + + +core +gcov +CPP-Check +Flawfinder + + +静态安全扫描 flawfinder、RATS、ITS4、VCG、CPPLint、SPlint + +Python: Pychecker、Pylint、RATS + + +python -m SimpleHTTPServer + +## flawfinder + +一个 Python 写的程序,用于扫描代码,然后在规则库 (c_ruleset) 中查找符合规则的场景。 + +源码可以直接从 [www.dwheeler.com](https://www.dwheeler.com/flawfinder/) 上下载,安装方式可以查看 README 文件,也就是如下命令。 + +$ tar xvzf FILENAME.tar.gz # Uncompress distribution file +$ cd flawfinder-* # cd into it. +# make prefix=/usr install # Install in /usr + +该工具只针对单个语句进行词法分析,不检查上下文,不分析数据类型和数据流;检查运行时可能存在的问题,比如内存泄露;然后会根据规则库给出代码建议。这也就意味着会有部分的误报,不过因为使用简单,仍不失为一个不错的静态检测工具。 + +检查可以直接指定文件或者目录,工具会自动查看所有的 C/C++ 文件,如果是 patch (diff -u、svn diff、git diff) 添加参数 --patch/-P 即可。严重等级从 0 到 5 依次增加,而且会标示出 [Common Weakness Enumeration, CWE](https://cwe.mitre.org/data/) 对应。 + +检查时会读取 ruleset 中的规则,然后如果匹配 (hit) 则将匹配数据保存到 hitlist 中, + +### 常见操作 + +1. 重点检查与外部不可信用户的交互程序,先确保这部分程序无异常; +2. 如果已经审计的函数,可以通过 ```// Flawfinder: ignore``` 或者 ```/* Flawfinder: ignore */``` 减少异常输出,为了兼容,也可以使用 ```ITS4``` 或者 ```RATS``` 替换 ```Flawfinder```; + + +--inputs/-I + 只检查从外部用户(不可信)获取数据的函数; +--neverignore/-n + 默认可以通过上述的方式忽略标记的行,通过该参数用于强制检测所有的代码; +--savehitlist, --loadhitlist, --diffhitlist + 用于保存、加载、比较hitlist; +--minlevel=NUMBER + 指定最小的错误汇报级别; + +--quiet/-Q + 默认会在检测时打印检查了哪些文件,通过该选项可以关闭,通常用于格式化输出检测; +--dataonly/-D + 不显示header和footer,可以配合--quiet参数只显示数据; +--singleline/-S + 检测结果默认会多行显示,该参数指定一行显示; +--immediate/-i + 默认在全部文件检测完之后,进行排序,然后显示最终的结果,该参数可以在监测到异常后立即显示; + + +----- 检查所有的代码,即使已经标记为ignore的代码 +$ flawfinder --neverignore src +----- 可以通过如下命令输出,以供其它自动化工具使用 +$ flawfinder -QD src +$ flawfinder -QDSC src +----- 检查代码只汇报CWE-120或者CWE-126 +$ flawfinder --regex "CWE-120|CWE-126" src/ + + +/* RATS: ignore */ + + + +uagent 调试, +export UAGENT_TRACE="yes" + +flawfinder -Q --minlevel=5 src | less + + + +int lcc_connect(const char *address, lcc_connection_t **ret_con); +功能: + 建立指向address的socket链接,通过ret_con返回链接信息; +入参: + address socket地址,如/usr/var/run/uagent.sock、unix:/usr/var/run/uagent.sock; + ret_con 返回的已经建立好的链接; +返回值: + -1 入参异常,或者没有内存; + 0 正常返回; + +int lcc_disconnect(lcc_connection_t *c); +功能: + 关闭链接,释放资源; + +## coverage +http://blog.csdn.net/livelylittlefish/article/details/6448885 +编译链接时需要修改配置选项。 + +* 编译的时候,增加 -fprofile-arcs -ftest-coverage 或者 –coverage; +* 链接的时候,增加 -fprofile-arcs 或者 –lgcov; +* 打开–g3 选项,去掉-O2以上级别的代码优化选项,否则编译器会对代码做一些优化,例如行合并,从而影响行覆盖率结果。 + +ifeq ($(coverage), yes) +CFLAGS += -fprofile-arcs -ftest-coverage -g3 +LDFLAGS += -fprofile-arcs -ftest-coverage +endif + +如下是测试步骤。 +----- 1. 编译源码,此时每个文件都会生成一个*.gcno文件 +$ make coverage=yes +----- 2. 运行,运行之后会生成 *.gcda 文件 +$ ./helloworld +----- 3.1 可以通过如下命令生成单个文件的覆盖率,生成的是文本文件*.gcov +$ gcov helloworld.c + +除了使用 gcov 之外,还可以通过 lcov 查看覆盖率,简单说下 *.gcov 的文件格式。 + + -: 2:#include 非有效行 + -: 3:#include + ... ... + 148: 71: if (n == NULL) 调用次数 +#####: 72: return (0); 未调用 + + +简单介绍下代码覆盖率的常见术语。 + + +主要是基本块(Basic Block),基本块图(Basic Block Graph),行覆盖率(line coverage), 分支覆盖率(branch coverage)等。 + +##### 基本块 +这里可以把基本块看成一行整体的代码,基本块内的代码是线性的,要不全部运行,要不都不运行,其详细解释如下: +A basic block is a sequence of instructions with only entry and only one exit. If any one of the instructions are executed, they will all be executed, and in sequence from first to last. + + + + + + 基本块图(Basic Block Graph),基本块的最后一条语句一般都要跳转,否则后面一条语句也会被计算为基本块的一部分。 如果跳转语句是有条件的,就产生了一个分支(arc),该基本块就有两个基本块作为目的地。如果把每个基本块当作一个节点,那么一个函数中的所有基本块就构成了一个有向图,称之为基本块图(Basic Block Graph)。且只要知道图中部分BB或arc的执行次数就可以推算出所有的BB和所有的arc的执行次数; + 打桩,意思是在有效的基本块之间增加计数器,计算该基本块被运行的次数;打桩的位置都是在基本块图的有效边上; + +##### 行覆盖率 +就是源代码有效行数与被执行的代码行的比率; + +##### 分支覆盖率 +有判定语句的地方都会出现 2 个分支,整个程序经过的分支与所有分支的比率是分支覆盖率。注意,与条件覆盖率(condition coverage)有细微差别,条件覆盖率在判定语句的组合上有更细的划分。 + +### gcc/g++ 编译选项 + +如上所述,在编译完成后会生成 *.gcno 文件,在运行正常结束后生成 *.gcda 数据文件,然后通过 gcov 工具查看结果。 + +--ftest-coverage + 让编译器生成与源代码同名的*.gcno文件 (note file),含有重建基本块依赖图和将源代码关联至基本块的必要信息; +--fprofile-arcs + 让编译器静态注入对每个源代码行关联的计数器进行操作的代码,并在链接阶段链入静态库libgcov.a,其中包含在程序正常结束时生成*.gcda文件的逻辑; + +可以通过源码解析来说明到底这 2 个选项做了什么,命令如下: +g++ -c -o hello.s hello.c -g -Wall -S +g++ -c -o hello_c.s hello.c -g -Wall –coverage -S +vimdiff hello.s hello_c.s + + +1. 覆盖率的结果只有被测试到的文件会被显示,并非所有被编译的代码都被作为覆盖率的分母 + +实际上,可以看到整个覆盖率的产生的过程是4个步骤的流程,一般都通过外围脚本,或者makefile/shell/python来把整个过程自动化。2个思路去解决这个问题,都是通过外围的伪装。第一个,就是修改lcov的 app.info ,中间文件,找到其他的文件与覆盖率信息的地方,结合makefile,把所有被编译过的源程序检查是否存于 app.info 中,如果没有,增加进去。第二个伪装,是伪装 *.gcda,没有一些源码覆盖率信息的原因就是该文件没有被调用到,没有响应的gcda文件产生。 + + +2. 后台进程的覆盖率数据收集; + + +其实上述覆盖率信息的产生,不仅可以针对单元测试,对于功能测试同样适用。但功能测试,一般linux下c/c++都是实现了某个Daemon进程,而覆盖率产生的条件是程序需要正常退出,即用户代码调用 exit 正常结束时,gcov_exit 函数才得到调用,其继续调用 __gcov_flush 函数输出统计数据到 *.gcda 文件中。同样2个思路可以解决这个问题, + +第一,给被测程序增加一个 signal handler,拦截 SIGHUP、SIGINT、SIGQUIT、SIGTERM 等常见强制退出信号,并在 signal handler 中主动调用 exit 或 __gcov_flush 函数输出统计结果。但这个需要修改被测程序。这个也是我们之前的通用做法。但参加过清无同学的一个讲座后,发现了下面第二种更好的方法。 + +第二,借用动态库预加载技术和 gcc 扩展的 constructor 属性,我们可以将 signalhandler 和其注册过程都封装到一个独立的动态库中,并在预加载动态库时实现信号拦截注册。这样,就可以简单地通过如下命令行来实现异常退出时的统计结果输出了。 + + +### lcov + +用于生成 html 格式的报告。 + +yum install --enablerepo=epel lcov perl-GD + +----- 1. 生成*.info文件 +$ lcov -d . -o 'hello_test.info' -t ‘Hello test’ -b . -c +参数解析: + -d 指定目录 +----- 2. 生成html,-o指定输出目录,可以通过HTTP服务器查看了 +$ genhtml -o result hello_test.info + + + +## 静态检查 + +http://www.freebuf.com/sectool/119680.html + +cppcheck、Splint(Secure Programming Lint) + +### cppcheck + +直接从 [github cppcheck](https://github.com/danmar/cppcheck) 下载,然后通过 make && make install 编译安装即可。 + +cppcheck -j 3 --force --enable=all src/* + +--force + 如果#ifdef的宏定义过多,则cppcheck只检查部分 +-j + 检查线程的个数,用于并发检查; +--enable + 指定当前的检查级别,可选的参数有all,style,information等; +--inconclusive + 默认只会打印一些确认的错误,通过该参数配置异常的都打印; + + + +### Splint + +http://www.cnblogs.com/bangerlee/archive/2011/09/07/2166593.html + + +## 圈复杂度 (Cyclomatic complexity) + +OCLint(Mac) cppncss SourceMonitor(Windows) + +常用概念介绍如下: + +* Non Commenting Source Statements, NCSS 去除注释的有效代码行; +* Cyclomatic Complexity Number, CCN 圈复杂度。 + +同样,一个函数的 CCN 意味着需要多少个测试案例来覆盖其不同的路径,当 CCN 发生很大波动或者 CCN 很高的代码片段被变更时,意味改动引入缺陷风险高,一般最好小于 10 。 + + + +Findbugs (compiled code analysis) +PMD (static code analysis) + +### SourceMonitor + +http://www.campwoodsw.com/sourcemonitor.html + +### cppncss + +很简单的计算圈复杂度的工具,java。 + + +## 内存检测 + +Valgrind + + +HAVE_LIBGEN_H 1 +HAVE_FNMATCH_H 1 + +----- 当前目录下生成buildbot_master目录,以及配置文件master.cfg.sample +$ buildbot create-master buildbot_master + + +$ cd buildbot_master && mv master.cfg.sample master.cfg +$ buildbot checkconfig master.cfg +c['buildbotNetUsageData'] = None + +----- 运行 +$ buildbot start buildbot_master +# 查看日志 +tail -f master/twistd.log + + + + +https://github.com/nodejs/http-parser +https://github.com/shellphish/how2heap +https://github.com/DhavalKapil/heap-exploitation + +https://github.com/maidsafe-archive/MaidSafe-RUDP/wiki +RTP https://tools.ietf.org/html/rfc3550 +UDT http://udt.sourceforge.net/ +https://github.com/dorkbox/UDT + +https://github.com/greensky00/avltree +http://blog.csdn.net/q376420785/article/details/8286292 +http://xstarcd.github.io/wiki/shell/UDP_Hole_Punching.html + + + +http://www.wowotech.net/timer_subsystem/tick-device-layer.html +https://www.w3.org/CGI/ +https://github.com/HardySimpson/zlog + + + + + + + + + + + +main() + |-handle_read() + |-httpd_start_request() + |-really_start_request() + |-cgi() + |-cgi_child() + |-make_envp() + |-build_env() + + + +onion_http_new() 会将onion_http_read_ready()赋值给read_ready + +onion_http_read_ready() + |-onion_request_process() + + +onion_url_new() + |-onion_url_handler() + +onion_listen_point_accept() 在listen端口上出现时调用 + +onion_listen_point_read_ready() + + + + + + + + + +cJSON_CreateObject() 创建新的对象,设置对应的type类型 + |-cJSON_New_Item() 新申请cJSON结构内存,并初始化为0 +cJSON_CreateString() 和cJSON_CreateRaw()函数调用相同,只是设置的类型不同 + |-cJSON_New_Item() + |-cJSON_strdup() 创建对象后会将字符串复制一份 +cJSON_Print() + |-print() + |-print_value() + + +typedef struct cJSON { + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + struct cJSON *child; 对于Array类型,则会作为链表头 + int type; 类型,包括了String、Number、Raw等 + char *valuestring; 如果是String或者Raw时使用 + int valueint; 这个已经取消,使用valuedouble替换,为了兼容未删除 + double valuedouble; 如果是Number时使用 + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +Invalid、True、False、NULL、Object、Array 通过 type 区分, + +cJSON_Parse() + |-cJSON_ParseWithOpts() + |-cJSON_New_Item() + |-parse_value() 根据不同的字段进行解析 + +cJSON_Duplicate() +cJSON_Minify() +???cJSON_Compare() + +cJSON_Parse() +cJSON_Print() 按照可阅读的格式打印,方便阅读,一般用于交互 +cJSON_PrintUnformatted() 最小化打印,一般用于格式发送 +cJSON_PrintBuffered() +cJSON_PrintPreallocated() + +string trim +https://stackoverflow.com/questions/122616/how-do-i-trim-leading-trailing-whitespace-in-a-standard-way + + +scanf 中一种很少见但很有用的转换字符 `[...]` 和 `[ ^...]` 。 + +#include +int main() +{ + char strings[100]; + scanf("%[1234567890]", strings); + printf("%s", strings); + return 0; +} + +运行,输入 `1234werew` 后,结果是 `1234` ,也就是说,如果输入的字符属于方括号内字符串中某个字符,那么就提取该字符;如果一经发现不属于就结束提取。 + +这就是 ANSI C 增加的一种新特性,称为扫描集 (scanset),由一对方括号中的一串字符定义,左方括号前必须缀以百分号,通过 `^` 表示补集。 + +注意,其中必须至少包含一个字符,否则非法,如 `%[]` 和 `%[^]` 是非法的。 + +%[a-z] 读取在 a-z 之间的字符串 + char s[]="hello, my friend"; // 注意: 逗号在不a-z之间 + sscanf(s, "%[a-z]", string); // string=hello +%[^a-z] 读取不在 a-z 之间的字符串,如果碰到a-z之间的字符则停止 + char s[]="HELLOkitty"; + sscanf( s, "%[^a-z]", string); // string=HELLO +%*[^=] 前面带 * 号表示不保存变量,跳过符合条件的字符串。 + char s[]="notepad=1.0.0.1001" ; + char szfilename [32] = "" ; + int i = sscanf( s, "%*[^=]", szfilename ) ; +// szfilename=NULL,因为没保存 + +int i = sscanf( s, "%*[^=]=%s", szfilename ) ; +// szfilename=1.0.0.1001 + + +%40c 读取40个字符 + + +%[^=] 读取字符串直到碰到’=’号,’^’后面可以带更多字符,如: + char s[]="notepad=1.0.0.1001" ; + char szfilename [32] = "" ; + int i = sscanf( s, "%[^=]", szfilename ) ; + // szfilename=notepad + 如果参数格式是:%[^=:] ,那么也可以从 notepad:1.0.0.1001读取notepad +http://www.cnblogs.com/mafly/p/postman.html + +http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html +http://www.blogjava.net/javagrass/archive/2011/07/12/354134.html +https://meekrosoft.wordpress.com/2009/11/09/unit-testing-c-code-with-the-googletest-framework/ + + +https://en.wikipedia.org/wiki/Network_Time_Protocol + +Linux 内核通过 adjtime() 或者 ntp_adjtime() 来进行时钟的同步,ntptime +http://jfcarter.net/~jimc/documents/bugfix/12-ntp-wont-sync.html +http://libev.schmorp.de/bench.c +https://stackoverflow.com/questions/14621261/using-libev-with-multiple-threads +https://curl.haxx.se/libcurl/c/evhiperfifo.html +https://github.com/HardySimpson/zlog +http://kagachipg.blogspot.com/2013/10/multi-thread-in-libev.html + + + + + + + + + + + + + + +conn_config() + |-port_collect_listening  如果配置了ListeningPorts变量则设置为1 + |-conn_get_port_entry()   对于LocalPort和RemotePort参数,如果存在则设置否则创建 + |-port_collect_total      如果配置了AllPortsSummary变量则设置为1 +conn_read() + |- +#### + +$ cat << EOF > request.txt +GET / HTTP/1.1 +Host: 127.1 +EOF +$ cat request.txt | openssl s_client -connect 127.1:443 + + +printf 'GET / HTTP/1.1\r\nHost: github.com\r\n\r\n' | ncat --ssl github.com 443 + +----- 发送系统日志内容 +$ ncat -l -p 6500 | tee -a copy.out | tar -zx -C $(mktemp -d) +$ (tar -zc -C /var/log; tail -f /var/log/syslog) | ncat 127.1 6500 + +----- 使用SSL传输 +$ ncat -l -p 6500 --ssl --ssl-cert /etc/ssl/host.crt \ +    --ssl-key /etc/ssl/host.key > out.tgz +$ tar -zc ~ | ncat --ssl 127.1 6500 + +----- 使用UDP协议 +$ ncat -l -p 6500 --udp > out.tgz +$ tar -zc ~ | ncat --udp machineb 6500 + +----- 使用SCTP +$ ncat --sctp -l -p 6500 > out.tgz +$ tar -zc ~ | ncat --sctp machineb 6500 +给系统添加根证书 +http://manuals.gfi.com/en/kerio/connect/content/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html +https://segmentfault.com/a/1190000002569859 +CentOS 会保存在 /etc/ssl/certs/ 目录下, + +--ssl                  Connect or listen with SSL +--ssl-cert             Specify SSL certificate file (PEM) for listening +--ssl-key              Specify SSL private key (PEM) for listening +--ssl-verify           Verify trust and domain name of certificates +--ssl-trustfile        PEM file containing trusted SSL certificates + +http://blog.csdn.net/ljy1988123/article/details/51424162 +http://blog.csdn.net/younger_china/article/details/72081779 +http://blog.csdn.net/yusiguyuan/article/details/48265205 + +SSL Certificate File 文件中包含了一个 X.509 证书,实际上也就是加密用的公钥,而 SSL Certificate Key File 文件中是公钥对应的私钥,在进行安全传输时就需要这对密钥。有的程序是将两者放在一起,如一些 Java 程序;有的则会分开存储,如 Apache 。 + +一般在申请了证书之后,如通过 GoDaddy,会提供上述的两个文件。 + +如果服务端只使用了上述的两个文件,那么实际上客户端并不知道这个证书是谁颁发的;不过一般来说没有太大问题,因为客户端会保存很多的 CA 证书,包括中间证书以及根证书。如果要直接指定证书的依赖关系,可以通过 SSLCertificateChainFile 参数指定。 + +Nginx https配置 +https://fatesinger.com/75967 +https://imququ.com/post/my-nginx-conf-for-security.html + + +tail  -> coreutils +tailf -> util-linux + +Linux Shell man 命令详细介绍 +http://blog.jobbole.com/93404/ +http://www.lai18.com/content/1010397.html + +网络监控 +https://stackoverflow.com/questions/614795/simulate-delayed-and-dropped-packets-on-linux + +The f_frsize value is the actual minimum allocation unit of the +filesystem, while the f_bsize is the block size that would lead to +most efficient use of the disk with io calls.  All of the block counts +are in terms of f_frsize, since it is the actual allocation unit size. + The BSD manpages are a bit more informative on this function than the +POSIX ones. + +https://blog.blahgeek.com/glibc-and-symbol-versioning/ +http://www.runoob.com/linux/linux-comm-indent.html +http://riemann.io/quickstart.html +http://blog.csdn.net/c80486/article/details/45066439 +http://www.hzrcj.org.cn/personnel/pd01/findda_qc01 +https://github.com/mkirchner/tcping/blob/master/tcping.c + +C格式化检查 +sparse + +indent                                \ + --ignore-profile                  \    不读取indent的配置文件 + --k-and-r-style                   \    指定使用Kernighan&Ritchie的格式 + --indent-level8                   \    缩进多少字符,如果为tab的整数倍,用tab来缩进,否则用空格填充 + --tab-size8                       \    tab大小为8 + --swallow-optional-blank-lines    \    删除多余的空白行 + --line-length130                  \    设置每行的长度 + --no-space-after-casts            \    不要在cast后添加一个空格 + --space-special-semicolon         \    若for或while区段只有一行时,在分号前加上空格 + --else-endif-column1              \    将注释置于else与elseif右侧 +    --use-tabs                        \    使用tab做缩进 + --blank-lines-after-procedures    \    函数结束后加空行 + --blank-lines-after-declarations  \    声明结束后加空行 +    load.c + +find -type f -regextype posix-egrep -regex ".*(~|\.bak)$" -exec ls -alh {} \; + +NAN 一种是 中提供的默认值,也可以自定义宏,如下 + +#define NAN          (0.0 / 0.0) +#define isnan(f)     ((f) != (f)) +#define isfinite(f)  (((f) - (f)) == 0.0) +#define isinf(f)     (!isfinite(f) && !isnan(f)) + +http://zh.cppreference.com/w/c/numeric/math/fpclassify + +其中使用 `isnan()` 时,`FLT_EVAL_METHOD` 将被忽略。 + +FIXME: +https://jin-yang.github.io/post/collectd-source-code.html + |   | | | | |-FORMAT_VL()                    ← 实际上是调用format_name()将vl中的值生成标示符 +             |-pthread_mutex_lock() + |   | | | | |-c_avl_get()                    ← 利用上述标示符获取cache_entry_t,在此会缓存最近的一次采集数据 +             |-uc_insert() 如果不存在则插入,直接解锁退出 +    |-合法性检查,上次的采集时间应该小于本次时间 +    |-根据不同的类型进行检查<<>>,计算方法详见如下 + |   | | | | |... ...                         ← 会根据不同的类型进行处理,例如DS_TYPE_GAUGE + |   | | | | |-uc_check_range()               ← 检查是否在指定的范围内 + +values_raw   保存原始数据的值 +values_gauge 会按照不同的类型进行计算,其中计算规则如下 + + + + + + + +git diff / +----- 查看stage中的diff +git diff --cached/--staged +http://perthcharles.github.io/2015/08/25/clean-commit-log-before-push/ +https://github.com/chenzhiwei/linux/tree/master/git +https://ddnode.com/2015/04/14/git-modify-remote-responsity-url.html + +通过amend修改之后,需要使用--force强制推送该分支。 +git push --force origin feature/ping:feature/ping + +etcd +https://tonydeng.github.io/2015/10/19/etcd-application-scenarios/ +https://tonydeng.github.io/2015/11/24/etcd-the-first-using/ +http://cizixs.com/2016/08/02/intro-to-etcd +http://debugo.com/using-etcd/ +curl +http://blog.likewise.org/2011/08/brushing-up-on-curl/ +http://www.ruanyifeng.com/blog/2011/09/curl.html +http://www.cnblogs.com/gbyukg/p/3326825.html +https://stackoverflow.com/questions/27368952/linux-best-way-in-two-way-ipc-in-c +http://www.cnblogs.com/zhang-shijie/p/5439210.html +正则表达式 +https://www.zhihu.com/question/27434493 + + +以 `CHECK_SYMBOL_EXISTS()` 宏为例,对于 CentOS,在 `/usr/share/cmakeX/Modules` 中存在 `CheckSymbolExists.cmake` 模板,可以直接查看相关宏的定义;其它类似模板同样可以进行相应的检查。 + +ss -tan |awk 'NR>1{++S[$1]}END{for (a in S) print a,S[a]}' && ./tcpstatus + +https://github.com/schweikert/fping +https://github.com/octo/liboping + + +https://www.typora.io/#windows +json-handle markdown edit chrome浏览器 +http://blog.csdn.net/fandroid/article/details/45787423 +进程通讯 +http://blog.csdn.net/21aspnet/article/details/7479469 + +https://github.com/TeamStuQ/skill-map + +## 网络监控 + +Interface /proc/net/dev +TCPConns +PowerDNS +IPtables +netlink + +命令 +ethtool +   ethtool -i eth0 +netstat + +libev压测 +http://libev.schmorp.de/bench.c +spark经典 +https://aiyanbo.gitbooks.io/spark-programming-guide-zh-cn/content/index.html +https://github.com/lw-lin/CoolplaySpark/blob/master/Spark%20Streaming%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E7%B3%BB%E5%88%97/readme.md +小型数据库 +lmdb +cdb  https://github.com/rmind/libcdb +Kyoto Cabinet   http://fallabs.com/kyotocabinet/ +https://github.com/symisc/unqlite +https://github.com/numetriclabz/awesome-db +https://github.com/gstrauss/mcdb +https://github.com/tklauser/inotail/blob/master/inotail.c +https://github.com/adamierymenko/kissdb + + + +Futex +http://www.cnblogs.com/bbqzsl/p/6814031.html +http://www.jianshu.com/p/570a61f08e27 +http://blog.csdn.net/Javadino/article/details/2891385 +http://blog-kingshaohua.rhcloud.com/archives/84 +http://blog.csdn.net/michael_r_chang/article/details/30717763 +http://www.anscen.cn/article1.html +http://kouucocu.lofter.com/post/1cdb8c4b_50f62fe +http://blog.sina.com.cn/s/blog_e59371cc0102v29b.html +https://bg2bkk.github.io/post/futex%E5%92%8Clinux%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E6%9C%BA%E5%88%B6/ +http://kexianda.info/2017/08/17/%E5%B9%B6%E5%8F%91%E7%B3%BB%E5%88%97-5-%E4%BB%8EAQS%E5%88%B0futex%E4%B8%89-glibc-NPTL-%E7%9A%84mutex-cond%E5%AE%9E%E7%8E%B0/ + +https://github.com/Hack-with-Github/Awesome-Hacking +NTPL生产者消费者模型 +http://cis.poly.edu/cs3224a/Code/ProducerConsumerUsingPthreads.c + + +多线程编程,其中有很多DESIGN_XXX.txt的文档,甚至包括了Systemtap的使用,其底层用到的是系统提供的 futex_XXX() 调用。 +https://github.com/lattera/glibc/tree/master/nptl +https://en.wikipedia.org/wiki/List_of_C%2B%2B_multi-threading_libraries + +浅谈C++ Multithreading Programming +http://dreamrunner.org/blog/2014/08/07/C-multithreading-programming/ +Introduction to Parallel Computing +https://computing.llnl.gov/tutorials/parallel_comp/ +解剖 Mutex +https://techsingular.org/2012/01/05/%E8%A7%A3%E5%89%96-mutex/ +Pthreads并行编程之spin lock与mutex性能对比分析 +http://www.parallellabs.com/2010/01/31/pthreads-programming-spin-lock-vs-mutex-performance-analysis/ +Linux线程浅析 +http://blog.csdn.net/qq_29924041/article/details/69213248 + +LinuxThreads VS. NPTL +https://www.ibm.com/developerworks/cn/linux/l-threading.html +http://pauillac.inria.fr/~xleroy/linuxthreads/ + +FUTEX简析,也可以通过man 7 futex man 2 futex 查看 +http://blog.sina.com.cn/s/blog_e59371cc0102v29b.html +futex and userspace thread syncronization (gnu/linux glibc/nptl) analysis +http://cottidianus.livejournal.com/325955.html +原始论文 +https://www.kernel.org/doc/ols/2002/ols2002-pages-479-495.pdf +进程资源 +http://liaoph.com/inux-process-management/ + +http://www.cnblogs.com/big-xuyue/p/4098578.html + +BMON Ncurse编程 +tcpconns +conn_read() + |-conn_reset_port_entry() + |-conn_read_netlink() + | |-conn_handle_ports() + |-conn_handle_line() +   |-conn_handle_ports() + +ping +Host    会新建一个hostlist_t对象 +SourceAddress   ping_source +Device          ping_device,要求OPING库的版本大于1.3 +TTL             ping_ttl,要求0me_metas[txn->mt_txnid & 1]; + +也就是用本次操作的事务 id 取最低位,后面解释这么使用的原因。 + +main() + |-mdb_env_create() + |-mdb_env_set_maxreaders() + |-mdb_env_set_mapsize() + |-mdb_env_open() + | |-mdb_fname_init() + | |-pthread_mutex_init() + | |-mdb_fopen() + | |-mdb_env_setup_locks() + | | |-mdb_fopen() + | | |-mdb_env_excl_lock() + | |-mdb_env_open2() + | |-mdb_env_read_header() 尝试从头部读取信息,如果是第一次创建,则会调用如下函数 + | |-mdb_env_init_meta0() 第一次创建 + | |-mdb_env_init_meta() 第一次创建时同样需要初始化 + | |-mdb_env_map() 调用mmap进行映射 + |-mdb_txn_begin() 开启事务,允许嵌套 + |-mdb_txn_renew0() + |-mdb_dbi_open() + |-mdb_put() + |-mdb_cursor_put() 最复杂的函数处理 + |-mdb_txn_commit() + |-mdb_env_stat() +mdb_get + +#ifndef LOG_ERR +/* NOTE: please keep consistent with */ +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* (used) critical conditions, print stack message and quit, nomemory */ +#define LOG_ERR 3 /* (used) error conditions */ +#define LOG_WARNING 4 /* (used) warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* (used) informational */ +#define LOG_DEBUG 7 /* (used) debug-level messages */ +#define LOG_DEBUG0 8 /* (used) print more debug-level messages */ +#else +/* Others check the file. */ +#define LOG_DEBUG0 8 /* (used) print more debug-level messages */ +#endif +int main(int argc, char *argv[]) +{ + char time_str[16]; /* 08-28 12:07:11 */ + time_t tt; + struct tm local_time; + time(&tt); + localtime_r(&tt, &local_time); + strftime(time_str, sizeof(time_str), "%m-%d %T", &local_time); + puts(time_str); + static char level_info[] = {' ', ' ', 'C', 'E', 'W', ' ', 'I', 'D', '0'}; + return 0; +} + +FIXME: +/post/linux-monitor-cpu.html + ps -Lf 60010 查看线程 + +CMAKE教程,很不错 +https://blog.gmem.cc/cmake-study-note + + +https://github.com/jamesroutley/write-a-hash-table + +使用场景: +1. 不判断内存是否申请失败 + +cJSON *tag = cJSON_CreateObject() 不判断是否为NULL +cJSON_AddStringToObject(tag, "key", "value"); 可能会导致内存泄露。场景tag=NULL时,会通过"value"创建一个对象,当tag为NULL时,则会直接退出,那么通过 "value" 创建的对象将无法释放。 + +cJSON_AddItemToArray(data, metric = cJSON_CreateObject()); +cJSON_AddStringToObject(metric, "mi_n", m->string); + +比如 Facebook 的 wdt (https://github.com/facebook/wdt),Twitter 的 ( https://github.com/lg/murder ),百度的 Ginko 等等,还有包括亚马逊 Apollo 里面的文件分发系统,它们那个和我们的有点不太一样,他们的是基于 S3 做的。 +蜻蜓 - P2P文件分发 +AOL - 集中配置管理 + +插件 + +PING_OPT_QOS: + 通过setsockopt()配置IP_TOS,实际上是设置IP头的Type-of-Service字段,用于描述IP包的优先级和QoS选项,例如IPTOS_LOWDELAY、IPTOS_THROUGHPUT等。 + +会议内容: +1. 监控需求。通过常驻进程记录上次历史数据,用于计数类型指标统计,目前方案可能会丢失部分监控数据。该问题CloudAgent方的郑力、王一力认可。 +2. CloudAgent实现。目前针对日志采集普罗米修斯已经开始开发常驻监控端口,细节尚未讨论,该功能点基本功能满足指标上报的需求。 +3. 对接CloudAgent方案。具体细节需要接下来讨论,包括对接方式、保活检测、常驻进程的管理等等。 +4. 监控Uagent需求。需要提供动态修改功能,目前来看开发量还比较大;监控插件部分需要做少量修改,之前实现的中间件如haproxy、nginx可以继续使用。 + +参数修改 +PING_OPT_TIMEOUT: + 设置超时时间。 +PING_OPT_TTL: + 同样是通过setsockopt()配置IP_TTL。 +PING_OPT_SOURCE: + 需要通过getaddrinfo()获取。 + +ENOTSUP + +main() + |-ping_construct() + |-ping_setopt() + |-ping_host_add() + | |-ping_alloc() + | |-getaddrinfo() reverse查找 + |-ping_send() + |-ping_open_socket() 如果还没有打开 + |-select() 执行异步接口 + |-ping_receive_one() + |-ping_send_one() + + +https://stackoverflow.com/questions/16010622/reasoning-behind-c-sockets-sockaddr-and-sockaddr-storage?answertab=votes + +FIXME: +获取CPU核数 +http://blog.csdn.net/turkeyzhou/article/details/5962041 +浮点数比较 +http://www.cnblogs.com/youxin/p/3306136.html +MemAvailable +https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + +https://stackoverflow.com/questions/1631450/c-regular-expression-howto + +Share your terminal over the web +https://github.com/tsl0922/ttyd +http-parser +https://github.com/nodejs/http-parser +picohttpparser +https://github.com/h2o/picohttpparser +为什么会使用http-parser +https://stackoverflow.com/questions/28891806/what-is-http-parser-where-it-is-used-what-does-it-do +http://mendsley.github.io/2012/12/19/tinyhttp.html +https://github.com/tinylcy/tinyhttpd + + + + +pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER; +http://www.cnblogs.com/renxinyuan/p/3875659.html +http://blog-kingshaohua.rhcloud.com/archives/54 + +设置线程名称 +http://blog.csdn.net/jasonchen_gbd/article/details/51308638 +https://gxnotes.com/article/78417.html +http://www.cprogramming.com/debugging/segfaults.html +http://cering.github.io/2015/11/10/%E8%BD%AC-%E8%AF%A6%E8%A7%A3coredump/ +https://www.ibm.com/developerworks/cn/linux/l-cn-deadlock/ +http://blog.jobbole.com/106738/ +http://blog.csdn.net/gebushuaidanhenhuai/article/details/73799824 +http://www.cnblogs.com/yuuyuu/p/5103744.html +http://blog.csdn.net/sunshixingh/article/details/50988109 +https://michaelyou.github.io/2015/03/07/epoll%E7%9A%84%E4%BA%8B%E4%BB%B6%E8%A7%A6%E5%8F%91%E6%96%B9%E5%BC%8F/ +http://kimi.it/515.html +https://jeff-linux.gitbooks.io/muduo-/chapter2.html +https://www.zhihu.com/question/20502870 +http://www.firefoxbug.com/index.php/archives/1942/ +http://cr.yp.to/daemontools.html +http://www.voidcn.com/article/p-vfwivasm-ru.html + +如果没有将线程设置为 detached ,而且没有显示的 pthread_exit(),那么在通过 valgrind 进行测试时会发现出现了内存泄露。 + +在通过 pthread_key_create() 创建私有变量时,只有调用 pthread_exit() 后才会调用上述函数注册的 destructor ;例如主进程实际上不会调用 destructors,此时可以通过 atexit() 注册回调函数。 + + + +################################ +# Core Dump +################################ +http://happyseeker.github.io/kernel/2016/03/04/core-dump-mechanism.html +http://blog.csdn.net/work_msh/article/details/8470277 + + +← + + + + + +O_NONBLOCK VS. O_NDELAY +http://blog.csdn.net/ww2000e/article/details/4497349 + +SO_ACCEPTFILTER +http://blog.csdn.net/hbhhww/article/details/8237309 + + + +Address already in use + +该错误信息是由于返回了 EADDRINUSE 错误码,通常是由 TCP 套接字的 TIME_WAIT 状态引起,该状态在套接字关闭后会保留约 2~4 分钟,只有在该状态 TIME_WAIT 退出之后,套接字被删除,该地址才能被重新绑定而不出问题。 + +在 C 中,可以通过如下方式设置端口允许重用。 + +int opt = 1; +setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + +/linux-monitor-cpu.html +ps -ax -o uid,pid,ppid,tpgid,pgrp,session,lstart,cmd + +Linux查看某个文件被谁占用 +当用户卸载某个目录的时候,因其他用户在当前目录或者当前目录在运行一个程序,卸载时报busy的处理办法: + +1:fuser -av /mnt +查看占用mnt的程序以及pid,根据pid去kill -9 + +2:fuser -km /mnt +查看占用mnt的程序并自动kill +-m是指定被占用的目录,-k是kill + +3:losf /mnt +查看正在使用的某个文件 +4:递归查看某个目录的文件信息 +lsof +D /mnt/fat + +5:列出某个用户打开的文件信息 + +lsof -u student + +6:列出某个程序打开的文件信息 + +lsof -c mysql +http://blog.csdn.net/kozazyh/article/details/5495532 + + +System V IPC 分三类:消息队列、信号量、共享内存区,都采用 `key_t` 作为其内部使用的标示,该类型在 `` 中定义,一般是 32 位整数。 + +通常可以使用 ftok() 函数,也就是 file to key,把一个已存在的路径名和一个整数标识符转换成一个 `key_t` 值,该函数声明如下: + +#include +key_t ftok (const char *pathname, int proj_id); + +pathname 通常是跟本应用相关的路径;proj_id 指的是本应用所用到的 IPC 的一个序列号,通常约定好,这样可以获取相同的 `key_t` 值。 + +注意,需要保证该路径应用程序可以访问,并且在运行期间不能删除。 + +#include          +#include     +#include   +  +int main()  +{  +        char filename[50];  +        struct stat buf;  +        int ret;  +        strcpy(filename, "/home/satellite/" );  +        ret = stat( filename, &buf );  +        if(ret) {  +                printf( "stat error\n" );  +                return -1;  +        }  +  +        printf( "the file info: ftok( filename, 0x27 ) = %x, st_ino = %x, st_dev= %x\n", ftok( filename, 0x27 ), buf.st_ino, buf.st_dev ); + +        return 0;  +} + +通过执行结果可看出,ftok获取的键值是由ftok()函数的第二个参数的后8个bit,st_dev的后两位,st_ino的后四位构成的 + +### semget + +创建一个新的信号量或获取一个已经存在的信号量的键值。 + +#include +int semget(key_t key, int nsems, int semflg); + +key: 为整型值,可以自己设定,有两种场景 +   1. IPC_PRIVATE 通常为 0,创建一个仅能被本进程给我的信号量。 +   2. 非 0 的值,可以自己手动指定,或者通过 ftok() 函数获取一个唯一的键值。 +nsems: 初始化信号量的个数。 +semflg: 信号量创建方式或权限,包括了 IPC_CREAT(不存在则创建,存在则获取);IPC_EXCL(不存在则建立,否则报错)。 + +#include +#include + +int main() +{ + int semid; + semid = semget(666, 1, IPC_CREAT | 0666); // 创建了一个权限为666的信号量 + printf("semid=%d\n", semid); + return 0; +} + +可以用 ipcs –s 来查看是否创建成功。 +用 ipcrm -s semid 号来删除指定的信号量。 + + + +################################ +# CMake +################################ + + +针对特定对象,可以通过如下方式指定特定的编译选项、头文件路径、宏定义。 + +target_compile_definitions(audio_decoder_unittests + PRIVATE "AUDIO_DECODER_UNITTEST" + PRIVATE "WEBRTC_CODEC_PCM16") +target_include_directories(audio_decoder_unittests + PRIVATE "interface" + PRIVATE "test" + PRIVATE "../codecs/g711/include") +target_compile_options(RTPencode PRIVATE "/wd4267") + + +## 配置文件 +CheckSymbolExists.cmake   宏定义检查 + + +################################ +# Curl +################################ + +详细可以查看 http://php.net/manual/zh/function.curl-setopt.php +https://moz.com/devblog/high-performance-libcurl-tips/ + +CURLOPT_NOSIGNAL + +CURLOPT_WRITEFUNCTION 用于设置数据读取之后的回调函数,通过该函数可以保存结果,其函数声明如下。 +    size_t function( char *ptr, size_t size, size_t nmemb, void *userdata); +CURLOPT_WRITEDATA 定义了上述函数声明中userdata的值。 + +#include +#include + +size_t save_data(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ +    size_t written; +    written = fwrite(ptr, size, nmemb, stream); +    return written; +} + +int main(void) +{ +    CURL *curl; +    CURLcode res; +    FILE *fp; + +    fp = fopen("index.html", "wb"); + +    curl = curl_easy_init(); +    if (curl) { +        curl_easy_setopt(curl, CURLOPT_URL, "www.baidu.com"); +        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, save_data); +        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); +        curl_easy_perform(curl); +        curl_easy_cleanup(curl); +        fclose(fp); +    } + +    return 0; +} + +CURLOPT_USERNAME +CURLOPT_PASSWORD 分别设置用户名密码,低版本可以通过CURLOPT_USERPWD选项设置,其值为"user:password" 。 + +CURLOPT_TIMEOUT_MS 设置超时时间。 + +CURLOPT_MAXREDIRS +CURLOPT_FOLLOWLOCATION 允许重定向,以及设置重定向的跳转次数。 + + +CURLOPT_SSL_VERIFYPEER 验证证书,证书信息可以通过 CURLOPT_CAINFO 设置,或在 CURLOPT_CAPATH 中设置证书目录。 + +CURLOPT_SSL_VERIFYHOST 设置为 1 是检查服务器SSL证书中是否存在一个公用名(common name)。译者注:公用名(Common Name)一般来讲就是填写你将要申请SSL证书的域名 (domain)或子域名(sub domain)。 设置成 2,会检查公用名是否存在,并且是否与提供的主机名匹配。 0 为不检查名称。 在生产环境中,这个值应该是 2(默认值)。 + + +./configure  \ +    --disable-shared --enable-static                     不使用动态库,而是静态编译 +    --without-libidn2 --without-winidn                   忽略国际化库 + --disable-ipv6 --disable-unix-sockets                关闭IPV6以及Unix Socket + --without-ssl --without-gnutls --without-nss         关闭安全配置项1 + --without-libssh2 --disable-tls-srp --without-gssapi 关闭安全配置项2 +    --without-zlib                                       不支持压缩 + --disable-ares --disable-threaded-resolver       + --without-librtmp  --disable-rtsp                    关闭不需要的协议1 + --disable-ldap --disable-ldaps                       关闭不需要的协议2 + --disable-dict --disable-file --disable-gopher + --disable-ftp --disable-imap --disable-pop3 + --disable-smtp --disable-telnet --disable-tftp + --disable-sspi                                       Windows选项 + --without-libpsl --without-libmetalink +    --with-nghttp2 +  +resolver:         ${curl_res_msg} +Built-in manual:  ${curl_manual_msg} +--libcurl option: ${curl_libcurl_msg} +Verbose errors:   ${curl_verbose_msg} +ca cert bundle:   ${ca}${ca_warning} +ca cert path:     ${capath}${capath_warning} +ca fallback:      ${with_ca_fallback} +HTTP2 support:    ${curl_h2_msg}                                                                                +Protocols:        ${SUPPORT_PROTOCOLS} + + +curl_code = curl_easy_perform (session); +long http_code = 0; +curl_easy_getinfo(session, CURLINFO_RESPONSE_CODE, &http_code);  /* 获取返回码 */ + + + +http://187.0.0.1:8080/status + + + +使用 curl 测量 Web 站点的响应时间。 + +curl -o /dev/null -s -w '%{http_code}-%{time_namelookup}:%{time_connect}:%{time_appconnect}:%{time_pretransfer}:%{time_redirect}:%{time_starttransfer}:%{time_total}\n' 'http://187.0.0.1:8080/status' + +time_namelookup     DNS解析时间,从请求开始到DNS解析完毕所用时间 +time_connect     建立到服务器的 TCP 连接所用的时间 +time_appconnect     连接建立完成时间,如SSL/SSH等建立连接或者完成三次握手时间 +time_pretransfer    准备传输的时间,对于一些协议需要做一些初始化操作 +time_redirect       重定向时间,包括到最后一次传输前的几次重定向的DNS解析、连接、预传输、传输时间 +time_starttransfer 传输时间,在发出请求之后,服务器返回数据的第一个字节所用的时间 +time_total          完成请求所用的时间 +speed_download      下载速度,单位是字节/秒 +http_code           返回码 + +注意,如果某一步失败了,该步骤对应的值实际上显示的是 0 ,此时需要通过总时间减去上一步的消耗时间。 + +上述的执行,是在执行 curl_easy_perform() 函数的时候开始的,在 docs/examples 目录下,有很多的参考实例。 + + +const char *optstr; +char *endptr = NULL; +double v; +long value; + +/* NOTE: if optstr = NULL, strtol() will raise 'Segmentation fault' */ +optstr = "  not a number"; /* errno=0, optstr == endptr */ +errno = 0; +value = strtol(optstr, &endptr, /* base = */ 0); +assert(value == 0); +assert(errno == 0); +assert(optstr == endptr); +printf("errno=%d, optstr=%p, endptr=%p, endchar='%c'/0x%02x, value=%ld\n", errno, optstr, endptr, *endptr, *endptr, value); + +optstr = "  12part number"; +errno = 0; +value = strtol(optstr, &endptr, /* base = */ 0); +printf("errno=%d, optstr=%p, endptr=%p, endchar='%c'/0x%02x, value=%ld\n", errno, optstr, endptr, *endptr, *endptr, value); + +optstr = "  12"; +errno = 0; +value = strtol(optstr, &endptr, /* base = */ 0); +printf("errno=%d, optstr=%p, endptr=%p, endchar='%c'/0x%02x, value=%ld\n", errno, optstr, endptr, *endptr, *endptr, value); + + +memory-barriers +https://www.kernel.org/doc/Documentation/memory-barriers.txt +http://ifeve.com/linux-memory-barriers/ +https://dirtysalt.github.io/memory-barrier.html +http://preshing.com/20120625/memory-ordering-at-compile-time/ +http://events.linuxfoundation.org/sites/events/files/slides/dbueso-elc2016-membarriers-final.pdf +http://larmbr.com/2014/02/14/the-memory-barriers-in-linux-kernel(1)/ +https://www.zhihu.com/question/47990356 +http://www.wowotech.net/kernel_synchronization/Why-Memory-Barriers.html +http://blog.csdn.net/qb_2008/article/details/6840570 + +网卡缓存 +https://zrj.me/archives/1102 + + + +http://www.cnblogs.com/bodhitree/p/6018369.html +sed高级用法 +https://www.zhukun.net/archives/6975 +http://gohom.win/2015/06/20/shell-symbol/ +mysql core dump +http://xiezhenye.com/2015/05/%E8%8E%B7%E5%8F%96-mysql-%E5%B4%A9%E6%BA%83%E6%97%B6%E7%9A%84-core-file.html +文件句柄数 +http://blog.sina.com.cn/s/blog_919f173b01014vol.html +http://www.opstool.com/article/166 +rpm 升级到旧的版本 +http://ftp.rpm.org/max-rpm/s1-rpm-upgrade-nearly-identical.html#S2-RPM-UPGRADE-OLDPACKAGE-OPTION +https://stackoverflow.com/questions/2452226/master-branch-and-origin-master-have-diverged-how-to-undiverge-branches +C hash算法 +http://troydhanson.github.io/uthash/index.html + +https://blog.zengrong.net/post/1746.html +https://stackoverflow.com/questions/9537392/git-fetch-remote-branch + +http://www.cnblogs.com/yuuyuu/p/5103744.html +https://codeascraft.com/2011/02/15/measure-anything-measure-everything/ + + +http://wkevin.github.io/2014/05/05/git-submodule/ +http://xstarcd.github.io/wiki/sysadmin/ntpd.html +ps -ax -o lstart,cmd + + +可以通过如下命令指定分支,提交后会修改原数据中的 `Subproject commit` 。 +cd submodule_directory +git checkout v1.0 +cd .. +git add submodule_directory +git commit -m "moved submodule to v1.0" +git push + +http://docs.python-requests.org/zh_CN/latest/user/quickstart.html +https://liam0205.me/2016/02/27/The-requests-library-in-Python/ + + + + + + + + + + + + + + + + + +https://github.com/MarkDickinson/scheduler +https://github.com/Meituan-Dianping/DBProxy +https://github.com/greensky00/avltree +http://www.freebuf.com/sectool/151426.html +http://www.freebuf.com/sectool/150367.html + + + +tar 打包可以通过 --exclude=dir 排除。 +通过 `--transform` 参数可以根据 `sed` 语法进行一些转换,例如增加前缀 `'s,^,prefix/,'` 或者 `s%^%prefix/%`。 + + + +支持数据类型float, int, str, text, log + +支持函数: +    abschange 计算最新值和上次采集值相减的绝对值,对于字符串0/相同、1/不同,1->5=4, 3->1=2 + +https://www.zabbix.com/documentation/3.0/manual/appendix/triggers/functions + + +支持数据类型: +   Numeric (unsigned) - 64位无符号整数; +   Numeric (float) - 浮点数,可以存储负值,范围是 [-999999999999.9999, 999999999999.9999],同时可以支持科学计算 1e+7、1e-4; +   Character - 短文本数据,最大255字节; +   +   +Log - 具有可选日志相关属性的长文本数据(timestamp, source, severity, logeventid) +Text - 长文本数据 + + +Tim O’Reilly and Crew [5, p.726] +The load average tries to measure the number of active processes at any time. As a measure of CPU utilization, the load average is simplistic, poorly defined, but far from useless. + +Adrian Cockcroft [6, p. 229] +The load average is the sum of the run queue length and the number of jobs currently running on the CPUs. + + +默认没有移动平均值计算,只是针对单个值进行计算。 + +threshold_tree 仍然通过format_name格式化名称。 +  +目前分为了三种类型,分别为 Host > Plugin > Type ,需要按照层级进行排列,例如 Host 下面可以有 Plugin 和 Type 段;Plugin 下可以有 Type 但是不能有 Host 。 + +其它的配置项用于一些类似阈值的判断等,只能在 Type 下面配置。 + +FailureMax Value +WarningMax Value + 设置报警的上限值,如果没有配置则是正无穷。告警发送规则如下: +    A) (FailureMax, +infty) 发送 FAILURE 通知; +    B) (WarningMax, FailureMax] 发送 WARNING 通知; + +FailureMin Value +WarningMin Value + 设置报警的下限值,如果没有配置则是负无穷。告警发送规则如下: +    A) (-infty, FailureMin) 发送 FAILURE 通知; +    B) [FailureMin, WarningMin) 发送 WARNING 通知; + +Persist true|false(default) + 多久发送一次报警,设置规则如下: +    true) 每次超过阈值之后都会发送一次报警通知; +    false) 只有在状态发生转换且前一次状态是OKAY时才会发送一次通知。 + +PersistOK true|false(default) + 定义如何发送OKAY通知,设置规则如下: +    true) 每次在正常范围内都会发送通知; +    false) 当本次状态正常而且之前状态不正常时才发送一次OK通知。 + +Hysteresis Value + 迟滞作用,用于处理在一个状态内重复变换,在该阈值范围内不会告警。 + +Hits Value +    告警条件必须连续满足多少次之后才会发送告警。 + +Interesting true(default)|false + 发现数据未更新时是否发送告警,会根据插件的采集时间间隔以及 Timeout 参数判断是否有事件发生。 +    true) 发送FAILURE报警; +    false) 忽略该事件。 + +DataSource  <-> Types.db 中字段,例如midterm +Host <-> host +Plugin <-> plugin +Type <-> type +Instance <-> type_instance + +1. 根据value list中的参数获取到具体的配置。 +2. + +Invert true|false +Percentage true|false + +以 loadavg 为例,其实现在 fs/proc/loadavg.c 中。 +calc_global_load() +监控指标,其中监控插件包括了 + +https://en.wikipedia.org/wiki/Moving_average +http://www.perfdynamics.com/CMG/CMGslides4up.pdf +https://zh.wikipedia.org/wiki/%E7%A7%BB%E5%8B%95%E5%B9%B3%E5%9D%87 + +移动平均 (Moving Average) 可以处理短期波动,反映长期趋势或周期,从数学上看做是卷积。 + + +## 简单移动平均 + +Simple Moving Average, SMA 将变量的前 N 值做平均。 + +SMA = (V1 + V2 + ... + Vn) / n + +当有新值之后,无需重复计算,只需要将最老的旧值删除,然后加入新值。 + +SMAn = SMAn-1 - V1/n + V/n + +这样需要保存 N 个值。 + +## 加权移动平均 + +Weighted Moving Average, WMA 也就是在计算平均值时将部分数据乘以不同数值, + +## 指数移动平均 + +Exponential Moving Average, EMA + + +----- 锁定用户该用户不能再次登录 +ALTER USER username ACCOUNT LOCK; +----- 解锁用户 +ALTER USER username ACCOUNT UNLOCK; + +aussdb plugin: Connect to database failed: FATAL:  The account has been locked. +FATAL:  The account has been locked. + +Zabbix上报数据格式 +http://www.ttlsa.com/zabbix/zabbix-active-and-passive-checks/ +Open-falcon 上报数据 +http://blog.niean.name/2015/08/06/falcon-intro +main_timer_loop() 周期计算定义的触发值,如果有事件发生,那么就直接写入到数据库中。 + + +timer_thread()     main_timer_loop时间相关的处理 + |-process_time_functions() + | |-DCconfig_get_time_based_triggers() 从缓存中获取trigger表达式 + | |-evaluate_expressions()       触发器表达式的主要处理函数,同时会产生事件 + | | |-substitute_simple_macros() 宏分为两类,分别是{...} {$...} + | | |-substitute_functions() + | | | |-zbx_evaluate_item_functions() + | | |   |-evaluate_function() + | | |-evaluate() + | | + | |-DBbegin() + | |-process_triggers() T:triggers + | | |-process_trigger() + | |   |-add_event()              会保存到内存的events数组中 + | |-process_events()             处理事件,主要是将新事件插入数据库 + | | |-save_events() T:events + | | |-process_actions() T:actions + | | |-clean_events() + | |-DBcommit() + | + |-process_maintenance() + + +源码解析 +https://jackywu.github.io/articles/zabbix_server%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/ + +## 表结构 + +{:.()} + +hosts  包含了主机以及模板信息。 +    select hostid, host from hosts where status = 0; +    hostid  主机ID + host    主机、模板名 + status  0->主机 3->模板 +goups   主机的逻辑分组 +hosts_grous 分组和主机之间的关联关系 +items 监控项,保存了每个主机的监控项,该监控项来自的模板ID +    select itemid, name, key_, templateid, delay, status, units from items where hostid = 10107; +triggers 触发器,其中表达式中使用的是function id + select triggerid, expression, status, value, description from triggers; + select * from functions where functionid = 13302; +functions + +Paxos协议解析 +https://zhuanlan.zhihu.com/p/21438357?refer=lynncui + +https://github.com/hanc00l/wooyun_public +https://github.com/niezhiyang/open_source_team +http://www.jianshu.com/p/43c604177c08 +http://kenwheeler.github.io/slick/ + +http://lovestblog.cn/blog/2016/07/20/jstat/ +http://blog.phpdr.net/java-visualvm%E8%AE%BE%E7%BD%AEjstat%E5%92%8Cjmx.html + + +http://metrics20.org/spec/ +Stack Overflow 的架构 +https://zhuanlan.zhihu.com/p/22353191 +GCC部分文件取消告警 +http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html +http://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html +http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Warning-Options.html + + + + + + + + + +SHELL反弹 +https://segmentfault.com/a/1190000010975294 +各种环境下反弹shell的方法 +http://www.zerokeeper.com/experience/a-variety-of-environmental-rebound-shell-method.html + +libev信号处理 +http://blog.csdn.net/gqtcgq/article/details/49688027 +信号相关参考 +http://www.cnblogs.com/mickole/p/3191281.html + +## Joinable VS. Detached + +默认创建的线程是 Joinable 的,也就是可以通过 `pthread_join()` 函数在任何其它线程中等待它的终止。 + +各个线程是独立运行的,也就意味着在调用 join 之前,目标线程可能已经终止了,那么如果一个线程是 Joinable ,POSIX 标准要求必须保持、维护一些信息,至少是线程 ID 以及返回值。 + +实际上,对于大多数系统,如 Linux、Sloaris 等,在实现时通过 Thread Control Block, TCB 保存一些相关的信息,包括线程 ID、属性、入口函数、参数、返回值;调度策略、优先级;信号掩码、信号栈等等;而且通常是一次性分配堆栈和 TCB,例如单次 mmap() 调用,并把 TCB 放在栈的开始位置处。 + +也就是说,不能立即释放对应的资源,这样就会造成浪费。 + +### Detached + +当设置为 Detached 时,表明对于开启的线程,如果一旦执行结束,则会立即清理资源。 + +可以在创建线程时设置该属性,或者调用 pthread_detach() 函数;当设置了该属性后,如果再次调用 pthread_join() 则会返回 EINVAL 错误。 +#include +#include +#include +#include +#include +#include + +void *thread(void *dummy) +{ + (void) dummy; + sleep(1); + return NULL; +} + +void detach_state(pthread_t tid, const char *tname) +{ + int rc; + + rc = pthread_join(tid, NULL); + if (rc == EINVAL) + printf("%s is detached\n", tname); + else if (rc == 0) + printf("%s was joinable\n", tname); + else + printf("ERROR: %s rc=%d, %s\n", tname, rc, strerror(rc)); +} + +int main(void) +{ + /* TODO: Check the return value */ + + /* normal thread creation */ + pthread_t tid1; + pthread_create(&tid1, NULL, thread, NULL); + detach_state(tid1, "thread1"); /* joinable */ + + /* detach thread from main thread */ + pthread_t tid2; + pthread_create(&tid2, NULL, thread, NULL); + pthread_detach(tid2); + detach_state(tid2, "thread2"); /* detached */ + + /* create detached thread */ + pthread_attr_t attr; + pthread_t tid3; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&tid3, &attr, thread, NULL); + detach_state(tid3, "thread3"); + + return EXIT_SUCCESS; +} + + +对于该特性,需要注意如下的规则: +1. 不要重复 join 一个线程,已经 join 线程的栈空间已经被回收,再次调用无法获取对应的信息; +2. 不要 join 一个是 detach 的线程,分离的线程栈空间是由系统内部来做回收的; + +sigprocmask() 函数能够根据 + +根据参数 how 实现对信号集的操作,主要包括如下三种: +* SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号,相当于 mask=mask|set; +* SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞,相当于 mask=mask|~set; +* SIG_SETMASK 更新进程阻塞信号集为set指向的信号集,相当于mask=set。 + +EPOLLIN 对应的文件描述符可读,包括对端 Socket 正常关闭(返回字节为 0); +EPOLLOUT 对应的文件描述符可写; +EPOLLPRI 所谓的带外数据,也就是在通过 send() 发送时指定 MSG_OOB 参数; +EPOLLERR 发生本地错误; +EPOLLHUP 表示客户端套接字已经断开连接; +EPOLLET 使用边沿触发,默认是水平触发; +EPOLLONESHOT 只监听一次事件,事件触发后会自动关闭,如果需要再次监听则需要重新设置。 + +1、listen fd,有新连接请求,触发EPOLLIN。 +2、对端发送普通数据,触发EPOLLIN。 +3、带外数据,只触发EPOLLPRI。 + +1. 当对端正常关闭时,会触发 EPOLLIN 和 EPOLLRDHUP 事件,而非 EPOLLERR 和 EPOLLHUP,此时读缓冲区大小为 0 。 + +注意,如果是对端发生错误,不会主动触发 EPOLLERR 错误,只有在下次调用读写时才会触发。 + +#### 文件描述符异常 + +一般来说,不需要手动从 epoll() 中删除,系统会自动删除掉,不过按照 man epoll(7) 的 Q6 介绍,需要保证文件描述符没有被 dup() 复制过。 + +#### 同时注册两次 + +如果将相同的 fd 添加到 epoll_set 两次,接口会返回 EEXIST 报错,不过可以通过 dup 接口复制一份并添加,例如多线程中的使用。 + +#### 关于 file descriptors and file descriptions +https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-broken-22/ +https://blog.codingnow.com/2017/05/epoll_close_without_del.html + + +### 边缘触发 VS. 水平触发 + +简单来说,两者区别如下: + +1. Level Triggered 水平触发:有事件触发时会通过 epoll_wait() 去处理,如果一次处理不完会持续通知,直到处理完成,当系统中有大量不需要的描述符时会大大降低处理效率。 +2. Edge Triggered 边缘触发:如果本次没有处理完成,那么即使下次调用 epoll_wait() 也不会再次通知,知道出现下次的读写事件时才会再次触发。 + +其中 select() poll() 的模型都是水平触发模式,信号驱动 IO 是边缘触发模式,epoll() 模型即支持水平触发,也支持边缘触发,默认是水平触发。 + +#include +#include +#include + +#include +#include +#include + +int main(void) +{ + glob_t globbuf; + struct stat statbuf; + int rc; + size_t pathc; + char **pathv; + + rc = glob("/var/run/haproxy[0-9]*.sock", GLOB_ERR | GLOB_NOSORT, NULL, &globbuf); + if (rc != 0) + return -1; + + pathv = globbuf.gl_pathv; + pathc = globbuf.gl_pathc; + printf("Got #%d matches\n", pathc); + + for (; pathc-- > 0; pathv++) { + rc = lstat(*pathv, &statbuf); + if (rc < 0 || !S_ISSOCK(statbuf.st_mode)) + continue; + printf("Match path: %s\n", *pathv); + } + + globfree(&globbuf); + return 0; +} + +mobius + + + + + + + + +aspire + + + +介绍TCP流 +http://kaiyuan.me/2015/09/04/TCP%E7%9A%84%E6%94%B6%E5%8F%91%E5%8C%85%E6%9C%BA%E5%88%B6%E8%A7%A3%E6%9E%90/ + + + + + + + + + + + + + + + +返回值:如果执行成功则返回子进程识别码(PID), 如果有错误发生则返回-1. 失败原因存于errno 中. + +https://github.com/krallin/tini +https://github.com/Yelp/dumb-init + +## tini + +其功能类似于 init 进程,一般用于容器中, + +在编译静态二进制文件时会依赖 glibc 的静态库,对于 CentOS 来说,需要通过 `yum install glibc-static` 安装。 + +可以通过该工程查看 CMake 的编写,以及编写类似 init 进程的注意事项。 + + +在 /post/linux-kernel-process 中有关于孤儿进程和僵尸进程的介绍,简单来说: + +* 孤儿进程。当父进程被 kill 掉,其子进程就会成为孤儿进程 (Orphaned Process),并被 init(PID=1) 所接管。 + + +### 孤儿进程如何被接管 + +在 Linux 内核中,有如下的代码 [Kernel find_new_reaper()](https://github.com/torvalds/linux/blob/eae21770b4fed5597623aad0d618190fa60426ff/kernel/exit.c#L479) ,其开头的注释摘抄如下: + +/* + * When we die, we re-parent all our children, and try to: + * 1. give them to another thread in our thread group, if such a member exists + * 2. give it to the first ancestor process which prctl'd itself as a + * child_subreaper for its children (like a service manager) + * 3. give it to the init process (PID 1) in our pid namespace + */ + +也就是说,接管分三步:A) 找到相同线程组里其他可用的线程;B) 如果没有找到则进行第二步C) 最后交由 PID=1 的进程管理。 + + + +### SubReaper + +当一个进程被标记为 child_subreaper 后,这个进程所创建的所有子进程,包括子进程的子进程,都将被标记拥有一个 subreaper。 + +那么当某个进程成为了孤儿进程时,会沿着它的进程树向祖先进程找一个最近的是 child_subreaper 且运行着的进程,这个进程将会接管这个孤儿进程。 + +http://adoyle.me/blog/orphaned-process-and-zombie-process-and-docker.html + + +https://stackoverflow.com/questions/9305992/if-threads-share-the-same-pid-how-can-they-be-identified/9306150#9306150 + +## RBASH + +restricted bash, rhash 也就是受限制的 bash,实际上这只是指向 bash 的软连接,也可以通过 `bash -r` 参数启动,作用相同。 + +此时,启动的这个 BASH 会在某些功能上受限制,包括: + + + +* 通过 cd 来改变工作目录 +* 设置或取消环境变量 SHELL、PATH、ENV、BASH_ENV +* 命令名中不能包含目录分隔符 '/' + + +包含有 ‘/’ 的文件名作为内置命令 ‘.’ 的参数 +hash 内置命令有 -p 选项时的文件名参数包含 ‘/’ +在启动时通过 shell 环境导入函数定义 +在启动时通过 shell 环境解析 SHELLOPTS 的值 +使用 >,>|, <>, >&, &>, >> 等重定向操作符 +使用 exec 内置命令 +通过 enable 内置命令的 -f 和 -d 选项增加或删除内置命令 +使用 enable 内置命令来禁用或启用 shell 内置命令 +执行 command 内置命令时加上 -p 选项 +通过 set +r 或 set +o restricted 关闭受限模式 + +## 逃逸 + +rbash 提供的受限环境的安全程度取决于用户能执行的命令,很多命令都能调用外部命令,从而导致逃逸出受限环境。 + +例如用 vim 打开一个文件,然后通过 `!bash` 执行外部命令,那么就可以启动一个不受限的 bash,这对 more,less,man 等命令同样有效。如果还能执行脚本,如 python、perl 等,则有更过的方式来启动一个不受限的 shell 。 + +也可以通过如下方式执行: + +$ BASH_CMDS[a]=/bin/sh;a +$ /bin/bash +$ export PATH=$PATH:/bin:/usr/bin + + +要让 rbash 更安全,可以限制用户能够执行的命令,如我们让用户执行执行 ssh 命令。一种方法是,修改 PATH 环境变量。 + +例如我们创建一个 ruser 用户,让他只能执行 ssh 命令: + +$ ls -s /bin/bash /bin/rbash + +$ useradd -s /bin/rbash ruser + +$ chown -R root:ruser /home/ruser/.bashrc /home/ruser/.bash_profile + +$ chmod 640 /home/ruser/.bashrc /home/ruser/.bash_profile + +$ mkdir /home/ruser/bin +然后修改 PATH 环境变量的值为 /home/ruser/bin,并将允许执行的命令放到这个目录下。: + +$ echo "export PATH=/home/ruser/bin" >> /home/ruser/.bash_profile +把用户可执行的命令链接到用户 PATH 路径下: + +$ ln -s /user/bin/ssh /home/ruser/bin/ssh +这样就可以只让登录的用户执行 ssh 命令。 + +## 限制用户执行命令 + +git比较两个分支 +https://blog.csdn.net/u011240877/article/details/52586664 + + + + + + + +一个简单的时序数据库 +https://github.com/Cistern/catena + +https://www.byvoid.com/zhs/blog/string-hash-compare +http://www.open-open.com/lib/view/open1451882746667.html +https://hitzhangjie.github.io/jekyll/update/2018/05/19/golang-select-case%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6.html + + + + + +HTTP平滑升级 +https://segmentfault.com/a/1190000004445975 + +### WithCancel + +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) + +当调用 Cencel() 函数时,相关的子协程可以通过 `ctx.Done()` 手动相关的请求。 + +### + +https://deepzz.com/post/golang-context-package-notes.html +https://juejin.im/post/5a6873fef265da3e317e55b6 + +## 常见问题 + +### 成员变量 + +cannot refer to unexported field or method ver + +在 GoLang 中要提供给外面访问的方法或是结构体必须是首字母大写,否则会报错。 + +exec.Command() 新建一个对象,但是没有执行 + +Output() 会等待任务执行完成并收集输出。 + +## GoReman 进程管理 +https://github.com/polaris1119/The-Golang-Standard-Library-by-Example/blob/master/chapter10/10.1.md +https://www.jianshu.com/p/49e83c39cffc + +/post/linux-commands-text.html + +AWK统计操作 + + +grep 'Got result' /tmp/foobar.log | awk '{s[$2]++} END{ for(i in s){print i, s[i]} }' | sort +http://blog.51cto.com/6226001001/1659824 +https://shaohualee.com/article/691 + + +使用较多的是网络编程中,假设存在 A 调用 B 的 API,然后 B 再调用 C 的 API,如果要取消 `A->B` 的调用,按照正常的逻辑也应该要取消 `B->C` 的调用,那么此时就可以通过传递 Context 以及正常的逻辑判断来实现。 + + + +Linux内核的DEBUG方法 +https://medium.com/square-corner-blog/a-short-guide-to-kernel-debugging-e6fdbe7bfcdf + + + + +#include +#include +#include +#include +#include + +#define log_it(fmt, args...) do { printf(fmt, ## args); putchar('\n'); } while(0) + +int main() +{ + pid_t pid; + int fd[2], rc, i; + char buffer[1024]; + + rc = socketpair(AF_LOCAL, SOCK_STREAM, 0, fd); + if (rc < 0) { + log_it("create sockpaire failed, %s", strerror(errno)); + return -1; + } + + pid = fork(); + if (pid < 0) { + log_it("fork failed, %s", strerror(errno)); + return -1; + } else if (pid == 0) { + close(fd[0]); + strcpy(buffer, "hello socketpair"); + for (i = 0; i < 10; i++) { + write(fd[1], buffer, strlen(buffer)); + usleep(200000); + } + strcpy(buffer, "exit"); + write(fd[1], buffer, strlen(buffer)); + close(fd[1]); + } else { + close(fd[1]); + while (1) { + rc = read(fd[0], buffer, sizeof(buffer) - 1); + if (rc > 0) { + buffer[rc] = 0; + if (strcmp(buffer, "exit") == 0) + break; + log_it("father: %s", buffer); + } + } + close(fd[0]); + } + + return 0; +} + +查找 socketpair 的对端。 + + +其中 `/proc//fd/` 中显示的数字是虚拟套接字文件系统中套接字的 `inode` 编号,创建管道或套接字对时,每个端口都会连续接收一个 inode 编号 + +如下命令实际上很难查找到,并没有将内核的信息暴露出来。 + +lsof -c progname +lsof -c parent -c child1 +ls -l /proc/$(pidof server)/fd +cat /proc/net/unix + +$ ss -xp | grep foobar + + +HTTP服务 +http://fuxiaohei.me/2016/9/20/go-and-http-server.html + + + +musl libc一个安全用户嵌入式设备的库 +http://www.musl-libc.org/ +http://www.etalabs.net/compare_libcs.html + + + + + + +Rootkit 是一套由入侵者留在系统中的后门程序,通常只有在系统被入侵后被安装进系统,用于长期控制,主要特征为:隐藏、操纵、收集数据。 + +Linux RooKit 可以简单地分为用户态和内核级,一些新的技术可能支持 BIOS、PIC、EFI 。 + +其中用户态通常是替换一些二进制文件,如 ps、netstat、ls 等,从而实现进程隐藏、网络连接信息隐藏、文件隐藏等功能。内核态由于隐蔽性好、攻击能力强,逐渐成为了主流,分为了 LKM 和 非LKM 类型。 + +一般其包含的功能有:远程指令执行、信息收集、文件隐藏、进程隐藏、网络连接隐藏、内核模块隐藏 。 + + + + +https://github.com/mempodippy/vlany +https://github.com/maK-/maK_it-Linux-Rootkit + + + + +http://www.freebuf.com/articles/network/185324.html +https://github.com/iagox86/dnscat2 +Netfilter RootKit +https://github.com/zionlion67/rootkit + +Wildpwn:Unix通配符攻击工具 +http://www.freebuf.com/sectool/185276.html + +anti-rootkit +https://github.com/dgoulet/kjackal +http://www.ywnds.com/?p=6905 +http://rkhunter.sourceforge.net/ +http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Set_UID.pdf + +https://www.ibm.com/developerworks/cn/linux/l-overflow/index.html + +SQLite +http://huili.github.io/ +http://www.iteye.com/blogs/subjects/deepfuture +http://www.cnblogs.com/hustcat/archive/2009/02/26/1398896.html +unix_dgram unix_stream unix_seqpacket + + + + + + + + + + +http://www.sqlite.org/howtocorrupt.html + + +### Atomic Operation + +所谓的原子操作就是 "不可中断的一个或一系列操作",这里说的是硬件的原子操作能力。 + +在单处理器系统(UniProcessor)来说,能够在单条指令中完成的操作都可以认为是 "原子操作",因为中断只能发生于指令之间。 + +而对称多处理器(Symmetric Multi-Processor)来说则会有很大区别,由于多个处理器同时在独立运行,即使能在单条指令中完成的操作也有可能受到干扰。 + +但是多核之间通常由于在共享内存空间会导致异常,为此在 x86 平台上,CPU 提供了在指令执行期间对总线加锁的手段。 + +简单来说,增加了 HLOCK 用来对总线进行加锁,从而在使用某个或者某段指令之间其它 CPU 无法访问内存,这样对于单个 CPU 来说,内存中的资源对于这个 CPU 就是独有的。 + +一般来说,CPU 使用较多的是针对单个数据类型的原子操作,最常见的是整形。 + +https://blog.csdn.net/qq100440110/article/details/51194563 +https://my.oschina.net/jcseg/blog/316726 +https://software.intel.com/zh-cn/blogs/2010/01/14/cpucpu + + +1. 读取PIDFile,获取进程PID + 1.1 检查进程是否存在 /proc/ 目录 + 不存在(通过kill -9强制杀死导致PIDFile未被清除)。 + 1.1.1 再次通过pidof检查所有进程,以防止PIDFile更新异常,或者被人为误更新PIDFile。 + 不存在,程序确实未启动,直接退出。 + 存在。发送kill信号,等待一个安全时间进程自动退出。 + 1.2 再次检查上次获取的PID及其子进程。 + 1.2.1 先向子进程组发送kill -9信号。 + 1.2.2 再向父进程组发送kill -9信号。 + 1.3 最后检查确保进程正常退出,如果未被清理输出告警信息。 + + + +/post/python-modules.html +搜索路径 +一般来说,顺序为当前路径(`' '`),环境变量 `PYTHONPATH` 指定路径,通过 `site` 模块生成的路径。 + +/post/python-tips.html +带 * 参数 + +注意,在进行参数传递时,也可以通过 `**` 进行字典的传参,示例如下。 + +#!/bin/python + +def foobar(name, phone, addr): + print name, phone, addr +#foobar("foobar", "137-0123", "US") +arg = dict(phone="137-0123", addr="US") +foobar("foobar", **arg) + +https://docs.python.org/2/library/sys.html#sys.path + + + + + + + + + + + + + + + + + + +https://matplotlib.org/faq/usage_faq.html + +load 用来表示系统的负载,通过 `top` `uptime` `w` 命令或者 `cat /proc/loadavg` 查看当前系统前 1min 5min 15min 的负载平均值。 + +实际上在计算时采用的是指数平滑法,只是 Linux 内核中不允许直接做浮点运算,而且有多个 CPU 核,考虑到效率问题,从而导致计算的代码比较复杂。 + +$$s_{t}=\alpha x_t + (1 - \alpha) s_{t-1}$$ + +简单来说,算法比较简单,但是 + + +We take a distributed and async approach to calculating the global load-avg +in order to minimize overhead. + +The global load average is an exponentially decaying average of nr_running + +nr_uninterruptible. + +Once every LOAD_FREQ: + + nr_active = 0; + for_each_possible_cpu(cpu) + nr_active += cpu_of(cpu)->nr_running + cpu_of(cpu)->nr_uninterruptible; + + avenrun[n] = avenrun[0] * exp_n + nr_active * (1 - exp_n) + +也就是说,核心的是:A) 多 CPU 中如何获取到整个系统的 `nr_active`;B) 定点计算指数平滑法。 + +## CPU 核数 + +其实对于一台机器来说有几种类型:A) Multi Processor 也就是多个物理 CPU;B) Multi-Core Processor 一个物理 CPU 中有多个核。 + +另外,为了提高并行计算的性能,Intel 还引入了 Hyper-Threading 特性,此时,一个物理 CPU 对于操作系统来说表现为两个。 + + +我们以 `/proc/loadavg` 为准,实际上实现在 +fs/proc/loadavg.c + +http://ilinuxkernel.com/?p=869 +http://brytonlee.github.io/blog/2014/05/07/linux-kernel-load-average-calc/ +https://www.teamquest.com/files/9214/2049/9761/ldavg1.pdf + +内核计算负载的公式如下: + +$$load_t=load_{t-1}e^{\frac{-5}{60R}}+queue(1-e^{\frac{-5}{60R}})$$ + +其中 $R$ 为 1 5 15 分别对应了 1min 5min 15min 的负载计算方法;$queue$ 对应了当前的运行队列长度。 + +通过 `unsigned long avenrun[3];` 存放负载情况,因为无法进行浮点运算,所以低 11 位用来保存负载的小数部分,其余的高位用来保存整数部分。 + +内核在进程调度时会调用 `calc_global_load()` 函数来计算负载,一般是每 5s 更新一次。 + + +unsigned long avenrun[3]; // 用于存放 + +#define FSHIFT 11 /* nr of bits of precision */ +#define FIXED_1 (1<>= FSHIFT; + + + +the CXX compiler identification is unknown + +也就是 CMake 找不到 C++ 对应的编译器,在 CentOS 可以通过 `yum install gcc-c++` 来安装。 + + + + + + + + + +/post/git-tips.html + +----- 对一些修改增加着色 +git config --global color.ui true + + +#include +#include +#include +#include +#include +#include + +#define THREAD_NAME_MAX 16 + +//#define HAVE_SETNAME_NP 1 + +static void *threadfunc(void __attribute__((unused)) *args) +{ +#ifndef HAVE_SETNAME_NP + if (prctl(PR_SET_NAME, "FOOOOO") < 0) + fprintf(stderr, "set thread name failed, %s.\n", strerror(errno)); +#endif + sleep(60); + + return NULL; +} + +int main(void) +{ + int rc; + pthread_t thread; + + rc = pthread_create(&thread, NULL, threadfunc, NULL); + if (rc != 0) { + fprintf(stderr, "create thread failed, %s.\n", strerror(rc)); + return -1; + } + +#ifdef HAVE_SETNAME_NP + char thdname[THREAD_NAME_MAX]; + + rc = pthread_getname_np(thread, thdname, sizeof(thdname)); + if (rc != 0) + fprintf(stderr, "get thread_np name failed, %s.\n", strerror(rc)); + fprintf(stdout, "current thread name is '%s'.\n", thdname); + + strncpy(thdname, "FOOBAR", sizeof(thdname) - 1); + rc = pthread_setname_np(thread, thdname); + if (rc != 0) + fprintf(stderr, "set thread_np name failed, %s.\n", strerror(rc)); +#endif + + rc = pthread_join(thread, NULL); + if (rc != 0) { + fprintf(stderr, "join thread failed, %s.\n", strerror(rc)); + return -1; + } +} + +## SendFile + +sendfile:Linux中的"零拷贝" +https://blog.csdn.net/caianye/article/details/7576198 +http://fred-zone.blogspot.com/2011/03/linux-kernel-sendfile-server.html + + +一般来说通过 SendFile 优化之后至少会有 2 倍的效率提升。 + +kHTTPd 一个内核中的HTTP服务器 +http://www.fenrus.demon.nl/ + +Life of a HTTP request, as seen by my toy web server 极限优化WEB服务器的性能 +https://tia.mat.br/posts/2014/10/06/life_of_a_http_request.html +https://jvns.ca/blog/2016/01/23/sendfile-a-new-to-me-system-call/ +https://blog.plenz.com/2014-04/so-you-want-to-write-to-a-file-real-fast.html + +可以通过 `man 2 sendfile` 查看相关的帮助文档。 + +#include +ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); + +其中 in_fd 必须是一个支持 mmap 的文件描述符,而 out_fd 在 2.6.33 之前只支持 socket ,在此之后支持任意的文件描述符。 + + size_t count = 0; + int filefd; + struct stat filestat; + + filefd = open(argv[3], O_RDONLY); + fstat(filefd, &filestat); + + char buff[1024]; + int sock, connfd; + int rc; + struct sockaddr_in address, cliaddr; + socklen_t addrlen = sizeof(cliaddr); + + bzero(&address, sizeof(address)); + address.sin_family = AF_INET; + inet_pton(AF_INET, argv[1], &address.sin_addr); + address.sin_port = htons(atoi(argv[2])); + + sock = socket(PF_INET, SOCK_STREAM, 0); + bind(sock, (struct sockaddr *)&address, sizeof(address)); + listen(sock, 5); + + connfd = accept(sock, (struct sockaddr *)&cliaddr, &addrlen); + while (1) { + rc = read(filefd, buff, sizeof(buff)); + count += rc; + write(connfd, buff, rc); + } + + close(connfd); + close(sock); + +https://blog.csdn.net/hnlyyk/article/details/50856268 + + + +灰度发布 +全链路压测 + +在 Nginx 中,通过 sticky 模块根据 Cookie 中的 mode 字段进行转发,包括了:A) 空 (真正业务流量);B) gray 灰度流量,用来发布测试;C) shadow 影子流量。 + +进行全链路压测时,实际上流量应该与真正用户相同,只是使用的是影子表,所以在 Nginx 做负载均衡时,实际上也可以将 mode 置空,那么此时就不会转发到不同的服务器。 + +https://www.kancloud.cn/kancloud/xorm-manual-zh-cn/56003 + +# Casbin + +Casbin 一个支持 Access Control List, ACL RBAC ABAC 的开源方案。 + +PERM stands for Policy, Effect, Request, Matchers. + +用户 (User, Subject) 发起操作的主体。 +对象 (Object) 操作所针对的目标,例如订单、文件等。 +操作 (Action) 执行的动作,例如创建用户、文章等。 + +## ACL + +全称为 Access Control List 。 + +最早也是最基本的一种访问控制机制,原理非常简单:每项资源都配有一个列表,这个列表记录的就是哪些用户可以对这项资源执行 CRUD 中的那些操作。 + +当访问某项资源时,会先检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应的操作。总得来说,ACL 是一种面向资源的访问控制模型,它的机制是围绕资源展开的。 + +ACL 简单,但缺点也是很明显。由于需要维护大量的访问权限列表,ACL 在性能上有明显的缺陷。另外,对于拥有大量用户与众多资源的应用,管理访问控制列表本身就变成非常繁重的工作。 + + +自主访问控制 Discretionary Access Control, DAC + +当获得授权之后,可以将相关的权限转移给他人使用。 + +强制访问控制 Mandatory Access Control, MAC + +## RBAC + +也就是基于角色的访问控制,其全称为 Role-Based Access Control 。简单来说,就是每个用户关联一个或多个角色,每个角色关联一个或多个权限,从而可以实现了非常灵活的权限管理。 + +## ABAC + +基于属性的权限验证,全称为 Attribute-Based Access Control ,被认为是权限系统设计的未来。 + +通过动态计算一个或一组属性来是否满足某种条件来进行授权判断,属性通常来说分为四类:用户属性 (如用户年龄)、环境属性 (如当前时间)、操作属性 (如读取) 和对象属性 (如一篇文章),理论上能够实现非常灵活的权限控制,几乎能满足所有类型的需求。 + +不过 ABAC 的管理太过复杂,实际使用较多的还是 RBAC 。 + +https://dinolai.com/notes/others/authorization-models-acl-dac-mac-rbac-abac.html +https://callistaenterprise.se/blogg/teknik/2017/09/11/go-blog-series-part11/ +https://marcus.se.net/hystrix-go-intro/ +https://github.com/callistaenterprise/goblog +http://www.ru-rocker.com/2017/04/24/micro-services-using-go-kit-hystrix-circuit-breaker/ +https://dzone.com/articles/go-microservices-part-11-hystrix-and-resilience +https://github.com/tcnksm-sample/hystrix-go + + +## N 的最小 2 次幂 + +其英文解释为 `Smallest power of 2 greater than or equal to N.` 。 + +## Method 1 + +通过纯数学的计算方法。 + +1. 计算 N 底为 2 的对数,并取其 ceil 值。 +2. 再计算其指数。 + +## Method 2 + +获取数字中的最高位信息。 + +1. 判断是否已经是 0 或者 2 的 N 次幂。 +2. 向右移位并获取最高位的值,同时需要统计移位次数。 +3. 按照移位次数向左移动。 + +## Method 3 + +与方法 2 类似,不需要再统计位移的次数。 + +https://jameshfisher.com/2018/03/30/round-up-power-2.html +https://jeffreystedfast.blogspot.com/2008/06/calculating-nearest-power-of-2.html + +#include +#include +#include +#include + +unsigned int next_pow2_1(unsigned int v) +{ + return pow(2.0, ceil((log(v) / log(2.0)) + 0.5)); +} + +unsigned int next_pow2_2(unsigned int v) +{ + int c = 0; + + if (v && !(v & (v - 1))) + return v; + + for (c = 0; v; c++) + v >>= 1; + + return 1 << c; +} + +unsigned int next_pow2_3(unsigned int v) +{ + unsigned int p = 1; + + if (v && !(v & (v - 1))) + return v; + + while (p < v) + p <<= 1; + + return p; +} + +int main(void) +{ + printf("xxxxx %d\n", next_pow2_1(5)); + + return 0; +} + + + +applyAll() + +## Snapshot + +设置 + + +applyAll() + |-triggerSnapshot() 通过apply的次数判断是否需要执行Snapshot + +在执行备份的时候,可以通过如下命令主动生成 snapshot 。 + + +ENDPOINTS='127.0.0.1:15379,127.0.0.1:25379,127.0.0.1:35379' +ETCDCTL_API=3 ./etcdctl --endpoints=${ENDPOINTS} snapshot save snap.db + + +## 测试 + +为了方便进行测试,在启动时添加 `--snapshot-count '100'` 参数,并通过如下脚本生成测试用的数据。 + +#!/bin/sh -e + +ENDPOINTS='127.0.0.1:15379,127.0.0.1:25379,127.0.0.1:35379' +for ((i=1; i<=150; i ++)); do + uuid=`uuidgen` + echo "ETCDCTL_API=3 ./etcdctl --endpoints=${ENDPOINTS} put ${uuid} '${uuid}-hello'" +done + +## Progress + +Leader 会通过 Progress 维护各个 Follower 的状态,会根据该状态向 Follower 发送日志信息(msgApp)。 + +对应的实现在 `raft/progress.go` 中,其中包括了两个重要的属性: + +* match + + +ins *inflights 发送数据的滑动窗口,最大值为MaxSizePerMsg + +becomeProbe + +resetState() + +https://github.com/metametaclass/libev-aliasing-warning/blob/master/test_alias.c + + + + +Protobuf 如果连续发包会导致粘包。 + +首先会判断是否为新的接口,也就是 `XXX_Unmarshal()` 的定义。 + +pb.Reset() 重置报文 + +SA_RESTART +http://www.cnblogs.com/mickole/p/3191832.html +commitC 的数据从何而来? + +通过 `publishEntries()[raft.go]` 将已经提交的数据添加到 commitC 管道中,而已经提交数据是从 Ready() 中获取。 + +初步判断,在 publishEntries() 函数中会添加 + +注意这里的处理方式有问题? + +`pthread_setname_np()` 函数是在 glibc 的 2.12 版本之后添加的,可以查看其源码实际上如果是本线程也是通过 `prctl()` 添加,而非本线程则实际修改的 `/proc/self/task/%u/comm` 文件的内容。 + + + + +在 C 中,可以通过 `goto` 跳转到同一个函数的某个 label 处,但是不能在不同的函数之间跳转。实际上,C 另外提供了 `setjmp()` 和 `longjmp()` 来完成这种类型的分支跳转。 + +实现这种类型的跳转,有点类似于操作系统中任务的上下文切换,这里只需要恢复 label 标签所处函数的上下文即可,一般函数上下文包括: + +* 函数栈帧,主要是栈帧指针 BP 和栈顶指针 SP; +* 程序指针 PC,也就是修改为指向 label 语句处的地址; +* 其它寄存器,这和 CPU 的体系相关,例如在 x86 体系下需要保存 `AX` `BX` `CX` 等等。 + +在执行跳转语句时,直接恢复 label 处的上下文,即完成跳转到 label 处的功能。 + +在 C 语言中 `setjmp()` 和 `longjmp()` 就提供了完成保存上下文和切换上下文的工作。 + +http://www.cnblogs.com/hazir/p/c_setjmp_longjmp.html +gcc 关键字 __thread +https://blog.csdn.net/liuxuejiang158blog/article/details/14100897 + + + + + + + +类似于top命令,但是监控的是进程的网络带宽 +https://github.com/raboof/nethogs +https://zhoujianshi.github.io/articles/2017/Linux%20%E8%8E%B7%E5%8F%96TCP%E8%BF%9E%E6%8E%A5%E4%B8%8Epid%E7%9A%84%E6%98%A0%E5%B0%84%EF%BC%8C%E5%8F%8A%E7%9B%91%E6%B5%8B%E8%BF%9B%E7%A8%8B%E7%9A%84TCP%E6%B5%81%E9%87%8F/index.html + +其中 `/proc/net/dev` 包含了网络设备的相关统计,而 `/proc//net/dev` 包含了进程相关的统计信息。注意,如果进程添加到了网络的 namespace 中 (man ip-netns) ,那么进程中的文件就只有指定的网络设备。 + + +关于CacheLine的介绍 +http://cenalulu.github.io/linux/all-about-cpu-cache/ + + +https://blog.csdn.net/muxiqingyang/article/details/6615199 +http://www.pandan.xyz/2016/09/23/mesi%20%E7%BC%93%E5%AD%98%E4%B8%80%E8%87%B4%E6%80%A7%E5%8D%8F%E8%AE%AE/ + +### False Sharing +http://blog.yufeng.info/archives/783 + +## 参考 + +详细可以查看 Intel 的文档 [Avoiding and Identifying False Sharing Among Threads](https://software.intel.com/en-us/articles/avoiding-and-identifying-false-sharing-among-threads/) 中相关介绍,或者本地文档。 + + +虽然目前 Python 在 AI 领域使用的越来越多,不过其在 DevOps、安全领域使用的还比较多,例如可以通过 Python 脚本来简化日常的运维工作。 + +在 Linux 中可以通过多个命令查看当前系统的信息,例如 `ps`、`top`、`free` 等等,如果这样就需要通过类似 `subprocess` 的模块进行调用,并解析其返回的结果。 + +实际上,存在一个 psutil (process and system utilities) 可用来获取当前系统的信息,支持 Linux Unix OSX Windows 等多个平台。 + +这里简单介绍,并提供一个 C 库。 + +## psutil + +如果 pip 源配置好之后,可以直接通过 `pip install psutil` 命令安装,提供的接口详细可以查看 [GitHub psutil](https://github.com/giampaolo/psutil/) 中的介绍。 + +## libsysx + +主要是参考 Python 中的 [psutil](https://github.com/giampaolo/psutil/) 包使用以及实现方式,提供一个 C 实现的 API 接口,实现一个信号安全、可重入的库。 + + +Linux安全审计 +https://cisofy.com/downloads/lynis/ +https://dacat.cc/1984.html + + + +1. Cron +2. PS +3. lsof +4. Pipe + +Jaeger源码相关的解析 +https://github.com/jukylin/blog + +Interface Description Language, IDL 接口描述语言 + + +PSUtils 支持 Windows Mac Linux 等 +https://www.jianshu.com/p/64e265f663f6 + + + +Keeping Redundancy Effective, KRE 保持冗余有效性 + +源于多年的运维实践,因为每次回溯故障时,都会痛心地发现几乎所有的故障都是可以避免的,只要我们的多道防线中的任何一道发挥阻拦效用,这起故障就不可能引起服务中断。 + +假设每一道防护的失效概率是0.01%,同时失效的概率就降到了0.0001%。如果三层冗余,同时失效率就是0.000001%,几乎就是百年不遇的了。 + +1. 这个系统有没有采用两道以上的冗余保护? +2. 是否有机制确保每一道保护在任何时候都是有效的,一旦失效能够及时发现(自动检测、管理手段等)? +3. 是否有方法和工具用最短的时间恢复? + + + + + +glibc 提供了内置的位运算符。 + +int __builtin_ffs (unsigned int x) +返回x的最后一位1的是从后向前第几位,比如7368(1110011001000)返回4。 + +//----- 前导的0的个数 +int __builtin_clz(unsigned int x); + + +int __builtin_ctz (unsigned int x) +返回后面的0个个数,和__builtin_clz相对。 +int __builtin_popcount (unsigned int x) +返回二进制表示中1的个数。 +int __builtin_parity (unsigned int x) +返回x的奇偶校验位,也就是x的1的个数模2的结果。 + +此外,这些函数都有相应的usigned long和usigned long long版本,只需要在函数名后面加上l或ll就可以了,比如int __builtin_clzll。 + + + + + + + + + + + + + + + +https://www.zfl9.com/c-regex-pcre.html + +其中 `regex.h` 是一个正则表达式实现的头文件,提供的常用函数有 `regcomp()`、`regexec()`、`regfree()` 和 `regerror()` 。 + +注意,`regexec()` 函数在一次匹配成功之后就会返回,例如对于 IP 地址 `192.168.9.100` 来说,如果使用 `[0-9]+` 进行匹配会返回 `192` ,如果要匹配所有的数值,则需要使用 `([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)` 。 + +当使用最后的匹配时,返回的第一条是整个完整的匹配字符串,然后是返回的匹配子串,也就是依次返回 `192.168.9.100` `192` `168` `9` `100` 五个字符串。 + +/var/log/message 常见异常 + +\ 段错误 +\ OOM + +#include +#include +#include + +#define MATCH_ITEMS_MAX 32 + +int main(void) +{ + int rc, i; + regex_t regex; + regmatch_t matches[MATCH_ITEMS_MAX]; + + //const char *pattern = "([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)"; + //char buff[] = "192.168.9.100"; + + //const char *pattern = "([0-9]+)"; + //char buff[] = "192.168.9.100"; + + const char *pattern = "\\"; + char buff[] = "Got package size=100B."; + + rc = regcomp(®ex, pattern, REG_EXTENDED | REG_NEWLINE); + if (rc != 0) { + fprintf(stderr, "compile pattern '%s' failed, rc %d.\n", pattern, rc); + return -1; + } + + rc = regexec(®ex, buff, MATCH_ITEMS_MAX, matches, 0); + if (rc == REG_NOMATCH) { + fprintf(stderr, "no match, pattern '%s' string '%s'.\n", pattern, buff); + regfree(®ex); + return -1; + } + + //assert(rc == 0); + for (i = 0; i < MATCH_ITEMS_MAX; i++) { + if (matches[i].rm_eo < 0 || matches[i].rm_so < 0) + break; + fprintf(stderr, "got match string '%.*s'.\n", matches[i].rm_eo - matches[i].rm_so, + buff + matches[i].rm_so); + } + + regfree(®ex); + + return 0; +} + +https://segmentfault.com/a/1190000008125359 +https://www.cnblogs.com/charlieroro/p/10180827.html +https://github.com/digoal/blog/blob/master/201701/20170111_02.md + +/post/golang-syntax-interface-introduce.html + +接口继承 + +一个接口可以继承多个其它接口,如果要实现这个接口,那么就必须要其所继承接口中的方法都实现。 + +type Saying interface { + Hi() error +} + +type Notifier interface { + Saying + Notify() error +} + +func (u *User) Hi() error { + log.Printf("Hi %s\n", u.Name) + return nil +} + +上述报错的大致意思是说 --> + +这里的关键是 User 的 Notify() 方法实现的是 Pointer Receiver ,而实际需要的是 Value Receiver 。 + + +在官方文档 [golang.org/doc](https://golang.org/doc/effective_go.html#pointers_vs_values) 中有相关的介绍。 + +> The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. +> +> This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake. + +通过 Pointer Method 可以直接修改对象的值,而 Value Method 在执行前会复制一份对应的对象,并在复制后的对象上执行相关操作,而不会修改原对象的值。 + +原则上来说,这样定义也正常,但是很容易误用而且很难发现,所以 GoLang 放弃了这一特性。 + + +## 使用 + +接口对于 GoLang 来说关键是其实现了泛型,类似于 C++ 中的多态特性,对于函数可以根据不同类型的入参生成不同的对象。 + +注意,GoLang 是静态编程语言,会在编译过程中检查对应的类型,包括了函数、变量等,同时又有一定的灵活性。实际上处于纯动态语言 (例如 Python) 以及静态语言之间 (例如 C),可以在一定程度上进行语法检查,同时又提供了高阶功能。 + +通过 Go 的接口,可以使用 Duck Typing 方式编程。 + +Duck typing in computer programming is an application of the duck test—"If it walks like a duck and it quacks like a duck, then it must be a duck"—to determine if an object can be used for a particular purpose. With normal typing, suitability is determined by an object's type. In duck typing, an object's suitability is determined by the presence of certain methods and properties, rather than the type of the object itself. + +### 标准库 + +比较典型的示例可以参考 `io/io.go` 中的读写接口。 + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +很多的标准库会使用这一接口,包括了网络、编码等类型,这里简单介绍 `encoding/binary` 的使用,其中 `Read()` 函数的声明为。 + +func Read(r io.Reader, order ByteOrder, data interface{}) error + +其中的 `r` 参数可以是任意一个支持 `type Reader interface` 的实现,例如,使用示例如下。 + +package main + +import ( + "bytes" + "encoding/binary" + "log" +) + +func main() { + var pi float64 + + buff := bytes.NewBuffer([]byte{0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40}) + if err := binary.Read(buff, binary.LittleEndian, &pi); err != nil { + log.Fatalln("binary.Read failed:", err) + } + log.Println(pi) +} + +如上,从新建的一个内存缓存中读取,并格式化,也可以是文件或者网络。也就是说,只要支持 `Read()` 函数即可 (包括入参等,一般称为签名 Signature ) ,对于 Python 来说编译阶段就会报错。 + +## 源码解析 + +在 `runtime/runtime2.go` 文件中定义了 `type iface struct` 以及 `type eface struct` 两个结构体。 + +type iface struct { + tab *itab + data unsafe.Pointer +} + +type eface struct { + _type *_type + data unsafe.Pointer +} + +分别表示包含方法以及不包含方法的接口。 + +// iface 含方法的接口 +type Person interface { + Print() +} + +// eface 不含方法的接口 +type Person interface {} +var person interface{} = xxxx实体 + +https://segmentfault.com/a/1190000017389782 + +## eface + +由两个属性组成:`_type` 类型信息;`data` 数据信息。 + +type eface struct { + _type *_type + data unsafe.Pointer +} + +其中 `_type` 是所有类型的公共描述,几乎所有的数据都可以抽象成 `_type` 。 + +## iface + +type iface struct { + tab *itab + data unsafe.Pointer +} + +https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/ + +SetDeadline +SetReadDeadline +SetWriteDeadline + +在 GoLang 提供的 net.Conn 结构中,提供了 Deadline 方法,包括了 + +其中 Deadline是一个绝对时间值,当到达这个时间的时候,所有的 I/O 操作都会失败,返回超时(timeout)错误。 + +https://colobu.com/2016/07/01/the-complete-guide-to-golang-net-http-timeouts/ + + +Answer to the Ultimate Question of Life, The Universe, and Everything. 42 + + +## Reference + +* [miniz](https://github.com/richgel999/miniz) Single C source file zlib-replacement library. + + + + +malloc产生SEGV问题排查方法 +https://blog.csdn.net/win_lin/article/details/7822762 + +https://eklitzke.org/memory-protection-and-aslr + +TIME_WAIT和端口复用 +https://www.cnblogs.com/kex1n/p/7437290.html +https://blog.csdn.net/u010585120/article/details/80826999 + +内存数据提取 +https://github.com/rek7/mXtract +https://github.com/hephaest0s/usbkill + +查找敏感信息 +https://www.freebuf.com/articles/system/23993.html +c++ pitfall + + +## 指针 + +### 数组指针 + +`int (*arr)[3]` 这定义了一个指向数组的指针,数组的元素必须是 3 。 + +#include + +int main(void) +{ + int (*ptr)[3], i, *data; + int array[3] = {1, 2, 3}; // size MUSTBE 3. + + ptr = &array; // ptr is a pointer to array. + for (i = 0; i < 3; i++) + printf("%d\n", (*ptr)[i]); // got the array first + + data = array; + for (i = 0; i < 3; i++) + printf("%d\n", data[i]); + + return 0; +} + +如上是容易出错的三个点: + +1. 数组的大小必须与声明的数组指针变量大小相同; +2. 因为ptr是一个数组指针,所以必须对数组取地址; +3. 由于ptr是数组指针,那么在获取数组中的元素时,需要先取地址,而且要加括号保证优先级。 + +后面是比较常用的使用方法,如果要传递给一个函数,那么数组的大小同样需要传递。 + + + + + + + +https://stackoverflow.com/questions/11167907/compression-in-openssl +https://blog.csdn.net/liujiayu2/article/details/51860184 + +SSH-Key的选择 +https://medium.com/@honglong/%E9%81%B8%E6%93%87-ssh-key-%E7%9A%84%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95-70ca45c94d8e + +很多不错的网络开发介绍 +http://www.52im.net/thread-50-1-1.html + +## 文件格式 + +假设下载的是一个 [CentOS 8](http://mirrors.163.com/centos/8/isos/x86_64/) 的镜像,可以直接下载。 + +协议简介,官方以及非官方 +https://wiki.theory.org/index.php/Main_Page +http://bittorrent.org/beps/bep_0003.html + +https://github.com/skeeto/bencode-c +https://github.com/amwales-888/ambencode +https://github.com/janneku/bencode-tools +https://github.com/willemt/heapless-bencode +https://github.com/somemetricprefix/tbl +https://segmentfault.com/a/1190000000681331 +https://github.com/Rudde/mktorrent + +其中比较关键的是 `announce` URL 以及 `info` 字典, + +MP3格式解析 +https://github.com/lieff/minimp3 +https://blog.csdn.net/u010650845/article/details/53520426 +https://www.cnblogs.com/ranson7zop/p/7655474.html + +GO客户端 +https://github.com/anacrolix/torrent +Tracker +https://github.com/chihaya/chihaya +https://github.com/masroore/opentracker +https://github.com/xaiki/opentracker +https://github.com/danielfm/bttracker +https://github.com/willemt/tracker-client +http://erdgeist.org/arts/software/opentracker/ +https://github.com/crosbymichael/tracker + +http://www.kristenwidman.com/blog/33/how-to-write-a-bittorrent-client-part-1/ +https://www.cnblogs.com/hnrainll/archive/2011/07/26/2117423.html + +https://blog.jse.li/posts/torrent/ +https://www.jianshu.com/p/22205fa24c9b +https://skerritt.blog/bit-torrent/ +µTorrent Vuze Deluge Transmission + +DFS非stack模式 +https://segmentfault.com/a/1190000010632749 +安全编译选项 +https://firmianay.gitbooks.io/ctf-all-in-one/doc/4.4_gcc_sec.html +https://blog.lao-yuan.com/2018/06/09/Linux-GCC%E5%AE%89%E5%85%A8%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6.html +https://blog.lao-yuan.com/2018/05/29/Linux%E4%B8%8B%E5%A0%86%E6%A0%88%E7%BB%93%E6%9E%84%E5%88%86%E6%9E%90.html + +* 不会存在环,即使存在不能存在总和为负值的环; +* 对于有 V 的节点的图,最多经过 V - 1 个边,此时退化成了链表; +* 最短路径上的较小段 (subpath) 也是最短路径。 + + +/post/program-c-gcc-security-options.html +VSDO随机化 +https://zhuanlan.zhihu.com/p/58419878 + + +为了方便调试,GDB 会自动关闭随机选项,可以通过 `set disable-randomization off` 打开该选项。 + +https://blog.csdn.net/Plus_RE/article/details/79199772 +https://yifengyou.gitbooks.io/learn-linux_exploit/ + +反ptrace +http://eternalsakura13.com/2018/02/01/ptrace/ +/proc//environ + +/post/kernel-memory-virtual-physical-map +/post/kernel-memory-management-from-userspace-view + + +文件 `/proc//maps` 显示了进程映射的内存区域和访问权限,通过 `proc_pid_maps_op` 实现,对应的函数为 `show_map()` ,对应内核中的 `task->mm->mmap` 链表。 + +https://blog.csdn.net/lijzheng/article/details/23618365 + +/post/charsets-encoding.html +https://upload.wikimedia.org/wikipedia/commons/d/dd/ASCII-Table.svg + +#include +#include +#include +#include +#include + +#include + +int strsplit(char *string, char **fields, size_t size) +{ + size_t i = 0; + char *ptr = string, *saveptr = NULL; + + while ((fields[i] = strtok_r(ptr, ", \t\r\n", &saveptr)) != NULL) { + ptr = NULL; + i++; + + if (i >= size) + break; + } + + return ((int)i); +} + +static void *read_data_range(int pid, void *start, void *end) +{ + long word; + void *data; + size_t len, offset; + + len = end - start; + if ((len % sizeof(void *)) != 0) { + fprintf(stderr, "malformed memory address, length %d.", len); + return NULL; + } + if (len > 1024 * 1024) + return NULL; + //fprintf(stdout, "read data from %p to %p, length %ld.\n", start, end, len); + + data = malloc(len); + if (data == NULL) { + fprintf(stderr, "malformed memory address, length %d.", len); + return NULL; + } + + errno = 0; + for (offset = 0; offset < len; offset += sizeof(long)) { + word = ptrace(PTRACE_PEEKTEXT, pid, start + offset, NULL); + if (word < 0 && errno != 0) { + fprintf(stderr, "peek text from %p failed, %d:%s.", + start + offset, errno, strerror(errno)); + free(data); + return NULL; + } + memcpy((uint8_t *)data + offset, &word, sizeof(word)); + } + + return data; +} +int main(void) +{ + FILE *maps; + int pid = 27898, rc, idx, len, i; + char path[128], line[1024], *fields[32], *end, *ptr, *data; + unsigned long long addr_start, addr_end; + + if (geteuid() != 0) { + fprintf(stdout, "Running as root is recommended."); + return -1; + } + + rc = snprintf(path, sizeof(path), "/proc/%d/maps", pid); + if (rc < 0 || rc >= (int)sizeof(path)) { + fprintf(stderr, "format maps filepath failed, rc %d.", rc); + return -1; + } + + maps = fopen(path, "r"); + if (maps == NULL) { + fprintf(stderr, "open map file '%s' failed, %d:%s.", path, errno, strerror(errno)); + return -1; + } + + // /proc//environ + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { + fprintf(stderr, "Attach to PID %d failed, %d:%s.", pid, errno, strerror(errno)); + fclose(maps); + return -1; + } + wait(NULL); + + while (feof(maps) == 0) { + if (fgets(line, sizeof(line), maps) == NULL) + break; + + rc = strsplit(line, fields, (sizeof(fields)/sizeof(fields[0]))); + if (rc < 2) { + fprintf(stderr, "invalid line '%s', at least 2 fields expect.", line); + break; + } + + if (strchr(fields[1], 'r') == 0) + continue; + fprintf(stderr, "======= %s\n", line); + + end = strchr(fields[0], '-'); + if (end == NULL) + continue; + *end = 0; + end++; + + errno = 0; + addr_start = strtoull(line, &ptr, 16); + if (line == ptr || errno != 0) { + fprintf(stderr, "convert start address '%s' failed, %d:%s.\n", + line, errno, strerror(errno)); + continue; + } + addr_end = strtoull(end, &ptr, 16); + if (end == ptr || errno != 0) { + fprintf(stderr, "convert end address '%s' failed, %d:%s.\n", + end, errno, strerror(errno)); + continue; + } + + data = read_data_range(pid, (void *)addr_start, (void *)addr_end); + if (data == NULL) + continue; + + len = addr_end - addr_start; + for (idx = 0, i = 0; i < len; i++) { + if (data[i] < ' ' || data[i] > '~') + continue; + data[idx++] = data[i]; + } + data[idx] = 0; + fprintf(stdout, "got data: %s\n", data); + + free(data); + //fprintf(stderr, "%p %p 0x%llx 0x%llx\n", line, ptr, addr_start, addr_end); + } + + fclose(maps); + + if (ptrace(PTRACE_DETACH, pid, NULL, NULL) < 0) { + fprintf(stderr, "Attach to PID %d failed, %d:%s.", pid, errno, strerror(errno)); + return -1; + } + + return 0; +} + +## 内存保护 + +简单来说,就是针对不同的场景设置内存的读写权限。 + +https://www.gnu.org/software/libc/manual/html_node/Memory-Protection.html +https://www.informit.com/articles/article.aspx?p=23618&seqNum=10 +https://www.cnblogs.com/rim99/p/5523289.html +https://unix.stackexchange.com/questions/211951/how-does-the-kernel-prevent-a-malicious-program-from-reading-all-of-physical-ram + +ASLR实现以及漏洞分析 +https://www.cnblogs.com/wangaohui/p/7122653.html +https://www.freebuf.com/articles/system/228731.html + +## 参考 + +* [Linux Kernel Memory Protection](http://ijcsit.com/docs/Volume%205/vol5issue04/ijcsit20140504225.pdf) + +检测在什么样的虚拟机里的脚本 +https://www.freebuf.com/articles/network/229040.html + + +Non-Deterministic Polynomial Complete Problem, NPC 问题 +Non-Deterministic Polynomially, NP 是指一个问题不能确定是否在多项式时间内找到答案,但是可以在多项式时间内验证答案是否正确,如完全子图问题、图着色问题、旅行商(TSP)问题等。 + +之所以要定义 NP 问题,是因为通常只有 NP 问题才可能找到多项式的算法,不能指望一个连多项式验证一个解都不行的问题存在一个解决它的多项式级的算法。 + + +http://halobates.de/memorywaste.pdf +--> + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2012-03-06-mysql-page-cleaner_init.md b/_drafts/2012-03-06-mysql-page-cleaner_init.md new file mode 100644 index 0000000..22b05e6 --- /dev/null +++ b/_drafts/2012-03-06-mysql-page-cleaner_init.md @@ -0,0 +1,290 @@ +--- +title: Hello World !!! +layout: post +comments: true +language: chinese +category: [mysql, database] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + +MySQL 5.6.2 引入了一个新的后台线程 page_cleaner,将之前主线程中的 adaptive flushing、前端线程触发的 async flushing、空闲时的刷新、关闭刷新都是在这个线程完成;目前只有同步刷新放在了前台的用户查询线程中。 + +同时,引入了多个计数器。 + +{% highlight text %} +mysql> SELECT name, comment FROM information_schema.innodb_metrics WHERE name LIKE 'buffer_flush_%'; ++---------------------------------------+-----------------------------------------------------------------+ +| name | comment | ++---------------------------------------+-----------------------------------------------------------------+ +| buffer_flush_batch_scanned | Total pages scanned as part of flush batch | +| buffer_flush_batch_num_scan | Number of times buffer flush list flush is called | +| buffer_flush_batch_scanned_per_call | Pages scanned per flush batch scan | +| buffer_flush_batch_total_pages | Total pages flushed as part of flush batch | +| buffer_flush_batches | Number of flush batches | +| buffer_flush_batch_pages | Pages queued as a flush batch | +| buffer_flush_neighbor_total_pages | Total neighbors flushed as part of neighbor flush | +| buffer_flush_neighbor | Number of times neighbors flushing is invoked | +| buffer_flush_neighbor_pages | Pages queued as a neighbor batch | +| buffer_flush_n_to_flush_requested | Number of pages requested for flushing. | +| buffer_flush_n_to_flush_by_age | Number of pages target by LSN Age for flushing. | +| buffer_flush_adaptive_avg_time_slot | Avg time (ms) spent for adaptive flushing recently per slot. | +| buffer_flush_adaptive_avg_time_thread | Avg time (ms) spent for adaptive flushing recently per thread. | +| buffer_flush_adaptive_avg_time_est | Estimated time (ms) spent for adaptive flushing recently. | +| buffer_flush_avg_time | Avg time (ms) spent for flushing recently. | +| buffer_flush_adaptive_avg_pass | Numner of adaptive flushes passed during the recent Avg period. | +| buffer_flush_avg_pass | Number of flushes passed during the recent Avg period. | +| buffer_flush_avg_page_rate | Average number of pages at which flushing is happening | +| buffer_flush_lsn_avg_rate | Average redo generation rate | +| buffer_flush_pct_for_dirty | Percent of IO capacity used to avoid max dirty page limit | +| buffer_flush_pct_for_lsn | Percent of IO capacity used to avoid reusable redo space limit | +| buffer_flush_sync_waits | Number of times a wait happens due to sync flushing | +| buffer_flush_adaptive_total_pages | Total pages flushed as part of adaptive flushing | +| buffer_flush_adaptive | Number of adaptive batches | +| buffer_flush_adaptive_pages | Pages queued as an adaptive batch | +| buffer_flush_sync_total_pages | Total pages flushed as part of sync batches | +| buffer_flush_sync | Number of sync batches | +| buffer_flush_sync_pages | Pages queued as a sync batch | +| buffer_flush_background_total_pages | Total pages flushed as part of background batches | +| buffer_flush_background | Number of background batches | +| buffer_flush_background_pages | Pages queued as a background batch | ++---------------------------------------+-----------------------------------------------------------------+ +31 rows in set (0.01 sec) +{% endhighlight %} + + + + + +page_cleaner线程负责刷脏,基本上是基于如下的两个因素: +1. 没有空闲缓存页,需要按照LRU规则将最近最少使用的页(the least recently used pages)从LRU_list上移除,因此也被称为LRU_list刷新; +2. 需要重用redo log的空间,现在多数(包括InnoDB)数据库都是循环使用redo空间,如果要重用,只有保证redo对应的脏页已经刷新到磁盘才可以,也就是将the oldest modified non-flushed pages从flush_list上移除,被称之为flush_list; + +在进行刷脏时,会导致IO出现尖刺,进而影响到redo log的刷盘,从而影响到系统的性能;为了解决这一问题,引入了 adaptive flushing 策略,这一策略主要作用于 flush_list 的刷脏,当然对 LRU_list 的刷脏也有一些影响。 + + +## Page Cleaner + +为了提升刷脏效率,在 MySQL 5.7.4 版本里引入了多个 page cleaner 线程,从而可以达到并行刷脏的效果。采用协调线程+工作线程模式,协调线程本身也是工作线程,可通过 ```innodb_page_cleaners``` 变量设置,例如设置为 8 时就是一个协调线程,加 7 个工作线程。 + +{% highlight text %} +buf_flush_page_cleaner_coordinator() 该函数基本上由page_cleaner每隔1s调用一次 + |-buf_flush_page_cleaner_set_priority() 设置线程的优先级,默认为-20 + |-page_cleaner_flush_pages_recommendation() + | |-af_get_pct_for_dirty() 需要刷新多个页 + | | |-buf_get_modified_ratio_pct() + | | |-buf_get_total_list_len() + | | + | |-af_get_pct_for_lsn() 计算是否需要进行异步刷redo log + | |-log_get_max_modified_age_async() + | + |-pc_request() + |-pc_flush_slot() 协调线程同时也处理请求 + |-pc_wait_finished() 等待刷新完成 + +buf_flush_page_cleaner_worker + |-my_thread_init() + |-buf_flush_page_cleaner_set_priority() 设置线程的优先级,默认为-20 + | ###BEGIN###while + |-os_event_wait() 等待page_cleaner->is_requested事件 + |-pc_flush_slot() 刷新 + | ###BEGIN###end +{% endhighlight %} + +在启动时会初始化一个slot数组,大小为buffer pool instance的个数(buf_flush_page_cleaner_init)。 + +协调线程在决定了需要flush的page数和lsn_limit后,会设置slot数组,将其中每个slot的状态设置为PAGE_CLEANER_STATE_REQUESTED, 并设置目标page数及lsn_limit,然后唤醒worker线程 (pc_request) + +worker线程被唤醒后,从slot数组中取一个未被占用的slot,修改其状态,表示已被调度,然后对该slot所对应的buffer pool instance进行操作。 + +为了支持对单个bp instance进行LRU/FLUSH_LIST的刷新,对原有代码做了大量的改动,worker线程可以直接调用buf_flush_LRU_list 及buf_flush_do_batch 指定buffer pool进行flush操作。 互相之间不干扰,因此可以并行刷脏。 改动整体而言比较简单。 + + +page_cleaner线程负责刷脏,基本上是基于如下的两个因素: +1. 最近最少(the least recently used pages )使用的页将会从LRU_list上移除; +2. the oldest modified non-flushed pages从flush_list上移除; + +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb *** + +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_6_multi_threaded + +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_5_innodb_adaptive + +https://blogs.oracle.com/mysqlinnodb/entry/introducing_page_cleaner_thread_in + + + + + + +buf_flush_wait_batch_end() + + +#define PCT_IO(p) ((ulong) (srv_io_capacity * ((double) (p) / 100.0))) + + + +af_get_pct_for_lsn()计算方法涉及变量 +srv_adaptive_flushing_lwm + +srv_flushing_avg_loops + +storage/innobase/log/log0log.cc + +max_modified_age_sync + + |log_write_up_to() + |-log_write_flush_to_disk_low() + |-fil_flush() + + +#####FLUSH_LRU_LIST Checkpoint +srv_LRU_scan_depth + + +#####Async/Sync Flush Checkpoint +log_free_check() 用户线程调用 + |-log_check_margins() + |-log_flush_margin() + | |-log_write_up_to() + |-log_checkpoint_margin() 执行sync操作,尝试空出足够的redo空间,避免checkpoint操作,可能会执行刷脏操作 + |-log_buf_pool_get_oldest_modification() 获取BP中最老的lsn,也就是LSN4 + | |-buf_pool_get_oldest_modification() 遍历各个BP实例,找出最大lsn,如果刚初始化完成则返回sys->lsn + | 计算log->lsn-oldest_lsn,如果超过了max_modified_age_sync值,则执行sync操作 + +log_checkpoint_margin 核心函数,用于判断当前age情况,是否需要执行异步甚至是同步刷新。 + +buff async/sync是在前面,因为redo的刷新成本更低 + +buf_pool_resize() BP调整大小时的操作 + |-buf_pool_withdraw_blocks() + + +innodb_adaptive_flushing +innodb_adaptive_flushing_lwm 百分比,配置自适应flush机制的低水位(low water mark),超过该限制之后,即使没有通过上述参数开启AF,仍然执行AF +innodb_io_capacity +innodb_io_capacity_max redo 刷盘的最大值,如果刷盘落后很多,那么IO可能会超过innodb_io_capacity而小于max +innodb_max_dirty_pages_pct 刷脏时,需要保证没有超过该值;注意,该值是一个目标,并不会影响刷脏的速率。 +innodb_max_dirty_pages_pct_lwm 脏页的低水位,用于决定什么时候开启pre-flush操作,从而保证不会超过上面配置的百分比 +innodb_flushing_avg_loops 决定了利用上述的值循环多少次之后重新计算dirty page和LSN,次数越少对外部的动态变化就越敏感 + + +要刷新多少page和lsn主要代码在af_get_pct_for_dirty()和af_get_pct_for_lsn()中,其中主要控制adaptive flush的代码位于后者函数中。 + +http://www.cnblogs.com/Amaranthus/p/4450840.html + +1.先判断redo log的容量是否到了innodb_adaptive_flushing_lwm低水位阀值。 +2.是否配置了adaptive flush或者age超过了异步刷新的阀值。 +3.lsn_age_factor=age占异步刷新阀值的比例。 +4.要被刷新的比率=innodb_io_capacity_max/innodb_io_capacity*lsn_age_factor* sqrt(innodb_io_capacity)/7.5 + + + + + + + + + + +https://www.percona.com/blog/2013/10/30/innodb-adaptive-flushing-in-mysql-5-6-checkpoint-age-and-io-capacity/ ******** + +https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb *** + +https://yq.aliyun.com/articles/64677 + +http://mysql.taobao.org/monthly/2015/06/01/ + +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb *** + +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_6_multi_threaded + +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_5_innodb_adaptive + + + + + + + + + + + +MySQL buffer pool里的三种链表和三种page + + +buffer pool是通过三种list来管理的 + +1) free list +2) lru list +3) flush list + + + +Buffer Pool 中的最小单位是 page,总共分为三种类型,包括了: + +* free page
预分配,此类 page 未被使用,位于 free 链表中; +* clean page
该类型的 page 对应数据文件中的一个页面,但是页面没有被修改,位于 lru 链表中; +* dirty page
对应数据文件中的一个页面,但是页面已经被修改过,此种类型 page 位于 lru 链表和 flush 链表中。 + +在 flush list 中存在的页只能是脏页,而且最近修改的页保存在链表头部,当页面修改时,都会被封装为一个 mtr ,在提交的时候,则 mtr 涉及到的页面就会添加到 flush 链表的头部。 + + + + +buffer pool lru list的工作原理 + +总的来说每当一个新页面被读取buffer pool之后,MySQL数据库InnoDB存储引擎都会判断当前buffer pool的free page是否足够,若不足,则尝试flush LRU链表。 + +在MySQL 5.6.2之前,用户线程在读入一个page (buf_read_page)、新建一个page(buf_page_create)、预读page(buf_read_ahead_linear) 等等操作时,都会在操作成功之后,调用buf_flush_free_margin函数,判断当前buffer pool是否有足够的free pages,若free pages不足,则进行LRU list flush,释放出足够的free pages,保证系统的可用性。 + + +通过判断当前buf pool中需要flush多少dirty pages,才能够预留出足够的可被替换的页面(free pages or clean pages in LRU list tail)。 + + + +说明: +可用pages由以下两部分组成: +1. buf pool free list中的所有page,都是可以立即使用的。 +2. buf pool LRU list尾部(5+2*BUF_READ_AHEAD_AREA)所有的clean pages。 +其中:BUF_READ_AHEAD_AREA为64,是一个linear read ahead读取的大小,1 extent + + + +由于buf_flush_free_margin函数是在用户线程中调用执行的,若需要flush LRU list,那么对于用户的响应时间有较大的影响。因此,在MySQL 5.6.2之后,InnoDB专门开辟了一个page cleaner线程,处理dirty page的flush动作(包括LRU list flush与flush list flush),降低page flush对于用户的影响。 + +在MySQL 5.6.2前后的版本中,LRU list flush的不同之处在于是由用户线程发起,还是有后台page cleaner线程发起。但是,无论是用户线程,还是后台page cleaner线程,再决定需要进行LRU list flush之后,都会调用buf_flush_LRU函数进行真正的flush操作。 + + +不同之处在于,MySQL 5.6.2之前,用户线程调用的buf_flush_free_margin函数,在判断是否真正需要进行LRU list flush时,将LRU list tail部分的clean pages也归为可以被replace的pages,不需要flush。而在page cleaner线程中,每隔1s,无论如何都会进行一次LRU list flush调用,无论LRU list tail中的page是否clean。这也可以理解,用户线程,需要尽量降低flush的概率,提高用户响应;而后台线程,尽量进行flush尝试,释放足够的free pages,保证用户线程不会堵塞。 + + + +Buffer Pool LRU/Flush List flush对比 + +1).LRU list flush,由用户线程触发(MySQL 5.6.2之前);而Flush list flush由MySQL数据库InnoDB存储引擎后台srv_master线程处理。(在MySQL 5.6.2之后,都被迁移到page cleaner线程中) + +2).LRU list flush,其目的是为了写出LRU 链表尾部的dirty page,释放足够的free pages,当buf pool满的时候,用户可以立即获得空闲页面,而不需要长时间等待;Flush list flush,其目的是推进Checkpoint LSN,使得InnoDB系统崩溃之后能够快速的恢复。 + +3).LRU list flush,其写出的dirty page,需要移动到LRU链表的尾部(MySQL 5.6.2之前版本);或者是直接从LRU链表中删除,移动到free list(MySQL 5.6.2之后版本)。Flush list flush,不需要移动page在LRU链表中的位置。 + +4).LRU list flush,由于可能是用户线程发起,已经持有其他的page latch,因此在LRU list flush中,不允许等待持有新的page latch,导致latch死锁;而Flush list flush由后台线程发起,未持有任何其他page latch,因此可以在flush时等待page latch。 + +5).LRU list flush,每次flush的dirty pages数量较少,基本固定,只要释放一定的free pages即可;Flush list flush,根据当前系统的更新繁忙程度,动态调整一次flush的dirty pages数量,量很大。 + + + +buffer pool free list工作原理 + +free链表里存放的是空闲页面,初始化的时候申请一定数量的page,在使用的过程中,每次成功load页面到内存后,都会判断free page是否够用,如果不够用的话,就flush lru链表和flush链表来释放free page,这就可以满足其他进程在申请页面,使系统可用。 + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2013-09-14-git-codereview_init.md b/_drafts/2013-09-14-git-codereview_init.md new file mode 100644 index 0000000..64499cc --- /dev/null +++ b/_drafts/2013-09-14-git-codereview_init.md @@ -0,0 +1,164 @@ +--- +title: Git 简明教程 +layout: post +comments: true +language: chinese +category: [misc] +keywords: flask,示例 +description: 记录 Flask 常见的示例,可以用来作为参考使用。 +--- + + + + + +## 简介 + +![git code review openstack]({{ site.url }}/images/misc/git-code-review-openstack.png "git code review openstack"){: .pull-center width="80%" } + + + +git-review 工具是一组 git 子命令,主要用于 OpenStack 代码与 gerrit (review系统) 交互,可以在后面添加 -v 参数打印所有运行的 git 命令。 + +### 配置GIT + +{% highlight text %} +----- 设置好全局参数,简化以后操作 +$ git config --global user.name 'YourName' +$ git config --global user.email example@example.com +$ git config --global gitreview.username YourName + +----- 确认下配置项 +$ gir config --list + +----- 克隆源码 +$ git clone https://github.com/openstack/FOOBAR.git + +----- 切换到源码目录下 +$ cd FOOBAR + +----- 建立git-review环境 +$ git review -s +{% endhighlight %} + +check out 到master分支,更新远端并将其pull到本地的master分支 +$ git checkout master; git remote update; git pull origin master + + + +6. 在Launchpad上report 一个新的bug, 或者找一个尚未被解决的bug然后将它assign给自己,将bug的状态改为In progress, OpenStack使用Launchpad记录Blueprints和报告bugs。 +7. 想要fix某个bug,就必须新建一个分支,然后在这个分支里对源代码进行修改,例如: +$ git checkout -b fix-bug-#123456 +上述命令创建并切换到新分支“fix-bug-#123456”,接下来所有的本地工作在这个分支里进行,直到所有fixation都完成后再commit, +$ git commit -a + +提交时会要求输入commit message,commit message可以有下面的字段: +Implements: blueprint BLUEPRINT +Closes-Bug: #123456 +Partial-Bug: #123456 +Related-Bug: #123456 +通过这些字段来标识自己工作相关的bug或者blueprint,一旦注明,CI系统会自动将你的commit和相同ID的bug对应起来。 + +上面的命令提交到本地repo后接下来就是push到Gerrit了。 +$ git review -v + +Gerrit是OpenStack远端Git仓库的一道大门,所有的submission都要在这里经过review后才能被merge到master分支中,因此之前的工作一定不能在master分支进行,这样会产生一个merge commit,Gerrit默认是不接受merge commit的。 + +如果提交成功,Gerrit将返回一个显示你此次提交内容的URL,打开它就可以查看commit以及reviewer的评价了:http://review.openstack.org/nnnnnn + +如果需要修改commit怎么办? + +此时需要到http://review.openstack.org上查找自己的patch记录,然后记下这一个patch的review number,就是review.openstack.org对应patch页面的后几位数字:https://review.openstack.org/#/c/nnnnnn/ + +$ cd ourTargetProjectName #切换到项目源码目录 +$ git review -d nnnnnn #把patch给check out,然后就可以编辑了 + + +接着根据reviewer们的意见重新编辑patch,然后提交 +$ git commit -a --amend #在本地commit +$ git review + +对上一次的commit进行了修改,或者commit message没有写标准,都可以重新提交commit,但是一定要切换到自己上次提交commit的分支执行上面的命令。如果希望查看完整的git命令流,可以在git review命令后添加 -v选项。 + + + + + + + + + +{% highlight text %} +----- 如果用户名和邮箱开始配置有问题,可以通过如下方式修改 +$ git commit --amend --author='foobar ' +{% endhighlight %} + +其它的一些常见的情况包括了:代码审核未通过,返回修改;代码有冲突,不能合入代码库。这些情况,其解决方法都类似,都可以通过 amend 解决。 +如果代码审核未通过,现在本地git log查看一下。最近的一条log是不是就是你要修改的那一个,是的话,OK,不 +是的话,git reset --soft commit_id到你需要修改的那一个commit记录。 +继续修改你要改的文件 +git add +git commit --amend +repo upload +三步,ok!注意如果你提交了3个文件,其中一个不过关,只需要修改、add 那一个文件就行。如果少提交了一个文件,也是add这个文件就ok了。 +如果你多提交了一个文件,处理方法: +mv filename newfilename #先把文件重命名,此时git status查看,可以看到多余commit的文件处于工作区delete状态。 +git commit -a --amend +然后git log --name-status -1 查看多余提交的文件已被撤销,此时可将之前重命名的文件再改回来重新upload后会生成一个patch set 2。 + + + + + + + + + +注意:当审核未通过打回时,我们再修改完成之后,执行: +git add 文件名 +git commit --amend ##注意会保留上次的 change-id ,不会生成新的评审任务编号,重用原有的任务编号,将该提交转换为老评审任务的新补丁集 +git review + + + + + + + + + + + +http://blog.csdn.net/agileclipse/article/details/38980419 + + + +## 参考 + +[OpenStack git-review](http://docs.openstack.org/infra/git-review/index.html) + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2014-02-01-network-collision-broadcast-domain_init.md b/_drafts/2014-02-01-network-collision-broadcast-domain_init.md new file mode 100644 index 0000000..2f774a0 --- /dev/null +++ b/_drafts/2014-02-01-network-collision-broadcast-domain_init.md @@ -0,0 +1,118 @@ +--- +Date: December 12, 2015 +title: 冲突域和广播域,以及网桥 +layout: post +comments: true +language: chinese +category: [network, linux] +--- + + + + +![network interface]({{ site.url }}/images/network/network-introduce-interface.png){: .pull-center width="500"} + + + + +# 以太网简介 + +目前经常使用的是总线型以太网,通信信道只有一个,采用 CSMA/CD 介质访问方法,也就是说,每个站点在发送数据之前首先要侦听网络是否空闲,如果空闲就发送数据,否则,继续侦听直到网络空闲为止。 + +如果两个站点同时检测到介质空闲并同时发送出一帧数据,则会导致数据帧的冲突,双方的数据帧均被破坏。这时,两个站点将采用 "二进制指数退避" 的方法各自等待一段随机的时间再侦听、发送。 + +![If the picture doesn't exist]({{ site.url }}/images/network/introduce_concepts_ethernet_general.jpg "The files in our folder"){: .pull-center} + +如上图,主机 A 只是想发送一个单播数据包给主机 B,但由于传统以太网的广播性质,接入到总线上的所有主机都将收到此单播数据包。同时,此时如果任何第二方,包括主机 B 也要发送数据到总线上都将冲突,导致双方数据发送失败。我们称连接在总线上的所有主机共同构成了一个冲突域。 + +当主机 A 发送一个目标是所有主机的广播类型数据包时,总线上的所有主机都要接收该广播数据包,并检查广播数据包的内容,如果需要的话加以进一步的处理。我们称连接在总线上的所有主机共同构成了一个广播域。 + +* 冲突域:在同一个冲突域中的每一个节点都能收到所有被发送的帧,位于第一层(物理层)。 + +* 广播域:网络中能接收任一设备发出的广播帧的所有设备的集合,位于第二层(数据链路层)。 + +总的来说,冲突域就是连接在同一导线上的所有工作站的集合,或者说是同一物理网段上所有节点的集合,或以太网上竞争同一带宽的节点集合。比如某台特定设备在网段上发送一个数据包,迫使同一个网段上的其他设备都必须注意到这一点,在同一时刻,如果两台不同的设备试图发送数据包,就会发生冲突,此后,两台设备都必须重新发送数据包,同一时刻只能有一台设备发送。 + + + + + + + + + + + + + + + + + + + +## 中继器 (Repeater) + +通常中继器只有两个端口,只是简单的将一个端口收到的信号传送给另外一个,它并不知道所谓的 frame、packet 等,其主要作用是:1)扩展网络距离,将衰减信号经过再生;2)实现粗细同轴电缆以太网的互连。 + +现在中继器已经很少见了,主要是由于网桥提供了相似的功能,而且价格基本相似。 + + +通过中继器虽然可以延长信号传输的距离、实现两个网段的互连。但并没有增加网络的可用带宽。如图2所示,网段1和网段2经过中继器连接后构成了一个单个的冲突域和广播域。

+ +


+中继器连接的网络

+

+ +

集线器(HUB)

+集线器实际上相当于多端口的中继器,通常有8个、16个或24个等数量不等的接口。可以延长网络的通信距离,或连接物理结构不同的网络,但主要还是作为一个主机站点的汇聚点,将连接在集线器上各个接口上的主机联系起来使之可以互相通信。它的每个端口是分享带宽的,也就是说,要是100mb的hub,10个端口,那每个口就只有10mb的带宽。

+ +如图3所示,所有主机都连接到中心节点的集线器上构成一个物理上的星型连接。但实际上,在集线器内部,各接口都是通过背板总线连接在一起的,在逻辑上仍构成一个共享的总线。因此,集线器和其所有接口所接的主机共同构成了一个冲突域和一个广播域。 + +



+集线器连接的网络

+

+ + + +## 网桥 () + + +

网桥(Bridge)

+可以隔离冲突域,不能隔离广播域。

+ +网桥(Bridge)又称为桥接器。和中继器类似,传统的网桥只有两个端口,用于连接不同的网段。和中继器不同的是,网桥具有一定的"智能"性,可以"学习"网络上主机的地址,同时具有信号过滤的功能。

+ +如图4所示,网段1的主机A发给主机B的数据包不会被网桥转发到网段2。因为,网桥可以识别这是网段1内部的通信数据流。同样,网段2的主机X发给主机Y的数据包也不会被网桥转发到网段1。可见,网桥可以将一个冲突域分割为两个。其中,每个冲突域共享自己的总线信道带宽。

+ +但是,如果主机C发送了一个目标是所有主机的广播类型数据包时,网桥要转发这样的数据包。网桥两侧的两个网段总线上的所有主机都要接收该广播数据包。因此,网段1和网段2仍属于同一个广播域。 + +



+ 网桥连接的网络

+

+ + + +{% highlight c %} +{% endhighlight %} diff --git a/_drafts/2014-02-10-network-socketfs_init.md b/_drafts/2014-02-10-network-socketfs_init.md new file mode 100644 index 0000000..07181cf --- /dev/null +++ b/_drafts/2014-02-10-network-socketfs_init.md @@ -0,0 +1,339 @@ +--- +Date: December 12, 2015 +title: Linux 中的 socketfs +layout: post +comments: true +language: chinese +category: [network, linux] +--- + +BSD socket 是用户程序与网络协议栈之间的接口层,用户通过调用 socket API 将报文传给协议栈,以及从协议栈读取报文。实际上,Linux 对于网络提供了一个与虚拟文件系统相似的接口,也就是可以通过 socket 接口打开一个类似的文件,而内核中实际是通过 sockfs 文件系统实现的。 + +接下来我们就在这篇文章中查看下与 socketfs 相关的内容。 + + + +# Socket层,系统调用 + +系统调用 socket()、bind()、connect()、accept()、send()、release() 等都是在 net/socket.c 实现的,下面介绍通过 socket 层和用户的衔接。 + +对于这么多的协议,都是通过 socket() 向用户提供统一的接口,下面是一个典型的 TCP 协议通讯。 + +{% highlight text %} +# 服务端 +listenfd = socket(AF_INET, SOCK_STREAM, 0); # 新建socket +bind(sock_descriptor, servaddr, size); # 绑定端口 +listen(listenfd, 5); # 开始监听端口 +accept(listenfd, cliaddr, clilen); # 接收新请求 + +# 客户端 +sock_descriptor = socket(AF_INET, SOCK_STREAM, 0); # 新建socket +connect(sock_descriptor, sockaddr, size); # 与服务端建立链接 +send(sock_descriptor, "hello world"); # 发送数据 +recv(sock_descriptor, buffer, 1024, 0); # 接收数据 +{% endhighlight %} + +socket() 的声明和实现如下,三个参数分别为协议族、协议类型 (面向连接或无连接) 以及协议。 + +{% highlight text %} +int socket(int domain, int type, int protocol); +{% endhighlight %} + +## socketfs 初始化 + +对于用户态而言, 返回的 socket 就是一个特殊的已经打开的文件,为了对 socket 抽像出文件的概念,内核中为 socket 定义了一个专门的文件系统类型 sockfs 。 + +{% highlight c %} +static struct vfsmount *sock_mnt __read_mostly; +static struct file_system_type sock_fs_type = { + .name = "sockfs", + .get_sb = sockfs_get_sb, + .kill_sb = kill_anon_super, +}; +{% endhighlight %} + +socket 系统的初始化通过 sock_init() 函数完成,通过如下程序实现。 + +{% highlight c %} +core_initcall(sock_init); +{% endhighlight %} + +通过 core_initcall() 宏实现,其中定义的函数会在系统初始化时调用,详细可以参考 [Linux initcall 机制实现](linux-initcalls),在通过 sock_init() 模块初始化的时候,会安装该文件系统。 + +{% highlight text %} +sock_init() + |-net_sysctl_init() + |-skb_init() + |-init_inodecache() + |-register_filesystem() # 将文件系统添加到一个列表中 + |-kern_mount() # 挂载文件系统 + |-kern_mount_data() + |-vfs_kern_mount() + |-alloc_vfsmnt() + |-mount_fs() # 返回root dentry +{% endhighlight %} + +在 vfs_kern_mount() 中,会申请文件系统 mnt 结构,调用之前注册的 sock_fs_type 的 get_sb(),获取相应的超级块,并将 mnt->mnt_sb 指向 sock_fs_type 中的超级块。 + +这里就是先获取/分配一个超级块,然后初始化超级块的各成员,包括 s_op,它封装了对应的功能函数表。s_op 自然就指向了 sockfs_ops,那前面提到的 new_inode() 函数分配 inode 时调用的,这个函数实际对应 sock_alloc_inode() 函数。 + +{% highlight c %} +sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb); +{% endhighlight %} + +可以看到 sock_alloc_inode() 是如何分配一个 inode 节点的,函数先分配了一个用于封装 socket 和 inode 的 ei,然后在高速缓存中为之申请了一块空间,这样 inode 和 socket 就同时都被分配了。 + +至目前为止,分配 inode、socket 以及两者如何关联,都已一一分析了。最后一个关键问题,就是如何把 socket 与一个已打开的文件,建立映射关系。 + + +sys_socketcall() 包含了所有 socket API 的入口。 + + +# socket() + +有了文件系统后,对内核而言,创建一个 socket,就是在 sockfs 文件系统中创建一个文件节点(inode),并建立起为了实现 socket 功能所需的一整套数据结构,包括 struct inode 和 struct socket。而 struct socket 结构在内核中就代表了一个 socket,然后再将其与一个已打开的文件 "建立映射关系",这样,用户态就可以用抽像的文件的概念来操作 socket 了。 + +而用户看到的是一个类似于文件描述符的 int 类型。在内核中 struct task_struct current 用来表示当前进程,用 struct file 描述一个已经打开的文件,当然一个进程可以打开多个文件,所以通过 struct file *fd_array[] 表示,文件描述符即对应该数组的下标。 + +通过文件描述符即可以找到对应内核中的 struct file 结构。 + +因此,对于网络编程需要做的是将 socket 与一个已经打开的文件建立映射,也就是为 socket 分配一个 struct file 以及相应的文件描述符 fd 。 + +如前所述,一个 socket 总是与一个 inode 密切相关的,为此内核引入了一个 socket_alloc 结构,当已知一个 inode 可以通过宏 SOCKET_I() 获取对应的 socket 。 + +{% highlight c %} +struct socket_alloc { + struct socket socket; + struct inode vfs_inode; +}; +sock = SOCKET_I(inode); +static inline struct socket *SOCKET_I(struct inode *inode) +{ + return &container_of(inode, struct socket_alloc, vfs_inode)->socket; +} +{% endhighlight %} + +这也同时意味着在正常分配一个 inode 后,必须再分配一个 socket_alloc 结构,并实现对应的封装。 + +接着申请分配一个相应的文件描述符 fd,因为 socket 并不支持 open() 方法,所以不能期望用户通过调用 open() API 分配一个 struct file,而是 sock_alloc_file() 获取,并通过让 current 的 files 指针的 fd 数组的 fd 索引项指向该 file 。 + +{% highlight text %} +sys_socket(family, type, protocol) + |-sock_create() # 分配inode和socket_alloc + | |-__sock_create() + | |-security_socket_create() # 调用安全接口,一般是selinux,可忽略 + | |-socket *sock=sock_alloc() # 主要的分配函数,分配一个struct socket + | | |-new_inode_pseudo() # 通过sock_mnt->mnt_sb生成一个inode + | | | |-alloc_inode() # 新建inode,并添加到sb.s_inodes链表中 + | | |-socket *sock=SOCKET_I(inode) # 根据上述的inode,获取sock + | | + | |-sock->type=type + | |-pf=net_families[family] + | |-pf->create(..., protocol, ...) # 其中TCP对应了inet_create()函数 + | | |-inetsw[sock->type] # 遍厉该数组的对象 + | | |-sock->ops=answer->ops ### struct proto_ops类型,后续收发操作的主要函数 + | | |-sock *sk=sk_alloc() # 分配sock结构体 + | | + | |-security_socket_post_create() + | + |-sock_map_fd() + |-get_unused_fd_flags() # 获取未使用的fd + |-sock_alloc_file() # 分配一个struct file + |-fd_install() +{% endhighlight %} + +在 alloc_inode() 函数中,会调用 sock_mnt->mnt_sb->s_op->alloc_inode(sock_mnt->mnt_sb) 返回一个 inode 结构体,而该 alloc_inode() 函数实际对应的是 sockfs_ops 中的成员变量。 + +在 Linux 中,通过 net_families[] 数组标示不同的 family 类型,对于 TCP/IP 采用的是 AF_INET 。 + +{% highlight c %} +#define PF_UNIX AF_UNIX +#define PF_INET AF_INET + +static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly; + +static const struct net_proto_family inet_family_ops = { + .family = PF_INET, + .create = inet_create, + .owner = THIS_MODULE, +}; +{% endhighlight %} + +其中,仍以 TCP 为例,其中入参采用 SOCK_STREAM,其中的 inetsw_array[] 会在 inet_init() 函数中通过 inet_register_protosw() 函数注册到 static struct list_head inetsw[SOCK_MAX] 中。 + +{% highlight c %} +const struct proto_ops inet_stream_ops = { + .family = PF_INET, + .owner = THIS_MODULE, + .sendmsg = inet_sendmsg, + .recvmsg = inet_recvmsg, + ... ... +}; + +static struct inet_protosw inetsw_array[] = +{ + { + .type = SOCK_STREAM, + .protocol = IPPROTO_TCP, + .prot = &tcp_prot, + .ops = &inet_stream_ops, + .flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK, + }, + ... ... +}; + +static int __init inet_init(void) +{ + ... ... + for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q) + inet_register_protosw(q); + ... ... +} +{% endhighlight %} + +那么,在 sys_socket() 函数中就会根据 family、type 的参数,通过 sock->ops=answer->ops 进行赋值,后面包括报文的收发操作等,都是根据该结构体进行操作。 + + +## 小结 + +上面已经介绍了在协议栈中是如何选择相关操作函数的,在此仍然一步步查看下具体的排查过程。 + +首先,socket() 系统调用会传入三个参数,接口如下。 + +{% highlight c %} +SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) +{ + ... ... + retval = sock_create(family, type, protocol, &sock); + ... ... +} +{% endhighlight %} + +实际最终会调用如下的函数。 + +{% highlight c %} +int __sock_create(..., int family, int type, int protocol, ...) { + ... ... + struct socket *sock = sock_alloc(); + sokc->type = type; + + const struct net_proto_family *pf; + pf = rcu_dereference(net_families[family]); //## 关系到如下函数的调用 + + err = pf->create(net, sock, protocol, kern); + ... ... +} +{% endhighlight %} + +其中 net_families[] 数组通过 sock_register() 函数进行注册,通过如下命令看到所有注册的协议,在此以 AF_INET 或者 PF_INET 为例,注册的是 inet_family_ops 。 + +{% highlight text %} +$ cd net && grep -rne '\type], list) { + err = 0; + /* Check the non-wild match. */ + if (protocol == answer->protocol) { + if (protocol != IPPROTO_IP) + break; + } + } + ... ... + sock->ops = answer->ops; + ... ... +} +{% endhighlight %} + +而其中 inetsw[] 数组实际是通过 inetsw_array[] 注册的结果。 + + +# send() 类的实现 + +在 Linux 中,应用层可以使用以下 socket 函数来发送数据: + +{% highlight c %} +ssize_t write(int fd, const void *buf, size_t count); +ssize_t send(int s, const void *buf, size_t len, int flags); +ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); +ssize_t sendmsg(int s, const struct msghdr *msg, int flags); +int sendmmsg(int s, struct mmsghdr *msgvec, unsigned int vlen, unsigned int flags); +{% endhighlight %} + +当 flags 为 0 时,send() 和 write() 功能相同;而 sendto(..., NULL, 0) 功能与 send() 的功能相同;write() 和 send() 在套接字处于连接状态时可以使用,而 sendto()、sendmsg() 和 sendmmsg() 在任何时候都可用。 + +对于 send() 而言,其入口函数就是 sys_send() 。 + +{% highlight text %} +sys_send() + |-sys_sendto() + |-sockfd_lookup_light() # 通过文件描述符fd,找到对应的socket实例 + |-... ... # 初始化消息头信息 + |-move_addr_to_kernel() # 把套接字地址从用户空间拷贝到内核空间 + |-sock_sendmsg() # 调用统一的发送入口函数 + | |-__sock_sendmsg() + | |-__sock_sendmsg_nosec() + | |-sock->ops->sendmsg() + | + |-fput_light() + +{% endhighlight %} + +在 sockfd_lookup_light() 函数中,以 fd 为索引从当前进程的文件描述符表 files_struct 实例中找到对应的 file 实例,然后从 file 实例的 private_data 成员中获取 socket 实例。 + +其中 sock->ops->sendmsg() 操作相关的结构体如下,其中 sock->ops 在 sys_socket() 中初始化。 + +{% highlight c %} +struct socket { + ... ... + const struct proto_ops *ops; +}; + +struct proto_ops { + int family; + struct module *owner; + int (*release) (struct socket *sock); + int (*bind) (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len); + int (*connect) (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags); + int (*socketpair)(struct socket *sock1, struct socket *sock2); + int (*accept) (struct socket *sock, struct socket *newsock, int flags); + int (*listen) (struct socket *sock, int len); + int (*shutdown) (struct socket *sock, int flags); + int (*sendmsg) (struct kiocb *iocb, struct socket *sock, + struct msghdr *m, size_t total_len); + int (*recvmsg) (struct kiocb *iocb, struct socket *sock, + struct msghdr *m, size_t total_len, int flags); +}; +{% endhighlight %} + +其中 socket(AF_INET, SOCK_STREAM, 0) 会将 sock->ops 设置为 inet_stream_ops 变量,那么后续的所有操作都会使用该结构体定义的函数进行操作。 + +# 参考 + +Linux 内核协议栈 关于 socket 创建的过程详解,或者参考 Linux TCP/IP协议栈之Socket的实现分析(一 套接字的创建) 。 + +Socket 层实现 同样是内核 Socket 层的相关实现。 + +{% highlight c %} +{% endhighlight %} diff --git a/_drafts/2014-03-02-linux-initcalls_init.md b/_drafts/2014-03-02-linux-initcalls_init.md new file mode 100644 index 0000000..89c9ef5 --- /dev/null +++ b/_drafts/2014-03-02-linux-initcalls_init.md @@ -0,0 +1,14 @@ +--- +Date: October 19, 2013 +title: Linux initcall 机制 +layout: post +comments: true +language: chinese +category: [linux] +--- + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2014-04-03-how-network-driver-works_init.md b/_drafts/2014-04-03-how-network-driver-works_init.md new file mode 100644 index 0000000..a95d642 --- /dev/null +++ b/_drafts/2014-04-03-how-network-driver-works_init.md @@ -0,0 +1,465 @@ +--- +Date: October 19, 2013 +title: Linux 网卡驱动的工作原理 +layout: post +comments: true +category: [linux, network] +language: chinese +--- + +如题所示,介绍网卡是如何接收数据,然后又是如何交给上层处理。因为鄙人的网卡驱动用的是 e1000e,所以就以此为例了。 + +Just enjoy it. + + + +网络设备有很多,但它们的工作原理基本相同,可以分为两个层次,分别为 MAC (Media Access Control) 对应于 OSI 的数据链路层,PHY (Physical Layer) 对应于物理层。 + +同时为了减小 CPU 的压力,底层很多采用 DMA 方式。 + + +# 查看驱动 + +对于网卡驱动,通常是以内核模块的方式提供,本机网卡对应的内核驱动可以通过如下方式查看。 + +{% highlight text %} +----- 可以查看Ethernet、Wireless字样,以及Kernel driver in use:(也就是所使用的驱动) +# lspci -vvv | less +... ... +00:19.0 Ethernet controller: Intel Corporation Ethernet Connection I218-LM (rev 04) + Subsystem: Dell Device 05cb + Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+ + Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- SERR- +... ... + +----- 查看对应的中断信息 +$ cat /proc/interrupts + CPU0 CPU1 CPU2 CPU3 +... ... + 46: 853 0 930 0 PCI-MSI-edge eth0 +... ... +{% endhighlight %} + +如上可以看到硬件的信息,以及所使用的驱动信息。 + + +# 源码解析 + +接下来查看下 e1000e 相关的驱动。 + +## 网络初始化 + +网络设备相关的驱动在内核的 drivers/net 目录下,如上的 e1000e 驱动在 net/ethernet/intel/e1000e 目录下,接下来我们就以此为例。 + +首先是模块初始化函数,也就是 module_init() 宏指定的相关函数。 +{% highlight c %} +char e1000e_driver_name[] = "e1000e"; +static struct pci_driver e1000_driver = { + .name = e1000e_driver_name, // 驱动名称 + .id_table = e1000_pci_tbl, // 指定了那些设备可以使用该驱动 + .probe = e1000_probe, + .remove = e1000_remove, + .driver = { + .pm = &e1000_pm_ops, + }, + .shutdown = e1000_shutdown, + .err_handler = &e1000_err_handler +}; + +static int __init e1000_init_module(void) +{ + int ret; + + pr_info("Intel(R) PRO/1000 Network Driver - %s\n", + e1000e_driver_version); + pr_info("Copyright(c) 1999 - 2014 Intel Corporation.\n"); + ret = pci_register_driver(&e1000_driver); + + return ret; +} +module_init(e1000_init_module); +{% endhighlight %} +如上,实际的初始化,只是通过 pci_register_driver() 注册的 PCI 驱动。当然其它的驱动也类似,同样会在加载的时候都会通过该函数注册,然后,在该函数中会通过驱动对应的 probe() 函数探测设备,如果找到则加载驱动,注册添加一个网络设备。 + +现在已经定义一个 PCI 驱动,但是哪些 PCI 设备可以使用此驱动? 实际上与 e1000_driver.id_table 中的定义相关,对于 e1000e 也就是通过 struct pci_device_id e1000_pci_tbl[] 定义。 + +接下来看看是如何匹配的。 + +首先介绍下 PCI 的相关内容,PCI 有 3 种地址空间:IO 空间、内存地址空间、配置空间。一个 PCI 配置空间至少有 256 字节,如下: + +![pci-config]{: .pull-center} + +id_table 是 struct pci_device_id 类型的一个数组,每个元素就对应一条使用的 PCI 硬件信息,如果符合就可以使用这个驱动,例如我的网卡,其类型为: + +{% highlight text %} +pci -nn | grep -E 'Ethernet controller.*Intel' +00:19.0 Ethernet controller [0200]: Intel Corporation Ethernet Connection I218-LM [8086:155a] (rev 04) +{% endhighlight %} + +刚好可以匹配上如下的记录。 +{% highlight c %} +#define E1000_DEV_ID_PCH_LPTLP_I218_LM 0x155A + +static const struct pci_device_id e1000_pci_tbl[] = { + ... ... + { PCI_VDEVICE(INTEL, E1000_DEV_ID_PCH_LPTLP_I218_LM), board_pch_lpt }, + ... ... +}; +{% endhighlight %} + +在 pci_driver 中的 probe 成员(也就是 e1000_probe() 函数)用来对设置进行一系列的初始化操作。在驱动和设备的初始化阶段,包括在总线上驱动注册初始化;驱动探测设备注册初始化;开启设备,初始化接收缓存。 + +网络设备通过 struct net_device 标示,包括硬件网络设备接口,如以太网;软件网络设备接口,如 loopback 。通过 dev_base 头指针将设备链接起来集体管理,每个节点代表一个网络设备接口。 + +另外,也个比较重要的是中断相关内容,也就是在 e1000_open() 函数中调用 e1000_request_irq() 函数。 + +{% highlight c %} +static int e1000_request_irq(struct e1000_adapter *adapter) +{ + ... ... + /* E1000E_INT_MODE_MSIX模式 + * # cat /proc/interrupts | grep eth0 + * 86 0 0 0 IR-PCI-MSI-edge eth0-rx-0 + * 0 0 0 0 IR-PCI-MSI-edge eth0-tx-0 + * 10 0 0 0 IR-PCI-MSI-edge eth0 + */ + if (adapter->flags & FLAG_MSI_ENABLED) { // 如果是MSI + err = request_irq(adapter->pdev->irq, e1000_intr_msi, 0, + netdev->name, netdev); + if (!err) + return err; + + /* fall back to legacy interrupt */ + e1000e_reset_interrupt_capability(adapter); + adapter->int_mode = E1000E_INT_MODE_LEGACY; + } + + /* E1000E_INT_MODE_MSI模式 + * # cat /proc/interrupts | grep eth0 + * 853 0 930 0 PCI-MSI-edge eth0 + */ + err = request_irq(adapter->pdev->irq, e1000_intr, IRQF_SHARED, + netdev->name, netdev); + ... ... +} +{% endhighlight %} + +如上,在注册完设备之后,会通过 request_irq() 申请中断,不同的网卡类型会注册不同的中断处理函数,下面以 e1000_intr() 为例。 + + +## NAPI (New API) + +报文接收是整个协议栈的入口,负责从网卡中把报文接收并送往内核协议栈相应协议处理模块处理。一般有中断和轮询两种方式,最开始,网络流量小,采用中断方式。 + +当流量增大时,有此引起的中断将会影响到系统的整体效率。例如,我们使用标准的 100M 网卡,可能实际达到的接收速率为 80MBits/s,而此时数据包平均长度为 1500Bytes,则每秒产生的中断数目为: + +{% highlight text %} +80M bits/s / (8 Bits/Byte * 1500 Byte) = 6667 个中断 /s +{% endhighlight %} + +当每秒数千个中断请求时,那么很大一部分时间会消耗在中断上下文中;因此,当流量较大时,最好采用轮询的方式。而轮询,在请求较少时仍需要定时捞取数据,从而也会导致资源浪费。 + +而 NAPI 就是为了解决此问题。??????? + + +除了中断次数之外,当系统压力很大,不得不丢数据报文时,最好的方式是在最底层直接丢弃。采用 NAPI 后,可以直接在网络驱动中丢弃,而不需要再通知到内核。 + +在网卡中断中,首先处理的是关闭中断,因为我们已经知道了现在有很多的报文需要去处理,关闭中断,从而防止其它的中断请求再次到来。接着就是通知 Network Subsystem 来处理现在已经接收的报文。 + + +### 结构体 +{% highlight c %} +//----- 每个CPU会分配一个结构体 +DEFINE_PER_CPU_ALIGNED(struct softnet_data, softnet_data); + +struct softnet_data { // 保存接收报文,每个CPU一个队列 + struct Qdisc *output_queue; // 输出帧的控制 + struct Qdisc **output_queue_tailp; + struct list_head poll_list; // 有输入帧待处理的设备链表 + struct sk_buff *completion_queue; // 已经成功被传递出的帧的链表 + struct sk_buff_head process_queue; + + unsigned int dropped; + struct sk_buff_head input_pkt_queue; // 接收到数据会分配一个skb,并保存在该链表中,注意只针对非NAPI + // 而NAPI有自己的私有队列 + struct napi_struct backlog; // 用来兼容非NAPI的驱动 +}; + +struct napi_struct { + struct list_head poll_list; // 等待被执行的设备链表,链表的头就是softnet_data.poll_list + unsigned long state; + int weight; // 标示设备的权重 + unsigned int gro_count; + int (*poll)(struct napi_struct *, int); // NAPI都有poll虚函数,而非NAPI没有,初始化会赋值process_backlog + struct net_device *dev; // 指向具体的网络设备 + struct sk_buff *gro_list; + struct sk_buff *skb; + struct list_head dev_list; // 指向设备的NAPI链表 + struct hlist_node napi_hash_node; + unsigned int napi_id; +}; +{% endhighlight %} +上述结构体中有 Qdisc (Queueing Discipline),即排队规则,也就是我们经常说的 QoS 。 + + +### 与设备关联 + +在网卡驱动中,同时会创建轮询函数,一般在网卡初始化的时候完成,通过 nefif_napi_add() 函数添加。 + +{% highlight c %} +void netif_napi_add(struct net_device *dev, struct napi_struct *napi, + int (*poll)(struct napi_struct *, int), int weight); +{% endhighlight %} + +也就是用于将轮询函数与实际的网络设备 struct net_device 关联起来。上述函数的入参中包括了一个权重,该值通常是一个经验数据,一般 10Mb 的网卡设置为 16,而更快的网卡则设置为 64 。 + + +### 通知软中断 +通知是通过 napi_schedule() 函数进行,有如下的两种方式;其中 napi_schedule_prep() 是为了判定现在是否已经进入了轮询模式。 + +{% highlight c %} +//----- 将网卡预定为轮询模式 +void napi_schedule(struct napi_struct *n); + +//----- 或者 +if (napi_schedule_prep(n)) // 返回0表示已经在做poll操作了 + __napi_schedule(n); +{% endhighlight %} + +接下来查看下源码的具体实现。 +{% highlight c %} +static inline bool napi_schedule_prep(struct napi_struct *n) +{ + return !napi_disable_pending(n) && + !test_and_set_bit(NAPI_STATE_SCHED, &n->state); +} +static inline void napi_schedule(struct napi_struct *n) +{ + if (napi_schedule_prep(n)) + __napi_schedule(n); +} +static inline void ____napi_schedule(struct softnet_data *sd, + struct napi_struct *napi) +{ + // 把自己挂到per cpu的softnet_data上,触发NET_RX_SOFTIRQ软中断 + list_add_tail(&napi->poll_list, &sd->poll_list); + __raise_softirq_irqoff(NET_RX_SOFTIRQ); +} +void __napi_schedule(struct napi_struct *n) +{ + unsigned long flags; + + local_irq_save(flags); + ____napi_schedule(this_cpu_ptr(&softnet_data), n); + local_irq_restore(flags); +} +{% endhighlight %} +可以看到 napi_schedule() 基本操作是添加到链表中,然后触发软中断。 + +### 软中断 + +软中断包括了读写中断,均在 net_dev_init() 函数中初始化。 +{% highlight c %} +static int __init net_dev_init(void) +{ + ... ... + open_softirq(NET_TX_SOFTIRQ, net_tx_action); + open_softirq(NET_RX_SOFTIRQ, net_rx_action); + ... ... +} +subsys_initcall(net_dev_init); +{% endhighlight %} + + +### 轮询函数 + +驱动中创建轮询函数,它的工作是从网卡获取数据包并将其送入到网络子系统,函数声明如下。 + +{% highlight c %} +int (*poll)(struct napi_struct *napi, int weight); +{% endhighlight %} + +该函数在将网卡切换为轮询模式之后,用 poll() 方法处理接收队列中的数据包,如队列为空,则重新切换为中断模式。在切换回中断模式前,需要先通过 netif_rx_completer() 关闭轮询模式,然后开启网卡接收中断。 + +{% highlight c %} +//----- 退出轮询模式 +void __napi_complete(struct napi_struct *n) +{ + list_del(&n->poll_list); + smp_mb__before_atomic(); + clear_bit(NAPI_STATE_SCHED, &n->state); +} +void napi_complete(struct napi_struct *n) +{ + unsigned long flags; + + /* + * don't let napi dequeue from the cpu poll list + * just in case its running on a different cpu + */ + if (unlikely(test_bit(NAPI_STATE_NPSVC, &n->state))) + return; + + napi_gro_flush(n, false); + local_irq_save(flags); + __napi_complete(n); + local_irq_restore(flags); +} +{% endhighlight %} + +关于驱动需要做的修改,可以参考 [Driver porting: Network drivers](http://lwn.net/Articles/30107/),这篇是 2.6 时的文章,稍微有点老。 + +## 接收报文 + +在 e1000e 驱动中,只有 NAPI,也就是在收到中断后,然后调用 NAPI 轮询。 + +{% highlight c %} +static irqreturn_t e1000_intr(int __always_unused irq, void *data) +{ + ... ... + if (napi_schedule_prep(&adapter->napi)) { + adapter->total_tx_bytes = 0; + adapter->total_tx_packets = 0; + adapter->total_rx_bytes = 0; + adapter->total_rx_packets = 0; + __napi_schedule(&adapter->napi); + } + + return IRQ_HANDLED; +} + +static int e1000e_poll(struct napi_struct *napi, int weight) +{ + ... ... + if (!adapter->msix_entries || + (adapter->rx_ring->ims_val & adapter->tx_ring->ims_val)) + tx_cleaned = e1000_clean_tx_irq(adapter->tx_ring); // 查看并回收tx slot + + adapter->clean_rx(adapter->rx_ring, &work_done, weight); + + if (!tx_cleaned) + work_done = weight; + + /* If weight not fully consumed, exit the polling mode */ + if (work_done < weight) { + if (adapter->itr_setting & 3) + e1000_set_itr(adapter); + napi_complete(napi); + if (!test_bit(__E1000_DOWN, &adapter->state)) { + if (adapter->msix_entries) + ew32(IMS, adapter->rx_ring->ims_val); + else + e1000_irq_enable(adapter); + } + } + + return work_done; +} + +{% endhighlight %} + + + +对于接收过程,我们仅大致疏理一下其执行过程。 +{% highlight text %} +napi_gro_receive() + |-skb_gro_reset_offset() + |-dev_gro_receive() + |-napi_skb_finish() + |-netif_receive_skb_internal() + |-__netif_receive_skb() # 进入上层的接收函数 +{% endhighlight %} + + +## rx_ring索引 + + + + + +所以,next_to_clean指示的位置,一定要next_to_use先初始化过。 + +空 代表没有分配内存 + +0 代表分配了内存但是没有数据 + +1 代表数据准备好 + + +接收到的数据通过 struct e1000_ring 结构体保存,其初始化在 e1000_sw_init() 中设置。 +{% highlight c %} +struct e1000_ring { + struct e1000_adapter *adapter; /* back pointer to adapter */ + void *desc; /* pointer to ring memory */ + dma_addr_t dma; /* phys address of ring */ + unsigned int size; /* length of ring in bytes */ + unsigned int count; /* number of desc. in ring */ + + u16 next_to_use; // 内存分配完毕,网卡可以将数据写入其描述的最后下标 + u16 next_to_clean; // 网卡已经将数据写入后,驱动从描述符中将数据取出的开始下标 + + void __iomem *head; // 头尾指针,为闭区间索引,假设slot个数为16 + void __iomem *tail; + + /* array of buffer information structs */ + struct e1000_buffer *buffer_info; + + char name[IFNAMSIZ + 5]; + u32 ims_val; + u32 itr_val; + void __iomem *itr_register; + int set_itr; + + struct sk_buff *rx_skb_top; +}; +{% endhighlight %} + +{% highlight c %} +{% endhighlight %} + +在 ISR 中通常会通过 netif_rx() 传递给上层。另外,内核采用 NAPI 机制来处理高流量时的并发,减小由于中断引起的负载。 + +在无线设备中 beacon 往往通过 tasklet 实现,将其传递给软中断处理,当然这也意味着会通过 tasklet_init 类似的函数进行初始化,由此可以查找到具体的处理函数。 + + +[e1000e-driver-analysis]: "http://mnstory.net/2014/12/e1000e-driver-source-analysis/" "e1000e 驱动源码分析" + +[pci-config]: /images/linux/pci-config.png "PCI 配置空间格式" + + + + diff --git a/_drafts/2014-04-05-elliptic_curve_cryptography_init.md b/_drafts/2014-04-05-elliptic_curve_cryptography_init.md new file mode 100644 index 0000000..7f50f0a --- /dev/null +++ b/_drafts/2014-04-05-elliptic_curve_cryptography_init.md @@ -0,0 +1,22 @@ +--- +title: Kernel 内存映射 +layout: post +comments: true +language: chinese +category: [linux] +keywords: linux,内存,memory,映射 +description: +--- + + + + + +[一个关于椭圆曲线密码学的初级读本(相当容易懂)](http://8btc.com/thread-1240-1-1.html) + +## 参考 + +英文原版可以参考 [A (relatively easy to understand) primer on elliptic curve cryptography](https://arstechnica.com/security/2013/10/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) 。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2014-06-19-gimp_init.md b/_drafts/2014-06-19-gimp_init.md new file mode 100644 index 0000000..aa8e3b5 --- /dev/null +++ b/_drafts/2014-06-19-gimp_init.md @@ -0,0 +1,25 @@ +--- +title: GIMP +layout: post +comments: true +language: chinese +category: [misc] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + + + +### 其它 + +比较简单的画直线、矩形、圆这些基本的图形,首先是通过 Ctrl+N 新建一个空白的图像。 + +* 画直线;按 "N" 选取 "铅笔" 工具,选择起始点然后按着 Shift 不放,选择结束点; + +* 画矩形;按 "R" 选取矩形选择工具,在画布上画出一个矩形选择框;然后点菜单栏的 "Edit" => "Stroke Selection"; + +* 文字;每次添加文字的时候都会新建一个图层,可以通过 Ctrl-M 合并所有可见的图层; + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2014-07-24-python-namespace_init.md b/_drafts/2014-07-24-python-namespace_init.md new file mode 100644 index 0000000..986e43d --- /dev/null +++ b/_drafts/2014-07-24-python-namespace_init.md @@ -0,0 +1,225 @@ +--- +Date: October 19, 2013 +title: Python 命名空间 +layout: post +comments: true +category: [linux, bash] +language: chinese +--- + + + + + + +# 修饰符 + +所谓修饰工作就是想给现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰侵入到原有的模块中的代码里去。 + +如下是最简单的方式。 + +{% highlight python %} +#!/usr/bin/env python +def foobar(cb): + print "I am foobar", id(cb) + return cb +@foobar +def test(id): + print "I am test" +print "Get test() ID:", id(test) + +# OUTPUT: +# I am foobar 139900142408360 +# Get test() ID: 139900142408360 +{% endhighlight %} + +该函数实际调用了 test = foobar(test),也就是会调用 foobar() 函数,而且 test() 函数没有改变。 + +上面的示例只是简单介绍下修饰符的调用方法,而实际使用较多的是如下的方式。 + +{% highlight python %} +#!/usr/bin/env python +def foo(fn): + print "I am foo()", id(fn) + def wrapper(): + print "hello, %s" % fn.__name__ + fn() + print "goodby, %s" % fn.__name__ + return wrapper +@foo +def foobar(): + print "I am foobar()", id(foobar) +print "Get foobar() ID:", id(foobar) +foobar() + +# OUTPUT: +# I am foo() 140187446047400 +# Get foobar() ID: 140187446047520 +# hello, foobar +# I am foobar() 140187446047520 +# goodby, foobar +{% endhighlight %} + +这里实际上是采用了闭包的概念,上述的操作实际上等价于 foobar = foo(foobar) + foobar()。foo(foobar) 返回了 wrapper() 函数,所以, foobar 变量其实变成了 wrapper 的函数对象,而后面调用 foobar() 的执行,其实就变成了 wrapper()。 + +除了上述的之外还可以通过 class 的修饰符来实现。 + +{% highlight python %} +#!/usr/bin/env python +class foo(object): + def __init__(self, fn): + print "hello foo.__init__()", id(fn) + self.fn = fn + def __call__(self): + self.fn() + print "hello foo.__call__()" +@foo +def foobar(): + print "I am foobar()", id(foobar) +print "Get foobar() ID:", id(foobar) +foobar() + +# OUTPUT: +# hello foo.__init__() 139943209067056 +# Get foobar() ID: 139943209032976 +# I am foobar() 139943209032976 +# hello foo.__call__() +{% endhighlight %} + +在创建修饰符时,首先调用 \_\_init\_\_(),此时需要一个 fn 参数;而执行时会调用 \_\_call\_\_() 。 + + +对于多个修饰符/带参数的为: + +{% highlight python %} +#!/usr/bin/env python +def foo_1(fn): + print "I am foo_1()", id(fn) + def wrapper_1(): + print "hello 111, %s" % fn.__name__ + fn() + print "goodby 111, %s" % fn.__name__ + return wrapper_1 +def foo_2(fn): + print "I am foo_2()", id(fn) + def wrapper_2(): + print "hello 222, %s" % fn.__name__ + fn() + print "goodby 222, %s" % fn.__name__ + return wrapper_2 +@foo_2 +@foo_1 +def foobar(): + print "I am foobar", id(foobar) +foobar() # equal to: foobar = foo_2(foo_1(foobar)) + +# OUTPUT: +# I am foo_1() 140528766391072 +# I am foo_2() 140528766391192 +# hello 222, wrapper_1 +# hello 111, foobar +# I am foobar 140528766391312 +# goodby 111, foobar +# goodby 222, wrapper_1 +{% endhighlight %} + +对于多个修饰符,实际上等价于 foobar = foo_2(foo_1(foobar)) ,也就是 foo_1(foobar) 返回了 wrapper_1() 接着会调用 foo_2(),因此最后 foobar = foo_2(wrapper_1) 。 + +另外一种,就是带有参数的修饰符。 + +{% highlight python %} +#!/usr/bin/env python +def foo(arg): + def real_decorator(fn): + def wrapped(): + print "hello %s, %s" % (arg, fn.__name__) + fn() + print "goodby %s, %s" % (arg, fn.__name__) + return wrapped + return real_decorator + +@foo("hi") +def foobar(): + print "hello world" +foobar() # foobar = foo("hi")(foobar) + +@foo("22") +@foo("11") +def foobar1(): + print "hello world" +foobar1() +{% endhighlight %} +带有参数的实际等价于 foobar = foo("hi")(foobar) ,首先执行的是 foo("hi"),返回一个真正的修饰符;然后剩下的就进入了之前的修饰符逻辑。


+ +Compound statements Python 定义中,在 function 的定义中,包含了对修饰符的定义。
+Python修饰器的函数式编程 cooshell 中关于 Python 修饰符的介绍。
+Python Decorators Library 很多修饰符的实现,可用来参考。 +

+ + +# 简介 + +在 Python 中,变量是没有类型的,在使用变量的时候,不需要提前声明,但必须要给这个变量赋值,否则 Python 会认为这个变量没有定义。 + +Python 是静态作用域语言,尽管它自身是一个动态语言。也就是说,变量的作用域是由它在源代码中的位置决定的,这与 C 有些相似,也就是说可以根据不同的作用域调用不同的命令,事实上也是这么做的。

+ +命名空间实际是一个名称到对象 (objects) 的映射关系 (A namespace is a mapping from names to objects),多数的命名空间是用字典实现的,键就是变量名,值是那些变量的对象,也即变量的值。

+ +在使用一个变量之前不必先声明它,但是在真正使用它之前,必须已经绑定到某个对象;而名字绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量,不论这个名字绑定发生在当前作用域中的哪个位置。

+ +在一个 Python 程序中的任何一个地方,都存在几个可用的命名空间。
  1. +局部命名空间,每个函数所有,包括了函数内定义的变量、参数。当函数被调用时创建一个局部命名空间,当函数返回结果或抛出异常时,被删除。每一个递归调用的函数都拥有自己的命名空间。

  2. +全局命名空间,每个模块所有,包括了模块内定义的函数、类、其它导入的模块、模块级的变量和常量。模块的全局命名空间在模块定义被读入时创建,通常模块命名空间也会一直保存到解释器退出。

  3. +还有就是内置命名空间,任何模块均可访问它,存放着内置的函数和异常。内置命名空间在 Python 解释器启动时创建,会一直保留,不被删除。 +
+早些时候,Python 的是按照 LGB 查找的,后来由于闭包和嵌套函数的出现,于是又增加了嵌套作用域。

+ +变量的查询顺序为 LEGB ,也就是局部作用域 (Local),嵌套作用域 (Enclosing),全局作用域 (Global),内置作用域 (Build-in),在查询的过程中如果找到则停止搜索,否则抛出 NameError: name 'xxx' is not defined. 错误。

+ +内置作用域不做修改,可以将如下程序以 LEG 的顺序,逐步注释掉 foo 变量,查看其显示的结果。 +
+#!/usr/bin/env python
+foo = "global region"
+def foobar():
+    #foo = "enclosing region"
+    def bar():
+        #foo = "local region"
+        print foo
+    bar()
+foobar()
+
+ + + + + + + + + + + + + + +是新类的特性 + + +描述符实际上就是可以重用的属性, + +通常Python默认对属性的操作是从对象的字典(\_\_dict\_\_)中获取get,设置set或者删除delete。如,对于实例a,a.x的查找顺序为a.\_\_dict\_\_['x'],接着是type(a).\_\_dict\_\_['x'],然后是父类中查找。而如果属性x是一个描述符,那么访问a.x时不再从字典\_\_dict\_\_中读取,而是调用描述符的\_\_get\_\_()方法,对于设置和删除也是同样的原理。因此个人猜测没有验证,实际上是先查找属性是否有\_\_get\_\_()等方法,如果没有则在\_\_dict\_\_中查找。 + +例如,我们要求薪水的值应该大于0,可以在\_\_init\_\_中进行检测,但是可以在外部调用修改为负值,详见property.py中的PayBug类。为了对该值进行检测,可以通过@property修饰符进行修改,内容详见property.py中的Pay类。 + +但是这样修改仍然存在这麻烦,如果一个类里含有多个相似的属性(要求不能为负值),那么需要重复添加多个类似的函数。 + +我们可以通过类的__init__函数对变量进行初始化(初始化的变量名与类的变量名相同),当通过instance.__dict__查看时,不存在对应的变量;只能通过Class.__dict__来查看。而对于正常的类,这样操作实际是保存了一组类变量和一组实例变量。也就是说我们可以在__init__中对属性进行赋值,但是操作还是在描述符中进行。 + +在参考文献1中,在描述符类中采用了WeakKeyDictionary来解决不同实例之间的冲突,但是测试发现,现在的Python中不存在类似的问题。 + + +http://nbviewer.ipython.org/urls/gist.github.com/ChrisBeaumont/5758381/raw/descriptor_writeup.ipynb +http://www.geekfan.net/7862/ + +{% highlight c %} +{% endhighlight %} diff --git a/_drafts/2014-08-08-linux-filesystem-application_init.md b/_drafts/2014-08-08-linux-filesystem-application_init.md new file mode 100644 index 0000000..acd75dd --- /dev/null +++ b/_drafts/2014-08-08-linux-filesystem-application_init.md @@ -0,0 +1,39 @@ +--- +Date: August 8, 2014 +title: Linux 文件系统 +layout: post +comments: true +category: [linux] +language: chinese +--- + +Linux 或者 Unix 的设计理念是:一切都是文件! + +可以看到包括常用的文件,其它的包括设备,虚拟文件系统,甚至是网络 socket 都是通过文件表示。而且与 Windows 不同,在 Linux 中没有分区的概念,只有目录以及文件。 + + +# 简介 +在 Linux 中,所有的文件以及目录组成了一个目录树结构,在根目录下包括 bin、boot、etc、home、lib、mnt、proc 等目录,而实际上存在一个标准 [Filesystem Hierarchy Standard (FHS)][fhs] 规定了目录的结构,当然不同的发行版本只是略有不同。 + +当然,在 Linux 中我们也可以对磁盘进行分区,然后挂载到目录上。当拿到一个磁盘后,需要做的是 A) 对磁盘分区;B) 针对分区安装文件系统,如 ext4、xfs、ntfs 等;C) 将分区挂载到目录上,如 /mnt/foobar,然后就可以对磁盘上的文件进行操作了。 + + +# 文件属性 + + +# inode 介绍 +inode 用来唯一标示 Linux 中的文件,包括设备文件、符号链接、目录等,可以通过 stat 命令查看对应文件的 inode 号。在此只针对磁盘文件系统,如 ext4 ,进行介绍。 + +对于一个文件来说,有唯一的 inode 与之对应,而对于一个 inode ,由于 Linux 支持链接,从而可以有多个文件名与之对应,也就是说,在磁盘上的同一个文件可以通过不同的路径去访问该文件。 + +另外,Linux 与其他类 UNIX 系统一样并不区分文件与目录,目录是记录了其它文件名的文件。这也意味着,当使用命令 mkdir 创建目录时,若期望创建的目录名称与现有的文件名 (或目录名) 重复,则会创建失败。 + + + + + + +[fhs]: http://www.pathname.com/fhs/ "Filesystem Hierarchy Standard" + + +[garbage-logo]: /images/python/garbages/garbage-logo.jpg "ASCII 字符集" diff --git a/_drafts/2014-08-13-nosql-data-modeling-techniques_init.md b/_drafts/2014-08-13-nosql-data-modeling-techniques_init.md new file mode 100644 index 0000000..b3bb015 --- /dev/null +++ b/_drafts/2014-08-13-nosql-data-modeling-techniques_init.md @@ -0,0 +1 @@ +http://coolshell.cn/articles/7270.html diff --git a/_drafts/2014-09-13-linux-kernel-filesystem_init.md b/_drafts/2014-09-13-linux-kernel-filesystem_init.md new file mode 100644 index 0000000..fa19d69 --- /dev/null +++ b/_drafts/2014-09-13-linux-kernel-filesystem_init.md @@ -0,0 +1,13 @@ +--- +Date: August 8, 2014 +title: Linux 文件系统内核介绍 +layout: post +comments: true +category: [linux] +language: chinese +--- + +Linux 或者 Unix 的设计理念是:一切都是文件! + + + diff --git a/_drafts/2014-09-16-linux-kernel-memory_init.md b/_drafts/2014-09-16-linux-kernel-memory_init.md new file mode 100644 index 0000000..cf13295 --- /dev/null +++ b/_drafts/2014-09-16-linux-kernel-memory_init.md @@ -0,0 +1,30 @@ +--- +title: Kernel 内存管理 +layout: post +comments: true +language: chinese +category: [misc] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + +Linux 或者 Unix 的设计理念是:一切都是文件! + + + + +Slab Allocator 是 Linux 内核中的内存分配机制,各内核子系统、模块、驱动程序都会使用,可以从 /proc/meminfo 查看可以回收 (SReclaimable) 以及不可回收 (SUnreclaim) 的内存数,更详细的可以查看 /proc/slabinfo 文件。 + +另外需要注意的是,slab 其实是一个统称,内核从 2.6.23 版本之后就已经从 Slab 进化成 [Slub](https://www.kernel.org/doc/Documentation/vm/slub.txt) 了;可以直接查看 /sys/kernel/slab 目录是否存在,如果存在就是 slub 否则是 slab 。 +http://events.linuxfoundation.org/sites/events/files/slides/slaballocators.pdf +http://events.linuxfoundation.org/images/stories/pdf/klf2012_kim.pdf +http://www.cnblogs.com/tolimit/p/4654109.html +怎样诊断slab泄露问题 +http://linuxperf.com/?p=148 +https://www.mawenbao.com/research/linux-ate-my-memory.html +http://farll.com/2016/10/high-memory-usage-alarm/ +https://linux-audit.com/understanding-memory-information-on-linux-systems/ + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2014-09-17-linux-interrupts_init.md b/_drafts/2014-09-17-linux-interrupts_init.md new file mode 100644 index 0000000..17c48ab --- /dev/null +++ b/_drafts/2014-09-17-linux-interrupts_init.md @@ -0,0 +1,256 @@ +--- +Date: October 19, 2013 +title: Linux 中断 +layout: post +comments: true +category: [linux] +language: chinese +--- + + +中断在很大程度上解放了 CPU,提高了 CPU 的执行效率,弥补了 CPU 与外设运行速度之间的差距。 + + + +内核主要通过中断来管理硬件,与其相似的还有异常。 + +异常是指 CPU 内部出现的中断,即在 CPU 执行特定指令时出现的非法情况;异常也称为同步中断,也就是只有在一条指令执行后才会发出中断,不会在指令执行期间发生异常。常见的有除 0 错误,缺页异常,这个是不能屏蔽的。 + +中断通常是由其他硬件设备随机产生,可能会在指令执行过程中产生中断,分为可屏蔽中断和不可屏蔽中断。不可屏蔽中断如电源掉电、物理存储的奇偶校验等,可屏蔽中断。 + + + + + + +# 中断原理 + +从硬件来开中断是由设备产生的一种电信号,并直接送入中断控制器 (Programmable Interrupt Controller, PIC),再向 CPU 发送相应的信号,然后会 CPU 会中断当前处理的任务去处理中断。 + +## 8259A + +在最早的 8086 阶段,CPU 是没有集成 PIC 的,但是提供了两个外接引脚 NMI 和 INTR,其中 NMI 为不可屏蔽中断,通常用于电源掉电和物理存储器奇偶校验;INTR 是可屏蔽中断,主要用于接受外部硬件的中断信号,可以进行屏蔽。 + +通常采用两个 8259A 级联实现,每个芯片提供 8 个中断源,总共可以提供 15 个中断信号源,使用之前需要对其进行初始化。 + +![8259a]{: .pull-center} + +而 8259A 只适用于单 CPU ,当出现了 SMP 后,Intel 引入了 APIC (Advanced Programmable Interrupt Controller),用于解决中断在各个 CPU 之间的路由关系,也即可以将中断传递给各个 CPU Cores ,该控制器最早在 Pentium 4 中出现。 + + + +## APIC + +APIC 经历了 APIC、xAPIC、x2APIC,其基本架构没有变化,只是其通讯的总线有所改变,或者对部分的功能进行了扩展。 + +该组件包含两部分组成:Local APIC 和 I/O APIC 。Local APIC 位于 CPU 内部,负责传递中断信号到指定的处理器,每个 CPU 都会对应一个,同时它还有一个 Timer 功能,可以为所属的处理器提供本地时钟功能,而且还可以给发送中断消息给其他处理器 IPI (Inter Processor Interrupt)。 + +![ACPI]{: .pull-center} + +I/O APIC 一般位于南桥芯片上,用来是收集来自 I/O 设备的中断信号,并按照配置将中断发送到 Local APIC,系统中最多可拥有 8 个 I/O APIC。 + +IO APIC 通过 LINT0 和 LINT1 引脚与 CPU 相连,相比于 8259,IOAPIC 可以处理更多的外设中断,如 ICH9 中单个 IOAPIC 可以支持 24 个中断,而且可以将接收到的中断分发到不同的处理器中。 + +每个 Local APIC 都有 32 位的寄存器,一个本地时钟以及为本地中断保留的两条额外的 IRQ 线 LINT0 和 LINT1,所有本地 APIC 都连接到 I/O APIC,形成一个多级 APIC 系统。 + + + +## MSI/MSI-X + +在 PCI 总线中,所有需要提交中断请求的设备,必须能够通过 INTx 引脚提交中断请求,而 MSI 是可选机制。而在 PCIe 总线中,PCIe 设备必须支持 MSI 或者 MSI-X 中断请求机制,可以不支持 INTx 中断。 + +MSI (Message-Signaled Interrupts) 也就是基于消息信号的中断,相比 APIC 来说,更加灵活,性能更高。 + +MSI 中断究其本质,就是一个存储器读写事件,将 MSI Address 设置为内存中的某个地址,产生中断时,中断源会在 MSI Address 所在的地址写入 MSI Data。例如,如果有四条 MSI 中断线,就会依次写入 Data、Data+1、Data+2、Data+3 在内存中,依次来区分中断源设备。 + +对于设备来说,会在自己的配置空间定义了自己的 Capabilities list,如果该设备支持 MSI 中断,在此 list 中必定有一个节点的 ID=0x5D,其中 0x5D 表明是 MSI 中断节点,其位置由设备自定义。 + +MSI-X 是 MSI 的扩展,可以让一个硬件设备初始化多个中断向量,支持多个 CPU 同时处理一个 PCIe 设备的中断任务。 + + + + +## 中断查看 + +要启用 MSI/MSI-X 类型的中断,需要在内核编译过程中带上相关的编译参数。通过 /proc/interrupts 可以查看系统的中断统计信息,以及中断类型,列表中如果有 IO-APIC 说明正在使用 APIC;如果看到 XT-PIC 则意味着正在使用 8259A 芯片;有 MSI 信息则说明是用 MSI 中断。 + + +在 /proc/interrupts 中,第 1 列是中断号;接着是 CPUx 表示接收到的中断请求的次数;接着是对当前中断的描述,在 request_irq() 函数中传入。另外,NMI 和 LOC 是系统所使用的驱动,用户是无法访问的。 + +IRQ 号决定了中断的优先级,越小意味着优先级越高。 + +* IRQ0:系统时钟,不能改变。 +* IRQ1:键盘控制器,不能改变。 +* IRQ3:串口 2 的串口控制器,如有串口4 也会使用这个中断。 +* IRQ4:串口 1 的串口控制器,如有串口3 也会使用这个中断。 +* IRQ5:并口 2 和 3 或 声卡。 +* IRQ6:软盘控制器。 +* IRQ7:并口 1 被用于打印机,若没有打印机,可以用于其它的并口。 + +当然,现在通常只有 IRQ0 和 IRQ1,其它的上述中断已经成为了历史。 + + +另外,IRQ 有一个关联的绑定属性 smp_affinity,该参数可以用来指定执行 ISR 的 CPU 核,该配置的内容保存在 /proc/irq/NUM/smp_affinity 文件中,可以通过 root 用户查看/修改该值。 + +该文件会一个十六进制的掩码,代表了系统中所有 CPU 核,以网卡 eth0 为例: + +{% highlight text %} +# grep eth0 /proc/interrupts + 57: 5 0 2 1203 PCI-MSI-edge eth0 +# cat /proc/irq/57/smp_affinity +8 +{% endhighlight %} + +其中绑定关系用的是二进制,其对应关系为 0001(1)-CPU0、0101(5)-CPU0/2 ,那么上述 eth0 的 ISR 绑定到了 CPU3。 + +当然这还有一个前提,就是 irqbalance 服务需要关闭。irqbalance 是个服务进程,用来自动绑定和平衡 IRQ 的,可以通过 ps 查看是否有该进程。 + + + +## Linux 中断查看 + +Linux 内核中定义了 softirq 类型,通常来说不需要添加其它类型的软中断,如果需要一般使用 tasklets 。 + +{% highlight c %} +enum { // include/linux/interrupt.h + HI_SOFTIRQ=0, + TIMER_SOFTIRQ, + NET_TX_SOFTIRQ, + NET_RX_SOFTIRQ, + BLOCK_SOFTIRQ, + BLOCK_IOPOLL_SOFTIRQ, + TASKLET_SOFTIRQ, + SCHED_SOFTIRQ, + HRTIMER_SOFTIRQ, + RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */ + + NR_SOFTIRQS +}; +{% endhighlight %} + +有如下的几种方式可以查看软中断的统计信息,包括在那些 CPU 上执行了多少次。 + +{% highlight text %} +----- 每种类型的软中断分别在每个CPU上执行了多少次 +$ cat /proc/softirqs + CPU0 CPU1 CPU2 CPU3 + HI: 148 86 96 66 + TIMER: 93155814 83650552 78772010 82808729 + NET_TX: 11483 14361 29725 6904 + NET_RX: 2885712 452024 2343460 263921 + BLOCK: 8943601 842 2215 1086 +BLOCK_IOPOLL: 58 0 1 16 + TASKLET: 19240313 848 221255 983 + SCHED: 20381866 17177463 11667301 10782048 + HRTIMER: 0 0 0 0 + RCU: 16624528 15482367 14754726 14837976 + +----- 查看每种中断的执行次数,第一列代表softirq总数,而后每一列分别对应一种软中断类型 +$ cat /proc/stat |grep "softirq" +softirq 497658376 396 340734538 63039 5986156 8993118 75 19475944 60410520 0 61994590 + +----- 查看各个中断的执行次数 +$ cat /proc/interrupts + CPU0 CPU1 CPU2 CPU3 + 0: 34 0 0 0 IO-APIC-edge timer + 1: 459391 0 8 2 IO-APIC-edge i8042 + 7: 24 0 0 0 IO-APIC-edge + 8: 0 0 1 0 IO-APIC-edge rtc0 + 9: 857316 11 9635 83 IO-APIC-fasteoi acpi + 12: 13855205 21 547 16 IO-APIC-edge i8042 + 16: 0 0 0 0 IO-APIC-fasteoi mmc0 + 19: 13747528 37 8297 14 IO-APIC-fasteoi ath9k + 21: 942 9 30 3 IO-APIC-fasteoi ehci_hcd:usb3 + 40: 117 0 82 0 PCI-MSI-edge snd_hda_intel + 41: 10 0 6 0 PCI-MSI-edge mei_me + 42: 9241838 717 2096 1017 PCI-MSI-edge 0000:00:1f.2 + 43: 19183739 6 245680 6 PCI-MSI-edge i915 + +----- 同样可以通过/proc/stat查看中断出现的次数 +$ cat /proc/stat |grep intr +intr 1006147094 34 459401 0 0 0 0 0 24 1 867368 0 0 13855789 ... ... +{% endhighlight %} + +除了直接查看文件之外,也可以通过 dstat、vmstat 等指令获取中断的次数;可以通过 mpstat 命令查看每个 CPU 上 softirq 的开销。一般情况下,中断总数略大于软中断数。 + + +## 中断亲和性 + +Linux 默认会在初始化时将所有的 CPU 分配给中断。 + +{% highlight c %} +static void __init init_irq_default_affinity(void) +{ + alloc_cpumask_var(&irq_default_affinity, GFP_NOWAIT); + cpumask_setall(irq_default_affinity); +} +{% endhighlight %} + +可以通过 cat /proc/irq/default_smp_affinity 查看当前的默认值。 + +实际上,很多设备不支持一个中断号被多个 CPU 处理,通常只有 CPU0 在真正处理中断请求,进而会导致 CPU0 由于压力过大产生问题,如响应时间增加,甚至可能产生丢包甚至 hang 住。 + +可以通过人工绑定,例如将一个网卡中断请求绑定到一个固定的 CPU core 上,步骤如下: + +1. 确定网卡队列的中断号,cat /proc/interrutps \| grep "eth0-TxRx-0"; + +2. 进入 /proc/irq/${IRQ}/ 查看其中的两个文件,smp_affinity 和 smp_affinity_list,改任意一个文件,另一个文件会同时更改; + smp_affinity 采用 16 进制掩码的方式,1 代表 CPU0,6 代表 CPU2、CPU1 ; + smp_affinity_list 采用 10 进制,可读性高,6 代表 CPU6,0-2,4-6 代表 0,1,2,4,5,6 ; + +目前,很多的网卡、RAID 卡是支持多队列的,而实际上很多的硬件设备是不支持多队列的,在绑定的时候需要注意的几个点: + +1. 有些中断还会落到 CPU0 上,因此最好不要将 CPU0 绑定到网卡中断。 + +2. 打散尽量按照物理 CPU 绑定,不要使用逻辑核。 + + + + + + + + +# Linux 中断实现 + +设备中断会打断内核中进程的正常调度和运行,为了提高效率,必然要求中断服务尽可能的短小精悍,但是,有些 ISR 却需要大量的耗时处理。 + +为了提高系统的响应能力,Linux 将中断处理程序分为两个部分:上半部 (top half) 和下半部 (bottom half)。上半部处理时中断是被屏蔽的,所以通常用来处理一些比较紧急的任务,而且要尽可能快;下半部分通常就是正常的中断处理程序。 + +不过,对于上半部分和下半部分之间的划分没有特别严格的规则,通常是靠驱动程序开发人员自己的编程习惯来划分,不过还是有些习惯供参考: + +* 如果该任务对时间比较敏感,将其放在上半部中执行。 +* 如果该任务要保证不被其他中断打断,放在上半部中执行,因为此时系统中断是关闭的。 +* 如果该任务和硬件相关,一般放在上半部中执行。 + +如果中断要处理的工作本身就很少,则完全可以直接在上半部全部完成。 + +如上所述,对于耗时的不太紧急的任务,一般会在下半部执行,而随着下半部的不断演化,已经从最原始的 Bottom Half 衍生出软中断 (softirq-2.3引入)、tasklet (2.3引入)、工作队列 (work queue-2.5引入)。 + + + + + + + + + + + + +[8259a]: /images/linux/interrupt-8259A.png "古老的8259A中断控制芯片" +[ACPI]: /images/linux/interrupt-xACPI.jpg "ACPI中断控制芯片架构" diff --git a/_drafts/2014-09-20-python-descriptor_init.md b/_drafts/2014-09-20-python-descriptor_init.md new file mode 100644 index 0000000..d450bf9 --- /dev/null +++ b/_drafts/2014-09-20-python-descriptor_init.md @@ -0,0 +1,30 @@ +--- +Date: October 19, 2013 +title: Python 修饰符 (Descriptor) +layout: post +comments: true +category: [linux, bash] +language: chinese +--- + +是新类的特性 + + +描述符实际上就是可以重用的属性, + +通常Python默认对属性的操作是从对象的字典(\_\_dict\_\_)中获取get,设置set或者删除delete。如,对于实例a,a.x的查找顺序为a.\_\_dict\_\_['x'],接着是type(a).\_\_dict\_\_['x'],然后是父类中查找。而如果属性x是一个描述符,那么访问a.x时不再从字典\_\_dict\_\_中读取,而是调用描述符的\_\_get\_\_()方法,对于设置和删除也是同样的原理。因此个人猜测没有验证,实际上是先查找属性是否有\_\_get\_\_()等方法,如果没有则在\_\_dict\_\_中查找。 + +例如,我们要求薪水的值应该大于0,可以在\_\_init\_\_中进行检测,但是可以在外部调用修改为负值,详见property.py中的PayBug类。为了对该值进行检测,可以通过@property修饰符进行修改,内容详见property.py中的Pay类。 + +但是这样修改仍然存在这麻烦,如果一个类里含有多个相似的属性(要求不能为负值),那么需要重复添加多个类似的函数。 + +我们可以通过类的__init__函数对变量进行初始化(初始化的变量名与类的变量名相同),当通过instance.__dict__查看时,不存在对应的变量;只能通过Class.__dict__来查看。而对于正常的类,这样操作实际是保存了一组类变量和一组实例变量。也就是说我们可以在__init__中对属性进行赋值,但是操作还是在描述符中进行。 + +在参考文献1中,在描述符类中采用了WeakKeyDictionary来解决不同实例之间的冲突,但是测试发现,现在的Python中不存在类似的问题。 + + +http://nbviewer.ipython.org/urls/gist.github.com/ChrisBeaumont/5758381/raw/descriptor_writeup.ipynb +http://www.geekfan.net/7862/ + +http://blog.csdn.net/bluehawksky/article/details/50372244 + diff --git a/_drafts/2014-10-02-network-loopback_init.md b/_drafts/2014-10-02-network-loopback_init.md new file mode 100644 index 0000000..9943322 --- /dev/null +++ b/_drafts/2014-10-02-network-loopback_init.md @@ -0,0 +1,150 @@ +--- +Date: October 19, 2013 +title: Linux 中的 loopback 设备 +layout: post +comments: true +language: chinese +category: [linux,network] +--- + +我们知道,在 Linux 设备中有一个 lo 设备,在此稍微介绍下。 + + + +# 简介 + +对于 loopback 设备,我们可以直接通过 ifconfig 查看 lo 设备的配置。 + +{% highlight text %} +$ ifconfig lo +lo: flags=73 mtu 65536 + inet 127.0.0.1 netmask 255.0.0.0 + inet6 ::1 prefixlen 128 scopeid 0x10 + loop txqueuelen 0 (Local Loopback) + RX packets 442824 bytes 208834219 (199.1 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 442824 bytes 208834219 (199.1 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 +{% endhighlight %} + +如上,可以看出 lo 的 mask 仅为 8bit,也就是说,只要 127 开头,任何网络主机号都可以,也就是说 lo 接口可以理解为一个网络号。 + +可以 ping 该网段中的 IP 地址,例如 127.0.0.1、127.255.255.200 等。 + +另外常用的是,通过 ifconfig 设置一个浮动 IP 地址,该地址在 127 网段,从而可以在本地使用非 127.1 进行测试,可以通过如下命令设置。 + +{% highlight text %} +# ifconfig lo:1 127.168.1.1 netmask 255.0.0.0 +{% endhighlight %} + +然后,像 MySQL 就可以绑定该 IP 即可。 + +# 源码解析 + +在内核初始化的时候,会调用 net_dev_init() 函数,其中通过 register_pernet_device() 注册 lo 设备。 +{% highlight c %} +/* Registered in net/core/dev.c */ +struct pernet_operations __net_initdata loopback_net_ops = { + .init = loopback_net_init, +}; + +static int __init net_dev_init(void) +{ + ... ... + if (register_pernet_device(&loopback_net_ops)) + goto out; + ... ... +} +subsys_initcall(net_dev_init); +{% endhighlight %} + +在进行注册的过程中会调用 loopback_net_init() 做初始化操作,也就是 loopback NIC 的驱动程序。 + +{% highlight c %} +#define alloc_netdev(sizeof_priv, name, name_assign_type, setup) \ + alloc_netdev_mqs(sizeof_priv, name, name_assign_type, setup, 1, 1) + +static __net_init int loopback_net_init(struct net *net) +{ + ... ... + /* 申请一个net_device实例,并进行初始化 */ + dev = alloc_netdev(0, "lo", NET_NAME_UNKNOWN, loopback_setup); + if (!dev) + goto out; + + dev_net_set(dev, net); + err = register_netdev(dev); /* 注册loopback NIC设备 */ + if (err) + goto out_free_netdev; + + BUG_ON(dev->ifindex != LOOPBACK_IFINDEX); + net->loopback_dev = dev; + return 0; + ... ... +} + +static const struct net_device_ops loopback_ops = { + .ndo_init = loopback_dev_init, + .ndo_start_xmit = loopback_xmit, + .ndo_get_stats64 = loopback_get_stats64, + .ndo_set_mac_address = eth_mac_addr, +}; + +static void loopback_setup(struct net_device *dev) +{ + ... ... + dev->netdev_ops = &loopback_ops; + ... ... +} +{% endhighlight %} + +在调用 alloc_netdev() 初始化设备时,该函数同时会初始化接收和发送队列,该函数在初始化完数据结构之后,同时会调用回调函数 loopback_setup(),该函数会初始化一些与 lo 设备相关的参数。 + +其中,最重要的是 dev->netdev_ops,其中发消息的方法就是调用 loopback_xmit() 函数即可,该函数的声明如下。 + +{% highlight c %} +netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev); +{% endhighlight %} + +其中,skb 是待发送的数据缓冲区,dev 是网络设备的一个指针。lo 设备是要把数据报文发给本机,所以其发送数据报文的函数比较特殊,它把 skb 稍加处理后,又转回给协议栈的数据报接收函数 netif_rx()。 + +{% highlight c %} +netdev_tx_t loopback_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct pcpu_lstats *lb_stats; + int len; + + skb_orphan(skb); + + /* Before queueing this packet to netif_rx(), + * make sure dst is refcounted. + */ + skb_dst_force(skb); + + skb->protocol = eth_type_trans(skb, dev); + + /* it's OK to use per_cpu_ptr() because BHs are off */ + lb_stats = this_cpu_ptr(dev->lstats); + + len = skb->len; + if (likely(netif_rx(skb) == NET_RX_SUCCESS)) { // 调用该函数发送 + u64_stats_update_begin(&lb_stats->syncp); + lb_stats->bytes += len; + lb_stats->packets++; + u64_stats_update_end(&lb_stats->syncp); + } + + return NETDEV_TX_OK; +} +{% endhighlight %} + +首先会调用 skb_orphan() 把 skb 孤立,使它跟发送 socket 和协议栈不再有任何联系,也即对本机来说,这个 skb 的数据内容已经发送出去了,而 skb 相当于已经被释放掉了。 + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2014-10-12-network-route-table_init.md b/_drafts/2014-10-12-network-route-table_init.md new file mode 100644 index 0000000..ae224fd --- /dev/null +++ b/_drafts/2014-10-12-network-route-table_init.md @@ -0,0 +1,40 @@ +--- +Date: October 19, 2013 +title: Linux 路由表 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + + + + + +linux在进行路由查找时,先查找local,再查找main: + + +实际上,如果内核认为目标地址是本机IP,就会将包的出口设备设置为loopback_dev(不管路由表将出口设备设置成什么)。 +{% highlight c lineno %} +static inline int fib_lookup(struct net *net, const struct flowi4 *flp, + struct fib_result *res) +{ + struct fib_table *table; + + table = fib_get_table(net, RT_TABLE_LOCAL); + if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) + return 0; + + table = fib_get_table(net, RT_TABLE_MAIN); + if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF)) + return 0; + return -ENETUNREACH; +} +{% endhighlight %} + + + + +ip route list table local +ip route list table main + diff --git a/_drafts/2014-10-27-network-speedup_init.md b/_drafts/2014-10-27-network-speedup_init.md new file mode 100644 index 0000000..810f3af --- /dev/null +++ b/_drafts/2014-10-27-network-speedup_init.md @@ -0,0 +1,475 @@ +--- +Date: October 19, 2013 +title: Linux 网络加速 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +硬件与 CPU 进行信息沟通有两种方式,一种是中断,另一种是轮询。中断是硬件主动产生中断信号,中断控制器将信号传递给 CPU,此时 CPU 会停下手中的工作,执行中断任务;轮询则是 CPU 主动,定时查询硬件设备的状态,是否处理硬件请求。 + +随着网络带宽越来越大,实际由中断带来的 CPU 负载越来越大,这也就导致了 NAPI 的出现。 + +不过这不是本文要讲的重点,本文着重看下网络针对 SMP 所做的优化。 + + + + +# 简介 + +内核的加速方式有几种,包括了网卡硬件加速、多队列、对 SMP 的支持等。 + +对于针对 SMP 的优化后面介绍,首先介绍通过网卡硬件方式的加速方法,包括了 LSO、LRO、GSO、GRO、TSO、USO,可以通过如下的方式查看网卡是否支持,其中 fixed 表示不能修改,其它可以通过 -K 参数修改: + + +{% highlight text %} +# ethtool -k eth0 # 查看网口的offload特性 +Offload parameters for eth0: +rx-checksumming: on +tx-checksumming: on + tx-checksum-ipv4: off [fixed] + tx-checksum-ip-generic: on + tx-checksum-ipv6: off [fixed] + tx-checksum-fcoe-crc: off [fixed] + tx-checksum-sctp: off [fixed] +scatter-gather: on +tcp-segmentation-offload: on + tx-tcp-segmentation: on + tx-tcp-ecn-segmentation: off [fixed] + tx-tcp6-segmentation: on +udp-fragmentation-offload: off [fixed] +generic-segmentation-offload: on +generic-receive-offload: on +large-receive-offload: off [fixed] +rx-vlan-offload: on +tx-vlan-offload: on +ntuple-filters: off [fixed] +receive-hashing: on +highdma: on [fixed] +rx-vlan-filter: off [fixed] +vlan-challenged: off [fixed] +tx-lockless: off [fixed] +netns-local: off [fixed] +tx-gso-robust: off [fixed] +tx-fcoe-segmentation: off [fixed] +tx-gre-segmentation: off [fixed] +tx-ipip-segmentation: off [fixed] +tx-sit-segmentation: off [fixed] +tx-udp_tnl-segmentation: off [fixed] +tx-mpls-segmentation: off [fixed] +fcoe-mtu: off [fixed] +tx-nocache-copy: off +loopback: off [fixed] +rx-fcs: off +rx-all: off +tx-vlan-stag-hw-insert: off [fixed] +rx-vlan-stag-hw-parse: off [fixed] +rx-vlan-stag-filter: off [fixed] +busy-poll: off [fixed] +{% endhighlight %} + +其中 offload 的作用就是将一个本来由软件实现的功能现在放到硬件上来实现,也就是说这些 offload 特性都是为了提升网络收/发性能,其中 TSO、UFO 和 GSO 是对应网络发送,在接收方向上对应的是 LRO、GRO 。 + +当数据包传输时,需要按照标准分割成一个一个小于 MTU 的小包进行传输,然后传输到另一边后还得将这些数据包拼装回去。本来这些分割、拼装的事情都是软件实现的,于是就有人想将这些事情 offload 到硬件上实现,于是就产生了如下的技术。 + + + + + + +### TSO (TCP Segmentation Offload)、USO (UDP Fragmentation Offload)、LSO (Large Segment Offload) + +一种利用网卡对 TCP 数据包分片,减轻 CPU 负载的一种技术,有时也被叫做 LSO,TSO 是针对 TCP 报文的,UFO 是针对 UDP 报文的。如果硬件支持 TSO 功能,同时也需要硬件支持的 TCP 校验计算和 Scatter Gather 功能。 + + +### GSO (Generic Segmentation Offload) + +比 TSO 更通用,基本思想就是尽可能的推迟数据分片直至发送到网卡驱动之前,此时会检查网卡是否支持分片功能(如TSO、UFO), 如果支持直接发送到网卡,如果不支持就进行分片后再发往网卡。这样大数据包只需走一次协议栈,而不是被分割成几个数据包分别走,这就提高了效率。 + + +### LRO (Large Receive Offload) + +将接收到的多个 TCP 数据聚合成一个大的数据包,然后传递给网络协议栈处理,以减少上层协议栈处理开销,提高系统接收 TCP 数据包的能力。 + +### GRO(Generic Receive Offload) + +基本思想跟 LRO 类似,克服了 LRO 的一些缺点,更通用。 + + + +### Scatter Gather + +这种方式是与传统的 block DMA 相对应的一种 DMA 方式,对应的 dev features 为 NETIF_F_SG 。 + +在 DMA 传输数据的过程中,要求源物理地址和目标物理地址必须是连续的,如果物理地址不连续,那么在传输完一块连续的物理内存后,会发起一次中断,然后进行下一块连续物理内存的数据传输,这种方式为 block DMA 方式。 + +Scatter/Gather 是通过一个链表描述物理不连续的存储器,然后把链表首地址告诉 DMA master,每次传输完一块物理连续的数据后,不用再发中断了,而是根据链表传输下一块物理连续的数据,最后发起一次中断。 + + + + + +# SMP 优化 + +目前在内核中采用了多种技术提高多处理器系统的并行性并改善性能: + +* RSS: Receive Side Scaling (接收侧的缩放) +* RPS: Receive Packet Steering (接收端包的控制) +* RFS: Receive Flow Steering (接收端流的控制) +* Accelerated Receive Flow Steering (加速的接收端流的控制) +* XPS: Transmit Packet Steering(发送端包的控制) + +## RSS (Receive Side Scaling) + +是一项网卡的新特性,也就是多队列,具备 RSS 功能的网卡,可以将不同的网络流分成不同的队列,再分别将这些队列分配到多个 CPU 核心上进行处理,从而将负荷分散,充分利用多核处理器的能力。 + + +![rss-struct]{: .pull-center} + +通常的处理流程如下: + +1. NIC 通过指定的区域(可以不连续,通常为源IP、目的IP、源Port、目的Port)计算一个 hash 值; +2. 通常选择 hash 的低位 (LSB) 作为索引,在 indirection table 中对应的就是 CPU 序号,而这些值是可以进行配置的; +3. 通过 MSI,网卡的中断也可以和相应的 CPU 绑定。 + +对于 RSS 特性需要硬件的支持:A) 网卡需要支持 RSS 和 MSI-X,可以参考相应网卡的产品文档,一般都会有 RSS 或者 multiple queue;B) 内核需要支持 MSI/MSI-X 。 + +当网卡驱动加载时,获取网卡型号得到网卡的硬件 queue 数量,并结合 CPU 核的数量,选择两者的最小值作为所要激活的网卡 queue 数量,并申请相应数目的中断号,分配给激活的各个 queue。 + +当某个 queue 收到报文时,触发相应的中断,收到中断的 CPU 将该任务加入到协议栈负责收包的该核的 NET_RX_SOFTIRQ 队列中 (NET_RX_SOFTIRQ 在每个核上都有一个实例),在 NET_RX_SOFTIRQ 中,会调用 NAPI 的收包接口,将报文收到有多个 netdev_queue 的 net_device 数据结构中。 + +可以通过如下方式查看内核中的相关信息: + +{% highlight text %} +----- 查看当前网卡 eth0 注册的多队列数目 +# grep -iE "msi.*eth0" /proc/interrupts + +----- 通过lspci查看网卡相关信息 +# lspci -vvvv | less +... ... +01:00.0 Ethernet controller: Intel Corporation I350 Gigabit Network Connection (rev 01) + ... ... + Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+ + Address: 0000000000000000 Data: 0000 + Masking: 00000000 Pending: 00000000 + Capabilities: [70] MSI-X: Enable+ Count=10 Masked- + Vector table: BAR=3 offset=00000000 + PBA: BAR=3 offset=00002000 + ... ... + Kernel driver in use: igb + Kernel modules: igb + +----- 通过modinfo查看内核加载信息 +# modinfo igb | grep -E "QueuePairs|RSS" +parm: RSS:Number of Receive-Side Scaling Descriptor Queues (0-8), default 0, 0=number of cpus (array of int) +parm: QueuePairs:Enable Tx/Rx queue pairs for interrupt handling (0,1), default 1=on (array of int) +{% endhighlight %} + +可以在载入模块时指定参数,如 modprobe igb RSS=8,或者直接在配置文件中指定: + +{% highlight text %} +$ cat /etc/modprobe.conf +options igb RSS=8,8 +{% endhighlight %} + +下图是网卡的处理流程,可供参考。 + +![rss-process]{: .pull-center width="700"} + + +另外,网卡绑定时,最好和同一个物理 CPU 的核挨个绑定,从而可以避免 L1、L2、L3 践踏,那些 CPU 属于同一个物理核可以通过 lscpu 查看。 + + + + + + + + + +## RPS (Receive Packet Steering) + +也就是接收端包的控制,实际上类似于 RSS,只是 RPS 是在软件层面实现的,它将软中断均摊到多个 CPU core 上,相比来说也更加灵活。 + +其中 RPS/RFS 是 Google 的 Tom Herbert 向 Linux 内核提交的一个 patch 。没有 RPS 时,一个网卡队列,硬中断和软中断都是相同的 CPU 在处理,而 RPS 可以将软中断均摊到多个 CPU 上。 + +之所以有 RPS/RFS 是因为: + +1. 如果网卡不支持 RSS+MSI-X,那么只能使用一个 CPU core 处理中断,此时性能较差。 + +2. 网卡中队列的选择,很多仍然是基于 4 元组 hash 出来的,如果两台机器间的流量较高,那么响应的队列就非常忙。有些 Intel 的网卡可以支持 [Flow Director][intel-flow-director] 的,可以提供更加精细的调优。 + +3. RSS 虽然可以使同一个连接的流量都走相同的队列,减少排序,但处理中断的 CPU 和上层应用的 CPU 仍有可能不是同一个,从而会造成额外的开销。 + + +内核中的实现是在收包的链路 netif_receive_skb_internal() 函数中,存在一个函数 get_rps_cpu(),根据 skb,找到该哪个 CPU 处理。简单得说,是建立一个 hash 表(一般使用 Toeplitz hash 函数),那个连接的流量应该在哪个 CPU 上处理,避免相同连接的流量被不同的 CPU 处理。 + +{% highlight c %} +static int netif_receive_skb_internal(struct sk_buff *skb) +{ + ... ... + if (static_key_false(&rps_needed)) { + ... ... + cpu = get_rps_cpu(skb->dev, skb, &rflow); + + if (cpu >= 0) { // 如果没有开启RPS,CPU返回-1,走原来的流程 + ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail); + rcu_read_unlock(); + return ret; + } + rcu_read_unlock(); + } + return __netif_receive_skb(skb); +} +{% endhighlight %} + +在调整时需要对每块网卡每个队列分别进行设置,仍以 eth0 的 0 号队列进行设置: + +{% highlight text %} +# echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus +{% endhighlight %} + +RPS 的设置方式和中断亲和力设置的方法类似,采用的是掩码的方式,但通常要将所有的 CPU 设置进去。 + + + +## RFS (Receive Flow Steering) + +主要是解决网卡数据流 CPU 与接受该数据流的应用程序所在 CPU 不同,导致 CPU 切换,RFS 就是为了保证程序运行的 CPU 和软中断处理 CPU 相同,从而提升 CPU 缓存命中率,降低网络延迟。 + +要开启 RFS 需要修改两个参数: + +{% highlight text %} +/proc/sys/net/core/rps_sock_flow_entries +/sys/class/net/${DEV}/queues/rx-${NUM}/rps_flow_cnt +{% endhighlight %} + +前者为期望获得的最大并发连接数,该值必须为 2 的幂,如果不是,系统设置为向上最接近的 2 的幂;后者的值为 rps_sock_flow_entries/N,N 表示设备接收队列的数量,对于单队列两者设置为同一值。 + + + + + + +## XPS (Transmit Packet Steering) + +同样是 Tom Herbert 提交的 patch,主要是针对多队列的网卡发送时的优化,对于单队列几乎没有效果。其中 RFS 是根据包接收的队列来选择 CPU,而 XPS 是根据 CPU 来选择要发送的网卡队列。 + +CPU MAP 通过如下的方式设置,这里的 xps_cpus 是一个 CPU 的掩码,表示当前队列对应的 CPU。 + +{% highlight text %} +/sys/class/net/${DEV}/queues/tx-${NUM}/xps_cpus +{% endhighlight %} + +XPS 就是提高多对列下的数据包发送吞吐量,具体来说就是提高了发送的局部性。 + + + +原理很简单,就是根据当前skb对应的hash值(如果当前socket有hash,那么就使用当前socket的)来散列到xps_cpus这个掩码所设置的cpu上,也就是cpu和队列是一个1对1,或者1对多的关系,这样一个队列只可能对应一个cpu,从而提高了传输结构的局部性。 + +没有xps之前的做法是这样的,当前的cpu根据一个skb的4元组hash来选择队列发送数据,也就是说cpu和队列是一个多对多的关系,而这样自然就会导致传输结构的cache line bouncing。 + +这里还有一个引起cache line bouncing的原因,不过这段看不太懂: + +点击(此处)折叠或打开 + + Also when sending from one CPU to a queue whose + transmit interrupt is on a CPU in another cache domain cause more + cache line bouncing with transmit completion. + +接下来来看代码,我这里看得代码是net-next分支,这个分支已经将xps合并进去了。 + +先来看相关的数据结构,首先是xps_map,这个数据结构保存了对应的cpu掩码对应的发送队列,其中queues队列就保存了发送对列.这里一个xps_map有可能会映射到多个队列。 + +点击(此处)折叠或打开 + + struct xps_map { + //队列长度 + unsigned int len; + unsigned int alloc_len; + struct rcu_head rcu; + //对应的队列序列号数组 + u16 queues[0]; + }; + +而下面这个结构保存了设备的所有的cpu map,比如一个设备 16个队列,然后这里这个设备的xps_dev_maps就会保存这16个队列的xps map(sysctl中设置的xps_map),而每个就是一个xps_map结构。 + +点击(此处)折叠或打开 + + struct xps_dev_maps { + //rcu锁 + struct rcu_head rcu; + //所有对列的cpu map数组 + struct xps_map __rcu *cpu_map[0]; + }; + +然后就是net_device结构增加了一个xps_dev_maps的域来保存这个设备所有的cpu map。 + +点击(此处)折叠或打开 + + struct net_device { + ................................ + #ifdef CONFIG_XPS + //保存当前设备的所有xps map. + struct xps_dev_maps __rcu *xps_maps; + #endif + .......................... + } + +内核发送数据包从 IP 层到驱动层会调用 dev_queue_xmit(),该函数会调用 dev_pick_tx() 选择一个队列。 + +先来分析下这个函数的主要流程,首先,如果设备只有一个队列,那么就选择这唯一的队列。 + +点击(此处)折叠或打开 + + if (dev->real_num_tx_queues == 1) + queue_index = 0; + +然后如果设备设置了回调函数ndo_select_queue,则调用ndo_select_queue来选择队列号,这里要注意,当编写驱动时,如果设置了回调函数ndo_select_queue,此时如果需要xps特性,则最好通过get_xps_queue来取得队列号。 + +点击(此处)折叠或打开 + + else if (ops->ndo_select_queue) { + queue_index = ops->ndo_select_queue(dev, skb); + queue_index = dev_cap_txqueue(dev, queue_index); + +然后进入主要的处理流程,首先从skb从属的sk中取得缓存的队列索引,如果有缓存,则直接返回这个索引,否则开始计算索引,这里就通过调用xps patch最重要的一个函数get_xps_queue来计算queue_index. + +点击(此处)折叠或打开 + + static struct netdev_queue *dev_pick_tx(struct net_device *dev, + struct sk_buff *skb) + { + .................................... + else { + struct sock *sk = skb->sk; + queue_index = sk_tx_queue_get(sk); + + if (queue_index < 0 || skb->ooo_okay || + queue_index >= dev->real_num_tx_queues) { + int old_index = queue_index; + //开始计算队列索引 + queue_index = get_xps_queue(dev, skb); + if (queue_index < 0) + //调用老的计算方法来计算queue index. + queue_index = skb_tx_hash(dev, skb); + ...................................................... + } + } + //存储队列索引 + skb_set_queue_mapping(skb, queue_index); + //返回对应的queue + return netdev_get_tx_queue(dev, queue_index); + } + +接下来我们来看get_xps_queue,这个函数是这个patch的核心,它的流程也很简单,就是通过当前的cpu id获得对应的xps_maps,然后如果当前的cpu和队列是1:1对应则返回对应的队列id,否则计算skb的hash值,根据这个hash来得到在xps_maps 中的queue的位置,从而返回queue id. + + + + +{% highlight c %} +static inline int get_xps_queue(struct net_device *dev, struct sk_buff *skb) +{ + struct xps_dev_maps *dev_maps; + struct xps_map *map; + int queue_index = -1; + + rcu_read_lock(); + dev_maps = rcu_dereference(dev->xps_maps); + if (dev_maps) { + // 根据cpu id得到当前cpu对应的队列集合 + map = rcu_dereference( + dev_maps->cpu_map[raw_smp_processor_id()]); + if (map) { + if (map->len == 1) // 如果队列集合长度为1,则说明是1:1对应 + queue_index = map->queues[0]; + else + queue_index = map->queues[reciprocal_scale(skb_get_hash(skb), + map->len)]; + if (unlikely(queue_index >= dev->real_num_tx_queues)) + queue_index = -1; + } + } + rcu_read_unlock(); + + return queue_index; +} +{% endhighlight %} + + + + static inline int get_xps_queue(struct net_device *dev, struct sk_buff *skb) + { + #ifdef CONFIG_XPS + struct xps_dev_maps *dev_maps; + struct xps_map *map; + int queue_index = -1; + + rcu_read_lock(); + dev_maps = rcu_dereference(dev->xps_maps); + if (dev_maps) { + //根据cpu id得到当前cpu对应的队列集合 + map = rcu_dereference( + dev_maps->cpu_map[raw_smp_processor_id()]); + if (map) { + //如果队列集合长度为1,则说明是1:1对应 + if (map->len == 1) + queue_index = map->queues[0]; + else { + //否则开始计算hash值,接下来和老的计算hash方法一致。 + u32 hash; + //如果sk_hash存在,则取得sk_hash(这个hash,在我们rps和rfs的时候计算过的,也就是四元组的hash值) + if (skb->sk && skb->sk->sk_hash) + hash = skb->sk->sk_hash; + else + //否则开始重新计算 + hash = (__force u16) skb->protocol ^ + skb->rxhash; + hash = jhash_1word(hash, hashrnd); + //根据hash值来选择对应的队列 + queue_index = map->queues[ + ((u64)hash * map->len) >> 32]; + } + if (unlikely(queue_index >= dev->real_num_tx_queues)) + queue_index = -1; + } + } + rcu_read_unlock(); + + return queue_index; + #else + return -1; + #endif + } + + + + +# 参考 + +相关内容可以参考内核文档 [Scaling in the Linux Networking Stack][network-scaling] ,实际上也就是内核中的 Documentation/networking/scaling.txt 。 + + + +[network-scaling]: https://www.kernel.org/doc/Documentation/networking/scaling.txt "关于网络针对SMP的优化" +[intel-flow-director]: http://www.intel.com/content/www/us/en/ethernet-products/ethernet-flow-director-video.html "关于 Intel 的 Flow Director 介绍视频" +[xps-mailist]: http://lwn.net/Articles/412062/ "Tom Herbert 提交的 XPS patch" +[rps-mailist]: http://lwn.net/Articles/328339/ "Tom Herbert 提交的 RPS patch" +[rfs-mailist]: http://lwn.net/Articles/381955/ "Tom Herbert 提交的 RFS patch" + + +[rss-struct]: /images/linux/network-rss.png +[rss-process]: /images/linux/network-speedup-rss.png + + + diff --git a/_drafts/2014-11-17-linux-context-schedule_init.md b/_drafts/2014-11-17-linux-context-schedule_init.md new file mode 100644 index 0000000..584dfdb --- /dev/null +++ b/_drafts/2014-11-17-linux-context-schedule_init.md @@ -0,0 +1,89 @@ +--- +Date: October 19, 2013 +title: Linux 进程切换与协程 +layout: post +comments: true +language: chinese +category: [linux] +--- + +在本文中介绍了 Linux 的进程切换以及协程的相关文档,包括了上下文相关信息、Linux 进程切换的过程以及协程相关的信息。 + + + + +# 简介 + +处理器在任何时候总会处于以下状态中的一种: + +1. 内核态,与进程无关,处于中断上下文,处理中断请求。 +2. 内核态,与进程无关,用于处理 softirq 或者 tasklet 。 +3. 内核态,运行于进程上下文,处理进程在内核态的一些请求。 +4. 用户态,运行于用户空间。 + +上述的状态是有顺序关系的,其中后两者可以相互抢断;而其它的则只能按照顺序优先级,例如当一个 CPU 上跑着 softirq 时,其它的 softirq 是不会执行的,但是硬件的中断可以抢占。 + +接下来看看什么是上下文。 + + + + +## 上下文 + +一个进程执行时,其中 CPU 所有寄存器的值、进程的状态信息、进程打开的文件、进程相关的内存信息、堆栈中的内容等被称为该进程的上下文。 + +另外,在硬件信号触发中断时,会导致内核调用中断处理程序,进入内核空间。而在这一过程的硬件相关的信息,也要传递给内核,内核通过这些参数进行中断处理,这一环境也就被称为中断上下文。 + +通常来说,一个进程的上下文就被分为如下的三个部分: + +* 用户级上下文: 正文、数据、用户堆栈以及共享存储区; +* 寄存器上下文: 通用寄存器、程序寄存器 (IP)、处理器状态寄存器 (EFLAGS)、栈指针 (ESP); +* 系统级上下文: 进程控制块 (task_struct)、内存管理信息 (mm_struct、vm_area_struct、pgd、pte)、内核栈。 + +当发生进程调度时,进行进程切换就是上下文切换 (context switch),操作系统必须对上面提到的全部信息进行切换,包括保存现在的进程上下文,并切换到新的进程上下文,新调度的进程才能运行。 + + + + +## 进程切换 + +Linux 进程切换是通过 schedule() 函数完成的,用于选择那个进程可以运行,何时投入运行。其中,会涉及到具体使用那个调度类执行,本文中不会涉及太多,可以通过如下命令查看所有的调度类: + +{% highlight text %} +$ grep kernel/sched -rne 'const struct sched_class .* = {' +{% endhighlight %} + +假设,已经选中了需要切换的进程,那么进程切换的流程基本如下: + +{% highlight text %} +schedule() + |-__schedule() + |-pick_next_task() # 选择需要调度的进程 + |-context_switch() # 切换到新的MM以及寄存器状态 + |-switch_mm() # 把虚拟内存从一个进程映射切换到新进程中 + |-switch_to() # 保存、恢复寄存器状态以及栈信息 + |-barrier() # 内存屏障 +{% endhighlight %} + +可见,在进行进程切换时,就涉及了上述进程上下文中的全部信息,同时会导致该进程相关的 CPU Cache 都失效了。 + + + + + + +# 参考 + +关于上下文信息可以参考 Documentation/DocBook/kernel-hacking.xml 的内容,需要通过 make help 查看内核所有的命令,通过 make htmldocs 生成 html 格式的文档。 + +介绍如何测试上下文切换的成本,可以参考一篇相关的论文 [Quantifying The Cost of Context Switch](http://www.cs.rochester.edu/u/cli/research/switch.pdf) 。 + + +关于进程切换的耗时强烈推荐参考 [How long does it take to make a context switch?](http://blog.tsunanet.net/2010/11/how-long-does-it-take-to-make-context.html) 这篇文章,其中包括了多种类型的比较,以防万一保存了一个 mhtml 格式的 [本地版本](/reference/linux/How long does it take to make a context switch.mhtml) 。 + + + + diff --git a/_drafts/2014-11-23-linux-rcu_init.md b/_drafts/2014-11-23-linux-rcu_init.md new file mode 100644 index 0000000..e567516 --- /dev/null +++ b/_drafts/2014-11-23-linux-rcu_init.md @@ -0,0 +1,41 @@ +--- +Date: October 19, 2013 +title: Linux 的 RCU 机制 +layout: post +comments: true +language: chinese +category: [linux] +--- + + + + + + + + + +# seqlock 顺序锁 + + +用于能够区分读与写的场合,并且是读操作很多、写操作很少,写操作的优先权大于读操作。 + +seqlock的实现思路是,用一个递增的整型数表示sequence。写操作进入临界区时,sequence++;退出临界区时,sequence再++。写操作还需要获得一个锁(比如mutex),这个锁仅用于写写互斥,以保证同一时间最多只有一个正在进行的写操作。 + +当sequence为奇数时,表示有写操作正在进行,这时读操作要进入临界区需要等待,直到sequence变为偶数。读操作进入临界区时,需要记录下当前sequence的值,等它退出临界区的时候用记录的sequence与当前sequence做比较,不相等则表示在读操作进入临界区期间发生了写操作,这时候读操作读到的东西是无效的,需要返回重试。 + + + + + +# 参考 + +关于 RCU 的实现机制,可以参考 [Introduction to RCU][rcu-paulmck],Kernel 实现者整理的相关资料,十分详细。 + +关于并行编程可以参考 [Is Parallel Programming Hard, And, If So, What Can You Do About It?][parallel-program],一篇不错的介绍文章,包括了免费的 PDF,也可以从 [本地][parallel-local] 下载,版权归作者所有。 + + + +[rcu-paulmck]: http://www2.rdrop.com/users/paulmck/RCU/ "RCU编程,Kernel 实现者整理的相关资料" +[parallel-program]: https://www.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html "关于并行编程,一本不错的书" +[parallel-local]: /reference/linux/perfbook.2015.01.31a.pdf "关于并行编程,一本不错的书,本地保存版本" diff --git a/_drafts/2014-11-24-network-load-balancing_init.md b/_drafts/2014-11-24-network-load-balancing_init.md new file mode 100644 index 0000000..fd07bf8 --- /dev/null +++ b/_drafts/2014-11-24-network-load-balancing_init.md @@ -0,0 +1,151 @@ +--- +Date: October 19, 2013 +title: Linux 网络负载均衡 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +对于一个每秒处理百万请求的网站来说,通常是有整个集群提供服务的,而如何做到负载均衡呢? + + + +# 简介 + +在 GRE 中,对负载均衡是如下定义的:Traffic load balancing is how we decide which of the many, many machines in our datacenters will serve a particular request. + +如何选择一个最优的解决方案实际上有多种考虑因素:从全局来看还是局部,使用硬件还是软件,所处理场景的特性等。例如,对于搜索以及视频服务来说,前者希望有较小的 RTT,而后者则希望有较大的带宽。 + +接下来我们看看从一个请求的处理流程来说,有哪些节点可以或者需要做负载均衡。 + +下图是 Amazon 的一套组网方案,首先是 DNS 系统,然后是 CDN 网络,前端的负载均衡,web 服务器,数据中心的负载均衡,应用服务器,数据库,存储等。 + +![network]({{ site.url }}/images/network/lb-overview.png){: .pull-center} + +其中前端负载均衡之后还包括了两个可用区,用来保证高可用。 + +# DNS + +在客户端发送请求前,首先需要通过 DNS 查询域名对应的 IP 地址,这也就意味着可以首先通过 DNS 做负载均衡,而在这阶段所作的就是,尽可能返回一个最优的 IP 。 + +首先,最简单的方式是返回多个 A 或者 AAAA 地址,而客户端随意选择其中的一个,从而可以做到打散。 + +上述的方法实现非常简单,同样也带有很多问题。首先,我们很难控制客户端应该选择那个 IP 地址,当然,可以通过 SRV 设置权重以及优先级,但目前对 HTTP 请求是无效的。 + +另外,为了选择最近的服务器,减小 RTT,可以通过 anycast+BGP 解决,后面我们详细讨论其实现的方式。 + +但是还有个问题,通常的 DNS 请求并不是直接请求权威服务器的,而是直接请求的 ISP 的代理。这也就意味着,权威服务器收到的将是代理的 IP 地址,而非客户端的 IP,从而可能会导致出错。目前的解决方案是使用 EDNS 协议,它将会携带客户端的子网信息,目前 OpenDNS 是支持该功能的。 + +DNS 存在着一些功能限制,最明显的就是缓存、512Bytes,我们所能做的就是在做方案时铭记。 + + + +# 软件负载均衡 + +网络中常见的的负载均衡主要分为两种:一种是通过硬件来进行进行,常见的硬件有比较昂贵的 NetScaler、F5、Radware 和 Array 等商用的负载均衡器;也有类似于 LVS、Nginx、HAproxy 的基于 Linux 的开源的负载均衡策略。 + +商用负载均衡可以建立在四~七层协议之上,因此适用面也就更广所以有其不可替代性,而且有专业的维护团队来对这些服务进行维护,不过缺点就是花销太大,所以对于规模较小的网络服务来说暂时还没有必要使用。 + +对于软件实现的负载均衡,其中 LVS 是建立在四层协议上面的,而另外 Nginx 和 HAproxy 是建立在七层协议之上的。 + + +## LVS + +从性能和稳定上来说 LVS 基本达到了 F5 硬件设备的 60% 性能,不过配置也最麻烦,而且健康检测需要另外配置 Ldirector 。在此简单介绍下 LVS,它配置非常简单,在内核中实现,仅做请求分发之用,可以支持几乎所有应用的负载均衡,包括 http、数据库、聊天室等。 + + +## Nginx + +Nginx 工作在网络的第 7 层,所以它可以针对 http 应用本身来做分流策略,比如针对域名、目录结构等,相比之下 LVS 并不具备这样的功能。 + +可以负载超过 1 万的并发,除了负载均衡,还能作 Web 服务器,而且可以通过 Geo 模块来实现流量分配,不过不支持 session 保持,而且对后端 real server 的健康检查功能效果不好,而且只支持通过端口来检测,不支持通过 url 来检测。 + +## HAproxy + +其优点正好可以补充 nginx 的缺点,支持 session 保持,同时支持通过获取指定的 url 来检测后端服务器的状态。支持 TCP 模式的负载均衡,比如可以给 MySQL 的从服务器集群和邮件服务器做负载均衡。 + + + + + + + + + + + + + + +# Virtual IP Address + +简单来说,用户的请求会直接发送给 VIP,而拥有该 VIP 的是一个负载均衡器,而真正处理该请求的服务器,接收的是负载均衡器发送的转发请求。 + +通常来说,我们希望请求发送给压力最小的服务器,不过这种方法只适用于无状态的请求。对于有状态的请求,可以将报文中的某个字段做 hash,从而一个请求只会发送到相同的服务器。 + +不过这样仍然存在问题,假设之前的 hash 算法是 id mod N,那么如果一台机器宕机则成为 id mod (N-1) 从而导致当前处理的连接被重置,为此就需要一种 consistent hashing 算法。 + + + + + + + + + + + + + diff --git a/_drafts/2014-12-08-network-nginx-loadbalance_init.md b/_drafts/2014-12-08-network-nginx-loadbalance_init.md new file mode 100644 index 0000000..36b1522 --- /dev/null +++ b/_drafts/2014-12-08-network-nginx-loadbalance_init.md @@ -0,0 +1,157 @@ +--- +Date: October 19, 2013 +title: Nginx 负载均衡 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +Nginx 是一个高性能的 HTTP 服务器和反向代理服务器,在此我们将其作为一个负载均衡服务器。 + + + +# 简介 + +Nginx 支持的三种负载均衡策略 + +轮询:将请求依次轮询发给每个服务器。 + +最少链接:将请求发送给持有最少活动链接的服务器。 + +ip哈希:通过哈希函数决定请求发送给哪个服务器。 + +权重:服务器的权重越高,处理请求的概率越大。 + + +轮询负载均衡 + +在nginx.conf配置文件中添加如下配置,此配置有三台服务器提供支付服务。 +复制代码 + +http { + upstream CashServers { + server CashServers1.com; + server CashServers2.com; + server CashServers3.com; + } + + server { + listen 80; + + location / { + proxy_pass http://CashServers; + } + } +} + +复制代码 + + + + 需要注意以下几点 + 1.缺省配置就是轮询策略; + 2.nginx负载均衡支持http和https协议,只需要修改 proxy_pass后协议即可; + 3.nginx支持FastCGI, uwsgi, SCGI,memcached的负载均衡,只需将 proxy_pass改为fastcgi_pass, uwsgi_pass, scgi_pass,memcached_pass即可。 + 4.此策略适合服务器配置相当,无状态且短平快的服务使用。 + 四、最少链接负载均衡 + + 复制代码 + + http { + upstream CashServers { + least_conn; + server CashServers1.com; + server CashServers2.com; + server CashServers3.com; + } + + server { + listen 80; + + location / { + proxy_pass http://CashServers; + } + } + } + + 复制代码 + + + + 需要注意以下几点 + 1.最少链接负载均衡通过least_conn指令定义; + 2.此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况; + 五、ip哈希负载均衡 + + 复制代码 + + http { + upstream CashServers { + ip_hash; + server CashServers1.com; + server CashServers2.com; + server CashServers3.com; + } + + server { + listen 80; + + location / { + proxy_pass http://CashServers; + } + } + } + + 复制代码 + + + + 需要注意以下几点 + 1.ip哈希负载均衡使用ip_hash指令定义; + 2.nginx使用请求客户端的ip地址进行哈希计算,确保使用同一个服务器响应请求; + 3.此策略适合有状态服务,比如session; + 六、权重负载均衡 + + 复制代码 + + http { + upstream CashServers { + server CashServers1.com weight=3; + server CashServers2.com weight=2; + server CashServers3.com weight=1; + } + + server { + listen 80; + location / { + proxy_pass http://CashServers; + } + } + } + + 复制代码 + + + + 需要注意以下几点 + + 1. 权重负载均衡需要使用weight指令定义; + + 2. 权重越高分配到需要处理的请求越多; + + 3.此策略可以与最少链接负载和ip哈希策略结合使用; + + 4.此策略比较适合服务器的硬件配置差别比较大的情况; + + + 七、健康检测 + + nginx内置了针对服务器的健康检测机制,如果特定服务器请求失败,则nginx即可进行标记待下次就不会请求分配给它。max_fails定义失败指定次数后进行标记服务器不可用。 +# 参考 + + +http://freeloda.blog.51cto.com/2033581/1288553 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-02-15-program-c-continuous-integration_init.md b/_drafts/2015-02-15-program-c-continuous-integration_init.md new file mode 100644 index 0000000..029bbbd --- /dev/null +++ b/_drafts/2015-02-15-program-c-continuous-integration_init.md @@ -0,0 +1,39 @@ +--- +title: C 持续集成 +layout: post +comments: true +language: chinese +category: [misc] +keywords: c +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + + + + +## 代码风格 + +Linux源码标准检测 +http://www.tuxradar.com/content/newbies-guide-hacking-linux-kernel +http://www.cnblogs.com/wwang/archive/2011/02/24/1960283.html + +https://www.kernel.org/doc/html/v4.11/translations/zh_CN/coding-style.html +http://blog.csdn.net/jiang_dlut/article/details/8163731 +http://www.cnblogs.com/wwang/archive/2011/02/24/1960283.html + + + +持续集成: +http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html +https://github.com/nukc/how-to-use-travis-ci +https://scarletsky.github.io/2016/07/29/use-gitlab-ci-for-continuous-integration/ +http://xinsuiyuer.github.io/blog/2014/02/24/jenkins-c-plus-plus-ci-env/ + + +~/Workspace/kernel/linux-3.18.18/Documentation/CodingStyle + + +{% highlight text %} +{% endhighlight %} + diff --git a/_drafts/2015-03-08-linux-container-lxc-network_init.md b/_drafts/2015-03-08-linux-container-lxc-network_init.md new file mode 100644 index 0000000..5215fff --- /dev/null +++ b/_drafts/2015-03-08-linux-container-lxc-network_init.md @@ -0,0 +1,218 @@ +--- +Date: October 19, 2013 +title: LXC 网络设置相关 +layout: post +comments: true +language: chinese +category: [linux] +--- + +LXC 本身就是基于内核的 Namespace、Cgroup 的隔离技术,对于网络同样使用了网络上常用的虚拟技术,包括 veth、vlan、macvlan 等等。 + +在此仅介绍一下 LXC 中与网络相关的使用方法。 + + + + +# 网络设置 + +创建完容器之后,默认会将容器的配置文件保存在 /var/lib/lxc/centos/config,可以参考网络相关的配置,默认使用 veth,此时可以自动联网。 + +网络相关的设置可以查看源码中的 lxc-net 脚本,通过如下命令查看容器中的网络配置。 + +{% highlight text %} +----- 容器中的网络设置 +[root@centos ~]# ip address show eth0 +23: eth0@if24: mtu 1500 qdisc pfifo_fast state UP qlen 1000 + link/ether fe:cf:69:db:e7:3c brd ff:ff:ff:ff:ff:ff link-netnsid 0 + inet 192.168.122.118/24 brd 192.168.122.255 scope global dynamic eth0 + valid_lft 3542sec preferred_lft 3542sec + inet6 fe80::fccf:69ff:fedb:e73c/64 scope link + valid_lft forever preferred_lft forever + +----- 容器中的路由设置 +[root@centos ~]# ip route show +default via 192.168.122.1 dev eth0 +169.254.0.0/16 dev eth0 scope link metric 1023 +192.168.122.0/24 dev eth0 proto kernel scope link src 192.168.122.118 + +----- DNS解析地址 +[root@centos ~]# cat /etc/resolv.conf +; generated by /usr/sbin/dhclient-script +nameserver 192.168.122.1 + +----- 测试下能否ping通外部网络 +[root@centos ~]# ping www.hao123.com -c 3 +PING hao123.n.shifen.com (180.149.132.3) 56(84) bytes of data. +64 bytes from 180.149.132.3: icmp_seq=1 ttl=53 time=40.3 ms +64 bytes from 180.149.132.3: icmp_seq=2 ttl=53 time=34.1 ms +64 bytes from 180.149.132.3: icmp_seq=3 ttl=53 time=32.6 ms + +--- hao123.n.shifen.com ping statistics --- +3 packets transmitted, 3 received, 0% packet loss, time 2002ms +rtt min/avg/max/mdev = 32.694/35.728/40.303/3.291 ms +{% endhighlight %} + +其中与网络相关的默认的配置如下。 + +{% highlight text %} +lxc.network.type = veth +lxc.network.flags = up +lxc.network.link = virbr0 +lxc.network.hwaddr = fe:cf:69:db:e7:3c +{% endhighlight %} + +也就是说,如上的配置文件采用的是 veth (Virtual Ethernet Interface) 模式。该模式会创建一对网络设备,其中一端在容器中,另一端会添加到 bridge (通过 lxc.network.link 指定)。 + +{% highlight text %} +----- 查看网桥的设置 +# brctl show virbr0 +bridge name bridge id STP enabled interfaces +virbr0 8000.52540054901b yes vethXTBOA0 + +----- 查看主机是否打开了路由转发 +# cat /proc/sys/net/ipv4/ip_forward +1 + +----- 查看iptables的SNAT设置 +# iptables -nL POSTROUTING -t nat --line-number +Chain POSTROUTING (policy ACCEPT) +num target prot opt source destination +1 MASQUERADE tcp -- 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535 +2 MASQUERADE udp -- 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535 +3 MASQUERADE all -- 192.168.122.0/24 !192.168.122.0/24 +{% endhighlight %} + +veth 实际上就是模拟了一对以太网络设备,类似于一个管道,从设备的一端传入,然后从另一端传出;两个设备处在同一个广播域中,而非基于 IP 的端对端的传输。 + +{% highlight text %} +实际上网络设备的大致创建过程如下。 + +创建一对 VETH 网络设备。后续的网络设置将会通过 DNSmasq 进行,该程序实际会监听网桥 (在此为 virbr0) 上的端口,而且网桥的 IP 将作为容器的默认网关。 +
+# netstat -atunp | grep dnsmasq                 # 在主机中查看DNSmasq,包括其监听的端口等
+# ip addr show virbr0                           # 查看网桥对应的IP
+
+# grep nameserver /etc/resolv.conf              # 容器中的DNS服务器实际上是网桥上的IP
+# route -nee                                    # 默认路由同样是
+

  • + +主机一侧的 VETH 设备,实际上会链接网桥,也就是 virbr0 。

  • + +另一侧的 VETH 设备会移动到容器中,并且重命名为 eth0,并在容器中进行配置。

  • + +当 init 进程启动之后,会设置相应的网络设备,并可以使用网络了。 +
  • +通过上述的步骤之后,通过一对网络设备可以在主机和容器之间进行通讯。

    + +在主机中会有一个 vethXXXXX 的设备,而另一端在容器中;而主机端的设备会添加到配置中的网桥中。 +# brctl show # 查看网桥的配置 + +在如下的步骤中,我们尝试模拟 lxc 配置网络设备。当新建 NET-NS 之后,会默认创建一个 lo 设备;然后可以进入这个命名空间,创建一对设备,不过此时还没有设置 IP 。 + +# mkdir -p /var/run/netns +# ip netns add mynamespace +# ip netns list +# ls -l /var/run/netns + +# ip netns exec mynamespace bash # 进入NET-NS +# ip link add vethMYTEST type veth peer name eth0 # NS中新建一对设备 +# ip link list # NS中的网络设备,应该有3个 + +# ip address add 192.168.122.63/24 dev eth0 # 设置IP,注意网段 +# ip link set eth0 up # 启动设备 +# ip address list # 查看 + +# ip link set vethMYTEST netns 1 # 将另一个设备添加到PID=1的命名空间 +# ip link set vethMYTEST up # 返回主机执行 +# ip link list vethMYTEST # 主机中查看 +# ping -c 2 192.168.122.63 # 此时网络是不通的 + +# brctl addif virbr0 vethMYTEST # 将设备添加到网桥中 +# ping -c 2 192.168.122.63 # 好了现在网络是通的 +{% endhighlight %} + +可以从源码的 config_network_type() 函数中看到,LXC 中可供选择的网络类型包括了 veth、macvlan、vlan、phys、empty、none 六种,不太清楚 empty 和 none 什么区别。 + +# empty + +配置文件中相关的网络配置如下。 + +{% highlight text %} +# grep network /var/lib/lxc/centos/config +lxc.network.type = empty +lxc.network.hwaddr = fe:cf:69:db:e7:3c +lxc.network.flags = up +{% endhighlight %} + +此时通过 lxc-info 查看时会发现,实际上是没有联网的,不过这正好符合预期,empty 网络定义如下: + +{% highlight text %} +empty network type creates only the loopback interface +{% endhighlight %} + +此时会创建一个 Namespace,所以在 host 上是看不到 lo 设备的。 + +{% highlight text %} +----- 查看进程ID +# ps aux | grep 'lxc-start -n centos' + +----- 通过procfs查看对应的namespace +# ls -l /proc/17923/ns/ +{% endhighlight %} + + + +# veth + +如下是对于 veth 的描述。 + +{% highlight text %} +a peer network device is created with one side assigned to the container and +the other side is attached to a bridge specified by the lxc.network.link. +{% endhighlight %} + +与网络相关的配置如下。 + +{% highlight text %} +# grep network /var/lib/lxc/veth01/config +lxc.network.type = veth +lxc.network.flags = up +lxc.network.link = virbr0 +lxc.network.hwaddr = fe:cf:69:db:e7:3c +{% endhighlight %} + + + + + +# phys + +{% highlight text %} +an already existing interface specified by the lxc.network.link is assigned to the container +{% endhighlight %} + + + + + + + + + + + + + + + + + +# 参考 + +[Exploring LXC Networking](http://containerops.org/2013/11/19/lxc-networking/),介绍 LXC 网络不错的文章,可以参考 [本地文档](/reference/linux/container/Exploring LXC Networking.mht) 。 + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-03-15-how-to-handle-passwords_init.md b/_drafts/2015-03-15-how-to-handle-passwords_init.md new file mode 100644 index 0000000..e90be8e --- /dev/null +++ b/_drafts/2015-03-15-how-to-handle-passwords_init.md @@ -0,0 +1,21 @@ +--- +title: Linux LVS +layout: post +comments: true +language: chinese +category: [linux] +keywords: linux,密码,保存,password +description: +--- + + + + + + +## 参考 + +[Storing User Passwords Securely: hashing, salting, and Bcrypt](http://dustwell.com/how-to-handle-passwords-bcrypt.html) 。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-03-27-linux-container-lxc-sourcecode_init.md b/_drafts/2015-03-27-linux-container-lxc-sourcecode_init.md new file mode 100644 index 0000000..c2ff61b --- /dev/null +++ b/_drafts/2015-03-27-linux-container-lxc-sourcecode_init.md @@ -0,0 +1,243 @@ +--- +Date: October 19, 2013 +title: LXC 源码解析 +layout: post +comments: true +language: chinese +category: [linux] +--- + + + + +在之前的 [LXC 简介](/blog/linux-lxc-introduce.html) 中介绍了如何搭建 CentOS 7 容器,在此介绍如何单独启动 sshd 这一个进程。 + +LXC 的源码可以直接从 [linuxcontainers.org](https://linuxcontainers.org/lxc/downloads/) 上下载。 + + +# lxc-start + +启动很简单,实际是执行 init 命令,而对于比较简单的容器,如单独启动 sshd 进程,则实际上会执行 /sbin/init.lxc 命令,其源码可以参考 src/lxc_init.c 文件。 + +如下是容器启动过程: + +{% highlight text %} +main() # lxc_start.c + |-lxc_list_init() # 初始化配置(-s 指定)的列表 + |-lxc_caps_init() # 以root或者程序的uid、gid启动程序 + |-lxc_arguments_parse() # 解析传入的参数 + |-lxc_log_init() # log初始化操作 + |-lxc_container_new() # 初始化container实例,包括调用的函数 + |-c->load_config() # 加载配置文件 + | + |-do_lxcapi_start() # 代码中通过c->start()调用 + | |-ongoing_create() # 判断是否在创建镜像时失败了,失败则退出 + | |-daemonize? # 是否后台运行 + | |-lxc_check_inherited() # 如果后台执行,需要关闭部分文件描述符 + | |-__lxc_start() # 尝试启动容器,实际调用该函数 + | | |-lxc_init() + | | | |-lsm_init() + | | | |-lxc_cmd_init() // LSM安全模块初始化 + | | | |-lxc_read_seccomp_config() + | | | |-lxc_set_state() + | | | |-run_lxc_hooks() + | | | |-lxc_create_tty() // 创建伪终端设备 + | | | |-setup_signal_fd() + | | | |-lxc_console_create() + | | | | |-openpty() + | | | |-ttys_shift_ids() + | | | + | | |-must_drop_cap_sys_boot() // 判断是否支持ctrl-alt-del重启,支持会新建进程 + | | | + | | |-lxc_spawn() # 此时会创建多个子进程 + | | | |-lxc_sync_init() # 创建sock对,用于通讯 + | | | |-resolve_clone_flags() # 设置flags + | | | |-cgroup_init() + | | | |-cgroup_create() + | | | |-preserve_ns() + | | | |-attach_ns() + | | | |-lxc_clone(do_start, ...) # 在新的NS中创建进程 + | | | | |-clone(do_clone, ...) # 系统调用,外面加了一个壳,实际调用do_start + | | | |-lxc_sync_fini_child() + | | | + | | |-get_netns_fd() + | | | + | | |-lxc_poll() // 等待程序结束 + | | | |-lxc_mainloop_open() + | | | |-lxc_mainloop_add_handler() + | | | |-lxc_console_mainloop_add() + | | | |-lxc_cmd_mainloop_add() + | | | | |-lxc_cmd_accept() + | | | | |-lxc_cmd_handler() + | | | | |-lxc_cmd_process() // 设置一系列的回调函数 + | | | | |-lxc_cmd_console_callback() + | | | |-lxc_mainloop() + | | | + | | |-waitpid() + | | + | |-free_init_cmd() # 清空之前申请的资源 + | + |-lxc_container_put() // 清理 + + +# 新创建的进程 +do_start() + |-lxc_setup() # 设置容器、ip、utsname等 + | |-do_rootfs_setup() // 设置根目录 + | | |-mount() + | | |-remount_all_slave() // 删除 + | | |-run_lxc_hooks() + | | |-setup_rootfs() // 挂载根目录 + | | + | |-setup_utsname() // 设置主机名 + | | + | |-setup_network() // 设置网络端口eth0 + | | |-setup_netdev() + | | + | |-check_autodev() // 设置init命令等 + | | + | |-setup_mount() // 设置配置文件中的挂载点 + | | |-setmntent() + | | |-setup_mount_entries() + | | | |-mount_entry_on_relative_rootfs() + | | |-endmntent() + | | + | |-lxc_mount_auto_mounts() // 自动挂载一些主要目录,如proc、sys + | | + | |-setup_console() + | |-setup_tty() + | |-setup_dev_symlinks() + | |-tmp_proc_mount() + | | + | |-setup_pivot_root() + | | |-setup_rootfs_pivot_root() + | | + | |-setup_pts() + | + |-lxc_sync_barrier_parent() + | + |-handler->ops->start() # 最后调用init,调用start()@start.c + + + lxc_console_allocate() + |-lxc_console_peer_proxy_alloc() + |-openpty() +{% endhighlight %} + +# 数据结构 + +## lxc_handler + +这个结构体种保存了核心的内容,通过 lxc_init() 进行初始化。 + + +# 调试 + + +## GDB + +对一些常见的问题我们可以直接通过 GDB 进行调试。 + +{% highlight text %} +# cd src/lxc // 跳转到含有编译后二进制文件的目录下 +# gdb lxc-start // 开始调试lxc-start +... ... +Reading symbols from src/lxc/lxc-start...done. +(gdb) set args -n sshd -F -l DEBUG -o /tmp/1 +(gdb) b main +Breakpoint 1 at 0x400f20: file lxc_start.c, line 205. +(gdb) run +Starting program: src/lxc/lxc-start -n sshd -l DEBUG -o /tmp/1 +[Thread debugging using libthread_db enabled] +Using host libthread_db library "/lib64/libthread_db.so.1". + +Breakpoint 1, main (argc=7, argv=0x7fffffffdb88) at lxc_start.c:205 +205 { +{% endhighlight %} + + + +## 修改日志级别 + +为了方便调试我们可以设置日志级别,默认日志级别为 ERROR,需要注意的是,因为在打印日志前需要做一系列的初始化操作,所以即使设置为 DEBUG 日志级别,开始的部分也无法输出。 + +在调试启动时,可以在命令行后面添加 -l DEBUG -o /tmp/1 用于调试,也即 \-\-logfile、\-\-logpriority,这两个参数需要同时指定,否则无法打印,例如: + +{% highlight text %} +# ./lxc-start -n sshd -l DEBUG -o /tmp/lxc-start.log +{% endhighlight %} + + + + + +# 其它 + +## 问题排查 + +在调试源码时,如果安装的 lxc 版本与编译源码的版本不同,那么有可能会报如下的错误。 + +{% highlight text %} +./lxc-start: symbol lookup error: ./lxc-start: undefined symbol: current_config +{% endhighlight %} + +这主要时由于动态库的版本不同导致的,通过 ldd 查看时会发现,加载的库时 /lib64/liblxc.so.1 的库。 + +{% highlight text %} +# ldd lxc-start + linux-vdso.so.1 => (0x00007fffae967000) + liblxc.so.1 => /lib64/liblxc.so.1 (0x00007f8c1c7ea000) + libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f8c1c5c5000) + libutil.so.1 => /lib64/libutil.so.1 (0x00007f8c1c3c1000) + libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f8c1c1a5000) + libc.so.6 => /lib64/libc.so.6 (0x00007f8c1bde4000) + libcap.so.2 => /lib64/libcap.so.2 (0x00007f8c1bbde000) + libseccomp.so.2 => /lib64/libseccomp.so.2 (0x00007f8c1b9b2000) + libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f8c1b79c000) + /lib64/ld-linux-x86-64.so.2 (0x00007f8c1ca79000) + libpcre.so.1 => /lib64/libpcre.so.1 (0x00007f8c1b53a000) + liblzma.so.5 => /lib64/liblzma.so.5 (0x00007f8c1b315000) + libdl.so.2 => /lib64/libdl.so.2 (0x00007f8c1b111000) + libattr.so.1 => /lib64/libattr.so.1 (0x00007f8c1af0b000) +{% endhighlight %} + +这主要是由于动态库的查找顺序导致的,更改查找顺序有很多种方法,在此简单使用如下的方法。 + +{% highlight text %} +----- 修改配置文件,将本地目录添加到开始位置 +# cat /etc/ld.so.conf +. +/lib64 +/usr/lib64 + +----- 直接加载更新配置缓存 +# ldconfig + +----- 建立liblxc.so.1的符号链接 +$ ln -s liblxc.so liblxc.so.1 +{% endhighlight %} + + + + + + + +# 源码解析 + +直接从官方网站 [linuxcontainers.org][lxc-offical] 下载源码,然后通过如下方式编译。 + + +{% highlight text %} +----- 如下方式安装会有部分文件在/share目录下 +$ ./configure --exec_prefix=/usr --prefix= +{% endhighlight %} + + + + +[lxc-offical]: https://linuxcontainers.org/ "LXC 官方网站" + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-04-01-bitcoin-introduce_init.md b/_drafts/2015-04-01-bitcoin-introduce_init.md new file mode 100644 index 0000000..ed7b4d4 --- /dev/null +++ b/_drafts/2015-04-01-bitcoin-introduce_init.md @@ -0,0 +1,57 @@ +--- +title: MySQL 写在开头 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql +description: 保存一下经常使用的经典 MySQL 资源。 +--- + + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-04-14-linux-daemon-process_init.md b/_drafts/2015-04-14-linux-daemon-process_init.md new file mode 100644 index 0000000..1e00078 --- /dev/null +++ b/_drafts/2015-04-14-linux-daemon-process_init.md @@ -0,0 +1,106 @@ +--- +title: MySQL 基本介绍 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + + + + + +http://www.cnblogs.com/JohnABC/p/4079669.html +http://yangxikun.github.io/linux/2013/11/11/linux-process.html +http://blog.csdn.net/a600423444/article/details/6958025 +http://alfred-sun.github.io/blog/2015/06/18/daemon-implementation/ +http://qujunorz.blog.51cto.com/6378776/1563927 +https://zhuanlan.zhihu.com/p/25118420 +https://www.ibm.com/developerworks/cn/linux/l-cn-nohup/ +http://blog.csdn.net/taiyang1987912/article/details/44850999 +http://www.cnblogs.com/zhiguo/p/3370599.html +http://blog.chinaunix.net/uid-27105712-id-3356916.html +http://www.embedu.org/Column/7509.html + +http://cwe.mitre.org/top25/ + + +https://gist.githubusercontent.com/jamiesun/3097215/raw/2873acdf1c784896442099cee3ef3093077a0877/daemon.py +在Start the daemon前增加,文件权限判断。 +try: + file(self.pidfile,'w').write("writable test\n") +except IOError, err: + print str(err) +sys.exit(1)AVL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +c_avl_tree_t *c_avl_create(int (*compare)(const void *, const void *)); +入参: + 比较函数,类似strcmp(); +实现: + 1. 保证 compare 有效,非 NULL; + 2. 申请内存,部分结构体初始化。 +返回: + 成功返回结构体指针;参数异常或者没有内存,返回 NULL; + +int c_avl_insert(c_avl_tree_t *t, void *key, void *value); +返回: + -1:内存不足; + 0: 节点写入正常; + 1: 节点已经存在; + +int c_avl_get(c_avl_tree_t *t, const void *key, void **value); +调用者保证 t 存在 (非NULL)。 +返回: + -1:对象不存在; + 0: 查找成功,对应的值保存在value中; + +int c_avl_remove(c_avl_tree_t *t, const void *key, void **rkey, void **rvalue); +返回: + -1:对象不存在; + + +_remove() +search() +rebalance() +verify_tree() + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-04-22-mysql-config_init.md b/_drafts/2015-04-22-mysql-config_init.md new file mode 100644 index 0000000..44b96d9 --- /dev/null +++ b/_drafts/2015-04-22-mysql-config_init.md @@ -0,0 +1,279 @@ +--- +title: MySQL 配置文件 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,database,配置文件,配置 +description: 在 MySQL 中,配置项可以通过配置文件文件或者启动时通过命令行指定,而且对于各个配置项的含义比较复杂,接下来我们看看 MySQL 中配置项的内容。 +--- + +在 MySQL 中,配置项可以通过配置文件文件或者启动时通过命令行指定,而且对于各个配置项的含义比较复杂,接下来我们看看 MySQL 中配置项的内容。 + + + +![mysql logo]({{ site.url }}/images/databases/mysql/config-logo.png "mysql logo"){: .pull-center width="300px"} + +## 简介 + +读取配置文件的顺序通常为 /etc/my.cnf 、 basedir/my.cnf 、 datadir/my.cnf 、\-\-defaults-extra-file 、 ~/.my.cnf 、 \-\-defaults-file ,也就是用户目录下的配置文件最后读取。 + +在实际环境中,如果需要准确找到 MySQL 的配置文件位置,我们可以尝试如下的方法。查看执行 MySQL 进程的全部命令参数,找到 mysqld 的位置。 + +{% highlight text %} +$ cat /proc/$(pidof mysqld)/cmdline | tr '\0' '\n' +{% endhighlight %} + +根据以上获取的 mysqld 的位置后,可以通过如下命令查询 mysqld 查找配置文件顺序,其中如下命令中的 mysqld 可以实用如上获得的绝对地址。 + +{% highlight text %} +$ mysqld --help --verbose 2>/dev/null | grep -A1 "Default options are read" +{% endhighlight %} + +最后可知道其读取的顺序为 /etc/mysql/my.cnf /etc/my.cnf ~/.my.cnf 。 + +其中,有一个参数 \-\-defaults-extra-file 指定了配置文件,该文件通常会出现在全局配置文件之后,在用户配置文件之前。 + +也就为意味着,如果启动时使用了上述配置项,而且其它配置文件都存在,当有一个 "变量" 在其它配置文件中都出现了,那么 **后面的配置文件中的参数变量值会覆盖前面配置文件中的参数变量值**,就是说会使用 ~/.my.cnf 中设置的值。 + + + +另外,一般在安装路径的 share 路径下存放了推荐的配置文件:my-small.cnf 、 my-medium.cnf 、 my-large.cnf 、 my-huge.cnf;可以通过如下命令查找。 + +{% highlight text %} +# find / -name "my-medium\.cnf" +{% endhighlight %} + +如果想查看 MySQL 的一些全局变量设置,在非登录并有权限情况下可以这样查看。 + +{% highlight text %} +$ mysqladmin variables -u root -p +{% endhighlight %} + +这个操作也就相当于登录时使用命令 show global variables; 。 + +### 配置文件格式 + +在每个配置文件中,根据不同的配置项分为了不同的模块,例如客户端配置项 ([client])、服务端配置项 ([mysqld]) 等。 + +而注释通过 # 标示。 + +## 典型配置 + +{% highlight text %} +[client] #=== 客户端配置 +user = root # 用户名、密码等设置 +password = passwd +host = 127.1 # 默认使用本地IP +socket = /tmp/mysql.sock # 指定UNIX Socket文件位置 + +[mysqld] #=== 服务端配置 +########basic settings######## +server-id = 11 +port = 3306 +user = mysql +bind_address = 10.166.224.32 + +plugin_dir = /opt/mysql/lib/plugin # 指定plugins的路径 + + +autocommit = 0 +character_set_server = utf8mb4 +skip_name_resolve = 1 +max_connections = 800 +max_connect_errors = 1000 +datadir = /data/mysql_data +transaction_isolation = READ-COMMITTED +explicit_defaults_for_timestamp = 1 +join_buffer_size = 134217728 +tmp_table_size = 67108864 +tmpdir = /tmp +max_allowed_packet = 16777216 +sql_mode = "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER" +interactive_timeout = 1800 +wait_timeout = 1800 +read_buffer_size = 16777216 +read_rnd_buffer_size = 33554432 +sort_buffer_size = 33554432 +########log settings######## +log_error = error.log # 使用datadir相对路径,如果没有err后缀会自动添加 +log_warnings = 1 # 是否将警告信息记录进错误日志,0(禁用),1(启用) + # >1(建新连接产生的"失败的连接"和"拒绝访问"的错误信息) +log_output = FILE # 日志输出到文件,可作用于查询日志和慢查询日志 +long_query_time = 2 # 记录超过2秒的查询输出,压力较大时会输出较多日志 +slow_query_log = ON # 是否记录超过long_query_time时间的慢查询 +slow_query_log_file = slow.log # 设定文件格式的慢查询日志的存储路径 +log_queries_not_using_indexes = ON # 记录没有使用索引的SQL +log_slow_admin_statements = ON # 记录表管理语句 +log_slow_slave_statements = ON # 记录主备复制时,超时的SQL +expire_logs_days = 90 # 超出此天数的二进制日志文件将被自动删除 +log_throttle_queries_not_using_indexes = 10 # 每分钟输出的未使用索引的SQL数量 +min_examined_row_limit = 100 # 查询检查返回少于该参数指定行的SQL不被记录到慢查询日志 + +########replication settings######## +log_bin = mysql-bin # 开启binlog +log_bin_index = mysql-bin.index # 设置二进制文件索引 +binlog-row-image = minimal # 传输时只记录后镜像,减小磁盘、网络、内存的使用 +binlog_format = row # 指定binlog的格式,包括了statement,row,mixed +sync_binlog = 1 # 日志缓存刷新时机,有组提交之后性能损耗不会太大 +max_binlog_size = 1073741824 # binlog文件的最大值,默认1GB +binlog_cache_size = 32768 # 为每个会话分配的binlog内存大小 + +relay_log = relay-log # (备库)对于设置relay-log +relay_log_index = relay-log.index # (备库)同时设置文件索引 +slave_parallel_workers = 4 # (备库)SQL线程的并发数,最大为1024个线程 +report_host = 127.1 # (备库)汇报host,在主使用SHOW SLAVE HOSTS查看 +report_port = 3308 # (备库)同上,会自动汇报 +master_info_repository = TABLE # (备库)保存主机相关信息master.info(FILE) + # slave_master_info(TABLE) +relay_log_info_repository = TABLE # (备库)记录relaylog相关信息relay-log.info(FILE) + # slave_relay_log_info(TABLE) +relay_log_recovery = ON # (备库)relaylog自动修复 +relay_log_purge = ON # (备库)不保存relaylog,执行完后直接删除 +sync-relay-log = 1 # (备库)SQL线程执行事务时,同时保存位点 +sync-master-info = 1000 # (备库) +log_slave_updates = OFF # (备库)是否将接收到的记录到本地binlog,用于级联复制 +slave-parallel-workers = 8 # (备库)设置SQL线程的并发 +slave-parallel-type = LOGICAL_CLOCK # (备库)利用组提交的逻辑值做并发 + +gtid_mode = ON # 开启GTID模式 +enforce_gtid_consistency = 1 # 设置GTID一致,需要与上一个参数同时开启 +binlog_gtid_simple_recovery = 1 +slave_skip_errors = ddl_exist_errors # 忽略可能导致的DDL异常 +binlog-rows-query-log-events = ON # ROW模式binlog添加SQL信息,方便排错 +log-bin-trust-function-creators = ON # 同时复制主库创建的函数 + + + +########innodb settings######## +innodb_page_size = 8192 +innodb_buffer_pool_size = 6G +innodb_buffer_pool_instances = 8 +innodb_buffer_pool_load_at_startup = 1 +innodb_buffer_pool_dump_at_shutdown = 1 +innodb_lru_scan_depth = 2000 +innodb_lock_wait_timeout = 5 +innodb_io_capacity = 4000 +innodb_io_capacity_max = 8000 +innodb_flush_method = O_DIRECT +innodb_file_format = Barracuda +innodb_file_format_max = Barracuda +innodb_log_group_home_dir = /redolog/ +innodb_undo_directory = /undolog/ +innodb_undo_logs = 128 +innodb_undo_tablespaces = 3 +innodb_flush_neighbors = 1 +innodb_log_file_size = 4G +innodb_log_buffer_size = 16777216 +innodb_purge_threads = 4 +innodb_large_prefix = 1 +innodb_thread_concurrency = 64 +innodb_print_all_deadlocks = 1 +innodb_strict_mode = 1 +innodb_sort_buffer_size = 67108864 + +########semi sync replication settings# +plugin_load = "rpl_semi_sync_master = semisync_master.so;rpl_semi_sync_slave = semisync_slave.so" +loose_rpl_semi_sync_master_enabled = ON # 开启semisync master +loose_rpl_semi_sync_slave_enabled = ON # 开启semisync slave +loose_rpl_semi_sync_master_timeout = 5000 # 设置超时时间5s + +[mysqld-5.7] +innodb_buffer_pool_dump_pct = 40 +innodb_page_cleaners = 4 +innodb_undo_log_truncate = 1 +innodb_max_undo_log_size = 2G +innodb_purge_rseg_truncate_frequency = 128 +binlog_gtid_simple_recovery = 1 +log_timestamps = system +transaction_write_set_extraction = MURMUR32 +show_compatibility_56 = on + + +{% endhighlight %} + + + + + +## 配置选项详解 + +为了方便登陆,可以在 [lient] 中根据需要使用如下配置文件指定登陆的默认值;当然,**线上生产环境一定不要设置** 。 + +### 常用配置 + +{% highlight text %} +bind-address=0.0.0.0 # 指定监听IP地址 +{% endhighlight %} + +### log-bin + +启动 binlog 记录,log-bin=/data/mysql/mysql-bin,可以指定绝对路径,默认在 datadir 目录下。 + +#### report-* + +report 相关的参数是设置在从库上的,包含四个参数 ```report-[host|port|user|password]```,可以在 my.cnf 中设置了 report-host 时,在从库执行 ```START SLAVE``` 的时候,会将 report-host 和 report-port(默认3306) 发给主库,主库记录在全局哈希结构变量 slave_list 中。 + +如果想要连 report-user 和 report-password 也显示出来,则需要主库配置 show-slave-auth-info 。 + + + + + +profiling=[ON|OFF]
    +在 5.1 中添加,可以用来查看一调 query 消耗的 CPU、IO、IPC、SWAP、PAGE FAULTS、CONTEXT SWITCH 等,同时还能得到该语句执行过程中 MySQL 所调用的各个函数在源文件中的位置。 +
    +mysql> set profiling=ON;
    +mysql> show profiles;                                         # 获取保存的所有查询的信息
    +mysql> select emp_no from salaries where salary = 90930;
    +mysql> show profile;                                          # 查看最近一条SQL的执行信息
    +mysql> show profile cpu, block io for query N;                # 查询单个SQL的性能指标
    +
    +详细语法信息可以查看 SHOW PROFILE Syntax 。 + +

    + +# 参考 + +关于 MySQL 配置选项的处理顺序可以参考 [Command-Line Options that Affect Option-File Handling](http://dev.mysql.com/doc/refman/5.7/en/option-file-options.html) 中的内容。 + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-04-30-mysql-security_init.md b/_drafts/2015-04-30-mysql-security_init.md new file mode 100644 index 0000000..47c28c0 --- /dev/null +++ b/_drafts/2015-04-30-mysql-security_init.md @@ -0,0 +1,62 @@ +--- +Date: October 19, 2013 +title: MySQL 安全设置 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + +很多时候在部署一种产品时,安全性往往是最后考虑的,不过这也带来了很大的风险。 + + + + +## 权限控制 + +通常数据库只需要本地网络访问,所以完全没有必要将权限开放给网络。 + +{% highlight text %} +mysql> GRANT ALL ON *.* TO 'user'@'%'; ← 可能会存在风险 +mysql> GRANT ALL ON *.* TO 'user'@'192.168.9.128'; ← 需要指定IP +{% endhighlight %} + +一定要设置 root 密码,通常可以设置成只能本地访问;最好可以将 root 修改为其它名称。 + +{% highlight text %} +mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new-passowrd'); +mysql> UPDATE mysql.user SET user='foobar' WHERE user='root'; +mysql> FLUSH PRIVILEGES; +{% endhighlight %} + +对于系统文件,其中数据文件最好使用 mysql:mysql 用户,其中需要确保只有 mysql 和 root 可以访问;对于二进制文件,同样需要设置。 + + + + +## 禁用 LOCAL INFILE + +用于防止非授权用户访问本地文件,可以通过如下方式查看。 + +{% highlight text %} +mysql> LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE tbl; +mysql> SELECT load_file('/etc/passwd'); +{% endhighlight %} + +此时,需要在配置文件中添加如下内容。 + +{% highlight text %} +[mysqld] +set-variable=local-infile=0 +{% endhighlight %} + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-05-02-mysql-skeleton_init.md b/_drafts/2015-05-02-mysql-skeleton_init.md new file mode 100644 index 0000000..66a93c3 --- /dev/null +++ b/_drafts/2015-05-02-mysql-skeleton_init.md @@ -0,0 +1,642 @@ +--- +Date: October 19, 2013 +title: MySQL 代码导读 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,database,数据库,源码,解析 +description: 在 MySQL 的官网上,MySQL 号称是 The World's Most Popular Open Source Database ,既然是开源的,据说又这么牛掰,那不看看源码真有点对不起 MySQL 了。 不禁想起了 PostgreSQL 号称是 The World's Most Advanced Open Source Database ^_^'' 废话少说,本文简单介绍一下 MySQL 的执行流程。 +--- + +在 MySQL 的官网上,MySQL 号称是 The World's Most Popular Open Source Database ,既然是开源的,据说又这么牛掰,那不看看源码真有点对不起 MySQL 了。 + +不禁想起了 PostgreSQL 号称是 The World's Most Advanced Open Source Database ^_^'' + +废话少说,本文简单介绍一下 MySQL 的执行流程。 + + + +![mysql skeleton]({{ site.url }}/images/databases/mysql/skeleton-logo.jpg "mysql skeleton"){: .pull-center width="50%" } + +## 简介 + +MySQL 是基于线程的,在进程启动之后可以通过如下方式查看 MySQL 启动的进程信息。 + +{% highlight text %} +$ cat /proc/`pidof mysqld`/status | grep ^Threads ← 查看线程数 +$ cat /proc/`pidof mysqld`/sched ← 第一行即为线程数 +$ ls /proc/`pidof mysqld`/task ← 查看对应线程信息 + +$ pstree ← 查看启动后进程之间的关系 +$ pstree -p `pidof mysqld` ← 查看进程对应的线程 + +$ ps -Lf `pidof mysqld` ← 同样查看线程 +$ ps -eo ruser,pid,ppid,lwp,psr,args -L | grep mysql ← psr为线程运行的cpu-id + +$ pstack `pidof mysqld` | less ← 打印对应进程的调用堆栈 +{% endhighlight %} + + + + + +## 源码导读 + +简单介绍下 MySQL 源码实现。 + +### 简介 + +在 MySQL 源码中,有很多类似 HAVE_XXX 的宏定义,如果是 RPM 安装包,也可以查看 mysql-xxx.devel 包中包含的 my_config.h 文件定义,也就是编译相关二进制时的宏定义。 + +实际上,在通过 CMake 编译源码时,会以 config.h.cmake 为模板扫描系统的一些配置,并生成 config.h 文件,然后 CMake 脚本会把 config.h 拷贝一份保存为 my_config.h 文件。 + +下面以 HAVE_SYS_EPOLL_H 宏定义为例,看下是如何实现的;在 configure.cmake 文件中,有如下的定义。 + +{% highlight text %} +CHECK_INCLUDE_FILES(sys/epoll.h HAVE_SYS_EPOLL_H) +{% endhighlight %} + +上述文件中包含了 ```INCLUDE(CheckSymbolExists)```,而 CheckSymbolExists 是 CMake 的公共模块,一般在 cmake 的安装目录下,通常位于 /usr/share/cmake-X.X/Modules 目录下。 + +继续研究上述的文件,在源代码中有 ```MACRO(CHECK_SYMBOL_EXISTS SYMBOL FILES VARIABLE)``` 定义;这个宏的作用就是,查找相关文件 (FILES) 里面是否包含相关符号 (SYMBOL);如果存在则设置变量为 1,MESSAGE 宏会在屏幕上做相关打印。 + +部分宏也可以在编译时,通过类似 ```-DEMBEDDED_LIBRARY``` 定义。 + + +### ut_ad()宏定义 + +在代码中,会有 ```ut_ad(dict_index_is_clust(index));``` 类似的代码,下面看看这段代码的作用。 + +{% highlight cpp %} +#include "os0thread.h" +#define ut_a(EXPR) do { \ + if (UNIV_UNLIKELY(!(ulint) (EXPR))) { \ + ut_dbg_assertion_failed(#EXPR, \ + __FILE__, (ulint) __LINE__); \ + } \ +} while (0) + +#define ut_error \ + ut_dbg_assertion_failed(0, __FILE__, (ulint) __LINE__) + +#ifdef UNIV_DEBUG +#define ut_ad(EXPR) ut_a(EXPR) +#define ut_d(EXPR) EXPR +#else +#define ut_ad(EXPR) +#define ut_d(EXPR) +#endif +{% endhighlight %} + +也就是只有在 debug 模式下,会执行上述的代码。 + +
    + +我们从 MySQL 启动开始,看一下 MySQL 业务流程,详细的执行流程如下。 + +### 系统启动 + +首先是入口函数,也就是 C/C++ 的通用入口 main(),该函数在 sql/main.cc 文件中,而实际上其最终调用的是 mysqld_main()@sql/mysqld.cc,也就是 MySQL 的真正入口函数。其详细内容如下: + + + +{% highlight text %} +mysqld_main() + |-my_init() ← 做一些基本的初始化工作 + | |-getenv() ← 设置umask,获取HOME等 + | |-my_thread_global_ init() ← 初始化全局线程环境,包括私有数据、互斥量的初始化等 + | |-my_thread_init() ← 分配线程内存,主要用于mysys以及dbug + | + |-load_defaults() ← 加载默认的配置项 + |-handle_early_options() ← 做些初始参数解析,例如PS的初始化 + | |-handle_options() ← 通用的解析命令行函数 + |-init_sql_statement_names() ← 通过com_status_vars[]初始化,例如analyze等 + |-sys_var_init() ← 系统变量初始化 + | |-my_hash_init() ← 通过hash保存系统变量 + |-adjust_related_options() ← 调整参数,如open_file_limit等 + |-initialize_performance_schema() ← 如果需要则初始化PS + |-init_server_psi_keys() ← 如果需要则初始化PSI + | + |-init_error_log() + |-mysql_audit_initialize() ← 初始化audit全局接口,具体初始化稍后完成 + | + |-init_common_variables() ← 变量的初始化 + |-my_init_signals() + |-init_server_components() ← MySQL Server常用模块的初始化 + | |-mdl_init() + | |-partitioning_init() + | |-my_timer_initialize() + | |-init_server_query_cache() + | |-randominit() + | |-setup_fpu() + | |-init_slave_list() + | |-open_error_log() + | |-transaction_cache_init() + | |-delegates_init() + | |-process_key_caches() + | |-ha_init_errors() + | |-gtid_server_init() + | |-plugin_init() + | | |-plugin_load_list() + | | |-plugin_dl_add() ← 包含了线程池类似插件的处理 + | | + | |-ha_init() + | |-initialize_storage_engine() + | |-init_optimizer_cost_module() + | + |-init_ssl() + |-network_init() ← 初始化网络模块,包括初始化调度器,创建socket监听端口 + | + |-init_status_vars() + | + |-connection_event_loop() ← 管理、创建新连接,会是一个死循环 + | |-listen_for_connection_event() + | | |-poll() + | |-process_new_connection() + | |-add_connection() + | |-mysql_thread_create() ← 根据thread_handling参数选择具体方法 + | + |-my_thread_join() + |-clean_up() + |-mysqld_exit() ← 程序退出 +{% endhighlight %} + +### 初始化网络配置 + +网络配置其实比较简单,就是设置端口,创建套接字,绑定端口,监听端口,实现全部集中在 network_init() 函数中,下面直接给出相应的伪代码: + +{% highlight text %} +network_init() + |-set_ports() ← 设置端口号,#define MYSQL_PORT 3306 + |-Mysqld_socket_listener() ← 根据参数等,启动实例 + |-init_connection_acceptor() + |-setup_listener() ← 不同类型listerner调用接口不同,如socket、pipe、share_memory + |-tcp_socket() ← 创建tcp_socket实例 + |-get_listener_socket() ← 创建监听socket,并准备接收连接 + |-create_lockfile() + |-mysql_socket_socket() + | |-inline_mysql_socket_socket() + | |-socket() ← 创建套接字 + | + |-mysql_socket_bind() + | |-inline_mysql_socket_bind() + | |-bind() ← 绑定端口号 + | + |-mysql_socket_listen() + |-inline_mysql_socket_listen() + |-listen() ← 监听端口号 +{% endhighlight %} + +客户端与服务端通信的方式不止是 SOCKET 一种,MySQL 还支持三种连接方式:namepipe、unix socket 和 shared memory,即命名管道、unix 套接字和共享内存的方式,这三种方式是可以共存的,只是有些只支持本地,socket 是最通用的方式。 + + +### 管理/创建新连接 !!!! + +通过 connection_event_loop() 实现,而且 socket 管理其实比较简单,下面是其简单的处理代码: + +{% highlight text %} +connection_event_loop() ← 对应死循环,不断判断abort_loop参数 + |-get_instance() ← 获取连接处理的实例 + |-listen_for_connection_event() + | |-poll()/select() ← 监视socket文件描述符 + | |-mysql_socket_accept() ← 处理到来的客户端连接 + | |-Channel_info_tcpip_socket() ← 创建一个实例 + | + |-process_new_connection() + |-add_connection() ← 创建一个新的线程,不同方式会有不同处理方式 + |-mysql_thread_create() + |-pthread_create() ← 对应的处理函数是handle_connection() +{% endhighlight %} + +主要处理函数,一系列异常保护之后会停止在 select()/poll() 函数处,等待接受到新的连接,如果监控到有连接,则通过 accept() 函数接受客户端的连接。 + + +,然后新建一个 THD 类,将连接参数全部设置到 THD 类的参数上,最后调用 create_new_thread() 函数,这个函数便是重点。 + +mysql 为每个连接设置一个线程,而 oracle 同时也可以将请求放入一个队列当中。 + + +接着是创建线程来处理客户端发送来的请求,通过 create_new_thread()@sql/mysqld.cc 实现,该函数执行的主要流程如下: + +{% highlight c %} +static void create_new_thread(THD *thd) { + ++*thd->scheduler->connection_count; // 全局连接数自增 + thread_count++; // 全局线程数自增 + + // 真正创建线程,实际调用的是 thd->scheduler.add_connection(thd); + MYSQL_CALLBACK(thd->scheduler, add_connection, (thd)); +} +{% endhighlight %} + +在创建链接时,会对当前连接数检测 connection_count,先对互斥量 LOCK_connection_count 加锁,如果大于 max_connections+1,则报错,没有问题,才新建线程,一个典型的互斥线程。此时,全局连接数+1,全局线程数+1,然后调用 add_connection() 函数,现在线程创建成功了。 + +在 create_new_thread(thd) 的末尾,有一行代码,也就是如下的宏定义: + +{% highlight c %} +MYSQL_CALLBACK(thd->scheduler, add_connection, (thd)); sql/sql_callback.h + +#define MYSQL_CALLBACK(OBJ, FUNC, PARAMS) \ + do { \ + if ((OBJ) && ((OBJ)->FUNC)) \ + (OBJ)->FUNC PARAMS; \ + } while (0) +{% endhighlight %} + +这样,这个代码就是调用 thd->scheduler 的 add_connection 函数,参数是 (thd) 。这个函数就是我们在上面第一步设置连接的线程数中,one_thread_scheduler 和 one_thread_per_connection_scheduler 中设置的一个参数。这两者的区别便是是否创建了一个新的线程来处理到来的连接。 + +thd->scheduler 在 THD::THD() 构建函数中初始化,该值将继承全局的 thread_scheduler 。 + + +## 链接处理 + +在此,根据不同的链接方式会调用不同的接口,现在 MariaDB 支持三种处理方式。one_thread_scheduler 是单线程方式,也就是不会去新建线程,而线程池实现方式有些复杂,以后再详细了解。 + +所以,在此,重点研究 one_thread_per_connection_scheduler 链接方式,也就是说设置的 add_connection 函数实际最终调用的是 create_thread_to_handle_connection()。 + +void create_thread_to_handle_connection(THD *thd)@sql/mysqld.cc,在该函数中,如果设置了线程缓存,且缓存中有空闲的线程,则直接从栈中取出一个线程即可。 + +{% highlight c %} +create_thread_to_handle_connection(THD *thd) +{ + if (cached_thread_count > wake_thread) + thread_cache.push_back(thd); + else + thread_created++; + threads.append(thd); // 创建线程数自增,并加入到threads链表上 + mysql_thread_create(key_thread_one_connection, + &thd->real_id,&connection_attrib, + handle_one_connection, + (void*)thd) ; // 这就是真正创建线程的地方了 +} +{% endhighlight %} + +可见,最后调用了 mysql_thread_create() 函数,这是一个封装之后的函数,用于跨平台调用,对于 Linux,最后实际是通过 pthread_create() 创建了一个新的线程,而新线程的 处理函数为 handle_one_connection()。 + + +### 新线程处理流程 + +新线程处理函数为 void *handle_connection(void *arg),到此为止,一个新的 connection 被一个新创建的线程所单独处理,我们看下其中是如何进行处理的。 + +{% highlight c %} +// 连接处理函数,入参是连接对象Channel_info +void *handle_connection(void *arg) +{ + my_thread_init() // 初始化线程 + for (;;) { + THD *thd= init_new_thd(channel_info); // 新建一个线程对象 + thd_manager->add_thd(thd); // 添加到线程管理 + + if (thd_prepare_connection(thd)) // 包括用户认证 + handler_manager->inc_aborted_connects(); + else + { + while (thd_connection_alive(thd)) + { + if (do_command(thd)) // 处理命令 + break; + } + end_connection(thd); + } + close_connection(thd, 0, false, false); + + thd->get_stmt_da()->reset_diagnostics_area(); + thd->release_resources(); + } +} +{% endhighlight %} + +在新建完线程之后,会先调用 my_thread_init() 做线程的初始化,到目前为止,才算创建了一个新的线程,接着会有一些初始化的工作。 + +**注意,在此新建完线程后,后续的很多操作都会携带上该线程对象指针。** + +接着会通过 thd_prepare_connection() 函数进行一些登陆认证等操作,通过 login_connection() 函数实现,还有一些其它的初始化工作。 + +接下来主要执行工作是在 do_command() 函数,也就是主要的命令处理函数。 + +### 命令分发 + +接下来是主要的命令处理函数 ```bool do_command(THD *thd)@sql/sql_parse.cc```,该函数主要用来接收、解析、执行命令报文;在线程中,该函数会不断循环执行。 + +{% highlight c %} +bool do_command(THD *thd) +{ + thd->m_server_idle= true; + // 如下的命令会阻塞在网络读取,直到读取了最新的报文 + rc= thd->get_protocol()->get_command(&com_data, &command); + thd->m_server_idle= false; + + // 接下来准备分发命令 + return_value= dispatch_command(thd, &com_data, command); +} +{% endhighlight %} + +当客户端通过 TCP 连接上 MySQL 的服务器后,在发送请求之前,服务端的线程实际上是阻塞在 do_command() 函数中,也就是 socket 里的 read()。当接收到报文后,该函数同时还会作一些处理,如去除头部等。 + + + +**需要注意的是**,有的命令只需要在 dispatch_command() 执行,例如 COM_REGISTER_SLAVE;而部分则会在 mysql_execute_command() 中执行,例如 SQLCOM_CHANGE_MASTER 。 + +在 dispatch_command() 函数中,其主要的处理流程如下。 + +{% highlight c %} +bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length) +{ + switch (command) { + case COM_INIT_DB: ... ...; + case COM_QUERY: { + if (alloc_query(thd, com_data->com_query.query, + com_data->com_query.length)) + break; // fatal error is set + + Parser_state parser_state; + if (parser_state.init(thd, thd->query().str, thd->query().length)) + break; + // 开始进行SQL解析 + mysql_parse(thd, &parser_state); + + // 如果SQL中有通过分号分割的多条语句,同时会在下面处理,在此不赘述 + } + } +} +{% endhighlight %} + + +在该函数中,其主要作用的是一个巨大的 switch 语句,涵盖了 MySQL 支持的所有语句,包括了查询、PING、QUIT等指令,这些命令会在 include/my_command.h 中定义: + +{% highlight c %} +enum enum_server_command +{ + COM_SLEEP, COM_QUIT, COM_INIT_DB, COM_QUERY, COM_FIELD_LIST, + ... ... + COM_END +}; +{% endhighlight %} + +接下来命令的处理,就是根据不同的请求通过 switch 进入不同的函数入口,对于查询命令最后进入的是 COM_QUERY,先做一些初始化、写日志等后进入 ```mysql_parse()@sql/sql_parse.cc```,该函数是 SQL 语句解析的总入口。 + +### 命令解析 + +SQL 的解析包括了:词法分析,语法分析,语义分析,构造执行树,生成执行计划,计划的执行。SQL92 是最新的标准,里面的定义都是一些巴科斯范式(BNF),就是一种语法定义的标准。 + +MySQL 通过 YACC(Yet Another Compiler Compiler) 进行语法解析,不过没有采用 LEX 进行词法分析,YACC 接收来自词法分析阶段分解出来的 token 然后去匹配那些 BNF 。 + +另外,比较不错的嵌入式数据库 SQLite,词法分析器是手工写的,语法分析器由 Lemon 生成,如果感兴趣可以看下代码,在此就不详述了。 + +在 sql/sql_yacc.cc 源码中,有如下的定义;其中词法解析相关的主要处理函数在 sql/sql_lex.cc 文件中,其入口即 MYSQLlex() ,而主要的分词处理函数为 lex_one_token() 。 + +{% highlight c %} +#define yyparse MYSQLparse +#define yylex MYSQLlex +{% endhighlight %} + +#### 词法解析 + +可以直接通过 state_map[] 获得对应的状态,该数组在 init_state_maps() 中初始化,首先会将字符设置为 MY_LEX_IDENT 、数字设置为 MY_LEX_NUMBER_IDENT、空白字符设置为 MY_LEX_SKIP、其它的设置为 MY_LEX_CHAR ,然后会将一些特殊字符初始化。 + +而关于字符的判断如下,其中 s 为对应的字符集,c 对应的序号,也就是通过 _MY_X 进行判断。 + +{% highlight c %} +#define my_isalpha(s, c) (((s)->ctype+1)[(uchar) (c)] & (_MY_U | _MY_L)) +{% endhighlight %} + +每个字符集都会对应一个 ctype ,会通过该数组判断其类型。在 sql/lex.h 中定义了关键字,用两个数组存储 static SYMBOL symbols[] 和 static SYMBOL sql_functions[]。 + +#### SQL解析 + +仍回到如上的函数入口。 + +SQL 命令解析的入口是 mysql_parse(); sql/sql_parse.cc,如上所述 SQL 的语法/语义解析是通过 yacc 实现,规则文件是 sql/sql_yacc.yy 。 + +{% highlight c %} +void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state) +{ + mysql_reset_thd_for_next_command(thd); // 重置结构体 + lex_start(thd); // 初始化词法分析结构体 + + if (query_cache_send_result_to_client(...) <= 0) { // 在cache中查询 + err= parse_sql(thd, parser_state, NULL); // 不在cache中,直接查询 + error= mysql_execute_command(thd); // 解析完后开始执行SQL + } else { // 命中cache,直接返回 + hd->lex->sql_command= SQLCOM_SELECT; // 设置结果,更新统计 + ... ... + } +} +{% endhighlight %} + +在 mysql_parse() 中有段注释,大概的意思是:本来应该先调用 query_cache_send_result_to_client(),也即在 query_cache 中查询该语句,加快查询速度。失败才调用 lex_start() 和 mysql_reset_thd_for_next_command() 来初始化 thd 解析 sql。但是查询 cache 也需要干净的 thd,只能先调用 lex_start() 和 mysql_reset_thd_for_next_command() 来初始化 thd 了,这样导致代码和逻辑有悖。 + +首先是初始化以及重置操作,接着会在 cache 中查询,如果有相同的语句,则立即从 cache 返回结果,于是整个 sql 就结束了。 + +如果 cache 里不存在该 sql,则继续前进来到 parse_sql()@sql/sql_parse.cc,这个函数主要就是调用了 MYSQLparse(),而 MYSQLparse() 其实就是 bison/yacc 里的 yyparse。 + +下面就开始解析 sql 了,主要是关于词法分析和语法匹配,对于一条像 select * from test 的语句首先进入词法分析,此时会找到 2 个 token(select, from),然后根据 token 进行语法匹配,规则在 sql/sql_yacc.yy 里。 + +最后的解析结果中,lex->sql_command 保存了相应的命令。 + +sql 解析完了,然后是一些优化操作等,接着进入 mysql_execute_command()@sql/sql_ parse.cc 函数,这个函数是所有 sql 命令执行的总入口。 + + +### 命令执行 + +{% highlight c %} +int mysql_execute_command(THD *thd) +{ + switch (lex->sql_command) { + case SQLCOM_SHOW_EVENTS: ...; + case SQLCOM_SELECT: { + check_table_access(...); + res= execute_sqlcom_select(thd, all_tables); // 执行查询 + } + } +} +{% endhighlight %} + +在 mysql_execute_command() 中,先确定 command 要对哪张表操作 lex->first_lists_tables_same(); 根据该表的状态,会做一些预处理,尽量减少之后的操作对表的影响(因为目前还不知道这条指令执行之后,会对数据库产生什么样的影响)做好保护是必须的。 + +然后有个 switch 语句,他决定了 command 属于哪种类型,这些类型定义在 sql/sql_ lex.h 中: + +{% highlight c %} +enum enum_sql_command { + SQLCOM_SELECT, SQLCOM_CREATE_TABLE, ...... SQLCOM_END +}; +{% endhighlight %} + +仍然以查询命令为例,最后会进入 SQLCOM_SELECT 这个 case 分支。之后就是命令的解析,处理,以及然后查询,规整结果集。 + +最后 select 的执行,通过 execute_sqlcom_select()@sql/sql_parse.cc 实现,在 execute_sqlcom_select() 函数中,调用 handle_select() (优化入口),然后调用 mysql_select()。 + +mysql_select() 就是执行模块,这个模块代码比较复杂,可以清楚看到创建优化器 (JOIN::prepare)、优化 (JOIN::optimize)、执行 (JOIN::exec) 的3个步骤,在 MySQL 中,会将任何 select 都转换为 JOIN 来处理的。 + +MySQL 在设计时,采用了这样的思路:针对主要应用场景选择一个或几个性能优异的核心算法作为引擎,然后努力将一些非主要应用场景作为该算法的特例或变种植入到引擎当中。具体而言,MySQL 的 select 查询中,核心功能就是 JOIN 查询,因此在设计时,核心实现 JOIN 功能,对于其它功能,都通过转换为 JOIN 来实现。 + +即使对于最简单的 select name from student 也会转换为 JOIN 来操作。 + +{% highlight c %} +if (!(join= new JOIN(thd, fields, select_options, result))) + ... +if ((err= join->optimize())) + ... +join->exec(); +{% endhighlight %} + +结束了优化,我们要具体执行 join->exec(),该函数实际进入的是 JOIN::exec()@sql_select.cc。 + +exec()首先向客户端发送字段title的函数send_result_set_metadata(),没数据但字段也是要的。然后再进入 do_select() ,根据表的存储引擎跳入到引擎具体的实现。如果是 myisam,则通过 myisam 引擎扫描文件,其中 info->filename 实际保存的是文件的地址。 + +最后通过 join->result->send_data() 将数据发送给用户。并从 dispatch_command() 返回,最后在 net_end_statement 结束整个 sql 。 + + +## 总结 + +处理 MySQL 客户端命令,在此以 one_thread_per_connection_scheduler 方式为例,也就是创建 handle_one_connection() 独立线程处理请求。 + +{% highlight text %} +handle_connections_sockets() + |-poll() 通过gdb查看,可以看到在此等待连接 + |-thd = new THD; my_net_init() + |-create_new_thread() 根据不同的thread handler调用不同的函数 + |-create_thread_to_handle_connection() one_thread_per_connection_scheduler方式 + |-handle_one_connection() 创建的新线程来处理 + |-do_handle_one_connection() + |-do_command() 在死循环中处理 + |-my_net_read_packet() + |-dispatch_command() 一堆的switch,根据客户端报文类型解析,include/mysql_com.h +++=== SQL Interface ==|+++| + | |-mysql_change_db() 执行use db命令,COM_INIT_DB + | |-sql_kill() 执行kill命令,COM_PROCESS_KILL + | |- ... ... + | |-mysql_parse() 执行SQL语句,COM_QUERY + | |-lex_start() + | |-mysql_reset_thd_for_next_command() + | |-query_cache_send_result_to_client() + | |-parse_sql() + | | |-MYSQLparse() 通过yacc解析SQL,规则文件保存在sql/sql_yacc.yy + | | + | | 各种类型的SQL,一个大switch语句 + | |-mysql_execute_command() 根据不同的SQL语句执行,sql/sql_cmd.h,对item调试 + | |-execute_show_status() 执行show status,SQLCOM_SHOW_STATUS + | |- ... ... + | |-check_table_access() 执行select,SQLCOM_SELECT + | |-execute_sqlcom_select() + | | |-open_and_lock_tables() + | | | |-open_tables() + | | | | |-open_and_process_table() + | | | | |-open_table() + | | | | |-Table_cache::get_table() + | | | | |-get_table_share_with_discover() + | | | | | |-get_table_share() + | | | | | |-open_table_def() + | | | | | |-my_open() + | | | | | |-open_binary_frm() + | | | | | |-get_new_handler() 获取表的handler + | | | | |-my_malloc // 申请表数据结构 + | | | | |-open_table_from_share + | | | | |-handler::ha_open + | | | | |-ha_innobase::open + | | | | |-dict_table_open_on_name + | | | | |-dict_load_table + | | | | |-btr_pcur_is_on_user_rec + | | | | |-dict_load_table_low + | | | | | |-dict_mem_table_create + | | | | |-fil_space_for_table_exists_in_mem + | | | | |-fil_open_single_table_tablespace // 打开表空间文件 + | | | |-lock_tables() + | | | |-mysql_handle_derived() + | | |-query_cache_store_query() 先查看缓存 + | | | + | | |-handle_select() SQL处理的真正入口,会判断是否为union + | | |-mysql_union() 如果含有union,则调用该函数 + | | |-mysql_select() 否则调用该函数 +++=== Query Parser ===|++ | | + | | |-mysql_prepare_select() + | | | |-JOIN::prepare()@sql/sql_select.cc + | | | | |-setup_tables_and_check_access() + | | | | |-setup_wild() + | | | | |-setup_fields() + | | | | |-setup_without_group() + | | | | |-setup_order() order by语句相关 + | | | |-find_order_in_list() + | | | |-find_item_in_list() + | | | + | | |-lock_tables() + | | |-query_cache_store_query() + | | |-mysql_execute_select() + | | | +++=== Query Prepare ==|+++ | | + | | |-JOIN::optimize() @sql/sql_optimizer.cc + | | | + | | | + | | |-JOIN::explain() @sql/sql_explain.cc + | | | | 如果使用的是explain语句,返回而不执行 + | | | |-prepare_result() + | | | |-explain_query_specification() + | | | + | Explain_query::send_explain() +++=== Query Optimizer |==+++ | | + | | |-JOIN::exec() 根据执行计划进行相应处理 + | | |-exec_inner() + | | |-select_result::prepare() + | | |-select_result::prepare2() + | | |-select_send::send_result_set_metadata() + | | | |-Protocol::send_result_set_metadata() + | | | + | | |-do_select() 查询入口函数 + | | |-join->first_select() 1. 实际调用sub_select(),也即循环调用 + | | rnd_next()+evaluate_join_record() + | | | | + | | | | while循环读取数据 + | | | |-join_tab->read_first_record() 首次调用,实际为init_read_record() + | | | | |-ha_rnd_init() + | | | | | |-change_active_index() + | | | | | |-innobase_get_index() + | | | | |-innobase_trx_init() + | | | |-info->read_record() 再次调用,该函数在init中初始化 + | | | | + | | | |-evaluate_join_record() 处理一条查询记录 + | | | |-end_send() + | | | |-select_send::send_data() + | | | |-Protocol::write() + | | | + | | |-join->result->send_eof() +++=== Query Execution |==+++ | | + st_select_lex::cleanup | + | | + | | + | |-update_precheck() + | |-mysql_update() + | | |-open_normal_and_derived_tables() + | | |-mysql_prepare_update() + | | |-innobase_register_trx() + | | |-innobase_register_trx() + | | + | | + | | + | | + | | + | | + | | + | | + | + |-thd->protocol->end_statement() 将获得的查询结果发送到客户端 +{% endhighlight %} + +在查询记录时,会循环调用 ha_innobase::rnd_next() 和 evaluate_join_record() 获取并处理该部分的每条记录。 + + +### 结论 + +整个 connection manager 的流程十分清晰,单线程的连接一般很少使用,大多使用多线程方式。多线程连接中其实还涉及到线程缓冲池的概念,即如果一个连接断开后,其所创建的线程不会被销毁掉,而是放到缓冲池中,等待下一个新的 connection 到来时,首先去线程缓冲池查找是否有空闲的线程,有的话直接使用,木有的话才去创建新的线程来管理这个 connection。 + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-05-18-mysql-connection_init.md b/_drafts/2015-05-18-mysql-connection_init.md new file mode 100644 index 0000000..7010907 --- /dev/null +++ b/_drafts/2015-05-18-mysql-connection_init.md @@ -0,0 +1,335 @@ +--- +title: MySQL 链接方式 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,连接,connection +description: 与 Oracle 或者 Postgre 不同,MySQL 采用的是线程模型,在这里介绍通过 socket 链接到服务器之后,线程与链接直接是怎么处理的。 +--- + +与 Oracle 或者 Postgre 不同,MySQL 采用的是线程模型,在这里介绍通过 socket 链接到服务器之后,线程与链接直接是怎么处理的。 + + + +## 简介 + +现在 MySQL 支持三种处理链接的方式:no-threads、one-thread-per-connection 和 pool-of-threads,默认使用 one-thread-per-connection。而线程池的方式,只有企业版才支持;单线程则通常用户调试或者嵌入式的模式,因此,在此主要介绍每个连接单线程的方式。 + +在启动时可以通过 \-\-thread-handling=XXX 参数指定,也可以在配置文件中指定,而当前使用的链接方式可以通过 ```show variables like 'thread_handling'``` 查看。注意,该选项是只读的,也就是链接方式只能在启动时进行设置。 + +MariaDB 在 5.5 引入了一个动态的线程池方案,可以根据当前请求的并发情况自动增加或减少线程数,在此的线程池就是 MariaDB 的解决方案。 + +1. 单线程
    在同一时刻,最多只能有一个链接连接到 MySQL ,其他的连接会被挂起,一般用于实验性质或者嵌入式应用。 + +2. 多线程
    + 同一时刻可以支持多个链接同时连接到服务器,针对每个链接分配一个线程来处理这个链接的所有请求,直到连接断开,线程才会结束。
    + 这种方式存在的问题就是需要为每个连接创建一个新的 thread,当并发连接数达到一定程度,性能会有明显下降,因为过多的线程会导致频繁的上下文切换,CPU cache 命中率降低和锁的竞争会更加激烈。 + +3. 线程池
    解决多线程的方法就是降低线程数,这样就需要多个连接共用线程,这便引入了线程池的概念。线程池中的线程是针对请求的,而不是针对连接的,也就是说几个连接可能使用相同的线程处理各自的请求。 + +注意,后面主要介绍 MariaDB 的实现方式。 + +### 设置 + +如上所述,可以通过设置服务器的启动参数来设定连接的方式,通过 mysqld \-\-verbose \-\-help 命令可以查看所支持的选项,启动后可以通过 show status 查看与行状态,通过 show variables 查看启动时的变量。 + +{% highlight text %} +$ mysqld --thread-handling=no-threads/one-thread-per-connection/pool-of-threads + +$ cat /etc/my.cnf +[mysqld] +thread_handling=pool-of-threads + +mysql> SHOW VARIABLES LIKE 'thread_handling'; ← 查看连接配置 ++-----------------+---------------------------+ +| Variable_name | Value | ++-----------------+---------------------------+ +| thread_handling | one-thread-per-connection | ++-----------------+---------------------------+ +1 row in set (0.01 sec) +{% endhighlight %} + +除了上述的链接方式之外,为了防止链接过多,导致在管理时无法登陆,MariaDB 提供了额外的链接方式,可以通过设置如下的参数实现 \-\-extra-port=3308 \-\-extra-max-connections=1 。 + +注意,如果 extra-max-connections 设置为 2 则实际上可以创建三个链接,而且只支持 one-thread-per-connection 类似的方式。 + +### 监控状态 + +MySQL 启动后,会监听端口,当有新的客户端发起连接请求时,MySQL 将为其分配一个新的 thread,去处理此请求。从建立连接开始,CPU 要给它划分一定的 thread stack,然后进行用户身份认证,建立上下文信息,最后请求完成,关闭连接,释放资源。 + +高并发情况下,将给系统带来巨大的压力,不能保证性能。MySQL 通过线程缓存来是实现线程重用,减小这部分的消耗;一个连接断开,并不销毁承载其的线程,而是将此线程放入线程缓冲区,并处于挂起状态,当下一个新的连接到来时,首先去线程缓冲区去查找是否有空闲的线程,如果有,则使用之,如果没有则新建线程。 + +{% highlight text %} +mysql> SHOW VARIABLES LIKE 'thread_cache_size'; ← 可以重用线程的个数 ++-------------------+-------+ +| Variable_name | Value | ++-------------------+-------+ +| thread_cache_size | 9 | ++-------------------+-------+ +1 row in set (0.03 sec) + +mysql> SHOW STATUS LIKE 'threads%'; ← 查看状态 ++-------------------+-------+ +| Variable_name | Value | ++-------------------+-------+ +| Threads_cached | 0 | ← 已被线程缓存池缓存的线程个数 +| Threads_connected | 2 | ← 当前MySQL的连接数 +| Threads_created | 1065 | ← 已创建线程个数,可用来判断thread_cache_size大小 +| Threads_running | 1 | ← 正在运行的线程数 ++-------------------+-------+ +4 rows in set (0.13 sec) +{% endhighlight %} + + + + +## 源码导读 + +首先大致介绍线程管理。 + +### 数据结构 + +MariaDB 支持的链接类型可以通过 enum scheduler_types 查看,目前只支持上述的三种类型。根据启动时的配置项,会将所使用的链接方式会保存在 scheduler_functions thread_scheduler 变量中。 + +{% highlight c %} +struct scheduler_functions +{ + uint max_threads, *connection_count; + ulong *max_connections; + bool (*init)(void); + bool (*init_new_connection_thread)(void); + void (*add_connection)(THD *thd); + void (*thd_wait_begin)(THD *thd, int wait_type); + void (*thd_wait_end)(THD *thd); + void (*post_kill_notification)(THD *thd); + bool (*end_thread)(THD *thd, bool cache_thread); + void (*end)(void); +}; +{% endhighlight %} + +在初始化时,会通过如下函数设置全局变量 thread_scheduler 的值。 + +{% highlight text %} +mysqld_main() + |-init_common_variables() + |-get_options() +{% endhighlight %} + +该选项会在 mysqld_get_one_option()@sql/mysqld.cc 中处理,这个是在解析参数时调用的,相关的部分如下: + +{% highlight c %} +case OPT_ONE_THREAD: + thread_handling = SCHEDULER_NO_THREADS; + break; +{% endhighlight %} + +在主函数中,调用 logger.init_base() 之后,调用了一个 init_common_variables() 函数,里面初始化了这个变量的值。在 init_common_variables() 中调用了一个 get_options(); sql/mysqld.cc 函数,在该函数中对全局变量 thread_handling 进行初始化,然后设置相应的模式: + +{% highlight c %} +if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION) + one_thread_per_connection_scheduler(thread_scheduler, &max_connections, + &connection_count); +else if (thread_handling == SCHEDULER_NO_THREADS) + one_thread_scheduler(thread_scheduler); +else + pool_of_threads_scheduler(thread_scheduler, &max_connections, + &connection_count); +{% endhighlight %} + +在这三个函数中,其时就是设置了一个类型为 struct scheduler_functions 的 thread_scheduler 变量,只不过这些参数是一些函数指针罢了,也就是说在具体的调用中,只需要调用 add_connection 或 end_thread 即可,不需要知道到底是调用了哪个函数。 + + + + +## 线程池 + +对于为每个连接创建一个线程的方式,当并发连接数与可以提供服务的 CPU 数的比例达到一定程度后,性能会有明显下降。这是因为过多的线程会导致较多的内存消耗、频繁的上下文切换以及 CPU cache 命中率下降;同时在访问临界资源(通常是用互斥量包裹的资源,用于防止不同的CPU同时修改导致错误)时会大量增加锁的竞争,这种情况对写入影响会更大。 + +为了解决这一问题,最好是服务的线程数要小于客户端的链接数,同时希望能发挥 CPU 的最大性能,因此,通常是每个 CPU 会有一个工作线程。线程池适合短查询以及 CPU 密集型(如OLTP)。 + +### 简介 + +线程池的特点。 + +1. 整个连接池内部被分成 N 个小的 group,默认为 CPU 的个数,可以通过参数 thread_pool_size 设置,group 之间通过 round robin 的方式分配连接,group 内通过竞争方式处理连接,一个 worker 线程只属于一个 group 。 + +2. 每个group有一个动态的listener,worker线程在循环取event时,发现队列为空时会充当listener通过epoll的方式监听数据,并将监听到的event放到group中的队列 + +3. 延时创建线程,group中的活动线程数为0或者group被阻塞时,worker线程会被创建,worker线程被创建的时间间隔会随着group内已有的线程数目的增加而变大 + +4. worker线程,数目动态变化,这也是相对于5.1版本的一个改进,并发较大时会创建更多的worker线程,当从队列中取不到event时work线程将休眠,超过thread_pool_idel_timeout后结束生命 + +5. timer线程,它会每隔一段时间做两件事情:1)检查每个group是否被阻塞,判定条件是:group中的队列中有event,但是自上次timer检查到现在还没有worker线程从中取出event并处理;2)kill超时连接并做一些清理工作 + +### 配置参数 + +线程池相关的参数都保存在 sql/sys_vars.cc 文件中,可以通过 show variables like 'thread_pool%'; 命令查看,相关的参数有: + +* thread_pool_size,线程池中group的数目 + + MariaDB 的线程池分成了不同的 group ,而且是按照到来 connection 的顺序进行分组的,如第一个 connection 分配到 group[0] ,那么第二个 connection 就分配到 group[1] ,是一种 Round Robin 的轮询分配方式,默认值是 CPU core 个数。 + +* thread_pool_idle_timeout,线程最大空闲时间 + + 如果某个线程空闲的时间大于这个参数,则线程退出。 + +* thread_pool_stall_limit,监控线程的间隔时间 + + thread pool 有个监控线程,每隔一段时间,会检查每个 group 的线程可用数等状态,然后进行相应的处理,如 wake up 或者 create thread 。 + +* thread_pool_oversubscribe,允许的每个 group 上的活跃的线程数 + + 注意这并不是每个 group上 的最大线程数,而只是可以处理请求的线程数。 + +* thread_pool_max_threads,最大线程数 + + + +### 源码解析 + +如上的源码中,对于线程池,会在 pool_of_threads_scheduler() 中会将全局变量 thread_scheduler 初始化为 tp_scheduler_functions 。 + +{% highlight c %} +static scheduler_functions tp_scheduler_functions = { + 0, // max_threads + NULL, + NULL, + tp_init, // init + NULL, // init_new_connection_thread + tp_add_connection, // add_connection + tp_wait_begin, // thd_wait_begin + tp_wait_end, // thd_wait_end + post_kill_notification, // post_kill_notification + NULL, // end_thread + tp_end // end +}; + +void pool_of_threads_scheduler(struct scheduler_functions *func, + ulong *arg_max_connections, uint *arg_connection_count) +{ + *func = tp_scheduler_functions; + func->max_threads= threadpool_max_threads; + func->max_connections= arg_max_connections; + func->connection_count= arg_connection_count; + scheduler_init(); +} +{% endhighlight %} + +也就是说,根据 tp_scheduler_functions 变量中的定义,对于 thread pool 方式,这种方式对应的初始函数为 tp_init(),创建新连接的函数为 tp_add_connection(),等待开始函数为 tp_wait_begin(),等待结束函数为 tp_wait_end() 。 + +其中 thread pool 涉及的源码在 sql/threadpool_{common,unix}.cc,其中初始化函数如下。 + + +### 初始化 + +初始化的函数调用逻辑如下: + +{% highlight text %} +tp_init() + |-scheduler_init() + |-thread_group_init() # 对组进行初始化 + |-start_timer() # 开启监控线程timer_thread() +{% endhighlight %} + +至此为止,thread pool 里面只有一个监控线程启动,而没有任何工作线程,直到有新的连接到来。 + +### 处理链接 + +当有新连接到来时,会调用 create_new_thread() 函数,而该函数实际会调用 tp_add_connection() 函数,其中比较重要的数据结构如下: + +{% highlight c %} +struct connection_t { + THD *thd; + thread_group_t *thread_group; + connection_t *next_in_queue; + connection_t **prev_in_queue; + ulonglong abs_wait_timeout; // 等待超时时间 + bool logged_in; // 是否进行了登录验证 + bool bound_to_poll_descriptor; // 是否添加到了epoll进行监听 + bool waiting; // 是否在等待状态,如I/O、sleep +}; + +struct thread_group_t { + mysql_mutex_t mutex; + connection_queue_t queue; // connection请求链表 + worker_list_t waiting_threads; // group中正在等待被唤醒的thread + worker_thread_t *listener; // 当前group中用于监听的线程 + pthread_attr_t *pthread_attr; + int pollfd; // epoll 文件描述符,用于绑定group中的所有连接 + int thread_count; // 线程数 + int active_thread_count;//活跃线程数 + int connection_count; //连接数 + /* Stats for the deadlock detection timer routine.*/ + int io_event_count; //epoll产生的事件数 + int queue_event_count; //工作线程消化的事件数 + ulonglong last_thread_creation_time; + int shutdown_pipe[2]; + bool shutdown; + bool stalled; // 工作线程是否处于停滞状态 +} MY_ALIGNED(512); +{% endhighlight %} + +调用逻辑。 + +{% highlight text %} +tp_add_connection() + |-threads.append() # 首先添加到threads列表中 + |-alloc_connection() # 申请连接 + |-queue_put() # 放到队列中 + |-wake_or_create_thread() # 如果没有活跃的线程,那么就无法处理这个新到的请求 + | 这时调用该函数 + |-wake_thread() # 首先尝试唤醒group +{% endhighlight %} + +先根据 thread_id 对 group_count 取模,找到所属的 group,然后调用 queue_put() 将此 connection 放到 group 中的 queue 中。 + +等待线程链表 waiting_threads 中的线程,如果没有等待中的线程,则需要创建一个线程。至此,新到的 connection 被挂到了 group 的 queue 上,这样一个连接算是 add 进队列了,那么如何处理这个连接呢? + +由于是第一个连接到来,那么肯定没有 waiting_threads,此时会调用create_worker() 创建一个工作线程,也就是 worker_main() 。 + +{% highlight text %} +worker_main() + |-get_event() # 获取要处理的连接,其中等待事件可能是登陆请求或socket中未读的字节 + | |-queue_get() # 从队列中获取相应的连接 + | |-listener() # 如果队列中没有监听的进程,则选择其中一个用来监听 + | + |-handle_event() # 处理相应的连接 + | |-threadpool_add_connection() # 如果没有登陆过,则调用该函数 + | |-threadpool_process_request() # 否则已经登陆,则直接处理请求 + | |-set_wait_timeout() + | |-start_io() + | |-mysql_socket_getfd() + | |-io_poll_associate_fd() # 将新到连接的socket绑定到group的epoll上进行监听 + | + |-my_thread_end() +{% endhighlight %} + +其中 one thread per connection 中每个线程也是一个循环体,这两者的区别是,thread pool 的循环等待的是一个可用的 event,并不局限于某个固定的 connection 的 event;而前者的循环等待是等待固定连接上的 event,这就是两者最大的区别。 + +第一个连接到来,queue 中有了一个 connection,这时 get_event 便会从 queue 中获取到一个 connection,返回给 worker_main 线程。worker_main 接着调用 handle_event 进行事件处理。 + +每个新的连接到服务器后,其 socket 会绑定到 group 的 epoll 中,所以,如果 queue 中没有连接,需要从 epoll 中获取,每个 group 的所有连接的 socket 都绑定在 group 的 epoll 中,所以任何一个时刻,最多只有一个线程能够监听 epoll,如果epoll 监听到有 event的话,也会返回相应的connection,然后再调用handle_event进行处理。 + + +当 group 中的线程没有任务执行时,所有线程都会在 get_event() 处等待,但是有两种等待方式,一种是在 epoll 上等待事件,每个 group 中只有一个线程会做这个事情,且这个会一直等待,直到有新的事件到来。 + +另一种是等待参数 thread_pool_idle_time 指定的时间,若超过了这个时间,那么当前的线程的 get_event 就会返回空,然后 worker_main 线程就会退出。如果在线程等待的过程被唤醒的话,那么就会继续在 get_event 中进行循环,等待新的事件。 + + + + +## 其它 + +介绍一些与线程相关的内容。 + +### 链接池和线程池 + +链接池是部署在应用端,为了防止客户端会频繁建立链接然后中断链接,当用户不需要该链接时,会在客户端缓存这些链接,这样如果下次用户再需要建立链接时可以直接复用该链接。 + +链接池可以有效减小服务器和客户端的执行时间,但是不会影响查询性能。 + + diff --git a/_drafts/2015-05-20-mysql-memory_init.md b/_drafts/2015-05-20-mysql-memory_init.md new file mode 100644 index 0000000..e870cd1 --- /dev/null +++ b/_drafts/2015-05-20-mysql-memory_init.md @@ -0,0 +1,114 @@ +--- +title: MySQL 内存配置 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,memory,内存 +description: +--- + + + + +![Monitor Logo]({{ site.url }}/images/databases/mysql/monitor-logo.png "Monitor Logo"){: .pull-center } + + +如果应用程序使用内存过多,可能会导致操作系统通过 oom-killer 将其杀死,通常会在 syslog 日志中有所体现。MySQL 内存分为了两部分,内存使用量可以查看 [MySQL 内存计算器](http://www.mysqlcalculator.com/) 。 + +这里的内存配置,可以直接通过 SHOW VARIABLES LIKE 'variable-name' 查看。 + +### 全局级别 + + +#### key_buffer_size +#### query_cache_size +#### tmp_table_size +#### innodb_buffer_pool_size + +InnoDB Buffer Pool 的内存大小。 + +#### innodb_additional_mem_pool_size + +InnoDB 中其它内存使用。 + +#### innodb_log_buffer_size + + + + + + + + + + +### 会话级别 + +通过 max_connections 可以设置最大连接数,然后计算单个连接最大的内存消耗,就可以计算出与会话相关的最大内存消耗。 + +需要注意的是 read_buffer_size, sort_buffer_size, read_rnd_buffer_size, tmp_table_size 这些参数在需要的时候才分配,操作后释放;而且不管使用多少都分配该 size 的值所对应的内存数,即使实际需要远远小于这些 size。 + +另外,每个线程可能会不止一次需要分配 buffer,例如子查询,每层都需要有自己的 read_buffer, sort_buffer, tmp_table_size 等。 + +#### sort_buffer_size + +排序使用最大内存,在 Linux 下,如果超过 256KB 和 2MB 建议使用会话级变量。 + +#### read_buffer_size + +#### read_rnd_buffer_size + +#### join_buffer_size + +#### thread_stack + +#### binlog_cache_size + +{% highlight text %} +mysql> SHOW VARIABLES LIKE '%binlog_cache_size%'; ++-----------------------+----------------------+ +| Variable_name | Value | ++-----------------------+----------------------+ +| binlog_cache_size | 32768 | +| max_binlog_cache_size | 18446744073709547520 | ++-----------------------+----------------------+ +2 rows in set (0.00 sec) +{% endhighlight %} + + + + + +### sort_buffer_size + +mysql> SHOW VARIABLES LIKE '%sort_buffer_size%'; +mysql> SET GLOBAL sort_buffer_size = 1024*1024; +mysql> SHOW GLOBAL STATUS LIKE '%sort%'; + +sort_buffer_size   每个会话都会分配,通用排序优化与存储引擎无关。注意: 需要最少存储15条记录。如果秒级的 SHOW GLOBAL STATUS LIKE 'Sort_merge_passes' 参数较大,可以适当增加该参数,从而优化 ORDER BY, GROUP BY 的性能。   另外,优化器可能会分配过多的内存,进而影响其它查询,所以最好针对需要较大内存的查询设置会话变量,减小影响。在 Linux 中,有 256KB 和 2MB 两个边界值,可能会导致内存申请效率降低。max_sort_length   在执行 GROUP BY, ORDER BY, DISTINCT 操作时,比较时判断多少字节;如果需要增加该值,则需要同时调整 sort_buffer_size 。innodb_sort_buffer_size   ???官方文档提供了一个计算方法,没有看懂???   创建 InnoDB 索引的时候可以使用的排序缓存大小,索引创建完成后释放,???同时也会影响到 Online DDL???。创建索引时分为了 sort+merge 两个阶段,增加该值可以减小迭代次数,从而加快索引创建速度。myisam_sort_buffer_size   MyISAM 在 REPAIR TABLE(Sorting Index), CREATE INDEX(Creating Index), ALTER TABLE(Creating Index) 时使用内存数目。 + + +默认256K每个session 需要做一个排序分配的一个buffer,sort_buffer_size 不指定任何的存储引擎,适用于一般的方式进行优化如果你看到很多的ort_merge_passes per second你可以考虑增加sort_buffer_size 来加速ORDER BY 或者GROUP BY 操作,不能通过查询或者索引优化的。在MySQL 5.6.4 优化器尝试解决需要多少空间,但可以分配更多,达到极限。 在MySQL 5.6.4, 优化器分配整个buffer 即使如果根本不需要所有。在任何情况下, 设置它大于需要的全局会减慢很多的查询。最后是作为一个会话设置来增加,只有对需要大量的内存的会话, 在Linux上,有阀值为256KB 和2MB ,大的值可能显著的减慢内存分配,因此你应该考虑下面中的一个值。Index Condition Pushdown (EXPLAIN: Using Index Condition)首先 MySQL-Server 和 Storage-Engine 是两个组件,Server 负责 SQL 解析、优化、执行;Storage Engine 真正的负责 data+index 的读写。入入. 以前是这样: server 命令 storage engine 按 index 把相应的 数据 从 数据表读出, 传给server, server来按 where条件 做选择; 现在 ICP则是在 可能的情况下, 让storage engine 根据index 做判断, 如果不符合 条件 则无须 读 数据表. 这样 节省了disk IO. https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html•不用index 因为 你 是 select *, 而且你的where 是 >=, mysql 如果用index查找 则 会有 太多的 random disk IO. 所以它选择了 全表读.如何避免全表扫描通过 EXPLAIN 查看时,如果在 type 字段中有 ALL 的话,就表示为全表扫描,通常为如下条件:• 表比较小(小于10行,行比较短),使用索引反而会浪费时间;• 在 ON 和 WHERE 子句中,没有有效的约束条件;• 区分度(cardinality) 过低,全表扫描效率可能更高;可以通过 ANALYZE TABLE tbl_name 更新,或者使用 FORCE INDEX 指定索引。SELECT * FROM t1, t2 FORCE INDEX (idx_for_column) WHERE t1.col_name=t2.col_name;另外,可以设置 max_seeks_for_key=1000 ,也就是告诉优化器,所有的 key scan 不会超过 1000 次。??????Cardinality is the count of how many items in the index are unique.Reducing max_seeks_for_key to 1,000 is like telling MySQL that you want it to use indexes when the cardinality of the index is over 1,000. I’ve seen this variable reduced to as low as 1 on some servers without any issues.https://major.io/2007/08/03/obscure-mysql-variable-explained-max_seeks_for_key/ + + +'Using index condition' VS. 'Using where; Using index'Using index condition : where condition contains indexed and non-indexed column and the optimizer will first resolve the indexed column and will look for the rows in the table for the other condition (index push down)Using where; Using index : 'Using index' meaning not doing the scan of entire table. 'Using where' may still do the table scan on non-indexed column but it will use if there is any indexed column in the where condition first more like using index conditionWhich is better? 'Using where; Using index' would be better then 'Using index condition' if query has index all covering.When you see Using Index in the Extra part of an explain it means that the (covering) index is adequate for the query. In your example: SELECT id FROM test WHERE id = 5; the server doesn't need to access the actual table as it can satisfy the query (you only access id) only using the index (as the explain says). In case you are not aware the PK is implemented via a unique index. When you see Using Index; Using where it means that first the index is used to retrieve the records (an actual access to the table is not needed) and then on top of this result set the filtering of the where clause is done. In this example: SELECT id FROM test WHERE id > 5; you still fetch for id from the index and then apply the greater than condition to filter out the records non matching the condition + + +https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html + + + + + +ORDER BY 优化   未完成???MySQL 中部分场景可以通过索引将排序优化消除掉。CREATE TABLE IF NOT EXISTS foobar (    id INT NOT NULL,    first_name VARCHAR(30) NOT NULL,    last_name VARCHAR(30) NOT NULL,    address VARCHAR(150) NOT NULL,    INDEX idx_name (first_name, last_name),    PRIMARY KEY(id))""")CREATE TABLE IF NOT EXISTS foobar (    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,    first_name VARCHAR(30) NOT NULL,    last_name VARCHAR(30) NOT NULL,    address VARCHAR(150) NOT NULL,    INDEX idx_name (first_name, last_name));INSERT INTO foobar(first_name, last_name, address) VALUES ('Andy', 'Ddufresne', 'The Shawshank Redemption');SELECT * FROM foobar WHERE first_name='Andy' and last_name='Ddufresne';// key_part1,key_part2 前缀匹配,排序方式相同EXPLAIN SELECT * FROM foobar ORDER BY first_name,first_name LIMIT 10;EXPLAIN SELECT * FROM foobar ORDER BY first_name DESC, first_name DESC LIMIT 10;+----+-------------+--------+-------+---------------+----------+---------+------+------+-------+| id | select_type | table  | type  | possible_keys | key      | key_len | ref  | rows | Extra |+----+-------------+--------+-------+---------------+----------+---------+------+------+-------+|  1 | SIMPLE      | foobar | index | NULL          | idx_name | 184     | NULL |   10 | NULL  |+----+-------------+--------+-------+---------------+----------+---------+------+------+-------+1 row in set (0.00 sec)// key_part1=constant,key_part2 开始为常量  ???为什么第一个采用了Using index conditionEXPLAIN SELECT * FROM foobar WHERE first_name='Andy' ORDER BY last_name;+----+-------------+--------+------+---------------+----------+---------+-------+-------+------------------------------------+| id | select_type | table  | type | possible_keys | key      | key_len | ref   | rows  | Extra                              |+----+-------------+--------+------+---------------+----------+---------+-------+-------+------------------------------------+|  1 | SIMPLE      | foobar | ref  | idx_name      | idx_name | 92      | const | 19244 | Using index condition; Using where |+----+-------------+--------+------+---------------+----------+---------+-------+-------+------------------------------------+1 row in set (0.00 sec)EXPLAIN SELECT * FROM foobar WHERE first_name='Andy' ORDER BY last_name DESC;+----+-------------+--------+------+---------------+----------+---------+-------+-------+-------------+| id | select_type | table  | type | possible_keys | key      | key_len | ref   | rows  | Extra       |+----+-------------+--------+------+---------------+----------+---------+-------+-------+-------------+|  1 | SIMPLE      | foobar | ref  | idx_name      | idx_name | 92      | const | 19244 | Using where |+----+-------------+--------+------+---------------+----------+---------+-------+-------+-------------+1 row in set (0.00 sec)// key_part1 > const ???为什么采用filesortEXPLAIN SELECT * FROM foobar WHERE first_name > 'Andy' ORDER BY last_name ASC;+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                       |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+|  1 | SIMPLE      | foobar | ALL  | idx_name      | NULL | NULL    | NULL | 99391 | Using where; Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+1 row in set (0.00 sec)// key_part1 < const ???为什么采用filesortEXPLAIN SELECT * FROM foobar WHERE first_name < 'Andy' ORDER BY last_name DESC;+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                       |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+|  1 | SIMPLE      | foobar | ALL  | idx_name      | NULL | NULL    | NULL | 99391 | Using where; Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+1 row in set (0.00 sec)EXPLAIN SELECT * FROM foobar WHERE first_name = 'Andy' AND last_name > 'Ddufresne' ORDER BY last_name;+----+-------------+--------+-------+---------------+----------+---------+------+------+-----------------------+| id | select_type | table  | type  | possible_keys | key      | key_len | ref  | rows | Extra                 |+----+-------------+--------+-------+---------------+----------+---------+------+------+-----------------------+|  1 | SIMPLE      | foobar | range | idx_name      | idx_name | 184     | NULL |    1 | Using index condition |+----+-------------+--------+-------+---------------+----------+---------+------+------+-----------------------+1 row in set (0.00 sec)使用两个不同索引EXPLAIN SELECT * FROM foobar ORDER BY id,first_name LIMIT 10;+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra          |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+|  1 | SIMPLE      | foobar | ALL  | NULL          | NULL | NULL    | NULL | 99391 | Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+1 row in set (0.00 sec)非前缀匹配EXPLAIN SELECT * FROM foobar WHERE last_name='Ddufresne' ORDER BY first_name; +----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                       |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+|  1 | SIMPLE      | foobar | ALL  | NULL          | NULL | NULL    | NULL | 99391 | Using where; Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+1 row in set (0.00 sec)采用不同排序策略EXPLAIN SELECT * FROM foobar ORDER BY first_name DESC, last_name ASC LIMIT 10;+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra          |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+|  1 | SIMPLE      | foobar | ALL  | NULL          | NULL | NULL    | NULL | 99391 | Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+1 row in set (0.00 sec)数据过滤与排序采用不同索引 ???? 貌似部分情况可以使用EXPLAIN SELECT * FROM foobar WHERE first_name='Andy' ORDER BY id;+----+-------------+--------+------+---------------+----------+---------+-------+-------+----------------------------------------------------+| id | select_type | table  | type | possible_keys | key      | key_len | ref   | rows  | Extra                                              |+----+-------------+--------+------+---------------+----------+---------+-------+-------+----------------------------------------------------+|  1 | SIMPLE      | foobar | ref  | idx_name      | idx_name | 92      | const | 19244 | Using index condition; Using where; Using filesort |+----+-------------+--------+------+---------------+----------+---------+-------+-------+----------------------------------------------------+1 row in set (0.00 sec)排序字段不只有字段,可能有函数处理SELECT * FROM t1 ORDER BY ABS(key);SELECT * FROM t1 ORDER BY -key;• The query joins many tables, and the columns in the ORDER BY are not all from the first nonconstant table that is used to retrieve rows. (This is the first table in the EXPLAIN output that does not have a const join type.) • The query has different ORDER BY and GROUP BY expressions. • There is an index on only a prefix of a column named in the ORDER BY clause. In this case, the index cannot be used to fully resolve the sort order. For example, if only the first 10 bytes of a CHAR(20) column are indexed, the index cannot distinguish values past the 10th byte and a filesort will be needed. • The index does not store rows in order. For example, this is true for a HASH index in a MEMORY table. + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-06-12-mysql-process_init.md b/_drafts/2015-06-12-mysql-process_init.md new file mode 100644 index 0000000..94ad787 --- /dev/null +++ b/_drafts/2015-06-12-mysql-process_init.md @@ -0,0 +1,61 @@ +--- +title: MySQL 执行过程 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,执行 +description: +--- + + + + + + + + +关于 SQL 的查询,源码可以直接从 mysql_select() 开始,其中包括其优化结果等信息都是保存在 JOIN 结构体中的,它主要包括如下的三个操作: + +* JOIN::prepare() + + 用于为整个查询做准备工作,包括分配内存、处理别名、通配符的处理、检查表是否可以访问、检查字段、检查非 group 函数、准备 procedure 等工作。 + +* JOIN::optimize() + + 整个查询优化器的核心内容,主要对 join 进行简化、优化 where 条件、优化 having 条件、裁剪分区 partition (如果查询的表是分区表)、优化 count()/min()/max() 聚集函数、统计 join 的代价、搜索最优的 join 顺序、生成执行计划、执行基本的查询、优化多个等式谓词、执行 join 查询、优化 distinct、创建临时表存储临时结果等操作。 + +* JOIN::exec() + + 负责执行优化后的执行计划。 + + +## 源码相关 + +其中与 JOIN 相关的头文件代码在 sql/sql_optimizer.h 中,包括了相关类以及接口的定义。 + +{% highlight cpp %} +class JOIN :public Sql_alloc +{ + bool need_tmp; // 是否需要tmp文件 + bool plan_is_const() const; // 是否为常量,例如返回空或者一行记录 + Next_select_func first_select; // 记录读取首条记录的函数,默认为sub_select() + Next_select_func get_end_select_func(); + + + bool is_optimized() const { return optimized; } + void set_optimized() { optimized= true; } + bool is_executed() const { return executed; } + void set_executed() { executed= true; } + void reset_executed() { executed= false; } +private: + bool optimized; ///< flag to avoid double optimization in EXPLAIN + bool executed; ///< Set by exec(), reset by reset() + + +} +{% endhighlight %} + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-06-15-mysql-optimizer-switch_init.md b/_drafts/2015-06-15-mysql-optimizer-switch_init.md new file mode 100644 index 0000000..acd1029 --- /dev/null +++ b/_drafts/2015-06-15-mysql-optimizer-switch_init.md @@ -0,0 +1,284 @@ +--- +Date: October 19, 2013 +title: MySQL 优化开关 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + + + + +![mysql optimizer]({{ site.url }}/images/databases/mysql/optimizer-logo.png "mysql optimizer"){: .pull-center width="80%" } + +## 简介 + +其中,MySQL 存在一系列的优化器开关,可以通过如下命令查看以及设置。 + +{% highlight text %} +----- 查看优化器选项 +mysql> SHOW VARIABLES LIKE 'optimizer_switch'; + +----- 关闭ICP选项,其它优化器选项不变 +mysql> SET optimizer_switch='index_condition_pushdown=off'; +{% endhighlight %} + + +### 源码相关 + +MySQL 优化器相关的开关,与源码相关的主要涉及 sql/{sql_const.h,sql_class.h,sys_vars.cc} 几个文件,相关内容分别如下。 + +{% highlight c %} +/* @@optimizer_switch flags. These must be in sync with optimizer_switch_typelib */ +#define OPTIMIZER_SWITCH_INDEX_MERGE (1ULL << 0) +#define OPTIMIZER_SWITCH_INDEX_MERGE_UNION (1ULL << 1) +#define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION (1ULL << 2) +#define OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT (1ULL << 3) +#define OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN (1ULL << 4) +#define OPTIMIZER_SWITCH_INDEX_CONDITION_PUSHDOWN (1ULL << 5) +#define OPTIMIZER_SWITCH_MRR (1ULL << 6) +#define OPTIMIZER_SWITCH_MRR_COST_BASED (1ULL << 7) +#define OPTIMIZER_SWITCH_BNL (1ULL << 8) +#define OPTIMIZER_SWITCH_BKA (1ULL << 9) +#define OPTIMIZER_SWITCH_MATERIALIZATION (1ULL << 10) +#define OPTIMIZER_SWITCH_SEMIJOIN (1ULL << 11) +#define OPTIMIZER_SWITCH_LOOSE_SCAN (1ULL << 12) +#define OPTIMIZER_SWITCH_FIRSTMATCH (1ULL << 13) +#define OPTIMIZER_SWITCH_DUPSWEEDOUT (1ULL << 14) +#define OPTIMIZER_SWITCH_SUBQ_MAT_COST_BASED (1ULL << 15) +#define OPTIMIZER_SWITCH_USE_INDEX_EXTENSIONS (1ULL << 16) +#define OPTIMIZER_SWITCH_COND_FANOUT_FILTER (1ULL << 17) +#define OPTIMIZER_SWITCH_DERIVED_MERGE (1ULL << 18) +#define OPTIMIZER_SWITCH_LAST (1ULL << 19) +{% endhighlight %} + + +{% highlight c %} +/** Tells whether the given optimizer_switch flag is on */ +inline bool optimizer_switch_flag(ulonglong flag) const +{ + return (variables.optimizer_switch & flag); +} +{% endhighlight %} + +{% highlight c %} +static const char *optimizer_switch_names[]= +{ + "index_merge", "index_merge_union", "index_merge_sort_union", + "index_merge_intersection", "engine_condition_pushdown", + "index_condition_pushdown" , "mrr", "mrr_cost_based", + "block_nested_loop", "batched_key_access", + "materialization", "semijoin", "loosescan", "firstmatch", "duplicateweedout", + "subquery_materialization_cost_based", + "use_index_extensions", "condition_fanout_filter", "derived_merge", + "default", NullS +}; +static Sys_var_flagset Sys_optimizer_switch( + "optimizer_switch", + "optimizer_switch=option=val[,option=val...], where option is one of " + "{index_merge, index_merge_union, index_merge_sort_union, " + "index_merge_intersection, engine_condition_pushdown, " + "index_condition_pushdown, mrr, mrr_cost_based" + ", materialization, semijoin, loosescan, firstmatch, duplicateweedout," + " subquery_materialization_cost_based" + ", block_nested_loop, batched_key_access, use_index_extensions," + " condition_fanout_filter, derived_merge} and val is one of " + "{on, off, default}", + SESSION_VAR(optimizer_switch), CMD_LINE(REQUIRED_ARG), + optimizer_switch_names, DEFAULT(OPTIMIZER_SWITCH_DEFAULT), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), ON_UPDATE(NULL)); +{% endhighlight %} + +## 优化详解 + +数据仍然参考 [MySQL 基本介绍](/post/mysql-basic.html) 中的第一个示例,为了测试方便,需要添加一个索引。 + +{% highlight text %} +mysql> CREATE INDEX idx_contacter ON customers (contactLastName, contactFirstName); +{% endhighlight %} + +接下来,就挨个看看各种优化选项。 + +### Index Condition Pushdown, ICP + +ICP 是 MySQL 5.6 新增的特性,是一种在存储引擎层使用索引过滤数据的优化方式。 + +{% highlight text %} +----- 查看优化器选项 +mysql> SHOW VARIABLES LIKE 'optimizer_switch'; + +----- 关闭ICP选项,其它优化器选项不变 +mysql> SET optimizer_switch='index_condition_pushdown=off'; + +----- ICP示例 +mysql> EXPLAIN SELECT * FROM customers WHERE contactLastName = 'Young' AND contactFirstName LIKE 'J%'; ++----+-------------+-----------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-----------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+ +| 1 | SIMPLE | customers | NULL | range | idx_contacter | idx_contacter | 104 | NULL | 1 | 100.00 | Using index condition | ++----+-------------+-----------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+ +1 row in set, 1 warning (0.01 sec) +{% endhighlight %} + +在源码 sql/sql_select.cc 文件中,有如下的相关注释,介绍在什么条件下使用 ICP 。 + +{% highlight c %} +/* + We will only attempt to push down an index condition when the + following criteria are true: + 0. The table has a select condition + 1. The storage engine supports ICP. + 2. The index_condition_pushdown switch is on and + the use of ICP is not disabled by the NO_ICP hint. + 3. The query is not a multi-table update or delete statement. The reason + for this requirement is that the same handler will be used + both for doing the select/join and the update. The pushed index + condition might then also be applied by the storage engine + when doing the update part and result in either not finding + the record to update or updating the wrong record. + 4. The JOIN_TAB is not part of a subquery that has guarded conditions + that can be turned on or off during execution of a 'Full scan on NULL + key'. + @see Item_in_optimizer::val_int() + @see subselect_single_select_engine::exec() + @see TABLE_REF::cond_guards + @see setup_join_buffering + 5. The join type is not CONST or SYSTEM. The reason for excluding + these join types, is that these are optimized to only read the + record once from the storage engine and later re-use it. In a + join where a pushed index condition evaluates fields from + tables earlier in the join sequence, the pushed condition would + only be evaluated the first time the record value was needed. + 6. The index is not a clustered index. The performance improvement + of pushing an index condition on a clustered key is much lower + than on a non-clustered key. This restriction should be + re-evaluated when WL#6061 is implemented. + 7. The index on virtual generated columns is not supported for ICP. +*/ +{% endhighlight %} + + + +a 当关闭ICP时,index 仅仅是data access 的一种访问方式,存储引擎通过索引回表获取的数据会传递到MySQL Server 层进行where条件过滤。 +b 当打开ICP时,如果部分where条件能使用索引中的字段,MySQL Server 会把这部分下推到引擎层,可以利用index过滤的where条件在存储引擎层进行数据过滤,而非将所有通过index access的结果传递到MySQL server层进行where过滤. +优化效果:ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数,减少io次数,提高查询语句性能。 + + + +{% highlight text %} +index_read()/general_fetch() + |-row_search_mvcc() + |-row_search_idx_cond_check() + |-innobase_index_cond() + +JOIN::optimize() + |-make_join_readinfo() + |-push_index_cond() 将索引条件下发到存储引擎 +{% endhighlight %} + + + + + +### Multi-Range Read, MRR + +MySQL 5.6 版本,对于二级索引的范围扫描并且需要回表的情况,进行的优化;主要是减少随机 IO,并且将随机 IO 转化为顺序 IO,提高查询效率。 + +MRR 适用于以下两种情况: + +1. range access; +2. ref and eq_ref access, when they are using Batched Key Access 。 + +另外,使用索引的等值 JOIN 查询也可以进行优化。 + +#### 原理 + +详细来说,其原理是,将多个需要回表的二级索引根据主键进行排序,然后一起回表,将原来的回表时进行的随机 IO,转变成顺序 IO。 + +![optimizer switch mrr]({{ site.url }}/images/databases/mysql/optimizer_switch_mrr_1.png "optimizer switch mrr"){: .pull-center } + +如上是没有开启 MRR 的情况下,MySQL 执行查询的伪代码大致如下: + +{% highlight text %} +1. 根据where条件中的二级索引获取二级索引与主键的集合,结果集为result。 + select key_column, pk_column from table where key_column=XXX order by key_column +2. 通过第一步获取的主键来获取对应的值。 + for each pk_column value in result do: + select non_key_column from table where pk_column=val +{% endhighlight %} + +由于 MySQL 存储数据的方式导致二级索引的存储顺序与主键的存储顺序不一致,如果根据二级索引获取的主键来访问表中的数据会导致大量的随机 IO,尤其当不同主键不在同一个 page 里面时,必然导致多次 IO 和随机读。 + +![optimizer switch mrr]({{ site.url }}/images/databases/mysql/optimizer_switch_mrr_2.png "optimizer switch mrr"){: .pull-center } + +上图是在使用 MRR 优化特性的情况下,此时 MySQL 对于基于二级索引的查询策略为: + +{% highlight text %} +1. 根据where条件中的二级索引获取二级索引与主键的集合,结果集为result。 + select key_column, pk_column from table where key_column=XXX order by key_column +2. 将结果集result放在缓存中(read_rnd_buffer_size大小直到buffer满了), + 然后对结果集result按照pk_column排序,得到结果集是result_sort +3. 利用已经排序过的结果集,访问表中的数据,此时是顺序IO + select non_key_column from table where pk_column in (result_sort) +{% endhighlight %} + +MySQL 根据二级索引获取的结果集根据主键排序,将乱序化为有序,在用主键顺序访问基表时,将随机读转化为顺序读,多页数据记录可一次性读入或根据此次的主键范围分次读入,以减少 IO 操作,提高查询效率。 + +#### 配置使用 + +关于该优化选项的查看和配置使用方式如下: + +{% highlight text %} +----- 查看mrr/mrr_cost_based是否开启 +mysql> SHOW VARIABLES LIKE 'optimizer_switch'; + +----- 打开开关 +mysql> SET GLOBAL optimizer_switch ='mrr=on,mrr_cost_based=on'; +{% endhighlight %} + +mrr=on 表示启用 MRR 优化;mrr_cost_based 是否通过 cost base 的方式来启用 MRR;如果选择 mrr=on,mrr_cost_based=off 则表示总是开启 MRR 优化; 参数 read_rnd_buffer_size 用来控制键值缓冲区的大小。 + + + + + + + + + + + + + + +## 参考 + + + + +http://dev.mysql.com/doc/refman/en/mrr-optimization.html + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-06-15-mysql-optimizer_init.md b/_drafts/2015-06-15-mysql-optimizer_init.md new file mode 100644 index 0000000..da5472e --- /dev/null +++ b/_drafts/2015-06-15-mysql-optimizer_init.md @@ -0,0 +1,629 @@ +--- +Date: October 19, 2013 +title: MySQL 优化详解 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + +优化应该是 MySQL 中比较复杂的一部分了。 + + + +![mysql optimizer]({{ site.url }}/images/databases/mysql/optimizer-logo.png "mysql optimizer"){: .pull-center width="80%" } + + +## 执行计划 + +可以通过 explain 查看 MySQL 的执行计划。 + +{% highlight text %} +mysql> [explain|describe|desc] [extended|partitions] select_clause; +{% endhighlight %} + +使用 extended 参数后,会包含了优化器优化后的 select 查询语句,可以通过 show warnings 查看;partitions 是用于分区表的查询语句;示例如下: + +{% highlight text %} +mysql> explain select emp_no from salaries where salary = 90930; ++----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+ +| 1 | SIMPLE | salaries | NULL | ALL | NULL | NULL | NULL | NULL | 2838718 | 10.00 | Using where | ++----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+ +1 row in set, 1 warning (0.00 sec) +{% endhighlight %} + +执行计划包含的信息如下: + +* **id** 识别符,也就是查询序列号;表示查询中执行 select 子句或操作表的顺序,id 相同,执行顺序由上至下,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行。 +* **select_type** 类型,可以为以下任何一种: + 1. SIMPLE: 简单 SELECT,没有使用 UNION 或子查询。 +* **table** 输出的行所引用的表。 +* **type** 联接类型。下面给出各种联接类型,按照从最佳类型到最坏类型进行排序: + 1. ALL: Full Table Scan,遍历全表以找到匹配的行。 + 2. index: Full Index Scan,与 ALL 的区别在于只会遍历索引树,通常比 ALL 快,因为索引文件通常比数据文件小。 + 3. range: 索引的范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于 in、<、>、between 等查询。 + 4. ref: 非唯一性索引扫描,返回匹配某个值的所有行;常见于使用非唯一索引或者唯一索引的非唯一前缀进行的查找。 + 5. eq_ref: 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。 + 6. const, system: 优化后可以转换为一个常量时,如主键查询;其中,system 是 const 类型的特例,当查询的表只有一行的情况下,即为 system 。 +* **possible_keys** 能使用哪个索引在该表中找到行,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。 +* **key** 实际决定使用的索引,如果没有选择索引,则是 NULL 。 +* **key_len** 使用的键长度,为最大可能长度;如果键是 NULL,则长度为 NULL 。 +* **ref** 显示使用哪个列或常数与 key 一起从表中选择行。 +* **rows** 认为它执行查询时必须检查的行数,多行之间的数据相乘可以估算要处理的行数。 +* **filtered** 显示了通过条件过滤出的行数的百分比估计值。 +* **Extra** 该列包含 MySQL 解决查询的详细信息。 + +数据库可以参考 [MySQL 基本介绍](/post/mysql-basic.html) 中的第一个示例数据库,另外,需要添加一个索引。 + +{% highlight text %} +mysql> CREATE INDEX idx_contacter ON customers (contactLastName, contactFirstName); +{% endhighlight %} + +接下来看看一些常见的示例。 + +{% highlight text %} +----- select_type(SIMPLE) +mysql> EXPLAIN SELECT * FROM employees WHERE employeeNumber = 1002; ++----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ +| 1 | SIMPLE | employees | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL | ++----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ +1 row in set, 1 warning (0.00 sec) +{% endhighlight %} + + + + + + + + + + + + + + + + + +###### extra的说明 +Distinct +MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。对于此项没有找到合适的例子,求指点。 + +Not exists +因为b表中的order_id是主键,不可能为NULL,所以mysql在用a表的order_id扫描t_order表,并查找b表的行时,如果在b表发现一个匹配的行就不再继续扫描b了,因为b表中的order_id字段不可能为NULL。这样避免了对b表的多次扫描。 +explain select count(1) from t_order a left join t_order_ext b on a.order_id=b.order_id where b.order_id is null; + +Range checked for each record +这种情况是mysql没有发现好的索引可用,速度比没有索引要快得多。 +explain select * from t_order t, t_order_ext s where s.order_id>=t.order_id and s.order_id<=t.order_id and t.express_type>5; + +--> + +详细内容可以参考 [MySQL Explain](/reference/databases/mysql/mysqlexplain.pdf) 中的介绍。 + + + + + + + + + + +### 源码解析 + +EXPLAIN 相关的源码在 sql/opt_explain.cc 文件中,主要处理流程如下。 + +{% highlight text %} +mysql_parse() + |-mysql_execute_command() + |-execute_sqlcom_select() + |-handle_query() + |-explain_query() +{% endhighlight %} + + + +## 配置参数 + +优化器的搜索空间越小,实际上对应的优化时间越小;同时可能会由于忽略了一些方案导致失去了最佳方案。对于搜索空间,可以通过如下的两个系统变量来控制: + +* optimizer_prune_level [1] + + 用来告诉优化器,可以通过评估每个表被访问记录的数量来忽略某些方案. 经验显示,这种类型的"学习猜想(educated guess)",很少会错失掉最佳方案,并会很明显地减少查询编译次数.这也是为什么MYSQL默认会设置:optimizer_prune_level=1的原因. 当然我们也可以将optimizer_prune_level=0,但需要承担编译查询可能会持续很长时间的危险. + +* optimizer_search_depth [62] + + 用来告诉优化器,对一个不完整方案的最大评估深度是多少.其值越小,那么查询编译的次数(时间)就会越少.如果此值为0,那么优化器会自动决定其值. + + +如果 search_depth = 0 ,那么实际上是由 MySQL 决定搜索深度的,通过 determine_search_depth() 实现,实际上就是 min(number of tables, 7)。另外,需要主要注意的是,表的数量是不包含常量表的。 + +实际上,在 MySQL 中,当超过了 7 个表的 JOIN 之后,优化器所带来的成本就非常高了,如 (7!=5040, 8!=40320, 9!=362 880) + + + + + + + + + + + + +

    Index Merge Optimization 索引合并优化

    +5.0 之前,每条单表查询只能使用一个索引,之后推出的索引合并优化,可以让 MySQL 在查询中对一个表使用多个索引,对它们同时扫描,并且合并结果。需要注意的是,该优化只能用于单表。

    + +对于该类型的优化,可以通过 explain 查看 type 字段为 index_merge,在 Extra 中显示具体的类型,包括了三种:unions、intersections、unions-of-intersections;详细可以参考 Index Merge Optimization 。 +

    +mysql> create index idx_firstname on employees(first_name);
    +mysql> create index idx_lastname on employees(last_name);
    +
    +
    1. +The Index Merge Intersection Access Algorithm
      +不同的索引的 range 查询,并通过 AND 链接。 +
      +mysql> explain select * from employees where first_name = 'Georgi' and last_name = 'Facello';
      +

    2. + +The Index Merge Union Access Algorithm
      +不同的索引的 range 查询,并通过 OR 链接。 +
      +mysql> explain select * from employees where first_name = 'Georgi' or last_name = 'Facello';
      +

    3. + + +The Index Merge Sort-Union Access Algorithm
      +
    + + + +get_key_scans_params() -> check_quick_select() -> sel_arg_range_seq_next() -> is_key_scan_ror() 用于判断是否为 Rowid-Ordered Retrieval, ROR +

    + + + + + + + + + + + + + + + + + +## Profile + +这一参数可以在全局和 session 级别来设置,该参数开启后,后续执行的 SQL 语句都将记录其资源开销,常见的有 IO、上下文切换、CPU、Memory 等,可以通过 help profile 查看帮助。 + +{% highlight text %} +----- 查看profile是否开启 +mysql> show variables like '%profiling%'; ++------------------------+-------+ +| Variable_name | Value | ++------------------------+-------+ +| have_profiling | YES | # 只读变量,是否支持profiling +| profiling | OFF | # 是否开启了语句刨析功能 +| profiling_history_size | 15 | # 保留剖析结果的数目,默认为15[0~100],为0时禁用 ++------------------------+-------+ +3 rows in set (0.00 sec) + +----- 打开会话级的剖析功能 +mysql> set profiling = on; +Query OK, 0 rows affected (0.00 sec) + +----- 关闭会话级的剖析功能 +mysql> set profiling = off; +Query OK, 0 rows affected (0.00 sec) + +----- 查看当前会话保存的profiling记录 +mysql> show profiles; ++----------+-------------+--------------------------------------------------+ +| Query_ID | Duration | Query | ++----------+-------------+--------------------------------------------------+ +| 1 | 11.24294018 | select emp_no from salaries where salary = 90930 | ++----------+-------------+--------------------------------------------------+ +1 rows in set (0.00 sec) + +----- 列出具体消耗时间的详细信息,1 表示query_id +mysql> show profile for query 1; ++----------------------+-----------+ +| Status | Duration | ++----------------------+-----------+ +| starting | 0.000095 | +| checking permissions | 0.000008 | +| Opening tables | 0.000034 | +| After opening tables | 0.000007 | +| System lock | 0.000006 | +| Table lock | 0.000011 | +| init | 0.000020 | +| optimizing | 0.000017 | +| statistics | 0.000023 | +| preparing | 0.000025 | +| executing | 0.000003 | +| Sending data | 11.242564 | +| end | 0.000014 | +| query end | 0.000012 | +| closing tables | 0.000005 | +| Unlocking tables | 0.000017 | +| freeing items | 0.000013 | +| updating status | 0.000059 | +| cleaning up | 0.000009 | ++----------------------+-----------+ +19 rows in set (0.00 sec) + +----- 其它的还支持查看cpu、memory、block io等方式查看,如下为查看CPU +mysql> show profile cpu for query 1; +{% endhighlight %} + + + + + + +## Optimizer Trace + +在 MySQL5.6 中,支持将执行的 SQL 的查询计划树记录下来,该功能可以通过一个会话级的参数 optimizer_trace 进行控制,默认是关闭的,因为这会影响查询的性能。 + +{% highlight text %} +----- 1. 查看相关变量是否打开,如果没有则打开 +mysql> SHOW VARIABLES LIKE 'optimizer_trace%'; +mysql> SET optimizer_trace="enabled=on"; + +----- 2. 执行一些查询,注意:只保留最后一次的结果 +... ... + +mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; # 查询结果,或者如下导入到文件中 +mysql> SELECT TRACE INTO DUMPFILE "xx.trace" FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; +{% endhighlight %} + +注意,上述的 dumpfile 会将该文件 dump 到该库所在的目录下。 + +常见的相关参数如下。 + +* optimizer_trace,开关控制。 + + 有两个字段,"enabled=on,one_line=off",前者表示是否打开 optimizer_trace,后者表示打印的查询计划是以一行显示,还是以 json 树的形式显示,默认为 json 数显示。 + +* optimizer_trace_limit、optimizer_trace_offset,保存以及显示的记录数。 + + 前者是正整数,后者为整数,默认值为 1/-1,也就是只会保存并显示一条记录;注意,重设变量会导致 trace 被清空。 + +* optimizer_trace_max_mem_size,内存使用大小。 + + trace 数据会存储在内存中,通过该参数设置最大的内存使用数;该参数是 session 级别,不应该设置的过大,而且不应该超过 OPTIMIZER_TRACE_MAX_MEM_SIZE,默认是 16K。 + +* optimizer_trace_features,控制打印查询计划树的选项。 + + 当不关心某些查询计划选项时,可以将其关闭掉,只打印关注的,这样可以减小查询计划树的输出。 + + +### 源码解析 + +在源码中,通过 trace_wrapper()、trace_prepare()、trace_steps() 等函数实现。如下是一个很简单的查询的执行计划 select * from customers limit 100, 2; 。 + + +{% highlight text %} +{ + "steps": [ + {"join_preparation": { 在JOIN::prepare()中 + "select#": 1, + "steps": [ + { + "expanded_query": "/* select#1 */..." + } + ] + } + }, + + {"join_optimization": { 在JOIN::optimize()中 + "select#": 1, + "steps": [ + { + "table_dependencies": [ + { + "table": "`customers`", + "row_may_be_null": false, + "map_bit": 0, + "depends_on_map_bits": [ + ] + } + ] + }, + { + "rows_estimation": [ + { + "table": "`customers`", + "table_scan": { + "rows": 122, + "cost": 3 + } + } + ] + }, + { + "considered_execution_plans": [ + { + "plan_prefix": [ + ], + "table": "`customers`", + "best_access_path": { + "considered_access_paths": [ + { + "access_type": "scan", + "rows": 122, + "cost": 27.4, + "chosen": true + } + ] + }, + "cost_for_plan": 27.4, + "rows_for_plan": 122, + "chosen": true + } + ] + }, + { + "attaching_conditions_to_tables": { + "original_condition": null, + "attached_conditions_computation": [ + ], + "attached_conditions_summary": [ + { + "table": "`customers`", + "attached": null + } + ] + } + }, + { + "refine_plan": [ + { + "table": "`customers`", + "access_type": "table_scan" + } + ] + } + ] + } + }, + + { + "join_execution": { + "select#": 1, + "steps": [ + ] + } + } + ] +} +{% endhighlight %} + +可以查看官方的介绍 [Tracing the Optimizer](http://dev.mysql.com/doc/internals/en/optimizer-tracing.html),以及其中的示例。 + + + + + + + + + + + + + + + + +## 参考 + +关于 MySQL 的大部分耗时都消耗在那里了,以及作者是如何通过 HandlerSocket 插件大幅度提高访问性能的,可以参考 [Using MySQL as a NoSQL - A story for exceeding 750,000 qps on a commodity server](http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html),或者直接参考 [本地版本](/reference/mysql/Using MySQL as a NoSQL.mhtml) 。 + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-06-18-mysql-innodb-buffer-pool_init.md b/_drafts/2015-06-18-mysql-innodb-buffer-pool_init.md new file mode 100644 index 0000000..73cb4a7 --- /dev/null +++ b/_drafts/2015-06-18-mysql-innodb-buffer-pool_init.md @@ -0,0 +1,1034 @@ +--- +title: InnoDB Buffer Pool +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,innodb,buffer pool +description: 无论哪种数据库,缓存都是提高数据库性能的关键技术,通过缓存可以在读写方面大幅提高数据库的整体性能,InnoDB 中就是通过 Buffer Pool 实现的。在此,简单介绍下 Buffer Pool 中的实现。 +--- + +无论哪种数据库,缓存都是提高数据库性能的关键技术,通过缓存可以在读写方面大幅提高数据库的整体性能,InnoDB 中就是通过 Buffer Pool 实现的。 + +在此,简单介绍下 Buffer Pool 中的实现。 + + + +## 简介 + +我们知道 InnoDB 使用 buffer pool 来缓存从磁盘读取到内存的数据页,BP 通常由数个内存块加上一组控制结构体对象组成,BP 每块内存通过 mmap 分配内存,而这些大片的内存块又按照 16KB 划分为多个 frame,用于存储数据页。 + +多数情况下 BP 是以 16KB 来存储数据页,但有一种例外:使用压缩表时,需要在内存中同时存储压缩页和解压页,对于压缩页,会使用 Binary buddy allocator 算法来分配内存空间。 + +例如我们读入一个 8KB 的压缩页,就从 BP 中取一个 16KB 的 block,取其中 8KB,剩下的 8KB 放到空闲链表上;如果紧跟着另外一个 4KB 的压缩页读入内存,就可以从这 8KB 中分裂 4KB,同时将剩下的 4KB 放到空闲链表上。 + +BP 通过 Least Recently Used, LRU 进行组织,当需要插入新数据时,将最近最少使用的 block 去除,并将新 block 插入到列表的中间,也就是 ```midpoint insertion strategy``` 。 + +头部的子链表,表示最近读取过;尾部的子链表表示最近很少读取;也就是说经常使用的 blocks 会放在头部,而需要去除的会放在尾部。 + +### 算法详解 + +单纯的 LRU 算法有一个缺点:如果有某一个查询做了一次全表扫描,如备份、DDL 等,都可能会导致整个 LRU 链表中的数据块都被替换了,甚至很多热点数据也会被替换,而这些新进的数据块可能在这一次查询之后就再也不会被读到了;此时也就是说 BP 被污染了。 + +即使采用上述的 midpoint 方法,也就是说当数据块需要从数据文件中读取时 (也包括了预读),首先会放到 old sublist 的头部 +(midpoint)。然后,如果有对这个数据块的访问,那么就将这个数据块放到 new sublist 的首部。 + +一般来说,一个数据块被取出后,立刻会有读取,也就很快会被放到 new sublist 的头部。一种糟糕的情况是,如果是 mysqldump 访问全部数据块,也就会导致所有的数据块被放到 new sublist;这样 BP 也会被全部污染。 + +为了解决这个问题,可以设置 ```innodb_old_blocks_time(ms)``` 参数,当页被插入到 midpoint 后,必须要在 old sublist 的头部停留超过上述的时间后,才有可能被转移到 new sublist。 + +参数 ```innodb_old_blocks_time``` 可以动态设置,在执行一些全表扫描时,可以将上述参数设置为比较大的值,操作完成之后再恢复为 0 。 + +{% highlight text %} +SET GLOBAL innodb_old_blocks_time = 1000; +... perform queries that scan tables ... +SET GLOBAL innodb_old_blocks_time = 0; +{% endhighlight %} + +还有一种情况时,希望数据加载的缓存中。例如,在进行性能测试时,通常会执行一次全表扫描,将磁盘中的数据加载到内存中,那么,此时就应该将上述的参数设置为 0 。 + +### 多实例配置 + + + +当服务器的内存比较大时,如果多个线程读取 BP 数据,会导致瓶颈;此时,可以将 BP 分为几个实例 (instance),从而提高并发,减少资源冲突。每个实例管理自己的 free lists、flush lists、LRUs、互斥锁 (mutex) 以及其它与 BP 相关的结构。 + +可以通过 innodb_buffer_pool_instances 参数配置实例个数,默认为 1,最大可以配置为 64;只有当 innodb_buffer_pool_size 的值大于 1G 时才会生效,而且每个实例均分 BP 缓存。 + +通过多个 BP 可以减小竞争,每个页(page)将通过一个 hash 函数随机分配到 BP 中。 + +### 参数、监控 + +InnoDB 主索引是聚簇索引,索引与数据共用表空间,也就是说,对于 InnoDB 而言,数据就是索引,索引就是数据。也就是说,Innodb 和 MyISAM 缓存的最大区别就在于前者不仅缓存索引,同时还会缓存数据。 + +#### 参数设置 + +通过 innodb_buffer_pool_size 参数来设置 InnoDB 缓存池大小,也就是缓存用户表及索引数据的最主要缓存空间,甚至其它管理数据 (例如元数据信息,行级锁信息);对于专用的数据库服务器上通常为物理内存的 70% ~ 80%;5.7.5 版本后可以动态调整,调整时按 Block 进行。 + +因此,对 Innodb 整体性能影响也最大。可通过如下命令查看当前 buffer pool 相关的参数。 + +{% highlight text %} +----- 当前缓存的大小 +mysql> SHOW VARIABLES LIKE 'innodb%pool%'; ++-----------------------------------------+----------------+ +| Variable_name | Value | ++-----------------------------------------+----------------+ +| innodb_buffer_pool_chunk_size | 134217728 | 动态调整时Block的大小 +| innodb_buffer_pool_dump_at_shutdown | ON | +| innodb_buffer_pool_dump_now | OFF | +| innodb_buffer_pool_dump_pct | 25 | +| innodb_buffer_pool_evict | | +| innodb_buffer_pool_filename | ib_buffer_pool | +| innodb_buffer_pool_instances | 1 | BP缓存的实例数,只有当BP大于1G时才会生效 +| innodb_buffer_pool_load_abort | OFF | +| innodb_buffer_pool_load_at_startup | ON | +| innodb_buffer_pool_load_now | OFF | +| innodb_buffer_pool_size | 134217728 | 缓存大小,包括了数据和索引 +| innodb_disable_resize_buffer_pool_debug | ON | ++-----------------------------------------+----------------+ +12 rows in set (0.00 sec) + +----- 当前缓存页的大小 +mysql> SHOW VARIABLES LIKE 'innodb_page_size'; ++------------------+-------+ +| Variable_name | Value | ++------------------+-------+ +| innodb_page_size | 16384 | ++------------------+-------+ +1 row in set (0.00 sec) +{% endhighlight %} + +如上所述,通常建议设置为系统内存的 70%~80%,不过也必须要对具体项目具体分析,比如最大链接数、是否有 MyISAM 引擎等。可以按照如下的值进行分配,仅做参考: + +{% highlight text %} +操作系统: + 800M~1G +线程独享: + 2GB = 线程数(500) * (1MB + 1MB + 1MB + 512KB + 512KB) + sort_buffer_size(1MB) + join_buffer_size(1MB) + read_buffer_size (1MB) + read_rnd_buffer_size(512KB) + thread_statck(512KB) +MyISAM Key Cache: + 1.5GB +Innodb Buffer Pool: + 8GB(系统内存) - 800MB - 2GB - 1.5GB = 3.7GB +{% endhighlight %} + +修改配置文件 /etc/my.cnf,并添加如下字段,然后重启 mysqld 即可。 + +{% highlight text %} +[mysqld] +innodb_buffer_pool_size = 3G +{% endhighlight %} + +除了上述的参数之外,与之相关的还有如下配置参数: + +* innodb_old_blocks_pct
    old-sublist 的分割点,可以为 5~95,默认为 37 也就是 3/8; +* innodb_old_blocks_time (ms)
    指定页必须在 old-sublist 中待多长时间之后,才有可能被移动到新列表中; + + + + + + + + +#### 监控指标 + +可以通过如下命令查看一些 InnoDB Buffer Pool 监控值。 + +{% highlight text %} +mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_%'; ++-----------------------------------+-------+ +| Variable_name | Value | ++-----------------------------------+-------+ +| Innodb_buffer_pool_pages_data | 70 | 已经使用的缓存页数 +| Innodb_buffer_pool_pages_dirty | 0 | 脏页数 +| Innodb_buffer_pool_pages_flushed | 0 | 刷新页数 +| Innodb_buffer_pool_pages_free | 1978 | 尚未使用的缓存页数 +| Innodb_buffer_pool_pages_misc | 0 | +| Innodb_buffer_pool_pages_total | 2048 | 缓存页面总数,innodb_page_size(16k) +| Innodb_buffer_pool_read_ahead_rnd | 1 | +| Innodb_buffer_pool_read_ahead_seq | 0 | +| Innodb_buffer_pool_read_requests | 329 | 读请求次数 +| Innodb_buffer_pool_reads | 19 | 从磁盘中读取数据的次数 +| Innodb_buffer_pool_wait_free | 0 | +| Innodb_buffer_pool_write_requests | 0 | ++-----------------------------------+-------+ +12 rows in set (0.01 sec) +{% endhighlight %} + +如果 Innodb_buffer_pool_pages_free 偏大的话,证明有很多缓存没有被利用到,这时可以考虑减小缓存;相反 Innodb_buffer_pool_pages_data 过大就考虑增大缓存。 + +对于缓存命中率可以通过如下方式计算。 + +{% highlight text %} +----- 缓存命中率,通常不低于99% +(Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100% +(329 - 19) / 329 * 100% = 94.22%。 +{% endhighlight %} + + + +另外,也可以通过 ```SHOW ENGINE INNODB STATUS\G``` 命令查看。 + +{% highlight text %} +mysql> SHOW ENGINE INNODB STATUS\G +---------------------- +BUFFER POOL AND MEMORY +---------------------- +Total large memory allocated 140836864 +Dictionary memory allocated 538303 +Buffer pool size 8192 +Free buffers 7810 +Database pages 382 +Old database pages 0 +Modified db pages 0 +Pending reads 0 +Pending writes: LRU 0, flush list 0, single page 0 +Pages made young 0, not young 0 +0.00 youngs/s, 0.00 non-youngs/s +Pages read 336, created 46, written 128 +0.00 reads/s, 0.00 creates/s, 0.00 writes/s +No buffer pool page gets since the last printout +Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s +LRU len: 382, unzip_LRU len: 0 +I/O sum[0]:cur[0], unzip sum[0]:cur[0] +{% endhighlight %} + +可以查看对应的源码。 + +{% highlight c %} +ibool srv_printf_innodb_monitor(FILE* file, ibool nowait, + ulint* trx_start_pos, ulint* trx_end) +{ + // ... ... + fputs("----------------------\n" + "BUFFER POOL AND MEMORY\n" + "----------------------\n", file); + fprintf(file, + "Total large memory allocated " ULINTPF "\n" + "Dictionary memory allocated " ULINTPF "\n", + os_total_large_mem_allocated, + dict_sys->size); + + buf_print_io(file); + // ... ... +} + +void buf_print_io( FILE* file) +{ + ulint i; + buf_pool_info_t* pool_info; + buf_pool_info_t* pool_info_total; + + /* If srv_buf_pool_instances is greater than 1, allocate + one extra buf_pool_info_t, the last one stores + aggregated/total values from all pools */ + if (srv_buf_pool_instances > 1) { + pool_info = (buf_pool_info_t*) ut_zalloc_nokey(( + srv_buf_pool_instances + 1) * sizeof *pool_info); + + pool_info_total = &pool_info[srv_buf_pool_instances]; + } else { + ut_a(srv_buf_pool_instances == 1); + + pool_info_total = pool_info = + static_cast( + ut_zalloc_nokey(sizeof *pool_info)); + } + + for (i = 0; i < srv_buf_pool_instances; i++) { + buf_pool_t* buf_pool; + + buf_pool = buf_pool_from_array(i); + + /* Fetch individual buffer pool info and calculate + aggregated stats along the way */ + buf_stats_get_pool_info(buf_pool, i, pool_info); + + /* If we have more than one buffer pool, store + the aggregated stats */ + if (srv_buf_pool_instances > 1) { + buf_stats_aggregate_pool_info(pool_info_total, + &pool_info[i]); + } + } + + /* Print the aggreate buffer pool info */ + buf_print_io_instance(pool_info_total, file); + + /* If there are more than one buffer pool, print each individual pool + info */ + if (srv_buf_pool_instances > 1) { + fputs("----------------------\n" + "INDIVIDUAL BUFFER POOL INFO\n" + "----------------------\n", file); + + for (i = 0; i < srv_buf_pool_instances; i++) { + fprintf(file, "---BUFFER POOL " ULINTPF "\n", i); + buf_print_io_instance(&pool_info[i], file); + } + } + + ut_free(pool_info); +} +{% endhighlight %} + +上述方式,只能看到所有 BP 的总体统计值,如果要查看每个实例的值,可以通过如下命令查看。 + +{% highlight text %} +mysql> SELECT * FROM information_schema.innodb_buffer_pool_stats\G +{% endhighlight %} + + +## 磁盘预热 + +如果一台高负荷的机器重启后,内存中大量的热数据被清空;重启后,会重新从磁盘加载到 Buffer Pool 中,此时,性能就会变得很差,连接数就会很高。 + +在 MySQL5.6 后,添加了一个新特性避免的这种问题的出现,当正常关闭时会将 BP 中的数据写入到磁盘;对于 kill -9 或者宕机仍无法使用。 + +{% highlight text %} +----- 关闭时把热数据dump到本地磁盘。 +innodb_buffer_pool_dump_at_shutdown = 1 + +innodb_buffer_pool_dump_now = 1 + +解释:采用手工方式把热数据dump到本地磁盘。 + +innodb_buffer_pool_load_at_startup = 1 + +解释:在启动时把热数据加载到内存。 + +innodb_buffer_pool_load_now = 1 + +解释:采用手工方式把热数据加载到内存。 + +{% endhighlight %} + +在关闭 MySQL 时,会把内存中的热数据保存在磁盘里 ib_buffer_pool 文件中,位于数据目录下。 + + + + +## 源码解析 + +MySQL 5.6 内存管理在 mem0pool.c 中实现,其文件的开头注释中都有说明,粗略的可以分成四部分,包含 9 大块。 + +{% highlight text %} +/* We would like to use also the buffer frames to allocate memory. This +would be desirable, because then the memory consumption of the database +would be fixed, and we might even lock the buffer pool to the main memory. +The problem here is that the buffer management routines can themselves call +memory allocation, while the buffer pool mutex is reserved. + +The main components of the memory consumption are: + +1. buffer pool, +2. parsed and optimized SQL statements, +3. data dictionary cache, +4. log buffer, +5. locks for each transaction, +6. hash table for the adaptive index, +7. state and buffers for each SQL query currently being executed, +8. session for each user, and +9. stack for each OS thread. + +Items 1 and 2 are managed by an LRU algorithm. Items 5 and 6 can potentially +consume very much memory. Items 7 and 8 should consume quite little memory, +and the OS should take care of item 9, which too should consume little memory. + +A solution to the memory management: + +1. the buffer pool size is set separately; +2. log buffer size is set separately; +3. the common pool size for all the other entries, except 8, is set separately. + +Problems: we may waste memory if the common pool is set too big. Another +problem is the locks, which may take very much space in big transactions. +Then the shared pool size should be set very big. We can allow locks to take +space from the buffer pool, but the SQL optimizer is then unaware of the +usable size of the buffer pool. We could also combine the objects in the +common pool and the buffers in the buffer pool into a single LRU list and +manage it uniformly, but this approach does not take into account the parsing +and other costs unique to SQL statements. + +The locks for a transaction can be seen as a part of the state of the +transaction. Hence, they should be stored in the common pool. We still +have the problem of a very big update transaction, for example, which +will set very many x-locks on rows, and the locks will consume a lot +of memory, say, half of the buffer pool size. + +Another problem is what to do if we are not able to malloc a requested +block of memory from the common pool. Then we can request memory from +the operating system. If it does not help, a system error results. + +Because 5 and 6 may potentially consume very much memory, we let them grow +into the buffer pool. We may let the locks of a transaction take frames +from the buffer pool, when the corresponding memory heap block has grown to +the size of a buffer frame. Similarly for the hash node cells of the locks, +and for the adaptive index. Thus, for each individual transaction, its locks +can occupy at most about the size of the buffer frame of memory in the common +pool, and after that its locks will grow into the buffer pool. */ +{% endhighlight %} + +### BP 管理概述 + +为了管理 buffer pool,每个 buffer pool instance 使用如下几个链表来管理: + +* LRU 链表包含所有读入内存的数据页; +* Flush_list 包含被修改过的脏页; +* unzip_LRU 包含所有解压页; +* Free list 上存放当前空闲的 block 。 + +另外为了避免查询数据页时扫描 LRU,还为每个 buffer pool instance 维护了一个 page hash,可以通过 space id 和 page no 直接找到对应的 page 。 + +一般情况下,当需要读入一个 Page 时,首先会根据 space id 和 page no 找到对应的 buffer pool instance;然后查询 page hash,如果 page hash 中没有,则表示需要从磁盘读取。 + +在读盘前,我们需要为即将读入内存的数据页分配一个空闲的 block,当 free list 上存在空闲的 block 时,可以直接从 free list 上摘取;如果没有,就需要从 unzip_lru 或者 lru 上淘汰 page 。 + +在淘汰页时会遵循一定原则,可参考 ```buf_LRU_get_free_block()->buf_LRU_scan_and_free_block()```: + +1. 首先尝试从 unzip_lru 上驱逐解压页,可以参考 ```buf_LRU_free_from_unzip_LRU_list()```; +2. 如果没有,再尝试从 lru 链表上淘汰 Page,可以参考 ```buf_LRU_free_from_common_LRU_list()``` ; +3. 如果还是无法从 lru 上获取到空闲 block,用户线程就会参与刷脏,尝试做一次 SINGLE PAGE FLUSH,单独从 lru 上刷掉一个脏页,然后再重试。 + +BP 中的页被修改后,不是立刻写入磁盘,而是由后台线程定时写入,和大多数数据库系统一样,脏页的写盘遵循日志先行 WAL 原则,因此在每个 block 上都记录了一个最近被修改时的 lsn,写数据页时需要确保当前写入日志文件的 redo 不低于这个 lsn。 + +然而基于 WAL 原则的刷脏策略可能带来一个问题:当数据库的写入负载过高时,产生 redo log 的速度极快,redo log 可能很快到达同步 checkpoint 点,这时候需要进行刷脏来推进 lsn 。 + +由于这种行为是由用户线程在检查到 redo log 空间不够时触发,大量用户线程将可能陷入到这段低效的逻辑中,产生一个明显的性能拐点。 + +### 数据结构 + + +{% highlight cpp %} + +class buf_page_t { + unsigned old:1; // 是否在LRU_list的old部分 +}; + +struct buf_block_t { +}; + +struct buf_buddy_free_t { +}; + + +struct buf_pool_t{ + /** @name General fields */ + /* @{ */ + ib_mutex_t mutex; /*!< Buffer pool mutex of this + instance */ + ib_mutex_t zip_mutex; /*!< Zip mutex of this buffer + pool instance, protects compressed + only pages (of type buf_page_t, not + buf_block_t */ + ulint instance_no; /*!< Array index of this buffer + pool instance */ + ulint old_pool_size; /*!< Old pool size in bytes */ + ulint curr_pool_size; /*!< Current pool size in bytes */ + ulint LRU_old_ratio; /*!< Reserve this much of the buffer + pool for "old" blocks */ +#ifdef UNIV_DEBUG + ulint buddy_n_frames; /*!< Number of frames allocated from + the buffer pool to the buddy system */ +#endif +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + ulint mutex_exit_forbidden; /*!< Forbid release mutex */ +#endif + ulint n_chunks; /*!< number of buffer pool chunks */ + buf_chunk_t* chunks; /*!< buffer pool chunks */ + ulint curr_size; /*!< current pool size in pages */ + hash_table_t* page_hash; /*!< hash table of buf_page_t or + buf_block_t file pages, + buf_page_in_file() == TRUE, + indexed by (space_id, offset). + page_hash is protected by an + array of mutexes. + Changes in page_hash are protected + by buf_pool->mutex and the relevant + page_hash mutex. Lookups can happen + while holding the buf_pool->mutex or + the relevant page_hash mutex. */ + hash_table_t* zip_hash; /*!< hash table of buf_block_t blocks + whose frames are allocated to the + zip buddy system, + indexed by block->frame */ + ulint n_pend_reads; /*!< number of pending read + operations */ + ulint n_pend_unzip; /*!< number of pending decompressions */ + + time_t last_printout_time; // when buf_print_io was last time called + buf_buddy_stat_t buddy_stat[BUF_BUDDY_SIZES_MAX + 1]; + /*!< Statistics of buddy system, + indexed by block size */ + buf_pool_stat_t stat; /*!< current statistics */ + buf_pool_stat_t old_stat; /*!< old statistics */ + + /* @} */ + + /** @name Page flushing algorithm fields */ + + /* @{ */ + + ib_mutex_t flush_list_mutex;/*!< mutex protecting the + flush list access. This mutex + protects flush_list, flush_rbt + and bpage::list pointers when + the bpage is on flush_list. It + also protects writes to + bpage::oldest_modification and + flush_list_hp */ + const buf_page_t* flush_list_hp;/*!< "hazard pointer" + used during scan of flush_list + while doing flush list batch. + Protected by flush_list_mutex */ + UT_LIST_BASE_NODE_T(buf_page_t) flush_list; + /*!< base node of the modified block + list */ + ibool init_flush[BUF_FLUSH_N_TYPES]; + /*!< this is TRUE when a flush of the + given type is being initialized */ + ulint n_flush[BUF_FLUSH_N_TYPES]; + /*!< this is the number of pending + writes in the given flush type */ + os_event_t no_flush[BUF_FLUSH_N_TYPES]; + /*!< this is in the set state + when there is no flush batch + of the given type running */ + ib_rbt_t* flush_rbt; /*!< a red-black tree is used + exclusively during recovery to + speed up insertions in the + flush_list. This tree contains + blocks in order of + oldest_modification LSN and is + kept in sync with the + flush_list. + Each member of the tree MUST + also be on the flush_list. + This tree is relevant only in + recovery and is set to NULL + once the recovery is over. + Protected by flush_list_mutex */ + ulint freed_page_clock;/*!< a sequence number used + to count the number of buffer + blocks removed from the end of + the LRU list; NOTE that this + counter may wrap around at 4 + billion! A thread is allowed + to read this for heuristic + purposes without holding any + mutex or latch */ + ibool try_LRU_scan; /*!< Set to FALSE when an LRU + scan for free block fails. This + flag is used to avoid repeated + scans of LRU list when we know + that there is no free block + available in the scan depth for + eviction. Set to TRUE whenever + we flush a batch from the + buffer pool. Protected by the + buf_pool->mutex */ + /* @} */ + + /** @name LRU replacement algorithm fields */ + /* @{ */ + + UT_LIST_BASE_NODE_T(buf_page_t) free; + /*!< base node of the free + block list */ + + UT_LIST_BASE_NODE_T(buf_page_t) withdraw; + /*!< base node of the withdraw + block list. It is only used during + shrinking buffer pool size, not to + reuse the blocks will be removed */ + + ulint withdraw_target;/*!< target length of withdraw + block list, when withdrawing */ + + /** "hazard pointer" used during scan of LRU while doing + LRU list batch. Protected by buf_pool::mutex */ + LRUHp lru_hp; + + /** Iterator used to scan the LRU list when searching for + replacable victim. Protected by buf_pool::mutex. */ + LRUItr lru_scan_itr; + + /** Iterator used to scan the LRU list when searching for + single page flushing victim. Protected by buf_pool::mutex. */ + LRUItr single_scan_itr; + + UT_LIST_BASE_NODE_T(buf_page_t) LRU; + /*!< base node of the LRU list */ + + buf_page_t* LRU_old; /*!< pointer to the about + LRU_old_ratio/BUF_LRU_OLD_RATIO_DIV + oldest blocks in the LRU list; + NULL if LRU length less than + BUF_LRU_OLD_MIN_LEN; + NOTE: when LRU_old != NULL, its length + should always equal LRU_old_len */ + ulint LRU_old_len; /*!< length of the LRU list from + the block to which LRU_old points + onward, including that block; + see buf0lru.cc for the restrictions + on this value; 0 if LRU_old == NULL; + NOTE: LRU_old_len must be adjusted + whenever LRU_old shrinks or grows! */ + + UT_LIST_BASE_NODE_T(buf_block_t) unzip_LRU; + /*!< base node of the + unzip_LRU list */ + + /* @} */ + /** @name Buddy allocator fields + The buddy allocator is used for allocating compressed page + frames and buf_page_t descriptors of blocks that exist + in the buffer pool only in compressed form. */ + /* @{ */ +#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG + UT_LIST_BASE_NODE_T(buf_page_t) zip_clean; + /*!< unmodified compressed pages */ +#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */ + + UT_LIST_BASE_NODE_T(buf_buddy_free_t) zip_free[BUF_BUDDY_SIZES_MAX]; + /*!< buddy free lists */ + + buf_page_t* watch; ?????TODODO:到底是干吗的 + /*!< Sentinel records for buffer + pool watches. Protected by + buf_pool->mutex. */ +}; + + + + +struct buf_chunk_t{ // include/buf0buf.ic + ulint mem_size; /*!< allocated size of the chunk */ + ulint size; /*!< size of frames[] and blocks[] */ + void* mem; /*!< pointer to the memory area which + was allocated for the frames */ + buf_block_t* blocks; /*!< array of buffer control blocks */ +}; +{% endhighlight %} + + + + +## Buffer Pool + + + + + +### 预读策略 + +预读其实是基于这样的判断,在读取磁盘数据时,接下来的请求,有很大可能要读取周围的数据;为此,InnoDB 会异步读取该页周围的数据,从而减小 IO 。 + +InnoDB 提供了顺序和随机两种策略。 + +#### 顺序预读 + + + + + + +### 源码解析 + +Buffer Pool 的初始化入口在 ```innobase_start_or_create_for_mysql()``` 函数中,调用流程如下。 + +{% highlight text %} +buf_pool_init() # 初始化buffer pool主函数入口 + |-buf_pool_init_instance() # 1.1 每个buf-pool大小为srv_buf_pool_size/instance,通过该函数初始化各个实例 + | |-UT_LIST_INIT(buf_pool->free) # 2.1 初始化buf pool的free list + | | + | |-buf_chunk_init() # 初始化buffer pool中的chunk + | | |-ut_2pow_round() # 2.2 计算每个buf pool实际所需空间,空间必须按照page_size对齐 + | | |-ut_2pow_round() # 必须为每个page分配一个内存block结构,用于管理内存page + | | |-os_mem_alloc_large() # 2.3 为buf pool分配大块内存,对于不同系统配置,调用不同函数 + | | | |-shmget() shmat() shmct() # 若用了大页(Huge Page),则使用这些方法分配空间 + | | | |-VirtualAlloc() # Win平台使用该函数分配空间 + | | | |-ut_malloc_low() # 若未使用MMAP,则调用该函数分配空间 + | | | |-mmap() # 若使用MMAP,则调用mmap函数分配空间 + | | |-ut_align() # 2.4 将分配出来的mem空间,按page size对齐,作为page的起点 + | | |-... ... # 2.5 将mem划分为两部分:前部分作为block结构空间;后部分作为page空间 + | | | # page空间每一个page的起始位置,必须按照page size对齐 + | | | # block结构起始位置为mem空间的0号位置 + | | | # page空间的起始位置,为预留足够的block结构之后的第一个frame位置 + | | |-buf_block_init() # 2.6 为每个page指定一个block头结构,并初始化各种mutex与lock + | | |-UT_LIST_ADD_LAST() # 2.7 将page加入buf pool的free list链表,等待分配 + | |-hash_create() # 2.8 创建相应的hash表,若page对应于文件中的一个页,则在page hash表中存在 + | # 便于page在内存中的快速定位 + | + |-buf_pool_set_sizes() # 1.2 设置大小参数 + |-buf_LRU_old_ratio_update() # 1.3 设置buf_pool.LRU中oldList部分所占的比率,默认为3/8 + |-btr_search_sys_create() +{% endhighlight %} + +其中比较重要的参数是 innodb_old_blocks_pct,该参数运行时可以调整,初始值为 3/8 。 + +若设置不同的 old ratio,则涉及到调整 buf_pool->LRU_old 指向的位置,LRU_old 指向的是 LRU list 中位于 old ratio 处的 block 位置,也就是说 old ration 调整,LRU_old 需要相应的做出调整。 + +### 页面管理 + +在 MySQL 5.5 之后,InnoDB 支持多 Buffer Pool Instance,内存中有多个 buffer pool 管理,如果此时指定一个 page,需要确定属于哪个 buffer pool 的。 + +其入参是给定一个 page 的 tablespace id 与 pageno,然后可以定位到对应的管理 buffer pool 。 + +{% highlight c %} +class page_id_t { +private: + ib_uint32_t m_space; // Tablespace id + ib_uint32_t m_page_no; // Page number + mutable ulint m_fold; // 通过m_space+m_page_no计算的hash值 +}; + +buf_pool_t* buf_pool_get(const page_id_t& page_id) +{ + // 每次读取64Pages,BUF_READ_AHEAD_AREA,这64页统一在一个BP实例中管理 + ulint ignored_page_no = page_id.page_no() >> 6; + page_id_t id(page_id.space(), ignored_page_no); // 创建一个实例,无它 + ulint i = id.fold() % srv_buf_pool_instances; // fold是计算一个hash值 + return(&buf_pool_ptr[i]); +} +{% endhighlight %} + +因为目前 InnoDB 的一个 read ahead 是 64 个 page,因此右移 6 位能够保证一个 read ahead area 中的页,都属于同一个 buffer pool 管理。 + + +### Buffer Pool LRU List + +如上所述,InnoDB Buffer Pool 会通过 LRU 算法作为管理页面的替换策略,将 LRU List 划分为两部分:LRU_young 与 LRU_old,参数的占比可以通过 ```innodb_old_blocks_pct``` 参数设置,默认是 37,也就是 LRU_old 为链表长度的 3/8 。 + +#### old-page 移动到 new-page + +在页面读取时 (get/read ahead),会先将页链入到 LRU_old 的头部;当页面第一次访问时 (read/write),从 LRU_old 链表移动到 LRU_young 的头部,也就是整个 LRU 链表头。 + + + +会通过如下函数进行判断是否要写入到 LRU_head 中。 + +{% highlight text %} +buf_page_make_young_if_needed() + |-buf_page_peek_if_too_old() + |-buf_page_make_young() + |-buf_LRU_make_block_young() + |-buf_LRU_remove_block() + |-buf_LRU_add_block_low() +{% endhighlight %} + +其中判断是否需要写入 LRU_head 的处理如下。 + +{% highlight c %} +ibool buf_page_peek_if_too_old(const buf_page_t* bpage) +{ + buf_pool_t* buf_pool = buf_pool_from_bpage(bpage); + + if (buf_pool->freed_page_clock == 0) { + /* 当前BP还没有淘汰任何页,说明在内存中还有足够的页,那么就不需要更新统计值, + 也不需要在LRU链表中移动;通常在warm-up阶段。 */ + return(FALSE); + } else if (buf_LRU_old_threshold_ms && bpage->old) { + unsigned access_time = buf_page_is_accessed(bpage); // 直接返回bpage->access_time + /* 若当前页已经被访问过,那么会计算距上次访问的时间间隔是否超过了用户设置的 + innodb_old_blocks_time参数,如果未超过,则不会将页移动到LRU_head。 + PS. 这里有个整数溢出的情况,通常是50天,也就是如果页面据上次访问超过了50天, + 紧接着在此访问时,可能会判断没有超过阈值,从而没有移动到LRU_head。 */ + if (access_time > 0 + && ((ib_uint32_t) (ut_time_ms() - access_time)) + >= buf_LRU_old_threshold_ms) { + return(TRUE); + } + buf_pool->stat.n_pages_not_made_young++; + return(FALSE); + } else { + /* 此时,已经有页面淘汰过,也就是内存中的页已经不够用;但没有设置old_blocks_time参数, + 或者该页第一次被访问,那么就通过如下算法判断是否需要移动到LRU_head。*/ + return(!buf_page_peek_if_young(bpage)); + } +} + +ibool buf_page_peek_if_young(const buf_page_t* bpage) +{ + buf_pool_t* buf_pool = buf_pool_from_bpage(bpage); + + /* 判断当前页是否足够新(Most Recently Update, MRU),而不需要移动 + buf_pool->freed_page_clock: 该BP实例一共淘汰了多少页 + bpage->freed_page_clock : 该页最近一次移动到LRU_head时,对应BP中该参数的取值 + buf_pool->curr_size : 该BP当前使用的页面数量 + BUF_LRU_OLD_RATIO_DIV : 宏定义为1024 + buf_pool->LRU_old_ratio : old LRU的占比,默认3/8时,该值为378 + 公式意义解释: + 从该页最近一次移动到BP的LRU_head以来,BP在此期间淘汰的页数量,超过了LRU_yound列表长度 + 的1/4,那么说明该页已经不够年轻,该页需要移动到LRU_head;否则该页属于MRU,不需要移动。 + */ + return((buf_pool->freed_page_clock & ((1UL << 31) - 1)) + < ((ulint) bpage->freed_page_clock + + (buf_pool->curr_size + * (BUF_LRU_OLD_RATIO_DIV - buf_pool->LRU_old_ratio) + / (BUF_LRU_OLD_RATIO_DIV * 4)))); +} +{% endhighlight %} + +#### new-page 移动到 old-page + +正常来说,每次访问页面时,都有可能会将该页移动到 LRU_head 中 (make young),随着越来越多的页加入 LRU_head,那么原来在 LRU list 中的页也就慢慢退化到 LRU list 的 LRU_old list 部分。 + +实际上,在 ```class buf_page_t``` 类中有个成员变量 old 用来标示是否在 LRU 的 old list 中;该变量的设置基本是通过 ```buf_page_set_old()``` 函数进行设置。 + +##### 第一次读取 + +在第一读入到 BP 时,会加入到 LRU 链表的 LRU_old 头部,调用堆栈如下。 + +{% highlight text %} +buf_page_init_for_read() + |-buf_LRU_add_block(bpage, TRUE) 参数TRUE表示添加到old-list + |-buf_LRU_add_block_low() +{% endhighlight %} + +当入参为 TRUE 时,会将页放到 LRU 的 old list 中,否则放在链表头部;当然,如果 LRU 链表比较小,那么会直接忽略该参数,将页放在头部。 + +{% highlight text %} +void buf_LRU_add_block_low(buf_page_t* bpage, ibool old) +{ + buf_pool_t* buf_pool = buf_pool_from_bpage(bpage); + + if (!old || (UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN)) { + + UT_LIST_ADD_FIRST(buf_pool->LRU, bpage); + + bpage->freed_page_clock = buf_pool->freed_page_clock; + } else { +#ifdef UNIV_LRU_DEBUG + /* buf_pool->LRU_old must be the first item in the LRU list + whose "old" flag is set. */ + ut_a(buf_pool->LRU_old->old); + ut_a(!UT_LIST_GET_PREV(LRU, buf_pool->LRU_old) + || !UT_LIST_GET_PREV(LRU, buf_pool->LRU_old)->old); + ut_a(!UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old) + || UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old)->old); +#endif /* UNIV_LRU_DEBUG */ + UT_LIST_INSERT_AFTER(buf_pool->LRU, buf_pool->LRU_old, + bpage); + + buf_pool->LRU_old_len++; + } + + ut_d(bpage->in_LRU_list = TRUE); + + incr_LRU_size_in_bytes(bpage, buf_pool); + + if (UT_LIST_GET_LEN(buf_pool->LRU) > BUF_LRU_OLD_MIN_LEN) { + + ut_ad(buf_pool->LRU_old); + + /* Adjust the length of the old block list if necessary */ + + buf_page_set_old(bpage, old); + buf_LRU_old_adjust_len(buf_pool); + + } else if (UT_LIST_GET_LEN(buf_pool->LRU) == BUF_LRU_OLD_MIN_LEN) { + + /* The LRU list is now long enough for LRU_old to become + defined: init it */ + + buf_LRU_old_init(buf_pool); + } else { + buf_page_set_old(bpage, buf_pool->LRU_old != NULL); + } + + /* If this is a zipped block with decompressed frame as well + then put it on the unzip_LRU list */ + if (buf_page_belongs_to_unzip_LRU(bpage)) { + buf_unzip_LRU_add_block((buf_block_t*) bpage, old); + } +} +{% endhighlight %} + + +### 其它 + +#### innodb_old_blocks_pct + +该变量会通过 buf_LRU_old_ratio_update_instance() 函数进行更新,默认 37,范围是 5~95 。 + +{% highlight c %} +#define BUF_LRU_OLD_RATIO_DIV 1024 +#define BUF_LRU_OLD_RATIO_MAX BUF_LRU_OLD_RATIO_DIV +#define BUF_LRU_OLD_RATIO_MIN 51 + +uint buf_LRU_old_ratio_update_instance(buf_pool_t* buf_pool, uint old_pct, ibool adjust) +{ + uint ratio; + + ratio = old_pct * BUF_LRU_OLD_RATIO_DIV / 100; + if (ratio < BUF_LRU_OLD_RATIO_MIN) { + ratio = BUF_LRU_OLD_RATIO_MIN; + } else if (ratio > BUF_LRU_OLD_RATIO_MAX) { + ratio = BUF_LRU_OLD_RATIO_MAX; + } + + if (adjust) { + buf_pool_mutex_enter(buf_pool); + + if (ratio != buf_pool->LRU_old_ratio) { + buf_pool->LRU_old_ratio = ratio; + + if (UT_LIST_GET_LEN(buf_pool->LRU) + >= BUF_LRU_OLD_MIN_LEN) { + + buf_LRU_old_adjust_len(buf_pool); + } + } + + buf_pool_mutex_exit(buf_pool); + } else { + buf_pool->LRU_old_ratio = ratio; + } + /* the reverse of + ratio = old_pct * BUF_LRU_OLD_RATIO_DIV / 100 */ + return((uint) (ratio * 100 / (double) BUF_LRU_OLD_RATIO_DIV + 0.5)); +} +{% endhighlight %} + +如上,在计算时,实际上会乘以 BUF_LRU_OLD_RATIO_DIV ,然后再除以 100,也就意味着在内部使用时会大值乘于 10 。 + + +### 读取 + +接下来看看如何读取一个页,其中实际分配缓存是在 buf_LRU_get_free_block() 函数中。这一函数是在用户线程中调用,而且只会从 free_list 中获取空闲页,即使是从 LRU_list 上获取的,也需要先将 LRU_list 中的页保存在 free_list 中。 + +尝试从 BP 中获取一个 block,大多数情况下 free_list 上是有足够的空闲页,因此可以直接分配,分配完之后,直接从 free_list 上删除。 + +如果 free_list 上没有空闲页了,那么尝试从 LRU_list 上分配,一般会经历如下循环。 + +{% highlight text %} +* iteration 0: + * get a block from free list, success:done + * if buf_pool->try_LRU_scan is set + * scan LRU up to srv_LRU_scan_depth to find a clean block + * the above will put the block on free list + * success:retry the free list + * flush one dirty page from tail of LRU to disk + * the above will put the block on free list + * success: retry the free list +* iteration 1: + * same as iteration 0 except: + * scan whole LRU list + * scan LRU list even if buf_pool->try_LRU_scan is not set +* iteration > 1: + * same as iteration 1 but sleep 10ms +@return the free control block, in state BUF_BLOCK_READY_FOR_USE */ +{% endhighlight %} + + + +{% highlight text %} +buf_page_get_gen() 获取数据库中的页 + |-buf_pool_get() 所在buffer pool实例 + |-buf_page_hash_lock_get() 获取锁 + |-rw_lock_s_lock() 加锁 + |-buf_page_hash_get_low() 尝试从bp中获取页 + |-buf_pool_watch_is_sentinel() 干吗的??? + |-buf_read_page() + | |-buf_read_page_low() + | |-buf_page_init_for_read() 初始化bp + | |-buf_LRU_get_free_block() 如果没有压缩,则直接获取空闲页 + | |-buf_LRU_get_free_only() 尝试从free_list获取,有则直接返回即可 + | |-buf_LRU_scan_and_free_block() + | |-buf_LRU_free_from_unzip_LRU_list() + | |-buf_LRU_free_from_common_LRU_list() + | |-buf_LRU_add_block() + | | + | |-buf_buddy_alloc() 压缩页,使用buddy系统 + | + |-buf_read_ahead_random() 如果需要做随机预读 + + + |-fil_io() + |-buf_block_get_state() 根据页的类型,判断是否需要进一步处理,如ZIP + +buf_read_ahead_linear() +{% endhighlight %} + +## 参考 + +关于 LRU list 被划分为 new 与 old 两部分的原因及意义,可参考 [InnoDB Buffer Pool](http://dev.mysql.com/doc/refman/en/innodb-buffer-pool.html) 。 + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-06-20-mysql-innodb-insert-buffer_init.md b/_drafts/2015-06-20-mysql-innodb-insert-buffer_init.md new file mode 100644 index 0000000..05eb38a --- /dev/null +++ b/_drafts/2015-06-20-mysql-innodb-insert-buffer_init.md @@ -0,0 +1,14 @@ +--- +title: InnoDB Insert Buffer +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-06-26-python-datetime_init.md b/_drafts/2015-06-26-python-datetime_init.md new file mode 100644 index 0000000..db83582 --- /dev/null +++ b/_drafts/2015-06-26-python-datetime_init.md @@ -0,0 +1,347 @@ +--- +title: Python 时间操作 +layout: post +comments: true +category: [program, python] +language: chinese +--- + + + + +Python 与时间处理相关的提供了 datetime、time、calendar 三个模块,而且还有三方模块 pytz 可以使用;另外,datetime 模块中又存在 datetime、time 类,不要与相应的模块混淆。 + +{% highlight text %} +datetime.date 理想化的日期对象,有年月日三个属性 +datetime.time 理想化的时间对象,不考虑闰秒,有时分秒微秒和时区五个属性 +datetime.datetime 上述两个对象的组合 +datetime.timedelta 可用于上述三个对象的差值 +datetime.tzinfo 时区信息 + +time 各种时间操作转换的方法 +calendar 提供日历相关的方法 +pytz 使用Olson TZ Database解决时区、夏令时等相关的计算问题 +{% endhighlight %} + + +简单来说,在 Pyton 中,与时间相关的时间类型主要有如下的四种。 + +{% highlight python %} +import time, datetime + +#----- 1. time string 用于打印输出的字符串 +time.ctime() # 'Fri June 26 21:02:55 2015' + +#----- 2. datetime tuple 包含了年月日-时分秒微妙-时区信息 +datetime.datetime.now() # datetime.datetime(2012, 12, 17, 21, 3, 44, 139715) + +#----- 3. time tuple 也就是time.struct_time对象类型 +datetime.datetime.now().timetuple() # time.struct_time(tm_year=2017, tm_mon=1, tm_mday=2, + # tm_hour=10, tm_min=10, tm_sec=48, tm_wday=0, tm_yday=2, tm_isdst=-1) + +#----- 4. timestamp 时间戳类型,从1970-01-01 00:00:00 GMT以来的秒数 +time.time() # 1355749338.05917 +{% endhighlight %} + + + + +![datetime transfrom]({{ site.url }}/images/python/datetime-transform.jpg "datetime transfrom"){: .pull-center } + +## 时间处理 + +很多与日期、时间的 api 都在 datetime 模块内,其中常见的操作如下。 + +{% highlight python %} +import datetime + +#----- 1. 日期输出格式化 datetime=>string +now = datetime.datetime.now() +now.strftime('%Y-%m-%d %H:%M:%S') # '2015-04-07 19:11:21' +now.strftime('%a, %d %b %Y %H:%M:%S %Z') # Sun, 01 Jan 2017 02:10:33 GMT + +#----- 2. 日期输出格式化 string=>datetime +t_str = '2015-04-07 19:11:21' +d = datetime.datetime.strptime(t_str, '%Y-%m-%d %H:%M:%S') + +#----- 3. 两个日期相差多少天 +d1 = datetime.datetime.strptime('2015-06-15 18:11:27', '%Y-%m-%d %H:%M:%S') +d2 = datetime.datetime.strptime('2015-06-02 18:11:27', '%Y-%m-%d %H:%M:%S') +print delta, delta,days + +#----- 4. 今天的n天后的日期 +now = datetime.datetime.now() +delta = datetime.timedelta(days=3) +print (now + delta).strftime('%Y-%m-%d %H:%M:%S') +{% endhighlight %} + +其中,时间格式化的参数内容与 man date 相同,可以直接参考。 + + + + + + +## xxx + + +由于国家和地区可以自己选择时区以及是否使用夏令时,所以pytz模块在有需要的情况下得更新自己的时区以及夏令时相关的信息。比如当前pytz版本的OLSON_VERSON = ‘2013g’, 就是包括了Morocco可以使用夏令时。 +如何正确为你所用 + +不是题外话的题外话,客户端必须正确收集用户的timezone信息。比较常见的一个错误是,保存用户所在时区的偏移值。比如对于中国的时区,保存了+8。这里其实丢失了用户所在的地区(同样的时间偏移,可能对应多个国家或者地区)。而且如果用户所在时区是有夏令时的话,在每年开始和结束夏令时的时候,这个偏移值都是要发生变化的。 + + +{% highlight text %} +>>> import pytz +>>> pytz.all_timezones # 查看所有的timezone +[...,'Asia/Shanghai', ...] # 选择上海的时区,也就是东八区 +>>> pytz.timezone('Asia/Shanghai') # 构建tzinfo对象 + +{% endhighlight %} + + +我们开始要把timezone加入时间的转换里面了。 + +首先,timestamp和datetime的转换。timestamp,一个数字,表示从UTC时间1970/01/01开始的秒数。 + + + +{% highlight text %} +>>> import datetime +>>> datetime.datetime.fromtimestamp(0, pytz.timezone('UTC')) # 含有时区信息 +datetime.datetime(1970, 1, 1, 0, 0, tzinfo=) +>>> tz = pytz.timezone('Asia/Shanghai') # 上海则早8小时 +>>> datetime.fromtimestamp(0, tz) +datetime.datetime(1970, 1, 1, 8, 0, tzinfo=) +{% endhighlight %} + +如上,timestamp 是和 UTC 绑定的,不同的时区会有不同的时间偏移。 + +给定一个timestamp,构建datetime的时候无论传入的是什么时区,对应出来的结果都是同一个时间。 但是python里面这里有个坑。 + +>>> ts = 1408071830 +>>> dt = datetime.fromtimestamp(ts, tz) +datetime.datetime(2014, 8, 15, 11, 3, 50, tzinfo=) +>>> time.mktime(dt.timetuple()) +1408100630.0 +>>> dt.timetuple() +time.struct_time(tm_year=2014, tm_mon=8, tm_mday=15, tm_hour=11, tm_min=3, tm_sec=50, tm_wday=4, tm_yday=227, tm_isdst=0) +>>> dt.astimezone(pytz.utc) +datetime.datetime(2014, 8, 15, 3, 3, 50, tzinfo=) +>>> time.mktime(dt.astimezone(pytz.utc).timetuple()) +1408071830.0 + +time模块的mktime方法支持从timetuple取得timestamp,datetime对象可以直接转换成timetuple。这时候直接使用time.mktime(dt.timetuple())看起来就是很自然的获取timestamp方法。但是我们注意到timetuple方法是直接把当前时间的年月日时分秒直接取出来的。所以这个转换过程在timetuple这个方法这一步丢了时区信息。根据timestamp的定义,正确的方法是把datetime对象利用asttimezone显式转换成UTC时间。 + +第二,datetime和date以及time的关系 datetime模块同时提供了datetime对象,time对象,date对象。他们之间的关系可以从如下代码简单看出来。 + +>>> d = datetime.date(2014, 8, 20) +>>> t = datetime.time(11, 30) +>>> dt = datetime.datetime.combine(d, t) +datetime.datetime(2014, 8, 20, 11, 30) +>>> dt.date() +datetime.date(2014, 8, 20) +>>> dt.time() +datetime.time(11, 30) + +>>> dt = datetime.datetime.fromtimestamp(1405938446, pytz.timezone('UTC')) +datetime.datetime(2014, 7, 21, 10, 27, 26, tzinfo=) +>>> dt.date() +datetime.date(2014, 7, 21) +>>> dt.time() +datetime.time(10, 27, 26) +>>> dt.timetz() +datetime.time(10, 27, 26, tzinfo=) + +>>> datetime.datetime.combine(dt.date(), dt.time()) +datetime.datetime(2014, 7, 21, 10, 27, 26) +>>> datetime.datetime.combine(dt.date(), dt.timetz()) +datetime.datetime(2014, 7, 21, 10, 27, 26, tzinfo=) + +简单说就是,datetime可以取得date和time对象,datetime和time对象可以带timezone信息。date和time对象可以使用datetime.datetime.combine合并获得datetime对象。 + +第三,日期的加减 datetime,date对象都可以使用timedelta来进行。 + 直接看代码 + +>>> d1 = datetime.datetime(2014, 5, 20) +>>> d2 = d1+datetime.timedelta(days=1, hours=2) +>>> d1 +datetime.datetime(2014, 5, 20, 0, 0) +>>> d2 +datetime.datetime(2014, 5, 21, 2, 0) +>>> x = d2 - d1 +>>> x +datetime.timedelta(1, 7200) +>>> x.seconds +7200 +>>> x.days +1 + + 第四,如何对datetime对象正确设置timezone信息 + +先看代码。 + +>>> ddt1 = datetime.datetime(2014, 8, 20, 10, 0, 0, 0, pytz.timezone('Asia/Shanghai')) +>>> ddt1 +datetime.datetime(2014, 8, 20, 10, 0, tzinfo=) +>>> ddt2 + datetime.datetime(2014, 8, 20, 11, 0) + +>>> ddt1.astimezone(pytz.utc) +datetime.datetime(2014, 8, 20, 1, 54, tzinfo=) +>>> ddt2.astimezone(pytz.utc) +ValueError: astimezone() cannot be applied to a naive datetime + +>>> tz = timezone('Asia/Shanghai') +>>> tz.localize(ddt1) +ValueError: Not naive datetime (tzinfo is already set) +>>> tz.localize(ddt2) +datetime.datetime(2014, 8, 20, 11, 0, tzinfo=) + +这里抛出来的ValueError,引入了一个naive datetime的概念。简单说naive datetime就是不知道时区信息的datetime对象。没有timezone信息的datetime理论上讲不能定位到具体的时间点。所以对于设定了timezone的datetime对象,可以使用astimezone方法将timezone设定为另一个。对于不包含timezone的datetime对象,使用timezone.localize方法设定timezone。 + +但是,这里有没有发现一个问题?我们明明设定的是11点整的,使用astimezone之后跑出来个54分是想怎样? + +我们注意到,datetime直接传入timezone对象构建出来的带timezone的datetime对象和使用locallize方法构建出来的datetime对象,在打印出来的时候tzinfo显示有所不同,一个是LMT+8:06,一个是CST+8:00,不用说了,54分就搁这来的吧。LMT学名Local Mean Time,用于比较平均日出时间的。有兴趣的可以自己看看Shanghai和Urumqi的LMT时间。CST是China Standard Time,不用解释了。根据pytz的文档, + +Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones. +It is safe for timezones without daylight saving transitions though, such as UTC: +The preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to be read by humans. +... +You can take shortcuts when dealing with the UTC side of timezone conversions. normalize() and localize() are not really necessary when there are no daylight saving time transitions to deal with. + +我们按照这个说法再试试看,如下,这回pytz.timezone('Asia/Shanghai’)没有再玩幺蛾子了。 + +>>> x = datetime.datetime(2014, 8, 20, 10, 0, 0, 0, pytz.utc) +>>> x +datetime.datetime(2014, 8, 20, 10, 0, tzinfo=) +>>> x.astimezone(pytz.timezone('Asia/Shanghai')) +datetime.datetime(2014, 8, 20, 18, 0, tzinfo=) + +所以最保险的方法是使用locallize方法构造带时区的时间。 + +顺带说下,里面提到了normalize是用来校正计算的时间跨越DST切换的时候出错的情况,还是参见文档,关键部分摘录如下: + +This library differs from the documented Python API for tzinfo implementations; if you want to create local wallclock times you need to use the localize() method documented in this document. In addition, if you perform date arithmetic on local times that cross DST boundaries, the result may be in an incorrect timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get 2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT). A normalize() method is provided to correct this. Unfortunately these issues cannot be resolved without modifying the Python datetime implementation (see PEP-431). + + 回到最初的问题,我程序需要给用户两天后的21点发送通知,这个时间怎么计算? + +>>> import pytz +>>> import time +>>> import datetime +>>> tz = pytz.timezone('Asia/Shanghai') +>>> user_ts = int(time.time()) +>>> d1 = datetime.datetime.fromtimestamp(user_ts) +>>> d1x = tz.localize(d1) +>>> d1x +datetime.datetime(2015, 5, 26, 1, 43, 41, tzinfo=) + +>>> d2 = d1x + datetime.timedelta(days=2) +>>> d2 +datetime.datetime(2015, 5, 28, 1, 43, 41, tzinfo=) +>>> d2.replace(hour=21, minute=0) +>>> d2 +datetime.datetime(2015, 5, 28, 21, 0, 41, tzinfo=) + +基本步骤为,根据时间戳和时区信息构建正确的时间d1x,使用timedelta进行对时间进行加减操作,使用replace方法替换小时等信息。 + +总结,基本上时间相关的这些方法,大部分你都可以直接按照自己的需要封装到一个独立的utility模块,然后就不需要再去管它了。你要做的是,至少有一个人先正确地管一下。 + + + + + + +http://tech.glowing.com/cn/dealing-with-timezone-in-python/ + +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2015-07-12-javascript-dom_init.md b/_drafts/2015-07-12-javascript-dom_init.md new file mode 100644 index 0000000..63651f1 --- /dev/null +++ b/_drafts/2015-07-12-javascript-dom_init.md @@ -0,0 +1,561 @@ +--- +Date: October 19, 2013 +title: Javascript DOM 操作 +layout: post +comments: true +language: chinese +category: [misc] +--- + + + + +# 简介 + +通过 JavaScript,添加、移除、改变或重排页面上的项目,甚至可以重构整个 HTML 文档。如果要改变某个元素,就需要有一个对 HTML 文档中所有元素进行访问的入口,这个入口都是通过 DOM 操作的。 + +根据 DOM,HTML 文档中的每个成分都是一个节点:A) 整个文档是一个文档节点;B) 每个 HTML 标签是一个元素节点;C) 包含在 HTML 元素中的文本是文本节点;D) 每一个 HTML 属性是一个属性节点;E) 注释属于注释节点。 + +各个节点之间彼此都有等级关系,HTML 文档中的所有节点组成了一个文档树或节点树。 + +![DOM tree]({{ site.url }}/images/webserver/dom-tree.jpg){: .pull-center width="500"} + + +# Javascript 操作 DOM + +## 获取节点元素 + +### getElementById() + +根据 ID 获取元素节点 。 + +{% highlight html %} + +
    + 我是第一个P
    + 我是第二个P +
    + + + +{% endhighlight %} + + +### document.getElementsByName() + +根据 name 获取元素节点。 + +{% highlight html %} + + + + + + +{% endhighlight %} + + +### document.getElementsByTagName() + +根据 HTML 标签名获取元素节点,注意 getElementsXXX() 的选择器返回的是一个 NodeList 对象,能根据索引号选择其中 1 个,可以遍历输出。 + +{% highlight html %} + +
    + 我是第一个SPAN
    + 我是第二个SPAN +
    + + + +{% endhighlight %} + +### document.getElementsByClassName() + +根据 class 获取元素节点。 + +{% highlight html %} + +
    + 我是第一个CLASS
    + 我是第二个CLASS +
    + + + +{% endhighlight %} + + +### document.querySelector()/querySelectorAll() + +Javascript 中的 CSS 选择器,前者会根据 CSS 选择器的规则,返回第一个匹配到的元素;后者会返回所有匹配到的元素。 + +{% highlight html %} + +
    + 我是第一个SLECTOR
    + 我是第二个SLECTOR +
    + + + +{% endhighlight %} + + +### 文档结构和遍历(节点树) + +{% highlight text %} +----- 作为节点树的文档,如下6个方法连元素节点也算一个节点 + 1 parentNode 获取该节点的父节点 + 2 childNodes 获取该节点的子节点数组 + 3 firstChild 获取该节点的第一个子节点 + 4 lastChild 获取该节点的最后一个子节点 + 5 nextSibling 获取该节点的下一个兄弟元素 + 6 previoursSibling 获取该节点的上一个兄弟元素 + 7 nodeType 节点的类型,9代表Document节点,1代表Element节点,3代表Text节点,8代表Comment节点,11代表DocumentFragment节点 + 8 nodeVlue Text节点或Comment节点的文本内容 + 9 nodeName 元素的标签名(如P,SPAN,#text(文本节点),DIV),以大写形式表示 +{% endhighlight %} + +测试如下: + +{% highlight html %} + +
    + 我是第一个SLECTOR
    + 我是第二个SLECTOR +
    + + + +{% endhighlight %} + +文档、元素、属性以及 HTML 或 XML 文档的其他方面拥有不同的节点类型,存在 12 种不同的节点类型。 + +{% highlight html %} + +
    + 文本1 + 我是第一个SLECTOR + 文本2 + 我是第二个SLECTOR + 文本3 +
    + + + +{% endhighlight %} + + + + +### 文档结构和遍历(元素树) + +此 5 个方法文本节点不算进去。 + +{% highlight html %} + 1 firstElementChild 第一个子元素节点 + 2 lastElementChild 最后一个子元素节点 + 3 nextElementSibling 下一个兄弟元素节点 + 4 previousElementSibling 前一个兄弟元素节点 + 5 childElementCount 子元素节点个数量 +{% endhighlight %} + +示例如下: + +{% highlight html %} + +
    +

    我是第一个P

    +

    我是第二个P

    +

    我是第三个P

    +
    + + + +{% endhighlight %} + + + +## 操作 HTML 属性 + + +### 属性的读取 + +此处要注意的是,某些 HTML 属性名称在 javascript 之中是保留字,因此会有些许不同,如 class、lable 中的 for 在 javascript 中变为 htmlFor、className。 + +{% highlight html %} + +
    + + 我是一张图片 +

    我是第一个P

    +
    + +{% endhighlight %} + + +### 属性的设置 + +此处同样要注意的是保留字,如下实现的效果是,当点击图片时变小。 + +{% highlight html %} + +
    + 我是一张图片 +
    +{% endhighlight %} + +### 非标准HTML属性 + +分别使用 getAttribute()、setAttribute(),这两个方法是不必理会 javascript 保留字的,HTML 属性是什么就怎么写。 + +{% highlight html %} + +
    + 我是一张图片 +
    +{% endhighlight %} + + + +### Attr节点的属性 + +attributes 属性,对于非 Element 对象返回 NULL,Element 一般返回 Attr 对象。Attr 对象是一个特殊的 Node,通过 name 与 value 获取属性名称与值。 + + +{% highlight html %} + +
    + 我是一张图片 +
    +{% endhighlight %} + + + +## 元素的内容 + +对于 innerText、textContent 的区别,当文本为空时,innerText 是 "",而 textContent 是 undefined 。 + +{% highlight html %} + +
    +

    我是第一个CONTEXT

    +

    我是第个CONTEXT

    +
    + +{% endhighlight %} + +## 创建、插入节点 + +### document.createTextNode() + +创建一个文本节点。 + +{% highlight html %} + +
    +

    我是第一个CONTEXT

    +

    我是第二个CONTEXT

    +
    + +{% endhighlight %} + +完成后 HTML 变为: + +{% highlight html %} +
    +

    我是第一个P

    +

    我是第二个P

    + <p>我是一个javascript新建的节点</p> +
    +{% endhighlight %} + +### document.createElement() + +创建一个元素节点。 + +{% highlight html %} + +
    +

    我是第一个P

    +

    我是第二个P

    +
    + +{% endhighlight %} + +执行之后 HTML 代码变为: + +{% highlight html %} +
    +

    我是第一个P

    +

    我是第二个P

    +

    新建一个P节点

    +
    +{% endhighlight %} + +### 插入节点 + +appendChild()、insertBefore(),其中前者将一个节点插入到调用节点的最后面;后者接受两个参数,第一个为待插入的节点,第二个指明在哪个节点前面,如果不传入第二个参数,则跟 appendChild() 一样,放在最后。 + +{% highlight html %} + +
    +

    我是第一个P

    +

    我是第二个P

    +
    + +{% endhighlight %} + +执行之后HTML代码为: + +{% highlight html %} +
    +

    insertBefore插入的节点

    +

    我是第一个P

    +

    我是第二个P

    +

    appendChild插入的节点

    +
    +{% endhighlight %} + + + +## 删除节点 + +removeChild(); 由父元素调用,删除一个子节点。注意是直接父元素调用,删除直接子元素才有效,删除孙子元素就没有效果了。 + +{% highlight html %} + +
    +

    我是第一个P

    +

    我是第二个P

    +
    + +{% endhighlight %} + +执行之后代码变为: + +{% highlight html %} +
    +

    我是第一个P

    +
    +{% endhighlight %} + +## 替换节点 + +replaceChild(); 删除一个子节点,并用一个新节点代替它,第一个参数为新建的节点,第二个节点为被替换的节点 + +{% highlight html %} + +
    +

    我是第一个P

    +

    我是第二个P

    +
    + +{% endhighlight %} + +执行完成后 HTML 代码变为: + +{% highlight html %} +
    +

    我是第一个P

    + 我是一个新建的span +
    +{% endhighlight %} + + +## 操作元素CSS + +通过元素的 style 属性可以随意读取和设置元素的 CSS 样式,例子: + +{% highlight html %} + +
    我是一个DIV
    + +{% endhighlight %} + + + + + + + + + +# 参考 + +如上的示例可以直接参考 [javascript-dom](/reference/webserver/javascript-dom.html) 。 + +另外可以参考一个不错的调试工具 [10分钟学会前端调试利器——FireBug](http://www.imooc.com/article/3012),或者 [本地文档](/reference/javascript/10分钟学会前端调试利器.mht) 。 + + + + +{% highlight html %} +{% endhighlight %} diff --git a/_drafts/2015-07-13-mysql-partition_init.md b/_drafts/2015-07-13-mysql-partition_init.md new file mode 100644 index 0000000..0060430 --- /dev/null +++ b/_drafts/2015-07-13-mysql-partition_init.md @@ -0,0 +1,429 @@ +--- +title: MySQL 分区表 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,plugin,插件 +description: 在 MySQL 中,为了提高其灵活性,很多的功能都是通过插件来实现的,常见的比如 semi-sync、存储引擎、登陆认证等等。因为 MySQL 是 C/C++ 实现的,对于插件来说实际为动态链接库,保存在 plugin_dir 变量对应的目录下。在此介绍一下 MySQL 的插件实现。 +--- + + + + + += 水平分区(根据列属性按行分)= +举个简单例子:一个包含十年发票记录的表可以被分区为十个不同的分区,每个分区包含的是其中一年的记录。 + +=== 水平分区的几种模式:=== +* Hash(哈希) – 这中模式允许DBA通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区,。例如DBA可以建立一个对表主键进行分区的表。 +* Key(键值) – 上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。 +* List(预定义列表) – 这种模式允许系统通过DBA定义的列表的值所对应的行数据进行分割。例如:DBA建立了一个横跨三个分区的表,分别根据2004年2005年和2006年值所对应的数据。 +* Composite(复合模式) - 很神秘吧,哈哈,其实是以上模式的组合使用而已,就不解释了。举例:在初始化已经进行了Range范围分区的表上,我们可以对其中一个分区再进行hash哈希分区。 += 垂直分区(按列分)= +举个简单例子:一个包含了大text和BLOB列的表,这些text和BLOB列又不经常被访问,这时候就要把这些不经常使用的text和BLOB了划分到另一个分区,在保证它们数据相关性的同时还能提高访问速度。 + + + + +{% highlight sql %} +CREATE TABLE foobar_partition ( + c1 INT DEFAULT NULL, + c2 VARCHAR(30) DEFAULT NULL, + c3 DATE DEFAULT NULL +) ENGINE=InnoDB PARTITION BY RANGE (year(c3)) ( + PARTITION p0 VALUES LESS THAN (1995), + PARTITION p1 VALUES LESS THAN (1996) , + PARTITION p2 VALUES LESS THAN (1997) , + PARTITION p3 VALUES LESS THAN (1998) , + PARTITION p4 VALUES LESS THAN (1999) , + PARTITION p5 VALUES LESS THAN (2000) , + PARTITION p6 VALUES LESS THAN (2001) , + PARTITION p7 VALUES LESS THAN (2002) , + PARTITION p8 VALUES LESS THAN (2003) , + PARTITION p9 VALUES LESS THAN (2004) , + PARTITION p10 VALUES LESS THAN (2010), + PARTITION p11 VALUES LESS THAN MAXVALUE +); +CREATE TABLE foobar_normal ( + c1 INT DEFAULT NULL, + c2 VARCHAR(30) DEFAULT NULL, + c3 DATE DEFAULT NULL +) ENGINE=InnoDB; + +TRUNCATE TABLE load_partition; +DELIMITER EOF +drop procedure if exists `test`.`load_partition` EOF +CREATE PROCEDURE load_partition(IN number INT(11)) + BEGIN + DECLARE i INT DEFAULT 1; + -- such as 1-200,200-400,.... + WHILE i <= number DO + IF mod(i, 20)=1 THEN + SET @sqltext=concat("(", i, ", '", concat('testing partitions ', i), "', '", + adddate('1995-01-01',(rand(i)*36520) mod 3652), "')"); + ELSEIF mod(i, 20)=0 THEN + SET @sqltext=concat(@sqltext, ",(", i, ", '", concat('testing partitions ', i), "', '", + adddate('1995-01-01',(rand(i)*36520) mod 3652), "')"); + SET @sqltext=concat("INSERT INTO foobar_partition VALUES", @sqltext); + -- SELECT @sqltext; + SELECT i AS count; + PREPARE stmt FROM @sqltext; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + SET @sqltext=''; + ELSE + SET @sqltext=concat(@sqltext, ",(", i, ", '", concat('testing partitions ', i), "', '", + adddate('1995-01-01',(rand(i)*36520) mod 3652), "')"); + END IF; + SET i = i + 1; + END WHILE; + -- process when number is not be moded by 2000, such as 201,402,1520,... + IF @sqltext<>'' THEN + SET @sqltext=concat("INSERT INTO foobar_partition VALUES", @sqltext); + -- SELECT @sqltext; + PREPARE stmt FROM @sqltext; + EXECUTE stmt; + DEALLOCATE PREPARE stmt; + SET @sqltext=''; + END IF; + END +EOF +DELIMITER ; +CALL load_partition(80); +{% endhighlight %} + +MySQL 中,可以将分区设置为独立的数据、索引存放目录,同时,这些目录所在的物理磁盘分区可能也都是完全独立的,从而可以提高磁盘 IO 吞吐量;需要注意的是 MERGE, CSV, FEDERATED 存储引擎不支持分区。 + +### RANGE/RANGE COLUMNS + +可以将数据划分不同的区间,这些区间要连续且不能重叠,使用 ```VALUES LESS THAN``` 操作符定义分区;例如,将一个表通过年份划分成三个分区,80 年代 (1980's) 的数据,90 年代 (1990's) 的数据以及任何在 2000 年 (包括2000年) 后的数据。 + +RANGE 分区主要是基于整数的分区,对于非整形的字段需要利用表达式将其转换成整形。 + +{% highlight text %} +CREATE TABLE users (  +  uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  +  name VARCHAR(30) NOT NULL DEFAULT '',  +  email VARCHAR(30) NOT NULL DEFAULT ''  +) PARTITION BY RANGE (uid) ( +  PARTITION p0 VALUES LESS THAN (3000000)  +  DATA DIRECTORY = '/data0/data'  +  INDEX DIRECTORY = '/data1/idx',  + +  PARTITION p1 VALUES LESS THAN (6000000)  +  DATA DIRECTORY = '/data2/data'  +  INDEX DIRECTORY = '/data3/idx',  +  +  PARTITION p2 VALUES LESS THAN (9000000)  +  DATA DIRECTORY = '/data4/data'  +  INDEX DIRECTORY = '/data5/idx',  +  +  PARTITION p3 VALUES LESS THAN MAXVALUE  必须 +  DATA DIRECTORY = '/data6/data'   +  INDEX DIRECTORY = '/data7/idx'  +); +{% endhighlight %} + + + +MySQL 5.5 改进 range 分区,提供 range columns 分区支持非整数分区。 + +COLUMNS分区:可以无需通过表达式进行转换直接对非整形字段进行分区,同时COLUMNS分区还支持多个字段组合分区,只有RANGELIST存在COLUMNS分区,COLUMNS是RANGE和LIST分区的升级 +RANGE COLUMNS是RANGE分区的一种特殊类型,它与RANGE分区的区别如下: +1. RANGE COLUMNS不接受表达式,只能是列名。而RANGE分区则要求分区的对象是整数。 +2. RANGE COLUMNS允许多个列,在底层实现上,它比较的是元祖(多个列值组成的列表),而RANGE比较的是标量,即数值的大小。 +3. RANGE COLUMNS不限于整数对象,date,datetime,string都可作为分区列。 +同RANGE分区类似,它的区间范围必须是递增的,有时候,列涉及的太多,不好判断区间的大小,可采用下面的方式进行判断: +SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12); + + +### LIST/LIST COLUMNS + +每个分区的定义和选择是基于某列的值或者一个返回整数的表达式,是基于列出的枚举值列表进行分区。 + +CREATE TABLE category (  +  cid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  +  name VARCHAR(30) NOT NULL DEFAULT ''  +)  +PARTITION BY LIST (cid) (  +  PARTITION p0 VALUES IN (0,4,8,12)  +  DATA DIRECTORY = '/data0/data'   +  INDEX DIRECTORY = '/data1/idx',  +    +  PARTITION p1 VALUES IN (1,5,9,13)  +  DATA DIRECTORY = '/data2/data'  +  INDEX DIRECTORY = '/data3/idx',  +    +  PARTITION p2 VALUES IN (2,6,10,14)  +  DATA DIRECTORY = '/data4/data'  +  INDEX DIRECTORY = '/data5/idx',  +    +  PARTITION p3 VALUES IN (3,7,11,15)  +  DATA DIRECTORY = '/data6/data'  +  INDEX DIRECTORY = '/data7/idx'  +); +LIST COLUMNS分区同样是LIST分区的一种特殊类型,它和RANGE COLUMNS分区较为相似,同样不接受表达式,同样支持多个列支持string,date和datetime类型。 +CREATE TABLE customers_2 ( +    first_name VARCHAR(25), +    last_name VARCHAR(25), +    street_1 VARCHAR(30), +    street_2 VARCHAR(30), +    city VARCHAR(15), +    renewal DATE +) +PARTITION BY LIST COLUMNS(city,last_name,first_name) ( +    PARTITION pRegion_1 VALUES IN (('Oskarshamn', 'Högsby', 'Mönsterås'),('Nässjö', 'Eksjö', 'Vetlanda')), +    PARTITION pRegion_2 VALUES IN(('Vimmerby', 'Hultsfred', 'Västervik'),('Uppvidinge', 'Alvesta', 'Växjo')) +); + + +### HASH/LINEAR HASH + +   TODODO: hash值是如何计算的 + +基于给定的分区个数,将数据分配到不同的分区,HASH分区只能针对整数进行HASH,对于非整形的字段只能通过表达式将其转换成整数。 + +CREATE TABLE users (  +  uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  +  name VARCHAR(30) NOT NULL DEFAULT '',  +  email VARCHAR(30) NOT NULL DEFAULT ''  +)  +PARTITION BY HASH (uid) PARTITIONS 4 (  +  PARTITION p0  +  DATA DIRECTORY = '/data0/data'  +  INDEX DIRECTORY = '/data1/idx',  +  +  PARTITION p1  +  DATA DIRECTORY = '/data2/data'  +  INDEX DIRECTORY = '/data3/idx',  +  +  PARTITION p2  +  DATA DIRECTORY = '/data4/data'  +  INDEX DIRECTORY = '/data5/idx',  +  +  PARTITION p3  +  DATA DIRECTORY = '/data6/data'  +  INDEX DIRECTORY = '/data7/idx'  +); +它的优点是在数据量大的场景,譬如TB级,增加、删除、合并和拆分分区会更快,缺点是,相对于HASH分区,它数据分布不均匀的概率更大。 +http://dev.mysql.com/doc/refman/5.6/en/partitioning-linear-hash.html +CREATE TABLE `test`.`partition_t5`(  +`id` INT UNSIGNED NOT NULL, +`username` VARCHAR(30) NOT NULL, +`email` VARCHAR(30) NOT NULL, +`birth_date` DATE NOT NULL +) ENGINE=MYISAM +PARTITION BY LINEAR HASH(id) +PARTITIONS 5; + +### KEY + +hash分区允许用户自定义的表达式,而key分区不允许使用用户自定义的表达式。 + +hash分区只支持整数分区,key分区支持除了blob或text类型之外的其他数据类型分区。 +与hash分区不同,创建key分区表的时候,可以不指定分区键,默认会选择使用主键/唯一键作为分区键,没有主键/唯一键,必须指定分区键。 +KEY分区和HASH分区的算法不一样,PARTITION BY HASH (expr),MOD取值的对象是expr返回的值,而PARTITION BY KEY (column_list),基于的是列的MD5值。 +CREATE TABLE users (  +  uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  +  name VARCHAR(30) NOT NULL DEFAULT '',  +  email VARCHAR(30) NOT NULL DEFAULT ''  +)  +PARTITION BY KEY (uid) PARTITIONS 4 (  +  PARTITION p0  +  DATA DIRECTORY = '/data0/data'  +  INDEX DIRECTORY = '/data1/idx',  +    +  PARTITION p1  +  DATA DIRECTORY = '/data2/data'   +  INDEX DIRECTORY = '/data3/idx',  +    +  PARTITION p2   +  DATA DIRECTORY = '/data4/data'  +  INDEX DIRECTORY = '/data5/idx',  +    +  PARTITION p3   +  DATA DIRECTORY = '/data6/data'  +  INDEX DIRECTORY = '/data7/idx'  +); + + +子分区 +子分区是针对 RANGE/LIST 类型的分区表中每个分区的再次分割。再次分割可以是 HASH/KEY 等类型。例如: +CREATE TABLE users (  +  uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  +  name VARCHAR(30) NOT NULL DEFAULT '',  +  email VARCHAR(30) NOT NULL DEFAULT ''  +)  +PARTITION BY RANGE (uid) SUBPARTITION BY HASH (uid % 4) SUBPARTITIONS 2(  +  PARTITION p0 VALUES LESS THAN (3000000)  +  DATA DIRECTORY = '/data0/data'  +  INDEX DIRECTORY = '/data1/idx',  +  +  PARTITION p1 VALUES LESS THAN (6000000)  +  DATA DIRECTORY = '/data2/data'  +  INDEX DIRECTORY = '/data3/idx'  +);  + +对 RANGE 分区再次进行子分区划分,子分区采用 HASH 类型。 +CREATE TABLE users (  +  uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  +  name VARCHAR(30) NOT NULL DEFAULT '',  +  email VARCHAR(30) NOT NULL DEFAULT ''  +)  +PARTITION BY RANGE (uid) SUBPARTITION BY KEY(uid) SUBPARTITIONS 2(  +  PARTITION p0 VALUES LESS THAN (3000000)  +  DATA DIRECTORY = '/data0/data'  +  INDEX DIRECTORY = '/data1/idx',  +  +  PARTITION p1 VALUES LESS THAN (6000000)  +  DATA DIRECTORY = '/data2/data'  +  INDEX DIRECTORY = '/data3/idx'  +);  + +对 RANGE 分区再次进行子分区划分,子分区采用 KEY 类型。 + += 分区管理 = + +删除分区  +ALERT TABLE users DROP PARTITION p0;  + +重建分区 +----- RANGE分区重建,将原来的 p0,p1 分区合并起来,放到新的 p0 分区中 +ALTER TABLE users REORGANIZE PARTITION p0,p1 INTO (PARTITION p0 VALUES LESS THAN (6000000)); +----- LIST分区重建,将原来的 p0,p1 分区合并起来,放到新的 p0 分区中。 +ALTER TABLE users REORGANIZE PARTITION p0,p1 INTO (PARTITION p0 VALUES IN(0,1,4,5,8,9,12,13));  +----- HASH/KEY分区重建,分区数只能减小不能增加,增加可以通过ADD PARTITION方法 +ALTER TABLE users REORGANIZE PARTITION COALESCE PARTITION 2;  + +新增分区 +----- 新增LIST分区   +ALTER TABLE category ADD PARTITION (PARTITION p4 VALUES IN (16,17,18,19)  +   DATA DIRECTORY = '/data8/data'  +   INDEX DIRECTORY = '/data9/idx');  +----- 新增HASH/KEY分区,将分区总数扩展到8个 +ALTER TABLE users ADD PARTITION PARTITIONS 8;  + + + +[ 给已有的表加上分区 ] +[sql] view plain copy + +    alter table results partition by RANGE (month(ttime))   +    (PARTITION p0 VALUES LESS THAN (1),  +    PARTITION p1 VALUES LESS THAN (2) , PARTITION p2 VALUES LESS THAN (3) ,  +    PARTITION p3 VALUES LESS THAN (4) , PARTITION p4 VALUES LESS THAN (5) ,  +    PARTITION p5 VALUES LESS THAN (6) , PARTITION p6 VALUES LESS THAN (7) ,  +    PARTITION p7 VALUES LESS THAN (8) , PARTITION p8 VALUES LESS THAN (9) ,  +    PARTITION p9 VALUES LESS THAN (10) , PARTITION p10 VALUES LESS THAN (11),  +    PARTITION p11 VALUES LESS THAN (12),  +    PARTITION P12 VALUES LESS THAN (13) );   + + + +默认分区限制分区字段必须是主键(PRIMARY KEY)的一部分,为了去除此 +限制: +[方法1] 使用ID +[sql] view plain copy + +    mysql> ALTER TABLE np_pk  +        ->     PARTITION BY HASH( TO_DAYS(added) )  +        ->     PARTITIONS 4;  + +ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function + +However, this statement using the id column for the partitioning column is valid, as shown here: + +[sql] view plain copy + +    mysql> ALTER TABLE np_pk  +        ->     PARTITION BY HASH(id)  +        ->     PARTITIONS 4;  + +Query OK, 0 rows affected (0.11 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +[方法2] 将原有PK去掉生成新PK +[sql] view plain copy + +    mysql> alter table results drop PRIMARY KEY;  + +Query OK, 5374850 rows affected (7 min 4.05 sec) +Records: 5374850 Duplicates: 0 Warnings: 0 + +[sql] view plain copy + +    mysql> alter table results add PRIMARY KEY(id, ttime);  + +Query OK, 5374850 rows affected (6 min 14.86 sec) + +Records: 5374850 Duplicates: 0 Warnings: 0 + +http://www.cnblogs.com/chenmh/p/5623474.html + +http://www.cnblogs.com/martinzhang/p/3467232.html + +http://www.simlinux.com/archives/133.html + +http://blog.csdn.net/tjcyjd/article/details/11194489 + +http://blog.51yip.com/mysql/1013.html + +https://segmentfault.com/a/1190000006812384 + +查看EXPLAIN命令 + +EXPLAIN PARTITIONS + +查看各个分区当前的数据量 + +SELECT partition_name part, partition_method method, partition_expression expr, partition_description descr, table_rows +  FROM INFORMATION_SCHEMA.partitions WHERE TABLE_SCHEMA = schema() AND TABLE_NAME='foobar_partition'; + +http://phpzf.blog.51cto.com/3011675/793775 + +L 180/100A 胸围 104 腰围 102 肩阔 45 后中长 70 袖长 44 + + + + + + + + + + + + + + + + + + + + + + + + +https://www.byvoid.com/zhs/blog/string-hash-compare + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-07-18-mysql-performance-schema_init.md b/_drafts/2015-07-18-mysql-performance-schema_init.md new file mode 100644 index 0000000..c64d9eb --- /dev/null +++ b/_drafts/2015-07-18-mysql-performance-schema_init.md @@ -0,0 +1,455 @@ +--- +title: MySQL Performance Schema +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + +这一存储引擎应该是在 MySQL 5.5 引入的,主要用于收集数据库服务器性能参数,提供等待事件的详细信息,包括锁、互斥变量、文件信息等;其中的表是只读的,用户不能创建或者修改。 + +在此简单介绍下。 + + + +## 简介 + +这一存储引擎应该是在 MySQL 5.5 引入的,主要用于收集数据库服务器性能参数,提供等待事件的详细信息,包括锁、互斥变量、文件信息等;其中的表是只读的,用户不能创建或者修改。 + +另外,需要注意的是,开启 PS 会有一定的性能损耗,不同的版本号,不同的分支可能略有不同,如果比较关注性能,应该以实测为准。 + +在该库中,数据表分为如下的几类: + +1. setup table:设置表,配置监控选项; +2. current events table:记录当前那些 thread 正在发生什么事情; +1. history table 发生的各种事件的历史记录表; +1. summary table 对各种事件的统计表; +1. 杂项表,乱七八糟表。 + + + +很多资料也可以参考官方网站的介绍 [MySQL Performance Schema](http://dev.mysql.com/doc/refman/en/performance-schema.html) 。 + +### 开启 + +如果使用 Performance Schema,需要在编译时进行配置,可以通过如下命令查看是否支持。 + +{% highlight text %} +$ mysqld --verbose --help | grep performance_schema + ... ... + --performance-schema + Enable the performance schema. + --performance-schema-accounts-size=# + Maximum number of instrumented user@host accounts. Use 0 + to disable, -1 for automated sizing. + ... ... +{% endhighlight %} + +在 5.5.6 之前,默认是关闭的,之后默认是打开的,在配置文件中通过如下方式进行设置,注意,这个参数是静态参数,只能在 my.cnf 设置,不能动态修改。 + +{% highlight text %} +[mysqld] +performance_schema=ON +{% endhighlight %} + +设置完重启后,可以查看相应变量,判断是否已经启动;若返回值为 ON,则说明性能数据库正常开启状态;如果是 OFF 可以查看日志为什么启动失败。 + +{% highlight text %} +mysql> SHOW VARIABLES LIKE 'performance_schema'; +{% endhighlight %} + +Performance Schema 通过存储引擎插件实现,也就意味着可以通过如下命令查看。 + +{% highlight text %} +mysql> SELECT * FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE='PERFORMANCE_SCHEMA'\G +*************************** 1. row *************************** + ENGINE: PERFORMANCE_SCHEMA + SUPPORT: YES + COMMENT: Performance Schema +TRANSACTIONS: NO + XA: NO + SAVEPOINTS: NO + +mysql> SHOW ENGINES\G +... ... + Engine: PERFORMANCE_SCHEMA + Support: YES + Comment: Performance Schema +Transactions: NO + XA: NO + Savepoints: NO +... ... +{% endhighlight %} + +对应的表存储在 performance_schema 库中,所包含的表可以通过如下两种方式查看,对应的表会随着新监控的添加慢慢增长。 + +{% highlight text %} +mysql> SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'performance_schema'; +mysql> SHOW TABLES FROM performance_schema; +{% endhighlight %} + +默认不是所有的 instrument 和 consumer 都会被 enable ,这也就意味着一开始不会收集所有的事件,可以通过执行如下 SQL 。 + +{% highlight text %} +mysql> UPDATE setup_instruments SET ENABLED = 'YES', TIMED = 'YES'; +Query OK, 338 rows affected (0.12 sec) +mysql> UPDATE setup_consumers SET ENABLED = 'YES'; +Query OK, 8 rows affected (0.00 sec) +{% endhighlight %} + +如果想查看某个时刻的等待事件,可以查询 events_waits_current 表,它记录了每个 thread 最近的监控信息。 + + + + + + + + + + +## setup table + +通过如下方式可以查看所有的 setup 表。 + +{% highlight text %} +mysql> SELECT table_name FROM information_schema.tables WHERE \ + table_schema = 'performance_schema' AND table_name LIKE 'setup%'; ++-------------------+ +| table_name | ++-------------------+ +| setup_actors | 配置用户纬度的监控,默认监控所有用户 +| setup_consumers | 消费者类型,即收集的事件写入到哪些统计表中 +| setup_instruments | 该数据库的表名以及是否开启监控 +| setup_objects | 监控对象 +| setup_timers | 监控选项已经采样频率的时间间隔 ++-------------------+ +5 rows in set (0.00 sec) +{% endhighlight %} + +### setup_actors + +配置用户纬度的监控,默认监控所有用户。 + +{% highlight text %} +mysql> SELECT * FROM performance_schema.setup_actors; ++------+------+------+---------+---------+ +| HOST | USER | ROLE | ENABLED | HISTORY | ++------+------+------+---------+---------+ +| % | % | % | YES | YES | ++------+------+------+---------+---------+ +1 row in set (0.00 sec) +{% endhighlight %} + + +### setup_consumers + +对于 setup_consumers 表,需要注意的是,如果要采集数据,需要确保其 ENABLED 列为 YES 才会收集,可以直接使用 UPDATE SQL 进行更新,更新完后立即生效。 + +{% highlight text %} +mysql> SELECT * FROM performance_schema.setup_consumers ; ++----------------------------------+---------+ +| NAME | ENABLED | ++----------------------------------+---------+ +| events_stages_current | NO | +| events_stages_history | NO | +| events_stages_history_long | NO | +| events_statements_current | YES | +| events_statements_history | YES | +| events_statements_history_long | NO | +| events_transactions_current | NO | +| events_transactions_history | NO | +| events_transactions_history_long | NO | +| events_waits_current | NO | +| events_waits_history | NO | +| events_waits_history_long | NO | +| global_instrumentation | YES | +| thread_instrumentation | YES | +| statements_digest | YES | ++----------------------------------+---------+ +15 rows in set (0.00 sec) +{% endhighlight %} + + + + +上述通过 UPDATE 配置后,服务器重启又会变回默认值,要永久生效需要在配置文件里添加。 + +{% highlight text %} +[mysqld] +performance_schema_consumer_events_waits_current = ON +performance_schema_consumer_events_stages_current = ON +performance_schema_consumer_events_statements_current = ON +performance_schema_consumer_events_waits_history = ON +performance_schema_consumer_events_stages_history = ON +performance_schema_consumer_events_statements_history = ON +{% endhighlight %} + +也即在这些表的前面加上performance_schema_consumer_xxx。 + +其中 history 和 history_long 保存的是 current 表的历史记录条数,长度可以通过变量设置。 + +{% highlight text %} +mysql> SHOW VARIABLES LIKE 'performance_schema%history%size'; ++----------------------------------------------------------+-------+ +| Variable_name | Value | ++----------------------------------------------------------+-------+ +| performance_schema_events_stages_history_long_size | 1000 | +| performance_schema_events_stages_history_size | 10 | +| performance_schema_events_statements_history_long_size | 1000 | +| performance_schema_events_statements_history_size | 10 | +| performance_schema_events_transactions_history_long_size | 1000 | +| performance_schema_events_transactions_history_size | 10 | +| performance_schema_events_waits_history_long_size | 1000 | +| performance_schema_events_waits_history_size | 10 | ++----------------------------------------------------------+-------+ +8 rows in set (0.00 sec) +{% endhighlight %} + +### setup_instruments + +配置具体的 instrument,主要包含如下的几大类:idle、stage/xxx、statement/xxx、wait/xxx、memory/xxx、transaction 。 + +{% highlight text %} +mysql> SELECT name, count(1) FROM performance_schema.setup_instruments GROUP BY left(name,5); ++-------------------------------------------+----------+ +| name | count(1) | ++-------------------------------------------+----------+ +| idle | 1 | +| memory/performance_schema/mutex_instances | 375 | +| stage/sql/After create | 129 | +| statement/sql/select | 193 | +| transaction | 1 | +| wait/synch/mutex/sql/TC_LOG_MMAP::LOCK_tc | 314 | ++-------------------------------------------+----------+ +6 rows in set (0.01 sec) +{% endhighlight %} + +idle 表示 socket 空闲的时间;stage 类表示语句的每个执行阶段的统计;statement 类统计语句维度的信息;wait 类统计各种等待事件,比如 IO、mutux、spin_lock、condition 等。 + + +### setup_objects + +默认对 MySQL、performance_schema 和 information_schema 中的表都不监控,而其它 DB 的所有表都监控。 + + + +{% highlight text %} +mysql> SELECT * FROM performance_schema.setup_objects; ++-------------+--------------------+-------------+---------+-------+ +| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | ENABLED | TIMED | ++-------------+--------------------+-------------+---------+-------+ +| EVENT | mysql | % | NO | NO | +| EVENT | performance_schema | % | NO | NO | +| EVENT | information_schema | % | NO | NO | +| EVENT | % | % | YES | YES | +| FUNCTION | mysql | % | NO | NO | +| FUNCTION | performance_schema | % | NO | NO | +| FUNCTION | information_schema | % | NO | NO | +| FUNCTION | % | % | YES | YES | +| PROCEDURE | mysql | % | NO | NO | +| PROCEDURE | performance_schema | % | NO | NO | +| PROCEDURE | information_schema | % | NO | NO | +| PROCEDURE | % | % | YES | YES | +| TABLE | mysql | % | NO | NO | +| TABLE | performance_schema | % | NO | NO | +| TABLE | information_schema | % | NO | NO | +| TABLE | % | % | YES | YES | +| TRIGGER | mysql | % | NO | NO | +| TRIGGER | performance_schema | % | NO | NO | +| TRIGGER | information_schema | % | NO | NO | +| TRIGGER | % | % | YES | YES | ++-------------+--------------------+-------------+---------+-------+ +20 rows in set (0.00 sec) +{% endhighlight %} + + +### setup_timers + +配置每种类型指令的统计时间单位。MICROSECOND表示统计单位是微妙,CYCLE表示统计单位是时钟周期,时间度量与CPU的主频有关,NANOSECOND表示统计单位是纳秒。但无论采用哪种度量单位,最终统计表中统计的时间都会装换到皮秒。(1秒=1000000000000皮秒) + +{% highlight text %} +mysql> SELECT * FROM performance_schema.setup_timers; ++-------------+-------------+ +| NAME | TIMER_NAME | ++-------------+-------------+ +| idle | MICROSECOND | +| wait | CYCLE | +| stage | NANOSECOND | +| statement | NANOSECOND | +| transaction | NANOSECOND | ++-------------+-------------+ +5 rows in set (0.00 sec) +{% endhighlight %} + +## instance + +### cond_instances + +条件等待对象实例,表中记录了系统中使用的条件变量的对象,OBJECT_INSTANCE_BEGIN 为对象的内存地址。 + +### file_instances + +记录了系统中打开了文件的对象,包括 ibdata、redo、binlog、用户的表文件等,open_count 显示当前文件打开的数目,如果没有打开过,不会出现在表中。 + +{% highlight text %} +mysql> SELECT * FROM performance_schema.file_instances; ++-------------------------------------------+--------------------------------------+------------+ +| FILE_NAME | EVENT_NAME | OPEN_COUNT | ++-------------------------------------------+--------------------------------------+------------+ +| /opt/mysql-5.7/share/english/errmsg.sys | wait/io/file/sql/ERRMSG | 0 | +| /opt/mysql-5.7/share/charsets/Index.xml | wait/io/file/mysys/charset | 0 | +| /tmp/mysql-master/ibdata1 | wait/io/file/innodb/innodb_data_file | 3 | +| /tmp/mysql-master/ib_logfile0 | wait/io/file/innodb/innodb_log_file | 2 | +| /tmp/mysql-master/ib_logfile1 | wait/io/file/innodb/innodb_log_file | 2 | +| /tmp/mysql-master/mysql/engine_cost.ibd | wait/io/file/innodb/innodb_data_file | 3 | ++-------------------------------------------+--------------------------------------+------------+ +269 rows in set (0.01 sec) +{% endhighlight %} + + + +### socket_instances + +记录活跃会话对象实例,表中记录了 thread_id、socket_id、ip 和 port,其它表可以通过 thread_id 与 socket_instance 进行关联,获取 IP-PORT 信息,能够与应用对接起来。 + +{% highlight text %} +mysql> select * from socket_instances; ++----------------------------------------+-----------+-----------+------------------+-------+--------+ +| EVENT_NAME | THREAD_ID | SOCKET_ID | IP | PORT | STATE | ++----------------------------------------+-----------+-----------+------------------+-------+--------+ +| wait/io/socket/sql/server_tcpip_socket | 1 | 33 | :: | 3307 | ACTIVE | +| wait/io/socket/sql/server_unix_socket | 1 | 34 | | 0 | ACTIVE | +| wait/io/socket/sql/client_connection | 31 | 41 | ::ffff:127.0.0.1 | 54834 | ACTIVE | +| wait/io/socket/sql/client_connection | 32 | 45 | | 0 | ACTIVE | ++----------------------------------------+-----------+-----------+------------------+-------+--------+ +4 rows in set (0.00 sec) +{% endhighlight %} + +event_name 主要包含 3 类: + +* wait/io/socket/sql/server_unix_socket,服务端 unix 监听 socket; +* wait/io/socket/sql/server_tcpip_socket,服务端 tcp 监听 socket; +* wait/io/socket/sql/client_connection,客户端 socket 。 + + + + + + + + + + + + +## 其它 + +### threads + +监视服务端的当前运行的线程。 + + + + +{% highlight text %} +----- 哪个SQL执行最多 +mysql> SELECT schema_name,digest_text,count_star,sum_rows_sent,sum_rows_examined + FROM events_statements_summary_by_digest ORDER BY count_star desc\G + +----- 哪个SQL平均响应时间最多 +mysql> SELECT schema_name,digest_text,count_star,avg_timer_wait,sum_rows_sent,sum_rows_examined + FROM events_statements_summary_by_digest ORDER BY AVG_TIMER_WAIT desc LIMIT 1\G + +{% endhighlight %} + + +## 源码解析 + +该引擎的源码保存在 storage/perfschema 目录下, + +{% highlight text %} +mysqld_main() + |-pre_initialize_performance_schema() 最先调用的函数 + |-initialize_performance_schema() + |-initialize_performance_schema_acl() + |-check_performance_schema() +{% endhighlight %} + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-07-27-mysql-transaction_init.md b/_drafts/2015-07-27-mysql-transaction_init.md new file mode 100644 index 0000000..7b4ded8 --- /dev/null +++ b/_drafts/2015-07-27-mysql-transaction_init.md @@ -0,0 +1,902 @@ +--- +title: MySQL 事务处理 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,事务,源码,XA,TM,RM +description: 事务处理是当前大多数数据库所需的,而不同的数据库实现方式又略有区别,在此介绍一下 MySQL 的实现方式,包括了 XA、。 +--- + +事务处理是当前大多数数据库所需的,而不同的数据库实现方式又略有区别,在此介绍一下 MySQL 的实现方式,包括了 XA、。 + + + + +## 简介 + +首先,介绍一下一些常见的概念,也就是 XA 。 + +XA (transaction accordant) 是由 X/Open 组织提出的分布式事务的规范,主要定义了全局事务管理器 (Transaction Manager, TM) 和局部资源管理器 (Resource Manager, RM)之间的接口。 + +MySQL XA 分为了内部 XA 与外部 XA 两类,内部 XA 用于同一实例下跨多个引擎的事务,由 binlog 作为协调者;外部 XA 用于跨多 MySQL 实例的分布式事务,需要应用层介入作为协调者,包括崩溃时的悬挂事务、全局提交还是回滚。 + +通常会将事务的提交分成了两个阶段,也就是两阶段提交 (tow phase commit, 2PC): + +1. Prepare 阶段
    第一阶段,事务管理器向所有涉及到的数据库服务器发出 "准备提交"(prepare) 请求,数据库收到请求后执行数据修改和日志记录等处理,处理完成后只是把事务的状态改成 "可以提交",然后把结果返回给事务管理器。 + +2. Commit 阶段
    第二阶段,在第一阶段中所有数据库都提交成功,那么事务管理器向数据库服务器发出 "确认提交" 请求,数据库服务器把事务的 "可以提交" 状态改为 "提交完成" 状态,然后返回应答。 + +如果在第一阶段内有任何一个数据库的操作发生了错误,或者事务管理器收不到某个数据库的回应,则认为事务失败,回撤所有数据库的事务;数据库服务器收不到第二阶段的确认提交请求,也会把 "可以提交" 的事务回滚。 + + +## 外部 XA 事务 + +也就是 MySQL 的跨库事务,或者称之为分布式事务,注意必须使用 serializable 隔离级别,而且只有 Innodb 支持。 + +{% highlight text %} +XA {START | BEGIN} xid // 开始一个事务,并将事务置于ACTIVE状态,此后执行的SQL语句都将置于该事务中 +XA END xid // 将事务置于IDLE状态,表示事务内SQL操作完成 +XA COMMIT xid [ONE PHASE] // 事务最终提交,完成持久化,事务完成;ONE PHASE将prepare和commit一步完成 +XA PREPARE xid // 实现事务提交的准备工作,事务状态置于PREPARED状态 +XA ROLLBACK xid // 事务回滚并终止 +{% endhighlight %} + +对于一个应用大致的操作示例如下。 + +{% highlight text %} +xa begin 'xa-trans'; // A1,两个数据库分别创建两个分布式事务 +xa start 'xa-trans'; // A2 + +... ... // 向两个库表中分别写入数据 + +xa end 'xa-trans'; // A1,SQL操作完成 +xa end 'xa-trans'; // A2 + +xa prepare 'xa-trans'; // A1,事务准备提交 +xa prepare 'xa-trans'; // A2 + +xa commit 'xa-trans'; // A1,事务最终提交 +xa commit 'xa-trans'; // A2 +{% endhighlight %} + + +## 内部 XA 事务 + +MySQL 为了兼容其它非事物引擎的复制,在 server 层引入了 binlog, 它可以记录所有引擎中的修改操作,因而可以对所有的引擎使用复制功能。 + +另外,由于 MySQL 采用插件式存储架构,导致开启 binlog 后,事务提交实际是采用 2PC 来保证 binlog 和 redolog 的一致性,以及事务提交和回滚等;这也就是内部 XA 事务。 + +MySQL 通过 WAL (Write-Ahead Logging) 方式,来保证数据库事务的一致性 (consistent) 和持久性 (durability);这是一种实现事务日志的标准方法,具体而言就是修改记录前,一定要先写日志;事务提交过程中,一定要保证日志先落盘,才能算事务提交完成。 + +在提交过程中,主要做了如下的几件事情: + +1. 是清理 undo 段信息,对于 innodb 存储引擎的更新操作来说,undo 段需要 purge,这里的 purge 主要职能是,真正删除物理记录。在执行delete或update操作时,实际旧记录没有真正删除,只是在记录上打了一个标记,而是在事务提交后,purge线程真正删除,释放物理页空间。因此,提交过程中会将undo信息加入purge列表,供purge线程处理。 + +2. 然后是释放锁资源,mysql通过锁互斥机制保证不同事务不同时操作一条记录,事务执行后才会真正释放所有锁资源,并唤醒等待其锁资源的其他事务; + +3. 再就是刷redo日志,前面我提到了,mysql实现事务一致性和持久性的机制。通过redo日志落盘操作,保证了即使修改的数据页没有即使更新到磁盘,只要日志是完成了,就能保证数据库的完整性和一致性; + +4. 最后就是清理保存点列表,每个语句实际都会有一个savepoint(保存点),保存点作用是为了可以回滚到事务的任何一个语句执行前的状态,由于事务都已经提交了,所以保存点列表可以被清理了。 + +其中相关的包括了 MySQL 的锁机制、purge 原理、redo 日志、undo 段等内容,其实都是数据库的核心。 + + +1. prepare,第一阶段
    binlog 不作任何操作。InnoDB prepare,包括的操作有 A) 持有 prepare_commit_mutex;B) 并且 write/sync redo log;C) 将回滚段设置为 Prepared 状态。 + +2. commit,第二阶段
    包括 write/sync Binlog。InnoDB commit,在写入 COMMIT 标记后释放 prepare_commit_mutex。 + +其中,以 binlog 的写入与否作为事务提交成功与否的标志,innodb commit 标志并不是事务成功与否的标志。此时,事务崩溃恢复过程如下: + +1. 崩溃恢复时,扫描最后一个 Binlog 文件,提取其中的 xid。 + +2. InnoDB 维持了状态为 prepare 的事务链表,将这些事务的 xid 和 binlog 中记录的 xid 做比较,如果在 binlog 中存在,则提交,否则回滚事务;从而可以让 InnoDB 和 binlog 中的事务状态保持一致。 + +在 prepare 阶段或者在 write/sync binlog 阶段崩溃,也会回滚;在写入 innodb commit 标志时崩溃,则恢复时,会重新对 commit 标志进行写入。 + + +## 组提交 + +MySQL 5.1 中,如果同时打开 sync_binlog=1、innodb_flush_log_at_trx_commit=1,TPS 将会下降到几十;这是因为 InnoDB 提交事务时,不仅需要将 REDO 刷盘,还需要将 Binlog 刷盘,每个事务都需要 2 次 sync 操作。而机械磁盘的 IOPS 也就为几百,所以导致 InnoDB 的性能极差。 + + + +为了保证主从库的一致性,必须要保证 Binlog 和 InnoDB 的一致性,即如果一个事务写入了 Binlog,InnoDB 中就必须提交该事务;相反,如果一个事务没有写入 Binlog,InnoDB 就不能提交该事务。 + +为此,在 MySQL 中的做法是:A) InnoDB 执行 Prepare,将 Redo 日志写磁盘;B) 将 Binlog 写磁盘;C) InnoDB 执行 Commit,将事务标记为提交。这样,可以保证 Binlog 和 InnoDB 的一致性,可以分三种情况考虑: + +1. 在 InnoDB Prepare 阶段 Crash。MySQL 在启动时做崩溃恢复,InnoDB 会回滚这些事务,同时由于事务也没有写到 binlog,InnoDB 和 Binlog 一致。 + +2. 在 Binlog 写磁盘阶段 Crash。MySQL 在启动做崩溃恢复时,会扫描未成功提交的事务,和当时未成功关闭的 binlog 文件,如果事务已经 Prepare 了,并且也已经在 Binlog 中了,InnoDB 会提交该事务;相反,如果事务已经在 Prepare 中了,但是不在 Binlog 中,InnoDB 会回滚该事务。结果就是 InnoDB 和 Binlog 一致。 + +3. 在 InnoDB 执行 Commit 阶段 Crash。和 2 类似,由于事务已经成功 Prepare,并且存在 Binlog 文件中,InnoDB 在崩溃恢复时,仍然会提交该事务,确保 Binlog 和 InnoDB 一致。 + +也就是说,统一按照 binlog 的为准。在实现时,将 mysql_bin_log 作为 2 阶段提交的协调者 (详见 ha_commit_trans)。 + + +于是,对 binlog 采用组提交 (注意,prepare 阶段没有变),将该过程分为三个阶段。 + +1. flush stage,将各个线程的 binlog 从 cache 写到文件中; + +2. sync stage 对binlog做fsync操作(如果需���的话;最重要的就是这一步,对多个线程的binlog合并写入磁盘); + +3. commit stage 为各个线程做引擎层的事务commit(这里不用写redo log,在prepare阶段已写)。每个stage同时只有一个线程在操作。(分成三个阶段,每个阶段的任务分配给一个专门的线程,这是典型的并发优化) + +这种实现的优势在于三个阶段可以并发执行,从而提升效率。 + + + + + +# 事务操作 + +MySQL 提供了多种方式来开启一个事务,最简单的就是以一条 BEGIN 语句开始,也可以以 START TRANSACTION 开启事务,你还可以选择开启一个只读事务还是读写事务。所有显式开启事务的行为都会隐式的将上一条事务提交掉。 + +所有显示开启事务的入口函数均为 trans_begin(),如下列出了几种常用的事务开启方式。 + +### BEGIN + +与该命令等效的命令还有 "BEGIN WORK" 及 "START TRANSACTION",下面仍以 BEGIN 为例。 + +当以 BEGIN 开启一个事务时,首先会去检查是否有活跃的事务还未提交,如果没有提交,则调用 ha_commit_trans() 提交之前的事务,并释放之前事务持有的 MDL() 锁。 + +执行 BEGIN 命令并不会真的去引擎层开启一个事务,仅仅是为当前线程设定标记,表示为显式开启的事务。 + + +### START TRANSACTION READ ONLY + +使用该选项开启一个只读事务,当以这种形式开启事务时,会为当前线程的 thd->tx_read_only 设置为 true。当服务端接受到任何数据更改的 SQL 时,都会直接拒绝请求,会返回如下的错误码,不会进入引擎层。 + +下面的文件会在编译的时候生成。 + +{% highlight cpp %} +// build/include/mysqld_ername.h +{ "ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION", 1792, "Cannot execute statement in a READ ONLY transaction." }, + +// build/include/mysqld_error.h +#define ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION 1792 + +// build/include/sql_state.h +{ ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION,"25006", "" }, +{% endhighlight %} + +这个选项可以强约束一个事务为只读的,而只读事务在引擎层可以走优化过的逻辑,相比读写事务的开销更小,例如不用分配事务id、不用分配回滚段、不用维护到全局事务链表中。 + +这种方式从 5.6 版本开始引入,在该版本中将全局事务链表拆成了两个链表:一个用于维护只读事务,一个用于维护读写事务。这样我们在构建一个一致性视图时,只需要遍历读写事务链表即可。 + +不过在 5.6 版本中,InnoDB 并不具备事务从只读模式自动转换成读写事务的能力,因此需要用户显式的使用以下两种方式来开启只读事务:A) 执行 START TRANSACTION READ ONLY;B) 将变量 tx_read_only 设置为 true 。 + +5.7 版本引入了模式自动转换的功能,但该语法依然保留了。 + + + +### START TRANSACTION READ WRITE + +用于开启读写事务,这也是默认的事务模式;如果当前实例的 read_only 打开了且当前连接不是超级账户,则显示开启读写事务会报错。 + + + + + + + + + + + + +## 源码详解 + +其中与事务提交相关的涉及到两个重要的参数,innodb_flush_log_at_trx_commit 和 sync_binlog,不同的模式区别在于,写文件调用 write 和落盘 fsync 调用的频率不同,所导致的后果是 mysqld 或 os crash 后,不严格的设置可能会丢失事务的更新。 + +双一模式是最严格的模式 (也就是上述参数均为一),这种设置情况下,单机在任何情况下不会丢失事务更新;不过同时也会带来极大的性能影响。 + +所有显示开启事务的入口函数均为 trans_begin() 。 + +### 初始化 + +在实例启动时,会选择使用哪种 XA 方式,默认的就是 BINLOG 和 ENGINE 做 XA,如果 BINLOG 禁止了,则只用引擎做 XA。 + +{% highlight cpp %} +static int init_server_components() // 初始化tc_log变量 +{ + ... ... + if (total_ha_2pc > 1 || (1 == total_ha_2pc && opt_bin_log)) + { + if (opt_bin_log) + tc_log= &mysql_bin_log; // 打开binlog时,使用mysql_bin_log做2PC协调者 + else + tc_log= &tc_log_mmap; // 没有打开binlog,且存在超过两个支持2PC的引擎时 + } + else + tc_log= &tc_log_dummy; // 不存在支持2PC的引擎,或只有1个支持2PC的引擎且没有打开binlog时 + ... ... +} +{% endhighlight %} + +其中 total_ha_2pc 代表了支持 2PC 引擎的数目,在 ha_initialize_handlerton() 初始化各个存储接口时,如果发现引擎的 prepare() 函数被定义,就会 total_ha_2pc++。 + +需要注意的是 binlog 模块也算是支持 2PC 的引擎,也就是只要又一个存储引擎执行 preprare() 正常,则 total_ha_2pc 值就大于 1 。 + +* TC_LOG_DUMMY
    直接调用引擎接口做 PREPARE/COMMIT/ROLLBACK,基本不做任何协调。 + +* MYSQL_BIN_LOG
    实际上 binlog 接口不做 prepare,只做 commit,也就所有写入 binlog 文件的事务,在崩溃恢复时,都应该能够提交。 + +* TC_LOG_MMAP
    使用 mmap() 方式映射到内存中,实现事务日志的策略。 + +上述类都是从 TC_LOG 这个纯虚类继承而来的,该类以及相关处理方法是日志子系统中为两阶段事务 (2PC) 提供的,并且在 binlog 中继承了该结构。 + +其中比较重要的方法包括了如下的几种:open() 打开日志文件、close() 关闭日志文件、log_xid() 记录 2PC 事务日志 id、unlog() 删除 2PC 事务日志 id。 + + +### TC_LOG_MMAP + +在此重点介绍 TC_LOG_MMAP,其中一个重要的结构体就是 st_page,使得日志按照页的方式进行组织和操作。 + +{% highlight cpp %} +class TC_LOG_MMAP: public TC_LOG { + typedef struct st_page { + struct st_page *next; // 指向下一个日志页,链接成一个FIFO队列 + my_xid *start, *end; // 指向页的第一个和最后一个位置 + my_xid *ptr; // 下一个要记录xid的位置 + int size, free; // 日志页可以存储xid的数目以及空闲的存储空间数目 + int waiters; // 当前页处于条件等待的操作数目 + PAGE_STATE state; // 当前页的状态,主要包括PS_POOL、PS_ERROR、PS_DIRTY三种状态 + mysql_mutex_t lock; // 用于控制日志页的并发操作 + mysql_cond_t cond; // 等待sync() + } PAGE; + + char logname[FN_REFLEN]; // mmap的日志文件名 + File fd; // 文件描述符 + my_off_t file_length; // 日志文件的大小 + uint npages; // 日志文件的页数,其中日志文件是按照页进行组织 + uint inited; // 一个流程状态参数,用于表明当前操作所处的状态 + uchar *data; // 用于存储xid数据内容 + struct st_page *pages; // 用于组织数据页 + struct st_page *syncing, *active, *pool; // 日志页分为syncing、active、pool三种状态存在 + struct st_page **pool_last_ptr; // 该指针指向pool队列中的最后一个元素 + mysql_mutex_t LOCK_active, LOCK_pool, LOCK_sync;// 三个锁结构,用于并发控制访问日志 + mysql_cond_t COND_pool, COND_active; // 该条件变量分别用于并发控制中,唤醒pool队列和active页访问 +}; +#define TC_LOG_PAGE_SIZE 8192 +#define TC_LOG_MIN_SIZE (3*TC_LOG_PAGE_SIZE) +{% endhighlight %} + +其中日志页按照队列的方式进行组织,其中 TC_LOG 日志文件默认每页大小为 8K,文件最小为 3 页,可以根据配置文件和启动参数指定日志文件大小。 + + + +### 执行流程 + +无论对于 DML (update, delete, insert)、TCL (commit, rollback) 语句,MySQL 提供了公共接口 mysql_execute_command(),基本流程如下: + +{% highlight cpp %} +mysql_execute_command(THD *thd) { + switch (lex->sql_command) { + case SQLCOM_INSERT: + mysql_insert(); + break; + case SQLCOM_UPDATE: + mysql_update(); + break; + case SQLCOM_BEGIN: + trans_begin() + break; + case SQLCOM_COMMIT: + trans_commit(); + break; + ...... + } + + if (thd->is_error() || ...) // 语句执行错误 + trans_rollback_stmt(thd); + else + trans_commit_stmt(thd); +} +{% endhighlight %} + +可以看到执行任何语句最后,都会执行 trans_rollback/commit_stmt(),这两个调用分别是语句级提交和语句级回滚。 + +语句级提交,对于非自动模式提交情况下,主要作两件事情,一是释放autoinc锁,这个锁主要用来处理多个事务互斥地获取自增列值,因此,无论最后该语句提交或是回滚,该资源都是需要而且可以立马放掉的。二是标识语句在事务中位置,方便语句级回滚。 + +{% highlight text %} +CREATE TABLE a ( + id int(10) unsigned NOT NULL, + weight int(10) unsigned default NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB; +CREATE TABLE b ( + id int(10) unsigned NOT NULL, + weight int(10) unsigned default NULL, + PRIMARY KEY (id) +) ENGINE=TokuDB; + +INSERT INTO a VALUES(1, 70),(2, 80); +INSERT INTO b VALUES(1, 70),(2, 80); + +SET autocommit = 0; +SHOW VARIABLES LIKE 'log_bin'; +SET SESSION debug = 'd:t:i:o,/tmp/mysqld.trace'; + +###################################################################### +### 最常见的,也即打开binlog,且只使用了InnoDB引擎 +### 此时tc_log使用的是mysql_bin_log +log-bin = 1, a (innodb) + +mysql> START TRANSACTION | BEGIN; +mysql_execute_command() SQLCOM_BEGIN + |-trans_begin() + |-trans_check_state() + +mysql> INSERT INTO a VALUES(9, 100); +Query OK, 1 row affected (0.01 sec) +mysql_execute_command() SQLCOM_INSERT + |-insert_precheck() + | |-check_access() + | |-check_grant() + |-mysql_insert() + +mysql> COMMIT; +Query OK, 0 rows affected (0.00 sec) +mysql_execute_command() SQLCOM_COMMIT + |-trans_commit() + |-trans_check_state() + |-ha_commit_trans() stmt(false) + | |-commit_owned_gtids() + | |-tc_log->prepare() 1. MYSQL_BINLOG::prepare() + | | |-ha_prepare_low() + | | |-##### for()中循环中遍厉所有接口,在此也就是binlog+InnoDB + | | |-ht->prepare() 调用存储引擎handlerton->prepare() + | | | ### 注意,实际调用如下的两个函数 + | | |-binlog_prepare() + | | |-innobase_xa_prepare() + | | |-check_trx_exists() + | | |-innobase_trx_init() + | | + | |-tc_log->commit() 2. MYSQL_BIN_LOG::commit() + | |-ordered_commit() 在此涉及到了binlog的组提交,详细《日志相关》 + | |-change_stage() 2.1 转为FLUSH_STAGE,写入binlog缓存 + | |-process_flush_stage_queue() + | |-flush_cache_to_file() + | | + | |-change_stage() 2.2 转为SYNC_STAGE,调用fsync()刷入磁盘 + | |-sync_binlog_file() 调用fsync()写入磁盘 + | | + | |-change_stage() 2.3 转为COMMIT_STAGE,提交阶段 + | |-process_commit_stage_queue() + | | |-ha_commit_low() + | | |-##### for()中循环中遍厉所有接口,在此也就是binlog+InnoDB + | | |-ht->commit() 调用存储引擎handlerton->commit() + | | | ### 注意,实际调用如下的两个函数 + | | |-binlog_commit() + | | |-innobase_commit() + | |-process_after_commit_stage_queue() + | |-finish_commit() + | + |-trans_track_end_trx() + + + + + + + + + + + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +log-bin = 0, a (innodb), b (tokudb) + +mysql> START TRANSACTION | BEGIN; +Query OK, 0 rows affected (0.00 sec) +mysql_execute_command() SQLCOM_BEGIN + |-trans_begin() 开启事务 + | |-trans_check_state() + | |-ha_commit_trans() 如果有未提交的事务,先提交掉 + | + |-ha_commit_trans() + | |-ha_commit_trans() + | |-tc_log->commit() 实际调用的是TC_LOG_MMAP::commit() + |-MDL_context::release_transactional_locks() + |- ... ... 将thd.server_status设置为SERVER_STATUS_IN_TRANS + +mysql> UPDATE a SET weight = 30 WHERE id = 1; +Query OK, 1 row affected (0.00 sec) +Rows matched: 1 Changed: 1 Warnings: 0 +mysql_execute_command() SQLCOM_UPDATE + |-update_precheck() + |-mysql_update() + +mysql> UPDATE b SET weight = 30 WHERE id = 1; +mysql> COMMIT; +mysql_execute_command() SQLCOM_COMMIT + |-trans_commit() + + + +###################################################################### +log-bin = 0, a (innodb) + +mysql> START TRANSACTION | BEGIN; +Query OK, 1 row affected (0.01 sec) +mysql> INSERT INTO a VALUES(4, 100); +Query OK, 1 row affected (0.01 sec) +mysql_execute_command() SQLCOM_COMMIT + |-insert_precheck() + |-mysql_insert() + | |-open_tables_for_query() + | | |-open_tables() + | | |-open_and_process_table() + | | |-open_table() + | | |-my_hash_first_from_hash_value() + | | |-innobase_trx_init() + | | |-column_bitmaps_signal() + | | + | |-write_record() + | |-handler::ha_write_row() + | |-ha_innobase::write_row() + | |-row_ins + | |-row_ins_index_entry_step + | |-row_ins_clust_index_entry + | |-row_ins_clust_index_entry_low + | + |-trans_commit_stmt() + |-ha_commit_trans() + |-ha_commit_low() + |-innobase_commit() + +mysql> COMMIT; +Query OK, 1 row affected (0.01 sec) +mysql_execute_command() SQLCOM_COMMIT + |-trans_commit + |-commit_owned_gtids(...) + |-ha_commit_trans + |-ha_commit_low + |-innobase_commit + |-innobase_trx_init + | |-THD::get_trans_pos + |-innobase_commit_low() + |-trx_commit_complete_for_mysql() + + + + + +/////////////// +log-bin = 1 +sync_binlog = 1 + + +------------------------------------------ +mysql> start transaction; +Query OK, 0 rows affected (0.00 sec) + trans_begin() + trans_commit_stmt + +mysql> insert into x4 values (10); +Query OK, 1 row affected (1 min 1.69 sec) + trans_commit_stmt + ha_commit_trans + binlog_prepare + innobase_xa_prepare + ha_commit_one_phase + binlog_commit(看起来什么也没做,仅仅做了cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);) + cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF); + innobase_commit(同样仅仅是做一些标记) + +mysql> commit ; + trans_commit + ha_commit_trans + binlog_prepare + innodb_xa_prepare + trx_prepare_for_mysql + mysql_mutex_lock(&prepare_commit_mutex); + MYSQL_BIN_LOG::log_xid + ha_commit_one_phase + binlog_commit(似乎木有做写binlog,莫非已经被group commit了。。。= =! log_xid..) + innobase_commit + innobase_commit_low + mysql_mutex_unlock(&prepare_commit_mutex); + trx_commit_complete_for_mysql(trx); + trans_commit_stmt + + + + + + + + + + + + +trans_commit_stmt() + |-ha_commit_trans() + |-tc_log->commit() 未开启binlog和gtid,则调用TC_LOG_MMAP::commit() + | 开启binlog后,则调用MYSQL_BIN_LOG::commit() + | + |-check_trx_exists() 获取innodb层对应的事务结构 + |-innobase_trx_init() + | + | #### 通过if判断需要事务提交 + |-innobase_commit_low() + | |-trx_commit_for_mysql() + | |-trx_commit() + | |-trx_commit_low() + | |-trx_write_serialisation_history() 更新binlog位点,设置undo状态 + | | |-trx_undo_update_cleanup() 供purge线程处理,清理回滚页 + | |-trx_commit_in_memory() 释放锁资源,清理保存点列表,清理回滚段 + | |-trx_flush_log_if_needed() 刷日志 + | + |-trx_deregister_from_2pc() + |-trx_commit_complete_for_mysql() 确定事务对应的redo日志是否落盘 + | |-trx_flush_log_if_needed() + | |-trx_flush_log_if_needed_low() + | |-log_write_up_to() 根据参数决定刷磁盘的时机 + | + | #### 单个语句,且非自动提交 + |-lock_unlock_table_autoinc() 释放自增列占用的autoinc锁资源 + |-trx_mark_sql_stat_end() 标识sql语句在事务中的位置,方便语句级回滚 + + +MYSQL_BIN_LOG::prepare() + |-ha_prepare_low() + |-ht->prepare() 实际调用如下的三个函数 + |-binlog_prepare() engine + |-innobase_xa_prepare() engine + |-trx_prepare_for_mysql() mysql + |-trx_start_if_not_started_xa() + | |-trx_start_if_not_started_xa_low() + | |-trx_start_low() + |-trx_prepare() + |-trx_undo_set_state_at_prepare() 设置undo段的标记为TRX_UNDO_PREPARED + |-trx->state=TRX_STATE_PREPARED 设置事务状态为TRX_STATE_PREPARED + |-trx_flush_log_if_needed() 将产生的redolog刷入磁盘 + + + + + + +xa start 'pp' + trans_xa_start + trans_begin (隐含的commit任何当前的事务并释放锁) + trans_check :(/* Conditions under which the transaction state must not change. */) + (likely(value)、unlikely(value)) + ha_commit_trans() + thd->mdl_context.release_transactional_locks() + trans_commit_stmt + +mysql> insert into x4 values(5); + trans_commit_stmt + ha_commit_trans + binlog_prepare:空函数 + innobase_xa_prepare + 对于xa事务,仅仅标记为ended + …… + ha_commit_one_phase + binlog_commit(该函数在每执行一次statement后) + cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF) + //binlog_commit_flush_trx_cache:将binlog cache中的内容写到文件中。 + //binlog_flush_cache + innobase_commit (对这条语句而言,仅仅标记该语句结束了,而不做commit) + +mysql> xa end 'pp'; + trans_commit_stmt + after_commit + + +mysql> xa prepare 'pp'; + trans_xa_prepare + ha_prepare + binlog_prepare + innobase_xa_prepare + …… + trx_prepare_for_mysql + trx_prepare_off_kernel-------根据my.cnf的选项决定是否刷新log到磁盘----做一次group commit + + 当不为XA_PREPARE语句时。。。 + if (thd_sql_command(thd) != SQLCOM_XA_PREPARE + pthread_mutex_lock(&prepare_commit_mutex); + trans_commit_stmt + after_commit + +mysql> xa commit 'pp'; + trans_xa_commit +92 /* +693 * Acquire metadata lock which will ensure that COMMIT is blocked +694 * by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in +695 * progress blocks FTWRL).We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does. + */ + 加锁…… + ha_commit_one_phase(thd, 1) + binlog_commit + binlog_commit_flush_trx_cache + binlog_flush_cache + innobase_commit + innobase_commit_low + trx_commit_for_mysql + trx_commit_off_kernel(看起来似乎是真的要提交了。。。。) + + + if (trx->declared_to_be_inside_innodb) { //不知道为什么没有持有prepare_commit_mutex + + + trx_commit_complete_for_mysql + + +log_write_up_to:(log0log.c) +实现group commit,这里仅考虑flushto_disk为真的情况,即需要向磁盘中fsync log +loop: + 当ut_dulint_cmp(log_sys->flushed_to_disk_lsn, lsn) >= 0 + 即lsn小于或等于全局变量log_sys中的记录的上次刷新的lsn时,直接返回 + + log_sys->n_pending_writes > 0 + if (flush_to_disk + && ut_dulint_cmp(log_sys->current_flush_lsn, lsn) + >= 0) { + /* The write + flush will write enough: wait for it to + complete */ + + + goto do_waits; + } + + + +{% endhighlight %} + + +## 常见配置项 + + +##### flush_log_at_trx_commit + +在 trx_commit_complete_for_mysql() 函数中,会根据 flush_log_at_trx_commit 参数,确定 redo 日志落盘方式。 + +##### binlog_max_flush_queue_time(0) 单位为us + +设置从 flush 队列中取事务的超时时间,防止并发事务过高,导致某些事务的 RT 上升,详细的实现参考 MYSQL_BIN_LOG::process_flush_stage_queue()。 + +##### binlog_order_commits(ON) +事务和binlog是否以相同顺序提交
    + + +不以顺序提交时可以稍微提升点性能,但并不是特别明显。
  • + +innodb_support_xa(ON) 是否启用 innodb 的 XA
    +它会导致一次额外的磁盘flush(prepare阶段flush redo log). 但是我们必须启用,而不能关闭它。因为关闭会导致binlog写入的顺序和实际的事务提交顺序不一致,会导致崩溃恢复和slave复制时发生数据错误。如果启用了log-bin参数,并且不止一个线程对数据库进行修改,那么就必须启用innodb_support_xa参数。 + + + + + + +## 事务ID + +在 InnoDB 中一直维护了一个不断递增的整数,存储在 trx_sys->max_trx_id 中,该事务 ID 可以看做一个事务的唯一标识;每次开启一个新的读写事务时,都将该 ID 分配给事务,同时递增全局计数。 + +{% highlight text %} +trx_start_low() # innobase/trx/trx0trx.cc + |-trx_sys_get_new_trx_id() # innobase/include/trx0sys.ic +{% endhighlight %} + +在 MySQL5.6 及之前的版本中,总是为事务分配 ID,而实际上只有做过数据更改的读写事务,才需要去根据事务 ID 判断可见性。因此在 MySQL5.7 版本中,只有读写事务才会分配事务 ID,只读事务默认为 0 。 + + + +对于全局最大事务 ID,每做 TRX_SYS_TRX_ID_WRITE_MARGIN (256) 次修改后,就持久化一次到 ibdata 的事务页 (TRX_SYS_PAGE_NO) 中。 + + + + + + + + +{% highlight cpp %} +/** The transaction system central memory data structure. */ +struct trx_sys_t{ + + ib_mutex_t mutex; /*!< mutex protecting most fields in + this structure except when noted + otherwise */ + ulint n_prepared_trx; /*!< Number of transactions currently + in the XA PREPARED state */ + ulint n_prepared_recovered_trx; /*!< Number of transactions + currently in XA PREPARED state that are + also recovered. Such transactions cannot + be added during runtime. They can only + occur after recovery if mysqld crashed + while there were XA PREPARED + transactions. We disable query cache + if such transactions exist. */ + trx_id_t max_trx_id; /*!< The smallest number not yet + assigned as a transaction id or + transaction number */ +#ifdef UNIV_DEBUG + trx_id_t rw_max_trx_id; /*!< Max trx id of read-write transactions + which exist or existed */ +#endif + trx_list_t rw_trx_list; /*!< List of active and committed in + memory read-write transactions, sorted + on trx id, biggest first. Recovered + transactions are always on this list. */ + trx_list_t ro_trx_list; /*!< List of active and committed in + memory read-only transactions, sorted + on trx id, biggest first. NOTE: + The order for read-only transactions + is not necessary. We should exploit + this and increase concurrency during + add/remove. */ + trx_list_t mysql_trx_list; /*!< List of transactions created + for MySQL. All transactions on + ro_trx_list are on mysql_trx_list. The + rw_trx_list can contain system + transactions and recovered transactions + that will not be in the mysql_trx_list. + There can be active non-locking + auto-commit read only transactions that + are on this list but not on ro_trx_list. + mysql_trx_list may additionally contain + transactions that have not yet been + started in InnoDB. */ + trx_rseg_t* const rseg_array[TRX_SYS_N_RSEGS]; + /*!< Pointer array to rollback + segments; NULL if slot not in use; + created and destroyed in + single-threaded mode; not protected + by any mutex, because it is read-only + during multi-threaded operation */ + ulint rseg_history_len;/*!< Length of the TRX_RSEG_HISTORY + list (update undo logs for committed + transactions), protected by + rseg->mutex */ + UT_LIST_BASE_NODE_T(read_view_t) view_list; + /*!< List of read views sorted + on trx no, biggest first */ +}; + +{% endhighlight %} + + + +事务子系统维护了三个不同的链表,用来管理事务对象。 + +trx_sys->mysql_trx_list +包含了所有用户线程的事务对象,即使是未开启的事务对象,只要还没被回收到trx_pool中,都被放在该链表上。当session断开时,事务对象从链表上摘取,并被回收到trx_pool中,以待重用。 + +trx_sys->rw_trx_list +读写事务链表,当开启一个读写事务,或者事务模式转换成读写模式时,会将当前事务加入到读写事务链表中,链表上的事务是按照trx_t::id有序的;在事务提交阶段将其从读写事务链表上移除。 + +trx_sys->serialisation_list +序列化事务链表,在事务提交阶段,需要先将事务的undo状态设置为完成,在这之前,获得一个全局序列号trx->no,从trx_sys->max_trx_id中分配,并将当前事务加入到该链表中。随后更新undo等一系列操作后,因此进入提交阶段的事务并不是trx->id有序的,而是根据trx->no排序。当完成undo更新等操作后,再将事务对象同时从serialisation_list和rw_trx_list上移除。 + +这里需要说明下trx_t::no,这是个不太好理清的概念,从代码逻辑来看,在创建readview时,会用到序列化链表,链表的第一个元素具有最小的trx_t::no,会赋值给ReadView::m_low_limit_no。purge线程据此创建的readview,只有小于该值的undo,才可以被purge掉。 + +总的来说,mysql_trx_list包含了rw_trx_list上的事务对象,rw_trx_list包含了serialisation_list上的事务对象。 + +事务ID集合有两个: + +trx_sys->rw_trx_ids +记录了当前活跃的读写事务ID集合,主要用于构建ReadView时快速拷贝一个快照 + +trx_sys->rw_trx_set +这是的映射集合,根据trx_id排序,用于通过trx_id快速获得对应的事务对象。一个主要的用途就是用于隐式锁转换,需要为记录中的事务id所对应的事务对象创建记录锁,通过该集合可以快速获得事务对象 + + + + + + + + + + + + + + + + + + +{% highlight cpp %} +struct trx_t{ + ulint magic_n; + ib_mutex_t mutex; + trx_id_t id; /*!< transaction id */ +}; +{% endhighlight %} + + + +http://blog.chinaunix.net/uid-26896862-id-3527584.html +http://mysql.taobao.org/monthly/2015/12/01/ +http://mysql.taobao.org/monthly/2015/11/04/ + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-07-31-mysql-replication-mmm_init.md b/_drafts/2015-07-31-mysql-replication-mmm_init.md new file mode 100644 index 0000000..830841d --- /dev/null +++ b/_drafts/2015-07-31-mysql-replication-mmm_init.md @@ -0,0 +1,624 @@ +--- +title: MySQL 高可用 MMM +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,高可用,mmm +description: MySQL-MMM (Master-Master Replication Manager for MySQL) 是 Google 的一个开源项目,用来监控 MySQL 双主+多读架构,并在失败时完成自动切换,不过该方案不适用对数据一致性要求很高的业务。其原理是将真实数据库节点的 IP 映射为虚拟 IP 地址,包括了一个专用于写的 IP,多个用于读的 IP 。接下来,看看详细的实现原理。 +--- + +MySQL-MMM (Master-Master Replication Manager for MySQL) 是 Google 的一个开源项目,用来监控 MySQL 双主+多读架构,并在失败时完成自动切换,不过该方案不适用对数据一致性要求很高的业务。 + +原理是将真实节点的 IP 映射为虚拟 IP 地址,包括了一个专用于写的 IP,多个用于读的 IP 。 + +接下来,看看详细的实现原理。 + + + +## 简介 + +MMM 适用于如下的两种场景,一种是双主,另外一种是双主+多个只读备库。 +  +![mmm]({{ site.url }}/images/databases/mysql/mmm-sample-setup-1.png "mmm"){: .pull-center } + +![mmm]({{ site.url }}/images/databases/mysql/mmm-sample-setup-2.png "mmm"){: .pull-center } + +简单来说,MMM 主要是为了保证系统的高可用,而非数据一致性,如下是其优缺点: + +##### 优点: + +1. 自动完成切换,包括了主主Failover切换; +2. 多个Slave读的负载均衡; +3. 自动的VIP切换,通过ARP广播发送,这也就意味着需要在同一个网段中,否则需要用到虚拟路由技术; +4. 支持抖动检测,防止服务器状态频繁切换; + +##### 缺点: + +1. 无法完全保证数据的一致性,备库落后时执行切换会导致数据丢失; +2. 无论何时可以保证只有一个可写(通过readonly设置),但切换时原链接没有直接断开,可能会导致不一致性; +3. monitor单点问题; + +MySQL-MMM中 有三种比较核心的概念:节点状态(States)、角色(Roles)、模式(Modes),如下简单介绍下: + +### 相关脚本 + +MySQL-MMM的主要功能通过以下三个脚本来实现: +mmm_mond:监控进程,监控所有服务器,决定节点角色。 +mmm_agentd:代理进程,运行在每台MySQL服务器上,为监控节点提供远程执行;TODODO: 是否支持双向通讯。 +mmm_control:为mmm_mond提供管理命令,常见的有状态检查,控制节点上下线; + + +### 节点状态 + +也就是MySQL服务相关的状态,实际上可以通过如下状态判断是否需要发送报警,总共有六种,如下: +1. ONLINE 正常运行状态,只有该状态才会有角色信息,切换之后角色将移除; +2. ADMIN_OFFLINE 人工设置为离线状态,一般为手动运维操作,如更换内存; +3. HARD_OFFLINE 离线状态,一般为ping主机失败或者mysql连接失败; +4. AWAITING_RECOVERY 等待恢复,需要手动处理,一般是由于抖动(Flapping)或者没有设置自动恢复(TODODO:配置项是那个???) +5. REPLICATION_DELAY 复制延时过大,也就是rep_backlog检测失败,实际通过SHOW SLAVE STATUS检测Seconds_Behind_Master状态值; +6. REPLICATION_FAIL 复制线程没有运行,也就是rep_threads检测失败,实际通过SHOW SLAVE STATUS检测Slave_IO_Running或者Slave_SQL_Running是否为No状态。 + +其中状态转换在Monitor的主处理逻辑的_check_host_states()函数中检测。 + +FIXME: 状态切换比较复杂,远超文档中的介绍,后面再具体梳理吧 +当主机处在REPLICATION_DELAY或REPLICATION_FAIL状态,一旦恢复,将切换到ONLINE状态。除非抖动不稳定。 +主机在HARD_OFFLINE状态,如果所有的问题都解决了,那么将会切换到AWAITING_RECOVERY状态。如果它故障时间小于60s,并且它没有重启或者auto_set_online>0, +那么将会被自动切换到ONLINE状态,除非抖动不稳定。 +活动的主服务器复制延时或复制失败将不被视为一个问题。因此活动的主服务器状态不会被置于REPLICATION_DELAY或REPLICATION_FAIL。 +在节点被切换到ONLINE状态的60s内,如果复制延时或复制失败将会被忽略(默认时间为master-connect-retry值)。 +如果rep_backlog和rep_threads都检测失败,将会切换到REPLICATION_FAIL状态。 +A host that was in state REPLICATION_DELAY or REPLICATION_FAIL will be switched back to ONLINE if everything is OK again, unless it is flapping (see Flapping). +A host that was in state HARD_OFFLINE will be switched to AWAITING_RECOVERY if everything is OK again. If its downtime was shorter than 60 seconds and it wasn't rebooted or auto_set_online is > 0 it will be switched back to ONLINE automatically, unless it is flapping (see Flapping again). +Replication backlog or failure on the active master isn't considered to be a problem, so the active master will never be in state REPLICATION_DELAY or REPLICATION_FAIL. +Replication backlog or failure will be ignored on hosts whos peers got ONLINE less than 60 seconds ago (That's the default value of master-connect-retry). +If both checks rep_backlog and rep_threads fail, the state will change to REPLICATION_FAIL. + +### 检测方式 + +mmm_mond对每个主机检测4项决定是否OK +1. ping 主机是否存活 +2. mysql mysqld 进程是否存活 +3. rep_threads 复制线程是否运行,通过SHOW SLAVE STATUS检测Slave_IO_Running或者Slave_SQL_Running是否为No状态。 +4. rep_backlog 延时少、复制积压的日志很少,通过SHOW SLAVE STATUS检测Seconds_Behind_Master状态值。 + + +3、角色 + +exclusive角色:互斥角色只有一个ip,并且同一时间只能分配给一个主机。可以指定一个首选主机(preferred 存疑??),如果这个主机是ONLINE状态 + +,那么角色将被赋予到这个主机。注意:不能移动被分配到首选主机的角色,因为他们将立刻再次被移动回到它。 + +balanced角色:负载均衡角色可以有多个ip。没有一个主机可以比其他主机多出两个角色。 +TODODO: 难点,既保证可以快速回复,有需要尽量避免Flapping。 + +## 抖动检测 + +mmm_mond支持抖动检测,通常是在网络不稳定时,例如光纤老化;抖动检测主要是针对主机频繁在ONLINE和(HARD_OFFLINE|REPLICATION_FAIL|REPLICATION_DELAY)状态之间切换, + + +每次切换到ONLINE状态(auto_set_online或者 + +down的时间小于60s),将会导致角色的切换非常频繁。 + + +为了避免这种情况mmm_mond内建了flap检测,可以通过配置文件配置。 + + +如果一个主机在flap_duration时间内宕掉了flap_count次,则认为该主机处于flap状态,就不会自动被设置为ONLINE状态,此时主机将一直处于AWAITING_RECOVERY状态,直到手动设置为online (mmm_control set online host)。 + + + +如果auto_set_online>0,处于flapping的主机在flap_duration时间后将自动设置为ONLINE状态 + +TODODO: 业务高峰期,可能会导致频繁的复制延迟???加权限 + +???5、模式 + +active mode:Monitor将会自动的把角色从失败的主机上移除,并切换到其他主机上。 + +manual mode:Moniter会自动把负载均衡的角色分配给对应主机,但是不会自动的把角色从失败的主机上移除。可以通过move_role来手工移除。 + +wait mode:类似manual模式,但是当两个master都是online状态或者超过了wait_for_other_master的时间,将被切换为ACTIVE模式。 + +passive mode:在此模式下,monitor不会改变角色,不更新状态文件和发送任何信息给mmm agents。在此模式下可以使用set_ip来改变roles,但是这些改变在monitor切换到 + +ACTIVE或者MANUAL模式(set_active or set_manual)前都不会生效。在启动时检测到角色发生冲突将会进入被动模式。 + +## 参考 + +关于 MMM 的文档参考 [mysql-mmm.org](http://mysql-mmm.org/mysql-mmm.html) 中的介绍。 + +http://www.cnblogs.com/chenmh/p/5563778.html + +What's wrong with MMM + +https://www.xaprb.com/blog/2011/05/04/whats-wrong-with-mmm/ + +mmm_tools配置手册 + +http://linuxguest.blog.51cto.com/195664/608338/ + +MMM部署常见问题 + +http://www.codexiu.cn/linux/blog/18484/ + +ifconfig lo:0 127.0.1.1 netmask 255.255.255.0 up +ifconfig lo:0 down + +ifconfig lo:1  127.0.1.1  netmask 255.255.255.0 up +ifconfig lo:1  down + + +用户权限设置,包括了mmm_agent(本地客户端操作,可以设置为本地登陆)、mmm_monitor(远程监控操作,可以只指定监控IP): + +GRANT REPLICATION CLIENT ON *.* TO 'mmm_monitor'@'127.0.1.%' IDENTIFIED BY 'monitor'; +GRANT SUPER, REPLICATION CLIENT, PROCESS ON *.* TO 'mmm_agent'@'127.0.1.%' IDENTIFIED BY 'agent'; +GRANT REPLICATION SLAVE ON *.* to 'mysync'@'127.0.1.%' IDENTIFIED BY 'kidding'; + +MMM::Monitor::Monitor::init()  初始化 +  |-main() 主要的循环处理过程 +    |-MMM::Monitor::NetworkChecker::main()  启动一个线程监控ping_ips指定的IP列表 +    | ###WHILE###BEGIN + |-_process_check_results() +    |-_check_host_states() +    |-_process_commands() +    |-_distribute_roles() +    |-send_status_to_agents() +    | ###WHILE###END + + +mmm_agentd启动过程 +MMM::Agent::Agent::main() + |-create_listener() + | ###WHILE1###BEGIN + |-accept() + | ###WHILE2###BEGIN + |-handler_command() + | |-cmd_ping() PING命令,直接返回OK: Pinged! + | |-cmd_set_status() SET_STATUS命令,设置状态 + | | |-MMM::Agent::Helper::set_active_master() 如果是备库,则设置主库,该函数中会执行CHANGE MASTER TO + | |-cmd_get_agent_status() + | |-cmd_get_system_status() + | |-cmd_clear_bad_roles() + | + | ###WHILE2###END + | ###WHILE1###END +bin/agent/configure_ip 检查是否设置IP,如果没有设置,则设置并通过ARP通知其它服务器 +configure_ip +MMM::Agent::Helpers::Network::send_arp() + + +HA工具需求 +错误配置时(从其他机器复制配置文件过来)不会对现有环境造成影响; + + + +#### 配置文件 + +DB服务器配置文件mmm_agent.conf、mmm_common.conf;监控服务器的配置文件mmm_mon.conf、mmm_common.conf;其中所有节点的mmm_common.conf文件都是相同的。 + +# +active_master_role      writer     ###积极的master角色的标示,所有的db服务器都需要开启read_only参数,对于writer服务器监控代理会自动将read_only属性关闭。 + + +    cluster_interface       eth0      #####群集的网络接口 +    pid_path                /var/run/mysql-mmm/mmm_agentd.pid    ####pid路径 +    bin_path                /usr/libexec/mysql-mmm/              #####可执行文件路径 +    replication_user        repl           #######复制用户 +    replication_password    repl           #######复制用户密码 +    agent_user              mmm_agent      #######代理用户,用于更改只读操作 +    agent_password          mmm_agent      #######代理用户密码 + + +            ##########master1的host名 +    ip      192.168.137.10   #####master1的ip +    mode    master       ########角色属性,master代表是主 +    peer    backup       ########与master1对等的服务器的host名,也就是master2的服务器host名 + + +     ####和master的概念一样 +    ip      192.168.137.20 +    mode    master +    peer    master + + +      #####从库的host名,如果存在多个从库可以重复一样的配置 +    ip      192.168.137.30   ####从的ip +    mode    slave    #####slave的角色属性代表当前host是从 + + +   ####writer角色配置 +    hosts   master,backup   ####能进行写操作的服务器的host名,如果不想切换写操作这里可以只配置master,这样也可以避免因为网络延时而进行write的切换,但是一旦master出现故障那么当前的MMM就没有writer了只有对外的read操作。 +    ips     192.168.137.100  #####对外提供的写操作的虚拟IP +    mode    exclusive    #####exclusive代表只允许存在一个主,也就是只能提供一个写的IP + + +   #####read角色配置 +    hosts   backup,slave  ######对外提供读操作的服务器的host名,当然这里也可以把master加进来 +    ips     192.168.137.120,192.168.137.130,192.168.137.140  ###对外提供读操作的虚拟ip,这两个ip和host不是一一对应的,并且ips也hosts的数目也可以不相同,如果这样配置的话其中一个hosts会分配两个ip +    mode    balanced   ###balanced代表负载均衡 + + +include mmm_common.conf + +    ping_ips            192.168.137.10,192.168.137.20,192.168.137.30 # 被监控的db服务器的ip地址 + ping_interval       1 # 默认是1秒,也就是ping主机的监控时间间隔,详见MMM::Monitor::NetworkChecker +    auto_set_online     0 # 设置自动online的时间,默认是超过60s就将它设置为online,默认是60s,这里将其设为0就是立即online +    ip                  127.0.0.1 +    pid_path            /var/run/mysql-mmm/mmm_mond.pid +    bin_path            /usr/libexec/mysql-mmm +    status_path         /var/lib/mysql-mmm/mmm_mond.status  #####群集的状态文件,也就是执行mmm_control show操作的显示来源。 + +    # The kill_host_bin does not exist by default, though the monitor will +    # throw a warning about it missing.  See the section 5.10 "Kill Host +    # Functionality" in the PDF documentation. +    # +    # kill_host_bin     /usr/libexec/mysql-mmm/monitor/kill_host +    # + + +### binlog_mode和隔离级别 + +1. 查看当前数据库状态,包括了版本、当前数据库等。 +mysql> status + +2. 创建测试表 +mysql>CREATE TABLE foobar ( +  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, +  book CHAR(10) DEFAULT NULL +  ) ENGINE=InnoDB; +Query OK, 0 rows affected (0.03 sec) + +3. 测试RC隔离级别,使用STATEMENT +----- 改变事务级别为read committed +mysql>set session transaction isolation level read committed; +Query OK, 0 rows affected (0.00 sec) +----- 改变二进制日志格式 +mysql>set binlog_format=STATEMENT; +Query OK, 0 rows affected (0.00 sec) +----- 插入数据测试 +mysql>insert into foobar(book) values('wuli'); +ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT' + +4. 测试隔离级别RR,使用STATEMENT +mysql>set session transaction isolation level REPEATABLE READ; +Query OK, 0 rows affected (0.00 sec) +mysql>insert into foobar(book) values('wuli'); +Query OK, 1 row affected (0.00 sec) + +5. 测试隔离级别RC,使用非STATEMENT格式 +mysql>set session transaction isolation level read committed; +Query OK, 0 rows affected (0.00 sec) +mysql>set session binlog_format=row;   改为行格式 +Query OK, 0 rows affected (0.00 sec) +mysql>insert into foobar(book) values('wuli'); +Query OK, 1 row affected (0.00 sec) +mysql>set session binlog_format=mixed; 改为混合格式 +Query OK, 0 rows affected (0.00 sec) +mysql>insert into foobar(book) values('wuli'); +Query OK, 1 row affected (0.00 sec) + +那是因为read committed可能会导致不可重复读,也就是说可以读取到其它事务已经提交的数据,如果基于STATEMENT格式的话,会导致主从数据不一样,因为STATEMENT是基于SQL语句的复制模式。 + + + +### 网页PPT +https://mmonit.com/monit/ +https://vxhly.github.io/2016/09/03/reveal-js-cn-document/ + +### master_pos_wait + +如果将MySQL设置为双主 M1(rw)+M2(r),正常switchover时的切换流程大致如下: +1. 停止应用写M1,将M1设置为只读; +2. 检查M2的slave status直到赶上M1; +   2.1 在M1上show master status;得到binlog位置P,因为已经设为只读,不会变化; +   2.2 循环检测M2上的执行位置,若未到P,则过几秒再查,循环直到从库追上; +3. 将M1设置为可写。 + +实际上,对于第2步,可以通过select master_pos_wait(file, pos[, timeout])函数完成,这里的file和pos对应于主库show master status得到的值,该函数会等待当前从库达到这个位置后返回,返回参数为这期间执行的事务数;超时时间可选,默认是无限等待(参数小于0)。 + +返回值:-1)等待超时;NULL) 当前slave未启动或在等待期间被终止;0) 指定的值已经在之前达到。 + +master_pos_wait的实现逻辑 + +    用户调用该函数后,根据传入参数调用pthread_cond_timedwait或pthread_cond_wait。 SQL_THREAD线程每次apply完一个事件后会触发更新relay info, 并通知上面等待的线程。因为可能有多个用户等待,因此用广播方式。 + +    关于事件个数的计算比较复杂,有兴趣的同学可以看这篇, 不过在本文讨论的这个问题上,正数返回值并不重要。 + +用master_pos_wait来实现上面的步骤b,则可以简化为: +b’) 在M上执行select master_pos_wait(file, pos),返回后判断一下返回值>=0 则认为主从同步完成。 + + 好处是 +1) 简化逻辑,不用在应用脚本判断 +2) 在追上的第一时间就能感知,否则可能多等若干秒 + +#### syslog +/etc/sysconfig/syslog +/etc/syslog-ng/syslog-ng.conf + +syslog系统默认会存在两个守护进程klogd+syslog-ng(syslog-Next generation),前者用于记录内核生成的日志,后者是用于非内核信息。 + + +### syslog-ng +http://cyr520.blog.51cto.com/714067/1245650 +http://ckl893.blog.51cto.com/8827818/1789967 +提供了Client、Relay、Server三种模式,对于Client来说,配置文件中包括了全局配置、消息源、过滤器、消息目的地和日志路径。 + +# Global options. +options { long_hostnames(off); sync(0); perm(0640); stats(3600); }; + +# 'src' is our main source definition. you can add more sources driver +# definitions to it, or define your own sources, i.e.: +#source my_src { .... }; +source src { +    # include internal syslog-ng messages +    # note: the internal() soure is required! +    internal(); +    # the default log socket for local logging: +    unix-dgram("/dev/log"); +    # uncomment to process log messages from network: +    #udp(ip("0.0.0.0") port(514)); +}; + +source my_src { +    udp(ip("127.0.0.1") port(514)); +}; +filter f_local2 { facility(local2); }; +destination d_haproxy { file("/var/log/haproxy.log", owner(elb), group(wheel), perm(0600)); }; +log { source(my_src); filter(f_local2); destination(d_haproxy); }; + + +#### 密码存储 +http://www.yunweipai.com/archives/4518.html +http://www.jianshu.com/p/d13ff016ff88 +https://www.zhihu.com/question/20479856 +http://www.williamlong.info/archives/3224.html +http://www.freebuf.com/articles/web/28527.html +http://blog.csdn.net/shanliangliuxing/article/details/7365920 + + +### Perl DBD::mysql 安装 + +安装perl-DBD-MySQL时,会出现错误提示依赖错误(error: Failed dependencies) libmysqlclient.so.18()(64bit) is needed by perl-DBD-MySQL-4.023-5.el7.x86_64 ,但是我们 mysql-community-libs 提供的是 libmysqlclient.so.20 动态库。当然,我们可以直接下载源码然后安装,不过也可以通过 mysql-community-libs-compat 安装,libmysqlclient.so.18 + +Percona-toolkits环境安装 +1. 安装DBD::mysql模块 +源码从https://dev.mysql.com/downloads/dbi.html下载, + +perl Makefile.PL --mysql_config=/bin/mysql_config +make +make test +make install + +#!/usr/bin/perl +use DBI; +$user="test"; +$passwd="test"; +$dbh=""; +$dbh = DBI->connect("dbi:mysql:database=test;host=192.1.1.168;port=3306",$user,$passwd) or die "can't connect to +database ". DBI-errstr; +$sth=$dbh->prepare("select * from infobright_loaddata_status"); +$sth->execute; +while (@recs=$sth->fetchrow_array) { +print $recs[0].":".$recs[1].":".$recs[2]."\n"; +} +$dbh->disconnect; + + +### Linux C 动态库依赖 + +http://littlewhite.us/archives/301 +http://bbs.chinaunix.net/thread-4130723-1-1.html + + +https://github.com/KredytyChwilowki/MySQLReplicaIntegrityCheck +https://www.percona.com/blog/2016/03/17/mysql-replication-primer-with-pt-table-checksum-pt-table-sync-part-2/ +https://segmentfault.com/a/1190000004309169 +http://www.cnblogs.com/zhoujinyi/archive/2013/05/09/3067045.html + +保证数据一致性,常用的如数据校验、数据一致性备份。 + +建议通过pt-table-checksum完成数据校验,然后通过pt-table-sync进行数据修复,原因是前者会提供很好的限流措施,可以尽量减小对现网的影响。 + +### 使用参数 + +直接从源码中截取。 + +``` +if DSN has a t part, sync only that table: +   if 1 DSN: +      if --sync-to-master: +         The DSN is a slave.  Connect to its master and sync. +   if more than 1 DSN: +      The first DSN is the source.  Sync each DSN in turn. +else if --replicate: +   if --sync-to-master: +      The DSN is a slave.  Connect to its master, find records +      of differences, and fix. +   else: +      The DSN is the master.  Find slaves and connect to each, +      find records of differences, and fix. +else: +   if only 1 DSN and --sync-to-master: +      The DSN is a slave.  Connect to its master, find tables and +      filter with --databases etc, and sync each table to the master. +   else: +      find tables, filtering with --databases etc, and sync each +      DSN to the first. +``` + +简单来说,如果使用了--replicate或者--sync-to-master可以只使用一个DSN配置,否则就需要配置两个DSN值。 + +#### 常用参数介绍如下 + +* --sync-to-master    +将当前库视为备库,需要去同步主库数据,此时会修改--wait=60 --lock=1 --notransaction ; +* --replicate    +与pt-table-checksum工具的对应参数相同,该工具会通过WHERE过滤检查数据,并与主库同步数据,如果没有通过--sync-to-master参数指定备库,那么就会尝试通过SHOW PROCESSLIST、SHOW SLAVE HOSTS查找备库; +* --wait    +主库等待备库多长时间追上主库,如果超时则输出MASTER_POS_WAIT returned -1,此时可以尝试增加时间,默认会同时设置--lock=1 --notransaction; +* --timeout-ok    +默认主库等待备库超时后会直接退出,通过该选项可以使程序继续执行,如果要保证一致性的比较最好退出!!! +* --[no]check-triggers   +检测目标表上是否定义了trigger,如果定义默认会直接退出; +* --execute    +如果有数据不一致可以直接执行,如果担心软件有问题,可以通过--print打印SQL而非执行; +* --algorithms    +数据校验时采用的算法,目前支持chunk、nibble、groupby、stream四种算法; + +调试参数: +* --print    +主要用于防止工具执行错误,此时不会真正执行表同步操作,只打印需要执行的SQL,用于手动执行; +* --dry-run    +用于调试,会同时设置--verbose选项,此时工具并不会访问表,所以也无从知道哪些表出现了不一致,只用于显示会执行哪些操作; +* --explain-hosts    +只打印链接各个数据库的参数信息,然后退出; + + +### 数据校验算法 + +数据校验算法目前包括了Chunk、Nibble、GroupBy、Stream四种算法,当然上述算法各有优缺点,在源码中通过插件实现,也可以新增新插件实现;在此,主要介绍前两种方式: +* Chunk开始会将表分给为chunks,适用于区分度高的表; +* Nibble与Chunk类似,不过分区是通过limit实现; + +### 执行流程 + + +在此,主要介绍指定--replicat参数时,该工具的详细执行流程: + +1. 从校验表中找到不一致的chunk,包括其边界,详见find_replication_differences()函数,然后一般是每行开始循环校验; +2. 对主库使用FOR UPDATE对chunk加锁,从库使用LOCK IN SHARE MODE,正常来说在主库添加了悲观锁之后从库的数据就不会被修改; +3. 上步同时会通过SELECT ... CRC32(CONCAT_WS('#', ...))生成校验码,这里实际上只使用主键,如果数据不一致后续统一获取各行的值; +4. 如果上述有异常,则将数据保存到队列中,后续统一处理; +5. 根据不同的类型,会生成REPLACE、INSERT、DELETE等语句; +6. 通过lock_and_wait()等待主从同步完成,然后开始修复数据,每次会处理一行记录,并最后等待同步完成。 + + +### pt-table-sync + +perldoc /bin/pt-table-sync + +pt-table-sync --print --algorithms=chunk --charset=utf8 --replicate=zabbix.checksums h=localhost,u=checker,p=Opsmonitordb@2015 h=192.30.19.82,u=checker,p=Opsmonitordb@2015 + +PTDEBUG=1 + +PTDEBUG=1 pt-table-sync --print --execute --algorithms=chunk --charset=utf8 --replicate=zabbix.checksums h=localhost,u=checker,p=Opsmonitordb@2015 h=192.30.19.82,u=checker,p=Opsmonitordb@2015 >/tmp/1 2>&1 + +### 登陆选项: +可以通过DSN指定,也可以使用参数。 +--ask-pass +--password +--port +--host +--user +--socket +--slave-user +--slave-password + +双向修复相关: +--bidirectional +--conflict-column +--conflict-comparison +--conflict-error +--conflict-threshold +--conflict-value + +* --[no]bin-log    +是否记录到binlog中,如果采用GTID,那么在备库修复会存在问题; + + +### 过滤选项 + +可以指定只检查哪些数据库、表、列、存储引擎等,可以使用Perl正则表达式,也可以指定忽略哪些。 + +--ignore-columns +--ignore-databases +--ignore-engines +--ignore-tables +--ignore-tables-regex +=item --columns +=item --databases +=item --tables +=item --engines +#### 安全检查 + +用于检查是否可以进行同步,详细可以查看ok_to_sync()。 + +--[no]check-child-tables +--[no]check-master +--[no]check-slave +--[no]check-triggers +--[no]foreign-key-checks + + +=item --buffer-in-mysql +=item --[no]buffer-to-client +=item --charset +=item --chunk-column +=item --chunk-index +=item --chunk-size +=item --config +=item --defaults-file +=item --float-precision +=item --function +=item --[no]hex-blob +=item --[no]index-hint +=item --lock +=item --lock-and-rename +=item --pid +=item --recursion-method +=item --replace +=item --set-vars +=item --timeout-ok +=item --[no]transaction +=item --trim +=item --[no]unique-checks +=item --verbose +=item --version +=item --[no]version-check +=item --wait +=item --where +=item --[no]zero-chunk + +GRANT ALL ON *.* TO 'checker'@'localhost'; +GRANT ALL ON *.* TO 'checker'@'192.30.19.83'; + +main() + |-lock_and_rename() 如果使用了--lock-and-rename参数 + |-sync_one_table() 只同步一个表,可通过--tables指定需要同步那些表 + |-sync_via_replication() 指定了--replicate参数,重点查看 + | | ###BEGIN###使用sync-to-master参数,也就是在备库执行 + | |-find_replication_differences() 校验主库是否由不一致,如果有那么直接忽略对应表 + | |-find_replication_differences() 查看备库中不一致的表 + | |-filter_diffs() 过滤不需要同步的表 + | |-lock_server() 只有在lock=3时才有效,会执行FLUSH TABLES WITH READ LOCK命令 + | |-sync_a_table() 一个表一个表同步数据 + | | |-get_server_time() 获取开始时间 + | | |-ok_to_sync() 检测是否可以同步表:1)获取表结构;2)确认表在目的库存在;3)确认目的库没有定义trigger;4)如果需要执行,确认目的库没有外键子表 + | | |-diff_where() 获取WHERE子句 + | | |-get_change_dbh() 获取需要执行修改操作的链接信息 + | | |-make_action_subs() 根据execute/print参数将设置回调actions函数 + | | |-sync_table() 真正执行数据修复的函数 + | | | |-get_best_plugin() 获取最适用的插件 + | | | | |-can_sync() 遍历各个插件,获取第一个可以使用的插件 + | | | |   |-find_chunk_columns() Chunk算法 + | | | |-prepare_to_sync() + | | | |-lock_and_wait() 处理表锁:1) COMMIT/UNLOCK TABLES;2) 开启事务或者锁表(lock=3);3) 通过MASTER_POS_WAIT()等待备库完成 + | | | | |-wait_for_master() 等待主库 + | | | |-###BEGIN###while 等待$plugin->done()完成 + | | | |-lock_and_wait() 只有在一个chunk的开始和结束会执行 + | | | |-execute() 在主库、备库分别执行 + | | | |-compare_sets() 比较结果集如果正常则会打印Left and right have the same key,如果有异常则会保存在队列中,一般值含主键 + | | | |-process_rows() 开始执行修复 + | | | | |-make_$action() 执行修改,一般为make_REPLACE... + | | | | | |-make_row() 获取SQL + | | | | |-_take_action() 开始执行 + | | | |-###END###while + | | |-get_server_time() 获取结束时间 + | |-unlock_server() + | | ###END###sync-to-master + |-sync_all() + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-08-01-wealth-financial-independence_init.md b/_drafts/2015-08-01-wealth-financial-independence_init.md new file mode 100644 index 0000000..9ff13c2 --- /dev/null +++ b/_drafts/2015-08-01-wealth-financial-independence_init.md @@ -0,0 +1,228 @@ +--- +title: 如何实现财务自由? +layout: post +comments: true +language: chinese +category: [misc] +keywords: 财务自由, financial independence +description: +--- + + + + + +现金流量表 (赚钱和花钱)、利润表 (结余)、资产负债表 (合理借钱买资产) 。 + +{% highlight text %} +>>>>>>>>>>>>>>>>>>> 现金流表(月) <<<<<<<<<<<<<<<<<<<<< +税后收入 20000.00 +房租 1300.00 +餐饮 1000.00 +购物 300.00 +杂项 0.00 + +支出合计 2600.00 +结余 17400.00 + +>>>>>>>>>>>>>>>>>> 资产负债表 <<<<<<<<<<<<<<<<<<<< +=================== 资产 ===================== +1. 流动资产 + A. 现金 + 支付宝 23000.00 + 工商银行 320000.00 + 招商银行 155000.00 + 京东金融 33000.00 + 公积金 213000.00 + 借出 130000.00 (梅元刚) + 贝米钱包 370000.00 + 54000.00 + B. 信用卡 + C. 活期存款 + D. 货币基金 + E. 其它 + ==> 合计 1244000.00 + +2. 长期投资 + A. 定期存款 + B. 国债 + C. 股票 + D. 其它 + ==> 合计 + +3. 固定资产 + A. 房产 + B. 其它 + ==> 合计 + +4. 其它资产 + A. 汽车 + ==> 合计 + + =====> 总资产 + +=================== 负债 ===================== +1. 短期负债 + A. 信用卡 + B. 消费贷款 + C. 其它 + ==> 合计 +2. 长期贷款 + A. 房屋贷款 + B. 汽车贷款 + ==> 合计 + + =====> 总负债 +{% endhighlight %} + + + + +## 定义 + +首先要定义什么是财务自由,不同的人会有不同的定义,例如:非工资收入大于支出; + +这里的很多概念是从 罗伯特·清崎 先生的 《穷爸爸富爸爸》系列丛书中摘取的,鄙人 + + + + + + +一般收入来自于两部分:A) 工作收入、年终年金等;一部分是投资收入,进行金融投资或实业投资的投资所得。 + +大部分人每月支出主要来自工作收入,没工作收入生活陷入困境。但如果投资收入够多,不用工作就可过好日子,随时可以退休或可以睡觉睡到然醒,就达到财务自由的境界了。当达到财务自由时,投资收入将成为个人收人的主要来源,不再为赚取生活费用而工作。 + +理财的最终目标是追求财务自由,财务自由的定义是:不靠工作收入,靠理财收入就能过生活。要求每年的理财收入大于生活支出,这对没工作的退休人士特别重要,理财规划师也特别对即将退休的人在财务上有这样的要求。 + +要达到财务自由是逐步完成的,中产阶级人士透过股票、债券、保险等投资获取现金流。 + +比如你生活费用一个月要一万元,一年要12万元,这就是你的年生活支出。你现在有第一个100万,放在理财型保险,每年生存金加分红有4%收益率,一年有4万元,这是不够生活支出的。 + +再存了第二个100万,放在高评等债券投资,每年利率6%,一年就有6万元,跟保险收益加起来10万元,还是达不到12万元的财务自由门坎。 + +再努力储蓄50万,放在蓝筹股,每年发放现金股利8%,一年就有4万元,跟保险债券收益加起来14万元,就达到财务自由。 + + + +| 投资工具 | 年收益率 | 数量 | +| -------- | -----: | :----: | +| 理财型保险 | $1 | 5 | +| | $1 | 6 | +| | $1 | 7 | + + + + +股票 +债券 +保险 + + + + + + + + + + + + + + + + + + + +### 第一步,生活成本 + + + +其实按我个人标准:当一个人的非工资收入≥总支出,他并 没有 达到财务自由。因为我已经实现这一步了,但我并没感到我在财务上有多自由。《富爸爸》系列作者罗伯特·清崎先生说过:“储蓄全都是输家的策略,你要尽快地实现财务自由。”我们更关心的是如何实现?我给出我早年的计划——理财七步走(走到第七步,就是实现了财务自由的目标。本人尚在第五、六步间)步骤一(财务自知阶段)列出你的每月支出(不能说出,要列出,写在纸上,或者在电脑上做个Excel表格。)以我本人婚前单身时候的情况为例(写现在的也行,不过那就长了去了): + +1、衣:一般三个月最多买一大件一小件,算下来摊到一个月100元就足够了;(这块单身男士比较省,有女生之后被迫骚包起来,这块就......(┬_┬)......都是泪) +2、吃:三餐加零食(有抽烟的可以放一起,本人不抽)每日花费是40元,一个月就是1200元; +3、住:房租和舍友一摊每月400元; +4、行:当时还是摩托族,每月油费100元,顶天了。 +5、杂项:应酬,一般控制在500元以内,再加上其它如电话费、水费、电费……200吧。那么,总计我的生活费用就是2500元人民币一个月。(我这是大概啊,纯粹为了回答问题,照记忆简单的列列,当时可是做了几个月的明细,精确到角)各位真想对自个财务负责,还请按自个现状列个详单,精确到角。(别说没角,淘宝花钱,还是有的)步骤二(财务生存阶段)存好应急钱应急钱要纯多少?要存半年的生活费。(最少4个月。不能再少了)我当时一个月生活费2500元。半年的生活费就是2500 X 6 =15000元。这1.5万直接存银行定期,能不动绝对不动,能动也咬牙不动。 + +(PS:这笔钱是我一工作就开始分批存的,所以网银上都是一长溜:1000的3个月定期、2000的一年定期、5000的六个月定期、1000的一年定期,......这样分批存,而且期限、数额不同,还有一个考虑是当时想实在要动这些钱,也可以不用全动,可以挑一些刚好到期限的小额的钱......当时,我真是好可爱~(≧▽≦)/~啦啦啦)为什么要存应急钱? +给你安全感。当时在外漂泊,若一时失业、碰上大病、有其他事故都很正常(你不在外漂泊,碰上这些也很正常)。万一发生上述情况,我有半年生活费,我至少可以不必每天为了吃饭而发愁,一有事就卷铺盖回家。让我有6个月的时间给我翻身,这份安全感很重要,真的。我个人用这笔钱,让我从从容容的换了3次更好的工作(各位知友,你离开一个老单位,他卡你2个月工资,这种情况,熟悉吧?)。卡呗,就知道你会卡,卡我我也要走。(在此,感激一下曾经的一个老大——阿山经理,不但不卡,还多发了我半个月工资。)有人会问,现在银行定期的利息都不高,我存余额宝行不?这个,其实我自己也已经忘记“银行定期存款”这种东西了............这笔钱的特点有两个:1、是安全,绝对的安全。2、是尽量不动(定期)。 +余额宝,不是说他不安全,就怕你那天淘宝支付宝钱不够了....行吧,想存哪就存哪吧。反正你给我放在一个安全稳定的地方。当你存下了半年的应急钱,我们再开始向更高的阶段迈进。步骤三:下班要准时回家,(当然不是这个,不过真要回家了,待续......) +________________________________________________________________________________继续。在说步骤三前,多说一句,存下步骤二的应急钱,这一步很重要,这一步没完成前,所有投资、理财都是奢谈,更不用说花钱、享受。还有随着你的情况改变,每月花费增多,这份半年的应急钱也要随之增加。如果减少呢?不减!(没办法,我就是这么简单粗暴明了)步骤三:(财务保障阶段)存下自己理财保障金。哥们,你TM在逗我吗?你前面明明说了“储蓄全都是输家的策略”,你咋还让我存钱????好吧,哥们你骂得对,我也发现我有点......请无情的折叠我吧......不过还是先请你存下你的财务保障金:你一年生活费。加上步骤二:半年生活费的应急钱。就是你要存下18个月的生活费。继续上面的例子每月2500。18个月就是4.5万元。这里有本人血的教训,回忆起来眼泪哗啦啦的流啊/(ㄒoㄒ)/~~/(ㄒoㄒ)/~~/(ㄒoㄒ)/~~本人青春年少时,刚步入社会,工作开始其实只用的不到1年的时间就完成了步骤二,存下了半年的生活费。然后春风他就得意了,意气他也风发了。开始理财吧!开始投资吧!股票、期货咱也学学吧,兄弟要开个店,咱也入个股吧.......然后......就重复步骤二吧......(┬_┬)(这里我犯了个严重的错误,我拿了应急钱进行投资)你会说,那是你没经验,我们都老江湖了,不会亏!你老江湖了,咋财务还不自由,咋还来看这个问题?财务自由和你会不会赚钱、会不会亏钱,没啥关系。更多的是,你收入和花销比。你月入10W,全花完了,到月底还要找别人借钱......让我想起了咬耳哥拳王泰森,这哥们曾经的收入超过80%知友吧(好吧别笑,90%......92%......95%.....随便几了)他财务自由了吗?莫说自由,有我们现在谈的步骤三财务保障吗?其实泰森(当时的)连步骤一 财务自知 都没完成。所以先继续老老实实的存钱,使劲存,我后来有几个月只花600,其他都存了,我舍友都不落忍了,说:兄弟,你再这样下去,可别搬走了啊,我一人可租不起这800一月的房,我这有几包没用完的方便面调料包拿去拌个汤吧,别噎着....这18个月的生活费,也就是财务保障金也请存在银行里,不要用,它不是给你投资的。它能给你的是更大的安全感。在任何的投资失误情况下,它可以让你很容易的重新开始,可以很自豪的说:老子以后再喝方便面汤,也要放火腿肠啦! +又或者你失业了、打球骨折上不了班了……至少可以安全、平稳地生存18个月。你可以在一年半的时间里,去整理你的思绪,规划和选择你今后想要做的事、喜欢做的事、适合做的事。有了这一年半的缓冲期,就不会乱了方寸,坏了阵脚。难道说只存钱,不过日子了?去捡别人的方便面调料包才是对的?当然不是,我那是对自己初次投资失败的惩罚。不要变成一个守财奴。我们要做的是:在保证自己不降低目前生活水准的前提下理财(当然,你是个月光欠费族,该消减开资,还是要减)。你原来每月花2500,在出现失误、事故的情况下还能每月花2500。所以要完成步骤二、三真的只要存钱就够了:定期存钱。每月存下收入的20%(好吧降到10%,再少就都花了吧,别理财了),应该不是难事。不行,就办个定存,工资一到账上,银行自动帮你扣掉一部分钱存定期的那种。在存下自己理财保障金前,请保证你的每月花费正常,保证在2500左右浮动,我允许你这个月份子钱多了,菜价涨了....这都正常。但是别突然说:老子要买个车、我看上个LV包包了......好吧,你工作需要,你就买吧,你能不动理财保障金的钱,随便买。其实我想说的是作为一个有存钱习惯的中国人,大多可以轻松完成步骤二。在老一辈人里面,完成步骤三也很正常。但是,像我当时26岁前就能完成步骤三的(非富二代)年轻人,(就不谦虚,你能把我怎么滴)应该不到一半。<( ̄▽ ̄)> 哇哈哈…完成步骤三换工作和完成步骤二换工作,是不一样的。完成二,你可以和原单位硬气:老子不干了。完成三:你就可以和应聘单位硬气,你完全可以对应骋单位挑肥捡瘦,开出你的条件和价码。钱有时候真的是你的底气,银行里有多少钱,你表现出来的气场真的不同,面试官看的出来,相信我。 步骤三完——待续,补充一句:理财的首要目标是达到拥有足够维持闲居生活的资产,并非成为大款。 _______________________________________________________________________________我们继续,上面排名第一的答案说:当一个人的非工资收入≥总支出,则该者即便达到了财务自由。其实在我的计划里,这只完成了我的第四步——步骤四:(财务自足阶段)使自己的非工资收入≥总支出。达到财务自足很难吗? +说个我兄弟25岁就达到财务自足的故事: +我有个兄弟大学一毕业,23岁就和在学校勾搭的妹子结婚了。两个人在一个二线城市,有点混不下去,工作不好找,苦闷窝在租来的屋子里打WOW,非常难得的是他家妹子没有嫌弃他,而是和他一起窝在家里打WOW,组队打。就这样,他俩靠着家里的接济,和时不时打点零工,混到了24岁。也算省吃俭用,两个人每月开支2000元,还要给WOW充点卡。 +终于,这哥们想,不能继续这么下去了,总要解决生存问题,努力吧!于是,在他24岁末的最后一个月,他们双方父母,合资给他们在所在城市买了套140平的大房子,一次付清。他俩,把这套房子每月3500元租了出去,自己仍旧租原来住的单间,夫妻俩继续窝着打WOW,零工都不打了。于是我这兄弟在25岁的时候,财务自足了。是的,这不是我兄弟的故事,不过这的确是对达到财务自足阶段的夫妻俩,只要他们不在WOW乱买装备。忘了上面的故事吧,看看我们自力更生的大多数。完成步骤三后,可以接触些稳定型的理财产品了。不过,还是想先用最安全的银行利率来做说明:这是今天从网上截图的国有银行存款利率。这是今天从网上截图的国有银行存款利率。 +请看第一项,活期年利率0.35。 +下面请做数学题,你要在银行存多少钱活期,才能靠吃利息,保证我上面的每月2500元开销???? 求?解得= 8571428 . 571428571 元就是说你要在银行存857万元人民币的活期(当然这钱你是不能动的),就算你不去工作,银行也可以每月给你2500元花,你这个月花完了,下个月继续给你2500.虽然这个数字有点坑爹,但是的确,你的钱已经在帮你生钱养你了。好吧,857万是个坑爹的数字,所以我们前面才说“储蓄全都是输家的策略”利率太低了。换个柔和点的,来看看现在大家熟悉的余额宝。这是昨天的余额宝年利率。我们重复上面的公式: 求?解得= 560014.9337315662 元好了56万,大家轻松多了吧,存个56万不是难事吧。其实还有收益更高一些,且相对稳定的理财产品,为了不做打广告之嫌,就不在这说了。 +所以我说使自己的非工资收入≥总支出并不是我最终的财务自由。因为我已经完成了,没啥难的。至于要如何存入56万元,那是你的事,我这不是在教你怎么赚钱,只要你不违法就行。你可以靠工资收入(这个比较慢)、做生意、低风险的投资(别动步骤二、三的钱)、像上面夫妻俩靠父母给......说财务自足并不是财务自由,是因为这份收入并不稳定。比如上面夫妻俩,突然有孩子了,一个月2000不够花了,那他们就不能靠父母给的房子继续WOW了,趁早告别部落和联盟吧。物价会涨、利率会调(余额宝利率都连续跌多久了不是?),还有那该死的通货膨胀!!!!!!这些都要求你不断追加本金。而且56W?买俩车吧?你就呵呵吧。虽然达到财务自足阶段,你已经可以比很多人活的轻松,活的舒服,甚至可以不用找工作,老主顾、新主顾的脸都不用看了。但是请不要满足于财务自足阶段,让我们向更高级的步骤五迈进。——待续,一样给个待续小结:财务自足只能让你活的很安全,活的很有保障,并不能让你活的滋润和精彩。所以这阶段钱(就是那56W)的去处,请放在稳妥型的地方(货币基金之类),千万不要放到股市、期货这些高风险的地方。(你拿固定的一点钱玩玩,也不反对)——————————————————————————————————————————— +天亮了,继续。财务保障到财务自足过程,也是个不断存钱的过程。中间你当然可以把每月余钱,做以下安排:1、全部存下;2、一部分雷打不动的存下,一部分进行一些其他高回报的投资,(投资都有风险,都要自己把握和承担)后果也无外乎两种: +a.投资获得收益,加速你完成步骤四(本来要五年,现在两年就完成了);#^_^#奇怪,我干嘛要脸红... +b.投资失败,推迟实现财务自足(现在要用八年了)。上面两种方式,你自己按个人能力选择。但是绝对不要选方案 3、全部用来投资。我们现在进入更高的一个阶段步骤五:(财务舒适阶段)使自己的非工资收入≥总支出的120%上面说了,完成了财务自足,你现在的余额宝里有了56万 ,但并不能让你活的滋润、精彩。因为不确定因素实在太多了,比如说父母生病了、二胎普及,太太又有小宝宝了-_-|||、投资出现重大失误、菜价涨价了、余额宝利率降到4.5了……于是我们希望再好上加好。上面算过,我们余额宝里有56万元,按5.357的年利率,每月能生出2500元利息,每年就是3万元。那么比3万元再多出20% ,就是3.6万元。那这时候我要存多少,才能生出这3.6万元?56直接×1.2呗,是......恩......等下......我找下计算器......啊.....是67.2万元!好吧,完成了上面步骤四财务自足,我还一直说,注意消费、别把钱乱投资、买车什么的要考虑好......我好烦人啊.....我都烦我自个....到了现在财务舒适阶段,我们能做什么?(当然还是保证维持原来每月2500生活水平的基础上) +我们已经可以在不用出去工作的情况下(理论上是这样,但天天玩其实是个很痛苦的事,真的!反正我们已经可以说我们是为了精神富足、自我提升而工作,而不是完全为了钱工作),还能够维持你基本的生活,而且,还有多余出来的20%的钱,用于比原先生活水平高一些的花费、享受,以及用这多出来20%的钱进行一些风险高一点的投资。从步骤一到步骤五,我们其实并没有用到什么太过高深的财务技巧,方法只有一个:就是——存钱!持续且安全的存钱!…… 然后嘞?就此止步?心安理得的窝着WOW?不想继续一窥我们最初目标“财务自由”的境界?——待续 ,不小结了。 +———————————————————————————————————————————清明扫墓归来,继续。因为本人目前只完成步骤五,所以六、七还是目标,不敢说太多。步骤六:(财务解锁阶段)使自己的非工资收入≥总支出的10倍先说说,本人对达到这个的阶段后的YY: +每日都可以不做讨厌事,不看恶心脸;成天事少钱多离家近,权重位高责任轻;还要睡到自然醒,吃成水桶腰……...(好恐怖)好像某个电视剧里有个富二代说过:“这不是我想要的日子!”(画外音(#‵′)凸:“这TM是我们想要的日子!”)放心吧,只要你是靠自己努力达到这个阶段的,工作绝对会成为你戒不掉的一个习惯(不管是打工,还是创业,又或是自由职业)。先看数字:不用公式了,直接把步骤四的钱x10就好。就是说你在余额宝里有560万人民币就达到这个目标了,你每月一天活不用干,照样有两万五给你花,花完下个月还有。但是要达到这个阶段,还是像前面几个步骤一样,光靠每月存下 百分之几 的死工资,这个.......也行.....就是时间有一丢丢的长.....开始学习和接触一些投资工具吧,(原则不能破:1、完成步骤三前不进行高风险投资;2、步骤四、五阶段可以适当投资;3、不要动步骤二到五里的本金。)可以选择工具有很多,回报和风险不同。因为我不在这个阶段,还没资格推荐。要自己去分析和学习,也可以请教专家(但是先要认清他是不是真的专家)。其实,达到财务舒适阶段之后(就是有67万之后),我们已经可以用每月多处的20%的钱,承受较高的风险了。最差情况,也不过是这些钱全部亏掉。只要本金67万,不动,我们就不会降低生活水平。笨点如我,交了半年几个月的学费,持续总结失败经验,找到适合自己的投资策略,坚定执行,你也不会一直亏(真一直亏,继续都存了吧)。投资方面,不敢做经验介绍。消费方面,倒可以说一点:大额消费(车房之类),永远不要一次付清,请你贷款或办分期。 本金,随便放个定投工具里(利息高过分期利息的工具有不少,不知道学习去),分期利息省掉不说,还能多赚几个点的利差。还有就是和不同层次、三教五流的朋友交往与交流的必要性,不是说只和高层次的,真是什么人(线下,不是网上)都要玩。虽然现在网络发达,但再发达......昨晚我几个朋友一起吃饭,一个兄弟捞到我们城市下属县的一个项目,分我们几块肉吃。这个在自己身边,稳赚不赔的短期投资小项目,网上搜死都搜不到。就算搜到也没用,你不可能大老远从京城来我们二、三线的小地方来入个股,来了也不给。再就是,外地的朋友,偶尔来玩,也会不经意地介绍一些信息。很多信息,其实每个人都能用,但很多人都没在意。比如:去年之前,是可以用支付宝给信用卡套现的。我以前用这一点,每月小收入几百到一千+,现在没了。还有就是要低调,不要臭屁(好吧,我前面说的都是骗人的,我可穷了)。因为我们在学会赚钱,收入提高的同时,还要用极大的耐心来控制欲望,继续过原先每月2500的生活。开源节流,确保我们的支出低于我的资产收入,永远是达到财务自由的一个前提。支出增加,你财务自由的本金也要增加。你每月真要花2.5万,我财务解锁阶段的本金,也不过够你实现财务自足。等至少实现第五步——财务舒适阶段之后,再开始慢慢享受人生。到了财务解锁阶段,我们每月有2.5万,每年有30万,给你超额的消费,和进行高风险的投资。这时离我们最初目标,也只有一步之遥。——待续最终一步“财务自由”——————————————————————————————————————————— +最近有点懒,趁现在下雨,出不了门,更新完。步骤七:(财务自由阶段)使自己的非工资收入≥总支出的12倍? NO !其实最后一步不写也罢,有点遥远......,我定的财务自由标准有点高。财务解锁,还是有希望达到的,自由就......(你妹,逗我吗?!不是说一步之遥吗?)好吧,哥几个也可以把财务自由的标准定为:使自己的非工资收入≥总支出的12倍。你若已经实现“财务解锁”,也就是10倍,再接着实现12倍这一标准,真也就是一步之遥。不过太无聊,太容易了不是?富爸爸还说财务自由是使自己的非工资收入≥总支出呢,任何词的定义都是人给的,他能给,我咋就不能给?我又不想拿经济学学位,不想背标准答案。我心里达到财务自由的人有谁?比尔·盖茨、李嘉诚、马云......都算。他们有足够的钱,自由的买自己所需的商品、服务、教育,甚至一个看好的企业......好吧,太远的事,不知道就不要YY。我们轻松点吧,就当使自己的非工资收入≥总支出的12倍时就实现财务自由了吧。—————————————————————————————————————————— +总结:以上七步,并没有教大家怎么去赚钱,其实个人认为,理财和实现财务自由(容易的那个)更多的是一种习惯,你月入5千的,并不比一个月入5万的人更难实现财务自由。(他入5W,花5W,没存款)做个申明:上面七个步骤,所涉及的具体数字、百分比、做法、规定,适合且只适合本人(其实我也没完全照章实施)。我只是展示出,个人粗陋的理财想法,各位不用照搬照套,理财还是要结合各自的实际情况,找到适合自己的,才是自己的理财之道。稍微说说接触过的一些工具: +A、有价证券,如:基金、股票、期货、外汇(后面三个,基本不玩了,不适合我,只是留着以前放在里面的钱,偶尔看看,没提现,也没继续投入); +B、房产,跟着几个前辈,主要三个玩法: +1、直接入股新盘,早几年年利率甚至可以到50%。当时直接被这么高的利率吓到,没敢借钱投,只是动了自己的积蓄。(现在也没后悔,原则不能破)这种机会,现在没有了。 +2、早年,贷款买了两套单身公寓,已租养贷,收租非常烦,就丢给中介打理了,现在卖了一套。 +3、自己住的房子,用房产证贷款投资,利息绝对赚的回来,但为安全,多投在1分多的地方,每年小赚几个点。 +C、公司(有几个纯粹是身边人的小打小闹) +第一个,血本无归(见步骤三)。后来,眼光尖了点,一定要挑,不成气候的就不投。 +有一次,一个非常好兄弟面上实在过不去,也知道他撑不起来,就说:我不投了,算我无息借你的,也不参与管理。半年后关门,但兄弟还是全款还了我,借钱也要挑人,真的。 +有一次,是跟一同事投一酒店。两年后倒闭,退股,两年没分红,亏了10个点,有以小股东身份去白住了两天。也还好,风险一开始就有估计到,在承受范围内。 +基本上,投成功的,一般每年也就在10到20个点左右。还有其他的,身边有人玩,自己没碰,比如:出版、网站、中介、专营等等……这些多少都要投入时间参与管理,就没碰。最后:每个人的欲望、赚钱能力、存款的自觉性都不相同,但我们在理财上能做相同的努力,可以归纳为那个老词——开源节流。祝各位心想事成。 ——终 ————————————原来专栏不是每个人都能写的 ( ̄. ̄) 分割线———————————同学提问:今年大四,想问更多的关于存钱之外的思路。想问您作为大学生在存钱之余,准备投资之前,能不能有什么资料看的?作为非经济类得人想要做一些资本运作之类的投资,根本无从下嘴啊,而且感觉很多比如买两套用贷款买房子再租出去还房贷这种方法好像很难接触到。(( ̄y▽ ̄)╭说我长的帅的话就不贴出来啦,留着自己看啦~~~)总结问题: +1、学生党,除了存钱还能做什么投资理财? +2、有什么有关投资的资料可看? +回答: +一、学生党和工作新人除了存钱还能干啥? +作为一个吃饭靠爸,花钱靠妈的学生党,其实还没资本投资,存个钱都奢侈。更不建议,拿爸妈的钱来投资,因为你爸给了你1万元,你拿来放在余额宝里,每年赚5个点(现在没有了,就4点多了),挺开心。我要说你是个好孩子,比那些钱一到手就进夜店的孩子好太多了。但你爸本来可以把这1万拿去放2分贷的。(啥?!你爸没那头脑??( ̄ε(# ̄)☆╰╮o( ̄皿 ̄///) 坏孩子,成年了,拿父母的钱就不对!!) +大四生首要任务,是找工作。找到之后,还有精力也不是投资,而是开源赚钱。(对完成步骤三以前的同志们这点同样适用)开源赚钱,增加收入,加快步骤四、五的完成,早日进入投资时代!!!投资投资,没有资,拿啥投?我当然不会告诉你,我大四把一年学费5000大洋借给我舍友家的企业(他向好多同学借了),借了一年,这期间,提心吊胆,一直瞒着父母。终于在临毕业前,收回本息6000元,然后对自己说,老子再也不把学费借人啦。后来我也遵守了我的承诺,工作十年来再也没把自己的学费借人<( ̄3 ̄)> 。因为这是个坏例子,告诉你们,你们可能会产生借钱放贷的冲动,所以不能告诉你们。 +我可以告诉你们的另一个例子是,我大二时,另一个舍友,借了全系20几个兄弟姐妹(没错姐妹!连同系妹子的钱他都借了,真不是人!姐姐是谁?是我们老师啦)的钱,几十到几千,总共也就几万,呵呵呵。然后,大三他就不来了,我们都没要利息......后来老师姐姐,好像是把他大一时交的学费拿来抵了,至于我们其他兄弟妹的钱就一直在借的进行时~~~~ +说这个例子是想说:1、刚入社会的新人们,其实不太具备对 投资风险 的评估和承受能力。那为什么,我大二被坑了一次,大四还敢继续(虽然投资成功)?讲感情呗!不善拒绝呗!不理性呗!我当时真不是冲着20个点的利息借钱的!!!你们要相信我!!2、因为本金太少,虽然年利率不低,但用几乎全部家当一年也只能赚1000元,对一个大学生来说,其实是很不划算的。那有没有学生就可以做的增加收入的事?有!不出宿舍就可以!真有,我前几个阶段就做过,现在考虑时间成本以及享乐主义的养成,已经不做了。那么下班了........待续——————————我是劳模分割线————————————————继续......细节请看 业余时间充足的 学生党 和 工作新人,怎么增加额外收入?讲完,谢谢,下一个问题...... +二、有什么有关投资的资料可看如果是要推荐具体哪本理财书籍,非常值得看,看了就改变了我什么......好像没有~~~~其实我有一直在看书,现在也还在看(看我文字这么凌乱,就知道了~~~)早期也看,各种排行版上的各种理财书,还买了大部头的《投资学》(好想上图,给大家看看我做的笔记,讲一大堆美国复杂的投资工具,看一半次贷危机就来了......妹的,亏我记得那么认真,对我而言,就这本书看了最没用)。要说哪本起到很大作用?没有!(非要说有,就是《富爸爸》给了我理财启蒙)现在看资料,都是碰上具体事情,才会有针对的去看。比如买房的时候,就猛翻各种房产投资书籍、上网也搜,还有贷款相关的(其实大多也就过过眼,根本不可能看完,在书店翻二十本,看到一本好的,就回家上网下载去了,下不到就上网买,我不是有会员账号......) +再比如,今年接触P2P,好像没有找到相关书籍,就上网搜各种讨论帖子,用户评价,关键还是身边用过朋友的评价,比较直观。然后再亲自试试。其他书,还用看吗?要啊,不看怎么装逼......不是,看书和逛知乎一样,扩展一下见世面,好玩而已,虽然不一定用的上。只看,看的下去的就好。三、个人:同学问我个人常用的投资,其实不是很推荐,想知道告诉大家也无妨——民间借贷。 +不推荐的原因,大家也都清楚。虽然平均收益2个点(就是年利24%),但身边坏贷、烂贷的例子层出不穷。还是那句话:实现步骤五之前,一切求稳。谢谢————————————————————2015.2.10——————————————————今天没有更新,未防止误导新人,和广告嫌疑,删除上文两段贷款方面的理财方案。本人也没有备份,不好意思啦╮(╯▽╰)╭ + + + + + + + + + + + + + + + + +收入来自于两部分:一部分是工作收入,工资薪金和养老金、年金等;一部分是投资收入,进行金融投资或实业投资的投资所得。 + +大部分人每月支出主要来自工作收入,没工作收入生活陷入困境。但如果投资收入够多,不用工作就可过好日子,随时可以退休或可以睡觉睡到然醒,就达到财务自由的境界了。当达到财务自由时,投资收入将成为个人收人的主要来源,不再为赚取生活费用而工作。 + +理财的最终目标是追求财务自由,财务自由的定义是:不靠工作收入,靠理财收入就能过生活。要求每年的理财收入大于生活支出,这对没工作的退休人士特别重要,理财规划师也特别对即将退休的人在财务上有这样的要求。 + +要达到财务自由是逐步完成的,中产阶级人士透过股票、债券、保险等投资获取现金流。 + +比如你生活费用一个月要一万元,一年要12万元,这就是你的年生活支出。你现在有第一个100万,放在理财型保险,每年生存金加分红有4%收益率,一年有4万元,这是不够生活支出的。 + +再存了第二个100万,放在高评等债券投资,每年利率6%,一年就有6万元,跟保险收益加起来10万元,还是达不到12万元的财务自由门坎。 + +再努力储蓄50万,放在蓝筹股,每年发放现金股利8%,一年就有4万元,跟保险债券收益加起来14万元,就达到财务自由。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-08-04-mysql-myisam_init.md b/_drafts/2015-08-04-mysql-myisam_init.md new file mode 100644 index 0000000..c4d7731 --- /dev/null +++ b/_drafts/2015-08-04-mysql-myisam_init.md @@ -0,0 +1,350 @@ +--- +Date: October 19, 2013 +title: MySQL MyISAM +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + + + + + +* 文件比较小; +* 支持 FULLTEXT、GIS,不过性能不太好; +* 支持复合索引以及自增主健(不建议使用); +* 表级锁,对性能影响较大; +* 不支持事务; +* 不支持表恢复(通过 REPAIR TABLE); +* update 效率较低,容易产生碎片。 + + + +.MYI 索引 +.MYD 数据 + + +--- MyISAM file structure + +记录在数据文件中依次存放 + Delete -> Gap + Gap -> Next Insert, or Append +FIXED vs. DYNAMIC vs. COMPRESSED + Fragmentation (String extension) + +Index: record Number (fixed), byte offset (DYNAMIC) + Data pointer size variable + myisam_data_pointer_size=6(256TB) +alter table t min_rows=n max_rows=m avg_row_size=p + +--- MyISAM update storm +update t set sth = concat(sth, sth) +split record = seek +move to end of table = all indices updated +either way: fragmentation, seeks + + +--- MyISAM file structure +update t set count=count+1, and server crash? +some records update, you do not know which. +recover from unchrashed slave, or have invalid data. +also, the table is marked as crashed. + +--- Compressed MyISAM +Close all filedhandles: FLUSH TABLES +run myisampack to compress data file: invalid index. +run myisamchk to rebuild index. +data footprint approx. 50% smaller. + read-only + +--- MyISAM locking +table level locks: shared(read), exclusive(write) +many concurrent select statements, serialized write statements. +writes have precedence +running statements have precedence. + +select high_priority ... +write (insert update delete, ...) +select ... +low priority (update low_priority ...) + +many selects, some slow (4s runtime) +foreach $i (@stmt) { $dbh->do($i) } + insert blocks subsequent reads. + insert blocked by runing(slow) read. +one insert every 4s. + + +--- Explicit locking +lock table t1 read, t2 read, t3 write. +all locks taken at once: avoids deadlocks. +unable to touch unlocked tables during lock: ensures correctness and completeness. +unlock tables (or disconnect) + +how does explicit locking affect the myisam interlock scenario? +locking can actually make things fasster. +but will increase latency + +--- Tuning MyISAM +MyISAM caches myi data, but no myd data. +system must have sufficient buffer cache: mysqld size must be limited. +key_buffer_size=... +ideally: sum of all myi files. + +key_cache_division_limit = 100 +the division point between the hot and warm sublists of the key cache buffer list(percent) + +alter a server restart, the key cache is cold. + load index into cache , , ... +this preloads the cache w/ linear disk accesses. +much faster than normal warmup. + +handling myisam index update storms: + delayed_key_write = OFF|ON|ALL + create table t (...) delay_key_write = 1; +status variable key_blocks_not_flushed. +large number of unflushed blocks + crash = monster repair table. + +myisam_recover_options = force,backup + may not be enough in the face of delayed key writes +myisam_repair_threads = 1 + used to be broken with > 1 + +--- monitoring myisam +table_locks_immediate + table locks granted without waiting +table_locks_waited + table locks granted after wait. +1%=dangerous, 3%=deadly + +--- myisam maintenance +show table status - data_free +optimize table to reclaim data_free + this is very slow and x-locks. +analyze tables - optimizer statistics + also very slow (in myisam) + + + +## 文件 + +总共有三类文件:.MYD (数据文件,MySQL Data)、.MYI (索引文件,MySQL Index)、.frm (格式文件,Format);而数据文件的行格式按照不同的类型分为了 fixed、dynamic、packed 三种。 + +### fixed format + +这个时默认格式,当表不含变长字段(varchar/varbinary/blob/text)时使用,每行固定,所以很容易获取行在页上的具体位置,存取效率高,但是占用磁盘空间较大。 + +* Page Size:MyISAM 没有像其它数据库一样保存数据时采用页存储,这也就意味着,你不会看到行间的填充字符。 + +* + +{% highlight text %} +CREATE TABLE foobar (c1 CHAR(1), c2 CHAR(1), c3 CHAR(1)) ENGINE=MyISAM; + +INSERT INTO foobar VALUES ('a', 'b', 'c'); +INSERT INTO foobar VALUES ('d', NULL, 'e'); +{% endhighlight %} + + + + + +# 锁机制 + +MySQL 有三种粒度的锁:表级锁、行级锁和页面锁;其中 InnoDB 支持表锁和行级锁,MyISAM 支持表级锁,表锁分为读锁 (read lock) 和写锁 (write lock),接下来我们看看 MyISAM 引擎的表锁。 + +为了测试,首先创建如下的表。 + +{% highlight text %} +CREATE TABLE foobar (id INT, name CHAR(30)) ENGINE=MyISAM; +INSERT INTO foobar VALUES (1, 'foo'), (2, 'bar'); + +CREATE TABLE test (id INT, name CHAR(30)) ENGINE=MyISAM; +INSERT INTO test VALUES (1, 'test1'), (2, 'test2'); +{% endhighlight %} + +## 读锁 (read lock) + +当一个会话加读锁后,其它会话是可以继续读取该表的,但所有更新、删除和插入将会阻塞,直到将表解锁。 + +{% highlight text %} +mysql[s1]> LOCK TABLE foobar READ; # s1中对表加读锁 +Query OK, 0 rows affected (0.00 sec) + +mysql[s2]> SELECT * FROM foobar; # s2中可以对表正常读取 ++------+------+ +| id | name | ++------+------+ +| 1 | foo | +| 2 | bar | ++------+------+ +2 rows in set (0.00 sec) +mysql[s2]> INSERT INTO foobar VALUES (3, 'test'); # s2中的写入被阻塞 + +mysql[s1]> UNLOCK TABLES; # s1中释放锁,s2写入成功 +Query OK, 0 rows affected (0.00 sec) +{% endhighlight %} + +MyISAM 会在执行 SELECT 时会自动给相关表加读锁,在执行 UPDATE、DELETE 和 INSERT 时会自动给相关表加写锁;对于表级读锁有几点需要特别注意的地方 (InnoDB相同): + +### 1. 锁表时要将所有表加锁 + +锁表时一定要把所有需要访问的表都锁住,因为锁表之后无法访问其他未加锁的表。 + +{% highlight text %} +mysql> LOCK TABLE foobar READ; +Query OK, 0 rows affected (0.00 sec) + +mysql> SELECT * FROM test; +ERROR 1100 (HY000): Table 'test' was not locked with LOCK TABLES +{% endhighlight %} + +### 2. 读锁后不能更新 + +当前会话对表加读锁之后,该会话只能读锁住的表,而无法对其进行 UPDATE、DELETE 和 INSERT 操作。 + +{% highlight text %} +mysql> LOCK TABLE foobar READ; +Query OK, 0 rows affected (0.00 sec) + +mysql> INSERT INTO foobar VALUES (4, 'again'); +ERROR 1099 (HY000): Table 'foobar' was locked with a READ lock and can't be updated +{% endhighlight %} + +### 3. 对表多次加锁 + +同一个表如果在 SQL 语句里面如果出现了 N 次,那么就要锁定 N 次,否则会出错。 + +{% highlight text %} +mysql> LOCK TABLE foobar READ; +Query OK, 0 rows affected (0.00 sec) + +mysql> SELECT * FROM foobar f1, foobar f2 WHERE f1.id = f2.id; +ERROR 1100 (HY000): Table 'f1' was not locked with LOCK TABLES + +mysql> UNLOCK TABLES; +Query OK, 0 rows affected (0.00 sec) +{% endhighlight %} + +应该使用如下的方法: + +{% highlight text %} +mysql> LOCK TABLE foobar AS f1 READ, foobar AS f2 READ; +Query OK, 0 rows affected (0.00 sec) + +mysql> SELECT * FROM foobar f1, foobar f2 WHERE f1.id = f2.id; ++------+------+------+------+ +| id | name | id | name | ++------+------+------+------+ +| 1 | foo | 1 | foo | +| 2 | bar | 2 | bar | +| 3 | test | 3 | test | ++------+------+------+------+ +3 rows in set (0.00 sec) + +mysql> UNLOCK TABLES; +Query OK, 0 rows affected (0.00 sec) +{% endhighlight %} + +## 写锁 (write lock) + +当一个会话给表加写锁后,其它会话所有读取、更新、删除和插入将会阻塞,直到将表解锁。 + +{% highlight text %} +mysql[s1]> LOCK TABLE foobar WRITE; # s1中对表加写锁 +Query OK, 0 rows affected (0.00 sec) + +mysql[s2]> SELECT * FROM foobar; # s2的读请求将会被阻塞,直到锁释放 +{% endhighlight %} + +同上,锁表的时候一定要把所有需要访问的表都锁住,因为锁表之后无法访问其他未加锁的表。 + +## concurrent_insert、local 操作 + +注意,这两个操作操作是 MyISAM 引擎所特有,InnoDB 无此功能。如上,当给表加锁之后,其它会话的写入将会被阻塞,通过 local 关键字,可以让其它会话也能添加数据。 + +上面所说的功能还需要配合 concurrent_insert 全局变量使用,该变量有三个值: + +* NEVER(0):加读锁后,不允许其它会话并发写入。 +* AUTO(1):加读锁后,在表里没有空洞(就是没有删除过行)的条件下,允许其会话并发写入。 +* ALWAYS(2):加读锁后,允许其它会话并发写入。 + +可以通过如下命令可以查看当前数据库的设置: + +{% highlight text %} +mysql> SHOW GLOBAL VARIABLES LIKE 'concurrent_insert'; +{% endhighlight %} + +接下来测试一下。 + + +{% highlight text %} +mysql[s1]> SET GLOBAL concurrent_insert=ALWAYS; +Query OK, 0 rows affected (0.00 sec) + +mysql[s1]> LOCK TABLE foobar READ LOCAL; +Query OK, 0 rows affected (0.00 sec) + +mysql[s2]> INSERT INTO foobar VALUES (4, 'again'); +Query OK, 1 rows affected (0.00 sec) +mysql[s2]> SELECT * FROM foobar; ++------+-------+ +| id | name | ++------+-------+ +| 1 | foo | +| 2 | bar | +| 3 | test | +| 4 | again | ++------+-------+ +4 rows in set (0.00 sec) + + +mysql[s1]> SELECT * FROM foobar; ++------+-------+ +| id | name | ++------+-------+ +| 1 | foo | +| 2 | bar | +| 3 | test | ++------+-------+ +3 rows in set (0.00 sec) +{% endhighlight %} + +注意,此时 s1 对 s2 写入的数据是不可见的,只有当 s1 释放锁之后才可见。 + +## MyISAM 锁的调度机制 + +默认写锁优于读锁,当有大量的对同一个表的读写请求时,只有在所有的写请求执行完成后,读请求才能获得执行机会。着就会导致,一个大量更新的表,将会无法读取数据。 + +### 手动设置优先级 + +此时可以使用 LOW_PRIORITY、HIGH_PRIORITY 和 DELAYED 关键字,来缓解这一问题;在执行 DELETE、INSERT、UPDATE、LOAD DATA 和 REPLACE 的时候可以使用 LOW_PRIORITY 来降低该更新语句的优先级,让读取操作能够执行。而在执行 SELECT 时,可以使用 HIGH_PRIORITY 来提高该语句的优先级,让读取操作能够执行。 + +另外,可以在执行 insert 和 replace 的时候可以使用 DELAYED 让 MySQL 返回 OK 状态给客户端,并且修改也是对该会话可见的。 + +不过,上述方法并非已经将数据插入表,而是存储在内存等待队列中,当能够获得表的写锁再插入。优点时客户无序等待,提高写入速度;坏处是,无法返回自增 ID,系统崩溃时会导致数据丢失。 + +### set LOW_PRIORITY_UPDATES = 1 + +让所有支持 LOW_PRIORITY 选项的语句都默认地按照低优先级来处理。 + +### MAX_WRITE_LOCK_COUNT + +该变量默认为 int 最大值,表示当一个表的写锁数量达到设定的值后,就降低写锁的优先级,让读锁有机会执行。 + +### 查看锁竞争 + +直接通过如下命令查看。 + +{% highlight text %} +mysql> SHOW STATUS LIKE 'table_locks%' +{% endhighlight %} + +# 参考 + +相关内容可以参考官方文档 [MySQL Internal Manual - MyISAM Storage Engine](http://dev.mysql.com/doc/internals/en/myisam.html)、[MySQL Reference Manual - The MyISAM Storage Engine](http://dev.mysql.com/doc/refman/en/myisam-storage-engine.html) 。 + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-08-17-network-timeout-retries_init.md b/_drafts/2015-08-17-network-timeout-retries_init.md new file mode 100644 index 0000000..2db99df --- /dev/null +++ b/_drafts/2015-08-17-network-timeout-retries_init.md @@ -0,0 +1,334 @@ +--- +Date: Auguest 05, 2015 +title: Linux 网络超时与重传 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +在此介绍重传,为了保证可靠性,在 TCP 的三次握手、数据传输、链接关闭阶段都有响应的重传机制。那么,重传的次数都是有那些参数指定?tcp_retries1 和 tcp_retries2 到底有什么区别?什么是 orphan socket ? + + + + +首先我们查看一下网络中与重传相关的参数有那些。 + +# 重传简介 + +关于重传的控制参数,可以查看内核中的 Documentation/networking/ip-sysctl.txt 文件,内核中的控制参数总共有五个相关参数。 + +{% highlight text %} +tcp_syn_retries - INTEGER + Number of times initial SYNs for an active TCP connection attempt + will be retransmitted. Should not be higher than 255. Default value + is 6, which corresponds to 63seconds till the last retransmission + with the current initial RTO of 1second. With this the final timeout + for an active TCP connection attempt will happen after 127seconds. + +tcp_synack_retries - INTEGER + Number of times SYNACKs for a passive TCP connection attempt will + be retransmitted. Should not be higher than 255. Default value + is 5, which corresponds to 31seconds till the last retransmission + with the current initial RTO of 1second. With this the final timeout + for a passive TCP connection will happen after 63seconds. + +tcp_orphan_retries - INTEGER + This value influences the timeout of a locally closed TCP connection, + when RTO retransmissions remain unacknowledged. + See tcp_retries2 for more details. + + The default value is 8. + If your machine is a loaded WEB server, + you should think about lowering this value, such sockets + may consume significant resources. Cf. tcp_max_orphans. + +tcp_retries1 - INTEGER + This value influences the time, after which TCP decides, that + something is wrong due to unacknowledged RTO retransmissions, + and reports this suspicion to the network layer. + See tcp_retries2 for more details. + + RFC 1122 recommends at least 3 retransmissions, which is the + default. + +tcp_retries2 - INTEGER + This value influences the timeout of an alive TCP connection, + when RTO retransmissions remain unacknowledged. + Given a value of N, a hypothetical TCP connection following + exponential backoff with an initial RTO of TCP_RTO_MIN would + retransmit N times before killing the connection at the (N+1)th RTO. + + The default value of 15 yields a hypothetical timeout of 924.6 + seconds and is a lower bound for the effective timeout. + TCP will effectively time out at the first RTO which exceeds the + hypothetical timeout. + + RFC 1122 recommends at least 100 seconds for the timeout, + which corresponds to a value of at least 8. +{% endhighlight %} + +简单来说,五个重试参数的含义为: + +* tcp_syn_retries:在三次握手阶段,客户端发起链接时,发送 SYN 报文的重试次数。 + +* tcp_synack_retries:在三次握手阶段,服务端回应 SYN+ACK 时,发送报文的重试次数。 + +* tcp_orphan_retries:在关闭阶段,会影响到主动关闭链接的孤儿 socket 重传。 + +* tcp_retries1:在数据传输阶段。 + +* tcp_retries2:在数据传输阶段。 + +在此重点关注一下 tcp_retries1、tcp_retries2 两个参数,以及 tcp_orphan_retries 。 + +# tcp_retries1、tcp_retries2 + +这两个参数在上述的介绍中有些模糊,可能由于过于概括,会令人产生很多疑问,甚至产生一些误解。 + +1. 当重试超过 tcp_retries1 这个阈值后,到底向网络层报告了什么 suspicion ? + +2. tcp_retries1 和 tcp_retries2 对应的是重传次数?其中的时间是怎么计算出来的? + +3. tcp_retries2 应该是重传的上限吧,其中的 lower bound for the effective timeout 又是几个意思?不应该是 upper bound 吗?effective timeout 又是做什么的? + +## 源码解析 + +定时器的初始化是在 tcp_init_xmit_timers() 函数中完成,超时重传相关的是 tcp_write_timer(),实际最终调用的是 tcp_write_timer_handler() 。 + +{% highlight c %} +void tcp_write_timer_handler(struct sock *sk) +{ + ... ... + switch (event) { + case ICSK_TIME_EARLY_RETRANS: + tcp_resume_early_retransmit(sk); + break; + case ICSK_TIME_LOSS_PROBE: + tcp_send_loss_probe(sk); + break; + case ICSK_TIME_RETRANS: + icsk->icsk_pending = 0; + tcp_retransmit_timer(sk); + break; + case ICSK_TIME_PROBE0: + icsk->icsk_pending = 0; + tcp_probe_timer(sk); + break; + } + ... ... +} +{% endhighlight %} + +如果是超时重传定时器触发的,就会调用 tcp_retransmit_timer() 进行处理,其中与 tcp_retries 相关的代码调用逻辑如下。 + +{% highlight text %} +tcp_retransmit_timer() + |-tcp_write_timeout() # 判断是否重传了足够的久 + | |-retransmit_timed_out() # 判断是否超过了阈值 + |-tcp_retransmit_skb() # 如果没有超过了重传阈值,则直接重传报文 +{% endhighlight %} + +接下来看一下 tcp_write_timeout() 的具体实现。 + +{% highlight c %} +static int tcp_write_timeout(struct sock *sk) +{ + struct inet_connection_sock *icsk = inet_csk(sk); + struct tcp_sock *tp = tcp_sk(sk); + int retry_until; + bool do_reset, syn_set = false; + + if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) { + /* 超时发生在三次握手期间,重传次数通过tcp_syn_retries指定 */ + ... ... + retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries; + syn_set = true; + } else { /* 超时发生在数据发送期间,也就是我们现在讨论的问题 */ + /* 下面的函数负责判断重传是否超过阈值,返回真表示超过 */ + if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) { + /* Black hole detection */ + tcp_mtu_probing(icsk, sk); /* 如果开启了tcp_mtu_probing,则执行PMTU */ + /* 更新路由缓存,用以避免由于路由选路变化带来的问题 */ + dst_negative_advice(sk); + } + + retry_until = sysctl_tcp_retries2; + if (sock_flag(sk, SOCK_DEAD)) { + /* 表示是没有应用在使用的一个孤立的socket */ + const int alive = icsk->icsk_rto < TCP_RTO_MAX; + + retry_until = tcp_orphan_retries(sk, alive); + do_reset = alive || + !retransmits_timed_out(sk, retry_until, 0, 0); + + if (tcp_out_of_resources(sk, do_reset)) + return 1; + } + } + + /* 一般来说,如果没有孤儿socket,那么一般重传次数是tcp_retries2 */ + if (retransmits_timed_out(sk, retry_until, + syn_set ? 0 : icsk->icsk_user_timeout, syn_set)) { + /* Has it gone just too far? */ + tcp_write_err(sk); /* 最终会调用tcp_done关闭TCP流 */ + return 1; + } + return 0; +} +{% endhighlight %} + +在如上的函数中,两次超时时间的计算都是通过 retransmits_timed_out() 函数计算的,那么也就是该函数实际会判断是否已经超时。 + +{% highlight c %} +#define tcp_time_stamp ((__u32)(jiffies)) + +static bool retransmits_timed_out(struct sock *sk, + unsigned int boundary, + unsigned int timeout, + bool syn_set) +{ + unsigned int linear_backoff_thresh, start_ts; + /* 如果是在三次握手阶段,syn_set为真 */ + unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN; + + /* 如果设置的重传次数为0,那么就直接退出 */ + if (!inet_csk(sk)->icsk_retransmits) + return false; + + /* 获取数据包第一次发送的时间,在tcp_retransmit_skb()中设置 */ + start_ts = tcp_sk(sk)->retrans_stamp; + if (unlikely(!start_ts)) + start_ts = tcp_skb_timestamp(tcp_write_queue_head(sk)); + + /* 如果用户态没有指定timeout,则自动计算一个出来 */ + if (likely(timeout == 0)) { + /* 计算一下以rto_base为第一次重传间隔,重传boundary次所需要的时间 */ + linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base); + + if (boundary <= linear_backoff_thresh) + timeout = ((2 << boundary) - 1) * rto_base; + else + timeout = ((2 << linear_backoff_thresh) - 1) * rto_base + + (boundary - linear_backoff_thresh) * TCP_RTO_MAX; + } + + /* 如果数据包第一次发送的时间距离现在的时间间隔,超过了timeout值,则认为重传超于阈值了 */ + return (tcp_time_stamp - start_ts) >= timeout; +} +{% endhighlight %} + +从以上的代码分析可以看到,如果用户没有设置超时时间,那么真正起到限制重传次数的并不是真正的重传次数,而是与此相关的一个超时时间。 + +如上,是以 tcp_retries1 或 tcp_retries2 为 boundary,以 rto_base (如 TCP_RTO_MIN=200ms) 为初始的 RTO,计算出来一个 timeout 值,当重传间隔超过这个 timeout 后,则认为超过了阈值。 + +如文档中所说的,当 tcp_retries2=15 时,那么计算得到的 timeout 是 924600ms,这个值是如何计算到的?简单来说,还是在 retransmits_timed_out() 函数计算完成,计算过程如下: + +{% highlight text %} +----- 首先计算thresh +linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base) = ilog2((120*HZ)/(HZ/5)) = ilog2(600) = 9 + +if (boundary <= linear_backoff_thresh) + timeout = ((2 << boundary) - 1) * rto_base; +else + timeout = ((2 << linear_backoff_thresh) - 1) * rto_base + + (boundary - linear_backoff_thresh) * TCP_RTO_MAX; + +----- 此时选择的是第二个分支 +timeout = (2<<9 - 1) * (HZ/5) + (15 - 9) * 120 * HZ = 924.6s +{% endhighlight %} + +如果用户没有指定超时时间,那么默认就是 924.6s,而对于具体重试了多少次,是与 RTT 相关的。 + +1. 如果 RTT 比较小,那么 RTO 初始值就约等于下限 200ms,这时表现出来的现象基本就是刚好重传了 15 次。 + +2. 如果 RTT 较大,比如 RTO 初始值计算得到的是 1000ms,那么根本不需要重传 15 次,重传总间隔就会超过 924.6s 。 + +对于上述的问题,基本也就有结论了: + +1. 重试超过了 tcp_retries1 之后,怀疑路由有问题,直接更新路由缓存。 + +2. 通过 tcp_retries 计算出来的是时间,而具体重传了多少次是与实际的 RTT 相关的。 + +3. effective timeout 实际就是 retransmits_timed_out() 函数计算得到的 timeout 值。所谓的 lower / upper bound 是指超时时间的范围,如果是 15 那么实际的重传范围是 [924.6s, 1044.6s) 。 + + +# tcp_orphan_retries + +首先介绍一下什么是 orphan socket,简单来说就是该 socket 不与任何一个文件描述符相关联。例如,当应用主动调用 close() 关闭一个链接时,此时该 socket 就成为了 orphan,但是该 sock 仍然会保留一段时间,直到最后根据 TCP 协议结束。 + + +![fourway]{: .pull-center} + +如上是 TCP 关闭链接时的握手过程。 + +主动关闭的一方发出 FIN,同时进入 FIN_WAIT1 状态,被动关闭的一方响应 ACK,从而使主动关闭的一方迁移至 FIN_WAIT2 状态,接着被动关闭的一方同样会发出 FIN,主动关闭的一方响应 ACK,同时链接的状态迁移至 TIME_WAIT 。 + +那么这与 tcp_orphan_retries 参数有什么关系? + +正常来说,服务器间的 ACK 确认是非常快的,通常不会有 FIN_WAIT1 状态存在,如果被动关闭的一段很长时间没有响应,此时的 TCP 协议会如何处理呢。 + +实际上这个参数决定了 FIN_WAIT1 状态的持续时间,其计算方式与 tcp_retries1 的相同。 + +内核中还有一个容易混淆的参数 net.ipv4.tcp_fin_timeout,它实际上它控制的是 FIN_WAIT2 的超时时间,其定义如下: + +{% highlight text %} +tcp_fin_timeout - INTEGER + The length of time an orphaned (no longer referenced by any + application) connection will remain in the FIN_WAIT_2 state + before it is aborted at the local end. While a perfectly + valid "receive only" state for an un-orphaned connection, an + orphaned connection in FIN_WAIT_2 state could otherwise wait + forever for the remote to close its end of the connection. + Cf. tcp_max_orphans + Default: 60 seconds +{% endhighlight %} + +需要注意的是,在 tcp_orphan_retries 的定义中,如果要设置成一个比较小的值,该值应该至少大于0,如果是 0 那么实际等同于 8 ,对应的代码如下: + +{% highlight c %} +/* Calculate maximal number or retries on an orphaned socket. */ +static int tcp_orphan_retries(struct sock *sk, int alive) +{ + int retries = sysctl_tcp_orphan_retries; /* May be zero. */ + + /* We know from an ICMP that something is wrong. */ + if (sk->sk_err_soft && !alive) + retries = 0; + + /* However, if socket sent something recently, select some safe + * number of retries. 8 corresponds to >100 seconds with minimal + * RTO of 200msec. */ + if (retries == 0 && alive) + retries = 8; + return retries; +} +{% endhighlight %} + +可以得出结论,如果系统负载较重,有很多处于 FIN_WAIT1 的链接,那么可以通过降低 tcp_orphan_retries 来解决问题,具体设置多少视网络条件而定。 + +另外,在遇到 DoS 攻击时,可以通过设置 tcp_max_orphans 减小。但是这和用来控制 TIME_WAIT 的最大值的 tcp_max_tw_buckets 参数一样,除非你遇到了 DoS 攻击,否则最好不要降低它。 + + +## 如何关闭一个链接 + +如果要关闭一个 TCP 连接,那么需要知道相应的 ACK 和 SEQ ,然后才可以 RESET 连接。 + +为了获取 ACK 和 SEQ 可以有主动与被动两种,分别有 tcpkill 以及 killcx,其中后者是 Perl 写的脚本。 + + + + + + + + + + + + +[fourway]: /images/linux/four-way-handshake.png + + diff --git a/_drafts/2015-08-19-mysql-backup-strategy_init.md b/_drafts/2015-08-19-mysql-backup-strategy_init.md new file mode 100644 index 0000000..994a182 --- /dev/null +++ b/_drafts/2015-08-19-mysql-backup-strategy_init.md @@ -0,0 +1,121 @@ +--- +title: MySQL 备份策略 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,备份,mysqldump,mysqlbinlog,xtrabackup +description: 为了保证数据安全,都会对硬件做高可用,防止出现单点故障,但是无论如何都无法取代备份,尤其对于数据库中所保存的数据而言。在此,介绍一下 MySQL 中常用的备份方法。 +--- + + + + + +## 准备工作 + +在刚接触一个环境时,首先需要确认一下几点的数据: + +1. 确定需要备份数据的大小,包括使用的存储引擎,例如 InnoDB、MyISAM、ARCHIVE 等; +2. 如果要获取一致性读,可以使用什么样的锁策略,当然这也根上述的存储引擎相关; +3. 备份时会花费多长时间; +4. 是否有维护时间窗口,通常是没有对外提供 7*24 服务的业务。 + +#### 确定备份大小 + +可以通过如下方式查看当前某个库的大小。 + +{% highlight text %} +mysql> SELECT ROUND(SUM(data_length+index_length)/1024/1024) AS total_mb, + ROUND(SUM(data_length)/1024/1024) AS data_mb, + ROUND(SUM(index_length)/1024/1024) AS index_mb + FROM information_schema.tables + WHERE table_schema NOT IN + ('information_schema', 'performance_schema', 'mysql', 'sys'); +{% endhighlight %} + + + +这里计算的大小会与实际导出的文件有些许区别,不过相差不大。 + +#### 使用锁策略 + +锁的策略决定了备份时是否会影响到在线业务的读写,例如对于 mysqldump 命令可以通过 \-\-lock-tables 参数开启表锁,也就是 ```LOCK TABLES``` 命令;显然,这会影响到线上业务,不过对于 MyISAM 存储引擎来说,如果要获取到一致性的读,这是必须的。 + +对于 mysqldump 命令,同时也提供了一个 \-\-single-transaction 参数在备份时开启一个事务,当然这需要存储引擎支持多版本事务,例如 InnoDB ,当使用该选项时,则会自动关闭 \-\-lock-tables 。 + +可以通过如下命令查看每个数据库包含的存储引擎。 + +{% highlight text %} +mysql> SELECT table_schema,engine,count(1) AS tables + FROM information_schema.tables + WHERE table_schema NOT IN + ('information_schema', 'performance_schema', 'mysql', 'sys') + GROUP BY table_schema, engine + ORDER BY 3 DESC; +{% endhighlight %} + +#### 备份时间 + +这个实际上比较难测量,包括使用的内存、备份的并发等等,所以在此就不过多介绍了。 + +#### 总结 + +可以将当前库的大小、存储引擎信息通过如下 SQL 一次查看。 + +{% highlight text %} +mysql> SELECT table_schema, engine, + ROUND(SUM(data_length+index_length)/1024/1024) AS total_mb, + ROUND(SUM(data_length)/1024/1024) AS data_mb, + ROUND(SUM(index_length)/1024/1024) AS index_mb, + count(1) AS tables + FROM information_schema.tables + WHERE table_schema NOT IN + ('information_schema', 'performance_schema', 'mysql', 'sys') + GROUP BY table_schema, engine + ORDER BY 3 DESC; +{% endhighlight %} + +收集到信息之后,接下来就是要决定如何备份、什么时候执行备份;在备份时需要检查是否备份成功,备份的大小等信息。 + +{% highlight text %} +----- 执行备份,并计算备份的时间 +$ time mysqldump -uroot -p --all-databases > backup.sql +----- 查看备份的返回值,确认返回0,否则备份时有异常 +$ echo $? +----- 查看备份的大小 +$ ls -lh backup.sql +{% endhighlight %} + +当然,备份完成之后,可以通过 rsync 同步一份到其它的服务器上,防止数据损坏。 + + + + +Point In Time Recovery, PITR + + + + + + + + + + + +## 参考 + +[Effective MySQL: Backup and Recovery](http://apprize.info/php/effective/index.html) + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-08-24-mysql-high-availability_init.md b/_drafts/2015-08-24-mysql-high-availability_init.md new file mode 100644 index 0000000..19feefc --- /dev/null +++ b/_drafts/2015-08-24-mysql-high-availability_init.md @@ -0,0 +1,213 @@ +--- +Date: October 19, 2013 +title: MySQL 高可用 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + +在此会介绍 MySQL 的高可用解决方案。 + + + + +实际上高可用需要从软件和硬件进行保证,目前对于 MySQL 来说,常见的高可用解决方案可以参考如下内容: + +* 硬件复用。基本都是两套系统,如多路电源、双网卡、RAID 卡等,从而避免单点故障 (Single Point Of Failure, SPOF)。 + +* 共享存储。RAID 可以避免单磁盘故障,但是如过整机故障仍有问题,此时可以使用一个备机,同时使用 SAN/NAS 搭建共享存储。这样单机故障之后,直接利用共享存储上的数据,在备机上启动进程即可。 + +* Distributed Replicated Block Device, DRBD。于共享存储相似,也是为了解决存储的故障,不过这是内核提供的一个文件复制模块。 + +* 主备复制。也就是本文讨论的内容。 + +* Network DataBase, NDB。可以支持多个示例写入,从而当一个服务器宕机之后,另外的机器仍可以提供服务。不过,这种方式只适合写多读少的场景。 + +* Galera。MySQL 的多主在发生网络分区后,可能会导致不一致产生,而 Galera 可以保证在副本中的多数派写入成功之后才提交,从而保证数据一致性。 + +* Percona XtraDB Cluster, PXC。这个是对 Galera 的优化,原理基本相似,还没有仔细研究。 + +接下来我们看看是如何通过复制来获取高可用的。 + +{% highlight text %} +=== Master Slave === ++-----+ +-----+ +|-----| | | +|--M--| =>=> | S | +|-----| | | ++-----+ +-----+ + +=== Master Multi-Slave === + +-----+ + | | + +=>| S | + | | | + | +-----+ + | ++-----+ | +-----+ +|-----| | | | +|--M--|=>=+=>| S | +|-----| | | | ++-----+ | +-----+ + | + | +-----+ + | | | + +=>| S | + | | + +-----+ + +=== Master Master === ++-----+ +-----+ +|-----| |-----| +|--M--| <==> |--M--| +|-----| |-----| ++-----+ +-----+ + +=== Master Slave Slave === + +-----+ + | | + +=>| S | + | | | + | +-----+ + | ++-----+ +-----+ | +-----+ +|-----| | | | | | +|--M--| =>=> | S | =+=>| S | +|-----| | | | | | ++-----+ +-----+ | +-----+ + + | +-----+ + | | | + +=>| S | + | | + +-----+ +{% endhighlight %} + +# 参考 + +当前 MySQL 高可用解决方案的很不错介绍,可以参考 [MySQL high availability -- Top 7 solutions to improve MySQL uptime](https://bobcares.com/blog/mysql-high-availability-top-7-solutions/),或者可以参考 [本地保存的版本](/reference/mysql/Top 7 solutions to improve MySQL uptime.mht) 。 + + + + +MySQL的各种高可用方案,大多是基于以下几种基础来部署的: + +基于主从复制; + +基于Galera协议; + +基于NDB引擎; + +基于中间件/proxy; + +基于共享存储; + +基于主机高可用; + +在这些可选项中,最常见的就是基于主从复制的方案,其次是基于Galera的方案,我们重点说说这两种方案。其余几种方案在生产上用的并不多,我们只简单说下。 + + + + +# 基于主从复制 + +双节点主从 + keepalived/heartbeat + +一般来说,中小型规模的时候,采用这种架构是最省事的。 + +两个节点可以采用简单的一主一从模式,或者双主模式,并且放置于同一个VLAN中,在master节点发生故障后,利用keepalived/heartbeat的高可用机制实现快速切换到slave节点。 + +在这个方案里,有几个需要注意的地方: + +采用keepalived作为高可用方案时,两个节点最好都设置成BACKUP模式,避免因为意外情况下(比如脑裂)相互抢占导致往两个节点写入相同数据而引发冲突; + +把两个节点的auto_increment_increment(自增起始值)和auto_increment_offset(自增步长)设成不同值。其目的是为了避免master节点意外宕机时,可能会有部分binlog未能及时复制到slave上被应用,从而会导致slave新写入数据的自增值和原先master上冲突了,因此一开始就使其错开;当然了,如果有合适的容错机制能解决主从自增ID冲突的话,也可以不这么做; + +slave节点服务器配置不要太差,否则更容易导致复制延迟。作为热备节点的slave服务器,硬件配置不能低于master节点; + +如果对延迟问题很敏感的话,可考虑使用MariaDB分支版本,或者直接上线MySQL 5.7最新版本,利用多线程复制的方式可以很大程度降低复制延迟; + +对复制延迟特别敏感的另一个备选方案,是采用semi sync replication(就是所谓的半同步复制)或者后面会提到的PXC方案,基本上无延迟,不过事务并发性能会有不小程度的损失,需要综合评估再决定; + +keepalived的检测机制需要适当完善,不能仅仅只是检查mysqld进程是否存活,或者MySQL服务端口是否可通,还应该进一步做数据写入或者运算的探测,判断响应时间,如果超过设定的阈值,就可以启动切换机制; + +keepalived最终确定进行切换时,还需要判断slave的延迟程度。需要事先定好规则,以便决定在延迟情况下,采取直接切换或等待何种策略。直接切换可能因为复制延迟有些数据无法查询到而重复写入; + +keepalived或heartbeat自身都无法解决脑裂的问题,因此在进行服务异常判断时,可以调整判断脚本,通过对第三方节点补充检测来决定是否进行切换,可降低脑裂问题产生的风险。 + +双节点主从+keepalived/heartbeat方案架构示意图见下: +MySQL双节点高可用架构 + +图解:MySQL双节点(单向/双向主从复制),采用keepalived实现高可用架构。 + +多节点主从+MHA/MMM + +多节点主从,可以采用一主多从,或者双主多从的模式。 + +这种模式下,可以采用MHA或MMM来管理整个集群,目前MHA应用的最多,优先推荐MHA,最新的MHA也已支持MySQL 5.6的GTID模式了,是个好消息。 + +MHA的优势很明显: + +开源,用Perl开发,代码结构清晰,二次开发容易; + +方案成熟,故障切换时,MHA会做到较严格的判断,尽量减少数据丢失,保证数据一致性; + +提供一个通用框架,可根据自己的情况做自定义开发,尤其是判断和切换操作步骤; + +支持binlog server,可提高binlog传送效率,进一步减少数据丢失风险。 + +不过MHA也有些限制: + +需要在各个节点间打通ssh信任,这对某些公司安全制度来说是个挑战,因为如果某个节点被黑客攻破的话,其他节点也会跟着遭殃; + +自带提供的脚本还需要进一步补充完善,当然了,一般的使用还是够用的。 + +多节点主从+etcd/zookeeper + +在大规模节点环境下,采用keepalived或者MHA作为MySQL的高可用管理还是有些复杂或麻烦。 + +首先,这么多节点如果没有采用配置服务来管理,必然杂乱无章,线上切换时很容易误操作。 + +在较大规模环境下,建议采用etcd/zookeeper管理集群,可实现快速检测切换,以及便捷的节点管理。 + + + +## 搭建环境 + +下图演示了从一个主服务器(master)把数据同步到从服务器(slave)的过程。 + +![Simple Master Slave]({{ site.url }}/images/databases/mysql/ha_master_slave.jpg "Simple Master Slave"){: .pull-center} + +这是一个简单的主-从复制的例子,而主-主互相复制只是把上面的例子反过来再做一遍,所以我们以这个例子介绍原理。 + +对于一个mysql服务器,一般有两个线程来负责复制和被复制,当开启复制之后。 + +1. 作为主服务器Master,会把自己的每一次改动都记录到二进制日志 Binarylog 中。 + +2. 从服务器会用 master 上的账号登陆到 master ,读取 master 的 Binarylog ,写入到自己的中继日志 Relaylog ,然后自己的 sql 线程会负责读取这个中继日志,并执行一遍。 + +其中服务器以 [MySQL 简介](/blog/mysql-introduce.html) 中介绍的多实例为例,在配置文件中需要设置 log-bin=mysql-bin ,server-id 两个参数,第一个表示启用二进制日志。 + +{% highlight text %} +----- 在主建立帐户并授权slave,一般来说用其它帐号也可以 +mysql> grant replication slave on *.* to 'mysync'@'localhost' identified by '123456'; + +----- 查看主服务器的状态,记住现在的File和Position +mysql> show master status; + +----- 启动slave +mysql> change master to master_host='localhost', master_user='mysync', master_port=3306, + -> master_password='123456', master_log_file='mysql-bin.000001', master_log_pos=313; + +----- 在导出数据时可以执行如下内容 +mysql> flush tables with read lock; // 锁定数据库然后将数据导出 +$ mysqldump --master-data -uroot -p some_db > some_db.sql // 将数据导出 +mysql> unlock tables; // 解锁数据库 +$ mysql -uroot -p some_db < some_db.sql // 重新导入 +{% endhighlight %} + +正常来说,只要开启了 bin-log ,show master status 都是有效的,show slave status 在备库中有效。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-08-25-mysql-backup-tools_init.md b/_drafts/2015-08-25-mysql-backup-tools_init.md new file mode 100644 index 0000000..8e23c91 --- /dev/null +++ b/_drafts/2015-08-25-mysql-backup-tools_init.md @@ -0,0 +1,391 @@ +--- +title: MySQL 备份工具 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,备份,mysqldump,mysqlbinlog,xtrabackup +description: 为了保证数据安全,都会对硬件做高可用,防止出现单点故障,但是无论如何都无法取代备份,尤其对于数据库中所保存的数据而言。在此,介绍一下 MySQL 中常用的备份方法。 +--- + +为了保证数据安全,都会对硬件做高可用,防止出现单点故障,但是无论如何都无法取代备份,尤其对于数据库中所保存的数据而言。 + +在此,介绍一下 MySQL 中常用的备份方法。 + + + +## 简介 + +MySQL 自带了 mysqldump、mysqlbinlog 两个备份工具,percona 又提供了强大的 XtraBackup,加上开源的 mydumper,还有基于主从同步的延迟备份、从库冷备等方式,以及基于文件系统快照的备份,其实选择已经多到眼花缭乱。 + +备份本身是为了能在出现故障时,可以迅速、准确恢复到原有状态,如果同时可以简单实用,那就最好不过了。接下来,我们看看这几种常用的备份工具。 + +## mysqldump mydumper + +mysqldump 是最简单的逻辑备份方式,在备份 myisam 表的时候,如果要得到一致的数据,就需要锁表。 + +备份 innodb 表时,加上 \-\-master-data=1 \-\-single-transaction 选项,在事务开始记录下 binlog pos 点,然后利用 mvcc 来获取一致的数据,由于是一个长事务,在写入和更新量很大的数据库上,将产生非常多的 undo,显著影响性能,所以要慎用。 + +这种方式可以针对单表备份,可以导出表结构。 + +mydumper 是 mysqldump 的开源加强版,支持压缩,支持并行备份和恢复,速度比要快很多,但是由于是逻辑备份,仍不是很快。 + + + +## XtraBackup + +这是 percona 开发的一个物理备份工具,可以在线对 InnoDB/XtraDB 引擎的表进行物理备份,相比于 mysqldump 来说,效率很不错。 + +XtraBackup 包含了两个主要的工具 xtrabackup、innobackupex,其中 xtrabackup 只能备份 InnoDB 和 XtraDB 两种数据表;innobackupex 则封装了 xtrabackup 同时可以备份 MyISAM 数据表。 + +如果通过源码进行编译,需要 boost 库才可以,为了简单起见,可以直接安装二进制文件。 + +{% highlight text %} +# yum install libev +# rpm -ivh percona-xtrabackup-xxx.rpm +{% endhighlight %} + +如果是源码安装,可以直接从 [Github - Percona XtraBackup](https://github.com/percona/percona-xtrabackup) 下载。 + +{% highlight text %} +innobackupex +xbcrypt +xbstream +xtrabackup +{% endhighlight %} + +实际上,在 2.3 版本之前,innobackupex 是通过 perl 实现的,而在此之后则采用 C 进行了重写;为了保持向前兼容,该文件实际上是指向 xtrabackup 的一个符号连接。 + +### apply-log + +一般情况下,在备份完成后,数据尚且不能用于恢复操作,因为备份的数据中可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务。也就是说,此时数据文件仍处理不一致状态;需要回滚未提交的事务及同步已经提交的事务至数据文件,使得数据文件处于一致性状态。 + +innobakupex 命令通过的 \-\-apply-log 选项可用于实现上述功能。 + +## 备份 + +如 [MySQL 复制](/blog/mysql-replication.html) 中所示,假设搭建了一个 Master 服务器。 + +### 全量备份 + +可以通过如下步骤进行全量备份。 + +{% highlight text %} +----- 全量备份到/tmp/percon目录下 +$ innobackupex --defaults-file=/tmp/my-master.cnf --user=root \ + --socket=/tmp/mysql-master.sock /tmp/percon +... ... +MySQL binlog position: filename 'mysql-bin.000001', position '1687589', GTID of the last change '0-1-5439' +161205 20:56:49 [00] Writing backup-my.cnf +161205 20:56:49 [00] ...done +161205 20:56:49 [00] Writing xtrabackup_info +161205 20:56:49 [00] ...done +xtrabackup: Transaction log of lsn (4397451) to (4397451) was copied. +161205 20:56:49 completed OK! +{% endhighlight %} + +当结尾为 __completed OK!__ 时,说明备份成功。备份完成之后,在目录下会有几个比较重要的元数据文件,简单介绍如下。 + +{% highlight text %} +$ cat xtrabackup_checkpoints +backup_type = full-backuped # 备份类型 +from_lsn = 0 +to_lsn = 4397451 +last_lsn = 4397451 # LSN号 +compact = 0 +recover_binlog_info = 0 + +$ cat xtrabackup_binlog_info # 备份时binlog文件及其位置 +mysql-bin.000001 1687589 0-1-5439 + +$ cat xtrabackup_binlog_pos_innodb # 同上,不包括GTID信息 +mysql-bin.000001 1687589 +{% endhighlight %} + +在使用 innobackupex 进行备份时,可以使用 \-\-no-timestamp 选项来阻止命令自动创建一个以时间命名的目录;此时,该命令将会创建一个 BACKUP-DIR 目录来存储备份数据。 + + +### 全量恢复 + +假设将其中的一个数据库删除掉,然后可以通过上述的备份恢复该数据库。 + +{% highlight text %} +----- 关闭数据库 +$ mysqladmin -uroot -S /tmp/mysql-master.sock shutdown + +----- 备份源数据库,并新建相同目录 +$ mv /tmp/mysql-master /tmp/mysql-master-bak +$ mkdir /tmp/mysql-master + +----- 准备备份的全量数据 +$ innobackupex --apply-log /tmp/percon/2015-08-23_20-55-32/ +... ... +xtrabackup: starting shutdown with innodb_fast_shutdown = 1 +InnoDB: FTS optimize thread exiting. +InnoDB: Starting shutdown... +InnoDB: Shutdown completed; log sequence number 4406415 +161205 21:27:15 completed OK! + +----- 恢复全量数据 +$ innobackupex --defaults-file=/tmp/my-master.cnf --copy-back --rsync /tmp/percon/2015-08-23_20-55-32/ +... ... +161205 21:34:36 completed OK! + +----- 启动服务器 +$ /opt/mariadb/bin/mysqld --defaults-file=/tmp/my-master.cnf --basedir=/opt/mariadb \ + --datadir=/tmp/mysql-master +{% endhighlight %} + +### 增量备份 + +注意,增量备份需要先有一个全量的备份,假设仍使用上述的备份。 + +{% highlight text %} +mysql> INSERT INTO test.foobar VALUES(5, "Hobart"), (6, "Raymond"); +Query OK, 2 rows affected (0.01 sec) +Records: 2 Duplicates: 0 Warnings: 0 + +----- 执行增量备份 +$ innobackupex --defaults-file=/tmp/my-master.cnf --user=root \ + --socket=/tmp/mysql-master.sock --incremental /tmp/percon/ \ + --incremental-basedir=/tmp/percon/2015-08-23_20-55-23/ --parallel=2 +... ... +xtrabackup: Transaction log of lsn (4408143) to (4408143) was copied. +161205 21:49:34 completed OK! + + +mysql> INSERT INTO test.foobar VALUES(7, "Jeremy"), (8, "Philip"); +Query OK, 2 rows affected (0.01 sec) +Records: 2 Duplicates: 0 Warnings: 0 + +$ innobackupex --defaults-file=/tmp/my-master.cnf --user=root \ + --socket=/tmp/mysql-master.sock --incremental /tmp/percon/ \ + --incremental-basedir=/tmp/percon/2015-08-23_21-48-23/ --parallel=2 +... ... +xtrabackup: Transaction log of lsn (4408743) to (4408743) was copied. +161205 21:59:20 completed OK! +{% endhighlight %} + +### 增量备份恢复 + +对于如上的增量备份,恢复时需要执行如下的 3 个步骤。 + +1. 准备完全备份; + +2. 将增量备份恢复到完全备份,开始恢复的增量备份要添加 \-\-redo-only 参数,到最后一次增量备份要去掉该参数; + +3. 对整体的完全备份进行恢复,回滚未提交的数据。 + +{% highlight text %} +mysql> DROP DATABASE TEST; + +----- 将增量1应用到完全备份 +$ innobackupex --apply-log --redo-only /tmp/percon/2015-08-23_20-55-23/ \ + --incremental-dir=/tmp/percon/2015-08-23_21-48-23/ + +----- 将增量2应用到完全备份 +$ innobackupex --apply-log /tmp/percon/2015-08-23_21-48-23 --incremental-dir=/tmp/pecon/2015-08-23_21-58-40/ + +----- 把所有合在一起的完全备份整体进行一次apply操作,回滚未提交的数据 +$ innobackupex --apply-log /tmp/percon/2015-08-23_20-55-23/ + +----- 恢复数据 +$ innobackupex --defaults-file=/tmp/my-master.cnf --copy-back --rsync /tmp/percon/2015-08-23_20-55-23/ +{% endhighlight %} + +另外,可以对数据进行压缩,在此暂不讨论。 + + +## 执行流程 + +如下是 xtrabackup 执行的详细流程。 + +![mysql percona xtrabackup procedure]({{ site.url }}/images/databases/mysql/pxb-backup-procedure.png){: .pull-center} + + + + +## mysqlbinlog + +上述备份方式,都只能把数据库恢复到备份时的时间点:A) mysqldump、mydumper、snapshot 是备份开始的时间点;B) XtraBackup 是备份结束的时间点。 + +如果要实现 point in time 恢复,还必须备份 binlog;开启 binlog 非常简单,可以通过修改如下配置文件打开。 + +{% highlight text %} +$ vi /etc/my.cnf +binlog_format = mixed +log-bin = mysql-bin +{% endhighlight %} + +其中 mysql 5.6 提供了远程备份 binlog 的能力,我么可以直接通过如下命令远程备份。 + +{% highlight text %} +$ mysqlbinlog --raw --read-from-remote-server --stop-never +{% endhighlight %} + +该命令会伪装成 mysql 从库,从远程获取 binlog 然后进行转储,不过这样状态监控需要单独部署;当然,还可以通过 blackhole 来备份全量的 binlog 。 + +## 常用解析 + +通过 mysqlbinlog 工具可以将 binlog 或者 relay-log 文件解析成文本文件,两者的格式相同。 + +{% highlight text %} +$ mysqlbinlog binlog.0000003 +{% endhighlight %} + +如上命令将会解析 binlog ,并输出该文件中的所有语句及其相关信息,例如每个语句花费的时间、客户发出的线程ID、发出线程时的时间戳等等。 + +也可以通过 mysql 客户端查看 binlog 相关信息。 + +{% highlight text %} +mysql> SHOW BINARY LOGS; # 查看binlog日志 ++-----------------+-----------+ +| Log_name | File_size | ++-----------------+-----------+ +| mysql-bin.000001| 409 | +| mysql-bin.000002| 346 | ++-----------------+-----------+ +2 rows in set (0.00 sec) + +mysql> SHOW BINLOG EVENTS; # 查看执行的SQL,默认是1 +mysql> SHOW BINLOG EVENTS IN 'mysql-bin.000002'; # 查看指定binlog文件的内容 +mysql> SHOW BINLOG EVENTS FROM 213; # 指定位置binlog的内容 +{% endhighlight %} + +接下来看看如何通过 mysqlbinlog 方式提取 binlog 日志。 + +{% highlight text %} +----- 提取整个binlog日志,压缩,执行 +$ mysqlbinlog mysql-bin.000001 +$ mysqlbinlog mysql-bin.000001 | gzip > foobar.sql.gz +$ mysqlbinlog mysql-bin.000001 | mysql -uroot -p + +----- 提取指定位置的binlog日志 +$ mysqlbinlog --start-position="120" --stop-position="332" mysql-bin.000001 + +----- 指定数据库、字符集、开始时间、结束时间的binlog并输出到日志文件 +$ mysqlbinlog --database=test --set-charset=utf8 \ + --start-datetime="2015-08-23 21:15:23" --stop-datetime="2015-08-23 22:15:23" \ + --result-file=foobar.sql mysql-bin.000002 mysql-bin.000003 + +----- 远程提取日志,指定结束时间 +# mysqlbinlog -uroot -p -h127.1 -P3306 --stop-datetime="2015-08-23 22:30:23" \ + --read-from-remote-server mysql-bin.000033 | less + +----- 远程提取使用row格式的binlog日志并输出到本地文件 +# mysqlbinlog -uroot -p -P3306 -h127.1 --read-from-remote-server -vv \ + mysql-bin.000005 > foobar.sql + +常用参数: + -p, --password[=name] # 密码 + -P, --port[=num] # 端口号 + -u, --user=name # 登陆用户名 + -h, --host=name # 主机地址 + -S, --socket=name # 套接字文件 + -d, --database=name # 只列出该数据库的条目(只适用本地日志) + --protocol=name # 连接协议 + --server-id[=num] # 仅提取指定id的binlog日志 + + --set-charset=name # 添加SET NAMES character_set到输出 + -r, --result-file=name # 输出指向给定文件 + -s, --short-form # 只显示包含的语句 + + --start-datetime=name # 等于或大于该值的事件 + --stop-datetime=name # 小于或等于该值的事件 + + -j, --start-position[=num] # 从指定位置开始读取事件 + --stop-position[=num] # 到该位置停止 + -o, --offset[=num] # 跳过前num个条目 + + -f, --force-read + 无法识别二进制事件,打印警告并忽略该事件继续;否则停止 + -H, --hexdump + 在注释中显示日志的十六进制转储,可以用于调试 +{% endhighlight %} + + +mysqlbinlog 可以基于 server_id,以及基于数据库级别提取日志,不支持表级别。 + + + + +## 参考 + +关于 XtraBackup 可以直接参考官方网站 [Percona XtraBackup - Documentation](https://www.percona.com/doc/percona-xtrabackup/index.html)。 + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-09-05-mysql-join_init.md b/_drafts/2015-09-05-mysql-join_init.md new file mode 100644 index 0000000..10d36e7 --- /dev/null +++ b/_drafts/2015-09-05-mysql-join_init.md @@ -0,0 +1,1136 @@ +--- +title: MySQL 笛卡尔积 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,笛卡尔积,join +description: 笛卡尔积实际上就是等值连接操作,MySQL 最初采用的是 Nested-Loop Join,也就是简单的嵌套循环查询,不过针对这种方式,进行了很多优化,在此就看看与 JOIN 相关的一些操作。 +--- + +笛卡尔积实际上就是等值连接操作,MySQL 最初采用的是 Nested-Loop Join,也就是简单的嵌套循环查询,不过针对这种方式,进行了很多优化,在此就看看与 JOIN 相关的一些操作。 + + + +## 简介 + +关系数据库中的查询中,JOIN 是将两个数据集合按照某个条件进行合并形成新的数据集合的操作,其理论基础是关系代数,由一个迪卡尔积运算和一个选择运算构成。 + +连接类型主要有两大类:内连接 (INNER JOIN, 也称为自然连接) 和外连接 (OUTER JOIN);其中外连接又包括左外连接 (LEFT OUTER JOIN)、右外连接 (RIGHT OUTER JOIN) 和全外连接 (FULL OUTER JOIN),不过 MySQL 不支持全外连接。 + +另外,连接条件有三种:自然连接(NATURAL JOIN)、条件连接(ON 谓词条件)和指定属性的连接(USING 属性)。自然连接就是以两个集合的公共属性作为条件进行等值连接,也就是对于 A NATURAL JOIN B 来说,等价于 A JOIN B USING(A1, A2),其中 A1、A2 是 A 和 B 的所有公共属性。还有一种连接类型叫交叉连接 (CROSS JOIN),它等价于无连接条件的 NATURAL INNER JOIN。 + +![mysql joins]({{ site.url }}/images/databases/mysql/joins.svg "mysql joins"){: .pull-center width="90%" } + +INNER JOIN 又称为等值连接,实际是笛卡尔积;LEFT JOIN 和 RIGHT JOIN 在概念上基本相同,只是 LEFT JOIN 会显示左侧的所有数据,如果存在不符合条件的记录则右侧的为 NULL ,通过这一特征可以确定右数据表中缺少了那些数据行 (注意,此时要求判断右行本身为 NOT NULL)。 + +在 MySQL 中没有 FULL JOIN 的概念,但是可以将 LEFT/RIGHT 的结果做 UNION 操作;另外,在 MySQL 中,INNER JOIN、CROSS JOIN、JOIN 的操作相同。 + + +### 示例 + +假设我们有两张表,Table A 是左边的表,Product;Table B 是右边的表,Product Details;通过如下命令创建示例表: + +{% highlight text %} +CREATE TABLE tablea ( + id int(10) unsigned NOT NULL auto_increment, + amount int(10) unsigned default NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; +CREATE TABLE tableb ( + id int(10) unsigned NOT NULL, + weight int(10) unsigned default NULL, + exist int(10) unsigned default NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +INSERT INTO tablea (id,amount) VALUES (1,100),(2,200),(3,300),(4,400); +INSERT INTO tableb (id,weight,exist) VALUES (2,22,0),(4,44,1),(5,55,0),(6,66,1); + + id amount id weight exist + -- ------ -- ------ ----- + 1 100 2 22 0 + 2 200 4 44 1 + 3 300 5 55 0 + 4 400 6 66 1 +{% endhighlight %} + +INNER JOIN 、CROSS JOIN、JOIN 以及 ',' 功能相似,需要注意的是 ',' 不能使用 ON 和 USING 子句。下面是简单的示例: + +{% highlight text %} +----- 其中tablea为内循环,tableb为外循环 +mysql> SELECT * FROM tablea INNER JOIN tableb; ++----+--------+----+--------+-------+ +| id | amount | id | weight | exist | ++----+--------+----+--------+-------+ +| 1 | 100 | 2 | 22 | 0 | +| 2 | 200 | 2 | 22 | 0 | +| 3 | 300 | 2 | 22 | 0 | +| 4 | 400 | 2 | 22 | 0 | +| 1 | 100 | 4 | 44 | 1 | +| 2 | 200 | 4 | 44 | 1 | +| 3 | 300 | 4 | 44 | 1 | +| 4 | 400 | 4 | 44 | 1 | +| 1 | 100 | 5 | 55 | 0 | +| 2 | 200 | 5 | 55 | 0 | +| 3 | 300 | 5 | 55 | 0 | +| 4 | 400 | 5 | 55 | 0 | +| 1 | 100 | 6 | 66 | 1 | +| 2 | 200 | 6 | 66 | 1 | +| 3 | 300 | 6 | 66 | 1 | +| 4 | 400 | 6 | 66 | 1 | ++----+--------+----+--------+-------+ +16 rows in set (0.00 sec) + + +----- 等值JOIN,两者结果集相同,只是USING会自动合并一个id,而且使用USING时列名需相同 +mysql> SELECT * FROM tablea a INNER JOIN tableb b ON a.id = b.id; ++----+--------+----+--------+-------+ +| id | amount | id | weight | exist | ++----+--------+----+--------+-------+ +| 2 | 200 | 2 | 22 | 0 | +| 4 | 400 | 4 | 44 | 1 | ++----+--------+----+--------+-------+ +2 rows in set (0.00 sec) + +MYSQL> SELECT * FROM tablea a INNER JOIN tableb b USING(id); ++----+--------+--------+-------+ +| id | amount | weight | exist | ++----+--------+--------+-------+ +| 2 | 200 | 22 | 0 | +| 4 | 400 | 44 | 1 | ++----+--------+--------+-------+ +2 rows in set (0.00 sec) + + +----- 首先确认tableb.id均为非NULL;然后利用左连接,查找a在b中不存在的id +mysql> SELECT * FROM tableb where id is NULL; +Empty set (0.00 sec) + +mysql> SELECT * FROM tablea a LEFT JOIN tableb b ON a.id = b.id where b.id is NULL; ++----+--------+------+--------+-------+ +| id | amount | id | weight | exist | ++----+--------+------+--------+-------+ +| 1 | 100 | NULL | NULL | NULL | +| 3 | 300 | NULL | NULL | NULL | ++----+--------+------+--------+-------+ +2 rows in set (0.00 sec) + + +----- 使用自连接 +mysql> SELECT a.id, b.id FROM tablea a INNER JOIN tablea b WHERE a.id > b.id; ++----+----+ +| id | id | ++----+----+ +| 2 | 1 | +| 3 | 1 | +| 3 | 2 | +| 4 | 1 | +| 4 | 2 | +| 4 | 3 | ++----+----+ +6 rows in set (0.01 sec) + +----- 获取FULL JOIN +mysql> SELECT * FROM tablea a LEFT JOIN tableb b ON a.id = b.id WHERE b.id IS NULL + UNION + SELECT * FROM tablea a RIGHT JOIN tableb b ON a.id = b.id WHERE a.id IS NULL; ++------+--------+------+--------+-------+ +| id | amount | id | weight | exist | ++------+--------+------+--------+-------+ +| 1 | 100 | NULL | NULL | NULL | +| 3 | 300 | NULL | NULL | NULL | +| NULL | NULL | 5 | 55 | 0 | +| NULL | NULL | 6 | 66 | 1 | ++------+--------+------+--------+-------+ +4 rows in set (0.01 sec) +{% endhighlight %} + +在 MySQL 中,尽量用 INNER JOIN 避免 LEFT JOIN 和 NULL。 + +### ON vs. WHERE + +在 MySQL 中,ON 子句与 WHERE 子句是有所区别的,"A LEFT JOIN B ON 条件表达式" 中的 ON 用来决定如何从 B 表中检索数据行。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据。 + +在匹配阶段 WHERE 子句的条件都不会被使用,仅在匹配阶段完成以后,WHERE 子句条件才会被使用,它将从匹配阶段产生的数据中检索过滤。 + +可以通过如下的例子查看 ON 子句和 WHERE 子句的区别。 + +{% highlight text %} +mysql> SELECT * FROM tablea LEFT JOIN tableb ON (tablea.id = tableb.id) AND tableb.id=2; ++----+--------+------+--------+-------+ +| id | amount | id | weight | exist | ++----+--------+------+--------+-------+ +| 1 | 100 | NULL | NULL | NULL | +| 2 | 200 | 2 | 22 | 0 | +| 3 | 300 | NULL | NULL | NULL | +| 4 | 400 | NULL | NULL | NULL | ++----+--------+------+--------+-------+ +4 rows in set (0.00 sec) + +mysql> SELECT * FROM tablea LEFT JOIN tableb ON (tablea.id = tableb.id) WHERE tableb.id=2; ++----+--------+----+--------+-------+ +| id | amount | id | weight | exist | ++----+--------+----+--------+-------+ +| 2 | 200 | 2 | 22 | 0 | ++----+--------+----+--------+-------+ +1 row in set (0.01 sec) +{% endhighlight %} + +第一条查询使用 ON 条件决定了从 LEFT JOIN 的 tableb 表中检索条件符合 (两个条件都需要符合) 的所有数据行,不符合条件的右侧显示为NULL。 + +第二条查询做了简单的 LEFT JOIN ,然后使用 WHERE 子句从 LEFT JOIN 的数据中过滤掉不符合条件的数据行。 + +{% highlight text %} +mysql> SELECT * FROM tablea LEFT JOIN tableb ON tablea.id = tableb.id AND tablea.amount=100; ++----+--------+------+--------+-------+ +| id | amount | id | weight | exist | ++----+--------+------+--------+-------+ +| 1 | 100 | NULL | NULL | NULL | +| 2 | 200 | NULL | NULL | NULL | +| 3 | 300 | NULL | NULL | NULL | +| 4 | 400 | NULL | NULL | NULL | ++----+--------+------+--------+-------+ +4 rows in set (0.00 sec) +{% endhighlight %} + +所有来自 tablea 表的数据行都被检索到了,但没有在 tableb 表中匹配到记录 (tablea.id = tableb.id AND product.amount=100 条件并没有匹配到任何数据,同上需要满足所有的条件)。 + + + +## 实现 + +数据库中 JOIN 操作的实现主要有三种:嵌套循环连接 (Nested Loop Join)、归并连接 (Merge Join) 和哈希连接 (Hash Join)。其中嵌套循环连接又视情况又有两种变形:块嵌套循环连接和索引嵌套循环连接。 + +在 MySQL 中,采用的是 Nested Loop Join,也就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果;如果还有第三个,则如此往复。 + +因此,MySQL 可以用来做一些 "简单" 的分析查询,当数据量比较大时,即使支持 Hash Join 的传统 MPP 架构的关系型数据库也不太合适,这类需求应该交给更为专业的 Hadoop 集群来计算。 + +> 数仓中使用的 Massively Parallel Processing 模型,是将任务并行的分散到多个服务器和节点上,在每个节点上计算完成后,将各自部分的结果汇总在一起得到最终的结果;与 Hadoop 类似,不过前者使用的是 SQL 后者时 MapReduce 程序。 + +接下来看看各个方法是如何实现的,例如,有两张表 R 和 S,R 共占有 M 页,S 共占有 N 页。r 和 s 分别代表元组,而 i 和 j 分别代表第 i 或者第 j 个字段,也就是后文提到的 JOIN 字段。 + + + + +## 原理详解 + +表连接的方式包括了 join, semi-join, outer-join, anti-join/anti-semi-join;实现方式包括了 nested loop, merge, hash,下面使用简单的 nested loop 介绍。 + +其他的连接方式还有 cross join,用于获取记录的完整笛卡尔积,不能使用 ON 谓词,通常使用不多。 + +
    • +inner-join
      +自然连接。查看申请表中相关学校的信息。 +
      +select * from College c inner join Apply a on c.name = a.name;
      +
      +for x in ( select * from tablea )
      +    for y in ( select * from tableb )
      +        if ( x.id == y.id )
      +            OutPut_Record(x.*, y.*)
      +        end if
      +    end loop
      +end loop
      +
      +内连接满足交换律:"A inner join B" 和 "B inner join A" 是相等的。

    • + +outer-join
      +2 个数据源键值一一比较,返回相互匹配的;但如果在另外一个 row source 没有找到匹配的也返回记录,不过是 NULL。 +
      +select * from tablea a left join tableb b on a.id = b.id;
      +
      +for x in ( select * from tablea )
      +    find_flag=false;
      +    for y in ( select * from tableb)
      +        if ( x.id == y.id )
      +            OutPut_Record(x.*, y.*)
      +            find_flag=true
      +        end if
      +    end loop
      +    if ( find_flag == false )
      +        OutPut_Record(x.*, null)
      +    end if
      +end loop
      +

    • + +semi-join
      +从一个表中返回的行与另一个表中数据行进行不完全联接查询,查找到匹配的数据行就返回,不再继续查找。

      + +与 join 和 outer join 不同,semi-join 和 anti-semi-join 没有明确的语法实现,可以通过 exits 和 not exits 实现。 +
      +select * from Student where exists (select null from Apply where Apply.id = Student.id);
      +select * from Student where exists (select * from Apply where Apply.id = Student.id);
      +
      +for x in ( select * from tablea )
      +    for y in ( select * from tableb)
      +        if ( x.id == y.id )
      +            OutPut_Record(x)
      +            Break;
      +        end if
      +    end loop
      +end loop
      +

    • + + +anti-join
      +现在要求我们找出还没有申请学校的学生信息 +
      +select * from Student where not exists (select * from Apply where Apply.id = Student.id);
      +
      +for x in ( select * from Student )
      +    for y in ( select * from Apply )
      +        if ( x.deptno != y.deptno )
      +            OutPut_Record(x.dname,y.deptno)
      +        End if
      +    end loop
      +end loop
      +
      +
    + +

    + + + +### 其它 + +尽量避免子查询,而用 JOIN。 + +STRAIGHT JOIN 完全等同于 INNER JOIN,不过可以实现强制多表的载入顺序,从左到右。默认,JOIN 语法是根据 "哪个表的结果集小,就以哪个表为驱动表" 来决定谁先载入的,而 STRAIGHT JOIN 会强制选择其左边的表先载入。 + +使用 NATURAL JOIN 时,MySQL 将表中具有相同名称的字段自动进行记录匹配,而这些同名字段类型可以不同。因此,NATURAL JOIN 不用指定匹配条件。 + + + + + + + + +### Nested-Loop Join, NLJ + +从第一个表每次读一行数据,传递到一个嵌套循环(处理join中的下一个表)。 +这个处理重复次数跟join中涉及的表相同? +例子: +Table Join Type +t1 range +t2 ref +t3 ALL +逻辑处理: +for each row in t1 matching range { + for each row in t2 matching reference key { + for each row in t3 { + if row satisfies join conditions, + send to client + } + } +} + +### Block Nested-Loop Join, BNL + +通过缓存外层循环读的行,来降低内层表的读取次数。比如: 10行数据读入到buffer中, +然后buffer被传递到内层循环,内层表读出的每一行都要跟这个缓存的10行依次做对比, +这样就降低了内层表数据的读取次数。 +使用条件: +1] join_buffer_size决定了每一个join buffer的大小 +2] 只有当join type是 all or index(没有合适的索引,使用全索引或者全表扫描的场景), + range的时候才会使用。5.6中,外连接也可以用buffer了。 +3] 每一个需要buffer的join都会申请一个独立的buffer,也就是说一个查询可能使用多个join buffer。 +4] 第一个非常量表是不会使用join buffer的。 +5] join buffer在执行join之前申请,在查询完成后释放。 +6] join buffer只保存跟join有关的列,而不是整行 + +explain 列显示:Using join buffer (Block Nested Loop) + +逻辑处理: +for each row in t1 matching range { + for each row in t2 matching reference key { + store used columns from t1, t2 in join buffer + if buffer is full { + for each row in t3 { + for each t1, t2 combination in join buffer { + if row satisfies join conditions, + send to client + } + } + empty buffer + } + } +} + +if buffer is not empty { + for each row in t3 { + for each t1, t2 combination in join buffer { + if row satisfies join conditions, + send to client + } + } +} + +### Multi-Range Read, MRR + +当一个表很大,不能存储到存储引擎的缓存的时候,使用二级索引做范围扫描会引起大量磁盘随机读。 +MRR的存在就是为了优化这些随机读。mysql开始只扫描跟行相关的索引和收集key,然后把这些key排序, +最后根据排好序的primary key来从基础表获取数据。 MRR的目的是,降低随机的磁盘IO,替换成相对更 +有顺序的IO。 +MRR的好处: +1、随机IO转换成顺序IO。 +2、批量处理请求 + +优化场景: +A: MRR可以用来做innodb,myiasm的索引范围扫描和等值join操作。 +1、索引元组累积到一个buffer +2、buffer中的元组根据rowid排序 +3、根据排序好的索引元组顺序去获取数据行 +4、当不需要回表访问的时候,MRR就失去意义了(比如覆盖索引) + +当使用MRR的时候 explain出现:Using MRR标志 + +存储引擎使用read_rnd_buffer_size 的值来确定MRR时的buffer大小。 + +### Batched Key Access, BKA + +当使用索引访问第二个join对象的时候,跟BNL类似,BKA使用一个join buffer + +来收集第一个操作对象生成的相关列值。BKA构建好key后,批量传给引擎层做索引 +查找。key是通过MRR接口提交给引擎的,这样,MRR使得查询更有效率。 +BKA使用join buffer size来确定buffer的大小,buffer越大,访问右侧表就越 +顺序。 +使用BAK的条件: +SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on'; + + + + + + + + + + + + + +

    嵌套循环连接

    +嵌套循环连接(Nested Loop Join),嵌套循环连接由两个 FOR 循环构成,其思路相当的简单和直接,对于关系 R 的每个元组 r 将其与关系 S 的每个元组 s 在 JOIN 条件的字段上直接比较并筛选出符合条件的元组。 + +Nest Loop Join的操作过程很简单,很想我们最简单的排序检索算法,两层循环结构。进行连接的两个数据集合(数据表)分别称为外侧表(驱动表)和内测表(被驱动表)。首先处理外侧表中每一行符合条件的数据,之后每一行数据和内测表进行连接匹配操作。最后可以获取到结果集合。 + +http://database.51cto.com/art/200904/117947.htm +

    +foreach tuple r in R do
    +    foreach tuple s in S do
    +        if ri == sj then add r,s to result
    +
    +嵌套循环连接比较通用,不需要索引,且不管是什么样的连接条件,该算法都适用。对于任何类型的JOIN,该算法都只需要做稍微的调整就能进行运算。如对于自然连接(Natural Join)该算法只需要在将(X, Y)加入结果之前消除其中的重复属性。

    + +但是,嵌套循环连接的代价比较大。因为该算法过程中需要逐一比较R和S中的每一个元组,当数据规模较大而不能完全放入内存中时其引起的磁盘与内存交换比较频繁,即使数据能够完全放入内存,则嵌套循环连接执行过程中也会引起CPU的CACHE命中率低下,从而严重影响系统效率。

    + +被联结的表所处内层或外层的顺序对磁盘I/O开销有着非常重要的影响。而CPU开销相对来说影响较小,主要是元组读入内存以后(in-memory)的开销,是 O (n * m)。对于I/O开销,根据 page-at-a-time 的前提条件,I/O cost = M + M * N,翻译一下就是 I/O的开销 = 读取M页的I/O开销 + M次读取N页的I/O开销。

    + +使用小结:
    • + 适用于一个集合大而另一个集合小的情况(将小集合做为外循环),I/O性能不错。
    • + + 当外循环输入相当小而内循环非常大且有索引建立在JOIN字段上时,I/O性能相当不错。
    • + + 当两个集合中只有一个在JOIN字段上建立索引时,一定要将该集合作为内循环。
    • + + 对于一对一的匹配关系(两个具有唯一约束字段的联结),可以在找到匹配元组后跳过该次内循环的剩余部分(类似于编程语言循环语句中的continue)。 +
    +

    + +块嵌套循环连接(Nested Block Loop Join),块嵌套循环连接是采用了分块策略的嵌套循环连接,不过它有四层循环,最外面两层是块循环,里面两层是块内循环。对于关系R和S以及连接条件P,块嵌套循环连接的回想是将R和S分成块,对于逐一连接R和S中的每一块。其算法如下: +
    +For each block A in R do
    +    For each block B in S do
    +        For each tuple X in A do
    +            For each tuple Y in B do
    +                If P(X, Y)
    +                    Then Result=Result+(X, Y)
    +            End
    +        End
    +    End
    +End
    +
    +相比嵌套循环连接,块嵌套循环连接在时间复杂度上没有改进,但是由于块循环连接是以块为单位进行处理,减少了磁盘交换次数。需要说明的是,对于外层循环的R而言,它并没有节省磁盘交换次数,关键在于每取S中的一块,处理了R中一块的数据,而不是R中的一条数据。如果一块有N个元组,则块嵌套循环连接节省了N倍的磁盘交换次数。

    + +索引嵌套循环连接,对于关系R和S以及连接条件P,则如果S中存在一种索引,无论是临时和还是永久的,对于R中的任一元组X,能够通过该索引找到S中满足条件P的元组,则可以用索引查找替代文件扫描。这样的算法也更加简单: +
    +For each tuple T X in R do
    +    Find all tuples {Y} in S that P(X, Y)
    +    For each y in {Y} do
    +        Result=Result+(X,y)
    +    End
    +End
    +
    +索引嵌套循环连接在已经有索引的情况下使用,或者为了进行连接操作专门建立一个索引。如果已经有了索引,则其时间复杂度比较小,只需要扫描一个表,然后根据索引查找另一个表。 + +Nested loops join支持包括相等连接谓词和不等谓词连接在内的所有连接谓词。 + +Nested loops join支持什么类型的逻辑连接? + +Nested loops join支持以下类型的逻辑连接: + +* Inner join +* Left outer join +* Cross join +* Cross apply and outer apply +* Left semi-join and left anti-semi-join + +Nested loops join不支持以下逻辑连接: + +* Right and full outer join +* Right semi-join and right anti-semi-join + + + +为什么Nested loops join 只支持左连接? + +我们很容易扩展Nested loops join 算法来支持left outer 和semi-joins.例如,下边是左外连接的伪码。 +我们可以写出相似的代码来实现 left semi-join 和 left anti-semi-join. + +for each row R1 in the outer table + begin + for each row R2 in the inner table + if R1 joins with R2 + return (R1, R2) + if R1 did not join + return (R1, NULL) + end + +这个算法记录我们是否连接了一个特定的外部行。如果已经穷尽了所有内部行,但是没有找到一个 +符合条件的内部行,就把该外部行做为NULL扩展行输出。 + +那么我们为什么不支持right outer join呢。在这里,我们想返回符合条件的行对(R1,R2) +和不符合连接条件的(NULL,R2)。问题是我们会多次扫描内部表-对于外部表的每行都要扫描一次。 +在多次扫描过程中我们可能会多次处理内部表的同一行。这样我们就无法来判断某一行到底符合 +不符合连接条件。更进一步,如果我们使用index join,一些内部行可能都不会被处理,但是这些行在 +外连接时是应该返回的。 + + +幸运的是right outer join可以转换为left outer join,right semi-join可以转换为left semi-join, +所以right outer join和semi-joins是可以使用nested loops join的。但是,当执行转换的时候可能会 +影响性能。例如,上边方案中的"Customer left outer join Sales",由于表内部表Sales有聚集索引,所以 +我们在连接过程中可以使用索引探寻。如果"Customer right outer join Sales" 转换为 "Sales left outer +join Customer”,我们则需要在Customer表上具有相应的索引了。 + + + +full outer joins是什么情况呢? + +nested loops join完全支持outer join.我们可以把"T1 full outer join T2"转换为"T1 left outer join T2 +UNION T2 left anti-semi-join T1".可以这样来理解,将full outer join转换为一个左连接-包含T1和T2所有的 +符合条件的连接行和T1表里没有连接的行,然后加上那些使用anti-semi-join从T2返回的行。下边是转换过程: + + + +select * +from Customers C full outer join Sales S +on C.Cust_Id = S.Cust_Id + +Rows Executes + + +5 1 |--Concatenation + +4 1 |--Nested Loops(Left Outer Join, WHERE:([C].[Cust_Id]=[S].[Cust_Id])) + +3 1 | |--Table Scan(OBJECT:([Customers] AS [C])) + +12 3 | |--Clustered Index Scan(OBJECT:([Sales].[Sales_ci] AS [S])) + +0 0 |--Compute Scalar(DEFINE:([C].[Cust_Id]=NULL, [C].[Cust_Name]=NULL)) + +1 1 |--Nested Loops(Left Anti Semi Join, OUTER REFERENCES:([S].[Cust_Id])) + +4 1 |--Clustered Index Scan(OBJECT:([Sales].[Sales_ci] AS [S])) + +3 4 |--Top(TOP EXPRESSION:((1))) + +3 4 |--Table Scan(OBJECT:([Customers] AS [C]), WHERE:([C].[Cust_Id]=[S].[Cust_Id])) + + +注意:在上边的例子中,优化器并选择了聚集索引扫描而不是探寻。这完全是基于成本考虑而做出的决定。表非常小(只有一页) +所以扫描或探寻并没有什么性能上的区别。 + + + +NL join好还是坏? + +实际上,并没有所谓"最好"的算法,连接算法也没有好坏之分。每一种连接方式在正确的环境下性能非常好, +而在错误的环境下则非常差。因为nested loops join的复杂度是与驱动表大小和内部表大小乘积成比例的,所以在驱动表比较小 +的情况下性能比较好。内部表不需要很小,但是如果非常大的话,在具有高选择性的连接列上建立索引将很有帮助。 + +一些情况下,Sql Server只能使用nested loops join算法,比如Cross join和一些复杂的cross applies,outer applies, +(full outer join是一个例外)。如果没有任何相等连接谓词的话nested loops join算法是Sql Server的唯一选择。 +--> +

    + + + +

    归并连接 (Merge Join)

    +Nested Loop 一般在两个集合都很大的情况下效率就相当差了,而 Sort-Merge 在这种情况下就比它要高效不少,尤其是当两个集合的 JOIN 字段上都有聚集索引 (clustered index) 存在时,Sort-Merge 性能将达到最好。

    + +归并连接算法主要用于计算自然连接和等值连接,主要有两个步骤:A) 按 JOIN 字段进行排序;B) 对两组已排序集合进行合并排序,从来源端各自取得数据列后加以比较。

    + +假设有关系 R 和 S,则在进行连接之前先让 R 和 S 是有序的,然后分别对两个表进行扫描一遍即可完成。 +

    +R、S 中元组按连接属性从小到大排序,i=j=0
    +while i<len(R) AND j<len(S) do
    +    while S[j] != R[i] do
    +        while S[j]<R[i] do
    +            j=j+1
    +        end
    +        while S[j]>R[i] do
    +            i=i+1
    +        end
    +    end
    +    m=i, n=j
    +    while S[m]=S[i] do
    +        M=m+1
    +    end
    +    while R[n]=R[j] do
    +        N=n+1
    +    end
    +    for each tuple X from R[i] to R[m - 1] do
    +        for each tuple Y from S[j] to S[n-1] do
    +            Result=Result+(X, Y)
    +        end
    +    end
    +end
    +
    +归并连接执行起来非常高效,其时间复杂度是线性的O(n),其中 n 为 R 和 S 中元组数最多的那个关系的元组个数。但是它只能进行等值连接和自然连接,对于其它谓词的连接显得力不从心,并且还要求连接之前对元组进行全排序。 +

    + + +

    散列连接 (Hash Join)

    + +

    + + + + + +JOIN实现实例 + +本部分主要调研了市场主流数据库对于连接的实现方式。在介绍这些内容之前,首先分析一下索引对于JOIN操作的影响。 + +对于嵌入循环连接,索引有可能有用,但在归并连接和散列连接中,有无索引对JOIN操作并没有任何影响。 + +再总结一下各种JOIN适用的场景。嵌套循环连接比较通用,可用于任何类型的连接。而归并连接和歼列连接只能用于等值连接和自然连接。 +MYSQL + +在MYSQL中,只有一种JOIN算法,就是最简单最通用的嵌套循环连接。它没有散列连接和排序归并连接。MYSQL中JOIN操作操作最好避免,特别是对于大表的JOIN,速度特别低下。 + +但是对于效率,MYSQL社区谈到了HASH JOIN,但却把JOIN效率的提高寄希望于INTEL的SSD技术。即半导体硬盘带来的速度提高。 +ORACLE + +Oracle数据库中对三种连接算法都有实现,并分不同情况分别使用不同的JOIN算法。 + +嵌入循环连接是最慢的算法,只有在不得已时才使用。索引能够有效地提高其速度。 + +归并连接比嵌入循环连接快的多,但它也不是Oracle优先选择的算法。 + +Oracle Hash join 是一种非常高效的join 算法,主要以CPU(hash计算)和内存空间(创建hash table)为代价获得最大的效率。Hash join一般用于大表和小表之间的连接,我们将小表构建到内存中,称为Hash cluster,大表称为probe表。 +SQL SERVER 7.0/2000 + +Microsoft SQL Server 7.0/2000的JOIN操作有三种类型:Nested-Loop Join, Merge Join和 Hash Join。 + +嵌套循环连接 + +Nested-Loop操作从关联的两个table中,选择一个作为外层循环,为每一条记录在另一个table中循环查找匹配的结果。作为外层循环的table为outer table,内层循环的table为inner table。在执行计划中,不管是图形还是文本显示方式,outer table位于上方,inner table位于下方。 SQL Server在自动选择join type时,大多数情况下使用Nested-Loop join的条件是:关联的两个Table中,有一个数据量比较小(记录数在2000左右以下),另外一个数据量大的Table又有对于关联条件可用的索引。 + +另外一种情况,假如查询语句类似如下: SELECT [...] FROM A INNER JOIN B ON A.?=B.? WHERE A.?=? 如果A、B的记录数都是几万到几十万,B中有ON A.?=B.?可用的索引,A中有WHERE A.?=?可用的索引,并且A通过WHERE子句条件的过滤后记录数比较小(2000左右以下),这种情况下仍然会使用Nested-Loop join。但是如果WHERE子句中既包含A也包含B的限制条件,则会选择Merge或Hash join了。从Nested-Loop join的选择条件可以看出,Nested-Loop join的执行非常高效。outer table的记录数很小,因此外层循环次数少;在inner table中搜索匹配记录时使用索引,就算inner table的数据量非常大,搜索也是相当快而有效的。 + +在查询语句中,可以强制SQL Server使用Nested-Loop方式关联两个table,例如:SELECT A.PONO,B.VCODE,B.VNAME FROM TBLPO A INNER JOIN TBLVENDOR B ON A.VENDORID=B.ID OPTION (LOOP JOIN) 。如果没有十分的把握,让SQL Server Optimizer自动选择join type。Optimizer总是尝试合理的确定inner talbe和outer table。通常情况下总是选择有可用索引的一个作为inner table,即使这个table的数据量可能会比另外一个多。如果两个table都没有可用索引,则选择数据量较小的一个作为outer table。这种情况下,如果数据量大的table记录数太多或内存有限,无法将inner table的数据全部读入内存中,则SQL Server会尝试将数据量小的作为inner table,以使inner table的数据全部驻留内存中,提高inner table循环的速度。在进行Nested-Loop join操作时,SQL Server Optimizer可能会对inner table进行一次排序,以提高对inner table搜索的速度 +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    代码实现

    +入口函数为 handle_select()@sql/sql_select.cc,首先对union进行判断,如果查询SQL中不包含union关键字,函数直接执行mysql_select()函数处理;否则,将执行mysql_union()函数。

    + +mysql_select()函数是单个select查询的“入口函数”,该函数执行SQL优化的一些列操作。区别于mysql_select()函数,mysql_union()函数逐个执行union中单个select查询优化的一系列操作。执行SQL优化的过程调用相同的函数实现,仅在执行前期的处理过程不一致。

    + +在mysql_select()中执行如下的优化顺序,join->prepare() join->optimize() join->exec() select_lex->cleanup()。

    + +JOIN::optimize(); sql/sql_select.cc
    +这是整个查询优化器的核心内容,查询优化主要对Join进行简化、优化where条件、优化having条件、裁剪分区partition(如果查询的表是分区表)、优化count()/min()/max()聚集函数、统计join的代价、搜索最优的join顺序、生成执行计划、执行基本的查询、优化多个等式谓词、执行join查询、优化distinct、创建临时表存储临时结果等操作。 + +


    +


    +将 outer-join 转换为 inner-join。 +

    +SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a WHERE t2.b < 5         转化为如下
    +SELECT * FROM t1 INNER JOIN t2 ON t2.a=t1.a WHERE t2.b < 5        进一步转化为
    +SELECT * FROM t1, t2 ON t2.a=t1.a WHERE t2.b < 5 AND t2.a=t1.a
    +
    + +MySQL 解析完 SQL 后,会生成很多 item 类,而 MySQL 是将任何 select 都转换为 JOIN 来处理的。 +
    +JOIN::optimize()         @sql/sql_optimizer.cc
    +  |-flatten_subqueries()                   优化IN子查询为semi-join
    +  |-handle_derived()                       处理衍生表
    +  |- ... ...                               对于limit的处理
    +  |-simplify_joins()                       如果有可能就将outer-join转换为inner-join
    +  |-record_join_nest_info()
    +  |-build_bitmap_for_nested_joins()        bitmap?????
    +  |-optimize_cond()                        优化查询条件
    +  |   |-build_equal_items()                等价传递
    +  |   |-propagate_cond_constants()         常量传递
    +  |   |-remove_eq_conds()                  剪除无效条件
    +  |
    +  |-optimize_cond()                        优化where、having查询条件,其中第二个参数为where条件,同上
    +  |-opt_sum_query()                        优化count(*),min(),max()
    +  |-make_tmp_tables_info()                 如果需要则构建临时表
    +  |-get_sort_by_table()                    是否有一个表在order by中,并且和group是相容的?
    +  |
    +  |-make_join_statistics()                 1. 计算最好的join顺序以及初始化join结构
    +  | |-update_ref_and_keys()                1.1 二级索引的ref查询
    +  | | |-add_key_fields()
    +  | |   |-add_key_equal_fields()
    +  | |
    +  | |-get_quick_record_count()             1.2 range查询优化,计算全表扫描以及possible_keys中代价最小的
    +  | | |-test_quick_select()
    +  | |   |-get_mm_tree()                    生成所有查询条件的一棵查询树
    +  | |   |-get_best_group_min_max()
    +  | |   |-get_key_scans_params()           获得最优的范围查询计划
    +  | |   |-get_best_ror_intersect()
    +  | |   |-get_best_index_intersect()
    +  | |   |-get_best_disjunct_quick()
    +  | |
    +  | |-pull_out_semijoin_tables()
    +  | |-optimize_semijoin_nests()
    +  | |-choose_plan()                        1.3 计算最优的join顺序
    +  |   |-optimize_straight_join()           如果采用STRAIGHT_JOIN的hint
    +  |   |-greedy_search()                    否则使用贪婪搜索
    +  |
    +  |-make_join_select()                     2. 对优化结果再次调整
    +  |
    +  |-ORDER_with_src()                       尝试优化distinct,group by,order by等
    +  |-test_if_subpart()
    +  |-plan_is_const()
    +  |
    +  |-make_join_readinfo()                   3. Plan Refinement Stage 根据表访问的最优路径,选择对应的执行方式。同时,进行索引覆盖扫描优化,将全表扫描转化为索引覆盖扫描。
    +    |-setup_semijoin_dups_elimination()    关于semi-join的优化
    +    |-check_join_cache_usage_for_tables()
    +  |-make_tmp_tables_info()
    +
    + + + +scan_time= (double) records / TIME_FOR_COMPARE + 1; +read_time= (double) head->file->scan_time() + scan_time + 1.1; + + + +

    +range 的解析是通过 get_mm_tree() 函数实现,如果要查看 range 的代码,或者解析后的结果可以在 get_best_group_min_max() 设置断点。 +
    +class SEL_ARG :public Sql_alloc
    +
    +
    +#0  get_mm_leaf ()                       at sql/opt_range.cc:8198
    +#1  get_mm_parts ()                      at sql/opt_range.cc:8166
    +#2  get_func_mm_tree ()                  at sql/opt_range.cc:7812
    +#3  get_full_func_mm_tree ()             at sql/opt_range.cc:7920
    +#4  get_mm_tree ()                       at sql/opt_range.cc:8107
    +#5  SQL_SELECT::test_quick_select ()     at sql/opt_range.cc:3125
    +#6  get_quick_record_count ()            at sql/sql_select.cc:3345
    +#7  make_join_statistics ()              at sql/sql_select.cc:3953
    +#8  JOIN::optimize_inner ()              at sql/sql_select.cc:1337
    +#9  JOIN::optimize ()                    at sql/sql_select.cc:1022
    +#10 mysql_select ()                      at sql/sql_select.cc:3294
    +#11 handle_select ()                     at sql/sql_select.cc:373
    +#12 execute_sqlcom_select ()i            at sql/sql_parse.cc:5274
    +#13 mysql_execute_command () at sql/sql_parse.cc:2562
    +#14 mysql_parse () at sql/sql_parse.cc:6529
    +#15 dispatch_command () at sql/sql_parse.cc:1308
    +#16 do_command () at sql/sql_parse.cc:999
    +#17 do_handle_one_connection () at sql/sql_connect.cc:1378
    +#18 handle_one_connection () at sql/sql_connect.cc:1293
    +#19 start_thread (arg=0x7f260df64700) at pthread_create.c:308
    +#20 clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113
    +
    +
    +
    +
    +#0  add_key_field (join=0x7f25e1822c90, key_fields=0x7f260df62168, and_level=0, cond=0x7f25e1822a60, field=0x7f25e1856c20,
    +    eq_func=false, value=0x7f25e1822af8, num_values=1, usable_tables=18446744073709551615, sargables=0x7f260df622b8)
    +    at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:4355
    +#1  0x00000000006831fc in add_key_equal_fields (join=0x7f25e1822c90, key_fields=0x7f260df62168, and_level=0, cond=0x7f25e1822a60,
    +        field_item=0x7f25e18228c8, eq_func=false, val=0x7f25e1822af8, num_values=1, usable_tables=18446744073709551615,
    +            sargables=0x7f260df622b8) at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:4549
    +#2  0x0000000000683d2b in add_key_fields (join=0x7f25e1822c90, key_fields=0x7f260df62168, and_level=0x7f260df62174,
    +                cond=0x7f25e1822a60, usable_tables=18446744073709551615, sargables=0x7f260df622b8)
    +    at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:4771
    +#3  0x0000000000685237 in update_ref_and_keys (thd=0x7f25f3bfa070, keyuse=0x7f25e1822f90, join_tab=0x7f25e1823ab8, tables=1,
    +        cond=0x7f25e1822a60, normal_tables=18446744073709551615, select_lex=0x7f25f3bfe0b8, sargables=0x7f260df622b8)
    +    at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:5250
    +#4  0x00000000006805e0 in make_join_statistics (join=0x7f25e1822c90, tables_list=..., conds=0x7f25e1822a60,
    +        keyuse_array=0x7f25e1822f90) at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:3590
    +#5  0x0000000000678b74 in JOIN::optimize_inner (this=0x7f25e1822c90)
    +            at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:1337
    +#6  0x0000000000677abc in JOIN::optimize (this=0x7f25e1822c90)
    +                at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:1022
    +#7  0x000000000067f76e in mysql_select (thd=0x7f25f3bfa070, rref_pointer_array=0x7f25f3bfe330, tables=0x7f25e18222c0, wild_num=1,
    +                    fields=..., conds=0x7f25e1822a60, og_num=0, order=0x0, group=0x0, having=0x0, proc_param=0x0, select_options=2147748608,
    +                        result=0x7f25e1822c70, unit=0x7f25f3bfd9c8, select_lex=0x7f25f3bfe0b8)
    +    at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:3294
    +#8  0x0000000000675ce6 in handle_select (thd=0x7f25f3bfa070, lex=0x7f25f3bfd900, result=0x7f25e1822c70, setup_tables_done_option=0)
    +        at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_select.cc:373
    +#9  0x000000000064a5f7 in execute_sqlcom_select (thd=0x7f25f3bfa070, all_tables=0x7f25e18222c0)
    +            at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_parse.cc:5274
    +#10 0x00000000006429ba in mysql_execute_command (thd=0x7f25f3bfa070)
    +                at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_parse.cc:2562
    +#11 0x000000000064d172 in mysql_parse (thd=0x7f25f3bfa070,
    +                    rawbuf=0x7f25e1822088 "select * from employees where emp_no > 1000 limit 1", length=51, parser_state=0x7f260df636a0)
    +    at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_parse.cc:6529
    +#12 0x000000000063fb5b in dispatch_command (command=COM_QUERY, thd=0x7f25f3bfa070,
    +        packet=0x7f25f97eb071 "select * from employees where emp_no > 1000 limit 1", packet_length=51)
    +    at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_parse.cc:1308
    +#13 0x000000000063eda0 in do_command (thd=0x7f25f3bfa070)
    +        at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_parse.cc:999
    +#14 0x000000000075beb8 in do_handle_one_connection (thd_arg=0x7f25f3bfa070)
    +            at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_connect.cc:1378
    +#15 0x000000000075bbfd in handle_one_connection (arg=0x7f25f3bfa070)
    +                at /home/andy/Workspace/databases/mariadb/mariadb-10.0.20/sql/sql_connect.cc:1293
    +#16 0x00007f260d96ddc5 in start_thread (arg=0x7f260df64700) at pthread_create.c:308
    +#17 0x00007f260c62828d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:113
    +
    +
    + + + + + +
    • +simplify_joins(); sql/sql_select.cc
      +simplify_joins()函数简化join操作。该函数将可以用内连接替换的外连接进行替换,并对嵌套join使用的表、非null表、依赖表等进行计算。该函数的实现是一个递归过程,在递归中,所有的特征将被计算,所有可以被替换的外连接被替换,所有不需要的括号也被去掉。此外,该函数前的注释中,给出了多个例子,用于解释外连接替换为内连接的一些特征。 +
    +Sample 1: +SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a WHERE t2.b < 5 ==> +SELECT * FROM t1 INNER JOIN t2 ON t2.a=t1.a WHERE t2.b < 5 ==> +SELECT * FROM t1, t2 WHERE t2.b < 5 AND t2.a=t1.a +Sample 2: +SELECT * from t1 LEFT JOIN (t2, t3) ON t2.a=t1.a t3.b=t1.b WHERE t2.c < 5 ==> +SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a t3.b=t1.b +Sample 3: +SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a LEFT JOIN t3 ON t3.b=t2.b WHERE t3 IS NOT NULL +==> +SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a, t3 WHERE t3 IS NOT NULL AND t3.b=t2.b +==> +SELECT * FROM t1, t2, t3 WHERE t3 IS NOT NULL AND t3.b=t2.b AND t2.a=t1.a + +build_bitmap_for_nested_joins(); sql/sql_select.cc +函数为每个嵌套join在bitmap中分配一位,该函数也是一个递归过程,返回第一个没有使用的bit。 + +optimize_cond():(sql/sql_select.cc:9405) +分别对where条件和having条件建立多个等价谓词,并且删除可以被推导出的等价谓词。该函数调用build_equal_items()函数(sql/sql_select.cc:8273)用于该处理过程,该函数是一个递归过程,并在函数前,列举了多个实例,供参考和理解。 +Sample 1: +SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t1.a=t3.a AND t2.a=t4.a WHERE t1.a=t2.a; +==> +SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON multiple equality (t1.a,t2.a,t3.a,t4.a) +Sample 2: +SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t1.a=t3.a AND t3.a=t4.a WHERE t1.a=t2.a +==> +SELECT * FROM (t1 LEFT JOIN (t3,t4) ON t1.a=t3.a AND t3.a=t4.a),t2 WHERE t1.a=t2.a +Sample 3: +SELECT * FROM (t1,t2) LEFT JOIN (t3,t4) ON t2.a=t4.a AND t3.a=t4.a WHERE t1.a=t2.a +==> +SELECT * FROM (t2 LEFT JOIN (t3,t4)ON t2.a=t4.a AND t3.a=t4.a), t1 WHERE t1.a=t2.a + 此外,该函数调用propagate_cond_constants()函数(sql/sql_select.cc:8763),用于处理(a=b and b=1)为(a=1 and b=1)操作,该函数也是一个递归过程。调用remove_eq_conds()函数(sql/sql_select():9617),用于删除(a=a以及1=1、1=2)条件,该函数调用了internal_remove_eq_conds()递归处理函数(sql_select.cc:9462)。 + +prune_partitions():(sql/opt_range.cc:2611) +prune_partitions()函数通过分析查询条件,查找需要的数据分区。该函数仅对分区表有效,普通的表不做处理。 + + + +make_join_statistics():(sql/sql_select.cc:2651) + make_join_statistics()函数用于计算最好的join执行计划。该过程较复杂,分为以下几个步骤: +首先如果join是外连接,利用Floyd Warshall(弗洛伊德)算法建立传递闭包依赖关系,该过程较复杂,具体参考该算法的一些内容。 +之后进行主键或唯一索引的常量查询处理,该过程是一个循环过程,与执行计划中的ref类型有关。 +接下来,计算每个表中匹配的行数以及扫描的时间,该过程得到的结果是估计的值,并非实际查询需要的记录数和时间。并且调用了一下的make_select()函数和get_quick_recond_count()函数,具体内容参照以下相应函数的分析。该过程从生成执行计划来看,与index和full类型有关。 +最后,根据以上统计的记录数和时间,选择一个最优的join顺序。实际该处理逻辑,调用choose_plan()函数处理,参照以下choose_plan()函数分析。 +该函数简化后的流程图如下图所示: + + + + + + +MySQL查询优化器源码分析 http://m.blog.chinaunix.net/uid-26896862-id-3218584.html + + +Optimizer阶段 +execute_sqlcom_select()函数中,调用了handle_select()函数,而该函数正是处理SQL查询的“入口函数” +然后调用的是mysql_select() + + + + + + + + +词法分析MYSQLlex + 客户端向服务器发送过来SQL语句后,服务器首先要进行词法分析,而后进行语法分析,语义分析,构造执行树,生成执行计划;词法分析是第一阶段。 + 词法分析即将输入的语句进行分词(token),解析出每个token的意义。分词的本质便是正则表达式的匹配过程,比较流行的分词工具应该是lex,通过简单的规则制定,来实现分词。Lex一般和yacc结合使用。然而Mysql并没有使用lex来实现词法分析,但是语法分析却用了yacc,而yacc需要词法分析函数yylex。 + 以select * from zzz where 为例。 + MySQL使用的是自己的词法分析,使用yacc作语法分析,而yacc需要词法分析函数yylex,故在sql_yacc.cc文件最前面我们可以看到如下的宏定义: + #define yyparse MYSQLparse + #define yylex MYSQLlex + 也就是yylex的实际入口是MYSQLlex(); sql/sql_lex.cc。lex_one_token()为主要的处理函数,先了解一下其中的几个重要的数据类型以及变量,分别是: + Lex_input_stream *lip= &thd->m_parser_state->m_lip; + uchar *state_map= cs->state_map; + lip用来表示输入流,在sql/sql_lex.h中定义,暂时不知道在何处初始化的;state_map保存了词法分析机中的各种状态,state_map是通过init_state_maps(); mysys/charset.c用来初始化的,应该是在初始化字符集信息的时候完成初始化的。 + 下面以insert into tablename values (2,‘Y’,2.3);为例对词法分析进行介绍 +1) state的初始状态为MY_LEX_START,首先会跳过所有的空格,然后读取到第一个字母'i',通过state_map数组返回的state状态为MY_LEX_IDENT。 +2) 接下来状态机进如MY_LEX_IDENT分支,这时会将整个insert单词读完直到遇到空格,然后对读到的单词进行判定是否为关键字(判定函数为find_keyword),如果是关键字则会返回hash表中该单词所对应的关键字,由于insert是关键字那么返回该token,并将下一个状态设置为MY_LEX_START。至此insert这个token被识别并被传到MYSQLlex中,被后续的语法分析所用。 +3) 重新进入lex_one_token函数,由于into同样是关键字,因此对于into的分析和insert是一样的,在此就不再赘述。 +4) 再次进入lex_one_token函数,在经过初始处理之后,状态机会进入MY_LEX_IDENT分支。在读完tablename之后发现不是关键字,这时由get_token函数将读到的单词保存到yylval中(yylval->lex_str)并返回result_state,result_state的取值是根据单词是否存在非ASCII码字母来决定为IDENT或是IDENT_QUOTED(这两个都是yacc中的一种token类型)。 +........... +可以参考http://blog.csdn.net/sfifei/article/details/9449629 + + 语法是自上而下的,实际的解析过程是自下而上的匹配过程,词法分析首先yacc送来SELECT关键字,但是为什么SELECT是关键字呢。 + 在sql/sql_yacc.yy中可以找到如下一个定义,也就是说通过词法分析传过来的应该是SELECT_SYM这个宏。 + %token SELECT_SYM /* SQL-2003-R */ + 在执行bison -d之后,实际会转换为一个宏SELECT_SYM,代表一个关键字,使用enum表示为一个宏定义,如下: + enum yytokentype {..., SELECT_SYM 687, ... } + 那么再MySQL中,是如何将关键字与xxx_SYM宏对应起来的呢?在sql/sql_lex.cc中的find_keyword(),通过该函数查找是否为关键字;而该函数实际调用的是get_hash_symbol(),该函数实际是在symbols_map中查找。这个数组在lex_hash.h中,从注释中可以看出该数组是由gen_lex_hash.cc产生的。 + 在gen_lex_hash.cc中,看到了个main函数,里面是一些生成文件的操作,在generate_find_structs()函数中找到了insert_symbols(),这应该是初始化我们的symbols_map数组了吧。可以看到函数的实现是循环取数组symbols,找到symbols定义,在文件lex.h中,可以看到这个数组。 + { "SELECT", SYM(SELECT_SYM)}, + 这就是将SELECT字符串与SELECT_SYM关联的地方了。 + 再来捋一下SELECT解析的思路,词法分析解析到SELECT后,执行find_keyword去找是否是关键字,发现SELECT是关键字,于是给yacc返回SELECT_SYM用于语法分析。NOTE:如果我们想要加关键字,只需在sql_yacc.yy上面添加一个%token xxx,然后在lex.h里面加入相应的字符串和SYM的对应即可。 + select在sql/sql_yacc.yy中的语法节点执行流程为: + query -> verb_clause -> statement -> select -> select_init(SELECT_SYM关键字) -> select_init2 -> select_part2 -> select_options(展开后为各个选项) -> select_item_list -> select_item(这里为指定的列,可以为*) -> select_alias +到此为止包括了select所包含的语义解析,包括了select [options] {*|columns} AS ident。 + select_part2: select_options select_item_list { } select_into select_lock_type ; + 当匹配 select_options select_item_list将执行{}中的内容,同时会继续匹配下面的内容,也就是将会匹配select_from等字段。 + 解析时通过对应的action将相应的参数添加到列表中,也就是对add_item_to_list 和table_list的赋值。解析后对于需要查询的表(zzz)和字段(*)这些信息都写入到thd->lex这个结构体里了,例如其中thd->lex->query_tables就是表(zzz)的状况;thd->lex->current_select->with_wild 是表示该语句是否使用了*;sql_command为SQLCOM_SELECT;where子句保存在select_lex->where;table列表select_lex->table_list;字段列表select_lex->item_list等等。 + 例如语句select table1.field1,'test',100 from table1 where table1.field1='field1' and (table1.field2=100 or table1.field2=200) + 1) 选择域的解析 table1.field1,'test',100 +将被解析为一个List,其中List的第一个元素Item的子类Item_field,表示表中的列。 +第二个元素为Item_string,表示字符串。由val_str方法可获得string值"test”,也可用tmp_table_field_from_field_type方法返回一个Field的子类Field_string的指针。 +第三个元素为Item_int,表示整数。由val_int获得int值100,也可以用tmp_table_field_from_field_type方法返回一个Field的子类Field_longlong的指针。 +2) Where域的解析where table1.field1='field1' and (table1.field2=100 or table1.field2=200) +将被解析为一个Item对象,这个对象的层次结构如下: + + 下面列举了一些常见语句解析后的结果。 +A) Select语句 + 对select类型的语句解析后,将结果存放在SELECT_LEX类中,其中: +选择域存放在SELECT_LEX::item_list中,类型为LIST +where域存放在SELECT_LEX::wheret中,类型为Item* +having域存放在SELECT_LEX::having中,类型为Item* +order域存放在SELECT_LEX::order_list中,实际类型为ORDER* +group域存放在SELECT_LEX::group_list中,实际类型为ORDER* +limit域存放在SELECT_LEX::select_limit中,unsigned long +table名字域存放在SELECT_LEX::table_list中,实际类型为TABLE_LIST* + where域和having域的解构请见上文中,其他几个域的解构类似于链表。 +B) Update语句 + 对update类型的语句解析后,将结果存放在SELECT_LEX类和LEX类中,其中: +更新域存放在SELECT_LEX::item_list中,类型为LIST +值域存放在LEX::value_list中,类型为LIST +where域存放在SELECT_LEX::wheret中,类型为Item* +table名字域存放在SELECT_LEX::table_list中,实际类型为TABLE_LIST* + (其中更新域和值域的结构请见上文中4(1),where域的解构请见上文,table名字域的解构类似于链表) +C) Insert语句 + 对insert类型的语句解析后,将结果存放在SELECT_LEX类和LEX类中,其中: +插入域存放在LEX::item_list中,类型为LIST +值域存放在LEX::many_values中,类型为LIST> +table名字域存放在SELECT_LEX::table_list中,实际类型为TABLE_LIST* +(其中插入域的结构请见上文中的4(1), 值域可以含有多个LIST, table名字域的解构类似于链表) +D) Delete语句 +对delete类型的语句解析后,将结果存放在SELECT_LEX类中,其中: +where域存放在SELECT_LEX::wheret中,类型为Item* +limit域存放在SELECT_LEX::select_limit中,unsigned long +table名字域存放在SELECT_LEX::table_list中,实际类型为TABLE_LIST* + (其中选择域的结构请见上文中的4(1),where域和having域的解构请见上文中的4(2), 其他几个域的解构类似于链表) + +PS: 对于查找关键字多说一句。 + 考虑到关键字是一个只读的列表,对它做一个只读的查找树可以改善查找的性能, + 产生查找树:A) 读取关键字数组,产生一个Trie树;B) 调整这棵树,并产生一个数组,也就是一个不用链表表示的树。 + 相关的Makefile规则,在sql/CMakeFiles/sql.dir/build.make文件中: +sql/lex_hash.h: sql/gen_lex_hash +$(CMAKE_COMMAND) -E cmake_progress_report /home/zedware/Workspace/mysql/CMakeFiles $(CMAKE_PROGRESS_153) +@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --blue --bold "Generating lex_hash.h" +cd /home/zedware/Workspace/mysql/sql && ./gen_lex_hash > lex_hash.h + 使用查找树:这个比较简单,直接看函数get_hash_symbol好了。 + + +在mysql里建立一个sql语句,类似于show authors,在此添加show disk_usage命令。 +1. 在sql/lex.h加入新语句的名称:disk_usage; + static SYMBOL symbols[] = { .... , {"DISK_USAGE", SYM(DISK_USAGE_SYM)}, ... } +2. 修改 sql/sql_lex.h + 增加一个助记符来标识这个命令。这个助记符将在解析器利用来创建和标识一个内部查询结构,并通过sql_parse.cc文件里的超大型switch语句的一个case分支对执行流程进行控制。 + enum enum_sql_command { ..., SQLCOM_SHOW_DISK_USAGE, ... } + 注意该变量上面的注释,增加完该命令后,也需要在sql/mysqld.cc文件中,为SHOW_VAR com_status_vars[] 增加一项。 + {"show_disk_usage", (char*) offsetof(STATUS_VAR, + com_stat[(uint) SQLCOM_SHOW_DISK_USAGE]), SHOW_LONG_STATUS}, +3. 修改sql/sql_yacc.yy + 在这个文件里定义在lex.h里使用的新记号。 + %token DISK_USAGE_SYM + 还需要把新命令的语法加到解析器的YACC代码里,在show:标签下, + show: + SHOW DISK_USAGE_SYM + { + LEX * lex=Lex; + lex->sql_command=SQLCOM_SHOW_DISK_USAGE; + } + | SHOW ... ... +4. 然后就是到了比较熟悉的sql/sql_parse.cc中增加相应的路由信息。 + case SQLCOM_SHOW_AUTHORS: + ... ... + case SQLCOM_SHOW_DISK_USAGE: + res = show_disk_usage_command(thd); + break; +5. 接着我们在sql/sql_show.cc中加上show_disk_usage_command的实现,可在mysqld_show_authors()函数后面添加该函数。同时在sql/sql_show.h文件中增加声明bool show_disk_usage_command(THD *thd);。 +bool show_disk_usage_command(THD *thd) +{ + List field_list; + Protocol *protocol = thd->protocol; + DBUG_ENTER(“show_disk_usage”); + + /* send fields */ + field_list.push_back(new Item_empty_string(“Database”, 50)); + field_list.push_back(new Item_empty_string(“Size (Kb)”, 30)); + if (protocol->send_result_set_metadata(&field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) + DBUG_RETURN(TRUE); + /* send test data */ + protocol->prepare_for_resend(); + protocol->store(“test_row”, system_charset_info); + protocol->store(“1024”, system_charset_info); + if (protocol->write()) + DBUG_RETURN(TRUE); + my_eof(thd); + DBUG_RETURN(FALSE); +} +6. 在sql目录下生成sql_yacc.cc和sql_yacc.h,bison -y -d sql_yacc.yy,将生成的y.tab.c和y.tab.h替换掉即可。 + 在sql_yacc.cc中添加如下内容。 + /* Pull parsers. */ + #define YYPULL 1 + /* Using locations. */ + #define YYLSP_NEEDED 0 + /* Substitute the variable and function names. */ + #define yyparse MYSQLparse + #define yylex MYSQLlex + #define yyerror MYSQLerror + #define yylval MYSQLlval + #define yychar MYSQLchar + #define yydebug MYSQLdebug + #define yynerrs MYSQLnerrs + +7. 现在重新生成下lex hash.h,gen_lex_hash > lex_hash.h ,替换掉源文件即可。 +8. 现在已经大功告成,重新编译一把mysqld工程,然后从client测试下。 +mysql -uroot -h127.1 +mysql> show disk_usage; +其它可以查看http://www.orczhou.com/index.php/2012/11/more-about-mysql-item/ +http://www.orczhou.com/index.php/2012/11/mysql-source-code-data-structure-about-index/ +http://www.orczhou.com/index.php/2012/11/mysql-innodb-source-code-optimization-1/ + + + + + + + + +class THD在sql/sql_class.h中定义,在sql/sql_class.cc中实现, + + + + +update t2 set b=b*2-1, c=c/2+4 where id=5; +set左值集合:fields +thd->lex->select_lex.item_list +List +|-->Item_field(b) +|-->Item_field(c) +set右值集合:values: +thd->lex->value_list +list +|-->Item_func_minus + |-->args[0]:Item_func_mul + |-->args[0]:Item_field(b) + |-->args[1]:Item_int(2) + |-->args[1]:Item_int(1) +|-->Item_func_plus + |-->args[0]:Item_func_div + |-->args[0]:Item_field(c) + |-->args[1]:Item_int(2) + |-->args[1]:Item_int(4) +where 条件: +thd->lex->select_lex.where +Item_func_eq +|-->args[0]:Item_field(id) +|-->args[1]:Item_int(5) + + +

    + + + + + + + + + + +## 参考 + +关于 MySQL 的语法可以参考 [MySQL Reference: JOIN Syntax](http://dev.mysql.com/doc/refman/en/join.html) 。 + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-09-13-network-ip-tunneling_init.md b/_drafts/2015-09-13-network-ip-tunneling_init.md new file mode 100644 index 0000000..f0f04c6 --- /dev/null +++ b/_drafts/2015-09-13-network-ip-tunneling_init.md @@ -0,0 +1,93 @@ +--- +Date: Auguest 05, 2015 +title: Linux IP 隧道技术 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +隧道是一种封装技术,它利用一种网络协议来传输另一种网络协议,即利用一种网络传输协议,将其它协议产生的数据报文封装在它自己的报文中,然后在网络中传输。 + +隧道可以看做是一条虚拟的点对点连接,隧道的两端需要对数据报文进行封装及解封装,常用的是基于 IP 的隧道。 + +在此简单介绍下隧道技术。 + + + +# 隧道技术 + +隧道技术就是指包括数据封装、传输和解封装在内的全过程。 + +![tunnel tecnology]({{ site.url }}/images/network/tunnel-arch.jpg){: .pull-center} + +Linux 内核支持的 tunnel 有 ipip、gre、sit,另外,还包括了其它非内核隧道的几种。其中 ipip 是最简单的一种隧道;gre 是对 ipip 的一种改进;sit 的作用是连接 ipv4 与 ipv6 的网络。另外,以上所有隧道都需要内核模块 tunnel4.ko 的支持。 + +在内核之外,还有很多实现隧道的方法,最有名的是 PPP 和 PPTP。 + +# IP-in-IP + +ipip 需要内核模块 ipip.ko,可以通过 modinfo ipip 查看该内核模块的相关信息。 + +该模式的特点是:A) 只是连接了两个一般情况下无法直接通讯的 IPv4 网络而已,简单有效;B) 不能通过 IPIP 隧道转发广播或者 IPv6 数据包。 + +{% highlight text %} + INTERNET +| 192.168.100.0/24 | 211.167.237.218 |<------------>| 123.127.177.195 |192.168.200.0/24 | + eth0 eth1 eth1 eth0 +{% endhighlight %} + +网络结构如上,现在就依据上面的结构来建立 tunnel 。 + +{% highlight text %} +----- 在211.167.237.218上 +# modprobe ipip +# ip tunnel add tun1 mode ipip remote 123.127.177.195 local 211.167.237.218 ttl 64 +# ip link set tun1 mtu 1480 up +# ip address add 192.168.200.253 brd 255.255.255.255 peer 123.127.177.195 dev tun1 +# ip route add 192.168.200.0/24 via 192.168.200.253 + +----- 在123.127.177.195上 +# modprobe ipip +# ip tunnel add tun1 mode ipip remote 211.167.237.218 local 123.127.177.195 ttl 64 +# ip link set tun1 mtu 1480 up +# ip address add 192.168.100.253 brd 255.255.255.255 peer 211.167.237.218 dev tun1 +# ip route add 192.168.100.0/24 via 192.168.100.253 +{% endhighlight %} + +其中需要注意的是: + +mtu 会增加协议开销,因为它需要一个额外的 IP 包头,一般是每个包增加 20 个字节,如果一个网络的 MTU 是 1500 字节的话,使用隧道技术后,实际的 IP 包长度最长只能有 1480 字节了。因此,如果要使用隧道技术构建一个比较大规模的网络的话,就要仔细研究一下关于 IP 包的分片和汇聚的知识。 + +ttl 数据包的生存期,如果网络规模比较大就需要提高这个值,不过一般 64 是比较安全的。 + + + + +# Generic Routing Encapsulation, GRE + +需要内核模块 ip_gre.ko ,同样可以通过 modinfo ip_gre 查看模块相关的信息。 + +gre 最初由 CISCO 开发的隧道协议,能够做一些 ipip 所不支持的功能,如多播以及 IPv6 数据包。使用 gre 与 ipip 的方式相同,只是加载的模块以及 mode 需要修改。 + + + diff --git a/_drafts/2015-09-14-network-tcp-fast-open_init.md b/_drafts/2015-09-14-network-tcp-fast-open_init.md new file mode 100644 index 0000000..685a2b3 --- /dev/null +++ b/_drafts/2015-09-14-network-tcp-fast-open_init.md @@ -0,0 +1,89 @@ +--- +Date: Auguest 05, 2015 +title: TCP Fast Open (TFO) 介绍 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +TFO 相关标准是由 Google 提出,目前已经是正式标准。该优化简单来说,是对 TCP 的增强,在 3 次握手期间也用来交换数据。 + +这主要是由于当前的很多链接都是短链接,通常建立链接之后,只有几次数据交互,尤其像现在广泛使用的 HTTP 协议。这样就导致很多无效的网络交互,而 TFO 正是基于此种场景的优化。 + +接下来我们会介绍 TFO 相关的内容。 + + +# 介绍 + +要注意,TFO默认是关闭的,因为它有一些特定的适用场景,下面我会介绍到。 + +TCP_FASTOPEN 参数。 + + + + +在内核中有两个控制参数: + +1. net.ipv4.tcp_fastopen + + 对不同的位置位将对应不同的功能,0 表示关闭,1 表示 + +The tcp_fastopen file can be used to view or set a value that enables the operation of different parts of the TFO functionality. Setting bit 0 (i.e., the value 1) in this value enables client TFO functionality, so that applications can request TFO cookies. Setting bit 1 (i.e., the value 2) enables server TFO functionality, so that server TCPs can generate TFO cookies in response to requests from clients. (Thus, the value 3 would enable both client and server TFO functionality on the host.) + +The tcp_fastopen_cookies file can be used to view or set a system-wide limit on the number of pending TFO connections that have not yet completed the three-way handshake. While this limit is exceeded, all incoming TFO connection attempts fall back to the normal three-way handshake. + + + + 我来简单的介绍下这个东西.想了解详细的设计和实现还是要去看上面的rfc。 + + 1 http的keepalive受限于idle时间,据google的统计(chrome浏览器),尽管chrome开启了http的 keepalive(chrome是4分钟),可是依然有35%的请求是重新发起一条连接。而三次握手会造成一个RTT的延迟,因此TFO的目标就是去除 这个延迟,在三次握手期间也能交换数据。 + + 2 RFC793允许在syn数据包中带数据,可是它要求这些数据必须当3次握手之后才能给应用程序,这样子做主要是两个原因,syn带数据可能会引起2个问 题。第一个是有可能会有前一个连接的重复或者老的数据的连接(syn+data的数据),这个其实就是三次握手的必要性所决定的。第二个就是最重要的,也 就是安全方面的,为了防止攻击。 + + 3 而在TFO中是这样解决上面两个问题的,第一个问题,TFO选择接受重复的syn,它的观点就是有些应用是能够容忍重复的syn+data的(幂等的操 作),也就是交给应用程序自己去判断需不需要打开TFO。比如http的query操作(它是幂等的).可是比如post这种类型的,就不能使用TFO, 因为它有可能会改变server的内容. 因此TFO默认是关闭的,内核会提供一个接口为当前的tcp连接打开TFO。为了解决第二个问题,TFO会有一个Fast Open Cookie(这个是TFO最核心的一个东西),其实也就是一个tag。 + + 4 启用TFO的tcp连接也很简单,就是首先client会在一个请求中(非tfo的),请求一个Fast Open Cookie(放到tcp option中),然后在下次的三次握手中使用这个cookie(这个请求就会在3次握手的时候交换数据). + + 下面的张图就能很好的表示出启用了TFO的tcp连接: + + + + + + +Google研究发现TCP三次握手是页面延迟时间的重要组成部分,所以他们提出了TFO:在TCP握手期间交换数据,这样可以减少一次RTT。根据测试数据,TFO可以减少15%的HTTP传输延迟,全页面的下载时间平均节省10%,最高可达40%。 + +目前互联网上页面平均大小为300KB,单个object平均大小及中值大小分别为7.3KB及2.4KB。所以在这种情况下,多一次RTT无疑会造成很大延迟。 + +在HTTP/1.1中,我们有了keep-alive。但实际应用并不理想,根据Google对一个大型CDN网络的统计,平均每次建连只处理了2.4个请求。 + +为了证实TCP三次握手是目前页面访问的重要性能瓶颈之一,Google工程师分析了Google的web服务器日志及Chrome浏览器统计数据。 + +2011年6月,对Google web服务器进行连续7天数十亿次针对80端口的请求分析,包括搜索、Gmail、图片等多个服务。其中第一次建连被称为cold requests,复用连接的请求称为warm requests。 + +图中描述了在cold requests及all requests中TCP握手时间在整个延迟时间中的占比。在cold requests中,TCP握手时间占延迟时间的8%-28%;即使统计所有的请求,TCP握手时间也占到了延迟时间的5%-7%。 + + + +简单说明:客户端通过TCP连接到服务器时,可以在SYN报文携带数据,这将提升TCP的效率(4%-41%)[5]。前提是在这个SYN报文中,有代表客户端的在之前的TCP连接中服务器产生的cookie字段。在客户端和服务器第一次的TCP连接创建过程中,是通常的三次握手过程,但是服务器会产生cookie作为后续TCP连接的认证信息,这就避免了恶意攻击。 + +对于客户端用户程序来说,可直接使用sendto等带有对端地址的系统调用发送数据,如果是第一次连接(或者cookie过期),则退化到正常三次握手过程,如果是非第一次连接,则可以继续创建连接且能够直接将数据交付给应用层处理。 + + + + +# 参考 + +[RFC-7413, TCP Fast Open][TCP-Fast-Open],在 RFC 中的相关定义。 + +[TFO Paper][TFO-paper],Yuchung Cheng 写的论文,Google 工程师,从国立台湾大学获得学士学位,加州大学圣迭戈分校获得博士学位。 + +[www.lwn.net][TFO-linux],TFO 在 Linux 中实现的相关介绍。 + +[TCP-Fast-Open]: https://tools.ietf.org/html/draft-ietf-tcpm-fastopen-10 "RFC-7413, TCP Fast Open" +[TFO-paper]: http://conferences.sigcomm.org/co-next/2011/papers/1569470463.pdf "Papper, TCP Fast Open" +[TFO-linux]: http://lwn.net/Articles/508865/ "lwn.net 中关于TFO的介绍" + +http://www.vants.org/?post=210 +http://blog.csdn.net/hanhuili/article/details/8540227 diff --git a/_drafts/2015-10-12-mysql-test-framework_init.md b/_drafts/2015-10-12-mysql-test-framework_init.md new file mode 100644 index 0000000..af7a8ff --- /dev/null +++ b/_drafts/2015-10-12-mysql-test-framework_init.md @@ -0,0 +1,423 @@ +--- +title: MySQL 测试框架 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,测试,mysqltest +description: MySQL 测试包含了多种类型的,当然同时也会含多种工具,例如 MySQL 的测试框架、基于 Test Anything Protocol, TAP 的单元测试框架、基于 Google Test Framework 的单元测试、性能压测工具、Random Query Generator 等等。在此介绍与测试框架相关的内容。 +--- + +MySQL 测试包含了多种类型的,当然同时也会含多种工具,例如 MySQL 的测试框架、基于 Test Anything Protocol, TAP 的单元测试框架、基于 Google Test Framework 的单元测试、性能压测工具、Random Query Generator 等等。 + +在此介绍与测试框架相关的内容。 + + + +## 简介 + +当给 MySQL 打了补丁之后, + +不仅需要测试新增的功能,同时更重要的问题是,需要对原有的功能作回归――若新增的patch导致原有其他功能产生bug,就得不偿失。 + +MySQL自动测试框架是一个以MySQL框架和内部引擎为测试对象的工具。主要执行脚本在发布路径的mysql-test目录下。自动测试框架的主要测试步骤,是通过执行一个case,将该case的输出结果,与标准的输出结果作diff。这里的“标准输出结果”指代在可信任的MySQL版本上的执行结果。 + +如果某个case的执行结果与标准输出结果不同,则说明正在执行的这个MySQL服务有问题,或许是框架,或许是引擎。 当然若干case执行正确并不能确保被测试的框架和引擎是没有问题的,除非能够证明执行的case已经覆盖了所有的分支(这太难了)。 + +这里说到的case,是指一系列的语句,包括SQL语句和一些必要的shell语句。在mysql-test/t/目录下,可以找到很多这样的case,他们的文件名以.test为后缀。接下来我们用一个简单的例子来说明用法。 + + + +2、初体验 + + 再简单的例子也不如自己写的简单。因此我们通过自己作一个case,来体验一下这个框架的便捷。 + +a) 在mysql-test/t/目录下创建文件 mytest.test, 内容为 + 1 — source include/have_innodb.inc + + + + 2 use test; + + 3 create table t(c int) engine=InnoDB ; + + 4 insert into t values(1); + + 5 select c from t; + + 6 drop table t; + + 从第二行开始是我们熟悉的SQL语句,创建一个InnoDB表,插入一行,执行一个查询,删除这个表。输出也是显而易见的。 + +b) 在mysql-test/r/目录下创建文件 mytest.result, 内容为 + 1 use test; + + + + 2 create table t(c int) engine=InnoDB ; + + 3 insert into t values(1); + + 4 select c from t; + + 5 c + + 6 1 + + 7 drop table t; + + + +c) 在mysql-test/ 目录下执行 ./mtr mytest + + + + +=================================================================== + +TEST RESULT TIME (ms) + +———————————————————— + +worker[1] Using MTR_BUILD_THREAD 300, with reserved ports 13000..13009 + +worker[1] mysql-test-run: WARNING: running this script as _root_ will cause some tests to be skipped + +main.mytest [ skipped ] No innodb support + +main.mytest ‘innodb_plugin’ [ pass ] 5 + +———————————————————— + +The servers were restarted 0 times + +Spent 0.005 of 3 seconds executing testcases + +Completed: All 1 tests were successful. + +最后一句话说明,测试通过了。 + +说明: + +1) mysql-test/mtr这个文件,是一个perl脚本。同目录下还有 mysql-test-run 和mysql-test-run.pl这两个文件,这三个文件一模一样。 + +2) 每个case会启动一个mysql服务,默认端口为13000。如果这个case涉及到需要启动多个服务(比如主从),则端口从13000递增。 + +3) ./mtr的参数只需要指明测试case的前缀即可,如你看到的。当你执行./mtr testname会自动到t/目录下搜索 testname.test文件来执行。当然你也可以执行./mtr mytest.test, 效果相同。 + +4) mytest.test第一行是必须的,当你需要使用InnoDB引擎的时候。可以看到mtr能够解释source语法,等效于将目标文件的内容全部拷贝到当前位置。Mysql-test/include目录下有很多这样的文件,他们提供了类似函数的功能,以简化每个case的代码。注意souce前面的 –, 大多数的非SQL语句都要求加 –。 + +5) mytest.test最后一行是删除这个创建的表。因为每个case都要求不要受别的case影响,也不要影响别的case,因此自己在case中创建的表要删除。 + +6) mtr会将mytest.test的执行结果与r/mytest.result作diff。 若完全相同,则表示测试结果正常。注意到我们例子中的mytest.result. 其中不仅包括了这个case的输出,也包括了输入的所有内容。实际上有效输出只有5、6两行。如果希望输出文件中只有执行结果,可以在第一行后面加入 — disable_query_log。 + +3、冲突结果及处理 + + 我们来测试一个错误的输入输出。就在上文的mytest.test中加入一行,如下: + 1 — source include/have_innodb.inc + + + + 2 — disable_query_log + + 3 use test; + + 4 create table t(c int) engine=InnoDB ; + + 5 insert into t values(1); + + 6 select c from t; + + 7 drop table t; + + 执行 ./mtr mytest,我们知道,他只会输出两行,分别为c和1. + +执行结果 +。。。。。。 + + + +@@ -1,7 +1,2 @@ + +-use test; + +-create table t(c int) engine=InnoDB ; + +-insert into t values(1); + +-select c from t; + + c + + 1 + +-drop table t; + +mysqltest: Result length mismatch + +。。。。。。 + +Completed: Failed 1/1 tests, 0.00% were successful. + +最后一句话说明我们的这个case测试没有通过。我们知道mtr将执行结果与r/mytest.result文件作diff,这个结果的前面部分就是diff的结果。 + + 说明: + +1) 执行case失败有很多中可能,比如中间执行了某个非法的语句,这个例子中的失败,指代的是执行结果与预期结果不同。 + +2) 当前的执行结果会保存在r/mytest.reject中 + +3) 如果mytest.reject中的结果才是正确的结果(错入出现在mytest.result中),可以用mytest.reject将result覆盖掉,这样正确的标准测试case就完成了。可以直接使用 ./mtr mytest –record命令生成mytest.result. + +4) 注意mtr在作diff的时候是直接文本比较,因此如果你的case中出现了多次执行结果可能不同的情况(比如时间相关),这不是一个好的case。当然处理的办法是有的,请参加附录中关于 replace_column的描述。 + + 4、批量执行的一些命令 + +实际上我们更常用到的是批量执行的命令。 + +1) ./mtr + +就是这么简单。会执行所有的case。当然包括刚刚我们加入的mytest.这里说的”所有的case”,包括t/目录下所有以.test为后缀的文件。也包括 suits目录下的所有以.test为后缀的文件。 + +注意只要任何一个case执行失败(包括内部执行失败和与result校验失败)都会导致整个执行计划退出。因此–force很常用,加入这个参数后,mtr会忽略错误并继续执行下一个case直到所有的case执行结束。 + +2) ./mtr –suite=funcs_1 + +Suits目录下有多个目录,是一些测试的套餐。此命令单独执行 suits/funcs_1目录下的所有case。(其他目录不执行) + +t/目录下的所有文件组成了默认的套餐main。 因此 ./mtr –suite=main则只执行t/*.test. + +3) ./mtr –do-test=events + +执行所有以 events为前缀的case(搜索范围为t/和所有的suite)。 + +–do-test的参数支持正则表达式,上诉命令等效于./mtr –do-test=events.* + +所以如果想测试所有的包括innodb的case,可以用 ./mtr –do-test=.*innodb.* + +5、结束了 + +好吧。上面说的一些太简单了,简单到入门都不够。需要详细了解的可以到官网看e文。以下的tips是看官网的一些笔记。看到一点记录一点的,不成章。 + +附录 +1、目录下的mtr文件即为mysql-test-run的缩写,文件内容相同 + + + +2、Mtr会启动mysql,有些case下可能重启server,为了使用不同的参数启动 + +3、调用bin/mysqltest读case并发送給mysql-server + +4、输入和输出分别放在不同的文件中,执行结果与输出文件内容作对比 + +5、输入文件都在t目录下,输出文件在r目录下。对应的输入输出文件仅后缀名不同,分别为 *.test 和 *.result + +6、每个testfile是一个测试用例,多个测试用例之间可能有关联。 一个file中任何一个非预期的失败都会导致整个test停止(使用force参数则可继续执行)。 + +7、注意如果服务端输出了未过滤的warning或error,则会也会导致test退出。 + +8、t目录下的*.opt文件是指在这个测试中,mysql必须以opt文件的内容作为测试参数启动。 *.sh文件是在执行启动mysql-server之前必须提前执行的脚本。disabled.def中定义了不执行的testfile。 + +9、r目录下, 若一个执行输出结果和testname.result文件不同,会生成一个testname.reject文件。 该文件在下次执行成功之后被删除。 + +10、 include目录是一些头文件,这些文件在t/*.test文件中使用,用source 命令引入 + +11、 lib目录是一些库函数,被mtr脚本调用 + +12、 有些测试需要用到一些标准数据,存在std_data目录下。 + +13、 Suite目录也是一些测试用例,每个目录下包含一套,./mtr –suite=funcs_1执行suits/funcs_1目录下的所有case + +14、 Mtr实际调用mysqltest作测试。 –result-file文件用于指定预定义的输出,用于与实际输出作对比。若同时指定了 –recored参数,则表示这个输出数据不是用来对比的,而是要求将这个输出结果写入到指定的这个文件中。 + +15、 Mtr所在的目录路径中不能有空格 + +16、 Mtr –force参数会跳过某个错误继续执行,以查看所有的错误。 + +17、 执行./mtr时另外启动了一个mysql server,默认端口13000 + +18、 指定执行某个具体case使用 ./mtr testname, 会自动使用t/testname.rest + +19、 ./mtr –do-test=events 执行以events开头的所有case,包括events_grant.test 等 + + 同理 –skip-test=events 将以events打头的所有case跳过。 这里支持正则表达是,如./mtr –do-test=.*innodb.*则会执行t/目录下所有包含innodb子串的case + +20、 Mtr允许并行执行,端口会从13000开始使用。但需要特别指定不同的—vardir指定不同的日志目录。但并行执行的case可能导致写同一个testname.reject. + +21、 使用–parallel=auto可以多线程执行case。 + +22、 Mtr对比结果使用简单的diff,因此自己编写的测试case不应该因为执行时间不同而导致结果不同。当然框架在执行diff之前,允许自定义处理规则对得到的result作处理,来应对一些变化。 + +23、 ./mtr –record test_name 会将输出结果存入文件 r/test_name.result + +24、 自己在脚本中创建的库、表等信息,要删除,否则会出warnning + +25、 Mtr默认使用的是mysql-test/var/my.cnf文件,需要替换该文件为自定义的配置文件 + +26、 若要使用innodb引擎,必须明确在testname.test文件头加 + +– source include/have_innodb.inc + +27、 Test文件名由字母数字、下划线、中划线组成,但只能以字母数字打头 + +28、 — sleep 10 等待10s 注意前面的—和后面没有分号 + +29、 默认情况下, r/testname.result中会包含原语句和执行结果,若不想输出原语句,需要自t/restname.test文件头使用 — disable_query_log + +30、 每个单独的case会重启服务并要求之前的数据是清空的。 + +31、 如果要测试出错语句,必须在testname.test文件中,在会出错的语句之前加入 –error 错误号。 + +比如重复创建表,语句如下 + +create table t(c int) engine=InnoDB ; + +– error 1050 + +create table t(c int) engine=InnoDB ; + +这样在testname.result中输出 + +create table t(c int) engine=InnoDB ; + + ERROR 42S01: Table ‘t’ already exists + +则能够正常通过 + +也可使用 ER_TABLE_EXISTS_TABLE (宏定义为1050) + +也可使用 –error S42S01 (注意需要加前缀S),但S系列的可能一个错误号对应多种错误。 + +32、 –enable_info 在testname.test文件头增加这个命令,在结果中会多输出影响行数。 + + 对应的关闭命令为 –disable_info + +33、 enable_metadata 可以显示更多信息 disable_result_log不输出执行结果 + +34、 有些case的输出结果可能包含时间因素的影响,导致无法重复验证。–replace_column 可以解决部分情况。 + +–replace_column 1 XXXXX + +Select a, b from t; + +输出结果会是 + +XXXXX b.value + +即将第一列固定替换为xxxxx。 注意,每个replace_column的影响范围仅局限于下一行的第一个select语句。 + +35、 mtr –mysqld=–skip-innodb –mysqld=–key_buffer_size=16384 用这种将参数启动传递給mysql server 。 每个选项必须有一个—mysqld打头,不能连在一起写,即使引号包含多个也不行。 + +36、 mysql-test-run.pl –combination=–skip-innodb –combination=–innodb,–innodb-file-per-table + +这个命令是参数传给多个test case, 第一个参数传 skip-innodb, 第二个参数传 –innodb, innodb-file-per-table。 若所有启动参数中combination只出现一次,则无效。 + +37、 skip-core-file 强行控制server不要core + +38、 如果需要在server启动前执行一些脚本,可以写在 t/testname.sh文件中,由mtr自动执行。 + +39、 等待语句 + +let $wait_condition= SELECT c = 3 FROM t; + +–source include/wait_condition.inc + + Mtr会一直停在 source这行,每隔0.1s检测,直到上个语句中的$wait_condition返回值非0 + +40、 主从测试case + + a) testname.test头部必须包含 souce include/master-slave.inc + + b) 主库的配置写在testname-master.opt, 从库的写在testname-slave.opt + + c) 对出从库的操作统一写在testname.test中,要对主库操作时,先执行connection master,之后的语句都是在主库上操作;同理connection slave; + +从库上执行start slave之后要执行 –source include/wait_for_slave_to_start.inc 等待启动完成, 执行stop slave之后要执行–source include/wait_for_slave_to_stop.inc 等待停止完成。 + +41、 Case完成后,mtr会检测server的错误日志,如果里面包含Error或Warning,则会认为case fail。 + + a) 如果要忽略整个验证server日志的过程,可以在文件头增加 –nowarnings + + b) 如果要指定忽略某些行,允许使用正则表达式,比如 call mtr.add_suppression(“The table ‘t[0-9]*’ is full”); 能够忽略 The table ‘t12′ is full 这样的warning + + c) 注意上面这个call语句也是输入语句的一部分,因此会输出到结果内容中,除非 + +–disable_query_log + +call mtr.add_suppression(“The table ‘t[0-9]*’ is full”); + +–enable_query_log + +42、 在一个case中重启server + +–exec echo “wait” > $MYSQL_TMP_DIR/mysqld.1.expect + +–shutdown_server 10 + +–source include/wait_until_disconnected.inc + +# Do something while server is down + +–enable_reconnect + +–exec echo “restart” > $MYSQL_TMP_DIR/mysqld.1.expect + +–source include/wait_until_connected_again.inc + +43、 循环语句语法 + +let $1= 1000; + +while ($1) + +{ + + # execute your statements here + + dec $1; + +} + +44、 mysql_client_test是一个单独的case,里面包含了多个case, 但并不是写成脚本,而是直接调用,可以直接从源码中看到里面调用的各个语句。源码位置tests/mysql_client_test.c + +45、 直接执行 ./mtr 会执行t/目录下的所有case,外加suits目录 + +46、 若在不同目录中有重名的case,则会依次全部执行 + +47、 Mysql-stress-test.pl用于压力测试,注意默认的my.cnf中的参数,比如innodb_buffer_pool_size只有128M + +48、 Case中的语句是以分号结尾的。echo a; select 1 from t limit 1; echo b;是三个语句,在result文件中的对应输出是 a \n 1 \n b + +49、 Testcase中支持的脚本语言函数 +http://dev.mysql.com/doc/mysqltest/2.0/en/mysqltest-commands.html + +没有列出的函数可以用 – exec +shell命令实现 + +50、 设置变量 let $a = xx, 前面可加 – + +51、 若是数字,可使用 inc $a / dec $a, 前面可加 – + +52、 可以赋值为sql返回值 let $q = `select c from t limit 1`, + 可以赋值为系统变量 let $q = $PATH + + + + + + + + + +## 参考 + +关于 MySQL 的测试框架可参考官方的手册 [The MySQL Test Framework](https://dev.mysql.com/doc/mysqltest/en/),也可下载 PDF 文档。 + +[Random Query Generator](https://launchpad.net/randgen) 产生随机数据,并生成随机的查询 SQL,用来测试 SQL 解析。 + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-10-20-kernel-memory-proc-filesystem_init.md b/_drafts/2015-10-20-kernel-memory-proc-filesystem_init.md new file mode 100644 index 0000000..2a386ea --- /dev/null +++ b/_drafts/2015-10-20-kernel-memory-proc-filesystem_init.md @@ -0,0 +1,144 @@ +--- +title: Linux 内存 Proc +layout: post +comments: true +language: chinese +category: [linux] +keywords: linux,内存,kernel,内存空间 +description: +--- + + + +## /proc/meminfo + +该文件是查看当前 Linux 系统内存使用状况的主要入口,最常用的 free、vmstat、dstat 等命令就是通过它获取数据,该文件的输出实现在 ```fs/proc/meminfo.c``` 文件中。 + +{% highlight text %} +MemTotal: 8070604 kB 内核可用内存 +MemFree: 842800 kB 从内核角度看,当前系统未使用内存(不含可回收内存),包括LowFree+HighFree(CONFIG_HIGHMEM) +MemAvailable: 3720728 kB 在不发生swap时的最大可用内存,真正可用物理内存 +Shmem: 962032 kB 一般是tmpfs使用,如/dev/shm,/run等,另外还有内核中的SysV + + +Buffers: 104944 kB +Cached: 3734768 kB +SwapCached: 1028 kB +Active: 4575172 kB +Inactive: 2156288 kB +Active(anon): 2779724 kB +Inactive(anon): 1074056 kB +Active(file): 1795448 kB +Inactive(file): 1082232 kB +Unevictable: 0 kB +Mlocked: 0 kB +SwapTotal: 8127484 kB +SwapFree: 8101540 kB +Dirty: 2156 kB +Writeback: 0 kB +AnonPages: 2891148 kB +Mapped: 554884 kB +Shmem: 962032 kB +Slab: 354444 kB +SReclaimable: 304396 kB +SUnreclaim: 50048 kB +KernelStack: 8144 kB +PageTables: 41536 kB +NFS_Unstable: 0 kB +Bounce: 0 kB +WritebackTmp: 0 kB +CommitLimit: 12162784 kB +Committed_AS: 6948452 kB +VmallocTotal: 34359738367 kB +VmallocUsed: 370112 kB +VmallocChunk: 34358947836 kB +HardwareCorrupted: 0 kB +AnonHugePages: 839680 kB +HugePages_Total: 0 +HugePages_Free: 0 +HugePages_Rsvd: 0 +HugePages_Surp: 0 +Hugepagesize: 2048 kB +DirectMap4k: 278372 kB +DirectMap2M: 5908480 kB +DirectMap1G: 2097152 kB +{% endhighlight %} + +在计算可用内存时,最早一般使用 free+cached,而实际上 cached 中包含了 + +> 简单介绍下 CONFIG_HIGHMEM 。 +> +> 部分 CPU (如ARM) 只能映射 4G 的内存管理空间,这 4G 空间包括了用户空间、内核空间、IO 空间,如果物理内存大于 4G ,那么必定有部分内存在这种情况下是无法管理的,这部分内存也就被称为 "high memory" 。 +> +> 简单来说,之所以有 high memory 是因为物理内存超过了虚拟内存,导致内核无法一次映射所有物理内存,为此就需要有临时的映射。注意,创建临时映射的成本很高,需要修改内核的 page table 以及 TLB/MMU 。 +> +> 详细可以查看 [Kenel-doc HIGH MEMORY HANDLING](https://www.kernel.org/doc/Documentation/vm/highmem.txt) 文档。 + +#### MemTotal + +系统从加电开始到引导完成,firmware/BIOS 要保留一些内存,Kernel 本身要占用一些内存,最后剩下可供 Kernel 支配的内存就是 MemTotal 。 + +该值在系统运行期间一般固定不变,详细可查看 dmesg 中的内存初始化信息。 + +#### MemFree + +从内核角度看,当前系统的可用内存,这里会将可以回收的内存也看作是已经分配的内存。 + +#### MemAvailable + +记录当前真正可用的物理内存,注意 MemFree 不能代表全部可用的内存,系统中有些内存虽然已被使用但是可以回收的,比如 cache、buffer、slab 都有一部分可以回收,所以这部分可回收的内存加上 MemFree 才是系统可用的内存。 + +### Active VS. Inactive + +除了通过 ```/proc/meminfo``` 查看外,还可以通过 ```vmstat -a``` 命令查看,与之相关的代码如下: + +{% highlight c %} +static int meminfo_proc_show(struct seq_file *m, void *v) +{ + ... ... + for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++) + pages[lru] = global_page_state(NR_LRU_BASE + lru); + + ... ... + seq_printf(m, + "Active: %8lu kB\n" + "Inactive: %8lu kB\n" + "Active(anon): %8lu kB\n" + "Inactive(anon): %8lu kB\n" + "Active(file): %8lu kB\n" + "Inactive(file): %8lu kB\n" + ... ... + K(pages[LRU_ACTIVE_ANON] + pages[LRU_ACTIVE_FILE]), + K(pages[LRU_INACTIVE_ANON] + pages[LRU_INACTIVE_FILE]), + K(pages[LRU_ACTIVE_ANON]), + K(pages[LRU_INACTIVE_ANON]), + K(pages[LRU_ACTIVE_FILE]), + K(pages[LRU_INACTIVE_FILE]), + ... ... +} +{% endhighlight %} + +为了实现 LRU 功能,正常应该有字段记录最近访问时间,可惜 x86 CPU 硬件并不支持这个特性,只能做到在访问页面时设置一个 Access Bit 标志位,无法记录时间。 + +所以 Linux 使用了一个折衷的方法,采用 LRU list 列表,把刚访问过的页面放在列首,越接近列尾的就是越长时间未访问过的页面,这样,虽然不能记录访问时间,但利用页面在 LRU list 中的相对位置也可以轻松找到年龄最长的页面。 + +内核设计了两种 LRU list: active list 和 inactive list,刚访问过的页面放进 active list,长时间未访问过的页面放进 inactive list,这样从 inactive list 回收页面就变得简单了。 + +内核线程 kswapd 会周期性地把 active list 中符合条件的页面移到 inactive list 中。 + +![active inactive list]({{ site.url }}/images/linux/kernel/memory-active-inactive-list.png "active inactive list"){: .pull-center width="80%" } + +如上,如果 inactive list 很大,表明在必要时可以回收的页面很多,反之,则说明可以回收的页面不多。另外,这里的内存是用户进程所占用的内存而言的,内核占用的内存 (包括slab) 不在其中。 + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-10-23-linux-security-enhance_init.md b/_drafts/2015-10-23-linux-security-enhance_init.md new file mode 100644 index 0000000..1e1f9c7 --- /dev/null +++ b/_drafts/2015-10-23-linux-security-enhance_init.md @@ -0,0 +1,44 @@ +--- +Date: Auguest 05, 2015 +title: Linux 安全加固 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + + + + +# sshd + +修改配置文件 /etc/ssh/sshd_config 。 + +{% highlight text %} +# 禁止root用户登陆 +PermitRootLog no +{% endhighlight %} + +3次登录密码错误,锁定账户5分钟: +vi /etc/pam.d/sshd +增加 +auth required pam_tally.so deny=3 unlock_time=5 + + +可以直接通过 /etc/motd 该文件,设置登陆提示。 + + +查看用户登陆历史 last -x 。 + +opasswd + + + + +改历史 + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-11-09-mysql-variables_init.md b/_drafts/2015-11-09-mysql-variables_init.md new file mode 100644 index 0000000..5bbe269 --- /dev/null +++ b/_drafts/2015-11-09-mysql-variables_init.md @@ -0,0 +1,264 @@ +--- +title: MySQL 变量相关 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: database,数据库,mysql,变量 +description: MySQL 通过变量设置来控制不同的行为,以及进行调优,从不同的角度看,定义方式各不相同,例如,如根据能否修改,可以分为动态和静态参数两种,动态参数可以修改,而静态参数是只读。在本文中简单介绍下 MySQL 中相关变量的设置,以及源码的实现。 +--- + +MySQL 通过变量设置来控制不同的行为,以及进行调优,从不同的角度看,定义方式各不相同,例如,如根据能否修改,可以分为动态和静态参数两种,动态参数可以修改,而静态参数是只读。 + +在本文中简单介绍下 MySQL 中相关变量的设置,以及源码的实现。 + + + +## 简介 + +MySQL 变量从不同的角度看,定义方式各不相同,例如,如根据能否修改,可以分为动态参数和静态参数两种,动态参数可以修改,而静态参数是只读。 + +例外,如果按照生命周期/作用域,可以将变量分为 gloal 和 session 两种,有些参数只能在会话中修改 (如 autocommit);有些参数会在整个实例生命周期内生效 (如 binlog_cache_size);有些既可以在会话又可以在整个声明周期内生效 (如 read_buffer_size)。 + +### 局部变量 + +也称为存储过程变量,通过 ```DECLARE variable_name data_type(size) DEFAULT default_value;``` 声明,不过只能在 BEGIN/END 声明之间使用。 + +{% highlight sql %} +drop procedure if exists add; +delimiter EOF +create procedure add ( in a int, in b int ) +begin + declare c int default 0; + set c = a + b; + select c as c; +end EOF +delimiter ; +{% endhighlight %} + +### 用户变量 + +用户变量的作用域要比局部变量要广,可作用于当前整个连接,但是在当前连接断开后,其所定义的用户变量都会消失。 + +{% highlight sql %} +drop procedure if exists math; +delimiter EOF +create procedure math ( in a int, in b int ) +begin + set @var1 = 1; --- 定义用户变量 + set @var2 = 2; + select @sum:=(a + b) as sum, @dif:=(a - b) as dif; ---- =在select中视为比较操作符 +end EOF +delimiter ; +{% endhighlight %} + +### 会话变量 + +服务器为每个连接的客户端维护一系列会话变量,在客户端连接时,使用相应全局变量的当前值对客户端的会话变量进行初始化。 + +客户端只能更改自己的会话变量,而不能更改其它客户端的会话变量,会话变量的作用域与用户变量一样,仅限于当前连接,当当前连接断开后,其设置的所有会话变量均失效。 + +可以通过如下的方式设置和查看会话变量。 + +{% highlight text %} +mysql> SET SESSION var_name = value; ← 设置会话变量 +mysql> SET @@session.var_name = value; +mysql> SET sort_buffer_size = 1024*1024*4; ← 默认为session级别 +mysql> SET sort_buffer_size = default; ← 恢复默认值 + +mysql> SELECT @@var_name; ← 查看会话变量 +mysql> SELECT @@session.var_name; +mysql> SHOW SESSION VARIABLES LIKE "%var%"; +mysql> SHOW SESSION VARIABLES; +{% endhighlight %} + +### 全局变量 + +全局变量影响服务器整体操作,当服务器启动时,它将所有全局变量初始化为默认值。这些默认值可以在选项文件中或在命令行中指定的选项进行更改。 + +要想更改全局变量,必须具有 SUPER 权限,全局变量作用于 server 的整个生命周期,重启后失效;当然,也可以在配置文件中设置。 + +{% highlight text %} +mysql> SET GLOBAL var_name = value; ← 设置全局变量,不能省略global,默认为session +mysql> SET @@global.var_name = value; + +mysql> SELECT @@global.var_name; +mysql> SHOW GLOBAL VARIABLES LIKE "%var%"; +mysql> SHOW GLOBAL VARIABLES; +{% endhighlight %} + +### 状态变量 + +其实就是监控 MySQL 服务器的运行状态,可以通过 ```show status``` 命令查看,只能由服务器修改,然后供用户查询。 + +### 其它 + +简单记录下比较容易混淆的地方。 + +有些变量同时为全局和会话变量,MySQL 将在建立连接时用全局级变量初始化会话级变量,但 **一旦连接建立之后,全局级变量的改变不会影响到会话级变量**。 + +查看系统变量的值,会优先显示会话级变量的值,如果这个值不存在,则显示全局级变量的值,当然你也可以加上 GLOBAL 或 SESSION/LOCAL 关键字区别。 + + + +{% highlight text %} +mysql> show variables like 'log%'; +mysql> show variables where variable_name like 'log%' and value='ON'; +mysql> show global variables; ← 查看全局变量 +mysql> show session/local variables; ← 查看局部变量 +mysql> select @@session/local.sql_mode; +{% endhighlight %} + +全局变量和会话变量可以从 INFORMATION_SCHEMA 数据库里的 GLOBAL_VARIABLES 和 SESSION_VARIABLES 表中获得。 + +注意:和启动时不一样的是,在运行时设置的变量不允许使用后缀字母 'K'、'M' 等,但可以用表达式来达到相同的效果,如:```SET GLOBAL read_buffer_size = 2*1024*1024``` 。 + + + +## 添加变量 + +当需要扩展 MySQL 时,可能需要通过变量来控制某些功能属性,当然可能在启动 server 时指定参数设置,通过配置文件,或者在运行时修改。 + +主要分为两种,全局变量控制系统的属性,会对所有的 session 生效;会话变量仅对当前的 session 有效,而对其他的 session 是透明的。

    + +其中有些变量是只能是全局变量,有些既可以是全局的又可以是会话的, + +可以通过下面的方式添加变量,添加方式可以详细参考 MySQL源码增加全局变量和会话变量。 + +

    + +

    全局变量

    +MariaDB 所支持的变量可以通过 mysqld --verbose --help 查看,添加时可以参考相应的类型,添加一个简单的布尔型全局变量 test_foobar,可以参考 old-mode 。 +

    1. +定义/声明全局变量
      +全局变量通常定义在 sql/mysqld.cc 文件的 /* Global variables */ 注释下面,当然可以放置到其它文件中,但是建议放在该文件中。可以使用条件编译选项,决定适用的平台等条件。

      + +全局变量需要在 sql/mysqld.h 中声明,以便在其它文件中使用/修改该变量。 +
      +/* Global variables @ sql/mysqld.cc */
      +my_bool test_foobar;
      +
      +/* @sql/mysqld.h */
      +extern my_bool test_foobar;
      +

    2. + +初始化全局变量
      +变量的初始化在 mysql_init_variables()@sql/mysqld.cc 中。 +
      +test_foobar = 0;
      +

    3. + +添加选项
      +选项保存在 sql/mysqld.cc 中的 my_long_options 数组中,用于在 mysqld 启动或者配置文件中设置启动的功能选项,最后一个为全NULL,表示结束。 +
      +struct my_option my_long_options[]=
      +{
      +    {"foobar", 0, "Just for a global test variable, default 1",
      +     &test_foobar, &test_foobar, 0,
      +     GET_BOOL, OPT_ARG, 1, 0, 0, 0, 0, NULL},
      +     ... ...
      +     {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
      +};
      +

    4. + + +添加变量set设置功能
      +对于全局变量可以通过 SET GLOBAL variables = value; 在 server 运行时进行设置,需要在 sql/sys_vars.cc 中设置。需要注意的是,不同类型的变量其初始化对象也不相同,可以参考 sys_vars.h 中不同类型的类。

      + +不同的类实际上是不同的类实例。 +
      +static Sys_var_mybool Sys_foobar(
      +       "foobar", "Just for test, foooooobar",
      +       GLOBAL_VAR(test_foobar), CMD_LINE(OPT_ARG), DEFAULT(TRUE));
      +
      + +

    5. + +测试
      +在启动时通过 --foobar=ON/OFF 指定,然后可以通过如下的方式查看。 +
      +mysql> show variables like 'foobar';        // 默认是ON
      +mysql> set global foobar=off;               // 设置全局变量,如果设置session变量则会报错
      +
      +可以尝试通过通过两个不同的客户端链接,可以发现当一个修改之后,另一个客户端可以同时看到该修改量。 +
    +如何添加处理过程????? +

    + + +

    会话变量

    +相比全局变量,会话变量的添加相对比较容易,在此添加同样布尔型的变量 test_session_foobar 。

    1. +定义会话变量
      +会话变量定义在 sql_class.h 中的 struct system_variables 结构体中。 +
      +typedef struct system_variables
      +{
      +    my_bool test_session_foobar;
      +    ... ...
      +} SV;
      +
      +

    2. + +添加选项
      +在 sql/mysqld.cc 的 my_long_options 数组中添加变量的参数选项,同上,此时可以通过 --session-foobar 指定。 +
      +struct my_option my_long_options[]=
      +{
      +    {"session-foobar", 0, "Just for a session test variable, default 1",
      +     &global_system_variables.test_session_foobar,
      +     &global_system_variables.test_session_foobar,
      +     0, GET_BOOL, OPT_ARG, 1, 0, 0, 0, 0, NULL},
      +     ... ...
      +     {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}
      +};
      +

    3. + +添加变量SET功能
      +在 sql/sys_vars.cc 中添加系统变量设置的代码,用于实现 set session VAR = VALUE; 功能。 +
      +static Sys_var_mybool Sys_session_foobar(
      +    "session_foobar", "Just for test, foooooobar",
      +    SESSION_VAR(test_session_foobar), CMD_LINE(OPT_ARG), DEFAULT(TRUE));
      +

    4. + +测试
      +与设置全局变量相似,可以在启动时通过 --session-foobar=ON/OFF 指定,然后可以通过如下的方式查看。 +
      +mysql> show variables like 'session_foobar';         // 默认是ON
      +mysql> set session session_foobar=off;               // 可以设置全局/会话变量
      +
      +
    +

    + +总体来说,如果想要在启动 server 时可以通过命令行或者配置文件指定,则需要添加到 my_long_options[]@sql/mysqld.cc 中,如 autocommit;如果想添加全局变量,可以在 sql/sys_vars.cc 中添加;如果要添加会话变量则需要在 struct system_variables@sql/sql_class.h 中添加成员变量;当然也可以设置只有 session 的变量。 + + + + + + + + +## 源码解析 + +变量的定义在 sql/sys_vars.cc 文件中, + +{% highlight text %} +mysqld_main() + |-init_common_variables() + |-umask() ← mask设置 + |-tzset() ← 时区设置 + |-init_thread_environment() ← 设置线程的变量 + |-mysql_init_variables() ← 设置全局变量 + |-ignore_db_dirs_init() + |-add_status_vars() + |-log_syslog_init() +{% endhighlight %} + +## 参考 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-12-03-mysql-innodb-deadlocks_init.md b/_drafts/2015-12-03-mysql-innodb-deadlocks_init.md new file mode 100644 index 0000000..958f3b2 --- /dev/null +++ b/_drafts/2015-12-03-mysql-innodb-deadlocks_init.md @@ -0,0 +1,230 @@ +--- +title: InnoDB 死锁处理 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,innodb,deadlock,死锁 +description: 当两个或以上的事务相互持有并请求锁,当形成一个循环的依赖关系时,就会产生死锁;InnoDB 会自动检测事务死锁,立即回滚其中代价最小的某个事务,并且返回一个错误。通常,在一个事务系统中,死锁是确切存在并且是不能完全避免的,偶然发生的死锁不必担心,但死锁频繁出现的时候就要引起注意了。接下来,我们看看 InnoDB 对死锁的处理,以及诊断方式。 +--- + +当两个或以上的事务相互持有并请求锁,当形成一个循环的依赖关系时,就会产生死锁;InnoDB 会自动检测事务死锁,立即回滚其中代价最小的某个事务,并且返回一个错误。 + +通常,在一个事务系统中,死锁是确切存在并且是不能完全避免的,偶然发生的死锁不必担心,但死锁频繁出现的时候就要引起注意了。 + +接下来,我们看看 InnoDB 对死锁的处理,以及诊断方式。 + + + +## 简介 + +InnoDB 引擎采用两种方法处理死锁:A) 被动等待超时,通过参数 innodb_lock_wait_timeout 控制;B) 主动通过 Wait for Graph 算法检测,每当加锁请求无法立即满足需要并进入等待时,该算法就会被触发。 + +Wait for Graph 就是当事务 A 需要等待事务 B 的资源时,就会生成一条有向边指向 B,依次类推,最后形成一个有向图;然后检测这个有向图是否出现环路即可,如果出现环路则发生了死锁! + +简单来说,产生死锁的必要条件: + +1. 两个及以上的多个并发事务; +2. 每个事务都持有了锁,或者是在等待锁; +3. 每个事务为了完成业务逻辑都需要再继续持有锁; +4. 事务之间产生加锁的循环等待,形成死锁。 + +InnoDB 有一个后台的锁监控线程,该线程负责查看可能的死锁问题,并自动告知用户。 + +另外,死锁与事务隔离级别、是否为二级索引相关。 + + +### 死锁诊断 + +在 MySQL 5.6 之前,只有最新的死锁信息可以使用 ```show engine innodb status``` 命令查看;也可以使用 Percona Toolkit 工具包中的 [pt-deadlock-logger](https://www.percona.com/doc/percona-toolkit/2.2/pt-deadlock-logger.html) 保存死锁信息,可以写入文件或者表中,该工具也是通过上述的命令查看。 + +对于 MySQL 5.6 及以上版本,可以通过参数 innodb_print_all_deadlocks 把 InnoDB 中发生的所有死锁信息都记录在错误日志里面。 + +死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁;InnoDB 目前处理死锁的方法就是将持有最少行级排他锁的事务进行回滚,这也是相对比较简单的死锁回滚方式。 + +### 死锁处理 + +首先死锁是正常的,不必过于担心;当然,有如下的方法,可以处理死锁。 + +#### 避免死锁 + +最好的方式是在业务逻辑层做调整,也就是修改 SQL 的操作顺序;另外,尽量缩短长事务。 + +#### 死锁检测 + +InnoDB 会记录事务所维持以及等待的锁,然后通过 Wait for Graph 算法进行检测。 + +#### 等锁超时 + +也就是锁的持有时间,如果说一个事务持有锁超过设置时间的话,就直接抛出一个错误,InnoDB 不会回滚该事务。如果业务逻辑中有超长的事务,就需要把锁超时时间设置为大于事务执行时间。 + +### 常用命令 + +与锁相关的环境变量,可以通过如下命令查看。 + +{% highlight text %} +----- 加锁的超时时间,单位为秒 +mysql> SHOW VARIABLES LIKE 'innodb_lock_wait_timeout'; + +----- 对于MySQL5.6之后可以进行设置,将死锁打印到错误日志 +mysql> SHOW VARIABLES LIKE 'innodb_print_all_deadlocks'; +----- 开启死锁日志,如果有死锁,则可以查看如下日志 +mysql> SET GLOBAL innodb_print_all_deadlocks = 1; +... ... +2016-02-16 22:10:52 InnoDB: transactions deadlock detected, dumping detailed information. +... ... + +----- 查看当前连接的事务ID +mysql> SELECT trx_id FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = connection_id(); +{% endhighlight %} + +注意,对于上述的事务 ID 查看命令,如果是只读的,启动时不会分配事务 ID 。 + + +### 测试 + +创建环境。 + +{% highlight text %} +mysql> CREATE TABLE account(id INT PRIMARY KEY, money INT NOT NULL); +mysql> INSERT INTO account values(1,1000), (2, 1000); +{% endhighlight %} + +接下来,分别在两个终端各启动一个事务。 + +{% highlight text %} +----- TRANS A -----------------------------+----- TRANS B ----------------------------- +### ID=9249 | ### ID=9250 +START TRANSACTION; | START TRANSACTION; +UPDATE account SET money=2000 WHERE id=1; | + | UPDATE account SET money=2000 WHERE id=2; +### 由于id=2记录加了X锁,此时会被卡住 | +UPDATE account SET money=3000 WHERE id=2; | + | ### 监测到发生死锁,直接回滚该事务 + | UPDATE account SET money=3000 WHERE id=1; +{% endhighlight %} + +对于事务 A ,当尝试更新 id=2 时,如果此时加锁超时,会返回客户端报错信息,不过 **该事务不会被回滚**;而事务 B 尝试更新 id=1 时,此时会回滚该事务。 + +上述事务 B 当检测到死锁后,会抛出如下的报错信息,即发生了死锁。 + +{% highlight text %} +ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction +{% endhighlight %} + +然后,通过 ```show engine innodb status``` 命令查看死锁信息,内容如下。 + +{% highlight text linenos %} +------------------------ +LATEST DETECTED DEADLOCK +------------------------ +2016-02-05 12:39:02 0x7f398fe72700 +*** (1) TRANSACTION: +TRANSACTION 9249, ACTIVE 255 sec starting index read +mysql tables in use 1, locked 1 +LOCK WAIT 3 lock struct(s), heap size 1160, 3 row lock(s), undo log entries 1 +MySQL thread id 2, OS thread handle 139885204420352, query id 60 localhost root updating +UPDATE account SET money=3000 WHERE id=2 +*** (1) WAITING FOR THIS LOCK TO BE GRANTED: +RECORD LOCKS space id 54 page no 3 n bits 72 index PRIMARY of table `test`.`account` trx id 9249 lock_mode X locks rec but not gap waiting +Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 + 0: len 4; hex 80000002; asc ;; + 1: len 6; hex 000000002422; asc $";; + 2: len 7; hex 37000001840b6a; asc 7 j;; + 3: len 4; hex 800007d0; asc ;; + +*** (2) TRANSACTION: +TRANSACTION 9250, ACTIVE 155 sec starting index read +mysql tables in use 1, locked 1 +3 lock struct(s), heap size 1160, 2 row lock(s), undo log entries 1 +MySQL thread id 3, OS thread handle 139885204154112, query id 61 localhost root updating +UPDATE account SET money=3000 WHERE id=1 +*** (2) HOLDS THE LOCK(S): +RECORD LOCKS space id 54 page no 3 n bits 72 index PRIMARY of table `test`.`account` trx id 9250 lock_mode X locks rec but not gap +Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 + 0: len 4; hex 80000002; asc ;; + 1: len 6; hex 000000002422; asc $";; + 2: len 7; hex 37000001840b6a; asc 7 j;; + 3: len 4; hex 800007d0; asc ;; + +*** (2) WAITING FOR THIS LOCK TO BE GRANTED: +RECORD LOCKS space id 54 page no 3 n bits 72 index PRIMARY of table `test`.`account` trx id 9250 lock_mode X locks rec but not gap waiting +Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 + 0: len 4; hex 80000001; asc ;; + 1: len 6; hex 000000002421; asc $!;; + 2: len 7; hex 3600000183026d; asc 6 m;; + 3: len 4; hex 800007d0; asc ;; + +*** WE ROLL BACK TRANSACTION (2) +{% endhighlight %} + +第 4 行是死锁发生的时间,可以通过该时间戳与应用程序的日志报错时间匹配,这样可以找到事务中对应的语句。 + + + +## 死锁案例 + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-12-04-mysql-innodb-locks_init.md b/_drafts/2015-12-04-mysql-innodb-locks_init.md new file mode 100644 index 0000000..666c1ea --- /dev/null +++ b/_drafts/2015-12-04-mysql-innodb-locks_init.md @@ -0,0 +1,31 @@ +--- +title: InnoDB 锁管理 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + + + + +InnoDB 对于主表采用聚簇索引,也就是表数据和主键一起存储,并按照主键递增顺序排序,主键索引的叶结点存储行数据;对于普通索引(或者二级索引),其叶子节点存储的是主键值。主键只锁单条记录;二级索引则包括了索引+主表;非索引全表扫描则会锁全表。不同隔离级别锁处理方式不同,同时死锁也不同;扫描范围同样不同;lock_sys_t@include/lock0lock.hlock_sys@lock/lock0lock.cc 全局变量(L290)lock_t@include/lock0types.hlatch闩锁(轻量级的锁),要求锁定的时间非常短,若持闩时间长,则应用性能变的非常差;InnoDB中可分为mutex(互斥锁)和rwlock(读写锁),其目的用来保证并发线程操作临界资源的正确性,并且没有死锁检测的机制。http://www.cnblogs.com/olinux/p/5174145.html + + +MySQL 加锁处理分析 +http://hedengcheng.com/?p=771 + +MySQL数据库InnoDB存储引擎中的锁机制 +http://www.searchdatabase.com.cn/showcontent_61663.htm + +[MySQL5.7] Innodb的索引锁优化 +https://yq.aliyun.com/articles/41087 + +MySQL · 引擎特性 · InnoDB 事务锁简介 +https://yq.aliyun.com/articles/4270 + +https://blogs.oracle.com/mysqlinnodb/resource/innodb-trxlocks.pdf + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-12-15-network-arp-protocol_init.md b/_drafts/2015-12-15-network-arp-protocol_init.md new file mode 100644 index 0000000..29c7813 --- /dev/null +++ b/_drafts/2015-12-15-network-arp-protocol_init.md @@ -0,0 +1,265 @@ +--- +title: Linux 嗅探工具 Dsniff +layout: post +comments: true +language: chinese +category: [linux, network] +--- + + + + + + + + + + + + + + + + + + + +在 CentOS 中,可以直接通过如下命令安装依赖库。 + +{% highlight text %} +# yum install libdb-devel libpcap-devel libnet-devel openssl-devel +# yum --enablerepo=epel install libnids-devel +{% endhighlight %} + +需要注意的时,libnet 使用的是 1.0.X 版本,而非 1.1.X ,两者的 API 是不同的,可以参考 [The Libnet Packet Construction Library](http://packetfactory.openwall.net/projects/libnet/index.html) 。所以,如果安装源的版本比较高,那么只能通过源码进行安装。 + +## ARP, Address Resolution Protocol + +地址解析协议用来根据 IP 地址获取 MAC 地址,主机发送信息时将包含目标 IP 地址的 ARP 请求广播到网络上的所有主机,并接收返回的目标物理地址;在本机中会暂时缓存 ARP 信息,下次请求直接查询缓存。 + +ARP 协议可以参考 [RFC1027](http://www.ietf.org/rfc/rfc1027.txt),常见的操作如下。 + +{% highlight text %} +----- 查看当前的ARP缓存,其中通过ip可以显示当前的状态 +$ arp -an +$ ip neigh show + +----- 直接通过Shell删除ARP缓存 +# arp -n|awk '/^[1-9]/{print "arp -d " $1}'|sh -x +# ip neigh flush dev eth0 + +----- 查看ARP老化时间,单位是秒,默认时30s +$ cat /proc/sys/net/ipv4/neigh/eth0/base_reachable_time + +----- 查看当前网关的MAC地址 +$ arping -I eth0 192.168.1.1 + +----- 查看当前网段的所有MAC地址 +$ nmap -sP 192.168.1.0/24 +{% endhighlight %} + +tcpdump -i eth0 -ent '(dst 192.168.0.125 and src 192.168.0.141) or (dst 192.168.0.141 and src 192.168.0.125)' + +tcpdump arp -n -i wlp3s0 + + + + +arpwatch 实际上就是通过 libpcap 进行监控,也就是根据 arp or rarp 过滤条件接收报文。 + + +driftnet 抓取图片 + + +### ARP 攻击 + +其中 192.168.3.11 为需要攻击的机器 IP ,192.168.3.1 为网关 IP 。 + +{% highlight text %} +# arpspoof -i wlp3s0 -t 192.168.3.11 192.168.3.1 +{% endhighlight %} + +这步是告诉 192.168.3.11 机器,192.168.3.1 的 MAC 地址是我们本机的 MAC 地址。然后,另外打开一个终端,告诉 192.168.3.1,机器 192.168.3.11 的 MAC 地址为本机地址。 + +{% highlight text %} +# arpspoof -i wlp3s0 -t 192.168.3.1 192.168.3.11 +{% endhighlight %} + +打开 IP 的转发功能,这样被攻击者 192.168.3.11 的所有报文都会经过本机。 + +{% highlight text %} +# echo 1 > /proc/sys/net/ipv4/ip_forward +{% endhighlight %} + +现在,就可以通过 tcpdump 监控 192.168.3.11 与外部网路的数据报文。 + +{% highlight text %} +# tcpdump host 192.168.3.11 and not arp +{% endhighlight %} + +对于这种中间人攻击,可以通过 arpwatch 进行监控。 + +{% highlight text %} +# arpwatch -I eth0 -d # 监控ARP的变化,并发送邮件 +# tailf /var/log/messages | grep arpwatch # 查看打印到syslog的日志 +{% endhighlight %} + +arpwatch 会将新增、变更等操作发送到 root 邮箱。 + + + + +tcpdump -nn vrrp + +Linux防止ARP欺骗攻击 + +https://www.haiyun.me/tag/arpoison/ + +linux下防止arp攻击 + +http://www.linuxsong.org/2010/09/prevent-arp-attack/ + +普通ARP和免费ARP及arping命令的使用 + +http://tenderrain.blog.51cto.com/9202912/1650245 + + + +### 其它 + +当使用 arpspoof 时,如果 -i 参数指定的端口不是 eth0,那么就会出现如下的错误: + +{% highlight text %} +arpspoof: couldn't arp for host 192.168.3.11 +{% endhighlight %} + +此时需要修改源码 arp_cache_lookup()@arp.c 中的 strncpy() 函数赋值的设备名称。 + + + + + + + + +Note: 如果出现no route to host;错误,那么可能是由于防火墙导致,可以通过iptables -F清理。 + +ifconfig eth0:0 192.144.51.73 netmask 255.255.255.0 up +nc -nvvvvkl 2389 + +另外机器: +nc -nvv 192.144.51.73 2389 +IP-MAC绑定 +arp -s 123.253.68.209 00:19:56:6F:87:D2 临时增加 +echo '192.168.1.1 00:02:B3:38:08:62' > /etc/ip-mac 启动时设置 +arp -f /etc/ip-mac +arp(Flags增加M) arp -a (增加PERM标志) 查看是否已经绑定 +arp -d IP 删除ARP缓存 + +tcpdump -nn arp | grep "192.144.51.73" +14:21:30.349933 ARP, Request who-has 192.144.51.73 tell 192.144.51.61, length 28 请求 +14:21:30.349949 ARP, Reply 192.144.51.73 is-at 28:6e:d4:88:dd:c2, length 28 相应 + +0000 ffff ffff ffff 0000 5e00 01ea 0806 0001 +0010 0800 06 04 00 01 00 00 5e 00 01 ea 86 4a ea 01 +0020 00 00 5e 00 01 ea 86 4a ea 01 0000 00 00 00 00 +0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +0040 00 00 00 00 + +ffff ffff ffff 目的MAC地址,说明前48bit均为1,表示这是一个局域网广播地址; +0000 5e00 01ea 数据包发送的源MAC地址; +08 06表示为数据包类型为arp数据包; +00 01表示这是一个以太网数据包; +08 00表示为IP协议,6是硬件地址长度,4为协议地址长度; +00 01表示是ARP请求报文; +00 00 5e 00 01 ea发送者MAC地址; +86 4a ea 01(转换成十进制是134.74.234.1)是发送者的协议地址(由于协议是IP,所以这是IP地址); +86 4a ea 01是被请求的协议地址(可以看到请求的地址就是自身的IP地址); +00 00 5e 00 01 ea是被请求的MAC地址,正常情况下,如果不是免费ARP,这里应该为全0,在响应的时候,由目的主机来填写,但是在免费ARP的请求报文中,这里已经自动填写上自身的MAC地址。 + +有两种作用:A) 查找某个IP(非本地)对应的MAC地址;B) 宣告要使用IP(本地),用于告诉整个广播域要使用这个IP,同时可以查看是否IP冲突。 + + + +## arping + +arping 有两个版本:A) Thomas Habets 写的,可以通过 ```arping ``` 查看该 MAC 对应的 IP 地址;B) Linux iputils suite 不提供上述功能。 + +两者的命令行参数有所区别,所以在使用时需要注意,一般 CentOS 是有的是后者,Debian 使用的是前者,可以通过 ```arping -V``` 查看版本。 + +如下是 CentOS 版本中的常用参数,简单列举如下: + +{% highlight text %} +常用参数: + -f + 第一次响应后立即退出,通常用于确认对应的IP是否存在; + -c NUM + 指定报文发送的次数; + -i DEVICE + 如果有多块网卡,指定报文发送的网卡; + -D + DAD(Duplicate Address Detection)模式,用于探测IP是否被使用,发送广播报文; + -U + UAP(Unsolicited ARP Mode)主动发送ARP请求,用于更新ARP caches; + -A + 与-U参数相同,不过使用ARP REPLY报文而非ARP REQUEST; +{% endhighlight %} + +在使用时,可以通过如下命令查看发送的报文。 + +{% highlight text %} +# tcpdump -i eth0 -nn arp | grep "192.144.51.73" +{% endhighlight %} + +如下是常用示例: + +{% highlight text %} +----- 发送广播请求,确认该IP对应的MAC地址,不会更新本地ARP缓存中 +arping 192.144.51.52 +14:48:54.527346 ARP, Request who-has 192.144.51.52 (28:6e:d4:88:dd:53) tell 192.144.51.85, length 28 +14:48:54.527484 ARP, Reply 192.144.51.52 is-at 28:6e:d4:88:dd:53, length 28 + +----- 发送广播报文,探测该IP是否使用,不会更新本地ARP缓存 +arping -D 192.144.51.52 +14:56:59.538745 ARP, Request who-has 192.144.51.52 (ff:ff:ff:ff:ff:ff) tell 0.0.0.0, length 28 +14:56:59.539401 ARP, Reply 192.144.51.52 is-at 28:6e:d4:88:dd:53, length 28 + +----- 告知192.144.51.61更新ARP缓存 +arping -U -Ieth0 -s192.144.51.73 192.144.51.61 +15:08:53.402293 ARP, Request who-has 192.144.51.61 (ff:ff:ff:ff:ff:ff) tell 192.144.51.73, length 28 +15:08:54.402451 ARP, Request who-has 192.144.51.61 (28:6e:d4:88:dd:4b) tell 192.144.51.73, length 28 +{% endhighlight %} + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2015-12-25-network-wireless-hack_init.md b/_drafts/2015-12-25-network-wireless-hack_init.md new file mode 100644 index 0000000..2f485fb --- /dev/null +++ b/_drafts/2015-12-25-network-wireless-hack_init.md @@ -0,0 +1,154 @@ +--- +Date: Auguest 05, 2015 +title: Linux 无线网络 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + + + + +{% highlight text %} +----- 查看网卡功能 +# iw list + +----- 查看wlp3s0的配置信息 +# iw wlp3s0 info +Interface wlp3s0 + ifindex 3 # 设备序号是3 + type managed # 连接类型,单点对AP的连接模式 + wiphy 0 + +----- 查看当前无线网络信号,包括网络名称(SSID)、信号强度、加密算法等 +# iw dev wlp3s0 scan | less + +----- 如果没有密码则直接连接 +# iw dev wlp3s0 connect [SSID] +----- 如果网络是用WEP加密的,也非常容易 +# iw dev wlp3s0 connect [SSID] key 0:[WEP-PASSWORD] + +----- 通过DHCP获取本地IP,连接到无线网络 +# dhcpcd wlp3s0 +{% endhighlight %} + +关于无线的配置,可以参考 iw 安装包,以及 [Linux Wireless](http://www.linuxwireless.org/en/users/Documentation/iw) 文档介绍;iw 是 iwconfig 的替换。 + + + + + + + + +# 常见操作 + +下面列举一下一些常见的操作。 + +## 网卡设置为 Monitor Mode + +通常一些无线秘密的破解工具,如 aircrack-ng 需要将网卡设置为监听模式。可以直接使用该工具包中的一个 Bash 脚本 airmon-ng 进行设置。 + +{% highlight text %} +# airmon-ng start wlp3s0 +{% endhighlight %} + +当然,也可以手动将无线网卡设置为 Monitor Mode 。 + +{% highlight text %} +----- 0. 查看网卡是否支持Monitor模式,在Supported interface modes中查看 +# iw list +Wiphy phy0 + ... ... + Supported interface modes: + * ... ... + * monitor + * ... ... + +----- 1. 关闭无线网卡 +# ip link set wlp3s0 down + +----- 2.0 获取物理设备号 +# ls -l "/sys/class/net/wlp3s0/phy80211" | sed 's/^.*\/\([a-zA-Z0-9_-]*\)$/\1/' +phy0 +----- 2.1 其中phy0是如上的输出结果 +# iw phy phy0 interface add wlp3s0mon type monitor +----- 2.2 查看设备 +# ifconfig wlp3s0mon +wlp3s0mon: flags=4099 mtu 1500 + ether ac:2b:6e:8b:42:28 txqueuelen 1000 (Ethernet) + RX packets 0 bytes 0 (0.0 B) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 0 bytes 0 (0.0 B) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + +----- 3. 将wlp3s0mon设置为monitor mode +# ip link set wlp3s0mon down +# iw dev wlp3s0mon set type monitor +# ip link set wlp3s0mon up +# iw dev wlp3s0mon set channel 10 +# iw dev wlp3s0 set freq 5825 # 也可以修改频率,可选 + +----- 4. 查看网卡的属性 +# ifconfig wlp3s0mon +wlp3s0mon: flags=4099 mtu 1500 + unspec AC-2B-6E-8B-42-28-F0-00-00-00-00-00-00-00-00-00 txqueuelen 1000 (UNSPEC) + RX packets 150 bytes 47203 (46.0 KiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 0 bytes 0 (0.0 B) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + +# iw wlp3s0mon info +Interface wlp3s0mon + ifindex 4 + wdev 0x2 + addr ac:2b:6e:8b:42:28 + type monitor + wiphy 0 + +----- 5. 恢复原无线设备 +# ip link set wlp3s0 up +{% endhighlight %} + + +## 无线密码破解 + +可以参考 [完全教程 Aircrack-ng破解WEP、WPA-PSK加密利器.pdf](/reference/linux/network/Aircrack_ng_WEP_WPA_PSK.pdf "完全教程 Aircrack-ng破解WEP、WPA-PSK加密利器.pdf"),以及《无线黑客傻瓜书》。 + + +

    +

    +

    +

    +

    + + + + + + + + + + + + + + + + + +{% highlight c %} +{% endhighlight %} diff --git a/_drafts/2016-01-04-network-namespace_init.md b/_drafts/2016-01-04-network-namespace_init.md new file mode 100644 index 0000000..38da324 --- /dev/null +++ b/_drafts/2016-01-04-network-namespace_init.md @@ -0,0 +1,151 @@ +--- +Date: October 19, 2013 +title: Linux 网络命名空间 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + + + + + + + + +在 IP 网段中有 A B C 三个网段的私有 IP 可以使用,分别是:10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255 。 + +上图是 docker 中的一个网络设置,其使用了一个私有网段,172.40.1.0/24,还可能会使用 10.0.0.0 和 192.168.0.0 这两个私有网段,关键看你的路由表中是否配置了,如果没有配置,就会使用,如果你的路由表配置了所有私有网段,那么 docker 启动时就会出错了。 + +当启动一个 Docker 容器后,可以使用 ip link/addr show 来查看当前宿主机的网络情况,通常有一个 docker0 以及 vethXXXXX 的虚拟网卡 (给容器用) 。 + + + + +## 创建一个 network namespace + +建立一个基本的网络命名空间。 + +{% highlight text %} +----- 新建的NET-NS会保存在该目录下,如果没有直接新建目录,非必须 +master# mkdir -p /var/run/netns + +----- 增加一个network namespace名称为foons +master# ip netns add foons + +----- 查看当前所有的网络命名空间,或者查看/var/run/netns目录 +master# ip netns list + +----- 激活foons中的loopback设备 +master# ip netns exec foons ip link set dev lo up + +----- 在新的命名空间中执行bash,这样就可以直接查看网络设备信息等 +master# ip netns exec foons bash + +----- 在新的命名空间查看设备 +foons# ip link show +{% endhighlight %} + +其中 ip netns exec NS CMD 命令可用于在一个网络命名空间中执行响应的命令。 + +接下来新建一对新的虚拟网卡,其中一个给搁到上面新建的网络命名空间中。 + +{% highlight text %} +----- 新建两个虚拟网卡,分别为veth0和foo-veth0 +master# ip link add veth0 type veth peer name foo-veth0 + +----- 查看新建的虚拟网卡 +master# ip link show + +----- 将其中的foo-veth0放到新建的命名空间foons中 +master# ip link set foo-veth0 netns foons + +----- 查看foons中的网络设备 +master# ip netns exec foons ip link list + +----- 把容器里的foo-veth0改名为eth0 +master# ip netns exec foons ip link set dev foo-veth0 name eth0 +{% endhighlight %} + +在网络命名空间中查看,除了上述 ip netns 方法之外,还可以在上述执行 bash 之后,直接在 bash 中查看,同样是执行 ip link show 查看。 + +接下来激活网络设备。 + +{% highlight text %} +----- 激活网络命名空间中的网络设备 +master# ip netns exec foons ifconfig eth0 172.18.0.20/16 up +master# ifconfig veth0 172.18.0.21/16 up + +----- 或者分为两步,同上 +master# ip netns exec foons ip addr add 172.18.0.20/16 dev eth0 +master# ip netns exec foons ip link set eth0 up +{% endhighlight %} + +接下来添加一个网桥,将 foons 的网络设备与真实的网络设备链接起来。 + +{% highlight text %} +----- 增加一个网桥foobr0 +master# brctl addbr foobr0 + +----- 暂时先关闭网桥 +master# brctl stp foobr0 off + +----- 为网桥设置IP地址,并启动 +master# ifconfig foobr0 172.18.0.1/24 up + +----- 将veth0添加到网桥上 +master# brctl addif foobr0 veth0 + +----- 为容器增加一个路由规则,让容器可以访问外面的网桥 +master# ip netns exec foons ip route add default via 172.18.0.1 +{% endhighlight %} + + +目前,可以从容器中访问到网桥,但是不能访问到外部网络,如果要访问外部网络,还需要添加 NAT 规则。 + +{% highlight text %} +----- 地址转换,从而可以访问外部的网络,对于源地址是127.18.0.0/16且目的地址不是172.18.0.0/16的IP进行转换 +master# iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -d 172.18.0.0/16 -j MASQUERADE + +----- 查看当前设置的规则 +master# iptables -nL POSTROUTING -t nat --line-number + +----- 假设对应的是第2条记录,可以通过如下方式删除记录 +master# iptables -D POSTROUTING 2 +{% endhighlight %} + + + +iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -o foobr0 -j SNAT --to-source 192.168.1.10 + + + + + + + + + + + + + +.2 举例新增zabbix端口的映射: +3.2.1 单IP单容器端口扩容: +iptables -t nat -A PREROUTING -p tcp -m tcp --dport 10050 -j DNAT --to-destination 172.17.0.3:10050 +iptables -t nat -A PREROUTING -p tcp -m tcp --dport 10051 -j DNAT --to-destination 172.17.0.3:10051 + +3.2.2 单IP多容器端口扩容: +iptables -t nat -A PREROUTING -p tcp -m tcp --dport 50010 -j DNAT --to-destination 172.17.0.3:10050 +iptables -t nat -A PREROUTING -p tcp -m tcp --dport 50011 -j DNAT --to-destination 172.17.0.3:10051 +#另一个容器则可以规划为60010,60011,这样在zabbix监控的时候,就需要指定客户容器的端口连接了。 + +3.2.3 多IP多容器端口扩容: +iptables -t nat -A PREROUTING -d 10.18.103.2 -p tcp -m tcp --dport 10050 -j DNAT --to-destination 172.17.0.3:10050 +iptables -t nat -A PREROUTING -d 10.18.103.2 -p tcp -m tcp --dport 10051 -j DNAT --to-destination 172.17.0.3:10051 +#iptables -t nat -A PREROUTING -d 10.18.103.3 -p tcp -m tcp --dport 10050 -j DNAT --to-destination 172.17.0.4:10050 +#iptables -t nat -A PREROUTING -d 10.18.103.3 -p tcp -m tcp --dport 10051 -j DNAT --to-destination 172.17.0.4:10051 +#这样zabbix连接10.18.103.2,3的正常zabbix端口就可以了。 + + diff --git a/_drafts/2016-01-09-saltstack-sourcecode_init.md b/_drafts/2016-01-09-saltstack-sourcecode_init.md new file mode 100644 index 0000000..6216e8e --- /dev/null +++ b/_drafts/2016-01-09-saltstack-sourcecode_init.md @@ -0,0 +1,56 @@ +--- +Date: October 19, 2013 +title: SaltStack 源码解析 +layout: post +comments: true +language: chinese +category: [misc] +--- + + + + + + +对于 master 可以通过 salt-master -d 启动。 + + +实际上执行的是 scripts.py:salt_master() 函数。 + +{% highlight text %} +salt_master()@scripts.py # +{% endhighlight %} + +命令是通过 salt 客户端发送的,实际上对应源码中的 class LocalClient,会将请求通过 4506 发送到 master,然后监听 master_event_pub.ipc 等待返回的消息。 + + +# 执行流程 + +假设通过 salt 执行如下命令。 + +{% highlight text %} +# salt '*' test.ping +{% endhighlight %} + +### 1. LocalClient + +salt 命令会通过 LocalClient 生成一个请求,然后将请求发送到 master 中 ReqServer 的 TCP:4506 端口。 + + +### 2. ReqServer + +在 salt-master 中的 ReqServer 发现上述的请求,并通过 workers.ipc 将请求发送给可用的 MWorker 。 + +### 3. MWorker + +其中的一个 worker 会处理上述发送的请求,首先会检查请求的用户是否有权限,然后通过 ClearFuncs.publish() 将请求发送所有连接的 minions 。 + + + +# Client + + +![SaltStack Commands Syntax]({{ site.url }}/images/network/saltstack/cmd-syntax.png "SaltStack Commands Syntax"){: .pull-center} + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-01-27-mysql-threads_init.md b/_drafts/2016-01-27-mysql-threads_init.md new file mode 100644 index 0000000..82294a2 --- /dev/null +++ b/_drafts/2016-01-27-mysql-threads_init.md @@ -0,0 +1,302 @@ +--- +title: MySQL 线程简介 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,杂项 +description: 简单记录下 MySQL 常见的一些操作。 +--- + + + + +## 简介 + +我们知道,MySQL 采用的是线程模型,在此主要列举出 MySQL 可能存在的线程,一些常见的操作命令如下。 + +{% highlight text %} +$ pstack `pgrep mysqld` 打印所有线程的堆栈 +{% endhighlight %} + +在源码中,主要通过 ```mysql_thread_create()``` 函数创建线程。 + + + +## 同步机制 + +InnoDB 中有多种类型的处理线程,通常是采用一个协调线程 + 多个工作线程,例如 page cleaner 线程,可以通过 innodb_page_cleaners 变量设置。 + +当将 innodb_page_cleaners 变量设置为 7 时,会包含一个协调线程以及 6 个工作线程, + +{% highlight text %} +pc_request() 协调线程,设置slots + |-os_event_set() 通知等待的工作线程 + |-os_event_t::set() + |-os_event_t::broadcast() + |-mysql_cond_broadcast() + |-inline_mysql_cond_broadcast() + |-native_cond_broadcast() +{% endhighlight %} + + + +## 主库线程 + +主要在链接主库时,会调用如下函数,执行主库的 dump 操作。 + +{% highlight text %} +mysql_binlog_send() +{% endhighlight %} + + + +## 备库线程 + +{% highlight text %} +start_slave_threads() + |-handle_slave_io() + |-handle_slave_sql() +{% endhighlight %} + + + + + + + +## InnoDB + +在 InnoDB 中,线程的创建都是通过 ```DECLARE_THREAD()``` 声明函数。 + + +### 并发控制 + +在 InnoDB 中,与并发控制相关的有 innodb_thread_concurrency、innodb_thread_sleep_delay、innodb_concurrency_tickets 三个参数,下面解释一下他们是如何工作的。 + +MySQL 的各个模块包括了解析、优化和存储引擎,而存储采用插件式存储引擎,对应 class handler 基类,常见的调用接口包括了 read_row()、index_read()等。

    + + + +在调用上述的这些 API 时,InnoDB 需要先检查内部的线程计数,如果内部线程计数超过了 innodb_thread_concurrency 就等待 innodb_thread_sleep_delay 变量设置的微妙后再尝试进入;如果第二次仍然不成功,则进入线程队列 sleep (FIFO)。之所以尝试两次,主要是为了减少线程等待计数和降低上下文切换。

    + +一但线程进入,就会得到 innodb_concurrency_tickets 票数,以便以后的 innodb_concurrency_tickets 次线程都不会再次检测,可以免 check 进入。

    + +当读写记录结束后退出 InnoDB 层时会将当前并发线程数减 1,并检查其是否低于 innodb_thread_concurrency,如果是的话,则从 FIFO 中唤醒一个等待的线程,保证并发线程不会超过 innodb_thread_concurrency 参数。

    + + + +从 5.6 开始,如果使用了 GCC Build-in 的原子操作,在进入 Innodb 层的线程并发控制走与之前不同的逻辑,5.5也可以调用通过原子操作进行并发控制的逻辑,但需要打开只读选项innodb_thread_concurrency_timer_based来控制. +
    +ha_innobase::index_read()
    +  |-innobase_srv_conc_enter_innodb()
    +  |   |-srv_conc_enter_innodb()
    +  |       |-srv_conc_enter_innodb_with_atomics()      如果使用了GCC Build-in的原子操作
    +  |
    +  |-row_search_for_mysql()
    +  |-innobase_srv_conc_exit_innodb()
    +
    +在 5.6 引入了 adaptive sleep 方法,可以根据系统的负载做一些自适应调整,且新增了 innodb_adaptive_max_sleep_delay 参数。

    + +当 innodb_adaptive_max_sleep_delay>0 时,innodb_thread_sleep_delay 则会动态调整,以前者为上限。 + +

    + + + + + + + +https://my.oschina.net/realfighter/blog/363853 +http://blog.itpub.net/15480802/viewspace-1067518/ + + + +### 主线程 + +{% highlight text %} +srv_master_thread() + 作用: +{% endhighlight %} + +主线程的当前状态可以通过 ```SHOW ENGINE INNODB STATUS\G``` 命令查看。 + +{% highlight text %} +mysql> SHOW ENGINE INNODB STATUS\G +... ... +----------------- +BACKGROUND THREAD +----------------- +srv_master_thread loops: 1 srv_active, 0 srv_shutdown, 78844 srv_idle +srv_master_thread log flush and writes: 78845 +... ... +-------------- +ROW OPERATIONS +-------------- +... ... +Main thread process no. 6432, id 140497645459200, state: sleeping +... ... +{% endhighlight %} + + + +如下是该线程的主要处理流程。 + +{% highlight text %} +srv_master_thread() + |-srv_get_activity_count() ← 初始化时获取活跃事件计数 + | + | ###BEGIN while + |-srv_master_sleep() ← 休眠1秒 + |-srv_check_activity() ← 查看是否有活跃的事件,也就是比较当前值与历史值 + | + |-srv_get_activity_count() ← 有活跃事件,则更新历史值old_activity_count + |-srv_master_do_active_tasks() ← 处理活跃的事件 + | |-row_drop_tables_for_mysql_in_background() ← 在没有查询时延迟删除表 + | |-log_free_check() ← 确保仍有足够的redo log空间 + | |-ibuf_merge_in_background() ← 做insert buffer的合并操作 + | |-srv_sync_log_buffer_in_background() ← 刷redo日志 + | |-srv_master_evict_from_table_cache() ← 检查dict cache + | |-log_checkpoint() + | + |-srv_master_do_idle_tasks() ← 没有活跃事件 + | |-row_drop_tables_for_mysql_in_background() ← 在没有查询时延迟删除表 + | |-log_free_check() ← 确保仍有足够的redo log空间 + | |-ibuf_merge_in_background() ← 做insert buffer的合并操作 + | |-srv_master_evict_from_table_cache() ← 检查dict cache + | |-srv_sync_log_buffer_in_background() ← 刷redo日志 + | |-log_buffer_sync_in_background() ← 根据innodb_flush_log_at_timeout参数判断刷新时间 + | |-log_write_up_to() + | |-log_checkpoint() + | ###END while +{% endhighlight %} + +http://mysqllover.com/?p=636 +http://www.penglixun.com/tech/database/innodb_master_thread.html + +在 srv_check_activity() 函数中,实际会比较 ```srv_sys->activity_count``` 与历史记录的值,srv_inc_activity_count + +从如上代码逻辑基本可以看出,实际上 idle 和 active 的时候,所做的事情几乎是一样的,不同的是,在 active 状态下,每 47 秒才检查 dict cache,每 7 秒才做一次 check point ;因此,在 idle 状态下,master 线程可能会更加繁忙。 + +{% highlight cpp %} +extern "C" os_thread_ret_t +DECLARE_THREAD(srv_master_thread)( + void* arg MY_ATTRIBUTE((unused))) +{ + ... ... + while (srv_shutdown_state == SRV_SHUTDOWN_NONE) { + + srv_master_sleep(); + + MONITOR_INC(MONITOR_MASTER_THREAD_SLEEP); + + if (srv_check_activity(old_activity_count)) { + old_activity_count = srv_get_activity_count(); + srv_master_do_active_tasks(); + } else { + srv_master_do_idle_tasks(); + } + } + + while (srv_shutdown_state != SRV_SHUTDOWN_EXIT_THREADS + && srv_master_do_shutdown_tasks(&last_print_time)) { + + /* Shouldn't loop here in case of very fast shutdown */ + ut_ad(srv_fast_shutdown < 2); + } + ... ... + my_thread_end(); + os_thread_exit(); + DBUG_RETURN(0); +} +{% endhighlight %} + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-02-06-mysql-innodb-undrop-tools_init.md b/_drafts/2016-02-06-mysql-innodb-undrop-tools_init.md new file mode 100644 index 0000000..f5e0bce --- /dev/null +++ b/_drafts/2016-02-06-mysql-innodb-undrop-tools_init.md @@ -0,0 +1,78 @@ +--- +title: InnoDB 崩溃恢复 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,innodb,crash recovery,崩溃恢复 +description: +--- + + + + +## undrop tools + + +### stream_parser + +stream_parser 用于在字节流中查找 InnoDB 页,可以解析多种文件,包括了 ibdata1、*.ibd、raw partition;其中 raw partitioin 可以通过 ```df -h /var/lib/mysql``` 查看。 + +可以通过如下方式查看如何使用。 + +{% highlight text %} +----- 查看磁盘 +$ df -h /var/lib/mysql +Filesystem Size Used Avail Use% Mounted on +/dev/sda7 45G 10G 32G 24% /data + +----- 可以直接解析raw partition +$ stream_parser -f /dev/sda7 -t 45g +常用选项: + -h 打印帮助信息; + -f 必选项,用于指定文件,可以为 ibdata1、table.ibd、/dev/sda1; + -d 指定输出的目录,不指定默认在当前目录下; + -V,-g 打印调试信息; + -s 指定缓存大小,例如1G、100M(默认值)、10K; + -t 指定扫描大小; + -T + +----- 编译调试模式 +$ make debug +{% endhighlight %} + + + + + +{% highlight text %} +main() + |-open_ibfile() 打开指定的文件,并清理cache + |-process_ibfile() 入参为起始偏移+长度 + |-lseek64() 多进程处理时,会将文件分块处理 + |-read() 将文件读取到缓存中 + |-valid_blob_page() 校验 + |-valid_innodb_page() + |-process_ibpage() 处理页 +{% endhighlight %} + +### c_parser + +c_parser 用于解析 InnoDB 页,并将其中的数据解析出来,因为 InnoDB 不包含表的结构信息,所以需要告诉 c_parser 解析的表结构信息。 + +https://twindb.com/undrop-tool-for-innodb/ + +https://twindb.com/tag/stream_parser/ + +https://github.com/chhabhaiya/undrop-for-innodb + + + + +https://launchpad.net/percona-data-recovery-tool-for-innodb + +MySQL数据库InnoDB数据恢复工具使用总结 +http://www.cnblogs.com/panfeng412/archive/2012/03/04/data-recorvery-of-mysql-innodb.html + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-02-17-python-profile_init.md b/_drafts/2016-02-17-python-profile_init.md new file mode 100644 index 0000000..5a15ef1 --- /dev/null +++ b/_drafts/2016-02-17-python-profile_init.md @@ -0,0 +1,16 @@ +--- +Date: June 17, 2015 +title: Python Profilers 性能分析器 +layout: post +comments: true +category: [program, python] +language: chinese +--- + + + + +![python modules]({{ site.url }}/images/python/python-modules-logo.jpg "python modules"){: .pull-center} + +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2016-02-30-linux-libevent_init.md b/_drafts/2016-02-30-linux-libevent_init.md new file mode 100644 index 0000000..4e678a9 --- /dev/null +++ b/_drafts/2016-02-30-linux-libevent_init.md @@ -0,0 +1,259 @@ +--- +title: libevent +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql +description: 保存一下经常使用的经典 MySQL 资源。 +--- + +libevent 是一个轻量级的开源的高性能的事件库,适用于 windows、linux、bsd 等多种平台,根据不同的平台,会选择使用 select、epoll、kqueue 等系统调用管理事件机制。 + +下面简单介绍下。 + + + +## 简介 + +很多程序在使用 [libevent](http://libevent.org/) 库,包括了 memcached、tmux、tor 等,主要有如下的特点: + +* 事件驱动,高性能,轻量级; +* 开源,代码相当精炼、易读; +* 跨平台,支持 Windows、Linux、BSD 和 Mac OS; +* 支持多种 IO 多路复用技术,如 epoll、poll、select、kqueue 等,不同平台会选择不同函数; +* 支持 IO、定时器和信号等事件; +* 采用 Reactor 模式。 + +接下来,看看如何安装。 + +### 安装 + +在 CentOS 中,可以直接通过 yum 安装。 + +{% highlight text %} +----- 直接通过yum安装 +# yum install libevent + +----- 查看安装的库 +$ rpm -ql libevent +/usr/lib64/libevent-2.0.so.5 +/usr/lib64/libevent-2.0.so.5.1.9 +/usr/lib64/libevent_core-2.0.so.5 +/usr/lib64/libevent_core-2.0.so.5.1.9 +/usr/lib64/libevent_extra-2.0.so.5 +/usr/lib64/libevent_extra-2.0.so.5.1.9 +/usr/lib64/libevent_openssl-2.0.so.5 +/usr/lib64/libevent_openssl-2.0.so.5.1.9 +/usr/lib64/libevent_pthreads-2.0.so.5 +/usr/lib64/libevent_pthreads-2.0.so.5.1.9 +{% endhighlight %} + +可以看到,libevent 会安装如下的库: + +* libevent_core
    所有核心的事件和缓冲功能,包含了 event_base、evbuffer、bufferevent 以及工具函数; +* libevent_extra
    包括了程序可能需要的协议特定功能,包括 HTTP、DNS 和 RPC; +* libevent
    因为历史原因而存在,包含 libevent_core 和 libevent_extra 的内容,以后可能会去掉; +* libevent_pthreads
    添加基于 pthread 可移植线程库的线程和锁定实现,独立于 core,这样使用 libevent 时就不需要链接到 pthread,除非是以多线程方式使用 libevent。 + + + + +### 示例程序 + +如下是一个简单的程序,每隔 1 秒输出 ```"Hello World!"``` 。 + +{% highlight c %} +#include +#include +#include +#include + +void on_time(int sock, short event, void *arg) +{ + struct timeval tv; + printf("Hello World!\n"); + + tv.tv_sec = 1; + tv.tv_usec = 0; + event_add((struct event*)arg, &tv); // 重新添加定时事件,默认会自动删除 +} + +int main(void) +{ + struct event ev_time; + struct timeval tv; + + event_init(); // 初始化 + evtimer_set(&ev_time, on_time, &ev_time); // 设置定时事件 + + tv.tv_sec = 1; + tv.tv_usec = 0; + event_add(&ev_time, &tv); // 添加定时事件 + + event_dispatch(); // 事件循环 + + return 0; +} +{% endhighlight %} + +然后通过如下方式编译。 + +{% highlight text %} +$ gcc -levent example.c -o example +{% endhighlight %} + +然后执行即可。 + +### 使用流程 + +如上, + +基本应用场景也是使用 +libevnet +的基本流程,下面来考虑一个最简单的场景,使用 +livevent +设置定时器,应用程序只需要执行下面几个简单的步骤即可。 +1 +)首先初始化 +libevent +库,并保存返回的指针 +struct event_base * base = event_init(); +实际上这一步相当于初始化一个 +Reactor +实例;在初始化 +libevent +后,就可以注册事件了。 +2 +)初始化事件 +event +,设置回调函数和关注的事件 +evtimer_set(&ev, timer_cb, NULL); +事实上这等价于调用 +event_set(&ev, -1, 0, timer_cb, NULL); +event_set +的函数原型是: +void event_set(struct +event *ev, +int fd, +short event, +void (*cb)(int, +short, void *), void *arg) +ev +:执行要初始化的 +event +对象; +fd +:该 +event +绑定的“句柄”,对于信号事件,它就是关注的信号; +event +:在该 +fd +上关注的事件类型,它可以是 +EV_READ, EV_WRITE, EV_SIGNAL +; +cb +:这是一个函数指针,当 +fd +上的事件 +event +发生时,调用该函数执行处理,它有三个参数, +调用时由 +event_base +负责传入,按顺序,实际上就是 +event_set +时的 +fd, event +和 +arg +; +arg +:传递给 +cb +函数指针的参数; +由于定时事件不需要 +fd +,并且定时事件是根据添加时( +event_add +)的超时值设定的,因此 +这里 +event +也不需要设置。 +这一步相当于初始化一个 +event handler +,在 +libevent +中事件类型保存在 +event +结构体中。 +注意: +libevent +并不会管理 +event +事件集合,这需要应用程序自行管理; +3 +)设置 +event +从属的 +event_base +event_base_set(base, &ev); +这一步相当于指明 +event +要注册到哪个 +event_base +实例上; +4 +)是正式的添加事件的时候了 +event_add(&ev, timeout); +基本信息都已设置完成,只要简单的调用 +event_add() +函数即可完成,其中 +timeout +是定时值; +10 +这一步相当于调用 +Reactor::register_handler() +函数注册事件。 +5 +)程序进入无限循环,等待就绪事件并执行事件处理 +event_base_dispatch(base) + + +## 参考 + +可以参考官方网站 [libevent – an event notification library](http://libevent.org/),以及 [github libevent](https://github.com/libevent/libevent),以及 [libevent 源码深度剖析](/reference/linux/libevent.pdf) 。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-03-12-memcached-introduce_init.md b/_drafts/2016-03-12-memcached-introduce_init.md new file mode 100644 index 0000000..e167022 --- /dev/null +++ b/_drafts/2016-03-12-memcached-introduce_init.md @@ -0,0 +1,681 @@ +--- +Date: October 19, 2013 +title: 分布式缓存 memcached +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +通常 Web 应用的数据都会持久化到数据库中,随着应用数据量以及访问量的增加,就会导致数据库负担加重,从而导致网站的延迟。 + +Memcached 是一个高性能的分布式内存缓存服务器,用于缓存数据库的查询结果,从而减少数据库的访问次数,提高动态 Web 应用的速度、提高扩展性。 + + + + + +# 简介 + +分布式缓存在设计时需要考虑几点常见的原则:A) 缓存本身的水平线性扩展;B) 缓存大并发下本身的性能问题,包括内存的管理问题,如内存的分配、管理、回收机制;C) 避免缓存的单点故障问题,以及分布式系统常见的多副本和副本一致性问题。 + +![memcached logo](/images/linux/memcached-logo.png "memcached logo"){: .pull-center} + +memcached 采用基于文本的简单通讯协议,可以直接通过 telnet 进行操作;其事件处理是基于 libevent;虽然,我们说 memcached 是分布式缓存服务器,但实际上各个服务器之间并不通讯,其分布式主要是通过客户端实现。 + + + +## 安装、使用 + +在 CentOS 上可以通过 yum 简单安装,或者直接通过源码安装。 + +{% highlight text %} +# yum install memcached # 直接安装 +# systemctl start memcached # 启动 +# ./configure && make && make test && make install # 通过源码编译 + +$ memcached -p 11211 -m 64m -d # 后台启动 +$ telnet 127.1 11211 # 通过telnet链接 +add foobar 0 60 5 # 添加,由重复则失败 +get foobar # 获取foobar的值 +set foobar 0 60 5 # 存在则更新,否则添加 +delete foobar # 删除 +stats # 查看状态,也可以使用memcached-tool +{% endhighlight %} + +对于 add 和 set 命令,其中 0 为标志,60 表示数据存放 60s,5 表示放入多大数据。 + +上述是简单的启动和使用方式,常见的启动选项可以参考如下,详细内容查看 man 1 memcached 。 + +{% highlight text %} +memcached [options] + -p [num] + 指定 TCP 的监听端口,默认使用 11211。 + -c [num] + 可以同时处理的最大链接数,默认是 1024 。 + -b [num] + 设置 backlog 队列的大小,默认是 1024 。 + -m [num] + 用户可以使用多少MB的内存,默认是 64M 。 + -d + 后台运行。 + -P [filename] + 后台启动时指定 pidfile 的路径。 +{% endhighlight %} + + +# 链接、线程处理 + +线程通过 LIBEVENT_THREAD *threads 表示, + +{% highlight text %} +main() + |-... ... # 设置进程打开文件个数限制等 + |-daemonize() # 如果需要后台运行 + | |-fork() + |-mlockall() # 锁定内存,保证内存不会换出 + | + |-event_init() # 1. 初始化主线程的event_base,也就是libevent实例 + |-stats_init() # 统计状态的初始化 + |-assoc_init() # hash表的初始化 + |-conn_init() # 连接初始化 + |-slabs_init() # slabs结构的初始化 + |-sigignore() # 忽略SIGPIPE信号 + |-memcached_thread_init() # 2. 创建工作线程,默认是4个,可以通过pstree -p PID查看 + | | + | |-pipe() # 3. 创建pipe用于主线程和工作线程通讯 + | | + | |-setup_thread() # 创建每个线程自己的libevent的event_base + | | |-event_init() # 每个独立的线程都包含独立的event_base + | | |-event_set() # 创建pipe的读事件监听,回调thread_libevent_process方法 + | | |-event_base_set() + | | |-event_add() # 添加事件操作 + | | |-cq_init() # 4. 初始化每个工作线程的队列 + | | |-pthread_mutex_init() # 初始化线程池 + | | |-cache_create() + | | + | |-create_worker() # 循环创建线程,真正的创建工作线程,实际是worker_libevent() + | |-pthread_attr_init() # 属性初始化 + | |-pthread_create() # 5. 创建线程worker_libevent(),直接启动 + | + |-start_assoc_maintenance_thread() # 启动 + |-start_item_crawler_thread() + |-start_lru_maintainer_thread() + |-start_slab_maintenance_thread() + | + |-clock_handler() # 初始化时间设置 + | + |-server_sockets() # 6. 创建socket,绑定地址,设置非阻塞模式,包括TCP/UDP + | |-server_socket() # 实际的绑定函数 + | |-conn_new() # 7. 将监听socket添加到main_base的libevent的事件队列中 + | + |-save_pid() # 保存PID到一个文件中 + |-event_base_loop() # 8. 启动主线程的libevetn循环 + +worker_libevent() # 新建的子线程,会初始化子线程的libevent + |-register_thread_initialized() + |-event_base_loop() # 开启事件循环,每个线程有自己的事件处理机制,启动libevent + +event_handler() # Master/Worker线程libevent回调函数,此时有网络事件到达 + |-drive_machine() # 进入业务处理状态机 + +thread_libevent_process() # pipe接收回调函数 + |-read() # 读取pipe中的信息 +{% endhighlight %} + +主线程中如果有新的连接,会向其中一个线程的 pipe 中写入 1,子线程读取 pipe 中的数据,如果为 1,则说明从 pipe 中获取的数据是正确的。 + +在初始化完成之后,会忽略 SIGPIPE 信号,其中 PIPE 信号是当网络连接一端已经断开,这时发送数据,会发送 RST 包,当再次发送数据,会触发 PIPE 信号,而 PIPE 信号的默认动作是退出进程,因此忽略。 + + +## 数据结构 + +CQ_ITEM 是主线程 accept() 后返回的已建立连接的 fd 的封装,而 CQ 是一个管理 CQ_ITEM 的单向链表。 + +{% highlight c %} +/* An item in the connection queue. */ +typedef struct conn_queue_item CQ_ITEM; +struct conn_queue_item { + int sfd; + enum conn_states init_state; + int event_flags; + int read_buffer_size; + enum network_transport transport; + CQ_ITEM *next; +}; + +/* A connection queue. */ +typedef struct conn_queue CQ; +struct conn_queue { + CQ_ITEM *head; + CQ_ITEM *tail; + pthread_mutex_t lock; +}; + +typedef struct { + pthread_t thread_id; /* unique ID of this thread */ + struct event_base *base; /* libevent handle this thread uses */ + struct event notify_event; /* listen event for notify pipe */ + int notify_receive_fd; /* receiving end of notify pipe */ + int notify_send_fd; /* sending end of notify pipe */ + struct thread_stats stats; /* Stats generated by this thread */ + struct conn_queue *new_conn_queue; /* queue of new connections to handle */ + cache_t *suffix_cache; /* suffix cache */ +} LIBEVENT_THREAD; + +typedef struct conn conn; +struct conn { + int sfd; + sasl_conn_t *sasl_conn; + bool authenticated; + enum conn_states state; + enum bin_substates substate; + rel_time_t last_cmd_time; + struct event event; + short ev_flags; + ... ... +}; +{% endhighlight %} + +LIBEVENT_THREAD 是 memcached 里的线程结构封装,如上所示,每个线程都包含一个 CQ 队列,一条通知管道 pipe,一个 libevent 的实例 event_base 以及线程的状态。 + +另外一个重要的结构是对每个网络连接的封装 conn,这个结构体的成员变量比较多,暂时只列举了部分。 + + +## 线程交互 + +memcached 采用典型的 Master-Worker 线程模型,其模型很简单,Master 监听网络链接,接受链接请求之后通过线程间通讯来唤醒 Worker 线程,后续的读写操作都是通过 Worker 完成。 + +主线程和各个线程的处理都是通过 libevent 处理,通过实例化多个 libevent 实例实现,分别对应了一个主线程和 N 个 workers 线程。Master 线程和 Workers 线程全部都是通过 libevent 管理网络事件,也就是说实际上每个线程都是一个单独的 libevent 实例。 + +主线程的 libevent 实例在主线程初始化时设置,工作线程则在 setup_thread() 中建立 libevent 实例。 + +Master 线程负责监听客户端的建立连接请求,并 accept 连接,Workers 线程负责处理已经建立好的连接的读写等事件。 + +![memcached threads](/images/linux/memcached-threads.png "memcached threads"){: .pull-center} + +### Master Thread + +首先看看主线程是如何通知 workers 线程处理新连接的,在初始化时会添加监听事件。 + +{% highlight c %} +static struct event_base *main_base; // 主libevent + +static int server_socket(...) { + ... ... + for (next= ai; next; next= next->ai_next) { + ... ... + if (!(listen_conn_add = conn_new(sfd, conn_listening, + EV_READ | EV_PERSIST, 1, + transport, main_base))) { + fprintf(stderr, "failed to create listening connection\n"); + exit(EXIT_FAILURE); + } + ... ... + } +} + +{% endhighlight %} + +主线程的 libevent 注册的是监听 socket 描述字的可读事件,就是说当有建立连接请求时,主线程会处理,新建监听端口是通过 conn_new() 初始化,其回调的函数是 event_handler() 。 + +对于主线程来说,会进入到 conn_listening 分支,也就是调用 dispatch_conn_new() 函数;在该函数中,会创建一个新的 CQ_ITEM,然后通过 round robin 策略选择了一个 thread,并通过 cq_push 将这个 CQ_ITEM 放入了该线程的 CQ 队列里。 + +最后通过 write() 向该线程管道写了 1 字节数据,则该线程的 libevent 立即回调 thread_libevent_process() 函数进行处理。 + +### Worker Thread + +当 memcached 刚启动时,也就是当刚初始化完成之后,每个 workers 线程只有在自己线程管道的读端有数据时触发调用 thread_libevent_process() 方法,而主线程在有链接时会写入数据,该函数处理的就是主线程新建链接的请求。 + +{% highlight c %} +static void thread_libevent_process(int fd, short which, void *arg) { + ... ... + if (read(fd, buf, 1) != 1) + if (settings.verbose > 0) + fprintf(stderr, "Can't read from libevent pipe\n"); + + switch (buf[0]) { + case 'c': + item = cq_pop(me->new_conn_queue); + + if (NULL != item) { + conn *c = conn_new(item->sfd, item->init_state, item->event_flags, + item->read_buffer_size, item->transport, me->base); + ... ... + } + break; + + /* we were told to pause and report in */ + case 'p': + register_thread_initialized(); + break; + } +} +{% endhighlight %} + + +入参的 fd 是这个线程的管道读端的描述符,在上述的函数中,首先将管道的 1 个字节通知信号读出。需要注意的是,在水平触发模式下如果不处理该事件,则会被循环通知,直到该事件被处理。 + +cq_pop() 会从该线程的 CQ 队列中取队列头的一个 CQ_ITEM,这个 CQ_ITEM 是被主线程丢到这个队列里的,item->sfd 是已建立的连接的描述符,然后会调用 conn_new() 函数。 + +{% highlight c %} +conn *conn_new(...) { + ... ... + event_set(&c->event, sfd, event_flags, event_handler, (void *)c); + event_base_set(base, &c->event); + c->ev_flags = event_flags; + + if (event_add(&c->event, 0) == -1) { + perror("event_add"); + return NULL; + } +} +{% endhighlight %} + +在 conn_new() 中,为 sfd 描述符注册 libevent 的读事件,接着会调用 event_add() 函数,也就是对该描述符的事件处理交给当前这个 workers 线程处理。 + +可以看到新的连接被注册了一个事件,实际上是 EV_READ\|EV_PERSIST 事件,当该连接有可读数据时会调用函数 event_handler(),实际上 event_handler() 里主要是调用 memcached 的核心方法 drive_machine() 。 + +而 Worker 线程最终会走到 conn_read 分支,可以参考如下从网上找的工作流程。 + +![memcached master workers](/images/linux/memcached-master-workers.jpg "memcached master workers"){: .pull-center} + +也就是说,实际上,Master/Worker 线程的 libevent 实例都会调用 event_handler() 函数,而在该函数中的核心处理为其状态机的处理。 + + + +## 状态机 + +Worker 线程在处理 libevent 的事件时,会进入状态机处理不同的逻辑,详细的调用逻辑如下: + +{% highlight text %} +event_handler() # libevent回调函数,此时有网络事件到达 + |-drive_machine() # 进入业务处理状态机 +{% endhighlight %} + +drive_machine() 就是通过当前连接的 state 来判断该进行何种处理,Master 和 Workers 的 libevent 事件处理都会调用该函数,其链接的状态是通过一个 enum 声明。 + +{% highlight c %} +enum conn_states { + conn_listening, // 主线程listen的主要状态,其工作就是把接到连接分发到worker子线程 + conn_new_cmd, // 将链接设置为初始态,准备接收下个命令,会清空读写buffer + conn_waiting, // 等待读取socket,实际上就是挂起该链接,等待新的链接到来 + conn_read, // 从客户端读取信息,也就是命令信息 + conn_parse_cmd, // 尝试从读取的buffer中解析命令 + conn_write, // 主要是out_string()函数,一般都是提示信息和返回的状态信息 + conn_nread, // 读取固定大小的数据,会更新到item、hash、lru中,并调转到conn_write + conn_swallow, // 忽略不需要的数据 + conn_closing, // 服务端主动调用close()关闭链接,并把conn结构体放到空闲队列 + conn_mwrite, // 将这个链接中的msglist返回给客户端,可能会返回多个 + conn_closed, // 链接已经被关闭 + conn_max_state // 由于assert判断 +}; +{% endhighlight %} + +首先着重说一下 conn_swallow 状态,对于像 update 操作,如果分配 item 失败,显然后面读取的数据是无效的,不过客户端是不知道的,客户端会继续发送特定的数量的数据,就需要把读到的这些数据忽略掉。 + +![memcached states](/images/linux/memcached-states.jpg "memcached sates"){: .pull-center width="600"} + +接下来详细查看 drive_machine() 函数的处理流程,首先查看下主线程 conn_listening 状态的处理。 + +{% highlight c %} +static void drive_machine(conn *c) { + ... ... + while (!stop) { + switch(c->state) { + case conn_listening: + addrlen = sizeof(addr); + sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen); + if (sfd == -1) { + ... ... // 多种的错误处理 + break; + } + if (!use_accept4) { // 设置为非阻塞 + if (fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL) | O_NONBLOCK) < 0) { + perror("setting O_NONBLOCK"); + close(sfd); + break; + } + } + + if (settings.maxconns_fast && + stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) { + ... .. // 超过了设置的最大链接数 + } else { // OK,分发给Worker线程,初始化状态为conn_new_cmd + dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST, + DATA_BUFFER_SIZE, tcp_transport); + } + + stop = true; + break; + } + } +} +{% endhighlight %} + + +### conn_new_cmd + +子线程最初进入的状态就是 conn_new_cmd 状态,这个状态主要是做一些清理。 + +{% highlight c %} +static void drive_machine(conn *c) { + ... ... + while (!stop) { + switch(c->state) { + ... ... + case conn_new_cmd: + --nreqs; // 记录每个工作线程处理,记录每个libevent实例处理的事件,通过初始启动参数配置 + if (nreqs >= 0) { // 还可以处理请求 + reset_cmd_handler(c); // 清空缓冲区 + } else { // 拒绝请求 + pthread_mutex_lock(&c->thread->stats.mutex); + c->thread->stats.conn_yields++; // 更新统计数据 + pthread_mutex_unlock(&c->thread->stats.mutex); + if (c->rbytes > 0) { + // 已经将数据读取到了buffer中,因此需要发送一个event + if (!update_event(c, EV_WRITE | EV_PERSIST)) { + if (settings.verbose > 0) + fprintf(stderr, "Couldn't update event\n"); + conn_set_state(c, conn_closing); + break; + } + } + stop = true; + } + break; + } + ... ... + } +} + +static void reset_cmd_handler(conn *c) { + c->cmd = -1; + c->substate = bin_no_state; + if(c->item != NULL) { + item_remove(c->item); // 如果有item,则直接删除 + c->item = NULL; + } + conn_shrink(c); // 整理缓冲区,暂不分析 + if (c->rbytes > 0) { // 如果缓冲区还有数据,则直接到conn_parse_cmd中继续处理 + conn_set_state(c, conn_parse_cmd); + } else { // 否则进入等待状态,状态机没有数据要处理,就进入这个状态 + conn_set_state(c, conn_waiting); + } +} +{% endhighlight %} + +根据是否有数据,分别会进入不同的状态,如果没有数据则会进入 conn_waiting 等待接收数据。 + + +### conn_waiting、conn_read + +接下来主要是处理数据报文的接收,包括了 conn_waiting、conn_read 两个状态。 + +{% highlight c %} +static void drive_machine(conn *c) { + ... ... + while (!stop) { + switch(c->state) { + ... ... + case conn_waiting: + // 更新libevent状态,也就是删除libevent事件后,重新注册libevent事件 + if (!update_event(c, EV_READ | EV_PERSIST)) { + if (settings.verbose > 0) + fprintf(stderr, "Couldn't update event\n"); + conn_set_state(c, conn_closing); + break; + } + + conn_set_state(c, conn_read); // 进入读数据状态 + stop = true; + break; + + case conn_read: + // 判断采用UDP协议还是TCP协议 + res = IS_UDP(c->transport) ? try_read_udp(c) : try_read_network(c); + + switch (res) { + case READ_NO_DATA_RECEIVED: // 未读取到数据 + conn_set_state(c, conn_waiting); // 则继续等待 + break; + case READ_DATA_RECEIVED: // 已经读取到数据 + conn_set_state(c, conn_parse_cmd); // 开始解析命令 + break; + case READ_ERROR: // 读取发生错误 + conn_set_state(c, conn_closing); // 直接关闭链接 + break; + case READ_MEMORY_ERROR: // 申请内存发生错误,则继续尝试 + break; + } + break; + ... ... + } + } +} +{% endhighlight %} + +如果采用 TCP 协议则会调用 try_read_network() 从网络读取数据;对于 UDP 则比较简单,因为是数据报,读取到一个,就是一个完整的数据报,所以其处理过程简单。 + +### conn_parse_cmd + +从网络读取了数据之后,则会进入该状态,按协议来解析读取到的网络数据。 + +{% highlight c %} +static void drive_machine(conn *c) { + ... ... + while (!stop) { + switch(c->state) { + ... ... + case conn_parse_cmd : + if (try_read_command(c) == 0) { // 尝试解析命令 + // 如果读取到的数据不够,则继续等待 + conn_set_state(c, conn_waiting); + } + + break; + ... ... + } + } +} + +static int try_read_command(conn *c) { + ... ... + if (c->protocol == binary_prot) { + ... ... + dispatch_bin_command(c); + } else { + ... ... + process_command(c, c->rcurr); + } +} +{% endhighlight %} + +命令解析包括了文本协议和二进制协议,其命令的处理分别调用不同的命令处理函数。 + + + + + +# 内存管理 + +对于 memcached 内存高效管理是其最重要的任务之一,为了减小内存碎片的产生,采用 slab 管理其内存。简单来说,就是分配一块大内存,然后按照不同的块切分这些内存,存储业务数据时,按需选择合适的内存空间存储数据。 + +默认分配 64M 内存,之后所有的数据都是在这 64M 空间进行存储,在启动之后,不会释放这些内存,直到进程退出。 + +![memcached memory structure](/images/linux/memcached-02-01.png "memcached memory structure"){: .pull-center} + +在 memcached 中,与内存相关的配置参数包括了设置内存大小、内存的增长因子,会在 slabs_init() 初始化的时候作为入参传入函数。 + +{% highlight c %} +static slabclass_t slabclass[MAX_NUMBER_OF_SLAB_CLASSES]; // 定义slab class最大值,64个 +static size_t mem_limit = 0; // 总的内存大小 +static size_t mem_malloced = 0; // 初始化内存的大小 +static void *mem_base = NULL; // 指向总的内存的首地址 +static void *mem_current = NULL; // 当前分配到的内存地址 +static size_t mem_avail = 0; // 当前可用的内存大小 + +void slabs_init(const size_t limit, const double factor, const bool prealloc) { + int i = POWER_SMALLEST - 1; + //size表示申请空间的大小,其值由配置的chunk_size和单个item的大小来指定 + unsigned int size = sizeof(item) + settings.chunk_size; + + mem_limit = limit; // 设置入参设置的内存上限 + + if (prealloc) { // 是否要预先分配设置的内存 + mem_base = malloc(mem_limit); // 通过malloc()申请内存,并将内存基址指向mem_base + if (mem_base != NULL) { + mem_current = mem_base; // mem_current指向当前地址 + mem_avail = mem_limit; // 可用内存大小为mem_limit + } else { // 预分配失败 + fprintf(stderr, "Warning: Failed to allocate requested memory in" + " one large chunk.\nWill allocate in smaller chunks\n"); + } + } + + memset(slabclass, 0, sizeof(slabclass)); // 置空slab class数组 + + // 开始分配,i<200 && 单个chunk的size<单个item最大大小/内存增长因子 + while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) { + if (size % CHUNK_ALIGN_BYTES) // 将size按照8byte对齐 + size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES); + + slabclass[i].size = size; // slab对应chunk的大小 + slabclass[i].perslab = settings.item_size_max / slabclass[i].size; // slab对应的chunk的个数 + size *= factor; // size下一个值为按增长因子的倍数增长 + if (settings.verbose > 1) { // 如果有打开调试信息,则输出调试信息 + fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n", + i, slabclass[i].size, slabclass[i].perslab); + } + } + + power_largest = i; // size已经增长到1M,再增加一个slab + slabclass[power_largest].size = settings.item_size_max; // slab的size为item_size_max + slabclass[power_largest].perslab = 1; // chunk个数为1 + if (settings.verbose > 1) { + fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n", + i, slabclass[i].size, slabclass[i].perslab); + } + ... ... + if (prealloc) { + // 在每个slab class中都先预分配一个1M的内存 + slabs_preallocate(power_largest); + } +} +{% endhighlight %} + + +## Hash表 + +在 memcached 中,其采用链接法来处理 Hash 冲突,如果冲突太高,则会导致链表过长,从而会导致查找时的耗时变长。 + +为次当表中元素的个数超过 Hash 容量的 1.5 倍时就进行扩容,扩容过程由独立的线程来完成,扩容过程中会采用 2 个 Hash 表,将老表中的数据通过 Hash 算法映射到新表中,每次移动的桶的数目可以配置,默认是每次移动老表中的 1 个桶。 + +在启动时,会通过 start_assoc_maintenance_thread() 函数启动一个线程,正常如果不需要扩容,则实际会调用 pthread 的 cond_wait() 函数一直等待。 + +首先看下如何在 hash 表中添加元素。 + + +{% highlight c %} +// 在hash表中增加元素,不同于assoc_update,需要先确保没有相应的数据 +int assoc_insert(item *it, const uint32_t hv) { + unsigned int oldbucket; + // 如果正在扩容且目前进行扩容还没到需要插入元素的桶,则将元素添加到旧桶中 + if (expanding && (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket) + { + it->h_next = old_hashtable[oldbucket]; // 添加元素 + old_hashtable[oldbucket] = it; + } else { // 如果没扩容,或者扩容已经到了新的桶中,则添加元素到新表中 + it->h_next = primary_hashtable[hv & hashmask(hashpower)]; + primary_hashtable[hv & hashmask(hashpower)] = it; + } + + pthread_mutex_lock(&hash_items_counter_lock); + hash_items++; + // 还没开始扩容,但是表中元素个数已经超过Hash表容量的1.5倍,触发开始扩容 + if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) { + assoc_start_expand(); // 唤醒扩容线程 + } + pthread_mutex_unlock(&hash_items_counter_lock); + + MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items); + return 1; +} + +static void assoc_start_expand(void) { // 唤醒对应的扩容线程 + if (started_expanding) + return; + + started_expanding = true; + pthread_cond_signal(&maintenance_cond); +} +{% endhighlight %} + + + + + + + + + +## CAS + +CAS 也即 Compare And Set 或 Compare And Swap,实现的是一中无锁方法,或者说是乐观锁的一种技术,过程中会使用 CPU 提供的原子操作指令,可以提高系统的并发性能,在 Memcached 中用来保证数据的一致性,不是为了实现严格的锁。 + +当多个应用尝试修改同一个数据时,会出现相互覆盖的情况,此时使用 CAS 版本号验证,可以有效的保证数据的一致性。每次存储数据时,都会将生成的 CAS 值和 item 一起存储,后续的 get 会返回对应的 CAS 值,执行 set 等操作时,需要将 CAS 值传入。 + +其处理函数为 do_store_item() 。 + +{% highlight c %} +enum store_item_type do_store_item(item *it, int comm, conn *c, const uint32_t hv) { + ... ... + } else if (comm == NREAD_CAS) { + /* validate cas operation */ + if(old_it == NULL) { + // LRU expired + stored = NOT_FOUND; + pthread_mutex_lock(&c->thread->stats.mutex); + c->thread->stats.cas_misses++; + pthread_mutex_unlock(&c->thread->stats.mutex); + } else if (ITEM_get_cas(it) == ITEM_get_cas(old_it)) { // CAS值一致 + // cas validates + // it and old_it may belong to different classes. + // I'm updating the stats for the one that's getting pushed out + pthread_mutex_lock(&c->thread->stats.mutex); + c->thread->stats.slab_stats[ITEM_clsid(old_it)].cas_hits++; + pthread_mutex_unlock(&c->thread->stats.mutex); + + item_replace(old_it, it, hv); // 执行存储逻辑 + stored = STORED; + } else { // CAS值不一致,不进行实际的存储 + pthread_mutex_lock(&c->thread->stats.mutex); + c->thread->stats.slab_stats[ITEM_clsid(old_it)].cas_badval++; + pthread_mutex_unlock(&c->thread->stats.mutex); + + if(settings.verbose > 1) { // 打印错误日志 + fprintf(stderr, "CAS: failure: expected %llu, got %llu\n", + (unsigned long long)ITEM_get_cas(old_it), + (unsigned long long)ITEM_get_cas(it)); + } + stored = EXISTS; + } + } +} + +uint64_t get_cas_id(void) // 为新的item生成cas值 +{ + static uint64_t cas_id = 0; + return ++cas_id; +} +{% endhighlight %} + + +# 参考 + +详细内容以及最新的版本可以查看官方网站 [www.memcached.org](https://memcached.org/) 。 + + diff --git a/_drafts/2016-03-13-memcached-reprinted_init.md b/_drafts/2016-03-13-memcached-reprinted_init.md new file mode 100644 index 0000000..9fc3d7e --- /dev/null +++ b/_drafts/2016-03-13-memcached-reprinted_init.md @@ -0,0 +1,1053 @@ +--- +Date: October 19, 2013 +title: memcached 完全剖析【转载】 +layout: post +comments: true +language: chinese +category: [linux, network] +--- + +一系列关于 memcached 很不错的文章,直接整理复制过来,顺便理一下 memcached 的处理逻辑。 + +原文在 [www.charlee.li](http://charlee.li/memcached-001.html),实际上是日文的翻译,原日文文章参考 [www.gihyo.jp](http://gihyo.jp/dev/feature/01/memcached/0001) 。 + + + + +# 1. memcached的基础 + +我是mixi株式会社开发部系统运营组的长野。从今天开始,将分几次针对最近在Web应用的可扩展性领域 的热门话题memcached,与我公司开发部研究开发组的前坂一起, 说明其内部结构和使用。 + +## memcached是什么? + +memcached是以LiveJournal旗下Danga Interactive 公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、 Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。 + +许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。 但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、 网站显示延迟等重大影响。 + +这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。 一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、 提高可扩展性。 + +![memcached 01](/images/linux/memcached-01-01.png){: .pull-center} +
    图1 一般情况下memcached的用途
    + + +## memcached的特征 + +memcached作为高速运行的分布式缓存服务器,具有以下的特点。 + +* 协议简单 +* 基于libevent的事件处理 +* 内置内存存储方式 +* memcached不互相通信的分布式 + +### 协议简单 + +memcached的服务器客户端通信并不使用复杂的XML等格式, 而使用简单的基于文本行的协议。因此,通过telnet 也能在memcached上保存数据、取得数据。下面是例子。 + +{% highlight text %} +$ telnet localhost 11211 +Trying 127.0.0.1... +Connected to localhost.localdomain (127.0.0.1). +Escape character is '^]'. +set foo 0 0 3 (保存命令) +bar (数据) +STORED (结果) +get foo (取得命令) +VALUE foo 0 3 (数据) +bar (数据) +{% endhighlight %} + +协议文档位于memcached的源代码内的 doc/protocol.txt 。 + +### 基于libevent的事件处理 + +libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。 memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。 关于事件处理这里就不再详细介绍,可以参考Dan Kegel的The C10K Problem。 + +* libevent: [http://www.monkey.org/~provos/libevent/](http://www.monkey.org/~provos/libevent/) +* The C10K Problem: [http://www.kegel.com/c10k.html](http://www.kegel.com/c10k.html) + +### 内置内存存储方式 + +为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。 由于数据仅存在于内存中,因此重启memcached、重启操作系统会导致全部数据消失。 另外,内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删除不使用的缓存。 memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问题。 关于内存存储的详细信息,本连载的第二讲以后前坂会进行介绍,请届时参考。 + +### memcached不互相通信的分布式 + +memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。 各个memcached不会互相通信以共享信息。那么,怎样进行分布式呢? 这完全取决于客户端的实现。本连载也将介绍memcached的分布式。 + +![memcached 02](/images/linux/memcached-01-02.png){: .pull-center} +
    图2 memcached的分布式
    + + +接下来简单介绍一下memcached的使用方法。 + + +## 安装memcached + +memcached的安装比较简单,这里稍加说明。 + +memcached支持许多平台。 * Linux * FreeBSD * Solaris (memcached 1.2.5以上版本) * Mac OS X + +另外也能安装在Windows上。这里使用Fedora Core 8进行说明。 + +### memcached的安装 + +运行memcached需要本文开头介绍的libevent库。Fedora 8中有现成的rpm包, 通过yum命令安装即可。 + +{% highlight text %} +$ sudo yum install libevent libevent-devel +{% endhighlight %} + +memcached的源代码可以从memcached网站上下载。本文执笔时的最新版本为1.2.5。 Fedora 8虽然也包含了memcached的rpm,但版本比较老。因为源代码安装并不困难, 这里就不使用rpm了。 + +下载memcached:[http://www.danga.com/memcached/download.bml](http://www.danga.com/memcached/download.bml) + +memcached安装与一般应用程序相同,configure、make、make install就行了。 + +{% highlight text %} +$ wget http://www.danga.com/memcached/dist/memcached-1.2.5.tar.gz +$ tar zxf memcached-1.2.5.tar.gz +$ cd memcached-1.2.5 +$ ./configure +$ make +$ sudo make install +{% endhighlight %} + +默认情况下memcached安装到/usr/local/bin下。 + + +### memcached的启动 + +从终端输入以下命令,启动memcached。 + +{% highlight text %} +$ /usr/local/bin/memcached -p 11211 -m 64m -vv +slab class 1: chunk size 88 perslab 11915 +slab class 2: chunk size 112 perslab 9362 +slab class 3: chunk size 144 perslab 7281 +... ... +slab class 38: chunk size 391224 perslab 2 +slab class 39: chunk size 489032 perslab 2 +<23 server listening +<24 send buffer was 110592, now 268435456 +<24 server listening (udp) +<24 server listening (udp) +<24 server listening (udp) +<24 server listening (udp) +{% endhighlight %} + +这里显示了调试信息。这样就在前台启动了memcached,监听TCP端口11211 最大内存使用量为64M。调试信息的内容大部分是关于存储的信息, 下次连载时具体说明。 + +作为daemon后台启动时,只需 + +{% highlight text %} +$ /usr/local/bin/memcached -p 11211 -m 64m -d +{% endhighlight %} + +这里使用的memcached启动选项的内容如下。 + +{% highlight text %} +选项 说明 +-p 使用的TCP端口。默认为11211 +-m 最大内存大小。默认为64M +-vv 用very vrebose模式启动,调试信息和错误输出到控制台 +-d 作为daemon在后台启动 +{% endhighlight %} + +上面四个是常用的启动选项,其他还有很多,通过 + +{% highlight text %} +$ /usr/local/bin/memcached -h +{% endhighlight %} + +命令可以显示。许多选项可以改变memcached的各种行为, 推荐读一读。 + +### 用客户端连接 + +许多语言都实现了连接memcached的客户端,其中以Perl、PHP为主。 仅仅memcached网站上列出的语言就有 Perl、PHP、Python、Ruby、C#、C/C++、Lua 等等。 + +memcached客户端API:[http://www.danga.com/memcached/apis.bml](http://www.danga.com/memcached/apis.bml) + +这里介绍通过mixi正在使用的Perl库链接memcached的方法。 + +## 使用Cache::Memcached + +Perl的memcached客户端有 + +* Cache::Memcached +* Cache::Memcached::Fast +* Cache::Memcached::libmemcached + +等几个CPAN模块。这里介绍的Cache::Memcached是memcached的作者Brad Fitzpatric的作品, 应该算是memcached的客户端中应用最为广泛的模块了。 + + Cache::Memcached - search.cpan.org: [http://search.cpan.org/dist/Cache-Memcached/](http://search.cpan.org/dist/Cache-Memcached/) + +### 使用Cache::Memcached连接memcached + +下面的源代码为通过Cache::Memcached连接刚才启动的memcached的例子。 + +{% highlight perl %} +#!/usr/bin/perl + +use strict; +use warnings; +use Cache::Memcached; + +my $key = "foo"; +my $value = "bar"; +my $expires = 3600; # 1 hour +my $memcached = Cache::Memcached->new({ + servers => ["127.0.0.1:11211"], + compress_threshold => 10_000 +}); + +$memcached->add($key, $value, $expires); +my $ret = $memcached->get($key); +print "$ret\n"; +{% endhighlight %} + +在这里,为Cache::Memcached指定了memcached服务器的IP地址和一个选项,以生成实例。 + +Cache::Memcached常用的选项如下所示。 + +{% highlight text %} +选项 说明 +servers 用数组指定memcached服务器和端口 +compress_threshold 数据压缩时使用的值 +namespace 指定添加到键的前缀 +{% endhighlight %} + +另外,Cache::Memcached通过Storable模块可以将Perl的复杂数据序列化之后再保存, 因此散列、数组、对象等都可以直接保存到memcached中。 + +### 保存数据 + +向memcached保存数据的方法有 add、replace、set,它们的使用方法都相同: + +{% highlight perl %} +my $add = $memcached->add( '键', '值', '期限' ); +my $replace = $memcached->replace( '键', '值', '期限' ); +my $set = $memcached->set( '键', '值', '期限' ); +{% endhighlight %} + +向memcached保存数据时可以指定期限(秒)。不指定期限时,memcached按照LRU算法保存数据。 这三个方法的区别如下: + +{% highlight text %} +选项 说明 +add 仅当存储空间中不存在键相同的数据时才保存 +replace 仅当存储空间中存在键相同的数据时才保存 +set 与add和replace不同,无论何时都保存 +{% endhighlight %} + +### 获取数据 + +获取数据可以使用get和get_multi方法。 + +{% highlight perl %} +my $val = $memcached->get('键'); +my $val = $memcached->get_multi('键1', '键2', '键3', '键4', '键5'); +{% endhighlight %} + +一次取得多条数据时使用get_multi。get_multi可以非同步地同时取得多个键值, 其速度要比循环调用get快数十倍。 + +### 删除数据 + +删除数据使用delete方法,不过它有个独特的功能。 + +{% highlight perl %} +$memcached->delete('键', '阻塞时间(秒)'); +{% endhighlight %} + +删除第一个参数指定的键的数据。第二个参数指定一个时间值,可以禁止使用同样的键保存新数据。 此功能可以用于防止缓存数据的不完整。但是要注意,set函数忽视该阻塞,照常保存数据 + +### 增一和减一操作 + +可以将memcached上特定的键值作为计数器使用。 + +{% highlight perl %} +my $ret = $memcached->incr('键'); +$memcached->add('键', 0) unless defined $ret; +{% endhighlight %} + +增一和减一是原子操作,但未设置初始值时,不会自动赋成0。因此, 应当进行错误检查,必要时加入初始化操作。而且,服务器端也不会对 超过2 SUP(32)时的行为进行检查。 + +## 总结 + +这次简单介绍了memcached,以及它的安装方法、Perl客户端Cache::Memcached的用法。 只要知道,memcached的使用方法十分简单就足够了。 + +下次由前坂来说明memcached的内部结构。了解memcached的内部构造, 就能知道如何使用memcached才能使Web应用的速度更上一层楼。 欢迎继续阅读下一章。 + + + + + +# 2. 理解memcached的内存存储 + +本次将介绍memcached的内部构造的实现方式,以及内存的管理方式。 另外,memcached的内部构造导致的弱点也将加以说明。 + +## Slab Allocation机制:整理内存以便重复使用 + +最近的memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存。 在该机制出现以前,内存的分配是通过对所有记录简单地进行malloc和free来进行的。 但是,这种方式会导致内存碎片,加重操作系统内存管理器的负担,最坏的情况下, 会导致操作系统比memcached进程本身还慢。Slab Allocator就是为解决该问题而诞生的。 + +下面来看看Slab Allocator的原理。下面是memcached文档中的slab allocator的目标: + +{% highlight text %} +the primary goal of the slabs subsystem in memcached was to eliminate memory fragmentation +issues totally by using fixed-size memory chunks coming from a few predetermined size classes. +{% endhighlight %} + +也就是说,Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块, 以完全解决内存碎片问题。 + +Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk), 并把尺寸相同的块分成组(chunk的集合)(图1)。 + +![memcached 01](/images/linux/memcached-02-01.png){: .pull-center} +
    图1 Slab Allocation的构造图
    + +而且,slab allocator还有重复使用已分配的内存的目的。 也就是说,分配到的内存不会释放,而是重复利用。 + + +## Slab Allocation的主要术语 + +* Page + + 分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。 + +* Chunk + + 用于缓存记录的内存空间。 + +* Slab Class + + 特定大小的chunk的组。 + + +## 在Slab中缓存记录的原理 + +下面说明memcached如何针对客户端发送的数据选择slab并缓存到chunk中。 + +memcached根据收到的数据的大小,选择最适合数据大小的slab(图2)。 memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk, 然后将数据缓存于其中。 + +![memcached 02](/images/linux/memcached-02-02.png){: .pull-center} +
    图2 选择存储记录的组的方法
    + +实际上,Slab Allocator也是有利也有弊。下面介绍一下它的缺点。 + +## Slab Allocator的缺点 + +Slab Allocator解决了当初的内存碎片问题,但新的机制也给memcached带来了新的问题。 + +这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。 例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了(图3)。 + +![memcached 03](/images/linux/memcached-02-03.png){: .pull-center} +
    图3 chunk空间的使用
    + +对于该问题目前还没有完美的解决方案,但在文档中记载了比较有效的解决方案。 + +{% highlight text %} +The most efficient way to reduce the waste is to use a list of size classes that closely matches +(if that’s at all possible) common sizes of objects that the clients of this particular +installation of memcached are likely to store. +{% endhighlight %} + +就是说,如果预先知道客户端发送的数据的公用大小,或者仅缓存大小相同的数据的情况下, 只要使用适合数据大小的组的列表,就可以减少浪费。 + +但是很遗憾,现在还不能进行任何调优,只能期待以后的版本了。 但是,我们可以调节slab class的大小的差别。 接下来说明growth factor选项。 + +## 使用Growth Factor进行调优 + +memcached在启动时指定 Growth Factor因子(通过-f选项), 就可以在某种程度上控制slab之间的差异。默认值为1.25。 但是,在该选项出现之前,这个因子曾经固定为2,称为“powers of 2”策略。 + +让我们用以前的设置,以verbose模式启动memcached试试看: + +{% highlight text %} +$ memcached -f 2 -vv +{% endhighlight %} + +下面是启动后的verbose输出: + +{% highlight text %} +slab class 1: chunk size 128 perslab 8192 +slab class 2: chunk size 256 perslab 4096 +slab class 3: chunk size 512 perslab 2048 +slab class 4: chunk size 1024 perslab 1024 +slab class 5: chunk size 2048 perslab 512 +slab class 6: chunk size 4096 perslab 256 +slab class 7: chunk size 8192 perslab 128 +slab class 8: chunk size 16384 perslab 64 +slab class 9: chunk size 32768 perslab 32 +slab class 10: chunk size 65536 perslab 16 +slab class 11: chunk size 131072 perslab 8 +slab class 12: chunk size 262144 perslab 4 +slab class 13: chunk size 524288 perslab 2 +{% endhighlight %} + +可见,从128字节的组开始,组的大小依次增大为原来的2倍。 这样设置的问题是,slab之间的差别比较大,有些情况下就相当浪费内存。 因此,为尽量减少内存浪费,两年前追加了growth factor这个选项。 + +来看看现在的默认设置(f=1.25)时的输出(篇幅所限,这里只写到第10组): + +{% highlight text %} +slab class 1: chunk size 88 perslab 11915 +slab class 2: chunk size 112 perslab 9362 +slab class 3: chunk size 144 perslab 7281 +slab class 4: chunk size 184 perslab 5698 +slab class 5: chunk size 232 perslab 4519 +slab class 6: chunk size 296 perslab 3542 +slab class 7: chunk size 376 perslab 2788 +slab class 8: chunk size 472 perslab 2221 +slab class 9: chunk size 592 perslab 1771 +slab class 10: chunk size 744 perslab 1409 +{% endhighlight %} + +可见,组间差距比因子为2时小得多,更适合缓存几百字节的记录。 从上面的输出结果来看,可能会觉得有些计算误差, 这些误差是为了保持字节数的对齐而故意设置的。 + +将memcached引入产品,或是直接使用默认值进行部署时, 最好是重新计算一下数据的预期平均长度,调整growth factor, 以获得最恰当的设置。内存是珍贵的资源,浪费就太可惜了。 + +接下来介绍一下如何使用memcached的stats命令查看slabs的利用率等各种各样的信息。 + +## 查看memcached的内部状态 + +memcached有个名为stats的命令,使用它可以获得各种各样的信息。 执行命令的方法很多,用telnet最为简单: + +{% highlight text %} +$ telnet 主机名 端口号 +{% endhighlight %} + +连接到memcached之后,输入stats再按回车,即可获得包括资源利用率在内的各种信息。 此外,输入”stats slabs”或”stats items”还可以获得关于缓存记录的信息。 结束程序请输入quit。 + +这些命令的详细信息可以参考memcached软件包内的protocol.txt文档。 + +{% highlight text %} +$ telnet localhost 11211 +Trying ::1... +Connected to localhost. +Escape character is '^]'. +stats +STAT pid 481 +STAT uptime 16574 +STAT time 1213687612 +STAT version 1.2.5 +STAT pointer_size 32 +STAT rusage_user 0.102297 +STAT rusage_system 0.214317 +STAT curr_items 0 +STAT total_items 0 +STAT bytes 0 +STAT curr_connections 6 +STAT total_connections 8 +STAT connection_structures 7 +STAT cmd_get 0 +STAT cmd_set 0 +STAT get_hits 0 +STAT get_misses 0 +STAT evictions 0 +STAT bytes_read 20 +STAT bytes_written 465 +STAT limit_maxbytes 67108864 +STAT threads 4 +END +quit +{% endhighlight %} + +另外,如果安装了libmemcached这个面向C/C++语言的客户端库,就会安装 memstat 这个命令。 使用方法很简单,可以用更少的步骤获得与telnet相同的信息,还能一次性从多台服务器获得信息。 + +{% highlight text %} +$ memstat --servers=server1,server2,server3,... +{% endhighlight %} + +libmemcached可以从下面的地址获得: [http://tangent.org/552/libmemcached.html](http://tangent.org/552/libmemcached.html) + +## 查看slabs的使用状况 + +使用memcached的创造者Brad写的名为memcached-tool的Perl脚本,可以方便地获得slab的使用情况 (它将memcached的返回值整理成容易阅读的格式)。可以从下面的地址获得脚本: + +[http://code.sixapart.com/svn/memcached/trunk/server/scripts/memcached-tool](http://code.sixapart.com/svn/memcached/trunk/server/scripts/memcached-tool) + +使用方法也极其简单: + +{% highlight text %} +$ memcached-tool 主机名:端口 选项 +{% endhighlight %} + +查看slabs使用状况时无需指定选项,因此用下面的命令即可: + +{% highlight text %} +$ memcached-tool 主机名:端口 +{% endhighlight %} + +获得的信息如下所示: + +{% highlight text %} + # Item_Size Max_age 1MB_pages Count Full? + 1 104 B 1394292 s 1215 12249628 yes + 2 136 B 1456795 s 52 400919 yes + 3 176 B 1339587 s 33 196567 yes + 4 224 B 1360926 s 109 510221 yes + 5 280 B 1570071 s 49 183452 yes + 6 352 B 1592051 s 77 229197 yes + 7 440 B 1517732 s 66 157183 yes + 8 552 B 1460821 s 62 117697 yes + 9 696 B 1521917 s 143 215308 yes +10 872 B 1695035 s 205 246162 yes +11 1.1 kB 1681650 s 233 221968 yes +12 1.3 kB 1603363 s 241 183621 yes +13 1.7 kB 1634218 s 94 57197 yes +14 2.1 kB 1695038 s 75 36488 yes +15 2.6 kB 1747075 s 65 25203 yes +16 3.3 kB 1760661 s 78 24167 yes +{% endhighlight %} + +各列的含义为: + +{% highlight text %} +列 含义 +# slab class编号 +Item_Size Chunk大小 +Max_age LRU内最旧的记录的生存时间 +1MB_pages 分配给Slab的页数 +Count Slab内的记录数 +Full? Slab内是否含有空闲chunk +{% endhighlight %} + +从这个脚本获得的信息对于调优非常方便,强烈推荐使用。 + +## 内存存储的总结 + +本次简单说明了memcached的缓存机制和调优方法。 希望读者能理解memcached的内存管理原理及其优缺点。 + +下次将继续说明LRU和Expire等原理,以及memcached的最新发展方向—— 可扩充体系(pluggable architecher))。 + + + + +# 3. memcached的删除机制和发展方向 + +memcached是缓存,所以数据不会永久保存在服务器上,这是向系统中引入memcached的前提。 本次介绍memcached的数据删除机制,以及memcached的最新发展方向——二进制协议(Binary Protocol) 和外部引擎支持。 + +## memcached在数据删除方面有效利用资源 + +### 数据不会真正从memcached中消失 + +上次介绍过, memcached不会释放已分配的内存。记录超时后,客户端就无法再看见该记录(invisible,透明), 其存储空间即可重复使用。 + +### Lazy Expiration + +memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。 这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。 + +## LRU:从缓存中有效删除数据的原理 + +memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新记录时空间不足的情况, 此时就要使用名为 Least Recently Used(LRU)机制来分配空间。 顾名思义,这是删除“最近最少使用”的记录的机制。 因此,当memcached的内存空间不足时(无法从slab class 获取到新的空间时),就从最近未被使用的记录中搜索,并将其空间分配给新的记录。 从缓存的实用角度来看,该模型十分理想。 + +不过,有些情况下LRU机制反倒会造成麻烦。memcached启动时通过“-M”参数可以禁止LRU,如下所示: + +{% highlight text %} +$ memcached -M -m 1024 +{% endhighlight %} + +启动时必须注意的是,小写的“-m”选项是用来指定最大内存大小的。不指定具体数值则使用默认值64MB。 + +指定“-M”参数启动后,内存用尽时memcached会返回错误。 话说回来,memcached毕竟不是存储器,而是缓存,所以推荐使用LRU。 + +## memcached的最新发展方向 + +memcached的roadmap上有两个大的目标。一个是二进制协议的策划和实现,另一个是外部引擎的加载功能。 + +### 关于二进制协议 + +使用二进制协议的理由是它不需要文本协议的解析处理,使得原本高速的memcached的性能更上一层楼, 还能减少文本协议的漏洞。目前已大部分实现,开发用的代码库中已包含了该功能。 memcached的下载页面上有代码库的链接。 + +[http://danga.com/memcached/download.bml](http://danga.com/memcached/download.bml) + +### 二进制协议的格式 + +协议的包为24字节的帧,其后面是键和无结构数据(Unstructured Data)。 实际的格式如下(引自协议文档): + +{% highlight text %} + Byte/ 0 | 1 | 2 | 3 | + / | | | | + |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + +---------------+---------------+---------------+---------------+ + 0/ HEADER / + / / + / / + / / + +---------------+---------------+---------------+---------------+ + 24/ COMMAND-SPECIFIC EXTRAS (as needed) / + +/ (note length in th extras length header field) / + +---------------+---------------+---------------+---------------+ + m/ Key (as needed) / + +/ (note length in key length header field) / + +---------------+---------------+---------------+---------------+ + n/ Value (as needed) / + +/ (note length is total body length header field, minus / + +/ sum of the extras and key length body fields) / + +---------------+---------------+---------------+---------------+ + Total 24 bytes +{% endhighlight %} + +如上所示,包格式十分简单。需要注意的是,占据了16字节的头部(HEADER)分为 请求头(Request Header)和响应头(Response Header)两种。 头部中包含了表示包的有效性的Magic字节、命令种类、键长度、值长度等信息,格式如下: + +{% highlight text %} +Request Header + + Byte/ 0 | 1 | 2 | 3 | + / | | | | + |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + +---------------+---------------+---------------+---------------+ + 0| Magic | Opcode | Key length | + +---------------+---------------+---------------+---------------+ + 4| Extras length | Data type | Reserved | + +---------------+---------------+---------------+---------------+ + 8| Total body length | + +---------------+---------------+---------------+---------------+ + 12| Opaque | + +---------------+---------------+---------------+---------------+ + 16| CAS | + | | + +---------------+---------------+---------------+---------------+ + +Response Header + + Byte/ 0 | 1 | 2 | 3 | + / | | | | + |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7| + +---------------+---------------+---------------+---------------+ + 0| Magic | Opcode | Key Length | + +---------------+---------------+---------------+---------------+ + 4| Extras length | Data type | Status | + +---------------+---------------+---------------+---------------+ + 8| Total body length | + +---------------+---------------+---------------+---------------+ + 12| Opaque | + +---------------+---------------+---------------+---------------+ + 16| CAS | + | | + +---------------+---------------+---------------+---------------+ +{% endhighlight %} + +如希望了解各个部分的详细内容,可以checkout出memcached的二进制协议的代码树, 参考其中的docs文件夹中的protocol_binary.txt文档。 + +### HEADER中引人注目的地方 + +看到HEADER格式后我的感想是,键的上限太大了!现在的memcached规格中,键长度最大为250字节, 但二进制协议中键的大小用2字节表示。因此,理论上最大可使用65536字节(216)长的键。 尽管250字节以上的键并不会太常用,二进制协议发布之后就可以使用巨大的键了。 + +二进制协议从下一版本1.3系列开始支持。 + +## 外部引擎支持 + +我去年曾经试验性地将memcached的存储层改造成了可扩展的(pluggable)。 + +[http://alpha.mixi.co.jp/blog/?p=129](http://alpha.mixi.co.jp/blog/?p=129) + +MySQL的Brian Aker看到这个改造之后,就将代码发到了memcached的邮件列表。 memcached的开发者也十分感兴趣,就放到了roadmap中。现在由我和 memcached的开发者Trond Norbye协同开发(规格设计、实现和测试)。 和国外协同开发时时差是个大问题,但抱着相同的愿景, 最后终于可以将可扩展架构的原型公布了。 代码库可以从memcached的下载页面 上访问。 + +## 外部引擎支持的必要性 + +世界上有许多memcached的派生软件,其理由是希望永久保存数据、实现数据冗余等, 即使牺牲一些性能也在所不惜。我在开发memcached之前,在mixi的研发部也曾经 考虑过重新发明memcached。 + +外部引擎的加载机制能封装memcached的网络功能、事件处理等复杂的处理。 因此,现阶段通过强制手段或重新设计等方式使memcached和存储引擎合作的困难 就会烟消云散,尝试各种引擎就会变得轻而易举了。 + +## 简单API设计的成功的关键 + +该项目中我们最重视的是API设计。函数过多,会使引擎开发者感到麻烦; 过于复杂,实现引擎的门槛就会过高。因此,最初版本的接口函数只有13个。 具体内容限于篇幅,这里就省略了,仅说明一下引擎应当完成的操作: + +* 引擎信息(版本等) +* 引擎初始化 +* 引擎关闭 +* 引擎的统计信息 +* 在容量方面,测试给定记录能否保存 +* 为item(记录)结构分配内存 +* 释放item(记录)的内存 +* 删除记录 +* 保存记录 +* 回收记录 +* 更新记录的时间戳 +* 数学运算处理 +* 数据的flush + +对详细规格有兴趣的读者,可以checkout engine项目的代码,阅读器中的engine.h。 + +## 重新审视现在的体系 + +memcached支持外部存储的难点是,网络和事件处理相关的代码(核心服务器)与 内存存储的代码紧密关联。这种现象也称为tightly coupled(紧密耦合)。 必须将内存存储的代码从核心服务器中独立出来,才能灵活地支持外部引擎。 因此,基于我们设计的API,memcached被重构成下面的样子: + +![memcached 03](/images/linux/memcached-03-01.png){: .pull-center} + +重构之后,我们与1.2.5版、二进制协议支持版等进行了性能对比,证实了它不会造成性能影响。 + +在考虑如何支持外部引擎加载时,让memcached进行并行控制(concurrency control)的方案是最为容易的, 但是对于引擎而言,并行控制正是性能的真谛,因此我们采用了将多线程支持完全交给引擎的设计方案。 + +以后的改进,会使得memcached的应用范围更为广泛。 + + +## 总结 + +本次介绍了memcached的超时原理、内部如何删除数据等,在此之上又介绍了二进制协议和 外部引擎支持等memcached的最新发展方向。这些功能要到1.3版才会支持,敬请期待! + +这是我在本连载中的最后一篇。感谢大家阅读我的文章! + +下次由长野来介绍memcached的应用知识和应用程序兼容性等内容。 + + + + +# 4. memcached的分布式算法 + +本次不再介绍memcached的内部结构,开始介绍memcached的分布式。 + +## memcached的分布式 + +正如第1次中介绍的那样,memcached虽然称为“分布式”缓存服务器,但服务器端并没有“分布式”功能。 服务器端仅包括第2次、第3次前坂介绍的内存存储功能,其实现非常简单。至于memcached的分布式,则是完全由客户端程序库实现的。这种分布式是memcached的最大特点。 + +## memcached的分布式是什么意思? + +这里多次使用了“分布式”这个词,但并未做详细解释。 现在开始简单地介绍一下其原理,各个客户端的实现基本相同。 + +下面假设memcached服务器有node1~node3三台, 应用程序要保存键名为“tokyo”“kanagawa”“chiba”“saitama”“gunma” 的数据。 + +![memcached 01](/images/linux/memcached-04-01.png){: .pull-center} +
    图1 分布式简介:准备
    + +首先向memcached中添加“tokyo”。将“tokyo”传给客户端程序库后, 客户端实现的算法就会根据“键”来决定保存数据的memcached服务器。 服务器选定后,即命令它保存“tokyo”及其值。 + +![memcached 02](/images/linux/memcached-04-02.png){: .pull-center} +
    图2 分布式简介:添加时
    + +同样,“kanagawa”“chiba”“saitama”“gunma”都是先选择服务器再保存。 + +接下来获取保存的数据。获取时也要将要获取的键“tokyo”传递给函数库。 函数库通过与数据保存时相同的算法,根据“键”选择服务器。 使用的算法相同,就能选中与保存时相同的服务器,然后发送get命令。 只要数据没有因为某些原因被删除,就能获得保存的值。 + +![memcached 03](/images/linux/memcached-04-03.png){: .pull-center} +
    图3 分布式简介:获取时
    + +这样,将不同的键保存到不同的服务器上,就实现了memcached的分布式。 memcached服务器增多后,键就会分散,即使一台memcached服务器发生故障 无法连接,也不会影响其他的缓存,系统依然能继续运行。 + +接下来介绍第1次中提到的Perl客户端函数库Cache::Memcached实现的分布式方法。 + +## Cache::Memcached的分布式方法 + +Perl的memcached客户端函数库Cache::Memcached是 memcached的作者Brad Fitzpatrick的作品,可以说是原装的函数库了。 + +[Cache::Memcached - search.cpan.org](http://search.cpan.org/dist/Cache-Memcached/) + +该函数库实现了分布式功能,是memcached标准的分布式方法。 + +### 根据余数计算分散 + +Cache::Memcached的分布式方法简单来说,就是“根据服务器台数的余数进行分散”。 求得键的整数哈希值,再除以服务器台数,根据其余数来选择服务器。 + +下面将Cache::Memcached简化成以下的Perl脚本来进行说明。 + +{% highlight perl %} +use strict; +use warnings; +use String::CRC32; + +my @nodes = ('node1','node2','node3'); +my @keys = ('tokyo', 'kanagawa', 'chiba', 'saitama', 'gunma'); + +foreach my $key (@keys) { + my $crc = crc32($key); # CRC値 + my $mod = $crc % ( $#nodes + 1 ); + my $server = $nodes[ $mod ]; # 根据余数选择服务器 + printf "%s => %s\n", $key, $server; +} +{% endhighlight %} + +Cache::Memcached在求哈希值时使用了CRC。 + +[String::CRC32 - search.cpan.org](http://search.cpan.org/dist/String-CRC32/) + +首先求得字符串的CRC值,根据该值除以服务器节点数目得到的余数决定服务器。 上面的代码执行后输入以下结果: + +{% highlight text %} +tokyo => node2 +kanagawa => node3 +chiba => node2 +saitama => node1 +gunma => node1 +{% endhighlight %} + +根据该结果,“tokyo”分散到node2,“kanagawa”分散到node3等。 多说一句,当选择的服务器无法连接时,Cache::Memcached会将连接次数 添加到键之后,再次计算哈希值并尝试连接。这个动作称为rehash。 不希望rehash时可以在生成Cache::Memcached对象时指定“rehash => 0”选项。 + +## 根据余数计算分散的缺点 + +余数计算的方法简单,数据的分散性也相当优秀,但也有其缺点。 那就是当添加或移除服务器时,缓存重组的代价相当巨大。 添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器, 从而影响缓存的命中率。用Perl写段代码来验证其代价。 + +{% highlight perl %} +use strict; +use warnings; +use String::CRC32; + +my @nodes = @ARGV; +my @keys = ('a'..'z'); +my %nodes; + +foreach my $key ( @keys ) { + my $hash = crc32($key); + my $mod = $hash % ( $#nodes + 1 ); + my $server = $nodes[ $mod ]; + push @{ $nodes{ $server } }, $key; +} + +foreach my $node ( sort keys %nodes ) { + printf "%s: %s\n", $node, join ",", @{ $nodes{$node} }; +} +{% endhighlight %} + +这段Perl脚本演示了将“a”到“z”的键保存到memcached并访问的情况。 将其保存为mod.pl并执行。 + +首先,当服务器只有三台时: + +{% highlight text %} +$ mod.pl node1 node2 nod3 +node1: a,c,d,e,h,j,n,u,w,x +node2: g,i,k,l,p,r,s,y +node3: b,f,m,o,q,t,v,z +{% endhighlight %} + +结果如上,node1保存a、c、d、e……,node2保存g、i、k……, 每台服务器都保存了8个到10个数据。 + +接下来增加一台memcached服务器。 + +{% highlight text %} +$ mod.pl node1 node2 node3 node4 +node1: d,f,m,o,t,v +node2: b,i,k,p,r,y +node3: e,g,l,n,u,w +node4: a,c,h,j,q,s,x,z +{% endhighlight %} + +添加了node4。可见,只有d、i、k、p、r、y命中了。像这样,添加节点后键分散到的服务器会发生巨大变化。26个键中只有六个在访问原来的服务器, 其他的全都移到了其他服务器。命中率降低到23%。在Web应用程序中使用memcached时,在添加memcached服务器的瞬间缓存效率会大幅度下降,负载会集中到数据库服务器上, 有可能会发生无法提供正常服务的情况。 + +mixi的Web应用程序运用中也有这个问题,导致无法添加memcached服务器。 但由于使用了新的分布式方法,现在可以轻而易举地添加memcached服务器了。这种分布式方法称为 Consistent Hashing。 + +## Consistent Hashing + +关于Consistent Hashing的思想,mixi株式会社的开发blog等许多地方都介绍过, 这里只简单地说明一下。 + +### Consistent Hashing的简单说明 + +Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值, 并将其配置到0~232的圆(continuum)上。 然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。 如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。 + +![memcached 04](/images/linux/memcached-04-04.png){: .pull-center} +
    图4 Consistent Hashing:基本原理
    + +从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化 而影响缓存的命中率,但Consistent Hashing中,只有在continuum上增加服务器的地点逆时针方向的 第一台服务器上的键会受到影响。 + +![memcached 05](/images/linux/memcached-04-05.png){: .pull-center} +
    图5 Consistent Hashing:添加服务器
    + +因此,Consistent Hashing最大限度地抑制了键的重新分布。 而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。 使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。 因此,使用虚拟节点的思想,为每个物理节点(服务器) 在continuum上分配100~200个点。这样就能抑制分布不均匀, 最大限度地减小服务器增减时的缓存重新分布。 + +通过下文中介绍的使用Consistent Hashing算法的memcached客户端函数库进行测试的结果是, 由服务器台数(n)和增加的服务器台数(m)计算增加服务器后的命中率计算公式如下: + +{% highlight text %} +(1 - n/(n+m)) * 100 +{% endhighlight %} + +### 支持Consistent Hashing的函数库 + +本连载中多次介绍的Cache::Memcached虽然不支持Consistent Hashing, 但已有几个客户端函数库支持了这种新的分布式算法。 第一个支持Consistent Hashing和虚拟节点的memcached客户端函数库是 名为libketama的PHP库,由last.fm开发。 + +至于Perl客户端,连载的第1次中介绍过的Cache::Memcached::Fast和Cache::Memcached::libmemcached支持 Consistent Hashing。 + +[Cache::Memcached::Fast - search.cpan.org](http://search.cpan.org/dist/Cache-Memcached-Fast/) +[Cache::Memcached::libmemcached - search.cpan.org](http://search.cpan.org/dist/Cache-Memcached-libmemcached/) + +两者的接口都与Cache::Memcached几乎相同,如果正在使用Cache::Memcached, 那么就可以方便地替换过来。Cache::Memcached::Fast重新实现了libketama, 使用Consistent Hashing创建对象时可以指定ketama_points选项。 + +{% highlight text %} +my $memcached = Cache::Memcached::Fast->new({ + servers => ["192.168.0.1:11211","192.168.0.2:11211"], + ketama_points => 150 +}); +{% endhighlight %} + +另外,Cache::Memcached::libmemcached 是一个使用了Brain Aker开发的C函数库libmemcached的Perl模块。 libmemcached本身支持几种分布式算法,也支持Consistent Hashing, 其Perl绑定也支持Consistent Hashing。 + +[Tangent Software: libmemcached](tangent.org/552/libmemcached.html) + +## 总结 + +本次介绍了memcached的分布式算法,主要有memcached的分布式是由客户端函数库实现, 以及高效率地分散数据的Consistent Hashing算法。下次将介绍mixi在memcached应用方面的一些经验, 和相关的兼容应用程序。 + + +# 5. memcached的应用和兼容程序 + +到上次为止,我们介绍了与memcached直接相关的话题,本次介绍一些mixi的案例和实际应用上的话题,并介绍一些与memcached兼容的程序。 + +## mixi案例研究 + +mixi在提供服务的初期阶段就使用了memcached。 随着网站访问量的急剧增加,单纯为数据库添加slave已无法满足需要,因此引入了memcached。 此外,我们也从增加可扩展性的方面进行了验证,证明了memcached的速度和稳定性都能满足需要。 现在,memcached已成为mixi服务中非常重要的组成部分。 + +![memcached 01](/images/linux/memcached-05-01.png){: .pull-center} +
    图1 现在的系统组件
    + + +## 服务器配置和数量 + +mixi使用了许许多多服务器,如数据库服务器、应用服务器、图片服务器、 反向代理服务器等。单单memcached就有将近200台服务器在运行。 memcached服务器的典型配置如下: + +{% highlight text %} +CPU:Intel Pentium 4 2.8GHz +内存:4GB +硬盘:146GB SCSI +操作系统:Linux(x86_64) +{% endhighlight %} + +这些服务器以前曾用于数据库服务器等。随着CPU性能提升、内存价格下降, 我们积极地将数据库服务器、应用服务器等换成了性能更强大、内存更多的服务器。 这样,可以抑制mixi整体使用的服务器数量的急剧增加,降低管理成本。 由于memcached服务器几乎不占用CPU,就将换下来的服务器用作memcached服务器了。 + +## memcached进程 + +每台memcached服务器仅启动一个memcached进程。分配给memcached的内存为3GB, 启动参数如下: + +{% highlight text %} +/usr/bin/memcached -p 11211 -u nobody -m 3000 -c 30720 +{% endhighlight %} + +由于使用了x86_64的操作系统,因此能分配2GB以上的内存。32位操作系统中, 每个进程最多只能使用2GB内存。也曾经考虑过启动多个分配2GB以下内存的进程, 但这样一台服务器上的TCP连接数就会成倍增加,管理上也变得复杂, 所以mixi就统一使用了64位操作系统。 + +另外,虽然服务器的内存为4GB,却仅分配了3GB,是因为内存分配量超过这个值, 就有可能导致内存交换(swap)。连载的第2次中 前坂讲解过了memcached的内存存储“slab allocator”,当时说过,memcached启动时 指定的内存分配量是memcached用于保存数据的量,没有包括“slab allocator”本身占用的内存、 以及为了保存数据而设置的管理空间。因此,memcached进程的实际内存分配量要比 指定的容量要大,这一点应当注意。 + +mixi保存在memcached中的数据大部分都比较小。这样,进程的大小要比 指定的容量大很多。因此,我们反复改变内存分配量进行验证, 确认了3GB的大小不会引发swap,这就是现在应用的数值。 + +## memcached使用方法和客户端 + +现在,mixi的服务将200台左右的memcached服务器作为一个pool使用。 每台服务器的容量为3GB,那么全体就有了将近600GB的巨大的内存数据库。 客户端程序库使用了本连载中多次提到车的Cache::Memcached::Fast, 与服务器进行交互。当然,缓存的分布式算法使用的是第4次介绍过的 Consistent Hashing算法。 + +[Cache::Memcached::Fast - search.cpan.org](http://search.cpan.org/dist/Cache-Memcached-Fast/) + +应用层上memcached的使用方法由开发应用程序的工程师自行决定并实现。 但是,为了防止车轮再造、防止Cache::Memcached::Fast上的教训再次发生, 我们提供了Cache::Memcached::Fast的wrap模块并使用。 + +### 通过Cache::Memcached::Fast维持连接 + +Cache::Memcached的情况下,与memcached的连接(文件句柄)保存在Cache::Memcached包内的类变量中。 在mod_perl和FastCGI等环境下,包内的变量不会像CGI那样随时重新启动, 而是在进程中一直保持。其结果就是不会断开与memcached的连接, 减少了TCP连接建立时的开销,同时也能防止短时间内反复进行TCP连接、断开 而导致的TCP端口资源枯竭。 + +但是,Cache::Memcached::Fast没有这个功能,所以需要在模块之外 将Cache::Memcached::Fast对象保持在类变量中,以保证持久连接。 + +{% highlight perl %} +package Gihyo::Memcached; + +use strict; +use warnings; +use Cache::Memcached::Fast; + +my @server_list = qw/192.168.1.1:11211 192.168.1.1:11211/; +my $fast; ## 用于保持对象 + +sub new { + my $self = bless {}, shift; + if ( !$fast ) { + $fast = Cache::Memcached::Fast->new({ servers => \@server_list }); + } + $self->{_fast} = $fast; + return $self; +} + +sub get { + my $self = shift; + $self->{_fast}->get(@_); +} +{% endhighlight %} + +上面的例子中,Cache::Memcached::Fast对象保存到类变量$fast中。 + +### 公共数据的处理和rehash + +诸如mixi的主页上的新闻这样的所有用户共享的缓存数据、设置信息等数据, 会占用许多页,访问次数也非常多。在这种条件下,访问很容易集中到某台memcached服务器上。 访问集中本身并不是问题,但是一旦访问集中的那台服务器发生故障导致memcached无法连接, 就会产生巨大的问题。 + +连载的第4次中提到,Cache::Memcached拥有rehash功能,即在无法连接保存数据的服务器的情况下, 会再次计算hash值,连接其他的服务器。 + +但是,Cache::Memcached::Fast没有这个功能。不过,它能够在连接服务器失败时, 短时间内不再连接该服务器的功能。 + +{% highlight text %} +my $fast = Cache::Memcached::Fast->new({ + max_failures => 3, + failure_timeout => 1 +}); +{% endhighlight %} + +在failure_timeout秒内发生max_failures以上次连接失败,就不再连接该memcached服务器。 我们的设置是1秒钟3次以上。 + +此外,mixi还为所有用户共享的缓存数据的键名设置命名规则, 符合命名规则的数据会自动保存到多台memcached服务器中, 取得时从中仅选取一台服务器。创建该函数库后,就可以使memcached服务器故障 不再产生其他影响。 + +## memcached应用经验 + +到此为止介绍了memcached内部构造和函数库,接下来介绍一些其他的应用经验。 + +### 通过daemontools启动 + +通常情况下memcached运行得相当稳定,但mixi现在使用的最新版1.2.5 曾经发生过几次memcached进程死掉的情况。架构上保证了即使有几台memcached故障 也不会影响服务,不过对于memcached进程死掉的服务器,只要重新启动memcached, 就可以正常运行,所以采用了监视memcached进程并自动启动的方法。 于是使用了daemontools。 + +daemontools是qmail的作者DJB开发的UNIX服务管理工具集, 其中名为supervise的程序可用于服务启动、停止的服务重启等。 + +[daemontools](http://cr.yp.to/daemontools.html) + +这里不介绍daemontools的安装了。mixi使用了以下的run脚本来启动memcached。 + +{% highlight bash %} +#!/bin/sh + +if [ -f /etc/sysconfig/memcached ];then + . /etc/sysconfig/memcached +fi + +exec 2>&1 +exec /usr/bin/memcached -p $PORT -u $USER -m $CACHESIZE -c $MAXCONN $OPTIONS +{% endhighlight %} + +监视 + +mixi使用了名为“nagios”的开源监视软件来监视memcached。 + +在nagios中可以简单地开发插件,可以详细地监视memcached的get、add等动作。 不过mixi仅通过stats命令来确认memcached的运行状态。 + +{% highlight text %} +define command { +command_name check_memcached +command_line $USER1$/check_tcp -H $HOSTADDRESS$ -p 11211 -t 5 -E -s 'stats\r\nquit\r\n' -e 'uptime' -M crit +} +{% endhighlight %} + +此外,mixi将stats目录的结果通过rrdtool转化成图形,进行性能监视, 并将每天的内存使用量做成报表,通过邮件与开发者共享。 + +## memcached的性能 + +连载中已介绍过,memcached的性能十分优秀。我们来看看mixi的实际案例。 这里介绍的图表是服务所使用的访问最为集中的memcached服务器。 + +![memcached 02](/images/linux/memcached-05-02.png){: .pull-center} +
    图2 请求数
    + +![memcached 03](/images/linux/memcached-05-03.png){: .pull-center} +
    图3 流量
    + +![memcached 04](/images/linux/memcached-05-04.png){: .pull-center} +
    图4 TCP连接数
    + +从上至下依次为请求数、流量和TCP连接数。请求数最大为15000qps, 流量达到400Mbps,这时的连接数已超过了10000个。 该服务器没有特别的硬件,就是开头介绍的普通的memcached服务器。 此时的CPU利用率为: + +![memcached 05](/images/linux/memcached-05-05.png){: .pull-center} +
    图5 CPU利用率
    + +可见,仍然有idle的部分。因此,memcached的性能非常高, 可以作为Web应用程序开发者放心地保存临时数据或缓存数据的地方。 + +## 兼容应用程序 + +memcached的实现和协议都十分简单,因此有很多与memcached兼容的实现。 一些功能强大的扩展可以将memcached的内存数据写到磁盘上,实现数据的持久性和冗余。 连载第3次介绍过,以后的memcached的存储层将变成可扩展的(pluggable),逐渐支持这些功能。 + +这里介绍几个与memcached兼容的应用程序。 + +* repcached: 为memcached提供复制(replication)功能的patch。 +* Flared: 存储到QDBM。同时实现了异步复制和fail over等功能。 +* memcachedb: 存储到BerkleyDB。还实现了message queue。 +* Tokyo Tyrant: 将数据存储到Tokyo Cabinet。不仅与memcached协议兼容,还能通过HTTP进行访问。 + +### Tokyo Tyrant案例 + +mixi使用了上述兼容应用程序中的Tokyo Tyrant。Tokyo Tyrant是平林开发的 Tokyo Cabinet DBM的网络接口。它有自己的协议,但也拥有memcached兼容协议, 也可以通过HTTP进行数据交换。Tokyo Cabinet虽然是一种将数据写到磁盘的实现,但速度相当快。 + +mixi并没有将Tokyo Tyrant作为缓存服务器,而是将它作为保存键值对组合的DBMS来使用。 主要作为存储用户上次访问时间的数据库来使用。它与几乎所有的mixi服务都有关, 每次用户访问页面时都要更新数据,因此负荷相当高。MySQL的处理十分笨重, 单独使用memcached保存数据又有可能会丢失数据,所以引入了Tokyo Tyrant。 但无需重新开发客户端,只需原封不动地使用Cache::Memcached::Fast即可, 这也是优点之一。关于Tokyo Tyrant的详细信息,请参考本公司的开发blog。 + +## 总结 + +到本次为止,“memcached全面剖析”系列就结束了。我们介绍了memcached的基础、内部结构、 分散算法和应用等内容。读完后如果您能对memcached产生兴趣,就是我们的荣幸。 关于mixi的系统、应用方面的信息,请参考本公司的开发blog。 感谢您的阅读。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-03-19-clucene-introduce_init.md b/_drafts/2016-03-19-clucene-introduce_init.md new file mode 100644 index 0000000..4eb3ba8 --- /dev/null +++ b/_drafts/2016-03-19-clucene-introduce_init.md @@ -0,0 +1,50 @@ +--- +Date: October 19, 2013 +title: CLucene +layout: post +comments: true +language: chinese +category: [linux] +--- + + + + + +Lucene 是 JAVA 版的全文检索引擎,而且据说性能相当出色,而 CLucene 是 C++ 版的全文检索引擎,完全移植于 Lucene,不过其缺点是不支持中文。 + + + + + + + + + +## 编译安装 + +可以直接从 [clucene.sourceforge.net](http://clucene.sourceforge.net/download.shtml) 上下载相应的源码,然后通过如下方式编译。 + +{% highlight text %} +----- 新建一个单独的目录进行编译 +$ mkdir build && cd build + +----- 配置生成Makefile +$ cmake ../clucene-code + +----- 直接编译,生成的文件保存在bin目录下 +$ make + +----- 编译生成测试程序cl_test +$ make cl_test + +----- 以及示例程序 +$ make cl_demo +{% endhighlight %} + + diff --git a/_drafts/2016-04-06-mysql-innodb-adaptive-hash-index_init.md b/_drafts/2016-04-06-mysql-innodb-adaptive-hash-index_init.md new file mode 100644 index 0000000..6d473c3 --- /dev/null +++ b/_drafts/2016-04-06-mysql-innodb-adaptive-hash-index_init.md @@ -0,0 +1,593 @@ +--- +title: InnoDB AHI +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,innodb,adaptive hash index,ahi +description: InnoDB 索引采用 B+Tree 组织,一般情况下需要根据查询条件,从根节点开始寻路到叶子节点,找到满足条件的记录,为了减少开销,做了几点优化。如下简单介绍下 AHI 的优化。 +--- + +InnoDB 索引采用 B+Tree 组织,一般情况下需要根据查询条件,从根节点开始寻路到叶子节点,找到满足条件的记录,为了减少开销,做了几点优化。 + + + +如下简单介绍下 AHI 的优化。 + + + +## Adaptive Hash Index + +哈希索引是一种非常快的等值查找方法,它查找的时间复杂度为常量,但必须是等值,对非等值查找方法无能为力。 + +InnoDB 采用自适用哈希索引技术,它会实时监控表上索引的使用情况,如果认为建立哈希索引可以提高查询效率,则自动在内存中的 "自适应哈希索引缓冲区" 建立哈希索引。 + +之所以该技术称为 "自适应" 是因为完全由 InnoDB 自己决定,不需要人为干预;它是通过缓冲池中的 B+ 树构造而来,且不需要对整个表建立哈希索引,因此它的数据非常快。 + +可以通过 innodb_adaptive_hash_index 变量决定是否打开该特性,通过 AHI 可以有效提高读取、写入、join 的速度。 + +### AHI 监控项 + +可以通过 ```SHOW ENGINE INNODB STATUS``` 命令查看,在 ```INSERT BUFFER AND ADAPTIVE HASH INDEX``` 段中。 + +{% highlight text %} +mysql> SHOW ENGINE INNODB STATUS\G +... ... +------------------------------------- +INSERT BUFFER AND ADAPTIVE HASH INDEX +------------------------------------- +Ibuf: size 1, free list len 0, seg size 2, 0 merges +merged operations: + insert 0, delete mark 0, delete 0 +discarded operations: + insert 0, delete mark 0, delete 0 +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +Hash table size 34679, used cells 0, node heap has 0 buffer(s) +0.00 hash searches/s, 0.00 non-hash searches/s +... ... +{% endhighlight %} + +可以通过 ```information_schema.innodb_metrics``` 来监控 AHI 模块的运行状态,首先打开监控。 + +{% highlight text %} +----- 打开所有与AHI相关的监控项 +mysql> SET GLOBAL innodb_monitor_enable=module_adaptive_hash; +Query OK, 0 rows affected (0.00 sec) + +----- 查看监控项是否已经开启 +mysql> SELECT name,status FROM information_schema.innodb_metrics WHERE subsystem='adaptive_hash_index'; ++------------------------------------------+---------+ +| name | status | ++------------------------------------------+---------+ +| adaptive_hash_searches | enabled | +| adaptive_hash_searches_btree | enabled | +| adaptive_hash_pages_added | enabled | +| adaptive_hash_pages_removed | enabled | +| adaptive_hash_rows_added | enabled | +| adaptive_hash_rows_removed | enabled | +| adaptive_hash_rows_deleted_no_hash_entry | enabled | +| adaptive_hash_rows_updated | enabled | ++------------------------------------------+---------+ +8 rows in set (0.00 sec) + +----- 重置所有的计数 +mysql> SET GLOBAL innodb_monitor_reset_all = 'adaptive_hash%'; +Query OK, 0 rows affected (0.00 sec) +{% endhighlight %} + +该表搜集了 AHI 子系统诸如 AHI 查询次数,更新次数等信息,可以很好的监控其运行状态,在某些负载下,AHI 并不适合打开,关闭 AHI 可以避免额外的维护开销。 + +## 源码解析 + +接下来看看 InnoDB 中的实现。 + +### AHI初始化 + +AHI 在内存中就是一个普通的哈希表对象,存储在 ```btr_search_sys_t.hash_index``` 中,对 AHI 的查删改操作都是通过一个全局读写锁 ```btr_search_latches``` 来保护。 + +{% highlight text %} +struct btr_search_sys_t{ + hash_table_t** hash_tables; +}; +rw_lock_t** btr_search_latches; + +innobase_start_or_create_for_mysql() + |-buf_pool_init() ← 在buff pool初始化完成后执行 + |-btr_search_sys_create() ← 创建AHI +{% endhighlight %} + +在实例启动,完成 buffer pool 初始化后,会初始化 AHI 子系统相关对象,并分配 AHI 内存,大小为 buffer pool 的 1/64 。 + +MySQL 5.7 已开始支持 Buffer Pool 的动态调整,其策略是 Buffer Pool 的大小改变超过 1 倍,就调用 ```btr_search_sys_resize()``` 重新分配 AHI Hash 内存。 + +### AHI 的维护 + +维护包括多个动作:A) 索引叶页面何时进入 Hash Index;B) 何时可以使用 Hash Index 加锁查询;C) 何时将索引叶页面从 Hash Index 中移除,等等。 + + + + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-04-23-nginx-press_init.md b/_drafts/2016-04-23-nginx-press_init.md new file mode 100644 index 0000000..8fb4aa0 --- /dev/null +++ b/_drafts/2016-04-23-nginx-press_init.md @@ -0,0 +1,171 @@ +--- +Date: October 19, 2013 +title: webserver 压测工具 +layout: post +comments: true +language: chinese +category: [webserver] +--- + + +在 Linux 下有四款常用的 Web 服务器压力测试工具,包括 http_load、webbench、ab、siege,在本文中会依次介绍这四种工具。 + + + +# webbench + +该软件很小,最多可以模拟 3 万个并发连接去测试网站的负载能力,其版本已经很老了,而且实际上只有两个源码文件,可以通过如下方式进行安装编译。 + + + +{% highlight text %} +$ wget http://home.tiscali.cz/cz210552/distfiles/webbench-1.5.tar.gz +$ tar zxvf webbench-1.5.tar.gz +$ cd webbench-1.5 +$ make +# make install +{% endhighlight %} + +接下来直接压测一下,压测时,一个并发是一个进程,所以最大的并发数与操作系统的资源有关系。 + +{% highlight text %} +$ webbench -c 500 -t 30 http://127.0.0.1/ +{% endhighlight %} +相关的参数可以直接通过 webbench -h 查看帮助,参数也比较简单。如上,-c 表示同时产生 500 个并发链接,-t 表示持续 30 秒。 + + +# http_load + +可以直接从 [http_load](http://www.acme.com/software/http_load/) 上下载源码进行安装;另外,在 [www.acme.com](http://www.acme.com/software/) 网站上还有很多不错的小程序,例如之前介绍过的小型 http 服务器 micro_httpd、micro_proxy、mini_httpd 等。 + +其源码文件同样很少,可以通过如下方式进行编译、安装。 + +{% highlight text %} +$ wget http://www.acme.com/software/http_load/http_load-09Mar2016.tar.gz +$ tar zxvf http_load-09Mar2006.tar.gz +$ cd http_load-09Mar2006 +$ make +# make install +{% endhighlight %} + +http_load 以并行复用的方式运行,调用的是 select() 系统调用,可以用来测试 web 服务器的吞吐量与负载。可以以一个单一的进程运行,可以限制速度,一般不会把客户机压垮,还可以测试 HTTPS 类的网站请求。 + +{% highlight text %} +----- 命令行使用方法 +http_load [-checksum] [-throttle] [-proxy host:port] [-verbose] [-timeout secs] [-sip sip_file] + -parallel N | -rate N [-jitter] + -fetches N | -seconds N + url_file + +----- 压测参数 +-parallel, -p + 并发的用户进程数 +-fetches, -f + 总共的访问次数 +-rate, -r + 每秒的访问频率 +-seconds, -s + 总计的访问时间 + +----- 示例 +$ http_load -parallel 5 -seconds 300 urls.txt +{% endhighlight %} + +其中的 URL 文件每行都是一个 URL,行数最好超过 50~100 个测试效果比较好,每次会随机读取文件中的 URL,如下: + +{% highlight text %} +http://www.vpser.net/uncategorized/choose-vps.html +http://www.vpser.net/vps-cp/hypervm-tutorial.html +http://www.vpser.net/coupons/diavps-april-coupons.html +http://www.vpser.net/security/vps-backup-web-mysql.html +{% endhighlight %} + + + +# AB (Apache Bench) + +该工具是 Apache 自带的一款功能强大的测试工具,在 CentOS 中,包含在 httpd-tools 包中。 + + + +# Siege + +开源的压力测试工具,可以根据配置对 web 站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。 + +可以参考官方网站 [www.joedog.org](http://www.joedog.org/),或者从 [www.github.com](https://github.com/JoeDog/siege) 上下载,直接通过如下方式编译、安装。 + +{% highlight text %} +$ wget http://download.joedog.org/siege/siege-latest.tar.gz +$ tar zxvf siege-latest.tar.gz +$ cd siege-4.0.2/ +$ ./configure && make +# make install +{% endhighlight %} + + + + + + + + + + + + + +{% highlight text %} + +{% endhighlight %} + diff --git a/_drafts/2016-05-03-browser-server_init.md b/_drafts/2016-05-03-browser-server_init.md new file mode 100644 index 0000000..cebbc72 --- /dev/null +++ b/_drafts/2016-05-03-browser-server_init.md @@ -0,0 +1,449 @@ +--- +Date: October 19, 2013 +title: 前后端交互 +layout: post +comments: true +language: chinese +category: [webserver] +--- + +对于一个网站,除了前端工程师的 UI 重构外,还有一个非常重要的职责是在相应的区域渲染出服务端的数据,毕竟,一个大的 web 应用,肯定不是普普通通的静态页面构成。 + +本文大致罗列一些前端与后端交互的方法方式。 + + + +# AJAX, Asynchronous JavaScript and XML + +AJAX 是一种使用现有标准的新方法,用于和服务器交换数据,可以在不重新加载整个页面的情况下,更新部分网页。例如,使用如下示例加载相关内容,其中 ajax/basic.html 的内容如下。 + +{% highlight html %} + + + + + + + +

    使用 AJAX 修改该文本内容

    + + + + + +{% endhighlight %} + +其中在服务器目录下有一个 ajax_info.txt 文件,内容如下。 + +{% highlight text %} +

    JUST FOR TEST

    +{% endhighlight %} + + +接下来一步步介绍相关内容。 + +## 执行流程 + +### 1. 新建 XMLHttpRequest 对象 + +所有现代浏览器均支持 XMLHttpRequest 对象,其中 IE5、IE6 使用 ActiveXObject,该对象用于在后台与服务器交换数据,创建 XMLHttpRequest 对象的语法如下。 + +{% highlight html %} +var xmlhttp=new XMLHttpRequest(); +{% endhighlight %} + +### 2. 向服务器发送请求 + +简单来说,发送请求需要使用 XMLHttpRequest 对象的 open() 和 send() 方法。 + +{% highlight text %} +xmlhttp.open("GET","/ajax/info.txt",true); +xmlhttp.send(); +{% endhighlight %} + +两个函数的声明如下: +{% highlight text %} +open(method,url,async); # 规定请求的类型、URL 以及是否异步处理请求 +send(string); # 将请求发送到服务器,参数仅用于 POST 请求 +{% endhighlight %} + +发送请求可以使用 POST 或者 GET,相比来说 GET 更简单迅速,在大部分情况下都能用; 但是,在以下情况中,需要使用 POST 请求: + +* 无法使用缓存文件,需要更新服务器上的文件或数据库; +* 向服务器发送大量数据,POST 没有数据量限制; +* 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠。 + + +一个简单的 GET 请求: + +{% highlight text %} +xmlhttp.open("GET","demo_get.html",true); +xmlhttp.send(); +{% endhighlight %} + +不过,上面的示例可能会得到缓存的结果,为了避免这种情况,可以在 URL 中添加一个唯一的 ID 。 + +{% highlight text %} +xmlhttp.open("GET","demo_get.html?t=" + Math.random(),true); +xmlhttp.send(); +{% endhighlight %} + +当然,也可以通过 GET 方法发送相关参数信息。 + +{% highlight text %} +xmlhttp.open("GET","demo_get2.html?fname=Henry&lname=Ford",true); +xmlhttp.send(); +{% endhighlight %} + + +如果使用 POST 方式,只需要将上述的 GET 参数改为 POST,如果需要像 HTML 表单那样 POST 数据,则使用 setRequestHeader() 来添加 HTTP 头,然后在 send() 方法中规定您希望发送的数据。 + +{% highlight text %} +xmlhttp.open("POST","ajax_test.html",true); +xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); +xmlhttp.send("fname=Henry&lname=Ford"); +{% endhighlight %} + + +### 3. 接收 + +如上述的示例,采用的是异步,那么就需要在响应处的 onreadystatechange 事件中设置就绪状态时执行的函数。如果采用同步,则不需要编写 onreadystatechange 函数,直接把代码放到 send() 语句后面即可。 + +{% highlight text %} +xmlhttp.open("GET","ajax_info.txt",false); +xmlhttp.send(); +document.getElementById("myDiv").innerHTML=xmlhttp.responseText; +{% endhighlight %} + + +### 4. 获取服务器响应 + +如需获得来自服务器的响应,可以使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性,分别对应字符串以及 XML 格式的数据。 + +{% highlight javascript %} +// 返回的为纯文本 +document.getElementById("myDiv").innerHTML=xmlhttp.responseText; + +// 返回的为 XML 格式 +xmlDoc=xmlhttp.responseXML; +txt=""; +x=xmlDoc.getElementsByTagName("ARTIST"); +for (i=0;i"; +} +document.getElementById("myDiv").innerHTML=txt; +{% endhighlight %} + + + +### 5. onreadystatechange 事件 + +请求发送到服务器后会执行一些基于响应的任务,每当 readyState 改变时,就会触发 onreadystatechange 事件。常见的有如下的几种状态: 0(请求未初始化)、1(服务器连接已建立)、2(请求已接收)、3(请求处理中)、4(请求已完成,且响应已就绪)。 + + + +## 示例 + +除了开始的简单示例之外,我们在此展示一个自动自动提示的示例,简单来说就是当用户在输入框中键入字符时,网页如何与 web 服务器进行通信,返回提示的结果,参考 ajax/auto.html 。 + +{% highlight html %} + + + + + + + +

    Start typing a name in the input field below:

    +
    +First name: +
    +

    Suggestions:

    + + + +{% endhighlight %} + + +如上,当用户在上面的输入框中键入字符时,会执行函数 "showHint()",该函数由 "onkeyup" 事件触发,如果输入框为空 (str.length==0),则该函数清空 txtHint 占位符的内容,并退出函数。 + +如果输入框不为空 showHint() 函数会执行如下过程: + +1. 创建 XMLHttpRequest 对象; +2. 注册当服务器响应就绪时执行的回调函数; +3. 把请求发送到服务器上; +4. 请注意我们向 URL 添加了一个参数 q 。 + +下面是用 PHP 编写的一个服务端的程序,也就是 ajax/gethint.php 文件。 + + +{% highlight php %} +0 +if (strlen($q) > 0) { + $hint=""; + for($i=0; $i +{% endhighlight %} + +## 与数据库的交互 + +通过 AJAX 可用来与数据库进行动态通信,如下的例子将演示网页如何通过 AJAX 从数据库读取信息,通过下拉列表中选择一个客户,然后列出相应的用户,对应了 ajax/db.html 。 + +{% highlight html %} + + + + + + + +
    + +
    +
    +
    Customer info will be listed here...
    + + + +{% endhighlight %} + +如下是服务端的 PHP 程序,也就是 ajax/getcustomer.php 。 + +{% highlight php %} + + +Firstname +Lastname +Age +Hometown +Job +"; + +while($row = mysqli_fetch_array($result)) { + echo ""; + echo "" . $row['FirstName'] . ""; + echo "" . $row['LastName'] . ""; + echo "" . $row['Age'] . ""; + echo "" . $row['Hometown'] . ""; + echo "" . $row['Job'] . ""; + echo ""; +} +echo ""; + +mysqli_close($con); +?> +{% endhighlight %} + + + + + + +# Comet + +对于一些定时更新数据的场景,可以通过 setInterval 设置时间间隔,例如 10s 去获取一次请求,从而做一些更新,不过这样的话比较低效,会增加服务器的请求数量。为此,可以由服务器进行数据推送,Comet 能够让信息近乎实时的被推送到页面上,非常适合要求实时性的获取的数据的页面。 + +其执行的效果类似于实时聊天、淘宝上在手机支付成功之后的自动调转、微信网页版在手机扫描完二维码之后的自动调转等等。 + +Comet 目前有如下的几种实现方式:长轮询 (long-polling) 和 iframe 流 (streaming)。 + + +## long polling + +## iframe streaming + + + +pushlet 提供了基于 AJAX 的 JavaScript 库文件用于实现长轮询方式的“服务器推”;还提供了基于 iframe 的 JavaScript 库文件用于实现流方式的“服务器推”。 + + + + + + + + + + + +# Server-Sent Events + + + + +# Web Sockets + +HTML5 WebSocket 设计出来的目的就是使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力,浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。 + +也就是我们可以使用 web 技术构建实时性的程序比如聊天游戏等应用,而 Web Sockets 的 API 却很少,就下面这些。 + +{% highlight text %} +websocket = new WebSocket("ws://your.socket.com:9001"); +websocket.onopen = function(evt) { /* do stuff */ }; // 当打开时 +websocket.onclose = function(evt) { /* do stuff */ }; // 当web socket关闭 +websocket.onmessage = function(evt) { /* do stuff */ }; // 进行通信时 +websocket.onerror = function(evt) { /* do stuff */ }; // 发生错误时 +websocket.send(message); // 向服务器发发送消息 +websocket.close(); // 关闭连接 +{% endhighlight %} + +而服务端大概会执行如下的内容:创建一个socket + 绑定地址和端口 + 监听进入的连接 + 接收新的连接 + web socket 握手 + 解码数据 + 返回数据 + 关闭连接。 + + +可以参考一个简单的聊天示例 [Simple Chat Using WebSocket and PHP Socket](https://www.sanwebe.com/2013/05/chat-using-websocket-php-socket) 。 + +# 总结 + + + AJAX - 请求 → 响应 (频繁使用) + Comet - 请求 → 挂起 → 响应 (模拟服务端推送) + Server-Sent Events - 客户单 ← 服务端 (服务端推送) + WebSockets - 客户端 ↔ 服务端 (未来趋势,双工通信) + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-05-20-html-css-examples_init.md b/_drafts/2016-05-20-html-css-examples_init.md new file mode 100644 index 0000000..66b8325 --- /dev/null +++ b/_drafts/2016-05-20-html-css-examples_init.md @@ -0,0 +1,62 @@ +--- +Date: October 19, 2013 +title: 一些不错的 HTML+CSS 入门示例 +layout: post +comments: true +language: chinese +category: [webserver] +--- + + + + + +# 登陆 + +这个实际上是从 [Slick login form with HTML5 & CSS3](http://red-team-design.com/slick-login-form-with-html5-css3/) 参考的示例,该 blog 中有很多不错的前端设计示例,其效果可以直接参考 [login examle](/reference/linux/html_css/login/index.html) 。 + +{% highlight html %} +
    +

    Log In

    +
    + + +
    +
    + + Forgot your password?Register +
    +
    +{% endhighlight %} + + + +根据最新的 HTML5 文档,其中的一些属性列举如下: + +* placeholder 用来提示输入框中所需要输入的内容。 + +* required 表示当表单提交的时候,该输入框是必须的。 + +* autofocus 指定当加载之后自动聚焦到该输入框。 + +* type 指定类型,对于 password 来说,字段中的字符会被遮蔽;而 text 则会直接显示。 + + + + + +## 常见示例 + +* 弹出联系方式等,可以参考 [示例](/reference/linux/bootstrap/pop_toolbars/index.html) 。 + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-05-22-apache-introduce_init.md b/_drafts/2016-05-22-apache-introduce_init.md new file mode 100644 index 0000000..0c15bb2 --- /dev/null +++ b/_drafts/2016-05-22-apache-introduce_init.md @@ -0,0 +1,256 @@ +--- +Date: October 19, 2013 +title: Apache 简介 +layout: post +comments: true +language: chinese +category: [webserver] +--- + +Apache 是北美印第安人的一个部落,叫阿帕奇族,在美国的西南部,在这里是个 web 服务器,同时也是一个基金会的名称、一种武装直升机。 + +目前 Apache 仍然占有很大的市场份额,是使用最广泛的一种,虽然现在 Nginx 的发展势头正盛。 + + + + + +# 简介 + +Apache 以跨平台、高效和稳定而闻名,它几乎可以运行在所有平台上,可以通过简单的 API 扩充,将 Perl、Python 等解析器编译到服务器中。 + +目前,它最为诟病的一点就是变得越来越重,被普遍认为是重量级的 WebServer,这也就导致了像 lighttpd、nginx 这种轻量级的替代产品,而且也越来越成熟。 + + + + + +Apache 基于模块化设计,其核心代码并不多,功能都被分散到各个模块中,而各个模块在系统启动的时候按需载入,其中主要的代码在 server/main.c 中。 + + + + +# MPM, Multi-Processing Modules + + + +MPM 是 Apache 的核心组件之一,该模块用于对进程和线程池进行管理,在编译的时候必须选择其中的一个,对于 *nix 来说可以选择任意的一个。 + +目前支持支持三种的 MPM,分别是:Prefork、Worker、Event 三种,可以通过 httpd -V 查看当前支持的模式。 + +## Prefork MPM + + +## Worker MPM + + +## Event MPM + +# APR, Apache portable Run-time libraries + + + +[httpd.apache.org](http://httpd.apache.org/) + + + + +# Virtual Hosts + +WEB 服务实际上就是访问某个 IP 主机上的某个端口,这也就意味着可以通过访问不同的域名或者端口实现对不同网站的访问,此时就需要设置虚拟主机,目前通常有三种方式。 + + +## 基于 IP + +假设服务器当前的 IP 地址为 192.168.1.100,通过 ifconfig 在同一个网络接口 eth0 上绑定 3 个 IP 。 + +{% highlight text %} +----- 添加3个IP地址 +# ifconfig eth0:1 192.168.1.101 +# ifconfig eth0:2 192.168.1.102 +# ifconfig eth0:3 192.168.1.103 + +----- 修改/etc/hosts文件,绑定三个域名的IP地址 +# cat /etc/hosts +192.168.1.101 www.foobar1.com +192.168.1.102 www.foobar2.com +192.168.1.103 www.foobar3.com + +----- 新建三个独立的目录,以及不同内容的index.html +{% endhighlight %} + + + 3. 建立虚拟主机存放网页的根目录,如在/www目录下建立test1、test2、test3文件夹,其中分别存放1.html、2.html、3.html +/www/test1/1.html +/www/test2/2.html +/www/test3/3.html + + 4. 在httpd.conf中将附加配置文件httpd-vhosts.conf包含进来,接着在httpd-vhosts.conf中写入如下配置: + + +  ServerName www.test1.com +  DocumentRoot /www/test1/ +   +     Options Indexes FollowSymLinks +     AllowOverride None +     Order allow,deny +     Allow From All +   + + + +  ServerName www.test1.com +  DocumentRoot /www/test2/ +   +     Options Indexes FollowSymLinks +     AllowOverride None +     Order allow,deny +     Allow From All +   + + + +  ServerName www.test1.com +  DocumentRoot /www/test3/ +   +     Options Indexes FollowSymLinks +     AllowOverride None +     Order allow,deny +     Allow From All +   + + +复制代码 + + 5. 大功告成,测试下每个虚拟主机,分别访问www.test1.com、www.test2.com、www.test3.com + + + +二、基于主机名 + + 1. 设置域名映射同一个IP,修改hosts: +192.168.1.10 www.test1.com +192.168.1.10 www.test2.com +192.168.1.10 www.test3.com + + 2. 跟上面一样,建立虚拟主机存放网页的根目录 +/www/test1/1.html +/www/test2/2.html +/www/test3/3.html + + 3. 在httpd.conf中将附加配置文件httpd-vhosts.conf包含进来,接着在httpd-vhosts.conf中写入如下配置: + + +  为了使用基于域名的虚拟主机,必须指定服务器IP地址(和可能的端口)来使主机接受请求。可以用NameVirtualHost指令来进行配置。 如果服务器上所有的IP地址都会用到, 你可以用*作为NameVirtualHost的参数。在NameVirtualHost指令中指明IP地址并不会使服务器自动侦听那个IP地址。 这里设定的IP地址必须对应服务器上的一个网络接口。 + +  下一步就是为你建立的每个虚拟主机设定配置块,的参数与NameVirtualHost指令的参数是一样的。每个定义块中,至少都会有一个ServerName指令来指定伺服哪个主机和一个DocumentRoot指令来说明这个主机的内容存在于文件系统的什么地方。 + +  如果在现有的web服务器上增加虚拟主机,必须也为现存的主机建造一个定义块。其中ServerName和DocumentRoot所包含的内容应该与全局的保持一致,且要放在配置文件的最前面,扮演默认主机的角色。 +复制代码 +NameVirtualHost *:80 + + +  ServerName * + +  DocumentRoot /www/ + + + + + +  ServerName www.test1.com + +  DocumentRoot /www/test1/ + +   + +    Options Indexes FollowSymLinks + +    AllowOverride None + +    Order allow,deny + +    Allow from all + +   + + + + + + + +  ServerName www.test2.com + +  DocumentRoot /www/test2/ + +   + +    Options Indexes FollowSymLinks + +    AllowOverride None + +    Order allow,deny + +    Allow from all + +   + + + + +  ServerName www.test3.com + +  DocumentRoot /www/test3/ + +   + +    Options Indexes FollowSymLinks + +    AllowOverride None + +    Order allow,deny + +    Allow from all + +   + +复制代码 + + 4. 大功告成,测试下每个虚拟主机,分别访问www.test1.com、www.test2.com、www.test3.com + + + +三、基于端口 +1. 修改配置文件 + +  将原来的 + +    Listen 80 + 改为 +   Listen 80 +    Listen 8080 + + +2. 更改虚拟主机设置: +复制代码 + + DocumentRoot /var/www/test1/ + ServerName www.test1.com + + + + DocumentRoot /var/www/test2 + ServerName www.test2.com + + + + + +# mod_wsgi + + +可以直接参考官网 [modwsgi.readthedocs.io](http://modwsgi.readthedocs.io/en/develop/index.html#) + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-05-26-jquery-datatables_init.md b/_drafts/2016-05-26-jquery-datatables_init.md new file mode 100644 index 0000000..63b92bb --- /dev/null +++ b/_drafts/2016-05-26-jquery-datatables_init.md @@ -0,0 +1,33 @@ +--- +Date: October 19, 2013 +title: jQuery DataTables +layout: post +comments: true +language: chinese +category: [webserver] +--- + + + + + + +首先是官网中的最简单示例,该示例将数据都保存在了表中,只需要在 javascript 中加载即可,可以直接查看本地的示例 [Zero configuration](/reference/webserver/jquery/datatables/examples1.html) 。 + +{% highlight javascript %} +$(document).ready(function() { + $('#example').dataTable(); +} ); +{% endhighlight %} + +[Zero configuration](https://datatables.net/examples/basic_init/zero_configuration.html) + + + +# python flask + +/reference/webserver/jquery/datatables/python_flask.py + + +{% highlight c %} +{% endhighlight %} diff --git a/_drafts/2016-06-07-mysql-proxy_init.md b/_drafts/2016-06-07-mysql-proxy_init.md new file mode 100644 index 0000000..6c4a501 --- /dev/null +++ b/_drafts/2016-06-07-mysql-proxy_init.md @@ -0,0 +1,209 @@ +--- +title: MySQL 代理设置 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,杂项 +description: 简单记录下 MySQL 常见的一些操作。 +--- + + + +## 简介 + +MySQL Proxy 是一个处于客户端和服务端的程序,可以监控、分析、修改数据传输,从而可以达到:负载均衡、读写分离、查询过滤等等。 + +简单来说,这就是一个连接池,对应用来说,MySQL Proxy 完全透明,应用只需要连接到 MySQL Proxy 的监听端口即可;此时,可能会成为单点,不过可以启动多个 Proxy,然后在链接池中配置。 + + +## 源码编译 + +MySQL Proxy 是基于 libevent 的,而且使用了 glib 库,所以,在编译源码时需要安装如下的包。 + +{% highlight text %} +# yum install libevent-devel glib-devel +{% endhighlight %} + +> glibc、libc、glib 的区别。 +> +> 其中,前两者都是 Linux 下的 C 函数库,libc 是 Linux 下的 ANSI C 函数库;glibc 是 Linux 下的 GUN C 函数库。glibc 后来逐渐成为了 Linux 的标准 C 库,而原来的标准 C 库 libc 不再被维护,可以直接通过 /lib/libc.so.6 或者 ldd \-\-version 命令查看。 +> +> 另外,glibc 和 glib 实际上没有太大关系,glib 是一个通用库,包括了常用的一些数据类型、宏定义、类型转换等等,通常在 Unix-Like 系统上使用,例如 gtk+ 和 gnome 都使用了该库, + +MySQL-5.7 之后头文件中的 ```CLIENT_SECURE_CONNECTION``` 宏定义,修改为 ```CLIENT_RESERVED2```,只需要将源码中出现的上述宏替换掉即可。 + +在源码的 lib 目录下,有常用的 lua 脚本,包括了读写分离(rw-spliting.lua)、管理脚本()等。 + +{% highlight text %} +cat << EOF > /tmp/mysql-proxy.conf +[mysql-proxy] +daemon=true # 以后台守护进程方式启动 +log-level=debug # 日志级别,可选为debug、info +proxy-address=0.0.0.0:4040 # 指定mysql-proxy的监听地址 +proxy-backend-addresses=127.1:3307 # 设置后台主服务器 +proxy-read-only-backend-addresses=127.1:3308 # 设置后台从服务器 +proxy-lua-script=/tmp/rw-splitting.lua # 设置读写分离脚本路径 + +admin-address=127.1:4041 # mysql-proxy管理地址,需要加载admin插件 +admin-username=admin # 登录管理地址用户 +admin-password=admin # 管理用户密码 +admin-lua-script=/tmp/admin.lua # 管理后台lua脚本路径 + +keepalive=true #当进程故障后自动重启 +log-file=/tmp/mysql-proxy.log #设置日志文件路径 +basedir=/usr/local/mysql-proxy #设置mysql-proxy的家目录 + + +{% endhighlight %} + + + +http://blog.csdn.net/wudongxu/article/details/7237830 + +http://ourmysql.com/archives/159 + +http://www.longlong.asia/2015/02/07/mysql-proxy.html + +http://www.nxops.cn/post/73 + +https://segmentfault.com/q/1010000000460861 + +http://www.cnblogs.com/yyhh/p/5084844.html + +http://www.cnblogs.com/tae44/p/4705078.html + +http://www.onexsoft.com/zh/oneproxy-take-fisrt-step.html + +http://m.gzhphb.com/article/15/152520.html + + +{% highlight text %} +main_cmdline() 真正的入口函数 + |-chassis_frontend_init_glib() + |-chassis_frontend_set_chassis_options() + | |-chassis_options_add() 添加选项,可以查看对应的变量 + | + |-chassis_frontend_write_pidfile() 创建PID文件 + |-chassis_frontend_log_plugin_versions() 打印已经加载的插件以及版本信息 + |-chassis_mainloop() + | + | ###FOR###BEGIN 循环创建N个线程 + |-chassis_event_thread_new() + | |-g_new0() + |-chassis_event_threads_init_thread() + | |-event_base_new() + | |-event_set() + | |-event_base_set() + | |-event_add() + |-chassis_event_threads_add() + | |-g_ptr_array_add() + | ###FOR###END + | + |-chassis_event_threads_start() 当大于1时会调用该函数 + |-chassis_event_thread_loop() + |-chassis_event_thread_set_event_base() + | |-g_private_set() + | + | ###WHILE###BEGIN + |-chassis_is_shutdown() while循环中判断是否需要退出 + |-event_base_loopexit() + |-event_base_dispatch() + | ###WHILE###END + + +chassis_frontend_load_plugins() +chassis_plugin_load() +{% endhighlight %} + + + + + + + + + +1. Libevent主要接口 +event_base_new:初始化一个event_base +event_set:设置event事件;即初始化struct event结构:类型,文件描述符,回调函数以及参数 +event_base_set:将上面设置的event绑定到指定的event_base;每个event都必须指定所属的event_base +event_add:注册event事件 +event_base_dispatch:阻塞在某个event_base上,监视绑定在它上面的所有fd + +2. mysql proxy启动 + +/u01/xiangzhong/mysql-proxy/bin/mysql-proxy --daemon + --pid-file=/u01/xiangzhong/mysql-proxy/proxy.pid + --plugins=proxy + --proxy-address=:4040 + --log-level=debug + --log-file=/u01/xiangzhong/mysql-proxy/proxy.log + --proxy-backend-addresses=127.0.0.1:4312 + --proxy-lua-script=/u01/xiangzhong/mysql-proxy/proxy.lua + --event-threads=5 + +这是我们的一个启动方式,可见它的启动参数非常的多;其中最重要的包括proxy-backend-addresses:表示mysql-proxy代理的后台服务器 + +proxy-lua-script:表示在proxy运行过程中它所hook的位置的callback函数,包括connect_server,read_handshake,read_auth,read_auth_result,read_query,read_query_result。相应的使用说明可见http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy-scripting.html。下面我们讲解一下mysql-proxy的启动过程。 + +3. Mysql-proxy启动过程 + +简单的说mysql-proxy是支持多线程的libevent模型。 +1. Mysql-proxy启动后进入main_cmdline,初始化各种结构,解析启动参数等; + +2.chassis_mainloop,它首先初始化主线程的相应结构; + +3.加载启动所有的plugins(mysql-proxy可由多个插件组成:admin,proxy,这里就是加载各个组件,即调用相应组件的apply_config函数,如对于proxy-plugin这个函数为network_mysqld_proxy_plugin_apply_config,这个函数又通过network_mysqld_proxy_connection_init(con)来初始化proxy的各个状态对应的处理函数【这些函数是以NETWORK_MYSQLD_PLUGIN_PROTO(***)开头的函数名,其中***值如proxy_init,proxy_read_auth】;以及加载lua script及设置相应的global tables,network_mysqld_lua_setup_global(chas->priv->sc->L, g);最后监视listen_sock->fd,event_set(&(listen_sock->event), listen_sock->fd, EV_READ|EV_PERSIST, network_mysqld_con_accept, con);【可见listen fd是由主线程监听的】) + +4.接下来创建其它的event_threads及启动(由启动参数指定个数),chassis_event_threads_init_thread用于初始化线程结构,包括创建每个线程的event_base,然后将chassis_event_threads_t->event_notify_fds[0]复制到每个线程的chassis_event_thread_t->notify_fd,即[0]是可读的,[1]是可写,当前线程负责写,所有线程监听[0]的可读事件event_set(&(event_thread->notify_fd_event), event_thread->notify_fd, EV_READ | EV_PERSIST, chassis_event_handle, event_thread); + +5.所有线程进入主循环chassis_event_thread_loop,然后调用event_base_dispatch,监听他们自己的event_base + +也就是说此时主线程监听两个fd:listen fd,chassis_event_thread_t->notify_fd;其它线程监听:chassis_event_thread_t->notify_fd。 + + + + + + + + + + + + + + +## 参考 + +源码可以直接从 [github mysql proxy](https://github.com/mysql/mysql-proxy) 上下载。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-06-29-openstack-paste-deploy_init.md b/_drafts/2016-06-29-openstack-paste-deploy_init.md new file mode 100644 index 0000000..7b8c03e --- /dev/null +++ b/_drafts/2016-06-29-openstack-paste-deploy_init.md @@ -0,0 +1,178 @@ +--- +Date: October 19, 2013 +title: OpenStack 基础之 Paste-Deploy +layout: post +comments: true +language: chinese +category: [network] +--- + +WSGI (Web Server Gateway Interface) 是 Python 应用程序和 Web 服务器之间定义的一套用来通讯的规范或者标准;按照这套规范,应用程序的实现以及修改将会非常简单。 + +Python paste 是一个 WSGI 工具包,在 WSGI 的基础上包装了几层,让应用管理和实现变得更加方便。 + + + +# 简介 + +通过 WSGI 标准,应用端只需要实现一个可以接收两个参数的可调用 + + + + + + +## 执行过程 + +在配置文件中,通过 use 用来定义使用的方法,确切来说就是调用在 /usr/lib/python2.7/site-packages 目录下的 paste/urlmap.py 模块。 + +另外,Python paste.deploy 中的 filter、filter_factory、app、app_factory 都是一个 callable object,也就是可调用的对象,只是其接口有些区别。 + +* app 接受 WSGI 规范的参数 (environ, start_response),这是由 paste 系统交给 app 的。app 需要响应 envrion 中的请求,准备好响应头和消息体,然后交给 start_response 处理,并返回响应消息体。 + +* filter 唯一参数是 (app),这是 WSGI 的 application 对象。filter 需要完成的工作是中间做些过滤操作,将 app 包装成另一个 app,并返回这个包装后的 app 。 + +* app_factory 接受的参数是一些关于 app 的配置信息 (global_conf, **kwargs),其中 global_conf 是在 ini 文件中 default section 中定义的一系列 key-value,而 **kwargs 是一些本地配置,在 ini 文件中相应 app:xxx section 中定义的一系列 key-value;其返回值是一个 app 对象。 + +* filter_factory 与 app_factory 接收的参数相似,只是其会返回一个 filter 对象。 + +## 配置文件 + +配置段设置。 + +* DEFAULT section,这个是全局的默认配置。 + +* composite section,用于在一个配置文件中定义多个应用入口,其中 use=egg:Paste#urlmap 表明使用 Paste 安装包中 urlmap,这是一个通用的应用,主要是将 URL 映射到具体的应用。 + +* pipeline section,由一系列的 filter 以及最后的 app 组成,前面是做一系列的过滤。 + +如下的是配置文件 foobar.ini 。 + +{% highlight text %} +[DEFAULT] +key=value + +[composite:foobar] +use = egg:Paste#urlmap +/ :root +/double :double + +[pipeline:root] +pipeline = logrequest showversion + +[pipeline:double] +pipeline = logrequest calculate + +[filter:logrequest] +paste.filter_factory = foobar:LogFilter.factory + +[app:showversion] +version = 1.0.0 +paste.app_factory = foobar:ShowVersion.factory + +[app:calculate] +description = Just double it +paste.app_factory = foobar:Calculator.factory +{% endhighlight %} + + +## 程序示例 + +如下是 foobar.py 文件,其中 factory 实现的函数可以是 classmethod 或者 staticmethod,两者都会在加载类的时候加载,也就是说其中的 print 只会在初始化的时候执行。 + +另外,需要注意的是 loadapp() 使用的是绝对路径,可以使用 os.path.abspath() 获得。 + +{% highlight python %} +import os +import webob +from webob import Request +from webob import Response +from paste.deploy import loadapp +from wsgiref.simple_server import make_server + +#Filter +class LogFilter(): + def __init__(self, app): + self.app = app + def __call__(self, environ, start_response): + print "filter:LogFilter is called." + return self.app(environ, start_response) + @classmethod + def factory(cls, global_conf, **kwargs): + print "in LogFilter.factory", global_conf, kwargs + return LogFilter + +# Application +class ShowVersion(): + def __init__(self, version): + self.version = version + def __call__(self, environ, start_response): + start_response("200 OK",[("Content-type", "text/plain")]) + return ["FOOBAR: Version = %s\r\n" % self.version, ] + @staticmethod + def factory(global_conf, **kwargs): + print "in ShowVersion.factory", global_conf, kwargs + return ShowVersion(kwargs["version"]) + +# Application +class Calculator(): + def __call__(self, environ, start_response): + req = Request(environ) + #print req.GET # Just for test + + res = Response() + res.status = "200 OK" + res.content_type = "text/plain" + + # get arguments + name = req.GET.get("name", None) + value = req.GET.get("arg", None) + + res.text = "Hi %s, RESULT = %d\r\n" % (name, 2*int(value)) + return res(environ, start_response) + + @classmethod + def factory(cls,global_conf,**kwargs): + print "in Calculator.factory", global_conf, kwargs + return Calculator() + +if __name__ == '__main__': + wsgi_app = loadapp("config:%s" % os.path.abspath("foobar.ini"), "foobar") + server = make_server('localhost', 8080, wsgi_app) + server.serve_forever() +{% endhighlight %} + +接下来可以直接通过如下方式进行测试。 + +{% highlight text %} +$ curl http://127.0.0.1:8080/ +FOOBAR: Version = 1.0.0 + +$ curl 'http://127.1:8080/double?name=foo-bar&arg=2' +Hi foo-bar, RESULT = 4 +{% endhighlight %} + +假设 pipeline 的顺序是 filter1, filter2, filter3,那么 app 会依次调用 filter1.\_\_call\_\_(env, start_response) 被首先调用,如果条件通过,则继续执行 filter2.\_\_call\_\_(env, start_response) ... ... + + + + + + +# 参考 + +[Paste Deployment](http://pythonpaste.org/deploy/) + +[Keystone, the OpenStack Identity Service](http://docs.openstack.org/developer/keystone/) + +[Understanding OpenStack Authentication: Keystone PKI](https://www.mirantis.com/blog/understanding-openstack-authentication-keystone-pki/) + +[理解 Keystone 的四种 Token](http://www.openstack.cn/?p=5120) + +[keystone源码分析(一)——Paste Deploy的应用](http://www.cnblogs.com/Security-Darren/p/4058148.html) + +[keystoneclient分析](http://www.cinlk.com/2015/07/26/keystoneclient/) + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-07-09-mysql-innodb-corruption-repair-guide_init.md b/_drafts/2016-07-09-mysql-innodb-corruption-repair-guide_init.md new file mode 100644 index 0000000..15b9d70 --- /dev/null +++ b/_drafts/2016-07-09-mysql-innodb-corruption-repair-guide_init.md @@ -0,0 +1,126 @@ +--- +title: InnoDB 故障修复 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,innodb,crash recovery,崩溃恢复 +description: 如果 InnoDB 没有正常关闭,会在服务器启动的时候执行崩溃恢复 (Crash Recovery),这一流程比较复杂,涉及到了 redo log、undo log 甚至包括了 binlog 。在此简单介绍下 InnoDB 崩溃恢复的流程。 +--- + + + + +## 崩溃恢复 + +第一步操作 +停止、备份、重启 + +1. 停止 +停止MySQL服务,如果已经下线或者崩溃可以忽略,目的是要冻结数据和表文件的当前状态保证没有新数据写入,可以直接进行文件操作,不需要关心是否会导致数据不一致或者数据丢失等。 +/etc/init.d/mysqld stop + +2. 备份 +备份整个数据目录,也可以只备份数据和日志文件。 +mkdir /data/innodb.bak +cd /var/lib/mysql +dd if=ibdata1 of=ibdata1.bak conv=noerror +cp -p ./ibdata* /data/innodb.bak/ +cp -p ./ib_log* /data/innodb.bak/ +里面需要注意的是,对于ibdata1分别使用了dd和cp进行复制,这是由于两者复制方式不同,dd是直接复制原始文件,而cp则复制文件内容到一个新的文件;可能这个不是必须的,但至少不是一个坏习惯。 + +如果没有数据备份,也可以直接备份整个数据目录;当然,这一步骤会比较耗时,对于紧急情况不太实际,如果这不可行,至少数据文件和InnoDB数据库目录应该提供一些能回退的数据: +cp -Rp /var/lib/mysql{,.orig} + +3. 备份InnoDB数据库文件 +如上所述,如果没有备份完整的MySQL数据目录,最好还是备份下InnoDB对应的目录,如果不确认哪些目录下含有InnoDB表,可以直接使用如下命令。 +DATADIR=/var/lib/mysql; find $DATADIR -type f -name *.ibd | awk -F/ '{print $(NF-1)}' | sort | uniq | xargs -I {} cp -Rp $DATADIR/{} /root/innodb.bak + +4. 重启MySQL服务 +如果启动不会导致崩溃的话,可以启动然后通过mysqldump备份数据。 +/etc/init.d/mysqld start +mysqldump --single-transaction -AER > /root/dump_wtrans.sql +mysqldump -AER > /root/dump.sql +备份之后,一定要检查是否可用,有可能会由于部分情况导致数据没有备份下来。 + +5. 无法启动 +如果无法启动或者启动后无法备份,除了希望尽快上线之外,启动服务的目的之一是尽可能的恢复数据,减小数据丢失。实际上,InnoDB为了满足ACID属性,保持数据一致性,如果遇到数据的任何问题,它几乎总是使MySQL崩溃以防止进一步损坏数据。 + +可以通过如下方式修改innodb_force_recovery参数: +mode=1; sed -i "/^\[mysqld\]/{N;s/$/\ninnodb_force_recovery=$mode/}" /etc/my.cnf +然后,一旦你准备把你的服务器返回到默认模式,你可以通过以下命令删除innodb_force_recovery行: +sed -i '/innodb_force_recovery/d' /etc/my.cnf + +Mode 1当遇到损坏页时,不使 MySQL 崩溃 +Mode 2不运行后台操作 +Mode 3不会尝试回滚事务 +Mode 4不计算统计数据或应用存储/缓冲的变化 +Mode 5在启动过程中不查看撤消日志 +Mode 6在启动时不从重做日志(ib_logfiles)前滚 +   +第二步 判断问题所在 + +1. 检查日志 +如果怀疑InnoDB表或数据库损坏,可能是因为数据页损坏、数据不存在、MySQL服务无法启动等,对于任何一种情况,你要首先查看的是MySQL错误日志。 +tail -200 /var/lib/mysql/`hostname`.err +通常可以大致确认服务崩溃的原因,在此仅讨论三种比较常见的情况:A)页损坏;B)LSN;C)数据字典。 + +1.A 页损坏 +InnoDB: Database page corruption on disk or a failed +InnoDB: file read of page 515891. +通常会包含了相关的一些信息,例如通过调用堆栈确认是在什么场景下触发的异常、崩溃时的环境参数,重点查看下是那个页损坏了,当然也有可能只是由于某些原因无法读取而已。 + +当然,崩溃后并不一定意味着真正有页损坏,事实上,在某些情况下,这可能只是操作系统的文件缓存被损坏;所以,建议在创建备份后,在无任何进一步操作之前,可以通过如下的工具检查是否有页损坏,或者直接重启你的计算机看下是否有异常。 +#!/bin/bash +for i in $( ls /var/lib/mysql/*/*.ibd); do +    innochecksum $i +done +innochecksum工具会查看表空间文件中的页,计算每页的校验值;然后,将计算值与存储的校验相比,如果两个值不同,通常意味着页被损坏,则报错。 +如果MySQL是在线且可访问的,你可以使用CHECK TABLE命令。 + +2.B LSN/时间异常 +120901  9:43:55  InnoDB: Error: page 70944 log sequence number 8 1483471899 +InnoDB: is in the future! Current system log sequence number 5 612394935. +InnoDB: Your database may be corrupt or you may have copied the InnoDB +InnoDB: tablespace but not the InnoDB log files. +在InnoDB上每次修改操作,都会生成一个redo记录,然后保存在ib_logfileN文件中,这些记录会按顺序写入文件,如果写满则重新开始;所有这些记录都有一个相关的LSN,通常也就是日志文件的偏移量。 + +?????? +此外,当一个数据库被修改,在该数据库中的特定页面也得到一个相关LSN。两者之间,这些LSN被一起检查,确保操作以正确的顺序执行。LSN本身基本上是一个到日志文件的偏移,且存储在数据库页头中的LSN告诉InnoDB有多少日志需要被刷。 +在过程中,无论是意外重启,内存问题,文件系统损坏,复制问题,手动更改为InnoDB的文件或其他,这些LSN不再“同步”。无论是否使你的服务器崩溃,这应该被当作合理损坏,通常你需要解决它。 + +2.C 数据字典错误 +[ERROR] Table ./database/table has no primary key in InnoDB data dictionary, but has one in MySQL! +InnoDB: Error: table ‘database/table’ +InnoDB: in InnoDB data dictionary has tablespace id 423, +InnoDB: but tablespace with that id or name does not exi st. Have +InnoDB: you deleted or moved .ibd files +?[ERROR] Cannot find or open table database/table from +the internal data dictionary of InnoDB though the .fr m file for the +table exists. Maybe you have deleted and recreated InnoDB data +files but have forgotten to delete the corresponding .frm file s +of InnoDB tables, or you have moved .frm files to another datab ase +?or, the table contains indexes that this version of the engine +doesn’t support. +InnoDB的数据字典存在于系统表空间(Space0),保存在ibdata1文件中,储存了包括表、列、索引的元数据信息;与每个InnoDB表的*.frm文件有很多相同信息。如果由于某些原因导致ibdata1文件被损坏、修改、移动或或替换,就可能导致无法通过数据字典查看数据。 + +???? +如果你看过之前的错误描述,你应该知道在ibdata1中(或以其他方式命名)文件中的数据与在单个表空间/.ibd / .frm文件的数据之间有明显的关联。当该关联丢失或损坏,可能会发生不好的情况。所以这像这样的数据字典的错误出现,最常见的原因是有些文件被手动移动或修改。它通常归结为:“数据字典预计这一文件或表空间在这里,但它不在!”,或“.ibd / .frm文件预计此项目在数据字典中,但它不在! “。再次记住,数据字典存储在ibdata文件中,在大多数环境中,就是MySQL数据目录中的ibdata1。 +  + +## 参考   + +[MySQL InnoDB Corruption Repair Guide](https://forums.cpanel.net/threads/innodb-corruption-repair-guide.418722/) 或者 [本地文档](/reference/databases/mysql/InnoDB Corruption Repair Guide.mht) 。 + +[xxxx](http://www.askmaclean.com/archives/mysql-recover-innodb.html) + + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-07-11-openstack-glance_init.md b/_drafts/2016-07-11-openstack-glance_init.md new file mode 100644 index 0000000..5f07ea1 --- /dev/null +++ b/_drafts/2016-07-11-openstack-glance_init.md @@ -0,0 +1,166 @@ +--- +Date: October 19, 2013 +title: OpenStack Keystone 服务 +layout: post +comments: true +language: chinese +category: [network] +--- + + + + + + + + + + + + + +业务端口:5000 +管理端口:35357 +2、业务API 测试: + +#### + + +获取api扩展: +curl http://0.0.0.0:35357/v2.0/extensions | python -mjson.tool + +#### 获取版本号 + +{% highlight text %} +----- 获取管理API的版本号 +$ curl -s http://127.1:5000/ | python -mjson.tool +$ curl -s http://127.1:5000/v2.0/ | python -mjson.tool + +----- 获取管理API的版本号 +$ curl -s http://127.1:35357/ | python -mjson.tool +$ curl -s http://127.1:35357/v2.0/ | python -mjson.tool +{% endhighlight %} + +#### 获取 API 扩展 + +{% highlight text %} +$ curl -s http://127.1:5000/v2.0/extensions | python -mjson.tool +{% endhighlight %} + +#### 用普通用户登录 + +{% highlight text %} +$ curl -s -X POST -d '{"auth": {"passwordCredentials":{"username": "admin", "password": "123"}}}' \ + -H "Content-type: application/json" http://127.1:5000/v2.0/tokens | python -mjson.tool +{ + "access": { + ... ... + "token": { + "id": "0d71cf53ad2d4bf5aa49296ffdd12a2f", + }, + ... ... + } +} + +{% endhighlight %} + + +#### 查看自己的租户 + +{% highlight text %} +$ curl -s -H "X-Auth-Token:0d71cf53ad2d4bf5aa49296ffdd12a2f" http://127.1:5000/v2.0/tenants | python -mjson.tool +{ + "tenants": [ + { + "description": "Admin Project", + "enabled": true, + "id": "f3f0c9353afc4a1ab74d143f85dfabe4", + "name": "admin" + } + ], + "tenants_links": [] +} + +{% endhighlight %} + + +#### 用角色admin登录 + +{% highlight text %} +$ curl -s -X POST -d '{"auth": {"tenantId": "f3f0c9353afc4a1ab74d143f85dfabe4", + "passwordCredentials":{"username": "admin", "password": "123"}}}' \ + -H "Content-type: application/json" http://127.1:35357/v2.0/tokens | python -mjson.tool +{ + { + ... ... + "token": { + "id": "0db5896265f1446c99fc79c1f99da121", + ... ... + }, + ... ... + } +} + +{% endhighlight %} + +#### 校验token的有效,并返回token的信息 + +{% highlight text %} +$ curl -s -H "X-Auth-Token: 0db5896265f1446c99fc79c1f99da121" \ + http://127.1:35357/v2.0/tokens/0db5896265f1446c99fc79c1f99da121 | python -mjson.tool +{% endhighlight %} + +#### 使用HEAD校验,如果返回码是20X,表示token有效 + +{% highlight text %} +$ curl -I -H "X-Auth-Token: 0db5896265f1446c99fc79c1f99da121" \ + http://127.1:35357/v2.0/tokens/0db5896265f1446c99fc79c1f99da121 +{% endhighlight %} + +#### 查看endpoints + +{% highlight text %} +$ curl -H "X-Auth-Token: 0db5896265f1446c99fc79c1f99da121" \ + http://127.1:35357/v2.0/tokens/0db5896265f1446c99fc79c1f99da121/endpoints +{% endhighlight %} + +#### 返回租户 + +{% highlight text %} +$ curl -H "X-Auth-Token: 0db5896265f1446c99fc79c1f99da121" \ + http://0.0.0.0:35357/v2.0/tenants|python -mjson.tool +{% endhighlight %} + +#### 返回某个租户 + +{% highlight text %} +$ curl -H "X-Auth-Token: 0db5896265f1446c99fc79c1f99da121" \ + http://0.0.0.0:35357/v2.0/tenants/6a524dbe23dd4e4ab672cd163c85a27d |python -mjson.tool +{% endhighlight %} + +#### 返回用户 + +curl -H “X-Auth-Token:5a10b008add4435f8473d2b11d3ba8a8” http://0.0.0.0:35357/v2.0/users|python -mjson.tool + +#### 返回某个用户 + +curl -H “X-Auth-Token:5a10b008add4435f8473d2b11d3ba8a8” http://0.0.0.0:35357/v2.0/users/3ff8fbca9794436c996d8c6e41427530|python -mjson.tool + +#### 返回某个租户上,用户授予的角色 + +curl -H “X-Auth-Token:5a10b008add4435f8473d2b11d3ba8a8” http://0.0.0.0:35357/v2.0/tenants/6a524dbe23dd4e4ab672cd163c85a27d/users/3ff8fbca9794436c996d8c6e41427530/roles |python -mjson.tool + +返回某个用户的角色:(出错,没有实现,参见 https://bugs.launchpad.net/keystone/+bug/933565) +curl -H “X-Auth-Token:5a10b008add4435f8473d2b11d3ba8a8” http://0.0.0.0:35357/v2.0/users/3ff8fbca9794436c996d8c6e41427530/roles + + + + + + + +[Keystone, the OpenStack Identity Service](http://docs.openstack.org/developer/keystone/) + +[Python bindings to the OpenStack Identity API (Keystone)](http://docs.openstack.org/developer/python-keystoneclient/) +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-07-14-openstack-introduce_init.md b/_drafts/2016-07-14-openstack-introduce_init.md new file mode 100644 index 0000000..9bdd087 --- /dev/null +++ b/_drafts/2016-07-14-openstack-introduce_init.md @@ -0,0 +1,655 @@ +--- +Date: October 19, 2013 +title: OpenStack 安装 +layout: post +comments: true +language: chinese +category: [network] +--- + + + +## 准备环境 + +### 安装 OpenStack 包 + +{% highlight text %} +----- 启用OpenStack数据源 +# yum install centos-release-openstack-liberty + +----- 更新软件包,部分依赖上述软件源的包会更新 +# yum upgrade + +----- 安装OpenStack客户端 +# yum install python-openstackclient + +----- CentOS默认启用SELinux,安装如下包实现对OpenStack服务的安全策略进行自动管理 +# yum install openstack-selinux +{% endhighlight %} + +### 安装 SQL 数据库 + +大多数 OpenStack 服务使用 SQL 数据库来存储信息,安装 SQL 数据库。 + +{% highlight text %} +----- 安装MySQL数据库 +# yum install mariadb mariadb-server MySQL-python + +----- 设置为开机启动,并启动MySQL服务 +# systemctl enable mariadb.service +# systemctl start mariadb.service +{% endhighlight %} + +### 安装 MQ + +OpenStack 使用 Message Queue 协调操作和各服务的状态信息,MQ 一般运行在控制节点上,支持 ZeroMQ、RabbitMQ、Qpid 等。 + +{% highlight text %} +----- 安装RabbitMQ +# yum install rabbitmq-server + +----- 设置为开机启动,并启动RabbitMQ服务 +# systemctl enable rabbitmq-server.service +# systemctl start rabbitmq-server.service + +----- 添加openstack用户 +# rabbitmqctl add_user openstack RABBIT_PASS +Creating user "openstack" ... + +----- 给openstack用户配置写和读权限 +# rabbitmqctl set_permissions openstack ".*" ".*" ".*" +Setting permissions for user "openstack" in vhost "/" ... +{% endhighlight %} + +如果出现 ERROR: epmd error for host XXXXX: timeout (time out),主要是由于主机名和 IP 不匹配导致,直接在 /etc/hosts 中添加 127.0.0.1 XXXXX 即可。 + + +## Keystone,身份认证服务 + +这一章描述如何在控制节点上安装和配置 OpenStack 身份认证服务,代码名称 keystone。出于性能原因,这个配置部署 Apache HTTP 服务处理查询并使用 Memcached 存储 tokens 而不用 SQL 数据库。 + + +配置 OpenStack 身份认证服务前,必须创建一个数据库和管理员令牌。 + +{% highlight text %} +$ mysql -u root -p +CREATE DATABASE keystone; +GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'KEYSTONE_DBPASS'; +GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'KEYSTONE_DBPASS'; +{% endhighlight %} + +生成一个随机值在初始的配置中作为管理员的令牌。 + +{% highlight text %} +$ openssl rand -hex 10 +42813d8157799f6b892d +{% endhighlight %} + +安装 Keystone 服务,使用 Nginx 提供服务。 + +{% highlight text %} +# yum install openstack-keystone httpd mod_wsgi +{% endhighlight %} + +编辑文件 /etc/keystone/keystone.conf 并设置如下的值。 + +{% highlight text %} +[DEFAULT] +# 定义管理员token初始值,也就是上述设置的随机值 +admin_token = 42813d8157799f6b892d +# 启用详细日志 +verbose = true +[database] +# 设置数据库的连接方式 +connection = mysql://keystone:KEYSTONE_DBPASS@127.1/keystone +[memcache] +# 配置Memcached服务,暂时没有使用,直接访问数据库 +#servers = localhost:11211 +[token] +# 配置UUID token provider,驱动暂时使用sql,如果是Memcached驱动则为(memcache) +provider = uuid +driver = sql +[revoke] +# 配置SQL回滚驱动 +driver = sql +{% endhighlight %} + +初始化身份认证服务的数据库,在此使用的是 MySQL 数据库,可以通过如下两个命令执行,其功能相同,是否执行成功可以查看 /var/log/keystone/keystone.log 中的内容。 + +{% highlight text %} +# keystone-manage db_sync +# su -s /bin/sh -c "keystone-manage db_sync" keystone +{% endhighlight %} + +此时,在 MySQL 中的 keystone 库中,会创建一堆的表。 + +其中需要 admin_token 来访问 keystone 的服务,后面也可以通过 keystone-client 来注册新的 token,默认是 ADMIN,可以把它添加到系统环境里去,如下,分别配置 TOKEN、端点 URL、认证 API 版本。 + +{% highlight text %} +export OS_TOKEN=42813d8157799f6b892d +export OS_URL=http://127.0.0.1:35357/v3 +export OS_IDENTITY_API_VERSION=3 +{% endhighlight %} + +配置 Apache HTTP 服务器,用下面的内容创建文件 /etc/httpd/conf.d/wsgi-keystone.conf。 + +{% highlight text %} +Listen 5000 +Listen 35357 + + + WSGIDaemonProcess keystone-public processes=5 threads=1 user=keystone group=keystone display-name=%{GROUP} + WSGIProcessGroup keystone-public + WSGIScriptAlias / /usr/bin/keystone-wsgi-public + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + = 2.4> + ErrorLogFormat "%{cu}t %M" + + ErrorLog /var/log/httpd/keystone-error.log + CustomLog /var/log/httpd/keystone-access.log combined + + + = 2.4> + Require all granted + + + Order allow,deny + Allow from all + + + + + + WSGIDaemonProcess keystone-admin processes=5 threads=1 user=keystone group=keystone display-name=%{GROUP} + WSGIProcessGroup keystone-admin + WSGIScriptAlias / /usr/bin/keystone-wsgi-admin + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + = 2.4> + ErrorLogFormat "%{cu}t %M" + + ErrorLog /var/log/httpd/keystone-error.log + CustomLog /var/log/httpd/keystone-access.log combined + + + = 2.4> + Require all granted + + + Order allow,deny + Allow from all + + + +{% endhighlight %} + +启动 Apache HTTP 服务并配置其随系统启动: + +{% highlight text %} +# systemctl enable httpd.service +# systemctl start httpd.service +{% endhighlight %} + +### 创建服务实体和API端点 + +为身份认证服务创建服务实体。 + +{% highlight text %} +# openstack service create --name keystone --description "OpenStack Identity" identity ++-------------+----------------------------------+ +| Field | Value | ++-------------+----------------------------------+ +| description | OpenStack Identity | +| enabled | True | +| id | e81ff0c213864ad19509f7fa3c711595 | +| name | keystone | +| type | identity | ++-------------+----------------------------------+ +{% endhighlight %} + +创建认证服务的 API 端点。 + +{% highlight text %} +# openstack endpoint create --region RegionOne identity public http://127.0.0.1:5000/v2.0 ++--------------+----------------------------------+ +| Field | Value | ++--------------+----------------------------------+ +| enabled | True | +| id | c6d75d31a7354c0e8f37e4b764a1d960 | +| interface | public | +| region | RegionOne | +| region_id | RegionOne | +| service_id | e81ff0c213864ad19509f7fa3c711595 | +| service_name | keystone | +| service_type | identity | +| url | http://127.0.0.1:5000/v2.0 | ++--------------+----------------------------------+ + +# openstack endpoint create --region RegionOne identity internal http://127.0.0.1:5000/v2.0 ++--------------+----------------------------------+ +| Field | Value | ++--------------+----------------------------------+ +| enabled | True | +| id | 23a675ff529e4d38a7ea73e487adaea5 | +| interface | internal | +| region | RegionOne | +| region_id | RegionOne | +| service_id | e81ff0c213864ad19509f7fa3c711595 | +| service_name | keystone | +| service_type | identity | +| url | http://controller:5000/v2.0 | ++--------------+----------------------------------+ + +# openstack endpoint create --region RegionOne identity admin http://127.0.0.1:35357/v2.0 ++--------------+----------------------------------+ +| Field | Value | ++--------------+----------------------------------+ +| enabled | True | +| id | 5fb0ad9d3c014c35bebcc937dd24889f | +| interface | admin | +| region | RegionOne | +| region_id | RegionOne | +| service_id | e81ff0c213864ad19509f7fa3c711595 | +| service_name | keystone | +| service_type | identity | +| url | http://127.0.0.1:35357/v2.0 | ++--------------+----------------------------------+ +{% endhighlight %} + +### 创建项目、用户和角色 + + +{% highlight text %} +----- 创建 admin 项目 +# openstack project create --domain default --description "Admin Project" admin ++-------------+----------------------------------+ +| Field | Value | ++-------------+----------------------------------+ +| description | Admin Project | +| domain_id | default | +| enabled | True | +| id | f3f0c9353afc4a1ab74d143f85dfabe4 | +| is_domain | False | +| name | admin | +| parent_id | None | ++-------------+----------------------------------+ + +----- 创建 admin 用户 +# openstack user create --domain default --password-prompt admin +User Password: 123 +Repeat User Password: 123 ++-----------+----------------------------------+ +| Field | Value | ++-----------+----------------------------------+ +| domain_id | default | +| enabled | True | +| id | 7022c60d8e8848e0bd48f3f2e420ffb7 | +| name | admin | ++-----------+----------------------------------+ + +----- 创建 admin 角色 +# openstack role create admin ++-------+----------------------------------+ +| Field | Value | ++-------+----------------------------------+ +| id | cb647e95f1694bb283f33cb55d1f5201 | +| name | admin | ++-------+----------------------------------+ + +----- 添加admin角色到admin项目和用户上 +# openstack role add --project admin --user admin admin + +----- 创建service项目 +# openstack project create --domain default --description "Service Project" service ++-------------+----------------------------------+ +| Field | Value | ++-------------+----------------------------------+ +| description | Service Project | +| domain_id | default | +| enabled | True | +| id | b810f722e8f340229c90e79f93254a02 | +| is_domain | False | +| name | service | +| parent_id | None | ++-------------+----------------------------------+ + +----- 创建demo项目 +# openstack project create --domain default --description "Demo Project" demo ++-------------+----------------------------------+ +| Field | Value | ++-------------+----------------------------------+ +| description | Demo Project | +| domain_id | default | +| enabled | True | +| id | 6423234435ee427aae04053ce1973418 | +| is_domain | False | +| name | demo | +| parent_id | None | ++-------------+----------------------------------+ + +----- 创建demo用户 +# openstack user create --domain default --password-prompt demo +User Password: 123 +Repeat User Password: 123 ++-----------+----------------------------------+ +| Field | Value | ++-----------+----------------------------------+ +| domain_id | default | +| enabled | True | +| id | 761444e971da4510990c13259c7ee4e3 | +| name | demo | ++-----------+----------------------------------+ + +----- 创建user角色 +# openstack role create user ++-------+----------------------------------+ +| Field | Value | ++-------+----------------------------------+ +| id | 4bfb5605bbfa4545a2027e9b3e346b3a | +| name | user | ++-------+----------------------------------+ + +----- 添加user角色到demo项目和用户 +# openstack role add --project demo --user demo user +{% endhighlight %} + + + +### 验证操作 + +在安装其他服务之前确认身份认证服务正常,首先关闭临时认证令牌机制。编辑 /usr/share/keystone/keystone-dist-paste.ini 文件,从 [pipeline:public_api],[pipeline:admin_api],[pipeline:api_v3] 部分删除 admin_token_auth 。 + +{% highlight text %} +----- 重置OS_TOKEN和OS_URL环境变量 +$ unset OS_TOKEN OS_URL + +----- 使用admin用户,请求认证令牌 +$ openstack --os-auth-url http://127.1:35357/v3 --os-project-domain-id default --os-user-domain-id default \ + --os-project-name admin --os-username admin --os-auth-type password token issue +Password: ++------------+----------------------------------+ +| Field | Value | ++------------+----------------------------------+ +| expires | 2016-08-03T15:40:27.899436Z | +| id | 558836819dbc40369d91157b0c286832 | +| project_id | f3f0c9353afc4a1ab74d143f85dfabe4 | +| user_id | 7022c60d8e8848e0bd48f3f2e420ffb7 | ++------------+----------------------------------+ + +----- 使用demo用户,请求认证令牌 +$ openstack --os-auth-url http://127.1:5000/v3 --os-project-domain-id default --os-user-domain-id default \ + --os-project-name demo --os-username demo --os-auth-type password token issue +Password: ++------------+----------------------------------+ +| Field | Value | ++------------+----------------------------------+ +| expires | 2016-08-03T15:43:19.799498Z | +| id | 39292a170d6b449c918e86590f84e985 | +| project_id | 6423234435ee427aae04053ce1973418 | +| user_id | 761444e971da4510990c13259c7ee4e3 | ++------------+----------------------------------+ +{% endhighlight %} + +### 创建OpenStack客户端环境脚本 + +创建 admin 和 demo 项目和用户创建客户端环境变量脚本,后面会经常使用。 + +{% highlight text %} +$ cat admin-openrc.sh +export OS_PROJECT_DOMAIN_ID=default +export OS_USER_DOMAIN_ID=default +export OS_PROJECT_NAME=admin +export OS_TENANT_NAME=admin +export OS_USERNAME=admin +export OS_PASSWORD=123 +export OS_AUTH_URL=http://127.1:35357/v3 +export OS_IDENTITY_API_VERSION=3 + +$ cat demo-openrc.sh +export OS_PROJECT_DOMAIN_ID=default +export OS_USER_DOMAIN_ID=default +export OS_PROJECT_NAME=demo +export OS_TENANT_NAME=demo +export OS_USERNAME=demo +export OS_PASSWORD=123 +export OS_AUTH_URL=http://127.1:5000/v3 +export OS_IDENTITY_API_VERSION=3 +{% endhighlight %} + +使用特定租户和用户运行客户端,你可以在运行之前简单地加载相关客户端脚本,例如:加载 admin-openrc.sh 文件来身份认证服务的环境变量位置和 admin 项目和用户证书: + +{% highlight text %} +$ source admin-openrc.sh + +----- 请求认证令牌 +$ openstack token issue ++------------+----------------------------------+ +| Field | Value | ++------------+----------------------------------+ +| expires | 2016-08-03T15:51:53.828491Z | +| id | 00813a839de04313a5e1004c8131c869 | +| project_id | f3f0c9353afc4a1ab74d143f85dfabe4 | +| user_id | 7022c60d8e8848e0bd48f3f2e420ffb7 | ++------------+----------------------------------+ +{% endhighlight %} + + +## Glance,镜像服务 + +允许用户发现、注册和恢复虚拟机镜像,提供了一个 REST API,允许查询虚拟机镜像的 metadata 并恢复一个实际的镜像。可以存储虚拟机镜像通过不同位置的镜像服务使其可用,就像 OpenStack 对象存储那样从简单的文件系统到对象存储系统。 + +在此使用 file 作为后端配置镜像服务,能够上传并存储在一个托管镜像服务的控制节点目录中,默认保存在 /var/lib/glance/images/ 目录下。 + + +{% highlight text %} +----- 创建一个数据库 +$ mysql -u root -p +CREATE DATABASE glance; +GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'GLANCE_DBPASS'; +GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'GLANCE_DBPASS'; + +----- 创建glance用户 +$ openstack user create --domain default --password-prompt glance +User Password: 123 +Repeat User Password: 123 ++-----------+----------------------------------+ +| Field | Value | ++-----------+----------------------------------+ +| domain_id | default | +| enabled | True | +| id | 403537aa9450454d824d6c1c6f8460ea | +| name | glance | ++-----------+----------------------------------+ + +----- 添加admin角色到glance用户和service项目上 +$ openstack role add --project service --user glance admin + +----- 创建glance服务实体 +$ openstack service create --name glance --description "OpenStack Image service" image ++-------------+----------------------------------+ +| Field | Value | ++-------------+----------------------------------+ +| description | OpenStack Image service | +| enabled | True | +| id | 9f36d1a4d41b42c892ee7fb86e11561a | +| name | glance | +| type | image | ++-------------+----------------------------------+ + +----- 创建镜像服务的API端点 +$ openstack endpoint create --region RegionOne image public http://127.1:9292 ++--------------+----------------------------------+ +| Field | Value | ++--------------+----------------------------------+ +| enabled | True | +| id | 63b4ee31dd7b4055a59f81ac2b1f5160 | +| interface | public | +| region | RegionOne | +| region_id | RegionOne | +| service_id | 9f36d1a4d41b42c892ee7fb86e11561a | +| service_name | glance | +| service_type | image | +| url | http://127.1:9292 | ++--------------+----------------------------------+ + +$ openstack endpoint create --region RegionOne image internal http://127.1:9292 ++--------------+----------------------------------+ +| Field | Value | ++--------------+----------------------------------+ +| enabled | True | +| id | 93925120486e451a9c1b413b4866cd94 | +| interface | internal | +| region | RegionOne | +| region_id | RegionOne | +| service_id | 9f36d1a4d41b42c892ee7fb86e11561a | +| service_name | glance | +| service_type | image | +| url | http://127.1:9292 | ++--------------+----------------------------------+ + +$ openstack endpoint create --region RegionOne image admin http://127.1:9292 ++--------------+----------------------------------+ +| Field | Value | ++--------------+----------------------------------+ +| enabled | True | +| id | 3b56485471e645c3b3613530015983c0 | +| interface | admin | +| region | RegionOne | +| region_id | RegionOne | +| service_id | 9f36d1a4d41b42c892ee7fb86e11561a | +| service_name | glance | +| service_type | image | +| url | http://127.1:9292 | ++--------------+----------------------------------+ +{% endhighlight %} + + +### 配置组件 + +{% highlight text %} +----- 安装软件包 +# yum install openstack-glance python-glance python-glanceclient +{% endhighlight %} + +编辑文件 /etc/glance/glance-api.conf 并完成如下动作。 + +{% highlight text %} +[DEFAULT] +# 禁止通知 +notification_driver = noop +verbose = True + +[database] +connection=mysql://glance:GLANCE_DBPASS@127.1/glance + +[keystone_authtoken] +auth_uri = http://127.1:5000 + +identity_uri = http://controller:35357 +admin_tenant_name = service +admin_user = glance +admin_password = 123 + +#auth_url = http://127.1:35357 +#auth_plugin = password +#project_domain_id = default +#user_domain_id = default +#project_name = service +#username = glance +#password = 123 + +[paste_deploy] +flavor = keystone + +[glance_store] +default_store = file +filesystem_store_datadir = /var/lib/glance/images/ +{% endhighlight %} + +编辑文件 /etc/glance/glance-registry.conf 并完成如下动作。 + +{% highlight text %} +[DEFAULT] +notification_driver = noop +verbose = True + +[database] +connection = mysql://glance:GLANCE_DBPASS@127.1/glance + +[keystone_authtoken] +auth_uri = http://127.1:5000/v2.0 + +identity_uri = http://controller:35357 +admin_tenant_name = service +admin_user = glance +admin_password = 123 + +#auth_url = http://127.1:35357 +#auth_plugin = password +#project_domain_id = default +#user_domain_id = default +#project_name = service +#username = glance +#password = 123 + +[paste_deploy] +flavor = keystone +{% endhighlight %} + +写入镜像服务数据库。 + + +{% highlight text %} +# glance-manage db_sync +{% endhighlight %} + + +完成安装,启动镜像服务、配置他们随机启动: +{% highlight text %} +# systemctl enable openstack-glance-api.service openstack-glance-registry.service +# systemctl start openstack-glance-api.service openstack-glance-registry.service +{% endhighlight %} + +在每个客户端脚本中,配置镜像服务客户端使用 2.0 的 API : + +{% highlight text %} +$ echo "export OS_IMAGE_API_VERSION=2" | tee -a admin-openrc.sh demo-openrc.sh +{% endhighlight %} + +获得 admin 凭证来获取只有管理员能执行命令的访问权限: + +{% highlight text %} +$ source admin-openrc.sh +{% endhighlight %} + +下载源镜像,一个很小的镜像,然后添加到 Glance 中: + +{% highlight text %} +$ wget http://download.cirros-cloud.net/0.3.4/cirros-0.3.4-x86_64-disk.img +$ glance image-create --name "cirros" \ + --file cirros-0.3.4-x86_64-disk.img \ + --disk-format qcow2 --container-format bare \ + --visibility public --progress +{% endhighlight %} + + + + + + +## 参考 + + +http://docs.openstack.org/ + + +http://docs.openstack.org/developer/python-openstackclient/command-list.html + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-01-mysql-multi-threaded-slaves_init.md b/_drafts/2016-08-01-mysql-multi-threaded-slaves_init.md new file mode 100644 index 0000000..0cf38ee --- /dev/null +++ b/_drafts/2016-08-01-mysql-multi-threaded-slaves_init.md @@ -0,0 +1,233 @@ +--- +title: MySQL 并行复制 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,replication,parallel,binlog,并行,复制 +description: 众所周知,MySQL 复制延迟是一直被诟病的问题之一,然而在 MySQL 5.7 版本已经支持 "真正" 的并行复制功能,官方称为 Enhanced Multi-Threaded Slave, MTS,因此复制延迟问题已经得到了极大的改进。结下来,详细看看 MySQL 的实现。 +--- + +众所周知,MySQL 复制延迟是一直被诟病的问题之一,然而在 MySQL 5.7 版本已经支持 "真正" 的并行复制功能,官方称为 Enhanced Multi-Threaded Slave, MTS,因此复制延迟问题已经得到了极大的改进。 + +结下来,详细看看 MySQL 的实现。 + + + +## 简介 + +在 MySQL 5.6 之前,备库上只有两个线程 (IO线程和SQL线程);IO 线程负责接收二进制日志 (更准确的说是二进制日志的 event),SQL 线程进行回放二进制日志。 + +在 MySQL 5.6 版本中,也支持所谓的并行复制,但是其并行只是基于库 (或者schema) 的;所以,只有当用户的 MySQL 数据库实例中存在多个库时,才会对于从机复制的速度有所帮助。 + +5.6 开启并行复制功能后,SQL 线程就变为了 coordinator 线程,该线程主要负责如下功能: + +* 若判断可以并行执行,那么选择 worker 线程执行事务的二进制日志; +* 若判断不可以并行执行 (如 DDL、事务跨 schema 操作),则等待所有 worker 线程执行完成之后,再执行当前的日志; + +这意味着 coordinator 线程并不是仅将日志发送给 worker 线程,自己也可以回放日志,但是所有可以并行的操作交付由 worker 线程完成。 + + + +### 5.7并行复制 + +MySQL 5.7 才可称为真正的并行复制:从库的回放与主库是一致的,即主库是怎么并行执行的备库上就怎样进行并行回放,不再有库的并行复制限制。 + +其并行复制的思路简单易懂,简言之:一个组提交的事务都是可以并行回放,因为这些事务都已进入到事务的 prepare 阶段,则说明事务之间没有任何冲突,否则就不可能提交。 + +> To provide parallel execution of transactions in the same schema, MariaDB 10.0 and MySQL 5.7 take advantage of the binary log group commit optimization. +> +> 主库依据 Group Commit 的并行性,在二进制日志中进行标记,备库利用主库提供的信息并行执行事务。 + +为了兼容 5.6 基于库的并行复制,5.7 引入了新变量 slave-parallel-type: + +* DATABASE:默认值,基于库的并行复制方式; +* LOGICAL_CLOCK:基于组提交的并行复制方式。 + +其并行复制可以通过如下参数配置: + +{% highlight text %} +slave_parallel_type=logical_clock + 默认database,也就是使用DB并行方式;或者logical_clock使用基于组提交的并行模式; +slave_parallel_workers=16 设置worker线程数 +binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 在master延时事务提交,增加group commit事务数 +{% endhighlight %} + +接下来看看,如何判断事务是否在一组中,因为原版的 MySQL 并没有提供这样的信息。 + +### 并行复制原理 + +在 MySQL 5.7 版本中,其设计方式是将组提交的信息存放在 GTID 中;对于没有开启 GTID 功能的用户,5.7 又引入了称之为 Anonymous_Gtid 的二进制日志 event 类型。 + +{% highlight text %} +----- 查看是否存在Event_Type为Anonymous_Gtid的事件 +mysql> SHOW BINLOG EVENTS IN 'mysql-bin.000001'; +{% endhighlight %} + +也就是说,即使 5.7 不开启 GTID,每个事务开始前也是会存在一个 Anonymous_Gtid,而这 GTID 中就存在着组提交的信息。 + +在上述命令中,通过 ```SHOW BINLOG EVENTS``` 查看时,并没有发现有关组提交的任何信息;但实际上可以通过 mysqlbinlog 工具查看组提交的内部信息。 + +{% highlight text %} +$ mysqlbinlog mysql-bin.0000006 | grep last_committed +#160720 22:13:41 server id 1 end_log_pos 259 CRC32 0x4ead9ad6 GTID last_committed=0 sequence_number=1 +#160720 22:13:41 server id 1 end_log_pos 1483 CRC32 0xdf94bc85 GTID last_committed=0 sequence_number=2 +#160720 22:13:41 server id 1 end_log_pos 2708 CRC32 0x0914697b GTID last_committed=0 sequence_number=3 +#160720 22:13:41 server id 1 end_log_pos 3934 CRC32 0xd9cb4a43 GTID last_committed=0 sequence_number=4 +#160720 22:13:41 server id 1 end_log_pos 5159 CRC32 0x06a6f531 GTID last_committed=0 sequence_number=5 +#160720 22:13:41 server id 1 end_log_pos 6386 CRC32 0xd6cae930 GTID last_committed=0 sequence_number=6 +#160720 22:13:41 server id 1 end_log_pos 7610 CRC32 0xa1ea531c GTID last_committed=6 sequence_number=7 +#160720 22:13:41 server id 1 end_log_pos 8834 CRC32 0x96864e6b GTID last_committed=6 sequence_number=8 +#160720 22:13:41 server id 1 end_log_pos 10057 CRC32 0x2de1ae55 GTID last_committed=6 sequence_number=9 +#160720 22:13:41 server id 1 end_log_pos 11280 CRC32 0x5eb13091 GTID last_committed=6 sequence_number=10 +#160720 22:13:41 server id 1 end_log_pos 12504 CRC32 0x16721011 GTID last_committed=6 sequence_number=11 +#160720 22:13:41 server id 1 end_log_pos 13727 CRC32 0xe2210ab6 GTID last_committed=6 sequence_number=12 +{% endhighlight %} + +与之前的 binlog 相比,日志中多了 last_committed 和 sequence_number,其中 last_committed 表示事务提交的时候,上次事务提交的编号,如果事务具有相同的 last_committed,表示这些事务都在一组内,可以进行并行的回放。 + +上述的 last_committed+sequence_number 就是所谓的 LOGICAL_CLOCK。 + + + +## 源码解析 + + + + + + +{% highlight cpp %} +class MYSQL_BIN_LOG: public TC_LOG +{ +... ... +public: + /* Committed transactions timestamp */ + Logical_clock max_committed_transaction; + /* "Prepared" transactions timestamp */ + Logical_clock transaction_counter; +... ... +} +{% endhighlight %} + +可以看到在类 MYSQL_BIN_LOG 中定义了两个 Logical_clock 变量: + +* max_committed_transaction
    上次组提交时的 logical_clock,也就是上述 binlog 中的 last_committed; +* transaction_counter
    当前组提交中各事务的 logcial_clock,也就是上述的 sequence_number 。 + + + + + + + +## 其它 + + +### 调优 + +master_info_repository + +开启MTS功能后,务必将参数master_info_repostitory设置为TABLE,这样性能可以有50%~80%的提升。这是因为并行复制开启后对于元master.info这个文件的更新将会大幅提升,资源的竞争也会变大。在之前InnoSQL的版本中,添加了参数来控制刷新master.info这个文件的频率,甚至可以不刷新这个文件。因为刷新这个文件是没有必要的,即根据master-info.log这个文件恢复本身就是不可靠的。在MySQL 5.7中,Inside君推荐将master_info_repository设置为TABLE,来减小这部分的开销。 + +slave_parallel_workers + +若将slave_parallel_workers设置为0,则MySQL 5.7退化为原单线程复制,但将slave_parallel_workers设置为1,则SQL线程功能转化为coordinator线程,但是只有1个worker线程进行回放,也是单线程复制。然而,这两种性能却又有一些的区别,因为多了一次coordinator线程的转发,因此slave_parallel_workers=1的性能反而比0还要差,在Inside君的测试下还有20%左右的性能下降,如下图所示: + + + +这里其中引入了另一个问题,如果主机上的负载不大,那么组提交的效率就不高,很有可能发生每组提交的事务数量仅有1个,那么在从机的回放时,虽然开启了并行复制,但会出现性能反而比原先的单线程还要差的现象,即延迟反而增大了。聪明的小伙伴们,有想过对这个进行优化吗? +Enhanced Multi-Threaded Slave配置 + +### 配置与监控 + +只需要在备库添加如下配置。 + +{% highlight text %} +slave-parallel-type = LOGICAL_CLOCK +slave-parallel-workers = 16 +master_info_repository = TABLE +relay_log_info_repository = TABLE +relay_log_recovery = ON +{% endhighlight %} + +并行复制监控仍然可以通过 ```SHOW SLAVE STATUS\G``` 查看,但是 MySQL 5.7 在 performance_schema 架构下多了以下这些元数据表,用户可以更细力度的进行监控。 + +{% highlight text %} +mysql> SHOW TABLES LIKE 'replication%'; ++---------------------------------------------+ +| Tables_in_performance_schema (replication%) | ++---------------------------------------------+ +| replication_applier_configuration | +| replication_applier_status | +| replication_applier_status_by_coordinator | +| replication_applier_status_by_worker | +| replication_connection_configuration | +| replication_connection_status | +| replication_group_member_stats | +| replication_group_members | ++---------------------------------------------+ +8 rows in set (0.00 sec) +{% endhighlight %} + + + + + + + + + + + + + + + + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-03-postgresql-misc_init.md b/_drafts/2016-08-03-postgresql-misc_init.md new file mode 100644 index 0000000..3ddbc19 --- /dev/null +++ b/_drafts/2016-08-03-postgresql-misc_init.md @@ -0,0 +1,177 @@ +--- +Date: October 19, 2013 +title: PG 常用命令 +layout: post +comments: true +language: chinese +category: [sql] +--- + +在此简单记录下,PostgreSQL 中常用的 SQL 指令,维护是常见的场景,还有些奇技淫巧。 + + + + +# .psqlrc + +该文件类似 bashrc,会在启动 psql 时执行该文件中的命令,通常可以在该文件下可以添加一些常用的 SQL 脚本,一般路径默认为 ~/.psqlrc 或者通过 PSQLRC 环境变量指定。 + +{% highlight text %} +--- \pset : for changing the output format. +--- \set : for everything else. + +-- Do not display the "help" message on startup. +\set QUIET 1 + +-- https://www.postgresql.org/docs/current/static/app-psql.html +\set PROMPT1 '%[%033[1m%]%M %n@%/%R%[%033[0m%]%# ' +-- PROMPT2 is printed when the prompt expects more input. +\set PROMPT2 '[more] %R > ' + +-- Show how long each query takes to execute. +\timing + +-- Use best available output format. +\x auto + +-- Ignore duplicated commands. +\set HISTCONTROL ignoredups + +-- How many history commands should be saved. +\set HISTSIZE 2000 + +-- Display detail message about the error. +--\set VERBOSITY verbose + +-- How psql handles errors. +\set ON_ERROR_ROLLBACK interactive + +-- Print "(NULL)" instead of "null" +\pset null '(NULL)' + + +-- ???? +\set COMP_KEYWORD_CASE upper + + + + + +-- Or \set QUIET OFF +\unset QUIET + + +\echo '\nCurrent Host Server Date Time: '`date` '\n' +\echo 'Administrative queries:\n' + + +\set foobar 'select * from pg_database;' + +-- Set your own personal settings. +--\i ~/.psqlrc.local +{% endhighlight %} + +http://opensourcedbms.com/dbms/psqlrc-psql-startup-file-for-postgres/ + + +# 常用脚本、命令 + +首先,需要在 postgresql.conf 中把 track_activites = on 打开,此时 stats collector process 会监控每个会话的 SQL 语句。 + +首先第一个脚本 viewsql.sh 会根据 pid 查看当前进程的 SQL 执行情况,这个脚本会显示指定的 pid 的会话目前正在执行的 SQL 语句;那么可以通过如下方式监控当前最占资源进程当前执行的 SQL 。 + +{% highlight text %} +$ top -b -c -n 1 | grep 'postgres:' | awk '{print $1}'| while read i; do ./viewsql.sh $i; done; +{% endhighlight %} + +第二个脚本 topsql.sh 查看全部进程 SQL 的执行情况。 + +第三个脚本 killsession.sh 用来 kill 占用资源高的 session。 + + + +{% highlight text %} +----- 查看配置项 +----- 1. 当前目录位置配置,可以根据分类来查找 +SELECT name, setting, source FROM pg_settings WHERE category = 'File Locations'; +----- 2. 或者直接根据变量的名称进行查找 +SELECT name, setting, source FROM pg_settings WHERE name = 'data_directory'; +----- 3. 会话变量设置 +SHOW work_mem; +SET work_mem='16MB'; +SELECT name, setting FROM pg_settings WHERE source = 'session'; +RESET work_mem; +----- 4. 查看那些变量被修改过 +SELECT name, setting, source FROM pg_settings WHERE source NOT IN ('default', 'override', 'configuration file'); + +----- 查看数据库的启动时间 +SELECT pg_postmaster_start_time() AS start_time; + +----- 查看数据库到目前为止的启动时间 +SELECT date_trunc('second', current_timestamp - pg_postmaster_start_time()) AS uptime; + +----- 查看有多少用户自己的表 +SELECT table_catalog, table_schema, table_name, table_type FROM information_schema.tables + WHERE table_schema NOT IN ('pg_catalog', 'information_schema'); +----- 可以查看information_schema.tables的定义 +\d information_schema.tables + +----- 各种对象大小查看 +----- 1. 查看当前、所有、某个数据库大小 +SELECT pg_database_size(current_database()); +SELECT datname, pg_database_size(datname) FROM pg_database; +\l+ +SELECT pg_size_pretty(pg_database_size('foobar')); +----- 2. 查看表大小 +SELECT pg_size_pretty(pg_relation_size('tbl')); +\dt+ tbl +----- 3. 查看表、索引、视图等在内的所有大小 +SELECT pg_size_pretty(pg_total_relation_size('tbl')); +----- 4. 查看表的记录数、非空所有行、去重后总行数 +SELECT count(1) FROM tbl; +SELECT count(col-name) FROM tbl; +SELECT count(DISTINCT col-name) FROM tbl; +----- 5. 上述方式当表数量大时会比较慢,可以用如下方式估算 +SELECT (CASE WHEN reltuples > 0 THEN pg_relation_size('tbl')/(8192*relpages/reltuples) + ELSE 0 END)::bigint as estimated_row_count FROM pg_class + WHERE oid='tbl'::regclass; +----- 6. 查看最大的表 +SELECT table_name, pg_relation_size(table_name) AS size FROM information_schema.tables WHERE table_schema + NOT IN ('information_schema', 'pg_catalog') order by size desc limit 10; +SELECT relname, relpages FROM pg_class ORDER BY relpages DESC; + +----- 查看表的索引 +\d tbl + +----- 查看查询的执行计划 +EXPLAIN query; +----- 在服务端执行查询来显示执行计划,会执行但不返回结果 +EXPLAIN ANALYZE query; + +----- 列出PG中基本的数据类型 +SELECT typname, typlen FROM pg_type WHERE typtype='b'; + +----- 创建不会持久化的表 +CREATE UNLOGGED TABLE test(id int); + +----- 查看监听端口????? +SELECT inet_server_port(); + +----- 查看当前用户 +postgres=# SELECT current_user; + +----- 查看当前PG版本 +postgres=# SELECT version(); +{% endhighlight %} + + +在第13个命令中,那个typtype='b'是什么意思? + +typtype='b'表示basetype。b==basetype. + +PostgreSQL有这么几种数据类型: composite types, domains, and pseudo-types. + +http://developer.postgresql.org/pgdocs/postgres/extend-type-system.html + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-04-postgresql-configuration_init.md b/_drafts/2016-08-04-postgresql-configuration_init.md new file mode 100644 index 0000000..2ce0ca7 --- /dev/null +++ b/_drafts/2016-08-04-postgresql-configuration_init.md @@ -0,0 +1,205 @@ +--- +Date: October 19, 2013 +title: PG 配置项 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + + + + + + + + +# 配置 + +在 $PGDATA 目录下有两个与配置相关的文件 postgresql.conf 以及 postgresql.auto.conf,后者为 ALTER SYSTEM 命令生成的文件,这里的参数生效会覆盖前者中的值。 + +其中部分参数可以 reload 生效,此时 postmaster 会收到 SIGHUP 信号,会触发读取这两个配置文件,并且会把该信号传播到其它运行中的服务进程。 + +{% highlight text %} +postgres=# SET enable_indexscan = off; # 设置会话级 +postgres=# ALTER ROLE foobar SET enable_indexscan TO 'off'; +postgres=# ALTER DATABASE foobar SET enable_indexscan = off; + + + +postgres=# SHOW enable_indexscan; +postgres=# SELECT current_setting('enable_indexscan'); + + + +----- 恢复到默认值 +postgres=# SET enable_indexscan TO DEFAULT; +postgres=# ALTER ROLE foobar SET enable_indexscan TO DEFAULT; +postgres=# ALTER DATABASE foobar SET enable_indexscan TO DEFAULT; + + + +$ pg_ctl reload +postgres=# SELECT pg_reload_conf() +{% endhighlight %} + + +PG 中的配置参数分为了四类: + +1. internal,参数在编译时确定,如 block_size 。 + +2. system,修改配置文件,需要重启才能生效,如 max_connections 。 + +3. global,所有会话都会生效。 + +4. session,只有当前会话会生效。 + +其中,global 通过 ALTER SYSTEM 设置后会向主进程发送 sighup 信号,主进程在 reaper() 中将 sighup 再发给各个子进程,各个进程会将 got_SIGHUP 设置为 true ,并在住流程中进行判断。 + +{% highlight c %} +if (got_SIGHUP) +{ + got_SIGHUP = false; + ProcessConfigFile(PGC_SIGHUP); +} +{% endhighlight %} + + +## 修改配置 + +9.5 之前修改参数需要登陆到服务器,修改配置文件 postgresql.conf,然后 restart 或者 reload 。在 9.5 增加了 ALTER SYSTEM 命令修改参数,在 pg_settings 中增加 pending_restart 字段,用于判断是否需要重启。 + +{% highlight text %} +ALTER SYSTEM SET archive_mode TO 'on'; +ALTER SYSTEM RESET archive_mode; +ALTER SYSTEM RESET ALL; +{% endhighlight %} + + +## GUC, Grand Unified Configuration + +GUC 是 PG 里对数据库变量进行设置,对数据库进行控制的机制,包括了配置文件中变量修改,或者通过 set 命令对参数进行设置。实际上其变量的种类,设置方法要更多样,可直接查看 guc.c 中的实现。 + +GUC 变量包括变量所属的功能组、变量类型、来源和作用上下文(context)。这里简单说一下GUC变量的作用上下文。 + + + +上下文共分internal,postmaster,sighup,backend,suset,userset六种: + +internal无法被用户修改,只能被内部进程设置,show命令能够查看此类变量。此类变量通常在编译时设置与改变。 + +postmaster在postmaster启动时通过读取configure文件或命令行来设置。这类变量的改变在postgreSQL重启时生效。 + +sighup在postmaster启动或向postmaster或backend进程发sighup信号来读取configure文件时设置。 + +backend在新backend进程启动时读取configure文件生效。 + +suset指超级用户修改生效,不需要重新读取configure文件。 + +user指普通用户修改生效,在当前会话下有效,无需读取configure文件。 + + + +几个常用的命令: + +{% highlight text %} +select * from pg_settings; +show variableName; +select pg_reload_conf(); +pg_ctl -D PGDATA reload +kill -HUG processID +select name,source,settings from pg_settings where source != 'default' and source !='override' order by 2,1; +{% endhighlight %} + +## 配置文件 + +通常 PG 的配置文件保存在 -D /var/lib/pgsql/data 指定目录下。 + +{% highlight text %} +PGDATA/pg_hba.conf: + 基于主机的访问控制文件,保存对客户端认证方式的设置信息。 +PGDATA/pg_ident.conf: + 用户名映射文件,定义了OS和PG用户名之前的对应关系,会被pg_hba.conf使用。 +PGDATA/postgresql.conf: + 主配置文件,除了与权限相关的配置外,其它可以设置的参数都保存在该文件中。 +PGDATA/postmaster.opts: + 上次启动时的命令行参数。 +PGDATA/PG_VERSION: + 版本信息。 + +PGDATA/pg_clog/: + 事务的元信息,告知PG那些事务已经完成那些还没有。 +PGDATA/pg_xlog/: + WAL日志,每个文件16M,如果归档失败会导致该目录非常大。 +PGDATA/pg_log/: + 日志信息。 +PGDATA/pg_multixact/: + 多重事务状态数据子目录。 +PGDATA/pg_notify/: + LISTEN/NOTIFY相关的状态数据。 +PGDATA/pg_stat_tmp/: + 状态统计信息所保存文件的临时目录。 +PGDATA/pg_xlog/: + WAL日志文件,每个文件占用16M。 +{% endhighlight %} + +其中常见的配置保存在 postgresql.conf 文件中,当前的变量值可以通过 show VARS 命令查看。 + +{% highlight text %} +max_connections = 1000 # 同时连接到PG的客户端数量 +log_directory = 'pg_log' # 数据库日志的位置 + + + +track_activities = on # 监控每个进程命令执行的情况 +track_counts = on # 监控表以及索引的访问情况 +track_io_timing = off # 监控block的读写次数 +track_functions = none # none, pl, all 跟踪用户定义函数的执行过程 + +#track_activity_query_size = 1024 # (change requires restart) +#update_process_title = on +stats_temp_directory = 'pg_stat_tmp' # 一般指向RAM,关闭后保存在pg_stat中 + + +# - Statistics Monitoring - + +#log_parser_stats = off +#log_planner_stats = off +#log_executor_stats = off +#log_statement_stats = off + + + + + + + + + + + +debug_print_parse = off # 打印查询解析日志 +debug_print_rewritten = off +debug_print_plan = off +debug_pretty_print = on + + +log_checkpoints = off +log_connections = off +log_disconnections = off +log_duration = off + + + + + + +{% endhighlight %} + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-06-postgresql-internal-table_init.md b/_drafts/2016-08-06-postgresql-internal-table_init.md new file mode 100644 index 0000000..b3a2fee --- /dev/null +++ b/_drafts/2016-08-06-postgresql-internal-table_init.md @@ -0,0 +1,31 @@ +--- +Date: October 19, 2013 +title: PG 内部表 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + + +## pg_class + +记录了几乎和表类似对象的所有字段,包括索引 (还需要参考pg_index)、序列、视图、物化视图、符合类型等。 + + +{% highlight text %} +relkind: r(ordinary table,普通表) +{% endhighlight %} + +## pg_stat_activity + +保存了当前连接的状态信息,可以通过如下 SQL 查询最近执行的 SQL 值。 + +{% highlight text %} +SELECT * FROM pg_stat_activity; +{% endhighlight %} + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-11-postgresql-log_init.md b/_drafts/2016-08-11-postgresql-log_init.md new file mode 100644 index 0000000..9d2cf33 --- /dev/null +++ b/_drafts/2016-08-11-postgresql-log_init.md @@ -0,0 +1,179 @@ +--- +Date: October 19, 2013 +title: PG 日志 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + + + + +{% highlight text %} +#----- 日志记录在那里 +log_destination = 'stderr' # 日志记录到什么地方,stderr、csvlog、syslog等 +logging_collector = on # 是否开启日志收集,此时会开启logger进程 +log_directory = 'pg_log' # 指定日志保存的目录,相对路径为$PGPATH +log_filename = 'postgre-%Y%m%d_%H%M%S.log' # 日志文件名格式,参数采用strftime()格式 +log_file_mode = 0600 # 日志文件的权限设置 +log_truncate_on_rotation = off # 如果有相同的日志文件名,则会truncate而非append +log_rotation_age = 1d # 多长时间切换一次日志 +log_rotation_size = 256MB # 输出多少日志后会切换日志文件,0表示不切换 + +#----- 什么时候记录日志 +client_min_messages = notice # 设置返回给客户端的日志,建议保持默认即可 +log_min_messages = warning # 保存到服务器日志的信息,只有superuser可以设置 +log_min_error_statement = error # 那些错误的SQL记录到日志 +log_min_duration_statement = 1s # SQL耗时多久会记录到日志 + +#----- 记录什么 +debug_print_parse = off +debug_print_rewritten = off +debug_print_plan = off +debug_pretty_print = on # 如上配置解析SQL时的数据结构 +log_checkpoints = on # 记录checkpoints日志,某些情况下会影响到性能 +log_connections = on +log_disconnections = on # 每次客户端连接/断开都会打印日志 +log_line_prefix = '[%m %p %u %d %h]' # 日志的格式,详细的可以查看配置文件 +log_lock_waits = on # 记录超过死锁时间的SQL,lock waits >= deadlock_timeout +log_statement = 'none' # 记录那些类型的SQL语句 +log_timezone = 'PRC' +log_duration = off # 关闭每次打印耗时,只有超过上述min才打印 + +log_hostname = off # ???? + + +#log_error_verbosity = default # terse, default, or verbose messages +#log_replication_commands = off ???? +#log_temp_files = -1 # log temporary files equal or larger + # than the specified size in kilobytes; + # -1 disables, 0 logs all temp files + + +{% endhighlight %} + + +# 常用技巧 + + +## 慢SQL查询 + +通过 log_min_duration_statement 参数进行设置,一般当超过 1s 后记录 SQL 。另外,log_duration 如果打开,则会记录所有 SQL 的耗时。 + +可以通过如下方式测试。 + +{% highlight text %} +postgres=# show log_min_duration_statement; + log_min_duration_statement +---------------------------- + 1s +(1 row) + +postgres=# \timing +Timing is on. +postgres=# select now(),pg_sleep(1); + now | pg_sleep +-------------------------------+---------- + 2016-08-10 15:10:04.713244+08 | +(1 row) + +Time: 1006.196 ms +postgres=# select now(),pg_sleep(3); + now | pg_sleep +-------------------------------+---------- + 2016-08-10 15:10:31.080744+08 | +(1 row) + +Time: 3002.663 ms +postgres=# \timing +Timing is off. +{% endhighlight %} + +日志信息类似如下。 + +{% highlight text %} +duration: 3002.663 ms statement: select now(),pg_sleep(3); +{% endhighlight %} + + +## 监控checkpoint + +当数据库进行大量数据更新时,如果参数设置有误,则会导致频繁做 checkpoint 导致系统变慢。 + +{% highlight text %} +postgres=# show log_checkpoints; + log_checkpoints +----------------- + on +(1 row) + +postgres=# checkpoint; +CHECKPOINT +{% endhighlight %} + +此时会打印如下信息。 + +{% highlight text %} +checkpoint starting: immediate force wait +checkpoint complete: wrote 0 buffers (0.0%); 0 transaction log file(s) added ... +{% endhighlight %} + +## 监控数据库死锁 + +当前使用的锁信息保存在 pg_locks 系统表中,如果要查看一天内有多少锁超时,可以开启日志。 + +{% highlight text %} +postgres=# show log_lock_waits; + log_lock_waits +---------------- + on +(1 row) + +postgres=# show deadlock_timeout; + deadlock_timeout +------------------ + 1s +(1 row) + +postgres=# CREATE TABLE foobar (id INT); +CREATE TABLE +postgres=# INSERT INTO FOOBAR VALUES(1); +INSERT 0 1 +postgres=# begin; +BEGIN +postgres=# delete from foobar; +DELETE 1 + +----- 开启另外一个会话 +postgres=# begin; +BEGIN +postgres=# delete from foobar; +DELETE 1 +{% endhighlight %} + +打印信息如下。 + +{% highlight text %} +LOG: process 9431 still waiting for ShareLock on transaction 1910 after 1000.523 ms +DETAIL: Process holding the lock: 3184. Wait queue: 9431. +CONTEXT: while deleting tuple (0,1) in relation "foobar" +STATEMENT: delete from foobar; +{% endhighlight %} + +# 最佳实践 + +{% highlight text %} +----- 按照创建时间倒序排序 +$ ls -hlrc +{% endhighlight %} + +# 参考 + +[Error Reporting and Logging](https://www.postgresql.org/docs/current/static/runtime-config-logging.html) + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-12-postgresql-python_init.md b/_drafts/2016-08-12-postgresql-python_init.md new file mode 100644 index 0000000..6c49c7b --- /dev/null +++ b/_drafts/2016-08-12-postgresql-python_init.md @@ -0,0 +1,48 @@ +--- +Date: October 19, 2013 +title: 通过 Python 访问 PG +layout: post +comments: true +language: chinese +category: [sql] +--- + +实际连接 PG 可以通过至少三种方式连接:PsyCopg、PyPgSQL、PyGreSQL,在此使用 psycopg2,相比来说其代码少,速度块,稳定。 + + + + +# 简介 + +PsyCopg 满足 [Python DB API 2.0](https://www.python.org/dev/peps/pep-0249/) 标准,如下是一个简单的示例。 + +{% highlight python %} +#!/usr/bin/env python +#-*- coding:utf-8 -*- +import psycopg2 +import pprint + +conn = psycopg2.connect( + database="postgres", + user="postgres", + password="", + host="127.1", + port="5432") + +cur = conn.cursor() +cur.execute("SELECT * FROM pg_tablespace LIMIT 10") +rows = cur.fetchall() +pprint.pprint(rows) + +conn.close() +{% endhighlight %} + +cursor 实际保存了一个 fetch 操作的上下文,在 Python DB API 2.0 中规定,如果操作系统不支持,那么建议通过软件自己实现。 + + +# 参考 + +相关的介绍可以直接参考官方文档 [initd.org/psycopg](https://initd.org/psycopg/) ,以及 PG 中与此相关的介绍,包括了主要的使用方式 [Using psycopg2 with PostgreSQL](https://wiki.postgresql.org/wiki/Using_psycopg2_with_PostgreSQL) 。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-17-postgresql-wal_init.md b/_drafts/2016-08-17-postgresql-wal_init.md new file mode 100644 index 0000000..fc9571e --- /dev/null +++ b/_drafts/2016-08-17-postgresql-wal_init.md @@ -0,0 +1,356 @@ +--- +Date: October 19, 2013 +title: PG WAL (Write Ahead Log) +layout: post +comments: true +language: chinese +category: [sql] +--- + + + + + +# 文件命名 + +在 $PGDATA/pg_xlog 目录下,保存的是 WAL 文件,默认是 16M,如果要修改,需要在编译时添加如下配置项,详细的其它参数可以直接参考源码的 INSTALL 文件。 + +{% highlight text %} +----- 编译时配置XLOG文件的大小 +$ ./configure --with-wal-segsize=64 + +----- 查看配置后的定义宏大小 +$ grep -rne 'define.*XLOG_SEG_SIZE' src/include/pg_config.h +#define XLOG_SEG_SIZE (16 * 1024 * 1024) +{% endhighlight %} + +注意,上述定义的 XLOG_SEG_SIZE 是 XLOG 日志文件的段大小,就是一个物理的日志文件的大小;而一个逻辑的 XLOG 日志文件大小是 4GB。 + + +每个文件的命名格式类似 000000010000000000000001,这个是起始的值。每个 XLOG 日志的文件名,实际上分为了两大部分,而第二部分又被分成两部分: + +* Time Line ID,用来标识数据库状态,每次恢复都会增加,主要为了将恢复后与之前的状态区分开。 + +* XLOG Sequence Number,表示物理日志文件的序列号。这部分又分为两部分,高 32bits 表示 xlog 的序号,低 32bits 表示此 xlog 包含的 segment 号。 + +在内存中,是通过 XLogRecPtr 类型表示内存偏移的,而保存到文件时,每个文件名按如上的方式定义,源码中各个类型的定义如下: + +{% highlight text %} +0000 0001 : 0000 0000 : 0000 000E + +----- 包括了32bits的TLI +$ grep -rne 'typedef.*TimeLineID;' include/access/xlogdefs.h +typedef uint32 TimeLineID; +----- 以及64bits的物理日志文件序列号 +$ grep -rne 'typedef.*XLogSegNo;' include/access/xlogdefs.h +typedef uint64 XLogSegNo; + +$ grep -rne 'define XLogSeg.*XLOG_SEG_SIZE' include/access/xlog_internal.h +#define XLogSegSize ((uint32) XLOG_SEG_SIZE) +#define XLogSegmentsPerXLogId (UINT64CONST(0x100000000) / XLOG_SEG_SIZE) + +----- 文件名的命名格式 +$ grep -A 3 -rne 'define XLogFileName(' include/access/xlog_internal.h +#define XLogFileName(fname, tli, logSegNo) \ + snprintf(fname, MAXFNAMELEN, "%08X%08X%08X", tli, \ + (uint32) ((logSegNo) / XLogSegmentsPerXLogId), \ + (uint32) ((logSegNo) % XLogSegmentsPerXLogId)) +{% endhighlight %} + +如上,其中文件的范围为 000000010000000000000001 ~ 00000001FFFFFFFF000000FF,可以做如下测试,当达到最大值之后会回到 00 ,而该值是一个非法值,对于 PG 来说,实际上会直接崩溃掉。 + +{% highlight text %} +$ pg_ctl -D /var/lib/pgsql/9.5/data stop +$ pg_resetxlog -f -l 00000001FFFFFFFF000000FE /var/lib/pgsql/9.5/data +$ pg_ctl -D /var/lib/pgsql/9.5/data -l logfile start +$ ls -lrt $PGDATA/pg_xlog/ +postgres=# checkpoint; +postgres=# select pg_switch_xlog(); +{% endhighlight %} + +有人计算过,如果每天产生 10T 的日志,可以使用 4K 多年,所以目前来说是没有问题的。 + + + +# XLOG 格式 + +PG XLOG 文件的存储格式如下: + +{% highlight text %} + + + + + +里面包括等 + + + + + + +{% endhighlight %} + + +## PageHeaderData + +PageHeaderData 在代码中可能是 XLogLongPageHeaderData 或者 XLogPageHeaderData 两个结构体中的一个,通过 xlp_info 中是否未 XLP_LONG_HEADER 标示符确定。 + +{% highlight c %} +typedef struct XLogPageHeaderData +{ + uint16 xlp_magic; /* magic value for correctness checks */ + uint16 xlp_info; // 记录flags,详细信息查看下面内容 + TimeLineID xlp_tli; // 本页中第一条记录的TimeLineID + XLogRecPtr xlp_pageaddr; /* XLOG address of this page */ + uint32 xlp_rem_len; /* total len of remaining data for record */ +} XLogPageHeaderData; +typedef struct XLogLongPageHeaderData +{ + XLogPageHeaderData std; /* standard header fields */ + uint64 xlp_sysid; /* system identifier from pg_control */ + uint32 xlp_seg_size; // XLOG文件大小,在编译时确定,如上所述 + uint32 xlp_xlog_blcksz; // XLOG文件中页的大小,默认为8K +} XLogLongPageHeaderData; +{% endhighlight %} + +其中 Long 模式的头只在一个 XLOG 文件的第一页保存,多余的字段用来校验有效性。另外,xlp_info 保存了一些标示,例如是否跨页、是否为 Long 模式,其值定义如下: + +{% highlight c %} +#define XLP_FIRST_IS_CONTRECORD 0x0001 /* 表示页头后面跟的是一个跨页结构的一部分 */ +#define XLP_LONG_HEADER 0x0002 /* 标示是Long模式,只在文件的第一页 */ +#define XLP_ALL_FLAGS 0x0003 /* 定义所有标志位,用于头的有效性检查 */ +{% endhighlight %} + + + + + +## XLogRecord + +日志在PG 定义如下。 + +{% highlight text %} +typedef struct XLogRecord +{ + uint32 xl_tot_len; // 整个记录的长度 + TransactionId xl_xid; // 该记录事务ID + XLogRecPtr xl_prev; // 前一个XLogRecord的指针 + uint8 xl_info; // 标准位 + RmgrId xl_rmid; // 本记录的资源管理器ID,例如RM_XLOG_ID、RM_CLOG_ID等 + pg_crc32c xl_crc; // 该记录的CRC校验 +} XLogRecord; +{% endhighlight %} + + +当页头记录是跨页记录的一部分时,在XLogLongPageHeaderData结构的后面跟着XLogContRecord结构。原则是XLogRecord记录头从不被分到多个页,如果页尾空间小于SizeOfXLogRecord,就弃之不用,直接使用下一个页面。如果在一个记录在一个页面没写完,在下一个页头的XLogLongPageHeaderData结构后面用XLogContRecord结构,此时XLogLongPageHeaderData结构的成员xlp_info的值是XLP_FIRST_IS_CONTRECORD。 + + +XLOG资源管理器时,其成员xl_info值可以是: + +#define XLOG_CHECKPOINT_SHUTDOWN 0x00 +#define XLOG_CHECKPOINT_ONLINE 0x10 +#define XLOG_NOOP 0x20 +#define XLOG_NEXTOID 0x30 +#define XLOG_SWITCH 0x40 +#define XLOG_BACKUP_END 0x50 +#define XLOG_PARAMETER_CHANGE 0x60 +#define XLOG_RESTORE_POINT 0x70 + + + +XACT资源管理器时,其成员xl_info高4位储存如下信息: + +#define XLOG_XACT_COMMIT 0x00 +#define XLOG_XACT_PREPARE 0x10 +#define XLOG_XACT_ABORT 0x20 +#defineXLOG_XACT_COMMIT_PREPARED 0x30 +#defineXLOG_XACT_ABORT_PREPARED 0x40 +#defineXLOG_XACT_ASSIGNMENT 0x50 + + +XLOG仅使用xl_info的低4位,高4为由资源管理器rmgr使用 + +#define XLR_INFO_MASK 0x0F + +如果用XLOG记录备份任一磁盘块(数据文件块吧),用其成员xl_info标志位记录,支持每个XLOG记录3个磁盘块的备份,使用xl_info标志的低1、2、3位 + +#define XLR_BKP_BLOCK_1 XLR_SET_BKP_BLOCK(0) /* 0x08 */ + +#define XLR_BKP_BLOCK_2 XLR_SET_BKP_BLOCK(1) /* 0x04 */ + +#define XLR_BKP_BLOCK_3 XLR_SET_BKP_BLOCK(2) /* 0x02 */ + + + +如果已备份块能从XLOG的压缩版本中被安全删除,设置Xl_info的0位(这就是,备份它们仅是为了防止写部分页(partial-page-write)问题,并且不保证PITR恢复的一致性)。压缩算法将需要从这些块中解析出数据以创建一个相同的非全页XLOG记录。 + +#define XLR_BKP_REMOVABLE 0x01 + + + + +3 + +可能是事务状态定义等,例如结构xl_xact_commit、xl_xact_abort等 + + + +typedefstruct xl_xact_commit + +{ + + TimestampTzxact_time; /* 提交时间 */ + + uint32 xinfo; /* 信息位 */ + + int nrels; /* 关系文件数 */ + + int nsubxacts; /* 子事务XID数 */ + + int nmsgs; /* 共享失效信息数 */ + + Oid dbId; /* 数据库ID */ + + Oid tsId; /* 数据库表空间ID */ + + /* 提交时要drop的关系文件节点数组*/ + + RelFileNodexnodes[1]; /* 变长数组 */ + + /* 后面跟已提交子事务ID数组 */ + + /* 后面跟共享失效消息数组*/ + +} xl_xact_commit; + + + +typedefstruct xl_xact_abort + +{ + + TimestampTzxact_time; /* 退出时间 */ + + int nrels; /* 关系文件节点数 */ + + int nsubxacts; /* 子事务XID数 */ + + /* 退出时要drop的关系文件节点数组 */ + + RelFileNodexnodes[1]; /*变长数组*/ + + /* 后面跟以退出的子事务XID数组 */ + +} xl_xact_abort; + + + +4 + +表示BkpBlock结构,是跟在XLOG记录XLogRecord后面的备份块头信息。XLOG代码知道PG数据页面中间常常包含一个无用的“洞”(“hole”——未使用的空间),其只包含值是0的字节。如果这个洞的长度大于0(hole_length >0),实际的跟在结构BkpBlock后面的块数据量是BLCKSZ -hole_length个字节 + +typedefstruct BkpBlock + +{ + + RelFileNodenode; /* 包含该块的关系文件 */ + + ForkNumberfork; /* 关系分支 */ + + BlockNumberblock; /* 块数 */ + + uint16 hole_offset; /* "hole"前字节数 */ + + uint16 hole_length; /* "hole"的字节数 */ + + + + /* 实际的块数据跟在结构后面 */ + +} BkpBlock; + + + +5 + +表示XLogRecData结构。要写入XLOG日志文件的资源管理器数据,由一或多个XLogRecData结构定义。 + +typedefstruct XLogRecData + +{ + + char *data; /* 资源管理器包含数据的开始 */ + + uint32 len; /* 资源管理器包含数据的长度 */ + + Buffer buffer; /* 有相应数据的buffer,如果有的话 */ + + bool buffer_std; /* buffer是否有标准pd_lower/pd_upper头 */ + + struct XLogRecData *next; /* 链里的下一个结构 */ + +} XLogRecData; + +pd_lower页面开始位置与未分配空间开头的字节偏移,pd_upper与未分配空间结尾的字节偏移,LSN:最后修改这个页面的 xlog 记录最后一个字节后面第一个字节。 + + + +XLogRecData结构里记录各种数据库操作数据,其中典型的,数据是一个检查点(CheckPoint struct)。根据XLogRecord的成员xl_rmid是否等于RM_XLOG_ID以及的成员xl_info是否等于XLOG_CHECKPOINT_SHUTDOWN或XLOG_CHECKPOINT_ONLINE来判定数据是检查点。检查点的定义见下面。 + + + +typedefstruct CheckPoint + +{ + + XLogRecPtrredo; /*开始创建一个检查点时下一个XLOG记录的位置*/ + + TimeLineIDThisTimeLineID; /*当前时间线*/ + + uint32 nextXidEpoch; /*下一个事务ID的高排序位 */ + + TransactionIdnextXid; /* 下一个空闲事务ID */ + + Oid nextOid; /* 下一个空闲OID */ + + MultiXactIdnextMulti; /* 下一个空闲多事务ID */ + + MultiXactOffsetnextMultiOffset; /* next free MultiXact offset */ + + TransactionIdoldestXid; /* cluster-wide minimum datfrozenxid */ + + Oid oldestXidDB; /* database with minimum datfrozenxid*/ + + pg_time_t time; /* 检查点时间戳 */ + + + + /* + +仍在运行的最早的事务ID(XID)。只有在从一个在线检查点初始化热备模式时才需要,以使在GUC参数wal_level是hot_standby时我们不用为在线检查点计算运行最早的XID。否则设置为常量InvalidTransactionId。 + + */ + + TransactionIdoldestActiveXid; + +} CheckPoint; + + +------------ + + +# 时间线 + +http://mysql.taobao.org/monthly/2015/07/03/ + + + + +pg_xlogdump -b 00000001000000000000000E + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-19-lmdb_init.md b/_drafts/2016-08-19-lmdb_init.md new file mode 100644 index 0000000..8fe9340 --- /dev/null +++ b/_drafts/2016-08-19-lmdb_init.md @@ -0,0 +1,112 @@ +--- +title: LMDB +layout: post +comments: true +language: chinese +category: [misc] +keywords: lmdb,mmap +description: LMDB 是 OpenLDAP 项目开发的嵌入式存储引擎,通过 mmap 实现,提供了 B+Tree 索引,并提供了 MVCC 功能。这里简单介绍其使用方法,以及相关的实现。 +--- + +LMDB 是 OpenLDAP 项目开发的嵌入式存储引擎,通过 mmap 实现,提供了 B+Tree 索引,并提供了 Multi Version Concurrent Control, MVCC 功能。 + +这里简单介绍其使用方法,以及相关的实现。 + + + +## 简介 + +LMDB 是一个嵌入式存储引擎,与之类似的数据库还有 [TokyoCabinet](http://fallabs.com/tokyocabinet/),其主要特性有: + +* 基于文件映射 mmap 实现,所有的操作直接访问通过 mmap 映射的内存; +* 将整个虚拟内存通过 B+Tree 维护,可以通过 Cursor 游标进行访问; +* 支持 MVCC 的事务处理,也就意味着支持事务的 ACID 特性; +* 对外提供类似 BerkeleyDB 的 API 接口。 + +其测试用例或者使用方法可以直接参考源码中的 `mtestN.c` 文件,大致的调用流程如下。 + +### 使用 + +{% highlight text %} +-----> 初始化 <----- +mdb_env_create() 创建一个环境变量,保存了相关的配置信息,可以通过如下接口设置 +mdb_env_set_maxreaders() +mdb_env_set_mapsize() +mdb_env_open() 打开文件 + +-----> 启动事务 <----- +mdb_txn_begin() +mdb_txn_abort() +mdb_txn_commit() +{% endhighlight %} + +#### 环境变量 + +用来访问单个数据库文件,通过 environment 保存了与之相关的配置信息,正常来说会在进程启动的创建并配置,然后直到进程退出时关闭。 + + + +### 源码编译 + +可以下载源码后直接通过 `make` 编译。 + +### 实现原理 + +LMDB 采用 mmap 文件映射,不管这个文件存储在内存还是磁盘,所有读取都是通过 mmap 将要访问的文件只读的映射到虚拟内存中,直接访问相应的地址。 + +使用方法可以参考 [LMDB Getting Started](http://www.lmdb.tech/doc/starting.html) 中的介绍,在此简单记录下常见操作。 + + +#### 初始化 + +需要 `mdb_env_create()` `mdb_env_open()`,前者只创建一个 `MDB_env` 对象,并对申请对象进行初始化,但是不创建资源(文件、锁等)。 + +默认通过 `mdb_env_open()` 函数传入的是一个已经存在的目录,执行该函数后会在该目录下创建 `data.mdb` 以及 `lock.mdb` 两个文件,如果使用了 `MDB_NOSUBDIR` 参数,则会将入参作为文件,并会同时创建一个 `XXX-lock` 文件。 + +* 开启事务 +支持读写和只读事务,而且前者可以进行嵌套;需要注意的是,即使是只读事务,都需要通过mdb_txn_begin()开启一个事务,主要目的是获取consistent view。 + +* 打开数据库 +通过mdb_dbi_open() + +## 并发控制 + +mdb_env_open() + |-mdb_fname_init() 根据传入的参数是否有MDB_NOSUBDIR标示,决定创建不同的文件 + |-mdb_env_setup_locks() 打开锁 + |-sem_open() + +多个进程使用共享的信号量(named POSIX semaphores) + + +pthread_cond_wait()用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast()来唤醒它;而且该函数必须与pthread_mutex_lock/unlock()函数配合使用。 +http://blog.csdn.net/yeyuangen/article/details/37593533 +http://www.cnblogs.com/dodng/p/4380035.html +http://zsxxsz.iteye.com/blog/2028452 + + +在多线程程序中,可以通过全局变量实现数据共享,此时需要通过互斥保证边界条件;当然,有时也会需要线程的私有数据 (Thread Specific Data)。这里主要测试和线程私有数据有关的 4 个函数: +pthread_key_create(); TODODO:创建的pthread_key_t用途是什么 +pthread_key_delete(); +pthread_getspecific(); +pthread_setspecific(); +pthread_cond_wait()用于阻塞当前线程,等待别的线程使用pthread_cond_signal()或pthread_cond_broadcast()来唤醒它;而且该函数必须与pthread_mutex_lock/unlock()函数配合使用。 +实测pthread_cond_signal()不存在竞争条件,此时只有一个线程被唤醒;而pthread_cond_broadcast()显然会唤醒所有等待线程。 +pthread_cond_wait()该API会以原子操作释放入参指定的mutex,并将线程阻塞等待条件变量;函数返回时,会将mutex加锁。 + + +## 参考 + + +http://wiki.dreamrunner.org/public_html/C-C++/Library-Notes/LMDB.html + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-21-mysql-utilities_init.md b/_drafts/2016-08-21-mysql-utilities_init.md new file mode 100644 index 0000000..269d033 --- /dev/null +++ b/_drafts/2016-08-21-mysql-utilities_init.md @@ -0,0 +1,24 @@ +--- +title: MySQL 组提交 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + +这个一套使用 Python 编写的工具集。 + + + +### 安装 + +可以直接下载源码并安装。 + + + +## 参考 + +官方文档 [MySQL Utilities 1.5 Manual](https://dev.mysql.com/doc/mysql-utilities/1.5/en/) + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-28-postgresql-high-available_init.md b/_drafts/2016-08-28-postgresql-high-available_init.md new file mode 100644 index 0000000..2a5a18c --- /dev/null +++ b/_drafts/2016-08-28-postgresql-high-available_init.md @@ -0,0 +1,48 @@ +--- +Date: October 19, 2013 +title: PG 高可用 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + + +# Warm Standby + +Runs normal crash recovery code, just never finishes. + +只要是异步都会有丢失数据的风险。 + +冷备:PITR 通常是在 WAL 日志 16M 打包之后,或者到了 archive_timeout 之后,从而将数据丢失的风险缩小到 timeout 。 + +温备:将日志归档+PITR恢复结合起来,只是从未停止恢复,采用 pg_standby 。 + +这样就导致如果 timeout 设置的较高就会导致延迟较高,低的话就会对磁盘 IO 造成较高的压力,这样就有了流复制。 + +1. 获得基线。 + +2. 通过restore_command恢复。 + +3. 开始流复制。 + +近乎实时,wal_sender_delay = 200ms +通过 trigger 停止,如果没有 trigger 则不停止。 + + +recovery_connections=on + + + +# Warm-Standy + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-29-postgresql-benchmark_init.md b/_drafts/2016-08-29-postgresql-benchmark_init.md new file mode 100644 index 0000000..e632476 --- /dev/null +++ b/_drafts/2016-08-29-postgresql-benchmark_init.md @@ -0,0 +1,25 @@ +--- +Date: October 19, 2013 +title: PG 压侧 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + +{% highlight text %} +----- 初始化 +pgbench -i + +-c 客户端数量 +-T 测试时间,单位为秒 +-r 返回测试的结果 +-j 线程数 + + +{% endhighlight %} + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-08-30-mysql-file-io_init.md b/_drafts/2016-08-30-mysql-file-io_init.md new file mode 100644 index 0000000..3052b31 --- /dev/null +++ b/_drafts/2016-08-30-mysql-file-io_init.md @@ -0,0 +1,1497 @@ +--- +title: MySQL 文件 IO +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + + + + +## 简介 + +在 InnoDB 中所有需要持久化的信息都需要文件操作,例如:表文件、重做日志文件、事务日志文件、备份归档文件等。InnoDB 对文件 IO 操作可以是煞费苦心,主要包括两方面:A) 对异步 IO 的实现;B) 对文件操作管理和 IO 调度的实现。 + +其主要实现代码集中在 os_file.* + fil0fil.* 文件中,其中 os_file.* 是实现基本的文件操作、异步 IO 和模拟异步 IO;fil0fil.* 是对文件 IO 做系统的管理和 space 结构化。 + +Innodb 的异步 IO 默认使用 libaio。 + + + + +其中数据刷盘的主要代码在 innodb/buf/buf0flu.c 中。 + +
    +buf_flush_batch()
    + |-buf_do_LRU_batch()                         根据传入的type决定调用函数
    + |-buf_do_flush_list_batch()
    +   |-buf_flush_page_and_try_neighbors()
    +     |-buf_flush_try_neighbors()
    +       |-buf_flush_page()                     刷写单个page
    +          |-buf_flush_write_block_low()       实际刷写单个page
    +
    +    buf_flush_write_block_low调用buf_flush_post_to_doublewrite_buf (将page放到double write buffer中,并准备刷写)
    +
    +    buf_flush_post_to_doublewrite_buf 调用 fil_io ( 文件IO的封装)
    +
    +    fil_io 调用 os_aio (aio相关操作)
    +
    +    os_aio 调用 os_file_write (实际写文件操作)
    +
    +
    + + +其中buf_flush_batch 只有两种刷写方式: BUF_FLUSH_LIST 和 BUF_FLUSH_LRU 两种方式的方式和触发时机简介如下: + +BUF_FLUSH_LIST: innodb master线程中 1_second / 10 second 循环中都会调用。触发条件较多(下文会分析) + +BUF_FLUSH_LRU: 当Buffer Pool无空闲page且old list中没有足够的clean page时,调用。刷写脏页后可以空出一定的free page,供BP使用。 + +从触发频率可以看到 10 second 循环中对于 buf_flush_batch( BUF_FLUSH_LIST ) 的调用是10秒一次IO高负载的元凶所在。 + +我们再来看10秒循环中flush的逻辑: + + 通过比较过去10秒的IO次数和常量的大小,以及pending的IO次数,来判断IO是否空闲,如果空闲则buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) ); + + 如果脏页比例超过70,则 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) ); + + 否则 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(10) ); + +可以看到由于SSD对于随机写的请求响应速度非常快,导致IO几乎没有堆积。也就让innodb误认为IO空闲,并决定全力刷写。 + +其中PCT_IO(N) = innodb_io_capacity *N% ,单位是页。因此也就意味着每10秒,innodb都至少刷10000个page或者刷完当前所有脏页。 + +updated on 2013/10/31: 在5.6中官方的adaptive flush算法有所改变,但是空闲状态下innodb_io_capacity对于刷写page数量的影响仍然不改变。 +UNIQUE 索引 IO 与聚簇索引 IO 完全一致,因为二者都必须读取页面,不能进行 Insert Buffer 优化。 + +{% highlight text %} + + +buf_page_get_gen() 获取数据库中的页 + |-buf_pool_get() 尝试从buffer pool中获取 + |-buf_page_hash_lock_get() # 判断所需的页是否在缓存中 + |-buf_read_page() # 如果不存在则直接从文件读取的buff_pool中 + |-buf_read_page_low() # 实际底层执行函数 + |-fil_io() + |-os_aio() # 实际是一个宏定义,最终调用如下函数 + | |-os_aio_func() # 其入参包括了mode,标识同步/异步 + | |-os_file_read_func() # 同步读 + | | |-os_file_pread() + | | |-pread() + | | + | |-os_file_write_func() # 同步写 + | | |-os_file_pwrite() + | | |-pwrite() + | | + | |-... ... # 对于异步操作,不同的mode其写入array会各不相同 #A + | |-os_aio_array_reserve_slot() # 从相应队列中选取一个空闲slot,保存需要读写的信息 + | | | + | | |-local_seg=... ... # 1. 首先在任务队列中选择一个segment #B + | | | + | | |-os_mutex_enter() # 2. 对队列加锁,遍历该segement,选择空闲的slot,如果没有则等待 + | | | + | | | # 3. 如果array已经满了,根据是否使用AIO决定具体策略 + | | |-os_aio_simulated_wake_handler_threads() # 非native AIO,模拟唤醒 + | | |-os_wait_event(array->not_full) # native aio 则等待not_full信号 + | | | + | | |-os_aio_array_get_nth_slot() # 4. 已经确定是有slot了,选择空闲的slot + | | | + | | |-slot... ... # 5. 将文件读写请求信息保存在slot,如目标文件、偏移量、数据等 + | | | + | | | # 6. 对于Win AIO、Native AIO采取不同策略 + | | |-ResetEvent(slot->handle) # 对于Win调用该接口 + | | |-io_prep_pread() # 而Linux AIO则根据传入的type,决定执行读或写 + | | |-io_prep_pwrite() + | | + | | # 执行IO操作 + | |-WriteFile() # 对于Win调用该函数 + | |-os_aio_linux_dispatch() # 对于LINUX_NATIVE_AIO需要执行该函数,将IO请求分发给内核层 + | | |-io_submit() # 调用AIO接口函数发送 + | | + | |-os_aio_windows_handle() # Win下如果AIO_SYNC调用则通过该函数等待AIO结束 + | |-... ... # 根据传入的array判断是否为sync_array + | |-WaitForSingleObject() # 是则等待指定的slot aio操作完成 + | |-WaitForMultipleObjects() # 否则等待array中所有的aio操作完成 + | |-GetOverlappedResult() # 获取AIO的操作结果 + | |-os_aio_array_free_slot() # 最后释放当前slot + | + | |-fil_node_complete_io() # 如果是同步IO,则会等待完成,也就是确保调用os_aio()已经完成了IO操作 + |-buf_read_ahead_random() # 同时做预读 + +fil_aio_wait() + |-os_aio_linux_handle() + +os_aio_linux_handle +{% endhighlight %} + + 分析完os_aio_windows_handle函数,接着分析Linux下同样功能的函数:os_aio_linux_handle + 无限循环,遍历array,直到定位到一个完成的I/O操作(slot->io_already_done)为止 + 若当前没有完成的I/O,同时有I/O请求,则进入os_aio_linux_collect函数 + os_aio_linux_collect:从kernel中收集更多的I/O请求 + 调用io_getevents函数,进入忙等,等待超时设置为OS_AIO_REAP_TIMEOUT + + /** timeout for each io_getevents() call = 500ms. */ + + #define OS_AIO_REAP_TIMEOUT (500000000UL) + 若io_getevents函数返回ret > 0,说明有完成的I/O,进行一些设置,最主要是将slot->io_already_done设置为TRUE + + slot->io_already_done = TRUE; + 若系统I/O处于空闲状态,那么io_thread线程的主要时间,都在io_getevents函数中消耗。 + + +log_buffer_flush_to_disk() + |-log_write_up_to() + + + + +
    1. + +在这步中会选择不同的 array,包括了 os_aio_sync_array、os_aio_read_array、os_aio_write_array、os_aio_ibuf_array、os_aio_log_array。每个 aio array 在系统启动时调用 os0file.c::os_aio_init() 初始化。 +
      +innobase_start_or_create_for_mysql() {
      +    ... ...
      +    os_aio_init(io_limit,            // 每个线程可并发处理pending IO的数量
      +        srv_n_read_io_threads,       // 处理异步read IO线程的数量
      +        srv_n_write_io_threads,      // 处理异步write IO线程的数量
      +        SRV_MAX_N_PENDING_SYNC_IOS); // 同步IO array的slots个数,
      +    ... ...
      +}
      +
      +io_limit:
      +   windows = SRV_N_PENDING_IOS_PER_THREAD = 32
      +     linux = 8 * SRV_N_PENDING_IOS_PER_THREAD = 8 * 32 = 256
      +
      +srv_n_read_io_threads:
      +    通过innobase_read_io_threads/innodb_read_io_threads参数控制
      +    因此可并发处理的异步read page请求为:io_limit * innodb_read_io_threads
      +
      +srv_n_write_io_threads:
      +    通过innobase_write_io_threads/innodb_write_io_threads参数控制
      +    因此可并发处理的异步write请求为:io_limit * innodb_write_io_threads
      +    注意,当超过此限制时,必须将已有的异步IO部分写回磁盘,才能处理新的请求
      +
      +SRV_MAX_N_PENDING_SYNC_IOS:
      +    同步IO不需要处理线程log thread、ibuf thread个数均为1
      +
      +接下来是创建 array 。 +
      +os_aio_init()
      + |-os_aio_array_create()
      +
      +异步 IO 主要包括两大类:A) 预读page,需要通过异步 IO 方式进行;B) 主动merge,Innodb 主线程对需要 merge 的 page 发出异步读操作,在read_thread 中进行实际 merge 处理。

    2. + + + +选择 segment 时,是根据偏移量来计算 segment 的,从而可以尽可能的将相邻的读写请求放到一起,从而有利于 IO 层的合并操作。 +
    + +

    + + + + + + +## 参考 + +XtraDB: The Top 10 enhancements +https://www.percona.com/blog/2009/08/13/xtradb-the-top-10-enhancements/ + +https://forums.cpanel.net/threads/innodb-corruption-repair-guide.418722/ + +http://www.itpub.net/thread-2083877-1-1.html + + + + + + + + + + + + + + + + + +当事务执行速度大于刷脏速度时,Ckp age和Buf age (innodb_flush_log_at_trx_commit!=1时) 都会逐步增长,当达到async点的时候,强制进行异步刷盘或者写Checkpoint,如果这样做还是赶不上事务执行的速度,则为了避免数据丢失,到达sync点的时候,会阻塞其它所有的事务,专门进行刷盘或者写Checkpoint。 + +因此从理论上来说,只要事务执行速度大于脏页刷盘速度,最终都会触发日志保护机制,进而将事务阻塞,导致MySQL操作挂起。 + + + +由于写Checkpoint本身的操作相比写脏页要简单,耗费时间也要少得多,且Ckp sync点在Buf sync点之后,因此绝大部分的阻塞都是阻塞在了Buf sync点,这也是当事务阻塞的时候,IO很高的原因,因为这个时候在不断的刷脏页数据到磁盘。例如如下截图的日志显示了很多事务阻塞在了Buf sync点: + + + +log_free_check() + |-log_check_margins() + +buf_flush_wait_batch_end() + + + + + + +buf_flush_page_cleaner_coordinator() 该函数基本上由page_cleaner每隔1s调用一次 + |-buf_flush_page_cleaner_coordinator() + |-page_cleaner_flush_pages_recommendation() + |-af_get_pct_for_dirty() + | |-buf_get_modified_ratio_pct() + | |-buf_get_total_list_len() + | + |-af_get_pct_for_lsn() 计算是否需要进行异步刷redo log + |-log_get_max_modified_age_async() + +af_get_pct_for_lsn()计算方法涉及变量 +srv_adaptive_flushing_lwm + +srv_flushing_avg_loops + + +innodb_adaptive_flushing +innodb_adaptive_flushing_lwm 百分比,配置自适应flush机制的低水位(low water mark),超过该限制之后,即使没有通过上述参数开启AF,仍然执行AF +innodb_io_capacity +innodb_io_capacity_max redo 刷盘的最大值,如果刷盘落后很多,那么IO可能会超过innodb_io_capacity而小于max +innodb_max_dirty_pages_pct 刷脏时,需要保证没有超过该值;注意,该值是一个目标,并不会影响刷脏的速率。 +innodb_max_dirty_pages_pct_lwm 脏页的低水位,用于决定什么时候开启pre-flush操作,从而保证不会超过上面配置的百分比 +innodb_flushing_avg_loops 决定了利用上述的值循环多少次之后重新计算dirty page和LSN,次数越少对外部的动态变化就越敏感 + + +要刷新多少page和lsn主要代码在af_get_pct_for_dirty()和af_get_pct_for_lsn()中,其中主要控制adaptive flush的代码位于后者函数中。 + +http://www.cnblogs.com/Amaranthus/p/4450840.html + +1.先判断redo log的容量是否到了innodb_adaptive_flushing_lwm低水位阀值。 +2.是否配置了adaptive flush或者age超过了异步刷新的阀值。 +3.lsn_age_factor=age占异步刷新阀值的比例。 +4.要被刷新的比率=innodb_io_capacity_max/innodb_io_capacity*lsn_age_factor* sqrt(innodb_io_capacity)/7.5 + + +定义BP中的页。 +class buf_page_t { +public: + buf_page_state state; + + UT_LIST_NODE_T(buf_page_t) list; 根据state的不同值决定了list的类型 + + UT_LIST_NODE_T(buf_page_t) LRU; + + - BUF_BLOCK_NOT_USED: free, withdraw + - BUF_BLOCK_FILE_PAGE: flush_list + - BUF_BLOCK_ZIP_DIRTY: flush_list + - BUF_BLOCK_ZIP_PAGE: zip_clean + +struct buf_pool_t{ + + +https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb *** + + +https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html +http://mysql.taobao.org/monthly/2015/07/01/ + +系统表空间包括了 InnoDB data dictionary(InnoDB相关的元数据)、doublewrite buffer、the change buffer、undo logs. + +innodb_data_file_path +https://www.slideshare.net/Leithal/mysql-monitoring-mechanisms + + + + + + + + + + + + + + +logs_empty_and_mark_files_at_shutdown()系统关闭时执行sharp checkpoint + + +当事务执行速度大于刷脏速度时,Ckp age和Buf age (innodb_flush_log_at_trx_commit!=1时) 都会逐步增长,当达到 async 点的时候,强制进行异步刷盘或者写 Checkpoint,如果这样做还是赶不上事务执行的速度,则为了避免数据丢失,到达 sync 点的时候,会阻塞其它所有的事务,专门进行刷盘或者写Checkpoint。 + +因此从理论上来说,只要事务执行速度大于脏页刷盘速度,最终都会触发日志保护机制,进而将事务阻塞,导致MySQL操作挂起。 + +class MVCC { +private: + view_list_t m_views; +}; + + +定义BP中的页。 + +class buf_page_t { +public: + buf_page_state state; + + UT_LIST_NODE_T(buf_page_t) list; 根据state的不同值决定了list的类型 + + UT_LIST_NODE_T(buf_page_t) LRU; + + - BUF_BLOCK_NOT_USED: free, withdraw + - BUF_BLOCK_FILE_PAGE: flush_list + - BUF_BLOCK_ZIP_DIRTY: flush_list + - BUF_BLOCK_ZIP_PAGE: zip_clean + +struct buf_pool_t{ + + +https://www.percona.com/blog/2013/10/30/innodb-adaptive-flushing-in-mysql-5-6-checkpoint-age-and-io-capacity/ ******** +https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb *** + +https://yq.aliyun.com/articles/64677 +http://mysql.taobao.org/monthly/2015/06/01/ +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb *** +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_6_multi_threaded +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_5_innodb_adaptive +https://blogs.oracle.com/mysqlinnodb/entry/introducing_page_cleaner_thread_in + + + +MySQL 5.6.2引入了一个新的后台线程page_cleaner,其中包括adaptive flushing、前端线程触发的 async flushing、空闲时的刷新、关闭刷新都是在这个线程完成;目前只有同步刷新放在了前台的查询线程中。 + +page_cleaner线程负责刷脏,基本上是基于如下的两个因素: +1. 没有空闲缓存页,需要按照LRU规则将最近最少使用的页(the least recently used pages)从LRU_list上移除,因此也被称为LRU_list刷新; +2. 需要重用redo log的空间,现在多数(包括InnoDB)数据库都是循环使用redo空间,如果要重用,只有保证redo对应的脏页已经刷新到磁盘才可以,也就是将the oldest modified non-flushed pages从flush_list上移除,被称之为flush_list; + +在进行刷脏时,会导致IO出现尖刺,进而影响到redo log的刷盘,从而影响到系统的性能;为了解决这一问题,引入了 adaptive flushing 策略,这一策略主要作用于 flush_list 的刷脏,当然对 LRU_list 的刷脏也有一些影响。 + + +https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html +http://mysql.taobao.org/monthly/2015/07/01/ + +系统表空间包括了 InnoDB data dictionary(InnoDB相关的元数据)、doublewrite buffer、the change buffer、undo logs. + +innodb_data_file_path +https://www.slideshare.net/Leithal/mysql-monitoring-mechanisms +https://www.percona.com/blog/2014/11/18/mysqls-innodb_metrics-table-how-much-is-the-overhead/ +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb + +http://www.cnblogs.com/digdeep/p/4947694.html ****** +http://hedengcheng.com/?p=220 *** +http://blog.itpub.net/30496894/viewspace-2121517/ + + +Purge 实际上就是一个垃圾回收策略,简单来说,对于类似 "DELETE FROM t WHERE c = 1;" 的 DML,InnoDB 实际上并不会直接就删除,主要原因是为了回滚以及MVCC机制,简述如下: +1. 在记录的控制标志位中,标记该行已经删除; +2. 将修改列的前镜像保存到UNDO log中; +3. 修改聚集索引中的DB_TRX_ID、DB_ROLL_PTR系统列,前者标示最近一次修改的事务信息,后者则指向undo log中的记录,而 undo log 可能会存在同样的两列指向其历史记录。 +另外,B+Tree的合并操作比较耗时,通过后台的异步线程可以避免阻塞用户的事务。 + +当事务已经提交,而且其它事务也不再依赖该记录了,那么就可以删除掉相应的记录,当然,也包括了二级索引对应的记录;这也就是 purge 线程的工作。 + +接下来,看看 purge 是如何工作的? + + +trx_purge是purge任务调度的核心函数,包含三个参数: +* n_purge_threads —>使用到的worker线程数 +* batch_size —-> 由innodb_purge_batch_size控制,表示一次Purge的记录数 +* truncate —>是否truncate history list + +trx_purge() + |-trx_purge_dml_delay() 计算是否需要对dml延迟 + | ### 持有purge_sys->latch的x锁 + |-clone_oldest_view() 复制当前的view,也就是Class MVCC:m_views链表的中尾部 + |-trx_purge_attach_undo_recs() 获取需要清理的undo记录 + | + | ### 多线程 + |-que_fork_scheduler_round_robin() 根据是否是单线程 + |-srv_que_task_enqueue_low() 将线程添加到队列中 + |-que_run_threads() 协调线程也会运行执行一个任务 + |-trx_purge_wait_for_workers_to_complete() 等待任务执行完成 + | + | ### 单线程 + | + |-trx_purge_truncate() 如果需要删除 + http://mysqllover.com/?p=696 + +purge 会复制一份系统中最老的 view,通过这一结构体,可以断定哪些回滚段需要回收。 + + +mysql> show variables like 'innodb%purge%'; ++-----------------------------------------+-------+ +| Variable_name | Value | ++-----------------------------------------+-------+ +| innodb_max_purge_lag | 0 | 如果purge操作比较慢,可以通过该参数设置dml操作的延迟时间 +| innodb_max_purge_lag_delay | 0 | 最大延迟不会超过该参数 +| innodb_purge_batch_size | 300 | 一次处理多少页 +| innodb_purge_rseg_truncate_frequency | 128 | +| innodb_purge_run_now | OFF | +| innodb_purge_stop_now | OFF | +| innodb_purge_threads | 4 | 并发线程数 +| innodb_trx_purge_view_update_only_debug | OFF | ++-----------------------------------------+-------+ +8 rows in set (0.00 sec) +Changes in 5.5 + +In 5.5 there is an option innodb-purge-threads=[0,1] to create a dedicated thread that purges asynchronously if there are UNDO logs that need to be removed. We also introduced another option innodb-purge-batch-size that can be used to fine tune purge operations. The batch size determines how many UNDO log pages purge will parse and process in one pass. + + + The default setting is 20, this is the same as the hard coded value that is in previous InnoDB releases. An interesting side effect of this value is that it also determines when purge will free the UNDO log pages after processing them. It is always after 128 passes, this magic value of 128 is the same as the number of UNDO logs in the system tablespace, now that 5.5 has 128 rollback segments. By increasing the innodb-purge-batch-size the freeing of the UNDO log pages behaviour changes, it will increase the number of UNDO log pages that it removes in a batch when the limit of 128 is reached. This change was seen as necessary so that we could reduce the cost of removing the UNDO log pages for the extra 127 rollback segments that were introduced in 5.5. Prior to this change iterating over the 128 rollback segments to find the segment to truncate had become expensive. + +Changes in 5.6 + +In 5.6 we have the same parameters as 5.5 except that innodb-purge-threads can now be between 0 and 32. This introduces true multi threaded purging. If the value is greater than 1 then InnoDB will create that many purge worker threads and a dedicated purge coordinator thread. The responsibility of the purge coordinator thread is to parse the UNDO log records and parcel out the work to the worker threads. The coordinator thread also purges records, instead of just sitting around and waiting for the worker threads to complete. The coordinator thread will divide the innodb-purge-batch-size by innodb-purge-threads and hand that out as the unit of work for each worker thread. + +对于单表来说,会阻塞在 dict_index_t::lock 中,除非使用分区;对于多表来说是可以并发的。 + + + + + + + + + + +####### 崩溃恢复(Crash Recovery) +Crash Recovery的起点,Checkpoint LSN存储位置? +InnoDB如何完成Redo日志的重做? +InnoDB如何定位哪些事务需要Rollback? +Crash Recovery需要等待Rollbach完成吗? +InnoDB各版本,在Crash Recovery流程上做了哪些优化? +mysqld_safe是否存在自动重启功能? + +ha_recover + +{% highlight text %} +innobase_init() + |-innobase_start_or_create_for_mysql() + | + |-recv_sys_create() 创建崩溃恢复所需要的内存对象 + |-recv_sys_init() + | + |-srv_sys_space.open_or_create() 通过系统表空间,获取flushed_lsn + | |-read_lsn_and_check_flags() + | |-open_or_create() 打开系统表空间 + | |-read_first_page() 读取第一个page + | |-buf_dblwr_init_or_load_pages() 加载double write buffer,如果ibdata日志损坏,则通过dblwr恢复 + | |-validate_first_page() 校验第一个页是否正常 + | |-restore_from_doublewrite() 如果有异常,则从dblwr恢复 + | + |-srv_undo_tablespaces_init() 对于undo log表空间恢复 + | + |-recv_recovery_from_checkpoint_start() ***从redo-log的checkpoint开始恢复;注意,正常启动也会调用 + | |-buf_flush_init_flush_rbt() 创建一个红黑树,用于加速插入flush list + | |-recv_recovery_on=true 表示崩溃恢复已经开始,很多代码逻辑会通过该变量进行判断 + | |-recv_find_max_checkpoint() 从日志中,找出最大的偏移量 + | |- + | |-recv_group_scan_log_recs() + | | |-recv_scan_log_recs() + | |-recv_group_scan_log_recs() + | |-recv_group_scan_log_recs() + | |-recv_init_crash_recovery_spaces() + | + |-trx_sys_init_at_db_start() 完成undo部分操作:收集未成功提交事务,按类别划分,前期准备 + | |-trx_sysf_get() 完成undo-log的收集 + | |-trx_lists_init_at_db_start() 根据undo信息重建未提交事务 + | + |-recv_apply_hashed_log_recs() 应用redo日志 + | |-recv_recover_page() 实际调用recv_recover_page_func() + | + |-trx_purge_sys_create() 构建purge,至此undo信息和undo事务创建结束 + | + |-recv_recovery_from_checkpoint_finish() 完成崩溃恢复 + + + |-os_thread_create() 创建srv_master_thread线程 +{% endhighlight %} + + +主线程主要完成 purge、checkpoint、dirty pages flush 等操作。 + + + +Database was not shutdown normally! # InnoDB开始Crash Recovery{recv_init_crash_recovery_spaces()} +Starting crash recovery. + + + +1. 读取Checkpoint LSN +2. 从Checkpoint LSN开始向前遍历Redo Log File + 重做从Checkpoint LSN开始的所有Redo日志 +3. 重新构造系统崩溃时的事务 + Commit事务,等待Purge线程回收 + Prepare事务,由MySQL Server控制提交或者回滚(与Binlog 2PC相关) + Active事务,回滚 +4. 新建各种后台线程,Crash Recovery完成返回 + + +正常关闭时,会在flush redo log和脏页后,做一次完全同步的checkpoint,并将checkpoint的LSN写到第一个ibdata文件的第一个page中,详细可以参考fil_write_flushed_lsn()。 + + + +http://mysqllover.com/?p=376 + +http://hedengcheng.com/?p=183 + +http://mysql.taobao.org/monthly/2015/05/01/ + +http://mysql.taobao.org/monthly/2016/05/01/ + +http://tech.uc.cn/?p=716 + +http://hedengcheng.com/?p=88InnoDB + +http://mysqllover.com/?p=620 + +http://apprize.info/php/effective/6.html + +http://www.cnblogs.com/chenpingzhao/p/5107480.html + +https://www.xaprb.com/blog/2011/01/29/how-innodb-performs-a-checkpoint/ + +数据库内核分享 + +https://www.slideshare.net/frogd/inno-db-15344119 + +检查保存到磁盘的最大checkpoint LSN与redo-log的LSN是否一致; + + +MySQL · 引擎特性 · InnoDB 崩溃恢复过程 + +http://mysql.taobao.org/monthly/2015/06/01/ + + + + + + + + + + + + + +https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in + + + +http://mysql.taobao.org/monthly/2015/12/01/ +http://hedengcheng.com/?p=148 +read_view +http://www.cnblogs.com/chenpingzhao/p/5065316.html +http://kabike.iteye.com/blog/1820553 +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ +http://mysqllover.com/?p=696 + +隔离级别 +详细可以查看row_search_mvcc()中的实现 + + +row_search_for_mysql() + |-row_search_no_mvcc() # 对于MySQL内部使用的表(用户不可见),不需要MVCC机制 + |-row_search_mvcc() + +row_search_no_mvcc()用于MySQL的内部表使用,通常是一些作为一个较大任务的中间结果存储,所以希望其可以尽快处理,因此不需要MVCC机制。 + + +innodb_force_recovery变量对应源码中的srv_force_recovery变量, + + + + +当innodb_fast_shutdown设置为0时,会导致purge一直工作近两个小时。????? + +从5.5版本开始,purge任务从主线程中独立出来;5.6开始支持多个purge线程,可以通过innodb_purge_threads变量控制。 + +purge后台线程的最大数量可以有32个,包括了一个coordinator线程,以及多个worker线程。 + +在innobase_start_or_create_for_mysql()函数中,会创建srv_purge_coordinator_thread以及srv_worker_thread线程。 + + +srv_purge_coordinator_thread() + |-srv_purge_coordinator_suspend() 如果不需要purge或者上次purge记录数为0,则暂停 + |-srv_purge_should_exit() 判断是否需要退出;fast_shutdown=0则等待所有purge操作完成 + |-srv_do_purge() 协调线程的主要工作,真正调用执行purge操作的函数 + | + |-trx_purge() 防止上次循环结束后又新的记录写入,此处不再使用worker线程 + | + |-trx_purge() 最后对history-list做一次清理,确保所有worker退出 + +srv_worker_thread() + + +最后一次做trx_purge()时,为了防止执行时间过程,批量操作时不再采用innodb_purge_batch_size(300)指定的值,而是采用20。 + + +InnoDB的数据组织方式采用聚簇索引,也就是索引组织表,而二级索引采用(索引键值,主键键值)组合来唯一确定一条记录。 +无论是聚簇索引,还是二级索引,每条记录都包含了一个DELETED-BIT位,用于标识该记录是否是删除记录;除此之外,聚簇索引还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR,分别表示产生当前记录项的事务ID以及指向当前记录的undo信息。 + + + +从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断???? + + +InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下: + +class ReadView { +private: + trx_id_t m_low_limit_id; // +}; + + +mtr_commit(struct mtr_t*) 提交一个mini-transaction,调用mtr_t::commit() + |-mtr_t::Command::execute() 写redo-log,将脏页添加到flush-list,并释放占用资源 + |-mtr_t::Command::prepare_write() 准备写入日志 + | |-fil_names_write_if_was_clean() + |-mtr_t::Command::finish_write() + + + + + + + + + + + + +妈的文件整理文件 + +http://www.sysdb.cn/index.php/2016/01/14/innodb-recovery/ + +http://www.cnblogs.com/liuhao/p/3714012.html + + +持续集成 https://www.zhihu.com/question/23444990 + + + + +buf_flush_batch + + + + + + + + + + + + + +FAQ系列 | 如何避免ibdata1文件大小暴涨 + +0、导读 + + 遇到InnoDB的共享表空间文件ibdata1文件大小暴增时,应该如何处理? + +1、问题背景 + +用MySQL/InnoDB的童鞋可能也会有过烦恼,不知道为什么原因,ibdata1文件莫名其妙的增大,不知道该如何让它缩回去,就跟30岁之后男人的肚腩一样,汗啊,可喜可贺的是我的肚腩还没长出来,hoho~ + +正式开始之前,我们要先知道ibdata1文件是干什么用的。 + +ibdata1文件是InnoDB存储引擎的共享表空间文件,该文件中主要存储着下面这些数据: + + data dictionary + double write buffer + insert buffer/change buffer + rollback segments + undo space + Foreign key constraint system tables + +另外,当选项 innodb_file_per_table = 0 时,在ibdata1文件中还需要存储 InnoDB 表数据&索引。ibdata1文件从5.6.7版本开始,默认大小是12MB,而在这之前默认大小是10MB,其相关选项是 innodb_data_file_path,比如我一般是这么设置的: + + innodb_data_file_path = ibdata1:1G:autoextend + +当然了,无论是否启用了 innodb_file_per_table = 1,ibdata1文件都必须存在,因为它必须存储上述 InnoDB 引擎所依赖&必须的数据,尤其是上面加粗标识的 rollback segments 和 undo space,它俩是引起 ibdata1 文件大小增加的最大原因,我们下面会详细说。 +2、原因分析 + +我们知道,InnoDB是支持MVCC的,它和ORACLE类似,采用 undo log、redo log来实现MVCC特性的。在事务中对一行数据进行修改时,InnoDB 会把这行数据的旧版本数据存储一份在undo log中,如果这时候有另一个事务又要修改这行数据,就又会把该事物最新可见的数据版本存储一份在undo log中,以此类推,如果该数据当前有N个事务要对其进行修改,就需要存储N份历史版本(和ORACLE略有不同的是,InnoDB的undo log不完全是物理block,主要是逻辑日志,这个可以查看 InnoDB 源码或其他相关资料)。这些 undo log 需要等待该事务结束后,并再次根据事务隔离级别所决定的对其他事务而言的可见性进行判断,确认是否可以将这些 undo log 删除掉,这个工作称为 purge(purge 工作不仅仅是删除过期不用的 undo log,还有其他,以后有机会再说)。 + +那么问题来了,如果当前有个事务中需要读取到大量数据的历史版本,而该事务因为某些原因无法今早提交或回滚,而该事务发起之后又有大量事务需要对这些数据进行修改,这些新事务产生的 undo log 就一直无法被删除掉,形成了堆积,这就是导致 ibdata1 文件大小增大最主要的原因之一。这种情况最经典的场景就是大量数据备份,因此我们建议把备份工作放在专用的 slave server 上,不要放在 master server 上。 + +另一种情况是,InnoDB的 purge 工作因为本次 file i/o 性能是在太差或其他的原因,一直无法及时把可以删除的 undo log 进行purge 从而形成堆积,这是导致 ibdata1 文件大小增大另一个最主要的原因。这种场景发生在服务器硬件配置比较弱,没有及时跟上业务发展而升级的情况。 + +比较少见的一种是在早期运行在32位系统的MySQL版本中存在bug,当发现待 purge 的 undo log 总量超过某个值时,purge 线程直接放弃抵抗,再也不进行 purge 了,这个问题在我们早期使用32位MySQL 5.0版本时遇到的比较多,我们曾经遇到这个文件涨到100多G的情况。后来我们费了很大功夫把这些实例都迁移到64位系统下,终于解决了这个问题。 + +最后一个是,选项 innodb_data_file_path 值一开始就没调整或者设置很小,这就必不可免导致 ibdata1 文件增大了。Percona官方提供的 my.cnf 参考文件中也一直没把这个值加大,让我百思不得其解,难道是为了像那个经常被我吐槽的xx那样,故意留个暗门,好方便后续帮客户进行优化吗?(我心理太阴暗了,不好不好~~) + +稍微总结下,导致ibdata1文件大小暴涨的原因有下面几个: + + 有大量并发事务,产生大量的undo log; + 有旧事务长时间未提交,产生大量旧undo log; + file i/o性能差,purge进度慢; + 初始化设置太小不够用; + 32-bit系统下有bug。 + +稍微题外话补充下,另一个热门数据库 PostgreSQL 的做法是把各个历史版本的数据 和 原数据表空间 存储在一起,所以不存在本案例的问题,也因此 PostgreSQL 的事务回滚会非常快,并且还需要定期做 vaccum 工作(具体可参见PostgreSQL的MVCC实现机制,我可能说的不是完全正确哈) +3、解决方法建议 + +看到上面的这些问题原因描述,有些同学可能觉得这个好办啊,对 ibdata1 文件大小进行收缩,回收表空间不就结了吗。悲剧的是,截止目前,InnoDB 还没有办法对 ibdata1 文件表空间进行回收/收缩,一旦 ibdata1 文件的肚子被搞大了,只能把数据先备份后恢复再次重新初始化实例才能恢复原先的大小,或者把依次把各个独立表空间文件备份恢复到一个新实例中,除此外,没什么更好的办法了。 + +当然了,这个问题也并不是不能防范,根据上面提到的原因,相应的建议对策是: + + 升级到5.6及以上(64-bit),采用独立undo表空间,5.6版本开始就支持独立的undo表空间了,再也不用担心会把 ibdata1 文件搞大; + 初始化设置时,把 ibdata1 文件至少设置为1GB以上; + 增加purge线程数,比如设置 innodb_purge_threads = 8; + 提高file i/o能力,该上SSD的赶紧上; + 事务及时提交,不要积压; + 默认打开autocommit = 1,避免忘了某个事务长时间未提交; + 检查开发框架,确认是否设置了 autocommit=0,记得在事务结束后都有显式提交或回滚。 + + + +关于MySQL的方方面面大家想了解什么,可以直接留言回复,我会从中选择一些热门话题进行分享。 同时希望大家多多转发,多一些阅读量是老叶继续努力分享的绝佳助力,谢谢大家 :) + +最后打个广告,运维圈人士专属铁观音茶叶微店上线了,访问:http://yejinrong.com 获得专属优惠 + + + + +MySQL-5.7.7引入的一个系统库sys-schema,包含了一系列视图、函数和存储过程,主要是一些帮助MySQL用户分析问题和定位问题,可以方便查看哪些语句使用了临时表,哪个用户请求了最多的io,哪个线程占用了最多的内存,哪些索引是无用索引等。 + +其数据均来自performance schema和information schema中的统计信息。 + +MySQL 5.7.7 and higher includes the sys schema, a set of objects that helps DBAs and developers interpret data collected by the Performance Schema. sys schema objects can be used for typical tuning and diagnosis use cases. + +MySQL Server blog中有一个很好的比喻: + +For Linux users I like to compare performance_schema to /proc, and SYS to vmstat. + +也就是说,performance schema和information schema中提供了信息源,但是,没有很好的将这些信息组织成有用的信息,从而没有很好的发挥它们的作用。而sys schema使用performance schema和information schema中的信息,通过视图的方式给出解决实际问题的答案。 + +查看是否安装成功 +select * from sys.version; +查看类型 +select * from sys.schema_object_overview where db='sys'; +当然,也可以通过如下命令查看 +show full tables from sys +show function status where db = 'sys'; +show procedure status where db = 'sys' + +user/host资源占用情况 +SHOW TABLES FROM `sys` WHERE + `Tables_in_sys` LIKE 'user\_%' OR + `Tables_in_sys` LIKE 'host\_%' +IO资源使用,包括最近IO使用情况latest_file_io +SHOW TABLES LIKE 'io\_%' +schema相关,包括表、索引使用统计 +SHOW TABLES LIKE 'schema\_%' +等待事件统计 +SHOW TABLES LIKE 'wait%' +语句查看,包括出错、全表扫描、创建临时表、排序、空闲超过95% +SHOW TABLES LIKE 'statement%' +当前正在执行链接,也就是processlist +其它还有一些厂家的帮助函数,PS设置。 +https://www.slideshare.net/Leithal/the-mysql-sys-schema +http://mingxinglai.com/cn/2016/03/sys-schema/ +http://www.itpub.net/thread-2083877-1-1.html + +x$NAME保存的是原始数据,比较适合通过工具调用;而NAME表更适合阅读,比如使用命令行去查看。 + + +select digest,digest_text from performance_schema.events_statements_summary_by_digest\G +CALL ps_trace_statement_digest('891ec6860f98ba46d89dd20b0c03652c', 10, 0.1, TRUE, TRUE); +CALL ps_trace_thread(25, CONCAT('/tmp/stack-', REPLACE(NOW(), ' ', '-'), '.dot'), NULL, NULL, TRUE, TRUE, TRUE); + +优化器调优 +https://dev.mysql.com/doc/internals/en/optimizer-tracing.html + + +MySQL performance schema instrumentation interface(PSI) + +struct PFS_instr_class {}; 基类 + + +通过class page_id_t区分页, + +class page_id_t { +private: + ib_uint32_t m_space; 指定tablespace + ib_uint32_t m_page_no; 页的编号 + + + + + +buf_page_get_gen() 获取数据库中的页 + |-buf_pool_get() 所在buffer pool实例 + |-buf_page_hash_lock_get() + |-buf_page_hash_get_low() 尝试从bp中获取页 + |-buf_read_page() + |-buf_read_page_low() + |-buf_page_init_for_read() 初始化bp + |-buf_LRU_get_free_block() 如果没有压缩,则直接获取空闲页 + |-buf_LRU_add_block() + | + |-buf_buddy_alloc() 压缩页,使用buddy系统 + |-fil_io() + |-buf_block_get_state() 根据页的类型,判断是否需要进一步处理,如ZIP + |-buf_read_ahead_random() + +buf_read_ahead_linear() + +http://www.myexception.cn/database/511937.html +http://blog.csdn.net/taozhi20084525/article/details/17613785 +http://blogread.cn/it/article/5367 +http://mysqllover.com/?p=303 +http://www.cnblogs.com/chenpingzhao/p/5107480.html ??? +https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/innodb-recovery-tablespace-discovery.html +http://mysqllover.com/?p=1214 + + + +[mysqld] +innodb_data_file_path = ibdata1:12M;ibdata2:12M:autoextend + + + +


    文件 IO 操作

    +在 InnoDB 中所有需要持久化的信息都需要文件操作,例如:表文件、重做日志文件、事务日志文件、备份归档文件等。InnoDB 对文件 IO 操作可以是煞费苦心,主要包括两方面:A) 对异步 IO 的实现;B) 对文件操作管理和 IO 调度的实现。

    + +其主要实现代码集中在 os_file.* + fil0fil.* 文件中,其中 os_file.* 是实现基本的文件操作、异步 IO 和模拟异步 IO;fil0fil.* 是对文件 IO 做系统的管理和 space 结构化。

    + +Innodb 的异步 IO 默认使用 libaio。 +

    + + +其中数据刷盘的主要代码在 innodb/buf/buf0flu.c 中。 +
    +buf_flush_batch()
    + |-buf_do_LRU_batch()                         根据传入的type决定调用函数
    + |-buf_do_flush_list_batch()
    +   |-buf_flush_page_and_try_neighbors()
    +     |-buf_flush_try_neighbors()
    +       |-buf_flush_page()                     刷写单个page
    +          |-buf_flush_write_block_low()       实际刷写单个page
    +
    +    buf_flush_write_block_low调用buf_flush_post_to_doublewrite_buf (将page放到double write buffer中,并准备刷写)
    +
    +    buf_flush_post_to_doublewrite_buf 调用 fil_io ( 文件IO的封装)
    +
    +    fil_io 调用 os_aio (aio相关操作)
    +
    +    os_aio 调用 os_file_write (实际写文件操作)
    +
    +
    + + +其中buf_flush_batch 只有两种刷写方式: BUF_FLUSH_LIST 和 BUF_FLUSH_LRU 两种方式的方式和触发时机简介如下: + +BUF_FLUSH_LIST: innodb master线程中 1_second / 10 second 循环中都会调用。触发条件较多(下文会分析) + +BUF_FLUSH_LRU: 当Buffer Pool无空闲page且old list中没有足够的clean page时,调用。刷写脏页后可以空出一定的free page,供BP使用。 + +从触发频率可以看到 10 second 循环中对于 buf_flush_batch( BUF_FLUSH_LIST ) 的调用是10秒一次IO高负载的元凶所在。 + +我们再来看10秒循环中flush的逻辑: + + 通过比较过去10秒的IO次数和常量的大小,以及pending的IO次数,来判断IO是否空闲,如果空闲则buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) ); + + 如果脏页比例超过70,则 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(100) ); + + 否则 buf_flush_batch( BUF_FLUSH_LIST,PCT_IO(10) ); + +可以看到由于SSD对于随机写的请求响应速度非常快,导致IO几乎没有堆积。也就让innodb误认为IO空闲,并决定全力刷写。 + +其中PCT_IO(N) = innodb_io_capacity *N% ,单位是页。因此也就意味着每10秒,innodb都至少刷10000个page或者刷完当前所有脏页。 + +updated on 2013/10/31: 在5.6中官方的adaptive flush算法有所改变,但是空闲状态下innodb_io_capacity对于刷写page数量的影响仍然不改变。 +UNIQUE 索引 IO 与聚簇索引 IO 完全一致,因为二者都必须读取页面,不能进行 Insert Buffer 优化。 + +
    +buf_page_get_gen()
    + |-buf_page_hash_lock_get()                 # 判断所需的页是否在缓存中
    + |-buf_read_page()                          # 如果不存在则直接从文件读取的buff_pool中
    +   |-buf_read_page_low()                    # 实际底层执行函数
    +     |-fil_io()
    +        |-os_aio()                          # 实际是一个宏定义,最终调用如下函数
    +        | |-os_aio_func()                   # 其入参包括了mode,标识同步/异步
    +        |   |-os_file_read_func()           # 同步读
    +        |   | |-os_file_pread()
    +        |   |   |-pread()
    +        |   |
    +        |   |-os_file_write_func()          # 同步写
    +        |   | |-os_file_pwrite()
    +        |   |   |-pwrite()
    +        |   |
    +        |   |-... ...                       # 对于异步操作,不同的mode其写入array会各不相同 #A
    +        |   |-os_aio_array_reserve_slot()   # 从相应队列中选取一个空闲slot,保存需要读写的信息
    +        |   | |
    +        |   | |-local_seg=... ...           # 1. 首先在任务队列中选择一个segment #B
    +        |   | |
    +        |   | |-os_mutex_enter()            # 2. 对队列加锁,遍历该segement,选择空闲的slot,如果没有则等待
    +        |   | |
    +        |   | |                             # 3. 如果array已经满了,根据是否使用AIO决定具体策略
    +        |   | |-os_aio_simulated_wake_handler_threads()    # 非native AIO,模拟唤醒
    +        |   | |-os_wait_event(array->not_full)             # native aio 则等待not_full信号
    +        |   | |
    +        |   | |-os_aio_array_get_nth_slot() # 4. 已经确定是有slot了,选择空闲的slot
    +        |   | |
    +        |   | |-slot... ...                 # 5. 将文件读写请求信息保存在slot,如目标文件、偏移量、数据等
    +        |   | |
    +        |   | |                             # 6. 对于Win AIO、Native AIO采取不同策略
    +        |   | |-ResetEvent(slot->handle)        # 对于Win调用该接口
    +        |   | |-io_prep_pread()                 # 而Linux AIO则根据传入的type,决定执行读或写
    +        |   | |-io_prep_pwrite()
    +        |   |
    +        |   |                               # 执行IO操作
    +        |   |-WriteFile()                       # 对于Win调用该函数
    +        |   |-os_aio_linux_dispatch()           # 对于LINUX_NATIVE_AIO需要执行该函数,将IO请求分发给内核层
    +        |   | |-io_submit()                 # 调用AIO接口函数发送
    +        |   |
    +        |   |-os_aio_windows_handle()       # Win下如果AIO_SYNC调用则通过该函数等待AIO结束
    +        |     |-... ...                     # 根据传入的array判断是否为sync_array
    +        |     |-WaitForSingleObject()           # 是则等待指定的slot aio操作完成
    +        |     |-WaitForMultipleObjects()        # 否则等待array中所有的aio操作完成
    +        |     |-GetOverlappedResult()       # 获取AIO的操作结果
    +        |     |-os_aio_array_free_slot()    # 最后释放当前slot
    +        |
    + |      |-fil_node_complete_io()            # 如果是同步IO,则会等待完成,也就是确保调用os_aio()已经完成了IO操作
    + |-buf_read_ahead_random()                  # 同时做预读
    +
    +fil_aio_wait()
    + |-os_aio_linux_handle()
    +
    +os_aio_linux_handle
    +
    +    分析完os_aio_windows_handle函数,接着分析Linux下同样功能的函数:os_aio_linux_handle
    +        无限循环,遍历array,直到定位到一个完成的I/O操作(slot->io_already_done)为止
    +        若当前没有完成的I/O,同时有I/O请求,则进入os_aio_linux_collect函数
    +            os_aio_linux_collect:从kernel中收集更多的I/O请求
    +                调用io_getevents函数,进入忙等,等待超时设置为OS_AIO_REAP_TIMEOUT
    +
    +            /** timeout for each io_getevents() call = 500ms. */
    +
    +            #define OS_AIO_REAP_TIMEOUT    (500000000UL)
    +                若io_getevents函数返回ret > 0,说明有完成的I/O,进行一些设置,最主要是将slot->io_already_done设置为TRUE
    +
    +                slot->io_already_done = TRUE;
    +                若系统I/O处于空闲状态,那么io_thread线程的主要时间,都在io_getevents函数中消耗。
    +
    +
    +log_buffer_flush_to_disk()
    + |-log_write_up_to()
    +
    + + + +
    1. + +在这步中会选择不同的 array,包括了 os_aio_sync_array、os_aio_read_array、os_aio_write_array、os_aio_ibuf_array、os_aio_log_array。每个 aio array 在系统启动时调用 os0file.c::os_aio_init() 初始化。 +
      +innobase_start_or_create_for_mysql() {
      +    ... ...
      +    os_aio_init(io_limit,            // 每个线程可并发处理pending IO的数量
      +        srv_n_read_io_threads,       // 处理异步read IO线程的数量
      +        srv_n_write_io_threads,      // 处理异步write IO线程的数量
      +        SRV_MAX_N_PENDING_SYNC_IOS); // 同步IO array的slots个数,
      +    ... ...
      +}
      +
      +io_limit:
      +   windows = SRV_N_PENDING_IOS_PER_THREAD = 32
      +     linux = 8 * SRV_N_PENDING_IOS_PER_THREAD = 8 * 32 = 256
      +
      +srv_n_read_io_threads:
      +    通过innobase_read_io_threads/innodb_read_io_threads参数控制
      +    因此可并发处理的异步read page请求为:io_limit * innodb_read_io_threads
      +
      +srv_n_write_io_threads:
      +    通过innobase_write_io_threads/innodb_write_io_threads参数控制
      +    因此可并发处理的异步write请求为:io_limit * innodb_write_io_threads
      +    注意,当超过此限制时,必须将已有的异步IO部分写回磁盘,才能处理新的请求
      +
      +SRV_MAX_N_PENDING_SYNC_IOS:
      +    同步IO不需要处理线程log thread、ibuf thread个数均为1
      +
      +接下来是创建 array 。 +
      +os_aio_init()
      + |-os_aio_array_create()
      +
      +异步 IO 主要包括两大类:A) 预读page,需要通过异步 IO 方式进行;B) 主动merge,Innodb 主线程对需要 merge 的 page 发出异步读操作,在read_thread 中进行实际 merge 处理。

    2. + + + +选择 segment 时,是根据偏移量来计算 segment 的,从而可以尽可能的将相邻的读写请求放到一起,从而有利于 IO 层的合并操作。 +
    + +

    + + + + + + +## 参考 + +XtraDB: The Top 10 enhancements +https://www.percona.com/blog/2009/08/13/xtradb-the-top-10-enhancements/ + +https://forums.cpanel.net/threads/innodb-corruption-repair-guide.418722/ + +http://www.itpub.net/thread-2083877-1-1.html + + + + + + + + + + + + + + + + + +当事务执行速度大于刷脏速度时,Ckp age和Buf age (innodb_flush_log_at_trx_commit!=1时) 都会逐步增长,当达到async点的时候,强制进行异步刷盘或者写Checkpoint,如果这样做还是赶不上事务执行的速度,则为了避免数据丢失,到达sync点的时候,会阻塞其它所有的事务,专门进行刷盘或者写Checkpoint。 + +因此从理论上来说,只要事务执行速度大于脏页刷盘速度,最终都会触发日志保护机制,进而将事务阻塞,导致MySQL操作挂起。 + + + +由于写Checkpoint本身的操作相比写脏页要简单,耗费时间也要少得多,且Ckp sync点在Buf sync点之后,因此绝大部分的阻塞都是阻塞在了Buf sync点,这也是当事务阻塞的时候,IO很高的原因,因为这个时候在不断的刷脏页数据到磁盘。例如如下截图的日志显示了很多事务阻塞在了Buf sync点: + + + +log_free_check() + |-log_check_margins() + +buf_flush_wait_batch_end() + + + + + + +buf_flush_page_cleaner_coordinator() 该函数基本上由page_cleaner每隔1s调用一次 + |-buf_flush_page_cleaner_coordinator() + |-page_cleaner_flush_pages_recommendation() + |-af_get_pct_for_dirty() + | |-buf_get_modified_ratio_pct() + | |-buf_get_total_list_len() + | + |-af_get_pct_for_lsn() 计算是否需要进行异步刷redo log + |-log_get_max_modified_age_async() + +af_get_pct_for_lsn()计算方法涉及变量 +srv_adaptive_flushing_lwm + +srv_flushing_avg_loops + + +innodb_adaptive_flushing +innodb_adaptive_flushing_lwm 百分比,配置自适应flush机制的低水位(low water mark),超过该限制之后,即使没有通过上述参数开启AF,仍然执行AF +innodb_io_capacity +innodb_io_capacity_max redo 刷盘的最大值,如果刷盘落后很多,那么IO可能会超过innodb_io_capacity而小于max +innodb_max_dirty_pages_pct 刷脏时,需要保证没有超过该值;注意,该值是一个目标,并不会影响刷脏的速率。 +innodb_max_dirty_pages_pct_lwm 脏页的低水位,用于决定什么时候开启pre-flush操作,从而保证不会超过上面配置的百分比 +innodb_flushing_avg_loops 决定了利用上述的值循环多少次之后重新计算dirty page和LSN,次数越少对外部的动态变化就越敏感 + + +要刷新多少page和lsn主要代码在af_get_pct_for_dirty()和af_get_pct_for_lsn()中,其中主要控制adaptive flush的代码位于后者函数中。 + +http://www.cnblogs.com/Amaranthus/p/4450840.html + +1.先判断redo log的容量是否到了innodb_adaptive_flushing_lwm低水位阀值。 +2.是否配置了adaptive flush或者age超过了异步刷新的阀值。 +3.lsn_age_factor=age占异步刷新阀值的比例。 +4.要被刷新的比率=innodb_io_capacity_max/innodb_io_capacity*lsn_age_factor* sqrt(innodb_io_capacity)/7.5 + + +定义BP中的页。 +class buf_page_t { +public: + buf_page_state state; + + UT_LIST_NODE_T(buf_page_t) list; 根据state的不同值决定了list的类型 + + UT_LIST_NODE_T(buf_page_t) LRU; + + - BUF_BLOCK_NOT_USED: free, withdraw + - BUF_BLOCK_FILE_PAGE: flush_list + - BUF_BLOCK_ZIP_DIRTY: flush_list + - BUF_BLOCK_ZIP_PAGE: zip_clean + +struct buf_pool_t{ + + +https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb *** + +page_cleaner线程负责刷脏,基本上是基于如下的两个因素: +1. 最近最少(the least recently used pages )使用的页将会从LRU_list上移除; +2. the oldest modified non-flushed pages从flush_list上移除; + +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb *** +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_6_multi_threaded +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_5_innodb_adaptive +https://blogs.oracle.com/mysqlinnodb/entry/introducing_page_cleaner_thread_in + +MySQL 5.6.2引入了一个新的后台线程page_cleaner, + +https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html +http://mysql.taobao.org/monthly/2015/07/01/ + +系统表空间包括了 InnoDB data dictionary(InnoDB相关的元数据)、doublewrite buffer、the change buffer、undo logs. + +innodb_data_file_path +https://www.slideshare.net/Leithal/mysql-monitoring-mechanisms + + + + + + + + + + + + + + +logs_empty_and_mark_files_at_shutdown()系统关闭时执行sharp checkpoint + + +当事务执行速度大于刷脏速度时,Ckp age和Buf age (innodb_flush_log_at_trx_commit!=1时) 都会逐步增长,当达到 async 点的时候,强制进行异步刷盘或者写 Checkpoint,如果这样做还是赶不上事务执行的速度,则为了避免数据丢失,到达 sync 点的时候,会阻塞其它所有的事务,专门进行刷盘或者写Checkpoint。 + +因此从理论上来说,只要事务执行速度大于脏页刷盘速度,最终都会触发日志保护机制,进而将事务阻塞,导致MySQL操作挂起。 + +class MVCC { +private: + view_list_t m_views; +}; + +buf_flush_wait_batch_end() + + + + +af_get_pct_for_lsn()计算方法涉及变量 +srv_adaptive_flushing_lwm + +srv_flushing_avg_loops + +storage/innobase/log/log0log.cc + +max_modified_age_sync + + |log_write_up_to() + |-log_write_flush_to_disk_low() + |-fil_flush() + + +#####FLUSH_LRU_LIST Checkpoint +srv_LRU_scan_depth + + +#####Async/Sync Flush Checkpoint + +log_free_check() 用户线程调用 + |-log_check_margins() + |-log_flush_margin() + | |-log_write_up_to() + |-log_checkpoint_margin() 执行sync操作,尝试空出足够的redo空间,避免checkpoint操作,可能会执行刷脏操作 + |-log_buf_pool_get_oldest_modification() 获取BP中最老的lsn,也就是LSN4 + | |-buf_pool_get_oldest_modification() 遍历各个BP实例,找出最大lsn,如果刚初始化完成则返回sys->lsn + | 计算log->lsn-oldest_lsn,如果超过了max_modified_age_sync值,则执行sync操作 + +log_checkpoint_margin 核心函数,用于判断当前age情况,是否需要执行异步甚至是同步刷新。 + +buff async/sync是在前面,因为redo的刷新成本更低 + +buf_pool_resize() BP调整大小时的操作 + |-buf_pool_withdraw_blocks() + + +innodb_adaptive_flushing +innodb_adaptive_flushing_lwm 百分比,配置自适应flush机制的低水位(low water mark),超过该限制之后,即使没有通过上述参数开启AF,仍然执行AF +innodb_io_capacity +innodb_io_capacity_max redo 刷盘的最大值,如果刷盘落后很多,那么IO可能会超过innodb_io_capacity而小于max +innodb_max_dirty_pages_pct 刷脏时,需要保证没有超过该值;注意,该值是一个目标,并不会影响刷脏的速率。 +innodb_max_dirty_pages_pct_lwm 脏页的低水位,用于决定什么时候开启pre-flush操作,从而保证不会超过上面配置的百分比 +innodb_flushing_avg_loops 决定了利用上述的值循环多少次之后重新计算dirty page和LSN,次数越少对外部的动态变化就越敏感 + + +要刷新多少page和lsn主要代码在af_get_pct_for_dirty()和af_get_pct_for_lsn()中,其中主要控制adaptive flush的代码位于后者函数中。 + +http://www.cnblogs.com/Amaranthus/p/4450840.html + +1.先判断redo log的容量是否到了innodb_adaptive_flushing_lwm低水位阀值。 +2.是否配置了adaptive flush或者age超过了异步刷新的阀值。 +3.lsn_age_factor=age占异步刷新阀值的比例。 +4.要被刷新的比率=innodb_io_capacity_max/innodb_io_capacity*lsn_age_factor* sqrt(innodb_io_capacity)/7.5 + + +定义BP中的页。 +class buf_page_t { +public: + buf_page_state state; + + UT_LIST_NODE_T(buf_page_t) list; 根据state的不同值决定了list的类型 + + UT_LIST_NODE_T(buf_page_t) LRU; + + - BUF_BLOCK_NOT_USED: free, withdraw + - BUF_BLOCK_FILE_PAGE: flush_list + - BUF_BLOCK_ZIP_DIRTY: flush_list + - BUF_BLOCK_ZIP_PAGE: zip_clean + +struct buf_pool_t{ + + +https://www.percona.com/blog/2013/10/30/innodb-adaptive-flushing-in-mysql-5-6-checkpoint-age-and-io-capacity/ ******** +https://blogs.oracle.com/mysqlinnodb/entry/redo_logging_in_innodb *** + +https://yq.aliyun.com/articles/64677 +http://mysql.taobao.org/monthly/2015/06/01/ +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb *** +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_6_multi_threaded +https://blogs.oracle.com/mysqlinnodb/entry/mysql_5_5_innodb_adaptive +https://blogs.oracle.com/mysqlinnodb/entry/introducing_page_cleaner_thread_in + + + +MySQL 5.6.2引入了一个新的后台线程page_cleaner,其中包括adaptive flushing、前端线程触发的 async flushing、空闲时的刷新、关闭刷新都是在这个线程完成;目前只有同步刷新放在了前台的查询线程中。 + +page_cleaner线程负责刷脏,基本上是基于如下的两个因素: +1. 没有空闲缓存页,需要按照LRU规则将最近最少使用的页(the least recently used pages)从LRU_list上移除,因此也被称为LRU_list刷新; +2. 需要重用redo log的空间,现在多数(包括InnoDB)数据库都是循环使用redo空间,如果要重用,只有保证redo对应的脏页已经刷新到磁盘才可以,也就是将the oldest modified non-flushed pages从flush_list上移除,被称之为flush_list; + +在进行刷脏时,会导致IO出现尖刺,进而影响到redo log的刷盘,从而影响到系统的性能;为了解决这一问题,引入了 adaptive flushing 策略,这一策略主要作用于 flush_list 的刷脏,当然对 LRU_list 的刷脏也有一些影响。 + + +https://dev.mysql.com/doc/refman/5.6/en/innodb-system-tablespace.html +http://mysql.taobao.org/monthly/2015/07/01/ + +系统表空间包括了 InnoDB data dictionary(InnoDB相关的元数据)、doublewrite buffer、the change buffer、undo logs. + +innodb_data_file_path +https://www.slideshare.net/Leithal/mysql-monitoring-mechanisms +https://www.percona.com/blog/2014/11/18/mysqls-innodb_metrics-table-how-much-is-the-overhead/ +https://blogs.oracle.com/mysqlinnodb/entry/data_organization_in_innodb + +http://www.cnblogs.com/digdeep/p/4947694.html ****** +http://hedengcheng.com/?p=220 *** +http://blog.itpub.net/30496894/viewspace-2121517/ + + +Purge 实际上就是一个垃圾回收策略,简单来说,对于类似 "DELETE FROM t WHERE c = 1;" 的 DML,InnoDB 实际上并不会直接就删除,主要原因是为了回滚以及MVCC机制,简述如下: +1. 在记录的控制标志位中,标记该行已经删除; +2. 将修改列的前镜像保存到UNDO log中; +3. 修改聚集索引中的DB_TRX_ID、DB_ROLL_PTR系统列,前者标示最近一次修改的事务信息,后者则指向undo log中的记录,而 undo log 可能会存在同样的两列指向其历史记录。 +另外,B+Tree的合并操作比较耗时,通过后台的异步线程可以避免阻塞用户的事务。 + +当事务已经提交,而且其它事务也不再依赖该记录了,那么就可以删除掉相应的记录,当然,也包括了二级索引对应的记录;这也就是 purge 线程的工作。 + +接下来,看看 purge 是如何工作的? + + +trx_purge是purge任务调度的核心函数,包含三个参数: +* n_purge_threads —>使用到的worker线程数 +* batch_size —-> 由innodb_purge_batch_size控制,表示一次Purge的记录数 +* truncate —>是否truncate history list + +trx_purge() + |-trx_purge_dml_delay() 计算是否需要对dml延迟 + | ### 持有purge_sys->latch的x锁 + |-clone_oldest_view() 复制当前的view,也就是Class MVCC:m_views链表的中尾部 + |-trx_purge_attach_undo_recs() 获取需要清理的undo记录 + | + | ### 多线程 + |-que_fork_scheduler_round_robin() 根据是否是单线程 + |-srv_que_task_enqueue_low() 将线程添加到队列中 + |-que_run_threads() 协调线程也会运行执行一个任务 + |-trx_purge_wait_for_workers_to_complete() 等待任务执行完成 + | + | ### 单线程 + | + |-trx_purge_truncate() 如果需要删除 + http://mysqllover.com/?p=696 + +purge 会复制一份系统中最老的 view,通过这一结构体,可以断定哪些回滚段需要回收。 + + +mysql> show variables like 'innodb%purge%'; ++-----------------------------------------+-------+ +| Variable_name | Value | ++-----------------------------------------+-------+ +| innodb_max_purge_lag | 0 | 如果purge操作比较慢,可以通过该参数设置dml操作的延迟时间 +| innodb_max_purge_lag_delay | 0 | 最大延迟不会超过该参数 +| innodb_purge_batch_size | 300 | 一次处理多少页 +| innodb_purge_rseg_truncate_frequency | 128 | +| innodb_purge_run_now | OFF | +| innodb_purge_stop_now | OFF | +| innodb_purge_threads | 4 | 并发线程数 +| innodb_trx_purge_view_update_only_debug | OFF | ++-----------------------------------------+-------+ +8 rows in set (0.00 sec) +Changes in 5.5 + +In 5.5 there is an option innodb-purge-threads=[0,1] to create a dedicated thread that purges asynchronously if there are UNDO logs that need to be removed. We also introduced another option innodb-purge-batch-size that can be used to fine tune purge operations. The batch size determines how many UNDO log pages purge will parse and process in one pass. + + + The default setting is 20, this is the same as the hard coded value that is in previous InnoDB releases. An interesting side effect of this value is that it also determines when purge will free the UNDO log pages after processing them. It is always after 128 passes, this magic value of 128 is the same as the number of UNDO logs in the system tablespace, now that 5.5 has 128 rollback segments. By increasing the innodb-purge-batch-size the freeing of the UNDO log pages behaviour changes, it will increase the number of UNDO log pages that it removes in a batch when the limit of 128 is reached. This change was seen as necessary so that we could reduce the cost of removing the UNDO log pages for the extra 127 rollback segments that were introduced in 5.5. Prior to this change iterating over the 128 rollback segments to find the segment to truncate had become expensive. + +Changes in 5.6 + +In 5.6 we have the same parameters as 5.5 except that innodb-purge-threads can now be between 0 and 32. This introduces true multi threaded purging. If the value is greater than 1 then InnoDB will create that many purge worker threads and a dedicated purge coordinator thread. The responsibility of the purge coordinator thread is to parse the UNDO log records and parcel out the work to the worker threads. The coordinator thread also purges records, instead of just sitting around and waiting for the worker threads to complete. The coordinator thread will divide the innodb-purge-batch-size by innodb-purge-threads and hand that out as the unit of work for each worker thread. + +对于单表来说,会阻塞在 dict_index_t::lock 中,除非使用分区;对于多表来说是可以并发的。 + + + + + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-09-03-mysql-repaire-table_init.md b/_drafts/2016-09-03-mysql-repaire-table_init.md new file mode 100644 index 0000000..c82b218 --- /dev/null +++ b/_drafts/2016-09-03-mysql-repaire-table_init.md @@ -0,0 +1,147 @@ +--- +title: MySQL 表修复 +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql,shutdown,关闭 +description: 简单分析下 mysqld 进程关闭的过程,并讨论如何安全地关闭 MySQL 实例。 +--- + + + + +MySQL 提供了一个命令行工具 mysqlcheck 来执行一些表的检查,而实际上这个命令只是提供了一种方便的使用 SQL 语句的方式,会根据不同类型拼接 SQL 语句,真正调用的还是 ```CHECK TABLE```、```REPAIR TABLE```、```ANALYZE TABLE``` 和 ```OPTIMIZE TABLE``` 命令。 + +解析时对应的 SQL 定义如下。 + +{% highlight cpp %} +enum enum_sql_command { + ... ... + SQLCOM_REPAIR, + SQLCOM_OPTIMIZE, + SQLCOM_CHECK, + SQLCOM_ANALYZE, + ... ... +}; +{% endhighlight %} + +关于上述的命令的解析,详细可以参考 ```sql/sql_yacc.yy``` 中的定义。 + +{% highlight text %} +repair: + REPAIR opt_no_write_to_binlog table_or_tables + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_REPAIR; + lex->no_write_to_binlog= $2; + lex->check_opt.init(); + lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; + } + table_list opt_mi_repair_type + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_repair_table(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; + +optimize: + OPTIMIZE opt_no_write_to_binlog table_or_tables + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_OPTIMIZE; + lex->no_write_to_binlog= $2; + lex->check_opt.init(); + lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; + } + table_list + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_optimize_table(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; + +check: + CHECK_SYM table_or_tables + { + LEX *lex=Lex; + + if (lex->sphead) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "CHECK"); + MYSQL_YYABORT; + } + lex->sql_command = SQLCOM_CHECK; + lex->check_opt.init(); + lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; + } + table_list opt_mi_check_type + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_check_table(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; + +analyze: + ANALYZE_SYM opt_no_write_to_binlog table_or_tables + { + LEX *lex=Lex; + lex->sql_command = SQLCOM_ANALYZE; + lex->no_write_to_binlog= $2; + lex->check_opt.init(); + lex->alter_info.reset(); + /* Will be overriden during execution. */ + YYPS->m_lock_type= TL_UNLOCK; + } + table_list + { + THD *thd= YYTHD; + LEX* lex= thd->lex; + DBUG_ASSERT(!lex->m_sql_cmd); + lex->m_sql_cmd= new (thd->mem_root) Sql_cmd_analyze_table(); + if (lex->m_sql_cmd == NULL) + MYSQL_YYABORT; + } + ; +{% endhighlight %} + +而在真正执行时,会调用如下的函数,可以看到实际上就是调用了上述类中的 execute() 函数,而类的定义在 sql/sql_admin.h 中定义,实现同样对应 sql/sql_admin.c 文件。 + +{% highlight cpp %} +int mysql_execute_command(THD *thd, bool first_level) +{ + ... ... + switch (lex->sql_command) { + case SQLCOM_ANALYZE: + case SQLCOM_CHECK: + case SQLCOM_OPTIMIZE: + case SQLCOM_REPAIR: + DBUG_ASSERT(lex->m_sql_cmd != NULL); + res= lex->m_sql_cmd->execute(thd); + break; + } + ... ... +} +{% endhighlight %} + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-09-26-postgresql-pg_rman_init.md b/_drafts/2016-09-26-postgresql-pg_rman_init.md new file mode 100644 index 0000000..986facd --- /dev/null +++ b/_drafts/2016-09-26-postgresql-pg_rman_init.md @@ -0,0 +1,336 @@ +--- +Date: October 19, 2013 +title: PG 备份 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + +# 简介 + +pg_rman 支持全量、增量、归档三种备份模式,支持压缩以及备份集的管理。源码可以参考 [github rman](https://github.com/ossc-db/pg_rman),需要选择相应的版本。 + + + +# Step By Step + +接下来,我们看看如何一步步进行测试。 + +## 0. 准备环境 + +{% highlight text %} +$ pg_ctl -D /var/lib/pgsql/data init +$ mkdir /tmp/archive +$ cat data/postgresql.conf +wal_level = archive +archive_mode = on +archive_command = 'cp %p /tmp/archive/%f' +$ pg_ctl -D /var/lib/pgsql/data -l logfile start + +SELECT name, setting FROM pg_settings WHERE name IN ('wal_level', 'archive_mode', 'archive_command'); +CREATE TABLE foobar(id INT); +INSERT INTO foobar VALUES(generate_series(1, 1000000)); + +$ mkdir /tmp/backup +{% endhighlight %} + +## 1. 初始化 + +实际上就是需要一个目录,用于存放备份的文件以及一些元数据,如备份的配置文件、数据库的 systemid、时间线文件历史等等。该命令只需要两个参数:备份目录、数据库的 $PGDATA 。 + +{% highlight text %} +$ pg_rman -B /tmp/backup -D /var/lib/pgsql/data init +$ tree /tmp/backup/ +|-- backup/ +| |-- pg_xlog/ +| `-- srvlog/ +|-- pg_rman.ini # 生成的配置文件 +|-- system_identifier # 数据库system-id +`-- timeline_history/ + +$ cat pg_rman.ini +ARCLOG_PATH='/tmp/archive' +SRVLOG_PATH='/var/lib/pgsql/data/pg_log' + +$ cat system_identifier +SYSTEM_IDENTIFIER='6340953224459249572' +{% endhighlight %} + +其中 system_identifier 用于区分备份的数据库是不是一个数据库,防止被冲。 + +## 2. 全量备份 + +{% highlight text %} +$ pg_rman backup --backup-path=/tmp/backup --pgdata=/var/lib/pgsql/data \ + --arclog-path=/tmp/archive --srvlog-path=/var/lib/pgsql/data/pg_log \ + --backup-mode=full --with-serverlog --compress-data --smooth-checkpoint \ + --keep-data-days=10 \ + --keep-arclog-files=15 --keep-arclog-days=10 \ + --keep-srvlog-files=10 --keep-srvlog-days=15 \ + --verbose --progress \ + -h 127.0.0.1 -U postgres -d postgres +{% endhighlight %} + +pg_rman 在备份时会记录每个备份文件的 crc 校验码,保存在 file_database.txt 文件中,内容为:路径、文件类型、大小、CRC校验值、权限、时间。 + +此时,可以通过如下命令查看当前备份的状态,正常来说应该是 DONE,需要再执行一次 validate 。 + +{% highlight text %} +$ pg_rman show -B /tmp/backup +{% endhighlight %} + +每个备份集都包含了一个备份状态文件 backup.ini,这个文件中包含了很重要的信息,如 LSN,后面 LSN 将用于比对增量备份时对比数据块的 LSN 是否发生了变化,是否需要备份。 + + +## 3. 数据校验 + +每次备份完,必须要做一次校验,否则备份集不可用来恢复,也不会用来做增量的比较。 + +{% highlight text %} +$ pg_rman validate -B /tmp/backup +{% endhighlight %} + +备份时,会记录每个备份文件的 CRC 校验,用来 validate 做校验,每个目录会对应着一个校验文件。 + +{% highlight text %} +$ ls /tmp/backup/20160826/105229/file_*.txt +file_arclog.txt file_database.txt file_srvlog.txt + +$ cat /tmp/backup/20160826/105229/file_database.txt +... ... +global/2397 F 18446744073709551615 0 0600 2016-08-26 10:45:19 +global/2676 F 18446744073709551615 0 0600 2016-08-26 10:45:18 +global/2677 F 18446744073709551615 0 0600 2016-08-26 10:45:18 +global/2694 F 18446744073709551615 0 0600 2016-08-26 10:45:18 +global/2695 F 18446744073709551615 0 0600 2016-08-26 10:45:18 +global/2697 F 18446744073709551615 0 0600 2016-08-26 10:45:18 +global/2698 F 18446744073709551615 0 0600 2016-08-26 10:45:18 +... ... +{% endhighlight %} + +每行分别对应了路径、文件类型、大小、CRC 校验值、权限、时间等值。另外,每个备份目录下还包括了一个 backup.ini 文件,保存了这个备份的状态,含有一些重要的信息,如 LSN 等。 + + +## 4. 增量备份 + +实际上与上述的全量备份相同,只需要修改 backup-mode 即可。 + +{% highlight text %} +----- 插入一部分数据 +INSERT INTO foobar VALUES(generate_series(1, 1000000)); + +$ pg_rman backup --backup-path=/tmp/backup --pgdata=/var/lib/pgsql/data \ + --arclog-path=/tmp/archive --srvlog-path=/var/lib/pgsql/data/pg_log \ + --backup-mode=incremental --with-serverlog --compress-data --smooth-checkpoint \ + --keep-data-days=10 \ + --keep-arclog-files=15 --keep-arclog-days=10 \ + --keep-srvlog-files=10 --keep-srvlog-days=15 \ + --verbose --progress \ + -h 127.0.0.1 -U postgres -d postgres + +$ pg_rman validate -B /tmp/backup + +----- 查看当前的备份集 +$ pg_rman show -B /tmp/backup +{% endhighlight %} + + +## 5. 按指定时间从catalog删除备份集 + +例如只需要我的备份集能恢复到 2016-08-26 19:59:00,在这个时间点以前,不需要用来恢复到这个时间点的备份全删掉。 + +{% highlight text %} +$ pg_rman delete "2016-09-22 23:12:14" -B /tmp/backup + +----- 物理删除已从catalog删除的备份集 +$ pg_rman purge -B /tmp/backup +{% endhighlight %} + +## 6. 恢复 + +数据恢复时有两个必要的要素:1) 新的 $PGDATA;2) 备份目录。备份恢复的时候有几个参数,实际上与 PG 的恢复配置文件 recovery.conf 中的意思对齐。 + +在恢复时,可以选择原地恢复,或者使用新的 $PGDATA 目录,不过不管是哪种恢复方式,如果在本机恢复,可能会覆盖原有的数据文件,所以最好先将原数据目录重命名。 + + + + + + + +### 5.1 在本机恢复的例子 + +{% highlight text %} +----- 1. 停库 +$ pg_ctl -D /var/lib/pgsql/data -l logfile stop + +----- 2. 重命名原数据相关目录,PGDATA、PG_XLOG、表空间、归档目录 +$ mv /var/lib/pgsql/{data,data.bak} +$ mv /tmp/{archive,archive.bak} + +----- 3. 恢复数据,并修改权限 +$ pg_rman restore -B /tmp/backup -D /var/lib/pgsql/data +$ chmod 700 /var/lib/pgsql/data + +----- 4. 启动恢复目标数据库 +$ pg_ctl start -D /var/lib/pgsql/data + +----- 5. 查看恢复的状态 +postgres=# select pg_is_in_recovery(); + pg_is_in_recovery +------------------- + f +(1 row) +{% endhighlight %} + +恢复时可以指定 $PGDATA,从而恢复到新目录,但是 arch_log、表空间、pg_xlog 目录无法指定新的位置,所以原地还原时,必须注意这些目录可能被覆盖,最好是重命名。 + + + + +# pg_rman 源码浅析 + +可以直接从官方下载相应的源码包,然后可以简单通过如下的命令进行安装。 + +{% highlight text %} +# yum install pam-devel +$ make +{% endhighlight %} + +实际上,pg_rman 的源码非常简单,开始会做基本的参数解析,对于不同的子命令会调用应用的 do_XXX() 函数,如 do_init()、do_show() 等等。 + +pg_rman 会将备份数据保存到 DATE/TIME 目录下,其中备份结果会保存到 backup.ini 文件中,实际上在内存中通过 struct pgBackup 结构体表示。 + + +{% highlight c %} +typedef struct pgBackup +{ + /* Backup Level */ + BackupMode backup_mode; + bool with_serverlog; + bool compress_data; + bool full_backup_on_error; + + /* Status - one of BACKUP_STATUS_xxx */ + BackupStatus status; + + /* Timestamp, etc. */ + TimeLineID tli; + + XLogRecPtr start_lsn; // pg_start_backup()调用返回值 + XLogRecPtr stop_lsn; // pg_stop_backup()调用返回值 + + time_t start_time; + time_t end_time; + time_t recovery_time; + uint32 recovery_xid; + + /* Size (-1 means not-backup'ed) */ + int64 total_data_bytes; + int64 read_data_bytes; + int64 read_arclog_bytes; + int64 read_srvlog_bytes; + int64 write_bytes; + + /* data/wal block size for compatibility check */ + uint32 block_size; + uint32 wal_block_size; + + /* if backup from standby or not */ + bool is_from_standby; + +} pgBackup; +{% endhighlight %} + + + + + +## 增量备份 + +上次备份后,数据块的 LSN 是否发生变化,若从上次备份的 start_lsn 以来没有发生变化,则不备份。 + +{% highlight text %} +do_backup() + |-fgets() # 获取备份目录下system_identifier的值 + |-read_control_file() # 读取当前数据库的system_identifier值 + |=========================== backup start + | + |-do_backup_database() # 备份数据库数据 + | | ===== INFO: copying database files等待时间有点长 + | |-pg_start_backup() + | | + | |-parray_new() # 新建需要复制文件的数组 + | |-dir_list_file() # 获取所有需要复制的目录列表 + | |-dir_print_mkdirs_sh() # 非check模式,则创建mkdirs.sh脚本 + | |-parray_free() # 清空目录列表 + | | + | |-backup_files() # 执行文件备份过程 + | | |-backup_data_file() # 会校验lsn是否需要复制 + | | + | |-pg_stop_backup() + | |-wait_for_archive() + | ===== database backup completed + | + |-do_backup_arclog() # 备份WAL日志 + |-do_backup_srvlog() # 备份系统日志 + | + |-pgBackupWriteIni() # 备份完成将相应信息保存到backup.ini文件中 + | + |-pgBackupDelete() # 清除老的备份数据 +{% endhighlight %} + +在启动时,会读取备份目录下的 system_identifier 文件的内容,与数据库中 global/pg_control 文件保存的相应的标示做对比,该文件保存格式为 ControlFileData 。 + + + + +StartupXLOG() +readRecoveryCommandFile() # 读取恢复文件中的配置项,可以判断保存的变量 + + + + + +# 注意事项 + +* PG 会从配置文件中读取 log_directory、archive_command 的值,所以如果这两个配置项在其它文件中或者在 postgresql.auto.conf 中,则这两个值将不准确。 + +* 在编译 pg_rman 时,需要保证 BLCKSZ、XLOG_BLCKSZ 与服务器相同,因为需要做块的校验、读取 LSN 等操作,都与块大小有关,最好使用 pg_config 获取相同的配置,确保块大小一致。 + +* 通过 pg_start_backup() 启动时,采用的是 exclusive 模式,所以同一时间,只能跑一次上述的命令。 + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-10-01-ansible-introduce_init.md b/_drafts/2016-10-01-ansible-introduce_init.md new file mode 100644 index 0000000..795efed --- /dev/null +++ b/_drafts/2016-10-01-ansible-introduce_init.md @@ -0,0 +1,739 @@ +--- +Date: October 19, 2013 +title: Ansible 简介 +layout: post +comments: true +language: chinese +category: [python] +--- + + +本片文章简单介绍下。 + + + + + +架构图 + +ansible架构图 + +ansible架构图 +工作原理 + +工作原理 + +ansible工作原理 + + 管理端支持 local、 ssh、zeromq 三种方式连接被控端,默认使用 ssh + 可以按照一定规则进行 inventory,管理节点通过模块实现对应操作–ad-hoc + 管理节点可以通过 playbook 实现对多个 task 的集合实现一类功能 + +安装 Ansible + + 源码安装 + +源码安装需要 python2.6 以上版本,依赖 paramiko, PyYAML, Jinja2, simplejsion、 pycrypto模块,可以通过 pip 来安装 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 + + + +// 获取源码 +git clone git://github.com/ansible/ansible.git --recursive +cd ./ansible +// 设置环境变量 +source ./hacking/env-setup +source ./hacking/env-setup.fish +source ./hacking/env-setup -q +// 安装 Python 依赖 +easy_install pip +pip install paramiko PyYAML Jinja2 httplib2 six +// 更新 Ansible +git pull --rebase +git submodule update --init --recursive +// 设置inventory文件 +echo "127.0.0.1" > ~/ansible_hosts +export ANSIBLE_HOSTS=~/ansible_hosts +// 测试命令 +ansible all -m ping --ask-pass + + 常用 Linux 发行版 + +1 +2 +3 +4 +5 +6 +7 +8 + + + +// CentOS、RHEL +yum install ansible +//Ubuntu、Debian +sudo apt-get install software-properties-common +sudo apt-add-repository ppa:ansible/ansible +sudo apt-get update +sudo apt-get install ansible + + 通过 pip 安装最新版 + Ansible 可以通过 pip 安装,同时也会安装 paramiko、PyYAML、jinja2 等 Python 依赖库。 + +1 +2 + + + +apt install python3-pip +pip3 install ansible + +运行 Ansible +添加被控远程主机清单 + +已经安装好了 Ansible ,先在就可以运行 Ansible 了。 首先要在 /etc/ansible/hosts 文件中加入一个或者多个远程 ip 或者 domain。 + +1 +2 + + + +172.16.11.210 +172.16.11.211 + +配置基于 SSH key 方式 连接 + +1 +2 +3 +4 + + + +// 主控端操作 +ssh-keygen -t rsa -q +ssh-copy-id 172.16.11.210 +ssh-copy-id 172.16.11.211 + +运行 Ansible + +1 +2 +3 +4 +5 +6 +7 +8 +9 + + + +ansible all -m ping +172.16.11.210 | SUCCESS => { + "changed": false, + "ping": "pong" +} +172.16.11.211 | SUCCESS => { + "changed": false, + "ping": "pong" +} + +配置 inventory + +Ansible 可以同时操纵属于一个组的多台主机,主机和组的关系是通过 inventory 文件来配置即/etc/ansible/hosts。 +inventory可以通过 IP、Domain 来指定,未分组的机器要保留在 host 文件顶部,通过[] 来配置分组信息。 +主机与组 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 + + + +// 简单分组 +[web1] +web210.example.com +web211.example.com +// 配置端口号 +[web2] +172.16.11.210:8000 +172.16.11.211:8000 +// 定义别名和端口 +[web] +www ansible_ssh_port=1234 ansible_ssh_host=172.16.11.211 ansible_ssh_pass=passwd \\ 远程ip,ssh登陆用户、密码 +other1 ansible_connertion=ssh ansible_ssh_user = illlusion +//执行主机,支持正则表达 +[web_www] +www[01:10].example.com +db-[a:f].erample.com + +变量 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 + + + +// 主机变量,分配变量给主机,这些变量可以在之后的 playbook 中使用 +[web-www] +www-a http_port=89 maxRequestsPerChild=808 +www-a http_port=303 maxRequestsPerChild=909 +//组的变量,组也可以赋予变量,这样组成员将继承组变量 +[web-www] +www-a http_port=89 maxRequestsPerChild=808 +www-a http_port=303 maxRequestsPerChild=909 +[web-www:vars] +ntp_server=ntp.example.com +proxy=proxy.example.com +// 组嵌套 可以把组作为另外一个组的子成员,已经分配变量给整个组使用。这些变量可以给 `/usr/bin/ansible-playbook` 使用,但是不能给 `/usr/bin/ansible` 使用 +[group1] +host1 +host2 +[group2] +host3 +host4 +[group3:children] +group1 +group2 +[group3:vars] +some_server=foo.example.com +halon_system_timeout=30 +self_destruct_countdown=60 +escape_pods=2 + +分文件定义 Host 和 group 变量 + +在 inventory 文件中保存的所有变量并不是最佳方式,还可以保存在独立的文件中, 这些文件与 inventory 关联,要求使用 YAML语法。host 和 gourp 变量 要求存储在与 host 和 group 相同的目录名中 + +1 +2 +3 +4 +5 +6 +7 +8 + + + +//假设有一个 host 为 foosball 主机,属于两个组,一个是 raleigh,另外一个是 webserver +/etc/ansible/group_vars/raleigh +/etc/ansible/group_vars/webservers +/etc/ansible/host_vars/foosball +// raleigh 组的变量 +ntp_server: acme.example.org +database_server: storage.example.org + +还可以在组变量目录下创建多个文件,设置不同类型的变量 + +1 +2 + + + +/etc/ansible/group_vars/raleigh/db_settings +/etc/ansible/group_vars/raleigh/cluster_settings + +inventory 参数说明 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 + + + +// 要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置. +ansible_ssh_host +// ssh 端口号.如果不使用默认,通过此变量设置. +ansible_ssh_port +// ssh 用户名 +ansible_ssh_user +// ssh 密码(这种明文方式并不安全,强烈建议使用 --ask-pass 或 SSH 密钥) +ansible_ssh_pass +// sudo 密码(这种方式并不安全,强烈建议使用 --ask-sudo-pass) +ansible_sudo_pass +// sudo 命令路径(适用于1.8及以上版本) +ansible_sudo_exe (new in version 1.8) +// 与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行. +ansible_connection +// ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况. +ansible_ssh_private_key_file +// 目标系统的 shell 类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'. +ansible_shell_type +// 目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python +ansible_python_interpreter + +Patterns + +在 ansible 中, patterns 是指如何确定有那些主机或组被管理,在 playbook 中,它是指对应主机应用特定的配置或执行特定进程。 +ansible + +1 +2 +3 +4 +5 + + + +// 语法 +ansible -m -a +// 示例 +ansible webservers -m service -a "name=httpd state=restarted" + +简单的说, pattern 是一个主机筛选器,支持正则匹配。 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 + + + +// 所有主机 +all +* +//特定主机,支持 ip 地址和主机名 +web211 +172.16.11.211 +//主机组,可以指定特定组或多个组,多个组之间使用`:`分隔 +web_server +web_server:database_server +// 支持正则表达式和逻辑运算 +web_server:!web211 +web_server:&db1 +web_server:database_server:&db1:!web211 + +playbook + +在 playbook 中,通过使用 -e参数可以实现通过变量来确定 group + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 + + + +webservers:!{{excluded}}:&{{required}} +// 通配符 +*.example.com +*.com +//通配符和正则同时 +one*.com:dbservers +// 在 patterns 应用正则式时,使用 `~` 开头 +~(web|db).*\.example\.com +// 索引和切片 +webservers[0] +webservers[0-25] +// 可以在使用 `--limit` 标记来添加排除条件 +ansible-playbook site.yml --limit datacenter2 +// 如果你想从文件读取 hosts,文件名以 @ 为前缀即可. +ansible-playbook site.yml --limit @retry_hosts.txt + +简单执行命令 + +1 +2 +3 +4 +5 +6 +7 +8 +9 + + + +ansible all -m ping +172.16.11.210 | SUCCESS => { + "changed": false, + "ping": "pong" +} +172.16.11.211 | SUCCESS => { + "changed": false, + "ping": "pong" +} + +可用该命令选项: + + -i:指定 inventory 文件,使用当前目录下的 hosts + all:针对 hosts 定义的所有主机执行,这里也可以指定组名或模式 + -m:指定所用的模块,我们使用 Ansible 内置的 ping 模块来检查能否正常管理远端机器 + -u:指定远端机器的用户 + +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 + + + +ansible all -m ping \\ping 所有的节点 +ansible 127* -m ping +ansible -i /etc/ansible/hosts -m command -a "uptime" // 指定 pattens 文件 +ansible all -m ping -u test +ansible all -m ping -u test --sudo +ansible all -m ping -u test --sudo --sudo-user tom +ansible testhost -m setup -a "filter=ansible_all_ipv4_addresses" \\使用 filter 过滤信息 +ansible testhosts -a "/sbin/reboot" -f 10 \\重启testhosts组的所有机器,每次重启10台 +ansible testhosts -m copy -a "src=/etc/hosts dest=/tmp/hosts" \\拷贝本地hosts 文件到testhosts组所有主机的/tmp/hosts +ansible webservers -m file -a "dest=/srv/foo/a.txt mode=600" \\file 模块允许更改文件的用户及权限 +ansible webservers -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan" +ansible webservers -m file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=directory" \\使用 file 模块创建目录,类似 mkdir -p +ansible webservers -m file -a "dest=/path/to/c state=absent" \\file 模块允许更改文件的用户及权限 +ansible testhosts -a 'cal' \\默认是使用 command 模块,所以使用command的命令时不用添加 -m +ansible webhosts -m command -a 'date' \\在 hosts 文件中的 webhosts 组下的所有主机都使用 date 命令 +ansible webhosts -m command -a 'ping' \\在 hosts 文件中的 webhosts 组下的所有主机都使用 date 命令 +ansible testhosts -m service -a "name=ntpd state=restarted" +使用 user 模块对于创建新用户和更改、删除已存在用户非常方便: +ansible all -m user -a "name=foo password=" +ansible all -m user -a "name=foo state=absent" +// 服务管理: +ansible webservers -m service -a "name=httpd state=restarted" \\重启 webservers 组所有主机的 httpd 服务 +ansible webservers -m service -a "name=httpd state=started" \\确保 webservers 组所有主机的 httpd 是启动的 +ansible webservers -m service -a "name=httpd state=stopped" \\确保 webservers 组所有主机的 httpd 是关闭的 +//后台运行,长时间运行的操作可以放到后台执行,ansible 会检查任务的状态;在主机上执行的同一个任务会分配同一个 job ID +ansible all -B 3600 -a "/usr/bin/long_running_operation --do-stuff" \\后台执行命令 3600s,-B 表示后台执行的时间 +ansible all -m async_status -a "jid=123456789" \\检查任务的状态 +ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff" \\后台执行命令最大时间是 1800s 即 30 分钟,-P 每 60s 检查下状态默认 15s +// 搜集系统信息 +ansible all -m setup \\搜集主机的所有系统信息 +ansible all -m setup --tree /tmp/facts \\搜集系统信息并以主机名为文件名分别保存在 /tmp/facts 目录 +ansible all -m setup -a 'filter=ansible_*_mb' \\搜集和内存相关的信息 +ansible all -m setup -a 'filter=ansible_eth[0-2]' \\搜集网卡信息 + +Ad-Hoc + +执行 Ad-Hoc 跟在 Linux 执行命令差不多, 用来快速完成简单的任务。 +语法 + +1 + + + +ansible [host or group] -m [module_name] -a [commond] [ ansible-options ] + +实例 + + 执行安装程序, 安装 python-simplejson + +1 + + + +ansible all -m raw -a 'yum -y install python-simplejson' + + 重启 web 服务 + 假如 web_server 是一个组, 这里组里面有很多webserver,先在需要在 web_server 组上的左右机器执行 reboot 命令, -f 参数会 fork 出 10 个子进程,以并行的方式执行 reboot,即每次重启 10 台 + +1 + + + +ansible web_server -a "/sbin/reboot" -f 10 + +在执行时,默认是以当前用户身份去执行该命令,如果需要执行执行用户,添加 -u username,或者需要使用 sudo 去执行,添加 -u username --sudo [--ask-sudo-pass]。如果不是以 passwordless 的模式执行 sudo,应加上 –ask-sudo-pass (-K)选项,加上之后会提示你输入 密码.使用 passwordless 模式的 sudo, 更容易实现自动化,但不要求一定要使用 passwordless sudo. + + 文件传输 + +ansible 的另外一种用法就是可以以并行的方式同时 scp 大量的文件到多台主机。 + +1 + + + +ansible all -m copy -a "src=/opt/ansible/test.txt dest=/opt/ansible/test.txt" + +如果是用 playbook,择可以利用 template 模块来实现更高级操作。 + +使用 file 模块 可以修改文件的属主和权限 + +1 + + + +ansible all -m file -a 'dest=/opt/ansible/test.txt mode=600 owner=nobody group=nobody' + +使用 file 模块还可以创建、删除目录和文件 + +1 +2 +3 +4 +5 + + + +// 创建目录 +ansible all -m file -a 'dest=/opt/ansible/test mode=755 owner=root group=root state=directory' +// 删除目录和文件 +ansible all -m file -a 'dest=/opt/ansible/test state=absent' + +更多详见copy模块说明 + + 包管理 + +ansible 提供了对 yum 和 apt 的支持 + +1 +2 +3 +4 +5 + + + + // 安装软件包 + ansible all -m yum -a 'name=vim state=present' +// 卸载软件包 + ansible all -m yum -a 'name=vim state=absent' + +在不同的发行版的软件包管理软件, ansible 有其对应的模块, 如果没有,你可以使用 command 模块去安装软件。 +更多详见package模块说明 + + 用户和组管理 + +1 +2 +3 +4 +5 +6 +7 +8 + + + +// 创建用户 +ansible all -m user -a 'name=charlie password=123456 state=present' +// 修改用户, 增加属组和修改shell +ansible all -m user -a 'name=Cc groups=nobody shell=/sbin/nologin state=present' +//移除用户 +ansible all -m user -a 'name=Cc state=absent' + +更多参数详见user模块说明 + + 服务管理 + +1 +2 +3 +4 +5 +6 + + + +// 启动服务 +ansible all -m service -a 'name=rsyslog state=started' +// 重启服务 +ansible all -m service -a 'name=rsyslog state=restarted' +// 停止服务 +ansible all -m service -a 'name=rsyslog state=stopped' + + 系统自身变量获取 + +ansible 可以通过 setup 模块来获取客户端自身的以便固有信息,叫做 facts + +1 +2 +3 +4 +5 + + + +// 获取所有 facts 变量 +ansible all -m setup +// 通过 filter 获取某一个 fact 变量 +ansible all -m setup -a 'filter=ansible_*mb' + + + + + + +{% highlight text %} +$ ansible all -m ping +{% endhighlight %} + + +http://docs.ansible.com/ansible/dev_guide/developing_api.html +http://www.tuicool.com/articles/uMBz2e6 +http://blog.csdn.net/python_tty/article/details/51387667 +http://docs.ansible.com/ansible/dev_guide/developing_api.html +http://blog.itpub.net/30109892/viewspace-2063898/ +http://blog.coocla.org/how-to-use-ansible-python-api-2.0.html +http://lixcto.blog.51cto.com/4834175/1434604 +http://www.jianshu.com/p/8558befb16c1 +http://www.jianshu.com/p/81da20dd9931 +https://czero000.github.io/2016/10/19/the-advanced-ansible.html + + +执行命令    ansible rds -a "id" --ask-pass批量修改密码    ansible rds -m user -a "name=jinyang password=Tools@123" -b -k + + +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2016-10-03-ansible-tips_init.md b/_drafts/2016-10-03-ansible-tips_init.md new file mode 100644 index 0000000..b7bfe52 --- /dev/null +++ b/_drafts/2016-10-03-ansible-tips_init.md @@ -0,0 +1,39 @@ +--- +Date: October 19, 2013 +title: Ansible 杂项 +layout: post +comments: true +language: chinese +category: [webserver] +--- + + + + + +# 用户转换 + +Ansible 2.0 通过 become 插件完成用户的转换,可以参考 [Become (Privilege Escalation)](http://docs.ansible.com/ansible/become.html) ,例如在 inventory 中,可以通过如下方式指定。 + +{% highlight text %} +webserver ansible_user=manager ansible_ssh_pass=PASSWORD ansible_become=true ansible_become_method=sudo ansible_become_user=root ansible_become_pass=PASSWORD +{% endhighlight %} + +假设使用的配置项如下,那么实际执行的命令为。 + +{% highlight python %} +ansible_become_method=sudo ansible_become_user=root +sudo -H -S -p"passwd" -u root /bin/bash -c "CMD" # 用户密码 + +ansible_become_method=su ansible_become_user=root +su root -c "CMD" # root密码 +{% endhighlight %} + +关于 inventory 中选项的设置可以参考 [Inventory ansile-doc](http://docs.ansible.com/ansible/intro_inventory.html) 。 + + + +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2016-10-03-jquery-introduce_init.md b/_drafts/2016-10-03-jquery-introduce_init.md new file mode 100644 index 0000000..aafe2ad --- /dev/null +++ b/_drafts/2016-10-03-jquery-introduce_init.md @@ -0,0 +1,249 @@ +--- +Date: October 19, 2013 +title: jquery 使用 +layout: post +comments: true +language: chinese +category: [webserver] +--- + + + + + + + + + + + +## AJAX + +load(url, [data], [callback]) 载入远程HTML文件代码并插入至DOM中 + url (String): 请求的HTML页的URL地址; + data (Map) : 发送至服务器的key/value数据,可选; + callback : 请求完成时(不需要是success的)的回调函数,可选。 + +jQuery.get( url, [data], [callback] ):使用GET方式来进行异步请求 +jQuery.post( url, [data], [callback], [type] ) :使用POST方式来进行异步请求 +jQuery.getScript( url, [callback] ) : 通过 GET 方式请求载入并执行一个 JavaScript 文件。 +jQuery Ajax 事件 +jQuery.ajax( options ) : 通过 HTTP 请求加载远程数据 +jQuery.ajaxSetup( options ) : 设置全局 AJAX 默认选项。 + +设置 AJAX 请求默认地址为 "/xmlhttp/",禁止触发全局 AJAX 事件,用 POST 代替默认 GET 方法。其后的 AJAX 请求不再设置任何选项参数。 + +jQuery 代码: + +$.ajaxSetup({ + url: "/xmlhttp/", + global: false, + type: "POST" +}); +$.ajax({ data: myData }); + + + + serialize() 与 serializeArray() + + serialize() : 序列表表格内容为字符串。 + + serializeArray() : 序列化表格元素 (类似 '.serialize()' 方法) 返回 JSON 数据结构数据。 + + 示例::%s/\s\+$//g + + + + +http://www.cnblogs.com/onlys/articles/jQuery.html + +## jquery 选择器 + +jQuery 的选择器可谓之强大无比,这里简单地总结一下常用的元素查找方法 + +$("#myELement") 选择id值等于myElement的元素,id值不能重复在文档中只能有一个id值是myElement所以得到的是唯一的元素 +$("div") 选择所有的div标签元素,返回div元素数组 +$(".myClass") 选择使用myClass类的css的所有元素 +$("*") 选择文档中的所有的元素,可以运用多种的选择方式进行联合选择:例如$("#myELement,div,.myclass") + +层叠选择器: +$("form input") 选择所有的form元素中的input元素 +$("#main > *") 选择id值为main的所有的子元素 +$("label + input") 选择所有的label元素的下一个input元素节点,经测试选择器返回的是label标签后面直接跟一个input标签的所有input标签元素 +$("#prev ~ div") 同胞选择器,该选择器返回的为id为prev的标签元素的所有的属于同一个父元素的div标签 + +基本过滤选择器: +$("tr:first") 选择所有tr元素的第一个 +$("tr:last") 选择所有tr元素的最后一个 +$("input:not(:checked) + span") + +过滤掉:checked的选择器的所有的input元素 + +$("tr:even") 选择所有的tr元素的第0,2,4... ...个元素(注意:因为所选择的多个元素时为数组,所以序号是从0开始) + +$("tr:odd") 选择所有的tr元素的第1,3,5... ...个元素 +$("td:eq(2)") 选择所有的td元素中序号为2的那个td元素 +$("td:gt(4)") 选择td元素中序号大于4的所有td元素 +$("td:ll(4)") 选择td元素中序号小于4的所有的td元素 +$(":header") +$("div:animated") +内容过滤选择器: + +$("div:contains('John')") 选择所有div中含有John文本的元素 +$("td:empty") 选择所有的为空(也不包括文本节点)的td元素的数组 +$("div:has(p)") 选择所有含有p标签的div元素 +$("td:parent") 选择所有的以td为父节点的元素数组 +可视化过滤选择器: + +$("div:hidden") 选择所有的被hidden的div元素 +$("div:visible") 选择所有的可视化的div元素 +属性过滤选择器: + +$("div[id]") 选择所有含有id属性的div元素 +$("input[name='newsletter']") 选择所有的name属性等于'newsletter'的input元素 + +$("input[name!='newsletter']") 选择所有的name属性不等于'newsletter'的input元素 + +$("input[name^='news']") 选择所有的name属性以'news'开头的input元素 +$("input[name$='news']") 选择所有的name属性以'news'结尾的input元素 +$("input[name*='man']") 选择所有的name属性包含'news'的input元素 + +$("input[id][name$='man']") 可以使用多个属性进行联合选择,该选择器是得到所有的含有id属性并且那么属性以man结尾的元素 + +子元素过滤选择器: + +$("ul li:nth-child(2)"),$("ul li:nth-child(odd)"),$("ul li:nth-child(3n + 1)") + +$("div span:first-child") 返回所有的div元素的第一个子节点的数组 +$("div span:last-child") 返回所有的div元素的最后一个节点的数组 +$("div button:only-child") 返回所有的div中只有唯一一个子节点的所有子节点的数组 + +表单元素选择器: + +$(":input") 选择所有的表单输入元素,包括input, textarea, select 和 button + +$(":text") 选择所有的text input元素 +$(":password") 选择所有的password input元素 +$(":radio") 选择所有的radio input元素 +$(":checkbox") 选择所有的checkbox input元素 +$(":submit") 选择所有的submit input元素 +$(":image") 选择所有的image input元素 +$(":reset") 选择所有的reset input元素 +$(":button") 选择所有的button input元素 +$(":file") 选择所有的file input元素 +$(":hidden") 选择所有类型为hidden的input元素或表单的隐藏域 + +表单元素过滤选择器: + +$(":enabled") 选择所有的可操作的表单元素 +$(":disabled") 选择所有的不可操作的表单元素 +$(":checked") 选择所有的被checked的表单元素 +$("select option:selected") 选择所有的select 的子元素中被selected的元素 + + + +选取一个 name 为”S_03_22″的input text框的上一个td的text值 +$(”input[@ name =S_03_22]“).parent().prev().text() + +名字以”S_”开始,并且不是以”_R”结尾的 +$(”input[@ name ^='S_']“).not(”[@ name $='_R']“) + +一个名为 radio_01的radio所选的值 +$(”input[@ name =radio_01][@checked]“).val(); + + + + + +$("A B") 查找A元素下面的所有子节点,包括非直接子节点 +$("A>B") 查找A元素下面的直接子节点 +$("A+B") 查找A元素后面的兄弟节点,包括非直接子节点 +$("A~B") 查找A元素后面的兄弟节点,不包括非直接子节点 + +1. $("A B") 查找A元素下面的所有子节点,包括非直接子节点 + +例子:找到表单中所有的 input 元素 + +HTML 代码: + +
    + + +
    + + +
    +
    + +jQuery 代码: + +$("form input") +结果: + +[ , ] + +2. $("A>B") 查找A元素下面的直接子节点 +例子:匹配表单中所有的子级input元素。 + +HTML 代码: + +
    + + +
    + + +
    +
    + +jQuery 代码: + +$("form > input") +结果: + +[ ] + +3. $("A+B") 查找A元素后面的兄弟节点,包括非直接子节点 +例子:匹配所有跟在 label 后面的 input 元素 + +HTML 代码: + +
    + + +
    + + +
    +
    + +jQuery 代码: + +$("label + input") +结果: + +[ , ] + + +4. $("A~B") 查找A元素后面的兄弟节点,不包括非直接子节点 +例子:找到所有与表单同辈的 input 元素 + +HTML 代码: + +
    + + +
    + + +
    +
    + +jQuery 代码: + +$("form ~ input") +结果: + +[ ] +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2016-10-05-postgresql-backup_init.md b/_drafts/2016-10-05-postgresql-backup_init.md new file mode 100644 index 0000000..6b96978 --- /dev/null +++ b/_drafts/2016-10-05-postgresql-backup_init.md @@ -0,0 +1,548 @@ +--- +Date: October 19, 2013 +title: PG 备份 +layout: post +comments: true +language: chinese +category: [sql] +--- + + + +# Dump + +通过 pg_dump 导出数据时,需要读权限,dump 时不会阻塞其它操作,当然不包括需要排他锁的操作,如 ALTER TABLE 。 + +{% highlight text %} +----- 可以按照不同的级别备份 +$ pg_dump -U postgres dbname > outfile.sql +$ pg_dump -U postgres -n schema-name > outfile.sql +$ pg_dump -U postgres -t table-name > outfile.sql + +----- 在一个事务中恢复,如果出错则退出 +$ psql --single-transaction --set ON_ERROR_STOP=on dbname < infile.sql + +----- 重新生成统计数据 +postgres=# ANALYZE + +----- 将数据导出到另外一个数据库 +$ pg_dump -h host1 dbname | psql -h host2 dbname +{% endhighlight %} + +pg_dump 只会备份一个数据库,不会备份 roles、tablespaces 等信息,为此可以通过 pg_dumpall 导出。 + +{% highlight text %} +----- 全部备份 +$ pg_dumpall > outfile +----- 只备份全局数据 +$ pg_dumpall --globals-only > outfile + +----- 恢复数据 +$ psql -f infile postgres +{% endhighlight %} + + + +# PITR (Point In Time Recovery) + +{% highlight text %} +----- 1. 创建表,并插入100W条记录 +postgres=# CREATE TABLE foobar(id INT); +CREATE TABLE +postgres=# INSERT INTO foobar VALUES(generate_series(1, 1000000)); +INSERT 0 1000000 + +----- 2. 通过如下两种方式可以查看现在数据库的大小 +postgres=# SELECT oid FROM pg_database WHERE datname='postgres'; + oid +------- + 13294 +(1 row) +postgres=# SELECT pg_size_pretty(pg_database_size('postgres')); + pg_size_pretty +---------------- + 42 MB +(1 row) +$ cd $PGDATA/base/13294 && du -sh +42M . + +----- 3. 在$PGDATA/postgresql.conf配置文件中添加如下内容,设置日志归档 +wal_level = archive +archive_mode = on +archive_timeout = 300 # 单位是秒,5分钟强制归档 +archive_command = 'cp %p /tmp/archive/%f' +{% endhighlight %} + +archive_command 会在每次 WAL 日志 16MB 段满的时候才执行,即把其拷贝到 /tmp/archive 目录下,或者是超时同样会做归档。配置完后重启服务器,使各个参数生效,然后开始准备实验。 + +### 1. 做一次基础备份 + +{% highlight text %} +----- 建立一个目录用于存储基础备份 +$ mkdir -p /tmp/backup + +----- 执行SQL,开始备份,在archive目录下生成00000001000000000000000B.00000028.backup文件 +postgres=# SELECT pg_start_backup('bak20160824'); + pg_start_backup +----------------- + 0/B000028 +(1 row) + +----- 将$PGDATA/base目录下的数据保存 +$ tar -czvf /tmp/backup/base.tar.gz /var/lib/pgsql/data + +----- 执行如下SQL +postgres=# SELECT pg_stop_backup(); +NOTICE: pg_stop_backup complete, all required WAL segments have been archived + pg_stop_backup +---------------- + 0/B0000C0 +(1 row) + +----- 切换日志,可以多执行几次 +postgres=# SELECT pg_switch_xlog(); + pg_switch_xlog +---------------- + 0/C0000B0 +(1 row) +{% endhighlight %} + +正常来说,现在已经备份成功,可以查看 /tmp/archive 目录中已经有了备份的 wal 日志段。 + + +接下来进行测试,在原表中再次插入 100W 条记录,并假设此时由于某种原因数据库出问题了,现在的问题是,能否利用之前的“基础备份”+“新产生的WAL日志”恢复数据库。 + +{% highlight text %} +----- 原表中再次插入100W条记录 +postgres=# INSERT INTO foobar VALUES(generate_series(1, 1000000)); +INSERT 0 1000000 + +----- 关闭数据库,并将$PGDATA/data目录改名,模拟故障 +# mv /var/lib/pgsql/{data,data.bak} + +----- 使用postgres用户,尝试将之前的基础备份拷贝到$PGDATA/base目录下,并解压 +$ cp /tmp/backup/base.tar.gz /var/lib/pgsql/base +$ tar -xzvf base.tar.gz + +----- 进入解压得到的data目录,删除pg_xlog文件夹,创建pg_xlog/archive_status文件夹 +$ rm -rf pg_xlog +$ mkdir -p pg_xlog/archive_status + +----- 删除data下的postmaster.pid文件: +$ rm -rf postmaster.pid + +----- 从安装的postgresql95-server的包中复制一份recovery.conf +$ cp /usr/pgsql-9.5/share/recovery.conf.sample recovery.conf + +----- 修改recovery.conf,添加如下内容 +restore_command = 'cp /tmp/archive/%f %p' +{% endhighlight %} + +然后启动数据库,查看表中的数据确实已经恢复了。 + + +# recovery.conf + +这个文件是在恢复的时候使用的,一旦开始恢复便不能使用。可以直接从安装的 postgresql95-server 包中复制一份 recovery.conf 。 + +{% highlight text %} +$ cp /usr/pgsql-9.5/share/recovery.conf.sample recovery.conf +{% endhighlight %} + +如下的配置参数,用于恢复过程中每个阶段的 HOOK 调用函数。 + +{% highlight text %} +restore_command = 'cp /mnt/server/archivedir/%f "%p"' + 获取归档文件的 shell 命令,其中归档恢复 (archive recovery) 必须,流复制 (streaming replication) 可选。 + %f : 需要获取的归档文件名。 + %p : 目的地的路径名+文件名,路径为当前工作目录 (PGDATA) 的相对路径。 + %r : 包含最近一次重启点,也就是能够恢复的最早的文件,可以用来清除多余数据。 + +archive_cleanup_command ( string ) + 这是一个可选配置,用在每次重启点 (restartpoint) 执行。主要用于像 standby 服务器清除不需要的 WAL 文件, + 所有 %r 之前的文件都可以删除,通常用于温备。需要注意的是,如果是多个 standby,需要确保所有的服务器都不 + 需要才可以删除。 + +recovery_end_command ( string ) + 可选,用于恢复后需要执行的命令。 +{% endhighlight %} + +PG 默认会恢复到最后的 WAL 日志,当然也可以通过如下的参数指定想要恢复到的日志点,如果配置了多个,那么会选择最新的日志点。 + +{% highlight text %} +recovery_target = ’immediate’ + 目前只有一个值,表示恢复到最新的一致日志点。 + +recovery_target_name ( string ) + 恢复到通过 pg_create_restore_point() 函数指定的时间点。 + +recovery_target_time ( timestamp ) + 指定恢复到的时间点,与 recovery_target_inclusive 配合使用。 + +recovery_target_xid ( string ) + 指定恢复到的事务 ID,不过需要注意的是,事务 ID 是在开始的赋予的,而保存的顺序可能有所不同,同样 + 需要与 recovery_target_inclusive 配合使用。 +{% endhighlight %} + +如下的选项,可以与上述的方式配合使用。 + +{% highlight text %} +recovery_target_inclusive ( boolean ) + 用于指定到了指定的对象后,需要恢复(true),还是忽略该值 (false),默认是 true 。 + +recovery_target_timeline ( string ) + 指定恢复的时间线,默认采用与基础备份相同的时间线。 + +recovery_target_action ( enum ) + 指定恢复完成之后采取的操作,默认为 pause 。 + pause : 暂停 + promote : 恢复完成后准备接收新连接 + shutdown : 恢复完后停止 +{% endhighlight %} + +对于 standby 模式的参数如下。 + +{% highlight text %} +standby_mode ( boolean ) + 指定是否作为 standby 模式,为 on 时,当恢复到了归档 WAL 日志之后仍然会尝试获取新的日志进行恢复。 + 获取可以通过 restore_command 或者 primary_conninfo 的配置。 + +primary_conninfo ( string ) + 指定连接到主服务器的连接方式。 + +primary_slot_name ( string ) + 当通过流复制方式连接到主使用的 slot,如果 primary_conninfo 设置了则该配置是无效的。 + +trigger_file ( string ) + +recovery_min_apply_delay ( integer ) + 默认standby会收到 WAL 后立即恢复,也可以通过该参数添加延迟时间。 +{% endhighlight %} + + + + + + + + + + + +# Warm-Backup + + + + + + + +为 了简化本文,关于HA的基础就到此为止,下面看看如何使用warm-standby(基于拷贝WAL文件的方法)来实施HA。首先我们来看看warm- standby的含义,根据http://www.postgresql.org/docs/9.1/interactive/high- availability.html,其中的一段话: + +Servers that can modify data are called read/write, master or primary servers. Servers that track changes in the master are called standby or slave servers. A standby server that cannot be connected to until it is promoted to a master server is called a warm standby server, and one that can accept connections and serves read-only queries is called a hot standby server. + +从中我们知道,warm-standby在系统出现fault的时候,可以提升为master,即可以接受客户端的connect连接,并提供数据库的读写,角色如同一个master一样。而根据25.5的说明(如下),hot-standby则在为archive recovery 或 standby mode时,只能接受可读的query。 + +Hot Standby is the term used to describe the ability to connect to the server and run read-only queries while the server is in archive recovery or standby mode. This is useful both for replication purposes and for restoring a backup to a desired state with great precision(这一句什么意思?). + +# 参考 + +[PostgreSQL9.1 Warm-Standby ---之基于拷贝WAL文件的方法](http://blog.sciencenet.cn/home.php?mod=space&uid=419883&do=blog&quickforward=1&id=539178) + + + + + + + + +## pg_standby + +{% highlight text %} +-d 打印详细的调试信息到stderr。 +-s 每隔几秒查看归档目录中是否已经准备好。 +-t 指定trigger file,当该文件出现时会进入 failover 。 +{% endhighlight %} + + + +restore_command = 'pg_standby -d -s 2 -t /tmp/pgsql.trigger.5442 /tmp/archive %f %p %r 2>>standby.log' + + +如果没有 recovery.conf 文件,则表示 PG 没有处于 standby 模式。 + + + + + + + +# Warm-Standby + +## 基于拷贝WAL文件的方法 + +可以分别通过 pg_controldata 命令查看 master 和 standby 的状态,如果正常,则这两者会分别处于 in production 和 in archive recovery 状态。 + +可以在 master 里插入一些新的数据,然后检验 standby 服务端是否是几乎同时在用 WAL 日志恢复。 + +{% highlight text %} +CREATE TABLE foobar (id INT); +INSERT INTO foobar VALUES(generate_series(1, 1000000)); +{% endhighlight %} + +### Failover 阶段 + +在 Failover 时,master 由于某种原因宕掉。 + +{% highlight text %} +pg_ctl stop -m fast -D /var/lib/pgsql/master +{% endhighlight %} + +确定 master 关机后,需要首先在提升 standby 为 master 之前做一些准备,即修改 standby 的主配置文件中的三个参数,一定要注意新增了 archive_failover 目录。此时配置的目的是让后面 Switchover 阶段时,原来的 master 可以使用新 master 的日志。 + +{% highlight text %} +wal_level = archive +archive_mode = on +archive_command = 'cp %p /tmp/archive_failover/%f' +{% endhighlight %} + +结下来通过如下方式提升 standby 为 master 。 + +{% highlight text %} +----- 1. 创建上述指定的trigger文件,可以为空或者"smart" +$ > /tmp/pgsql.trigger.5433 + +----- 2. 发送promote命令,通产需要十几秒钟才能切换成master +$ pg_ctl promote -D /var/lib/pgsql/standby +{% endhighlight %} + +然后重新启动新的 master 库,让 postgresql.conf 生效。 + +{% highlight text %} +$ pg_ctl restart -D /var/lib/pgsql/standby +{% endhighlight %} + +最后需要维护原 master 库,把 fault 修正后,进入下一阶段。 + + +### Switchover + +这个阶段是将原来的 master 重新启动为 standby 模式。 + + + + + +## 基于流复制的方法 + + +## 基于同步复制的方法 + + +PostgreSQL standby 可以通过两种方法来激活成为主库: + +trigger file,配置在recovery.conf中。 +pg_ctl promote发送SIGUSR1信号给postmaster进程。 + +同时,PostgreSQL支持快速激活(fast promote)和非快速激活(fallback promote): + +fast promote 开启数据库读写前,不需要做检查点。而是推到开启读写之后执行一个CHECKPOINT_FORCE检查点。 + +pg_ctl promote发送SIGUSR1信号给postmaster进程。 + + + +# Hot-Standby + + + + + +# 何时做归档 + +{% highlight text %} +wal_level = archive +archive_mode = on +archive_command = 'cp %p /tmp/archive/%f' +{% endhighlight %} + +归档的隐含前提是,WAL 中仍有未归档的日志,通常有三种方式: + +1. 执行 SELECT pg_switch_xlog() 。 + +2. 日志写满 16M,可以在编译的时候修改。 + +3. 超过了 archive_command 设置的时间。 + +# 流复制 + +PG 9.0 引入了主备流复制机制,备库不断的从主库同步相应的数据,并在备库 apply 每个 WAL record,这里的流复制每次传输单位是WAL日志的record。而PostgreSQL9.0之前提供的方法是主库写完一个WAL日志文件后,才把WAL日志文件传送到备库,这样的方式导致主备延迟特别大。同时PostgreSQL9.0之后提供了Hot Standby,备库在应用WAL record的同时也能够提供只读服务,大大提升了用户体验。 + +## 主备总体结构 + +PG 主备流复制的核心部分由 walsender、walreceiver 和 startup 三个进程组成。 + +walsender 进程在主库中,用来发送 WAL 日志记录的,执行顺序如下。 + +{% highlight text %} +PostgresMain() + |-exec_replication_command() + |-StartReplication() + |-WalSndLoop() + |-XLogSendPhysical() +{% endhighlight %} + +walreceiver 进程是用来接收 WAL 日志记录的,执行顺序如下。 + +{% highlight text %} +sigusr1_handler() + |-StartWalReceiver() + |-AuxiliaryProcessMain() + |-WalReceiverMain() + |-walrcv_receive() +{% endhighlight %} + +startup进程是用来apply日志的,执行顺序如下: + +{% highlight text %} +PostmasterMain() + |-StartupDataBase() + |-AuxiliaryProcessMain() + |-StartupProcessMain() + |-StartupXLOG() +{% endhighlight %} + + + + + +![streaming replication architecture]({{ site.url }}/images/databases/postgresql/streaming-replication-architecture.png){: .pull-center width="800"} + + +walsender 和 walreceiver 交互主要分为以下几个步骤。 + +{% highlight text %} + + WalReceiverMain() + |-walrcv_connect() + | for() + |-walrcv_identify_system() # 执行 + |-WalRcvFetchTimeLineHistoryFiles() + +XLogPageRead() + |-WaitForWALToBecomeAvailable() + |-RequestXLogStreaming() # 启动walreceiver进程 +{% endhighlight %} + + +walreceiver启动后通过recovery.conf文件中的primary_conninfo参数信息连向主库,主库通过连接参数replication=true启动walsender进程; + +walreceiver执行identify_system命令,获取主库systemid/timeline/xlogpos等信息,执行TIMELINE_HISTORY命令拉取history文件; + +执行wal_startstreaming开始启动流复制,通过walrcv_receive获取WAL日志,期间也会回应主库发过来的心跳信息(接收位点、flush位点、apply位点),向主库发送feedback信息(最老的事务id),避免vacuum删掉备库正在使用的记录; + +执行walrcv_endstreaming结束流复制,等待startup进程更新receiveStart和receiveStartTLI,一旦更新,进入步骤2。 + + + + + + + + + + + + + + + + + +startup进程进入standby模式和apply日志主要过程: + +读取pg_control文件,找到redo位点;读取recovery.conf,如果配置standby_mode=on则进入standby模式。 + +如果是Hot Standby需要初始化clog、subtrans、事务环境等。初始化redo资源管理器,比如Heap、Heap2、Database、XLOG等。 +读取WAL record,如果record不存在需要调用XLogPageRead->WaitForWALToBecomeAvailable->RequestXLogStreaming唤醒walreceiver从walsender获取WAL record。 + +对读取的WAL record进行redo,通过record->xl_rmid信息,调用相应的redo资源管理器进行redo操作。比如heap_redo的XLOG_HEAP_INSERT操作,就是通过record的信息在buffer page中增加一个record: + + + + + + + + + +# pg_basebackup + +通常在搭建备库时,需要执行如下的步骤: + +1. select pg_start_backup(); +2. 复制 $PGDATA +3. select pg_end_backup(); + +如果利用 pg_basebackup,可以直接一步搞定。 + +# pg_restore + + + + + +# 参考 +[gearman 搭建主备的测试脚本](/reference/databases/postgresql/gearman) +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-10-08-mysql-tmp-memory-table_init.md b/_drafts/2016-10-08-mysql-tmp-memory-table_init.md new file mode 100644 index 0000000..541a391 --- /dev/null +++ b/_drafts/2016-10-08-mysql-tmp-memory-table_init.md @@ -0,0 +1,233 @@ +--- +title: MySQL 临时表和内存表 +layout: post +comments: true +language: chinese +category: [mysql,database] +--- + +MySQL 在处理一些语句时,会自动创建临时表,当然这是客户端无法控制的。 + + + +## 简介 + +会话使用到临时表时,会自动分配,最大使用内存是 ```min(tmp_table_size, max_heap_table_size)``` ,如果内存中超出了限制,就会自动将内存中的数据转化到磁盘,存储在 tmpdir 目录下。 + +首先看下内存表的使用。 + +{% highlight text %} +----- TRANS A -------------------------------------+----- TRANS B -------------------------------- +CREATE TABLE tmp_mem (i INT) ENGINE = memory; +Query OK, 0 rows affected (0.00 sec) + +INSERT INTO tmp_mem VALUES(1); +Query OK, 1 row affected (0.00 sec) + CREATE TABLE tmp_mem (i INT) ENGINE = memory; + ERROR 1050 (42S01): Table 'tmp_mem' already exists + + SELECT * FROM tmp_mem; + +------+ + | i | + +------+ + | 1 | + +------+ + 1 row in set (0.00 sec) +{% endhighlight %} + +内存表会将表结构信息 tmp_mem.frm 保存在磁盘上,而数据则保存在内存中,所以有如下特性: + +1. 由于会在磁盘上保存表结构信息,所以多个会话,创建表的名字不能一样; +2. 一个会话创建表,写入数据之后,对其它会话也是可见的; +3. 服务器重启后内存表里的数据会丢失,但是表结构仍然存在; +4. 可以创建、删除索引,支持唯一索引; +5. 不影响主备复制,主库上插入的数据,备库也可以查到; +6. 可以通过 ```SHOW TABLES``` 查看表。 + +下面来看看临时表。 + +{% highlight text %} +----- TRANS A -------------------------------------+----- TRANS B -------------------------------- +CREATE TEMPORARY TABLE tmp_tbl (i INT); +Query OK, 0 rows affected (0.00 sec) + +INSERT INTO tmp_tbl VALUES(1); +Query OK, 1 row affected (0.00 sec) + +SELECT * FROM tmp_tbl; ++------+ +| i | ++------+ +| 1 | ++------+ +1 row in set (0.00 sec) + CREATE TEMPORARY TABLE tmp_tbl (i INT); + Query OK, 0 rows affected (0.00 sec) + + INSERT INTO tmp_tbl VALUES(2); + Query OK, 1 row affected (0.00 sec) + + SELECT * FROM tmp_mem; + +------+ + | i | + +------+ + | 2 | + +------+ + 1 row in set (0.00 sec) +{% endhighlight %} + +可以看出来,临时表的表结构和数据都保存在内存中,有如下特性: + +1. 不同的会话创建的表名可以相同; +2. 会话消失表结构和数据都消失; +3. 可以创建、删除索引; +4. 主库创建的表,备库查不到; +5. 通过 ```SHOW TABLES``` 无法看到表。 + + +### 配置参数 + + + + + +max_tmp_tables 一个客户能同时保持打开的临时表的最大数量,这个值默认32,可以根据需要调整此值 + + + + + +在某些场景下,服务器会创建内部临时表,当然,这一行为用户是无法控制的,其中常见的场景如下: + +* UNION 查询,部分不使用的场景后面介绍; +* 一些视图相关的使用,例如使用到了 TEMPTABLE、UNION、聚合; +* 处理衍生表时,也就是子查询在 FROM 中; +* 对于子查询或者 semi-join 时创建的表,可查看 [Optimizing Subqueries](https://dev.mysql.com/doc/refman/en/subquery-optimization.html); +* ORDER BY 和 GROUP BY 子句不同,ORDER BY 和 GROUP BY 的列不是第一个表中列; +* DISTINCT 查询并且加上 ORDER BY 可能需要临时表; + + + +### 临时表 + + + + + +它规定了内部内存临时表的最大值,每个线程都要分配。(实际起限制作用的是tmp_table_size和max_heap_table_size的最小值。)如果内存临时表超出了限制,MySQL就会自动地把它转化为基于磁盘的MyISAM表,存储在指定的tmpdir目录下,默认: + +mysql> show variables like "tmpdir"; ++---------------+-------+ +| Variable_name | Value | ++---------------+-------+ +| tmpdir | /tmp/ | ++---------------+-------+ + +优化查询语句的时候,要避免使用临时表,如果实在避免不了的话,要保证这些临时表是存在内存中的。如果需要的话并且你有很多group by语句,并且你有很多内存,增大tmp_table_size(和max_heap_table_size)的值。这个变量不适用与用户创建的内存表(memory table). + +你可以比较内部基于磁盘的临时表的总数和创建在内存中的临时表的总数(Created_tmp_disk_tables和Created_tmp_tables),一般的比例关系是: + +Created_tmp_disk_tables/Created_tmp_tables<5% + +max_heap_table_size + +这个变量定义了用户可以创建的内存表(memory table)的大小.这个值用来计算内存表的最大行数值。这个变量支持动态改变,即set @max_heap_table_size=# + +,但是对于已经存在的内存表就没有什么用了,除非这个表被重新创建(create table)或者修改(alter table)或者truncate table。服务重启也会设置已经存在的内存表为全局max_heap_table_size的值。 + +这个变量和tmp_table_size一起限制了内部内存表的大小。 + +如果想知道更详细的信息,请参考“MySQL是怎样使用内部临时表的?”和“内存存储引擎” + + + + + + +## 临时表 + + + + + + + + +内存表: +1. 参数控制:max_heap_table_size +2. 到达上线后报错。 +3. 表定义保存在磁盘上,数据和索引保存在内存里面。 +4. 不能包含TEXT,BLOB等字段。 + +临时表: +1. 参数控制:tmp_table_size。 +2. 到达上线后创建文件在磁盘上。 +3. 表定义和数据都在内存里。 +4. 可以包含TEXT, BLOB等字段。 + + + + + + + + +1、heap对所有用户的连接是可见的,这使得它非常适合做缓存。 + +2、仅适合使用的场合。heap不允许使用xxxTEXT和xxxBLOB数据类型;只允许使用=和<=>操作符来搜索记录(不允许<、>、<=或>=);不支持auto_increment;只允许对非空数据列进行索引(not null)。 +注:操作符 “<=>” 说明:NULL-safe equal.这个操作符和“=”操作符执行相同的比较操作,不过在两个操作码均为NULL时,其所得值为1而不为NULL,而当一个操作码为NULL时,其所得值为0而不为NULL。 + +3、一旦服务器重启,所有heap表数据丢失,但是heap表结构仍然存在,因为heap表结构是存放在实际数据库路径下的,不会自动删除。重启之后,heap将被清空,这时候对heap的查询结果都是空的。 + +4、如果heap是复制的某数据表,则复制之后所有主键、索引、自增等格式将不复存在,需要重新添加主键和索引,如果需要的话。 + +5、对于重启造成的数据丢失,有以下的解决办法: + a、在任何查询之前,执行一次简单的查询,判断heap表是否存在数据,如果不存在,则把数据重新写入,或者DROP表重新复制某张表。这需要多做一次查询。不过可以写成include文件,在需要用该heap表的页面随时调用,比较方便。 + b、对于需要该heap表的页面,在该页面第一次且仅在第一次查询该表时,对数据集结果进行判断,如果结果为空,则需要重新写入数据。这样可以节省一次查询。 + c、更好的办法是在mysql每次重新启动时自动写入数据到heap,但是需要配置服务器,过程比较复杂,通用性受到限制。 + + + + + + + +有这么几种情况会出现临时内存表: + + 包含 UNION 的query; + 使用了 UNION 或者聚合运算的视图,有个算法专门用于判定一个视图是不是会使用临时表: http://dev.mysql.com/doc/refman/5.0/en/view-algorithms.html ; + 如果query包含 order by 和 group by 子句,并且两个子句中的字段不一样;或者 order by 或者 group by 中包含除了第一张表中的字段; + query中同时包含 distinct 和 order by 。 + +如果一个query的explain结果中的extra字段包含 using temporary ,说明这个query使用了临时表。 + +使用explain的时候extra里面如果输出 using temporary; 说明需要使用临时表,但是这里无论如何都看不出来是不是使用了磁盘临时表(因为是不是使用磁盘临时表要根据query结果的大小来判定,在SQL分析阶段是无法判定的), using filesort 是说没办法使用索引进行排序( order by 和 group by 都需要排序),只能对输出结果进行quicksort,参考 http://www.mysqlperformanceblog.com/2009/03/05/what-does-using-filesort-mean-in-mysql/ + +当MySQL处理query是创建了一个临时表(不论是内存临时表还是磁盘临时表), created_tmp_tables 变量都会增加1。如果创建了一个磁盘临时表, created_tmp_disk_tables 会增加1。 + +可以通过 show session status like 'Created_tmp_%'; 来查看,如果要查看全局的临时表使用次数,将其中的 session 改为 global 即可。 + +一般磁盘临时表会比内存临时表慢,因此要尽可能避免出现磁盘临时表。下面几种情况下,MySQL会直接使用磁盘内存表,要尽可能避免: + + 表中包含了 BLOB 和 TEXT 字段(MEMORY引擎不支持这两种字段); + group by 和 distinct 子句中的有超过512字节的字段; + UNION 以及 UNION ALL 语句中,如果 SELECT 子句中包含了超过512(对于binary string是512字节,对于character是512个字符)的字段。 + + + + + +## 参考 + +[MySQL Reference Manual - Internal Temporary Table Use in MySQL](https://dev.mysql.com/doc/refman/en/internal-temporary-tables.html) 。 + + +http://www.open-open.com/lib/view/open1436690608048.html + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2016-10-13-ansible-development-api-20_init.md b/_drafts/2016-10-13-ansible-development-api-20_init.md new file mode 100644 index 0000000..58643e2 --- /dev/null +++ b/_drafts/2016-10-13-ansible-development-api-20_init.md @@ -0,0 +1,226 @@ +--- +Date: October 19, 2013 +title: Ansible 使用 API 2.0 开发 +layout: post +comments: true +language: chinese +category: [python] +--- + +Ansible 提供了 Python 直接调用的 API,方便对这些软件进行二次开发和整合。 + + + +# 简介 + +在 2.0 之前,使用 API 非常简单,可以参考官方 API 文档 [Python API](http://docs.ansible.com/ansible/dev_guide/developing_api.html),详细的代码如下所示。 + +{% highlight python %} +import ansible.runner + +runner = ansible.runner.Runner( + module_name='ping', + module_args='', + pattern='web*', + forks=10 +) +datastructure = runner.run() +{% endhighlight %} + +到了 Ansible 2.0 以后,对代码重构了大部分的代码逻辑,处理起来就非常麻烦了。 + + + +老的版本只能发送单个命令或者 playbook ,在 2.0 则推荐调用 API 时将 playbook 中的每个任务拆出来,获取每个任务的执行结果,然后可以根据执行结果灵活处理执行。 + + 将执行操作的队列模型,包含各类环境参数设置,归结到“ansible.executor.task_queue_manager”类中 + 将执行过程中的各个task的设置,或者说playbook中的编排内容,归结到“ansible.playbook.play”中 + +上述两个东西,几乎囊括了可以在执行过程中设置的所有参数,足够灵活,也让人抓狂,相当于需要自己写一个1.9版本中的runner。 +他们的确也都是原生类,并非专用于外部调用。 +ansible.executor.task_queue_manager + +这是ansible的一个内部模块(ansible/executor/task_queue_manager.py)。初始化的源码如下: + +class TaskQueueManager: + + ''' + This class handles the multiprocessing requirements of Ansible by + creating a pool of worker forks, a result handler fork, and a + manager object with shared datastructures/queues for coordinating + work between all processes. + + The queue manager is responsible for loading the play strategy plugin, + which dispatches the Play's tasks to hosts. + ''' + + def __init__(self, inventory, variable_manager, loader, options, passwords, stdout_callback=None, run_additional_callbacks=True, run_tree=False): + + self._inventory = inventory + self._variable_manager = variable_manager + self._loader = loader + self._options = options + self._stats = AggregateStats() + self.passwords = passwords + self._stdout_callback = stdout_callback + self._run_additional_callbacks = run_additional_callbacks + self._run_tree = run_tree + + self._callbacks_loaded = False + self._callback_plugins = [] + self._start_at_done = False + self._result_prc = None + + …… + +创建时,需要的主要参数包括: + + inventory --> 由ansible.inventory模块创建,用于导入inventory文件 + variable_manager --> 由ansible.vars模块创建,用于存储各类变量信息 + loader --> 由ansible.parsing.dataloader模块创建,用于数据解析 + options --> 存放各类配置信息的数据字典 + passwords --> 登录密码,可设置加密信息 + stdout_callback --> 回调函数 + +ansible.playbook.play + +ansible.playbook是一个原生模块,既用于CLI也用于API。从源码可以看出来: + +try: + from __main__ import display +except ImportError: + from ansible.utils.display import Display + display = Display() + +ansible.playbook.play(ansible/playbook/play.py)。初始化源码的介绍如下: + +__all__ = ['Play'] + + +class Play(Base, Taggable, Become): + + """ + A play is a language feature that represents a list of roles and/or + task/handler blocks to execute on a given set of hosts. + + Usage: + + Play.load(datastructure) -> Play + Play.something(...) + """ + + 最后,用task_queue_manager(play)来执行,老规矩,源码的官方解释。 + +def run(self, play): + ''' + Iterates over the roles/tasks in a play, using the given (or default) + strategy for queueing tasks. The default is the linear strategy, which + operates like classic Ansible by keeping all hosts in lock-step with + a given task (meaning no hosts move on to the next task until all hosts + are done with the current task). + ''' + +一个完整的例子 + +# -*- coding:utf-8 -*- +# !/usr/bin/env python +# +# Author: Shawn.T +# Email: shawntai.ds@gmail.com +# +# this is the Interface package of Ansible2 API +# + +from collections import namedtuple +from ansible.parsing.dataloader import DataLoader +from ansible.vars import VariableManager +from ansible.inventory import Inventory +from ansible.playbook.play import Play +from ansible.executor.task_queue_manager import TaskQueueManager +from tempfile import NamedTemporaryFile +import os + +class AnsibleTask(object): + def __init__(self, targetHost): + Options = namedtuple( + 'Options', [ + 'listtags', 'listtasks', 'listhosts', 'syntax', 'connection','module_path', + 'forks', 'remote_user', 'private_key_file', 'ssh_common_args', 'ssh_extra_args', + 'sftp_extra_args', 'scp_extra_args', 'become', 'become_method', 'become_user', + 'verbosity', 'check' + ] + ) + + # initialize needed objects + self.variable_manager = VariableManager() + + self.options = Options( + listtags=False, listtasks=False, listhosts=False, syntax=False, connection='smart', + module_path='/usr/lib/python2.7/site-packages/ansible/modules', forks=100, + remote_user='root', private_key_file=None, ssh_common_args=None, ssh_extra_args=None, + sftp_extra_args=None, scp_extra_args=None, become=False, become_method=None, become_user='root', + verbosity=None, check=False + ) + self.passwords = dict(vault_pass='secret') + self.loader = DataLoader() + + # create inventory and pass to var manager + self.hostsFile = NamedTemporaryFile(delete=False) + self.hostsFile.write(targetHost) + self.hostsFile.close() + self.inventory = Inventory(loader=self.loader, variable_manager=self.variable_manager, host_list=self.hostsFile.name) + self.variable_manager.set_inventory(self.inventory) + + def ansiblePlay(self, action): + # create play with tasks + args = "ls /" + play_source = dict( + name = "Ansible Play", + hosts = 'all', + gather_facts = 'no', + tasks = [ + dict(action=dict(module='shell', args=args), register='shell_out'), + dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}'))) + ] + ) + play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader) + + # run it + tqm = None + try: + tqm = TaskQueueManager( + inventory=self.inventory, + variable_manager=self.variable_manager, + loader=self.loader, + options=self.options, + passwords=self.passwords, + stdout_callback='default', + ) + result = tqm.run(play) + finally: + # print result + if tqm is not None: + tqm.cleanup() + os.remove(self.hostsFile.name) + self.inventory.clear_pattern_cache() + return result + +写一个ansibleTask类,创建了上述的各类必要的配置信息对象,最后使用ansibleTask.ansiblePlay()函数执行。 + + inventory文件的动态生成 + +写上面的代码的过程中,碰到一个问题:inventory对象创建时需要一个实体的hosts文件,而文件需要动态生成。 +生成的方法参考了这篇牛逼闪闪的文章。使用tempfile.NamedTemporaryFile这个方法来创建一个有名称的临时文件,可以选择关闭后删除或保留。上面的处理办法是:不删除,在执行完毕之后,通过os.remove(self.hostsFile.name)进行删除。 + + + +# 参考 + +可以参考官方 API 文档 [Python API](http://docs.ansible.com/ansible/dev_guide/developing_api.html) 。 + + + + + +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2016-10-28-jquery-plugins_init.md b/_drafts/2016-10-28-jquery-plugins_init.md new file mode 100644 index 0000000..e4c4933 --- /dev/null +++ b/_drafts/2016-10-28-jquery-plugins_init.md @@ -0,0 +1,62 @@ +--- +title: jquery 常用组件 +layout: post +comments: true +language: chinese +category: [webserver] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + + + + +## NPM + +这是 JavaScript 的一个包管理工具,在 CentOS 中,可以通过 yum 进行安装。 + +{% highlight text %} +$ yum --enablerepo=epel install npm +{% endhighlight %} + + + +## dygraphs + +![dygraphs example]({{ site.url }}/images/webserver/dygraphs_example.png "dygraphs example"){: .pull-center width="80%"} + + + + +## DataTables + +一个很强大的表格解决方案,而且文档非常全。 + + + + + + +## 参考 + +DataTables 很多不错的示例,可以参考 [Examples index](https://datatables.net/examples/index) 。 + +在一行中添加默认值 [Generated content for a column](https://datatables.net/examples/ajax/null_data_source.html) 。 + +介绍了每一列中如何设置一些属性,如可以排序、可以搜索、渲染等,可以参考 [columnDefs](https://datatables.net/reference/option/columnDefs) 。 + +通过 AJAX 重新加载数据 [ajax.reload()](https://datatables.net/reference/api/ajax.reload%28%29) 。 + +自带的 buttons 示例,很不错 [Buttons for DataTables](https://datatables.net/extensions/buttons/examples/) 。 + + + +{% highlight python %} +{% endhighlight %} diff --git a/_drafts/2016-12-03-mysql-sqlmap_init.md b/_drafts/2016-12-03-mysql-sqlmap_init.md new file mode 100644 index 0000000..16a3596 --- /dev/null +++ b/_drafts/2016-12-03-mysql-sqlmap_init.md @@ -0,0 +1,23 @@ +--- +title: sqlmap +layout: post +comments: true +language: chinese +category: [mysql,database] +keywords: mysql +description: 保存一下经常使用的经典 MySQL 资源。 +--- + + + + + + +http://sqlmap.org/ + + +http://huangzp.blog.51cto.com/12434999/1899871 +http://www.freebuf.com/articles/web/29942.html + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2017-01-09-linux-program-pthread-atomic-free-lock_init.md b/_drafts/2017-01-09-linux-program-pthread-atomic-free-lock_init.md new file mode 100644 index 0000000..4899222 --- /dev/null +++ b/_drafts/2017-01-09-linux-program-pthread-atomic-free-lock_init.md @@ -0,0 +1,158 @@ +--- +title: C 无锁编程 +layout: post +comments: true +language: chinese +category: [linux,program] +keywords: linux,lock-free +description: +--- + + + + +## 原子操作 + +在多线程编程时,通常需要对一些常见的基本类型进行操作,如 `int` `float` 等等,一般为了解决竞态条件,通常是通过 mutex、spinlock 等进行保护。 + +如果不进行保护,那么实际得到的值是什么?可以从如下程序进行测试。 + +{% highlight c %} +#include +#include +#include +#include +#include + +#define INC_TO 1000000 // one million... + +int global_int = 0; + +pid_t gettid( void ) +{ + return syscall( __NR_gettid ); +} + +void *thread_routine(void *arg) +{ + int i; + int proc_num = (int)(long)arg; + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(proc_num, &set); + + if (sched_setaffinity(gettid(), sizeof(cpu_set_t), &set)) { + perror( "sched_setaffinity" ); + return NULL; + } + + for (i = 0; i < INC_TO; i++) { + global_int++; + } + + return NULL; +} + +int main(void) +{ + int procs = 0; + int i; + pthread_t *thrs; + + /* Getting number of CPUs */ + procs = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (procs < 0) { + perror("sysconf"); + return -1; + } + + thrs = (pthread_t *)malloc(sizeof(pthread_t) * procs); + if (thrs == NULL) { + perror( "malloc" ); + return -1; + } + + printf("Starting %d threads...\n", procs); + + for (i = 0; i < procs; i++) { + if (pthread_create(&thrs[i], NULL, thread_routine, (void *)(long)i)) { + perror( "pthread_create" ); + procs = i; + break; + } + } + + for (i = 0; i < procs; i++) + pthread_join(thrs[i], NULL); + + free(thrs); + + printf("After doing all the math, global_int value is: %d\n", global_int); + printf("Expected value is: %d\n", INC_TO * procs); + + return 0; +} +{% endhighlight %} + +如上程序中,每个 CPU 会绑定一个线程,并对一个线程累加,不同的平台可能会有所区别,源码可以从 [github atomics.c]({{ site.example_repository }}/linux/pthread/atomics.c) 上下载。 + +一般输出的内容如下,当然不同的平台也可能会输出 `4000000`。 + +{% highlight text %} +$ ./atomics +Starting 4 threads... +After doing all the math, global_int value is: 2933043 +Expected value is: 4000000 +{% endhighlight %} + +在编译使用 `-O2` 参数时会输出 `4000000`,实际上这是编译器优化的效果,将原来的循环直接替换成了加 1000000 。 + +对于 CPU 操作,每次读写、累加都是原子操作,但是几个操作的组合将不再是原子操作。 + +### 原理 + +原子操作对于 CPU 来说很简单,在访问内存时,可以通过特定的指令可以锁定 Front Serial Bus, FSB 。FSB 就是 CPU 与内存通讯的总线,当锁 FSB 时,CPU 就无法访问内存,从而达到原子操作。 + +内核中很早就在使用原子操作了,而 gcc 在 4.1.2 才支持用户模式下的原子操作。 + +假设,有如下的伪代码,看看当两个线程同时操作时会发生什么问题。 + +{% highlight text %} +decrement_atomic_value(); +if (atomic_value() == 0) + fire_a_gun(); +{% endhighlight %} + +假设其执行顺序如下。 + +![hello world logo]({{ site.url }}/images/linux/pthread-atomic-two-threads.png "hello world logo"){: .pull-center } + +对于上述的执行顺序,实际上 line3 不会执行。 + +### 实现 + +gcc 中提供了 加法、减法、或、异或、与、与非,每类分别有两个函数,一个返回操作之前的值,一个返回之后的值。 + +{% highlight text %} +type __sync_fetch_and_add (type *ptr, type value); +type __sync_fetch_and_sub (type *ptr, type value); +type __sync_fetch_and_or (type *ptr, type value); +type __sync_fetch_and_and (type *ptr, type value); +type __sync_fetch_and_xor (type *ptr, type value); +type __sync_fetch_and_nand (type *ptr, type value); + +type __sync_add_and_fetch (type *ptr, type value); +type __sync_sub_and_fetch (type *ptr, type value); +type __sync_or_and_fetch (type *ptr, type value); +type __sync_and_and_fetch (type *ptr, type value); +type __sync_xor_and_fetch (type *ptr, type value); +type __sync_nand_and_fetch (type *ptr, type value); +{% endhighlight %} + +其中的 type 可以取如下的值,包括了 `int` `unsigned int` `long` `unsigned long` `long long` `unsigned long long` 几种类型。 + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2017-01-28-program-c-common-extra_init.md b/_drafts/2017-01-28-program-c-common-extra_init.md new file mode 100644 index 0000000..24d2929 --- /dev/null +++ b/_drafts/2017-01-28-program-c-common-extra_init.md @@ -0,0 +1,435 @@ +--- +title: C 常用库 +layout: post +comments: true +language: chinese +category: [misc] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + + + +## JSON + +一个比较简单的库。 + + +----- 字符串解析 +parser() 内存策略自动管理,返回非NULL时则认为成功,失败时内存由函数内部管理 +parser_opts() 同parser() + +----- 转换为字符串 +print() 可以选择是否要格式化(方便阅读)输出 +cJSON_Print() 只能打印256字节 +cJSON_PrintBuffered() 也可以通过该函数指定缓存的长度 +cJSON_PrintPreallocated() 已经申请了内存,然后作为参数传入 + +----- 动态申请 +new_item() 申请结构体,并初始化为0 +create_null() 申请结构体,设置type类型 +create_true() 申请结构体,设置type类型 +create_false() 申请结构体,设置type类型 +create_bool() 申请结构体,设置type类型 +create_object() 申请结构体,设置type类型 +create_number() 申请结构体,设置type类型,由于历史原因会保存valuedouble和valueint中 +create_string() 申请结构体,设置type类型,字符串复制到valuestring,同时会处理可能出现的内存失败 +create_raw() 与 create_string() 相同 + +create_array() 申请结构体,设置type类型 +create_int_array() 申请结构体,设置type类型,并新建number类型,然后添加到链表中 +create_float_array() 与create_int_array相同 +create_double_array() 与create_int_array相同 +create_string_array() 与create_int_array相同,只是新建的是string类型 + +----- 类型判断、比较 +compare()、is_string() + +----- 其它 +minify()、duplicate() + +cJSON_AddItemToArray() 添加到一个数组中,在链表末尾添加 +cJSON_AddItemReferenceToArray() 创建一个对象并添加到数组中,原对象不变 +cJSON_AddItemReferenceToObject() 创建一个对象并添加到对象中,原对象不变 + +cJSON_GetObjectItem() 获取对象指针,不会从原对象中摘除 +cJSON_GetObjectItemCaseSensitive() 获取对象指针,不会从原对象中摘除 + +cJSON_DetachItemViaPointer() 可以从对象或者数组中摘除一个对象 +cJSON_DetachItemFromArray()  从一个数组中摘除对象 +cJSON_DeleteItemFromArray() 从一个数组中删除对象 + +cJSON_InsertItemInArray() 替换 +cJSON_ReplaceItemViaPointer() 删除 + + +/* The cJSON structure: */ +typedef struct cJSON +{ +    struct cJSON *next;  对象同样通过链表链接 +    struct cJSON *prev; +    struct cJSON *child; array中使用,类似于链表头 +    int type; 类型 +    char *valuestring;  string和raw类型保存 +    int valueint;   已经删除,由于历史原因暂时保留 +    double valuedouble; number类型 + +    /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ +    char *string; +} cJSON; + + +### 使用场景 + +简单介绍下其经常使用的场景,一般不会判断内存是否失败。 + +{% highlight text %} +cJSON *data = cJSON_CreateObject(); // 不判断是否为NULL +cJSON_AddStringToObject(data, "key", "value"); + +cJSON_AddItemToArray(data, metric = cJSON_CreateObject()); +cJSON_AddStringToObject(metric, "foobar", "foobar"); +{% endhighlight %} + +当 `data` 为 `NULL` 时,可能会导致内存泄露,以字符串为例,会通过 `"value"` 创建一个对象,当 `data` 为 `NULL` 时,则会直接退出,那么通过 `"value"` 创建的对象将无法释放。 + + + + + + + + + + + + + + + + +## zlog + +Zlog 是一个不错的 C 日志库,具有如下的特性: + +* 采用 syslog 分类模型,比 log4j 模型更加直接了当; +* 日志格式定制,类似于 log4j 的 pattern layout; +* 多种输出,包括动态文件、静态文件、stdout、stderr、syslog、用户自定义输出函数; +* 运行时手动、自动刷新配置文件(同时保证安全); +* 用户自定义等级; +* 多线程和多进程环境下保证安全转档; +* 精确到微秒; +* MDC,线程键-值对的表,可以扩展用户自定义的字段; +* 自诊断,可以在运行时输出zlog自己的日志和配置状态; +* 不依赖其他库,只要是个 POSIX 系统就成,当然还要一个C99兼容的vsnprintf; + + + +如下是一个简单的示例: + +{% highlight text %} +zlog_init("test_hello.conf"); // 1. 加载配置文件,包括了formats+rules +zlog_get_category("my_cat"); // 2. 获取文件中my_cat对应的规则 +zlog_info(c, "hello, zlog"); // 3. 输出信息 +zlog_fini(); +{% endhighlight %} + +以及其配置文件 `test_hello.conf` 。 + +{% highlight text %} +[formats] +simple = "%m%n" +[rules] +my_cat.DEBUG >stdout; simple +{% endhighlight %} + +其中,与之相关的有三个重要概念: + +* 分类(Category):用于区分不同的输入,实际上就是一个字符串映射的一个分类,用于不同目的的输出。 +* 格式(Format):设置输出日志的格式,比如是否有带有时间戳,是否包含文件位置信息等,上面的例子里的格式就是简单输出信息+换行符。 +* 规则(Rule):把分类、级别、输出文件、格式组合起来,决定一条代码中的日志是否输出,输出到哪里,以什么格式输出。 + +详细的配置项如下。 + +{% highlight text %} +[global] +strict init = true # 严格检查配置文件,如果格式有问题则直接退出 +buffer min = 1024 # 在堆上为每个线程分配内存,日志超过缓存后会以2的倍数自动扩充,直达最大值 +buffer max = 2MB +rotate lock file = zlog.lock # 用于安全转档时的加锁 +default format = "%d %V [%p:%F:%L] %m%n" # 默认的日志格式, + # 2012-02-14 17:03:12 INFO [3758:test_hello.c:39] hello, zlog +file perms = 600 # 设置日志的读写权限 +reload conf period # 写N次之后重新加载配置,原子操作,失败后继续使用之前的配置 +fsync period # 刷磁盘的周期,每个规则日志写了一定次数后会调用fsync(3)刷新日志 + # 用于平衡写日志速度和安全,默认是0,由系统决定刷磁盘时间 +[levels] +TRACE = 10 # 日志等级定义,值越大优先级越高 +CRIT = 130, LOG_CRIT #(level string) = (level int), (syslog level, optional) + +[formats] +simple = "%m%n" # 定义格式化时的格式 +normal = "%d %m%n" + +[rules] +default.* >stdout; simple +*.* "%12.2E(HOME)/log/%c.log", 1MB*12; simple +my_.INFO >stderr; +my_cat.!ERROR "/var/log/aa.log" +my_dog.=DEBUG >syslog, LOG_LOCAL0; simple +my_mice.* $user_define; + + # 输出文件名称 转储大小 转储格式 +my_cat.!ERROR "%E(HOME)/log/out.log", 1M * 3 ~ "%E(HOME)/log/out.log.#r" +{% endhighlight %} + + +### 锁文件 + +用来保证多进程情况下日志安全转档,会在 `zlog_init()` 初始化时以读写权限打开这个文件,转档日志的伪代码如下: + +{% highlight text %} +write(log_file, a_log) + if (log_file > 1M) + if (pthread_mutex_lock succ && fcntl_lock(lock_file) succ) + if (log_file > 1M) rotate(log_file); + fcntl_unlock(lock_file); + pthread_mutex_unlock; +{% endhighlight %} + +`pthread_mutex_lock()` 用于多线程, `fcntl_lock()` 用于多进程,其中后者是 POSIX 建议锁 (man 3 fcntl),这个锁是全系统有效的,在某个进程意外死亡后,操作系统会释放此进程持有的锁。 + +这就是为什么用 fcntl 锁来保证安全转档,注意进程需要对锁文件有读写权限。 + +也可以设置为 `rotate lock file = self` 此时,zlog 不会创建任何锁文件,而是用配置文件作为锁文件,因为是建议锁,用户可以自由的修改存储他们的配置文件。 + + + +### 规则 + +用于描述日志是怎么被过滤、格式化以及被输出的。 + +{% highlight text %} +(category).(level) (output), (options, optional); (format name, optional) +{% endhighlight %} + +在通过 `zlog_init()` 初始化时,所有规则都会被读到内存中;当 `zlog_get_category()` 被调用,规则就被被分配给分类;在实际写日志时,如 `zlog_info()` 被调用的时候,就会比较这个INFO和各条规则的等级,来决定这条日志会不会通过这条规则输出。 + +当 `zlog_reload()` 被调用的时候,配置文件会被重新读入,包括所有的规则,并且重新计算分类对应的规则。 + +#### 级别匹配 + +默认有 6 个级别:`"DEBUG"` `"INFO"` `"NOTICE"` `"WARN"` `"ERROR"` 和 `"FATAL"`,具体匹配方式可以通过如下方式: + +{% highlight text %} +* 所有等级 +foobar.debug 代码内等级>=debug +foobar.=debug 代码内等级==debug +foobar.!debug 代码内等级!=debug +{% endhighlight %} + +在源码中,通过 `zlog_rule_output()` 判断是否需要进行输出。 + +### 文档转储 + +通常是为了防止磁盘被撑爆,或者由于打个文件过大导致 grep 查看不方便,使用方式有几种。 + +#### 1. 按照固定时间段分割 + +{% highlight text %} +nodus.0912.log +nodus.0913.log +nodus.0914.log +{% endhighlight %} + +例如,每天生成一个日志文件,通常每天生成的日志量大致相同,从而可以快速找到某天的日志。 + +可以使用 cronosplit、logrotate、cronolog 类似的工具完成。 + + + + +#### 2. 按照日志大小分割 + +用于短时间内生成大量日志的场景,通常有两种模式,在 nlog 中称之为 Sequence 和 Rolling 。 + +{% highlight text %} +Sequence: + nodus.log (new) + nodus.log.2 (less new) + nodus.log.1 + nodus.log.0 (old) + +Rolling: + nodus.log (new) + nodus.log.0 (less new) + nodus.log.1 + nodus.log.2 (old) +{% endhighlight %} + + + +#### 3. 日志大小切分,加上时间标签 + +如上两种场景的综合。 + +{% highlight text %} +nodus.log +nodus.log-0305.00.log +nodus.log-0501.00.log +nodus.log-0501.01.log +nodus.log-1008.00.log +{% endhighlight %} + + + + +### 自身诊断 + +默认是不打印日志的,可以通过 `export ZLOG_PROFILE_DEBUG=/tmp/zlog.debug.log` 变量进行设置,当然建议设置为 ERROR 这样尽量减小对性能的影响。 + +一般从配置文件中读取了配置信息之后,不会再修改,也可以通过 `zlog_profile()` 函数将配置打印到上述的 ERROR 中。 + + + + +{% highlight text %} +zlog_init() + |-pthread_rwlock_wrlock() zlog_env_lock + |-zlog_init_inner() + | |-pthread_key_create() 创建zlog_thread_key + | |-zlog_conf_new() + | | |-zlog_level_list_new() 保存在conf->levels中 + | | | |-zc_arraylist_new() + | | | |-zlog_level_list_set_default() + | | | + | | |-zc_arraylist_new() conf->formats + | | |-zc_arraylist_new() conf->rules + | | | + | | |-zlog_conf_build_with_file() + | | | |-zlog_conf_parse_line() + | | | |-zlog_rule_new() + | | | |-zlog_rule_parse_path() + | | | |-zc_str_replace_env() 替换环境变量 + | | | |-zc_arraylist_new() + | | | |-zlog_spec_new() 新建specs, + | | | |- + | | | |-zc_arraylist_add() + | | |-zlog_conf_build_without_file() + | | | + | | |-zlog_conf_profile() + | | + | |-zlog_category_table_new() + | |-zlog_record_table_new() + |-zlog_category_table_fetch_category() + |-pthread_rwlock_unlock() + +zlog_get_category() + |-zlog_category_table_fetch_category() 尝试从zlog_env_categories中获取 + |-zlog_category_new() 如果不存在则新建并添加 + | |-zlog_category_obtain_rules() + | | |-zlog_rule_match_category() 遍厉各个rules,匹配满足的分类 + | | |-zc_arraylist_add() + | | |-zlog_cateogry_overlap_bitmap() + | |-zlog_category_profile() + |-zc_hashtable_put() + +zlog_info() + |-zlog_category_needless_level() 通过level_bitmap判断是否需要记录日志 + |-pthread_rwlock_rdlock() 获取zlog_env_lock的读锁 + |-zlog_fetch_thread() + | |-pthread_getspecific() 通过zlog_thread_key获取获取线程的私有变量,如果不存在则新建 + | |-zlog_thread_rebuild_msg_buf() 如果版本号不同,说明配置文件重新加载了 + | |-zlog_thread_rebuild_event() + | + |-va_start() + |-zlog_event_set_fmt() 设置变量格式 + |-zlog_category_output() 真正的日志输出 + | |-zc_arraylist_foreach() 遍厉category中各个fit_rules输出 + | |-zlog_rule_output() 会根据不同的级别输出,输出函数在rule.c文件中配置 + | | |-zlog_rule_output_stdout() + | | |-zlog_format_gen_msg() + | | | |-zlog_buf_restart() + | | | |-zlog_spec_gen_msg() 调用每个pattern_specs + | | |-zlog_buf_str() + |-va_end() + |-pthread_rwlock_unlock() + |-zlog_reload() 判断是否需要重新打开日志 + |-zlog_conf_new() + |-zlog_category_table_update_rules() + |-zlog_category_table_commit_rules() + + +zlog_fini() + |-pthread_rwlock_wrlock() 对zlog_env_lock进行加锁 + |-zlog_fini_inner() + | |-zlog_category_table_del() + | |-zlog_record_table_del() + | |-zlog_conf_del() + |-pthread_rwlock_unlock() + + +zlog_category_table_update_rules() + |-zlog_category_update_rules() + + +zlog_conf_parse_line() + |-zlog_format_new() + + + +zlog_rule_output_dynamic_file_rotate() +zlog_rule_output_static_file_rotate() + +zlog_rotater_rotate() + |-zlog_rotater_trylock() 尝试对文件锁进行加锁 + |-zlog_rotater_lsmv() + |-zlog_rotater_add_archive_files() + |-zlog_rotater_unlock() +{% endhighlight %} + + +## 参考 + +[zlog1使用手册](https://hardysimpson.github.io/zlog/UsersGuide-CN.html) 。 + + + +zlog +vzlog +hzlog +dzlog(使用默认分类) + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2017-03-09-linux-program-miscellaneous_init.md b/_drafts/2017-03-09-linux-program-miscellaneous_init.md new file mode 100644 index 0000000..ffe6205 --- /dev/null +++ b/_drafts/2017-03-09-linux-program-miscellaneous_init.md @@ -0,0 +1,181 @@ +--- +title: Linux C 编程杂项 +layout: post +comments: true +language: chinese +category: [linux,program] +keywords: linux,aio +description: +--- + + + + +## eventfd + +eventfd 在内核版本,2.6.22 以后有效。 + +{% highlight text %} +#include +int eventfd(unsigned int initval, int flags); + +参数: + initval 用于初始化计数器; + flags 设置标志位,常见的有如下: + EFD_NONBLOCK + 设置为非阻塞状态,否则在调用read()时,如果计数器值为0,则会一直堵塞。 + EFD_CLOEXEC + 我的理解是,这个标识被设置的话,调用exec后会自动关闭文件描述符,防止泄漏。 +{% endhighlight %} + +这个函数会创建一个事件对象 (eventfd object), 用来实现进程或者线程间的等待/通知 (wait/notify) 机制,内核会为这个对象维护一个 64 位的计数器,可以通过 `initval` 初始化。 + + + +## CLOEXEC + +关于 `open()` 函数的 `O_CLOEXEC` 模式,以及 `fcntl()` 函数的 `FD_CLOEXEC` 选项,总结如下: + +1. 调用 `open()` 时使用 `O_CLOEXEC` 模式打开的文件描述符,在执行 `exec()` 调用新程序中关闭,且为原子操作。 + +2. 调用 `fcntl()` 设置 `open()` 打开的文件描述符为 `FD_CLOEXEC` 选项时,其效果和在 `open()` 时使用 `O_CLOEXEC` 选项相同,不过不再是原子操作,可能存在竞态条件。 + +3. 通过 `fork()` 调用产生的子进程中不被关闭。 + +4. 调用 `dup` 族类函数得到的新文件描述符将清除 `O_CLOEXEC` 模式。 + + + + + + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2017-06-24-zeromq-c-introduce_init.md b/_drafts/2017-06-24-zeromq-c-introduce_init.md new file mode 100644 index 0000000..1af9705 --- /dev/null +++ b/_drafts/2017-06-24-zeromq-c-introduce_init.md @@ -0,0 +1,20 @@ +--- +title: +layout: post +comments: true +language: chinese +category: [network,linux] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + + + +{% highlight text %} +$ yum install --enablerepo=epel czmq-devel +{% endhighlight %} + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2017-12-12-fastdfs-introduce_init.md b/_drafts/2017-12-12-fastdfs-introduce_init.md new file mode 100644 index 0000000..16d3d44 --- /dev/null +++ b/_drafts/2017-12-12-fastdfs-introduce_init.md @@ -0,0 +1,229 @@ +--- +title: AirFlow 工作流简介 +layout: post +comments: true +language: chinese +category: [misc] +keywords: airflow,dac,Directed Acyclical Graphs,编排工具,有向非循环图 +description: +--- + + + + +https://www.cnblogs.com/Leo_wl/p/6731647.html + + +https://github.com/aleksandar-todorovic/awesome-linux +cmus glances +https://github.com/LewisVo/Awesome-Linux-Software + + +一个SQL注入工具官方地址及其简介 +https://sqlchop.chaitin.cn/ +https://blog.chaitin.cn/sqlchop-the-sqli-detection-engine/ +手动编写的解析工具 +https://github.com/client9/libinjection/ + +########################################### +## FastDFS +########################################### + +这是一个开源的轻量级分布式文件系统,主要功能有文件存储、文件同步、文件访问等,解决了大容量存储和负载均衡的问题,适合以文件为载体的在线服务,如相册网站、视频网站等等,一般文件大小为 4KB < file_size < 500MB 。 + +服务端包括了 Tracker Server 和 Storage Server 两个部分,前者用于负载均衡和调度,后者用于文件存储。两种类型服务可以多节点部署,单个节点宕机不会影响整体提供服务。 + +### Tracker Server + +负责管理所有的 Storage Server 和 Group ,每个 Storage Server 启动后会主动连接到 Tracker ,告知自己所属的 Group 同时保持心跳信息,Tracker 会根据上报的元数据在内存中维护映射信息。 + +### Storage Server + +真正存储数据的地方,以分组 Group 为单位,每个分组包含了多个 Storage Server ,数据互为备份,存储空间以最小的为准。 + +实际上 Group 是一个逻辑概念,主要是为了能够方便的进行应用隔离、负责均衡和副本数定制,如果 Group 内机器出现故障需要该 Group 内的其它机器重新同步数据。 + +## 安装 + +首先是通过源码编译打包,V5.0 之前的版本依赖 libevent 而 V5.0 以后不再依赖 libevent ,V5.04 开始依赖 libfastcommon 。 + +----- 安装libfastcommon,这个是提取出来的单独公共代码库 +./make.sh +./make.sh install +rpmbuild --bb libfastcommon.spec + +----- 编译安装fastdfs +./make.sh +./make.sh install +rpmbuild --bb fastdfs.spec + +注意,编译时如果报访问头文件没有权限,有可能是由于目录不允许非 root 用户访问,需要执行 `chmod o+x /usr/include/fastcommon` 。 + +无论是通过源码安装,还是通过除了会安装一些库以及头文件外,会在 /usr/bin 目录下安装一堆的 fdfs_xxx 的命令行,也就是常用的命令。 + +### 安装配置 + +在已经打包了 RPM 之后,接着就可以通过如下的方式安装测试。 + +----- 安装基本依赖库以及服务 +# rpm -ivh libfastcommon-1.0.36-1.x86_64.rpm +# rpm -ivh fastdfs-server-5.11-1.x86_64.rpm + +修改 tracker 的配置文件,主要是 `base_path=/home/fastdfs` 选项。 + +----- 创建目录并启动tracker服务,可以启动一个或者多个 +# mkdir -p /home/fastdfs +# /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart + +修改 storage 的配置文件,修改的配置项为: + +base_path=/home/fastdfs +store_path0=/home/fastdfs/storage +# 如果有多个tracker,可以指定多行,不能是127.0.0.1 +tracker_server=192.168.11.75:22122 + +然后启动 storge 的服务器即可。 + +# mkdir -p /home/fastdfs/storage +# /usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart + +上述的日志会保存在 base_path/logs 目录下,如果启动失败可以查看其日志信息。 + +### 上传测试 + +实际上可以单台部署测试,测试需要安装一些依赖包。 + +----- 需要安装两个RPM +# rpm -qpl libfdfsclient-5.11-1.x86_64.rpm +# rpm -qpl fastdfs-tool-5.11-1.x86_64.rpm + +修改 client 的配置文件,修改选项以及内容如下。 + +base_path=/home/fastdfs +tracker_server=192.168.11.75:22122 + +然后通过如下方式上传即可。 + +/usr/bin/fdfs_test /etc/fdfs/client.conf upload /etc/passwd + +### 监控 + +/usr/bin/fdfs_monitor /etc/fdfs/client.conf + + +### Nginx 融合 + +多数场景都需要为 FastDFS 存储的文件提供 http 下载服务,尽管在 storage 以及 tracker 中都内置了 http 服务,但性能表现却不尽如人意,所以通常是基于当前主流 Web 服务器做模块扩展,用于提高下载性能。 + +对于这一功能,需要安装 fastdfs-nginx-module 模块。 + +FastDFS 通过 Tracker 服务器,将文件放在 Storage 服务器存储,但是同组存储服务器之间的复制会有同步延迟,当客户端尝试访问还未同步到的 Storage 服务器时,就会报错,而通过 fastdfs-nginx-module 模块就可以重定向到其它组的服务器,避免客户端由于复制延迟导致的文件无法访问错误。 + + +组名/磁盘名/目录名1/目录名2/文件名 +group1/M00/00/00/wKgLS1pO7XaARf5HAAAGBhFrQFQ5416274 + + +## FAQ. + +### Storage Server 僵死 + +启动 storage server 后会一直尝试链接到 tracker server 直到成功,当 tracker server 不满足多数派时就可能会导致失败。 + +### 启停服务 + +停止服务可以直接 kill ,但是不要使用 `kill -9` 否则可能会导致 binlog 数据丢失。 + +/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf stop +/usr/bin/fdfs_storaged /etc/fdfs/storage.conf stop + +/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart +/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart + +/usr/local/CloudAgent/agent/plugins/CloudMonitor/ + +bin/uagentctl config setlog debug0 + +fdfs_test和fdfs_test1是做什么用的? +   这两个是FastDFS自带的测试程序,会对一个文件上传两次,分别作为主文件和从文件。返回的文件ID也是两个。 +   并且会上传文件附加属性,storage server上会生成4个文件。 +   这两个程序仅用于测试目的,请不要用作实际用途。 +   V2.05提供了比较正式的三个小工具: +      上传文件:/usr/bin/fdfs_upload_file  +      下载文件:/usr/bin/fdfs_download_file [local_filename] +       删除文件:/usr/bin/fdfs_delete_file +tracker_service_init() + +tracker_accept_loop() + +client_sock_read() + |-tracker_deal_task() + + +relationship_thread_entrance() 选择和维护leader + + + + +注意,实际上 tracker server 是对等的,客户端可以访问任意一台,之所以引入 leader ,主要是为了解决如下问题: +  a. 新加入一台storage server时,由leader指定向其同步的源storage server; +  b. 使用了合并存储特性时,leader为每个group选举和维护唯一的一个trunk server; +以上分配如果不由leader来完成的话,可能会出现混乱情况,尤其是第2条。 + +tracker_service_init() + |-work_thread_entrance() 工作线程入口 +   |-recv_notify_read() + +tracker_relationship_init() 启动选主流程 + |-relationship_thread_entrance() 真正入口函数 +   |-relationship_select_leader() 选择leader + +storage_dio_init() 磁盘IO读写线程 + |-blocked_queue_init() 读写队列 + |-dio_thread_entrance() 真正入口函数 +storage_accept_loop() 启动多个接收线程 + |-accept_thread_entrance() 真正入口函数 +   |-accept() 接收请求 +   |-getPeerIpaddr() 获取对端的IP地址,用于白名单判断 +   |-tcpsetnonblockopt() 设置为非阻塞模式 +   |-write() 将任务的地址发送给工作线程 +work_thread_entrance() 真正的工作线程 + |-ioevent_loop() 检查是否有读请求 +   |-storage_recv_notify_read() 读请求的回调函数 +     |-client_sock_read() +    |-fast_timer_modify() 更新超时时间 +    |-storage_deal_task() 处理不同的任务 +         |-storage_upload_file() 上传文件,可以是append方式 +           |-fdfs_validate_filename() 获取文件大小、文件名等信息 +           |-trunk_client_trunk_alloc_space() 为trunk文件名分配空间,并添加到缓存 +           |-trunk_get_full_filename() 如果是trunk模式则设置相关内容 +           |-storage_write_to_file() 开始写文件 +       |-storage_dio_queue_push() +      |-blocked_queue_push() + +通过BASE64算法生成文件ID。 +提供 Trunk 功能合并小文件,否则小文件会大量消耗文件系统的 inode 资源。 + +## 参考 + +分布式文件存储系统FastDFS源码 +https://github.com/happyfish100/fastdfs +FastDFS FAQ. +http://bbs.chinaunix.net/thread-1920470-1-1.html +如何搭建FastDFS的步骤 +http://www.ityouknow.com/fastdfs/2017/10/10/cluster-building-fastdfs.html +基本概念介绍 +http://blog.chinaunix.net/uid-20196318-id-4058561.html +http://blog.csdn.net/xingjiarong/article/details/50559849 + + +http://seanlook.com/2015/05/18/nginx-keepalived-ha/ +http://cpper.info/2016/09/15/keepalived-for-master-backup.html + + +http://wdxtub.com/2016/06/28/sso-guide/ +https://blog.miguelgrinberg.com/post/oauth-authentication-with-flask + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2018-01-25-react-python-flask-practice_init.md b/_drafts/2018-01-25-react-python-flask-practice_init.md new file mode 100644 index 0000000..4ce9f70 --- /dev/null +++ b/_drafts/2018-01-25-react-python-flask-practice_init.md @@ -0,0 +1,65 @@ +--- +title: Flask-React 实践 +layout: post +comments: true +language: chinese +category: [program,linux] +keywords: flask,react +description: +--- + +这里实际上是参考 《实例讲解基于 Flask+React 的全栈开发和部署》中的介绍。 + + + +{% highlight text %} +----- 0. 如果不存在则提前安装,webpack全局安装 +$ pip install virtualenv +# npm install -g webpack webpack-cli + +----- 1. 新建一个虚拟的Python环境,并切换到新环境中,同时切换到源码目录 +$ virtualenv --no-site-packages foobar +$ source foobar/bin/activate +$ cd your-project-source-dir +$ pip install -r requirements.txt + +----- 2. 安装npm依赖,默认安装到当前目录的node_modules目录下 +$ npm install +$ export PATH=`pwd`/node_modules/bin:$PATH + +----- 3. 执行npm的启动 +$ npm start +{% endhighlight %} + + + + + + +## 使用 + +这里通过 Socket.IO ZeroMQ 等搭建一个高效的。 + + +https://github.com/gabrielfalcao/flask-react-bootstrap + + + + + +## 参考 + +直接参考的 [实例讲解基于 Flask+React 的全栈开发和部署](http://funhacks.net/2016/12/06/flask_react_news/) 中的内容。 + + + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2018-02-01-cap-theory-and-distributed-system_init.md b/_drafts/2018-02-01-cap-theory-and-distributed-system_init.md new file mode 100644 index 0000000..0ae951f --- /dev/null +++ b/_drafts/2018-02-01-cap-theory-and-distributed-system_init.md @@ -0,0 +1,213 @@ +--- +title: CAP 理论简介 +layout: post +comments: true +language: chinese +category: [misc] +keywords: cap theory,distributed system +description: +--- + + + + +## 简介 + +当一个系统中机器数增加时,系统内可能发生失败的几率会随着结点数增加而呈指数级增加。 + +{% highlight text %} +P(any failure) = 1 - P(individual node not failing)^(number of nodes) +{% endhighlight %} + + + + + + + + +## 基本概念 + +分布式领域 CAP 理论,可以简单介绍如下: + +* 一致性,数据一致更新,每次都会读取到最近写入的结果; +* 可用性,如果没有失败,用户的请求在一定时间内都会得到应答; +* 分区容忍性,即使节点之间的连接关闭,其它两个属性也会得到保证。 + +正常来说,分布式系统中各节点互通,但可能因为一些原因 (网络、GC、BUG 等 ),使得有些节点间不通,那么网络就分成了几块区域,数据散布在这些不连通的区域中,形成分区。 + +如果数据只在单个结点保存,分区后就会导致不连通的网络无法访问数据,这时分区就是无法容忍的,最简单的就是复制多份数据,以提高容忍性。 + +然而,复制多份数据就带来了一致性问题,要保证多结点的数据一致需要等待多数甚至全部结点写成功,不过此时就带来了可用性的问题。 + +总的来说,数据节点越多分区容忍性越高;但要复制更新数据就越多,一致性就越难保证;为了保证一致性,更新所有节点数据所需要的时间就越长,可用性就会降低。 + +![cap]({{ site.url }}/images/databases/cap-introduce.png "cap"){: .pull-center } + +也就是说,任何分布式系统只可同时满足二点,没法三者兼顾,对于系统的架构设计,需要从中进行取舍。 + + + + + +## 理解CAP + +一般任务 CAP 三者并不对等,P 是基础,只能从 CA 之间做选择;在全球分布式系统中,网络分区是一个自然的事实,甚至被认为是必然的。 + + +在这样的情况下,有两种声音: + 因为分区是必然的,系统设计时,只能实现AP和CP系统,CA系统是不可能的。 + 从技术上来说,分区确实会出现,但从效果来说,或者从概率来说,分区很少出现,可以认为系统不会发生分区。由于分区很少发生,那么在系统不存在分区的情况下没什么理由牺牲C或A。 + +从更广阔的分布式计算理论背景下审视CAP理论,可以发现C,A,P三者并不对等。 + +CAP之父在《Spanner,真时,CAP理论》一文中写道:如果说Spanner真有什么特别之处,那就是谷歌的广域网。Google通过建立私有网络以及强大的网络工程能力来保证P,在多年运营改进的基础上,在生产环境中可以最大程度的减少分区发生,从而实现高可用性。 + +从Google的经验中可以得到的结论是,一直以来我们可能被CAP理论蒙蔽了双眼,CAP三者之间并不对称,C和A不是P的原因啊(P不能和CA trade-off,CP和AP中不存在tradeoff,tradeoff在CA之间)。提高一个系统的抗毁能力,或者说提高P(分区容忍能力)是通过提高基础设施的稳定性来获得的,而不是通过降低C和A来获得的。也就说牺牲C和A也不能提高P。 + + + +还有一种说法是,放弃C不是为了获得A,而是为了低延迟(延迟不也是可用性的内涵吗?我这里有疑问)。PNUTS为了降低WAN上的消息事务的延迟(几百毫秒,对于像亚马逊和雅虎这样的企业需要实施的许多Web应用程序来说,成本太高),采用放弃一致性的做法。 + + + +而CA是系统的刚性强需求,但是CA两者也不对等。系统无论如何要保证一致性(无论事先还是事后,这是系统设计的最大不变性约束,后文会详述),在这个前提下,可以谈谈可用性的程度。Google的Spanner就是这样的思路。 + + + +总结:P是一个自然的事实,CA是强需求。三者并不对等。 + + + +补充:文章写完之后,看到最新出版的文章《分布式数据库中一致性与可用性的关系》,值得一读。 + + +4.2 保证不发生分区,CA也不容易兼得 + + +在分布式系统中,安全性,活性是本质需求,并且广泛的研究结果是分布式系统中一直存在一个广泛意义的trade-off:在不可靠的分布式系统中无法同时实现安全性和活性。分布式系统的设计中充满了安全性和活性的trade-off,FLA著名的论文《Impossibility of Distributed Consensus withOne Faulty process》就是说我们不可能设计一个算法既可以绝对保证一致性(安全性)又无需时间等待的实现一致性(活性)。 + + + +CAP就是这个trade-off的的集中体现。分别对应于: + + Safety:非正式的说,一个算法没有任何坏的事情发生,那么该算法就是安全的。CAP中的C就是典型的safety属性:所有对客户的响应都是正确的。 + + Liveness:相反,一个算法最终有有一些好的事情发生,那么该算法就是活性的。CAP中的A就是典型的liveness属性:所有的客户最终都会收到回应。 + + + +FLA中的故障是指: + +Unreliable:有很多种方式可以让一个系统不可靠,CAP中的P,以及其他故障:系统崩溃,消息丢失,恶意攻击,拜占庭故障等。 + + + +所以,CAP理论可以说是FLA不可能性的不同表达方式。P只是Unreliable的一种极端形式而已。在Google的Chubby文章中,指出paxos协议维护系统的safety,引入时钟来保证liveness,由此克服FLA的不可能性。实际上,基本的Paxos协议可以保证值一旦被选出后就一定不会改变,但不能保证一定会选出值来。换句话说,这个投票算法不一定收敛。所以在系统设计时,paxos算法的使用是有条件的。 + + + +在数据库领域,CAP也正是ACID和BASE长期博弈(tradeoff)的结果。ACID伴随数据库的诞生定义了系统基本设计思路,所谓先入为主。2000年左右,随着互联网的发展,高可用的话题被摆上桌面,所以提出了BASE。从此C和A的取舍消长此起彼伏,其结晶就是CAP理论。 + +从ACID和BASE来说,ACID是为了保证一致性而诞生,因而侧重一致性;BASE是为了高可用系统的设计而诞生,因而侧重可用性。在分解C和A的情况时,肯定要涉及P,所以CAP理论统一了这一切。如果非要说酸碱,或者说酸碱平衡,那就是平衡于CAP理论。 + + + +CAP并不与ACID中的A(原子性)冲突,值得讨论的是ACID中的C(一致性)和I(隔离性)。ACID的C指的是事务不能破坏任何数据库规则,如键的唯一性。与之相比,CAP的C仅指单一副本这个意义上的一致性,因此只是ACID一致性约束的一个严格的子集。如果系统要求ACID中的I(隔离性),那么它在分区期间最多可以在分区一侧维持操作。事务的可串行性(serializability)要求全局的通信,因此在分区的情况下不能成立。 + + + +C与A之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,乃至因为牵涉到特定的数据或用户而有所不同。我们在分布式系统设计的两大原则中讨论过保持一致性的手段:同步复制和异步复制,结合复制协议的各种模式,请参考下表。例如简单满足了C,但延迟升高了,吞吐量下来了,还有什么可用性?我觉得延迟是包含在可用性的范围内的,不可用就是延迟的极大极限值。还有文章就只讨论一致性,可用性和性能问题(比如阿里何登成的《数据一致性-分区可用性-性能——多副本强同步数据库系统实现之我见》),说明在不考虑分区的情况下,CA问题依然是系统设计的难点。 + + +重新审视本文的时候,恰好看到一个新的理论PACELC:even when the system is running normally in theabsence of partitions, one has to choose between latency (L) and consistency(C). 可谓和我的想法不谋而合。 + +PACELC:In case of network partitioning (P) in a distributed computersystem, one has to choose between availability (A) and consistency (C) (as perthe CAP theorem), but else (E), even when the system is running normally in theabsence of partitions, one has to choose between latency (L) and consistency(C).(https://en.wikipedia.org/wiki/PACELC_theorem) + + +可用性并不是简单的网络连通,服务可以访问,数据可以读取就是可用性,对于互联网业务,可用性是完整的用户体验,甚至会延伸到用户现实生活中(补偿)。有的系统必须容忍大规模可靠分布式系统中的数据不一致,其中原因就是为了在高并发条件下提高读写性能。 + + + +必须容忍大规模可靠分布式系统中的数据不一致,有两个原因:在高并发条件下提高读写性能, 并要区分物理上导致的不一致和协议规定的不一致: + + 节点已经宕机,副本无法访问(物理) + + 法定数模型会使部分系统不可用的分区情况,即使节点已启动并运行(paxos协议) + + 网络断开,节点孤立(物理) + + + +所以,保证不发生分区,CA也不是免费午餐:尽管保证了网络可靠性,尽量不发生分区,同时获得CA也不是一件简单的事情。 + + + +CA系统才是真正的难点。 + + + +宣称是CA系统的,目前有两家:一家是Google的Spanner,一家是Alibaba的OceanBase。 + + +4.3 发生分区时,也不要随意牺牲CA + + +虽然架构师仍然需要在分区的前提下对数据一致性和可用性做取舍,但具体如何处理分区和恢复一致性,这里面有不计其数的变通方案和灵活度。 + +当发生分区时,系统设计人员不应该盲目地牺牲一致性或可用性。当分区存在或可感知其影响的情况下,就要预备一种策略去探知分区并显式处理其影响。这样的策略应分为三个步骤:探知分区发生,进入显式的分区模式以限制某些操作,启动恢复过程以恢复数据一致性并补偿分区期间发生的错误。 + + + +这一切都需要在系统设计之初考虑到,并在测试时模拟各种故障保证覆盖到你的测试点。 + +构建高度稳健的基础设施永远是第一要务,所以我不认为网络分区与CA属性是对等的。 + + + + + + + + + + + +## 参考 + + + +关于数据库中一致性和可用性的探讨可以参考 [分布式数据库中一致性与可用性的关系](http://www.jos.org.cn/ch/reader/create_pdf.aspx?file_no=5433&journal_id=jos) ,或者直接参考 [本地文档](/reference/databases/distributed-database-consistency-available.pdf) 。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2018-02-12-react-environment-introduce_init.md b/_drafts/2018-02-12-react-environment-introduce_init.md new file mode 100644 index 0000000..8d1230d --- /dev/null +++ b/_drafts/2018-02-12-react-environment-introduce_init.md @@ -0,0 +1,1298 @@ +--- +title: Hello World !!! +layout: post +comments: true +language: chinese +category: [misc] +keywords: hello world,示例,sample,markdown +description: 简单记录一下一些与 Markdown 相关的内容,包括了一些使用模版。 +--- + + + + + + + + + + + + 每个命令行块都是以根目录为基础的。例如下面命令行块,都是基于根目录的。 + +cd src/pages +mkdir Home + + 技术栈均是目前最新的。 + + react 15.6.1 + react-router-dom 4.2.2 + redux 3.7.2 + webpack 3.5.5 + + 目录说明 + +│ .babelrc #babel配置文件 +│ package-lock.json +│ package.json +│ README.MD +│ webpack.config.js #webpack生产配置文件 +│ webpack.dev.config.js #webpack开发配置文件 +│ +├─dist +├─public #公共资源文件 +└─src #项目源码 + │ index.html #index.html模板 + │ index.js #入口文件 + │ + ├─component #组建库 + │ └─Hello + │ Hello.js + │ + ├─pages #页面目录 + │ ├─Counter + │ │ Counter.js + │ │ + │ ├─Home + │ │ Home.js + │ │ + │ ├─Page1 + │ │ │ Page1.css #页面样式 + │ │ │ Page1.js + │ │ │ + │ │ └─images #页面图片 + │ │ brickpsert.jpg + │ │ + │ └─UserInfo + │ UserInfo.js + │ + ├─redux + │ │ reducers.js + │ │ store.js + │ │ + │ ├─actions + │ │ counter.js + │ │ userInfo.js + │ │ + │ ├─middleware + │ │ promiseMiddleware.js + │ │ + │ └─reducers + │ counter.js + │ userInfo.js + │ + └─router #路由文件 + Bundle.js + router.js + + +### 1. 初始化项目 + +首先创建项目目录,并初始化。 + +{% highlight text %} +----- 创建文件夹并进入 +$ mkdir react && cd react + +----- 按照提示填写项目基本信息 +$ npm init +{% endhighlight %} + +### 2. 配置WebPack + +首先是安装,默认是安装到本地目录下。 + +{% highlight text %} +----- 安装 +$ npm install --save-dev webpack@3 +{% endhighlight %} + +一般来说,`--save-dev` 是开发时依赖的东西,`--save` 是发布之后还依赖的东西。 + +#### 2.1 编写配置文件 + +编写最基础的配置文件 `webpack.dev.config.js` 。 + +{% highlight javascript %} +const path = require('path'); +module.exports = { + /*入口*/ + entry: path.join(__dirname, 'src/index.js'), + + /*输出到dist文件夹,输出文件名字为bundle.js*/ + output: { + path: path.join(__dirname, './dist'), + filename: 'bundle.js' + } +}; +{% endhighlight %} + +#### 2.2 编译文件 + +首先,根据如上的配置新建入口文件 `src/index.js` ,并添加如下内容。 + +{% highlight javascript %} +document.getElementById('app').innerHTML = "Webpack works" +{% endhighlight %} + +然后,执行命令 `./node_modules/.bin/webpack --config webpack.dev.config.js` ,编译完成后,会自动生成 `dist/bundle.js` 文件。 + +最后,在 `dist` 文件夹下面新建一个 `index.html` 文件。 + +{% highlight text %} + + + + + Document + + +
    + + + +{% endhighlight %} + +用浏览器打开 `index.html` 即可。 + +也就是说,这里把入口文件 `index.js` 处理后,生成 `bundle.js` 。 + +### 3. Babel + +Babel 把用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本,通俗的说,就是我们可以用 ES6, ES7 等来编写代码,Babel 会把他们统统转为 ES5 。 + +{% highlight text %} +babel-core 调用Babel的API进行转码 +babel-loader +babel-preset-es2015 用于解析 ES6 +babel-preset-react 用于解析 JSX +babel-preset-stage-0 用于解析 ES7 提案 +{% endhighlight %} + +先通过如下命令安装。 + +{% highlight text %} +$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 +{% endhighlight %} + +然后,新建 babel 配置文件 `.babelrc` 。 + +{% highlight text %} +{ + "presets": [ + "es2015", + "react", + "stage-0" + ], + "plugins": [] +} +{% endhighlight %} + +修改 `webpack.dev.config.js` 文件,增加 `babel-loader` 。 + +{% highlight javascript %} +/*src文件夹下面的以.js结尾的文件,要使用babel解析*/ +/*cacheDirectory是用来缓存编译结果,下次编译加速*/ +module: { + rules: [{ + test: /\.js$/, + use: ['babel-loader?cacheDirectory=true'], + include: path.join(__dirname, 'src') + }] +} +{% endhighlight %} + +简单测试下是否能正确转义 `ES6`,修改 `src/index.js` 。 + +{% highlight text %} +/*使用es6的箭头函数*/ +var func = str => { + document.getElementById('app').innerHTML = str; +}; +func('Hello Babel!'); +{% endhighlight %} + +执行打包命令 `./node_modules/.bin/webpack --config webpack.dev.config.js`,并通过浏览器打开 `index.html` 。 + + + + +### 4. React + +同样,首先要安装。 + +{% highlight text %} +$ npm install --save react react-dom +{% endhighlight %} + +修改 `src/index.js` 使用 React 。 + +{% highlight text %} +import React from 'react'; +import ReactDom from 'react-dom'; + +ReactDom.render(
    Hello React!
    , document.getElementById('app')); +{% endhighlight %} + +执行打包命令 `./node_modules/.bin/webpack --config webpack.dev.config.js`,然后打开 `index.html` 看效果。 + +#### 4.1 组件化 + +这里简单处理下,把 `Hello React` 放到组件里面,新建文件 `src/component/Hello/Hello.js`。 + +{% highlight text %} +import React, {Component} from 'react'; + +export default class Hello extends Component { + render() { + return (
    Hello Component React!
    ) + } +} +{% endhighlight %} + +然后修改 `src/index.js`,引用 `Hello` 组件! + +{% highlight text %} +import React from 'react'; +import ReactDom from 'react-dom'; +import Hello from './component/Hello/Hello'; + +ReactDom.render(, document.getElementById('app')); +{% endhighlight %} + +同上,在根目录执行打包命令,并测试。 + +#### 4.3 优化命令 + +修改 `package.json` 里面的 script,增加 `dev-build` ,然后可以通过 `npm run dev-build` 编译即可。 + +{% highlight text %} +"scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "dev-build": "webpack --config webpack.dev.config.js" +} +{% endhighlight %} + + +### 5. React Router + + +{% highlight text %} +----- 安装 +$ npm install --save react-router-dom + +----- 新建Router文件夹和组件 +$ mkdir src/router && touch src/router/router.js +{% endhighlight %} + +如下添加一个最基本的 Router 文件,也就是 `src/router/router.js` 。 + +{% highlight text %} +import React from 'react'; +import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom'; +import Home from '../pages/Home/Home'; +import Page1 from '../pages/Page1/Page1'; + +const getRouter = () => ( + +
    +
      +
    • 首页
    • +
    • Page1
    • +
    + + + + +
    +
    +); + +export default getRouter; +{% endhighlight %} + +接着创建所需的文件。 + +{% highlight text %} +----- 新建两个页面 Home Page1 +$ mkdir -p src/pages/{Home,Page1} +{% endhighlight %} + +对应的源码文件如下,分别为 `src/pages/Home/Home.js` 和 `src/pages/Page1/Page1.js` 。 + +{% highlight text %} +import React, {Component} from 'react'; + +export default class Home extends Component { + render() { return(
    this is home~
    ) } +} +{% endhighlight %} + +{% highlight text %} +import React, {Component} from 'react'; + +export default class Page1 extends Component { + render() { return(
    this is Page1~
    ) } +} +{% endhighlight %} + +路由和页面建好了,然后需要修改入口文件 `src/index.js` 。 + +{% highlight text %} +import React from 'react'; +import ReactDom from 'react-dom'; +import getRouter from './router/router'; + +ReactDom.render(getRouter(), document.getElementById('app')); +{% endhighlight %} + +最后,执行打包命令 `npm run dev-build` ,打开 `index.html` 页面。 + +注意,这里点击链接实际上是没有反映的,主要是由于采用的是类似 `file:///your/index/path/index.html` 路径,而非 `http://localhost:3000/` 之类的路径。 + + + +### 6. webpack-dev-server + +简单来说 `webpack-dev-server` 就是一个小型的静态文件服务器,可以为 webpack 打包生成的资源文件提供简单的 Web 服务。 + +{% highlight text %} +$ npm install webpack-dev-server@2 --save-dev +{% endhighlight %} + +注意:这里没有全局进行安装,后面使用的时候要写相对路径,可以通过 `npm install webpack-dev-server@2 -g` 进行全局安装。 + +接着,修改 `webpack.dev.config.js` 增加 webpack-dev-server 的配置。 + +{% highlight text %} +devServer: { + contentBase: path.join(__dirname, './dist') +} +{% endhighlight %} + +接着执行 `./node_modules/.bin/webpack-dev-server --config webpack.dev.config.js`,然后浏览器打开 `http://localhost:8080`,现在就可以点击相关的链接了。 + + + + +### 7. 模块热替换 + + + +到目前为止,当修改代码时浏览器会自动刷新,这里看下 webpack 模块热替换教程。 + + + + + +接下来需要在 `package.json` 文件中增加 `--hot`,也就是如下内容。 + +{% highlight text %} +"start": "./node_modules/.bin/webpack-dev-server --config webpack.dev.config.js --color --progress --hot" +{% endhighlight %} + +同时在 `src/index.js` 文件中增加 `module.hot.accept()`,内容如下,当模块更新时,会通知 `index.js` 。 + +{% highlight text %} +import React from 'react'; +import ReactDom from 'react-dom'; +import getRouter from './router/router'; + +if (module.hot) { + module.hot.accept(); +} + +ReactDom.render(getRouter(), document.getElementById('app')); +{% endhighlight %} + +现在执行 `npm start`,打开浏览器,修改 Home.js ,在不刷新页面的情况下,内容会自动更新了。 + + + +### 8. 文件路径优化 + +在之前的代码中,在引用组件或者页面时,写的是相对路径,例如比如 `src/router/router.js` 里面,引用 Home.js 的时候就用的相对路径。 + +{% highlight text %} +import Home from '../pages/Home/Home'; +{% endhighlight %} + +webpack 提供了一个别名配置,就是我们无论在哪个路径下,引用都可以这样 + +{% highlight text %} +import Home from 'pages/Home/Home'; +{% endhighlight %} + +修改 `webpack.dev.config.js` 增加别名。 + +{% highlight text %} + resolve: { + alias: { + pages: path.join(__dirname, 'src/pages'), + component: path.join(__dirname, 'src/component'), + router: path.join(__dirname, 'src/router') + } + } +{% endhighlight %} + +然后把之前使用的绝对路径统统改掉,包括 `src/router/router.js` 以及 `src/index.js` 。 + +{% highlight text %} +import Home from 'pages/Home/Home'; +import Page1 from 'pages/Page1/Page1'; +{% endhighlight %} + +{% highlight text %} +import getRouter from 'router/router'; +{% endhighlight %} + + +### 9. Redux + +这里简单实现一个计数器。 + +{% highlight text %} +$ npm install --save redux +{% endhighlight %} + +初始化目录结构以及相关的文件。 + +{% highlight text %} +$ mkdir -p src/redux/{actions,reducers} +{% endhighlight %} + +先来写 action 创建函数,通过该函数可以创建 action,也就是 `src/redux/actions/counter.js` 。 + +{% highlight text %} +/*action*/ +export const INCREMENT = "counter/INCREMENT"; +export const DECREMENT = "counter/DECREMENT"; +export const RESET = "counter/RESET"; + +export function increment() { + return {type: INCREMENT} +} + +export function decrement() { + return {type: DECREMENT} +} + +export function reset() { + return {type: RESET} +} +{% endhighlight %} + +接着是 reducer,一个纯函数,接收 action 和旧的 state 生成新 state ,`src/redux/reducers/counter.js` 。 + +{% highlight text %} +import {INCREMENT, DECREMENT, RESET} from '../actions/counter'; + +/* + * 初始化state + */ +const initState = { + count: 0 +}; +/* + * reducer + */ +export default function reducer(state = initState, action) { + switch (action.type) { + case INCREMENT: + return { count: state.count + 1 }; + case DECREMENT: + return { count: state.count - 1 }; + case RESET: + return {count: 0}; + default: + return state + } +} +{% endhighlight %} + +一个项目可能会有很多个 `reducers`,可以通过如下方式把它们整合到一起,`src/redux/reducers.js` 。 + +{% highlight text %} +import counter from './reducers/counter'; + +export default function combineReducers(state = {}, action) { + return { counter: counter(state.counter, action) } +} +{% endhighlight %} + +这里无论是 combineReducers 还是 reducer 函数,都是接收 state 和 action,然后返回一个新的 state。 + +#### 9.1 创建 Store + +前面可以使用 action 来描述 "发生了什么",使用 action 创建函数来返回 action ;并使用 reducers 来根据 action 更新 state 。 + +那如何提交 action 并触发 reducers 呢? + +store 就是把它们联系到一起的对象,其具有以下职责: + +* 维持应用的 state; +* 提供 getState() 方法获取 state; +* 提供 dispatch(action) 触发reducers方法更新 state; +* 通过 subscribe(listener) 注册监听器; +* 通过 subscribe(listener) 返回的函数注销监听器。 + +新建 `src/redux/store.js` 文件。 + +{% highlight text %} +import {createStore} from 'redux'; +import combineReducers from './reducers.js'; + +let store = createStore(combineReducers); + +export default store; +{% endhighlight %} + +到此为止,可以使用 redux 了,这里简单测试下,新建 `src/redux/testRedux.js` 。 + +{% highlight text %} +import {increment, decrement, reset} from './actions/counter'; + +import store from './store'; + +// 打印初始状态 +console.log(store.getState()); + +// 每次 state 更新时,打印日志 +// 注意 subscribe() 返回一个函数用来注销监听器 +let unsubscribe = store.subscribe(() => + console.log(store.getState()) +); + +// 发起一系列 action +store.dispatch(increment()); +store.dispatch(decrement()); +store.dispatch(reset()); + +// 停止监听 state 更新 +unsubscribe(); +{% endhighlight %} + +当前文件夹执行命令 `../../node_modules/.bin/webpack testRedux.js build.js && node build.js` ,此时输出为。 + +{% highlight text %} +{ counter: { count: 0 } } +{ counter: { count: 1 } } +{ counter: { count: 0 } } +{ counter: { count: 0 } } +{% endhighlight %} + +也就是说,redux 和 react 没关系,虽说他俩能合作。 + + + +调整 `webpack.dev.config.js` 路径别名。 + +{% highlight text %} +alias: { + actions: path.join(__dirname, 'src/redux/actions'), + reducers: path.join(__dirname, 'src/redux/reducers'), + redux: path.join(__dirname, 'src/redux') +} +{% endhighlight %} + +把前面的相对路径都改改。 + +#### 9.2 配合 React 使用 + +写一个 Counter 页面 `src/pages/Counter/Counter.js` 。 + +{% highlight text %} +import React, {Component} from 'react'; + +export default class Counter extends Component { + render() { + return ( +
    +
    当前计数为(显示redux计数)
    + + + +
    + ) + } +} +{% endhighlight %} + +修改路由,增加 Counter,也就是 `src/router/router.js` 。 + +{% highlight text %} +import React from 'react'; + +import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom'; + +import Home from 'pages/Home/Home'; +import Page1 from 'pages/Page1/Page1'; +import Counter from 'pages/Counter/Counter'; + +const getRouter = () => ( + +
    +
      +
    • 首页
    • +
    • Page1
    • +
    • Counter
    • +
    + + + + + +
    +
    +); + +export default getRouter; +{% endhighlight %} + +接着运行 `npm start` 看下效果。 + +#### 9.3 联合 Counter 和 Redux + +使 Counter 能获得到 Redux 的 state,并且能发射 action。 + + + +先通过命令 `npm install --save react-redux` 安装 `react-redux` 。 + +然后修改 `src/pages/Counter/Counter.js` 。 + +{% highlight text %} +import React, {Component} from 'react'; +import {increment, decrement, reset} from 'actions/counter'; + +import {connect} from 'react-redux'; + +class Counter extends Component { + render() { + return ( +
    +
    当前计数为{this.props.counter.count}
    + + + +
    + ) + } +} + +const mapStateToProps = (state) => { + return { + counter: state.counter + } +}; + +const mapDispatchToProps = (dispatch) => { + return { + increment: () => { + dispatch(increment()) + }, + decrement: () => { + dispatch(decrement()) + }, + reset: () => { + dispatch(reset()) + } + } +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Counter); +{% endhighlight %} + +下面我们要传入store + + 所有容器组件都可以访问 Redux store,所以可以手动监听它。一种方式是把它以 props 的形式传入到所有容器组件中。但这太麻烦了,因为必须要用 store 把展示组件包裹一层,仅仅是因为恰好在组件树中渲染了一个容器组件。 + + 建议的方式是使用指定的 React Redux 组件 来 魔法般的 让所有容器组件都可以访问 store,而不必显示地传递它。只需要在渲染根组件时使用即可。 + +src/index.js + +import React from 'react'; +import ReactDom from 'react-dom'; +import {AppContainer} from 'react-hot-loader'; +import {Provider} from 'react-redux'; +import store from './redux/store'; + +import getRouter from 'router/router'; + +/*初始化*/ +renderWithHotReload(getRouter()); + +/*热更新*/ +if (module.hot) { + module.hot.accept('./router/router', () => { + const getRouter = require('router/router').default; + renderWithHotReload(getRouter()); + }); +} + +function renderWithHotReload(RootElement) { + ReactDom.render( + + + {RootElement} + + , + document.getElementById('app') + ) +} + +到这里我们就可以执行npm start,打开localhost:8080/counter看效果了。 + +但是你发现npm start一直报错 + +ERROR in ./node_modules/react-redux/es/connect/mapDispatchToProps.js +Module not found: Error: Can't resolve 'redux' in 'F:\Project\react\react-family\node_modules\react-redux\es\connect' + +ERROR in ./src/redux/store.js +Module not found: Error: Can't resolve 'redux' in 'F:\Project\react\react-family\src\redux' + +WTF?这个错误困扰了半天。我说下为什么造成这个错误。我们引用redux的时候这样用的 + +import {createStore} from 'redux' + +然而,我们在webapck.dev.config.js里面这样配置了 + + resolve: { + alias: { + ... + redux: path.join(__dirname, 'src/redux') + } + } + +然后webapck编译的时候碰到redux都去src/redux去找了。但是找不到啊。所以我们把webpack.dev.config.js里面redux这一行删除了,就好了。 +并且把使用我们自己使用redux文件夹的地方改成相对路径哦。 + +现在你可以npm start去看效果了。 + +这里我们再缕下(可以读React 实践心得:react-redux 之 connect 方法详解) + + Provider组件是让所有的组件可以访问到store。不用手动去传。也不用手动去监听。 + + connect函数作用是从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。也传递dispatch(action)函数到props。 + +接下来,我们要说异步action + +参考地址: http://cn.redux.js.org/docs/advanced/AsyncActions.html + +想象一下我们调用一个异步get请求去后台请求数据: + + 请求开始的时候,界面转圈提示正在加载。isLoading置为true。 + 请求成功,显示数据。isLoading置为false,data填充数据。 + 请求失败,显示失败。isLoading置为false,显示错误信息。 + +下面,我们以向后台请求用户基本信息为例。 + + 我们先创建一个user.json,等会请求用,相当于后台的API接口。 + +cd dist +mkdir api +cd api +touch user.json + +dist/api/user.json + +{ + "name": "brickspert", + "intro": "please give me a star" +} + + 创建必须的action创建函数。 + +cd src/redux/actions +touch userInfo.js + +src/redux/actions/userInfo.js + +export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST"; +export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS"; +export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL"; + +function getUserInfoRequest() { + return { + type: GET_USER_INFO_REQUEST + } +} + +function getUserInfoSuccess(userInfo) { + return { + type: GET_USER_INFO_SUCCESS, + userInfo: userInfo + } +} + +function getUserInfoFail() { + return { + type: GET_USER_INFO_FAIL + } +} + +我们创建了请求中,请求成功,请求失败三个action创建函数。 + + 创建reducer + +再强调下,reducer是根据state和action生成新state的纯函数。 + +cd src/redux/reducers +touch userInfo.js + +src/redux/reducers/userInfo.js + +import {GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL} from 'actions/userInfo'; + + +const initState = { + isLoading: false, + userInfo: {}, + errorMsg: '' +}; + +export default function reducer(state = initState, action) { + switch (action.type) { + case GET_USER_INFO_REQUEST: + return { + ...state, + isLoading: true, + userInfo: {}, + errorMsg: '' + }; + case GET_USER_INFO_SUCCESS: + return { + ...state, + isLoading: false, + userInfo: action.userInfo, + errorMsg: '' + }; + case GET_USER_INFO_FAIL: + return { + ...state, + isLoading: false, + userInfo: {}, + errorMsg: '请求错误' + }; + default: + return state; + } +} + +这里的...state语法,是和别人的Object.assign()起同一个作用,合并新旧state。我们这里是没效果的,但是我建议都写上这个哦 + +组合reducer + +src/redux/reducers.js + +import counter from 'reducers/counter'; +import userInfo from 'reducers/userInfo'; + +export default function combineReducers(state = {}, action) { + return { + counter: counter(state.counter, action), + userInfo: userInfo(state.userInfo, action) + } +} + + 现在有了action,有了reducer,我们就需要调用把action里面的三个action函数和网络请求结合起来。 + 请求中 dispatch getUserInfoRequest + 请求成功 dispatch getUserInfoSuccess + 请求失败 dispatch getUserInfoFail + +src/redux/actions/userInfo.js增加 + +export function getUserInfo() { + return function (dispatch) { + dispatch(getUserInfoRequest()); + + return fetch('http://localhost:8080/api/user.json') + .then((response => { + return response.json() + })) + .then((json) => { + dispatch(getUserInfoSuccess(json)) + } + ).catch( + () => { + dispatch(getUserInfoFail()); + } + ) + } +} + +我们这里发现,别的action创建函数都是返回action对象: + +{type: xxxx} + +但是我们现在的这个action创建函数 getUserInfo则是返回函数了。 + +为了让action创建函数除了返回action对象外,还可以返回函数,我们需要引用redux-thunk。 + +npm install --save redux-thunk + +这里涉及到redux中间件middleware,我后面会讲到的。你也可以读这里Middleware。 + +简单的说,中间件就是action在到达reducer,先经过中间件处理。我们之前知道reducer能处理的action只有这样的{type:xxx},所以我们使用中间件来处理 +函数形式的action,把他们转为标准的action给reducer。这是redux-thunk的作用。 +使用redux-thunk中间件 + +我们来引入redux-thunk中间件 + +src/redux/store.js + +import {createStore, applyMiddleware} from 'redux'; +import thunkMiddleware from 'redux-thunk'; +import combineReducers from './reducers.js'; + +let store = createStore(combineReducers, applyMiddleware(thunkMiddleware)); + +export default store; + +到这里,redux这边OK了,我们来写个组件验证下。 + +cd src/pages +mkdir UserInfo +cd UserInfo +touch UserInfo.js + +src/pages/UserInfo/UserInfo.js + +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {getUserInfo} from "actions/userInfo"; + +class UserInfo extends Component { + + render() { + const {userInfo, isLoading, errorMsg} = this.props.userInfo; + return ( +
    + { + isLoading ? '请求信息中......' : + ( + errorMsg ? errorMsg : +
    +

    用户信息:

    +

    用户名:{userInfo.name}

    +

    介绍:{userInfo.intro}

    +
    + ) + } + +
    + ) + } +} + +export default connect((state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo); + +这里你可能发现connect参数写法不一样了,mapStateToProps函数用了es6简写,mapDispatchToProps用了react-redux提供的简单写法。 + +增加路由 +src/router/router.js + +import React from 'react'; + +import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom'; + +import Home from 'pages/Home/Home'; +import Page1 from 'pages/Page1/Page1'; +import Counter from 'pages/Counter/Counter'; +import UserInfo from 'pages/UserInfo/UserInfo'; + +const getRouter = () => ( + +
    +
      +
    • 首页
    • +
    • Page1
    • +
    • Counter
    • +
    • UserInfo
    • +
    + + + + + + +
    +
    +); + +export default getRouter; + +现在你可以执行npm start去看效果啦! + + + + + + + + + + + + + + + + + +https://doc.webpack-china.org/guides/ + +Redux 中文文档 +http://cn.redux.js.org/index.html + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2018-06-01-react-complete-examples_init.md b/_drafts/2018-06-01-react-complete-examples_init.md new file mode 100644 index 0000000..3ae50b0 --- /dev/null +++ b/_drafts/2018-06-01-react-complete-examples_init.md @@ -0,0 +1,30 @@ +--- +title: React 全家桶 +layout: post +comments: true +language: chinese +category: [misc] +keywords: react,introduce +description: +--- + + + + +## 环境准备 + +安装所需要的包,并启动。 + +{% highlight text %} +$ npm install +{% endhighlight %} + +根据 `package.json` 中的配置安装所需要的包,此时会在本地目录下安装 `webpack-dev-server` 包,本地可以通过如下方式启动。 + +{% highlight text %} +$ ./node_modules/.bin/webpack-dev-server +{% endhighlight %} + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2018-09-29-sqlite-sourcecode-analyze_init.md b/_drafts/2018-09-29-sqlite-sourcecode-analyze_init.md new file mode 100644 index 0000000..45c9b93 --- /dev/null +++ b/_drafts/2018-09-29-sqlite-sourcecode-analyze_init.md @@ -0,0 +1,178 @@ +--- +title: SQLite 源码解析 +layout: post +comments: true +language: chinese +category: [program,database,linux] +keywords: SQLite,database +description: SQLite 嵌入到使用它的应用程序中,它们共用相同的进程空间,而不是单独的一个进程,从外部看,它并不像一个 RDBMS,但在进程内部,它却是完整的,自包含的数据库引擎。核心大约有 3W 行标准 C 代码,这些代码都是模块化的,很容易阅读,如下简单介绍。 +--- + +SQLite 嵌入到使用它的应用程序中,它们共用相同的进程空间,而不是单独的一个进程,从外部看,它并不像一个 RDBMS,但在进程内部,它却是完整的,自包含的数据库引擎。 + +核心大约有 3W 行标准 C 代码,这些代码都是模块化的,很容易阅读,如下简单介绍。 + + + +## 架构 + +SQLite 采用了模块的设计,它由三个子系统,包括 8 个独立的模块构成。 + +![SQLite Architecture]({{ site.url }}/images/databases/sqlite/architecture.png "SQLite Architecture"){: .pull-center width="70%" } + +#### 接口 Interface + +接口由 SQLite C API 组成,也就是说不管是程序、脚本语言还是库文件,最终都是通过它与 SQLite 交互。 + +#### 编译器 Compiler + +在编译器中,分词器(Tokenizer)和分析器(Parser)对SQL进行语法检查,然后把它转化为底层能更方便处理的分层的数据结构-语法树,然后把语法树传给代码生成器(code generator)进行处理。而代码生成器根据它生成一种针对SQLite的汇编代码,最后由虚拟机(Virtual Machine)执行。 + + + +#### 后端 Backend + +后端由 B-tree、页缓存 (PageCache, Pager) 和操作系统接口构成,通过 B-tree 和 Pager 共同对数据进行管理。 + +B-tree 的主要功能就是索引,它维护着各个页面之间的复杂的关系,便于快速找到所需数据。而 Pager 的主要作用就是通过 OS 接口在 B-tree 和 Disk 之间传递页面。 + + +![SQLite Execute Routine]({{ site.url }}/images/databases/sqlite/execute-routine.jpg "SQLite Execute Routine"){: .pull-center width="50%" } + +实现的源码都保存在 `src` 目录下,当删除调 test 文件之后,实际上真正有效的代码文件没有太多,但是各个文件中为了适配不同的平台有很多的宏定义。 + +{% highlight text %} +configure Linux中的编译脚本 +ext/ 扩展代码 + |-fts3.c 全文搜索引擎支持 + |-rtree.c RTree索引支持 +tool/ 一些常用的工具 +test/ 测试代码 +{% endhighlight %} + + + +事务执行过程 +https://www.sqlite.org/atomiccommit.html + +SQLite优化过程 +https://www.sqlite.org/optoverview.html + + + +## VDBE + +其全称为 Virtual DataBase Engine 虚拟数据库引擎,类似于 Python、JAVA 中的虚拟机,主要为了实现其跨平台的特性,经过编译器生成的 VDBE 指令最终通过解释引擎解释执行。 + +如下是一个 SQL 语句生成的字节码指令。 + +{% highlight text %} +sqlite> CREATE TABLE foobar(id INTEGER PRIMARY KEY, value TEXT); +sqlite> .explain +sqlite> EXPLAIN INSERT INTO foobar(id, value) VALUES(1, 'Micheal'); +addr opcode p1 p2 p3 p4 p5 comment +---- ------------- ---- ---- ---- ------------- -- ------------- +0 Trace 0 0 0 00 +1 Goto 0 15 0 00 +2 OpenWrite 0 2 0 2 00 +3 Integer 1 1 0 00 +4 NotNull 1 6 0 00 +5 NewRowid 0 1 0 00 +6 MustBeInt 1 0 0 00 +7 Null 0 2 0 00 +8 String8 0 3 0 Micheal 00 +9 NotExists 0 11 1 00 +10 Halt 1555 2 0 PRIMARY KEY must be unique 00 +11 MakeRecord 2 2 4 da 00 +12 Insert 0 4 1 foobar 13 +13 Close 0 0 0 00 +14 Halt 0 0 0 00 +15 Transaction 0 1 0 00 +16 VerifyCookie 0 1 0 00 +17 TableLock 0 2 1 foobar 00 +18 Goto 0 2 0 00 +{% endhighlight %} + + + + + + + + +通过 `.explain` 命令使得显示字节码时会进行格式化显示,更加清晰。 + + +## Atomic Commit + +对于支持事务的 DB 引擎来说原子提交是基本的能力,SQLite 提供了两种方式:A) [Rollback Mode](https://www.sqlite.org/atomiccommit.html);B) [Write-Ahead Logging ](https://www.sqlite.org/wal.html) 。 + + +## Pager + +解析 SQL 后需要对磁盘中的数据进行操作,可通过 B-Tree 进行查找,不过 B-Tree 不会直接读写磁盘,会通过 Pager 模块来获取所需的页面或修改页面,该模块可以看作是 B-Tree 和磁盘读写的中间代理。 + +Pager 作为事务管理器,实现了数据库的 ACID 特性,会依赖三个模块:锁管理、缓存管理和日志管理。 + + +{% highlight text %} +sqlite3_open() + |-openDatabase() + |-sqlite3MallocZero() + |-createCollation() 字符比较方式包括了BINARY NOCASE RTRIM 三种 + |-sqlite3BtreeOpen() + |-sqlite3MallocZero() + |-sqlite3PagerOpen() + |-sqlite3PagerSetBusyhandler() + |-sqlite3BtreeEnter() +sqlite3_exec() +sqlite3_close() +{% endhighlight %} + + + +## 压测 + +跟磁盘的性能有很大关系,单线程简单更新,如果使用 RAMDISK 可以达到 `1W` 的 TPS 。 + +### 单线程 + +当多个线程使用不同的链接句柄时极易发生冲突,可以将所有线程共有一个 SQLite Handle,并用线程锁避免多线程问题。当多线程并发时,各线程的数据库操作同步顺序进行,这就导致后来的线程会被阻塞较长的时间。 + +SQLite的多句柄方案及Busy Retry方案 + +SQLite实际是支持多线程(几乎)无锁地并发操作。只需 + +* 在编译时开启 `SQLITE_THREADSAFE=2` 选项,然后通过 `PRAGMA` 进行设置; +* 确保同一个句柄同一时间只有一个线程在操作。 + +Multi-thread. In this mode, SQLite can be safely used by multiple threads provided that no single database connection is used simultaneously in two or more threads. + +如果此时再开启 `Write Ahead Log, WAL` 模式,多线程的并发性将得到进一步的提升。 + +此时写操作会先 append 到 wal 文件末尾,而不是直接覆盖旧数据;而读操作开始时,会记下当前的 WAL 文件状态,并且只访问在此之前的数据,有点类似 MVCC 操作。这就确保了多线程读与读、读与写之间可以并发地进行。 + + +## 参考 + + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2018-10-09-sqlite-optimize-introduce_init.md b/_drafts/2018-10-09-sqlite-optimize-introduce_init.md new file mode 100644 index 0000000..93a0619 --- /dev/null +++ b/_drafts/2018-10-09-sqlite-optimize-introduce_init.md @@ -0,0 +1,123 @@ +--- +title: SQLite 优化实践 +layout: post +comments: true +language: chinese +category: [database,program,linux] +keywords: SQLite,optimize +description: SQLite 的写入性能与磁盘的性能有很大的关系,例如在 SATA 上可能只有 100 左右,在 SSD 上大概有 1K 左右,如果直接使用 RAMDisk 测试能达到 1W 以上。除此之外,还可以通过一些方法进行相关的优化。 +--- + +SQLite 的写入性能与磁盘的性能有很大的关系,例如在 SATA 上可能只有 100 左右,在 SSD 上大概有 1K 左右,如果直接使用 RAMDisk 测试能达到 1W 以上。除此之外,还可以通过一些方法进行相关的优化。 + + + + + +## 修改配置 + +在介绍如何修改参数前,提前说明下相关的命令。 + +`PRAGMA` 语句是 SQLite 的扩展,主要用于修改数据库的配置等。 + +1. 不同版本的特性可能会被删除或者新增,无法保证其兼容性。 +2. 未知的命令不会有错误消息出现,它只是简单的忽略。 +3. 有些 PRAGMA 只在 SQL 执行的部分阶段起作用,例如编译阶段`sqlite3_prepare`、执行阶段`sqlite3_step`。 + +#### 配置 + +可以通过 `PRAGMA` 命令查询或者设置当前系统的配置参数。 + +{% highlight text %} +----- 要查询当前PRAGMA的值 +sqlite> PRAGMA pragma_name; +----- 设置为新值 +sqlite> PRAGMA pragma_name = value; +{% endhighlight %} + +设置模式,可以是名称或等值的整数,但返回的值将始终是一个整数。 + +### 常用参数 + +主要包含了两个:A) `synchronous` 定义了何时写入磁盘;B) `journal_mode` 日志写入模式。 + +#### synchronous + +{% highlight text %} +sqlite> PRAGMA synchronous; +sqlite> PRAGMA synchronous = 2; +{% endhighlight %} + +用于设置磁盘的写入时机。 + +* `0/OFF` 不进行同步设置,完全由操作系统进行控制; +* `1/NORMAL` V2默认设置,在关键操作后刷新磁盘,小几率导致数据库损坏; +* `2/FULL` V3默认设置,每次关键操作都会刷新磁盘,性能差但是可以保证数据库不损坏。 + +对于磁盘来说,此时的吞吐量会增加很多,1/2 差异不大。 + + + +#### journal_mode + +控制日志文件如何存储和处理的日志模式,可以设置数据库级别。 + +其中 `journal` 为数据库事务提供 `rollback` 操作,当事务写入时,首先写入 `journal` 文件中,在提交时,根据 `journal-mode` 来处理 `journal` 日志文件。 + +若在提交之前由于断电等原因造成无法提交,当再次启动时,通过 `journal` 文档做回滚操作,保证数据库的完整性和一致性。 + +{% highlight text %} +sqlite> PRAGMA journal_mode; +sqlite> PRAGMA journal_mode = mode; +sqlite> PRAGMA database.journal_mode; +sqlite> PRAGMA database.journal_mode = mode; +{% endhighlight %} + +这里支持五种日志模式: + +* `DELETE` 默认,在事务结束时,日志文件将被删除; +* `WAL` 使用 Write Ahead Log; +* `MEMORY` 日志记录保留在内存中,而不是磁盘上,断电会丢失; +* `OFF` 不保留任何日志记录; + + + + +DISK 40->120 +RAMDISK 1W->4W + + +### 关于 WAL + +在 `V3.7.0` 版本支持,修改不直接写入数据库文件中,而是直接一个 WAL 的文件中,若事务失败,WAL 记录被忽略;若事务成功,随后在某个 checkpoint 时间点写回数据库。 + +此时读写、读读可完全并发执行,不会互相阻塞(写之间不能并发)。除了数据库文件,同时会增加 `name.db-shm` `name.db-wal` 两个文件。 + +为了避免读取的数据不一致,查询时也需要读取 WAL 文件,这样的代价就是读取会变得稍慢,但是写入会变快很多。要提高查询性能的话,可以减小 WAL 文件的大小,但写入性能也会降低。 + +另外,需要注意各个版本间的 WAL 可能不兼容,但是数据库文件是通用的。 + +## 参考 + + + +[微信 iOS SQLite 源码优化实践](https://github.com/WeMobileDev/article/blob/master/%E5%BE%AE%E4%BF%A1iOS%20SQLite%E6%BA%90%E7%A0%81%E4%BC%98%E5%8C%96%E5%AE%9E%E8%B7%B5.md) 。 + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/2019-06-30-linux-program-cpu-cache-introduce_init.md b/_drafts/2019-06-30-linux-program-cpu-cache-introduce_init.md new file mode 100644 index 0000000..a7ad8f1 --- /dev/null +++ b/_drafts/2019-06-30-linux-program-cpu-cache-introduce_init.md @@ -0,0 +1,49 @@ +--- +title: CPU Cache +layout: post +comments: true +language: chinese +category: [linux,program,misc] +keywords: +description: +--- + +CPU 的发展因为受制于制造工艺和成本限制,CPU 的频率、内存的访问速度都没有太多质的突破,但是两者直接的访问处理速度相差越来越大。 + +所以,目前在 CPU 和内存之间包含了多级缓存,用来提高 CPU 的处理速度。 + +那么 CPU Cache 也就成了理解计算机体系架构的重要知识点,同样也是并发编程设计中的技术难点。 + + + +![intel cpu chips]({{ site.url }}/images/hardware/intel_cpu_chips.jpg "intel cpu chips"){: .pull-center width="70%" } + +## 简介 + +在 Linux 中可以通过 `lscpu` 命令查看 CPU 相关信息。 + +## 实例测试 + +如下通过一些实例展示一些内存相关特性。 + +### 访问速度 + + +### Cache Line + +所谓的 Cache Line 可以简单理解为 CPU Cache 中的最小缓存单位,目前主流的 CPU 的 Cache Line 大小都是 64Bytes 。 + +例如,假设有一个 32KB 的一级数据缓存,那么按照 64B 的缓存行来计算,这个一级缓存能够存放 32K/8=4K 。 + +## 参考 + +* [Gallery of Processor Cache Effects](http://igoro.com/archive/gallery-of-processor-cache-effects/) 通过代码介绍 CPU Cache 的一些特性。 +* [CPU 性能天梯图](http://www.mydrivers.com/zhuanti/tianti/cpu/) 以及 [ZOL CPU 天梯图](https://cpu.zol.com.cn/soc/)。 + + + +{% highlight text %} +{% endhighlight %} diff --git a/_drafts/foobar.c b/_drafts/foobar.c deleted file mode 100644 index 2f703b8..0000000 --- a/_drafts/foobar.c +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include -#include - -#define BUF_SIZE (8 * 1024 * 1024) -#define LOOPS 16 - -char arr[BUF_SIZE] __attribute__((__aligned__((64)), __section__(".data.cacheline_aligned"))) ; - -int main(int argc, char **argv) -{ - (void) argc; - int i, j, step; - - step = atoi(argv[1]); - - for (i = 0; i < LOOPS; i++) - for (j = 0; j < BUF_SIZE; j += step) - arr[j] = 3; - return 0; -} diff --git a/_drafts/main.c b/_drafts/main.c deleted file mode 100644 index c3f0d0d..0000000 --- a/_drafts/main.c +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include -#include -#include - -#define NUMS (64 * 1024 * 1024) - -int main(void) -{ - int *arr, i, j, rc; - struct timeval beg, end, diff; - - //void * memalign (size_t boundary, size_t size) - - rc = posix_memalign((void **)&arr, 4096, NUMS * sizeof(int)); - if (arr == NULL) - return -1; -printf("xxxxxxxxxxxx %p\n", arr); - - for (j = 1; j < 2; j++) { -j = 1; - for (i = 0; i < NUMS; i++) - arr[i] = i; - - gettimeofday(&beg, NULL); - for (i = 0; i < NUMS; i += j) - arr[i]++; - gettimeofday(&end, NULL); - timersub(&end, &beg, &diff); - fprintf(stderr, "%02d round time consume %ld.%06lds.\n", - j, diff.tv_sec, diff.tv_usec); - } - - return 0; -} diff --git a/_drafts/sslsync/CMakeLists.txt b/_drafts/sslsync/CMakeLists.txt deleted file mode 100644 index b3e6bf2..0000000 --- a/_drafts/sslsync/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -ADD_EXECUTABLE(esyncclient sync_client.c) -TARGET_LINK_LIBRARIES(esyncclient -lssl -lcrypto) - -ADD_EXECUTABLE(esyncserver sync_server.c) -TARGET_LINK_LIBRARIES(esyncserver -lssl -lcrypto) diff --git a/_drafts/sslsync/client.c b/_drafts/sslsync/client.c deleted file mode 100644 index f476c2a..0000000 --- a/_drafts/sslsync/client.c +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -int verify_show_certs(SSL *ssl) -{ - X509 *cert; - char *line; - - cert = SSL_get_peer_certificate(ssl); - if (cert == NULL) { - printf("No certificate information!\n"); - return -1; - } - - if (SSL_get_verify_result(ssl) != X509_V_OK){ - printf("Invalid certificate information!\n"); - return -1; - } - - printf("Digital certificate information:\n"); - - line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); - printf("Certificate: %s\n", line); - free(line); - - line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); - printf(" Issuer: %s\n", line); - free(line); - - X509_free(cert); - return 0; -} - -int main(void) -{ - SSL *ssl; - SSL_CTX *ctx; - int sockfd, rc; - struct sockaddr_in dst; - - SSL_library_init(); - //OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - - ctx = SSL_CTX_new(SSLv23_client_method()); - if (ctx == NULL) { - ERR_print_errors_fp(stderr); - exit(1); - } - -#if 0 - //ECDHE-ECDSA-AES256-GCM-SHA384:RC4:HIGH:!MD5:!aNULL:!EDH - /* set our supported ciphers */ - if (SSL_CTX_set_cipher_list(ctx, "AES256-GCM-SHA384") != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } -#endif - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); - - /* load ca file */ - if (SSL_CTX_load_verify_locations(ctx, "pki/CA/cacert.pem", 0) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - if (SSL_CTX_set_default_verify_paths(ctx) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - - /* load certificate file, which will send public key to client. */ - if (SSL_CTX_use_certificate_file(ctx, "pki/CLI/cert.pem", SSL_FILETYPE_PEM) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - /* and also private key file */ - if (SSL_CTX_use_PrivateKey_file(ctx, "pki/CLI/key.pem", SSL_FILETYPE_PEM) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - /* check if the private key is valid */ - if (SSL_CTX_check_private_key(ctx) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); - - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { - fprintf(stderr, "create socket failed, %s.\n", strerror(errno)); - exit(1); - } - - memset(&dst, 0, sizeof(dst)); - dst.sin_family = PF_INET; - dst.sin_port = htons(8899); - inet_aton("127.0.0.1", (struct in_addr *)&dst.sin_addr.s_addr); - rc = connect(sockfd, (struct sockaddr *)&dst, sizeof(dst)); - if (rc < 0) { - fprintf(stderr, "connect to server failed, %s.\n", strerror(errno)); - close(sockfd); - exit(1); - } - - ssl = SSL_new(ctx); - SSL_set_fd(ssl, sockfd); - if (SSL_connect(ssl) < 0) { - ERR_print_errors_fp(stderr); - exit(1); - } - printf("Connected with %s encryption\n", SSL_get_cipher(ssl)); - - if (verify_show_certs(ssl) < 0) { - fprintf(stderr, "invalid certs from peer.\n"); - SSL_shutdown(ssl); - SSL_free(ssl); - close(sockfd); - SSL_CTX_free(ctx); - return -1; - } - - SSL_write(ssl, "Hello!", strlen("Hello!")); - - SSL_shutdown(ssl); - SSL_free(ssl); - close(sockfd); - SSL_CTX_free(ctx); - - return 0; -} diff --git a/_drafts/sslsync/common.c b/_drafts/sslsync/common.c deleted file mode 100644 index 96161e8..0000000 --- a/_drafts/sslsync/common.c +++ /dev/null @@ -1,385 +0,0 @@ -#include "common.h" - -#include - -#define USE_SSL 1 - -struct ss_context *ssock_context_create(int size) -{ - struct ss_context *ctx; - - ctx = (struct ss_context *)calloc(1, sizeof(*ctx)); - if (ctx == NULL) - return NULL; - ctx->sockfd = -1; - - ctx->buff.max = size; - ctx->buff.body = malloc(ctx->buff.max); - if (ctx->buff.body == NULL) { - free(ctx); - return NULL; - } - - ctx->rsock.data = ctx; - ctx->wsock.data = ctx; - ctx->wtimeout.data = ctx; - - return ctx; -} - -void ssock_context_reset(EV_P_ struct ss_context *ctx) -{ - if (ctx == NULL) - return; - if (ev_is_active(&ctx->rsock)) - ev_io_stop(EV_A_ &ctx->rsock); - if (ev_is_active(&ctx->wsock)) - ev_io_stop(EV_A_ &ctx->wsock); - if (ev_is_active(&ctx->wtimeout)) - ev_timer_stop(EV_A_ &ctx->wtimeout); - if (ctx->sockfd >= 0) - close(ctx->sockfd); - ctx->sockfd = -1; - - if (ctx->ssl.ssl != NULL) { - SSL_shutdown(ctx->ssl.ssl); - SSL_free(ctx->ssl.ssl); - } - ctx->ssl.ssl = NULL; - - if (ctx->buff.body != NULL) - free(ctx->buff.body); - ctx->buff.body = NULL; -} - -void ssock_context_destroy(EV_P_ struct ss_context *ctx) -{ - if (ctx == NULL) - return; - - ssock_context_reset(EV_A_ ctx); - free(ctx); -} - -void ssock_openssl_detail_info(const SSL *ssl, int where, int ret) -{ - int w; - const char *str; - - w = where & ~SSL_ST_MASK; - if (w & SSL_ST_CONNECT) - str = "SSL_connect"; - else if (w & SSL_ST_ACCEPT) - str = "SSL_accept"; - else - str = "SSL_undefined"; - - if (where & SSL_CB_LOOP) { - fprintf(stderr, "%s:%s\n", str, SSL_state_string_long(ssl)); - return; - } else if (where & SSL_CB_ALERT) { - str = (where & SSL_CB_READ) ? "read" : "write"; - fprintf(stderr, "SSL3 alert %s:%s:%s\n", str, - SSL_alert_type_string_long(ret), - SSL_alert_desc_string_long(ret)); - return; - } else if (where & SSL_CB_EXIT) { - if (errno == EAGAIN || errno == EINTR) - return; - if (ret == 1) - return; - - if (ret == 0) { - fprintf(stderr, "%s:failed in state %s\n", - str, SSL_state_string_long(ssl)); - } else if (ret < 0) { - fprintf(stderr, "%s:error in state %s\n", - str, SSL_state_string_long(ssl)); - } - return; - } -} -void ssock_openssl_simple_info(const SSL *ssl, int where, int ret) -{ - int logit = 0; /* ignore low-level SSL stuff */ - - if (where & SSL_CB_ALERT) - logit = 1; - if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) - logit = 1; - if ((where & SSL_CB_EXIT) && ret == 0) - logit = 1; - if (logit == 0) - return; - - fprintf(stderr, "0x%04x %s, return 0x%x.\n", where, SSL_state_string_long(ssl), ret); - if (where == SSL_CB_HANDSHAKE_DONE) - fprintf(stderr, "using SSL version %s cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl)); -} - -SSL_CTX *ssock_ssl_init(int flag) -{ - SSL_CTX *ctx; - -#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); //OpenSSL_add_ssl_algorithms(); -#else - if (OPENSSL_init_ssl(OPENSSL_INIT_ENGINE_ALL_BUILTIN | OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) { - fprintf(stderr, "init OpenSSL failed.\n"); - return NULL; - } -#endif - -#if OPENSSL_VERSION_NUMBER < 0x10100000L - if (flag == SS_R_SERVER) - ctx = SSL_CTX_new(SSLv23_server_method()); - else // flag == SS_R_CLIENT - ctx = SSL_CTX_new(SSLv23_client_method()); -#else - if (flag == SS_R_SERVER) - ctx = SSL_CTX_new(TLS_server_method()); - else // flag == SS_R_CLIENT - ctx = SSL_CTX_new(TLS_client_method()); -#endif - if (ctx == NULL) { - ERR_print_errors_fp(stderr); - SSL_CTX_free(ctx); - return NULL; - } - //SSL_CTX_set_msg_callback(ctx, SSL_trace); - SSL_CTX_set_info_callback(ctx, ssock_openssl_detail_info); - //SSL_CTX_set_info_callback(ctx, ssock_openssl_simple_info); - - return ctx; -} - -int ssock_ssl_prepre(SSL_CTX *ctx, struct ss_certinfo *cert) -{ - if (ctx == NULL || cert == NULL) - return -EINVAL; - - /* load ca file */ - if (cert->cacert) { - if (SSL_CTX_load_verify_locations(ctx, cert->cacert, 0) != 1) { - ERR_print_errors_fp(stderr); - return -1; - } - if (SSL_CTX_set_default_verify_paths(ctx) != 1) { - ERR_print_errors_fp(stderr); - return -1; - } - } - - if (cert->key == NULL && cert->cert == NULL) - return 0; - - /* load certificate file, which will send public key to client. */ - if (SSL_CTX_use_certificate_file(ctx, cert->cert, SSL_FILETYPE_PEM) != 1) { - ERR_print_errors_fp(stderr); - return -1; - } - /* and also private key file */ - if (SSL_CTX_use_PrivateKey_file(ctx, cert->key, SSL_FILETYPE_PEM) != 1) { - ERR_print_errors_fp(stderr); - return -1; - } - /* check if the private key is valid */ - if (SSL_CTX_check_private_key(ctx) != 1) { - ERR_print_errors_fp(stderr); - return -1; - } - - return 0; -} - -SSL *ssock_ssl_create(SSL_CTX *ctx, int sockfd, int flag) -{ - SSL *ssl; - - ssl = SSL_new(ctx); - if (ssl == NULL) { - fprintf(stderr, "create SSL failed.\n"); - return NULL; - } - - if (SSL_set_fd(ssl, sockfd) == 0) { - fprintf(stderr, "set SSL fd failed, %s.\n", - ERR_reason_error_string(ERR_get_error())); - SSL_free(ssl); - return NULL; - } - //SSL_set_mode(ctx->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE); - - if (flag == SS_R_SERVER) - SSL_set_accept_state(ssl); - else if (flag == SS_R_CLIENT) - SSL_set_connect_state(ssl); - - return ssl; -} - -int ssock_verify_show_certs(SSL *ssl) -{ - X509 *cert; - char *line; - - cert = SSL_get_peer_certificate(ssl); - if (cert == NULL) { - printf("No certificate information!\n"); - return -1; - } - - if (SSL_get_verify_result(ssl) != X509_V_OK){ - printf("Invalid certificate information!\n"); - return -1; - } - printf("Digital certificate information:\n"); - - line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); - printf("Certificate: %s\n", line); - free(line); - - line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); - printf(" Issuer: %s\n", line); - free(line); - - X509_free(cert); - return 0; -} - -void ssock_ssl_handshake(EV_P_ struct ss_context *ctx) -{ - int rc, err; - - ERR_clear_error(); - rc = SSL_do_handshake(ctx->ssl.ssl); - if (rc == 1) { - fprintf(stderr, "TLS/SSL handshake was successfully completed, %d.\n", SSL_is_init_finished(ctx->ssl.ssl)); - if (ctx->ssl.hook != NULL) - ctx->ssl.hook(EV_A_ ctx); - else - ssock_context_destroy(EV_A_ ctx); - return; - } else if (rc == 0) { - fprintf(stderr, "TLS/SSL handshake was not successful but was shutdown.\n"); - ssock_context_destroy(EV_A_ ctx); - return; - } - - err = SSL_get_error(ctx->ssl.ssl, rc); - if (err == SSL_ERROR_WANT_READ) { - fprintf(stderr, "TLS/SSL want read.\n"); - if (ev_is_active(&ctx->wsock) == 1) - ev_io_stop(EV_A_ &ctx->wsock); - if (ev_is_active(&ctx->rsock) == 0) - ev_io_start(EV_A_ &ctx->rsock); - return; - } else if (err == SSL_ERROR_WANT_WRITE) { - fprintf(stderr, "TLS/SSL want write.\n"); - if (ev_is_active(&ctx->rsock) == 1) - ev_io_stop(EV_A_ &ctx->rsock); - if (ev_is_active(&ctx->wsock) == 0) - ev_io_start(EV_A_ &ctx->wsock); - return; - } else if (err == SSL_ERROR_SSL) { - fprintf(stderr, "bad TLS/SSL peer.\n"); - ssock_context_destroy(EV_A_ ctx); - return; - } else if (err == SSL_ERROR_SYSCALL && rc == 0) { - fprintf(stderr, "peer TLS/SSL client maybe closed.\n"); - ssock_context_destroy(EV_A_ ctx); - return; - } - - fprintf(stderr, "SSL do handshake failed, %d:%s.\n", errno, strerror(errno)); - ssock_context_destroy(EV_A_ ctx); -} - -int ssock_read(struct ss_context *ctx, char *buff, int len) -{ -#if USE_SSL - int rc, err; - - rc = SSL_read(ctx->ssl.ssl, buff, len); - err = SSL_get_error(ctx->ssl.ssl, rc); - if (rc > 0) { - return rc; - } else if (rc < 0) { - /* SSL_ERROR_SYSCALL SSL_ERROR_SSL */ - err = SSL_get_error(ctx->ssl.ssl, rc); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) - return 0; - fprintf(stderr, "got SSL error, %s\n", - ERR_reason_error_string(SSL_get_error(ctx->ssl.ssl, rc))); - return rc; - } else if (rc == 0) { - err = SSL_get_error(ctx->ssl.ssl, 0); - if (err == SSL_ERROR_SYSCALL) { - fprintf(stderr, "peer SSL client maybe closed.\n"); - return -1; - } - fprintf(stderr, "got SSL error, %s\n", - ERR_reason_error_string(SSL_get_error(ctx->ssl.ssl, rc))); - return -1; - } - return rc; -#else - int rc; - - rc = read(ctx->sockfd, buff, len); - if (rc < 0) { - fprintf(stderr, "read from socket %d failed, %d:%s.\n", ctx->sockfd, - errno, strerror(errno)); - return rc; - } else if (rc == 0) { - fprintf(stderr, "peer maybe closed.\n"); - return -1; - } - return rc; -#endif -} - -int ssock_write(struct ss_context *ctx, char *buff, int len) -{ -#if USE_SSL - int rc, err; - - rc = SSL_write(ctx->ssl.ssl, buff, len); - if (rc > 0) { - return rc; - } else if (rc < 0) { - /* SSL_ERROR_SYSCALL SSL_ERROR_SSL */ - err = SSL_get_error(ctx->ssl.ssl, rc); - if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) - return 0; - fprintf(stderr, "got SSL error, %s\n", - ERR_reason_error_string(SSL_get_error(ctx->ssl.ssl, rc))); - return rc; - } else if (rc == 0) { - err = SSL_get_error(ctx->ssl.ssl, 0); - if (err == SSL_ERROR_SYSCALL) { - fprintf(stderr, "peer SSL client maybe closed.\n"); - return -1; - } - fprintf(stderr, "got SSL error, %s\n", - ERR_reason_error_string(SSL_get_error(ctx->ssl.ssl, rc))); - return -1; - } - return rc; -#else - int rc; - - rc = write(ctx->sockfd, buff, len); - if (rc < 0) { - fprintf(stderr, "write to socket %d failed, %d:%s.\n", ctx->sockfd, - errno, strerror(errno)); - return rc; - } else if (rc == 0) { - fprintf(stderr, "peer maybe closed.\n"); - return -1; - } - return rc; -#endif -} diff --git a/_drafts/sslsync/common.h b/_drafts/sslsync/common.h deleted file mode 100644 index 38ee70c..0000000 --- a/_drafts/sslsync/common.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef COMMON_H_ -#define COMMON_H_ - -#include -#include - -#include -#include - -#include "libev/ev.h" - -#define likely -#define unlikely - -enum ss_state { - SS_INVALID, - SS_CONNECTING, - SS_SSL_HANDSHAKE, - SS_HANDSHAKE, - SS_APP_READ, - SS_APP_WRITE, -}; - -enum ss_role { - SS_R_NONE, - SS_R_SERVER, - SS_R_CLIENT, -}; - -struct ss_certinfo { - const char *cacert; - const char *key; - const char *cert; -}; - -struct ss_context { - enum ss_role role; - enum ss_state state; - - int sockfd; - struct ev_timer wtimeout; - struct ev_io wsock, rsock; - - struct { - SSL *ssl; - SSL_CTX *ctx; - void(*hook)(EV_P_ struct ss_context *); - } ssl; - - struct { - char *body; - int len, max; - } buff; - struct sockaddr_in addr; -}; - -struct ss_context *ssock_context_create(int size); -void ssock_context_reset(EV_P_ struct ss_context *ctx); -void ssock_context_destroy(EV_P_ struct ss_context *ctx); - -SSL_CTX *ssock_ssl_init(int flag); -int ssock_ssl_prepre(SSL_CTX *ctx, struct ss_certinfo *cert); -SSL *ssock_ssl_create(SSL_CTX *ctx, int sockfd, int flag); -void ssock_ssl_handshake(EV_P_ struct ss_context *ctx); - -int ssock_verify_show_certs(SSL *ssl); - -int ssock_read(struct ss_context *ctx, char *buff, int len); -int ssock_write(struct ss_context *ctx, char *buff, int len); - -#endif diff --git a/_drafts/sslsync/server.c b/_drafts/sslsync/server.c deleted file mode 100644 index 3b15381..0000000 --- a/_drafts/sslsync/server.c +++ /dev/null @@ -1,183 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include - -#define MAXBUF 1023 - -int verify_show_certs(SSL *ssl) -{ - X509 *cert; - char *line; - - cert = SSL_get_peer_certificate(ssl); - if (cert == NULL) { - printf("No certificate information!\n"); - return -1; - } - - if (SSL_get_verify_result(ssl) != X509_V_OK){ - printf("Invalid certificate information!\n"); - return -1; - } - - printf("Digital certificate information:\n"); - - line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); - printf("Certificate: %s\n", line); - free(line); - - line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); - printf(" Issuer: %s\n", line); - free(line); - - X509_free(cert); - return 0; -} - -int main(void) -{ - SSL *ssl; - SSL_CTX *ctx; - socklen_t len; - char buff[MAXBUF + 1]; - int sockfd, clifd, rc; - struct sockaddr_in svraddr, cliaddr; - - signal(SIGPIPE, SIG_IGN); - - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - - ctx = SSL_CTX_new(SSLv23_server_method()); /* support both V2 and V3 */ - if (ctx == NULL) { - ERR_print_errors_fp(stderr); - exit(1); - } - - /* set our supported ciphers */ - if(SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH") != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - - /* the client have to send it's certificate, default SSL_VERIFY_NONE */ - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); - - /* load ca file */ - if (SSL_CTX_load_verify_locations(ctx, "pki/CA/cacert.pem", 0) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - -#if 0 - /* enable srtp */ - r = SSL_CTX_set_tlsext_use_srtp(k->ctx, "SRTP_AES128_CM_SHA1_80"); - if(r != 0) { - printf("Error: cannot setup srtp.\n"); - ERR_print_errors_fp(stderr); - return -3; - } -#endif - /* load certificate file, which will send public key to client. */ - if (SSL_CTX_use_certificate_file(ctx, "pki/SVR/cert.pem", SSL_FILETYPE_PEM) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - /* and also private key file */ - if (SSL_CTX_use_PrivateKey_file(ctx, "pki/SVR/key.pem", SSL_FILETYPE_PEM) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - /* check if the private key is valid */ - if (SSL_CTX_check_private_key(ctx) != 1) { - ERR_print_errors_fp(stderr); - exit(1); - } - SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); - - sockfd = socket(PF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { - fprintf(stderr, "create socket failed, %s.\n", strerror(errno)); - exit(1); - } - - memset(&svraddr, 0, sizeof(svraddr)); - svraddr.sin_family = PF_INET; - svraddr.sin_port = htons(8090); - svraddr.sin_addr.s_addr = INADDR_ANY; - if (bind(sockfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr)) < 0) { - fprintf(stderr, "bind socket failed, %s.\n", strerror(errno)); - close(sockfd); - exit(1); - } - - if (listen(sockfd, 128) < 0) { - fprintf(stderr, "listen socket failed, %s.\n", strerror(errno)); - close(sockfd); - exit(1); - } - - len = sizeof(struct sockaddr); - while (1) { - clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); - if (clifd < 0) { - fprintf(stderr, "listen socket failed, %s.\n", strerror(errno)); - continue; - } - fprintf(stdout, "got connection #%d from [%s:%d]\n", clifd, - inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); - - ssl = SSL_new(ctx); - if (ssl == NULL) { - fprintf(stderr, "create SSL context failed.\n"); - close(clifd); - continue; - } - SSL_set_fd(ssl, clifd); - - if (SSL_accept(ssl) < 0) { - fprintf(stderr, "accept SSL failed.\n"); - close(clifd); - continue; - } - - if (verify_show_certs(ssl) < 0) { - fprintf(stderr, "invalid certs from peer.\n"); - SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - //SSL_shutdown(ssl); - SSL_free(ssl); - close(clifd); - continue; - } - - rc = SSL_read(ssl, buff, sizeof(buff) - 1); /* for '\0' */ - if (rc < 0) { - fprintf(stderr, "read from SSL failed.\n"); - SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - //SSL_shutdown(ssl); - SSL_free(ssl); - close(clifd); - continue; - } - buff[rc] = 0; - fprintf(stdout, "got data(%02d): %s\n", rc, buff); - - rc = SSL_write(ssl, buff, rc); /* for '\0' */ - - - SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); - //SSL_shutdown(ssl); - SSL_free(ssl); - close(clifd); - } - - SSL_CTX_free(ctx); - return 0; -} diff --git a/_drafts/struct/hash/list.c b/_drafts/struct/hash/list.c deleted file mode 100644 index 3665589..0000000 --- a/_drafts/struct/hash/list.c +++ /dev/null @@ -1,220 +0,0 @@ -#include "list.h" - -#include -#include -#include -#include - -#ifndef HASH_DEFAULT_CAPACITY -#define HASH_DEFAULT_CAPACITY 10 -#endif - -/* http://www.cse.yorku.ca/~oz/hash.html */ -static inline unsigned int hash_default_func(const char *str) -{ - int c; - unsigned int hash = 5381; - - assert(str); - while ((c = *str++)) - hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ - return hash; -} - -static inline struct hash_entry *create_new_entry(char *key, void *value) -{ - struct hash_entry *ent; - - ent = (struct hash_entry *)malloc(sizeof(*ent)); - if (ent == NULL) - return NULL; - ent->next = NULL; - ent->key = key; - ent->value = value; - - return ent; -} - -struct hash_table *hash_create(unsigned int size) -{ - struct hash_table *hash; - - hash = (struct hash_table *)malloc(sizeof(*hash)); - if (hash == NULL) - return NULL; - - hash->buckets = (struct hash_header *)calloc(size, - sizeof(struct hash_header)); - if (hash->buckets == NULL) { - free(hash); - return NULL; - } - hash->size = size; - hash->entry_destroy = NULL; - hash->hash_func = hash_default_func; - hash->capacity = HASH_DEFAULT_CAPACITY; - - return hash; -} -int hash_options(struct hash_table *h, int action, void *data) -{ - if (h == NULL) { - errno = EINVAL; - return -1; - } - - switch (action) { - case HASH_OPT_CAPACITY: - h->capacity = *(unsigned int *)data; - return 0; - case HASH_OPT_HASH_FUNC: - h->hash_func = (unsigned int(*)(const char *))data; - return 0; - case HASH_OPT_ENTRY_DESTORY: - h->entry_destroy = (void(*)(char *, void *))data; - return 0; - } - - errno = EINVAL; - return -1; -} - -void hash_destroy(struct hash_table *h) -{ - unsigned int i; - struct hash_entry *curr, *next; - - if (h == NULL) - return; - if (h->buckets == NULL) { - free(h); - return; - } - - for (i = 0; i < h->size; i++) { - for (curr = h->buckets[i].head; curr; curr = next) { - next = curr->next; - if (h->entry_destroy) - (*h->entry_destroy)(curr->key, curr->value); - free(curr); - } - } - - free(h->buckets); - free(h); -} -static int hash_expend(struct hash_table *h) -{ - unsigned int i, idx, newsz; - struct hash_header *headers; - struct hash_entry *curr, *next; - - newsz = h->size * 2; - headers = (struct hash_header *)calloc(newsz, sizeof(*headers)); - if (headers == NULL) - return -1; - - for (i = 0; i < h->size; i++) { - for (curr = h->buckets[i].head; curr; curr = next) { - next = curr->next; - - idx = (*h->hash_func)(curr->key) % newsz; - if (headers[idx].head == NULL) - curr->next = NULL; - else - curr->next = headers[idx].head; - headers[idx].head = curr; - headers[idx].count++; - } - } - - free(h->buckets); - h->buckets = headers; - - return 0; -} - - -int hash_put(struct hash_table *h, char *key, void *value) -{ - struct hash_header *header; - struct hash_entry *curr, *ent; - - header = h->buckets + (h->hash_func(key) % h->size); - if (header->head == NULL) { - ent = create_new_entry(key, value); - if (ent == NULL) - return -1; - header->head = ent; - header->count = 1; - return 0; - } - - /* replace it if entry already exists. */ - for (curr = header->head; curr; curr = curr->next) { - if (strcmp(curr->key, key)) - continue; - - if (h->entry_destroy) - (*h->entry_destroy)(curr->key, curr->value); - curr->key = key; - curr->value = value; - return 0; - } - - /* add to head. */ - ent = create_new_entry(key, value); - if (ent == NULL) - return -1; - ent->next = header->head; - header->head = ent; - header->count++; - - if (header->count > h->capacity) - return hash_expend(h); - - return 0; -} - -void *hash_get(struct hash_table *h, const char *key) -{ - struct hash_header *header; - struct hash_entry *entry; - - header = h->buckets + (h->hash_func(key) % h->size); - if (header == NULL) - return NULL; - - for (entry = header->head; entry; entry = entry->next) - if (strcmp(entry->key, key) == 0) - return entry->value; - - return NULL; -} - -void *hash_del(struct hash_table *h, const char *key) -{ - void *data; - struct hash_header *header; - struct hash_entry *curr, *prev = NULL; - - header = h->buckets + (h->hash_func(key) % h->size); - if (header == NULL) - return NULL; - - for (curr = header->head; curr; curr = curr->next) { - if (strcmp(curr->key, key) == 0) { - if (prev == NULL) - header->head = curr->next; - else - prev->next = curr->next; - data = curr->value; - free(curr); - header->count--; - return data; - } - prev = curr; - } - - return NULL; -} diff --git a/_drafts/struct/hash/list.h b/_drafts/struct/hash/list.h deleted file mode 100644 index f8f9583..0000000 --- a/_drafts/struct/hash/list.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef LIBUTILS_STRUCT_HASH_LIST_H_ -#define LIBUTILS_STRUCT_HASH_LIST_H_ - -enum { - HASH_OPT_CAPACITY, - HASH_OPT_HASH_FUNC, - HASH_OPT_ENTRY_DESTORY -}; - -#define HASH_EXISTS -2 -#define HASH_NOT_EXISTS -3 - -struct hash_entry { - struct hash_entry *next; - char *key; - void *value; -}; - -struct hash_header { - struct hash_entry *head; - unsigned int count; -}; - -struct hash_table { - struct hash_header *buckets; - unsigned int size, capacity; - - void (*entry_destroy)(char *, void *); - unsigned int (*hash_func)(const char *); -}; - -struct hash_table *hash_create(unsigned int size); -int hash_options(struct hash_table *h, int action, void *data); -int hash_put(struct hash_table *h, char *key, void *value); - -void hash_destroy(struct hash_table *h); -void *hash_get(struct hash_table *h, const char *key); -void *hash_del(struct hash_table *h, const char *key); - -#endif diff --git a/_drafts/struct/heap.c b/_drafts/struct/heap.c deleted file mode 100644 index eababd5..0000000 --- a/_drafts/struct/heap.c +++ /dev/null @@ -1,216 +0,0 @@ -#include -#include -#include - -#include "heap.h" - -static inline unsigned int _child_left(const unsigned int idx) -{ - return idx * 2 + 1; -} - -static inline unsigned int _child_right(const unsigned int idx) -{ - return idx * 2 + 2; -} - -static inline unsigned int _parent(const unsigned int idx) -{ - return (idx - 1) / 2; -} - -static inline unsigned int _node_max(struct heap *h) -{ - if (h->count <= 1) - return 0; - return (h->count - 2) / 2; -} - -static inline void _swap(struct heap *h, const unsigned int i1, const unsigned int i2) -{ - uintptr_t tmp; - assert(i1 < h->count && i1 < h->count); - - tmp = h->data[i1]; - h->data[i1] = h->data[i2]; - h->data[i2] = tmp; -} - -struct heap *heap_create(int (*cmp)(const uintptr_t, const uintptr_t)) -{ - struct heap *h; - - if (cmp == NULL) - return NULL; - - h = (struct heap *)malloc(sizeof(*h)); - if (h == NULL) - return NULL; - h->compare = cmp; - h->count = 0; - h->size = HEAP_DEFAULT_SIZE; - h->data = malloc(sizeof(uintptr_t) * h->size); - if (h->data == NULL) { - heap_destroy(h); - return NULL; - } - - return h; -} - -void heap_destroy(struct heap *h) -{ - if (h == NULL) - return; - if (h->data != NULL) - free(h->data); - free(h); -} - -static int _pushup(struct heap * h, unsigned int idx) -{ - unsigned int parent; - - /* 0 is the root node */ - while (0 != idx) { - parent = _parent(idx); - - log_info("------- %d(%ld) %d(%ld)", idx, h->data[idx], parent, h->data[parent]); - /* we are smaller than the parent */ - if (h->compare(h->data[idx], h->data[parent]) < 0) { - log_info("------- swap"); - _swap(h, idx, parent); - } else { - log_info("------- return"); - return 0; - } - idx = parent; - } - - return 0; -} - -static int _pushdown(struct heap * h, unsigned int idx) -{ - unsigned int left, right, child; - - while (1) { - left = _child_left(idx); - right = _child_right(idx); - - if (right >= h->count) { - /* can't pushdown any further */ - if (left >= h->count) - return 0; - child = left; - } else if (h->compare(h->data[left], h->data[right]) < 0) { - /* find smallest child */ - child = left; - } else { - child = right; - } - - /* idx is smaller than child */ - if (h->compare(h->data[idx], h->data[child]) < 0) - return 0; - _swap(h, idx, child); - idx = child; - } - - return 0; -} - -static int heap_verify_node(struct heap *h, unsigned int idx) -{ - unsigned int left, right; - - log_debug("verify node %u.", idx); - left = _child_left(idx); - if (left >= h->count) - return HEAP_OK; - if (h->compare(h->data[idx], h->data[left]) > 0) - return HEAP_NOT_OK; - - right = _child_right(idx); - if (right >= h->count) - return HEAP_OK; - if (h->compare(h->data[idx], h->data[right]) > 0) - return HEAP_NOT_OK; - - return HEAP_OK; -} - -int heap_verify(struct heap *h) -{ - unsigned int idx; - - if (h == NULL) - return -1; - - log_info("node max %d/%d", _node_max(h), h->count); - for (idx = _node_max(h); idx > 0; idx--) - if (heap_verify_node(h, idx) == HEAP_NOT_OK) - return HEAP_NOT_OK; - return heap_verify_node(h, 0); -} - -int heap_insert(struct heap *h, uintptr_t v) -{ - uintptr_t *tmp; - unsigned int size; - - if (h == NULL) - return -EINVAL; - - if (h->count >= h->size) { - size = h->size * 2; - tmp = realloc(h->data, size); - if (tmp == NULL) - return -ENOMEM; - h->data = tmp; - h->size = size; - } - h->data[h->count] = v; - - return _pushup(h, h->count++); -} - -/* NOTE: valid only when value is a ptr */ -int heap_remove(struct heap *h, uintptr_t v) -{ - unsigned int idx; - - if (h == NULL) - return -EINVAL; - - for (idx = 0; idx < h->count; idx++) - if (h->data[idx] == v) - break; - if (idx == h->count) - return -ENOENT; - - /* swap the item we found with the last item on the heap */ - h->data[idx] = h->data[h->count - 1]; - h->count--; - _pushup(h, idx); /* ensure heap property */ - - return 0; -} - -int heap_get_root(struct heap *h, uintptr_t *ret) -{ - uintptr_t tmp; - - if (h == NULL || ret == NULL) - return -EINVAL; - if (heap_count(h) == 0) - return -1; - - tmp = h->data[0]; - h->data[0] = h->data[h->count - 1]; - h->count--; - if (h->count > 1) - _pushdown(h, 0); - *ret = tmp; - return 0; -} diff --git a/_drafts/struct/heap.h b/_drafts/struct/heap.h deleted file mode 100644 index 334b687..0000000 --- a/_drafts/struct/heap.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef LIBUTILS_STRUCT_HEAP_H_ -#define LIBUTILS_STRUCT_HEAP_H_ 1 - -#include - -#define HEAP_DEFAULT_SIZE 16 -#define HEAP_OK 0 -#define HEAP_NOT_OK -1 - - -#include - -#define log_debug(...) do { printf("debug: " __VA_ARGS__); putchar('\n'); } while(0); -#define log_info(...) do { printf("info : " __VA_ARGS__); putchar('\n'); } while(0); -#define log_error(...) do { printf("error: " __VA_ARGS__); putchar('\n'); } while(0); - -/* - * index #0 is the root node. - */ -struct heap { - int (*compare)(const uintptr_t, const uintptr_t); - - uintptr_t *data; - unsigned int count, size; -}; - -static inline int heap_count(struct heap *h) -{ - return h->count; -} - -static inline int heap_size(struct heap *h) -{ - return h->size; -} - -void heap_destroy(struct heap *h); -struct heap *heap_create(int (*cmp)(const uintptr_t, const uintptr_t)); - -int heap_verify(struct heap *h); -int heap_insert(struct heap *h, uintptr_t v); -int heap_remove(struct heap *h, uintptr_t v); -int heap_get_root(struct heap *h, uintptr_t *ret); - -#endif diff --git a/_drafts/struct/lists/double.c b/_drafts/struct/lists/double.c deleted file mode 100644 index d635580..0000000 --- a/_drafts/struct/lists/double.c +++ /dev/null @@ -1,104 +0,0 @@ -#include -#include - -#include "double.h" - -struct dlist_header *dlist_header_create(void) -{ - return calloc(1, sizeof(struct dlist_header)); -} - -void dlist_header_destroy(struct dlist_header *header) -{ - if (header == NULL) - return; - free(header); -} - -void dlist_destroy(struct dlist_header *list, void (*func)(void *)) -{ - struct dlist_node *curr, *next; - - assert(list != NULL); - for (curr = list->head; curr; curr = next) { - next = curr->next; - if (func) - (*func)(curr->data); - free(curr); - } - free(list); -} - -void dlist_del_node(struct dlist_header *list, struct dlist_node *node, void (*func)(void *)) -{ - assert(list != NULL && node != NULL); - if (list->head == node) { - list->head = node->next; - if (list->head == NULL) - list->tail = NULL; - else - node->next->prev = NULL; - } else if (list->tail == node) { - list->tail = node->prev; - if (list->tail == NULL) - list->head = NULL; - else - node->prev->next = NULL; - } else { - node->prev->next = node->next; - node->next->prev = node->prev; - } - - if (func) - (*func)(node->data); - free(node); - list->size--; -} - -struct dlist_node *dlist_add_tail(struct dlist_header *list, void *data) -{ - struct dlist_node *node; - - assert(list != NULL && data != NULL); - node = (struct dlist_node *)malloc(sizeof(*node)); - if (node == NULL) - return NULL; - - node->next = NULL; - node->data = data; - if (list->tail == NULL) { - list->head = node; - node->prev = NULL; - } else { - list->tail->next = node; - node->prev = list->tail; - } - list->tail = node; - list->size++; - - return node; -} - -struct dlist_node *dlist_add_head(struct dlist_header *list, void *data) -{ - struct dlist_node *node; - - assert(list != NULL && data != NULL); - node = (struct dlist_node *)malloc(sizeof(*node)); - if (node == NULL) - return NULL; - node->data = data; - - node->prev = NULL; - if (list->head == NULL) { - list->tail = node; - node->next = NULL; - } else { - list->head->prev = node; - node->next = list->head; - } - list->head = node; - list->size++; - - return node; -} diff --git a/_drafts/struct/lists/double.h b/_drafts/struct/lists/double.h deleted file mode 100644 index 4e70692..0000000 --- a/_drafts/struct/lists/double.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef LIBUTILS_STRUCT_LIST_DOUBLE_H_ -#define LIBUTILS_STRUCT_LIST_DOUBLE_H_ - -struct dlist_node { - struct dlist_node *next, *prev; - void *data; -}; - -struct dlist_header { - struct dlist_node *head, *tail; - int size; -}; - -struct dlist_header *dlist_header_create(void); -void dlist_header_destroy(struct dlist_header *header); -struct dlist_node *dlist_add_tail(struct dlist_header *list, void *data); -struct dlist_node *dlist_add_head(struct dlist_header *list, void *data); -void dlist_destroy(struct dlist_header *list, void (*func)(void *)); -void dlist_del_node(struct dlist_header *list, struct dlist_node *node, void (*func)(void *)); - -#endif diff --git a/_drafts/struct/lists/list.c b/_drafts/struct/lists/list.c deleted file mode 100644 index a9b202f..0000000 --- a/_drafts/struct/lists/list.c +++ /dev/null @@ -1,306 +0,0 @@ - -#include -#include -#include - -#include "list.h" - -llentry_t *llentry_create(char *key, void *value) -{ - llentry_t *e; - - e = (llentry_t *)malloc(sizeof(*e)); - if (e) { - e->key = key; - e->value = value; - e->next = NULL; - } - - return e; -} - -void llentry_destroy(llentry_t *e) -{ - free(e); -} - -llist_t *llist_create(void) -{ - llist_t *ret; - - ret = (llist_t *)calloc(1, sizeof(*ret)); - if (ret == NULL) - return NULL; - - return ret; -} - -void llist_destroy(llist_t *l) -{ - llentry_t *e_this; - llentry_t *e_next; - - if (l == NULL) - return; - - for (e_this = l->head; e_this != NULL; e_this = e_next) { - e_next = e_this->next; - llentry_destroy(e_this); - } - - free(l); -} - -void llist_append(llist_t *l, llentry_t *e) -{ - e->next = NULL; - - if (l->tail == NULL) - l->head = e; - else - l->tail->next = e; - - l->tail = e; - - ++(l->size); -} - -void llist_prepend(llist_t *l, llentry_t *e) -{ - e->next = l->head; - l->head = e; - - if (l->tail == NULL) - l->tail = e; - - ++(l->size); -} - -void llist_remove(llist_t *l, llentry_t *e) -{ - llentry_t *prev; - - if ((l == NULL) || (e == NULL)) - return; - - prev = l->head; - while ((prev != NULL) && (prev->next != e)) - prev = prev->next; - - if (prev != NULL) - prev->next = e->next; - if (l->head == e) - l->head = e->next; - if (l->tail == e) - l->tail = prev; - - --(l->size); -} - -int llist_size(llist_t *l) -{ - return (l ? l->size : -1); -} - -static int llist_strcmp(llentry_t *e, void *ud) -{ - if ((e == NULL) || (ud == NULL)) - return -1; - return (strcmp(e->key, (const char *)ud)); -} - -llentry_t *llist_search_custom(llist_t *l, int (*compare)(llentry_t *, void *), void *user_data) -{ - llentry_t *e; - - if (l == NULL) - return NULL; - - e = l->head; - while (e != NULL) { - llentry_t *next = e->next; - - if (compare(e, user_data) == 0) - break; - - e = next; - } - - return e; -} - -llentry_t *llist_search(llist_t *l, const char *key) -{ - return (llist_search_custom(l, llist_strcmp, (void *)key)); -} - -llentry_t *llist_head(llist_t *l) -{ - if (l == NULL) - return NULL; - return (l->head); -} - -llentry_t *llist_tail(llist_t *l) -{ - if (l == NULL) - return NULL; - return (l->tail); -} - - - - - - - - - - - - - - - - - - - - - -struct slnode *slnode_create(char *key, void *value) -{ - struct slnode *node; - - node = (struct slnode *)malloc(sizeof(*node)); - if (node) { - node->key = key; - node->value = value; - node->next = NULL; - } - - return node; -} - -void slnode_destory(struct slnode *node) -{ - free(node); -} - -struct slist *slist_create(void) -{ - return (struct slist *)calloc(1, sizeof(struct slist)); -} - -void slist_destroy(struct slist *list) -{ - struct slnode *this, *next; - - if (list == NULL) - return; - - for (this = list->head; this != NULL; this = next) { - next = this->next; - slnode_destory(this); - } - - free(list); -} - -struct slnode *slist_head(struct slist *list) -{ - if (list == NULL) - return NULL; - return (list->head); -} - -struct slnode *slist_tail(struct slist *list) -{ - if (list == NULL) - return NULL; - return (list->tail); -} - -void slist_append(struct slist *list, struct slnode *node) -{ - if (list == NULL || node == NULL) - return; - - node->next = NULL; - - if (list->tail == NULL) - list->head = node; - else - list->tail->next = node; - list->tail = node; - - ++(list->size); -} - -void slist_prepend(struct slist *list, struct slnode *node) -{ - if (list == NULL || node == NULL) - return; - - node->next = list->head; - list->head = node; - - if (list->tail == NULL) - list->tail = node; - - ++(list->size); -} - -void slist_remove(struct slist *list, struct slnode *node) -{ - struct slnode *prev; - - if (list == NULL || node == NULL) - return; - - prev = list->head; - while ((prev != NULL) && (prev->next != node)) - prev = prev->next; - - if (prev != NULL) - prev->next = node->next; - if (list->head == node) - list->head = node->next; - if (list->tail == node) - list->tail = prev; - - --(list->size); -} - -int slist_size(struct slist *list) -{ - return (list ? list->size : -1); -} - -static int slist_strcmp(struct slnode *node, void *ud) -{ - if ((node == NULL) || (ud == NULL)) - return -1; - return (strcmp(node->key, (const char *)ud)); -} - -struct slnode *slist_search_custom(struct slist *list, int (*compare)(struct slnode *, void *), void *user_data) -{ - struct slnode *node, *next; - - if (list == NULL) - return NULL; - - for (node = list->head; node; node = next) { - next = node->next; - if (compare(node, user_data) == 0) - break; - } - - return node; -} - -struct slnode *slist_search(struct slist *list, const char *key) -{ - return (slist_search_custom(list, slist_strcmp, (void *)key)); -} diff --git a/_drafts/struct/lists/list.h b/_drafts/struct/lists/list.h deleted file mode 100644 index a0a3fb3..0000000 --- a/_drafts/struct/lists/list.h +++ /dev/null @@ -1,70 +0,0 @@ - -#ifndef LIBUTIL_LIST_H_ -#define LIBUTIL_LIST_H_ 1 - -typedef struct llentry_s { - char *key; - void *value; - struct llentry_s *next; -} llentry_t; - -typedef struct llist_s { - llentry_t *head; - llentry_t *tail; - int size; -} llist_t; - - -llist_t *llist_create(void); -void llist_destroy(llist_t *l); - -llentry_t *llentry_create(char *key, void *value); -void llentry_destroy(llentry_t *e); - -void llist_append(llist_t *l, llentry_t *e); -void llist_prepend(llist_t *l, llentry_t *e); -void llist_remove(llist_t *l, llentry_t *e); - -int llist_size(llist_t *l); - -llentry_t *llist_search(llist_t *l, const char *key); -llentry_t *llist_search_custom(llist_t *l, int (*compare)(llentry_t *, void *), void *user_data); - -llentry_t *llist_head(llist_t *l); -llentry_t *llist_tail(llist_t *l); - - - - - - - - - -/* single linked list */ -struct slnode { - char *key; - void *value; - struct slnode *next; -}; - -struct slist { - struct slnode *head, *tail; - int size; -}; - -struct slnode *slnode_create(char *key, void *value); -void slnode_destory(struct slnode *node); -struct slist *slist_create(void); -void slist_destroy(struct slist *list); -struct slnode *slist_head(struct slist *list); -struct slnode *slist_tail(struct slist *list); -void slist_append(struct slist *list, struct slnode *node); -void slist_prepend(struct slist *list, struct slnode *node); -void slist_remove(struct slist *list, struct slnode *node); -int slist_size(struct slist *list); - -struct slnode *slist_search_custom(struct slist *list, int (*compare)(struct slnode *, void *), void *user_data); -struct slnode *slist_search(struct slist *list, const char *key); - -#endif diff --git a/_drafts/struct/queue.c b/_drafts/struct/queue.c deleted file mode 100644 index be2cf2f..0000000 --- a/_drafts/struct/queue.c +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include -#include - -#include "queue.h" - -void queue_destroy(struct queue *q) -{ - if (q == NULL) - return; - if (q->data != NULL) - free(q->data); - free(q); -} - -struct queue *queue_create(int capacity) -{ - struct queue *q; - - if (capacity <= 0) - capacity = 64; - - q = (struct queue *)calloc(1, sizeof(*q)); - if (q == NULL) - return NULL; - q->data = malloc(sizeof(Q_TYPE) * capacity); - if (q->data == NULL) { - free(q); - return NULL; - } - q->capacity = capacity; - - return q; -} - -int queue_get_front(struct queue *q, Q_TYPE *ret) -{ - if (queue_is_empty(q)) - return -1; - if (ret != NULL) - *ret = q->data[(q->front + 1) % q->capacity]; - return 0; -} - -int queue_get_back(struct queue *q, Q_TYPE *ret) -{ - if (queue_is_empty(q)) - return -1; - if (ret != NULL) - *ret = q->data[q->back]; - return 0; -} -static int queue_double_capacity(struct queue *q) -{ - Q_TYPE *tmp; - int cap, size, i, j; - - cap = q->capacity * 2; - tmp = malloc(sizeof(Q_TYPE) * cap); - if (tmp == NULL) - return -ENOMEM; - size = queue_get_size(q); - for (i = 1, j = q->front + 1; i <= size; i++, j++) - tmp[i] = q->data[j % q->capacity]; - - free(q->data); - q->data = tmp; - q->capacity = cap; - q->front = 0; - q->back = size; - - return 0; -} - -int queue_push(struct queue *q, Q_TYPE val) -{ - if (queue_is_full(q) && queue_double_capacity(q) < 0) - return -1; - q->back = (q->back + 1) % q->capacity; - q->data[q->back] = val; - - return 0; -} - -int queue_pop(struct queue *q, Q_TYPE *ret) -{ - if (queue_is_empty(q)) - return -1; - q->front = (q->front + 1) % q->capacity; - if (ret != NULL) - *ret = q->data[q->front]; - - return 0; -} diff --git a/_drafts/struct/queue.h b/_drafts/struct/queue.h deleted file mode 100644 index bf3c023..0000000 --- a/_drafts/struct/queue.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef LIBUTILS_STRUCT_QUEUE_H_ -#define LIBUTILS_STRUCT_QUEUE_H_ 1 - -#define Q_TYPE int - -/* -** NOTE: the front one will be empty, the first one will be (front + 1). -** NOTE: it's actually capacity is (capacity - 1). -*/ -struct queue { - int front, back, capacity; - Q_TYPE *data; -}; - -static inline int queue_is_empty(struct queue *q) -{ - return (q->front == q->back); -} - -static inline int queue_is_full(struct queue *q) -{ - return (((q->back + 1) % q->capacity) == q->front); -} - -static inline int queue_get_size(struct queue *q) -{ - if (q->back >= q->front) - return q->back - q->front; - else - return q->capacity - (q->front - q->back); -} - -static inline int queue_get_capacity(struct queue *q) -{ - return q->capacity; -} - -void queue_destroy(struct queue *q); -struct queue *queue_create(int capacity); -int queue_get_front(struct queue *q, Q_TYPE *ret); -int queue_get_back(struct queue *q, Q_TYPE *ret); -int queue_push(struct queue *q, Q_TYPE val); -int queue_pop(struct queue *q, Q_TYPE *ret); - -#endif diff --git a/_drafts/struct/skiplist/rank.c b/_drafts/struct/skiplist/rank.c deleted file mode 100644 index f2b4f14..0000000 --- a/_drafts/struct/skiplist/rank.c +++ /dev/null @@ -1,260 +0,0 @@ -#include "rank.h" - -#include - -#define SASIZE(arr) (int)(sizeof(arr)/sizeof((arr)[0])) - -static inline void list_init(struct skiplist_list *list) -{ - list->prev = list; - list->next = list; -} - -static inline void list_do_add(struct skiplist_list *list, struct skiplist_list *prev, struct skiplist_list *next) -{ - list->next = next; - list->prev = prev; - next->prev = list; - prev->next = list; -} - -static inline void list_do_del(struct skiplist_list *prev, struct skiplist_list *next) -{ - prev->next = next; - next->prev = prev; -} - -static inline void list_add(struct skiplist_list *list, struct skiplist_list *next) -{ - list_do_add(list, next->prev, next); -} - -static inline void list_del(struct skiplist_list *link) -{ - list_do_del(link->prev, link->next); - list_init(link); -} - -static inline int list_empty(struct skiplist_list *link) -{ - return link->next == link; -} - -struct skiplist *skiplist_create(int (*cmp)(const uintptr_t, const uintptr_t)) -{ - int i; - struct skiplist *s; - - if (cmp == NULL) - return NULL; - - s = (struct skiplist *)malloc(sizeof(*s)); - if (s == NULL) - return NULL; - for (i = 0; i < SASIZE(s->head); i++) { - list_init(&s->head[i]); - s->head[i].span = 0; - } - s->level = 1; - s->count = 0; - s->compare = cmp; - - return s; -} - -void skiplist_destroy(struct skiplist *s) -{ - struct skipnode *node; - struct skiplist_list *pos, *end, *ptr; - - end = &s->head[0]; - pos = s->head[0].next; - for (ptr = pos->next; pos != end; pos = ptr, ptr = pos->next) { - node = list_entry(pos, struct skipnode, list[0]); - free(node); - } - free(s); -} - -static int random_level(void) -{ - int level = 1; - - while ((random() & 0xffff) < 0xffff * 0.25) - level++; - return level > SKIPLIST_MAX_LEVEL ? SKIPLIST_MAX_LEVEL : level; -} - -static struct skipnode *skipnode_new(int level, int key, int value) -{ - struct skipnode *node; - - node = malloc(sizeof(*node) + level * sizeof(struct skiplist_list)); - if (node == NULL) - return NULL; - node->key = key; - node->value = value; - return node; -} - -struct skipnode *skiplist_insert(struct skiplist *s, uintptr_t key, uintptr_t value) -{ - int level, i; - int rank[SKIPLIST_MAX_LEVEL]; - struct skipnode *node, *tmp; - struct skiplist_list *pos, *end, *update[SKIPLIST_MAX_LEVEL]; - - level = random_level(); - if (level > s->level) - s->level = level; - node = skipnode_new(level, key, value); - if (node == NULL) - return NULL; - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - rank[i] = (i == s->level - 1) ? 0 : rank[i + 1]; - for (pos = pos->next; pos != end; pos = pos->next) { - tmp = list_entry(pos, struct skipnode, list[i]); - if (s->compare(tmp->key, key) >= 0) { - end = &tmp->list[i]; - break; - } - rank[i] += tmp->list[i].span; - } - update[i] = end; - pos = end->prev; - pos--; - end--; - } - - for (i = 0; i < s->level; i++) { - if (i < level) { - list_add(&node->list[i], update[i]); - node->list[i].span = rank[0] - rank[i] + 1; - update[i]->span -= node->list[i].span - 1; - } else { - update[i]->span++; - } - } - s->count++; - - return node; -} - -static void _remove(struct skiplist *s, struct skipnode *node, int level) -{ - int i; - - for (i = 0; i < level; i++) { - list_del(&node->list[i]); - if (list_empty(&s->head[i])) - s->level--; - } - free(node); - s->count--; -} - -void skiplist_remove(struct skiplist *s, uintptr_t key) -{ - int i; - struct skipnode *node; - struct skiplist_list *pos, *end, *ptr; - - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - pos = pos->next; - for (ptr = pos->next; pos != end; pos = ptr, ptr = pos->next) { - node = list_entry(pos, struct skipnode, list[i]); - if (s->compare(node->key, key) > 0) { - end = &node->list[i]; - break; - } else if (s->compare(node->key, key) == 0) { - /* remove all nodes with same key. */ - _remove(s, node, i + 1); - } - } - pos = end->prev; - pos--; - end--; - } -} - -/* get the node key rank */ -int skiplist_key_rank(struct skiplist *s, int key) -{ - int rank = 0, i; - struct skipnode *node = NULL; - struct skiplist_list *pos, *end; - - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - for (pos = pos->next; pos != end; pos = pos->next) { - node = list_entry(pos, struct skipnode, list[i]); - if (s->compare(node->key, key) >= 0) { - end = &node->list[i]; - break; - } - rank += node->list[i].span; - } - if (node->key == key) - return rank + node->list[i].span; - pos = end->prev; - pos--; - end--; - } - - return 0; -} - -struct skipnode *skiplist_search_by_key(struct skiplist *s, uintptr_t key) -{ - int i; - struct skipnode *node = NULL; - struct skiplist_list *pos, *end; - - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - for (pos = pos->next; pos != end; pos = pos->next) { - node = list_entry(pos, struct skipnode, list[i]); - if (s->compare(node->key, key) >= 0) { - end = &node->list[i]; - break; - } - } - - if (node != NULL && s->compare(node->key, key) == 0) - return node; - pos = end->prev; - pos--; - end--; - } - - return NULL; -} - -/* search the node with specified key rank. */ -struct skipnode *skiplist_search_by_rank(struct skiplist *list, int rank) -{ - int i; - int traversed = 0; - struct skipnode *node = NULL; - struct skiplist_list *pos, *end; - - if (rank == 0 || rank > list->count) - return NULL; - - for (i = list->level - 1, pos = &list->head[i], end = pos; i >= 0; i--) { - for (pos = pos->next; pos != end; pos = pos->next) { - node = list_entry(pos, struct skipnode, list[i]); - if (traversed + node->list[i].span > rank) { - end = &node->list[i]; - break; - } - traversed += node->list[i].span; - } - if (rank == traversed) - return node; - pos = end->prev; - pos--; - end--; - } - - return NULL; -} - diff --git a/_drafts/struct/skiplist/rank.h b/_drafts/struct/skiplist/rank.h deleted file mode 100644 index 07d566a..0000000 --- a/_drafts/struct/skiplist/rank.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LIBUTILS_STRUCT_SKIPLIST_RANK_H_ -#define LIBUTILS_STRUCT_SKIPLIST_RANK_H_ 1 - -#include - -/* should be enough for 2^64 elements */ -#define SKIPLIST_MAX_LEVEL 64 -#define list_entry(ptr, type, member) \ - ((type *)((char *)(ptr) - (size_t)(&((type *)0)->member))) - -struct skiplist_list { - int span; - struct skiplist_list *prev, *next; -}; - -struct skiplist { - int (*compare)(const uintptr_t, const uintptr_t); - - int level, count; - struct skiplist_list head[SKIPLIST_MAX_LEVEL]; -}; - -struct skipnode { - uintptr_t key, value; - struct skiplist_list list[0]; -}; - -static inline int skiplist_get_count(const struct skiplist *s) -{ - return s->count; -} - -struct skiplist *skiplist_create(int (*cmp)(const uintptr_t, const uintptr_t)); -void skiplist_destroy(struct skiplist *s); - -void skiplist_remove(struct skiplist *s, uintptr_t key); -struct skipnode *skiplist_insert(struct skiplist *s, uintptr_t key, uintptr_t value); - -int skiplist_key_rank(struct skiplist *list, int key); -struct skipnode *skiplist_search_by_key(struct skiplist *s, uintptr_t key); -struct skipnode *skiplist_search_by_rank(struct skiplist *list, int rank); - -#endif diff --git a/_drafts/struct/skiplist/skiplist.c b/_drafts/struct/skiplist/skiplist.c deleted file mode 100644 index 68cbe72..0000000 --- a/_drafts/struct/skiplist/skiplist.c +++ /dev/null @@ -1,189 +0,0 @@ -#include "skiplist.h" - -#include - -#define SASIZE(arr) (int)(sizeof(arr)/sizeof((arr)[0])) - -static inline void list_init(struct skiplist_list *list) -{ - list->prev = list; - list->next = list; -} - -static inline void list_do_add(struct skiplist_list *list, struct skiplist_list *prev, struct skiplist_list *next) -{ - list->next = next; - list->prev = prev; - next->prev = list; - prev->next = list; -} - -static inline void list_do_del(struct skiplist_list *prev, struct skiplist_list *next) -{ - prev->next = next; - next->prev = prev; -} - -static inline void list_add(struct skiplist_list *list, struct skiplist_list *prev) -{ - list_do_add(list, prev, prev->next); -} - -static inline void list_del(struct skiplist_list *link) -{ - list_do_del(link->prev, link->next); - list_init(link); -} - -static inline int list_empty(struct skiplist_list *link) -{ - return link->next == link; -} - -struct skiplist *skiplist_create(int (*cmp)(const uintptr_t, const uintptr_t)) -{ - int i; - struct skiplist *s; - - if (cmp == NULL) - return NULL; - - s = (struct skiplist *)malloc(sizeof(*s)); - if (s == NULL) - return NULL; - for (i = 0; i < SASIZE(s->head); i++) - list_init(&s->head[i]); - s->level = 1; - s->count = 0; - s->compare = cmp; - return s; -} - -void skiplist_destroy(struct skiplist *s) -{ - struct skipnode *node; - struct skiplist_list *pos, *end, *ptr; - - end = &s->head[0]; - pos = s->head[0].next; - for (ptr = pos->next; pos != end; pos = ptr, ptr = pos->next) { - node = list_entry(pos, struct skipnode, list[0]); - free(node); - } - free(s); -} - -static int random_level(void) -{ - int level = 1; - - while ((random() & 0xffff) < 0xffff * 0.25) - level++; - return level > SKIPLIST_MAX_LEVEL ? SKIPLIST_MAX_LEVEL : level; -} - -static struct skipnode *skipnode_create(int level, int key, int value) -{ - struct skipnode *node; - - node = malloc(sizeof(*node) + level * sizeof(struct skiplist_list)); - if (node == NULL) - return NULL; - node->key = key; - node->value = value; - return node; -} - -struct skipnode *skiplist_insert(struct skiplist *s, uintptr_t key, uintptr_t value) -{ - int level, i; - struct skipnode *node, *tmp; - struct skiplist_list *pos, *end; - - level = random_level(); - if (level > s->level) - s->level = level; - node = skipnode_create(level, key, value); - if (node == NULL) - return NULL; - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - for (pos = pos->next; pos != end; pos = pos->next) { - tmp = list_entry(pos, struct skipnode, list[i]); - if (s->compare(tmp->key, key) >= 0) { - end = &tmp->list[i]; - break; - } - } - pos = end->prev; - if (i < level) - list_do_add(&node->list[i], pos, end); - pos--; - end--; - } - s->count++; - - return node; -} - -struct skipnode *skiplist_search(struct skiplist *s, uintptr_t key) -{ - int i; - struct skipnode *node = NULL; - struct skiplist_list *pos, *end; - - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - for (pos = pos->next; pos != end; pos = pos->next) { - node = list_entry(pos, struct skipnode, list[i]); - if (s->compare(node->key, key) >= 0) { - end = &node->list[i]; - break; - } - } - - if (node != NULL && s->compare(node->key, key) == 0) - return node; - pos = end->prev; - pos--; - end--; - } - - return NULL; -} - -static void _remove(struct skiplist *s, struct skipnode *node, int level) -{ - int i; - - for (i = 0; i < level; i++) { - list_del(&node->list[i]); - if (list_empty(&s->head[i])) - s->level--; - } - free(node); - s->count--; -} - -void skiplist_remove(struct skiplist *s, uintptr_t key) -{ - int i; - struct skipnode *node; - struct skiplist_list *pos, *end, *ptr; - - for (i = s->level - 1, pos = &s->head[i], end = pos; i >= 0; i--) { - pos = pos->next; - for (ptr = pos->next; pos != end; pos = ptr, ptr = pos->next) { - node = list_entry(pos, struct skipnode, list[i]); - if (s->compare(node->key, key) > 0) { - end = &node->list[i]; - break; - } else if (s->compare(node->key, key) == 0) { - /* we allow nodes with same key. */ - _remove(s, node, i + 1); - } - } - pos = end->prev; - pos--; - end--; - } -} - diff --git a/_drafts/struct/skiplist/skiplist.h b/_drafts/struct/skiplist/skiplist.h deleted file mode 100644 index dbcea1b..0000000 --- a/_drafts/struct/skiplist/skiplist.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef LIBUTILS_STRUCT_SKIPLIST_H_ -#define LIBUTILS_STRUCT_SKIPLIST_H_ 1 - -#include - -/* should be enough for 2^64 elements */ -#define SKIPLIST_MAX_LEVEL 64 -#define list_entry(ptr, type, member) \ - ((type *)((char *)(ptr) - (size_t)(&((type *)0)->member))) - -struct skiplist_list { - struct skiplist_list *prev, *next; -}; - -struct skiplist { - int (*compare)(const uintptr_t, const uintptr_t); - - int level, count; - struct skiplist_list head[SKIPLIST_MAX_LEVEL]; -}; - -struct skipnode { - uintptr_t key, value; - struct skiplist_list list[0]; -}; - -static inline int skiplist_get_count(const struct skiplist *s) -{ - return s->count; -} - -struct skiplist *skiplist_create(int (*cmp)(const uintptr_t, const uintptr_t)); -void skiplist_destroy(struct skiplist *s); - -void skiplist_remove(struct skiplist *s, uintptr_t key); -struct skipnode *skiplist_search(struct skiplist *s, uintptr_t key); -struct skipnode *skiplist_insert(struct skiplist *s, uintptr_t key, uintptr_t value); - -#endif diff --git a/_drafts/struct/stack.c b/_drafts/struct/stack.c deleted file mode 100644 index 2ac6ab7..0000000 --- a/_drafts/struct/stack.c +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include - -#include "stack.h" - -void stack_destroy(struct stack *s) -{ - if (s == NULL) - return; - if (s->data != NULL) - free(s->data); - free(s); -} - -struct stack *stack_create(int capacity) -{ - struct stack *s; - - if (capacity <= 0) - capacity = 64; - - s = (struct stack *)malloc(sizeof(*s)); - if (s == NULL) - return NULL; - - s->data = malloc(sizeof(uintptr_t) * capacity); - if (s->data == NULL) { - free(s); - return NULL; - } - s->top = -1; - s->capacity = capacity; - - return s; -} - -static int stack_double_capacity(struct stack *s) -{ - int cap; - uintptr_t *tmp; - - cap = s->capacity * 2; - tmp = realloc(s->data, sizeof(uintptr_t) * cap); - if (tmp == NULL) - return -ENOMEM; - s->data = tmp; - s->capacity = cap; - - return 0; -} - -int stack_push(struct stack *s, uintptr_t val) -{ - int rc; - - if (stack_is_full(s)) { - rc = stack_double_capacity(s); - if (rc < 0) - return rc; - } - s->data[++(s->top)] = val; - return 0; -} - -int stack_pop(struct stack *s, uintptr_t *ret) -{ - uintptr_t val; - - if (stack_is_empty(s)) - return -1; - val = s->data[(s->top)--]; - if (ret != NULL) - *ret = val; - return 0; -} - -int stack_top(struct stack *s, uintptr_t *ret) -{ - if (ret == NULL) - return -EINVAL; - if (stack_is_empty(s)) - return -1; - *ret = s->data[s->top]; - return 0; -} diff --git a/_drafts/struct/stack.h b/_drafts/struct/stack.h deleted file mode 100644 index e87f4b8..0000000 --- a/_drafts/struct/stack.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef LIBUTILS_STRUCT_STACK_H_ -#define LIBUTILS_STRUCT_STACK_H_ 1 - -#include - -struct stack { - int top, capacity; - uintptr_t *data; -}; - -static inline int stack_is_full(struct stack *s) -{ - return (s->top >= (s->capacity - 1)); -} - -static inline int stack_is_empty(struct stack *s) -{ - return (s->top <= -1); -} - -static inline int stack_get_size(struct stack *s) -{ - return s->top + 1; -} - -static inline int stack_get_capacity(struct stack *s) -{ - return s->capacity; -} - -void stack_destroy(struct stack *s); -struct stack *stack_create(int capacity); -int stack_push(struct stack *s, uintptr_t val); -int stack_pop(struct stack *s, uintptr_t *ret); -int stack_top(struct stack *s, uintptr_t *ret); - -#endif diff --git a/_drafts/struct/trees/avl.c b/_drafts/struct/trees/avl.c deleted file mode 100644 index 6dca2cb..0000000 --- a/_drafts/struct/trees/avl.c +++ /dev/null @@ -1,615 +0,0 @@ -#include -#include - -#include "avl.h" - -#define ISBALANCE(n) ((((n)->left == NULL) ? 0 : (n)->left->height) - (((n)->right == NULL) ? 0 : (n)->right->height)) - -#ifdef __DEBUG__ -static void verify_tree (struct avlnode *n) -{ - if (n == NULL) - return; - - verify_tree(n->left); - verify_tree(n->right); - - assert((ISBALANCE (n) >= -1) && (ISBALANCE (n) <= 1)); - assert((n->parent == NULL) || (n->parent->right == n) || (n->parent->left == n)); -} -#else -#define verify_tree(n) -#endif - -static void free_node(struct avlnode *n) -{ - if (n == NULL) - return; - - if (n->left != NULL) - free_node(n->left); - if (n->right != NULL) - free_node(n->right); - - free(n); -} - -struct avltree *avltree_create(int (*compare)(const void *, const void *)) -{ - struct avltree *t; - - if (compare == NULL) - return (NULL); - - t = (struct avltree *)malloc(sizeof(*t)); - if (t == NULL) - return NULL; - - t->root = NULL; - t->compare = compare; - t->size = 0; - - return t; -} - -void avltree_destroy(struct avltree *t) -{ - if (t == NULL) - return; - free_node(t->root); - free(t); -} - -int avltree_size(struct avltree *t) -{ - if (t == NULL) - return 0; - return t->size; -} - -static struct avlnode *search(struct avltree *t, const void *key) -{ - struct avlnode *n; - int cmp; - - n = t->root; - while (n != NULL) { - cmp = t->compare(key, n->key); - if (cmp == 0) - return n; - else if (cmp < 0) - n = n->left; - else - n = n->right; - } - - return NULL; -} - -static int calc_height(struct avlnode *n) -{ - int height_left; - int height_right; - - if (n == NULL) - return 0; - - height_left = (n->left == NULL) ? 0 : n->left->height; - height_right = (n->right == NULL) ? 0 : n->right->height; - - return (((height_left > height_right) ? height_left : height_right) + 1); -} - -/* - * (x) (y) - * / \ / \ - * (y) /\ /\ (x) - * / \ /_c\ ==> / a\ / \ - * /\ /\ /____\/\ /\ - * / a\ /_b\ /_b\ /_c\ - * /____\ - * - */ -static struct avlnode *rotate_right(struct avltree *t, struct avlnode *x) -{ - struct avlnode *p; - struct avlnode *y; - struct avlnode *b; - - assert(x != NULL); - assert(x->left != NULL); - - p = x->parent; - y = x->left; - b = y->right; - - x->left = b; - if (b != NULL) - b->parent = x; - - x->parent = y; - y->right = x; - - y->parent = p; - assert((p == NULL) || (p->left == x) || (p->right == x)); - if (p == NULL) - t->root = y; - else if (p->left == x) - p->left = y; - else - p->right = y; - - x->height = calc_height(x); - y->height = calc_height(y); - - return y; -} - -/* - * (x) (y) - * / \ / \ - * /\ (y) (x) /\ - * /_a\ / \ ==> / \ / c\ - * /\ /\ /\ /\/____\ - * /_b\ / c\ /_a\ /_b\ - * /____\ - * - */ -static struct avlnode *rotate_left(struct avltree *t, struct avlnode *x) -{ - struct avlnode *p; - struct avlnode *y; - struct avlnode *b; - - assert(x != NULL); - assert(x->right != NULL); - - p = x->parent; - y = x->right; - b = y->left; - - x->right = b; - if (b != NULL) - b->parent = x; - - x->parent = y; - y->left = x; - - y->parent = p; - assert((p == NULL) || (p->left == x) || (p->right == x)); - if (p == NULL) - t->root = y; - else if (p->left == x) - p->left = y; - else - p->right = y; - - x->height = calc_height(x); - y->height = calc_height(y); - - return y; -} - -static struct avlnode *rotate_left_right(struct avltree *t, struct avlnode *x) -{ - rotate_left(t, x->left); - return rotate_right(t, x); -} - -static struct avlnode *rotate_right_left(struct avltree *t, struct avlnode *x) -{ - rotate_right(t, x->right); - return rotate_left(t, x); -} - -static void rebalance(struct avltree *t, struct avlnode *n) -{ - int b_top; - int b_bottom; - - while (n != NULL) { - b_top = ISBALANCE(n); - assert((b_top >= -2) && (b_top <= 2)); - - if (b_top == -2) { // right is higher - assert(n->right != NULL); - b_bottom = ISBALANCE(n->right); - assert((b_bottom >= -1) && (b_bottom <= 1)); - if (b_bottom == 1) - n = rotate_right_left(t, n); - else - n = rotate_left(t, n); - } else if (b_top == 2) { // left is higher - assert(n->left != NULL); - b_bottom = ISBALANCE(n->left); - assert((b_bottom >= -1) && (b_bottom <= 1)); - if (b_bottom == -1) - n = rotate_left_right(t, n); - else - n = rotate_right(t, n); - } else { - int height = calc_height(n); - if (height == n->height) - break; - n->height = height; - } - assert(n->height == calc_height(n)); - - n = n->parent; - } /* while (n != NULL) */ -} - -int avltree_insert(struct avltree *t, void *key, void *value) -{ - struct avlnode *newnode; - struct avlnode *nptr; - int cmp; - - newnode = (struct avlnode *)malloc(sizeof(*newnode)); - if (newnode == NULL) - return -1; - - newnode->key = key; - newnode->value = value; - newnode->height = 1; - newnode->left = NULL; - newnode->right = NULL; - - if (t->root == NULL) { - newnode->parent = NULL; - t->root = newnode; - t->size = 1; - return 0; - } - - nptr = t->root; - while (1) { - cmp = t->compare(nptr->key, newnode->key); - if (cmp == 0) { - free_node(newnode); - return 1; - } else if (cmp < 0) { /* nptr < newnode */ - if (nptr->right == NULL) { - nptr->right = newnode; - newnode->parent = nptr; - rebalance(t, nptr); - break; - } else { - nptr = nptr->right; - } - } else { /* nptr > newnode */ - if (nptr->left == NULL) { - nptr->left = newnode; - newnode->parent = nptr; - rebalance(t, nptr); - break; - } else { - nptr = nptr->left; - } - } - } - - verify_tree(t->root); - ++t->size; - return 0; -} - -int avltree_get(struct avltree *t, const void *key, void **value) -{ - struct avlnode *n; - assert(t != NULL); - - n = search(t, key); - if (n == NULL) - return -1; - - if (value != NULL) - *value = n->value; - - return 0; -} - -static struct avlnode *avltree_node_next(struct avlnode *n) -{ - struct avlnode *r; /* return node */ - - if (n == NULL) - return NULL; - - /* - * If we can't descent any further, we have to backtrack to the first - * parent that's bigger than we, i. e. who's _left_ child we are. - */ - if (n->right == NULL) { - r = n->parent; - while ((r != NULL) && (r->parent != NULL)) { - if (r->left == n) - break; - n = r; - r = n->parent; - } - - /* - * n->right == NULL && r == NULL => t is root and has no next - * r->left != n => r->right = n => r->parent == NULL - */ - if ((r == NULL) || (r->left != n)) { - assert((r == NULL) || (r->parent == NULL)); - return NULL; - } else { - assert(r->left == n); - return r; - } - } else { - r = n->right; - while (r->left != NULL) - r = r->left; - } - - return r; -} - -int avltree_pick(struct avltree *t, void **key, void **value) -{ - struct avlnode *n, *p; - - if (t == NULL || t->root == NULL) - return -1; - - n = t->root; - while ((n->left != NULL) || (n->right != NULL)) { - if (n->left == NULL) { - n = n->right; - continue; - } else if (n->right == NULL) { - n = n->left; - continue; - } - - if (n->left->height > n->right->height) - n = n->left; - else - n = n->right; - } - - p = n->parent; - if (p == NULL) - t->root = NULL; - else if (p->left == n) - p->left = NULL; - else - p->right = NULL; - - if (key) - *key = n->key; - if (value) - *value = n->value; - - free_node(n); - --t->size; - rebalance(t, p); - - return 0; -} - -static struct avlnode *avltree_node_prev(struct avlnode *n) -{ - struct avlnode *r; /* return node */ - - if (n == NULL) - return NULL; - - /* - * If we can't descent any further, we have to backtrack to the first - * parent that's smaller than we, i. e. who's _right_ child we are. - */ - if (n->left == NULL) { - r = n->parent; - while ((r != NULL) && (r->parent != NULL)) { - if (r->right == n) - break; - n = r; - r = n->parent; - } - - /* - * n->left == NULL && r == NULL => t is root and has no next - * r->right != n => r->left = n => r->parent == NULL - */ - if ((r == NULL) || (r->right != n)) { - assert((r == NULL) || (r->parent == NULL)); - return NULL; - } else { - assert(r->right == n); - return r; - } - } else { - r = n->left; - while (r->right != NULL) - r = r->right; - } - - return r; -} - -static int _remove(struct avltree *t, struct avlnode *n) -{ - assert((t != NULL) && (n != NULL)); - - if ((n->left != NULL) && (n->right != NULL)) { - struct avlnode *r; /* replacement node */ - if (ISBALANCE(n) > 0) { /* left subtree is higher */ - assert(n->left != NULL); - r = avltree_node_prev(n); - - } else { /* right subtree is higher */ - assert(n->right != NULL); - r = avltree_node_next(n); - } - - assert((r->left == NULL) || (r->right == NULL)); - - /* copy content */ - n->key = r->key; - n->value = r->value; - - n = r; - } - - assert((n->left == NULL) || (n->right == NULL)); - - if ((n->left == NULL) && (n->right == NULL)) { - /* Deleting a leave is easy */ - if (n->parent == NULL) { - assert(t->root == n); - t->root = NULL; - } else { - assert((n->parent->left == n) || (n->parent->right == n)); - if (n->parent->left == n) - n->parent->left = NULL; - else - n->parent->right = NULL; - - rebalance(t, n->parent); - } - - free_node(n); - } else if (n->left == NULL) { - assert(ISBALANCE(n) == -1); - assert((n->parent == NULL) || (n->parent->left == n) || (n->parent->right == n)); - if (n->parent == NULL) { - assert(t->root == n); - t->root = n->right; - } else if (n->parent->left == n) { - n->parent->left = n->right; - } else { - n->parent->right = n->right; - } - n->right->parent = n->parent; - - if (n->parent != NULL) - rebalance(t, n->parent); - - n->right = NULL; - free_node(n); - } else if (n->right == NULL) { - assert(ISBALANCE(n) == 1); - assert((n->parent == NULL) || (n->parent->left == n) || (n->parent->right == n)); - if (n->parent == NULL) { - assert(t->root == n); - t->root = n->left; - } else if (n->parent->left == n) { - n->parent->left = n->left; - } else { - n->parent->right = n->left; - } - n->left->parent = n->parent; - - if (n->parent != NULL) - rebalance(t, n->parent); - - n->left = NULL; - free_node(n); - } else { - assert(0); - } - - return 0; -} - -int avltree_remove(struct avltree *t, const void *key, void **rkey, void **rvalue) -{ - struct avlnode *n; - int status; - - assert(t != NULL); - - n = search(t, key); - if (n == NULL) - return -1; - - if (rkey != NULL) - *rkey = n->key; - if (rvalue != NULL) - *rvalue = n->value; - - status = _remove(t, n); - verify_tree(t->root); - --t->size; - return status; -} - -struct avliter *avltree_get_iterator(struct avltree *t) -{ - struct avliter *iter; - - if (t == NULL) - return NULL; - - iter = calloc(1, sizeof(*iter)); - if (iter == NULL) - return NULL; - iter->tree = t; - - return iter; -} - -int avltree_iterator_next(struct avliter *iter, void **key, void **value) -{ - struct avlnode *n; - - if (iter == NULL) - return -1; - - if (iter->node == NULL) { - for (n = iter->tree->root; n != NULL; n = n->left) - if (n->left == NULL) - break; - iter->node = n; - } else { - n = avltree_node_next(iter->node); - } - - if (n == NULL) - return -1; - - iter->node = n; - if (key) - *key = n->key; - if (value) - *value = n->value; - - return 0; -} - -int avltree_iterator_prev(struct avliter *iter, void **key, void **value) -{ - struct avlnode *n; - - if ((iter == NULL) || (key == NULL) || (value == NULL)) - return -1; - - if (iter->node == NULL) { - for (n = iter->tree->root; n != NULL; n = n->left) - if (n->right == NULL) - break; - iter->node = n; - } else { - n = avltree_node_prev(iter->node); - } - - if (n == NULL) - return -1; - - iter->node = n; - *key = n->key; - *value = n->value; - - return 0; -} - -void avltree_iterator_destroy(struct avliter *iter) -{ - if (iter) - free(iter); -} diff --git a/_drafts/struct/trees/avl.h b/_drafts/struct/trees/avl.h deleted file mode 100644 index 1551a29..0000000 --- a/_drafts/struct/trees/avl.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef LIBUTILS_STRUCT_TREES_AVL_H_ -#define LIBUTILS_STRUCT_TREES_AVL_H_ 1 - -/* private data types */ -struct avlnode { - void *key; - void *value; - - struct avlnode *left; - struct avlnode *right; - struct avlnode *parent; - int height; -}; - -struct avltree { - struct avlnode *root; - int (*compare)(const void *, const void *); - int size; -}; - -struct avliter { - struct avltree *tree; - struct avlnode *node; -}; -typedef int (*avltree_compare)(const void *, const void *); - -/* - * Allocates a new AVL-tree. - * - * compare The function-pointer is used to compare two keys. It has to - * return less than zero if its first argument is smaller then - * the second argument, more than zero if the first argument - * is bigger than the second argument and zero if they are equal. - * If your keys are char-pointers, you can use the `strcmp' - * function from the libc here. - */ -struct avltree *avltree_create(int (*compare)(const void *, const void *)); - -/* - * Deallocates an AVL-tree. - * NOTE: Stored Key-Value pointer are lost, but not freed. - */ -void avltree_destroy(struct avltree *tree); - -/* - * Stores the key-value-pair in the AVL-tree. - * - * NOTE: Key used to store the value under. This is used to get back to - * the value again. The pointer is stored in an internal structure - * and _not_ copied. So the memory pointed to may _not_ be freed - * before this entry is removed. You can use the `rkey' argument - * to `avltree_remove' to get the original pointer back and free it. - * - * RETURN VALUE - * Zero upon success, non-zero otherwise. It's less than zero if an error - * occurred or greater than zero if the key is already stored in the tree. - */ -int avltree_insert(struct avltree *tree, void *key, void *value); - -/* - * Removes a key-value-pair from the tree t. The stored key and value may be - * returned in 'rkey' and 'rvalue'. - * - * NOTE: Pointer to a pointer in which to store the key. May be NULL. - * Since the `key' pointer is not copied when creating an entry, - * the pointer may not be available anymore from outside the tree. - * You can use this argument to get the actual pointer back and - * free the memory pointed to by it. - * - * RETURN VALUE - * Zero upon success or non-zero if the key isn't found in the tree. - */ -int avltree_remove(struct avltree *tree, const void *key, void **rkey, void **rvalue); - -/* - * Retrieve the 'value' belonging to 'key'. - * - * RETURN VALUE - * Zero upon success or non-zero if the key isn't found in the tree. - */ -int avltree_get(struct avltree *tree, const void *key, void **value); - -/* - * Remove a (pseudo-)random element from the tree and return its 'key' and - * 'value'. Entries are not returned in any particular order. This function - * is intended for cache-flushes that don't care about the order but simply - * want to remove all elements, one at a time. - * - * RETURN VALUE - * Zero upon success or non-zero if the tree is empty or key or value is - * NULL. - */ -int avltree_pick(struct avltree *tree, void **key, void **value); - -int avltree_size(struct avltree *tree); - -struct avliter *avltree_get_iterator(struct avltree *tree); -void avltree_iterator_destroy(struct avliter *iter); -int avltree_iterator_next(struct avliter *iter, void **key, void **value); -int avltree_iterator_prev(struct avliter *iter, void **key, void **value); - -#endif diff --git a/_drafts/struct/trees/bst.c b/_drafts/struct/trees/bst.c deleted file mode 100644 index 3e0c970..0000000 --- a/_drafts/struct/trees/bst.c +++ /dev/null @@ -1,296 +0,0 @@ -#include - -#include "bst.h" - -static inline void set_left(struct bstree_node *l, struct bstree_node *n) -{ - n->left = l; - n->left_is_thread = 0; -} - -static inline void set_right(struct bstree_node *r, struct bstree_node *n) -{ - n->right = r; - n->right_is_thread = 0; -} - -static inline void set_prev(struct bstree_node *t, struct bstree_node *n) -{ - n->left = t; - n->left_is_thread = 1; -} - -static inline void set_next(struct bstree_node *t, struct bstree_node *n) -{ - n->right = t; - n->right_is_thread = 1; -} - -static inline struct bstree_node *get_left(const struct bstree_node *n) -{ - if (n->left_is_thread) - return NULL; - return n->left; -} - -static inline struct bstree_node *get_right(const struct bstree_node *n) -{ - if (n->right_is_thread) - return NULL; - return n->right; -} - -static inline struct bstree_node *get_prev(const struct bstree_node *n) -{ - if (!n->left_is_thread) - return NULL; - return n->left; -} - -static inline struct bstree_node *get_next(const struct bstree_node *n) -{ - if (!n->right_is_thread) - return NULL; - return n->right; -} - -static inline struct bstree_node *get_first(struct bstree_node *node) -{ - struct bstree_node *left; - while ((left = get_left(node))) - node = left; - return node; -} - -static inline struct bstree_node *get_last(struct bstree_node *node) -{ - struct bstree_node *right; - while ((right = get_right(node))) - node = right; - return node; -} - -struct bstree_node *bstree_first(const struct bstree *tree) -{ - if (tree->root) - return tree->first; - return NULL; -} - -struct bstree_node *bstree_last(const struct bstree *tree) -{ - if (tree->root) - return tree->last; - return NULL; -} - -struct bstree_node *bstree_next(const struct bstree_node *node) -{ - struct bstree_node *right = get_right(node); - if (right) - return get_first(right); - return get_next(node); -} - -struct bstree_node *bstree_prev(const struct bstree_node *node) -{ - struct bstree_node *left = get_left(node); - if (left) - return get_last(left); - return get_prev(node); -} - -int bstree_init(struct bstree *tree, bstree_cmp_t cmp) -{ - if (tree == NULL) - return 0; - - tree->size = 0; - tree->compare = cmp; - return 0; -} - -static struct bstree_node *do_lookup(const struct bstree *tree, - const struct bstree_node *key, struct bstree_node **pparent, int *is_left) -{ - int rc; - struct bstree_node *node; - - *is_left = 0; - *pparent = NULL; - node = tree->root; - while (node) { - rc = tree->compare(node, key); - if (rc == 0) - return node; - *pparent = node; - *is_left = rc > 0; - if (rc > 0) - node = get_left(node); - else - node = get_right(node); - } - - return NULL; -} - -static inline void node_do_init(struct bstree_node *node) -{ - node->left = NULL; - node->right = NULL; - node->left_is_thread = 0; - node->right_is_thread = 0; -} - -struct bstree_node *bstree_insert(struct bstree *tree, struct bstree_node *node) -{ - int is_left; - struct bstree_node *key, *parent; - - key = do_lookup(tree, node, &parent, &is_left); - if (key != NULL) - return key; - - if (parent == NULL) { /* tree is empty */ - node_do_init(node); - tree->root = tree->first = tree->last = node; - return NULL; - } - - if (is_left) { - if (parent == tree->first) - tree->first = node; - set_prev(get_prev(parent), node); - set_next(parent, node); - set_left(node, parent); - } else { - if (parent == tree->last) - tree->last = node; - set_prev(parent, node); - set_next(get_next(parent), node); - set_right(node, parent); - } - - return NULL; -} - -struct bstree_node *bstree_get(const struct bstree *tree, const struct bstree_node *key) -{ - int is_left; - struct bstree_node *parent; - - return do_lookup(tree, key, &parent, &is_left); -} - -static void set_child(struct bstree_node *child, struct bstree_node *node, int left) -{ - if (left) - set_left(child, node); - else - set_right(child, node); -} - -void bstree_remove(struct bstree_node *node, struct bstree *tree) -{ -#if 0 - int is_left; - struct bstree_node *left, *right, *next; - struct bstree_node *parent, fake_parent; - - do_lookup(tree, node, &parent, &is_left); - if (parent == NULL) { - node_do_init(&fake_parent); - parent = &fake_parent; - is_left = 0; - } - - left = get_left(node); - right = get_right(node); - if (left == NULL && right == NULL) { - if (is_left) - set_prev(get_prev(node), parent); - else - set_next(get_next(node), parent); - next = parent; - - goto update_first_last; - } - - if (left == NULL) { - next = get_first(right); - set_prev(get_prev(node), next); - set_child(right, parent, is_left); - goto update_first_last; - } - - if (right == NULL) { - next = get_last(left); - set_next(get_next(node), next); - set_child(left, parent, is_left); - goto update_first_last; - } - - next = get_first(right); - if (next != right) { - /* 'm' is the parent of 'next' */ - struct bstree_node *m = get_next(get_last(next)); - - if (get_right(next)) - set_left(get_right(next), m); - else - set_prev(next, m); - - set_right(right, next); - } - set_child(next, parent, is_left); - set_left(left, next); - set_next(next, get_last(left)); -out: - if (parent == &fake_parent) - tree->root = get_right(parent); - return; - -update_first_last: - if (node == tree->first) - tree->first = next; - if (node == tree->last) - tree->last = next; - goto out; -#endif -} - -#if 0 -void bstree_replace(struct bstree_node *old, struct bstree_node *new, - struct bstree *tree) -{ - struct bstree_node *parent, *next, *prev; - int is_left; - - if (tree->first == old) - tree->first = new; - if (tree->last == old) - tree->last = new; - if (tree->root == old) - tree->root = new; - else { - /* - * Update the parent: do a full lookup to retrieve - * it. There's probably a better way but it's bst... - */ - do_lookup(old, tree, &parent, &is_left); - if (parent) - set_child(new, parent, is_left); - } - - /* update the thread links */ - prev = bstree_prev(old); - if (prev && get_next(prev) == old) - set_next(new, prev); - next = bstree_next(old); - if (next && get_prev(next) == old) - set_prev(new, next); - - *new = *old; -} -#endif - diff --git a/_drafts/struct/trees/bst.h b/_drafts/struct/trees/bst.h deleted file mode 100644 index 2c49173..0000000 --- a/_drafts/struct/trees/bst.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef LIBUTILS_STRUCT_TREES_BST_H_ -#define LIBUTILS_STRUCT_TREES_BST_H_ 1 - -#include -#include - - -#ifndef offsetof -#define offsetof(type, member) ((size_t)&((type *)0)->member) -#endif - -#define bstree_container_of(node, type, member) \ - ((type *)((char *)(node) - offsetof(type, member))) - -struct bstree_node { - struct bstree_node *left, *right; - unsigned left_is_thread:1; - unsigned right_is_thread:1; -}; - -typedef int (*bstree_cmp_t)(const struct bstree_node *, const struct bstree_node *); - -struct bstree { - struct bstree_node *root, *first, *last; - bstree_cmp_t compare; - int size; -}; - -int bstree_init(struct bstree *tree, bstree_cmp_t cmp); -struct bstree_node *bstree_insert(struct bstree *tree, struct bstree_node *node); -struct bstree_node *bstree_get(const struct bstree *tree, const struct bstree_node *key); - -#endif diff --git a/_drafts/tcpcli.c b/_drafts/tcpcli.c deleted file mode 100644 index f289ba8..0000000 --- a/_drafts/tcpcli.c +++ /dev/null @@ -1,515 +0,0 @@ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#define LOG_TIME 1 - -#include "log_simple.c" - -#include "libev/ev.h" - -#define MOD "(sock) " - -#define THD_NUMS 1 -#define CLI_CONN_TO 10 /* connect timeout */ - -struct proto_hdr { - uint8_t magic[4]; - uint16_t type; - uint32_t length; -} __attribute__((packed)); -#define SVR_P_HDR_MIN 10 -#define SVR_P_MAGIC_0 'A' -#define SVR_P_MAGIC_1 'G' -#define SVR_P_MAGIC_2 'T' -#define SVR_P_MAGIC_3 '1' - -enum { - S_INIT, - S_START, - S_ESTB, - S_FAILED, - S_MAX -}; -const char *CLIS[S_MAX] = { - "init", - "start", - "establish", - "failed", -}; - -#define CLI_RERND_CNT 20 -struct sockinfo { - int naddr, max, next, count; - struct sockaddr *addrs; -}; -struct clictx { - char *oaddr; /* orignal address */ - struct sockinfo socks; - - int state; - int retry_secs; - - struct ev_loop *loop; - - struct ev_io wsock; - struct ev_timer wretry; - struct ev_timer wtimeout; -}; - -static ssize_t sock_write_fully(int fd, const char *buff, ssize_t nbytes) -{ - int rc; - ssize_t nwritten; - - for (nwritten = 0; nwritten < nbytes;) { - rc = write(fd, buff + nwritten, nbytes - nwritten); - if (rc < 0) { - if (errno == EINTR) { - continue; - } else if (errno == EAGAIN) { - usleep(10000); - continue; - } - - log_error(MOD "write to #%d failed, rc %d, %d:%s.", - fd, rc, errno, strerror(errno)); - return -1; - } else if (rc == 0) { - break; - } - nwritten += rc; - } - - return nwritten; -} -/* NOTE: this will block while parse the address. */ -static int sock_do_parse_address(struct sockinfo *socks, char *addr) -{ - void *tmp; - int rc, nsize; - char *port = NULL; - struct sockaddr_in *ipv4; - char ipaddr[INET_ADDRSTRLEN]; - struct addrinfo hints, *res, *curr; - - port = strchr(addr, ':'); - if (port != NULL) { - *port = 0; - port++; - } - - memset_s(&hints, sizeof(hints), 0, sizeof(hints)); - hints.ai_family = AF_INET; /* AF_UNSPEC AF_INET(IPv4) AF_INET6(IPv6) */ - hints.ai_socktype = SOCK_STREAM; /* TCP stream sockets */ - - rc = getaddrinfo(addr, port, &hints, &res); - if (rc) { - log_error(MOD "getaddrinfo for '%s' failed, %d:%s.", - addr, rc, gai_strerror(rc)); - return -1; - } - - for (curr = res; curr != NULL; curr = curr->ai_next) { - if (curr->ai_family != AF_INET) { - log_error(MOD "got invalid family %d for '%s'.", - curr->ai_family, addr); - continue; - } - - if (socks->naddr + 1 > socks->max) { - assert(socks->max == 0); - assert(socks->naddr == 0); - if (socks->max == 0) - nsize = 8; /* min */ - else - nsize = socks->max * 2; - - tmp = realloc(socks->addrs, nsize * sizeof(*(socks->addrs))); /* CodeMars */ - if (tmp == NULL) { - log_error(MOD "parse address '%s' failed, out of memory.", addr); - freeaddrinfo(res); - return -1; - } - socks->max = nsize; - socks->addrs = (struct sockaddr *)tmp; - } - ipv4 = (struct sockaddr_in *)curr->ai_addr; - memcpy_s(&socks->addrs[socks->naddr++], sizeof(struct sockaddr), - ipv4, sizeof(struct sockaddr)); - inet_ntop(ipv4->sin_family, &ipv4->sin_addr, ipaddr, sizeof(ipaddr)); - log_info(MOD "got IP '%s:%d', current %d items.", ipaddr, - ntohs(ipv4->sin_port), socks->naddr); - } - freeaddrinfo(res); - - return 0; -} -/* addrs: 'svr.foobar.com:8090,127.1:10086' */ -static int sock_parse_address(struct clictx *ctx, const char *addrstr) -{ - int len; - struct sockinfo socks; - char *tmp, *ptr, *saveptr, *addr; - - assert(ctx && addrstr); - - if (addrstr[0] == 0) - return -1; - memset(&socks, 0, sizeof(socks)); - len = strlen(addrstr); - - tmp = (char *)malloc((len + 1) * 2); - if (tmp == NULL) { - log_error(MOD "parse address failed, out of memory."); - return -1; - } - ptr = tmp + len + 1; - strncpy(tmp, addrstr, len); - strncpy(ptr, addrstr, len); - - saveptr = NULL; - while ((addr = strtok_s(ptr, ",", &saveptr)) != NULL) { - ptr = NULL; - log_info(MOD "get address '%s'.", addr); - - if (sock_do_parse_address(&socks, addr) < 0) { - if (socks.addrs) - free(socks.addrs); - free(tmp); - log_info(MOD "address '%s' parse failed, use old config.", addrstr); - return -1; - } - } - - log_info(MOD "update address info from %d items to %d items.", - ctx->socks.naddr, socks.naddr); - if (ctx->socks.addrs) - free(ctx->socks.addrs); - memcpy(&ctx->socks, &socks, sizeof(socks)); - - if (ctx->oaddr) - free(ctx->oaddr); - ctx->oaddr = tmp; - - return 0; -} -static struct sockaddr *sock_choose_server(struct sockinfo *sock) -{ - if (sock->naddr == 1) /* fast path */ - return &sock->addrs[0]; - - if (sock->count++ > CLI_RERND_CNT) { - sock->count = 0; - sock->next = -1; - } - - if (sock->next < 0) { - sock->next = rand() % sock->naddr; - log_debug(MOD "choose a random server, start with %d, current %d.", - sock->next, sock->naddr); - } - - if (sock->next + 1 >= sock->naddr) - sock->next = 0; - else - sock->next++; - log_debug(MOD "choose %dth server.", sock->next); - return &sock->addrs[sock->next]; -} - -static void sock_context_reset(EV_P_ struct clictx *ctx) -{ - assert(ctx); - assert(ev_is_active(&ctx->wretry) == 0); - - if (ev_is_active(&ctx->wsock)) - ev_io_stop(EV_A_ &ctx->wsock); - if (ctx->wsock.fd > 0) { - //close(ctx->wsock.fd); - shutdown(ctx->wsock.fd, SHUT_RDWR); - ctx->wsock.fd = -1; - } -} - -void sock_retry_later(EV_P_ struct clictx *ctx) -{ - if (ev_is_active(&ctx->wretry)) { - log_info(MOD "retry is in progress, ignore this now."); - return; - } - sock_context_reset(EV_A_ ctx); - assert(ctx->wsock.fd < 0); - - ctx->state = S_FAILED; - ev_timer_set(&ctx->wretry, ctx->retry_secs, 0.); - ev_timer_start(EV_A_ &ctx->wretry); - log_info(MOD "retry in %d seconds later.", ctx->retry_secs); -} -void sock_read_hook(EV_P_ struct ev_io *w, int revents) -{ - (void) revents; - int rc; - char buff[256]; - struct clictx *ctx; - - ctx = (struct clictx *)w->data; - - rc = read(w->fd, buff, sizeof(buff)); - if (rc == 0) { - log_error(MOD "read 0 byte from socket, peer maybe close."); - sock_retry_later(EV_A_ ctx); - return; - } else if (rc < 0) { - if (errno == EINTR) { - return; - } else if (errno == EAGAIN) { - usleep(10000); - return; - } - - log_error(MOD "read from socket failed, %d:%s.", errno, strerror(errno)); - sock_retry_later(EV_A_ ctx); - return; - } - log_debug(MOD "receive %d bytes from socket: %.*s", rc, rc, buff); -} - -static void sock_handle_connected(EV_P_ struct clictx *ctx, int sockfd) -{ - assert(ev_is_active(&ctx->wsock) == 0); - - ctx->state = S_ESTB; - log_info(MOD "connected success, sockfd %d.", sockfd); - - ev_io_init(&ctx->wsock, sock_read_hook, sockfd, EV_READ); - ctx->wsock.data = ctx; - ev_io_start(EV_A_ &ctx->wsock); -} -void sock_connect_hook(EV_P_ struct ev_io *w, int revents) -{ - (void) revents; - int rc, sockfd, serror; - socklen_t err_len; - struct clictx *ctx; - ctx = (struct clictx *)w->data; - - if (ev_is_active(&ctx->wsock)) - ev_io_stop(EV_A_ &ctx->wsock); - if (ev_is_active(&ctx->wtimeout)) - ev_timer_stop(EV_A_ &ctx->wtimeout); - sockfd = w->fd; - log_info(MOD "connected async, current state '%s'.", CLIS[ctx->state]); - - serror = 0; - err_len = sizeof(serror); - rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&serror, &err_len); - if (rc < 0) { - log_error(MOD "get socket option failed, %d:%s.", - errno, strerror(errno)); - sock_retry_later(EV_A_ ctx); - return; - } - - if (serror != 0) { - log_error(MOD "connect failed, %d:%s.", serror, strerror(serror)); - sock_retry_later(EV_A_ ctx); - return; - } - - sock_handle_connected(EV_A_ ctx, sockfd); -} -/* try to connect to a server, if failed start a retry timer. */ -static int sock_connect_to(EV_P_ struct clictx *ctx, struct sockaddr_in *addr) -{ - int sockfd, rc; - struct linger linger; - char ipaddr[INET_ADDRSTRLEN]; - - inet_ntop(addr->sin_family, &addr->sin_addr, ipaddr, sizeof(ipaddr)); - log_info(MOD "connect to '%s:%d'.", ipaddr, ntohs(addr->sin_port)); - - sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0); - if (sockfd < 0) { - log_error(MOD "create socket failed, %d:%s.", errno, strerror(errno)); - return -1; - } - assert(ev_is_active(&ctx->wsock) == 0); - - linger.l_onoff = 1; - linger.l_linger = 0; - rc = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); - if (rc < 0) { - log_error(MOD "set socket LINGER option failed, %d:%s.", - errno, strerror(errno)); - close(sockfd); - return -1; - } - - rc = connect(sockfd, (struct sockaddr *)addr, sizeof(*addr)); - if (rc < 0) { - if (errno == EINPROGRESS) { - ctx->state = S_START; - ev_timer_set(&ctx->wtimeout, CLI_CONN_TO, 0.); - ev_timer_start(EV_A_ &ctx->wtimeout); - - ev_io_init(&ctx->wsock, sock_connect_hook, sockfd, EV_WRITE); - ctx->wsock.data = ctx; - ev_io_start(EV_A_ &ctx->wsock); - - log_info(MOD "connect to server async, timeout %ds.", CLI_CONN_TO); - return 0; - } - - log_error(MOD "connect to server failed, %d:%s.", errno, strerror(errno)); - close(sockfd); - return -1; - } - - sock_handle_connected(EV_A_ ctx, sockfd); - - return 0; -} -void sock_retry_hook(EV_P_ ev_timer *w, int revents) -{ - (void) revents; - struct clictx *ctx; - struct sockaddr *svr; - - ctx = (struct clictx *)w->data; - assert(ev_is_active(&ctx->wsock) == 0); - assert(ev_is_active(&ctx->wtimeout) == 0); - assert(ctx->state == S_INIT || ctx->state == S_FAILED); - - log_debug(MOD "retry timer timeout, try to connect to server."); - if (ev_is_active(&ctx->wtimeout)) - ev_timer_stop(EV_A_ &ctx->wtimeout); - - svr = sock_choose_server(&ctx->socks); - assert(svr != NULL); - - if (sock_connect_to(EV_A_ ctx, (struct sockaddr_in *)svr) < 0) { - sock_retry_later(EV_A_ ctx); - return; - } -} - -void sock_timeout_hook(EV_P_ ev_timer *w, int revents) -{ - (void) revents; - struct clictx *ctx; - - ctx = (struct clictx *)w->data; - log_error(MOD "timeout, current state '%s'.", CLIS[ctx->state]); - sock_retry_later(EV_A_ ctx); -} - - -void sock_thd_retry_hook(EV_P_ ev_timer *w, int revents) -{ - (void) revents; - ssize_t rc; - char buff[1024]; - struct clictx *ctx; - struct proto_hdr hdr; - unsigned int cnt = 0; // UINT_MAX 4294967295U - - hdr.type = 0; - hdr.magic[0] = SVR_P_MAGIC_0; - hdr.magic[1] = SVR_P_MAGIC_1; - hdr.magic[2] = SVR_P_MAGIC_2; - hdr.magic[3] = SVR_P_MAGIC_3; - hdr.length = 20; - - memcpy(buff, &hdr, SVR_P_HDR_MIN); - - log_info(MOD "start to send data."); - ctx = (struct clictx *)w->data; - while (1) { - cnt++; - snprintf(buff + SVR_P_HDR_MIN, 1024, "%010d%010d", cnt, cnt); - - rc = sock_write_fully(ctx->wsock.fd, buff, hdr.length + SVR_P_HDR_MIN); - if (rc < 0) { - log_info(MOD "current count %d.", cnt); - ev_timer_set(w, 3., 0.); - ev_timer_start(EV_A_ w); - return; - } - } -} - -void *thd_worker(void *arg) -{ - struct clictx *ctx; - struct ev_timer wtimer; - struct ev_loop *loop = ev_loop_new(0); - - if (loop == NULL) { - log_error(MOD "create libev loop failed, out of memory."); - return NULL; - } - - ctx = (struct clictx *)arg; - log_info(MOD "worker thread started."); - - ev_init(&wtimer, sock_thd_retry_hook); - wtimer.data = ctx; - ev_timer_set(&wtimer, 5., 0.); - ev_timer_start(EV_A_ &wtimer); - - ev_run(EV_A_ 0); - - return NULL; -} - -int main(void) -{ - int i, rc; - pthread_t thds[THD_NUMS]; - struct clictx ctxs; - struct ev_loop *loop = ev_default_loop(0); - - signal(SIGPIPE, SIG_IGN); - memset(&ctxs, 0, sizeof(ctxs)); - if (sock_parse_address(&ctxs, "192.168.170.220:10087") < 0) { - log_error(MOD "parse address failed."); - return -1; - } - ctxs.wsock.fd = -1; - - ctxs.retry_secs = 5; - ev_init(&ctxs.wretry, sock_retry_hook); - ctxs.wretry.data = &ctxs; - - ev_init(&ctxs.wtimeout, sock_timeout_hook); - ctxs.wtimeout.data = &ctxs; - - ev_timer_set(&ctxs.wretry, 3., 0.); - ev_timer_start(EV_A_ &ctxs.wretry); - - for (i = 0; i < THD_NUMS; i++) { - rc = pthread_create(&thds[i], NULL, thd_worker, &ctxs); - if (rc != 0) { - log_error(MOD "create thread failed, %d:%s.", - rc, strerror(rc)); - exit(1); - } - } - - rc = ev_run(EV_A_ 0); - log_info(MOD "still remain %d watchers.", rc); - - for (i = 0; i < THD_NUMS; i++) - pthread_join(thds[i], NULL); - - return 0; -} diff --git a/_drafts/tcpsvr.c b/_drafts/tcpsvr.c deleted file mode 100644 index dab7d35..0000000 --- a/_drafts/tcpsvr.c +++ /dev/null @@ -1,271 +0,0 @@ -#include -#include -#include - -#include -#include - -#define LOG_TIME 1 - -#include "log_simple.c" - -#include "libev/ev.h" - -#define MOD "(sock) " - -#define SVR_PORT 10087 -#define SVR_IDLE_TO 3. /* idle timeout */ -#define SVR_BUFF_SIZE 4096 - -struct proto_hdr { - uint8_t magic[4]; - uint16_t type; - uint32_t length; -} __attribute__((packed)); -#define SVR_P_HDR_MIN 10 -#define SVR_P_MAGIC_0 'A' -#define SVR_P_MAGIC_1 'G' -#define SVR_P_MAGIC_2 'T' -#define SVR_P_MAGIC_3 '1' - -struct svr_ctx { - int len; - char *buff, *tail, *end; - - struct sockaddr_in addr; - - struct ev_io wsock; - struct ev_timer wtimeout; -}; - -static void sock_context_destroy(EV_P_ struct svr_ctx *ctx) -{ - if (ctx == NULL) - return; - - if (ev_is_active(&ctx->wsock)) { - assert(ctx->wsock.fd > 0); - ev_io_stop(EV_A_ &ctx->wsock); - close(ctx->wsock.fd); - ctx->wsock.fd = -1; - } - - if (ev_is_active(&ctx->wtimeout)) - ev_timer_stop(EV_A_ &ctx->wtimeout); - - if (ctx->buff != NULL) - free(ctx->buff); - free(ctx); -} -static int sock_valid_check(struct proto_hdr *hdr) -{ - if (hdr->magic[0] != SVR_P_MAGIC_0 || hdr->magic[1] != SVR_P_MAGIC_1 || - hdr->magic[2] != SVR_P_MAGIC_2 || hdr->magic[3] != SVR_P_MAGIC_3) - return -1; - - if (hdr->length > SVR_BUFF_SIZE - SVR_P_HDR_MIN - 1) { - log_error(MOD "package size overflow, got %d > %d.", - hdr->length, SVR_BUFF_SIZE - SVR_P_HDR_MIN - 1); - return -1; - } - - return 0; -} - -static int sock_handle_package(char *buff, int len) -{ - struct proto_hdr hdr; - - if (len < SVR_P_HDR_MIN) - return 0; - memcpy_s(&hdr, SVR_P_HDR_MIN, buff, SVR_P_HDR_MIN); - if (sock_valid_check(&hdr) < 0) { - log_error(MOD "invalid header: 0x%02x%02x%02x%02x%02x%02x%02x.", - (unsigned char)buff[0], - (unsigned char)buff[1], - (unsigned char)buff[2], - (unsigned char)buff[3], - (unsigned char)buff[4], - (unsigned char)buff[5], - (unsigned char)buff[6]); - return -1; - } - - if (len < SVR_P_HDR_MIN + (int)hdr.length) { - log_info(MOD "need more data, got length(%d).", hdr.length); - return 0; - } - - log_info(MOD "got request type(0x%04x) length(%d).", hdr.type, hdr.length); - log_info(MOD "got data: %.*s", hdr.length, buff + SVR_P_HDR_MIN); - - return hdr.length + SVR_P_HDR_MIN; -} -static void sock_read_hook(EV_P_ struct ev_io *w, int revents) -{ - char *buff; - int rc, len, left; - struct svr_ctx *ctx; - - if (EV_ERROR & (unsigned int)revents) { - log_error(MOD "invoking client failed, got invalid event."); - return; - } - - ctx = (struct svr_ctx *)w->data; - left = ctx->end - ctx->tail; - log_info(MOD "invoking client hook, got event 0x%x, left %d.", revents, left); - assert(left > 0); - - rc = recv(w->fd, ctx->tail, left, 0); - if (rc < 0) { - log_error(MOD "receive from #%d failed, %d:%s.", w->fd, - errno, strerror(errno)); - sock_context_destroy(EV_A_ ctx); - return; - } else if (rc == 0) { - log_error(MOD "peer #%d maybe closed.", w->fd); - sock_context_destroy(EV_A_ ctx); - return; - } - - ctx->tail += rc; - buff = ctx->buff; - len = ctx->tail - ctx->buff; /* current received buffer length */ - log_info(MOD "receive %d bytes from socket, total %d.", rc, len); - - while (1) { - rc = sock_handle_package(buff, len); - if (rc == 0) { /* need more data */ - len = ctx->tail - buff; - memmove(ctx->buff, buff, len); - ctx->tail = ctx->buff + len; - return; - } else if (rc < 0) { - sock_context_destroy(EV_A_ ctx); - return; - } else if (rc == len) { - ctx->tail = ctx->buff; - return; - } - - assert(len > rc); - len -= rc; - buff += rc; - } - - if (ctx->tail >= ctx->end) { - log_error(MOD "receive buffer full."); - sock_context_destroy(EV_A_ ctx); - return; - } -} - - -static void sock_client_timeout_hook(EV_P_ struct ev_timer *w, int revents) -{ - (void) revents; - struct svr_ctx *ctx; - - ctx = (struct svr_ctx *)w->data; - log_error(MOD "socket #%d timeout.", ctx->wsock.fd); - sock_context_destroy(EV_A_ ctx); -} - -static void sock_accept_hook(EV_P_ struct ev_io *w, int revents) -{ - int sockfd; - struct svr_ctx *ctx; - socklen_t socklen = sizeof(ctx->addr); - - if (EV_ERROR & revents) { - log_error(MOD "invoking accept failed, got invalid event."); - return; - } - log_info(MOD "invoking accept hook, got event 0x%x.", revents); - - ctx = (struct svr_ctx *)calloc(1, sizeof(*ctx)); - if (ctx == NULL) { - log_error(MOD "accept failed, out of memory."); - return; - } - ctx->wsock.fd = -1; - ctx->len = SVR_BUFF_SIZE; - - ctx->buff = (char *)malloc(ctx->len); - if (ctx->buff == NULL) { - log_error(MOD "accept failed, out of memory."); - free(ctx); - return; - } - ctx->tail = ctx->buff; - ctx->end = ctx->buff + ctx->len; - - sockfd = accept(w->fd, (struct sockaddr *)&ctx->addr, &socklen); - if (sockfd < 0) { - log_error(MOD "accept failed, %d:%s.", errno, strerror(errno)); - sock_context_destroy(EV_A_ ctx); - return; - } - log_info(MOD "client '%s:%d' connected, fd %d.", - inet_ntoa(ctx->addr.sin_addr), ntohs(ctx->addr.sin_port), sockfd); - - ev_io_init(&ctx->wsock, sock_read_hook, sockfd, EV_READ); - ctx->wsock.data = ctx; - ev_io_start(loop, &ctx->wsock); - - ev_timer_init(&ctx->wtimeout, sock_client_timeout_hook, SVR_IDLE_TO, SVR_IDLE_TO); - ctx->wtimeout.data = ctx; - ev_timer_start(EV_A_ &ctx->wtimeout); -} -int main(void) -{ - int sockfd, opt; - struct ev_io wsvr; - struct sockaddr_in addr; - struct ev_loop *loop = ev_default_loop(0); - - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { - log_error(MOD "create socket failed, %d:%s.", errno, strerror(errno)); - return -1; - } - - opt = 1; - if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { - log_error(MOD "set socket REUSEADDR failed, %d:%s.", errno, - strerror(errno)); - close(sockfd); - return -1; - } - - bzero(&addr, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(SVR_PORT); - addr.sin_addr.s_addr = INADDR_ANY; - - if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - log_error(MOD "bind socket failed, %d:%s.", errno, strerror(errno)); - close(sockfd); - return -1; - } - - if (listen(sockfd, SOMAXCONN) < 0) { - log_error(MOD "listen socket failed, %d:%s.", errno, strerror(errno)); - close(sockfd); - return -1; - } - - //ev_io_init(&wsvr, sock_accept_hook, sockfd, EV_READ); - ev_io_init(&wsvr, sock_accept_hook, sockfd, EV_READ | EV_WRITE); - ev_io_start(EV_A_ &wsvr); - - log_info(MOD "start to listen ':%d'.", SVR_PORT); - - /* now wait for events to arrive. */ - ev_run(EV_A_ 0); - - log_info(MOD "leave now."); - - return 0; -} diff --git a/_drafts/tree.c b/_drafts/tree.c deleted file mode 100644 index b8568d2..0000000 --- a/_drafts/tree.c +++ /dev/null @@ -1,472 +0,0 @@ -/* - * ##### 94 ##### Binary Tree Inorder Traversal - * Given a binary tree, return the inorder traversal of its nodes' values. - * - * Example: - * - * Input: [1,null,2,3] - * 1 - * \ - * 2 - * / - * 3 - * - * Output: [1,3,2] - * - * Follow up: Recursive solution is trivial, could you do it iteratively? - */ - -/* =============================================================================== */ -/** - * Definition for a binary tree node. - * struct TreeNode { - * int val; - * struct TreeNode *left; - * struct TreeNode *right; - * }; - */ -/** - * Note: The returned array must be malloced, assume caller calls free(). - */ -#if 0 -#include - - -struct TreeNode { - int val; - struct TreeNode *left; - struct TreeNode *right; -}; - -/* Method #1 递归法 */ -void RecursiveInorderTraversal(struct TreeNode *node, int *array, int *offset) -{ - if (node == NULL) - return; - - RecursiveInorderTraversal(node->left, array, offset); - - array[*offset] = node->val; - *offset += 1; - - RecursiveInorderTraversal(node->right, array, offset); -} - -int *InorderTraversal(struct TreeNode *root, int *returnSize) -{ - int *array, offset = 0; - - if (NULL == root) { - *returnSize = 0; - return NULL; - } - - array = calloc(sizeof(*array), 256); - if (array == NULL) { - *returnSize = 0; - return NULL; - } - - RecursiveInorderTraversal(root, array, &offset); - - *returnSize = offset; - return array; -} -#endif - - - -#if 0 -//方法二:迭代法 -//1,遍历节点A,将节点压入栈中, -//2,遍历A的左支, -//3,A出栈,访问A -//4,遍历A的右支 -int *inorderTraversal(struct TreeNode *root, int *returnSize) { - int iMax = 100; - int iTop = 0; - int* pRet = NULL; - int iRetSize = 0; - - struct TreeNode* pTmp = root; - struct TreeNode* pStrTreeBuf[iMax]; //建立节点指针数组,模拟栈保存节点 - - pRet = (int*)malloc(sizeof(int) * iMax); - memset(pRet, 0x00, sizeof(int) * iMax); - - while((pTmp != NULL) || (iTop != 0)) - { - while(pTmp != NULL) - { - //1,遍历节点,将检点压入栈中 - pStrTreeBuf[iTop] = pTmp; - iTop += 1; - - //2,遍历左支 - pTmp = pTmp->left; - } - - //3,出栈,访问节点 - iTop -= 1; - pTmp = pStrTreeBuf[iTop]; - pRet[iRetSize] = pTmp->val; - iRetSize += 1; - - //4,遍历右支 - pTmp = pTmp->right; - } - - //5,返回 - *returnSize = iRetSize; - return pRet; -} -#endif - - -#include -#include -#include -#include -#include - -struct bstree_node { - uintptr_t key, value; - struct bstree_node *left, *right, *parent; -}; - -struct bstree { - struct bstree_node *root; - int (*compare)(const uintptr_t, const uintptr_t); - int size; -}; - -struct bstree_node *bstree_node_create(uintptr_t key, uintptr_t value) -{ - struct bstree_node *node; - - node = calloc(1, sizeof(*node)); - if (node == NULL) - return NULL; - node->key = key; - node->value = value; - return node; -} - -void bstree_destroy(struct bstree *tree) -{ - if (tree == NULL) - return; - free(tree); -} - -struct bstree *bstree_create(int (*cmp)(const uintptr_t, const uintptr_t)) -{ - struct bstree *tree; - - tree = calloc(1, sizeof(*tree)); - if (tree == NULL) - return NULL; - tree->compare = cmp; - return tree; -} - -static struct bstree_node *bstree_do_search(struct bstree *tree, uintptr_t key, - struct bstree_node **parent, int *isleft) -{ - int rc; - struct bstree_node *curr; - - *isleft = 0; - *parent = NULL; - curr = tree->root; - while (curr != NULL) { - *parent = curr; - rc = tree->compare(key, curr->key); - if (rc == 0) { - return curr; - } else if (rc > 0) { - *isleft = 0; - curr = curr->right; - } else { - *isleft = 1; - curr = curr->left; - } - } - - return NULL; -} - -struct bstree_node *bstree_insert(struct bstree *tree, uintptr_t key, uintptr_t value) -{ - int isleft; - struct bstree_node *node, *parent; - - if (tree == NULL) - return NULL; - - node = bstree_do_search(tree, key, &parent, &isleft); - if (node != NULL) - return node; - - node = bstree_node_create(key, value); - if (node == NULL) - return NULL; - - node->parent = parent; - if (parent == NULL) - tree->root = node; - else if (isleft > 0) - parent->left = node; - else - parent->right = node; - - return node; -} - -int bstree_search(struct bstree *tree, uintptr_t key, uintptr_t *ret) -{ - int isleft; - struct bstree_node *node, *tmp; - - if (tree == NULL) - return -EINVAL; - - node = bstree_do_search(tree, key, &tmp, &isleft); - if (node == NULL) - return -1; - - *ret = node->value; - return 0; -} - -void bstree_preorder(struct bstree_node *node) -{ - if (node == NULL) - return; - //printf("Key=%ld Value=%ld\n", node->key, node->value); - printf("%c ", (char)node->key); - bstree_preorder(node->left); - bstree_preorder(node->right); -} - -void bstree_inorder(struct bstree_node *node) -{ - if (node == NULL) - return; - bstree_inorder(node->left); - //printf("Key=%ld Value=%ld\n", node->key, node->value); - printf("%c ", (char)node->key); - bstree_inorder(node->right); -} - -void bstree_postorder(struct bstree_node *node) -{ - if (node == NULL) - return; - bstree_postorder(node->left); - bstree_postorder(node->right); - //printf("Key=%ld Value=%ld\n", node->key, node->value); - printf("%c ", (char)node->key); -} - - -#if 0 -node *new(){ - return (node *)malloc(sizeof(node)); -} -node *root(int value){ //call at the time of initilisation - node *ptr = new(); - ptr-> data = value; - ptr-> father = 0; - ptr-> lptr = 0; - ptr-> lptr = 0; - return ptr; -} -void insert(int x, node *ptr){ - if(x > ptr->data){ - if( ptr-> rptr){ - insert(x,ptr-> rptr); - }else{ - node *temp = new(); - temp-> data = x; - temp-> father = ptr; - temp-> lptr = 0; - temp-> rptr = 0; - ptr-> rptr = temp; - } - }else{ - if( ptr-> lptr){ - insert(x,ptr-> lptr); - }else{ - node *temp = new(); - temp-> data = x; - temp-> father = ptr; - temp-> lptr = 0; - temp-> rptr = 0; - ptr-> lptr = temp; - } - } -} -void preorder(node *ptr){ - if(ptr){ - printf("%d\n",ptr-> data ); - preorder( ptr-> lptr); - preorder( ptr-> rptr); - } -} -void postorder(node *ptr){ - if(ptr){ - postorder( ptr-> lptr); - postorder( ptr-> rptr); - printf("%d\n",ptr-> data ); - } -} -void inorder(node *ptr){ - if(ptr){ - inorder( ptr-> lptr); - printf("%d\n",ptr-> data ); - inorder( ptr-> rptr); - } -} -node *search(int x, node *ptr){ - if(ptr){ - int y = ptr-> data; - if(y == x) return ptr; - else if( y < x ) return search(x,ptr->rptr); - else return search(x,ptr->lptr); - }else return 0; -} -node *parent(int x, node *ptr){ - node *temp; - if(temp = search(x,ptr)){ - return temp-> father; - }else return 0; -} -node *lchild(int x, node *ptr){ - node *temp; - if(temp = search(x,ptr)){ - return temp-> lptr; - }else return 0; -} -node *rchild(int x, node *ptr){ - node *temp; - if(temp = search(x,ptr)){ - return temp-> rptr; - }else return 0; -} -int delete(int x, node *ptr){ - node *temp; - if(temp = search(x,ptr)){ - if(temp == ptr){ /// ROOT - node *z = ptr-> rptr; - ptr-> rptr-> father = 0; - while( z-> lptr) z = z-> lptr; - z-> lptr = ptr-> lptr; - rut = ptr-> rptr; - free(ptr); - }else if(ptr-> lptr && ptr-> rptr){ /// Both child - node *z,*temp = ptr-> father; - temp-> rptr = ptr-> rptr; - ptr-> rptr-> father = temp; - z = temp-> rptr; - while( z-> lptr) z = z-> lptr; - z-> lptr = ptr-> lptr; - free(ptr); - }else if(ptr-> lptr && !ptr-> rptr){ /// Left child only - node *temp = ptr-> father; - temp-> lptr = ptr-> lptr; - ptr-> lptr-> father = temp; - free(ptr); - }else if(!ptr-> lptr && ptr-> rptr){ /// Right child only - node *temp = ptr-> father; - temp-> rptr = ptr-> rptr; - ptr-> rptr-> father = temp; - free(ptr); - }else{ /// No child - node *temp = ptr-> father; - int k = temp->data; - if(k < x) temp-> rptr = 0; - else temp-> lptr = 0; - free(ptr); - } - return(x); - }else{ - printf("NOT FOUND\n"); - return 0; - } -} -int height(node *ptr,int count){ - if(ptr){ - int x = height(ptr-> lptr,count+1),y = height(ptr-> rptr,count+1); - return ( x > y ? x : y); - } - return count; -} - -int main(void) -{ - rut = root(100); - insert(95,rut); - insert(45,rut); - insert(195,rut); - insert(145,rut); - printf("height %d\n",height(rut,0)); - inorder(rut); - postorder(rut); - preorder(rut); - printf("%d\n",search(95,rut)->data); - printf("%d\n",lchild(100,rut)->data); - printf("%d\n",rchild(100,rut)->data); - printf("%d\n",delete(100,rut)); - preorder(rut); - printf("height %d\n",height(rut,0)); - return 0; -} -#endif - -static int compare_function(const uintptr_t a0, const uintptr_t a1) -{ - return a0 - a1; -} - -int main(void) -{ - struct bstree *tree; - struct bstree_node *nA = bstree_node_create('A', 'A'); - struct bstree_node *nB = bstree_node_create('B', 'B'); - struct bstree_node *nC = bstree_node_create('C', 'C'); - struct bstree_node *nD = bstree_node_create('D', 'D'); - struct bstree_node *nE = bstree_node_create('E', 'E'); - struct bstree_node *nF = bstree_node_create('F', 'F'); - struct bstree_node *nG = bstree_node_create('G', 'G'); - struct bstree_node *nH = bstree_node_create('H', 'H'); - struct bstree_node *nI = bstree_node_create('I', 'I'); - - tree = bstree_create(compare_function); - if (tree == NULL) - return -1; - nA->left = nB; nA->right = nC; - nB->left = nD; nB->right = nE; - nE->left = nG; nE->right = nH; - nC->left = nF; nF->right = nI; - - bstree_preorder(nA); // A B D E G H C F I - puts(""); - bstree_inorder(nA); // D B G E H A F I C - puts(""); - bstree_postorder(nA); // D G H E B I F C A - puts(""); - - -#if 0 - n1 = bstree_insert(tree, 1, 100); - assert(n1 != NULL); - n2 = bstree_insert(tree, 1, 100); - assert(n1 == n2); -#endif - - bstree_destroy(tree); - - return 0; -} - diff --git a/_includes/ads_content.html b/_includes/ads_content01.html similarity index 100% rename from _includes/ads_content.html rename to _includes/ads_content01.html diff --git a/_includes/ads_content1.html b/_includes/ads_content02.html similarity index 100% rename from _includes/ads_content1.html rename to _includes/ads_content02.html diff --git a/_includes/ads_contents.html b/_includes/ads_contents.html deleted file mode 100644 index 8b13789..0000000 --- a/_includes/ads_contents.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/_includes/advertisements.html b/_includes/advertisements.html deleted file mode 100644 index 8b13789..0000000 --- a/_includes/advertisements.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/_includes/head.html b/_includes/head.html index 24bfde9..af42bca 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -1,39 +1,35 @@ + {% if page.title %} {{ page.title }} {% else %} {{ site.title }} {% endif %} + + {% if page.keywords %}{% endif %} + + + + + - - {% if page.title %}{{ page.title }} | {% endif %}悟空小饭 - - - + {% if jekyll.environment == "production" %} + + - - - - - + {% else %} + + - - + {% endif %} + - {% endif %} - - {% if jekyll.environment == "production" %} {% endif %} - {% if page.usemath %} - + {% if jekyll.environment == "production" %} + + {% else %} + + {% endif %} {% endif %} {% if jekyll.environment == "production" %} diff --git a/_includes/sidebar.html b/_includes/sidebar.html index 5e1e6f0..b0bb7dc 100644 --- a/_includes/sidebar.html +++ b/_includes/sidebar.html @@ -40,6 +40,7 @@

    Categories

    {% endfor %} +

    Search

    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/static/js/mathjax.min.js b/static/js/mathjax.min.js new file mode 100644 index 0000000..9240f37 --- /dev/null +++ b/static/js/mathjax.min.js @@ -0,0 +1,19 @@ +/* + * /MathJax/latest.js + * + * Copyright (c) 2009-2018 The MathJax Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function(){var h={"cdnjs.cloudflare.com":{api:"https://api.cdnjs.com/libraries/mathjax?fields=version",version:"version",mathjax:"https://cdnjs.cloudflare.com/ajax/libs/mathjax/"},"cdn.rawgit.com":{api:"https://api.github.com/repos/mathjax/mathjax/releases/latest",version:"tag_name",mathjax:"https://cdn.rawgit.com/mathjax/MathJax/"},"cdn.jsdelivr.net":{api:"https://api.jsdelivr.com/v1/jsdelivr/libraries?name=mathjax&lastversion=*",version:"lastversion",mathjax:"https://cdn.jsdelivr.net/mathjax/"}};function g(q){if(console&&console.log){console.log(q)}}function e(){if(document.currentScript){return document.currentScript}var r=document.getElementsByTagName("script");for(var v=0,q=r.length;v element")}}function i(){var q=e();if(q){o(q.src.replace(/\/latest\.js/,"/MathJax.js"))}else{g("Can't determine the URL for loading MathJax")}}function m(q,r,s){var t=j();if(t){t.onreadystatechange=function(){if(t.readyState===4){if(t.status===200){var v=JSON.parse(t.responseText);if(v instanceof Array){v=v[0]}var u=v[q.version];if(u.substr(0,2)==="2."){c(u);o(q.mathjax+v[q.version]+s+"/MathJax.js"+r);return}}else{g("Problem acquiring MathJax version: status = "+t.status)}i()}};t.open("GET",q.api,true);t.send(null)}else{g("Can't create XMLHttpRequest object");i()}}var n=e();var p=a(n);if(p){var b=n.src.replace(/.*?(\?|$)/,"$1");b+=(b?"&":"?")+"latest";var f=(n.src.match(/\/unpacked\/latest\.js/)?"/unpacked":"");var k=d();if(k){o(p.mathjax+k+f+"/MathJax.js"+b)}else{m(p,b,f)}}else{i()}})(); diff --git a/update.md b/update.md new file mode 100644 index 0000000..4a9c515 --- /dev/null +++ b/update.md @@ -0,0 +1,547 @@ +2012-01-01-notes-finance-introduce.md +2012-07-04-awesome-stuff.md +2013-02-05-english-stuff.md +2013-03-09-linux-tips.md +2013-03-15-linux-lvs-introduce.md +2013-03-19-linux-commands-text.md +2013-03-29-linux-monitor.md +2013-04-05-kernel-memory-virtual-physical-map.md +2013-04-06-linux-monitor-memory.md +2013-04-12-linux-commands-tips.md +2013-04-14-linux-monitor-cpu.md +2013-04-20-linux-monitor-memory-cache-buffer-introduce.md +2013-04-29-network-setting.md +2013-05-12-linux-monitor-misc.md +2013-06-01-time-mess.md +2013-06-21-linux-kernel-scheduler.md +2013-07-02-linux-kernel-timer.md +2013-08-31-git-simple-guide.md +2013-10-03-build-a-rtems-environment.md +2014-01-02-linux-bash-some-shell-scripts.md +2014-01-13-linux-chroot.md +2014-01-20-continuous-improvement.md +2014-01-23-network-introduce.md +2014-01-30-network-nettools-vs-iproute2.md +2014-02-03-linux-monitor-network.md +2014-02-19-linux-communication-netlink.md +2014-02-23-kernel-compile.md +2014-02-25-kernel-hardware-startup.md +2014-02-28-kernel-bootstrap.md +2014-03-10-linux-file-operations.md +2014-03-15-linux-kernel-virtual-file-system-introduce.md +2014-03-19-linux-commands-text-awk-introduce.md +2014-03-21-linux-commands-text-sed-introduce.md +2014-03-23-kernel-syscall.md +2014-03-29-always-someone-go-first.md +2014-04-07-network-tcpip-introduce.md +2014-04-29-network-tcpip-timewait.md +2014-05-03-linux-monitor-io.md +2014-05-09-network-wireshark-introduce.md +2014-05-21-tmux-introduce.md +2014-05-27-details-about-dstat.md +2014-06-01-security-encryption-introduce.md +2014-06-02-linux-timer-functions.md +2014-06-07-https-introduce.md +2014-06-30-rsync-inotify.md +2014-07-06-python-environment-prepare.md +2014-07-16-security-libgcrypt-practice.md +2014-08-01-program-exec-basic-concept-introduce.md +2014-08-26-program-c-complie-link.md +2014-08-30-linux-leap-seconds-basic-introduce.md +2014-09-04-python-gevent-greenlet.md +2014-09-08-linux-bash-pitfalls.md +2014-09-10-linux-program-shared-memory.md +2014-09-27-kernel-memory-management-from-userspace-view.md +2014-10-03-linux-user-management-introduce.md +2014-10-05-stay-hungry-stay-foolish.md +2014-10-21-kernel-memory-management-from-kernel-view.md +2014-10-31-network-commands.md +2014-11-20-no-efforts-made-for-nothing.md +2014-11-28-haproxy-introduce.md +2014-12-27-security-ssl-tls-nginx-https-setting.md +2015-01-02-ssh-introduce.md +2015-01-08-gnucach-personal-financial-accounting-introduce.md +2015-01-15-ssh-simplify-your-life.md +2015-01-18-ssh-proxy.md +2015-01-23-centos-config-yum-rpm-stuff-introduce.md +2015-01-28-ssh-tips.md +2015-02-21-kernel-memory-tips.md +2015-03-02-linux-container-lxc-introduce.md +2015-03-11-linux-container-lxc-sshd.md +2015-03-16-linux-dnsmasq-introduce.md +2015-03-19-network-dns-basic-introduce.md +2015-03-23-program-c-linux-pthreads-tips.md +2015-03-25-new-curses-library-stuff-introduce.md +2015-03-30-network-dns-resolv-conf-usage-introduce.md +2015-04-01-mysql-begin.md +2015-04-05-mysql-introduce.md +2015-04-10-mysql-mysqld-safe.md +2015-04-12-mysql-basic.md +2015-04-18-network-dns-async-resolve-introduce.md +2015-04-23-program-c-linux-pthreads-synchronize.md +2015-04-27-mysql-users.md +2015-04-29-network-service-ftp.md +2015-05-08-charsets-encoding.md +2015-05-09-linux-program-aio.md +2015-05-12-mysql-protocol.md +2015-05-13-python-most-useful-tools.md +2015-05-29-mysql-parser.md +2015-06-01-mysql-innodb-introduce.md +2015-06-02-linux-kernel-process.md +2015-06-06-python-tips.md +2015-06-07-python-garbage-collection.md +2015-06-09-linux-basic-conception-for-time-details.md +2015-06-09-mysql-innodb-storage.md +2015-06-11-python-modules-logging.md +2015-06-13-linux-vim-introduce.md +2015-06-16-python-eval.md +2015-06-17-python-modules.md +2015-06-19-java-environment.md +2015-06-23-network-netfilter-iptables.md +2015-06-27-mysql-executor.md +2015-07-02-mysql-plugin.md +2015-07-04-linux-vim-third-plugins-introduce.md +2015-07-05-mysql-storage-engine-plugin.md +2015-07-14-mysql-monitor.md +2015-07-17-an-interesting-strategy-for-thieves.md +2015-07-23-mysql-tools.md +2015-07-24-mysql-handler.md +2015-07-25-mysql-tools-internal.md +2015-07-28-details-about-cronie.md +2015-08-05-network-synack-queue.md +2015-08-09-mysql-log.md +2015-08-12-mysql-replication.md +2015-08-15-mysql-replication-sourcecode.md +2015-08-16-program-c-tips.md +2015-08-18-mysql-replication-mha.md +2015-08-20-centos-config-from-scratch.md +2015-08-23-git-branch-model.md +2015-08-26-linux-the-really-teensy-elf-file.md +2015-09-01-linux-vim-third-plugins-tags-stuff-introduce.md +2015-09-08-program-c-string-linux-wildcard-introduce.md +2015-09-10-git-tips.md +2015-09-12-linux-program-io-multiplexing.md +2015-09-15-program-c-load-process.md +2015-09-16-program-c-string-stuff.md +2015-09-17-linux-systemd.md +2015-09-20-program-c-load-process-plt-got.md +2015-09-21-linux-container-cgroup-introduce.md +2015-09-23-linux-bash-related-stuff.md +2015-09-25-lua-introduce.md +2015-09-31-mysql-innodb-double-write-buffer.md +2015-10-01-mysql-deploy-online.md +2015-10-03-linux-selinux-introduce.md +2015-10-06-lua-coroutine.md +2015-10-16-kernel-memory-mmap-introduce.md +2015-10-17-network-protocols.md +2015-10-19-java-jdbc-introduce.md +2015-10-21-linux-create-rpm-package.md +2015-10-24-security-how-to-save-password.md +2015-10-28-lua-sourcecode.md +2015-11-01-mysql-innodb-isolation-level.md +2015-11-02-the-basic-of-economy.md +2015-11-03-linux-makefile-auto-compile-introduce.md +2015-11-05-linux-kernel-process-introduce.md +2015-11-10-program-c-linux-getopt-usage-introduce.md +2015-11-13-linux-cmake-auto-compile-introduce.md +2015-11-17-algorithm-structure-trees-introduce.md +2015-11-19-linux-tcprstat.md +2015-11-20-linux-program-structure-skiplist-introduce.md +2015-11-23-the-real-meaning-of-life.md +2015-11-27-linux-package-config-introduce.md +2015-11-30-linux-autotools-auto-compile-introduce.md +2015-12-05-wonderful-things-about-virtualbox.md +2015-12-07-inkscape-introduce.md +2015-12-09-mysql-semisync.md +2015-12-19-mysql-capi-introduce.md +2015-12-23-program-c-java.md +2016-01-02-saltstack-introduce.md +2016-01-03-mysql-gtid.md +2016-01-13-theme-algorithm-introduce.md +2016-01-19-sqlite-introduce.md +2016-01-31-logrotate-usage.md +2016-02-03-security-dvwa.md +2016-02-10-python-async-queue.md +2016-02-13-python-ansible.md +2016-02-19-mysql-replication-pt-table-checksum.md +2016-02-23-kernel-modules.md +2016-02-26-mysql-group-commit.md +2016-03-06-mysql-innodb-checkpoint.md +2016-03-08-mysql-innodb-redo-log.md +2016-03-23-webserver-introduce.md +2016-03-27-its-humor-instead-of-funny.md +2016-04-01-mysql-sandbox.md +2016-04-02-nginx-introduce.md +2016-04-18-nginx-cgi-introduce.md +2016-04-27-nginx-logs-introduce.md +2016-04-30-nginx-monitor.md +2016-05-12-bootstrap-etc.md +2016-05-23-nginx-uwsgi-flask.md +2016-06-05-linux-kernel-io-scheduler.md +2016-06-10-zeromq-introduce.md +2016-06-20-zeromq-architecture.md +2016-06-21-program-c-elf-details.md +2016-06-23-uwsgi-introduce.md +2016-06-27-mysql-tips.md +2016-06-28-nginx-sourecode-analyze.md +2016-07-06-mysql-innodb-crash-recovery.md +2016-07-17-the-boxers-movement.md +2016-07-18-redis-introduce.md +2016-07-20-theme-develop-workspace.md +2016-07-22-theme-language-javascript.md +2016-07-25-theme-linux-kenerl-memory.md +2016-07-26-theme-linux-security.md +2016-07-28-two-factors-authenticator-introduce.md +2016-07-30-redis-cluster-and-devops-introduce.md +2016-08-02-postgresql-introduce.md +2016-08-05-postgresql-structure-privileges.md +2016-08-08-golang-some-third-tools.md +2016-08-10-flask-introduce.md +2016-08-13-flask-request-process.md +2016-08-14-golang-raft-etcd-introduce.md +2016-08-15-golang-introduce.md +2016-08-16-collectd-introduce.md +2016-08-18-flask-context.md +2016-08-20-postgresql-c-language-pgcenter.md +2016-08-22-mysql-crash-safe-replication.md +2016-08-23-flask-route.md +2016-08-24-postgresql-source-code-introduce.md +2016-08-25-golang-how-to-write-go-code.md +2016-08-26-collectd-source-code.md +2016-08-27-kafka-introduce.md +2016-08-31-flask-tips.md +2016-09-01-mysql-shutdown.md +2016-09-06-flask-unittest.md +2016-09-09-network-http-introduce.md +2016-09-12-time-series-database-introduce.md +2016-09-14-mysql-core-file.md +2016-09-15-linux-systemtap.md +2016-09-17-linux-xwindows-introduce.md +2016-09-23-linux-bash-redirect-details.md +2016-09-25-influxdata-influxdb-introduce.md +2016-09-27-grit-the-key-to-sucess.md +2016-10-06-theme-language-ccpp.md +2016-10-09-theme-linux-kenerl-monitor.md +2016-10-10-theme-language-java.md +2016-10-12-theme-language-bash.md +2016-10-13-theme-linux-kenerl-stuff.md +2016-10-15-theme-linux-environment.md +2016-10-16-theme-language-golang.md +2016-10-17-theme-language-python.md +2016-10-19-theme-database-mysql.md +2016-10-20-theme-database-postgresql.md +2016-10-21-security-pgp-introduce.md +2016-10-23-cookbook-of-midnight-diner.md +2016-10-25-theme-linux-kenerl-network.md +2016-10-26-theme-linux-kenerl-container.md +2016-11-03-linux-gnuplot.md +2016-11-09-linux-libev-source-code-details-introduce.md +2016-11-14-python-basic-introduce.md +2016-11-17-conky-introduce.md +2016-11-28-government-smaller.md +2016-12-01-linux-rpm-fixdb-introduce.md +2016-12-23-linux-daemon-tools.md +2016-12-24-mysql-group-replication.md +2016-12-30-linux-libev.md +2017-01-02-javascript-umi-introduce.md +2017-01-05-linux-virtual-kvm-introduce.md +2017-01-08-python-dictioniary-string-hash-table.md +2017-01-12-linux-process-exec-priority-nice-introduce.md +2017-01-15-theme-linux-time-stuff.md +2017-01-20-linux-performance-benchmark-tools-sysbench.md +2017-02-03-statsd-monitor-introduce.md +2017-02-06-linux-details-of-thundering-herd.md +2017-02-12-linux-page-cache-concept-introduce.md +2017-02-16-linux-process-state-introduce.md +2017-02-20-linux-network-mtu-oversize.md +2017-02-23-python-version2-vs-version3-introduce.md +2017-02-28-linux-ntp-related-stuff-introduce.md +2017-03-01-linux-cpu-physical-arch-introduce.md +2017-03-04-language-cpp-concurrency-mutex-introduce.md +2017-03-06-language-cpp-concurrency-condition-variable-introduce.md +2017-03-10-program-c-gdb-basic-usage-introduce.md +2017-03-12-language-cpp-concurrency-promise-future-introduce.md +2017-03-16-program-c-gtest-gmock.md +2017-03-20-language-cpp-basic-syntax-introduce.md +2017-03-23-language-cpp-stl-basic-usage-introduce.md +2017-03-25-linux-monitor-process-introduce.md +2017-03-30-linux-libev-timers.md +2017-04-05-linux-signal-safe-introduce.md +2017-04-10-linux-c-fail-point-introduce.md +2017-04-15-linux-libev-source-code-signal-process-details.md +2017-04-24-webserver-restful-api-introduce.md +2017-05-01-program-c-compress-library-introduce.md +2017-05-09-linux-resource-limit-introduce.md +2017-05-12-linux-c-memory-barriers-basic-introduce.md +2017-05-14-math-probability-basic-concept-discrete-distribution-introduce.md +2017-05-20-math-probability-basic-concept-continuous-distribution-introduce.md +2017-05-21-math-probability-continuous-normal-distribution-introduce.md +2017-05-23-python-orm-introduce.md +2017-05-25-language-cpp-memory-module-introduce.md +2017-05-28-language-cpp-memory-layout-introduce.md +2017-05-31-language-cpp-basic-constructor-destructor-introduce.md +2017-06-02-python-basic-syntax-introduce.md +2017-06-03-python-basic-syntax-with-introduce.md +2017-06-05-python-basic-syntax-docstring-introduce.md +2017-06-08-golang-common-module-bytes-introduce.md +2017-06-10-golang-basic-concept-nil-introduce.md +2017-06-15-linux-ptrace-api-introduce.md +2017-06-20-golang-testing-method-introduce.md +2017-06-26-golang-testing-method-httptest-introduce.md +2017-06-29-math-statistics-kernel-density-estimates-introduce.md +2017-07-03-wish-you-bad-luck.md +2017-07-10-thirteen-tips-about-lifelong-growth.md +2017-07-15-linux-R-language-some-statistic-function-introduce.md +2017-07-19-git-advance-usage.md +2017-07-23-linux-monitor-cgroup-introduce.md +2017-07-25-language-c-some-sanitizers-introduce.md +2017-07-29-linux-cgroup-cpuset-subsys-introduce.md +2017-08-01-math-statistics-likelihood-function-introduce.md +2017-08-07-bitcoin-50-python-lines.md +2017-08-10-javascript-basic-syntax-introduce.md +2017-08-12-hash-functions-introduce.md +2017-08-15-http-basic-concept-cors-introduce.md +2017-08-21-program-c-network.md +2017-08-25-language-cpp-basic-callable-introduce.md +2017-08-28-linux-protocol-network-torrent-p2p-introduce.md +2017-09-01-centos-update-kernel-version.md +2017-09-05-program-c-string-regular-expression.md +2017-09-10-zabbix-monitor-introduce.md +2017-09-12-javascript-environment-introduce.md +2017-09-15-uuid-introduce.md +2017-09-17-program-concept-lexical-flex-introduce.md +2017-09-20-program-concept-syntax-bison-introduce.md +2017-09-23-lua-how-capi-works.md +2017-09-25-websocket-protocol-introduce.md +2017-09-29-program-lexical-basic-introduce.md +2017-10-03-chinese-tea-introduce.md +2017-10-05-program-c-gdb-stack-frame-introduce.md +2017-10-08-git-some-basic-patch-comand-usage.md +2017-10-11-kernel-inotify-introduce.md +2017-10-14-golang-example-socket-introduce.md +2017-10-18-golang-array-slice-concept-introduce.md +2017-10-20-language-c-error-message-usage-introduce.md +2017-10-23-flask-basic-restfull-api-and-test.md +2017-10-27-linux-direct-io-introduce.md +2017-10-31-program-c-fuzzing-test-introduce.md +2017-11-01-language-cpp-some-pitfalls.md +2017-11-05-golang-raft-etcd-example-sourcode-details.md +2017-11-09-theme-database-etcd.md +2017-11-12-linux-umask-and-open-introduce.md +2017-11-15-golang-raft-etcd-sourcode-storage.md +2017-11-19-airflow-dac-introduce.md +2017-11-20-program-c-gdb-deadlock-analyze-introduce.md +2017-11-25-kernel-signal-introduce.md +2017-11-30-golang-raft-etcd-sourcode-consistent-reading.md +2017-12-01-network-traffic-control-introduce.md +2017-12-05-golang-raft-etcd-sourcode-network.md +2017-12-07-javascript-css-html-browser-basic-introduce.md +2017-12-10-program-c-gdb-dwarf-format-introduce.md +2017-12-15-golang-raft-etcd-sourcode-details.md +2017-12-18-javascript-react-syntax-introduce.md +2017-12-20-language-cpp-basic-right-value-reference-introduce.md +2017-12-23-linux-memory-basic-introduce.md +2017-12-28-react-redux-introduce.md +2017-12-31-language-cpp-basic-smart-pointer-introduce.md +2018-01-03-linux-c-reload-dynamic-library.md +2018-01-04-python-pandas-package-introduce.md +2018-01-06-program-c-elf-symbol-section.md +2018-01-10-shadowsock-some-basic-tips-introduce.md +2018-01-13-react-practice-examples.md +2018-01-15-network-ipv6-basic-introduce.md +2018-01-18-life-some-basic-management-tips.md +2018-01-21-message-queue-beanstalk-introduce.md +2018-01-30-python-c-bind-introduce.md +2018-02-03-git-some-interesting-terminology.md +2018-02-08-managing-oneself.md +2018-02-13-json-web-token-introduce.md +2018-02-20-single-sign-on-introduce.md +2018-02-23-linux-capabilities-introduce.md +2018-02-25-react-antd-design-introduce.md +2018-03-01-linux-bash-basic-syntax.md +2018-03-08-zhang-professor-some-tips.md +2018-03-10-software-patterns-uml-tools-introduce.md +2018-03-13-golang-basic-syntax-introduce.md +2018-03-25-golang-common-module-introduce.md +2018-03-26-golang-common-module-time-introduce.md +2018-03-30-golang-syntax-interface-introduce.md +2018-04-01-language-c-pointer-basic-usage-introduce.md +2018-04-02-network-icmp-ping-program-introduce.md +2018-04-13-raft-consensus-algorithms-introduce.md +2018-04-17-network-tools-nmap-introduce.md +2018-04-20-language-c-some-basic-functions-usage-introduce.md +2018-04-23-golang-raft-etcd-backend-boltdb.md +2018-04-27-golang-net-http-webserver-introduce.md +2018-04-30-raft-consistency-models-basic-introduce.md +2018-05-07-tensorflow-introduce.md +2018-05-09-python-ai-environment-prepare.md +2018-05-12-linux-random-introduce.md +2018-05-18-how-chinese-economy-corrupted.md +2018-05-20-linux-c-volatile-statement-introduce.md +2018-05-24-protobuf-protocol-introduce.md +2018-05-30-linux-c-gnu-inline-assembly-language-introduce.md +2018-06-05-some-interesting-things-about-hybrid-rice.md +2018-06-07-linux-signal-vs-thread.md +2018-06-10-math-statistics-basic-concept.md +2018-06-13-program-c-language-gcc-some-stuff.md +2018-06-16-program-c-debug-release-etc.md +2018-06-20-language-c-socket-close-method.md +2018-06-21-math-statistics-basic-concept-bayes-theorem-introduce.md +2018-06-27-hacker-attack-and-defense-diary.md +2018-06-30-linux-kernel-hang-task-panic-introduce.md +2018-07-01-golang-grpc-introduce.md +2018-07-05-golang-http-structure-echo-introduce.md +2018-07-08-program-c-ssl-tls-basic-introduce.md +2018-07-10-python-matplotlib-package-intorduce.md +2018-07-16-program-c-preload-introduce.md +2018-07-20-linux-pipe-stuff-introduce.md +2018-07-23-golang-grpc-sourcecode-details.md +2018-07-28-linux-memory-buffer-vs-cache-details.md +2018-07-30-linux-c-mock-wrap-unit-test.md +2018-08-01-kernel-memory-oom-killer-introduce.md +2018-08-03-math-basic-elementary-function.md +2018-08-05-kernel-cgroup-memory-introduce.md +2018-08-12-golang-json-encode-decode-introduce.md +2018-08-15-linux-git-gogs-introduce.md +2018-08-17-linux-c-flock-introduce.md +2018-08-19-golang-db-introduce.md +2018-08-20-language-c-i18n-multi-language-gettext-usage.md +2018-08-23-linux-ramdisk-introduce.md +2018-08-27-linux-systemd-notify-watchdog-introduce.md +2018-08-30-golang-db-orm-xorm-package-introduce.md +2018-09-01-language-c-c11-standard-introduce.md +2018-09-03-the-truth-of-cancer.md +2018-09-05-theme-artificial-intelligence.md +2018-09-07-language-c-integer-basic-concepts-introduce.md +2018-09-10-golang-concurrenct-control-introduce.md +2018-09-12-python-seaborn-package-introduce.md +2018-09-13-git-some-statistic-commands-and-tools-introduce.md +2018-09-15-golang-basic-error-panic-introduce.md +2018-09-18-language-c-bit-field-and-endian-introduce.md +2018-09-20-language-c-structure-align-basic-introduce.md +2018-09-22-golang-basic-package-introduce.md +2018-09-25-golang-basic-closure-introduce.md +2018-09-27-language-c-gcc-some-basic-tips.md +2018-09-30-language-c-macros-basic-concepts-introduce.md +2018-10-01-math-probability-basic-concept.md +2018-10-04-math-calculus-basic-concept.md +2018-10-05-language-c-inline-concept-introduce.md +2018-10-09-python-numpy-package-introduce.md +2018-10-13-program-c-strong-weak-symbol-reference.md +2018-10-16-linux-network-http2-protocol-introduce.md +2018-10-20-artificial-intelligence-entropy-concept-introduce.md +2018-10-23-golang-appdash-apm-introduce.md +2018-10-27-linux-c-gcc-atomic-operation-introduce.md +2018-10-30-math-machine-learning-some-datasets-introduce.md +2018-11-02-automatic-control-proportional-integral-derivative-details.md +2018-11-04-linux-process-exit-code-introduce.md +2018-11-06-machine-learning-algorithms-linear-regression-basic-introduce.md +2018-11-08-simple-life-ukiyoe-01.md +2018-11-10-simple-life-ukiyoe-02.md +2018-11-13-linux-socket-port-range-introduce.md +2018-11-16-linux-network-http2-hpack-introduce.md +2018-11-20-linux-huffman-stuff-simple-introduce.md +2018-11-22-machine-learning-algorithms-linear-regression-bayes-introduce.md +2018-11-24-protobuf-protocol-serialize-introduce.md +2018-11-28-language-c-coverage-basic-introduce.md +2018-11-30-kubernets-basic-introduce.md +2018-12-01-git-large-file-storage-introduce.md +2018-12-04-linux-bash-auto-completion-introduce.md +2018-12-06-machine-learning-algorithms-logistic-regression-introduce.md +2018-12-09-linux-c-program-lock-free-queue-introduce.md +2018-12-11-docker-basic-introduce.md +2018-12-13-python-pymc-package-introduce.md +2018-12-15-python-scipy-package-intorduce.md +2018-12-16-git-cherry-pick-introduce.md +2018-12-19-linux-c-program-replace-glibc-memory-function-introduce.md +2018-12-21-network-dns-protocol-details-introduce.md +2018-12-23-linux-network-http2-basic-usage-introduce.md +2018-12-26-git-multi-remote-repos.md +2018-12-30-golang-syntax-structure-introduce.md +2019-01-02-git-cleanup-method-introduce.md +2019-01-05-git-cancel-reset-operation-introduce.md +2019-01-08-linux-kernel-vfs-multi-write-methods-introduce.md +2019-01-10-linux-logical-volume-manager-introduce.md +2019-01-11-proxysql-basic-introduce.md +2019-01-15-math-statistics-markov-process-introduce.md +2019-01-17-python-koch-julia-mandelbrot-introduce.md +2019-01-19-linux-pid-allocate-method-introduce.md +2019-01-21-git-internal-object-introduce.md +2019-02-01-linux-c-memory-reordering-basic-introduce.md +2019-02-05-machine-learning-algorithms-loss-functions-introduce.md +2019-02-07-linux-perf-tools-basic-usage-introduce.md +2019-02-10-linux-vim-latex-snippets-introduce.md +2019-02-13-security-key-exchange-method-introduce.md +2019-02-16-mysql-localhost-vs-127.0.0.1-introduce.md +2019-02-18-personal-evaluation-stuff.md +2019-02-20-golang-concept-memory-management-module-introduce.md +2019-02-20-leetcode-divide-and-conquer-method.md +2019-02-23-golang-assembly-language-introduce.md +2019-02-25-linux-c-some-memory-debugger-tools-introduce.md +2019-02-27-linux-c-udp-optimize-introduce.md +2019-03-01-golang-basic-syntax-channel-details-introduce.md +2019-03-04-computer-hardware-details-introduce.md +2019-03-05-algorithm-graph-theroy-basic-introduce.md +2019-03-08-golang-race-condition-introduce.md +2019-03-10-linux-c-some-pitfalls-introduce.md +2019-03-11-program-c-gdb-init-scripts-introduce.md +2019-03-12-linux-R-language-basic-introduce.md +2019-03-16-linux-c-valgrind-tools-introduce.md +2019-03-19-linux-R-language-graph-function-introduce.md +2019-03-25-docker-component-containerd-introduce.md +2019-03-28-golang-interface-source-code-introduce.md +2019-03-30-math-basic-concept-optimize-method-introduce.md +2019-04-01-security-ctf-pwn-introduce.md +2019-04-02-beringei-memory-database-introduce.md +2019-04-06-golang-reflect-introduce.md +2019-04-08-docker-component-runc-introduce.md +2019-04-10-linux-namespace-network-introduce.md +2019-04-12-kubernets-basic-components-msic-pause-introduce.md +2019-04-15-golang-concept-scheduler-introduce.md +2019-04-16-golang-concept-much-more-gopher-introduce.md +2019-04-18-security-ssl-tls-overview.md +2019-04-20-artificial-intelligence-prophet-usage-introduce.md +2019-04-23-algorithm-knapsack-problem-introduce.md +2019-04-25-statistic-tools-stan-introduce.md +2019-04-29-golang-concept-concurrency-module-introduce.md +2019-04-30-linux-cicd-drone-tools-introduce.md +2019-05-01-golang-concept-memory-module-introduce.md +2019-05-03-golang-some-pitfalls-introduce.md +2019-05-06-kubernets-network-stuff-introduce.md +2019-05-08-free-opensource-software-basic-concept.md +2019-05-10-security-openssl-commands-usage-introduce.md +2019-05-12-math-monte-carlo-sample-introduce.md +2019-05-16-security-ssl-tls-ciphersuites-introduce.md +2019-05-20-linux-ebpf-basic-usage-introduce.md +2019-05-22-math-machine-learning-pre-processing-methods-introduce.md +2019-05-25-algorithm-min-heap-basic-introduce.md +2019-05-30-linux-ebpf-bcc-tools-introduce.md +2019-06-01-monitor-system-prometheus-introduce.md +2019-06-03-golang-escape-analysis-introduce.md +2019-06-08-program-executable-binary-parse-tools.md +2019-06-11-docker-compose-tools-introduce.md +2019-06-13-linux-tools-alternatives-command-introduce.md +2019-06-15-flink-streaming-introduce.md +2019-06-20-math-gradient-descent-optimize-method-introduce.md +2019-06-25-artificial-intelligence-gaussian-process-introduce.md +2019-07-02-math-monte-carlo-mcmc-metropolis-introduce.md +2019-07-06-program-compute-instruction-set-sse-introduce.md +2019-07-08-golang-call-frame-debug-method-introduce.md +2019-07-11-docker-basic-concept-dockfile-introduce.md +2019-07-16-algorithm-binary-search-introduce.md +2019-07-18-python-statsmodels-package-introduce.md +2019-07-21-docker-basic-concept-volume-introduce.md +2019-07-29-docker-basic-concept-network-introduce.md +2019-08-01-algorithm-shuffling-method-introduce.md +2019-08-05-golang-common-module-pprof-performace-introduce.md +2019-08-15-kubernets-pod-info-introduce.md +2019-08-20-security-ssl-tls-wireshark-debug-introduce.md +2019-08-30-security-ssl-tls-certification-chains-introduce.md +2019-09-03-llvm-compiler-infrastructure-introduce.md +2019-09-28-python-matplotlib-ploting-method-intorduce.md +2019-10-02-amazing-developer-things-network-same-source-destination-port.md +2019-10-08-security-bash-commands-injection-introduce.md +2019-12-01-podman-container-basic-introduce.md +2019-12-02-cunning-lie-about-facts-and-digital.md +2019-12-08-security-ssl-tlsv1.3-some-basic-conception-introduce.md +2020-05-25-artificial-intelligence-decision-tree-introduce.md