跳至主要內容
MybatisPlus更新Null字段

本文介绍【Mybatis-plus】updateById()方法不能更新字段为null的原因及解决办法。

一、问题描述

在日常项目开发过程中,经常会使用MybatisPlus的updateById()方法,快速将接收到的参数或者查询结果中原本不为null的字段更新为null,并且该字段在数据库中可为null,这个时候使用updateById()并不能实现这个操作,不会报错,但是对应的字段并没有更新为null。

二、问题原因


集成配置MyBatisMyBatis大约 2 分钟
Keepalived脑裂的解决和预防

Keepalived脑裂的解决和预防

#!/bin/bash
export PATH=$PATH:/usr/sbin
## 脑裂检查及控制:第三方仲裁机制,使用ping网关ip方式
## 循环次数
CHECK_TIME=3
## 虚拟ip
VIP=$1
## 网关ip(根据实际环境修改)
GATEWAY=192.168.1.8
## 本机网卡
eth=enp2s0
## 服务器和网关通信状态  0=失败,1=成功
keepalived_communication_status=1
## 是否获取vip状态 0=失败,1=成功
get_vip_status=1
## keepalived服务状态 0=未运行,1=运行中
keepalived_service_status=1
## 服务状态运行中字符串
active_status_str='active (running)'
echo "开始执行脚本 check_gateway.sh $VIP;时间:"
date
## 查看是否获取vip状态
function check_get_vip_status() {
  ## 通过ip add命令查看ip信息,搜索$VIP,统计行数,是否等于1
  if [ $(ip add | grep "$VIP" | wc -l) -eq 1 ]; then
    get_vip_status=1
  else
    get_vip_status=0
  fi
  return $get_vip_status
}
 
## 检查通信状态
function check_keepalived_status() {
  ## 检测$VIP 能否ping通$GATEWAY:使用$eth网络设备(-I $eth),发送数据包5(-c 5),源地址$VIP询问目的地[vip] $GATEWAY [网关地址 公用参考ip](-s $VIP $GATEWAY) 日志不保存 >/dev/null 2>&1
  /sbin/arping -I $eth -c 5 -s $VIP $GATEWAY >/dev/null 2>&1
  ## 判断上一步执行结果 等于0成功
  if [ $? = 0 ]; then
    keepalived_communication_status=1
  else
    keepalived_communication_status=0
  fi
  return $keepalived_communication_status
}
 
## 检查keepalived服务状态
function check_keepalived_service_status() {
  ## 通过systemctl status keepalived.service命令查看keepalived服务状态,搜索$active_status_str,统计行数,是否等于1
  if [ $(systemctl status keepalived.service | grep "$active_status_str" | wc -l) -eq 1 ]; then
    keepalived_service_status=1
  else
    keepalived_service_status=0
  fi
  return $keepalived_service_status
}
 
## 循环执行
## 判断$CHECK_TIME 不等于 0
while [ $CHECK_TIME -ne 0 ]; do
  ## 执行check_get_vip_status获取get_vip_status
  check_get_vip_status
  ## 未获取vip
  if [ $get_vip_status = 0 ]; then
    ## 修改CHECK_TIME值 结束循环
    CHECK_TIME=0
    ## 检查服务状态 执行check_keepalived_service_status获取keepalived_service_status
    if [ $keepalived_service_status = 0 ]; then
      echo "执行脚本 check_gateway.sh $VIP;启动keepalived服务"
      systemctl start keepalived.service
    fi
    echo "执行脚本 check_gateway.sh $VIP;执行结果:未获取vip,无需处理,脚本执行结束,时间:"
    date
    ## 正常运行程序并退出程序
    exit 0
  fi
  ## $CHECK_TIME  = $CHECK_TIME-1
  let "CHECK_TIME -= 1"
  ## 执行check_keepalived_status获取keepalived_communication_status
  check_keepalived_status
  ## 判断 $keepalived_communication_status = 1 通信成功
  if [ $keepalived_communication_status = 1 ]; then
    ## 修改CHECK_TIME值 结束循环
    CHECK_TIME=0
    ## 检查服务状态 执行check_keepalived_service_status获取keepalived_service_status
    check_keepalived_service_status
    if [ $keepalived_service_status = 0 ]; then
      echo "执行脚本 check_gateway.sh $VIP;启动keepalived服务"
      systemctl start keepalived.service
    fi
 
    echo "执行脚本 check_gateway.sh $VIP;GATEWAY=$GATEWAY,执行结果:通信正常,无需处理,脚本执行结束,时间:"
    date
    ## 正常运行程序并退出程序
    exit 0
  fi
  ## 通信失败&&连续3次
  if [ $keepalived_communication_status -eq 0 ] && [ $CHECK_TIME -eq 0 ]; then
    ## 关闭keepalived
    echo "执行脚本 check_gateway.sh $VIP;关闭keepalived服务"
    systemctl stop keepalived.service
    echo "执行脚本 check_gateway.sh $VIP;GATEWAY=$GATEWAY,执行结果:通信失败&&连续3次 关闭keepalived,脚本执行结束,时间:"
    date
    ## 非正常运行程序并退出程序
    exit 1
  fi
  sleep 3
done

集成配置NginxNginx大约 2 分钟
雪崩、穿透、击穿

1. 缓存雪崩

对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是缓存雪崩。

缓存雪崩的事前事中事后的解决方案如下:

  • 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
  • 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
  • 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

集成配置缓存Reids缓存Reids大约 3 分钟
Jenkins模板文件

Jenkinsfile声明模板

// Jenkinsfile声明模板
pipeline {
  // Agent: 表示整个流水线或特定阶段中的步骤和命令执行的位置
  // Agent any 在任何可用的代理上执行流水线
  // Agent none 表示该 Pipeline 脚本没有全局的 agent 配置。当顶层的 agent 配置为 none 时, 每个 stage 部分都需要包含它自己的 agent
  agent any
  // 全局变量,会在所有stage中生效
  environment {
    NAME= 'ZHANG'
    // 动态变量 returnStdout: 将命令的执行结果赋值给变量,比如下述的命令返回的是 clang,此时 CC 的值为“clang”。
    CC = """${sh(
         returnStdout: true,
         script: 'echo -n "clang"'   //如果使用shell命令的echo赋值变量最好加-n取消换行
         )}"""
    // 动态变量 returnStatus: 将命令的执行状态赋值给变量,比如下述命令的执行状态为 1,此时 EXIT_STATUS 的值为 1
    EXIT_STATUS = """${sh(
         returnStatus: true,
         script: 'exit 1'
         )}"""
    // 加密文本
    AWS_ACCESS_KEY_ID = credentials('txt1')
    AWS_SECRET_ACCESS_KEY = credentials('txt2')
  }
  // Options: Jenkins 流水线支持很多内置指令,比如 retry 可以对失败的步骤进行重复执行 n 次,可以根据不同的指令实现不同的效果。
  // buildDiscarder : 保留多少个流水线的构建记录
  // disableConcurrentBuilds : 禁止流水线并行执行,防止并行流水线同时访问共享资源导致流水线失败。
  // disableResume : 如果控制器重启,禁止流水线自动恢复。
  // newContainerPerStage : agent 为 docker 或 dockerfile 时,每个阶段将在同一个节点的新容器中运行,而不是所有的阶段都在同一个容器中运行。
  // quietPeriod : 流水线静默期,也就是触发流水线后等待一会在执行。
  // retry : 流水线失败后重试次数。
  // timeout : 设置流水线的超时时间,超过流水线时间,job 会自动终止。如果不加 unit 参数默认为 1 分。
  // timestamps : 为控制台输出时间戳。
  options {
    timeout(time: 1, unit: 'HOURS')                     // 超时时间1小时,如果不加unit参数默认为1分
    timestamps()                                        // 所有输出每行都会打印时间戳
    buildDiscarder(logRotator(numToKeepStr: '3'))       //保留三个历史构建版本
    quietPeriod(10)                                     // 注意手动触发的构建不生效
    retry(3)                                            // 流水线失败后重试次数
  }
  // Parameters: 提供了一个用户在触发流水线时应该提供的参数列表 只能定义在 pipeline 顶层。
  // 插件: imageTag | gitParameter
  parameters {
    string(name: 'DEPLOY_ENV', defaultValue: 'staging', description: '1')                   // 执行构建时需要手动配置字符串类型参数,之后赋值给变量
    text(name:  'DEPLOY_TEXT', defaultValue: 'One\nTwo\nThree\n', description: '2')         // 执行构建时需要提供文本参数,之后赋值给变量
    booleanParam(name: 'DEBUG_BUILD',  defaultValue: true, description: '3')                // 布尔型参数
    choice(name: 'CHOICES', choices: ['one', 'two', 'three'], description: '4')             // 选择形式列表参数
    password(name: 'PASSWORD', defaultValue: 'SECRET', description: 'A  secret password')   // 密码类型参数,会进行加密
    imageTag(name: 'DOCKER_IMAGE', description: '', image: 'kubernetes/kubectl', filter: '.*', defaultTag: '', registry: 'https://192.168.10.15', credentialId: 'harbor-account', tagOrder: 'NATURAL')   //获取镜像名称与tag
    gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE',  tagFilter: '*', type: 'PT_BRANCH')
  }
  // 定时构建 注意: H 的意思不是 HOURS 的意思,而是 Hash 的缩写。主要为了解决多个流水线在同一时间同时运行带来的系统负载压力。
  triggers {
    cron('H */4 * * 1-5')   // 周一到周五每隔四个小时执行一次
    cron('H/12 * * * *')    // 每隔12分钟执行一次
    cron('H * * * *')       // 每隔1小时执行一次
  }
  // 定义流水线
  stages {
    // 执行某阶段
    stage('Build') {
      steps {
        echo 'Build'
      }
    }
    stage('Stage For Build'){
      // label: 以节点标签形式选择某个具体的节点执行 Pipeline 命令
      agent { label 'role-master' }
      steps {
        sh """
           echo 'role-master'
           echo 'role-master'
        """
      }
    }
    stage('Stage For Build'){
      agent {
        // node: 和 label 配置类似,只不过是可以添加一些额外的配置,比如 customWorkspace(设置默认工作目录)
        node {
          label 'role-master'
          customWorkspace "/tmp/zhangzhuo/data"
        }
      }
      steps {
        sh "echo role-master > 1.txt"
      }
    }
    agent {
      // dockerfile: 使用从源码中包含的 Dockerfile 所构建的容器执行流水线或 stage
      dockerfile {
        filename 'Dockerfile.build'  //dockerfile文件名称
        dir 'build'                  //执行构建镜像的工作目录
        label 'role-master'          //执行的node节点,标签选择
        additionalBuildArgs '--build-arg version=1.0.2' //构建参数
      }
    }
    agent{
      // docker: 相当于 dockerfile,可以直接使用 docker 字段指定外部镜像即可,可以省去构建的时间。比如使用 maven 镜像进行打包,同时可以指定 args
      docker{
        image '192.168.10.15/kubernetes/alpine:latest'   //镜像地址
        label 'role-master' //执行的节点,标签选择
        args '-v /tmp:/tmp'      //启动镜像的参数
      }
    }
    // docker 的示例
    stage('Example Build') {
      agent { docker 'maven:3-alpine' }
      steps {
        echo 'Hello, Maven'
        sh 'mvn --version'
      }
    }
    stage('env1') {
      // 定义在stage中的变量只会在当前stage生效,其他的stage不会生效
      environment {
        HARBOR = 'https://192.168.10.15'
      }
      steps {
        sh "env"
      }
    }
    stage('env1') {
      options {                               // 定义在这里这对这个stage生效
        timeout(time: 2, unit: 'SECONDS')     // 超时时间2秒
        timestamps()                          // 所有输出每行都会打印时间戳
        retry(3)                              // 流水线失败后重试次数
      }
      steps {
        sh "env && sleep 2"
      }
    }
    // Parameters 测试
    stage('git') {
      steps {
        // 使用gitParameter,必须有这个
        git branch: "$BRANCH", credentialsId: 'gitlab-key', url: 'git@192.168.10.14:root/env.git'
      }
    }
    // Input 字段可以实现在流水线中进行交互式操作
    stage('Example') {
      input {
        message "还继续么?"
        ok "继续"
        submitter "alice,bob"     // 可选,允许提交 input 操作的用户或组的名称,如果为空,任何登录用户均可提交 input;
        parameters {
          string(name: 'PERSON', defaultValue: 'Mr Jenkins', description: 'Who should I say hello to?')
        }
      }
      steps {
        echo "Hello, ${PERSON}, nice to meet you."
      }
    }
    stage('Example Deploy') {
      when {
        // beforeAgent: 如果 beforeAgent 为 true,则会先评估 when 条件。在 when 条件为 true 时,才会进入该 stage
        // beforeInput: 如果 beforeInput 为 true,则会先评估 when 条件。在 when 条件为 true 时,才会进入到 input 阶段;
        // beforeOptions: 如果 beforeInput 为 true,则会先评估 when 条件。在 when 条件为 true 时,才会进入到 options 阶段;
        // beforeOptions 优先级大于 beforeInput 大于 beforeAgent
        beforeAgent true
        branch 'main'         // 多分支流水线,分支为main才会执行。
        expression { BRANCH_NAME ==~ /(main|master)/ }  // 并且 满足正则表达式
        anyOf {                                         // 并且 DEPLOY_TO 为 master 或 main
          environment name: 'DEPLOY_TO', value: 'main'
          environment name: 'DEPLOY_TO', value: 'master'
        }
      }
      steps {
        echo 'Deploying'
      }
    }
    // Parallel: 很方便的实现并发构建
    stage('Parallel Stage') {
      failFast true         // 表示其中只要有一个分支构建执行失败,就直接推出不等待其他分支构建
      parallel {
        stage('Branch A') {
          steps {
            echo "On Branch A"
          }
        }
        stage('Branch B') {
          steps {
            echo "On Branch B"
          }
        }
        stage('Branch C') {
          stages {
            stage('Nested 1') {
              steps {
                echo "In stage Nested 1 within Branch C"
              }
            }
            stage('Nested 2') {
              steps {
               echo "In stage Nested 2 within Branch C"
              }
            }
          }
        }
      }
    }
    // 静态变量
    // Jenkins 有许多内置变量可以直接在 Jenkinsfile 中使用,可以通过 JENKINS_URL/pipeline/syntax/globals#env 获取完整列表。目前比较常用的环境变量如下
    // BUILD_ID: 当前构建的 ID,与 Jenkins 版本 1.597+中的 BUILD_NUMBER 完全相同
    // BUILD_NUMBER: 当前构建的 ID,和 BUILD_ID 一致
    // BUILD_TAG: 用来标识构建的版本号,格式为: jenkins-{BUILD_NUMBER}, 可以对产物进行命名,比如生产的 jar 包名字、镜像的 TAG 等;
    // BUILD_URL: 本次构建的完整 URL,比如: http://buildserver/jenkins/job/MyJobName/17/%EF%BC%9B
    // JOB_NAME: 本次构建的项目名称
    // NODE_NAME: 当前构建节点的名称;
    // JENKINS_URL: Jenkins 完整的 URL,需要在 SystemConfiguration 设置;
    // WORKSPACE: 执行构建的工作目录。
    stage('STATIC_ENV') {
      steps {
        echo "$env.BUILD_ID"
        echo "$env.BUILD_NUMBER"
        echo "$env.BUILD_TAG"
      }
    }
    //Post: 一般用于流水线结束后的进一步处理 | 一般情况下 post 部分放在流水线的底部
    post {
      // always: 无论 Pipeline 或 stage 的完成状态如何,都允许运行该 post 中定义的指令;
      // changed: 只有当前 Pipeline 或 stage 的完成状态与它之前的运行不同时,才允许在该 post 部分运行该步骤;
      // fixed: 当本次 Pipeline 或 stage 成功,且上一次构建是失败或不稳定时,允许运行该 post 中定义的指令;
      // regression: 当本次 Pipeline 或 stage 的状态为失败、不稳定或终止,且上一次构建的 状态为成功时,允许运行该 post 中定义的指令;
      // failure: 只有当前 Pipeline 或 stage 的完成状态为失败(failure),才允许在 post 部分运行该步骤,通常这时在 Web 界面中显示为红色
      // success: 当前状态为成功(success),执行 post 步骤,通常在 Web 界面中显示为蓝色 或绿色
      // unstable: 当前状态为不稳定(unstable),执行 post 步骤,通常由于测试失败或代码 违规等造成,在 Web 界面中显示为黄色
      // aborted: 当前状态为终止(aborted),执行该 post 步骤,通常由于流水线被手动终止触发,这时在 Web 界面中显示为灰色;
      // unsuccessful: 当前状态不是 success 时,执行该 post 步骤;
      // cleanup: 无论 pipeline 或 stage 的完成状态如何,都允许运行该 post 中定义的指令。和 always 的区别在于,cleanup 会在其它执行之后执行。
      always {
        echo 'I will always say Hello again!'
      }
      failure {
        echo 'I will failure say Hello again!'
      }
    }
  }
}


集成配置集成构建集成构建大约 8 分钟
MyBatis中xml写法
[[toc]] concat模糊查询 choose (when, otherwise)标签 selectKey 标签 if标签 foreach

集成配置MyBatisMyBatis大约 2 分钟
文件存储方案

主要考虑

  1. 分布式系统文件存储。
  2. 文件分块的能力。断电续传、妙传等。
  3. 可扩展、
  4. 平滑迁移(可以不影响业务的情况下可以平滑迁移)
  5. 隔离性,产品应用的隔离性。
  6. 容错和灾备的能力。

FastDFS

FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。

分布式,没有单点依赖,用C编写,性能较好


集成配置文件系统文件系统大约 2 分钟
自动封禁IP

nginx统计

## AWK统计access.log,记录每分钟访问超过60次的ip
awk '{print $1}' access.log | sort | uniq -cd | awk '{if($1>60)print $0}'

# 1. awk '{print $1}' access.log   取出access.log的第一列即为ip。
# 2. sort | uniq -cd  去重和排序
# 3. awk '{if($1>60)print $0}' 判断重复的数量是否超过60个,超过60个就展示出来

集成配置Nginxnginx大约 1 分钟
nginx核心配置

代理服务器:

  • Nginx 的特点:高并发 / 低消耗 / 热部署 / 高可用 / 高扩展
  • 正向代理:隐藏 / 翻墙 / 提速 / 缓存 / 授权
  • 反向代理:保护隐藏 / 分布式路由 / 负载均衡 / 动静分离 / 数据缓存

集成配置NginxNginx大约 16 分钟
2
3
4
5