如何实现 全链路异步

206 阅读6分钟

全链路异步的好处

  1. 提高并发处理能力:全链路异步处理使系统能够同时处理更多的请求,因为线程不需要等待I/O操作完成,可以立即切换到处理其他任务。
  2. 减少资源消耗:异步处理减少了线程的等待时间,降低了CPU和内存的消耗,提高了资源利用率。
  3. 提高响应速度:异步处理使得系统能够更快地响应请求,因为I/O操作和其他耗时操作可以并行处理。
  4. 增强系统弹性:全链路异步处理使系统更具弹性和可扩展性,能够更好地应对高并发和高负载的场景。

要实现一个使用 WebFlux 的网关,同时在应用中调用 Dubbo 服务、Redis 和 MySQL,并且实现全链路异步处理的系统设计,可以按以下步骤进行:

步骤 1:配置项目依赖

确保你的项目包含所有必要的依赖,包括 WebFlux、Spring Cloud、Dubbo、Lettuce 和 MySQL。

pom.xml 示例

<dependencies>
    <!-- Spring Boot and WebFlux -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <!-- Spring Cloud -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    <!-- Dubbo -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.22</version>
    </dependency>
    <!-- Redis (Lettuce) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <!-- MySQL -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
    
</dependencies>

网关中的过滤器异步调用Redis和Dubbo

在网关的过滤器中异步调用Redis和Dubbo,可以确保在请求进入网关时就开始异步处理,减少延迟。以下是如何在过滤器中实现异步调用Redis和Dubbo的示例:

示例:网关过滤器异步调用Redis和Dubbo

package com.example.gateway.filter;

import com.example.gateway.service.RedisService;
import com.example.gateway.service.UserService;
import com.example.gateway.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
public class AsyncGatewayFilter implements WebFilter {

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserService userService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        String userId = exchange.getRequest().getHeaders().getFirst("USER_ID");
        String redisKey = "user:" + userId;

        // 先从缓存获取
        Mono<String> redisMono = redisService.getValue(redisKey);

        // 如果缓存中没有数据,再调用 Dubbo 服务,并将结果存入缓存
        Mono<User> userMono = redisMono
            .flatMap(value -> {
                // 如果缓存中有数据,直接返回
                return Mono.just(new User(userId, value));
            })
            .switchIfEmpty(userService.getUserById(Long.valueOf(userId))
                .flatMap(user -> {
                    // 将结果存入缓存
                    return redisService.setValue(redisKey, user.getName())
                        .then(Mono.just(user));
                })
            );

        return userMono.flatMap(user -> {
                    // 设置用户信息到请求头
                    exchange.getRequest().mutate()
                            .header("REDIS_VALUE", user.getName())
                            .header("USER_NAME", user.getName());

                    return chain.filter(exchange.mutate().request(exchange.getRequest().mutate().build()).build());
                })
                .switchIfEmpty(Mono.defer(() -> {
                    exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                    return exchange.getResponse().setComplete();
                }));
    }
}

User服务中的Controller异步调用Redis、Dubbo和MySQL

在User服务中,Controller可以异步调用Redis、Dubbo和MySQL,实现全链路异步处理。以下是如何在Controller中实现异步调用的示例:

示例:User服务Controller异步调用Redis、Dubbo和MySQL

package com.example.user.controller;

import com.example.user.service.ApplicationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class UserController {

    @Autowired
    private ApplicationService applicationService;

    @GetMapping("/process")
    public Mono<String> process(@RequestParam Long userId, @RequestParam String redisKey) {
        return applicationService.processUser(userId, redisKey);
    }
}

示例:ApplicationService异步调用Redis、Dubbo和MySQL

package com.example.user.service;

import com.example.common.model.User;
import com.example.gateway.service.RedisService;
import com.example.gateway.service.UserService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.concurrent.TimeUnit;

@Service
public class ApplicationService {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisService redisService;

    @PersistenceContext
    private EntityManager entityManager;

    private Cache<Long, String> localCache;

    @PostConstruct
    public void init() {
        localCache = Caffeine.newBuilder()
                .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间
                .maximumSize(1000) // 设置缓存最大容量
                .build();
    }

    public Mono<String> processUser(Long userId, String redisKey) {
        // 从本地缓存获取
        String localValue = localCache.getIfPresent(userId);
        if (localValue != null) {
            return Mono.just(localValue);
        }

        // 从Redis获取
        Mono<String> redisMono = redisService.getValue(redisKey);

        return redisMono.switchIfEmpty(
                // 从Dubbo服务获取
                userService.getUserById(userId)
                        .flatMap(user -> {
                            // 用户信息存储到Redis
                            String userInfo = user.getName();
                            return redisService.setValue(redisKey, userInfo)
                                    .thenReturn(userInfo);
                        })
                        .switchIfEmpty(
                                // 从数据库获取
                                getUserFromDb(userId)
                                        .flatMap(user -> {
                                            // 用户信息存储到Redis
                                            String userInfo = user.getName();
                                            return redisService.setValue(redisKey, userInfo)
                                                    .thenReturn(userInfo);
                                        })
                        )
        ).flatMap(userInfo -> {
            // 缓存到本地
            localCache.put(userId, userInfo);
            return Mono.just(userInfo);
        });
    }

    private Mono<User> getUserFromDb(Long userId) {
        return Mono.fromCallable(() -> {
            Query query = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :userId");
            query.setParameter("userId", userId);
            return (User) query.getSingleResult();
        });
    }
}


优化一下 :

package com.example.user.service;

import com.example.common.model.User; import com.example.gateway.service.RedisService; import com.example.gateway.service.UserService; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import java.util.concurrent.TimeUnit;

@Service public class ApplicationService {

@Autowired
private UserService userService;

@Autowired
private RedisService redisService;

@PersistenceContext
private EntityManager entityManager;

private Cache<Long, String> localCache;

@PostConstruct
public void init() {
    localCache = Caffeine.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间
            .maximumSize(1000) // 设置缓存最大容量
            .build();
}

public Mono<String> processUser(Long userId, String redisKey) {
    return getFromLocalCache(userId)
            .switchIfEmpty(getFromRedis(redisKey)
                    .switchIfEmpty(getFromDubboService(userId, redisKey)
                            .switchIfEmpty(getFromDatabase(userId, redisKey))));
}

private Mono<String> getFromLocalCache(Long userId) {
    String localValue = localCache.getIfPresent(userId);
    if (localValue != null) {
        return Mono.just(localValue);
    }
    return Mono.empty();
}

private Mono<String> getFromRedis(String redisKey) {
    return redisService.getValue(redisKey);
}

private Mono<String> getFromDubboService(Long userId, String redisKey) {
    return userService.getUserById(userId)
            .flatMap(user -> {
                String userInfo = user.getName();
                return redisService.setValue(redisKey, userInfo)
                        .thenReturn(userInfo);
            });
}

private Mono<String> getFromDatabase(Long userId, String redisKey) {
    return getUserFromDb(userId)
            .flatMap(user -> {
                String userInfo = user.getName();
                return redisService.setValue(redisKey, userInfo)
                        .thenReturn(userInfo);
            });
}

private Mono<User> getUserFromDb(Long userId) {
    return Mono.fromCallable(() -> {
        Query query = entityManager.createQuery("SELECT u FROM User u WHERE u.id = :userId");
        query.setParameter("userId", userId);
        return (User) query.getSingleResult();
    });
}

}

注意事项

  1. 全链路异步处理:确保所有调用都是非阻塞的,使用 MonoFlux 来处理异步流。
  2. Dubbo 服务:使用 Mono 作为返回类型,实现异步 Dubbo 调用。
  3. Redis 操作:使用 ReactiveStringRedisTemplate 实现非阻塞 Redis 操作。
  4. 数据库操作:使用 Mono.fromCallable 包装阻塞的 JPA 查询,以实现异步处理。

通过上述配置和实现,你可以实现一个使用 WebFlux 的网关,同时在应用中调用 Dubbo 服务、Redis 和 MySQL,并且实现全链路异步处理的系统。

另外 数据库 如果 不改成 异步 形式 可以使用同步转异步 的模式

package com.crazymaker.springcloud.reactive.user.info.parallel;

import com.crazymaker.springcloud.common.result.RestOut;
import com.crazymaker.springcloud.common.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import rx.Observable;

import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Supplier;

@Slf4j
public class DBFunctionWrapper {

    public static final int QUEUE_CAPACITY = 200;
    public static final int THREAD_COUNT = 128;
    //    static ExecutorService bizPool = ThreadUtil.getIoIntenseTargetThreadPool();
    static LinkedBlockingQueue blockingDeque = new LinkedBlockingQueue(QUEUE_CAPACITY);
    static ExecutorService dbPool = new ThreadPoolExecutor(
            THREAD_COUNT,
            THREAD_COUNT,
            30,
            TimeUnit.SECONDS,
            blockingDeque,
            new ThreadUtil.CustomThreadFactory("db-pool"));

    public static <T> Consumer<MonoSink<T>> dbFunWrapAsync(Supplier<T> bizFun) {
        return sink -> dbPool.execute(() -> {
            log.info("db 方法被调用");
            sink.success(bizFun.get());
        });
    }

    public static <T> Consumer<MonoSink<T>> dbFunWrapSafe(Supplier<T> bizFun) {

        //这个地方,可以使用类似 hystrix 的思想进行熔断
        log.info("队列情况:" + blockingDeque.size(), QUEUE_CAPACITY);
        if (blockingDeque.size() >= QUEUE_CAPACITY) {
            Supplier<T> fallBackFun = () -> (T) RestOut.failed("系统太忙");
            return sink -> sink.success(fallBackFun.get());
        }

        return sink -> dbPool.execute(() -> {
            log.info("db 方法被调用");
            sink.success(bizFun.get());
        });
    }

    public static Mono<String> dbCallHystrix() {
        return Mono.defer(() -> {
            final Mono<String>[] limited = new Mono[]{null};

            Observable<Mono<String>> command = new DbProtectCommand(blockingDeque)
                    .toObservable();
            command.toBlocking()
                    .subscribe(mono -> limited[0] = mono);

            return limited[0];
        });
    }
}

package com.crazymaker.springcloud.reactive.user.info.service.impl;

import com.crazymaker.springcloud.common.util.BeanUtil;
import com.crazymaker.springcloud.common.util.ThreadUtil;
import com.crazymaker.springcloud.reactive.user.info.dao.impl.JpaUserRepositoryImpl;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import com.crazymaker.springcloud.reactive.user.info.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.MonoSink;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

import static com.crazymaker.springcloud.reactive.user.info.parallel.DBFunctionWrapper.dbFunWrapAsync;

@Slf4j
@Service
public class JpaEntityServiceImpl {

    @Resource
    private JpaUserRepositoryImpl userRepository;


    @Transactional
    //增加用户
    public User addUser(User dto) {
        User userEntity = new UserEntity();
        userEntity.setUserId(dto.getUserId());
        userEntity.setName(dto.getName());
        userRepository.insert(userEntity);
        BeanUtil.copyProperties(userEntity, dto);
        return dto;
    }

    @Transactional
    //删除用户
    public User delUser(User dto) {
        userRepository.delete(dto.getUserId());
        return dto;
    }

    //查询全部用户
    public List<User> selectAllUser() {
        log.info("方法 selectAllUser 被调用了");

        return userRepository.selectAll();
    }

    //查询一个用户
    public User selectOne(final Long userId) {

        log.info("方法 selectOne 被调用了");
        try {
            return userRepository.selectOne(userId);

        } catch (Throwable throwable) {
            log.error("db error", throwable);
            return null;
        }

    }

    //查询一个用户
    public Optional<User> selectOneOptional(final Long userId) {

        log.info("方法 selectOne 被调用了");
        try {
            return Optional.of(userRepository.selectOne(userId));

        } catch (Throwable throwable) {
            log.error("db error", throwable);
            return Optional.empty();
        }

    }

    //查询一个用户
    public Optional<User> longSelectOneOptional(final Long userId) {

        log.info("方法 selectOne 被调用了");
        try {
            ThreadUtil.sleepMilliSeconds(100000);
            return Optional.of(userRepository.selectOne(userId));

        } catch (Throwable throwable) {
            log.error("db error", throwable);
            return Optional.empty();
        }

    }

    public Consumer<MonoSink<Optional<User>>> selectOneAsynic(long userId) {
        return dbFunWrapAsync(() -> selectOneOptional(userId));
    }

    public Consumer<MonoSink<Optional<User>>> longSelectOneAsynic(long userId) {
        return dbFunWrapAsync(() -> longSelectOneOptional(userId));
    }

}
package com.crazymaker.springcloud.reactive.user.info.controller;

import com.crazymaker.springcloud.common.dto.UserDTO;
import com.crazymaker.springcloud.common.result.RestOut;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import com.crazymaker.springcloud.reactive.user.info.service.impl.JpaEntityServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveValueOperations;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import reactor.core.scheduler.Schedulers;

import javax.annotation.Resource;
import java.util.Optional;
import java.util.function.Consumer;

import static com.crazymaker.springcloud.reactive.user.info.controller.UserReactiveController.bizPool;
import static com.crazymaker.springcloud.reactive.user.info.dto.User.USER_PREFIX;
import static com.crazymaker.springcloud.reactive.user.info.parallel.DBFunctionWrapper.dbCallHystrix;

/**
 * Mono 和 Flux 适用于两个场景,即:
 * Mono:实现发布者,并返回 0 或 1 个元素,即单对象。
 * Flux:实现发布者,并返回 N 个元素,即 List 列表对象。
 * 有人会问,这为啥不直接返回对象,比如返回 City/Long/List。
 * 原因是,直接使用 Flux 和 Mono 是非阻塞写法,相当于回调方式。
 * 利用函数式可以减少了回调,因此会看不到相关接口。这恰恰是 WebFlux 的好处:集合了非阻塞 + 异步
 */
@Slf4j
@Api(value = "用户信息缓存 学习DEMO", tags = {"用户Cache AsideDEMO"})
@RestController
@RequestMapping("/api/userCacheAside")
public class UserCacheAsideController {


    @Autowired
    private ReactiveRedisTemplate reactiveRedisTemplate;

    @Resource
    JpaEntityServiceImpl jpaEntityService;



    @GetMapping("/detail/v1")
    @ApiOperation(value = "响应式的查看")
    public  Mono<String>  getUser(@RequestParam(value = "userId", required = true) Long userId)
    {
        log.info("方法 getUser 被调用了");

        ReactiveValueOperations<String, String> opsForValue = reactiveRedisTemplate.opsForValue();
        String key = USER_PREFIX + userId;
        Mono<String> fromCache = opsForValue.get(key);

        Consumer<MonoSink<Optional<User>>> fromDbConsumer = jpaEntityService.selectOneAsynic(userId);

        Mono<String> fromDb = Mono.create(fromDbConsumer)
                .flatMap(user -> {
                    log.info("异步设置缓存");
                    RestOut<User> restOut = RestOut.success(user.orElse(null));
                    opsForValue.set(key, restOut.toJson()).subscribe();
                    return Mono.just(RestOut.success(user.orElse(null)).toJson());
                });

        Mono<String> outMono = fromCache.switchIfEmpty(fromDb);

        return outMono.doOnError(e -> Mono.just(RestOut.<String>failed(e.getMessage())));
    }


    //hystrix 限流 + reactor 响应式编程 组合

    @PostMapping("/hystrixSpecial/v1")
    @ApiOperation(value = "熔断hystrixSpecial")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "body", dataTypeClass = User.class, dataType = "User", name = "dto", required = true),
    })
    public Mono<String> hystrixSpecial(@RequestBody User dto) {
        log.info("方法 getUser 被调用了");

         Mono<String> dbCallHystrix = dbCallHystrix();

        Consumer<MonoSink<Optional<User>>> fromDbConsumer = jpaEntityService.longSelectOneAsynic(dto.getUserId());
        Mono<String> fromDb = Mono.create(fromDbConsumer)
                .flatMap(user -> {
                    log.info("异步设置缓存");
                      return Mono.just(RestOut.success(user.orElse(null)).toJson());
                });

        Mono<String> outMono = dbCallHystrix.switchIfEmpty(fromDb).subscribeOn(Schedulers.fromExecutorService(bizPool));
        return outMono.doOnError(e -> Mono.just(RestOut.<String>failed(e.getMessage())));
    }

    @PostMapping("/detailWithHystrix/v1")
    @ApiOperation(value = "熔断+响应式的查看")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "body", dataTypeClass = User.class, dataType = "User", name = "dto", required = true),
    })

    public Mono<String> getUserWithHystrix(@RequestBody User dto) {
        log.info("方法 getUser 被调用了");

        ReactiveValueOperations<String, String> opsForValue = reactiveRedisTemplate.opsForValue();
        String key = USER_PREFIX + dto.getUserId();
        Mono<String> fromCache = opsForValue.get(key);

        Mono<String> dbCallHystrix = dbCallHystrix();

        Consumer<MonoSink<Optional<User>>> fromDbConsumer = jpaEntityService.selectOneAsynic(dto.getUserId());
        Mono<String> fromDb = Mono.create(fromDbConsumer)
                .flatMap(user -> {
                    log.info("异步设置缓存");
                    RestOut<User> restOut = RestOut.success(user.orElse(null));
                    opsForValue.set(key, restOut.toJson()).subscribe();
                    return Mono.just(RestOut.success(user.orElse(null)).toJson());
                });

        Mono<String> responseMono = Mono.create(monoSink -> {
            // callback的处理类,并传入monoSink供使用
            final String[] temp = new String[1];
            dbCallHystrix.switchIfEmpty(fromDb).flatMap(s-> {
                temp[0] =s;
                return Mono.just(s);
            }).block();
            monoSink.success(temp[0]);
        });

        Mono<String> outMono = fromCache.switchIfEmpty(responseMono);


        return outMono.doOnError(e -> Mono.just(RestOut.<String>failed(e.getMessage())));
    }


}