目前分布式开发应用中,使用较多的一个RPC框架。
分布式系统是若干独立计算机的集合,这些计算机对于用户来说就单个系统。
当网站流量很小的时候,只需要一个应用,将所有的功能部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
可以将单体应用独立拆分为单个小的应用,并独立部署到不同的服务器上
随着吹着应用越来越多,应用之间还需要交互,可以将核心功能抽取
A服务器调B服务器,代码之间调用为RPC(远程过程调用)
难点:
调度中心负责维护调度之间的复杂关系,以及实施管理服务集群。
基于访问压力,实时管理集群容量,提高集群利用率。
官网
官方推荐Zookeeper。
Zookeeper注册中心官网地址
官网
下载列表地址
下载了3.4.11版本
3.4.11下载链接
tar -zxvf zookeeper-3.4.11.tar.gz
进入bin目录
cd bin
启动(Linux)
./zkServer.sh start
报错:
原因:
zoo.cfg文件在conf目录下没有
复制zoo_sample.cfg目录复制一份,并修改名称为zoo.cfg
cp zoo_sample.cfg zoo.cfg
创建data文件夹,并在zoo.cfg配置中指定目录用来存储文件
mkdir data
./zkServer.sh start
使用zookeeper的客户端来连接
./zkCli.sh
get /
ls /
#创建一个临时节点 test001 值为abc
create -e /test001 abc
可选,不影响操作, 帮助用户通过可视化界面管理服务,查看情况。
下载
<modelVersion>4.0.0modelVersion>
<groupId>com.xiu.gmallgroupId>
<artifactId>user-service-providerartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.4version>
dependency>
dependencies>
实体类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author zhangzengxiu
* @date 2023/3/18
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserAddress implements Serializable {
private static final long serialVersionUID = -5242080009018461173L;
private Integer id;
/**
* 用户地址
*/
private String userAddress;
/**
* 用户id
*/
private String userId;
/**
* 收货人
*/
private String consignee;
/**
* 电话号码
*/
private String phoneNum;
/**
* 是否默认地址
*/
private String isDefault;
}
接口:
import com.xiu.bean.UserAddress;
import java.util.List;
/**
* @author zhangzengxiu
* @date 2023/3/18
*/
public interface UserService {
/**
* 获取用户地址列表
*
* @param userId 用户id
* @return
*/
public List<UserAddress> getUserAddressList(String userId);
}
接口实现类:
import com.xiu.bean.UserAddress;
import com.xiu.service.UserService;
import java.util.Arrays;
import java.util.List;
/**
* @author zhangzengxiu
* @date 2023/3/18
*/
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress userAddress1 = new UserAddress(1, "北京市xxxxx", "1", "张三", "12345678902", "Y");
UserAddress userAddress2 = new UserAddress(2, "杭州市xxxxx", "1", "张三", "12345678902", "N");
return Arrays.asList(userAddress1, userAddress2);
}
}
pom
接口:
此时需要调用user-service中的接口,但是该服务部署在其他服务器上,所以并不能通过本地jar包的形式进行引用,需要调用需要如下操作:
将provider中的bean及其接口复制一份到consumer中:
此时会出现的问题:
我们可以将通用的bean和接口抽取到一个工程中:
dubbo官方也建议我们将服务模型,接口,服务异常等均放在我们的api包下。
其他需要用到的服务进行依赖api包。
将接口和bean放在公共模块中,其他服务依赖该服务。
坐标:
<dependency>
<groupId>com.xiu.gmallgroupId>
<artifactId>gmall-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
因为我们依赖的接口的真正实现是在其他的服务中,到时候甚至会部署到不同的服务器上,此时就需要进行远程调用才行。
将服务注册到注册中心,需要导入dubbo的依赖。
2.6dubbo及以后版本的zookeeper用的是
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
2.6之前用的是
<dependency>
<groupId>com.101tecgroupId>
<artifactId>zkclientartifactId>
<version>0.10version>
dependency>
pom
<groupId>com.xiu.gmallgroupId>
<artifactId>user-service-providerartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>com.xiu.gmallgroupId>
<artifactId>gmall-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.4version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>dubboartifactId>
<version>2.6.7version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.32.Finalversion>
dependency>
dependencies>
官网配置:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubb0="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<dubbo:application name="user-service-provider"/>
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<dubb0:protocol name="dubbo" port="20880"/>
<dubbo:service interface="com.xiu.service.UserService" ref="userService"/>
<bean id="userService" class="com.xiu.service.impl.UserServiceImpl"/>
beans>
<dubbo:application name="user-service-provider"/>
配置方式一:
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
配置方式二:
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
protocol不止zookeeper一种,还有其他多种注册中心可以选择。
其他注册中心:注册中心地址
<dubb0:protocol name="dubbo" port="20880"/>
name指定协议名称,port指定端口。
同样dubbo也有多种协议可供选择:多种协议(官网)
<dubbo:service interface="com.xiu.service.UserService" ref="userService"/>
dubbo对外的暴露的接口,interface指定接口,ref指向真正的实现,该实现需要时Spring容器的bean
将该实现类放在在Spring容器中:
<bean id="userService" class="com.xiu.service.impl.UserServiceImpl"/>
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
/**
* @author zhangzengxiu
* @date 2023/3/18
*/
public class App {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
context.start();
//
System.in.read();
}
}
观察控制台:
控制台中已经展示了应用及其暴露的接口。
pom
<groupId>com.xiu.gmallgroupId>
<artifactId>order-service-consumerartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>com.xiu.gmallgroupId>
<artifactId>gmall-interfaceartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.4version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>dubboartifactId>
<version>2.6.7version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-allartifactId>
<version>4.1.32.Finalversion>
dependency>
dependencies>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubb0="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiu"/>
<dubbo:application name="order-service-consumer"/>
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<dubbo:reference interface="com.xiu.service.UserService" id="userService"/>
beans>
不同的配置:
<dubbo:reference interface="com.xiu.service.UserService" id="userService"/>
申明需要远程调用的服务接口,并放到IOC容器中,service用到时直接注入即可。
service:
import com.xiu.bean.UserAddress;
import com.xiu.service.OrderService;
import com.xiu.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author zhangzengxiu
* @date 2023/3/18
*/
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService;
@Override
public void createOrder(String userId) {
System.out.println("createOrder userId=" + userId);
//查询用户收货地址
List<UserAddress> list = userService.getUserAddressList(userId);
list.stream().forEach(x -> System.out.println(x.getUserAddress()));
}
}
服务提供者与消费者进行配置:
<dubbo:monitor protocol="registry"/>
配置方式:
方式一:
<dubbo:monitor protocol="registry"/>
方式二:
<dubbo:monitor address="127.0.0.1:7001"/>
导入dubbo-starer依赖
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
依赖传递导入的dubbo其他依赖
dubbo提供的版本之间的对应关系:
通过使用application进行配置
server.port=8001
#服务名称
dubbo.application.name=boot-user-service-provider
#注册中心位置
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181
#通信规则
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#监控中心
dubbo.monitor.protocol=registry
与原生Spring配置的区别:
配置对外暴露的接口:
我们实际开发中,需要对外暴露的接口会非常的多,不可能会挨个手动去配置,使用注解进行配置:
SpringBoot配置方式:是Dubbo的包不是Spring的包!!!
需要在主启动类上添加注解:@EnableDubbo:开启基于注解的Dubbo功能
启动服务:
查看监控台:
pom
dubbo的相关坐标与provider相同
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
使用application配置问价配置其他配置项:
配置远程调用的接口:
使用纯Spring配置Dubbo远程接口:
实际开发过程中需要远程调用的接口会很多,不会去挨个配置,还是使用注解的方式配置。
使用SpringBoot配置远程调用的接口:
主启动配置:开启Dubbo注解支持
启动服务:
请求:http://192.168.10.105:8002/order/createOrder?userId=1
总结:
我们最初是通过spring的配置文件进行dubbo相关信息的配置 配置项参考手册
JVM---->application.properties------>dubbo.properties
从下到下是优先级顺序。
我们可以单独建一个dubbo的配置文件,将公共的配置抽取到该配置文件中:
如果启动JVM的时候指定参数,方式为:
以配置通信规则端口为例:
JVM优先级最高,如果JVM中没有,会找application的配置文件,该配置文件中也没有会去找dubbo.properties的配置文件。
后续,我们可以将公共的配置放到dubbo.properties中,额外需要改变的放到application.properties,临时需要改变的放在JVM参数中。
默认服务消费者启动时会去检查自己依赖的提供者是否可用,如果不可用,会抛出异常,阻止Spring初始化完成,默认check=true。
我们可以修改配置为false,关闭检查。当服务之间进行调用时,才会进行请求检查。
默认开启检查配置:
直接启动消费者,启动报错:
配置文件配置方式:
配置在Consumer端
<dubbo:reference check="false" interface="com.xiu.service.UserService" id="userService"/>
@Reference(check = false)
UserService userService;
<dubbo:consumer check="false"/>
SpringBoot启动检查配置
#配置当前所有服务启动都不检查
dubbo.consumer.check=false
关闭某个服务的启动时检查(没有服务提供者时报错)
<dubbo:reference check="false" interface="com.xiu.service.UserService" id="userService"/>
关闭所有服务的启动时检查(没有服务提供者时报错)
<dubbo:consumer check="false"/>
关闭注册中心启动时检查(注册订阅失败时报错)
<dubbo:registry check="false"/>
#配置当前所有服务启动都不检查
dubbo.consumer.check=false
dubbo.reference.com.xiu.service.UserService.check=false
dubbo.reference.check=false
dubbo.registry.check=false
服务消费者在引用服务提供方时,执行一个方法可能需要很长时间,很长时间都没有返回,导致线程阻塞,会引发性能下降,我们可以指定超时时间,在规定时间内未返回,就立即终止。
配置方式:
Spring配置,通过timeout属性配置,默认时间单位是毫秒。
默认使用的是dubbo:consumer/中的timeout,默认是:1000ms
<dubbo:reference check="false" timeout="3000" interface="com.xiu.service.UserService" id="userService"/>
全部配置timeout
<dubbo:consumer check="false" timeout="4000"/>
timeout不仅仅可以配置在全局和接口上,也可以配置在具体的某个方法上
<dubbo:reference check="false" timeout="3000" interface="com.xiu.service.UserService" id="userService">
<dubbo:method name="getUserAddressList" timeout="3000"/>
dubbo:reference>
如果有多处配置了超时时间,作用优先级:
以timeout为例,显示了配置的查找顺序,其他retries,loadbalanc,actives类似:
其中如果服务提供方配置,通过URL经由注册中心传递给消费者。
总结:越精确,优先级越高,级别一样,消费方优先级高。
比如消费方配置在了消费者接口级别,提供者配置在了方法级,结论:方法级优先,因为方法级比接口级别优先级更高!!!
SpringBoot配置,引用超时时间:
@Reference(check = false, timeout = 3000)
UserService userService;
方法级别配置:
@Reference(check = false, parameters = {
"createOrder.timeout", "3000"})
UserService userService;
全局配置
dubbo.consumer.timeout=3000
一般配合timeout一起使用,是一个整数,但是,不包含第一次调用
服务之间调用时,由于网络不佳或者缓慢等原因,会出现超时的情况,我们可以通过配置重试次数来进行重试,但是,并不是所有的接口都能配置重试次数:幂等方法可以设置、非幂等方法不能设置
<dubbo:reference check="false" timeout="3000" interface="com.xiu.service.UserService" id="userService">
<dubbo:method name="getUserAddressList" timeout="3000" retries="3"/>
dubbo:reference>
服务提供者集群的情况下,如果调用失败,重试时会去调用其他服务提供者。
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
我们可以先让一部分人先用新版本,一部人仍旧使用老版本。灰度发布的概念。
可以按照以下步骤进行版本迁移:
0、在低压力时间段,先升级一半提供者为新版本
1、再将所有消费者升级为新版本
2、然后将剩下的一半提供者升级为新版本
spring配置:
<dubbo:service version="1.0.0" interface="com.xiu.service.UserService" ref="userService"/>
SpringBoot注解配置
import com.alibaba.dubbo.config.annotation.Service;
@Service(version = "1.0.0")
Spring配置:
<dubbo:reference version="1.0.0" check="false" timeout="3000" interface="com.xiu.service.UserService" id="userService">
dubbo:reference>
SpringBoot配置:
@Reference(check = false, version = "1.0.0")
UserService userService;
可以配置为*,代表随机调用
@Reference(check = false, version = "*")
UserService userService;
我们可以在真正发起远程调用实现之前进行一些验证、缓存等操作
远程引用的接口在我们的服务消费者本地也有一些存根代码。
当满足我们的要求时再去发起远程调用,否则就可以不发起远程调用。
我们需要在服务消费方实现一个远程接口的本地存根实现:
必须有一个有参构造器,参数为远程接口的代理实现,Dubbo默认会传进来。
代码:
/**
* @author zhangzengxiu
* @date 2023/3/19
*/
public class UserServiceStub implements UserService {
private UserService userService;
/**
* Dubbo会自动创建创建本地存根对象
* 自动传入UserService远程代理对象
*
* @param userService
*/
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public List<UserAddress> getUserAddressList(String userId) {
if (!StringUtils.isEmpty(userId)) {
return userService.getUserAddressList(userId);
}
return null;
}
}
配置:
通过dubbo:servic配置,是配置在服务提供者里
<dubbo:reference version="1.0.0" check="false" timeout="3000" interface="com.xiu.service.UserService" id="userService" stub="com.xiu.service.impl.UserServiceStub">
dubbo:reference>
或
@Reference(check = false, version = "1.0.0",parameters = {"createOrder.timeout", "3000"},stub = "com.xiu.service.UserServiceStub")
UserService userService;
注解不能配置某个方法的详细配置
导入dubbo-stater,在application.properties中进行配置
@Service进行服务暴露
@Reference引用服务
以上需要在主启动类开启@EnableDubbo
也可以在配置中配置dubbo.scan.base-packages来指定包
保留原生的dubbo的配置方式,application中的配置全部都不要了
主启动类的@EnableDubbo也不再需要
通过@ImportResource(locations=“provider.xml”)来导入,也不再需要@service和Reference方式
使用API的方式
我们可以将所有的配置以配置类的形式进行配置,将每一个组件配置
举例:
配置服务名称:
<dubbo:application name="boot-user-service-provider"/>
@Bean
public ApplicationConfig getApplicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("boot-user-service-provider");
return applicationConfig;
}
配置注册中心位置
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
@Bean
public RegistryConfig getRegistryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
return registryConfig;
}
配置通信规则
<dubbo:protocol name="dubbo" port="20880"/>
@Bean
public ProtocolConfig getProtocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
配置接口
<dubbo:reference version="1.0.0" check="false" timeout="3000" interface="com.xiu.service.UserService" id="userService" stub="com.xiu.service.UserServiceStub">
<dubbo:method name="getUserAddressList" timeout="3000" retries="3"/>
dubbo:reference>
部分属性省略配置:
@Bean
public ServiceConfig<UserService> userServiceServiceConfig(UserService userService) {
ServiceConfig<UserService> serviceConfig = new ServiceConfig<UserService>();
serviceConfig.setInterface(UserService.class);
serviceConfig.setRef(userService);
serviceConfig.setVersion("1.0.0");
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getUserAddressList");
methodConfig.setTimeout(3000);
List<MethodConfig> methodConfigs = new ArrayList<MethodConfig>();
methodConfigs.add(methodConfig);
serviceConfig.setMethods(methodConfigs);
return serviceConfig;
}
我们需要在主启动类上配置Dubbo的接口的扫描路径:
@DubboComponentScan(basePackages = "com.xiu")
其实EnableDubbo注解本质上还是DubboComponentScan
通过EnableDubbo也可以配置,配置scanBasePackages属性
分布式系统中,可以通过架构设计,减少系统不能提供服务的时间
我们也可以绕过注册中心,使用dubbo直连的形式。
@Reference(url = "127.0.0.1:20880")
UserService userService;
问:如果没有这册中心是否能完成调用?
答:可以,
负载均衡
算法 | 特性 | 备注 |
---|---|---|
Weighted Random LoadBalance | 加权随机 | 默认算法,默认权重相同 |
RoundRobin LoadBalance | 加权轮询 | 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同, |
LeastActive LoadBalance | 最少活跃优先 + 加权随机 | 背后是能者多劳的思想 |
Shortest-Response LoadBalance | 最短响应优先 + 加权随机 | 更加关注响应速度 |
ConsistentHash LoadBalance | 一致性哈希 | 确定的入参,确定的提供者,适用于有状态请求 |
我们可以为每台服务设置权重,访问时会根据一定的概率访问每台服务。
访问是有顺序的,而且要考虑权重。
服务会统计上次的调用时间,会去找上次处理请求最快的服务器。
一致性hash,比如getId=1的去找1号机器,id=2的去找二号机器,id=3的去找3号机器等等。
@Reference(loadbalance = "leastactive")
value来源AbstractLoadBalance实现类里的值
配置按照权重随机
@Reference(loadbalance = "random")
配置权重方式一:
暴露Dubbo接口时配置,缺点:写死了,固定无法调节
@Service(weight = 100)
服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或者换种简单的方式处理,从而释放服务区资源以保证核心交易正常运行或高效运作。
当我们去发起远程调用时,返回为空,不发起远程调用,用来屏蔽不重要的服务不可用时候对调用方的影响。
管理控制台配置:消费者侧配置
消费方调用服务失败后,返回为空,不抛出异常,用来容忍不重要服务不稳定时对调用方的影响。
管理控制台配置:消费者侧配置
<dubbo:service cluster="failsafe" interface="com.xiu.service.UserService" ref="userService" version="1.0.0"/>
或者
@Service(weight = 100,version = "1.0.0",cluster = "failsafe")
<dubbo:reference cluster="failsafe"/>
或者
@Reference(cluster = "failsafe")
实际开发中,是通过整合Hystrix来进行设计,Hystrix也是SpringCloud中默认的服务容错解决方案
依赖导入
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
<version>1.4.4.RELEASEversion>
dependency>
启用:主启动类上开启
@EnableHystrix
在提供者方法上添加注解
@HystrixCommand
在消费者方法上添加注解并编写回调方法
@HystrixCommand(fallbackMethod = "回调方法名")
一次完整的RPC调用流程,同步调用过程如下:
1、服务消费方调用以本地调用方式调用服务
2、client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体
3、client stub找到服务地址,并将消息发送给服务端
4、server stub收到消息后进行解码
5、server stub根据解码结果调用本地服务
6、本地服务执行并将结果返回给server stub
7、server stub将返回结果打包成消息并发送给消费方
8、client stub接收到消息,并进行解码
9、服务消费方得到最终结果
RPC框架的目标就是要将2-8步,封装起来,这些细节对于用户来说是透明、不可见的。
Dubbo底层两台机器要建立通信,使用netty框架实现,基于Java的NIO。
Netty是一个异步时间驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。极大的简化了TCP和UDP套接字服务器等网络编程。
Netty其实就是基于NIO的多路复用模型去做的。
一次请求一个线程,当线程没有处理完成时,线程是得不到释放的,这样就使得服务器不能一次处理太多请求。我们可以通过NIO的形式。