皮皮网

【足疗养生源码】【黑色网站源码】【智能建站源码】dubbo源码中的锁

2024-11-23 12:39:23 来源:剑灵革命 源码

1.dubbo和spring cloud区别
2.为什么wait,码中notify和notifyall这些方法不在thread类里
3.Dubbo调用超时那些事儿
4.java springboot dubbo的SPI
5.Redisson对Redis分布式锁的实现原理

dubbo源码中的锁

dubbo和spring cloud区别

       dubbo和spring cloud的定位不同。Dubbo的定位始终是一款RPC框架,而SpringCloud的目标是微服务架构下的一站式解决方案。Dubbo可以类比到Netflix OSS技术栈,而Spring Cloud集成了Netflix OSS作为分布式服务治理解决方案,但除此之外Spring Cloud还提供了配置、消息、安全、调用链跟踪等分布式问题解决方案。

       Spring Cloud是一系列微服务框架的有序集合,而Dubbo有两中常见理解,一种是狭义的理解,一种是广义的。狭义的Dubbo,指的是一款高性能的RPC框架,广义的Dubbo值得是一整套微服务解决方案!

        简而言之,Dubbo确实类似于Spring Cloud的一个子集,Dubbo功能和文档完善,在国内有很多的成熟用户。

        Dubbo具有调度、发现、监控、治理等功能,支持相当丰富的服务治理能力。Dubbo架构下,注册中心对等集群,并会缓存服务列表已被数据库失效时继续提供发现功能,本身的服务发现结构有很强的可用性与健壮性,足够支持高访问量的网站。

        SpringCloud有众多子项目组成,这其中有Spring Cloud Netflix,Spring Cloud Config,Spring Cloud Consul,Spring Cloud Alibaba,包含了Dubbo整套。SpringCloud提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等,满足了构建微服务所需的所有解决方案。

为什么wait,notify和notifyall这些方法不在thread类里

       对象锁与线程锁是编程中的两种不同锁机制。对象锁主要用于方便多个线程间的码中交互,如参数传递、码中指定顺序执行和线程重入,码中程序员可根据需求灵活控制线程交互。码中

       线程锁则较为简单,码中足疗养生源码主要用于控制线程自身,码中提升CPU使用效率,码中但无法实现复杂的码中线程交互。下面分述这些锁相关的码中具体方法。

       sleep方法,码中属于线程锁范畴,码中它使线程释放CPU进入休眠状态,码中但不会释放锁。码中在编程中,码中如果线程执行过快导致频繁进行垃圾回收,可通过Thread.sleep(0)释放CPU,让垃圾回收线程执行回收。

       yield方法,较少使用,黑色网站源码表示当前线程愿意主动放弃CPU执行权,释放CPU,不释放锁,但调度器可能忽略这个提示。它主要用于使多线程执行进度尽可能一致。

       join方法控制线程执行顺序,常用在等待其他线程执行完毕后继续执行。底层使用wait()实现,释放CPU与线程锁,但不释放对象锁。

       wait()与notify()及notifyAll()方法,属于对象锁范畴。wait()使线程释放synchronized锁,暂停执行,直到被notify()或notifyAll()唤醒。经典用例如Dubbo底层使用Netty实现RPC调用,调用后进入wait状态等待结果。

       wait()与notify()在synchronized代码块中使用的原因是实现线程间的参数传递。Synchronized关键字可实现互斥条件,智能建站源码通过共享变量让参与通信的线程竞争锁资源,修改数据后释放锁,其他线程再次获取锁,完成线程间通信。

Dubbo调用超时那些事儿

       其实之前很早就看过Dubbo源码中关于超时这部分的处理逻辑,但是没有记录下来,最近在某脉上看到有人问了这个问题,想着再回顾一下。

开始

       从dubbo的请求开始,看看dubbo(2.6.6)在超时这块是怎么处理的:

com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int)@Overridepublic ResponseFuture request(Object request, int timeout) throws RemotingException { if (closed) { throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");}// create request.Request req = new Request();req.setVersion(Version.getProtocolVersion());req.setTwoWay(true);req.setData(request);DefaultFuture future = new DefaultFuture(channel, req, timeout);try { channel.send(req);} catch (RemotingException e) { future.cancel();throw e;}return future;}DefaultFuture

       从返回值ResponseFuture类型可以看出,这是一个异步方法(不等同于Dubbo的异步调用)。那么调用超时的关键可以从ResponseFuture来看:

public interface ResponseFuture { Object get() throws RemotingException;Object get(int timeoutInMillis) throws RemotingException;void setCallback(ResponseCallback callback);boolean isDone();}

       可以看到这是一个接口,从request方法可以得知实现类是DefaultFuture,从构造函数入手:

public DefaultFuture(Channel channel, Request request, int timeout) { this.channel = channel;this.request = request;this.id = request.getId();this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);// put into waiting map.FUTURES.put(id, this);CHANNELS.put(id, channel);}

       可以得知每一个DefaultFuture都有一个id,并且等于requestId,timeout是从url中获取的配置,没有时默认ms。

       从代码的注释可以看到FUTURES这个map应该就是关键,是获取器源码一个waiting map。

       DefaultFuture中还有一个方法:

public static void received(Channel channel, Response response) { try { DefaultFuture future = FUTURES.remove(response.getId());if (future != null) { future.doReceived(response);} else { logger.warn("The timeout response finally returned at "+ (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()))+ ", response " + response+ (channel == null ? "" : ", channel: " + channel.getLocalAddress()+ " -> " + channel.getRemoteAddress()));}} finally { CHANNELS.remove(response.getId());}}

       可以看到调用的地方为:

       com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#received

@Overridepublic void received(Channel channel, Object message) throws RemotingException { //省略一些代码} else if (message instanceof Response) { handleResponse(channel, (Response) message);//省略一些代码}}

       com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#handleResponse

static void handleResponse(Channel channel, Response response) throws RemotingException { if (response != null && !response.isHeartbeat()) { DefaultFuture.received(channel, response);}}

       回到DefaultFuture.received,可以看到通过Response id从FUTURES中拿了一个DefaultFuture出来,然后调用了doReceived方法,也就是说Response id和Request id 相同。结下来看看doReceived做了什么:

private void doReceived(Response res) { lock.lock();try { response = res;if (done != null) { done.signal();}} finally { lock.unlock();}if (callback != null) { invokeCallback(callback);}}

       首先是加锁,然后通过唤醒了阻塞在Condition上的线程。看看什么地方会阻塞在done这个条件上:

@Overridepublic Object get(int timeout) throws RemotingException { if (timeout <= 0) { timeout = Constants.DEFAULT_TIMEOUT;}if (!isDone()) { long start = System.currentTimeMillis();lock.lock();try { while (!isDone()) { done.await(timeout, TimeUnit.MILLISECONDS);if (isDone() || System.currentTimeMillis() - start > timeout) { break;}}} catch (InterruptedException e) { throw new RuntimeException(e);} finally { lock.unlock();}if (!isDone()) { throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));}}return returnFromResponse();}

       是get方法,get方法确实在request请求后被调用:

(Result) currentClient.request(inv, timeout).get()

       可以看到get方法的大致逻辑为,先获取锁,然后循环判断isDone,并阻塞等到条件,当条件超时,如果任务完成,或者超过timeout结束循环,接着判断isDone,如果超时抛出TimeoutException。并且通过sent(request请求时间)是否>0()来判断是clientSide还是serverSide超时。

       isDone逻辑如下:

@Overridepublic boolean isDone() { return response != null;}

       如果是正常Response,也有可能是诚信源码超时的现象,可以看到get方法最后调用了一个函数:

public interface ResponseFuture { Object get() throws RemotingException;Object get(int timeoutInMillis) throws RemotingException;void setCallback(ResponseCallback callback);boolean isDone();}0TIMEOUT SIDE

       SERVER_TIMEOUT(服务端超时): 这个就是正常的我们消费端请求一个RPC接口,服务端由于性能等一些原因处理时间超过了timeout配置时间。

       CLIENT_TIMEOUT:我们可以看到是通过sent(上面有说sent>0)这个来判断是否clientTimeout,那么这个sent什么时候改变呢?就在发送请求的地方:

public interface ResponseFuture { Object get() throws RemotingException;Object get(int timeoutInMillis) throws RemotingException;void setCallback(ResponseCallback callback);boolean isDone();}1

       也就是说handler.sent一旦调用成功返回,那么就不算clientSide Timeout了。那么CLIENT_TIMEOUT大概率就是由于client端网络,系统等原因超时。

原文:/post/

java springboot dubbo的SPI

       Service Provider Interface (SPI) 是 Java 的一种服务提供发现机制,主要用于框架扩展和替换组件。例如,java.sql.Driver 接口允许不同厂商提供针对同一接口的不同实现,如 MySQL 和 PostgreSQL。Java 中的 SPI 机制将装配的控制权移至程序之外,这对于模块化设计尤为重要,核心思想是解耦。

       Java 的 SPI 机制通过 `ServiceLoader.load(Search.class)` 实现。当加载某个接口时,系统会在 `META-INF/services` 下查找接口的全限定名文件,并根据文件内容加载相应的实现类。SPI 思想在于接口的实现由提供者实现,提供者只需在提交的 jar 包中 `META-INF/services` 目录下创建对应接口的文件,并添加实现类内容。

       在 JDBC4.0 之后,通过 Java 的 SPI 扩展机制,开发者无需再使用 `Class.forName("com.mysql.jdbc.Driver")` 来加载驱动,而是可以直接获取连接。驱动实现遵循 `java.sql.Driver` 接口,提供方实现该接口并指定实现,通过 `META-INF/services` 文件完成。

       Java SPI 的一个缺点是文件中的所有实现都会被加载,缺乏灵活性。如果某个实现类初始化过程耗费资源且不被使用,将会导致资源浪费。因此,没有实现按需加载的机制。

       Spring Boot 的自动装配解决了 Java SPI 的灵活性问题。通过读取 `META-INF/spring.factories` 文件,解析 key-value 对,获取需要实例化的类。再根据类上的 `@ConditionalOn` 注解过滤,仅实例化满足条件的类,从而实现灵活的自动装配。

       核心流程涉及 `SpringFactoriesLoader` 类,该类封装了元数据信息来存储类信息,并获取 value 字符集集合。通过这些信息,Spring Boot 实现了对类的实例化和过滤。

       Dubbo 的 SPI 机制与 Java SPI 不同,分为三类目录。接口需带有 `@SPI` 注解,并创建一个以接口名为文件名的文件存储键值对。Dubbo 实现了按需加载机制,只有在获取 key 时才会实例化相应的类。通过 `ExtensionLoader`,系统先缓存接口层,然后根据 key-value 映射查找类,实例化后进行依赖注入。`@Adaptive` 和 `@SPI("file")` 注解分别用于获取实例。

       双检锁是获取实例的一种机制。通过 `ExtensionLoader` 的 `getExtension` 和 `createExtension` 方法实现类的实例化与依赖注入,确保线程安全。

Redisson对Redis分布式锁的实现原理

       面试时,分布式系统常被提及,包括服务框架(Spring Cloud、Dubbo)等。其中,分布式锁是关键话题之一。本文旨在探讨Redis分布式锁的实现原理,以及Redisson框架在这一过程中的应用。

       Redisson框架在企业生产环境中广泛使用,其易于集成和使用。开发人员可通过Redisson官网获取如何在项目中引入依赖,实现分布式锁的加锁与释放。

       以下是一段简洁的使用代码,直观展示了Redisson的便捷性。

       Redisson在底层通过Lua脚本实现分布式锁,确保复杂业务逻辑的原子性。使用Lua脚本能有效执行加锁操作,保证锁的唯一性和一致性。

       若客户端尝试加锁,Redisson将通过Lua脚本与Redis交互,检查锁是否存在,若不存在则添加锁。客户端ID与锁的生存时间作为参数传递,确保加锁操作的正确执行。

       在加锁过程中,Redisson采用锁互斥机制,确保同一时间仅一个客户端持有锁。当尝试加锁的客户端发现锁已存在且与自己ID不匹配时,将根据剩余生存时间决定是否继续尝试加锁。

       为了延长锁的生存时间,Redisson实现了一个看门狗机制,后台线程定期检查并延长锁的时间,确保锁的有效性。

       在可重入加锁机制中,已持有锁的客户端可以再次加锁,通过增加锁的计数器实现。当释放锁时,Redisson通过减少计数器并删除锁键,允许其他客户端尝试加锁。

       尽管Redis分布式锁提供了诸多优势,但在Redis集群或主从架构中,主从异步复制可能导致的redis master实例宕机问题,是其主要缺陷之一。这可能导致多个客户端同时加锁,进而引发业务问题。