架構、分散式、日誌隊列,標題自己都看著唬人,其實就是一個日誌收集的功能,只不過中間加了一個Redis做消息隊列罷了。 前言 為什麼需要消息隊列? 當系統中出現“生產“和“消費“的速度或穩定性等因素不一致的時候,就需要消息隊列,作為抽象層,彌合雙方的差異。 比如我們系統中常見的郵件、簡訊發送,把這些不 ...
架構、分散式、日誌隊列,標題自己都看著唬人,其實就是一個日誌收集的功能,只不過中間加了一個Redis做消息隊列罷了。
前言
為什麼需要消息隊列?
當系統中出現“生產“和“消費“的速度或穩定性等因素不一致的時候,就需要消息隊列,作為抽象層,彌合雙方的差異。
比如我們系統中常見的郵件、簡訊發送,把這些不需要及時響應的功能寫入隊列,非同步處理請求,減少響應時間。
如何實現?
成熟的JMS消息隊列中間件產品市面上有很多,但是基於目前項目的架構以及部署情況,我們採用Redis做消息隊列。
為什麼用Redis?
Redis中list數據結構,具有“雙端隊列”的特性,同時redis具有持久數據的能力,因此redis實現分散式隊列是非常安全可靠的。
它類似於JMS中的“Queue”,只不過功能和可靠性(事務性)並沒有JMS嚴格。Redis本身的高性能和"便捷的"分散式設計(replicas,sharding),可以為實現"分散式隊列"提供了良好的基礎。
提供者端
項目採用第三方redis插件spring-data-redis,不清楚如何使用的請自行谷歌或者百度。
redis.properties:
#redis 配置中心
redis.host=192.168.1.180
redis.port=6379
redis.password=123456
redis.maxIdle=100
redis.maxActive=300
redis.maxWait=1000
redis.testOnBorrow=true
redis.timeout=100000
redis配置:
<!-- redis 配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" />
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="jedisPoolConfig" />
<property name="usePool" value="true" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
切麵日誌配置(偽代碼):
/**
* 系統日誌,切麵處理類
* 創建者 張志朋
* 創建時間 2018年1月15日
*/
@Component
@Scope
@Aspect
public class SysLogAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
//註解是基於swagger的API,也可以自行定義
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();
//把日誌消息寫入itstyle_log頻道
redisTemplate.convertAndSend("itstyle_log","日誌數據,自行處理");
return result;
}
}
消費者端
Redis配置:
<!-- redis 配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" />
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="jedisPoolConfig" />
<property name="usePool" value="true" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory">
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
</bean>
<!-- 監聽實現類 -->
<bean id="listener" class="com.itstyle.market.common.listener.MessageDelegateListenerImpl"/>
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<redis:listener-container connection-factory="jedisConnectionFactory">
<!-- topic代表監聽的頻道,是一個正規匹配 其實就是你要訂閱的頻道-->
<redis:listener ref="listener" serializer="stringRedisSerializer" method="handleLog" topic="itstyle_log"/>
</redis:listener-container>
監聽介面:
public interface MessageDelegateListener {
public void handleLog(Serializable message);
}
監聽實現:
public class MessageDelegateListenerImpl implements MessageDelegateListener {
@Override
public void handleLog(Serializable message) {
if(message == null){
System.out.println("null");
}else {
//處理日誌數據
}
}
}
Q&A
【問題一】為什麼使用Redis?
上面其實已經有做說明,儘管市面上有許多很穩定的產品,比如可能大家會想到的Kafka、RabbitMQ以及RocketMQ。但是由於項目本身使用了Redis做分散式緩存,基於省事可行的原則就選定了Redis。【問題二】日誌數據如何存儲?
原則上是不建議存儲到關係資料庫的,比如MySql,畢竟產生的日誌數量是巨大的,建議存儲到Elasticsearch等非關係型資料庫。【問題三】切麵日誌收集是如何實現的?
切麵日誌需要引入spring-aspects相關Jar包,並且配置使Spring採用CGLIB代理
開源項目源碼(參考):https://gitee.com/52itstyle/spring-boot-mail