Pārlūkot izejas kodu

1.content-service 中添加一个 consumer 的 hystrix 降级熔断 demo
2.使用 feign 实现了 SpringCloud 服务间调用, feign 调用 service-provider 提供的服务时也通过 CircuitBreaker 实现了降级熔断

reghao 8 mēneši atpakaļ
vecāks
revīzija
6384ce9fcd

+ 5 - 0
content/content-service/pom.xml

@@ -149,6 +149,11 @@
             <groupId>org.springframework.cloud</groupId>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>
         </dependency>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
+            <version>2.2.10.RELEASE</version>
+        </dependency>
 
 
         <dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <groupId>org.springframework.boot</groupId>

+ 13 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/config/SpringCloudConfig.java

@@ -0,0 +1,13 @@
+package cn.reghao.tnb.content.app.config;
+
+import org.springframework.cloud.netflix.hystrix.EnableHystrix;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author reghao
+ * @date 2025-07-22 09:30:18
+ */
+@Configuration
+@EnableHystrix
+public class SpringCloudConfig {
+}

+ 16 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/controller/ProductController.java

@@ -9,6 +9,9 @@ import cn.reghao.tnb.content.app.mall.model.dto.ProductAddDto;
 import cn.reghao.tnb.content.app.mall.model.po.Product;
 import cn.reghao.tnb.content.app.mall.model.po.Product;
 import cn.reghao.tnb.content.app.mall.service.BuyService;
 import cn.reghao.tnb.content.app.mall.service.BuyService;
 import cn.reghao.tnb.content.app.mall.service.ProductService;
 import cn.reghao.tnb.content.app.mall.service.ProductService;
+import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
+import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
+import com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Operation;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
@@ -54,11 +57,24 @@ public class ProductController {
 
 
     @Operation(summary = "获取商品详情", description = "N")
     @Operation(summary = "获取商品详情", description = "N")
     @GetMapping(value = "/{itemId}", produces = MediaType.APPLICATION_JSON_VALUE)
     @GetMapping(value = "/{itemId}", produces = MediaType.APPLICATION_JSON_VALUE)
+    @HystrixCommand(
+            fallbackMethod = "getProductFallback",
+            commandProperties = {
+                    @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "false"),
+                    @HystrixProperty(name="metrics.rollingStats.timeInMilliseconds", value="5000"),
+                    @HystrixProperty(name="circuitBreaker.requestVolumeThreshold", value="4"),
+                    @HystrixProperty(name="circuitBreaker.errorThresholdPercentage", value="50"),
+                    @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds", value="3000")
+            })
     public String getProduct(@PathVariable("itemId") Long itemId) {
     public String getProduct(@PathVariable("itemId") Long itemId) {
         Product product = productService.getProduct(itemId);
         Product product = productService.getProduct(itemId);
         return WebResult.success(product);
         return WebResult.success(product);
     }
     }
 
 
+    public String getProductFallback(Long itemId) {
+        return WebResult.fallback("fallback");
+    }
+
     @Operation(summary = "购买商品", description = "N")
     @Operation(summary = "购买商品", description = "N")
     @PostMapping("/buy")
     @PostMapping("/buy")
     public String buyProduct(@RequestBody @Validated BuyProductDto buyProductDto) {
     public String buyProduct(@RequestBody @Validated BuyProductDto buyProductDto) {

+ 77 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/feign/CircuitBreakerConfig.java

@@ -0,0 +1,77 @@
+package cn.reghao.tnb.content.app.mall.feign;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
+import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
+import org.springframework.cloud.client.circuitbreaker.ConfigBuilder;
+import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * @author reghao
+ * @date 2025-07-22 10:46:17
+ */
+@Slf4j
+@Configuration
+public class CircuitBreakerConfig {
+    @Bean
+    MyCircuitBreaker myCircuitBreaker() {
+        return new MyCircuitBreaker();
+    }
+
+    @SuppressWarnings("rawtypes")
+    @Bean
+    CircuitBreakerFactory circuitBreakerFactory(MyCircuitBreaker myCircuitBreaker) {
+        return new CircuitBreakerFactory() {
+            @Override
+            public CircuitBreaker create(String id) {
+                log.info("Creating a circuit breaker with id [{}]", id);
+                return myCircuitBreaker;
+            }
+
+            @Override
+            protected ConfigBuilder configBuilder(String id) {
+                return Object::new;
+            }
+
+            @Override
+            public void configureDefault(Function defaultConfiguration) {
+
+            }
+        };
+    }
+
+    static class MyCircuitBreaker implements CircuitBreaker {
+        AtomicBoolean runWasCalled = new AtomicBoolean();
+
+        @Override
+        public <T> T run(Supplier<T> toRun) {
+            try {
+                this.runWasCalled.set(true);
+                return toRun.get();
+            }
+            catch (Throwable throwable) {
+                throw new NoFallbackAvailableException("No fallback available.", throwable);
+            }
+        }
+
+        @Override
+        public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
+            try {
+                return run(toRun);
+            }
+            catch (Throwable throwable) {
+                return fallback.apply(throwable);
+            }
+        }
+
+        public void clear() {
+            this.runWasCalled.set(false);
+        }
+    }
+}

+ 1 - 1
content/content-service/src/main/java/cn/reghao/tnb/content/app/vod/feign/FeignClientConfig.java → content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/feign/FeignClientConfig.java

@@ -1,4 +1,4 @@
-package cn.reghao.tnb.content.app.vod.feign;
+package cn.reghao.tnb.content.app.mall.feign;
 
 
 import org.springframework.cloud.openfeign.EnableFeignClients;
 import org.springframework.cloud.openfeign.EnableFeignClients;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;

+ 28 - 0
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/feign/UserFeignClient.java

@@ -0,0 +1,28 @@
+package cn.reghao.tnb.content.app.mall.feign;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * @author reghao
+ * @date 2025-07-19 23:57:25
+ */
+@FeignClient(name = "user-service", fallback = UserFeignClient.UserFeignClientImpl.class)
+public interface UserFeignClient {
+    @GetMapping(value = "/api/user/hystrix/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    String getUserInfo(@RequestParam("userId") Long userId);
+
+    @Slf4j
+    @Component
+    class UserFeignClientImpl implements UserFeignClient {
+        @Override
+        public String getUserInfo(Long userId) {
+            log.info("getUserInfo fallback");
+            return "";
+        }
+    }
+}

+ 11 - 1
content/content-service/src/main/java/cn/reghao/tnb/content/app/mall/service/ProductService.java

@@ -3,6 +3,7 @@ package cn.reghao.tnb.content.app.mall.service;
 import cn.reghao.file.api.iface.OssService;
 import cn.reghao.file.api.iface.OssService;
 import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.jdk.result.Result;
 import cn.reghao.jutil.jdk.result.Result;
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
 import cn.reghao.jutil.tool.id.SnowFlake;
 import cn.reghao.jutil.tool.id.SnowFlake;
 import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
 import cn.reghao.oss.sdk.model.dto.media.ImageInfo;
 import cn.reghao.tnb.content.api.dto.TaobaoItem;
 import cn.reghao.tnb.content.api.dto.TaobaoItem;
@@ -10,6 +11,8 @@ import cn.reghao.tnb.content.app.mall.db.mapper.ProductMapper;
 import cn.reghao.tnb.content.app.mall.db.repository.MallRepository;
 import cn.reghao.tnb.content.app.mall.db.repository.MallRepository;
 import cn.reghao.tnb.content.app.mall.model.dto.ProductAddDto;
 import cn.reghao.tnb.content.app.mall.model.dto.ProductAddDto;
 import cn.reghao.tnb.content.app.mall.model.po.Product;
 import cn.reghao.tnb.content.app.mall.model.po.Product;
+import cn.reghao.tnb.content.app.mall.feign.UserFeignClient;
+import cn.reghao.tnb.user.api.dto.UserInfo;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
@@ -28,11 +31,13 @@ public class ProductService {
     private final SnowFlake idGenerator;
     private final SnowFlake idGenerator;
     private final ProductMapper productMapper;
     private final ProductMapper productMapper;
     private final MallRepository mallRepository;
     private final MallRepository mallRepository;
+    private final UserFeignClient userFeignClient;
 
 
-    public ProductService(ProductMapper productMapper, MallRepository mallRepository) {
+    public ProductService(ProductMapper productMapper, MallRepository mallRepository, UserFeignClient userFeignClient) {
         this.idGenerator = new SnowFlake(1L, 1L);
         this.idGenerator = new SnowFlake(1L, 1L);
         this.productMapper = productMapper;
         this.productMapper = productMapper;
         this.mallRepository = mallRepository;
         this.mallRepository = mallRepository;
+        this.userFeignClient = userFeignClient;
     }
     }
 
 
     public void add(TaobaoItem taobaoItem) {
     public void add(TaobaoItem taobaoItem) {
@@ -63,6 +68,11 @@ public class ProductService {
     }
     }
 
 
     public Product getProduct(Long itemId) {
     public Product getProduct(Long itemId) {
+        Product product = mallRepository.getProduct(itemId);
+        long sellerId = product.getSellerId();
+        String userInfoJson = userFeignClient.getUserInfo(sellerId);
+        UserInfo userInfo = JsonConverter.jsonToObject(userInfoJson, UserInfo.class);
+
         return mallRepository.getProduct(itemId);
         return mallRepository.getProduct(itemId);
     }
     }
 }
 }

+ 0 - 16
content/content-service/src/main/java/cn/reghao/tnb/content/app/vod/feign/UserFeignClient.java

@@ -1,16 +0,0 @@
-package cn.reghao.tnb.content.app.vod.feign;
-
-import org.springframework.cloud.openfeign.FeignClient;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-
-/**
- * @author reghao
- * @date 2025-07-19 23:57:25
- */
-@FeignClient(value = "user-service")
-public interface UserFeignClient {
-    @GetMapping(value = "/api/user/feign/info", produces = MediaType.APPLICATION_JSON_VALUE)
-    String getUserInfo(@RequestParam("userId") Long userId);
-}

+ 1 - 9
content/content-service/src/main/java/cn/reghao/tnb/content/app/vod/service/impl/VideoPostQueryImpl.java

@@ -3,7 +3,6 @@ package cn.reghao.tnb.content.app.vod.service.impl;
 import cn.reghao.file.api.iface.OssService;
 import cn.reghao.file.api.iface.OssService;
 import cn.reghao.jutil.jdk.db.Page;
 import cn.reghao.jutil.jdk.db.Page;
 import cn.reghao.jutil.jdk.db.PageList;
 import cn.reghao.jutil.jdk.db.PageList;
-import cn.reghao.jutil.jdk.serializer.JsonConverter;
 import cn.reghao.jutil.web.WebResult;
 import cn.reghao.jutil.web.WebResult;
 import cn.reghao.oss.sdk.model.dto.media.VideoInfo;
 import cn.reghao.oss.sdk.model.dto.media.VideoInfo;
 import cn.reghao.oss.sdk.model.dto.media.VideoUrlDto;
 import cn.reghao.oss.sdk.model.dto.media.VideoUrlDto;
@@ -17,7 +16,6 @@ import cn.reghao.tnb.content.api.dto.VideoCard;
 import cn.reghao.tnb.content.api.dto.VideoPostCard;
 import cn.reghao.tnb.content.api.dto.VideoPostCard;
 import cn.reghao.tnb.content.app.vod.db.mapper.*;
 import cn.reghao.tnb.content.app.vod.db.mapper.*;
 import cn.reghao.tnb.content.app.vod.db.repository.VideoRepository;
 import cn.reghao.tnb.content.app.vod.db.repository.VideoRepository;
-import cn.reghao.tnb.content.app.vod.feign.UserFeignClient;
 import cn.reghao.tnb.content.app.vod.model.po.*;
 import cn.reghao.tnb.content.app.vod.model.po.*;
 import cn.reghao.tnb.content.app.vod.model.vo.BannerVideoVO;
 import cn.reghao.tnb.content.app.vod.model.vo.BannerVideoVO;
 import cn.reghao.tnb.content.app.vod.model.vo.VideoDetail;
 import cn.reghao.tnb.content.app.vod.model.vo.VideoDetail;
@@ -26,7 +24,6 @@ import cn.reghao.tnb.content.app.vod.service.CategoryService;
 import cn.reghao.tnb.content.app.vod.service.ContentPermission;
 import cn.reghao.tnb.content.app.vod.service.ContentPermission;
 import cn.reghao.tnb.content.app.vod.service.VideoPostQuery;
 import cn.reghao.tnb.content.app.vod.service.VideoPostQuery;
 import cn.reghao.tnb.user.api.dto.UserCard;
 import cn.reghao.tnb.user.api.dto.UserCard;
-import cn.reghao.tnb.user.api.dto.UserInfo;
 import cn.reghao.tnb.user.api.iface.UserService;
 import cn.reghao.tnb.user.api.iface.UserService;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -64,13 +61,12 @@ public class VideoPostQueryImpl implements VideoPostQuery {
     private final VideoRepository videoRepository;
     private final VideoRepository videoRepository;
     private final Random random = new SecureRandom();
     private final Random random = new SecureRandom();
     private final BannerVideoMapper bannerVideoMapper;
     private final BannerVideoMapper bannerVideoMapper;
-    private final UserFeignClient userFeignClient;
 
 
     public VideoPostQueryImpl(VideoPostMapper videoPostMapper, VideoFileMapper videoFileMapper, VideoTagMapper videoTagMapper,
     public VideoPostQueryImpl(VideoPostMapper videoPostMapper, VideoFileMapper videoFileMapper, VideoTagMapper videoTagMapper,
                               VideoStatisticMapper videoStatisticMapper, VideoCategoryPostMapper videoCategoryPostMapper,
                               VideoStatisticMapper videoStatisticMapper, VideoCategoryPostMapper videoCategoryPostMapper,
                               ContentPermission contentPermission, CategoryService categoryService,
                               ContentPermission contentPermission, CategoryService categoryService,
                               VideoRepository videoRepository, VideoPostTagMapper videoPostTagMapper,
                               VideoRepository videoRepository, VideoPostTagMapper videoPostTagMapper,
-                              BannerVideoMapper bannerVideoMapper, UserFeignClient userFeignClient) {
+                              BannerVideoMapper bannerVideoMapper) {
         this.videoPostMapper = videoPostMapper;
         this.videoPostMapper = videoPostMapper;
         this.videoFileMapper = videoFileMapper;
         this.videoFileMapper = videoFileMapper;
         this.videoTagMapper = videoTagMapper;
         this.videoTagMapper = videoTagMapper;
@@ -81,7 +77,6 @@ public class VideoPostQueryImpl implements VideoPostQuery {
         this.videoRepository = videoRepository;
         this.videoRepository = videoRepository;
         this.videoPostTagMapper = videoPostTagMapper;
         this.videoPostTagMapper = videoPostTagMapper;
         this.bannerVideoMapper = bannerVideoMapper;
         this.bannerVideoMapper = bannerVideoMapper;
-        this.userFeignClient = userFeignClient;
     }
     }
 
 
     public List<VideoCard> getVideoCards(List<String> videoIds) {
     public List<VideoCard> getVideoCards(List<String> videoIds) {
@@ -262,9 +257,6 @@ public class VideoPostQueryImpl implements VideoPostQuery {
         }
         }
 
 
         long publishBy = videoPost.getPublishBy();
         long publishBy = videoPost.getPublishBy();
-        String userInfoJson = userFeignClient.getUserInfo(publishBy);
-        UserInfo userInfo = JsonConverter.jsonToObject(userInfoJson, UserInfo.class);
-
         String publishByStr = accountQuery.getUserIdStr(publishBy);
         String publishByStr = accountQuery.getUserIdStr(publishBy);
         VideoStatistic videoStatistic = videoStatisticMapper.findByVideoId(videoId);
         VideoStatistic videoStatistic = videoStatisticMapper.findByVideoId(videoId);
         VideoDetail videoDetail = new VideoDetail(videoPost, videoStatistic, publishByStr);
         VideoDetail videoDetail = new VideoDetail(videoPost, videoStatistic, publishByStr);

+ 11 - 0
content/content-service/src/main/resources/application.yml

@@ -37,6 +37,9 @@ spring:
         - veth.*
         - veth.*
       preferred-networks:
       preferred-networks:
         - 192.168
         - 192.168
+    loadbalancer:
+      cache:
+        enabled: true
 mybatis:
 mybatis:
   configuration:
   configuration:
     map-underscore-to-camel-case: true
     map-underscore-to-camel-case: true
@@ -48,6 +51,14 @@ eureka:
   client:
   client:
     register-with-eureka: true
     register-with-eureka: true
     fetch-registry: true
     fetch-registry: true
+feign:
+  circuitbreaker:
+    enabled: true
+  httpclient:
+    hc5:
+      enabled: true
+      connection-request-timeout-unit: seconds
+      connection-request-timeout: 1
 management:
 management:
   endpoints:
   endpoints:
     web:
     web: