Solon

与 Spring Cloud 的区别

</> markdown

Solon Cloud 是一套接口定义与配置规范,相当于DDD里的防腐层。可为常见的分布式系统架构提供了一种简单且方便的编程模式,帮助开发人员构建有弹性的、可靠的、协调的应用程序。Solon Cloud 主要由三部份组成:

  • 接口定义与配置规范
  • 实现相关接口定义的各种插件(只要实现相关接口的 Solon 插件,即是 Solon Cloud 插件。)
  • 以及通用客户端。

1、Solon Cloud 套件内容

1.1、接口定义及配置规范清单

接口定义及配置规范,为第三方框架的适配与使用提供了统一模式

功能名称Solon Cloud接口定义配置规范(具体暂略)
分布式注册与发现Solon Cloud DiscoveryCloudDiscoveryServicesolon.cloud.@@.discovery
分布式网关Solon Cloud Gateway--
分布式断路器Solon Cloud BreakerCloudBreakerServicesolon.cloud.@@.breaker
分布式配置Solon Cloud ConfigCloudConfigServicesolon.cloud.@@.config
分布式跟踪Solon Cloud TraceCloudTraceServicesolon.cloud.@@.trace
分布式监控Solon Cloud MetricCloudMetricServicesolon.cloud.@@.metric
分布式事件总线Solon Cloud EventCloudEventServicesolon.cloud.@@.event
分布式任务Solon Cloud JobCloudJobServicesolon.cloud.@@.job
分布式IDSolon Cloud IdCloudIdServicesolon.cloud.@@.id
分布式文件Solon Cloud FileCloudFileServicesolon.cloud.@@.file
分布式名单Solon Cloud ListCloudListServicesolon.cloud.@@.list
分布式锁Solon Cloud LockCloudLockServicesolon.cloud.@@.lock
分布式日志Solon Cloud LoggingCloudLogServicesolon.cloud.@@.log

1.2、通用客户端

通用客户端,提供了所有不同框架的统一使用界面,同时提供了自由手动操控的机制。当然,注解支持不会少!

//手动获取配置(不管背后是哪个配置框架,都是如此)
 Config val1 = CloudClient.config().pull(Solon.cfg().appGroup(), "demo.ds");
 
 //手动生成ID
 long val2 = CloudClient.id().generate();
 
 //手动发布事件(不管背后是哪个消息队列,都是如此)
 CloudClient.event().publish(new Event("demo.user.login","1"));
 
 //等...

2、快速概览

2.1、Hello world

一个普通的 rest api,输出 hello world

@SolonMain
public class DemoApp {
    public static void main(String[] args) {
        Solon.start(DemoApp.class, args, app->{
          app.get("/", c -> c.output("Hello world!"));
        });
    }
}

2.2、使用分布式配置服务

通过本地配置导入需要的分布式配置

solon.app:
  group: "demo"
  name: "demoapp"

solon.cloud.water:
  server: "waterapi:9371"
  config:
    load: "demoapp.yml" #默认加载一个配置

或者,使用 @CloudConfig 注解生成Bean

@Configuration
public class Config {
    @Bean
    public DataSource ds(@CloudConfig("demo.ds") HikariDataSource ds){
        return ds;
    }
}

2.3、使用分布式注册与发现服务实现 Rpc 调用

服务端

//
// 1.所有 remoting = true 的组件,即为 rpc 服务;
// 2.以 uri 的形式提供资源描述,以同时支持 rest api 和 rpc 两种模式(不要有相同的函数名出现)
//
@Mapping("/rpc/")
@Remoting
public class HelloServiceImpl implements HelloService{

    @Override
    public String hello(String name) {
        return null;
    }
}

客户端

@Controller
public class HelloController {
    //注入Rpc服务代理(会自动通过发现服务获取服务集群)
    @NamiClient(name = "hellorpc", path = "/rpc/")
    HelloService helloService;
    
    public String hello(String name){
        return helloService.hello(name);
    }
}

2.4、使用 Slf4j 日志接口,转发到分布式日志记录器

Solon Cloud Log 强调语义标签。通过语议标签,对日志进行固定索引,进而实现更快的查询,或者关联查询。

@Slf4j
public class LogController {
    @Mapping("/")
    public String hello(String name){
        //将元信息固化为 tag0 ... tag4;利于做日志索引
        TagsMDC.tag0("user_"+name); //相当于 MDC.put("tag0", "user_"+name);
        
        log.info("有用户来了");
        
        return name;
    }
}

注:也可以改用 logbacklog4j 日志框架(已适配 cloud 添加器)

2.5、使用分布式事件进行业务水平扩展

Solon Cloud Event 的两个特性说明:

  1. 自守护模式,即失败后不断延时重发确保最终成功。此特性可支持SAGA分布式事务模型。
  2. 多通道模式,即不同消息框架并存。此特性可按业务做不同安排,例如:业务消息用 RabbitMQ,IoT消息用 Mqtt。

例,发布事件

public class EventController {
    public void onUserRegistered(long user_id) {
        //用户注册完成后,发布一个事件
        //
        CloudClient.event().publish(
                new Event("user.registered", String.format("{\"user_id\":%d}", user_id)));
    }
}

订阅与消费事件

@CloudEvent("user.registered")
public class EventListen implements CloudEventHandler {
    @Override
    public boolean handler(Event event) throws Throwable {
        //用户注册完成后,送个金币...
        //
        return true;
    }
}

2.6、使用分布式名单做Ip或Token限制

public class ListController {
    public void hello(Context ctx){
        String ip = IpUtils.getIP(ctx);
        
        if(CloudClient.list().inListOfIp("safelist", ip) == false){
            return;
        }
        
        //业务处理...
    }
}

2.7、使用分布式融断器进行限流控制

添加配置(此配置可通过配置服务,动态更新)

solon.cloud.local:
  breaker:
    main: 100 #qps = 100

通过注解,添加埋点

//此处的注解埋点,名称与配置的断路器名称须一一对应
@CloudBreaker("main")
@Controller
public class BreakerController {
    @Mapping("/breaker")
    public void breaker(){
        
    }
}

2.8、使用分布式跟踪服务获取并传播TraceId

通过MDC传递给 slf4j MDC

String traceId = CloudClient.trace().getTraceId();

MDC.put(CloudClient.trace().HEADER_TRACE_ID_NAME(), traceId);

通过Http Header 传给后Http节点

HttpUtils.url("http://x.x.x.x")
  .headerAdd(CloudClient.trace().HEADER_TRACE_ID_NAME(), traceId).get();

等......(Solon Cloud Log 默认支持 CloudClient.trace() 接口)

2.9、使用简单的分布式监控服务

//监控服务的路径请求性能(后端实现的时候,可以进一步记录超5秒、超1秒的次数;以及典线图)
CloudClient.metric().addMeter("path", path, milliseconds);

//监控服务的路径请求出错次数
CloudClient.metric().addCount("path_err", path, 1);

//监控服务的运行时状态
CloudClient.metric().addGauge("service", "runtime", RuntimeStatus.now());

2.10、使用分布式任务

//注解模式 - Hander 风格(也可以用:Bean method 风格)
@CloudJob("JobHandlerDemo1")
public class JobHandlerDemo1 implements CloudJobHandler {
    @Override
    public void handle(Context ctx) throws Throwable {
        //任务处理
    }
}

//手动模式
CloudClient.job().register("JobHandlerDemo3","",c->{
    //任务处理 
});

2.11、使用分布式ID,生成有序不重复ID

long log_id = CloudClient.id().generate();

一般用于无逻辑性的ID生成,如:日志ID、事务ID、自增ID...

2.12、使用分布式锁,对流量或资源进行控制

if(CloudClient.lock().tryLock("user_"+user_id, 3)){
    //对一个用户尝试3秒的锁;3秒内不充行重复提交
}else{
    //请求太频繁了...
}

2.13、使用分布式文件服务

//使用分布式文件,存储用户扩展信息
CloudClient.file().put("solon/user_"+user_id, new Media("{name:noear}"));

//读取
Media data = CloudClient.file().get("solon/user_"+user_id);

2.14、使用网关,为同一套接口提供不同的输出(Solon 自带)

网关的技术本质,是一个定制了的 Solon Handler。如此理解,新切感会好些:)

//网关1
@Mapping("/api/rest/**")
@Component
public class Gateway1 extends Gateway {
    @Override
    protected void register() {
        //设定默认render
        before(c -> c.attrSet("@render", "@json"));

        //添加服务
        add("user", UserServiceImpl.class, true);

    }
}

//网关2
@Mapping("/api/rpc/**")
@Component
public class Gateway3 extends Gateway {
    @Override
    protected void register() {
        //设定默认render
        before(c -> c.attrSet("@render", "@type_json"));

        //添加服务(不带mapping的函数;需要 remoting = true,才会加载出来)
        add("user", UserServiceImpl.class, true);
    }
}

//网关3(这个比较复杂,和实战性)
@Mapping("/api/v2/app/**")
@Component
public class Gateway3 extends UapiGateway {
    @Override
    protected void register() {
    
        filter(new BreakerFilter()); //融断过滤器

        before(new StartHandler()); //开始计时
        before(new ParamsParseHandler()); //参数解析
        before(new ParamsSignCheckHandler(new Md5Encoder())); //参数签名较验
        before(new ParamsRebuildHandler(new AesDecoder())); //参数重构

        after(new OutputBuildHandler(new AesEncoder())); //输出构建
        after(new OutputSignHandler(new Md5Encoder())); //输出签名
        after(new OutputHandler()); //输出
        after(new EndBeforeLogHandler()); //日志
        after(new EndHandler("v2.api.app")); //结束计时

        addBeans(bw -> "api".equals(bw.tag()));
    }
}