瑞吉外卖优化
缓存优化
使用Git管理代码
先让IDEA绑定Github
,在vcs下选择创建一个git仓库,并选择需要被Git管理的目录,选择确认
再右击整个项目,菜单栏有个Git,依次点击命令即可
记得选中整个文件夹,不然推送可能不一定完整,commit后会出现以下情况,展示页面跟IDEA的版本有关,不过整体内容大差不差
这里我们选择连Commit and Push提交带推送,这样比较快捷,但是我们还没有设置自己的远程仓库,需要去Github上复制一下自己的仓库地址来进行上传
Commit and Push后出现以下页面
在这里定义远程仓库,把刚刚复制的仓库地址粘贴上去
点击push推送过去,可能会让你登录一下Github进行授权啥的,这里登录就行了,然后等待推送完成,去Github上面查看一下,有就成功了
这时候我们再创建一个新的分支,把内容跟master分支分隔开
这里我创建了一个v1.0的分支
然后,我们把内容推送到v1.0这个分支上去
现在的内容没有变化,是因为还没有修改代码的内容,后期在v1.0上开发,内容就不一样了
在v1.0上开发完成后,还可以将内容合并回master分支上,非常方便和快捷,即使写错了也不会修改原master上的代码
环境搭建
问题说明
问题说明:
- 当用户数量足够多的时候,系统访问量大
- 频繁的访问数据库,系统性能下降,用户体验差
- 所以一些通用、常用的数据,我们可以使用Redis来缓存,避免用户频繁访问数据库
导入SpringDataRedis的maven坐标
1 | <dependency> |
redis配置文件
1 | redis: |
配置序列化器
- 配置一下序列化器,防止乱码问题(实际上不是乱码,只是一种展现的形式),方便我们在图形化界面中查看我们存入的数据,在config包下新建RedisConfig类
- 但是也可以不配置RedisConfig,而是直接用
SpringRedisConfig
,它的默认序列化器就是StringRedisSerializer
1 | import org.springframework.cache.annotation.CachingConfigurerSupport; |
最后将修改完的数据提交并推送到那个新的v1.0的分支上去即可
缓存手机验证码
实现思路
先想一下之前做的邮件验证码是存储在哪的
存储在Session域中,现在学习了Redis,就可以缓存在Redis中
操作步骤如下:
- 在服务端UserController中注入RedisTemplate对象,用于操作Redis;
- 在服务端UserController中的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟,因为一般的手机短信有效期也为5分钟
- 在服务端UserController中的login方法中,从Redis中获取缓存的代码,如果登录成功则删除Redis的验证码
代码改造
在UserController中注入RedisTemplate对象,用于操作Redis
1 |
|
在服务端UserController中的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟
修改前后比较
1 | @PostMapping("/sendMsg") |
修改后的代码
1 |
|
在服务端UserController中的login方法中,从Redis中获取缓存的代码,如果登录成功则删除Redis的验证码
修改前后比较
1 | @PostMapping("/login") |
修改后的代码
1 |
|
功能测试
在测试时,发现一个报错:ERR Client sent AUTH, but no password is set,说是密码没有设置,原因是我们是通过redis-server.exe启动的,而双击启动默认是去找redis.conf的配置文件,然后没找到,所以也就报了之前的ERR Client sent AUTH, but no password is set错误。
解决办法:
方案1:
在redis安装目录下找到redis.windows.conf文件
找到这一行:
然后在redis目录下cmd运行命令:redis-server.exe redis.windows.conf
方案2:
其实就没设置密码,你直接把IDEA里的密码删了就行,无需密码可以直接访问
其实是类似于之前在Linux中的vim文件里的配置
这里我采用的是方案2,方案1我试了几次,感觉比较麻烦,所以我采用了方案2
配好之后我们再次测试
打开redis的服务器和客户端
获取邮箱验证码后,在redis中进行查看
这里发现,redis中已经缓存了我们的数据了
此时我们进行登录
此时就登录成功了,我们再去看看验证码有没有被删除
缓存菜品数据
- 菜品数据是我们登录移动端之后的展示页面
- 所以每当我们访问首页的时候,都会调用数据库查询一遍菜品数据
- 对于这种需要频繁访问的数据,我们可以将其缓存到Redis中以减轻服务器的压力
实现思路
- 移动端对应的菜品查看功能,是DishController中的list方法,此方法会根据前端提交的查询条件进行数据库查询操作(用户选择不同的菜品分类)。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。所以现在我们需要对此方法进行缓存优化,提高系统性能
- 但是还有存在一个问题,我们是将所有的菜品缓存一份,还是按照菜品/套餐分类,来进行缓存数据呢?
- 答案是后者,当我们点击某一个分类时,只需展示当前分类下的菜品,而其他分类的菜品数据并不需要展示,所以我们在缓存的时候,根据菜品的分类,缓存多分类数据,页面在查询时,点击某个分类,则查询对应分类下的菜品的缓存数据
- 具体实现思路如下
- 修改DishController中的list方法,先从Redis中获取分类对应的菜品数据,如果有,则直接返回;如果无,则查询数据库,并将查询到的菜品数据存入Redis缓存中
- 修改DishController的save、update和delete方法,加入清理缓存的逻辑,避免产生脏数据,也就是说我们修改/更新/删除了菜品,但是缓存没有被清理,数据还是之前的,所以展示的还是之前的,我们需要清理缓存,让它重新加载数据(其实就是我们实际已经在后台修改/更新/删除了某些菜品,但由于缓存数据未被清理,未重新查询数据库,用户看到的还是我们修改之前的数据)
代码改造
在DishController中需要注入RedisTemplate
来使用Redis
1 |
|
修改后的代码如下
梳理一下思路,设立一个key用于区分不同的缓存内容,比如1号菜品里有什么菜,二号菜品有什么菜,这些缓存的key都是不同的,不能放在一个里面,通过key来进行区分,然后我们先从redis中获取对应的key,来进行判断,如果key存在,说明该缓存存在,直接返回缓存即可,否则,进行数据的查询,查询完成后,再使用redis进行数据的保存,最后返回数据
1 |
|
为save和update方法加入清理缓存的逻辑
1 |
|
1 |
|
这里我还没有写这个停售起售的功能,所以就没有进行更改,后期项目做完了再回头来补
注意:这里并不需要我们对删除操作也进行缓存清理,因为删除操作执行之前,必须先将菜品状态修改为停售
,而停售状态也会帮我们清理缓存,同时也看不到菜品,随后将菜品删除,仍然看不到菜品,故删除操作不需要进行缓存清理
功能测试
在进行测试前,我们需要做一件事,就是修改RedisConfig类中之前写的值的序列化器统一,之前把他们统一成String了,所以就会只能返回String,我们去注释掉里面的代码
1 | import org.springframework.cache.annotation.CachingConfigurerSupport; |
然后启动测试
此时,内容就都展现出来了
并且,我们也能在redis中查询得到菜品的数据,其他内容我就不测试了,有兴趣可以自己测一下
最后,我们将这次写的代码push到github上
Spring Cache
4.1Spring Cache介绍
- SpringCache是一个框架,实现了基本注解的缓存功能,只需要简单的添加一个注解,就能实现缓存功能
- SpringCache提供了一层抽象,底层可以切换不同的cache实现,具体就是通过CacheManager接口来统一不同的缓存技术
- 针对不同的缓存技术,需要实现不同的CacheManager
CacheManger | 描述 |
---|---|
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Googke的GuavaCache作为缓存技术 |
RedisCacheManager | 使用Rdis作为缓存技术 |
SpringCache使用方式
在springboot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching
开启缓存支持即可
如果只是使用SpringCache的基础功能,只需要导入spring-boot-starter-web这个包就可以了
1 | <dependency> |
使用Redis作为缓存技术,需要导入Spring data Redis
的maven坐标即可
1 | <dependency> |
随后配置application.yml
1 | spring: |
Spring Cache常用注解
注解 | 说明 |
---|---|
@EnableCaching | 开启缓存注解功能 |
@Cacheable | 在方法执行前spring先查看缓存中是否有数据。如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或者多条数据从缓存中删除 |
@Cacheable
@Cacheable
的作用主要针对在方法执行前spring先查看缓存中是否有数据。如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中,其主要参数说明如下
注解 | 说明 | 举例 |
---|---|---|
value | 缓存的名称,每个缓存的名称下面可以有多个Key,必须指定至少一个value | 例如:@Cacheable(value=”mycache”)或者@Cacheable(value=(“cache7”, “cache2”] |
key | 缓存的key,可以为空 | 例如:@Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,返回true或者false,只有为true 才进行缓存 | 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
unless | 满足条件则不缓存 | 例如:@Cacheable(value = “userCache”,key = “#id”,unless = “#result == null”) |
@CachePut
@CachePut
的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,将方法的返回值放入缓存
注解 | 说明 | 举例 |
---|---|---|
value | 缓存的名称,每个缓存的名称下面可以有多个Key,必须指定至少一个value | 例如:@CachePut(value = {“userCache”,”userCacher2”} |
key | 缓存的key,可以为空 | 例如:@CachePut(value = {“userCache”,”userCacher2”},key = “#user.id”) |
condition | 缓存的条件,可以为空,返回true或者false,只有为true 才进行缓存 | 例如:@CachePut(value=”testcache”,condition=”#userName.length()>2” |
这边的key可以通过里面的参数名.属性的形式来获取,使用**#参数名.属性**就可以将数据放入缓存中
当整个方法执行完后,通过返回的值来给key赋上值,缓存的值是被返回的整个对象
例如:
1 |
|
@CacheEvict
@CacheEvict
的作用是清除缓存
注解 | 说明 | 举例 |
---|---|---|
value | 缓存的名称,每个缓存的名称下面可以有多个Key,必须指定至少一个value | 例如:@CacheEvict(value=”mycache”)或者@CacheEvict(value={“cache1”, “cache2”] |
key | 缓存的key,可以为空 | 例如:@CacheEvict(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,返回true或者false,只有为true 才进行缓存 | 例如:@CacheEvict(value=”testcache”,condition=”#userName.length()>2”) |
@CacheEvict
可以清除某个缓存名称下的key缓存数据
例如:
1 | // 如果key为#p1,#p2这种,代表有多参数,根据p后面的数字来决定的 |
缓存套餐数据
在做套餐之前,我们可以先用注解的方式去缓存菜品数据
先在main下开启缓存功能
1 | import lombok.extern.slf4j.Slf4j; |
然后来到DishController的list方法下进行修改
1 |
|
在做缓存的时候报了一个错误DefaultSerializer requires a Serializable payload but received an object of type
解决方法:
为Result对象实现一个Serializable接口即可,因为Spring 会将对象先序列化再存入 Redis,所以需要实现这个接口
1 | import lombok.AllArgsConstructor; |
记得删除缓存,在方法中删除缓存使用@CacheEvict
save:
1 |
|
update:
1 |
|
至于为什么不给delete清缓存,在3.3功能测试有讲
这里我们的菜品就全部重新用注解重写了
再来写套餐的,写法是类似的,这里就不做说明了,直接给上代码
1 |
|
1 |
|
update方法也没写,哎,后期学完补上
将代码push到仓库里
读写分离
问题分析
- 目前我们所有的读和写的压力都是由一台数据库来承担,
- 如果数据库服务器磁盘损坏,则数据会丢失(没有备份)
- 解决这个问题,就可以用MySQL的主从复制,写操作交给主库,读操作交给从库
- 同时将主库写入的内容,同步到从库中
MySql主从复制
介绍
- MySQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台NysQL数据库(slave,即从库)从另一台MySQL数据库(master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。MySQL主从复制是MySQL数据库自带功能,无需借助第三方工具。
- 讲一下自己的理解,由从库(slave)向主库(master)进行日志的复制,再解析日志,解析完之后,就知道在主库里执行的是一个什么样的sql语句,然后在从库(slave)里面再执行一遍刚才得到的sql语句
- MySQL复制过程分成三步:
maste
r将改变记录到二进制日志(binary log
)slave
将master
的binary log
拷贝到它的中继日志(relay log
)slave
重做中继日志中的事件,将改变应用到自己的数据库中
配置
前置条件
准备好两台服务器,分别安装MySQL并启动服务成功,这里准备两台虚拟机,建议使用克隆
我们先得到主库和从库的IP地址
进入系统,打开命令行,输入ifconfig
查看IP地址
- 主库IP地址:192.168.10.135
- 从库IP地址:192.168.10.134
然后去mysql中连接这两个的数据库
如果在连接数据库时出现这样一个问题:
vim /etc/my.cnf
在[mysqld]后添加[skip-grant-tables
(登录时跳过权限检查)
重启MySQL服务:sudo systemctl restart mysqld
然后输入mysql就可以进去了,在里面就可以改密码干啥了,改了密码再重新连接一下就行,或者不搞密码直接搞连接也是可以的
到数据库中连接,发现这里也可以了,我就没有设置密码,感觉设置密码比较麻烦,后面再来考虑密码的问题
数据库搞好之后,我们用Xshell来连接一下自己的服务器,一个主库,一个从库
OK,这里连接完毕,确认一下mysql服务是否开启
配置主库
修改Mysql数据库的配置文件/etc/my.cnf
1 | vim /etc/my.cnf |
在mysqld下加入如下内容,skip-grant-tables是我们之前加上来用于跳过权限检查的
1 | log-bin=mysql-bin #[必须]启用二进制日志 |
因为刚刚改了配置文件,所以我们一下重启Mysql服务
1 | systemctl restart mysqld |
这时候我们再登陆Mysql数据库,执行下面的SQL
1 | grant replication slave on *.* to 'eastwind'@'%' identified by '1234'; |
在执行SQL的时候报了个异常:The MySQL server is running with the --[skip-grant-tables] option so it cannot execute this statement
解决方法:
先刷新一下权限表,把在所有数据库的所有表的所有权限赋值给位于所有IP地址的root用户。
1 | flush privileges; |
再执行就没问题了
注:上面SQL的作用是创建一个用户eastwind,密码为1234,并且给eastwind用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。
接着登录Mysql数据库,执行下面的SQL,记录下结果File和Position的值
1 | show master status |
1 | mysql> show master status; |
注:上面的SQL的作用是查看Master的状态,执行完该SQL后不要再执行任何操作,因为我们在执行其他任何操作时,里面的日志会变化,所以不要执行其他任何操作
配置从库
修改配置文件
跟配置从表差不多,也是在文件里面加入一点内容
1 | vim /etc/my.cnf |
1 | server-id=101 #[必须]服务器唯一ID |
接着重启mysql服务
1 | systemctl restart mysqld |
登陆Mysql数据库,执行下面的SQL
1 | change master to master_host='192.168.10.135',master_user='eastwind',master_password='1234',master_log_file='mysql-bin.000001',master_log_pos=612; |
简单解释一下这里的配置,master_host对应主库的ip地址,master_user对应主库的用户名,master_password对应主库的密码,master_log_file对应之前主库查出来的File,master_log_pos对应对应之前主库查出来的Position,根据自己的情况修改
然后执行这条SQL
1 | start slave; |
最后执行一条SQL查看一下slave的状态
1 | show slave status\G; |
本来是show slave status
但是显示的不太美观,所以加上\G格式化输出
这两个是必须是yes的,并且Slave IO State
什么也没有,我们发现这里其中一个是no,在翻看了文档后发现是克隆机的问题,我们需要修改克隆机的uuid,现在我们修改一下uuid
1 | mysql> select uuid(); |
查询克隆机的uuid,并记住它
接着查看配置文件目录
1 | mysql> show variables like "datadir"; |
编辑配置文件目录,修改uuid为刚刚我们生成的uuid
1 | vi /var/lib/mysql/auto.cnf |
重启服务
1 | systemctl restart mysqld |
再次进入mysql查看slave的状态
此时发现这两个都是Yes了,并且Slave IO State
也有了对应的内容,说明我们的配置完成了
测试
在主库中新建数据库,刷新一下从库
继续在主库中进行测试,新建一张表,对表进行一下增删改操作,看看从库中是否有变化,如果有变化说明配置的没有问题,这里其他的测试我就不展示了,挺简单的
读写分离案例
背景
面对日益增加的系统访问量,数据库的吞吐量面临着巨大的瓶颈。对于同一时刻有大量并发读操作
和较少的写操作
类型的应用系统来说,将数据库拆分为主库
和从库
,主库
主要负责处理事务性的增删改操作
,从库主要负责查询操作
,这样就能有效避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善
Sharding-JDBC介绍
Sharding-JDBC定位为轻量级的JAVA框架,在JAVA的JDBC层提供额外的服务,它使得客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架
使用Sharding-JDBC可以在程序中轻松的实现数据库读写分离
- 适用于任何基于JDBC的ORM框架
- 支持任何第三方的数据库连接池
- 支持任意实现JDBC规范的数据库
使用Sharding-JDBC框架的步骤
导入对应的maven坐标
1
2
3
4
5<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>在配置文件中配置读写分离规则,并配置允许bean定义覆盖配置项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41spring:
shardingsphere:
datasource:
names:
# 这里的master和slave并不是写死的,但是需要跟下面的master和slave对应
# 定义了两个数据源,名叫master和slave
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 主库ip及连接的数据库名
url: jdbc:mysql://192.168.10.135:3306/reggie?serverTimezone=UTC&useSSL=false
username: root
# 我的数据库没有密码,所以这里注释了,有需要可以自己调整
# password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库ip及连接的数据库名
url: jdbc:mysql://192.168.10.134:3306/reggie?serverTimezone=UTC&useSSL=false
username: root
# password: root
masterslave:
# 读写分离配置
# 负载均衡:配置的是从库的负载均衡策略(轮询策略)
# 轮询:从库可以有多个,假设有3个从库,第一次走sql查询就是走1号库,第二次走2号,这样以此类 推,直到走完之后再次重复一遍,说白了就是按顺序来
load-balance-algorithm-type: round_robin
# 最终的数据源名称
name: dataSource
# 指定主库数据源名称
master-data-source-name: master
# 指定从库数据源名称列表,多个从库用逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false,就是在控制台可以输出sql
# 配置允许bean定义覆盖配置项
main:
allow-bean-definition-overriding: true
项目实现读写分离
之前已经搭建好了主从复制的数据库,现在到主库去搭建项目的数据库
相应的,从库里面也会创建,检查一下就行
然后运行一下之前的sql文件代码
记得把这个下面这个读写分离的规则修改成改变之后的
导入对应的maven坐标
1 | <dependency> |
在配置文件中配置读写分离规则,并配置允许bean定义覆盖配置项
配置项可能会爆红,但是不影响影响项目启动,是IDEA的问题
1 | spring: |
测试一下,主要检查的查询语句是不是由从库发出,以及增删改操作是不是由主库发出即可
最后我们将代码上传到github,
然后合并到master分支
如何合并?
先回到master分支,点一下master然后checkout就可以切回去了
然后选中v1.0,选中Merge,合并到master
查看master分支下的yml配置文件,发现已经更新了
Nginx
简介
- Nginx是一款轻量级的
Web
/反向代理
服务器以及电子邮件(IMAP/POP3)代理服务器,其特点是占有内存少,并发能力强。 - 事实上Nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用Nginx的网站有:百度、京东、新浪、网易、腾讯、淘宝等。
- Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Pam6nep)开发的,第一个公开版本0.1.0发布于2004年10月4日。
- 官网:
https://nginx.org/
Nginx的下载和安装
Nginx是C语言开发的,所以需要先安装依赖
1 | yum -y install gcc pcre-devel zlib-devel openssl openssl-devel |
安装时遇到一个问题:Another app is currently holding the yum lock; waiting for it to exit…
说是另一个应用程序目前持有yum锁;等待它退出
解决方法:
1 | # 强行解除锁定 |
下载Nginx安装包
这里我去了nginx官网查看了现在的稳定版本,现在是1.24.0所以我也选择1.24.0
1 | wget https://nginx.org/download/nginx-1.24.0.tar.gz |
可以使用命令行方式,你也可以通过在Windows下载好之后传上来,我是直接上传了
解压,放在/usr/local
目录下
1 | tar -zxvf nginx-1.24.0.tar.gz -C /usr/local/ |
进入到我们解压完毕后的文件夹内
1 | cd /usr/local/nginx-nginx-1.24.0/ |
创建安装路径文件夹
1 | mkdir /usr/local/nginx |
安装前检查工作
使用nginx-nginx-1.24.0目录下的configure来指定安装目录(不是真正的安装,只是安装前的检查工作)
1 | ./configure --prefix=/usr/local/nginx |
编译并安装
1 | make && make install |
Nginx目录结构
- 安装完Nginx后,我们先来熟悉一下Nginx的目录结构
- 重点目录/文件:
- conf
- nginx配置文件
- 操作较多的是里面的nginx.conf
- html
- 存放静态文件(html、css、Js等)
- logs
- 日志目录,存放日志文件
- sbin/nginx
- 二进制文件,用于启动、停止Nginx服务
- conf
可以使用一个命令来展示当前目录下的内容
先安装这个tree
1 | yum install tree |
再输入tree
以树形结构来展示当前目录下的所有内容
Nginx配置文件结构
- Nginx配置文件(conf/nginx.conf)整体分为三部分
- 全局块 和Nginx运行相关的全局配置
- events块 和网络连接相关的配置
- http块 代理、缓存、日志记录、虚拟主机配置
- http全局块
- Server块
- Server全局块
- location块
注意:http块中可以配置多个Server块,每个Server块中可以配置多个location块
1 | worker_processes 1; <-- 全局块 |
Nginx命令
查看版本
- 进入sbin目录,输入
./nginx -v
1 | [root@localhost sbin]# ./nginx -v |
检查配置文件正确性
进入sbin目录,输入./nginx -t
,如果有错误会报错,而且也会记录日志
一般在启动Nginx服务之前检查
1 | [root@localhost sbin]# ./nginx -t |
启动和停止
进入sbin目录,输入./nginx
,启动完成后查看进程
1 | [root@localhost sbin]# ./nginx |
如果想停止Nginx服务,输入./nginx -s stop
,停止服务后再次查看进程
1 | [root@localhost sbin]# ./nginx -s stop |
重新加载配置文件
重新加载配置文件
- 当修改Nginx配置文件后,需要重新加载才能生效,可以使用下面命令重新加载配置文件:
./nginx -s reload
上面的所有命令,都需要我们在sbin目录下才能运行,比较麻烦,所以我们可以将Nginx的二进制文件配置到环境变量中,这样无论我们在哪个目录下,都能使用上面的命令
使用vim /etc/profile
命令打开配置文件,并配置环境变量,保存并退出
这个需要配置jdk的环境,记得改成自己的
1 | JAVA_HOME=/usr/local/jdk1.8.0_212 |
之后重新加载配置文件,使用source /etc/profile
命令,然后我们在任意位置输入nginx
即可启动服务,nginx -s stop
即可停止服务
1 | [root@localhost jdk1.8.0_212]# vim /etc/profile |
查看自己IP,启动服务后,浏览器输入ip地址就可以访问Nginx的默认页面
ifconfig
如果发现自己连不上Nginx的话,可能是防火墙的问题,这里我也遇到了这个问题,所以我们需要开放80端口,并重启防火墙
1 | firewall-cmd --zone=public --add-port=80/tcp --permanent |
1 | systemctl restart firewalld.service |
重启完成后,在浏览器中输入ip即可访问
Nginux具体应用
部署静态资源
- Nginx可以作为静态web服务器来部署静态资源。静态资源指在服务端真实存在并且能够直接展示的一些文件,比如常见的html页面、css文件、js文件、图片、视频等资源。
- 相对于Tomcat,Nginx处理静态资源的能力更加高效,所以在生产环境下,一般都会将静态资源部署到Nginx中。
- 将静态资源部署到Nginx非常简单,只需要将文件复制到Nginx安装目录下的html目录中即可。
反向代理
正向代理
- 正向代理是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
- 正向代理的典型用途是为在防火墙内的局域网客户端提供访问Internet的途径。
- 正向代理一般是在客户端设置代理服务器,通过代理服务器转发请求,最终访问到目标服务器。
总结一下正向代理,其实很好理解,客户端直接访问原始服务器是访问不到的,得依靠代理服务器来访问,所以客户端需要先向代理服务器发送请求来指定原始服务器,然后代理服务器向原始服务器转交请求,原始服务器将内容发给代理服务器,再依靠代理服务器来返回给客户端
反向代理
- 反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向代理服务器负责将请求转发给目标服务器。
- 用户不需要知道目标服务器的地址,也无须在用户端作任何设定。
简单来说,正向代理是:(客户端+代理服务器)访问(web服务器),, 反向代理是:(客户端)访问(代理服务器+web服务器)
反向代理直接访问的是代理服务器,然后让代理服务器去web服务器里转发给你
配置反向代理
1 | server { |
负载均衡
- 早期的网站流量和业务功能都比较简单,单台服务器就可以满足基本需求,但是随着互联网的发展,业务流量越来越大并且业务逻辑也越来越复杂,单台服务器的性能及单点故障问题就凸显出来了,因此需要多台服务器组成应用集群,进行性能的水平扩展以及避免单点故障出现。
- 应用集群:将同一应用部署到多台机器上,组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回响应数据。
- 负载均衡器:将用户请求根据对应的负载均衡算法分发到应用集群中的一台服务器进行处理。
配置负载均衡
默认是轮询算法,第一次访问是IP地址1
,第二次访问是IP地址2
也可以改用权重方式,权重越大,几率越大,现在的访问三分之二是第一台服务器接收,三分之一是第二台服务器接收server IP地址1 weight=10
server IP地址2 weight=5
proxy_pass要与targetServer一致
1 | upstream targetServer{ |
- 负载均衡策略
名称 | 说明 |
---|---|
轮询 | 默认方式 |
weight | 权重方式 |
ip_hash | 依据ip分配方式 |
least_conn | 依据最少连接方式 |
url_hash | 依据url分配方式 |
fair | 依据响应时间方式 |
Nginx的特点
- 跨平台:Nginx可以在大多数操作系统中运行,而且也有Windows的移植版本
- 配置异常简单:非常容易上手。配置风格跟程序开发一样,神一般的配置
- 非阻塞、高并发:数据复制时,磁盘I/O的第一阶段是非阻塞的。官方测试能够支撑5万并发连接,在实际生产环境中跑到2-3万并发连接数(这得益于Nginx使用了最新的epoll模型)
- 事件驱动:通信机制采用epoll模式,支持更大的并发连接数
- 内存消耗小:处理大并发的请求内存消耗非常小。在3万并发连接下,开启的10个Nginx进程才消耗150M内存(15M*10=150M)
- 成本低廉:Nginx作为开源软件,可以免费试用。而购买F5 BIG-IP、NetScaler等硬件负载均衡交换机则需要十多万至几十万人民币
- 内置健康检查功能:如果Nginx Proxy后端的某台Web服务器宕机了,不会影响前端访问。
- 节省带宽:支持GZIP压缩,可以添加浏览器本地缓存的Header头。
- 稳定性高:用于反向代理,宕机的概率微乎其微。
前后端分离开发
问题说明
- 开发人员同时负责前端和后端代码开发,分工不明确,开发效率低
- 前后端代码混合在一个工程中,不便于管理
- 对开发人员要求高,人员招聘困难
- 所以衍生出了一种前后端分离开发
介绍
前后端分离开发
,就是在项目开发过程中,对前端代码的开发,专门由前端开发人员
负责,后端代码由后端开发人员
负责,这样可以做到分工明确,各司其职,提高开发效率,前后端代码并行开发,可以加快项目的开发速度。目前,前后端分离开发方式已经被越来越多的公司采用了,成为现在项目开发的主流开发方式。- 前后端分离开发后,从工程结构上也会发生变化,即前后端代码不再混合在同一个maven工程中,而是分为
前端工程和后端工程
开发流程
- 前后端开发人员都参照接口API文档进行开发
- 接口(API接口) 就是一个http的请求地址,主要就是去定义:请求路径、请求方式、请求参数、响应参数等内容。
YApi
介绍
Api是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护API,YApi还为用户提供了优秀的交互体验,开发人员只需要利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具有可读性、可维护性,让团队协作更合理。
Git仓库:https://github.com/YMFE/yapi
使用
使用YApi,可以执行下面操作:
- 添加项目
- 添加分类
- 添加接口
- 编辑接口
- 查看接口
这个后期看看文档自学一下即可
Swagger
介绍
- 使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做成各种格式的接口文档,以及在线接口调试页面等。
- 官网:
https://swagger.io/
使用方式
- 导入对应的maven坐标
1 | <dependency> |
导入knife4j相关配置,并配置静态资源映射,否则接口文档页面无法访问,注意将controller的包路径修改为你自己的
1 |
|
在拦截器在中设置不需要处理的请求路径
1 | //定义不需要处理的请求 |
启动服务,访问 http://localhost/doc.html
,我这里的端口号用的80,根据自己的需求改,运行之前记得把linux的服务开起来
这里我启动时报了一个异常,
Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerException
springboot 升级到 2.6.0之后,swagger版本和springboot出现了不兼容情况,因为SpringBoot处理映射匹配的默认策略发生变化:请求路径与 Spring MVC 处理映射匹配的默认策略已从AntPathMatcher更改为PathPatternParser
在application.yml中配置,加在spring的下面,注意层级关系
1 | mvc: |
访问接口文档
此时就显示出来了
常用注解
注解 | 说明 |
---|---|
@Api | 用在请求的类上,例如Controller,表示对类的说明 |
@ApiModel | 用在类上,通常是个实体类,表示一个返回响应数据的信息 |
@ApiModelProperty | 用在属性上,描述响应类的属性 |
@ApiOperation | 用在请求的方法上,说明方法的用途、作用 |
@ApilmplicitParams | 用在请求的方法上,表示一组参数说明 |
@ApilmplicitParam | 用在@ApilmplicitParams注解中,指定一个请求参数的各个方面 |
加上这些注解,可以将我们生成的接口文档更规范,具体使用效果可以看看文档,这里不做太多介绍
项目部署
配置环境说明
一共需要三台服务器
- 192.168.10.135(服务器A)
- Nginx:部署前端项目、配置反向代理
- MySql:主从复制结构中的主库
- 192.168.10.134(服务器B)
- jdk:运行java项目
- git:版本控制工具
- maven:项目构建工具
- jar:Spring Boot 项目打成jar包基于内置Tomcat运行
- MySql:主从复制结构中的从库
- IP地址(服务器C)
- Redis:缓存中间件
在服务器A中安装Nginx,将前端项目打包
目录上传到Nginx的html目录下
修改Nginx配置文件nginx.conf,新增如下配置
1 | server { |
在服务器B中安装JDK,Git,MySql
将项目打成jar包,手动上传并部署(当然你也可以选择git拉取代码,然后shell脚本自动部署)
部署完后端项目之后,我们就能完成正常的登录功能了,也能进入到后台系统进行增删改查操作