基于servlet并发的日志存储(下)

为了解决基于servlet并发的日志存储(上)中提到的,无法满足多个url同时请求的的问题,因此有了这个版本的优化。

这个版本重点解决了多个请求生成多个文件的情景。其整体思路为:使用一个ConcurrentHashMap来存储每一个url对应的content,其Key为String类型,存储每一个请求的path,Value为StringBuffer类型,对应存放content的StringBuffer,每个请求在Map中查找对应的path是否存在,如果存在则直接在对应的Value中追加内容,不存在则创建文件,并将Key添加到Map中。写入文件时使用定时任务,定时的将Map写入到文件中。
以下是具体实现的代码:
Servlet与Global类与基于servlet并发的日志存储(上) 中大体相同

servlet:

package savelog.youxinpai.com;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Timer;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * Servlet implementation class SaveLog
 */
@WebServlet("/SaveLog")
public class SaveLog extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SaveLog() {
        super();
        // TODO Auto-generated constructor stub
    }

    public void init() {
        System.out.println("init Timer.");
        Global.timer = new Timer(true);
        Global.timer.schedule(Data.timeTask(), 0,Global.waitTime);
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        request.setCharacterEncoding("UTF-8");
        PrintWriter writer = response.getWriter();
        writer.println("DIR1:"+request.getParameter("dir1"));
        writer.println("DIR2:"+request.getParameter("dir2"));
        writer.println("DIR3:"+request.getParameter("dir3"));
        writer.println("filename:"+request.getParameter("filename"));
        writer.println("content:"+request.getParameter("content"));
        writer.println(request.getCharacterEncoding());

        if(request.getParameter("dir1")==null){
            writer.println("dir1 muxt exist");
            return;
        }

        //String path=request.getParameter("dir1")+"\\"+request.getParameter("dir2")+"\\"+request.getParameter("dir3");
        StringBuilder path=new StringBuilder();
        path.append(request.getParameter("dir1"));
        if(request.getParameter("dir2")!=null&&!request.getParameter("dir2").equals(""))
            path.append("\\"+request.getParameter("dir2"));
        if(request.getParameter("dir3")!=null&&!request.getParameter("dir3").equals(""))
            path.append("\\"+request.getParameter("dir3"));
        String filename=request.getParameter("filename");
        String content=new String(request.getParameter("content").getBytes("ISO-8859-1"),"UTF-8");
        writer.println("Path:"+path);
        int mapSize = Data.createPath(path.toString(), filename, content);
        writer.println("MapSize:"+mapSize);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

}

Global:

package savelog.youxinpai.com;

import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

public class Global {

    /*写日志缓冲区*/
    public static StringBuffer stringBuffer=new StringBuffer();
    /*filename标志*/
    public static String filename=new String("");
    /*根路径*/
    public static final String rootPath = "e:\\logroot";
    /*保存路径和内容*/
    public static ConcurrentHashMap<String,StringBuffer> pathMap=new ConcurrentHashMap<String,StringBuffer>();
    /*定义Timer类*/
    public static Timer timer = null;
    /*定时任务等待时间  单位毫秒*/
    public static long waitTime = 200; 
    /*存放每隔path锁的map*/
    public static ConcurrentHashMap<String,Lock> lockMap=new ConcurrentHashMap<String,Lock>();
    /*分隔线*/
    public static String separator ="---";

}

Data类实现了对程序的优化

package savelog.youxinpai.com;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;


public class Data {

    /**
     * 程序入口方法
     * @param path
     * @param filename
     * @param content
     * @return
     */
    public static synchronized int createPath(String path, String filename, String content){
        String saveDirPath=Global.rootPath+"\\"+path+"\\"+getDate();
        String filePath=saveDirPath+"\\"+filename+".log";
        if(Global.pathMap.containsKey(filePath)){
            try{
                Global.lockMap.get(filePath).lock();
                Global.pathMap.put(filePath,Global.pathMap.get(filePath).append(getTime()+Global.separator+content+"\r\n"));
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                Global.lockMap.get(filePath).unlock();
            }
        }else {
            new File(saveDirPath).mkdirs();
            content=getTime()+Global.separator+content+"\r\n";
            createFile(filePath, content);
            Global.pathMap.put(filePath, new StringBuffer());
            Global.lockMap.put(filePath, new ReentrantLock());
        }
        return Global.pathMap.size();
    }

    /**
     * 将map的内容写到文件
     * @param ctMap
     */
    public static synchronized void writeFile(ConcurrentHashMap<String,StringBuffer> ctMap){
        Iterator<Entry<String,StringBuffer>> it = ctMap.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String,StringBuffer> entry = it.next();
            if(entry.getValue().length()<1)
                continue;

            Global.lockMap.get(entry.getKey()).lock();
            try{
                createFile(entry.getKey(), entry.getValue().toString());
                Global.pathMap.put(entry.getKey(), new StringBuffer());
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                Global.lockMap.get(entry.getKey()).unlock();
            }
        }
    }

    /**
     * 根据Map的K,V写文件
     * @param path
     * @param content
     */
    public static void createFile(String path,String content){
        BufferedWriter writer = null; 
        try {
            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path, true),"UTF-8"));
            writer.write(content);
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 定时执行写入文件,清空Map的任务
     * @return
     */
    public static TimerTask timeTask(){
        TimerTask timerTask=new TimerTask() {   
            public void run() {   
                writeFile(Global.pathMap);
            }   
        };   
        return timerTask;
    }

    /**
     * 获得当前日期
     * @return 例:2015-10-11
     */
    private static String getDate(){
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        String dateStr = "" + year + "-" + month + "-" + day;
        return dateStr;
    }

    /**
     * 获得当前时间 例:2015-10-11 15:00:01
     * @return
     */
    private static String getTime(){
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return simpleDateFormat.format(new Date().getTime());
    }
}

改进部分:
1.定时执行部分,使用了Timer类,创建定时任务,定时执行写入从Map中读取数据,写入文件的任务。

定义Timer类,等待时间和存放Path的ConcurrentHashMap

    /*保存路径和内容*/
    public static ConcurrentHashMap<String,StringBuffer> pathMap=new ConcurrentHashMap<String,StringBuffer>();
    /*定义Timer类*/
    public static Timer timer = null;
    /*定时任务等待时间  单位毫秒*/
    public static long waitTime = 200; 

定义TimerTask方法:

    /**
     * 定时执行写入文件,清空Map的任务
     * @return
     */
    public static TimerTask timeTask(){
        TimerTask timerTask=new TimerTask() {   
            public void run() {   
                writeFile(Global.pathMap);
            }   
        };   
        return timerTask;
    }

重写Servlet的init()方法,执行定时任务

    public void init() {
        System.out.println("init Timer.");
        Global.timer = new Timer(true);
        Global.timer.schedule(Data.timeTask(), 0,Global.waitTime);
    }

2.由于涉及到了并发存储。将content写入StringBuffer–>从StringBuffer中读取内容写入到位文件–>清空StringBuffer这一复合操作,会在并发执行时会出现竞技条件问题。因此这里使用了显示锁处理StringBuffer的写入,读取和存储。为尽可能减小锁的冲突,对每一个Path加锁

定义存放锁的ConcurrentHashMap

    /*存放每隔path锁的map*/
    public static ConcurrentHashMap<String,Lock> lockMap=new ConcurrentHashMap<String,Lock>();

初始化每一个path的锁

Global.pathMap.put(filePath, new StringBuffer());
Global.lockMap.put(filePath, new ReentrantLock());

在写入StringBuffer时获得锁,写入结束时释放锁

            try{
                Global.lockMap.get(filePath).lock();
                Global.pathMap.put(filePath,Global.pathMap.get(filePath).append(getTime()+Global.separator+content+"\r\n"));
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                Global.lockMap.get(filePath).unlock();
            }

在执行将StringBuffer写入到文件–>清空StringBuffer的操作时,从ConcurrentHashMap中获得对应的锁,在清空后释放对应锁,这样就可以保证写入StringBuffer和清除StringBuffer是互斥的。

            try{
                createFile(entry.getKey(), entry.getValue().toString());
                Global.pathMap.put(entry.getKey(), new StringBuffer());
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                Global.lockMap.get(entry.getKey()).unlock();
            }

这样就可以保证不同url执行并发的效率。

在实际测试中,使用Http_load工具进行压力测试,22个不同的url,50个并发进行请求,其测试结果为每秒可以执行2000次请求,满足性能需求。

另外,以下代码会存在隐患:

        }else {
            new File(saveDirPath).mkdirs();
            content=getTime()+Global.separator+content+"\r\n";
            createFile(filePath, content);
            Global.pathMap.put(filePath, new StringBuffer());
            Global.lockMap.put(filePath, new ReentrantLock());
        }

由于这部分符合操作并不是原子的,并发执行时会出现一些数据丢失的现象,出于实际性能需求,并发执行这段代码可以大大提高访问的效率,因此暂时不同步这段代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值