01月08, 2018

2017年参加公司内部性能PK心得

2017年公司内部组织了一次《性能比赛》语言不限,我作为最终没拿奖的队员参与了一下,技术栈选择的是node。接下来总结一下,本人水平有限,所以很多地方可能理解的不到,还望大家多多指教~

题目

给定一批示例数据,提供对应的数据API,单机QPS高者获胜。

成绩

我的成绩:采用node技术栈: 700M数据 qps:979

技术选型

plan A:

使用node技术栈,数据存库,进行库查询

优点:可操作性较高,能跑通,难度系数中

缺点:qps值不会很高

plan B: 使用C语言实现nginx插件服务。插件主要逻辑通过Hash表对数据做一级映射。 对数据文件做与干预简历索引结构,来实现快速加载。数据存储在redis中。

优点:qps值有冲击空间。

缺点:难度系数较高,操作内容较复杂。

plan C:

使用node搭建查询服务,使用shell来导入数据到redis中。

优点:可操作性较高,能跑通,难度系数中。

缺点:shell脚本读写效率较低。

plan D:

使用node搭建查询服务,使用java来导入数据到redis中。

优点:可操作性适中,能跑通,难度系数中。

缺点:redis存储要求很高,5G数据很容易GG掉。

综上描述,选择使用plan D。5G数据GG主要是因为我的硬件设备的硬伤。。。

存储选型

mysql:无论数据还是索引都存放在硬盘中。要使用的时候交换到内存中。优点在于能够处理大量数据数据。缺点在于查询速度会比较慢。

redis: 所有数据都放在内存中。短小精悍。优点在于查询速度很快。缺点在于处理大量数据会比较困难。

mongo: 内存数据库,数据都是放在内存里面的。mongodb的所有数据实际上是存放在硬盘的,所有要操作的数据通过mmap的方式映射到内存某个区域内。所以mongo其实是结合了sql和redis。

所以比较来看,当物理内存够用的时候,redis>mongodb>mysql 经过层层筛选,我选择了redis来做数据存储。

构建redis服务以及启动redis服务通过shell脚本实现:

#!/bin/sh
echo "=========deploy======="
ulimit -n 65535
# make redis
if [ ! -d redis-unstable ]; then
  echo "========unpacking redis=========="
  tar -zxf redis-unstable.tar.gz
  cd redis-unstable
  echo "========make redis============="
  make -j 4
  cd ..
fi

# stop server
pro_num=`ps -ef | grep redis-server | grep -v bash | grep -v grep | awk 'NR = 1{print $2}'| wc -l`
if [ $pro_num -gt 0 ]; then
  ps -ef | grep redis-server | grep -v bash | grep -v grep | awk '{print $2}' | xargs kill
  sleep 10
fi
# start server
./redis-unstable/src/redis-server redis.conf
sleep 5
./load_data.sh

导入数据选型

shell脚本:纯shell脚本读写数据效率太低。1K 的数据导入需要500ms+,所以放弃shell选型。

代码如下:

echo "===========load data=========="
REDIS_CLI_CMD='./redis-unstable/src/redis-cli'
PREFIX_KEY='id::'
TMP_FILE='/tmp/qps_race.tmp'
rm -f $TMP_FILE
cat sample_data.txt | while read line
do
  key=$(echo $line | awk '{print $1}')
  value=$(echo $line | awk '{print $1"::"$2"::"$3"::"$4"::"$5}')
  echo "SET $PREFIX_KEY$key $value" >> $TMP_FILE
done
cat $TMP_FILE | $REDIS_CLI_CMD

java代码导入:使用java创建一个进程,对数据进行读写,由于java底层是并发实现的,所以导入效率非常高。

700M数据导入统计62s

package com.lianjia.fe;


import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;


import java.io.File;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;




public class DataImport {


    private static void fail() {
        System.out.println("java class inputPath");
        System.exit(-1);
    }


    public static void main(String[] args) throws FileNotFoundException, InterruptedException {
        int startTime = (int) (System.currentTimeMillis() / 1000);
        if (args.length < 1) {
            fail();
        }
        String inputPath = args[0];
        System.out.println("INPUT FILE : " + inputPath);
        File inputFile = new File(inputPath);
        if (!inputFile.exists()) {
            fail();
        }
        Jedis jedisClient = new Jedis("127.0.0.1");
        Pipeline pipeline = jedisClient.pipelined();
        List<Response<String>> responses = new ArrayList<>();


        int lineNumber = 0;
        Scanner scanner = new Scanner(inputFile);
        while (scanner.hasNext()) {
            String line = scanner.nextLine();
            if (lineNumber++ == 0) {
                continue;
            }
            if (lineNumber % 10000 == 0) {
                System.out.println("Processing " + lineNumber + " lines.");
            }


            String[] elements = line.split("\t");
            final String key = "id::" + elements[0];
            final JSONObject value = new JSONObject();
            JSONObject data = new JSONObject();
            value.put("data", data);
            data.put("id", elements[0]);
            data.put("user_name", elements[1]);
            data.put("npc_unique_string", elements[2]);
            data.put("sex", elements[3]);
            data.put("extra", elements[4]);
            responses.add(pipeline.set(key, value.toJSONString()));
        }
        try {
            pipeline.sync();
            pipeline.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
//        Thread.sleep(10000);
//        for (Response<String> response : responses) {
//            if (!response.get().equals("OK")) {
//                System.out.println(response.get());
//            }
//        }


        System.out.println("During " + ((int) (System.currentTimeMillis() / 1000) - startTime) + "s");
    }
}

node查询选型

采用express框架。

redis服务

采用redis依赖:700M数据,qps:979

采用redis-connection-pool依赖:700M数据,qps:700多,

后来我分析了一下,采用线程池QPS不增反降的原因,大概是node是单线程的,实现并发的方式,

也是通过异步的方式,所以说到底还是一个线程来读取,使用了线程池反而增加了读取压力,所以QPS有所下降。

比赛总结

正式数据使用5G的数据,电脑太渣,没能完整的读到内存中区,4000万条数据,到2900万的时候内存就爆掉了,截图为证。

alt

通过这件事情,我明白了一个道理,测试数据靠不住,当初用700M的数据流畅的跑完,想当然的觉得5G的也没问题,

却发现内存不够,小小的遗憾。。。

以上。

本文链接:https://www.imwineki.cn/post/performancepksum.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。