0%

  1. 起因

    在centos上发现mysql容器启动不起来,于是用docker logs mysql看了下日志,发现有报错信息,显示磁盘没有空间了

    1
    Error writing file '/var/lib/mysql/auto.cnf' (OS errno 28 - No space left on device) 
  2. 分析

    • 使用df -h可以看到/目录的磁盘使用率已经达到100%

      1
      2
      3
      4
      5
      6
      7
      8
      df -h
      Filesystem Size Used Avail Use% Mounted on
      devtmpfs 3.8G 0 3.8G 0% /dev
      tmpfs 3.8G 0 3.8G 0% /dev/shm
      tmpfs 3.8G 796K 3.8G 1% /run
      tmpfs 3.8G 0 3.8G 0% /sys/fs/cgroup
      /dev/vda1 50G 48G 0 100% /
      tmpfs 768M 0 768M 0% /run/user/0
    • 进入/目录,使用du -sh *查看各个目录所占用的空间大小,可以看到其中usrvar两个目录占用的空间最大

      1
      2
      3
      4
      5
      cd /
      du -sh *
      ..........
      26G usr
      21G var
    • 继续查看usrvar目录的空间占用情况,这里使用sort命令按照空间占用从大到小的顺序进行排列,并且通过head命令只展示前10项

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      du /usr/ | sort -nr | head
      20092340 /usr/
      5654140 /usr/elasticsearch
      4665460 /usr/nacos
      4562944 /usr/nacos/bin
      4562900 /usr/nacos/bin/logs
      2743372 /usr/elasticsearch/node-0
      2022416 /usr/elasticsearch/node-1
      1571536 /usr/from_pc
      1359084 /usr/elasticsearch/node-0/logs
      1349160 /usr/share

      du -h var
      13778628 /var/
      7738316 /var/lib
      6870460 /var/lib/docker
      5616924 /var/log
      4220916 /var/log/journal
      4220908 /var/log/journal/20200817140600710641424667768813
      3852356 /var/lib/docker/containers
      3825708 /var/lib/docker/containers/67a37780732bd46633f1ef94ad3ba9ea389f41bee9e929fc63c7e96fe1d3d57c
      2789052 /var/lib/docker/overlay2
      1007108 /var/log/redis
  3. 清理

    经过上面分析后,可以发现占用空间较大的主要是一些日志和自己创建的目录,比如这里的/usr/from_pc保存了一些软件压缩包,清理该目录也不会有任何影响

  1. 介绍

    正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

    如:

    • abc*d:可以匹配abd、abcd、abccd等,*表示它前面的字符出现零次或多次,所以可以匹配没有c字符、有一个或多个c字符的字符串
    • abc+d:可以匹配abcd、abccd、abcccd等,+表示它前面的字符出现至少一次,所以可以匹配有一个c字符、有多个c字符的字符串
  2. 普通字符和非打印字符参考W3C正则表达式教程

  3. 元字符:

    • (pattern):匹配该pattern并获取匹配结果,获取的匹配可以从产生的Matches集合得到
    • (?:pattern):匹配该pattern但不获取匹配结果,这是一个非获取匹配,即不进行存储供以后使用,如'industr(?:y|ies) 就是一个比 'industry|industries' 更简略的表达式
    • (?=pattern):正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,这是一个非获取匹配,即不进行存储供以后使用,如"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的”Windows”,但不能匹配"Windows3.1"中的”Windows”
    • (?!pattern):正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,这是一个非获取匹配,即不进行存储供以后使用,如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的”Windows”,但不能匹配"Windows2000"中的”Windows”
    • (?<=pattern):反向肯定预查,与正向肯定预查类似,只是方向相反,如”(?<=95|98|NT|2000)Windows“能匹配”2000Windows“中的”Windows“,但不能匹配”3.1Windows“中的”Windows
    • (?<!pattern):反向否定预查,与正向否定预查类似,只是方向相反,如”(?<!95|98|NT|2000)Windows“能匹配”3.1Windows“中的”Windows“,但不能匹配”2000Windows“中的”Windows
    • x|y:匹配x或y,如'a|b'匹配'a''b''(a|b)c'匹配'ac''bc'
    • [xyz]:字符集合,匹配[]包含的任意字符,如'[abc]'匹配'abcde'中的a、b、c
    • [^xyz]:负值字符集合,匹配[]未包含的任意字符,如'[^abc]'匹配'abcde'中的d、e
    • [a-z]:字符范围,匹配指定范围内的任意字符,如'[a-z]'匹配'a'~`’z’范围内的任意小写字母,[A-Z]`同理
    • [^a-z]:负值字符范围,匹配不在指定范围内的任意字符,如'[^a-z]'匹配不在'a'~`’z’范围内的任意字符,‘[^A-Z]’`同理
    • \cx:匹配由x指明的控制字符,如'\cM'匹配一个Control-M或回车符,x的值必须为a-z或A-Z之一,否则将c视为一个原义的'c'字符
    • \d:匹配一个数字字符,等价于'[0-9]'
    • \D:匹配一个非数字字符,等价于'[^0-9]'
    • \f:匹配一个换页符,等价于'\x0c''\cL'
    • \n:匹配一个换行符,等价于'\x0a''\cJ'
    • \r:匹配一个回车符,等价于'\x0d''\cM'
    • \s:匹配任何空白符,包括空格、制表符、换页符等,等价于'[ \f\n\r\t\v]'
    • \S:匹配任何非空白符,等价于'[^ \f\n\r\t\v]'
    • \t:匹配一个制表符,等价于'\x09''\cI'
    • \v:匹配一个垂直制表符,等价于'\x0b''\cK'
    • \w:匹配字母、数字、下划线,等价于'[A-Za-z0-9_]'
    • \W:匹配非字母、数字、下划线,等价于'[^A-Za-z0-9]'
  4. 特殊字符:有特殊含义的字符,比如*、+、?等

    • ^:匹配字符串开始位置,如'^abc'匹配abc开头的字符串,;如果在方括号表达式中使用,表示不接受后面的字符,如'[^abc]'匹配a、b、c之外的字符;如果要匹配字符本身,使用'\^'进行转义
    • $:匹配字符串结束位置;如果设置了RegExp 对象的 Multiline 属性,也可以匹配'\n''\r';如果要匹配字符本身,使用'\$'进行转义
    • ():标记一个子表达式的开始和结束位置;子表达式可以获取供以后使用;如果要匹配字符本身,使用'\(''\)'进行转义
    • *:匹配前面的子表达式零次或多次;如果要匹配字符本身,使用'\*'进行转义
    • +:匹配前面的子表达式一次或多次(至少一次);如果要匹配字符本身,使用'\+'进行转义
    • ?:匹配前面的子表达式零次或一次(最多一次);指明一个非贪婪限定符;如果要匹配字符本身,使用'\?'进行转义
    • .:匹配除换行符'\n'之外的任何单字符,如果要匹配字符本身,使用'\.'进行转义
    • [:标记方括号表达式;如果要匹配字符本身,使用'\['进行转义
    • \:将下一个字符标记为特殊字符、原义字符、向后引用、八进制转义符,如'\n'匹配换行符,'\\'匹配'\'
    • {:标记限定符表达式开始位置;如果要匹配字符本身,使用'\{'进行转义
    • |:指明两项之间的一个选择;如果要匹配字符本身,使用'\|'进行转义
  5. 限定符:限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 ***** 或 +?{n}{n,}{n,m} 共6种,下面只介绍 {n}{n,}{n,m}

    • {n}:n为非负整数,表示确切匹配n次,如abc{3}匹配'abccc',不匹配'abcc'
    • {n,}:n为非负整数,表示至少匹配n次,如'abc{3,}'匹配'abcccc'中的所有c
    • {n,m}:n和m均为非负整数,且n<=m,表示最少匹配n次,最多匹配m次,如'abc{1,3}'匹配'abcccc'中的前三个c
  6. 贪婪和非贪婪:*、+ 限定符都是贪婪的,因为它们会尽可能多的匹配文字,只有在它们的后面加上一个?就可以实现非贪婪或最小匹配。

  7. 定位符:定位符能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。定位符用来描述字符串或单词的边界,^$ 分别指字符串的开始与结束,\b 描述单词的前或后边界,\B 表示非单词边界。

    • ^:匹配字符串开始的位置;如果设置了RegExp 对象的 Multiline 属性,'^'还会和'\n''\r'之后的位置匹配
    • $:匹配字符串结束的位置;如果设置了RegExp 对象的 Multiline 属性,'$'还会和'\n''\r'之前的位置匹配
    • \b:匹配一个单词边界,即单词和空格之间的位置
    • \B:匹配一个非单词边界
    • 注意:不能将限定符与定位符一起使用
  8. 运算符优先级:正则表达式从左到右进行计算,并遵循优先级顺序,相同优先级的从左到右进行运算,不同优先级的运算先高后低,下面按照最高到最低顺序说明

    • \:转义符

    • (), (?:), (?=), []:圆括号和方括号

    • *, +, ?, {n}, {n,}, {n,m}:限定符

    • ^, $, \任何元字符、任何字符:位置和顺序

    • |:替换,”或”操作,字符具有高于替换运算符的优先级,使得”m|food”匹配”m”或”food”。若要匹配”mood”或”food”,请使用括号创建子表达式,从而产生”(m|f)ood”

  1. mybatis核心类及接口

    1. Configuration:

      mybatis核心配置类,用于管理mybatis相关的配置信息

      当调用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,该方法继续调用mapperProxyFactory.newInstance方法来生成一个具体的代理对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public <T> T getMapper(Class<T> type, org.apache.ibatis.session.SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
      }

      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
      if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
      }
      try {
      return mapperProxyFactory.newInstance(sqlSession);
      } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
      }
      }

      protected T newInstance(MapperProxy<T> mapperProxy) {
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }

      public T newInstance(SqlSession sqlSession) {
      final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
      return newInstance(mapperProxy);
      }
    2. SqlSessionFactoryBuilder:

      mybatis初始化时,调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,构建mybatis运行的核心对象Configuration,然后将其作为参数构建一个SqlSessionFactory对象

      其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder来读取*Mapper,XMLMapperBuilder会使用XMLStatementBuilder来读取和构建所有的SQL语句

      1
      2
      3
      4
      5
      6
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());

      public SqlSessionFactory build(Configuration config) {
      return new DefaultSqlSessionFactory(config);
      }
    3. SqlSession:

      mybatis工作的主要接口,可以通过此接口执行SQL语句、获取mapper、管理事务等;SqlSession的执行,实际是通过对应Executor来进行的

      1
      2
      3
      4
      5
      public class DefaultSqlSession implements SqlSession {
      private final Configuration configuration;
      private final Executor executor;
      ......
      }
    4. SqlSessionFactory:

      创建SqlSession的工厂类接口

    5. SqlNode:

      sql节点接口,相关实现类:IfSqlNode、TrimSqlNode、ChooseSqlNode、TextSqlNode等

      1
      2
      3
      public interface SqlNode {
      boolean apply(DynamicContext context);
      }
    6. Executor:

      SqlSession的Sql执行,都是委托给Executor实现的,

      1
      2
      3
      Executor
      BaseExecutor CachingExecutor
      SimpleExecutor ReuseExecutor BatchExecutor

      其中,BaseExecutor定义了几个抽象方法,该方法的具体实现交由子类完成

      1
      2
      3
      4
      5
      BaseExecutor.java

      protected abstract int doUpdate(xxx)
      protected abstract List<BatchResult> doFlushStatements(xxx)
      protected abstract <E> List<E> doQuery(xxx)

      SimpleExecutor:每次执行update或select时,会开启一个新的Statement对象,执行完立马关闭该对象

      ReuseExecutor:每次执行update或select时,先从StatementMap(Map<String,Statement>)中根据sql查找对应的Statement对象,如果存在,直接取出使用,如果不存在,新建一个Statement对象,执行完后不关闭,而是将其放置到StatementMap中

      BatchExecutor:执行update时,如果当前要执行的sql和Statement对象和保存的sql和Statement对象不相同,将当前要执行的Statement对象放到StatementList(List)中,并保存此次执行的sql和Statement对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      @Override
      public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
      ......
      if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      handler.parameterize(stmt);//fix Issues 322
      org.apache.ibatis.executor.BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
      } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt); //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new org.apache.ibatis.executor.BatchResult(ms, sql, parameterObject));
      }
      ......

      // 这里没有直接通过statement对象执行SQL操作
      handler.batch(stmt);
      return BATCH_UPDATE_RETURN_VALUE;
      }

      // 真正执行SQL操作是在doFlushStatements方法中进行的,当Executor执行doQuery方法或commit方法(如SqlSession执行commit方法)时,会调用flushStatements方法
      public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
      ......
      batchResult.setUpdateCounts(stmt.executeBatch());
      ......
      }

      BaseExecutor.java
      public void commit(boolean required) throws SQLException {
      if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
      }
      clearLocalCache();
      flushStatements();
      if (required) {
      transaction.commit();
      }
      }
  2. SqlSession、Executor、PerpetualCache层级关系

    1. SqlSession <<<<<< Executor <<<<<< PerpetualCache

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class DefaultSqlSession implements SqlSession {
      private final Executor executor;
      ......
      }

      public abstract class BaseExecutor implements Executor {
      protected PerpetualCache localCache;
      ......
      }

      public class PerpetualCache implements Cache {
      private final Map<Object, Object> cache = new HashMap<>();
      ......
      }
  3. mybatis缓存

    1. mybatis中的缓存功能由根接口Cache定义,整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache永久缓存实现,然后根据一系列的装饰器对PerpetualCache类进行缓存策略等方面的控制

      1
      2
      3
      4
      5
      6
      7
      public interface Cache {
      /**
      * @return The identifier of this cache
      */
      String getId();
      ......
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      FifoCache:先进先出算法,缓存回收策略

      LoggingCache:输出缓存命中的日志信息

      LruCache:最近最少使用算法,缓存回收策略

      ScheduledCache:调度缓存,负责定时清空缓存

      SerializedCache:缓存序列化和反序列化存储

      SoftCache:基于软引用实现的缓存管理策略

      SynchronizedCache:同步的缓存装饰器,用于防止多线程并发访问

      WeakCache:基于弱引用实现的缓存管理策略
    2. 一级缓存,又叫“本地缓存”,是PerpetualCache类型的永久缓存,位于Executor中,由1可知,Executor又位于SqlSession中,因此,一级缓存的生命周期和SqlSession的生命周期是一致的

    3. 二级缓存,又叫“自定义缓存”,实现了Cache接口的类都可以作为二级缓存;二级缓存根据namespace命名空间作为唯一标识,保存在Configuration核心配置对象中;二级缓存默认缓存类型为PerpetualCache,如果配置的缓存为默认类型,则mybatis会根据配置自动追加一系列装饰器

      1
      2
      3
      4
      public class Configuration {
      protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
      ......
      }

      Cache对象之间的引用顺序为:

      SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

  1. 序列化

    • 概念:

      将Java中的对象转换为字节序列

    • 使用场景:

      如在程序停止运行后,需要将之前的对象状态保存到文件中,在程序重新启动后,可以根据该文件重新读取相应的对象信息(持久化存储);

      在网络中传输对象

    • 实现方式:

      实现java.io.Serializable接口

    • 注意点:

      基本类型、数组、枚举等也会进行序列化

      transient修饰的属性和静态属性不参与序列化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      and the values of the non-transient
      and non-static fields of the class and all of its supertypes are
      written.

      if (obj instanceof String) {
      writeString((String) obj, unshared);
      } else if (cl.isArray()) {
      writeArray(obj, desc, unshared);
      } else if (obj instanceof Enum) {
      writeEnum((Enum<?>) obj, desc, unshared);
      } else if (obj instanceof Serializable) {
      writeOrdinaryObject(obj, desc, unshared);
      } else {
      if (extendedDebugInfo) {
      throw new NotSerializableException(
      cl.getName() + "\n" + debugInfoStack.toString());
      } else {
      throw new NotSerializableException(cl.getName());
      }
      }

      private void writeArray(xxx) {
      if (ccl.isPrimitive()) {
      if (ccl == Integer.TYPE) {
      int[] ia = (int[]) array;
      bout.writeInt(ia.length);
      bout.writeInts(ia, 0, ia.length);
      } else if (ccl == Byte.TYPE) {
      byte[] ba = (byte[]) array;
      bout.writeInt(ba.length);
      bout.write(ba, 0, ba.length, true);
      } else if (ccl == Long.TYPE) {
      ......
      }
      ......
      }

      }
    • 代码实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      class Student implements Serializable {
      private String name;
      private int number;
      // 被transient标记的属性不参与序列化,创建对象时赋值18,序列化后重新读取该属性的值为0
      private transient int age;
      // 静态属性不参与序列化,序列化后该属性值为null
      private static String gender;

      public void showStudent() {
      System.out.println("name: " + name);
      System.out.println("number: " + number);
      System.out.println("age: " + age);
      }

      .......省略getter、setter方法
      }

      @Test
      public void serializable() {
      // 创建对象
      Student student = new Student();
      student.setName("小花");
      student.setNumber(36);
      student.setAge(18);
      student.setGender("female");
      String greeting = "HelloWorld";

      // 创建FileOutputStream
      // 创建ObjectOutputStream
      try (FileOutputStream fos = new FileOutputStream("D:\\MyWork\\student.ser");
      ObjectOutputStream oos = new ObjectOutputStream(fos)) {
      // 将对象写入到文件
      oos.writeObject(student);
      oos.writeObject(greeting);
      } catch (IOException e) {
      e.printStackTrace();
      }
      }
  2. 反序列化

    • 概念:

      将字节序列转换为Java对象

    • 使用场景

      如在程序停止运行后,需要将之前的对象状态保存到文件中,在程序重新启动后,可以根据该文件重新读取相应的对象信息(持久化存储)

      在网络中传输对象,接收字节序列后重新读取对象信息

    • 注意点:

      必须确保该读取程序的CLASSPATH中包含有序列化类的class文件

    • 代码实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      @Test
      public void deserializable() {
      Student student = null;
      String greeting = "";
      // 创建FileInputStream
      // 创建ObjectInputStream
      try (FileInputStream fis = new FileInputStream("D:\\MyWork\\student.ser");
      ObjectInputStream ois = new ObjectInputStream(fis);) {
      // 从文件读取对象
      student = (Student) ois.readObject();
      greeting = (String) ois.readObject();
      } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
      }

      // 使用对象
      if (null != student) {
      student.showStudent();
      }
      System.out.println(greeting);
      }

      // 输出结果
      name: 小花
      number: 36
      age: 0
      gender: null
      HelloWorld
  3. serialVersionUID

    • 概念:

      序列化版本号,用来标识当前实现序列化接口的类,如果不显式指定,编译器会给类自动生成一个serialVersionUID

    • 作用:

      先看一个场景,在上述(1)中,Student类定义了name、number、age、gender四个属性,然后我们创建一个student对象并将其序列化保存到student.ser文件中,然后我们在Student类中增加一个属性,如className,再通过反序列化读取student.ser文件中的字节信息,转换成student对象,此时程序会报错

      1
      java.io.InvalidClassException: com.easyfun.base.Student; local class incompatible: stream classdesc serialVersionUID = -6313947761321746093, local class serialVersionUID = 4891437043098475710

      根据异常信息找到相应的代码:java.io.ObjectStreamClass#initNonProxy,当原class的序列化版本号和当前class的序列化版本号不一致会导致反序列化失败

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      void initNonProxy(xxx){
      long suid = Long.valueOf(model.getSerialVersionUID());
      ......
      if (model.serializable == osc.serializable &&
      !cl.isArray() &&
      suid != osc.getSerialVersionUID()) {
      throw new InvalidClassException(osc.name,
      "local class incompatible: " +
      "stream classdesc serialVersionUID = " + suid +
      ", local class serialVersionUID = " +
      osc.getSerialVersionUID());
      }
      ......
      }

      现在我们在Student类中添加上serialVersionUID(可以通过Idea自动生成,方法可自行搜索),然后调用序列化方法后,在Student类增加一个属性className,然后再调用反序列化方法,发现此次序列化不会再报错了

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      private static final long serialVersionUID = -6313947761321746093L;


      // 输出结果
      name: 小花
      number: 36
      age: 0
      gender: null
      className: null
      HelloWorld

      因此serialVersionUID的作用就比较好理解了,我们可以显式去指定某个类的序列化版本号,这样我们在反序列化时,即使该类的结构发生了变化,也能保证反序列化能够正常进行

环境准备

  • 操作系统:CentOS 7.5
  • Redis版本:6.2.6,链接

搭建Redis主从集群

  • 新建slave1slave2目录,复制两份redis配置文件

    1
    2
    3
    4
    5
    mkdir slave1
    mkdir slave2

    cp redis.conf.bak /usr/my_software/redis/slave1/redis.conf
    cp redis.conf.bak /usr/my_software/redis/slave2/redis.conf

    修改端口号等其他配置,slave2设置类似

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    cd slave1
    vim redis.conf

    # 设置端口号
    port 6389

    # 设置后台运行
    daemonize yes

    # 设置pid路径
    pidfile /var/run/redis_6389.pid

    # 设置redis.log路径
    logfile "/usr/my_software/redis/slave1/redis.log"

    # 设置dump.rdb路径
    dir "/usr/my_software/redis/slave1"

    # 设置主节点
    replicaof 127.0.0.1 6379

    # 设置主节点密码(如果主节点有设置密码)
    masterauth "codecho@0110"
  • 启动slave1slave2

    1
    2
    3
    4
    5
    cd slave1
    redis-server redis.conf

    cd slave2
    redis-server redis.conf

    查看主从节点启动情况

    1
    ps -ef|grep redis

  • 连接主节点,使用info命令可以看到主节点现在有两个从节点slave1slave2

    1
    2
    redis-cli -p 6379 [-a 密码]
    127.0.0.1:6379> info

  • 测试主节点和从节点数据同步

    !!!注意!!!从节点只能读数据,无法进行写数据操作

    再开启一个窗口,连接从节点slave1slave2,通过keys *命令可以发现redis中没有数据

    1
    2
    3
    redis-cli -p 6389
    127.0.0.1:6389> keys *
    (empty array)

    在连接主节点的窗口中插入一些数据

    1
    2
    3
    4
    127.0.0.1:6379> set today 2022-01-05
    OK
    127.0.0.1:6379> hset student_1001 name xiaoming gender male age 22
    (integer) 3

    在连接从节点的窗口中重新查询数据,现在从节点也有数据了

    1
    2
    3
    127.0.0.1:6389> keys *
    1) "student_1001"
    2) "today"

在这种模式下,如果主节点挂掉,我们只能通过手动重新启动主节点,显然,这种情况是我们难以接受的,因此,需要另一种方式来实现主节点的故障转移,而哨兵模式就可以做到这一点。

设置哨兵模式

  • 新建sentinel1sentinel2sentinel3目录,复制三份sentinel配置文件

    1
    2
    3
    4
    5
    6
    7
    mkdir sentinel1
    mkdir sentinel2
    mkdir sentinel3

    cp sentinel.conf /usr/my_software/redis/sentinel1
    cp sentinel.conf /usr/my_software/redis/sentinel2
    cp sentinel.conf /usr/my_software/redis/sentinel3

    修改端口等信息,sentinel2sentinel3设置类似

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    cd sentinel1
    vim sentinel.conf

    # 设置端口号
    port 26379

    # 设置后台运行
    daemonize yes

    # 设置pid路径
    pidfile /var/run/redis-sentinel1.pid

    # 设置sentinel日志路径
    logfile "/usr/my_software/redis/sentinel1/sentinel.log"

    # 设置工作目录
    dir "/usr/my_software/redis/sentinel1"

    # sentinel monitor <master-name> <ip> <redis-port> <quorum>
    # quorum表示至少有<quorum>个哨兵认定主节点下线(主观下线),这个主节点才真正下线了(客观下线)
    sentinel monitor mymaster 127.0.0.1 6379 2

    # 设置主节点连接密码(如果主节点设置了)
    sentinel auth-pass mymaster codecho@0110
  • 启动哨兵

    1
    2
    3
    4
    /usr/local/bin
    ./redis-sentinel /usr/my_software/redis/sentinel1/sentinel.conf
    ./redis-sentinel /usr/my_software/redis/sentinel2/sentinel.conf
    ./redis-sentinel /usr/my_software/redis/sentinel3/sentinel.conf

    查看sentinel1sentinel2sentinel3启动情况

    查看sentinel1日志,可以看到主节点、从节点、其他哨兵节点信息

  • 模拟主节点挂掉的情况

    1
    kill -9 redis主节点pid

    查看sentinel1的日志

    1
    tail -f sentinel.log

    可以看到,当主节点6379挂掉后,哨兵选举了从节点6399作为新的主节点,并且会更新其他从节点配置文件中的主节点信息

  • 重新启动原来的主节点6379

    1
    redis-server redis.conf

    可以看到原来的主节点的配置文件中多了一行replicaof 127.0.0.1 6399 ,表示它现在是6399的从节点了

    也可以用info命令查看从节点信息

搭建Redis Cluster

  • 创建cluster目录,在cluster目录中创建6379、6380、6381、6382、6383、6384(每个目录代表一个端口号,可以使用其他端口号)共6个目录,表示一共有6个redis节点(3主3从)

    1
    2
    3
    mkdir cluster
    cd cluster
    mkdir 6379 6380 6381 6382 6383 6384
  • 将redis源码目录中的redis.conf分别复制到上述6个目录下,并修改端口号等信息

    注意,像设置后台运行、pid路径、redis日志文件路径等配置自行参考上节部分按需修改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    cd 6379

    # 修改端口号
    port 6379

    # 开启aof
    appendonly yes

    # 设置以集群方式运行
    cluster-enabled yes

    # 每个集群节点的配置文件,不需要手动创建
    cluster-config-file nodes.conf

    # 集群节点超时,单位:毫秒
    cluster-node-timeout 5000
  • 启动上述6个节点

    1
    2
    3
    4
    5
    cd 6379
    redis-server redis.conf


    ...

    查看节点是否启动成功

  • 创建真正的集群!!!

    对于redis-5及之后的版本,使用redis-cli 命令来创建集群,redis-4.x及之前的版本貌似使用的是redis-trib.rb可参考官网文档

    注意:如果要想在外部通过代码连接集群,这里的ip地址需要换成服务器的公网ip

    1
    2
    3
    redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 --cluster-replicas 1

    输入yes确认保存配置

  • 更简单快速地创建集群

    使用redis源码create-cluster目录下的create-cluster脚本创建集群

    1
    2
    3
    4
    5
    cd utils/create-cluster
    ./create-cluster start
    ./create-cluster create

    ./create-cluster stop

    可以看到create-cluster脚本中端口号是从30000开始的,也是创建6个节点,本质上和上面手动创建是一样的操作

  • 连接集群并使用

    1
    redis-cli -c -p 6380

    可以看到,我们连接任意一个集群节点,写入的数据会根据key重定向到不同的哈希槽,同样,查询数据时也会从对应的集群节点中获取

  • 模拟三个主节点中的一个挂掉

    1
    kill -9 6379主节点的pid

    可以看到,6379的从节点6384被选举为新的主节点

    重新启动6379节点,连接集群后可以看到6379已经变成6384的从节点了

使用Redis工具连接Redis Cluster

  • 推荐使用AnotherRedisDesktopManager客户端工具,github地址

Centos7安装Redis + RedisTemplate使用

环境准备

  • 操作系统:CentOS7.5
  • Redis版本:6.2.6,链接

安装步骤

  • 从官网获取Redis压缩包

    1
    wget https://download.redis.io/releases/redis-6.2.6.tar.gz

    如果速度很慢可以先在windows下下载好压缩包,再通过远程连接工具上传到服务器上

  • 编译安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    tar -zxvf redis-6.2.6.tar.gz

    cd redis-6.2.6

    # 执行make命令来编译,需要gcc支持,如果没有需要安装gcc相关工具
    make

    # 执行make install来安装
    make install
  • 启动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 在/usr/local/bin目录下可以看到redis-benchmark、redis-cli、redis-server等相关命令
    cd /usr/local/bin

    # 启动redis-server,可以看到Redis的logo
    ./redis-server /usr/my_software/redis/redis-6.2.6/redis.conf

    # 查看redis-server是否成功启动
    ps -ef|grep redis
    root 30570 18113 0 21:30 pts/0 00:00:00 ./redis-server 127.0.0.1:6379
  • 使用redis-cli连接redis-server

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    ./redis-cli -p 6379 [-a 密码]

    # 使用ping命令查看是否连接成功
    127.0.0.1:6379> ping
    PONG
    # 使用info命令查看redis详细信息
    127.0.0.1:6379> info
    # Server
    redis_version:6.2.6
    redis_git_sha1:00000000
    redis_git_dirty:0
    redis_build_id:a1222172bdd35f61
    redis_mode:standalone
    os:Linux 3.10.0-1127.19.1.el7.x86_64 x86_64
    arch_bits:64
    multiplexing_api:epoll
    atomicvar_api:atomic-builtin
    gcc_version:4.8.5
    process_id:30570
    process_supervised:no
    run_id:4de193462f2317104a79ee50969de49216991d60
    tcp_port:6379
    ...

    # 使用set命令保存一个字符串
    127.0.0.1:6379> set msg Hello,Redis
    OK
    127.0.0.1:6379> get msg
    "Hello,Redis"
  • 修改Redis配置文件(推荐先将原始配置文件备份,再做修改)

    • !!!注意!!!在redis-6.2.6版本中,redis配置文件中关于快照部分需要取消save 3600 1、save 300 100、save 60 10000这几行的注释,否则redis不会持久化数据,重启后数据会丢失

    • 当我们在当前终端窗口启动redis-server后,如果退出了当前终端,redis-server也会随之停止,因此我们要修改配置文件,让Redis能够保持后台运行
    1
    2
    3
    4
    5
    6
    7
    8
    # 配置文件路径视个人安装情况而定
    vim /xxx/xxx/redis.conf

    # 将daemonize no改为daemonize yes
    daemonize yes

    # 此时启动redis-server后,Redis就可以一直在后台运行了
    ./redis-server /usr/my_software/redis/redis-6.2.6/redis.conf
    • 当我们使用Redis连接工具,如AnotherRedisDesktopManager或者在程序中连接Redis时,会发现无法连接,此时需要修改redis.conf配置文件

    注意!!!如果是使用的云服务器,需要在云服务器控制台的安全组里放行Redis的端口号,否则在外网无法连接到云服务器的Redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 配置文件路径视个人安装情况而定
    vim /xxx/xxx/redis.conf

    # 找到 bind 127.0.0.1 -::1 这行配置,将其注释
    #bind 127.0.0.1 -::1

    # 找到 protected-mode yes 这行配置,将其改为 protected-mode no
    protected-mode no

    # 如果要为Redis设置密码,取消 # requirepass foobared 的注释,在requirepass后添加自己要设置的密码
    # 如果我们设置了密码,那么就不用配置 protected-mode no 了,在连接Redis时配置我们设置的密码就好了
    requirepass helloredis

在Java程序中使用Jedis或RedisTemplate连接Redis

  • 创建一个SpringBoot项目(个人习惯,可以使用普通的Spring项目),在application.yml配置文件中添加Redis的配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    server:
    port: 8080

    spring:
    redis:
    host: 101.34.155.68
    port: 6379
    password: codecho@0110
  • 引入Redis依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.4.2</version>
    </dependency>
  • 通过RedisTemplate操作Redis

    1
    2
    3
    4
    5
    6
    7
    8
    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void testRedis() {
    redisTemplate.opsForValue().set("today", "Tuesday");
    System.out.println(redisTemplate.opsForValue().get("today"));
    }

Arthas(阿尔萨斯)是什么

Arthas是Alibaba开源的Java诊断工具

Arthas能做什么

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?
  7. 怎么快速定位应用的热点,生成火焰图?
  8. 怎样直接从JVM内查找某个类的实例?

开发中常见的问题

  1. 前端调用一个后端的接口,该接口逻辑非常复杂,可能会调用其他service接口或其他模块的FeignClient,但是在其中某个接口报错了,报错信息又不是很明显,这种情况下,一般要么分析代码逻辑,要么增加日志输出来排查发生异常的原因

    使用Arthas后,可以通过watch命令观察指定方法的调用情况,可以观察入参返回值异常信息本次调用的方法信息本次调用的类信息等,配合ognl表达式可以实现复杂的操作

  2. 后端改动或者增加一个复杂的接口后,联调时可能遇到一个问题,改一遍代码,然后pull代码,编译,重启服务,然后又遇到问题,再修改代码,再重复同样的操作,这些步骤十分的耗时,如果开发时间比较紧迫,更加让人难以忍受

    使用Arthas的jadmcretransform命令修改java代码,编译字节码文件,加载新的字节码,不需要重新pull代码,编译,重启等一系列耗时的操作,就能直接实现修改后的效果

如何使用

  1. 安装启动

    安装方式很多,有直接启动jar包、使用shell脚本、rpm安装等,这里使用直接启动jar包的方式

    1
    2
    3
    4
    5
    # 下载arthas-boot.jar
    curl -O https://arthas.aliyun.com/arthas-boot.jar

    # 启动
    java -jar arthas-boot.jar
  2. 选择要attach的进程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [INFO] arthas-boot version: 3.5.4
    [INFO] Process 10294 already using port 3658
    [INFO] Process 10294 already using port 8563
    [INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
    * [1]: 10294 java-study-0.0.1-SNAPSHOT.jar
    [2]: 13008 org.apache.zookeeper.server.quorum.QuorumPeerMain
    [3]: 19681 /usr/nacos/target/nacos-server.jar
    [4]: 4819 org.elasticsearch.bootstrap.Elasticsearch
    [5]: 21381 /usr/my_service/java_study/java-study-0.0.1-SNAPSHOT.jar
    [6]: 22535 kafka.Kafka
    [7]: 21772 /usr/my_service/java_study/java-study-0.0.1-SNAPSHOT.jar
    1
    [INFO] arthas home: /root/.arthas/lib/3.5.4/arthas
    [INFO] The target process already listen port 3658, skip attach.
    [INFO] arthas-client connect 127.0.0.1 3658
    ,---. ,------. ,--------.,--. ,--. ,---. ,---.
    / O \ | .--. ''--. .--'| '--' | / O \ ' .-'
    | .-. || '--'.' | | | .--. || .-. |`. `-.
    | | | || |\ \ | | | | | || | | |.-' |
    `--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'


    wiki https://arthas.aliyun.com/doc
    tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
    version 3.5.4
    main_class
    pid 10294
    time 2021-09-15 10:18:15
  3. 输入help查看命令帮助信息

使用watch命令观察方法的调用情况,如入参,返回值,异常信息等

  1. watch命令参数

    参数名称 参数说明
    class-pattern 类名表达式匹配
    method-pattern 方法名表达式匹配
    express 观察表达式,默认值:{params, target, returnObj}
    condition-express 条件表达式
    [b] 方法调用之前观察
    [e] 方法异常之后观察
    [s] 方法返回之后观察
    [f] 方法结束之后(正常返回和异常返回)观察
    [E] 开启正则表达式匹配,默认为通配符匹配
    [x:] 指定输出结果的属性遍历深度,默认为 1
  2. 观察表达式

    观察表达式的构成主要由ognl表达式组成,使用它可以获取对象的属性,调用对象的方法等

    ognl表达式可以使用的对象有params(入参)、target(当前调用的类信息)、returnObj(返回值)、throwExp(异常信息)等,详细内容参考表达式核心变量

  3. 测试代码

    根据id查询用户信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Override
    public User getUserById(Long id) throws Exception {
    if (StringUtils.isEmpty(id)) {
    throw new Exception("用户id为空!");
    }

    List<User> collect = userList.stream().filter(u -> id.equals(u.getId()))
    .collect(Collectors.toList());
    if (CollectionUtils.isEmpty(collect)) {
    throw new Exception("根据id未找到用户信息");
    }

    return collect.get(0);
    }
  4. 观察方法调用情况

    • 观察方法入参和返回值

      执行watch命令:

      1
      2
      # {params, returnObj} 表示需要观察方法入参和返回值,-n 5 表示执行5次后结束 -x 3 表示遍历对象的深度
      watch com.codecho.test.service.UserService getUserById '{params, returnObj}' -n 5 -x 3

      调用接口:

      1
      curl http://localhost:8088/test/user/1

      查看控制台输出:这里result中包含了入参和返回的对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      [arthas@10294]$ watch com.codecho.test.service.UserService getUserById '{params, returnObj}' -n 5 -x 3
      Press Q or Ctrl+C to abort.
      Affect(class count: 2 , method count: 1) cost in 71 ms, listenerId: 25
      method=com.codecho.test.service.UserServiceImpl.getUserById location=AtExit
      ts=2021-09-17 14:28:29; [cost=1.151249ms] result=@ArrayList[
      @Object[][
      @Long[1],
      ],
      @User[
      id=@Long[1],
      username=@String[1001],
      avatar=@String[https://xxxx.com/1001.png],
      phoneNumber=@String[10010010001],
      age=@Integer[20],
      gender=@String[男],
      ],
      ]
    • 观察方法抛出的异常信息

      执行watch命令:

      1
      2
      # {params, throwExp} 表示需要观察方法入参和异常对象
      watch com.codecho.test.service.UserService getUserById '{params, throwExp}' -n 5 -x 3

      调用接口:

      1
      curl http://localhost:8088/test/user/1001

      查看控制台输出:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      [arthas@10294]$ watch com.codecho.test.service.UserService getUserById '{params, throwExp}' -n 5 -x 3
      Press Q or Ctrl+C to abort.
      Affect(class count: 2 , method count: 1) cost in 70 ms, listenerId: 26
      method=com.codecho.test.service.UserServiceImpl.getUserById location=AtExceptionExit
      ts=2021-09-17 14:29:56; [cost=2.055759ms] result=@ArrayList[
      @Object[][
      @Long[1001],
      ],
      java.lang.Exception: 根据id未找到用户信息
      at com.codecho.test.service.UserServiceImpl.getUserById(UserServiceImpl.java:48)
      at com.codecho.test.controller.TestController.getUserById(TestController.java:28)
      at sun.reflect.GeneratedMethodAccessor25.invoke(Unknown Source)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:498)
      ...

      可以通过调整观察表达式只展示异常具体信息

      1
      watch com.codecho.test.service.UserService getUserById '{params, throwExp.getMessage}' -n 5 -x 3

      重新调用接口,再次查看控制台输出

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      [arthas@10294]$ watch com.codecho.test.service.UserService getUserById '{params, throwExp.getMessage}' -n 5 -x 3
      Press Q or Ctrl+C to abort.
      Affect(class count: 2 , method count: 1) cost in 71 ms, listenerId: 27
      method=com.codecho.test.service.UserServiceImpl.getUserById location=AtExceptionExit
      ts=2021-09-17 14:31:08; [cost=1.685761ms] result=@ArrayList[
      @Object[][
      @Long[1001],
      ],
      @String[根据id未找到用户信息],
      ]
    • 只观察满足条件的方法调用

      执行watch命令:

      1
      2
      # params[0]%2==0 表示当用户id为偶数时才会记录调用信息
      watch com.codecho.test.service.UserService getUserById '{params, returnObj}' 'params[0]%2==0' -n 5 -x 3

      调用接口:

      1
      2
      3
      4
      curl http://localhost:8088/test/user/1
      curl http://localhost:8088/test/user/2
      curl http://localhost:8088/test/user/3
      curl http://localhost:8088/test/user/4

      查看控制台输出:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      [arthas@10294]$ watch com.codecho.test.service.UserService getUserById '{params, returnObj}' 'params[0]%2==0' -n 5 -x 3
      Press Q or Ctrl+C to abort.
      Affect(class count: 2 , method count: 1) cost in 71 ms, listenerId: 28
      method=com.codecho.test.service.UserServiceImpl.getUserById location=AtExit
      ts=2021-09-17 14:32:06; [cost=0.067629ms] result=@ArrayList[
      @Object[][
      @Long[2],
      ],
      @User[
      id=@Long[2],
      username=@String[1002],
      avatar=@String[https://xxxx.com/1002.png],
      phoneNumber=@String[10010010002],
      age=@Integer[23],
      gender=@String[男],
      ],
      ]
      method=com.codecho.test.service.UserServiceImpl.getUserById location=AtExit
      ts=2021-09-17 14:32:11; [cost=0.081801ms] result=@ArrayList[
      @Object[][
      @Long[4],
      ],
      @User[
      id=@Long[4],
      username=@String[1004],
      avatar=@String[https://xxxx.com/1004.png],
      phoneNumber=@String[10010010004],
      age=@Integer[21],
      gender=@String[女],
      ],
      ]

      执行watch命令:

      1
      2
      # #cost > 5 表示耗时超过5ms才会记录调用信息 -v 表示打印条件表达式的值和结果
      watch com.codecho.test.service.UserService * '{params, returnObj}' '#cost > 5' -v -n 1 -x 3

      调用接口:

      1
      curl http://localhost:8088/test/user/1

      查看控制台输出:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      [arthas@10294]$ watch com.codecho.test.service.UserService * '{params, returnObj}' '#cost > 5' -v -n 1 -x 3
      Press Q or Ctrl+C to abort.
      Affect(class count: 2 , method count: 8) cost in 76 ms, listenerId: 29
      Condition express: #cost > 5 , result: false
      Condition express: #cost > 5 , result: false
      Condition express: #cost > 5 , result: false
      Condition express: #cost > 5 , result: false
      Condition express: #cost > 5 , result: false
      Condition express: #cost > 5 , result: false

使用trace命令查看方法的调用路径和每个节点的耗时

  1. trace命令参数

    参数名称 参数说明
    class-pattern 类名表达式匹配
    method-pattern 方法名表达式匹配
    condition-express 条件表达式
    [E] 开启正则表达式匹配,默认为通配符匹配
    [n:] 命令执行次数
    #cost 方法执行耗时
  2. 观察接口调用路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    [arthas@40528]$ trace *ActionInfoServiceImpl interactionAssist -n 2
    Press Q or Ctrl+C to abort.
    Affect(class count: 2 , method count: 2) cost in 857 ms, listenerId: 4
    `---ts=2021-09-17 15:37:53;thread_name=http-nio-8325-exec-1;id=85;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@4258106
    `---[26.143404ms] com.hand.interaction.base.actioninfo.service.ActionInfoServiceImpl$$EnhancerBySpringCGLIB$$34d24eff:interactionAssist() [throws Exception]
    +---[25.926649ms] org.springframework.cglib.proxy.MethodInterceptor:intercept() [throws Exception]
    | `---[21.546574ms] com.hand.interaction.base.actioninfo.service.ActionInfoServiceImpl:interactionAssist() [throws Exception]
    | +---[0.131916ms] com.alibaba.fastjson.JSONObject:toJSONString() #6957
    | +---[0.190136ms] org.apache.logging.log4j.Logger:info() #6957
    | +---[0.00505ms] com.hand.core.util.UserUtil:getUser() #6960
    | +---[0.00535ms] com.hand.interaction.base.actioninfo.model.ActionInfo:getInteractionId() #6966
    | +---[1.262189ms] com.hand.interaction.base.interaction.service.InteractionService:queryByIdCache() #6966
    | +---[0.00564ms] com.hand.interaction.base.interaction.model.Interaction:getEndTime() #6970
    | +---[0.00526ms] com.hand.interaction.base.interaction.model.Interaction:getLicenseMode() #6977
    | +---[0.010319ms] com.hand.interaction.base.actioninfo.service.ActionInfoServiceImpl:checkUserInfo() #6977
    | +---[0.004508ms] com.hand.interaction.base.actioninfo.model.ActionInfo:getInteractionId() #6980
    | +---[0.00497ms] com.hand.interaction.base.actioninfo.model.ActionInfo:getOpenId() #6980
    | +---[0.006282ms] com.hand.interaction.base.actioninfo.model.ActionInfo:getMobilePhone() #6980
    | +---[0.127087ms] com.hand.interaction.base.util.CommonUtils:tianYu() #6980
    | +---[0.005831ms] com.hand.base.user.model.User:getOpenId() #6982
    | +---[0.005991ms] com.hand.core.util.StringUtils:isBlank() #6983
    | +---[0.009688ms] com.hand.interaction.base.interaction.model.Interaction:getBoostType() #6988
    | +---[0.008306ms] com.hand.interaction.base.actioninfo.model.ActionInfo:getHpOpenid() #6988
    | +---[0.004388ms] com.hand.core.util.StringUtils:isBlank() #6989
    | +---[0.007474ms] com.hand.interaction.base.actioninfo.model.ActionInfo:setHpOpenid() #6992
    | +---[min=0.003366ms,max=0.007244ms,total=0.01061ms,count=2] com.hand.interaction.base.interaction.model.Interaction:getInteractionFormat() #6997
    | +---[0.005109ms] com.hand.interaction.base.interaction.model.Interaction:getShareMode() #7026
    | +---[0.004529ms] com.hand.interaction.base.interaction.model.Interaction:getId() #7028
    | +---[0.005249ms] com.hand.interaction.base.interaction.model.Interaction:getBoostType() #7035
    | +---[19.019048ms] com.hand.interaction.base.actioninfo.service.ActionInfoServiceImpl:newMemBoostValidate() #7050 [throws Exception]
    | `---throw:com.hand.core.basic.service.BasicServiceException #7585 [您已经是会员,无法助力!]
    `---throw:com.hand.core.basic.service.BasicServiceException #49 [您已经是会员,无法助力!]

使用jadmcretransform命令热更新代码

  1. 测试代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Override
    public void payMoney(User user) throws Exception {
    // 查找用户
    User queryUser = null;
    if (!StringUtils.isEmpty(user.getId())) {
    queryUser = getUserById(user.getId());
    } else {
    queryUser = getUserByUsername(user.getUsername());
    }

    // 测试调用普通方法
    callPublicMethod(queryUser);
    callPrivateMethod(queryUser);

    // 支付收款
    payService.pay(queryUser, new BigDecimal("88.8"));
    }
  2. 使用jad命令反编译要修改的类的字节码,并保存到指定目录

    1
    jad --source-only *UserServiceImpl > /usr/arthas/UserServiceImpl.java
  3. 使用vim编辑反编译后的java代码,将金额从88.8改成99.9

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void payMoney(User user) throws Exception {
    // 查找用户
    User queryUser = null;
    if (!StringUtils.isEmpty(user.getId())) {
    queryUser = getUserById(user.getId());
    } else {
    queryUser = getUserByUsername(user.getUsername());
    }

    // 测试调用普通方法
    callPublicMethod(queryUser);
    callPrivateMethod(queryUser);

    // 支付收款
    payService.pay(queryUser, new BigDecimal("88.8"));
    }
  4. 使用mc命令内存编译修改后的java代码,-d 指定输出class的目录

    1
    mc /usr/arthas/UserServiceImpl.java -d /usr/arthas

    注意mc命令是可能编译失败的,如果编译失败,可以在本地修改java代码后将编译后的class文件上传到服务器上再使用mc命令

  5. 使用retransform命令加载修改后的字节码文件

    1
    retransform /usr/arthas/UserServiceImpl.class
  6. 使用watch命令观察接口入参,重新调用接口

    1
    watch *PayServiceImpl pay '{params[0], params[1].toString}' -n 5 -x 3
    1
    curl -H "Content-Type:application/json" -X POST -d '{"username": "1002"}' http://localhost:8088/test/user/payMoney
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 99.9 元的收款
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [arthas@10294]$ watch *PayServiceImpl pay '{params[0], params[1].toString}' -n 5 -x 3
    Press Q or Ctrl+C to abort.
    Affect(class count: 1 , method count: 1) cost in 48 ms, listenerId: 30
    method=com.codecho.test.service.PayServiceImpl.pay location=AtExit
    ts=2021-09-17 15:40:10; [cost=0.112135ms] result=@ArrayList[
    @User[
    id=@Long[2],
    username=@String[1002],
    avatar=@String[https://xxxx.com/1002.png],
    phoneNumber=@String[10010010002],
    age=@Integer[23],
    gender=@String[男],
    ],
    @String[99.9],
    ]
  7. 如果要取消retransform命令的效果,需要删除修改类的retransform entry 并重新触发retransform,如果不进行此操作,即使stop arthas后,retransform的效果仍会生效

    1
    2
    3
    4
    5
    6
    7
    8
    # 查看retransform entry
    retransform -l

    # 删除指定的retransform entry
    retransform -d 1

    # 重新触发retransform
    retransform --classPattern *UserServiceImpl

    使用watch命令观察接口入参,重新调用接口

    1
    watch *PayServiceImpl pay '{params[0], params[1].toString}' -n 5 -x 3
    1
    curl -H "Content-Type:application/json" -X POST -d '{"username": "1002"}' http://localhost:8088/test/user/payMoney
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 99.9 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 99.9 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 99.9 元的收款
    测试调用普通public方法
    测试调用普通private方法
    用户 1002支付了一笔 88.8 元的收款
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [arthas@10294]$ watch *PayServiceImpl pay '{params[0], params[1].toString}' -n 5 -x 3
    Press Q or Ctrl+C to abort.
    Affect(class count: 1 , method count: 1) cost in 48 ms, listenerId: 30
    method=com.codecho.test.service.PayServiceImpl.pay location=AtExit
    ts=2021-09-17 15:40:10; [cost=0.112135ms] result=@ArrayList[
    @User[
    id=@Long[2],
    username=@String[1002],
    avatar=@String[https://xxxx.com/1002.png],
    phoneNumber=@String[10010010002],
    age=@Integer[23],
    gender=@String[男],
    ],
    @String[88.8],
    ]

quitstop

  1. quit

    退出当前arthas客户端,arthas服务端不会关闭,所做的修改不会重置

  2. stop

    关闭arthas服务端,所有arthas客户端都会退出,关闭前增强类会被重置,使用redefineredefine重加载的类不会被重置

斜体

粗体

斜粗

段落1


段落2


段落3

hello

详情请参考^官网

  • no.1
  • no.2
  1. no.1
  2. no.2
  1. no.1
    • hello
  2. no.2
    • world
  • 要点1

    细心

    诚信

    勤奋

  • 要点2

    能力

    学习能力

    解决问题能力

hello

<www.world.com>

菜鸟教程

参数名 类型 含义
str String 要比较的字符串
1
2
3
4
5
6
7
| 参数名 | 类型 | 含义 |
| --- | :--: | :--: |
| str | String | 要比较的字符串 |

:- 左对齐
-: 右对齐
:-: 居中对齐
markdown高级技巧

部分HTML标签,可以直接在markdown中书写

示例:

使用 win+L锁定屏幕

这是HTML的b标签

转义

由于markdown使用了很多特殊符号表示格式,因此某些特殊符号的显示需要使用转义字符,markdown使用反斜杠转义字符

示例:

显示** **

公式

$$
x² + 4 = 20
$$

锚点

我来了





到我这里来

  • hello
    • world
      • hh
  • 早餐
  • 无参

  1. 起别名

    • 优点

      • 便于理解
      • 如果要查询的字段有重名的情况,使用别名可以区分开来
    • 使用方式

      1
      2
      3
      4
      5
      6
      # 方式一
      select count(*) '总预约数' from reservation_record
      # 方式二
      select count(*) as '总预约数' from reservation_record
      # 如果别名是关键字,可以使用 '' 或 "" 包裹别名
      select count(*) as "select" from reservation_record
  2. 去重

    • 使用方式

      1
      select distinct hospital_id from reservation_record 
  3. “+”号的作用:mysql中的 “+” 只用来作为运算符

    • 使用方式

      1
      2
      3
      4
      5
      6
      # 如果两个操作数都是数值型,做加法运算
      select 500+20
      # 如果其中一个操作数为字符型,先尝试将其转换为数值型,如果转换成功,做加法运算;否则将其当作 0 ,再做加法运算
      select "500"+20
      # 如果其中一个操作数为 null ,则结果必为 null
      select 500+null
  4. concat()函数实现拼接

    • 说明

      • 使用concat()函数可以拼接多个内容
    • 使用方式

      1
      select CONCAT('how ','are ','u') as title
  5. 比较运算符

    • 说明

      运算符 作用
      >、<、= 大于、小于、等于
      !=、<> 不等于,<>是mysql的标准写法,建议使用<>
      >=、<= 大于等于、小于等于
      <=> 严格比较两个null值是否相等,均为null时结果为1,只有一个为null时结果为0
      between、not between (不)在两个范围之间
      in、not in (不)在集合中
      is null、is not null (不)为空
      like 模糊匹配
    • 通配符

      • %:替代0个或多个字符

      • _:替代单个字符

      • 转义:

        1
        2
        3
        4
        # 通过 "\"转义符 ,这里 \ 后的 % 不再是通配符
        select * from reservation_record where title like '_\%%'
        # 通过 escape关键字 ,这里 & 之后的 % 不再是通配符
        select * from reservation_record where title like '_&%%' escape '&'
    • 使用方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # 查找年龄大于1的宠物信息
      select * from pet_archives where age > 1
      # 查找年龄不等于2的宠物信息
      select * from pet_archives where age <> 2
      # 查找年龄大于等于2的宠物信息
      select * from pet_archives where age >= 2
      # 查找年龄不在1~2范围的宠物信息
      select * from pet_archives where age not between 1 and 2
      # 查找宠物主id在(1,3,5)中的宠物信息
      select * from pet_archives where pet_user_id in (1,3,5)
      # 查找品种不为null的宠物信息
      select * from pet_archives where variety is not null
      # 查找标题第三个字符为“哈”的预约信息
      select * from reservation_record where title like '__哈%'
  6. 逻辑运算符

    • 说明:用于连接条件表达式

      运算符 作用
      &&或and 逻辑与
      ||或or 逻辑或
      !或not 逻辑非
      xor 逻辑异或
    • 使用方式

      1
      2
      3
      4
      5
      6
      7
      8
      # 查找性别为m,年龄大于1的宠物信息
      select * from pet_archives where gender = 'm' and age > 1
      # 查找性别为m或者年龄大于1的宠物信息
      select * from pet_archives where gender = 'm' or age > 1
      # 查找性别不为m的宠物信息
      select * from pet_archives where not gender = 'm'
      # 查找性别为m,年龄小于等于1、性别不为m,年龄大于1的宠物信息
      select * from pet_archives where gender = 'm' xor age > 1
  7. 排序查询

    • 说明

      • select 查询列表 from 表名 [where 查询条件] order by 排序列表 asc|desc
      • 默认升序
    • 使用方式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # 按照年龄从大到小顺序查询宠物信息
      select * from pet_archives order by age desc
      # 按照评分(按表达式排序)从低到高顺序查询预约信息
      select *,rate*1.0 from reservation_record order by rate*1.0 asc
      # 按照评分(按别名排序)从低到高顺序查询预约信息
      select *,rate*1.0 评分 from reservation_record order by 评分 asc
      # 按照标题长度(按函数排序)从大到小顺序查询预约信息
      select * from reservation_record order by length(title) desc
      # 按照年龄从大到小,id从小到大顺序(按多字段排序)查询宠物信息
      select * from pet_archives order by age desc,id asc
  8. 字符函数

    • length(str)
      • 返回参字符串的字节个数

        1
        select length(title) 字节数 from reservation_record
    • char_length(str)
      • 返回字符串的字符个数

        1
        select char_length(title) 字符数 from reservation_record
    • concat(str1,str2,…)
      • 拼接多个字符串

        1
        select concat(title,reservation_date) from reservation_record
    • lower(str)
      • 将字符串转为小写

        1
        select lower('HELLO')
    • upper(str)
      • 将字符串转为大写

        1
        select upper('hello')
    • substr(str,pos)
      • 截取从pos位置及末尾所有字符

        1
        select SUBSTR('welcometomysql',10)
    • substr(str,pos,len)
      • 截取从pos位置开始,字符长度为len的字符

        1
        select SUBSTR('welcometomysql',8,2)
    • instr(str,substr)
      • 返回substr子串在str中出现的第一个位置,找不到返回0

        1
        select instr('HelloWorldWorld','World')
    • trim(str)
      • 去除字符串str开始和结尾的空格

        1
        select trim('    hellowor ld   ')
    • trim(remstr from str)
      • 去除字符串str开始和结尾的remstr字符

        1
        select TRIM('A' FROM 'Ahelloworld')
    • lpad(str1,len,str2)
      • 在str1字符串开始处填充字符串str2,使其长度达到len,如果len小于原长度,从右边截断

        1
        select lpad('HELLO',8,'z')
    • rpad(str1,len,str2)
      • 在str1字符串结尾处填充字符串str2,….

        1
        select rpad('HELLO',8,'z')
    • replace(str1,s,str2)
      • 使用str2字符串替换str1字符串中的s字符串

        1
        select replace('hello','e','a')
  9. 数字函数

    • round(x)
    • 对x四舍五入,等同于round(x,0)
    • round(x,d)
      • 对x四舍五入,保留d位小数,d为负数时,指定小数点左边d位整数位为0,同时小数位也为0

        1
        2
        select round(3213.1415926,-2)
        # 3200
    • ceil(x)
      • 向上取整,返回>=x的最小整数

        1
        2
        select ceil(1.23)
        # 2
    • floor(x)
      • 向下取整,返回<=x的最大整数

        1
        2
        select floor(1.01)
        # 1
    • truncate(x,d)
    • 截断x,保留d位小数,不会进行四舍五入,d为负数时,指定小数点左边d为整数位为0
      1
      2
      select truncate(1.234,-2)
      # 0
    • mod(x,y)
      • 返回x除以y后的余数

      • 运算规则:x-x/y*y

        1
        select mod(-10,3)
  10. 日期函数

    • now()
  • 返回系统当前日期和时间
      
    1
    2
    select NOW()
    # 2019-08-12 09:35:59
    • current_date、curdate
      • 返回系统当前日期

        1
        2
        select CURRENT_DATE()
        # 2019-08-12
    • current_time、curtime
      • 返回系统当前时间

        1
        2
        select CURRENT_TIME()
        # 09:37:27
    • year(d)
      • 根据d返回年份

        1
        2
        3
        4
        select year(now())
        # 2019
        select year('2019-08-12')
        # 2019
    • month(d)
      • 根据d返回月份

        1
        2
        select month(now())
        # 8
    • day(d)
      • 根据d返回日

        1
        2
        select day(now())
        # 12
    • hour(d)
      • 根据d返回小时

        1
        2
        select hour(now())
        # 9
    • minute(d)
      • 根据d返回分钟

        1
        2
        select minute(now())
        # 41
    • second(d)
      • 根据d返回秒

        1
        2
        select second(now())
        # 50
    • str_to_date(str,format)
      • 将日期格式的字符串转换成指定格式的日期

        1
        2
        select str_to_date('2019-08-12 9:42:00','%Y-%m-%d %H:%i:%s')
        # 2019-08-12 09:42:00
    • date_format(date,format)
      • 将日期转换成字符串

        1
        2
        3
        4
        select date_format(now(),'%Y_%m_%d %H:%i:%s')
        # 2019_08_12 09:49:51
        select date_format('2019/08/12 09:51:00','%Y/%m/%d %H:%i:%s')
        # 2019/08/12 09:51:00
    • mysql日期格式
      格式 描述
      %Y 4位年份
      %y 2位年份
      %M 英文月名(January…December)
      %m 月份(01,02,03…12)
      %c 月份(1,2,3…12)
      %D 带有英文的天
      %d 天(00,01,02,03…31)
      %e 天(0,1,2,3…31)
      %H 小时,二十四小时制(00,01,02…23)
      %h 小时,十二小时制(01,02,03…12)
      %i 分钟(00,01,02…59)
      %s 秒(00,01,02…59)
  1. 其他函数

    • version()

      • 查询数据库版本

        1
        2
        select version()
        # 8.0.14
    • database()

      • 查询当前数据库名称

        1
        2
        select database()
        # pet_hospital
    • user()

      • 查看当前用户

        1
        2
        select user()
        # root@101.231.252.114
  2. 流程控制函数

    • if(expr,v1,v2)

      • 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2

        1
        2
        select IF(10%2=0,'偶数','奇数')
        # 偶数
    • case结构

      • CASE 表示函数开始,END表示函数结束。如果condition1成立,返回result1;如果condition2成立,返回result2;如果所有condition都不成立,返回result;其中一个condition成立,后面的condition不再判断

      • 语法

        1
        2
        3
        4
        5
        6
        7
        CASE expression
        WHEN condition1 THEN result1
        WHEN condition2 THEN result2
        ...
        WHEN conditionN THEN resultN
        ELSE result
        END
      • 简单case函数

        1
        2
        3
        4
        5
        6
        7
        8
        select 
        case age
        when 1 then '一岁大的狗'
        when 2 then '两岁大的狗'
        when 3 then '三岁大的狗'
        else '老狗'
        end '年龄'
        from pet_archives
      • case搜索函数

        1
        2
        3
        4
        5
        6
        7
        8
        select 
        case
        when age = 1 then '一岁大的狗'
        when age = 2 then '两岁大的狗'
        when age = 3 then '三岁大的狗'
        else '老狗'
        end '年龄'
        from pet_archives
      • 注意:简单case函数只返回第一个符合条件的值,剩下的自动忽略

  3. 分组函数

    • 功能:用作统计使用,又称作聚合函数、统计函数

    • 分类

      • 求和:sum

        1
        select sum(age) from pet_archives
      • 平均值:avg

        1
        select avg(age) from pet_archives
      • 最大值:max

        1
        select max(age) from pet_archives
      • 最小值:min

        1
        select min(age) from pet_archives
      • 统计数量:count

        1
        select count(*) from pet_archives
    • 特点

      • sum()、avg()一般用于处理数值型
      • max()、min()、count()可以处理任意类型
      • 以上函数都可以忽略null值
      • 可以和distinct关键字搭配上述函数进行去重
    • count()函数详解

      • count(*):对行的数目进行统计,包含NULL
      • count(column):对指定列不为NULL的行的数目进行统计
      • 效率
        • MYISAM引擎下,count(*)的效率高
        • INNODB引擎下,count(*)和count(1)的效率差不多,比count(column)要高一些
      • 注意:和分组函数一同查询的字段要求是group by后面的字段
  4. 分组查询

    • group by

      • 语法

        1
        2
        3
        4
        5
        select 分组函数,字段(需要出现在group by后面)
        from table
        [where 筛选条件]
        group by 分组列表
        [order by 字句]
    • 特点

      • 分组查询中的筛选条件分为两类

        数据源 位置 关键字
        分组前筛选 原始表 group by子句前面 where
        分组后筛选 分组后的结果集 group by子句后面 having
      • 分组函数做条件肯定是放在having子句中

      • 能用分组前筛选的,就优先考虑使用分组前筛选

    • 按函数分组

      • 示例

        1
        select count(*),length(name) lth from pet_archives group by lth
    • 按多个字段分组

      • 示例

        1
        select id,name,age lth from pet_archives group by name,id,age
    • 添加排序

      • 示例

        1
        select id,name,age from pet_archives group by id,name,age order by age desc
  5. 连接查询

    • 含义:多表查询,查询的字段来自多个表时,使用连接查询

    • 笛卡尔乘积:A表m行数据,B表n行数据,查询结果X=m*n行数据

      • 原因:没有有效的连接条件
      • 解决:添加有效的连接条件
    • 连接查询分类

      • 按年代分类
        • sql92标准:仅仅支持内连接
        • sql99标准:推荐,支持内连接+外连接(左外和内外)+交叉连接
      • 按功能分类
        • 内连接
          • 等值连接
          • 非等值连接
          • 自连接
        • 外连接
          • 左外连接
          • 右外连接
          • 全外连接
        • 交叉连接
    • sql92标准

      • 等值连接

        • 多表等值连接的结果为多表的交际部分

        • n表连接,至少需要n-1个连接条件

        • 多表的顺序没有要求

        • 一般需要给表起别名,方便书写、避免字段模棱两可

        • 可以和之前介绍的子句配合使用,如排序、分组、筛选

        • 示例

          1
          select a.name 宠物,a.age 宠物年龄,b.nickname 主人 from pet_archives a,pet_owner_user b where a.pet_user_id = b.id and a.age > 1 group by a.name,a.age,b.nickname order by a.age desc
      • 非等值连接

        • 示例

          1
          select salary,grade_level from employee a,job_grade b where salary between b.lowest_salary and highest_salary
      • 自连接

        • 示例

          1
          select a.employee_id 员工号,a.name 员工姓名,b.employee_id 主管员工号,b.name 主管姓名 from employee a,employee b where a.manager_id = b.employee_id
    • sql99标准

      • 语法

        1
        select 查询列表 from table1 [连接类型] join table2 on 连接条件 [where 筛选条件] [group by 分组列表] [having 筛选条件] [order by 排序列表] 
      • 连接类型

        • 内连接:inner
        • 外连接
          • 左外:left [outer]
          • 右外:right [outer]
          • 全外:full [outer]
        • 交叉连接:cross
      • 内连接

        • 语法

          1
          select 查询列表 from table1 inner join table2 on 连接条件
        • 特点

          • 添加排序、分组、筛选
          • inner可以省略
          • 筛选条件放在where后面,连接条件放在on后面,提高阅读性
          • inner join连接和sql92中的等值连接效果一样,都是查询多表的交集
        • 等值连接

          • 示例:三表联查

            1
            select a.name,a.age,b.nickname from pet_archives a inner join pet_owner_user b on a.pet_user_id = b.id inner join visit_record c on a.id = c.pet_id group by a.name,a.age,b.nickname order by a.age desc
        • 非等值连接

          • 示例

            1
            2
            select count(*),grade_level from employee a join job_grade b on a.salary between b.lowest_salary and highest_salary group by grade_level having
            count(*) > 20 order by grade_level desc
        • 自连接

          • 示例

            1
            select a.name 员工姓名,b.name 主管姓名 from employee a join employee b on a.manager_id = b.employee_id
      • 外连接

        • 应用场景:用于查询表1有,表2没有的记录

        • 特点

          • 外连接的查询结果为主表中的所有记录,如果从表中有和它匹配的,显示匹配的值,如果从表中没有和它匹配的,显示NULL,外连接查询结果=内连接查询结果+主表中存在但从表中不存在的记录
          • 左外连接,left join左边的是主表;右外连接,right join右边的的是主表
          • 左外和右外交换两个表的顺序,可以实现同样的效果
          • 全外连接=内连接的结果+表1有但表2没有的记录+表2有但表1没有的记录
        • 左外连接

          1
          select name,nickname from pet_owner_user a left outer join pet_archives b on a.id = b.pet_user_id
        • 全外连接

          • 注意:MySQL不支持全连接,可以通过下面的sql来实现全连接
          1
          select a.name,b.nickname from pet_archives a left join pet_owner_user b on a.pet_user_id = b.id union select a.name,b.nickname from pet_archives a right join pet_owner_user b on a.pet_user_id = b.id
      • 交叉连接

        • 示例

          1
          select a.name,b.nickname from pet_archives a cross join pet_owner_user b
  6. 子查询

    • 含义:出现在其他语句中的select语句,称为子查询或内查询;外部的查询语句,称为主查询或外查询

    • 分类

      • 按子查询出现的位置
        • select后面:仅仅支持标量子查询
        • from后面:支持表子查询
        • where或having后面:标量子查询、列子查询、行子查询
        • exist后面(相关子查询):表子查询
      • 按结果集的行列数不同
        • 标量子查询(结果集只有一行一列)
        • 列子查询(结果集只有一列多行)
        • 行子查询(结果集只有一行多列)
        • 表子查询(结果集一般为多行多列)
    • where或having后面的子查询

      • 分类

        • 标量子查询(单行子查询)

        • 列子查询(多行子查询)

        • 行子查询(一行多列)

      • 特点

        • 子查询放在小括号内
        • 子查询一般放在条件的右侧
        • 标量子查询,一般配合单行操作符使用 > < >= <= = <> ;列子查询,一般配合多行操作符使用 IN ANY/SOME ALL
        • 子查询的执行优先于主查询执行,主查询的条件需要使用子查询的结果
      • 标量子查询

        1
        select min(salary),department_id from employee group by department_id having min(salary) > (select min(salary) from employee where department_id = 50)
      • 多行比较操作符

        操作符 含义
        IN/NOT IN 等于列表中的任意一个
        ANY/SOME 和子查询返回的某个值比较
        ALL 和子查询返回的所有值比较
      • 列子查询

        1
        select id,name,pet_user_id from pet_archives where pet_user_id in (select id from pet_owner_user where age > 20)
    • select后面的子查询

      • 示例

        1
        select a.nickname 姓名,(select count(*) from pet_archives b where a.id = b.pet_user_id) 宠物总数 from pet_owner_user a
    • from后面的子查询

      • 示例

        1
        select temp.*,a.grade_level from (select avg(salary) avg_salary,department_id from employee group by department_id) temp inner join job_grade b on temp.avg_salary between lowest_sal and highest_sal
    • exist后面的子查询

      • 示例

        1
        select a.nickname from pet_owner_user a where exists (select * from pet_archives b where a.id = b.pet_user_id)
  7. 分页查询

    • 应用场景:当要显示的数据,一页显示不完,需要分页提交sql请求

    • 语法

      • offset:要显示条目的起始索引(起始索引从0开始)
      • size:要显示的条目个数
      1
      select 查询列表 from table [join type join 表2 on 连接条件 where 筛选条件 group by 分组字段 having 分组后的筛选 oder by 排序列表] limit offset,size 
    • 特点

      • limit语句放在查询语句的最后
    • 示例

      1
      select id,name,pet_user_id from pet_archives group by id limit 0,5
  8. 联合查询

    • union:联合,合并,将多条查询语句的结果合并成一个结果

    • 语法

      1
      2
      3
      4
      5
      6
      查询语句1
      union
      查询语句2
      union
      查询语句3
      ...
    • 特点

      • 要求多条查询语句的查询列数是一致的
      • 多条查询语句的查询列的类型和顺序最好保持一致
      • union关键字默认去重,如果使用union all,可以包含重复项
    • 应用场景

      • 要查询的结果来自多个表,且多个表没有直接的连接,但查询的信息一致时
    • 示例

      1
      2
      3
      select * from pet_archives where name like '%哈%' 
      union
      select * from pet_archives where age > 1
  9. DML语言

    • 数据操纵语言

      • 插入:insert
      • 删除:delete
      • 修改:update
    • 插入语句

      • 方式1

        1
        insert into table(column1,...) values(value1,...)
      • 示例

        1
        insert into reservation_type(name,total,hospital_id) values('宠物寄养',0,1001)
      • 方式2

        1
        insert into table set column1 = value1,...
      • 示例

        1
        insert into reservation_type set name = '代遛狗',total = 0,hospital_id = 1001
      • 批量插入

        1
        insert into table(column1,...) values(value,...),(value,...),(value,...)
      • 示例

        1
        insert into reservation_type(name,total,hospital_id) values('测试1',0,1001),('测试2',0,1001),('测试3',0,1001)
      • insert方式1和方式2的比较

        • 方式1支持插入多行
        • 方式1支持子查询,方式2不支持
          • insert into table(column1,…) select xxx,…
    • 修改语句

      • 修改单表的记录

        1
        update table set column1 = value1,... where 筛选条件
      • 示例

        1
        update reservation_type set name = '代撸猫' where id = 2010
      • 修改多表的记录

        • sql92语法

          1
          update table1 别名,table2 别名,... set column1 = value1,... where 连接条件 and 筛选条件
        • sql99语法

          1
          update table1 别名 inner|left|right join table2 别名 on 连接条件 set column = value1,... where 筛选条件
      • 示例

        1
        update pet_archives a left join pet_owner_user b on a.pet_user_id = b.id set a.gender = 'f',b.gender = 'f' where b.id = 18
    • 删除语句

      • 方式1:delete

        • 删除单表的记录

          1
          delete from table where 筛选条件
        • 示例

          1
          delete from reservation_type where id = 2016
        • 删除多表的记录

          • sql92语法

            1
            delete table1的别名,table2的别名 from table1 别名,table2 别名 where 连接条件 and 筛选条件
          • sql99语法

            1
            delete table1的别名,table2的别名 from table1 别名 inner|left|right join table2 别名 on 连接条件 where 筛选条件
        • 示例

          1
          delete a,b from pet_archives a left join pet_owner_user b on a.pet_user_id = b.id where b.id = 18
      • 方式2:truncate

        • 语法

          1
          truncate table 表名
      • delete和truncate的比较

        • delete可以添加where筛选条件,truncate不可以添加筛选条件
        • truncate删除效率比delete要高一些
        • 使用delete删除记录后,再插入数据,自增列的值从断点开始;truncate删除记录后,再插入数据,自增列的值从1开始
  • delete删除有返回值;truncate删除没有返回值
    - delete是DML语句,可以使用rollback进行回滚;truncate是DDL语句,需要drop权限,因此会隐式提交,不能rollback
    
  1. DDL语言

    • 数据定义语言

      • 库的管理
        • 创建、修改、删除
      • 表的管理
        • 创建、修改、删除
      • 创建:create
      • 修改:alter
      • 删除:drop
    • 库的管理

      • 库的创建

        • 语法

          1
          create database 库名
      • 库的修改

        • 修改库名

          1
          rename 旧库名 to 新库名
        • 修改库的字符集

          1
          alter database 库名 character set utf8
      • 库的删除

        • 语法

          1
          drop database 库名
    • 表的管理

      • 表的创建

        • 语法

          1
          2
          3
          4
          create table 表名(
          列名 列类型(长度) 约束,# 注释
          ...
          )
        • 示例

          1
          2
          3
          4
          create table testTable(
          testId int(10) primary key,# 测试id
          testName varchar(255)# 测试名称
          )
      • 表的修改

        • 语法

          1
          alter table 表名 add|drop|modify|change column 列名 [列类型 约束]
        • 修改列名

          1
          alter table 表名 change column 旧列名 新列名 类型(长度) 
        • 修改列类型或约束

          1
          alter table 表名 modify column 列名 类型(长度)
        • 添加新列

          1
          alter table 表名 add column 列名 类型(长度)
        • 删除列

          1
          alter table 表名 drop column 列名
        • 修改表名

          1
          alter table 表名 rename to 新表名
      • 表的删除

        • 语法

          1
          drop table if exists 旧表名
      • 表的复制

        • 仅仅复制表的结构

          1
          create table 目标表 like 源表 
        • 复制表的结构和数据

          1
          create table 目标表 select * from 源表
        • 只复制部分数据

          1
          create table 目标表 select 查询列表 from 源表 where 筛选条件
        • 只复制某些字段

          1
          create table 目标表 select 要复制的字段 from 源表 where 0 
  2. 常见数据类型

    • 数值型
      • 整数:默认有符号,如果要设置无符号,使用unsigned关键字
        • tinyint:一个字节
        • smallint:两个字节
        • mediumint:三个字节
        • int/integer:四个字节
        • bigint:八个字节
      • 小数
        • 定点型
          • decimal(m,d):如果m>d,m+2个字节,否则d+2个字节
        • 浮点型
          • fload(m,d):四个字节,单精度
          • double(m,d):八个字节,双精度
    • 字符型
      • 较短的文本
        • char(m):固定长度字符串,m在0~255之间
        • varchar(m):可变长度字符串,m在0~65535之间
      • 较长的文本
        • text:长文本数据
        • blob:二进制长文本数据
    • 日期型
      • date:最小值1000-01-01,最大值9999-12-31
      • datetime:最小值1000-01-01 00:00:00,最大值9999-12-31 23:59:59
      • timestamp:最小值1970-01-01 00:00:00,最大值2038-01-19
      • year:最小值1901,最大值2155
      • time:最小值-838:59:59,最大值838:59:59
  3. 常见约束

    • 含义:为了保证表中数据的准确性和可靠性,对表中的数据进行的一种限制

    • 分类

      • not null:非空,保证该字段的值不能为空
      • default:默认,保证该字段有默认值
      • primary key:主键,保证该字段的值在表中是唯一的,并且不为空
      • unique:唯一,保证该字段的值具有唯一性,可以为空
      • check:检查约束,mysql不支持
      • foreign key:外键,限制两个表的关系,保证该字段的值必须来自主表的关联列的值,在从表中添加外键约束,用于引用主表中某列的值
    • 列级约束和表级约束

      • 列级约束:对一个数据列建立的约束,可在列定义时声明,也可在列定义后声明,not null、default只存在列级约束
      • 表级约束:对多个数据列简历的约束,只能在列定义后声明,primary key、unique、foreign key同时存在列级约束和表级约束
    • 创建表时添加约束

      • 添加列级约束:字段名 数据类型 约束类型

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        create table student(
        id int(11) primary key,# 主键
        st_name varchar(255) not null,# 非空
        gender char(1),
        age int default 0,# 默认
        c_id int foreign key reference class(id)# 外键
        )

        create table class(
        id int(11) primary key,# 主键
        c_name varchar(255) not null# 非空
        )
      • 添加表级约束:constraint 约束名 约束类型(字段名)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        create table student(
        id int,
        st_name varchar(255),
        gender varchar(255),
        age int,
        c_id int,

        constraint pk primary key(id),# 主键
        constraint uk unique(st_name),# 唯一
        constraint fk_c_id foreign key(c_id) reference class(id),# 外键
        )
    • 主键和唯一的比较

      • 主键:保证唯一性;不允许为空;一张表中只能有一个
      • 唯一:保证唯一性;允许为空;一张表中可以有多个
    • 外键特点

      • 需要在从表中设置外键关系
      • 从表的外键列的类型和主表的关联列的类型要求一致或兼容
      • 主表的关联列必须是一个key(一般为主键或唯一)
      • 插入数据时,先插入主表,再插入从表;删除数据时正好相反
    • 修改表时添加约束

      • 添加列级约束

        1
        alter table 表名 modify column 列名 数据类型 约束
      • 添加表级约束

        1
        alter table 表名 add constraint 约束名 约束类型(列名) [主表(关联列)]
      • 添加非空约束

        1
        alter table 表名 modify column 列名 数据类型 not null
      • 添加默认约束

        1
        alter table 表名 modify column 列名 数据类型 default 默认值
      • 添加主键

        1
        2
        3
        4
        # 列级约束
        alter table 表名 modify column 列名 数据类型 primary key
        # 表级约束
        alter table 表名 add primary key(列名)
      • 添加唯一

        1
        2
        3
        4
        # 列级约束
        alter table 表名 modify column 列名 数据类型 unique
        # 表级约束
        alter table 表名 add unique(列名)
      • 添加外键

        1
        alter table 从表名 add constraint 外键名称 foreign key(列名) reference 主表名(列名)
    • 修改表时删除约束

      • 删除非空约束

        1
        alter table 表名 modify column 列名 数据类型 NULL
      • 删除默认

        1
        alter table 表名 modify column 列名 数据类型
      • 删除主键

        1
        alter table 表名 drop primary key
      • 删除唯一

        1
        alter table 表名 drop index 约束名
      • 删除外键

        1
        alter table 表名 drop foreign key 约束名
  4. 标识列

    • 含义:不需要手动设置值,系统提供默认的序列值,又叫自增序列

    • 特点

      • 自增序列必须和主键配合使用吗?
        • 不一定,但是必须是一个key()
      • 一个表最多有几个自增序列?
        • 最多一个
      • 自增序列可以是什么类型?
        • 只能是数值型
      • 自增序列如何设置自增的步长值?
        • set auto_increment_increment = x
    • 创建表时设置自增序列

      1
      2
      3
      create table 表名(
      id int primary key auto_increment
      )
    • 修改表时设置自增序列

      1
      alter table 表名 modify column 列名 数据类型 [约束] auto_increment
    • 修改表时删除自增序列

      1
      alter table 表名 modify column 列名 数据类型
  5. 事务

    • 含义

      • 一条或多条sql语句执行时要么全部执行成功,要么全部失败
    • 特性

      • 原子性(Atomicity):事务中的操作像原子一样不可分割,要么全部成功,要么全部失败
      • 一致性(Consistency):事务的执行结果必须使数据库从一个一致性状态变为另一个一致性状态(系统状态满足数据的完整性约束;系统的状态反映数据库本应描述的现实世界的真实状态)
      • 隔离性(Isolation):并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样
      • 持久性(Durability):事务一旦提交,其对数据库的影响是永久性的。任何事务或系统故障都不会导致数据丢失
    • 事务的创建

      • 隐式事务:事务没有明显的开启和结束的标记,如insert、update、delete语句

      • 显式事务:事务具有明显的开启和结束的标记

        • 前提:必须设置禁用自动提交

        • 步骤

          1
          2
          3
          4
          5
          begin或start transaction
          sql语句,
          ...
          ...
          commit
    • 事务并发问题

      • 脏读:事务A读取了被事务B更新的数据,但事务B未提交,后面如果事务B回滚,事务A之前读取到的数据就是临时且无效的
      • 不可重复读:事务A第一次读取到一行记录row1,事务B提交后,事务A第二次读取到row1,但row1的数据已经发生变化
      • 幻读:事务A第一次读取到一行记录row1,事务B提交修改后,事务A第二次读取到row1和row2…
    • 数据库事务的隔离性:数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题

    • 事务隔离级别

      • READ UNCOMMITED:允许事务读取未被其他事务提交的变更,脏读、不可重复读、幻读都可能存在
      • READ COMMITED:只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复读、幻读可能存在
      • REPEATABLE READ:一个事务在其提交之前,读取的数据都是相同的,即使其他事务作了修改,但幻读可能存在
      • SERIALIZABLE:一个事务持续期间,禁止其他事务进行任何操作,但效率很低
    • Oracle支持的事务隔离级别:READ COMMITED(默认)、SERIALIZABLE

    • MySQL支持的事务隔离级别:READ UNCOMMITED、READ COMMITED、REPEATABLE READ(默认)、SERIALIZABLE

一、linux系统启动
  1. 修改root用户密码

    1
    sudo passwd root
  2. 停机和关机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    shutdown -H		停机
    shutdown -P 关机(断电)
    shutdown -r 重启
    shutdown -h 相当于 -P ,除非指定 -H
    shutdown [options] [time] 在指定时间执行操作
    shutdown -h now 马上关机
    shutdown -h +n 在 n 分钟后关机
    shutdown -h 13:14 在今天 13:14 关机

    shutdown -k 只发送 wall message
    shutdown -c 取消 shutdown 命令 执行
    shutdown --no-wall 执行 shutdown 命令前不发送 wall message
二、linux系统目录结构
  1. 输入 ls -/ 命令查看目录结构

    1
    bin  boot  dev  etc  home  init  lib  lib64  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var
  2. 目录结构的作用和含义

    • /bin 存放常用的命令,如 cat、chmod、cp 等
    • /boot 存放启动linux需要的核心文件
    • /dev 存放linux的外部设备
    • /etc 存放系统管理需要的配置文件和子目录
    • /home 用户主目录,以用户名命名,如 /codecho
    • /lib 存放系统最基本的动态链接共享库,作用和windows中的DLL文件类似,几乎每个程序都使用到这些共享库
    • /lost+found 一般为空,当系统非法关机后,该目录存放一些“孤儿???”文件
    • /media linux识别设备后将其挂载到此目录
    • /mnt 系统临时挂载点
    • /opt 存放安装的第三方软件
    • /proc 虚拟的文件系统,非真实存在,记录和系统相关的信息
    • /root root用户主目录
    • /sbin 存放root用户使用的命令
    • /srv 存放一些服务启动后需要的数据
    • /sys 也是一个虚拟的文件系统,是对linux系统下所有设备的映射
    • /tmp 存放临时文件
    • /usr 存放用户的程序和文件
    • /usr/bin 存放一般的应用程序
    • /usr/sbin 存放root用户使用的应用程序
    • /usr/src 存放linux内核源码
    • /var 存放经常会变化的文件,比如系统的日志
    • /run 存放系统启动后的信息,重启时清空
三、linux文件基本属性
  1. 使用 ls -l 或 ll 命令显示文件/文件夹的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    drwxr-xr-x  23 root root      4096 Jan 25 19:47 ./
    drwxr-xr-x 23 root root 4096 Jan 25 19:47 ../
    drwxr-xr-x 2 root root 4096 Jan 25 17:23 bin/
    drwxr-xr-x 3 root root 4096 Dec 12 2018 boot/
    drwxr-xr-x 18 root root 3740 Jan 25 16:29 dev/
    drwxr-xr-x 91 root root 4096 Jul 19 09:49 etc/
    drwxr-xr-x 3 root root 4096 Dec 26 2018 home/
    lrwxrwxrwx 1 root root 33 Dec 12 2018 initrd.img -> boot/initrd.img-4.15.0-42-generic
    lrwxrwxrwx 1 root root 33 Dec 12 2018 initrd.img.old -> boot/initrd.img-4.15.0-20-generic
    drwxr-xr-x 19 root root 4096 Dec 12 2018 lib/
    drwxr-xr-x 2 root root 4096 Jan 25 17:22 lib64/
    drwx------ 2 root root 16384 Dec 12 2018 lost+found/
    drwxr-xr-x 4 root root 4096 Dec 12 2018 media/
    drwxr-xr-x 2 root root 4096 Apr 27 2018 mnt/
    drwxr-xr-x 3 root root 4096 Dec 27 2018 opt/
    dr-xr-xr-x 134 root root 0 Jan 25 16:29 proc/
    drwx------ 11 root root 4096 Jul 16 20:19 root/
    drwxr-xr-x 25 root root 800 Jul 19 09:09 run/
    drwxr-xr-x 2 root root 4096 Dec 27 2018 sbin/
    drwxr-xr-x 2 root root 4096 Apr 27 2018 srv/
    -rw------- 1 root root 993249280 Dec 12 2018 swapfile
    dr-xr-xr-x 13 root root 0 Jan 26 00:29 sys/
    drwxr-xr-x 3 root root 4096 Jan 25 19:47 test/
    drwxrwxrwt 22 root root 4096 Jul 19 10:58 tmp/
    drwxr-xr-x 12 root root 4096 Jan 25 18:36 usr/
    drwxr-xr-x 12 root root 4096 Jun 18 15:40 var/
    • 第一个字符为 d 时,表示是目录
    • 第一个字符为 - 时,表示是文件
    • 第一个字符为 l 时,表示是链接文件
    • 第一个字符为 b 时,表示是装置文件里面的可供储存的接口设备(可随机存取装置)
    • 第一个字符是 c 时,表示是装置文件里面的串行端口设备,例如键盘、鼠标(一次性读取装置)
    • rwx rwx rwx 分别表示 文件所有者(owner) 、所有者同组用户(group)、其他用户(others) 拥有该文件的权限
    • r 表示读权限,- 表示没有读权限
    • w 表示写权限,- 表示没有写权限
    • x 表示执行权限,- 表示没有执行权限
    • 特殊情况: t 表示 sticky bit ,可以理解为 防删除位,一般用在目录上,如 /temp 目录,用户只能删除自己的文件,不能删除其他用户的文件
  2. 更改文件属性的命令

    • chgrp:更改文件所属的用户组 -R 表示递归更改所属的用户组,更改某个目录下的所有文件的所属的用户组
    1
    chgrp [-R] 所属的用户组 文件/目录
    • chown:更改文件所属用户,也可以同时更改文件的所属的用户组

      1
      2
      chown [-R] 所属的用户 文件/目录
      chown [-R] 所属的用户:所属的用户组 文件/目录
    • chmod:更改文件的9个属性 其中 xyz 表示三个权限的数字之和,如下所示

      1
      2
      3
      4
      5
      6
      -----------------使用数字改变文件的权限-----------------
      chmod [-R] xyz 文件/目录
      各权限代表的数字:r=4 w=2 x=1 -=0(没有权限则为0)
      x: [r+w+x] = [4+2+1] --> 7 表示 owner 所拥有的权限
      y: [r+w+x] = [4+2+1] --> 7 表示 group 所拥有的的权限
      z: [r+w+x] = [4+2+1] --> 7 表示 others 所拥有的权限
      1
      2
      3
      4
      5
      6
      -----------------使用符号改变文件的权限-----------------
      将用户分为四种类型:a=all(所有用户) u=user(所属用户) g=group(所属用户组) o=others(其他用户)
      将操作分为 +(添加) -(去除) =(设置)
      chmod u=rw,g=wx,o=x test.txt 将test.txt文件的权限设为rw--wx--x
      chmod a-w test.txt 去除所有用户对test.txt文件的写权限
      chmod a+x test.txt 添加所有用户对test.txt文件的执行权限
四、linux文件与目录的操作
  1. linux根目录:/

  2. 文件/目录的常用操作命令

    • ls:列出目录

      1
      2
      3
      ls -a 列出所有的文件/目录,包括以 . 开头的文件
      ls -d 列出目录本身,不包括其内容
      ls -l 列出所有详细信息,包括权限、日期、所属用户等
    • cd:切换目录

      1
      2
      3
      cd [绝对路径/相对路径] 
      cd / 切换到根目录
      cd .. 返回当前目录的上一级目录
    • pwd:显示当前目录

      1
      pwd	显示当前目录
    • mkdir:创建目录

      1
      2
      mkdir -m xyz 目录名 创建目录时同时设置目录的权限,如 mkdir -m 777 test
      mkdir -p 目录1/目录2/目录3 递归创建多级目录,如 test1/hello/world
    • rmdir:删除空目录

      1
      rmdir -p 目录1/目录2/目录3 连带其上级目录一起删除
    • cp:复制文件/目录

      1
      2
      cp [options] source dest
      cp -r test test1/test2 递归复制
    • rm:删除目录或文件

      1
      2
      3
      4
      rm [-fir] 文件/目录
      rm -f 文件/目录 直接删除文件/目录,没有警告信息
      rm -i 文件/目录,删除前显示警告
      rm -r 文件/目录,递归删除
    • mv:移动目录/文件,或者修改名称

      1
      2
      3
      mv [-fi] source dest
      mv -f source dest 若目标文件存在,直接覆盖
      mv -i source dest 若目标文件存在,询问是否覆盖
  3. linux文件内容查看

    • cat:从第一行开始显示内容

      1
      2
      3
      4
      cat -b test.txt 列出行号,空白行不显示行号
      cat -n test.txt 列出行号,空白行也显示行号
      cat -E test.txt 显示结尾的断行字节 &
      cat -T test.txt 将 tab 制表符显示为 ^|
    • tac:从最后一行开始显示内容,和 cat 相反

    • tail:显示文件最后 n 行内容

      1
      tail [-n number] 文件名 number 为要显示的行数
五、linux用户和用户组管理
  1. 添加用户账号

    • useradd [options] 用户名

      1
      2
      3
      4
      5
      6
      sudo useradd -d /home/pain -m pain
      -d 指定用户主目录
      -m 如果主目录不存在则创建
      -g 指定用户所属用户组
      -G 指定用户所属的附加组
      -s 指定用户登录shell
  2. 设置用户账号密码

    • passwd [options] 账号

      1
      2
      3
      -l 锁定账号
      -e 强制账号密码过期
      -d 强制删除账号密码
  3. 删除用户账号

    • userdel [options] 账号

      1
      2
      userdel -r pain 
      -r 将账号的主目录也删除
  4. 修改用户账号

    • usermod [options] 账号

      1
      2
      3
      4
      usermod -s /bin/hello -d /home/hello -g test pain
      -s 指定新的登录shell
      -d 指定新的用户主目录
      -g 指定新的用户组
  5. 添加用户组

    • groupadd [options] 用户组

      1
      2
      -g GID 指定用户组的id
      -o 表示新用户组的id可以和已有用户组的id相同
  6. 删除用户组

    • groupdel 用户组
  7. 修改用户组

    • groupmod [options] 用户组

      1
      2
      groupmod -g 102 group2
      将group2的用户组id改为102
  8. 切换用户组(前提是该用户属于要切换的用户组)

    • newgrp 要切换的用户组
六、linux磁盘管理
  1. df:查看文件系统的整体磁盘空间使用量

    1
    2
    3
    df [options] 目录/文件
    -a 列出所有的文件系统
    -T 显示文件系统类型
  2. du:查看文件和目录的磁盘使用空间

    1
    2
    3
    du [options] 文件/目录
    -a 列出所有的文件与目录容量
    -h 以易读的方式显示
七、linux vi/vim
  1. vi/vim主要分为三种模式:命令行模式、编辑模式、底部命令行模式

    • 命令行模式:此时用户输入会被识别为命令

      1
      2
      3
      i 切换到编辑模式
      x 删除光标所在位置的字符
      : 切换到底部命令行模式
    • 编辑模式:可以输入内容编辑文件,按下 esc ,退出编辑模式

    • 底部命令行模式:输入命令

      1
      2
      3
      4
      q 退出
      w 保存
      wq 保存退出
      q! 不保存退出
    • 常用按键:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      —————————————底部命令行使用的复制粘贴、搜索替换等操作—————————————
      /key 寻找光标后方的名为 key 的字符串
      ?key 寻找光标前方的名为 key 的字符串
      n 重复上一个寻找的操作,如执行 /key 寻找光标后方的名为 key 的字符串,按下 n 继续向光标后方寻找
      N 反向重复上一个寻找的操作,如上,按下 N 向光标前方寻找
      :n1,n2s/key1/key2/g 在n1行到n2行之间寻找名为key1的字符串,并将其替换为key2
      :1,$s/key1/key2/g 从第一行到最后一行之间寻找key1的字符串,并将其替换为key2
      :$s/word1/word2/g 同上,写法不同
      :1,$s/key1/key2/gc 从第一行到最后一行之间寻找key1的字符串,并将其替换为key2,取代之前会有提示信息,让用户确认是否替换
      :$s/word1/word2/gc 同上,写法不同
      dd 删除光标所在的一整行
      nx 连续向后删除 n 个字符
      ndd 删除光标后方的 n 行
      d$ 删除光标到该行最后一个字符
      d0 删除光标到改行第一个字符
      yy 复制光标所在行的内容
      nyy 复制光标后方的 n 行内容
      y$ 复制光标到该行最后一个字符
      y0 复制光标到该行第一个字符
      p,P p 将复制的内容粘贴到光标的下一行, P 将复制的内容粘贴到光标的上一行
      c 重复删除多个数据,如 10cj 删除光标下方10行
      u 复原前一个动作
      ctrl+r 重做前一个动作

      —————————————底部命令行模式切换到编辑模式的操作—————————————
      i,I i 从光标处输入 I 从光标所在行第一个非空格符处输入
      a,A a 从光标处的下一个字符处输入 A 从光标所在行最后一个字符处输入
      o,O o 从光标处的下一行输入 O 从光标处的上一行输入

      —————————————编辑模式切换到底部命令行模式的操作—————————————
      ESC 从编辑模式退出到底部命令行模式

      —————————————底部命令行模式切换到命令行模式的操作—————————————
      :w 保存内容
      :w! 强制保存
      :q 不保存退出
      :q! 强制不保存退出
      :wq 保存并退出
八、linux yum命令
  1. yum:查找、安装、删除软件包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    yum [options] [command] [package]
    -y 安装过程选择 yes
    -q 不显示安装过程
    command 要执行的命令
    package 软件包

    yum check-update 列出可更新的软件
    yum update 更新所有软件
    yum list 列出可安装的软件
    yum install package 安装指定的软件
    yum update package 更新指定的软件
    yum remove package 删除指定的软件
九、Shell脚本
  1. shell:一种应用程序,它是用户和linux内核之间的桥梁,用户通过shell来间接地使用内核

  2. shell脚本:一种为shell编写的脚本程序

  3. shell的分类:

    • Bourne Shell(/usr/bin/sh或/bin/sh)
    • Bourne Again Shell(/bin/bash)是linux的默认shell
    • C Shell(/usr/bin/csh)
    • K Shell(/usr/bin/ksh)
    • Shell for Root(/sbin/sh)
  4. 查看系统可用的shell

    1
    2
    3
    4
    5
    6
    7
    8
    cat /etc/shells

    /bin/sh
    /bin/bash
    /bin/rbash
    /bin/dash
    /usr/bin/tmux
    /usr/bin/screen
  5. 查看当前系统的默认shell

    1
    2
    3
    echo $SHELL

    /bin/bash
  6. 编写一个简单的shell脚本

    1
    2
    #!/bin/bash
    echo "hello world,this is a shell script"
  7. 执行shell脚本:

    1
    2
    3
    4
    5
    ./helloworld.sh		注意要在shell脚本所在目录
    hello world,this is a shell script

    /bin/sh helloworld.sh
    hello world,this is a shell script
十、linux命令(经常使用的)
  1. 文件管理

    • cat [options] filename

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      -n | --number	从1开始对输出的行数编号
      -b | --number-nonblank 和-n相似,但是对空白行不编号
      -s | --squeeze-blank 连续两行以上空白行,替换为一行空白行
      -E | --show-ends 在每行结束处显示 $
      -T | --show-tabs 将 TAB 符显示为 ^|

      cat test1.txt 显示test1.txt中的内容
      cat test1.txt test2.txt 同时显示test1.txt和test2.txt中的内容
      cat test1.txt test2.txt > test3.txt 将test1.txt和test2.txt中的内容写到test3.txt中
      cat /dev/null > test4.txt 将test4中的内容清空

      /dev/null 是一个特殊的设备文件,它丢弃一切写入其中的数据(报告写入成功),读取它则会立即得到一个 EOF
    • chgrp:参考 三

    • chmod:同上

    • chown:同上

    • find path [options],命令行上第一个 -(),! 之前的部分为path,后面的是expression,如果path为空字符串,使用当前路径,如果expression为空字符串,使用-print为预设expression

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      find . -name ".txt"		在当前目录查找所有以 .txt 结尾的文件
      find . -iname "T*" 查找文件名满足T*的文件,忽略大小写
      find . -cmin +10 查找修改时间超过10分钟的文件
      find . -amin +10 查找超过10分钟内被访问的文件
      find . -newer test3.txt 查找比test3.txt文件修改时间更晚的文件
      find . -anewer test3.txt 查找比test3.txt文件更晚被读取的文件
      find . -type c 查找文件类型是 c 的文件
      c的取值
      d 目录
      l 符号链接
      f 普通文件
      c 字符设备
      b 块设备
      s 套接字
    • mv:参考 三

    • rm:同上

    • tee [-ai][file]

      1
      2
      3
      tee test1.txt -a	读取用户输入的内容,将其输出到test1.txt文件中
      -a | --append 附加到文件后面,而非覆盖
      -i | --ignore-interrupts 忽略中断信号