0%

我们在使用云服务器时的过程中,可能经常会发现自己的主机被各种扫描端口和尝试登录,如果安全意识不够,很可能会发生服务器被别人入侵,导致自己的数据资产受损。下面介绍如何修改ssh登录默认端口,减少自己主机被暴力登录的情况发生。

查看服务器是否被暴力登录

  • 查看/var/log/secure日志,如果发现有很多登录密码错误的信息,表示服务器可能正在被别人尝试暴力登录

    1
    tail -n 100 /var/log/secure | grep "Failed password"

修改默认端口

  • 修改sshd配置文件

    1
    2
    3
    4
    5
    vim /etc/ssh/sshd_config

    # 增加另一个端口号 1234
    Port 22
    Port 1234
  • 重启sshd服务

    1
    systemctl restart sshd
  • 修改防火墙或安全组配置,增加新端口

  • 使用新端口连接服务器,修改sshd_config配置文件,将原端口注释,保留新端口

    1
    2
    # Port 22
    Port 1234

屏蔽尝试非法登录的ip

​ 通过shell脚本和定时任务,将ip加入屏蔽名单,详细参考这里

需要提前知道的一些内容

  1. Linux下的/var/log/secure文件记录了登录Linux服务器的日志信息,如果有人尝试破解你的服务器,那么可以在这个文件中看到很多密码错误的信息,如下图

  2. Linux下的/etc/hosts.deny文件可以配置需要禁止访问的ip,外部请求进入时,会先在/etc/hosts.allow中检查ip是否存在,若存在,直接放行请求,如果没有,判断在是否在/etc/hosts.deny中,如果在,禁止请求连接

  3. Linux支持cron定时任务,搭配shell脚本可以让我们定时运行自己编写的脚本,不需要人工进行干预

本机Linux版本为CentOS7

先梳理整个流程

  1. 读取/var/log/secure文件,筛选出试图登录服务器但密码错误的信息
  2. 从每一行信息中分离出对应的外部ip
  3. 对这些ip进行排序和去重,统计每个ip尝试登录的次数
  4. 遍历所有的外部ip,判断当前ip尝试登录次数是否超过忍耐值(可能自己有时候也会输错密码或者其他原因导致登录失败),将该ip添加到/etc/hosts.deny
  5. 使用Linux自带的cron服务创建定时任务,每隔一定的时间就执行该脚本

开始动手

  1. 创建deny_illegal_ip.sh脚本文件

    1
    2
    cd /usr/shell_study
    touch deny_illegal_ip.sh
  2. 创建一个文件来保存非法访问的ip

    1
    touch illegal_ip_list.txt
  3. 创建一个文件来保存cron定时任务执行日志

    1
    touch cron_task.log
  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
    32
    33
    34
    35
    36
    37
    38
    39
    IP_LIST_PATH="/usr/shell_study/illegal_ip_list.txt"
    # hosts.deny path
    HOSTS_DENY_PATH="/etc/hosts.deny"
    # hosts.deny backup path
    HOSTS_DENY_BAK_PATH="/etc/hosts.deny.bak"
    # cron task log path
    CRON_TASK_LOG_PATH="/usr/shell_study/cron_task.log"
    # the number of illegal ip
    IP_COUNT=0

    # clean old ip file
    if [ -e $IP_LIST_PATH ]
    then
    cat /dev/null > $IP_LIST_PATH
    fi

    # extract illegal ip
    cat $SECURE_LOG_PATH | grep "Failed password" | awk -F "from" '{print $2}' | awk -F "port" '{print $1}' | sort | uniq -c > $IP_LIST_PATH

    # reset hosts.deny, as we check the ip whether existed in hosts.deny, so this step can be removed
    #if [ -e $HOSTS_DENY_BAK_PATH ]
    #then
    # cat $HOSTS_DENY_BAK_PATH > $HOSTS_DENY_PATH
    #fi

    # add illegal ip to hosts.deny which appears more than once
    while read IP_LINE
    do
    COUNT=`echo $IP_LINE | awk -F " " '{print $1}'`
    IP=`echo $IP_LINE | awk -F " " '{print $2}'`
    if [ $COUNT -ge 2 -a `grep -c $IP $HOSTS_DENY_PATH` -eq 0 ]
    then
    let IP_COUNT++
    echo "sshd:$IP" >> $HOSTS_DENY_PATH
    fi
    done < $IP_LIST_PATH

    # add cron task log
    echo "$(date "+%Y-%m-%d %H:%M:%S"): added $IP_COUNT illegal ip" >> $CRON_TASK_LOG_PATH
  5. 设置定时任务,每天执行一次

    1
    2
    crontab -e
    0 0 * * * /usr/shell_study/deny_illegal_ip.sh
  6. 查看定时任务日志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    tail -f /usr/shell_study/cron_task.log
    2022-10-24 22:00:01: added 0 illegal ips
    2022-10-24 22:30:01: added 0 illegal ips
    2022-10-24 23:00:01: added 0 illegal ips
    2022-10-24 23:30:01: added 0 illegal ips
    2022-10-25 00:00:01: added 0 illegal ips
    2022-10-25 00:30:01: added 1 illegal ips
    2022-10-25 01:00:02: added 0 illegal ips
    2022-10-25 01:30:01: added 0 illegal ips
    2022-10-25 02:00:01: added 0 illegal ips
    2022-10-25 02:30:01: added 0 illegal ips
    2022-10-25 03:00:01: added 0 illegal ips
  7. 查看ip是否被禁止访问

    1
    vim /etc/hosts.deny

软件版本

  • Dubbo 3.X
  • Nacos 2.X

报错信息

Failed to create nacos naming service client. Reason: server status check failed

问题原因

Nacos2.X版本相比1.X新增了gRPC通信,因此添加了额外的端口,详见Nacos文档

问题解决

1
2
3
4
5
# 防火墙开放9848端口
[root@localhost logs]# firewall-cmd --add-port=9848/tcp --permanent
success
[root@localhost logs]# firewall-cmd --reload
success

相信大部分Java开发者在使用SpringBoot开发项目时,经常会遇到代码频繁改动,每次打包部署到开发/测试环境时,由于jar包体积过大,上传jar包消耗了大量的时间,下面介绍如何给SpringBoot项目jar包减肥(使用maven作为打包工具)

注:Spring Boot版本:2.5.7、JDK版本:jdk8

jar包体积

可以看到,一个普通的Spring Boot项目,其生成的jar包体积就高达几十近百兆


jar包结构

解压生成的jar包,查看其目录结构

  • BOOT-INF

    • classes 业务代码
    • lib 项目依赖
  • META-INF

    • maven maven配置信息
    • MANIFEST.MF jar包的描述信息和属性
    • spring-configuration-metadata.json 配置提示信息,使用IDE编写配置文件会有提示
  • org

    • springframework 启动jar包需要的class


修改maven打包配置

  • 修改项目pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>

    <configuration>
    <layout>ZIP</layout>
    <includes>
    <include>
    <groupId>nothing</groupId>
    <artifactId>nothing</artifactId>
    </include>
    </includes>
    </configuration>
    </plugin>
    </plugins>
    </build>
  • 使用maven重新打包,可以发现现在的jar包体积比原来要小很多


准备jar包运行需要的依赖

  • 不修改maven打包配置,生成带有lib目录的jar包,然后解压将lib目录单独提取出来

  • 修改maven打包配置,将依赖copy到指定目录

    在plugins标签中添加下面内容,如果maven-dependency-plugin找不到,可以先引入该依赖,然后删除该依赖配置,以后每次打包后在target目录下会生成一个lib依赖目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
    <execution>
    <id>copy-dependencies</id>
    <phase>prepare-package</phase>
    <goals>
    <goal>copy-dependencies</goal>
    </goals>
    <configuration>
    <outputDirectory>${project.build.directory}/lib</outputDirectory>
    </configuration>
    </execution>
    </executions>
    </plugin>

运行瘦身后的jar包(仅限JDK8版本)

  • 通过指定loader.path参数指定依赖目录

    1
    java -Dloader.path=./lib -jar user-0.0.1-SNAPSHOT.jar

  • 通过指定java.ext.dirs参数,注意不能覆盖原有的依赖($JAVA_HOME/jre/lib/ext),可以通过:添加多个依赖

    1
    java -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:./lib -jar user-0.0.1-SNAPSHOT.jar

    ==注意:==java.ext.dirs参数在JDK8以上版本好像不支持

在工作开发中,我们有时候需要将一份原始数据复制成一份新数据,再对新数据进行部分修改,这里就引出浅拷贝与深拷贝两个概念。

需要注意的是,浅拷贝和深拷贝是编程中的概念,它们并不局限于某一种语言,每种语言都有自己的浅拷贝/深拷贝方式。

什么是浅拷贝?

  • 对于基本类型数据,浅拷贝复制了数据本身,而对于对象类型数据,浅拷贝只复制了对象的引用,而不是对象本身

  • 因此,如果修改了浅拷贝后数据中的对象,那么原始数据中的对象也会受到影响

什么是深拷贝

  • 对于基本类型数据,深拷贝复制了数据本身,而对于对象类型数据,深拷贝会递归复制对象的所有元素,包括对象类型

  • 深拷贝后的数据是完全和原始数据独立的,因此对它进行任何操作都是安全的

Java中实现浅拷贝

  1. 使用Object.clone()方法

    • 代码示例

      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
      45
      46
      47
      48
      49
      50
      public class Order implements Cloneable {
      private String orderName;
      private Long orderNo;
      private Address address;

      // 省略getter/setter

      @Override
      protected Object clone() throws CloneNotSupportedException {
      return super.clone();
      }
      }

      public class Address {
      private String country;
      private String province;
      private String city;
      private String district;
      private String detailAddress;

      // 省略getter/setter
      }

      public class TestCopy {
      public static void main(String[] args) throws CloneNotSupportedException {
      Address address = new Address();
      address.setCountry("中国");
      address.setProvince("浙江省");
      address.setCity("杭州市");
      address.setDistrict("西湖区");
      address.setDetailAddress("凤起路");

      Order order = new Order();
      order.setOrderName("双十一好物订单");
      order.setOrderNo(202300156L);
      order.setAddress(address);

      System.out.println("order: " + order);
      System.out.println("address: " + address);
      System.out.println("detailAddress: " + address.getDetailAddress());

      Order copyOrder = (Order) order.clone();

      System.out.println("copyOrder: " + copyOrder);
      System.out.println("copyAddress: " + copyOrder.getAddress());

      copyOrder.getAddress().setDetailAddress("武林广场");
      System.out.println("detailAddress: " + address.getDetailAddress());
      }
      }
    • 运行结果

      1
      2
      3
      4
      5
      6
      order: com.codecho.copy.Order@677327b6
      address: com.codecho.copy.Address@14ae5a5
      detailAddress: 凤起路
      copyOrder: com.codecho.copy.Order@7f31245a
      copyAddress: com.codecho.copy.Address@14ae5a5
      detailAddress: 武林广场

      ==可以看出,使用Object.clone()方法复制出了一个新的Order对象,但是Order对象内部的Address还是原来的Address对象,并且对新Order对象内部Address进行修改会影响到原Address对象,说明Object.clone()方法只是拷贝了对象的引用,并没有拷贝对象本身。==

  2. 使用apache或spring提供的BeanUtils工具类

    • 代码示例

      1
      2
      3
      4
      5
      <dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.9.4</version>
      </dependency>
      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
      public class TestCopy {

      public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
      Address address = new Address();
      address.setCountry("中国");
      address.setProvince("浙江省");
      address.setCity("杭州市");
      address.setDistrict("西湖区");
      address.setDetailAddress("凤起路");

      Order order = new Order();
      order.setOrderName("双十一好物订单");
      order.setOrderNo(202300156L);
      order.setAddress(address);

      System.out.println("order: " + order);
      System.out.println("address: " + address);
      System.out.println("detailAddress: " + address.getDetailAddress());

      Order copyOrder = new Order();
      BeanUtils.copyProperties(copyOrder, order);

      System.out.println("copyOrder: " + copyOrder);
      System.out.println("copyAddress: " + copyOrder.getAddress());

      copyOrder.getAddress().setDetailAddress("武林广场");
      System.out.println("detailAddress: " + address.getDetailAddress());
      }

      }
    • 运行结果

      1
      2
      3
      4
      5
      6
      order: com.codecho.copy.Order@330bedb4
      address: com.codecho.copy.Address@2503dbd3
      detailAddress: 凤起路
      copyOrder: com.codecho.copy.Order@685f4c2e
      copyAddress: com.codecho.copy.Address@2503dbd3
      detailAddress: 武林广场

      ==和Object.clone()方法一样,BeanUtils.copyProperties()方法也是浅拷贝==

Java中实现深拷贝

  1. 序列化和反序列化

    • 代码示例

      ==注意:Order和Address需要实现Serializable接口==

      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
      45
      46
      public class Order implements Serializable {
      ...
      }

      public class Address implements Serializable {
      ...
      }

      public static void main(String[] args) throws IOException, ClassNotFoundException {
      Address address = new Address();
      address.setCountry("中国");
      address.setProvince("浙江省");
      address.setCity("杭州市");
      address.setDistrict("西湖区");
      address.setDetailAddress("凤起路");

      Order order = new Order();
      order.setOrderName("双十一好物订单");
      order.setOrderNo(202300156L);
      order.setAddress(address);

      System.out.println("order: " + order);
      System.out.println("address: " + address);
      System.out.println("detailAddress: " + address.getDetailAddress());

      Order copyOrder = deepCopy(order);

      System.out.println("copyOrder: " + copyOrder);
      System.out.println("copyAddress: " + copyOrder.getAddress());

      copyOrder.getAddress().setDetailAddress("武林广场");
      System.out.println("detailAddress: " + address.getDetailAddress());
      }


      public static Order deepCopy(Order source) throws IOException, ClassNotFoundException {
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(outputStream);
      out.writeObject(source);

      ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
      ObjectInputStream in = new ObjectInputStream(inputStream);

      Order copy = (Order) in.readObject();
      return copy;
      }
    • 运行结果

      1
      2
      3
      4
      5
      6
      order: com.codecho.copy.Order@330bedb4
      address: com.codecho.copy.Address@2503dbd3
      detailAddress: 凤起路
      copyOrder: com.codecho.copy.Order@7e6cbb7a
      copyAddress: com.codecho.copy.Address@7c3df479
      detailAddress: 凤起路

在使用CentOS时,有时候我们需要执行某个时间之前的命令,但是这些命令有时候参数过多,一时不容易想起,这时候很需要一个工具来帮我们记录执行过哪些命令

使用ctrl+R反向查找|搜索历史命令

1
2
3
4
5
6
7
8
9
(reverse-i-search)`':

# 输入要查找的命令,如docker
(reverse-i-search)`docker': docker logs 6316724f7425

# 使用ctrl+R向前连续查找

# 使用方向键 < or > 选取命令
[root@centos7 codecho]# docker logs 6316724f7425

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"));
    }

环境准备

  • 操作系统: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地址

  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保存了一些软件压缩包,清理该目录也不会有任何影响

在使用GitHub Page搭建博客后,买了一个域名和xxx.github.io绑定,域名过期后,直接访问xxx.github.io发现每次都重定向到之前的域名,然而在更换了浏览器重新访问发现可以正常访问,因此想到可能是浏览器的缓存信息没有更新

解决方法:清除浏览器缓存(以Google Chrome为例)

打开浏览器设置,点击隐私和安全,选择清除浏览数据建议通过google账号同步数据后退出,然后勾选下面的浏览记录、cookie、缓存文件、网站设置等,点击清除数据,重新访问GitHub Page网址,发现可以正常访问博客内容