# txing **Repository Path**: rika00/txing ## Basic Information - **Project Name**: txing - **Description**: 同行组团 项目采用微服务架构,根据对业务类型、流量等各方面的考量,系统拆分为用户、组团、通讯、商品、秒杀等多个服务,各服务各司其职,提高系统的稳定、可靠性。 同时 ,该项目也支持单体部署,通过修改简单的配置,即可实现。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-08-07 - **Last Updated**: 2025-08-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # TXing ![LOGO.png](picture/LOGO.png) ### 项目简介 在我们日常生活中,在各个方面都会存在着许许多多的组团需求,然而,找不到理想的“合伙人”却是很多人的一大烦恼!因此,我们致力于打造一个可以解决该问题的平台, 平台会分为三大组团专区:学习区、生活区、娱乐区,组团涉及面广,以满足更多用户的需求! 项目主要功能模块有 - 组团模块:旅行、运动、创业、参赛等各种事情均可发起组团,同时也可申请加入他人的团队 - 通讯模块:用户可以互相沟通了解, 每个团队也会有自己独立的群聊空间 - 电商模块:项目中与电商相结合,包括商品购买、秒杀等,以后可实现为用户推荐组团活动内容相关的商品 ### 项目架构 采用微服务的架构,根据对业务类型、流量等各方面的考量,系统拆分为用户、组团、通讯、商品、秒杀等多个服务,各服务各司其职,提高系统的稳定、可靠性。 同时 ,项目支持单体部署,通过修改简单的配置,即可实现。 项目架构图如下: ![项目架构图.png](picture/项目架构图-导出.png) ### 服务规划 | 服务 | 服务名 | 端口 | 表名前缀 | | --- | --- | --- | --- | | 网关 | tx-gateway | 88 | - | | 用户服务 | tx-users | 10001 | tx_ums_ | | 组团服务 | tx-group | 10002 | tx_gms_ | | 聊天服务 | tx-chat | 10003 | tx_cms_ | | 第三方服务 | tx-third-party | 10004 | tx_tms_ | | 检索服务 | tx-search | 10005 | - | | 库存服务 | tx-mall-ware | 10006 | tx_wms_ | | 秒杀服务 | tx-mall-seckill | 10007 | tx_sms_ | | 商品服务 | tx-mall-product | 10008 | tx_pms_ | | 支付服务 | tx-mall-payment | 10009 | tx_pyms_ | | 订单服务 | tx-mall-order | 10010 | tx_oms_ | | 购物车服务 | tx-mall-cart | 10011 | tx_ctms_ | ### 主要技术及项目体现 | 名称 | 描述 | 项目体现 | | --- | --- | --- | | Springboot(Web) | - | 用于搭建各服务 | | JUC | Java并发工具包 | (1)在接口中实现以异步、并行的方式去获取多个数据提高响应速度(2)线程池,使用线程池处理异步任务等 | | Mysql | 数据库 | 用于数据的持久化 | | Mybatis-Plus | 持久层框架 | 完成对数据库的操作 | | Nacos | - | 用作项目的注册中心、配置中心 | | SpringCloud GateWay | 网关 | 用作网关,负载均衡请求各子服务 | | Seata | - | 解决项目中分布式事务的问题 | | ElasticSearch | 搜索引擎 | 用于对组团数据以及商品数据的检索,提交检索体验 | | Openfeign(Ribbon) | - | 实现项目中各服务间的远程访问 | | Sa-token | 认证授权框架 | 用于项目中认证与鉴权 | | Table-creator | 数据库表自动生成器(自研框架) | 用于开发中根据实体类一键生成数据库表,提高开发效率 | | Redis | 非关系型数据库 | (1)用于缓存热门访问数据,例如三级分类、秒杀商品 (2)存储用户登录状态、Session等,实现分布式Session(3)用于存储购物车、聊天记录等(4)使用Set数据类型实现点赞功能 | | Redisson | | (1)信号量机制实现秒杀(2)实现布隆过滤器(3)分布式锁的实现 | | RabbitMq | 消息队列 | (1)在秒杀中使用RabbitMq实现流量削峰(2)秒杀中使用RabbitMq实现异步创建订单,提高并发量(3)实现RabbitMq延时队列自动关闭超时订单以及解锁库存 | | Sentinel | - | 在项目中用其实现流量控制、熔断降级,当某远程服务不可用时,可及时熔断,不至于把整个系统拖垮,保证高可用 | | Sleuth、Zipkin | - | 实现对系统的链路追踪和监控 | | Oss | 阿里云对象存储 | 主要用于存储图片 | | Netty、Websocket | - | 实现用户的通讯 | | Fastjson | JSON解析框架 | 实现Java对象与JSON之间的互相转换 | | Docker | | (1)用于安装与部署项目环境,包括MySQL、Redis、ElasticSearch等(2)用于部署项目 | | Maven | | 构建与管理项目 | | Git | | 用于项目的版本管理 | ### 部署说明 项目采用微服务的架构,但是同时支持**分布式部署**以及**单体部署**两种方式,可以适用不同的应用情况和需求 #### 微服务部署 因项目采用了微服务架构,因此微服务部署即正常部署即可,无需做特殊配置 #### 单机部署 原理:把其他各个服务都引入到tx-group服务当中,最终部署tx-group服务即是部署了整个项目 操作步骤(均是针对于tx-group服务操作即可): 1. 修改yaml部署方式配置 ```yaml tx: deploy: # mode: cloud # 集群部署 mode: standalone # 单机部署 ``` 2. 修改包扫描路径以及排除Seata相关的自动配置 1. 说明一: 把包扫描路径改为"com.bitdf.txing.scanconfig"即可扫描到其他服务的需要注册的组件,至于其他服务需要哪个组件被注册,由其他服务自行指定,本服务无需理会;在微服务部署时,就改为"com.bitdf.txing.group",tx-group服务只需要扫描其本身即可。 2. 说明二:由于单体部署不需要处理分布式事务的问题,因此把Seata相关的自动配置类去除掉。 ```java @SpringBootApplication(scanBasePackages = {"com.bitdf.txing.scanconfig"}, exclude = {SeataAutoConfiguration.class, SeataFeignClientAutoConfiguration.class}) ``` 3. 数据库处理,可以把其他服务的数据库表都整合到tx-group服务的数据库当中,也可以分库、配置多数据源等 4. 至此,即可单机部署,只需运行tx-group服务即可 ### 认证与鉴权 #### 用户访问鉴权 由于在微服务系统下,采用传统的本地Session会有服务间数据不一致的问题,因此项目中的用户Token以及Session数据都由Redis进行集中管理,实现服务间的数据一致。同时项目中的用户分设超级管理员、会员、普通用户等多级角色,对各级角色进行了具体的权限绑定,实现接口级的权限管理。 #### 网关转发鉴权 本项目架构中,由网关来作为中间人,负责将请求负载均衡地转发到对应的子服务,用户不可直接访问子服务。因此我们需要采取一定的措施,来保证用户无法绕过网关直接访问到内部服务。常用的方法有以下两种: - 物理隔离:子服务直接在内网部署,只有网关对外网开发 - 逻辑隔离 :子服务也可以部署在外网,但在处理请求时,其会对请求采取一定的鉴权措施。 本项目中实现了逻辑隔离,做法是在网关向子服务发送请求时,会在请求头带上一个Token,子服务接受到请求时,先校验这一Token是否合法,只有合法情况下才会放行,否则拦截,该Token也是由Redis进行存储,并且会定期更新。 由于用户不知道此Token的值, 因此也无法绕过网关访问子服务了。 #### 微服务之间鉴权 除了网关,子服务之间的访问也一样需要鉴权,和以上方案同理,在子服务发送请求时添加Token的Header,接收方校验即可。 注:本项目的认证与鉴权借助了SaToken开源框架进行实现 ### 组团数据持久化 在项目中,为了提高组团检索的体验以及效率,使用了ElasticSearch作为搜索引擎,因此,我们需要一种机制来同步ES和Mysql的数据,常用方案如下: - 采用双写策略 即更新Mysql的同时也更新ES - 使用定时器 更新数据只更新ES中的数据,使用定时器将ES中数据定时同步到Mysql - 使用Canal + Kafka进行同步 本项目主要采用了前两种方案,即在用户创建组团的时候采用双写策略,而对于组团的更新操作,例如收藏数、评论数的改变,就仅仅更新ES,然后使用定时器凌晨同步数据到Mysql 那当定时器执行同步,当组团数量很庞大的时候,我们要查出那么多的数据进行更新数据库,这种方案可行吗? 答:为了解决这一问题,项目中采用了两种优化方法: (1)项目中给ES中组团数据添加了修改标志位字段,在同步时,我们可以通过该字段查出真正被修改的组团,修改组团并不是频繁的操作,因此这样查出来的数据并不会很多 (2)在查出ES中组团数据时,采用分页查询的方式,避免数据太大,容易发生远程传输失败等问题。 ### 通信模块 项目中主要使用Netty + WebSocket实现了用户的即时通讯。 #### 消息类型 | 标识 | 类型 | 描述 | | --- | --- | --- | | single_sending | 私聊消息 | | | group_sending | 群聊消息 | 在项目中会为每个组团自动创建一个群聊,群聊成员即为目前该组团的团队成员 | | group_request | 申请加入组团通知 | 申请加入组团时同时会以消息的方式实时通知对方 | | agree_or_reject_group_request | 同意/拒绝组团申请通知 | | | add_friend_request_reply | 申请加好友消息 | | | agree_or_reject_friend_request | 同意/拒绝加好友通知 | | | REGISTER | 注册消息 | 当用户进聊天室,前端就会发送该类型消息到服务器端,服务器端保存在线状态 | | read_reply_sending | 对方已读消息反馈 | | #### 聊天记录持久化 由于用户对于聊天记录的查询较为频繁,因此为了提高响应速度以及减低数据库的压力,项目中使用Redis存储用户当天的聊天记录,使用定时器凌晨把聊天记录同步到Mysql。 同时,在将聊天记录同步到Mysql后,并不会完全清空Redis中用户当天的聊天记录,而是保留最新的15条记录,这样做主要是预防会有过多用户同时打开与好友的聊天界面,以致于大量的请求打到数据库,数据库会不堪重负! #### 聊天记录的获取 当用户刚打开聊天界面时,仅会为其返回与好友的最新15条聊天记录,若用户继续查看更早的聊天记录,前端向后端请求,同时带上当前前端已拿到的聊天记录中最早一条的时间(时间戳),后端去查询比该时间戳更早的并且最新的15条记录返回,当Redis查完就会去Mysql中查询。 ![用户通讯.jpg](picture/用户通讯.jpg) ### ### 布隆过滤器的应用 项目中使用了布隆过滤器,使得我们可以快速判断一个组团是否存在,对于查询不存在的组团的查询请求,可以快速地通过布隆过滤器进行判断以及过滤,而无需真正到Redis以及ES中查询,这样做的好处主要有以下两点: - 预防缓存穿透 - 减少系统压力 布隆过滤器在本项目中的应用如下图所示: ![布隆过滤器-导出.png](picture/布隆过滤器-导出.png) ### 秒杀实现 项目中对于秒杀的实现,主要是通过使用 MQ 进行流量削峰,使用Redission 信号量机制执行秒杀,再通过MQ 异步生成订单等实现高并发秒杀,本地测试可达 850QPS,秒杀流程图如下所示 注: 1. 对于秒杀而言,850QPS算不上是一个可观的并发量,但这主要是受限于本人的笔记本配置,因为测试时笔记本上跑着好几个微服务以及RabbitMq、Mysql、Redis、Nacos、压测工具等等应用,因此测试数据仅供参考,在实际环境并发量不止如此! 2. 此处只给出了秒杀实现的大致流程,实际上秒杀过程中需要解决的问题还有很多,例如防止链接暴漏(项目中采用随机码解决)、前端秒杀按钮秒杀前置灰等等细节上的优化,更多细节请参考项目中具体实现。 ![秒杀-导出 (2).png](picture/秒杀-导出.png) ### 数据一致性问题 在项目中很多地方都有可能会出现数据不一致的情况,因此在项目也对这些问题采用了相应的解决方案。 #### 布隆过滤器的重置 布隆过滤器的重置,在项目中指的是将布隆过滤器的数据清空,再去查出所有组团id,再添加到布隆过滤器中,达到重构的效果。之所以需要重置,主要是因为随着组团数量的增大,布隆过滤器的误判率也会越来越大,因此我们需要给布隆过滤器设置更大的空间,以减低误判率,当然,我们也可以根据对组团数据的增速,一开始就为其设置很大的空间,那么我们在短时间内都不用再重置。 项目中目前采用定时器的方法来重置,每天重置一次,每次增加2000容量。 那么哪里会出现数据不一致的情况呢? 假设定时器要去重置布隆过滤器,它先去ES查出(分页)组团数据,查出数据后 这个时候定时器还没来得及重置布隆过滤器,而此时刚好有别的用户新建了组团,并添加到了布隆过滤器,之后定时器再次抢到时间片,对布隆过滤器的数据进行清除重置,这样就有可能会导致新添加的组团没成功添加到最新布隆过滤器当中! 解决方案: 项目中的做法是当定时器执行时,第一步先把布隆过滤器中的数据清空了,然后再去ES查询组团,这样一来,尽管在这过程中别的用户新建组团,该组团也是被添加到最新的布隆过滤器里,并不会出现漏添的情况。 #### Redis中热门组团的更新 由于用户对于一些较为热门的组团 查询频率较高,因此为了提高响应速度以及减低系统的压力,所以会把较为热门的组团缓存到Redis当中,然后使用定时器每30分钟更新一次, 而在更新的过程中,要注意的是同时可能也会有用户会更新自己的组团,更新操作也会对Redis中的热门组团进行检查与更新,如果用户更新操作发生在定时器向ES查出最新的热门组团之后、把热门组团更新到Redis之前的话,那么就会导致Redis中的数据不是最新的,因此出现了数据不一致的情况,在项目也是不可容忍这种情况的的发生的! 本项目中采用的解决方案 如图所示: ![更新热门组团-导出.png](picture/更新热门组团-导出.png) #### 分布式事务问题 由于我们采用了微服务的架构,我们在完成一个操作时可能需要远程调用多个服务来进行完成,因此,传统的本地事务已经不能满足我们的需求了,我们需要 分布式事务 来保证数据的一致性。 项目中根据不同的场景,采用了不用的分布式事务解决方案: - 对于并发量不大的操作,例如后台商品上架等操作,采用了Seata框架来实现分布式事务,保证数据的强一致性; - 对于并发量可能较大的操作,例如下订单等操作,主要使用了RabbitMq消息队列实现柔性事务,保证数据的最终一致性,这样可以减低系统性能损耗,提高并发量。 例如:订单服务在下订单的时候,会调用远程的商品、库存等服务,在库存服务成功扣减库存后,会向Mq发送一个消息,并且设置消息有效时间30分钟,程序并不会去消费该消息,而是等超过了30分钟,该消息就会变成死信,从未被发送到死信队列,此时库存服务就会监听到这一死信,并且开始处理,检查订单状态,根据情况进行关释放库存,从而保证数据的一致性,这种方式相对于强一致性来说,性能消耗会更小! > 关于项目中的技术说明,将会持续更新!