聊聊缓存穿透

在我们的项目中多少都会使用缓存,因为有些数据我们没有必要每次都去查询数据库,特别是高QPS的系统,每次都去查询数据库会影响数据库性能。

业务系统一般调用流程:

我们一般的做法是先从缓存中查询,如果缓存中查询到了则直接返回,如果缓存中没有,则查询数据库,如果从数据库中查询到了,则先将数据写入缓存,然后返回数据给调用方。

public Result<Post> queryPostFromCache(Long id) {
    // POST:id
    final String key = Constant.Cache.POST + Constant.COLON + id;

    // 从缓存中查询
    Object post = cacheService.get(key);
    if(post != null) {
        return Result.success((Post) post);
    }

    // 从数据库中查询
    post = postMapper.queryPostById(id);
    if(post != null) {
        cacheService.set(key, post, Duration.ofHours(1));
    }
    return Result.success((Post) post);
}

正常情况下,我们查询的数据都是存在的,比如从文章列表页面跳转到文章详情页查询文章详细信息,此时根据文章ID查询,文章的ID是真实存在的。不正常的情况是如果请求查询的文章ID是一条根本不存在的数据,也就是说缓存和数据库中都不会有值,这会导致请求每次都会到数据库中查询。这种查询不存在数据的现象我们称之为缓存穿透

缓存穿透是指用户查询的数据在数据库中没有,那么在缓存中也不会有,也就是说查询的是一条根本不存在的记录。这样就会导致用户首先在缓存中找不到 ,则每次都要去数据库再查询一遍,然后返回空。这样就相当于进行了两次无用的查询。要是有人利用这种不存在的key频繁攻击我们的系统,很可能导致数据库压力增大,甚至导致数据库挂掉。

解决方案

1.将未在数据库中查询到值的key也写入缓存,缓存的值为空对象,同时设置一个较短的过期时间,以防止后面真的有数据。

2.采用布隆过滤器(Bloom Filter),使用一个足够大的bitmap,用于存储数据库中可能访问的key。布隆过滤器可以理解为一个不太精确的set集合,当你使用它的contains方法判断某个对象是否存在时,它可能会误判。它的特点是当布隆过滤器判断某个值存在时,这个值可能不存在(误判),当它判断某个值不存在时,那就肯定不存在。这个特点可以用在判断要查询的key是否存在,如果判断不存在时,那就肯定不存在,这个时候就不用再查询数据库了,直接返回给调用方值不存在。

使用空对象解决缓存穿透问题的示例代码


public Result<Post> queryPostFromCache(Long id) {
    // POST:id
    final String key = Constant.Cache.POST + Constant.COLON + id;

    // 从缓存中查询
    Object post = cacheService.get(key);
    if(post != null) {
        // 判断缓存中的值是否为默认对象
        if(post instanceof NullObjectValue) {
            return Result.fail(ResultCode.RESOURCES_NOT_FOUND);
        }
        return Result.success((Post) post);
    }

    // 从数据库中查询
    post = postMapper.queryPostById(id);
    if(post != null) {
        cacheService.set(key, post, Duration.ofHours(1));
    }else {
        // 数据库中没有对应记录,给这个key缓存设置一个默认值
        cacheService.set(key, new NullObjectValue(), Duration.ofHours(1));
    }

    return Result.success((Post) post);
}

有关使用布隆过滤器来解决缓存穿透问题的具体实现,我将会在下篇文章中介绍。

<think>嗯,用户这次想了解用Spring Boot开发微信群聊机器人的具体方法。结合用户之前的查询历史,ta应该是个Java开发者,正在探索企业级机器人应用的实现方案。 从用户提供的三个引用来看,信息点比较分散:引用[1]侧重企业微信官方接口集成,引用[2]聚焦群机器人基础功能,引用[3]却提到微信开发者工具和小程序配置(这其实属于个人微信机器人范畴)。需要帮用户厘清这两个关键差异点:企业微信群机器人和个人微信群机器人实现路径完全不同。 用户可能没意识到,企业微信的群机器人只需要Webhook调用,而个人微信号自动化需要逆向工程或微信官方API(后者对普通开发者限制很多)。考虑到用户提到Spring Boot,应该优先推荐企业微信方案——更合规也更稳定。 上次回答时已经给出基础架构图,这次应该深化具体实现步骤。特别要注意提醒用户企业微信的安全限制:每个机器人每分钟最多发送20条消息,消息内容需要转义HTML特殊字符。对于文本消息的markdown支持也是个实用功能点,用户可能会需要。 注意到用户引用的资料里有提到消息规则配置(如关键词回复),这个在企业微信机器人里需要自行实现逻辑匹配。可以补充说明如何设计可扩展的规则引擎,比如用策略模式封装不同关键词处理器。 文件上传部分是个技术难点。企业微信要求先上传到临时素材库获取media_id,这个过程中需要处理multipart/form-data请求和响应解析。示例代码里展示的FormDataHttpMessageConverter很关键,很多开发者容易忽略这个细节准备阶段要强调网络要求:企业微信API必须使用公网IP或备案域名,本地测试需要用内网穿透工具。另外access_token需要全局缓存,避免每次调用都刷新——这个性能优化点值得展开 最后关于消息类型,除了示例的文本和文件,其实还支持图文卡片(news_template)和任务卡片(taskcard),这对企业场景更实用。不过考虑到用户初次接触,先聚焦基础文本功能更合适 </think>根据企业微信官方接口规范,使用Spring Boot开发微信群聊机器人主要分为两种方式:应用消息推送和群机器人Webhook调用。以下是具体实现方案和技术要点: --- ### 一、企业微信群机器人(Webhook方式) #### 技术实现步骤 1. **创建群机器人** ```mermaid graph LR A[企业微信工作台] --> B{目标群聊} B --> C[右上角菜单-添加群机器人] C --> D[设置机器人名称] D --> E[获取Webhook地址] ``` 2. **Spring Boot集成** ```java // 消息实体 @Data public class RobotMsg { private String msgtype = "text"; private Text text; @Data public static class Text { private String content; private List<String> mentioned_list; // @成员列表 } } ``` 3. **消息发送服务** ```java @Service public class WechatRobotService { private final RestTemplate restTemplate = new RestTemplate(); public void sendText(String webhookUrl, String content) { RobotMsg msg = new RobotMsg(); RobotMsg.Text text = new RobotMsg.Text(); text.setContent(content); msg.setText(text); restTemplate.postForObject(webhookUrl, msg, String.class); } } ``` #### 支持的消息类型 | 类型 | 特性 | 适用场景 | |------------|---------------------------|-----------------------| | 文本 | 支持@成员 | 普通通知 | | Markdown | 支持富文本格式 | 复杂消息排版 | | 文件 | 需先上传素材获取media_id | 文档/图片分享 | | 图文卡片 | 标题+描述+跳转链接 | 系统报警/报告推送 | > ⚠️ 限制:每个机器人发送频率上限20条/分钟[^1] --- ### 二、企业微信应用消息(API方式) #### 开发流程 1. **企业微信后台配置** - 创建应用 → 获取`CorpID`、`AgentID`、`Secret` - 配置应用可信域名(需备案) - 设置消息接收权限 2. **AccessToken获取** ```java public String getAccessToken(String corpId, String secret) { String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + corpId + "&corpsecret=" + secret; JsonNode res = restTemplate.getForObject(url, JsonNode.class); return res.get("access_token").asText(); } ``` 3. **发送应用消息 ```java public void sendAppMsg(String userId, String content) { String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; JsonObject msg = new JsonObject(); msg.addProperty("touser", userId); msg.addProperty("msgtype", "text"); msg.addProperty("agentid", agentId); msg.add("text", new JsonParser().parse("{'content':'" + content + "'}")); restTemplate.postForObject(url, msg, String.class); } ``` --- ### 三、高级功能实现 1. **文件上传接口** ```java public String uploadFile(File file, String accessToken) { String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=" + accessToken + "&type=file"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); body.add("media", new FileSystemResource(file)); ResponseEntity<JsonNode> response = restTemplate.exchange( url, HttpMethod.POST, new HttpEntity<>(body, headers), JsonNode.class); return response.getBody().get("media_id").asText(); } ``` 2. **加密消息处理(需配置EncodingAESKey)** ```xml <!-- pom.xml 添加加解密依赖 --> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-cp</artifactId> <version>4.5.0</version> </dependency> ``` --- ### 四、部署注意事项 1. **网络要求** - 服务器需配置公网IP/域名 - 本地开发建议使用内网穿透工具(Ngrok/Sunny-Ngrok) 2. **安全机制** - Webhook地址需保密(包含敏感权限) - AccessToken有效期2小时需缓存 - 接收消息需验证MSG签名 > 📌 最佳实践:建议使用Spring Boot Actuator监控消息发送状态,结合Redis缓存AccessToken[^2] --- ### 五、技术选型对比 | 方式 | 开发难度 | 消息能力 | 安全性 | 适用场景 | |--------------|----------|----------------|------------|------------------| | 群机器人 | ⭐☆ | 基础消息 | 依赖Webhook| 简单通知 | | 企业微信应用 | ⭐⭐⭐ | 完整API能力 | OAuth2认证 | 企业级系统集成 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值