1.dubboåspring cloudåºå«
2.为什么wait,码中notify和notifyall这些方法不在thread类里
3.Dubbo调用超时那些事儿
4.java springboot dubbo的SPI
5.Redisson对Redis分布式锁的实现原理
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类里
对象锁与线程锁是编程中的两种不同锁机制。对象锁主要用于方便多个线程间的码中交互,如参数传递、码中指定顺序执行和线程重入,码中程序员可根据需求灵活控制线程交互。码中
线程锁则较为简单,码中问道1.60源码主要用于控制线程自身,码中提升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 SIDESERVER_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实例宕机问题,是其主要缺陷之一。这可能导致多个客户端同时加锁,进而引发业务问题。