余晖落尽暮晚霞,黄昏迟暮远山寻
本站
当前位置:网站首页 > 编程知识 > 正文

01.mybatis 一级、二级缓存源码解读

xiyangw 2023-05-13 16:07 10 浏览 0 评论


课程标题《mybatis框架源码解读一级、二级缓存源码解读》
课程内容:
1.什么是一级、二级缓存
2.一级缓存特点?源码解读、优缺点
3.如何禁止使用一级缓存
4.spring中底层如何使用一级缓存
5.什么是二级缓存?如何开启二级缓存
6.二级缓存的优缺点有哪些

01.mybatis 一级、二级缓存源码解读


mybatis一级、二级缓存概述

缓存基本越小 查询速度越快、缓存内容越少
缓存基本越大 查询速度越慢 缓存非常多内容

多级缓存概念

之前学习到多级缓存查询方式

先查询一级、一级缓存如果没有
在查询二级 二级缓存没有在查询数据库

在mybatis中反过来

先查询二级、二级如果没有在查询一级、一级如果没有在查询

数据库。


BaseExecutor 属于一级缓存执行器

CachingExecutor 属于二级缓存执行器


缓存 缓存key 、缓存value


1.Mybatis 中有一级缓存和二级缓存,采用装饰设计模式;

2.默认情况下一级缓存是开启的,而且是不能关闭的 ,一级缓存是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。

3.二级缓存是指可以跨 SqlSession 的缓存。 是 mapper 级别的缓存,对于 mapper 级别的缓存不同的sqlsession 是可以共享的,需要额外整合到第三方缓存 例如Redis、MongoDB、oscache、ehcache等。

一级、二级缓存 采用装饰模式设计封装。

mybatis一级缓存源码解读

一级缓存特点

一级缓存也叫本地缓存,在MyBatis中,一级缓存是在会话(SqlSession)层面实现的,这就说明一级缓存作用范围只能在同一个SqlSession中,多个不同的SqlSession是无效的。

MyBatis中一级缓存是默认开启的,不需要任何额外配置


一级缓存效果演示

        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 1.定义mybatis-config.xml
        String resource = "mybatis-config.xml";
        // 2.读取mybatis-config.xml
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //3. 创建SqlSessionFactoryBuilder.build(inputStream) 建造者  键盘快捷键ctrl+点击方法
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //4.获取到session
        SqlSession sqlSession = sqlSessionFactory.openSession();
        System.out.println("第一次查询:");
        List<UserEntity> userList1 = sqlSession.selectList("com.mayikt.mapper.UserMapper.getByUsers2",
                UserEntity.class);
        System.out.println("userList1:" + userList1);
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        System.out.println("第二次查询:");
        List<UserEntity> userList2 = sqlSession2.selectList("com.mayikt.mapper.UserMapper.getByUsers2",
                UserEntity.class);
        System.out.println("userList2:" + userList2);

如果是在同一个sqlSession 第二次在查询 则会走一级缓存 不会查询db。

通过日志可以得出第二次没有查询db 走的是 一级缓存返回数据:

一级缓存源码解读

第一次发出一个查询 sql, sql 查询结果写入 sqlsession 的一级缓存中,缓存使用的数据结构是一个Map集合。

key: MapperID+offset+limit+Sql+所有的入参

value:缓存的数据

同一个 sqlsession 再次发出相同的 sql,就从缓存中取出数据。如果两次中间出现 commit 操作(修改、添加、删除),本 sqlsession 中的一级缓存区域全部清空,下次再去缓存中查询不到所以要从数据库查询, 从数据库查询到再写入缓存。

1.默认是开启了二级缓存,则应该将二级缓存关闭

在mayikt-config.xml 设置 关闭二级缓存

    <settings>
        <setting name="cacheEnabled" value="false"/>
        <!-- 打印sql日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

2.执行到org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)

3.执行到org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 拼接缓存key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }


CacheKey主要是由以下6部分组成

1、将Statement中的id添加到CacheKey对象中的updateList属性

2、将offset(分页偏移量)添加到CacheKey对象中的updateList属性(如果没有分页则默认0)

3、将limit(每页显示的条数)添加到CacheKey对象中的updateList属性(如果没有分页则默认Integer.MAX_VALUE)

4、将sql语句(包括占位符?)添加到CacheKey对象中的updateList属性

5、循环用户传入的参数,并将每个参数添加到CacheKey对象中的updateList属性

6、如果有配置Environment,则将Environment中的id添加到CacheKey对象中的updateList属性

4.一级缓存核心代码

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 如果list是为null 则调用 localCache.getObject(key) 是为一级缓存如果
      // 如果一级缓存不为null 则直接返回一级缓存数据
      // 否则查询db
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
      //查询db 
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }


PerpetualCache 为一级缓存 底层采用Map集合实现

/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;

/**
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<Object, Object>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}


一级缓存没有数据 则查询db,如果db中有数据 则将db中数据返回给一级缓存。

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

当我们一级缓存有数据之后 在同一个sqlsession 则会使用一级缓存中数据,不会查询db。

List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }

第二次查询时,判断如果一级缓存有数据 则 list返回数据 就是 从一级缓存中查询的数据

一级缓存优缺点

1. Mybatis的一级缓存存放在SqlSession的生命周期,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个Map对象中。

如果同一个SqlSession中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map缓存对象中已经存在改键值时,则会返回缓存中的对象。(一个SqlSession连续两次查询 得到的是同一个java对象)

任何的insert update delete操作都会清空一级缓存(增删改任何记录都会清空当前SqlSession的缓存)。

2.如果服务器集群的时候,每个sqlSession有自己独立的缓存相互之间不存在共享,所以在服务器集群的时候容易产生查询数据冲突问题。


一级缓存依赖jvm--而与jvm没有解耦

如果缓存数据过多的情况下 容易造成内存溢出的问题

如何禁止使用mybatis一级缓存

方案1 在sql语句上 随机生成 不同的参数 存在缺点:map集合可能爆 内存溢出的问题

方案2 开启二级缓存(共享 依赖Redis实现)

方案3 使用sqlSession强制清除缓存

方案4 创建新的sqlSession连接。

2022年7月16日22:04开始

一级缓存在什么时候清除?

1.提交事务的时候

sqlSession.commit();//提交事务 把一级缓存中数据给清除掉的

@Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();// 一级缓存对应的 map集合中数据 清空
      localOutputParameterCache.clear();
    }

2.执行 insert、delete、update语句的时候


Spring整合Mybatis的时候一级缓存的问题

Spring中底层有一个模板 SqlSessionTemplate 封装 mybatis sql session

1.在未开启事务的情况之下,每次查询spring都会关闭旧的sqlSession而创建新的sqlSession,因此此时的一级缓存是没有启作用的

2.在开启事务的情况之下,spring模板使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的

spring中sql session 被代理 SqlSessionInterceptor invoke

sql session.selectList方法()

场景1:

发送多次请求

场景2:2

知道 sqlsession.反射机制 调用selectList方法







mybatis二级缓存源码解读

mybatis二级缓存特点

1.二级缓存的范围是 mapper 级别( mapper 同一个命名空间), mapper 以命名空间为单位创建缓存数据结构,结构是 map, mybatis 的二级缓存是通过 CacheExecutor 实现的。 CacheExecutor其实是 Executor 的代理对象,所有的查询操作,在 CacheExecutor 中都会先匹配缓存中是否存在,不存在则查询数据库,该过程采用装饰模式设计

key: MapperID+offset+limit+Sql+所有的入参

具体使用需要配置:

Mybatis 全局配置中启用二级缓存配置

在对应的 Mapper.xml 中配置 cache 节点

在对应的 select 查询节点中添加 useCache=true


开启mybatis二级缓存

1.mybatis 配置文件 默认开启了二级缓存配置

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.MybatisRedisCache(先启动Redis)

package com.mayikt.cache;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.mayikt.utils.SerializeUtil;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

public class MybatisRedisCache implements Cache {
    private static Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);

    private Jedis redisClient = createReids();

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private String id;

    public MybatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>MybatisRedisCache:id=" + id);
        this.id = id;
    }


    public String getId() {
        return this.id;
    }


    public int getSize() {

        return Integer.valueOf(redisClient.dbSize().toString());
    }


    public void putObject(Object key, Object value) {
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>putObject:" + key + "=" + value);
        redisClient.set(SerializeUtil.serialize(key.toString()), SerializeUtil.serialize(value));
    }

    public Object getObject(Object key) {
        Object value = SerializeUtil.unserialize(redisClient.get(SerializeUtil.serialize(key.toString())));
        logger.debug(">>>>>>>>>>>>>>>>>>>>>>>>getObject:" + key + "=" + value);
        return value;
    }


    public Object removeObject(Object key) {
        return redisClient.expire(SerializeUtil.serialize(key.toString()), 0);
    }


    public void clear() {
        redisClient.flushDB();
    }


    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }

    protected static Jedis createReids() {
        JedisPool pool = new JedisPool("127.0.0.1", 6379);
        return pool.getResource();
    }
}

3.UserMapper 中配置:

    <cache eviction="LRU" type="com.mayikt.cache.MybatisRedisCache"/>

mybatis源码解读

1、创建一级缓存的CacheKey

2、获取二级缓存

3、如果没有获取到二级缓存则执行被包装的Executor对象中的query方法,此时会走一级缓存中的流程。

4、查询到结果之后将结果进行缓存。


1.先执行org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)


  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

执行到二级缓存org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 如果开启了二级缓存 则cache不为null
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        // 读取二级缓存中数据
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
           // 如果二级缓存没有数据 则开始调用一级缓存查询数据
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 在将一级缓存数据存入到map集合中  
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

二级缓存底层也是采用 HashMap集合实现

/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.cache;

import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.cache.decorators.TransactionalCache;

/**
 * @author Clinton Begin
 */
public class TransactionalCacheManager {

  private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }

}
       List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            // 回调到我们自定义的  将数据存入到redis中
            tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;



笔记


@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
       // 先查询我们的一级缓存 一级返回如果没有数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
         // 一级返回如果没有数据 则开始查询db
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
     // 在查询db之前 该缓存的内容 占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 开始查询db
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        // 删除占位符
      localCache.removeObject(key);
    }
      // 在将我们db中数据返回到一级缓存中存放
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }


PerpetualCache 一级缓存 底层就算一个map集合

key

value


一级缓存默认就是开启




  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //判断是否配置了我们二级缓存 如果配置了cache != null
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
          // 先查询二级缓存---(redis)
        List<E> list = (List<E>) tcm.getObject(cache, key);
          // 如果二级缓存中没有该数据 则查询 简单执行器
        if (list == null) {
            // 简单执行器---base执行器  调用一级缓存查询-----装饰模式
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            // 如果一级缓存中能够查询到数据 则将一级缓存数据 存放到二级缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }


源码解读:

二级缓存在什么时候存放数据到redis中呢?

1.先将一级缓存中数据 先放入到 二级缓存临时 map集合中

   tcm.putObject(cache, key, list);
   entriesToAddOnCommit.put(key, object);


2.当我们调用commit 将二级缓存临时 map集合 中数据 遍历 写入到redis中

  @Override
  public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    tcm.commit();
  }

private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
         // 将数据写入到redis中缓存中
        delegate.putObject(entry, null);
      }
    }
  }

相关代码

一级、二级缓存源码解读.rar

相关推荐

辞旧迎新,新手使用Containerd时的几点须知

相信大家在2020年岁末都被Kubernetes即将抛弃Docker的消息刷屏了。事实上作为接替Docker运行时的Containerd在早在Kubernetes1.7时就能直接与Kubelet集成使...

分布式日志系统ELK+skywalking分布式链路完整搭建流程

开头在分布式系统中,日志跟踪是一件很令程序员头疼的问题,在遇到生产问题时,如果是多节点需要打开多节点服务器去跟踪问题,如果下游也是多节点且调用多个服务,那就更麻烦,再者,如果没有分布式链路,在生产日志...

Linux用户和用户组管理

1、用户账户概述-AAA介绍AAA指的是Authentication、Authorization、Accounting,即认证、授权和审计。?认证:验证用户是否可以获得权限,是3A的第一步,即验证身份...

linux查看最后N条日志

其实很简单,只需要用到tail这个命令tail-100catalina.out输入以上命令,就能列出catalina.out的最后100行。...

解决linux系统日志时间错误的问题

今天发现一台虚拟机下的系统日志:/var/log/messages,文件时间戳不对,跟正常时间差了12个小时。按网上说的执行了servicersyslogrestart重启syslog服务,还是不...

全程软件测试(六十二):软件测试工作如何运用Linux—读书笔记

从事过软件测试的小伙们就会明白会使用Linux是多么重要的一件事,工作时需要用到,面试时会被问到,简历中需要写到。对于软件测试人员来说,不需要你多么熟练使用Linux所有命令,也不需要你对Linux...

Linux运维之为Nginx添加错误日志(error_log)配置

Nginx错误日志信息介绍配置记录Nginx的错误信息是调试Nginx服务的重要手段,属于核心功能模块(nginx_core_module)的参数,该参数名字为error_log,可以放在不同的虚机主...

Linux使用swatchdog实时监控日志文件的变化

1.前言本教程主要讲解在Linux系统中如何使用swatchdog实时监控日志文件的变化。swatchdog(SimpleWATCHDOG)是一个简单的Perl脚本,用于监视类Unix系统(比如...

syslog服务详解

背景:需求来自于一个客户想将服务器的日志转发到自己的日志服务器上,所以希望我们能提供这个转发的功能,同时还要满足syslog协议。1什么是syslog服务1.1syslog标准协议如下图这里的fa...

linux日志文件的管理、备份及日志服务器的搭建

日志文件存放目录:/var/log[root@xinglog]#cd/var/log[root@xinglog]#lsmessages:系统日志secure:登录日志———————————...

运维之日志管理简介

日志简介在运维过程中,日志是必不可少的东西,通过日志可以快速发现问题所在。日志分类日志分类,对不同的日志进行不同维度的分析。操作系统日志操作系统是基础,应用都是在其之上;操作系统日志的分析,可以反馈出...

Apache Log4j 爆核弹级漏洞,Spring Boot 默认日志框架就能完美躲过

这两天沸沸扬扬的Log4j2漏洞门事件炒得热火朝天:突发!ApacheLog4j2报核弹级漏洞。。赶紧修复!!|Java技术栈|Java|SpringBoot|Spring...

Linux服务器存在大量log日志,如何快速定位错误?

来源:blog.csdn.net/nan1996jiang/articlep/details/109550303针对大量log日志快速定位错误地方tail/head简单命令使用:附加针对大量log日志...

Linux中查看日志文件的正确姿势,求你别tail走天下了!

作为一个后端开发工程师,在Linux中查看查看文件内容是基本操作了。尤其是通常要分析日志文件排查问题,那么我们应该如何正确打开日志文件呢?对于我这种小菜鸡来说,第一反应就是cat,tail,vi(或...

分享几款常用的付费日志系统,献给迷茫的你!

概述在前一篇文章中,我们分享了几款免费的日志服务器。他们各有各的特点,但是大家有不同的需求,有时免费的服务器不能满足大家的需要,下面推荐几款付费的日志服务器。1.Nagios日志服务器Nagio...

取消回复欢迎 发表评论: