思路
1、log的保存不应该被当前线程的session影响,和log4j一样,运行到哪一步就记录哪一步,输出到数据库,不能出现错误回滚或则保存失败。
2、log的保存和插入不能影响当前线程,不能因为日志保存过多而影响当前进程响应速度,或则占用数据库资源影响进程的读写。
解决方法
先将日志产生后放入缓存队列中,不影响当前进程,另起一个进程去消费队列中的日志信息,保存至数据库。
本次使用了redis 的生产消费模式,生产日志,另起线程消费日志
优点: 不影响当前进程,也不受当前进程影响,用redis队列的话,读写速度极快,每秒百万级别基本不会出现堵塞现象。
缺点:需要另起进程消费,虽然读写很快,但是还是做不到实时保存。
步骤:
1、新建日志实体,数据库日志表,可以根据自己的需求创建多个日志实体保存。
public class SystemLoginfo extends BaseEntity<String> implements Serializable {
/** 日志级别 */
@Column(name = "log_level", length = 10)
private String logLevel;
/** 日志打印时间 */
@JsonSerialize(using = JsonDateSerializer.class)
@JsonDeserialize(using = JsonDateDeSerializer.class)
@Column(name = "log_time", length = 30)
private Date logTime;
/** 端口号 */
@Column(name = "port", length = 30)
private String port=AllConstant.getInstance().getLocalPort();
/** 日志信息 */
@Column(name = "message", length = 2000)
private String message;
/** ip地址 */
@Column(name = "ip_address", length = 50)
private String ipAddress= AllConstant.getInstance().getLocalAddr();
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
public Date getLogTime() {
return logTime;
}
public void setLogTime(Date logTime) {
this.logTime = logTime;
}
public String getLogLevel() {
return logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
}
2集成jedis;(这里我用到了集群,所以创建了jedisCluster的bean对象)
<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
<constructor-arg>
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="${redis.cluster.node-1.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.cluster.node-1.port}"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="${redis.cluster.node-2.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.cluster.node-2.port}"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="${redis.cluster.node-3.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.cluster.node-3.port}"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="${redis.cluster.node-4.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.cluster.node-4.port}"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="${redis.cluster.node-5.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.cluster.node-5.port}"></constructor-arg>
</bean>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg name="host" value="${redis.cluster.node-6.host}"></constructor-arg>
<constructor-arg name="port" value="${redis.cluster.node-6.port}"></constructor-arg>
</bean>
</set>
</constructor-arg>
<constructor-arg index="1" value="${redis.timeOut}"></constructor-arg>
<constructor-arg index="2" value="${redis.socketTimeout}"></constructor-arg>
<constructor-arg index="3" value="${redis.maxAttempts}"></constructor-arg>
<constructor-arg index="4" value="${redis.cluster.password}"></constructor-arg>
<constructor-arg index="5" ref="poolConfig"></constructor-arg>
</bean>
3调用 jedisCluster.lpush()左边插入数据(即生产数据)
jedisCluster.lpush(RedisSerializerUtil.serialize(MESSAGE_KEY),RedisSerializerUtil.serialize(message));
4调用jedisCluster.brpop(),从右侧取数据(即消费数据)
wateTime即为等待时间,如果没有数据会等待直至队列中有数据,为0 时会一直等待(
因为redis队列是单线程,所以这个等待会堵塞队列的获取。所以消费线程只需要一个就可以,无需多个)
List<byte[]> message =jedisCluster.brpop(wateTime, RedisSerializerUtil.serialize(Logging.MESSAGE_KEY));
if (null != message && (!message.isEmpty())){
Object object = RedisSerializerUtil.unserialize(message.get(1));
SystemLoginfo entity = (SystemLoginfo)object;
//插入数据库
systemLoginfoDao.save(entity);
}
5总结,redis的特性有很多,本次用到了redis的生产消费模式