当前位置:首页 > 科技  > 软件

聊聊分布式服务下的八种异步实现方式

来源: 责编: 时间:2023-11-03 17:06:58 231观看
导读一、异步的八种实现方式1、线程Thread2、Future3、异步框架CompletableFuture4、Spring注解@Async5、Spring ApplicationEvent事件6、消息队列7、第三方异步框架,比如Hutool的ThreadUtil8、Guava异步二、什么是异步?首

一、异步的八种实现方式

1、线程ThreadD0J28资讯网——每日最新资讯28at.com

2、FutureD0J28资讯网——每日最新资讯28at.com

3、异步框架CompletableFutureD0J28资讯网——每日最新资讯28at.com

4、Spring注解@AsyncD0J28资讯网——每日最新资讯28at.com

5、Spring ApplicationEvent事件D0J28资讯网——每日最新资讯28at.com

6、消息队列D0J28资讯网——每日最新资讯28at.com

7、第三方异步框架,比如Hutool的ThreadUtilD0J28资讯网——每日最新资讯28at.com

8、Guava异步D0J28资讯网——每日最新资讯28at.com

二、什么是异步?

首先我们先看一个常见的用户下单的场景:D0J28资讯网——每日最新资讯28at.com

D0J28资讯网——每日最新资讯28at.com

什么是异步?

在同步操作中,我们执行到 发送短信 的时候,我们必须等待这个方法彻底执行完才能执行 赠送积分 这个操作,如果 赠送积分 这个动作执行时间较长,发送短信需要等待,这就是典型的同步场景。D0J28资讯网——每日最新资讯28at.com

实际上,发送短信和赠送积分没有任何的依赖关系,通过异步,我们可以实现赠送积分和发送短信这两个操作能够同时进行,比如:D0J28资讯网——每日最新资讯28at.com

异步异步D0J28资讯网——每日最新资讯28at.com

这就是所谓的异步,是不是非常简单,下面就说说异步的几种实现方式吧。D0J28资讯网——每日最新资讯28at.com

三、异步编程

1、线程异步

public class AsyncThread extends Thread {    @Override    public void run() {        System.out.println("Current thread name:" + Thread.currentThread().getName() + " Send email success!");    }    public static void main(String[] args) {        AsyncThread asyncThread = new AsyncThread();        asyncThread.run();    }}

当然如果每次都创建一个Thread线程,频繁的创建、销毁,浪费系统资源,我们可以采用线程池:D0J28资讯网——每日最新资讯28at.com

private ExecutorService executorService = Executors.newCachedThreadPool();public void fun() {    executorService.submit(new Runnable() {        @Override        public void run() {            log.info("执行业务逻辑...");        }    });}

可以将业务逻辑封装到Runnable或Callable中,交由线程池来执行。D0J28资讯网——每日最新资讯28at.com

2、 Future异步

@Slf4jpublic class FutureManager {    public String execute() throws Exception {        ExecutorService executor = Executors.newFixedThreadPool(1);        Future<String> future = executor.submit(new Callable<String>() {            @Override            public String call() throws Exception {                System.out.println(" --- task start --- ");                Thread.sleep(3000);                System.out.println(" --- task finish ---");                return "this is future execute final result!!!";            }        });        //这里需要返回值时会阻塞主线程        String result = future.get();        log.info("Future get result: {}", result);        return result;    }    @SneakyThrows    public static void main(String[] args) {        FutureManager manager = new FutureManager();        manager.execute();    }}

输出结果:D0J28资讯网——每日最新资讯28at.com

--- task start ---  --- task finish --- Future get result: this is future execute final result!!!

(1) Future的不足之处

Future的不足之处的包括以下几点:D0J28资讯网——每日最新资讯28at.com

  • 无法被动接收异步任务的计算结果:虽然我们可以主动将异步任务提交给线程池中的线程来执行,但是待异步任务执行结束之后,主线程无法得到任务完成与否的通知,它需要通过get方法主动获取任务执行的结果。
  • Future件彼此孤立:有时某一个耗时很长的异步任务执行结束之后,你想利用它返回的结果再做进一步的运算,该运算也会是一个异步任务,两者之间的关系需要程序开发人员手动进行绑定赋予,Future并不能将其形成一个任务流(pipeline),每一个Future都是彼此之间都是孤立的,所以才有了后面的CompletableFuture,CompletableFuture就可以将多个Future串联起来形成任务流。
  • Futrue没有很好的错误处理机制:截止目前,如果某个异步任务在执行发的过程中发生了异常,调用者无法被动感知,必须通过捕获get方法的异常才知晓异步任务执行是否出现了错误,从而在做进一步的判断处理。

3、CompletableFuture实现异步

public class CompletableFutureCompose {    /**     * thenAccept子任务和父任务公用同一个线程     */    @SneakyThrows    public static void thenRunAsync() {        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {            System.out.println(Thread.currentThread() + " cf1 do something....");            return 1;        });        CompletableFuture<Void> cf2 = cf1.thenRunAsync(() -> {            System.out.println(Thread.currentThread() + " cf2 do something...");        });        //等待任务1执行完成        System.out.println("cf1结果->" + cf1.get());        //等待任务2执行完成        System.out.println("cf2结果->" + cf2.get());    }    public static void main(String[] args) {        thenRunAsync();    }}

我们不需要显式使用ExecutorService,CompletableFuture 内部使用了ForkJoinPool来处理异步任务,如果在某些业务场景我们想自定义自己的异步线程池也是可以的。D0J28资讯网——每日最新资讯28at.com

4、Spring的@Async异步

(1)自定义异步线程池

/** * 线程池参数配置,多个线程池实现线程池隔离,@Async注解,默认使用系统自定义线程池,可在项目中设置多个线程池,在异步调用的时候,指明需要调用的线程池名称,比如:@Async("taskName")@EnableAsync@Configurationpublic class TaskPoolConfig {    /**     * 自定义线程池     *     **/    @Bean("taskExecutor")    public Executor taskExecutor() {        //返回可用处理器的Java虚拟机的数量 12        int i = Runtime.getRuntime().availableProcessors();        System.out.println("系统最大线程数  :" + i);        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        //核心线程池大小        executor.setCorePoolSize(16);        //最大线程数        executor.setMaxPoolSize(20);        //配置队列容量,默认值为Integer.MAX_VALUE        executor.setQueueCapacity(99999);        //活跃时间        executor.setKeepAliveSeconds(60);        //线程名字前缀        executor.setThreadNamePrefix("asyncServiceExecutor -");        //设置此执行程序应该在关闭时阻止的最大秒数,以便在容器的其余部分继续关闭之前等待剩余的任务完成他们的执行        executor.setAwaitTerminationSeconds(60);        //等待所有的任务结束后再关闭线程池        executor.setWaitForTasksToCompleteOnShutdown(true);        return executor;    }}

(2) AsyncService

public interface AsyncService {    MessageResult sendSms(String callPrefix, String mobile, String actionType, String content);    MessageResult sendEmail(String email, String subject, String content);}@Slf4j@Servicepublic class AsyncServiceImpl implements AsyncService {    @Autowired    private IMessageHandler mesageHandler;    @Override    @Async("taskExecutor")    public MessageResult sendSms(String callPrefix, String mobile, String actionType, String content) {        try {            Thread.sleep(1000);            mesageHandler.sendSms(callPrefix, mobile, actionType, content);        } catch (Exception e) {            log.error("发送短信异常 -> ", e)        }    }        @Override    @Async("taskExecutor")    public sendEmail(String email, String subject, String content) {        try {            Thread.sleep(1000);            mesageHandler.sendsendEmail(email, subject, content);        } catch (Exception e) {            log.error("发送email异常 -> ", e)        }    }}

在实际项目中, 使用@Async调用线程池,推荐等方式是是使用自定义线程池的模式,不推荐直接使用@Async直接实现异步。D0J28资讯网——每日最新资讯28at.com

5、Spring ApplicationEvent事件实现异步

(1)定义事件

public class AsyncSendEmailEvent extends ApplicationEvent {    /**     * 邮箱     **/    private String email;   /**     * 主题     **/    private String subject;    /**     * 内容     **/    private String content;      /**     * 接收者     **/    private String targetUserId;}

(2)定义事件处理器

@Slf4j@Componentpublic class AsyncSendEmailEventHandler implements ApplicationListener<AsyncSendEmailEvent> {    @Autowired    private IMessageHandler mesageHandler;        @Async("taskExecutor")    @Override    public void onApplicationEvent(AsyncSendEmailEvent event) {        if (event == null) {            return;        }        String email = event.getEmail();        String subject = event.getSubject();        String content = event.getContent();        String targetUserId = event.getTargetUserId();        mesageHandler.sendsendEmailSms(email, subject, content, targerUserId);      }}

另外,可能有些时候采用ApplicationEvent实现异步的使用,当程序出现异常错误的时候,需要考虑补偿机制,那么这时候可以结合Spring Retry重试来帮助我们避免这种异常造成数据不一致问题。D0J28资讯网——每日最新资讯28at.com

6、消息队列

(1)回调事件消息生产者

@Slf4j@Componentpublic class CallbackProducer {    @Autowired    AmqpTemplate amqpTemplate;    public void sendCallbackMessage(CallbackDTO allbackDTO, final long delayTimes) {        log.info("生产者发送消息,callbackDTO,{}", callbackDTO);        amqpTemplate.convertAndSend(CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getExchange(), CallbackQueueEnum.QUEUE_GENSEE_CALLBACK.getRoutingKey(), JsonMapper.getInstance().toJson(genseeCallbackDTO), new MessagePostProcessor() {            @Override            public Message postProcessMessage(Message message) throws AmqpException {                //给消息设置延迟毫秒值,通过给消息设置x-delay头来设置消息从交换机发送到队列的延迟时间                message.getMessageProperties().setHeader("x-delay", delayTimes);                message.getMessageProperties().setCorrelationId(callbackDTO.getSdkId());                return message;            }        });    }}

(2)回调事件消息消费者

@Slf4j@Component@RabbitListener(queues = "message.callback", containerFactory = "rabbitListenerContainerFactory")public class CallbackConsumer {    @Autowired    private IGlobalUserService globalUserService;    @RabbitHandler    public void handle(String json, Channel channel, @Headers Map<String, Object> map) throws Exception {        if (map.get("error") != null) {            //否认消息            channel.basicNack((Long) map.get(AmqpHeaders.DELIVERY_TAG), false, true);            return;        }        try {            CallbackDTO callbackDTO = JsonMapper.getInstance().fromJson(json, CallbackDTO.class);            //执行业务逻辑            globalUserService.execute(callbackDTO);            //消息消息成功手动确认,对应消息确认模式acknowledge-mode: manual            channel.basicAck((Long) map.get(AmqpHeaders.DELIVERY_TAG), false);        } catch (Exception e) {            log.error("回调失败 -> {}", e);        }    }}

7、ThreadUtil异步工具类

@Slf4jpublic class ThreadUtils {    public static void main(String[] args) {        for (int i = 0; i < 3; i++) {            ThreadUtil.execAsync(() -> {                ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();                int number = threadLocalRandom.nextInt(20) + 1;                System.out.println(number);            });            log.info("当前第:" + i + "个线程");        }        log.info("task finish!");    }}

8、Guava异步

Guava的ListenableFuture顾名思义就是可以监听的Future,是对java原生Future的扩展增强。我们知道Future表示一个异步计算任务,当任务完成时可以得到计算结果。如果我们希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态。这样做,代码复杂,而且效率低下。使用「Guava ListenableFuture」可以帮我们检测Future是否完成了,不需要再通过get()方法苦苦等待异步的计算结果,如果完成就自动调用回调函数,这样可以减少并发程序的复杂度。D0J28资讯网——每日最新资讯28at.com

ListenableFuture是一个接口,它从jdk的Future接口继承,添加了void addListener(Runnable listener, Executor executor)方法。D0J28资讯网——每日最新资讯28at.com

我们看下如何使用ListenableFuture。首先需要定义ListenableFuture的实例:D0J28资讯网——每日最新资讯28at.com

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());        final ListenableFuture<Integer> listenableFuture = executorService.submit(new Callable<Integer>() {            @Override            public Integer call() throws Exception {                log.info("callable execute...")                TimeUnit.SECONDS.sleep(1);                return 1;            }        });

首先通过MoreExecutors类的静态方法listeningDecorator方法初始化一个ListeningExecutorService的方法,然后使用此实例的submit方法即可初始化ListenableFuture对象。D0J28资讯网——每日最新资讯28at.com

ListenableFuture要做的工作,在Callable接口的实现类中定义,这里只是休眠了1秒钟然后返回一个数字1,有了ListenableFuture实例,可以执行此Future并执行Future完成之后的回调函数。D0J28资讯网——每日最新资讯28at.com

Futures.addCallback(listenableFuture, new FutureCallback<Integer>() {    @Override    public void onSuccess(Integer result) {        //成功执行...        System.out.println("Get listenable future's result with callback " + result);    }    @Override    public void onFailure(Throwable t) {        //异常情况处理...        t.printStackTrace();    }});

那么,以上就是本期介绍的分布式服务下实现异步的八种方式了,供您参考。D0J28资讯网——每日最新资讯28at.com

本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-16837-0.html聊聊分布式服务下的八种异步实现方式

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 一文搞懂为什么选择 Java 虚拟线程?

下一篇: Go异步任务解决方案:Asynq

标签:
  • 热门焦点
  • Mate60手机壳曝光 致敬自己的经典设计

    Mate60手机壳曝光 致敬自己的经典设计

    8月3日消息,今天下午博主数码闲聊站带来了华为Mate60的第三方手机壳图,可以让我们在真机发布之前看看这款华为全新旗舰的大致轮廓。从曝光的图片看,Mate 60背后摄像头面积依然
  • Redmi Buds 4开箱简评:才199还有降噪 可以无脑入

    Redmi Buds 4开箱简评:才199还有降噪 可以无脑入

    在上个月举办的Redmi Note11T Pro系列新机发布会上,除了两款手机新品之外,Redmi还带来了两款TWS真无线蓝牙耳机产品,Redmi Buds 4和Redmi Buds 4 Pro,此前我们在Redmi Note11T
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • 8月总票房已突破10亿!《封神》第一:口碑已经成了

    8月总票房已突破10亿!《封神》第一:口碑已经成了

    8月5日消息,据灯塔专业版数据,截至8月5日9时35分,8月总票房(含预售)已突破10亿。其中,《封神》以大比分的优势领先。根据官方消息,目前该片总票房已经超过14.
  • CSS单标签实现转转logo

    CSS单标签实现转转logo

    转转品牌升级后更新了全新的Logo,今天我们用纯CSS来实现转转的新Logo,为了有一定的挑战性,这里我们只使用一个标签实现,将最大化的使用CSS能力完成Logo的绘制与动画效果。新logo
  • 一篇文章带你了解 CSS 属性选择器

    一篇文章带你了解 CSS 属性选择器

    属性选择器对带有指定属性的 HTML 元素设置样式。可以为拥有指定属性的 HTML 元素设置样式,而不仅限于 class 和 id 属性。一、了解属性选择器CSS属性选择器提供了一种简单而
  • 三星折叠屏手机去年销售近1000万台 今年目标定为1500万

    三星折叠屏手机去年销售近1000万台 今年目标定为1500万

    7月29日消息,三星率先发力可折叠手机市场,在全球市场已经取得了非常亮眼的成绩,接下来会进一步巩固和扩大这一优势。三星在推出Galaxy Z Flip5和Galax
  • 微软发布Windows 11新版 引入全新任务栏状态

    微软发布Windows 11新版 引入全新任务栏状态

    近日,微软发布了Windows 11新版,而Build 22563更新主要引入了几周前曝光的平板模式任务栏等,系统更流畅了。更新中,Windows 11加入了专门针对平板优化的任务栏
  • 三翼鸟智能家居亮相电博会,让用户体验更真实

    三翼鸟智能家居亮相电博会,让用户体验更真实

    2021电博会在青岛国际会展中心开幕中,三翼鸟直接把“家”搬到了现场,成为了展会的一大看点。这也是三翼鸟继9月9日发布了行业首个一站式定制智慧家平台后的
Top
Baidu
map