Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Где мой сервис,
чувак?
Việt
[вьет]
2
Việt
[вьет]
3
‣ Пишу код больше 10 лет

‣ Начинал как фронтендер

‣ Написал распределенный
компилятор - полюбил бекенд

‣ ...
Việt
[вьет]
4
‣ Пишу код больше 10 лет

‣ Начинал как фронтендер

‣ Написал распределенный
компилятор - полюбил бекенд

‣ ...
Việt
[вьет]
5
‣ Пишу код больше 10 лет

‣ Начинал как фронтендер

‣ Написал распределенный
компилятор - полюбил бекенд

‣ ...
6
7
DevOps
8
Конфиги!
Они везде
@EnableEverything
@FixThis
@FixThat
@DoWhatever
public class App {
// no code - no cry
}
9
:(
О чём доклад?
‣ Обновление микросервисов 

без недоступности

‣ Тулзы
‣ Конфиги

‣ Заклятья
10
О чём доклад?
‣ Обновление микросервисов 

без недоступности

‣ Тулзы 

‣ Конфиги

‣ Заклятья
11
12
Монолит Микросервисы
13
UI UI UI
14
UI UI UI
API
API
API
API
API
API
API
API
API
15
UI UI UI
API
API
API
API
API
API
API
API
API
API
API
API
API
16
UI UI UI
API
API
API
API
API
API
API
API
API
API
API
API
API
DB
DB
DB
17
UI UI UI
API
API
API
API
API
API
API
API
API
API
API
API
API
DB
DB
DB
18
UI UI UI
API
API
API
API
API
API
API
API
API
API
API
API
API
DB
DB
DB
19
UI UI UI
API
API
API
API
API
API
API
API
API
API
API
API
API
DB
DB
DB
А за окном…
‣ Сеть отваливается

‣ Серваки отваливаются

‣ Отвечают с задержкой

‣ Все такое неоднородное

‣ Толпа вредных...
А за окном…
‣ Сеть отваливается

‣ Серваки отваливаются

‣ Отвечают с задержкой

‣ Все такое неоднородное

‣ Толпа вредных...
А за окном…
‣ Сеть отваливается

‣ Серваки отваливаются

‣ Отвечают с задержкой

‣ Все такое неоднородное

‣ Толпа вредных...
А за окном…
‣ Сеть отваливается

‣ Серваки отваливаются

‣ Отвечают с задержкой

‣ Все такое неоднородное

‣ Толпа вредных...
Следствия
‣ В распределенных системах 

отказы - обычное дело

‣ Нужно воспринимать это 

как естественный атрибут системы...
“Hope 

is not 

a strategy”
25
KISS FTW
27
cards API
transactions
API
:8081
:8080
Client
KISS FTW
28
cards API
transactions
API
:8081
:8080
Client
Где мои деньги?!
KISS FTW
30
cards API
transactions
API
:8081
:8080
Client
Катимся к успеху
‣ In-place update

‣ Proxy

‣ Resource management

‣ Client side LB
31
vegeta
KISS FTW
32
cards API:8081
:8080
ansible
transactions
API
vegeta
$ echo "GET http://localhost” 
| vegeta attack 
| vegeta report
33
vegeta
$ echo "GET http://localhost” 
| vegeta attack 
| vegeta report
34
vegeta
$ echo "GET http://localhost” 
| vegeta attack 
| vegeta report
35
vegeta
$ echo "GET http://localhost” | vegeta attack | vegeta report


Requests [total, rate] 500, 50.10
Duration [total, ...
ansible
$ ansible-playbook 
-i inventory 
playbook.yml
37
ansible
$ ansible-playbook 
-i inventory 
playbook.yml
38
ansible
$ ansible-playbook 
-i inventory 
playbook.yml
39
ansible inventory
[frontend]
server1
server2
[backend]
server3
[backend:vars]
timeout = 2s
40
ansible inventory
[frontend]
server1
server2
[backend]
server3
[backend:vars]
timeout = 2s
41
ansible inventory
[frontend]
server1
server2
[backend]
server3
[backend:vars]
timeout = 2s
42
ansible inventory
[frontend]
server1
server2
[backend]
server3
[backend:vars]
timeout = 2s
43
ansible playbook
- hosts: frontend
tasks:
- apt:
name: haproxy
update_cache: yes
- service:
name: haproxy
enabled: yes
sta...
ansible playbook
- hosts: frontend
tasks:
- apt:
name: haproxy
update_cache: yes
- service:
name: haproxy
enabled: yes
sta...
ansible playbook
- hosts: frontend
tasks:
- apt:
name: haproxy
update_cache: yes
- service:
name: haproxy
enabled: yes
sta...
В прод!
Упс
48
t
:(
old
new
Клиенты уходят!
Катимся к успеху
‣ In-place update

‣ Proxy

‣ Resource management

‣ Client side LB
50
Катимся к успеху
‣ In-place update

‣ Proxy

‣ Resource management

‣ Client side LB
51
Proxy has arrived
52
cards API
transactions
API 

oldHAProxy
:8080
:8082
Временная избыточность
53
cards API
transactions
API 

oldHAProxy
transactions
API

new
:8080
:8082
:8083
Временная избыточность
54
cards API
transactions
API 

oldHAProxy
transactions
API

new
:8080
:8082
:8083
Ну норм чо
55
cards API
HAProxy
transactions
API

new
:8080
:8083
HAProxy
56
haproxy -f /my.conf -D -p /my.pid
HAProxy
57
haproxy -f /my.conf -D -p /my.pid
HAProxy
58
haproxy -f /my.conf -D -p /my.pid
Обновляем конфиг HAProxy
59
haproxy -f /my.conf -D -p /my.pid
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
Обновляем конфиг HAProxy
60
haproxy -f /my.conf -D -p /my.pid
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
Обновляем конфиг HAProxy
61
haproxy -f /my.conf -D -p /my.pid
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
finish...
Обновляем конфиг HAProxy
62
haproxy -f /my.conf -D -p /my.pid
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
finish...
Обновляем конфиг HAProxy
63
haproxy -f /my.conf -D -p /my.pid
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
finish...
Обновляем конфиг HAProxy
64
haproxy -f /my.conf -D -p /my.pid
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
finish...
Обновляем конфиг HAProxy
65
haproxy -f /my.conf -D -p /my.pid 
-sf $(cat /my.pid)
Катай!
Ну почемууу?
67
t
old
new
haproxy
1 2
3 4
Ну почемууу?
68
t
old
new
haproxy
1 2
3 4
Ну почемууу?
69
t
old
new
haproxy
1 2
3 4
Ну почемууу?
70
t
old
new
haproxy
1 2
3 4
Ну почемууу?
71
t
old
new
haproxy
1 2
3 4
Ну почемууу?
72
t
old
new
haproxy
1 2
3 4
Ну почемууу?
73
t
old
new
haproxy
1 2
3 4
Ну почемууу?
74
t
old
new
haproxy
1 2
3 4
Ну почемууу?
75
t
old
new
haproxy
1 2
3 4
Healthcheck идёт на помощь!
@RequestMapping("/health")

public String getHealth() {

return "OK";

}
76
Вжух!
Как получилось
‣ Победили недоступность :)

‣ Приложения потребовали только добавления
healthcheck’ов :)
‣ Не валим прилож...
Как получилось
‣ Победили недоступность :)

‣ Приложения потребовали только добавления
healthcheck’ов :)
‣ Не валим прилож...
80
cards API transactions API
HAProxy
:8080
:8082
81
cards API transactions API
HAProxy
:8080
:8082
82
UI UI UI
API
API
API
API
API
API
API
API
API
API
API
API
API
DB
DB
DB
Катимся к успеху
‣ In-place update

‣ Proxy

‣ Resource management

‣ Client side LB
83
Катимся к успеху
‣ In-place update

‣ Proxy

‣ Resource management

‣ Client side LB
84
Mesos, Marathon & Co.
85
Host 1
Host 2
Host 5 Host 4
Host 3
Mesos, Marathon & Co.
86
Host 1
Host 2
Host 5
Host 3
Host 4
a
a
a
a
a
a = mesos agent
Mesos, Marathon & Co.
87
Host 1
Host 2
Host 5
Host 3
Host 4
a
mesos

master
a
a
a
a
Mesos, Marathon & Co.
88
Host 1
Host 2
Host 5
Host 3
Host 4
a
mesos

master
a
a
a
a
marathon
Mesos, Marathon & Co.
89
Host 1
Host 2
Host 5
Host 3
Host 4
a
mesos

master
a
a
a
a
marathon
app
manifest
Mesos, Marathon & Co.
90
Host 1
Host 2
Host 5
Host 3
Host 4
a
mesos

master
a
a
a
a
marathon
app
app
app
А HAProxy кто будет настраивать?
91
marathon
marathon-lb
marathon_lb.py
HAProxy
Ставь!
HAProxy socket flags
‣ SO_REUSEADDR - ignore TIME_WAIT

‣ SO_REUSEPORT - bind same IP:PORT
93
94
spring-boot-starter-web
95
<project …>
<artifactId>spring-boot-starter-web</artifactId>
<!-- … -->
<dependencies>
<depende...
spring-boot-starter-web
96
<project …>
<artifactId>spring-boot-starter-web</artifactId>
<!-- … -->
<dependencies>
<depende...
Tomcat
97
public void bind() throws Exception {
this.serverSock = ServerSocketChannel.open();
this.socketProperties.setPro...
Tomcat
98
public void bind() throws Exception {
this.serverSock = ServerSocketChannel.open();
this.socketProperties.setPro...
Tomcat
99
public void bind() throws Exception {
this.serverSock = ServerSocketChannel.open();
this.socketProperties.setPro...
Tomcat
100
public void bind() throws Exception {
this.serverSock = ServerSocketChannel.open();
this.socketProperties.setPr...
Tomcat
101
public void bind() throws Exception {
this.serverSock = ServerSocketChannel.open();
this.socketProperties.setPr...
Tomcat
102
public void setProperties(ServerSocket socket) throws SocketException {
if (this.soReuseAddress != null) {
sock...
Tomcat
103
public void setProperties(ServerSocket socket) throws SocketException {
if (this.soReuseAddress != null) {
sock...
Копай глубже!
linux/net/core/sock_reuseport.c
105
linux/net/core/sock_reuseport.c
106
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
linux/net/core/sock_reuseport.c
107
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
linux/net/core/sock_reuseport.c
108
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
linux/net/core/sock_reuseport.c
109
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
linux/net/core/sock_reuseport.c
110
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
linux/net/core/sock_reuseport.c
111
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
linux/net/core/sock_reuseport.c
112
struct sock *reuseport_select_sock(hash, …)
{
// …
struct sock *sk2 = NULL;
reuse = rc...
struct sock *__inet_lookup_listener(…)
{
u32 phash = 0;
struct sock *result = NULL;
phash = inet_ehashfn(net, daddr, hnum,...
struct sock *__inet_lookup_listener(…)
{
u32 phash = 0;
struct sock *result = NULL;
phash = inet_ehashfn(net, daddr, hnum,...
struct sock *__inet_lookup_listener(…)
{
u32 phash = 0;
struct sock *result = NULL;
phash = inet_ehashfn(net, daddr, hnum,...
static u32 inet_ehashfn(const struct net *net,
const __be32 laddr, const __u16 lport,
const __be32 faddr, const __be16 fpo...
static u32 inet_ehashfn(const struct net *net,
const __be32 laddr, const __u16 lport,
const __be32 faddr, const __be16 fpo...
static u32 inet_ehashfn(const struct net *net,
const __be32 laddr, const __u16 lport,
const __be32 faddr, const __be16 fpo...
struct sock *__inet_lookup_listener(const __be32 saddr,
__be16 sport, …)
{
u32 phash = 0;
struct sock *result = NULL;
phas...
Here comes the pain
‣ Запросы на соединение приходят на случайный socket

‣ Старый процесс HAProxy тоже получает запросы н...
Here comes the pain
‣ Запросы на соединение приходят на случайный socket

‣ Старый процесс HAProxy тоже получает запросы н...
Here comes the pain
‣ Запросы на соединение приходят на случайный socket

‣ Старый процесс HAProxy тоже получает запросы н...
Here comes the pain
‣ Запросы на соединение приходят на случайный socket

‣ Старый процесс HAProxy тоже получает запросы н...
Решения?
‣ Ничего не делать

‣ Низкоуровневые заклятья для HAProxy

‣ Отказаться от HAProxy
124
Решения?
‣ Ничего не делать

‣ Низкоуровневые заклятья для HAProxy

‣ Отказаться от HAProxy
125
Решения?
‣ Ничего не делать

‣ Низкоуровневые заклятья для HAProxy

‣ Отказаться от HAProxy
126
Катимся к успеху
‣ In-place update

‣ Proxy

‣ Resource management

‣ Client side LB
127
We are server side
128
app 1
app 2
proxy
Лишнее звено
129
app 1
app 2
proxy
Client side!
130
app 1
app 2
A piece of proxy
‣ Список сервисов

‣ Балансировщик
131
A piece of proxy
‣ Список сервисов

‣ Балансировщик
132
Netflix Ribbon
133
HttpResourceGroup resourceGroup = Ribbon

.createHttpResourceGroup(
“transactionsClient",
ClientOptions...
Netflix Ribbon
134
HttpRequestTemplate requestTemplate = resourceGroup
.newTemplateBuilder("requestTemplate")
.withMethod(...
Netflix Ribbon
135
public class BaseLoadBalancer extends AbstractLoadBalancer ... {
protected IRule rule = new RoundRobinR...
Netflix Ribbon
136
public class BaseLoadBalancer extends AbstractLoadBalancer ... {
protected IRule rule = new RoundRobinR...
Netflix Ribbon
137
public class BaseLoadBalancer extends AbstractLoadBalancer ... {
protected IRule rule = new RoundRobinR...
Netflix Ribbon
138
public class BaseLoadBalancer extends AbstractLoadBalancer ... {
protected IRule rule = new RoundRobinR...
Netflix Ribbon
139
Timer lbTimer = // …;
void setupPingTask() {
lbTimer.schedule(
new PingTask(),
0,
pingIntervalSeconds *...
Netflix Ribbon
140
public Server chooseServer(Object key) {
try {
return rule.choose(key);
} catch (Exception e) {
log.war...
Netflix Ribbon
141
public Server chooseServer(Object key) {
try {
return rule.choose(key);
} catch (Exception e) {
log.war...
Netflix Ribbon
142
public class RoundRobinRule extends AbstractLoadBalancerRule {
public Server choose(Object key) {
nextS...
Netflix Ribbon
143
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();...
Netflix Ribbon
144
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();...
Netflix Ribbon
145
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();...
Spring cloud Netflix
146
app 1
eureka
server
app 2
app 3
app 4
Spring cloud Netflix
147
app 1
eureka
server
app 2
app 3
app 4
Eureka server
148
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] ...
Eureka server
149
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] ...
Eureka client
150
@SpringBootApplication
@RestController
@EnableEurekaClient
public class CardsApi {
public static void ma...
Eureka client
151
@SpringBootApplication
@RestController
@EnableEurekaClient
public class CardsApi {
public static void ma...
‣ Прямая коммуникация

‣ Нет точки отказа в виде 

прокси-сервера

‣ Можно задавать правила
per-app

‣ Дополнительная логи...
‣ Прямая коммуникация

‣ Нет точки отказа в виде 

прокси-сервера

‣ Можно задавать правила
per-app

‣ Дополнительная логи...
Выдохнули
‣ In-place обновление - самое простое. 

Но вызывает длинные окна недоступности.

‣ Недоступность можно обеспечи...
Выдохнули
‣ In-place обновление - самое простое. 

Но вызывает длинные окна недоступности.

‣ Недоступность можно обеспечи...
Выдохнули
‣ In-place обновление - самое простое. 

Но вызывает длинные окна недоступности.

‣ Недоступность можно обеспечи...
Выдохнули
‣ In-place обновление - самое простое. 

Но вызывает длинные окна недоступности.

‣ Недоступность можно обеспечи...
Спасибо за внимание!
stereohorse/jpoint2017
stereohorse
JPoint 2017 - Where is my service, dude?
Upcoming SlideShare
Loading in …5
×

JPoint 2017 - Where is my service, dude?

331 views

Published on

How to replace the wheel of a car on the go? How to change the gear in the mechanism, so that everything does not break? And if at the same time the remaining gears are constantly falling off? And the mechanism is on fire? And you in hell?

In this talk, I will show how to update Java-microservices without unavailability for customers.

We will touch many different tools, such as HAProxy, Ansible, Vegeta, Mesos / Marathon, Ribbon / Eureka; there will be loads of configs and a bottomless ocean of pain of distributed systems, into which we will consistently sink.

Published in: Software
  • Be the first to comment

  • Be the first to like this

JPoint 2017 - Where is my service, dude?

  1. 1. Где мой сервис, чувак?
  2. 2. Việt [вьет] 2
  3. 3. Việt [вьет] 3 ‣ Пишу код больше 10 лет ‣ Начинал как фронтендер ‣ Написал распределенный компилятор - полюбил бекенд ‣ Больше 6 лет на Java
  4. 4. Việt [вьет] 4 ‣ Пишу код больше 10 лет ‣ Начинал как фронтендер ‣ Написал распределенный компилятор - полюбил бекенд ‣ Больше 6 лет на Java
  5. 5. Việt [вьет] 5 ‣ Пишу код больше 10 лет ‣ Начинал как фронтендер ‣ Написал распределенный компилятор - полюбил бекенд ‣ Больше 6 лет на Java
  6. 6. 6
  7. 7. 7 DevOps
  8. 8. 8 Конфиги! Они везде
  9. 9. @EnableEverything @FixThis @FixThat @DoWhatever public class App { // no code - no cry } 9 :(
  10. 10. О чём доклад? ‣ Обновление микросервисов 
 без недоступности
 ‣ Тулзы ‣ Конфиги ‣ Заклятья 10
  11. 11. О чём доклад? ‣ Обновление микросервисов 
 без недоступности
 ‣ Тулзы ‣ Конфиги ‣ Заклятья 11
  12. 12. 12 Монолит Микросервисы
  13. 13. 13 UI UI UI
  14. 14. 14 UI UI UI API API API API API API API API API
  15. 15. 15 UI UI UI API API API API API API API API API API API API API
  16. 16. 16 UI UI UI API API API API API API API API API API API API API DB DB DB
  17. 17. 17 UI UI UI API API API API API API API API API API API API API DB DB DB
  18. 18. 18 UI UI UI API API API API API API API API API API API API API DB DB DB
  19. 19. 19 UI UI UI API API API API API API API API API API API API API DB DB DB
  20. 20. А за окном… ‣ Сеть отваливается ‣ Серваки отваливаются ‣ Отвечают с задержкой ‣ Все такое неоднородное ‣ Толпа вредных админов 20
  21. 21. А за окном… ‣ Сеть отваливается ‣ Серваки отваливаются ‣ Отвечают с задержкой ‣ Все такое неоднородное ‣ Толпа вредных админов 21
  22. 22. А за окном… ‣ Сеть отваливается ‣ Серваки отваливаются ‣ Отвечают с задержкой ‣ Все такое неоднородное ‣ Толпа вредных админов 22
  23. 23. А за окном… ‣ Сеть отваливается ‣ Серваки отваливаются ‣ Отвечают с задержкой ‣ Все такое неоднородное ‣ Толпа вредных админов 23 ? ? ?
  24. 24. Следствия ‣ В распределенных системах 
 отказы - обычное дело ‣ Нужно воспринимать это 
 как естественный атрибут системы ‣ Принять и осознанно проектировать систему 24
  25. 25. “Hope 
 is not 
 a strategy” 25
  26. 26. KISS FTW 27 cards API transactions API :8081 :8080 Client
  27. 27. KISS FTW 28 cards API transactions API :8081 :8080 Client
  28. 28. Где мои деньги?!
  29. 29. KISS FTW 30 cards API transactions API :8081 :8080 Client
  30. 30. Катимся к успеху ‣ In-place update ‣ Proxy ‣ Resource management ‣ Client side LB 31
  31. 31. vegeta KISS FTW 32 cards API:8081 :8080 ansible transactions API
  32. 32. vegeta $ echo "GET http://localhost” | vegeta attack | vegeta report 33
  33. 33. vegeta $ echo "GET http://localhost” | vegeta attack | vegeta report 34
  34. 34. vegeta $ echo "GET http://localhost” | vegeta attack | vegeta report 35
  35. 35. vegeta $ echo "GET http://localhost” | vegeta attack | vegeta report 
 Requests [total, rate] 500, 50.10 Duration [total, attack, wait] 9.9s, 9.9s, 8.2ms Latencies [mean, 50, 95, 99, max] 9.7ms, 7.4ms, … Bytes In [total, mean] 73260, 146.52 Bytes Out [total, mean] 0, 0.00 Success [ratio] 84.20% Status Codes [code:count] 500:79 200:421 36
  36. 36. ansible $ ansible-playbook -i inventory playbook.yml 37
  37. 37. ansible $ ansible-playbook -i inventory playbook.yml 38
  38. 38. ansible $ ansible-playbook -i inventory playbook.yml 39
  39. 39. ansible inventory [frontend] server1 server2 [backend] server3 [backend:vars] timeout = 2s 40
  40. 40. ansible inventory [frontend] server1 server2 [backend] server3 [backend:vars] timeout = 2s 41
  41. 41. ansible inventory [frontend] server1 server2 [backend] server3 [backend:vars] timeout = 2s 42
  42. 42. ansible inventory [frontend] server1 server2 [backend] server3 [backend:vars] timeout = 2s 43
  43. 43. ansible playbook - hosts: frontend tasks: - apt: name: haproxy update_cache: yes - service: name: haproxy enabled: yes state: started 44
  44. 44. ansible playbook - hosts: frontend tasks: - apt: name: haproxy update_cache: yes - service: name: haproxy enabled: yes state: started 45
  45. 45. ansible playbook - hosts: frontend tasks: - apt: name: haproxy update_cache: yes - service: name: haproxy enabled: yes state: started 46
  46. 46. В прод!
  47. 47. Упс 48 t :( old new
  48. 48. Клиенты уходят!
  49. 49. Катимся к успеху ‣ In-place update ‣ Proxy ‣ Resource management ‣ Client side LB 50
  50. 50. Катимся к успеху ‣ In-place update ‣ Proxy ‣ Resource management ‣ Client side LB 51
  51. 51. Proxy has arrived 52 cards API transactions API 
 oldHAProxy :8080 :8082
  52. 52. Временная избыточность 53 cards API transactions API 
 oldHAProxy transactions API
 new :8080 :8082 :8083
  53. 53. Временная избыточность 54 cards API transactions API 
 oldHAProxy transactions API
 new :8080 :8082 :8083
  54. 54. Ну норм чо 55 cards API HAProxy transactions API
 new :8080 :8083
  55. 55. HAProxy 56 haproxy -f /my.conf -D -p /my.pid
  56. 56. HAProxy 57 haproxy -f /my.conf -D -p /my.pid
  57. 57. HAProxy 58 haproxy -f /my.conf -D -p /my.pid
  58. 58. Обновляем конфиг HAProxy 59 haproxy -f /my.conf -D -p /my.pid haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid)
  59. 59. Обновляем конфиг HAProxy 60 haproxy -f /my.conf -D -p /my.pid haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid)
  60. 60. Обновляем конфиг HAProxy 61 haproxy -f /my.conf -D -p /my.pid haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid) finish, please
  61. 61. Обновляем конфиг HAProxy 62 haproxy -f /my.conf -D -p /my.pid haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid) finish, please OK!
  62. 62. Обновляем конфиг HAProxy 63 haproxy -f /my.conf -D -p /my.pid haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid) finish, please OK! Unbind ports
  63. 63. Обновляем конфиг HAProxy 64 haproxy -f /my.conf -D -p /my.pid haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid) finish, please OK! Unbind ports Serve last connections
  64. 64. Обновляем конфиг HAProxy 65 haproxy -f /my.conf -D -p /my.pid -sf $(cat /my.pid)
  65. 65. Катай!
  66. 66. Ну почемууу? 67 t old new haproxy 1 2 3 4
  67. 67. Ну почемууу? 68 t old new haproxy 1 2 3 4
  68. 68. Ну почемууу? 69 t old new haproxy 1 2 3 4
  69. 69. Ну почемууу? 70 t old new haproxy 1 2 3 4
  70. 70. Ну почемууу? 71 t old new haproxy 1 2 3 4
  71. 71. Ну почемууу? 72 t old new haproxy 1 2 3 4
  72. 72. Ну почемууу? 73 t old new haproxy 1 2 3 4
  73. 73. Ну почемууу? 74 t old new haproxy 1 2 3 4
  74. 74. Ну почемууу? 75 t old new haproxy 1 2 3 4
  75. 75. Healthcheck идёт на помощь! @RequestMapping("/health")
 public String getHealth() {
 return "OK";
 } 76
  76. 76. Вжух!
  77. 77. Как получилось ‣ Победили недоступность :) ‣ Приложения потребовали только добавления healthcheck’ов :) ‣ Не валим приложения на старте :)
 ‣ Усложнился deployment :( 78
  78. 78. Как получилось ‣ Победили недоступность :) ‣ Приложения потребовали только добавления healthcheck’ов :) ‣ Не валим приложения на старте :)
 ‣ Усложнился deployment :( 79
  79. 79. 80 cards API transactions API HAProxy :8080 :8082
  80. 80. 81 cards API transactions API HAProxy :8080 :8082
  81. 81. 82 UI UI UI API API API API API API API API API API API API API DB DB DB
  82. 82. Катимся к успеху ‣ In-place update ‣ Proxy ‣ Resource management ‣ Client side LB 83
  83. 83. Катимся к успеху ‣ In-place update ‣ Proxy ‣ Resource management ‣ Client side LB 84
  84. 84. Mesos, Marathon & Co. 85 Host 1 Host 2 Host 5 Host 4 Host 3
  85. 85. Mesos, Marathon & Co. 86 Host 1 Host 2 Host 5 Host 3 Host 4 a a a a a a = mesos agent
  86. 86. Mesos, Marathon & Co. 87 Host 1 Host 2 Host 5 Host 3 Host 4 a mesos master a a a a
  87. 87. Mesos, Marathon & Co. 88 Host 1 Host 2 Host 5 Host 3 Host 4 a mesos master a a a a marathon
  88. 88. Mesos, Marathon & Co. 89 Host 1 Host 2 Host 5 Host 3 Host 4 a mesos master a a a a marathon app manifest
  89. 89. Mesos, Marathon & Co. 90 Host 1 Host 2 Host 5 Host 3 Host 4 a mesos master a a a a marathon app app app
  90. 90. А HAProxy кто будет настраивать? 91 marathon marathon-lb marathon_lb.py HAProxy
  91. 91. Ставь!
  92. 92. HAProxy socket flags ‣ SO_REUSEADDR - ignore TIME_WAIT ‣ SO_REUSEPORT - bind same IP:PORT 93
  93. 93. 94
  94. 94. spring-boot-starter-web 95 <project …> <artifactId>spring-boot-starter-web</artifactId> <!-- … --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> </dependencies> </project>
  95. 95. spring-boot-starter-web 96 <project …> <artifactId>spring-boot-starter-web</artifactId> <!-- … --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> </dependencies> </project>
  96. 96. Tomcat 97 public void bind() throws Exception { this.serverSock = ServerSocketChannel.open(); this.socketProperties.setProperties(this.serverSock.socket()); InetSocketAddress addr = // …; this.serverSock.socket().bind(addr, this.getBacklog()); // … }
  97. 97. Tomcat 98 public void bind() throws Exception { this.serverSock = ServerSocketChannel.open(); this.socketProperties.setProperties(this.serverSock.socket()); InetSocketAddress addr = // …; this.serverSock.socket().bind(addr, this.getBacklog()); // … }
  98. 98. Tomcat 99 public void bind() throws Exception { this.serverSock = ServerSocketChannel.open(); this.socketProperties.setProperties(this.serverSock.socket()); InetSocketAddress addr = // …; this.serverSock.socket().bind(addr, this.getBacklog()); // … }
  99. 99. Tomcat 100 public void bind() throws Exception { this.serverSock = ServerSocketChannel.open(); this.socketProperties.setProperties(this.serverSock.socket()); InetSocketAddress addr = // …; this.serverSock.socket().bind(addr, this.getBacklog()); // … }
  100. 100. Tomcat 101 public void bind() throws Exception { this.serverSock = ServerSocketChannel.open(); this.socketProperties.setProperties(this.serverSock.socket()); InetSocketAddress addr = // …; this.serverSock.socket().bind(addr, this.getBacklog()); // … }
  101. 101. Tomcat 102 public void setProperties(ServerSocket socket) throws SocketException { if (this.soReuseAddress != null) { socket.setReuseAddress(this.soReuseAddress.booleanValue()); } // … }
  102. 102. Tomcat 103 public void setProperties(ServerSocket socket) throws SocketException { if (this.soReuseAddress != null) { socket.setReuseAddress(true); } if (this.soReusePort != null) { socket.setReusePort(true); } // … }
  103. 103. Копай глубже!
  104. 104. linux/net/core/sock_reuseport.c 105
  105. 105. linux/net/core/sock_reuseport.c 106 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  106. 106. linux/net/core/sock_reuseport.c 107 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  107. 107. linux/net/core/sock_reuseport.c 108 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  108. 108. linux/net/core/sock_reuseport.c 109 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  109. 109. linux/net/core/sock_reuseport.c 110 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  110. 110. linux/net/core/sock_reuseport.c 111 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  111. 111. linux/net/core/sock_reuseport.c 112 struct sock *reuseport_select_sock(hash, …) { // … struct sock *sk2 = NULL; reuse = rcu_dereference(sk->sk_reuseport_cb); socks = READ_ONCE(reuse->num_socks); sk2 = reuse->socks[reciprocal_scale(hash, socks)]; // … return sk2; }
  112. 112. struct sock *__inet_lookup_listener(…) { u32 phash = 0; struct sock *result = NULL; phash = inet_ehashfn(net, daddr, hnum, saddr, sport); result = reuseport_select_sock(phash, …); // … return result; } linux/net/ipv4/inet_hashtables.c 113
  113. 113. struct sock *__inet_lookup_listener(…) { u32 phash = 0; struct sock *result = NULL; phash = inet_ehashfn(net, daddr, hnum, saddr, sport); result = reuseport_select_sock(phash, …); // … return result; } linux/net/ipv4/inet_hashtables.c 114
  114. 114. struct sock *__inet_lookup_listener(…) { u32 phash = 0; struct sock *result = NULL; phash = inet_ehashfn(net, daddr, hnum, saddr, sport); result = reuseport_select_sock(phash, …); // … return result; } linux/net/ipv4/inet_hashtables.c 115
  115. 115. static u32 inet_ehashfn(const struct net *net, const __be32 laddr, const __u16 lport, const __be32 faddr, const __be16 fport) { // … return __inet_ehashfn(laddr, lport, faddr, fport, inet_ehash_secret + net_hash_mix(net)); } linux/net/ipv4/inet_hashtables.c 116
  116. 116. static u32 inet_ehashfn(const struct net *net, const __be32 laddr, const __u16 lport, const __be32 faddr, const __be16 fport) { // … return __inet_ehashfn(laddr, lport, faddr, fport, inet_ehash_secret + net_hash_mix(net)); } linux/net/ipv4/inet_hashtables.c 117
  117. 117. static u32 inet_ehashfn(const struct net *net, const __be32 laddr, const __u16 lport, const __be32 faddr, const __be16 fport) { // … return __inet_ehashfn(laddr, lport, faddr, fport, inet_ehash_secret + net_hash_mix(net)); } linux/net/ipv4/inet_hashtables.c 118
  118. 118. struct sock *__inet_lookup_listener(const __be32 saddr, __be16 sport, …) { u32 phash = 0; struct sock *result = NULL; phash = inet_ehashfn(net, daddr, hnum, saddr, sport); result = reuseport_select_sock(phash, …); // … return result; } linux/net/ipv4/inet_hashtables.c 119
  119. 119. Here comes the pain ‣ Запросы на соединение приходят на случайный socket ‣ Старый процесс HAProxy тоже получает запросы на connect ‣ При большом RPS соединения кладутся в очередь ‣ Старый HAProxy может завершиться, 
 когда в очереди уже есть соединения :( 120
  120. 120. Here comes the pain ‣ Запросы на соединение приходят на случайный socket ‣ Старый процесс HAProxy тоже получает запросы на connect ‣ При большом RPS соединения кладутся в очередь ‣ Старый HAProxy может завершиться, 
 когда в очереди уже есть соединения :( 121
  121. 121. Here comes the pain ‣ Запросы на соединение приходят на случайный socket ‣ Старый процесс HAProxy тоже получает запросы на connect ‣ При большом RPS соединения кладутся в очередь ‣ Старый HAProxy может завершиться, 
 когда в очереди уже есть соединения :( 122
  122. 122. Here comes the pain ‣ Запросы на соединение приходят на случайный socket ‣ Старый процесс HAProxy тоже получает запросы на connect ‣ При большом RPS соединения кладутся в очередь ‣ Старый HAProxy может завершиться, 
 когда в очереди уже есть соединения :( 123
  123. 123. Решения? ‣ Ничего не делать ‣ Низкоуровневые заклятья для HAProxy ‣ Отказаться от HAProxy 124
  124. 124. Решения? ‣ Ничего не делать ‣ Низкоуровневые заклятья для HAProxy ‣ Отказаться от HAProxy 125
  125. 125. Решения? ‣ Ничего не делать ‣ Низкоуровневые заклятья для HAProxy ‣ Отказаться от HAProxy 126
  126. 126. Катимся к успеху ‣ In-place update ‣ Proxy ‣ Resource management ‣ Client side LB 127
  127. 127. We are server side 128 app 1 app 2 proxy
  128. 128. Лишнее звено 129 app 1 app 2 proxy
  129. 129. Client side! 130 app 1 app 2
  130. 130. A piece of proxy ‣ Список сервисов ‣ Балансировщик 131
  131. 131. A piece of proxy ‣ Список сервисов ‣ Балансировщик 132
  132. 132. Netflix Ribbon 133 HttpResourceGroup resourceGroup = Ribbon
 .createHttpResourceGroup( “transactionsClient", ClientOptions.create() .withConfigurationBasedServerList(“srv1:8080, srv2:8088”) );
  133. 133. Netflix Ribbon 134 HttpRequestTemplate requestTemplate = resourceGroup .newTemplateBuilder("requestTemplate") .withMethod(“POST") .withUriTemplate("/transactions") .build();
  134. 134. Netflix Ribbon 135 public class BaseLoadBalancer extends AbstractLoadBalancer ... { protected IRule rule = new RoundRobinRule(); protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>()); protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList<Server>()); }
  135. 135. Netflix Ribbon 136 public class BaseLoadBalancer extends AbstractLoadBalancer ... { protected IRule rule = new RoundRobinRule(); protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>()); protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList<Server>()); }
  136. 136. Netflix Ribbon 137 public class BaseLoadBalancer extends AbstractLoadBalancer ... { protected IRule rule = new RoundRobinRule(); protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>()); protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList<Server>()); }
  137. 137. Netflix Ribbon 138 public class BaseLoadBalancer extends AbstractLoadBalancer ... { protected IRule rule = new RoundRobinRule(); protected volatile List<Server> allServerList = Collections .synchronizedList(new ArrayList<Server>()); protected volatile List<Server> upServerList = Collections .synchronizedList(new ArrayList<Server>()); }
  138. 138. Netflix Ribbon 139 Timer lbTimer = // …; void setupPingTask() { lbTimer.schedule( new PingTask(), 0, pingIntervalSeconds * 1000); }
  139. 139. Netflix Ribbon 140 public Server chooseServer(Object key) { try { return rule.choose(key); } catch (Exception e) { log.warn("LoadBalancer: Error choosing server for key {}”, key, e); return null; } }
  140. 140. Netflix Ribbon 141 public Server chooseServer(Object key) { try { return rule.choose(key); } catch (Exception e) { log.warn("LoadBalancer: Error choosing server for key {}”, key, e); return null; } }
  141. 141. Netflix Ribbon 142 public class RoundRobinRule extends AbstractLoadBalancerRule { public Server choose(Object key) { nextServerIndex = incrementAndGetModulo(serverCount); return allServers.get(nextServerIndex); } }
  142. 142. Netflix Ribbon 143 private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } }
  143. 143. Netflix Ribbon 144 private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } }
  144. 144. Netflix Ribbon 145 private int incrementAndGetModulo(int modulo) { for (;;) { int current = nextServerCyclicCounter.get(); int next = (current + 1) % modulo; if (nextServerCyclicCounter.compareAndSet(current, next)) return next; } }
  145. 145. Spring cloud Netflix 146 app 1 eureka server app 2 app 3 app 4
  146. 146. Spring cloud Netflix 147 app 1 eureka server app 2 app 3 app 4
  147. 147. Eureka server 148 @SpringBootApplication @EnableEurekaServer public class EurekaServer { public static void main(String[] args) { new SpringApplicationBuilder(EurekaServer.class) .web(true) .run(args); } }
  148. 148. Eureka server 149 @SpringBootApplication @EnableEurekaServer public class EurekaServer { public static void main(String[] args) { new SpringApplicationBuilder(EurekaServer.class) .web(true) .run(args); } }
  149. 149. Eureka client 150 @SpringBootApplication @RestController @EnableEurekaClient public class CardsApi { public static void main(String... args) { SpringApplication.run(CardsApi.class, args); } }
  150. 150. Eureka client 151 @SpringBootApplication @RestController @EnableEurekaClient public class CardsApi { public static void main(String... args) { SpringApplication.run(CardsApi.class, args); } }
  151. 151. ‣ Прямая коммуникация ‣ Нет точки отказа в виде 
 прокси-сервера ‣ Можно задавать правила per-app
 ‣ Дополнительная логика на клиенте 152 Client-side Server-side ‣ Прозрачно для приложений ‣ Можно задавать глобальные правила
 
 ‣ Дополнительные точки отказа vs Load balancing
  152. 152. ‣ Прямая коммуникация ‣ Нет точки отказа в виде 
 прокси-сервера ‣ Можно задавать правила per-app
 ‣ Дополнительная логика на клиенте 153 Client-side Server-side ‣ Прозрачно для приложений ‣ Можно задавать глобальные правила
 
 ‣ Дополнительные точки отказа vs Load balancing
  153. 153. Выдохнули ‣ In-place обновление - самое простое. 
 Но вызывает длинные окна недоступности. ‣ Недоступность можно обеспечить настройками прокси 
 и оркестрацией с healthcheck’ами ‣ Для large-scale оркестрации можно использовать 
 Mesos/Marathon + Marathon-LB ‣ Альтернатива проксям - Client-side Service Discovery 154
  154. 154. Выдохнули ‣ In-place обновление - самое простое. 
 Но вызывает длинные окна недоступности. ‣ Недоступность можно обеспечить настройками прокси 
 и оркестрацией с healthcheck’ами ‣ Для large-scale оркестрации можно использовать 
 Mesos/Marathon + Marathon-LB ‣ Альтернатива проксям - Client-side Service Discovery 155
  155. 155. Выдохнули ‣ In-place обновление - самое простое. 
 Но вызывает длинные окна недоступности. ‣ Недоступность можно обеспечить настройками прокси 
 и оркестрацией с healthcheck’ами ‣ Для large-scale оркестрации можно использовать 
 Mesos/Marathon + Marathon-LB ‣ Альтернатива проксям - Client-side Service Discovery 156
  156. 156. Выдохнули ‣ In-place обновление - самое простое. 
 Но вызывает длинные окна недоступности. ‣ Недоступность можно обеспечить настройками прокси 
 и оркестрацией с healthcheck’ами ‣ Для large-scale оркестрации можно использовать 
 Mesos/Marathon + Marathon-LB ‣ Альтернатива проксям - Client-side Service Discovery 157
  157. 157. Спасибо за внимание! stereohorse/jpoint2017 stereohorse

×