etcd简介

Etcd是CoreOS团队于2013年6月发起的开源项目,他的目标是构建一个高可用的分布式键值(key-value)数据库,etcd内部采用raft协议作为一致性算法,etcd基于Go语言实现.

特点
// 简单:  安装配置简单,而且提供了HTTP API进行交互,使用也很简单.
// 安全: 支持SSL证书验证
// 快速: 根据官方提供的benchmark数据,单实例支持每秒2k+读操作
// 可靠: 采用raft算法,实现分布式系统数据的可用性和一致性.
Etcd vs zk

本文选取ZK作为典型代表与ETCD进行比较,而不考虑[Consul]项目作为比较对象,原因为Consul的可靠性和稳定性还需要时间来验证(项目发起方自身服务并未使用Consul, 自己都不用...

# 一致性协议: ETCD使用[Raft]协议, ZK使用ZAB(类PAXOS协议),前者容易理解,方便工程实现;
# 运维方面:ETCD方便运维,ZK难以运维;
# 项目活跃度:ETCD社区与开发活跃,ZK已经快死了;
# API:ETCD提供HTTP+JSON, gRPC接口,跨平台跨语言,ZK需要使用其客户端;
# 访问安全方面:ETCD支持HTTPS访问,ZK在这方面缺失;
读写性能

按照官网给出的[Benchmark], 在2CPU,1.8G内存,SSD磁盘这样的配置下,单节点的写性能可以达到16K QPS, 而先写后读也能达到12K QPS。这个性能还是相当可观的。

概念术语

// Raft:  etcd所采用的保证分布式系统强一致性的算法
// Node: 一个Raft状态机实例
// Member: 一个etcd实例,他管理着一个Node,并且可以为客户端请求提供服务
// Cluster: 由多个Member构成可以协同工作的etcd集群
// Peer: 对同一etcd集群另一个Member的称呼
// Client: 向etcd集群发送http请求的客户端
// WAL: 预写式日志,etcd用于持久化存储的日志格式
// snapshot: etcd防止WAL文件过多而设置的快照,存储etcd数据状态
// Proxy: etcd的一种模式,为etcd集群提供反向代理服务.
// Leader: Raft算法中通过竞选而产生的处理所有数据提交的节点.
// Follower: 竞选失败的节点作为Raft中的从属节点,为算法提供强一致性保证.
// Candidate: 当Follower超过一定时间接受不到Leader的心跳时转变为Candidate开始竞选.
// Term: 某个节点成为Leader到下一次竞选时间,称为一个Term.
// Index: 数据项编号,Raft中通过Term和Index来定位数据
数据读写顺序

为了保证数据的强一致性,etcd集群中所有数据流向都是同一个方向,从Leader (主节点) 流向Follower,也就是所有Follower的数据必须与Leader保证一致,如果不一致会被覆盖.

用户对于etcd集群所有节点进行读写

// 读取:  由于集群所有节点数据是强一致性的,读取可以从集群中随便哪个节点进行读取数据.

// 写入: etcd集群有leader,如果写入往leader写入,可以直接写入,然后Leader节点会把写入分发给所有Follower,如果往Follower写入,然后Leader节点会把写入分发给所有Follower.
leader选举

假设三个节点的集群,三个节点上均运行Timer(每个Timer持续时间是随机的),Raft算法使用随机Timer来初始化Leader选举流程,第一个节点率先完成了Timer,随后他就会向其他两个节点发送成为Leader的请求,其他节点接收到后会以投票回应然后第一个节点被选举为Leader.

成为Leader后,该节点会以固定时间间隔向其他节点发送通知,确保自己仍然是Leader,有些情况下当Follower收不到Leader的通知后,比如Leader节点宕机或者失去了连接,其他节点会重复之前选举过程选举出新的Leader.

判断数据是否写入

etcd认为写入请求被Leader节点处理并分给了多数节点后,就是一个成功的写入,那么多少节点如何判定尼,假设总节点数就是N,那么多数节点Quorum=N/2+1, 关于如何确定etcd集群应该有多少个的问题,用Instances减去Quorom就是集群中容错节点(允许出故障节点)的数量.

所以在集群中推荐的最少节点数量是3个,因为1和2个节点的容错点数都是0,一旦有一个节点宕掉,整个集群就不能正常工作了.

etcd架构及解析

架构图

架构解析

从etcd的架构图中我们可以看到, etcd主要分为四个部分:

// HTTP Server: 用于处理用户发送的API请求以及其他的etcd节点的同步与心跳信息请求
// Store: 用于处理etcd支持的各类功能的事务,包含数据索引,节点状态变更,监控与反馈,事件处理与执行等等,是etcd对用户提供的大多数API功能的具体实现
// Raft: Raft强一致性算法的具体实现,是etcd的核心.
// WAL: Write Ahead Log(预写式日志),是etcd的数据存储方式,除了在内存中所有数据的状态以及节点的索引以外,etcd就通过WAL进行持久化存储,WAL中,所有的数据提交前都会事先记录日志.
// Shapshot是为了防止数据过多而进行的状态快照.
// Entry表示存储的具体日志内容

通常,一个用户的请求发送过来,会经由HTTP Server转发给Store进行具体的事务处理,如果涉及到节点的修改,则交给Raft模块进行状态的变更,日志的记录,然后再同步给别的etcd节点以确认数据提交,最后进行数据的提交,再次同步.

应用场景

服务发现/注册

前后端业务注册发现

中间件以及后端服务在etcd中注册,前端和中间件可以很轻松的从etcd中发现相关服务器然后服务器之间根据调用关系相互绑定调用

多组后端服务器注册发现

后端多个无状态相同副本app可以同时注册到etcd中,前端可以通过haproxy从etcd中获取到后端的ip和端口组,然后进行请求转发,可以用来故障转移屏蔽后端端口以及后端多组app实例.

消息发布和订阅

etcd可以充当消息中间件,生产者可以往etcd中注册topic并发送消息,消费者从etcd中订阅topci,来获取生产者发送至etcd中的消息.

负载均衡

后端多组相同的服务提供者可以经自己服务注册到etcd中,etcd并且会与注册的服务进行监控检查,服务请求这首先从etcd中获取到可用的服务提供者真正的ip:port, 然后对此多组服务发送请求,etcd在其中充当了负载均衡的功能.

分布式通知与协调

// 当etcd watch服务发现丢失,会通知服务检查
// 控制器向etcd发送启动服务,etcd通知服务进行相应操作
// 当服务完成work会将状态更新至etcd, etcd对应会通知用户
分布式锁,分布式队列

// 有多个node, etcd根据每个node来创建对应的node的队列,根据不同的队列可以在etcd中找到对应的competitor
集群监控与Leader竞选

etcd可以根据raft算法在多个node节点来选举出leader.

单机部署

yum -y install etcd
systemctl enable etcd grep -Ev "^#|^$" /etc/etcd/etcd.conf
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
ETCD_LISTEN_CLIENT_URLS="http://localhost:2379"
ETCD_NAME="default"
ETCD_ADVERTISE_CLIENT_URLS="http://localhost:2379"

集群部署

最好部署奇数位,能达到更好的集群容错

cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.43.233 etcd1
192.168.43.130 etcd2
192.168.43.246 etcd3
安装etcd
yum -y install etcd

mkdir -p /data/app
chown etcd:etcd /data/app -R
systemctl enable etcd
etcd配置

参数说明

# —data-dir 指定节点的数据存储目录,这些数据包括节点ID,集群ID,集群初始化配置,Snapshot文件,若未指定—wal-dir,还会存储WAL文件;
# —wal-dir 指定节点的was文件的存储目录,若指定了该参数,wal文件会和其他数据文件分开存储。
# —name 节点名称
# —initial-advertise-peer-urls 告知集群其他节点url.
# — listen-peer-urls 监听URL,用于与其他节点通讯
# — advertise-client-urls 告知客户端url, 也就是服务的url
# — initial-cluster-token 集群的ID
# — initial-cluster 集群中所有节点

etcd1

[root@etcd1 ~]# egrep "^#|^$" /etc/etcd/etcd.conf -v
ETCD_DATA_DIR="/data/app/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.43.233:2380"
ETCD_LISTEN_CLIENT_URLS="http://127.0.0.1:2379,http://192.168.43.233:2379"
ETCD_NAME="etcd1"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.43.233:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://127.0.0.1:2379,http://192.168.43.233:2379"
ETCD_INITIAL_CLUSTER="etcd1=http://192.168.43.233:2380,etcd2=http://192.168.43.130:2380,etcd3=http://192.168.43.246:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-token"
ETCD_INITIAL_CLUSTER_STATE="new"

etcd2

[root@etcd2 ~]#  egrep "^#|^$" /etc/etcd/etcd.conf -v
ETCD_DATA_DIR="/data/app/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.43.130:2380"
ETCD_LISTEN_CLIENT_URLS="http://127.0.0.1:2379,http://192.168.43.130:2379"
ETCD_NAME="etcd2"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.43.130:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://127.0.0.1:2379,http://192.168.43.130:2379"
ETCD_INITIAL_CLUSTER="etcd1=http://192.168.43.233:2380,etcd2=http://192.168.43.130:2380,etcd3=http://192.168.43.246:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-token"
ETCD_INITIAL_CLUSTER_STATE="new"

etcd3

[root@etcd-3 ~]#  egrep "^#|^$" /etc/etcd/etcd.conf -v
ETCD_DATA_DIR="/data/app/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.43.246:2380"
ETCD_LISTEN_CLIENT_URLS="http://127.0.0.1:2379,http://192.168.43.246:2379"
ETCD_NAME="etcd3"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.43.246:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://127.0.0.1:2379,http://192.168.43.246:2379"
ETCD_INITIAL_CLUSTER="etcd1=http://192.168.43.233:2380,etcd2=http://192.168.43.130:2380,etcd3=http://192.168.43.246:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-token"
ETCD_INITIAL_CLUSTER_STATE="new"
验证集群服务
[root@etcd-3 ~]# systemctl status etcd
● etcd.service - Etcd Server
Loaded: loaded (/usr/lib/systemd/system/etcd.service; enabled; vendor preset: disabled)
Active: active (running) since Thu 2020-08-27 00:45:52 CST; 2min 12s ago
Main PID: 1385 (etcd)
CGroup: /system.slice/etcd.service
└─1385 /usr/bin/etcd --name=etcd3 --data-dir=/data... Aug 27 00:45:52 etcd-3 etcd[1385]: ready to serve client requ...
Aug 27 00:45:52 etcd-3 etcd[1385]: serving insecure client re...
Aug 27 00:45:52 etcd-3 etcd[1385]: ready to serve client requ...
Aug 27 00:45:52 etcd-3 etcd[1385]: serving insecure client re...
Aug 27 00:45:52 etcd-3 systemd[1]: Started Etcd Server.
Aug 27 00:45:52 etcd-3 etcd[1385]: 2407f8cc1c4a982 initialzed...
Aug 27 00:45:52 etcd-3 etcd[1385]: established a TCP streamin...
Aug 27 00:45:52 etcd-3 etcd[1385]: established a TCP streamin...
Aug 27 00:45:52 etcd-3 etcd[1385]: established a TCP streamin...
Aug 27 00:45:53 etcd-3 etcd[1385]: established a TCP streamin...
Hint: Some lines were ellipsized, use -l to show in full. [root@etcd-3 ~]# ss -atnlp |grep etcd
LISTEN 0 128 192.168.43.246:2379 *:* users:(("etcd",pid=1385,fd=7))
LISTEN 0 128 127.0.0.1:2379 *:* users:(("etcd",pid=1385,fd=6))
LISTEN 0 128 192.168.43.246:2380 *:* users:(("etcd",pid=1385,fd=5)) [root@etcd-3 ~]# etcdctl cluster-health
member 2407f8cc1c4a982 is healthy: got healthy result from http://127.0.0.1:2379
member 7d37bdfae6fd099b is healthy: got healthy result from http://127.0.0.1:2379
member 94362b802cdd1767 is healthy: got healthy result from http://127.0.0.1:2379
cluster is healthy [root@etcd-3 ~]# etcdctl member list
2407f8cc1c4a982: name=etcd3 peerURLs=http://192.168.43.246:2380 clientURLs=http://127.0.0.1:2379,http://192.168.43.246:2379 isLeader=false
7d37bdfae6fd099b: name=etcd1 peerURLs=http://192.168.43.233:2380 clientURLs=http://127.0.0.1:2379,http://192.168.43.233:2379 isLeader=true
94362b802cdd1767: name=etcd2 peerURLs=http://192.168.43.130:2380 clientURLs=http://127.0.0.1:2379,http://192.168.43.130:2379 isLeader=false

动态发现启动etcd集群

https://developer.aliyun.com/article/765312

简单使用

增加

set

[root@etcd-3 ~]# etcdctl set /testdir/testkey "hello"
hello # 支持的选项包括
--ttl '0' 该键值的超时时间(单位为妙), 不配置(默认为0)则永不超时.
--swap-with-value value 若该键的值为value,则进行设置操作
--swap-with-index '0' 若该键现在值为索引,则进行设置操作

mk

# 如果给定的键不存在,则创建一个新的键值,例如:
[root@etcd-3 ~]# etcdctl mk /testdir/testkey2 "hello"
hello # 当键存在就会报错
[root@etcd-3 ~]# etcdctl mk /testdir/testkey "hello"
Error: 105: Key already exists (/testdir/testkey) [10] # 支持的选项为:
--ttl '0' # 超时时间(单位为秒),不配置(默认为0),则永不超时

mkdir

# 如果给定的键目录不存在,则创建一个新的键目录.
[root@etcd-3 ~]# etcdctl mkdir youmen [root@etcd-3 ~]# etcdctl mkdir youmen
Error: 105: Key already exists (/youmen) [12] # 支持的选项
--ttl '0' # 超时时间(单位为秒),不配置(默认为0)则永不超时

setdir

# 创建一个键目录,如果目录不存在就创建,如果目录存在更新目录TTL
[root@etcd-3 ~]# etcdctl setdir testdir3
[root@etcd-3 ~]# etcdctl setdir testdir3
Error: 102: Not a file (/testdir3) [13] # 支持的选项
--ttl '0' # 超时时间(单位为秒),不配置(默认为0)则永不超时
删除

rm

# 删除某个键值
[root@etcd-3 ~]# etcdctl rm /testdir/testkey hello
PrevNode.Value: hello # 支持的选项为:
# --dir 如果键是个空目录或键值对则删除
# --recursive 删除目录和所有子健
# --with-value 检查现有的值是否匹配
# --with-index '0' 检查现有的index是否匹配

rmdir

# 删除一个空目录,或者键值对
[root@etcd-3 ~]# etcdctl rmdir testdir3 [root@etcd-3 ~]# etcdctl rmdir testdir3
Error: 100: Key not found (/testdir3) [15]
更新

update

# 当键存在时,更新值内容
[root@etcd-3 ~]# etcdctl set /testdir/testkey "hello"
hello
[root@etcd-3 ~]# etcdctl update /testdir/testkey "hello2"
hello2

updatedir

# 更新一个已经存在的目录
etcdctl updatedir testdir2
查询

get

# 获取指定键的值
[root@etcd-3 ~]# etcdctl get /testdir/testkey
hello2 # 当键不存在时,则会报错,例如
[root@etcd-3 ~]# etcdctl get /testdir/testkeydsf
Error: 100: Key not found (/testdir/testkeydsf) [18] # 支持选项
# --sort 对结果进行排序
# --consistent 将请求发给主节点,保证获取内容的一致性

ls

# 列出目标(默认为根目录)下的键或者子目录,默认不显示子目录中内容:

[root@etcd-3 ~]# etcdctl mk /testdir2/testkey "hello"
hello
[root@etcd-3 ~]# etcdctl ls /testdir2/
/testdir2/testkey # 支持选项
# --sort 对结果进行排序
# --recursive 如果目录下有子目录,则递归输出其中的内容-p ,对于输出为目录,在最后添加/进行区分

watch

# 检测一个键值的变化,一旦键值发生更新,就会输出最新的值并退出.
# 例如用户更新testkey键值为hello watch
[root@etcd2 ~]# etcdctl get /testdir2/testkey
hello
[root@etcd2 ~]# etcdctl set /testdir2/testkey "hello watch"
hello watch [root@etcd-3 ~]# etcdctl watch testdir2/testkey
hello watch # --forever 一直检测到用户按CTRL+C退出
# --after-index '0' 在指定index之前一直监测
# --recursive 返回所有的键值和子键值

exec-watch

# 检测一个键值的变化,一旦键值发生更新,就执行指定命令
[root@etcd-3 ~]# etcdctl get /testdir2/testkey
hello watch
[root@etcd-3 ~]# etcdctl set /testdir2/testkey "hello exec-watch"
hello exec-watch [root@etcd2 ~]# etcdctl exec-watch testdir2/testkey -- sh -c 'ls /'
bin data etc lib media opt root sbin sys usr
boot dev home lib64 mnt proc run srv tmp var # --after-index '0' 在指定index之前一直监测
# --recursive 返回所有键值和子键值
备份
# 备份etcd的数据
[root@etcd-3 ~]# etcdctl backup --data-dir /data/app/etcd --backup-dir /home/etcd_backup_2020_08_27
2020-08-27 10:48:47.833840 I | ignoring EntryConfChange raft entry
2020-08-27 10:48:47.833904 I | ignoring EntryConfChange raft entry
2020-08-27 10:48:47.833913 I | ignoring EntryConfChange raft entry
2020-08-27 10:48:47.833959 I | ignoring member attribute update on /0/members/7d37bdfae6fd099b/attributes
2020-08-27 10:48:47.833976 I | ignoring member attribute update on /0/members/94362b802cdd1767/attributes
2020-08-27 10:48:47.833990 I | ignoring member attribute update on /0/members/2407f8cc1c4a982/attributes
2020-08-27 10:48:47.834002 I | ignoring member attribute update on /0/members/2407f8cc1c4a982/attributes # 支持选项
# --data-dir etcd的数据目录
# --backup-dir 备份到指定路径
member
# 通过list,add,remove命令列出,添加,删除etcd实例到etcd集群中

查看集群中存在节点

[root@etcd-3 ~]# etcdctl member list
2407f8cc1c4a982: name=etcd3 peerURLs=http://192.168.43.246:2380 clientURLs=http://127.0.0.1:2379,http://192.168.43.246:2379 isLeader=true
7d37bdfae6fd099b: name=etcd1 peerURLs=http://192.168.43.233:2380 clientURLs=http://127.0.0.1:2379,http://192.168.43.233:2379 isLeader=false
94362b802cdd1767: name=etcd2 peerURLs=http://192.168.43.130:2380 clientURLs=http://127.0.0.1:2379,http://192.168.43.130:2379 isLeader=false

删除集群中存在的节点

[root@etcd-3 ~]# etcdctl member remove 94362b802cdd1767

向集群中新加节点

[root@etcd-3 ~]# etcdctl add etcd4 http://192.168.43.254:2380
节点迁移

在生产环境中,不可避免遇到机器硬件故障。当遇到硬件故障发生的时候,我们需要快速恢复节点。ETCD集群可以做到在不丢失数据的,并且不改变节点ID的情况下,迁移节点。

# 1)停止待迁移节点上的etc进程;
# 2)将数据目录打包复制到新的节点;
# 3)更新该节点对应集群中peer url,让其指向新的节点;
# 4)使用相同的配置,在新的节点上启动etcd进程;

小结

etcd默认只保存1000个历史事件,不适合大量更新的场景,这样会导致数据丢失,典型应用场景是配置管理和服务发现,这些场景都是读多写少的

相比于zookeeper,etcd使用起来简单很多,不过要实现真正的服务发现功能,etcd还需要和其他工具(比如registerator,confd等)一起实现服务的自动注册和更新

目前etcd还没有图形化工具

本文部分摘自

https://segmentfault.com/a/1190000023047434

01 . etcd简介原理,应用场景及部署,简单使用的更多相关文章

  1. 01 . RPC简介原理及用Go实现一个简单的RCP

    RPC简介 本地过程调用 // 正常情况下程序的执行和调用情况.例如有如下go语言代码: package main import "fmt" func main() { var a ...

  2. 01 . Varnish简介,原理,配置缓存

    简介 Varnish是高性能开源的反向代理服务器和HTTP缓存服务器,其功能与Squid服务器相似,都可以用来做HTTP缓存.可以安装 varnish 在任何web前端,同时配置它缓存内容.与传统的 ...

  3. etcd简介及集群安装部署使用

    目录 1. 简介 2. Linux下载安装 3. 单机模式启动 4. 指定各集群成员的方式配置集群 5. 使用discovery service的方式配置集群 6. 集群模式下客户端命令行 7. et ...

  4. 01 . Vue简介,原理,环境安装

    简介 vue是一个JavaMVVM库,是一套用于构建用户界面的渐进式框架,是初创项目的首选前端框架.它是以数据驱动和组件化的思想构建的,采用自底向上增量开发的设计.它是轻量级的,它有很多独立的功能或库 ...

  5. 转:etcd:从应用场景到实现原理的全方位解读

    原文来自于:http://www.infoq.com/cn/articles/etcd-interpretation-application-scenario-implement-principle ...

  6. etcd:从应用场景到实现原理的全方位解读

    随着CoreOS和Kubernetes等项目在开源社区日益火热,它们项目中都用到的etcd组件作为一个高可用强一致性的服务发现存储仓库,渐 渐为开发人员所关注.在云计算时代,如何让服务快速透明地接入到 ...

  7. etcd:从应用场景到实现原理的全方位解读 转自infoq

    转自 infoq etcd:从应用场景到实现原理的全方位解读 http://www.infoq.com/cn/articles/etcd-interpretation-application-scen ...

  8. [转帖]kafka入门:简介、使用场景、设计原理、主要配置及集群搭建

    kafka入门:简介.使用场景.设计原理.主要配置及集群搭建 http://www.aboutyun.com/thread-9341-1-1.html 还没看完 感觉挺好的. 问题导读: 1.zook ...

  9. 01 HDFS 简介

    01.HDFS简介 大纲: hadoop2 介绍 HDFS概述 HDFS读写流程 hadoop2介绍 框架的核心设计是HDFS(存储),mapReduce(分布式计算),YARN(资源管理),为海量的 ...

随机推荐

  1. Django学习路3

    1.打开 Data Source alt insert 打开 Data Source 找到 db.sqlite3 确定 Download 下载后 TestConnection 测试是否成功 2.项目下 ...

  2. PHP gettimeofday() 函数

    ------------恢复内容开始------------ 实例 返回当前时间: <?php// Print the array from gettimeofday()print_r(gett ...

  3. PHP date_timestamp_set() 函数

    ------------恢复内容开始------------ 实例 设置基于 Unix 时间戳的日期和时间: <?php$date=date_create();date_timestamp_se ...

  4. PHP filetype() 函数

    定义和用法 filetype() 函数返回指定文件或目录的类型. 如果成功,该函数返回 7 种可能的值之一.如果失败,则返回 FALSE. 可能的返回值: fifo char dir block li ...

  5. HTML - XHTML

    HTML - XHTML XHTML 是以 XML 格式编写的 HTML.高佣联盟 www.cgewang.com 什么是 XHTML? XHTML 指的是可扩展超文本标记语言 XHTML 与 HTM ...

  6. Nginx的文章推荐

    Nginx服务器之负载均衡策略(6种) Nginx与Tomcat实现请求动态数据与请求静态资源的分离 Nginx 相关介绍(Nginx是什么?能干嘛?)     https://www.cnblogs ...

  7. 通过源代码分析Mybatis的功能

    SQL解析 Mybatis在初始化的时候,会读取xml中的SQL,解析后会生成SqlSource对象,SqlSource对象分为两种. DynamicSqlSource,动态SQL,获取SQL(get ...

  8. C 语言学习 --2

    memset Declaration: void *memset(void *str, int c, size_t n); Copies the character c (an unsigned ch ...

  9. Kafka 错误信息 java.io.IOException: Can't resolve address: VM_0_15_centos:9092

    kafka   安装完成之后 代码运行的时候,运行完成报这个错误 java.io.IOException: Can't resolve address: VM_0_15_centos:9092 解决办 ...

  10. Android RecyclerView的补充。

    明天写吧.. 今天写,然后再写今天的内容,虽然结课了,我们还是得学习,所以如果我学习了一些知识,不出意外每天会持续更新的. RecyclerView其实是可以完全代替ListView的存在, 但是为啥 ...