|
| 1 | +## PG多节点(quorum based), 0丢失 HA(failover,switchover)方案 |
| 2 | + |
| 3 | +### 作者 |
| 4 | +digoal |
| 5 | + |
| 6 | +### 日期 |
| 7 | +2017-06-12 |
| 8 | + |
| 9 | +### 标签 |
| 10 | +PostgreSQL , 同步复制 , quorum based |
| 11 | + |
| 12 | +---- |
| 13 | + |
| 14 | +## 背景 |
| 15 | +PostgreSQL 10加入了quorum based的同步复制功能,用户可以配置若干standby节点,并配置需要将WAL发送多少份才返回给客户端事务结束的消息。 |
| 16 | + |
| 17 | +``` |
| 18 | +ANY num_sync ( standby_name [, ...] ) |
| 19 | +``` |
| 20 | + |
| 21 | +原理详见 |
| 22 | + |
| 23 | +https://www.postgresql.org/docs/10/static/runtime-config-replication.html#runtime-config-replication-master |
| 24 | + |
| 25 | +https://www.postgresql.org/docs/10/static/warm-standby.html#synchronous-replication |
| 26 | + |
| 27 | +例子 |
| 28 | + |
| 29 | +``` |
| 30 | +s1,s2,s3为standby recovery.conf配置的application_name, 即standby的唯一标示 |
| 31 | + |
| 32 | +下面配置表示WAL需要复制到s1,s2,s3中的任意2个副本。 |
| 33 | + |
| 34 | +synchronous_standby_names = 'ANY 2 (s1, s2, s3)' |
| 35 | + |
| 36 | +下面配置表示WAL需要复制到任意standby的任意2个副本。 |
| 37 | +synchronous_standby_names = 'ANY 2 (*)' |
| 38 | +``` |
| 39 | + |
| 40 | +PostgreSQL的quorum base配置比较灵活,用户可以根据地域、延迟、保护级别等需求来配置synchronous_standby_names。 |
| 41 | + |
| 42 | +例如 |
| 43 | + |
| 44 | +master有4个standby分别是s1,s2,s3,s4,s1在同机房, s2,s3在某个同城45公里以内的机房, s4在其他城市。 |
| 45 | + |
| 46 | +那么可以这么配置 |
| 47 | + |
| 48 | +``` |
| 49 | +下面配置表示WAL至少在一个其他机房有一份拷贝,防止整个机房的故障。 |
| 50 | + |
| 51 | +synchronous_standby_names = 'ANY 2 (s1,s2,s4)' |
| 52 | +``` |
| 53 | + |
| 54 | +未来甚至有更灵活的配置(畅想) |
| 55 | + |
| 56 | +``` |
| 57 | +下面配置表示s2,s3只算一次,但是它们任意一个feedback都算数。不失可靠性的情况下,提高可用性。 |
| 58 | + |
| 59 | +synchronous_standby_names = 'ANY 2 (s1,[s2,s3],s4)' |
| 60 | +``` |
| 61 | + |
| 62 | +但是问题来了,当master出现故障时,如何failover,如何switchover呢? |
| 63 | + |
| 64 | +## 架构 |
| 65 | + |
| 66 | + |
| 67 | +1、每个数据库实例对应一个静态IP,PostgreSQL master - slave搭建好时就固定下来。 |
| 68 | + |
| 69 | +2、每个数据库实例对应一个角色,master或slave。 |
| 70 | + |
| 71 | +3、每个角色对应一个域名,当实例为master角色时,对应的域名为master,当角色为slave时,对应slave的域名。 |
| 72 | + |
| 73 | +4、集群初次创建好之后,将IP和域名的对应关系写入DNS。 |
| 74 | + |
| 75 | +5、HA管理软件,使用master域名从DNS得到静态IP,连接到静态IP,并探测master是否正常。发生异常时,进入failover流程。(后面讲failover流程) |
| 76 | + |
| 77 | +6、最终应用、PROXY,通过域名连接数据库。 |
| 78 | + |
| 79 | +例如,这是tom lane所在的crunchydata公司开源的一个PostgreSQL proxy,用golang写的,不做SQL解析,仅仅通过SQL HINT做简单的路由,够用。效率比较高。 |
| 80 | + |
| 81 | +https://github.com/CrunchyData/crunchy-proxy |
| 82 | + |
| 83 | +如果使用客户端连接多实例的话,可以参考如下文章 |
| 84 | + |
| 85 | +[《PostgreSQL 10.0 preview 功能增强 - libpq支持多主机连接(failover,LB)让数据库HA和应用配合更紧密》](../201704/20170420_01.md) |
| 86 | + |
| 87 | +7、failover结束后,master角色更替,ha管理软件通知DNS修改解析信息。 |
| 88 | + |
| 89 | + |
| 90 | + |
| 91 | +8、即使应用程序、PROXY因为DNS缓存,有可能在DNS TTL失效前,短暂的连接到错误的MASTER,也没有关系,因为master配置了quorum based sync replication,所以failover结束后,(即使old master突然好了)写请求下去是不会响应的。 |
| 92 | + |
| 93 | +读请求则可能受到影响,可能读到old master的data。(短暂影响) |
| 94 | + |
| 95 | +## 配置 |
| 96 | + |
| 97 | +1、初始配置 |
| 98 | + |
| 99 | +初始配置,将master, slave配置好,以前面的图为例,master配置复制到3个副本。 |
| 100 | + |
| 101 | +slave配置,连接master域名,application_name配置为master ID+slave ID。 |
| 102 | + |
| 103 | +例子(关键配置) |
| 104 | + |
| 105 | +DNS |
| 106 | + |
| 107 | +``` |
| 108 | +N1 : 192.168.1.100 : master |
| 109 | +N2 : 192.168.1.101 : slave1 |
| 110 | +N3 : 192.168.1.102 : slave2 |
| 111 | +N4 : 192.168.1.103 : slave3 |
| 112 | +N5 : 192.168.1.104 : slave4 |
| 113 | +N6 : 192.168.1.105 : slave5 |
| 114 | +``` |
| 115 | + |
| 116 | +master(N1) postgresql.conf |
| 117 | + |
| 118 | +``` |
| 119 | +synchronous_standby_names = 'ANY 3 (N1_N2, N1_N3, N1_N4, N1_N5, N1_N6)' |
| 120 | +``` |
| 121 | + |
| 122 | +slave recovery.conf |
| 123 | + |
| 124 | +``` |
| 125 | +N2: |
| 126 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N1_N2' |
| 127 | +N3: |
| 128 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N1_N3' |
| 129 | +N4: |
| 130 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N1_N4' |
| 131 | +N5: |
| 132 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N1_N5' |
| 133 | +N6: |
| 134 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N1_N6' |
| 135 | +``` |
| 136 | + |
| 137 | +2、DNS配置 |
| 138 | + |
| 139 | +TTL设置(尽量缩短客户端的dns cache时间, 例如10秒),域名映射配置。 |
| 140 | + |
| 141 | +``` |
| 142 | +192.168.1.100 : master |
| 143 | +192.168.1.101 : slave1 |
| 144 | +192.168.1.102 : slave2 |
| 145 | +192.168.1.103 : slave3 |
| 146 | +192.168.1.104 : slave4 |
| 147 | +192.168.1.105 : slave5 |
| 148 | +``` |
| 149 | + |
| 150 | +3、ha管理软件配置 |
| 151 | + |
| 152 | +可以使用一个单独的数据库来存储,或者使用文件配置。 |
| 153 | + |
| 154 | +3\.1 IP,数据库监听端口和ID的关系(端口必须固定、相等) |
| 155 | + |
| 156 | +``` |
| 157 | +N1 : 192.168.1.100 , 1921 |
| 158 | +N2 : 192.168.1.101 , 1921 |
| 159 | +N3 : 192.168.1.102 , 1921 |
| 160 | +N4 : 192.168.1.103 , 1921 |
| 161 | +N5 : 192.168.1.104 , 1921 |
| 162 | +N6 : 192.168.1.105 , 1921 |
| 163 | +``` |
| 164 | + |
| 165 | +3\.2 角色与域名的关系 |
| 166 | + |
| 167 | +``` |
| 168 | +master : master |
| 169 | +slave : slave1 |
| 170 | +slave : slave2 |
| 171 | +slave : slave3 |
| 172 | +slave : slave4 |
| 173 | +slave : slave5 |
| 174 | +``` |
| 175 | + |
| 176 | +3\.3 数据库用户密码 |
| 177 | + |
| 178 | +``` |
| 179 | +user : xx |
| 180 | + |
| 181 | +pwd : xx |
| 182 | +``` |
| 183 | + |
| 184 | +3\.4 重试间隔,重试次数。 |
| 185 | + |
| 186 | +3\.5 quorum数 = 3 # (取自master postgresql.conf的配置synchronous_standby_names = 'ANY 3 (N1_N2, N1_N3, N1_N4, N1_N5, N1_N6)')。 |
| 187 | + |
| 188 | +3\.6 总节点数(包括master) = 6 |
| 189 | + |
| 190 | +## failover流程 |
| 191 | +1、请求解析,从master角色的域名"master",得到IP。 |
| 192 | + |
| 193 | +2、探测IP,数据库监听端口连通性。(异常时,注意重试次数、超时) |
| 194 | + |
| 195 | +异常时,重试若干次(设置好重试间隔、重试次数),重试若干次均不可用,则进入failover流程。一旦重启期间可用,则退出failover。 |
| 196 | + |
| 197 | +3、探测数据库是否可以正常登录。(异常时,注意重试次数、超时) |
| 198 | + |
| 199 | +异常时,重试若干次(设置好重试间隔、重试次数),重试若干次均不可用,则进入failover流程。一旦重启期间可用,则退出failover。 |
| 200 | + |
| 201 | +4、登录数据库,探测数据库可用性,(数据库alive检测,封装成数据库函数,返回true or false表示数据库是否可用)。(异常时,注意重试次数、超时) |
| 202 | + |
| 203 | +返回false,或者返回异常,则数据库不可用。 |
| 204 | + |
| 205 | +异常时,重试若干次(设置好重试间隔、重试次数),重试若干次均不可用,则进入failover流程。一旦重启期间可用,则退出failover。 |
| 206 | + |
| 207 | +### 孤立slave |
| 208 | +孤立slave的意思是让slave进入孤立状态,能接收读请求,但是wal receiver进程不工作,也不发feedback给master,和master脱离关系。 |
| 209 | + |
| 210 | +孤立必须具备持久性,例如,重启后依旧处于孤立状态。 |
| 211 | + |
| 212 | +### 实例操作方法 |
| 213 | +failover过程中,会涉及实例的操作,可以通过多种方式实现 |
| 214 | + |
| 215 | +1、通过数据库UDF实现数据库实例的文件、脚本等操作。 |
| 216 | + |
| 217 | +2、通过在数据库主机部署agent软件实现数据库实例的文件、脚本等操作。 |
| 218 | + |
| 219 | +推荐使用agent。 |
| 220 | + |
| 221 | + |
| 222 | + |
| 223 | +### failover |
| 224 | +1、使用配置,获取slave角色对应的域名,获取域名对应的IP。后面连接SLAVE的操作,均使用IP。 |
| 225 | + |
| 226 | +ip - node name 已有映射关系。 |
| 227 | + |
| 228 | +2、孤立若干个slave,若干是如何计算的呢? |
| 229 | + |
| 230 | +公式 |
| 231 | + |
| 232 | +``` |
| 233 | +若干 = 总节点数(包括master) - quorum数 = 6-3 = 3 |
| 234 | +``` |
| 235 | + |
| 236 | +3、孤立若干个slave后,如果还有未孤立的slave,则继续孤立。 |
| 237 | + |
| 238 | +第2步的slave即使孤立不成功,也继续。(假设N2也异常) |
| 239 | + |
| 240 | +4、从已孤立的SLAVE中,选择一个LSN最大的slave(我们这里假设为N3),作为new master,修改postgresql.conf。 |
| 241 | + |
| 242 | +``` |
| 243 | +synchronous_standby_names = 'ANY 3 (N3_N1, N3_N2, N3_N4, N3_N5, N3_N6)' |
| 244 | +``` |
| 245 | + |
| 246 | +5、重命名new master的recovery.conf |
| 247 | + |
| 248 | +``` |
| 249 | +rename recovery.conf to recovery.done |
| 250 | +``` |
| 251 | + |
| 252 | +6、修改其他已孤立的slave - recovery.conf |
| 253 | + |
| 254 | +``` |
| 255 | +N4: |
| 256 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N3_N4' |
| 257 | +N5: |
| 258 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N3_N5' |
| 259 | +N6: |
| 260 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N3_N6' |
| 261 | +``` |
| 262 | + |
| 263 | +7、通知dns修改域名映射 |
| 264 | + |
| 265 | +``` |
| 266 | +-- 对调old master和new master |
| 267 | + |
| 268 | +192.168.1.102 : master |
| 269 | +192.168.1.101 : slave1 |
| 270 | +192.168.1.102 : slave2 |
| 271 | +192.168.1.100 : slave3 |
| 272 | +192.168.1.104 : slave4 |
| 273 | +192.168.1.105 : slave5 |
| 274 | +``` |
| 275 | + |
| 276 | +8、重启孤立实例。 |
| 277 | + |
| 278 | +9、解除孤立。 |
| 279 | + |
| 280 | +10、激活new master。 |
| 281 | + |
| 282 | +11、结束failover流程。 |
| 283 | + |
| 284 | +#### 注意事项 |
| 285 | +任何步骤失败,解除孤立,从头再来。 |
| 286 | + |
| 287 | +## 修复异常节点 |
| 288 | +### 实例回退修复 |
| 289 | +通常不需要修复,因为不可能出现数据丢失的情况,对于已结束的事务,new master的wal内容和old master一定是一样的。 |
| 290 | + |
| 291 | +https://www.postgresql.org/docs/10/static/app-pgrewind.html |
| 292 | + |
| 293 | +但是一些未结束事务,可能会在new master产生更多的WAL,所以new master可能需要rewind。参考pg_rewind的方法。 |
| 294 | + |
| 295 | +1、配置recovery.conf |
| 296 | + |
| 297 | +old master: |
| 298 | + |
| 299 | +``` |
| 300 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N3_N1' |
| 301 | +``` |
| 302 | + |
| 303 | +N2(异常slave): |
| 304 | + |
| 305 | +``` |
| 306 | +primary_conninfo = 'host=master port=xx user=xx password=xx application_name=N3_N2' |
| 307 | +``` |
| 308 | + |
| 309 | +2、配置old master postgresql.conf |
| 310 | + |
| 311 | +``` |
| 312 | +注释 |
| 313 | + |
| 314 | +# synchronous_standby_names = .......... |
| 315 | +``` |
| 316 | + |
| 317 | +重启或启动修复实例 |
| 318 | + |
| 319 | +## 小结 |
| 320 | +1、建议使用虚拟IP,否则管理软件还需要维护一套IP变更的方法。 |
| 321 | + |
| 322 | +2、当节点数不为6,或者quorum数不等于3时,都可以支持,使用前面提到的孤立公式即可。 |
| 323 | + |
| 324 | +## 参考 |
| 325 | +https://www.postgresql.org/docs/10/static/runtime-config-replication.html#runtime-config-replication-master |
| 326 | + |
| 327 | +https://www.postgresql.org/docs/10/static/app-pgrewind.html |
| 328 | + |
| 329 | +http://dalibo.github.io/PAF/administration.html |
| 330 | + |
| 331 | +https://github.com/digoal/PostgreSQL_HA_with_primary_standby_2vip |
| 332 | + |
0 commit comments