Статьи

Использование Groovy для быстрого анализа свойств терракоты HealthCheck

Одним из соображений при настройке серверов Terracotta с помощью tc-config.xml является спецификация свойств проверки работоспособности между серверами Terracotta (L2-L2), от клиентов к серверу (L1-L2) и от сервера к клиенту (L2-L1). , Terracotta проверяет комбинацию конфигураций этих свойств в сценариях высокой доступности, чтобы убедиться, что эти комбинации попадают в определенные диапазоны. В этом блоге показано использование Groovy для анализа и анализа данного tc-config.xml чтобы определить, будет ли Terracotta предоставлять сообщение уровня WARN относительно конфигураций этих свойств.

Раздел « О HealthChecker » руководства по высокой доступности Terracotta 4.3.2 BigMemory Max ( PDF ) описывает назначение HealthChecker: «HealthChecker — это монитор подключений, аналогичный TCP keep-alive. HealthChecker функционирует между экземплярами сервера Terracotta (в средах высокой доступности) и между экземплярами сервера Terracotta и клиентами. Используя HealthChecker, терракотовые узлы могут определить, достижимы ли одноранговые узлы, активны ли они или в операции GC. Если одноранговый узел недоступен или не работает, узел Terracotta, использующий HealthChecker, может предпринять корректирующие действия ».

Руководство по высокой доступности Terracotta 4.3.2 BigMemory Max включает в себя таблицу в разделе « Свойства HealthChecker», в которой описаны свойства терракоты, которые используются в расчетах, используемых для определения необходимости регистрации предупреждений о неправильно настроенной высокой доступности. Для каждой из комбинаций указаны свойства с одинаковыми именами (свойства l2.healthcheck.l1.* Для сервера к клиентам [L2L1], l2.healthcheck.l2.* Для сервера к серверу [L2L2] и l1.healthcheck.l2.* для клиентов к серверу [L1L2]), а также свойства, важные для проверок конфигурации высокой доступности (часть имен только что упомянутых свойств): ping.enabled , ping.idletime , ping.interval , ping.probes , socketConnect , socketConnectCount и socketConnectTimeout . Сценарий Groovy, связанный с этим сообщением, предполагает, что каждый из них имеет свойства ping.enabled и socketConnect для L2-L2, L1-L2 и L2-L1, все из которых настроены на true (что является значением по умолчанию для обоих свойств для всех комбинаций L2L2, L1L2, L2L1) ,

Класс Terracotta com.tc.l2.ha.HASettingsChecker обнаруживает две комбинации этих свойств, которые приводят к сообщениям журнала уровня WARN, начинающимся с фразы «Высокая доступность не настроена должным образом:…». В двух предупреждающих сообщениях конкретно указано: «Высокая доступность не настроена правильно: L1L2HealthCheck должен быть меньше, чем L2-L2HealthCheck + ElectionTime + ClientReconnectWindow» и «Высокая доступность не настроена должным образом: L1L2HealthCheck должно быть больше, чем L2-L2HealthCheck + ElectionTime».

Терракотовый класс HASettingsChecker реализует формулу, HASettingsChecker в разделе « Расчет максимума HealthChecker » Руководства по высокой доступности в своем методе interNodeHealthCheckTime(int,int,int,int,int) :

pingIdleTime + ((socketConnectCount) * (pingInterval * pingProbes + socketConnectTimeout * pingInterval))

Следующий скрипт Groovy анализирует указанный файл tc-config.xml и применяет ту же проверку свойств проверки работоспособности к соответствующим свойствам, определенным в разделе <tc-properties> этого файла. Сценарий Groovy, показанный здесь, не имеет внешних зависимостей, кроме действительного tc-config.xml который нужно проанализировать и проанализировать. Сценарий будет короче и потребует меньшего количества обслуживания в будущем, если он com.tc.properties.TCPropertiesConsts доступ к константам String, определенным в com.tc.properties.TCPropertiesConsts вместо определения своих собственных жестко закодированных версий.

checkTCServerProperties.groovy

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env groovy
 
def cli = new CliBuilder(
   usage: 'checkTCServerProperties -f <pathToTcConfigXmlFile> [-v] [-h]',
   header: '\nAvailable options (use -h for help):\n',
   footer: '\nParses referenced tc-config.xml file and analyzes its health check parameters..\n')
import org.apache.commons.cli.Option
cli.with
{
   h(longOpt: 'help', 'Usage Information', required: false)
   f(longOpt: 'file', 'Path to tc-config.xml File', args: 1, required: true)
   v(longOpt: 'verbose', 'Specifies verbose output', args: 0, required: false)
}
def opt = cli.parse(args)
 
if (!opt) return
if (opt.h) cli.usage()
 
String tcConfigFileName = opt.f
boolean verbose = opt.v
 
println "Checking ${tcConfigFileName}'s properties..."
def tcConfigXml = new XmlSlurper().parse(tcConfigFileName)
TreeMap<String, String> properties = new TreeSet<>()
tcConfigXml."tc-properties".property.each
{ tcProperty ->
   String tcPropertyName = tcProperty.@name
   String tcPropertyValue = tcProperty.@value
   properties.put(tcPropertyName, tcPropertyValue)
}
if (verbose)
{
   properties.each
   { propertyName, propertyValue ->
      println "${propertyName}: ${propertyValue}"
   }
}
 
boolean isL2L1PingEnabled = extractBoolean(properties, "l2.healthcheck.l1.ping.enabled")
boolean isL2L2PingEnabled = extractBoolean(properties, "l2.healthcheck.l2.ping.enabled")
boolean isL1L2PingEnabled = extractBoolean(properties, "l1.healthcheck.l2.ping.enabled")
boolean isPingEnabled = isL2L1PingEnabled && isL2L2PingEnabled && isL1L2PingEnabled
println "Health Check Ping ${isPingEnabled ? 'IS' : 'is NOT'} enabled."
if (!isPingEnabled)
{
   System.exit(-1)
}
 
Long pingIdleTimeL2L1 = extractLong(properties, "l2.healthcheck.l1.ping.idletime")
Long pingIdleTimeL2L2 = extractLong(properties, "l2.healthcheck.l2.ping.idletime")
Long pingIdleTimeL1L2 = extractLong(properties, "l1.healthcheck.l2.ping.idletime")
 
Long pingIntervalL2L1 = extractLong(properties, "l2.healthcheck.l1.ping.interval")
Long pingIntervalL2L2 = extractLong(properties, "l2.healthcheck.l2.ping.interval")
Long pingIntervalL1L2 = extractLong(properties, "l1.healthcheck.l2.ping.interval")
 
Long pingProbesL2L1 = extractLong(properties, "l2.healthcheck.l1.ping.probes")
Long pingProbesL2L2 = extractLong(properties, "l2.healthcheck.l2.ping.probes")
Long pingProbesL1L2 = extractLong(properties, "l1.healthcheck.l2.ping.probes")
 
boolean socketConnectL2L1 = extractBoolean(properties, "l2.healthcheck.l1.socketConnect")
boolean socketConnectL2L2 = extractBoolean(properties, "l2.healthcheck.l2.socketConnect")
boolean socketConnectL1L2 = extractBoolean(properties, "l1.healthcheck.l2.socketConnect")
 
if (!socketConnectL2L1 || !socketConnectL2L2 || !socketConnectL1L2)
{
   println "Socket connect is disabled."
   System.exit(-2)
}
 
Long socketConnectTimeoutL2L1 = extractLong(properties, "l2.healthcheck.l1.socketConnectTimeout")
Long socketConnectTimeoutL2L2 = extractLong(properties, "l2.healthcheck.l2.socketConnectTimeout")
Long socketConnectTimeoutL1L2 = extractLong(properties, "l1.healthcheck.l2.socketConnectTimeout")
 
Long socketConnectCountL2L1 = extractLong(properties, "l2.healthcheck.l1.socketConnectCount")
Long socketConnectCountL2L2 = extractLong(properties, "l2.healthcheck.l2.socketConnectCount")
Long socketConnectCountL1L2 = extractLong(properties, "l1.healthcheck.l2.socketConnectCount")
 
Long maximumL2L1 = calculateMaximumTime(pingIdleTimeL2L1, pingIntervalL2L1, pingProbesL2L1, socketConnectCountL2L1, socketConnectTimeoutL2L1)
Long maximumL2L2 = calculateMaximumTime(pingIdleTimeL2L2, pingIntervalL2L2, pingProbesL2L2, socketConnectCountL2L2, socketConnectTimeoutL2L2)
Long maximumL1L2 = calculateMaximumTime(pingIdleTimeL1L2, pingIntervalL1L2, pingProbesL1L2, socketConnectCountL1L2, socketConnectTimeoutL1L2)
 
if (verbose)
{
   println "L2-L1 Maximum Time: ${maximumL2L1}"
   println "L2-L2 Maximum Time: ${maximumL2L2}"
   println "L1-L2 Maximum Time: ${maximumL1L2}"
}
 
long electionTime = 5000
long clientReconnectWindow = 120000
 
long maximumL2L2Election = maximumL2L2 + electionTime
long maximumL2L2ElectionReconnect = maximumL2L2Election + clientReconnectWindow
 
if (verbose)
{
   println "L2-L2 Maximum Time + ElectionTime: ${maximumL2L2Election}"
   println "L2-L2 Maximum Time + ElectionTime + Client Reconnect Window: ${maximumL2L2ElectionReconnect}"  
}
 
if (maximumL1L2 < maximumL2L2Election)
{
   print "WARNING: Will lead to 'High Availability Not Configured Properly: L1L2HealthCheck should be more than L2-L2HealthCheck + ElectionTime' "
   println "because ${maximumL1L2} < ${maximumL2L2Election}."
}
else if (maximumL1L2 > maximumL2L2ElectionReconnect)
{
   print "WARNING: Will lead to 'High Availability Not Configured Properly: L1L2HealthCheck should be less than L2-L2HealthCheck + ElectionTime + ClientReconnectWindow' "
   println "because ${maximumL1L2} > ${maximumL2L2ElectionReconnect}."
}
 
/**
 * Extract a Boolean value for the provided property name from the provided
 * properties.
 *
 * @return Boolean value associated with the provided property name.
 */
boolean extractBoolean(TreeMap<String, String> properties, String propertyName)
{
   return  properties != null && properties.containsKey(propertyName)
         ? Boolean.valueOf(properties.get(propertyName))
         : false
}
 
/**
 * Extract a Long value for the provided property name from the provided
 * properties.
 *
 * @return Long value associated with the provided property name.
 */
Long extractLong(TreeMap<String, String> properties, String propertyName)
{
   return  properties != null && properties.containsKey(propertyName)
         ? Long.valueOf(properties.get(propertyName))
         : 0
}
 
/**
 * Provides the maximum time as calculated using the following formula:
 *
 * Maximum Time =
 *      (ping.idletime) + socketConnectCount *
 *      [(ping.interval * ping.probes) + (socketConnectTimeout * ping.interval)]
 */
Long calculateMaximumTime(Long pingIdleTime, Long pingInterval, Long pingProbes,
   Long socketConnectCount, Long socketConnectTimeout)
{
   return pingIdleTime + socketConnectCount * pingInterval * (pingProbes + socketConnectTimeout)
}

Этот скрипт также будет доступен на GitHub . В какой-то момент я могу рассмотреть некоторые из его недостатков и ограничений в этой версии GitHub. В частности, как показано выше, этот сценарий в настоящее время принимает значения по умолчанию для «времени выборов» и «окна переподключения клиента», но они могут быть проанализированы из файла tc-config.xml .

На следующих снимках экрана этот скрипт демонстрируется в действии с различными файлами tc-config.xml . На первом изображении показано поведение скрипта, когда пинг не включен. Второе изображение отображает поведение скрипта, когда проверка сокетов не включена. На третьем и четвертом изображениях показаны два предупреждения, с которыми можно столкнуться, если свойства конфигурации высокой доступности не настроены должным образом. Пятое изображение показывает полностью успешное выполнение сценария, которое указывает конфигурацию свойств проверки работоспособности, которые находятся в ожидаемых диапазонах.

Пинг не включен (не по умолчанию)

Сокет не включен (не по умолчанию)

Предупреждение о свойствах HealthCheck # 1

Предупреждение о свойствах HealthCheck # 2

Свойства HealthCheck включены и настроены правильно

Я использовал простую электронную таблицу для выполнения этих расчетов, и она работает довольно хорошо. Однако сценарий Groovy, обсуждаемый в этом посте, позволяет выполнять автоматический анализ файла-кандидата tc-config.xml а не копировать и вставлять значения в электронную таблицу. Скрипт Groovy может быть адаптирован для использования предоставленных Terracotta файлов Java, как обсуждалось ранее. Есть также несколько других улучшений, которые могут сделать сценарий более полезным, например, синтаксический анализ окна переподключения клиента и времени выбора из tc-config.xml вместо принятия значений по умолчанию.