netty项目注册到eureka

项目中需要实现即时通信IM沟通,由于项目是微服务架构,并且每个子系统都是集群部署模式。需要解决前端的websocket请求通过springcloud-gateway网关均衡分发到集群中的netty服务中,实现系统的高可用。

netty服务注册到eureka中

import com.netflix.appinfo.*;
import com.netflix.discovery.DiscoveryClient;
import com.xgjk.xgware.im.websocket.NettyProperties;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class NettyDiscoveryClient implements SmartInitializingSingleton {

    @Autowired
    private EurekaInstanceConfig config;

    @Autowired
    private NettyProperties nettyProperties;

    @Autowired
    private InetUtils inetUtils;

    @Autowired
    private EurekaClientConfigBean eurekaClientConfigBean;

    /**
     * 在spring容器管理的所有单例对象(非懒加载对象)初始化完成之后调用的回调接口
     */
    @Override
    public void afterSingletonsInstantiated() {
        /*EurekaClientConfigBean eurekaClientConfigBean = new EurekaClientConfigBean();
        eurekaClientConfigBean.setServiceUrl(new HashMap() {{
            put("defaultZone", defaultZone);
        }});*/
        String host = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
        InstanceInfo instanceInfo = createInstance(config);
        ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(new MyDataCenterInstanceConfig() {
            @Override
            public String getHostName(boolean refresh) {
                return host;
            }
        }, instanceInfo);
        //创建一个客户端实例
        DiscoveryClient discoveryClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfigBean);
    }

    private InstanceInfo createInstance(EurekaInstanceConfig config) {

        LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
                .setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
                .setDurationInSecs(config.getLeaseExpirationDurationInSeconds());

        // Builder the instance information to be registered with eureka
        InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();

        String namespace = config.getNamespace();
        if (!namespace.endsWith(".")) {
            namespace = namespace + ".";
        }

        String host = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
        builder.setNamespace(namespace).setAppName(nettyProperties.getName())
                .setInstanceId(String.join(":", host, String.valueOf(nettyProperties.getPort())))
                .setAppGroupName(config.getAppGroupName())
                .setDataCenterInfo(config.getDataCenterInfo())
                .setIPAddr(host).setHostName(host)
                .setPort(nettyProperties.getPort())
                .enablePort(InstanceInfo.PortType.UNSECURE,
                        config.isNonSecurePortEnabled())
                .setSecurePort(config.getSecurePort())
                .enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled())
                .setVIPAddress(nettyProperties.getName())
                .setSecureVIPAddress(nettyProperties.getName())
                .setHomePageUrl("/", null)
                .setStatusPageUrl(config.getStatusPageUrlPath(),
                        config.getStatusPageUrl())
                .setHealthCheckUrls(config.getHealthCheckUrlPath(),
                        config.getHealthCheckUrl(), config.getSecureHealthCheckUrl())
                .setASGName(config.getASGName());
        builder.setStatus(InstanceInfo.InstanceStatus.UP);

        // Add any user-specific metadata information
        for (Map.Entry mapEntry : config.getMetadataMap().entrySet()) {
            String key = mapEntry.getKey();
            String value = mapEntry.getValue();
            // only add the metadata if the value is present
            if (value != null && !value.isEmpty()) {
                builder.add(key, value);
            }
        }

        InstanceInfo instanceInfo = builder.build();
        instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
        return instanceInfo;
    }
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "netty")
@Data
public class NettyProperties {

    /**
     * socket端口
     */
    private int port;

    private String name;

    private String path = "/webSocket";
}

配置文件

netty:
  name: scp-im2-websocket-netty
  port: 14052
  path: /webSocket

编写websocket长连接服务端代码,由于代码较多,这里只给出入口代码

@Slf4j
@Component
public class NettyServer {

    @Autowired
    private ChannelDeadCheckHandler channelDeadCheckHandler;

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Autowired
    private MessageOutHandler messageOutHandler;

    @Autowired
    private HeartbeatHandler heartbeatHandler;

    @Autowired
    private NettyProperties nettyProperties;

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();//负责接收连接
        EventLoopGroup group = new NioEventLoopGroup();////负责处理请求,工作线程数默认是CPU数*2
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(group, bossGroup) // 绑定线程池
                    .channel(NioServerSocketChannel.class) // 指定使用Nio channel
                    .localAddress(nettyProperties.getPort())// 绑定监听端口
                    .childHandler(new ChannelInitializer() {
                        public void initChannel(SocketChannel channel) {
                            ChannelPipeline pipeline = channel.pipeline();
                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            pipeline.addLast(new HttpServerCodec());
                            //将HTTP消息的多个部分合成一条完整的HTTP消息
                            pipeline.addLast(new HttpObjectAggregator(65536));
                            //用于支持大数据流的支持
                            pipeline.addLast(new ChunkedWriteHandler());
                            //用来判断是否读空闲时间过长,或写空闲时间过长
                            //如果60秒中没有收到客户端消息,则触发事件IdleState.READER_IDLE
                            pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
                            pipeline.addLast(channelDeadCheckHandler);
                            pipeline.addLast(webSocketHandler);//自定义消息处理类
                            pipeline.addLast(heartbeatHandler);

                            pipeline.addLast(messageOutHandler);
                            pipeline.addLast(new WebSocketServerProtocolHandler("/webSocket", null, true, 65536 * 10));
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 1024)//服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝, linux设置/proc/sys/net/core/somaxconn
                    .childOption(ChannelOption.SO_KEEPALIVE, true);//设置保持活动的连接
            ChannelFuture cf = bootstrap.bind().sync(); // 服务器异步创建绑定
            log.info("NettyServer已启动... ");
            cf.channel().closeFuture().sync(); // 关闭服务器通道
        } finally {
            group.shutdownGracefully().sync(); // 释放线程池资源
            bossGroup.shutdownGracefully().sync();
        }
    }
}

springcloud-gateway项目中配置路由

spring.cloud.gateway:
  httpclient:
    #连接超时时间
    connect-timeout: 1000
    #响应超时时间
    responseTimeout: 60s
  #默认过滤器,对所有的路由请求生效,order:按申明顺序从1递增
  #当过滤器的order值一样时,会按照defaultFilter>路由过滤器>GlobalFilter的顺序执行
  default-filters:
    #- StripPrefix=1 #去除请求路径的第一个前缀后访问微服务
  #全局跨域处理
  globalcors:
    add-to-simple-url-handler-mapping: true
    cors-configurations:
      '[/**]':
        allowCredentials: true #是否允许携带cookie
        allowedOriginPatterns: "*"
        allowedMethods: "*"
        allowedHeaders: "*"
        maxAge: 360000 #跨域检测有效期
  # 网关指定路径路由
  routes:
    - id: scp-im2-websocket-netty
      uri: lb:ws://scp-im2-websocket-netty
      predicates:
        - Path=/webSocket

然后,前端通过ws://localhost:14052/webSocket 即可连接netty集群中的服务

你可能感兴趣的:(netty项目注册到eureka)