Unix Domain Socket与PostgreSQL的local连接

前言

前面章节中提到了PostgreSQL的peer认证方式,说到这种认证方式是通过Unix Domain Socket连接方式认证的。本章节就来详细谈谈Unix Domain Socket及其应用。

Unix Domain Socket VS TCP/UDP Socket

看到Socket这个词,很多人可能会联想到网络编程非常常见的TCP/UDP Socket。本质上他们的作用都一样,都是为了网络通信而生。Unix Domain Socket是在此基础之上发展的IPC机制,在本机两个进程之间直接建立通信方式,不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,所以性能上和效率上比TCP/UDP Socket高得多,并且Unix Domain Socket是一种可靠的通信机制,而network socket是不可靠的通信,可能发生丢包、乱序等现象。

Unix Domain Socket的表现形式

是以文件形式存放在本机上,通常不占用容量。以大多数运维人员非常熟悉的MySQL服务为例,默认配置下MySQL服务运行时会创建一个这样的文件:

# ll /var/lib/mysql/mysql.sock 
srwxrwxrwx 1 mysql mysql 0 12月 28 21:30 /var/lib/mysql/mysql.sock 
#^ 注意第一个file类型标记是s

# file  /var/lib/mysql/mysql.sock 
 /var/lib/mysql/mysql.sock : socket

不同的操作系统可能这个文件位置略有不同,具体位置参考MySQL的配置文件中的socket配置项。

咋一看有点像命名管道(named pipe),而且作用也差不多。但是命名管道只能支持字节流,而Unix Domain Socket既能支持字节流,又能支持队列。

另外Unix Domain Socket使用系统文件的地址来作为自己的身份,所以可以用于权限认证(PostgreSQL的Peer认证),它可以被系统进程引用。所以可以支持不同的进程同时使用,而命名管道是没有这种功能的。

Unix Domain Socket的使用

很多运行于Linux/Unix上的服务不止可以监听一个TCP/UDP端口,还支持监听一个Unix Domain Socket。如Mysql的socket配置,redis的unixsocket配置,nginx的listen配置,Postgresql的unix_socket_directories配置等等。只要客户端支持Unix Domain Socket,就可以直接使用这种方式连接。

如MySQL的[client]配置段下面socket配置就直接指向unix domain socket地址,代表默认使用unix domain socket连接。psql默认连接方式也是使用unix domain socket,会在/var/run/postgresql/目录下查找.s.PGSQL.5432这个socket。可以通过psql -h /path/to/socket/directory/ -p port切换其他目录下的socket文件。

大多数使用C/C++语言开发的client也都支持Unix Domain Socket。比如Python(使用官方的解释器Cython)的Mysql driver Connector/Python,就有这样的连接方式:

cnx = mysql.connector.connect(user='root',password='password',unix_socket='/var/lib/mysql/mysql.sock')

直接指定连接本地的Unix Domain Socket访问Mysql服务。

再比如PostgreSQL的Python driver Psycopg2的文档就直接写明connect()函数不指明host参数,默认就使用Unix Domain Socket等等。

再比如说流行的web服务器Nginx,可能很多人都没不知道Nginx的listen指令是可以支持unix domain socket的:

server {
  listen unix:/var/run/nginx.sock;
}

这样可以限制这个虚拟主机不能直接暴露给外网用户,需要暴露的时候可以在加一个反向代理,以便做一些其他的配置限制:

upstream backend-nginx {
  server unix:/var/run/nginx.sock;
}

server {
  location / {
    proxy_pass http://backend-nginx;
  }
}

Unix Domain Socket的限制

Unix Domain Socket的性能很高,安全性好,在network socket端口有限的情况下,Unix Domain Socket无需占用有限的TCP/UDP端口。现在谈谈这玩意的限制了。很明显的可以看到两个限制:

  • 只能本机访问,不能用于远程访问(Docker可以利用挂载的形式实现本机不同容器或容器与宿主机之前的访问,但还是在同一台主机上)
  • 只在POSIX兼容的系统有实现。意味着Windows下没有对应的实现,所以MySQL这一类的服务跑在Windows下就默认关闭Unix Domain Socket的功能了

还有就是像Java这种编程语言为了简化跨平台的兼容性问题,底层也不提供Unix Domain Socket的支持。比如JDBC就没有Unix Domain Socket的连接实现,所以jdbc url在连接本机服务的时候,也只能写127.0.0.1,不能写Unix Domain Socket路径。

PostgreSQL的local连接与peer认证

说清楚了Unix Domain Socket之后,PostgreSQL的peer认证就很容易理解了。

有关postgresql完整的认证方式,参见官方文档: https://www.postgresql.org/docs/current/auth-methods.html

我们先看看默认安装下的postgresql的pg_hba.conf文件(已过滤空行和注释行):

local   all             postgres                                peer
local   all             all                                     peer
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            md5
host    replication     all             ::1/128                 md5

其中host代表client的访问方式是TCP,而local就代表client的访问方式是Unix Domain Socket。所以可以很明显的看到local行的配置是没有ip配置段的。

那么再说说peer认证方式是怎么回事呢?之前提到过,Unix Domain Socket是可以拿到用户身份信息的,这个在Unix/Linux系统中就是用户账号,而peer则代表着用户psql以user身份运行,连接到数据库上对应的用户身份就是user这种方式。所以peer认证只支持local连接方式,所以在windows不起作用。

举个例子,如果我当前以myuser这个账号登录到Linux系统中,运行psql命令,那么连接到数据库上就是myuser这个用户,不需要密码认证。如果postgresql数据库中没有myuser这个用户,就会报错找不到这个用户。

再看看postgres这个用户默认配置下限定local连接方式,认证方式是peer。代表postgres(默认的postgresql的管理员账户)必须以Unix Domain Socket方式连接,并且使用操作系统中的postgres用户身份运行。由于这个账户默认在Linux系统中是不允许直接登录的,所以我们需要susudo命令切换用户身份运行,这里我们以sudo为例:

sudo -u postgres psql

由于psql默认就是以Unix Domain Socket方式连接(Windows系统则以TCP方式),所以无需指定其他连接参数。

再比如我们在postgresql上创建了一个用户myadmin,但是我们的操作系统中没有myadmin这个账户,那么此时我们就不能使用local渠道连接了,只能以host方式连接,即TCP方式:

psql -U myadmin -h 127.0.0.1

直接指定-h参数为127.0.0.1localhost,限定psql使用TCP方式连接postgresql,则可以命中后面的host all all 127.0.0.1/32 md5这个配置,于是连接到数据库中。

psql使用unix domain socket连接本机其他postgresql实例的方案:

psql -h /path/to/socket/dir/ -p other_port

推荐阅读更多精彩内容