1 day01 微服务服务注册与发现
你们的项目为什么要用微服务架构?
可从几个方面回答:
- 我们这个项目在立项时就规划了几个版本,考虑项目的规模比较大,为了方便后期项目的扩展和维护,以及方便团队协作等方面使用了微服务架构。
- 我们公司有一个架构组,他们负责对系统架构进行选型、封装等工作,我们项目的系统架构是由架构组的同事提供好的。
SpringBoot和SpringCloud,请你谈谈对他们的理解?
1)、SpringBoot是一个快速构建工程的框架,基于自动装配去实现,可以快速向项目中加入依赖的组件。
2)、SpringCloud是关注微服务开发,协调、治理的框架,它将SpringBoot开发的单体整合并管理起来。SpringCloud常用组件:网关、注册中心、配置中心、feign、熔断降级。
3)、SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系。
Spring Cloud Alibaba与Spring Cloud区别和联系?
联系:
● 两者都是基于 Spring Boot 的微服务框架。
● Spring Cloud Alibaba 遵循 Spring Cloud 的规范。
● Spring Cloud Alibaba 的组件可以与 Spring Cloud 的其他组件配合使用。
区别:
● 服务发现:Spring Cloud 使用的是 Netflix Eureka,而 Spring Cloud Alibaba 使用的是 Nacos。
● 断路器:Spring Cloud 原生支持 Hystrix,而 Spring Cloud Alibaba 推荐使用 Sentinel。
● 配置管理:Spring Cloud 使用 Spring Cloud Config Server,而 Spring Cloud Alibaba 使用 Nacos 作为配置中心。
● 负载均衡:Spring Cloud LoadBalancer结合 Nacos实现负载均衡。
● 远程调用支持:Spring Cloud Alibaba 支持 Dubbo 作为 RPC 调用框架和Feign方式,Spring Cloud 默认不包含
你们Spring Cloud用的什么版本?
我们项目用的Spring Cloud 2021版以及Spring Boot 2.7.12版本,具体使用Spring Cloud Alibaba框架,具体使用了Nacos配置中心与注册中心、Spring Cloud Gateway网关、Spring Cloud LoadBalancer负载均衡、OpenFeign远程调用、Sentinel熔断降级限流等组件。
说几个Java远程调用的技术?你们项目用的什么技术?
在Java中实现RPC远程调用的技术有很多,常用的有: - 使用RestTemplate调用RESTful 接口。
- 使用Java提供RMI技术。
- 使用Dubbo技术。
- 在微服务中我们用Feign或OpenFeign实现远程调用。
我们项目用的RestTemplate、OpenFeign实现微服务之间的远程调用。
如何使用nginx实现负载均衡? - 在nginx.conf 配置文件(/etc/nginx/nginx.conf)中配置upstream
- 在upstream 配置一组后端服务节点地址,并可以指定负载均衡的权重。
- 在 server 块内,你可以在需要负载均衡的 location 中使用 proxy_pass 指令指向前面定义的 upstream。
- 当请求该虚拟目录,通过upstream 实现负载均衡。
Nginx是反向代理,为什么叫反向代理?
Nginx 作为反向代理服务器,其“反向”的概念主要体现在代理的方向上。传统的代理服务器(正向代理)是通过代理服务器访问外网服务器,而“反向”代理表示方向正好相反,是外网通过Nginx返回内部服务器。
说一下服务注册与发现流程?
服务注册与发现流程包括三个角色:服务注册中心、服务提供者、服务调用者。
三者的分工如下:
注册中心:提供服务注册接口,接收服务注册请求,保存服务实例的信息。我们项目用的Nacos。
服务提供者:服务接口提供方,请求注册中心将服务信息注册到注册中心。
服务调用者:远程调用的客户端,请求注册中心查询服务地址,通过负载均衡选取目标服务地址进行远程调用。
服务注册与发现流程如下:
● 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
● 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
● 调用者作为客户端自己通过负载均衡算法挑选一个服务提供者实例进行远程调用,即客户端负载均衡
● 调用者向该实例发起远程调用
微服务是怎么实现远程调用的?
首先使用Nacos实现服务注册中心,服务提供方将自己注册到服务注册中心,服务调用方通过客户端负载均衡器从服务注册中心选取一个目标服务地址进行远程调用。
客户端负载均衡器早期用的Ribbon,现在使用的是Spring Cloud Loadbalancer。
具体使用的有两种方式: - 一种是通过@LoadBalanced注解标识RestTemplate,使用RestTemplate进行远程调用。
- 另一种是使用OpenFeign进行远程调用。
我们项目主要用的是OpenFeign。
OpenFeign的工作原理?
OpenFeign是一个微服务之间调用的Http客户端,它集成了负载均衡组件自动将请求发到不同的服务实例,它是一个http通信模板,底层使用第三方框架OkHttp发送http请求并获取响应结果, 整个请求的过程实现了RPC远程方法调用,将请求和响应自动封装为Java 对象。
它的使用流程: - 接口定义:开发者通过定义Java接口来描述服务间的通信协议,包括URL、请求方法、请求参数等。
- 代理生成:在应用启动时,Feign接口会生成代理对象。
- 请求发送:当调用代理对象的方法时,Feign会根据方法的注解和参数生成HTTP请求,并发送给目标服务。Feign的底层使用的是第三方的OkHttp框架发起http请求。
- 负载均衡:OpenFeign集成了负载均衡组件(如Ribbon或Spring Cloud Loadbalancer),可以自动将请求发到不同的服务实例。
- 响应处理:目标服务处理完请求后,将响应返回给Feign客户端。
- 结果解析:OpenFeign会根据接口定义和注解,将HTTP响应解析为Java对象,并返回给调用者。
2 day02 服务保护 分布式事务
你们是怎么做微服务保护的?
为了避免微服务雪崩问题我们项目引入Sentinel实现微服务保护。
具体我们使用Sentinel实现熔断降级,对高并发接口进行限流、线程隔离等。
每个远程调用接口我们会编写对应的降级方法,当接口异常或发生熔断会直接走降级逻辑,迅速响应给用户,避免长期等待造成系统阻塞。
你的项目中熔断降级怎么实现的?
我们项目使用的是Sentinel实现的熔断降级。
熔断和降级是两个概念:
微服务远程调用我们用的OpenFeign,它集成了Sentinel,在远程调用时由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。
降级是当遇到访问失败可以快速返回一些默认数据或者友好提示,避免长期等待服务提供方,提高系统的稳定性。
熔断降级结合后是当线路断开后直接走降级线路避免再次去请求失败线路。
断路器包括三个状态:
● closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例、异常数。超过阈值则切换到open状态
● open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
● half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。请求成功则切换到closed状态,请求失败则切换到open状态。
我们在开发中具体是这么做的: - 首先在服务调用方配置sentinel,引入sentinel的依赖,配置sentinel的地址
- 在服务调用方配置开启feign使用sentinel。
- 在服务调用方编写feign接口并编写降级逻辑。
- 通过Sentinel控制台配置熔断策略,比如:异常比例、慢请求比例。
Sentinel实现熔断、限流的底层原理是什么?
Sentinel提供多种流量 控制算法 ,包括:滑动窗口算法、令牌桶算法以及漏桶算法等。
滑动窗口算法:
是一种用于统计单位时间内请求次数的方法,用来实现限流的目的。它将时间划分为多个小的时间窗口,并在每个小窗口内记录请求的数量。当新的请求到来时,系统会检查当前小窗口内的请求数量是否超过了预设的阈值,如果超过则拒绝请求。
令牌桶算法:
核心思想是一个桶中存储了一定数量的令牌,每当有请求到达时,就需要从桶中取出一个令牌。如果没有足够的令牌,则请求被延迟或拒绝。
以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余令牌丢弃。
请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理
如果令牌桶中没有令牌,则请求等待或丢弃
在使用令牌桶算法时,可能会存在突发流量导致系统过载,所以尽量不要将令牌上限设定到服务能承受的QPS上限。而是预留一定的波动空间,这样我们才能应对突发流量。
漏桶算法:
漏桶算法与令牌桶相似,但在设计上更适合应对并发波动较大的场景,以解决令牌桶中的问题。
就是请求到达后不是直接处理,而是先放入一个队列。而后以固定的速率从队列中取出并处理请求。之所以叫漏桶算法,就是把请求看做水,队列看做是一个漏了的桶。
seata是怎么进行分布式事务控制的?
Seata支持四种不同的分布式事务解决方案:
● XA
● TCC
● AT
● SAGA
这四种方案可以满足CP和AP的需求,比如:XA可以实现CP即强一致性,AT可以实现AP最终一致性。
我们项目用的AT模式,它的工作原理是:
Seata有一个TC事务协调器,负责控制所有分支事务。
我们在分布式事务开启的方法上使用@GlobalTransactional 开启全局事务,向TC报告开启全局事务。
Seata的TM请求每个分支事务提交事务,分支提交事务完成向TC报告事务提交结果。
如果存在一个失败一个成功的情况,会让成功的分支事务回滚。
回滚的机制:在分支事务提交事务后生成undolog日志,根据undolog日志生成反向SQL,执行该反向SQL实现回滚。
3 day03 微服务网关与配置中心
说说Spring Cloud五大组件? - 服务注册与发现(Eureka/Nacos):Eureka是一个用于实现服务注册与发现的组件,提供了服务注册中心来管理服务实例的注册和发现,使得服务之间可以方便地进行通信和调用。我们项目用的Nacos。
- 客户端负载均衡(Ribbon/ Spring Cloud LoadBalancer):是一个用于在客户端实现负载均衡的组件,它可以根据一定的策略选择合适的服务实例进行负载均衡,提高系统的可用性和性能。早期版本是Ribbon,Ribbon停更后改为 Spring Cloud LoadBalancer。
- 服务调用(Feign/OpenFeign):Feign是一个声明式的服务调用组件,它基于注解和动态代理,可以让开发者使用简单的接口定义服务调用,而无需关注底层的具体实现,随着Netflix公司不再维护Feign,Spring Cloud在Feign基础上新建了一个开源项目OpenFeign。
- 熔断器(Hystrix/Sentinel):Hystrix是一个用于实现服务容错和熔断的组件,它可以保护系统免受服务故障的影响,通过实现服务降级、熔断和隔离等机制,提高系统的稳定性和可靠性。我们项目用的阿里的Sentinel。
- 网关(Gateway):Zuul或Spring Cloud Gateway是用于构建统一的API网关的组件,它可以实现请求的路由、过滤和转发等功能,提供了对外的统一的接入点,并可以对请求进行安全验证、限流和监控等。
你们项目网关用什么实现,实现了什么功能?
网关是前端请求后端服务的统一入口,通过网关实现下边的功能:
1.请求路由,根据请求路径将请求转发到不同的应用服务器。
2.负载均衡,通过负载均衡算法将请求转发到不同的应用服务器。
3.用户身份鉴权,校验用户身份及用户的权限。
我们项目使用Spring Cloud gateway实现网关。
网关鉴权怎么实现的? - 用户请求认证服务进行登录,认证成功向前端返回token
- 前端携带token请求网关
- 网关通过过滤器(GlobalFilter)拦截请求,获取token
- 解析token拿到用户信息,如果token合法则继续,否则拒绝访问
- 网关将用户放在http头中向下传到微服务
- 微服务通过拦截器获取用户信息并写入Threadlocal中方便在service方法获取当前用户信息
- 微服务在远程调用时通过Feign的拦截器将当前登录用户信息写入http头中
- 以上是用户认证在网关校验身份合法性的过程
- 用户的权限校验我们是在微服务中进行的,微服务通过Spring security进行权限判断,具体是在controller方法上添加@PreAuthorize注解,在注解中指定该方法对应的权限字符串,用户拥有此权限则继续访问该方法,否则拒绝访问此方法。
说一下你项目配置文件的加载顺序?
思路:先说明项目中配置文件有哪些,再说加载顺序。
我们项目用的nacos作为配置中心,每个微服务在nacos对应一个或多个配置文件:
配置文件的命名规则是“服务名-profile.yml”
还有一些公用的配置文件,如数据库相关、redis相关、feign相关等,每个都是一个共用配置文件。
还有微服务本地配置文件,有boostrap.yml、application.yaml、application-profile.yaml
在boostrap.yml中配置nacos注册中心和配置中心的信息
在application.yaml和application-profile.yaml本地配置文件中配置了一些不经常改变的配置信息。
加载顺序如下,后边加载的覆盖前边加载的配置:
Bootstrap 配置:(本地配置) - bootstrap.yml
- bootstrap-{profile}.yml
Application 配置 :(本地配置) - application.yml
- application-{profile}.yml
nacos配置: - 扩展配置文件
- 共享配置文件
- {spring.application.name}.yml
- {spring.application.name}-{profile}.yml
nacos配置可以热更新吗?
可以热更新,可以通过下边的方案实现:
1.使用@Value注解+@RefreshScope
2.属性类使用@ConfigurationProperties
day04 MQ基础
MQ有什么应用场景? - 异步处理:
a. 场景: 当应用程序需要执行耗时的操作(如发送电子邮件、文件上传或下载等)时,可以将这些任务发送到消息队列中,由专门的任务处理程序异步执行。
b. 好处: 减少了用户的等待时间,提高了用户体验。 - 解耦:
a. 场景: 当一个系统由多个组件组成时,这些组件之间可以使用消息队列进行通信。
b. 好处: 单个组件的变化不会直接影响到其他组件,提高了系统的可维护性和可扩展性。 - 流量削峰
a. 场景:在许多互联网应用和服务中,尤其是那些具有明显周期性流量特征的应用(如电商平台、社交网络等),常常会遇到流量突增的情况。例如,在电商促销期间,大量的用户会在短时间内访问网站并提交订单,这种短时间内产生的巨大流量可能会导致服务器过载,影响用户体验甚至导致服务不可用。
b. 好处:当流量激增时,前端接收的请求会被暂时存储在消息队列中,而不是直接发送到后端服务进行处理。这样做可以避免后端服务因为短时间内处理大量请求而过载。后端服务可以从消息队列中按需拉取消息进行处理。这种异步处理机制可以有效地分散流量峰值,确保后端服务的稳定运行。
RabbitMQ 的工作模型有哪些? - 工作队列模型
消费者直接绑定到队列上。
一个队列可以绑定一个或多个消费者,多个消费者绑定到一个队列会共同消费队列中的消息,提高消费能力避免消息堆积。 - 发布订阅模型
发布订阅模型可以实现一条消息发给多个队列,每个队列绑定到同一个交换机,最终实现了向多个消费者发送一条消息,这种模式称为“发布/订阅”模型。
发布订阅模型中通过交换机有不同的类型,完成将消息推送到队列:
Fanout:广播类型,将消息交给所有绑定到交换机的队列。
Direct:直接类型,基于RoutingKey(路由key)发送给订阅了消息的队列
Topic:通配符类型(主题类型),与Direct类似,只不过RoutingKey可以使用通配符
Headers:头匹配,基于MQ的消息头匹配,用的较少。
如果解决MQ消息堆积问题?
消息队列(MQ)中的消息堆积问题通常是指消息发送速度远大于消息处理速度导致消息积压的情况。这可能导致消息延迟处理、资源耗尽等问题。
● 增加消费者数量:通过增加更多的消费者实例来提高消费能力。
● 提高单个消费者的处理能力:优化业务逻辑,减少不必要的网络调用或数据库操作。
● 异步处理:对于一些耗时的操作,考虑使用异步方式处理,以减少处理单条消息的时间。
● 多线程处理:消费者通过多线程接收消息并处理。
● 批量处理:尽可能批量处理消息,减少每次处理的开销。
● 限流:限制消息发送速率,例如采用漏桶算法或令牌桶算法控制生产者发送速度。
● 优先级队列:如果支持,可以使用优先级队列来保证重要的消息能够被优先处理。
● 增加服务器资源:根据实际情况增加服务器的CPU、内存等硬件资源。
day05 MQ高级
如何保证消息的可靠性?可以百分百保证MQ的消息可靠性吗?
保证MQ消息的可靠性可从几个方面去处理。 - 保证生产消息的可靠性
首先在发送消息时可以开启重试机制,避免因为短暂的网络问题导致发送消息失败。
RabbitMQ还提供生产者确认机制保证发送消息到MQ的可靠性。
消息投递成功返回ack,投递失败返回nack。
消息投递成功但路由失败会返回异常信息。
我们在发送消息时给每个消息指定一个唯一ID,设置回调方法,如果Publisher Return失败或Publisher Confirm返回nack,我们在回调方法中解析失败消息,并记录到失败表由定时任务去异步重新发送。 - 设置消息持久化
设置交换机持久化:将交换机的定义信息(元数据)持久化到RabbitMQ的数据库中
队列持久化:将队列的定义信息(元数据)持久化到RabbitMQ的数据库中
消息持久化:发送消息设置delivery_mode=2,消息进行持久化。 - 保证消费消息的可靠性
RabbitMQ是通过消费者回执来确认消费者是否成功处理消息的:消费者获取消息后,应该向RabbitMQ发送ACK回执,表明自己已经处理完成消息,RabbitMQ收到ACK后删除消息,在处理失败后向MQ发送NACK那么消息会重新投递到消费者。
RabbitMQ提供三个确认模式:手动ack,自动ack、关闭ack
本项目使用自动ack模式,当消费消息失败会重试,重试3次如果还失败会将消息投递到固定的交换机,通过交换机将消息转发到失败消息队列,程序监听失败消息队列,接收到失败消息,将失败消息存入失败消息表,通过定时任务进行处理。 - 消息队列无法百分百保证可靠性
消息队列无法百分百保证可靠性,一定要提供补偿机制,比如:支付通知消息发送失败,我们可以在业务层提供支付结果查询接口,由消息消费方调用接口去查询支付结果。
可以百分百保证MQ的消息可靠性吗?
无法保证百分百消息可靠,所以使用MQ是针对那些对数据一致性要不是很高的场景。
当消息出现丢失允许暂时数据不一致最后通过补偿措施保证数据最终一致性即可。
如何保证MQ幂等性?或 如何防止消息重复消费? - 设置消息唯一ID,对已处理消息进行记录
发送消息时给消息指定一个唯一的ID
消费消息完成将消息ID记录在数据库或Redis中
消费时根据消息ID查询数据库或Redis判断是否已经消费,如果已经消费则不再消费。 - 通过业务状态判断
业务判断就是基于业务本身的逻辑或状态来判断是否是重复的请求或消息,通过业务状态判断如果已经处理则不再重复处理。
比如:更新订单状态为由未支付更新为已支付,可以在执行更新时判断订单状态是否是未支付,如果不是则证明订单已经被处理过,无需重复处理。
day06 Elasticsearch基础
说下倒排索引?为什么叫倒排索引?
传统的索引是正向索引,从文档中找词,倒排索引是根据词找文章。
倒排索引的建立过程是:
● 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
● 倒排索引记录每个词条对应的文档id
搜索的过程是:
● 对输入的关键字进行分词,得到一个一个的词条
● 拿词条去倒排索引中查找,找到词对应的文档id
● 将所有匹配的文档id合并为结果集。
项目为什么要用Elasticsearch?数据很多吗?
项目使用Elasticsearch是商品搜索。
平台上的商品数据是并不是很多,差不多2,3万条,之所以使用Elasticsearch是因为:
1、公司架构师在系统架构时考虑几年后的数据及对全文检索使用的需求使用了Elasticsearch.
2、对商品信息进行搜索使用的是全文检索方式,虽然MySQL也支持全文检索,但是MySQL全文检索的情况没有ES强,所以使用了ES。
3、虽然现在数据量不大考虑几年后的数据量增长问题,我们使用了Elasticsearch。
4、在项目中除了通过关键字搜索息,还有根据地理坐标进行搜索,使用Elasticsearch也考虑了这一点。
day07 Elasticsearch高级
Canal是怎么伪装成 MySQL slave?
1、Canal模拟 MySQL 从库的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
2、MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
。一旦连接建立成功,Canal会一直等待并监听来自MySQL主服务器的binlog事件流,当有新的数据库变更发生时MySQL master主服务器发送binlog事件流给Canal。
3、Canal会及时接收并解析这些变更事件并解析 binary log
Canal数据同步异常了怎么处理?
检查MySQL主从状态: 检查MySQL主从服务器的状态,确保主从服务器之间的连接正常,MySQL主库的binlog开启,Canal连接是否正常等。如果有网络故障、MySQL主从服务器之间的通信问题,或者Canal连接问题,都可能导致数据同步异常。
确认Canal配置: 检查Canal的配置文件,确保配置正确。特别是Canal的过滤规则等是否正确,即canal.instance.filter.regex的配置。
查看Canal日志: Canal会生成日志文件,其中包含了关于数据同步的详细信息。
通过日志可以了解出现同步是成功的还是出了错误,如果同步错误在日志会显示读取哪个binlog文件出现了错误,然后通过show binary logs 查询是否存在该 binlog日志。
如果由于binlog日志被删除导致canal同步失败,可以将canal中的meta.dat清理,并且将master复位,重启canal。
此时为了防止数据不同步需要对表中的数据进行全量同步,可以批量执行update,触发canal读取update产生的binlog,最终保证当前数据是全部同步的。
项目中如何进行索引同步的?
项目使用了Canal+MQ的方式进行索引同步。
Canal会定时读取数据库的binlog日志,解析出增加、修改及删除的数据内容并将其写入MQ。
同步程序监听MQ,收到消息后根据消息内容请求ES同步索引数据。
这里最关键的问题是MQ的消息可靠性,我们是这样保证MQ的消息可靠性的。
如何保证Canal+MQ同步消息的顺序性?
Canal解析binlog日志信息按顺序发到MQ的队列中。
现在是要保证消费端如何按顺序消费队列中的消息。
解决方法:
多个jvm进程监听同一个队列保证只有消费者活跃,即只有一个消费者接收消息。
队列需要增加x-single-active-consumer参数,值为true,表示否启用单一活动消费者模式。
消费队列中的数据使用单线程。
在监听队列的java代码中指定消费线程为1,如下图:
面试篇
说一下Redid的持久化机制
Redis使用两种持久化机制来保证数据的安全性:RDB和AOF。
RDB:
RDB是快照持久化方式:它是把内存中的所有数据周期性的记录到磁盘中,当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。
AOF:
AOF是追加日志持久化方式,这是将每个写操作以追加的方式记录到日志文件中。
选择RDB还是AOF?
● 对数据完整性的要求不是那么高,那么可以采用RDB持久化的方式。
● 对数据完整性的要求很高,那么可以采用AOF的持久化方式。
● 如果既要兼顾性能,又注重数据完整性,那么可以采用混合持久化的方式。
我们项目用的是RDB加AOF结合的方式:
● 配置RDB定期生成快照文件,用于灾难恢复。
● 在redis.conf中配置:save 900 1,在900秒内有一个key修改进行RDB持久化。
● RDB文件默认在redis容器根目录,可以在redis.conf中配置rdb文件的路径。
● 配置AOF以everysec同步策略运行,确保数据的完整性。
AOF共三种策略:
定期进行AOF重写以保持文件大小在可管理范围内,通过配置阈值控制去重写AOF文件的时机。
AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb
你们用的是Redis单机还是Redis集群?Redis集群具体怎么做的?
我们项目中用的Redis分片集群,具体是3主3从,由我们公司的运维人员进行部署。
分片集群将16384个插槽分配到不同的实例,当向redis操作key时根据key计算哈希值,对16384取余确定将数据写入哪个槽。
分片集群的节点之间会互相通过ping的方式做心跳检测,超时未回应的节点会被标记为下线状态。当发现master下线时,会将这个master的某个slave提升为master。
Java程序如何访问Redis集群?
- 引入redis的starter依赖(spring-boot-starter-data-redis)
- 在application.yml中配置分片集群地址
spring:
redis:
cluster:
nodes:- 192.168.150.101:7001 - 192.168.150.101:7002 - 192.168.150.101:7003 - 192.168.150.101:8001 - 192.168.150.101:8002 - 192.168.150.101:8003 - 配置读写分离,让java客户端将写请求发送到master节点,读请求发送到slave节点
@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
//优先从slave节点读取,所有的slave都不可用才读取master
return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
Redis的SortedSet底层的数据结构是怎样的?
答:SortedSet是有序集合,底层的存储的每个数据都包含element和score两个值。score是得分,element则是字符串值。SortedSet会根据每个element的score值排序,形成有序集合。
它支持的操作很多,比如:
● 根据element查询score值
● 按照score值升序或降序查询element
要实现根据element查询对应的score值,就必须实现element与score之间的键值映射。SortedSet底层是基于HashTable来实现的。
要实现对score值排序,并且查询效率还高,就需要有一种高效的有序数据结构,SortedSet是基于跳表实现的。
加分项:因为SortedSet底层需要用到两种数据结构,对内存占用比较高。因此Redis底层会对SortedSet中的元素大小做判断。如果元素大小小于128且每个元素都小于64字节,SortedSet底层会采用ZipList,也就是压缩列表来代替HashTable和SkipList
Redis7.0版本以后ZipList被替换为Listpack(紧凑列表)。
Redis过期删除策略?
惰性删除:
当一个客户端尝试访问一个键时,Redis 会检查该键是否已经过期。如果过期,Redis 将删除该键,并返回一个表示键不存在的响应给客户端。这种方式确保了对内存的有效管理,但可能在高并发访问过期键的情况下导致性能下降。
定期删除:
Redis 还有一个后台线程,以一定的频率检查过期的键并删除它们。这个频率可以通过 server.hz 参数配置。server.hz 定义了 Redis 服务器每秒运行维护任务的次数,包括但不限于过期键的清理。默认情况下,server.hz 的值为 10,意味着每秒进行 10 次检查。
Redis淘汰策略有哪几种?
1)noeviction: 不删除,直接返回报错信息。
2)volatile-lfu:在设置了过期时间的key中,移除最近最少使用的key。
4)volatile-lru:在设置了过期时间的key中,移除最久未使用的key。
3)volatile-ttl: 在设置了过期时间的key中,移除准备过期的key。
5)volatile-random:在设置了过期时间的key中,随机移除某个key。
6)allkeys-random:随机移除某个key。
7)allkeys-lru:移除最久未使用的key。
8)allkeys-lfu:移除最近最少使用的key。
你的项目是怎么保证缓存一致性的?或 说一下双写不一致的解决方案。
导致双写不一致主要是因为多线程并发导致。
保证双写不一致包括两个方向:
保证强一致性、保证最终一致性。
保证强一致性需要使用分布式锁对缓存的读写加锁控制,这样会影响读缓存的性能。
我们项目保证缓存的最终一致性,这里分场景说明:
1、对数据实时性有一定要求
对数据实时性有一定要求即数据库数据更新需要近实时查询到最新的数据,针对这种情况可采用延迟双删、MQ异步同步的方式。
2、对数据实时性要求不高
使用定时任务的方式定时更新缓存。
3、对数据实时性要求非常高
此类场景不适合用缓存,直接使用数据库即可。
注意:在使用缓存时不论采用哪种方式如果没有特殊要求一定要对key加过期时间,即使一段时间缓存不一致当缓存过期后最终数据是一致的。
分布式锁与syncronized锁的区别 - Synchronized锁
Synchronized 是 Java 语言内置的关键字,提供了一种简单的方式来实现线程之间的互斥。它可以在方法或代码块级别上使用,保证了在同一时刻只有一个线程可以执行被 synchronized 修饰的方法或代码块。
● 作用范围:synchronized 只能在单个 JVM 中起作用,适用于多线程环境中的同步问题。
● 实现方式:通过 JVM 实现,利用了底层的操作系统互斥锁(mutex)。
● 使用简便:直接在代码层面使用,不需要额外的配置或依赖。
● 应用场景:synchronized 主要用于解决同一 JVM 内多线程间的同步问题。 - 分布式锁
分布式锁是一种在分布式系统中实现同步的机制。当应用程序分布在不同的机器上时,需要一种协调机制来确保多个节点之间的一致性。分布式锁已不属于某个虚拟机,而是分布式部署,由多个虚拟机所共享。
● 用范围:分布式锁跨越多个 JVM 或者多个服务实例,适用于分布式系统中的同步问题。
● 实现方式:通常依赖于外部的服务或中间件,如 Redis、Zookeeper、Etcd 等,通过一定的协议(如两阶段锁、心跳检测)来实现。
● 复杂性:实现相对复杂,需要考虑网络延迟、故障恢复等问题
● 应用场景:分布式锁则用于解决跨多个 JVM 或者跨多个服务实例的同步问题。
分布式锁你们用什么实现的?
实现分布式锁的技术方案有很多:数据库、通过Redis的SETNX、Redisson、zookeeper等。
我们项目用的Redisson实现。
使用Redisson实现分布式锁的流程是: - 首先创建Redisson客户端
- 调用getLock方法获取分布式锁对象
- 通过分布式锁对象获取锁,调用tryLock方法获取,这是一个非阻塞方法。
- 获取锁后执行业务逻辑代码,要使用try/finally将代码包裹,在finally中去释放锁。
- Redisson还提供一个看门狗机制,可以对锁进行自动续期,避免锁到期被其它线程强占的问题。
什么是缓存穿透?如何解决缓存穿透?
缓存穿透是指请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。
解决缓存穿透的方法有很多,常用的有:
1、对请求增加校验机制
2、缓存空值或特殊值
3、使用布隆过滤器
我们项目使用的是缓存空值或特殊值的办法。
什么是布隆过滤器?如何使用布隆过滤器?
布隆过滤器(Bloom Filter)是一种数据结构,用于快速判断一个元素是否属于一个集合中。它基于哈希函数实现,可以高效地判断一个元素是否在集合中,但不能精确地确定一个元素在集合中的位置。
布隆过滤器是适合处理大规模数据集,比如:海量数据去重、垃圾邮件过滤、避免缓存穿透等。
使用布隆过滤器需要提前将数据通过多个hash函数映射布隆过滤器中,从布隆过滤器查询的方法也是通过多hash函数进行映射找到具体的位置,如果找到一个位置的值为0则说明数据一定不存在,如果找到位置的值都是1则说明可能存在。
我知道的在redit中提供bitmap位图结构可以实现布隆过滤器,使用redisson也可以实现,使用google的Guava库可以实现。
什么是缓存击穿?如何解决缓存击穿?
缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。
解决方案:
1、使用同步锁或分布式锁控制。
2、热点数据永不过期。
3、缓存预热,分为提前预热、定时预热
4、降级处理
本项目对热点数据定时预热,使用定时任务刷新缓存保证缓存永不过期,解决缓存穿透问题。
什么是缓存雪崩?如何解决缓存雪崩?
缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
解决方案:
1、使用锁进行控制
2、对同一类型信息的key设置不同的过期时间
3、缓存预热
通过定时任务,定时将数据库的数据同步到redis
本项目对key设置不同的过期时间解决缓存雪崩问题。