MySQL(115)如何配置负载均衡器?

9 阅读3分钟

为了详细介绍如何配置负载均衡器,我们将通过应用程序级别和外部工具级别分别进行配置。下面我将详细讲解这两种方法。

一. 应用程序级别负载均衡

假设我们使用Spring Boot和MySQL,并希望实现一个包含一个主数据库和两个从数据库的负载均衡架构。

1. 项目依赖

pom.xml中添加必要的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

2. 数据源配置

配置主库和从库的数据源,并设置一个路由数据源来进行读写分离及负载均衡。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/db_master")
                .username("root")
                .password("password")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build();
    }

    @Bean(name = "slaveDataSource1")
    public DataSource slaveDataSource1() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/db_slave1")
                .username("root")
                .password("password")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build();
    }

    @Bean(name = "slaveDataSource2")
    public DataSource slaveDataSource2() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/db_slave2")
                .username("root")
                .password("password")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build();
    }

    @Bean
    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource1") DataSource slaveDataSource1,
                                        @Qualifier("slaveDataSource2") DataSource slaveDataSource2) {
        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                return DbContextHolder.getDbType();
            }
        };

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DbType.MASTER, masterDataSource);
        dataSourceMap.put(DbType.SLAVE1, slaveDataSource1);
        dataSourceMap.put(DbType.SLAVE2, slaveDataSource2);

        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        routingDataSource.setTargetDataSources(dataSourceMap);
        return routingDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

3. 数据源上下文

定义一个上下文类来存储当前的数据库类型(主库或从库):

public class DbContextHolder {
    private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>();

    public static void setDbType(DbType dbType) {
        contextHolder.set(dbType);
    }

    public static DbType getDbType() {
        return contextHolder.get();
    }

    public static void clearDbType() {
        contextHolder.remove();
    }
}

public enum DbType {
    MASTER,
    SLAVE1,
    SLAVE2
}

4. 负载均衡策略实现

在AOP切面类中实现负载均衡策略。在读操作前选择合适的从库。

轮询策略

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

@Aspect
@Component
public class DataSourceAspect {

    private AtomicInteger counter = new AtomicInteger(0);

    @Before("execution(* com.example.service.*.find*(..)) || execution(* com.example.service.*.get*(..))")
    public void setReadDataSourceType() {
        int index = counter.incrementAndGet() % 2;
        if (index == 0) {
            DbContextHolder.setDbType(DbType.SLAVE1);
        } else {
            DbContextHolder.setDbType(DbType.SLAVE2);
        }
    }

    @Before("execution(* com.example.service.*.insert*(..)) || execution(* com.example.service.*.update*(..)) || execution(* com.example.service.*.delete*(..))")
    public void setWriteDataSourceType() {
        DbContextHolder.setDbType(DbType.MASTER);
    }
}

数据库操作服务实现

实现具体的数据库操作服务类:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class OrderService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    public void insertOrder(String orderId, String productName, double price) {
        String sql = "INSERT INTO orders (order_id, product_name, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, orderId, productName, price);
    }

    public List<Order> getOrdersByProductName(String productName) {
        String sql = "SELECT * FROM orders WHERE product_name = ?";
        return jdbcTemplate.query(sql, new Object[]{productName}, (rs, rowNum) ->
                new Order(rs.getString("order_id"), rs.getString("product_name"), rs.getDouble("price")));
    }
}

二. 使用HAProxy进行负载均衡

HAProxy是一款开源的高性能TCP/HTTP负载均衡器,可以用来实现数据库负载均衡。

1. 安装HAProxy

根据不同的操作系统,安装HAProxy:

  • Ubuntu/Debian:

    sudo apt-get update
    sudo apt-get install haproxy
    
  • CentOS/RHEL:

    sudo yum install epel-release
    sudo yum install haproxy
    

2. 配置HAProxy

编辑HAProxy配置文件(通常位于/etc/haproxy/haproxy.cfg),配置数据库负载均衡。

global
    log /dev/log    local0
    log /dev/log    local1 notice
    chroot /var/lib/haproxy
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s
    user haproxy
    group haproxy
    daemon

defaults
    log     global
    mode    tcp
    option  tcplog
    option  dontlognull
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

frontend mysql_front
    bind *:3306
    default_backend mysql_back

backend mysql_back
    balance roundrobin
    server mysql1 192.168.1.101:3306 check
    server mysql2 192.168.1.102:3306 check
    server mysql3 192.168.1.103:3306 check

在此配置中,我们定义了一个前端mysql_front监听3306端口,并将请求分发到后端mysql_back使用轮询策略的MySQL服务器。

3. 启动HAProxy

启动并启用HAProxy服务:

  • Ubuntu/Debian:

    sudo systemctl start haproxy
    sudo systemctl enable haproxy
    
  • CentOS/RHEL:

    sudo systemctl start haproxy
    sudo systemctl enable haproxy
    

三. 测试负载均衡

通过调用OrderService中的方法进行测试: