Python运维学习笔记

文章目录

1. 项目运维
1.1 运维的分类

① helpdesk:维修电脑、打印机等,随叫随到。

② 初级运维:熟悉linux命令,可以搭建ftp,smba,nginx,apache等,发布开发打包过来的项目+nginx+uwgsi,发布高性能的网站。

③ 架构师(运维开发):精通python,精通linux,精通网络,知识领域非常广。

1.2 项目发布流程

开发人员把代码写好之后发给测试人员测试性能。测试通过之后,给运维人员进行线上发布。监控运维,无论是各部门的运维,都要保障项目/网站 7*24小时正常工作。

2. 软件安装与项目部署
2.1 软件安装的方式

① yum安装:适合小型软件

② rpm安装:需要处理依赖关系,不推荐使用

③ 编译安装:适合大型软件,需要自定制功能的软件

2.2 编译安装Python3

① 解决依赖关系

[root@instance-mtfsf05r ~]# yum update

[root@VM_39_157_centos ~]# yum install gcc patch libffi-devel python-devel zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel -y

② 下载python3的源代码

[root@VM_39_157_centos ~]# wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz,或

[root@instance-mtfsf05r ~]# wget https://www.python.org/ftp/python/3.7.4/Python-3.7.4.tar.xz

③ 解压缩源代码

[root@instance-mtfsf05r ~]# tar zxvf Python-3.7.3.tgz -C ./

[root@instance-mtfsf05r ~]# xz -d Python-3.4.7.tar.xz

[root@instance-mtfsf05r ~]# tar -xf Python-3.4.7.tar

④ 切换进入python源代码目录

[root@instance-mtfsf05r ~]# cd Python-3.7.3/

⑤ 释放编译文件

[root@instance-mtfsf05r Python-3.7.3]# ./configure --prefix=/usr/local/python373

执行完这条语句后还不会生成/usr/local/python373这个文件夹。./configure是用来检测安装平题啊的目标特征。比如,它会检测你是不是有CC或GCC,它是一个sheel脚本。configure脚本执行后,会生成一个Makefile文件。

./configure:是当前文件夹下面的configure文件(绿色的文件表示可执行文件)
./configure --prefix=/usr/local:释放脚本文件,指明安装路径。

执行后会检查依赖问题,如果依赖没解决好,就会有各种报错。

⑥ 编译与安装

[root@instance-mtfsf05r Python-3.7.3]# make

[root@instance-mtfsf05r Python-3.7.3]# make install

这两步完成后才会创建/usr/local/python373这个文件夹。make是用来编译的,它从Makefile中读取指令,然后编译。make install是用来安装的,它也会从Makefile中读取指令,安装到指定的位置。

这两句也可以一句执行:make && make install,表示make执行成功之后,才会执行make install命令。make命令执行的时候,就会调Makefile开始编译。

⑦ 配置软链接,快捷启动python3和pip3(如果选择配置软链接,请忽略⑧⑨ )

[root@instance-mtfsf05r bin]# ln -s /usr/local/python373/bin/python3 /usr/bin/python3

[root@instance-mtfsf05r bin]# ln -s /usr/local/python373/bin/pip3 /usr/bin/pip3

[root@instance-mtfsf05r ~]# pip3 install --upgrade pip

pip3 install --upgrade pip的意思是:通过pip3 install 这个命令去升级pip

如果想要直接执行python就使用python3可以执行下面的操作:
[root@instance-mtfsf05r ~]# cd /usr/bin
[root@instance-mtfsf05r bin]# mv python python.backup
[root@instance-mtfsf05r bin]# ln -s /usr/local/bin/python3.7 /usr/bin/python

⑧ 配置系统环境变量,加入python3的目录(与⑦ 任选其一种方式)

  • 第一种方式:PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/python3/bin/
  • 第二种方式:PATH=$PATH:/usr/local/python3/bin/

配置环境变量需要谨慎,如果配错了,其它命令也会丢失。

⑨ 写入个人配置文件,永久生效

  • 编辑配置文件:[root@instance-mtfsf05r bin]# vim /etc/profile
  • 将PATH写入到配置文件中:PATH=$PATH:/usr/local/python3/bin/(可以放到最后一行)
  • 读取配置文件,生效配置:[root@instance-mtfsf05r bin]# source /etc/profile
2.3 安装启动django

① 安装django

[root@instance-mtfsf05r ~]# pip3 install django

② 创建django项目

[root@instance-mtfsf05r ~]# /usr/local/python373/bin/django-admin startproject mysite

③ 修改django项目中的settings.py配置文件

settings.py

…
ALLOW_HOST = ['*']

④ 启动django

[root@instance-mtfsf05r mysite]# python3 manage.py runserver 0.0.0.0:5001
在这里插入图片描述

2.4 virtualenv详解

在使用Python开发的过程中,物理环境中存在各种各样的库,这些库的版本是固定的,而不同的工程依赖于不同版本的库,就会引起依赖问题。这个时候,我们需要对不同的工程使用不同的虚拟环境来保持开发环境以及宿主环境的清洁。virtualenv是一个帮助我们管理不同python环境的工具,使用virtualenv可以在系统上建立多个不同并且相互不干扰的虚拟环境。

virtualenv的原理是:把系统python复制一份到virtualenv环境,用命令source venv/bin/activate进入virtualenv环境时,virtualenv会修改环境变量,在原有$ PATH(echo $ PATH)的最前面加上虚拟环境的bin目录,这样,在虚拟环境下优先执行虚拟环境中的Python3和pip3。也就是说虚拟环境中的$PATH和物理环境中的是不一样的。

① 安装virtualenv库

pip3 install --upgrade pip:升级pip工具

pip3 install virtualenv:安装virtualenv。如果下载慢,可以指定清华源或者豆瓣源下载:

pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple virtualenv

② 创建独立运行

virtualenv --no-site-packages --python=python3 venv

–no-site-packages的意思是构建一个干净的、和外界没有关系的虚拟环境。虚拟环境是基于物理环境的复制,这里是基于Python3,所有需要使用–python=python3。venv是虚拟环境的名字,这里使用的相对路径。

③ 激活虚拟环境

source venv/bin/activate

④ 退出虚拟环境

deactivate

⑤ 查看pip3,可以看到pip3来自于虚拟环境

which pip3

2.5 开发环境的一致性

我们在本地开发环境准备好了Python项目和依赖环境,现在需要放到服务器中上线发布,那么必须要保证服务器服务器的Python环境和本地环境是一致的。

① 导出当前Python环境的包

pip3 freeze > requirements.txt

这句命令执行后将会创建一个 requirements.txt 的文件,其中包含了当前环境所有的包/库以及各自版本的简单列表。

② 将产生的文件上传到服务器,在服务器下创建虚拟环境,在虚拟环境(venv)中导入项目所需要的模块依赖。

pip3 install -r requirements.txt

2.6 virtualenvwrapper

virtualenv最大的缺点就是每次开启虚拟环境都要去虚拟环境所在目录的bin目录下激活虚拟环境,这就要求我们每次都要记住这个虚拟环境所在的目录。一种解决方案就是将所有的虚拟环境目录全部收集起来,针对不同的目录做不同的事。为了方便我们使用虚拟环境我们可以使用virtualenvwrapper来管理虚拟环境,其实virtualenvwrapper就是统一管理虚拟环境的目录,并且省去了source的步骤。

① 安装virtualenvwrapper

# 安装virtualenvwrapper
[root@master ~]# pip3 install virtualenvwrapper
# 如果上面的安装比较慢,可以指定清华源安装virtualenvwrapper
[root@master ~]# pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple virtualenvwrapper

② 设置linux环境变量,每次启动就加载virtualenvwrapper

# 找到which virtualenvwrapper.sh所在路径
[root@master ~]# which virtualenvwrapper.sh
# 没有找到使用find命令
[root@master ~]# find / -name virtualenvwrapper.sh
/usr/local/python373/bin/virtualenvwrapper.sh
# 将/usr/local/python373/bin/目录加入到环境变量
[root@master ~]# PATH=$PATH:/usr/local/python373/bin
# 写入个人配置文件,永久生效
[root@master ~]# vim /etc/profile
# 将下面一行代码放在/profile文件的最下面
PATH=$PATH:/usr/local/python373/bin/
# 读取文件生效
[root@master ~]# source /etc/profile
# 编辑~/.bashrc文件,此文件在用户每次登录的时候,系统就会读取这个文件
[root@master ~]# vim ~/.bashrc 
# 在.bashrc文件只写入下面两行代码
export WORK_HOME=/Envs # 设置virtualenv的统一管理目录
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
source /usr/local/python373/bin/virtualenvwrapper.sh # 执行virtualenvwrapper安装脚本
# 读取文件使生效,此时就可以使用virtualenvwrapper
[root@master ~]# source ~/.bashrc 
virtualenvwrapper.user_scripts creating /root/.virtualenvs/premkproject
virtualenvwrapper.user_scripts creating /root/.virtualenvs/postmkproject
virtualenvwrapper.user_scripts creating /root/.virtualenvs/initialize
virtualenvwrapper.user_scripts creating /root/.virtualenvs/premkvirtualenv
virtualenvwrapper.user_scripts creating /root/.virtualenvs/postmkvirtualenv
virtualenvwrapper.user_scripts creating /root/.virtualenvs/prermvirtualenv
virtualenvwrapper.user_scripts creating /root/.virtualenvs/postrmvirtualenv
virtualenvwrapper.user_scripts creating /root/.virtualenvs/predeactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/postdeactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/preactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/postactivate
virtualenvwrapper.user_scripts creating /root/.virtualenvs/get_env_details

③ 虚拟环境相关命令

# 创建venv虚拟环境,默认会激活这个虚拟环境
[root@master ~]# mkvirtualenv venv
# 退出虚拟环境
(venv) [root@master ~]# deactivate
# 进入/切换虚拟环境,workon提供自动补全功能,可以停止你当前的环境可以在多个虚拟环境中来回切换
[root@master ~]# workon venv
# 删除虚拟环境venv
(venv) [root@master ~]# deactivate
[root@master ~]# rmvirtualenv venv
# 列举所有虚拟环境
[root@master ~]# lsvirtualenv
# 在虚拟环境下做了统一的管理,查看Envs目录
[root@master ~]# ls ~/Envs/
get_env_details  postactivate    postmkproject     postrmvirtualenv  predeactivate  premkvirtualenv
initialize       postdeactivate  postmkvirtualenv  preactivate       premkproject   prermvirtualenv
# 切换到当前激活的虚拟环境的目录
(venv) [root@master ~]# cdvirtualenv 
(venv) [root@master venv]#
3. mysql相关
3.1 安装mysql

可以参考官方安装方式:https://downloads.mariadb.org/mariadb/repositories/#distro=CentOS&distro_release=centos7-ppc64le–centos7&mirror=tuna&version=10.4

① 添加MariaDB yum仓库

1、切换到yum.repos.d目录下
[root@instance-mtfsf05r ~]# cd /etc/yum.repos.d/
2、创建并且编辑MariaDB.repo yum仓库文件
[root@instance-mtfsf05r yum.repos.d]# vim MariaDB.repo
3、将下面的内容写入到MariaDB.repo
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.4/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
PS:还可以指定指定清华源,下载更快
[mariadb]
name = MariaDB
baseurl = https://mirrors.ustc.edu.cn/mariadb/yum/10.4/centos7-amd64
gpgkey=https://mirrors.ustc.edu.cn/mariadb/yum/RPM-GPG-KEY-MariaDB
gpgcheck=1

清理yum缓存:yum clean all

生成缓存:yum makecache

② 安装MariaDB

sudo yum install MariaDB-server MariaDB-client -y

③ MariaDB的卸载

1、使用yum卸载mariadb
[root@instance-mtfsf05r ~]# yum remove mariadbdb
2、删除数据库配置文件
[root@instance-mtfsf05r ~]# rm -f /etc/my.cnf
3、删除数据目录
[root@instance-mtfsf05r ~]# rm -rf  /var/lib/mysql
3.2 启动/关闭相关命令
  • 启动Mariadb:systemctl start mariadb
  • 查看MariaDB的启动状态:systemctl status mariadb
  • 停止Mariadb:systemctl stop mariadb
  • 重启Mariadb:systemctl restart mariadb
  • 设置开机启动:systemctl enable mariadb
3.3 初始化mysql
在确认 MariaDB 数据库软件程序安装完毕并成功启动后请不要立即使用。为了确保数据 库的安全性和正常运转,需要先对数据库程序进行初始化操作。这个初始化操作涉及下面 5 个 步骤。
➢ 设置 root 管理员在数据库中的密码值(注意,该密码并非 root 管理员在系统中的密 码,这里的密码值默认应该为空,可直接按回车键)。
➢ 设置 root 管理员在数据库中的专有密码。
➢ 随后删除匿名账户,并使用 root 管理员从远程登录数据库,以确保数据库上运行的业
务的安全性。
➢ 删除默认的测试数据库,取消测试数据库的一系列访问权限。
➢ 刷新授权列表,让初始化的设定立即生效。

确保mariadb服务器启动后,执行命令初始化:mysql_secure_installation,执行后会问你这几个问题,需要y然后设置即可。

Set root password? [Y/n] 
Remove anonymous users? [Y/n] 
Disallow root login remotely? [Y/n]
Remove test database and access to it? [Y/n]
Reload privilege tables now? [Y/n]
3.4 mysql基本命令
  • 修改mysql密码:MariaDB [(none)]> set password=password('123456');
  • 创建其它数据库用户:MariaDB [(none)]> create user thanlon@'%' identified by '123456';
  • 查看那thanlon数据库用户的相关信息:
MariaDB [mysql]> use mysql
MariaDB [mysql]> select *from user where user='thanlon'\G;
MariaDB [mysql]> select host,user,password from user where user='thanlon'\G;

如果切换到thanlon用户是无法查看到完整的数据库列表。

3.5 权限的配置

mysql使用grant命令对账户进行授权:

grant 权限 on 数据库.表名 to 账户@主机名           #对特定数据库中的特定表授权
grant 权限 on 数据库.* to 账户@主机名             #对特定数据库中的所有表给与授权
grant 权限1,权限2,权限3 on *.* to 账户@主机名     #对所有库中的所有表给与多个授权
grant all privileges on *.* to 账户@主机名      #对所有库和所有表授权所有权限

给thanlon所有数据库所有表的权限:MariaDB [(none)]> grant all privileges on *.* to thanlon@'%';
给thanlon创建数据库数据表的权限:MariaDB [(none)]> grant create on *.* to thanlon@'%';
给thanlon删除数据库数据表的权限:MariaDB [(none)]> grant drop on *.* to thanlon@'%';
查看授权给某个用户的权限:MariaDB [(none)]> show grants for thanlon@'%';
移除权限:MariaDB [(none)]> revoke all privileges on *.* from thanlon@'%';

3.5 端口的修改
# 查看数据库端口
MariaDB [(none)]> show global variables like 'port'
# 修改配置文件
[root@master ~]$ vim /etc/my.cnf
# 在[mysqld]的下面添加或修改port=3333
[mysqld]
port=3333
# 重启mysql
[root@master ~]$ systemctl restart mysql
# 查看更改后的端口
MariaDB [(none)]> show global variables like 'port';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| port          | 3333  |
+---------------+-------+
1 row in set (0.001 sec)
3.6 编码的配置

查看数据库中编码:\s

Server characterset:	latin1
Db     characterset:	latin1
Client characterset:	utf8
Conn.  characterset:	utf8

需要都配置成utf8的编码,才能显示中文。所以需要修改配置文件,编辑配置文件vim /etc/my.cnf,在mysqld下输入:

[mysqld]
character-set-server=utf8
collation-server=utf8_general_ci
log-error=/var/log/mysqld.log
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8

重启mysql:systemctl restart mysql,此时输入\s,查看编码:

Server characterset:	utf8
Db     characterset:	utf8
Client characterset:	utf8
Conn.  characterset:	utf8
3.7 远程登录的配置

授权配置:远程连接设置所有库和所有表的所有权限,赋值权限给所有ip地址的thanlon用户

# 授权
grant all privileges on *.* to root@'%';
# 刷新权限,防止授权没有生效
flush privileges;
# 查看权限
show grants for root@'%';
# 移除权限
revoke all privileges on *.* from root@'%';

远程登录:

thanlon@plus-book:~$ mysql -uroot -p -h10.0.0.2
thanlon@plus-book:~$ mysql -uroot -p -h10.0.0.3

如果遇到问题,查看是不是防火墙的问题,可以关闭防火墙:

1、查看防火墙的状态
[root@instance-mtfsf05r ~]# getenforce
2、清空防火墙规则																
[root@instance-mtfsf05r ~]# iptables -F
3、关闭防火墙
[root@instance-mtfsf05r ~]# systemctl stop firewalld
4、移除开机自启
[root@instance-mtfsf05r ~]# systemctl disable firewalld
3.8 数据的备份与恢复

mysqldump命令用于备份数据库的数据,备份的命令是:

[root@instance-mtfsf05r ~]# mysqldump -uroot -p --all-databases > all.dump

删除一个数据库,如何恢复,可以通过备份文件进行恢复:

1、登录数据库后使用source
[root@instance-mtfsf05r ~]# source all.dump
2、登录数据库的时候,使用<是重定向写入符号写入数据库
[root@instance-mtfsf05r ~]# mysql -uroot -p < all.dump
3.9 主从复制

mysql数据库的主从复制方案,是其自带的功能。并且主从复制并不是复制磁盘上的数据库文件,而是通过binlog日志复制到需要同步的从服务器上。mysql数据库支持单向、双向、链式级联,等不同业务场景的复制。在复制的过程中,一台服务器充当主服务器(master),接收来自用户的内容更新。而一个或多个其他的服务器充当从服务器(slave),接收来自master上binlog文件的日志内容,解析出sql,重新更新到自己的服务器上,使得主从服务器数据达到一致。

① 主从复制的架构

  • 一主一从,单向主从同步模式,只能在Master端写入数据
    在这里插入图片描述
  • 一主多从
    在这里插入图片描述
  • 双主复制架构,此架构可以在master或master进行数据写入,或者两端同事写入(特殊设置):
    在这里插入图片描述

注意:在生产环境中,mysql主从复制都是异步的复制方式,即不是严格的实时复制,但是给用户的体验都是实时的。mysql主从复制集群功能使得mysql数据库支持大规模高并发读写成为可能,且有效的保护了服务器宕机的数据备份。

② 主从复制的应用场景
利用复制功能当Master服务器出现问题时,我们可以人工的切换到从服务器继续提供服务,此时服务器的数据和宕机时的数据几乎完全一致。复制功能也可用作数据备份,但是如果人为的执行drop,delete等语句删除,那么从库的备份功能也就失效。

③ 主从复制的实现原理
在这里插入图片描述

1、master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events); 
2、slave将master的binary log events拷贝到它的中继日志(relay log)3、slave重做中继日志中的事件,将改变反映它自己的数据。
3.10 主从复制的实现

实现主从配置的环境是需要两台服务器,一台服务器安装主数据库,一台安装从数据库。如果你有两台云服务器,这将节省下很多安装系统的时间。如果你没有云服务器,也是没有关系的。我们可以VMware软件虚拟化两台服务器,注意这两台服务器要在使用NAT方式连接网络,并且要配置静态ip,还要在同一个局域网中。具体配置可以到网上搜索,配置实在是有问题可以在评论区留言。我这里虚拟化两台服务器,ip地址分别是10.0.0.210.0.0.3,前者安装主库,后者安装从库。主从复制有好三种架构,我们这里使用其中的一种:一主一从。
在这里插入图片描述
① 主库的配置

  • 修改配置文件
[root@master ~]# vim /etc/my.cnf
在配置文件的[mysqld]下追加下面的内容(server-id服务的唯一标识,主从之间都必须不同;log-bin启动二进制日志名称为mysql-bin):
[mysqld]
server-id=1
log-bin=mysql-bin
  • 重启mysql数据库
[root@master ~]# systemctl restart mariadb
重启后,mysql主库已经开启同步功能,创建了mysql-bin日志文件
可以通过下面的命令查看生成的二进制文件:
[root@master ~]# ls /var/lib/mysql
  • 创建用于主从同步的账号并赋予权限
# 创建用于主从同步的账号
MariaDB [(none)]> create user 'slave'@'%' identified with mysql_native_password by '123456';
# 为这个账户赋予slave权限
MariaDB [(none)]> grant replication slave on *.* to slave@'%';
# 刷新权限列表,防止没有生效
MariaDB [(none)]> flush privileges;
# 检查一下主库创建的账号信息
MariaDB [(none)]> select user,host from mysql.user;
# 查看这个账户的权限
MariaDB [(none)]> show grants for slave@'%';
  • 主库的锁表,防止数据写入
MariaDB [(none)]> flush table with read lock;
  • 检查主库的日志状态,查看日志位置(锁表是防止日志位置发生变化)
MariaDB [(none)]> show master status
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 |      632 |              |                  |
+------------------+----------+--------------+------------------+
  • 数据备份,导出主库的已经存在数据,然后用于从库写入
# 将主库数据全部导出到/tmp/my_all.dump文件中
[root@master ~]# mysqldump -uroot -p  --all-databases > /tmp/my_all.dump
# 上传到从库所在服务器的/tmp/目录下
[root@master ~]# scp /tmp/my_all.dump root@10.0.0.3:/tmp

② 从库的配置

  • 在从库上导入主库的最新数据,保证数据的一致性
1、登录从库
3、删除从库所有的数据库
2、导入数据
MariaDB [(none)]> source /tmp/my_all.dump
  • 修改从库数据库配置文件,添加用于同步的配置,和主库区别开
[root@slave ~]# vi /etc/my.cnf
# 在[mysqld]下追加下面的内容,server-id不一定是2,只要和主库配置的不同即可
[mysqld]
character-set-server=utf8
server-id=2
  • 重启数据库
[root@slave ~]# systemctl restart mariadb
  • 登录数据库检查从库的各项参数
MariaDB [(none)]> show variables like 'server_id';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| server_id     | 2     |
+---------------+-------+
1 row in set (0.001 sec)

MariaDB [(none)]> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | OFF   |
+---------------+-------+
  • 将从库同步于主库
#主库所在的主机 master_host
#用于主从同步的账号 master_user和密码master_password
#主库配置的日志文件 master_log_file
#和主库日志文件的起始位置 master_log_pos
#(可在主库中执行 show master status;查看master_log_file和master_log_pos)
MariaDB [(none)]> change master to master_host='10.0.0.2',master_user='slave', master_password='123456', master_log_file='mysql-bin.000001', master_log_pos=632;
  • 开启主从复制
MariaDB [(none)]> start slave;
  • 检查复制的状态
MariaDB [(none)]> show slave status\G;
# 检查从库同步参数,这两条参数都为YES说明配置成功
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

③ 测试主从同步

  • 主库中的操作
# 主库解锁
MariaDB [(none)]> unlock tables;
# 查看主库当前数据库
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
# 主库中创建test数据库
MariaDB [(none)]> create database test;
Query OK, 1 row affected (0.001 sec)
# 查看创建的数据库
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.001 sec)
  • 从库中的操作
# 登录从数据库后,查看当前数据库,发现从苦衷出现 test 数据库
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| test               |
+--------------------+
3 rows in set (0.001 sec)
3.10 主从复制中遇到的问题

问题1:Authentication requires secure connection.,具体报错信息如下:

Last_IO_Error: error connecting to master '[email protected]:33221' - retry-time: 60 retries: 1 message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.

问题分析与解决:主库在创建用于主从同步的账号时加上with mysql_native_password,即:create user 'slave'@'%' identified with mysql_native_password by '123456',操作具体步骤如下:

mysql> create user 'slave'@'%' identified with mysql_native_password by 'MysqlSlave018018!';
Query OK, 0 rows affected (0.00 sec)

mysql> show grants for slave@'%';
+-----------------------------------------------+
| Grants for slave@%                            |
+-----------------------------------------------+
| GRANT REPLICATION SLAVE ON *.* TO `slave`@`%` |
+-----------------------------------------------+
1 row in set (0.00 sec)

问题2:Operation CREATE USER failed for 'slave'@'%'' on query,具体报错信息如下:

Last_SQL_Error: Error 'Operation CREATE USER failed for 'slave'@'%'' on query. Default database: 'mysql'. Query: 'CREATE USER 'slave01'@'%' IDENTIFIED WITH 'caching_sha2_password' AS '$A$005+7{d%k`?DtShA3dAwGMNco97UMbZXL.G2SF587N1giGMn6wU5btRJ2''

问题分析与解决:当前已经创建过slave这个用户,已存在该用户的授权信息。需要将该用户从user表中删除,然后重新创建该用户并授予slave权限。具体操作如下:

mysql> delete from user where user='slave';
Query OK, 1 row affected (0.00 sec)

mysql> select user,host,plugin from user;
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
4 rows in set (0.00 sec)

mysql> create user 'slave'@'%' identified with mysql_native_password by '123456';
Query OK, 0 rows affected (0.01 sec)

mysql> grant replication slave on *.* to 'slave'@'%';
Query OK, 0 rows affected (0.01 sec)

问题3:Can't connect to MySQL server on '106.12.115.136' (111),具体报错信息如下:

Last_IO_Error: error connecting to master '[email protected]:3306' - retry-time: 60 retries: 1 message: Can't connect to MySQL server on '106.12.115.136' (111)

问题分析与解决:检查主库MySQL数据库的端口,如果主库的端口不是默认端口,那么就需要指定端口,mysql> change master to master_host='106.12.115.101',master_user='slave',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=509,master_port=3307;

4. Redis相关
4.1 Redis基本使用

① 安装redis:https://blog.csdn.net/Thanlon/article/details/101925916#51__43

② 启动redis:https://blog.csdn.net/Thanlon/article/details/101925916#53__112

③ redis的配置

# 找到自己redis配置文件,然后修改这个配置文件
[root@master redis]# vim redis.conf
protected-mode yes    # 打开保护模式
port 6380                          # 生产环境中端口不要使用默认的6379,使用其它端口,如6380等。
requirepass xxx        	  # 找到requirepass,设置redis的启动密码,xxx是自定义的密码
bind 0.0.0.0                      # 如果想让外部计算机也连接redis,可以设置绑定的ip。

设置好配置文件后要指定这个配置文件启动redis:

[root@master redis]# redis-server redis.conf 
27615:C 10 Oct 2019 08:39:39.974 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
27615:C 10 Oct 2019 08:39:39.974 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=27615, just started
27615:C 10 Oct 2019 08:39:39.974 # Configuration loaded

[root@master redis]# ps -ef |grep redis
root     27616     1  0 08:39 ?        00:00:00 redis-server 0.0.0.0:6380
root     27659 26530  0 08:40 pts/0    00:00:00 grep --color=auto redis

# 注意如果redis可以在多个端口开启多个进程 ,只需按不同的配置文件(配置不同的端口)启动即可
[root@master redis]# ps -ef |grep redis
root     27616     1  0 08:39 ?        00:00:00 redis-server 0.0.0.0:6380
root     28016     1  0 08:46 ?        00:00:00 redis-server 0.0.0.0:6390
root     28029 26530  0 08:46 pts/0    00:00:00 grep --color=auto redis

如果你的redis在本地,使用客户端命令连接redis的时候,只需要指定端口。如果你的redis在服务器中,打开客户端的时候记得要指定端口指定主机地址:

thanlon@plus-book:~$ redis-cli -p 6380 -h 106.12.115.136
106.12.115.136:6380> 

如果你开始操作数据库,这个时候redis会要求让你输入启动密码:

thanlon@plus-book:~$ redis-cli -p 6380 -h 106.12.115.136
106.12.115.136:6380> set username thanlon  
(error) NOAUTH Authentication required.
106.12.115.136:6380> auth redis   		# 密码错误
(error) ERR invalid password
106.12.115.136:6380> auth redis       # 密码正确
OK

# 也可以在连接redis服务端的时候指定redis启动密码,使用-a参数。但是,不建议,因为直接暴露了密码,redis也会提示你这样可能是不安全的。
thanlon@plus-book:~$ redis-cli -p 6380 -h 106.12.115.136 -a redis
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
106.12.115.136:6380> 

关闭redisk可以直接杀死进程,但这样可能会造成数据丢失。建议使用客户端发起关闭redis服务的,着属于正常关闭redis,可以数据的安全:

# 设置了启动密码,所以被拒绝关闭
thanlon@plus-book:~$ redis-cli -p 6380 -h 106.12.115.136 shutdown
(error) NOAUTH Authentication required.
# 我们带上密码来关闭,这样就成功关闭了
thanlon@plus-book:~$ redis-cli -p 6380 -h 106.12.115.136 -a redis shutdown
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
thanlon@plus-book:~$ 
# 再到服务器确定关闭与否(redis已经被成功关闭)
[root@master ~]# ps -ef |grep redis
root     28786 28722  0 08:57 pts/1    00:00:00 grep --color=auto redis

注意:本文不对redis的具体使用做讲解,redis具体使用以及更多安全、备份等配置可以看我的这篇文章https://blog.csdn.net/Thanlon/article/details/101925916

4.2 Redis主从复制
  • redis集群中数据库的复制是通过主从同步来实现的
  • 主节点(Master)把数据分发给从节点(Slave)
  • 主从同步的好处在于高可用,redis节点有冗余设计

在这里插入图片描述

准备两台机器,我这里使用的是VMware虚拟化两台cetnos机器,ip分别是10.0.0.2和10.0.0.3,跟别作为主、从数据库。就使用这两台机器来做redis的主从同步。另外,为了方便使用,我使用XShell软件来连接我的这两台linux机器。

① 为两天机器安装redis,上文有介绍,在这里不赘述:https://blog.csdn.net/Thanlon/article/details/100941385#41_redis_548

② 配置主(Master)数据库的配置文件

# 查看配置文件有用的信息行
[root@slave redis]# grep -v '#|^$' /usr/local/redis/redis.conf
# 编辑配置文件
[root@master redis]# vim redis.conf
# 修改下面几行为
bind 10.0.0.2      # 绑定本机的ip地址。外网情况下,不要绑定端口,直接bind 0.0.0.0
daemonize yes      # 设置后台进程方式运行
requirepass redis  # 为主redis设置密码
# 查看redis是否设置密码
10.0.0.2:6379> config get requirepass
1) "requirepass" # 关键字 
2) "redis"    	 # 密码
# 可以更改密码
10.0.0.2:6379> config set requirepass "redis"
OK
# 指定此配置文件启动redis
[root@master redis]# redis-server redis.conf

③ 配置从(Slave)数据库的配置文件

# 查看配置文件有用的信息行
[root@slave redis]# grep -v '#|^$' /usr/local/redis/redis.conf
# 编辑从数据库配置文件
[root@master redis]# vim redis.conf
# 修改下面几行为
bind 10.0.0.3             # 绑定本机的ip地址。外网情况下,不要绑定端口,直接bind 0.0.0.0
replicaof 10.0.0.2 6379   # Master的ip和端口
masterauth redis          # Master的密码
# 指定此配置文件启动redis
[root@slave redis]# redis-server redis.conf

④ 主从同步的验证

  • 主服务器的操作
# 登录主redis
[root@master redis]# redis-cli -h 10.0.0.2
# 输入主redis密码
10.0.0.2:6379> auth redis
OK
# 向主redis中添加数据
10.0.0.2:6379> set username thanlon
OK
# 查看主redis中的已经添加的数据
10.0.0.2:6379> keys *
1) "username"
  • 从服务器的操作
# 登录从redis
[root@slave redis]# redis-cli -h 10.0.0.3
# 查看从redis的数据(从redis中一开始没有数据)
10.0.0.3:6379> keys *
1) "username"
# 如果操作(例如删除)从主redis中同步的数据,就会提示数据是只读的
10.0.0.3:6379> del username
(error) READONLY You can't write against a read only replica.

注意:如果两个redis链接不成功,要排查redis是不是同一个版本、是不是都是源码安装或yum安装的。

4.3 Redis多实例配置

redis支持多实例的用法,也就是一个服务器可以运行多个redis服务端。我们这里把只用Master一台服务器就可以了。

# 查看redis下的文件(redis.conf是从源码包中拷贝过来的)
[root@master redis]# ls
bin  dump.rdb  redis.conf
# 修改该配置文件
[root@master redis]# mv redis.conf redis-6379.conf
# 将该配置文件备份(备份与否都可以,此步可忽略)
[root@master redis]# cp redis-6379.conf redis-6379.conf.bak
# 再复制一个redis-6379.conf并命名为redis-6380.conf作为另一个实例
[root@master redis]# cp redis-6379.conf redis-6380.conf 
# 修改redis-6379.conf配置文件中的参数
bind 10.0.0.2
port 6379
daemonize yes
pidfile /var/run/redis_6379.pid
# 修改redis-6380.conf配置文件中的参数
bind 10.0.0.2
port 6380
daemonize yes
pidfile /var/run/redis_6380.pid
# 查看redis的运行状态
[root@master redis]# ps -ef |grep redis
root       6436      1  0 Oct09 ?        00:00:17 redis-server 10.0.0.2:6379
root       6485      1  0 00:55 ?        00:00:00 redis-server 10.0.0.2:6380
root       6498   6402  0 00:56 pts/1    00:00:00 grep --color=auto redis

PS:上面的操作表示两个redis实例的配置已经完成,下面我们将这两个实例做主从同步

# 修改“从数据库”的配置文件redis_6379.conf
[root@master redis]# vim redis-6379.conf 
# 找到replicaof参数修改如下,表示从10.0.0.26379端口同步
replicaof 10.0.0.2 6379
# 开始两个实例服务
[root@master redis]# redis-server redis-6379.conf
[root@master redis]# redis-server redis-6380.conf
# 客户端连接这两个端口的redis服务
[root@master redis]# redis-cli -p 6379 -h 10.0.0.2
[root@master redis]# redis-cli -p 6380 -h 10.0.0.2
# 6379端口所在的redis客户端进行添加数据操作
10.0.0.2:6379> set username thanlon
OK
# 查看6380端口所在的redis客户端的数据,至此配置成功
10.0.0.2:6380> keys *
1) "username"
4.4 Python连接Redis

第一种方式使用的是普通的连接:

# -*- coding: utf-8 -*-
import redis

conn = redis.Redis(host='106.12.115.133', port=6379,password='xxx')
conn.set('name', 'kiku')
print(conn.get('name'))

第二种方式使用的是连接池连接(与线程池是一样的,可以防止重复的连接节省开销,建议使用这种方式):

# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.133', port=6379, max_connections=1000,password='xxx')  # max_connections最大连接数
conn = redis.Redis(connection_pool=pool)
conn.set('name', 'thanlon')
4.5 Python操作Redis

① String操作
redis中的String在内存中按照一个name对应一个value来存储

  • set(name, value, ex=None, px=None, nx=False, xx=False):在redis中设置值,默认不存在则创建存在则修改
ex:过期时间()
px:过期时间(毫秒)
nx:如果设置为True,只有name不存在时,当前set操作才执行(添加)
xx:如果设置为True,只有name存在时,当前set才执行操作(修改)
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.get('name'))  # b'thanlon'
  • setnx(name,value):设置值,只有name不存在时,执行设置操作
  • setex(name,value,time):设置值,time是过期时间(数字秒或timedelta对象)
  • psetex(name,time_ms,value):设置值,time_ms是过期时间(数字秒或timedelta对象)
  • mset(*args,**keargs):批量设置值,如:
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.mset({'name':'thanlon','age':23})
  • get(name):获取值
  • mget(keys,*args):批量获取值,如:
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.mget('name', 'age'))  # [b'thanlon', b'23']
print(conn.mget(['name', 'age']))  # 与上一行等价
  • getset(name,value):设置新值并获取原来的值
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.getset('name', 'kiku'))  # b'thanlon'
  • getrange(key,start,end):获取子序列(根据字节获取,非字符),start表示起始位置(字节);end表示结束位置(字节)。Python2中代表的是字符。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.getrange('name', 0, 2))  # 三个字符:b'kik'
conn.set('name', '奈何先生')
print(conn.getrange('name', 0, 2))  # 以中文字节形式获取中文呢:b'\xe5\xa5\x88'
  • setrange(name,offset,value):修改字符串内容,从指定字符串索引开始后替换(新值太长时,则向后添加)。offset表示字符串索引,字节(一个汉字三个字节);value是要设置的值。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.set('name', 'thanlon')
print(conn.get('name'))
conn.setrange('name', 7, 'kiku')  #
print(conn.get('name'))  # b'thanlonkiku'
  • setbit(name,offset,value):对name对应的值的二进制表示的位进行操作,修改二进制位。name是redis中的name,offseet是位的索引(将值变换成二进制后再进行索引),value的值只能是1或0。
  • getbit(name,offset):获取name对应的值的二进制表示中的某位的值,可用来判断获取的是0还是1
  • bitcount(key,start=None,end=None):获取二进制数中有多少个1
  • bitop(operation,dest,*keys):获取多个值,并将值作做位运算,将最后的结果保存至新的name对应的值
参数:
operation--------AND(),OR(),NOT(),XOR(异或)
dest---------------新的redisd的name
*keys------------- 要查找的redis的name
例如:获取n1,n2,n3对应的值,然后将所有的值做位运算(求并集),然后将结果保存在new_name对应的值中
bitop('AND','new_name','n1','n2','n3')
  • strlen(name):返回name对应值的字节长度(一个汉字3个字节)
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.set('name', 'thanlon')
print(conn.strlen('name'))
conn.set('name', '奈何先生')  # 一个汉字三个字节
print(conn.strlen('name'))
  • incr(self,name,amount=1):自增,name对应的值,当name不存在时,则创建name=amount,否则则自增。同incrby(self,name,amount=1)
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.get('age'))  # b'24'
conn.incr('age')
print(conn.get('age'))  # b'25'
  • decr(self,name,amount=1):自减,name对应的值,当name不存在时,则创建name=amount,否则则自减。同decrby(self,name,amount=1)。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.get('age'))  # b'25'
conn.decr('age')
print(conn.get('age'))  # b'24'
  • incrbyfloat(self,name,amount=1.0):自增,只不过可以自增浮点数,如果amount的值是负的,则表示自减,注意没有decrbyfloat(self,name,amount=1.0)
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.get('no'))  # None
conn.incrbyfloat('no')
print(conn.get('no'))  # b'1'
conn.incrbyfloat('no', 0.1)
print(conn.get('no'))  # b'1.1'
  • append(key,value):在name对应的值后面追加内容
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.set('name', 'thanlon')
print(conn.get('name'))  # b'thanlon'
conn.append('name', ' love kiku')
print(conn.get('name'))  # b'thanlon love kiku'

② hash操作
redis中的hash类型可以看成具有String key和String Value的map容器。所以,该类型非常适合于存储值对象的信息,如:username,password,age,sex等。我们可以使用redis的hash来存储session。

  • hset(name,key,value):name对应的hash中设置一个键值对,参数name是redis的name,key是name对应的hash中的key,value是name对应的hash中的value。hsetnx(name,key,value)表示当name对应的hash中不存在当前key时则创建(相当于添加)
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
conn.hset('admin','name','thanlon')
conn.hset('admin','age',23)
  • hmset(name,mapping):在name对应的hash中批量设置键值对
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.hmset('admin', {'name': 'thanlon', 'age': 23, 'sex': '男'})
  • hget(name,key):在name对应的hash中获取key的值value
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.hset('admin','name','thanlon')
conn.hset('admin','age',23)
print(conn.hget('admin','name'))
print(conn.hget('admin','age'))
  • hmget(name,keys,*args):在name对应hash中获取多个key的值value
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
conn.hmset('admin', {'name': 'thanlon', 'age': 23, 'sex': '男'})
print(conn.hmget('admin', 'name', 'age', 'sex'))  # [b'thanlon', b'23', b'\xe7\x94\xb7']
  • hgetall(name):获取name对应的hash的所有键值,以字典类型返回
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.hgetall('admin'))  # {b'name': b'thanlon', b'age': b'23', b'sex': b'\xe7\x94\xb7'}
  • hlen(name):获取name对应的hash中键值对的个数
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.hlen('admin'))  # 3
  • hkeys(name):获取name对应的hash中所有key的值,列表形式返回
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.hkeys('admin'))  # [b'name', b'age', b'sex']
  • hvals(name):获取name对应的hash中所有value值
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.hvals('admin'))#[b'thanlon', b'23', b'\xe7\x94\xb7']
  • hexits(name,key):检查name对应的hash是否存在当前输入的key
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.hexists('admin', 'age'))  # True
  • hdel(name,*keys):将name对应的hash中的指定key的键值对删除
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
print(conn.hdel('admin', 'sex', 'age'))  # 2
  • hincrby(name,key,amount=1):自增name对应的hash值中的指定key值,不存在则创建key=mount
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
conn.hincrby('admin',1,amount=2)
print(conn.hget('admin', 1))  # b'2'
  • hincrbyfloat(name,key,amount=1.0):
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
conn.hincrbyfloat('admin', 2, amount=0.1)
print(conn.hget('admin', 2))  # b'1.1'
  • hscan(name,cursor=0,match=None,count=None):增量式迭代获取,对于数据大的数据非常有用。hscan可以实现分片地获取数据,并非一次性将数据全部获取完放置内存被撑爆。name是redis中的name,cursor是游标(基于游标分批获取数据),match是匹配指定的key,默认None,表示所有key,count是每次分片最少获取的个数,默认是None表示采用redis的默认分片个数。如:
# 第一次:
cursor1,data1 = conn.hscan('xxx',cursor=0,match=None,count=None)
# 第二次:
cursor2,data2 = conn.hscan('xxx',cursor=cursor1,match=None,count=None)
……
直到返回值cursor的值为0时,表示数据已经通过分片获取完毕。
  • hscan_iter(name,match=None,count=None):利用yield封装hscan创建生成器,实现分批去redis中获取数据
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.101', port=6379, password='xxx')
conn = redis.Redis(connection_pool=pool)
for item in conn.hscan_iter('admin'):
    print(item)
# (b'name', b'thanlon')
# (b'sex', b'man')
# (b'age', b'23')

③ List操作

  • lpush(name,values):在name对应的list中添加元素,每个新的元素添加到列表的最左边
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.lpush('names', 'thanlon')
conn.lpush('names', 'kiku')
print(conn.lrange('names', 0, -1))  # [b'kiku', b'thanlon']
  • lpushx(name,value):在name对应的list中添加元素,只有name存在时,值添加到列表的最左边
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.lpushx('names', '奈何先生')
  • llen(name):name对应的list元素的个数
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'kiku', b'thanlon']
print(conn.llen('names'))  # 2
  • linsert(name,where,refvalue,value):在name对应的列表的某一个值前或后插入一个新值。where只能写两个值一个是BEFORE,一个是AFTER。refvalue是标杆值,即:在它的前后插入数据,value是要插入的数据。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'kiku', b'thanlon']
conn.linsert('names', 'after', 'thanlon', 'john')
conn.linsert('names', 'before', 'thanlon', 'Tom')
print(conn.lrange('names', 0, -1))  # [b'kiku',b'Tom', b'thanlon', b'john']
  • lset(name,index,value):对name对应的list中某一个索引位置重新赋值,index是list的索引位置,value是要设置的值。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'kiku', b'thanlon']
conn.lset('names', 0, 'Tom')
print(conn.lrange('names', 0, -1))  # [b'Tom', b'john']
  • lrem(name,count,value):在name对应的list中删除指定的值,count=0表示删除列表中所有指定的值,count=2表示从前到后删除2个,count=-2表示从后向前删除2个。
# -*- coding: utf-8 -*-
# 删除所有指定的“Tom”
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'Tom', b'Tom', b'Thanlon', b'kiku', b'john']
conn.lrem('names', 0, 'Tom')  # 删除所有的Tom
print(conn.lrange('names', 0, -1))  # [b'Thanlon', b'kiku', b'john']
# 从前到后删除2个“Tom”
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'Tom', b'Tom', b'Thanlon', b'kiku', b'john', b'Tom']
conn.lrem('names', 2, 'Tom')
print(conn.lrange('names', 0, -1))  # [b'Thanlon', b'kiku', b'john', b'Tom']
  • lpop(name):在name对应的列表的最左侧获取第一个元素并删除,返回值则是第一个元素。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lpop('names'))  # b'kiku'
  • lindex(name,index):在name对应的列表中根据索引获取列表中的元素
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lindex('names', 0))  # b'kiku'
print(conn.lindex('names', 1))  # b'thanlon'
  • lrange(name,start,end):在name对应的列表分片获取数据。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'kiku', b'thanlon']
  • ltrim(name,start,end):在name对应的列表中移除没有在start~end索引之间的值。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.lrange('names', 0, -1))  # [b'Thanlon', b'kiku', b'john', b'Tom']
conn.ltrim('names', 0, 1)  # 0~1
print(conn.lrange('names', 0, -1))  # [b'Thanlon', b'kiku']
  • rpoplpush(src,dst):从一个列表取出最右边的元素,同时将其添加至另外一个列表的最左边,src是要取数据的列表的name,dst是要添加数据列表name。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.lpush('developers', 'Thanlon')
conn.lpush('developers', 'Kiku')
conn.lpush('masters', 'John')
conn.lpush('masters', 'Maria')
print(conn.lrange('developers', 0, -1))  # [b'Kiku', b'Thanlon']
print(conn.lrange('masters', 0, -1))  # [ b'Maria', b'John']
conn.rpoplpush('developers', 'masters')  # 将developers列表中的最右边元素添加到masters列表中元素的最左边
print(conn.lrange('developers', 0, -1))  # [b'Kiku']
print(conn.lrange('masters', 0, -1))  # [b'Thanlon', b'Maria', b'John']
  • blpop(keys,timeout):将多个值进行排列,按照从左到右去pop对应列表的元素。timeout是超时时间,当元素所有列表的数据获取完后,阻塞等待列表内有数据的时间(秒),0表示永远阻塞。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.lpush('names', 'Thanlon')
conn.lpush('names', 'Kiku')
conn.lpush('names', 'John')
conn.lpush('names', 'Maria')
print(conn.lrange('names', 0, -1))  # [b'Maria', b'John', b'Kiku', b'Thanlon']
print(conn.blpop('names'))  # (b'names', b'Maria')
print(conn.lrange('names', 0, -1))  # [b'John', b'Kiku', b'Thanlon']
print(conn.blpop('names'))  # (b'names', b'John')
print(conn.lrange('names', 0, -1))  # [b'Kiku', b'Thanlon']
  • brpoplpush(src,dst,timeout=0):从一个列表取出最右边的元素,同时将其添加至另外一个列表的最左边。前面有介绍rpoplpush方法,这里只是可以添加超时时间。
  • 自定义增量迭代:**由于redis类库中没有提供对列表元素的增量迭代,如果想要循环name对应的列表的所有元素,就需要获取name对应的列表,然后循环列表。但是如果列表非常大,就有可能在获取name对应的列表时撑爆内存,所以,有必要自定义增量迭代功能。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)

def list_iter(name):
    list_count = conn.llen(name)
    for index in range(0, list_count):
        yield conn.lindex(name, index)

for item in list_iter('names'):
    print(item)
# b'Kiku'
# b'Thanlon'

④ Set操作:Set集合不允许重复的列表

  • sadd(name,values):对name对应的集合添加元素
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.sadd('employees', 'thanlon')
conn.sadd('employees', 'kiku')
  • scard(name):获取name对应集合中的元素个数
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.scard('employees'))
  • sadiff(keys,*args):在第一个name对应的集合中且不在其它name对应的集合中的元素集合
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
# employees:kiku,thanlon
# bosses:Tom,John
print(conn.sdiff('employees', 'bosses'))  # {b'kiku', b'thanlon'}
  • sdiffstore(dest,keys,*args):获取在第一个name对应的集合中且不在其它name对应的集合中的元素集合,再将其新加入到dest对应的集合中。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
# employees:kiku,thanlon
# bosses:Tom,John
print(conn.sdiffstore('dest', 'employees', 'bosses'))  # dest:{b'kiku', b'thanlon'}
  • sinter(keys,*args):获取多个name对应集合的交集。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'Kiku', b'Tom'}
print(conn.smembers('bosses'))  # {b'John', b'Thanlon', b'Tom'}
print(conn.sinter('employees', 'bosses'))  # {b'Tom'}
  • sinterstore(dest,keys,*args):获取多一个name对应集合的交集,再将其加入到dest对应的集合中
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'Kiku', b'Tom'}
print(conn.smembers('bosses'))  # {b'John', b'Thanlon', b'Tom'}
conn.sinterstore('dest', 'employees', 'bosses')
print(conn.smembers('dest'))  # {b'Tom'}
  • sismember(name,value):检查value是否是name对应的集合成员。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'thanlon', b'kiku'}
print(conn.sismember('employees', 'thanlon'))  # True
  • smembers(name):获取name对应集合的所有成员。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'kiku', b'thanlon'}
  • smove(src,dst,value):把某一个成员从一个集合(src)中移到另外一个集合(dst)中。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'Thanlon', b'Kiku'}
print(conn.smembers('bosses'))  # {b'John', b'Tom'}
conn.smove('employees', 'bosses', 'Thanlon')
print(conn.smembers('employees'))  # {b'Thanlon', b'Kiku'}
print(conn.smembers('bosses'))  # {b'John', b'Tom'}
  • spop(name):从集合的右侧(尾部)移除一个成员,并将其返回
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('bosses'))  # {b'John', b'Thanlon', b'Tom'}
print(conn.spop('bosses'))  # b'Thanlon'
print(conn.smembers('bosses'))  # {b'Tom', b'John'}
  • srandmember(name,number):从name对应的集合中随机获取numbers个元素
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
# 如果nmber没有指定则默认是1
print(conn.srandmember('employees'))  # b'Kiku'
print(conn.srandmember('employees', number=2))  # [b'Kiku', b'Tom']
  • srem(name,values):在name 对应的集合中删除某些值
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'Kiku', b'Tom'}
conn.srem('employees', 'Kiku')
print(conn.smembers('employees'))  # {b'Tom'}
  • sunion(keys,*args):获取多个name对应集合的并集
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'Kiku', b'Tom'}
print(conn.smembers('bosses'))  # {b'John', b'Tom'}
print(conn.sunion('employees', 'bosses'))  # {b'Kiku', b'John', b'Tom'}
  • sunionstore(dest,keys,*args):获取多个name对应集合的并集,并将结果保存到dest对应的集合中
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.smembers('employees'))  # {b'Kiku', b'Tom'}
print(conn.smembers('bosses'))  # {b'John', b'Tom'}
print(conn.sunionstore('dest', 'employees', 'bosses'))
print(conn.smembers('dest'))  # {b'Kiku', b'John', b'Tom'}
  • sscan(name,cursor=0,match=None,count=None):同字符串操作,用于迭代分批获取数据,避免内存消耗太大。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
# 第一次:
cursor1, data1 = conn.sscan('employees', cursor=0, match=None, count=1)
print(data1)  # [b'Thanlon']
# 第二次:
cursor2, data2 = conn.sscan('employees', cursor=cursor1, match=None, count=2)
print(data2)  # [b'John', b'Kiku', b'Tom', b'Maria']
  • sscan_iter(name,match=None,count=None):同字符串操作,用于迭代分批获取数据,避免内存消耗太大
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
for item in conn.sscan_iter('employees', match=None, count=None):
    print(item)
# b'Thanlon'
# b'John'
# b'Kiku'
# b'Tom'
# b'Maria'

⑤ 有序集合
在集合的基础上,为每个元素排序。元素的排序需要根据另外一个值来进行比较,所以对于有序集合,每个元素有两个值,即值和分数,分数专门用来做排序。

  • zdd(name,mapping,nx,xx,ch,incr):在name对应的有序集合中添加元素(有两种方式)
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('zset1', {'thanlon': 1, 'kiku': 2, 'Tom': 0})
  • zcard(name):获取name对应的有序集合元素的数量
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.zrange('zset1', 0, -1))  # [b'Tom', b'thanlon', b'kiku']
print(conn.zcard('zset1'))  # 3
  • zcount(name,min,max):获取name对应的有序集合中分数在[min,max]之间的数
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.zrange('zset1', 0, -1))  # [b'Tom', b'thanlon', b'kiku']
print(conn.zcount('zset1', 0, 1))  # 2
  • zincrby(name,amount,value):自增name对应的有序集合的name对应的分数
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.zrange('zset1', 0, -1))  # [b'Tom', b'thanlon', b'kiku']
conn.zincrby('zset1', 10, 'thanlon')
print(conn.zrange('zset1', 0, -1))  # [b'Tom', b'kiku', b'thanlon']
  • zrange(name,start,end,desc=False,withscores=False,score_cast_func=float):按照索引获取name对应的有序集合的元素。参数:start是有序集合索引起始位置;end是有序集合索引结束位置 ;desc是排序规则,默认按照从小到大排序;withscores是是否获取元素的分数,默认是获取元素的值;score_cast_func是对分数进行数据转换的函数。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('name', {'Thanlon': 1, 'Kiku': 2, 'Tom': 0, 'Maria': -2})
print(conn.zrange('name', 0, 0))  # [b'Maria']
print(conn.zrange('name', 0, 0, desc=True))  # [b'Kiku']
print(conn.zrange('name', 0, 1))  # [b'Maria', b'Tom'], 0, 1只决定取的数量,不表示分数范围
  • zrank(name,value):获取值在name对应的有序集合中的排行(从0开始)
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('name', {'Thanlon': 1, 'Kiku': 2, 'Tom': 0, 'Maria': -2})
print(conn.zrange('name', 0, -1))  # [b'Maria', b'Tom', b'Thanlon', b'Kiku']
print(conn.zrank('name', 'Thanlon'))  # 2,表示排第三位(是从0开始的)
  • zrangebylex(self, name, min, max, start=None, num=None):当有序集合的所有成员都具有相同的分数,有序集合的元素会根据成员的值来进行排序。对集合中的每个成员进行逐个字节的对比,并按照从低到高的顺序,返回排序后的集合成员。min是左区间,+和-分别表示正无限和负无限,(或者表示开区间,[表示 闭区间;max是右区间的值;start是对结果进行分片处理的索引位置,num是对结果进行分片处理,索引后面有num个元素。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 100, 'Kiku': 100, 'Maria': 100}) 
#  # 字节转换成数字来一个一个比较
print(conn.zrange('students', 0, -1)) # [b'Kiku', b'Maria', b'Thanlon']
ret = conn.zrangebylex('students', '-', '[Maria') # [b'Kiku', b'Maria']
ret2 = conn.zrangebylex('students', '-', '(Maria')# [b'Kiku']
print(ret)
print(ret2)
  • zrem(self, name, *values):删除name对应的有序集合值是value的成员
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 100, 'Kiku': 100, 'Maria': 100})
print(conn.zrange('students', 0, -1))  # [b'Kiku', b'Maria', b'Thanlon']
conn.zrem('students', 'Thanlon')  # 删除name对应有序集合中的Thanlon
print(conn.zrange('students', 0, -1))  # [b'Kiku', b'Maria']
  • zremrangebyrank(name,min,max):根据排行来删除
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 0, 'Kiku': 1, 'Maria': 2})
print(conn.zrange('students', 0, -1))  # [b'Kiku', b'Maria', b'Thanlon']
conn.zremrangebyrank('students', 0, 1)  # 删除排名在第一名到第二名
print(conn.zrange('students', 0, -1))  # [b'Maria']
  • zremrangebyscore(name, min, max):根据分数范围删除有序集合中的元素加
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 59, 'Kiku': 100, 'Maria': 100})
print(conn.zrange('students', 0, -1))  # [b'Kiku', b'Maria', b'Thanlon']
# 根据分数来删除有序集合里面的值,这里删除的是分数在0~59之间的,只有Thanlon是满足加的,所以删除它
conn.zremrangebyscore('students', 0, 59)
print(conn.zrange('students', 0, -1))  # [b'Kiku', b'Maria']
  • zremrangebylex(name, min, max):根据指定的范围的值删除
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 100, 'Kiku': 100, 'Maria': 100})
#  # 字节转换成数字来一个一个比较
print(conn.zrange('students', 0, -1, withscores=True))  # [(b'Kiku', 100.0), (b'Maria', 100.0), (b'Thanlon', 100.0)]
conn.zremrangebylex('students', '-', '[Maria')
print(conn.zrange('students', 0, -1, withscores=True))  # [(b'Thanlon', 100.0)]
  • zscore(name, value):根据name对应有序集合中的value对应的分数
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.zscore('students', 'Maria'))  # 100.0
  • zinterstore(self, dest, keys, aggregate=None):获取两个有序集合的交集,如果遇到相同的值不同的分数,则按照aggregate进行操作,aggregate的值有SUM 、MIN和MAX,默认是SUM。使用小写sum也对,不区分大小。
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.zrange('bosses', 0, -1, withscores=True))  # [(b'Jack', 1.0), (b'Thanlon', 4.0)]
print(conn.zrange('employees', 0, -1, withscores=True))  # [(b'thanlon', 0.0), (b'Jack', 2.0)]
conn.zinterstore('dest', ['bosses', 'employees'])
print(conn.zrange('dest', 0, -1, withscores=True))  # [(b'Jack', 3.0)]
  • zunionstore( dest, keys, aggregate=None):获取两个有序集合的并集,如果遇到相同的值不同的分数,则按照aggregate进行操作,aggregate的值有SUM 、MIN和MAX
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.zrange('bosses', 0, -1, withscores=True))  # [(b'Jack', 1.0), (b'Thanlon', 4.0)]
print(conn.zrange('employees', 0, -1, withscores=True))  # [(b'thanlon', 0.0), (b'Jack', 2.0)]
conn.zunionstore('dest', ['bosses', 'employees'])
print(conn.zrange('dest', 0, -1, withscores=True))  # [(b'thanlon', 0.0), (b'Jack', 3.0), (b'Thanlon', 4.0)]
  • zscan(name, cursor=0, match=None, count=None,score_cast_func=float):
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 59, 'Kiku': 100, 'Maria': 100})
cursor1, data1 = conn.zscan('students', cursor=0, match=None, count=None, score_cast_func=float)
print(data1)  # [(b'Thanlon', 59.0), (b'Kiku', 100.0), (b'Maria', 100.0)]
  • scan_iter(name, match=None, count=None,score_cast_func=float):
# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.zadd('students', {'Thanlon': 59, 'Kiku': 100, 'Maria': 100})
for item in conn.zscan_iter('students', score_cast_func=float):
    print(item)
# (b'Thanlon', 59.0)
# (b'Kiku', 100.0)
# (b'Maria', 100.0)

⑥ 其它常用操作

  • delete(*names):删除redis任意一种数据类型
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.delete('teachers')
  • exists(*names):检查redis中的name是否存在
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.exists('students'))  # 1表示存在
  • keys(pattern=’*’):默认是所有的keys
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.keys())  # [b'bosses', b'dest_tmp', b'students']
print(conn.keys('bo?ses'))  # [b'bosses']
print(conn.keys('b*s'))  # [b'bosses']
print(conn.keys('b[ao]sses'))  # [b'bosses'],要么匹配bosses,否则匹配basses
  • expire(name, time):为某个redis的name设置超时时间,redis中设置超时时间的粒度比较小,只能对name对应的数据的整体做超时时间。比如,hash中一个name对应多个key-value,我们只能对这多个key-value整体设置超时时间,不可以对key-value做超时时间。
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.expire('students', time=100)  # 超时时间必须是integer类型的
  • rename(src, dst):
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.keys())  # [b'employees', b'dest_tmp']
conn.rename('employees', 'bosses')
print(conn.keys())  # [b'bosses', b'dest_tmp']
  • move(name, db):
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.keys())  # [b'bosses', b'dest_tmp']
conn.move('bosses', 15)
print(conn.keys())  # [b'dest_tmp']
  • randomkey():随机获取redis中的name
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.randomkey())#b'dest_tmp'
  • type(name):获取redis中name的数据类型
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.type('dest_tmp'))  # b'set'
  • scan(cursor=0, match=None, count=None):
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
print(conn.scan())  # (0, [b'dest_tmp'])
  • scan_iter(match=None, count=None):
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
for item in conn.scan_iter():
    print(item)
# b'dest_tmp'
4.6 管道

管道是用来做事务的,python连接redis在执行每次请求都会创建(连接池申请连接)和断开(归还连接池)一次连接操作,如果想要在一次请求中指定多个命令,则可以使用pipline实现一次请求指定多个命令,并且默认情况下一次pipline是原子性操作。

# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
pipe = conn.pipeline(transaction=True)
pipe.set('name', 'thanlon')
pipe.set('name', 'kiku')
pipe.execute()  # 整体提交,如果一条发生错误,整个就不会生效
4.7 Redis发布和订阅

发布者:

# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.publish('news', 'Pay Attention To!')

接收者1:

# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
pb = conn.pubsub()
pb.subscribe('news')
while True:
    print(pb.parse_response()) # [b'message', b'news', b'Pay Attention To!']

接收者2:

# -*- coding: utf-8 -*-
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
pb = conn.pubsub()
pb.subscribe('news')
while True:
    print(pb.parse_response()) # [b'message', b'news', b'Pay Attention To!']
4.8 Redis面试题
  • redis和memcached的比较:redis不仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;memcached不支持主从,redis支持主从,可用于数据的备份,即master-slave模式的数据备份;Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
  • redis中数据库默认有多少个数据库及作用:16个数据库;所有数据库保存到结构 redisServer 的一个成员 redisServer.db 数组中。当我们选择数据库 select number 时,程序直接通过 redisServer.db[number] 来切换数据库。有时候当程序需要知道自己是在哪个数据库时,直接读取 redisDb.id 即可。
  • python如何操作redis模块:安装redis模块(或库),然后使用redis模块与redis服务端创建连接之后,进行一系列redis的操作
  • 如何循环显示redis中某个列表比较大的数据量的每一个值:可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个redis实例中,降低对单个redis的io影响
  • redis如何实现主从复制和数据同步机制:在从redis数据库(slave)配置文件中添加replicaof参数用于绑定主库(master)的ip和端口;在slave启动时,会向其master发送一条SYNC消息,master收到slave的这条消息之后,将可能启动后台进程进行备份,备份完成之后就将备份的数据发送给slave。
  • redis中的sentinel的作用:sentinel是一个独立运行的进程,用于监控master-slave集群,一旦发现master宕机,会进行自动切换到slave。[用于监控redis集群中Master状态的工具]
  • 如何实现redis集群:采用主从模式,通过增加slave来增加并发读的能力,master的写能力是个瓶颈;还有hash slot的模式,将Redis的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。
  • redis中默认有多少个哈希槽:16384个哈希槽
  • 简述redis的有哪几种持久化策略及比较:redis有RDB和AOF两种持久化策略,RDF相当于快照,保存数据块,还原数据快,适合灾难恢复。但是RDB只要符合要求就会照快照,会占用一部分系统资源,适用于内存比充足的机器。AOF是持续化占用极少的内存资源,采用日志记录redis操作。但是日志文件会特别大,不适用于灾难恢复,恢复效率远远低于RDB。适用于内存较小的机器。[简单回答:RDF是快照形式的,直接把内存中的数据保存到一个dump文件中;AOF是把所有的对redis的服务器进行修改的命令都存到一个文件里]
  • 列举redis支持的过期策略:定期删除和惰性删除策略,定期删除指的是每隔一定时间就抽取一些设置了过期时间的key,检查是否过期,如果过期了就把删除。惰性策略是在获取key的时候,如果此时的key已经过期,就会直接删除,不会返回任何东西。当然,内存中这还会存大量过期的的key,我们还可以使用内存淘汰机制。
  • 列举内存淘汰机制
volatile-lru 				从已设置过期时间的数据集中挑选最近最少使用的数据淘汰(默认)
volatile-ttl					从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random 		从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru					从数据集中挑选最近最少使用的数据淘汰(最常用的)
allkeys-random		从数据集中任意选择数据淘汰
no-enviction				禁止驱逐数据
  • mysql里有两千万数据,redis中只存二十万的数据,如何保证 Redis 中都是热点数据:redis 内存数据集大小上升到一定大小的时候,就会实施数据淘汰策略。可参考上一题理解着回答。
  • 写代码,基于redis的列表实现先进先出、后进先出队列和优先级队列
# 先进先出
import redis
'''
可以使用lpush和rpop或rpush和lpop
'''
pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.lpush('employees', 'thanlon')
conn.lpush('employees', 'kiku')
conn.lpush('employees', 'maria')
conn.lpush('employees', 'jack')
conn.lpush('employees', 'michael')
conn.lpush('employees', 'angle')
print(conn.lrange('employees', 0, -1))
print(conn.rpop('employees'))
print(conn.rpop('employees'))
print(conn.rpop('employees'))
print(conn.rpop('employees'))
print(conn.rpop('employees'))
print(conn.rpop('employees'))
'''
[b'angle', b'michael', b'jack', b'maria', b'kiku', b'thanlon']
b'thanlon'
b'kiku'
b'maria'
b'jack'
b'michael'
b'angle'
'''
# 后进先出
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
conn.lpush('employees', 'thanlon')
conn.lpush('employees', 'kiku')
conn.lpush('employees', 'maria')
conn.lpush('employees', 'jack')
conn.lpush('employees', 'michael')
conn.lpush('employees', 'angle')
print(conn.lrange('employees', 0, -1))
print(conn.lpop('employees'))
print(conn.lpop('employees'))
print(conn.lpop('employees'))
print(conn.lpop('employees'))
print(conn.lpop('employees'))
print(conn.lpop('employees'))
'''
[b'angle', b'michael', b'jack', b'maria', b'kiku', b'thanlon']
b'angle'
b'michael'
b'jack'
b'maria'
b'kiku'
b'thanlon'
'''
# 优先级队列,采用任务入队的时候设置权值
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
'''
只执行一次添加元素
'''
# conn.lpush('bosses', 'thanlon')
# conn.set('bosses_score_thanlon', 2)
# conn.lpush('bosses', 'kiku')
# conn.set('bosses_score_kiku', 3)
# conn.lpush('bosses', 'tom')
# conn.set('bosses_score_tom', -1)

# 原来的队列
print(conn.lrange('bosses', 0, -1))  # [b'tom', b'kiku', b'thanlon']
# 排序后的任务队列
print(conn.sort('bosses', by='bosses_score_*'))  # [b'tom', b'thanlon', b'kiku']
# 取出优先级最高的任务
high_work = conn.sort('bosses', start=0, num=1, by='bosses_score_*', desc=True)
print(high_work)  # [b'kiku']
# 移除这个任务
conn.lrem('bosses', 0, high_work[0].decode('utf-8'))
'''
# 第一次
[b'tom', b'kiku', b'thanlon']
[b'tom', b'thanlon', b'kiku']
[b'kiku']第一优先级任务
# 第二次
[b'tom', b'thanlon']
[b'tom', b'thanlon']
[b'thanlon']第二优先级任务
[b'tom']
[b'tom']
[b'tom']第三优先级任务
'''
  • 如何基于redis实现消息队列:使用lpush向list的左端推送数据(发送消息),使用rpop从list的右端接收数据(接收消息)。
  • 如何基于redis实现发布和订阅:使用publish来发布消息,使用subscribe来接收订阅,获取订阅消息。
  • 消息队列和发布订阅的区别:“发布/订阅”模式包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息。【 区别一:前者通过key队列方式实现,取出就删掉了,其他进程也取不到,阻塞进程。订阅发布可以支持多客户端获取同一个频道发布的消息。区别二:前者消息不处理会缓存在列表,后者不处理的话消息就丢失了。】
  • 什么是codis及其作用:codis是一个分布式redis解决方案,可以用于redis的扩容、支持在线数据迁移,可以自动进行数据分配。codis比较适合那种数据库比较大,但并发量不是特别高的系统。
  • 什么是twemproxy及其作用:twemproxy是一种代理分片机制,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。可以解决了单个Redis实例承载能力的问题,可用来扩张redis,Twemproxy需要更多的硬件资源和在redis性能有一定的损失(twitter测试约20%),但是能够提高整个系统的高可用(HA)也还是不错的。回答的时候说类似于nginx,是一种代理的存在。[twemproxy是 Twtter 开源的一个 Redis 和 Memcache 代理服务器,主要用于管理 Redis 和 Memcached 集群,减少与Cache 服务器直接连接的数量。]
  • 写代码实现redis事务操作
import redis

pool = redis.ConnectionPool(host='106.12.115.136', port=6390, password='redis6390')
conn = redis.Redis(connection_pool=pool)
pipe = conn.pipeline(transaction=True)
pipe.set('name', 'thanlon')
pipe.set('age', 23)
pipe.execute()
  • redis中的watch命令的作用:watch( watch key[key...])命令用于在进行事务操作的最后一步(也就是在执行exec 之前)对某个key进行监视。如果这个被监视的key被改动,那么事务就被取消,否则事务正常执行。一般在mulit 命令前就用watch命令对某个key进行监控,如果想让key取消被监控,可以用unwatch命令。【当某个事务需要按条件执行时,就要使用这个命令将给定的键设置为受监控的】
  • 基于redis如何实现商城商品数量计数器:使用redis的Incr自增和desr自减命令可以实现商城商品数量计数器。
  • 简述redis分布式锁redlock的实现机制:互斥,任何时刻只能有一个client获取锁。释放死锁,即使锁定资源的服务崩溃或者分区,仍然能释放锁。容错性,只要多数redis节点(一半以上)在使用,client就可以获取和释放锁。
  • 什么是一致性哈希,Python中是否有相应模块:一致性哈希简而言之就是不仅仅数据做哈希,机器也做哈希;python中有相应的模块,即:hashlib模块。
  • 如何高效的找到redis中所有以某某开头的key:使用keys命令的话,如果数据量比较大,可能会卡顿,不适用于生产环境,应该使用基于游标的迭代器来做。
5. nginx相关
5.1 web服务基础

① DNS解析流程
在这里插入图片描述

用户输入域名
本地hosts文件(/etc/host)中查找解析记录
本地DNS缓存中查找记录
指定的公网DNS服务器(/etc/resolv.conf),通过公网DNS服务器查找记录
将此记录缓存到本地DNS中,用于下次加速解析
浏览器访问域名对应的ip,发送http请求

② http协议
http协议的全称是HyperText Tansfer Protocol,也就是超文本传输协议,它是互联网最常见的协议。http协议最重要的是www(World Wide Web)服务,也叫web服务器,中文叫“万维网”。web服务端口默认是80,另外一个加密的www服务应用https的默认端口是443。

# 访问某个域名,获取该域名请求体信息
[root@master ~]# curl -I baidu.com
HTTP/1.1 200 OK
Date: Sat, 05 Oct 2019 12:48:33 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Sun, 06 Oct 2019 12:48:33 GMT
Connection: Keep-Alive
Content-Type: text/html

http协议诞生以来有若干个版本,主要是http/1.0 http/1.1。http/1.0规定浏览器和服务器只能保持短暂的连接,浏览器的每次请求都需要和服务器建立一个TCP连接,服务器完成请求后即断开TCP连接,服务器不跟踪每个链接,也不记录请求。http/1.1是对HTTP的缺陷进行重点修复,从可扩展性,缓存,带宽优化,持久连接,host头,错误通知等访问改进。http/1.1支持长连接,增加了更多的请求头和响应头信息,例如配置请求头的Connection的值为keep-alive,表示请求结果返回后保持连接。

③ https请求的方法

GET    请求指定的页面信息,并返回实体主体。
HEAD    类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
POST    向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
PUT    从客户端向服务器传送的数据取代指定的文档的内容。
DELETE    请求服务器删除指定的页面。
CONNECT    HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS    允许客户端查看服务器的性能。
TRACE    回显服务器收到的请求,主要用于测试或诊断。

④ http状态码

HTTp状态码表示web服务器响应http请求状态的数字代码
常见状态码以及作用是
1**    信息,服务器收到请求,需要请求者继续执行操作
2**    成功,操作被成功接收并处理
3**    重定向,需要进一步的操作以完成请求
4**    客户端错误,请求包含语法错误或无法完成请求
5**    服务器错误,服务器在处理请求的过程中发生了错误

⑤ http状态码的命令查看

[root@master ~]# curl -I www.baidu.com
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 277
Content-Type: text/html
Date: Sat, 05 Oct 2019 12:48:45 GMT
Etag: "575e1f7c-115"
Last-Modified: Mon, 13 Jun 2016 02:50:36 GMT
Pragma: no-cache
Server: bfe/1.0.8.18

⑥ http报文
http请求由请求行、请求头部、空行、请求报文主体几个部分组成。http报文是HTTP应用程序之间发送的数据块。这些数据块以一些文本形式的元信息开头,这些信息描述了报文的内容及含义,后面跟着可选的数据部分。这些报文都是在客户端、服务器和代理之间流动。

  • 请求报文的格式
起始行:<method> <request-URL> <version>
头部:<headers>
主体:<entity-body>
  • GET请求报文
GET /books/?sex=man&name=Professional HTTP/1.1
Host: www.example.com  主机名
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)Gecko/20050225 Firefox/1.0.1  客户端类型
Accept-Encoding:gzip,deflate    支持压缩
Accept-Language:zh-cn  支持语言类型
Connection: Keep-Alive  长链接
  • 请求行:请求报文第一行,表示客户端想要什么,由请求方法 url 协议版本组成。
  • 请求头部:请求头由关键字:值组成,通过客户端把请求相关信息告诉服务器。
  • 空行:请求头信息之后是一个空行,发送回车和换行符,通知web服务器以下没有请求头信息了。
  • 请求报文主体
POST / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive

sex=man&name=Professional  

请求体中包含了要发送给web服务器的数据信息,请问报文主体不用于get方法,而是用于post方法。post方法适用于客户端填写表单的场合。

⑦ http响应报文
http 响应与 http 请求相似,http响应也由3个部分构成,分别是:状态行、响应头(Response Header)和响应正文

  • 状态行:协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。用来说明服务器响应客户端的状况,一般分为协议版本号,数字状态码,状态情况。
  • 响应头部
常见响应头信息
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Mon, 13 Aug 2018 06:06:54 GMT
Expires: Mon, 13 Aug 2018 06:06:54 GMT
  • 空白行:通知客户端空行以下没有头部信息了
  • 响应报文主体:主要包含了返回给客户端的数据,可以是文本,二进制(图片,视频)
  • HTTP响应例子
HTTP/1.1 200 OK

Server:Apache Tomcat/5.0.12
Date:Mon,6Oct2003 13:23:42 GMT
Content-Length:112

<html>...

⑧ url
url的意思是“统一资源标识符”,是一个用于标识某一互联网资源名称的字符串,在世界范围内标识定位某一个唯一信息资源。主要用在各种www客户端和服务器程序上,url可以用一种统一的格式来描述各种信息资源,包括文件,服务器地址和目录等。
url组成:

协议  
主机ip或域名
主机资源具体地址
第一部分用"://"隔开,第二部分用"/"符号隔开

⑨ 静态网页资源
在网页设计中,纯HTMl格式的网页(包含图片,视频,JS,CSS等样式)通常被称作“静态网页”。静态网页是相对于动态网页而言的,是指没有后台数据库,不包含程序,不可交互的网页。

  • 静态网页的特性
1、每个页面有一个固定的url地址,url地址不含有问号"?""&"等符号
2、网页一经发布到服务器,网页内容是保存在服务器文件系统上的,每个网页都是独立的一个文件
3、网页内容固定不变,容易被搜索引擎收录(优点)
4、网页没有数据库支撑,在网站制作和维护上工作量很大(缺点)
5、网页的交互性很差,缺少程序的功能实现(缺点)
6、客户端解析网址时,由于不需要读取数据库,因此服务器端可以接受更高的并发访问。请求到来时,直接从磁盘上返回数据。(优点)

高并发架构思想:在高并发,高访问量的场景下做架构优化时,比较关键的就是把动态网页转化成静态网页,而不是直接请求数据库和动态服务器,并且可以吧静态内容推到缓存中,这样就提升用户体验,节约服务器压力成本。

⑩ 动态网页资源
动态网页是和静态网页相对而言的,动态网页的url后缀一般是.asp .aspx .php .js .cgi。并且动态网页都有标志性的符号"? &",后端都有数据库的支持。

  • 动态网页的特性
1、网页以数据库技术为支撑,大大降低网站维护的工作量
2、动态网页技术的网站可以实现更多的功能,如用户注册,用户登录,投票,用户管理,博客管理等
3、动态网页不是独立存在服务器上的网页文件,用户请求动态程序时,服务器解析程序并且可能读取数据库返回一个完整的网页内容
4、搜索引擎(爬虫)一般不会抓取网址中的“?”后面的内容,因此企业都会做伪静态技术页面

PS:网站流量术语

  • IP即Internet Protocol,这里是指独立ip数,不同的ip地址的计算机访问网站时被计算的总次数。独立ip数是网站流量的一个重要指标,一般相同ip地址的客户端访问网站页面一天内只会被计算一次。这里的ip指的是是固定的公网ip
  • PVPage View的简写即是页面浏览量,不管客户端是不是相同,也不管ip是否相同,用户只要访问网站页面就会被计算PV,一次计算一个PV。PV的度量方法就是客户端从浏览器发出一个web请求(request),服务器接收请求返回一个页面给客户端,这样就产生一个PV。PV是衡量网站被访问的一个标准,但并不准确有多少人访问了网站。
  • IUV即unique visitor,同一个客户端(pc或移动端)访问网站被计算为一个访客。一天内相同的客户端访问同一个网站只计一次uv,uv是以cookie等技术为统计依据,实际统计存在误差。
  • I并发数并发数指系统同时能处理的请求数量,也反应了系统的负载能力
  • I响应时间响应时间是指执行一个请求从开始到最后收到响应数据所花费的总体时间。
  • IQPSQuery Per Second,指每秒查询数服务器在一秒内处理了多少个请求,显然数字越大代表服务器的负载越高,处理能力越强。
5.2 web服务器和web框架

web服务器只接收请求,然后返回数据,如nginx。而web服务框架是用来开发web应用程序,首先需要接受请求,然后是处理接收到的请求,再返回数据。

5.3 nginx简介(面试)

nginx是一个开源的,支持高性能,高并发的www服务和代理服务软件。它是一个俄罗斯人lgor sysoev开发的,作者将源代码开源出来供全球使用。nginx的优点如下:

  • 支持高并发,能支持几万并发连接
  • 资源消耗少,例如在3万并发连接下开启10个nginx线程消耗的内存不到200MB
  • 可以做http反向代理和负载均衡
  • 支持异步网络i/o事件模型epoll(nginx快就是基于epoll去做的)

面试:为什么要使用nginx,可以按照上面所述

5.4 nginx的安装与配置

① 安装nginx依赖环境:

[root@instance-mtfsf05r ~]# yum install gcc patch libffi-devel python-devel  zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel openssl openssl-devel -y

nginx 不仅支持 http 协议,还支持 https(即在ssl协议上传输http),所以需要在 Centos 安装 OpenSSL 库。 OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及 SSL 协议,并提供丰富的应用程序供测试或其它目的使用。

② 安装nginx

nginx的下载:http://nginx.org/en/download.html

# 下载源码包
[root@instance-mtfsf05r ~]# wget http://nginx.org/download/nginx-1.16.1.tar.gz
# 解压缩
[root@instance-mtfsf05r ~]# tar -zxf nginx-1.16.1.tar.gz
# 切换到nginx目录
[root@instance-mtfsf05r ~]# cd nginx-1.16.1/
# 配置安装位置这里不会生成文件夹
[root@instance-mtfsf05r nginx-1.16.1]# ./configure --prefix=/usr/local/nginx-16/ --with-http_stub_status_module --with-http_ssl_module
# 编译安装,编译安装后才会生成/usr/local/nginx-16/目录
[root@instance-mtfsf05r nginx-1.16.1]# make && make install
# 进入nginx安装目录
[root@instance-mtfsf05r ~]# cd /usr/local/nginx-16/
# 进入/sbin目录
[root@instance-mtfsf05r nginx-16]# cd sbin/
# 启动nginx
[root@instance-mtfsf05r sbin]# ./nginx
# 查看nginx进程
[root@instance-mtfsf05r sbin]# ps -ef |grep nginx
root      6770     1  0 15:12 ?        00:00:00 nginx: master process ./nginx
nobody    6771  6770  0 15:12 ?        00:00:00 nginx: worker process
root      6779  2444  0 15:12 pts/0    00:00:00 grep --color=auto nginx
# 关闭nginx
[root@instance-mtfsf05r sbin]# ./nginx -s stop
# 重新加再nginx配置文件,不重启nginx
[root@instance-mtfsf05r sbin]# ./nginx -s reload
# 为了方便nginx的相关操作,可以做软链接
[root@instance-mtfsf05r sbin]# ln -s /usr/local/nginx-16/sbin/nginx /usr/bin/nginx
# 这样就可以直接使用nginx命令了
[root@instance-mtfsf05r ~]# nginx

操作nginx的其它命令:

# 监测nginx配置文件nginx.conf的语法是否正确
[root@instance-mtfsf05r ~]# nginx -t
# 查看nginx默认的80端口
[root@instance-mtfsf05r ~]# netstat -tunlp|grep 80
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      6955/nginx: master
# 查看配置文件有哪些信息
[root@instance-mtfsf05r ~]# nginx -T
# 安装后访问本地ip地址,如果访问不了,可以检查selinux,iptables
[root@instance-mtfsf05r ~]# curl -I 127.0.0.1

nginx安装完毕并且启动后,默认使用的是80端口,可通过浏览器访问80端口的程序:
在这里插入图片描述

5.5 nginx目录结构
[root@instance-mtfsf05r ~]# ls /usr/local/nginx-16/
client_body_temp  fastcgi_temp  logs        sbin       uwsgi_temp
conf              html          proxy_temp  scgi_temp
conf:存放nginx所有配置文件的目录,主要nginx.conf
html:存放nginx默认站点的目录,如index.html、error.html等
logs:存放nginx默认日志的目录,如error.log、access.log
sbin:存放nginx主命令的目录,sbin/nginx
5.6 nginx配置文件解析

nginx主配置文件/etc/nginx/nginx.conf是一个纯文本类型的文件,整个配置文件是以区块的形式组织的。一般每个区块以一对大括号{}来表示开始与结束。

######Nginx配置文件nginx.conf中文详解#####

#定义Nginx运行的用户和用户组
user www www;

#nginx进程数,建议设置为等于CPU总核心数。
worker_processes 8;
 
#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]
error_log /usr/local/nginx/logs/error.log info;

#进程pid文件
pid /usr/local/nginx/logs/nginx.pid;

#指定进程可以打开的最大描述符:数目
#工作模式与连接数上限
#这个指令是指当一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx进程数相除,但是nginx分配请求并不是那么均匀,所以最好与ulimit -n 的值保持一致。
#现在在linux 2.6内核下开启文件打开数为65535,worker_rlimit_nofile就相应应该填写65535。
#这是因为nginx调度时分配请求到进程并不是那么的均衡,所以假如填写10240,总并发量达到3-4万时就有进程可能超过10240了,这时会返回502错误。
worker_rlimit_nofile 65535;

events
{
    #参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型
    #是Linux 2.6以上版本内核中的高性能网络I/O模型,linux建议epoll,如果跑在FreeBSD上面,就用kqueue模型。
    #补充说明:
    #与apache相类,nginx针对不同的操作系统,有不同的事件模型
    #A)标准事件模型
    #Select、poll属于标准事件模型,如果当前系统不存在更有效的方法,nginx会选择select或poll
    #B)高效事件模型
    #Kqueue:使用于FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X.使用双处理器的MacOS X系统使用kqueue可能会造成内核崩溃。
    #Epoll:使用于Linux内核2.6版本及以后的系统。
    #/dev/poll:使用于Solaris 7 11/99+,HP/UX 11.22+ (eventport),IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+。
    #Eventport:使用于Solaris 10。 为了防止出现内核崩溃的问题, 有必要安装安全补丁。
    use epoll;

    #单个进程最大连接数(最大连接数=连接数*进程数)
    #根据硬件调整,和前面工作进程配合起来用,尽量大,但是别把cpu跑到100%就行。每个进程允许的最多连接数,理论上每台nginx服务器的最大连接数为。
    worker_connections 65535;

    #keepalive超时时间。
    keepalive_timeout 60;

    #客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求头的大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。
    #分页大小可以用命令getconf PAGESIZE 取得。
    #[root@web001 ~]# getconf PAGESIZE
    #4096
    #但也有client_header_buffer_size超过4k的情况,但是client_header_buffer_size该值必须设置为“系统分页大小”的整倍数。
    client_header_buffer_size 4k;

    #这个将为打开文件指定缓存,默认是没有启用的,max指定缓存数量,建议和打开文件数一致,inactive是指经过多长时间文件没被请求后删除缓存。
    open_file_cache max=65535 inactive=60s;

    #这个是指多长时间检查一次缓存的有效信息。
    #语法:open_file_cache_valid time 默认值:open_file_cache_valid 60 使用字段:http, server, location 这个指令指定了何时需要检查open_file_cache中缓存项目的有效信息.
    open_file_cache_valid 80s;

    #open_file_cache指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive时间内一次没被使用,它将被移除。
    #语法:open_file_cache_min_uses number 默认值:open_file_cache_min_uses 1 使用字段:http, server, location  这个指令指定了在open_file_cache指令无效的参数中一定的时间范围内可以使用的最小文件数,如果使用更大的值,文件描述符在cache中总是打开状态.
    open_file_cache_min_uses 1;
    
    #语法:open_file_cache_errors on | off 默认值:open_file_cache_errors off 使用字段:http, server, location 这个指令指定是否在搜索一个文件是记录cache错误.
    open_file_cache_errors on;
}
 
#设定http服务器,利用它的反向代理功能提供负载均衡支持
http
{
    #文件扩展名与文件类型映射表
    include mime.types;

    #默认文件类型
    default_type application/octet-stream;

    #默认编码
    #charset utf-8;

    #服务器名字的hash表大小
    #保存服务器名字的hash表是由指令server_names_hash_max_size 和server_names_hash_bucket_size所控制的。参数hash bucket size总是等于hash表的大小,并且是一路处理器缓存大小的倍数。在减少了在内存中的存取次数后,使在处理器中加速查找hash表键值成为可能。如果hash bucket size等于一路处理器缓存的大小,那么在查找键的时候,最坏的情况下在内存中查找的次数为2。第一次是确定存储单元的地址,第二次是在存储单元中查找键 值。因此,如果Nginx给出需要增大hash max size 或 hash bucket size的提示,那么首要的是增大前一个参数的大小.
    server_names_hash_bucket_size 128;

    #客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求的头部大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令getconf PAGESIZE取得。
    client_header_buffer_size 32k;

    #客户请求头缓冲大小。nginx默认会用client_header_buffer_size这个buffer来读取header值,如果header过大,它会使用large_client_header_buffers来读取。
    large_client_header_buffers 4 64k;

    #设定通过nginx上传文件的大小
    client_max_body_size 8m;

    #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
    #sendfile指令指定 nginx 是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度,降低系统uptime。
    sendfile on;

    #开启目录列表访问,合适下载服务器,默认关闭。
    autoindex on;

    #此选项允许或禁止使用socke的TCP_CORK的选项,此选项仅在使用sendfile的时候使用
    tcp_nopush on;
     
    tcp_nodelay on;

    #长连接超时时间,单位是秒
    keepalive_timeout 120;

    #FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。
    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;

    #gzip模块设置
    gzip on; #开启gzip压缩输出
    gzip_min_length 1k;    #最小压缩文件大小
    gzip_buffers 4 16k;    #压缩缓冲区
    gzip_http_version 1.0;    #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
    gzip_comp_level 2;    #压缩等级
    gzip_types text/plain application/x-javascript text/css application/xml;    #压缩类型,默认就已经包含textml,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
    gzip_vary on;

    #开启限制IP连接数的时候需要使用
    #limit_zone crawler $binary_remote_addr 10m;

    #负载均衡配置
    upstream jh.w3cschool.cn {
     
        #upstream的负载均衡,weight是权重,可以根据机器配置定义权重。weigth参数表示权值,权值越高被分配到的几率越大。
        server 192.168.80.121:80 weight=3;
        server 192.168.80.122:80 weight=2;
        server 192.168.80.123:80 weight=3;

        #nginx的upstream目前支持4种方式的分配
        #1、轮询(默认)
        #每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
        #2、weight
        #指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
        #例如:
        #upstream bakend {
        #    server 192.168.0.14 weight=10;
        #    server 192.168.0.15 weight=10;
        #}
        #2、ip_hash
        #每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
        #例如:
        #upstream bakend {
        #    ip_hash;
        #    server 192.168.0.14:88;
        #    server 192.168.0.15:80;
        #}
        #3、fair(第三方)
        #按后端服务器的响应时间来分配请求,响应时间短的优先分配。
        #upstream backend {
        #    server server1;
        #    server server2;
        #    fair;
        #}
        #4、url_hash(第三方)
        #按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
        #例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法
        #upstream backend {
        #    server squid1:3128;
        #    server squid2:3128;
        #    hash $request_uri;
        #    hash_method crc32;
        #}

        #tips:
        #upstream bakend{#定义负载均衡设备的Ip及设备状态}{
        #    ip_hash;
        #    server 127.0.0.1:9090 down;
        #    server 127.0.0.1:8080 weight=2;
        #    server 127.0.0.1:6060;
        #    server 127.0.0.1:7070 backup;
        #}
        #在需要使用负载均衡的server中增加 proxy_pass http://bakend/;

        #每个设备的状态设置为:
        #1.down表示单前的server暂时不参与负载
        #2.weight为weight越大,负载的权重就越大。
        #3.max_fails:允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream模块定义的错误
        #4.fail_timeout:max_fails次失败后,暂停的时间。
        #5.backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。

        #nginx支持同时设置多组的负载均衡,用来给不用的server来使用。
        #client_body_in_file_only设置为On 可以讲client post过来的数据记录到文件中用来做debug
        #client_body_temp_path设置记录文件的目录 可以设置最多3层目录
        #location对URL进行匹配.可以进行重定向或者进行新的代理 负载均衡
    }
     
     
    #虚拟主机的配置
    server
    {
        #监听端口
        listen 80;

        #域名可以有多个,用空格隔开
        server_name www.w3cschool.cn w3cschool.cn;
        index index.html index.htm index.php;
        root /data/www/w3cschool;

        #对******进行负载均衡
        location ~ .*.(php|php5)?$
        {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            include fastcgi.conf;
        }
         
        #图片缓存时间设置
        location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires 10d;
        }
         
        #JS和CSS缓存时间设置
        location ~ .*.(js|css)?$
        {
            expires 1h;
        }
         
        #日志格式设定
        #$remote_addr与$http_x_forwarded_for用以记录客户端的ip地址;
        #$remote_user:用来记录客户端用户名称;
        #$time_local: 用来记录访问时间与时区;
        #$request: 用来记录请求的url与http协议;
        #$status: 用来记录请求状态;成功是200,
        #$body_bytes_sent :记录发送给客户端文件主体内容大小;
        #$http_referer:用来记录从那个页面链接访问过来的;
        #$http_user_agent:记录客户浏览器的相关信息;
        #通常web服务器放在反向代理的后面,这样就不能获取到客户的IP地址了,通过$remote_add拿到的IP地址是反向代理服务器的iP地址。反向代理服务器在转发请求的http头信息中,可以增加x_forwarded_for信息,用以记录原有客户端的IP地址和原来客户端的请求的服务器地址。
        log_format access '$remote_addr - $remote_user [$time_local] "$request" '
        '$status $body_bytes_sent "$http_referer" '
        '"$http_user_agent" $http_x_forwarded_for';
         
        #定义本虚拟主机的访问日志
        access_log  /usr/local/nginx/logs/host.access.log  main;
        access_log  /usr/local/nginx/logs/host.access.404.log  log404;
         
        #对 "/" 启用反向代理
        location / {
            proxy_pass http://127.0.0.1:88;
            proxy_redirect off;
            proxy_set_header X-Real-IP $remote_addr;
             
            #后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
             
            #以下是一些反向代理的配置,可选。
            proxy_set_header Host $host;

            #允许客户端请求的最大单文件字节数
            client_max_body_size 10m;

            #缓冲区代理缓冲用户端请求的最大字节数,
            #如果把它设置为比较大的数值,例如256k,那么,无论使用firefox还是IE浏览器,来提交任意小于256k的图片,都很正常。如果注释该指令,使用默认的client_body_buffer_size设置,也就是操作系统页面大小的两倍,8k或者16k,问题就出现了。
            #无论使用firefox4.0还是IE8.0,提交一个比较大,200k左右的图片,都返回500 Internal Server Error错误
            client_body_buffer_size 128k;

            #表示使nginx阻止HTTP应答代码为400或者更高的应答。
            proxy_intercept_errors on;

            #后端服务器连接的超时时间_发起握手等候响应超时时间
            #nginx跟后端服务器连接超时时间(代理连接超时)
            proxy_connect_timeout 90;

            #后端服务器数据回传时间(代理发送超时)
            #后端服务器数据回传时间_就是在规定时间之内后端服务器必须传完所有的数据
            proxy_send_timeout 90;

            #连接成功后,后端服务器响应时间(代理接收超时)
            #连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理(也可以说是后端服务器处理请求的时间)
            proxy_read_timeout 90;

            #设置代理服务器(nginx)保存用户头信息的缓冲区大小
            #设置从被代理服务器读取的第一部分应答的缓冲区大小,通常情况下这部分应答中包含一个小的应答头,默认情况下这个值的大小为指令proxy_buffers中指定的一个缓冲区的大小,不过可以将其设置为更小
            proxy_buffer_size 4k;

            #proxy_buffers缓冲区,网页平均在32k以下的设置
            #设置用于读取应答(来自被代理服务器)的缓冲区数目和大小,默认情况也为分页大小,根据操作系统的不同可能是4k或者8k
            proxy_buffers 4 32k;

            #高负荷下缓冲大小(proxy_buffers*2)
            proxy_busy_buffers_size 64k;

            #设置在写入proxy_temp_path时数据的大小,预防一个工作进程在传递文件时阻塞太长
            #设定缓存文件夹大小,大于这个值,将从upstream服务器传
            proxy_temp_file_write_size 64k;
        }
         
         
        #设定查看Nginx状态的地址
        location /NginxStatus {
            stub_status on;
            access_log on;
            auth_basic "NginxStatus";
            auth_basic_user_file confpasswd;
            #htpasswd文件的内容可以用apache提供的htpasswd工具来产生。
        }
         
        #本地动静分离反向代理配置
        #所有jsp的页面均交由tomcat或resin处理
        location ~ .(jsp|jspx|do)?$ {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://127.0.0.1:8080;
        }
         
        #所有静态文件由nginx直接读取不经过tomcat或resin
        location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|
        pdf|xls|mp3|wma)$
        {
            expires 15d; 
        }
         
        location ~ .*.(js|css)?$
        {
            expires 1h;
        }
    }
}
######Nginx配置文件nginx.conf中文详解#####

CoreModule核心模块:

user www;                       #Nginx进程所使用的用户
worker_processes 1;             #Nginx运行的work进程数量(建议与CPU数量一致或auto)
error_log /log/nginx/error.log  #Nginx错误日志存放路径
pid /var/run/nginx.pid          #Nginx服务运行后产生的pid进程号

events事件模块:

events {            
    worker_connections  //每个worker进程支持的最大连接数
    use epool;          //事件驱动模型, epoll默认
}

http内核模块:

//公共的配置定义在http{}
http {  //http层开始
...    
    //使用Server配置网站, 每个Server{}代表一个网站(简称虚拟主机)
    'server' {
        listen       80;        //监听端口, 默认80
        server_name  localhost; //提供服务的域名或主机名
        access_log host.access.log  //访问日志
        //控制网站访问路径
        'location' / {
            root   /usr/share/nginx/html;   //存放网站代码路径
            index  index.html index.htm;    //服务器返回的默认页面文件
        }
        //指定错误代码, 统一定义错误页面, 错误代码重定向到新的Locaiton
        error_page   500 502 503 504  /50x.html;
    }
    ...
    //第二个虚拟主机配置
    'server' {
    ...
    }
    
    include /etc/nginx/conf.d/*.conf;  //包含/etc/nginx/conf.d/目录下所有以.conf结尾的文件

}   //http层结束
5.7 默认web站点的配置
# 切换到配置文件所在目录
[root@instance-mtfsf05r ~]# cd /usr/local/nginx-16/conf/
# 编辑nginx配置文件
[root@instance-mtfsf05r conf]# vim nginx.conf
# 找到http中的server里面的location模块,可以配置网站根目录为其它目录
location / {
            root   html; #html为网站根目录
            index  index.html index.htm; #指定网站首页文件
        }

配置文件中一些常见参数的解释:

# nginx是c写的
#user  nobody;
#表示两个进程,官方推荐根据cpu核数,我的云主机是两核,通过top然后按1可查看机器核数
worker_processes  2;

#error_log是记录错误日志的,原来是关闭的,我们这里可以打开
error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#nginx的进程id
#pid        logs/nginx.pid;

#worker_connections:可连接数
events {
    worker_connections  1024;
}

#web服务器的配置都在http里
http {
    include       mime.types;
    default_type  application/octet-stream;
    #日志格式,$http_x_forwarded_for是:用于抓取爬虫,爬虫使用了代理,不知道你真实的ip,这个参数可以获取真实的ip
    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    #长链接超时
    keepalive_timeout  65;
    #支持文件传输的时候进行压缩,传输效率提高
    #gzip  on;
    #虚拟主机标签段
    server {
        #端口
        listen       80;
        #域名
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        #url路径匹配
        location / {
            #网站根目录是html目录
            root   html;
            #网站首页文件页
            index  index.html index.htm;
        }
        #错误页面
        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
}
5.8 nginx多虚拟主机

① 虚拟主机类型

  • 基于域名的虚拟主机:通过不同的域名区分不同的虚拟主机,是企业应用最广的虚拟主机。
  • 基于端口的虚拟主机:通过不同的端口来区分不同的虚拟主机,一般用作企业内部网站,不对外直接提供服务的后台
  • 基于ip的虚拟主机:通过不同的ip区分不同的虚拟主机,此类比较少见,一般业务需要多IP的常见都会在负载均衡中绑定VIP

② 多虚拟主机的配置
我们这里做“基于域名的虚拟主机”,基于域名的虚拟主机可以帮助我们实现使用不同的域名访问同一台服务器的不同虚拟主机(端口都是80)获取不同的资源。也就是说你想在一台服务器上做两个网站以及多个网站都没问题,这在企业中应用比较广泛。并且相较于以前一个域名只能访问一台服务器有了很大进步,节省了资源和成本。

  • 配置两台虚拟主机
# 修改配置文件
[root@instance-mtfsf05r ~]# vim /usr/local/nginx-16/conf/nginx.conf
# 再增加一个server标签,并且修改server_name对应的域名:
 server{
        listen       80;
        server_name  www.thanlon.cn;
        location / {
                root /opt/nginx/thanlon/;
                index  index.html index.htm;
        }
    }
 server{
        listen       80;
        server_name  www.kiku.cn;
        location / {
                root /opt/nginx/kiku/;
                index  index.html index.htm;
        }
    }
# 创建虚拟主机的目录
[root@instance-mtfsf05r ~]# mkdir -p /opt/nginx/{thanlon,kiku}
# 在thanlon和kiku文件夹创建index.html文件
[root@instance-mtfsf05r thanlon]# vim index.html
[root@instance-mtfsf05r kiku]# vim index.html
  • 找到本地主机的hosts文件(C:\Windows\System32\drivers\etc)进行强制域名解析:
# linux桌面系统(ubuntu linux等)的用户同理找到系统里的hosts文件(/etc/hosts)
# 在文件末尾追加下面两行,这样访问这两个域名就直接解析到106.12.115.101
106.12.115.101 www.thanlon.cn 
106.12.115.101 www.kiku.cn

直接在本地访问这两个站点:
在这里插入图片描述
在这里插入图片描述

如果你的域名是公网域名,并且已经解析到你的云服务器,不要在本地做强制解析,直接访问你的域名即可。

5.9 nginx访问日志

日志功能对每个用户访问网站的日志信息记录到指定的日志文件中,开发运维人员可以分析用户的浏览器行为。此功能由ngx_http_log_module模块负责,将配置文件中的下面四行的注释去掉,开启访问日志功能:

 log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                   '$status $body_bytes_sent "$http_referer" '
                   '"$http_user_agent" "$http_x_forwarded_for"';

 access_log  logs/access.log  main;

参数的解析:

$remote_addr    记录客户端ip
$remote_user    远程用户,没有就是 “-”
$time_local    对应[14/Aug/2018:18:46:52 +0800]
$request     对应请求信息"GET /favicon.ico HTTP/1.1"
$status      状态码
$body_bytes_sent  571字节 请求体的大小
$http_referer  对应“-”  由于是直接输入浏览器就是 -
$http_user_agent  客户端身份信息
$http_x_forwarded_for  记录客户端的来源真实ip 97.64.34.118

可以通过配置好的域名访问虚拟主机,使用tail -f access.log命令来监控日志文件的变化:

# 监控日志文件
[root@instance-mtfsf05r logs]# tail -f access.log 
# 可以看到访问记录
122.96.44.251 - - [06/Oct/2019:08:30:37 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36" "-"
122.96.44.251 - - [06/Oct/2019:08:30:49 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36" "-"
# 访问记录解析
122.96.44.251:访问机器的ip
[06/Oct/2019:08:30:37 +0800]:访问时间
"GET / HTTP/1.1":请求的方式,请求的资源(根路径)、http协议
304:状态码
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36":客户端信息(User-Agent)
"-":表示没有使用代理,如果用户使用代理,这里会显示用户真实ip
5.10 限制网站来源ip访问

如果nginx很慢,或者在检查访问日志的时候,看到有人恶意请求你的网站资源。可以查看访问日志中,找到这个ip进行限制。

location / {
			# 限制122.96.44.251访问此站点
            deny 122.96.44.251;
            root   html;
            index  index.html index.htm;
        }

还可以限制访问允许某ip段访问:

location / {
			# 允许某ip段访问此站点
			allow 10.1.1.0/16;  
            root   html;
            index  index.html index.htm;
        }

也可以禁止某ip或ip段访问服务器指定的资源:

# 禁止访问/images资源
location /images {
			deny 122.96.44.251;
			#alias /opt/nginx1-12/html/images;
			allow 10.1.1.0/16;  
}

ip被限制后访问到的页面是这样的:
在这里插入图片描述

5.11 nginx错误页面优化

为了增强用户的体验效果,可以对一系列错误页面进行优化,可通过nginx配置文件完成:

error_page 400 403 404 405 /40x.html;
# redirect server error pages to the static page /50x.html
error_page   500 502 503 504  /50x.html;
location = /50x.html {
	root   html;
}
5.12 正向代理

正向代理等同于代理,举个例子:用户不能访问一个网站,例如google。然后想着通过连接一个可以访问google的代理服务器,让这个代理服务器会帮助用户去google的服务器请求资源,然后把资源返回给我,这就是属于正向代理。
在这里插入图片描述
正向代理的实际应用是公司职员在公司办公时通过vpn代理ip连接外网,在家的时候也可以使用这个代理ip连接公司的内网环境。

5.13 反向代理

来谈反向代理,对于用户而言,反向代理服务器就像是原始服务器,用户并不知道资源来自哪里。但是,用户只要通过这个反向代理服务器就可以访问到我们想要的资源,反向代理服务器可以保护和隐藏原始资源服务器。
在这里插入图片描述

下面简单叙述一个反向代理的过程:

1、用户向www.blueflags.cn/login 发送一个登录的请求,需要先到nginx服务器,告诉nginx我的“登录”请求
2、nginx作为一个中间代理,不做用户请求的处理,把请求直接抛给web应用服务器django
3、django收到用户的请求并处理请求后返回请求给nginx
4、nginx返回处理完毕好的数据,返回给用户(浏览器)
5、客户的浏览器得到这个返回结果进行展示给用户

nginx实现负载均衡的组件:ngx_http_proxy_module proxy代理模块,用于把请求抛给服务器节点或者upstream服务器池

5.14 反向代理的实现

实现的目标:向本地nginx服务器发送请求,本地nginx服务器不处理请求,把请求直接转交给www.baidu.com的服务器。www.baidu.com的服务器返回结果给本地的nginx服务器,本地nginx服务器再把结果返回到浏览器。

首先在本地安装nginx并配置反向代理:

# 更新apt源
thanlon@plus-book:~$ sudo apt update
# 安装nginx
thanlon@plus-book:~$ sudo apt install nginx
# 安装后默认是启动的状态,可以查看nginx进程(因为nginx默认配置进程数是自动的,我的电脑是八核,也就开了8个nginx进程)
thanlon@plus-book:~$ ps -ef |grep nginx
root     19853  1230  0 13:29 ?        00:00:00 nginx: master process nginx
www-data 19854 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19855 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19856 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19857 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19858 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19859 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19860 19853  0 13:29 ?        00:00:00 nginx: worker process
www-data 19861 19853  0 13:29 ?        00:00:00 nginx: worker process
thanlon  19864  5276  0 13:29 pts/2    00:00:00 grep --color=auto nginx
# 切换到/etc/nginx/conf.d目录
thanlon@plus-book:/etc/nginx$ cd /etc/nginx/conf.d/
# 新建一个nginx配置文件,文件只要以.conf结尾即可
thanlon@plus-book:/etc/nginx/conf.d$ sudo vim nginx.conf
# 在配置文件写入下面的内容
server{
        listen 80;
        server_name xxx;
        location /{
        		#proxy_pass参数指定了转交处理请求的ip
                proxy_pass http://www.baidu.com;
        }
}
# 为了使配置文件生效,还要修改主配置文件/etc/nginx/nginx.conf
thanlon@plus-book:/etc/nginx$ vim nginx.conf
# 把下面一行注释掉,表示不使用默认的配置,这样nginx便成功将请求转发到百度
include /etc/nginx/sites-enabled/*;

当在浏览器输入localhost或127.0.0.1的时候,会看到www.baidu.com返回的结果。

5.15 项目发布

① 项目发布需要用到的软件
django项目发布使用的软件有:CentOS7、Nginx、uWSGI、Virtualenv、Supervisor。使用CentOS系统这没什么多说的;使用的Nginx是为了做反向代理,也没什么多说的。下面主要介绍为什么要使用uWSGI、Virtualenv、Supervisor。

② 为什么要使用uWSGI
uWSGI是一个全功能的http服务器,实现了WSGI协议、uwsgi协议、http协议等。使用uWSGI是因为它可以把http协议转化成语言支持的协议,比如把http协议转换为WSGI协议让python可以直接使用。Python Web服务器开发使用的WSGI(Web Server Gateway Interface)协议,Python Web项目默认会生成一个wsgi.py文件。但在生产环境中使用的是uWSGI,uWSGI实现了WSGI所有接口,是用C语言编写的,所以它是一个效率很高的Web服务器。

③ 为什么要使用virtualenv
使用virtualenv可以构建一个干净的、隔离的Python解释器环境,可以防止软件依赖冲突等问题,非常建议使用。

④ 为什么要使用Supervisor
Supervisor是Python开发的一个C/S服务,是Linux系统下的远程管理工具,它不支持window系统。它可以很方便地监听、启动、停止一个或多个进程。使用Supervisor管理进程,当一个进程被意外杀死后,Supervisor监听到进程死掉,会里立即将它重新拉起,很方便做到自动恢复的功能,不再需要写shell脚本来控制。

⑤ 使用python解释器运行django程序

# 进入venv虚拟环境
[root@master ~]# workon venv
# 安装django程序
(venv) [root@master mysite]#  pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple django==2.1.12
# 切换到venv虚拟环境目录
(venv) [root@master ~]# cdvirtualenv
# 创建django项目
(venv) [root@master venv]# django-admin startproject mysite
# 修改配置文件,让所有ip都可以访问
(venv) [root@master venv]# vim mysite/mysite/settings.py
ALLOWED_HOSTS = ['*']
# 切换到mysite目录
(venv) [root@master venv]# cd mysite/
# 启动django程序
(venv) [root@master mysite]# python manage.py runserver 0.0.0.0:8000
# 查看运行的python进程
[root@master ~]# ps -ef|grep python
root       885     1  0 1005 ?      00:00:17 /usr/bin/python2 -Es /usr/sbin/tuned -l -P
root      5261  3823  0 22:40 pts/0    00:00:00 python manage.py runserver 0.0.0.0:8000
root      5263  5261  1 22:40 pts/0    00:00:06 /root/Envs/venv/bin/python manage.py runserver 0.0.0.0:8000
root      5879  5767  0 22:49 pts/1    00:00:00 grep --color=auto python

⑥ 使用uWSGI启动一个Python Web服务(uwsgi是基于python的)

# 安装uWSGI模块
(venv) [root@master mysite]# pip install uwsgi
# 查看版本
(venv) [root@master mysite]# uwsgi --version
2.0.18
# 或者通过pip查看版本
(venv) [root@master mysite]# pip list
Package    Version
---------- -------
Django     2.1.12 
pip        19.2.3 
pytz       2019.2 
setuptools 41.2.0 
sqlparse   0.3.0  
uWSGI      2.0.18 
wheel      0.33.6
# 检查uwsig是基于哪个python解释器运行的
(venv) [root@master mysite]# uwsgi --python-version
3.7.3
# 当前所在目录创建test_uwsgi.py文件,向代码写入下面的内容 
(venv) [root@master mysite]# ls
db.sqlite3  manage.py  mysite
(venv) [root@master mysite]# vim test_uwsgi.py
def application(env, start_response):
        start_response('200 OK', [('Content-Type','text/html')])
        return [b"Hello World, I am uwsgi....."]
 # 通过命令启动这个文件
 (venv) [root@master mysite]# uwsgi --http :8001 --wsgi-file test_uwsgi.py

访问8001端口:
在这里插入图片描述
⑦ 通过uwsgi启动django项目

# 查看当前目录下的资源
(venv) [root@master mysite]# ls
db.sqlite3  manage.py  mysite  test_uwsgi.py
# 通过wsgi其功django项目,mysite.wsgi指mysite目录下的wsgi.py,这里不用加.py
(venv) [root@master mysite]# uwsgi --http :9000 --module mysite.wsgi
# uwsgi热加载python程序:不重启项目生效。需要加上--py-autoreload=1
(venv) [root@master mysite]# uwsgi --http :9000 --module mysite.wsgi --py-autoreload=1
# 配置nginx,使用nginx转发给本地9000端口处理请求
location / {
        proxy_pass http://0.0.0.0:9000;
        #root   html;
        #index  index.html index.htm;
 }

访问9000端口后发现此时你的项目丢失静态文件,因为uwsgi不解析静态文件配置,后面会通过nginx来解决这个问题。

⑧ 使用uWSGI配置文件启动项目

# 查看uwsgi配置文件,这个配置文件内置在nginx
[root@master ~]# cat /usr/local/nginx-16/conf/uwsgi_params

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

# 配置nginx配置文件,需要结合uWSGI
  location / {
           include /usr/local/nginx-16/conf/uwsgi_params;
           uwsgi_pass 0.0.0.0:9000;
           #root   html;
           #index  index.html index.htm;
        }

启动这个项目,注意不要使用下面这条命令:uwsgi --http :9000 --module mysite.wsgi --py-autoreload=1,虽然启动不会处问题,但是访问资源会出错。否则会出现这样的页面:
在这里插入图片描述
这是uwsgi找不到9000口的错误5000错误,是服务器内部错误,原因是这里不能使用的是http协议,uwsgi_pass识别不了这个协议,应该使用socket协议:

(venv) [root@master mysite]# uwsgi --socket :9000 --module mysite.wsgi --py-autoreload=1

正常访问:
在这里插入图片描述
nginx有关uWSGI模块的介绍:http://nginx.org/en/docs/http/ngx_http_uwsgi_module.html,示例程序:

Example Configuration:
location / {
   include    uwsgi_params;
    uwsgi_pass localhost:9000;
}

⑨ nginx解决uWSGI启动django项目静态文件丢失
启动后django是有问题的,我们可以通过“右键”->“检查”或者ctrl + shift +i,刷新网页查看网络资源,发现django中的静态资源没有找到:
在这里插入图片描述
使用nginx处理静态页面的资源:

# 编辑nginx配置文件

# 在server标签段下添加下面的内容

# 
(venv) [root@master mysite]# vim /usr/local/nginx-16/conf/nginx.conf
location /static{
           # alias参数告诉静态资源放在哪里
           alias /opt/django/tmp/static/;
}

# 修改django的settings.py文件
(venv) [root@master mysite]# vim mysite/settings.py 
# 找到下面内容
Static files (CSS, JavaScript, Images)
https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'
# 修改为下面的内容
Static files (CSS, JavaScript, Images)
https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_ROOT = '/opt/django/tmp/static/'
STATIC_URL = '/static/'
STATICFILES_DIRS=[
        os.path.join(BASE_DIR,'static')
]
# 收集所有的静态文件保存到STATIC_ROOT对应的目录中
(venv) [root@master mysite]# ls
db.sqlite3  manage.py  mysite  test_uwsgi.py
(venv) [root@master mysite]# mkdir static
(venv) [root@master mysite]# python manage.py collectstatic

119 static files copied to '/opt/django/tmp/static'.
# 进入到/root/static/django目录下可以看到拷贝完成的静态文件目录
(venv) [root@master mysite]# cd /root/static/django/
(venv) [root@master django]# ls
admin
(venv) [root@master django]# cd admin/
(venv) [root@master admin]# ls
css  fonts  img  js
# 最后启动项目
(venv) [root@master mysite]# uwsgi --socket :9000 --module mysite.wsgi --py-autoreload=1

静态资源正常:
在这里插入图片描述
⑩ 配置启动文件启动django

# 创建uwsgi.ini的文件(在哪里创建都可以)
(venv) [root@master mysite]# vim uwsgi.ini
# 在该文件中写入下面的内容
[uwsgi]
#使用nginx连接时使用
socket=0.0.0.0:9000
#不用nginx直接当作web服务器使用
#http=0.0.0.0:9000
#项目目录绝对路径
chdir=/root/Envs/venv/mysite
#wsgi文件路径,在项目底下
wsgi-file=mysite/wsgi.py
#指定解释器目录
home=/root/Envs/venv
processes=4
thread=2
master=True
pidfile=uwsgi.pid
# 启动
(venv) [root@master mysite]# uwsgi --ini uwsgi.ini

正常访问:
在这里插入图片描述
PS:使用supervisor管理进程

# 安装supervisor
[root@master mysite]# pip3 install supervisor
# 通过命令生成supervisor的配置文件
[root@master mysite]# echo_supervisord_conf >/etc/supervisord.conf
# 编辑配置文件
[root@master mysite]# vim /etc/supervisord.conf
# 在该配置文件最下面追加以下内容,这是在配置文件写入一个名为“django_pro”的任务
[program:django_pro]
command=/root/Envs/venv/bin/uwsgi --ini /root/Envs/venv/mysite/uwsgi.ini
# 启动supervisord
[root@master mysite]# supervisord -c /etc/supervisord.conf
# 通过服务端命令supervisord -c参数指明一个配置文件
root     21950     1  0 19:22 ?        00:00:00 /usr/local/python373/bin/python3.7 /usr/local/python373/bin/supervisord -c /etc/supervisord.conf
# 进入交互式环境,可以看到时正在运行的任务,以及任务的pid和运行了多久
[root@master mysite]# supervisorctl 
django_pro                       RUNNING   pid 21952, uptime 0:03:19
supervisor> 
# 停止这个任务
supervisor> stop django_pro
django_pro: stopped
# 开启这个任务
supervisor> start django_pro
5.16 设置nginx负载均衡池

nginx能够支持高并发的集群功能,主要是nginx使用轮训算法和权重算法。轮训算法使用nginx可以一个个转交处理的请求 ,简而言之,也就是如果web服务器挂掉,接着给下一个web服务器。权重算法,则是根据权重,改变转交处理请求的顺序。哪个web服务器权重大,优先把权重交给哪个。

# 将原来进行修改
  location / {
           include /usr/local/nginx/conf/uwsgi_params;
           uwsgi_pass 0.0.0.0:9000;
           #root   html;
           #index  index.html index.htm;
        }
 # 修改为
 location / {
           include /usr/local/nginx/conf/uwsgi_params;
           uwsgi_pass django;
        }
 # 并在http内、server标签外添加下面的内容(我把这段内容写在server边前段的上面了)
   upstream django{
        #0.0.0.0:90;
        server 0.0.0.0:9000 weight=10;
    }
    server {
        #端口
        listen       80;
        ……
5.17 ssl证书的安装
1、将证书公钥和证书私钥上传到服务器
thanlon@thanlon-master:~/下载/cert/Nginx$ scp 1_www.bysj39.com_bundle.crt 2_www.bysj39.com.key root@106.12.115.136:/home/thanlon/cert/39bysj
2、打开nginx安装目录下conf目录中的nginx.conf文件
[root@master conf]# vim nginx.conf
3、找到被注释掉的部分
 # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
4、修改nginx.conf,添加公钥证书和证书密钥
   #HTTPS server
    #
    server {
        listen       443 ssl;
        server_name  localhost;

        ssl_certificate      /home/thanlon/cert/39bysj/1_www.bysj39.com_bundle.crt; # 写证书公钥;
        ssl_certificate_key  /home/thanlon/cert/39bysj/2_www.bysj39.com.key;        # 写证书密钥;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
          root   html;
          index  index.html index.htm;
        }
    }

可参考数安时代, Nginx SSL证书安装指南:https://www.trustauth.cn/ssl-guide/676.html

5.18 nginx中遇到的问题

配置文件出现下面的错误:

nginx: [emerg] unknown directive "ssl_certificate" in /usr/local/nginx-16/conf/nginx.conf:124
[root@instance-mtfsf05r nginx-1.16.1]# ./configure --prefix=/usr/local/nginx-16/ --with-http_stub_status_module --with-http_ssl_module
[root@instance-mtfsf05r nginx-1.16.1]# make

如果出现下面的错误:

nginx: [emerg] invalid number of arguments in "ssl_certificate" directive in /usr/local/nginx/conf/n

看一下是不是分号没有添加。

6. Docker相关
6.1 Docker的安装

① 卸载旧版本

[root@instance-mtfsf05r ~]# 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

② 设置存储库

[root@instance-mtfsf05r ~]# sudo yum install -y yum-utils device-mapper-persistent-data lvm2

[root@instance-mtfsf05r ~]# sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

③ 安装Docker

安装社区版:[root@instance-mtfsf05r ~]# sudo yum install docker-ce

安装企业版:[root@instance-mtfsf05r ~]# sudo yum install docker-ee

④启动Docker

[root@instance-mtfsf05r ~]# systemctl start docker

⑤ 查看启动状态

[root@instance-mtfsf05r ~]# systemctl status docker

⑥ 查看版本

[root@instance-mtfsf05r ~]# docker version

6.2 Docker常用的命令

① 配置 Docker 镜像站,加速镜像下载

[root@instance-mtfsf05r ~]# curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io

[root@instance-mtfsf05r ~]# sudo systemctl restart docker
[root@instance-mtfsf05r ~]# cat /etc/docker/daemon.json
在这里插入图片描述
② 获取Docker镜像

查找镜像:[root@instance-mtfsf05r ~]# docker search hello-world

下载镜像:[root@instance-mtfsf05r ~]# docker pull ubuntu

下载过程可以看出镜像是由多层存储构成的,下载也是一层一层,并非单一文件
下载过程中给出每一层的前12位ID,下载结束后会给出sha246的文件一致性校验值。

③ 更改linux的dns记录,提升网速

[root@instance-mtfsf05r ~]# vi /etc/resolv.conf

博主用的是百度的云主机,先前用的是腾讯的,两者相比较而言,百度的网速要快很多。下面是我的百度云主机中使用的dns:

resolv.conf:

# Generated by NetworkManager
nameserver 172.16.0.3
nameserver 172.16.0.2
options rotate timeout:1

④ 查看已存在的镜像

[root@instance-mtfsf05r ~]# docker imagesdocker image ls
在这里插入图片描述
⑤ 运行镜像产生容器

[root@instance-mtfsf05r ~]# docker run hello-world

[root@instance-mtfsf05r ~]# docker run fce

运行镜像可以使用docker run hello-world,也可以使用docker run 加上镜像号前三位。

Docker容器必须有后台进程在运行,如果没有运行的进程,Docker容器实例就会挂掉

⑥ 检查正在运行的容器

[root@instance-mtfsf05r ~]# docker ps

⑦ 检查所有容器记录

[root@instance-mtfsf05r ~]# docker ps -a

⑧ 删除镜像文件

删除镜像记录:docker rmi 镜像名(镜像id),

如:[root@instance-mtfsf05r ~]# docker rmi centos

删除容器记录:docker rm 容器id,

如:[root@instance-mtfsf05r ~]# docker rm c5461ef5579a

批量删除容器记录:docker rm ‘docker ps -aq’,如:

[root@instance-mtfsf05r ~]# docker rm docker ps -aq

⑨ 交互式运行docker容器

docker run -it --rm ubuntu bash

docker run:运行容器的命令
-it :-i 是交互式操作,-t是终端
-rm : 容器退出后将其删除。也可以不指定参数,手动docker rm,使用-rm可以避免浪费空间。
ubuntu :镜像文件
bash:指定用交互式的shell,因此需要bash命令

示例:运行ubuntu

[root@instance-mtfsf05r ~]# docker run -it --rm ubuntu
在这里插入图片描述
root@919a342809f9:/# hostname
在这里插入图片描述
查看ubuntu版本信息:root@919a342809f9:/# uname -a
在这里插入图片描述
查看ubuntu发行版版本:root@919a342809f9:/# cat /etc/os-release
在这里插入图片描述
退出容器:root@919a342809f9:/# exit

3. Docker运行CentOS

① 下载CentOS镜像

[root@instance-mtfsf05r ~]# docker pull centos

② 查看CentOS镜像

[root@instance-mtfsf05r ~]# docker images

③运行一个交互式的容器

docker run -it centosdocker run -it centos /bin/bash

如果没有下载镜像使用这条命令后会自动下载CentOS镜像

④查看CentOS版本信息

[root@e5b3b931b3d1 /]# cat /etc/os-release

⑤ 在CentOS中安装vim

[root@e5b3b931b3d1 /]# yum install vim

⑥ 查看CentOS中的进程

[root@0920c89974cf /]# ps -ef

⑦ 使用CentOS自带Python解释器运行Python脚本

[root@0920c89974cf /]# vim hello_docker.py

[root@0920c89974cf /]# python hello_docker.py Hello Docker!

控制台:Hello Docker!

⑧ 后台模式启动Docker

[root@instance-mtfsf05r ~]# docker run centos

执行这个指令运行容器后“立刻”就退出了,Docker容器中必须要有后台进程在运行着,才能保证Docker不会挂掉。
Docker提供的-d参数:后台运行容器,返回容器ID

[root@instance-mtfsf05r ~]# docker run -d centos /bin/sh -c ‘while true;do echo hello centos!;sleep 1;done’
在这里插入图片描述
⑨ 进入容器
查看正在运行的容器:[root@instance-mtfsf05r ~]# docker ps

进入容器:[root@instance-mtfsf05r ~]# docker exec -it 30025944a231 /bin/bash

⑩ 查看容器正在运行产生的日志

[root@instance-mtfsf05r ~]# docker logs 5de(5de是容器ID前三位)

[root@instance-mtfsf05r ~]# docker logs 5de -f

PS:启动容器:[root@instance-mtfsf05r ~]# docker start 5de

停止容器:[root@instance-mtfsf05r ~]# docker stop 5de

删除容器:[root@instance-mtfsf05r ~]# docker rm 5de

6.4 容器的删除

① 删除没有运行的容器

使用[root@instance-mtfsf05r ~]# docker rm docker ps -aq(docker ps -aq需要加反引号,编辑器的原因没有显示)只可以移除没有运行的容器。

② 删除所有容器
如果想强制移除所有容器,需要使用:[root@instance-mtfsf05r ~]# docker rm -f docker ps -aq,危险命令,不建议使用。

③ 合理性删除容器
对于正在运行的容器:[root@instance-mtfsf05r ~]# docker ps或:

[root@instance-mtfsf05r ~]# docker container ls(查看正在运行的容器)

可以先停止容器的运行:[root@instance-mtfsf05r ~]# docker stop 300(300是容器ID)

然后删除所有容器:[root@instance-mtfsf05r ~]# docker rm docker ps -aq

6.5 提交自定义创建的镜像

① 进入交互式CentOS容器中

[root@instance-mtfsf05r ~]# docker run -it centos

② 在CentOS容器中安装vim

安装:[root@71b84d6cfc20 /]# yum install vim -y

退出容器:[root@71b84d6cfc20 /]# exit

③ 查看安装好的vim的容器记录

[root@instance-mtfsf05r ~]# docker container ls -a[root@instance-mtfsf05r ~]# docker ps -a

④ 提交容器创建新的镜像

[root@instance-mtfsf05r ~]# docker commit f23e390f54d2 thanlon/centos-vim

[root@instance-mtfsf05r ~]# docker images

⑤ 运行新创建的容器

[root@instance-mtfsf05r ~]# docker run -it thanlon/centos-vim或:

[root@instance-mtfsf05r ~]# docker run -it a9f(a9f是镜像ID)

查看镜像中是否有安装vim:[root@d90d346a47f3 /]# vim

6.6 外部访问容器(一)

① 外部访问容器的大体过程
Docker运行一个Web应用,通过端口映射,用户通过浏览器可以访问Docker中的web应用。

②运行一个Python Web应用

容器中可以运行网络应用,但是想要让外部访问这些应用,可以通过-p或-P参数指定端口映射。

[root@instance-mtfsf05r ~]# docker run -d -P training/webapp python app.py

-d: 后台运行容器

-P:随机映射端口到容器开放的网络端口

-p:可以通过-p参数指定映射端口,如:(访问宿主机的9000端口其实是访问Docker容器的5000端口)

[root@instance-mtfsf05r ~]# docker run -d -p 4000:5000 training/webapp python app.py

③ 查看容器内的端口映射状态

[root@instance-mtfsf05r ~]# docker port 47e(“47e是端口号前三位”)
在这里插入图片描述
也可以使用docker ps查看到端口映射状态。

6.7 外部访问容器(二)

示例:Docker运行Flask Web应用,外部访问这个容器中的应用

① 运行CentOS镜像

[root@instance-mtfsf05r ~]# docker run -it centos

② 在容器中easy_install模块

[root@09a475fade35 /]# yum install python-setuptools -y

③ 安装Flask模块

[root@09a475fade35 /]# easy_install flask

检验是否安装成功:import flask

[root@2471dd89bbc1 /]# python

Python 2.7.5 (default, Oct 30 2018, 23:45:53)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import flask
>>>

④ 写一个Flask Web脚本

在home目录下新建Flask脚本文件 flask-web.py:

[root@2471dd89bbc1 /]# cd home

[root@2471dd89bbc1 home]# ls

[root@2471dd89bbc1 home]# touch flask-web.py

需要用到vim,安装vim:

[root@2471dd89bbc1 home]# yum install vim -y

[root@2471dd89bbc1 home]# vim flask-web.py

flask-web.py:

from flask import Flask
app= Flask(__name__)

@app.route('/')
def index():
        return 'Flask Application!'

if __name__ == '__main__':
        app.run(host = '0.0.0.0',port = 5000)

运行这个脚本文件,检验是否有误:

[root@2471dd89bbc1 home]# python flask-web.py
在这里插入图片描述
⑤ 退出容器,产生容器记录

[root@2471dd89bbc1 home]# exit

⑥ 提交容器记录构建镜像

[root@instance-mtfsf05r ~]# docker commit 2471dd89bbc1 test/flask_web

查看镜像:[root@instance-mtfsf05r ~]# docker image ls
在这里插入图片描述
⑦ 运行镜像产生容器

使用:[root@instance-mtfsf05r ~]# docker run -d -p 7000:5000 test/flask_web是没有动作的,需要运行脚本,使容器持续运行:

[root@instance-mtfsf05r ~]# docker run -d -p 7000:5000 test/flask_web python /home/flask-web.py

“7000”是主机(我这里是云主机)端口,“5000”是Docker中容器中的应用端口。外部访问宿主主机“7000”端口也就是访问Docker中5000端口对应的Docker应用。

⑧ 访问容器中的应用
在你的浏览器中输入:106.12.115.123:7000/(主机IP+端口号)就会访问到云主机中的Flask Web应用:
在这里插入图片描述

6.8 发布docker镜像到Docke Hub公有仓库

① 注册登录Docker Hub

Docker Hub网址:https://hub.docker.com/

② 修改tag

博主的Daoker Hub账号是Thanlon,先查看将要发布的镜像:

[root@instance-mtfsf05r ~]# docker images
在这里插入图片描述
镜像名是:test/flask_web
push到Daoker Hub的命令格式是:docker push 账号/仓库名,为了使镜像名与"账号/仓库名"对应,需要修改tag:

[root@instance-mtfsf05r ~]# docker tag test/flask_web thanlon/flask-web

其过程就是建立一个镜像的副本,并修改镜像名,可以再次查看镜像:

[root@instance-mtfsf05r ~]# docker images
在这里插入图片描述
很明显镜像列表中增加了thanlon/flask-web的镜像。

③ push镜像到Docker Hub中

[root@instance-mtfsf05r ~]# docker push thanlon/flask-web
在这里插入图片描述
④ 查看Daoker Hub仓库
在这里插入图片描述

6.9 Docker搭建私有仓库

① 下载Docker私有仓库镜像

[root@instance-mtfsf05r ~]# docker pull registry

② 启动私有仓库容器实例

[root@instance-mtfsf05r ~]# docker run -d -p 5000:5000 -v /opt/data/registry:/var/lib/registry registry

私有仓库会被创建在容器中的/var/lib/registry下,通过-v参数将镜像文件存储到本地的/opt/data/registry下,容器中的5000端口映射到宿主的5000端口。

③ 查看私有仓库的容器运行状态

[root@instance-mtfsf05r ~]# docker ps

④ 修改本地镜像的tag

修改本地镜像的tag,便于推送到本地镜像仓库:

[root@instance-mtfsf05r ~]# docker tag registry 106.12.115.123:5000/registry

⑤ 测试容器的连接是否正常

[root@instance-mtfsf05r ~]# telnet 106.12.115.136 5000
在这里插入图片描述
⑥ 使Docker允许HTTPS方式push镜像

通过Docker的配置选项来取消不允许非HTTPS方式推送镜像这个默认规则,修改配置文件/etc/docker/daemon.json

[root@instance-mtfsf05r ~]# vim /etc/docker/daemon.json

修改为:

{
"registry-mirrors": ["http://f1361db2.m.daocloud.io"],
"insecure-registries":["106.12.115.136:500"]
}

修改Docker服务配置文件,写入以下内容:

EnvironmentFile=/etc/docker/daemon.json

在这里插入图片描述
注意要写入 [Serveice] 配置块中,加载配置文件(daemon.json)。

⑦ 重新加载docker

[root@instance-mtfsf05r ~]# systemctl daemon-reload

⑧ 重启Docker服务

[root@instance-mtfsf05r ~]# systemctl restart docker

⑨ 开启私有仓库的容器实例

由于重启了Docker,所有容器镜像实例全部被关闭,所以需要开启私有仓库的容器实例。
查看私有仓库的容器记录:

[root@instance-mtfsf05r ~]# docker ps -a

启动容器实例:

[root@instance-mtfsf05r ~]# docker start 2522102623c2(2522102623c2是容器ID)

⑩ 推送本地镜像到私有仓库

[root@instance-mtfsf05r ~]# docker push 106.12.115.123:5000/hello-world

PS:
查看推送到私有仓库中的镜像:http://106.12.115.123:5000/v2/

从私有仓库中下载镜像:[root@instance-mtfsf05r ~]# docker pull 106.12.115.136:5000/hello-world

7. Flask项目的部署

技术栈:Linux+Nginx+Mysql+Python3+Flask+Git

7.1 python3环境的安装

在本文软件安装与部署中已经介绍过 编译安装Python3,这里不再赘述。

7.2 nginx环境的安装

在本文nginx相关部分已经介绍过 nginx的安装与部署,这里不再赘述。

7.3 mysql环境的安装
# 下载mysql rpm包
[root@master ~]# wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
#  安装rpm包
[root@master ~]# rpm -ivh mysql-community-release-el7-5.noarch.rpm
# 安装mysql-server
[root@master ~]# yum install mysql-server
# 授权
[root@master ~]# chown mysql:mysql -R /var/lib/mysql
# 安装mysql
[root@master ~]# mysqld --initialize 
# 开启服务
[root@master ~]# systemctl start mysqld
# 查看服务状态
[root@master ~]# systemctl status mysqld
# 设置打开计算机时启动服务
[root@master ~]# systemctl enable mysqld
# 设置用户密码
[root@master ~]# mysqladmin -uroot password '123456'
⑩ 停止服务
[root@master ~]# systemctl stop mysqld
7.4 虚拟化环境的安装

使用virtualenvwrapper来安装虚拟化环境,本文在 virtualenvwrapper 部分已经介绍过,不再赘述。

7.5 部署流程

下面详细介绍部署流程:

# 激活本地虚拟化环境
thanlon@thanlon-master:~/PycharmProjects/venv/39bysj/bin$ source activate
# 导出虚拟化环境中的库
(39bysj) thanlon@thanlon-master:~/PycharmProjects/venv/39bysj/bin$ pip freeze > flask_39bysj_venv.txt
# 将导出的虚拟化环境上传到服务器中
(39bysj) thanlon@thanlon-master:~/PycharmProjects/venv/39bysj/bin$ scp flask_39bysj_venv.txt root@106.12.115.136:/root/
# 在服务器中安装并激活虚拟化环境
[root@master Envs]# mkvirtualenv flask_39bysj_venv
# 安装这些依赖包
(flask_39bysj_venv) [root@master ~]# pip3 install -r flask_39bysj_venv.txt 
# 到本地环境中导出mysql数据库
thanlon@thanlon-master:~$ mysqldump -uroot -p 39bysj>39bysj.sql
# 将数据库文件上传到服务器
thanlon@thanlon-master:~$ scp 39bysj.sql root@106.12.115.123:/root/
# 登录mysql然后把数据导入
MariaDB [(none)]> source 39bysj.sql
# 从git中获取项目的代码(如果没有git使用yum -y install git安装)
 [root@master flask_39bysj]# git clone https://github.com/ThanlonSmith/39bysj.git
# 创建名为uwsgiconf.ini的文件
[root@master flask_39bysj]# touch uwsgi.ini
# 在uwsgiconf.ini文件中写入以下内容(注意注释全不要,不然报错!!!)
[uwsgi]
socket = 127.0.0.1:8001                   # 启动程序时使用的地址和端口,与nginx中配置的对应
chdir = /home/thanlon/flask_39bysj/39bysj
wsgi-file = manage.py                     # 项目程序启动文件
callable = app                            # 程序内启用的application变量名字
processes = 2                             # 处理器的数量,我的是2个处理器
stats = 127.0.0.1:9191                    # 获取uwsgi统计信息的服务地址
threads = 2                               # 线程数量
pidfile = /home/thanlon/flask_39bysj/39bysj.pid
# 激活python虚拟环境中(才能使用uwsgi)
[root@master flask_39bysj]# workon flask_39bysj_venv
# 启动uwsgi(或者使用uwsgi --ini uwsgi.ini)
(flask_39bysj_venv) [root@master flask_39bysj]# uwsgi uwsgi.ini
# 编辑nginx.conf
[root@master flask_39bysj]# vim /usr/local/nginx/conf/nginx.conf
# 修改nginx的内容(后面有注释的部分需要做添加或修改)
location / {
           include uwsgi_params;
           uwsgi_pass 127.0.0.1:8001;                                 # 需要和uwsgi的配置文件里socket项的地址,注意与uwsgi.ini对应
           uwsgi_param UWSGI_PYHOME /root/Envs/flask_39bysj_venv;     # python的位置(虚拟环境),注意与uwsgi.ini对应
           uwsgi_param UWSGI_CHDIR /home/thanlon/flask_39bysj/39bysj; # 项目根目录,,注意与uwsgi.ini对应
           uwsgi_param UWSGI_SCRIPT manage:app;                       # 启动文件名和application变量,注意与uwsgi.ini对应
           # uwsgi_pass django;
                #root   html;
           #index  index.html index.htm;
        }
# 启动nginx
# 测试nginx配置文件是否没有语法错误
[root@master conf]# nginx -t
# 启动nginx
[root@master conf]# nginx 

uwsgi的重启命令:uwsgi --reload xxx.pid
uwsgi的关闭命令:uwsgi --stop xxx.pid[如遇关闭不了,直接杀死uwsgi进程]

nginx正常运行后,可以访问服务器,发现项目已经被部署好(注意这里无需解决静态文件,因为flask程序运行时已经加载静态文件):
在这里插入图片描述
nginx.conf 文件的所有配置信息(ssl证书也配置了):

# nginx是c写的
#user  nobody;
#表示两个进程,官方推荐根据cpu核数,我的云主机是两核,通过top然后按1可查看机器核数
worker_processes  auto;

#error_log是记录错误日志的,原来是关闭的,我们这里可以打开
error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#nginx的进程id
#pid        logs/nginx.pid;

#worker_connections:可连接数
events {
    worker_connections  1024;
}

#web服务器的配置都在http里
http {
    include       mime.types;
    default_type  application/octet-stream;
    #日志格式,$http_x_forwarded_for是:用于抓取爬虫,爬虫使用了代理,不知道你真实的ip,这个参数可以获取真实的ip
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    #长链接超时
    keepalive_timeout  65;
    #支持文件传输的时候进行压缩,传输效率提高
    #支持文件传输的时候进行压缩,传输效率提高
    #gzip  on;
    #虚拟主机标签段,在这里定义thanlon.cn
    #upstream django{
        #0.0.0.0:90;
    #   server 0.0.0.0:9000 weight=10;
    #}
    server {
        # 端口
        listen       80;
        # 域名
        server_name  localhost;

        # charset koi8-r;

        # access_log  logs/host.access.log  main;
        access_log  /home/thanlon/flask_39bysj/logs/access.log; # 服务器接收的请求日志
        error_log   /home/thanlon/flask_39bysj/logs/error.log;  # 错误日志

        location / {
           include uwsgi_params;
           uwsgi_pass 127.0.0.1:8001;                                 # 需要和uwsgi的配置文件里socket项的地址,注意与uwsgi.ini对应
           uwsgi_param UWSGI_PYHOME /root/Envs/flask_39bysj_venv;     # python的位置(虚拟环境),注意与uwsgi.ini对应
           uwsgi_param UWSGI_CHDIR /home/thanlon/flask_39bysj/39bysj; # 项目根目录,,注意与uwsgi.ini对应
           uwsgi_param UWSGI_SCRIPT manage:app;                       # 启动文件名和application变量,注意与uwsgi.ini对应
           # uwsgi_pass django;
                #root   html;
           #index  index.html index.htm;
        }
        # nginx处理静态页面资源
#       location /static{
           # alias参数告诉静态资源放在哪里
#         alias /opt/django/tmp/static/;
#       }
        #错误页面
        error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }
    #虚拟主机标签段
    server{
        listen       80;
        server_name  blueflags.cn;
        location / {
                root html;
                index  index.html index.htm;
        }
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    #HTTPS server
    #
    server {
        listen       443 ssl;
        server_name  localhost;
        server_name  localhost;

        ssl_certificate      /home/thanlon/cert/39bysj/1_www.bysj39.com_bundle.crt; # 写证书公钥;
        ssl_certificate_key  /home/thanlon/cert/39bysj/2_www.bysj39.com.key;        # 写证书密钥;

        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers  on;

        location / {
           include uwsgi_params;
           uwsgi_pass 127.0.0.1:8001;                                 # 需要和uwsgi的配置文件里socket项的地址
           uwsgi_param UWSGI_PYHOME /root/Envs/flask_39bysj_venv;     # python的位置(虚拟环境)
           uwsgi_param UWSGI_CHDIR /home/thanlon/flask_39bysj/39bysj;
           uwsgi_param UWSGI_SCRIPT manage:app;
           # uwsgi_pass django;
                #root   html;
           #index  index.html index.htm;
        }
    }
}
8. Linux补充
8.1 crond服务中设置定时任务向仓库中拉取代码
  • 安装crond相关的软件包:

其实不用安装的,一般系统中最小化安装系统时就已经安装了,并且会开机自启动crond服务,并为我们提供好编写计划任务的crontab命令。可以查看crond相关的安装包:

[root@instance-mtfsf05r ~]#  rpm -qa | grep cron
cronie-1.4.11-23.el7.x86_64
cronie-anacron-1.4.11-23.el7.x86_64
crontabs-1.11-6.20121102git.el7.noarch
  • 配置crond任务
# 打开配置文件
[root@master flask_39bysj]# crontab -e
# 这时会打开一个空白文件,这实际上调用的是vi,写配置任务例如:
0 0 * * * /usr/bin/cd /home/thanlon/flask_39bysj/39bysj/&&git pull origin master

我的计划任务中执行两条命令,一条是cd到我的项目目录,另一条时向我的仓库中拉取代码。这样当我更新完项目推送到仓库后,在每天的00:00:00时刻就会开始执行这两条命令向仓库拉取代码,并不需要我自己手动到服务器中执行这两条命令(手动更新资源)。

注意:向配置文件中写入计划任务保存退出后,不需要做任何操作,因为计划任务会自动执行。

  • 查看crond任务:crontab -l命令可用来查看执行的计划任务:
[root@master flask_39bysj]# crontab -l
0 0 * * * cd /home/thanlon/flask_39bysj/39bysj/&&git pull origin master
8.2 修改SSH默认登录端口
# 编辑ssh配置文件
[root@slave01 ~]# vim /etc/ssh/sshd_config
将"#Port 22"的注释"#"号去掉,把22改为你想要改成的端口号。
注意:0~65535全部是标准端口,但是从0~1024号端口是系统端口,用户无法修改。
1025~65534端口是系统为用户预留的端口,而65535号端口为系统保留。
# 配置完成后,重启ssh服务
[root@slave01 ~]# systemctl restart sshd
# 查看端口是否启动
[root@slave01 ~]# netstat -tunlp|grep 12345
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN      9073/sshd 
# 接下来你可以指定12345端口使用ssh
thanlon@thanlon-master:~$ ssh -p 12345 root@106.12.115.xxx
root@106.12.115.xxx's password: 
8.3 阿里云添加安全组规则

修改ssh的端口后,如果是百度的云服务器是不需自己手动把该端口添加到安全组规则中的。但如果是阿里的云服务器,是需要手动将该端口添加到安全组规则中的。下面简单描述阿里的云服务器是如何添加安全规则的:

(1)在实例中找到安全组配置
在这里插入图片描述
(2)选择配置规则
在这里插入图片描述(3)添加安全组规则
在这里插入图片描述
(4)配置安全组规则
在这里插入图片描述
配置完成后就可以使用ssh的这个端口连接云服务器了:ssh -p 43322 [email protected]

8.3 Centos7安装MySQL8.0

本文第三部分,也就是“MySQL相关”这部分,我们安装使用的是MariaDB。虽然MariaDB和MySQL基本没有什么区别,但有些同学可能就想安装使用MySQL。为满足大家的需求,所以这里还是以实战操作的方式和大家分享MySQL 8.0的安装过程。

首先你需要卸载服务器上已经安装的MySQ,这个可以根据你的安装方式自行卸载。查询服务器中是否有以rpm包方式安装的MySQL,可以使用rpm -qa | grep -i mysql命令。使用whereis mysqlfind / -name mysql命令可以查看与mysql相关的目录以及文件,如:

[root@slave01 ~]# whereis mysql
mysql:[root@slave01 ~]# 
[root@slave01 ~]# find / -name mysql
/var/lib/mysql
/var/lib/mysql/mysql
/etc/selinux/targeted/active/modules/100/mysql
[root@slave01 ~]# rm -rf /var/lib/mysql/
[root@slave01 ~]# rm -rf /etc/selinux/targeted/active/modules/100/mysql/
[root@slave01 ~]# find / -name mysql	
[root@slave01 ~]# 

我们使用安装包离线安装,需要下载Mysql离线安装包,官网MySQL 8.0下载链接。我的服务器操作系统是Centos7,我这里选择Centos7版本的MySQL。普及一下,Centos系统是Red Hat Linux的开源版本,相较于Red Hat Linux,其免费开源的特性比较受用户青睐。但是Red Hat Linux有专门的人员提供企业及技术支持,Centos却没有。图示如下:
在这里插入图片描述
可以本地下载完成后上传到服务器,也可以复制下载链接,使用wget命令下载。我这里直接在服务器环境下使用命令下载,下载完成后即可开始MySQL的安装,操具体作如下:

# 下载MySQL 8.0的安装包
[root@slave01 ~]# wget https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.18-1.el7.x86_64.rpm-bundle.tar
# 解压安装包
[root@slave01 ~]# tar xvf mysql-8.0.18-1.el7.x86_64.rpm-bundle.tar 
mysql-community-libs-8.0.18-1.el7.x86_64.rpm
mysql-community-devel-8.0.18-1.el7.x86_64.rpm
mysql-community-embedded-compat-8.0.18-1.el7.x86_64.rpm
mysql-community-libs-compat-8.0.18-1.el7.x86_64.rpm
mysql-community-common-8.0.18-1.el7.x86_64.rpm
mysql-community-test-8.0.18-1.el7.x86_64.rpm
mysql-community-server-8.0.18-1.el7.x86_64.rpm
mysql-community-client-8.0.18-1.el7.x86_64.rpm

解压完成后,会有如上所示8个rpm包。我们只需要按顺序安装其中的四个包,因存在包与包之间的依赖关系,所以需要按照顺序,具体操作如下:

# 安装mysql-community-common
[root@slave01 ~]# rpm -ivh mysql-community-common-8.0.18-1.el7.x86_64.rpm
# 安装mysql-community-libs
[root@slave01 ~]# rpm -ivh mysql-community-libs-8.0.18-1.el7.x86_64.rpm
# 安装mysql-community-client
[root@slave01 ~]# rpm -ivh mysql-community-client-8.0.18-1.el7.x86_64.rpm
# 安装mysql-community-server
[root@slave01 ~]# rpm -ivh mysql-community-server-8.0.18-1.el7.x86_64.rpm

安装完毕后,需要初始化数据库。MySQL 8.0不再有 my.ini 配置文件,我们通过mysqld --initialize --console命令可以自动生成MySQL的初始化配置(data文件目录等)。执行mysql_secure_installation命令做一些常规化安全设置:

# 初始化MySQL数据库
[root@slave01 ~]# mysqld --initialize --console
# 开启MySQL服务
[root@slave01 ~]# systemctl start mysqld

如果开启数据库失败,遇到如下问题:

Job for mysqld.service failed because the control process exited with error code. See "systemctl status mysqld.service" and "journalctl -xe" for details.

你需要这样操作(如果没有遇到跳过这部分):

# 删除/var/lib/mysql目录
[root@slave01 ~]# rm -rf /var/lib/mysql
# 开启MySQL服务
[root@slave01 ~]# systemctl start mysqld
# 查看MySQL的开启状态
[root@slave01 ~]# systemctl status mysqld
● mysqld.service - MySQL Server
   Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled)
   Active: active (running) since 六 2019-12-21 13:11:33 CST; 2min 42s ago
     Docs: man:mysqld(8)
           http://dev.mysql.com/doc/refman/en/using-systemd.html
  Process: 30057 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS)
 Main PID: 30139 (mysqld)
   Status: "Server is operational"
   CGroup: /system.slice/mysqld.service
           └─30139 /usr/sbin/mysqld

不论是不带密码使用mysql命令还是使用mysql -uroot -p都是是不能登录的:

[root@slave01 ~]# mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
[root@slave01 ~]# mysql -uroot -p
Enter password: 
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)

需要设置root密码:

# 编辑配置文件
[root@slave01 ~]# vim /etc/my.cnf

在末行加入skip-grant-tables参数,表示跳过输入密码,如下图所示:
在这里插入图片描述
保存退出后要重启MySQL服务。使用mysqlmysql -uroot -p命令可以免密直接登录数据库:

[root@slave01 ~]# mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.18 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

修改密码的操作如下:

# 选择数据库
mysql> use mysql
# 将user表中root字段对应的authentication_string字段清空
mysql> update user set authentication_string='' where user='root';
# 退出MySQL
mysql> exit
# 编辑配置文件,将参数skip-grant-tables注释掉或者删掉
[root@slave01 ~]# vim /etc/my.cnf
# 重启MySQL服务
[root@slave01 ~]# systemctl restart mysqld
# 再次登录MySQL
[root@slave01 ~]# mysql
# 为root用户设置密码,要注意密码不能设置得太简单,否则系统还会提示你再次输入
mysql> alter user 'root'@'localhost' identified by 'Root123456!';

退出数据库,重新登录时要使用设置好的密码登录。至此,安装与配置已完成,有问题可以在评论区留言。

Linux命令可以参考:1.《Linux入门到进阶》2.《Linux命令分类汇总

发布了54 篇原创文章 · 获赞 138 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Thanlon/article/details/100941385
今日推荐