目前正在做基于Red 5 的Meeting系统,我们会在Meeting server上生成一个唯一的StreamKey,用于RTMP协议中,NetStream.publish的StreamID,但是有一个问题,我们调用allocateStream的时候,如果一个会议中的多个成员并发请求这个服务时(Meeting server上生成一个唯一的StreamKey),会造成同一个meeting,使用不同的StreamID。
在单虚拟机的情况下,可以使用Synchronized原生锁来对同一个RoomKey进行加锁,如果reques的是同一个RoomKey,则需要排队处理,直到拿到这个StreamKey为止。
此例只适用于单JVM情况下的控制,如果在多虚拟机情况下,需要采用分布式锁的解决方案,可以用ZoomKeeper实现一个基于RoomKey的分布式锁逻辑,以后有空,会写出一个基于ZoomKeeper的分布式锁的版本。
注:此类并发问题,是经常会遇到的,写这篇blog,一方面提出解决方案,另外一方面希望初学者能够有所理解并注意并发访问而产生的问题
1.Class 1
package org.developerworks.test;
import UUIDGenerator;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* <p>Description:
*
* 这个类源自于一个需求:当并发访问时,同一个RoomKey对应只能生成一次UUID,
* 因此需要把RoomKey作为一个对象锁,保证请求同一RoomKey锁的操作要进行同步(或者称为锁定)
* 以保证同一个RoomKey只生成一个UUID
*
* 同步方法不能影响并发性(吞吐量),比如对于不同的RoomKey,并应该允许并发创建
* 对于相同的RoomKey,应该锁定
*
* 此例关键点在于要使用RoomKey作为Synchronized的锁对象,已达到同一个RoomKey,采用排它锁,
* 已避免同一个RoomKey生成了同多个StreamKey的作用
*
* </p>
* <p>Copyright (c) 2010</p>
* <p>Department: None</p>
* <p>Company: None</p>
* <p>Project:Test</p>
*
* @author Tomy Zhou
* @since 1.0
* @version org.developerworks.test; JoinServlet.java
* 2010-7-26
**/
public class JoinServlet extends HttpServlet{
public static ConcurrentHashMap<String, String> map = new ConcurrentHashMap(20);
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String roomKey = req.getParameter("roomKey");
RoomKeySingleton roomKeySingleton = RoomKeySingleton.getInstance(roomKey);
String streamKey = null;
String newUUID = null;
//If the streamKey is not existed on the server, then create one.
//If existed on the server, use the existed
synchronized (roomKeySingleton) {
if(map.get(roomKey)==null){
System.out.println("Concurrent write="+roomKey);
newUUID = UUIDGenerator.getUUID();
map.put(roomKey, newUUID);
System.out.println("Concurrent write complete="+roomKey);
}
}
resp.getWriter().println(roomKey+"-"+newUUID);
}
}
2.Class 2
package org.developerworks.test;
import java.util.concurrent.ConcurrentHashMap;
/**
* <p>Description: WeatherServer</p>
* <p>Copyright (c) 2010</p>
* <p>Department: None</p>
* <p>Company: None</p>
* <p>Project:WeatherServer</p>
*
* 此类用于为每个RoomKey建立一个单例的对象,对应来说,
* 一个RoomKey对应唯一一个对象
* @author Tomy Zhou
* @since 1.0
* @version org.developerworks.test; RoomKeySingleton.java
* 2010-7-26
**/
public class RoomKeySingleton {
private static RoomKeySingleton roomKeySingleton;
private static String roomKey;
private static ConcurrentHashMap<String, RoomKeySingleton> map = new ConcurrentHashMap<String, RoomKeySingleton>();
private RoomKeySingleton(String roomKey){
this.roomKey = roomKey;
}
public static synchronized RoomKeySingleton getInstance(String roomKey){
if(map.get(roomKey)==null){
roomKeySingleton = new RoomKeySingleton(roomKey);
map.put(roomKey, roomKeySingleton);
}
return map.get(roomKey);
}
}
3. Class 3
package org.developerworks.test;
import java.util.UUID;
public class UUIDGenerator {
public UUIDGenerator() {
}
/**
* 获得一个UUID
* @return String UUID
*/
public static String getUUID(){
String s = UUID.randomUUID().toString();
//去掉“-”符号
return s.substring(0,8)+s.substring(9,13)+s.substring(14,18)+s.substring(19,23)+s.substring(24);
}
/**
* 获得指定数目的UUID
* @param number int 需要获得的UUID数量
* @return String[] UUID数组
*/
public static String[] getUUID(int number){
if(number < 1){
return null;
}
String[] ss = new String[number];
for(int i=0;i<number;i++){
ss[i] = getUUID();
}
return ss;
}
public static void main(String[] args){
String[] ss = getUUID(10);
for(int i=0;i<ss.length;i++){
System.out.println(ss[i]);
}
}
}