Why do audio and video technologies need microservices
Microservice, English name: MicroService, baidu Baike defines it as: a variant of SOA architecture. A microservice (or microservice architecture) is an application constructed as a set of low-coupling services.
Microservices have some distinctive features:
- Function of the single
- Small service granularity
- Strong inter-service independence
- Dependencies between services are weak
- Service independent maintenance
- Independent service deployment
For each microservice, it should provide a single function; Having a very small particle size; It only provides the relevant interfaces involved in a business function. For example, the order system, payment system, product system, etc., in the e-commerce system, each system service is only an independent function of the system, and does not involve the functional logic that does not belong to it.
The dependency between microservices should be as weak as possible. The advantage of this is that the failure of a single system service will not cause the normal operation of other systems, thus affecting the user experience. Similarly, take the e-commerce system as an example: After the user adds the goods to the shopping cart and submits the order, the user goes to pay and finds that the payment cannot be made. In this case, the order can be put into the state to be paid, so as to prevent the loss of the order and unfriendly user experience. If there is a strong dependence between the order system and the payment system, the order system will always wait for the response from the payment system, which will result in the loading state of the user interface all the time, so that the user cannot perform any operation.
When the function of a micro-service needs to be upgraded or a function needs to fix bugs, the current service only needs to be compiled and deployed, rather than a huge number of services that package the business functions of the whole product one by one, and are maintained and deployed independently.
The microservices described above, in fact, highlight their distinctive characteristics: high cohesion, low coupling, and here’s the problem. What is high cohesion and what is low coupling? High cohesion: each service is in the same network or domain, and the whole is a closed, secure box relative to the outside. The external interfaces of the box remain unchanged, as do the interfaces between modules inside the box, but the contents inside each module can be changed. Modules expose only minimal interfaces to avoid strong dependencies. Adding or deleting a module should only affect related modules that have dependencies, and should not affect irrelevant modules.
The so-called low coupling: from a small point of view, it is to reduce the coupling between each Java class, use interfaces, use the Java object-oriented programming ideas of encapsulation, inheritance, polymorphism, hidden implementation details. From the point of view of modules, it is to reduce the relationship between each module, reduce the complexity of redundancy, repetition and crossover, and the function division of modules is as single as possible.
In audio and video application technology, we know that the main resources are CPU and memory, and related to the problem of resource sharing, so we need to combine NFS to achieve cross-node resource sharing. Of course, the problem with a single node is that once the client is connected to the server for a long time, and different clients are sending requests at the same time, the single node can be very stressful. CPU and memory may be tight, leading to node crash, which is not conducive to the high availability of the system and the robustness of services. At this time, we need to solve the problem of resource shortage in audio and video communication. In the system field, multi-node mode is usually adopted to realize distributed and high concurrent requests. When the requests come, load balancing can be adopted and certain policies can be adopted, such as: Depending on the minimum number of requests, or assigning a weight value to each server, the longer the server responds, the smaller the weight of the server and the lower the chance of being selected. This controls the service request pressure and allows the client and server to communicate efficiently for long periods of time.
How to use Springboot framework to build microservices
introduce
With the rapid development in recent years, microservices have become more and more popular. Among them, Spring Cloud is constantly being updated and used by most companies. Representative examples are Alibaba. Around November 2018, Spencer Gibb, co-founder of Spring Cloud, announced on the blog page of Spring’s official website that Alibaba was open source Spring Cloud Alibaba and released the first preview version. This was later announced on Spring Cloud’s official Twitter account.
Spring Boot1.x includes Eureka, Zuul, Config, Ribbon, Hystrix, etc. In Spring Boot2.x, the Gateway uses its own Gateway. Of course, in the Alibaba version, the components are even richer: Alibaba’s Nacos is used as the registry and configuration center. Sentinel is used as current limiting and fuse breaker.
Setting up a registry
Today we mainly use Springboot combined with Alibaba’s plug-in to achieve micro chat system micro service design. Start by creating a registry, Nacos.
Let’s download Nacos first, Nacos address: github.com/alibaba/nac… After downloading the binary file of the corresponding system, execute the following command for the corresponding system:
Linux/Unix/Mac: sh startup. Sh -m standalone Windows: CMD startup. CMD -m standaloneCopy the code
Startup is complete, visit: http://127.0.0.1:8848/nacos/, can enter the Nacos service management page, specific as follows:
The default username and password are both nacos.
After login, open service Management and you can see the list of services registered with Nacos:
You can click Config Management to view the configuration:
If no service configuration is configured, create:
The above description of Nacos as a registry and configuration center is simple enough.
The first microservice
Next, for microservices, which require a service to be registered and discovered, we explain the service provider code:
<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>
<groupId>com.damon</groupId>
<artifactId>provider-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>provider-service</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<swagger.version>2.6.1</swagger.version>
<xstream.version>1.4.7</xstream.version>
<pageHelper.version>4.1.6</pageHelper.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- <springcloud.version>2.1.8.RELEASE</springcloud.version> -->
<springcloud.version>Greenwich.SR3</springcloud.version>
<springcloud.kubernetes.version>1.1.1.RELEASE</springcloud.kubernetes.version>
<mysql.version>5.1.46</mysql.version>
<alibaba-cloud.version>2.1.1.RELEASE</alibaba-cloud.version>
<springcloud.alibaba.version>0.9.0.RELEASE</springcloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- <dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency> -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${springcloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pageHelper.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- datasource pool-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.3</version>
</dependency>
<!-- 对redis支持,引入的话项目缓存就支持redis了,所以必须加上redis的相关配置,否则操作相关缓存会报异常 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
<fork>true</fork>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- 自动生成代码 插件 begin -->
<!-- <plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</plugin> -->
</plugins>
</build>
</project>
Copy the code
As always, importing dependencies, configuring the bootstrap file:
management: endpoint: restart: enabled: true health: enabled: true info: enabled: true spring: application: name: Provider-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 refreshable - dataids: physical properties, the properties of HTTP: encoding: charset: utf-8 enabled: true force: true mvc: throw-exception-if-no-handler-found: true main: allow-bean-definition-overriding: Logging: path: /data/${spring.application.name}/logs cas-server-url http://oauth-cas #http://localhost:2000# set the address that can be accessed security: oauth2: # provider-service client-secret: provider-service-123 user-authorization-uri: ${cas-server-URL}/oauth/authorize # specifies the access-tok-URI required for authorization code authentication. Resource: loadBalanced: true # JWT: # JWT: # JWT: ${cas-server-url}/oauth/token # ${cas-server-url}/oauth/token_key #key-value: test_jwt_sign_key ID: provider-service # ${cas-server-url}/ API /user # specifies the user info URI. The original address suffix is /auth/user prefer-token-info: false #token-info-uri: authorization: check-token-access: ${cas-server-url}/oauth/check_token # ${cas-server-url}/oauth/check_token # ${cas-server-url}/oauth/check_token # ${cas-server-url}/oauth/ oauth server: port: 2001 undertow: accesslog: enabled: false pattern: combined servlet: session: timeout: PT120M cookie: Client: HTTP: Request: connectTimeout: 8000 readTimeout: 30000 mybatis: mapperLocations: classpath:mapper/*.xml typeAliasesPackage: com.damon.*.model backend: ribbon: client: Enabled: true ServerListRefreshInterval: 5000 ribbon: ConnectTimeout: 3000 # set the global default ribbon read timeout ReadTimeout: 1000 eager-load: enabled: true clients: oauth-cas,consumer-service MaxAutoRetries: 1 # for the first time the requested service retries MaxAutoRetriesNextServer: 1 # to retry the next service the maximum number of (not including the first service) # listOfServers: localhost:5556,localhost:5557 #ServerListRefreshInterval: 2000 OkToRetryOnAllOperations: true NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule hystrix.command.BackendCall.execution.isolation.thread.timeoutInMilliseconds: 5000 hystrix.threadpool.BackendCallThread.coreSize: 5Copy the code
Next start the class:
package com.damon; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; /** * @author Damon * @date January 13, 2020 3:23:06 ** / @configuration @enableAutoConfiguration @ComponentScan(basePackages = {"com.damon"}) @EnableDiscoveryClient @EnableOAuth2Sso public class ProviderApp { public static void main(String[] args) { SpringApplication.run(ProviderApp.class, args); }}Copy the code
Note: The @enableDiscoveryClient and @enableoAuth2SSO annotations are required.
In this case, you also need to configure ResourceServerConfig and SecurityConfig.
If you need a database, you can add:
package com.damon.config; import java.util.Properties; import javax.sql.DataSource; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.EnableTransactionManagement; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.github.pagehelper.PageHelper; / * * * * * created by Damon * on May 23, 2018 afternoon 7:39:37 * * / @ Component @ Configuration @ EnableTransactionManagement @MapperScan("com.damon.*.dao") public class MybaitsConfig { @Autowired private EnvConfig envConfig; @Autowired private Environment env; @Bean(name = "dataSource") public DataSource getDataSource() throws Exception { Properties props = new Properties(); props.put("driverClassName", envConfig.getJdbc_driverClassName()); props.put("url", envConfig.getJdbc_url()); props.put("username", envConfig.getJdbc_username()); props.put("password", envConfig.getJdbc_password()); return DruidDataSourceFactory.createDataSource(props); } @Bean public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); Fb.setdatasource (dataSource); // The following two sentences are only for *.xml files, if the entire persistence layer operation does not need to use XML files (just annotations can be done), Do not add fb. SetTypeAliasesPackage (env. GetProperty (" mybatis. TypeAliasesPackage ")); // Specify base package fb.setmapperlocations (new) PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations"))); PageHelper PageHelper = new PageHelper(); Properties props = new Properties(); // If rationalization is enabled, pageNum<1 will query the first page; if pageNum>pages will query the last page; If pageNum<1 or pageNum>pages returns props. SetProperty ("reasonable", "true"); // specify the database props. SetProperty ("dialect", "mysql"); // Support for passing paging parameters through Mapper interface parameters. SetProperty ("supportMethodsArguments", "true"); SetProperty ("returnPageInfo", "check"); // always returnPageInfo,check if the return type is PageInfo,none returns Page props. SetProperty ("returnPageInfo", "check"); props.setProperty("params", "count=countSql"); pageHelper.setProperties(props); Fb.setplugins (new Interceptor[] {pageHelper}); try { return fb.getObject(); } catch (Exception e) { throw e; }} / configuration transaction manager * * * * @ param dataSource * @ return * @ throws the Exception * / @ Bean public DataSourceTransactionManager transactionManager(DataSource dataSource) throws Exception { return new DataSourceTransactionManager(dataSource); } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); }}Copy the code
Let’s write a new Controller class:
package com.damon.user.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.damon.commons.Response; import com.damon.user.service.UserService; /** ** * @author Damon * @date January 13, 2020 3:31:07 ** / @restController @requestMapping ("/ API /user") public class UserController { private static final Logger logger = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; @GetMapping("/getCurrentUser") @PreAuthorize("hasAuthority('admin')") public Object getCurrentUser(Authentication authentication) { logger.info("test password mode"); return authentication; } @PreAuthorize("hasAuthority('admin')") @GetMapping("/auth/admin") public Object adminAuth() { logger.info("test password mode"); return "Has admin auth!" ; } @getMapping (value = "/get") @preauthorize ("hasAuthority('admin')") @preauthorize ("hasRole('admin')")// Invalid public Object get(Authentication authentication){ //Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); authentication.getCredentials(); OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails(); String token = details.getTokenValue(); return token; } @GetMapping("/getUserInfo") @PreAuthorize("hasAuthority('admin')") public Response<Object> getUserInfo(Authentication authentication) { logger.info("test password mode"); Object principal = authentication.getPrincipal(); if(principal instanceof String) { String username = (String) principal; return userService.getUserByUsername(username); } return null; }}Copy the code
It’s basically done in one code. Here’s a test:
Certification:
curl -i -X POST -d "username=admin&password=123456&grant_type=password&client_id=provider-service&client_secret=provider-service-123" http://localhost:5555/oauth-cas/oauth/token
Copy the code
After getting the token:
curl -i -H "Accept: application/json" -H "Authorization:bearer f4a42baa-a24a-4342-a00b-32cb135afce9" -X GET http://localhost:5555/provider-service/api/user/getCurrentUser
Copy the code
Here we use port 5555, which is a gateway service. Well, while we’re at it, let’s look at gateways and introduce dependencies:
The < 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 > < groupId > com. Damon < / groupId > < artifactId > alibaba - gateway < / artifactId > < version > 0.0.1 - the SNAPSHOT < / version > < packaging > jar < / packaging > < name > alibaba - gateway < / name > <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the boot - starter - parent < / artifactId > < version > 2.1.8. RELEASE < / version > < relativePath / > < / parent > <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> < project. Reporting. OutputEncoding > utf-8 < / project. Reporting. OutputEncoding > < Java version > 1.8 < / Java version > < swagger version > 2.6.1 < / swagger. Version > < xstream. Version > 1.4.7 < / xstream version > < pageHelper version > 4.1.6 < / pageHelper version > < fastjson. Version > 1.2.51 < / fastjson version > <! - < springcloud. Version > 2.1.8. RELEASE < / springcloud version > -- > < springcloud. Version > Greenwich. The SR3 < / springcloud version > < springcloud. Kubernetes. Version > 1.1.1. RELEASE < / springcloud. Kubernetes. Version > < mysql. Version > 5.1.46 < / mysql version > "Alibaba - cloud. Version > 2.1.1. RELEASE < / alibaba - cloud. Version > . < springcloud. Alibaba. Version > 0.9.0 RELEASE < / springcloud. Alibaba. Version > < / properties > < dependencyManagement > <dependencies> <! -- <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${alibaba-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${springcloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${springcloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <! -- Do not rely on spring-boot-starter-web, which conflicts with spring-cloud-starter-gateway. Abnormal startup - > < the dependency > < groupId > org. Springframework. Cloud < / groupId > <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <! Reactive Stream redis --> reactive Stream redis -- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId> Guava </artifactId> <version>19.0</version> </dependency> </dependencies> <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments> <fork>true</fork> </configuration> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId> maven-plugin</artifactId> <version>0.7.8</version> <executions> <execution> < Goals > <goal>prepare-agent</goal> <goal>report</goal> </goals> </execution> </executions> </plugin> <! -- Automatic code generation plug-in begin --> <! -- <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> < version > 1.3.2 < / version > < configuration > < configurationFile > SRC/main/resources/generatorConfig XML < / configurationFile > <verbose>true</verbose> <overwrite>true</overwrite> </configuration> <dependencies> <dependency> < the groupId > org. Mybatis. The generator < / groupId > < artifactId > mybatis generator - core < / artifactId > < version > 1.3.2 < / version > </dependency> </dependencies> </plugin> --> </plugins> </build> </project>Copy the code
Nacos is also used to discover services.
The registration configuration here is:
Spring: Cloud: Gateway: Discovery: locator: enabled: true # And we did not configure routes for each service separately. Instead, we used lowerCaseServiceId: True nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 refreshable-dataids: true nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 refreshable-dataids: actuator.properties,log.propertiesCopy the code
I used Kubernetes.
Ok, after the gateway is configured, the service can be seen in the Nacos Dashboard after startup, indicating that the service is successfully registered. You can then use it to invoke other services. Curl command
curl -i -H "Accept: application/json" -H "Authorization:bearer f4a42baa-a24a-4342-a00b-32cb135afce9" -X GET http://localhost:5555/consumer-service/api/order/getUserInfo
Copy the code
Ok, the authentication center, service provider, service consumer, service registration and discovery, and configuration center have been completed.
Why choose Netty as the technical framework for instant communication
Introduction to the
Netty is a high-performance, asynchronous event-driven NIO framework that provides support for TCP, UDP, and file transfers. As the most popular NIO framework, Netty has been widely used in the field of Internet, big data distributed computing, game industry and communication industry.
The characteristics of
- High concurrency
- Transmission is fast
- Good packaging
Advantages of Netty communications
Netty is a high-performance, highly scalable, asynchronous event-driven network application framework that greatly simplifies network programming such as TCP and UDP client and server development.
- Memory management: Enhanced ByteBuf buffers
- Reactor Threading Model: A high performance multithreaded programming
- Enhanced version of channel channel concept
- ChannelPipeline responsibility chain design pattern: event handling mechanism
Netty implements the Reactor thread model, which has four core concepts: Synchronous Event Demultiplexer, Dispatcher Dispatcher, Request Handler Two EventLoopGroups (thread groups, underlying the JDK thread pool) handle connections and data reads separately to improve thread utilization.
A Channel in Netty is an abstract concept that can be understood as an enhancement and extension of THE JDK NIO Channel. Added many properties and methods.
The ChannelPipeline responsibility chain holds all processor information for the channel. A proprietary pipeline is automatically created when a new channel is created and performs actual output operations on both inbound events (usually when the I/O thread generates inbound data, see ChannelInboundHandler) and outbound events (usually when the I/O thread generates the actual output operations). See ChannelOutboundHandler) to call the processor on the pipeline. When an inbound event is executed, the order is first to last of the pipeline. When an outbound event occurs, the execution sequence is from Last of pipeline to first. The order of processors in the pipeline is determined by the time they are added.
Netty’s own ByteBuf has solved the problems of JDK’s ByteBuffer, such as its inability to dynamically expand and complex API usage. ByteBuf implements four enhancements: convenient API operation, dynamic capacity expansion, multiple ByteBuf implementations, and efficient zero-copy mechanism.
To achieve a simple Netty client, server communication
Actual server
The advantages and characteristics of Netty’s implementation in the audio and video watershed have been introduced. Next, we will first write a server. Start by creating a Java project:
After creating the project, we need to introduce base dependencies:
The < 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 > < groupId > com. Damon < / groupId > < artifactId > netty - the client - service < / artifactId > < version > 0.0.1 - the SNAPSHOT < / version > < packaging > jar < / packaging > < name > netty - the client - service < / name > <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the boot - starter - parent < / artifactId > < version > 2.1.1. RELEASE < / version > < relativePath / > < / parent > < properties > < Java version > 1.8 < / Java version > < spring - the boot. Version > 2.1.1. RELEASE < / spring - the boot. Version > < springcloud. Kubernetes. Version > 1.0.1. RELEASE < / springcloud. Kubernetes. Version > < springcloud version > 2.1.1. RELEASE < / springcloud version > < swagger. Version > 2.6.1 < / swagger. Version > < fastjson version > 1.2.51 < / fastjson version > < pageHelper) version > 4.1.6 < / pageHelper version > < protostuff version > 1.0.10 < / protostuff version > < objenesis. Version > 2.4 < / objenesis version > < / properties > <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <type>pom</type> <scope>import</scope> <version>${spring-boot.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <! -- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-kubernetes-core</artifactId> <version>${springcloud.kubernetes.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-kubernetes-discovery</artifactId> <version>${springcloud.kubernetes.version}</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId> <version>${springcloud.kubernetes.version}</version> </dependency> --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> <version>${springcloud.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency> <! -- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> < the groupId > Commons - collections < / groupId > < artifactId > Commons - collections < / artifactId > < version > 3.2.2 < / version > </dependency> <! -- mybatis --> <! -- <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> --> <dependency> <groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <! -- <dependency> <groupId>org.bytedeco</groupId> <artifactId> Javacv-platform </artifactId> <version>1.4.1</version> </dependency> --> <! -- <dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>opencv-platform</artifactId> <version>3.4.1-1.4.1</version> </dependency> --> <dependency> <groupId> io.ty </groupId> < artifactId > netty -all < / artifactId > < version > 4.1.64. The Final < / version > < / dependency > <! -- protobuf --> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> < version > 3.5.0 < / version > < / dependency > < the dependency > < groupId > com. Googlecode. Protobuf - Java - the format < / groupId > < artifactId > protobuf - Java - the format < / artifactId > < version > 1.2 < / version > < / dependency > < the dependency > <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>${protostuff.version}</version> </dependency> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>${protostuff.version}</version> </dependency> <dependency> <groupId>org.objenesis</groupId> <artifactId>objenesis</artifactId> <version>${objenesis.version}</version> </dependency> </dependencies> </project>Copy the code
Service startup class:
@EnableScheduling @SpringBootApplication(scanBasePackages = { "com.damon" }) public class StorageServer { public static void main(String[] args) { SpringApplication.run(StorageServer.class, args); }}Copy the code
To start the Netty service first, we just need to add the netty configuration:
Spring. The application. The name = netty - server server port = 2002 netty. Host = 127.0.0.1 netty. Port = 9999 logging.path=/data/${spring.application.name}/logs spring.profiles.active=dev spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true spring.http.encoding.force=true spring.mvc.throw-exception-if-no-handler-found=true server.undertow.accesslog.enabled=false server.undertow.accesslog.pattern=combined client.http.request.readTimeout=30000 client.http.request.connectTimeout=8000Copy the code
After adding the configuration, we can start the service to see if it has logs:
After the netty service configuration is added, a Server Handle needs to be injected. When the client initiatively links to the Server, the Server Handle will be triggered to execute some messages:
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { SocketChannel channel = (SocketChannel) ctx.channel(); Logger. info(" link to report start "); Logger. info(" link report info: a client is linked to this server "); Logger.info (" link report IP:{}", channel.localAddress().gethostString ()); Logger.info (" Link report Port:{}", channel.localAddress().getPort()); Logger. info(" link report completed "); ChannelHandler.channelGroup.add(ctx.channel()); String STR = "" +" "+ new Date() +" + channel.localAddress().gethostString () + "\r\n"; ByteBuf buf = Unpooled.buffer(str.getBytes().length); buf.writeBytes(str.getBytes("GBK")); ctx.writeAndFlush(buf); }Copy the code
If a client is connected to a server, some information will be printed. Here is what I printed after joining the client in advance:
The channel is inactive when the client actively disconnects from the server. This means that the client and server are closed and cannot transfer data:
@override public void channelInactive(ChannelHandlerContext CTX) throws Exception {logger.info(" Client disconnection {}", ctx.channel().localAddress().toString()); ChannelHandler.channelGroup.remove(ctx.channel()); }Copy the code
Of course get the data function here:
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException { if(msg instanceof ByteBuf) { ByteBuf buf = (ByteBuf) msg; byte[] msgByte = new byte[buf.readableBytes()]; buf.readBytes(msgByte); System.out.println(new String(msgByte, Charset.forName("GBK"))); String STR = "Server received:" + new Date() + "" + new String(msgByte, charset.forname ("GBK")) + "\r\n"; ByteBuf buf2 = Unpooled.buffer(str.getBytes().length); buf2.writeBytes(str.getBytes("GBK")); ctx.writeAndFlush(buf2); }}Copy the code
If there is an exception, catch the exception, when the exception occurs, you can do some corresponding processing, such as printing logs, close links ** : **
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); Logger. info(" exception message: \r\n" + cause.getMessage()); }Copy the code
In addition, on the server side, it is generally necessary to define some information protocol information, such as the connection information, whether it is spontaneous message or group message, which communication channel is, and communication information, etc. :
public class ServerMsgProtocol { private int type; // Link information; 1 Spontaneous message, 2 group message Private String channelId; Private String userHeadImg; private String userHeadImg; private String userHeadImg; Private String msgInfo; Public int getType() {return type; } public void setType(int type) { this.type = type; } public String getChannelId() { return channelId; } public void setChannelId(String channelId) { this.channelId = channelId; } public String getUserHeadImg() { return userHeadImg; } public void setUserHeadImg(String userHeadImg) { this.userHeadImg = userHeadImg; } public String getMsgInfo() { return msgInfo; } public void setMsgInfo(String msgInfo) { this.msgInfo = msgInfo; }}Copy the code
Above, is a simple server, comb or relatively clear.
Actual client
Next, how does the client connect to and communicate with the server? In order to communicate with the server, the client must first connect to the server.
private EventLoopGroup workerGroup = new NioEventLoopGroup();
private Channel channel;
Copy the code
The logic to connect to the server is:
public ChannelFuture connect(String inetHost, int inetPort) { ChannelFuture channelFuture = null; try { Bootstrap b = new Bootstrap(); b.group(workerGroup); b.channel(NioSocketChannel.class); b.option(ChannelOption.AUTO_READ, true); b.handler(new MyChannelInitializer()); channelFuture = b.connect(inetHost, inetPort).syncUninterruptibly(); this.channel = channelFuture.channel(); channel.closeFuture(); } catch (Exception e) { e.printStackTrace(); } finally { if (null ! = channelFuture && channelFuture.isSuccess()) { System.out.println("demo-netty client start done."); } else { System.out.println("demo-netty client start error."); } } return channelFuture; }Copy the code
Let’s see how to destroy the connection:
public void destroy() {
if (null == channel) return;
channel.close();
workerGroup.shutdownGracefully();
}
Copy the code
Finally, let’s connect to the server:
New NettyClient (), connect (" 127.0.0.1 ", 9999);Copy the code
The IP address and port of the netty on the server side are set as: local port 9999.
Similarly, if the client needs to receive data information, it needs to define how to receive it in the pipe:
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void InitChannel (SocketChannel channel) throws Exception {// Add our own receiving data implementation method in the pipeline channel.pipeline().addlast (new) MyClientHandler()); }}Copy the code
The channel is active when the client actively links to the server. The client and the server establish a communication channel and can transfer data:
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { SocketChannel channel = (SocketChannel) ctx.channel(); System.out.println(" link report start "); System.out.println(" Link report information: this client links to the server. ChannelId: "+ channel.id()); System.out.println(" Link report IP:" + channel.localAddress().gethostString ()); System.out.println(" Link report Port:" + channel.localAddress().getPort()); System.out.println(" link report completed "); }Copy the code
The channel is inactive when the client actively disconnects from the server. This means that the client and server are closed and cannot transfer data:
@Override public void channelInactive(ChannelHandlerContext CTX) throws Exception {system.out. println(" Disconnecting "+ ctx.channel().localAddress().toString()); super.channelInactive(ctx); }Copy the code
When encountering an exception, catch the exception. When an exception occurs, you can do some corresponding processing, such as printing logs and closing links:
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); System.out.println(" exception message: \r\n" + cause.getMessage()); }Copy the code
After the client connects to the server, processes the information sent by the server, and handles exceptions, we start the client. The control surface of the client will print the following information:
If the client disconnects, the server displays the following message:
The remote host forced an existing connection down. 19:33:35 2021-05-13. 148736-691 the INFO [ntLoopGroup - 3-2) com. Leinao. Handler. ServerHandler: client disconnect links / 127.0.0.1:9999Copy the code
At this point, a simple Netty client, server communication is completed.
Micro-service Springboot actual combat chat system
Having introduced a simple Netty client-server communication example, let’s move on to the live chat system.
Websocket server startup class
Based on the Netty features mentioned earlier, the chat room needs a front end and a back end. We need to create a Websocket Server with a pair of thread groups called EventLoopGroup. After defining a Server, we need to define a Server:
public static void main(String[] args) throws Exception { EventLoopGroup mainGroup = new NioEventLoopGroup(); EventLoopGroup subGroup = new NioEventLoopGroup(); try { ServerBootstrap server = new ServerBootstrap(); server.group(mainGroup, subGroup) .channel(NioServerSocketChannel.class) .childHandler(new WSServerInitialzer()); ChannelFuture future = server.bind(8088).sync(); future.channel().closeFuture().sync(); } finally { mainGroup.shutdownGracefully(); subGroup.shutdownGracefully(); }}Copy the code
To add the thread group to the Server, you need to set a channel: NioServerSocketChannel and an initializer: WSServerInitialzer.
Step 2: Bind the Server to port version:
ChannelFuture future = server.bind(8088).sync()
Copy the code
Finally, you need to listen on the Future. The thread resource needs to be closed after listening:
mainGroup.shutdownGracefully();
subGroup.shutdownGracefully();
Copy the code
Websocket child processor Initialzer
For a socket, there is an initialization handler. Let’s define one:
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(1024*64)); pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); pipeline.addLast(new ChatHandler()); }}Copy the code
Because websocket is based on HTTP protocol, so need to have HTTP codec HttpServerCodec, at the same time, on some HTTP, there are some data flow processing, and, data flow is large and small, so you can add a big data flow processing: ChunkedWriteHandler.
Generally, there is an aggregation of httpMessages into FullHttpRequest or FullHttpResponse, and almost all programming in Netty uses this hanler.
In addition, the protocol handled by the WebSocket server is used to specify the route for client connection access: Handshaking (close, ping, pong) Ping + pong = heart beating For Websockets, frames are transmitted in frames, and different data types correspond to different frames.
Finally, we define a custom handler to handle the message: ChatHandler.
ChatHandler’s handling of messages
In Netty, there is an object dedicated to processing text for WebSockets, TextWebSocketFrame, which is the carrier of messages.
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { String content = msg.text(); System.out.println(" received data: "+ content); Clients. WriteAndFlush (new TextWebSocketFrame(" Server time at "+ localDateTime.now () +" received message: "+ content)); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { clients.add(ctx.channel()); System.out.println(" + ctx.channel().id().aslongText ()); System.out.println(" client connection, channle corresponding short ID: "+ ctx.channel().id().asshorttext ()); } @override public void handlerRemoved(ChannelHandlerContext CTX) throws Exception {system.out.println (" The client is disconnected, The channle id is "+ ctx.channel().id().aslongtext ()); System.out.println(" The client is disconnected, the channle corresponding short ID is: "+ ctx.channel().id().asshorttext ()); }}Copy the code
The message starts out in the carrier TextWebSocketFrame, so you can take the contents directly and print them out. The message can also be sent to the corresponding requesting client. Of course, messages can also be forwarded to all clients, which involves channels in Netty. At this point, you need to manage the users in the channel so that messages can be forwarded to all users of the channel. That is, the above handlerAdded function. When the client connects to the server, it opens the connection, obtains the channle of the client, and manages it in the ChannelGroup. The handlerRemoved function is triggered after the client and server are disconnected or closed, and the ChannelGroup automatically removes the channel from the client.
Next, we need to flush the data to all clients:
for (Channel channel : Clients) {channel.writeAndFlush(new TextWebSocketFrame("[server in]" + localDatetime.now () + "Received message, message:" + content)); }Copy the code
Note: You need to use a vector to Flush the information, because the writeAndFlush function needs to be passed to an object vector, not a string. ChannelGroup clients also provides a writeAndFlush function for all clients:
Clients. WriteAndFlush (new TextWebSocketFrame(" Server time at "+ localDateTime.now () +" received message: "+ content));Copy the code
Websocket API based on JS
First, we need a connection between the client and the server. This connection bridge in JS is a socket:
Var socket = new WebSocket (" ws: / / 192.168.174.145:8088 / ws ");Copy the code
At the back end, a channel has its life cycle, and at the front end, a socket has its life cycle:
- Onopen (), the onopen event is emitted when the client establishes a connection with the server
- Onmessage () is an onMessage event that is emitted when the client receives a message
- Onerror (), the front end will raise an onError event when an exception occurs
- Onclose (), the onClose event is emitted when the client and server connection is closed
Let’s look at two proactive approaches:
- Socket.send() : after the front-end actively obtains content, it sends messages through SEND
- Socket.close(), which disconnects the client from the server when the user triggers a button
This is the API corresponding to the front-end Websocket JS.
Implement front-end Websocket
Above introduced the back end for the message processing, encoding and decoding, and introduced the websocket JS related. Next, let’s see how the front end implements websocket. First, let’s write a text input, click and other functions:
<html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <div>send msg:</div> <input type="text" id="msgContent"/> <input type="button" value="send" onclick="CHAT.chat()"/> <div>receive msg:</div> <div id="receiveMsg" style="background-color: gainsboro;" ></div> </body> </html>Copy the code
Access link: C: Users\ Damon \Desktop\netty\WebChat\index.html, we can see the effect:
Next, we need to write websocket JS:
<script type="application/javascript"> window.CHAT = { socket: null, init: Function () {if (window. WebSocket) {CHAT. The socket = new WebSocket (ws: / / 192.168.174.145:8088 / ws "); Chat.socket. onopen = function() {console.log(" Connection established successfully... ); }, chat.socket. onclose = function() {console.log(" Connection closed... ); }, chat.socket. onerror = function() {console.log(" error... ); }, chat.socket. onmessage = function(e) {console.log(" received message: "+ e.data); var receiveMsg = document.getElementById("receiveMsg"); var html = receiveMsg.innerHTML; receiveMsg.innerHTML = html + "<br/>" + e.data; }} else {alert(" Browser does not support websocket protocol..." ); } }, chat: function() { var msg = document.getElementById("msgContent"); CHAT.socket.send(msg.value); }}; CHAT.init(); </script>Copy the code
Now that a simple WebSocket JS has been written, let’s demonstrate.
If the connection fails, onError and onClose events will be triggered. If the connection fails, onError and onClose events will be triggered.
Next, we start the back-end WSServer and refresh the page to see that the connection is successful. Console information:
Here, since I have two pages open, you can see that the back-end console prints two client connections, one for each client. Next, we type: Hi,Damon
After sending the message, we can see the output on the page: “Server time received message at 2021-05-17T20:05:22.802. The message is: Hi,Damon.” At the same time, in another client window, you can also see the output:
This is because the back end receives the request information from the first client and forwards it to all clients. Next, if we close the first client window, the back end listens and prints:
Similarly, if I open a new client and enter information, it will be forwarded to another client:
At the same time, the back-end console prints the corresponding request information:
Finally, if we primarily shut down the backend service, all clients will lose socket connections and the message will be:
Back – end integration of Springboot chat system
In front of the Websocket back-end processing and front-end implementation logic, finally, we combine with Springboot, to see the implementation of back-end logic.
First, we enter the dependency POM:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> < version > 2.3.10. RELEASE < / version > < relativePath / > < / parent > < properties > <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> < project. Reporting. OutputEncoding > utf-8 < / project. Reporting. OutputEncoding > < Java version > 1.8 < / Java version > </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> Ty < groupId > io.net < / groupId > < artifactId > netty -all < / artifactId > < version > 4.1.64. The Final < / version > < / dependency > < the dependency > < groupId > Commons - codec < / groupId > < artifactId > Commons - codec < / artifactId > < version > 1.11 < / version > </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> < version > 3.4 < / version > < / dependency > < the dependency > < groupId > org.apache.com mons < / groupId > <artifactId> Commons -io</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> < artifactId > mysql connector - Java < / artifactId > < version > 5.1.41 was < / version > < / dependency > <! -- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> < artifactId > mybatis - spring - the boot - starter < / artifactId > < version > 1.3.1 < / version > < / dependency > <! --mapper --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> < version > 1. < / version > < / dependency > <! --pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> < artifactId > pagehelper - spring - the boot - starter < / artifactId > < version > 1.2.3 < / version > < / dependency > <! <dependency> <groupId>com.github. Tobato </groupId> <artifactId>fastdfs-client</artifactId> < version > 1.26.2 < / version > < / dependency > < the dependency > < groupId > org. Springframework < / groupId > <artifactId>spring-test</artifactId> </dependency> <! Zxing </groupId> <artifactId>javase</artifactId> <version>3.3.3</version> </dependency>Copy the code
It mainly relies on Springboot 2.3.10.RELEASE, and also adds the dependency of Netty, database Mybatis, Fastdfs and other distributed file services.
Next, let’s look at the startup class:
MapperScan(basePackages="com.damon.mapper") // Scan all required packages, @ComponentScan(basePackages= {"com.damon", "org.n3r.idworker"}) public class Application { @Bean public SpringUtil getSpingUtil() { return new SpringUtil(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code
In the boot class, we see annotations injected according to Springboot, and we scan and inject some boot beans. Next, let’s look at how to introduce Netty server startup:
@Component public class NettyBooter implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext().getParent() == null) { try { WSServer.getInstance().start(); } catch (Exception e) { e.printStackTrace(); }}}}Copy the code
The main thing here is to inject a listener with the @Component annotation and start the Netty service when the main service starts. Netty’s actual service logic is described in the previous section:
@Component public class WSServer { private static class SingletionWSServer { static final WSServer instance = new WSServer(); } public static WSServer getInstance() { return SingletionWSServer.instance; } private EventLoopGroup mainGroup; private EventLoopGroup subGroup; private ServerBootstrap server; private ChannelFuture future; public WSServer() { mainGroup = new NioEventLoopGroup(); subGroup = new NioEventLoopGroup(); server = new ServerBootstrap(); server.group(mainGroup, subGroup) .channel(NioServerSocketChannel.class) .childHandler(new WSServerInitialzer()); } public void start() { this.future = server.bind(8088); System.err.println("netty websocket server start over"); }}Copy the code
For thread groups, when the client communicates with the slave thread group, the slave thread group processes the corresponding Channel. Also, every Channel has an initializer, so there is a childHandler function. ChannelHandler supports Http, Websocket, and other protocol requests.
public class WSServerInitialzer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); AddLast (new HttpServerCodec())); // Support for writing big data streams pipelines.addlast (new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(1024*64)); pipeline.addLast(new IdleStateHandler(8, 10, 12)); Pipeline.addlast (new HeartBeatHandler()); pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); // Custom handler pipeline.addLast(new ChatHandler()); }}Copy the code
At this point, all the technical aspects of the back end are covered.