近期临时笔记

第一章 概述

解决秒杀高并发问题的方案,大并发的瓶颈在数据库

  • 缓存系统
  • 异步化
  • 横向扩展

技术点

image-20201202191033606
  • Model的作用

    在控制器中,数据会存放到Model对象中,当需要生成HTML的时候,模板引擎会根据名字来定位数据。

  • 热加载主要是在idea的setting的compiler中勾选 build project automatically,且只能在debug模式下可用。也可以使用jrebel

  • 泛型的定义和使用?

  • Result的封装

秒杀的业务逻辑

首先完成分布式session,之后用户浏览商品列表,查看商品详情,点击秒杀按钮,如果成功,则生成订单。

业务逻辑完成之后,进行系统压测,进行高并发请求。

高并发下出现问题,需要进行缓存优化,缓存是个大系统,

网关nginx缓存、应用服务器缓存、页面级缓存、URL缓存、对象缓存,缓存系统应对打并发的方法。缓存要解决数据不一致问题。

秒杀的并发请求太大,如果请求都透传给DB,DB抗不住请求,使用消息队列设计异步请求。

使用nginx进行横向扩展解决大并发。先优化才能扩展。否则请求还给数据库,瓶颈仍然在数据库。

业务系统完成安全问题。

结果封装

  • code和msg是一一对应的,成对出现
  • 如果不允许修改对象的属性值,则去掉属性值的setter方法
  • 如果不允许外部创建对象,则将构造方法私有化

事务

在controller的方法上面添加transaction,可保证对数据库的操作是事务的,它能保证方法内多个数据库操作要么同时成功、要么同时失败。

编程知识

  • 一般springboot的启动类MiaoshaApplication放到最外层,是为了方便进行controllerScan。
  • 后台controller向前台一般会输出两类,包括1 rest api json的基本输出 2 页面

序列化

序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。注意,序列化不仅仅只是指可传输,可存储也可称作序列化。

因此,把不可存储的对象转化为可存储在数据库中的json串也是一种序列化。

其他

  • 对系统模块的拆分:登录模块、鉴权模块、安全措施、缓存系统、Result结果封装、日志系统
  • 应对大并发思路: 1、缓存 2、异步 3 横向扩展
  • 查看springboot的官方文档时,搜索srpintboot进入主页,然后点击learn,选择一个分支,点击Reference Doc进入参考文档页面,在里面可选多页面观看或单页面观看或PDF。在springboot官网上的文档是英文文档,可以搜索对应的中文文档。在参考指南的附录部门,会给出常用应用程序的属性配置选项,可以在application.properties文件内,application.yml文件内或命令行开关中指定各种属性。
  • snapshot 快照 在maven中version属性会使用 2.0.2-SNAPSHOT表示是快照版本
  • 一般springboot集成其他插件分为两大步:1)添加maven依赖 2)添加配置

redis相关

  • 安装路径
    /usr/local/redisFormiaosha

  • 修改的配置

    • daem yes
    • requirepass 123456 //访问密码 123456
      这导致每次使用redis-cli之后,需要先输入auth 123456授权
    • bind 0.0.0.0 //任何地址可以访问
  • 把redis做成系统服务

    • 制作
    • 启动 在redis目录下 ./redis-serve ./redis.config
    • 停止 使用 service redis_6379 stop 因有密码 无法停止 https://blog.csdn.net/aoshilang2249/article/details/88555828

linux相关

  • ps -ef | grep redis
  • su 切换用户
  • chkconfig --list | grep redis 查看系统服务是否有redis
  • 带密码的系统服务启动问题

集成Redis

一般springboot集成其他插件分为两大步:1)添加maven依赖 2)添加配置。3)获取配置 4)生成实例 5)使用。

Redis的常用三个java客户端:Jedis,Redisson,Lettuce。Jedis 和 Lettuce 是 Java 操作 Redis 的客户端。在 Spring Boot 1.x 版本默认使用的是 Jedis ,而在 Spring Boot 2.x 版本默认使用的就是Lettuce。

  1. 添加依赖
    在pom文件中,添加Jedis依赖

  2. 在application中添加配置信息

    spring.redis.host=175.175.16.239
    # Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=123456
    # 链接超时时间 单位 ms(毫秒)
    spring.redis.timeout=3000
    #redispool最大活动对象数
    spring.redis.jedis.pool.max-active=100
    #redispool当池内没有返回对象时,最大等待时间
    #此处按照秒来配置  注意在redispoolconfig对象的参数是毫秒
    spring.redis.jedis.pool.max-wait=10
    #redispool最大能够保持idel状态的对象数
    spring.redis.jedis.pool.max-idle=100
    
  3. 创建configuration类,获取配置信息

      @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private int port;
        @Value("${spring.redis.timeout}")
        private int timeout;
        @Value("${spring.redis.password}")
        private String password;
        @Value("${spring.redis.jedis.pool.max-active}")
        private int maxActive;
        @Value("${spring.redis.jedis.pool.max-idle}")
        private int maxIdle;
        @Value("${spring.redis.jedis.pool.max-wait}")
        private int maxWait;
    
  4. 获取jedispool,然后使用jedispool获取jedis实例

    jedis = jedisPool.getResource();
    
  5. 封装jedis提供的方法。封装主要针对使用redis的封装。

       public <T> T get(String key,Class<T> clazz){
            Jedis jedis = null;
            try{
                jedis = jedisPool.getResource();
                String s = jedis.get(key);
                T t = stringToBean(s,clazz);
                return  t;
            }finally {
                //关闭连接
                returnToPool(jedis);
            }
        }
    

添加Fastjson依赖

Fastjson作用可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符串转换为 Java 对象。对象不可以存储到数据库,而json可以存储到数据库,即Fastjson可以序列化。

序列化最快的是谷歌的protobuf,但是序列化之后不可读(二进制)。

Fastjson序列化之后内容可读(json格式),但是效率是protobuf的一半。

springboot注入bean

* redis的key都是string类型,而value有五种类型。
* 本项目假设所有value均为string类型,因此存在类型转化问题。
* 在存储时,需要将程序产生的java的类型(可能时 int、integer、bean等)转化为string类型,存储到redis中
* 在读取时,需要将从redis中取出的字符串转化为程序可使用的java数据类型;
* 以上的两个过程用到了fastjson的相关方法,具体使用参加get set方法。

Springboot注解

@Component

有一个接口,在这个接口的实现类里,需要用到@Autowired注解,一时大意,没有在实现类上加上@Component注解,导致了Spring报错,找不到这个类。

一旦使用关于Spring的注解出现在类里,例如我在实现类中用到了@Autowired注解,被注解的这个类是从Spring容器中取出来的,那调用的实现类也需要被Spring容器管理,加上@Component

@ConfigurationProperties(prefix = “spring.redis”)

@ConfigurationProperties 是spring-boot中特有的注解,可以使属性文件中的值和类中的属性对应起来。

使用场景如下:
假设application.properties 文件存在redis配置如下:

redis.config.maxTotal=5000
redis.config.maxIdle=10
redis.config.maxWaitMillis=5000

在定义的Redis配置类中

@Configuration
@ConfigurationProperties(prefix = "redis.config")
@Data
public class RedisConfiguration {

    private int maxTotal;
    private int maxIdle;
    private int maxWaitMillis;
    ...
} 

以上代码会将配置文件中的值“赋值”给类属性,当创建RedisConfiguration类时,使用getXX方法即可取到配置文件中的值。RedisConfiguration实际上是配置文件和调用者之间的桥梁。

这是读取配置一种方式,另外一种方式是使用value注解。

读取配置另外一种方式

使用用@Value注解。格式为@Value("${xx.xx.xx}")

@Value(“${spring.redis.maxTotal})  //括号里要写全路径
private int maxTotal;

这个直接

@bean

Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。

@Configuration

读取配置文件,内部可使用@value注解.

@Value读取具体配置值给属性。

@Configuration
public class RedisConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private int maxWait;
    ...
}

java.lang.SuppressWarnings

可以标注在类、字段、方法、参数、构造方法,以及局部变量上。
作用:告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。

@ControllerAdvice

@ControllerAdvice注解是Spring3.2中新增的注解,学名是Controller增强器,作用是给Controller控制器添加统一的操作或处理。

该注解作用对象为TYPE,包括类、接口和枚举等,在运行时有效,并且可以通过Spring扫描为bean组件。其可以包含由@ExceptionHandler、@InitBinder 和@ModelAttribute标注的方法,可以处理多个Controller类,这样所有控制器的异常可以在一个地方进行处理。

增强controler的意思是指在一般controller的基础上【拦截特定的请求】,拦截所有异常,功能更强大。

对于@ControllerAdvice,我们比较熟知的用法是结合@ExceptionHandler用于全局异常的处理。

@ExceptionHandler

用于拦截异常。当一个Controller中有方法加了@ExceptionHandler之后,这个Controller其他方法中没有捕获的异常就会以参数的形式传入加了@ExceptionHandler注解的那个方法中。

如果单使用@ExceptionHandler,只能在当前Controller中处理异常。但当配合@ControllerAdvice一起使用的时候,就可以摆脱那个限制,对所有controller层异常进行处理,即所谓的全局异常处理。此时可以在全局异常拦截器中给ExceptionHandler注解指定要拦截的异常类型。

   //ExceptionHandler配合ControllerAdvice,在全局拦截器中定义针对多个不同类的拦截器。
	@ControllerAdvice
    @ResponseBody
 	public class WebExceptionHandle {
     
         xxx

          //拦截器1  拦截HttpRequestMethodNotSupportedException中的异常
       	 @ExceptionHandler(HttpRequestMethodNotSupportedException.class)  
         public ServiceResponse handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
                xxx
           }

         //拦截器2  拦截HttpMediaTypeNotSupportedException中的异常
        @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
        public ServiceResponse handleHttpMediaTypeNotSupportedException(Exception e) {
                xxx
            }

         xxx
 }

@Restcontroller @Controller @Controller + @ResponseBody

@Controller

单据使用@Controller不加@ResponseBody,用于返回一个视图,该情况属于比较传统的Spring MVC应用,前后端不分离的场景。
Spring回去resources/templates目录下查找hello.html,并且携带参数

@Controller
public class HelloController {
    @GetMapping("/hello")
    public String greeting(@RequestParam(name = "name", required = false, defaultValue = "World") String name, Model model) {
        model.addAttribute("name", name);
        return "hello";
    }
}

@RestController

只返回对象,并且对象直接以JSON或XML的形式传入HTTP响应中,该情况属于RESTful Web服务,前后端分离场景。

@Controller
public class HelloController {
    @PostMapping("/hello")
    @ResponseBody
    public Person greeting(@RequestBody Person person) {
    	//返回json格式的Person对象
        return person;
    }

@Controller + @ResponseBody

返回JSON或XML形式数据;在Spring4之前开发RESTful Web服务,需要使用@Controller + @ResponseBody 注解使用;在Spring4之后,使用@RestController即可

//@Controller + @ResponseBody在功能层面上等于@RestController
@RestController
public class HelloController {
    @PostMapping("/hello")
    public Person greeting(@RequestBody Person person) {
    	//返回json格式的Person对象
        return person;
   }
}

设计模式

  • 模板模式 在设计redis的key阶段

FastJson

fastjson 是一个性能很好的 Java 语言实现的 JSON 解析器和生成器,来自阿里巴巴的工程师开发。其主要特点是:

① 快速:fastjson采用独创的算法,将parse的速度提升到极致,超过所有基于Java的json库,包括曾经号称最快的jackson;

② 强大:Fastjson完全支持https://json.org的标准(也是Google官方网站收录的参考实现之一);支持各种JDK类型;包括基本类型、JavaBean、Collection、Map、Enum、泛型等;

③零依赖:没有依赖其它任何类库除了JDK,能够直接运行在Java SE 5.0以上版本;支持Android;开源 (Apache 2.0)。

parseObject(String str)

该方法将str转化为相应的JSONObject对象,其中str是“键值对”形式的json字符串,转化为JSONObject对象之后就可以使用其内置的方法,进行各种处理。

JSON和JSONObject都提供该方法,且JSONObject是JSON的子类,当调用JSONObject.parseObject(result)时,会直接调用父类的parseObject(String text)。一个是用父类去调用父类自己的静态的parseObject(String text),一个是用子类去调用父类的静态parseObject(String text),两者调的是同一个方法。

遇到的问题

1、在idea中,使用jedis操作vmware虚拟机中的redis出现java.net.SocketException: Network is unreachable: connect异常。这个问题主要是本机和虚拟机无法互通。

最终原因有两个

  • RedisConfig获取host的注解写错,将以下value中的host写成port

  • @Value("${spring.redis.host}")
    private String host;
    
  • 主机和虚拟机无法互通,这需要关闭主机和虚拟机的防火墙,注意是两者的防火墙都需要关闭。

  • redis所在虚拟机的网络设置为桥接模式,并设置centos的ip和主机中虚拟机对应的网卡的ipv4都设置为自动获取。

日志使用

第一步 在类中定义一个静态日志对象,有不同的方式

第二步 在想打印的内容输出在idea控制台,方便调试

public class LoginController {
    
    //定义日志对象
    private static Logger log = LoggerFactory.getLogger(LoginController.class);

    @RequestMapping("/to_login")
    public String home(){
        return "login";
    }

    @RequestMapping("/do_login")
    @ResponseBody
    public Result<Boolean> dologin(LoginVo loginVo){
        //打印在idea的控制台
        log.info(loginVo.toString());
        return null;
    }
}

Vo

value object 值对象 / view object 表现层对象.定义在后端的实体,用于接收前端数据,

接收前端传递给后端的值:前端的ajax请求的data(是个json对象)中有两个属性,后端创建一个对象包含这些属性,然后就可以接收前端的值。

登录的对象 LoginVo接收登录页面传递的实体。

HttpServletRequest&HttpServletResponse

前端请求controller中的方法,任一方法中都可以使用HttpServletRequest,该类用于存储请求参数。

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,通过它可以获得客户的所有参数信息。

Web服务器收到一个http请求,会针对每个请求创建一个HttpServletRequest和HttpServletResponse对象,从客户端取数据找HttpServletRequest,向客户端发送数据就是HttpServletResponse,HttpServletResponse对象可以向客户端发送三种类型的数据:a.响应头b.状态码c.数据.

这两个参数一般用在controller的方法中,用于后端获取当前请求的参数值或后端向当前请求中设置参数值。

使用Springboot,我们很多时候直接使用@PathVariable、@RequestParam、@Param来获取较少数量的参数值,而HttpServletRequest可以获取全部。

Springboot获取request和response

异常

RuntimeException是程序错误,需要修改程序,自然不需要抛出异常。Error是jvm错误,较严重,程序员无可奈何,抛出异常没有意义。这两项是非受检异常(免检,编译器不检查 程序是否处理这两类异常)

对于受检异常,可认为是导致程序中断的一种可能情况,程序员需要手动处理这种情况。

Java异常报错的详细解析(一)

throw throws

泛型方法

其他知识

  • 抽象类
    抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。

    如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为abstract类。

    抽象类可以避免被new,只能通过子类创建。

  • 访问修饰符

    • private 和 public 两极分化,前者只能在类内部访问,后者在所有位置可访问
    • default 包访问权限,只要在一个包下都可以访问,也是默认访问修饰符
    • protected 包和子类可以访问,有特殊情况,略去。
  • 由普通的类来实现接口,必须将接口所有抽象方法重写
    由抽象类来实现接口,则不必重写接口的方法。可以全部不重写或只重写一部分方法。

需求分析

环境搭建

  • 整合redis,设计生成key的方式,用到了模板方法模式

登录模块

密码保护策略

进行两次MD5操作,双重保护密码的安全。

  1. 用户端在输入密码之后,使用MD5进行加密,避免密码以明文的方式在网络上传输。即`PSW1 = MD5(明文 + 固定Salt)

  2. 服务端在接收到用户密码之后,再次使用MD5加密,然后存储到数据库,避免密码数据库被盗。即`PSW2 = MD5(PSW1 + 随机Salt)。

  3. salt的使用方式。将salt定义为静态变量,使用其中的某个字符。

    //固定的salt,前端也使用这个salt
        private static final String salt = "1a2b3c4d";
    	md5("" + salt.charAt(1) + salt.charAt(2) +
                    psw + salt.charAt(4))
    

JSR303参数校验

JSR303 规范(Bean Validation 规范)提供了对 Java EE 和 Java SE 中的 Java Bean 进行验证的方式。该规范主要使用注解的方式来实现对 Java Bean 的验证功能,并且这种方式会覆盖使用 XML 形式的验证描述符,从而使验证逻辑从业务代码中分离出来。

在对参数设置值时,触发校验规则。可用于校验前端页面向后端传递的值,后端新建vo接收该值。

SpringBoot 如何进行参数校验

1.引入pom,即可使用
不引入import javax.validation.constraints.NotNull;

2.对于常规的校验规则,譬如长度、非空等,直接在实体的属性上添加注解即可,这些有校验框架内部定义完成的。

public class LoginVo {

    @NotNull      //常规校验  直接添加注解
    @Length(min=6,max=50)
    private String password;
    ...
}

3.对于特殊的校验规则,譬如手机号码格式,需要自定义

public class LoginVo {

    @NotNull
    @IsMobile  //特殊校验  需要自定义
    private String mobile;
    
	 ...
         
}

自定义校验规则要写两项内容

//其一
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class })
public @interface IsMobile {

    boolean required() default true;
    //以下三项必须
    String message() default "手机格式错误";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}
//其二  具体的校验逻辑
public class IsMobileValidator implements ConstraintValidator<IsMobile,String> {

    private boolean required = false;
    public void initialize(IsMobile constraintAnnotation) {
        required = constraintAnnotation.required();
    }


    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(required){
            return ValidatorUtil.isMobile(value);
        }else{
            if(StringUtils.isEmpty(value)) {
                return true;
            }else{
                return ValidatorUtil.isMobile(value);
            }
        }
    }
}
  1. 使用valid注解

    @Valid注解可以实现数据的验证,你可以定义实体,在实体的属性上添加校验规则,而在API接收数据时添加@valid关键字,这时你的实体将会开启一个校验的功能。

     @RequestMapping("/do_login")
        @ResponseBody
        public Result<Boolean> dologin(@Valid  LoginVo loginVo){
            //valid会替代以下注释中的校验逻辑
            /*String mobile = loginVo.getMobile();
            String password = loginVo.getPassword();
            if(mobile == null){
                return Result.error(CodeMsg.MOBIL_EMPTY);
            }
            if(password == null){
                return Result.error(CodeMsg.PASSWORD_EMPTY);
            }
            if(!ValidatorUtil.isMobile(mobile)){
                return Result.error(CodeMsg.MOBIL_ERROR);
            }*/
            log.info(loginVo.toString());
            CodeMsg login = miaoshaUserService.login(loginVo);
            log.info(login.toString());
            if(login.getCode() == 0 ){
                return Result.success(true);
            }
            return Result.error(login);//?
        }
    

全局异常拦截

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值