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

前言

最近在尝试用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系统,所以可以控制权限和归属)。
  • 多看看官方文档。
好的,下面是一个简单的 Spring Boot WebSocket 入门 demo: 1. 首先,在 pom.xml 文件中引入 `spring-boot-starter-websocket` 依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建一个简单的 WebSocket 处理器: ```java @Component public class WebSocketHandler extends TextWebSocketHandler { private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); session.sendMessage(new TextMessage("连接成功!")); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { for (WebSocketSession webSocketSession : sessions) { webSocketSession.sendMessage(new TextMessage("客户端说:" + message.getPayload())); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } } ``` 3. 创建 WebSocket 配置类: ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Autowired private WebSocketHandler webSocketHandler; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketHandler, "/ws").setAllowedOrigins("*"); } } ``` 4. 编写一个简单的页面来测试 WebSocket: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>WebSocket</title> </head> <body> <h1>WebSocket Demo</h1> <div> <input type="text" id="input"/> <button onclick="send()">发送</button> </div> <div id="output"></div> <script> var socket = new WebSocket("ws://localhost:8080/ws"); socket.onmessage = function(event) { var output = document.getElementById("output"); output.innerHTML += "<p>" + event.data + "</p>"; }; function send() { var input = document.getElementById("input"); socket.send(input.value); input.value = ""; } </script> </body> </html> ``` 5. 运行程序,访问 http://localhost:8080/index.html,打开浏览器控制台,输入命令 `socket.send("Hello, WebSocket!")`,即可看到页面上显示出 "客户端说:Hello, WebSocket!"。 希望这个 demo 能帮助到你。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值