소스 검색

user-service 中添加一个 producer 的 hystrix 降级熔断 demo

reghao 8 달 전
부모
커밋
ecc1096da1

+ 78 - 0
user/user-service/src/main/java/cn/reghao/tnb/user/app/config/MyHystrixConcurrencyStrategy.java

@@ -0,0 +1,78 @@
+package cn.reghao.tnb.user.app.config;
+
+import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.Callable;
+
+/**
+ * @author reghao
+ * @date 2025-07-22 14:06:34
+ */
+@Slf4j
+public class MyHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
+    @Override
+    public <T> Callable<T> wrapCallable(Callable<T> callable) {
+        /**
+         * 1 获取当前线程的threadlocalmap
+         */
+        Object currentThreadlocalMap = getCurrentThreadlocalMap();
+
+        Callable<T> finalCallable = new Callable<T>() {
+            // 2
+            private Object callerThreadlocalMap = currentThreadlocalMap;
+            // 3
+            private Callable<T> targetCallable = callable;
+
+            @Override
+            public T call() throws Exception {
+                /**
+                 * 4 将工作线程的原有线程变量保存起来
+                 */
+                Object oldThreadlocalMapOfWorkThread = getCurrentThreadlocalMap();
+                /**
+                 *5 将本线程的线程变量,设置为caller的线程变量
+                 */
+                setCurrentThreadlocalMap(callerThreadlocalMap);
+
+                try {
+                    // 6
+                    return targetCallable.call();
+                } finally {
+                    // 7
+                    setCurrentThreadlocalMap(oldThreadlocalMapOfWorkThread);
+                    log.info("restore work thread's threadlocal");
+                }
+
+            }
+        };
+
+        return finalCallable;
+    }
+
+    private Object getCurrentThreadlocalMap() {
+        Thread thread = Thread.currentThread();
+        try {
+            Field field = Thread.class.getDeclaredField("threadLocals");
+            field.setAccessible(true);
+            Object o = field.get(thread);
+            return o;
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            log.error("{}", e);
+        }
+        return null;
+    }
+
+    private void setCurrentThreadlocalMap(Object newThreadLocalMap) {
+        Thread thread = Thread.currentThread();
+        try {
+            Field field = Thread.class.getDeclaredField("threadLocals");
+            field.setAccessible(true);
+            field.set(thread,newThreadLocalMap);
+
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            log.error("{}", e);
+        }
+    }
+}

+ 1 - 1
user/user-service/src/main/java/cn/reghao/tnb/user/app/config/HystrixConfig.java → user/user-service/src/main/java/cn/reghao/tnb/user/app/config/SpringCloudConfig.java

@@ -9,5 +9,5 @@ import org.springframework.context.annotation.Configuration;
  */
 @Configuration
 @EnableHystrix
-public class HystrixConfig {
+public class SpringCloudConfig {
 }

+ 87 - 0
user/user-service/src/main/java/cn/reghao/tnb/user/app/controller/UserControllerHystrix.java

@@ -0,0 +1,87 @@
+package cn.reghao.tnb.user.app.controller;
+
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.tnb.user.app.service.UserServiceHystrix;
+import cn.reghao.tnb.user.app.service.UserProfileService;
+import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
+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.Operation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2025-07-19 23:59:57
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/user/hystrix")
+@DefaultProperties(defaultFallback = "defaultFallback")
+public class UserControllerHystrix {
+    private final UserProfileService userProfileService;
+    private final UserServiceHystrix userServiceHystrix;
+
+    public UserControllerHystrix(UserProfileService userProfileService, UserServiceHystrix userServiceHystrix) {
+        this.userProfileService = userProfileService;
+        this.userServiceHystrix = userServiceHystrix;
+    }
+
+    @Operation(summary = "获取用户资料", description = "N")
+    @GetMapping(value = "/info", produces = MediaType.APPLICATION_JSON_VALUE)
+    @HystrixCommand(
+            fallbackMethod = "getUserInfoFallback",
+            commandProperties = {
+                    @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "false"),
+                    // 5 秒内, 请求达到 4 个以上, 失败率达到 50% 以上, 则开启熔断, 此时会直接使用 fallback, 不会再调用业务方法
+                    // 熔断 3 秒后有请求到来, 则会再执行一次业务方法, 执行失败则继续保持熔断, 执行成功则取消熔断
+                    @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 getUserInfo(@RequestParam("userId") Long userId) {
+        UserHolder.setUser("reghao");
+        userServiceHystrix.hello();
+        log.info("user -> {}", UserHolder.getUser());
+        //UserInfo userInfo = userProfileService.getUserInfo(userId);
+        String userInfoJson = userServiceHystrix.getUserInfo(userId);
+        return userInfoJson;
+    }
+
+    public String getUserInfoFallback(Long userId) {
+        System.out.printf("hystrix fallback with userId %s\n", userId);
+        Map<String, String> map = new HashMap<>();
+        map.put("code", "0");
+        map.put("msg", "hystrix fallback");
+        return JsonConverter.objectToJson(map);
+    }
+
+    public String defaultFallback() {
+        Map<String, String> map = new HashMap<>();
+        map.put("code", "0");
+        map.put("msg", "hystrix default fallback");
+        return JsonConverter.objectToJson(map);
+    }
+
+    public static class UserHolder{
+        private static ThreadLocal<String> userHolder = new ThreadLocal<>();
+
+        public static void setUser(String user){
+            userHolder.set(user);
+        }
+
+        public static String getUser(){
+            return userHolder.get();
+        }
+
+        public static void clean(){
+            userHolder.remove();
+        }
+    }
+}

+ 0 - 50
user/user-service/src/main/java/cn/reghao/tnb/user/app/controller/UserFeignController.java

@@ -1,50 +0,0 @@
-package cn.reghao.tnb.user.app.controller;
-
-import cn.reghao.jutil.jdk.serializer.JsonConverter;
-import cn.reghao.tnb.account.api.iface.AccountQuery;
-import cn.reghao.tnb.user.api.dto.UserInfo;
-import cn.reghao.tnb.user.app.service.UserProfileService;
-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.Operation;
-import org.apache.dubbo.config.annotation.DubboReference;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author reghao
- * @date 2025-07-19 23:59:57
- */
-@RestController
-@RequestMapping("/api/user/feign")
-public class UserFeignController {
-    @DubboReference(check = false, retries = 0, timeout = 60_000)
-    private AccountQuery accountQuery;
-    private final UserProfileService userProfileService;
-
-    public UserFeignController(UserProfileService userProfileService) {
-        this.userProfileService = userProfileService;
-    }
-
-    @Operation(summary = "获取用户资料", description = "N")
-    @GetMapping(value = "/info", produces = MediaType.APPLICATION_JSON_VALUE)
-    @HystrixCommand(fallbackMethod = "getUserInfoFallback", commandProperties = {
-            @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "false")
-    })
-    public String getUserInfo(@RequestParam("userId") Long userId) {
-        UserInfo userInfo = userProfileService.getUserInfo(userId);
-        return JsonConverter.objectToJson(userInfo);
-    }
-
-    public String getUserInfoFallback(Long userId) {
-        System.out.printf("hystrix fallback with userId %s\n", userId);
-        Map<String, String> map = new HashMap<>();
-        map.put("code", "0");
-        map.put("msg", "hystrix fallback");
-        return JsonConverter.objectToJson(map);
-    }
-}

+ 76 - 0
user/user-service/src/main/java/cn/reghao/tnb/user/app/service/UserServiceHystrix.java

@@ -0,0 +1,76 @@
+package cn.reghao.tnb.user.app.service;
+
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
+import cn.reghao.tnb.user.api.dto.UserInfo;
+import cn.reghao.tnb.user.app.controller.UserControllerHystrix;
+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 lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author reghao
+ * @date 2025-07-21 13:51:11
+ */
+@Slf4j
+@Service
+public class UserServiceHystrix {
+    private final UserProfileService userProfileService;
+
+    public UserServiceHystrix(UserProfileService userProfileService) {
+        this.userProfileService = userProfileService;
+    }
+
+    @HystrixCommand
+    /*@HystrixCommand(
+            commandProperties = {
+                    @HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
+            }
+    )*/
+    public String hello() {
+        log.info("user -> {}", UserControllerHystrix.UserHolder.getUser());
+        return null;
+    }
+
+    public String helloFallback() {
+        log.info("user -> {}", UserControllerHystrix.UserHolder.getUser());
+        return null;
+    }
+
+    @HystrixCommand(
+            fallbackMethod = "getUserInfoFallback",
+            commandProperties = {
+                    @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED, value = "false"),
+                    // 5 秒内, 请求达到 4 个以上, 失败率达到 50% 以上, 则开启熔断, 此时会直接使用 fallback, 不会再调用业务方法
+                    // 熔断 3 秒后有请求到来, 则会再执行一次业务方法, 执行失败则继续保持熔断, 执行成功则取消熔断
+                    @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 getUserInfo(Long userId) {
+        if (userId % 3 == 0) {
+            try {
+                log.info("{} 模拟耗时, 休眠 10s", userId);
+                Thread.sleep(10_000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        UserInfo userInfo = userProfileService.getUserInfo(userId);
+        return JsonConverter.objectToJson(userInfo);
+    }
+
+    public String getUserInfoFallback(Long userId) {
+        log.info("hystrix fallback with userId {}", userId);
+        Map<String, String> map = new HashMap<>();
+        map.put("code", "0");
+        map.put("msg", "hystrix fallback");
+        return JsonConverter.objectToJson(map);
+    }
+}