Play with Nginx Part 1 [Polling Strategy and Weighted Polling]

1: Download Nginx

Download it from the Nginx official website. I downloaded the Windoiws environment package nginx-1.23.3

Two: Backend service preparation

I chose the Java back-end service. Of course, if you are a front-end programmer or a full-stack developer, you can choose nodejs to build the service. This is only for testing.

Controller interface

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class LoadBalanceController {

    @Autowired
    private ILoadBalanceService loadBalanceService;

    @GetMapping("/test")
    public Object whoAmI(){
        return loadBalanceService.whoAmI();
    }


}

Service layer interface and implementation class

public interface ILoadBalanceService {

    Object whoAmI();

}



import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class LoadBalanceServiceImpl implements ILoadBalanceService {

    @Value("${server.port}")
    public Integer serverPort;

    @Override
    public Object whoAmI() {
        ServerInfo info = new ServerInfo();
        info.setCode(200);
        info.setServer("Server#5");
        info.setPort(serverPort);
        return info;
    }
}

Configuration file

## log
debug=true
logging.level.org.springframework.web = DEBUG

#log
logging.path=./log

#encoding
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true

server.servlet.context-path=/load_balance
server.port=8885

pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
        <relativePath/>
    </parent>

    <groupId>com.lemon</groupId>
    <artifactId>load-balance</artifactId>
    <version>1.0-Alpha</version>
    <name>Load Balance</name>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Here I use the specified configuration file to start five back-end codes respectively. The ports are 8881, 8882, 8883, 8884, and 8885. The interfaces can be accessed by using a browser to access the five back-end Java services separately.

Three: Configure nginx.conf

#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;
    #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    # '$status $body_bytes_sent "$http_referer" '
    # '"$http_user_agent" "$http_x_forwarded_for"';
    #access_log logs/access.log main;
    sendfile on;
    #tcp_nopush on;
    #keepalive_timeout 0;
    keepalive_timeout 65;
    #gzip on;
\t
upstream polling {
server 192.168.3.99:8881 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8882 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8883 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8884 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8885 max_fails=3 fail_timeout=30s;
}

    server {
        listen 88;
        server_name 192.168.3.180;

        location /load_balance {
proxy_pass http://polling/load_balance;
}
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            roothtml;
        }
    }
}

Use upstream to declare a group of cluster services. When the polling strategy is subsequently used, Nginx will select one of the services from this group of services based on the configured parameters. This test uses the polling strategy and implements the service function of eliminating downtime.

Introduction to parameters in upstream declaration

upstream polling { }

Among them, “polling” is a name I gave to this group of cluster services. This name can be arbitrarily named according to personal preference.

server 192.168.3.99:8881 max_fails=3 fail_timeout=30s;

The server declares a service, followed by the address of a service. max_fails declares how many times the service will be deleted without response. fail_timeout declares how long it will take to resume posting of this service after it is deleted. If there is no response after max_fails times after restoration, the service will be deleted again. Eliminate fail_timeout seconds from the cluster queue and loop in sequence until the service is available. Of course, you can use the default values without configuring max_fails and fail_timeout, such as server 192.168.3.99:8881; At this time, max_fails defaults to 3 times, and fail_timeout defaults to 10 seconds.

After the configuration is completed, start nginx. From the access interface, we can see that access will be polled among these 5 services. When one of the services is unavailable, it will be switched to the next service. When the service is restored, the head of the queue will be selected.

Introduction to parameters in server declaration

In the Nginx configuration file nginx.conf, the server parameter is usually used to define the configuration of the server block or virtual host. Each server block contains a set of directives that configure the behavior of a specific virtual host or server. Here are some important functions of the server parameter:

  1. Virtual host configuration: The server parameter allows you to define multiple virtual hosts. Each virtual host can listen to different domain names or IP addresses and provide different web services. This allows you to host multiple websites or applications on the same Nginx instance.

  2. Listening port and IP address: Through the listen directive, you can specify the port and IP address that the Nginx server block listens on. This allows you to distribute requests to different virtual hosts and even listen on different network interfaces.

  3. Domain name binding: You can use the server_name directive to define the domain name or host name of the virtual host. This allows Nginx to know how to handle requests from different domain names and thus route the requests to the appropriate virtual host.

  4. SSL/TLS configuration: The server parameter allows you to configure SSL/TLS encryption, including specifying an SSL certificate and other related options to provide a secure HTTPS connection.

  5. Request handling: In the server block, you can configure a series of directives to define how to handle requests, including proxying requests, caching, restricting access, and more. This allows you to define the behavior of your virtual host to suit your application’s needs.

  6. Logging: You can configure logging options to specify the location and format of the server block’s access and error logs. This helps monitor and troubleshoot issues.

  7. Reverse proxy: If you want Nginx to act as a reverse proxy server, you can configure proxy-related instructions in the server block, such as proxy_pass. This allows Nginx to proxy requests to backend servers for functions such as load balancing and caching.

  8. Cache configuration: You can configure the Nginx server block to enable HTTP caching to speed up the transmission of static resources and reduce the burden on the backend server.

  9. Access Control: Using the location block and other related directives, you can define how to control access to a specific URL path or resource. This includes allow/deny access, basic authentication, etc.

In short, the server parameter is used to define the configuration of the Nginx server block, allowing you to configure the behavior of the virtual host or server as needed to meet the needs of different applications and websites. You can define multiple server blocks in a single nginx.conf file to support multiple virtual hosts.

Here we use reverse proxy proxy_pass to declare the backend service, and introduce the backend service address configured in upstream polling { }. After nginx detects polling (a set of cluster service addresses we declared), it will Replace it with one of the servers in the upstream. The specific selection of the server is based on our configuration. Here we use the polling strategy.

Four: Statistics of polling times

In order to see the effect more intuitively, we write another statistical function to count the total number of visits and the number allocated to each server, so that we can feel the effect more intuitively.

Implementation code

import org.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class StartMain {

    public static void main(String[] args) {
        //statistics items
        int Server1 = 0;
        int Server2 = 0;
        int Server3 = 0;
        int Server4 = 0;
        int Server5 = 0;

        // deal with
        String getUrl = "http://192.168.3.180:88/load_balance/api/test";
        HttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(getUrl);
        try {
            httpGet.setHeader("Content-Type", "application/json;charset=UTF-8");
            httpGet.setHeader("Accept","application/json");
            for (int i = 0; i < 500; i + + ) {
                Thread.sleep(100);
                HttpResponse response = httpClient.execute(httpGet);
                // handle response
                HttpEntity entity = response.getEntity();
                String responseBody = EntityUtils.toString(entity, "UTF-8");
                JSONObject jsonObject = new JSONObject(responseBody);
                int port = jsonObject.getInt("port");
                System.out.println(port);
                if (8881 == port){
                    Server1++;
                } else if (8882 == port) {
                    Server2++;
                } else if (8883 == port) {
                    Server3++;
                } else if (8884 == port) {
                    Server4++;
                } else if (8885 == port) {
                    Server5++;
                }
            }
            System.out.println("Server#1,2,3,4,5 are assigned times respectively: " + Server1 + "," + Server2 + "," + Server3 + "," + Server4 + "," + Server5 );
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}

Need to import the maven coordinates of json

<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20210307</version>
</dependency>

Using the above code, we ran 500 rounds of requests. It can be seen that the requests were evenly distributed, and each service was evenly distributed 100 times.

The demo service is not down here. You can set it up and test it yourself.

Five: Weight polling

Modified upstream polling { } file

Weighted keyword: weight, the default value is 1.

8881, 8882, 8883, 8884, and 8885 are weighted 5, 4, 3, 2, and 1 respectively, with a total weight of 15. That is to say, among 15 requests, 5 times are assigned to 8881, 4 times are assigned to 8882, and so on.

The modified upstream file is as follows:

 upstream polling {
server 192.168.3.99:8881 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8882 weight=4 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8883 weight=3 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8884 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.3.99:8885 weight=1 max_fails=3 fail_timeout=30s;
}

Other places remain unchanged. We modify the number of loops in the code to (5 + 4 + 3 + 2 + 1) * 100 = 1500 times. The final statistical result distribution ratio is 5:4:3:2:1, as shown below: