總體來說該項目由服務注冊 + 服務發現 + 服務代理 + 服務調用四部分組成。
使用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") } });