1 | 在 dir 目录下查找包含 content 的文件 |
1 | grep -n 'SET timestamp=15925248..' slow.log |
1 | 读取 slow.log 文件前 100 行文本 |
1 | sed -n 'from,to'p slow.log |
1 | sed -n 'row'p slow.log |
这篇文章中,我会编写 2 个微服务集成 Prometheus,并通过 docker 来安装 Prometheus 和 Grafana 来展示对微服务的监控。
以下列出文中提及的各软件版本:
Prometheus 基本原理是通过 HTTP 协议周期性抓取被监控组件的状态,这样做的好处是任意组件只要提供 HTTP 接口就可以接入监控系统。
Springboot 中则通过 spring-boot-starter-actuator 可以以不同的 HTTP 端口来暴露应用的状态等信息。同时 Springboot 2.x 版本中引入了 io.micrometer,通过 micrometer-registry-prometheus 两者可以很方便的集成。
1 | <dependency> |
/actuator/prometheus
端口的访问1 | management.endpoints.web.exposure.include=prometheus |
按照以上两步,微服务就算完成了基础的 Prometheus 的接入。
1 |
|
这里我们为采集的信息添加了一个 application 标签,与微服务的应用名称一致。
由于 Prometheus 官方没有提供 Eureka 注册中心的接入支持,我们可以通过 eureka-consul-adapter
来利用官方提供的对 Consul 的支持达成目标。
pom.xml 中添加依赖
1 | <dependency> |
按照基础步骤接入就可以了。
Eureka 注册中心和 Demo 项目运行起来之后,Eureka 界面如下:
访问微服务的 /actuator/prometheus
端点,界面大致如下:
如需项目源码,可关注我的公众号 up2048
,并回复 prometheus-demo 获取。
编辑 prometheus.yml:
1 | # my global config |
下载并启动容器
1 | docker pull prom/prometheus:v2.11.2 |
容器启动成功后,在浏览器中访问 localhost:9090/targets
。界面如下:
其中 http://ip:12345
是 Eureka 实例,http://ip:8099
和 http://ip:9999
是两个 RON-DEMO 实例。
下载并启动容器
1 | docker pull grafana/grafana |
浏览器访问 localhost:3000
输入默认用户名: admin,密码:admin 登录。首次登录后会提示修改密码。
修改密码后进入首页
添加数据源,选择 Prometheus
编辑相关信息
保存,出现绿色提示,表示成功。
Grafana 的 DashBoard 和 Panel 都支持自定义,这里不做阐述。
此外,Grafana 官方的 DashBoard 市场 Grafana Lab - Dashboards 提供了可拿来即用的强大而通用的 DashBoard。这里我介绍两款:
4701
的 JVM (Micrometer) 10280
的 Spring Boot 2.1 Statistics左上角 [ + ],点击 Import:
在 Grafana.com Dashboard 框中输入 Id,点击 Load
设置数据源,Import
现成的 DashBoard 就可以使用了。
这里分别贴一张 JVM (Micrometer) 和 Spring Boot 2.1 Statistics 的截图供大家参考。
基于 Prometheus 与 Grafana 微服务监控体系到这里就算初步搭建完了。
最开始使用的是 Prometheus 最新版本的镜像,eureka-consul-adapter
是 1.1.0 版本,按照上面一样的配置之后,在 /targets
页面上看不到监控的微服务。报了大致如下的日志:404 /v1/health/service/ron-demo not found.
后来发现 eureka-consul-adapter
是没有提供 Consul 的 health 类端口的,可能因为这个原因,无法被 Prometheus 发现。但是很多参考材料上都是可行的,于是我针对参考材料发布的时间,找到了那个时间段发布的 Prometheus 版本,将版本回退到 v2.11.2 之后,/targets
页面终于出现了预期的画面。
经之后的测试发现,Prometheus 换成最新版本,eureka-consul-adapter
是 1.4.0 版本以后,这个问题也可以解决。
因此,如果大家遇到这个问题,很可能是 Prometheus 和 eureka-consul-adapter
版本不匹配导致的。
这个是在解决第一个问题过程中,发现其他人遇到的问题,详见:prometheus报错Error refreshing service Unexpected response code: 503解决办法。当我解决了第一个问题之后,查看日志,也发现了一样的 503 错误。解决方案可以参看我链接的文章。
这主要是因为没有给 Prometheus 采集的信息添加 application 标签导致。因为我最初的代码并没有加入前文微服务接入 -> 基础步骤 -> 3 章节中的相关代码。在 MicroMeter 1.1.0 版本之后,可以在应用配置中添加如下属性,达到一样的效果。
1 | management.metrics.tags.application: ${spring.application.name} |
Actuator + Prometheus + Grafana搭建微服务监控平台
prometheus报错Error refreshing service Unexpected response code: 503解决办法
SpringCloud使用Prometheus监控(基于Eureka)
Spring Cloud 集成 Prometheus Grafana
]]>下载 elasticsearch-6.6.0 二进制包,解压,复制 3 份,分别命名为 es1, es2, es3。
分别修改 config/elasticsearch.yml 的配置,
es1 如下:
1 | # 集群名称,相同集群的节点名称一致 |
es2 如下:
1 | # 集群名称,相同集群的节点名称一致 |
es3 如下:
1 | # 集群名称,相同集群的节点名称一致 |
1 | cd /path/to/es1 |
通过 elasticsearch-head 可以可视化的管理 es 集群。
elasticsearch-head 项目地址 http://mobz.github.io/elasticsearch-head。
如果本地有 nodejs 环境,通过以下步骤安装 elasticsearch-head。
1 | git clone git://github.com/mobz/elasticsearch-head.git |
安装完成,在浏览器中访问 http://localhost:9100/
elasticsearch-head 在访问 es 实例 RESTful API 时会存在跨域问题,es 实例启动时,配置文件需要添加一下参数
http.cors.enabled: true
http.cors.allow-origin: "*"
创建索引
1 | PUT /my_index |
number_of_shards: 主分片数量,默认为 5
number_of_replicas: 分片副本数量,默认为 1
动态更新副本数量
1 | PUT /my_index/settings |
这里以另外一个真实在用的集群来演示集群的高可用性。
app_jgus_yn
与 app_jgus
都是在单节点集群情况下创建的,默认主分片数量为 5,副本数量为 0。app_jgus
动态更新了副本数量为 1。app_jgus
索引的查询依然可以正常进行,因为 app_jgus
的每个主分片都有 1 个副本,剩余 2 个节点中保存有所有的索引数据。app_jgus_yn
由于没有为主分片生成副本,随 es3 节点失败,缺少了 2 个主分片,缺失的分片数据将无法被操作。app_jgus_yn
动态更新副本数量,将 number_of_replicas
设为 1,即每个主分片都有 1 份副本。在集群搭建完成以后,创建索引过程中,遇到了数据分片 unassigned
的异常。大致情况如下:
查看分片 unassigned
原因
1 | GET /_cluster/allocation/explain?pretty |
这里是因为空间不够引起的。
解决方案是调整集群磁盘水印的参数。
1 | PUT /_cluster/settings |
集群搭建过程中,配置文件显式指定 transport.tcp.port
,但未显式设置 discovery.zen.ping.unicast.hosts
时,几个节点之间无法组成集群,各自独立。
我在配置时,3 个 es 节点的 transport.tcp.port
分别为 5301、5302、5303。当没有显式设置 discovery.zen.ping.unicast.hosts
时,会自动按照 9300、9301、9302 的端口顺序设置这个广播地址,因此无法与真实的实例端口通信。以上是我通过查看进程占用的端口得出的结论。
在浏览器上多次刷新同一个页面,发现同一区域的数据会时多时少。
查看接口响应发现同一接口多次请求会返回不一样的数据。
页面显示
接口响应
页面显示
接口响应
正常接口响应与异常接口响应数据量对比
多个不同版本的应用实例,代码实现或者数据库数据不同导致。
经过排查,开发和测试环境都存在一样的问题,并且这两个环境都是单实例的。
走查了代码,发现这里只是一个简单的单表查询,并没有复杂逻辑。
同时针对单个接口反复测试,无法重现响应数据时多时少的问题。
目前看,这块的代码并没有很直观的错误。
使用查询语句,多次重试,没有复现异常。
经过猜测二、三的排查,怀疑是页面上多个请求同时发起共同导致的问题。
于是反复刷新页面,查看后端日志,发现正常情况与异常情况时执行的 sql 不一致。
异常情况下,会在正常的 sql 后面添加 limit
子句。
到这里,我只能猜测是 mybatis 动态生成的 sql 有问题。
那么什么原因导致会在正常的 sql 后添加 limit 子句,并且这个 5
是从哪里来的呢?
在度娘上以 “mybatis 自动添加 limit” 为关键字搜索,发现由于 PageHelper 插件自动添加 limit 的问题很多。
在 Mybatis-PageHelper 的使用方法文章中有如下说明:
由此,可能的原因是在这个页面上的其他请求带有分页,同时由于后端对 Mybatis PageHelper 的使用不规范导致。
页面上有分页的请求
在排查了该接口的代码后发现:代码中调用 PageHelper.startPage
方法的地方与真正执行查询的代码中间有很多复杂的业务处理。
问题原因基本清楚了。
PageHelper.startPage
方法的使用,只在真实查询要执行前,调用该方法,确保安全分页。PageHelper.clearPage
目前按照临时解决方案进行了处理,反复测试以后,问题得到了解决。
]]>这里总结了几种方式。
ss 一般用于转储套接字统计信息。它还可以显示所有类型的套接字统计信息,包括 PACKET、TCP、UDP、DCCP、RAW、Unix 域等。
1 | ~# ss -lntpd | grep :80 |
netstat 能够显示网络连接、路由表、接口统计信息、伪装连接以及多播成员。目前 netstat 已经过时了,都推荐使用 ss 来代替。
1 | ~# netstat -tnlp | grep :80 |
lsof (list open files) 是一个列出系统上被进程打开的文件的相关信息。
1 | ~# lsof -i tcp:80 |
fuser 可以显示出当前哪个程序在使用磁盘上的某个文件、挂载点、甚至网络端口,并给出程序进程的详细信息。fuser 只把 PID 输出到标准输出,其他的都输出到标准错误输出。
1 | ~# fuser 80/tcp |
说明:下文中的
$go-lab
为自己电脑上的某个目录。
下载镜像
1 | docker pull golang |
新建 gopath 目录及项目源码目录 projects,将在运行容器时做映射
1 | docker 中默认 /go 为 gopath |
需要将项目源码目录与 gopath 目录分开,这是 go mod 的要求。
进入 projects
下载 html2md 项目源码
1 | cd $go-lab/projects |
运行容器并进入容器
1 | cd $go-lab |
在容器中设置环境变量
1 | go env -w GOPROXY=https://goproxy.cn,direct |
关键:GOPROXY 代理最好要设置,因为 golang 的许多依赖是被 GFW 给墙了,不设置带来会导致依赖无法下载。
下载依赖
1 | cd /root/html2md |
go mod init project_name
: 用 go mod 初始化目录
go mod tidy
: 会同步依赖包,添加需要的,移除多余的
go mod download
:下载依赖
go clean -modcache
: 清除缓存
编写 golang 项目,引用 html2md 库
1 | mkdir $go-lab/demo |
html2md.go
文件内容如下
1 | package main |
编译项目
1 | go build html2md.go |
编译完成后,在目录下生成 html2md 可执行文件。
测试 html2md 工具
1 | html2md code.html code.md |
查看是否正确生成 code.md
golang 提供了完备的交叉编译环境。docker 中直接通过 go build
生成的执行文件只能在 linux 环境下运行。通过交叉编译,可以很方便的编译在 mac 或者 windows 下运行的可执行文件。
1 | 编译 mac 可用的可执行文件,生成 html2md |
mysql 中常用的脱敏方法有以下两种。
1 | SELECT |
1 | SELECT |
# update select 语句(注意:必须使用 inner join)# 语法 update ta inner join (select yy from tb) tc on ta.id = tc.id set ta.xx = tc.yy-- 行政处罚信息表UPDATE T_XYXX_XZCFXX oriINNER JOIN (SELECT uuid, INSERT ( FDDBR, 1, 1, '*' ) AS NAME, CONCAT( '000000', '****', RIGHT ( FDDBRZJHM, 8 ) ) AS idcardno FROM T_XYXX_XZCFXX ) nosense ON ori.uuid = nosense.uuid SET ori.FDDBR = nosense.NAME, ori.FDDBRZJHM = nosense.idcardno;
]]>这是一张打满马赛克的图。
嗯,把你的思绪拉回来先。我来介绍下团队的一些简单背景。
我们部门在基于 spring cloud 做微服务的实践。业务上在公司内部相对独立,部门内部有几个团队负责不同的项目。在部门内部希望生产的代码等可以跨几个团队共用,以满足不同项目快速开发的需求。
开头的图片展示的是其中一个团队目前代码的组织方式。
xxxx.yyyy.common.feign
包下聚合了其他微服务所实现能力的声明。在一个团队内部,这样的设计具有一定的合理性。
将这个团队作为一个内聚的单元,通过 common 项目来聚合内部各个微服务提供的能力,统一暴露,很好的使用了一个外观模式。外部不必关心内部实现细节,只需专注在接口上。对内部来讲,各个服务的能力汇总到 common 中,便于管理。
但是,请回顾一下之前我提到的团队的背景。这个团队只是部门中的一个,其生产的代码需要能在部门中共享与复用。
这时的 common 项目却对部门其他团队关上了门。纵然这个团队生产的多数代码可以通过复制的形式减少其他团队的工作量,但由于 common 包含了众多这个团队业务本身的东西,在其他团队中使用时必定需要做较大的调整。
是时候打破 common 这扇门了,让门内已有的微服务走入到其他团队中。那该怎么做呢?
每个微服务对外声明自己的能力,不聚合在 common 中。每个微服务独立维护。内部负责实现自己对外声明的能力,外部使用微服务声明能力的接口。调整一下代码的组织,实例如下:
1 | |- xxx-yyy-zzz // 主项目 |
如上面代码块中的显示,api 子项目负责声明微服务提供的能力,以 Feign 接口的形式暴露,可以独立打包为 jar,并需要的项目引用。service 子项目负责实现微服务声明的能力。
这样,每个微服务成为高内聚的单元,可以供其他团队复用。微服务的维护人员,不必局限在原来小团队中,而是放眼整个部门,同时专注维护微服务本身。在团队治理上,也打破了原来几个团队之间的无形屏障。
]]>1 | Cornerstone.app 已损坏,打不开。您应该将它移到废纸篓。 |
如果你要继续安装,需要打开系统偏好设置-安全性与隐私-通用
,在允许从以下位置下载应用中
选择任何来源
。之后就可以正常安装了。
如果上面的操作没有看到任何来源
的选项。通过以下命令开启。
1 | sudo spctl --master-disable |
安装完成以后,通过以下命令隐藏任何来源
选项。
1 | sudo spctl --master-enable |
通过 start.spring.io 生成项目代码,添加 web
, actuator
, eureka server
依赖。
解压源码包,在 idea 中打开项目。
在 application.properties
中添加以下内容:
1 | spring.application.name=spring-cloud-eureka-server |
新建 application-server1.properties
,application-server2.properties
,application-server3.properties
三个文件,用于配置 3 个 eureka server 实例。
application-server1.properties
内容如下:
1 | spring.profiles=server1 |
application-server2.properties
内容如下:
1 | spring.profiles=server2 |
application-server3.properties
内容如下:
1 | spring.profiles=server3 |
主启动类添加 @EnableEurekaServer
来激活注册服务器。
1 | package ltd.pinshi.springcloudeurekaserver; |
由于 3 个实例都运行在一台机器上,需要在 hosts 文件上做下配置。Mac 下为 /etc/hosts
,在结尾添加以下映射,保存退出。
1 | 127.0.0.1 server1 |
在 idea 右上角 Edit Configurations… 新建 3 个运行配置。在 Program arguments 中设置 --spring.profiles.active=server1
相应的启动场景。
点击右上角 Run 来启动实例。
进入项目根目录,执行 mvn clean package
。在 target 子目录下会生成 spring-cloud-eureka-server-0.0.1-SNAPSHOT.jar
文件。
通过以下命令分别启动 3 个实例:
1 | java -jar spring-cloud-eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=server1 |
浏览器输入 http://localhost:8761,内容如下:
http://localhost:8762,http://localhost:8763 内容相似。
需要注意的是 DS Replicas 部分的内容。
通过 Instances currently registered with Eureka 可以看到注册了 3 个实例。
General Info 中的 registered-replicas
,unavailable-replicas
,available-replicas
可以通过停止某个实例观察变化。
在 application.properties
中
1 | 表示是否注册自身到eureka服务器,默认为 true |
大家可以取消这两个选项的注释,查看浏览器的内容显示。
]]>修改 /etc/ssh/sshd_config
找到如下两行:
1 | ClientAliveInterval 0 |
修改为
1 | 客户端每隔多少秒向服务发送一个心跳数据 |
重启 sshd 服务
1 | service sshd restart |
1 | ssh -i key.pem |
如果出现报错,说明文件的权限太大了,修改下权限。
1 | sudo chmod 600 key.pem |
2. 通过 ssh-add 添加 .pem 文件
1 | ssh-add -k key.pem |
这样就可以通过 ssh root@ip
直接登录了。
apachectl -version
和 php -v
进行查看。1 | ➜ apachectl -version |
启动:sudo apachectl start
停止:sudo apachectl stop
重启:sudo apachectl restart
查看版本号:sudo apachectl -v
通过 vi 打开 apache 的配置文件,配置文件需要超级权限才能进行读写。
1 | sudo vi /etc/apache2/httpd.conf |
找到下面这行
1 | # LoadModule php7_module libexec/apache2/libphp7.so |
去掉前面的 #
注释。
保存,退出,重启 apache 就可以生效了。
1 | mkdir -p /Users/your_user/...your_site |
httpd.conf
中开启虚拟主机支持1 | sudo vi /etc/apache2/httpd.conf |
找到下面这行
1 |
去掉前面的 #
注释。
找到 httpd.conf
中的如下内容
1 | DocumentRoot "/Library/WebServer/Documents" |
复制一份,直接添加到这段后面。
修改其中的 /Library/WebServer/Documents
为上面建好的文件夹路径 /Users/your_user/...your_site
。
保存,退出 httpd.conf
。
1 | sudo vi /etc/apache2/extra/httpd-vhost.conf |
在最后添加如下代码:
1 | # 'localhost' 虚拟主机 |
保存退出。重启 apache 服务。
/etc/hosts
1 | sudo vi /etc/hosts |
添加 127.0.0.1 mysite
,保存退出。
1 | sudo vi /Users/your_user/...your_site/info.php |
info.php
内容如下:
1 |
|
保存退出。
在浏览器中输入 http://mysite/info.php,查看结果。
]]>默认下载最新版本,指定版本可以在 zookeeper 后面添加 tag,如 zookeeper:latest
1 | docker pull zookeeper |
1 | docker run --name first-zk -d zookeeper |
这个命令会创建名为 first-zk 的 zookeeper 容器,在后台运行,并默认导出 2181 端口。
以下命令通过输出日志可以查看 zk 运行情况。
1 | docker logs -f first-zk |
1. 在已创建的 first-zk 容器中,执行 zkCli.sh:
1 | docker exec -it first-zk zkCli.sh |
2. 新建 zookeeper 容器作为客户端,并连接到 first-zk:
1 | docker run -it --rm --link first-zk zookeeper zkCli.sh -server first-zk |
这里
--link first-zk
通过 docker 的 link 机制来访问 first-zk 容器;--rm
在容器退出后会自动删除容器;zkCli.sh -server first-zk
则启动 zkCli.sh 命令,连接到 first-zk。
zkCli.sh 常用命令的使用,请参考这里。
1 | version: '2.2' |
在这个配置文件中,docker 运行了 3 个 zookeeper 镜像,通过 ports
字段分别将本地的 2181, 2182, 2183 端口绑定到对应容器的 2181 端口上。
ZOO_MY_ID
和ZOO_SERVERS
是搭建 Zookeeper 集群需要的两个环境变量。ZOO_MY_ID
标识服务的 id,为 1-255 之间的整数,必须在集群中唯一。ZOO_SERVERS
是集群中的主机列表。
在 docker-compose.yml
所在目录下执行 COMPOSE_PROJECT_NAME=docker-zk-cluster docker-compose up
1 | ➜ docker-zk-cluster COMPOSE_PROJECT_NAME=docker-zk-cluster docker-compose up |
COMPOSE_PROJECT_NAME=docker-zk-cluster
设置该环境变量是为当前的 compose 工程取的名字,与其他工程做区分。
启动后,打开另一终端窗口,运行 COMPOSE_PROJECT_NAME=docker-zk-cluster docker-compose ps
,查看服务运行状态
1 | ➜ COMPOSE_PROJECT_NAME=docker-zk-cluster docker-compose ps |
1 | docker run -it --rm --link zk1 --link zk2 --link zk3 --net docker-zk-cluster_default zookeeper zkCli.sh -server zk1:2181,zk2:2181,zk3:2181 |
--net docker-zk-cluster_default
其中docker-zk-cluster
是 compose 工程名,不作这个配置会导致docker: Error response from daemon: Cannot link to /zk1, as it does not belong to the default network.
异常。具体原因请看这里。
通过 nc 命令连接到指定 Zookeeper 服务器,发送 stat 来查看状态。
1 | ➜ ~ echo stat | nc localhost 2181 |
如上,我们发现 zk3(端口为 2183)为 leader,其他为 follower。
]]>a. 先对数组的元素进行排序,然后再取其中的 k 个数。时间复杂度主要在排序算法上,可以做到 nlog(n)
b. 循环 1 - k,每个循环内对数组做一次冒泡取最大值,则时间复杂度 kn
c. 先取 k 个元素,建立小顶堆,再遍历剩下的元素,如果元素比堆顶大,则放入堆中根据需要调整堆形态。最终堆中的元素就是最大的 k 个。
面试时很自然想到了 a 方案;b 想过,不过现场没有描述清楚;c 是参考了网络上的答案。
分析:n=1 的时候,只有走 1 步的方案;n=2 的时候,有走 2 个 1 步或 1 个 2 步 2个方案;而 n 步可以归纳为走 n-1 步的所有方案和走 n-2 步的所有方案的和。
a. 递归
1 | public int f(int n) { |
b. 迭代
1 | public int f(int n) { |
面试时最先想到的了 a 方案;b 方案脑海中一直没有想起来递归对应的迭代这个词。
1 | select |
面试时没有给出这条语句,group by 与 order by 各想了一部分,没有拼成一条语句。回来后用实例测试了一下。平时在应用中基本走简单查询。
1 | a = 4; |
问线程 A 运行到②时,线程 B 将读取到 a 的值是多少?
面试时想到了数据库的事务隔离级别。未提交读、读已提交(不可重复读)、可重复读、串行化。
答曰:未提交读时结果是 5,读已提交时是 4。
最终的结果是 24 个。
每个 5 和 10,会使结果多一个 0,其中 25,50,75,100 会多两个,所以 10 个 5 + 10 个 10 + 4 = 24 个。
面试时给了 22 个的结果。未考虑到 25 和 75。
题目 1,2,5 都是网上现成的题目,其实还是比较简单的。工作 10 年,很久没有参加面试了,也没有遇到面这些题目的。没有准备到,回答时只能根据自己临时的思路,回答的不尽如人意,比较遗憾。
]]>./gradlew clean build
,报了如下提示:1 | env: bash \r: No such file or directory |
刚开始一头雾水,之前没有遇到过这个问题,而且这是提示没有什么可以跟踪的信息。在百思不得其解之际想到源码是朋友通过 windows 传给我的,随即想到 dos 下的换行与 unix 下换行不一致的特性。
所以通过 vi 打开 gradlew 文件,在命令模式下,执行:
1 | :set fileformat=unix |
来切换文件格式为unix格式,并保存退出。
继续在终端中执行 ./gradlew clean build
,问题就解决了。
1 | <packaging>war</packaging> |
使用 Maven 创建 SpringBoot 项目,默认打包为 jar。
spring-boot-starter-web
依赖中移除 tomcat 模块1 | <dependency> |
SpringBoot 自带 tomcat,需要移除
1 | <dependency> |
<build>
节点中的 finalName
修改为 server.context-path 中的路径。1 | <build> |
SpringBootServletInitializer
1 | public class MySpringBootApplication extends SpringBootServletInitializer { |
mvn
打包1 | mvn clean package -Dmaven.test.skip=true |
在项目的 target 目录下就可以看到 .war 文件
ubuntu 14.04 默认的 java 版本为1.7,为了升级到 1.8并保持干净,我们先删除低版本的 open jdk。
1 | sudo apt-get autoremove openjdk-7-jre |
执行完可以通过 java -version
来确认是否已经删除
Oracle java 的 PPA 源: https://launchpad.net/~webupd8team/+archive/ubuntu/java
在 shell 中执行如下命令:
1 | sudo add-apt-repository ppa:webupd8team/java |
执行上以上操作,同样可以输入 java -version
来确认是否新版 jdk 已经安装成功。
本系列思维导图整理自 Laravel 5.4 版本官方文档。
思维导图导出为图片时会导致其变模糊。需要高清的思维导图源文件,请关注微信公众号:up2048,并回复“精进脑图”来获取。
今天我们就通过 4 张思维导图来快速看 Laravel 5.4 入门指南。
接下来,本人会继续对 Laravel 做比较系统的学习,会以思维导图的形式做梳理并于大家分享。这些文章都将首发在微信公众号:up2048 上。欢迎大家扫描下面的二维码,我们一起学习,分享,讨论,反思。
- EOF -
在打算系统的学习 Laravel 之前,我抽空看了 PHP 语言基础,并且绘制了各个部分的思维导图,以方便自己从一些图片就可以查看相关知识的全貌。
这部分思维导图整理自菜鸟教程的 PHP 教程。
思维导图导出为图片时会导致其变模糊。需要高清的思维导图源文件,请关注微信公众号:up2048,并回复“精进脑图”来获取。
下面我们就通过 15 张思维导图来快速学习 PHP 语言基础。
接下来,本人会对 Laravel 做比较系统的学习,会以思维导图的形式做梳理并于大家分享。这些文章都将首发在微信公众号:up2048 上。欢迎大家扫描下面的二维码,我们一起学习,分享,讨论,反思。