如何在产线上玩转SolrCloud

如何在产线上玩转SolrCloud

由于笔者所在的公司一直使用Solr作为主要搜索引擎,并且随着数据越来越多,单节点的Solr已经不能撑住线上服务的压力,我们需要升级到SolrCloud,并且为了更好的配合数据更新提高客户相应速度,我们需要将原先的每天定时更新模式改成实时更新模式。(这里的每天定时更新并不是直接写到生产Solr上,而是在单独的服务器也保存这全量的数据,然后每天将增量的数据重新index一遍,然后定点将全量的数据copy到线上的机器,然后重启线上的服务进行切换)。这里分享下我们迁移到SolrCloud模式上的所遇到的问题以及解决方案![这里的Solr版本是7.7.2]

如何选择SolrCloud的部署模式

和MySql类似,Solr也有master slaver模式,但是很遗憾的是在Solr7.x版本之前并不支持SolrCloud的master slaver模式,也就是说如果你希望所有的写都发生在master机器,而slaver只需要同步master的数据的话在solr7.x之前是无法做到的(之前笔者使用的的solr版本上6.5.1,是的,为了能使用master slaver模式,笔者还把solr版本从6.5.1升级到7.7.2)。那么为什么笔者需要使用master-slaver模式,以及如何根据自己的业务场景使用solrcloud的部署模式呢?

Solr7.7.2中solrcloud的集中模式

Solr在7.x中solrcloud中有三种副本模式:

  • NRT 默认模式,(NRT= Near Real Time)近实时模式,这种模式下副本会在本地维护 transaction log ,并且新的文档都是在本地的索引进行更新,这种模式下的relica都可以作为leader的候选者
  • TLOG 这种模式下副本会维护一份transaction log,但是不会将新的文档更新到本地的index中,而是会从leader中拉去增量数据来完成数据更新,这种模式下的replica也是可以作为leader的候选者,并且如果他成为leader,首先会处理translation log,然后所有的行为会和NRT模式保持一致
  • PULL 这种模式下副本不会维护transaction log,也不会将新的文档更新到本地的index中,这种模式下的副本只会从leader中拉去增量数据来完成数据更新,并且无法参与leader选取。
    Solr官方推荐的方式有三种:
  • All NRT replicas (全部使用NRT模式):
    建议使用在小规模的集群或者数据更新量娇小的大集群上,并且NRT也是唯一一种支持soft-commit的模式,所以如果需要进行数据的实时更新话的,那么推荐使用这种模式。
  • All TLOG Replicas (全部使用TLOG模式):
    如果不需要实时索引并且shard的relica的数量比较大的时候可以使用,这种模式下所有的副本都可以处理更新请求。
  • TLOG replicas with PULL replicas (TLOG和PULL的组合使用)
    如果不需要实时索引并且shard的replica的数量比较大的时候可以使用,并且这种模式下可以提高集群的高可用,但是由于PULL同步会有时间差,所以查询会有脏读的可能性。
    其他模式,官方文档不推荐,但是有兴趣的同学可以自行尝试。
    笔者所在的公司选择的是TLOG replicas with PULL replicas,具体选择的根据是:
  • 数据不需要100%实时更新,或者只有在及少数的情况下需要实时更新
  • 集群规模较大,目前有16个shard,总数据量超过5TB
  • 数据更新量比较大,单个文档可能超过MB,并且每天的更新量可能到千万级别
    并且为了节约成本并且提供一定的伸缩性能笔者使用了TLOG replicas with PULL replicas模式,并且TLOG节点的机器配置要弱于PULL节点(大约是PULL节点的一半成本)

如何部署TLOG-PULL模式的solrcloud

这里为了演示solrcloud部署和模拟产线环境笔者选取了4台服务,对应的服务如下:

ip address 服务名称 备注
192.168.2.112 zookeeper, haproxy 这里使用haproxy模拟负载均衡
192.168.2.113 solr master 模拟master节点
192.168.2.114 solr slaver 模拟slaver节点
192.168.2.115 solr slaver 模拟slaver节点

Solr的官方教程提供了很好的solrcloud部署教程,笔者鼓励大家多看看官方文档,我们可以从文档中了解很多solr的用法(先要会用,然后再看源码)。但是如果你是从solr的低版本升级到solr7.x版本,这里有几个细节需要注意下:

  • 配置文件已经上传到zookeeper,但是节点在启动的时候却无法读取
    具体的表现为:3AVHtx.png
    这是因为在solr7.x版本中参数legacyCloud被默认设置成了false,也就是说,如果某个replica没有在state.json中定义那么该replica在启动之后不会自动加入到集群中,当然如果你希望replica在启动之后可以自动注册到整个集群中可以使用脚本
    1
    ./server/scripts/cloud-scripts/zkcli.sh -zkhost 127.0.0.1:2181 -cmd clusterprop -name legacyCloud -val true

打开自动注册功能

  • 如何通过core.properties设置replica的类型
    这一点官方文档中并没有体现,这是笔者看源代码发现的,改动也比较简单

    1
    2
    3
    4
    5
    6
    ## core.properties 文件的内容
    name=TEST
    shard=shard1
    collection=TEST
    coreNodeName=core_node3
    replicaType=PULL # 可以通过配置replicaType来设定relica的类型
  • 不要使用bin/solr命令指定zookeeper地址
    笔者在升级的过程中发现,如果使用bin/solr命令指定zookeeper地址(-z和-c命令)那么集群在启动的时候还是会读取不到配置文件,可以使用低版本的启动模式来解决,例如:

    1
    2
    /data/master_solr7/solr-7.7.2/bin/solr start -p 8983 -m 1g -s /data/master_solr7/shard1 -force -a "-DnumShards=2 -DzkHost=192.168.2.112:2181 -Dsolr.log.dir=/data/master_solr7/logs/shard1/"
    # -p 指定端口号, -m 指定内存大小, -s 指定solr_home 路径, -force 用于root用户启动

如果搭建成功可以通过cloud页面查看集群状态:
3AMk3q.png
Haproxy的配置也相对简单,但是这里要注意的是linux中需要通过

1
2
setsebool -P haproxy_connect_any=1
#(https://stackoverflow.com/questions/34793885/haproxy-cannot-bind-socket-0-0-0-08888)

来设置haproxy的链接策略。haproxy的具体配置为(端口号可以自定义设置):

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#---------------------------------------------------------------------
# Example configuration for a possible web application. See the
# full configuration options online.
#
# http://haproxy.1wt.eu/download/1.4/doc/configuration.txt
#
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
# to have these messages end up in /var/log/haproxy.log you will
# need to:
#
# 1) configure syslog to accept network log events. This is done
# by adding the '-r' option to the SYSLOGD_OPTIONS in
# /etc/sysconfig/syslog
#
# 2) configure local2 events to go to the /var/log/haproxy.log
# file. A line like the following can be added to
# /etc/sysconfig/syslog
#
# local2.* /var/log/haproxy.log
#
log 127.0.0.1 local2

chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon

# turn on stats unix socket
stats socket /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
maxconn 3000

listen stats
stats enable
bind 0.0.0.0:1080
stats refresh 30s
stats uri /stats
stats realm Haproxy Manager
stats auth admin:admin

#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
frontend main *:80
default_backend app

#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
#backend static
# balance roundrobin
# server static 127.0.0.1:4331 check

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
backend app
balance roundrobin
server slaver1_shard1 192.168.2.114:8983 check
server slaver1_shard2 192.168.2.114:7973 check
server slaver2_shard1 192.168.2.115:8983 check
server slaver2_shard2 192.168.2.115:7973 check

配置成功之后可以通过ip:1080/stats(笔者这里是http://192.168.2.112:1080/stats)来查看服务的状态:
3AQoYd.png
这里有个细节有读者可能已经注意到了,笔者并没有将master节点所在的机器加入到对外服务的列表中,这里的原因有两个:
1) master节点负责所有的写操作,对磁盘的压力和cpu的压力比较大,所以保证所有的slaver节点对外提供服务。
2) 如果不能通过zookeeper访问到solr集群,那么使用http的方式通过haproxy可以做到一定程度的负载均衡。

如何使用TLOG-PULL模式的solrcloud

目前我们已经知道如何部署TLOG-PULL模式下的solrcloud集群,那么在使用的时候我们需要注意什么,以及可能遇到的问题及其解决方案又是什么?笔者在下面的篇幅进行一一讲解。

如何保证请求打在slaver节点上

由于master节点负责所有的写入请求,那么在写入量较大的时候必然会对cpu和磁盘产生较大的压力,虽然我们可以在写入端进行速度控制,但是有的时候我们还是对客户妥协(毕竟是乙方)。所以我们必须保证所有的请求可以精确的打在所有的slaver机器上。有的读者可能在搭建好solrcloud集群的时候就请求过slaver/master节点,通过日志不难发现,如果依然按照正常的参数请求,那么请求可能会打到master节点,也有可能打到slaver节点上 (例如:curl “http://192.168.2.112/solr/TEST/select?q=*:*"),那么如果保证请求一定打到slaver节点上呢?答案很简单: 在请求的参数上加上shards.preference=replica.location:local,replica.type:PULL 参数即可。(例如: curl “http://192.168.2.112/solr/TEST/select?q=*:*&shards.preference=replica.location:local,replica.type:PULL")这里解释下两个参数的值的含义

  • replica.location:local
    尽可能使用本地副本进行请求处理,这里的本地副本只是,如果多个shard部署在一起的时候,尽可能使用最近的(同一台机器上)副本进行请求,而降低集群通信成本,所以如果分片数量较少但是副本较多的情况下可以使用改参数进行查询优化。
  • replica.type:PULL
    使用PULL节点进行查询请求的处理,这里并不能保证请求永远都打到slaver节点上,如果所有的slaver节点都挂了或者在非active状态,那么请求会打到master节点上。

    如何为集群增加slaver节点

    在正常的业务发展中,我们或多或少的会遇到请求量增多的情况,最常见的比如双11这样的活动,不过这里要指出一点:对有状态的服务进行扩容是需要时间的,这个时间的多少和“状态“的”量“有关,举个例子:假设某个shard的索引只有几十GB的容量,那么增加副本的时间相对还是比较短的,按照千兆网卡来算,100GB的数据需要copy 大约28分钟即可完成,如果按照万兆网卡来算,100GB的数据需要copy 2分钟即可完成。但是随着数据的增加增加副本的时间会越来越长,所以类似无状态的服务那种秒级的auto-scaling是无法做到的。笔者所在的公司需要维护5~6TB的索引,分了16个分片,为了节约成本,我们选择了aws的instance storage i3.8xlarge机器进行部署,用过aws服务的都知道,aws的机器是无法进行配置的,越好的机器网络越好,没有那种网络非常好但是cpu和内存一般的机器,所以我们不可能开16台i3.8xlarge来放分别放16个分片的数据,当然如果钱到位,可以忽略上述的问题。
    但是幸运的是:大部分情况下的服务请求量激增是可以预测的,比如双11,双12,发生的时间是相对固定的,某个平台在下一秒就增加10倍的请求量发生的概率是比较低的,也就是说我们可以提前对集群进行扩容来应对请求量的突增。那么在对TLOG-PULL模式的solrcloud进行集群扩容需要注意什么?
  • 数据需要尽可能提前copy到目标机器上
    由于刚启动的副本是没有数据的,但是TLOG-PULL模式是允许将请求打到刚启动但是已经加入集群的节点的,所以推荐的方式为:
    1) 暂停master节点的数据更新
    2) copy master节点上的数据到新的节点上
    3) 启动新的节点,由于设置了legacyCloud=true,新的节点会自动加入集群并且参与服务
    4) 如果有需要(不使用zookeeper进行进群访问的情况下)可以将新的节点加入到负载均衡中
    5) 恢复master节点的数据更新

    如何替换挂掉的slaver节点

    一般来说如果不是机器的硬件有问题,某个slaver挂掉了我们只需要重新启动就行,如果稍作改进可以使用supervisor进行进程管理就能自动重启,但是在真实场景用我们可能会遇到机器的硬件问题,那么这个时候我们如何对挂掉的slaver节点进行替换呢?
  • 将挂掉的节点从集群和负责均衡器中去除
    在之前的例子中,假设192.168.2.115这台slaver由于硬件问题已经无法恢复,
    从haproxy中去掉比较简单,直接删除对应的配置比较简单:

    1
    2
    3
    4
    5
    6
    backend app
    balance roundrobin
    server slaver1_shard1 192.168.2.114:8983 check
    server slaver1_shard2 192.168.2.114:7973 check
    #server slaver2_shard1 192.168.2.115:8983 check
    #server slaver2_shard2 192.168.2.115:7973 check

    从solr集群中去除也比较简单,但是要注意的是,如果某个slaver节点挂掉之后,整个集群会从healthy转变成degraded状态,这时如果master节点不挂,那么集群还是可以正常进行数据更新和查询的,这里可以使用solr的DELETEREPLICA命令将挂点的节点从集群中去除,直接上命令:

    1
    2
    curl "http://192.168.2.112:80/solr/admin/collections?action=DELETEREPLICA&collection=TEST&shard=shard1&replica=core_node5&deleteInstanceDir=false"
    #这里的core_node5可以使用bin/solr healthcheck -z 192.168.2.112:2181 -c TEST 查看到状态为down的节点名称。

    删除之后集群就会恢复到healthy状态

  • 将新的节点加入到集群中
    这里只要注意需要将master的数据提前copy到新增的节点即可,否则可能会出现查询结果不正确的问题,当新节点启动之后,按照之前所说,会自动加入集群。如果需要将新的节点加入到负责均衡就行

    如何替换挂掉的master节点

    master节点挂掉和slaver节点类似,但是导致的问题会相对复杂一点,主要体现在
    1) 可能会导致部分数据丢失
    2) 各个slaver的数据不同步,查询结果会有不一致的场景
    3) 无法进行数据更新
    相对而言master节点挂掉产生的问题是比slaver挂掉更加严重的,所以需要尽可能的快速修复。(如果在云平台上不要选择易失性的机器类型,比如在aws上不要选择instnace storage类型的机器,因为机器一旦挂掉所有数据不可找回)。
  • 将新的master节点加入到集群中
    由于master节点挂掉不会影响slaver的正常请求处理,所以我们可以将数据从slaver上copy到新的master节点上,这里要注意的是因为slaver之前可能不是100%同步,特别是在数据更新的时候,所以尽可能选择最新状态的slaver节点。当数据ready之后,直接将master节点启动并且会自动加入进群,由于master节点的类型为TLOG,所以集群会进行选主,之前的master节点将降级slaver节点(虽然还是down的状态)
  • 将老的master节点从集群中剔除
    这里直接使用DELETEREPLICA 命令即可,不在赘述
    不过这里仍然要提醒的是,由于数据容量可能会很大,所以不要选择易失性的节点作为master机器类型,例如笔者在aws上选择的是普通的机器类型+ebs(ebs有6个9 的可用性),并且对磁盘进行定期的snapshot防止在特殊情况下(比如整个AZ都挂了)仍然可以保证部分服务可用。