公司最新的專案Lolttery已經開始動工了。
因為微服務很火,之前專門研究了一陣子。決定在新專案中採用微服務結構。在此部落格開始記錄學習和開發的問題和進步。
採用Netty+Spring+mybatis的核心框架,內部通訊使用socket tcp通訊。協議為json。同時用Spring MVC做對外的http介面。資料庫採用Mysql+Redis。
唉……反正說來說去伺服器+web端都是我自己一個人的活,用什麼技術完全不用討論啊……
個人經驗也不是很豐富,本系列部落格作為學習日誌和踩坑筆記,歡迎各路大神拍磚指正。
##第一篇:實現netty服務 框架的骨架是netty服務,netty是優秀的非同步網路處理框架,通過各種Handle可以適應不同的網路協議。同時又不依賴於tomcat等中介軟體,是實現微服務的合適選擇。
實現netty服務基本照搬了官網的原始碼。
在啟動器中包含了spring的初始化,netty的啟動和服務的註冊。
/**
* 啟動器
* Created by shizhida on 16/3/15.
*/
public class Bootstrap {
private int port = 2333;
public static String serverName = "";
private Logger logger = LoggerFactory.getLogger(Bootstrap.class);
public Bootstrap(int port){
//在初始化啟動器時獲取spring的上下文。
ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
//將上下文加入到一個全域性的變數中便於使用
Application.setApplicationContext(context);
this.port = port;
}
/**
* 在redis中註冊本服務,以便被客戶端獲取
* @param host
* @param serverName
* @return
*/
public Bootstrap register(String host,String serverName){
RedisDao redisDao = new RedisDao("localhost");
Map<String,String> info = new HashMap<>();
info.put("ip",host);
info.put("port",port+"");
this.serverName = serverName;
redisDao.getJedis().hmset(serverName,info);
return this;
}
/**
* netty啟動
* @throws Exception
*/
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new JsonDecoder(),
new DispatchHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
//儲存至全域性變數,便於關閉服務
Application.future = f;
logger.info("start service bin port " + port);
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
try {
new Bootstrap(2333).register("locahost","server").run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
複製程式碼
完成啟動器之後,是協議的解析
為了避免出現粘包、半包等情況,協議採用4byte報文長度+json字串的方式進行傳輸。
json包括header、body和foot三個部分,header包含了serviceName,serviceCode等請求資訊,body為請求的引數,foot包含了來源、校驗碼等相關資訊
自行編寫了解析器如下,json解析使用阿里的fastjson庫
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
//若可讀取長度不足4位元組,不進行讀取
if(in.readableBytes()<4)
{
logger.debug("長度不足,可讀取長度為:" + in.readableBytes());
return;
}
byte[] byte_length = new byte[4];
in.readBytes(byte_length);
//讀取報文長度
int length = BUtil.bytes2int(byte_length,0);
//若可讀取長度小於約定長度,則不讀取
if(in.readableBytes()<length)
{
logger.debug("可讀取長度小於約定長度,約定:"+length+" 可讀取"+in.readableBytes());
in.resetReaderIndex();
return;
}
logger.debug("約定讀取資料長度:" + length);
byte[] data = new byte[length];
in.readBytes(data);
String json = new String(data);
logger.debug("讀取到的字元資料:"+new String(data));
JSONObject object = JSON.parseObject(json);
//組裝request
XORequest request = new XORequest(object);
out.add(request);
}
複製程式碼
然後是服務的分發。
一個服務中也可以細分為多個業務。設計上使用serviceName和serviceCode來確定一個具體的業務。serviceName用於服務註冊,可以獲取到服務的ip和埠資訊。serviceCode用於服務內部的業務具體劃分。
利用spring框架的功能,服務分發可以做的很簡單: netty處理:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
//分發並處理請求
XORequest request = (XORequest) msg;
logger.info("-------------request start ------------");
logger.info("request to "+request.getServiceName());
logger.info("request at "+new Date(request.getRequestDate()));
logger.info("request for "+request.getServiceCode());
XOResponse result = dispatcher.dispatch(request);
//組裝處理結果
byte[] json = result.toString().getBytes();
ByteBuf byteBuf = ctx.alloc().buffer(json.length);
byteBuf.writeBytes(json);
//傳送給客戶端
final ChannelFuture f = ctx.writeAndFlush(byteBuf);
f.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
assert f == future;
ctx.close();
logger.info("request process done");
}
});
}
複製程式碼
分發服務:(dispatcher)
public XOResponse dispatch(XORequest request) { String service_code = request.getServiceCode(); XOService service = Application.applicationContext.getBean(service_code,XOService.class); return service.run(request); }
預設所有的服務都實現XOService介面,利用spring的Service("serviceCode")註解就可以簡單的實現服務的分發。
通過兩層分發器、通用協議和服務介面。在這裡就實現了業務邏輯與框架功能的高度分離。
實現業務邏輯只需要新增更多的XOService介面的例項,就可以擴充套件業務邏輯。
在每個服務上依賴此框架,實現一個Bootstrap啟動器,並新增Service等業務邏輯程式碼即可完成一個新的服務。
複製程式碼