ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

技术派学习

技术派学习 1. 导入项目1.1 架构方案设计1.1.1 业务模块拆解角色拆解社区系统的用户角色权限管理员用户读者。读者阅读文章用户编辑发布修改文章。管理员整个系统运营管理如标签、分类管理、文章审核等。基于以上分析用户可以分为普通用户社区的注册用户围绕文章主体展开其覆盖的业务功能点管理员作为官方角色负责整个社区的生态运营。业务拆解整个社区系统按照业务边界进行初始化分用户、文章、评论、专栏、消息通知1.1.2 模块交互设计整体交互设计登录交互设计消息通知方案1.1.3 整体架构方案1.2 项目本地编译运行1.2.1 空文件右键GitBash加载源码1.2.2 修改配置文件 ymlpaicoding-web/src/main/resources-env/dev目录下的配置文件application-dal.yml修改MySQL和Redis的用户名和密码 redis不能是localhost只能是192.168.203.128运行后出现命令行过长的提示1.2.3 运行knife4j1.2.4 别人的代码clone后push到自己Gitee1.3 项目部署Gitee git cloneGithub git clone2. 基础篇2.1 项目的MVC分层架构2.2 Redis实现用户活跃排行榜用户活跃度的排行榜主要基于Redis的ZSET数据结构来实现。幂等 干过一次就不要再干第二次。就是“同样的操作只会生效一次”比如今天登录加过 5 分了就不能再加同样的 5 分。对于上面的流程使用redis的zrevrange方法倒叙展示前30名更方便。昨天收藏今天取消收藏业务失效是否算存在bug不算如果用户上线取消了收藏发现活跃度降低不符合实际要求因为用户活跃度排行榜的存在就是为了增加用户黏性。2.3 Redis实现作者白名单作者在白名单里文章直接上线无需审核不在白名单里文章进入审核流程审核通过后再上线目前本项目白名单成员是通过管理员手动添加并非自动添加。自动添加的思路2.4 Redis实现计数的业务场景2.5 项目中缓存的使用2.5.1 Redis缓存2.5.2 CaffeineSpringCache本地缓存2.5.3 缓存更新策略Spring Cache 注解配合 Redis/Caffeine 做方法级缓存2.6 项目中事务的使用我们的技术派项目当然也用到了事务最能体现这一点就是发布文章它包含●文章表写入●文章详情表写入●文章标签●阅读记录声明式事务以 Transactional注解修饰方法的方式修饰位置在方法上添加注解在类上添加注解表示这个类的所有公共方法都支持事务roolbackfor: 不指定具体的异常时默认只有运行时异常才会触发事务回滚声明式基于AOP方式实现代码更简洁、使用更方便理解更容易编程式事务声明式事务有一个最明显的约束最小粒度为方法级别当我们希望在一个方法内的部分代码块进行事务约束时这个时候就可以用编程式事务了Autowired private TransactionTemplate transactionTemplate;编程式非常灵活完全由用户自己控制可以最小粒度的控制事务范围事务不生效的场景数据库引擎MySQL 的 MyISAM 引擎就不支持事务Innodb 支持事务类内部访问非直接调用不生效非直接访问带注解标记的方法 B而是通过普通方法 A然后由 A 访问 B私有方法在私有方法上添加 Transaction 注解也不会生效私有方法外部不能访问只能内部访问所以也不生效。异常不匹配注解默认只处理运行时异常。如果没有抛出运行时异常或不是 rollback 指定抛出的异常就不生效。没有被 Spring 管理的类类不是由 Spring 管理所以不会生成对应的代理类事务当然也不会生效。3. 进阶篇3.1 前台微信公众号自动登录整个登录是基于个人微信公众号来实现的。作为一个文章分享社区登录是基本的功能点很多的功能都要求登录之后才能继续比如发文、点赞、评论等。首先后端需要和前端构建一个半长连接当用户向公众号发送验证码之后微信公众平台会将用户发送的信息转发给服务端通过验证码来识别请求登录的用户身份找到对应的半长连接实现用户的自动登录跳转。我们项目中实现了一套基于微信公众号的扫码登录系统整个流程主要包含以下几个步骤前端初始化 用户访问登录页面时前端会与后端建立一个半长连接这是一种单向的长连接机制允许服务器向客户端推送消息。验证码生成 后端收到连接请求后会为用户生成一个唯一的验证码并将这个验证码与 用户的设备ID和SSE 连接一起存储在两个缓存中1️⃣ verifyCodeCache 存储验证码到SSE连接的映射。后续在登录时就可以通过验证码找到对应的 SseEmitter从而实现登录。2️⃣deviceCodeCache 存储设备ID到验证码的映射。验证码与设备ID绑定防止盗用验证码展示 后端通过SSE连接将验证码推送给前端前端展示给用户。用户扫码操作 用户通过微信扫描页面上的公众号二维码关注公众号并发送验证码微信服务器回调 当用户在微信中发送验证码时微信服务器会向我们配置的回调URL发送一个POST请求包含用户发送的消息内容。验证与登录 后端接收到回调请求后会解析XML格式的消息内容提取验证码然后进行验证。如果验证码有效即在缓存中找到了对应的SSE连接系统会1️⃣会根据用户ID生成一个会话标识sessionid用于标识用户的登录状态2️⃣通过之前建立的SSE连接将会话信息推送给前端3️⃣前端接收到登录成功的消息后保存会话信息并刷新页面完成登录关闭SSE连接并从缓存中移除验证码这样可以确保验证码只能使用一次提高安全性。安全机制 整个流程中我们实现了多重安全保障验证码有5分钟的有效期SSE连接有15分钟的超时时间验证码与设备ID绑定防止盗用验证码一次性使用登录成功后立即失效技术上我们主要使用了Spring的 SseEmitter 实现服务器推送使用Guava Cache管理验证码和连接的映射关系通过微信公众号的消息回调机制实现用户验证。优点对于用户而言登录方式简单无需记忆密码、用户名有微信即可缺点企业公众号可以实现扫码之后直接自动登录无需输入验证码。个人公众号不支持自定义二维码参数因此还需要输入验证码这一步骤操作麻烦了一点微信公众平台配置因为个人公众号能使用的微信公众平台功能较少主要就是接收用户的发送信息所以需要的配置也不多直接登录后台开启服务器相关配置。微信公众平台接入验证服务器这段代码的作用是验证你的服务器。当你在微信公众平台配置服务器URL时微信会发送一个GET请求到这个URL请求中包含一个 echostr 参数。你的服务器需要原样返回这个参数值以证明这个URL确实是你控制的。微信消息回调处理除此之外需要接收微信公众平台的回调注意微信公众号采用的是 xml 进行通讯。半长连接的建立SSEServer-Sent Events是服务器单向推送数据给浏览器的一种机制也是一种“长连接”方式但比 WebSocket 更轻量适合做实时推送但不要求客户端反向通信的场景使用场景实时通知、消息提醒、系统日志推送、秒杀倒计时、天气推送等半长连接映射关系是指在我们的微信公众号登录系统中使用 SSE 技术建立的一种单向长连接通信机制。与WebSocket这种双工长连接不同SSE是一种单工的长连接只允许服务器向客户端推送消息客户端不能通过这个连接向服务器发送消息。具体来说我们的系统维护了两个关键的映射缓存1.验证码到SSE连接的映射 verifyCodeCache 2.设备ID到验证码的映射 deviceCodeCache 当用户通过微信公众号输入验证码后系统会通过验证码找到对应的SSE连接然后通过这个连接向前端推送登录成功的消息和会话信息从而实现自动登录。后台用户名密码登录当前项目针对前台和管理员的后台登录设置了两套不同的玩法前者是基于微信公众号后者则是传统的用户名密码虽然登录方式不同但底层的原理一致3.2 消息队列RabbitMQ当我们对文章进行评论点赞、收藏、评论或者管理员用户发送系统消息的时候那么对应的文章用户就可以实时的来接收到其消息。消息队列模式点对点模式Point to Point一个消息只能被一个消费者处理。发布/订阅模式Pub/Sub一个消息可以被多个消费者同时处理。3.2.1 配置与初始化配置文件 application-rabbitmq.yml 包含RabbitMQ的连接信息自动配置类 RabbitMqAutoConfig 负责初始化RabbitMQ连接池并启动消息消费进程。条件加载这个类只有在配置文件中开启rabbitmq.switchFlag的情况下才会加载。初始化连接池根据配置文件中的信息初始化 RabbitMQ 连接池。异步启动消息消费启动了一个异步线程去消费消息。每次使用 RabbitMQ 时都去 New 一个连接导致并发起不来所以这次我们就给 RabbitMQ 加一个连接池。3.2.2 连接管理连接池 RabbitmqConnectionPool 实现了连接池管理用于复用连接对象避免频繁创建和销毁连接。连接类 RabbitmqConnection 封装了RabbitMQ的连接创建和关闭操作3.2.3 服务接口与实现服务接口 RabbitmqService 定义了消息发布和消费的方法服务实现 RabbitmqServiceImpl 实现了消息的发布和消费RabbitMQ 发送消息从连接池拿到连接 - 创建通道 - 声明交换机 - 发送消息 - 将连接归还连接池。RabbitMQ 消费消息从连接池拿到连接 - 创建通道 - 确定消息队列 - 绑定队列到交换机 - 接受并消费消息 - 将连接归还连接池。4. 使用场景点赞操作 在 UserFootServiceImpl 中当用户点赞时系统会通过RabbitMQ发送消息消息处理 在 RabbitmqServiceImpl 的消费者中接收到消息后保存到数据库3.3 Mysql/Redis缓存一致性常见的更新缓存策略是先删缓存再更新数据库这时候另一个线程可能刚好查询了数据库的旧数据并写入缓存导致缓存又变成了旧数据数据回滚。而且数据库操作更费时很可能有其他线程进来。先更新缓存在更新数据库万一 DB 挂了你把数据写到缓存DB 无数据这个是灾难性的。先更新数据库在更新缓存会让无效写操作变多让缓存只在需要的时候去更新。第一种方式“先更新数据库再删除缓存”更适合低并发系统。1️⃣A 删除缓存是为了让后续请求走数据库并刷新缓存。2️⃣但由于 B 在 A 删除前把旧数据提前写了进去A 删除的就是那个旧值等于白删了3️⃣如果是低并发这个概率非常小基本不会遇到上面的问题因为更新数据库的时间更久删除缓存的时间短所以可以使用“先更新数据库再删缓存”的写法。第二种方式“延迟双删”即“先删除缓存再更新数据库延迟一段时间后再次删除缓存”更安全更适合高并发系统先写 MySQL通过 Binlog异步更新 Redis对于异地容灾、数据汇总等建议会用这种方式比如 binlog kafka数据的一致性也可以达到秒级纯粹的高并发场景不建议用这种方案比如抢购、秒杀等。实时一致性方案采用“先写 MySQL再删除 Redis”的策略这种情况虽然也会存在两者不一致但是需要满足的条件有点苛刻所以是满足实时性条件下能尽量满足一致性的最优解。最终一致性方案采用“先写 MySQL通过 Binlog异步更新 Redis”可以通过 Binlog结合消息队列异步更新 Redis是最终一致性的最优解。因为项目对实时性要求高所以采用延迟双删先写 MySQL再删除 Redis 的方式。3.4 Mysql/Redis缓存一致性之canal上篇文章中实战为了保证数据的实时性采用了“先写数据库再删除缓存”的方式。本文不追求数据实时性只追求最终一致性来实现利用了“先写数据库通过Binlog来异步更新缓存”方案。3.5 Canal实现MySQL和ES同步MySQL如何利用Canal中间件将数据同步至ES用 Canal 订阅 MySQL binlog 做增量实时投递到 ES辅以定时全量校验或用应用内事件/消息队列触发写 ES再配合定时任务兜底。这样兼顾实时性与一致性3.6 ES实现查询3.6.1 整体设计思路我们项目采用了双重搜索策略的设计。既支持Elasticsearch的高级全文检索也支持MySQL的基础搜索作为兜底方案。这样设计的好处是当ES服务正常时用户享受强大的搜索功能当ES不可用时系统自动降级到MySQL搜索保证搜索功能始终可用。3.6.2 配置和初始化首先在配置文件中我们通过elasticsearch.open这个开关来控制是否启用ES功能。配置包含了连接信息、认证、超时设置、连接池等参数。我们使用了Spring的条件注解ConditionalOnProperty只有当elasticsearch.opentrue时才会创建ES客户端Bean。这样在开发环境没有ES时可以直接关闭非常灵活。3.6.3 核心查询实现面试官您好关于ES核心查询实现我来详细介绍一下查询的核心逻辑在ArticleReadServiceImpl中。当用户搜索时系统会根据配置选择不同的搜索策略ES搜索流程ES启用使用MultiMatchQuery在文章的title和short_title字段中进行搜索。这种查询方式的优势是支持多字段同时匹配并且ES会根据匹配程度进行相关性评分排序。接着通过RestHighLevelClient发送请求到ES服务器我们配置了连接池来提高连接复用率。ES返回匹配的文章ID列表后系统再用这些ID从MySQL查询完整的文章信息。为了提升用户体验搜索提示功能限制返回10条结果并且使用AJAX异步调用不会阻塞用户的其他操作。降级机制如果ES未启用或查询失败系统会自动降级到MySQL的LIKE查询确保搜索功能始终可用。3.6.4 数据存储策略我们采用了分离存储的设计ES主要存储用于搜索的关键字段如标题和短标题而MySQL存储文章的完整信息包括内容、作者、统计数据等。查询时先从ES获取匹配的ID再从MySQL获取完整数据。这样既发挥了ES的搜索优势又保证了数据的完整性。项目配置目前我们使用的是单节点ES部署配置了详细的连接参数包括超时时间、连接池大小等。通过配置开关可以灵活控制ES功能的启用这样在开发环境或ES维护时可以直接使用MySQL搜索。这种设计既保证了搜索的高性能又确保了系统的高可用性。3.7 Redis分布式锁3.8 xxl-job实现定时任务
返回列表