之前使用ES还是在其1.4.x~2.0.x左右的时候,现在 ES 版本已经发展到了7.x以上了(中间经历过于kibana同步版本号,所以版本变动比较大)
大致安装步骤和之前的版本差别不大,依然保持了其开箱即用的特性,只需要很少的配置即可
单机很简单,修改集群名称,节点名称,数据及日志目录,以及监听绑定的ip,然后直接运行可执行文件即可
1 | 注:下载的打包文件中,附带一个openjdk可以直接使用,如果使用 supervisor 来管理 es, 可以使用 |
单机没什么问题,很顺畅的就启动起来了,通过 curl http://ip:es_port 可以得到很熟悉的返回:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"name" : "node1",
"cluster_name" : "cluster-name",
"cluster_uuid" : "2c2jICSxTHK_jUcJjaBucw",
"version" : {
"number" : "7.8.1",
"build_flavor" : "default",
"build_type" : "tar",
"build_hash" : "b5ca9c58fb664ca8bf9e4057fc229b3396bf3a89",
"build_date" : "2020-07-21T16:40:44.668009Z",
"build_snapshot" : false,
"lucene_version" : "8.5.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
既然单机的起来的,那如法炮制,将配置文件复制一份,修改了其 node name, scp到另外一台机器上,准备好 supervisor 配置,准备开始组建两个节点组成的集群
满以为不会有什么问题,启动之后,发现,第二个节点,怎么也加入不到第一个节点之中
问题出现了,看了看文档,发现自 es7.x版本以后,更新换代了一个集群协调的功能工具 zen, 简化了很多配置项,组建集群时,
基本上就需要配置两个:
discovery.seed_hosts, cluster.initial_master_nodes
seed_hosts: 启动当前节点时,用于发现其他节点的初始列表
initial_master_nodes: 初始的候选master节点列表
最开始的时候,这两个均配置了两台机器的ip,即[node1,node2],分别启动节点后,发现后启动的节点,怎么也加入不到之前启动的节点中,组成集群
两个节点,脑裂了~~~~
网上搜了很多解决方案,绝大多数是说不该监听0或者0.0.0.0,遂调整配置,再次启动,发现依然无法解决两个节点脑裂问题
后来仔细研读了下,文档中对 initial_master_nodes 的说明,发现了问题:
initial_master_nodes 中配置了当前节点,即意味着,当前节点也是可以候选的可用的master节点,但其实,当前节点都还没启动,它是没有资格去竞选成为master的,所以导致第二个节点启动后,自己独立成群,无法与已有节点组成集群
问题找到了,修改第二个节点的配置文件,initial_master_nodes 仅指定第一个节点为可用的候选 master节点
随后启动节点二,启动正常,节点二和节点1成功组成一个集群
]]>后来,将 network.host 改为监听 0.0.0.0,两者依然可以组成集群,可见,网传的不能监听0.0.0.0的说法是不成立的
git
项目中,可以使用以下命令为项目添加submodule
1 | git submodule add -b branch_name git@github.com:javasgl/xxx.git module_path |
执行以上命令后,会在当前项目下生成一个.gitmodules
文件,内容如下
1 | [submodule "module_path"] |
同时也将修改 .git/config
文件1
2
3[submodule "module_path"]
url = git@github.com:javasgl/xxx.git
active = true
此时,执行以下命令即可从远程拉取相关代码到submodule
中:1
2
3git submodule update --init
// 可以使用 git submodule deinit 重新初始化
执行之后,会在 .git/modules/module_path/
下看到 submodeule
相关的代码
1 | .git/modules |
有时候,对于已经添加了submodule的项目,如果想更换 submodule
的地址,那个这就比较麻烦,目前git
并没有直接提供相关命令,需要进行一系列的手工操作
简单记录如下,备查
比如,想把 submodule
的地址换为 github.com/xxx/xxx.git
将 submodule
移除 git
版本控制
1 | git rm module_path |
移除 .git/config
.gitmodules
中的配置
删除 .git/modules/
下相关代码
1 | rm .git/modules/module_path |
执行之前的初始化,可以重新add
新的submodule
1 | git submodule add github.com/xxx/xxx.git xxx_path |
这些问题不是独立的,一般都会存在因果关系或者同时存在
下面列举一下问题原因及基本解决思路
缓存穿透问题是指请求数据在缓存中没有找到,导致请求直接打到数据库的情况
原因 1 的解决方案:
为数据设置默认值,即使从数据库没有查询到,也设置缓存,之后数据有值了去主动更新缓存
这样虽然可以避免查询一直不存在的数据而导致的缓存穿透问题,但是也容易引起恶意攻击,攻击方式:构造大量无效的条件去查询,这样导致缓存中大量的存在默认缓存,导致缓存占用量急剧增大而引起缓存驱逐,针对这种方式,可以为默认缓存设置一些比较短的过期时间来稍微缓解这一问题,只是缓解,无法完全解决这个攻击
原因 2 的解决方案:
不要设置统一的过期时间,为过期时间添加随机数,让数据过期时间分散开,不要集中在同一时间过期
缓存并发问题是指多个请求请求同一个数据,而且都没有从缓存中获取到数据,并从数据库查询后都去更新缓存,导致缓存重复更新问题
大多数场景下,这个问题影响并不大,只是会产生一些重复的数据库查询和重复更新缓存
但是如果有些缓存不能重复更新,比如一些自动设置的时间、计数器等,那么重复更新会带来问题
这个问题的解决方案:
使用
CAS
方案,即设置缓存之前先检查是否已经设置了,如果设置过,则不作任何操作
如果是使用redis
,则可以直接使用其SETNX
命令
绝大多数系统中,频繁访问的数据只是一小部分,而受硬件、成本等因素,把所有的数据都放在缓存中也不太现实
这个问题,可以使用日志分析、访问计数等方式统计热点数据,基于历史访问情况刷新
TOP-N
数据到缓存中,并且不定时的去更新这个TOP-N
列表
这个问题一般比较难弄,只要使用缓存,则必然可能出现缓存不一致的问题,这个问题和使用缓存更新策略有很大的关系,具体问题需要根据不同的缓存更新方式来定制化解决,不能一概而论
]]>这个问题基本思路是使用补偿机制,即使用定时、不定时巡检的方式,刷新缓存数据,保证数据一致性
Generated Columns 定义方式如下:1
2
3
4col_name data_type [GENERATED ALWAYS] AS (expr)
[VIRTUAL | STORED] [NOT NULL | NULL]
[UNIQUE [KEY]] [[PRIMARY] KEY]
[COMMENT 'string']
注:
默认是 VIRTUAL,即 虚拟列,不实际占用存储空间,在读取数据时即时计算而来;
STORED 则与之相反,它会生成真实的数据列,和正常的列无异,占用存储空间;
两者在限制上各有细微区别,具体可查看文末 mysql/mariadb 链接阅读详情
以下样例,展示了自动生成指定时间字段的月份:c_month
的值通过对c_time
进行内置函数month
调用自动得到,插入语句中无需指定c_month
的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22mysql> CREATE TABLE generated_columns_test (c_time datetime,c_month int generated always as(month(c_time)));
Query OK, 0 rows affected (0.19 sec)
mysql> desc generated_columns_test;
+---------+----------+------+-----+---------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+---------+----------+------+-----+---------+-------------------+
| c_time | datetime | YES | | NULL | |
| c_month | int(11) | YES | | NULL | VIRTUAL GENERATED |
+---------+----------+------+-----+---------+-------------------+
2 rows in set (0.01 sec)
mysql> insert into generated_columns_test(c_time)values (now());
Query OK, 1 row affected (0.00 sec)
mysql> select * from generated_columns_test;
+---------------------+---------+
| c_time | c_month |
+---------------------+---------+
| 2020-02-18 09:33:42 | 2 |
+---------------------+---------+
1 row in set (0.00 sec)
是不是很熟悉?以前这样的需求,一般需要在程序中处理好月份后和 c_time
一并插入数据库,是 generated columns
则可以做自动生成,将程序逻辑后移到数据库层面了。
可以设想一下,generated columns
可用于哪些场景?
当然,generated columns
并非银弹,不是所有的业务逻辑都适合运用它,不应该把本该程序处理的部分业务交给数据库去处理,这将给系统维护带来隐患,这和不推荐使用存储过程和自定义函数一样的道理
总体来说,generated columns
虽然有诸多限制,但总体上使用起来还是很方便的,具体的文档可以查看:
open too many files
, 竟然提示打开文件数过多。使用ulimit -n
查看,最大的文件数限制为 256 ,这明显太小了。 使用launchctl limit
查看,结果如下:
1 | ~/codes/backupAnyThing/blog(master*) » launchctl limit |
解决方案大致上有三种:
命令行中执行 ulimit -n xxx
, 但是仅对当前 session 生效,新开 session 或者重启后会恢复为系统值
在 .bashrc / .zshrc 等脚本中加上 ulimit -n xxxx
, 这样每次新开 session 即可自动设置最大文件描述符显示
加入到 launchd 服务中, 永久修改
这里记录下第三种方案的步骤
创建两个 plist文件,民间名分别为 limit.maxproc.plist
, limit.maxfiles.plist
, 内容如下:
file: limit.maxfiles.plist1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20"1.0" encoding="UTF-8" xml version=
<plist version="1.0">
<dict>
<key>Label</key>
<string>limit.maxfiles</string>
<key>ProgramArguments</key>
<array>
<string>launchctl</string>
<string>limit</string>
<string>maxfiles</string>
<string>200000</string>
<string>200000</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>ServiceIPC</key>
<false/>
</dict>
</plist>
file: limit.maxproc.plist
1 | "1.0" encoding="UTF-8" xml version= |
将以上两个文件移动到 /Library/LaunchDaemons
目录下,确保两个文件的权限如下:
1 | -rw-r--r-- 1 root wheel 592B Sep 29 16:39 limit.maxfiles.plist |
重启系统,重启后自动生效。
注: 如果只需要修改 max files
, 仅添加 limit.maxfiles.plist
文件即可。
MVVM
的思维模式下,很容易就想到使用数组的添加和删除来实现DOM元素的动态添加和删除。首先定义一个空数组,push
几条初始数据(或者在相关按钮的click
事件中去对这个数组进行增删),然后在页面上使用ng-repeat
和ng-model
在循环中绑定这个数组的数据。1
2
3
4
5
6item= {
name:'default value'
}
$scope.items= []
$scope.items.push(item)
$scope.items.push(item)
html页面如下:1
2
3<div ng-repeat="item in items track by $index">
<input type="text" ng-model="item.name">
</div>
以上代码看似没有问题,但是实际结果却并不预期。页面渲染完成后,修改两个input
中的任何一个,数组中的两个元素的数据都保持了一致变动。尝试了一下几种方案之后均无法实现单独绑定:1
2
3
4
5
6
7
8
9方案1:
<div ng-repeat="item in items track by $index">
<input type="text" ng-model="items[$index].name">
</div>
方案2:
<div ng-repeat="item in items track by $index">
<input type="text" ng-model="$parent.items[$index].name">
</div>
后来怀疑是代码中push(item)
的问题,每次push
的时候,添加的都是同一个item
,这样数组中的每一个元素引用的都是内存中的同一个item
的地址,所以修改任何元素,都会影响内存中的同一个item
对象,从而导致所有的数组元素的值都同步一致修改了。
基于以上思路,产生了两种解决方案:
方案一,每次push
的时候,添加一个空对象到数组中,这样避免每个元素引用同一个内存对象1
2
3
4$scope.items.push({})
$scope.items.push({})
$scope.items.push({})
......
方案二,每次push
的时候,使用 angular.copy
重新复制出一个新对象出来添加到数组中1
2
3
4
5
6
7item= {
name:'default value'
}
$scope.items.push(angular.copy(item))
$scope.items.push(angular.copy(item))
$scope.items.push(angular.copy(item))
......
官方文档在此:https://golang.org/pkg/plugin/
编写一个 Plugin 基本有以下几步:
1.Plguin 需要有自己的 main package
2.编译的时候,使用 go build -buildmode=plugin file.go
来编译
3.使用 plugin.Open(path string)
来打开.so文件,同一插件只能打开一次,重复打开会报错
4.使用 plugin.LookUp(name string)
来获取插件中对外暴露的方法或者类型
5.使用类型断言,断言后执行相应的方法
main.go1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("plugin.so")
if err != nil {
panic(err)
}
m, err := p.Lookup("GetProductBasePrice")
if err != nil {
panic(err)
}
res := m.(func(int) int)(30)
fmt.Println(res)
}
plugin.go1
2
3
4
5
6
7
8
9
10
11
12package main
import "fmt"
func main() {
}
func GetProductBasePrice(basePrice int) int {
return basePrice + 200
}
注意几点问题:
下面这段注释来源于pool.go
:1
2
3
4
5
6
7
8
9
10
11
12
13// A Pool is a set of temporary objects that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// A Pool is safe for use by multiple goroutines simultaneously.
//
// Pool's purpose is to cache allocated but unused items for later reuse,
// relieving pressure on the garbage collector. That is, it makes it easy to
// build efficient, thread-safe free lists. However, it is not suitable for all
// free lists.
让我们用代码验证一下 sync.Pool中对象的回收时机: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
26package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
p := &sync.Pool{
New: func() interface{} {
return 0
},
}
a := p.Get().(int)
p.Put(1)
b := p.Get().(int)
fmt.Println(a, b)
a = p.Get().(int)
p.Put(1)
runtime.GC() //手动调用GC
b = p.Get().(int)
fmt.Println(a, b)
}
执行结果分别打印出了:
0 10 0
可见,在手动调用GC之后,Pool中的对象被全部清除了,在Get的时候重新调用定义的New方法创建了新的对象
]]>使用起来非常简单,定义一个Service,实现其 svc.Service中的接口即可。
1 | package main |
1.Start方法中不能只直接阻塞,需要在Start方法中新开goroutine去写需要阻塞的代码。
2.svc.Run()方法的第二个参数可以指定需要程序监听的信号,默认情况下不指定的话,默认会监听 SIGINT和 SIGTERM两个。根据具体需要进行指定,比如本例中还监听了SIGUSR1.
其实go-svc代码实现很简单,使用了标准库中的os/signal
的Notify
方法。
下面的代码截取于judwhite/go-svc/svc/svc-other.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// Run runs your Service.
//
// Run will block until one of the signals specified in sig is received.
// If sig is empty syscall.SIGINT and syscall.SIGTERM are used by default.
func Run(service Service, sig ...os.Signal) error {
env := environment{}
if err := service.Init(env); err != nil {
return err
}
if err := service.Start(); err != nil {
return err
}
if len(sig) == 0 {
sig = []os.Signal{syscall.SIGINT, syscall.SIGTERM}
}
signalChan := make(chan os.Signal, 1)
signalNotify(signalChan, sig...)
//signalNotify方法其实就是 signal.Notify 方法
//var signalNotify = signal.Notify
<-signalChan
return service.Stop()
}
标准库里面的 os/signal
中的 Notfiy
方法签名如下:1
func Notify(c chan<- os.Signal, sig ...os.Signal) {}
1 | time1:=time.Now() |
介绍一下各组件时区设置:
SYSTEM
parseTime=true&loc=Asia%2FShanghai
以上代码,在本地机器上跑单元测试时无异常,能正常通过。但是,上了CI环境后,单元测试却失败了。
通过观察CI的日志和自己打印的日志,发现入库前后,这两个时间相差了8个小时,很明显,这个时间受到时区的影响了。
后来调试发现,本地 mysql time_zone SYSTEM的实际值是 CST (中国标准时间),所以 dsn中指定loc为 Asia/Shanghai
并不会有问题。
而CI环境中,mysql的 time_zone SYSTEM的实际值是 UTC.而此时代码中使用dsn却是Asia/Shanghai
,这样就会导致 xorm.Get()
的时候,将时间按照Asia/Shanghai
来解析,所以,解析出来的结果和UTC时间相差了8个小时。
解决方案:
将代码中使用的dsn改为 parseTime=true&loc=Local 。
最终总结,其实这个问题是可以避免的,起初在设计数据库的时候,时间字段如果没有特殊需求,不应该使用数据库的date
,timestamp
等类型,而是统一都使用 INT UNSIGNED NOT NULL
。这样数字类型的时间戳,即可以避免时区问题,还可以用来直接比较大小和进行算数运算。
附上mysql查看时区配置的几种方式:1
select @@global.time_zone,@@session.time_zone;
1 | show variables like %time_zone%;' |
1 | go test -v ./... -cover |
结果如下:1
2
3...
PASS
coverage: 34.4% of statements
但是有时候需要提高覆盖测试率,就需要知道哪些代码被覆盖到了,而哪些代码没有被覆盖到。这时候可以借助go提供的一个工具来实现。
-coverprofile=profileName
,生成 profile文件。1 | go test -v ./... -coverprofile=aaaa |
go tool cover -html=profileName -o coverage.html
来生成html文件后用浏览器打开。1 | go tool cover -html=aaaa -o coverage.html |
最终看到的效果是:
红色表示没有覆盖到的代码,绿色表示覆盖到的代码,这样就能针对性的编写单元测试,提高测试覆盖率了。
在一次项目中,有一个字段结构如下:1
2
3
4
5
6{
"logistics":{
"company":"string",
"no":"string"
}
}
由于当初创建索引的时候,既没有给这个索引中的这个字段指定合适的类型,也没有通过动态模板来为这个字段指定类型,导致 elasticsearch 默认将这种结构的数据设置成了 Object 类型,而我们真正想要设置的类型却是 Nested 类型。
第一种方案,给这个索引追加一个新的字段,同时给这个字段指定类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16PUT test/_mapping/test
{
"properties": {
"logisticsV2":{
"type": "nested",
"properties": {
"company":{
"type":"keyword"
},
"no":{
"type":"keyword"
}
}
}
}
}
执行之后,index的mapping中就是这样的了: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{
"test":{
"mappings":{
"test":{
"properties":{
"logistics":{
"properties":{
"company":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
},
"no":{
"type":"text",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
}
}
},
"logisticsV2":{
"type":"nested",
"properties":{
"company":{
"type":"keyword"
},
"no":{
"type":"keyword"
}
}
}
}
}
}
}
}
其中 logisticsV2
的类型是 nested 类型,之后依赖于 nested 类型的相关功能使用 logisticsV2字段来实现即可。
这个中方式有一些弊端,比如数据冗余问题、数据一致性问题等
第二个方案,使用 elasticsearch 提供的 reindex api 来迁移数据,创建新的索引。
首先创建好目标索引,并设置好mapping:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17PUT test_new
PUT test_new/_mapping/test
{
"properties": {
"logistics":{
"type": "nested",
"properties": {
"company":{
"type":"keyword"
},
"no":{
"type":"keyword"
}
}
}
}
}
之后,使用 reindex 将原来的索引重建到新的索引上:1
2
3
4
5
6
7
8
9POST _reindex
{
"source": {
"index": "test"
},
"dest": {
"index": "test_new"
}
}
等待 reindex 完成后, test_new 就结构就就是你想要的mapping了。
为了对线上业务做到无感,可以使用 alias 别名功能来实现,具体操作可以参考 使用 Elasticsearch alias 功能切换 indexl. 这里不再赘述.
]]>1 | a = [1,2,3] |
如果这段代码放在其他语言,Java 或者 PHP 中,结果大多是这样的:1
2[1,2,3,4]
[1,2,3]
但是在 python 中,实际执行的结果却是这样的:1
2[1,2,3,4]
[1,2,3,4]
修改 b 的值,却影响了 a 的值。这个就是 python 和其他语言有一点不一样的地方了,简单可以理解为:python 没有赋值,只有引用。
如果以其他语言的方式来理解,那就是 a,b 都是指向同一块内存数据的地址,所以修改其中任何一个,都会引起彼此的变化,因为底层数据都是同一份。
现在,如果需要这段代码表现的和其他语言一致,该怎么写呢?
对于list 列表,python 有 copy 方法:1
2
3
4
5a = [1,2,3]
b = a.copy()
b.append(4)
print(b)
print(a)
代码执行结果就基本能达到预期了:1
2[1,2,3,4]
[1,2,3]
但是需要注意的是,这个 copy 方法是浅复制(shallow copy),那么它对于嵌套对象就无能为力了。
如果需要复制嵌套对象,可以使用 import copy ,然后使用 copy.deepcoopy()
来实现对嵌套对象的复制1
2
3
4
5
6
7
8import copy
a = [1,2,3,[4,5]]
# b = a.copy()
b = copy.deepcopy(a)
b.append(7)
b[3][1]=6
print(b)
print(a)
代码执行结果是这样的:1
2[1, 2, 3, [4, 6],7]
[1, 2, 3, [4, 5]]
可以看到 a 并没有因为 b 对嵌套内容的修改而改变。这里需要注意的一点是,如果修改的不是嵌套的内容,那么普通的copy也还是可以的,
此例中,append(7) 时,使用 copy
或者 copy.deepcopy
的效果是一样的,a的值都不会受到影响。
deepcopy的本质是递归复制。
对于 list 数据结构而言,可以用 list(x) 来代替 copy, 不过它依然是浅复制,这点需要注意。
]]>1 | gevent.hub.LoopExit:('This operation would block forever....') |
这个问题可以通过打上 monkey patch(猴子补丁)来规避。
在导入 multiprocess 库的文件头部加入以下代码:1
2
3from gevent import monkey
monkey.patch_all()
注意,这两行代码需要放置在文件导入库的最前面.
我遇到的场景下, 导致 LoopExit 的原因是 multiprocess 的job中远程网络请求超时导致的,如果相应的job任务中没有远程请求的话,即使不用 pathch,代码也是能够正常运行的。由于避免加上超时处理的复杂逻辑,就简单的使用monkey patch来解决这个问题。
]]>@asyncio.coroutine
注解 和 yield from
来实现。一下代码是3.5+以后的写法,和3.4版本的写法主要区别在于:
1 |
|
参考:
廖雪峰的Python3.x教程-异步io
以下简单使用案例以 multiprocessing.dummy 多线程为例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from multiprocessing.dummy import Pool
def compute(param):
return param*100
params = [ _ for _ in range(10) ]
pool = Pool()
results = pool.map(compute, params)
pool.close()
pool.join()
print(results)
注意:多进程(multiprocessing) 无法在 celery 等后台进程中使用,因为 celery 等后台进程不再允许生成子进程,这时候就只能使用多线程或者协程了。
1 | daemonic processes are not allowed to have children |
1 | go build -ldflags '-w -s' |
说明:
-w 禁止生成debug信息,注意使用该选项后,无法使用 gdb 进行调试
-s 禁用符号表
可以使用 go tool link --help
查看 ldflags 各参数含义
1 | go build -gcflags '-N -l' |
说明:
-N 禁止编译优化
-l 禁止内联,禁止内联也可以一定程度上减小可执行程序大小
可以使用 go tool compile --help
查看 gcflags 各参数含义
项目入口 main.go 中,之前是监听 TCP 连接,代码大致如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func main(){
server,err:= net.Listen("tcp","host:port")
if err!=nil{
return
}
defer server.Close()
for{
conn,err:= server.Accept()
if err!=nil{
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn){
//do somethings
}
现在需要在此基础之上监听 http 连接,由于 http.ListenAndServe() 方法是阻塞的,所以需要新开goroutine进行监听,代码示意如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24func main(){
http.HandleFunc("/hi", Router)
//因为会阻塞,所以需要新开goroutine进行监听
go http.ListenAndServe("host:http_port", nil)
server,err:= net.Listen("tcp","host:tcp_port")
if err!=nil{
return
}
defer server.Close()
for{
conn,err:= server.Accept()
if err!=nil{
continue
}
go handleConn(conn)
}
}
func handleConn(conn net.Conn){
//do somethings
}
基本上有两种方案,一是改代码,使线上代码访问新的索引,二是使用 alias 别名功能。
第一种方案有弊端,在代码进行发布到具体机器之前的并不能保证所有的访问都是访问的新的索引。
这里选择通过 alias 来切换索引,因为 alias 中的多条命令是原子性。
1.创建索引 test 和 test_v21
2
3PUT test
PUT test_v2
GET test*
结果如下: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{
"test": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1514183636616",
"number_of_shards": "5",
"number_of_replicas": "1",
"uuid": "oOgEhLzqS_usf7floIhNig",
"version": {
"created": "5030099"
},
"provided_name": "test"
}
}
},
"test_v2": {
"aliases": {},
"mappings": {},
"settings": {
"index": {
"creation_date": "1514183638407",
"number_of_shards": "5",
"number_of_replicas": "1",
"uuid": "o6x6VtmQSFaOpOB-OpIwBg",
"version": {
"created": "5030099"
},
"provided_name": "test_v2"
}
}
}
}
2.现在需要将对 test 的访问无缝切换到 test_v2,使用 alias 功能即可做到,对线上服务、对用户无感1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16POST _aliases
{
"actions": [
{
"add": {
"index": "test_v2",
"alias": "test"
}
},
{
"remove_index": {
"index": "test"
}
}
]
}
actions 中的多条命令是原子性的,所以可以做到无缝切换,不必担心切换过程中会存在访问不到 test 时候。
3.命令解释
actions 中第一条命令:add 命令,给索引 test_v2 添加了一个别名 test。
actions 中第二条命令:remove_index, 删除原来的 test 索引。
执行之后,原来的 test索引会被删除,而对于访问方而言,访问的索引依然是 test,不过访问的是一个别名,底层实际访问的索引已经是 test_v2了
1 | GET test* |
从结果中可见,test索引已经不存在了,但是通过 GET test 还是能访问到,而现在 test_v2 多了一个 alias test。
]]>1 | RuntimeWarning: You're running the worker with superuser privileges: this is absolutely not recommended! |
低版本的 celery 情况不清楚,但是 celery 4.1.0 版本中,这个警告是无法去除的。因为源码中是这样写的:
1 | def check_privileges(accept_content): |
很明显,这个 RuntimeWarning 是无法通过 C_FORCE_ROOT 设置来屏蔽的。当然了,这个只是一个警告,celery的运行并不受影响。
如果遇到某些版本的 celery 因为 root 用户而无法启动、无法执行的情况,可以通过改变 C_FORCE_ROOT=True 来规避这个问题。
主要有四种方式:
1.手动设置环境变量1
export C_FORCE_ROOT="true"
2.使用 supervisor 管理 celery 时,可以用 environment 来设置环境变量.1
environment=PYTHONPATH="xxx",C_FORCE_ROOT="true"
3.代码中硬编码指定.1
2
3from celery import platforms
platforms.C_FORCE_ROOT=True
4.写入 bashrc 或者其他系统启动脚本之中1
2file:.bashrc
export C_FORCE_ROOT="true"