spring懒加载?

SpringCloud系列之负载均衡Ribbon·9-懒加载和饥饿加载

因为Ribbon的懒加载机制所以会导致在第一个方法调用时候才会去初始化LoadBalancer,然后第一个方法请求过来视乎不仅仅包含HTTP连接和方法的响应时间,还包括了LoadBalancer的创建耗时。假如你的方法本身就比较耗时的话,而且超时时间又设置的比较短,那么很大可能这第一次http调用就会失败。其实还有很多框架也实现了类似的懒加载功能,比如Hibernate的lazy-fetch,懒加载在大部分情况下可以节省系统资源开销,但某些情况下反而导致服务响应时间被延长。

之所以这类问题难以发现的原因,在于它无法稳定重现,比如只能通过重启来重现,对这种问题往往直接从生产环境的log入手去分析是比较有效的手段

解决这个问题就是更改Ribbon的加载方式,将懒加载改为饥饿模式:

第一个参数开启了Ribbon的饥饿加载模式,第二个属性指定了需要应用饥饿加载的服务名称。完成上面配置并再次重启服务,就会发现LoadBalancer初始化日志在方法调用之前就打印出来了。

Spring Cloud Feign使用详解

 通过前面两章对Spring Cloud Ribbon和Spring Cloud Hystrix的介绍,我们已经掌握了开发微服务应用时,两个重要武器,学会了如何在微服务架构中实现客户端负载均衡的服务调用以及如何通过断路器来保护我们的微服务应用。这两者将被作为基础工具类框架广泛地应用在各个微服务的实现中,不仅包括我们自身的业务类微服务,也包括一些基础设施类微服务(比如网关)。此外,在实践过程中,我们会发现对这两个框架的使用几乎是同时出现的。既然如此,那么是否有更高层次的封装来整合这两个基础工具以简化开发呢?本章我们即将介绍的Spring Cloud Ribbon与Spring Cloud Hystrix,除了提供这两者的强大功能之外,它还提供了一种声明式的Web服务客户端定义方式。

 我们在使用Spring Cloud Ribbon时,通常都会利用它对RestTemplate的请求拦截来实现对依赖服务的接口调用,而RestTemplate已经实现了对HTTP请求的封装处理,形成了一套模版化的调用方法。在之前的例子中,我们只是简单介绍了RestTemplate调用对实现,但是在实际开发中,由于对服务依赖对调用可能不止于一处,往往一个接口会被多处调用,所以我们通常都会针对各个微服务自行封装一些客户端累来包装这些依赖服务的调用。这个时候我们会发现,由于RestTemplate的封装,几乎每一个调用都是简单的模版化内容。综合上述这些情况,Spring Cloud Fegin在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Spring Cloud Feign的实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用Spring Cloud Ribbon时自行封装服务调用客户端的开发量。Spring Cloud Feign具备可插拔的注解支持,包括Feign注解和JAX-RS注解。同时,为了适应Spring的广大用户,它在Netflix Feign的基础上扩展了对Spring MVC的注解支持。这对于习惯于Spring MVC的开发者来说,无疑是一个好消息,你我这样可以大大减少学习适应它的成本。另外,对于Feign自身的一些主要组件,比如编码器和解码器等,它也以可插拔的方式提供,在有需求等时候我们以方便扩张和替换它们。

 在本节中,我们将通过一个简单示例来展示Spring Cloud Feign在服务客户端定义所带来的便利。下面等示例将继续使用之前我们实现等hello-service服务,这里我们会通过Spring Cloud Feign提供的声明式服务绑定功能来实现对该服务接口的调用。

▪️首先,创建一个Spring Boot基础工程,取名为kyle-service-feign,并在pom.xml中引入spring-cloud-starter-eureka和spring-cloud-starter-feign依赖,具体内容如下所示。

▪️创建应用主类Application,并通过@EnableFeignClients注解开启Spring Cloud Feign的支持功能。

▪️定义HelloServiceFeign,接口@FeignClient注解指定服务名来绑定服务,然后再使用Spring MVC的注解来绑定具体该服务提供的REST接口。

▪️接着,创建一个RestClientController来实现对Feign客户端的调用。使用@Autowired直接注入上面定义的HelloServiceFeign实例,并在postPerson函数中调用这个绑定了hello-service服务接口的客户端来向该服务发起/hello接口的调用。

▪️最后,同Ribbon实现的服务消费者一样,需要在application.properties中指定服务注册中心,并定义自身的服务名为feign-service-provider,为了方便本地调试与之前的Ribbon消费者区分,端口使用8868。

 发送几次GET请求到 ,可以得到如之前Ribbon实现时一样到效果,正确返回 hi, kyle! i from 10.166.37.142:8877 。依然是利用Ribbon维护了针对HELLO-SERVICE-PROVIDER的服务列表信息,并且通过轮询实现了客户端负载均衡。而与Ribbon不同到是,通过Feign只需定义服务绑定接口,以声明式的方法,优雅而简单地实现了服务调用。

 现实系统中的各种业务接口要比上一节复杂得多,我们会再HTTP的各个位置传入各种不同类型的参数,并且再返回响应的时候也可能是一个复杂的对象结构。再本节中,我们将详细介绍Feign中的不同形式参数的绑定方法。

 再开始介绍Spring Cloud Feign的参数绑定之前,我们先扩张以下服务提供者hello-service-provider。增加下面这些接口,其中包含带有Request参数的请求、带有Header信息的请求、带有RequestBody的请求以及请求响应体中是一个对象的请求。

 在完成了对hello-service-provider的改造之后,下面我们开始在快速入门示例的kyle-service-feign应用中实现这些新增的绑定。

 这里一定要注意,再定义各参数绑定时,@RequestParam、@RequestHeader等可以指定参数名称的主角,它们的value千万不能少。在Spring MVC程序中,这些注解会根据参数名来作为默认值,但是在Feign中绑定参数必须通过value属性来指明具体的参数名,不然会抛出==IllegalStateException==异常,value属性不能为空。

 在完成上述改造之后,启动服务注册中心、两个hello-service-privider服务以及我们改造的kyle-service-feign。通过发送GET请求到== ;age=18== ,通过发送POST请求到== ,请求触发HelloServiceFeign对新增接口的调用。最终,我们会获得如下图的结果,代表接口绑定和调试成功。

 由于Spring Cloud Feign的客户端负载均衡是通过Spring Cloud Ribbon实现的,所以我们可以直接配置Ribbon客户端的方式来自定义各个服务客户端调用参数。那么我们如何使用Spring Cloud Feign的工程中使用Ribbon的配置呢?

 全局配置的方法非常简单,我们可以直接使用ribbon.key=value的方式来设置ribbon的各项默认参数。如下:

 大多数情况下,我们对于服务调用的超时时间可能会根据实际服务的特性做一些调整,所以仅仅进行个性化配置的方式与使用Spring Cloud Ribbon时的配置方式是意义的,都采用client.ribbon.key=value的格式进行设置。但是,这里就有一个疑问了,cleint所指代的Ribbon客户端在那里呢?

 回想一下,在定义Feign客户端的时候,我们使用了@FeignClient注解。在初始化过程中,Spring Cloud Feign会根据该注解的name属性或value属性指定的服务名,自动创建一个同名的Ribbon客户端。如下:

 Spring Cloud Ribbon默认负载均衡策略是轮询策略,不过该不一定满足我们的需要。Ribbon一共提供了7种负载均衡策略,如果我们需要ZoneAvoidanceRule,首先要在application.properties文件中添加配置,如下所示:

 不过,只是添加了如上配置,还无法实现负载均衡策略的更改。我们还需要实例化该策略,可以在应用主类中直接加入IRule实例的创建,如下:

 想要深入了解Ribbon的原理,或者想详细了解7种负载均衡策略的,可以参考我另一篇博客 《Ribbon详解》 ,我会在博客最下面给出链接。

 从前两节来看在Spring Boot工程中使用Feign,非常的便利。不过实际生产中,在微服务的初期只能从次要系统开始进行改造,可能很多系统由于历史原因仍然是非Spring Boot的工程,然后这些系统如何使用微服务?如何使用注册中心?如何进行负载均衡呢?

 ▪️首先我们在kyle-service-feign创建调用接口OldSystemPostFeign和OldSystemGetFeign,然后使用feign注解提供的相关注解,包含@RequestLine、@Param、@HeaderParam、@Headers等,主要提供了请求方法、请求参数、头信息参数等操作。

 ▪️我们需要脱离Spring Boot和Spring Cloud的支持,使用feign原生的一些东西。在进行Feign封装之前我们需要一些额外的组件,比如编码器。新增组件依赖如下所示:

 ▪️我们需要一个feign-clientproperties文件,来进行ribbon相关的参数配置,配置如下:

 ▪️到目前为止,相关要素已经准备好了,接下来需要feign和ribbon的封装了。我们需要创建OldSystemFeignClientConfiguration类,作用是加载feign-client.properties文件,并创建一个附带负载均衡器的RibbonClient,然后封装出一个附带Jackson编解码器的FeignClient,如下所示:

 ▪️然后我需要一个测试类FeignClientTest,测试以上3个接口,然后将结果输出到控台如下所示:

 ▪️在完成上述改造之后,启动测试类FeignClientTest,获得如下的结果,说明调用使用了负载均衡。

 细心的同学会发现,非Spring Boot使用feign调用根本没有使用到注册中心的服务发现。在此我提供一个思路,我们可以调用代理微服务,再由代理进行服务发现。那么这个代理服务应该具备哪些功能和作用呢?我将会在下一篇博客详细讲述Netflix公司的API网关组件zuul,它承担路由转发,拦截过滤,流量控制等功能。

▪️ 第一次请求失败

 原因:由于spring的懒加载机制导致大量的类只有在真正使用的才会真正创建,由于默认的熔断超时时间(1秒)过短,导致第一次请求很容易失败,特别互相依赖复杂的时候。

 解决方法:提升熔断超时时间和ribbon超时时间,配置如下:

▪️ Feign的Http Client

 Feign在默认情况下使用的是JDK原生URLConnection发送HTTP请求,没有连接池,但是对每个地址会保持一个长连接,即利用HTTP的persistence connection。我们可以用Apache的HTTP Client替换Feign原始的http client,从而获取连接池、超时时间等与性能息息相关的控制能力。Spring Cloud从Brixtion.SR5版本开始支持这种替换,首先在项目中声明Apcahe HTTP Client和feign-httpclient依赖,然后在application.properties中添加:

▪️ 如何实现在feign请求之前进行操作

 feign组件提供了请求操作接口RequestInterceptor,实现之后对apply函数进行重写就能对request进行修改,包括header和body操作。

▪️ 请求压缩

 Spring Cloud Feign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。我们只需通过下面两个参数设置,就能开启请求与响应的压缩功能:

 同时,我们还能对请求压缩做一些更细致的设置,比如下面的配置内容指定了压缩的请求数据类型,并设置了压缩的大小下限,只有超过这个大小的请求才会对其进行压缩。

 上述配置的feign.compression.request.nime-types和feign.compression.requestmin-request-size均为默认值。

▪️ 日志配置

 Spring Cloud Feign在构建被@FeignClient注解修饰的服务客户端时,会为每一个客户端都创建一个feign的请求细节。可以在 application.properties 文件中使用logging.level.FeignClient的参数配置格式来开启指定Feign客户端的DEBUG日志,其中FeignClient为Feign客户端定义捷克队完整路径,比如针对本博文中我们实现的HelloServiceFeign可以如下配置开启:

 但是,只是添加了如上配置,还无法实现对DEBUG日志的输出。这时由于Feign客户端默认对Logger.Level对象定义为NONE级别,该界别不会记录任何Feign调用过程中对信息,所以我们需要调整它对级别,针对全局对日志级别,可以在应用主类中直接假如Logger.Level的Bean创建,具体如下:

 在调整日志级别为FULL之后,我们可以再访问第一节的 接口,这是我们在kyle-service-feign的控制台中可以看到类似下面的请求详细的日志:

 对于Feign的Logger级别主要有下面4类,可根据实际需要进行调整使用。

▪️ 负载均衡异常

 当我们只是对一个微服务进行调用的时候,Ribbon提供的支持好像没什么问题。不过在我们进行多个微服务调用时会产生异常,这也是大多数人忽略的。

  情景描述 :2个应用B和C,在A中使用feign client调用B和C;测试结果,假如先调用B,再调用C都是有效的,但是再调用B就是无效的;(B,C先后顺序改变,都会产生这个bug)

  解决方法 :在主启动类使用注解@RibbonClient,进行RibbonClient配置,如下所示:

 看不懂是吗?不要紧,我下面详细讲解一下,先看一下我们之前的非Spring Boot工程中封装FeignClient:

 OldSystemPostFeign只是一个接口,Feign为什么需要使用接口来调用远程接口?原因就是使用JDK动态代理,我们可以去看Feign是如何进行处理。

spring懒加载?  第1张

springboot下搭建springdata出现的懒加载异常

org.hibernate.LazyInitializationException: could not initialize proxy [com.springdata.pojo.Roles#1] - no Session

没开事物因为springdata底层是hibernate如果你是多表关联映射时

是必须要开启事物进行增删改查的不开就会报错

注意重点:在关系映射里面开启级联也是必须要开始事物操作不让报错

-------------------》》》》》@Transactional在测试类上加一个这个注解即可

实例------------------------

@RunWith(SpringRunner.class)

@SpringBootTest

@Transactional//用级联必须开事物不开报错  不开事物查询报lazy异常

public class DemoApplicationTests {

    @Autowired

    private RoleRepoJPA roleJpa;

    @Autowired

    private UserRepoJPA userJpa;

    @Test

    public void testSaveUsers(){

        //创建一个用户

        User user = new User();

        user.setName("张三");

        //创建一个角色

        Role role = new Role();

        role.setRname("DBA");

        //关联

        //role.getUsers().add(user);

        user.setRoles(role);

        //保存

        this.roleJpa.save(role);

        this.userJpa.save(user);

    }

    @Test

    public void contextLoads() {

        OptionalUser User = this.userJpa.findById(1);

        System.out.println(User.get().getName());

        Role R = User.get().getRoles();

        System.out.println(R);

    }

}

springboot2全局指定@Lazy(懒加载)

         Spring默认在启动时立即实例化配置的bean,要修改为懒加载(在实际使用的时候实例化).

0.1 在xml配置中:

0.2 在JavaConfig配置中:

0.3 SpringBoot中指定bean的懒加载,可以在对应的类上直接使用@Lazy

         那么SpringBoot中如何全局配置懒加载呢?

         通过在stackoverflow上查找, 发现的答案是, 在启动类SpringbootApplication上加上@Lazy注解即可. 原来注解@SpringBootApplication是@Configuration, @EnableAutoConfiguration和@ComponentScan注解的合体.

而这个SpringbootApplication本身就是个配置类, 所以在上面加@Lazy注解理论上是可以的.果然是直观的东西不方便, 方便的东西不直观.

1.1 错误方式一:

执行 gradle bootRun 启动应用, 发现输出了

也就是说配置并没有生效. 但是so上的回答一般不会是错的. 那会是哪里出了问题呢?

1.2 方式一修正

不使用@Component, 而是在配置文件中声明bean:

这种方式实现了懒加载,但是这跟 0.2 中的方式是一样的.

1.3 方式二

spring2.2 中引入了一个application.properties中的新属性.

spring.main.lazy-initialization=true 来指定整个应用的懒加载.

这种方式不论是@Component声明的bean,还是@Bean声明的bean, 都可以实现懒加载.

虽然 懒加载可以提升应用的启动速度, 但是不利于尽早的发现错误, 对于HTTP请求, 首次访问的响应时间也会增长.

怎么使用SpringBoot实现懒加载和init-method

        在以前使用的Spring框架中,我们知道在Spring容器ioc的配置xml中,可以配置各种各样的Bean,并且可以指定Bean的加载方式,单例在ioc容器启动的时候,就开始加载,多例在获取bean的时候加载,但是我们也可以通过一个lazy-init来实现懒加载,不仅这样,我们还可以指定当Bean在容器中初始化的时候执行某些方法。这个时候就要使用init-method来指定方法名字。在使用完对象的时候,调用destory-method,来执行销毁方法。

        那么,现在使用了SpringBoot,怎么在项目中,实现上面说的内容呢。我们可以在SpringBoot的引导启动类中。配置Bean的时候,加上参数。像是下面这样:

如果要实现懒加载,可以加上注解@Lazy,这个时候,便会在使用到Bean获取该Bean的时候,才会初始化这个Bean。

还有一个全局懒加载,则是在启动引导类上面添加上注解@Lazy。这样。所有配置在启动引导类中的@Bean。都会被懒加载。

以上内容为新媒号(sinv.com.cn)为大家提供!新媒号,坚持更新大家所需的互联网后端知识。希望您喜欢!

版权申明:新媒号所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,不声明或保证其内容的正确性,如发现本站有涉嫌抄袭侵权/违法违规的内容。请发送邮件至 k2#88.com(替换@) 举报,一经查实,本站将立刻删除。

(0)
上一篇 2023-09-23 13:45
下一篇 2023-09-23 13:45

相关推荐

发表回复

登录后才能评论