添加 incremental_backup.sh
This commit is contained in:
564
incremental_backup.sh
Normal file
564
incremental_backup.sh
Normal file
@@ -0,0 +1,564 @@
|
||||
#!/bin/bash
|
||||
# ============================================================================
|
||||
# MySQL 8.0.24 增量备份脚本
|
||||
# ============================================================================
|
||||
# 作者: AI Assistant
|
||||
# 版本: 1.0.0
|
||||
# 说明: 基于 binlog 的增量备份
|
||||
#
|
||||
# 功能特点:
|
||||
# - 基于 binlog 实现增量备份
|
||||
# - 从上次全量/增量备份点开始备份
|
||||
# - 支持压缩
|
||||
# - 自动清理过期备份
|
||||
#
|
||||
# 工作原理:
|
||||
# 1. 读取上次备份记录的 binlog 位置
|
||||
# 2. 复制从该位置到当前位置的 binlog 文件
|
||||
# 3. 记录新的 binlog 位置用于下次增量备份
|
||||
#
|
||||
# 使用方法:
|
||||
# ./incremental_backup.sh [选项]
|
||||
#
|
||||
# 选项:
|
||||
# -f, --full-backup <path> 指定全量备份目录 (默认使用最新的全量备份)
|
||||
# -c, --compress 启用压缩 (默认: 是)
|
||||
# -n, --no-compress 禁用压缩
|
||||
# -h, --help 显示帮助信息
|
||||
#
|
||||
# 前提条件:
|
||||
# - 必须先执行过全量备份
|
||||
# - MySQL 必须启用 binlog
|
||||
# - MySQL 用户需要 REPLICATION SLAVE, REPLICATION CLIENT 权限
|
||||
# ============================================================================
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 脚本路径和配置加载
|
||||
# ----------------------------------------------------------------------------
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# 加载配置文件
|
||||
if [[ ! -f "${SCRIPT_DIR}/config.sh" ]]; then
|
||||
echo "[ERROR] 配置文件不存在: ${SCRIPT_DIR}/config.sh"
|
||||
exit 1
|
||||
fi
|
||||
source "${SCRIPT_DIR}/config.sh"
|
||||
|
||||
# 加载公共函数库
|
||||
if [[ ! -f "${SCRIPT_DIR}/lib/common.sh" ]]; then
|
||||
echo "[ERROR] 公共函数库不存在: ${SCRIPT_DIR}/lib/common.sh"
|
||||
exit 1
|
||||
fi
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 全局变量
|
||||
# ----------------------------------------------------------------------------
|
||||
BACKUP_TIMESTAMP=$(date +"${TIMESTAMP_FORMAT}")
|
||||
BACKUP_NAME="${BACKUP_PREFIX}_incr_${BACKUP_TIMESTAMP}"
|
||||
BACKUP_DIR="${INCREMENTAL_BACKUP_DIR}/${BACKUP_NAME}"
|
||||
LOCK_FILE="${BACKUP_ROOT_DIR}/.incremental_backup.lock"
|
||||
LOG_FILE="${LOG_DIR}/incremental_backup_${BACKUP_TIMESTAMP}.log"
|
||||
ENABLE_COMPRESS=true
|
||||
SPECIFIED_FULL_BACKUP=""
|
||||
BASE_BACKUP_DIR="" # 基准备份目录 (全量或上次增量)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 显示帮助信息
|
||||
# ----------------------------------------------------------------------------
|
||||
show_help() {
|
||||
cat << EOF
|
||||
MySQL 8.0.24 增量备份脚本
|
||||
|
||||
使用方法:
|
||||
$(basename "$0") [选项]
|
||||
|
||||
选项:
|
||||
-f, --full-backup <path> 指定全量备份基准目录
|
||||
-b, --base-backup <path> 指定增量备份基准目录 (用于增量链)
|
||||
-c, --compress 启用压缩 (默认: 是)
|
||||
-n, --no-compress 禁用压缩
|
||||
-h, --help 显示此帮助信息
|
||||
|
||||
示例:
|
||||
$(basename "$0") # 基于最新全量备份
|
||||
$(basename "$0") -f /path/to/full_backup # 指定全量备份
|
||||
$(basename "$0") -b /path/to/last_incr_backup # 基于上次增量备份
|
||||
|
||||
说明:
|
||||
增量备份基于 MySQL binlog 实现。脚本会从基准备份记录的 binlog
|
||||
位置开始,复制到当前 binlog 位置的所有日志。
|
||||
|
||||
前提条件:
|
||||
- 必须先执行过全量备份
|
||||
- MySQL 必须启用 binlog (log_bin=ON)
|
||||
- MySQL 用户需要 REPLICATION SLAVE, REPLICATION CLIENT 权限
|
||||
EOF
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 解析命令行参数
|
||||
# ----------------------------------------------------------------------------
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-f|--full-backup)
|
||||
shift
|
||||
if [[ -z "$1" ]]; then
|
||||
die "选项 -f/--full-backup 需要一个参数"
|
||||
fi
|
||||
SPECIFIED_FULL_BACKUP="$1"
|
||||
shift
|
||||
;;
|
||||
-b|--base-backup)
|
||||
shift
|
||||
if [[ -z "$1" ]]; then
|
||||
die "选项 -b/--base-backup 需要一个参数"
|
||||
fi
|
||||
BASE_BACKUP_DIR="$1"
|
||||
shift
|
||||
;;
|
||||
-c|--compress)
|
||||
ENABLE_COMPRESS=true
|
||||
shift
|
||||
;;
|
||||
-n|--no-compress)
|
||||
ENABLE_COMPRESS=false
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "未知选项: $1\n使用 --help 查看帮助"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 检查 binlog 是否启用
|
||||
# ----------------------------------------------------------------------------
|
||||
check_binlog_enabled() {
|
||||
log_info "检查 binlog 状态..."
|
||||
|
||||
local binlog_status=$(execute_mysql "SHOW VARIABLES LIKE 'log_bin';")
|
||||
|
||||
if ! echo "$binlog_status" | grep -q "ON"; then
|
||||
die "MySQL binlog 未启用。请在 my.cnf 中设置 log_bin=ON"
|
||||
fi
|
||||
|
||||
log_info "Binlog 已启用"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 获取 binlog 目录
|
||||
# ----------------------------------------------------------------------------
|
||||
get_binlog_dir() {
|
||||
local binlog_basename=$(execute_mysql "SHOW VARIABLES LIKE 'log_bin_basename';" | awk '{print $2}')
|
||||
|
||||
if [[ -z "$binlog_basename" ]]; then
|
||||
# 尝试使用数据目录
|
||||
echo "$MYSQL_DATA_DIR"
|
||||
else
|
||||
dirname "$binlog_basename"
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 查找基准备份
|
||||
# ----------------------------------------------------------------------------
|
||||
find_base_backup() {
|
||||
if [[ -n "$BASE_BACKUP_DIR" ]]; then
|
||||
# 使用指定的基准备份
|
||||
if [[ ! -d "$BASE_BACKUP_DIR" ]]; then
|
||||
die "指定的基准备份目录不存在: $BASE_BACKUP_DIR"
|
||||
fi
|
||||
echo "$BASE_BACKUP_DIR"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ -n "$SPECIFIED_FULL_BACKUP" ]]; then
|
||||
# 使用指定的全量备份
|
||||
if [[ ! -d "$SPECIFIED_FULL_BACKUP" ]]; then
|
||||
die "指定的全量备份目录不存在: $SPECIFIED_FULL_BACKUP"
|
||||
fi
|
||||
echo "$SPECIFIED_FULL_BACKUP"
|
||||
return
|
||||
fi
|
||||
|
||||
# 首先查找最新的增量备份
|
||||
local latest_incr=$(find "$INCREMENTAL_BACKUP_DIR" -maxdepth 1 -type d -name "${BACKUP_PREFIX}_incr_*" 2>/dev/null | sort -r | head -n1)
|
||||
|
||||
if [[ -n "$latest_incr" && -f "${latest_incr}/binlog_position.txt" ]]; then
|
||||
log_info "找到最新的增量备份作为基准: $latest_incr"
|
||||
echo "$latest_incr"
|
||||
return
|
||||
fi
|
||||
|
||||
# 查找最新的全量备份
|
||||
local latest_full=$(find "$FULL_BACKUP_DIR" -maxdepth 1 -type d -name "${BACKUP_PREFIX}_full_*" 2>/dev/null | sort -r | head -n1)
|
||||
|
||||
if [[ -z "$latest_full" ]]; then
|
||||
die "没有找到可用的全量备份。请先执行全量备份"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${latest_full}/binlog_position.txt" ]]; then
|
||||
die "全量备份缺少 binlog 位置信息: $latest_full"
|
||||
fi
|
||||
|
||||
log_info "使用全量备份作为基准: $latest_full"
|
||||
echo "$latest_full"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 读取基准 binlog 位置
|
||||
# ----------------------------------------------------------------------------
|
||||
read_base_binlog_position() {
|
||||
local base_dir="$1"
|
||||
local position_file="${base_dir}/binlog_position.txt"
|
||||
|
||||
if [[ ! -f "$position_file" ]]; then
|
||||
die "找不到 binlog 位置文件: $position_file"
|
||||
fi
|
||||
|
||||
source "$position_file"
|
||||
|
||||
if [[ -z "$BINLOG_FILE" || -z "$BINLOG_POSITION" ]]; then
|
||||
die "binlog 位置信息不完整"
|
||||
fi
|
||||
|
||||
echo "${BINLOG_FILE}:${BINLOG_POSITION}"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 获取 binlog 文件列表
|
||||
# ----------------------------------------------------------------------------
|
||||
get_binlog_files_to_backup() {
|
||||
local start_file="$1"
|
||||
local start_pos="$2"
|
||||
local binlog_dir="$3"
|
||||
|
||||
# 获取当前 binlog 位置
|
||||
local current_pos=$(get_binlog_position)
|
||||
local current_file=$(echo "$current_pos" | cut -d: -f1)
|
||||
local current_position=$(echo "$current_pos" | cut -d: -f2)
|
||||
|
||||
log_info "起始 binlog: $start_file:$start_pos"
|
||||
log_info "当前 binlog: $current_file:$current_position"
|
||||
|
||||
# 获取 binlog 索引文件
|
||||
local binlog_index=$(execute_mysql "SHOW VARIABLES LIKE 'log_bin_index';" | awk '{print $2}')
|
||||
|
||||
# 获取需要备份的文件列表
|
||||
local in_range=false
|
||||
local files_to_backup=()
|
||||
|
||||
# 使用 SHOW BINARY LOGS 获取文件列表
|
||||
local log_list=$(execute_mysql "SHOW BINARY LOGS;" | awk '{print $1}')
|
||||
|
||||
while IFS= read -r log_file; do
|
||||
if [[ "$log_file" == "$start_file" ]]; then
|
||||
in_range=true
|
||||
fi
|
||||
|
||||
if [[ "$in_range" == true ]]; then
|
||||
files_to_backup+=("$log_file")
|
||||
fi
|
||||
|
||||
if [[ "$log_file" == "$current_file" ]]; then
|
||||
break
|
||||
fi
|
||||
done <<< "$log_list"
|
||||
|
||||
echo "${files_to_backup[@]}"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 初始化备份环境
|
||||
# ----------------------------------------------------------------------------
|
||||
init_backup() {
|
||||
log_info "============================================================"
|
||||
log_info "MySQL 增量备份开始"
|
||||
log_info "============================================================"
|
||||
log_info "备份时间: $BACKUP_TIMESTAMP"
|
||||
log_info "备份目录: $BACKUP_DIR"
|
||||
log_info "日志文件: $LOG_FILE"
|
||||
log_info "============================================================"
|
||||
|
||||
# 设置错误处理
|
||||
setup_error_trap
|
||||
|
||||
# 检查必要命令
|
||||
check_commands "$MYSQL_PATH" "$MYSQLBINLOG_PATH" "gzip"
|
||||
|
||||
# 检查 MySQL 连接
|
||||
check_mysql_connection
|
||||
|
||||
# 检查 binlog 是否启用
|
||||
check_binlog_enabled
|
||||
|
||||
# 创建必要目录
|
||||
ensure_dir "$BACKUP_DIR"
|
||||
ensure_dir "$LOG_DIR"
|
||||
|
||||
# 检查磁盘空间
|
||||
check_disk_space "$BACKUP_DIR" 2048
|
||||
|
||||
# 获取锁
|
||||
acquire_lock "$LOCK_FILE" "增量备份"
|
||||
|
||||
# 设置清理 trap
|
||||
trap cleanup EXIT
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 清理函数
|
||||
# ----------------------------------------------------------------------------
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
|
||||
# 释放锁
|
||||
release_lock "$LOCK_FILE"
|
||||
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
log_error "备份过程中发生错误,退出码: $exit_code"
|
||||
|
||||
# 清理不完整的备份
|
||||
if [[ -d "$BACKUP_DIR" ]]; then
|
||||
log_warn "清理不完整的备份目录: $BACKUP_DIR"
|
||||
rm -rf "$BACKUP_DIR"
|
||||
fi
|
||||
|
||||
send_notification "[失败] MySQL 增量备份" "备份失败,请检查日志: $LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 执行 binlog 备份
|
||||
# ----------------------------------------------------------------------------
|
||||
backup_binlogs() {
|
||||
local base_dir="$1"
|
||||
local binlog_dir=$(get_binlog_dir)
|
||||
|
||||
log_info "Binlog 目录: $binlog_dir"
|
||||
|
||||
# 读取基准位置
|
||||
local base_pos=$(read_base_binlog_position "$base_dir")
|
||||
local start_file=$(echo "$base_pos" | cut -d: -f1)
|
||||
local start_position=$(echo "$base_pos" | cut -d: -f2)
|
||||
|
||||
# 获取当前位置
|
||||
local current_pos=$(get_binlog_position)
|
||||
local end_file=$(echo "$current_pos" | cut -d: -f1)
|
||||
local end_position=$(echo "$current_pos" | cut -d: -f2)
|
||||
|
||||
# 检查是否有新的数据需要备份
|
||||
if [[ "$start_file" == "$end_file" && "$start_position" == "$end_position" ]]; then
|
||||
log_warn "没有新的 binlog 数据需要备份"
|
||||
log_warn "起始位置和当前位置相同: $start_file:$start_position"
|
||||
# 创建空的增量备份记录
|
||||
save_binlog_position "$end_file" "$end_position"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 获取需要备份的 binlog 文件列表
|
||||
local files=$(get_binlog_files_to_backup "$start_file" "$start_position" "$binlog_dir")
|
||||
|
||||
if [[ -z "$files" ]]; then
|
||||
log_warn "没有需要备份的 binlog 文件"
|
||||
save_binlog_position "$end_file" "$end_position"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "需要备份的 binlog 文件: $files"
|
||||
|
||||
local backup_count=0
|
||||
local is_first_file=true
|
||||
|
||||
for binlog_file in $files; do
|
||||
log_info "备份 binlog: $binlog_file"
|
||||
|
||||
local binlog_path="${binlog_dir}/${binlog_file}"
|
||||
local output_file="${BACKUP_DIR}/${binlog_file}"
|
||||
|
||||
if [[ ! -f "$binlog_path" ]]; then
|
||||
# binlog 可能已被清理,尝试从 MySQL 读取
|
||||
log_warn "本地 binlog 文件不存在,尝试使用 mysqlbinlog 远程读取"
|
||||
|
||||
local mysqlbinlog_args="-h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}"
|
||||
if [[ -n "$MYSQL_PASSWORD" ]]; then
|
||||
mysqlbinlog_args="$mysqlbinlog_args -p${MYSQL_PASSWORD}"
|
||||
fi
|
||||
|
||||
# 对于第一个文件,从指定位置开始
|
||||
if [[ "$is_first_file" == true && "$binlog_file" == "$start_file" ]]; then
|
||||
mysqlbinlog_args="$mysqlbinlog_args --start-position=$start_position"
|
||||
fi
|
||||
|
||||
# 对于最后一个文件,在指定位置停止
|
||||
if [[ "$binlog_file" == "$end_file" ]]; then
|
||||
mysqlbinlog_args="$mysqlbinlog_args --stop-position=$end_position"
|
||||
fi
|
||||
|
||||
mysqlbinlog_args="$mysqlbinlog_args --read-from-remote-server"
|
||||
|
||||
if ! $MYSQLBINLOG_PATH $mysqlbinlog_args "$binlog_file" > "$output_file" 2>> "$LOG_FILE"; then
|
||||
die "备份 binlog 失败: $binlog_file"
|
||||
fi
|
||||
else
|
||||
# 使用本地 binlog 文件
|
||||
local mysqlbinlog_args=""
|
||||
|
||||
# 对于第一个文件,从指定位置开始
|
||||
if [[ "$is_first_file" == true && "$binlog_file" == "$start_file" ]]; then
|
||||
mysqlbinlog_args="--start-position=$start_position"
|
||||
fi
|
||||
|
||||
# 对于最后一个文件,在指定位置停止
|
||||
if [[ "$binlog_file" == "$end_file" ]]; then
|
||||
mysqlbinlog_args="$mysqlbinlog_args --stop-position=$end_position"
|
||||
fi
|
||||
|
||||
if ! $MYSQLBINLOG_PATH $mysqlbinlog_args "$binlog_path" > "$output_file" 2>> "$LOG_FILE"; then
|
||||
die "备份 binlog 失败: $binlog_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 压缩
|
||||
if [[ "$ENABLE_COMPRESS" == "true" ]]; then
|
||||
gzip -f "$output_file" || die "压缩失败: $output_file"
|
||||
output_file="${output_file}.gz"
|
||||
fi
|
||||
|
||||
((backup_count++))
|
||||
is_first_file=false
|
||||
|
||||
log_info "备份完成: $(get_file_size $output_file)"
|
||||
done
|
||||
|
||||
log_info "共备份 $backup_count 个 binlog 文件"
|
||||
|
||||
# 记录新的 binlog 位置
|
||||
save_binlog_position "$end_file" "$end_position"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 保存 binlog 位置
|
||||
# ----------------------------------------------------------------------------
|
||||
save_binlog_position() {
|
||||
local binlog_file="$1"
|
||||
local binlog_pos="$2"
|
||||
local position_file="${BACKUP_DIR}/binlog_position.txt"
|
||||
|
||||
log_info "记录 binlog 位置: $binlog_file:$binlog_pos"
|
||||
|
||||
cat > "$position_file" << EOF
|
||||
# MySQL Binlog Position
|
||||
# 备份时间: $BACKUP_TIMESTAMP
|
||||
# 用于下次增量备份的起始位置
|
||||
|
||||
BINLOG_FILE=$binlog_file
|
||||
BINLOG_POSITION=$binlog_pos
|
||||
EOF
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 保存备份元数据
|
||||
# ----------------------------------------------------------------------------
|
||||
save_metadata() {
|
||||
local base_dir="$1"
|
||||
local metadata_file="${BACKUP_DIR}/metadata.txt"
|
||||
|
||||
log_info "保存备份元数据"
|
||||
|
||||
local end_time=$(date +%s)
|
||||
local duration=$(calculate_duration "$START_TIME" "$end_time")
|
||||
local backup_size=$(get_file_size "$BACKUP_DIR")
|
||||
|
||||
cat > "$metadata_file" << EOF
|
||||
# MySQL 增量备份元数据
|
||||
# ========================================
|
||||
|
||||
备份类型: 增量备份 (Incremental Backup)
|
||||
备份时间: $BACKUP_TIMESTAMP
|
||||
备份目录: $BACKUP_NAME
|
||||
备份大小: $backup_size
|
||||
执行耗时: $duration
|
||||
基准备份: $base_dir
|
||||
|
||||
MySQL 服务器: ${MYSQL_HOST}:${MYSQL_PORT}
|
||||
MySQL 用户: $MYSQL_USER
|
||||
|
||||
备份选项:
|
||||
- 压缩: $ENABLE_COMPRESS
|
||||
- 压缩工具: $COMPRESS_TOOL
|
||||
|
||||
备份状态: 成功
|
||||
EOF
|
||||
|
||||
# 记录基准备份路径 (用于恢复时的依赖链)
|
||||
echo "$base_dir" > "${BACKUP_DIR}/base_backup_path.txt"
|
||||
|
||||
log_info "元数据已保存"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 清理过期备份
|
||||
# ----------------------------------------------------------------------------
|
||||
cleanup_expired_backups() {
|
||||
log_info "检查过期备份..."
|
||||
cleanup_old_backups "$INCREMENTAL_BACKUP_DIR" "$INCREMENTAL_BACKUP_RETENTION_DAYS"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# 主函数
|
||||
# ----------------------------------------------------------------------------
|
||||
main() {
|
||||
# 记录开始时间
|
||||
START_TIME=$(date +%s)
|
||||
|
||||
# 解析参数
|
||||
parse_args "$@"
|
||||
|
||||
# 初始化
|
||||
init_backup
|
||||
|
||||
# 查找基准备份
|
||||
local base_backup=$(find_base_backup)
|
||||
log_info "基准备份: $base_backup"
|
||||
|
||||
# 执行 binlog 备份
|
||||
backup_binlogs "$base_backup"
|
||||
|
||||
# 保存元数据
|
||||
save_metadata "$base_backup"
|
||||
|
||||
# 清理过期备份
|
||||
cleanup_expired_backups
|
||||
|
||||
# 计算总耗时
|
||||
local end_time=$(date +%s)
|
||||
local duration=$(calculate_duration "$START_TIME" "$end_time")
|
||||
local backup_size=$(get_file_size "$BACKUP_DIR")
|
||||
|
||||
log_info "============================================================"
|
||||
log_info "MySQL 增量备份完成"
|
||||
log_info "============================================================"
|
||||
log_info "备份目录: $BACKUP_DIR"
|
||||
log_info "备份大小: $backup_size"
|
||||
log_info "执行耗时: $duration"
|
||||
log_info "基准备份: $base_backup"
|
||||
log_info "============================================================"
|
||||
|
||||
# 发送成功通知
|
||||
send_notification "[成功] MySQL 增量备份" "备份完成\n目录: $BACKUP_DIR\n大小: $backup_size\n耗时: $duration"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user