本文主要参考Lamport算法,是对多进程调用共享资源算法的一个具体实现.业务场景为分布式中,使用dubbo调用后台某个服务,该服务部署有多个,服务中有对hdfs写的操作,要知道hdfs每次只支持一个客户端去写,所以这就是个多进程互斥访问共有资源的问题.下面是主要实现代码,具体业务可根据使用场景做调整:
public class WriteHDFS {
//本地用一个LinkedHashMap做时间戳存储
private static TreeMap<Long, String> queue = new TreeMap<Long, String>();
//每个服务初始化序号为0
private static long num = 0;
//最长心跳间隔
private static int MAX_TIME = 10000;
private static Date time1 = null;
private static Date time2 = null;
static Consumer statToHdfs = null;
public static void WriteHdfs(StatService statService, UserService userService) throws Exception {
//服务所在主机地址
String local = InetAddress.getLocalHost().getHostAddress();
InetAddress ip = InetAddress.getByName("255.255.255.255");
DatagramSocket sendDs = new DatagramSocket();
//将本地序号,ip封装后广播发送
HashMap<Long, String> sendMap = new HashMap<Long, String>();
/**
* 接收广播线程,更新序号
*/
new Thread(new Runnable(){
@Override
public void run() {
//接收广播,将序号更新
DatagramSocket recvDs = null;
try {
recvDs = new DatagramSocket(8888);
byte[] buf = new byte[1024];
DatagramPacket recvDp = new DatagramPacket(buf, buf.length);
recvDs.receive(recvDp);
String revStr = recvDp.getData().toString();
JSONObject data = JSONObject.parseObject(revStr);
if (data.containsKey("startData") || data.containsKey("endData")) {
@SuppressWarnings("unchecked")
HashMap<Integer, String> revMap = (HashMap<Integer, String>) data.get("startData");
//遍历revMap,并放入本地队列
for(Integer key:revMap.keySet()) {
if (num <= key)
num = key;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
recvDs.close();
}
}
}).start();
/**
* 发送广播线程,更新队列
*/
new Thread(new Runnable(){
@Override
public void run() {
//DatagramSocket sendDs = null;
//当要获取资源时,序号自增1,并广播通知其他服务
try {
num++;
sendMap.put(num, local);
JSONObject startObj = new JSONObject();
startObj.put("startData", sendMap);
String startStr = startObj.toJSONString();
DatagramPacket sendDp = new DatagramPacket(startStr.getBytes(),startStr.getBytes().length, ip, 8888);
sendDs.send(sendDp);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
/**
* 接收广播,更新队列判断是否可以获取资源
*/
new Thread(new Runnable() {
@Override
public void run() {
//接收广播,并比较key-value,直到获取资源
while(true) {
//比较queue中最小key和此时num,以及主机ip,如果相同则表示可以获得资源
if (queue.firstKey() == num && queue.get(queue.firstKey()) == local) {
// 开启将数据周期写入Hdfs线程
//此处为写hdfs业务逻辑,已省略xxxxxxx
//queue移除首元素
queue.remove(queue.firstKey());
//资源释放,广播通知其他服务
JSONObject endObj = new JSONObject();
endObj.put("endData", sendMap);
String endStr = endObj.toJSONString();
DatagramPacket freeDp = new DatagramPacket(endStr.getBytes(),endStr.getBytes().length, ip, 8888);
try {
sendDs.send(freeDp);
} catch (Exception e) {
e.printStackTrace();
}
sendDs.close();
break;
}
DatagramSocket ds = null;
try {
ds = new DatagramSocket(8888);
} catch (Exception e) {
e.printStackTrace();
}
byte[] bf = new byte[1024];
DatagramPacket dp = new DatagramPacket(bf, bf.length);
try {
ds.receive(dp);
} catch (Exception e) {
e.printStackTrace();
}
String str = dp.getData().toString();
JSONObject obj = JSONObject.parseObject(str);
if (obj.containsKey("startData")) {
@SuppressWarnings("unchecked")
HashMap<Long, String> revMap = (HashMap<Long, String>) obj.get("startData");
//遍历revMap,并放入本地队列
for(Long key:revMap.keySet()) {
if (!queue.containsKey(key)) {
queue.put(key, revMap.get(key));
//建立和正在使用资源服务的心跳连接,如果挂掉就从队列删除key-value
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
//向正在使用资源的服务发送心跳包
try {
@SuppressWarnings("resource")
DatagramSocket reqSocket = new DatagramSocket();
JSONObject reqObj = new JSONObject();
reqObj.put("requestData", local);
String reqStr = reqObj.toJSONString();
DatagramPacket reqDp = new DatagramPacket(reqStr.getBytes(),
reqStr.getBytes().length, new InetSocketAddress(revMap.get(key), 8888));
reqSocket.send(reqDp);
time1 = new Date();
} catch (Exception e) {
e.printStackTrace();
}
}
}, 1000, 3000);
if(time1 != null && time2 != null && Math.abs(time1.getTime() - time2.getTime())/1000 > MAX_TIME) {
queue.remove(key);
}
}
}
}
if (obj.containsKey("requestData")) {
String reqHost = (String) obj.get("requestData");
try {
@SuppressWarnings("resource")
DatagramSocket repSocket = new DatagramSocket();
JSONObject repObj = new JSONObject();
repObj.put("reponseData", local);
String repStr = repObj.toJSONString();
DatagramPacket repDp = new DatagramPacket(repStr.getBytes(),
repStr.getBytes().length, new InetSocketAddress(reqHost, 8888));
repSocket.send(repDp);
} catch (Exception e) {
e.printStackTrace();
}
}
if (obj.containsKey("reponseData")) {
time2 = new Date();
}
if (obj.containsKey("endData")) {
@SuppressWarnings("unchecked")
HashMap<Long, String> revMap = (HashMap<Long, String>) obj.get("endData");
//遍历revMap,从本地队列删除对应key-value
for(Long key:revMap.keySet()) {
if (queue.containsKey(key))
queue.remove(key);
}
}
}
}
}).start();
}
}
需要用三个线程实现,主要原因是接受广播的方法是阻塞的.一个线程用来发送广播,一个用来接收广播,最后一个用来判断本地队列是否获取资源,如果为否则继续接收广播更新队列;队列用TreeMap顺序存储,如果获取资源的服务挂掉则可以通过心跳判断来更新本地队列;