场景题
1. 如何设计一个高并发高可用的Web系统?
这个问题我觉得可以从下面五个点回答:
- 微服务模块拆分
- 缓存
- MQ
- 分库分表
- 读写分离
1.1 模块拆分
通过Spring Cloud将一个系统拆分为多个子系统,也即微服务模块,然后每个微服务模块连接一个数据库,这样就有多个数据库同时处理并发业务。
同时通过微服务拆分业务模块有以下优点:
- 不同的微服务之间可以使用不同的技术;
- 隔离性。一个服务不可用不会导致其他服务不可用;
- 可扩展性。某个服务出现性能瓶颈,只需对此服务进行升级即可;
- 简化部署。服务的部署是独立的,哪个服务出现问题,只需对此服务进行修改重新部署;
1.2 缓存
这也是常见的一种优化方式,在数据库层之上加一层缓存,减少对数据库的访问压力。
因为缓存中的数据都是存储在内存里的,而数据库中的数据是写在磁盘上的,所以访问内存肯定是比访问磁盘快的可不止一个数量级。
大部分的高并发场景,都是读多写少,那完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存。毕竟 Redis 轻轻松松单机几万的并发。
所以那些主要请求是读多写少的高并发场景下,可以采用缓存来抗高并发。
1.3 MQ
用Redis可以解决读多写少的情况,那么大量写操作的情况下可以采用MQ解决,因为MQ的优点就是可以:流量削峰、异步处理。
当大量的写请求灌入 MQ 里,让这些请求一个一个排队慢慢玩儿,后台系统根据实际情况觉得消费速度,然后慢慢写,将并发量控制在 MySQL 承载范围的之内。
所以高并发场景下那些承载复杂写业务逻辑的场景里,如何用 MQ 来异步写,提升并发性,MQ 单机抗几万并发也是 ok 的。
1.4 分库分表
当数据量达到某个阀值时,数据库拆分就会成为一个紧急的需求。
一般从业务上进行垂直拆分,也即按照业务类型进行拆分。但如果业务单一,也可从水平上进行拆分。
拆分的原则一般要避免跨数据库事务。跨数据库事务可以选择在前期调研时把同一事务中的表放在一个数据库中。
将一个数据库拆分为多个库,多个库来扛更高的并发;然后将一个表拆分为多个表,每个表的数据量保持少一点,提高 SQL的性能。
1.5 读写分离
读写分离,这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上,可以搞个主从模式,主库写入,从库读取,搞一个读写分离。
当读流量太多的时候,还可以加更多的从库。
2.如何设计一个日志管理平台?
持久层(数据库层)设计:
- 数据库选择: 选择适合的数据库管理系统,如关系型数据库(如MySQL、PostgreSQL)或非关系型数据库(如MongoDB、Elasticsearch)等,根据日志数据的特性和需求来选择。
- 表设计: 设计适合存储日志数据的数据表结构,可以考虑分表、分区等策略,以提高查询性能。常见的字段包括时间戳、日志级别、模块、内容等。
- 索引优化: 根据常见的查询需求,添加合适的索引以提高查询效率。例如,根据时间范围、模块、关键字等查询。
- 数据清理: 定期清理过期的日志数据,避免数据库过大影响性能。
缓存设计:
- 缓存类型: 可以考虑使用缓存来加速频繁查询的数据。常见的缓存系统有Redis等。
- 缓存策略: 根据访问模式和数据更新频率,选择合适的缓存策略,如LRU(最近最少使用)、TTL(过期时间)、缓存预热等。
- 数据同步: 如果涉及到数据更新,需要考虑缓存和数据库之间的数据同步机制,以保持数据的一致性。
控制层(应用程序)设计:
- 日志收集: 设计日志收集模块,能够从不同的应用、模块收集日志,并进行标准化、分类。
- 日志级别和分类: 设计合适的日志级别(如DEBUG、INFO、ERROR等),根据不同的级别分类存储和展示。
- 日志展示: 设计用户界面以展示日志数据,可以实现按时间、级别、模块等过滤和查询。
- 权限管理: 考虑日志的访问权限,确保只有授权的用户可以访问相应的日志数据。
- 实时监控: 如果需要实时监控系统运行情况,可以设计实时监控面板,显示实时的日志信息和系统状态。
- 告警和通知: 根据日志的内容和级别,设计告警和通知机制,及时通知管理员发现问题。
2.1 采集的过程要怎么做才能不影响其他的服务?
通过Spring AOP来实现,因为AOP能够将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑(例如事务处理、日志管理、权限控制等)抽取公共模块复用,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
- 创建切面类: 首先,你需要创建一个切面类,该类将包含日志采集的逻辑。这个类应该继承自
org.aspectj.lang.annotation.Aspect
,并使用@Aspect
注解进行标记。在切面类中,你可以定义通知(Advice)来拦截指定的方法。 - 定义切点: 在切面类中,你需要定义切点,切点是指在哪些方法上应用切面逻辑。可以使用
@Pointcut
注解来定义切点表达式,以匹配要拦截的方法。 - 编写通知: 通知是在切点方法执行前、执行后、出现异常时等特定时机执行的逻辑。Spring AOP 支持多种类型的通知,如
@Before
、@After
、@AfterReturning
和@AfterThrowing
。 - 配置切面: 在 Spring 配置文件(如 XML 配置文件或 Java 配置类)中,将切面类和切点配置为 Spring Bean,并将通知与切点关联起来。
1 | import org.aspectj.lang.JoinPoint; |
在上面的示例中创建了一个名为 LoggingAspect
的切面类,定义了 serviceMethods()
切点用于匹配位于 com.example.myapp.service
包下的所有方法。然后,我们编写了 @Before
、@AfterReturning
和 @AfterThrowing
通知,分别用于在方法执行前、执行后(包括返回结果)和出现异常时执行相应的日志记录。
最后,将切面类配置为 Spring Bean,可以通过 XML 配置文件或者使用 @ComponentScan
注解扫描到该类。这样,当匹配的方法被调用时,切面逻辑会自动触发。
2.2 采集下来的日志数据用什么存储?数据量大了怎么办?
日志基本都是以文件形式采集的,所以可以采用文本类型的数据库进行存储,比如MongoDB数据库,该数据库更适合存储大量文本类型的数据。
当日志数据量过大时,有几种解决办法:
- 数据清理和归档: 定期清理过期的日志数据,将数据归档到硬盘中,以保持数据库的性能。
- 数据压缩和索引优化:可以使用数据压缩技术和适当的索引优化来减少存储空间和提高查询性能。
- 分布式计算和存储: 对于大数据量,可以考虑使用分布式计算和存储解决方案,如Hadoop、Spark等,以实现高性能的数据处理和分析。
- 流式处理: 如果日志数据是实时产生的,可以考虑使用流式处理平台,如Apache Kafka,将日志数据以流的形式进行处理和存储。
2.3 如何高效的搜索已经存储的日志?
- 索引: 为日志数据建立合适的索引,索引可以显著提高查询性能。索引字段应该基于查询的频率和条件来选择,例如时间戳、关键字、日志级别等。对于文本搜索,全文索引技术可以帮助搜索非结构化的日志数据。
- 合理的查询语句: 编写优化的查询语句,避免不必要的全表扫描。使用适当的条件、操作符和逻辑连接符来缩小查询范围,提高查询效率。
- 分区和分表: 对于关系型数据库,可以使用分区和分表策略,将数据分散存储在多个物理存储位置上,以减少每次查询的数据量。