公司最新的項目Lolttery已經開始動工了。java
由於微服務很火,以前專門研究了一陣子。決定在新項目中採用微服務結構。在此博客開始記錄學習和開發的問題和進步。web
採用Netty+Spring+mybatis的核心框架,內部通訊使用socket tcp通訊。協議爲json。同時用Spring MVC作對外的http接口。數據庫採用Mysql+Redis。redis
唉……反正說來講去服務器+web端都是我本身一我的的活,用什麼技術徹底不用討論啊……spring
我的經驗也不是很豐富,本系列博客做爲學習日誌和踩坑筆記,歡迎各路大神拍磚指正。sql
第一篇:實現netty服務數據庫
框架的骨架是netty服務,netty是優秀的異步網絡處理框架,經過各類Handle能夠適應不一樣的網絡協議。同時又不依賴於tomcat等中間件,是實現微服務的合適選擇。json
實現netty服務基本照搬了官網的源碼。tomcat
在啓動器中包含了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庫
@Override 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處理:
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2) //分發並處理請求 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); // (3) f.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { assert f == future; ctx.close(); logger.info("request process done"); } }); }
分發服務:(dispatcher)
@Override 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等業務邏輯代碼便可完成一個新的服務。