jjzjj

分布式锁-Redisson

断河愁 2023-07-19 原文

目录

1.分布式并发问题

2.如何解决分布式并发问题呢 ?

3.使⽤Redis实现分布式锁-代码实现

4.解决因线程异常导致⽆法释放锁的问题

5.解决因t1过期释放t2锁的问题

6.看⻔狗机制

7.分布式锁框架-Redisson

7.1 Redisson介绍

7.2 在SpringBoot应⽤中使⽤Redisson

7.3 Redisson⼯作原理

7.4 Redisson使⽤扩展

7.4.1 Redisson单机连接

7.4.2 Redisson集群连接

7.4.3 Redisson主从连接

7.5 分布式锁总结

7.5.1 分布式锁特点

7.5.2 锁的分类

7.5.3 Redission的使⽤


1.分布式并发问题

提交订单:商品超卖问题

 

2.如何解决分布式并发问题呢 ?

使⽤ redis 实现分布式锁

 

3.使⽤Redis实现分布式锁-代码实现

@Transactional
public Map<String,String> addOrder(String cids,Orders order) throws
SQLException {
 logger.info("add order begin...");
 Map<String, String> map = null;
 //1.校验库存:根据cids查询当前订单中关联的购物⻋记录详情(包括库存)
 String[] arr = cids.split(",");
 List<Integer> cidsList = new ArrayList<>();
 for (int i = 0; i < arr.length; i++) {
 cidsList.add(Integer.parseInt(arr[i]));
 }
 
 //根据⽤户在购物⻋列表中选择的购物⻋记录的id 查询到对应的购物⻋记录
 List<ShoppingCartVO> list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 //从购物⻋信息中获取到要购买的 skuId(商品ID) 以skuId为key写到redis中: 1
 2 3
 boolean isLock = true;
 String[] skuIds = new String[list.size()]; //记录已经锁定的商品的ID
 for (int i = 0; i <list.size() ; i++) {
 String skuId = list.get(i).getSkuId(); //订单中可能包含多个商品,
每个skuId表示⼀个商品
 Boolean ifAbsent =
stringRedisTemplate.boundValueOps(skuId).setIfAbsent("fmmall");
 if(ifAbsent){
 skuIds[i] = skuId;
 }
 isLock = isLock && ifAbsent;
 }
 //如果isLock为true,表示“加锁”成功
 if(isLock){
 try{
 //1.⽐较库存: 当第⼀次查询购物⻋记录之后,在加锁成功之前,可能被其他
的并发线程修改库存
 List<ShoppingCartVO> list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 boolean f = true;
 String untitled = "";
 for (ShoppingCartVO sc : list) {
 if (Integer.parseInt(sc.getCartNum()) >
sc.getSkuStock()) {
 f = false;
 }
 untitled = untitled + sc.getProductName() + ",";
 }
 if (f) {
 //2.添加订单
 //3.保存快照
 //4.修改库存
 //5.删除购物⻋
 map = new HashMap<>();
 logger.info("add order finished...");
 map.put("orderId", orderId);
 map.put("productNames", untitled);
 }
 }catch(Exception e){
 e.printStackTrance();
 }finally{
 //释放锁
 for (int m = 0; m < skuIds.length ; m++) {
 String skuId = skuIds[m];
 if(skuId!=null && !"".equals(skuId)){
 stringRedisTemplate.delete(skuId);
 }
 }
 }
 return map;
 }else{
 //表示加锁失败,订单添加失败
 // 当加锁失败时,有可能对部分商品已经锁定,要释放锁定的部分商品
 for (int i = 0; i < skuIds.length ; i++) {
 String skuId = skuIds[i];
 if(skuId!=null && !"".equals(skuId)){
 stringRedisTemplate.delete(skuId);
 }
 }
 return null;
 }
}
问题:
1. 如果订单中部分商品加锁成功,但是某⼀个加锁失败,导致最终加锁状态失败 —— 需要对
已经锁定的部分商品释放锁
2. 在成功加锁之前,我们根据购物⻋记录的 id 查询了购物⻋记录(包含商品库存),能够直接
使⽤这个库存进⾏库存校验?
—— 不能,因为在查询之后加锁之前可能被并发的线程修改了库存;因此在进⾏库存⽐较之
前需要重新查询库存。
3. 当当前线程加锁成功之后,执⾏添加订单的过程中,如果当前线程出现异常导致⽆法释放
锁,这个问题⼜该如何解决呢?

4.解决因线程异常导致⽆法释放锁的问题

解决⽅案:在对商品进⾏加锁时,设置过期时间,这样⼀来及时线程出现故障⽆法释放
锁,在过期时间结束时也会⾃动 释放锁
 
问题:当给锁设置了过期时间之后,如果当前线程 t1 因为特殊原因,在锁过期前没有完成业
务执⾏,将会释放锁,同时其他线程( t2 )就可以成功加锁了,当 t2 加锁成功之后, t1 执⾏结
束释放锁就会释放 t2 的锁 , 就会导致 t2 在⽆锁状态下执⾏业务。

5.解决因t1过期释放t2锁的问题

  • 在加锁的时候,为每个商品设置唯⼀的value

 

  • 在释放锁的时候,先获取当前商品在redis中对应的value,如果获取的值与当前value同,则释放锁

 

问题:当释放锁的时候,在查询并判断 这个锁是当前线程加的锁 成功之后,正要进⾏删除时
锁过期了,并且被其他线程成功加锁,⼀样会导致当前线程删除其他线程的锁。
  • Redis的操作都是原⼦性的
  • 要解决如上问题,必须保证查询操作和删除操作的原⼦性——使⽤lua脚本
使⽤ lua 脚本
  • resources⽬录下创建unlock.lua,编辑脚本:
if redis.call("get",KEYS[1]) == ARGV[1] then
 return redis.call("del",KEYS[1])
else
 return 0
end
  • 配置Bean加载lua脚本
@Bean
public DefaultRedisScript<List> defaultRedisScript(){
 DefaultRedisScript<List> defaultRedisScript = new
DefaultRedisScript<>();
 defaultRedisScript.setResultType(List.class);
 defaultRedisScript.setScriptSource(new ResourceScriptSource(new
ClassPathResource("unlock.lua")));
 return defaultRedisScript; }
  • 通过执⾏lua脚本解锁
@AutoWired
private DefaultRedisScript defaultRedisScript;
//执⾏lua脚本
List<String> keys = new ArrayList<>();
keys.add(skuId);
List rs = stringRedisTemplate.execute(defaultRedisScript,keys ,
values.get(skuId));
System.out.println(rs.get(0));

6.看⻔狗机制

 

看⻔⼝线程:⽤于给当前 key 延⻓过期时间,保证业务线程正常执⾏的过程中,锁不会过期。

7.分布式锁框架-Redisson

基于 Redis+ 看⻔狗机制的分布式锁框架

7.1 Redisson介绍

Redisson 在基于 NIO Netty 框架上,充分的利⽤了 Redis 键值数据库提供的⼀系列优势,在
Java 实⽤⼯具包中常⽤接⼝的基础上,为使⽤者提供了⼀系列具有分布式特性的常⽤⼯具
类。使得原本作为协调单机多线程并发程序的⼯具包获得了协调分布式多机多线程并发系统
的能⼒,⼤⼤降低了设计和研发⼤规模分布式系统的难度。同时结合各富特⾊的分布式服
务,更进⼀步简化了分布式环境中程序相互之间的协作

7.2 SpringBoot应⽤中使⽤Redisson

  • 添加依赖

<dependency>
<groupId> org.redisson </groupId>
<artifactId> redisson </artifactId>
<version> 3.12.0 </version>
</dependency>
  • 配置yml

redisson :
addr :
singleAddr :
host : redis : //47.96.11.185 : 6370
password : 12345678
database : 0
  • 配置RedissonClient
@Configuration
public class RedissonConfig {
 @Value("${redisson.addr.singleAddr.host}")
 private String host;
 @Value("${redisson.addr.singleAddr.password}")
 private String password;
 @Value("${redisson.addr.singleAddr.database}")
 private int database;
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config();
 
config.useSingleServer().setAddress(host).setPassword(password).se
tDatabase(database);
 return Redisson.create(config);
 }
}
  • 在秒杀业务实现中注⼊RedissonClient对象

7.3 Redisson⼯作原理

看⻔狗
Redisson ⼯作原理图

 

7.4 Redisson使⽤扩展

7.4.1 Redisson单机连接

  • application.yml

redisson :
addr :
singleAddr :
host : redis : //47.96.11.185 : 6370
password : 12345678
database : 0
  • RedissonConfig
@Configuration
public class RedissonConfig {
 @Value("${redisson.addr.singleAddr.host}")
 private String host;
 @Value("${redisson.addr.singleAddr.password}")
 private String password;
 @Value("${redisson.addr.singleAddr.database}")
 private int database;
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config(); 
config.useSingleServer().setAddress(host).setPassword(password).se
tDatabase(database);
 return Redisson.create(config);
 }
}

7.4.2 Redisson集群连接

  • application.yml

redisson :
addr :
cluster :
hosts : redis : //47.96.11.185 : 6370,...,redis : //47.96.11.185 : 6373
password : 12345678
  • RedissonConfig——RedissonClient对象
@Configuration
public class RedissonConfig {
 @Value("${redisson.addr.cluster.hosts}")
 private String hosts;
 @Value("${redisson.addr.cluster.password}")
 private String password;
 /**
 * 集群模式
 * @return
 */
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config();
 config.useClusterServers().addNodeAddress(hosts.split("
[,]"))
 .setPassword(password)
 .setScanInterval(2000)
 .setMasterConnectionPoolSize(10000)
 .setSlaveConnectionPoolSize(10000);
 return Redisson.create(config);
 }
}

7.4.3 Redisson主从连接

  • application.yml

redisson :
addr :
masterAndSlave :
masterhost : redis : //47.96.11.185 : 6370
slavehosts :
redis : //47.96.11.185 : 6371,redis : //47.96.11.185 : 6372
password : 12345678
database : 0
  • RedissonConfig --- RedissonClient
@Configuration
public class RedissonConfig3 {
 @Value("${redisson.addr.masterAndSlave.masterhost}")
 private String masterhost;
 @Value("${redisson.addr.masterAndSlave.slavehosts}")
 private String slavehosts;
 @Value("${redisson.addr.masterAndSlave.password}")
 private String password;
 @Value("${redisson.addr.masterAndSlave.database}")
 private int database;
 /**
 * 主从模式
 * @return
 */
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config();
 config.useMasterSlaveServers()
 .setMasterAddress(masterhost)
 .addSlaveAddress(slavehosts.split("[,]"))
 .setPassword(password)
 .setDatabase(database)
 .setMasterConnectionPoolSize(10000)
 .setSlaveConnectionPoolSize(10000);
 return Redisson.create(config);
 }
}

7.5 分布式锁总结

7.5.1 分布式锁特点

1 、互斥性
和我们本地锁⼀样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
2 、可重⼊性
同⼀个节点上的同⼀个线程如果获取了锁之后那么也可以再次获取这个锁。
3 、锁超时
和本地锁⼀样⽀持锁超时,加锁成功之后设置超时时间,以防⽌线程故障导致不释放锁,防
⽌死锁。
4 、⾼效,⾼可⽤
加锁和解锁需要⾼效,同时也需要保证⾼可⽤防⽌分布式锁失效,可以增加降级。
redission 是基于 redis 的, redis 的故障就会导致 redission 锁的故障,因此 redission ⽀持单节
redis reids 主从、 reids 集群
5 、⽀持阻塞和⾮阻塞
ReentrantLock ⼀样⽀持 lock trylock 以及 tryLock(long timeOut)

7.5.2 锁的分类

1 、乐观锁与悲观锁
乐观锁
悲观锁
2 、可重⼊锁和⾮可重⼊锁
可重⼊锁:当在⼀个线程中第⼀次成功获取锁之后,在此线程中就可以再次获取
⾮可重⼊锁
3 、公平锁和⾮公平锁
公平锁:按照线程的先后顺序获取锁
⾮公平锁:多个线程随机获取锁
4 、阻塞锁和⾮阻塞锁
阻塞锁:不断尝试获取锁,直到获取到锁为⽌
⾮阻塞锁:如果获取不到锁就放弃,但可以⽀持在⼀定时间段内的重试
—— 在⼀段时间内如果没有获取到锁就放弃

7.5.3 Redission的使⽤

1 、获取锁 —— 公平锁和⾮公平锁
// 获取公平锁
RLock lock = redissonClient . getFairLock ( skuId );
// 获取⾮公平锁
RLock lock = redissonClient . getLock ( skuId );
2 、加锁 —— 阻塞锁和⾮阻塞锁
// 阻塞锁(如果加锁成功之后,超时时间为 30s ;加锁成功开启看⻔狗,剩 5s 延⻓过期时间)
lock . lock ();
// 阻塞锁(如果加锁成功之后,设置⾃定义 20s 的超时时间)
lock . lock ( 20 , TimeUnit . SECONDS );
// ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功默认超时间为 30s
boolean b = lock . tryLock ( 3 , TimeUnit . SECONDS );
// ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功设置⾃定义超时间为 20s
boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS );
3 、释放锁
lock . unlock ();
4 、应⽤示例
// 公平⾮阻塞锁
RLock lock = redissonClient . getFairLock ( skuId );
boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS );
8. 分布式锁释放锁代码优化
  • 伪代码

HashMap map = null ;
加锁
try {
if ( isLock ){
校验库存
if ( 库存充⾜ ){
保存订单
保存快照
修改库存
删除购物⻋
map = new HashMap ();
...
}
}
} catch ( Exception e ){
e . printStackTrace ();
} finally {
释放锁
}
return map ;
  • Java代码实现
/**
* 保存订单业务
*/
@Transactional
public Map<String, String> addOrder(String cids, Orders order)
throws SQLException {
 logger.info("add order begin...");
 Map<String, String> map = null;
 //1.校验库存:根据cids查询当前订单中关联的购物⻋记录详情(包括库存)
 String[] arr = cids.split(",");
 List<Integer> cidsList = new ArrayList<>();
 for (int i = 0; i < arr.length; i++) {
 cidsList.add(Integer.parseInt(arr[i]));
 }
 //根据⽤户在购物⻋列表中选择的购物⻋记录的id 查询到对应的购物⻋记录
 List<ShoppingCartVO> list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 //加锁
 boolean isLock = true;
 String[] skuIds = new String[list.size()]; 
 Map<String, RLock> locks = new HashMap<>(); //⽤于存放当前订单的锁
 for (int i = 0; i < list.size(); i++) {
 String skuId = list.get(i).getSkuId();
 boolean b = false;
 try {
 RLock lock = redissonClient.getLock(skuId);
 b = lock.tryLock(10, 3, TimeUnit.SECONDS);
 if (b) {
 skuIds[i] = skuId;
 locks.put(skuId, lock);
 }
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 isLock = isLock & b;
 }
 //如果isLock为true,表示“加锁”成功
 try {
 if (isLock){
 //1.检验库存
 boolean f = true;
 String untitled = "";
 list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 for (ShoppingCartVO sc : list) {
 if (Integer.parseInt(sc.getCartNum()) >
sc.getSkuStock()) {
 f = false;
 }
 untitled = untitled + sc.getProductName() + ",";
 }
 if (f) {
 //如果库存充⾜,则进⾏下订单操作
 logger.info("product stock is OK...");
 //2.保存订单
 order.setUntitled(untitled);
 order.setCreateTime(new Date());
 order.setStatus("1");
 //⽣成订单编号
 String orderId =
UUID.randomUUID().toString().replace("-", "");
 order.setOrderId(orderId);
 int i = ordersMapper.insert(order);
 //3.⽣成商品快照
 for (ShoppingCartVO sc : list) {
 int cnum = Integer.parseInt(sc.getCartNum());
 String itemId = System.currentTimeMillis() +
"" + (new Random().nextInt(89999) + 10000);
 OrderItem orderItem = new OrderItem(itemId,
orderId, sc.getProductId(), sc.getProductName(),
sc.getProductImg(), sc.getSkuId(), sc.getSkuName(), new
BigDecimal(sc.getSellPrice()), cnum, new
BigDecimal(sc.getSellPrice() * cnum), new Date(), new Date(), 0);
 orderItemMapper.insert(orderItem);
 //增加商品销量
 }
 //4.扣减库存:根据套餐ID修改套餐库存量
 for (ShoppingCartVO sc : list) {
 String skuId = sc.getSkuId();
 int newStock = sc.getSkuStock() -
Integer.parseInt(sc.getCartNum());
 ProductSku productSku = new ProductSku();
 productSku.setSkuId(skuId);
 productSku.setStock(newStock);
 
productSkuMapper.updateByPrimaryKeySelective(productSku);
 //5.删除购物⻋:当购物⻋中的记录购买成功之后,购物⻋中对应
做删除操作
 for (int cid : cidsList) {
 shoppingCartMapper.deleteByPrimaryKey(cid);
 }
 map = new HashMap<>();
 logger.info("add order finished...");
 map.put("orderId", orderId);
 map.put("productNames", untitled);
 }
 }
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 //释放锁
 for (int i = 0; i < skuIds.length; i++) {
 String skuId = skuIds[i];
 if (skuId != null && !"".equals(skuId)) {
 locks.get(skuId).unlock();
 System.out.println("-----------------------
unlock");
 }
 }
 }
 return map; }

有关分布式锁-Redisson的更多相关文章

  1. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  2. ruby - 停止分布式 Ruby 服务 - 2

    我有一个启动DRb服务的脚本,然后生成处理程序对象并通过DRb.thread.join等待。我希望脚本一直运行直到被明确杀死,所以我添加了trap"INT"doDRb.stop_serviceend在Ruby1.8下成功停止DRb服务并退出,但在1.9下似乎死锁(在OSX10.6.7上)。对该进程进行采样显示在semaphore_wait_signal_trap中有几个线程在旋转。我假设我在调用stop_service时做错了什么,但我不确定是什么。谁能给我任何关于如何正确处理它的指示? 最佳答案 好的,我想我已经找到了解决方案。如

  3. BigData/Cloud Computing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程 - 2

    BigData/CloudComputing:基于阿里云技术产品的人工智能与大数据/云计算/分布式引擎的综合应用案例目录来理解技术交互流程目录一、云计算网站建设:部署与发布网站建设:简单动态网站搭建云服务器管理维护云数据库管理与数据迁移云存储:对象存储管理与安全超大流量网站的负载均衡二、大数据MOOC网站日志分析搭建企业级数据分析平台基于LBS的热点店铺搜索基于机器学习PAI实现精细化营销基于机器学习的客户流失预警分析使用DataV制作实时销售数据可视化大屏使用MaxCompute进行数据质量核查使用Quick BI制作图形化报表使用时间序列分解模型预测商品销量三、云安全云平台使用安全云上服务

  4. ruby - 数组的所有可能分布,来自一个数字 - 2

    我不太确定如何表达这一点,所以我只是举个例子。如果我写:some_method(["a","b"],3)我希望它返回某种形式的[{"a"=>0,"b"=>3},{"a"=>1,"b"=>2},{"a"=>2,"b"=>1},{"a"=>3,"b"=>0}]如果我传入some_method(%w(abc),2)期望的返回值应该是[{"a"=>2,"b"=>0,"c"=>0},{"a"=>1,"b"=>1,"c"=>0},{"a"=>1,"b"=>0,"c"=>1},{"a"=>0,"b"=>2,"c"=>0},{"a"=>0,"b"=>1,"c"=>1},{"a"=>0,"b"=>0,"

  5. Seatunnel超高性能分布式数据集成平台使用体会 - 2

    文章目录概述定义使用场景特点工作流程连接器转换为何选择SeaTunnel安装下载配置文件部署模式入门示例启动脚本配置文件使用参数示例Kafka进Kafka出的ETL示例FlinkRun传递参数概述定义SeaTunnel官网http://seatunnel.incubator.apache.org/SeaTunnel最新版本官网文档http://seatunnel.incubator.apache.org/docs/2.1.3/intro/aboutSeaTunnelGitHub地址https://github.com/apache/incubator-seatunnelSeaTunnel是一个

  6. ruby - 在 Ruby 中生成高斯(正态分布)随机数的代码 - 2

    用ruby​​生成正态分布随机数的代码是什么?(注意:我回答了我自己的问题,但我会等几天再接受,看看是否有人有更好的答案。)编辑:为此,我查看了两次搜索产生的SO上的所有页面:+“正态分布”ruby和+高斯+随机ruby 最佳答案 Python的random.gauss()和Boost的normal_distribution都使用Box-Mullertransform,所以这对Ruby来说也应该足够好了。defgaussian(mean,stddev,rand)theta=2*Math::PI*rand.callrho=Math.s

  7. 《统计学》第八版贾俊平第六章统计量及抽样分布知识点总结及课后习题答案 - 2

    一、知识框架二、练习题调节一个装瓶机使其对每个瓶子的灌装量均值为μ盎司,通过观察这台装瓶机对每个瓶子的灌装量服从标准差σ=1.0盎司的正态分布。随机抽取这台机器灌装的9个瓶子组成一个样本,并测定每个瓶子的灌装量。试确定样本均值偏离总体均值不超过0.3盎司的概率。解:设每个瓶子的灌装量为X,X为样本均值,样本容量为n。由于总体X服从正态分布,样本均值X也服从正态分布,且均值相同,标准差为所以三、简述题1什么是统计量?为什么要引进统计量?统计量中为什么不含任何未知参数?答:(1)统计量的定义:设X1,X2,…,Xn是从总体X中抽取的容量为n的一个样本,如果由此样本构造一个函数T(X1,X2,…,X

  8. javascript - 均匀分布的随机数 - 2

    嘿,有没有办法选择均匀分布的随机数?我用过这个功能Math.floor(Math.random()*2)返回1或0。但是,我不认为它有确切的50%的机会产生任何一个。更好的想法?谢谢 最佳答案 如果你不相信,检查:vartotal=0;varones=0;for(vari=0;i此代码给出0.49972-非常接近50%。 关于javascript-均匀分布的随机数,我们在StackOverflow上找到一个类似的问题: https://stackoverflo

  9. javascript - 如何生成元素的随机加权分布 - 2

    我想返回一个数组,其中包含一组根据自定义频率随机分布的唯一元素。我的真实用例是根据对这些图像的流行程度进行定性加权来重复轮播图像。例如假设我有5个带权重的元素:一个,20%B、50%C、80%D、10%我想写一个函数,在给定长度的情况下,尝试逼近一个序列,使得C出现的频率是D的八倍;D出现的次数比B少5倍;A的出现频率是C的三倍。 最佳答案 CwillappeareighttimesmoreoftenthanD;Dwillappear5timeslessoftenthanB;Awillappearthreetimeslessofte

  10. 【微服务36】分布式事务Seata源码解析四:图解Seata Client 如何与Seata Server建立连接、通信【云原生】 - 2

    文章目录一、前言二、概述三、TM事务管理器初始化1、TM初始化流程图2、TM初始化流程1)获取TmNettyRemotingClient实例1>TmNettyRemotingClient实例化2>AbstractNettyRemotingClient实例化2)初始化TmNettyRemotingClient1>注册一些请求处理组件2>初始化AbstractNettyRemotingClient(1)AbstractNettyRemoting初始化(2)启动netty客户端组件Abs

随机推荐