第一时间收到文章更新
来源丨王中阳Go(ID:wangzhongyanggo)
朋友在成都某自研公司的一面面经,薪资为15-22K*13,下面是这个岗位的JD:
【岗位职责】
1、梳理业务,完成核心业务模块;
2、负责后台系统设计、管理和服务端开发;
3、代码性能优化,确保线上业务的稳定性和架构合理性;
4、对研发中疑难技术问题提供有效解决方案并主导实施改进和优化。
【任职需求】
计算机或通信相关专业; 5年以上Golang开发经验; 熟悉beego,gin,iris,echo,gomicro,grpc至少一种Go语言下的高性能Web框架; 熟悉MySQL、MongoDB , Kafka, AFlink, Nosql,elasticsearch, Prometheus, grafana, tidb等多种以上数据库,并具有较好的SQL优化知识; 熟练掌握TCP、HTTP、UDP等网络协议; 熟悉 protobuf,Json; 熟悉AES、DES、RSA:DSA 等加密协议的使用; 熟悉etcd、nats、redis、rpc等相关技术; 熟悉docker相关技术; 参于分布式项目者优先。
2. 怎么理解微服务
微服务架构是一种设计模式,它将单一应用程序拆分为一组小型、独立的服务,每个服务实现特定的业务功能,并通过轻量级通信机制(通常是HTTP/REST API)进行交互。与传统的单体应用相比,微服务具有以下特点:
解耦:服务之间保持低耦合,高内聚。 独立部署:每个微服务可以独立开发、测试、部署和扩展。 技术多样性:不同的微服务可以根据需求使用最适合的技术栈。 容错性:即使一个服务失败,其他服务仍然可以正常工作,提高了系统的整体稳定性和可用性。 可扩展性:易于水平扩展以应对增长的负载。
同时微服务也带来了复杂性增加的问题,比如分布式系统的管理、服务间通信、数据一致性等。因此,需要引入诸如服务发现、配置管理、API网关、熔断器等中间件来解决这些问题。
3. redis库存超卖解决除了分布式锁还有什么其他方式
Lua脚本
Lua脚本的优势:
原子性操作:Lua脚本在Redis中是原子执行的,这意味着在一个Lua脚本内的所有命令都会连续执行,不会被其他客户端请求打断。 减少网络往返:由于Lua脚本是在服务器端执行,减少了客户端与服务器之间的交互次数,提高了效率。
乐观锁
乐观锁的工作原理:乐观锁假设并发冲突很少发生,因此它不会事先锁定资源,而是在更新时检查是否有其他事务已经修改了数据。如果发现冲突,则拒绝当前的更新请求。
数据库表设计:可以在商品表中添加一个版本号字段,在每次更新库存的同时也更新版本号。当用户尝试购买商品时,先获取当前的版本号,然后在更新库存时验证版本号是否一致,如果不一致说明有其他用户已经修改了库存信息。
消息队列
消息队列的作用:消息队列可以帮助平滑流量高峰,确保即使在高并发情况下也能有序地处理订单请求。通过将下单请求放入消息队列中,系统可以在后台异步处理这些请求,从而避免直接面对大量的并发访问压力。
具体流程:
用户提交秒杀请求后,系统将该请求封装成一条消息发送到消息队列中。 后端服务从消息队列中取出消息并依次处理每个请求,包括检查库存、创建订单等操作。 如果库存不足或者出现任何异常情况,系统可以直接回滚相关操作,保证数据的一致性。
技术选型:可以选择Kafka、RabbitMQ等成熟的消息中间件来构建这样的系统架构。
批量处理
批量处理的意义:对于大型促销活动,可以通过预先分配库存或按照一定策略分批释放库存来应对瞬间爆发的巨大流量。这种方法能够更好地控制销售节奏,同时也降低了因短时间内大量请求导致的系统负载。
实施步骤:
提前根据预测销量准备足够的库存,并将其划分为若干批次。 在活动开始前设定好每一批次开放的时间点,以及相应的库存数量。 活动期间严格按照预定计划逐步释放库存,确保每个阶段都有适量的商品可供抢购。
4. 数据ES同步流程
Elasticsearch (ES) 数据同步通常涉及以下几个步骤:
源数据变更捕获:通过监听数据库的变化日志(如MySQL的Binlog),捕捉到新增、更新或删除的数据记录。 消息队列(MQ) :将这些变更事件发送到消息队列系统中,如Kafka、RabbitMQ等,以便异步处理。 消费端逻辑:编写消费者程序从MQ中读取消息,并根据消息内容对Elasticsearch执行相应的增删改操作。 错误处理与重试机制:确保即使在某些情况下同步失败也能自动恢复,不会丢失数据。 统计处理:在同步过程中可能会涉及到一些额外的统计计算,比如聚合查询结果、生成报表等,这部分可以在消费端逻辑里实现。
5. 为什么不选择成熟的go框架,类似goZero
快速上手:Gin是一个非常轻量且高性能的HTTP框架,学习曲线平缓,适合快速原型开发;gRPC则提供了高效的RPC通信协议,二者结合能够满足大部分Web服务的需求。 社区活跃度:Gin拥有庞大的用户群体和支持文档,遇到问题容易找到解决方案;而gRPC由Google开发,有着良好的性能表现和技术保障。 灵活性:虽然go-zero提供了很多开箱即用的功能,但对于特定项目来说,有时并不需要那么多内置特性,反而增加了不必要的复杂度。相比之下,Gin+gRPC组合更灵活,可以根据实际需要定制化开发。
6. 缓存一致性怎么解决
缓存一致性是分布式系统中一个常见的问题,尤其是在读写分离架构下。下面是几种解决缓存一致性问题的方法,以及它们的工作原理和应用场景:
1.延时双删
工作原理:
在更新数据库之后,首先删除缓存中的旧值。 然后设置一个短暂的时间延迟,再执行第二次删除操作。 这段时间窗口允许其他并发请求有机会读取到最新的数据库值并更新缓存,从而避免了直接覆盖新值的问题。
优点:
可以有效地防止脏读现象。 减少了频繁更新缓存带来的资源浪费。
缺点:
需要精确控制延迟时间,过短可能导致效果不佳,过长则增加了复杂度。 如果第二次删除失败,可能会留下不一致的数据。
2.更新缓存
工作原理:
每当数据库中的数据发生变化时,立即同步地更新对应的缓存项。 这种方式确保了缓存始终包含最新的数据副本。
优点:
提供强一致性保证,即一旦数据被更新,所有后续读取都将获得最新版本。
缺点:
对系统的性能有一定影响,因为每次更新都需要额外的网络调用来刷新缓存。 如果缓存更新失败,会导致不一致的状态。
3.失效策略
工作原理:
为每个缓存条目设定一个有限的生命期(Time To Live, TTL),当达到这个期限时,缓存自动失效。 下次访问该数据时,会重新从数据库加载最新的信息,并再次存储到缓存中。
优点:
实现简单,易于维护。 自然解决了长时间未使用的冷数据占用空间的问题。
缺点:
在高并发场景下,可能会导致“缓存击穿”,即大量请求同时尝试获取已失效的缓存数据,造成数据库负载骤增。 数据在一段时间内可能是陈旧的,不能满足对实时性要求较高的业务需求。
4.Cache Aside Pattern(旁路缓存模式)
工作原理:
读路径:查询时先检查缓存,如果命中则直接返回结果;若未命中,则从数据库读取数据,并将其放入缓存以便将来使用。 写路径:仅更新数据库,然后删除或使缓存中的相应条目失效。这使得下一次对该数据的读取将触发新的缓存填充过程。
优点:
分离了读写逻辑,简化了设计。 避免了不必要的缓存更新开销,特别是在那些写多读少的情况下。 能够较好地处理突发流量,因为它不会试图在每次写入时都更新缓存。
缺点:
存在一定的最终一致性窗口,在这段时间内,用户可能会看到稍微过时的数据。 如果缓存删除失败,可能会残留旧的数据副本。
5.消息队列驱动的异步更新
工作原理:
使用消息队列作为中介来异步通知缓存更新服务,每当数据库发生变更时,发布一条消息给消息队列。 缓存更新服务订阅这些消息,并根据消息内容进行相应的缓存更新或删除操作。
优点:
解耦了业务逻辑和缓存管理,提高了系统的可扩展性和灵活性。 支持分布式部署,适用于大规模、复杂的系统架构。
缺点:
引入了额外的技术栈(如Kafka、RabbitMQ等),增加了系统的复杂性和运维成本。 存在一个短暂的不一致时间段,直到缓存更新完成为止。
6.基于Binlog的日志订阅
工作原理:
订阅数据库的二进制日志(Binlog),通过监听SQL语句的变化来触发缓存的更新或删除。 将消费服务配置为MySQL的一个slave节点,解析binlog中的更新事件,并据此调整缓存状态。
优点:
与数据库的操作紧密集成,几乎可以实现实时同步。 不需要修改业务代码,减少了侵入性。
缺点:
构建和维护这样的基础设施可能较为复杂。 性能取决于MySQL的压力水平以及日志解析的速度。
8. 消息重复怎么解决
针对消息重复的问题,常见的解决办法包括:
幂等性设计:确保接收方对同一消息多次处理的结果是一致的。比如,基于唯一标识符(如UUID)判断是否已经处理过某条消息。 布隆过滤器 + Redis:布隆过滤器是一种空间效率极高的概率型数据结构,用于检测元素是否存在集合中。它可以用来快速排除明显不属于的消息,降低Redis的压力。当然,布隆过滤器存在一定的误判率,所以在实际应用中往往还需要结合其他手段进行验证。 去重表:创建一张专门用于存储已处理消息ID的表,每次接收到新消息时先检查这张表,只有确认未曾出现过的消息才会被正式处理。这种做法虽然直观可靠,但是会占用较多存储资源并且影响性能。
推荐阅读:
推荐阅读: