Service 组件介绍

       Service 是RedKale最核心的组件,依赖于Convert、SNCP协议、Resource依赖注入。Service主要处理业务逻辑和操作数据层,是微服务架构中的单一原子服务。每一个Service实例分两种模式: 本地模式远程模式。其模式由 conf/application.xml 文件来配置。使用者在调用过程中通常不需要区分当前Service实例是哪种模式。
       为了能确保本地模式与远程模式自由切换,对Service的实现类有一定的约束:
             1. Service实现类会被继承,不能修饰为 final
             2. 带@MultiRun注解的方法会被重载,不能修饰为 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<>();

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

    public UserInfo findUserInfo(int userid) {
        return users.get(userid);
    }

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

    @MultiRun
    public void register(UserInfo user) {
        this.users.put(user.getUserid(), user);
    }

    @MultiRun(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)
public final class _DynLocalUserService extends UserService {

    @Resource
    private BsonConvert _convert;

    private Transport[] _sameGroupTransports;

    private Transport[] _diffGroupTransports;

    private SncpClient _client;

    private String _selfstring;

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

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

    @SncpDyn(remote = false, index = 0)
    public void _register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
        if (selfrunnable) super.register(user);
        if (_client == null) return;
        if (samerunnable) _client.remote(_convert, _sameGroupTransports, 0, true, false, false, user);
        if (diffrunnable) _client.remote(_convert, _diffGroupTransports, 0, true, true, false, user);
    }

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

    @SncpDyn(remote = false, index = 1)
    public UserInfo _updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
        UserInfo rs = super.updateUsername(userid, username);
        if (_client == null) return null;
        if (samerunnable) _client.remote(_convert, _sameGroupTransports, 1, true, false, false, userid, username);
        if (diffrunnable) _client.remote(_convert, _diffGroupTransports, 1, true, true, false, userid, username);
        return rs;
    }
}
                

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

public @interface MultiRun {

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

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

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

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

      在动态生成的远程模式UserService时会根据不同参数生成相应的方法。若一个Service类没有含@MultiRun注解的方法,那么动态类只会重载public String name()和toString方法。当UserService服务仅需要部署一个进程,由于没有其他等同服务的进程因此在UserService实例化时_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)
public final class _DynRemoteUserService extends UserService {

    @Resource
    private BsonConvert _convert;

    private Transport _transport;

    private SncpClient _client;

    private String _selfstring;

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

    @SncpDyn(remote = false, index = 0)
    public void _register(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, UserInfo user) {
        _client.remote(_convert, _transport, 0, selfrunnable, samerunnable, diffrunnable, user);
    }

    @SncpDyn(remote = false, index = 1)
    public UserInfo _updateUsername(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, int userid, String username) {
        return _client.remote(_convert, _transport, 1, selfrunnable, samerunnable, diffrunnable, userid, username);
    }

    @Override
    public UserInfo findUserInfo(int userid) {
        return _client.remote(_convert, _transport, 2, userid);
    }

    @Override
    public RetResult<UserInfo> login(LoginBean bean) {
        return _client.remote(_convert, _transport, 3, bean);
    }

    @Override
    public void register(UserInfo user) {
        _client.remote(_convert, _transport, 4, user);
    }

    @Override
    public UserInfo updateUsername(int userid, String username) {
        return _client.remote(_convert, _transport, 5, userid, username);
    }
}
                

      由以上代码可以看出来,远程模式Service是根据本地模式Service临时类动态生成的。远程类执行方法时通过SNCP协议将参数序列化并带上当前方法信息传输到远程服务器上,执行完后将结果流反解析并返回, 其流程与WebService类似。与WebService最大的区别在于,远程模式的Service允许修改参数本身的内容。范例如下:

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



/**  DynCallLoginBeanAttribute 的实现  **/
public class DynCallLoginBeanAttribute 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时发现参数带有@DynCall注解的方法,在远程调用返回结果时会进行回调处理。