以下的内容纯属理论探讨,除非万不得已最好不要这么干,因为会造成集群的有状态化和维护困难。
假设一个集群中有三台tomcat,在发生某件事情(比如接口调用)的时候我需要首先在本机做某件事情,如果做不了,就调用下一台机器,如此往复,直到最后一台。
我们可以将tomcat定义成为一个环,每台保存自己的标识和下一台的标识,调用的时候带上已经经过的节点,如果下一个节点已经在已经经过的节点中,说明就是最后一台了,不能再往下调用。
/**
* @author xiongshiyan at 2018/10/24 , contact me with email yanshixiong@126.com or phone 15208384257
*/
public interface NodeNext {
/**
* 往下执行
* @param payload 需要发送的内容
* @param crossNodes 已经经过的node
*/
String next(String payload , Set<String> crossNodes) throws IOException;
}
/**
* @author xiongshiyan at 2018/10/24 , contact me with email yanshixiong@126.com or phone 15208384257
*/
@Component
public class DefaultNodeNext implements NodeNext {
@Autowired
private SmartHttpClient smartHttpClient;
@Autowired
private Node node;
@Override
public String next(String payload , Set<String> crossNodes) throws IOException{
//如果所有经过的节点包含本节点的下个节点,那么就不干什么
if(crossNodes.contains(node.getNextName())){
return "";
}
if(null == node.getName()){
return "";
}
Request request = Request.of(node.getUrl());
Map<String , Object> map = new HashMap<>(2);
map.put(Node.PAYLOAD, payload);
crossNodes.add(node.getName());
//添加本地的名字
map.put(Node.CROSS_NODES , crossNodes);
request.setBody(JsonUtil.serializeMap(map));
return smartHttpClient.post(request).getBody();
}
}
/**
* 一个tomcat节点,实现环形调用的数据结构,一个节点保存本节点名和下一个节点名,调用的时候带上已经经过的节点,这样就能知道何时结束
* @author xiongshiyan at 2018/10/24 , contact me with email yanshixiong@126.com or phone 15208384257
*/
@ConfigurationProperties(prefix = "spring.circle.send")
@PropertySource(value = "file:/mnt/install/tomcat/circle.properties" , ignoreResourceNotFound = true)
@Component
public class Node {
public static final String PAYLOAD = "payload";
public static final String CROSS_NODES = "crossNodes";
private String name;
private String nextName;
private String url;
...
}
主要的逻辑都在DefaultNextNode中,实现了判断是否应该结束和添加本地节点。Node代表一个节点,有自己的标识名字,下一个节点的标识名字,和访问的URL。为了做到每个tomcat中代码的不修改,这个配置需要放到每个机器的相同位置。
spring.circle.send.name=node1
spring.circle.send.nextName=node2
spring.circle.send.url=http://xxxxxxxx1:8080/api/circle/receive
spring.circle.send.name=node2
spring.circle.send.nextName=node3
spring.circle.send.url=http://xxxxxxxx2:8080/api/circle/receive
spring.circle.send.name=node3
spring.circle.send.nextName=node1
spring.circle.send.url=http://xxxxxxxx3:8080/api/circle/receive
需要在每个tomca中添加一个controller来接收上一台的请求,并作出处理和判断。
@Autowired
private NodeNext nodeNext;
@Autowired
private NodeCurrent nodeCurrent;
@PostMapping(value = "/receive")
public String receive(@RequestBody String json) throws Exception{
boolean jsonObject = JsonUtil.isJsonObject(json);
if(!jsonObject){
return null;
}
JSONObject object = new JSONObject(json);
String payload = object.getString(Node.PAYLOAD);
boolean canDo = nodeCurrent.doWithPayLoad(payload);
if(canDo){
return "";
}
JsonArray crossNodes = object.getJsonArray(Node.CROSS_NODES);
int size = crossNodes.size();
Set<String> nodes = new HashSet<>(size);
for (int i = 0; i < size; i++) {
nodes.add(crossNodes.getString(i));
}
return nodeNext.next(payload , nodes);
}
本地处理的方法接口为NodeCurrent,需要返回是否能处理的标识。
public interface NodeCurrent {
/**
* 当前节点需要做的事情
* @param payload 接收到的数据
* @return 当前是否能做
*/
boolean doWithPayLoad(String payload);
}