JAVABK

认清明天的去向,不忘昨日的来处

0%

什么是ThreadLocal

全称thread local variable(线程局部变量)功用非常简单,使用场合主要解决多线程中数据因并发产生不一致问题。

ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

总结起来就是:同个线程共享数据

注意:ThreadLocal不能使用原子类型,只能使用Object类型

核心应用场景

ThreadLocal 用作每个线程内需要独立保存信息,方便同个线程的其他方法获取该信息的场景。

每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,比如用户登录令牌解密后的信息传递(还有用户权限信息、从用户系统获取到的用户名、用户ID)

1
2
3
4
5
6
7
8
9
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();


LoginUser loginUser = new LoginUser();
loginUser.setId(id);
loginUser.setName(name);
loginUser.setMail(mail);
loginUser.setHeadImg(headImg);
threadLocal.set(loginUser);

ThreadLocal底层源码和原理

  • ThreadLocal中的一个内部类ThreadLocalMap,这个类没有实现map接口,就是一个普通的Java类,但是实现的类似map的功能

  • 每个数据用Entry保存,其中的Entry继承与WeakReference,用一个键值对存储,键为ThreadLocal的引用。

  • 每个线程持有一个ThreadLocalMap对象,每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals,则直接使用已经存在的对象。

ThreadLocal常见核心面试题

P6面试题:ThreadLocal和Synchronized的区别

  • 都是为了解决多线程中相同变量的访问冲突问题
  • Synchronized是通过线程等待,牺牲时间来解决访问冲突
  • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,
  • 对比Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值

P6面试题:为啥什么ThreadLocal的键是弱引用,如果是强引用有什么问题?

Java中除了基础的数据类型以外,其它的都为引用类型。
而Java根据其生命周期的长短将引用类型又分为强引用 、 软引用 、 弱引用 、 虚引用
正常情况下我们平时基本上我们只用到强引用类型,而其他的引用类型我们也就在面试中,或者平日阅读类库或其他框架源码的时候才能见到

1、强引用 new了一个对象就是强引用 Object obj = new Object();

2、软引用的生命周期比强引用短一些,通过SoftReference类实现,当内存空间足够,垃圾回收器就不会回收它; 当JVM认为内存空间不足时,就会去试图回收软引用指向的对象,也就是说在JVM抛出OutOfMemoryError之前,会去清理软引用对象
主要用来描述一些【有用但并不是必需】的对象
使用场景:适合用来实现缓存,内存空间充足的时候将数据缓存在内存中,如果空间不足了就将其回收掉

3、弱引用是通过WeakReference类实现的,它的生命周期比软引用还要短,在GC的时候,不管内存空间足不足都会回收这个对象
使用场景:一个对象只是偶尔使用,希望在使用时能随时获取,但也不想影响对该对象的垃圾收集,则可以考虑使用弱引用来指向该对象。

ThreadLocal为什么是WeakReference呢?

如果是强引用,即使把ThreadLocal设置为null,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏

如果是弱引用
引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。

仓库,镜像,容器的关系

Docker包括三个基本概念:

  • 镜像(Image):Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
  • 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
  • 仓库(Repository):仓库(Repository)类似Git的远程仓库,集中存放镜像文件。

Docker命令概览

  • Docker帮助命令—docker 命令 --help
  • Docker环境信息 — docker [info|version]
  • 容器生命周期管理 — docker [create|exec|run|start|stop|restart|kill|rm|pause|unpause]
  • 容器操作管理 — docker [ps|inspect|top|attach|wait|export|port|rename|stat]
  • 容器rootfs命令 — docker [commit|cp|diff]
  • 镜像仓库 — docker [login|pull|push|search]
  • 本地镜像管理 — docker [build|images|rmi|tag|save|import|load]
  • 容器资源管理 — docker [volume|network]
  • 系统日志信息 — docker [events|history|logs]

常用的命令

docker exec

1
2
3
4
5
6
7
8
-d :分离模式: 在后台运行

-i :即使没有附加也保持STDIN 打开

-t :分配一个伪终端

#实例
docker exec -it nginx /bin/bash

docker run

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
-i, --interactive=false   打开STDIN,用于控制台交互,以交互模式运行容器,通常与 -t 同时使用
-t, --tty=false 分配tty设备,该可以支持终端登录,默认为false,通常与 -i 同时使用
-d, --detach=false 指定容器运行于前台还是后台,默认为false,并返回容器ID,也即启动守护式容器
-u, --user="" 指定容器的用户
-a, --attach=[] 登录容器(必须是以docker run -d启动的容器)
-w, --workdir="" 指定容器的工作目录
-c, --cpu-shares=0 设置容器CPU权重,在CPU共享场景使用
-e, --env=[] 指定环境变量,容器中可以使用该环境变量
-m, --memory="" 指定容器的内存上限
-P, --publish-all=false 指定容器暴露的端口,随机端口映射,容器内部端口随机映射到主机的端口
-p, --publish=[] 指定容器暴露的端口,指定端口映射,格式为:主机(宿主)端口:容器端口
-h, --hostname="" 指定容器的主机名
-v, --volume=[] 给容器挂载存储卷,挂载到容器的某个目录 顺序:主机:容器
--volumes-from=[] 给容器挂载其他容器上的卷,挂载到容器的某个目录
--cap-add=[] 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[] 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile="" 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="" 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[] 添加主机设备给容器,相当于设备直通
--dns=[] 指定容器的dns服务器
--dns-search=[] 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
--entrypoint="" 覆盖image的入口点
--env-file=[] 指定环境变量文件,文件格式为每行一个环境变量
--expose=[] 指定容器暴露的端口,即修改镜像的暴露端口
--link=[] 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[] 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
--name="" 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge" 容器网络设置:
bridge 使用docker daemon指定的网桥
host //容器使用主机的网络
container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
none 容器使用自己的网络(类似--net=bridge),但是不进行配置
--privileged=false 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="no" 指定容器停止后的重启策略:
no:容器退出时不重启
on-failure:容器故障退出(返回值非零)时重启
always:容器退出时总是重启
--rm=false 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
--sig-proxy=true 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理

Docker安装

前提说明

从 2017 年 3 月开始 docker 在原来的基础上分为两个分支版本: Docker CE 和 Docker EE:Docker CE 即社区免费版;Docker EE 即企业版,强调安全,但需付费使用;按照官网上Docker Engine - Community包现在就是叫做Docker CE。这里将展示在CentOS上安装Docker。

CentOS 版本要求

  • Centos Docker安装

    • Centos7 (64bit)
  • 前提条件

    • Centos7 系统为64位,内核版本为3.10以上
    • 启用centos-extras
    • 推荐使用overlay2存储驱动
  • 查看自己的内核版本

    1
    cat /etc/redhat-release

Docker的基本组成

  • 镜像

    • 就是一个只读的模板,java中的Class。镜像可以用来创建Docker容器,一个镜像可以创建很多的容器
  • 容器

    • 镜像的实例,java中的instance

    • Docker利用容器独立运行的一个或者一组应用
      容器可以被启动、开始、停止、删除。
      每一个容器都是相互隔离的、保证安全的平台

    • 可以把容器看作是简易版的Linux环境(包括root用户权限、进程空间、用户控件和网络空间)和运行在其中的应用程序

    • 容器的定义和镜像几乎是一样的,也是一堆层的统一视角,唯一却别在与容器的最上层是可读可写的

  • 仓库

    • 仓库是集中存放镜像文件的场所

    • 仓库和仓库注册服务器是有区别的

      • 仓库注册服务器上往往存放着多个仓库,每个仓库又包含了多个镜像,每个镜像有不同的标签(tag)
    • 仓库分为公开仓库和私有仓库两种形式

    • 最大的公开仓库是Docker Hub (https://hub.docker.com),它存放了数量庞大的镜像供用户下载
      国内的公开仓库包括阿里云、网易云等

  • 小结

    • Docker本身是一个容器运行载体或者称之为管理引擎
      我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个环境就称之为image镜像文件。只有通过这个镜像文件才能生成Docker容器,image文件可以看作是容器的模板。Docker根据image文件生成容器的实例。同一个image文件,可以生成多个同时运行的容器实例
    • imags文件生成的容器实例,本身也是一个文件,称之为镜像文件
    • 一个容器运行一种服务,当我们需要的时候,就可以通过docker客户端创建一个对应的运行实例,也就是容器
    • 仓储就是存放一堆镜像文件的地方,我们可以把镜像发布到仓储中,需要的时候从仓储中拉下来就可以了

安装步骤

  • 官网安装参考手册
    https://docs.docker.com/engine/install/centos/

  • 卸载老的Docker及依赖

    为什么你可能还需要删除较低的Docker安装?因为较旧版本的Docker被称为docker或docker-engine(它是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure)

1
2
3
4
5
6
7
8
9
10
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
  • 安装依赖库
    • yum-utils 提供 yum-config-manager 类库
    • device-mapper-persistent-data 和 lvm2 被devicemapper 存储驱动依赖
1
2
3
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
  • 设置stable镜像仓库

    • 非常慢的方式

      1
      yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    • 比较快的方式(使用镜像地址)

      1
      yum-config-manager --add-repo http://mirror.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  • 更新yum软件包索引

    1
    yum makecache fast
  • 安装社区版docker

    1
    sudo yum -y install docker-ce
  • 启动docker

    1
    sudo systemctl start docker
  • 确认docker安装成功

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@test ~]# systemctl status docker
    ● docker.service - Docker Application Container Engine
    Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
    Active: active (running) since Sat 2022-11-05 20:34:14 CST; 5 days ago
    Docs: https://docs.docker.com
    Main PID: 24058 (dockerd)
    Tasks: 9
    Memory: 70.7M
    CGroup: /system.slice/docker.service
    └─24058 /usr/bin/dockerd --graph=/data/docker -H fd:// --containerd=/run/containerd/containerd.sock
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [root@test ~]# docker run hello-world

    Hello from Docker!
    This message shows that your installation appears to be working correctly.

    To generate this message, Docker took the following steps:

    1. The Docker client contacted the Docker daemon.
    2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
    3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
    4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

    To try something more ambitious, you can run an Ubuntu container with:
    $ docker run -it ubuntu bash

    Share images, automate workflows, and more with a free Docker ID:
    https://hub.docker.com/

    For more examples and ideas, visit:
    https://docs.docker.com/get-started/
  • 配置镜像加速

    Docker 安装好以后,我们就要开始为拉取镜像准备了;国内从 DockerHub 拉取镜像有时会特别慢,此时可以配置镜像加速器

这里配置 Docker官方中国的加速器

​ 对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件)

1
2
3
4
5
6
7
sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
EOF

​ 配置完后重启docker服务

1
2
sudo systemctl daemon-reload
sudo systemctl restart docker
  • 启动Docker后台容器(测试运行hello world)

    • docker run helloworld
      • 相当于docker run helloworld:latest
    • run干了什么
      • 先在本地找helloworld这个镜像
      • 如果找不到,就去仓库中去找,找到后pull到本地
      • 根据这个镜像启动一个容器
  • 卸载

    1
    2
    3
    systemctl stop docker
    yum -y remove docker-ce
    rm -rf /var/lib/docker

底层原理

  • Docker是怎么工作的

    • Docker是一个CS结构的系统,Docker守护进程运行在主机上,然后通过socket连接从客户端访问,守护进程从客户端接收命令并管理运行在主机上的容器。容器,是一个运行时环境
  • Docker为什么比vm快

    • docker有着比虚拟机更少的抽象层。docker不需要实现硬件资源虚拟化,docker上的程序直接使用的是实际物理机的硬件资源,因此在cpu、内存利用率上docker有明显的优势
    • docker利用的是宿主机的内核,而不是vm的内核,所以加载docker容器会很快。那这就是说,centos上的容器只能跑在centos的机器上

良好的 SQL 习惯能大大降低慢查询出现的概率,下面主要介绍一些 SQL 规范

避免隐式转换

当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。则会发生转换隐式
隐式转换的类型主要有字段类型不一致、in参数包含多个类型、字符集类型或校对规则不一致等
隐式类型转换可能导致无法使用索引、查询结果不准确等

尽量不使用 select * ,只选择需要的字段

因为使用 selec * 会读取不需要的列,会增加 CPU 负载、磁盘 I/O、网络流量的消耗,并且容易在增加或者删除字段后导致程序报错

禁止单条 SQL 语句同时更新多张表

禁止使用以 “%” 开头的模糊查询

以 “%” 开头的模糊查询大都是不能使用索引的

在 SQL 中不建议使用 sleep()

使用 sleep() 可能会增加 SQL 加锁的时间,从而影响并发性能

避免大表的 join

可能会导致数据库高负载,从而影响其他查询

同一张表的多条 alter 语句要合成一次操作

规范的部署和操作可以让MySQL更加安全,并且可以提高数据库的性能。

选择合适的MySQL版本

不建议使用太旧的版本
因为很多特性旧版本都没有,并且性能也没有新版本的好
线上环境建议使用 GA (General Availability,正式发布的版本)

必须开启 Binlog

Binlog 的两个主要作用:复制和灾备
在通常情况下,必须开启 Binlog

对数据安全要求较高的场景,建议设置为“双 1”

innodb_flush_log_at_trx_commit 参数和 sync_binlog 参数都设置为 1

配置不区分大小写

可以将 lower_case_table_names 参数设置为 1,表示表名以小写形式存储在磁盘上,并且不区分大小写

如无特殊说明,设置业务用户只有增、删、查、改的权限

权限最小化可以大大降低误删数据库的风险

合理设置缓冲池

将 innodb_buffer_pool_size 参数设置为机器内存的 60%~75%

批量导入、导出、更新、删除操作建议在业务低峰操作,并且要提前通知DBA协助观察

批量导入、导出、更新、删除操作以及会导致主从延迟和磁盘空间增加的操作(如碎片整理、增减字段等)建议在业务低峰进行。防止这些操作对线上业务影响。例如,批量删除数据可能会导致主从延迟,如果有从库提供业务查询,可能会导致读取延迟。碎片整理、增减字段等操作,短时间需要大量的磁盘空间,可能会导致数据库磁盘跑满

如果有可能导致 MySQL QPS 上升,应提前告知 DBA

如果超过现有数据库配置所能承载的最大 QPS,则需要考虑扩容

删除库或表前,先修改库名或表名,观察几天,确定对业务没有影响,再删除,防止有其他不知道的业务连接了该库或该表

docker简介

前置知识

  • Linux命令
  • git
  • Docker相关开发语言和技术
    • Swarm
    • Compose
    • Mesos
    • Machine
    • k8s
    • CI/CD Jenkins整合

是什么

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroupnamespace,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 版本开始,则进一步演进为使用 runCcontainerd

  • 问题:为什么会有Docker出现

    • 开发和运维环境的差异导致部署失败
  • 出于高并发以及快速响应的需要,系统新功能需要快速上线(秒级)

    • 软件可以带环境安装
    • 安装的时候,把原始环境一模一样地复制过来
      开发人员利用Docker可以消除协作编码时“在我的机器上可以正常工作”的问题
  • Docker理念

    • 打破代码即应用的理念,变成镜像即应用
  • 一次构建,处处运行

  • 一句话

    • 是解决了运行环境和配置问题的软件容器,是方便做持续集成并有助于整体发布的容器虚拟化技术

能干嘛

  • 之前的虚拟机技术

    • 虚拟机是带环境安装的一种解决方案
    • 它可以在一种操作系统里面运行另一种操作系统,应用程序对此毫无感知。虚拟机看上去跟真实系统完全一样,对于底层系统来说,虚拟机就是一个普通文件,不需要就可以删除。
      这类虚拟机完美地运行了另一套系统,能够使应用程序、操作系统和硬件三者之间的逻辑不变
    • 缺点:
      • 资源占用多
      • 冗余步骤多
      • 启动慢
  • 容器虚拟化技术

    • Linux发展了另一种虚拟化技术,LXC

    • Linux容器不是模拟一个完整地操作系统,而是对进程进行隔离
      容器和虚拟机不同,不需要捆绑一个完整的操作系统,只需要软件工作所需要的库资源和设置

    • 重点就是容器是共用操作系统内核的,不进行硬件虚拟

    • 每个容器是相互隔离的,每个容器都有自己的文件系统,容器之间进程不会相互影响,能够区分计算资源

  • 开发/运维(DevOps)

    • 开发自运维
    • 一次构建,随处运行
      一次构建是指开发工程师做一次构建
      无需运维工程师再做构建
      随处运行是指对于这个构建的交付物,可以随处运行
      • 更快捷的应用交付和部署
      • 更便捷的升级和扩缩容
      • 更简单的系统运维
      • 更高效的计算资源利用
  • 企业级

去哪下

简介

本规范由四部分组成:表结构设计、索引设计、高可用设计、分布式架构设计。

四大规范,每个规范10条准则。

表结构设计规范

  1. INT 类型不使用 unsigned 无符号属性,容易引入额外的计算问题。

  2. 自增用 8 字节 BIG INT,不要使用 4 字节 INT,且自增在 MySQL 8.0 版本前有回溯问题,请考虑是否业务有影响。

  3. 字符集使用 UTF8MB4 字符编码,不推荐 GBK、UTF-8 等其他字符集。

  4. 日期类型用 DATETIME 类型,需要精确到毫秒用 DATETIME(6),不要使用 INT、TIMESTAMP。

  5. 类型 JSON 可用于存储非结构化数据,典型场景为用户标签,不要将 JSON 用于频繁更新的字段场景。

  6. 每张表一定要有一个主键,这样至少满足一范式的要求,核心业务表用全局唯一字段(雪花算法、有序UUID)做主键,不要使用自增做主键。

  7. 对于日志类的流水表、报警表、日志表,可以使用压缩设计,提升存储效率。MySQL 5.7 版本开始推荐使用透明页压缩,不要使用传统的 KEY_BLOCK_SIZE 的页压缩。

  8. 类别设计,用 ENUM+CHECK 约束,不要使用 INT 类型的设计。

  9. 敏感字段需加密,如账户密码、信用卡号等存储使用:动态盐 + 非固定加密算法(MD5/AES256等) + 多轮加密,不要简单使用 MD5 算法加密,容易被暴力破解。

  10. MySQL 可以通过 KV 的方式访问表中的数据,若业务只是简单的 SET、GET 请求,可考虑将其转化为 Memcached 的 KV 访问方式,减少 SQL 解析的开销,性能可以有至少 50% 的提升。

索引设计规范

  1. 不要陷入设置单表行数、列数限制的固有印象,其他关系型数据库没有行数、列数限制,MySQL 也没有,大表的缺点不是性能,而是后续的 DDL 管理问题,随着 MySQL 8.0 快速加列功能的上线,大表 DDL 问题基本已解决。

  2. MySQL 是索引组织表,表中的数据以 B+ 树索引结构,根据主键逻辑排序,由于 B+ 树索引的特点是树的高度为 3~4 层,所以从数十亿的记录中,通过主键查询一条记录只需要 3、4 次 I/O,当前的 SSD 存储设备设置每秒至少能完成 10000 次的 I/O 查询,不要担心通过索引查询一条或几条记录的性能,每秒百万次查询并不难。

  3. MySQL 是索引组织表,二级索引只存储(键值、主键值),因此需要再通过一次主键索引查询得到记录,这种方式成为回表。在核心业务中,使用索引覆盖技术,提升索引查询性能,对于回表记录数比较大的场景,甚至可以有 10 倍的性能提升;

  4. 对类似 WHERE a = ? ORDER BY b 这样的查询,一定要创建(a、b)组合索引,这样可以避免一次额外排序,提升查询性能。

  5. MySQL 优化器是 CBO(Cost-based Optimizer),所有查询基于成本而不是规则,若发现 SQL 执行计划发生变化,不要怀疑 MySQL 出错,请先分析数据特点、索引创建是否合理,是否可以通过直方图校准数据

  6. MySQL JOIN 支持 NLJ(Nested Loop Join)和 NHJ(Nested Hash Join)两种方式。对于 OLTP 业务,放心大胆使用 JOIN,但一定要做好索引的设计和索引覆盖的考虑(不考虑分布式数据库场景);对于 OLAP 业务,MySQL 8.0 版本开始,支持 Hash Join,对于大数据量的关联,性能提升非常多,可以在不超过 10T 的数仓场景中考虑使用,超过 10T数据量,请一定使用大数据产品,如 Hive、Spark、麒麟等产品。

  7. MySQL 5.7 版本开始子查询优化已经做得不错,但是编写的子查询不能是关联子查询,上线前一定需要确认,若发现关联子查询,请改写子查询为 JOIN 或其他方式。

  8. 不要因为数据量大,使用分区表,MySQL 是索引组织表,数据量再大,定位记录也只需要3、4 次 I/O。可以考虑分区表唯一的应用场景是:需要定期清理历史流水类数据,但如果业务可以按月、按天做分表,那么当前 MySQL 8.0 版本,分区表也不推荐使用。

  9. 业务上线或新版本发布前,DBA 一定要进行所有 SQL Review,确保 SQL 走索引,否则不予上线,或由业务以邮件等正式方式,通知 DBA 该 SQL 不会引起线上事故,业务方承担后续责任。

  10. DBA 每天要对数据库进行巡检,及早发现慢查询或潜在数据库风险,将任何潜在问题尽早抛出,否则后续自己承担相关责任。

高可用设计规范

  1. MySQL 高可用的基石是利用二进制日志的复制技术,核心业务一定要使用无损半同步方式,但凡不使用无损半同步的高可用架构,请业务方业务以邮件等正式方式回复,数据丢失等后续问题自己承担相关责任。

  2. MySQL 5.7 版本开始,一定要使用基于 WRITESET 的从机回放,避免主从延迟。

  3. 当前 MySQL 发生主从延迟的可能性主要是存在大事务,比如定期计算收益等操作,这类大事务,一定要通知业务方将大事务拆成小事务,否则不予上线;若要上线,请业务方以邮件等方式回复,自己承担后续主从延迟带来的后续一系列问题。

  4. MySQL 8.0 版本开始推荐金融业务使用 MGR(MySQL Group Replication),通过 Paxos 协议保证数据一致性,并自己完成选主的逻辑,也可以使用多主模式,数据不冲突的情况下,可以大幅提升写入性能。

  5. 对于核心业务,必须遵循互不信任原则,数据一致性不单单依赖 MySQL 复制本身,DBA 这里需要通过逻辑的方式,对主从数据进行核对,业务这里也需要一套业务层的逻辑进行“对账”。

  6. 核心业务,务必使用一地三中心,两地三中心的跨机房复制架构,这样发生机房级故障,可以切换到另一个机房,保证业务可用性。

  7. 同城容灾架构一定要评估切换到另一个机房后业务访问的性能,多次跨机房访问 DB,虽然每次只多了 2~3ms,但也存在业务雪崩问题,推荐 DB 切换机房,上层业务跟着一起联动切换。

  8. 对于有跨城容灾需求的业务,可以考虑使用三地五中心架构,但是由于 30ms 延迟,业务需要进行评估,对于核心业务,务必使用业务层的跨城机制,将数据层的多次网络耗时合并为一次,这样能大大提升业务的性能。

  9. 业界的 MHA、Ochestrator 等高可用套件都是基于 ssh 访问 MySQL,稳定性、安全性不高,不推荐大厂使用;自己开发一个数据库管理平台,通过 agent 的模式管理高可用和 MySQL 数据库的日常操作更为安全、有效。

  10. 一定做好数据备份架构的设计,全备 + 增量备份 + 延迟备机(可选),做到可以基于任何一点恢复和回滚,同时,遵循互不信任原则,备份文件一定要进行检查,确保需要时一定能够进行恢复。

分布式架构设计规范

  1. 分布式数据库的本质就是根据某几个列的规则,将数据水平打散,存在不同的实例中。数据拆分的列称为分区键(Shard Key),分区键一定是业务大部分访问(超过 80%)的表都会使用的列。若选不出合适的分区键,那就一定不要进行分布式数据库架构的设计;互联网业务绝大部分分区键的选择是用户维度。

  2. 分区算法绝大部分场景使用 Hash算法,这样数据的存储和访问可以平均到下面多个实例,真正的做到可扩展性,Range 算法通常无法解决热点问题,会是灾难,但 Range 算法可以在单实例中使用,作为二级拆分数据的规则。

  3. 分布式数据库分片时,一开始就设计为不少于 1000 个分片的规则,不用担心分片过多的问题,管理 1 个分片和 1000 个分片的成本是一样的,但为后续的扩容做好了充足的准备。

  4. 分布式数据库扩缩容就是通过部分过滤的复制技术,按库或按表进行数据同步,分库分表设计推荐库名、表都不同,做到全局唯一,方便后续拆分。

  5. 分布式数据库索引设计中,非分区键的唯一索引一定带入分区键信息,这样业务查询时可以直接定位到数据所在分片,提升查询效率。

  6. 分布式数据库索引设计中,数据库层的唯一约束只在单个实例中保证,若要保证全局唯一,一定要使用全局唯一的索引设计。

  7. 直接使用 JOIN 请确认一定可以单元化在一个分片中完成,如果涉及跨分片的 JOIN,请通知业务修改成多条 SQL 的访问方式,只访问指定分片而不是所有分片。

  8. 分布式数据库可以进行业务层的分库分表访问,和通过数据库中间件的访问,对于业务耗时敏感的业务,推荐业务层直接根据路由规则访问数据,否则使用数据库中间件,简单易用。

  9. 对于耗时敏感的核心业务,推荐使用最终一致的业务层柔性事务,数据库层的 2PC 分布式事务耗时较大,性能较为一般,但是 2PC 使用简单,能满足大部分业务的使用。

  10. 一定要利用好分布式数据库架构的特点,设计多活架构,每个机房都可以有写入流量,提升资源使用率和业务连续性,请 DBA 和业务方一起做好全链路的架构设计。

MySQL规范–建表规范

规范的表结构能降低MySQL出现问题的概率,下面总结一些MySQL的建表规范

1、库名、表名、字段名全部采用小写格式。

这是防止出现大小写不一致导致找不到对应表的情况。

2、避免使用MySQL的保留字。

使用mysql时一定要注意,尽量不要使用它的保留字作为表名或者列名,否则会出现莫名其妙的错误。如果之前用了,在sql语句中就用``包括起来。

3、表名、列名建议不要超过30个字符。

4、临时库表、备份库表的命名

临时库表必须以tmp加上日期为后缀,比如user_info_tmp_20221109。
备份库表必须以bak加上日期为后缀,比如user_info_bak_20221109。

5、索引命名

非唯一索引建议按照”idx字段名”进行命名。
唯一索引建议按照 “uniq_字段名”进行命名。

6、主键的建议

表必须有主键。
不使用有业务意义的的列作为主键,以免收到业务变化的影响。
不使用更新频繁的列作为主键。
不使用UUID、MD5等作为主键,因为它们可能会导致数据过于离散。
建议使用非空的唯一键作为主键,并配置自增或者发号器。

7、建议使用 InnoDB

InnoDB 具有支持事务、行锁设计、在高并发场景下性能更好等特性。因此在绝大多数业务场景下,InnoDB 都是唯一选择。

8、使用 utf8mb4 字符集,数据排序规则使用 utf8mb4_general_ci

utf8mb4 为万国码,无乱码风险;与 utf8 编码相比,utf8mb4 支持 Emoji 表情。

9、所有表、字段都需要增加 comment 来描述此表、字段的含义

10、如无特殊要求,表必须包含记录的创建时间和更新时间的字段

11、用尽量少的存储空间来存储一个字段的数据

能用 int 的就不用 char 或 varchar
能用 tinyint 的就不用 int
使用 unsigned 存储非负数值

12、尽可能不使用 text 和 blob 类型

使用 text 或 blob 类型,会浪费更多的磁盘空间和内存空间。如无法避免使用 text 或 blob 类型,则建议独立出一张表,使用主键或其他唯一的字段来对应,以避免影响原有表的查询效率。

13、禁止在数据库中存储明文密码

数据安全第一,防止密码泄露

14、索引设计

在设计索引时,建议考虑以下因素:
经常作为条件、排序、join关联的字段,建议加上索引。
单表索引数不建议过多,因为索引也会占用空间。
如果可以使用覆盖索引,或者几个字段经常同时作为条件,则可以考虑使用联合索引。
不在低基数的列上创建索引,如性别字段。

15、不建议使用外键

在有外键的情况下,update 操作和 delete 操作都会涉及相关联的表,不仅会影响 SQL 的性能,还会大大增加死锁出现的概率

16、线上业务禁止使用存储过程、视图、触发器、Event 等

在高并发的情况下,这些功能很可能会影响数据库的性能,如果有类似的需求,则建议把这些逻辑放到服务层实现

17、单表字段数不宜过多,建议小于 30 个字段,也可根据业务场景确定

操作场景

本文以云服务器的操作系统为“CentOS 7.4 64位”为例,采用Parted分区工具为数据盘设置分区。

MBR支持的磁盘最大容量为2 TB,GPT最大支持的磁盘容量为18 EB,因此当您初始化容量大于2 TB的磁盘时,分区形式请采用GPT。对于Linux操作系统而言,当磁盘分区形式选用GPT时,fdisk分区工具将无法使用,需要采用parted工具。

划分分区并挂载磁盘

本操作以该场景为例,当云服务器挂载了一块新的数据盘时,采用parted分区工具为数据盘设置分区,分区形式设置为GPT,文件系统设为ext4格式,挂载在“/mnt/sdc”下,并设置开机启动自动挂载。

  1. 执行以下命令,查看新增数据盘。

    lsblk

    回显类似如下信息:

    1
    2
    3
    4
    5
    root@ecs-test-0001 ~]# lsblk
    NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
    vda 253:0 0 40G 0 disk
    └─vda1 253:1 0 40G 0 part /
    vdb 253:16 0 100G 0 disk

    表示当前的云服务器有两块磁盘,“/dev/vda”是系统盘,“/dev/vdb”是新增数据盘。

  2. 执行以下命令,进入parted分区工具,开始对新增数据盘执行分区操作。

    parted 新增数据盘

    命令示例:

    parted /dev/vdb

    回显类似如下信息:

    1
    2
    3
    4
    5
    [root@ecs-test-0001 ~]# parted /dev/vdb
    GNU Parted 3.1
    Using /dev/vdb
    Welcome to GNU Parted! Type 'help' to view a list of commands.
    (parted)
  3. 输入“p”,按“Enter”,查看当前磁盘分区形式。

    回显类似如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    (parted) p
    Error: /dev/vdb: unrecognised disk label
    Model: Virtio Block Device (virtblk)
    Disk /dev/vdb: 107GB
    Sector size (logical/physical): 512B/512B
    Partition Table: unknown
    Disk Flags:
    (parted)

    “Partition Table”为“unknown”表示磁盘分区形式未知,新的数据盘还未设置分区形式。

  4. 输入以下命令,设置磁盘分区形式。

    mklabel 磁盘分区形式

    磁盘分区形式有MBR和GPT两种,以GPT为例:

    mklabel gpt

    img须知:

    MBR支持的磁盘最大容量为2 TB,GPT最大支持的磁盘容量为18 EB,当前数据盘支持的最大容量为32 TB,如果您需要使用大于2 TB的磁盘容量,分区形式请采用GPT。

    当磁盘已经投入使用后,此时切换磁盘分区形式时,磁盘上的原有数据将会清除,因此请在磁盘初始化时谨慎选择磁盘分区形式。

  5. 输入“p”,按“Enter”,设置分区形式后,再次查看磁盘分区形式。

    回显类似如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (parted) mklabel gpt
    (parted) p
    Model: Virtio Block Device (virtblk)
    Disk /dev/vdb: 107GB
    Sector size (logical/physical): 512B/512B
    Partition Table: gpt
    Disk Flags:

    Number Start End Size File system Name Flags

    (parted)

    “Partition Table”为“gpt”表示磁盘分区形式已设置为GPT。

  6. 输入“unit s”,按“Enter”,设置磁盘的计量单位为磁柱。

  7. 以整个磁盘创建一个分区为例,执行以下命令,按“Enter”。

    mkpart 磁盘分区名称 起始磁柱值 截止磁柱**值

    命令示例:

    mkpart test 2048s 100%

    “2048s”表示磁盘起始磁柱值,“100%”表示磁盘截止磁柱值,此处仅供参考,您可以根据业务需要自行规划磁盘分区数量及容量。

    回显类似如下信息:

    1
    2
    (parted) mkpart opt 2048s 100%
    (parted)
  8. 输入“p”,按“Enter”,查看新建分区的详细信息。

    回显类似如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (parted) p
    Model: Virtio Block Device (virtblk)
    Disk /dev/vdb: 209715200s
    Sector size (logical/physical): 512B/512B
    Partition Table: gpt
    Disk Flags:

    Number Start End Size File system Name Flags
    1 2048s 209713151s 209711104s test

    (parted)
  9. 输入“q”,按“Enter”,退出parted分区工具。

    回显类似如下信息:

    1
    2
    (parted) q
    Information: You may need to update /etc/fstab.

    “/etc/fstab”文件控制磁盘开机自动挂载,请先参考以下步骤为磁盘分区设置文件系统和挂载目录后,再根据文档指导更新“/etc/fstab”文件。

  10. 执行以下命令,查看磁盘分区信息。

    lsblk

    回显类似如下信息:

    1
    2
    3
    4
    5
    6
    [root@ecs-test-0001 ~]# lsblk
    NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
    vda 253:0 0 40G 0 disk
    └─vda1 253:1 0 40G 0 part /
    vdb 253:16 0 100G 0 disk
    └─vdb1 253:17 0 100G 0 part

    此时可以查看到新建分区“/dev/vdb1”

  11. 执行以下命令,将新建分区文件系统设为系统所需格式。

    mkfs -t 文件系统格式 /dev/vdb1

    以设置文件系统为“ext4”为例:

    mkfs -t ext4 /dev/vdb1

    回显类似如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    [root@ecs-test-0001 ~]# mkfs -t ext4 /dev/vdb1
    mke2fs 1.42.9 (28-Dec-2013)
    Filesystem label=
    OS type: Linux
    Block size=4096 (log=2)
    Fragment size=4096 (log=2)
    Stride=0 blocks, Stripe width=0 blocks
    6553600 inodes, 26213888 blocks
    1310694 blocks (5.00%) reserved for the super user
    First data block=0
    Maximum filesystem blocks=2174746624
    800 block groups
    32768 blocks per group, 32768 fragments per group
    8192 inodes per group
    Superblock backups stored on blocks:
    32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
    4096000, 7962624, 11239424, 20480000, 23887872

    Allocating group tables: done
    Writing inode tables: done
    Creating journal (32768 blocks): done
    Writing superblocks and filesystem accounting information: done

    格式化需要等待一段时间,请观察系统运行状态,不要退出。

    img须知:

    不同文件系统支持的分区大小不同,请根据您的业务需求选择合适的文件系统。

  12. 执行以下命令,新建挂载目录。

    mkdir 挂载目录

    以新建挂载目录“/mnt/sdc”为例:

    mkdir /mnt/sdc

  13. 执行以下命令,将新建分区挂载到12中创建的目录下。

    mount 磁盘分区 挂载目录

    以挂载新建分区“/dev/vdb1”至“/mnt/sdc”为例:

    mount /dev/vdb1 /mnt/sdc

  14. 执行以下命令,查看挂载结果。

    df -TH

    回显类似如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@ecs-test-0001 ~]# df -TH
    Filesystem Type Size Used Avail Use% Mounted on
    /dev/vda1 ext4 43G 1.9G 39G 5% /
    devtmpfs devtmpfs 2.0G 0 2.0G 0% /dev
    tmpfs tmpfs 2.0G 0 2.0G 0% /dev/shm
    tmpfs tmpfs 2.0G 9.0M 2.0G 1% /run
    tmpfs tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup
    tmpfs tmpfs 398M 0 398M 0% /run/user/0
    /dev/vdb1 ext4 106G 63M 101G 1% /mnt/sdc

    表示新建分区“/dev/vdb1”已挂载至“/mnt/sdc”。

    说明:

    云服务器重启后,挂载会失效。您可以修改“/etc/fstab”文件,将新建磁盘分区设置为开机自动挂载

设置开机自动挂载磁盘分区

设置云服务器系统启动时自动挂载磁盘分区,不能采用在“/etc/fstab”直接指定设备名(比如/dev/vdb1)的方法,因为云中设备的顺序编码在关闭或者开启云服务器过程中可能发生改变,例如/dev/vdb1可能会变成/dev/vdb2。推荐使用UUID来配置自动挂载磁盘分区。

说明:

UUID(universally unique identifier)是Linux系统为磁盘分区提供的唯一的标识字符串。

  1. 执行如下命令,查询磁盘分区的UUID。

    blkid 磁盘分区

    以查询磁盘分区“/dev/vdb1”的UUID为例:

    blkid /dev/vdb1

    回显类似如下信息:

    1
    2
    [root@ecs-test-0001 ~]# blkid /dev/vdb1
    /dev/vdb1: UUID="0b3040e2-1367-4abb-841d-ddb0b92693df" TYPE="ext4"

    表示“/dev/vdb1”的UUID。

  2. 执行以下命令,使用VI编辑器打开“fstab”文件。

    vi /etc/fstab

  3. 按“i”,进入编辑模式。

  4. 将光标移至文件末尾,按“Enter”,添加如下内容。

    1
    UUID=0b3040e2-1367-4abb-841d-ddb0b92693df /mnt/sdc                ext4    defaults        0 2

    以内容上仅为示例,具体请以实际情况为准,参数说明如下:

    • 第一列为UUID,此处填写1中查询到的磁盘分区的UUID。

    • 第二列为磁盘分区的挂载目录,可以通过df -TH命令查询。

    • 第三列为磁盘分区的文件系统格式, 可以通过df -TH命令查询。

    • 第四列为磁盘分区的挂载选项,此处通常设置为defaults即可。

    • 第五列为Linux dump备份选项。

      • 0表示不使用Linux dump备份。现在通常不使用dump备份,此处设置为0即可。
      • 1表示使用Linux dump备份。
    • 第六列为fsck选项,即开机时是否使用fsck检查磁盘。

      • 0表示不检验。

      • 挂载点为(/)根目录的分区,此处必须填写1。

        根分区设置为1,其他分区只能从2开始,系统会按照数字从小到大依次检查下去。

  5. 按“ESC”后,输入“:wq”,按“Enter”。

    保存设置并退出编辑器。

  6. 执行以下步骤,验证自动挂载功能。

    1. 执行如下命令,卸载已挂载的分区。

      umount 磁盘分区

      命令示例:

      umount /dev/vdb1

    2. 执行如下命令,将“/etc/fstab”文件所有内容重新加载。

      mount -a

    3. 执行如下命令,查询文件系统挂载信息。

      mount | grep 挂载目录

      命令示例:

      mount | grep /mnt/sdc

      回显类似如下信息,说明自动挂载功能生效:

      1
      2
      root@ecs-test-0001 ~]# mount | grep /mnt/sdc
      /dev/vdb1 on /mnt/sdc type ext4 (rw,relatime,data=ordered)

运维安全的四个层次

网络安全

网络设备的安全

  • 思科、华为等网络设备定期升级,修复bug和曝出的漏洞
  • 公网防火墙,核心交换机等核心网络设备的管理

外网安全策略

  • IDC,防火墙策略,严把上行端口开放
  • 公网上下行流量监控
  • 对DDos攻击提高警惕,提前准备应急预案
    • 临时提高流量,硬抗
    • 启动流量清洗,将攻击流量引入黑洞,有可能误杀正常用户

专线安全策略

  • 对涉及金融、支付等项目设立专线

VPN安全策略

  • IPsec VPN:site to site
  • OpenVPN: peer to site
  • 摒弃PPTP等不含加密算法的vpn服务
  • 端口全禁止,需要通信的申请审批后,再由管理员开放

数据安全

数据库用户权限

  • 管理员权限限定,不允许远程root
  • 定期更换管理员密码
  • 应用权限最小化,专人管理
  • 手动查询权限可审计

数据库审计设备

  • 数据库主库不能开一般查询日志(为了性能)
  • 交换机上镜像流量,接入审计设备,实现实时审计
  • 不要设计串行在系统里,形成单点和瓶颈

数据库脱敏

  • 姓名、身份证、手机号、银行卡号等敏感信息应脱敏处理
  • 对程序脱敏协同系统架构部共同出规范
  • 对手动查询权限脱敏,按列授权,录屏

备份策略

  • 每周全备,每天增备
  • 备份文件要每天利用内网流量低谷时间,推送到远程主机,有条件的应跨机房备份
  • 一定要规划定期恢复测试,保证备份的可用性

应用安全

操作系统安全

  • 系统基础优化(内核优化,优化工具)
  • 日期,时区同步
  • root密码复杂度足够高,需要在操作系统里做定时过期策略
  • 每三个月使用脚本更新服务器的root密码和iDrac密码,并将更换后的密码加密打包发送给指定管理员邮箱,同时提交gitlab
  • 对系统关键文件进行md5监控,例如/etc/passwd,~/.ssh/authorized_keys文件等,如有变更,触发报警
  • 定期查毒,漏扫,定期安排更新操作系统
  • /etc/ssh/sshd_config里配置:
    • PasswordAuthentication no
    • PermitRootLogin without-password
  • 使用saltstack等批量管理软件进行特权命令执行和备份脚本执行(避开ssh协议)

应用系统安全

WEB应用防火墙(WAF)

  • 防SQL注入
  • 防CC攻击
  • 防XSS跨站脚本

应用系统漏洞

  • 关注0day漏洞新闻
  • 及时整改并上线投产
  • 组织技术力量测试,复现

日志收集和分析

  • 完善日志收集方案,集中转储
  • 通过应用系统日志分析,进行安全预警

DNS劫持

  • 全站https,购买泛域名证书
  • 有条件的可以自己维护公网DNS,上dnssec数字签名
  • 采购基调、听云等第三方拨测服务,分布式监控网站质量
  • 向ISP投诉,工信部举报

Basic Auth

  • 在nginx上做,非常简单
  • 对防脚本攻击有奇效

企业邮箱服务器安全

推荐使用微软的Exchange

功能强大,维护相对简单

投产反垃圾邮件网关

投产梭子鱼反垃圾邮件网关,防伪造发信人

群发审核管控

用好邮件组

接入AD域控

域名安全管理

做好ICP备案

  • 域名证书
  • 域名实名认证(公司模板)
  • 接入商处蓝色幕布拍照
  • 法人身份证、管理员身份证
  • 网站真实性核验单

公网解析

  • 专人管理,邮件申请,审批
  • 将业务解析至不同公网IP出口,双活机房
  • 智能解析,解析至不同线路
  • 如有条件,可购买公网解析套餐服务,安全服务等

内网安全

80%以上的企业IT安全问题出自内网安全

堡垒机

  • 一定要强制使用堡垒机登录服务器
  • ssh私钥通行短语机制,避免密钥失窃
  • 定期审计堡垒机操作日志
  • 如果有必要,可以上2FA(双因子验证)

AD域控

有条件一定要接入windows域控,要求密码复杂度和定期过期

  • 邮箱
  • wifi
  • vpn账号密码
  • 内网系统账号
  • 业务系统账号
  • 网络设备等

办公网安全

  • 专业的HelpDesk团队
  • 企业级杀毒软件
  • 办公电脑接入域控
  • 上网行为管理
  • 流量监控,mac地址绑定
  • 有条件的可以在办公环境上一个小型的业务机房
  • wifi管控,单做guest接入点,不能访问业务核心网络