MySQL优化浅析

来源:互联网 发布:linux 抓包文件 导出 编辑:程序博客网 时间:2024/05/29 21:31

1. 场景简介

最近在使用Storm做日志的分析处理。众所周知,Storm是流式的处理框架,也就是每次只能处理和看到一条日志。然而做分析的时候,不可避免的要进行一些统计操作,如统计一分钟内某个ip的访问频率或者某个uid的访问频率等。于是我就会在内存中(也就是某些特定的Bolt中),维护一个HashMap队列,用来存储之前一段时间的日志列表。

这个做法在日志量较小,且统计时间较小的情况下是没有任何问题的。然而当需求变更,要求统计更长的时间(几小时或者一天),这种方式就会导致HashMap越来越大,最后内存不够用,报出OOM异常来。

具体情况如下图所示:
这里写图片描述

既然内存不够用,于是我想到的办法就是单开一个数据库的服务器,专门用来做存储和统计工作。这样一来,既可以利用数据库的高效存储方式(内存利用率高),也可以利用数据库的统计功能(SQL语句)。具体效果如下图所示:

这里写图片描述

1.1 SQL语句

主要使用到SQL语句如下:

  • insert into table(ip, time, username) values(?,?,?)
  • select * from table where ip = ? / select * from table where username = ?
  • select * from table where ip = ? and time > ? / select * from table where username = ? and time > ?
  • delete from table where time < date_sub(current_timestamp, interval 24 HOUR)

基本就是插入和按某个key进行查询,统计分析工作因为比较复杂,且在Storm中已经有了完整的实现,就没有放到SQL中去做了。

1.2 特点

主要的特点有:

  • 总量较大:大概需要存储50G左右的数据,总行数大于4000w
  • 高并发:日志速率约3K/s。基本每条日志都需要读写数据库
  • 多个Key:需要按IP、用户等多个字段来进行统计
  • 实时性:从写库到读库到分析,整个过程不能超过100ms
  • 变动大:存储最近一段时间内的日志,旧日志需及时删除

2. 对比分析

2.1 数据库

目前主流的关系型数据库就是MySQL和Postgres。这两种库我都尝试使用了一下,其具体的对比就不在这细说了,网上已有的对比文章比较多。

在这里,我选择使用了MySQL。主要原因有两点

  • 用的比较多。这个原因虽然很low,但缺不可忽略。对于从一个项目开发,deadline在这摆着,肯定是选一个熟悉的工具比较靠谱。
  • 运维功能比较完善。MySQL的运维功能在我看来是最完善了,一大杀器就是show processlist,可以查看当前数据库在执行哪些语句。而对于其他的很多数据库而言,这种功能有的需要通过查看慢查询日志来操作,有的则需要自己定义一些配置才能实现。

2.2 引擎

选择了MySQL后,就面临了存储引擎的选择,主流的也就是两种MyISAM和InnoDB。这个对比在网上也介绍的比较全面了。在这里也就介绍一下主要的区别:

  • MyISAM是表锁,InnoDB是行锁。这点很关键,也就意味着MyISAM在大部分的写操作,是需要锁整表的。而InnoDB只需要对写的行加锁就行。
  • MyISAM的单次读写速度比InnoDB快,而且通常来说快很多。尤其对于少写多读的场景,MyISAM可以快的飞起来。
  • InnoDB支持事务,对于数据的完整性也有更好的保障。

3. 优化要点

3.1 MyISAM

3.1.1 配置

首先是从配置上面进行优化:

  • key_buffer_size:
    • 指定用于索引的缓冲区大小,增加它可得到更好处理的索引
    • 比例key_reads / key_read_requests应该尽可能的低,至少是1:100,1:1000
      这里写图片描述
  • concurrent_insert:
    • concurrent _insert =0 ,无论MyISAM的表数据文件中间是否存在因为删除而留下俄空闲空间,都不允许concurrent insert。
    • concurrent_insert = 1,是当MyISAM存储引擎表数据文件中间不存在空闲空间的时候,从文件尾部进行Concurrent Insert。
    • concurrent_insert = 2, 无论 MyISAM存储引擎的表数据文件的中间部分是否存在因为删除而留下的空闲空间,都允许在数据文件尾部进行concurrent insert操作。至于由此产生的文件碎片,可以定期使用OPTIMIZE TABLE语法优化。
  • myisam-recover
    • Backup:Mysql将数据文件备份到一个BAK文件中。
    • FORCE:即使.MYD文件丢失的数据多余一行,恢复也会继续。
  • max_write_lock_count
    当系统处理一个写操作后,就会暂停写操作,给读操作执行的机会。

3.1.2 语句优化

  • replace delay into table(ip, time, username, aid) values(?,?,?,?)

通过delay关键词,可以将插入变成异步的操作,避免同步写导致程序长时间等待。

  • select high_priority * from table where ip = ? / select high_priority * from table where username = ?

通过high_priority可以提升读的优先级。在场景简介中说过,需求的最终步骤是读取数据。只要读取过程完成,单次的任务就可以结束,转而执行下一个任务。因此,在这里优先保障读取的优先级,避免大量写导致读取操作饿死。

3.2 InnoDB

3.2.1 配置

  • innodb_buffer_pool_size
    • 数据和索引的缓冲区大小
    • 空闲内存的80%左右
  • innodb_flush_log_at_trx_commit
    • 0:log buffer将每秒一次地写入log file中,并且log file的flush(刷到磁盘)操作同时进行。该模式下在事务提交的时候,不会主动触发写入磁盘的操作。
    • 1:每次事务提交时MySQL都会把log buffer的数据写入log file,并且flush(刷到磁盘)中去,该模式为系统默认。
    • 2:每次事务提交时MySQL都会把log buffer的数据写入log file,但是flush(刷到磁盘)操作并不会同时进行。该模式下,MySQL会每秒执行一次 flush(刷到磁盘)操作。
  • innodb_log_file_size
    • 日志文件大小,当一个日志文件写满后,会触发数据库的检查点(Check Point)
    • 过小,频发触发检查点;过大,恢复较慢
  • innodb_flush_method
    • O_DIRECT,写操作是从MySQL innodb buffer里直接向磁盘上写
    • 避免两次缓存,减少交换的压力,通常能够提升性能
      这里写图片描述

3.2.2 语句优化

  • 加快频率,小步删除:
    delete from table where time < date_sub(current_timestamp, interval 24 HOUR) limit 10000

  • 如果需要删除大部分的表数据,因为会大部分索引需要重新调整,所以删除操作会需要很长时间。这种情况下,不如另外创建一个新的表:
    create table new_table like old_table;
    insert into new_table (select * from old_table where );
    rename table old_table to old_table_drop_prep, new_table to old_table;
    drop table old_table_drop_prep;

3.2.3 表结构优化

  • 使用自增主键:InnoDB不支持随机索引,如果使用随机主键,会显著降低插入速度。如果不设置主键,则和设置随机主键效果一致。因为InnoDB会自动分配一个随机的行键(row key)

4. 总结

通过数据库的方式,虽然很大程度上解决了这个需求。但是仍然存在着一些不足:

  • 删除操作饿死: 因为删除操作无法作优先级调度,导致运行一段时间后,出现大量delete排队等待的现象
  • 性能瓶颈:当数据量超过一定大小的时候,延迟明显升高
  • CPU爆炸:频繁的读写操作,直接占满CPU
  • 带宽:大量的数据传输,带宽达到50Mb/s,sending data的时间较长

后续优化的方向主要有:

  • 集群扩展:如果能有更多机器的话,可以对搭建多个MySQL来进行分库存储,从而降低单个MySQL的处理和存储需求
  • Apache Ignite:这是一个Apache开源的高性能的、集成化的以及分布式的内存平台。其提供的存储方式和功能,能解决大部分内存缓存的痛点(比如Redis太过单一的功能)。
    这里写图片描述