Redkale

##

Redkale 入门 Convert 组件 Service 组件 Source 组件 Net 组件 Watch 组件 Redkale 插件 技术文章

### Service 组件介绍

Service 是Redkale最核心的组件，依赖于Convert、SNCP协议、Resource依赖注入。Service主要处理业务逻辑和操作数据层，是微服务架构中的单一原子服务。每一个Service实例分两种模式: 本地模式和远程模式。其模式由 conf/application.xml 文件来配置。开发人员在调用过程中通常不需要区分当前Service实例是哪种模式。
为了能确保本地模式与远程模式自由切换，对Service的实现类有一定的约束:
1、Service实现类会被继承，不能修饰为 final
2、带@RpcMultiRun注解的方法会被重载，不能修饰为 final
Redkale进程启动时扫描可加载的Service实现类，根据配置文件配置的模式采用JDK 8内置的ASM技术动态生成相应的Service临时类进行实例化，并注册到ResourceFactory同其他Service、Servlet依赖注入。

### Service 本地模式

以一个简单的UserService类范例来说明动态生成的两种模式临时类。UserService提供查询用户、注册、登陆、修改用户名功能:

    public class UserService implements Service {

        //用户简单信息缓存
        private final Map<Integer, UserInfo> users = new ConcurrentHashMap<>();

        //存放手机号码与userid的对应关系
        private final Map<Long, Integer> mobileToUserids = new ConcurrentHashMap<>();

        public final String testLocalNodeName() {
            return "本地节点名";
        }

        //查询用户信息
        public UserInfo findUserInfo(int userid) {
            return users.get(userid);
        }

        //根据手机号码查询用户ID，没有返回0
        public int findUserid(long mobile) {
            Integer rs = mobileToUserids.get(mobile);
            return rs == null ? 0 : rs;
        }

        //缓存用户信息
        public void putUserInfo(UserInfo user) {
            users.put(user.getUserid(), user);
            if (user.getMobile() > 0) mobileToUserids.put(user.getMobile(), user.getUserid());
        }

        //登录
        public RetResult<UserInfo> login(LoginBean bean) {
            // 登陆逻辑
            return new RetResult<>(100);
        }

        //注册
        @RpcMultiRun
        public void register(UserInfo user) {
            this.users.put(user.getUserid(), user);
        }

        //更新用户名
        @RpcMultiRun(diffrun = false)
        public UserInfo updateUsername(int userid, String username) {
            UserInfo user = this.users.get(userid);
            if (user != null) user.setUsername(username);
            return user;
        }
    }

动态生成的本地模式UserService：

    @Resource(name = "")
    @SncpDyn(remote = false)
    @ResourceType({UserService.class})
    public final class _DynLocalUserService extends UserService {

        @Resource
        private BsonConvert _redkale_bsonConvert;

        @Resource
        private JsonConvert _redkale_jsonConvert;

        private Transport _redkale_sameGroupTransport;

        private Transport[] _redkale_diffGroupTransports;

        private SncpClient _redkale_client;

        private String _redkale_selfstring;

        @Override
        public String toString() {
            return _redkale_selfstring == null ? super.toString() : _redkale_selfstring;
        }

        @Override
        public void register(UserInfo user) {
            this._redkale_register(true, true, true, user);
        }

        @SncpDyn(remote = false, index = 0)
        public void _redkale_register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
            if (selfrunnable) super.register(user);
            if (_redkale_client == null) return;
            if (samerunnable) _redkale_client.remoteSameGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_sameGroupTransport, 0, true, false, false, user);
            if (diffrunnable) _redkale_client.remoteDiffGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_diffGroupTransports, 0, true, true, false, user);
        }

        @Override
        public UserInfo updateUsername(int userid, String username) {
            return this._redkale_updateUsername(true, true, false, userid, username);
        }

        @SncpDyn(remote = false, index = 1)
        public UserInfo _redkale_updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
            UserInfo rs = super.updateUsername(userid, username);
            if (_redkale_client == null) return null;
            if (samerunnable) _redkale_client.remoteSameGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_sameGroupTransport, 1, true, false, false, userid, username);
            if (diffrunnable) _redkale_client.remoteDiffGroup(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_diffGroupTransports, 1, true, true, false, userid, username);
            return rs;
        }
    }

由以上等价的代码可以看出来，本地模式Service会重载被@RpcMultiRun注解的方法。@RpcMultiRun有以下几个参数:

    public @interface RpcMultiRun {

        boolean selfrun() default true; //当前本地实例是否运行指定操作；只有当指定操作的方法的返回值为void时，该值才能为true，否则忽略。

        boolean samerun() default true;  //是否同组节点运行指定操作

        boolean diffrun() default true; //是否不同组节点运行指定操作

        boolean async() default true; //分布式运行是否采用异步模式
    }

在动态生成的远程模式UserService时会根据不同参数生成相应的方法。若一个Service类没有含@RpcMultiRun注解的方法，那么动态类只会重载toString方法。当UserService服务仅需要部署一个进程，由于没有其他等同服务的进程因此在UserService实例化时_redkale_client会赋值为null。

    <resources>
        <group name="GROUP-A">
            <node addr="192.168.10.111" port="7070"/>
            <node addr="192.168.10.112" port="7070"/>
            <node addr="192.168.10.113" port="7070"/>
        </group>
        <group name="GROUP-B">
            <node addr="192.168.20.121" port="7070"/>
            <node addr="192.168.20.122" port="7070"/>
        </group>
        <group name="GROUP-C">
            <node addr="192.168.30.131" port="7070"/>
            <node addr="192.168.30.132" port="7070"/>
        </group>
    </resources>

    <!-- 配置UserService的节点组  --->
    <service name="" value="org.redkale.demo.user.UserService" groups="GROUP-A;GROUP-B;GROUP-C"/>

如上配置，若当前进程所在IP是192.168.10.111，则UserService采用本地模式加载。执行register方法时， 先本地执行超类的register，然后远程执行同组的进程(192.168.10.112、192.168.10.113)，最后远程执行异组的进程(192.168.20.121、192.168.20.122、192.168.30.131、192.168.30.132)。若当前进程所在IP是192.168.10.100，则UserService采用远程模式加载。需要注意的一点是，每个IP所在的服务必须开通SNCP协议服务以便能接收远程的调用请求。

### Service 远程模式

动态生成的远程模式UserService：

    @Resource(name = "")
    @SncpDyn(remote = true)
    @ResourceType({UserService.class})
    public final class _DynRemoteUserService extends UserService {

        @Resource
        private BsonConvert _redkale_bsonConvert;

        @Resource
        private JsonConvert _redkale_jsonConvert;

        private Transport _redkale_transport;

        private SncpClient _redkale_client;

        private String _redkale_selfstring;

        @Override
        public String toString() {
            return _redkale_selfstring == null ? super.toString() : _redkale_selfstring;
        }

        @SncpDyn(remote = false, index = 0)
        public void _redkale_register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
            _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 0, selfrunnable, samerunnable, diffrunnable, user);
        }

        @SncpDyn(remote = false, index = 1)
        public UserInfo _redkale_updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
            return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 1, selfrunnable, samerunnable, diffrunnable, userid, username);
        }

        @Override
        public UserInfo findUserInfo(int userid) {
            return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 2, userid);
        }

        @Override
        public int findUserid(long mobile) {
            return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 3, mobile);
        }

        @Override
        public RetResult<UserInfo> login(LoginBean bean) {
            return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 4, bean);
        }

        @Override
        public void putUserInfo(UserInfo user) {
            _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 5, user);
        }

        @Override
        public void register(UserInfo user) {
            _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 6, user);
        }

        @Override
        public UserInfo updateUsername(int userid, String username) {
            return _redkale_client.remote(_redkale_bsonConvert, _redkale_jsonConvert, _redkale_transport, 7, userid, username);
        }
    }

由以上代码可以看出来，远程模式Service是根据本地模式Service临时类动态生成的。远程类执行方法时通过SNCP协议将参数序列化并带上当前方法信息传输到远程服务器上，执行完后将结果流反序列化并返回, 其流程与WebService类似。

远程模式的@RpcCall回调

与WebService的区别除了更具性能的二进制的数据格式，更差异的是远程模式的Service允许修改参数本身的内容。范例如下：

    /**
     * 由于该方法在处理过程中修改了参数bean的内容，为了保证本地模式与远程模式的一致性，需要提供@RpcCall回调接口
     *
     * @param bean
     * @return
     */
    public RetResult<UserInfo> login(@RpcCall(RpcCallLoginBeanAttribute.class) LoginBean bean) {
        bean.setLogintime(System.currentTimeMillis());
        bean.setSessionid("SID" + System.currentTimeMillis());
        // 登陆逻辑
        return new RetResult<>(100);
    }

    /**  RpcCallLoginBeanAttribute 的实现  **/
    public class RpcCallLoginBeanAttribute implements Attribute<LoginBean, Object[]> {

        @Override
        public Object[] get(LoginBean obj) {
            return new Object[]{obj.getLogintime(), obj.getSessionid()};
        }

        @Override
        public void set(LoginBean obj, Object[] value) {
            obj.setLogintime((Long) value[0]);
            obj.setSessionid((String) value[1]);
        }

        @Override
        public Class<? extends Object[]> type() {
            return Object[].class; //
        }

        @Override
        public Class<LoginBean> declaringClass() {
            return LoginBean.class; //
        }

        @Override
        public String field() {
            return ""; //可以随意值
        }
    }

生成远程模式Service时发现参数带有@RpcCall注解的方法，在远程调用返回结果时会进行回调处理。

### Service REST

RestService提供类似Spring Boot的功能。开启REST功能的HTTP Server在实例化标记为@RestService的Service后自动生成对应的HttpServlet，免去开发人员编写HttpServlet的工作量。