SpringBoot + nodeJS + zookeeper 搭建微服务示例
总体来说该项目由服务注册 + 服务发现 + 服务代理 + 服务调用四部分组成。
使用java客户的开发服务注册组件,它是整个微服务架构中的服务注册表,使用Node.js客户端开发服务发现组件,它用于在服务注册表中根据具体的服务名称获取对应的服务配置。
由项目1提供接口
/** * 注册服务信息 * @param serviceName 服务名称 * @param serviceAddress 注册服务的地址 */ void register(String serviceName,String serviceAddress);
由项目2依赖项目1,实现其提供的接口
@Component public class ServiceRegistryImpl implements ServiceRegistry,Watcher{ private static Logger logger = LoggerFactory.getLogger(ServiceRegistryImpl.class); private static CountDownLatch latch = new CountDownLatch(1); private static final int SESSION_TIMEOUT = 5000; private static final String REGISTRY_PATH = "/registry"; private ZooKeeper zk; public ServiceRegistryImpl() { } /** * * @param zkServers registry.servers */ public ServiceRegistryImpl(String zkServers) { try { zk = new ZooKeeper(zkServers, SESSION_TIMEOUT, this); latch.await(); logger.info("connected to zookeeper"); logger.info("connected to zookeeper"); } catch (Exception e) { logger.error("create zookeeper client failure",e); e.printStackTrace(); } } @Override public void register(String serviceName, String serviceAddress) { try { //创建根节点(持久节点) String registryPath = REGISTRY_PATH; if(zk.exists(registryPath, false) == null) { zk.create(registryPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); logger.info("create registry node:{}",registryPath); } //创建服务节点(持久节点) String servicePath = registryPath + "/" + serviceName; if(zk.exists(servicePath, false) == null) { zk.create(servicePath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); logger.info("create service node:{}",servicePath); } //创建地址节点(临时顺序节点) String addressPath = servicePath + "/address-"; if(zk.exists(addressPath, false) == null) { String addressNode = zk.create(addressPath, serviceAddress.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); logger.info("create address node:{}",addressNode); } } catch (Exception e) { logger.error("create node failure",e); e.printStackTrace(); } } @Override public void process(WatchedEvent event) { if(event.getState() == Event.KeeperState.SyncConnected) { latch.countDown(); } } }
项目启动时注册服务
@Component public class RegistryZooListener implements ServletContextListener { @Value("${server.address}") private String serverAddress; @Value("${server.port}") private int serverPort; @Autowired private ServiceRegistry serviceRegistry; @Override public void contextInitialized(ServletContextEvent servletContextEvent) { ServletContext servletContext = servletContextEvent.getServletContext(); WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); //获取到所有的请求mapping Map<RequestMappingInfo, HandlerMethod> infoMap = mapping.getHandlerMethods(); for (RequestMappingInfo info:infoMap.keySet()){ String serviceName = info.getName(); if (serviceName != null){ //注册服务 // System.out.println(serviceName); serviceRegistry.register(serviceName, String.format("%s:%d",serverAddress,serverPort)); } } } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { } }
接下来的工作将是服务发现
ar express = require('express'); var zookeeper = require('node-zookeeper-client'); var httpProxy = require('http-proxy'); var PORT = 1234; var CONNECTION_STRING = '127.0.0.1:2181'; var REGISTER_ROOT = "/registry"; //连接zookeeper var zk = zookeeper.createClient(CONNECTION_STRING); zk.connect(); //创建代理服务器对象并监听错误事件 var proxy = httpProxy.createProxyServer(); proxy.on('error', function (err, req, res) { res.end();//输出空白数据 }); //启动web服务器 var app = express(); app.use(express.static('public')); app.all('*', function (req, res) { //处理图标请求 if (req.path == '/favicon.ico') { res.end(); return; } //获取服务器名称 var serviceName = req.get('Service-Name'); console.log('service-name : %s', serviceName); if (!serviceName) { console.log('Service-Name request head is not exist'); res.end(); return; } //获取服务路径 var servicePath = REGISTER_ROOT + '/' + serviceName; console.log('service path is : %s', servicePath); //获取服务路径下的地址节点 zk.getChildren(servicePath, function (error, addressNodes) { console.log("into zk getChildren!"); if (error) { console.log(error.stack); res.end(); return; } var size = addressNodes.length; if (size == 0) { console.log('address node is not exist'); res.end(); return; } //生成地址路径 var addressPath = servicePath + '/'; if (size == 1) { //如果只有一个地址 addressPath += addressNodes[0]; } else { //如果存在多个地址,则随即获取一个地址 addressPath += addressNodes[parseInt(Math.random() * size)]; } console.log('addressPath is : %s',addressPath); //获取服务地址 zk.getData(addressPath,function (error,serviceAddress) { if (error) { console.log(error.stack); res.end(); return; } console.log('serviceAddress is : %s',serviceAddress); if (!serviceAddress) { console.log('serviceAddress is not exist'); res.end(); return; } console.log('TART###http://' + serviceAddress); proxy.web(req,res,{ target:'http://'+serviceAddress//目标地址 }) }); }); }); app.listen(PORT,function () { console.log('server is running at %d',PORT); });
随后调用即可
$.ajax({ method: "GET", url: 'user/hello', headers: { 'Service-Name': 'hello' }, success: function (data) { console.log("data:" + data); $("#console").append(data + '<br>'); }, error:function (error){ console.log("error") } });