linux是操作系统。一般来讲,我们的应用程序完成开发之后, 是会把它部署到服务器上的。
windows系统的稳定性不高,linux操作系统的稳定性比windows高。目前来说,国内选择使用linux操作系统来做应用服务器的操作系统。
linux是基于unix的,开放源代码。任何个人或者公司都可以修改linux内核的源代码,实现或者增强自己想要功能。
unix之父:丹尼斯.里奇,肯.汤姆逊
linux之父:林纳斯.托瓦兹
linux是一个性能稳定的多用户网络操作系统。
目前市面上比较知名的发行版:redhat、ubuntu、debian、centos等。
| 比较 | windows | linux |
|---|---|---|
| 操作界面 | 界面形式统一,快捷键几乎相同。 | 图形界面风格依发布版本不同而不同,可能互不兼容的。 |
| 驱动程序 | 驱动程序丰富,版本更新频繁。 | 由志愿者开发,由linux核心开发小组发布,由于linux开源,硬件厂商基于版权考虑并未提供驱动程序。 |
| 使用 | 使用比较简单,容易入门。 | 如果是图形界面,使用简单。但如果是文字界面,需要专门的学习才可以使用。 |
| 学习 | 易学难精。 | 难入门,入门后想学精,比较容易。 |
| 软件 | 商业软件多,且需要购买相应授权。 | 大部分软件可以自由获取,同样功能的软件选择范围小。 |
Linux安装
安装虚拟机和linux。
虚拟机:vmware
linux:centos7
系统目录和分区
目录介绍
bin:
binary的缩写,这个目录存放的是经常使用的命令。
boot:
存放的是启动linux时使用的一些核心文件。
dev:
是device(设备)缩写,存放linux外部设备。
etc:
存放所有系统管理的配置文件和子目录。
home:
用户的主目录,在linux中,每个用户都会有一个自己的目录,一般该目录名以用户的账号命名。用户对自己的home目录有完全操作权限。
lib:
存放着系统最基本的动态连接共享库,作用类似于windows中的dll文件。
lost+found:
一般来说,该目录都是空的。当系统非法关机时,这里就存放了一些文件。
media:
存放的是一些外接设备。linux系统会自动识别U盘和光驱等设备,当识别后,会将这些设备存放到media目录中。
mnt:
系统提供该目录用于让用户临时挂载别的文件系统,把光驱挂载在mnt目录,我们可以进该目录查看光驱内容。
opt:
给主机额外安装软件所摆放的目录。
proc:
这是一个虚拟的目录,它是系统内存的映射。
使用以下命令可以屏蔽主机的ping命令,让别人无法ping你的机器echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
root:
系统管理员目录,也称作超级权限者的用户主目录。
sbin:
super user,存放的是系统管理员使用的系统管理程序。
selinux:
redhat/centos所特有的目录,类似于windows的防火墙,是一个安全机制。
srv:
存放着一些服务启动后的需要提取的数据。
sys:
该目录下安装了linux2.6内核中新出现的一个文件系统sysfs。
tmp:
存放临时文件。
usr:
这是一个非常重要的目录,用户的很多应用程序和文件都放在该目录下,类似于windows系统中program files目录。一般会在usr/local下新建一个tmp,再把所有压缩包上传到tmp目录中,当外部资源使用完成后,可以直接删除该目录。
var:
这是一个非常重要的目录,存放着一些不断扩充着的东西。我们习惯于将那些经常被修改的目录放到该目录下,包括各种日志文件。
常用命令
ls
显示当前目录下所有子目录及文件。
常用参数:
-a:显示隐藏文件和隐藏目录。在linux中,目录或文件名以.开头是隐藏文件或隐藏目录。
-l:显示文件或目录的详细信息。ll命令与ls -l等效。
linux的命令中,参数个数可以是0到多个。
cd
进入某个目录。
cd 路径
路径:相对路径和绝对路径。
. 当前目录.. 上一级目录
相对路径不能以/开头,因为以/开头是绝对路径的表示方式。
相对路径从当前目录开始查找,绝对路径从根目录开始查找。
mkdir
创建空目录。
-p:在创建过程中,如果路径中的某一级目录缺失,会自动进行创建。
如果不使用-p参数,创建时如果路径中缺失了某一级目录,创建失败。
touch
创建空文件。
vi
进行文件编辑。
vi:类似于普通文本编辑。
vim:类似于高级文档编辑器,自带颜色。
文件编辑的步骤:
1、进入文件编辑界面时,按insert或i进入可编辑状态;
2、编辑文件内容结束后,按esc退出可编辑状态;
3、退出文件编辑(有3种方式可退出)
没有对文件进行任何修改时,可以使用:q退出
对文件进行了修改,但不想保存该修改时,可使用:q!强制退出
对文件进行了修改,并要保存修改后的状态,可使用:wq保存退出
使用vi/vim进行文件编辑时,如果该文件不存在,会自动创建空文件以供使用。
cat
查看文件内容。
cat 文件1 文件2 >> 文件3:将文件1和文件2的内容联合起来放入到文件3中。
head [-n]
查看文件的前n行内容。如果不加参数n,默认查看前10行内容。
tail [-n]
查看文件的后n行内容。如果不加参数n,默认查看后10行内容。
tail [-f]
动态查看文件的后n行内容。
less
分页查看文件内容。j ->向上 k->向下
ctrl+c
不是复制的意思,而是中断某个正在进行的操作。
echo
输出内容并向文件中添加。
echo '内容' >> 文件名称
cp [-r]
复制文件或目录。
复制文件:
cp 要复制的文件 想要粘贴的路径/粘贴后文件的名称
注意:复制文件完成在粘贴时,可以修改文件名称(这不是对原文件进行重命名操作,是复制文件)
复制目录:
cp -r 要复制的目录 想要粘贴的路径/粘贴后目录的名称
mv
移动文件或目录。mv命令移动目录时,不需要-r参数。
可使用该命令完成目录或文件的重命名。
rm [-rf]
删除文件或目录。
删除文件:
rm 文件名
-f:直接执行删除操作,用户无需再次确认是否删除。
-r:操作的是目录时,需要使用此参数。
注意:删除操作需谨慎,不要直接进行删除操作,如果要删除,应该先备份再删除。
ip addr
查看IP。
clear
清屏。
df
查看磁盘可用空间及空间节点信息。
free
查看内存
reboot
重启虚拟机。
shudown/halt
关机。
shutdown -h 时间(分钟) 多久之后关机halt 直接关机
ln [-s]
创建软链接,类似于windows系统中创建快捷方式。
不跟参数是创建硬链接
tar
解压。
tar -zxvf 文件名
压缩
tar -zcvf 文件名 被压缩文件
常用参数:zxvf
z:先解压gzip格式文件
x:解包
v:显示解压详细信息
f:使用文件,紧跟f参数后一定是空格加文件,也就是说,f是tar命令的最后一个参数
c : 建立压缩档案
find
find 路径 方式 表达式
-name
find 路径 -name “java”
? 一个字符
* 任意多个字符
[]` 任何一个 `[H|d]* 以 H或者d开头的文件
- [a-z] 小写字符
- [A-Z] 大写字母
- [a-Z] 大小写字母
- [0-9]数字
find . -name "[a|A].*"find / -name "boot.*"
-type
find 路径 -type 文件类型符号
- f:普通文件,如文本文件,可执行文件
- d:目录
- l:软链接文件
- c:字符设备,如终端,磁带机等
- b:块设备,如光盘,硬盘等
ps
查看进程
ps -ef | grep tomcat | grep -v grep 查看tomcat进程号,排除grep自身产生的进程号
kill
杀死进程
kill -9 进程编号
service(systemctl)
系统服务
service 服务名 start|stop|restart|statussystemctl 服务名 start | stop | restart |status(查看状态)
跟权限相关的命令
linux中的用户:所有者(u-user),所属组(g-group),其他用户(o-other)
所有者是文件或者目录的创建者。
-— -— -—
表示文件的类型,是目录还是文件,还是其他类型的。
第一个-—表示所有者u,第二个-—表示所属组,第三个-—表示其他用户。
‘r’:读权限
‘w’:写权限
‘x’:执行权限
‘-’:没有权限
chmod
授权。
+表示增加权限
-表示减少权限
=表示指定权限
在实际应用中,权限使用数字进行描述。
使用3个数字(0-7)来描述目录或文件的当前权限。 chmod 000 tmp
第一个数字表示所有者的权限;
第二个数字表示所属组的权限;
第三个数据表示其他用户的权限。
‘r’:4
‘w’:2
‘x’:1
\’-‘:0
授权时,目录和文件是有区别的。
对文件而言:
r表示可以对文件进行读取:cat\head\tail\tailf
w表示可以对文件进行修改:vi/vim
x表示当前文件可以被执行,一般来说,只有scripts(脚本)和command(命令)是可以执行的。
对目录而言:
r表示可以访问当前目录:cd
w表示可以对当前目录中的子目录或文件进行创建和删除操作。touch\mkdir\rm
chmod 指定的权限 指定的目录或文件chmod [[u|g|o][+|-|=][r|w|x|-] | 3个0-7的数字] 指定的目录或文件含义为将指定的目录或文件的权限修改为指定的权限。注意:只有目录或文件的所有者和root用户可执行此操作。
chown
修改文件或目录的所有者。
chown 指定的用户 指定的目录或文件含义为将指定的目录或文件的所有者修改为指定的用户。注意:只有root用户可执行此操作。
groupadd
创建一个新的用户组。
chgrp
修改文件或目录所属的用户组。
chgrp 指定的用户组 指定的目录或文件含义为将指定的目录或文件的所属用户组修改为指定的用户组。注意:只有root用户可执行此操作。
useradd
创建新用户。
useradd 用户名
passwd
修改用户密码。
passwd 用户
id
查看用户信息。
id 用户
whoami
查看当前用户。
su
切换用户。root切换到其他,不需要输入密码。其他切换root,需要输入密码。
su 用户名
常用辅助工具
xshell:建立本地与服务器之间的远程连接,从而可以开发人员在本地对远程服务器进行操作。
xftp:文件上传。
安装软件源yum -y install epel-releaseyum -y install 软件名常用的如下:vim 文档编辑wget 远程下载curl 客户端(client)的URL工具,用来请求 Web 服务器lrzsz 在linux中可以代替ftp的上传和下载 使用rz命令可实现上传,只适合传输小文件,大文件应使用专业工具实现
安装JDK
步骤1:
下载资源:一定保证本地版本和远程服务器版本保持一致。jdk-11.0.6_linux-x64_bin.tar.gz
下载时应注意:下linux版本的,后缀以.tar.gz结束的。
步骤2:
文件上传。/usr/local/tmp
步骤3:
解压。
[root@localhost tmp]# tar -zxvf jdk-11.0.6_linux-x64_bin.tar.gz
步骤4:
剪切至/usr/local/jdk。
[root@localhost tmp]# mv jdk-11.0.6 ../jdk
步骤5:
配置环境变量。
[root@localhost local]# vim /etc/profile增加以下内容:export JAVA_HOME=/usr/local/jdkexport PATH=$PATH:$JAVA_HOME/bin如果javac和java命令不能使用,则需要手动配置CLASSPATH。可用则无需配置。#export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
系统级环境变量可以在/etc/profile.d/下面去自定义.sh文件,profile会自动加载
步骤6:
导入环境变量。
[root@localhost local]# source /etc/profile
步骤7:
测试: java -version, javac
yum安装mysql、jdk
#语法yum -y install 软件名 安装软件:-y表示安装过程中所有询问都为yesyum remove 软件名 删除已安装软件yum update {软件} 更新当前软件版本,如果有新版本的话yum info {软件} 查看软件信息yum list 软件名 查询可供安装的软件yum search 软件名 查询可供安装的软件
yum安装jdk1.8(openjdk)
# 安装前先检查系统中是否已安装jdk,如有,应先删除rpm -qa |grep javarpm -qa |grep jdkrpm -qa |grep gcj# 如检测到,可使得以下命令删除原有的jdkrpm -qa | grep java | xargs rpm -e --nodeps# 查看可供安装的1.8版本yum list java-1.8*# 安装jdkyum -y install java-1.8.0-openjdk*# 验证java -version
注意:使用yum安装openjdk,无需配置环境变量。
yum安装mysql5.7
# 1 下载mysql57wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm# 2 安装rpmyum -y install mysql57-community-release-el7-10.noarch.rpm# 3 安装mysqlyum -y install mysql-community-server# 4 启动mysql服务systemctl start mysqld.servicesystemctl status mysqld.service# 5 获取mysql安装时生成的随机密码grep "password" /var/log/mysqld.log# 6 登录mysql,注意:如果密码中有特殊字符,需要在特殊字符前加\进行转译mysql -uroot -p密码# 7 在修改密码前,关闭mysql密码验证规范,避免过于简单的密码设置失败set global validate_password_policy=0;set global validate_password_length=1;# 8 设置密码,修改用户以提供远程访问set password for 'root'@'localhost' = 'root';rename user 'root'@'localhost' to 'root'@'%';# 9 放行3306端口firewall-cmd --add-port=3306/tcp --zone=public --permanentfirewall-cmd --reload#10 防止自动更新,删除下载的mysql源#yum -y remove mysql57-community-release-el7-10.noarch
补充
- 网络如何配置

- 编辑配置文件
- vi sysconfig/network-scripts/ifcfg-ens33
- 重启network服务 systemctl restart network
- nmtui / 改完重启网络服务
- bash 与 alias
配置文件 /etc/bashrc 每次进入shell时都会source以下bashrc,配置生效
alias 别名 给一个命令取一个别名,方便记忆和使用
alias sysout='cowsay -f turtle'alias chkps='ps aux | grep'----source /etc/bashrc
alias cd=’rm -rf’
nginx
http://nginx.org/en/download.html
轻量级高性能的http服务器,将资源发布为网络资源,俄罗斯
安装步骤
添加仓库,在/etc/yum.repos.d路径下,创建文件 nginx.repo,内容如下:
[nginx]name=nginx repobaseurl=http://nginx.org/packages/centos/$releasever/$basearch/gpgcheck=0enabled=1
yum install -y nginx
- nginx命令启动
- 配置文件
- /etc/nginx nginx.conf
- /usr/share/nginx html的位置
- /var/log/nginx/ 日志
反向代理

反向代理:
- 隐藏内部结构
- 提供高性能的http服务,在tomcat出错,得到友好的提示
- 负载均衡服务
步骤:
- 关闭selinux,防止403问题
- 修改/etc/selinux/config文件,将enforcing改为disabled;
- 重启电脑 shutdown -r / halt / reboot
配置nginx vim /etc/nginx/default.d/default.conf
location / {proxy_pass http://localhost:8080;# 转发原始IP和headerproxy_set_header X-real-ip $remote_addr;proxy_set_header Host $http_host;}
修改nginx.conf
#root /usr/share/nginx/html;
启动nginx,重新加载配置
nginx -s reload- 启动 spring-boot项目
负载均衡
单机问题:
- 出现故障,导致服务不可用
- 单机性能有上限,无法永远支撑服务规模扩张
冷备(主机,备机,备机不开机) 热备(主机备机开着,但只有主机提供服务,浪费)
集群:
负载均衡问题:将请求均衡的分发给所有参与服务的服务器
如何具体分发请求的策略称为:负载均衡策略
- 轮询
- 加权轮询
- 随机
- IP hash,对请求的IP进行hash计算,保证同一个IP的请求被分发给同一台服务器处理
- …
- 编写一个测试用的spring-boot项目
- 配置一个随机端口 server.port=${random.int(8000,8010)}
- 开发一个返回端口的接口 ```java package com.woniuxy.simple;
- 配置一个随机端口 server.port=${random.int(8000,8010)}
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class GetPortController {
@Autowiredprivate Environment environment;@GetMapping("getPort")public String getPort(){//获得当前应用端口return environment.getProperty("local.server.port");}
}
2. 部署两次,获得两个端口2. 配置nginxnginx.conf```nginx# 配置一个集群upstream woniu_cluster{server 127.0.0.1:8001 weight=1;server 127.0.0.1:8009 weight=2;}
default.conf
location / {proxy_pass http://woniu_cluster;proxy_set_header X-real-ip $remote_addr;proxy_set_header Host $http_host;}
- 重载nginx
nginx -s reload - 在服务器外部,访问 http://192.168.60.179/getPort
两个版本
- 我的版本 nginx.conf + /etc/nginx/default.d/default.conf
- 另一个那本 nginx.conf + /etc/nginx/config.d/default.conf
修改default.conf 里的location 的配置
upstream也是配在default.conf
Docker



容器 vs 虚拟机
虚拟机: 虚拟一个完整地操作系统;传输,封装效率很低;大小一般在若干G
容器:复用宿主机的操作系统;内部包含了一个完整地操作系统;大小在几十上百M
- windows版本的docker与linux版本的docker的容器不相通的
容器可以理解为轻量级虚拟机
原理:复用宿主机系统,并且进行了各种资源隔离;
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。由于容器是进程级别的,相比虚拟机有很多优势。
- 启动快
- 资源占用少
- 体积小
安装docker
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
修改docker的镜像源为国内源
/etc/docker/daemon.json
{"registry-mirrors": ["https://ustc-edu-cn.mirror.aliyuncs.com","https://hub-mirror.c.163.com"]}
启动docker : systemctl start docker
docker info ==>
Registry Mirrors:https://ustc-edu-cn.mirror.aliyuncs.com/https://hub-mirror.c.163.com/
测试: docker run hello-world
docker的概念
client: docker客户端
deamon: docker守护线程
registry: 仓库 https://hub.docker.com/search?q=&type=image
container: 容器
image: 镜像文件
封装镜像文件 ——> 发布到仓库(registry)——> 下载镜像文件———> 创建容器(在宿主机上运行的一个程序)
centos+wnmysql
镜像:文件,类似Java类
容器:程序,类似Java的对象
nginx
docker pull 拉取镜像 docker pull nginx
docker run 创建并运行一个容器 docker run nginx 创建并运行一个nginx的容器
docker images 查看本地镜像
docker container ls -a 查看所有容器 简写为 docker ps -a
docker run ubuntu:latest 直接创建容器,并运行,并退出
docker run -itd ubuntu 运行并保持容器不关闭
docker rm id/名字 删除
docker ps -aq | xargs docker rm 删除全部容器,删除之前需要先关闭
docker run -itd —name=myubtu ubuntu
进入容器中,并打开一个bash终端
docker exec -t -i myubtu /bin/bash
docker run -itd —name=jdk openjdk
安装mysql
- docker run -itd —name=mysql5 -e MYSQL_ROOT_PASSWORD=root123 -p 3307:3306 mysql:5
- -itd 后台运行
- —name 名字
- -e 环境变量,设置的root用户密码
- mysql:5 镜像名:标签
- -p 端口映射 {宿主机端口}:{容器端口}
下载一个mysql:5镜像,并且创建容器,运行该容器
- 进入容器,操作mysql docker exec -it mysql5 /bin/bash
> mysql -uroot -proot123 # 进入mysql
> grant all privileges on . to root@’%’identified by ‘root123’; #授权,外部也可以访问
> FLUSH PRIVILEGES;
> exit; # 退出mysql
exit 退出容器
- 测试
systemctl stop firewalld 关闭防火墙,便于测试
在window上通过navicat访问3307端口,测试连接
复习
- nginx
- /etc/nginx/nginx.conf
- /var/log/nginx/error.log access.log
- 静态资源托管
- 反向代理: 在服务端的代理:隐藏内部结构;提供高性能的http服务,提供出错提示
- proxy_pass
- 负载均衡:将请求均衡的分发给后端服务器
- upsteam xxx_cluster{}
- proxy_pass http://xxx_cluster
- 策略
- 轮询: 按顺序调用后端服务器,每个服务器被调用的频率是均等的
- 加权轮询: 按顺序调用后端服务器,每个服务器被调用的频率根据权重来分配
- 随机:随机分配
- IP Hash:对来源IP进行hash计算,根据结果分配到指定服务器,理论上一个IP固定分配到同一台服务器
- …
- docker
- docker vs vm虚拟机区别
- vm: 内部包含一个完整地操作系统,体积大,一般若干个G
- docker:
- 复用宿主机的操作系统,体积小,速度快,效率高,一般几十M
- 资源隔离:linux的namespace机制,docker容器是进程级别
- docker基本概念
- 容器:container,程序,从镜像生成的一个实例,在进程中运行
- 镜像: 文件,容器的模板,类似于Java类与Java对象中的Java类
- 仓库 registry: 存储镜像文件,便于分发
- docker-daemon
- docker-client
- docker vs vm虚拟机区别


- 命令
- 镜像
- 查看 docker image ls <=> docker images;
- docker images -q 只输出id
- docker images -q | xargs docker image rm
- 查看信息 docker inspect 查看镜像的详细信息
- 拉取 docker image pull <=> docker pull 从仓库拉取镜像
- 推送 docker image push <=> docker push 向仓库推送镜像
- 删除 docker image rm
- 查看 docker image ls <=> docker images;
- 容器
- 查看容器 docker container ls <=> docker ps -aq
- 创建容器 docker container create <=> docker create
- 启动容器 docker container start <=> docker start {容器ID或名字}
- 进入容器 docker exec -it {容器ID或名字} /bin/bash 进入容器并执行一个命令
- 运行容器 docker container run <=> docker run {镜像名字:tag} 每次新建
- 没有镜像,拉镜像 > 创建容器 > 启动容器
- 选项
-p端口映射 {宿主机端口}:{容器端口}-i保持住标准输出-t分配一个虚拟TTY,终端-d在后台运行-v挂载数据卷。将宿主机的文件或者文件夹映射到容器中 {宿主机的文件夹}:{容器的文件夹}--name取名字-e环境变量,向mysql容器传递一个root用户密码
- 镜像

容器是对于环境的封装;隐藏细节;开放接口
- -v 数据卷- -p 端口映射- -e 环境变量 mysql容器需要配置,其他容器的一些功能开关- ...
jdk:
docker run —name=jdk openjdk
mysql:
docker run —name=mysql5 -p 3307:3306 -d -e MYSQL_ROOT_PASSWORD=root123 mysql:5
nginx:
docker run —name=nginx -p 81:80 -d -v ~/html:/usr/share/nginx/html nginx

docker cp 拷贝 宿主机到容器
docker cp ./a.txt nginx:/tmp/
docker cp 宿主机文件位置 容器名:容器路径
容器使用注意事项
- 尽量让一个容器做一件事情,或启动一个服务。
- 尽量使用挂载的方式将数据文件挂载到容器中,容器里面尽量不要保存数据。
- 尽量让容器按照Docker化的要求来使用容器,而不是安装一个虚拟机。
- 尽量不使用交互模式来直接操作容器,而是在宿主机上执行命令,或者使用Docker File。
- 只要能解决问题,高效地解决问题,无论怎么用,都行。
DockerFile
自行构建镜像
构建一个spring-boot项目的镜像 simple:latest
docker run -d -p 8080:8080 simple 启动spring-boot,运行,成功,直接访问
显然,运行springboot需要基于jdk
docker镜像是分层的
Dockerfile是一个构建镜像的脚本文件
docker执行dockerfile后,根据指令,构建出一个镜像
dockfile 本身有自己的指令集
# Dockerfile# 基础镜像FROM openjdk:8-jdk-alpine# 设置工作目录WORKDIR /tmp# 将jar文件拷贝到镜像中ADD *.jar app.jar# 运行时设置JAVA环境参数ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"# 端口暴露(推荐不暴露端口,在运行容器时才对端口进行绑定)# EXPOSE 9702#ENTRYPOINT不支持环境变量展开,specify spring.profiles.activeENTRYPOINT ["java", "-Duser.timezone=Asia/Shanghai -Dspring.profiles.active=docker", "-jar", "/tmp/app.jar"]
dockerfile存在target所在的目录
- 将项目打包 mvn clean package -Dmaven.test.skip=true
- 将jar包与dockerfile 上传到虚拟机的一个文件夹下
- 使用命令
docker build -t simple:latest .构建镜像 ,注意末尾的. - docker images查看
- docker run 运行 ,绑定端口
mvn spring-boot:build-image
docker网络
docker image xxx
container xxx
volume xxx
network xxx
docker network
- create 创建网络
- connect 将容器加入到网络
- rm 删除网络
- ls 列出网络
- inspect 查看网络的详细信息
实验:
- 启动一个名为mysql5的容器
- 将spring-boot项目封装成docker镜像,运行容器
- 将数据库地址的主机名改为 mysql5
url: jdbc:mysql://mysql5:3306/edu_56 - 封装镜像,运行,会失败
- 将数据库地址的主机名改为 mysql5
- 创建一个docker网络
- 将msyql5 和 项目加入同一个网络,项目通过网络可以访问到mysql5
# 创建网络docker network create app-net# 查看网络列表docker network ls# 将mysql5容器加入到app-net网络中docker network connect app-net mysql5# 查看app-net的详情docker network inspect app-net# 将myapp容器加入到app-net网络中docker network connect app-net myapp# 查看日志 -f 跟随docker logs -f myapp# 测试是否连接成功curl localhost:8082/students
总结部署
前端脚手架项目部署:
- npm run build 构建,成品在dist目录下,都成为了静态资源
- 使用nginx进行静态资源托管
部署的配置问题
- 环境
- 开发环境: 本机mysql
- 测试环境
- 联调环境
- 预发布环境
- 生产环境
- spring.profiles.active 环境配置
1)开发环境,通过运行配置设置profile
2)生产环境,通过环境变量设置profile,设置成prod
修改环境变量 /etc/profile ,修改完成记住source
export SPRING_PROFILES_ACTIVE=prod
3)其他方式
java -jar -Dspring.profiles.active=dev simple-0.0.1-SNAPSHOT.jar
不同环境变量对应的配置文件 为 application-{xxx}.yaml
docker容器部署之后,网页上的ajax发请求,地址写谁的地址?
redis
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
- 存储使用内存,快
- 用作 缓存、数据库
- 数据结构
- string 字符串,对数值可以进行计算
- hash kv形式的存储,类似于Java中的Map
- list 列表
- set 集合
- sorted set 有序集合
- bitmap 位图
- hyperloglog
- geospatial 地理空间
- 副本 、lua脚本、持久化、LRU、事务
- Redis哨兵提供高可用
- Cluster提供了自动分区
centos安装
简单命令
k-v形式
k 字符串
v 是各种类型 ( string, hash ,bitmap,hyerloglogs)
Key操作
| 序号 | 命令及描述 |
|---|---|
| 1 | DEL key 该命令用于在 key 存在时删除 key。 |
| 2 | DUMP key 序列化给定 key ,并返回被序列化的值。 |
| 3 | EXISTS key 检查给定 key 是否存在。 |
| 4 | EXPIRE key seconds 为给定 key 设置过期时间,以秒计。 |
| 5 | EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 |
| 6 | PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。 |
| 7 | PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 |
| 8 | KEYS pattern 查找所有符合给定模式( pattern)的 key 。 |
| 9 | MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。 |
| 10 | PERSIST key 移除 key 的过期时间,key 将持久保持。 |
| 11 | PTTL key 以毫秒为单位返回 key 的剩余的过期时间。 |
| 12 | TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 |
| 13 | RANDOMKEY 从当前数据库中随机返回一个 key 。 |
| 14 | RENAME key newkey 修改 key 的名称 |
| 15 | RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。 |
| 16 | TYPE key 返回 key 所储存的值的类型。 |
# 增set k v ex 过期时间(秒) px 过期时间(毫秒)# 删del k# 改rename k newk# 过期expire k secs 设置过期时间(秒)pexpire k millis 设置过期时间(毫秒)persist k 取消过期时间# 查ttl / pttl 剩余过期时间get k 查看内容type k 查看类型exists k 是否存在
- redis
- 概念: 开源的内存数据结构存储,可以用作数据库、缓存、消息中间件等。
- 数据结构:string\hash\set\list\sorted set\bitmap\hyperloglogs\geospatial
- 特性:持久化、通过哨兵实现高可用、通过cluster实现自动分区、事务、lua脚本支持、LRU内存清理策略
- 安装 yum install -y redis
- key相关的命令
- set
- get
- expire pexpire <=> persist
- ttl pttl
- del
- keys
- exists
- type
- rename
- 概念: 开源的内存数据结构存储,可以用作数据库、缓存、消息中间件等。
String
set 新增或者修改
- EX 过期时间秒
- PX 过期时间毫秒
- NX 新增操作,必须不存在,如果存在则失败
- XX 修改操作,必须存在,如果不存在则失败
get 获取
mget 一次获取多个key的值
getset
strlen 长度
incr 对数字加一
decr 对数字减一
incrby 对数字加N
decrby 对数字减N
append 追加
mset user:huangguang:degree baobiao user:huangguange:age 18 user:huangguange:gender Mmget user:cyx:degree user:cyx:age user:cyx:gender
使用场景:
- 统计:在线人数
- 缓存
- 分布式锁:排他性,A加锁之后其他人加不了锁
- set NX 加锁 加过期时间防止死锁 、 value表示加锁者 ```bash 127.0.0.1:6379> set lock:balance:decr james ex 30 OK 127.0.0.1:6379> set lock:balance:decr durant ex 30 NX (nil)
execA set lock lucas ex 30 NX | set lock curry ex 30 NX(失败)<br />execB<br />execC 解锁 | set lock curry ex 30 NX(成功)- del 解锁 不能直接删除,判断value是否为当前用户,当前用户是否为加锁者 if(val==当前用户){del lock}- 从获取值到判断时存在一段时间间隔,可能存在并发问题,需要保证判断和删除两个操作的原子性,使用lua脚本封装<a name="LNLSa"></a># Hash K-V结构<br />hset 添加修改<br />hget 获取<br />hmset 添加修改多个字段<br />hmget 获取多个字段<br />hgetall 获取所有字段<br />hincrby 增加某个数字的值,可以为负数<br />hexists 判断是否有字段```bash127.0.0.1:6379> hset user:cyx age 23(integer) 1127.0.0.1:6379> hset user:cyx score 1000(integer) 1127.0.0.1:6379> hget user:cyx age"23"127.0.0.1:6379> hgetall user:cyx1) "age"2) "23"3) "score"4) "1000"127.0.0.1:6379> hget user:cyx score"1000"127.0.0.1:6379> hgetall user:cyx1) "age"2) "23"3) "score"4) "1000"127.0.0.1:6379> hmset user:hg age 18 score 1000 hobby codingOK127.0.0.1:6379> hmget user:hg age score hobby1) "18"2) "1000"3) "coding"127.0.0.1:6379> hincrby user:hg age 10(integer) 28127.0.0.1:6379> hincrby user:hg age -5(integer) 23127.0.0.1:6379> hexists user:hg age(integer) 1127.0.0.1:6379> hexists user:hg honor(integer) 0127.0.0.1:6379> hexists user:hg lunch(integer) 0127.0.0.1:6379> hkeys user:hg1) "age"2) "score"3) "hobby"127.0.0.1:6379> hvals user:hg1) "23"2) "1000"3) "coding"127.0.0.1:6379> hlen user:hg(integer) 3
使用场景:
- 缓存,存对象
- 短网址生成器 短网址<=>真实网址
- 用户登录存储
- 手机验证码,带过期时间
- …
List


LPUSH 加入,从尾部加入
RPUSH
LINSERT 插入
lset
LPOP 弹出,从头部取出
RPOP
LREM key count element
删除等于element的元素
删除count次
count如果>0 从左往右 头到尾
count<0 从右往左 尾到头
count=0 删除所有
LLEN
lrange ```bash lpush eat pty hqh ls fg
lrange eat 0 -1 rpush eat cyx
linsert eat AFTER hqh zyq linsert eat BEFORE hqh pyy lpop eat lindex eat 1
lrem eat 3 pty
<a name="ceJa8"></a>## 使用场景1. 队列数据结构: 消息队列1. 分页查询:lrange<a name="xCu1K"></a># Setsadd 添加<br />spop 随机删除<br />srandmember 随机取出<br />scard 统计个数<br />sdiff k1 k2 从第一个减去其他的集合<br />sinter k1 k2 取得交集,都有元素<br />sunion k1 k2 取并集,将所有不同的元素合并<br />sdiffstore sinterstore sunionstore 将计算出的结果存入新的集合<br />smove 原始集合 目标集合 元素 移动到另一个集合中<br />sismember 判断是否在集合中<a name="JyaPD"></a>## 使用场景1. 社交网络:共同好友,共同关注,共同爱好....1. 唯一计数器:去重特性1. 标签:1. 点赞、投票1. 随机点名<a name="5mGOF"></a># SortedSetzadd 元素需要一个分数 zadd products 1000 wahaha 300 cokecola 299 pepsicola<br />zrange 默认升序排序<br />zrevrange 默认降序 zrevrange products 0 3<br />zrangebyscore 返回分数,升序<br />zrevrangebyscore 返回分数,降序<br />zcount products 300 10000 分数在区间范围内的数量<br />zrank products cokecola 排名<br />zscore products wahaha 得到对应的分数<a name="R1kVU"></a>## 使用场景- 排行榜- 时间线,将时间转换为毫秒,按时间排序<a name="CAQFH"></a># Redis使用规范[https://developer.aliyun.com/article/531067](https://developer.aliyun.com/article/531067)keys \flushall \config 不能使用<br />vim /etc/redis.conf```bashrename-command flushall ""rename-command flushdb ""rename-command keys ""
Scan
SCAN cursor [MATCH pattern] [COUNT count]
cursor 游标,特殊游标0表示开始迭代
match 匹配规则, glob风格(=所有,?=单个字符…)
count 限制最大返回数量
循环按批取出所有数据
scan 0 match user: count 10
返回两个元素的数组,第一个元素是下一次迭代的游标 第二元素是本次迭代的数据
下一次迭代时用 上一次的游标;当游标再次返回0时,表示迭代结束
full iteration :以 0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历。
127.0.0.1:6379> set user:cyx:energy lowOK127.0.0.1:6379> set user:zzq:energy lowOK127.0.0.1:6379> set user:fg:energy 0OK127.0.0.1:6379> set user:djx:energy lowOK127.0.0.1:6379> set user:yxd:energy highOK127.0.0.1:6379> set a bOK127.0.0.1:6379> set c dOK127.0.0.1:6379> scan 0 match user:* count 101) "0"2) 1) "user:fg:energy"2) "user:yxd:energy"3) "user:djx:energy"4) "user:cyx:energy"5) "user:zzq:energy"127.0.0.1:6379> scan 0 match user:* count 31) "6"2) 1) "user:fg:energy"2) "user:yxd:energy"127.0.0.1:6379> scan 6 match user:* count 31) "7"2) 1) "user:djx:energy"2) "user:cyx:energy"3) "user:zzq:energy"127.0.0.1:6379> scan 7 match user:* count 31) "0"2) (empty list or set)127.0.0.1:6379> scan 6 match user:* count 31) "7"2) 1) "user:djx:energy"2) "user:cyx:energy"3) "user:zzq:energy"127.0.0.1:6379> scan 7 match user:* count 31) "0"2) (empty list or set)127.0.0.1:6379> scan 7 match user:* count 21) "0"2) (empty list or set)127.0.0.1:6379> scan 0 match user:* count 21) "2"2) 1) "user:fg:energy"127.0.0.1:6379> scan 2 match user:* count 21) "1"2) 1) "user:yxd:energy"127.0.0.1:6379> scan 1 match user:* count 21) "7"2) 1) "user:djx:energy"2) "user:cyx:energy"3) "user:zzq:energy"127.0.0.1:6379> scan 7 match user:* count 21) "0"2) (empty list or set)127.0.0.1:6379> sadd stus wangyang tangxiao xiongwei suhaoran yuexiaodong liuyouli chenyang dingyi(integer) 8127.0.0.1:6379> sscan stus 01) "0"2) 1) "yuexiaodong"2) "liuyouli"3) "dingyi"4) "xiongwei"5) "suhaoran"6) "wangyang"7) "chenyang"8) "tangxiao"127.0.0.1:6379> sscan stus 0 count 31) "5"2) 1) "yuexiaodong"2) "liuyouli"3) "dingyi"4) "xiongwei"5) "suhaoran"127.0.0.1:6379> sscan stus 5 count 31) "0"2) 1) "wangyang"2) "chenyang"3) "tangxiao"127.0.0.1:6379>
Java连接Redis
1. Jedis 基础驱动
建的maven的quickstart项目,加依赖
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.1</version></dependency>
编码 ```java package com.woniuxy.redis;
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool;
/**
Hello world! / public class App { public static void main( String[] args ) {
//建立链接JedisPool jedisPool = new JedisPool("localhost", 6379);Jedis jedis = jedisPool.getResource();//发送指令if(!jedis.exists("stus")) {jedis.sadd("stus", "邓继鑫", "董海川", "丁懿", "陈扬", "刘又溧", "程昱翔", "杨林", "白秋洋", "田杰仁", "乐小东", "傅刚", "彭淘沿", "胡启昊", "黄小渝", "杜罗乐", "栗浩然", "谭宪军", "黄煜", "熊威", "彭亿元", "周橘恒", "熊春林", "唐肖", "周友强", "朱志强", "黄广", "王阳", "彭章明", "杨文", "甘可可", "徐阳", "彭川弋", "严川", "廖松", "汪界行");}String theOne = jedis.srandmember("stus");System.out.println("天之骄子:"+theOne);//清理关闭jedis.close();jedisPool.close();
} }
<a name="ZMMB9"></a>## 2. spring-data-redis 高级封装[https://spring.io/projects/spring-data-redis](https://spring.io/projects/spring-data-redis)<br />1. 建项目 选择 spring-web lombok spring-data-redis<br />2.编码- RedisTemplate- opsForXXX 针对于XXX类型的操作- boundXXXOps```javapackage com.woniuxy.redis.redisbootdemo.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.*;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Set;@RestController@Slf4jpublic class StudentController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("dm")public String callTheRoll() {//String操作ValueOperations ops = redisTemplate.opsForValue();ops.set("qbzs", "cyx");// ops.setIfAbsent();//如果不存在,则设置 set NX// ops.setIfPresent()//如果存在,则设置 set XX//Hash操作HashOperations hashOps = redisTemplate.opsForHash();//hset user:1 age 23hashOps.put("user:1", "age", 23);//Set操作SetOperations setOps = redisTemplate.opsForSet();if(!redisTemplate.hasKey("stusboot")){setOps.add("stusboot", "邓继鑫", "董海川", "丁懿", "陈扬", "刘又溧", "程昱翔", "杨林", "白秋洋", "田杰仁", "乐小东", "傅刚", "彭淘沿", "胡启昊", "黄小渝", "杜罗乐", "栗浩然", "谭宪军", "黄煜", "熊威", "彭亿元", "周橘恒", "熊春林", "唐肖", "周友强", "朱志强", "黄广", "王阳", "彭章明", "杨文", "甘可可", "徐阳", "彭川弋", "严川", "廖松", "汪界行");}Object theOne = setOps.randomMember("stusboot");log.info("天之骄子 : {}",theOne);//boundXXXOps 绑定一个KEY,后续操作无须带keyBoundZSetOperations zSetOps = redisTemplate.boundZSetOps("products");zSetOps.add("wahaha",1000);zSetOps.add("tianfukele",200);zSetOps.add("jianlibao",300);Set set = zSetOps.reverseRange(0, zSetOps.size());log.info("排名情况:{}",set);return "天之骄子 :"+theOne;}}
Redisson
Redis特性
缓存
@GetMapping("/student/{id}")public Student find(@PathVariable("id")Integer id) throws ParseException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {log.info("查找学生 :{}",id);if(id==null){return null;}Student student = null;//1. 从缓存中获取String stuKey= "stu:"+id;if(redisTemplate.hasKey(stuKey)){log.info("缓存中存在{},从缓存中获取",stuKey);BoundHashOperations ops = redisTemplate.boundHashOps(stuKey);//hash的所有字段,表示学生的所有信息Map<String,Object> entries = ops.entries();log.info("{}",entries);// //转换成student对象 反序列化student = new Student();student.setClassName((String) entries.get("className"));student.setStudentId(Integer.parseInt((String)entries.get("studentId")) );student.setStudentName((String) entries.get("studentName"));student.setStudentNo((String) entries.get("studentNo"));student.setGender((String) entries.get("gender"));student.setAge(Integer.parseInt((String) entries.get("age")));student.setMajor((String) entries.get("major"));//String registerTime = (String) entries.get(entries.get("registerTime"));// student.setRegisterTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(registerTime));student.setCounselor((String) entries.get("counselor"));// student = BeanUtils.mapToBean(entries, Student.class);// student = new Student();// BeanUtils.populate(student,entries);return student;}//2. 从数据库中查询log.info("查询数据库,id=",id);student = studentService.findById(id);//3. 加入到缓存中log.info("存入缓存中{}",student);BoundHashOperations ops = redisTemplate.boundHashOps(stuKey);Map<String, String> stringObjectMap = BeanUtils.describe(student);ops.putAll(stringObjectMap);redisTemplate.expire(stuKey,30, TimeUnit.MINUTES);return student;}
- 不够通用 2. 不够简洁
Spring 缓存抽象 spring cache abstraction
增 @Cachable
删 @CacheEvict
改 @CachePut
查
在application类上加 @EnableCaching
@GetMapping("findById")@Cacheable(cacheNames = "stuCache",key="#stuId")public Student findById(int stuId){log.info("从数据库获取数据{}",stuId);return studentService.findById(stuId);}@PutMapping("updateStu")@CachePut(cacheNames = "stuCache",key = "#student.studentId")public Student updateStudent(@RequestBody Student student){studentService.update(student);return student;}@DeleteMapping("/deleteById")@CacheEvict(cacheNames = "stuCache",key = "#id")public void deleteStudent(int id){studentService.delById(id);}
- pom.xml必须要有redis的依赖, SpringCache 支持多种不同的缓存,其中包括内存缓存,如果没有redis依赖,则使用内存缓存
- 缓存对象,要求加Serializable接口
- 如何查看redis中的缓存?
keys *string缓存配置
spring:redis:host: localhostport: 6379cache:type: redisredis:time-to-live: 60Mkey-prefix: 'redisbootdemo::'
缓存问题

缓存过期的作用=> 保证数据的一致性;减少内存空间;
缓存击穿:有一个key过期时,大量的查询该key的请求,透过redis进入到数据库,key是热点key
- 时间点:在某个key过期 到 该key的缓存重新生成之间
- 解决方案:在缓存过期后,请求加互斥锁
缓存穿透:大量查询不存在的key
- 解决方案:缓存空值;布隆过滤器 BloomFilter(超大的位数组,每1位只有0和1可以表示是否存在)
缓存雪崩:多个key在同一个时间点过期,造成大量的查询透过redis进入到数据库
- 解决方案:设置不同的过期时间
redis缓存支持
指定缓存的内存空间大小 maxmemory 100mb
达到内存上限时如何删除? maxmemory-policy
- noeviction:返回错误,当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于带有过期时间的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
LRU Least-Recently-Used 最近最少使用
