Service 组件介绍

         Service 是Redkale最核心的组件,依赖于Convert、SNCP协议、Resource依赖注入。Service主要处理业务逻辑和操作数据层,是微服务架构中的单一原子服务。每一个Service实例分两种模式: 本地模式远程模式。其模式由 conf/application.xml 文件来配置。开发人员在调用过程中通常不需要区分当前Service实例是哪种模式。
         为了能确保本地模式与远程模式自由切换,对Service的实现类有一定的约束:
                 1、Service实现类会被继承,不能修饰为 final
                 2、带@RpcMultiRun注解的方法会被重载,不能修饰为 final
         Redkale进程启动时扫描可加载的Service实现类,根据配置文件配置的模式采用ASM技术动态生成相应的Service临时类进行实例化,并注册到ResourceFactory同其他Service、Servlet依赖注入。

Service 本地模式

        以一个简单的UserService类范例来说明动态生成的两种模式临时类。UserService提供查询用户、注册、登陆、修改用户名功能:

public class UserService implements Service {

    //用户简单信息缓存
    private final Map<Integer, UserInfo> users = new ConcurrentHashMap<>();

    //存放手机号码与userid的对应关系
    private final Map<Long, Integer> mobileToUserids = new ConcurrentHashMap<>();

    public final String testLocalNodeName() {
        return "本地节点名";
    }

    //查询用户信息
    public UserInfo findUserInfo(int userid) {
        return users.get(userid);
    }

    //根据手机号码查询用户ID,没有返回0
    public int findUserid(long mobile) {
        Integer rs = mobileToUserids.get(mobile);
        return rs == null ? 0 : rs;
    }

    //缓存用户信息
    public void putUserInfo(UserInfo user) {
        users.put(user.getUserid(), user);
        if (user.getMobile() > 0) mobileToUserids.put(user.getMobile(), user.getUserid());
    }

    //登录
    public RetResult<UserInfo> login(LoginBean bean) {
        // 登陆逻辑
        return new RetResult<>(100);
    }

    //注册
    @RpcMultiRun
    public void register(UserInfo user) {
        this.users.put(user.getUserid(), user);
    }

    //更新用户名
    @RpcMultiRun(diffrun = false)
    public UserInfo updateUsername(int userid, String username) {
        UserInfo user = this.users.get(userid);
        if (user != null) user.setUsername(username);
        return user;
    }
}
                

        动态生成的本地模式UserService:

@Resource(name = "")
@SncpDyn(remote = false)
@ResourceType(UserService.class)
public final class _DynLocalUserService extends UserService {

    private SncpClient _redkale_client;

    @Override
    public void register(UserInfo user) {
        this._redkale_register(true, true, true, user);
    }

    @SncpDyn(remote = false, index = 0)
    public void _redkale_register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
        if (selfrunnable) super.register(user);
        if (_redkale_client == null) return;
        if (samerunnable) _redkale_client.remoteSameGroup(0, true, false, false, user);
        if (diffrunnable) _redkale_client.remoteDiffGroup(0, true, true, false, user);
    }

    @Override
    public UserInfo updateUsername(int userid, String username) {
        return this._redkale_updateUsername(true, true, false, userid, username);
    }

    @SncpDyn(remote = false, index = 1)
    public UserInfo _redkale_updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
        UserInfo rs = super.updateUsername(userid, username);
        if (_redkale_client == null) return null;
        if (samerunnable) _redkale_client.remoteSameGroup(1, true, false, false, userid, username);
        if (diffrunnable) _redkale_client.remoteDiffGroup(1, true, true, false, userid, username);
        return rs;
    }
}
                

        由以上等价的代码可以看出来,本地模式Service会重载被@RpcMultiRun注解的方法。@RpcMultiRun有以下几个参数:

public @interface RpcMultiRun {

    boolean selfrun() default true; //当前本地实例是否运行指定操作;只有当指定操作的方法的返回值为void时,该值才能为true,否则忽略。

    boolean samerun() default true;  //是否同组节点运行指定操作

    boolean diffrun() default true; //是否不同组节点运行指定操作

    boolean async() default true; //分布式运行是否采用异步模式
} 

        在动态生成的远程模式UserService时会根据不同参数生成相应的方法。若一个Service类没有含@RpcMultiRun注解的方法,那么动态类只会重载toString方法。当UserService服务仅需要部署一个进程,由于没有其他等同服务的进程因此在UserService实例化时_redkale_client会赋值为null。

<resources>  
    <group name="GROUP-A">
        <node addr="192.168.10.111" port="7070"/>
        <node addr="192.168.10.112" port="7070"/>
        <node addr="192.168.10.113" port="7070"/>
    </group>
    <group name="GROUP-B">            
        <node addr="192.168.20.121" port="7070"/>
        <node addr="192.168.20.122" port="7070"/>
    </group>
    <group name="GROUP-C">            
        <node addr="192.168.30.131" port="7070"/>
        <node addr="192.168.30.132" port="7070"/>
    </group>
</resources>


<!-- 配置UserService的节点组  --->
<service name="" value="org.redkale.demo.user.UserService" groups="GROUP-A;GROUP-B;GROUP-C"/>
                

        如上配置,若当前进程所在IP是192.168.10.111,则UserService采用本地模式加载。执行register方法时, 先本地执行超类的register,然后远程执行同组的进程(192.168.10.112、192.168.10.113),最后远程执行异组的进程(192.168.20.121、192.168.20.122、192.168.30.131、192.168.30.132)。若当前进程所在IP是192.168.10.100,则UserService采用远程模式加载。需要注意的一点是,每个IP所在的服务必须开通SNCP协议服务以便能接收远程的调用请求。

Service 远程模式

        动态生成的远程模式UserService:

@Resource(name = "")
@SncpDyn(remote = true)
@ResourceType(UserService.class)
public final class _DynRemoteUserService extends UserService {

    private SncpClient _redkale_client;

    @SncpDyn(remote = false, index = 0)
    public void _redkale_register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
        _redkale_client.remote(0, selfrunnable, samerunnable, diffrunnable, user);
    }

    @SncpDyn(remote = false, index = 1)
    public UserInfo _redkale_updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
        return _redkale_client.remote(1, selfrunnable, samerunnable, diffrunnable, userid, username);
    }

    @Override
    public UserInfo findUserInfo(int userid) {
        return _redkale_client.remote(2, userid);
    }

    @Override
    public int findUserid(long mobile) {
        return _redkale_client.remote(3, mobile);
    }

    @Override
    public RetResult<UserInfo> login(LoginBean bean) {
        return _redkale_client.remote(4, bean);
    }

    @Override
    public void putUserInfo(UserInfo user) {
        _redkale_client.remote(5, user);
    }

    @Override
    public void register(UserInfo user) {
        _redkale_client.remote(6, user);
    }

    @Override
    public UserInfo updateUsername(int userid, String username) {
        return _redkale_client.remote(7, userid, username);
    }
}
                

        由以上代码可以看出来,远程模式Service是根据本地模式Service临时类动态生成的。远程类执行方法时通过SNCP协议将参数序列化并带上当前方法信息传输到远程服务器上,执行完后将结果流反序列化并返回, 其流程与WebService类似。

  远程模式的@RpcCall回调

        与WebService的区别除了更具性能的二进制的数据格式,更差异的是远程模式的Service允许修改参数本身的内容。范例如下:

/**
 * 由于该方法在处理过程中修改了参数bean的内容,为了保证本地模式与远程模式的一致性,需要提供@RpcCall回调接口
 *
 * @param bean
 * @return
 */
public RetResult<UserInfo> login(@RpcCall(RpcCallLoginBeanAttribute.class) LoginBean bean) {
    bean.setLogintime(System.currentTimeMillis());
    bean.setSessionid("SID" + System.currentTimeMillis());
    // 登陆逻辑
    return new RetResult<>(100);
}



/**  RpcCallLoginBeanAttribute 的实现  **/
public class RpcCallLoginBeanAttribute implements Attribute<LoginBean, Object[]> {

    @Override
    public Object[] get(LoginBean obj) {
        return new Object[]{obj.getLogintime(), obj.getSessionid()};
    }

    @Override
    public void set(LoginBean obj, Object[] value) {
        obj.setLogintime((Long) value[0]);
        obj.setSessionid((String) value[1]);
    }

    @Override
    public Class<? extends Object[]> type() {
        return Object[].class; //
    }

    @Override
    public Class<LoginBean> declaringClass() {
        return LoginBean.class; //
    }

    @Override
    public String field() {
        return ""; //可以随意值
    }
}
                

        生成远程模式Service时发现参数带有@RpcCall注解的方法,在远程调用返回结果时会进行回调处理。

Service REST

        RestService提供类似Spring Boot的功能。开启REST功能的HTTP Server在实例化标记为@RestService的Service后自动生成对应的HttpServlet,免去开发人员编写HttpServlet的工作量。主要通过@RestService@RestMapping@RestParam这三个注解来实现,同时为了获取其他类型的参数也有@RestAddress@RestCookie@RestHeader@RestSessionid@RestBody 提供其扩展功能。

    @RestService :

/**
 * 只能依附在Service类上,name默认为Service的类名小写并去掉Service字样及后面的字符串 (如HelloService/HelloServiceImpl,的默认路径为 hello)。
 */
@Target({TYPE})
@Retention(RUNTIME)
public @interface RestService {

    /**
     * 模块名, 只能是模块名,不能含特殊字符, 只能小写字母+数字,且不能以数字开头
     *
     * @return 模块名
     */
    String name() default "";

    /**
     * 目录名, 不能含特殊字符, 只能小写字母+数字,且不能以数字开头
     *
     * @return 目录名
     */
    String catalog() default "";

    /**
     * 模块ID值,鉴权时用到, 对应&#64;WebServlet.moduleid
     *
     * @return 模块ID值
     */
    int moduleid() default 0;

    /**
     * 没有标记&#64;RestMapping的方法是否转换, 默认为false
     *
     * @return 默认false
     */
    boolean automapping() default false;

    /**
     * 是否屏蔽该类的转换
     *
     * @return 默认false
     */
    boolean ignore() default false;

    /**
     * 同&#64;WebServlet的repair属性
     *
     * @return 默认true
     */
    boolean repair() default true;

    /**
     * 备注描述
     *
     * @return 备注描述
     */
    String comment() default "";
}
                

    @RestMapping :

/**
 * 只能依附在Service实现类的public方法上
 * value默认为"/" + Service的类名去掉Service及后面字样的小写字符串 (如HelloService,的默认路径为/hello)。
 */
@Target({METHOD})
public @interface RestMapping {

    boolean ignore() default false; //是否屏蔽该方法的转换

    //请求的方法名, 不能含特殊字符
    //默认为方法名的小写(若方法名以createXXX、updateXXX、deleteXXX、queryXXX、findXXX且XXXService为Service的类名将只截取XXX之前)
    String name() default "";

    String comment() default ""; //备注描述, 对应@HttpMapping.comment 

    boolean auth() default true; //是否鉴权,默认需要鉴权, 对应@HttpMapping.auth 

    int cacheseconds() default 0; //结果缓存的秒数, 为0表示不缓存, 对应@HttpMapping.cacheseconds

    int actionid() default 0; //操作ID值,鉴权时用到, 对应@HttpMapping.actionid

    String[] methods() default {};  //允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法, 对应@HttpMapping.methods
}
                

    @RestParam :

/**
 * 只能依附在Service类的方法的参数上
 */
@Target({PARAMETER})
public @interface RestParam {

    //参数名 name='&'表示当前用户;
    //参数名 name='#'表示截取uri最后一段; 
    //参数名 name='#xxx:'表示从uri中/pipes/xxx:v/截取xxx:的值; 
    String name(); 

    int radix() default 10; //转换数字byte/short/int/long时所用的进制数, 默认10进制

    boolean required() default true; //参数是否必传 

    String comment() default ""; //备注描述, 对应@HttpMapping.comment 
}
                

        开启REST功能的步骤很简单:在 application.xml<server> 节点下增加<rest>指明RestServlet的子类。

<!-- 
   REST的核心配置项, 存在[rest]节点则Server启动时会加载REST服务, 当Server为SNCP协议时,则SncpServer会变成REST的HttpServer, 节点可以多个
   path:     servlet的ContextPath前缀 默认为空
   base:     REST服务的BaseServlet,必须是org.redkale.net.http.HttpServlet的子类,且子类必须标记 @HttpUserType。
   autoload:默认值"true"  默认值. 加载当前server所能使用的Servce对象;    
   includes:当autoload="true", 拉取类名与includes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
   excludes:当autoload="true", 排除类名与excludes中的正则表达式匹配的类, 多个正则表达式用分号;隔开
-->
<rest path="/pipes" base="org.redkale.net.http.HttpServlet" autoload="true" includes="" excludes="">
    <!-- 
       value:  Service类名,列出的表示必须被加载的Service对象
       ignore: 是否忽略,设置为true则不会加载该Service对象,默认值为false
    -->
    <service value="com.xxx.XXXXService"/>
</rest>
                

        Redkale中的REST并非严格遵循标准的REST规范,在尽量满足规范的同时也考虑到开发的灵活性和简易性。以下通过简单的实例来说明其特性。

        通常配置都需要编写一个 org.redkale.net.http.HttpServlet 子类,主要用于获取当前用户信息和鉴权,且必须指定具体的User对象类。开发者的实现类可以参考 redkale-demo 中的BaseServlet类,以下是一个简单的范例:

@HttpUserType(UserInfo.class)
public class SimpleRestServlet extends HttpServlet {

    protected static final RetResult RET_UNLOGIN = RetCodes.retResult(RetCodes.RET_USER_UNLOGIN);

    protected static final RetResult RET_AUTHILLEGAL = RetCodes.retResult(RetCodes.RET_USER_AUTH_ILLEGAL);

    @Resource
    private UserService userService;

    @Override
    public void preExecute(HttpRequest request, HttpResponse response) throws IOException {
        final String sessionid = request.getSessionid(false);
        if (sessionid != null) request.setCurrentUser(userService.current(sessionid));
        response.nextEvent();
    }

    //普通鉴权
    @Override
    public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
        UserInfo info = request.currentUser();
        if (info == null) {
            response.finishJson(RET_UNLOGIN);
            return;
        } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
            response.finishJson(RET_AUTHILLEGAL);
            return;
        }
        response.nextEvent();
    }

}
                

        REST的设置方式有两种: 一种采用默认REST注解,一种是显式的设置。

public class HelloBean implements FilterBean {

    private int helloid;

    @RestHeader(name = "User-Agent")
    private String useragent; //从Http Header中获取浏览器信息
    
    @RestCookie(name = "hello-cookie")
    private String rescookie;  //从Cookie中获取名为hello-cookie的值

    @RestAddress
    private String clientaddr;  //客户端请求IP

    @RestSessionid
    private String sessionid;  //用户Sessionid, 未登录时为null

    /** 以下省略getter setter方法 */
}
                
public class HelloEntity {

    @Id
    private int helloid;

    private String helloname;

    private int creator;

    private long updatetime;

    private long createtime;

    @RestHeader(name = "hello-res")
    private String resname;

    @RestAddress
    private String clientaddr;

    /** 以下省略getter setter方法 */
}
                
/**
 * 类说明:
 * Flipper : Source组件中的翻页对象
 * UserInfo :当前用户类
 * HelloEntity: Hello模块的实体类
 * HelloBean: Hello模块实现FilterBean的过滤Bean类
 *
 */
@RestService(name = "hello", moduleid = 0, repair = true, ignore = false, comment = "Hello服务模块")
public class HelloService implements Service {

    @Resource
    private DataSource source;

    //增加记录
    @RestMapping(name = "create", auth = false, comment = "创建Hello对象")
    public RetResult<HelloEntity> createHello(UserInfo info, @RestParam(name = "bean", comment = "Hello对象") HelloEntity entity) {
        entity.setCreator(info == null ? 0 : info.getUserid()); //设置当前用户ID
        entity.setCreatetime(System.currentTimeMillis());
        source.insert(entity);
        return new RetResult<>(entity);
    }

    //删除记录
    @RestMapping(name = "delete", auth = false, comment = "根据id删除Hello对象")
    public void deleteHello(@RestParam(name = "#", comment = "Hello对象id") int id) { //通过 /hello/delete/1234 删除对象
        source.delete(HelloEntity.class, id);
    }

    //修改记录
    @RestMapping(name = "update", auth = false, comment = "修改Hello对象")
    public void updateHello(@RestParam(name = "bean", comment = "Hello对象") HelloEntity entity) { //通过 /hello/update?bean={...} 修改对象
        entity.setUpdatetime(System.currentTimeMillis());
        source.update(entity);
    }

    //查询列表
    //通过 /hello/query/offset:0/limit:20?bean={...} 获取结果
    @RestMapping(name = "query", auth = false, comment = "查询Hello对象列表")
    public Sheet<HelloEntity> queryHello(@RestParam(name = "bean", comment = "过滤条件") HelloBean bean, Flipper flipper) {
        return source.querySheet(HelloEntity.class, flipper, bean);
    }

    //查询单个
    @RestMapping(name = "find", auth = false, comment = "根据id查找单个Hello对象")
    public HelloEntity findHello(@RestParam(name = "#", comment = "Hello对象id") int id) {  //通过 /hello/find/1234 查询对象
        return source.find(HelloEntity.class, id);
    }

    //异步查询单个
    @RestMapping(name = "asyncfind", auth = false, comment = "根据id查找单个Hello对象")
    public CompletableFuture<HelloEntity> findAsyncHello(@RestParam(name = "#") int id) {  //通过 /pipes/hello/asyncfind/1234 查询对象
        return source.findAsync(HelloEntity.class, id);
    }
}
                

        根据默认命名规则可以看出,以上范例生成的HttpServlet与去掉所有@RestService、@RestMapping、@RestParam后的Service生成的是完全相同的。 REST根据Service会动态生成HttpServlet,以上范例自动生成的HttpServlet如下:

@WebServlet(value = {"/hello/*"}, repair = true)
public class _DynHelloRestServlet extends SimpleRestServlet {

    @Resource
    private HelloService _service;

    @Resource
    private Map<String, HelloService> _servicemap;

    @HttpMapping(url = "/hello/create", auth = false, comment = "创建Hello对象")
    @HttpParam(name = "bean", type = HelloEntity.class, comment = "Hello对象")
    public void create(HttpRequest req, HttpResponse resp) throws IOException {
        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
        HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
        bean.setClientaddr(req.getRemoteAddr());
        bean.setResname(req.getHeader("hello-res"));
        UserInfo user = currentUser(req);
        RetResult<HelloEntity> result = service.createHello(user, bean);
        resp.finishJson(result);
    }

    @HttpMapping(url = "/hello/delete/", auth = false, comment = "根据id删除Hello对象")
    @HttpParam(name = "#", type = int.class, comment = "Hello对象id")
    public void delete(HttpRequest req, HttpResponse resp) throws IOException {
        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
        int id = Integer.parseInt(req.getRequstURILastPath());
        service.deleteHello(id);
        resp.finishJson(RetResult.success());
    }

    @HttpMapping(url = "/hello/update", auth = false, comment = "修改Hello对象")
    @HttpParam(name = "bean", type = HelloEntity.class, comment = "Hello对象")
    public void update(HttpRequest req, HttpResponse resp) throws IOException {
        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
        HelloEntity bean = req.getJsonParameter(HelloEntity.class, "bean");
        bean.setClientaddr(req.getRemoteAddr());
        bean.setResname(req.getHeader("hello-res"));
        service.updateHello(bean);
        resp.finishJson(RetResult.success());
    }

    @HttpMapping(url = "/hello/query", auth = false, comment = "查询Hello对象列表")
    @HttpParam(name = "bean", type = HelloBean.class, comment = "过滤条件")
    public void query(HttpRequest req, HttpResponse resp) throws IOException {
        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
        HelloBean bean = req.getJsonParameter(HelloBean.class, "bean");
        bean.setClientaddr(req.getRemoteAddr());
        bean.setUseragent(req.getHeader("User-Agent"));
        bean.setRescookie(req.getCookie("hello-cookie"));
        bean.setSessionid(req.getSessionid(false));
        Flipper flipper = req.getFlipper();
        Sheet<HelloEntity> result = service.queryHello(bean, flipper);
        resp.finishJson(result);
    }

    @HttpMapping(url = "/hello/find/", auth = false, comment = "根据id查找单个Hello对象")
    @HttpParam(name = "#", type = int.class, comment = "Hello对象id")
    public void find(HttpRequest req, HttpResponse resp) throws IOException {
        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
        int id = Integer.parseInt(req.getRequstURILastPath());
        HelloEntity bean = service.findHello(id);
        resp.finishJson(bean);
    }

    @HttpMapping(url = "/hello/asyncfind/", auth = false, comment = "根据id查找单个Hello对象")
    @HttpParam(name = "#", type = int.class, comment = "Hello对象id")
    public void asyncfind(HttpRequest req, HttpResponse resp) throws IOException {
        HelloService service = _servicemap == null ? _service : _servicemap.get(req.getHeader(Rest.REST_HEADER_RESOURCE_NAME, ""));
        int id = Integer.parseInt(req.getRequstURILastPath());
        resp.finishJson(service.findAsyncHello(id));
    }
}  
                

Service 异步调用

        远程模式不仅对@RpcCall注解进行处理,而且对方法含有 CompletionHandler 的参数或返回类型为CompletableFuture也进行异步特殊处理。异步调用对远程模式非常有意义,可以减少同步方式对当前线程的占用时间。也给Source组件的异步调用提供了基础。

    @Override
    public <T> void updateAsync(final CompletionHandler<Void, T[]> handler, @RpcAttachment final T... values) {
        source.update(values);
        if (handler != null) handler.completed(null, values);
    }

        如上图源码,异步接口(含CompletionHandler参数或返回类型为CompletableFuture)与同步接口执行流程相同。当Service为远程模式时,调用异步接口时,通信接口发到远程服务器时CompletionHandler参数的值传null,返回数据后再调用本地的CompletionHandler参数值执行。
        异步调用方式是提高服务并发性的有效手段,特别是在远程模式Service比较多的情况下效果更明显。以HTTP服务为例,在Tomcat刚刚改版成NIO的时候,网上随处可见都是大谈NIO性能比BIO多好,认为BIO与NIO的不同,是BIO往往会引入多线程,每个连接一个单独的线程;NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。而事实上呢,通常还是通过增加线程数来提高并发量。为什么NIO作用不大呢, 因为一个HTTP动态请求耗时最多是业务逻辑层,特别是操作数据库,IO操作的耗时比重小得多,只有在静态资源请求这种纯IO操作才能体现NIO、AIO(NIO.2)的优势。举例一个简单的数据查询请求,采用BIO方式耗时(为了方便比较将所耗时间扩大几倍)如下:
                1、服务器TCP连接开始到进入HttpServlet,耗时 10ms
                2、用户态判断和参数验证,                         耗时 10ms
                3、调用远程数据源(DataSource)查询数据,耗时 150ms
                4、数据序列化与response的IO输出,          耗时 10ms
        如上描述,一个请求处理耗时 180ms,同时占用一个线程的时间也是 180ms。若换成NIO使IO耗时减少,为了方便计算假设IO耗时为0(实际情况是不可能的), 那么步骤1、4的耗时忽略不计,线程的占用时间由180ms变成160ms。 假设数据查询接口IO操作本身耗时也是10ms,那么有140ms是用于等待。若采用DataSource异步接口, 则140ms的等待时间可以释放当前线程资源。虽然整个请求的处理时间还是180ms,但是线程的占用时间却只有20ms。可以看出减少耗时多的步骤的等待时间才能事半功倍,大幅度地提高性能。异步接口的主要作用是远程请求在等待过程中释放当前线程资源,大大减少线程数。