优化
- 一、缓存优化
-
- 1.环境搭建
-
- 1.1 Maven坐标
- 1.2 配置文件
- 1.3 配置类
- 2.缓存短信验证码
-
- 2.1 实现思路
- 2.2 代码改造
- 3.缓存菜品数据
-
- 3.1 实现思路
- 3.2 代码改造
- 4.Spring Cache
-
- 4.1 Spring Cache介绍
- 4.2 Spring Cache常用注解
- 4.3 Spring Cache使用方式
- 5.缓存套餐数据
-
- 5.1 实现思路
- 5.2 代码改造
- 二、读写分离
-
- 1.Mysql主从复制
-
- 1.1 介绍
- 1.2 配置
-
- 1.2.1 配置-主库Master
- 1.2.2 配置-从库Slave
- 2.读写分离案例
-
- 2.1 背景
- 2.2 Sharding-JDBC介绍
- 2.3 入门案例
- 3.项目实现读写分离
-
- 3.1 数据库环境准备(主从复制)
- 3.2 代码改造
- 三、前后端分离开发
-
- 1.前后端分离开发
-
- 1.1 介绍
- 1.2 开发流程
- 1.3 前端技术栈
- 2.Yapi
-
- 2.1 介绍
- 2.2 使用
- 3.Swagger
-
- 3.1 介绍
- 3.2 使用方式
- 3.3 常用注解
- 4.项目部署
-
- 4.1 部署架构
- 4.2 部署环境说明
- 4.3 部署前端项目
- 4.4 部署后端项目
黑马平台瑞吉外卖学习地址
一、缓存优化
问题说明
用户数量多,系统访问量大
频繁访问数据库,系统性能下降,用户体验差
1.环境搭建
1.1 Maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.2 配置文件
spring:
redis:
host: 192.168.81.128
port: 6379
password: root
database: 0
1.3 配置类
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//默认key序列化器为:JdkSerializationRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
2.缓存短信验证码
2.1 实现思路
前面已经实现了移动端手机验证登录,随机生成的验证码我们是保存在HttpSession中的。现在需要改造为将验证码缓存在Redis中。
2.2 代码改造
具体的实现思路如下:
1、在服务端UserController中注入RedisTemplate对象,用于操作Redis
@Autowired
private RedisTemplate redisTemplate
2、在服务端UserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分钟
//将生成的验证码缓存到redis中,并且设置有效期为5分钟
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
3、在服务端UserController的login方法中,从Redis中获取缓存验证码,如果登录成功则删除Redis中的验证码
//从Redis中获取缓存的验证码
Object codeInSession = redisTemplate.opsForValue().get(phone);
//如果用户登录成功,删除Redis中的缓存验证码
redisTemplate.delete(phone);
3.缓存菜品数据
3.1 实现思路
前面已经实现了移动端菜品查看功能,对应的服务端方法为DishController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长,现在需要对此方法进行缓存优化,提高系统的性能。
3.2 代码改造
实现思路如下:
1、改造DishController的list方法,先从Redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询到的菜品数据让如redis。
List<DishDto> dishDtoList = null;
//动态构造key
String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus(); //dish_1524731277968793602_1
//先从redis中获取数据;
dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
if (dishDtoList != null) {
//如果存在,直接返回,无需查询数据库
return R.success(dishDtoList);
}
//如果不存在,需要查询数据库,将查询到的菜品数据缓存到redis
//查询代码.....程序往下走查询完毕后 存入缓存
//如果不存在,需要查询数据库,将查询到的菜品数据缓存到Redis
redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
2、改造DishController的save方法和update方法,加入缓存清理的逻辑
//清理所有的菜品缓存数据
//Set keys = redisTemplate.keys("dish_*");
//redisTemplate.delete(keys);
//清理某个分类下面的菜品缓存数据
String key = "dish" + dishDto.getCategoryId() + "_1";
redisTemplate.delete(key);
注意:在使用缓存的过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中发生变化,需要及时清理缓存数据。
4.Spring Cache
4.1 Spring Cache介绍
Spring Cache是一个框架,实现了基本注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象,底层可以切换不同的cache实现,具体就是通过CacheManager接口来统一不同的缓存技术。
CacheManager是Spring提供的各种缓存技术抽象接口。
针对不同的缓存技术需要实现不同的CacheManager:
CacheManger | 描述 |
---|---|
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Googke的GuavaCache作为缓存技术 |
RedisCacheManager | 使用Rdis作为缓存技术 |
4.2 Spring Cache常用注解
注解 | 说明 |
---|---|
@EnableCaching | 开启缓存注解功能 |
@Cacheable | 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或者多条数据从缓存中删除 |
在spring boot项目中,使用缓存技术只需要在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存技术支持即可。
例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
4.3 Spring Cache使用方式
在Spring Boot项目中使用Spring Cache的操作步骤(使用redis缓存技术):
1、导入maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、配置application.yml
spring:
redis:
host: 192.168.81.128
port: 6379
password: root
database: 0
cache:
redis:
time-to-live: 1800000 # 设置缓存有效期
5.缓存套餐数据
5.1 实现思路
前面已经实现了移动端套餐查看功能,对应的服务端方法未SetmealController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作。在高并发的情况下,频繁查询数据库会导致性能下降,服务端响应时间增长。现在需要对此方法进行缓存优化。提高系统的性能。
5.2 代码改造
实现思路如下:
1、导入Spring Cache和Redis相关的maven坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、在appilcation.yml中配置缓存数据的过期时间
spring:
cache:
redis:
time-to-live: 180000
3、在启动类上加入@EnableCaching注解,开启缓存注解功能
@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableCaching //开启缓存注解功能
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功...");
}
}
4、在SetmealController的list方法上加入@Cacheable注解
@GetMapping("/list")
@Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_' + setmeal.status")
public R<List<Setmeal>> list(Setmeal setmeal) {
List<Setmeal> list = null;
//动态构造key
String key = "setmeal_" + setmeal.getCategoryId() + "_" + setmeal.getStatus(); //setmeal_1524731277968793602_1
//先从redis中获取数据;
list = (List<Setmeal>) redisTemplate.opsForValue().get(key);
if (list != null) {
//如果存在,直接返回,无需查询数据库
return R.success(list);
}
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId());
queryWrapper.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus());
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
list = setmealService.list(queryWrapper);
//如果不存在,需要查询数据库,将查询到的菜品数据缓存到Redis
redisTemplate.opsForValue().set(key, list, 60, TimeUnit.MINUTES);
return R.success(list);
}
5、在SetmealController的save和delete方法上加入CacheEvict注解
/**
* 新增套餐
*
* @param setmealDto
* @return
*/
@PostMapping
@CacheEvict(value = "setmealCache", allEntries = true)
public R<String> save(@RequestBody SetmealDto setmealDto) {
log.info("套餐信息:{}", setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}
/**
* 删除套餐
*
* @param ids
* @return
*/
@DeleteMapping
@CacheEvict(value = "setmealCache", allEntries = true)
public R<String> delete(@RequestParam List<Long> ids) {
log.info("ids:{}", ids);
setmealService.removeWithDish(ids);
return R.success("套餐数据删除成功");
}
二、读写分离
1.Mysql主从复制
1.1 介绍
Mysql主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台Mysql数据库(slave,即从库)从另一台Mysql数据库(master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致,Mysql主从复制是Mysql数据库自带的功能,无需借助第三方工具。
MySQL复制过程分成三步:
- master将改变记录到二进制日志(binary log)
- slave将master的binary log拷贝到它的中继日志(relay log)
- slave重做中继日志中的事件,将改变应用到自己的数据库中
1.2 配置
提前准备好两台服务器,分别安装MySQL并启动服务成功
- 主库Master
- 从库Slave
1.2.1 配置-主库Master
第一步:修改Mysql数据库的配置文件 /etc/my.cnf
[mysqld]
log-bin=mysql-bin #[必须]启用二进制日志
server-id=100 #[必须]服务器唯一ID
第二步:重启MySql服务
systemctl restart mysqld
第三步:登录MySql数据库,执行下面命令:
- 创建用户
create user 'xiaoming'@'%' identified by 'Root@123456';
注:这里表示创建一个不限制ip登录的用户 xiaoming
该用户的密码是 Root@123456
%代表不限制ip登录
- 给用户授权
GRANT REPLICATION SLAVE ON *.* TO xiaoming;
- 刷新权限,每一次权限更改后都刷新一下
flush privileges;
第四步:登录MySql数据库,执行下面SQL,记录下结果中File和Position的值
show master status;
注:上面SQL的作用是查看Master的状态,执行完此SQL后不要再执行任何操作
1.2.2 配置-从库Slave
第一步:修改MySQL数据库的配置文件/etc/my.cnf
[mysqld]
server-id=101 #[必须]服务器唯一ID
第二步:重启Mysql服务
systemctl restart mysqld
第三步:登录sql数据库,执行以下命令:
change master to master_host='180.76.54.3',master_user='xiaoming',master_password='Root@123456',master_port=3306, master_log_file='mysql-bin.000004',master_log_pos=85320;
start slave;
如果出现错误:
分别执行:
stop slave;
reset slave;
第四步:查看从数据库的状态:
show slave status;
2.读写分离案例
2.1 背景
面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库 和 从库 ,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。
2.2 Sharding-JDBC介绍
Sharding-JDBC定位为轻量级的Java框架,在Java的JDBC层提供额外服务。它使用客户端直连数据库,以jar包的形式提供服务,无需额外部署和依赖,可理解为增强的JDBC驱动,完全兼容JDBC和各种ORM框架。
使用Sharding-JDBC可以在程序中轻松实现数据库读写分离。
- 适用于任何基于JDBC的ORM框架,如:JPA,Hibernate,Mybatis,Spring JDBC Template或者直接使用JDBC。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
2.3 入门案例
使用Sharding-JDBC实现读写分离步骤:
1、导入maven坐标
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
2、在配置文件中配置读写分离规则
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://180.76.54.3:3306/rw?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.81.128:3306/rw?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
3、在配置文件中配置允许bean定义覆盖配置顶
spring:
main:
allow-bean-definition-overriding: true
3.项目实现读写分离
3.1 数据库环境准备(主从复制)
使用以上搭建的环境,在主库中创建数据库reggie,运行SQL
3.2 代码改造
在项目中导入Sharding-JDBC实现读写分离的步骤:
1、导入Maven坐标
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
2、在配置文件中配置读写分离的规则
spring:
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://180.76.54.3: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.81.128: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
main:
allow-bean-definition-overriding: true
3、在配置文件中配置允许bean定义覆盖配置顶
spring:
main:
allow-bean-definition-overriding: true
三、前后端分离开发
开发人员同时负责前端和后端代码开发,分工不明确
开发效率低
前后端代码混合在一个工程中,不便于管理
对开发人员要求高,人员招聘困难
1.前后端分离开发
1.1 介绍
前后端分离开发 ,就是在项目开发过程中,对于前端代码的开发由专门的前端开发人员 负责,后端代码则由后端开发人员 负责,这样可以做到分工明确,各司其职,提高开发效率,前后端代码并行开发,可以加快项目的开发速度。目前,前后端分离开发方式已经被越来越多的公司采用了,成为当前项目开发的主流开发方式。
前后端分离开发后,从工程结构上也会发生变化,即前后端代码不再混合在同一个maven工程中,而是分为前端工程和后端工程 。
1.2 开发流程
接口(API接口) 就是一个http的请求地址,主要就是去定义:请求路径、请求方式、请求参数、响应参数等内容。
1.3 前端技术栈
开发工具:
- Visual Studio Code
- hbuilder
技术框架:
- node.js
- VUE
- ElementUI
- mock
- Webpacl
2.Yapi
2.1 介绍
YApi是高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护API,YApi还为用户提供了优秀的交互体验,开发人员只需要利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理。
YApi让接口开发更简单高效,让接口的管理更具有可读性、可维护性,让团队协作更合理。
源码地址:https://github.com/YMFE/yapi
要使用YApi,需要自己进行部署。
2.2 使用
使用YApi,可以执行下面操作:
- 添加项目
- 添加分类
- 添加接口
- 编辑接口
- 查看接口
3.Swagger
3.1 介绍
使用Swagger你只需要按照它的规范去定义接口及接口相关的信息,再通过Swagger衍生出来的一系列项目和工具,就可以做成各种格式的接口文档,以及在线接口调试页面等。
官网:https://swagger.io/
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方法。
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
3.2 使用方式
操作步骤:
1、导入Knife4j的maven坐标
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
2、导入knife4j相关配置(WebMvcConfig)
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Bean
public Docket createRestApi() {
//文档类型
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.itheima.reggie.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("瑞吉外卖")
.version("1.0")
.description("瑞吉外卖接口文档")
.build();
}
}
3、设置静态资源,否则接口文档页面无法访问
设置静态资源映射(WebMvcConfig类中的addResourceHandlers方法),否则接口文档页面无法访问
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
4、在LoginCheckFilter中设置不需要处理的请求路径
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"
3.3 常用注解
注解 | 说明 |
---|---|
@Api | 用在请求的类上,例如Controller,表示对类的说明 |
@ApiModel | 用在类上,通常是个实体类,表示一个返回响应数据的信息 |
@ApiModelProperty | 用在属性上,描述响应类的属性 |
@ApiOperation | 用在请求的方法上,说明方法的用途、作用 |
@ApilmplicitParams | 用在请求的方法上,表示一组参数说明 |
@ApilmplicitParam | 用在@ApilmplicitParams注解中,指定一个请求参数的各个方面 |
4.项目部署
4.1 部署架构
4.2 部署环境说明
服务器:
- 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:缓存中间件
4.3 部署前端项目
第一步:在服务器A中按照Nginx,将前端项目打包目录上传到Nginx的html目录下
第二步:修改Nginx配置文件nginx.conf
server {
listen 80;
server_name localhost;
location / {
root html/dist;
index index.html;
}
#反向代理配置
location ^~ /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://192.168.138.101:8080;
}
}
4.4 部署后端项目
第一步:在服务器B中安装JDK,Git,MySql,使用git clone命令将git远程仓库的代码克隆下来
第二步:将自动化脚本上传到服务器B,通过chmod命令设置执行权限
第三步:执行脚本,自动化部署项目