目录
缓存优化
环境搭建
缓存短信验证码
缓存菜品数据
Spring Cache 实现案例
缓存套餐数据
读写分离
MySQL主从复制
读写分离案例
项目实现读写分离
Nginx
Nginx概述
Nginx命令
Nginx配置文件结构
Nginx具体应用
前后端分离开发
前后端分离开发
YApi
项目部署
问题说明
用户数量多,系统访问量大,频繁访问数据库,系统性能下降,用户体验差
对项目进行git管理
1、创建远程仓库reggie_take_out 私有仓库
2、在IDEA中的reggie_take_out项目创建本地仓库
3、配置好.gitignore文件,设置不被git管理的文件
4、添加远程仓库管理,推送项目到远程仓库
5、建立新的分支v1.0来实现缓存优化
maven坐标
在Spring Boot 项目中,可以使用Spring Data Redis来简化Redis操作,maven坐标:
org.springframework.boot
spring-boot-starter-data-redis
配置文件
在项目中的application.yml中加入Redis相关配置:
spring
redis:
host: localhost
port: 6379
password:
database: 0
配置类
在项目中加入配置类RedisConfig,改变key的序列化方式
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate
实现思路
前面已经实现了移动端手机验证码登录,随机生成的验证码是保存在HttpSession中的。
现在需要改造为将验证码缓存在Redis中,具体的实现思路如下:
1、在服务端UserController中注入RedisTemplate对象,用于操作Redis
2、在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟
3、在服务端UserController的lodin方法中,从Redis中获取缓存的验证码,
如果登录成功则删除Redis中的验证码
代码改造
在服务端UserController中注入RedisTemplate对象,用于操作Redis
@Autowired
private RedisTemplate redisTemplate;
发送手机验证码,改变存储方式
@PostMapping("/sendMsg")
public R sendMsg(@RequestBody User user, HttpSession session) {
//将要生成的验证码保存到Session
//session.setAttribute(phone, code);
//将生成的验证码缓存到Redis中,并且设置有效期5分钟
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
}
修改登录方法
@PostMapping("/login")
public R login(@RequestBody Map map, HttpSession session) {
//从session中获取保存的验证码
//Object codeInSession = session.getAttribute(phone);
//从Redis中获取缓存的验证码
Object codeInSession = redisTemplate.opsForValue().get(phone);
//如果用户登录成功,删除Redis中缓存的验证码
redisTemplate.delete(phone);
}
实现思路
前面已经实现了移动端菜品查看功能,对应的服务端方法为DishController的list方法,
此方法会根据前端提交的查询条件进行数据库查询操作。
在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长。
现在需要对此方法进行缓存优化,提高系统的性能。具体的实现思路如下:
1、改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;
如果没有则查询数据库,并将查询到的菜品数据放入Redis。
2、改造DishController的save和update方法,加入清理缓存的逻辑
这里清理缓存有两种方式 ①清理全部缓存 ②根据分类id清理缓存
注意事项:在使用缓存过程中要注意保证数据库中的数据和缓存中的数据一致,
如果数据库中的数据发生变化,需要及时清理缓存数据
代码改造
/**
* 菜品管理
*/
@Slf4j
@RestController
@RequestMapping("/dish")
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
@Autowired
private RedisTemplate redisTemplate;
/**
* 添加菜品
*
* @param dishDto
* @return
*/
@PostMapping
public R save(@RequestBody DishDto dishDto) {
log.info(dishDto.toString());
//清理所有菜品的缓存数据
/*Set keys = redisTemplate.keys("dish_*");
redisTemplate.delete(keys);*/
//清理某个分类下面的菜品缓存
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
dishService.saveWishFlavor(dishDto);
return R.success("添加菜品成功");
}
/**
* 修改菜品
* http://localhost:8080/dish
*
* @param dishDto
* @return
*/
@PutMapping
public R update(@RequestBody DishDto dishDto) {
log.info(dishDto.toString());
dishService.updateWishFlavor(dishDto);
//清理所有菜品的缓存数据
/*Set keys = redisTemplate.keys("dish_*");
redisTemplate.delete(keys);*/
//清理某个分类下面的菜品缓存
String key = "dish_" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
return R.success("添加菜品成功");
}
/**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
@GetMapping("/list")
public R> setmealByDishName(Dish dish) {
List dishDtoList = null;
//动态构造key dish_1397844263642378242_1
String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
//先从Redis中获取缓存数据
dishDtoList = (List) redisTemplate.opsForValue().get(key);
//如果存在,直接返回,无需查询数据库
if (dishDtoList != null) {
return R.success(dishDtoList);
}
//如果不存在,需要查询数据库
//构建查询条件
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId());
//添加条件,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus, 1);
//添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
//执行查询
List list = dishService.list(queryWrapper);
dishDtoList = list.stream().map((item -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);
Long categoryId = item.getCategoryId();//分类id
//根据id查询分类对象
Category category = categoryService.getById(categoryId);
if (category != null) {
String categoryName = category.getName();
dishDto.setCategoryName(categoryName);
}
//当前菜品id
Long dishId = item.getId();
//根据菜品id查询口味信息
LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(DishFlavor::getDishId, dishId);
//SQL:select * from dish_flavor where dish_id = dishId
List dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
dishDto.setFlavors(dishFlavorList);
return dishDto;
})).collect(Collectors.toList());
//如果不存在,需要查询数据库,将查询到的菜品数据缓存到Redis 60分钟
redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
return R.success(dishDtoList);
}
/**
* 修改status状态
*
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R updateStatus(@PathVariable Integer status, @RequestParam List ids) {
log.info("status:{}", status);
log.info("ids:{}", ids);
//根据传过来的ids数组,修改dish表的status状态
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
//添加条件
//selec * from dish where id in(?,?,?)
queryWrapper.in(ids != null, Dish::getId, ids);
//查询数据
List list = dishService.list(queryWrapper);
//修改dish表的status状态
list.stream().map((item) -> {
//清理某个分类下面的菜品缓存
String key = "dish_" + item.getCategoryId() + "_1";
redisTemplate.delete(key);
item.setStatus(status);
//修改状态
dishService.updateById(item);
return item;
}).collect(Collectors.toList());
return R.success("修改状态成功");
}
}
Spring Cache介绍
Spring Cache是一个框架,实现了基于注解的缓存功能,
只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象,底层可以切换不同的cache实现。
具体就是通过CacheManaqer接口来统一不同的缓存技术。
CacheManager是Spring提供的各种缓存技术抽象接口。
针对不同的缓存技术需要实现不同的CacheManager:
CacheManager | 描述 |
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
RedisCacheManager | 使用Redis作为缓存技术 |
Spring Cache常用注解
注解 | 说明 |
@EnableCaching | 开启缓存注解功能 |
@Cacheable | 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据,若没有数据,调用方法并将方法返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或多条数据从缓存中删除 |
在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,
并在启动类上使用@EnableCaching开启缓存支持即可。
例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
/**
* CachePut: 将方法返回值放入缓存
* value: 缓存的名称,每个缓存名称下面可以有多个key
* key: 缓存的key
* @param user
* @return
*/
@CachePut(value = "userCache",key = "#user.id")
@PostMapping
public User save(User user){
userService.save(user);
return user;
}
/**
* CacheEvict: 删除指定缓存
* value: 缓存的名称,每个缓存名称下面可以有多个key
* key: 缓存的key
*/
//注意key的参数名字要和方法参数名字一致
@CacheEvict(value = "userCache", key = "#id")
// @CacheEvict(value = "userCache", key = "#p0")
// @CacheEvict(value = "userCache", key = "#root.args[0]")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
@CacheEvict(value = "userCache", key = "#user.id")
// @CacheEvict(value = "userCache", key = "#result.id")
// @CacheEvict(value = "userCache", key = "#p0.id")
// @CacheEvict(value = "userCache", key = "#root.args[0].id")
@PutMapping
public User update(User user){
userService.updateById(user);
return user;
}
/**
* Cacheable:在执行方法前先查看当前缓存是否有数据,有数据则直接返回缓存数据,没有则调用方法,将方法返回值放入缓存
* value: 缓存的名称,每个缓存名称下面可以有多个key
* key: 缓存的key
* condition:条件,满足条件是执行
* @param id
* @return
*/
@Cacheable(value = "userCache",key = "#id",condition = "#result != null ")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.getById(id);
return user;
}
@Cacheable(value = "userCache",key = "#user.id + '_' + #user.name",condition = "#result != null")
@GetMapping("/list")
public List list(User user){
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(user.getId() != null,User::getId,user.getId());
queryWrapper.eq(user.getName() != null,User::getName,user.getName());
List list = userService.list(queryWrapper);
return list;
}
}
Spring Cache使用方式
在Spring Boot项目中使用Spring Cache的操作步骤(使用redis缓存技术):
1、导入maven坐标
spring-boot-starter-data-redis、spring-boot-starter-cache
2、配置application.yml
spring:
cache:
redis:
time-to-live: 1800000 #设置缓存有效期
3、在启动类上加入@EnableCaching注解,开启缓存注解功能
4、在Controller的方法上加入@Cacheable、@CacheEvict等注解,进行缓存操作
添加依赖坐标
org.springframework.boot
spring-boot-starter-data-redis
mysql
mysql-connector-java
runtime
配置application.yml
spring:
redis:
host: localhost
port: 6379
#password:
database: 0
cache:
redis:
time-to-live: 1800000 #设置缓存有效期
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private CacheManager cacheManager;
@Autowired
private UserService userService;
/**
* CachePut: 将方法返回值放入缓存
* value: 缓存的名称,每个缓存名称下面可以有多个key
* key: 缓存的key
* @param user
* @return
*/
@CachePut(value = "userCache",key = "#user.id")
@PostMapping
public User save(User user){
userService.save(user);
return user;
}
/**
* CacheEvict: 删除指定缓存
* value: 缓存的名称,每个缓存名称下面可以有多个key
* key: 缓存的key
*/
//注意key的参数名字要和方法参数名字一致
@CacheEvict(value = "userCache", key = "#id")
// @CacheEvict(value = "userCache", key = "#p0")
// @CacheEvict(value = "userCache", key = "#root.args[0]")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
@CacheEvict(value = "userCache", key = "#user.id")
// @CacheEvict(value = "userCache", key = "#result.id")
// @CacheEvict(value = "userCache", key = "#p0.id")
// @CacheEvict(value = "userCache", key = "#root.args[0].id")
@PutMapping
public User update(User user){
userService.updateById(user);
return user;
}
/**
* Cacheable:在执行方法前先查看当前缓存是否有数据,有数据则直接返回缓存数据,没有则调用方法,将方法返回值放入缓存
* value: 缓存的名称,每个缓存名称下面可以有多个key
* key: 缓存的key
* condition:条件,满足条件时才缓存
* unless: 满足条件则不缓存
* @param id
* @return
*/
// @Cacheable(value = "userCache",key = "#id",condition = "#result != null ")
@Cacheable(value = "userCache",key = "#id",unless = "#result == null ")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.getById(id);
return user;
}
@Cacheable(value = "userCache",key = "#user.id + '_' + #user.name",unless = "#result == null")
@GetMapping("/list")
public List list(User user){
LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(user.getId() != null,User::getId,user.getId());
queryWrapper.eq(user.getName() != null,User::getName,user.getName());
List list = userService.list(queryWrapper);
return list;
}
}
实现思路
前面已经实现了移动端套餐查看功能,对应的服务端方法为SetmealController的list方法,
此方法会根据前端提交的查询条件进行数据库查询操作。
在高并发的情况下,频繁查询数据库会导致系统性能下降,
服务端响应时间增长现在需要对此方法进行缓存优化,提高系统的性能。具体的实现思路如下:
1、导入Spring Cache和Redis相关maven坐标
2、在application.yml中配置缓存数据的过期时间
3、在启动类上加入@EnableCaching注解,开启缓存注解功能
4、在SetmealController的list方法上加入@Cacheable注解
5 、在SetmealController的save和delete方法上加入CacheEvict注解
代码改造
在SetmealController的方法上添加spring Cache缓存的注解开发
@CacheEvict(value = "setmealCache",allEntries = true) //删除setmealCache下的所有缓存数据
@PostMapping
public R save(@RequestBody SetmealDto setmealDto){...}
@CacheEvict(value = "setmealCache",allEntries = true) //删除setmealCache下的所有缓存数据
@DeleteMapping
public R deleteByIds(@RequestParam List ids) {...}
@CacheEvict(value = "setmealCache",allEntries = true) //删除setmealCache下的所有缓存数据
@PostMapping("/status/{status}")
public R update(@PathVariable("status") Integer status, @RequestParam List ids) {...}
@Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_'+#setmeal.status")
@GetMapping("/list")
public R> list(Setmeal setmeal) {...}
问题说明
读和写的所有压力都由一台数据库承担,压力大
数据库服务器磁盘损坏则数据丢失,单点故障
介绍
MySQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。
就是一台或多台MySOL数据库(slave,即从库)从另一台MySOL数据库(master,即主库)
进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。
MySOL主从复制是MySOL数据库自带功能,无需借助第三方工具。
MySQL复制过程分成三步
master将改变记录到二进制日志 (binary log)
slave将master的binary log拷贝到它的中继日志 (relay log)
slave重做中继日志中的事件,将改变应用到自己的数据库中
配置
提前准备好两台服务器,分别安装Mysg1并启动服务成功
主库Master 192.168.138.100
从库slave 192.168.138.101
克隆一台centos7,修改centos7克隆的ip地址:192.168.147.101
vi /etc/sysconfig/network/-scripts/ifcfg-ens33
重启网络服务
systemctl restart network
查询IP地址
ip addr
配置-主库master
第一步:修改Mysql数据库的配置文件/etc/my.cnf
[mysqld]
log-bin=mysql-bin#[必须]启用二进制日志
server-id=100#[必须]服务器唯一ID
第二步:重启MySQL服务
systemctl restart mysqld
第三步:登录Mysql数据库,执行下面SQL
GRANT REPLICATION SLAVE ON *.* to 'xiaoming'@'%' identified by 'Root@123456';
注:上面SOL的作用是创建一个用户xiaoming,密码为Rot@123456,
并且给xiaoming用户授予REPLICATION SLAVE权限。常用于建立复制时所需要用到的用户权限,
也就是slave必须被master授权具有该权限的用户,才能通过该用户复制。
第四步:登录Mysql数据库,执行下面SQL,记录下结果中File和Position的值
show master status;
注:上面SQL的作用是查看Master的状态,执行完此SQL后不要再执行任何操作
配置—从库Slave
第一步:修改Mysql数据库的配置文件/etc/my.cnf
[mysqld]
server-id=101#[必须]服务器唯一ID
第二步:重启MySQL服务
systemctl restart mysqld
第三步:登录Mysq1数据库,执行下面SQL
change master to master_host='192.168.147.100',master_user='xiaoming',master_password='Root@123456',master_log_file='mysql-bin.000001',master_log_pos=441;
#启动slave
start slave;
第四步:登录Mysq1数据库,执行下面SQL,查看从数据库的状态
show slave status;
连接测试主从复制
在测试的过程中遇到了修改了主库,从库没同步的问题,查看了从库 show slave status\G
发现 Slave_SQL_Running: No
解决方法:
停止同步:mysql>stop slave;
跳过一个错误事件:mysql>set GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
开启同步:mysql>start slave;
使用命令:mysql>show slave status\G,查看Slave_SQL_Running的值,
如果不为Yes,则重复上述3步,直到为Yes
背景
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。
对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,
将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,
能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
Sharding-JDBC介绍
Sharding-JDBC定位为轻量级Java框架,在Java的]DBC层提供的额外服务。
它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的IDBC驱动,
完全兼容IDBC和各种ORM框架。使用sharding-JDB可以在程序中轻松的实现数据库读写分离
1、适用于任何基于]DBC的ORM框架,如: JPA,Hibernate,Mybatis,Spring JDBCTemplate或直接使用DBC。
2、支持任何第三方的数据库连接池,如: DBCP,C3PO,BoneCp,Druid,HikariCP等。
3、支持任意实现IDBC规范的数据库。目前支持MVSOL.Oracle,SOLServer,PostareOL以及任何遵循SOL92标准的数据库。
入门案例
使用Sharding-JDBC实现读写分离步骤
1、导入maven坐标
2、在配置文件中配置读写分离规则
3、在配置文件中配置允许bean定义覆盖配置项
导入maven依赖坐标
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
4.0.0-RC1
#设置配置文件
server:
port: 8080
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.147.100:3306/demo?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.147.101:3306/demo?characterEncoding=utf-8
username: root
password: root
masterslave:
# 读写分离配置
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
main:
allow-bean-definition-overriding: true #允许bean定义覆盖配置项
数据库环境准备
直接使用前面在虚拟机中搭建的主从复制的数据库环境即可。
在主库中创建瑞吉外卖项目的业务数据库reggie并导入相关表结构和数据。
代码改造
在项目中加入sharding-JDBC实现读写分离步骤
1、导入maven坐标
2、在配置文件中配置读写分离规则
3、在配置文件中配置允许bean定义覆盖配置项
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
4.0.0-RC1
server:
port: 8080
spring:
application:
#应用的名称,可选
name: reggie_take_out
# datasource:
# druid:
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
# username: root
# password: 123456
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.147.100:3306/reggie?characterEncoding=utf-8
username: root
password: root
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.147.101:3306/reggie?characterEncoding=utf-8
username: root
password: root
masterslave:
# 读写分离配置
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
main:
allow-bean-definition-overriding: true #允许bean定义覆盖配置项
redis:
host: localhost
port: 6379
#password:
database: 0
cache:
redis:
time-to-live: 1800000 #设置缓存有效期
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID #主键生成策略
reggie:
path: D:\img\
Nginx介绍
Nginx是一款轻量级的we 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。
其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,
中国大陆使用nginx的网站有: 百度、京东新浪、网易、腾讯、淘宝等。
Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文: PaM6nep)开发的,
第一个公开版本0.1.0发布于2004年1月4日。
官网: https://nginx.org/
Nginx下载与安装
安装过程:
1、安装依赖包 yum -y install gcc pcre-devel zlib-devel openssl openssl-devel
2、下载Nginx安装包wget https://nginx.org/download/nginx-1.24.0.tar.gz
3、解压 tar -zxvf nginx-1.24.0.tar.gz
4、cd nginx-1.24.0
5、mkdir -p /usr/local/nginx
6、./configure --prefix=/usr/local/nginx 安装到指定目录
7、make && make install make先编译后执行安装
Nginx目录结构
重点目录/文件:
conf/nginx.conf nginx配置文件
html 存放静态文件 (htm1、css、Js等)
1ogs 日志目录,存放日志文件
sbin/nginx 二进制文件,用于启动、停止Nginx服务
查看版本
./nginx -v
检查配置文件正确性
在启动Nginx服务之前,可以先检查一下conf/nginx.conf文件配置的是否有错误,命令如下
./nginx -t
启动和停止
启动Nginx服务使用如下命令:/usr/local/nginx/sbin/nginx
停止Nginx服务使用如下命令:/usr/local/nginx/sbin/nginx -s stop
启动完成后可以查看Nginx进程:ps -ef | grep nginx
重新加载配置文件
当修改Nginx配置文件后,需要重新加载才能生效,可以使用下面命令重新加载配置文件
/usr/local/nginx/sbin/nginx -s reload
把Nginx的二进制文件路径配置到系统环境变量里,不管那个路径下都可以去执行nginx命令
vim /etc/profile
修改PATH=/usr/local/nginx/sbin:$JAVA_HOME/bin:$PATH
source /etc/profile 立即生效
整体结构介绍
全局块
Events块
Http块
部署静态资源
Nginx可以作为静态web服务器来部署静态资源。
静态资源指在服务端真实存在并且能够直接展示的一些文件,
比如常见的html页面、css文件、js文件、图片、视频等资源。
相对于Tomcat,Nginx处理静态资源的能力更加高效,所以在生产环境下,
一般都会将静态资源部署到Nginx中。将静态资源部署到Nginx非常简单,
只需要将文件复制到Nginx安装目录下的html目录中即可。
反向代理
正向代理
是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,
客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
正向代理的典型用途是为在防火墙内的局域网客户端提供访问internet的途径。
正向代理一般是在客户端设置代理服务器,通过代理服务器转发请求,最终访问到目标服务器
反向代理
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,
即用户直接访问反向代理服务器就可以获得目标服务器的资源,反向代理服务器负责将请求转发给目标服务器。
用户不需要知道目标服务器的地址,也无须在用户端作任何设定。
配置反向代理
反向代理测试
在101主机里导入一个SpringBoot jar包运行测试
在100主机里配置反向代理
修改配置文件 vim /usr/local/nginx/conf/nginx.conf
server {
listen 82;
server_name localhost;
location /{
proxy_pass http://192.168.138.101:8080; #反向代理配置,将请求转发到指定服务
}
}
重新加载配置文件 nginx -s reload
负载均衡
早期的网站流量和业务功能都比较简单,单台服务器就可以满足基本需求,但是随着互联网的发展,
业务流量越来越大并且业务逻辑也越来越复杂,单台服务器的性能及单点故障问题就凸显出来了,
因此需要多台服务器组成应用集群进行性能的水平扩展以及避免单点故障出现。
应用集群:将同一应用部署到多台机器上,组成应用集群,接收负载均衡器分发的请求,进行业务处理并返回响应数据
负载均衡器:将用户请求根据对应的负载均衡算法分发到应用集群中的一台服务器进行处理
配置负载均衡:下面采用的是轮询
upstream targetserver{ #upstream指令可以定义一组服务器
server 192.168.138.101:8080;
server 192.168.138.101:8081;
}
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://targetserver;
}
}
负载均衡策略:
名称 | 说明 |
轮询 | 默认方式 |
weight | 权重方式 |
ip_hash | 依据ip分配方式 |
least_conn | 依据最少连接方式 |
url_hash | 依据url分配方式 |
fair | 依据响应时间方式 |
开发人员同时负责前端和后端代码开发,分工不明确
开发效率低
前后端代码混合在一个工程中,不便于管理对开发人员要求高,人员招聘困难
介绍
前后端分离开发,就是在项目开发过程中,对于前端代码的开发由专门的前端开发人员负责,
后端代码则由后端开发人员负责,这样可以做到分工明确、各司其职,提高开发效率,
前后端代码并行开发,可以加快项目开发进度。目前,前后端分离开发方式已经被越来越多的公司所采用,
成为当前项目开发的主流开发方式。
前后端分离开发后,从工程结构上也会发生变化,即前后端代码不再混合在同一个maven工程中,
而是分为前端工程和后端工程。
开发流程
前后端分离开发后,面临一个问题,就是前端开发人员和后端开发人员如何进行配合来共同开发一个项目?
可以按照如下流程进行:
接口(API接口)就是一个htp的请求地址,主要就是去定义:请求路径、请求方式、请求参数、响应数据等内容
前端技术栈
开发工具
Visual Studio
Codehbuilder
技术框架
nodejs
VUE
ElementUlmock
webpack
介绍
YApi是高效、易用、功能强大的 api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。
可以帮助开发者轻松创建、发布、维护 AP1,YApi 还为用户提供了优秀的交互体验,
开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具可读性、可维护性,让团队协作更合理。
源码地址: https://github.com/YMFE/yapi
要使用YApi,需要自己进行部署
Swagger
介绍
使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,
再通过Swagger衍生出来的一系列项目和工具,就可以做到生成各种格式的接口文档,
以及在线接口调试页面等等。官网: https://swagger.io/
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案
使用
操作步骤
1、导入knife4j的maven坐标
2、导入knife4j相关配置类
3、设置静态资源,否则接口文档页面无法访问
4、在LoginCheckFilter中设置不需要处理的请求路径
com.github.xiaoymin
knife4j-spring-boot-starter
3.0.2
导入knife4j相关配置类(WebMvcConfig)
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
*
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
/**
* 扩展MVC框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转换为JSON
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到MVC框架的转换器集合中
converters.add(0,messageConverter);
}
@Bean
public Docket createRestApi() {
// 文档类型
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.green.reggie.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("瑞吉外卖")
.version("1.0")
.description("瑞吉外卖接口文档")
.build();
}
}
设置静态资源映射 (WebMvcConfig类中的addResourceHandlers方法),否则接口文档页面无法访问
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
在LoginCheckFilter中设置不需要处理的请求路径
启动项目访问:http://localhost:8080/doc.html 查看接口文档
常用注解
注解 | 说明 |
@Api | 用在请求的类上,例如Controller,表示对类的说明 |
@ApiModel | 用在类上,通常是实体类,表示一个返回响应数据的信息 |
@ApiModelProperty | 用在属性上,描述响应类的属性 |
@ApiOperation | 用在请求的方法上,说明方法的用途、作用 |
@ApilmplicitParams | 用在请求的方法上,表示一组参数说明 |
@ApilmplicitParam | 用在@ApilmplicitParams注解中,指定一个请求参数的各个方面 |
部署架构
部署环境说明
服务器:
192.168.138.100(服务器A)
Nginx: 部署前端项目、配置反向代理
Mysql:主从复制结构中的主库
192.168.138.101(服务器B)
jdk: 运行Java项目
git:版本控制工具
maven: 项目构建工具
jar:Spring Boot项目打成jar包基于内置Tomcat运行
Mysql:主从复制结构中的从库
172.17.2.94(服务器C)
Redis: 缓存中间件
部署前端项目
第一步:在服务器A中安装Nginx,将项目的静态资源目录上传到html目录下
第二步:修改Nginx配置文件nginx.conf
修改配置文件 vim /usr/local/nginx/conf/nginx.conf
反向代理的url:http://192.168.147.100/api/employee/login
要转发到:http://192.168.147.101/employee/login
部署后端项目
第一步:在服务器B中安装jdk、git、maven、MySQL,使用git clone命令将git 远程仓库代码克隆下来
[root@localhost javaapp]# git clone https://gitee.com/chi-baizhou/reggie_take_out.git
[root@localhost javaapp]# ll
drwxr-xr-x. 4 root root 62 6月 15 20:30 reggie_take_out
第二步:将reggieStart.sh文件上传到服务器B,通过chmod命令设置执行权限
[root@localhost javaapp]# chmod 777 reggieStart.sh
[root@localhost javaapp]# ll
-rwxrwxrwx. 1 root root 859 4月 4 2022 reggieStart.sh
drwxr-xr-x. 4 root root 62 6月 15 20:30 reggie_take_out
第三步:执行reggieStart.sh脚本,自动部署项目
[root@localhost javaapp]# ./reggieStart.sh
访问http://192.168.138.100 登录
图片没展示出来是因为找不到图片的路径
application.yml修改图片的路径path: /usr/local/img/
推送到远程仓库
重新执行脚本文件
[root@localhost javaapp]# ./reggieStart.sh