haokeed

haokeed 查看完整档案

温州编辑温州大学  |  计算机科学与应用 编辑  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

haokeed 发布了文章 · 10月26日

Nginx实用操作

实用手册:https://wizardforcel.gitbooks...

1 nginx基础操作

1.1 nginx安装

# yum快速安装
yum -y install pcre pcre-devel zlib zlib-devel openssl openssl-devel


# 编译安装
wget http://nginx.org/download/nginx-1.14.1.tar.gz
tar -zxvf nginx-1.14.1.tar.gz
./configure --prefix=/usr/local/nginx \
--with-http_stub_status_module \
--with-http_gzip_static_module\
--with-http_realip_module\
--with-http_sub_module \
--with-http_ssl_module\
--with-http_realip_module \
--with-http_sub_module \
--with-http_gunzip_module\
--with-http_gzip_static_module\
--with-http_auth_request_module\
--with-http_random_index_module \
--with-http_slice_module \
--with-http_stub_status_module

make && make install

# 查看加载的模块及版本信息
nginx -V

1.2 nginx常用命令

# 查看Nginx的版本号
nginx -V

# 停止
nginx -s stop

# 退出 
nginx -s quit

# 重启加载配置 
nginx -s reload

# 配置文件启动
nginx -c </path/to/config> 
# </path/to/config> 为 Nginx 指定一个配置文件,来代替缺省的

# 不运行,而仅仅测试配置文件
nginx -t
# nginx 将检查配置文件的语法的正确性,并尝试打开配置文件中所引用到的文件。

1.3 nginx控制信号

再次回过头看看nginx的原理图,我们还可以通过信号去操作nginx,默认情况下nginx将其主进程的pid写入到/usr/local/nginx/nginx.pid文件中。

# nginx 停止命令,等所有请求结束后关闭服务。(从容关闭)
Kill -QUIT nginx 主进程号

# 重新载入配置。用新的配置开始新的工作进程,从容关闭旧的工作进程
kill -HUP nginx 主进程号

# 快速关闭
kill -TERM nginx 主进程号
# 或
kill -INT nginx 主进程号

# 重新打开日志文件
kill -USR1 nginx 主进程号

# 平滑升级可执行程序
kill -USR2 nginx 主进程号

# 从容关闭工作进程
kill -WINCH nginx 主进程号

2 nginx配置说明

2.1 基础配置说明

image.png
配置语法说明:

1、配置文件由指令与指令块构成
2、每条指令以;分号结尾,指令与参数间以空格符号分隔
3、指令块以{}大括号将多条指令组织在一起
4、使用#符号添加注释,提高可读性
5、include语句允许组合多个配置文件以提升可维护性
6、使用$符号使用变量
7、部分指令的参数支持正则表达式

Nginx的各种指令以及配置繁多,有些配置可以在如下的链接 https://tengine.taobao.org/nginx_docs/cn/docs/ 或者在官方文档上查看

为了对配置功能进行可扩展化,并对相关配置能进行明确的区分,让配置看见就能很明了。采用了模块化配置。
nginx常用模块
image.png

http模块

http标准核心模块,http服务的相应配置

server模块

接收请求的服务器需要将不同的请求按规则转发到不同的后端服务器上,在 nginx 中我们可以通过构建虚拟主机(server)的概念来将这些不同的服务配置隔离。

server_name

由于IP地址的数量有限,因此经常存在多个主机域名对应着同一个IP地址的情况,这时在 nginx.conf中就可以按照server_name(对应用户请求中的主机域名)并通过server块来定义虚拟主机,每个server块就是一个虚拟主机,它只处理与之相对应的主机域名请求。这样,一台服务器上的 Nginx 就能以不同的方式处理访问不同主机域名的HTTP请求了。

语法:

主机名称
语法: server_name name[...];
默认: server_name"";
配置块: server

虚拟主机名可以使用确切的名字,通配符,或者是正则表达式来定义,在开始处理一HTTP请求时, Nginx会取出header头中的Host,与每个server中的server_name进行匹配,以此决定到底由哪一个server块来处理这个请求。有可能一个Host与多个server块中的server_name都匹配,这时就会根据匹配优先级来选择实际处理的server块。

注意:优先级问题,所导致的配置不生效
server_name与Host的匹配优先级如下:
1)首先选择所有字符串完全匹配的server_name,如 nginx.2367.com 。
2)其次选择通配符在前面的server_name,如 *.2367.com。
3)再次选择通配符在后面的server_name,如nginx.2367.* 。
4)最后选择使用正则表达式才匹配的server_name,如 ~^\.testweb\.com$
如果都不匹配
1、优先选择listen配置项后有default或default_server的
2、找到匹配listen端口的第一个server块

location(URL匹配特定位置后的设置)

location部分用于匹配网页位置(比如,根目录“/”,“/images”,等等),server是对应一个域名进行的配置,而location是在一个域名下对更精细的路径进行配置

语法:

语法: location[=|~|~*|^~|@]/uri/{...}
配置块: server
location会尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择
location{}块中的配置来处理用户请求。当然,匹配方式是多样的,下面介绍location的匹配
规则。

location表达式类型:

~ 表示执行一个正则匹配,区分大小写;
~* 表示执行一个正则匹配,不区分大小写;
^~ 表示普通字符匹配。使用前缀匹配。如果匹配成功,则不再匹配其他location;
= 进行普通字符精确匹配。也就是完全匹配;
@ 它定义一个命名的 location,使用在内部定向时,例如 error_page, try_files

优先级:
(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径)

等号类型(=)的优先级最高。一旦匹配成功,则不再查找其他匹配项。
前缀普通匹配(^~)优先级次之。不支持正则表达式。使用前缀匹配,如果有多个location匹配的话,则使用表达式最长的那个。
正则表达式类型(~ ~*)的优先级次之。一旦匹配成功,则不再查找其他匹配项。
常规字符串匹配,如果有多个location匹配的话,则使用表达式最长的那个。

root(文件路径的定义)和alias(以别名方式设置资源路径)

以root方式设置资源路径
语法:

语法: root path;
配置块: http、server、location、if

以别名方式设置资源路径
语法:

语法: alias path;
配置块: location
alias也是用来设置文件资源路径的,它与root的不同点主要在于如何解读紧跟location后面的uri参数

注意: location中使用root指令和alias指令的意义不同

  • root,相当于追加在root目录后面 。比如访问的是 xxx/test=>/www/test
  • alias,相当于对location中的uri进行替换,比如访问的是 xxx/test,想要访问到/www/test就必须设置 alias /www/test

2.2 nginx内置变量说明

NginxApacheLighttpd等其他 Web 服务器的配置记法不太相同,Nginx的配置文件使用语法的就是一门微型的编程语言。可以类似写程序一般编写配置文件,可操作性很大。既然是编程语言,一般也就少不了“变量”这种东西。
所有的Nginx变量在Nginx配置文件中引用时都须带上$前缀在Nginx配置中,变量只能存放一种类型的值,有且也只存在一种类型,那就是字符串类型
使用 set 配置指令对变量 $a 进行了赋值操作
set $hello "hello world" ;
Nginx变量一旦创建,其变量名的可见范围就是整个Nginx配置,甚至可以跨越不同虚拟主机的server配置块
Nginx变量名的可见范围虽然是整个配置,但每个请求都有所有变量的独立副本,或者说都有各变量用来存放值的容器的独立副本,彼此互不干扰
Nginx内置变量存放在ngx_http_core_module模块中
image.png

3 常用模块配置或者实践案例

3.1 IP和目录权限访问控制配置

IP访问控制模块用来对特定IP的进行访问控制。默认是允许所有ip访问,若部分允许需定义deny all
allow

语法: allow address | CIDR | unix: | all;
默认值: —
区块: http, server, location, limit_except
允许某个ip或者一个ip段访问

deny

语法: deny address | CIDR | unix: | all;
默认值: —
区块: http, server, location, limit_except

allow、deny实例

  • 对IP的访问的控制

    location / {
        deny 192.168.1.1; # 拒绝IP访问 
        allow 192.168.1.0/24; # 允许IP段访问
        allow 47.98.147.49; # 允许IP访问
        deny all; # 启用IP控制
    }
  • 对目录文件的访问
    比如可以限制某些目录下的某些文件的访问,具体可以自己组合。

    # 禁止访问所有目录下的 sql|log|txt|jar|sh|py 后缀的文件,
    location ~.*\.(sql|log|txt|jar|war|sh|py|php) {
        deny all;
    }

3.2 限流模块

场景:抢购的场景,下载限速下就会有涉及运用
限流:主要是当访问量达到一个限制量的时候可以选择以服务器为主要,而选择对用户访问请求的量做限制,对于超出限制的用户请求会采取丢弃或者延迟处理等方式处理,来保证更多用户来访问处理。

比如:某一服务器正常在高峰期上能支持的访问量是1w,但是突然某一时刻在访问量上突然暴增一下子超过3w,5w则可能会导致服务器宕机,这个时候我们就可以通过设置最大的访问如1分钟访问8000次。

也可以防止攻击(对同一个ip每秒访问多少次)如:30min/次

原理:令牌桶算法

对应模块:

  • ngx_http_limit_conn_module限制连接数
  • ngx_http_limit_reg_module限制请求频率

3.2.1 ngx_http_limit_req_module限制请求频率

https://tengine.taobao.org/ng...
基本示例:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
    # 使用$binary_remote_addr【这是一个二进制的信息记录 $remote_addr(非二进制的)】(nginx本身存在的,保存客户端的ip地址)变量,可以将每条状态记录的大小减少到64个字节,这样1M的内存可以保存大约1万6千个64字节的记录。如果限制域的存储空间耗尽了,对于后续所有请求,服务器都会返回 503 (Service Temporarily Unavailable)错误。
    # zone=one:10m 表示设置了名为“one”,大小为10兆字节,也可以理解为设置的限流名为one
    # rate=10r/s 的意思是允许1秒钟不超过10个请求。速度可以设置为每秒处理请求数和每分钟处理请求数,其值必须是整数。如果请求频录不到每秒1次,你可以设置每分钟几次(r/m)。比如每秒半次就是30r/m。
    
    limit_req_log_level notice;
    # 如果你说希望的日志级别,当访问因为频录过高拒绝或延迟处理请求时可以记下相应级别的日志。延迟记录的日志级别比拒绝的低一个级别;比如,如果设置“limit_req_log_level notice",延迟的日志就是info级别。
    # 语法:limit_req_log_level info | notice | warn | error;
    # 默认值:limit_req_log_level error;
    # 上下文:http、server、location
    # 版本要求0.8.18+
    
    ...
    server {
        ...
        location /search /{ 
            
            limit_req zone=one burst=5;
            # 使用了上方名字为”one"的限流。
            # burst=5 允许超过频率限制的请求数不多于5个,假设1、2、3、4秒请求为每秒9个,那么第5秒内请求15个是允许的,反之,如果第一秒内请求15个,会
将5个请求放到第二秒,第二秒内超过10的请求直接503,类似多秒内平均速率限制。
这里多出来的5个请求是被放到10m的哪个缓存中,等待下一秒的处理。
            # nodelay 超过的请求不被延迟处理,设置后15个请求在1秒内处理。这里回有5个超出,超出的5个会直接503。写法:limit_req zone=one burst=5 nodelay;
        }
    }
}

3.2.2 ngx_http_limit_conn_module限制请求连接数

https://tengine.taobao.org/ng...
基本示例:

http {
   limit_conn_zone $binary_remote_addr zone=addr:10m; 
   # 区域名称为addr,大小为10m,键值是客户端IP。
   # 用于限制每个已定义键的连接数,特别是来自单个IP地址的连接数。并不是所有的连接都被计算在内。只有当服务器处理了一个请求,并且整个请求头已经被读取时,才会计算连接。
   # 语法: limit_conn_zone $binary_remote_addr zone=addr:10m;
   # 默认值: none
   # 上下文:http
   # 如果限制域的存储空间耗尽了,对于后续所有请求,服务器都会返回 503 (Service Temporarily Unavailable)错误。

   ...
   server {
        ...
        location /download/{ 
            
            limit_conn addr 1;
            # 限制每个IP只能发起1个连接。(addr 要跟 limit_conn_zone 的变量对应)
        }
    }
}

下载限速设置

http {
    # ....
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    # ....
    server {
        location / {
            # root html;
            autoindex on;
            # 是限制每个IP只能发起1个连接 (addr 要跟 limit_conn_zone 的变量对应)
            limit_conn addr 1;
            limit_rate 10k; #限速为 10KB/秒
            root /redis_2004;
            # index index.html index.htm;
        }
    }
}

测试下载文件
image.png

3.2.3 IP黑白名单

连接限制跟请求限制,会对所有的ip进行限制,我们不希望自己的测试的ip,或者搜索引擎蜘蛛受到限制。

根据上面的速率限制的问题:

limit_req_zone $binary_remote_addr zone=testz:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

目前的情况是针对于所有的请求均会有做限制,而在业务中可能会存在这对于某一些ip是不做限制的需求;在nginx的配置中并不能直接对于变量编辑相应的逻辑计算因此我们不得不需要换一个方式。

3.2.3.1 ngx_http_geo_module

http://tengine.taobao.org/nginx_docs/cn/docs/http/ngx_http_geo_module.html
ngx_http_geo_module模块创建变量,并根据客户端IP地址对变量赋值.

语法: geo [$address] $variable { ... }
默认值: —
上下文: http

其实这个操作就有点类似于枚举变量,会根据我们传递的参数然后匹配并返回相应的参数。
基础体验:

http {
    // ..
    geo $whiteIpList {
        default 1;
        192.168.169.139 0;# 如果访问IP=192.168.169.139,下方$whiteIpList就返回0
        192.168.169.160 0;
        include ip/whiteIp.conf;# 可以设置ip列表
    }
    server {
        // ..
        location /geo {
            return 200 '$whiteIpList';
            # 访问http://xxxx/geo =》浏览器页面输出1或0
        }
    }
}

3.2.3.2 ngx_http_map_module

ngx_http_map_module可以根据我们传递的参数。

语法: map string $variable { ... }
默认值: —
上下文: http

使用方面和geo的方式类似.需要值得注意的是

limit_req_zone $binary_remote_addr zone=testz:10m rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

如果binary_remote_addr为空则限制失败。
操作测试:

map $whiteIpList $limit_ip {
    0 "";
    1 $remote_addr;
}

image.png

最后配合geo实现白名单的操作

http {
    // ..
    limit_req_zone $limit_ip zone=testz:10m rate=1r/s;
    limit_conn_zone $limit_ip zone=addr:10m;
    
    // ..
    geo $whiteIpList {
        default 1;
        192.168.169.139 0;# 如果访问IP=192.168.169.139,下方$whiteIpList就返回0
        include ip/whiteIp.conf;# 可以设置ip列表
    }
    
    map $whiteIpList $limit_ip {
        0 "";
        1 $binary_remote_addr;
    }
    
    server {
        // ..
        location / {
            root html;
            limit_conn addr 1;
            limit_rate 10k; #限速为 100KB/秒
            index index.html index.htm;
            
            # 这里192.168.169.139和whiteIp.conf中的都可以是白名单;如果是这些IP,geo返回的都是0,0在map中对应的值为"",所以上方的limit_req_zone和limit_conn_zone中设置的$limit_ip对应的值就是"",限制等功能无用,就实现了白名单功能了。
        }
    }
}

3.3 rewrite模块(ngx_http_rewrite_module)

rewrite的主要功能是实现URL地址的重定向。Nginxrewrite功能需要PCRE软件的支持,即通过perl兼容正则表达式语句进行规则匹配的。默认参数编译nginx就会支持rewrite的模块,但是也必须要PCRE的支持。

使用场景:

1、可以调整用户浏览的URL,看起来更规范,合乎开发及产品人员的需求。
2、为了让搜索引擎搜录网站内容及用户体验更好,企业会将动态URL地址伪装成静态地址提供服务。
3、网址换新域名后,让旧的访问跳转到新的域名上。例如,访问京东的360buy.com会跳转到jd.com
4、根据特殊变量、目录、客户端的信息进行URL调整等。

3.3.1 if指令

# 语法:if(condition){…}
# 上下文:server,location
# 该指令用于检查一个条件是否符合,如果条件符合,则执行大括号内的语句。if指令不支持嵌套,不支持多个条件&&和||处理。
# 其中,condition中可以包含的判断标识如下
# ~为区分大小写匹配
# ~*为不区分大小写匹配
# -f和!-f用来判断是否存在文件
# -d和!-d用来判断是否存在目录
# -e和!-e用来判断是否存在文件或目录
# -x和!-x用来判断文件是否可执行
if ($http_user_agent~*(mobile|nokia|iphone|ipad|android|samsung|htc|blackberry)) {
    rewrite ^(.*) http://peter.23673.com$1 permanent;
}

3.3.2 return指令

# 语法:return code [text]
# return code URL;
# return URL;
# 上下文:server,location,if
# 该指令用于结束规则的执行并返回状态吗给客户端。
# 状态码包括:
# 204(No Content)、
# 400(Bad Request)、402(PaymentRequired)、403(Forbidden)、404(Not Found)、405(Method Not Allowed)、406(Not Acceptable)、408(Request Timeout)、410(Gone)、411(Length Required)、413(Request Entity Too Large)、416(Requested Range Not Satisfiable)、
# 500(Internal Server Error)、501(Not Implemented)、502(Bad Gateway)、503(Service Unavailable)和504(Gateway Timeout)。

# 例如,示例,如果访问的URL以.sh .bash 结尾,返回状态码403
location ~ .*\.(sh|bash)?$ {
    return 403;
}

3.3.3 Rewrite语法

# 语法:rewrite regex replacement [flag];
# 默认值:—
# 上下文:server, location, if
# rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到replacement,结尾是flag标记。
#     正则:perl兼容正则表达式语句进行规则匹配
#     替代内容:将正则匹配的内容替换成replacement
#     flag标记:rewrite支持的flag标记[last...]

3.3.4 last、break、redirect、permane标记说明


last 
# 本条规则匹配完成后,继续向下匹配新的location URI规则。
# 结束当前的请求处理,用替换后的URI重新匹配location;
#   可理解为重写(rewrite)后,发起了一个新请求,进入server模块,匹配locaton;
# 如果重新匹配循环的次数超过10次,nginx会返回500错误;

break 
# 本条规则匹配完成即终止,不再匹配后面的任何规则
#   结束当前的请求处理,使用当前资源,不在执行location里余下的语句

redirect
# 返回302临时重定向,浏览器地址会显示跳转后的URL地址。

permane
    返回301永久重定向, 地址栏显示重定向后的url,爬虫更新url

4 图形化压测工具:jmeter在nginx中使用

4.1 下载安装

这个工具是需要jdk 1.8 以上。
自行百度下载。
下载后双击 apache-jmeter-5.3binjmeter.bat 即可运行

4.2 实现类似ab这种功能

测试计划->right click ->add ->线程(用户)->线程组
线程组->right click ->add ->取样器->http请求
http请求->right click ->add->监听器->查看结果树

http请求->HTTP请求栏目中填写路径(http://xxxx)
线程组->线程属性下->线程数填写(表示几个客户端发起请求)和Ramp-Up填写(0表示并发)
查看结果树->点击上方”三角形“启动

查看原文

赞 0 收藏 0 评论 0

haokeed 发布了文章 · 10月25日

网路的几种IO模型以及Nginx基本原理

1 网络IO模型

1.1 网络IO基本概念理解

IO分别表示输入(input)和输出(output)。它描述的是计算机的数据流动的过程,因此IO第一大特征是有数据的流动;那么对于IO的整个过程大体上分为2个部分,第一个部分为IO的调用,第二个过程为IO的执行。IO的调用指的就是系统调用,IO的执行指的是在内核中相关数据的处理过程,这个过程是由操作系统完成的,与程序员无关。

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程,目前支持I/O多路复用的系统调用有selectpollepoll,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符(socket),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

描述符(socket)在windows中可以叫做句柄。我们可以理解成一个文件对应的ID。IO其实就是对

1.2 对同步 异步 阻塞 非阻塞在网络中的理解

可以先看以前写的文章:对同步 异步 阻塞 非阻塞在网络中的理解

阻塞IO:请求进程一直等待IO准备就绪。
非阻塞IO:请求进程不会等待IO准备就绪。
同步IO操作:导致请求进程阻塞,直到IO操作完成。
异步IO操作:不导致请求进程阻塞。

举个小例子来理解阻塞,非阻塞,同步和异步的关系,我们知道编写一个程序可以有多个函数,每一个函数的执行都是相互独立的;但是,对于一个程序的执行过程,每一个函数都是必须的,那么如果我们需要等待一个函数的执行结束然后返回一个结果(比如接口调用),那么我们说该函数的调用是阻塞的,对于至少有一个函数调用阻塞的程序,在执行的过程中,必定存在阻塞的一个过程,那么我们就说该程序的执行是同步的,对于异步自然就是所有的函数执行过程都是非阻塞的。

这里的程序就是一次完整的IO,一个函数为IO在执行过程中的一个独立的小片段。

我们知道在Linux操作系统中内存分为内核空间用户空间,而所有的IO操作都得获得内核的支持,但是由于用户态的进程无法直接进行内核的IO操作,所以内核空间提供了系统调用,使得处于用户态的进程可以间接执行IO操作,IO调用的目的是将进程的内部数据迁移到外部即输出,或将外部数据迁移到进程内部即输入。而在这里讨论的数据通常是socket进程内部的数据。

1.3 五种IO模型

基本原理

image.png
在上图中,每一个客户端会与服务端建立一次socket连接,而服务端获取连接后,对于所有的数据的读取都得经过操作系统的内核,通过系统调用内核将数据复制到用户进程的缓冲区,然后才完成客户端的进程与客户端的交互。那么根据系统调用的方式的不同分为阻塞和非阻塞,根据系统处理应用进程的方式不同分为同步和异步。

模型1:阻塞式IO

image.png
每一次客户端产生的socket连接实际上是一个文件描述符fd,而每一个用户进程读取的实际上也是一个个文件描述符fd,在该时期的系统调用函数会等待网络请求的数据的到达和数据从内核空间复制到用户进程空间,也就是说,无论是第一阶段的IO调用还是第二阶段的IO执行都会阻塞,那么就像图中所画的一样,对于多个客户端连接,只能开辟多个线程来处理。

模型2:非阻塞IO模型

image.png
对于阻塞IO模型来说最大的问题就体现在阻塞2字上,那么为了解决这个问题,系统的内核因此发生了改变。在内核中socket支持了非阻塞状态。既然这个socket是不阻塞的了,那么就可以使用一个进程处理客户端的连接,该进程内部写一个死循环,不断的询问每一个连接的网络数据是否已经到达。此时轮询发生在用户空间,但是该进程依然需要自己处理所有的连接,所以该时期为同步非阻塞IO时期,也即为NIO。

模型3:IO多路复用

在非阻塞IO模型中,虽然解决了IO调用阻塞的问题,但是产生了新的问题,如果现在有1万个连接,那么用户线程会调用1万次的系统调用read来进行处理,在用户空间这种开销太大,那么现在需要解决这个问题,思路就是让用户进程减少系统调用,但是用户自己是实现不了的,所以这就导致了内核发生了进一步变化。在内核空间中帮助用户进程遍历所有的文件描述符,将数据准备好的文件描述符返回给用户进程。该方式是同步阻塞IO,因为在第一阶段的IO调用会阻塞进程。

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程,目前支持I/O多路复用的系统调用有selectpollepoll
,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符(socket),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读
写操作。

select/poll

image.png
为了让内核帮助用户进程完成文件描述符的遍历,内核增加了系统调用select/poll(select与poll本质上没有什么不同,就是poll减少了文件描述符的个数限制),现在用户进程只需要调用select系统调用函数,并且将文件描述符全部传递给select就可以让内核帮助用户进程完成所有的查询,然后将数据准备好的文件描述符再返回给用户进程,最后用户进程依次调用其他系统调用函数完成IO的执行过程。

epoll

在select实现的多路复用中依然存在一些问题。

1、用户进程需要传递所有的文件描述符,然后内核将数据准备好的文件描述符再次传递回去,这种数据的拷贝降低了IO的速度。
2、内核依然会执行复杂度为O(n)的主动遍历操作。

对于第一个问题,提出了一个共享空间的概念,这个空间为用户进程和内核进程所共享,并且提供了mmap系统调用,实现用户空间和内核空间到共享空间的映射,这样用户进程就可以将1万个文件描述符写到共享空间中的红黑树上,然后内核将准备就绪的文件描述符写入共享空间的链表中,而用户进程发现链表中有数据了就直接读取然后调用read执行IO即可。

对于第二个问题,内核引入了事件驱动机制(类似于中断),不再主动遍历所有的文件描述符,而是通过事件驱动的方式主动通知内核该文件描述符的数据准备完毕了,然后内核就将其写入链表中即可。
image.png
对于epoll来说在第一阶段的epoll_wait依然是阻塞的,故也是同步阻塞式IO。

模型4:信号驱动式IO

在IO执行的数据准备阶段,不会阻塞用户进程。当用户进程需要等待数据的时候,会向内核发送一个信号,告诉内核需要数据,然后用户进程就继续做别的事情去了,而当内核中的数据准备好之后,内核立马发给用户进程一个信号,用户进程收到信号之后,立马调用recvfrom,去查收数据。该IO模型使用的较少。
image.png

模型5:异步IO(AIO)

应用进程通过 aio_read 告知内核启动某个操作,并且在整个操作完成之后再通知应用进程,包括把数据从内核空间拷贝到用户空间。信号驱动 IO 是内核通知我们何时可以启动一个 IO 操作,而异步 IO 模型是由内核通知我们 IO 操作何时完成。是真正意义上的无阻塞的IO操作,但是目前只有windows支持AIO,linux内核暂时不支持。
image.png

总结

前四种模型的主要区别于第一阶段,因为他们的第二阶段都是一样的:在数据从内核拷贝到应用进程的缓冲区期间,进程都会阻塞。相反,异步 IO 模型在这两个阶段都不会阻塞,从而不同于其他四种模型。
image.png

直接内存与零拷贝

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中农定义的内存区域。直接内存申请空间耗费更高的性能,直接内存IO读写的性能要优于普通的堆内存,对于java程序来说,系统内核读取堆类的对象需要根据代码段计算其偏移量来获取对象地址,效率较慢,不太适合网络IO的场景,对于直接内存来说更加适合IO操作,内核读取存放在直接内存中的对象较为方便,因为其地址就是裸露的进程虚拟地址,不需要jvm翻译。那么就可以使用mmap开辟一块直接内存mapbuffer和内核空间共享,并且该直接内存可以直接映射到磁盘上的文件,这样就可以通过调用本地的put而不用调用系统调用write就可以将数据直接写入磁盘,RandomAccessFile类就是通过开辟mapbuffer实现的读写磁盘。

以消息队列Kafka来说,有生产者和消费者,对于生产者,从网络发来一个消息msg并且被拷贝到内核缓冲区,该消息通过Kafka调用recvfrom将内核中的msg读到队列中,然后加上消息头head,再将该消息写入磁盘。如果没有mmap的话,就会调用一个write系统调用将该消息写入内核缓冲区,然后内核将该消息再写入磁盘。在此过程中出现一次80中断和2次拷贝。但实际上Kafka使用的是mmap开辟了直接内存到磁盘的映射,直接使用put将消息写入磁盘。实际上也是通过内核访问该共享区域将该消息写入的磁盘。同时在Kafka中有一个概念叫segment,一般为1G大小。它会充分利用磁盘的顺序性,只追加数据,不修改数据。而mmap会直接开辟1G的直接内存,并且直接与segment形成映射关系,在segment满了的时候再开辟一个新的segment,清空直接内存然后在与新的segment形成映射关系。
clipboard.png

零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽。

在Kafka的消费者读取数据的时候,如果当前消费者想读取的数据是不是当前直接内存所映射的segment怎么办?如果没有零拷贝的话,进程会先去调用read读取,然后数据会从磁盘被拷贝到内核,然后内核再拷贝到Kafka队列,进程再调用write将数据拷贝到内核缓冲区,最后再发送给消费者。实际上可以发现,数据没有必要读到Kafka队列,直接读到内核的缓冲区的时候发送给消费者就行了。实际上,linux内核中有一个系统调用就是实现了这种方式读取数据——sendfile,它有2个参数,一个是infd(读取数据的文件描述符),一个是outfd(客户端的socket文件描述符).消费者只需调用该函数,告诉它需要读取那个文件就可以不经过Kafka直接将数据读到内核,然后由内核写到消费者进程的缓冲区中。
clipboard.png

2 nginx原理了解

2.1 什么是nginx

Nginx 是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器;同时也是一个IMAP、POP3、SMTP代理服务器; Nginx 可以作为一个HTTP服务器进行网站的发布处理,另外 Nginx 可以作为反向代理进行负载均衡的实现。

2.1.1 nginx的三个主要应用场景

1、静态资源服务(通过本地文件系统提供服务)
2、缓存、负载均衡服务器
3、API服务(OpenResty)
image.png

2.2 为什么选择nginx?

  1. 更快
    这表现在两个方面:一方面,在正常情况下,单次请求会得到更快的响应;另一方面, 在高峰期(如有数以万计的并发请求), Nginx 可以比其他Web服务器更快地响应请求。
  2. 高扩展性
    Nginx 的设计极具扩展性,它完全是由多个不同功能、不同层次、不同类型且耦合度极低的模块组成。因此,当对某一个模块修复 Bug 或进行升级时,可以专注于模块自身,无须在意其他。
    而且在HTTP模块中,还设计了 HTTP 过滤器模块:一个正常的 HTTP 模块在处理完请求后,会有一串 HTTP 过滤器模块对请求的结果进行再处理。这样,当我们开发一个新的 HTTP 模块时,不但可以使用诸如 HTTP 核心模块、events模块、log模块 等不同层次或者不同类型的模块,还可以原封不动地复用大量已有的 HTTP 过滤器模块。
    这种低耦合度的优秀设计,造就了 Nginx 庞大的第三方模块,当然,公开的第三方模块也如官方发布的模块一样容易使用。
    Nginx 的模块都是嵌入到二进制文件中执行的,无论官方发布的模块还是第三方模块都是如此。这使得第三方模块一样具备极其优秀的性能,充分利用 Nginx的高并发特性,因此,许多高流量的网站都倾向于开发符合自己业务特性的定制模块。
  3. 高可靠性
    高可靠性是我们选择 Nginx 的最基本条件,因为 Nginx 的可靠性是大家有目共睹的,很多家高流量网站都在核心服务器上大规模使用 Nginx 。 Nginx 的高可靠性来自于其核心框架代码的优秀设计、模块设计的简单性;另外,官方提供的常用模块都非常稳定,每个 worker 进程相对独立,master进程在1个worker进程出错时可以快速“拉起”新的 worker 子进程提供服务。
  4. 低内存消耗
    一般情况下,10000个非活跃的HTTP Keep-Alive连接在 Nginx 中仅消耗2.5MB的内存,这是 Nginx 支持高并发连接的基础。
  5. 单机支持10万以上的并发连接
    这是一个非常重要的特性!随着互联网的迅猛发展和互联网用户数量的成倍增长,各大公司、网站都需要应付海量并发请求,一个能够在峰值期顶住10万以上并发请求的Server, 无疑会得到大家的青睐。理论上,Nginx支持的并发连接上限取决于内存,当然,能够及时地处理更多的并发请求,是与业务特点紧密相关的。
  6. 热部署
    master管理进程与 worker 工作进程的分离设计,使得 worker 能够提供热部署功能,即可以在7×24小时不间断服务的前提下,升级 worker 的可执行文件。当然,它也支持不停止服务就更新配置项、更换日志文件等功能。
  7. 最自由的BSD许可协议
    这是 worker 可以快速发展的强大动力。BSD许可协议不只是允许用户免费使用 worker ,它还允许用户在自己的项目中直接使用或修改 worker 源码,然后发布。这吸引了无数开发者继续为 worker 贡献自己的智慧。

2.3 Nginx相关的开源版本

1、阿里巴巴Tengine Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。
2、openresty OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

2.4 Nginx高效的原因及原理解析

2.4.1 服务器的网络服务模型

web服务器和客户端是一对多的关系,Web服务器必须有能力同时为多个客户端提供服务。一般来说完成并行处理请求工作有三种方式:

2.4.1.1 单进程阻塞的网络服务器

image.png
说明:

1、创建一个socket,绑定服务器端口(bind),监听端口(listen),在PHP中用stream_socket_server一个函数就能完成上面3个步骤
2、进入while循环,阻塞在accept操作上,等待客户端连接进入。此时程序会进入睡眠状态,直到有新的客户端发起connect到服务器,操作系统会唤
醒此进程。accept函数返回客户端连接的socket
3、利用fread读取客户端socket当中的数据收到数据后服务器程序进行处理然后使用fwrite向客户端发送响应。长连接的服务会持续与客户端交互,
而短连接服务一般收到响应就会close。
缺点: 一次只能处理一个连接,不支持多个长连接同时处理

2.4.1.2 多进程方式

多进程方式指,服务器每当收到一个客户端请求时,就有服务器主进程生成一个子进程出来和客户端建立连接进行交互,直到连接断开该子进程就结束了。
image.png
说明:

前面流程一致就不补充了
1、程序启动后就会创建N个进程。每个子进程进入 Accept,等待新的连接进入。当客户端连接到服务器时,其中一个子进程会被唤醒,开始处理客户端请求,并且不再接受新的TCP连接。
    当连接关闭时,子进程会释放,重新进入 Accept,参与处理新的连接。
    这个模型的优势是完全可以复用进程,不需要太多的上下文切换,比如php-fpm基于此模型来处理解析php.
多进程方式的优点是设计简单,各个子进程相对独立,处理客户端请求时彼此不受干扰;

缺点是操作系统生成一个子进程需要进行内存复制等操作,在资源和时间上会产生一定的开销;当有大量请求时,会导致系统性能下降;
例如:即时聊天程序,一台服务器可能要维持数十万的连接,那么就要启动数十万的进程来维持。这显然不可能

2.4.1.3 多线程方式

多线程方式指每当服务器接收到一个请求后,会由服务器主进程派生出一个线程出来和客户端进行交互。由于操作系统产生出一个线程的开销远远小于一个进程的开销。故多线程方式在很大程度上减轻了Web服务器对系统资源的要求。

缺点:稳定性!假设某个进程突然关闭会造成整个进程中的所有线程都崩溃。

基于上面的模式我们发现单个进程每次只能通过每次(accept)处理单个请求,有没有办法一次性连接多个请求,需要的时候再处理呢?

2.4.1.4 单进程IO复用方式

image.png
说明:

1、保存所有的socket,通过select模型,监听socket描述符的可读事件
2、Select会在内核空间监听一旦发现socket可读,会从内核空间传递至用户空间,在用户空间通过逻辑判断是服务端socket可读,还是客户端
的socket可读
3、如果是服务端的socket可读,说明有新的客户端建立,将socket保留到监听数组当中
4、如果是客户端的socket可读,说明当前已经可以去读取客户端发送过来的内容了,通过fread读取socket内容,然后fwrite响应给客户端。
优点:性能最好!一个进程或线程处理多个请求,不需要额外开销,性能最好,资源占用最低。

缺点:稳定性!某个进程或线程出错,可能导致大量请求无法处理,甚至导致整个服务宕机,单进程对于大量任务处理乏力。

2.4.1.5 多进程的master-workerIO复用方式

2.4.1.5.1 nginx的基本架构

image.png

1.Nginx启动后,会产生一个主进程,主进程执行一系列的工作后会产生一个或者多个工作进程
2.在客户端请求动态站点的过程中,Nginx服务器还涉及和后端服务器的通信。Nginx将接收到的Web请求通过代理转发到后端服务器,由后端服务器进行
数据处理和组织;
3.Nginx为了提高对请求的响应效率,降低网络压力,采用了缓存机制,将历史应答数据缓存到本地。保障对缓存文件的快速访问

master进程主要用来管理 worker 进程,具体包括以下主要功能:

(1)接收来自外界的信号。
(2)处理配置文件读取。
(3)创建,绑定和关闭套接字
(4)启动,终止和维护配置的工作(worker)进程数
(5)当woker进程退出后(异常情况下),会自动重新启动新的woker进程

image.png
worker进程的主要任务是完成具体的任务逻辑。其主要关注点是与客户端或后端真实服务器(此时 worker作为中间代理)之间的数据可读/可写等I/O交互事件。

(1)接收客户端请求;
(2)将请求一次送入各个功能模块进行过滤处理;
(3)与后端服务器通信,接收后端服务器处理结果;
(4)数据缓存;
(5)响应客户端请求;

资料来源:
[1] https://segmentfault.com/a/11...
[2] 六星教育

查看原文

赞 0 收藏 0 评论 0

haokeed 关注了专栏 · 10月19日

springboot 最佳实践

SpringBoot技术的势、道、术分析与实践。

关注 554

haokeed 发布了文章 · 10月19日

docker基础使用命令手册和操作(附带说明)

安装基本配置

安装

方法1-脚本安装

在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,CentOS
系统上可以使用这套脚本安装:

# 在实际过程中get-docker.sh文件可能curl很慢。可以浏览器直接打开get.docker.com,将期复制下来,再创建get-docker.sh,将复制的内容保存进去。
$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun

方法2-普通命令行安装

# 1、更新update到最新的版本
~ yum update
# 2、卸载老版本docker
~ yum remove docker docker-common docker-selinux docker-engine
# 3、安装需要的软件包
~ yum install -y yum-utils device-mapper-persistent-data lvm2
# 4、设置yum源
~ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 5、查看docker版本
~ yum list docker-ce --showduplicates|sort -r
# 6、安装docker
~ yum install docker-ce-18.03.1.ce -y
# 7、启动docker
~ systemctl start docker
# 8、加入开机自启
~ systemctl enable docker
# 9、配置国内镜像,个人觉得国内镜像用专用的阿里云比较好。可看下方阿里云镜像源获取
~ vi /etc/docker/daemon.json
{
"registry-mirrors": ["https://cr.console.aliyun.com/"]
}
~ ps -aux | grep docker

设置阿里云镜像

多说无益,直接截图
image.png

dockerhub账户关联

这个有点类似于composer的仓库地址就是用来存储我们开会人员的镜像地址的;
官网:https://hub.docker.com
自己注册一个账号,这个过程我就跳过。

~ docker login
Username: haokeed(对应dockerhub的账号)
Password:
Login Successded

# 异常!
# 在上面执行的代码可能出现如下异常 rror response from daemon: Get https://registry-1.docker.io/v2/library/hello-world/manifest
# 这个问题的异常主要是
# 1. 解析ip不可用
# 2. 是因为dns域名没有解析的原因;
# 解决:
~ yum install bind-utils
~ dig @114.114.114.114 registry-1.docker.io

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.el7 <<>> @114.114.114.114 registry-1.docker.io
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57840
;; flags: qr rd ra; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;registry-1.docker.io. IN A
;; ANSWER SECTION:
registry-1.docker.io. 33 IN A 34.205.88.205
registry-1.docker.io. 33 IN A 35.169.133.189
registry-1.docker.io. 33 IN A 34.202.247.184
registry-1.docker.io. 33 IN A 3.224.11.4
registry-1.docker.io. 33 IN A 3.82.34.201
registry-1.docker.io. 33 IN A 34.195.36.112
registry-1.docker.io. 33 IN A 3.221.133.86
registry-1.docker.io. 33 IN A 3.224.75.242
;; Query time: 26 msec
;; SERVER: 114.114.114.114#53(114.114.114.114)
;; WHEN: 三 4月 22 04:41:31 CST 2020
;; MSG SIZE rcvd: 177

~ vi /etc/hosts
34.205.88.205 registry-1.docker.io
~ systemctl restart docker

登入成功之后,使用push命令就可以上传镜像,如果不指定tag则本地的镜像都会上传。如下实例:
注意docker hub的push要求是如下格式

docker push 注册名/镜像名:tag

可以在https://hub.docker.com的自己...,自己上去看吧。

镜像和容器最常用命令

镜像

# 查看现有的镜像列表
docker images

# 下载镜像
# 这里下载了nginx
docker pull nginx


# 删除镜像
## 指定删除
docker rmi 镜像id(image id)
## 强制删除
docker rmi -f 镜像id(image id)
## 删除所有镜像
docker rmi $(docker images -q)

# 根据dockerfile构建镜像
# redis5:构建的镜像名称
# ".":构建的dockerfile文件目录在当前目录下
docker build -t redis5 .

# 执行tag修改
# 将原来构建镜像名为redis5的修改为haokeed/redis5,并打上tag为v1
docker tag redis5 haokeed/redis5:v1

# 上传docker镜像到dockerhub。执行该命令时,个人建议现在dockerhub上建立对应的仓库。建立好后可以在任何其他地方docker pull haokeed/redis5:v1
docker push haokeed/redis5:v1

使用commit命令创建本地镜像
使用镜像创建并运行一个容器,实际上是在父镜像的基础上创建一个可读写的文件层级。我们在容器里所做的修改(包括安装新的应用程序、更改系统配置),都发生在这个层级上面。下面的一下列命令展示redis5镜
像上创建和运行一个容器,并在该容器上完成redis的安装或者做其他的软件也可以创建一个文件写入 redis5 test docker 等操作。

[root@localhost /]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d82c67522964 redis5:v1 "bash" 20 minutes ago Up 20 minutes redis5
[root@localhost /]# docker exec -it redis5 bash
[root@d82c67522964 /]# ls
bin dump.rdb home lib64 media opt root sbin sys usr
dev etc lib lost+found mnt proc run srv tmp var
[root@d82c67522964 /]# echo "hello docker commit" >> hellodocker.txt
[root@d82c67522964 /]# ls
bin etc lib media proc sbin tmp
dev hellodocker.txt lib64 mnt root srv usr
dump.rdb home lost+found opt run sys var
[root@d82c67522964 /]# cat hellodocker.txt
hello docker commit
[root@d82c67522964 /]# exit
exit
[root@localhost /]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d82c67522964 redis5:v1 "bash" 21 minutes ago Up 21 minutes redis5

# 上面对容器里面的就是在容器里面添加了一个文件,通常情况下的操作都是在容器中安装了应用等的修改。

# 重点来了
# commit命令后,返回一个长的字符串,这个字符串就是刚创建镜像的完整id,这个id也可以通过docker ps -l -q(用于获取最近创建的容器id) 命令得到。 
# 命令中-m参数是描述我们此创建images的信息(--author 参数用来指定
作者信息)
[root@localhost /]# docker commit -m="test commit" d82c67522964 haokeed/redis5:v2
sha256:20036b73669961f3d8fc91a423db91bf24a7d6390edfb5295f5d7ec105b2e405

root@localhost /]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
haokeed/redis5 v2 20036b736699 6 seconds ago 734MB
redis5 v1 42b05d59fa02 13 hours ago 734MB

# 查看对某个镜像修改的历史
[root@localhost /]# docker history haokeed/redis5:v2
IMAGE CREATED CREATED BY SIZE COMMENT
20036b736699 4 minutes ago bash 212B test commit
42b05d59fa02 13 hours ago 734MB Imported from -

容器

容器类型

  • 交互型容器:运行在前台,通常会制定有交互的控制台,可以给容器输入,也可以得到容器的输出。创建该日期的终端被关闭,在容器内部使用exit命令或者调用docker stop 、docker kill命令后,容器会停止运行。
  • 后台型容器:运行在后台,创建启动之后就与终端无关。即便终端关闭了,改后台容器也依然存在,只有执行docker stop或者docker kill命令

时候才能够使容器变成停止状态

# 启动容器
docker start 容器名

# 查看容器详细信息
docker inspect redis5

# 查看容器日志
docker logs 容器id或名称

# 删除容器
docker rm 容器名

# 列出正在运行的容器
docker ps

# 显示所有的容器,包括未运行的
docker ps -a

# 停止所容器
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)

# 基于镜像与构建容器
docker run -itd --name 容器名称(自定义) 镜像名称:标识

# docker run 执行语法和参数说明
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
OPTIONS说明:
    -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
    -d: 后台运行容器,并返回容器ID;
    -i: 以交互模式运行容器,通常与 -t 同时使用;
    -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
    --name="nginx-lb": 为容器指定一个名称;
    --dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
    --dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
    -h "mars": 指定容器的hostname;
    -e username="ritchie": 设置环境变量;
    --env-file=[]: 从指定文件读入环境变量;
    --cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行;
    -m :设置容器使用内存最大值;
    --net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
    --link=[]: 添加链接到另一个容器;
    --expose=[]: 开放一个端口或一组端口;
    
# 进入容器
# 这里表示进入容器名称为reids5的容器,并且以bash命令行模式进入
docker exec -it redis5 bash

导出与导入

容器的导出与导入

docker的流行与它对容器的易分享和易移植密不可分。用户不仅可以把容器提交到公共服务器上,还可以将容器导出到本地文件系统中。同样我们也可以讲导出的
容器重新导入到docker运行环境中。docker的导入和导出分别由import命令和export命令完成。

# 导出:将容器名称为redis5的导出,导出的文件为当前目录下的redis.tar,导出的是单个的tar文件
# docker export命令会把容器的文件系统以tar包的格式导出到标准输出,我们将其重定位到目标文件name.tar。将容器保存到本地文件也算是其持久化方式的一种。 
docker export redis5 > redis5.tar

# 容器的导入:我们可以使用docker import命令,导人一个本地的tar包作为镜像。
# 这里将redis5.tar文件导出为redis5:v1镜像。然后就可以通过docker run直接构建镜像了。
docker import redis5.tar redis5:v1

镜像的导出与导入

# 将指定镜像保存成 tar 归档文件
# docker save [OPTIONS] IMAGE [IMAGE...]
# options说明
# -o :输出到的文件。

# haokeed/redis5:v1导出为redis5.tar文件
docker save -o redis5.tar haokeed/redis5:v1

# 将redis5.tar导入镜像
docker load<redis5.tar

save/load(image级)和export/import(container级)的区别

exportimport导出的是一个容器的快照, 不是镜像本身, 也就是说没有 layer。
你的 dockerfile 里的 workdir, entrypoint 之类的所有东西都会丢失,commit 过的话也会丢失。快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也更大。而saveload保存的是镜像(image),载入的是镜像包。

  • docker save 保存的是镜像(image),docker export 保存的是容器(container);
  • docker load 用来载入镜像包,docker import 用来载入容器包,但两者都会恢复为镜像;
  • docker load 不能对载入的镜像重命名,而 docker import 可以为镜像指定新名称。

image.png

Dockerfile语法

dockerfile文件样例:

FROM centos
RUN groupadd -r redis && useradd -r -g redis redis
RUN yum update -y ; \
yum -y install gcc automake autoconf libtool make wget epel-release gcc-c++;
RUN mkdir -p /usr/src/redis; \
wget https://github.com/antirez/redis/archive/5.0.7.tar.gz; \
tar -zxvf 5.0.7.tar.gz -C /usr/src/redis; \
rm -rf 5.0.7.tar.gz; \
cd /usr/src/redis/redis-5.0.7 && make && make PREFIX=/usr/local/redis install;
# 构建命令
docker build -t 名称 .

# 参数说明
Usage:docker build [OPTIONS] PATH | URL | -
OPTIONS:
    -t ,--tag list  #构建后的镜像名称
    -f, --file string #指定Dockerfiile文件位置

# 示例:
1 docker build .
2 docker build -t redis:v1 .
3 docker build -t redis:v2 -f /path/Dockerfile /path
# 一般常用第2种方式构建,我们在构建时都会切换到Dockerfile文件的目录下进行构建,所以不需要指定-f参

指令参数说明

  • FROM:
    指定待扩展的父级镜像。除了注释外,在文件开头必须是一一个FROM指令, 接下来白指令便在这个父级镜像的环境中运行,直到遇到下一一个FROM指令。通过添加多个FROM命令可以在同一个Dockerfile文件中创建多个镜像。
    所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个redis镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
  • RUN:
    RUN 指令是用来执行命令行命令的。
    格式:

    RUN \<command\> (类似/bin/sh -cshell格式)
    RUN ["executable", "param1", "param2"] (exec格式)
    
    # 类似shell脚本风格的:
    FROM alpine
    RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
    RUN apk add gcc g++ libc-dev wget vim openssl-dev make linux-headers
    RUN mkdir -p /usr/src/redis
    RUN cd /usr/src/redis
    RUN wget -O redis-4.0.11.tar.gz "http://download.redis.io/releases/redis-4.0.11.tar.gz"
    RUN tar -xzf redis-4.0.11.tar.gz -C /usr/src/redis
    RUN cd /usr/src/redis/redis-4.0.11 && make && make PREFIX=/usr/local/redis install
  • MAINTAINER:
    用来声明创建的镜像的作者信息。
    格式为MAINTAINER user_name user_email,指定维护者信息。
  • EXPOSE:
    用来指明容器内进程对外开放的端口,多个端口之间使用空替隔力。运行容器通过参数-p(大写)即可将EXPOSE里所指定的端口映射到主机上另外的随机端口,容器;或主机就可以通过映射后的端口与此容器通信。同时,我们也可以通过-p (小写)参数将dockerfile中EXPOSE中没有列出的端口设置成公开的。
  • ADD:
    向新镜像中添加文件,这个文件可以是一个主机文件,也可以是一个网络文件, 也可以是一个文件夹。
  • ENV:
    设置容器运行的环境变量。在运行容器的时候,通过-e参数可以修改这个环境变量值,也可以添加新的环境变量。
    格式为:EVN key valueENV key=value key2=value ...。用于指定环境变量,这些环境变量,后续可以被RUN指令使用,容器运行起来之后,也可以在容器中获取这些环境变量。
    例如:
    ENV word hello
    RUN echo $word
  • ARG:
    格式:ARG 参数名[=默认值]
    和ENV一样都是设置环境变量;不同的是ARG锁这是的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要用ARG来保存密码之类的信息,因为docker history还是可以看到所有的值的。
    该默认值可以在构建命令docker build中用--build-arg 参数名=参数值来覆盖。
    注意:1.13之前的版本,--build-arg中的参数名,必须在dockerfile中ARG定义过。1.13版本之后,不是必须,但是会报警告。
  • ADD:
    向新镜像中添加文件,这个文件可以是一个主机文件,也可以是一一个网络文件,也 可以是一个文件夹。
从构建环境的上下文或远程URL复制文件至镜像。如果是从一个本地路径添加一个归档文件,那么它会被自动解压。由于ADD指令涵盖的功能相当广泛,一般最好还是使用相对简单的COPY指令来复制构建环境上下。

文的文件和目录,并用RUN指令配合curl或wget来下载远程资源(这样还可以在同一个指令中处理和删除下载文件)。

ADD命令的第一个参数用来指定源文件 (夹),它可 以是文件路径、文件夹的路径或网络文件的URL地址。需要特别注意的是,如果是文件路径或文件夹路径,它必须是相对Dockerfile所在目录的相对路径。如果是一个文件URL,在创建镜像时,会先下载下来,然后再添加到镜像里去。第二个参数是文件需要放置在目标镜像的位置。如果源文件是gzip、bzip2或者xz形式的压缩文件,Docker会先解压缩,然后将文件添加到镜像的指定位置。如果源文件是一一个通过URL指定的网络压缩文件,则不会解压。

  • COPY:
    用于从构建环境的上下文复制文件至镜像。它有两种形式,COPY src dest 以及COPY[ "src","dest"],两者皆从上下文中的sre复制文件或目录至容器内的dest。如果路径中有空格的话,那么必须使用JSON数组的格式。通配符可以用来指定多个文件或目录。请注意,你不能指定上下文以外的srC路径(例如./another di/mnyfile是不管用的)。

    COPY ./conf/nginx.conf /conf
  • CMD与ENTRYPOINT:
    用来设置启动容器时默认运行的命令,当然我们也可以通过指定的方式去覆盖。
    我们在dockerfile中的内容如果是下面的情况:

    FROM alpine
    CMD ["ls", "-a", "-l"]

    如果我们改一下docker run的命令让其执行其他命令
    docker run 构建的镜像名 echo "hello docker"
    可以看到默认的被替换了
    ENTRYPOINT:与CMD类似,它也是用来指定容器启动的时候默认运行的命令

    FROM alpine
    ENTRYPOINT ["ehco ENTRYPOINT"]

    ENTRYPOINT和CMD的区别在于运行容器时添加在镜像名之后的参数,对ENTRYPOINT是拼接,而对于CMD命令则是覆盖。幸运的是,我们在运行容器的时候可以通过 --entrypoint来覆盖dockerfile中的命令

  • EXPOSE:
    用来指明容器内进程对外开放的端口,多个端口之间使用空替隔力。运行容器通过参数-p(大写)即可将EXPOSE里所指定的端口映射到主机上另外的随机端口,容器或主机就可以通过映射后的端口与此容器通信。

    EXPOSE 6379

    同时,我们也可以通过-p (小写)参数将dockerfile中EXPOSE中没有列出的端口设置成公开的。
    注意,expose只是开放端口,运行的时候还需要利用-p绑定到宿主机对应的端口上才能提供给外部网络用。

    # -p 8000:80 是告诉宿主机用户访问8000端口转发至容器80上
    docker run -d -p 8000:80 nginx
  • VOLUME:
    定义匿名券,这个放到下方的"数据卷和数据容器管理数据"中特别说明,请看下方。

比较完全的dockerfile文件内容

FROM centos
RUN mkdir /data && mkdir /conf &&
RUN groupadd -r nginx && useradd -r -g nginx nginx
#修改时区
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
ARG PHP_VERSION=7.2
#添加centos源(先下载wget)
COPY ./epel-7.repo /etc/yum.repos.d/epel.repo
#COPY
#RUN yum install -y wget
#RUN wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/Centos-7.repo
RUN yum update -y \
&& yum clean all \
&& yum makecache \
&& yum -y install gcc gcc-c++ autoconf automake make zlib zlib-devel net-tools openssl* pcre* wget \
&& yum clean all && rm -rf /var/cache/yum/*
#声明匿名卷
VOLUME /data
RUN cd /data \
&& wget http://nginx.org/download/nginx-1.14.1.tar.gz \
&& tar -zxvf nginx-1.14.1.tar.gz \
&& cd nginx-1.14.1 \
&& ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx \
&& make && make install && rm -rf /data/nginx-1.14.1.tar.gz && rm -rf /data/nginx-1.14.1
COPY ./conf/nginx.conf /conf
#全局使用nginx,软链接
RUN ln -s /usr/local/nginx/sbin/* /usr/local/sbin
#进入容器时默认打开的目录
WORKDIR /conf
#声明端口
EXPOSE 80
#容器启动的时候执行,在docker run过程当中是会被其他指令替代
#CMD ["/usr/local/nginx/sbin/nginx","-c","/conf/nginx.conf","-g","daemon off;"]
#执行一条指令
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-c","/conf/nginx.conf","-g","daemon off;"]

注意问题:
镜像过大:我们的系统可能会因为我们的安装的疏忽造成镜像过大的问题。

系统一般在进行yum安装之后会有一个cache的记录
yum命令会下载我们需要安装的软件然后进行缓存到默认的目录(当然这个目录我们是可以指定的)
centos下执行yum install xxx后。
系统会从yum源下载rpm,将rpm放置到缓存目录下: /var/cache/yum/(yum源的不同则下载后存在的路径也有所不同,通常都是存放在packages目录下,如 /var/cache/yum/*/packages。)
需要注意就是yum安装之后这些缓存不一定全部删除了有可能就会有些没有删除
需要做的是手动把这些东西删除

挂载(数据卷和数据容器管理数据)

因为考虑到一个问题就是容器它可能会存在误删的情况,而如果我们的数据都在容器中这就会成为一个很大的问题;docker就提出了一个解决方案,就是共享到宿主机;如docker run -v 挂载数据卷
格式:-v 容器目录 或 -v 本地(宿主机)目录:容器目录

# 根据下面的那个命令我们可以让本地的 /root/docker/redis5v/data 与docker容器中的 /data 地址进行绑定了;就可以在宿主机的/root/docker/redis5v/data和容器中的/data目录中镜像文件和数据同步了。
[root@localhost 02]# docker run -itd --name redis5v -v /root/docker/redis5v/data:/data redis5v

在实际中,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库这类需要保存动态数据的应用,其数据文件应该保存于券(volume)中。在dockerfile中,我们可以实现指定某些目录挂载为匿名券,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。所以,在dockerfile中有了VOLUME参数。

VOLUME:定义匿名券
格式:VOLUME [“路径1”,“路径2”...]VOLUME 路径

# dockerfile中
VOLUME /data

# 这里的/data目录就会在运行时自动挂载为匿名券,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容易存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:
docker run -d -v mydata:/data xxxx
# 在这个命令中,就使用了mydata这个命名券挂载到了/data这个位置,替代了dockerfile中定义的匿名券的挂载配置。如果没有-v填写,docker会创建一个文件记录。

docker inspect -f {{.Mounts}} 容器名称
# 查看匿名挂载在宿主机哪个目录中。

docker网络ip 与 EXPOSE

对于docker里面的容器来说(也就是系统),从系统来看是会独立的网络,同时也会有独立的ip,而如果说外部想要访问的话就需要暴露端口;可以通过dockerfile中EXPOSE参数或者docker run -p的方式。
image.png

docker安装后,默认会创建下面三种网络类型:

[root@localhost ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
0a95d66b232f        bridge              bridge              local
8a7a74a01de4        host                host                local
c54cd01dbebf        none                null                local

可在docker run 或其他地方通过--network bridge指定网络类型。

1. bridge:桥接网络

默认情况下启动的Docker容器,都是使用 bridge,Docker安装时创建的桥接网络,每次Docker容器重启时,会按照顺序获取对应的IP地址,这个就导致重启下,Docker的IP地址就变。
image.png
从上面的网络模型可以看出,容器从原理上是可以与宿主机乃至外界的其他机器通信的。同一宿主机上,容器之间都是连接掉docker0这个网桥上的,它可以作 为虚拟交换机使容器可以相互通信。然而, 由于宿主机的IP地址与容器的IP地址均不在同一个网段,不足以使宿主机以外的网络主动发现容器的存在。为了使外界可以方位容器中的进程,docker采用了端口绑定的方式,也就是通过iptables的NAT, 将宿主机上的端口端口流量转发到容器内的端口上。

在宿主机上,可以通过iptables -t nat -L -n,查到一条DNAT规则:
bridge模式的容器与外界通信时,必定会占用宿主机上的端口,从而与宿主机竞争端口资源,对宿主机端口的管理会是一个比较大的问题。同时,由于容 器与外界通信是基于三层上iptables NAT,性能和效率上的损耗是可以预见的。

2.none:无指定网络

无指定网络 使用 --network=none ,docker 容器就不会分配局域网的IP

3. host:互通

主机网络 使用 --network=host,此时,Docker 容器的网络会附属在主机上,两者是互通的。 例如,在容器中运行一个Web服务,监听8080端口,则主 机的8080端口就会自动映射到容器中。
image.png
弊端:

1)最明显的就是容器不再拥有隔离、独立的网络栈。容器会与宿主机竞争网络栈的使用,并且容器的崩溃就可能导致宿主机崩溃,在生产环境中,这种 问题可能是不被允许的。
2)容器内部将不再拥有所有的端口资源,因为一些端口已经被宿主机服务、bridge模式的容器端口绑定等其他服务占用掉了

4. 指定自定义网络

因为默认的网络不能制定固定的地址,所以我们将创建自定义网络,并指定网段:192.168.1.0/24 并命名为mynetwork,指令
如下:

# 创建自定义网络,并设置网段为:192.160.1.0/24,名称为:mynetwork
~ docker network create --subnet=192.160.1.0/24 mynetwork
# 将网络配置分配给即将运行的容器,使用的网段名称为:mynetwork,指定容器ip为192.168.1.10
~ docker run -itd --network=mynetwork --ip 192.168.1.10 --name dockerName imagesName
# 查看当前的所有网络
~ docker network ls
# 移除自定义网络,移除的名称为mynetwork
~ docker network rm mynetwork

!注意:这个网络段不要和宿主机的网络端冲突,不然会容易对宿主机产生影响

查看原文

赞 0 收藏 0 评论 0

haokeed 发布了文章 · 10月17日

mysql主从理解配置和主从备份与恢复实操

二进制日志(bin-log)的说明——主从的前提

bin-log的相关字段和文件说明

查看日志日否开启等信息情况

  • log_bin:是否开启
  • log_bin_basename:二进制日志文件名字(实际情况下会在后面加上.索引来命名文件)
  • log_bin_index:记录日志文件的索引文件

image.png
查看准作为主从中的主库的信息。

  • File:当前使用的二进制日志的文件和索引
  • Possition:当前日志运行到那个点了

image.png
对应的二进制日志文件和索引文件的关系。
image.png
注意:每次mysql重启都会重新开启一个mysql-bin.*日志文件。长期不重启mysql-bin文件会过大。还是请dba来处理文件过大问题吧/(ㄒoㄒ)/~~。

如何查看文件内容

方法1:mysqlbinlog命令查看

mysqlbinlog 命令,以用户可视的方式展示出二进制日志中的内容。同时,也可以将其中的内容读取出来,供其他MySQL实用程序使用。

为了方便,我们将mysql其他相关命令加入到系统命令行中。不需要或已经懂的直接跳过。
image.png

image.png

查看日志
[root@MiWiFi-R3P-srv data]# mysqlbinlog ./mysql-bin.000006
image.png

方法2:show binlog events命令查看

show binlog events 命令查看某个binlog日志内容。

命令:

mysql> show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];

选项解析:

  IN 'log_name' 指定要查询的binlog文件名(不指定就是第一个binlog文件)
  FROM pos 指定从哪个pos起始点开始查起(不指定就是从整个文件首个pos点开始算)
  LIMIT [offset,] 偏移量(不指定就是0)
  row_count 查询总条数(不指定就是所有行)

实例:

A.查询第一个(最早)的binlog日志:
  mysql> show binlog events\G; 

B.指定查询 mysql-bin.000021 这个文件:
  mysql> show binlog events in 'mysql-bin.000021'\G;

C.指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起:
  mysql> show binlog events in 'mysql-bin.000021' from 8224\G;

D.指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起,查询10条
  mysql> show binlog events in 'mysql-bin.000021' from 8224 limit 10\G;

E.指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起,偏移2行,查询10条
  mysql> show binlog events in 'mysql-bin.000021' from 8224 limit 2,10\G;

实操截图

[root@MiWiFi-R3P-srv data]# mysqlbinlog mysql-bin.000001 --start-position 3078 --stop-position 4413 | mysql -u root -p     

image.png
image.png

主从配置

master(1)-slave(1)实现过程

1.开启主库的binlog

在主库的mysql中:
image.png

2.主库中配置复制账号和账号的权限

主库中创建复制账号和分配账号权限
image.png
在从库中连接测试
image.png

4.配置从库配置文件

在从库中配置
image.png
在实际过程中,很可能不会进行整个mysql从节点的设置。一般情况下,会设置某个库或者某个表进行主从配置。在这个情况下,选择下面的配置参数到从库配置文件中就可以了
image.png

5.启动复制

在从库中执行:

# 参数说明
master_host:# master ip
master_port: # master 端口号
master_user: # 连接主库的用户名
master_password: # 连接主库的密码
master_log_file: # 启动是开始读取的二进制日志
master_log_pos: # 打算从主库开始复制的日志节点,对应日志文件中的position

# 执行语句
change master to
master_host='192.168.153.129',
master_port=3306,
master_user='slave_user',
master_password='slave_pwd',
master_log_file='mysql-bin.000001',
master_log_pos=0;

# 启动从节点
start slave;

# 停止从节点
stop slave;

# 清除slave信息(配置有误的时候用)
reset slave;

image.png
验证从库是否启动正常(在从库中执行):

show slave status\G

# 主要看字段信息
slave_io_running:表示异步连接主库进行binlog同步的IO是否正常
slave_sql_running:表示中继日志文件同步到磁盘是否正常

Last_IO_Errno: 对应slave_io_running错误的错误码
Last_IO_Error: 对应slave_io_running错误的错误信息
Last_SQL_Errno: 对应slave_sql_running错误的错误码
Last_SQL_Error: 对应slave_sql_running错误的错误信息

image.png

关于slave_io_running和slave_sql_running图片解释(来自于某视频)
image.png

相关报错:

Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.

data目录中有auto.cnf文件 这个和原来的克隆过来冲突,将该文件删除重启就好了

6.检查结果

在主库和从库分别执行查看对应的库和表数据。
image.png
image.png
在主库中执行sql操作语句

insert into user(name) value('李四');

在从库中进行检验

select * from user;

image.png
检验成功!!!

MySQL主要复制启动配置

MySOL安装配置的时候,已经介绍了几个启动时的常用参数,其中包括MASTER HOST、MASTER PORT、MASTER_USER、MASTER PASSWORD、MASTER_LOG_FILE 和MASTER LOG POS。这几个参数需要在从服务器上配置,下面介绍几个常用的启动选项,如log-slave-updates、master-connect-retry、read-only 和slave-skip-errors等。

(1)log-slave-updates log-slave updates 参数主要用来配置从服务器的更新是否写入二进制日志,该选项默认是不打开的,如果这个从服务器同时也作为其他服务器的主服务器,搭建一个链式的复制,那么就需要开启这个选项,这样他的从服务器才能获取它的二进制日志进行同步操作。更加详细的请看本文最下面的原理解释。

(2)master-connect-retry master-connect-retry参数是用来设置在和主服务器连接丢失的时候,重试的时间间隔,默认是60秒。

(3)read-only read-only是用来限制普通用户对从数据库的更新操作,以确保从数据库的安全性,不过如果是超级用户依然可以对从数据库进行更新操作。如果主数据库创建了一个普通用户,在默认情况下,该用户是可以更新从数据库中的数据的,如果使用read-only选项启动从数据库以后,该用户对从数据库的更新会提示错误。使用read-only选项启动语法如下。

(4)slave-skip-errors 在复制的过程中,从服务器可能会执行BINLOG中的错误的SQL语句,此时如果不忽略错误,从服务器将会停止复制进程,等待用户处理错误。这种错误如果不能及时发现,将会对应用或者备份产生影响。slave-skip-errors的作用就是用来定义复制过程中从服务器可以自动跳过的错误号,设置该参数后,MySQL会自动跳过所配置的一系列错误,直接执行后面的SQL语句,该参数可以定义多个错误号,如果设置成all,则表示跳过所有的错误,具体语法如下:

vi /etc/my. cnf
slave-skip-errors=1007,1051,1062

如果从数据库主要是作为主数据库的备份,那么就不应该使用这个启动参数,设置不当,很可能造成主从数据库的数据不同步。如果从数据库仅仅是为了分担主数据库的查询压力,并且对数据的完整性要求不是很严格,那么这个选项可以减轻数据库管理源维护从数据库的工作量。

主从数据库备份和恢复(高级篇)

在实际项目中,数据的主从同步配置大多情况下都是项目上线或运行一段时间后开始考虑主从同步配置(大厂可能会直接上吧ԾㅂԾ)。数据库开始可能二进制日志(bin-log)都没开启。这个情况下,开启主从同步,就需要从库与主库的数据保持一致,才能开启主从同步。

另外一些情况就是单库数据提供了一个比较大的系统运行,发现数据库的瓶颈了,需要开始做读写分离来解决了,但是有不想要停止数据库来备份,那就需要深入考虑和使用如何做主从数据备份和恢复了。

有几种办法来初始化备库或者从其他服务器克隆数据大备库。包括从主库复制数据、从另一台备库克隆数据等等。主要的思路就是:

  1. 在某个时间点的主库的数据快照。
  2. 二进制日志文件(其中必须拥有快照时间点到现在的二进制日志)。
  3. 数据快照的时间点该时间点对应的二进制日志文件中的偏移量,我们把这两个值成为日志文件坐标。通过这两个值可以确定二进制日志的位置。可以通过SHOW MASTER STATUS命令来获取这些值。

数据库的备份可按照两种方式进行划分:备份方式划分和运行方式划分。

  • 备份方式划分

    • 逻辑备份:逻辑备份又称为导出,比如使用navicate工具的导出功能

      *优点:灵活性高,版本要求不算特别高,小数据量OK,可以拿到各种地方用*
      *缺点:慢,大数据量不适合。*
    • 物理备份:物理备份可以称为暴力备份,就是直接把整个mysql的data文件进行copy备份。

      *优点:快,简单粗暴*
      *缺点:迁移到新的地方,需要重新配置一些新的信息,还有版本要和新的地方保持一致*
  • 运行方式划分

    • 冷备份:数据库停止服务运行或者停止写操作,然后在这个基础上进行备份。

      *优点:数据安全,不会出现这边写,这边又备份,最后导致数据不一致*
      *缺点:需要停止服务,不适合希望不停止服务的项目*
    • 热备份:在服务不停机的情况下进行在线备份。

      *优点:服务照常运行,不需要停止服务器*
      *缺点:技术要求高,备份中需要用到binlog*
      

在实际的工作中,我们一般对于数据库优化或者进行架构的时候主数据库往往是会有数据的,而从一开始就进行主从的基本很少。 所以这并非我们实际的情况。

备份的抉择

  • 一般来说凡是有用的数据库文件尽量不删除,都进行备份。
  • 根据备份周期(根据服务器的负载、评估、上一次的备份恢复的操作时间来决定),备份保留时间等实际情况来指定备份计划。

接下来就主要讲解几种备份与恢复方式。

mysqldump数据备份与恢复(逻辑备份)

mysql自带逻辑备份命令工具:mysqldump。它位于mysql项目文件的bin目录下。

主从备份与恢复步骤

  1. 在主库中开启binlog功能
    同主从复制,见上方
  2. 在主库中配置复制账号和账号的权限

    create user 'dump_user'@'%' identified by 'dump_pwd';
    -- 如果是用mysqldump 来做备份、那么备份用户的相关权限如下
    grant select on *.* to dump_user@'%'; 
    grant show view on *.* to dump_user@'%'; 
    grant trigger on *.* to dump_user@'%';
    -- 如果要产生一份一致的备份 mysqldump 要有lock tables 权限。这里要用到mysqldump就必须加这个权限
    grant lock tables on *.* to dump_user@'%'; 
    grant process on *.* to dump_user@'%'; 
  3. 主库停止写操作
    这里我们采用主库中对数据库全局加锁的方式。

    -- 主库中加锁
    -- 理论上备份账户有锁权限,我觉得这里其实就不用该命令了。不过本人没试过(*^_^*)。
    mysql >  flush tables with read lock;

    image.png

  4. 从库备份
    使用mysqldump进行从库备份

    # 可以用这个命令多试试,就能找出账户需要什么权限和其他错误了。
    [root@MiWiFi-R3-srv data]# mysqldump -h 192.168.31.162 -u dump_user -p laravel-shop
    [root@MiWiFi-R3-srv data]# mysqldump -h 192.168.31.162 -u dump_user -p laravel-shop > /home/laravel-shop.sql

    image.png

  5. 主库释放锁

    -- 在主库中的mysql执行解锁
    -- 步骤3 不需要的话,这里也就不需要了
    mysql >  unlick tables; // 主库中解锁
  6. 从库进行主从的slave配置。
    请看上方知识主从配置内容。
  7. 从库进行与主库的slave的"change master to..."设
    请看上方知识主从配置内容。
  8. 恢复数据
    在从库中执行

    [root@MiWiFi-R3-srv data]# mysql -f -u root -p laravel-shop < /home/laravel-shop.sql 

    image.png

  9. 开启同步

    -- 在从库中执行
    mysql > start slave;

mydumper数据备份与恢复(逻辑备份)(第三方)

mydumper是一个针对MySQL和drizzle的高性能多线程的备份和恢复工具。此工具的开发人员分别来自MySQL、facebook、skysql公司、目前已经有一些大型产品业务测试并使用了该工具。我们在恢复数据库时也可使用myloader工具。 Mydumper的主要特性包括:

· 采用轻量级C语言写的代码。 · 相比于mysqldump,其速度快了近10倍。 · 具有事务性和非事务性表一致的快照(适用于0.2.2+)。 · 可快速进行文件压缩(File compression on-the-fly)。 · 支持导出binlog。 · 可多线程恢复(适用于0.2.1+)。 · 可以用守护进程的工作方式,定时扫描和输出连续的二进制日志。

步骤

  1. 下载安装
    从库中执行

    yum install glib2-devel zlib-devel pcre-devel cmake
    git clone https://github.com/maxbube/mydumper.git
    cd mydumper
    cmake .
    make
    make install
    # 验证安装是否成功
    mydumper -V
    # 参数介绍
    -host,-h:连接的MySQL服务器。 
    -user,-u:用户备份的连接用户。
    -password,-p:用户的密码。 
    -port,-P:连接端口。 
    -socket,-S:连接socket文件。
    -database,-B:需要备份的数据库。 
    -table-list,-T:需要备份的表,用逗号(,)分隔。 
    -outputdir,-o:输出的目录。 
    -build-empty-files,-e:默认无数据则只有表结构文件。 
    -regex,-x:支持正则表达式,如mydumper-regex'(2l(mysqltest)'。 
    -ignore-engines,-i:忽略的存储引擎。 
    -no-schemas,-m:不导出表结构。 
    -long-query-guard:长查询,默认60s。 
    -kill-long-queries,-k:可以设置kill长查询。 
    -verbose,-v:0=silent,1=errors,2=warmings,3=info,默认是2。 
    -binlogs,-b:导出binlog。
    -daemon,-D:启用守护进程模式。 
    -snapshot-interval,-I:dump快照间隔时间,默认60s。 
    -logfile,-L:mysaqldumper的目志输出,一般在Daemon模式下使用。
  2. 导出数据
    从库中执行

    [root@MiWiFi-R3-srv home]# mydumper -h 192.168.31.162 -u root -p adminadmin123 -B laravel-shop -o /home/laravel-shop-mydumper
    # 这里可以直接-p 填写密码。
    # -B 表示对应要导出的主库中的哪个数据库,如果不填写,表示主库中的所有数据库。
    # -o 表示导出到从服务器中的哪个目录,注意是目录不是文件;导出后可以进入该目录中查看,是一些.sql文件的。

    image.png

  3. 导入(恢复)数据

    [root@MiWiFi-R3-srv home]# myloader -u root -p adminadmin123 -B laravel-shop -d /home/laravel-shop-mydumper/

    image.png

与mysqldump效率对比的执行窍门

time 执行命令,可以查看命令执行完成所需要的时间来对比mydumper和mysqldump的执行效率
如:time mydumper -h 192.168.31.162 -u root -p adminadmin123 -B laravel-shop -o /home/laravel-shop-mydumper可得出任务执行花费的时间。

xtralBackup(热备份)

热备份的方式也是直接复制数据物理文件,和冷备份一样,但热备份可以不停机直接复制,一般用于7×24小时不间断的重要核心业务。MySQL社区版的热备份工具ImnoDB Hot Backup是付费的,只能试用30天,只有购买企业版才可以得到永久使用权。Percona公司发布了一个xtrabackup热备份工具,和官方付费版的功能一样,支持在线热备份(备份时不影响数据读写),是商业备份工具InnoDBHot Backup的一个很好的替代品。下面具体介绍一下这个软件的使用方法。

xtrabackup是Percona公司的开源项目,用以实现类似ImnoDB官方的热备份工具ImmoDB Hot Backup的功能,它能非常快速地备份与恢复MySQL数据库。xtrabackup中包含两个工具:

· xtrabackup是用于热备份InnoDB及XtraDB表中数据的工具,不能备份其他类型的表,也不能备份数据表结构。 · innobackupex是将xtrabackup进行封装的perl脚本,它提供了备份MyISAM表的能力。由于innobackupex的功能更为全面完善,所以一般选择innobackupex来进行备份。

对于热备份实现解释

  1. innobackupex启动后,会先fork一个进程,用于启动xtrabackup,然后等待xtrabackup备份ibd数据文件;
  2. xtrabackup在备份innoDB数据是,有2种线程:redo拷贝线程和ibd数据拷贝线程。xtrabackup进程开始执行后,会启动一个redo拷贝的线程,用于从最新的checkpoint点开始顺序拷贝redo.log;再启动ibd数据拷贝线程,进行拷贝ibd数据。这里是先启动redo拷贝线程的。在此阶段,innobackupex进行处于等待状态(等待文件被创建)
  3. xtrabackup拷贝完成ibd数据文件后,会通知innobackupex(通过创建文件),同时xtrabackup进入等待状态(redo线程依旧在拷贝redo.log)
  4. innobackupex收到xtrabackup通知后哦,执行FLUSH TABLES WITH READ LOCK(FTWRL),取得一致性位点,然后开始备份非InnoDB文件(如frm、MYD、MYI、CSV、opt、par等格式的文件),在拷贝非InnoDB文件的过程当中,数据库处于全局只读状态。
  5. 当innobackup拷贝完所有的非InnoDB文件后,会通知xtrabackup,通知完成后,进入等待状态;
  6. xtrabackup收到innobackupex备份完成的通知后,会停止redo拷贝线程,然后通知innobackupex,redo.log文件拷贝完成;
  7. innobackupex收到redo.log备份完成后,就进行解锁操作,执行:UNLOCK TABLES;
  8. 最后innbackupex和xtrabackup进程各自释放资源,写备份元数据信息等,innobackupex等xtrabackup子进程结束后退出。

步骤

  1. 下载安装
    这里特别需要注意,xtralBackup不同的版本对应不同的mysql版本,所以要到官网查看对应的版本。
    可以查看官网,我这里用的是mysql5.7,所以用2.4版本的。地址为:https://www.percona.com/doc/p...,请选择对应的linux操作系统进行安装。
    image.png

    # 下载
    wget https://www.percona.com/downloads/XtraBackup/Percona-XtraBackup-2.4.4/binary/redhat/7/x86_64/percona-xtrabackup-24-2.4.4-1.el7.x86_64.rpm
    # 安装
    yum localinstall percona-xtrabackup-24-2.4.4-1.el7.x86_64.rpm -y # 本地安装
    Xtrabackup中主要包含两个工具:
    xtrabackup:是用于热备innodb,xtradb表中数据的工具,不能备份其他类型的表,也不能备份数据表结构;
    innobackupex:是将xtrabackup进行封装的perl脚本,提供了备份myisam表的能力。
    常用选项:
       --host     指定主机
       --user     指定用户名
       --password    指定密码
       --port     指定端口
       --databases     指定数据库
       --incremental    创建增量备份
       --incremental-basedir   指定包含完全备份的目录
       --incremental-dir      指定包含增量备份的目录
       --apply-log        对备份进行预处理操作
     一般情况下,在备份完成后,数据尚且不能用于恢复操作,因为备份的数据中可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务。因此,此时数据文件仍处理不一致状态。“准备”的主要作用正是通过回滚未提交的事务及同步已经提交的事务至数据文件也使得数据文件处于一致性状态。
       --redo-only      不回滚未提交事务
       --copy-back     恢复备份目录
  2. 主库中执行备份操作
    针对数据库data下的文件

    [root@MiWiFi-R3-srv home]# innobackupex --defaults-file=/etc/my.cnf --user=root --password=adminadmin123 --backup /home/laravel-shop-innobackupex
    # 主库中执行,设置对应的mysql的配置文件为/etc/my.cnf。
    # 设置数据库用户名为root,设置密码为adminadmin123。
    # 设置备份到的目录为/home/laravel-shop-innobackupex。
    # 就可以在/home/laravel-shop-innobackupex看到备份过来的文件了。
  3. 把备份的主库中文件传递给从库

    [root@MiWiFi-R3-srv home]# scp -r /home/laravel-shop-innobackupex/ root@192.168.31.207:/home/laravel-shop-innobackupex
     # 主库中执行;把主库下的整个备份文件夹/home/laravel-shop-innobackupex/ 远程传递给从库对应服务器192.168.31.207,以root方式登录传递;传递到的目标目录为/home/laravel-shop-innobackupex
  4. 从库中准备恢复工作

    • 停止从库服务systemctl stop mysql
    • 清空data或合并。这里选择:mv /www/server/data/ /www/server/data_bak;将原来的mysql的data进行迁移备份。
  5. 配置从库的my.cnf - 与主库配置尽量一致
  6. 恢复

    [root@MiWiFi-R3-srv laravel-shop-innobackupex]# innobackupex --defaults-file=/etc/my.cnf --copy-back /home/laravel-shop-innobackupex/2020-10-16_07-41-44
    # 从库执行。执行恢复。设置mysql对应的配置文件,设置需要恢复的源文件文件夹(注意:主库物理备份的时候,备份时会产生带时间的子文件夹,这里要指定到对应的带日期的子文件夹中。)

    image.png

  7. 启动数据库

    chown -R mysql:mysql /www/server/data  # 从库中执行。将原来备份回来的文件重新分配权限。如果不分配执行的时候会报类似pid的错误
    /etc/init.d/mysqld start  # 从库中执行。启动mysql这里可能会报错误。请使用lsof -i:3306找找看进程,有的话杀死就好了。

    一般启动报错

image.png

  1. 验证
    自己查看吧!!!!!睡觉了!

logs-slave-updates的参数说明

logs-slave-updates 参数主要在多主多从的集群架构中开启,否则会导致各从实例无法完整同步集群的全量数据的问题。

多主多从集群架构:
masterA → slaveA
↑ ↓
masterB → slaveB

logs-slave-updatesNormally, a slave does not log to its own binary log any updates that are received from a master server. This option tells the slave to log the updates performed by its SQL thread to its own binary log.

即,正常情况下,一个slave节点是不会将其从master节点同步的数据更新操作记录至自己的二进制日志bin-log中的。

在多主的场景下,各master节点其实又相互作为另一方的slave节点进行着数据的一致性同步操作。例如 masterA 会以slave的角色同步 masterB 上的数据,masterB 也会以slave的角色同步 masterA 上的数据,如果没有开启 logs-slave-updates参数配置,则masterAmasterB 虽然也能保证数据的一致性和完整性,但二者的 bin-log 中都只记录了作用在自身实例上的数据更新操作。

查看原文

赞 0 收藏 0 评论 0

haokeed 发布了文章 · 10月15日

mysql主从理解和配置

二进制日志(bin-log)的说明——主从的前提

bin-log的相关字段和文件说明

查看日志日否开启等信息情况

  • log_bin:是否开启
  • log_bin_basename:二进制日志文件名字(实际情况下会在后面加上.索引来命名文件)
  • log_bin_index:记录日志文件的索引文件

image.png
查看准作为主从中的主库的信息。

  • File:当前使用的二进制日志的文件和索引
  • Possition:当前日志运行到那个点了

image.png
对应的二进制日志文件和索引文件的关系。
image.png
注意:每次mysql重启都会重新开启一个mysql-bin.*日志文件。长期不重启mysql-bin文件会过大。还是请dba来处理文件过大问题吧/(ㄒoㄒ)/~~。

如何查看文件内容

方法1:mysqlbinlog命令查看

mysqlbinlog 命令,以用户可视的方式展示出二进制日志中的内容。同时,也可以将其中的内容读取出来,供其他MySQL实用程序使用。

为了方便,我们将mysql其他相关命令加入到系统命令行中。不需要或已经懂的直接跳过。
image.png

image.png

查看日志
[root@MiWiFi-R3P-srv data]# mysqlbinlog ./mysql-bin.000006
image.png

方法2:show binlog events命令查看

show binlog events 命令查看某个binlog日志内容。

命令:

mysql> show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];

选项解析:

  IN 'log_name' 指定要查询的binlog文件名(不指定就是第一个binlog文件)
  FROM pos 指定从哪个pos起始点开始查起(不指定就是从整个文件首个pos点开始算)
  LIMIT [offset,] 偏移量(不指定就是0)
  row_count 查询总条数(不指定就是所有行)

实例:

A.查询第一个(最早)的binlog日志:
  mysql> show binlog events\G; 

B.指定查询 mysql-bin.000021 这个文件:
  mysql> show binlog events in 'mysql-bin.000021'\G;

C.指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起:
  mysql> show binlog events in 'mysql-bin.000021' from 8224\G;

D.指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起,查询10条
  mysql> show binlog events in 'mysql-bin.000021' from 8224 limit 10\G;

E.指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起,偏移2行,查询10条
  mysql> show binlog events in 'mysql-bin.000021' from 8224 limit 2,10\G;

实操截图

[root@MiWiFi-R3P-srv data]# mysqlbinlog mysql-bin.000001 --start-position 3078 --stop-position 4413 | mysql -u root -p     

image.png
image.png

主从配置

master(1)-slave(1)实现过程

1.开启主库的binlog

在主库的mysql中:
image.png

2.主库中配置复制账号和账号的权限

主库中创建复制账号和分配账号权限
image.png
在从库中连接测试
image.png

4.配置从库配置文件

在从库中配置
image.png

5.启动复制

在从库中执行:

# 参数说明
master_host:# master ip
master_port: # master 端口号
master_user: # 连接主库的用户名
master_password: # 连接主库的密码
master_log_file: # 启动是开始读取的二进制日志
master_log_pos: # 打算从主库开始复制的日志节点,对应日志文件中的position

# 执行语句
change master to
master_host='192.168.153.129',
master_port=3306,
master_user='slave_user',
master_password='slave_pwd',
master_log_file='mysql-bin.000001',
master_log_pos=0;

# 启动从节点
start slave;

# 停止从节点
stop slave;

# 清除slave信息(配置有误的时候用)
reset slave;

image.png
验证从库是否启动正常(在从库中执行):

show slave status\G

# 主要看字段信息
slave_io_running:表示异步连接主库进行binlog同步的IO是否正常
slave_sql_running:表示中继日志文件同步到磁盘是否正常

Last_IO_Errno: 对应slave_io_running错误的错误码
Last_IO_Error: 对应slave_io_running错误的错误信息
Last_SQL_Errno: 对应slave_sql_running错误的错误码
Last_SQL_Error: 对应slave_sql_running错误的错误信息

image.png

关于slave_io_running和slave_sql_running图片解释(来自于某视频)
image.png

相关报错:

Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.

data目录中有auto.cnf文件 这个和原来的克隆过来冲突,将该文件删除重启就好了

6.检查结果

在主库和从库分别执行查看对应的库和表数据。
image.png
image.png
在主库中执行sql操作语句

insert into user(name) value('李四');

在从库中进行检验

select * from user;

image.png
检验成功!!!

logs-slave-updates的参数说明

logs-slave-updates 参数主要在多主多从的集群架构中开启,否则会导致各从实例无法完整同步集群的全量数据的问题。

多主多从集群架构:
masterA → slaveA
↑ ↓
masterB → slaveB

logs-slave-updatesNormally, a slave does not log to its own binary log any updates that are received from a master server. This option tells the slave to log the updates performed by its SQL thread to its own binary log.

即,正常情况下,一个slave节点是不会将其从master节点同步的数据更新操作记录至自己的二进制日志bin-log中的。

在多主的场景下,各master节点其实又相互作为另一方的slave节点进行着数据的一致性同步操作。例如 masterA 会以slave的角色同步 masterB 上的数据,masterB 也会以slave的角色同步 masterA 上的数据,如果没有开启 logs-slave-updates参数配置,则masterAmasterB 虽然也能保证数据的一致性和完整性,但二者的 bin-log 中都只记录了作用在自身实例上的数据更新操作。

查看原文

赞 0 收藏 0 评论 0

haokeed 赞了文章 · 10月15日

这个是真的厉害,高并发场景下的订单和库存处理方案,讲的很详细了!

前言

之前一直有小伙伴私信我问我高并发场景下的订单和库存处理方案,我最近也是因为加班的原因比较忙,就一直没来得及回复。今天好不容易闲了下来想了想不如写篇文章把这些都列出来的,让大家都能学习到,说一千道一万都不如满满的干货来的实在,干货都下面了!

介绍

前提:分布式系统,高并发场景
商品A只有100库存,现在有1000或者更多的用户购买。如何保证库存在高并发的场景下是安全的。
预期结果:1.不超卖 2.不少卖 3.下单响应快 4.用户体验好

下单思路

  1. 下单时生成订单,减库存,同时记录库存流水,在这里需要先进行库存操作再生成订单数据,这样库存修改成功,响应超时的特殊情况也可以通过第四步定时校验库存流水来完成最终一致性。
  2. 支付成功删除库存流水,处理完成删除可以让库存流水数据表数据量少,易于维护。
  3. 未支付取消订单,还库存+删除库存流水
  4. 定时校验库存流水,结合订单状态进行响应处理,保证最终一致性

(退单有单独的库存流水,申请退单插入流水,退单完成删除流水+还库存)

什么时候进行减库存

  • 方案一:加购时减库存。
  • 方案二:确认订单页减库存。
  • 方案三:提交订单时减库存。
  • 方案四:支付时减库存。

分析

  • 方案一:在这个时间内加入购物车并不代表用户一定会购买,如果这个时候处理库存,会导致想购买的用户显示无货。而不想购买的人一直占着库存。显然这种做法是不可取的。唯品会购物车锁库存,但是他们是另一种做法,加入购物车后会有一定时效,超时会从购物车清除。
  • 方案二:确认订单页用户有购买欲望,但是此时没有提交订单,减库存会增加很大的复杂性,而且确认订单页的功能是让用户确认信息,减库存不合理,希望大家对该方案发表一下观点,本人暂时只想到这么多。
  • 方案三:提交订单时减库存。用户选择提交订单,说明用户有强烈的购买欲望。生成订单会有一个支付时效,例如半个小时。超过半个小时后,系统自动取消订单,还库存。
  • 方案四:支付时去减库存。比如:只有100个用户可以支付,900个用户不能支付。用户体验太差,同时生成了900个无效订单数据。

所以综上所述:
选择方案三比较合理。

重复下单问题

  1. 用户点击过快,重复提交。
  2. 网络延时,用户重复提交。
  3. 网络延时高的情况下某些框架自动重试,导致重复请求。
  4. 用户恶意行为。

解决办法

  1. 前端拦截,点击后按钮置灰。
  2. 后台:
    (1)redis 防重复点击,在下单前获取用户token,下单的时候后台系统校验这个 token是否有效,导致的问题是一个用户多个设备不能同时下单。
    //key , 等待获取锁的时间 ,锁的时间
    redis.lock("shop-oms-submit" + token, 1L, 10L);

redis的key用token + 设备编号 一个用户多个设备可以同时下单。

    //key , 等待获取锁的时间 ,锁的时间
    redis.lock("shop-oms-submit" + token + deviceType, 1L, 10L);

(2)防止恶意用户,恶意攻击 : 一分钟调用下单超过50次 ,加入临时黑名单 ,10分钟后才可继续操作,一小时允许一次跨时段弱校验。使用reids的list结构,过期时间一小时

/**
     * @param token
     * @return true 可下单
     */
    public boolean judgeUserToken(String token) {
        //获取用户下单次数 1分钟50次
        String blackUser = "shop-oms-submit-black-" + token;
        if (redis.get(blackUser) != null) {
            return false;
        }
        String keyCount = "shop-oms-submit-count-" + token;
        Long nowSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
        //每一小时清一次key 过期时间1小时
        Long count = redis.rpush(keyCount, String.valueOf(nowSecond), 60 * 60);
        if (count < 50) {
            return true;
        }
        //获取第50次的时间
        List<String> secondString = redis.lrange(keyCount, count - 50, count - 49);
        Long oldSecond = Long.valueOf(secondString.get(0));
        //now > oldSecond + 60 用户可下单
        boolean result = nowSecond.compareTo(oldSecond + 60) > 0;
        if (!result) {
            //触发限制,加入黑名单,过期时间10分钟
            redis.set(blackUser, String.valueOf(nowSecond), 10 * 60);
        }
        return result;
    }

如何安全的减库存

多用户抢购时,如何做到并发安全减库存?

  • 方案1: 数据库操作商品库存采用乐观锁防止超卖:
sql:update sku_stock set stock = stock - num where sku_code = '' and stock - num > 0;

分析
高并发场景下,假设库存只有 1件 ,两个请求同时进来,抢购该商品.
数据库层面会限制只有一个用户扣库存成功。在并发量不是很大的情况下可以这么做。但是如果是秒杀,抢购,瞬时流量很高的话,压力会都到数据库,可能拖垮数据库。

  • 方案2:利用Redis单线程 强制串行处理
/**
     * 缺点并发不高,同时只能一个用户抢占操作,用户体验不好!
     *
     * @param orderSkuAo
     */
    public boolean subtractStock(OrderSkuAo orderSkuAo) {
        String lockKey = "shop-product-stock-subtract" + orderSkuAo.getOrderCode();
        if(redis.get(lockKey)){
            return false;
        }
        try {
            lock.lock(lockKey, 1L, 10L);
            //处理逻辑
        }catch (Exception e){
            LogUtil.error("e=",e);
        }finally {
            lock.unLock(lockKey);
        }
        return true;
    }

分析
利用Redis 分布式锁,强制控制同一个商品处理请求串行化,缺点并发不高 ,处理比较慢,不适合抢购,高并发场景。用户体验差,但是减轻了数据库的压力。

  • 方案3 :redis + mq + mysql 保证库存安全,满足高并发处理,但相对复杂。
     /**
     * 扣库存操作,秒杀的处理方案
     * @param orderCode
     * @param skuCode
     * @param num
     * @return
     */
    public boolean subtractStock(String orderCode,String skuCode, Integer num) {
        String key = "shop-product-stock" + skuCode;
        Object value = redis.get(key);
        if (value == null) {
            //前提 提前将商品库存放入缓存 ,如果缓存不存在,视为没有该商品
            return false;
        }
        //先检查 库存是否充足
        Integer stock = (Integer) value;
        if (stock < num) {
            LogUtil.info("库存不足");
            return false;
        } 
       //不可在这里直接操作数据库减库存,否则导致数据不安全
       //因为此时可能有其他线程已经将redis的key修改了
        //redis 减少库存,然后才能操作数据库
        Long newStock = redis.increment(key, -num.longValue());
        //库存充足
        if (newStock >= 0) {
            LogUtil.info("成功抢购");
            //TODO 真正扣库存操作 可用MQ 进行 redis 和 mysql 的数据同步,减少响应时间
        } else {
            //库存不足,需要增加刚刚减去的库存
            redis.increment(key, num.longValue());
            LogUtil.info("库存不足,并发");
            return false;
        }
        return true;
    }

分析
利用Redis increment 的原子操作,保证库存安全,利用MQ保证高并发响应时间。但是事需要把库存的信息保存到Redis,并保证Redis 和 Mysql 数据同步。缺点是redis宕机后不能下单。
increment 是个原子操作。

综上所述

方案三满足秒杀、高并发抢购等热点商品的处理,真正减扣库存和下单可以异步执行。在并发情况不高,平常商品或者正常购买流程,可以采用方案一数据库乐观锁的处理,或者对方案三进行重新设计,设计成支持单订单多商品即可,但复杂性提高,同时redis和mysql数据一致性需要定期检查。

订单时效问题
超过订单有效时间,订单取消,可利用MQ或其他方案回退库存。

设置定时检查
Spring task 的cron表达式定时任务
MQ消息延时队列

订单与库存涉及的几个重要知识

TCC 模型:Try/Confirm/Cancel:不使用强一致性的处理方案,最终一致性即可,下单减库存,成功后生成订单数据,如果此时由于超时导致库存扣成功但是返回失败,则通过定时任务检查进行数据恢复,如果本条数据执行次数超过某个限制,人工回滚。还库存也是这样。
幂等性:分布式高并发系统如何保证对外接口的幂等性,记录库存流水是实现库存回滚,支持幂等性的一个解决方案,订单号+skuCode为唯一主键(该表修改频次高,少建索引)
乐观锁:where stock + num>0
消息队列:实现分布式事务 和 异步处理(提升响应速度)
redis:限制请求频次,高并发解决方案,提升响应速度
分布式锁:防止重复提交,防止高并发,强制串行化
分布式事务:最终一致性,同步处理(Dubbo)/异步处理(MQ)修改 + 补偿机制

写在最后的话

大家看完有什么不懂的可以在下方留言讨论,也可以私信问我一般看到后我都会回复的。最后觉得文章对你有帮助的话记得点个赞哦,点点关注不迷路,每天都有新鲜的干货分享!

查看原文

赞 14 收藏 10 评论 0

haokeed 赞了文章 · 10月15日

彻底学会element-ui按需引入和纯净主题定制

前言

手上有些项目用的element-ui,刚好有空琢磨一下怎么减小打包文件大小和打包速度方面,为了演示实验,用 vue-cli 生成初始项目,在这仅对 element-ui 主题和组件方面来优化。

vue init webpack vuecli

完整引入

完整地将 ui 和样式引入。

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

在页面简单使用 2 个组件,看看效果。

<el-tabs v-model="activeName" @tab-click="handleClick">
  <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
  <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
  <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
  <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
</el-tabs>

<el-steps :active="2" align-center>
  <el-step title="步骤1" description="这是一段很长很长很长的描述性文字"></el-step>
  <el-step title="步骤2" description="这是一段很长很长很长的描述性文字"></el-step>
  <el-step title="步骤3" description="这是一段很长很长很长的描述性文字"></el-step>
  <el-step title="步骤4" description="这是一段很长很长很长的描述性文字"></el-step
></el-steps>

组件效果

再看一下打包后的资源大小情况npm run build --report

Hash: 40db03677fe41f7369f6
Version: webpack 3.12.0
Time: 20874ms
                                                  Asset       Size  Chunks                    Chunk Names
    static/css/app.cb8131545d15085cee647fe45f1d5561.css     234 kB       1  [emitted]         app
                 static/fonts/element-icons.732389d.ttf      56 kB          [emitted]
               static/js/vendor.a753ce0919c8d42e4488.js     824 kB       0  [emitted]  [big]  vendor
                  static/js/app.8c4c97edfce9c9069ea3.js    3.56 kB       1  [emitted]         app
             static/js/manifest.2ae2e69a05c33dfc65f8.js  857 bytes       2  [emitted]         manifest
                static/fonts/element-icons.535877f.woff    28.2 kB          [emitted]
static/css/app.cb8131545d15085cee647fe45f1d5561.css.map     332 kB          [emitted]
           static/js/vendor.a753ce0919c8d42e4488.js.map    3.26 MB       0  [emitted]         vendor
              static/js/app.8c4c97edfce9c9069ea3.js.map    16.6 kB       1  [emitted]         app
         static/js/manifest.2ae2e69a05c33dfc65f8.js.map    4.97 kB       2  [emitted]         manifest
                                             index.html  506 bytes          [emitted]

发现打包后提取公共模块 static/js/vendor.js824kb

再看一下各个模块占用情况:

各个模块占用情况

发现 elment-ui.common.js 占用最大。所有模块资源总共有 642kb。怎么才能减小打包后的大小呢?很容易就会想到 ui 的引入和样式的引入中,实际我们只使用了三个组件,却整体都被打包了,在这里引入这三个组件即可。

按需引入组件样式

新建一个 element-variables.scss 文件(为什么是 SCSS 文件,后面自定义主题会用到)。

/*icon字体路径变量*/
$--font-path: "~element-ui/lib/theme-chalk/fonts";

/*按需引入用到的组件的scss文件和基础scss文件*/
@import "~element-ui/packages/theme-chalk/src/base.scss";
@import "~element-ui/packages/theme-chalk/src/rate.scss";
@import "~element-ui/packages/theme-chalk/src/button.scss";
@import "~element-ui/packages/theme-chalk/src/row.scss";

按需引入组件

新建一个 element-config.js 文件,将项目用到的 element 组件引入。

import {
  Tabs,
  TabPane,
  Steps,
  Step
} from 'element-ui'

export default {
  install (V) {
    V.use(Tabs)
    V.use(TabPane)
    V.use(Steps)
    V.use(Step)
  }
}

第一次优化后打包分析

将以上 element-variables.scsselement-config.js 引入到 main.js 中。

import ElementUI from '@/assets/js/element-config'
import '@/assets/css/element-variables.scss'

Vue.use(ElementUI)

貌似上面一切都很顺理成章,打包后大小会减小。

Hash: 2ef987c23a5d612e00e1
Version: webpack 3.12.0
Time: 17430ms
                                                  Asset       Size  Chunks                    Chunk Names
    static/css/app.3c70d8d75c176393318b232a345e3f0f.css    38.8 kB       1  [emitted]         app
                 static/fonts/element-icons.732389d.ttf      56 kB          [emitted]
               static/js/vendor.caa5978bb1eb0a15b097.js     824 kB       0  [emitted]  [big]  vendor
                  static/js/app.5ebb19489355acc3167b.js    3.64 kB       1  [emitted]         app
             static/js/manifest.2ae2e69a05c33dfc65f8.js  857 bytes       2  [emitted]         manifest
                static/fonts/element-icons.535877f.woff    28.2 kB          [emitted]
static/css/app.3c70d8d75c176393318b232a345e3f0f.css.map    53.9 kB          [emitted]
           static/js/vendor.caa5978bb1eb0a15b097.js.map    3.26 MB       0  [emitted]         vendor
              static/js/app.5ebb19489355acc3167b.js.map      17 kB       1  [emitted]         app
         static/js/manifest.2ae2e69a05c33dfc65f8.js.map    4.97 kB       2  [emitted]         manifest
                                             index.html  506 bytes          [emitted]

结果可知,static/js/vendor.js 还是 824kb

再看各个模块占用情况:

第一次优化后各个模块占用情况

WHAT? 竟然模块都没什么变化,岂不是竹篮打水,事与愿违。

再次打包优化尝试

后来查到有人同样遇到这个问题,提出一个issues#6362,原来只引入需要的element-ui组件,webpack还是把整体的 UI 库和样式都打包了,需要一个 webpackbabel 插件 babel-plugin-component,这样才能真正按需引入打包。这块其实被写到官方文档更换 自定义主题 的配置了。

于是 npm i babel-pugin-componet -D 安装后,在增加 .babelrc 文件插件配置

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2"
  ],
  "plugins": [
    "transform-vue-jsx",
    "transform-runtime",
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

页面运行正常,再次打包。

Hash: f182f70cb4ceee63b5d5
Version: webpack 3.12.0
Time: 10912ms
                                                  Asset       Size  Chunks             Chunk Names
    static/css/app.95c94c90ab11fdd4dfb413718f444d0c.css    39.9 kB       1  [emitted]  app
                 static/fonts/element-icons.732389d.ttf      56 kB          [emitted]
               static/js/vendor.befb0a8962f74af4b7e2.js     157 kB       0  [emitted]  vendor
                  static/js/app.5343843cc20a78e80469.js    3.86 kB       1  [emitted]  app
             static/js/manifest.2ae2e69a05c33dfc65f8.js  857 bytes       2  [emitted]  manifest
                static/fonts/element-icons.535877f.woff    28.2 kB          [emitted]
static/css/app.95c94c90ab11fdd4dfb413718f444d0c.css.map    93.5 kB          [emitted]
           static/js/vendor.befb0a8962f74af4b7e2.js.map     776 kB       0  [emitted]  vendor
              static/js/app.5343843cc20a78e80469.js.map    17.1 kB       1  [emitted]  app
         static/js/manifest.2ae2e69a05c33dfc65f8.js.map    4.97 kB       2  [emitted]  manifest
                                             index.html  506 bytes          [emitted]

static/js/vendor.js 确实变小了,157kB。再来看各个模块分析图。

再次优化后各个模块分析图

模块总共 157.93KB,少了 5 倍!

更换主题-覆盖样式

element-uitheme-chalk 使用 SCSS 编写,如果在自己的项目中也是用 SCSS,那么可以直接在项目中改变样式变量,因此可以在前面新建的 element-variables.scss 文件用新的主题颜色变量覆盖即可。

/**
* 覆盖主题色
*/
/*主题颜色变量*/
$--color-primary: #f0f;

/*icon字体路径变量*/
$--font-path: '~element-ui/lib/theme-chalk/fonts';

/* 引入全部默认样式 会引入没用到的组件样式 */
// @import '~element-ui/packages/theme-chalk/src/index';

/* 按需引入用到的组件的scss文件和基础scss文件 */
@import '~element-ui/packages/theme-chalk/src/base.scss';
@import '~element-ui/packages/theme-chalk/src/rate.scss';
@import '~element-ui/packages/theme-chalk/src/button.scss';
@import '~element-ui/packages/theme-chalk/src/row.scss';

现在我们的主题就变成了预期效果

主题改变了

可能你已经注意到了,这里推荐的是分别引入用到的组件样式,而不是引入全部默认样式,因为这样会导致引入没有使用到的组件样式。比如当前案例中我们没有使用到 ColorPicker 组件,在打包输出的 css 文件中确有该组件样式。

打包样式表出现没有使用的样式

更换主题-纯净样式

通过以上优化可以按需的将所用到组件打包,排除没用到的组件,减少包的大小。但是,还是存在一个小瑕疵:一个用到的组件样式会被两次打包,一次是默认的样式,一次是覆盖的样式。

还存在默认样式

出现这个问题是由于我们在两个地方对样式进行引入了,一个是在 .babelrc 文件中通过 babel-plugin-component 插件按需引入 element-ui 组件及其默认样式,一个是在 element-variables.scss 文件中覆盖默认样式生成的自定义样式。

所以怎样将二者结合,即babel-plugin-component 插件按需引入的组件样式改成用户自定义样式,达成纯净样式目标呢?这里就要用到 element-ui 的主题工具进行深层次的主题定制。

主题和主题工具安装

首先安装主题工具 element-theme,可以全局安装也可安装在项目目录。这里推荐安装在项目录,方便别人 clone 项目时能直接安装依赖并启动。

npm i element-theme -D

然后安装白垩主题,可以从 npm 安装或者从 GitHub 拉取最新代码。

# 从 npm
npm i element-theme-chalk -D

# 从 GitHub
npm i https://github.com/ElementUI/theme-chalk -D

主题构建

element-theme 支持的构建有 Node APICLI 方式。

通过 CLI 构建方式

如果全局安装可以在命令行里通过 et 调用工具,如果安装在当前目录下,需要通过 node_modules/.bin/et 访问到命令。执行 -i--init) 初始化变量文件。默认输出到 element-variables.scss,当然你可以传参数指定文件输出目录。如果你想启用 watch 模式,实时编译主题,增加 -w--watch) 参数;如果你在初始化时指定了自定义变量文件,则需要增加 -c--config) 参数,并带上你的变量文件名。默认情况下编译的主题目录是放在 ./theme 下,你可以通过 -o--out) 参数指定打包目录。

# 初始化变量文件
et --init [file path]

# 实时编译
et --watch [--config variable file path] [--out theme path]

# 编译
et [--config variable file path] [--out theme path] [--minimize]

通过 Node API 构建方式

引入 element-theme 通过 Node API 形式构建

var et = require('element-theme')

// 实时编译模式
et.watch({
  config: 'variables/path',
  out: 'output/path'
})

// 编译
et.run({
  config: 'variables/path', // 配置参数文件路径 默认`./element-variables.css`
  out: 'output/path', // 输出目录 默认`./theme`
  minimize: false, // 压缩文件
  browsers: ['ie > 9', 'last 2 versions'], // 浏览器支持
  components: ['button', 'input'] // 选定组件构建自定义主题
})

应用 Node API 构建自定义主题

在这里,为了让主题的构建更加直观和被项目共享,采用 Node API 方式构建,在项目根目录下新建 theme.js文件。

const et = require('element-theme')
// 第一步生成样式变量文件
// et.init('./src/theme.scss')
// 第二步根据实际需要修改该文件
// ...
// 第三步根据该变量文件编译出自定义的主题样式文件
et.run({
  config: './src/theme.scss',
  out: './src/theme'
})

package.json 中增加 scripts 指令

{
  "scripts": {
    "theme": "node theme.js"
  }
}

这样就可以通过 npm run theme 指令来编译主题了。编译过程:

  • 运行该指令初始化主题变量文件 theme.scss
  • 根据实际需要修改这个文件里主题样式。
  • 再运行该指令编译输出自定义的主题样式文件放在 theme 目录下。

这样就完成了所有自定义主题样式的构建。要想将这些自定义样式随着组件按需引入,需要将 .babelrc 文件中按需引入插件 babel-plugin-component 参数 styleLibraryName 从原本的 element-ui 默认样式目录变成现在自定义目录 ~src/theme

"plugins": [
    "transform-vue-jsx",
    "transform-runtime",
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "~src/theme"
      }
    ]
  ]

一切准备就绪,项目打包,打包后的 css 文件中只有唯一自定义样式,没有了默认样式,也不存在没被引入组件的样式,实现了我们预期的纯净的自定义样式!

不存在默认样式

Hash: c442bcf9d471bddfdccf
Version: webpack 3.12.0
Time: 10174ms
                                                  Asset       Size  Chunks             Chunk Names
    static/css/app.52d411d0c1b344066ec1f456355aa7b9.css    38.8 kB       1  [emitted]  app
                static/fonts/element-icons.535877f.woff    28.2 kB          [emitted]
               static/js/vendor.befb0a8962f74af4b7e2.js     157 kB       0  [emitted]  vendor
                  static/js/app.43c09c1f16b24d371e07.js    3.82 kB       1  [emitted]  app
             static/js/manifest.2ae2e69a05c33dfc65f8.js  857 bytes       2  [emitted]  manifest
                 static/fonts/element-icons.732389d.ttf      56 kB          [emitted]
static/css/app.52d411d0c1b344066ec1f456355aa7b9.css.map    81.3 kB          [emitted]
           static/js/vendor.befb0a8962f74af4b7e2.js.map     776 kB       0  [emitted]  vendor
              static/js/app.43c09c1f16b24d371e07.js.map    17.1 kB       1  [emitted]  app
         static/js/manifest.2ae2e69a05c33dfc65f8.js.map    4.97 kB       2  [emitted]  manifest
                                             index.html  506 bytes          [emitted]

由于样式是纯净的,css 文件大小从原来完全引入的 234KB 变成 38.8KB,进一步减小了打包大小。

总结

通过以上实验分析我们可以得知,element-ui 要想实现按需引入和纯净的主题样式:

  • 首先通过 babel-plugin-component 插件进行按需引入。
  • 再用 element-theme 工具生成样变量文件。
  • 然后根据项目需求修改自定义样式,依据该文件构建生成所有样式。
  • 最后将按需引入样式 styleLibraryName 指向自定义样式目录。

如果对样式提取要求不高,可直接采取变量覆盖形式(同时存在默认样式)。
还有不清楚可以戳这里查看案例源码,赠人 star,手有余香。

完~ps:个人见解有限,欢迎指正。

查看原文

赞 28 收藏 20 评论 7

haokeed 关注了专栏 · 10月15日

Java中文社群

专注Java干货和面试题分享

关注 302

haokeed 发布了文章 · 10月14日

mysql中物理文件(日志文件、数据库文件、配置文件)

mysql的物理文件这里主要讲:日志文件、数据库文件和配置文件

1.Mysql日志文件

mysql的日志文件主要有5种:错误日志、二进制日志、事务日志、慢查询日志、查询日志。

1.1 错误日志(Error Log)

文件名:*.err

默认情况下错误日志大概记录以下几个方面的信息:

  1. 服务器启动和关闭过程中的信息(未必是错误信息,如mysql如何启动InnoDB的表空间文件的、如何初始化自己的存储引擎的等等。
  2. 服务器运行过程中的错误信息、事件调度器运行一个事件时产生的信息、在从服务器上启动服务器进程时产生的-信息。

注意:这里并不会记录sql执行错误的日志。

# 查询错误日志位置命令
show global variables like '%log_error%';

1.2 事务日志(InnoDB redo Log & undo Log)

事务日志是InnoDB独有的日志。事务日志又分为:重做日志(redo Log)、回滚日志(undo Log)

使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把修改行为记录到硬盘上持久的事务日志中,而不用每次都将修改的数据本身持久到磁盘。
事务日志采用追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。
事务日志持久以后,内存中被修改的数据在后台可以慢慢的刷回到磁盘。目前大多数的存储引擎都是这样实现的。
事务具体执行流程:https://www.cnblogs.com/maypattis/p/5628355.html

  • 重做日志(redo Log)
    文件名:*.ib_logfile0
    redo log是指在回放日志的时候把已经COMMIT的事务重做一遍,对于没有COMMIT的事务按照abort处理,不进行任何操作。Redo日志记录了InnoDB所做的所有物理变更和事务信息
  • 回滚日志(undo Log)
    文件名:*.ibdata
    undo log是把所有没有COMMIT的事务回滚到事务开始前的状态,系统崩溃时,可能有些事务还没有COMMIT,在系统恢复时,这些没有COMMIT的事务就需要借助undo log来进行回滚。
    Innodb存储引擎可将所有数据存放于ibdata*的共享表空间,也可将每张表存放于独立的.ibd文件的独立表空间。
-- 查看事务日志 :
show engine innodb statusG;
-- 查看日志文件设置状态
show variables like 'innodb_%';
-- innodb_log_files_in_group DB中设置几组事务日志,默认是2
-- innodb_log_group_home_dir 事务日志存放目录;不设置,ib_logfile0...存在在数据文件目录下

1.3 二进制日志(Binary Log)

二进制日志,也就是我们常说的binlog。二进制日志记录了MySQL所有修改数据库的操作,然后以二进制的形式记录日志在日志文件中,其中还包括没调语句所执行的时间和消耗的资源,以及相关的事务信息。

作用:

  1. 记录sql操作(数据备份)
  2. 增量数据备份及恢复
  3. sql主从复制

说明:

  1. 默认情况下二进制日志是关闭的,但是在宝塔中是默认开启的。
  2. 由于记录的是所有sql的执行记录,所以产生的日志文件的量会很大,这个情况下就要进行日志文件的定时挪到备份服务器或者进行定时删除。
# 查询二进制日志开启等情况
show global variables like '%log_bin%';

1.4 慢查询日志(Show Query Log)

文件名:*show.log(默认目录是data目录下)

慢查询日志中记录的是执行时间较长的query,也就是我们常说的slow query。

专业一点:慢查询日志是值所有SQL执行的实际超过long_query_time变量的语句和达到min_examined_row_limit条举例的语句。用户可以针对这部分语句性能调优。慢查询日志通过设置log-slow_queries[=file_name]选项开启后,将记录日志所在的路劲和名称。

注意:慢查询 对事务的执行是不记录的。如果测试的话,请使用select sleep(60);这样的语句来测试
-- 查看慢查询信息
show variables like "%slow%" ;
-- log_slow_queries  off    表示“慢查询”是“关闭的状态”
-- slow_launch_time  2     表示“查询时间超过2秒就记录到慢查询日志中”;
-- slow_queries_log  off   表示慢查询日志开关是关着的
-- slow_query_log_file "路径"   表示慢查询日志存放完整路径

-- 查看long_query_time(最大等待时间,或者慢查询时间)设置时间
-- 注意和slow_launch_time的区别
show variables like "%long%"
-- long_query_time  就这个值

image.png

-- 开启慢查询的功能
set global log_slow_queries=on; 
-- 这样就开启了慢查询的功能,此参数打开了,slow_query_log就自动变成了on,关闭了的话也跟着关闭。

image.png

注意:
slow_launch_time设定跟慢查询日志的查询阀值设定不同,slow_launch_time表示了thread create(线程创建)的一个阀值,如果thread create(线程创建)的时间超过了这个值,slow_launch_time的值加1。set global slow_launch_time=1 这里的时间值必须是整数,否则的话就话执行出错。
long_query_time表示超过多少秒的查询就写入日志,默认的是10s,设置为0的话表示记录所有的查询。在Mysql 5.5可以追踪到微秒的查询。

开启慢查询:

# 对应的mysql配置文件(my.cnf)中,并且还得重新启动mysql才会生效
slow_query_log=1 # 开启慢查询日志 也可以slow_query_log =on
slow_query_log_file="D:/phpStudy/1.log" # 慢查询日志文件
log_slow_queries=on   # 开启慢查询的功能
long_query_time=2 # 最大等待时间

1.5 查询日志(Query Log)

2.数据库文件

MySQL数据库会在data目录下面建立一个以数据库为名的文件夹,用来存储数据库中的表文件数据。
不同的数据库引擎,每个表的扩展名也不一样,例如:MyISAM用“.MYD”作为扩展名,Innodb用“.ibd”,Archive 用“.arc”,CSV 用“.csv
image.png

2.1 .frm文件

无论是那种存储引擎,创建表之后就一定会生成一个以表明命名的.frm文件。
.frm文件主要存放与表相关的数据信息,主要包括表结构的定义信息。当数据库崩溃时,用户可以通过frm文件来恢复数据表结构。

2.2 .MYD文件

.MYD文件是MyISAM存储引擎专用,存放MyISAM表的数据。每一个MyISAM表都会有一个.MYD文件与之对应,同样存放于所属数据库的文件夹下,和.frm文件在一起。

2.3 .MYI文件

.MYI文件也是专属于MyISAM存储引擎的,主要存放MyISAM表的索引相关信息。对于MyISAM存储来说,可以被cache 的内容主要就是来源于.MYI文件中。每一个MyISAM表对应一个.MYI文件,存放于位置和.frm以及.MYD一样。

2.4 .idb文件和iddata文件

这两种文件都是存放Innodb数据的文件,之所以有两种文件来存放Innodb的数据(包括索引),是因为Innodb的数据存储方式能够通过配置来决定是使用共享表空间存放存储数据,还是独享表空间存放存储数据。
独享表空间存储方式使用.ibd文件来存放数据,且每个表一个.ibd文件,文件存放在和MyISAM数据相同的位置。如果选用共享存储表空间来存放数据,则会使用ibdata文件来存放,所有表共同使用一个(或者多个,可自行配置)ibdata文件。

ibdata文件

  • 可以通过innodb_data_home_dir(数据存放目录)和innodb_data_file_path(配置每个文件的名称)两个参数配置组成

    // innodb_data_file_path中可以一次配置多个ibdata文件
    innodb_data_file_path=ibdata1:2000M;ibdata2:10M:autoextend 
共享表空间以及独占表空间都是针对数据的存储方式而言的。

共享表空间: 某一个数据库的所有的表数据,索引文件全部放在一个文件中。

独占表空间: 每一个表都将会生成以独立的文件方式来进行存储,每一个表都有一个.frm表描述文件,还有一个.ibd文件。其中这个文件包括了单独一个表的数据内容以及索引内容。

共享表空间和独占表空间的对比

共享表空间:

  • 优点:

可以放表空间分成多个文件存放到各个磁盘上。数据和文件放在一起方便管理。

  • 缺点:

所有的数据和索引存放到一个文件中,多个表及索引在表空间中混合存储,这样对于一个表做了大量删除操作后表空间中将会有大量的空隙,特别是对于统计分析,日值系统这类应用最不适合用共享表空间。

独立表空间:

  • 优点:
  1. 每个表都有自已独立的表空间。
  2. 每个表的数据和索引都会存在自已的表空间中。
  3. 可以实现单表在不同的数据库中移动。
  4. 空间可以回收:
    a) Drop table操作自动回收表空间,如果对于统计分析或是日值表,删除大量数据后可以通过:altertable TableName engine=innodb;回缩不用的空间。
    b) 对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理。
  • 缺点:
  1. 单表增加过大,如超过100个G。

相比较之下,使用独占表空间的效率以及性能会更高一点

共享表空间和独立表空间之间的转换

show variables like "innodb_file_per_table"; 
-- ON代表独立表空间管理,OFF代表共享表空间管理;

-- 修改数据库的表空间管理方式
-- 修改innodb_file_per_table的参数值即可,但是修改不能影响之前已经使用过的共享表空间和独立表空间;
innodb_file_per_table=1 # 1为使用独占表空间;0为使用共享表空间

3.配置文件

3.1 系统配置文件(my.ini或my.cnf)

系统核心配置文件

linux/mac : etc/my.cnf
windows : mysql/my.ini

image.png
MySQL 的系统配置文件一般都是my.cnf,默认存放在"/etc"目录下,my.cnf文件中包含多种参数选项组(group),每一种参数组都通过中括号给定了固定的组名,如“[mysqld]”组中包括了mysqld服务启动时候的初始化参数,“[client]”组中包含着客户端工具程序可以读取的参数。

3.2 socket文件

MySQL服务器启动后socket文件自动生成,该文件主要用来连接客户端
在有些时候连接MySQL会出现如下的问题:
image.png
这个问题的解决主要是sock文件没有找到.
image.png
通常的解决办法:可以尝试重启一下MySQL的服务器;如果不信就可以去查找mysql.sock,并在my.cnf中指定该文件的位置(移动也可以)。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 0 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 10月14日
个人主页被 153 人浏览