articleList

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

2025/03/16 posted in  Redis
Tags: 

第 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 进行测试