spring-Sessions are not supported by the MongoDB cluster to which this client is connected

鲍鸿波
2023-12-01

前言

最近在尝试用spring framework的事务功能来进行数据系统的事务。其中用JPA事务的时候没啥问题,但是使用mongoDB的事务就报了这个异常。看了下异常栈,发现是这个异常是Java API开启Session时(事务依赖Session)抛出的:

if (clientSession == null) {
    throw new MongoClientException("Sessions are not supported by the MongoDB cluster to which this client is connected");
}

问题定位

百度了一番,发现是连接uri缺少副本集replicaSet=***参数。心想:部署的mongoDB服务还是单独的实例,可能就是这个原因吧,谁知道呢,不试下怎么知道。于是乎,使用swarm部署了三个mongoDB实例,部署的时候重载command指令,加入–replSet选项指定副本集名称,如果有–bind_ip选项的话,不要配置其他实例ip或域名,因为如果其他实例还没启动的话,会报找不到主机的异常。然后参照官网配置副本集的方法,选择了一个实例,执行rs.initiate():

rs.initiate(
   {
      _id: "xxl",
      version: 1,
      members: [
         { _id: 0, host : "mongo1:27017" },
         { _id: 1, host : "mongo2:27017" },
         { _id: 2, host : "mongo3:27017" }
      ]
   }
)

发现,并不行。报错说,集群之间需要验证才能发送心跳(默认开了密码认证)。

各种找资料

然后,去了查了mongoDB的手册,发现了部署副本集安全检查列表等文章,隐约找到了方法,但又无从下手。后来干脆从mongoDB手册的基本概念看起,写了好几篇文章:

把这几篇文章完成之后,对mongoDB副本集的各种成员和每种成员的作用更加清晰了。于是,又回到mongoDB手册,复习了部署副本集,结果在末尾意外了发现了“请参阅使用keyfile认证副本集”。点开一看,正是要找的内容,当时怎么就没发现呢,白白看了那么多文章。原来启动mongoDB的时候多加个–keyFile选项就可以了,keyfile是一个yml格式的文件。副本集的各个节点通过keyfile中的内容来验证身份。

于是,新建了个keyfile.yml文件,写入了身份验证的密钥。但是,问题又来了,怎么才能把这个keyfile.yml传到docker容器中呢?考虑到docker一般用secrets管理密钥,于是创建了一个secret并在容器中引用,命令行也加入–keyFile选项,开始部署副本集。结果,又报了一个异常:permissions on keyfile.yml are too open。

permissions on keyfile.yml are too open

于是,又百度了一番,发现这个问题是由于keyfile.yml的权限导致的,要chmod 600 keyfile.yml才行。但是问题来了,怎么修改secrets的访问权限啊?这难不倒细心的网友,有个网友提到,可以开启个容器,比如busybox,然后在这个容器中访问/run/secrets/secret-name(默认secrets都是挂在/run/secrets/**目录下的)就好了。咋一看,是个好办法。

于是用busybox镜像启动了一个容器,并挂载了那个secret,进入容器,果然发现了这个secret在目录/run/secrets/下,然后调用chmod 666 /run/secrets/keyfile.yml,提示Read-Only file system。不行。

此路不通,又想:会不会因为是密码文件,才不能修改文件权限呢?于是又创建了个config,然后挂载config到容器中,在根目录发现了这个文件/key.file(config默认挂在/目录下),同样也修改这个文件的权限,发现不行。

上面两步总结就是:不管是secrets还是configs,都是只读的。

一番操作后,有点丧气,病急乱投医,想着直接挂载宿主(windows)的文件行不行呢?实验之后,结果依旧,还是不行。为什么说是病急乱投医呢?因为windows文件系统是不支持权限的,不用试就知道不行的,可还是试了,哎。

命名卷

正当一筹莫展之际,想到命名卷(部署gitlab和rabbitmq吃过亏的)是可以挂载目录的,能不能在命名卷里面放这个文件呢?于是,创建了一个命名卷,docker volume ls可以看到。那么,问题又来了,怎么才能把keyfile.yml文件放进去呢?考虑到docker可以通过“docker cp在宿主和容器间复制文件”,然后就创建了一个busybox容器搭桥,把宿主的文件复制到busybox容器中,并在容器中修改了命名卷里keyfile.yml的权限。是的,chmod 600 keyfile.yml没有报错。

于是,把busybox临时容器移除了,重新部署了mongoDB副本集,把–keyFile选项指向命名卷里的keyfile.yml,发现,又报了另外一个错误:bad file。

bad file

屋漏偏逢连夜雨,刚收了一个怪,又来了一个妖。无奈,只好又百度了一番,只发现了一个答案:chown 999 keyfile.yml。不知道对不对,只好硬着头皮再次用busybox搭桥执行了命令chown 999 keyfile.yml。移除后busybox后,重新部署了mongoDB副本集,结果,成功了。哈哈哈,居然成功了。

原来,除了权限问题,还有归属问题,好复杂。

mongoDB事务

mongoDB副本集部署好之后,启动Spring程序,重新运行了那段抛异常的事务代码,发现程序没有报Sessions are not supported by the MongoDB cluster to which this client is connected异常,mongoDB也回滚了事务。

这说明:

  1. mongoDB单个实例不支持事务,副本集才支持事务
  2. 连接uri不加replicaSet=***也是可以正常连接并开始事务的

总结

  • 问题总是有解的,需要自下而上的归纳结合自上而下的推演。
  • docker的secrets和configs都是只读的。
  • docker的命名卷本质上是虚拟机的目录(windows上docker desktop的虚拟机默认是linux系统,所以可以控制权限和归属)。
  • 多看看官方文档。
 类似资料: