【Lolttery】項目開發日誌 - (一) 微服務框架搭建

公司最新的項目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等業務邏輯代碼便可完成一個新的服務。

相關文章
相關標籤/搜索