概念
信号是软件中断。很多比较重要的应用程序都需处理信号。首先,每个信号都有一个名字。这些名字都以三个字符 SIG 开头。在头文件
所有信号名称
|
|
信号是软件中断。很多比较重要的应用程序都需处理信号。首先,每个信号都有一个名字。这些名字都以三个字符 SIG 开头。在头文件
|
|
要知道,在linux的世界里,一切皆文件.因此要实现大的并发量的第一步,修改linux系统的文件标识符限制数,也就是文件打开数量的限制
man proc 里有这么一段话
查看限制数
|
|
修改限制数
|
|
需要永久生效则
|
|
其中第一个数表示当前系统已分配使用的打开文件描述符数,第二个数为分配后已释放的(目前已不再使用),第三个数等于file-max。
|
|
|
|
通过ulimit -Sn设置最大打开文件描述符数的soft limit,注意soft limit不能大于hard limit(ulimit -Hn可查看hard limit),另外ulimit -n默认查看的是soft limit,但是 ulimit -n 204800 则会同时设置soft limit和hard limit。对于非root用户只能设置比原来小的hard limit
若要使修改永久有效,则需要在/etc/security/limits.conf中进行设置,可添加如下两行。
以上设置需要注销之后重新登录才能生效:
设置nofile的hard limit还有一点要注意的就是hard limit不能大于/proc/sys/fs/nr_open,假如hard limit大于nr_open,注销后无法正常登录。可以修改nr_open的值: echo 2000000 > /proc/sys/fs/nr_open
因为是一个内存问题,考虑使用一些内存调试工具来定位问题。因为OB内部对于内存块有自己的缓存,需要去除它的影响。修改OB内存分配器,让它每次都直接调用c库的malloc和free等,不做缓存。然后,可以使用glibc内置的内存块完整性检查功能。
使用这一特性,程序无需重新编译,只需要在运行的时候设置环境变量MALLOCCHECK(注意结尾的下划线)。每当在程序运行过程free内存给glibc时,glibc会检查其隐藏的元数据的完整性,如果发现错误就会立即abort。
用类似下面的命令行启动server程序:
MALLOCCHECK有三种设定,即:
但这个core能带给我们想信息也很少。我们只是找到了另外一种稍高效地重现问题的方法而已。或许最初看到的core的现象是延后显现而已,其实“更早”的时刻内存就被破坏掉了。
glibc提供的MALLOCCHECK功能太简单了,有没有更高级点的工具不光能够报告错误,还能分析出问题原因来?我们自然想到了大名鼎鼎的valgrind。用valgrind来检查内存问题,程序也不需要重新编译,只需要使用valgrind来启动:
nohup valgrind –error-limit=no –suppressions=suppress bin/mergeserver -z 45447 -r 10.232.36.183:45401 -p45441 >nohup.out &
默认情况下,当valgrind发现了1000中不同的错误,或者总数超过1000万次错误后,会停止报告错误。加了–error-limit=no以后可以禁止这一特性。–suppressions用来屏蔽掉一些不关心的误报的问题。
bug代码示例
|
|
编译&运行
|
|
出错提示
[img01]
Core,又称之为Core Dump文件,是Unix/Linux操作系统的一种机制,对于线上服务而言,Core令人闻之色变,因为出Core的过程意味着服务暂时不能正常响应,需要恢复,并且随着吐Core进程的内存空间越大,此过程可能持续很长一段时间(例如当进程占用60G+以上内存时,完整Core文件需要15分钟才能完全写到磁盘上),这期间产生的流量损失,不可估量。
凡事皆有两面性,OS在出Core的同时,虽然会终止掉当前进程,但是也会保留下第一手的现场数据,OS仿佛是一架被按下快门的相机,而照片就是产出的Core文件。里面含有当进程被终止时内存、CPU寄存器等信息,可以供后续开发人员进行调试。
关于Core产生的原因很多,比如过去一些Unix的版本不支持现代Linux上这种GDB直接附着到进程上进行调试的机制,需要先向进程发送终止信号,然后用工具阅读core文件。在Linux上,我们就可以使用kill向一个指定的进程发送信号或者使用gcore命令来使其主动出Core并退出。如果从浅层次的原因上来讲,出Core意味着当前进程存在BUG,需要程序员修复。从深层次的原因上讲,是当前进程触犯了某些OS层级的保护机制,逼迫OS向当前进程发送诸如SIGSEGV(即signal 11)之类的信号, 例如访问空指针或数组越界出Core,实际上是触犯了OS的内存管理,访问了非当前进程的内存空间,OS需要通过出Core来进行警示,这就好像一个人身体内存在病毒,免疫系统就会通过发热来警示,并导致人体发烧是一个道理(有意思的是,并不是每次数组越界都会出Core,这和OS的内存管理中虚拟页面分配大小和边界有关,即使不出Core,也很有可能读到脏数据,引起后续程序行为紊乱,这是一种很难追查的BUG)。
不能直接修改,需要通过下面的方法:
a. vim /etc/sysctl.conf在最后一行添加kernel.core_uses_pid = 1
b. 执行sysctl -p
查看core文件的大小
ulimit –a
修改core文件的大小
ulimit –c
像bmp、exe等文件一样,ELF的文件头包含整个文件的控制结构。它的定义如下
coredump函数在kernel/fs/exec.c中函数为do_coredump( ),如果coredump生成失败可以在do_coredump函数中增加打印,do_coredump的源代码如下所示。
最新的版本可以通过官网 http://hadoop.apache.org/zookeeper/来获取,Zookeeper 的安装非常简单,下面将从单机模式和集群模式两个方面介绍 Zookeeper 的安装和配置。
解压安装包zookerper-3.4.7.tar.gz
|
|
创建Zookeeper子目录
|
|
修改Zookeeper配置文件conf/zoo.cfg
|
|
运行
|
|
查看运行状态
|
|
新建两个directory
|
|
修改Zookeeper配置文件conf/zoo.cfg,这里配置了3个实例
|
|
分别在data目录下添加myid文件
|
|
分别运行3个实例
概念
如果索引包含所有满足查询需要的数据的索引成为覆盖索引(Covering Index),也就是平时所说的不需要回表操作
判断标准
使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为using index,MySQL查询优化器在执行查询前会决定是否有索引覆盖查询
注意
1、覆盖索引也并不适用于任意的索引类型,索引必须存储列的值
2、Hash 和full-text索引不存储值,因此MySQL只能使用B-TREE
3、并且不同的存储引擎实现覆盖索引都是不同的
4、并不是所有的存储引擎都支持它们
5、如果要使用覆盖索引,一定要注意SELECT 列表值取出需要的列,不可以是SELECT *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降,不能为了利用覆盖索引而这么做
InnoDB
1、覆盖索引查询时除了除了索引本身的包含的列,还可以使用其默认的聚集索引列
2、这跟INNOB的索引结构有关系,主索引是B+树索引存储,也即我们所说的数据行即索引,索引即数据
3、对于INNODB的辅助索引,它的叶子节点存储的是索引值和指向主键索引的位置,然后需要通过主键在查询表的字段值,所以辅助索引存储了主键的值
4、覆盖索引也可以用上INNODB 默认的聚集索引
5、innodb引擎的所有储存了主键ID,事务ID,回滚指针,非主键ID,他的查询就会是非主键ID也可覆盖来取得主键ID
覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不用读取数据有以下一些优点
1、索引项通常比记录要小,所以MySQL访问更少的数据
2、索引都按值的大小顺序存储,相对于随机访问记录,需要更少的I/O
3、大多数据引擎能更好的缓存索引,比如MyISAM只缓存索引
4、覆盖索引对于InnoDB表尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引中包含查询所需的数据,就不再需要在聚集索引中查找了
在sakila的inventory表中,有一个组合索引(store_id,film_id),对于只需要访问这两列的查 询,MySQL就可以使用索引,如下
表结构
查询语句
在大多数引擎中,只有当查询语句所访问的列是索引的一部分时,索引才会覆盖。但是,InnoDB不限于此,InnoDB的二级索引在叶子节点中存储了 primary key的值。因此,sakila.actor表使用InnoDB,而且对于是last_name上有索引,所以,索引能覆盖那些访问actor_id的查 询,如下
使用索引进行排序
MySQL中,有两种方式生成有序结果集:一是使用filesort,二是按索引顺序扫描
利用索引进行排序操作是非常快的,而且可以利用同一索引同时进 行查找和排序操作。当索引的顺序与ORDER BY中的列顺序相同且所有的列是同一方向(全部升序或者全部降序)时,可以使用索引来排序,如果查询是连接多个表,仅当ORDER BY中的所有列都是第一个表的列时才会使用索引,其它情况都会使用filesort
1、 explain select actor_id from actor order by actor_id \G
2、explain select actor_id from actor order by password \G
3、explain select actor_id from actor order by name \G
当MySQL不能使用索引进行排序时,就会利用自己的排序算法(快速排序算法)在内存(sort buffer)中对数据进行排序,如果内存装载不下,它会将磁盘上的数据进行分块,再对各个数据块进行排序,然后将各个块合并成有序的结果集(实际上就是外排序)
Redis主从复制可将主节点数据同步给从节点,从节点此时有两个作用:
但是问题来了:
Redis Sentinel是一个分布式架构,包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当发现节点不可达时,会对节点做下线标识。
如果被标识的是主节点,他还会选择和其他Sentinel节点进行“协商”,当大多数的Sentinel节点都认为主节点不可达时,他们会选举出一个Sentinel节点来完成自动故障转移工作,同时将这个变化通知给Redis应用方。
整个过程完全自动,不需要人工介入,所以可以很好解决Redis的高可用问题。
接下来我们就通过部署一个Redis Sentinel实例来了解整体框架。
我们部署的拓扑结构如图所示:
[img01]
分别有3个Sentinel节点,1个主节点,2个从节点组成一个Redis Sentinel。
role | IP | port |
---|---|---|
master | 127.0.0.1 | 6379 |
slave1 | 127.0.0.1 | 6380 |
slave2 | 127.0.0.1 | 6381 |
Sentinel1 | 127.0.0.1 | 26379 |
Sentinel2 | 127.0.0.1 | 26380 |
Sentinel3 | 127.0.0.1 | 26381 |
配置:
|
|
启动主节点
|
|
使用PING命令检测是否启动
|
|
配置(两个从节点配置相同,除了文件名有区分)
|
|
启动两个从节点
|
|
使用PING命令检测是否启动
|
|
|
|
*从节点视角(6380端口)
3个Sentinel节点的部署方法是相同的(端口不同)。以26379为例。
配置
|
|
启动(两种方法)
|
|
➜ redis-cli -h 127.0.0.1 -p 26379 INFO Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=1 //sentinels=1表示启
port 26379
dir “/var/redis/data”
sentinel myid 70a3e215c1a34b4d9925d170d9606e615a8874f2
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
daemonize yes
logfile “26379.log”
// 发现了两个从节点
sentinel known-slave mymaster 127.0.0.1 6381
sentinel known-slave mymaster 127.0.0.1 6380
// 发送了连个Sentinel节点
sentinel known-sentinel mymaster 127.0.0.1 26381 e1148ad6caf60302dd6d0dbd693cb3448e209ac2
sentinel known-sentinel mymaster 127.0.0.1 26380 39db5b040b21a52da5334dd2d798244c034b4fc3
sentinel current-epoch 0
➜ ps -aux | grep redis
root 18225 0.1 0.0 40208 11212 ? Ssl 22:10 0:05 redis-server 127.0.0.1:6379
root 18234 0.0 0.0 38160 8364 ? Ssl 22:10 0:04 redis-server 127.0.0.1:6380
root 18244 0.0 0.0 38160 8308 ? Ssl 22:10 0:04 redis-server 127.0.0.1:6381
root 20568 0.1 0.0 38160 8460 ? Ssl 23:05 0:02 redis-sentinel :26379 [sentinel]
root 20655 0.1 0.0 38160 8296 ? Ssl 23:07 0:02 redis-sentinel :26380 [sentinel]
root 20664 0.1 0.0 38160 8312 ? Ssl 23:07 0:02 redis-sentinel *:26381 [sentinel]
➜ sudo kill -9 18225
➜ ps -aux | grep redis
root 18234 0.0 0.0 38160 8364 ? Ssl 22:10 0:05 redis-server 127.0.0.1:6380
root 18244 0.0 0.0 38160 8308 ? Ssl 22:10 0:05 redis-server 127.0.0.1:6381
root 20568 0.1 0.0 38160 8460 ? Ssl 23:05 0:03 redis-sentinel :26379 [sentinel]
root 20655 0.1 0.0 38160 8296 ? Ssl 23:07 0:03 redis-sentinel :26380 [sentinel]
root 20664 0.1 0.0 38160 8312 ? Ssl 23:07 0:03 redis-sentinel *:26381 [sentinel]
127.0.0.1:26379> sentinel masters
1) 1) “name”
2) “mymaster”
3) “ip”
4) “127.0.0.1”
5) “port”
6) “6380” //可以看到主节点已经成为6380端口的节点
7) “runid”
8) “084850ab4ff6c2f2502b185c8eab5bdd25a26ce2”
9) “flags”
10) “master”
…………..
127.0.0.1:26379> sentinel slaves mymaster
1) 1) “name”
2) “127.0.0.1:6379” //ip:port
3) “ip”
4) “127.0.0.1”
5) “port”
6) “6379”
7) “runid”
8) “”
9) “flags”
10) “s_down,slave,disconnected” //端口6379的原主节点已经断开了连接
…………..
2) 1) “name”
2) “127.0.0.1:6381”
3) “ip”
4) “127.0.0.1”
5) “port”
6) “6381”
7) “runid”
8) “24495fe180e4fd64ac47467e0b2652894406e9e4”
9) “flags”
10) “slave” //本来的从节点,还是从节点的role
…………..
➜ sudo redis-server redis-6379.conf
➜ ps -aux | grep redis
root 18234 0.1 0.0 40208 11392 ? Ssl 5月22 0:06 redis-server 127.0.0.1:6380
root 18244 0.1 0.0 40208 10356 ? Ssl 5月22 0:07 redis-server 127.0.0.1:6381
root 20568 0.1 0.0 38160 8460 ? Ssl 5月22 0:05 redis-sentinel :26379 [sentinel]
root 20655 0.1 0.0 38160 8296 ? Ssl 5月22 0:05 redis-sentinel :26380 [sentinel]
root 20664 0.1 0.0 38160 8312 ? Ssl 5月22 0:05 redis-sentinel *:26381 [sentinel]
menwen 22475 0.0 0.0 14216 5920 pts/2 S+ 5月22 0:00 redis-cli -p 26379
// 6379的数据节点已重启
root 22617 0.0 0.0 38160 8304 ? Ssl 00:00 0:00 redis-server 127.0.0.1:6379
127.0.0.1:26379> sentinel slaves mymaster
1) 1) “name”
2) “127.0.0.1:6379” //6379端口的节点重启后,变成了”活”的从节点
3) “ip”
4) “127.0.0.1”
5) “port”
6) “6379”
7) “runid”
8) “de1b5c28483cf150d9550f8e338886706e952346”
9) “flags”
10) “slave”
…………..
2) 1) “name” //6381端口的节点没有变化,仍是从节点
2) “127.0.0.1:6381”
…………..
```
他被降级成为端口6380的从节点。
[img05]
从上面的逻辑架构和故障转移试验中,可以看出Redis Sentinel的以下几个功能。
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
故障转移超时时间为180000
sentinel auth-pass \ \
如果Sentinel监控的主节点配置了密码,可以通过sentinel auth-pass配置通过添加主节点的密码,防止Sentinel节点无法对主节点进行监控。
例如:sentinel auth-pass mymaster MySUPER–secret-0123passw0rd
sentinel notification-script \ \
在故障转移期间,当一些警告级别的Sentinel事件发生(指重要事件,如主观下线,客观下线等)时,会触发对应路径的脚本,想脚本发送相应的事件参数。
例如:sentinel notification-script mymaster /var/redis/notify.sh
sentinel client-reconfig-script \ \
在故障转移结束后,触发应对路径的脚本,并向脚本发送故障转移结果的参数。
例如:sentinel client-reconfig-script mymaster /var/redis/reconfig.sh。
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合
特点:
第一种方式,
也是通常我们使用的遍历方式
第二种方式,
列表推导
range(10)返回的也是一个list
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator),如果函数中包含yield语法,那这个函数就会变成生成器。这个yield的主要效果呢,就是可以使函数中断,并保存中断状态,中断后,代码可以继续往下执行,过一段时间还可以再重新调用这个函数,从上次yield的下一句开始执行。
用法一
|
|
用法二
|
|
xrange是一个生成器,而range是迭代器。
不希望修改变原有函数定义,在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator,类似于设计模式中的装饰器模式。
代码示例
有时候,有很多问题只有在线上或者预发环境才能发现,而线上又不能调试代码,所以线
上问题定位就只能看日志、系统状态和dump线程,介绍一些常用的工具,介绍一些常用命令定位线上问题。
在Linux命令行下使用TOP命令查看每个进程的情况,显示如下。
|
|
strace就是这样一款工具。通过它,我们可以跟踪程序执行过程中产生的系统调用及接收到的信号,帮助我们分析程序或命令执行中遇到的异常情况。
|
|
使用以下命令,我们将使用strace对以上程序进行跟踪,并将结果重定向至main.strace文件:
接下来我们来看main.strace文件的内容:
strace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:
系统调用的名称( 参数… ) = 返回值 错误标志和描述
|
|
呼呼!看完这么多系统调用函数,是不是有点摸不着北?让我们从整体入手,回到主题strace上来。
从上面输出可以发现,真正能与源码对应上的只有open这一个系统调用(Line25),其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入libc函数库、设置内存映射等。
源码中的if语句或其他代码在相应strace输出中并没有体现,因为它们并没有唤起系统调用。strace只关心程序与系统之间产生的交互,因而strace不适用于程序逻辑代码的排错和分析。
strace还可以记录程序与系统交互时,各个系统调用发生时的时间信息,有r、t、tt、ttt、T等几个选项,它们记录时间的方式为:
-T: 记录各个系统调用花费的时间,精确到微秒
-r: 以第一个系统调用(通常为execve)计时,精确到微秒
-t: 时:分:秒
-tt: 时:分:秒 . 微秒
-ttt: 计算机纪元以来的秒数 . 微秒
比较常用的为T选项,因为其提供了每个系统调用花费时间。而其他选项的时间记录既包含系统调用时间,又算上用户级代码执行用时,参考意义就小一些。对部分时间选项我们可以组合起来使用,例如:
最左边一列为-r选项对应的时间输出,最右边一列为-T选项对应的输出。
|
|
可向该程序传送user和system参数,以上代码使用死循环模拟用户态挂死,调用sleep模拟内核态程序挂死。
用户态挂死跟踪输出:
|
|
内核态挂死跟踪输出:
|
|
用户态挂死情况下,strace在getpid()一行输出之后没有其他系统调用输出;进程在内核态挂死,最后一行的系统调用nanosleep不能完整显示,这里nanosleep没有返回值表示该调用尚未完成。
因而我们可以得出以下结论:使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。