项目中遇到的Redis缓存问题及面试问题总结

1.Redis服务器 can not get resource from pool.

1000个线程并发还能跑,5000个线程的时候出现这种问题,查后台debug日志,发现redis 线程池不够。刚开始设置的是:

# redis 配置文件
#redis
redis.host=127.0.0.1
redis.port=6379
redis.timeout=300        等待时间  10s改为300s
redis.password=123456
redis.poolMaxTotal=1000   连接数,刚开始最大连接数 设置为100.
redis.poolMaxIdle=500      最大空闲连接数  100改成500
redis.poolMaxWait=300

顺便也改了一下jdbc 的连接池参数,最大空闲和最大连接数都改成1000.在测一下。可以

spring.datasource.filters=stat
spring.datasource.maxActive=1000
spring.datasource.initialSize=100
spring.datasource.maxWait=60000
spring.datasource.minIdle=500
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=select 'x'
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxOpenPreparedStatements=20

2.5000并发下的问题,20个商品,库存减到-4980。

后来看代码发现,判断库存用的是if(stock==0 ) 抛出异常。应该用stock<0,因为 若此时同时2个线程进来,就永远小于0,后面的业务逻辑都可以执行。

3.然后就是超卖的问题

第一次压力测试的时候,5000个线程,分别取不同的token(sessionId),同时访问 秒杀这个接口,商品个数只放了20个。结果出现最后商品数量变负的问题。

4.编码的问题

接口限流防刷的时候,通过计数器限流,如果超过某个阈值,向前端返回一个codeMsg对象用于显示的时候,显示的是String是乱码的问题,之前由于一直返回都是json 格式,都是封装好在data里。

这次返回是直接通过输出流直接写到response直接返回字节数组的,而不是spring controller 返回数据(springboot 默认utf-8),出现乱码问题,用utf-8编码,解决。

5.压测是如何压测的,以及压测的瓶颈?

压测是利用Jmeter压测。(Apache开发的基于java的压测工具)。

压测具体实现:

1.在数据库中提前插入5000个用户密码(脚本 for循环 id是13000000+i),密码统一为“123456”,随机盐值也是固定的,方便操作。用JDBC存入数据库。作为5000个备用用户。

2.然后写了一个小脚本让5000个用户post请求我的登陆接口(login),生成sessionId并存入缓存,并改写了一下login接口让其换回sessionId。把这5000个用户的id和对应sessionid写到了一个TXT文件里面。

3.最后利用jmeter 创建5000个线程,账号每个线程携带提前写好的用户token(sessionId),参数就是商品id和sessionid,商品id确定我要买的票是哪个,sessionid用来获取用户信息。(从缓存中拿)

压测的瓶颈:

qps-126/s----静态化-250/s---接口优化-860/s.

瓶颈主要是对数据库的访问。

1.数据库读取,写入,处理请求的速度。

数据库读取写入加上网络IO速度很慢,减少对数据库的访问,在缓存这一端就屏蔽掉大部分访问数据库的请求(Redis预减库存操作)

2.利用消息队列,异步业务逻辑的处理速度慢,可以先返回结果,让其轮询。

3.利用内存map,减少对Redis服务器的访问,flag机制。

4.其他想到的但还没实现

服务器系统的负载均衡+集群

数据库数据达到1000W以上就很慢,分库分表

6.用户登陆的整个流程是如何实现的?

1.首先输入登陆页面的url.http://localhost:8080/login/to_login,controller根据map映射返回给html页,到达登陆页面

2.整个页面是一个login表单,包含用户名和密码两个输入框部分,还有一个登陆按钮和重置按钮。

3.在前端,给登陆按钮绑定一个login()方法,login()方法中会获取表单中的用户名和密码,然后将密码利用封装好的md5()函数以及设置的固定盐值进行拼接,盐值设置为“1a2b3c”,然后进行MD5算法生成4个32位拼接的散列值作为输入密码(用于 网络传输),作为参数传给后端。(这里的目的主要是第一道加密,防止http明文传输,泄漏密码)。

4.然后ajax异步访问do_login 接口,参数为用户名和md5之后的密码,后端接收到前端传输来的参数后,会对用户名和密码进行参数校验,验证是否为空,是否有格式问题(密码长度6位以上,用户名格式11位等等),如果验证不通过,返回CodeMsg(),封装好的对应的错误信息给前端。

5.如果验证成功,进入下一步,用户的登陆,首先通过用户名取用户对象信息(先从缓存中取,取不到取数据库取,取到了将用户信息存入缓存中,下一次登录我们可以先从缓存中取用户,降低数据库压力),然后返回一个user对象,再判断这个user对象是否为空,若是空就抛出异常,不是空的情况说明数据库中有该用户,然后根据传入的密码和数据中保存的随机盐值,进行md5再次拼接,获得的值若是和数据库中的密码一致,那么说明登陆成功。

关键点: 6.登陆成功的时候,随机生成uuid作为sessionId,将其写入cookie中返回给客户端,并且将模块前缀+该用户id作为key和sessionId 作为值,存入缓存(这里为分布式缓存提供的基础)。这时候跳转到 抢票列表页面,如果密码不匹配,抛出异常,返回。

7.秒杀的两个关键点如何应对--高并发应对策略+页面加载速度?

短时间的大访问量 网站服务器 同网站,不同项目部署,/独立域名 避免对网站造成影响 高并发问题,不停刷新 数据库 页面静态化
同网站,不同项目部署,/独立域名 避免对网站造成影响 宽带 同网站,不同项目部署,/独立域名 避免对网站造成影响 不能提前下单 服务器 url动态化,+随机数
下单之后的抢的问题 sql 乐观锁

大量访问高并发的应对(主要访问大量访问数据库崩溃)

1.Redis预减库存减少数据库访问

2.map标记减少Redis访问屏蔽一定的请求减轻缓存压力

3.消息队列异步处理

  • 流量削峰 开始抢购的瞬间 大量并发进入,先将请求入队,若队列满了,那么舍弃再入队的请求返回一个异常

  • 先给前端一个数据返回表示排队中,再进行后续的业务处理,前端轮询最后成功或者失败在显示业务结果

4.数据库运行的问题,传统的sql写成存储过程(直接调用),加速sql

5.数据库里锁及唯一索引来处理抢的问题。

页面加载速度

页面静态化,缓存在客户端

CDN服务器

在上表中列出来的解决方案中看出,利用 页面静态化、数据静态化,反向代理 等方法可以避免 带宽和sql压力 ,但是随之而来一个问题,页面抢单按钮也不会刷新了,可以把 js 文件单独放在js服务器上,由另外一台服务器写 定时任务 来控制js 推送。

另外还有一个问题,js文件会被大部分浏览器缓存,我们可以使用xxx.js?v=随机数 的方式来避免js被缓存

8.页面静态化的过程

更为激进的缓存方式(之前可以用将html源码缓存起来再读,避免服务器渲染html过程)。

什么是浏览器缓存:

  简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过网站的网页,当再次访问这个URL地址的时候,如果网页没有更新,就不会再次下载网页,而是直接使用本地缓存的网页。只有当网站明确标识资源已经更新,浏览器才会再次下载网页。

页面静态化的好处:

我们知道浏览器会将html,图片等静态数据,缓存到本地,在高并发抢票场景,用户会通过不断的刷新页面来进行抢票操作,这样带来Web带宽的浪费以及服务器的访问压力。于是,我们可以通过将抢票页面做成静态页面html页,其中的票务数据通过ajax异步调用接口来获取,仅仅交互的是部分数据,减少了带宽,也加快用户访问的速度。

  function getDetail() {
        var goodsId = g_getQueryString("goodsId");
        $.ajax({
            url : "/goods/to_detail/"+goodsId,
            type : "GET",
            success: function (data) {
                if (data.code  == 0) {// 访问后端detail 接口拿到数据
                    render(data.data);//渲染界面的方法
                }else {
                    layer.msg(data.msg)
                }
            },
            error:function () {
             layer.msg("客户端请求有误!")
            }
        })
    }

    function render(detail) {
        var  goodsVo =detail.goodsVo;
        var miaoshaStatus =detail.miaoshaStatus;
        var remainSeconds =detail.remainSeconds;
        var user =detail.user;
        if (user) {
            $("#userTip").hide();//没有就不展示
        }
        //用获取的参数 放入 对应的模板中
            $("#goodsName").text(goodsVo.goodsName);
            $("#goodsImg").attr("src", goodsVo.goodsImg);
            $("#startTime").text(new Date(goodsVo.startDate).format("yyyy-MM-dd hh:mm:ss"));
            $("#remainSeconds").val(remainSeconds);
            $("#goodsId").val(goodsVo.id);
            $("#goodsPrice").text(goodsVo.goodsPrice);
            $("#miaoshaPrice").text(goodsVo.miaoshaPrice);
            $("#stockCount").text(goodsVo.stockCount);
            countDown();//调用倒计时
    }
    function countDown() {
        var remainSeconds = $("#remainSeconds").val();
        // var remainSeconds = $("#remainSeconds").val();
        var timeout;//定义一个timeout 保存Timeout 值
        if (remainSeconds>0){//秒杀未开始
            $("#buyButton").attr("disabled",true);/*还没开始的时候按钮不让点*/
            $("#miaoshaTip").html("秒杀倒计时:"+remainSeconds+"秒");
            /*且做一个倒计时*/
            timeout=setTimeout(function () {//setTimeout 为时间到了之后执行 该函数
                $("#countDown").text(remainSeconds-1);//将显示中的值 -1
                $("#remainSeconds").val(remainSeconds-1);// remianSeconds 值减一
                countDown();//在调用该方法 实现循环
            },1000)
        }else if (remainSeconds == 0){//秒杀进行中
            $("#buyButton").attr("disabled",false);
            //当remainSeconds =0
            clearTimeout(timeout);//取消timeout 代码执行
            $("#miaoshaTip").html("秒杀进行中!")//修改其中的内容
            /**加入秒杀数学验证码 功能
             * 1.一开始图形验证码和输入框都是隐藏的
             * 2.当秒杀进行的时候,显示验证码和输入框
             * */
            $("#verifyCodeImg").attr("src", "/miaosha/verifyCode?goodsId="+$("#goodsId").val());//访问验证码接口
            $("#verifyCodeImg").show();
            $("#verifyCode").show();

        } else {//秒杀结束
            $("#buyButton").attr("disabled",true);
            $("#miaoshaTip").html("结束!!!")//修改其中的内容
        }
    }

做法:首先将票务详情这个template 模板 html页放在static 文件下,然后改掉thymeleaf 模板语言标签让其成为纯html语言,然后将票务列表中的链接指向(本来是requestMapping,向后端contrller 请求这个详情业务及数据,然后利用spring渲染模板,在返回的),现在直接指向static文件下的票务详情页(链接中带商品id作为参数),最后在这个html页面写ajax异步访问后端接口/getdetail,后端接口也改造一下返回的是这个商品的全部详细信息,封装在data里,以json的形式,然后写了一个render(),把从后端传来的数据写进对应数据中。

 /** 页面静态化:商品详情页面
     * 方法:返回的是一个静态html 页面 + 利用ajax(通过接口)从服务端获取对应数据 + js技术将数据放入html
     * */
    @RequestMapping(value = "/to_detail/{goodsId}") // 前端传入的参数 goodsId
    @ResponseBody
    public Result<GoodsDetailVo> detail(HttpServletRequest request, HttpServletResponse response, Model model, MiaoshaUser user,
                         @PathVariable("goodsId") Long goodsId){//通过注解@PathVariable获取路径参数
        /*先将user 传进去 用来判断是否登录*/
        model.addAttribute("user",user);
        /*根据传入的Id 通过service 拿到对应的Good信息*/
        GoodsVo goods = goodsService.getGoodsById(goodsId);
        model.addAttribute("goods",goods);

        long startTime = goods.getStartDate().getTime();
        long endTime = goods.getEndDate().getTime();
        long nowTime = System.currentTimeMillis();/* 拿到现在时间的毫秒值*/
        /**这里要做一个秒杀时间的判断 秒杀开始 秒杀结束 秒杀进行
         * */
        int miaoshaStatus = 0;/*用该变量来表示 秒杀的状态 0 表示秒杀未开始 1 开始 2 结束*/
        int remainSeconds = 0; /*表示剩余时间 距离秒杀开始的时间*/
        if (nowTime<startTime){//秒杀未开始
            miaoshaStatus = 0;
            remainSeconds = (int)((startTime-nowTime)/1000);//注意此时是 毫秒值 要除以1000
        }else if (endTime<nowTime){//秒杀结束
            miaoshaStatus = 2;
            remainSeconds = -1;
        }else {//秒杀进行中
            miaoshaStatus = 1;
            remainSeconds = 0;
        }
        model.addAttribute("remainSeconds",remainSeconds);
        model.addAttribute("miaoshaStatus",miaoshaStatus);
        /*
        将我们需要的数据 封装到GoodsDetailVo中
         */
        GoodsDetailVo goodsDetailVo = new GoodsDetailVo();
        goodsDetailVo.setGoodsVo(goods);
        goodsDetailVo.setMiaoshaStatus(miaoshaStatus);
        goodsDetailVo.setRemainSeconds(remainSeconds);
        goodsDetailVo.setUser(user);
        return Result.success(goodsDetailVo);

文末彩蛋

针对于上面所涉及到的知识点我总结出了有1到5年开发经验的程序员在面试中涉及到的绝大部分架构面试题及答案做成了文档和架构视频资料免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以关注我一下以后会有更多干货分享。

资料获取方式 QQ群搜索“708-701-457” 即可免费领取

项目中遇到的Redis缓存问题及面试问题总结
项目中遇到的Redis缓存问题及面试问题总结
项目中遇到的Redis缓存问题及面试问题总结

转载于:https://blog.51cto.com/14230003/2380737

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
### 回答1: 为了在Java处理Redis缓存问题,您需要使用Redis Java客户端库。常用的Java Redis客户端有Jedis、Lettuce和Redisson。 Jedis是一个纯Java实现的Redis客户端,使用简单,支持同步和异步两种操作方式。 Lettuce是一个高性能的Redis客户端,支持同步、异步和响应式操作。 Redisson是一个基于Netty的Redis客户端,支持分布式和可扩展的特性。 以下是使用Jedis的一个简单示例: ``` import redis.clients.jedis.Jedis; public class JedisExample { public static void main(String[] args) { Jedis jedis = new Jedis("localhost"); jedis.set("foo", "bar"); String value = jedis.get("foo"); System.out.println(value); } } ``` 通过以上示例,您可以很容易地对Redis缓存进行读写操作。 ### 回答2: 在Java处理Redis缓存问题的方法有以下几个步骤: 首先,需要使用Java的Redis客户端库来连接和操作Redis服务器。常用的Java Redis客户端库包括Jedis、Lettuce等。 其次,需要在Java代码导入合适的Redis客户端库,并通过连接池或者单例模式创建Redis连接对象。 然后,可以通过Redis连接对象进行操作,比如设置缓存、获取缓存、删除缓存等。对于设置缓存,可以使用set(key, value)方法来缓存键值对;对于获取缓存,可以使用get(key)方法来获取对应的值;对于删除缓存,可以使用del(key)方法来删除某个键。 另外,为了实现缓存的过期时间和淘汰策略,Redis提供了expire(key, seconds)方法来设置过期时间,以及setex(key, seconds, value)方法来设置带有过期时间的缓存。 此外,为了提高缓存的效率,可以使用批量操作和管道技术。比如,可以使用mget(keys)方法一次性获取多个缓存值;可以使用pipeline()和exec()方法来批量执行一组操作,从而减少网络往返时间。 最后,为了保证程序的健壮性和可靠性,需要在处理Redis缓存的代码做好异常处理,并考虑并发访问和数据一致性等问题。 总之,通过以上步骤,在Java处理Redis缓存问题可以使用Redis客户端库提供的API来连接、操作和管理Redis服务器,从而实现高效的缓存操作和管理。 ### 回答3: 用Java处理Redis缓存问题可以通过使用Java客户端库来与Redis进行交互。以下是处理Redis缓存的一般步骤: 1. 引入Java客户端库:首先,需要在Java项目引入适当的Redis客户端库,例如Jedis或Lettuce。 2. 创建Redis连接:通过客户端库提供的API,使用Redis的主机名、端口号和密码创建Redis连接。连接信息可以在应用程序的配置文件配置,以便在需要的时候进行更改。 3. 设置和获取缓存数据:使用客户端库提供的方法,可以将数据存储到Redis缓存。例如,可以使用"SET"命令将键值对存储在Redis,并使用"GET"命令来获取存储在Redis的数据。 4. 设置缓存过期时间:如果需要给缓存数据设置过期时间,可以使用客户端库提供的方法来设置键的过期时间。例如,可以使用"EXPIRE"命令设置键的过期时间。 5. 处理缓存失效:在从缓存获取数据之前,需要先检查缓存是否存在。如果缓存不存在,可以从其他数据源获取数据,并将数据存储在缓存以供下一次使用。 6. 使用缓存策略:根据应用程序的需求,可以采用不同的缓存策略来提高缓存的效率和性能。例如,可以使用LRU(最近最少使用)策略来淘汰最近最少使用的缓存数据。 7. 监控和管理Redis缓存:使用客户端库提供的方法,可以监控和管理Redis缓存。例如,可以使用"INFO"命令获取Redis服务器的详细信息,使用"KEYS"命令列出或删除缓存数据等。 总结:使用Java处理Redis缓存问题主要涉及创建Redis连接、设置和获取缓存数据、设置缓存过期时间、处理缓存失效、使用缓存策略以及监控和管理Redis缓存。通过Java客户端库提供的API,可以方便地与Redis进行交互,实现缓存功能,提高应用程序的性能和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值