首页
归档
留言
广告合作
友链
美女主播
Search
1
博瑞GE车机升级/降级
5,173 阅读
2
修改elementUI中el-table树形结构图标
4,540 阅读
3
Mac打印机设置黑白打印
4,535 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,372 阅读
5
intelliJ Idea 2022.2.X破解
4,092 阅读
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Spring Cloud
Mac
mybatis
WordPress
Nacos
Spring Cloud Alibaba
Mybatis-Plus
jQuery
Java Script
asp.net
微信小程序
Sentinel
UniApp
MySQL
asp.net core
IntelliJ IDEA
Jpa
树莓派
Laughing
累计撰写
576
篇文章
累计收到
1,425
条评论
首页
栏目
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
广告合作
友链
美女主播
搜索到
89
篇与
的结果
2022-01-05
Spring Boot另类前后端分离项目部署
对于前后端分离的项目,一般情况下,我们是把静态文件放到nginx中,然后通过端口转发,请求后端避免跨域的问题。我们知道,Spring Boot项目中,我们可以添加静态资源,根据约定大于配置的规则,默认静态文件位置位于以下位置classpath:/static,classpath:/public,classpath:/resources,classpath:/META-INF/resources,servlet context:将前端文件放到Spring Boot静态资源中的好处是不存在跨域的问题。默认静态资源,如果我们放到resources中,这显然不是我们想看到的。我们希望的是,将静态文件放到jar包之外的某个特定位置,这样静态资源的修改,不会涉及jar包的修改。配置文件修改为了实现静态资源文件的配置,我们需要通过spring.web.resources.static-locations指定静态文件的位置web: spring: web: resources: static-locations: file:${web}之所以添加一个web,是为了方便我们在命令行动态指定位置。放置静态资源我这里演示将index.html放到--web=/Users/lisen/IdeaProjects/demo/web中进行访问。内容如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"> </script> </head> <body> 这是首页1 <div id="div1"></div> <script> $(document).ready(function(){ $.ajax({ url:"/book/get/1", type : "POST", //请求成功 success : function(result) { $("#div1").html(result); }, }) }); </script> </body> </html>启动命令java -jar /Users/lisen/IdeaProjects/demo/target/demo-0.0.1-SNAPSHOT.jar --web=/Users/lisen/IdeaProjects/demo/web访问前端页面可以看到,页面正确访问,并且没有出现跨域的问题。
2022年01月05日
913 阅读
0 评论
2 点赞
2021-12-18
SpringBoot JPA自动设置创建者、最后修改者
书接上文,SpringBoot JPA自动设置创建时间、修改时间,审计不可能只包含创建时间、最后修改时间,肯定得有人,也就是必须得有创建者、最后修改者。Spring Data Jpa设置创建者、最后修改者也非常简单。实体修改@Data @Entity @Table(name = "Book") @EntityListeners(AuditingEntityListener.class) public class Book { @Id private long id; private String name; private String author; private BigDecimal price; @CreatedDate private Timestamp createdDate; @CreatedBy private String createBy; @LastModifiedDate private Timestamp lastModifiedDate; @LastModifiedBy private String lastModifiedBy; }@CreatedBy注解用于标识创建者。@LastModifiedBy注解用于标识最后修改者。其他注解跟设置创建时间、最后修改时间一致,不再赘述,这里重点说一下,如何获取创建者、最后修改者。设置创建者、最后修改者值在Spring Data Jpa中,可以通过实现AuditorAware接口让程序知道当前审核程序的用户,实现逻辑根据项目实际情况编写。/** * 审计接口,获取当前人员 */ @Configuration public class JpaAuditWare implements AuditorAware<String> { @Override public Optional<String> getCurrentAuditor() { return Optional.of(UUID.randomUUID().toString()); } }验证创建验证 @GetMapping("save") public Book saveBook() { Book book = new Book(); book.setId(1L); book.setName("《山海经》"); book.setAuthor("佚名"); book.setPrice(new BigDecimal("500")); return bookService.saveBook(book); }修改验证然后我们修改一下数据,验证一下最后修改人、最后修改时间[alt type="info"]如果数据未发生改变,那么最后修改者、最后修改时间是不会发生改变的。[/alt]
2021年12月18日
1,730 阅读
0 评论
0 点赞
2021-12-18
SpringBoot JPA自动设置创建时间、修改时间
JPA提供了审计功能,用于设置创建者、创建时间、修改者、修改时间等参数。创建时间、修改时间很好理解,就是当前时间,但是创建者、修改者一般都是通过上下文信息获取的,由于我这边是接口里面使用,未使用创建者、修改者,所以先介绍一下创建时间、修改时间的使用。添加依赖那些巴拉巴拉的就不啰嗦。创建实体@Getter @Setter @ToString @Entity @Table(name = "ARAPDiscountRecord") @EntityListeners(AuditingEntityListener.class) public class ARAPDiscountRecordEntity implements Serializable { @CreatedDate @Column(name = "timestamp_createdon") private Timestamp timestampCreatedon; @LastModifiedDate @Column(name = "timestamp_lastchangedon") private Timestamp timestampLastchangedon; }@CreatedDate注解用于标识创建时间。@LastModifiedDate注解用于标识修改时间。实体类添加@EntityListeners(AuditingEntityListener.class)标识启动审计。启动审计再启用或者配置类上添加@EnableJpaAuditing启动审计。@EnableJpaAuditing @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }这样的话,每次创建数据,系统会自动赋值timestampCreatedon列,修改数据时,系统会自动赋值timestampLastchangedon字段。
2021年12月18日
1,505 阅读
0 评论
0 点赞
2021-12-12
Spring Boot中ThreadLocal踩坑
ThreadLocal作用ThreadLocal的作用:用来存当前线程的局部变量,不同线程间互不干扰。拿完数据记得需要移除数据,不然JVM不会将ThreadLocal回收(可能还会被引用),多了就会出现内存泄漏的情况。解析ThreadLocal类ThreadLocal包含几个方法:public T get() { } public void set(T value) { } public void remove() { } protected T initialValue() { }T get()get()方法是用来获取ThreadLocal在当前线程中保存的变量副本set(T value)set()用来设置当前线程中变量的副本void remove()remove()用来移除当前线程中变量的副本T initialValue()initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。ThreadLocal变量污染ThreadLocal会在每个线程存储一个副本,但是如果我们使用的是比如Tomcat,Tomcat自身会维护一个线程池,线程结束后,并不会马上销毁,而是会重新进入线程池,下次有请求时,有可能会复用当前线程,如果我们每次使用ThreadLocal之前,没有进行Set(T value),那么就有可能导致不同线程之间变量污染,比如下面的代码@RestController @RequestMapping(value = "book") @Slf4j public class BookController { private static final ThreadLocal<String> THREAD_LOCAL_TEST = new ThreadLocal<>(); @Resource private IBookService bookService; /** * 构造函数 */ public BookController() { log.info("构造函数,此时bookService :" + bookService); } @PostConstruct public void init() { log.info("PostConstruct,此时bookService :" + bookService); } @PreDestroy public void destroy() { log.info("PreDestroy"); } @GetMapping(value = "set") public void set() { if (StringUtils.isEmpty(THREAD_LOCAL_TEST.get())) { THREAD_LOCAL_TEST.set(UUID.randomUUID().toString()); } } @GetMapping(value = "search") public List<Book> search() { log.error(THREAD_LOCAL_TEST.get()); return bookService.search(); } }使用jmeter进行请求,可以看到同一线程,输出的内容永远不会发生改变。可以在内次使用之前进行set(T value),但是set(T value)可能会导致内存无法释放。ThreadLocal可能导致的内存泄露ThreadLocal为了避免内存泄露,不仅使用了弱引用维护key,还会在每个操作上检查key是否被回收,进而再回收value。但是从中也可以看到,ThreadLocal并不能100%保证不发生内存泄漏。比如,很不幸的,你的get()方法总是访问固定几个一直存在的ThreadLocal,那么清理动作就不会执行,如果你没有机会调用set()和remove(),那么这个内存泄漏依然会发生。一个良好的习惯依然是:当你不需要这个ThreadLocal变量时,主动调用remove(),这样对整个系统是有好处的。@RestController @RequestMapping(value = "book") @Slf4j public class BookController { private static final ThreadLocal<String> THREAD_LOCAL_TEST = new ThreadLocal<>(); @Resource private IBookService bookService; /** * 构造函数 */ public BookController() { log.info("构造函数,此时bookService :" + bookService); } @PostConstruct public void init() { log.info("PostConstruct,此时bookService :" + bookService); } @PreDestroy public void destroy() { log.info("PreDestroy"); } @GetMapping(value = "set") public void set() { THREAD_LOCAL_TEST.set(UUID.randomUUID().toString()); } @GetMapping(value = "search") public List<Book> search() { log.error(THREAD_LOCAL_TEST.get()); THREAD_LOCAL_TEST.remove(); return bookService.search(); } }ThreadLocal与局部变量局部变量和ThreadLocal起到的作用是一样的,保证了并发环境下数据的安全性。那就是说,完全可以用局部变量来代替ThreadLocal咯?This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).[typing]翻译过来[/typing] ThreadLocal提供的是一种线程局部变量。这些变量不同于其它变量的点在于每个线程在获取变量的时候,都拥有它自己相对独立的变量初始化拷贝。ThreadLocal的实例一般是私有静态的,可以做到与一个线程绑定某一种状态。所以就这段话而言,我们知道ThreadLocal不是为了满足多线程安全而开发出来的,因为局部变量已经足够安全。ThreadLocal是为了方便线程处理自己的某种状态。 可以看到ThreadLocal实例化所处的位置,是一个线程共有区域。好比一个银行和个人,我们可以把钱存在银行,也可以把钱存在家。存在家里的钱是局部变量,仅供个人使用;存在银行里的钱也不是说可以让别人随便使用,只有我们以个人身份去获取才能得到。所以说ThreadLocal封装的变量我们是在外面某个区域保存了处于我们个人的一个状态,只允许我们自己去访问和修改的状态。
2021年12月12日
1,858 阅读
0 评论
0 点赞
2021-12-05
@SpringBootApplication标注非引导类
一般情况下,@SpringBootApplication一般都是标注在项目引导类上。像下面这样:@Slf4j @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoConfiguration.class, args); } }但是@SpringBootApplication一定要标注到引导类上吗?答案是否定的。我们可以将@SpringBootApplication标注到任意的类上。比如我们增加以下类package net.xiangcaowuyu.demo.config; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoConfiguration { }然后改造引导类@Slf4j public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoConfiguration.class, args); } }再次运行项目,可以发现项目可以正常运行但是如果我们访问我们的接口,会发现提示404注意我们DemoConfiguration所在的包net.xiangcaowuyu.demo.config,@SpringBootApplication只会扫描当前包及下级包,所以,我们的接口它扫描不到,就提示404。如果希望我们其他的类被扫描到,我们就需要添加scanBasePackages属性。package net.xiangcaowuyu.demo.config; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages = {"net.xiangcaowuyu.demo"}) public class DemoConfiguration { }重启项目,在访问接口,发现能正常访问
2021年12月05日
948 阅读
0 评论
0 点赞
2021-12-05
理解SpringBoot 中的@AliasFor注解
感觉Spring Boot中的@AliasFor注解是一个既熟悉又陌生的注解。说熟悉,是因为我们经常使用的比如@Service、@RestController、@Repository甚至@SpringBootApplication中都有他们的身影。说陌生,是因为其实从来没有真正用过这个注解。@AliasFor注解有两个作用:定义一个注解中的两个属性互为别名。桥接其他注解的属性。一、定义一个注解中的两个属性互为别名。我们以ComponentScan注解为例,先来看下ComponentScan的定义@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Repeatable(ComponentScans.class) public @interface ComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default "**/*.class"; boolean useDefaultFilters() default true; ComponentScan.Filter[] includeFilters() default {}; ComponentScan.Filter[] excludeFilters() default {}; boolean lazyInit() default false; @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Filter { FilterType type() default FilterType.ANNOTATION; @AliasFor("classes") Class<?>[] value() default {}; @AliasFor("value") Class<?>[] classes() default {}; String[] pattern() default {}; } }以上代码,我们关注点在于 @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {};这段代码,代表着,在ComponentScan注解中,basePackages属性与value是一样的,可以相互调用。我们可以测试一下@ComponentScan(basePackages = {"net.xiangcaowuyu.demo"}) @Component @EnableAutoConfiguration @Slf4j public class DemoApplication { public static void main(String[] args) { ComponentScan componentScan = AnnotatedElementUtils.getMergedAnnotation(DemoApplication.class, ComponentScan.class); assert componentScan != null; System.out.println(Arrays.toString(componentScan.basePackages())); System.out.println(Arrays.toString(componentScan.value())); SpringApplication.run(DemoApplication.class, args); } }二、桥接其他注解的属性这次,我们看下@Service的代码@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Service { @AliasFor( annotation = Component.class ) String value() default ""; }可以看到,@Service将value属性,桥接到了@Component注解的value属性。@ComponentScan(basePackages = {"net.xiangcaowuyu.demo"}) @Component @EnableAutoConfiguration @Slf4j public class DemoApplication { public static void main(String[] args) { Component component = AnnotatedElementUtils.getMergedAnnotation(BookService.class, Component.class); assert component != null; System.out.println(component.value()); SpringApplication.run(DemoApplication.class, args); } }
2021年12月05日
1,104 阅读
0 评论
1 点赞
2021-10-23
Spring PostConstruct注解的使用
一、基本介绍@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。@PostConstruct在构造函数之后执行,init()方法之前执行。通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注解的方法)二、用途@PostConstruct主要用于处理一些初始化工作。比如下面代码@RestController @RequestMapping(value = "book") @Slf4j public class BookController { @Resource private IBookService bookService; /** * 构造函数 */ public BookController() { log.info("构造函数,此时bookService :" + bookService); } @PostConstruct public void init() { log.info("PostConstruct,此时bookService :" + bookService); } @GetMapping(value = "search") public List<Book> search() { return bookService.search(); } }看下控制台可以看到,构造函数里面,Bean还没有初始化,@PostConstruct里面已经完成初始化,所以,我们可以通过@PostConstruct完成一些初始化后的操作。
2021年10月23日
1,158 阅读
0 评论
0 点赞
2021-07-29
Spring Cloud集成Gateway
一、SpringCloud Gateway 简介SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。二、SpringCloud Gateway 特征SpringCloud官方,对SpringCloud Gateway 特征介绍如下:基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0集成 Hystrix 断路器集成 Spring Cloud DiscoveryClientPredicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters具备一些网关的高级功能:动态路由、限流、路径重写从以上的特征来说,和Zuul的特征差别不大。SpringCloud Gateway和Zuul主要的区别,还是在底层的通信框架上。简单说明一下上文中的三个术语:Filter(过滤器):和Zuul的过滤器在概念上类似,可以使用它拦截和修改请求,并且对上游的响应,进行二次处理。过滤器为org.springframework.cloud.gateway.filter.GatewayFilter类的实例。Route(路由):网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个ID,一个目标URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。Predicate(断言):这是一个 Java 8 的Predicate,可以使用它来匹配来自HTTP请求的任何内容,例如headers或参数。断言的输入类型是一个 ServerWebExchange。三、Spring Cloud Gateway的处理流程客户端向 Spring Cloud Gateway 发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。四、路由匹配规则对于这种配置类的,我们一般情况都会放到配置文件中去配置。Spring Cloud Gateway 的功能很强大,我们仅仅通过 Predicates 的设计就可以看出来,前面我们只是使用了 predicates 进行了简单的条件匹配,其实 Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。代码Demo父工程依赖<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR12</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <!-- <version>2.1.0.RELEASE</version>--> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement>当前模块依赖 <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>配置文件server: port: 9527 spring: application: name: cloud-gateway-service cloud: nacos: discovery: server-addr: 192.168.120.180:8848 gateway: discovery: locator: enabled: true routes: - id: payment-routh uri: lb://nacos-provider-payment # 服务名称 predicates: - Path=/app/** - Header=username,laughing可以看到gateway的路由匹配规格主要是包含id、uri、predicates。id随意指定,唯一即可。uri代表配置的路径,可以匹配URL或者服务名称,如果是服务名称,以lb:打头。predicates代表断言,条件为true时才能进入对应的路径。说白了 Predicate 就是为了实现一组匹配规则,方便让请求过来找到对应的 Route 进行处理,接下来我们接下 Spring Cloud GateWay 内置几种 Predicate 的使用。假定转发uri都设定为lb://nacos-provider-payment规则实例说明Path- Path=/gate/,/rule/当请求的路径为gate、rule开头的时,转发到nacos-provider-payment服务Before- Before=2017-01-20T17:42:47.789-07:00[America/Denver]在某个时间之前的请求才会被转发到nacos-provider-payment服务After- After=2017-01-20T17:42:47.789-07:00[America/Denver]在某个时间之后的请求才会被转发到nacos-provider-payment服务Between- Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]在某个时间段之间的才会被转发Cookie- Cookie=chocolate, ch.p名为chocolate的表单或者满足正则ch.p的表单才会被匹配到进行请求转发Header- Header=X-Request-Id, \d+携带参数X-Request-Id或者满足\d+的请求头才会匹配Host- Host=www.xiangcaowuyu.net当主机名为www.xiangcaowuyu.net的时候直接转发到nacos-provider-payment服务Method- Method=GET只有GET方法才会匹配转发请求,还可以限定POST、PUT等请求方式测试我们上面代码的匹配规则如果我们访问的路径是/echo开头,并且Header中username=laughing就转发到nacos-provider-payment服务。第一次正常访问第二次,我们配置Header可以看到报404第三次,我们配置Header但是参数值设置为test,可以看到也是报404其他参数配置我们不再测试。五、过滤器规则(Filter)过滤规则实例说明PrefixPath- PrefixPath=/app在请求路径前加上appRewritePath- RewritePath=/test, /app/test访问localhost:9022/test,请求会转发到localhost:8001/app/testSetPathSetPath=/app/{path}通过模板设置路径,转发的规则时会在路径前增加app,{path}表示原请求路径RedirectTo 重定向RemoveRequestHeader 去掉某个请求头信息测试Demo我们只测试一个PrefixPath服务提供者 @GetMapping(value = "app/echo/{string}") public String echo1(@PathVariable("string") String string) { return "app/echo/{string} " + string+"\t当前服务端口:"+serverPort; }配置文件server: port: 9527 spring: application: name: cloud-gateway-service cloud: nacos: discovery: server-addr: 192.168.120.180:8848 gateway: discovery: locator: enabled: true routes: - id: payment-routh uri: lb://nacos-provider-payment # 服务名称 predicates: - Path=/** filters: PrefixPath=/app测试
2021年07月29日
1,321 阅读
0 评论
1 点赞
2021-07-29
Spring Cloud集成Sentinel之使用OpenFein实现服务调用
在Spring Cloud使用OpenFeign调用Nacos服务提供者中,我们介绍了OpenFeign调用Nacos的方式。在Spring Cloud集成Sentinel之@SentinelResource注解使用一文中,我们也介绍了@SentinelResource注解,但是注解的方式。本文我们更进一步,介绍以下OpenFeign如何与Setinel集成。添加依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Cloud2020</artifactId> <groupId>net.xiangcaowuyu</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-consumer-order9003</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> </dependencies> </project>修改配置文件server: port: 8888 spring: application: name: nacos-consumer-order cloud: nacos: discovery: server-addr: 192.168.120.180:1111 sentinel: transport: dashboard: 192.168.120.180:9000 port: 8719 management: endpoints: web: exposure: include: "*" logging: level: net.xiangcaowuyu.springcloud.alibaba.service.NacosPaymentService: debug #设置feign客户端超时时间(OpenFeign默认支持ribbon) nacos-provider-payment: ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000 NFLoadBalancerRuleClassName: net.xiangcaowuyu.springcloud.alibaba.config.CustomerBalancerRule sentinel: enabled: true修改启动类@SpringBootApplication //@EnableDiscoveryClient @EnableFeignClients public class NacosConsumerOrderMain8888 { public static void main(String[] args) { SpringApplication.run(NacosConsumerOrderMain8888.class, args); } }定义Feign接口@FeignClient(value = "nacos-provider-payment", fallback = NacosPaymentServiceFallback.class) public interface NacosPaymentService { @GetMapping(value = "/echo/{string}") String echo(@PathVariable("string") String string); } class NacosPaymentServiceFallback implements NacosPaymentService { @Override public String echo(String string) { return "进入feignClient fallback了"; } }定义测试接口 @GetMapping("/echo/{id}") public String echo(@PathVariable("id") String id) { log.info("******************输出:" + id); return nacosPaymentService.echo(id); }
2021年07月29日
1,044 阅读
0 评论
0 点赞
2021-07-28
Spring Cloud集成Sentinel之@SentinelResource注解使用
通过Spring Cloud集成Sentinel之流量控制及Spring Cloud集成Sentinel之服务熔断降级两篇文章,我们可以发现一个问题,当系统触发限流或者熔断时,系统排除的异常很不友好。现在前后端分离项目,我们后端一般会封装一个公共的实体返回给前端,比如下面代码中的CommonResult,了解到这,我们分别介绍以下@SentinelResource注解中常用的几个参数:value、blockHandler、fallback。测试代码@SentinelResource注解代码@RestController public class RateLimitController { @GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException",fallback = "handleFallback") public CommonResult byResource() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } public CommonResult handleException(BlockException exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); } public CommonResult handleFallback(Throwable exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); } @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler",blockHandlerClass = MyHandler.class,blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } }异常代码public class MyHandler { public static CommonResult handlerException1(BlockException exception) { return new CommonResult(4444, "按客户自定义的global handlerException---1"); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(4444, "按客户自定义的global handlerException---2"); } }valuevalue用于指定资源名称。在Sentinel配置中,我们前面使用的api地址作为的资源名称,其实也可以通过这个value指定资源名称。在Sentinel中,如果以/开头,那么便意味着通过api地址作为资源名,否则系统认为value作为资源名。比如这个方法@GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler",blockHandlerClass = MyHandler.class,blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); }在sentinel配置中,指定资源名称/rateLimit/customerBlockHandler或customerBlockHandler是等价的。[alt type="warning"]value必须是唯一的[/alt]只指定fallbackfallback负责业务异常和限流时处理。测试代码 @GetMapping("/fallbackOnly") @SentinelResource(value = "fallbackOnly", fallback = "handlefallbackOnlyException") public CommonResult fallbackOnly() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); }我们触发限流规则,可以看到如下输出,已经是我们自定义的异常类,而不是系统自带的。还有一个fallbackClass注解,如此类似,不再赘诉。只指定blockHandlerblockHandler只负责sentinel控制台配置违规测试代码 @GetMapping("/blockHandlerOnly") @SentinelResource(value = "blockHandlerOnly", blockHandler = "handleblockHandlerException") public CommonResult blockHandlerOnly() { int age = 10/0; return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } public CommonResult handleblockHandlerException(BlockException exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); }我们触发一个熔断规则,可以看到如下输出fallback和blockHandler同时存在fallback负责处理异常,blockHandler负责sentinel控制台配置违规。@GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException", fallback = "handleFallback") public CommonResult byResource() { return new CommonResult(200, "按资源名称限量ok", new Payment(2020L, "SERIAL001")); } public CommonResult handleException(BlockException exception) { return new CommonResult(444, "handleException\t 服务不可用"); } public CommonResult handleFallback(Throwable exception) { return new CommonResult(444, "handleFallback\t 服务不可用"); }
2021年07月28日
1,127 阅读
0 评论
0 点赞
2021-07-28
Spring Cloud集成Sentinel之热点参数
热点参数何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。Sentinel 利用LRU策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。 @GetMapping("/testHotkey") // @SentinelResource(value = "testHotkey", blockHandler = "deal_testHotkey") @SentinelResource(value = "testHotkey") public String testHotkey(@RequestParam(value = "productId", required = false) String productId, @RequestParam(value = "userId", required = false) String userId) { return "--------------testHotkey"; }如果参数0,1s请求次数超过依次,触发熔断,但是如果参数0的值是p2,那么可以请求2次。自适应限流Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统规则 系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。系统规则支持以下的模式:Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps minRt 估算得出。设定参考值一般是 CPU cores 2.5。CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
2021年07月28日
1,031 阅读
0 评论
0 点赞
2021-07-28
Spring Cloud集成Sentinel之服务熔断降级
Sentinel除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。降级策略通常用以下几种方式来衡量资源是否处于稳定的状态:慢调用比例 (DEGRADE_GRADE_RT):当统计时常内持续进入N个请求同时请求数到达最小请求数,对应时刻的平均响应时间(秒级)均超过比例阈值,那么在接下的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException)。注意 Sentinel 默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms。若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx来配置。异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的在统计时常内请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule中的count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表0% - 100%。异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后仍可能再进入熔断状态。代码测试慢调用比例测试代码@GetMapping("/testRT") public String testRT() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return "-----------testRT"; }在1s内,请求数如果超过5个,平均请求时常超过800ms的比例达到20%,那么在接下来的1min内进行服务熔断。一开始正常访问,但是访问第6次时,达到条件,出发熔断降级。异常比例测试代码@GetMapping("/testExceptionPer") public String testExceptionPer() { int age = 1 / 0; return "-----------testExceptionPer"; }在1s内,请求数如果超过5个,异常的比例达到10%,那么在接下来的1min内进行服务熔断。一开始访问,报以下异常多次访问后,触发熔断降级异常数可以参考异常比例,不再赘述。
2021年07月28日
1,137 阅读
0 评论
0 点赞
1
2
3
...
8