为了简化应用支持服务方便的分合,使用Zookeeper embbed模式。集成Zookeeper比较容易,使用starter或自己写代码都可以。但是由于集成了Dubbo,每次启动时都会发现zookeeper没有启动就开始报错退出,但是确是已经集成了。
于是只能翻Dubbo源码
发现Dubbo启动时,会添加一个早期事件DubboConfigInitEvent。在spring afterproperties后,会立即触发该事件。在该事件里调用zookeeper注册事件。
因此,解决方式是添加早期事件DubboConfigInitEvent的侦听,在侦听里去初始化zookeeper server,这样就能保证在Dubbo初始化之前具备zookeeper服务了:
添加一个Helper Bean,实现代码如下:
package org.ccframe.commons.helper;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.dubbo.config.spring.context.event.DubboConfigInitEvent;
import org.apache.zookeeper.server.NIOServerCnxnFactory;
import org.apache.zookeeper.server.ZooKeeperServer;
import org.ccframe.config.GlobalEx;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
@Component
public class EmbeddedZookeeperLauncher implements ApplicationListener<DubboConfigInitEvent>{
private String zooDir;
@Value("${app.zookeeper.embed.dir:}")
public void setZooDir(String zooDir) {
this.zooDir = zooDir;
if (zooDir == null || zooDir.isEmpty()) {
this.zooDir = GlobalEx.APP_BASE_DIR + File.separator + GlobalEx.EMBEDDED_ZOOKEEPER_DIR; //zookeeper数据放到应用运行目录
}
}
@Value("${app.zookeeper.embed.port:2181}")
private Integer zooPort;
@Value("${app.zookeeper.embed.max-connection:1024}")
private Integer maxConnection;
@Value("${app.zookeeper.embed.tick-time:500}")
private Integer tickTime;
@Getter
private ZooKeeperServer zooKeeperServer;
@Override
@SneakyThrows
public void onApplicationEvent(DubboConfigInitEvent event){
if (event.getApplicationContext().getParent() == null) {
Path zooPath = Paths.get(zooDir);
zooKeeperServer = new ZooKeeperServer(zooPath.toFile(), zooPath.toFile(), tickTime);
NIOServerCnxnFactory factory = new NIOServerCnxnFactory();
System.setProperty("zookeeper.maxCnxns", Integer.toString(maxConnection));
factory.configure(new InetSocketAddress(zooPort), maxConnection);
factory.startup(zooKeeperServer);
}
}
}
然后启动就不会报错了
本人还实现了elasticsearch 7的集成模式,这样额外只需要启动一个独立的redis即可在单台2C2G的云主机上运行ccframe微服务框架了。耐斯~
【24.02.29】更新:
将服务拆分成多个JAR后,又出现了dubbo启动时找不到zookeeper的问题。直接原因,是因为zookeeper又启动在dubbo初始化之后了,可能打包成jar后,扫描的顺序不一样。经过数小时调试解决,需要2个改进点:
1. 给ApplicationListener指定启动顺序,让其在第一位启动。并且采用spring.factories的方式来指定listener:
org.springframework.context.ApplicationListener=org.ccframe.commons.helper.EmbeddedZookeeperLauncher
2. 无需@Component注入,因为这种模式下侦听早期事件时Value还没注入,因此换一种方式。好在event.getApplicationContext()可用了,因此可以直接event.getApplicationContext().getEnvironment()
来获取环境变量参数
更新后的代码如下:
package org.ccframe.commons.helper;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.dubbo.config.spring.context.event.DubboConfigInitEvent;
import org.apache.zookeeper.server.NIOServerCnxnFactory;
import org.apache.zookeeper.server.ZooKeeperServer;
import org.ccframe.config.GlobalEx;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
@Order(Ordered.HIGHEST_PRECEDENCE)
public class EmbeddedZookeeperLauncher implements ApplicationListener<DubboConfigInitEvent>{
@Getter
private ZooKeeperServer zooKeeperServer;
@Override
@SneakyThrows
public void onApplicationEvent(DubboConfigInitEvent event){
Environment environment = event.getApplicationContext().getEnvironment();
String zooDir = environment.getProperty("app.zookeeper.embed.dir");
if (zooDir == null || zooDir.isEmpty()) {
zooDir = GlobalEx.APP_BASE_DIR + File.separator + GlobalEx.EMBEDDED_ZOOKEEPER_DIR;
}
int zooPort = environment.getProperty("app.zookeeper.embed.port", Integer.class, 2181);
int maxConnection = environment.getProperty("app.zookeeper.embed.max-connection", Integer.class, 1024);
int tickTime = environment.getProperty("app.zookeeper.embed.tick-time", Integer.class, 500);
boolean embedded = environment.getProperty("app.zookeeper.embedded", Boolean.class, Boolean.FALSE);
if (embedded && event.getApplicationContext().getParent() == null) {
Path zooPath = Paths.get(zooDir);
zooKeeperServer = new ZooKeeperServer(zooPath.toFile(), zooPath.toFile(), tickTime);
NIOServerCnxnFactory factory = new NIOServerCnxnFactory();
System.setProperty("zookeeper.maxCnxns", Integer.toString(maxConnection));
factory.configure(new InetSocketAddress(zooPort), maxConnection);
factory.startup(zooKeeperServer);
}
}
}