阅读视图

发现新文章,点击刷新页面。
🔲 ⭐

Macos上跑Docker Desktop遇到registry-1.docker.io网络不通问题填坑笔记

问题:

docker、openai、claudeai等国外的站点对国内IP有限制,所以在使用vpn等要注意一些点

原因:

当使用docker指令启动项目,报错

1
Error response from daemon: Get "https://registry-1.docker.io/v2/": EOF(base) wx@wxs-MacBook-Pro docker

img

是因为:registry-1.docker.io被墙

解决方法:

1)切记VPN要设置“全局代理”

在使用docker desktop、或者直接调用claude/chatgpt的open api时候,也需要设置“全局代理”

例如我用的VPN是XXXXXVpn,是设置“安全模式”

img

可以在一个网站查看是否生效:https://whatismyipaddress.com

img

如图,我的已经生效。如果一直不生效,请在无痕模式下打开浏览器

2)添加国内代理站点:

请将下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"builder": {
"gc": {
"defaultKeepStorage": "20GB",
"enabled": true
}
},
"debug": true,
"dns": [
"8.8.8.8",
"114.114.114.114"
],
"experimental": true,
"proxies": {
"http-proxy": "http://127.0.0.1:7890",
"https-proxy": "http://127.0.0.1:7890",
"no-proxy": "localhost,127.0.0.0/8"
},
"registry-mirrors": [
"https://hub.rat.dev"
]
}

拷贝到 Docker Desktop的 设置按钮–> Docker Engine 文本框

img

对应文件的磁盘存储路径为:~/.docker/daemon.json

🔲 ⭐

Dubbo服务间的连接是怎么控制处理的?

​ 最近被问到一个问题: 有依赖关系的两个dubbo服务。通过TCP进行连接时候,他们之间的连接是怎么控制的?怎么达到一个合理的数量?

​ 我们从一个例子开始吧:一个订单服务 OrderService,IP为192.168.0.110 连接了商品服务 ProductService, ip 为192.168.0.111 ,其中订单服务中的方法比较多,并且很多请求也刚好路由到192.168.0.111ProductService 服务。那问题就来了:110机器作为客户端是怎么控制连接数的?会不会无限量地和111机器进行TCP连接?

​ 我们先看一下Dubbo的官方文档对“连接控制”的说明文档 : http://dubbo.apache.org/zh-cn/docs/user/demos/config-connections.html

​ 在xml配置方式中xml accepts="10"connections="10" 分别在服务端和客户端进行了相应的连接控制。下面我们看一下源码,追一下连接控制的原理。

​ 我们看一下DubboProtocol.java的创建客户端tcp连接的方法,int connectNum正是每个客户端对服务端的tcp连接数,默认是1,当然可以修改成更大。默认是1,这样一个客户端的调用service数最多也不会超过1000吧。这样就不会出现单机创建TCP连接数过多的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Bulk build client
*
* @param url
* @param connectNum
* @return
*/
private List<ReferenceCountExchangeClient> buildReferenceCountExchangeClientList(URL url, int connectNum) {
List<ReferenceCountExchangeClient> clients = new ArrayList<>();

for (int i = 0; i < connectNum; i++) {
clients.add(buildReferenceCountExchangeClient(url));
}
return clients;
}

客户端和服务端是一对一的,建立长连接,那么如果客户端并发访问,他们是怎么和服务端交互的呢?
经过看代码:

下面咱们试图从代码中找到痕迹。一路追踪,我们来到这个类:com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel.java,先来看看其中的request方法,大概在第101行左右:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  public 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("2.0.0");
req.setTwoWay(true);
req.setData(request);

//这个future就是前面我们提到的:客户端并发请求线程阻塞的对象
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try{
channel.send(req); //非阻塞调用
}catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}

注意这个方法返回的ResponseFuture对象,当前处理客户端请求的线程在经过一系列调用后,会拿到ResponseFuture对象,最终该线程会阻塞在这个对象的下面这个方法调用上,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public 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();
}

上面我已经看到请求线程已经阻塞,那么又是如何被唤醒的呢?再看一下com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler.java,其实所有实现了ChannelHandler接口的类都被设计为装饰器模式,所以你可以看到类似这样的代码:

1
2
3
4
5
6
  protected ChannelHandler wrapInternal(ChannelHandler handler, URL url) {
return new MultiMessageHandler(
new HeartbeatHandler(
ExtensionLoader.getExtensionLoader(Dispather.class).getAdaptiveExtension().dispath(handler, url)
));
}

现在来仔细看一下HeaderExchangeHandler类的定义,先看一下它定义的received方法,下面是代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void received(Channel channel, Object message) throws RemotingException {
channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());
ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
try {
if (message instanceof Request) {
.....
} else if (message instanceof Response) {
//这里就是作为消费者的dubbo客户端在接收到响应后,触发通知对应等待线程的起点
handleResponse(channel, (Response) message);
} else if (message instanceof String) {
.....
} else {
handler.received(exchangeChannel, message);
}
} finally {
HeaderExchangeChannel.removeChannelIfDisconnected(channel);
}
}

我们主要看中间的那个条件分支,它是用来处理响应消息的,也就是说当dubbo客户端接收到来自服务端的响应后会执行到这个分支,它简单的调用了handleResponse方法,我们追过去看看:

1
2
3
4
5
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) { //排除心跳类型的响应
DefaultFuture.received(channel, response);
}
}

熟悉的身影:DefaultFuture,它是实现了我们上面说的ResponseFuture接口类型,实际上细心的童鞋应该可以看到,上面request方法中其实实例化的就是这个DefaultFutrue对象:

1
DefaultFuture future = new DefaultFuture(channel, req, timeout);

那么我们可以继续来看一下DefaultFuture.received方法的实现细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());
}
}

留一下我们之前提到的id的作用,这里可以看到它已经开始发挥作用了。通过idDefaultFuture.FUTURES可以拿到具体的那个DefaultFuture对象,它就是上面我们提到的,阻塞请求线程的那个对象。好,找到目标后,调用它的doReceived方法,这就是标准的java多线程编程知识了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void doReceived(Response res) {
lock.lock();
try {
response = res;
if (done != null) {
done.signal();
}
} finally {
lock.unlock();
}
if (callback != null) {
invokeCallback(callback);
}
}

这样我们就可以证实上图中左边的绿色箭头所标注的两点。

参考链接:https://blog.csdn.net/joeyon1985/article/details/51046548

🔲 ⭐

JVM内存结构的历史 (从Jdk1.6、1.7、8)

从JDK1.6到1.8, 运行时内存分配简图分别如下:

Mechanism

在JDK1.7中的HotSpot中,已经把原本放在方法区的字符串常量池移出。

  从JDK7开始永久代的移除工作,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
Mechanism

随着JDK8的到来:

JVM不再有PermGen。但类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

Mechanism

一、java7到java8的第一部分变化:元空间

下面来一张图看一下java7到8的内存模型吧(这个是在网上找的图,如有侵权问题请联系我删除。)

img

二、java7到java8的第二部分变化:运行时常量池

运行时常量池(Runtime Constant Pool)的所处区域一直在不断的变化,在java6时它是方法区的一部分;1.7又把他放到了堆内存中;1.8之后出现了元空间,它又回到了方法区。

Metaspace 结构是怎么样的?

Mechanism

参考:https://blog.csdn.net/weixin_42711325/article/details/86533192

☑️ ⭐

MQ组件盘点,哪些你用在了生产中?

市面上的MQ也好几种了,ActiveMq、RabbitMq、rocketMq、kafka、Pulsar。最近国内又陆陆续续开源了几个MQ,如:去哪儿网开源的qmq、腾讯开源的TubeMq、拍拍贷开源的pmq。
现在想需要对比区别一下这些消息队列的不同,分析其优缺点。

一、基本比较

ActiveMQRabbitMQRocketMQKafkaPulsar
代码地址apache/activemqapache/rabbitmq-serverapache/rocketmqapache/kafkaapache/pulsar
PRODUCER-COMSUMER支持支持支持支持支持
PUBLISH-SUBSCRIBE支持支持支持支持支持
REQUEST-REPLY支持支持
API完奋性
多语言支持支持,IAVA优先语言无关支持支持,java优先
单机吞吐量万级万级万级十万级单个分区高达 1.8 M 消息/秒
消息延迟微秒级毫秒级毫秒级99% 的生产延迟小于5ms。
可用性高(主从)高(主从)非常高(分布式)非常高(分布式)
消息丢失理论上不会丢失理论上不会丢失
消息重复可控制理论上会有重复
文挡完备性
提供快速入门
首次部署难度
社区活跃度
商业支持阿里云
成熟度成熟成熟成熟成熟日志领域
支持协议OpenWire、STOMP、REST、 XMPP、AMQPAMQP白己定义的一套,社区提供JMS,不成熟)
持久化内存、文件、数据库内存、文件磁盘文件PageCache ->磁盘Apache BookKeeper
事务支持支持支持
负载均衡支持支持支持
管理界面一般有web console实现
部署方式独立、嵌入独立独立
特点功能齐全,被大 望开源项目使用由于Erlang语言的并发能力,性能很好各个环节分布式扩展设计,主从HA;支持上万个队列;多 种消费模式;性能很好
评价:优点成熟的产品,已经在很多公司得到应用(非大规横场景);有较多的文档;备种协议支持较好;有多重语富的成熟的客户端;由于erlang语富的特 性,mq性能较好;管埋界面 较丰富,在互联网公司也有 较大规棋的应用;支持amqp协议,有多种语言且支持 amqp的客户端可用;模型简单,接口易用(JMS接口在很多场合并不太实用);在阿里大规棋应用;目前支付宝中的余额宝等新兴产品均使用rocketmq;集群规棋大槪在50台左右,单日处理消息上百亿;性能非常好,可以大量消息堆积在broker中;支持多种消费:包括集群消费、广播消费等;社区活跃,版本更新很快。地域复制、多租户、扩展性、读写隔离等等;对 Kubernetes 的友好支持。
评价:缺点根据其他用户反馈,会出现莫名其妙的问题,且会丢消息。目前社区不活跃;不适合用于上千个队列的应用场景。erlang语言难度较大。集群不支持动态扩展。多语言客户端支持需加强部署相对复杂;新来者,文档较少

二、各自优缺点

1、Kafka

大数据行业标配组件

2、RocketMq

​ 有事务性消息、私信队列等支持,适合交易场景

3、Pulsar

​ 新贵,地域复制、多租户、扩展性比较好

4、RabbitMq

​ erlang编写,性能较好。有不少互联网公司用。不过因为erlang,社区开发者较少

5、ActiveMq

项目较老,不够活跃,会丢消息,不适合在互联网项目使用

三、一些问题

1、Kafka的数据丢失问题

一开始就是存储在PageCache上的,定期flush到磁盘上的,也就是说,不是每个消息都被存储在磁盘了,如果出现断电或者机器故障等,PageCache上的数据就丢失了。
这个是总结出的到目前为止没有发生丢失数据的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//producer用于压缩数据的压缩类型。默认是无压缩。正确的选项值是none、gzip、snappy。压缩最好用于批量处理,批量处理消息越多,压缩性能越好
props.put("compression.type", "gzip");
//增加延迟
props.put("linger.ms", "50");
//这意味着leader需要等待所有备份都成功写入日志,这种策略会保证只要有一个备份存活就不会丢失数据。这是最强的保证。,
props.put("acks", "all");
//无限重试,直到你意识到出现了问题,设置大于0的值将使客户端重新发送任何数据,一旦这些数据发送失败。注意,这些重试与客户端接收到发送错误时的重试没有什么不同。允许重试将潜在的改变数据的顺序,如果这两个消息记录都是发送到同一个partition,则第一个消息失败第二个发送成功,则第二条消息会比第一条消息出现要早。
props.put("retries ", MAX_VALUE);
props.put("reconnect.backoff.ms ", 20000);
props.put("retry.backoff.ms", 20000);

//关闭unclean leader选举,即不允许非ISR中的副本被选举为leader,以避免数据丢失
props.put("unclean.leader.election.enable", false);
//关闭自动提交offset
props.put("enable.auto.commit", false);
限制客户端在单个连接上能够发送的未响应请求的个数。设置此值是1表示kafka broker在响应请求之前client不能再向同一个broker发送请求。注意:设置此参数是为了避免消息乱序
props.put("max.in.flight.requests.per.connection", 1);

2、Kafka重复消费原因

强行kill线程,导致消费后的数据,offset没有提交,partition就断开连接。比如,通常会遇到消费的数据,处理很耗时,导致超过了Kafka的session timeout时间(0.10.x版本默认是30秒),那么就会re-blance重平衡,此时有一定几率offset没提交,会导致重平衡后重复消费。
如果在close之前调用了consumer.unsubscribe()则有可能部分offset没提交,下次重启会重复消费

kafka数据重复 kafka设计的时候是设计了(at-least once)至少一次的逻辑,这样就决定了数据可能是重复的,kafka采用基于时间的SLA(服务水平保证),消息保存一定时间(通常为7天)后会被删除

☑️ ⭐

一些有用的github配置

Github是Git的一个商业化平台,git对应的一些功能在Github上都有体现。 在github的使用过程中,一些有用的github配置,明显改进了流程,方便了开发,随手记录了下来。

1、pr完毕联动直接关闭issue :

功能: 当committer合并别人提交的pull request的时候,同时联动关闭问题issue
配置: https://help.github.com/cn/articles/closing-issues-using-keywords

2、设置成sqash 合并提交点:

功能: 当committer合并别人提交的选择将提交点进行合并,类似于指令git squash
配置: https://help.github.com/cn/articles/about-merge-methods-on-github

3、更新其他人的分支代码:

功能:在合作开发中,一些分支的提交者好久没有活动了,远程分支已经修改了很多东西。 committer或者pr的提交可以在github界面手动同步主分支的最新代码.
类似于指令

1
2
git remote update -p
git pull origin master

配置方法如下:

效果:

配置: https://help.github.com/cn/articles/enabling-required-status-checks

4、添加测试覆盖率travis-ci 和codecov

功能: 用junit、travis-ci 和codecov等采集项目的测试覆盖率,从而进行持续集成
配置:

5、将issue指派给项目的contributor

最早时候github不支持将一个issue指派给对项目没有写权限的用户。在2019年下半年开始,github增加了“Triage”角色:没有项目的直接写权限,却能review pr的人。首先issue可以指派给“Triage”角色;一般用户只要在issue上加评论,就可以将issue指派给她。

☑️ ☆

我看《subscribed》

前一段时间在朋友圈看到国内开源界大佬姜宁和冯嘉在推荐一本《subscribed》,
​ 《订阅》,我是最近在朋友圈看到这个本书的。两个开源股界的大佬姜宁和冯嘉同时推荐这本书的。好奇后读起来。发现完全是一本商科的书呀,在推一个观点:各行各业的未来都会用“订阅”的模式进行发展。

​ 订阅的含义是啥??用图画一下订阅模式。一般情况:订报刊。搞技术的:消息队列,异步。为什么异步的模式是未来商业模式的方向和趋势?重点

​ 将书中各个行业的异步商业模型简要讲解,必要时候画出流程图。

​ 自己切身感受和思考。在软件架构方面,事情本来的样子是什么?异步编程,消息驱动。开源社区的运营和商业化方面:吸引同好,

☑️ ☆

为什么Redis读写快?

问题一: Redis是单线程还是多线程?为什么redis会被认为读写都快呢?

单线程的, Redis6 之后多线程IO

1.redis是基于内存的,内存的读写速度非常快;

2.redis是单线程的,省去了很多上下文切换线程的时间;

3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

问题二:Redis都有什么数据结构,内部存储数据结构是啥?

​ String——字符串 简单动态字符串(SDS[Simple Dynamic String])

​ Hash——字典

​ List——列表

​ Set——集合

​ Sorted Set——有序集合

skiplist数据结构

 skiplist作为zset的存储结构,整体存储结构如下图,核心点主要是包括一个dict对象和一个skiplist对象。dict保存key/value,key为元素,value为分值;skiplist保存的有序的元素列表,每个元素包括元素和分值。两种数据结构下的元素指向相同的位置。

问题三 Redis的持久化策略有哪些?

Redis两种持久化方式(RDB&AOF)

RDB每次进行快照方式会重新记录整个数据集的所有信息。RDB在恢复数据时更快,可以最大化redis性能,子进程对父进程无任何性能影响。

AOF有序的记录了redis的命令操作。意外情况下数据丢失甚少。AOF同步也是一种把记录写到硬盘上的行为,在上述两个步骤之外,Redis额外加一步命令,Redis先把记录追加到自己维护的一个aof_buf中。所以AOF持久化分为三步:1、命令追加 2、文件写入 3.文件同步

☑️ ⭐

Dubbo代码分析—超时和重试设计

dubbo提供在provider和consumer端,都提供了超时(timeout)和重试(retries)的参数配置。

一、Dubbo协议中的超时机制

​ 超时机制是dubbo中的一个很重要的机制。在“快速失败”设计中,能将客户端断掉,防止服务端资源耗尽而被压挂。

​ 我们先看一下Dubbo协议中的超时机制是怎么实现的。

​ 超时的实现原理是什么?

我们先回顾一下dubbo的大致流程图。

在生产过程中都是客户端向服务端发送请求,在流程4。

​ 我们可以看到:4的流程是: 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

​ 超时是针对消费端还是服务端?

  • 如果是争对消费端,那么当消费端发起一次请求后,如果在规定时间内未得到服务端的响应则直接返回超时异常,但服务端的代码依然在执行。
  • 如果是争取服务端,那么当消费端发起一次请求后,一直等待服务端的响应,服务端在方法执行到指定时间后如果未执行完,此时返回一个超时异常给到消费端。

dubbo的超时是争对客户端的,由于是一种NIO模式,消费端发起请求后得到一个ResponseFuture,然后消费端一直轮询这个ResponseFuture直至超时或者收到服务端的返回结果。虽然超时了,但仅仅是消费端不再等待服务端的反馈并不代表此时服务端也停止了执行。

​ 超时的实现原理是什么?

之前有简单提到过, dubbo默认采用了netty做为网络组件,它属于一种NIO的模式。消费端发起远程请求后,线程不会阻塞等待服务端的返回,而是马上得到一个ResponseFuture,消费端通过不断的轮询机制判断结果是否有返回。因为是通过轮询,轮询有个需要特别注要的就是避免死循环,所以为了解决这个问题就引入了超时机制,只在一定时间范围内做轮询,如果超时时间就返回超时异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//ResponseFuture接口定义

public interface ResponseFuture {

/**
* get result.
*
* @return result.
*/
Object get() throws RemotingException;

/**
* get result with the specified timeout.
*
* @param timeoutInMillis timeout.
* @return result.
*/
Object get(int timeoutInMillis) throws RemotingException;

/**
* set callback.
*
* @param callback
*/
void setCallback(ResponseCallback callback);

/**
* check is done.
*
* @return done or not.
*/
boolean isDone();
}


//ReponseFuture的实现类:DefaultFuture
//只看它的get方法,可以清楚看到轮询的机制。

public 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();
}
超时解

超时的实现原理是什么?
之前有简单提到过, dubbo默认采用了netty做为网络组件,它属于一种NIO的模式。消费端发起远程请求后,线程不会阻塞等待服务端的返回,而是马上得到一个ResponseFuture,消费端通过不断的轮询机制判断结果是否有返回。因为是通过轮询,轮询有个需要特别注要的就是避免死循环,所以为了解决这个问题就引入了超时机制,只在一定时间范围内做轮询,如果超时时间就返回超时异常。

二、Dubbo协议中的重试机制

在客户端
int DEFAULT_RETRIES = 2;
看到重试次数是2 ,就是说出了本身的一次请求,再失败后,又会再请求一次。

重试策略在集群的失败重试 FailoverClusterInvoker 的策略中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
checkInvokers(copyInvokers, invocation);
String methodName = RpcUtils.getMethodName(invocation);
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if (i > 0) {
checkWhetherDestroyed();
copyInvokers = list(invocation);
// check again
checkInvokers(copyInvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
Result result = invoker.invoke(invocation);
......

可以看到使用的是for循环做的失败重试的。当有异常发生时候,就重试一次访问,直到达到最大重试次数为止。

参考:https://www.cnblogs.com/xuwc/p/8974709.html

🔲 ⭐

【译】apache 基金会角色列表

角色

精英管理通常在每个Apache项目社区中都有不同的角色:

User(用户)

用户是指使用 apache 软件的人。他们通过以错误报告和功能建议的形式向开发人员提供反馈,为Apache项目做出贡献。用户通过在邮件列表和用户支持论坛上帮助其他用户来参与Apache社区的建设。

Contributor (开发者/贡献者)

开发者是用代码或文档形式个 apache 项目做贡献的用户。他们采取额外步骤参与项目,积极参与开发人员邮件列表,参与讨论,提供补丁,文档,建议和批评。开发人员也被称为贡献者

Committer (提交者)

提交者是签订了贡献者许可协议(CLA)文件,并且被授予了 apache 代码库的写入权限的开发者。他们有一个apache.org邮件地址。他们不需要依赖其他人来打补丁,他们实际上正在为项目做出短期决策。PMC可以(甚至是默许)同意并批准它成为永久性的,或者他们可以拒绝它。但是请记住,是PMC做出的决定,而不是单个提交者。

PMC Member (PMC会员)

PMC会员由于项目的发展和承诺的证明而当选的开发商或委员会成员。他们拥有对代码存储库的写访问权、apache.org邮件地址、对与社区相关的决策进行投票的权利以及向活动用户建议提交的权利。作为一个整体,项目管理咨询公司是控制项目的实体,没有其他人。特别是,项目管理委员会必须对其项目软件产品的任何正式发布进行投票。

PMC CHAIR (PMC主席)

项目管理委员会(PMC)的主席由董事会从PMC成员中任命。PMC作为一个整体是控制和领导项目的实体。主席是理事会与项目之间的接口。 PMC主席有特定的职责

ASF MEMBER (ASF会员)

ASF成员是由现任成员提名并因基金会的发展和进步而当选的人。会员关心ASF本身。这通常通过与项目相关和跨项目活动的根源来证明。法律上,会员是基金会的“股东”之一。他们有权选举董事会,作为董事会选举的候选人,并提名一名委员为成员。他们也有权提出一个新的孵化项目(稍后我们将看到这意味着什么)。成员通过邮件列表和年度会议协调其活动。我们有一个完整的Apache成员列表

原文地址:http://apache.org/foundation/how-it-works.html#roles

🔲 ⭐

Dubbo代码分析---服务端线程池

一、Dubbo服务端线程池耗尽问题

在使用Dubbo的过程中,在服务端压力大时候我们常常会遇到说线程池耗尽的这样一个错误日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
17:54:34,026 WARN [New I/O server worker #1-4] -  [DUBBO] Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-10.8.64.57:20880, Pool Size: 300 (active: 300, core: 300, max: 300, largest: 300), Task: 5821 (completed: 5621), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://x.x.x.x:20880!, dubbo version: 2.6.5, current host: x.x.x.x
com.alibaba.dubbo.remoting.ExecutionException: class com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler error when process caught event .
at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:67)
at com.alibaba.dubbo.remoting.transport.AbstractChannelHandlerDelegate.caught(AbstractChannelHandlerDelegate.java:44)
at com.alibaba.dubbo.remoting.transport.AbstractChannelHandlerDelegate.caught(AbstractChannelHandlerDelegate.java:44)
at com.alibaba.dubbo.remoting.transport.AbstractPeer.caught(AbstractPeer.java:127)
at com.alibaba.dubbo.remoting.transport.netty.NettyHandler.exceptionCaught(NettyHandler.java:112)
at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.exceptionCaught(NettyCodecAdapter.java:165)
at org.jboss.netty.channel.Channels.fireExceptionCaught(Channels.java:432)
at org.jboss.netty.channel.AbstractChannelSink.exceptionCaught(AbstractChannelSink.java:52)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:302)
at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:148)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:274)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:261)
at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:350)
at org.jboss.netty.channel.socket.nio.NioWorker.processSelectedKeys(NioWorker.java:281)
at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:201)
at org.jboss.netty.util.internal.IoWorkerRunnable.run(IoWorkerRunnable.java:46)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:619)

Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-10.8.64.57:20880, Pool Size: 300 (active: 300, core: 300, max: 300, largest: 300), Task: 5821

我们从错误堆栈可以看到,是服务端线程池溢出了。

二、源码分析
看一下服务端线程池代码是怎么写的,起到什么作用

画图 dubbo客户端调用服务端线程池的示意图

三、线程池知识

解释一下线程池的定义是用来干嘛的。

一些处理的办法:
适当加大服务端线程池,找到合理的配置。这个关联一线程池的配合准则,链接到老的文章。

四、分析原因,处理建议

为什么线程池升高呢? 主要从流量、cpu数据率、负载、jvm日志几方面分析
1)流量过大?(加缓存、对于上游上游服务)
2) 扩机器:cpu核数的增加
3) 宿主机此时负载高?
4)自己代码优化
5) 设置客户端快速失败

程序慢?io过多?(并行io)

☑️ ⭐

rpc跟踪日志常用shell统计脚本

一、应用日志分析
rpc跟踪日志作为时间序列数据,一般字段有:时间、traceId、接口名、执行时间ms、执行结果等。笔者将自己常用的shell统计脚本记录下来,也希望读大家有帮助。

下面的日志信息trace.log,字段分别是:时间| traceId|接口名|执行时间ms|执行结果

1
2
3
4
5
6
7
8
9
10
11
2018-10-01 14:00:00|1|getById|1|success|
2018-10-01 03:01:00|2|getById|100|fail|
2018-10-02 02:00:00|3|getById|1000|success|
2018-10-01 02:00:00|4|updateById|1000|success|
2018-10-01 02:00:00|5|getById|1000|success|
2018-10-01 02:00:00|6|updateById|200|success|
2018-10-02 14:56:00|7|updateById|20|success|
2018-10-01 02:00:00|8|updateById|60|fail|
2018-10-01 03:00:00|9|updateById|200|success|
2018-10-01 02:02:00|10|updateById|20|success|
2018-10-01 03:02:00|11|insert|20|success|

请实现如下需求:

1、执行结果大于200ms的记录

1
awk -F "|" '{if($4>=200){print $1" "$2" "$3" "$4 }}' trace.log

执行结果

1
2
3
4
5
2018-10-02 02:00:00 3 getById 1000
2018-10-01 02:00:00 4 updateById 1000
2018-10-01 02:00:00 4 getById 1000
2018-10-01 02:00:00 5 updateById 200
2018-10-01 03:00:00 8 updateById 200

2、2018-10-01 日接口数量排行前3

1
awk '/2018-10-01/' trace.log |awk -F "|" '{print $3}'  |sort|uniq -c|sort -rn| head -3

执行结果

1
2
3
5 updateById
3 getById
1 insert

3、对于各个接口的执行时间ms按照(0-50,50-100,100-300,300以上)范围进行数量统计

1
awk -F "|" '{totalCnt[$3]++;if($4<=50){ms50[$3]++};if($4>50 && $4<=100){ms100[$3]++};if($4>100 && $4<=300){ms300[$3]++};if($4>300){ms300b[$3]++}}END{for(i in totalCnt)print i,int(ms50[i]),int(ms100[i]),int(ms300[i]),int(ms300b[i])}' trace.log

执行结果

1
2
3
getById 1 1 0 2
updateById 2 1 2 1
insert 1 0 0 0

4、查询trace.log文件各个接口的失败率

1
awk -F "|" '{totalCnt[$3]++;if($5=="fail"){failCnt[$3]++}}END{for(i in totalCnt)print i,(failCnt[i]/totalCnt[i])*100"%"}' trace.log

执行结果

1
2
3
getById 25%
updateById 16.6667%
insert 0%

5、查询trace.log各个接口的平均耗时

1
awk -F "|" '{totalCnt[$3]++;{rtSum[$3]+=$4}}END{for(i in totalCnt)print i,(rtSum[i]/totalCnt[i])}' trace.log

执行结果

1
2
3
getById 525.25
updateById 250
insert 20

二 、简单数据处理

1、删除id.txt重复id

1
2
3
4
5
6
7
8
aaaaa
2
111
2
111
aaaaa
aaaaa
aaaaa

脚本

1
cat id.txt | sort | uniq

执行结果

1
2
3
111
2
aaaaa

2、递归查看某个目录磁盘占用大小

1
du -h --max-depth=2 /data

一些参考:
http://techslides.com/grep-awk-and-sed-in-bash-on-osx
http://www.grapenthin.org/teaching/geop501/lectures/lecture_10_unix2.pdf
http://coewww.rutgers.edu/www1/linuxclass2005/lessons/lesson9/shell_script_tutorial.html
http://blog.chinaunix.net/uid-26736384-id-5756343.html

☑️ ☆

JVM基本结构

一、什么是 Java GC (垃圾回收) ?

​ 我们知道,java语言不像C++,将内存的分配和回收给程序员来处理。java用统一的垃圾回收机制来管理java进程的内存,就是通常所说的垃圾回收:GC(Garbage Collection)
先到维基百科上看一下Garbage Collection的概念:
​ In computer science, garbage collection (GC) is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objectst that are no longer in use by the program. Garbage collection was invented by John McCarthy around 1959 to simplify manual memory management in Lisp.
​ 这就面临一些问题:java进程到底是什么样的结构?java进程的内存哪些需要被回收,在什么条件下才回收,谁来回收?我们首先来看一些概念

二、什么是JVM ?

从维基百科上看jvm的定义:
A Java virtual machine (JVM) is a virtual machine that enables a computer to run Java programs as well as programs written in other languages that are also compiled to Java bytecode.

三、Jvm运行时数据区

在Java语言中,采用的是共享内存模型来实现多线程之间的信息交换和数据同步的。 根据《Java虚拟机文档》 第七版的规定,Java虚拟机锁管理的内存包含下面几个运行时数据区域:

方法区、虚拟机栈、本地方法栈、队、程序计数器等。

  • 程序计数器:

    一块较小的内存空间,可以看做是当前线程所执行的字节码的行数指示器

  • JAVA虚拟机栈:

    虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

    局部变量表存放了编译器可知的:

    • 基本数据类型(boolean、byte、char、short、int、float、long、double)
    • 对象引用:reference类型,可能是一个指向对象起始地址的引用指针,也可能是一个指向对象起始地址的引用指针
    • returnAddress地址:指向了一条字节码指令的地址
  • 本地方法栈:

    为虚拟机使用到的Native方法服务,与虚拟机栈的作用类似。

  • JAVA堆:

    Java堆是被所有线程共享的一块内存区域,此内存区域的唯一目的就是存放内存实例,所有的对象实例以及数组都要在堆上分配,是垃圾收集管理的主要区域。

  • 方法区:
    和Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编辑器编译后的代码

  • 运行时常量池

    是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池来存放编译期生成的各种字面量和符号引用。

  • 直接内存

    对外内存,不是jvm的存储,所以不受jvm的堆参数控制。NIO可以直接分配对外内存,可能会导致OutOfMemoryError,导致java进程僵死。

四、对象的访问定位

​ 我们Java程序需要通过栈上的reference数据来操作堆上的具体对象。 有两种方式:使用句柄访问通过指针访问

  • 使用句柄

    Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,句柄中包含了对象实例与类型数据各自的具体地址信息。

  • 通过指针

    Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

参考:

  • 《深入理解java虚拟机》周志明
  • 图片来自网络
❌