首先,并不是说redis是内存应用就完全没性能问题,用的不好,还是会出现各种状况,例如RDB频繁,碎片太多等.

性能分析

info信息:

在redis-cli进入登录界面后,输入info all,或者redis-cli -h ${ip} -p ${post} -a "${pass}" -c info all,通常我们只输入info就够了,是简介模式的意思,info all是详细模式

之后,就会获取所有与Redis服务相关的实时性能信息,类似于linux命令top那样的东西.

info命令输出的数据可分为10个类别,分别是:server:服务器运行的环境参数

clients:    客户端相关信息

memory:    服务器运行内存统计数据

persistence:    持久化信息

stats:    通用统计数据

replication:    主从复制相关信息

cpu:    CPU使用情况

commandstats:    操作的统计信息

cluster:    集群信息

keyspace:    键值对统计数量信息

下面展开解析一些重点信息.

server 部分:

redis_version :     Redis 服务器版本,不同版本会有些功能和命令不同

arch_bits :     架构(32 或 64 位),某些情况,容易被忽略的坑

tcp_port :     TCP/IP 监听端口,确认你操作的对不对

uptime_in_seconds :     自 Redis 服务器启动以来,经过的秒数,可以确认有没有被重启过

uptime_in_days:     自 Redis 服务器启动以来,经过的天数,可以确认有没有被重启过

clients 部分:

connected_clients :     已连接客户端的数量(不包括通过从属服务器连接的客户端)

client_longest_output_list :     当前连接的客户端当中,最长的输出列表

client_longest_input_buf:     当前连接的客户端当中,最大输入缓存

blocked_clients :     正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量

memory部分:

maxmemory/maxmemory_human:    配置文件redis.conf限制的可分配的最大内存总量,当超过之后,就会触发LRU删除旧数据.

used_memory/used_memory_human:    当前redis-server实际使用的内存总量,如果used_memory >maxmemory ,那么操作系统开始进行内存与swap空间交换,以便腾出新的物理内存给新页或活动页(page)使用,那是有多糟糕大家可以想得到.

used_memory_rss/used_memory_rss_human:    从操作系统上显示已经分配的内存总量,也就是这个redis-server占用的系统物理内存实际值,比used_memory多出来的就可能是碎片.

mem_fragmentation_ratio:    内存碎片率,内存碎片率稍大于1是合理的,说明redis没有发生内存交换,如果内存碎片率超过1.5,那就说明Redis消耗了实际需要物理内存的150%,其中50%是内存碎片率。若是内存碎片率低于1的话,说明Redis内存分配超出了物理内存,操作系统正在进行内存交换。内存交换会引起非常明显的响应延迟.

下面是计算公式:

当碎片率出现问题,有3种方法解决内存管理变差的问题,提高redis性能:

1. 重启Redis服务器:如果内存碎片率超过1.5,重启Redis服务器可以让额外产生的内存碎片失效并重新作为新内存来使用,使操作系统恢复高效的内存管理。

2.限制内存交换: 如果内存碎片率低于1,Redis实例可能会把部分数据交换到硬盘上。内存交换会严重影响Redis的性能,所以应该增加可用物理内存或减少实Redis内存占用

3.修改内存分配器:

Redis支持glibc’s

malloc、jemalloc11、tcmalloc几种不同的内存分配器,每个分配器在内存分配和碎片上都有不同的实现。不建议普通管理员修改Redis默认内存分配器,因为这需要完全理解这几种内存分配器的差异,也要重新编译Redis。

used_memory_lua:    Lua脚本引擎所使用的内存大小。redis默认允许使用lua脚本,不过太多了的话就占用了可用内存

mem_allocator:    在编译时指定的Redis使用的内存分配器,可以是libc、jemalloc、tcmalloc.

persistence 部分:

RDB信息,redis的RDB的操作默认用到bgsave命令,是比较耗费资源的持久化操作,而且不是实时的,容易造成宕机数据消失,如果内存容量满了,不能做bgsave操作的话,隐患会很大.

rdb_changes_since_last_save :     距离最近一次成功创建持久化文件之后,经过了多少秒。持久化是需要占用资源的,在高负载下需要尽量避免持久化的影响,下列参数均有参考价值.

rdb_bgsave_in_progress:当前是否在进行bgsave操作。是为1

rdb_last_save_time :     最近一次成功创建 RDB 文件的 UNIX 时间戳。

rdb_last_bgsave_time_sec :     记录了最近一次创建 RDB 文件耗费的秒数。

rdb_last_bgsave_status:    上次保存的状态

rdb_current_bgsave_time_sec :     如果服务器正在创建 RDB 文件,那么这个域记录的就是当前的创建操作已经耗费的秒数。

AOF信息,AOF是持续记录命令到持久化文件的方法,比较节省资源,但是AOF存储文件会没有限制存储,时间一长,或者操作太频繁,那就会出现AOF文件过大,撑爆硬盘的事.而且,这个方法还是会定期bgsave操作.

aof_enabled:    AOF文件是否启用

aof_rewrite_in_progress:    表示当前是否在进行写入AOF文件操作

aof_last_rewrite_time_sec :     最近一次创建 AOF 文件耗费的时长。

aof_current_rewrite_time_sec :     如果服务器正在创建 AOF 文件,那么这个域记录的就是当前的创建操作已经耗费的秒数。

aof_last_bgrewrite_status:    上次写入状态

aof_last_write_status:    上次写入状态

aof_base_size :     服务器启动时或者 AOF 重写最近一次执行之后,AOF 文件的大小。

aof_pending_bio_fsync :     后台 I/O 队列里面,等待执行的 fsync 调用数量。

aof_delayed_fsync :     被延迟的 fsync 调用数量。

stats部分:

total_commands_processed:    显示了Redis服务处理命令的总数,且值是递增的.因为Redis是个单线程模型,客户端过来的命令是按照顺序执行的,如果命令队列里等待处理的命令数量比较多,命令的响应时间就变慢,甚至于后面的命令完全被阻塞,导致Redis性能降低.所以这个时候就需要记录这个参数的值,是不是增长过快,导致了性能降低.

instantaneous_ops_per_sec :     服务器每秒钟执行的命令数量,同上,如果增长过快就有问题。

expired_keys :     因为过期而被自动删除的数据库键数量,具有参考意义。

evicted_keys:    显示因为maxmemory限制导致key被回收删除的数量.根据配置文件中设置maxmemory-policy值来确定Redis是使用lru策略还是过期时间策略.如果是过期回收的,不会被记录在这里,通常这个值不为0,那就要考虑增加内存限制,不然就会造成内存交换,轻则性能变差,重则丢数据.

keyspace_hits:    查找成功的key总数量,也就是所有存在redis中的一个或多个key被查找过的总次数

keyspace_misses:    查找失败的key的总数量,和上面相反,一个或多个key并不存在redis中,但是被查找过的总次数,通常查找不到,应用就会去直连后端DB,造成缓存穿透

latest_fork_usec:     最近一次 fork() 操作耗费的微秒数.通常来说是指RDB持久化用的时间,fork()是很耗费资源的操作,所以要留意一下是不是太久,单位微妙,确保不要超过1秒.

rejected_connections:    由于达到maxclient限制而被拒绝的连接数.redis默认maxclient是1万,所以这个值一般不会有数值,如果有,那就是并发非常高了.

Replication部分:

role:    主从角色,主库是master,从库是slave

connected_slaves:    如果当前为主库,从库的个数

master_last_io_seconds_ago:    如果当前为从库,最近一次主从交互使用的秒数,切换的速度当然应该是要相当快,如果慢了,那就可能有其他问题了.

slave0~n:当前为主库的,其他从库信息

master_repl_offset:当前主库的数据偏移量,如果从库和这个值相差太远,则会主动从新同步主从

slave_repl_offset:当前从库的数据偏移量,如果主库和这个值相差太远,则会主动从新同步主从

slave_read_only: 从库只读标识,一般默认就是开启,可以手动关闭,但是不建议,如果一定要关闭,执行的数据将不会记录到日志,最终重启就丢失.

master_replid:主库的唯一ID值,主从关系建立后,master节点会生成一个随机的40位16进制字符,作为唯一标识,在4.0之前,这个值若变化,则会从新同步主从,

master_replid2:    redis4.0后新加参数ID值,如果master节点宕机了,slave节点成功切换为master后,会将之前master_replid记录的值存储到master_replid2中,自己生成一个新的随机字符,作为自己的master标识,存储在master_replid中.在4.0之后用了PSYNC2.0方式,master_replid2和second_repl_offset:无论主从,都表示自己上次主实例repid1和复制偏移量,用于兄弟实例或级联复制,主库故障切换psync,只做增量同步即可.

commandstats部分:

cmdstat_XXX:    记录了各种不同类型的命令的执行统计信息,命令包含有读有写,分得比较细,其中calls代表命令执行的次数,usec代表命令耗费的 CPU 时间,usec_per_call代表每个命令耗费的平均 CPU 时间,单位为微秒.对于排错有一定用途.

cluster部分:

如果使用了redis-cluster集群,就会有这部分信息,这里不详细说明.

keyspace部分:

所有DB的key数量,过期时间平均值,ttl信息.通常来说一个KEY内的数据量并不固定,所以只有参考意义,太多当然不好.

---------------------------------------------------------------------------------------------------

--stat:

算是info信息的整合结果,示例:redis-cli -h ${ip} -p ${post} -a "${pass}" --stat

keys       mem      clients blocked requests            connections

1955       8.19M    160     0       61962740 (+2621)    5741

1841       8.22M    160     0       61965176 (+2436)    5741

1983       8.23M    160     0       61967823 (+2647)    5741

解析:

keys:当前key总数;

mem:内存使用;

clients:当前连接client数;

blocked:阻塞数;

requests:累计请求数;

connections:累计连接数

-------------------------------------------------------------------------------------------

通用术语解析:

1.缓存穿透

缓存通常是通过后端持久化存储的DB(mysql/mongodb/hbase等)查询数据,然后加载到内存,从而增加访问速度的概念.从一般代码层逻辑来说,当在缓存找不到对应的key和value时,就会主动去直接查询后端DB,这个时候缓存就不起作用了,压力都会打在后端的DB上,仿佛就是被穿透了一样.

原因有很多种:

A.本来就没加载到缓存,但是又突然来了频繁查询,只能穿透到DB,这个时候应该考虑的是我们加载缓存的策略不太管用导致,例如数据预热不到位导致.

B.LRU原则被删了,但是对缓存key的访问并没有停止,导致穿透,不用说了,就是内存不够用.

C.过期时间到了自动删除了,和第一种情况类似,我们要考虑缓存加载策略.

D.后端DB对应这个key的数据修改太过于频繁,新的值没加载到缓存就又被访问了,也是和第一种情况类似,我们要考虑缓存加载策略.

E.被攻 击了,引用了本来就不存在key,直接穿透到DB,这种情况只能在代码层限制他们访问的key值

通常来说,少量的穿透是被允许的,毕竟理论上来说,把后端DB的所有数据加载到内存并不现实.如果出现大量的穿透,那就肯定是有问题了,可能是代码的逻辑有问题,也可能是DB设计有问题.解决的方案大部分情况要在代码层解决,要么就是加大内存来做缓存.

2.缓存失效

当缓存服务器重启或者没有预热导致大量缓存集中在某一个时间段没有加载到缓存,穿透至后端DB,有或者是大量的key同一时间过期,导致大量key删除和重写,这样就叫缓存失效.这会给后端系统(比如DB)带来很大压力,因为要把这些数据重新加载到缓存。

解决方案要从代码层去做,首先我们要合理做好缓存的预热,然后对于不同的key,设置不同的过期时间,对于批量的key让缓存失效的时间点尽量平滑过渡,即使一定要同时过期,也要设置一个时间段内相对随机的过期时间,不会挤在一起。

3.缓存雪崩

通常是针对缓存集群来说,意思是说一个很大的量打到缓存上,然后这个缓存节点挂掉了,但是这些量没有停止,继续打到集群内部的其他节点上,再导致其他缓存节点也挂掉了,最终导致整个缓存集群失效,变成了雪崩效应.

这种情况在一些异常大量的情况经常出现,例如微博的某些明星出轨,导致微博某条记录访问量巨大,就很有可能造成缓存雪崩.解决方法只能是做好早期预案,或者说留有足够的余地空间,又或者开发一套可以迅速动态扩容的架构.

4.缓存命中率

过低的缓存命中率就证明这个缓存服务经常要到DB加载数据到缓存里,由于DB的IO,延时代价都远高于缓存,所以低命中率是系统性能不高的一个标识.

通常优秀的缓存命中率都应该在90%以上,微博的缓存命中率甚至达到了99.5%,这是相当优秀的数据.不过高缓存命中率通常也会有副作用,因为过高的缓存命中率也代表key值的修改不一定能及时落到硬盘,因为难免是一个异步写入过程,就是说数据一致性并不严谨,可能有些丢了,有些多了,要综合考虑.

计算公式如下:

缓存命中率 = keyspace_hits / (keyspace_hits + keyspace_misses)

一般来说,缓存命中率在QPS高的时候更体现其性能,影响他的因素多数情况是和key的过期时间,代码加载DB数据逻辑有关,QPS低的时候,缓存命中率可以要求不太高.

其他问题的分析方法:

查看redis的网络延时:

网络延时对于redis性能影响是巨大的,我们都知道内存处理数据的速度是非常快的,快到甚至可以忽略误差.所以网络延时0.1毫秒,1毫秒,10毫秒看似相差不大,但是就等于是10倍和100倍的差别.

Redis的延迟数据是无法从info信息中获取的。倘若想要查看延迟时间,可以用Redis-cli工具加--latency参数运行,可以选择不指定密码,毕竟也只是测试网络,redis-cli -h 10.1.2.11 -p 6379 -a 123 --latency

他将会持续扫描延迟时间,直到按ctrl+C退出,以毫秒为单位测量Redis的响应延迟时间,由于服务器不同的运行情况,延迟时间可能有所误差,通常1G网卡的延迟时间是0.2毫秒,若延时值远高于这个参考值,那就有可能是有性能问题了。这个时候我们要考虑检查一下网络状况.

注意:网络延时的问题需要综合考虑,因为不同的网络情况不尽相同,例如同交换机,同内网,跨城,集群化等等.一般来说,同交换机单机redis的网络延时应该少于0.2毫秒,但是一个codis集群的网络延时可能就去到2毫秒,看起来没差多少,其实也有十倍,加上程序客户端的网络延时,相差就可能30倍,所以我们部署redis应用时,必须把网络状况也考虑进去.

查看redis的慢查询:

Slow

log 是 Redis

用来记录查询执行时间的日志系统。Redis中的slowlog命令可以让我们快速定位到那些超出指定执行时间的慢命令,默认情况下会记录执行时间超过10ms的记录到日志,由参数slowlog-log-slower-than控制.最多记录128条,由参数slowlog-max-len控制,超过就自动删除.

通常这个默认参数够用,也可以在线CONFIG SET修改参数slowlog-log-slower-than和slowlog-max-len来修改这个时间和限制条数。

通常1gb带宽的网络延迟,预期在0.2ms左右,倘若一个命令仅执行时间就超过10ms,那比网络延迟慢了近50倍。可以通过使用Redis-cli工具,输入slowlog

get命令查看,返回结果的第三个字段以微妙位单位显示命令的执行时间。假如只需要查看最后3个慢命令,输入slowlog get 10即可。127.0.0.1:6379> slowlog get 10

.

.

.

4) 1) (integer) 215

2) (integer) 1489099695

3) (integer) 11983

4) 1) "SADD"

2) "USER_TOKEN_MAP51193"

3) "qIzwZKBmBJozKprQgoTEI3Qo8QO2Fi!4"

5) 1) (integer) 214

2) (integer) 1489087112

3) (integer) 18002

4) 1) "SADD"

2) "USER_TOKEN_MAP51192"

3) "Z3Hs!iTUNfweqvLLf!ptdchSV2JAOrrH"

6) 1) (integer) 213

2) (integer) 1489069123

3) (integer) 15407

4) 1) "SADD"

2) "USER_TOKEN_MAP51191"

3) "S3rNzOBwUlaI3QfOK9dIITB6Bk7LIGYe"

1=日志的唯一标识符

2=被记录命令的执行时间点,以 UNIX 时间戳格式表示

3=查询执行时间,以微秒为单位。例子中命令使用11毫秒。

4= 执行的命令,以数组的形式排列。完整命令是拼在一起.

捕捉所有操作语句:

有时候我们想捕捉所有的执行语句来分析问题,就像mysql的general_log的概念,我们就可以用以下命令来捕捉.redis-cli -h 10.1.2.11 -p 6379 -a 123 monitor

这个方法主要用于调试代码,也有助于排查一些疑难杂症的用途,但是有一个相当不好的隐患,就是这个命令本身也是占用内存资源的,如果长期开启,redis-server的内存也是容易撑爆,最少也是会影响并发能力,所以要按需开启.

监控客户端的连接:

因为Redis是单线程模型(只能使用单核),来处理所有客户端的请求, 但由于客户端连接数的增长,处理请求的线程资源开始降低分配给单个客户端连接的处理时间,这时每个客户端需要花费更多的时间去等待Redis共享服务的响应。#查看客户端连接状态

127.0.0.1:6379> info clients

# Clients

connected_clients:11

client_longest_output_list:0

client_biggest_input_buf:0

blocked_clients:0

第一个字段(connected_clients)显示当前实例客户端连接的总数,Redis默认允许客户端连接的最大数量是10000。若是看到连接数超过5000以上,那可能会影响Redis的性能。倘若一些或大部分客户端发送大量的命令过来,这个数字会低的多。

查当前客户端状态#查看所有正在连接的客户端状态,

127.0.0.1:6379> client list

id=821882 addr=10.25.138.2:60990 fd=8 name= age=53838 idle=24 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=ping

这个看起来有点绕,因为和历史数据是混在一起的:

addr:客户端的地址和端口,包含当前连接和历史连接

age:这个客户端连进来的生命周期,也就是连进来之后的持续时间,单位是秒

idle:这个客户端的空闲时间,也就是说这个时间内,客户端没有操作,单位是秒

db:操作的数据库,redis默认有db0~db15可用选择

cmd:客户端最后一次使用的命令

也就是说,idle越少,那就代表这个客户端刚刚操作,反则是历史记录而已.age越少就代表是刚刚建立的连接,越大则是历史连接.而有些时候个别使用者用了scan或keys命令,会对数据量大的redis造成很大的负载压力,所以需要特别关注.

特大key的统计:

在redis的单线程处理方式下,一些数据量比较大的key的操作明显是会影响性能,所以必要时,我们要统计出来,交给开发来优化#统计生产上比较大的key

redis-cli -h* -a* -p* --bigkeys

#查看某个key的持续时间

127.0.0.1:6379> OBJECT IDLETIME key名字

--bigkeys信息解析:

1.该命令使用scan方式对key进行统计,所以使用时无需担心对redis造成阻塞。

2.输出大概分为两部分,summary之上的部分,只是显示了扫描的过程。summary部分给出了每种数据结构中最大的Key,所以下面部分更重要些。

3.统计出的最大key只有string类型是以字节长度为衡量标准的。list,set,zset等都是以元素个数作为衡量标准,不能说明其占的内存就一定多,需要另外计算。

得出最大key名字后,就去看看粗略大小,#查看某个key序列化后的长度

debug object key

输出的项的说明:Value at:key的内存地址

refcount:引用次数

encoding:编码类型

serializedlength:经过压缩后的序列化长度,单位是 B, 也就是 Byte(字节),因为压缩效果要看编码类型,不一定反应内存中的大小,只是有参考价值.

lru_seconds_idle:空闲时间

终上所述,我们要关注的大key信息正是serializedlength的长度了.

另外还有一个工具[rdbtools],可以全面分析redis里面的key信息,但是要额外安装,对于内网用户不可谓不麻烦,因为不是系统自带的功能,这里不详细说明,请等待另一篇文章另外介绍.

数据持久化引发的延迟

Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:

1.AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响

2.AOF + fsync every second是比较好的折中方案,每秒fsync一次

3.AOF + fsync never会提供AOF持久化方案下的最优性能,使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置

4.每一次RDB快照和AOF

Rewrite都需要Redis主进程进行fork操作。fork操作会生成一个内存镜像,生成的期间会锁定整个redis的写操作,从而产生耗时,会与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF

Rewrite时机,避免过于频繁的fork带来的延迟.

例如:Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝24GB / 4kB * 8 = 48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。

可以通过INFO命令返回的latest_fork_usec字段查看上一次fork操作的耗时(微秒)。

Swap引发的延迟

上面说,如果redis把物理内存用光之后(无论是实际用光还是碎片撑到用光),Linux系统会将Redis所用的内存分页移至swap空间,这会阻塞Redis进程读写,导致Redis出现不正常的延迟,而且swap绝大部分情况下就是硬盘,速度更加是和内存差很远。如果同一台服务器有别的业务的话,也有可能互相影响到,Swap的使用通常在物理内存不足或一些进程在进行大量I/O操作时发生,所以应尽可能避免上述两种情况的出现。

在/proc/redis进程号/smaps文件中会保存进程的swap记录,通过查看这个文件,能够判断Redis的延迟是否由Swap产生。如果这个文件中记录了较大的Swap size,则说明延迟很有可能是Swap造成的。

例子如下,可以看到当前分配给/usr/local/bin/redis-server的swap的数据量size是1220KB,但是swap的状态是0KB,也就是没用到swap,把这些swap不为0的项加起来即是当前使用的swap数据量.#/proc/pid/smaps显示了进程运行时的内存影响,系统的运行时库(so),堆,栈信息均可在其中看到。

cat /proc/`ps aux |grep redis |grep -v grep |awk '{print $2}'`/smaps

00400000-00531000 r-xp 00000000 fc:02 805438521 /usr/local/bin/redis-server

Size:               1220 kB

Rss:                 924 kB

Pss:                 924 kB

Shared_Clean:          0 kB

Shared_Dirty:          0 kB

Private_Clean:       924 kB

Private_Dirty:         0 kB

Referenced:          924 kB

Anonymous:             0 kB

AnonHugePages:         0 kB

Shared_Hugetlb:        0 kB

Private_Hugetlb:       0 kB

Swap:                  0 kB

SwapPss:               0 kB

KernelPageSize:        4 kB

MMUPageSize:           4 kB

Locked:                0 kB

过期的key没有被删除:

听起来很诡异,过期了为什么不会被删除?是的,很奇怪,但是这个redis的策略有关.

redis对于过期键有三种清除策略:

1.被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key

2.主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key

3.当前已用内存超过maxmemory限定时,触发主动清理策略

被动删除是只有key被客户端操作时(如get等操作),redis-server才会被动检查该key是否过期,如果过期则删除之并且返回NIL.

这种删除策略对CPU是友好的,删除操作只有在key被访问到的时候才会占用资源,不会对其他的expire key上浪费无谓的CPU时间.

但是这种策略对内存不友好,因为如果key已经过期,但是在它被操作之前是不会被删除的,仍然占据内存空间.如果有大量的过期键存在但是又很少被访问到,那会造成大量的内存空间浪费.因为可能存在一些key永远不会被再次访问到,这些设置了过期时间的key也是需要在过期后被删除的,我们甚至可以将这种情况看作是一种内存泄露—-无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息.

主动删除在 Redis 中, 常规操作由 redis.c/serverCron 实现, 它主要执行以下操作

1.更新服务器的各类统计信息,比如时间、内存占用、数据库占用情况等.

2.清理数据库中的过期键值对.

3.对不合理的数据库进行大小调整.

4.关闭和清理连接失效的客户端.

5.尝试进行 AOF 或 RDB 持久化操作.

6.如果服务器是主节点的话,对附属节点进行定期同步.

7.如果处于集群模式的话,对集群进行定期同步和连接测试.

Redis 将 serverCron 作为时间事件来运行, 从而确保它每隔一段时间就会自动运行一次, 又因为 serverCron 需要在 Redis 服务器运行期间一直定期运行, 所以它是一个循环时间事件: serverCron 会一直定期执行,直到服务器关闭为止.

在 Redis 2.6 版本中, 程序规定 serverCron 每秒运行 10 次, 平均每 100 毫秒运行一次. 从 Redis 2.8 开始, 用户可以通过修改 hz选项来调整 serverCron 的每秒执行次数, 具体信息请参考 redis.conf 文件中关于hz选项的说明也叫定时删除,这里的“定期”指的是Redis定期触发的清理策略,由位于src/redis.c的activeExpireCycle(void)函数来完成.serverCron是由redis的事件框架驱动的定位任务,这个定时任务中会调用activeExpireCycle函数,针对每个db在限制的时间REDIS_EXPIRELOOKUPS_TIME_LIMIT内迟可能多的删除过期key,之所以要限制时间是为了防止过长时间 的阻塞影响redis的正常运行.这种主动删除策略弥补了被动删除策略在内存上的不友好.

因此,Redis会周期性的随机测试一批设置了过期时间的key并进行处理.测试到的已过期的key将被删除.典型的方式为,Redis每秒做10次如下的步骤:

1.随机测试100个设置了过期时间的key

2.删除所有发现的已过期的key

3.若删除的key超过25个则重复步骤1

这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下.这也意味着在任何给定的时刻已经过期但仍占据着内存空间的key的量最多为每秒的写操作量除以4.

Redis-3.0.0中的默认值是10,代表每秒钟调用10次后台任务.

hz调大将会提高Redis主动淘汰的频率,如果你的Redis存储中包含很多冷数据占用内存过大的话,可以考虑将这个值调大,但Redis作者建议这个值不要超过100.我们实际线上将这个值调大到100,观察到CPU会增加2%左右,但对冷数据的内存释放速度确实有明显的提高(通过观察keyspace个数和used_memory大小).

从以上的分析看,当redis中的过期key比率没有超过25%之前,提高hz可以明显提高扫描key的最小个数.假设hz为10,则一秒内最少扫描200个key(一秒调用10次*每次最少随机取出20个key),如果hz改为100,则一秒内最少扫描2000个key;另一方面,如果过期key比率超过25%,则扫描key的个数无上限,但是cpu时间每秒钟最多占用250ms.

当REDIS运行在主从模式时,只有主结点才会执行上述这两种过期删除策略,然后把删除操作”del key”同步到从结点.

超过限定最大内存被删除就是主动调用LRU配置的原则来删除

volatile-lru:只对设置了过期时间的key进行LRU(默认值)

allkeys-lru : 删除lru算法的key

volatile-random:随机删除即将过期key

allkeys-random:随机删除

volatile-ttl : 删除即将过期的

noeviction : 永不过期,返回错误

当mem_used内存已经超过maxmemory的设定,对于所有的读写请求,都会触发redis.c/freeMemoryIfNeeded(void)函数以清理超出的内存.注意这个清理过程是阻塞的,直到清理出足够的内存空间.所以如果在达到maxmemory并且调用方还在不断写入的情况下,可能会反复触发主动清理策略,导致请求会有一定的延迟.

清理时会根据用户配置的maxmemory-policy来做适当的清理(一般是LRU或TTL),这里的LRU或TTL策略并不是针对redis的所有key,而是以配置文件中的maxmemory-samples个key作为样本池进行抽样清理.maxmemory-samples在redis-3.0.0中的默认配置为5,如果增加,会提高LRU或TTL的精准度,redis作者测试的结果是当这个配置为10时已经非常接近全量LRU的精准度了,并且增加maxmemory-samples会导致在主动清理时消耗更多的CPU时间.

当然了,触发了这个策略,其实就是内存不足,考虑那么多其实不如早点想着扩容比较实际,毕竟误删一些生产确实用到的key,并不是好事.

看完上面的介绍,就清楚了为什么会出现过期了但是又没被删除的key是什么回事,在一些高并发环境尤为明显,然后如果想解决问题,其实在3.0之后的版本可以尝试调大参数hz来实现.而上面第一条也说了,如果主动get,是可以触发删除的,例如我们主动执行脚本scan来实现#用scan扫面10000个key信息

SCAN 0 match * count 10000

然后具体操作.

内存满了怎么办:

首先我们要了解,redis的内存满,并不代表用完maxmemory的设置或者是用了系统的100%内存,为什么这么说呢?因为我们还要计算内存碎片率,实际占用内存看哪里,上面已经说了,内存满了的定义,并不一定只是实际内存占用,碎片也是要包含在内的,例如:#这种情况,肯定就是内存满

used_memory_human:4.0G

maxmemory_human:4.00G

#但是这种情况,也是内存满

used_memory_human:792.30M

used_memory_rss_human:3.97G

used_memory_peak_human:4.10G

maxmemory_human:4.00G

因为内存碎片没有被释放,那它还是会占用内存空间,对于系统来说,碎片也是redis-server占用的内存,不是空闲的内存,那剩下的内存还是不足以用来bgsave.那怎么解决碎片呢?这个后面说.

然后,redis做持久化是save和bgsave,而常用也是默认的bgsave是fork一个进程,对比已经落盘的rdb数据和内存的数据的差异,把差异的部分内存数据copy一份再压缩后存到硬盘成tmp-*.rdb文件,最后合并到原来的rdb文件上.29992:S 04 Jan 08:38:12.094 * 1 changes in 3600 seconds. Saving...

29992:S 04 Jan 08:38:12.167 * Background saving started by pid 11729

11729:C 04 Jan 08:38:23.201 * DB saved on disk

11729:C 04 Jan 08:38:23.260 * RDB: 19 MB of memory used by copy-on-write

29992:S 04 Jan 08:38:23.379 * Background saving terminated with success

上面的例子显示,当次的RDB用了19MB的内存,因为只有这些数据量有新增和更改.

正常来说fork操作的原理是把内存空间做一份镜像,那严格来说,只要你的内存超过系统内存的50%,那就可以被称为redis内存满了,因为要用同等空间来建立fork内存镜像.要是结合上面说的,碎片过多的话,系统剩余可用内存空间不足50%的话,那么RDB就会失败报错.

然后redis的持久化策略,上面只说了持久化会有一瞬间阻塞操作导致延时,而如果内存满了,数据量的增加会让持久化造成的延时会更严重,而且持久化失败后是默认每分钟重试一遍,如下面日志所示.725:M 01 Jan 02:17:53.288 * Slave 10.233.8.159:6379 asks for synchronization

725:M 01 Jan 02:17:53.288 * Full resync requested by slave 10.233.8.159:6379

725:M 01 Jan 02:17:53.288 * Starting BGSAVE for SYNC with target: disk

725:M 01 Jan 02:17:53.288 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:17:53.288 # BGSAVE for replication failed

725:M 01 Jan 02:17:59.092 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:17:59.092 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:05.017 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:05.017 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:11.032 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:11.032 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:17.054 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:17.055 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:23.068 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:23.069 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:29.092 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:29.092 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:35.007 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:35.007 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:41.027 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:41.027 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:47.043 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:47.043 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:53.068 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:18:53.068 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:54.366 * Slave 10.233.8.159:6379 asks for synchronization

725:M 01 Jan 02:18:54.366 * Full resync requested by slave 10.233.8.159:6379

725:M 01 Jan 02:18:54.366 * Starting BGSAVE for SYNC with target: disk

725:M 01 Jan 02:18:54.367 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:18:54.367 # BGSAVE for replication failed

725:M 01 Jan 02:19:00.093 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:00.093 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:06.011 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:06.012 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:12.044 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:12.044 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:18.067 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:18.067 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:24.002 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:24.002 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:30.028 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:30.028 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:36.058 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:36.058 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:42.077 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:42.077 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:48.100 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:48.100 # Can't save in background: fork: Cannot allocate memory

725:M 01 Jan 02:19:54.033 * 1 changes in 3600 seconds. Saving...

725:M 01 Jan 02:19:54.033 # Can't save in background: fork: Cannot allocate memory

那么问题就来了,因为内存满了,持久化失败,然后一分钟后再持久化,就造成了恶性循环,redis的性能直线下降,甚至中断.那怎么办好呢?

更改持久化策略是个临时解决方案,可以先尝试关闭持久化失败导致的终止所有的客户端write请求的选项,config set stop-writes-on-bgsave-error no

但是这个方法治标不治本,我们只是忽略了问题而已,另一种解决方案就是直接把rdb持久化关闭掉:config set save ""

为什么能解决,答案也很明显,关闭了持久化,那就不会再有bgsave操作,也就不会阻塞其他操作,那redis的性能还是保证到了.但是又会引入新问题,没了持久化,内存数据如果redis-server程序重启或关闭就没了,还是比较危险的.而且内存满的问题还在,如果内存用到了系统内存100%,甚至触发了系统的OOM,那就坑大了,因为内存被彻底清空,数据也都没有了.也就是所谓的临时解决办法.

所以正确的做法是,在不阻塞操作之后,删掉可以删除的数据,再重新拉起持久化,或者先扩大swap来暂时使用,然后准备扩容的工作.

至于怎么解决碎片问题,在redis4.0之前,只能等待内存碎片的内存空间超时被系统回收,这个时间一般比较漫长,视乎你的key的数量有多少,如果key的数据量多就更慢,没其他更好办法,想快就只能重启redis-server.在redis4.0之后的新版本则新加了一个碎片回收参数,杜绝了这个问题.

碎片问题其实影响颇大,因为正常情况下,这些内存碎片一但产生是不能被redis或者其他应用使用的,只能等待回收.造成用不了又确实占着内存的数据,会让我们的redis浪费空间之余,还会额外造成内存满的风险.所以也正如上面说的那样,如果碎片率超过1.5之后,是该想想回收一下.

那确实需要保存内存数据怎么办?要么就是忍痛割爱,把不必要的数据删除掉,让内存降到能做bgsave之后再重启来回收碎片.要么就是临时扩大swap,暂时用来存储bgsave数据.要想根本解决,还是要升级到4.0之后才能避免同类问题.

redis4.0之后的碎片回收方法有两种:

第一种:直接手动命令memory purgeredis-cli -p 6379 -a 123 memory purge

它会以一个I/O事件的形式注册到主线程当中去执行。值得注意的是,它和参数activedefrag回收的并不是同一块区域的内存,它尝试清除脏页以便内存分配器回收使用

第二种:启用内存碎片回收配置activedefragredis-cli -p 6379 -a 123 CONFIG SET activedefrag yes

但是,无论是前者还是后者,都会对redis增加负载,甚至卡死操作,因为碎片整理会涉及内存数据迁移的操作,必然会锁定key的写操作,所以不能长期开启,用完就要关掉,而且还要在晚上做.至于activedefrag涉及的其他参数,则需要另外更改.

优化建议

系统优化

1.关闭Transparent huge pages

Transparent

HugePages会让内核khugepaged线程在运行时动态分配内存。在大部分linux发行版本中默认是启用的,缺点是可能会造成内存在运行时的延迟分配,对于大内存应用并不友好,例如:oracle,redis等会占用大量内存的应用,所以建议关闭。#关闭Transparent HugePages,默认状态是[always]

echo never > /sys/kernel/mm/transparent_hugepage/enabled

2.修改 vm.overcommit_memory 参数

解析:0 表示检查是否有足够的内存可用,如果是,允许分配;如果内存不够,拒绝该请求,并返回一个错误给应用程序。

1 允许分配超出物理内存加上交换内存的请求

2 内核总是返回 true

在做bgsave时,RDB文件写的时候会先fork一个子进程,相当于复制了一个内存镜像.当系统的内存是8G,而redis占用了近 5G的内存时,由于剩下的内存不足够fork一个镜像空间,因此肯定会报内存无法分配而失败.默认情况下,vm.overcommit_memory设置为 0,在可用内存不足的情况下,

就无法分配新的内存。如果vm.overcommit_memory设置为1。 那么redis将使用交换分区swap来做fork,虽然速度会慢一些,但是至少是不会失败了。#修改内核参数

vi /etc/sysctl

#设置,没有就添加一行

vm.overcommit_memory = 1

#然后执行

sysctl -p

当然了,使用交换内存swap并不是一个完美的方案,有些服务器在分配时甚至就没有swap。所以,最好的办法是扩大物理内存,或者合理分配合适的内存空间,让LRU规则来淘汰旧key。

3.在物理机部署redis,这点不用多说了,虚拟机或者是docker的内存都会有一定的延时,没有必要为了好管理而浪费这些性能。

4.多用连接池,而不是频繁断开再连接,效果我想不言而喻。

5.客户端进行的批量数据操作,应使用Pipeline特性在一次交互中完成。

6.当并发和QPS都非常大的情况下,应该改用redis-cluster或者codis这些高性能redis集群

行为优化

1.假如缓存数据小于4GB,可以选择使用32位的Redis实例。因为32位实例上的指针大小只有64位的一半,它的内存空间占用空间会更少些。Redis的dump文件在32位和64位之间是互相兼容的,

因此倘若有减少占用内存空间的需求,可以尝试先使用32位,后面再切换到64位上。

2.尽可能的使用Hash数据结构。因为Redis在储存小于100个字段的Hash结构上,其存储效率是非常高的。所以在不需要集合(set)操作或list的push/pop操作的时候,尽可能的使用Hash结构。Hash结构的操作命令是HSET(key,

fields, value)和HGET(key, field),使用它可以存储或从Hash中取出指定的字段。

3.尽量设置key的过期时间。一个减少内存使用率的简单方法就是,每当存储对象时确保设置key的过期时间。倘若key在明确的时间周期内使用或者旧key不大可能被使用时,就可以用Redis过期时间命令(expire,expireat,

pexpire,

pexpireat)去设置过期时间,这样Redis会在key过期时自动删除key.用ttl命令可以查询过期时间,单位是秒,显示-2代表key不存在,显示-1代表没有设置超时时间(也就是永久的).

4.使用多参数命令:若是客户端在很短的时间内发送大量的命令过来,会发现响应时间明显变慢,这由于后面命令一直在等待队列中前面大量命令执行完毕。举例来说,循环使用LSET命令去添加1000个元素到list结构中,是性能比较差的一种方式,更好的做法是在客户端创建一个1000元素的列表,用单个命令LPUSH或RPUSH,通过多参数构造形式一次性把1000个元素发送的Redis服务上。

5.管道命令:另一个减少多命令的方法是使用管道(pipeline),把几个命令合并一起执行,从而减少因网络开销引起的延迟问题。因为10个命令单独发送到服务端会引起10次网络延迟开销,使用管道会一次性把执行结果返回,仅需要一次网络延迟开销。Redis本身支持管道命令,大多数客户端也支持,倘若当前实例延迟很明显,那么使用管道去降低延迟是非常有效的。

6.避免操作大集合的慢命令:如果命令处理频率过低导致延迟时间增加,这可能是因为使用了高时间复杂度的命令操作导致,这意味着每个命令从集合中获取数据的时间增大。 所以减少使用高时间复杂的命令,能显著的提高的Redis的性能。

7.限制客户端连接数:自Redis2.6以后,允许使用者在配置文件(Redis.conf)maxclients属性上修改客户端连接的最大数,也可以通过在Redis-cli工具上输入config

set maxclients

去设置最大连接数。根据连接数负载的情况,这个数字应该设置为预期连接数峰值的110%到150之间,若是连接数超出这个数字后,Redis会拒绝并立刻关闭新来的连接。通过设置最大连接数来限制非预期数量的连接数增长,是非常重要的。另外,新连接尝试失败会返回一个错误消息,这可以让客户端知道,Redis此时有非预期数量的连接数,以便执行对应的处理措施。

上述二种做法对控制连接数的数量和持续保持Redis的性能最优是非常重要的

安全优化

1.数据安全优化

首先我们知道,对于redis的数据更新频率总是非常高的,如果过于频繁的持久化RDB或者AOF其实对性能影响都挺大,但是如果直接关闭,数据安全又大打折扣,所以我们要根据实际情况来定义最佳的数据安全的持久化方案.

通常来说如果负载不高,怎么做都没所谓,而数据更改过于频繁,反而是不适用AOF,定期做RDB反而是更适合,例如下面这样就足够了:#设置1小时内,redis有一次更改就做一次RDB

config set save '3600 1'

这样对于高负载的环境,既保证了负载能力,也能避免redis完全挂掉时,没有一丝的备份,当然,你能设置更长时间,取决于你能接受丢多少数据,这里是1小时内.但是请记住留有足够的内存空间来做RDB.

2.命令权限优化

虽然redis不支持用户控制,也没有角色的概念,但是并不代表完全不能做安全优化.还记得redis.conf里面有一个配置rename-command么,我们就要用他来做控制.#打开配置文件

vim redis.conf

#直接禁用下列这些命令

rename-command FLUSHALL ""

rename-command FLUSHDB  ""

rename-command CONFIG   ""

rename-command KEYS     ""

#把下列这些命令更改成特定的hash字符串

rename-command FLUSHALL 4be35d96-cd3a-494b-842f-89ab59c0dffd

rename-command FLUSHDB  fe274238-4d4e-44c0-942b-4054a8b655c6

rename-command CONFIG   74efbde8-5394-40a9-8c7f-5d5e2d9f1907

rename-command KEYS     12b8e8f6-787a-4c64-9ea2-24157a024e9f

这个操作只能在配置文件实现,不能动态config set来修改.

这样一顿配置下,只要你不说,别人就不知道怎么用,也就不存在误操作和手贱的说法了,安全性就提高了很多了.

redis提高oracle性能,redis性能分析与优化建议相关推荐

  1. 大型网站性能监测、分析与优化常见问题QA

    @tanwen110 (唐文),曾负责腾讯四大平台之一网络媒体平台的整体运维.运营规划工作:曾任百度T7架构师和百度性能优化TOPIC.百度UAQ.APM平台负责人:畅销书<海量运维.运营规划之 ...

  2. 记一次数据库的分析和优化建议(r6笔记第24天)

    数据库的巡检是DBA工作中的一部分,有时候我们还是希望能够在巡检的基础上发现一些潜在的问题,把尽可能多的问题解决在初始阶段. 今天来给大家举一个数据库巡检和性能分析的例子. 首先拿到一个数据库服务器, ...

  3. WIFI快连(一键配网)原理分析及优化建议

    一键配网说明 文章目录 一键配网说明 一键配网基本流程 说明 配网流程 EZ配网原理 组播 广播 EZ配网优缺点及优化建议 优点 缺点 优化建议 一键配网基本流程 说明 wifi快连也叫一键配网,也叫 ...

  4. MySQL数据库的性能的影响分析及优化

    MySQL数据库的性能的影响 一. 服务器的硬件的限制 二. 服务器所使用的操作系统 三. 服务器的所配置的参数设置不同 四. 数据库存储引擎的选择 五. 数据库的参数配置的不同 六. (重点)数据库 ...

  5. BenchmarkSQL 测试Oracle 12c TPC-C 性能

    使用BenchmarkSQL测试一下Oracle 12c的TPC-C性能,同时对比一下PostgreSQL 9.5的性能. 测试机: 3 * PCI-E SSD,逻辑卷条带,XFS,数据块对齐,16核 ...

  6. Android基础性能检测与分析

    本文内容:基于Android基础性能检测与分析 版权声明:本文为原创文章,未经允许不得转载 博客地址:http://blog.csdn.net/kevindgk 前言 UI性能分析 应用启动时间计算以 ...

  7. web移动端性能调优及16ms优化

    本文只是一个索引,收集了网络上大部分关于调试及优化方面的文章,从中挑选了一些比较好的文章分享给大家. 移动端性能不及桌面浏览器性能的10分之1,特别是在android设备良莠不齐的情况下,性能显得尤为 ...

  8. redis集合数据过期_关于redis性能问题分析和优化

    一.如何查看Redis性能 info命令输出的数据可以分为10个分类,分别是: server,clients,memory,persistence,stats,replication,cpu,comm ...

  9. 关于redis性能问题分析和优化

    一.如何查看Redis性能 info命令输出的数据可以分为10个分类,分别是: server,clients,memory,persistence,stats,replication,cpu,comm ...

最新文章

  1. 框架模式与设计模式之区别
  2. 7个小众却很有意思的工具推荐,每一个都是大宝藏!
  3. 隔离见证地址区别_科普:比特币钱包的隔离见证地址与普通地址有何区别?
  4. torch.flatten()函数
  5. java ftp复制文件_如何使用Java将FTP服务器上的文件复制到同一服务器上的目录中?...
  6. Java TheadLocal
  7. 【luogu P5022 旅行】 题解
  8. 产品经理学习---好产品需要用户有感知
  9. JVM :Btrace监控工具
  10. C# DataSet和DataTable详解
  11. bug-Skipping optimization due to error while loading function libraries: Invalid argument: Functions
  12. 基于文本数据的情感分析系统
  13. 卫星地图-resolution和scale解析
  14. 串级控制系统matlab仿真,锅炉串级三冲量给水控制系统的MATLAB 仿真
  15. PHP接收云之家审批结果,首页云之家开放平台文档
  16. 神经计算棒是什么_这是太棒了
  17. 终于知道什么是URL编码
  18. 公寓上网新认证方式破解研究
  19. 如何用纯CSS将图片填满div,自适应容器大小
  20. Caused by:java.lang.NullPointerException: Attempt to invoke virtual method ‘boolean java.lang.Stri

热门文章

  1. windowsXP远程桌面连接失败 “由于账户限制,无法登录”
  2. 计算机区块链的杂志,CCF区块链技术大会收录Wanchain共识论文并推荐SCI期刊检索...
  3. 32位和64位Windows有什么区别?
  4. 13:1群殴还不赢,腾讯短视频真是扶不起来的阿斗?
  5. fasttext源码学习(2)--模型压缩
  6. [创新工场]2014创新工场校园招聘之回文串修复
  7. 算法竞赛进阶指南---0x14(Hash)后缀数组
  8. 尴尬的数字(C++,数学)
  9. [深入理解Android卷一全文-第三章]深入理解init
  10. 华中科技大学431金融学综合考研新祥旭授课计划参考书题型分数线