【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库

前言

Unix domain socket 又叫 IPC(inter-process communication 进程间通信)socket,用于实现同一主机上的进程间通信。
socket 原本是为网络通讯设计的,但后来在 socket 的框架上发展出一种 IPC 机制,就是 UNIX domain socket。虽然网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。
Unix 套接字:Unix 套接字、 UNIX 域套接字、 Unix 域协议族AF_UNIX、POSIX 本地进程间通信套接字、POSIX 本地 IPC 套接字、、、、、、——所有这些术语或多或少指的是同一个概念——主机内部协议和寻址该方案或多或少类似于 Internet 上计算机之间的套接字通信。通常,但并非总是如此,Unix 套接字由文件系统上的路径名引用。PF_UNIXPF_LOCALAF_FILEPF_FILE
Unix 套接字可用于多种平台(包括 macOS、Windows 和 Linux),它们的工作方式都略有不同。

Unix域套接字有兴趣的同学可以百度查找相关资料,这里不再赘述;

由上可知,unix套接字的通讯效率是高于socket通讯的,在同一台服务器上使用unix domian socket进行进程间的通讯,也能提高我们的通讯效率,例如同一台服务java server与java serverjava server与mysqljava server与postgresqljava server与docker等通讯,可以使用unix domain socket进行数据交互,可以提高执行效率,同时也也避免了服务对外暴露端口,提高的安全性

这篇文章主要实现java通过unix domain socket技术对mysql数据库的应用,主要涉及步骤讲解、代码示例;后续也会提供java基于unix domain socket实现Socketrmipostgresql应用,尤其java通过unix domain socket技术调用postgresql数据库,我们工作中涉及到的应用最多,后续会着重讲解;

注:为了方便大家使用,本文介绍两种unix domain socket连接mysql数据库的方式:java + jdbcspring boot + mybatis

mysql.sock简介

“mysql.sock”是mysql的套接字文件,是mysql的主机和客户机在同一host上的时候,使用unix socket做为通讯协议的载体。在UNIX系列系统下本地连接MySQL可以采用TCP连接UNIX域套接字两种方式;其中UNIX域套接字方式需要一个套接字文件,可用“show variables like ‘socket’\G;”命令来查看本地socket文件位置。

MySQL 中我们常用的有两种连接数据库的方式,分别是:

  1. TCP/IP
mysql -uyouruser -pyourpassword -P3306 -h127.0.0.1
mysql -uyouruser -pyourpassword -P3306 -h192.168.199.198
  1. Socket
mysql -uyouruser -pyourpassword --socket=/tmp/mysql.sock
mysql -uyouruser -pyourpassword -hlocalhost   
mysql -uyouruser -pyourpassword

以上三种方式是等价的,默认不填时,默认--host=localhost--socket=/tmp/mysql.sock

登录示例
【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库_第1张图片

mysql使用 Unix 域套接字连接:

Connector/J 本身不支持使用 Unix 域套接字连接到 MySQL 服务器。但是,可以使用通过可插入套接字工厂提供功能的第 3 方库。这样的自定义工厂应该实现 Connector/J 的 com.mysql.cj.protocol.SocketFactory 接口或遗留 com.mysql.jdbc.SocketFactory接口。当您为 Unix 套接字使用此类自定义套接字工厂时,请遵循以下要求:
1)MySQL 服务器必须配置系统变量 --socket(对于使用 JDBC API 的本机协议连接)或 --mysqlx-socket(对于使用 X DevAPI 的 X 协议连接),它必须包含 Unix 套接字文件的文件路径。
2)自定义工厂的完全限定类名应该通过连接属性传递给 Connector/J socketFactory。例如,对于 junixsocket 库,设置:

socketFactory=org.newsclub.net.mysql.AFUNIXDatabaseSocketFactory

您可能还需要将其他参数作为连接属性传递给自定义工厂。例如,对于 junixsocket 库,提供具有以下属性的套接字文件的文件路径junixsocket.file

junixsocket.file=path_to_socket_file

3)Fore release 8.0.21 及更早版本:使用 X 协议时,设置连接属性 xdevapi.useAsyncProtocol=false(这是 Connector/J 8.0.12 及更高版本的默认设置)。异步套接字通道不支持 Unix 套接字。当 时xdevapi.useAsyncProtocol=true,该 socketFactory属性将被忽略(连接属性 xdevapi.useAsyncProtocol自 8.0.22 版以来已被弃用)。

注:可以参考MySQL Connector/J 8.0 开发人员指南

【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库_第2张图片
代码实现

创建数据库表student

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(10) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `sex` char(6) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `pwd` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `email` varchar(20) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;

连接socket,创建AFUNIXDatabaseSocketFactoryCJ类:

package com.config;

import com.mysql.cj.conf.PropertySet;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.protocol.ServerSession;
import com.mysql.cj.protocol.SocketConnection;
import com.mysql.cj.protocol.SocketFactory;
import org.newsclub.net.unix.AFUNIXSocket;
import org.newsclub.net.unix.AFUNIXSocketAddress;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.Socket;

public class AFUNIXDatabaseSocketFactoryCJ implements SocketFactory {

    private AFUNIXSocket rawSocket;
    private Socket sslSocket;

    @Override
    public <T extends Closeable> T connect(String s, int i, PropertySet propertySet, int i1) throws IOException {
        RuntimeProperty<String> prop = propertySet.getStringProperty("junixsocket.file");
        String sock;
        if (prop != null && !prop.isExplicitlySet()) {
            sock = prop.getStringValue();
        } else {
            sock = "/var/lib/mysql/mysql.sock";
        }
        final File socketFile = new File(sock);

        this.rawSocket = AFUNIXSocket.connectTo(AFUNIXSocketAddress.of(socketFile));
        this.sslSocket = rawSocket;
        return (T) rawSocket;
    }

    @Override
    public <T extends Closeable> T performTlsHandshake(SocketConnection socketConnection, ServerSession serverSession) throws IOException {
        return null;
    }
}

建立数据库Connection连接:

package com.sk.jdbc;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class MysqlDemo {
    
    //建立connection连接
    public static Connection getConn() throws Exception{
        Driver driver = new com.mysql.cj.jdbc.Driver();
        String url = "jdbc:mysql://localhost/test";
        //将用户名和密码封装在Properties中
        Properties info = new Properties();
        info.setProperty("socketFactory","com.config.AFUNIXDatabaseSocketFactoryCJ");
        info.setProperty("user","root");
        info.setProperty("password","%Dsh0406");
        info.setProperty("junixsocket.file","/var/lib/mysql/mysql.sock");
        info.setProperty("sslMode","DISABLED");
        Connection conn = driver.connect(url,info);
        System.out.println(conn);
        return conn;
    }

    //通过conn操作数据库,主要用于jdbc实现
    public static List<String> run() throws Exception{
        Connection conn = getConn();
        Statement stmt = conn.createStatement();
        String sql = "select * from student";
        ResultSet rs = stmt.executeQuery(sql);
        //处理结果集
        List<String> res = new ArrayList<>();
        while(rs.next()) {
            String str = rs.getString("name");
            res.add(str);
        }
        rs.close();
        System.out.println("结果集关闭成功!");
        stmt.close();
        System.out.println("语句通道关闭成功!");
        conn.close();
        System.out.println("关闭连接成功!");

        //遍历输出
        if(res!=null){
            for(String out:res) {
                System.out.println(out);
            }
        }
        return res;
    }
}

jdbc实现对数据库调用,结合上一步的run()

package com.controller;

import com.bean.Student;
import com.mapper.StudentMapper;
import com.sk.jdbc.MysqlDemo;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.List;

@Log4j2
@RestController
public class TestAction {

    @Resource
    private StudentMapper studentMapper;

    /**
     * 验证jdbc调用
     * @return
     */
    @GetMapping("/test/local")
    private Object get(){
        try {
            return MysqlDemo.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

jdbc调用执行结果:
日志信息
【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库_第3张图片
接口调用:
【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库_第4张图片
集合springboot+mybatis使用
创建mapper

package com.mapper;

import com.bean.Student;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;

@Mapper
@Repository
public interface StudentMapper {

    @Select("select * from student")
    List<Student> selectAll();

    @Select("select * from student where id = #{id}")
    Student selectById(@Param("id") int id);

    @Select("select * from student where name = #{name}")
    Map<String, Object> selectByName1(@Param("name") String name);

    @Select("select * from student where name = #{name}")
    Student selectByName2(@Param("name") String name);

    @Select("select * from student where name = #{name} and pwd = #{pwd}")
    Student selectByNameAndPwd(String name, String pwd);

    @Delete("delete from student where id = #{id}")
    boolean deleteById(int id);

    @Insert("insert into student values (null,#{name},#{sex},#{pwd},#{email})")
    boolean insertUser(String name, String sex, String pwd, String email);

    @Update("update student set name =  #{name} where id = #{id}")
    boolean updateById(String name, int id);

}

重写DataSourceMyDataSource.java

package com.config;


import com.sk.jdbc.MysqlDemo;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class MyDataSource implements DataSource {

    @Override
    public Connection getConnection() {
        try {
            return MysqlDemo.getConn();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Connection getConnection(String username, String password) {
        try {
            return MysqlDemo.getConn();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

手动初始化数据库连接DruidConfig.java

package com.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@Configuration
public class DruidConfig {

    @Bean
    public DataSource druid(){
        return new MyDataSource();
    }

}

创建验证接口

package com.controller;

import com.bean.Student;
import com.mapper.StudentMapper;
import com.sk.jdbc.MysqlDemo;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.List;

@Log4j2
@RestController
public class TestAction {

    @Resource
    private StudentMapper studentMapper;

   
    /**
     * 验证mybatis
     * @return
     */
    @GetMapping("/test")
    private Object get2(){
        List<Student> studentList = studentMapper.selectAll();
        log.info(studentList);
        return studentList;
    }
}

执行结果:
在这里插入图片描述

参考资料:
https://github.com/kohlschutter/junixsocket
https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-unix-socket.html
https://mp.weixin.qq.com/s/h3jOYAD05QfHSRXLnQtPYw

=有下午茶,必开会
【技术应用】java基于UNIX域套接字(unix domain socket)连接mysql数据库_第5张图片

你可能感兴趣的:(java,unix,mysql)