数据库路由中间件MyCat - 测试篇

勿忘初心2018-11-01 15:54

此文已由作者张镐薪授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。


测试背景

为了检测MyCat性能表现以及架构扩展性,设计测试。首先需要编写压力测试代码,程序基于Jmeter,并且封装了JDBC,模拟涅槃项目实际应用的连接方式。测试程序生成基于当前系统时间的随机数,并且保证这个随机数一秒内重复概率为百万分之一。 程序请求必须保证每个分片的请求量是一样的. 测试脚本举例:

package com.sf.hash.drm;

import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ThreadLocalRandom;

public class DRMTest2 extends AbstractJavaSamplerClient{
    private String JDBC_DRIVER;
    private String DB_URL;
    private String USER;
    private String PASSWORD;

    private static ThreadLocal <Connection> conn = new ThreadLocal<Connection>();
    private static ThreadLocal<Statement> stmt = new ThreadLocal<Statement>();

    private String testStr;

    private int aa=0;
    private int count=0;
    //初始化方法,实际运行时每个线程仅执行一次,在测试方法运行前执行,类似于LoadRunner中的init方法
    public void setupTest(JavaSamplerContext arg0) {

        JDBC_DRIVER = arg0.getParameter("JDBC_DRIVER", "");
        USER = arg0.getParameter("USER", "");
        PASSWORD = arg0.getParameter("PASSWORD", "");
        DB_URL = arg0.getParameter("DB_URL","");
        aa = Integer.parseInt(arg0.getParameter("ID",""));
        try {
            Class.forName(JDBC_DRIVER);
            conn.set(DriverManager.getConnection(arg0.getParameter("DB_URL",""), arg0.getParameter("USER", ""), arg0.getParameter("PASSWORD", "")));
            stmt.set(conn.get().createStatement());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread()+" start!");
    }
    //设置传入的参数,可以设置多个,已设置的参数会显示到Jmeter的参数列表中
    public Arguments getDefaultParameters() {
        Arguments params = new Arguments();
        params.addArgument("JDBC_DRIVER", "com.mysql.jdbc.Driver");   //定义一个参数,显示到Jmeter的参数列表中,第一个参数为参数默认的显示名称,第二个参数为默认值
        params.addArgument("DB_URL", "jdbc:mysql://10.202.44.205:8666/test");
        params.addArgument("USER", "test");
        params.addArgument("PASSWORD", "test");
        params.addArgument("ID", "0");
        return params;
    }
    //测试执行的循环体,根据线程数和循环次数的不同可执行多次,类似于LoadRunner中的Action方法
    public SampleResult runTest(JavaSamplerContext arg0) {

        SampleResult results = new SampleResult();
        long currentTime = System.nanoTime();
        int key = ThreadLocalRandom.current().nextInt(0,100000000);
        long id = currentTime*4 + key*4 + (aa);
        String sql = "INSERT INTO proxyha(id,name,age) VALUES (" +
                (id) +
                ", '"+aa+"', 18)";

//        String sql2 = "UPDATE proxyha set age ='" +
//                (id) +
//                "'where name ='"+Thread.currentThread()+aa+"'";
        int a = 0;
        try {
            results.sampleStart();
            a = stmt.get().executeUpdate(sql);
//            a = stmt.get().executeUpdate(sql2);
            results.sampleEnd();
        } catch (SQLException e) {
            e.printStackTrace();
            int count = 0;
            //重试
            while(!reconnect(arg0))
            {
                count++;
                if(count>100)
                {
                    System.out.print(0);
                    break;
                }
            }
        }
        if(a <= 0){
            results.setSuccessful(false);   //用于设置运行结果的成功或失败,如果是"false"则表示结果失败,否则则表示成功
        }else
            results.setSuccessful(true);
        return results;
    }

    private boolean reconnect(JavaSamplerContext arg0){
         try {
             stmt.get().close();
             conn.get().close();
             conn.set(DriverManager.getConnection(arg0.getParameter("DB_URL",""), arg0.getParameter("USER", ""), arg0.getParameter("PASSWORD", "")));
             stmt.set(conn.get().createStatement());
             aa++;
        } catch (SQLException e) {
            return false;
        }
        return true;
    }

    //结束方法,实际运行时每个线程仅执行一次,在测试方法运行结束后执行,类似于LoadRunner中的end方法
    public void teardownTest(JavaSamplerContext arg0) {
        System.out.println(Thread.currentThread()+" end!");
    }


}

测试场景


  1. 数据库能力测试,用压测程序直接压测每一个库,为了看数据库能力是否一致。
  2. 压测程序能力测试,用n个压测程序压测m个数据库,为了看压测程序是否存在性能瓶颈。
  3. MyCat能力测试,用n个压测程序压测o个MyCat实例后台连接着同样的m个数据库节点。
  4. MyCat故障边界测试:
    1. 单节点缓慢
    2. 单节点down机(对于全局表的dml都会失效)
    3. 分片表单节点锁定
    4. 分片表索引摘除测试update影响测试环境

测试环境



测试结果


1. 数据库能力测试

排除db1m-db4m(因为性能差太多)的问题,之后db1m-db4m与其他12个数据库的性能差不多了。


2. 压测程序能力测试


3. MyCat能力测试

测试证明,MyCat的TPS可以随着测试脚本与实例数量还有DB节点的数量增加而增加


4. MyCat故障边界测试


4.1 单节点缓慢

使用cpulimit命令限制某个DB节点的cpu使用率,原本不限制可以用到1500%, 分别限制到1000%,500%,100%;

4.2 单节点宕机


让MyCat的16个节点其中一个节点在运行脚本过程中宕机

4.3 分片表单节点锁定


运行脚本,让MyCat的16个节点其中一个节点在运行脚本过程中做在线的ddl

运行脚本,让MyCat的16个节点其中一个节点在运行脚本过程中全表锁定


4.3 分片表索引摘除测试update影响


修改脚本(从insert改成以主键查找update)。运行脚本,让MyCat的16个节点其中一个节点在运行脚本过程中删除主键索引(表是以主键作为分片id)

修改脚本(从insert改成以非主键(unique字段)查找update)。运行脚本,让MyCat的16个节点其中一个节点在运行脚本过程中删除非主键(unique)索引(表是以主键作为分片id)

测试结论


根据上面测试结果,在物理环境正常的情况下,TPS是可以随着MyCat实例个数与后台db个数增加而增加的。 以下故障边界都有个前提条件:请求是均匀分布在每个后台db节点上的。


  • MyCat性能不受限于最慢的分片主机,分片主机缓慢只是单点影响,不会影响全局
  • 单节点宕机不会影响只发到其他节点的命令,但是只要涉及到这个节点的命令(比如select * from table range(0,1000))都不能正常执行
  • 分片表单节点锁定对于MyCat性能影响不大。
  • 分片表分片字段索引丢失如果按照分片字段查找更新的话,影响不大。但是如果非分片字段索引丢失如果按照那个字段查找更新的话,由于请求会发到每个节点,那么每个请求都会全量搜索,对于性能影响很大。


免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击