项目实训(8) - 简易服务注册中心


前言

简易服务注册中心,采用http实现,其实采用websocket会更好。

一、心跳

import com.meeting.common.entity.ResponseData;
import com.meeting.common.entity.Service;
import com.meeting.record.Consumers;
import com.meeting.record.Providers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.*;

@Component
public class Heartbeat {

    @Autowired
    private volatile Providers providers;

    @Autowired
    private volatile Consumers consumers;

    @Autowired
    private RestTemplate restTemplate;

    private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    private final ExecutorService pool = new ThreadPoolExecutor(10, 200, 60L, TimeUnit.SECONDS,
            new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    @PostConstruct
    public void heartbeat() {
        service.scheduleWithFixedDelay(() -> {
            try {
                // 拷贝serviceMap中的keys
                int[] providerKeys = providers.keys();

                for (int key : providerKeys) {
                    Service service = providers.get(key);
                    if (service != null) {
                        pool.submit(
                                new ProviderTask(service.getServiceId(),
                                        service.getIp(), service.getPort())
                        );
                    }
                }

                // 拷贝serviceMap中的keys
                int[] consumerKeys = consumers.keys();

                for (int key : consumerKeys) {
                    Service service = consumers.get(key);
                    if (service != null) {
                        pool.submit(
                                new ConsumerTask(service.getServiceId(),
                                        service.getIp(), service.getPort())
                        );
                    }
                }
            } catch (URISyntaxException exception) {
                System.out.println(exception.getMessage());
            }
        }, 3, 3, TimeUnit.SECONDS);
    }

    class ProviderTask implements Runnable {

        private final int id;
        private final URI uri;

        public ProviderTask(int id, String ip, int port) throws URISyntaxException {
            this.id = id;
            // 本地测试使用
            // this.uri = new URI("http://" + "127.0.0.1" + ":" + port + "/heartbeat");
            this.uri = new URI("http://" + ip + ":" + port + "/heartbeat");
        }

        @Override
        public void run() {
            RequestEntity<String> requestEntity
                    = new RequestEntity<>(null, null, HttpMethod.GET, this.uri);
            try {
                ResponseEntity<ResponseData> exchange
                        = restTemplate.exchange(requestEntity, ResponseData.class);
                if (exchange.getStatusCode() != HttpStatus.OK) {
                    providers.removeService(id);
                }
            } catch (ResourceAccessException | HttpClientErrorException exception) {
                providers.removeService(id);
            }
        }

    }

    class ConsumerTask implements Runnable {

        private final int id;
        private final URI uri;

        public ConsumerTask(int id, String ip, int port) throws URISyntaxException {
            this.id = id;
            // 本地测试使用
            // this.uri = new URI("http://" + "127.0.0.1" + ":" + port + "/heartbeat");
            this.uri = new URI("http://" + ip + ":" + port + "/heartbeat");
        }

        @Override
        public void run() {
            RequestEntity<String> requestEntity
                    = new RequestEntity<>(null, null, HttpMethod.GET, this.uri);
            try {
                ResponseEntity<ResponseData> exchange
                        = restTemplate.exchange(requestEntity, ResponseData.class);
                if (exchange.getStatusCode() != HttpStatus.OK) {
                    consumers.removeService(id);
                }
            } catch (ResourceAccessException | HttpClientErrorException exception) {
                consumers.removeService(id);
            }
        }

    }

}

二、服务变动

@Component
public class UpdateInfo {

    @Autowired
    private Consumers consumers;

    @Autowired
    private Providers providers;

    @Autowired
    private RestTemplate restTemplate;

    private final ScheduledExecutorService service = Executors.newScheduledThreadPool(1);

    private final ExecutorService pool = new ThreadPoolExecutor(10, 200, 60L, TimeUnit.SECONDS,
            new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    @PostConstruct
    public void updateInfo() {
        service.scheduleWithFixedDelay(() -> {
            try {
                boolean modified = providers.isModified();
                if (modified) {
                    Map<Integer, Service> copy = providers.getAllCopy();

                    // 拷贝serviceMap中的keys
                    int[] keys = consumers.keys();

                    for (int key : keys) {
                        Service service = consumers.get(key);
                        if (service != null) {
                            pool.execute(
                                    new Task(service.getServiceId(),
                                            service.getIp(), service.getPort(), copy)
                            );
                        }
                    }

                    providers.modify();

                }
            } catch (URISyntaxException exception) {
                System.out.println(exception.getMessage());
            }
        }, 5, 5, TimeUnit.SECONDS);
    }

    class Task implements Runnable {

        private final int id;
        private final URI uri;
        private final Map<Integer, Service> copy;

        public Task(int id, String ip, int port, Map<Integer, Service> copy) throws URISyntaxException {
            this.id = id;
            // 本地测试
            // this.uri = new URI("http://" + "127.0.0.1" + ":" + port + "/service/list");
            this.uri = new URI("http://" + ip + ":" + port + "/service/list");
            this.copy = copy;
        }

        @Override
        public void run() {
            MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
            params.add("serviceList", copy);
            HttpEntity<MultiValueMap<String, Object>> httpEntity
                    = new HttpEntity<>(params);
            ResponseEntity<ResponseData> response
                    = restTemplate.postForEntity(uri, httpEntity, ResponseData.class);
            try {
                if (response.getStatusCode() != HttpStatus.OK) {
                    consumers.removeService(id);
                }
            } catch (ResourceAccessException exception) {
                consumers.removeService(id);
            }
        }

    }

}

三、注册

import com.meeting.counter.ServiceIdCounter;
import com.meeting.common.entity.ResponseData;
import com.meeting.common.entity.Service;
import com.meeting.record.Consumers;
import com.meeting.record.Providers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

@Controller
@CrossOrigin(origins = {"*"})
public class RegistryController {

    @Autowired
    private ServiceIdCounter counter;

    @Autowired
    private Providers providers;

    @Autowired
    private Consumers consumers;

    @ResponseBody
    @PostMapping("/registerProvider")
    public ResponseData registerProvider(HttpServletRequest request,
                                         @RequestParam("name") String serviceName,
                                         @RequestParam("port") int port) {
        ResponseData responseData = null;
        Service service = new Service();
        Integer id = counter.getNext();
        service.setServiceId(id);
        service.setServiceName(serviceName);
        service.setIp(request.getRemoteAddr());
        service.setPort(port);
        if (providers.addRecord(service)) {
            responseData = new ResponseData(200, "ok");
            responseData.getData().put("id", id);
        } else {
            responseData = new ResponseData(400, "error");
        }
        return responseData;
    }

    @ResponseBody
    @PostMapping("/registerConsumer")
    public ResponseData registerConsumer(HttpServletRequest request,
                                         @RequestParam("name") String serviceName,
                                         @RequestParam("port") int port) {
        ResponseData responseData = null;
        Service service = new Service();
        Integer id = counter.getNext();
        service.setServiceId(id);
        service.setServiceName(serviceName);
        service.setIp(request.getRemoteAddr());
        service.setPort(port);
        if (consumers.addRecord(service)) {
            responseData = new ResponseData(200, "ok");
            responseData.getData().put("id", id);
        } else {
            responseData = new ResponseData(400, "error");
        }
        return responseData;
    }

}

总结

当服务启动后,需要向服务注册中心发送消息注册。同时,每隔一定时间,注册中心向服务发送心跳,确认是否在线,如果不在线,发送服务变动消息。在项目中,采用了http请求实现,实际写完后,认为采用长连接会更合适。另外,个人能力有限,没有写springboot的启动器,本来,注册消息还有接受心跳,管理服务都可以写在一个项目中,通过启动器注入bean,但是,在本次项目中,将一段代码四处粘贴。此外,rpc本来想尝试写简单的通用模板的,但最终没有这么做。这部分确实写得不好,但快期末考试了。

关于聊天室心跳

原本的打算是使用netty的IdleStateHandler,如果服务端30秒内没有接收到来自客户端的消息,发送一个心跳,在这之后的30秒内如果还没有收到消息,就判断客户端离线,断开连接,不过,出于一些原因,在项目中最终没有采用这种方法,代码就不贴了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东羚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值