• 【Lolttery】项目开发日志


    公司最新的项目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库

    @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等业务逻辑代码即可完成一个新的服务。

  • 相关阅读:
    多线程demo
    my parnter code review
    my code review
    思考题
    数组中最大子数组的和
    TestForGit
    say Hellow的测试类及测试结果
    读《Build To Win》感想
    随机生成300道四则运算
    思考题 程序员本计划在三天内完成任务,但在第三天的下午他发现了自己程序的弱点,他在想是否是自己采取另一种方法去解决它,但是需要额外的时间。如若反之,则会在后期团队会在集成方面花费时间。该怎么办?
  • 原文地址:https://www.cnblogs.com/Ayanami-Blob/p/5311162.html
Copyright © 2020-2023  润新知