JavaWeb项目架构之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 {
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> RedisTemplate<String, String> redisTemplate;
<span class="hljs-comment">//注解是基于swagger的API,也可以自行定义</span>
<span class="hljs-meta">@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">logPointCut</span><span class="hljs-params">()</span> {
}
<span class="hljs-meta">@Around("logPointCut()")</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">around</span><span class="hljs-params">(ProceedingJoinPoint point)</span> <span class="hljs-keyword">throws</span> Throwable {
<span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> point.proceed();
<span class="hljs-comment">//把日志消息写入itstyle_log频道</span>
redisTemplate.convertAndSend(<span class="hljs-string">"itstyle_log"</span>,<span class="hljs-string">"日志数据,自行处理"</span>);
<span class="hljs-keyword">return</span> result;
}
}
消费者端
Redis 配置:
<!-- redis 配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" />
<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"jedisConnectionFactory"</span>
<span class="hljs-attr">class</span>=<span class="hljs-string">"org.springframework.data.redis.connection.jedis.JedisConnectionFactory"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"hostName"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"${redis.host}"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"port"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"${redis.port}"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"${redis.password}"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"timeout"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"${redis.timeout}"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"poolConfig"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"jedisPoolConfig"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"usePool"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"true"</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">bean</span>></span>
<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"redisTemplate"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"org.springframework.data.redis.core.RedisTemplate"</span>
<span class="hljs-attr">p:connection-factory-ref</span>=<span class="hljs-string">"jedisConnectionFactory"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"keySerializer"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"org.springframework.data.redis.serializer.StringRedisSerializer"</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">property</span>></span>
<span class="hljs-tag"><<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"hashKeySerializer"</span>></span>
<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"org.springframework.data.redis.serializer.StringRedisSerializer"</span> /></span>
<span class="hljs-tag"></<span class="hljs-name">property</span>></span>
<span class="hljs-tag"></<span class="hljs-name">bean</span>></span>
<span class="hljs-comment"><!-- 监听实现类 --></span>
<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"listener"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"com.itstyle.market.common.listener.MessageDelegateListenerImpl"</span>/></span>
<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"stringRedisSerializer"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"org.springframework.data.redis.serializer.StringRedisSerializer"</span> /></span>
<span class="hljs-tag"><<span class="hljs-name">redis:listener-container</span> <span class="hljs-attr">connection-factory</span>=<span class="hljs-string">"jedisConnectionFactory"</span>></span>
<span class="hljs-comment"><!-- topic代表监听的频道,是一个正规匹配 其实就是你要订阅的频道--></span>
<span class="hljs-tag"><<span class="hljs-name">redis:listener</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"listener"</span> <span class="hljs-attr">serializer</span>=<span class="hljs-string">"stringRedisSerializer"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"handleLog"</span> <span class="hljs-attr">topic</span>=<span class="hljs-string">"itstyle_log"</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">redis:listener-container</span>></span>
监听接口:
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 代理 <aop:aspectj-autoproxy proxy-target-class="true" />。
开源项目源码 (参考):https://gitee.com/52itstyle/spring-boot-mail