前言
简易服务注册中心,采用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秒内如果还没有收到消息,就判断客户端离线,断开连接,不过,出于一些原因,在项目中最终没有采用这种方法,代码就不贴了。