DEV Community

Woody
Woody

Posted on

Take Out优化

一、缓存优化

问题说明

用户数量多,系统访问量大

频繁访问数据库,系统性能下降,用户体验差

1. 环境搭建

1.1 Maven 坐标

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 
Enter fullscreen mode Exit fullscreen mode

1.2 配置文件

spring: redis: host: 192.168.81.128 port: 6379 password: root database: 0 
Enter fullscreen mode Exit fullscreen mode

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; } 
Enter fullscreen mode Exit fullscreen mode

2. 缓存短信验证码

2.1 实现思路

前面已经实现了移动端手机验证登录,随机生成的验证码我们是保存在 HttpSession 中的。现在需要改造为将验证码缓存在 Redis 中。

2.2 代码改造

具体的实现思路如下:

1、在服务端 UserController 中注入 RedisTemplate 对象,用于操作 Redis

@Autowired private RedisTemplate redisTemplate 
Enter fullscreen mode Exit fullscreen mode

2、在服务端 UserController 的 sendMsg 方法中,将随机生成的验证码缓存到 Redis 中,并设置有效期为 5 分钟

//将生成的验证码缓存到redis中,并且设置有效期为5分钟 redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES); 
Enter fullscreen mode Exit fullscreen mode

3、在服务端 UserController 的 login 方法中,从 Redis 中获取缓存验证码,如果登录成功则删除 Redis 中的验证码

//从Redis中获取缓存的验证码 Object codeInSession = redisTemplate.opsForValue().get(phone); 
Enter fullscreen mode Exit fullscreen mode
//如果用户登录成功,删除Redis中的缓存验证码 redisTemplate.delete(phone); 
Enter fullscreen mode Exit fullscreen mode

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); 
Enter fullscreen mode Exit fullscreen mode

2、改造 DishController 的 save 方法和 update 方法,加入缓存清理的逻辑

//清理所有的菜品缓存数据 //Set keys = redisTemplate.keys("dish_*"); //redisTemplate.delete(keys); //清理某个分类下面的菜品缓存数据 String key = "dish" + dishDto.getCategoryId() + "_1"; redisTemplate.delete(key); 
Enter fullscreen mode Exit fullscreen mode

注意:在使用缓存的过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中发生变化,需要及时清理缓存数据。

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> 
Enter fullscreen mode Exit fullscreen mode

2、配置 application.yml

spring: redis: host: 192.168.81.128 port: 6379 password: root database: 0 cache: redis: time-to-live: 1800000 # 设置缓存有效期 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

2、在 appilcation.yml 中配置缓存数据的过期时间

spring: cache: redis: time-to-live: 180000 
Enter fullscreen mode Exit fullscreen mode

3、在启动类上加入 @EnableCaching 注解,开启缓存注解功能

@Slf4j @SpringBootApplication @ServletComponentScan @EnableTransactionManagement @EnableCaching //开启缓存注解功能 public class ReggieApplication { public static void main(String[] args) { SpringApplication.run(ReggieApplication.class,args); log.info("项目启动成功..."); } } 
Enter fullscreen mode Exit fullscreen mode

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); } 
Enter fullscreen mode Exit fullscreen mode

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("新增套餐成功"); } 
Enter fullscreen mode Exit fullscreen mode
/** * 删除套餐 * * @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("套餐数据删除成功"); } 
Enter fullscreen mode Exit fullscreen mode

二、读写分离

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 
Enter fullscreen mode Exit fullscreen mode

第二步:重启 MySql 服务

systemctl restart mysqld 
Enter fullscreen mode Exit fullscreen mode

第三步:登录 MySql 数据库,执行下面命令:

  1. 创建用户
create user 'xiaoming'@'%' identified by 'Root@123456'; 
Enter fullscreen mode Exit fullscreen mode
注:这里表示创建一个不限制ip登录的用户 xiaoming 该用户的密码是 Root@123456 %代表不限制ip登录 
Enter fullscreen mode Exit fullscreen mode
  1. 给用户授权
GRANT REPLICATION SLAVE ON *.* TO xiaoming; 
Enter fullscreen mode Exit fullscreen mode
  1. 刷新权限,每一次权限更改后都刷新一下
flush privileges; 
Enter fullscreen mode Exit fullscreen mode

第四步:登录 MySql 数据库,执行下面 SQL,记录下结果中 File 和 Position 的值

show master status; 
Enter fullscreen mode Exit fullscreen mode

注:上面SQL的作用是查看Master的状态,执行完此SQL后不要再执行任何操作

1.2.2 配置 - 从库 Slave

第一步:修改 MySQL 数据库的配置文件 / etc/my.cnf

[mysqld] server-id=101 #[必须]服务器唯一ID 
Enter fullscreen mode Exit fullscreen mode

第二步:重启 Mysql 服务

systemctl restart mysqld 
Enter fullscreen mode Exit fullscreen mode

第三步:登录 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; 
Enter fullscreen mode Exit fullscreen mode
start slave; 
Enter fullscreen mode Exit fullscreen mode

如果出现错误:



分别执行:

stop slave; 
Enter fullscreen mode Exit fullscreen mode
reset slave; 
Enter fullscreen mode Exit fullscreen mode

第四步:查看从数据库的状态:

show slave status; 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

3、在配置文件中配置允许 bean 定义覆盖配置顶

spring: main: allow-bean-definition-overriding: true 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

3、在配置文件中配置允许 bean 定义覆盖配置顶

spring: main: allow-bean-definition-overriding: true 
Enter fullscreen mode Exit fullscreen mode

三、前后端分离开发

开发人员同时负责前端和后端代码开发,分工不明确

开发效率低

前后端代码混合在一个工程中,不便于管理

对开发人员要求高,人员招聘困难

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> 
Enter fullscreen mode Exit fullscreen mode

3.2 使用方式

操作步骤:

1、导入 Knife4j 的 maven 坐标

<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> 
Enter fullscreen mode Exit fullscreen mode

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(); } } 
Enter fullscreen mode Exit fullscreen mode

3、设置静态资源,否则接口文档页面无法访问

设置静态资源映射(WebMvcConfig 类中的 addResourceHandlers 方法),否则接口文档页面无法访问

registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); 
Enter fullscreen mode Exit fullscreen mode

4、在 LoginCheckFilter 中设置不需要处理的请求路径

"/doc.html", "/webjars/**", "/swagger-resources", "/v2/api-docs" 
Enter fullscreen mode Exit fullscreen mode

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; } } 
Enter fullscreen mode Exit fullscreen mode

4.4 部署后端项目

第一步:在服务器 B 中安装 JDK,Git,MySql,使用 git clone 命令将 git 远程仓库的代码克隆下来

第二步:将自动化脚本上传到服务器 B,通过 chmod 命令设置执行权限

第三步:执行脚本,自动化部署项目

Top comments (0)