iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >【巨杉数据库SequoiaDB】巨杉 Tech | SequoiaDB SQL实例高可用负载均衡实践
  • 288
分享到

【巨杉数据库SequoiaDB】巨杉 Tech | SequoiaDB SQL实例高可用负载均衡实践

摘要

1 前言   在应用程序中,应用配置连接的数据库IP地址和端口号都是固定一个的,当所属IP地址的服务器宕机后,需要人为手工更改IP地址切换数据库服务器。同时当应用接收到成千上万的并发 Http 请求时,会导致服务器消耗大量系统资源,轻

【巨杉数据库SequoiaDB】巨杉 Tech | SequoiaDB SQL实例高可用负载均衡实践

1

前言

 

在应用程序中,应用配置连接的数据库IP地址和端口号都是固定一个的,当所属IP地址的服务器宕机后,需要人为手工更改IP地址切换数据库服务器。同时当应用接收到成千上万的并发 Http 请求时,会导致服务器消耗大量系统资源,轻则响应速度降低,严重的甚至会引发宕机。

 

为了充分合理的利用服务器资源,提高数据服务的性能和稳定性,在较低成本的前提下,保证在部分服务器宕机或发生故障的情况下不影响业务的正常运作。本文主要介绍 Nginx+Keepalived 连接 SequoiaDB -Mysql 实例的高可用方案与实践。

 

2

SequoiaDB 数据库介绍

 

SequoiaDB 巨杉数据库是一款完全自研的金融级分布式数据库产品,采用计算与存储分离架构,由数据库实例层和数据库存储引擎层组成。数据库实例层负责解析请求并转发至数据库存储引擎层处理,同时会将数据库存储引擎层的响应结果反馈给应用层,数据库实例层支持包括针对结构化数据的 mysql 实例、postgresql 实例、sparksql 实例,以及针对非结构化数据的 S3 和 PosixFS 文件系统的对象存储实例实例,而数据库存储引擎层是由 SequoiaDB 巨杉数据库的协调节点、编目节点和数据节点组成。该数据库集群架构能方便用户实现由传统数据库到巨杉数据库的无缝迁移,减少应用开发者的开发和学习成本。

 

1

 

SequoiaDB MySQL实例 的关系

 

如图所示是 SequoiaDB 巨杉数据库集群的逻辑架构图,用户由图可知,应用程序实际是通过计算层的实例节点连接、访问数据库。例如图中所示,应用程序可以直接通过 MySQL 的 JDBC 驱动连接、访问 SequoiaDB 数据库集群。

 

SequoiaDB 数据库的存储层是一个能够支持分布式事务、多中心容灾的分布式存储集群。

 

SequoiaDB 的分布式存储层,其中包括:协调节点、编目节点和数据库节点三类角色节点。分布式存储层中的所有节点均支持多节点部署,支持分布式水平扩展和高可用容灾功能。

计算层中的 MySQL 实例和分布式存储引擎的连接,可以通过多协调节点的负载均衡功能实现高可用和避免单点故障。而在传统的应用程序中,应用程序通过 JDBC 连接 MySQL 时,一般只会填写一个服务器的IP 地址和端口号。在这种架构中,为了解决 MySQL 连接的单点故障隐患,会在应用服务器和数据库服务器中间,搭建一层负载均衡服务,以实现应用程序和数据库连接的高可用切换。

 

2

 

传统解决方案

 

在过去企业级解决方案中,为了解决数据库的高可用问题,一般会在数据库服务器与外部网络之间安装负载均衡器(如F5),从而避免因为某台服务器宕机而对业务造成影响。

 

虽然这种硬件负载均衡技术较为成熟,在过去的企业重要系统中均有部署使用,但该种方案需要额外购买价格高昂的负载均衡设备,成本比较高。

 

3

Nginx+Keepalived 解决方案

 

1

 

整体架构

 

本方案与传统解决方案的思路一致,在数据库服务器与外部网络之间新增负载均衡层,唯一不同的是传统解决方案使用负载均衡器,而本方案采用 Nginx+Keepalived  来实现负载均衡。该种解决方案采用纯软件的方式,实现多机器的数据库服务器统一固定一个IP 地址和服务端口,部署方式灵活。

具体部署架构如下图所示,Nginx 反向代理三台数据库服务器的 MySQL 及负载使用实例,将3306端口映射到3307端口,而应用程序服务器会通过 Keepalived 虚拟IP地址的 Nginx 反向代理端口3307来连接三台数据库服务器的 MySQL 实例。

VRRP 技术可以将两台或者多台物理路由器设备虚拟成一个虚拟路由器,这个虚拟路由器通过虚拟IP(192.168.81.100)对外提供服务。

 

在虚拟路由器内部,同一时间只有一台物理路由器在对外提供服务,这台物理路由器被称为主路由器(Master),一般而言Master通过选举算法产生,它拥有对外服务的虚拟IP,提供各种网络功能。而其他物理路由器不拥有对外的虚拟IP,仅仅接收 Master 的 VRRP 状态通告信息,这部分路由器叫做备份路由器(Slave)。

 

当主路由器失效的时候,备份路由器重新进行选举,产生一个新的路由器成为 Master。如下图所示,当主路由器(Master)宕机后,备份路由器(Slave)进行选举成为Master,然后接管虚拟IP,继续对外提供服务,对应用程序服务器而言,整个切换过程是对应用程序透明,无任何影响。

 

2

 

Nginx 产品介绍

 

Nginx (engine x) 是一个高性能的 HTTP 和反向代理 WEB 服务器,同时也提供了 IMAP/POP3/SMTP 服务。Nginx是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在 BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上Nginx的并发能力在同类型的网页服务器中表现较好。

 

Nginx 有很强的代理功能,但是仅在一台服务器部署 Nginx 服务,依然存在 Nginx 单点故障的问题,因此在高可用负载均衡解决方案中,Nginx 需要结合 Keepalived 软件解决单点故障问题。

 

Nginx+Keepalived 双机实现 Nginx 反向代理服务的高可用,保证任意服务器宕机,或者是任何 Nginx 服务被意外停止,也会不影响业务的正常运作。

 

3

 

Keepalived 产品介绍

 

Keepalived 软件的作用是检测服务器的状态。

 

在部署了 Keepalived 服务的软件中,如果一台服务器意外宕机,或工作出现故障, Keepalived 软件将会检测到该问题服务器,并将有故障的服务器从系统中剔除。

 

同时,Keepalived 软件会自动使用其他服务器代替该问题服务器的工作。当问题服务器重新正常工作后,Keepalived 软件又会自动将原来问题服务器加入到服务器列表中。整个高可用切换工作全部由 Keepalived 软件自动感知,自动切换,无任何需人工干涉。

 

4

Nginx+Keepalived 安装部署

 

假设存在以下的服务器环境环境,后续内容将介绍如何通过 Keepalived + Nginx 软件实现 SequoiaDB 数据库的 MySQL 实例高可用部署:

 

1

 

部署前环境检查

 

检查 selinux 状态是否为 disabled

 

sestatus

 

使用 root 权限,打开 /etc/selinux/config 文件

 

vi /etc/selinux/config

 

将 SELINUX 调整成 disabled

# This file controls the state of SELinux on the system.

# SELINUX= can take one of these three values:#     enforcing - SELinux security policy is enforced.#     permissive - SELinux prints warnings instead of enforcing.#     disabled - No SELinux policy is loaded.SELINUX=disabled# SELINUXTYPE= can take one of three two values:#     targeted - Targeted processes are protected,#     minimum - Modification of targeted policy. Only selected processes are protected. #     mls - Multi Level Security protection.SELINUXTYPE=targeted 

 

重启操作系统以使SELINUX 设置生效

reboot

 

安装依赖插件

yum install -y GCc pcre pcre-devel openssl openssl-devel gd gd-devel net-snmp-agent-libs

 

2

Nginx 安装与部署

 

4.2.1 Nginx 安装

 

通过以下命令下载 Nginx 安装包

wget https://nginx.org/download/nginx-1.16.0.tar.gz

 

解压并安装

tar -zxvf nginx-1.16.0.tar.gz

cd nginx-1.16.0./configure --prefix=/usr/local/nginx --with-streammake && make install

 

4.2.2 Nginx部署

 

打开 Nginx 配置文件

vi /usr/local/nginx/conf/nginx.conf

 

修改 Nginx 配置文件,新增以下内容,stream 段的配置要与 http 段在同级目录。新增内容表示将134、135和136 服务器的 3306 服务加入到 Nginx 的负载均衡中,并且 Nginx 对外发布 3307 的服务端口。

stream {    upstream sequoiadb-mysql {        server 192.168.81.134:3306 weight=1;        server 192.168.81.135:3306 weight=1;        server 192.168.81.136:3306 weight=1;        }
    server {        listen 3307;        proxy_connect_timeout 5s;        proxy_timeout 300s;        proxy_pass sequoiadb-mysql;    }}

 

添加全局命令

ln -s /usr/local/nginx/sbin/nginx /usr/bin/nginx

 

测试安装

nginx -V

 

预期返回结果​​​​​​​

nginx version: nginx/1.16.0built by gcc 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) configure arguments: --prefix=/usr/local/nginx

 

新建Nginx服务脚本

vi /etc/init.d/nginx

 

新增以下内容​​​​​​​

#!/bin/sh# nginx - this script starts and stops the nginx daemin## chkconfig:   - 85 15# description:  Nginx is an HTTP(S) server, HTTP(S) reverse #               proxy and IMAP/POP3 proxy server# processname: nginx# config:      /usr/local/nginx/conf/nginx.conf# pidfile:     /usr/local/nginx/logs/nginx.pid# Source function library.. /etc/rc.d/init.d/functions# Source networking configuration.. /etc/sysconfig/network# Check that networking is up.[ "$NETWORKING" = "no" ] && exit 0nginx="/usr/local/nginx/sbin/nginx"prog=$(basename $nginx)NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"lockfile=/var/lock/subsys/nginxstart() {    [ -x $nginx ] || exit 5    [ -f $NGINX_CONF_FILE ] || exit 6    echo -n $"Starting $prog: "    daemon $nginx -c $NGINX_CONF_FILE    retval=$?    echo    [ $retval -eq 0 ] && touch $lockfile    return $retval}
stop() {    echo -n $"Stopping $prog: "    killproc $prog -QUIT    retval=$?    echo    [ $retval -eq 0 ] && rm -f $lockfile    return $retval} restart() {    configtest || return $?    stop    start}
reload() {    configtest || return $?    echo -n $"Reloading $prog: "    killproc $nginx -HUP    RETVAL=$?    echo}
force_reload() {    restart} configtest() {  $nginx -t -c $NGINX_CONF_FILE} rh_status() {    status $prog} rh_status_q() {    rh_status >/dev/null 2>&1}
case "$1" in    start)        rh_status_q && exit 0        $1        ;;    stop)        rh_status_q || exit 0        $1        ;;    restart|configtest)        $1        ;;    reload)        rh_status_q || exit 7        $1        ;;    force-reload)        force_reload        ;;    status)        rh_status        ;;    condrestart|try-restart)        rh_status_q || exit 0            ;;    *)        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"        exit 2esac

 

赋予nginx服务脚本权限

chmod 755 /etc/init.d/nginx

 

添加Nginx到系统服务

chkconfig --add nginx

 

启动 Nginx 服务

systemctl start nginx.service

 

设置 Nginx 为开机自启动

 

systemctl enable nginx.service

 

检查 Nginx 服务

 

systemctl status nginx.service

 

预期返回结果

 

● nginx.service - SYSV: Nginx is an HTTP(S) server, HTTP(S) reverse proxy and IMAP/POP3 proxy server   Loaded: loaded (/etc/rc.d/init.d/nginx; bad; vendor preset: disabled)   Active: active (running) since Fri 2020-01-17 01:03:12 PST; 36s aGo     Docs: man:systemd-sysv-generator(8) Main PID: 407 (nginx)   CGroup: /system.slice/nginx.service           ├─407 nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf           └─409 nginx: worker process
Jan 17 01:03:12 sdb2 systemd[1]: Starting SYSV: Nginx is an HTTP(S) server, HTTP(S) reverse proxy and IMAP/POP3 proxy server...Jan 17 01:03:12 sdb2 nginx[394]: Starting nginx: [  OK  ]Jan 17 01:03:12 sdb2 systemd[1]: PID file /usr/local/nginx/logs/nginx.pid not readable (yet?) after start.Jan 17 01:03:12 sdb2 systemd[1]: Started SYSV: Nginx is an HTTP(S) server, HTTP(S) reverse proxy and IMAP/POP3 proxy server.

 

通过 Nginx 登录 MySQL 实例

mysql -h 192.168.81.134 -uroot -P 3307 -proot

 

预期返回结果

mysql: [Warning] Using a passWord on the command line interface can be insecure.Welcome to the MySQL monitor.  Commands end with ; or g.Your MySQL connection id is 5Server version: 5.7.25 Source distribution
Copyright (c) 2000, 2019, oracle and/or its affiliates. All rights reserved.
Oracle is a reGIStered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.
Type "help;" or "h" for help. Type "c" to clear the current input statement.
mysql> 

 

3

 

Keepalived 安装与部署

 

 

4.3.1 Keepalived 安装

 

用户通过以下命令下载 Keepalived 安装包

wget https://www.keepalived.org/software/keepalived-2.0.19.tar.gz

 

解压并安装

tar -zxvf keepalived-2.0.19.tar.gz

cd keepalived-2.0.19./configure --prefix=/usr/local/keepalivedmake && make install

 

4.3.2 Keepalived 部署

 

添加全局命令

ln -s /usr/local/keepalived/sbin/keepalived /usr/bin/keepalived

 

测试安装

keepalived -v

 

预期返回结果

Keepalived v2.0.19 (10/19,2019)

Copyright(C) 2001-2019 Alexandre Cassen, Built with kernel headers for Linux 3.10.0Running on Linux 3.10.0-693.el7.x86_64 #1 SMP Thu Jul 6 19:56:57 EDT 2017configure options: --prefix=/usr/local/keepalivedConfig options:  LVS VRRP VRRP_AUTH OLD_CHKSUM_COMPAT FIB_ROUTINGSystem options:  PIPE2 SIGNALFD INOTIFY_INIT1 VSYSLOG EPOLL_CREATE1 IPV6_ADVANCED_api RTA_ENCAP RTA_EXPIRES FRA_TUN_ID RTAX_CC_ALGO RTAX_QUICKACK FRA_OIFNAME IFA_FLAGS IP_MULTICAST_ALL NET_LINUX_IF_H_COLLISION LIBIPTC_LINUX_NET_IF_H_COLLISION VRRP_VMac IFLA_LINK_NETNSID CN_PROC SOCK_NONBLOCK SOCK_CLOEXEC O_PATH GLOB_BRACE INET6_ADDR_GEN_MODE SO_MARK SCHED_RT SCHED_RESET_ON_FORK

 

新建 Nginx 检查脚本

vi /usr/local/keepalived/nginx_check.sh

 

添加以下内容​​​​​​​

#!/bin/bashA=`ps -C nginx --no-header |wc -l`if [ $A -eq 0 ];then     /usr/sbin/nginx    sleep 2    if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then        killall keepalived    fifi

 

赋予检查脚本可执行权限

chmod +x /usr/local/keepalived/nginx_check.sh

 

打开 Keepalived 配置文件

vi /usr/local/keepalived/etc/keepalived/keepalived.conf

 

修改主 Keepalived 的配置文件

! Configuration File for keepalived
global_defs {   router_id sdb1}
vrrp_script chk_nginx {     script "/usr/local/keepalived/nginx_check.sh"     interval 3     weight -20 }
vrrp_instance VI_1 {    state MASTER    interface ens33    virtual_router_id 66    priority 100    advert_int 1    authentication {        auth_type PASS        auth_pass 1111    }    track_script {        chk_nginx    }    virtual_ipaddress {        192.168.81.100    }}

 

修改备 Keepalived 的配置文件​​​​​​​

! Configuration File for keepalived
global_defs {   router_id sdb2}
vrrp_script chk_nginx {     script "/usr/local/keepalived/nginx_check.sh"     interval 3     weight -20 }
vrrp_instance VI_1 {    state BACKUP    interface ens33    virtual_router_id 66    priority 90    advert_int 1    authentication {        auth_type PASS        auth_pass 1111    }    track_script {        chk_nginx    }    virtual_ipaddress {        192.168.81.100    }}

 

修改配置文件路径vi /usr/local/keepalived/etc/sysconfig/keepalived

KEEPALIVED_OPTIONS="-D -f /usr/local/keepalived/etc/keepalived/keepalived.conf"

 

启动 Keepalived 服务

systemctl start keepalived.service

 

设置 Keepalived 为开机自启动

systemctl enable keepalived.service

 

检查 Keepalived 服务

systemctl status keepalived.service

 

预期返回结果​​​​​​​

keepalived.service - LVS and VRRP High Availability Monitor   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; enabled; vendor preset: disabled)   Active: active (running) since Fri 2020-01-17 07:52:10 PST; 1min 45s ago  Process: 76416 ExecStart=/usr/local/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS) Main PID: 76417 (keepalived)   CGroup: /system.slice/keepalived.service           ├─76417 /usr/local/keepalived/sbin/keepalived -D -f /usr/local/keepalived/etc/keepalived/keepalived.conf           └─76418 /usr/local/keepalived/sbin/keepalived -D -f /usr/local/keepalived/etc/keepalived/keepalived.conf

 

检查 Master 和 Backup 的虚拟IP地址

ip addr show ens33

 

Master 预期返回结果,部分截取inet 192.168.81.134/24 brd 192.168.81.255 scope global ens33

       valid_lft forever preferred_lft foreverinet 192.168.81.100/32 scope global ens33       valid_lft forever preferred_lft forever

 

Backup 预期返回结果,部分截取​​​​​​​

inet 192.168.81.135/24 brd 192.168.81.255 scope global ens33       valid_lft forever preferred_lft forever

 

通过虚拟IP地址登录 MySQL 实例

mysql -h 192.168.81.100 -uroot -P 3307 -proot

 

预期返回结果​​​​​​​

mysql: [Warning] Using a password on the command line interface can be insecure.Welcome to the MySQL monitor.  Commands end with ; or g.Your MySQL connection id is 5Server version: 5.7.25 Source distribution
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.
Type "help;" or "h" for help. Type "c" to clear the current input statement.
mysql>

 

4

 Keepalived + Nginx 实现负载均衡高可用

 

用户通过 Navicat 连接MySQL数据库,IP 使用Keepalived 提供的虚拟IP 地址,MySQL实例的端口为Nginx 提供的3307 服务端口。

因为 sdb1 服务器部署的 Keepalived 为 Master,所以 Navicat 连接的是这台机器,通过执行 netstat 命令可以确认​​​​​​​

[root@sdb1 ~]# netstat -nap|grep 3307tcp        0      0 0.0.0.0:3307            0.0.0.0:*               LISTEN      1393/nginx: master  tcp        0      0 192.168.81.100:3307     192.168.81.1:53385      ESTABLISHED 1395/nginx: worker  tcp        0      0 192.168.81.100:3307     192.168.81.1:53380      ESTABLISHED 1395/nginx: worker  

 

如果主动关闭 sdb1 服务器

shutdown -h now

 

然后继续检查sdb2的虚拟IP地址

ip addr show ens33

 

预期返回结果,部分截取​​​​​​​

inet 192.168.81.135/24 brd 192.168.81.255 scope global ens33       valid_lft forever preferred_lft foreverinet 192.168.81.100/32 scope global ens33       valid_lft forever preferred_lft forever

 

如果此时主动关闭sdb2 服务器的MySQL服务

 

service sequoiasql-mysql stop

 

在Navicat 中继续查询一张表的数据,显示查询成功。

 

81.136:3306     192.168.81.135:49646    ESTABLISHED 1388/mysqld         unix  2      [ ACC ]     STREAM     LISTENING     22202    1388/mysqld          /opt/sequoiasql/mysql/database/3306/mysqld.sock

 

通过以上验证步骤,可以证明 Keepalived 软件能够实现多服务器之间虚拟 IP 地址自动漂移,Nginx 服务能够自动实现 MySQL 实例的负载均衡。用户用过 Keepalived 软件和 Nginx 服务,最终实现了在对应用零影响的情况下,实现了 SequoiaDB 数据库集群的多 MySQL 实例的负载均衡高可用功能。

 

5

总结

本文介绍了如何通过 Nginx+Keepalived 双机实现数据库实例的高可用,用户可以通过 Nginx+Keepalived 解决方案来实现应用通过一个 IP 地址和端口号来访问多个 MySQ 实例,保证在任意一台机器宕机或 MySQL 服务停止的时候,应用也能基于同一个 IP 地址来访问 SequoiaDB 数据库.

您可能感兴趣的文档:

--结束END--

本文标题: 【巨杉数据库SequoiaDB】巨杉 Tech | SequoiaDB SQL实例高可用负载均衡实践

本文链接: https://www.lsjlt.com/news/5112.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • sql中year是集函数吗
    否,year 不是 sql 中的聚合函数。year 函数是一个日期函数,用于从给定的日期值中提取年份。它是一个标量函数,返回单个值,而不是值的集合。相反,聚合函数对一组值进行操作并生成一...
    99+
    2024-05-15
    聚合函数
  • sql中between的用法
    sql 中 between 运算符用于检查值是否在指定范围之内,其语法为:select column_name from table_name where colum...
    99+
    2024-05-15
  • sql中update用法
    sql 中的 update 语句用于更新表中的现有数据,通过指定要更新的表、列、值和可选的更新条件来实现,可更新特定行或组行的特定列值。 SQL 中的 UPDATE 语句 什么是 UP...
    99+
    2024-05-15
  • sql中for循环的用法
    sql 中 for 循环可用于遍历结果集,逐行执行操作。语法:for var_name in (select_statement) [loop_statement] end f...
    99+
    2024-05-15
  • sql中any和all的区别
    sql 中 any 和 all 运算符的区别在于:any 检查子查询中是否存在满足条件的行,返回 true 或 false。all 检查子查询中所有行是否都满足条件,返回 true 或 ...
    99+
    2024-05-15
  • sql中exists具体用法
    exists 子查询用于检查外层查询中的行是否存在匹配记录,用法如下:包含在 select 语句的 where 子句中。返回布尔值 true (存在匹配) 或 fal...
    99+
    2024-05-15
  • sql中union用法
    union 运算符在 sql 中用来合并相同结构的表或子查询的结果集,排除重复行。它具有以下用法:合并具有相同列名和数据类型的多个表或子查询的结果集合并为一个。排除结果集中重复...
    99+
    2024-05-15
  • sql中索引的用法
    sql 中索引是一种通过创建数据指针来提高查询性能的技术,主要用于where、order by、join和group by子句。索引类型包括聚集索引、非聚集索引、主键索引、唯一...
    99+
    2024-05-15
    聚合函数
  • sql中nullif怎么用
    sql 中的 nullif() 函数,用于比较两个表达式并返回较小的值,若均为 null 则返回 null,语法为 nullif(expression1, expression2)。可用...
    99+
    2024-05-15
  • sql中decode用法
    decode 函数根据输入表达式值将值转换为另一个值,语法为 decode(expression, value1, result1, value2, result2, ..., defa...
    99+
    2024-05-15
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作