如何使APP成爲服務端?並通過http調用
2020年開年第一篇!!!繼續加油
快照版本git源碼地址
簡介
- 編程語言:Java
- JDK版本:JDK1.8
- 構建工具:Maven
- 項目描述:整體設計思路爲,使用netty作爲serverSocket服務器,移動端利用socket協議與服務端建立連接,使用自定義通信協議解決大字符串傳輸問題,使用sun提供的HttpHandler提供Http服務,通過繼承NettyDefaultChannelGroup實現對移動端的管理,通過Channel與消息id的綁定進行異步通信,通過label標籤 (自定義,通過唯一不同的標籤來確定接口,分佈式情況下如果不是唯一會隨機分配) 來定義當前移動端所具備的功能,使用zookeeper實現分佈式協調服務,當單機模式下,http請求會分爲公平和非公平兩種請求方式,以達到均衡請求和隨機請求,當分佈式模式下會優先使用本地所連接的移動端,如需要的label在其他移動端上將會調用rpc方式去請求(減少額外網絡開銷)
項目結構樹圖:
工作流程圖:
單機,三個客戶端,併發測試圖:
線程組配置:
吞吐量(感覺時間還是慢):
響應時間圖(不太理想還得優化,最慢超過了10s):
部分代碼:
啓動類源碼
import com.sun.net.httpserver.HttpServer;
import config.DispatchConfig;
import core.NettyStart;
import core.ZookeeperClient;
import http.DispatchHandler;
import http.MonitorHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 萬物開始的地方
*/
public class DispatchBootStrap {
private static final Logger LOGGER = LoggerFactory.getLogger(DispatchBootStrap.class);
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), r -> {
Thread thread = new Thread(r);
thread.setName("dispatch-");
return thread;
});
if(DispatchConfig.ZOOKEEPER_ENABLE){
ZookeeperClient.init(DispatchConfig.ZOOKEEPER_HOST);
}
// ExecutorService threadPoolExecutor = Executors.newCachedThreadPool();
InetSocketAddress address = new InetSocketAddress(DispatchConfig.DISPATCH_PORT);
try {
HttpServer httpServer = HttpServer.create(address, 0);
httpServer.createContext("/get",new DispatchHandler(DispatchConfig.WAIT_TIME));
httpServer.createContext("/info",new MonitorHandler());
httpServer.setExecutor(threadPoolExecutor);
httpServer.start();
LOGGER.info("====== HTTP START port is {} ======",DispatchConfig.DISPATCH_PORT);
threadPoolExecutor.execute(new DispatchNetty());
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if(LOGGER.isWarnEnabled()){
LOGGER.warn("shut down server !");
}
ZookeeperClient.deleteLocal();
threadPoolExecutor.shutdown();
}));
} catch (IOException e) {
e.printStackTrace();
}
}
static class DispatchNetty implements Runnable{
@Override
public void run() {
LOGGER.info("====== NETTY START port is {} ======",DispatchConfig.NETTY_PORT);
NettyStart nettyStart = new NettyStart(DispatchConfig.NETTY_PORT);
nettyStart.start();
}
}
}
zk客戶端代碼
package core;
import beans.RemoteChannel;
import config.DispatchConfig;
import org.apache.commons.collections.CollectionUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.IpUtils;
import utils.JsonUtils;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE;
/**
* zk客戶端
*/
public class ZookeeperClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperClient.class);
private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
/**
* 客戶端節點
*/
private static final String PATH = "/channels";
/**
* 服務端節點
*/
private static final String REMOTES_PATH = "/remotes";
private static final ConnectionRepository CHANNEL = ConnectionRepository.get();
private static ZooKeeper zooKeeper = null;
private static final Stat stat = new Stat();
public ZookeeperClient() { LOGGER.info("load ZookeeperInstance ..."); }
public static void init(String host){
LOGGER.info("wait zk callback ...");
try {
zooKeeper = new ZooKeeper(host,500000,watchedEvent -> {
List<String> children = null;
if(Watcher.Event.KeeperState.SyncConnected == watchedEvent.getState()){
if(Watcher.Event.EventType.None == watchedEvent.getType() && null == watchedEvent.getPath()){
connectedSemaphore.countDown();
try {
//每次都新加一個watch監聽channel下節點變化
children = zooKeeper.getChildren(PATH, true);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
if(CollectionUtils.isNotEmpty(children)){
LOGGER.info("add old channel size {}",children.size());
addRemoteNodeChannels(children);
}
}else if(Watcher.Event.EventType.NodeChildrenChanged == watchedEvent.getType()){
try {
//每次都新加一個watch監聽channel下節點變化
children = zooKeeper.getChildren(PATH, true);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
if(Objects.isNull(children)){
LOGGER.warn("children node is null .." );
return;
}
LOGGER.info("add node changes remote size {} ..", children.size());
addRemoteNodeChannels(children);
}
}
});
connectedSemaphore.await();
LOGGER.info("zookeeper successful connected !");
Stat rootExists = zooKeeper.exists(PATH, false);
if(Objects.isNull(rootExists)){
LOGGER.info("[ init create channels root node ! ]");
zooKeeper.create(PATH,"".getBytes(), OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}
assert IpUtils.getRealIp() != null;
Stat remoteExists = zooKeeper.exists(REMOTES_PATH, false);
if(Objects.isNull(remoteExists)){
LOGGER.info("[ init create remotes root node! ]");
zooKeeper.create(REMOTES_PATH , "".getBytes(), OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
Thread.sleep(100);
createZkRemotesNode();
}else{
Stat exists = zooKeeper.exists(REMOTES_PATH + "/" + IpUtils.getRealIp(), false);
if(Objects.isNull(exists)){
createZkRemotesNode();
}else{
LOGGER.warn("{} already exists !",REMOTES_PATH + "/" + IpUtils.getRealIp());
}
}
} catch (Exception e) {
LOGGER.error("zookeeper connection exception !" + e.getMessage());
}
}
public static void addRemoteNodeChannels(List<String> children){
for (String child : children) {
try {
byte[] data = zooKeeper.getData(PATH+"/"+child, false ,stat);
RemoteChannel remoteChannel = JsonUtils.get().readValue(data, RemoteChannel.class);
List<String> label = remoteChannel.getLabel();
// 127.0.0.0:8080
CHANNEL.addRemoteChannel(label,child.split("#")[0] + ":" + DispatchConfig.DISPATCH_PORT);
} catch (KeeperException | InterruptedException | IOException e) {
e.printStackTrace();
}
}
}
public static void createZkRemotesNode(){
try {
zooKeeper.create(REMOTES_PATH + "/" + IpUtils.getRealIp(), IpUtils.getRealIp().getBytes(), OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static boolean createChannel(String remoteIp, String info){
try {
synchronized (zooKeeper){
zooKeeper.create(PATH + remoteIp , info.getBytes(), OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}
// /channel/127.0.0.1#0
} catch (KeeperException | InterruptedException e) {
LOGGER.error("create remote channel error !");
e.printStackTrace();
}
return true;
}
public static void deleteLocal(){
try {
zooKeeper.delete(REMOTES_PATH + "/" + IpUtils.getRealIp(),stat.getVersion());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
public static void deleteNode(String ip) {
try {
zooKeeper.delete(PATH + ip,0);
LOGGER.info("Delete remote ip : {}",ip);
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
}