Zookeeper+Dubbo与面试周介绍
- Zookeeper的下载、配置与运行
- 数据结构node与常用命令
- Watcher机制和权限cal介绍
- 使用Zookeeper的Java原生客户端和Curator进行开发
- RPC调用,在Dubbo架构下各服务的关系
- 整合Dubbo和Zookeeper
- 完成Dubbo开发案例
- 线程进阶面试
- 分布式、微服务面试题
- Spring Cloud、Zookeeper的理解
Zookeeper
- 理解Zookeeper
- 安装、配置
- 节点znode
- 常用命令
- Watcher机制
- ACL权限控制
- 代码实操
理解Zookeeper
- 5大特点
- 集群架构
- Zookeeper和CAP关系
- Zookeeper作用
为什么需要Zookeeper
用起来像单机但是又比单机更可靠
leader在团队里的协调作用
内存、单机
集群、可靠
当信息还没同步完成时,不对外提供服务
同步的时间压缩的更短
Zookeeper诞生历史
雅虎研究室
- 无单点问题的分布式协调架构,精力集中在处理业务逻辑
- 内部很多项目都是使用动物的名字来命名
- 大型动物园
Zookeeper是什么 [底层是Java]
- Zookeeper是开源的高性能的分布式应用协调系统,一个高性能的分布式数据一致性解决方案
5大特点
- 顺序一致性
- 原子性 [全部成功或者全部不成功]
- 单一视图 [无论连接哪个 都是一致的信息]
- 可靠性
- 及时性 [一定时间内能从服务器读到状态]
架构图、集群、工作过程
Zookeeper和CAP的关系
- CP:一致性+分区容错性
- 能得到一致的数据结果,同时系统对网络具备容错性
- 但是它不能保证每次服务请求的可用性
作用
- 分布式服务注册与订阅
- 统一配置文件
- 生成分布式唯一ID [/order-0000001、/order-0000002]
- Master节点选举
针对不能同时进行写数据,保证互斥同步 → 分布式锁
Zookeeper的安装、配置
- 寻找教辅里的
apache-zookeeper-3.6.0-bin.tar
- 解压压缩包:
tar zxvf apache-zookeeper-3.6.0-bin.tar.gz
- 进入压缩包:
cd apache-zookeeper-3.6.0-bin
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ls
bin conf docs lib LICENSE.txt NOTICE.txt README.md README_packaging.md
- 配置[进入文件]:
cp conf/zoo_sample.cfg conf/zoo
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# cp conf/zoo_sample.cfg conf/zoo
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ls conf/
configuration.xsl log4j.properties zoo zoo.cfg zoo_sample.cfg
- 打开文件, 进行修改:
vi conf/zoo.cfg
找到 dataDir=/tmp/zookeeper
修改成 dataDir=/tmp/lib/zookeeper 以免被自动清除
- 启动:
./bin/zkServer.sh start
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ./bin/zkServer.sh start
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... already running as process 1877.
======================================================================
[root@iZbp1dssknxftmjczbtpndZ bin]# ./zkServer.sh start
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... ^[[ASTARTED
[root@iZbp1dssknxftmjczbtpndZ bin]# ./zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
======================================================================
- 停止:
./bin/zkServer.sh stop
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ./bin/zkServer.sh stop
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
znode节点 [基本数据模型]
节点性质
- 树形结构,也可以理解为linux的文件目录
- 每一个节点都是znode,里面可以包含数据,也可以有子节点
- 点分为永久节点和临时节点(与客户端绑定) [session失效,也就是客户端断开过,临时节点消失]
- 每个znode都有版本号,每当数据变化,版本号都会累加(乐观锁)
- 删除或修改节点,版本号不匹配的话(版本号已超时), 会报错)
- 每个节点存储的数据不宜过大,几k即可 [保存路径再去查询]
- 节点可以设置权限,来限制用户的访问
- Zookeeper保证读和写都是原子操作,且每次读写操作都是对数据的完整读取或完整写入
节点类型
- 持久节点
- 临时节点
- 顺序节点
节点属性
- dataVersion
- cversion [child]
- aclVersion [权限]
常用命令
启动:
./bin/zkServer.sh start
连接到Zookeeper
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ./bin/zkServer.sh start /usr/bin/java ZooKeeper JMX enabled by default Using config: /root/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ./bin/zkCli.sh -server 127.0.0.1:2181
查看节点
[zk: 127.0.0.1:2181(CONNECTED) 3] ls ls [-s] [-w] [-R] path [zk: 127.0.0.1:2181(CONNECTED) 4] ls / [zookeeper] [zk: 127.0.0.1:2181(CONNECTED) 5] ls /zookeeper [config, quota] [zk: 127.0.0.1:2181(CONNECTED) 6]
- 查看节点状态:
stat /
[zk: 127.0.0.1:2181(CONNECTED) 6] stat / cZxid = 0x0 ctime = Thu Jan 01 08:00:00 CST 1970 mZxid = 0x0 mtime = Thu Jan 01 08:00:00 CST 1970 pZxid = 0x0 cversion = -1 //子节点更改的次数 dataVersion = 0 //数据更改的情况 aclVersion = 0 //权限修改的情况 ephemeralOwner = 0x0 //[0是永久节点 其他的是临时节点] dataLength = 0 numChildren = 1 //有几个子节点
- 查看节点的数据和状态:
get
[zk: 127.0.0.1:2181(CONNECTED) 7] get /45 jj
- 创建、修改、删除节点
[zk: 127.0.0.1:2181(CONNECTED) 8] create create [-s] [-e] [-c] [-t ttl] path [data] [acl] //创建 [zk: 127.0.0.1:2181(CONNECTED) 9] create /imooc2 Created /imooc2 [zk: 127.0.0.1:2181(CONNECTED) 10] create /imooc3 123 Created /imooc3 [zk: 127.0.0.1:2181(CONNECTED) 11] get /imooc3 123 [zk: 127.0.0.1:2181(CONNECTED) 12] stat /imooc3 cZxid = 0x5 ctime = Sun May 05 01:41:21 CST 2024 mZxid = 0x5 mtime = Sun May 05 01:41:21 CST 2024 pZxid = 0x5 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 0 //修改 [zk: 127.0.0.1:2181(CONNECTED) 13] set /imooc3 456 [zk: 127.0.0.1:2181(CONNECTED) 14] get /imooc3 456 [zk: 127.0.0.1:2181(CONNECTED) 15] stat /imooc3 cZxid = 0x5 ctime = Sun May 05 01:41:21 CST 2024 mZxid = 0x6 mtime = Sun May 05 01:42:16 CST 2024 pZxid = 0x5 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 0
- 查看节点状态:
高级命令
创建节点的高级功能
[zk: 127.0.0.1:2181(CONNECTED) 16] create /imooc4
Created /imooc4
[zk: 127.0.0.1:2181(CONNECTED) 17] create -s /imooc4 /s
Created /imooc40000000003
[zk: 127.0.0.1:2181(CONNECTED) 18] create -s /imooc4 /s
Created /imooc40000000004
[zk: 127.0.0.1:2181(CONNECTED) 22] create /imooc
Created /imooc
[zk: 127.0.0.1:2181(CONNECTED) 23] create -e /imooc/tmp 123
Created /imooc/tmp
[zk: 127.0.0.1:2181(CONNECTED) 24] stat /imooc/tmp
cZxid = 0xd
ctime = Sun May 05 01:48:50 CST 2024
mZxid = 0xd
mtime = Sun May 05 01:48:50 CST 2024
pZxid = 0xd
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x10085ad02c90001 //[0x0是永久节点 其他的是临时节点]
dataLength = 3
numChildren = 0
[zk: 127.0.0.1:2181(CONNECTED) 27] set /imooc 6
[zk: 127.0.0.1:2181(CONNECTED) 28] get /imooc
6
[zk: 127.0.0.1:2181(CONNECTED) 29] stat /imooc
cZxid = 0xc
ctime = Sun May 05 01:48:46 CST 2024
mZxid = 0xe
mtime = Sun May 05 01:51:15 CST 2024
pZxid = 0xd
cversion = 1
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 1
numChildren = 1
//set -v 1 /imooc 9 是因为上面 dataVersion = 1 指定条件版本更新
[zk: 127.0.0.1:2181(CONNECTED) 35] set -v 1 /imooc 9
[zk: 127.0.0.1:2181(CONNECTED) 36] get /imooc
9
[zk: 127.0.0.1:2181(CONNECTED) 38] delete
delete [-v version] path //也可以按照版本号去删除
[zk: 127.0.0.1:2181(CONNECTED) 42] ls /
[imooc, imooc2, imooc3, imooc4, imooc40000000003, imooc40000000004, imooc40000000005, zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 43] delete /imooc40000000003
[zk: 127.0.0.1:2181(CONNECTED) 44] ls /
[imooc, imooc2, imooc3, imooc4, imooc40000000004, imooc40000000005, zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 45]
Watcher机制
Watcher事件类型
EventType | 触发条件 |
---|---|
NodeCreated (节点创建) | Watcher监听的对应数据节点被创建 |
NodeDeleted (节点删除) | Watcher监听的对应数据节点被删除 |
NodeDataChanged(节点数据修改) | Watcher监听的对应数据节点的数据内容发生变更 |
NodeChildrenChanged(子节点变更) | Watcher监听的对应数据节点的子节点列表发生变更 |
ACL
- access control list 权限控制
- 它使用权限位来允许/禁止对话节点及其所作用域的各种操作
- ACL仅与特定的znode有关,与子节点无关
Scheme
- ACL:[scheme采用的权限机制:id用户:permissions权限组合字符串]
- world
- auth [认证登录]
- digest [密文加密]
- ip [只允许特定ip访问]
- super [超级权限]
权限字符串crdwa
[ACL]权限使用场景
- 区分开发/测试/运维环境,防止误操作
- 可以针对不同IP而产生具体的配置,更安全
Java原生客户端连接到zookeeper [ZK]
- 利用ZK原生的Java的API
- 利用Apache Curator作为客户端来操作ZK
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.imooc</groupId>
<artifactId>zk-practicer</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
</dependency>
</dependencies>
</project>
com/imooc/zkjavaapi/ZKConnect.java
package com.imooc.zkjavaapi;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.omg.CORBA.TIMEOUT;
import java.io.IOException;
/**
* 连接到ZK服务端,打印连接状态
*/
public class ZKConnect implements Watcher {
public static final String SERVER_PATH="47.98.225.105:2181";
public static final Integer TIMEOUT = 5000;
public static void main(String[] args) throws IOException, InterruptedException {
//后面new的相当于把这个作为参数传递进去
//客户端和服务端是异步连接,连接成功之后,客户端会收到watcher通知
//connectString:服务器的IP+端口号
//sessionTImeout:超时时间
//watcher:接收通知事件
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKConnect());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(2000);
System.out.println(zk.getState());
}
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("收到了通知" + watchedEvent);
}
}
===========================================================
17:35:46 INFO zookeeper.ZooKeeper: Client environment:os.memory.free=466MB
17:35:46 INFO zookeeper.ZooKeeper: Client environment:os.memory.max=7209MB
17:35:46 INFO zookeeper.ZooKeeper: Client environment:os.memory.total=487MB
17:35:46 INFO zookeeper.ZooKeeper: Initiating client connection, connectString=127.0.0.1:2181 sessionTimeout=5000 watcher=com.imooc.zkjavaapi.ZKConnect@7591083d
17:35:46 INFO common.X509Util: Setting -D jdk.tls.rejectClientInitiatedRenegotiation=true to disable client-initiated TLS renegotiation
17:35:46 INFO zookeeper.ClientCnxnSocket: jute.maxbuffer value is 1048575 Bytes
17:35:46 INFO zookeeper.ClientCnxn: zookeeper.request.timeout value is 0. feature enabled=false
客户端开始连接ZK服务器了
CONNECTING
17:35:46 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
17:35:46 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
17:35:46 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:50517, server: 127.0.0.1/127.0.0.1:2181
17:35:46 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x10004ec08710001, negotiated timeout = 5000
收到了通知WatchedEvent state:SyncConnected type:None path:null
CONNECTED
log4j.properties
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss} %p %c{2}: %m%n
[root@iZbp1dssknxftmjczbtpndZ apache-zookeeper-3.6.0-bin]# ./bin/zkServer.sh start
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/apache-zookeeper-3.6.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... already running as process 4147.
windows环境下安装zookeeper教程详解(单机版)_windows zooke-CSDN博客
用代码对节点进行操作
com/imooc/zkjavaapi/ZKOperator.java
package com.imooc.zkjavaapi;
import java.io.IOException;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
/**
* 描述: 演示对节点的操作,包含创建、读取、删除等。
*/
public class ZKOperator implements Watcher {
public static final String SERVER_PATH = "127.0.0.1:2181";
public static final Integer TIMEOUT = 5000;
public static void main(String[] args)
throws IOException, InterruptedException, KeeperException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKOperator());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(2000);
/**
* path:创建的路径
* data:存储的数据
* acl:权限,开放
* createMode:永久、临时、顺序。
*/
System.out.println(zk.create("/imooc-create-node2", "imooc2".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT));
}
@Override
public void process(WatchedEvent event) {
}
}
==========================================================================
客户端开始连接ZK服务器了
CONNECTING
17:57:14 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
17:57:14 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
17:57:14 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:57443, server: 127.0.0.1/127.0.0.1:2181
17:57:14 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x10004ec08710008, negotiated timeout = 5000
/imooc-create-node2
==========================================================================
==========================================================================
/**
* path:创建的路径
* data:存储的数据
* acl:权限,开放
* createMode:永久、临时、顺序。
*/
// System.out.println(zk.create("/imooc-create-node2", "imooc2".getBytes(), Ids.OPEN_ACL_UNSAFE,
// CreateMode.PERSISTENT));
// zk.setData("/imooc-create-node", "imooc3".getBytes(), 1);
byte[] data = zk.getData("/imooc-create-node2", null, null);
System.out.println(new String(data));
}
==========================================================================
客户端开始连接ZK服务器了
CONNECTING
17:58:09 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
17:58:09 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
17:58:09 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:57766, server: 127.0.0.1/127.0.0.1:2181
17:58:09 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x10004ec08710009, negotiated timeout = 5000
imooc2
version版本不一样 保证不做修改
com/imooc/zkjavaapi/ZKOperator.java
修改值 让版本变成1
zk.setData("/imooc-create-node2", "imooc3".getBytes(), 1);
byte[] data = zk.getData("/imooc-create-node2", null, null);
System.out.println(new String(data));
----------------------------------------------------------------------------
Exception in thread "main" org.apache.zookeeper.KeeperException$BadVersionException: KeeperErrorCode = BadVersion for /imooc-create-node2
at org.apache.zookeeper.KeeperException.create(KeeperException.java:122)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:54)
at org.apache.zookeeper.ZooKeeper.setData(ZooKeeper.java:2551)
at com.imooc.zkjavaapi.ZKOperator.main(ZKOperator.java:41)
报错=>版本不一致
===========================================================================
[修改]
zk.setData("/imooc-create-node2", "imooc3".getBytes(), 0);
byte[] data = zk.getData("/imooc-create-node2", null, null);
System.out.println(new String(data));
----------------------------------------------------------------------------
客户端开始连接ZK服务器了
CONNECTING
18:01:35 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
18:01:35 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
18:01:35 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:58870, server: 127.0.0.1/127.0.0.1:2181
18:01:35 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x10004ec0871000d, negotiated timeout = 5000
imooc3 [修改成功]
删除节点 [引入回调函数+休眠]
com/imooc/zkjavaapi/ZKOperator.java
package com.imooc.zkjavaapi;
import java.io.IOException;
import com.imooc.zkjavaapi.callback.DeleteCallBack;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
/**
* 描述: 演示对节点的操作,包含创建、读取、删除等。
*/
public class ZKOperator implements Watcher {
public static final String SERVER_PATH = "127.0.0.1:2181";
public static final Integer TIMEOUT = 5000;
public static void main(String[] args)
throws IOException, InterruptedException, KeeperException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKOperator());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(2000);
/**
* path:创建的路径
* data:存储的数据
* acl:权限,开放
* createMode:永久、临时、顺序。
*/
zk.create("/imooc-create-node3", "imooc3".getBytes(), Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
// zk.setData("/imooc-create-node2", "imooc3".getBytes(), 0);
// byte[] data = zk.getData("/imooc-create-node2", null, null);
String ctx = "删除成功"; //把ctx的内容代入到DeleteCallBack()里面去运行
zk.delete("/imooc-create-node3",0,new DeleteCallBack(),ctx);
Thread.sleep(2000);
// System.out.println(new String(data));
}
@Override
public void process(WatchedEvent event) {
}
}
--------------------------------------------------------------------------------
客户端开始连接ZK服务器了
CONNECTING
18:10:01 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
18:10:01 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
18:10:01 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:2600, server: 127.0.0.1/127.0.0.1:2181
18:10:01 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x10004ec0871000f, negotiated timeout = 5000
删除节点/imooc-create-node3
删除成功
com/imooc/zkjavaapi/callback/DeleteCallBack.java
package com.imooc.zkjavaapi.callback;
import org.apache.zookeeper.AsyncCallback;
/**
* 删除后运行的方法
*/
public class DeleteCallBack implements AsyncCallback.VoidCallback {
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println("删除节点" + path);
System.out.println((String)ctx);
}
}
处理Watcher事件
com/imooc/zkjavaapi/ZKGetNode.java
package com.imooc.zkjavaapi;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* 和节点相关:是否存在,获取数据,加上Watch
*/
public class ZKGetNode implements Watcher {
public static final String SERVER_PATH = "127.0.0.1:2181";
public static final Integer TIMEOUT = 5000;
//这个是门闩
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
/**
* 客户端和服务端他们是异步连接,连接成功之后,客户端会收到watcher通知。
* connectString:服务器的IP+端口号,比如127.0.0.1:2181
* sessionTimeout:超时时间
* watcher:通知事件
*/
ZooKeeper zk = new ZooKeeper(SERVER_PATH, TIMEOUT, new ZKGetNode());
System.out.println("客户端开始连接ZK服务器了");
System.out.println(zk.getState());
Thread.sleep(2000);
System.out.println(zk.getState());
// Stat exists = zk.exists("/imooc-create-node", false);//不需要额外监听
// if (exists != null){
// System.out.println("节点的版本为: "+exists.getVersion());
// }else{
// System.out.println("该节点不存在");
// }
zk.getData("/imooc-create-node", true, null);
countDownLatch.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeChildrenChanged){
System.out.println("数据被改变");
countDownLatch.countDown();
}
System.out.println("收到了通知" + event);
}
}
--------------------------------------------------------------------------------
在运行的情况下 去cmd中 修改
[zk: localhost:2181(CONNECTED) 1] set /imooc-create-node 11
--------------------------------------------------------------------------------
客户端开始连接ZK服务器了
CONNECTING
18:52:14 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
18:52:14 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
18:52:14 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:16204, server: 127.0.0.1/127.0.0.1:2181
18:52:14 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x1000534afde0001, negotiated timeout = 5000
收到了通知WatchedEvent state:SyncConnected type:None path:null
CONNECTED
数据被改变
收到了通知WatchedEvent state:SyncConnected type:NodeDataChanged path:/imooc-create-node
用Curator操作ZK
原生的Java的API的缺点
- 不支持连接超时后的自动连接
- Watcher注册一次后会失效
- 不支持递归创建节点
利用Apache Curator
- 解决了Watcher注册一次后会失效的问题
- API更加简单易用,提供了工具类
com/imooc/curator/CuratorTests.java
package com.imooc.curator;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
/**
* 用Curator来操作ZK
*/
public class CuratorTests {
public static void main(String[] args) throws Exception {
String connectString = "127.0.0.1:2181";
RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retry);
client.start();
String path = "/curator";
String data = "test";
client.create().withMode(CreateMode.PERSISTENT).forPath(path,data.getBytes());
byte[] bytes = client.getData().forPath(path);
System.out.println(new String(bytes));
}
}
更改高级一点!!!【添加+修改+删除】
com/imooc/curator/CuratorTests.java
package com.imooc.curator;
import com.sun.net.httpserver.Authenticator.Retry;
import java.text.MessageFormat;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher.Event.EventType;
/**
* 描述: 用Curator来操作ZK
*/
public class CuratorTests {
public static void main(String[] args) throws Exception {
String connectString = "127.0.0.1:2181";
String path = "/curator2";
RetryPolicy retry = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.newClient(connectString, retry);
client.start();
client.getCuratorListenable().addListener((CuratorFramework c, CuratorEvent event) -> {
switch (event.getType()) {
case WATCHED:
WatchedEvent watchedEvent = event.getWatchedEvent();
if (watchedEvent.getType() == EventType.NodeDataChanged) {
System.out.println(new String(c.getData().forPath(path)));
}
}
});
String data = "test";
String data2 = "test2";
//添加
client.create().withMode(CreateMode.PERSISTENT).forPath(path, data.getBytes());
byte[] bytes = client.getData().watched().forPath(path);
System.out.println(new String(bytes));
//更改
client.setData().forPath(path, data2.getBytes());
//删除
client.delete().forPath(path);
Thread.sleep(2000); //保证足够时间运行成功
}
}
-------------------------------------------------------------------------------------
19:44:41 INFO zookeeper.ClientCnxn: Opening socket connection to server 127.0.0.1/127.0.0.1:2181.
19:44:41 INFO zookeeper.ClientCnxn: SASL config status: Will not attempt to authenticate using SASL (unknown error)
19:44:41 INFO zookeeper.ClientCnxn: Socket connection established, initiating session, client: /127.0.0.1:32984, server: 127.0.0.1/127.0.0.1:2181
19:44:41 INFO zookeeper.ClientCnxn: Session establishment complete on server 127.0.0.1/127.0.0.1:2181, session id = 0x1000534afde0005, negotiated timeout = 40000
19:44:41 INFO state.ConnectionStateManager: State change: CONNECTED
test
test2
Dubbo [RPC远程过程调用]
- 初识Dubbo
- RPC介绍
- Dubbo工作原理
- 案例实操:项目编写
- 整合Dubbo和Zookeeper
- 实现服务间调用
初始Dubbo
Dubbo是什么
- 轻量级、高性能的RPC框架
- 并不是要成为一个微服务的全面解决方案
- 以Java语言而出名
Dubbo现状
- 全称是Apache Dubbo
- 微店、网易云音乐、滴滴、中国电信、中国人寿
- star有30K+个,fork有20K+个
Dubbo的故事
- 09年开始做,做的第一个版本
- 10年初的时候,架构升级,Dubbo2.0
- 开源
- one company战略
- 合到HSF去
- 第3节点,捐给Apache
开源的理解
- 共同成长、巨人的肩膀上
- 演化慢、不断革新、很强大的生命力
- 突破任何的束缚,突破任何的常规,包容和开放
RPC介绍
- RPC ——远程过程调用
- 早期单机时代:IPC
- 网络时代:把IPC扩展到网络上,这就是RPC
- 实现RPC很头疼,于是有了RPC框架
- 调用其他机器上的程序和调用本地的程序一样方便
常见的RPC框架
- 阿里的Dubbo
- 新浪的Montan
- Facebook的Thrift
- 各个框架都有其各自的优缺点
HTTP和RPC对比
- 普通话[通用] 与 方言[企业内部]
- 普通话本质上也是一种方言,只不过它是官方的方言
- 传输效率
- RPC定制自己传输请求让传输效率更高
- HTTP会包含一些无用的内容效率较低
- 性能消耗,主要在于序列化和反序列化的耗时
- 负载均衡
Dubbo工作原理
一旦注册中心的信息有变化的时候会主动推送信息
- 服务容器负责启动,加载,运行服务提供者
- 服务提供者在启动时,向注册中心注册自己提供的服务
- 服务提供者在启动时,向注册中心订阅自己所需的服务
- 注册中心返回服务提供者地址列表给消费者
- 从提供者地址列表中,选一台提供者进行调用
- 定期发送一次统计数据到监控中心
模块 | 说明 |
---|---|
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的控制中心 |
Container | 服务运行容器 |
服务提供者开发
案例实操
- 引入依赖
- 添加注解
- 整合Dubbo和Zookeeper
pom.xml[dubbo-practice]
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>producer</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.imooc</groupId>
<artifactId>dubbo-practice</artifactId>
<version>0.0.1</version>
<name>dubbo-practice</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.1.12.RELEASE</spring-boot.version>
<dubbo.version>2.7.4.1</dubbo.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Apache Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-bom</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
</project>
pom.xml[dubbo-practice-producer]
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo-practice</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>producer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!-- Zookeeper dependencies -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Web 功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL connector, 需要与 MySQL 版本对应 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- MyBatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</project>
com/imooc/producer/service/CourseListService.java
package com.imooc.producer.service;
import com.imooc.producer.entity.Course;
import java.util.List;
/**
* 课程列表服务
*/
public interface CourseListService {
List<Course> getCourseList();
}
com/imooc/producer/entity/Course.java
package com.imooc.producer.entity;
import java.io.Serializable;
/**
* 描述: Course实体类
*/
public class Course implements Serializable {
Integer id;
Integer courseId;
String name;
//1上架,0下架
Integer valid;
@Override
public String toString() {
return "Course{" +
"id=" + id +
", courseId=" + courseId +
", name='" + name + '\'' +
", valid=" + valid +
'}';
} Getter+Setter
}
com/imooc/producer/service/impl/CourseListServiceImpl.java
package com.imooc.producer.service.impl;
import com.imooc.producer.entity.Course;
import com.imooc.producer.mapper.CourseMapper;
import com.imooc.producer.service.CourseListService;
import java.util.List;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 描述: 课程列表服务实现类
*/
@Service(version = "${demo.service.version}")
public class CourseListServiceImpl implements CourseListService {
@Autowired
CourseMapper courseMapper;
public List<Course> getCourseList() {
return courseMapper.findValidCourses();
}
}
com/imooc/producer/mapper/CourseMapper.java
package com.imooc.producer.mapper;
import com.imooc.producer.entity.Course;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* Mapper类
*/
@Mapper
@Repository
public interface CourseMapper {
@Select("SELECT * FORM course WHERE valid = 1")
List<Course> findValidCourses();
}
application.properties
demo.service.version=1.0.0
#server.port=8081
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/course_prepare?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}
spring.application.name=course-list
#dubbo协议
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
#dubbo注册
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.file=${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache
mybatis.configuration.map-underscore-to-camel-case=true
dubbo.scan.base-packages=com.imooc.producer.service.impl
com/imooc/producer/DubboProducerApplication.java
package com.imooc.producer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 描述: Spring Boot启动类
*/
@EnableAutoConfiguration
public class DubboProducerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboProducerApplication.class, args);
}
}
服务消费方开发
查看PID为8080:netstat -ano|findstr 8080
杀死进程:taskkill /pid 查询的PID /f
pom.xml[dubbo-practice-consumer]
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo-practice</artifactId>
<groupId>com.imooc</groupId>
<version>0.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>concumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
</dependency>
<!-- Zookeeper dependencies -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>${dubbo.version}</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Web 功能 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL connector, 需要与 MySQL 版本对应 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- MyBatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.imooc</groupId>
<artifactId>producer</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
com/imooc/consumer/service/CoursePriceService.java
package com.imooc.consumer.service;
import com.imooc.consumer.entity.CourseAndPrice;
import com.imooc.consumer.entity.CoursePrice;
import java.util.List;
/**
* 描述: 课程价格服务
*/
public interface CoursePriceService {
CoursePrice getCoursePrice(Integer courseId);
List<CourseAndPrice> getCoursesAndPrice();
}
com/imooc/consumer/service/impl/CoursePriceServiceImpl.java
package com.imooc.consumer.service.impl;
import com.imooc.consumer.dao.CoursePriceMapper;
import com.imooc.consumer.entity.CourseAndPrice;
import com.imooc.consumer.entity.CoursePrice;
import com.imooc.consumer.service.CoursePriceService;
import com.imooc.producer.entity.Course;
import com.imooc.producer.service.CourseListService;
import java.util.ArrayList;
import java.util.List;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 描述: 课程 价格服务
*/
@Service
public class CoursePriceServiceImpl implements CoursePriceService {
@Autowired
CoursePriceMapper coursePriceMapper;
@Reference(version = "${demo.service.version}")
CourseListService courseListService;
@Override
public CoursePrice getCoursePrice(Integer courseId) {
return coursePriceMapper.findCoursePrices(courseId);
}
@Override
public List<CourseAndPrice> getCoursesAndPrice() {
List<CourseAndPrice> courseAndPriceList = new ArrayList<>();
List<Course> courseList = courseListService.getCourseList();
for (int i = 0; i < courseList.size(); i++) {
Course course = courseList.get(i);
if (course != null) {
CoursePrice price = getCoursePrice(course.getCourseId());
if (price != null && price.getPrice() > 0) {
CourseAndPrice courseAndPrice = new CourseAndPrice();
courseAndPrice.setId(course.getId());
courseAndPrice.setCourseId(course.getCourseId());
courseAndPrice.setName(course.getName());
courseAndPrice.setPrice(price.getPrice());
courseAndPriceList.add(courseAndPrice);
}
}
}
return courseAndPriceList;
}
}
com/imooc/consumer/dao/CoursePriceMapper.java
package com.imooc.consumer.dao;
import com.imooc.consumer.entity.CoursePrice;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;
/**
* 描述: Mapper类
*/
@Mapper
@Repository
public interface CoursePriceMapper {
@Select("SELECT * FROM course_price WHERE course_id = #{courseId}")
CoursePrice findCoursePrices(Integer courseId);
}
com/imooc/consumer/entity/CourseAndPrice.java
package com.imooc.consumer.entity;
import java.io.Serializable;
/**
* 描述: CoursePrice实体类
*/
public class CourseAndPrice implements Serializable {
Integer id;
Integer courseId;
String name;
Integer price;
@Override
public String toString() {
return "CourseAndPrice{" +
"id=" + id +
", courseId=" + courseId +
", name='" + name + '\'' +
", price=" + price +
'}';
} Getter+Setter
}
com/imooc/consumer/entity/CoursePrice.java
package com.imooc.consumer.entity;
import java.io.Serializable;
/**
* 描述: CoursePrice实体类
*/
public class CoursePrice implements Serializable {
Integer id;
Integer courseId;
Integer price;
} Getter+Setter
com/imooc/consumer/controller/CoursePriceController.java
package com.imooc.consumer.controller;
import com.imooc.consumer.entity.CourseAndPrice;
import com.imooc.consumer.entity.CoursePrice;
import com.imooc.consumer.service.CoursePriceService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 描述:CoursePriceController
*/
@RestController
public class CoursePriceController {
@Autowired
CoursePriceService coursePriceService;
@GetMapping({"/price"})
public Integer getCoursePrice(Integer courseId) {
CoursePrice coursePrice = coursePriceService.getCoursePrice(courseId);
if (coursePrice != null) {
return coursePrice.getPrice();
} else {
return -1;
}
}
@GetMapping({"/coursesAndPrice"})
public List<CourseAndPrice> getcoursesAndPrice() {
return coursePriceService.getCoursesAndPrice();
}
}
application.properties
demo.service.version=1.0.0
server.port=8084
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/course_practice?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root
logging.pattern.console=%clr(%d{${LOG_DATEFORMAT_PATTERN:HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}
spring.application.name=course-price
#dubboåè®®
dubbo.protocol.name=dubbo
dubbo.protocol.port=-1
#dubbo注å
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.registry.file=${user.home}/dubbo-cache/${spring.application.name}/dubbo.cache
com/imooc/consumer/DubboConsumerApplication.java
package com.imooc.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 描述: Spring Boot启动类
*/
@SpringBootApplication
public class DubboConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboConsumerApplication.class, args);
}
}
案例实操总结
- 自动检查zk和依赖的服务
- dubbo.scan.base-packages配置
- 实现服务间调用
面试课
Spring Boot常见面试题
线程常见面试题
分布式的面试题
Docker相关面试题
Nginx和Zookeeper相关面试题
RabbitMQ相关面试题
微服务相关
彩蛋:学习方法
锁分类、死锁
HashMap和final
单例模式
面试避坑指南
重要的软实力
Spring、Spring Boot和Spring Cloud的关系?
- Spring最初利用IOC和AOP解耦
- 按照这种模式搞了MVC框架 [之后就配置太繁琐了]
- 写了很多样板代码很麻烦,就有了Spring Boot
- Spring Boot是在强大的Spring帝国发展起来的,发明Spring Boot是为了让人们更好更高效的使用Spring,Spring Boot理念是约定优于配置
- Spring Cloud是在Spring Boot基础上诞生的 [一系列框架的有序集合]
Spring Boot如何配置多环境
面试官你好,我这边平时是会使用多套环境,比如说”开发、测试、预发、生产”环境。
开发环境通常在本地,它所连接的数据库也是专门用于开发的,里面的数据也是一定情况下算出来的,因为并不需要在开发环境的情况下保证数据的完全精确,为了开发效率的提高,我们通常造一些模拟的数据,通常开发完后我们要把程序部署到测试环境,因为测试环境通常是公司所提供的服务器,而开发环境通常是我们本机,对于本机而言如果关闭或关机后别人就无法访问了,但是测试的同学工作时间不一定能和开发的同学一致,如果把程序关掉了他们就没办法测试了。我们需要给测试同学提供一套稳定的环境去测试。而且有的时候会同时开发多种功能,前一个功能开发完了需要去测试,这个时候就要去开发新的功能了,此时本地的代码已经发生了变化,如果把开发环境当成测试环境的话会发生很多问题,它实际测试的和我们想要测试的不是同一套代码,正是这个原因测试环境是必不可少的,需要用一台稳定的服务器把我们开发好的部署上测试环境中去,这样的话无论电脑是否关机都不会影响测试人员的进度。但是在测试环境的数据库往往可以和开发环境的保持一致可以允许公用同一个数据。
预发环境是预备发布,和真正的线上环境高度统一,和测试环境的区别:
1.网络隔离 为了保证线上环境的稳定会采取环境隔离,在本地或者测试环境下是没有办法访问到预发环境的机器,不可直接访问。在预发环境通常采用真实的数据库去测试。在测试环境并不能把所有问题都测试出来,所以在测试环境中无法测试到的问题在预发环境就可以暴露出来了,有时候在测试环境中模拟的数据不是准确,比如模拟一个商品详情,报的是50个字,最后发现真实情况是100个字,就能看到数据库大小不够,再次比如测试的是整数,到真实环境中发现是小数。隔离+数据验真
生产环境是真实对外的数据,也会有很多流量进来,直接面向所有用户,也有并发问题,要确保数据稳定
在发布到某个环境之前,不建议把配置文件全部删除替换,有可能漏了文件导致了错误的替换,如果发布环境是测试环境的数据库,有可能会产生对外暴露的是测试环境的情况,这是很严重的事故
com/imooc/profiles/ProfilesApplication.java
package com.imooc.profiles;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProfilesApplication {
public static void main(String[] args) {
SpringApplication.run(ProfilesApplication.class, args);
}
}
application.properties
spring.profiles.active=prod
application-pre.properties
spring.profiles.active=test
server.port=8082
application-prod.properties
spring.profiles.active=test
server.port=8083
application-test.properties
spring.profiles.active=test
server.port=8081
实际工作中,如何全局处理异常?
如果我们不进行处理的话,异常可能会把整个堆栈抛出去,一旦发生异常,用户或者别用用心的黑客可以看到详细的异常发生情况,包含详细的错误信息和代码的行数,这样的话对方可以利用一个漏洞进行不同的尝试,而且可以顺藤摸瓜分析出更多潜在的风险,最终把系统攻击破,异常是必须处理的。
但为什么要全局处理呢?电商项目→exception→GlobalExceptionHandler
package com.imooc.mall.exception;
import com.imooc.mall.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
/**
* 19.处理统一异常的handler 业务异常 处理不同逻辑异常 20对密码进行MD5加密UserServiceImpl 先创建MD5Utils
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 统一处理Exception.class异常 所有异常的父类
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handleException(Exception e) {
log.error("Default Exception: ", e);
return ApiRestResponse.error(ImoocMallExceptionEnum.SYSTEM_ERROR);
}
// 处理自己所定义的异常 用户/密码不能为空......
@ExceptionHandler(ImoocMallException.class)
@ResponseBody
public Object handleImoocMallException(ImoocMallException e) {
log.error("ImoocMallException: ", e); //传进来的是什么就传出去
return ApiRestResponse.error(e.getCode(), e.getMessage());
}
// 39.处理方法参数不合规的情况
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ApiRestResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException: ", e);
return handleBindingResult(e.getBindingResult());
}
// 40.处理返回异常的ApiRespond 41去pom引入Swagger自动生成API文档
private ApiRestResponse handleBindingResult(BindingResult result){
// 把异常处理为对外暴露的提示
List<String> list = new ArrayList<>();
if (result.hasErrors()){
List<ObjectError> allErrors = result.getAllErrors();
for (ObjectError objectError : allErrors) { //itli快速 对着for按alt+回车 改成增强for
String message = objectError.getDefaultMessage();
list.add(message);
}
}
if (list.size() == 0){
return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR);
} //list.toString()生成所创建的异常描述信息
return ApiRestResponse.error(ImoocMallExceptionEnum.REQUEST_PARAM_ERROR.getCode(), list.toString());
}
}
线程如何启动?
Thread.start.run
因为它只是一个普通的java代码,而不会真正的启动一个线程,调用一次run()方法只执行一次,而且是在主线程执行的,就没有起到任何创建线程的效果了。
如果选择start方法的话会在后台执行很多操作,比如去申请一个线程、让子方法去执行run()里的内容,而且还包括执行之后的对线程状态的调整。所以说表面上是相同都是执行一段代码,但是实际上是不同的。
两次调用start()方法会报异常,异常类型叫做
IllegalThreadStateException
,在start()的时候首先会进行线程状态的检测只有是new的时候才能正常启动,不允许启动两次
com/imooc/interniew/StartTwice.java
package com.imooc.interniew;
/**
* 描述: 两次启动线程
*/
public class StartTwice {
public static void main(String[] args) {
Thread thread = new Thread();
thread.start();
thread.start();
}
}
======================== 报错信息 ==========================
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.imooc.interniew.StartTwice.main(StartTwice.java:11)
Thread.java
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
实现多线程的方法有几种?
com/imooc/interniew/createthreads/RunnableStyle.java
package com.imooc.interniew.createthreads;
import java.util.concurrent.Callable;
/**
* 描述: 用Runnable方式创建线程
*/
public class RunnableStyle implements Runnable {
//new里new的意思是 把这个Runnable类作为参数传进Thread里面
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方法实现线程");
}
}
package com.imooc.interniew.createthreads;
import java.util.Timer;
import java.util.TimerTask;
/**
* 描述: 利用定时器新建线程
*/
public class TimerDemo {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1000, 1000);
}
}
两种方式的对比
方法1(实现Runnable接口更好)
实现多线程——常见面试问题
实现Runnable接口和继承Thread类哪种方式更好?
本意是想让我们的执行类和任务的具体内容解耦,关系不那么密切,从架构角度好
★ Runnable具体描述的是工作的内容和线程的启动没有什么关系
★ Thread是维护整个线程的: 线程的启动、线程状态更改、线程结束,这两个本身的任务很分明的,不应该过度耦合[未来会发生很难扩展的问题]★ Runnable 在线程池更高级的用法中,一定不是每个任务都去新建一个线程的,为了提高整体的效率会让有限数量的线程由我们自己来确定,10个线程可以运行成千上万个任务。减少了新建线程的损耗。
可以把任务作为一个参数直接传递给线程池,线程池里面用固定的线程去执行任务不需要每次都新建和销毁线程,这样大大降低了线程的开销。★ Runnable 如果用这个不得不去把线程损耗承担起来,有的时候run方法执行的比较少,开销的少比不上新建线程的开销[捡了芝麻丢了西瓜]。
public class ThreadStyle extends Thread,Date (×)
Class cannot extend multiple classes从语法的角度不允许继承多个类,一旦选定了一个父类就无法更改了[一辈子就被定死了]。在创建线程起就限制了代码的可扩展性,如果实现Runnable接口就不会出现这个问题,实现接口并不仅仅只能实现一个,实现接口后还可以继承类
public class RunnableStyle extends Thread implements Runnable,Callable
两种方法的本质对比
方法一:最终调用target.run();
此方法本质是传入类后调用!
Ctrl+F12可以精确查找方法
@Override
public void run(){
if(target != null){
target.run()
}
}
而target是什么呢?实际上就是我们写的
@Override
public void run() {
System.out.println(“用Runnable方法实现线程”);
}
方法二:run()整个都被重写
整个重写代码
若同时使用这两种方法会发生什么?
com/imooc/interniew/createthreads/BothRunnableThread.java
package com.imooc.interniew.createthreads;
/**
* 描述: 同时使用RUNNABLE和Thread两种方式实现线程
*/
public class BothRunnableThread {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("实现Runnable接口的方式");
}
}) {
@Override
public void run() {
System.out.println("我来自Thread");
}
};
t1.start();
}
}
=========================================================
我来自Thread
---------------------------------------------------------
因为 run重写会被覆盖!!子类覆盖父类时 实行子类方法
@Override
public void run(){
if(target != null){
target.run()
}
}
其他观点分析
线程池创建线程也算是一种新建线程的方式 [把那两种方式进行包装]
通过Callable创建线程,也算是一种新建线程的方式
定时器[方法二:extends Thread]
package com.imooc.interniew.createthreads; import java.util.Timer; import java.util.TimerTask; /** * 描述: 利用定时器新建线程 */ public class TimerDemo { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); Timer timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }, 1000, 1000); } } ======================================================= main //两个不一致证明新建了一个线程 Timer-0 Timer-0 ......
匿名内部类
Lambda表达式
实现多线程——常见面试问题
- 有多少种实现线程的方法?5点思路
- 从不同的角度看,会有不同的答案
- 经典答案是两种
- 我们看原理,两种本质都是一样的
- 具体展开说其他方式
总结:最精准的描述
准确地讲:创建线程只有一种方法那就是构造Thread类,而实现线程的执行单元有两种方式
★ 方法一:实现Runnable接口的run方法,并把Runnable实例传給Thread类
★ 方法二:重写Thread的run方法(继承Thread类)
多线程的实现方法,在代码种写法千变万化,但其本质万变不离其宗
线程的生命周期是什么?
线程有几种状态?
- 有哪6种状态
- 每个状态是什么含义?
- 状态间的转化?
- 阻塞状态是什么?
每个状态是什么含义?
- New
- Runnable [从new到调用start方法]
- Blocked [线程状态由sychronized修饰]
- Waiting
- Timed Waiting
- Terminated

状态转换的注意点和阻塞
com/imooc/interniew/NewRunnableTerminated.java
package com.imooc.interniew;
/**
* 描述: 演示New、Runnable、Terminated状态。
*/
public class NewRunnableTerminated {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread();
//打印出NEW的状态 线程被创建但没有启动会打出new状态
System.out.println(thread.getState());
thread.start();
//打印出Runnable状态 线程被启动后打印runnable状态
System.out.println(thread.getState());
Thread.sleep(100);
//打印出TERMINATED状态 打印terminate状态
System.out.println(thread.getState());
}
}
=================================================================================
NEW
RUNNABLE
TERMINATED
Process finished with exit code 0
com/imooc/interniew/BlockedWaitingTimedWaiting.java
package com.imooc.interniew;
/**
* 描述: 展示Blocked、Waiting、Timed_Waiting状态
*/
public class BlockedWaitingTimedWaiting implements Runnable {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new BlockedWaitingTimedWaiting();
Thread t1 = new Thread(runnable);
t1.start();
Thread t2 = new Thread(runnable);
t2.start();
Thread.sleep(10);
//打印Timed_Waiting状态,因为正在执行Thread.sleep(1000);
System.out.println(t1.getState());
//打印出BLOCKED状态,因为t2拿不到synchronized锁[线程1还在休眠]
System.out.println(t2.getState());
Thread.sleep(1300);
//打印出WAITING状态,以为执行了wait()
System.out.println(t1.getState());
}
@Override
public void run() {
syn();
}
private synchronized void syn() { //锁!!
try {
Thread.sleep(1000);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
=================================================================================
TIMED_WAITING
BLOCKED
WAITING
阻塞状态
- 一般习惯而言,把Blocked(被阻塞)、Waiting(等待)、Timed_waiting(计时等待)都称为阻塞状态
- 不仅仅是Blocked
分布式面试题
什么是分布式
- 饭店厨师的例子
- 一个厨师
- 多个厨师
- 术业有专攻:配菜师、洗菜工
- 实际项目的演进过程
- 一个项目,大而全
- 多台机器,部署同样的应用
- 分布式:权限系统、员工系统、请假系统
分布式和单体结构哪个更好?[脱离业务场景和发展阶段的空谈就是耍流氓]
传统单体架构 | 分布式架构 | |
---|---|---|
新人的学习成本 | 业务逻辑成本高 | 架构逻辑成本高 |
部署、运维 | 容易 | 发布频繁、发布顺序复杂、运维难 |
隔离性 | 一损俱损,殃及鱼池 | 故障影响范围小 |
CAP理论是什么?[只选其二 三者不可兼得]
- C(Consistency, 一致性):读操作是否总能读到前一个写操作的结果
- A(Availability, 可用性):非故障节点应该在合理的时间内做出合理的响应
- P(Partition tolerance, 分区容错性):当出现网络分区现象后,系统能够继续运行
CAP怎么选?
- CP或者AP
- 在什么场合,可用性高于一致性?
为什么需要Docker?
- Docker:用来装程序以及环境的容器
- 环境配置的难题
- 虚拟机
Docker的用途是什么?
- 提供统一的环境
- 提供快速拓展、弹性伸缩的云服务
- 防止其他用户的进程把服务器资源占用过多
Docker的架构是什么样的?
Docker的网络模式有哪些?
- Bridge [桥接 用外面主机的端口号映射到里面的端口号 实现了一座桥]
- Host [里面的容器不会获得独立的网络配置 不会使用虚拟网卡ip 而是使用宿主机上的ip和端口号]
- None [不需要网络模式]
Nginx的适用场景有哪些?
HTTP的反向代理服务器
动态静态资源分离
- 不分离会变慢
- 静态资源无需经过Tomcat,Tomcat只负责处理动态请求
- 后缀为gif的时候,Nginx会直接获取到当前请求的文件并返回
- 静态资源服务器
Nginx常用命令有哪些?
/usr/sbin/nginx 启动
-h 帮助
-c 读取指定的配置文件
-t 测试
-v 版本
-s信号
stop 立即停止(不再接收任何请求立刻停止)
quit 优雅停止(不接收了但目前的请求要作完)
reload 重启
reopen 更换日志文件
Zookeeper有哪些节点类型?
- 持久节点
- 临时节点
- 顺序节点
为什么要用消息队列?什么场景用?
- 系统解耦
- 异步调用
- 流量削峰
消息队列RabbitMQ核心概念?
同一个RabbitMQ的Server下建立不同的虚拟主机,他们之间是相互独立的,用于不同的业务线。
交换机工作模式有哪4种?
fanout:广播,这种模式只需要将队列绑定到交换机上即可,是不需要设置路由键的
direct:根据RoutingKey匹配消息路由到指定队列 [消费者接收消息不一致]
topic:比如消息严重性怎么样、只想记录error模块的用户信息
***** 可以替代一个单词
# 可以替代零个或多个单词
- headers:根据发送消息内容中的headers属性来匹配
微服务面试题
微服务有哪两大门派?
- Spring Cloud:众多子项目
- dubbo:高性能、轻量级的开源RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现
- dubbo提供的能力只是SpringCloud的一部分子集
Spring Cloud核心组件有哪些?
核心组件 | Spring Cloud |
---|---|
服务注册中心 | Spring Cloud Netflix Eureka |
服务调用 | Spring Cloud Netflix Feign |
服务网关 | Spring Cloud Netflix Zuul |
断路器 | Spring Cloud Netflix Hystrix |
画一下Eureka架构
- EureKa Server 和 EureKa Client
- 集群 [只要能获得一个Eureka Server 就能获得整个信息]
负载均衡的两种类型是什么?
- 客户端负载均衡(Ribbon)
- 服务端负载均衡(Nginx)
负载均衡有哪些策略?
- RandomRule表示随机策略
- RoundRobinRule表示轮询策略
- ResponseTimeWeightedRule加权,根据每一个Server的平均响应时间动态加权
为什么需要断路器?
防止线程突然卡住,当发现某个模块不可用时,把它摘除不影响主要流程。
为什么需要网关?
- 签名校验、登录校验冗余问题
- 统一对外,安全 [对恶意IP进行拦截 打出日志]
Dubbo的工作流程是什么?
彩蛋:学习编程知识的优质路径
- 并不是靠工作年限,有的人工作5年技术却还是只懂皮毛
- 要有强大的责任心,不放过任何bug,找到原因并去解决,这就是提高
- 主动:永远不会觉得自己的时间多余,重构、优化、学习、总结等
- 敢于承担:虽然这个技术难题以前没有碰到过,但是在一定的了解调研后,敢于承担技术难题,让工作充满挑战,这一次次攻克难关的过程种,进步是飞速的
- 关心产品,关心业务,而不是只写代码
- 系统化的学习 看经典的书籍
- 看官方文档
- 自己动手写代码,尝试应用到项目中
- 不理解的内容参考多个知识来源,综合判断
- 学习开源项目,总结代码
Synchronized和Lock
Lock简介、地址、作用
- 锁是一种工具,用于控制对共享资源的访问
- Lock和Synchronized,这两个是最常见的锁,它们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同
- Lock并不是用来替代Synchronized的,而是当使用Synchronized不合适或不满足要求的时候,来提供高级功能的
- Lock接口最常见的实现类是ReentrantLock
- lock()、tryLock()、tryLock(long time, TimeUnit unit) 和locakInterruptibly()
lock()
- lock()就是最普通的获取锁。如果锁已经被其他线程获取,则进行等待
- Lock不会像Synchronized一样在异常时自动释放锁
- 因此最佳实践是,在finally中释放锁,以保证发生异常时锁一定被释放
- lock()方法不能被中断,这就会带来很大隐患:一旦陷入死锁,lock()就会陷入永久等待
tryLock()
- tryLock()用来尝试获取锁,如果当前锁没有被其他线程占用,则获取成功,则返回true,否则返回false,代表获取锁失败
- 相对比lock,这样的方法显然功能更加强大了,我们可以根据是否能获取到锁来决定后续程序的行为
- 该方法会立即返回,即便在拿不到锁时不会一直在那等
tryLock(long time, TimeUnit unit):超时就放弃
locakInterruptibly():相当于tryLock(long time, TimeUnit unit)把超时时间设置为无限。在等待锁的过程中,线程可以被中断
unlock():解锁 [最应该写在try…finally里面]
Synchronized和Lock有什么不同?
相同点:
- 保障资源线程的安全:目的和作用都是为了 保障资源线程的安全
[使用Synchronized后被保护的代码块最多只有一个线程可以访问] - 可重入 [不然就必须在获得第二个锁前释放]
com/imooc/interniew/Reentrant.java
package com.imooc.interniew;
/**
* 描述: synchronized可重入
*/
public class Reentrant {
public synchronized void f1() {
System.out.println("f1方法被运行了");
f2();
}
public synchronized void f2() {
System.out.println("f2方法被运行了");
}
public static void main(String[] args) {
Reentrant reentrant = new Reentrant();
reentrant.f1();
}
}
========================================================
f1方法被运行了
f2方法被运行了
- ReentrantLock [实现了Lock接口]
不同点:
用法
- Synchronized用在方法上、用在同步代码块上 [隐式]
- Lock必须使用lock方法加锁,unlock方法解锁 [显式]
加解锁顺序不同
- Synchronized是java内部控制,自动加解锁
- Lock可以手动调节
Synchronized锁不够灵活
- Synchronized获得了一个锁 其他的只能等待
- Lock获得锁很灵活 可以随时调整
性能区别
- Synchronized由差到好 目前同等程度的性能
你知道有几种锁?
共享锁[读写锁] 和 独占锁[排他锁]
共享锁,又称为读锁,获得共享锁之后,可以查看但无法修改和删除数据,其他线程此时也可以获取到共享锁,也可以查看但无法修改和删除数据
共享锁和排他锁的典型是读写锁ReentrantReadWriteLock,其中读锁是共享锁,写锁是独享锁
读写锁的作用
- 在没有读写锁之前,我们假设使用ReentrantLock,那么虽然我们保证了线程安全,但是也浪费了一定的资源:多个读操作同时进行,并没有线程安全问题
- 在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,提高了程序的执行效率
读写锁的规则
- 多个线程只申请读锁,都可以申请到
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁
- 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁
- 一句话总结:要么是一个或多个线程同时有读锁,要么一个线程有写锁,但是两者不会同时出现(要么多读,要麽一写)
公平锁 和 非公平锁
- 公平指的是按照线程请求的顺序,来分配锁
- 非公平指的是不完全按照请求的顺序,在一定情况下,可以插队
- 注意:非公平也同样不提倡 ”插队“ 行为,这里的非公平,指的是”在合适的时机”插队,而不是盲目插队
- 什么是合适的时机呢?
- 买火车票被插队的例子,排队买的例子
- 实际情况并不是这样的,java设计者这样设计的目的是为了提高效率
- 避免唤醒带来的空档期,提升吞吐量
优势 | 劣势 | |
---|---|---|
公平锁 | 各线程公平平等,每个线程在等待一段时间后,总有执行的机会 | 更慢,吞吐量更小 |
不公平锁 | 更快,吞吐量更大 | 有可能产生线程饥饿,也就是某些线程在长时间内,始终得不到执行 |
悲观锁 和 乐观锁
- 从是否锁住资源的角度分类
悲观锁
- 如果我不锁住这个资源,别人就会来争抢,就会造成数据结果错误,所以每次悲观锁为了确保结果的正确性,会在每次获取并修改数据时,把数据锁住,让别人无法访问该数据,这样就可以确保数据内容万无一失
- java中悲观锁的实现就是
synchronized
和Lock
相关类
乐观锁
- 认为自己在处理操作的时候不会有其他线程来干扰,所以并不会锁住被操作对象
- 在更新的时候,去对比在我修改的期间数据有没有被其他人改变过,如果没被改变过,就说明真的是只有我自己在操作,那我就正常去修改数据
- 如果数据和我一开始拿到的不一样了,说明其他人在这段时间内改过数据,那我就不能继续刚才的更新数据过程了,我会选择放弃、报错、重试等策略
- 乐观锁的实现一般都是利用CAS算法来实现的
在数据库中
- select for update就是悲观锁
- 用version控制数据库就是乐观锁
经典例子
添加一个字段lock_version
先查询这个更新语句的vesion:SELECT * FROM table
然后
UPDATE SET num = 2,
version = version + 1 WHERE version = 1 AND id = 5
如果version被更新了等于2,不一样就会更新出错,这就是乐观锁的原理
自旋锁 和 非自旋锁
- 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间
- 如果同步代码块的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长
- 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复线程的花费可能会让系统得不偿失
- 如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁
- 而为了让当前线程“稍等一下”,我们需要让当前线程进行自旋,如果在自旋完成后前面锁定同步资源资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销,这就是自旋锁。
自旋锁的缺点
- 如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源
- 在自旋的过程中,一直消耗CPU,所以虽然自旋锁的起始开销低于悲观锁,但是随着自旋时间的增长,开销也是线性增长的
可重入锁 和 非可重入锁
什么是可重入 [摇一个号拿N个牌]
好处 [避免死锁]
可中断锁 和 不可中断锁
- 可中断锁 [可以随时中断]
死锁相关
写一个必然死锁的例子?
什么是死锁?
发生在并发中
互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁
一图胜千言
线程A持有锁1但试图获取锁2 线程B持有锁2但视图获取锁1
多个线程造成死锁的情况
- 如果多个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发生死锁
死锁的影响
- 死锁的影响在不同系统中是不一样的,这取决于系统对死锁的处理能力
- 数据库中:检测并放弃事务
- JVM中:无法自动处理
几率不高但危害大
- 不一定发生,但是遵守墨菲定律
- 一旦发生,多是高并发场景,影响用户多
- 整个系统崩溃、子系统崩溃、性能降低
- 压力测试无法找出所有潜在的死锁
deadlock/DeadLock.java
package deadlock;
/**
* 描述: 必然发生死锁
*/
public class DeadLock implements Runnable {
public int flag;
static Object o1 = new Object();
static Object o2 = new Object();
public void run() {
System.out.println("开始执行");
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("成功获取到了两把锁");
}
}
}
if (flag == 2) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("成功获取到了两把锁");
}
}
}
}
public static void main(String[] args) {
DeadLock r1 = new DeadLock();
DeadLock r2 = new DeadLock();
r1.flag = 1;
r2.flag = 2;
new Thread(r1).start();
new Thread(r2).start();
}
}
====================================== 分析 =======================================
★ 当类的对象flag=1时(T1),先锁定O1,睡眠500毫秒,然后锁定O2;
★ 而T1在睡眠的时候另一个flag=2的对象(T2)线程启动,先锁定O2,睡眠500毫秒,等待T1释放O1;
★ T1睡眠结束后需要锁定O2才能继续执行,而此时O2已被T2锁定
★ T2睡眠结束后需要锁定O1才能继续执行,而此时O1已被T1锁定
★ T1、T2相互等待,都需要对方锁定的资源才能继续执行,从而死锁
哲学家就餐问题?
- 先拿起左手的筷子
- 然后拿起右手的筷子
- 如果筷子被人使用了,那就等别人用完
while(true){ //伪代码
think();
pick_up_left_fork();
pick_up_right_fork();
eat();
put_down_right_fork();
put_down_left_fork();
}
package deadlock;
/**
* 描述: 哲学家就餐问题导致的死锁
*/
public class DiningPhilosophers {
public static class Philosopher implements Runnable {
private Object leftChopstick;
public Philosopher(Object leftChopstick, Object rightChopstick) {
this.leftChopstick = leftChopstick;
this.rightChopstick = rightChopstick;
}
private Object rightChopstick;
@Override
public void run() {
try {
while (true) {
doAction("Thinking");
synchronized (leftChopstick) {
doAction("Picked up left chopstick");
synchronized (rightChopstick) {
doAction("Picked up right chopstick - eating");
doAction("Put down right chopstick");
}
doAction("Put down left chopstick");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doAction(String action) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " " + action);
Thread.sleep((long) (Math.random() * 10));
}
}
public static void main(String[] args) {//五个哲学家方便管理
Philosopher[] philosophers = new Philosopher[5];
Object[] chopsticks = new Object[philosophers.length];
for (int i = 0; i < chopsticks.length; i++) {
chopsticks[i] = new Object();
}
for (int i = 0; i < philosophers.length; i++) {
Object leftChopstick = chopsticks[i]; //从0开始 i为5 所以底下要加1 但是越界就取余
Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];
//领导调节(检测与恢复策略) [定期巡视命令哲学家] 让最后一个 跟别人不一样
//别人都是先左后右面 它是先后面再左边 避免了环路的形成
//直接避免死锁发生!!!!!
if (i == philosophers.length - 1) {
philosophers[i] = new Philosopher(rightChopstick, leftChopstick);
} else {
philosophers[i] = new Philosopher(leftChopstick, rightChopstick);
}
new Thread(philosophers[i], "哲学家" + (i + 1) + "号").start();
}
}
}
=============================================================================
哲学家4号 Thinking
哲学家5号 Thinking
哲学家3号 Thinking
哲学家1号 Thinking
哲学家2号 Thinking
哲学家2号 Picked up left chopstick
哲学家3号 Picked up left chopstick
哲学家1号 Picked up left chopstick
哲学家4号 Picked up left chopstick
哲学家4号 Picked up right chopstick - eating
哲学家4号 Put down right chopstick
哲学家4号 Put down left chopstick
哲学家4号 Thinking
哲学家3号 Picked up right chopstick - eating
哲学家3号 Put down right chopstick
哲学家3号 Put down left chopstick
哲学家4号 Picked up left chopstick
哲学家4号 Picked up right chopstick - eating
哲学家4号 Put down right chopstick
哲学家4号 Put down left chopstick
哲学家3号 Thinking
哲学家2号 Picked up right chopstick - eating
哲学家4号 Thinking
哲学家2号 Put down right chopstick
哲学家4号 Picked up left chopstick
哲学家4号 Picked up right chopstick - eating
.............................
发生死锁的时候哲学家都拿着左边的筷子
原理:Thread.sleep((long) (Math.random() * 10));
random到了一个更大的数
多种解决策略
- 服务员检查(避免策略) [提前看一看是否发生死锁]
- 改变一个哲学家拿叉子的顺序(避免策略)
- 餐票(避免策略)
- 领导调节(检测与恢复策略) [定期巡视命令哲学家]
实际工程中如何避免死锁
① 设置超时时间
- Lock的tryLock(long timeout, TImeUnit unit)
- synchronized不具备尝试锁的能力
- 造成超时的可能性很多:发生了死锁、线程陷入死循环、线程执行很慢
- 获取锁失败:打日志、发报警邮件、重启等
- 代码演示:退一步海阔天空
package deadlock;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 用tryLock来避免死锁
*/
public class TryLockDeadlock implements Runnable {
int flag = 1;
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
TryLockDeadlock r1 = new TryLockDeadlock();
TryLockDeadlock r2 = new TryLockDeadlock();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (flag == 1) {
try {
if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
System.out.println("线程1获取到了锁1");
Thread.sleep(new Random().nextInt(1000));
if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
System.out.println("线程1获取到了锁2");
System.out.println("线程1成功获取到了两把锁");
lock2.unlock();
lock1.unlock();
break;
} else {
System.out.println("线程1尝试获取锁2失败,已重试");
lock1.unlock();
Thread.sleep(new Random().nextInt(1000));
}
} else {
System.out.println("线程1获取锁1失败,已重试");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (flag == 0) {
try {
if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)) {
System.out.println("线程2获取到了锁2");
Thread.sleep(new Random().nextInt(1000));
if (lock1.tryLock(3000, TimeUnit.MILLISECONDS)) {
System.out.println("线程2获取到了锁1");
System.out.println("线程2成功获取到了两把锁");
lock1.unlock();
lock2.unlock();
break;
} else {
System.out.println("线程2尝试获取锁1失败,已重试");
lock2.unlock();
Thread.sleep(new Random().nextInt(1000));
}
} else {
System.out.println("线程2获取锁2失败,已重试");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
====================================================================================
线程1获取到了锁1
线程2获取到了锁2
线程1尝试获取锁2失败,已重试 【正是有了线程1的释放 才让线程2拿到了锁1】
线程2获取到了锁1
线程2成功获取到了两把锁
线程1获取到了锁1
线程1获取到了锁2
线程1成功获取到了两把锁
② 多使用并发类而不是自己设置锁
③ 尽量降低锁的使用粒度:用不同的锁而不是一个锁
④ 如果能使用同步代码块,就不使用同步方法:自己锁定锁对象
⑤ 给你的线程起个有意义的名字:debug和排查时事半功倍,框架和JDK都遵守这个最佳实践
Hashmap为什么[线程]不安全?
i++
- 第一个步骤是读取
- 第二个步骤是增加
- 第三个步骤是保存
有可能会发生线程不安全的情况
同时put碰撞导致数据丢失
可见性问题无法保证
final的作用是什么?有哪些用法?
- final修饰变量
- final修饰方法
- final修饰类
final的作用
- 早期
- 锁定
- final效率:早期的Java实现版本中,会将final方法转为内嵌调用
- 现在
- 类防止被继承、方法防止被重写、变量防止被修改
- 天生是线程安全的,而不需要额外的同步开销
final的3种用法
final修饰变量:赋值时机
属性被声明为final后,该变量则只能被赋值一次。且一旦被赋值,final的变量就不能再被改变,无论如何也不会改变
final修饰变量
final instance variable (类中的final属性)
- 第一种是在声明变量的等号右边直接赋值
package com.imooc.interniew; /** * 描述: final修饰变量 */ public class FinalVariable { public static int a = 5; public static void main(String[] args) { FinalVariable.a = 8; } }
- 第二种就是构造函数中赋值
package com.imooc.interniew; /** * 描述: final修饰变量 */ public class FinalVariable { public final int a; public testFinal(int a) { this.a = b; } public static void main(String[] args) { } }
- 第三种就是在类的初始代码块种赋值(不常用)
package com.imooc.interniew; /** * 描述: final修饰变量 */ public class FinalVariable { public static final int a; static { a = 9; } void testFinal() { final int b; } public static void main(String[] args) { } }
- 如果不采用第一种赋值方法,那么就必须在第2、3种挑一个来赋值,而不能不赋值,这是final语法所 规定的
final static variable (类中的static final属性)
- 两种赋值时机:除了在声明变量的等号右边直接赋值外,static final变量还可以用static初始代码块赋值,但是不能用普通的初始代码块赋值
final local variable (方法中的final变量)
- 和前两种不同,由于这里的变量是在方法里的,所以没有构造函数,也不存在初始代码块
- final local variable不规则赋值时机,只要求在使用前必须赋值,这和方法中的非final变量的要求也是一样的
为什么要规定赋值时机?
- 如果初始化不赋值,后续赋值,就是从null变成你的赋值,这就违反final不变的规则了
final修饰方法
- 构造方法不允许final修饰
- 不可被重写,也就是不能被override
package com.imooc.interniew; /** * 描述: final修饰方法 */ public class FinalMethodDemo{ public void drink() { } public final void eat() { } } class SubClass extends FinalMethodDemo { @Override public void drink() { super.drink(); } // @Override // public void eat() { // super.drink(); // } }
final修饰类
- 不可被继承
- 例如经典的String类就是final的,我们从见过哪个类是继承String类的
单例模式你会写吗?
保证只有一个实例且提供只有一个全局入口
为什么需要单例:节省内存和计算、保证结果正确、方便管理
适用场景
- 无状态的工具类
- 全局信息类
单例模式的8种写法
- 饿汉式(静态常量) [可用]
package com.imooc.interniew.singleton; /** * 描述: 饿汉式(静态常量)(可用) */ public class Singleton1 { private Singleton1() { } private final static Singleton1 INSTANCE = new Singleton1(); // [未达到懒加载] 直接创建出来了 public static Singleton1 getInstance() { return INSTANCE; } }
- 饿汉式(静态代码块) [可用]
package com.imooc.interniew.singleton; /** * 描述: 饿汉式(静态代码块)(可用) // [未达到懒加载] */ public class Singleton2 { private Singleton2() { } static { INSTANCE = new Singleton2(); } private final static Singleton2 INSTANCE; public static Singleton2 getInstance() { return INSTANCE; } }
- 懒汉式(线程不安全) [不可用]
package com.imooc.interniew.singleton; /** * 描述: 懒汉式(线程不安全) */ public class Singleton3 { private Singleton3() { } private static Singleton3 INSTANCE; public static Singleton3 getInstance() { if (INSTANCE == null) {//第一次访问方法 INSTANCE = new Singleton3();//初始化 } //此时如果两个线程同时访问,都是null,就创造了两个初始化 违反单例模式 return INSTANCE; //已经被初始化 就返回 } }
- 懒汉式(线程安全,同步方法) [不推荐用]
package com.imooc.interniew.singleton; /** * 描述: 懒汉式(线程安全,同步方法)(不推荐) */ public class Singleton4 { private Singleton4() { } private static Singleton4 INSTANCE; //synchronized同步关键字 最多一个线程访问 //不推荐用的原因是因为一旦适用了synchronized同步关键字 线程就要排队 并发量大 public synchronized static Singleton4 getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton4(); } return INSTANCE; } }
//方法上不进行同步了 package com.imooc.interniew.singleton; /** * 描述: 懒汉式(线程安全,同步方法)(不推荐) */ public class Singleton5 { private Singleton5() { } private static Singleton5 INSTANCE; public static Singleton5 getInstance() { if (INSTANCE == null) { //此时不会存在两个线程同时出来了 synchronized (Singleton5.class) { //假如第一个执行完了 第二个进去执行 那么结果还是生成了两个 不符合单例 INSTANCE = new Singleton5(); } } return INSTANCE; } }
- 双重检查[推荐用]
- 新建一个对象,但还未初始化
- 调用构造函数等来初始化该对象
- 把对象指向引用
package com.imooc.interniew.singleton; /** * 描述: 懒汉式(线程安全,同步方法)(不推荐) */ public class Singleton6 { private Singleton6() {//2 } private static volatile Singleton6 INSTANCE; public static Singleton6 getInstance() { if (INSTANCE == null) { synchronized (Singleton6.class) { if (INSTANCE == null) { //3 //就不会出现多个结果了 INSTANCE = new Singleton6 //1 } } } return INSTANCE; } }
- 静态内部类[推荐用]
package com.imooc.interniew.singleton; /** * 描述: 静态内部类写法(推荐用) */ public class Singleton7 { private Singleton7() { } private static class SingletonInstance { private static Singleton7 INSTANCE = new Singleton7(); } public static Singleton7 getInstance() { return SingletonInstance.INSTANCE; } }
- 枚举[推荐用]
package com.imooc.interniew.singleton; /** * 描述: 枚举单例模式 */ public enum Singleton8 { //1.写法简洁 //2.线程安全 //3.防止反射 INSTANCE; }
不同写法对比
- 饿汉:简单,但是没有lazy loading
- 懒汉:有线程安全问题
- 静态内部类:可用
- 双重检查:面试用
- 枚举:最好
单例模式面试常见问题
- 饿汉式的缺点?[没有懒加载]
- 懒汉式的缺点?[不可以保证线程安全]
- 为什么要用double-check?不用就不安全吗?
- 为什么双重检查模式要用volatile?
- 应该如何选择,用哪种单例的实现方案最好?
- 单元素的枚举类型已经成为实现Singleton的最佳方法
- 写法简单
- 线程安全有保障
- 避免反射破坏单例
面试避坑指南
- 何时投简历 [Offer数量只会越来越少,越早越好] 秋招7-9月 社招金3银4
- 信息尽量全面
- 技术栈契合
- 慎用”精通“ [对源码有很多熟悉] => 多写熟悉
- 面试无处不在 [如果没时间可以申请换一个时间节点 并询问对方是否有时间]
- 提前调试设备
- 仪容仪表、提前到场 [提前5分钟左右联系面试官]
- 确认问题 [实在不会可以说思路设想]
- 问面试官的问题 [提前查公司信息 我了解到我们公司… 可不可以介绍一下… 未来规划… 技术栈…]
哪些软素质值得面试官认可?
- 基本能力:聆听、沟通表达、学习能力
- 工作能力:协作、执行力、管理能力
- 个人素质:技术自驱力、韧性、积极开放的心态
面试课总结
- Spring Boot常见面试题
- 线程常见面试题
- 分布式的面试题
- Docker相关面试题
- Nginx和Zookeeper相关面试题
- RabbitMQ相关面试题
- 微服务相关
- 彩蛋:学习方法
- 锁分类、死锁
- HashMap和final
- 单例模式[高频考点]
- 面试避坑指南
- 重要的软实力