第六章 分布式缓存实战-String数据结构最佳案例+Jmeter5.x压测

第1集 案例实战需求之图形验证码+谷歌开源Kaptcha介绍

简介:案例实战之注册登录-图形验证码+谷歌开源Kaptcha引入

  • 背景

    • 注册-登录-修改密码一般需要发送验证码,但是容易被攻击恶意调用

    • 什么是短信-邮箱轰炸机

      • 手机短信轰炸机是批􏰀、循环给手机无限发送各种网站的注册验 证码短信的方法。
    • 公司带来的损失

      • 短信一条5分钱,如果被大􏰀盗刷大家自己计算 邮箱通知不用钱,但被大􏰀盗刷,带宽、连接等都被占 用,导致无法正常使用
  • 如何避免自己的网站成为”肉鸡“或者被刷呢

    • 增加图形验证码(开发人员)
    • 单IP请求次数限制(开发人员)
    • 限制号码发送(一般短信提供商会做)
    • 攻防永远是有的,只过加大了攻击者的成本,ROI划不 过来自然就放弃了
  • Kaptcha 框架介绍

    • 谷歌开源的一个可高度配置的实用验证 码生成工具

      • 验证码的字体/大小/颜色
      • 验证码内容的范围(数字,字母,中文汉字!)
      • 验证码图片的大小,边框,边框粗细,边框颜色
      • 验证码的干扰线 验证码的样式(⻥眼样式、3D、普通模糊)
  • 添加依赖

 
 
 
 
 
 
 <!--kaptcha依赖包--> 
<dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>kaptcha-spring-boot-starter</artifactId>
      <version>1.0.0</version>
    </dependency>
 
  • 代码配置
 
 
 
 
 
 
@Configuration
public class CaptchaConfig {
    /**
     * 验证码配置
     * Kaptcha配置类名
     * 
     * @return
     */
    @Bean
    @Qualifier("captchaProducer")
    public DefaultKaptcha kaptcha() {
        DefaultKaptcha kaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        //验证码个数
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        //字体间隔
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE,"8");
        //干扰线颜色
        //干扰实现类
        properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
        //图片样式
        properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple");
        //文字来源
        properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
        Config config = new Config(properties);
        kaptcha.setConfig(config);
        return kaptcha;
    }
}
 

 

 

 

 

 

 

 

 

第2集 案例实战之企业级-验证码存储Redis和工具类介绍

简介:验证码存储Redis逻辑编码实战

  • CommonUtil工具类
 
 
 
 
 
 
    /**
     * 获取ip
     * @param request
     * @return
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) {
                // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        return ipAddress;
    }
    public static String MD5(String data)  {
        try {
            java.security.MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
            return sb.toString().toUpperCase();
        } catch (Exception exception) {
        }
        return null;
    }
 
  • 接口开发
 
 
 
 
 
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private Producer captchaProducer;
    /**
     *临时使用10分钟有效,方便测试
     */
    private static final long CAPTCHA_CODE_EXPIRED = 60 * 1000 * 10;
    /**
     * 获取图形验证码
     * @param request
     * @param response
     */
    @GetMapping("captcha")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response){
        String captchaText = captchaProducer.createText();
        //存储
        redisTemplate.opsForValue().set(getCaptchaKey(request),captchaText,CAPTCHA_CODE_EXPIRED,TimeUnit.MILLISECONDS);
        BufferedImage bufferedImage = captchaProducer.createImage(captchaText);
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            ImageIO.write(bufferedImage,"jpg",outputStream);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
        }
    }
    /**
     * 获取缓存的key
     * @param request
     * @return
     */
    private String getCaptchaKey(HttpServletRequest request){
        String ip = CommonUtils.getIpAddr(request);
        String userAgent = request.getHeader("User-Agent");
        String key = "user-service:captcha:"+CommonUtils.MD5(ip+userAgent);
        return key;
    }
 

 

 

 

 

 

 

 

第3集 案例实战之JsonData工具类封装+验证码校验编码实战

简介:JsonData工具类封装+验证码校验编码实战

  • JsonData响应工具类封装
 
 
 
 
 
 
public class JsonData {
    /**
     * 状态码 0 表示成功
     */
    private Integer code;
    /**
     * 数据
     */
    private Object data;
    /**
     * 描述
     */
    private String msg;
    public JsonData(int code,Object data,String msg){
        this.code = code;
        this.msg = msg;
        this.code = code;
    }
    /**
     * 成功,不传入数据
     * @return
     */
    public static JsonData buildSuccess() {
        return new JsonData(0, null, null);
    }
    /**
     *  成功,传入数据
     * @param data
     * @return
     */
    public static JsonData buildSuccess(Object data) {
        return new JsonData(0, data, null);
    }
    /**
     * 失败,传入描述信息
     * @param msg
     * @return
     */
    public static JsonData buildError(String msg) {
        return new JsonData(-1, null, msg);
    }
    
    //set get 方法省略
}
 

 

  • 校验逻辑
 
 
 
 
 
 
    /**
     * 支持手机号、邮箱发送验证码
     * @return
     */
    @GetMapping("send_code")
    public JsonData sendRegisterCode(@RequestParam(value = "to", required = true)String to,
                                     @RequestParam(value = "captcha", required = true)String  captcha,
                                     HttpServletRequest request){
        String key = getCaptchaKey(request);
        String cacheCaptcha = redisTemplate.opsForValue().get(key);
        if(captcha!=null && cacheCaptcha!=null && cacheCaptcha.equalsIgnoreCase(captcha)) {
            redisTemplate.delete(key);
           //TODO 发送验证码
            return jsonData;
        }else {
            return JsonData.buildResult("图形验证码错误");
        }
    }
 

 

 

 

 

 

 

 

 

 

第4集 小滴课堂官网-高并发下-商品首页热点数据开发实战

简介:高并发商品首页热点数据开发实战

  • 热点数据

    • 经常会被查询,但是不经常被修改或者删除的数据
    • 首页-详情页
  • 链路逻辑

    • 检查缓存是否有
    • 缓存不存在则查询数据库
    • 查询结果放到缓存,设置过期时间
    • 下次访问则命中缓存
  • 接口开发

    • 模拟数据库查询耗时200ms
    • 未加缓存逻辑:controller-service-dao层
    • 加缓存逻辑:controller-service-dao层

     

  • 注意点

    • 缓存击穿
    • 缓存穿透
    • 缓存雪崩
    • 缓存和数据库数据一致性

 

 

 

 

 

 

 

 

第5集 一线大厂必备Jmeter5.x压力测试工具急速入门

简介: 一线大厂必备Jmeter5.x压力测试工具急速入门

  • LoadRunner

    • 性能稳定,压测结果及细粒度大,可以自定义脚本进行压测,但是太过于重大,功能比较繁多
  • Apache AB(单接口压测最方便)

    • 模拟多线程并发请求,ab命令对发出负载的计算机要求很低,既不会占用很多CPU,也不会占用太多的内存,但却会给目标服务器造成巨大的负载, 简单DDOS攻击等
  • Webbench

    • webbench首先fork出多个子进程,每个子进程都循环做web访问测试。子进程把访问的结果通过pipe告诉父进程,父进程做最终的统计结果。
  • Jmeter

    • 开源免费,功能强大,在互联网公司普遍使用
  • 压测工具本地快速安装Jmeter5.x

  • 目录

 
 
 
 
 
 
bin:核心可执行文件,包含配置
        jmeter.bat: windows启动文件(window系统一定要配置显示文件拓展名)
        jmeter: mac或者linux启动文件
        jmeter-server:mac或者Liunx分布式压测使用的启动文件
        jmeter-server.bat:window分布式压测使用的启动文件
        jmeter.properties: 核心配置文件   
extras:插件拓展的包
lib:核心的依赖包
 
  • Jmeter语言版本中英文切换

    • 控制台修改 menu -> options -> choose language
  • 配置文件修改

    • bin目录 -> jmeter.properties
    • 默认 #language=en
    • 改为 language=zh_CN

 

 

 

 

 

 

 

 

 

第6集 Jmeter5.X基础功能组件介绍+线程组和Sampler

简介:讲解Jmeter里面GUI菜单栏主要组件

  • 添加->threads->线程组(控制总体并发)

     
     
     
     
     
     
    线程数:虚拟用户数。一个虚拟用户占用一个进程或线程
    准备时长(Ramp-Up Period(in seconds)):全部线程启动的时长,比如100个线程,20秒,则表示20秒内 100个线程都要启动完成,每秒启动5个线程
    循环次数:每个线程发送的次数,假如值为5,100个线程,则会发送500次请求,可以勾选永远循环
     

     

  • 线程组->添加-> Sampler(采样器) -> Http (一个线程组下面可以增加几个Sampler)

     
     
     
     
     
     
    名称:采样器名称
    注释:对这个采样器的描述
    web服务器:
      默认协议是http
      默认端口是80
      服务器名称或IP :请求的目标服务器名称或IP地址
    路径:服务器URL
     

     

  • 查看测试结果

     
     
     
     
     
     
    线程组->添加->监听器->察看结果树
    线程组->添加->监听器->聚合报告
     

     

 

 

第7集 Jmeter5.x压测接口实战-接口性能优化前后QPS对比

简介: Jmeter5.x压测接口实战-接口性能优化前后QPS对比

  • 热点数据接口压测

    • QPS: (Query Per Second): 每秒请求数,就是说服务器在一秒的时间内处理了多少个请求

     

  • 新增聚合报告:线程组->添加->监听器->聚合报告(Aggregate Report)

 
 
 
 
 
 
  lable: sampler的名称
  Samples: 一共发出去多少请求,例如10个用户,循环10次,则是 100
  Average: 平均响应时间
  Median: 中位数,也就是 50% 用户的响应时间
  90% Line : 90% 用户的响应不会超过该时间 (90% of the samples took no more than this time.     The remaining samples at least as long as this)
  95% Line : 95% 用户的响应不会超过该时间
  99% Line : 99% 用户的响应不会超过该时间
  min : 最小响应时间
  max : 最大响应时间
  
  Error%:错误的请求的数量/请求的总数
  Throughput: 吞吐量——默认情况下表示每秒完成的请求数(Request per Second) 可类比为qps、tps
  KB/Sec: 每秒接收数据量
 
  • 基于当前机器配置压测

    • 未用缓存接口
    • 用缓存接口
  • 当前架构存在的问题

    • 分布式缓存和应用服务器网络需要内网通信
    • 大家可以自己本地部署Redis进行测试
posted @ 2021-06-19 09:56  老吴IT代码笔记*  阅读(115)  评论(0)    收藏  举报