全链路异步的好处
- 提高并发处理能力:全链路异步处理使系统能够同时处理更多的请求,因为线程不需要等待I/O操作完成,可以立即切换到处理其他任务。
- 减少资源消耗:异步处理减少了线程的等待时间,降低了CPU和内存的消耗,提高了资源利用率。
- 提高响应速度:异步处理使得系统能够更快地响应请求,因为I/O操作和其他耗时操作可以并行处理。
- 增强系统弹性:全链路异步处理使系统更具弹性和可扩展性,能够更好地应对高并发和高负载的场景。
要实现一个使用 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();
});
}
}
注意事项
- 全链路异步处理:确保所有调用都是非阻塞的,使用
Mono或Flux来处理异步流。 - Dubbo 服务:使用
Mono作为返回类型,实现异步 Dubbo 调用。 - Redis 操作:使用
ReactiveStringRedisTemplate实现非阻塞 Redis 操作。 - 数据库操作:使用
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())));
}
}