多线程和HTTP异步

多线程和HTTP异步


场景
基于一个jvm下的批量执行业务。为了提高用户体验度。再多服务器多应用的情景下,使用多线程和HTTP异步。

注重点
①数据重复消费
②数据一致性
③HTTP数据交互

业务实例
一、线程池配置


import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import com.syw.common.utils.Threads;

/**
 * 线程池配置
 *
 * @author syw
 **/
@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer
{
    // 核心线程池大小
    private int corePoolSize = 50;

    // 最大可创建的线程数
    private int maxPoolSize = 200;

    // 队列最大长度
    private int queueCapacity = 1000;

    // 线程池维护线程所允许的空闲时间
    private int keepAliveSeconds = 300;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
    {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.initialize();
        // 线程池对拒绝任务(无线程可用)的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

二、具体业务实现
1.添加redis缓存锁,避免数据被重复消费。
注:非高并发场景,高并发场景可考虑redis分布式锁。


 //唯一key
 String order_key ="";
  for(Object object: List){
     order_key =object.getId();
     //判断redis是否存在key值
     if(!redisCache.redisTemplate.hasKey(order_key)){
     	//根据具体业务存储key值和设置失效时长。
         redisCache.redisTemplate.opsForValue().set(order_key,1,2, TimeUnit.DAYS);
     }else{
         throw new CustomException("业务数据被执行或正在执行!");
     }
 }

2.进入多线程方法

        System.out.println("....线程池检测开始");
        //任务单执行
        for(Object object:List){
            service.updateOrderStateByOrderId(object);
        }
        System.out.println("....线程池检测结束");
        

3.需执行线程业务实例
通过@Async(“threadPoolTaskExecutor”)注解,和交给spring管理的线程名,启动线程。
因为在具体的业务场景中,一个用户的数据变动,可能在其他业务场景中也在变动,而往往我们记录明细或者日志需要当前场景下用户的数据。这里可以考虑在sql查询语句中使用排他锁for update,需要注意的是,排他锁可能会造成数据库阻塞
排他锁属于行级锁,排他锁需要开启事务@Transactional才能使用,主要目的是限制其他场景或者线程下的对同一数据同时进行操作,只有当开启事务的业务结束时,其他场景对此数据操作才能进行。



import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@Service
@Slf4j
public class AsyncTaskService {


    /**
     * 
     *
     * @param taskOrders
     * @param state
     */
    @Async("threadPoolTaskExecutor")
    @Transactional
    public void updateOrderStateByOrderId(Object object) {
        System.out.println("线程ID:" + Thread.currentThread().getId() + "线程名字:" + Thread.currentThread().getName() + "执行异步任务:");
        log.info("执行批量任务:" + taskOrders.getId());
		//业务块
		Synchronized(this){
		
		}

     //http异步数据交互,因为开启了事务,在多数据库交互时,为了避免回滚造成的数据不一致性,http请求应该放在业务的最后,保证当前应用业务执行完毕。
		HttpClientUtilAsync.sendPostAsync(requestUrl,param);
    }

}

//查询用户对象添加排他锁
    <select id="selectTaskBeforUserByIdAsync" parameterType="Integer" resultMap="TaskBeforUserResult">
        <include refid="selectTaskBeforUserVo"/>
        where id = #{id} for update
    </select>

4.http异步

package com.syw.merchant.api.web.config;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CountDownLatch;

@Slf4j
@Component
public class HttpClientUtilAsync {



    public static HttpClientUtilAsync httpClientUtilAsync;

    @Resource
    private TaskOrdersMapper taskOrdersMapper;

    @PostConstruct
    public void init(){
        httpClientUtilAsync=this;
        //在异步中应用实例时,需初始化赋值。
        httpClientUtilAsync.taskOrdersMapper=this.taskOrdersMapper;
    
    }
    /**
     * 异步
     * @param requestUrl
     * @param params
     * @return
     */
    public static String sendPostAsync(String requestUrl,String params){
        try{
            final String[] resData = new String[1];
            CloseableHttpAsyncClient client = HttpAsyncClients.createDefault();
            client.start();
            final CountDownLatch latch = new CountDownLatch(1);
            HttpPost post = new HttpPost(requestUrl);
            //设置请求头
            post.addHeader("Content-type", "application/json; charset=utf-8");
//        post.setHeader("Accept", "application/json");
            StringEntity stringEntity = new StringEntity(params);
            post.setEntity(stringEntity);
            client.execute(post, new FutureCallback<HttpResponse>() {
                @Override
                public void completed(HttpResponse httpResponse) {
                    latch.countDown();
                    //响应内容
                    int a = httpResponse.getStatusLine().getStatusCode();
                    System.out.println("状态码:"+a);
                    if (a == 200) {
                        HttpEntity entity = httpResponse.getEntity();
                        try {
                            resData[0] = EntityUtils.toString(entity);
                            
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        System.out.println("成功!");

                    } else {
  
                    }
                }
                //请求失败处理
                @Override
                public void failed(final Exception e) {
                    log.info("批量回款,请求失败:orderId"+orderId+"响应信息"+e.getMessage());
                    latch.countDown();
                   
                    }

                }
                //请求取消后处理
                @Override
                public void cancelled() {
                    latch.countDown();
                }
            });
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //关闭
            try {
                client.close();
            } catch (IOException ignore) {
                ignore.printStackTrace();
            }
        }catch(Exception e){
            e.printStackTrace();
        }



        return "";
    }

    
  
}

后记
本想开篇写个长点的博客记录下。但一提笔就词穷了。我们总是在步履蹒跚的学着一切,生活、事业、、、

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值