Jasper Ji

开口不在舌头上

0%

坚持冥想已经有六七年了,略有点经验与大家分享下,一家之言,只供参考。

时间

冥想多长时间比较好?我目前是30分钟,之前一段时间都是1个小时,改成30分钟是因为后来找的工作太远,个人建议不低于30分钟为好。

什么时间冥想比较好?我是早上冥想了,下午很少冥想。首先早上精神比较好,不容易发困,下午冥想的时候往往劳累一天状态不是很好。早上冥想是因为时间比较好安排,只要你起的早,时间都是可以挪出来的。

姿势

首推双盘,其次单盘,最不好散盘。能单盘不要散盘,能双盘不要单盘。单盘基本上大家都可以,双盘如果身体不够瘦或者腿实在太硬,还是有点难度的。不过双盘只是时间问题了,我单盘五年一直双盘不上,基本上都已经放弃对双盘的想法,不过五年后的某天我居然可以双盘了,所以现在觉得双盘这个只是时间的问题,可能每个人不太一样,不过就是不要太主观的设置一个时间点,顺其自然就好,功夫到了自然盘的上。

双盘和单盘冥想有什么差别,进入状态后其实差别不大,不过双盘确实比较稳。双盘比较难,很大程度是因为在一开始不熟练的情况下,疼痛会干扰到无法安心坐下去。双盘我目前采用先盘左腿,再盘右腿的吉祥坐,另外手印也采用右手放在左手掌的方式,目前自我感觉这样的姿势可以很好的安心双盘。

调身,总的来说身体瘦一点,下午少吃点,对于盘腿还是有帮助的。我断食21天时,双盘完全不是问题,所以还是不要自己太胖。

观呼吸

干坐着,心可能会更乱,一般人都会觉得坐不了多久,自己就想不坐了。我早期接触的时候,也是不得其法,一开始就那样坐着,时间比较短。后来去了禅寺的禅堂坐了几次香(打坐),下午香时间最长一个半小时,当时还是散盘,到最后腿巨痛,虽然坚持了下来,后来问一个坐的比较好的小姑娘,原来她在用数呼吸,我也学着使用这个方式。数呼吸,一呼一吸算一下,从1到10,重复,实际上比较难,你会发现你的心会莫名的飞出个念头,打乱数呼吸的节奏。不过你只要持续的数,专注了,我当时觉得时间一下子变短了,腿也不痛了,所以数呼吸对于摄心还是很有效果了。结果一数就是到现在了,关于数呼吸详细的内容可以参阅南传佛教的典籍《清静道论》。

心态

不管什么原因让你开始冥想,但不要对冥想有一个心理期许,放长远一点,用余生去练习,每个阶段你能收获什么取决于你走的有多远,而多远不是你主观可以预判的。虎头蛇尾的冥想我也见过几个,其实冥想对我而言更多获得了一种内心的平静,而我觉得这就够了。但很多人,不是这样想的,期许太多,最终没有能坚持下来。坚持冥想,全身心的投入到工作,让冥想成为一种习惯,不要太热衷,也不要太懈怠。

书籍

最好的依旧是坚持练习,少看书,偶尔看一点就可以了,多了反而是障碍。偏方法的推荐《清静道论》或者南传佛教相关的书籍,不过《清静道论》是根本。心态类的推荐《禅者的初心》,心态要对,这很关键。

如何开始?

去禅寺的禅堂学习冥想是我走过的路,正规的禅寺有一套很完备的机制,让你短时间内可以上路。不过很多人在禅寺的时候可以冥想,但出了禅寺就不行了,总是找各种理由。禅寺只是开始的地方,冥想之路才刚开始。

主要是想把Elasticsearch整合到当前的系统中,用来搜索文章。

环境

Elasticsearch 7.12.0
Logstash 7.12.0
Mysql 8.0

数据同步

把Elasticsearch整合到现有系统意味需要把当前要搜索的数据添加到Elasticsearch里面,这就有个数据同步问题。因为是测试工程使用的是Logstash的全量数据同步。Logstash的新版本已经包含了Jdbc的插件,所以不需要安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
input {
jdbc {
# myslq驱动,可以官网下载,我用的是Maven依赖已经下载的Jar文件
jdbc_driver_library => ""
# Mysql驱动类全类名,注意mysql8.x以上需要加cj
jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
# mysql连接url
jdbc_connection_string => ""
# mysql 用户名
jdbc_user => ""
# mysql 密码
jdbc_password => ""
# 设置定时任务,多久执行一次查询,默认一分钟,需要无延迟可使用schedule => "* * * * * *"
schedule => "* * * * *"
# 清空上次的sql_last_value记录
clean_run => true
# 要执行的sql(同步)语句,替换成你自己的sql
statement => "SELECT * FROM t_article"
}
}

output {
elasticsearch {
# es主机和端口
hosts => ["127.0.0.1:9200"]
# 同步数据在ES的索引名称,替换为你自己的。
index => "my-cms"
# es文档的id,表示使用mysql表的id
document_id => "%{id}"
}
}

生产环境的数据同步可以参考阿里云Elasticsearch

参考:
Jdbc input plugin

Spring Boot配置

添加Maven依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

添加Elasticsearch客户端配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
@EnableElasticsearchRepositories(basePackages = "io.pratik.elasticsearch.repositories")
@ComponentScan(basePackages = { "io.pratik.elasticsearch" })
public class ElasticsearchClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
// 配置连接信息
final ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo("localhost:9200")
.build();

return RestClients.create(clientConfiguration).rest();
}

/**
* 这个很重要,不添加的话无法使用@Filed注解
*/
@Bean
@Override
public EntityMapper entityMapper() {

ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(
elasticsearchMappingContext(), new DefaultConversionService()
);
entityMapper.setConversions(elasticsearchCustomConversions());

return entityMapper;
}
}

测试文章类,必须要加type='_doc',不然无法使用。另外遇到的问题是我数据库的日期用的是Date话,一直提示解析错误,最后使用LocalDateTime后就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@Document(indexName = "my-cms", type = "_doc")
public class EsArticle {
@Id // org.springframework.data.annotation.Id
private Integer id; // 文章ID

@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title; // 文章标题

@Field(name = "content", type = FieldType.Text, analyzer = "ik_max_word")
private String articleContent; // 文章正文内容

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Field(name = "create_time")
private LocalDateTime createTime; // 文章创建时间
}

创建Repository类。

1
2
3
public interface ElasticArticleRepository extends ElasticsearchRepository<EsArticle, Integer> {

}

生成Controller,使用Postman测试接口是否正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RestController
@RequestMapping("/article")
public class SearchController {
@Autowired
private ElasticArticleRepository elasticRepository;

@PostMapping(value = "/search")
public List<EsArticle> handleSearchRequest(
@RequestParam Map<String, Object> requestParam) {
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
// 构造ES查询条件
String keyword = (String) requestParam.get("keyword");
queryBuilder.should(QueryBuilders.matchPhraseQuery("title", keyword))
.should(QueryBuilders.matchPhraseQuery("content", keyword));

StopWatch watch = new StopWatch();
watch.start(); // 开始计时

Page<EsArticle> articles = (Page<EsArticle>)
elasticRepository.search(queryBuilder); // 检索数据

watch.stop(); // 结束计时
System.out.println(String.format("数据检索耗时:%s ms",
watch.getTotalTimeMillis()));

return articles.getContent();
}
}

是在Mac环境下跑的例子。

安装ELK相关软件

ELK主要指Elasticsearch、Logstash、Kibana,使用elastic.co提供的安装指南,使用Brew安装。

切换Tab

1
brew tap elastic/tap

安装软件

1
2
3
brew install elastic/tap/elasticsearch-full  # 安装Elasticsearch
brew install elastic/tap/logstash-full # Logstash
brew install elastic/tap/kibana-full # Kibana

可以在前台或者后台启动软件

1
2
3
4
5
6
7
8
brew services start elastic/tap/elasticsearch-full # 后台启动
elasticsearch # 前台启动

brew services start elastic/tap/logstash-full
logstash

brew services start elastic/tap/kibana-full
kibana

Spring Boot配置

Maven添加Logstash依赖

1
2
3
4
5
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.3</version>
</dependency>

在Resources目录下添加logback-spring.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- 配置输出地址 -->
<destination>127.0.0.1:4560</destination>
<!-- 日志输出编码 -->
<encoder charset="UTF-8"
class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"logLevel": "%level",
"serviceName": "${springAppName:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"rest": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="LOGSTASH" />
<appender-ref ref="CONSOLE" />
</root>
</configuration>

配置Logstash,创建一个logstash-sample.conf文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
input {
tcp {
mode => "server"
host => "0.0.0.0"
port => 4560
codec => json
}
}
output{
elasticsearch {
hosts => ["http://localhost:9200"] # Elasticsearch 地址
index => "spring_cms-%{+YYYY.MM.dd}" # 这里我用spring_cms,可以替换为自己的。需要注意ES关于索引的命名规范
}
}

使用配置文件重启Logstash服务

1
logstash  -f logstash-sample.conf

最后在项目中输出一段测试日志

1
logger.info("Hello,ELK");

最后在Kibana创建索引就应该能看到日志了。

额外工具

一开始发现Kibana中没有数据,猜测Elasticsearch没有收到数据,最后用到了ElasticSearch head这个可以查看Elasticsearch存储的工具,具体安装可以参考Github的说明。

运行这个工具遇到问题,发现ElasticSearch连接不上,原来是跨域访问的问题,使用brew安装ElasticSearch后会有如下目录

1
2
3
4
Data:    /usr/local/var/lib/elasticsearch/elasticsearch_xxxx/ # xxxx此处是你自己的电脑名
Logs: /usr/local/var/log/elasticsearch/elasticsearch_xxxx.log # xxxx此处是你自己的电脑名
Plugins: /usr/local/var/elasticsearch/plugins/
Config: /usr/local/etc/elasticsearch/

编辑配置文件nano /usr/local/etc/elasticsearch/elasticsearch.yml,添加如下配置。

1
2
http.cors.enabled: true
http.cors.allow-origin: "*"

最后就可以正常访问了。

总结

使用ELK访问日志确实很方便,实际开发中可能会使用云服务商提供ELK服务。

打包现有Java工程的Jar到Docker镜像,也是遇到了问题。

不熟悉Dockerfile

COPY 源 目标

一开始把源和目标的顺序给弄反了,但是还在镜像中有那个文件,结果执行java -jar xxx.jar老提示Error: Unable to access jarfile xxx.jar。最后登录到容器内部发现jar的文件大小不对,最后才反应过来把顺序弄反了。

登入容器内部

一开始为了诊断Java运行Jar文件不成功的问题,我就想看看容器内的文件是否已经在。最后用如下命令,登入容器,这个还是比较有用,不过应该去掉镜像的CMD命令。

1
docker run -t -i xxx_container /bin/bash

镜像大小

一开始用的FROM openjdk:8,打出的镜像有800多M,后来使用FROM openjdk:8-jdk-alpine,大小150多M,我的Jar包实际只有不到50M的样子,所以还是比较大了。

学习Docker后,发现还是得学习下k8s,目前使用Spring Cloud,引入k8s后原来现有的架构确实会有些变化,比如k8s的服务与Spring Eureka实际上有些功能相似的地方。最早我打算使用微服务的时候也是看中可以使用其他语言,但实际上使用了Eureka后,发现用其他语言还是有困难的,Java依旧是服务的首选。引入k8s后这似乎成为了一些可能,不过目前还没有使用来着。

书籍

关于k8s的书籍目前只买了《Kubernetes实战》,之前也是选了几本,但这本当时一看就觉得干货很多,不过在解决细节问题时还是依赖网路了,不过这次可能错了,关于k8s的资料网上的还是太少,解决细节的问题最后还是依靠这本书。这本书在理论和细节方面结合的很好。

使用本地镜像

我用的minikube,本地构建的doker镜像,我并没有推送到docker的镜像服务器,打出的docker镜像,其实还是蛮大的,Python和Flask,只是个简单的Hello World的例子,有800多M。再将这个推送到docker镜像服务器太慢了,实际我根本就没有成功,但拉取镜像速度还不错。

一开始我使用kubectrl run创建Pods。

1
kubectrl run testflask --image=testflask --port=5000 --generator=run/v1

上面这种方式还是有局限,网上说要把imagePullPolicy设置成IfNotPresent或者Never。但是怎么设置?网上的博文根本不说,最后还是从《Kubernetes实战》这书找到答案,使用配置文件来创建,创建一个flasktest.yaml的配置文件。这里需要注意的是配置文件中一定要使用空格缩进而不是Tab,貌似我用的vs code和textmate都是使用Tab缩进。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: flasktest
spec:
containers:
- image: testflask
name: flasktest-container
imagePullPolicy: IfNotPresent # 拉取的策略
ports:
- containerPort: 5000 # 容器的端口
protocol: TCP

通过配置文件来创建pods

1
kubectl create -f flasktest.yaml # 通过配置文件创建pods

最后发现还是有问题,这个镜像始终还是无法拉下来,网上搜索半天也没有找到个解决方案,最后打算直接使用docker的公共镜像docker/getting-started,估计是网络的问题,始终没有拉取下来。我一直把minikube的当成本地的了,docker也是共用一个,实际上犯了个严重的认知错误,minikube在VirtualBox创建了一个minikube虚拟机在运行,docker肯定与我本地的不是一个,《Kubernetes实战》书中提供了一种把本地的docker复制到minikube虚拟机中docker镜像库的方法。

1
docker save testflask | (eval $(minikube docker-env) && docker load)

可以使用minikube ssh登录到minikube虚拟机中,查看docker镜像是否已经复制过去了。最后顺利启动了,不过怎么访问服务呢?

1
kubectl expose rc flasktest --type=LoadBalancer --name flasktest-http

不过这里也个问题,提示replicationcontrollers "flasktest" not found。其实命令kubectrl run testflask --image=testflask --port=5000 --generator=run/v1中,--generator=run/v1是创建replicationcontroller的,但我使用的是配置文件创建,问题不大,修改下配置文件。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: ReplicationController
metadata:
name: flasktest
spec:
replicas: 3
selector:
app: flasktest
template:
metadata:
labels:
app: flasktest
spec:
containers:
- image: testflask
name: flasktest-container
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
protocol: TCP

重新创建,再启动服务,服务时启动了,不过还是有问题,使用minikube没有外部的ip的访问,需要使用如下命令。不过这个端口是随机的,跟原来用docker的那个不太一样。

1
minikube service flasktest-http

总结

网络的问题使本来简单的入门变得复杂,另外k8s的资料目前确实不是很多,一些细节网上的很少有人总结出来。不过感觉方案虽好,但是镜像大,以及可能要自己部署私有的镜像,再到其他的,其实投入还是蛮多的。

去年年底的时候,当时有简单的研究下Docker,不过只是下载了一些类似MongoDB的镜像运行了下,感觉Docker非常适合安装这些软件。最近开始看Kubernetes,Docker的研究难免,那么如何把现有项目打包成一个Docker镜像以及运行,去年好像有简单的试下,不过有点遗忘,再次运行发现一些问题。

准备

这里运行Flask的一个简单例子,只为演示。创建一个文件flask_test的文件夹,

目录文件

1
2
3
app.py
requirements.txt
Dockerfile

app.py,默认host是127.0.0.1,这里有个问题就是打包成镜像,无法访问端口。在mac和linux都一样,具体可以参考这篇文章《Connection refused? Docker networking and how it impacts your image》

1
2
3
4
5
6
7
8
9
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
return 'Hello, World!'

if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)

requirements.txt,这个主要是放置python依赖库,目前只有flask一项。

1
flask

Dockerfile,这个是Build Docker的配置文件,EXPOSE 5000这个是指定容器内部的端口的,在Mac使用Docker客户端启动镜像时会有出现在设置里面。在Linux上构建镜像时出现下载python依赖https的错误SSL: CERTIFICATE_VERIFY_FAILED,需要去掉验证,RUN pip install --trusted-host pypi.org --trusted-host files.pythonhosted.org -r requirements.txt,下面这个是在Mac上可以运行的配置。

1
2
3
4
5
6
7
8
9
10
11
FROM python:3.9
WORKDIR /code
EXPOSE 5000

COPY requirements.txt ./
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

COPY . .

CMD ["python", "app.py"]

构建

构建Docker镜像,在当前目录运行该命令,记得不要忽略这个.,另外镜像的名字不能出现大写,我一直开始写成testFlask,结果构建失败。

1
sudo docker build -t 'testflask' .

####运行
下面这个是以后台形式运行容器,8000是外部的访问端口,5000是容器内的端口。

1
docker run -p 8000:5000 -d testflask

常用命令

Mac上我会用Docker Desktop,不过Linux上还是命令行是王道。主要记几个常用的命令。

1
2
3
4
docker ps # 列出运行的容器
docker images # 列出当前的所有镜像
docker rm 容器ID # 删除指定的容器
docker rmi 镜像ID # 删除指定的镜像

后记

目前打包出的镜像居然有900多M,只有一个python和flask的依赖,觉得可能还是研究的不够。通过这个例子,发现还是得找本Docker的书籍系统的研究下,问题解决了,但是缺乏理论依据。

更新

因为初次接触docker,后来发现可以使用基础版本,修改Dockerfile文件FROM python:3.9-alpine,最后打包后50M。

Mac上安装软件基本上都在使用Brew,做一些记录。

###常用命令

brew info 有些软件长时间不用了,记不起来命令的使用,用这个可以查到如何使用。

###常见问题

更新慢

执行brew update时很慢的问题,主要是是Github访问慢的缘故,设置从镜像拉取,基本上可以解决这个问题。用了中科大的源,亲测速度感刚的。

替换brew.git
1
2
cd "$(brew --repo)"
git remote set-url origin https://mirrors.ustc.edu.cn/brew.git
替换homebrew-core.git
1
2
cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-core
git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-core.git
替换homebrew-cask.git
1
2
cd "$(brew --repo)"/Library/Taps/homebrew/homebrew-cask
git remote set-url origin https://mirrors.ustc.edu.cn/homebrew-cask.git

参考

brew update 更新时 shallow clone
mac使用brew update无反应,更新慢解决办法

为什么是Eclispe

最早接触Eclispe主要是用来开发Android应用,早期Android开发没有Android Studio,只能通过安装插件来开发。后来开发iOS后,Eclispe就用的少了,偶尔用来跑些简单的例子。后来做Java web,Eclispe又开始用了起来。

虽然很多人推荐IntelliJ IDEA,下载的试用了下,某些功能确实比Eclispe强大,但是Eclispe+Spring Tools,对目前的开发来说确实已经够用了,另外还是有点用不习惯,也没有Spring tools 这样的插件,所以干嘛花钱买个自己用不惯的东西呢?

自动补全

默认出现点号时才会出现自动补全的提示,不过可以设置,这里以Mac环境为例。

选择菜单Perferences->Java->Content Assist->Auto activation triggers for Java,默认为***.***,使用.abcdefghijklmnopqrstuvwsyzABCDEFGHIJKLMNOPQRSTUVWSYZ_替换,点击Ok。

常用插件

EasyShell,这个工具主要是可以快速在当前目录打开Shell以及打开当前的文件夹。

Maven 配置文件报错

Maven使用本地Jar包后pom.xml显示错误,实际上本地Jar包是可以加载的,但总是提示错误。

dependencies.dependency.systemPath’ for com.test:test:jar must specify an absolute path but is ${pom.basedir}/libs/test.jar

1
2
3
4
5
6
7
<dependency>
<groupId>com.test</groupId>
<artifactId>test</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${pom.basedir}/libs/test.jar</systemPath>
</dependency>

这个问题虽然不影响使用,但是老显示红叉,总感觉像去掉的。我原来使用Eclispe版本是2020-03 (4.15.0),没有这个问题。但是使用最新的Eclipse版本2021-03 (4.19.0)有这个问题,包括Spring ToolSuite 4也有这个问题,我把4.15.0到4.19.0版本下载几个对比后,终于发现4.16.0开始都有问题,最后发现4.16.0版本Preferences多了一个XMl(Wide Web Developer)项,把子项Validation & Resolution,Enable validation 禁用就没有了。

升级与不升级

如果现有环境稳定,那就最好不要升级。Eclispe好处是可以支持多个语言的开发,可以装一堆插件支持,但感觉只把它当作Java IDE就可以了,现在VS Code完全可以做其他语言的IDE。

之前的Eclispe很稳定,但一次插件的安装后,弹出需要更高版本的Eclispe,然后就无法启动了。一气之下,直接删除Eclispe,重新安装那些插件,结果发现2020-03 (4.15.0)下载的Spring Tools插件不能使用,发现Spring Tools 4已经出了,但是我当前这个版本无法使用。最后下载了Spring Tools官方出品的Eclispe整合版本Spring ToolSuite 4,就结果就出现了上面提到的Maven配置报错的问题,虽然问题解决了,但是还是提示插件安装也是要谨慎,可能升级不对就的折腾半天了。

最近突然间想对比下几个语言间的性能,日常使用的是Java,但平时也会用Python,JavaScript,C,Go之类的语言。Java写项目真的也没有什么问题,成熟的生态圈,遇到问题很容易解决。不过写点小东西的时候,还是觉得Python比较好用,不过就是速度慢了点,很早之前写过一个游戏的移植,脚本语言重构真的是个大问题。Go在上家公司的时候也使用过,不过也都是小的测试了,并没有拿来写过完整点的项目。不过语言的简洁性和C的这种传承,还是很有好感,不过在当时重写公司系统时,最后还是用了Java,主要是大部分的对接都有Java的实现。C一直是我研究的语言,虽然实际写东西不多,但是却看了很多相关的书籍,另外最近研究操作系统和学习汇编语言后,对C的认识又有了新的认识,C的厉害地方就是简洁的设计,性能强大,他的诞生就是为写Unix,缺点是实用性不强,另外内存管理,实际上成熟的项目在内存管理上都有自己的框架,所以对于C/C++之类的语言光学习了语言其实并不能做什么,离应用的距离还很远。

回到正题,我实际上想把Go作为业务研究的语言了,虽然脑海中一直印象他的性能不错,但具体表现怎样能,好像我也并不清楚。最后选择对比的语言是Java、Go、C、Rust四个语言,程序都是运行100000000循环,循环内执行冒泡排序。

Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Test {
public static void main(String[] args) {
// 记录开始时间
long start = System.nanoTime();
int num = 100000000;
for (int i = 0; i < num; i++) {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
bubbleSort(arr);
}
// 打印耗时时间
System.out.println(System.nanoTime() - start);
}

// 排序
public static void bubbleSort(int[] arr) {
int length = arr.length - 1;
for (int i = 0; i < length; i++) {
for (int j = 0; j < length - i; j++) {
if (arr[j] < arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}

运行时间

1
3052757121

Go 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"time"
)

func main() {
start := time.Now().UnixNano()
const NUM int = 100000000
for i := 0; i < NUM; i++ {
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
bubbleSort(arr)
}
// 打印消耗时间
fmt.Println(time.Now().UnixNano() - start)
}

// 排序
func bubbleSort(arr []int) {
for j := 0; j < len(arr)-1; j++ {
for k := 0; k < len(arr)-1-j; k++ {
if arr[k] < arr[k+1] {
arr[k], arr[k+1] = arr[k+1], arr[k]
}
}
}
}

运行时间

1
3804215000 # 纳秒

C 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#include <sys/time.h>
#include <unistd.h>

void bubbleSort(int* array, int length) {
for (int i = 0; i < length -1 ; i++) {
for(int j=0; j < length -1 -i; j++) {
if(array[j] < array[j+1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}

int main() {
struct timeval t_val;
gettimeofday(&t_val, NULL);
printf("start, now, sec=%ld m_sec=%d \n", t_val.tv_sec, t_val.tv_usec);

for (int i = 0; i < 100000000; i++) {
int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int length = sizeof(array)/sizeof(int);
bubbleSort(array, length);
}

struct timeval t_val_end;
gettimeofday(&t_val_end, NULL);

struct timeval t_result;
timersub(&t_val_end, &t_val, &t_result);

double consume = t_result.tv_sec + (1.0 * t_result.tv_usec)/1000000;
printf("end.elapsed time= %fs \n", consume);

return 0;
}

运行时间

1
2
start, now, sec=1617327646 m_sec=57219 
end.elapsed time= 1.746955s

Rust代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::time::Instant;

pub fn bubble_sort<T: Ord>(arr: &mut [T]) {
for i in 0..arr.len() {
for j in 0..arr.len() - 1 - i {
if arr[j] < arr[j + 1] {
arr.swap(j, j + 1);
}
}
}
}

fn main() {
let start = Instant::now();

for _i in 0..100000000 {
let mut numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
bubble_sort(&mut numbers);
}

println!("time cost: {:?} ms", start.elapsed().as_millis()); // ms
println!("time cost: {:?} us", start.elapsed().as_micros()); // us
println!("time cost: {:?} ns", start.elapsed().as_nanos()); // us
}

运行时间

1
2
3
time cost: 1763 ms
time cost: 1763298 us
time cost: 1763300912 ns

Swift 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation

func bubbleSort<T: Comparable>(with array: inout [T]) -> [T] {
for i in 1..<array.count {
for j in 0..<array.count-i where array[j] < array[j+1] {
array.swapAt(j, j+1)
}
}
return array
}


let start = DispatchTime.now()
for _ in 0..<10000000 {
var intArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]
bubbleSort(with: &intArray)
}
let end = DispatchTime.now()
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
let timeInterval = Double(nanoTime) / 1_000_000_000
print("Time: \(timeInterval) seconds")

运行时间

1
Time: 2.391004642 seconds

总结

最早我对比的是Java和C,因为C是在Debug模式下跑的,反而比Java的慢,这个让我无法接受,不应该啊,后来让朋友跑了下,说应该用Release模式,这才反应过来,Release + O3优化后速度一下子就上来了,明显比Java快。Rust和C基本上都有Debug和Release的区分,现在看来Rust和C是一个级别,Java和Go是另一个档次上的同一级别。现在的Java运行默认是解释和编译的混合模式,所以速度上自然也不错,和Java对标的Go数据上来看略微差点,不过考虑到Java的运行时开销,Go其实还是很有诱惑的,后续有可能赶超Java。

企业开发的话用Java依旧最好的选择,个人创业项目的话用Go其实不错。对于Rust和C,不好选择C语法上比较简洁,很多语言特性跟底层更靠近,需要一定底层知识,写操作系统的话,C依旧是首选。Rust呢?个人觉得语法上比较复杂,混杂的语言特性过于多了,既想兼容底层有想高级别的抽象上靠近现代语言,不过这样的情况下依然保持高性能,还是很令人惊讶的。这个不好选,但Rust更充满诱惑,但复杂的语法让人畏惧。

Go语言虽然有C的传承,不过在类型定义上使用后缀的形式跟C语言正好相反,有时还是比较抵触的,另外因为GC的缘故,性能毕竟还是处于应用语言的级别。Rust太复杂,Swift没有用GC是引用计数,速度上比Rust差些,语法上其实跟Rust也差不多,不过作为一个曾经的iOS开发者,虽然Swift在一段时间大家都非常看好,不过最近几年过去了,其实Swift更多还是Oc的一种替代品,并没有突破苹果的范围,服务端也没有掀起什么风浪,Swift终究是Oc的替代品,也正因为此,他也跟苹果生态深度的绑定,成了某种无法脱颖而出的枷锁。

我心中语言其实就是C语言的改良版,内存管理上希望在语言层面上解决,比如用引用计数。语法上不要太加很多的特性,有Go的那样就可以了,但不是Go。不过目前来看是没有的,要不就是自己写一个,也不是没有可能。但实在要是也没有的Go和Rust总还是不错的选择。

开始用Go应该是17年底的时候,那会做游戏,后台是用Go写的,虽然我主要负责Unity客户端方面的工作,不过也是经常帮忙给后端的Go小伙解决一些问题,自己也写一些Go的小东西。

后来又做物流系统,重写系统时有考虑用Go,不过因为大部分的外部接口对接都是Java的,为了省事就使用了Java。不过如果是自己的项目的话,还是愿意用Go,毕竟Java还是有点重。

IDE

原来有用过GoLand,为了轻量点,先用VS Code,众所周知的原因安装插件老失败,最后设置Go的代理就可以了。

1
go env -w GOPROXY=https://goproxy.cn,direct

C程序员的礼物

直接试着写个搜索二叉树,其实除了语法上跟C的一些类型定义次序上有点不习惯外,其他方面对于C程序员来说,还是熟悉的用法,比如指针的指针这样的概念,毕竟Go是原来C的那帮人搞的,兼顾C的同时也结合不少Python这样的现代语言的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import "fmt"

type Node struct {
data int
leftNode *Node
rightNode *Node
}

func insert(tree **Node, value int) {
if *tree == nil {
var tempNode Node
tempNode.data = value
*tree = &tempNode
return
}

if (*tree).data > value {
insert(&(*tree).leftNode, value)
} else if (*tree).data < value {
insert(&(*tree).rightNode, value)
}
}

func perOrder(tree *Node) {
if tree != nil {
fmt.Printf("data: %d\n", tree.data)
perOrder(tree.leftNode)
perOrder(tree.rightNode)
}
}

func main() {
var tree *Node

insert(&tree, 9)
insert(&tree, 4)
insert(&tree, 15)
insert(&tree, 6)
insert(&tree, 12)
insert(&tree, 17)
insert(&tree, 2)

perOrder(tree)
}