go结构化日志库logrus

2019年3月5日 没有评论

日志无论在开发还是生产当中,都是很重要的部分,帮助我们快速定位和发现问题。logrus是一个可以格式化存储日志的golang库,而且它还兼容标准库中的logger。logrus目前在github的start数量已经快达到10k,可谓是一个非常受欢迎的golang第三方库了。

logrus特性

  • 完全兼容golang标准库日志模块:logrus拥有六种日志级别:debug、info、warn、error、fatal和panic,这是golang标准库日志模块的API的超集。如果您的项目使用标准库日志模块,完全可以以最低的代价迁移到logrus上。
  • 可扩展的Hook机制:允许使用者通过hook的方式将日志分发到任意地方,如本地文件系统、标准输出、logstash、elasticsearch或者mq等,或者通过hook定义日志内容和格式等。
  • 可选的日志输出格式:logrus内置了两种日志格式,JSONFormatter和TextFormatter,如果这两个格式不满足需求,可以自己动手实现接口Formatter,来定义自己的日志格式。
  • Field机制:logrus鼓励通过Field机制进行精细化的、结构化的日志记录,而不是通过冗长的消息来记录日志。
  • logrus是一个可插拔的、结构化的日志框架。

最简例子

下面是一个最简单的logrus例子:

package main

import (
	log "github.com/sirupsen/logrus"
)

func main() {
	
	log.Info("hello log")
}

分隔日志

分隔日志使用了logrus提供的hook机制,然后利用rotatelogs实现这些功能。

package main

import (
        "path"
	"time"
	"github.com/lestrrat-go/file-rotatelogs"
	"github.com/pkg/errors"
	"github.com/rifflock/lfshook"

	log "github.com/sirupsen/logrus"
)

func ConfigLocalFilesystemLogger(logPath string, logFileName string, maxAge time.Duration) {
	baseLogPaht := path.Join(logPath, logFileName)
	writer, err := rotatelogs.New(
		baseLogPaht+".%Y%m%d.log",
		rotatelogs.WithLinkName(baseLogPaht),      // 生成软链,指向最新日志文件
		rotatelogs.WithMaxAge(maxAge),             // 文件最大保存时间
		rotatelogs.WithRotationTime(time.Hour*24), // 日志切割时间间隔
	)
	if err != nil {
		log.Errorf("config local file system logger error. %+v", errors.WithStack(err))
	}
	lfHook := lfshook.NewHook(lfshook.WriterMap{
		log.DebugLevel: writer, // 为不同级别设置不同的输出目的
		log.InfoLevel:  writer,
		log.WarnLevel:  writer,
		log.ErrorLevel: writer,
		log.FatalLevel: writer,
		log.PanicLevel: writer,
	}, &log.JSONFormatter{
		TimestampFormat:"2006-01-02 15:04:05",
	})
	log.AddHook(lfHook)
}

func main() {
	//log.SetOutput(os.Stdout)
	ConfigLocalFilesystemLogger("./log/", "simple", time.Hour*24*60)
	log.Info("hello log")
	log.WithFields(log.Fields{
		"animal": "walrus",
		"size":   10,
	}).Info("A group of walrus emerges from the ocean")
}

参考资料:

《golang日志框架logrus》

分类: golang 标签:

angular组件数据双向绑定

2019年3月4日 没有评论

周末的时候给网站做一个后台,前端用的是阿里的antd的angular版本。其中有用到他们的上传组件,由于有多个地方使用到上传,就把它封装成了一个通用图片上传组件。这时候服务端返回的图片链接地址,如何返回给父组件里面的表单里面哪?

通用上传组件

下面是我图片上传组件的实现:

image-upload/image-upload.component.html

<nz-upload class="avatar-uploader"
           nzAction="{{serverUrl}}"
           nzName="avatar"
           nzListType="picture-card"
           [nzShowUploadList]="false"
           [nzHeaders]="headers"
           [nzBeforeUpload]="beforeUpload"
           (nzChange)="handleChange($event)">
  <ng-container *ngIf="!previewUrl">
    <div class="ant-upload-text">Upload</div>
  </ng-container>
  <img *ngIf="previewUrl" [src]="previewUrl" class="avatar">
</nz-upload>

image-upload/image-upload.component.ts

import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {BACKEND_BASE_HOST, UPLOAD_IMAGE_API} from '../../services/Apis.namespace';
import { NzMessageService, UploadFile } from 'ng-zorro-antd';
import {LocalStorageService} from '../../services/local-storage.service';

@Component({
  selector: 'app-image-upload',
  templateUrl: './image-upload.component.html',
  styleUrls: ['./image-upload.component.css']
})
export class ImageUploadComponent implements OnInit {
  @Input() uploadUrl: string;
  @Output() uploadUrlChange = new EventEmitter();

  previewUrl: string;
  serverUrl: string;
  loading: boolean;
  headers: {};

  constructor(private msg: NzMessageService,
              private store: LocalStorageService) { }

  ngOnInit() {
    console.log(UPLOAD_IMAGE_API);
    this.serverUrl = UPLOAD_IMAGE_API;
    this.loading = false;
    this.headers = {'Authorization': 'Bearer ' + this.store.get('user.token')};
    this.previewUrl = this.uploadUrl;
  }

  beforeUpload() {

  }

  handleChange(info: { file: UploadFile }): void {
    console.log('info: ' + info);
    switch (info.file.status) {
      case 'uploading':
        this.loading = true;
        break;
      case 'done':
        if (info.file.response.error === '200') {
          this.previewUrl = BACKEND_BASE_HOST + info.file.response.data;
          this.uploadUrlChange.emit(info.file.response.data);
        }
        this.loading = false;

        break;
      case 'error':
        this.msg.error('Network error');
        this.loading = false;
        break;
    }
  }
}

这里贴了一下代码的实现,原理可以查看官方的相关文档。我写的时候也参考了《Angular:实现组件间双向数据绑定》

一致性哈希在golang里面的hash

2019年2月28日 没有评论

一致性哈希也不是什么新东西,我第一次看到应该是2年前看《大型网站技术架构:核心原理与案例分析》的时候,不过除了面试的时候其他时候机会没有遇到过和一致性哈希相关的内容,然后慢慢的就是书的知识,我又还给了书。我有时候觉得有些知识不用代码敲出来,自己好像还是不会一样,所以今天就动手把别人一致性哈希golang的实现搬过来。在把代码复制拷贝一份,认认真真的看了以后,感觉一致性哈希也就这样(当然有空还是要好好补补高数)。

讲一致性哈希的资料很多,随便用google搜索一下,高质量的答案很多。我如果再说感觉意义也不是很大,如果你有幸读到这篇文章,我在结尾也会放一些相关的资料链接。

Golang实现

package main

import (
	"fmt"
	"hash/crc32"
	"sort"
	"strconv"
)

type UInt32Slice []uint32

func (s UInt32Slice) Len() int {
	return len(s)
}

func (s UInt32Slice) Less(i, j int) bool {
	return s[i] < s[j]
}

func (s UInt32Slice) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

type Hash func(data []byte) uint32

type Map struct {
	hash     Hash
	replicas int               // 复制因子
	keys     UInt32Slice       // 已排序的节点哈希切片
	hashMap  map[uint32]string // 节点哈希和KEY的map,键是哈希值,值是节点Key
}

func New(replicas int, fn Hash) *Map {
	m := &Map{
		replicas: replicas,
		hash:     fn,
		hashMap:  make(map[uint32]string),
	}
	// 默认使用CRC32算法
	if m.hash == nil {
		m.hash = crc32.ChecksumIEEE
	}
	return m
}

func (m *Map) IsEmpty() bool {
	return len(m.keys) == 0
}

// Add 方法用来添加缓存节点,参数为节点key,比如使用IP
func (m *Map) Add(keys ...string) {
	for _, key := range keys {
		// 结合复制因子计算所有虚拟节点的hash值,并存入m.keys中,同时在m.hashMap中保存哈希值和key的映射
		for i := 0; i < m.replicas; i++ {
			hash := m.hash([]byte(strconv.Itoa(i) + key))
			m.keys = append(m.keys, hash)
			m.hashMap[hash] = key
		}
	}

	// 对所有虚拟节点的哈希值进行排序,方便之后进行二分查找
	sort.Sort(m.keys)
}

// Get 方法根据给定的对象获取最靠近它的那个节点key
func (m *Map) Get(key string) string {
	if m.IsEmpty() {
		return ""
	}

	hash := m.hash([]byte(key))

	// 通过二分查找获取最优节点,第一个节点hash值大于对象hash值的就是最优节点
	idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash })

	// 如果查找结果大于节点哈希数组的最大索引,表示此时该对象哈希值位于最后一个节点之后,那么放入第一个节点中
	if idx == len(m.keys) {
		idx = 0
	}

	return m.hashMap[m.keys[idx]]
}

func main() {
	unique_hash := New(100, nil)
	unique_hash.Add("127.0.0.1", "192.168.1.2", "196.168.1.3")

	fmt.Println(unique_hash.Get("king"))
	fmt.Println(unique_hash.Get("niu"))
	fmt.Println(unique_hash.Get("fire"))
	fmt.Println(unique_hash.Get("water"))
	fmt.Println(unique_hash.Get("war"))
	fmt.Println(unique_hash.Get("war2"))
	fmt.Println(unique_hash.Get("war1"))
	fmt.Println(unique_hash.Get("war2"))
	fmt.Println(unique_hash.Get("women"))
	fmt.Println(unique_hash.Get("man"))
	fmt.Println(unique_hash.Get("body:1"))
}

然后你会发现当你把虚拟节点设置的越大的时候,key的分布就会越平均,当值很小的时候分布的就很不均匀了。当然这也和我们测试的key的数量有关,我就放了这几个,如果你放的越多,它可能就越平均了。

参考资料:

《使用Go实现一致性哈希》

《一致性哈希算法的理解与实践》

golang冒泡排序的实现

2019年2月28日 没有评论

冒泡排序应该是我们程序员接触的第一个算法了吧。前几天去面试,考了一下冒泡排序,长时间不用,只记得个大概,就是相邻的2个元素比较大小,然后互换,具体的记不清了。然后很紧张,就没写出来。今天趁着有时间就用代码敲了一遍,当做笔记。

冒泡排序原理

  1. 比较相邻的元素。 如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。 在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

Golang实现

package main

import "fmt"

func main() {
	sort_array := [10]int{10,7,25,99,1,8,50,85,26,12}
	array_len := len(sort_array)
	for pos:=array_len-1; pos >= 0; pos--  {
		for i :=0; i < pos;  i++ {
			if sort_array[i] > sort_array[i+1] {
				tmp := sort_array[i+1]
				sort_array[i+1] = sort_array[i]
				sort_array[i] = tmp
			}
		}
	}
	fmt.Println(sort_array)
}

 

分类: golang, 算法 标签:

搭建小型团队Wiki系统-gollum

2019年2月19日 没有评论

这两天在考虑给我们4个人的小团队使用什么好的知识共享软件?第一个进入我的视野就是大名鼎鼎的confluence,无论从哪个角度考虑,confluence都是首选,而且我也装了,不过由于云服务器配置实在是太差,做一个操作就要卡上半天(cpu跑满了)。因此无奈,confluence被我pass掉了!接下来我考虑了一下Doku,这是一个php开发的小型wiki系统,其实它很不错,各方面都不错,而且插件也比较丰富。但是它有自己的一套语法系统,使用插件的话可以支持Markdown,但是相对比较麻烦一些,主要还是内心对它并不是特别喜欢吧,也被我pass掉了。最后进入我的视野的gollum(咕噜),这gollum和指环王中的是同一个词,不知道当初起名时,是不是作者比较喜欢电影中的咕噜。gollum是ruby开发的一套wiki系统,它支持Markdown语法、轻量级、结构清晰,看起来是不错的选择。

Docker安装Gollum

首先这里假设你已经成功安装docker了,如果你还没有安装,可以自行搜索一下资料,还是很简单的,这里就不再叙说了。

Dockerfile文件

FROM ruby
RUN apt-get -y update && apt-get -y install libicu-dev cmake && rm -rf /var/lib/apt/lists/*
RUN gem install github-linguist
RUN gem install gollum-rugged_adapter
RUN gem install gollum
RUN gem install org-ruby  # optional
WORKDIR /wiki
ENTRYPOINT ["gollum", "--port", "80", "--adapter", "rugged"]
EXPOSE 80

Docker-compose文件

version: "2"

services:
  gamemodr_wiki:
    build: ./gollum
    volumes:
      - /data/wiki/gamemodr:/wiki
    expose:
      - 80
    ports:
      - 8888:80

运行”docker-compose up -d “就可以让容器服务运行了,注意挂载的”/data/wiki/gamemod”目录需要是一个git初始化过的目录。

如果需要在线编辑,可以用nginx做一个反向代理,然后加一个http用户认证,当然权限部分就没办法了。如果不需要在线编辑,可以去掉gollum在线编辑功能,然后和类似jenkins集成工具整合,也是不错的选择。

分类: Docker, 随笔 标签: ,

c/c++嵌入lua

2018年11月21日 没有评论

最近看了原网易游戏引擎架构师蔡能老师一些游戏相关的文章,其中有提到在游戏中常用的脚本语言lua和C/C++之间相互调用。lua在游戏中有很多运用,耳熟能言的有《魔兽世界》,魔兽中UI和很多插件都是用lua来写。在服务端也有很多运用,nginx有支持lua语言的模块,具体可以看一下openresty这款阿里改写nginx服务器,它集成nginx lua模块,书籍可以翻阅《OpenResty-Best-Practices》。

嵌入lua脚本

在C\C++里面调用lua脚本通常有二种做法:一是读取后直接运行,调用luaL_dofile函数;还有一种方式是用luaL_loadfile函数将脚本压到栈顶,手动调用pcall运行脚本

下面图片给出了lua堆栈的示意图,方面我们理解下面的代码。

Lua调用C函数

有时候我们会把需要运行效率的模块用C/C++来使用,然后封装以后交给lua来调用。这时候我们要用lua_register将函数注册到lua虚拟机里面,然后我们就可以在lua脚本里面调用这个函数了。lua_register是一个宏函数,如下:

#define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n)))

第1个参数L是虚拟机指针,第2个参数是注册到虚拟中的函数名称,第3个参数是一个函数指针,格式为”int ()(lua_State*)” .下面我们来看一下具体的实现。

代码实现

这些代码能运行的前提是你的电脑已经安装了lua,具体如果安装lua请自行搜索一下。

main.cpp代码

#include <iostream>

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

//lua_register函数的第3个参数需要的是int ()(lua_State*)类型的函数指针
int c_sum(lua_State* l)
{
    long long result;
    const long long num1 = lua_tointeger(l, 1);
    const long long num2 = lua_tointeger(l, 2);
    result = num1 + num2;
    std::cout<< num1 << "+" << num2 << "=" << result << std::endl;
    lua_pushinteger(l, result);
    return 1;
}

int main(int argc, char **argv) {
    int r;
    const  char* err;
    lua_State* ls;

    ls = luaL_newstate();
    luaL_openlibs(ls);
    //注册c_sum函数到lua虚拟机,名称改为my_sum
    lua_register(ls, "my_sum", &c_sum);
    //加载lua脚本
    r = luaL_loadfile(ls, argv[1]);
    if (r) {
        err = lua_tostring(ls, -1);
        std::cout<< "lua err1: " << err << std::endl;
        return 1;
    }
    //运行lua脚本
    r = lua_pcall(ls, 0, 0, 0);
    if (r) {
        err = lua_tostring(ls, -1);
        if (err) {
            std::cout << "lua err2: " << err << std::endl;
        }
        return  2;
    }
    lua_close(ls);
    return 0;
}

测试脚本test.lua脚本

print("test lua running")
result=my_sum(5,8)
print("sum result: " .. result)

编译脚本,然后运行程序,将脚本名称作为第一个参数传入。结果如下:

#./use_lua test.lua
test lua running
5+8=13
sum result: 13

这里我提供一下我代码的示例,我用ide用的是clion。源码下载,use_lua.tar

分类: C/C++, Lua 标签:

alfred自定义搜索命令

2018年9月25日 没有评论

如果你是一个mac党,而且正在Alfred,那么这篇简短的文章应该可以帮到你。如果你是Alfred新手,还不清楚它是干什么的,那你最好去Alfred官网下载一个,然后自己动手试试,你可能就会爱上它。这里假设你是和我一样用过一段时间Alfred的”菜鸟”,希望它来帮助我们能来更好提高工作效率。那么Alfred提供的自定义搜索功能就不得不提了,欲知后事如何,请继续往下看哈!

那么我们就直奔主题,如何自定义搜索命令哪?我们先以google翻译的中英翻译为例来说一说吧!

自定义Alfred搜索命令

首先我们要自定义命令,需要去Preferences->Features->Web Search->Add Custom Search来添加自定义搜索名称,如下图:

自定义Google中英翻译命令

我们想要使用web搜索功能,那么我们清楚网站提供什么样的web API给我们调用,这里面的google中英翻译的API大致是这样的”https://translate.google.com/#zh-CN/en/{query}”,其中的{query}就是我们在通过Alfred搜索时提供的第二个参数,也就是我们搜索的内容了。具体的设置如下图:

其中的Search Url我们上面已经提到了,下面的”Encode input as UTF8″就是会把我们的输入参数按UTF8编码处理,后面的”Encode spaces as”是空格你希望转化成什么格式”%20″或者”+”,根据提供API的网站自行设置即可。Keyword就是我们在Alfred输入时激活命令了,Validation后面的内容是你测试用的,可以自己设定,然后保存就完成了。

最终成果

来看看我们最终的劳动成果是什么样子的吧!按”alt+空格”(默认)激活Alfred,然后输入上面的Keyword中的命令,如果得到类似下面的结果,恭喜你!

 

分类: Mac 标签:

C语言中p[j]和j[p]

2018年8月30日 没有评论

首页我们来看一段有关C语言指针相关的代码,代码不是很规范,但是可以表明问题。

#include <stdio.h>

int main() {
    char *p;
    int i=1;
    p = &i;
    *(p+1) = 2;
    p[2] = 3;
    for (int j = 0; j < 3; ++j) {
        printf("p[%d]: %d \n", j, p[j]);
        printf("%d[p]: %d \n", j, j[p]);
    }
    return 0;
}

那么这里”p[j]”和”j[p]”数据结果会一样吗?

是的,他们的结果完全一样,下面是上面代码的运行结果。

p[0]: 1 
0[p]: 1 
p[1]: 2 
1[p]: 2 
p[2]: 3 
2[p]: 3 

大多数的C语言书籍里面,都会把p[j]说成数组p中的第j个元素,虽然这种说法也没有错,其实p[j]等价于*(p+j)的。这也是我上面示例中赋值时使用2中方法的原因。“加法运算可以交换顺序,所以将*(p + i)写成*(i + p) 也是可以的。同理,将p[i]写成i[p]也是可以的(可能你会不相信,但这样写既不会出错,也能正常运行)。摘录来自: 川合秀实. “30天自制操作系统”。

分类: C/C++ 标签:

elasticsearch自定义ik词库

2018年8月7日 没有评论

最近一直在研究使用elasticsearch,es默认的分析器对中文只能提供一个一个汉字的分词,这对搜索有些要求的应用或项目来说有些差强人意的。所以很多人选择国人开发的分词插件ik来选择中文默认的分词器,插件在github上面的地址是medcl/elasticsearch-analysis-ik

安装ik

官方提供了2中方式来安装ik,下载稳定的发布包,或者通过”elasticsearch-plugin”来安装。elasticsearch-plugin安装是很方便,不过可能会有一些问题要解决“java.security.AccessControlException: access denied”问题,在最新版的6.3版本中,加入了”plugin-security.policy”来解决这个文。如果有AccessControlException问题的,查看《elasticsearch ik socket access denied》来解决你的问题。

plugin安装命令

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

 发布包方式

cd {your-es-root}/plugins/ && mkdir ik
cp -r elasticsearch-analysis-ik-* {your-es-root}/plugins/ik

这里的”{your-es-root}”是你es的根目录,”elasticsearch-analysis-ik-*”表示你解压以后的ik插件目录。

可以通过访问”http://localhost:9200/_cat/plugins?pretty”来查看你的ik是否安装成功。

自定义分词

ik的配置文件IKAnalyzer.cfg.xml可以在 ” {conf}/analysis-ik/config/IKAnalyzer.cfg.xml" 或者 “{plugins}/elasticsearch-analysis-ik-*/config/IKAnalyzer.cfg.xml"。如果你没有找到试试用find命令来查找。

<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<!-- <entry key="ext_dict">custom/custom.dic</entry> -->
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">http://xxxxx/ik/custom.dic</entry>
	<!--用户可以在这里配置远程扩展停止词字典-->
	<!-- <entry key="remote_ext_stopwords">http://xxxxxx/ik/stopwords.dic</entry> -->
</properties>

这是IKAnalyzer.cfg.xml的文件格式,我们要自定义分词,把”ext_dict”设置成我们分词文件所在的目录就可以。分词文件的格式是一行一个分词,也就是用”\n”分隔,和下面要介绍的热加载分词格式一样。然后重启es,如果没有错误发生,那么自定义分词就生效了。但是这种方式如果我们想要修改添加分词每次必须要重启es。

热加载自定义分词

通过”remote_ext_dict”我们可以配置热加载的分词词库,这是一个url地址,它大概会每隔60秒来检查一下地址,如果有变化就加载新的词库。

1、该 http 请求需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,这两者都是字符串类型,只要有一个发生变化,该插件就会去抓取新的分词进而更新词库。

2、该 http 请求返回的内容格式是一行一个分词,换行符用 \n 即可。

把词库生成到nginx服务器指定路径下面即可。注意修改完配置文件以后要生效也需要重启。

elasticsearch ik socket access denied

2018年8月7日 没有评论

最近在使用elasticsearch,然后中分词选择使用ik,ik支持热加载分词,但是配置的时候总是失败。没有撸过java代码,所有对java在socket连接时居然还需要权限验证还是蛮吃惊的。我当初是用elasticsearch-plugin安装的,如果使用ik github上面提供的稳定版的发布包来手动安装就不会有这个问题,里面的”plugin-security.policy”已经包含了解决方案。

在启动es时,日志文件里面有如下错误信息。

[2018-08-07T14:52:26,004][WARN ][o.e.g.Gateway            ] [75Cwi4-] recovering index [index/Tmk_kfl-SqqeSSPx7vjXQw] failed - recovering as closed
java.security.AccessControlException: access denied ("java.net.SocketPermission" "120.77.217.214:80" "connect,resolve")
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) ~[?:1.8.0_65]
	at java.security.AccessController.checkPermission(AccessController.java:884) ~[?:1.8.0_65]
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) ~[?:1.8.0_65]
	at java.lang.SecurityManager.checkConnect(SecurityManager.java:1051) ~[?:1.8.0_65]
	at java.net.Socket.connect(Socket.java:584) ~[?:1.8.0_65]
	at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:74) ~[?:?]
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141) ~[?:?]
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353) ~[?:?]
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380) ~[?:?]
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) ~[?:?]
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184) ~[?:?]
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88) ~[?:?]
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[?:?]
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184) ~[?:?]
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82) ~[?:?]
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107) ~[?:?]
	at org.wltea.analyzer.dic.Dictionary.getRemoteWords(Dictionary.java:470) ~[?:?]
	at org.wltea.analyzer.dic.Dictionary.loadRemoteExtDict(Dictionary.java:439) ~[?:?]
	at org.wltea.analyzer.dic.Dictionary.loadMainDict(Dictionary.java:380) ~[?:?]
	

这个错误是由java安全管理器SecurityManager引起的,相关的信息可以查看一下这篇《java安全管理器SecurityManager入门》。由于我对这方面的知识了解不多,就不再展开了。解决这个问题的思路是我们在SecurityManager配置文件里面允许这些socket连接。找到”java.policy”文件,我在mac上面的位置在”/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/security/java.policy”,当然如果你是windows或者linux系统,可以搜索java.policy文件,注意不要修改成其他软件自带的jre中的配置文件。

grant {
    ...
    //默认配置省略
    permission java.net.SocketPermission "*", "connect,resolve";
}

在配置文件最后增加上面的权限相关的最后2行内容,然后重启es,一切就ok了!