【Redis】redis非同步訊息佇列+Spring自定義註解+AOP方式實現系統日誌持久化
阿新 • • 發佈:2019-01-02
說明:
SSM專案中的每一個請求都需要進行日誌記錄操作。一般操作做的思路是:使用springAOP思想,對指定的方法進行攔截。拼裝日誌資訊實體,然後持久化到資料庫中。可是仔細想一下會發現:每次的客戶端的每一次請求,伺服器都會處理兩件事情。一個是正常的業務操作;另一個就是我們額外要做的日誌資料記錄。這樣的話,每次請求的“效率”就變得收到影響了,換句話說就是“耦合”了。明明一個請求是幹一件特定的事情,你卻又給我加上一部分東西。而且這一次請求是必須在額外做的事情做完才能返回。面向切面 程式設計就是為了“解耦”的。所以想到了日誌持久化這個動作使用非同步處理方式,不當誤真正的請求效率。(這一段寫的可能有點luan,大家先將就著看)。
分析:
① 非同步訊息佇列中有【消費者】和【生產者兩個角色】。生產者負責產生訊息,並放入佇列中。消費者負責監聽佇列,一旦佇列中有新的訊息了,取出後根據訊息的型別選擇對應的業務處理操作。
② 消費者在這裡是在系統啟動的時候,啟動一個執行緒,對redis指定的key進行監聽。使用redis的指令brpop阻塞指令進行監聽對應的list。
環境:
jdk1.8、maven、idea、jedis3.2、mysql資料庫
程式碼:
自定義註解:
/** * 自定義系統日誌註解 * @author 魏正迪 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)public @interface SysLog { /** * 操作描述 * @return */ String value() default ""; /** * 日誌型別 * @return */ short type(); }
AOP切面
/** * @author wzd * @data 2018/03/06 * 系統日誌切面 */ @Component @Aspect public class LogAspect { @Autowired private ILogService logService; @Autowiredprivate JedisClientPool jedisClientPool; /** * 自動註冊當前執行緒的request物件 */ @Autowired private HttpServletRequest request; /** * 日誌的切點 */ @Pointcut("@annotation(top.oldwei.common.annotation.SysLog)") public void logPoint(){ } /** * 日誌採用環繞通知來進行處理 * @param point * @return * @throws Throwable */ @Around("logPoint()") public Object around(ProceedingJoinPoint point)throws Throwable{ // 執行方法之前 UserEntity currentUser = ShiroUtils.getUserEntity(); long start = SystemClock.now(); Object result = point.proceed(); long end = SystemClock.now(); saveSysLog(point,end-start,currentUser); return result; } /** * 儲存日誌操作 * @param point * @param time * @param userEntity */ private void saveSysLog(ProceedingJoinPoint point,long time ,UserEntity userEntity){ try{ MethodSignature methodSignature = (MethodSignature) point.getSignature(); Method method = methodSignature.getMethod(); LogEntity logEntity = new LogEntity(); logEntity.setId(IdWorker.getId()); SysLog syslog = method.getAnnotation(SysLog.class); if(StringUtils.checkValNotNull(syslog)){ // 註解的value logEntity.setOperation(syslog.value()); // 註解的type logEntity.setType(syslog.type()); } // 呼叫的方法 logEntity.setMethod(point.getTarget().getClass().getName()+"."+method.getName()+"()"); logEntity.setIp(IpUtils.getIpAddr(request)); logEntity.setTime(time); // 請求引數 Object [] args = point.getArgs(); try{ logEntity.setParams(JSON.toJSON(args[0]).toString()); }catch (Exception e){} if(StringUtils.checkValNotNull(userEntity)){ // 建立人 logEntity.setCreateByCode(userEntity.getUserCode()); logEntity.setCreateByName(userEntity.getUserName()); }else{ // 登入操作時,方法執行後才能獲取使用者資訊 userEntity = ShiroUtils.getUserEntity(); if(StringUtils.checkValNotNull(userEntity)){ logEntity.setCreateByCode(userEntity.getUserCode()); logEntity.setCreateByName(userEntity.getUserName()); }else{ logEntity.setCreateByCode(""); logEntity.setCreateByName(""); } } logEntity.setCreateDate(new Date()); // 使用redis非同步佇列方式進行儲存日誌 //logService.save(logEntity); TaskEntity taskEntity = new TaskEntity(); taskEntity.setTaskType(TaskType.LOG_TASK); taskEntity.setData(JSON.toJSONString(logEntity)); jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity)); taskEntity.setTaskType(TaskType.MAIL_TASK); jedisClientPool.lpush(JedisConstants.AYSC_TASK_KEY,JSON.toJSONString(taskEntity)); }catch (Exception e){ e.printStackTrace(); } } }
訊息實體類
/** * 任務實體類 * @author wzd * @date 2018/04/01 */ public class TaskEntity implements Serializable { /** * 任務的唯一性編碼 */ private Long id; /** * 任務型別,通過型別找到對應任務處理器進行處理 */ private TaskType taskType; /** * 需要傳輸的資料 json格式的 */ private String data; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public TaskType getTaskType() { return taskType; } public void setTaskType(TaskType taskType) { this.taskType = taskType; } public String getData() { return data; } public void setData(String data) { this.data = data; } }
消費者:啟動註冊消費者任務處理器多個。監聽佇列,取出任務根據任務型別選擇對應的 任務處理器進行相應處理。
/** * redis 佇列消費者 * 容器啟動時載入並啟動相應的執行緒,進行阻塞讀取redis * 對應的任務佇列。根據任務的型別選擇對應的任務處理器進行處理。 * @author wzd * @data 2018/04/01 */ @Component public class TaskConstomer implements InitializingBean, ApplicationContextAware { /** * spring上下文 */ private ApplicationContext applicationContext; /** * 載入所有的任務處理器 */ private Map<TaskType, List<TaskHandler>> config = new HashMap<>(); /** * redis操作 */ @Autowired private JedisClientPool jedisClientPool; @Override public void afterPropertiesSet() throws Exception { // 獲取系統所有實現TaskHandler的任務處理器 Map<String,TaskHandler> handlers = applicationContext.getBeansOfType(TaskHandler.class); if(StringUtils.checkValNotNull(handlers)){ for(Map.Entry<String,TaskHandler> entry:handlers.entrySet()){ List<TaskType> supportTaskTypes = entry.getValue().getTaskType(); for(TaskType taskType:supportTaskTypes){ if(!config.containsKey(taskType)){ config.put(taskType,new ArrayList<TaskHandler>()); } config.get(taskType).add(entry.getValue()); } } } // 啟動執行緒 // 構建執行緒池 ExecutorService executorService = Executors.newCachedThreadPool(); Thread thread = new Thread(new Runnable() { @Override public void run() { while (true){ List<String> task = jedisClientPool.brpop(10, JedisConstants.AYSC_TASK_KEY); if(StringUtils.checkValNotNull(task) && task.size()>1 ){ TaskEntity entity = JSON.parseObject(task.get(1),TaskEntity.class); if(config.containsKey(entity.getTaskType())){ for(TaskHandler handler:config.get(entity.getTaskType())){ handler.doTask(entity); } } } } } }); thread.start(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
任務處理器介面:
/** * @author wzd * 非同步任務通用介面 */ public interface TaskHandler { /** * 執行任務 * @param taskEntity */ void doTask(TaskEntity taskEntity); /** * 任務型別 * * @return */ default List<TaskType> getTaskType(){ return new ArrayList<>(); } }
日誌任務
/** * 日誌處理任務 * @author wzd */ @Component public class LogTaskHandler implements TaskHandler { @Autowired private ILogService logService; @Override public void doTask(TaskEntity taskEntity) { try{ LogEntity logEntity = JSON.parseObject(taskEntity.getData(),LogEntity.class); logService.save(logEntity); }catch (Exception e){} } @Override public List<TaskType> getTaskType() { return Arrays.asList(TaskType.LOG_TASK); } }
傳送郵件任務
/** * @author wzd * 傳送簡訊的非同步佇列任務 */ @Component public class MailTaskHandler implements TaskHandler{ @Autowired private MailMessageHandler mailMessageHandler; @Override public void doTask(TaskEntity taskEntity) { // 進行傳送簡訊的業務邏輯 try{ mailMessageHandler.doSend(null); }catch (Exception e){ e.printStackTrace(); } } @Override public List<TaskType> getTaskType() { return Arrays.asList(TaskType.MAIL_TASK); } }
、、、、、
其他的任務實現介面即可。
特殊說明:以上程式碼需要重構的地方很多,僅給大家參考思路。也歡迎指正