文章目录

  • Spring
  • 第一问:BeanFactory和ApplicationContext的对比
  • 第二问:bean的生命周期
  • 第三问:BeanPostProcessor
  • 第四问:BeanFactoryProcessor
  • 第五问:Aware接口和IntializingBean接口
  • 第六问:初始化销毁顺序
  • 第七问:scope类
  • 第八问:aop实现
  • 第九问:RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter
  • 第十问:mvc 处理流程【重点】
  • 第十一问:循环依赖
  • 第十二问:Spring 事务失效
  • 第十三问:SpringBoot 自动配置原理
  • 第十四问:SpringBoot 设计模式
  • 第十五问:Spring Refresh流程
  • SpringCloud生态
  • MyBatis
  • Ribbon的工作原理
  • 讲一讲springcloud的5大组件,工作流程?
  • Eureka
  • Eureka和zookeeper作为注册中心的区别?
  • NACOS
  • open feign远程调用
  • Gateway
  • Nginx
  • Redis
  • Redis基础知识
  • Redis为什么这么快?
  • 数据库一致性问题
  • 什么是Redis大key
  • MyBatis-Plus的使用
  • XXL-job
  • 商城相关业务方案
  • 下单+支付链路
  • 登录
  • 发短信验证码
  • 秒杀
  • MQ
  • Kafka、ActiveMQ、RabbitMQ、RocketMQ对比
  • 消息丢失、积压、重复等问题
  • 线上mq消息如何保证99.99999%不丢失?
  • RabbitMQ实现延时队列的需求
  • 顺序消费
  • ElasticSearch性能优化方案


Spring

第一问:BeanFactory和ApplicationContext的对比

BeanFactory是Spring的核心接口,主要实现ApplicationContext组合了它的功能,BeanFactory表面上只有getBean接口,实际上控制反转、依赖注入、直至Bean的生命周期的各种功能,都由它的实现类提供

常见的ApplicationContext实现:

  • ClassPathXmlApplicationXml
  • FileSystemXmlApplicationContext
  • AnnotationConfigApplicationContext
  • AnnotationConfigWebServletContext
第二问:bean的生命周期

构造器->依赖注入->初始化->销毁(不完整)

  1. 处理名称,检查缓存:处理别名,检查一级、二级、三级缓存
  2. 处理父子容器
  3. 处理 dependsOn
  4. 选择 scope 策略
  5. 创建 bean

5.1 创建bean实例

5.2 依赖注入

5.3 初始化

5.4 注册可销毁bean

  1. 类型转换处理
  2. 销毁 bean
第三问:BeanPostProcessor

BeanPostProcessor模板设计模式的应用:BeanFactory的基本流程已确定,通过扩展BeanPostProcessor扩展功能

常见的BeanPostProcessor:

  • AutowiredAnnotationBeanPostProcessor 解析 @Autowired 与 @Value
  • CommonAnnotationBeanPostProcessor 解析 @Resource、@PostConstruct、@PreDestroy
  • ConfigurationPropertiesBindingPostProcessor 解析 @ConfigurationProperties

另外

ContextAnnotationAutowireCandidateResolver 负责获取 @Value 的值,解析 @Qualifier、泛型、@Lazy 等

第四问:BeanFactoryProcessor
  • ConfigurationClassPostProcessor 可以解析
  • MapperScannerConfigurer 可以解析Mapper 接口

自定义BeanFactoryProcessor:实现BeanDefinitionRegistryPostProcessor

第五问:Aware接口和IntializingBean接口

Aware 接口提供了一种【内置】 的注入手段,InitializingBean 接口提供了一种【内置】的初始化手段。

@Autowired和@PostContrust需要使用Bean后处理器,Aware接口、InitializingBean接口属于内置功能。

@Autowired等注解失效注入了BeanFactoryPostProcessor会导致要先创建Java配置类,此时BeanPostProcessor还未准备好,因此@Autowired等注解失效

第六问:初始化销毁顺序

对于init, 三个初始化方法的执行顺序:@PostConstruct -> InitializingBean接口 -> @Bean的initMethod

对于destory, 三个销毁方法的执行顺序是:@PreDestroy -> DisposableBean接口 -> @Bean的destroy

第七问:scope类
  • singleton单例
  • prototype多例
  • request web请求
  • session web会话
  • applicaion:web的ServletContext

scope失效问题:单例scope注入其他scope实例失效

解决方法推迟其它scope bean的获取

  • @Lazy、添加@Scope属性
  • proxyMode=ScopedProxyMode.TARGET_CLASS
  • 使用ObjectFactory注入
  • 使用applicationContext获取Bean
第八问:aop实现

1、aop之ajc增强这是一种编译时的代码增强

2、aop之agent增强这是一种类加载时的代码增强

3、jdk proxy方法

Target target = new Target();
        Foo proxy = (Foo)Proxy.newProxyInstance(JdkProxyDemo.class.getClassLoader(), new Class[]{Foo.class}, (p, method, args1) -> {
            System.out.println("...before...");
            // 目标.方法(参数)
            // 方法.invoke(目标,参数)
            Object result = method.invoke(target, args1);
            System.out.println("...after...");
            return result;
        });

代理一点都不难,无非就是利用了多态、反射的知识

  1. 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
  2. 通过接口回调将【增强逻辑】置于代理类之外
  3. 配合接口方法反射(是多态调用),就可以再联动调用目标方法
  4. 限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现

4、cglib proxy方法

Target target = new Target();

        Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
            System.out.println("before...");
//            Object result = method.invoke(target, args); // 用方法反射调用目标
            // methodProxy 它可以避免反射调用
//            Object result = methodProxy.invoke(target, args); // 内部没有用反射, 需要目标 (spring)
            Object result = methodProxy.invokeSuper(p, args); // 内部没有用反射, 需要代理
            System.out.println("after...");
            return result;
        });

代理的目前是方法对象的增强,对于传统的JDK代理,只能实现带接口的对象,然后通过newPrxoxyInstance实现需要增强的方法,底层原理就是多态和反射。而cjlib代理则是用到了methodPrxoy,代理对象内部初始化methodProxy,通methodProxy无需可以代理任何,并且可不需要使用反射和目标对象。

第九问:RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter

RequestMappingHandlerMapping处理 @RequestMapping 映射;RequestMappingHandlerAdapter调用控制器方法、并处理方法参数与方法返回值。

1、DispatcherServlet 初始化

  1. DispatcherServlet 是在第一次被访问时执行初始化, 也可以通过配置修改为 Tomcat 启动后就初始化
  2. 在初始化时会从 Spring 容器中找一些 Web 需要的组件, 如 HandlerMapping、HandlerAdapter 等,并逐一调用它们的初始化
  3. RequestMappingHandlerMapping 初始化时,会收集所有 @RequestMapping 映射信息,封装为 Map,其中
  • key 是 RequestMappingInfo 类型,包括请求路径、请求方法等信息
  • value 是 HandlerMethod 类型,包括控制器方法对象、控制器对象
  • 有了这个 Map,就可以在请求到达时,快速完成映射,找到 HandlerMethod 并与匹配的拦截器一起返回给 DispatcherServlet
  1. RequestMappingHandlerAdapter 初始化时,会准备 HandlerMethod 调用时需要的各个组件,如:
  • HandlerMethodArgumentResolver 解析控制器方法参数
  • HandlerMethodReturnValueHandler 处理控制器方法返回值

2、自定义参数与返回值处理器

第十问:mvc 处理流程【重点】

当浏览器发送一个请求 http://localhost:8080/hello 后,请求到达服务器,其处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
  • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器
  • jsp 不会匹配到 DispatcherServlet
  • 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
  • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
  • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
  • HandlerMapping,初始化时记录映射关系
  • HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
  • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
  • ViewResolver
  1. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
  • 例如根据 /hello 路径找到 @RequestMapping(“/hello”) 对应的控制器方法
  • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
  • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
  1. DispatcherServlet 接下来会:
  1. 调用拦截器的 preHandle 方法
  2. RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
  • @ControllerAdvice 全局增强点1️⃣:补充模型数据
  • @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
  • 使用 HandlerMethodArgumentResolver 准备参数
  • @ControllerAdvice 全局增强点3️⃣:RequestBody 增强
  • 调用 ServletInvocableHandlerMethod
  • 使用 HandlerMethodReturnValueHandler 处理返回值
  • @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
  • 根据 ModelAndViewContainer 获取 ModelAndView
  • 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
  • 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
  • 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
  1. 调用拦截器的 postHandle 方法
  2. 处理异常或视图渲染
  • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
  • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
  • 正常,走视图解析及渲染流程
  1. 调用拦截器的 afterCompletion 方法

精简版

  1. 客户端发送请求至前端控制器DispatcherServlet接收请求
  2. DispatcherServlet收到请求并调用HandlerMapping处理映射器
  3. HandlerMapping通过系统或者自定义的映射器配置找到对应的处理器handler,生成HandlerExecutionChain{handler(处理器对象)、HandlerInterceptor(处理拦截器)}返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter处理适配器
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器),Controller执行完成后返回ModelAndViewHandlerAdapter将执行结果ModelAndView返回给DispatcherServlet
  6. DispatcherServletModelAndView传给视图解析器ViewResolverViewResolver解析后返回具体的ViewDispatcherServlet根据View进行渲染视图,DIspatcherServlet响应客户端
第十一问:循环依赖

一级缓存作用:限制bean在beanFactory中只存一份,即实现sinleton scope,无法解决循环依赖(singletonObjects)

二级缓存作用:a执行依赖注入前,需要将被依赖注入的半成品对象b注入,执行依赖注入的从singletonFactories取出半成品b,b的流程顺利走完后,将b的成品放入到singletonObject一级缓存,返回到a的依赖注入流程(singletonFactories)

三级缓存作用:只有发生循环依赖时,从singletonFactories拿到工厂对象FacoryBean,注入代理对象,同时将代理对象放入三级缓存earlySingletonObject。(earlySingletonObject)

总结:

单例 set方法(包括成员变量)循环依赖,Spring会利用三级缓存解决,无需额外配置

  • 一级缓存存放成品对象
  • 二级缓存存放发生了循环依赖时的产品对象(可能是原始bean,也可能是代码bean)
  • 三级缓存存放工厂对象,发生循环依赖时,会调用工厂获取产品
  • Spring期望在初始化时创建代理,但如果发生了循环依赖,会由工厂提前创建代理,后续初始化时就不重复创建代理
  • 二级缓存的意义在于,如果提前创建了代理对象,在最后的阶段需要从二级缓存中获取此代理对象,作为最终结果

构造方法及多例循环依赖解决方法

  • @Lazy
  • @Scope
  • ObjectFactory & ObjectProvider
  • Provider
第十二问:Spring 事务失效
  • 抛出检查导致事务不能正确回滚
  • 业务方法内自己try-catch异常导致事务不能正确回滚
  • aop 切面顺序导致导致事务不能正确回滚
  • 非public方法导致的事务失效
  • 父子容器导致的事务失效
  • 调用本类方法导致传播行为失效
  • @Transactional没有保证原子行为
  • @Transational方法导致的synchrionized失效
第十三问:SpringBoot 自动配置原理

@SpringBootConfiguration是一个组合注解,由@ComponetScan、@EnableAutoConfiguration和@SpringBootConfiguration组成

  1. @SpringBootConfiguration 与普通 @Configuration 相比,唯一区别是前者要求整个 app 中只出现一次
  2. @ComponentScan
    excludeFilters - 用来在组件扫描时进行排除,也会排除自动配置类
  3. @EnableAutoConfiguration 也是一个组合注解,由下面注解组成
  • @AutoConfigurationPackage – 用来记住扫描的起始包
  • @Import(AutoConfigurationImportSelector.class) 用来加载 META-INF/spring.factories 中的自动配置类
第十四问:SpringBoot 设计模式

Singleton:spring bean scope

Builder:BeanDefinitionBuilder

Adapter:HandlerMappingAdapter

Composite:HandlerMethodArgumentResolverComposite、HandlerMethodReturnValueHandlerComposite

Observer:ApplicationListener、ApplicationEvent

Chain of Responsibility:HandlerInterceptor

第十五问:Spring Refresh流程
  • prepareRefresh:做好准备工作
  • obtainFreshBeanFactory:创建或获取BeanFactory
  • prepareBeanFactory:准备BeanFactory
  • postProcessBeanFactory:子类扩展BeanFactory
  • invokeBeanFactoryPostProcessors:后处理器扩展 BeanFactory
  • registerBeanPostProcessors:准备Bean后处理器
  • initMessageSource:为ApplicationContext 提供国际化功能
  • initApplicationEventMulticaster:为ApplicationContext 提供事件发布器
  • onRefresh:留给子类扩展
  • registerListeners:为ApplicationContext准备监听器
  • finishBeanFactoryInitialization:初始化单例Bean,执行Bean后处理器扩展
  • finishRefresh:准备生命周期管理器,发布ContextRefreshed 事件

SpringCloud生态

Eureka

OpenFeign

Hystrix

Gateway

Nacos

Sentinel

Seata

xxl-job

MyBatis

mybatis分页插件实现原理:实现一个拦截器结合反射改写SQL

mybatis拦截器的应用:通用属性的注入

不修改对象null、空字符串类型的SQL:mybatis-plus可配置策略:对 not_null,not_empty,ignored

动态SQLifwheretrimchoose、when、otherwiseforeach

mybatis设置一级缓存和二级缓存的意义

xml配置的bean优先级比注解扫描的bean优先级高

Ribbon的工作原理

集中式LB:即在服务的消费方和提供方之间使用的独立的LB设施,如F5、Nginx
进程内LB:将LB逻辑继承到消费方,消费者从服务注册中心获知哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB

Ribbon客户端组件提供一 系列完善的配置项如连接超时重试等

Ribbon在工作时分为两步:

  1. 选择EurekaServer,它优先选择在同一个区域内负载较少的Server
  2. 根据用户指定的策略,在从Server取到的服务注册列表中选择一个地址

其中Ribbon提供多种策略:比如轮询、随机、和根据响应时间加权。

拦截器拦截@LoadBalance注解,根据给定算法从实例列表选择对应的实例,

Ribbon中的IPing 会有一个定时任务,每隔30秒执行一下pingTask 任务,把server list 里的服务都ping 一遍,然后通过那个实例调用。

负载均衡是如何判断调用哪个服务的?

访问次数(AtomicInteger) % 实例数 (CAS)

讲一讲springcloud的5大组件,工作流程?

Eureka:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号等注册信息

Ribbon:客户端代理,服务间发起请求的时候,基于Ribbon服务做到负载均衡,从一个服务的对台机器中选择一台

Feign:基于fegin的动态代理机制,根据注解和选择机器,拼接Url地址,发起请求

Hystrix

  • 服务降级:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示fallback;以下情况会发生服务降级:程序运行异常、超时、服务熔断出发服务降级、线程池/信号量已满
  • 服务熔断:类似保险丝,达到一些限制条件时,直接跳闸不允许请求
  • 实时监控

Zuul(Netflix)/Gateway(SpringCloud):如果前端后端移动端调用后台系统,统一走zull网关进入,有zull网关转发请求给对应的服务

  • 反向代理
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控

Eureka

Eureka和zookeeper作为注册中心的区别?

Eureka(AP)

  1. eureka优先保证可用性。
  2. Eureka不会有类似于ZooKeeper的选举leader的过程。
  3. 当网络分割故障发生时,每个Eureka节点,会持续的对外提供服务(注:ZooKeeper不会)
  4. Eureka各个节点都是平等的,个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。

ZooKeeper(CP)

  1. 作为一个分布式协同服务,ZooKeeper非常好,但是对于Service发现服务来说就不合适了。
  2. 当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举,选举期问整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。

NACOS

是客户端主动拉取配置文件

@RefreshScope注解,该注解可以动态的从Nacos Config 中获取相应的配置

命名空间和配置分组

加载多配置

Ribbon

为什么需要Ribbon的客户端负载均衡?一半Ribbon的使用一般是客户端和客户端之间的调用,而客户端可能是多实例的,这时需要Ribbon的负载均衡

Gateway

配置routes

open feign远程调用

feigin的原理:

  1. 开启Feign的支持:两个核心注解@EnableFeignClients@FeignClient
  2. 服务进行扫描,通过FeignInvocationHandle为每个远程接口创建JDK Proxy代理对象,并将这些对象注入到IOC容器中
  3. FeignInvocationHandler根据要调用的远程方法找到他对应的MethodHandler方法处理器
  4. MethodHandler通过RequestTemplate根据参数和URL等信息封装成Request对象,并调用Encoder进行编码
  5. Client接口根据http请求框架发送http请求,将Request对象发送至对应的远程服务地址,并获取远程服务的Response对象,调用Decoder对Response对象进行解码

feign调用存在的问题:

① 远程调用丢失请求头

注入RequestInterceptor 拦截器,将原RequestContextHolder.getRequestAttributes()属性注入

② 异步调用feign丢失上下文问题

问题描述:由于feign请求拦截器为新的request设置请求头底层是使用ThreadLocal保存刚进来的请求,所以在异步情况下,其他线程并不能获取到主线程的ThreadLocal,所以也拿不到请求。

解决:先获取主线程的requestAttributes,再分别向其他线程中设置

RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture.runAsync(() ->{
   RequestContextHolder.setRequestAttributes(requestAttributes);
});

Gateway

通过cros解决跨域问题

Nginx

  • Http服务器, 部署静态资源
  • 反向代理,负载均衡

Nginx配置代码

java 微服务 需要联查其他服务中的数据怎么办 java微服务测试_java

Nginx 负载到网关

java 微服务 需要联查其他服务中的数据怎么办 java微服务测试_微服务_02

Nginx动静分离

将静态资源放到Nginx

java 微服务 需要联查其他服务中的数据怎么办 java微服务测试_拦截器_03

Redis

Redis基础知识

**Redis是什么?**C语言,高性能非关系型键值对数据库, 与传统数据库做对比,Redis是存放在内存中,读写快,被广泛用于缓存方法。redis可以将数据写入到磁盘中,保证了数据安全不丢失

Redis的应用方式:缓存 、分布式锁、解决表单重复提交(前端跳转的时候生成唯一ID存放到Session中),比较一直则处理,处理完之后将唯一标识符删掉,不相等是重复提交,就不再处理。

Redis配置文件重点

【server块】:bind、port、timeout

【append only mode】appendonly no

Redis几种数据类型场景的应用:、

Rediskey的删除策略:主动删除、被动删除、内存不够时清理

内存淘汰策略:LRU、LFU

安全策略:网络安全,不对未知用户开发端口,设置密码,禁止某些指令

Redis为什么这么快?
  1. Redis是一个纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快
  2. Redis使用的是非阻塞IO、IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争
  3. Redis采用了单线程模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争
  4. Redis避免了多线程的锁的消耗
  5. Redis采用自己实现的事件分离器,效率比较高,内部采用了非阻塞的执行方式,吞吐能力比较大。
数据库一致性问题

双写模式 x 失效模式 x

解决方案:

1、实时性、一致性要求低的数据直接采用加过期时间

2、要求高的数据直接查看,cannal订阅binglog,采用双写模式写的时候加锁

什么是Redis大key
  • string类型的值大于10kb
  • hash、list、set、zset元素个数(元素个数超过5000)

如何找到大key

  • String类型通过命令查找:redis-cli -h 127.0.0.1 -p6379 -a "password" --bigkeys
  • RabTool工具:rbd dump.rdb -c memory --bytes 10240 -f redis.csv

直接删除大key会造成阻塞,为redis是单线程执行,阻塞期间,其他所有请求可能都会超时。超时越来越多,会造成redis连接会耗尽,产生各种异常

  • 低峰期删除:凌晨,观察qps,选择低的时候,无法彻底解决
    阻塞
  • 分批次删除:对于hash ,使hscan扫描法,对于集合采用srandmember每次随机取数据进行删除。对于有序用zremrangebyrank直接删除,对于列表直接pop即可。
  • 异步删除法:用unlink代替del来删除,这样redis会将这个key
    放入到一个异步线程中进行删除,这样不会阻塞主线程。

MyBatis-Plus的使用

继承

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements
UserService {
   
}

service是baseMapper的扩展,但是我们可以通过mybatis xml方式给baseMapper进行扩展。

常用主键:@TableName、@TableId、@TableField、@TableLogic

XXL-job

传统定时任务的不足:

  • 不支持集群
  • 不支持任务重试
  • 不支持动态调用
  • 无报警机制
  • 不支持生命周期的统一管理
  • 任务数据难以统计

xxl-job使用步骤:

  1. 调度中心的部署
  2. 集成xxl-job执行器和任务
  3. 任务相关配置:集群调度策略、父子任务、动态参数任务、分片任务、日志回调、生命周期

商城相关业务方案

下单+支付链路

前往订单页面/toTrade:这里有一个关键点就是设置令牌(USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId()

提交订单/submitOrder

  1. 令牌的对比和删除必须保证原子性,使用lua脚本
  2. 订单生成之前,让feign接口调用库存系统去锁库存(stock.locked),发锁库存延时队列消息(主动去锁库存);
rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", lockedTo);
//Binding的配置
@Bean
public Binding stockLockedBinding() {
    return new Binding("stock.delay.queue",
                       Binding.DestinationType.QUEUE,
                       "stock-event-exchange",
                       "stock.locked",
                       null);
}
//延迟队列
@Bean
public Queue stockDelay() {

    Map<String, Object> arguments = new HashMap<>();
    arguments.put("x-dead-letter-exchange", "stock-event-exchange");
    arguments.put("x-dead-letter-routing-key", "stock.release");
    // 消息过期时间 2分钟
    arguments.put("x-message-ttl", 120000);

    Queue queue = new Queue("stock.delay.queue", true, false, false,arguments);
    return queue;
}

java 微服务 需要联查其他服务中的数据怎么办 java微服务测试_微服务_04

解库存消息消息队列代码

@Slf4j
@RabbitListener(queues = "stock.release.stock.queue")
@Service
public class StockReleaseListener {
	@RabbitHandler
    public void handleStockLockedRelease(StockLockedTo to, Message message, Channel channel) throws IOException {
        log.info("******收到解锁库存的信息******");
        try {
            //当前消息是否被第二次及以后(重新)派发过来了
            // Boolean redelivered = message.getMessageProperties().getRedelivered();

            //解锁库存
            wareSkuService.unlockStock(to);
            // 手动删除消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch(  Exception e){
            // 解锁失败 将消息重新放回队列,让别人消费
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }
}
  1. 订单发送成功,发订单成功的消息(order.create.order);这个时候订单就有两种情况,一种是成功完成订单,支付完成之后修改订单状态即可。当这个消息进入到死信队列里面,查询状态状态为未消费,给库存系统发送释放库存。消费了,就正常Ack即可;
rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order.getOrder());
  1. 支付成功后,两个重要参数notify_url(异步回调)和return_url(返回的页面)
    notify_url:首先做的是验签,
登录

首先定义一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
    boolean isNeedLogin() default true;
}

验证方法/verify:通过jwt解密token,如果成功获取token中的userId,返回给

登录login:生成token,并将token放入到cookie中

创建一个拦截器去拦截具有@LoginRequired的注解

备注:

JWT定义

public static String encode(String key,Map map,String salt){


        if(salt!=null){
            key+=salt;
        }
        JwtBuilder jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS256, key);
        jwtBuilder.addClaims(map);

        String token = jwtBuilder.compact();
        return token;
    }

JWT定义

public static  Map decode(String key,String token,String salt)throws SignatureException{
        if(salt!=null){
            key+=salt;
        }
        Claims map = null;

        map = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();

        return map;

    }
发短信验证码

通常我们会将短信服务放到第三方组件微服务(sms、oss)中,发短信一半是要对他做接口防刷,一般是用redis(sms-手机号:随机数+时间戳)

秒杀

秒杀单独服务,这样好做限流

  1. 商品的上架,通过定时任务
  1. 先查询范围内的时间场次然后通过场次查询所在的sku,返回秒杀场次信息
  2. 如果不为空则上架商品,缓存商品信息到Redis,保存各个Sku值Hash结构(skill-sesession_id-skuInfo(uuid))中,通过Reddision分布式信号量(sku-uuid)保存秒杀数量。同事将活动商品信息放到list类型中(session_id)
  1. 基于分布式信号量去做
RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
//TODO 秒杀成功,快速下单
boolean semaphoreCount = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);

超卖问题

秒杀的时候分两步:

  1. 判断库存名额是否充足
  2. 减少库存名额,扣减成功就是抢到

使用Lua脚本

少卖问题

扣成库存之后,订单没生成。

我们扣减库存后,发送消息队列,这里要有重试策略,如果发送消息失败进行重试,超过重试次数后,则要持久化磁盘,由补偿服务来进行扫描,进行后续业务的修改。类似mysql的日志持久化。

MQ

Kafka、ActiveMQ、RabbitMQ、RocketMQ对比

MQ的应用场景:应用解耦、流量削峰、数据分发

缺点:系统可用性降低,系统复杂度提高、一致性问题

JMS协议、AMQP协议对比:java api/网络线级协议,AMQP支持跨语言、跨平台

ActiveMQ: JMS规范,支持事务、支持XA协议,没有生产大规模支撑场景、官方维护越来越少

RabbitMQ: erlang语言开发、性能好、高并发,支持多种语言,社区、文档方面有优势,erlang语言不利于java程序员二次开发,依赖开源社区的维护和升级,需要学习AMQP协议、学习成本相对较高

kafka:高性能,高可用,生产环境有大规模使用场景,单机容量有限(超过64个分区响应明显变长)、社区更新慢卡夫卡:高性能,高可用,生产环境有大规模使用场景,单机容量有限(超过64个分区响应明显变长)、社区更新慢;只支持主要的MQ功能,像一些消息查询、消息回溯等功能没有提供,在大数据领域应用广

吞吐量单机百万吞吐量单机百万

rocketmq:java实现,方便二次开发、设计参考了kafka,高可用、高可靠,社区活跃度一般、支持语言较少,吞吐量单机十万

消息丢失、积压、重复等问题

消息发送出去,由于网络问题没有抵达服务器

  • 做好容错方法(try-catch),发送消息可能会网络失败,失败后要有重试机制,可记录到数据库,采用定期扫描重发的方式
  • 做好日志记录,每个消息状态是否都被服务器收到都应该记录
  • 做好定期重发,如果消息没有发送成功,定期去数据库扫描未成功的消息进行重发

消息抵达Broker,Broker要将消息写入磁盘(持久化)才算成功。此时Broker尚未持久化完成,宕机。

  • publisher也必须加入确认回调机制,确认成功的消息,修改数据库消息状态。
  • 手动ACK,消费成功才移除,失败或者没来得及处理就noAck并重新入队

消息重复:

  • 消费者的业务消费接口应该设计为幂等性的。比如扣库存有工作单的状态标志。
  • rabbitMQ的每一个消息都有redelivered字段,可以获取是否是被重新投递过来的,而不是第一次投递过来的。

消息积压问题

  • 消费者宕机积压
  • 消费者消费能力不足积压
  • 发送者发送流量太
  • 上线更多的消费者,进行正常消费
  • 上线专门的队列消费服务,将消息先批量取出来,记录数据库,离线慢慢处理
线上mq消息如何保证99.99999%不丢失?

confirm消息机制
生产者投递消息到mq,mq收到后,会回复一个confirm消息,代表我收到了。如果没生产者没收到confirm则要重新发送消息。

持久化

mq收到消息之后,是先存在内存中,那么如果不持久化,可能会由于mq的重启或者宕机,导致内存消息丢失。所以必须要持久化。一旦持久化,mq的重启也不会丢。主要是基于raid的刷盘机制。raid0主要是磁盘的集成,可以将多块磁盘变为1块使用,提高存储。raid1是磁盘镜像,将磁盘分两半,同样的数据存储在两块区域,一般我们用的是raid 0 1。两种结合。

极端情况,mq还没有进行持久化,就挂掉了。或者由于网络原因之类的,生产者收不到confirm。我们对于极其重要的消息要做数据入库。

例如,生产者要发送一条消息之前,先插入数据库,status标识设置为0。收到回调成功之后设置为1。通过定时任务来定期扫描重发,进行消息的补偿。要设置最大重试次数。这种方式会加到数据库交互的压力,建议也可以直接通过在缓存中操作状态的变更,数据库定时清扫,减少压力,视我们的实际情况而定

消费者 ack机制

mq需要接受到消费者的应答后,才能确定当前消息消费完毕。如果超时或者未收到,则要进行重试,这也是我们说在消费消息的时候,一定要保证幂等。

Kafka:主从复制+同步刷盘

RabbitMQ实现延时队列的需求
  1. 设置队列的过期时间
  2. 设置消息的过期时间(TTL+死信队列)

采用方式1,RabbitMQ采用的是惰性检查机制,如果发送的是带过期时间的消息,RabbitMQ会从队列顶端读取消息的过期时间,然后到了过期时间才会检查,这样会存在时间误差,通常我们采用带过期时间的队列。

顺序消费

单线程消费保证消息的顺序性;对消息进行编号,消费者根据编号处理消息;比如xxl-job中选择执行策略选择第一个。

java 微服务 需要联查其他服务中的数据怎么办 java微服务测试_初始化_05

ElasticSearch性能优化方案

filesystem cache

filesystem cache使我们操作系统的文件缓存机制。es就是依赖这种文件缓存机制,但是例如mysql这种就是自己实现的存储引擎机制。所以当我们遇到操作系统内存分配的时候其实要视情况而定,es的机器自然是给文件缓存多些,mysql的话就直接给mysql即可。

回到es,es的数据存储到磁盘上,我们在进行查询的时候,会从磁盘读取数据后,缓存到内存里,也就是我们的filesystem cache。如果我们读取数据的时候直接走磁盘逮度可能会到秒级,但是如果我们走内存,毫秒级就可以查到数据。可以想到redis为啥快,因为内存操作。所以我们要尽可能找到平衡点,提高文件存储内存。

减少非搜索字段的存储

我们有时候一般会这样搞,mysql里面存了什么,我们就往es里面继续存什么。那么可能我们实际搜索的字段只是其中某一个字段,例如content内容。其他的title啊,digest啊,都是不会被查询到的,那么这种就不要存进来。查的时候可以减少一点内存的占用,提高我们整体的有效数据存储。

数据预热
拿大家平时在京东上购物来说,就比如可口可乐吧,大家搜索的会比较多,非常可乐可能搜索的就会少一些。由于我们的内存缓存数据是具有时效性的,所从大家可以针对热点数据进行提前预刷。通过热数据探测啊,记录搜索次数等等啊,动态的去查询一次数据,保证每次尽量命中内存,这样可从极大的提高效率。

冷热分离
我们也可以通过不同的索引来存储不同的数据,把我们业务产品认为搜索可能性极大的数椐单独在一个索引,搜索可能性极小的走另一个索引,而不是为了方便直接存在一个里面,做聚合,这样可以保证我们的热数据大量保存在内存中,不被冷数据占用。这样也可以极大提高我们的速度。

分页优化
es的分页是聚合产生的。如果说我们每页10条数据,我查询第100页的数据,es的执行逻辑是会把每个shard上存储的前1000条数据都查到个协调节点上,如果你有个5个shard,那么就有5000条数据,接着协调节点对这5000条数据进行一些合并、处理,再获取到最终第100页10条数据。那么随着页的越来越深,就会导致效率极慢。所从我们从产品设计上要避免深分页问题。我们一般是使用scroll api来进行解决。scroll会生成数据的快照,每次进行翻页进行数据的滚动,比如你在京东上买货,你会发现他一般是触底加载的,b站也同样如此。