首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >pmq学习总结

pmq学习总结

作者头像
路行的亚洲
发布于 2021-03-04 07:47:50
发布于 2021-03-04 07:47:50
65100
代码可运行
举报
文章被收录于专栏:后端技术学习后端技术学习
运行总次数:0
代码可运行

pmq架构图

pmq中消息端到端流程

1.消息的核心是什么?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1) 消息发送publish =>MqClient.publish(topicName, "",new ProducerDataDto(String.valueOf(i)));=>ConsumerController#publish
2)消息存储 saveMessage =>msgNotifyService.notify(request);=>MqQueueExcutorService#notifyMsg
3)消息消费 doPullingData=>pullData =>ConsumerController#consumerService.pullData(request)    

2.消息发送的过程中需要考虑什么?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1)消息发送中丢失的情况,而此时必然需要保证至少交付1次(at least one)2)消息发送采用重试机制(retry),如果重试没有效果,会将失败的队列的相关信息记录到链路和日志中(cat and log),保证交付。
3)同时对重平衡的处理(考虑消费者上下线和队列上下线的情况,也即扩缩容的情况),同时消费的过程中,会对偏移量进行统计,同时会进行心跳上报,证明自己还活着没有下线。
4)而这个偏移量则是消息id。与rocketMQ中不同。
5)消费者在业务方需要做幂等处理,因为消息重启后可能会接收到少量重复的消息。
6)而高可用通过数据库主备来保证。
7)而如果出现生产者、消费者、broker挂掉的情况,在Metric中有详细的情况记录。
8)分区通过表隔离,一个分区对应一个表,不同主题/分区互不干扰。上线下线时,通过Broker向DB插入一条消息,表示需要触发重平衡。一旦broker有重平衡产生,立即通知消费者进行重平衡。消费端收到重平衡后,立即提交偏移并停止消费,连续三次(可配)被分配队列相同时,则开启新的消费。  

进行发送消息处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//进行判空处理,处理完成返回true
if (request == null) {
    return true;
}
//进行事务处理、同时进行metric信息收集,执行请求处理,处理完,进行耗时统计,设置事务状态,添加信息到链路中,如果失败,执行失败处理,将失败信息放入链路中
Transaction transaction = Tracer.newTransaction("mq-client-publish", request.getTopicName());
Timer.Context timer1 = MetricSingleton.getMetricRegistry()
    .timer("mq.client.publish.time?topic=" + request.getTopicName()).time();
try {
    String url = MqConstanst.CONSUMERPRE + "/publish";
    long start = System.nanoTime();
    PublishMessageResponse response = post(request, url, retryTimes, PublishMessageResponse.class, true);
    long end = System.nanoTime();
    if (response.getTime() > 0) {
        long t = end - start - response.getTime();
        t = (t - t % 1000000) / 1000000;
        MetricSingleton.getMetricRegistry()
            .histogram("mq.client.publish.network.time?topic=" + request.getTopicName()).update(t);
    }
    transaction.setStatus(Transaction.SUCCESS);
    if (!response.isSuc()) {
        String json = JsonUtil.toJson(request);
        logger.error(response.getMsg());
        CatRequest request2 = new CatRequest();
        request2.setMethod("publish_fail");
        request2.setJson(json);
        request2.setMsg(response.getMsg());
        addCat(request2);
    }
    return response.isSuc();
} catch (Exception e) {
    MetricSingleton.getMetricRegistry().counter("mq.client.publish.fail.count?topic=" + request.getTopicName())
        .inc();
    logger.error("publish_error", e);
    String json = JsonUtil.toJson(request);
    transaction.setStatus(e);
    CatRequest request2 = new CatRequest();
    request2.setMethod("publish");
    request2.setJson(json);
    request2.setMsg(e.getMessage());
    addCat(request2);

    SendMailRequest mailRequest = new SendMailRequest();
    mailRequest.setSubject("消息发送失败,客户端:" + request.getClientIp() + ",Topic:" + request.getTopicName());
    mailRequest.setContent("消息发送异常," + ",消息体是:" + json + ",异常原因是:" + e.getMessage());
    mailRequest.setType(2);
    mailRequest.setTopicName(request.getTopicName());
    sendMail(mailRequest);
    return false;
} finally {
    transaction.complete();
    timer1.stop();
}

发送请求的过程中,如果失败还会进行降速处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (response != null) {
   if (isImportant) {
      failUrlG1.put(host, System.currentTimeMillis() - 10 * 1000);
   } else {
      failUrlG2.put(host, System.currentTimeMillis() - 10 * 1000);
   }
   if (response instanceof PublishMessageResponse) {
      PublishMessageResponse response2 = ((PublishMessageResponse) response);
      if (response2.getSleepTime() > 0) {
         response = null;
         logger.info(response2.getMsg());
         Util.sleep(response2.getSleepTime());
         // 这个不算重试,只是降速
         count--;
      }
   } else {
      BaseResponse baseResponse = (BaseResponse) response;
      if (!baseResponse.isSuc() && baseResponse.getCode() == MqConstanst.NO) {
         response = null;
         Util.sleep(1000);
      } else {
         if (!baseResponse.isSuc()) {
            logger.error(baseResponse.getMsg());
         }
      }
   }
} else {
   // response 等于null 说明接口调用失败了。此时需要将url 放入失败接口中。
   if (isImportant) {
      failUrlG1.put(host, System.currentTimeMillis());
   } else {
      failUrlG2.put(host, System.currentTimeMillis());
   }
   Util.sleep(500);
}

保存消息到数据库

存储的过程就是将消息进行保存的过程,在pmq中就是将数据保存到数据库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//publish操作
@Override
public PublishMessageResponse publish(PublishMessageRequest request) {
    PublishMessageResponse response = new PublishMessageResponse();
    checkVaild(request, response);
    if (!response.isSuc()) {
        return response;
    }
    try {
        if (!checkTopicRate(request, response)) {
            return response;
        }
        //获取所有分配好的topic写队列
        Map<String, List<QueueEntity>> queueMap = queueService.getAllLocatedTopicWriteQueue();
        //获取所以分配好的主题队列
        Map<String, List<QueueEntity>> topicQueueMap = queueService.getAllLocatedTopicQueue();
        if (queueMap.containsKey(request.getTopicName()) || topicQueueMap.containsKey(request.getTopicName())) {
            List<QueueEntity> queueEntities = queueMap.get(request.getTopicName());
            if (queueEntities == null || queueEntities.size() == 0) {
                response.setSuc(false);
                response.setMsg("topic_" + request.getTopicName() + "_and_has_no_queue!");
                //如果主题队列map中包含请求中拿到主题名称,或者配置中拿到发布模式,则执行获取队列信息,同时更新队列缓存
                if (topicQueueMap.containsKey(request.getTopicName()) && soaConfig.getPublishMode() == 1) {
                    queueEntities = topicQueueMap.get(request.getTopicName());
                    updateQueueCache(request.getTopicName());
                } else {
                    updateQueueCache(request.getTopicName());
                    return response;
                }
            }
            //如果队列实体列表>0,则执行保存消息操作
            if (queueEntities.size() > 0) {
                saveMsg(request, response, queueEntities);
            }
        } else {
            response.setSuc(false);
            response.setMsg("topic1_" + request.getTopicName() + "_and_has_no_queue!");
            return response;
        }
    } catch (Exception e) {
        log.error("publish_error,and request json is " + JsonUtil.toJsonNull(request), e);
        response.setSuc(false);
        response.setMsg(e.getMessage());
    } finally {
        //最终,将计数进行自减,同时获取
        if (soaConfig.getEnableTopicRate() == 1) {
            totalMax.decrementAndGet();
            //获取主题
            topicPerMax.get(request.getTopicName()).decrementAndGet();
        }
    }
    return response;
}

3.消息存储的过程中需要考虑高可用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
使用数据库主备db,消息存储同时进行负载均衡,避免单个数据库出现消息队列请求频繁的情况,造成单个库压力过大,同时库可以水平扩展

进行消息的保存操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//执行保存消息
protected void doSaveMsg(List<Message01Entity> message01Entities, PublishMessageRequest request,
                         PublishMessageResponse response, QueueEntity temp) {
    // Transaction transaction = Tracer.newTransaction("PubInner-" +
    // temp.getIp(), request.getTopicName());
    //消息服务设置数据库id
    message01Service.setDbId(temp.getDbNodeId());
    Transaction transaction = Tracer.newTransaction("Publish-Data", temp.getIp());
    try {
        transaction.addData("topic", request.getTopicName());
        //执行批量插入
        message01Service.insertBatchDy(request.getTopicName(), temp.getTbName(), message01Entities);
        // 如果订阅该queue的组,开启了实时消息,则给对应的客户端发送异步通知
        if (soaConfig.getMqPushFlag() == 1) {// apollo开关
            //通知客户端
            notifyClient(temp);
        }
        dbFailMap.put(getFailDbUp(temp), System.currentTimeMillis() - soaConfig.getDbFailWaitTime() * 2000L);
        response.setSuc(true);
        transaction.setStatus(Transaction.SUCCESS);
        return;
    } catch (Exception e) {
        // sendPublishFailMail(request, e, 1);
        transaction.setStatus(e);
        if (e instanceof DataIntegrityViolationException
                || e.getCause() instanceof DataIntegrityViolationException) {
            response.setSuc(false);
            response.setMsg(e.getMessage());
            return;
        }
        dbFailMap.put(getFailDbUp(temp), System.currentTimeMillis());
        // transaction.setStatus(e);
        throw new RuntimeException(e);
    } finally {
        transaction.complete();
    }
}

4.消息的消费需要考虑

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
保证消费一次,采用重试机制进行间断性重试。如果重试后没有成功,采用cat进行链路追踪,同时在业务系统进行幂等处理。

进行消息拉取

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//拉取数据,消费消息
@Override
public PullDataResponse pullData(PullDataRequest request) {
    PullDataResponse response = new PullDataResponse();
    response.setSuc(true);
    Map<Long, QueueEntity> data = queueService.getAllQueueMap();
    checkVaild(request, response, data);
    if (!response.isSuc()) {
        return response;
    }
    //获取消息,通过队列id获取
    QueueEntity temp = data.get(request.getQueueId());
    Map<Long, DbNodeEntity> dbNodeMap = dbNodeService.getCache();
    List<Message01Entity> entities = new ArrayList<>();
    Transaction transaction = null;
    if (checkFailTime(request.getTopicName(), temp, null) && checkStatus(temp, dbNodeMap)) {
        //设置数据库id
        message01Service.setDbId(temp.getDbNodeId());
        transaction = Tracer.newTransaction("Pull-Data", temp.getIp());
        try {
            //获取消息,批量
            entities = message01Service.getListDy(temp.getTopicName(), temp.getTbName(), request.getOffsetStart(),
                    request.getOffsetEnd());
            transaction.setStatus(Transaction.SUCCESS);
            dbFailMap.put(getFailDbUp(temp), System.currentTimeMillis() - soaConfig.getDbFailWaitTime() * 2000L);
        } catch (Exception e) {
            transaction.setStatus(e);
            dbFailMap.put(getFailDbUp(temp), System.currentTimeMillis());
            // TODO: handle exception
        }

    } else {
        transaction = Tracer.newTransaction("PullData", "PullData-wait");
        transaction.setStatus(Transaction.SUCCESS);

    }
    transaction.complete();
    List<MessageDto> messageDtos = converMessageDto(entities);
    response.setMsgs(messageDtos);
    return response;
}

5.重平衡:为什么进行重平衡?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
进行重平衡的条件:出现消费者consumer的加入和退出、主题topic的增加和退出,通常是进行扩缩容的时候会用到

进行重平衡会做的一个操作:通知重平衡,而过程中会更新重平衡的版本,同时设置通知的消息,同时将通知的消息插入到通知消息服务中,也即会插入到通知消息表中。而在生产者将消息发送存储成功后,也会执行发送通知消费者的动作,也是会做一个通知的操作。例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public void notifyRb(long id) {
   updateRbVersion(Arrays.asList(id));
   List<NotifyMessageEntity> notifyMessageEntities = new ArrayList<>();
   NotifyMessageEntity notifyMessageEntity = new NotifyMessageEntity();
   notifyMessageEntity.setConsumerGroupId(id);
   notifyMessageEntity.setMessageType(MessageType.Rb);
   notifyMessageEntities.add(notifyMessageEntity);

   notifyMessageEntity = new NotifyMessageEntity();
   notifyMessageEntity.setConsumerGroupId(id);
   notifyMessageEntity.setMessageType(MessageType.Meta);
   notifyMessageEntities.add(notifyMessageEntity);
   notifyMessageService.insertBatch(notifyMessageEntities);
}

6.排查问题的保证

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
审计日志AuditLog和Cat监控=>这个可以从消息发送开始就可以看到直至消息消费

7.优化部分

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
定时清理冗余数据、定时清理历史数据、定时不再存活的消费者通知邮件告警、定时不再进行消息订阅服务通知邮件、定时截断、定时检查、堆积告警(进行邮件发送),对于失败消息进行存储,同时进行告警、心跳检查(采用时间对比的方式,如果超过心跳上报监测时间,则进行数据移除处理,同时offset设置为0)

进行消息清理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public void doStart() {
   long minId = notifyMessageService.getMinId();
   if (minId > 0 && isMaster()&&soaConfig.isEnbaleNotifyMessageClean()) {
      log.info("clear_old_data_minId_is_{}_and_maxId_is_{}", minId, minId + 500);
      int count = notifyMessageService.clearOld(soaConfig.getCleanInterval(), minId + 500);
      while (count > 0 && isMaster()) {
         minId = notifyMessageService.getMinId();
         log.info("clear_old_data_minId_is_{}_and_maxId_is_{}", minId, minId + 500);
         count = notifyMessageService.clearOld(soaConfig.getCleanInterval(), minId + 500);
         Util.sleep(3000);
      }
   }
}

8.提交偏移量

偏移量提交在启动broker定时任务的时候,就执行了提交偏移量服务的启动。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
     * 提交偏移量
     * @param request
     * @return
     */
    @Override
    public CommitOffsetResponse commitOffset(CommitOffsetRequest request) {
        // Transaction catTransaction = Tracer.newTransaction("Timer-service",
        // "commitOffset");
        CommitOffsetResponse response = new CommitOffsetResponse();
        response.setSuc(true);
        Map<Long, ConsumerQueueVersionDto> map = mapAppPolling.get();
        try {
            //如果请求不为空,同时请求的队列偏移量不为空,则执行提交操作
            if (request != null && !CollectionUtils.isEmpty(request.getQueueOffsets())) {
                request.getQueueOffsets().forEach(t1 -> {
                    ConsumerQueueVersionDto temp = map.get(t1.getQueueOffsetId());
                    boolean flag1 = true;
                    if (temp == null) {
                        synchronized (lockObj1) {
                            temp = map.get(t1.getQueueOffsetId());
                            if (temp == null) {
                                map.put(t1.getQueueOffsetId(), t1);
                                flag1 = false;
                            }
                        }
                    }
                    //如果flag1为true,偏移量版本小,则清理掉老数据,同时将新数据放入,如果当前偏移量版本相等,但偏移量小,则清掉老数据。

                    if (flag1) {
                        if (temp.getOffsetVersion() < t1.getOffsetVersion()) {
                            clearOldData();
                            map.put(t1.getQueueOffsetId(), t1);
                        } else if (temp.getOffsetVersion() == t1.getOffsetVersion()
                                && temp.getOffset() < t1.getOffset()) {
                            clearOldData();
                            map.put(t1.getQueueOffsetId(), t1);
                        }
                    }
                });
                //如果是请求1,则获取版本执行提交偏移量操作
                if (request.getFlag() == 1) {
                    Map<Long, OffsetVersionEntity> offsetVersionMap = queueOffsetService.getOffsetVersion();
                    request.getQueueOffsets().forEach(t1 -> {
                        //执行提交偏移量,此时会进行查询,获取标识flag,查询提交偏移量如果>0,则执行后续操作,此时必须满足有偏移的消息和偏移信息
                        doCommitOffset(t1, 1, offsetVersionMap, 0);
                    });
                }
            }
        } catch (Exception e) {
        }
        // catTransaction.setStatus(Transaction.SUCCESS);
        // catTransaction.complete();
        return response;
    }

9.对于消息进行重平衡的过程中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
比如会涉及到偏移量的问题,考虑解决偏移量offset的处理

执行重平衡中执行偏移量处理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//执行rb重平衡操作
    @Override
    // @Transactional(rollbackFor = Exception.class)
    public void rb(List<QueueOffsetEntity> queueOffsetEntities) {
        Map<Long, String> idsMap = new HashMap<>(30);
        List<NotifyMessageEntity> notifyMessageEntities = new ArrayList<>(30);
        //将传入的查询偏移量的消费组id放入,并执行更新操作
        queueOffsetEntities.forEach(t1 -> {
            idsMap.put(t1.getConsumerGroupId(), "");
            NotifyMessageEntity notifyMessageEntity = new NotifyMessageEntity();
            notifyMessageEntity.setConsumerGroupId(t1.getConsumerGroupId());
            notifyMessageEntity.setMessageType(MessageType.Meta);
            notifyMessageEntities.add(notifyMessageEntity);
            // 更新consumerid 和consumername
            queueOffsetService.updateConsumerId(t1);
        });
        // 更新重平衡版本,注意这个代码非常的重要,这个可以保证客户端能够拿到最新的重平衡版本号
        updateRbVersion(new ArrayList<>(idsMap.keySet()));
        // 批量插入消息事件
        notifyMessageService.insertBatch(notifyMessageEntities);

    }

总结

一个消息中间件少不了的是消息的发送、存储、消费,而在pmq发送过程中需要考虑消息的发送失败情况,此时需要使用重试+链路,如果还是不行,将失败消息放入数据库中,此时将发送失败的消息存入到数据中,以便重新发送。消息发送成功,进行消息存储。而存储的过程中,需要考虑数据库的挂了的情况,因此需要采用数据库主备。而采用通知消费者消息的方式,通知消费者可以拉取消息进行消费了。此时消费者进行消息的消费,此时需要考虑消息偏移量,而这个偏移量则是消息id,与rocketmq中不同,同时在消费的过程中,不排除重平衡的情况产生,也即消费者的上下线、队列上下线的情况,因此此时进行重平衡。而消费的过程中,也会产生消息丢失的情况,因此需要采用重试+链路监控的方式。同时对于消费者,需要进行消息至少一次,因此如果需要一次,在业务消费方中需要进行幂等处理。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Guava 源码分析(Cache 原理【二阶段】)
在上文「Guava 源码分析(Cache 原理)」中分析了 Guava Cache 的相关原理。
纯洁的微笑
2018/08/03
7080
Guava 源码分析(Cache 原理【二阶段】)
日常使用的 Cache 组件来看看 Google 大牛们是如何设计
我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。
爱明依
2022/04/01
3470
GuavaCache学习笔记三:底层源码阅读
申明:转载自 https://www.cnblogs.com/dennyzhangdd/p/8981982.html
一枝花算不算浪漫
2018/12/25
1.1K0
Guava 源码分析(Cache 原理)
我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。
爱撸猫的杰
2019/03/28
1.1K0
Guava 源码分析(Cache 原理)
spring - guava-cache
创建的关键便在于build方法,build方法的核心逻辑位于LocalCache构造器,构造器完成了两件事:
MickyInvQ
2021/10/22
6450
spring - guava-cache
读源码——Guava-Cache
今天,听同事介绍了Cuava-cache,这是个老牌缓存了,虽然近来被Caffine的出现遮盖了风头,但依然不能掩盖它往日的辉煌,至少在我们团队,还有很多项目在使用它,索性就以它为基础,对缓存做一次总结。
早安嵩骏
2020/08/11
9100
Guava Cache缓存设计原理
Google开源的Java重用工具集库Guava里的一款缓存工具,实现的缓存功能:
JavaEdge
2021/02/23
1.1K0
Guava Cache缓存设计原理
Guava Cache -- Java 应用缓存神器
Guava 作为Google开源Java 库中的精品成员,在性能、功能上都十分出色,本文将从实际使用的角度,来对Guava进行讲解。
特鲁门
2018/07/17
7.6K5
缓存那些事儿之【本地缓存篇】
一般来说,一个业务平台系统的整体流程可以基本概括为如下图所示,用户请求从UI(浏览器或者客户端)到网络转发,经过应用服务的业务逻辑处理,再到存储(文件系统或数据库),然后经过渲染将UI呈现给用户。
用户2991389
2018/09/05
3.3K0
缓存那些事儿之【本地缓存篇】
深入解析 Guava Cache- 从基本用法、回收策略、刷新策略到实现原理
Guava Cache 是非常强大的本地缓存工具,提供了非常简单 API 供开发者使用。
勇哥java实战
2025/05/16
2880
深入解析 Guava Cache- 从基本用法、回收策略、刷新策略到实现原理
Guava学习:Cache缓存
摘要: 学习Google内部使用的工具包Guava,在Java项目中轻松地增加缓存,提高程序获取数据的效率。 一、什么是缓存? 根据科普中国的定义,缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。
用户5325874
2020/01/16
1.1K0
guava cache 用法详解
在计算机领域的各个场景中,缓存都是一个非常常用的技术手段。通过高性能的缓存暂时存储重要的数据,可以有效提升整个系统的性能。
用户3147702
2022/06/27
1.3K0
3. java缓存-线程内缓存guava cache
根据上图中的缓存框架,我们常用的一些缓存实例有:LocalManualCache和LocalLoadingCache,两者唯一的区别就是LocalLoadingCache extends LocalManualCache implements LoadingCache<K,V>接口。 LocalManualCache和LocalLoadingCache两者都是对LoaclCache的包装,而LocalCache就是一个缓存的存储器,通过继承AbstractMap和实现ConcurrentMap接口,实现了支持并发的本地map(可以看成类似的ConcunrrentHashMap),LocalCache不对外暴露,因此只能通过其他方式提供实例,这就是CacheBuilder,以后建议大家也可以通过Builder的形式对外暴露实例。
全栈程序员站长
2022/11/09
9480
3. java缓存-线程内缓存guava cache
Guava Cache高级特性
缓存回收:LRU,定时(expireAfterAccess,expireAfterWrite),软弱引用,显示删除(Cache接口方法invalidate,invalidateAll)
黑洞代码
2021/07/14
7810
Guava Cache高级特性
[Java 缓存] Java Cache之 Guava Cache的简单应用.
前言 今天第一次使用MarkDown的形式发博客. 准备记录一下自己对Guava Cache的认识及项目中的实际使用经验. 一: 什么是Guava Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing]
一枝花算不算浪漫
2018/05/18
1.6K0
缓存那些事
前言 一般而言,现在互联网应用(网站或App)的整体流程,可以概括如图1所示,用户请求从界面(浏览器或App界面)到网络转发、应用服务再到存储(数据库或文件系统),然后返回到界面呈现内容。 随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的,且技术变革是缓慢的,数据库每秒能接受的请求次数也是有限的(或者文件的读写也是有限的),如何能够有效利用有限的资源来提供尽可能大的吞吐量
美团技术团队
2018/03/12
2.8K0
缓存那些事
你应该知道的缓存进化史
本文是上周去技术沙龙听了一下爱奇艺的Java缓存之路有感写出来的。先简单介绍一下爱奇艺的java缓存道路的发展吧。
用户5397975
2019/10/14
5660
Guava Cache用法介绍(极简版)
看了官方的关于Guava Cache的介绍,感觉太过于啰嗦,我个人是很不喜欢,看了好大半天也看不懂,直到翻到了一篇国内的文章才看懂,特此记录,以备查阅。
诺浅
2020/08/20
4.6K0
死磕 java集合之WeakHashMap源码分析
WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。
彤哥
2019/07/08
4350
死磕 java集合之WeakHashMap源码分析
缓存那些事
导语:在网络分层应用服务中,缓存的使用已比较普及,本文将结合作者实际工作经验总结,讲述在不同的场景下如何选择和使用适用的缓存框架,以达到提升服务质量,优化系统架构的目的。 一般而言,现在互联网模式(一个网站或一个应用),整体流程可以概括描述为 浏览器→应用服务器→数据库或文件(存储)→应用服务器→浏览器,这是一个标准流程,通过浏览器(或App界面)发起请求,经过服务器、数据库计算整合后反馈浏览器呈现内容。随着互联网的普及,内容信息越来越复杂,使用者和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们
CSDN技术头条
2018/02/12
1K0
缓存那些事
相关推荐
Guava 源码分析(Cache 原理【二阶段】)
更多 >
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档