pika常见故障排查

一、告警处理

1.内存告警

【等级】: 【通知】
【类型】: zabbix报警(生产环境)
【内容】: PROBLEM: Pika "端口25000, PID{#PIKA_PID, 内存占用" [10.03 G > 10]
【聚合策略】: 10m(按机器聚合)
【事件名称】: zabbix_other(monitorType)
【主机】: xxxxx
【时间】: 2020-03-25 15:44:37
类型一、缓慢上涨到10G,可能出现内存泄露的情况。
redis-cli -p port rnconfig set timeout 5
sleep 10
redis-cli -p port rnconfig set timeout 120
redis-cli -p port tcmalloc free
类型二、 瞬间内存上涨到10G。

可能是持续大KEY写入或者get导致,造成临时占用巨量内存(这些内存用于该连接存放key 的执行结果,会在key 执行完毕后释放)。

2.磁盘告警

【等级】: 【通知】
【服务】: xxxxx
【类型】: pika报警(生产环境)
【策略名称】: db_size数据量
【内容】: xxxx,指标db_size的当前值199.59G > 100.00G
【聚合策略】: 1m聚合(按source聚合)
【时间】: 2020-03-25 17:15:00

紧急处理:

  • pika的主节点上一般会有dump 目录, 如果该目录不为空, 则直接删除该目录。
  • 适当调大节点上pika实例的slowlog-log-lower-than阈值。redis-cli -p port rnconfig set slowlog-log-lower-than n
  • 在pika的数据目录下, 有个log, 下面会生成大量日志文件, 这些文件根据生成时间产生, 类似于pika***.20190618-100133.31608, 除了pika.WARNING, pika.ERROR, pika.INFO当前所指向的文件, 其他日志文件都可以删除。

3.CPU告警

首先要找到哪几个线程在占用cpu,之后再通过线程的pid值在堆栈中查找具体的线程,看具体是什么问题。

top -c 显示进程运行信息列表 (按键P按CPU占有资源排序)
top -Hp PID 显示一个进程ID的线程运行信息列表 (按键P按CPU占有资源排序)
pstack tid 查询该线程具体堆栈
例1:

当rocksdb的某一Level的sst文件个数达到一定的数量的时候触发compact。
info信息 is_compact : Key
使用上述命令发现有2个线程rocksdb:bg0和rocksdb:bg1的CPU一直几乎100%。
执行pstack 查看堆栈信息:

[root@localhost pika-new]# pstack 97044
Thread 1 (process 97044):
#0  0x00007f9b13833965 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x0000000000706b03 in ConditionWait (lock=..., condition=...) at util/threadpool_imp.cc:97
#2  rocksdb::ThreadPoolImpl::BGThread (this=this@entry=0x1b71760, thread_id=thread_id@entry=1) at util/threadpool_imp.cc:175
#3  0x0000000000706d33 in rocksdb::BGThreadWrapper (arg=0x1b7f250) at util/threadpool_imp.cc:253
#4  0x00007f9b1382fdd5 in start_thread () from /lib64/libpthread.so.0
#5  0x00007f9b1269bead in clone () from /lib64/libc.so.6
例2:

类似compression造成CPU过高的问题:
可以关闭压缩来降低cpu压力,代价是磁盘占用的增加,但需要注意关闭、开启压缩是需要重启实例但无需重做数据。

二、sentinel 主从切换

pika-wather发送ping命令等待超时,认为节点不存活,会进行域名切换。

类型一、pika假死导致
1.操作大KEY导致。

用ZSET中使用ZRANG读取成员数特别大的key(10w+)会导致假死。
使用zrembyscore在删除一个key中的大量members,也会导致假死。
排查问题时需要注意:

  • 特别慢的命令直到关闭服务时始终没有执行完,从slowlog中是无法获取的。
  • 另外像zrange这种读命令是不会写到binlog中,只有写命令才会写binlog。
2.bgsave/flushall/flushdb/compact等命令导致

以flushdb为例:

127.0.0.1:9559> flushdb set
OK
(5.78s)
I0302 12:00:37.375150 187593 pika_server.cc:1565] Delete old set db...
I0302 12:00:37.860126 187593 pika_server.cc:1579] Prepare open new set db...
I0302 12:00:43.157172 187593 pika_server.cc:1582] open new set db success
I0302 12:00:43.157317 187600 pika_server.cc:1596] Delete dir: /tmp/pika9559/db/set_deleting start
I0302 12:00:43.489825 187600 pika_server.cc:1598] Delete dir: /tmp/pika9559/db/set_deleting done
I0302 12:00:54.713183 187600 pika_server.cc:1327] Success purge 2
同时执行set操作
127.0.0.1:9559> set test test
OK
(1.90s)

127.0.0.1:9559> flushdb set
OK
(7.53s)
127.0.0.1:9559> SADD runoobkeyc redis
(integer) 1
(6.66s)
3.rocksdb write stall

当flush/compaction赶不上write rate的速度时,rocksdb会降低write rate,甚至直接停写。
通过这几个地方你可以知道你数据库是不是在进行write stall:

  • Too many immemtable
    延缓写: 如果max_write_buffer_number 大于3, 将要flush的memtables大于等于max_write_buffer_number - 1, write 延缓

    停写: 如果将要flush 的memtable的个数大于等于max_write_buffer_number, write 直接停止等flush完成
    在以上情况下, 一般会有这样的日志:
    Stopping writes because we have 5 immutable memtables (waiting for flush), max_write_buffer_number is set to 5
    Stalling writes because we have 4 immutable memtables (waiting for flush), max_write_buffer_number is set to 5

  • Too many level-0 SST file

    延缓写: 如果L0的文件数量达到了level0_slowdown_writes_trigger,write 延缓写

    停写: 如果文件数量达到了level0_stop_writes_trigger, 直接停写直到L0->L1的compactiom减少了L0的文件数。
    在以上情况下, 会出现这样的日志
    Stalling writes because we have 4 level-0 files
    Stopping writes because we have 20 level-0 files

  • Too many pending compaction bytes

    延缓写: 如果要compation的的字节数达到soft_pending_compaction_bytes,延缓写

    停写: 如果该字节数目大于hard_pending_compaction_bytes, 直接停写
    以上两种情况时, 会出现这样的日志
    Stalling writes because of estimated pending compaction bytes 500000000
    Stopping writes because of estimated pending compaction bytes 1000000000

类型二、其他
1.主从延迟过大
127.0.0.1:9559> info Replication
# Replication(MASTER)
role:master
connected_slaves:1
slave0:ip=10.87.109.74,port=9669,state=online,sid=2,lag=705032146
2.宕机或实际的物理部署与配置中心不一致

三、处理磁盘容量过大

1.清理磁盘

备份清理:

pika备份是以硬链接db目录中的sst的方式产生的,因此在存在备份文件的情况下,一旦执行全量compact由于Pika db目录中的所有sst都会被compact“清洗”一遍(逐步将所有老的sst删除替换成新的sst),这将造成备份硬链接文件的体积变为真实体积,极端情况下备份文件会额外占用一倍的空间(如果你的磁盘空余空间不大,那么在执行全量compact之前最好删除备份)。

LOG清理:
  • 批量DEL, 在pika内部其实依次删除, 整体用时会偏长, 进入slowlog, 进一步加快日志的增加。
  • 大量日志产生磁盘IO, 磁盘读写lantcy长, 导致pika的读写性能有一定程度影响, 进入slowlog的命令较多。
  • 盲删的时候, 会去更改各个slot的key, 如果在某个slot下找不到相应的key, 会打相应的日志, 盲删的时候,日志会暴涨。
避免盲删数据:

在开了slot的前提下:
可以扫slot信息, slot信息只相当于数据的元信息, 这样可以大大减少所涉及到的文件, 依据线上一个大实例, 直接扫库需要扫描120G数据,对线上的其他读写影响比较大, 而扫slot信息的话, 最多只需要扫描10G数据. 拿到这些key之后, 可以根据自己的需要适当删除. 注意这些元信息的操作需要存储组的配合, 不要盲目操作。

2.手动compact

异常的数据体积(大于估算值10%以上),可以通过执行compact命令,在compact执行完毕后观察数据体积是否恢复正常。
全量compact的原理是逐步对rocksdb的每一层做数据合并、清理工作,在这个过程中会新增、删除大量的sst文件,因此在执行全量compact的时候可以发现数据体积先增大后减小并最终减小到一个稳定值(无效、重复数据合并、清理完毕仅剩有效数据),建议在执行compact前确保磁盘空余空间不低于30%,避免新增sst文件时将磁盘空间耗尽。
另外pika支持对指定数据结构进行compact,例如一个实例中已知hash结构的无效数据很少但hash结构数据量很大,set结构数据量很大且无效数据很多,在这个例子中hash结构的compact是没有必要的,你可以通过compact set实现仅仅对set结构的compact。

3.扩容迁移

扩容详细步骤待补
删除冗余数据步骤:

step1: slotscleanup 64 65 66...1023。
step2: 周期性检测info 中的is_slots_cleanuping:Yes,xx,xx, 直到Yes变成No。
step2.1: 如果触发slotscleanup后, 如果发现对业务影响比较大, 可以slotscleanupoff关闭, 等到合适的时间再删。
step3: slotsdel 64 65 66...1023, 并等待5s左右。
step4: 检查pika是否进行compact。若没有,则手动触发compact。周期性检查info中的is_compact是否完成,完成则退出,避免触发io风暴。
具体命令:

  • 删除[start,end]以外其他slot对应key:
    python slotscleanup.py slotscleanup ip port start,end
  • slotscleanupoff关闭当前进行的slotscleanup:
    python slotscleanup-2.py slotscleanupoff ip port
1.删除[start,end]以外其他slot对应key,且未使用slotscleanupoff关闭。

中间需要交互输入yes,确认本次有无执行slotscleanupoff。

[[email protected] tmp]# python slotscleanup-2.py slotscleanup 127.0.0.1 9559 0,4
----------starting print-----------stay slot 127.0.0.1:9559[0,4),start delete
start cleanup
   1) (integer) 4
   2) (integer) 5
   3) (integer) 6
......

1019) (integer) 1022
1020) (integer) 1023
cron until cleanup finied
tried 1 times, retried 60 * 5s later
tried 2 times, retried 60 * 5s later
tried 3 times, retried 60 * 5s later
tried 4 times, retried 60 * 5s later
Has all the key been deleted? input yes or no?yes//删除完所有key,会弹出提示。此时输入yes,表明未执行slotscleanupoff。
delete slots info
(integer) 1020
compacting now, tried 1 times, retried 60 * 1s later
compact start
OK
tried 1 times, retried 60 * 1s later
......
tried 89 times, retried 60 * 1s later
successfully
2.删除[start,end]以外其他slot对应key,过程中使用slotscleanupoff关闭此次删除。
[[email protected] tmp]# python slotscleanup-2.py slotscleanup 127.0.0.1 9559 0,4
----------starting print-----------stay slot 127.0.0.1:9559[0,4),start delete
start cleanup
   1) (integer) 4
   2) (integer) 5
   3) (integer) 6
......

1019) (integer) 1022
1020) (integer) 1023
cron until cleanup finied
tried 1 times, retried 60 * 5s later
tried 2 times, retried 60 * 5s later
tried 3 times, retried 60 * 5s later
tried 4 times, retried 60 * 5s later
Has all the key been deleted? input yes or no?no  //执行slotscleanupoff成功后,会弹出提示。输入no,表明未执行slotscleanupoff。
del remaining keys next time
successfully

在其他窗口slotscleanupoff,会触发上面脚本交互:

[[email protected] tmp]# python slotscleanup-2.py slotscleanupoff 127.0.0.1 9559
----------starting print-----------slotscleanupoff start
OK
# Stats
total_connections_received:66
instantaneous_ops_per_sec:0
total_commands_processed:191327535
is_bgsaving:No, 20200331111915, 0
is_slots_reloading:No, , 0
is_slots_cleanuping:No, 20200331162726, 0
is_scaning_keyspace:No
is_compact:No
compact_cron:
compact_interval:
0
----------slotscleanupoff end-------------

代码如下:

import os
import sys
import time

def IsCleanupStop(ip, port):
  cmd = "redis-cli -h %s -p %s info stats"%(ip, port)
  f = os.popen(cmd)
  for line in f.readlines():
    strs = line.strip().split(':')
    if len(strs) == 2 and strs[0] == "is_slots_cleanuping":
      substrs = strs[1].strip().split(',')
      if len(substrs) == 3 and substrs[0] == "No":
        return True
  return False

def IsCompactStop(ip, port):
  cmd = "redis-cli -h %s -p %s info stats"%(ip, port)
  f = os.popen(cmd)
  for line in f.readlines():
    strs = line.strip().split(':')
    if len(strs) == 2 and strs[0] == "is_compact":
      if strs[1] == "No":
        return True
  return False

def StartCleanup(ip, port, target):
  print "start cleanup"
  cmd = "redis-cli -h %s -p %s slotscleanup %s"%(ip, port, " ".join(target))
  os.system(cmd)
  time.sleep(5)

def WaitCleanupFinished(ip, port):
  print "cron until cleanup finied"
  count = 0
# cron until slotscleanup is finished
  while False == IsCleanupStop(ip, port):
    count = count + 1
    print "tried %d times, retried 60 * 5s later"%(count)
    time.sleep(5 * 60)

def DelSlotsInfo(ip, port, target):
# delete slots info
  print "delete slots info"
  cmd = "redis-cli -h %s -p %s slotsdel %s"%(ip, port, " ".join(target))
  os.system(cmd)
  time.sleep(1 * 60)

def Compact(ip, port):
  count = 0
  while False == IsCompactStop(ip, port):
    count = count + 1
    print "compacting now, tried %d times, retried 60 * 1s later"%(count)
    time.sleep(1 * 60)

  print "compact start"
  cmd = "redis-cli -h %s -p %s compact"%(ip, port)
  os.system(cmd)
  count = 0
  while False == IsCompactStop(ip, port):
    count = count + 1
    print "tried %d times, retried 60 * 1s later"%(count)
    time.sleep(1 * 60)


def DelRedundancy(ip, port, span):
  print('----------starting print-----------stay slot %s:%s[%s),start delete') % (ip,port,span)
  slots = span.split(',')

  if len(slots) != 2:
    return False
  range_start = int(slots[0].strip())
  range_end = int(slots[1].strip())

  if range_start >= range_end:
    range_start, range_end = range_end, range_start

  target= []
  for each in range(1024):
    if each < range_start or each >= range_end:
      target.append(str(each))

  if len(target) == 0:
    print "no slot to cleanup"
    return False

  # start slotscleanup
  StartCleanup(ip, port, target)
  WaitCleanupFinished(ip, port)

  isDelSlotsInfo = raw_input("Has all the key been deleted? input yes or no?")
  if (isDelSlotsInfo == "yes"):
    DelSlotsInfo(ip, port, target)
    Compact(ip, port)
  else:
    print("del remaining keys next time")
  return True

if __name__ == '__main__':
    if len(sys.argv) != 5 and len(sys.argv) != 4:
        print "./exe slotscleanup ip port start,end" #del slot in span(0,start) and (end,1024)
        print "./exe slotscleanupoff ip port"
        exit(0)

    if len(sys.argv) == 5 and sys.argv[1] == "slotscleanup":
        if True == DelRedundancy(sys.argv[2], sys.argv[3], sys.argv[4]):
            print "successfully"
        else:
            print "failed"

    if len(sys.argv) == 4 and sys.argv[1] == "slotscleanupoff":
        ip = sys.argv[2]
        port = sys.argv[3]
        print "----------starting print-----------slotscleanupoff start"
        cmd = "redis-cli -h %s -p %s slotscleanupoff"%(ip, port)
        os.system(cmd)
        cmd = "redis-cli -h %s -p %s info stats"%(ip, port)
        print os.system(cmd)
        print "----------slotscleanupoff end-------------"


四、超时排查

1.查看响应时段pika慢日志。

线上配置:

[[email protected] ~]$ redis-cli -h pika_cluster_user_profile_and_diandian_a0_9338 -p 9338 rnconfig get slowlog-max-len
1) "slowlog-max-len"
2) "1000"
[[email protected] ~]$ redis-cli -h pika_cluster_user_profile_and_diandian_a0_9338 -p 9338 rnconfig get slowlog-log-slower-than
1) "slowlog-log-slower-than"
2) “50000"

通过命令获取redis-cli -h pika_cluster_user_profile_and_diandian_a0_9338 -p 9338 slowlog get [n]

[[email protected] ~]$ redis-cli -h pika_cluster_user_profile_and_diandian_a0_9338 -p 9338 slowlog  get 2
1) 1) (integer) 8014
   2) (integer) 1585635640
   3) (integer) 69977
   4) 1) "DEL"
      2) "1:1:781634055:B160FFBD-3FBC-2218-C929-C2C6CF31347A20190424"
2) 1) (integer) 8013
   2) (integer) 1585631381
   3) (integer) 50200
   4) 1) "SET"
      2) "6:1:652466732"
      3) "{\"subTags\":{\"last_active_type_in_30days\":......
类型一:

查看在某一时间段,集中执行耗时比较多的请求,导致其他请求无法响应。如果慢日志过多,可以在pika.ERR中查找。
zrange,hkeys和hgetall的复杂度均为O(N)。确认是否是这些命令导致。
在数据量较大的情况下不推荐直接使用keys,hkeys,hgetall等获取全量数据的接口,可以使用scan,hscan加count来替代,将一次请求切分为多次来执行。
在使用Pika多数据结构(hash,list,zset,zset)的时候,尽量确保每个key中的field不要太多,如果业务类型为耗时敏感型那么建议每个key的field数量不要超过1万个,特大key可以考虑拆分为多个小key,这样可以避免超大key很多潜在的性能风险,而存储型业务(耗时不敏感)则没有这个要求。

类型二:

是否进行bgsave/flushall/flushadb这类操作。
在生成备份快照的时候,为了确保数据的一致性Pika会暂时阻塞写入,阻塞时间与实际数据量相关,根据测试500g的Pika生成备份快照也仅需50ms,在写入阻塞的过程中连接不会中断请求不会异常,但client会感觉到“在那一瞬间请求耗时增加了一些”。

类型三:

是否DBSIZE缓慢增长
处理 :
请求耗时突然异常增大,可以通过执行compact命令,在compact执行完毕后观察请求耗时是否恢复正常

类型四:直接查看rocksdb日志

过期或者大量删除类的操作导致,
底层DB在做大量的compaction,一次读写合并多个文件, 导致IO读写lantecy增加。

五、pika无法启动

有其他的pika进程持有了db
启动时报错 :open kv db failed, IO error: While lock file: ./db/strings/LOCK: Resource temporarily unavailable

安装protobuf
启动报错:fatal error: google/protobuf/message.h: No such file or directory

配置network-interface
err network interface:, please check!

六、监控上的有效信息

hubble 监控

[图片上传失败...(image-daf047-1589361032794)]

zabbix 监控

cpu:

[系统-CPU]CPU系统核心负载:各CPU利用率
[系统-CPU]CPU负载使用百分比:
load average: 1.15, 1.42, 1.44
load average后面的三个数分别是1分钟、5分钟、15分钟的负载情况。
load average数据是每隔5秒钟检查一次活跃的进程数,然后按特定算法计算出的数值。如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了。
查看逻辑CPU的个数(线程数)

cat /proc/cpuinfo |grep "processor"|wc -l

磁盘IO:

[系统-硬盘]IO平均耗时svctm:如果该项大于15ms,并且util%接近100%,那就说明,磁盘现在是整个系统性能的瓶颈了。
[系统-硬盘]IO使用百分比%util:一般地,如果该参数是100%表示设备已经接近满负荷运行了。
[系统-硬盘]读写耗时:读写磁盘耗时ms。
linux命令:

iostat -xk 1
Linux 2.6.33-fukai (fukai-laptop)          _i686_    (2 CPU)
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
5.47    0.50    8.96   48.26    0.00   36.82

Device:         rrqm/s   wrqm/s     r/s     w/s   rsec/s   wsec/s avgrq-sz avgqu-sz   await  svctm  %util
sda               6.00   273.00   99.00    7.00  2240.00  2240.00    42.26     1.12   10.57   7.96  84.40
sdb               0.00     4.00    0.00  350.00     0.00  2068.00     5.91     0.55    1.58   0.54  18.80

rrqm/s:          每秒进行 merge 的读操作数目。即 delta(rmerge)/s
wrqm/s:         每秒进行 merge 的写操作数目。即 delta(wmerge)/s
r/s:            每秒完成的读 I/O 设备次数。即 delta(rio)/s
w/s:            每秒完成的写 I/O 设备次数。即 delta(wio)/s
rsec/s:         每秒读扇区数。即 delta(rsect)/s
wsec/s:         每秒写扇区数。即 delta(wsect)/s
rkB/s:          每秒读K字节数。是 rsect/s 的一半,因为每扇区大小为512字节。(需要计算)
wkB/s:          每秒写K字节数。是 wsect/s 的一半。(需要计算)
avgrq-sz:       平均每次设备I/O操作的数据大小 (扇区)。delta(rsect+wsect)/delta(rio+wio)
avgqu-sz:       平均I/O队列长度。即 delta(aveq)/s/1000 (因为aveq的单位为毫秒)。
await:          平均每次设备I/O操作的等待时间 (毫秒)。即 delta(ruse+wuse)/delta(rio+wio)
svctm:          平均每次设备I/O操作的服务时间 (毫秒)。即 delta(use)/delta(rio+wio)
%util:          一秒中有百分之多少的时间用于 I/O 操作,或者说一秒中有多少时间 I/O 队列是非空的。即 delta(use)/s/1000 (因为use的单位为毫秒)

%util:一般地,如果该参数是100%表示设备已经接近满负荷运行了,可以通过优化程序或者通过更换更高、更快的磁盘来解决此问题。
%iowait(iostat),如果该值较高,表示磁盘存在I/O瓶颈。
svctm:平均每次设备I/O操作的服务时间 (毫秒),反应了磁盘的负载情况。如果该项大于15ms,并且util%接近100%,那就说明,磁盘现在是整个系统性能的瓶颈了。
await:平均每次设备I/O操作的等待时间 (毫秒)。一般地,应该低于5ms,如果大于10ms就比较大了。也要多和 svctm 来参考,正常情况下svctm应该是小于await值的,差的过高就一定有IO的问题。如果svctm比较接近await,说明I/O几乎没有等待时间;如果 await远大于svctm,说明I/O队列太长,应用得到的响应时间变慢。
avgqu-sz:如果I/O请求压力持续超出磁盘处理能力,该值将增加。如果单块磁盘的队列长度持续超过2,一般认为该磁盘存在I/O性能问题。(需要注意的是,如果该磁盘为磁盘阵列虚拟的逻辑驱动器,需要再将该值除以组成这个逻辑驱动器的实际物理磁盘数目,以获得平均单块硬盘的I/O等待队列长度。)

网络:

[系统网卡]bond0网卡流量:万兆网卡,流入流出流量
[系统网卡]bond0网卡丢包数量:drop细节 https://www.jianshu.com/p/38bc49e3a054

你可能感兴趣的:(pika常见故障排查)